微信公众号开发(接收公众号消息)

微信公众号开发(接收公众号消息)

场景:工作中的一个小机能,想要通过公众号实现微信的openid与公司员数据绑定,方便后续给员工推送一些消息
(公司员工向公众号发送指定数据,包含员工的工号,后台解析出OpenId和员工的工号,写入到数据库)
参考开发文档: https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html

一、配置公众号

这里使用的自己的公众号,公众号的申请就不再记录
在这里插入图片描述

URL   			配置自己的域名(这里使用的花生壳)
Token		    随意指定即可
EncodingAESKey  随机生成
消息加解密方式    这里并没有必要(后续如果有用到再记录)

二、微信公众号链接认证

在这里插入图片描述

三、定义接口和实体类

  1. 接口
@Api(tags = "微信公众号处理类", description = "微信公众号消息处理类")
@FeignClient(value = "XXXX")
public interface WxMessageApi {
	/**
	* 公众号设置中设定接收消息的地址时,会发送一个GET请求进行验证
	* signature 微信加密签名
	* timestamp 时间戳
	* nonce 随机数
	* echostr 随机字符串
	*/
    @ApiOperation(value = "微信公众号请求校验")
    @RequestMapping(value = "/access", method = RequestMethod.GET)
    void wxInterface(@RequestParam("signature") String signature,@RequestParam("timestamp") String timestamp,
                       @RequestParam("nonce") String nonce, @RequestParam("echostr") String echostr,HttpServletResponse response) throws IOException;

	/**
	* 接收公众号消息的接口(同上面的接口一样,不同的是POST请求)
	* 
	* 由于微信发送的消息是xml形式,所以这里就需要解析xml进行处理
	* 
	* produces 指定处理请求的提交内容类型
	* consumes 指定返回值类型和返回值编码
	*/
    @ApiOperation(value = "微信公众号消息处理")
    @RequestMapping(value = "/access", method = RequestMethod.POST,
            consumes = "text/xml; charset=UTF-8")
    String parseMessage(HttpServletRequest request, HttpServletResponse response);

}
  1. 消息实体基类(包含公共的属性)
    这里使用了 @XmlElement @XmlTransient @XmlRootElement 等标签结合 consumes 属性,Controller返回时会将其转换成XML返回
/**
 * 消息基类
 * @author fyang
 * @version V1.0
 * @date 2020/9/2 16:34
 */
public class BaseMessage {
    /**
     * 开发者微信号
     */
    private String toUserName;
    /**
     * 发送方帐号(一个OpenID)
     */
    private String fromUserName;
    /**
     * 消息创建时间 (整型)
     */
    private Long createTime;
    /**
     * 消息类型,文本为text
     */
    private String msgType;
    /**
     *消息id,64位整型
     */
    private Long msgId;

    @XmlElement(name = "ToUserName")
    public String getToUserName() {
        return toUserName;
    }

    public void setToUserName(String toUserName) {
        this.toUserName = toUserName;
    }

    @XmlElement(name = "FromUserName")
    public String getFromUserName() {
        return fromUserName;
    }

    public void setFromUserName(String fromUserName) {
        this.fromUserName = fromUserName;
    }

    @XmlElement(name = "CreateTime")
    public Long getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Long createTime) {
        this.createTime = createTime;
    }

    @XmlElement(name = "MsgType")
    public String getMsgType() {
        return msgType;
    }

    public void setMsgType(String msgType) {
        this.msgType = msgType;
    }

    @XmlElement(name = "MsgId")
    public Long getMsgId() {
        return msgId;
    }

    public void setMsgId(Long msgId) {
        this.msgId = msgId;
    }
}
  1. 文本消息实体类
    其他类型实体类类似的写法
/**
 * 普通文本消息
 * @author fyang
 * @version V1.0
 * @date 2020/9/2 16:42
 */
