# 前置条件

  1. 已认证公众号 / 已申请微信测试接口
    公众号 -> 设置与开发 -> 基本配置中获取 appid 与 appsecret
    微信测试接口同理
  2. 自己的服务器资源
  3. 需要微信的接口,获取 access_token生成带参数二维码、获取二维码图片(GET 请求(请使用 https 协议)https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET)
  4. 建立数据库绑定表(对服务器中的用户与公众号的绑定)

# 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();  
    }  
}

# 消息转译与绑定

# 主要思路

  1. 生成带参数二维码中可以传入场景值数字或者字符串,假设当前服务器的用户标识为字符串,那么可以将对应的场景值传入字符串,也就是 userId (在 scene_str 中传入 id 即可)。
  2. 生成的二维码在手机扫码后,会对服务器进行传值,将场景值与 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.getUserId().equals(userId)) {  
        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;  
    }}

参考链接