参考
- gitee:https://gitee.com/binary/weixin-java-tools
- 微信官方文档:https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
申请微信测试号
- 网址:
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Requesting_an_API_Test_Account.html
- url后面要填到如下未知
内网转发
- 下载ngrok:https://ngrok.com/download
- cmd进入到当前目录,并输入如下命令:(80表示映射本机的80端口,可修改为别的,需要和springboot项目server.port一致)
ngrok.exe http 80
- 下图中的网址就是你在公网中的地址
- 配置测试号url:
http://0938-111-18-95-182.ngrok.io/message
,token自己填,相当于一个密钥,可以改;
基本配置
pom
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>(不同模块参考下文)</artifactId>
<version>4.2.0</version>
</dependency>
- 微信小程序:weixin-java-miniapp
- 微信支付:weixin-java-pay
- 微信开放平台:weixin-java-open
- 公众号(包括订阅号和服务号):weixin-java-mp
- 企业号/企业微信:weixin-java-cp
例如:使用微信公众号(测试号):
<!-- https://mvnrepository.com/artifact/com.github.binarywang/wx-java-mp-spring-boot-starter -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>wx-java-mp-spring-boot-starter</artifactId>
<version>4.2.0</version>
</dependency>
yml
wx:
mp:
app-id: appid
secret: secret
token: token # 配置消息回调地址接入公众号时需要的token
server:
port: 80
- app-id、secret、token请替换为自己的
验证
注意,controller使用@RestController
注解!
注意,controller使用@RestController
注解!
注意,controller使用@RestController
注解!
方法一(推荐使用第二个方法)
https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html
开发者通过检验signature对请求进行校验(下面有校验方式)。若确认此次GET请求来自微信服务器,请原样返回echostr参数内容,则接入生效,成为开发者成功,否则接入失败。加密/校验流程如下:
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
...
// 1)将token、timestamp、nonce三个参数进行字典序排序
String[] strs=new String[]{token,timestamp,nonce};
Arrays.sort(strs);
System.out.println(Arrays.toString(strs));
// 2)将三个参数字符串拼接成一个字符串进行sha1加密
StringBuilder sb=new StringBuilder();
for (String str : strs) {
sb.append(str);
}
String str_sha1= Sha1Util.sha1(sb.toString());
// 3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
if (str_sha1.equals(signature)){
return echostr;
}
...
方法二
@GetMapping("message")
@ResponseBody
public String configAccess(String signature,String timestamp,String nonce,String echostr) {
// 校验签名
if (wxMpService.checkSignature(timestamp, nonce, signature)){
// 校验成功原样返回echostr
return echostr;
}
// 校验失败
return null;
}
接收、处理消息
- 实现如下接口
package me.chanjar.weixin.mp.api;
import java.util.Map;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
public interface WxMpMessageHandler {
WxMpXmlOutMessage handle(WxMpXmlMessage var1, Map<String, Object> var2, WxMpService var3, WxSessionManager var4) throws WxErrorException;
}
详细解释:
原样返回消息
package com.ljy.wechat_02.component;
import me.chanjar.weixin.common.error.WxErrorException;
import me.chanjar.weixin.common.session.WxSessionManager;
import me.chanjar.weixin.mp.api.WxMpMessageHandler;
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.springframework.stereotype.Component;
import java.util.Map;
@Component
public class TextHandler implements WxMpMessageHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException {
// 接收的消息内容
String inContent = wxMessage.getContent();
// 构造响应消息对象,原样返回
return WxMpXmlOutMessage
.TEXT()
.content(inContent)
.fromUser(wxMessage.getToUser())
.toUser(wxMessage.getFromUser())
.build();
}
}
配置自己的规则
package com.ljy.wechat_02.config;
import com.ljy.wechat_02.component.TextHandler;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.WxMpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WxJavaConfig {
@Autowired
private WxMpService wxMpService;
@Autowired
private TextHandler textHandler;
@Bean
public WxMpMessageRouter messageRouter() {
// 创建消息路由
final WxMpMessageRouter router = new WxMpMessageRouter(wxMpService);
// 添加文本消息路由
router.rule().async(false).msgType(WxConsts.XmlMsgType.TEXT).handler(textHandler).end();
return router;
}
}
使用规则
在controller中加入以下内容:
...
@Autowired
WxMpMessageRouter wxMpMessageRouter;
...
@PostMapping(value = "message", produces = "application/xml; charset=UTF-8")
public String handleMessage(@RequestBody String requestBody,
@RequestParam("signature") String signature,
@RequestParam("timestamp") String timestamp,
@RequestParam("nonce") String nonce) {
// 校验消息是否来自微信
if (!wxMpService.checkSignature(timestamp, nonce, signature)) {
throw new IllegalArgumentException("非法请求!");
}
// 解析消息体,封装为对象
WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody);
WxMpXmlOutMessage outMessage;
try {
// 将消息路由给对应的处理器,获取响应
outMessage = wxMpMessageRouter.route(inMessage);
} catch (Exception e) {
outMessage = null;
}
// 将响应消息转换为xml格式返回
log.info("响应消息:{}",outMessage);
return outMessage == null ? "" : outMessage.toXml();
}
事件推送
关注事件
- 添加handler
// 关注处理器
@Component
@Slf4j
public class SubscribeHandler implements WxMpMessageHandler {
@Override
public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context, WxMpService wxMpService, WxSessionManager sessionManager) throws WxErrorException {
log.info("SubscribeHandler调用");
return WxMpXmlOutMessage.TEXT().fromUser(wxMessage.getToUser()).toUser(wxMessage.getFromUser())
.content("欢迎关注").build();
}
}
- 在配置类中增加自己的规则
...
//关注
router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
.event(WxConsts.EventType.SUBSCRIBE)
.handler(subscribeHandler).end();
自定义菜单
微信官方文档
https://developers.weixin.qq.com/doc/offiaccount/Custom_Menus/Creating_Custom-Defined_Menu.html
菜单
- 通过以下类定义菜单
按钮
- 通过以下类定义按钮
@Data
public class WxMenuButton implements Serializable {
private static final long serialVersionUID = -1070939403109776555L;
/**
* <pre>
* 菜单的响应动作类型.
* view表示网页类型,
* click表示点击类型,
* miniprogram表示小程序类型
* </pre>
*/
private String type;
/**
* 菜单标题,不超过16个字节,子菜单不超过60个字节.
*/
private String name;
/**
* <pre>
* 菜单KEY值,用于消息接口推送,不超过128字节.
* click等点击类型必须
* </pre>
*/
private String key;
/**
* <pre>
* 网页链接.
* 用户点击菜单可打开链接,不超过1024字节。type为miniprogram时,不支持小程序的老版本客户端将打开本url。
* view、miniprogram类型必须
* </pre>
*/
private String url;
/**
* <pre>
* 调用新增永久素材接口返回的合法media_id.
* media_id类型和view_limited类型必须
* </pre>
*/
@SerializedName("media_id")
private String mediaId;
/**
* <pre>
* 小程序的appid.
* miniprogram类型必须
* </pre>
*/
@SerializedName("appid")
private String appId;
/**
* <pre>
* 小程序的页面路径.
* miniprogram类型必须
* </pre>
*/
@SerializedName("pagepath")
private String pagePath;
@SerializedName("sub_button")
private List<WxMenuButton> subButtons = new ArrayList<>();
@Override
public String toString() {
return WxGsonBuilder.create().toJson(this);
}
}
其中有该方法,可用作设置子按钮
- 按钮的类型包括:
public static class MenuButtonType {
/**
* 点击推事件.
*/
public static final String CLICK = "click";
/**
* 跳转URL.
*/
public static final String VIEW = "view";
/**
* 跳转到小程序.
*/
public static final String MINIPROGRAM = "miniprogram";
/**
* 扫码推事件.
*/
public static final String SCANCODE_PUSH = "scancode_push";
/**
* 扫码推事件且弹出“消息接收中”提示框.
*/
public static final String SCANCODE_WAITMSG = "scancode_waitmsg";
/**
* 弹出系统拍照发图.
*/
public static final String PIC_SYSPHOTO = "pic_sysphoto";
/**
* 弹出拍照或者相册发图.
*/
public static final String PIC_PHOTO_OR_ALBUM = "pic_photo_or_album";
/**
* 弹出微信相册发图器.
*/
public static final String PIC_WEIXIN = "pic_weixin";
/**
* 弹出地理位置选择器.
*/
public static final String LOCATION_SELECT = "location_select";
/**
* 下发消息(除文本消息).
*/
public static final String MEDIA_ID = "media_id";
/**
* 跳转图文消息URL.
*/
public static final String VIEW_LIMITED = "view_limited";
}
例子
@GetMapping("createMenu")
public String createMenu() throws WxErrorException {
// 创建菜单对象
WxMenu menu = new WxMenu();
// 创建按钮1
WxMenuButton button1 = new WxMenuButton();
button1.setType(WxConsts.MenuButtonType.CLICK);
button1.setName("1");
button1.setKey("1");
// 创建按钮2
WxMenuButton button2 = new WxMenuButton();
button2.setName("2");
// 创建按钮2的子按钮1
WxMenuButton button21 = new WxMenuButton();
button21.setType(WxConsts.MenuButtonType.VIEW);
button21.setName("2.1");
button21.setUrl("https://www.baidu.com/");
// 创建按钮2的子按钮2
WxMenuButton button22 = new WxMenuButton();
button22.setType(WxConsts.MenuButtonType.VIEW);
button22.setName("2.2");
button22.setUrl("https://v.qq.com/");
// 将子按钮添加到按钮2
button2.getSubButtons().add(button21);
button2.getSubButtons().add(button22);
// 将按钮1和按钮2添加到菜单
menu.getButtons().add(button1);
menu.getButtons().add(button2);
// 创建按钮
return wxMpService.getMenuService().menuCreate(menu);
}
- 写好该GetMapping后,公众号依然没有菜单,可先拿浏览器访问该请求,例如我的是:http://localhost/createMenu,访问后重新进入公众号。
- click:点击推事件用户点击click类型按钮后,微信服务器会通过消息接口推送消息类型为event的结构给开发者(参考消息接口指南),并且带上按钮中开发者填写的key值,开发者可以通过自定义的key值与用户进行交互;
- view:跳转URL用户点击view类型按钮后,微信客户端将会打开开发者在按钮中填写的网页URL,可与网页授权获取用户基本信息接口结合,获得用户基本信息。