java对接微信公众号API,实现扫码关注公众号,触发多条消息回复

一、准备工作

1. 依赖库

这里使用的是binarywang的Wxjava 库,源码地址:https://github.com/binarywang/WxJava。截止发稿前最新版本是4.6.7.B,我采用的是4.5.0版本。

<dependency><groupId>com.github.binarywang</groupId><artifactId>weixin-java-mp</artifactId><version>4.5.0</version>
</dependency>

除了这个还使用到了fastjson2lombok的库,可自行添加

2. 公众号的相关配置
  • 2.1 注册并认证微信公众号平台:https://mp.weixin.qq.com/
    在这里插入图片描述
  • 2.2 开启相关接口的权限
    在这里插入图片描述
    在这里插入图片描述
  • 2.3 获取AppIdAppSecret,以及配置IP白名单服务器的回调地址
    在这里插入图片描述
3. 微信相关的API文档

以下API用的比较多,可以收藏一下。

  • 2.1 获取 Access token
    https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Get_access_token.html

  • 2.2 获取 Stable Access token
    https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/getStableAccessToken.html

  • 2.3 生成带参数的二维码
    https://developers.weixin.qq.com/doc/offiaccount/Account_Management/Generating_a_Parametric_QR_Code.html

  • 2.4 接收事件推送
    https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html

  • 2.5 客服接口-发消息
    https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Service_Center_messages.html#7

  • 2.6 新增永久素材
    https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/Adding_Permanent_Assets.html

二、编码过程

1. 生成带参数的二维码
// 创建二维码ticket
public static String WX_QRCODE_TICKET_URL = "https://api.weixin.qq.com/cgi-bin/qrcode/create?access_token=%s";
// 通过ticket换取二维码
public static String WX_SHOW_QRCODE_URL = "https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=%s";/**
* 生成带参数的二维码
* @param type 二维码类型,1-永久二维码、其他值-临时二维码
* @param scene 自定义参数,API支持字符串和整形,这里使用的是字符串
*/
public String generateQrCode(String type, String scene) throws Exception {if (StringUtils.isBlank(scene)) {throw new Exception("参数不正确");}// API地址String url = String.format(WX_QRCODE_TICKET_URL, getStableAccessToken());JSONObject object = new JSONObject();if ("1".equals(type)) {//永久二维码object.put("action_name", "QR_LIMIT_STR_SCENE"); // 字符串,整形使用 QR_LIMIT_SCENEJSONObject json = JSONObject.of("scene", JSONObject.of("scene_str", scene));// 字符串,整形使用 scene_idobject.put("action_info", json);} else {object.put("expire_seconds", 60 * 60 * 24); // 24小时object.put("action_name", "QR_STR_SCENE"); // 字符串,整形使用 QR_SCENEJSONObject json = JSONObject.of("scene", JSONObject.of("scene_str", scene)); // 字符串,整形使用 scene_idobject.put("action_info", json);        }log.info("object = {}", object);HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);HttpEntity<String> httpEntity = new HttpEntity<>(object.toString(), headers);ResponseEntity<String> responseEntity = restTemplate.postForEntity(url, httpEntity, String.class);if (!responseEntity.getStatusCode().is2xxSuccessful()) {throw new Exception("创建二维码ticket失败");}JSONObject responseBody = JSON.parseObject(responseEntity.getBody());log.info("responseBody = {}", responseBody);if (responseBody == null || responseBody.getInteger("errcode") != null) {throw new Exception(responseBody.getString("errmsg"));}String ticket = responseBody.getString("ticket");return String.format(WX_SHOW_QRCODE_URL, ticket);
}

成功后返回如https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=gQEa8DwAAAAxxxZm0wS05EMVoAAgRWby1nAwSAUQEA的字符串,通过浏览器访问就可以得到二维码了。

2. 获取 Stable Access token

获取tonken需要用到appId和appSecret