@XmlRootElement(name = "xml")
public class TextMessage extends BaseMessage{
    /**
     * 文本消息内容
     */
    private String content;

    @XmlElement(name = "Content")
    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

四、验证的实现

  1. 验证接口接收验证的数据,并返回echostr:
@RequestMapping("/wechat")
@RestController
public class WxMessageApiImpl implements WxMessageApi{
	// 这里的token和公众号中配置的一样,我是将其写在了SpringBoot的配置文件中
    @Value("${wx.token}")
    private String token;
	
	// 用于处理消息的Server
	@Autowired
    private WxMessageService wxMessageService;

    /**
     * 微信公众号链接校验
     *
     * signature 微信加密签名
     * timestamp 时间戳
     * nonce 随机数
     * echostr 随机字符串
     */
     @RequestMapping(value = "/access", method = RequestMethod.GET)
    public void wxInterface(@RequestParam("signature") String signature,@RequestParam("timestamp") String timestamp,
                              @RequestParam("nonce") String nonce, @RequestParam("echostr") String echostr,HttpServletResponse response) throws IOException {
        PrintWriter writer = response.getWriter();
        // 验证
        if (StringUtils.isNoneBlank(signature, timestamp, nonce)
                && WeChatUtil.checkSignature(signature, timestamp, nonce, token)){
            writer.print(echostr);
        }
        writer.close();
        writer = null;
    }
}
  • 小记
    这里wxInterface方法一定不能直接返回字符串,直接返回字符串会一直出现token认证失败
    下面的写法是错误的
@RequestMapping(value = "/access", method = RequestMethod.GET)
    public String wxInterface(@RequestParam("signature") String signature,@RequestParam("timestamp") String timestamp,
                              @RequestParam("nonce") String nonce, @RequestParam("echostr") String echostr,HttpServletResponse response) {
        PrintWriter writer = response.getWriter();
        // 验证
        if (StringUtils.isNoneBlank(signature, timestamp, nonce)
                && WeChatUtil.checkSignature(signature, timestamp, nonce, token)){
            return echostr;
        }
		return null;
    }
  1. 工具类,实现连接数据的验证:
// 1)将token、timestamp、nonce三个参数进行字典序排序
// 2)将三个参数字符串拼接成一个字符串进行sha1加密 
// 3)开发者获得加密后的字符串可与signature对比
public class WeChatUtil {
	/**
     * 验证签名
     * @param signature 微信加密签名
     * @param timestamp 时间戳
     * @param nonce 随机数
     * @return
     */
    public static boolean checkSignature(String signature, String timestamp, String nonce, String token) {
        String[] arr = new String[] { token, timestamp, nonce };
        // 1)将token、timestamp、nonce三个参数进行字典序排序
        Arrays.sort(arr);
        // 2)将三个参数字符串拼接成一个字符串 
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < arr.length; i++) {
            content.append(arr[i]);
        }
        MessageDigest md = null;
        String tmpStr = null;
        try {
            md = MessageDigest.getInstance("SHA-1");
            // 2)进行 sha1 加密
            byte[] digest = md.digest(content.toString().getBytes());
            // 转换成字符串
            tmpStr = byteToStr(digest);
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        content = null;
        // 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
        return tmpStr != null ? tmpStr.equals(signature.toUpperCase()) : false;
    }

    /**
     * 将字节数组转换为十六进制字符串
     * @param byteArray
     * @return
     */
    private static String byteToStr(byte[] byteArray) {
        String strDigest = "";
        for (int i = 0; i < byteArray.length; i++) {
            strDigest += byteToHexStr(byteArray[i]);
        }
        return strDigest;
    }

