在微信里有这样一个公众号【华为运动健康】,当点击最新排行的时候,公众号就会发送今天最新的运动步数给你。如下图:

这里有两种格式的消息

1、有头像框,有聊天框——普通消息

2、消息有样式、颜色等——模板消息

本篇文章主要介绍的就是如何让微信公众号自动回复消息

参考文档链接:https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Passive_user_reply_message.html

开发之前,给大家介绍一个weixin-java-tools

简单介绍一下:

1、微信各种平台的api它都集成了,直接调用就行,不用自己维护微信官方url、各种常量、accessToken等;

2、微信那艹蛋的xml返回数据格式可以直接使用对象进行构建,它已经做好了封装;

3、WxMpMessageRouter非常好用,将微信的各种事件进行分发处理,示例如下:

maven引入:

<!-- weixin-java-mp SDK框架-->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>3.6.0</version>
</dependency>

github地址如下:https://github.com/binarywang/weixin-java-mp-demo

wiki文档如下:https://github.com/Wechat-Group/WxJava/wiki

SpringBoot-WeChat示例参考项目:https://github.com/Thinkingcao/SpringBootLearning/tree/master/springboot-wechat

一、登录微信公众平台,启用服务器配置

1、登录https://mp.weixin.qq.com,选择公众号进入,在设置与开发—基本配置

2、记下开发者AppId和开发者秘钥,IP白名单待会再说

3、点击修改配置,配置服务器地址,输入令牌生成加解密秘钥,选择安全模式点击保存

4、上面配置的服务器地址,就是微信回调我们服务器的回调地址,由于微信的数据格式是xml,所以我们需要在接口上进行一些处理

代码如下:

@RequestMapping(value = "handleWxEvent", method = RequestMethod.POST, produces = "application/xml; charset=UTF-8")
@ResponseBody
public String handleWxEvent(@RequestBody String requestBody,
@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("openid") String openid,
@RequestParam(name = "encrypt_type", required = false) String encType,
@RequestParam(name = "msg_signature", required = false) String msgSignature){
//微信加密签名
String signature = request.getParameter("signature");
//时间戳
String timestamp = request.getParameter("timestamp");
//随机数
String nonce = request.getParameter("nonce");
//随机字符串
String echostr = request.getParameter("echostr");
//接入验证
if (checkSignature(signature, timestamp, nonce, gzhToken)) {
log.info("微信公众号校验完成echostr:[{}]", echostr);
try {
         //这样写可以防止服务器给返回的字符串加上双引号,导致验证失败
response.getWriter().print(echostr);
} catch (IOException e) {
log.error("输出返回值异常", e);
}
return;
}
throw new DscException(ErrorCodeEnum.SYSTEM_EXCEPTION, "解析签名发生异常");
} /**
* 校验签名
*
* @param signature 签名
* @param timestamp 时间戳
* @param nonce 随机数
* @return 布尔值
*/
public static boolean checkSignature(String signature, String timestamp, String nonce, String token) {
String checkText = null;
if (null != signature) {
//对ToKen,timestamp,nonce 按字典排序
String[] paramArr = new String[]{token, timestamp, nonce};
Arrays.sort(paramArr);
//将排序后的结果拼成一个字符串
String content = paramArr[0].concat(paramArr[1]).concat(paramArr[2])
try {
MessageDigest md = MessageDigest.getInstance("SHA-1");
//对接后的字符串进行sha1加密
byte[] digest = md.digest(content.toString().getBytes());
checkText = byteToStr(digest);
} catch (Exception e) {
log.error("解码发生异常", e);
}
}
//将加密后的字符串与signature进行对比
return checkText != null ? checkText.equals(signature.toUpperCase()) : false;
}

二、配置公众号信息,实例化WxMpService、WxMpMessageRouter

WxMpService功能很多如验证消息的确来自微信服务器、获取access_token、进行相应的公众号切换...

WxMpMessageRouter:微信消息路由器,通过代码化的配置,把来自微信的消息交给handler处理

说明:

1. 配置路由规则时要按照从细到粗的原则,否则可能消息可能会被提前处理

2. 默认情况下消息只会被处理一次,除非使用 WxMpMessageRouterRule.next()

3. 规则的结束必须用WxMpMessageRouterRule.end()或者WxMpMessageRouterRule.next(),否则不会生效

1、在application.properties或者application.yml配置好公众号的相关信息

2、wxmp包如下

WxMpConfig.class

import com.google.common.collect.Maps;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.config.WxMpConfigStorage;
import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import java.util.Map; import static me.chanjar.weixin.common.api.WxConsts.EventType.SUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.EventType.UNSUBSCRIBE;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType.EVENT;
import static me.chanjar.weixin.mp.constant.WxMpEventConstants.CustomerService.*;
import static me.chanjar.weixin.mp.constant.WxMpEventConstants.POI_CHECK_NOTIFY;