public static String WX_STABLE_TOKEN_BASE_URL = "https://api.weixin.qq.com/cgi-bin/stable_token";
// 微信公众号 stable accessToken, 2小时过期
public static final String MP_STABLE_ACCESS_TOKEN ="mp_stable_access_token";
// 公众号配置
@Value("${weixin.mp.appId}")
private String mpAppId;
@Value("${weixin.mp.secret}")
private String mpSecret;/**
* 获取 Stable Access token
* 避免频繁调用API,浪费调用次数,token需要做一下缓存,这里是存到redis,2小时过期
*/
private String getStableAccessToken() throws Exception {String accessToken = redisService.getCacheObject(MP_STABLE_ACCESS_TOKEN);if (accessToken == null) {JSONObject object = new JSONObject();object.put("grant_type", "client_credential"); // 固定object.put("appid", mpAppId);object.put("secret", mpSecret);String payload = object.toString();HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);HttpEntity<String> httpEntity = new HttpEntity<>(payload, headers);ResponseEntity<String> responseEntity = restTemplate.postForEntity(WX_STABLE_TOKEN_BASE_URL, httpEntity, String.class);JSONObject responseBody = JSON.parseObject(responseEntity.getBody());if (responseBody == null || responseBody.getInteger("errcode") != null) {throw new Exception("getStableAccessToken fail:" + responseBody.toJSONString());}accessToken = responseBody.getString("access_token");if (null == accessToken) {throw new Exception("getStableAccessToken fail:accessToken is null");}redisService.setCacheObject(MP_STABLE_ACCESS_TOKEN, accessToken, 7200L, TimeUnit.SECONDS);}return accessToken;
}
3. 微信公众号路由配置相关
import lombok.AllArgsConstructor;
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.impl.WxMpDefaultConfigImpl;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;import java.util.List;
import java.util.stream.Collectors;import static me.chanjar.weixin.common.api.WxConsts.EventType;
import static me.chanjar.weixin.common.api.WxConsts.XmlMsgType;
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;/*** 微信公众号路由配置*/
@AllArgsConstructor
@Configuration
@EnableConfigurationProperties(WxMpProperties.class)
public class WxMpConfig {private final MsgHandler msgHandler;private final SubscribeHandler subscribeHandler;private final ScanHandler scanHandler;private final WxMpProperties properties;@Beanpublic WxMpService wxMpService() {final List<WxMpProperties.MpConfig> configs = this.properties.getConfigs();if (configs == null) {throw new RuntimeException("微信公众号配置错误!");}WxMpService service = new WxMpServiceImpl();service.setMultiConfigStorages(configs.stream().map(a -> {WxMpDefaultConfigImpl configStorage = new WxMpDefaultConfigImpl();configStorage.setAppId(a.getAppId());configStorage.setSecret(a.getSecret());configStorage.setToken(a.getToken());configStorage.setAesKey(a.getAesKey());return configStorage;}).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o)));return service;}@Beanpublic WxMpMessageRouter messageRouter(WxMpService wxMpService) {final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);// 记录所有事件的日志 (异步执行)newRouter.rule().handler(this.logHandler).next();//其他事件,此处省略... ...// 关注事件newRouter.rule().async(false).msgType(EVENT).event(EventType.SUBSCRIBE).handler(this.subscribeHandler).end();// 扫码事件newRouter.rule().async(false).msgType(EVENT).event(EventType.SCAN).handler(this.scanHandler).end();// 默认newRouter.rule().async(false).handler(this.msgHandler).end();return newRouter;}
}

微信公众号配置 WxMpProperties

