WxJava
承接上文:微信开发工具包WxJava之微信公众号开发的入门使用篇,续写下文。
微信开发工具包WxJava是一个基于Java语言的微信公众号和小程序开发工具包,提供了丰富的API接口,可以根据自己的需求选择相应的接口。
WxJava中常用API:
获取access_token:access_token是调用微信公众平台API的全局唯一凭证,通过获取access_token可以进行后续的API调用。
用户管理:包括用户信息的获取、设置备注名、拉黑用户等。
创建自定义菜单:创建自定义菜单,包括点击事件、跳转URL等。自定义菜单是指将公众号的功能以菜单形式展现给用户,方便用户快速访问公众号的各项功能。
模板消息:通过调用模板消息API可以向用户发送预设的模板消息,方便公众号进行消息推送,包含文字、链接、图片等。
素材管理:素材可以是图文消息、图片、语音、视频等多种类型,通过调用素材管理API可以实现对素材的上传、获取和删除等操作。
消息管理:通过客服接口发送文本、图片、语音、视频等消息给用户。同理,用户向公众号发送消息,公众号向用户回复的消息。
网页授权:网页授权是指允许公众号获取用户在微信内的基本信息,并在网页中进行相关操作的功能。
JS-SDK:JS-SDK可以帮助公众号在网页中调用微信提供的一些基础服务,如分享、支付等。
用户管理
用户管理提供了:
更新用户备注名、获得用户信息、获得用户列表、查询用户标签列表、批量给用户添加标签、批量给用户移除标签
等API
相关API
更新用户备注名
wxMpService.getUserService().userUpdateRemark(openid, "测试备注名");
获得用户信息
String lang = "zh_CN"; //语言
WxMpUser user = wxMpService.getUserService().userInfo(openid,lang);
获得用户列表
WxMpUserList wxUserList = wxMpService.getUserService().userList(next_openid);
查询用户标签列表
List<Long> tags = wxMpService.getUserTagService().userTagList(openid);
批量给用户添加标签
String[]openids,long tagid;
wxMpService.getUserTagService().batchTagging(tagid,openids);
批量给用户移除标签
wxMpService.getUserTagService().batchUntagging(tagid,openids);
使用示例
用户管理以使用获得用户列表
API为例:
@RequestMapping("/user")
public String user() throws WxErrorException {
// 可选,第一个拉取的OPENID,null为从头开始拉取
String nextOpenid = null;
// 获得用户列表,一次拉取调用最多拉取10000个关注者的OpenID
WxMpUserList wxMpUserList = wxMpService.getUserService().userList(nextOpenid);
log.info("wxMpUserList = {}", wxMpUserList);
// 语言
String lang = "zh_CN";
List<String> openids = wxMpUserList.getOpenids();
for (String openid : openids) {
// 获得用户信息
WxMpUser user = wxMpService.getUserService().userInfo(openid, lang);
log.info("user = {}", user);
// 更新用户备注名
wxMpService.getUserService().userUpdateRemark(openid, "开发者");
}
return "OK";
}
wxMpUserList = {
"total":1,"count":1,"openids":["oqIoX6A_aW-_VxMbfV3vn3N2_adc"],"nextOpenid":"oqIoX6A_aW-_VxMbfV3vn3N2_adc"}
user = {
"subscribe":true,"openId":"oqIoX6A_aW-_VxMbfV3vn3N2_adc","nickname":"","language":"zh_CN","headImgUrl":"","subscribeTime":1682671274,"remark":"","groupId":0,"tagIds":[],"subscribeScene":"ADD_SCENE_OTHERS","qrScene":"0","qrSceneStr":""}
二维码管理
二维码管理介绍
可以生成带参数的二维码,应用于为了满足用户渠道推广分析和用户帐号绑定等场景
用户扫描带场景值二维码时,可以推送以下两种事件:
如果用户未关注公众号,则用户可以关注公众号,关注后微信会将带场景值关注事件推送给开发者。
如果用户已关注公众号,在用户扫描后会自动进入会话,微信也会将带场景值扫描事件推送给开发者。
二维码管理中有2种类型的二维码:
1、临时二维码
临时二维码是有过期时间的,最长可以设置为在二维码生成后的30天(即2592000秒)后过期,但能够生成较多数量。临时二维码主要用于帐号绑定等不要求二维码永久保存的业务场景
2、永久二维码
永久二维码是无过期时间的,但数量较少(目前为最多10万个)。永久二维码主要用于适用于帐号绑定、用户来源统计等场景。
创建二维码ticket
每次创建二维码ticket需要提供一个开发者自行设定的参数scene_id
// 临时ticket
WxMpQrCodeTicket ticket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket(scene, expire_seconds);
// 永久ticket
WxMpQrCodeTicket ticket = wxMpService.getQrcodeService().qrCodeCreateLastTicket(scene);
通过ticket换取二维码
获取二维码ticket后,开发者可用ticket换取二维码图片。请注意,本接口无须登录态即可调用。
// 创建二维码ticket
WxMpQrCodeTicket ticket = wxMpService.getQrcodeService().qrCodeCreate*();
// 获得在系统临时目录下的文件,需要自己保存使用,注意:临时文件夹下存放的文件不可靠,不要直接使用
File file = wxMpService.getQrcodeService().qrCodePicture(ticket);
@RequestMapping("/user")
public String user() throws WxErrorException {
WxMpQrCodeTicket ticket = wxMpService.getQrcodeService().qrCodeCreateTmpTicket("scene_id", 2592000);
File file = wxMpService.getQrcodeService().qrCodePicture(ticket);
log.info("file = {}", file);
return file.getPath();
}
ticket = {
"ticket":"gQHz7zwAAAAAAAAAAS5odHRwOi8vd2VpeGluLnFxLmNvbS9xLzAySjhRY0VsRWFjbkUxaFN0UGhBY24AAgR2kEtkAwQAjScA","expireSeconds":2592000,"url":"http://weixin.qq.com/q/02J8QcElEacnE1hStPhAcn"}
file = C:\Users\Admin\AppData\Local\Temp\wxjava-temp4795776614163231975\4063eff3-53d5-4227-9ee4-eff1be9201fa1918629243212569901.jpg
获取二维码ticket后,开发者可用ticket换取二维码图片。请注意,本接口无须登录态即可调用
https://mp.weixin.qq.com/cgi-bin/showqrcode?ticket=TICKET
接收消息
接收事件推送
实现一个接收关注、取消关注事件推送的处理。
@Component
public class SubscribeHandler implements WxMpMessageHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException {
return WxMpXmlOutMessage.TEXT().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
.content("欢迎关注XX公众号").build();
}
}
@Component
@Slf4j
public class UnSubscribeHandler implements WxMpMessageHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException {
return WxMpXmlOutMessage.TEXT().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
.content("注意:已经取消关注,即使回复消息也不会收到").build();
}
}
@Configuration
public class MessageRouterConfig {
@Autowired
private WxMpService wxMpService;
@Autowired
private SubscribeHandler subscribeHandler;
@Autowired
private UnSubscribeHandler unSubscribeHandler;
@Bean
public WxMpMessageRouter messageRouter() {
// 创建消息路由
final WxMpMessageRouter router = new WxMpMessageRouter(wxMpService);
// 订阅事件
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT).event(WxConsts.EventType.SUBSCRIBE).handler(subscribeHandler).end();
// 取消订阅事件
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT).event(WxConsts.EventType.UNSUBSCRIBE).handler(unSubscribeHandler).end();
return router;
}
}
@RequestMapping("send")
public String configAccess(@RequestBody String requestBody, @RequestParam("signature") String signature, @RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce) {
if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
log.error("签名校验 ===》 非法请求");
return null;
}
WxMpXmlMessage xmlMessage = WxMpXmlMessage.fromXml(requestBody);
WxMpXmlOutMessage outMessage = null;
try {
outMessage = wxMpMessageRouter.route(xmlMessage);
} catch (Exception e) {
log.error("消息路由异常", e);
}
return outMessage == null ? null : outMessage.toXml();
}
群发接口
文本消息
@RequestMapping("send")
public String send() throws WxErrorException {
WxMpMassOpenIdsMessage massMessage = new WxMpMassOpenIdsMessage();
massMessage.setMsgType(WxConsts.MassMsgType.TEXT);
massMessage.setContent("文本消息内容文本消息内容文本消息内容文本消息内容文本消息内容文本消息内容");
// 用户openid 扫描`测试号二维码`可以得到
massMessage.getToUsers().add("oqIoX6A_aW-_VxMbfV3vn3N2_adc");
// 针对异常:errcode: 40130,具体异常请参考文末异常项
massMessage.getToUsers().add("");
WxMpMassSendResult massResult = wxMpService.getMassMessageService().massOpenIdsMessageSend(massMessage);
return "OK";
}
图片消息
@RequestMapping("send")
public String send() throws WxErrorException {
File file = new File("D://test.png");
WxMediaUploadResult uploadMediaRes = null;
try (
FileInputStream fileInputStream = new FileInputStream(file);
) {
uploadMediaRes = wxMpService.getMaterialService().mediaUpload(WxConsts.MediaFileType.IMAGE, "jpg", fileInputStream);
} catch (Exception e) {
e.printStackTrace();
return "Fail";
}
WxMpMassOpenIdsMessage massMessage = new WxMpMassOpenIdsMessage();
massMessage.setMsgType(WxConsts.MassMsgType.IMAGE);
massMessage.setMediaId(uploadMediaRes.getMediaId());
// 用户openid 扫描`测试号二维码`可以得到
massMessage.getToUsers().add("oqIoX6A_aW-_VxMbfV3vn3N2_adc");
// errcode: 40130
massMessage.getToUsers().add("");
WxMpMassSendResult massResult = wxMpService.getMassMessageService().massOpenIdsMessageSend(massMessage);
log.info("massResult ===》 {}", massResult);
return "OK";
}
异常
在测试群发接口时,出现以下异常:
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is me.chanjar.weixin.common.error.WxRuntimeException: javax.net.ssl.SSLException: java.lang.RuntimeException: Unexpected error: java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty] with root cause
java.security.InvalidAlgorithmParameterException: the trustAnchors parameter must be non-empty
at java.security.cert.PKIXParameters.setTrustAnchors(PKIXParameters.java:200) ~[na:1.8.0_41]
at java.security.cert.PKIXParameters.<init>(PKIXParameters.java:120) ~[na:1.8.0_41]
at java.security.cert.PKIXBuilderParameters.<init>(PKIXBuilderParameters.java:104) ~[na:1.8.0_41]
因为群发的用户数用户有两个或两个以上,由于本公众号只有一个关注用户,所以报错。
me.chanjar.weixin.common.error.WxErrorException: 错误代码:40130, 错误信息:invalid openid list size, at least two openid rid: 644a404f-092ae85f-012852f0,微信原始报文:{
"errcode":40130,"errmsg":"invalid openid list size, at least two openid rid: 644a404f-092ae85f-012852f0"}
me.chanjar.weixin.common.error.WxErrorException: 错误代码:40017, 错误信息:不合法的按钮类型,微信原始报文:{
"errcode":40017,"errmsg":"invalid button type rid: 644b41e9-28e5a17e-0983ff62"}
模板消息、业务通知
通过调用模板消息API可以向用户发送预设的模板消息,方便公众号进行消息推送等。
配置消息模板
创建微信模板
模板标题:系统异常通知
模板内容:
服务名称: {
{
keyword1.DATA}}
IP和端口: {
{
keyword2.DATA}}
异常内容: {
{
keyword3.DATA}}
异常时间: {
{
keyword4.DATA}}
系统异常,请及时处理。
业务通知实现
@RequestMapping("send")
public String send() {
WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()
// 用户openid 扫描`测试号二维码`可以得到
.toUser("oqIoX6A_aW-_VxMbfV3vn3N2_adc")
// 模板id
.templateId(templateId)
//点击消息要访问的网址
.url("https://baidu.com/")
.build();
templateMessage.addData(new WxMpTemplateData("keyword1", "user服务", "#000000"))
.addData(new WxMpTemplateData("keyword2", "192.168.30.30:8080"))
.addData(new WxMpTemplateData("keyword3", "获取数据失败"))
.addData(new WxMpTemplateData("keyword4", LocalDateTime.now()+""));
try {
// 发送模板消息
wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);
} catch (WxErrorException e) {
e.printStackTrace();
}
return "OK";
}
自定义菜单管理
创建自定义菜单,包括点击事件、跳转URL等。自定义菜单是指将公众号的功能以菜单形式展现给用户,方便用户快速访问公众号的各项功能。
AIP介绍
自定义菜单使用2个核心对象:
WxMenu:菜单对象
WxMenuButton:按钮对象
创建自定义菜单
包括新增和修改,修改相当于覆盖之前的菜单。
WxMenu wxMenu = new WxMenu();
设置菜单
wxMpService.getMenuService().menuCreate(wxMenu);
删除自定义菜单
wxMpService.getMenuService().menuDelete();
获得自定义菜单
WxMenu wxMenu = wxMpService.getMenuService().menuGet();
传递JSON参数
@GetMapping("createMenu")
public String createMenu() throws WxErrorException {
String json="{\"button\": [{\"name\": \"点击\",\"type\": \"click\",\"key\": \"1\"},{\"name\": \"跳转\",\"type\": \"view\",\"url\": \"http://www.baidu.com\"}]}";
// 创建按钮
wxMpService.getMenuService().menuCreate(json);
// 获取菜单信息
WxMpGetSelfMenuInfoResult selfMenuInfo = wxMpService.getMenuService().getSelfMenuInfo();
log.info("selfMenuInfo : {}", selfMenuInfo);
return "OK";
}
传递对象参数
@GetMapping("createMenu")
public String createMenu() throws WxErrorException {
// 创建菜单对象
WxMenu menu = new WxMenu();
// 创建按钮A
WxMenuButton button_a = new WxMenuButton();
button_a.setType(WxConsts.MenuButtonType.CLICK);
button_a.setName("按钮A");
button_a.setKey("BUTTON_A");
// 创建按钮A的子按钮1
WxMenuButton button_a_1 = new WxMenuButton();
button_a_1.setType(WxConsts.MenuButtonType.VIEW);
button_a_1.setName("子按钮1");
button_a_1.setUrl("https://www.baidu.com");
// 将子按钮添加到按钮A
button_a.getSubButtons().add(button_a_1);
// 创建按钮A的子按钮2
WxMenuButton button_a_2 = new WxMenuButton();
button_a_2.setType(WxConsts.MenuButtonType.CLICK);
button_a_2.setName("子按钮2");
button_a_2.setKey("BUTTON_A_2");
button_a.getSubButtons().add(button_a_2);
// 创建按钮B
WxMenuButton button_b = new WxMenuButton();
button_b .setType(WxConsts.MenuButtonType.CLICK);
button_b.setName("按钮B");
button_b.setKey("BUTTON_B");
// 将按钮A和按钮B添加到菜单
menu.getButtons().add(button_a);
menu.getButtons().add(button_b);
// 创建按钮
wxMpService.getMenuService().menuCreate(menu);
// 获取菜单信息
WxMpGetSelfMenuInfoResult selfMenuInfo = wxMpService.getMenuService().getSelfMenuInfo();
log.info("selfMenuInfo : {}", selfMenuInfo);
return "OK";
}
OAuth2网页授权
流程时序图
微信网页授权流程时序图如下:
相关API说明
构造网页授权url
wxMpService.getOAuth2Service().buildAuthorizationUrl(url, WxConsts.OAuth2Scope.SNSAPI_USERINFO, null)
获得access token
WxOAuth2AccessToken wxOAuth2AccessToken = wxMpService.getOAuth2Service().getAccessToken(code);
获得用户基本信息
WxOAuth2UserInfo wxMpUser = wxMpService.getOAuth2Service().getUserInfo(wxMpOAuth2AccessToken, null);
刷新access token
WxOAuth2AccessToken = wxMpService.getOAuth2Service().refreshAccessToken(wxOAuth2AccessToken.getRefreshToken());
验证access token
boolean valid = wxMpService.getOAuth2Service().validateAccessToken(wxOAuth2AccessToken);
构造网页授权url
首先构造网页授权url,然后构成超链接让用户点击:
/**
* 构造网页授权url
*
* @return
*/
@GetMapping("authPage")
public String authPage() {
WxOAuth2Service oAuth2Service = wxMpService.getOAuth2Service();
String callbackUrl = "https://*.zicp.fun/callback";
// 构建授权url
String url = oAuth2Service.buildAuthorizationUrl(callbackUrl, WxConsts.OAuth2Scope.SNSAPI_USERINFO, null);
return url;
}
获得access token
当用户同意授权后,会回调所设置的url并把authorization code传过来,然后用这个code获得access token,其中也包含用户的openid等信息
/**
* 用户确认授权后的回调处理
*/
@GetMapping("callback")
public WxOAuth2UserInfo callback(String code) throws WxErrorException {
WxOAuth2Service oAuth2Service = wxMpService.getOAuth2Service();
// 利用code获取accessToken
WxOAuth2AccessToken accessToken = oAuth2Service.getAccessToken(code);
// 利用accessToken获取用户信息
WxOAuth2UserInfo userInfo = oAuth2Service.getUserInfo(accessToken, null);
return userInfo;
}
@GetMapping("createMenu")
public String createMenu() throws WxErrorException {
// 创建菜单对象
WxMenu menu = new WxMenu();
// 创建按钮A
WxMenuButton button_a = new WxMenuButton();
button_a.setType(WxConsts.MenuButtonType.VIEW);
button_a.setName("授权登录");
button_a.setUrl("https://*.zicp.fun/authPage");
// 创建按钮B
WxMenuButton button_b = new WxMenuButton();
button_b .setType(WxConsts.MenuButtonType.CLICK);
button_b.setName("按钮B");
button_b.setKey("BUTTON_B");
// 将按钮A和按钮B添加到菜单
menu.getButtons().add(button_a);
menu.getButtons().add(button_b);
// 创建按钮
wxMpService.getMenuService().menuCreate(menu);
return "OK";
}
执行测试
在进行OAuth2网页授权测试过程中,获取构造网页授权url
,遇到如下异常:
需要再公众号开发平台设置网页回调域名地址
当填写网页回调授权域名地址后,再次获取构造网页授权url
,结果如下:
将得到的构造网页授权url
发送到公众号,然后点击该链接,进入到用户授权详情页,用户同意后则将获得access token
利用accessToken获取用户信息,具体用户信息格式如下