微信公众号绑定后台服务器已有账号
前置条件
- 已认证公众号/已申请微信测试接口
公众号->设置与开发->基本配置中获取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; }}
参考链接