com.github.binarywang
weixin-java-mp
4.1.0
与微信公众号保持心跳的接口,以及自定义回复公众号信息
package com.kenick.mp.controller;
import com.kenick.mp.config.WxMpUtil;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.builder.outxml.TextBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* @description 微信公众号
* @date 2023/12/27
*/
@Controller
@RequestMapping("/mp")
public class WxMpController {
private final static Logger logger = LoggerFactory.getLogger(WxMpController.class);
@Resource
private WxMpUtil wxMpUtil;
/**
* 与微信服务器保持心跳,同时进行内容校验
* 必须为get方式
*
* @param signature 心跳内容签名,微信服务器配置的token+timestamp+nonce进行sha1加密后的密文
* @param timestamp 时间戳字符串
* @param nonce 随机数
* @param echostr 随机内容,回复时要保持一致
* @return 校验通过回复对方发送过来的内容, 否则回复空字符串
*/
@ResponseBody
@RequestMapping("/heartbeat")
public String heartbeat(@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("echostr") String echostr) {
logger.debug("WxMpController.heartbeat,{},{},{},{}", signature, timestamp, nonce, echostr);
try {
if (wxMpUtil.checkSignature("微信配置的token,不是accessToken", timestamp, nonce, signature)) {
return echostr;
}
} catch (Exception e) {
logger.error("公众号心跳异常", e);
}
return "";
}
/**
* 接收微信公众号发送过来的消息,与心跳接口路径一样
* 必须为post方式
*
* @return 无自定义回复返回success, 表示服务器已收到;自定义回复xml格式数据
*/
@ResponseBody
@PostMapping(value = "/heartbeat")
public String receive(HttpServletRequest request) {
try {
WxMpXmlMessage wxMpXmlMessage = WxMpXmlMessage.fromXml(request.getInputStream());
logger.debug("WxMpController.receive,{}", wxMpXmlMessage);
if (wxMpXmlMessage != null && "text".equals(wxMpXmlMessage.getMsgType())) {
String toUserName = wxMpXmlMessage.getFromUser();
String fromUserName = wxMpXmlMessage.getToUser();
String content = wxMpXmlMessage.getContent();
logger.debug("toUserName:{},fromUserName:{},content:{}", toUserName, fromUserName, content);
String replyXml = new TextBuilder().fromUser(fromUserName)
.toUser(toUserName)
.content("replay:" + content + ",\n链接:https://www.baidu.com")
.build().toXml();
logger.debug("回应的xml:{}", replyXml);
return replyXml;
}
} catch (Exception e) {
logger.error("接收消息异常", e);
}
return "success";
}
/**
* 获取微信公众号的accessToken
*
* @param passwd 简单的自定义密码验证
*/
@ResponseBody
@RequestMapping("/getAccessToken")
public String getAccessToken(@RequestParam(value = "passwd", required = false) String passwd) {
logger.debug("WxMpController.getAccessToken");
try {
if ("kenick".equals(passwd)) {//简单的密码验证
return wxMpUtil.getAccessToken();
}
} catch (Exception e) {
logger.error("getAccessToken异常", e);
}
return "";
}
}
微信公众号工具类,注意校验心跳消息时使用的token是在微信平台上配置的token,而不是accessToken。
package com.kenick.mp.config;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.util.Arrays;
/**
* @description 微信公众号工具类
* @date 2023/12/27
*/
@Component
public class WxMpUtil {
private final static Logger logger = LoggerFactory.getLogger(WxMpUtil.class);
//微信公众号id
@Value("${wechat.mpAppId}")
private String mpAppId;
//微信公众号密码
@Value("${wechat.mpAppSecret}")
private String mpAppSecret;
//微信公众号工具服务
private static WxMpService wxMpService;
//获取mp服务
public WxMpService getMpService() {
if (wxMpService != null) {
return wxMpService;
}
WxMpDefaultConfigImpl wxMpConfigStorage = new WxMpDefaultConfigImpl();
wxMpConfigStorage.setAppId(mpAppId);
wxMpConfigStorage.setSecret(mpAppSecret);
WxMpService wxMpService = new WxMpServiceImpl();
wxMpService.setWxMpConfigStorage(wxMpConfigStorage);
return wxMpService;
}
//通过WxMpService工具获取accessToken
public String getAccessToken() {
try {
return getMpService().getAccessToken();
} catch (Exception e) {
logger.error("获取accessToken异常", e);
}
return "";
}
//校验心跳消息签名
public boolean checkSignature(String token, String timestamp, String nonce, String signature) {
try {
return signature.equals(gen(token, timestamp, nonce));
} catch (Exception e) {
logger.error("校验签名异常", e);
}
return false;
}
//sha1加密
public String gen(String... arr) {
if (StringUtils.isAnyEmpty(arr)) {
throw new IllegalArgumentException("非法请求参数,有部分参数为空 : " + Arrays.toString(arr));
} else {
Arrays.sort(arr);
StringBuilder sb = new StringBuilder();
String[] var2 = arr;
int var3 = arr.length;
for (int var4 = 0; var4 < var3; ++var4) {
String a = var2[var4];
sb.append(a);
}
return DigestUtils.sha1Hex(sb.toString());
}
}
}
微信公众号的appid配置,放在spring配置文件中。
wechat.mpAppId=xxxx
wechat.mpAppSecret=xxxx
注意事项:
1.调用获取accessToken时,注意程序运行的ip地址要放进白名单
2.个人订阅号和未经过微信认证的不能使用官方的自定义菜单,也不能通过接口配置菜单
3.提交服务器配置时,会验证心跳接口是否可用,不可用无法提交
4.被动接收公众号发送过来的消息接口,使用的就是心跳接口路径,只是请求方式为post,接收的数据为xml格式,奇奇怪怪
5.注意配置好服务器后,需要启用配置,自定义回复才会生效