首先,WxJava 是大佬们开发的关于的微信的公众号、小程序和支付等的全能型开发包,强,真的强!
<dependency> <groupId>com.github.binarywanggroupId> <artifactId>weixin-java-mpartifactId> <version>3.6.8.Bversion>dependency>
对,就是上面这个东西,当然上面这个只是关于公众号的(mp),小伙伴们可按需自行引入。
开发测试公众号可自己申请,可以是订阅号,也可以是服务号,但是个人只能申请订阅号。当然也可申请一个测试账号。
正文
首先我们将公众号的有关信息配置在 yml 文件里,以方便管理。
@Data@EqualsAndHashCode(callSuper = false)@ConfigurationProperties(prefix = "wx.mp")public class MpConfigDto { //这玩意是支持多公众号的 private List configs; @Data public static class Config { private String appId; // appId private String appSecret; // appSecret private String token; //消息 token private String aesKey; //消息密文 }}
然后我们可以根据 WxJava 的接口文档来创建一些处理器,比如事件、消息、菜单等。
//这是一个关注事件处理器,即用户在关注公众号后//就可以给用户回复@Slf4j@Componentpublic class SubscribeHandler extends AbstractHandler { @Override public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, WxMpService wxMpService, WxSessionManager wxSessionManager) { log.info("\n新关注用户: openId = " + wxMpXmlMessage.getFromUser()); //订阅还没有权限 try { //这里获取用户信息只能是服务号,小小的订阅好八太行 WxMpUser wxMpUser = wxMpService.getUserService().userInfo(wxMpXmlMessage.getFromUser(), null); if (wxMpUser != null) { log.info("\n微信用户信息: " + wxMpUser.toString()); // TODO 微信用户信息持久化 } } catch (WxErrorException e) { log.error("\n获取微信用户信息失败!"); e.printStackTrace(); } return new TextBuilder().build("官人,奴家等候多时了呢!", wxMpXmlMessage, wxMpService); }}
//这是取关事件处理器@Slf4j@Componentpublic class UnSubscribeHandler extends AbstractHandler { @Override public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, WxMpService wxMpService, WxSessionManager wxSessionManager) { log.info("\n用户取关: openId = " + wxMpXmlMessage.getFromUser()); return null; }}
//这是普通文本消息处理器//比如与用户之间的简单对话@Slf4j@Componentpublic class TextMsgHandler extends AbstractHandler { @Override public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map map, WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException { //判断消息是否为文本消息 if (wxMpXmlMessage.getMsgType().equals(WxConsts.XmlMsgType.TEXT)) { //TODO: 这里可将消息持久化 } //获取微信用户的基本信息 //订阅号没有权限 WxMpUser wxMpUser = wxMpService.getUserService().userInfo(wxMpXmlMessage.getFromUser(), "zh_CN"); if (null != wxMpUser){ log.info("\n微信用户信息: \n{}", wxMpUser); //下面两种响应方式都可 // TODO 这里可以将用户信息持久化 } return new TextBuilder().build("您已解锁本站全部文章!",wxMpXmlMessage,wxMpService); }}
接着我们创建 MpConfig 配置文件,注入 WxMpService ,并且在注入的时候初始化公众号配置信息,然后再将各种自定义的处理器注入。
注:这里的 WxMpService 是 WxJava 对外的接口。
@Slf4j@Configuration@AllArgsConstructor@EnableConfigurationProperties(MpConfigDto.class)public class MpConfig { private final MpConfigDto mpConfigDto; private final LogHandler logHandler; private final SubscribeHandler subscribeHandler; private final UnSubscribeHandler unSubscribeHandler; private final TextMsgHandler textMsgHandler; private final MsgHandler msgHandler; @Bean public WxMpService wxMpService() { final List configs = this.mpConfigDto.getConfigs(); if (configs == null) { log.error("配置文件出错了哦!"); throw new ExceptionDto(3301, "配置文件出错了哦!"); } WxMpService wxMpService = new WxMpServiceImpl(); wxMpService.setMultiConfigStorages(configs .stream().map(item -> { WxMpDefaultConfigImpl wxMpDefaultConfig = new WxMpDefaultConfigImpl(); wxMpDefaultConfig.setAppId(item.getAppId()); wxMpDefaultConfig.setSecret(item.getAppSecret()); wxMpDefaultConfig.setToken(item.getToken()); wxMpDefaultConfig.setAesKey(item.getAesKey()); return wxMpDefaultConfig; }).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, item -> item, (o ,n) -> o))); return wxMpService; } @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(SUBSCRIBE).handler(this.subscribeHandler).end(); //取消关注事件 newRouter.rule().async(false).msgType(EVENT).event(UNSUBSCRIBE).handler(this.unSubscribeHandler).end(); //文本消息 newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.TEXT).handler(this.textMsgHandler).end(); // 默认 newRouter.rule().async(false).handler(this.msgHandler).end(); return newRouter; }}
最后定义 MpController,这里主要由两个方法,一个 get, 一个 post。
get: 用来处理微信公众号的认证请求,也就是在微信公众平台的公众号配置中,填写 url 时,就需要填写此 get 的 url,提交时,微信服务器就会发出一个 get 请求,请求我们自己的服务,携带 appId、时间戳、签名的参数,以防止非法请求。
post:
SHA签名校验:post方法的主要功能是当你在微信公众号对话栏里输入:文本、图片、语音、视频、点击菜单等操作时,该一操作将会被封装为一个xml数据体,记住啊,微信开发使用的是xml格式传输的,非JSON格式;该xml数据体被微信服务器从我们接入开发者的URL上推送到我们应用程序的后台,这时候这一类请求都是Post类型。传递的Post请求在我们应用程序后台被接收了之后,首先做参数的签名校验,目的也是防止非法请求;
区分明密文:然后再是区分消息是明文传输还是密文传输,是明文还是密码区别于你在微信公众平台接入开发者时是否勾选了密文传输。一般都是使用明文传输,因为有使用SHA散列对请求合法性签名校验,相对来说还是挺安全的哦,所以密文就显得没必要了。
匹配route:区分明文还是密文之后,会根据消息类型或者事件的类型来动态的遍历已经装载的route,匹配到对应类型的路由处理器,也就是xxxHandler,通过路由找到消息或事件的处理器之后,剩下的事情就交给xxxHandler来完成了,xxxHandler中会进行一些业务逻辑处理,其中可能会涉及到数据库交互,总之需要做的事情就在这里面处理,最后xxxHandler会将响应结果组装成xml响应给会话者,这一过程在WxJava中被包装在WxMpXmlOutMessage类来完成。
@Slf4j@RestController@RequestMapping("/mp/chat/{appId}")@AllArgsConstructorpublic class MpController { private final WxMpService wxMpService; private final WxMpMessageRouter messageRouter; /** * * @param appId * @param signature * @param timestamp * @param nonce * @param echostr * @return */ @LogAnnotation("公众号认证消息") @GetMapping(produces = "text/plain;charset=utf-8") public String get(@PathVariable String appId, @RequestParam(name = "signature", required = false) String signature, @RequestParam(name = "timestamp", required = false) String timestamp, @RequestParam(name = "nonce", required = false) String nonce, @RequestParam(name = "echostr", required = false) String echostr) { log.info("\n接受到来自微信服务器的认证消息: [{}, {}, {}, {}, {}]", appId, signature, timestamp, nonce, echostr); if (!this.wxMpService.switchover(appId)) { log.error(String.format("\n未找到对应 appId = [%s] 的配置,请确认!", appId)); throw new ExceptionDto(3302, String.format("未找到对应 appId = [%s] 的配置,请确认!", appId)); } if (StringUtils.isAnyBlank(signature, timestamp, nonce)) { log.error("\n请求参数非法,请确认!"); throw new ExceptionDto(3303, "请求参数非法,请确认!"); } if (wxMpService.checkSignature(timestamp, nonce, signature)) { return echostr; } return "验签失败!"; } @LogAnnotation("公众号交互消息") @PostMapping(produces = "application/xml; charset=UTF-8") public String post(@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接收微信请求: [appId=[{}]], [openid=[{}]], [signature=[{}]], [timestamp=[{}]], [nonce=[{}]], " + "[encType=[{}]], [msgSignature=[{}]], [requestBody=[\n{}\n]]", appId, openid, signature, timestamp, nonce, encType, msgSignature, requestBody); if (!this.wxMpService.switchover(appId)) { log.error(String.format("\n未找到对应 appId = [%s] 的配置,请确认!", appId)); throw new ExceptionDto(3302, String.format("未找到对应 appId = [%s] 的配置,请确认!", appId)); } if (StringUtils.isAnyBlank(signature, timestamp, nonce)) { log.error("\n请求参数非法,请确认!"); throw new ExceptionDto(3303, "请求参数非法,请确认!"); } if (!wxMpService.checkSignature(timestamp, nonce, signature)) { throw new ExceptionDto(3304, "非法请求,可能属于伪造请求!"); } 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, wxMpService.getWxMpConfigStorage(), timestamp, nonce, msgSignature); log.info("\n消息解密后的内容为: \n{}", inMessage.toString()); WxMpXmlOutMessage outMessage = this.route(inMessage); if (outMessage == null) { return ""; } out = outMessage.toEncryptedXml(wxMpService.getWxMpConfigStorage()); } log.info("\n组装回复的消息为: \n{}", out); return out; } private WxMpXmlOutMessage route(WxMpXmlMessage message) { try { return this.messageRouter.route(message); } catch (Exception e) { } return null; }}
测试
源码: https://github.com/XGLLHZ/springboot-frame.git
这是第一篇,好激动啊!
麻烦关注下!
有问题随时评论,随时讨论!