@Configuration
public class WxMpConfig { /**
* 设置微信公众号的appid
*/
@Value("${gzh.appId}")
private String appId; /**
* 设置微信公众号的app secret
*/
@Value("${gzh.appSecret}")
private String secret; /**
* 设置微信公众号的token
*/
@Value("${gzh.token}")
private String token; /**
* 设置微信公众号的EncodingAESKey
*/
@Value("${gzh.aesKey}")
private String aesKey;
/**
* 日志处理
*/
@Autowired
private LogHandler logHandler;
@Autowired
private NullHandler nullHandler;
@Autowired
private KfSessionHandler kfSessionHandler;
@Autowired
private StoreCheckNotifyHandler storeCheckNotifyHandler;
@Autowired
private LocationHandler locationHandler;
@Autowired
private MenuHandler menuHandler;
@Autowired
private MsgHandler msgHandler;
@Autowired
private UnsubscribeHandler unsubscribeHandler;
@Autowired
private SubscribeHandler subscribeHandler;
@Autowired
private ScanHandler scanHandler;
@Autowired
private TextMsgHandler textMsgHandler;
@Autowired
private ImgHandler imgHandler; @Bean("wxMpService")
public WxMpService wxMpService() {
WxMpDefaultConfigImpl wxMpDefaultConfig = new WxMpDefaultConfigImpl();
wxMpDefaultConfig.setAppId(appId);
wxMpDefaultConfig.setSecret(secret);
wxMpDefaultConfig.setToken(token);
wxMpDefaultConfig.setAesKey(aesKey);
Map<String, WxMpConfigStorage> configMap = Maps.newHashMap();
configMap.put(appId,wxMpDefaultConfig);
WxMpService service = new WxMpServiceImpl();
service.setMultiConfigStorages(configMap);
return service;
}
@Bean
public WxMpMessageRouter messageRouter(WxMpService wxMpService) {
final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService); // 记录所有事件的日志 (异步执行)
newRouter.rule().handler(this.logHandler).next(); // 接收客服会话管理事件
newRouter.rule().async(false).msgType(EVENT).event(KF_CREATE_SESSION)
.handler(this.kfSessionHandler).end();
newRouter.rule().async(false).msgType(EVENT).event(KF_CLOSE_SESSION)
.handler(this.kfSessionHandler).end();
newRouter.rule().async(false).msgType(EVENT).event(KF_SWITCH_SESSION)
.handler(this.kfSessionHandler).end(); // 门店审核事件
newRouter.rule().async(false).msgType(EVENT).event(POI_CHECK_NOTIFY).handler(this.storeCheckNotifyHandler).end(); // 自定义菜单事件
newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.CLICK).handler(this.menuHandler).end(); // 点击菜单连接事件
newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.VIEW).handler(this.nullHandler).end(); // 关注事件
newRouter.rule().async(false).msgType(EVENT).event(SUBSCRIBE).handler(this.subscribeHandler).end(); // 取消关注事件
newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.unsubscribeHandler).end(); // 上报地理位置事件
newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.LOCATION).handler(this.locationHandler).end(); // 接收地理位置消息
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION).handler(this.locationHandler).end(); // 扫码事件
newRouter.rule().async(false).msgType(EVENT).event(WxConsts.EventType.SCAN).handler(this.scanHandler).end(); // 文本消息处理
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.TEXT).handler(this.textMsgHandler).end(); // 图片消息处理
newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.IMAGE).handler(this.imgHandler).end(); // 默认
newRouter.rule().async(false).handler(this.msgHandler).end(); return newRouter;
} }

3、各个handler如下

需要注意的是:

(1)消息回复的类型有文本、图片、图文、语音、视频、音乐等;

(2)图片、视频等消息都需要先上传到素材库并获取该素材的mediaId;

(3)回复的消息还可以是小程序卡片,但需要保证当前小程序必须绑定在该公众号下

(4)假如服务器无法保证在五秒内处理并回复,也必须回复,这样微信服务器才不会对此作任何处理,并且不会发起重试(这种情况下,可以使用客服消息接口进行异步回复),否则,将出现严重的错误提示

