视频地址https://www.bilibili.com/video/BV1Av4y1V77b
网站https://natapp.cn/
使用步骤页面https://natapp.cn/article/natapp_newbie
1.注册账号
2.购买免费/付费隧道(付费优惠码B9D625E4)
微信公众号开发,隧道协议选web
3.启动本地web项目,确保http://127.0.0.1可以访问
注意项目端口号,微信公众号限定http://必须使用80端口,https://必须使用443端口。
4.下载本地版本的natapp客户端。
例如我的是win10,64位,下载解压得到natapp.exe。
5.在同文件夹下新建配置文件config.ini,内容如下,修改authtoken为你的隧道的authtoken:
[default]
authtoken=123 #对应一条隧道的authtoken
clienttoken= #对应客户端的clienttoken,将会忽略authtoken,若无请留空,
log=none #log 日志文件,可指定本地文件, none=不做记录,stdout=直接屏幕输出 ,默认为none
loglevel=ERROR #日志等级 DEBUG, INFO, WARNING, ERROR 默认为 DEBUG
http_proxy= #代理设置 如 http://10.123.10.10:3128 非代理上网用户请务必留空
6.点击natapp.exe运行(这个必须上面都完成了再执行)
打开的界面找到Forwarding http://x79ada.natappfree.cc -> 127.0.0.1:80
访问http://x79ada.natappfree.cc看是否成功
公众号-开发者工具-公众平台测试账号-开通:获得appID和appsecret
maven依赖:
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.11.1</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.72</version>
</dependency>
Controller类
package com.wxTest.controller;
import com.alibaba.fastjson.JSONObject;
import com.wxTest.vo.TextMessage;
import com.thoughtworks.xstream.XStream;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.web.bind.annotation.*;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.security.MessageDigest;
import java.util.*;
@Slf4j
@RestController
@RequestMapping("/")
public class WxController {
@GetMapping("/")
public String test(String signature,String timestamp,String nonce,String echostr) {
// signature微信加密签名,signature结合了开发者填写的token参数和请求中的timestamp参数、nonce参数。
// timestamp时间戳
// nonce随机数
// echostr 随机字符串
log.info("signature="+signature);
log.info("timestamp="+timestamp);
log.info("nonce="+nonce);
log.info("echostr="+echostr);
if(StringUtils.isEmpty(signature)||StringUtils.isEmpty(timestamp)||StringUtils.isEmpty(nonce)||StringUtils.isEmpty(echostr)){
return null;
}
String token="123";//微信接口配置信息配置的Token
List<String> list = Arrays.asList(token, timestamp, nonce);
Collections.sort(list);
StringBuilder stringBuilder = new StringBuilder();
for(String s:list){
stringBuilder.append(s);
}
try {
MessageDigest instance = MessageDigest.getInstance("sha1");
//sha1加密
byte[] digest = instance.digest(stringBuilder.toString().getBytes());
StringBuilder sum = new StringBuilder();
for(byte b:digest){
sum.append(Integer.toHexString((b>>4)&15));
sum.append(Integer.toHexString(b&15));
}
log.info("sum="+sum);
if(signature.equals(sum.toString())){
return echostr;
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
@PostMapping("/")
public String receiveMsg(HttpServletRequest request){
HashMap<String, String> map = new HashMap<>();
try {
ServletInputStream inputStream = request.getInputStream();
SAXReader reader = new SAXReader();
Document document = reader.read(inputStream);
Element root = document.getRootElement();
List<Element> elements = root.elements();
for(Element e:elements){
map.put(e.getName(),e.getStringValue());
}
} catch (Exception e) {
e.printStackTrace();
}
log.info(map.toString());
StringBuilder xml=new StringBuilder();
xml.append("<xml><ToUserName><![CDATA[");
xml.append(map.get("FromUserName"));
xml.append("]]></ToUserName><FromUserName><![CDATA[");
xml.append(map.get("ToUserName"));
xml.append("]]></FromUserName><CreateTime>");
xml.append(new Date().getTime());
xml.append("</CreateTime><MsgType><![CDATA[text]]></MsgType><Content><![CDATA[");
if("image".equals(map.get("MsgType"))){
xml.append("收到用户图片:"+map.get("PicUrl"));
}else{
xml.append("收到用户消息:"+map.get("Content"));
}
xml.append("]]></Content></xml>");
return getReplyMessage(map);
}
public static String getReplyMessage(HashMap<String, String> map) {
TextMessage textMessage = new TextMessage();
textMessage.setToUserName(map.get("FromUserName"));
textMessage.setFromUserName(map.get("ToUserName"));
textMessage.setMsgType("text");
if("image".equals(map.get("MsgType"))){
textMessage.setContent("收到用户图片:"+map.get("PicUrl"));
}else{
textMessage.setContent("收到用户消息:"+map.get("Content"));
}
textMessage.setCreateTime(System.currentTimeMillis()/1000);
XStream xStream=new XStream();
xStream.processAnnotations(TextMessage.class);
return xStream.toXML(textMessage);
}
public static String doGet(String href) {
try {
// 创建URL对象
URL url = new URL(href);
// 创建HttpURLConnection对象
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
// 设置请求方法为GET
httpConn.setRequestMethod("GET");
int responseCode = httpConn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
// 读取响应体内容
InputStream inputStream = httpConn.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
inputStream.close();
return response.toString();
} else {
System.out.println("GET请求失败,状态码:" + responseCode);
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static String doPost(String href,String jsonInputString) {
try {
System.out.println("href:"+href);
System.out.println("请求参数:"+jsonInputString);
// 创建URL对象
URL url = new URL(href);
// 创建HttpURLConnection对象
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
// 设置请求方法为GET
httpConn.setRequestMethod("POST");
// 设置请求头
httpConn.setRequestProperty("Content-Type", "application/json; utf-8");
httpConn.setRequestProperty("Accept", "application/json");
// 设置是否向httpUrlConnection输出,因为这个是post请求,所以肯定会有输出,设置为true,
// 设置是否使用缓存
httpConn.setUseCaches(false);
// 允许写入数据
httpConn.setAllowUserInteraction(false);
// 设置连接超时时间
httpConn.setConnectTimeout(10000);
// 设置读取超时时间
httpConn.setReadTimeout(10000);
// 发送POST请求必须设置如下两行
httpConn.setDoOutput(true);
httpConn.setDoInput(true);
// 获取输出流
OutputStream os = httpConn.getOutputStream();
byte[] input = jsonInputString.getBytes("utf-8");
os.write(input, 0, input.length);
// 读取响应
BufferedReader br = new BufferedReader(new InputStreamReader(httpConn.getInputStream(), "utf-8"));
StringBuilder response = new StringBuilder();
String responseLine = null;
while ((responseLine = br.readLine()) != null) {
response.append(responseLine.trim());
}
// 关闭连接
httpConn.disconnect();
br.close();
os.close();
return response.toString();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public static void main(String[] args) {
//获取 Access token
String appid="你的公众号appid";
String secret="你的公众号secret";
String tokenStr = doGet("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="+appid+"&secret="+secret);
System.out.println(tokenStr);
JSONObject jsonObject = JSONObject.parseObject(tokenStr);
String access_token = jsonObject.getString("access_token");
//更新公众号菜单
List<Map<String,Object>> button=new ArrayList<>();
List<Map<String,Object>> sub_button=new ArrayList<>();
Map m=new HashMap();
m.put("type","click");
m.put("name","今日歌曲");
m.put("key","V1001_TODAY_MUSIC");
button.add(m);
m=new HashMap();
m.put("type","view");
m.put("name","搜搜");
m.put("url","http://www.soso.com/");
sub_button.add(m);
m=new HashMap();
m.put("type","view");
m.put("name","百度");
m.put("url","http://www.baidu.com/");
sub_button.add(m);
m=new HashMap();
m.put("type","click");
m.put("name","赞一个");
m.put("key","V1001_GOOD");
sub_button.add(m);
m=new HashMap();
m.put("name","菜单");
m.put("sub_button",sub_button);
button.add(m);
String url="https://api.weixin.qq.com/cgi-bin/menu/create?access_token="+access_token;
JSONObject json =new JSONObject();
json.put("button",button);
String resultMenu = doPost(url, json.toJSONString());
System.out.println("菜单更新结果:"+resultMenu);
}
}
TextMessage实体类
package com.wxTest.vo;
import com.thoughtworks.xstream.annotations.XStreamAlias;
@XStreamAlias("xml")
public class TextMessage {
// <ToUserName><![CDATA[toUser]]></ToUserName>
// <FromUserName><![CDATA[fromUser]]></FromUserName>
// <CreateTime>12345678</CreateTime>
// <MsgType><![CDATA[text]]></MsgType>
// <Content><![CDATA[你好]]></Content>
@XStreamAlias("ToUserName")
private String toUserName;
@XStreamAlias("FromUserName")
private String fromUserName;
@XStreamAlias("CreateTime")
private Long createTime;
@XStreamAlias("MsgType")
private String msgType;
@XStreamAlias("Content")
private String content;
//自己补充get和set方法,或引用lombok插件
}
第四步 接口联调与测试
1.公众号-开发者工具-公众平台测试账号-接口配置信息
点击提交看是否成功,项目是否打印日志
2.用微信关注测试微信公众号,发送文字和图片看是否有返回结果
3.执行WxController的main方法看公众号是否出现菜单