@Data
@ConfigurationProperties(prefix = "weixin.mp")
public class WxMpProperties {//多个公众号配置信息private List<MpConfig> configs;@Datapublic static class MpConfig {//设置微信公众号的appidprivate String appId;//设置微信公众号的app secretprivate String secret;//设置微信公众号的tokenprivate String token;//设置微信公众号的EncodingAESKeyprivate String aesKey;}
}
4. 公众号事件处理
4.1 事件类型定义
public enum TriggerWay {TALK,SUBSCRIBE,BUTTON,
}
4.2 默认事件处理
@Component
public class MsgHandler extends AbstractHandler {@Resourceprivate WechatMessageMapper wechatMessageMapper;@Overridepublic WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,Map<String, Object> context, WxMpService weixinService,WxSessionManager sessionManager) {String msgType = wxMessage.getMsgType();String msg = wxMessage.getContent();              //从数据库查询对应的关键字配置WechatMessageConfig config = wechatMessageMapper.selectByParam(TriggerWay.TALK.name(), msg);if (config != null) {return build(config, wxMessage);}return null;}private WxMpXmlOutMessage build(WechatMessageConfig config, WxMpXmlMessage wxMessage) {switch (config.getType()) {case "NEWS":return buildNews(config, wxMessage);case "TEXT":return buildText(config, wxMessage);case "IMAGE":return buildImage(config, wxMessage);default:break;}return null;}/*** 发送图文消息** @param config* @param wxMessage* @return*/public static WxMpXmlOutMessage buildNews(WechatMessageConfig config, WxMpXmlMessage wxMessage) {WxMpXmlOutNewsMessage.Item item = new WxMpXmlOutNewsMessage.Item();item.setTitle(config.getMessage());item.setUrl(config.getUrl());item.setPicUrl(config.getPicUrl());return WxMpXmlOutMessage.NEWS().addArticle(item).fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).build();}/*** 发送图片** @param config* @param wxMessage* @return*/public WxMpXmlOutMessage buildImage(WechatMessageConfig config, WxMpXmlMessage wxMessage) {//调用上传图片素材接口,此方法省略WxMediaUploadResult res = upload(config.getPicUrl());return res == null ? res : WxMpXmlOutMessage.IMAGE().mediaId(res.getMediaId()).fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).build();}/*** 发送文本消息** @param config* @param wxMessage* @return*/public static WxMpXmlOutMessage buildText(WechatMessageConfig config, WxMpXmlMessage wxMessage) {return buildText(config.getMessage(), wxMessage);}/*** 发送文本消息** @param msg* @param wxMessage* @return*/public static WxMpXmlOutMessage buildText(String msg, WxMpXmlMessage wxMessage) {return WxMpXmlOutMessage.TEXT().content(msg).fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser()).build();}
}
4.3 扫码事件处理
@Component
public class ScanHandler extends AbstractHandler {@Resourceprivate WechatMessageMapper wechatMessageMapper;// 生成二维码时给的参数private final String SCENE_STR = "xxxx";@Overridepublic WxMpXmlOutMessage handle(WxMpXmlMessage message,Map<String, Object> context, WxMpService weixinService,WxSessionManager sessionManager) throws WxErrorException {// 扫码事件处理String eventKey = message.getEventKey();logger.info("扫码用户 FromUser:{},eventKey:{}", message.getFromUser(), eventKey);if (SCENE_STR.equals(eventKey)) {//从数据库查询对应的关键字配置WechatMessageConfig config = wechatMessageMapper.selectByParam(TriggerWay.TALK.name(), SCENE_STR);if (config != null) {//触发推送模板消息return MsgHandler.buildText(config, message);}}return null;}
}
4.4 公众号关注事件处理

扫码关注与搜索公众号关注的需求有点不同,扫码关注除了需要返回默认信息外,还需要根据二维码中的关键字返回特定内容,所以需要触发多条消息。微信的API是不支持这个操作的,只能换一种思路,可以借助客服消息发送接口。
代码如下:

@Component
public class SubscribeHandler extends AbstractHandler {@Resourceprivate WechatMessageMapper wechatMessageMapper;@Resourceprivate ScanAsynHandler scanAsynHandler;@Overridepublic WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,Map<String, Object> context, WxMpService weixinService,WxSessionManager sessionManager) throws WxErrorException {try {String fromUser = wxMessage.getFromUser();// 获取微信用户基本信息,lang: zh_CN 简体(默认)WxMpUser userWxInfo = weixinService.getUserService().userInfo(fromUser, null);if (userWxInfo != null && userWxInfo.getSubscribe()) {String unionId = userWxInfo.getUnionId();String openId = userWxInfo.getOpenId();String sceneStr = userWxInfo.getQrSceneStr();this.logger.info("新关注用户 FromUser:{},UnionId:{},OpenId:{},sceneStr:{} ", fromUser, unionId, openId, sceneStr);//TODO 保存到数据库... ...//如果是扫码跳转的,关注后会自动携带sceneStr参数if (SCENE_STR.equals(sceneStr)) {//此时需要触发异步推送第二条模板消息scanAsynHandler.handleSpecial(wxMessage);}//触发新用户订阅时推送的默认模板消息WechatMessageConfig config = wechatMessageMapper.selectByParam(TriggerWay.SUBSCRIBE.name(), "SUBSCRIBE");if (config != null) {return MsgHandler.buildText(config, wxMessage);}}} catch (WxErrorException e) {int errorCode = e.getError().getErrorCode();this.logger.error("用户关注公众号失败:状态码 {},失败原因:{}", errorCode, e.getError().getErrorMsg());}return null;}
}

ScanAsynHandler部分

//客服发送消息接口
public final static String WX_CUSTOM_BASE_URL = "https://api.weixin.qq.com/cgi-bin/message/custom/send?access_token=%s";// @Async的用法自行研究
// 触发异步推送第二条模板消息
@Async("taskExecutor")
protected Boolean handleSpecial(WxMpXmlMessage wxMessage) {   WechatMessageConfig config = wechatMessageMapper.selectByParam(TriggerWay.TALK.name(), SCENE_STR);if (config != null) {//调用客服消息发送接口String customerUrl = String.format(WX_CUSTOM_BASE_URL, getStableAccessToken());//还支持其他形式的消息内容,可查阅文档JSONObject object = JSONObject.of("touser", wxMessage.getFromUser(), "msgtype", "text", "text", JSONObject.of("content", config.getMessage()));log.info("发送模板消息:{}", object);HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_JSON);HttpEntity<String> httpEntity = new HttpEntity<>(object.toString(), headers);ResponseEntity<JSONObject> responseEntity = restTemplate.postForEntity(customerUrl, httpEntity, JSONObject.class);JSONObject body = responseEntity.getBody();log.info("模板消息发送结果:{}", body);return body.containsKey("errcode") && body.getInteger("errcode") == 0;}return false;
}

其他事件处理此处省略了,可以根据自身的业务来完成。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/885765.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一文学习Android中的Property

在 Android 系统中&#xff0c;Property 是一种全局的键值对存储系统&#xff0c;允许不同组件和进程间以轻量级的方式进行数据传递。它主要用于系统配置、状态标识等场景&#xff0c;使得不同进程能够通过属性的设置或获取来通信。property 的核心特性是快速、高效&#xff0…

网络编程——Python简单TCP通信功能代码实践

这里写目录标题 Python简单TCP通信功能代码实践阅读本博客前需准备的几个问题1. 网络通信的机制是什么&#xff1f;2. 什么是python进行网络编程&#xff1f;3. IP地址和端口是什么&#xff1f; 一个简单的TCP通信功能示例&#xff1a;client端.pysever端.pyPYCHARM运行结果 Py…

qt QGesture详解

1、概述 QGesture 是 Qt 框架中用于处理多点触控和手势识别的类。它封装了用户输入的手势信息&#xff0c;如触摸、滑动、捏合、旋转等&#xff0c;使得开发者能够轻松地实现复杂的手势交互功能。QGesture 类本身是一个抽象基类&#xff0c;不能直接实例化&#xff0c;而是通过…

基于C语言——跑得快扑克牌游戏开发指南

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 ✨特色专栏&#xff1a…

7+纯生信,单细胞识别细胞marker+100种机器学习组合建模,机器学习组合建模取代单独lasso回归势在必行!

影响因子&#xff1a;7.3 研究概述&#xff1a; 皮肤黑色素瘤&#xff08;SKCM&#xff09;是所有皮肤恶性肿瘤中最具侵袭性的类型。本研究从GEO数据库下载单细胞RNA测序&#xff08;scRNA-seq&#xff09;数据集&#xff0c;根据原始研究中定义的细胞标记重新注释各种免疫细胞…

CatBoost中的排序提升(Ordered Boosting)

排序提升&#xff08;Ordered Boosting&#xff09;是 CatBoost 的核心创新之一&#xff0c;用于解决梯度提升决策树&#xff08;GBDT&#xff09;在训练过程中可能产生的信息泄漏&#xff08;Information Leakage&#xff09;和预测偏移&#xff08;Prediction Shift&#xff…

丹摩征文活动 | 0基础带你上手经典目标检测模型 Faster-Rcnn

文章目录 &#x1f34b;1 引言&#x1f34b;2 平台优势&#x1f34b;3 丹摩平台服务器配置教程&#x1f34b;4 实操案例&#xff08; Faster-rcnn 项目&#xff09;&#x1f34b;4.1 文件处理&#x1f34b;4.2 环境配置&#x1f34b;4.3 训练模型&#x1f34b;4.4 数据保存并导…

【GPTs】Get Simpsonized:一键变身趣味辛普森角色

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | GPTs应用实例 文章目录 &#x1f4af;GPTs指令&#x1f4af;前言&#x1f4af;Get Simpsonized主要功能适用场景优点缺点使用方式 &#x1f4af;小结 &#x1f4af;GPTs指令 中文翻译&#xff1a; 指令保护和安全规则&…

【每日 C/C++ 问题】

一、什么是 c 命名空间 有什么作用&#xff1f; C命名空间是一种用于解决全局作用域&#xff08;如全局变量名或函数名&#xff09;的冲突问题的机制&#xff0c;是一种可以将全局作用域划分成不同区域的机制。 它可以将一组相关的变量、函数和类组织在一起&#xff0c;形成一个…

sqlserver删除最近2个月的记录

--上个月1号如 2024-10-01 00:00:00.000 select DATEADD(month, DATEDIFF(month, 0, GETDATE())-1, 0); -下个月1号如2024-12-01 00:00:00.000select DATEADD(month, DATEDIFF(month, 0, GETDATE())1, 0); --返回2024-11的格式select YearMonth from Dec_EPInventor…

【C++】 C++游戏设计---五子棋小游戏

1. 游戏介绍 一个简单的 C 五子棋小游戏 1.1 游戏规则&#xff1a; 双人轮流输入下入点坐标横竖撇捺先成五子连线者胜同一坐标点不允许重复输入 1.2 初始化与游戏界面 初始化界面 X 输入坐标后 O 输入坐标后 X 先达到胜出条件 2. 源代码 #include <iostream> #i…

树-好难-疑难_GPT

// // Created by 徐昌真 on 2024/11/10. // #include <iostream> using namespace std;template<typename T> struct ListNode{ //新建链表节点T data; //指向下一个子节点 ListNode< TreeNode<T>* > childHead; 这里的 T 是TreeNde类型的…

源代码加密技术对比:教你正确选择源代码加密方案

源代码加密技术对比&#xff1a;教你正确选择源代码加密方案 一、源代码加密技术概述 源代码加密旨在保护软件开发过程中的知识产权&#xff0c;防止源代码被盗用、篡改或逆向工程。随着开发环境日益复杂&#xff0c;选择合适的源代码加密解决方案显得尤为重要。在多样化的开…

洛谷 P1725 琪露诺(线段树优化dp)

题目链接 https://www.luogu.com.cn/problem/P1725 思路 我们令 d p [ i ] dp[i] dp[i]表示琪露诺移动到第 i i i个格子时能够获得的最大冰冻指数。 显然&#xff0c;状态转移方程为&#xff1a; d p [ i ] m a x ( d p [ i ] , d p [ k ] a [ i ] ) dp[i] max(dp[i],dp…

Suricata

02-Suricata 一 ICMP流量预警 一条ICMP报文有四个重要内容&#xff0c;可与相应的ICMP关键字相匹配。它们是&#xff1a;消息的类型、代码、ID和序列。 通过ICMP的type进行匹配 alert icmp any any <> any any (msg:"icmp流量预警";itype:8;threshold:type t…

分享一些Kafka集群优化的最佳实践?

以下是一些 Kafka 集群优化的最佳实践&#xff1a; 复制策略配置&#xff1a; 在 server.properties 文件中配置 default.replication.factor 来指定每个主题的默认副本因子&#xff0c;以及 min.insync.replicas 来配置每个分区中必须要保持同步的最小副本数。这可以提高 Kafk…

论分布式事务及其解决方案

一、引言 在分布式系统中&#xff0c;事务管理是实现数据一致性的关键。传统单机事务在分布式环境下面临诸多挑战&#xff0c;无法有效保证各节点之间的数据一致性和操作原子性。分布式事务通过跨服务和跨数据库的协调机制&#xff0c;实现数据一致性和事务完整性。本论文将结…

web前端动画按钮(附源代码)

效果图 源代码 HTML部分 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>Document</title> …

实操篇:容器服务如何启动系统?

容器服务如何启动系统&#xff1f;容器服务的启动主要依赖Docker和Kubernetes。Docker通过镜像创建和管理容器&#xff0c;支持多种重启策略以确保容器稳定运行。Kubernetes则负责自动化部署、扩展和管理容器化应用&#xff0c;其核心是Pod&#xff0c;包含一个或多个容器。用户…