三、服务器处理微信回调事件,并进行分发

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*; @Slf4j
@AllArgsConstructor
@RestControllerpublic class WxPortalController {
private final WxMpService wxService;
private final WxMpMessageRouter messageRouter; @PostMapping(value="handleWxEvent",produces = "application/xml; charset=UTF-8")
public String handleWxEvent(@PathVariable String appid,
@RequestBody String requestBody,
@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce,
@RequestParam("openid") String openid,
@RequestParam(name = "encrypt_type", required = false) String encType,
@RequestParam(name = "msg_signature", required = false) String msgSignature) {
log.info("\n接收微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}],"
+ " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ",
openid, signature, encType, msgSignature, timestamp, nonce, requestBody);
if (!wxService.checkSignature(timestamp, nonce, signature)) {
throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
} String out = null;
if (encType == null) {
// 明文传输的消息
WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody);
WxMpXmlOutMessage outMessage = this.route(inMessage);
if (outMessage == null) {
return "";
} out = outMessage.toXml();
} else if ("aes".equalsIgnoreCase(encType)) {
// aes加密的消息
WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, wxService.getWxMpConfigStorage(),
timestamp, nonce, msgSignature);
log.debug("\n消息解密后内容为:\n{} ", inMessage.toString());
WxMpXmlOutMessage outMessage = this.route(inMessage);
if (outMessage == null) {
return "";
}
out = outMessage.toEncryptedXml(wxService.getWxMpConfigStorage());
} log.debug("\n组装回复信息:{}", out);
return out;
} private WxMpXmlOutMessage route(WxMpXmlMessage message) {
try {
return this.messageRouter.route(message);
} catch (Exception e) {
log.error("路由消息时出现异常!", e);
} return null;
} }

四、构建回复消息

1、关注公众号回复欢迎用户

这里我们可以看到,关注【乡荣】公众号后,不仅回复了信息,而且是两条。实现思路:

(1)第一条消息,当用户订阅公众号时会调用SubscribeHandler,该handler会返回一个WxMpXmlOutMessage,这个就是默认返回值

(2)第二条消息,我们可以使用【微信客服】发送,直接使用下面的方法进行发送

wxService.getKefuService().sendKefuMessage(WxMpKefuMessage
.TEXT()
.toUser(wxMessage.getFromUser())
.content("客服回复内容")
.build()); 

2、回复小程序卡片消息

回复小程序卡片这个功能在微信公众平台是找不到的,这个功能只能使用代码实现,这里就体现出代码的强大了。

如上图所示:当我们输入“最新直播”时,小程序会返回当前最新直播的小程序卡片,点击即可进入小程序。实现思路:

(1)用户输入的是文本消息,处理应该在TextMsgHandler中

(2)默认返回的WxMpXmlOutMessage是没有小程序卡片这个类型的,所以这里还得借助客服消息

(3)小程序卡片需要一个封面,且这个封面图必须上传到微信素材库后获取mediaId,上传代码如下:

//获取素材库相关实现
WxMpMaterialService materialService = wpService.getMaterialService();
//上传临时素材
WxMediaUploadResult wxMediaUploadResult = materialService.mediaUpload(WxConsts.KefuMsgType.IMAGE, ".jpg", inputStream);
//获取素材ID
String mediaId = wxMediaUploadResult.getMediaId();

(4)构建小程序卡片信息

 wpService.getKefuService().sendKefuMessage(WxMpKefuMessage
.MINIPROGRAMPAGE()
.title("小程序卡片标题")
.toUser(wxMessage.getFromUser())
.thumbMediaId(mediaId)
.appId("小程序ID")
.pagePath("卡片要跳转的路径").build());