    /**
     * 将字节转换为十六进制字符串
     * @param mByte
     * @return
     */
    private static String byteToHexStr(byte mByte) {
        char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' };
        char[] tempArr = new char[2];
        tempArr[0] = Digit[(mByte >>> 4) & 0X0F];
        tempArr[1] = Digit[mByte & 0X0F];
        String s = new String(tempArr);
        return s;
    }

/****************************上面是用来做链接验证的************************************/
/****************************下面是用来处理消息的************************************/
	/**
     * 解析微信发来的请求(XML)
     *
     * @param request
     * @return
     * @throws Exception
     */
    public static Map<String, String> xml2Map(HttpServletRequest request) {
        // 将解析结果存储在HashMap中
        Map<String, String> map = Maps.newHashMap();
        // 从request中取得输入流
        InputStream inputStream = null;
        try {
            inputStream = request.getInputStream();
            // 读取输入流
            SAXReader reader = new SAXReader();
            Document document = reader.read(inputStream);
            // 得到xml根元素
            Element root = document.getRootElement();
            // 得到根元素的所有子节点
            List<Element> elementList = root.elements();
            // 遍历所有子节点
            for (Element e : elementList){
                map.put(e.getName(), e.getText());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(inputStream!=null){
                // 释放资源
                try {
                    inputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                inputStream = null;
            }
        }
        return map;
    }

	/**
	*  将java对象转换成xml方法(空属性会被过滤)
	* 	
	* */
	public static String beanToXml(Object obj, Class<?> load) {
	   String xmlStr = null;
	   try {
	       JAXBContext context = JAXBContext.newInstance(load);
	       Marshaller marshaller = context.createMarshaller();
	       // 去掉生成xml的默认报文头
	       marshaller.setProperty(Marshaller.JAXB_FRAGMENT, true);
	       StringWriter writer = new StringWriter();
	       marshaller.marshal(obj, writer);
	       xmlStr = writer.toString();
	   } catch (JAXBException e) {
	       e.printStackTrace();
	   }
	   return xmlStr;
	}
}

五、接收消息方法

在这里插入图片描述

  1. 文本消息格式:
<xml>
  <ToUserName><![CDATA[toUser]]></ToUserName>
  <FromUserName><![CDATA[fromUser]]></FromUserName>
  <CreateTime>1348831860</CreateTime>
  <MsgType><![CDATA[text]]></MsgType>
  <Content><![CDATA[this is a test]]></Content>
  <MsgId>1234567890123456</MsgId>
</xml>
ToUserName			开发者微信号(也就是我们公众号的id)
FromUserName  		发送方帐号(一个OpenID,唯一的标识,指明消息是谁发来的)
CreateTime   		消息创建时间 (整型)
MsgType   			消息类型,文本为text
Content  			文本消息内容
MsgId  				消息id,64位整型
  1. 在WxMessageApiImpl中追加接收消息的接口
/**
* 接收公众号消息的接口(同上面的接口一样,不同的是POST请求)
* 
* 由于微信发送的消息是xml形式,所以这里就需要解析xml进行处理
* 
* consumes 指定处理请求的提交内容类型
* produces 指定返回值类型和返回值编码
*/
@Override
@RequestMapping(value = "/access", method = RequestMethod.POST,
        consumes = "text/xml; charset=UTF-8")
public String parseMessage(HttpServletRequest request, HttpServletResponse response) {
    //处理消息,获取返回数据
    TextMessage textMessage = wxMessageService.wxMessageHandleCoreService(WeChatUtil.xml2Map(request));
    // 将数据转换成xml格式返回
    return textMessage == null ? null : WeChatUtil.beanToXml(textMessage, TextMessage.class);
}

六、处理消息数据

/**
 * 解析公众号的消息,将员工的openid插入到数据库
 * @param msgMap 微信文本消息数据
 * @return
 */
@Override
public TextMessage wxMessageHandleCoreService(Map<String,String> msgMap) {
    TextMessage result = new TextMessage();
    Optional.ofNullable(msgMap)
            // 判断数据有否存在
            //.filter(map -> StringUtils.isNoneBlank(map.get("FromUserName"), map.get("ToUserName") , map.get("MsgType"), map.get("Content")))
            // 判断消息是text文本消息
            // 这里是一个枚举类,其实就是 text 字符串
            .filter(map -> WxMessageTypeEnum.REQ_MESSAGE_TYPE_TEXT.getValue().equals(map.get("MsgType")))
            // 判断文本消息是否是指定的格式  XXXX# + 员工工号
            .filter(map -> Pattern.compile("^XXXX#.+$").matcher(map.get("Content")).matches())
            // 截取工号
            .map(map -> map.get("Content").substring(5))
            .ifPresent(jobNo -> {
                // 根据员工工号查询员工信息
                UserDTO emp = userApi.getUserByJobId(jobNo, Long.parseLong("1"));
                if (Optional.ofNullable(emp).isPresent() && Optional.ofNullable(emp.getId()).isPresent()) {
                    // 将openid写入到数据库
                    Boolean updateResult = userApi.updateOpenId(emp.getId().longValue()
                            , emp.getCompanyId()
                            , msgMap.get("FromUserName"));
                    result.setContent((updateResult!=null && updateResult) ? "绑定成功":"绑定失败,检查员工编号是否正确");
                }else{
                    result.setContent("绑定失败,检查员工编号是否正确");
                }
                // 回传消息,所以讲fromuser和toUser交换
                result.setFromUserName(msgMap.get("ToUserName"));
                result.setToUserName(msgMap.get("FromUserName"));
                // text
                result.setMsgType(WxMessageTypeEnum.REQ_MESSAGE_TYPE_TEXT.getValue());
                result.setCreateTime(new Date().getTime());
            });
    return StringUtils.isBlank(result.getContent())?null:result;
}

小白一枚,可能代码逻辑/风格不够完善,有不足之处请大神多多指出,感激不尽,谢谢!!!

  • 踩到的坑
  1. 返回给微信的数据如果缺少东西会出现异常
    (例如最早没有返回消息的时候,我的返回内容是空的)
  2. 这里的bean转成xml 会将null 数据过滤掉(注意)
  3. 验证的方法一定要将返回值写入到response中,不能直接返回
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
引用\[1\]:源代码中使用了OpenCV库来实现循环读取图片的功能。在代码中,首先定义了一个常量NUM,表示要读取的图片个数。然后使用while循环,从1到NUM依次读取图片。具体的读取图片的代码被隐藏在了注释中,可以通过搜索公众号"qxsf321"并关注后回复0012获取完整的代码。\[1\] 引用\[2\]:另一种循环读取图片的方法是将图片的名字放在一个txt文件中,每一行是一幅图像的名字。代码中使用了OpenCV库来实现这个功能。首先定义了一个字符数组filename,用于存储图片的路径和文件名。然后使用for循环,从1到80依次读取图片。具体的读取图片的代码被隐藏在了注释中,可以通过搜索公众号"qxsf321"并关注后回复0012获取完整的代码。\[2\] 引用\[3\]:第三种方法也是将图片的名字放在一个txt文件中,每一行是一幅图像的名字。代码中使用了OpenCV库来实现这个功能。首先定义了一个ifstream对象fin,用于打开存储图片名字的txt文件。然后使用while循环,从txt文件中逐行读取图片名字,并进行相应的处理。具体的处理代码被隐藏在了注释中,可以通过搜索公众号"qxsf321"并关注后回复0012获取完整的代码。\[3\] 综上所述,以上是三种使用OpenCV循环读取图片的方法。具体的代码实现可以通过搜索公众号"qxsf321"并关注后回复0012获取。 #### 引用[.reference_title] - *1* *3* [0012-用OpenCV批量读取图片的三种方法](https://blog.csdn.net/lehuoziyuan/article/details/84064056)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [OpenCV中实现循环读图&&将当前图片写到某一个文件夹](https://blog.csdn.net/wwwsssZheRen/article/details/76408023)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值