前置条件
- 已认证公众号/已申请微信测试接口
公众号->设置与开发->基本配置中获取appid与appsecret
微信测试接口同理 - 自己的服务器资源
- 需要微信的接口,获取access_token、生成带参数二维码、获取二维码图片(GET请求(请使用https协议)https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET)
- 建立数据库绑定表(对服务器中的用户与公众号的绑定)
maven插件
下方代码用到的XmlUtil与BeanUtil需要该插件
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.25</version>
</dependency>
具体代码
三个基础类
绑定类
public class WeChatBindModel extends BaseModel<String> {
/**
*网站用户标识
*/
@Column(name = "user_id",columnDefinition = "VARCHAR")
private String userId;
/**
*公众号用户标识
*/
@Id
@Column(name = "open_id",columnDefinition = "VARCHAR")
private String openId;
/**
*创建时间
*/
@Column(name = "create_time",columnDefinition = "VARCHAR")
private String createTime;
}
消息类
@Data
public class WechatNotifyRequestVO {
private String ToUserName;
private String FromUserName;
private String CreateTime;
private String MsgType;
private String MsgId;
private String Event;
private String EventKey;
private String Ticket;
private String Content;
}
method枚举类
public enum RequestMethodEnum {
/** get*/
GET(1,"GET"),
/** post*/
POST(2,"POST");
private final Integer code;
private final String desc;
RequestMethodEnum(Integer id, String name){
this.code = id;
this.desc = name;
}
public Integer getCode() {
return code;
}
public String getDesc() {
return desc;
}
}
微信接入服务器的接口
接口
@RequestMapping(value = "")
public void checkSignature(@RequestBody String xml, HttpServletRequest request, HttpServletResponse response) throws IOException {
String method = request.getMethod();
// get方法,验证微信配置是否成功
if (RequestMethodEnum.GET.getDesc().equals(method)) {
String signature = request.getParameter("signature");/// 微信加密签名
String timestamp = request.getParameter("timestamp");/// 时间戳
String nonce = request.getParameter("nonce"); /// 随机数
String echostr = request.getParameter("echostr"); // 随机字符串
boolean bool = TokenValidator.validate(signature, timestamp, nonce);
if(bool){
response.getWriter().print(echostr);
}
} else {
// post方法,用来接收微信返回的消息
wechatbindService.notifyMsg(xml, response);
}}
验证类TokenValidator
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class TokenValidator {
private static final String TOKEN = "FBuVsYCoPQxbPAFnrwZGRxTIWD";
public static boolean validate(String signature, String timestamp, String nonce) {
String[] arr = new String[]{TOKEN, timestamp, nonce};
Arrays.sort(arr);
StringBuilder sb = new StringBuilder();
for (String s : arr) {
sb.append(s);
} String str = sb.toString();
String result = "";
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
byte[] digest = md.digest(str.getBytes());
result = bytesToHex(digest);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} return result.equals(signature);
}
private static String bytesToHex(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
String hex = Integer.toHexString(b & 0xFF);
if (hex.length() == 1) {
sb.append("0");
}
sb.append(hex);
} return sb.toString();
}
}
消息转译与绑定
主要思路
- 生成带参数二维码中可以传入场景值数字或者字符串,假设当前服务器的用户标识为字符串,那么可以将对应的场景值传入字符串,也就是userId(在scene_str中传入id即可)。
- 生成的二维码在手机扫码后,会对服务器进行传值,将场景值与openid同时给到服务器进行绑定(在EventKey中获取场景值)
传参样例
{"expire_seconds": 604800, "action_name": "QR_STR_SCENE", "action_info": {"scene": {"scene_str": "d30dadd6-7941-4759-8bca-87eae8e10437"}}}
返回样例
<xml><ToUserName><![CDATA[开发者ID]]></ToUserName>
<FromUserName><![CDATA[openid]]></FromUserName>
<CreateTime>1708652664</CreateTime>
<MsgType><![CDATA[event]]></MsgType>
<Event><![CDATA[subscribe]]></Event>
<EventKey><![CDATA[d30dadd6-7941-4759-8bca-87eae8e10437]]></EventKey>
</xml>
主要方法
@Transactional(rollbackFor = Exception.class)
@Override
public void notifyMsg(String xml, HttpServletResponse response) throws IOException {
log.info("后台接收到公众号发来的数据是:\n{}", xml);
// 把微信发送给后端的xml消息转成WechatNotifyRequestVO对象
Map<String, Object> map = XmlUtil.xmlToMap(xml);
WechatNotifyRequestVO notify = BeanUtil.fillBeanWithMap(map, new WechatNotifyRequestVO(), true);
// 绑定用户信息,不存在则插入,不匹配则更新
log.info("notify \n{}",notify);
boolean isBind = handleBindInfo(notify);
// 事件类型,subscribe表示订阅,unsubscribe表示取消订阅, null表示用户发送消息过来
String event = notify.getEvent();
String content = "";
if (StringUtils.equals(event, "subscribe")) {
log.info("用户关注公众号了....");
// 欢迎语句
returnResponse(response, notify, "[Party]终于等到你!欢迎你的关注!");
}
if (StringUtils.equals(event, "unsubscribe")) {
log.info("用户取消公众号的订阅了....");
// 删除所绑定的用户信息
}
if (!StringUtils.isBlank(notify.getContent())) {
log.info("用户发送消息过来,消息内容是:" + notify.getContent());
//回复,可以回复表情,蓝色可点击链接文字等
//收到消息自动回复
returnResponse(response, notify, "/:sun已经收到您的消息~");
}
if (isBind) {
returnResponse(response, notify, "绑定成功");
}}
返回微信的方法
public void returnResponse(HttpServletResponse response, WechatNotifyRequestVO wechatNotifyRequestVO, String content) throws IOException {
response.setHeader("Content-type", "application/xml");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(getXmlReturnMsg(wechatNotifyRequestVO.getFromUserName(),wechatNotifyRequestVO.getToUserName(),System.currentTimeMillis() / 1000, content));
}
对返回的xml封装成一个方法
public String getXmlReturnMsg(String toUser,String fromUser,Long createTime,String content) {
return "<xml>\n" +
" <ToUserName><![CDATA["+toUser+"]]></ToUserName>\n" +
" <FromUserName><![CDATA["+fromUser+"]]></FromUserName>\n" +
" <CreateTime>"+createTime+"</CreateTime>\n" +
" <MsgType><![CDATA[text]]></MsgType>\n" +
" <Content><![CDATA["+content+"]]></Content>\n" +
"</xml>";
}
对绑定信息的处理
public boolean handleBindInfo(WechatNotifyRequestVO wechatNotifyRequestVO) {
// 判断是否是绑定信息
if (wechatNotifyRequestVO.getEvent().equals("SCAN") && wechatNotifyRequestVO.getEventKey() != null) {
return saveBindInfo(wechatNotifyRequestVO);
} else if (wechatNotifyRequestVO.getEvent().equals("subscribe") && wechatNotifyRequestVO.getEventKey() != null) {
return saveBindInfo(wechatNotifyRequestVO);
} return false;
}
public boolean saveBindInfo(WechatNotifyRequestVO wechatNotifyRequestVO) {
// 获取二维码参数
String userId = wechatNotifyRequestVO.getEventKey();
// 如果event是SCAN则进行正则匹配
String pattern = "qrscene_(\\d+)";
Pattern regexPattern = Pattern.compile(pattern);
Matcher matcher = regexPattern.matcher(userId);
if (matcher.find()) {
userId = matcher.group(1);
}
// 获取openid
String openId = wechatNotifyRequestVO.getFromUserName();
// 获取时间
String time = wechatNotifyRequestVO.getCreateTime();
WeChatBindModel model = new WeChatBindModel(userId, openId, time);
WeChatBindModel existingModel = wechatbindRepo.findByOpenId(model.getOpenId());
if (existingModel != null && existingModel.getOpenId().equals(openId)) {
return false;
} else if (existingModel != null) {
existingModel.setUserId(model.getUserId());
existingModel.setCreateTime(model.getCreateTime());
wechatbindRepo.save(existingModel);
return true;
} else {
wechatbindRepo.save(model);
return true;
}}