微信公众号开发
微信公众号开发
基本配置
配置内网穿透
使用ngrok进行内网穿透。(这个方法不行,不要用了,可能是域名里面有-的影响)
错误链接:https://developers.weixin.qq.com/community/develop/doc/000684b1e282e0084f6ff3fcc5d000?highLine=-106
ngrok http --domain=moving-enhanced-woodcock.ngrok-free.app 8080
使用natapp进行内网穿透:
Nat官网:https://natapp.cn/
注册好账号,在网站里面配置要映射的端口,然后启动
./natapp -authtoken=你的token
注册一个测试号
接入指南:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
主要是配置接口信息,这里的URL需要填写刚才内网穿透的地址,并且需要返回微信需要的特定信息,如下:
开启端口映射:
这是我提供的后端接口:
/**
* @author houyunfei
*/@RestController
@RequestMapping("/wx")
@Slf4j
public class WxController {
@GetMapping(value = "/hello")
public String hello() {
return "Hello, WeChat!";
}
/**
* 微信接入验证
*
* @param signature 签名
* @param timestamp 时间戳
* @param nonce 随机数
* @param echostr 随机字符串
*/
@GetMapping(value = "/signature")
public String signature(String signature, String timestamp, String nonce, String echostr, HttpServletResponse response) {
log.info("signature: {}, timestamp: {}, nonce: {}, echostr: {}", signature, timestamp, nonce, echostr);
//请讲返回微信服务器的Respose的content-type字段,改成,text/html; charset=utf-8。
//怎么改? answer: 在response header中设置content-type为text/html; charset=utf-8
response.setHeader("Content-Type", "text/html; charset=utf-8");
return echostr;
}
}
此时访问:http://yfjpvb.natappfree.cc/wx/signature 可以访问
配置成功:
微信消息验证
微信想要的验证方式如下:
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
校验的工具类:
public class WxUtils {
public static final String token = "123";
/**
* 校验签名
*
* @param signature 签名
* @param timestamp 时间戳
* @param nonce 随机数
* @return 布尔值
*/
public static boolean checkSignature(String signature, String timestamp, String nonce) {
String checktext = null;
if (null != signature) {
// 对ToKen,timestamp,nonce 按字典排序
String[] paramArr = new String[]{token, timestamp, nonce};
Arrays.sort(paramArr);
// 将排序后的结果拼成一个字符串
String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2]);
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
// 对接后的字符串进行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
checktext = byteToStr(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
// 将加密后的字符串与signature进行对比
return checktext != null ? checktext.equals(signature.toUpperCase()) : false;
}
/**
* 将字节数组转化我16进制字符串
*
* @param byteArrays 字符数组
* @return 字符串
*/
private static String byteToStr(byte[] byteArrays) {
String str = "";
for (int i = 0; i < byteArrays.length; i++) {
str += byteToHexStr(byteArrays[i]);
}
return str;
}
/**
* 将字节转化为十六进制字符串
*
* @param myByte 字节
* @return 字符串
*/
private static String byteToHexStr(byte myByte) {
char[] Digit = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
char[] tampArr = new char[2];
tampArr[0] = Digit[(myByte >>> 4) & 0X0F];
tampArr[1] = Digit[myByte & 0X0F];
String str = new String(tampArr);
return str;
}
}
完善验证逻辑:
/**
* 微信接入验证
*
* @param signature 签名
* @param timestamp 时间戳
* @param nonce 随机数
* @param echostr 随机字符串
*/
@GetMapping(value = "/signature")
public String signature(String signature, String timestamp, String nonce, String echostr) {
log.info("signature: {}, timestamp: {}, nonce: {}, echostr: {}", signature, timestamp, nonce, echostr);
boolean res = WxUtils.checkSignature(signature, timestamp, nonce);
return res ? echostr : "error";
}
接收普通消息
https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html
注意:这里发送普通消息他调用的接口是一开始填的,所以我改成了http://tdikru.natappfree.cc/wx/
@PostMapping("/")
public String post(HttpServletRequest request) {
log.info("接收到消息");
return "success";
}
关注测试公众号,随便发送消息:
接收消息:
dom4j用来转换接收到的消息,xstream用来发送xml类型消息。
<dependency>
<groupId>com.thoughtworks.xstream</groupId>
<artifactId>xstream</artifactId>
<version>1.4.19</version>
</dependency>
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
根据消息进行封装:
封装实体类:
@Data
@XStreamAlias("xml")
public class TextMessage {
@XStreamAlias("ToUserName")
private String toUserName;
@XStreamAlias("FromUserName")
private String fromUserName;
@XStreamAlias("CreateTime")
private Long createTime;
@XStreamAlias("MsgType")
private String ssgType;
@XStreamAlias("Content")
private String content;
@XStreamAlias("MsgId")
private Long ssgId;
@XStreamAlias("MsgDataId")
private String ssgDataId;
@XStreamAlias("Idx")
private String idx;
}
具体的响应用户发送消息的代码:
/**
* 接收微信用户发送来的消息
*
* @param request 请求
* @return 返回消息
* @throws IOException
*/@PostMapping("/")
public String receiveMessage(HttpServletRequest request) throws IOException {
ServletInputStream inputStream = request.getInputStream();
HashMap<String, String> map = new HashMap<>();
SAXReader reader = new SAXReader();
try {
Document document = reader.read(inputStream);
Element root = document.getRootElement();
List<Element> elements = root.elements();
for (Element element : elements) {
map.put(element.getName(), element.getStringValue());
}
} catch (DocumentException e) {
throw new RuntimeException(e);
}
System.out.println(map);
log.info("接收到消息");
String message = getReplyMessage(map);
return message;
}
/**
* 获取回复消息 发送给用户 xml格式
*
* @param map
* @return
*/
private String getReplyMessage(HashMap<String, String> map) {
TextMessage message = new TextMessage();
message.setToUserName(map.get("FromUserName"));
message.setFromUserName(map.get("ToUserName"));
message.setCreateTime(System.currentTimeMillis());
message.setSsgType("text");
message.setContent("小黑子,欢迎关注本公众号!");
// XStream将对象转换为xml字符串
XStream xStream = new XStream();
xStream.processAnnotations(TextMessage.class);
String xml = xStream.toXML(message);
return xml;
}
结果如下:
开发案例:获取同义词
System.out.println(map);
log.info("接收到消息");
String content = map.get("Content");
if (content.contains("同义词")) {
String[] split = content.split(" ");
String word = split[1];
String res = WordUtils.getWord(word);
TextMessage message = new TextMessage();
message.setToUserName(map.get("FromUserName"));
message.setFromUserName(map.get("ToUserName"));
message.setCreateTime(System.currentTimeMillis());
message.setSsgType("text");
message.setContent(res);
// XStream将对象转换为xml字符串
XStream xStream = new XStream();
xStream.processAnnotations(TextMessage.class);
String xml = xStream.toXML(message);
return xml;
}
得到结果:
图文消息回复
private String getReplyNewsMessage(HashMap<String, String> map) {
NewsMessage newsMessage = new NewsMessage();
newsMessage.setToUserName(map.get("FromUserName"));
newsMessage.setFromUserName(map.get("ToUserName"));
newsMessage.setCreateTime(System.currentTimeMillis() / 1000);
newsMessage.setMsgType("news");
newsMessage.setArticleCount(1);
List<Article> articles = new ArrayList<>();
Article article = new Article();
article.setTitle("跟着ikun学Java,最新教程!");
article.setDescription("来自于Ikun学习Java,详细的Java开发教程");
article.setPicUrl("http://yunfei.plus/site_logo.png");
articles.add(article);
newsMessage.setArticles(articles);
XStream xStream = new XStream();
xStream.processAnnotations(NewsMessage.class);
return xStream.toXML(newsMessage);
}
效果如下:
微信功能开发
获取Access_token
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID + "&secret=" + APPSECRET;
// 发送请求
String result = HttpUtil.get((url));
System.out.println(result);
return result;
报错:原因是因为接口不在白名单
配置IP白名单,把刚刚报错的IP输入进去:
拿到结果:
封装结果:
@Data
public class AccessToken {
private String access_token;
private long expires_in;
public void setExpires_in(long expires_in) {
this.expires_in = System.currentTimeMillis() + expires_in * 1000;
}
public boolean isExpired() {
return System.currentTimeMillis() > expires_in;
}
}
获取Token的代码,和单例模式非常像,并发问题?
public static AccessToken token = new AccessToken();
public static void getAccessToken() {
String APPID = "xx";
String APPSECRET = "xx";
String url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=" + APPID + "&secret=" + APPSECRET;
// 发送请求
String result = HttpUtil.get((url));
//{"access_token":"xxx","expires_in":7200}
// 解析
JSONObject jsonObject = JSONUtil.parseObj(result);
String accessToken = jsonObject.getStr("access_token");
long expiresIn = jsonObject.getLong("expires_in");
token.setAccessToken(accessToken);
token.setExpiresIn(expiresIn);
}
public static String getToken() {
if (token == null || token.isExpired()) {
getAccessToken();
}
return token.getAccessToken();
}
自定义菜单
链接:https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
没有认证弄不了