我的小程序之旅八:基于weixin-java-mp实现微信公众号被动回复消息的更多相关文章

  1. 在微信框架模块中,基于Vue&Element前端的微信公众号和企业微信的用户绑定

    在一个和微信相关的业务管理系统,我们有时候需要和用户的微信账号信息进行绑定,如对公众号.企业微信等账号绑定特定的系统用户,可以进行扫码登录.微信信息发送等操作,用户的绑定主要就是记录公众号用户的ope ...

  2. 基于IdentityServer的系统对接微信公众号

    业务需求 公司有两个业务系统,A和B,AB用户之间属于多对一的关系,数据库里面也就是两张表,A表有个外键指向B.现在需要实现以下几个功能. A用户扫描B的二维码,填写相关的注册信息,注册完成之后自动属 ...

  3. 基于NodeJS微信公众号

    最近重新研究了微信公众号的高级接口,原来也利用C#或JAVA写过微信公众号,主要是消息的基础接口. 由于当时不知道微信公众号可以申请测试公众号,微信测试公众号基本上没有任何限制,对于开发来说是一个不错 ...

  4. weiphp 微信公众号用程序来设置指定内容消息回复业务逻辑操作

    微信公众号机器人回复设置 在公众号插件里面的Robot- Model- weixinAddonModel.php里面的 reply设置 reply($dataArr,$keywordArr) 解析方法 ...

  5. 微信小程序实战篇:基于wxcharts.js绘制移动报表

    前言 微信小程序图表插件(wx-charts)是基于canvas绘制,体积小巧,支持图表类型饼图.线图.柱状图 .区域图等图表图形绘制,目前wx-charts是微信小程序图表插件中比较强大好使的一个. ...

  6. CRMEB系统就是集客户关系管理+营销电商系统,能够真正帮助企业基于微信公众号、小程序实现会员管理、数据分析,精准营销的电子商务管理系统。可满足企业新零售、批发、分销、等各种业务需求。

    **可以快速二次开发的开源小程序商城系统源码**源码开源地址:https://github.crmeb.net/u/LXT 项目介绍: CRMEB系统就是集客户关系管理+营销电商系统,能够真正帮助企业 ...

  7. 微信小程序把玩(八)view组件

    原文:微信小程序把玩(八)view组件 刚看到这个效果的时候还真是和ReactNative的效果一致,属性也基本的一样. view这个组件就是一个视图组件使用起来非常简单. 主要属性: flex-di ...

  8. 使用Appium 测试微信小程序和微信公众号方法

    由于腾讯系QQ.微信等都是基于腾讯自研X5内核,不是google原生webview,需要打开TBS内核Inspector调试功能才能用Chrome浏览器查看页面元素,并实现Appium自动化测试微信小 ...

  9. Senparc.Weixin.MP SDK 微信公众平台开发教程(二十一):在小程序中使用 WebSocket (.NET Core)

    本文将介绍如何在 .NET Core 环境下,借助 SignalR 在小程序内使用 WebSocket.关于 WebSocket 和 SignalR 的基础理论知识不在这里展开,已经有足够的参考资料, ...

  10. CabloyJS的微信API对接模块:当前支持微信公众号和微信小程序

    Cabloy-微信是什么 Cabloy-微信是基于CabloyJS全栈业务开发框架开发的微信接口模块,当前整合了微信公众号和微信小程序的接口,达到开箱即用的使用效果.在Cabloy-微信的基础上,可以 ...

随机推荐

  1. [转帖]深入浅出分析LSM树(日志结构合并树)

    https://zhuanlan.zhihu.com/p/415799237 ​ 目录 收起 零.前言 一.LSM树数据结构定义 二.插入操作 三.删除操作 四.修改操作 五.查询操作 六.合并操作 ...

  2. [转帖]br备份时排除某个库

    https://tidb.net/blog/2a88149e?utm_source=tidb-community&utm_medium=referral&utm_campaign=re ...

  3. [转帖]深入理解mysql-第六章 mysql存储引擎InnoDB的索引-B+树索引

    一.引入索引 在没有索引的情况下,不论是根据主键列或者其他列的值进行查找,由于我们并不能快速的定位到记录所在的页,所以只能从第一个页沿着双向链表一直往下找,因为要遍历所有的数据页,时间复杂度就是O(n ...

  4. [转帖]安全技术和iptables防火墙

    目录 安全技术 Netfilter 防火墙工具介绍 iptables firewalld nftables iptables的组成概述 netfilter与iptables关系 iptables的四表 ...

  5. 从好玩到好用:程序员用AI提效的那些事儿

    本片内容是[AI思维空间]ChatGPT纵横编程世界,点亮智慧火花的续作,主要记录组内开发小伙伴儿们在开发过程中的实际应用案例,记录典型案例,尽量不要和其他人重复,以解决开发过程中的实际问题为主,设计 ...

  6. 【杂题,树】【Uoj】Uoj618 【JOISC2021】聚会 2

    2023.7.3 Problem Link 给定一棵 \(n\) 个点的树,对于一个点集 \(S\),定义 \(f(u,S)\) 为 \(\min_u \sum_{v\in S} \mathrm{di ...

  7. 【JS逆向百例】某音乐网分离式 webpack 非 IIFE 改写实战

    关注微信公众号:K哥爬虫,QQ交流群:808574309,持续分享爬虫进阶.JS/安卓逆向等技术干货! 声明 本文章中所有内容仅供学习交流,抓包内容.敏感网址.数据接口均已做脱敏处理,严禁用于商业用途 ...

  8. P5963 [BalticOI ?] Card 卡牌游戏【来源请求】

    [rt](https://www.luogu.com.cn/problem/P5963)------------## part1### 题意简述给你 $n$ 张纸牌,每张纸牌有两个面.将 $n$ 张纸 ...

  9. 开源IM项目OpenIM单聊及万人群压测报告

    单聊压测结论: 华为云主机s3一台:8核16G内存,网络带宽10Mb,普通磁盘(非SSD) 同时在线及压测客户端数量:1万 每秒钟发送消息量:2300条: 从发送到对方接收平均消息延时:5秒 群聊压测 ...

  10. lua开发和调试环境

    Lua开发环境搭建 Lua官网提供源码下载需要自己编译,Lua官网:https://www.lua.org/ftp/ lua for windows.exe(占二十多MB那个) 目前在网络上没有找到 ...