微信开发者接入文档 : https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319
前言
多的废话我们不说,在要做微信项目开发前,相信大家都会去了解微信公众号的类型和注册流程,以及不同公众号的功能使用权限,这个前面也有介绍: Java微信公众号开发之初步认识微信公众平台 ,这次我主要记录下怎么将开源作者班纳睿的微信SDK的demo接入到我们自己的项目中,因为里面有一套写的比较好的路由和微信接入入口,不用我们自己再去写,接下来关于写在前面的话,比较重要,请大家一定要认真阅读
写在前面的话
(1). 第一点,微信系列项目开发我们选用当下微信比较热门的框架WxJava,前名叫weixin-java-tools,WxJava (微信开发 Java SDK),支持包括微信支付、开放平台、小程序、企业微信/企业号和公众号等的后端开发,本次我们单纯的开发微信公众号项目,则Maven引入依赖:
<!-- 微信框架 参考:https://github.com/Wechat-Group/weixin-java-tools -->
<dependency>
<groupId>com.github.binarywang</groupId>
<artifactId>weixin-java-mp</artifactId>
<version>3.0.0</version>
</dependency>
(2).第二点,微信开发需要使用内网穿透才可以进行开发调试,也就是将本地localhost地址通过内网穿透,映射到外网去,通过外网才可以和微信的服务器进行消息通讯,数据的传输,这里我用过好几种内网穿透,为了避免大家不知道怎么选择,我推荐大家选择Natapp,Natapp很稳定,速度也快,https://natapp.cn/ ,单纯的微信开发调试选择购买9元每个月的隧道就行了,具体使用和配置参考:内网穿透常用的工具
一、接入weixin-java-mp
1、下载weixin-java-demo-springmvc的demo代码,https://github.com/Wechat-Group/weixin-java-demo-springmvc
demo下载下来后,看一下WeixinService代码,里面涉及到作者自己写的路由,如果不想自己写路由,可以直接借鉴复用
package com.github.binarywang.demo.spring.service;
import javax.annotation.PostConstruct;
import me.chanjar.weixin.mp.constant.WxMpEventConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.github.binarywang.demo.spring.config.WxMpConfig;
import com.github.binarywang.demo.spring.handler.AbstractHandler;
import com.github.binarywang.demo.spring.handler.KfSessionHandler;
import com.github.binarywang.demo.spring.handler.LocationHandler;
import com.github.binarywang.demo.spring.handler.LogHandler;
import com.github.binarywang.demo.spring.handler.MenuHandler;
import com.github.binarywang.demo.spring.handler.MsgHandler;
import com.github.binarywang.demo.spring.handler.NullHandler;
import com.github.binarywang.demo.spring.handler.StoreCheckNotifyHandler;
import com.github.binarywang.demo.spring.handler.SubscribeHandler;
import com.github.binarywang.demo.spring.handler.UnsubscribeHandler;
import me.chanjar.weixin.common.api.WxConsts;
import me.chanjar.weixin.mp.api.WxMpInMemoryConfigStorage;
import me.chanjar.weixin.mp.api.WxMpMessageRouter;
import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
import me.chanjar.weixin.mp.bean.kefu.result.WxMpKfOnlineList;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import static me.chanjar.weixin.common.api.WxConsts.*;
/**
*
* @author Binary Wang
*
*/
@Service
public class WeixinService extends WxMpServiceImpl {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
protected LogHandler logHandler;
@Autowired
protected NullHandler nullHandler;
@Autowired
protected KfSessionHandler kfSessionHandler;
@Autowired
protected StoreCheckNotifyHandler storeCheckNotifyHandler;
@Autowired
private WxMpConfig wxConfig;
@Autowired
private LocationHandler locationHandler;
@Autowired
private MenuHandler menuHandler;
@Autowired
private MsgHandler msgHandler;
@Autowired
private UnsubscribeHandler unsubscribeHandler;
@Autowired
private SubscribeHandler subscribeHandler;
private WxMpMessageRouter router;
@PostConstruct
public void init() {
final WxMpInMemoryConfigStorage config = new WxMpInMemoryConfigStorage();
config.setAppId(this.wxConfig.getAppid());// 设置微信公众号的appid
config.setSecret(this.wxConfig.getAppsecret());// 设置微信公众号的app corpSecret
config.setToken(this.wxConfig.getToken());// 设置微信公众号的token
config.setAesKey(this.wxConfig.getAesKey());// 设置消息加解***
super.setWxMpConfigStorage(config);
this.refreshRouter();
}
private void refreshRouter() {
final WxMpMessageRouter newRouter = new WxMpMessageRouter(this);
// 记录所有事件的日志
newRouter.rule().handler(this.logHandler).next();
// 接收客服会话管理事件
newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION).handler(this.kfSessionHandler).end();
newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION).handler(this.kfSessionHandler).end();
newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION).handler(this.kfSessionHandler).end();
// 门店审核事件
newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(WxMpEventConstants.POI_CHECK_NOTIFY).handler(this.storeCheckNotifyHandler).end();
// 自定义菜单事件
newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(MenuButtonType.CLICK).handler(this.getMenuHandler()).end();
// 点击菜单连接事件
newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(MenuButtonType.VIEW).handler(this.nullHandler).end();
// 关注事件
newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(EventType.SUBSCRIBE).handler(this.getSubscribeHandler()).end();
// 取消关注事件
newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(EventType.UNSUBSCRIBE).handler(this.getUnsubscribeHandler()).end();
// 上报地理位置事件
newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(EventType.LOCATION).handler(this.getLocationHandler()).end();
// 接收地理位置消息
newRouter.rule().async(false).msgType(XmlMsgType.LOCATION).handler(this.getLocationHandler()).end();
// 扫码事件
newRouter.rule().async(false).msgType(XmlMsgType.EVENT).event(EventType.SCAN).handler(this.getScanHandler()).end();
// 默认
newRouter.rule().async(false).handler(this.getMsgHandler()).end();
this.router = newRouter;
}
public WxMpXmlOutMessage route(WxMpXmlMessage message) {
try {
return this.router.route(message);
}
catch (Exception e) {
this.logger.error(e.getMessage(), e);
}
return null;
}
public boolean hasKefuOnline() {
try {
WxMpKfOnlineList kfOnlineList = this.getKefuService().kfOnlineList();
return kfOnlineList != null && kfOnlineList.getKfOnlineList().size() > 0;
}
catch (Exception e) {
this.logger.error("获取客服在线状态异常: " + e.getMessage(), e);
}
return false;
}
protected MenuHandler getMenuHandler() {
return this.menuHandler;
}
protected SubscribeHandler getSubscribeHandler() {
return this.subscribeHandler;
}
protected UnsubscribeHandler getUnsubscribeHandler() {
return this.unsubscribeHandler;
}
protected AbstractHandler getLocationHandler() {
return this.locationHandler;
}
protected MsgHandler getMsgHandler() {
return this.msgHandler;
}
protected AbstractHandler getScanHandler() {
return null;
}
}
2. 从这一步开始,我们就把上面的weixin-java-demo-springmvc demo代码接入到我们已经搭建好框架的项目中去
①、 首先在我的项目中新建包名为wechat
②、然后将weixin-java-mp-demo中包aop、builder、config、controller、dto、handler、service包括一个配置文件wx.properties复制到我们自己的项目中
③、在我们自己项目中配置wx.properties
wx.properties
#=========微信公众号开发基本配置============
#微信公众号的appid
wx_appid=xxx
#微信公众号的appsecret
wx_appsecret=xxx
#微信公众号的token
wx_token=xxx
#微信公众号的消息加解***aeskey
wx_aeskey=WoDWc9c9jseK4gYzOcZChVaGDkcLTgmoS3O2F7KZtWV
④、需要在wx.properties中配置微信公众平台的几个参数来接入微信服务器的验证参数
#=========微信公众号开发基本配置============
#微信公众号的appid
wx_appid=xxx
#微信公众号的appsecret
wx_appsecret=xxx
#微信公众号的token
wx_token=xxx
#微信公众号的消息加解***aeskey
wx_aeskey=WoDWc9c9jseK4gYzOcZChVaGDkcLTgmoS3O2F7KZtWV
⑤、微信的入口 :WxMpPortalController
package com.thinkgem.jeesite.modules.wechat.controller;
import me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;
import me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.thinkgem.jeesite.modules.wechat.service.WXLogService;
import com.thinkgem.jeesite.modules.wechat.service.WeixinService;
/**
*
* @ClassName: WxMpPortalController
* @Description: 微信主入口
* @author CaoWenCao
* @date 2018年6月12日 下午10:13:57
*/
@RestController
@RequestMapping("/wechat/portal")
public class WxMpPortalController {
@Autowired
private WeixinService wxService;
@Autowired
private WXLogService wXLogService;
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/*
* 微信验签
*/
@ResponseBody
@GetMapping(produces = "text/plain;charset=utf-8")
public String authGet(@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) {
this.logger.info("\n接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature, timestamp, nonce, echostr);
if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {
throw new IllegalArgumentException("请求参数非法,请核实!");
}
if (this.getWxService().checkSignature(timestamp, nonce, signature)) {
return echostr;
}
return "非法请求";
}
/*
* 消息转发---中转站 每次微信端的消息都会来到这里进行分发 对微信公众号相关的一些动作,都以报文形式推送到该接口,根据请求的类型,进行路由分发处理
*/
@ResponseBody
@PostMapping(produces = "application/xml; charset=UTF-8")
public String post(@RequestBody String requestBody, @RequestParam("signature") String signature, @RequestParam(name = "encrypt_type", required = false) String encType, @RequestParam(name = "msg_signature", required = false) String msgSignature,
@RequestParam("timestamp") String timestamp, @RequestParam("nonce") String nonce) {
this.logger.info("\n接收微信请求:[signature=[{}], encType=[{}], msgSignature=[{}]," + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ", signature, encType, msgSignature, timestamp, nonce, requestBody);
if (!this.wxService.checkSignature(timestamp, nonce, signature)) {
throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
}
String out = null;
if (encType == null) {
// 明文传输的消息
WxMpXmlMessage inMessage = WxMpXmlMessage.fromXml(requestBody);
// 如果是明文传输 ,保存 微信接收 信息,保存到数据库,用于微信消息管理中的客服互动
wXLogService.doSaveReceiveLog(inMessage);
//this.logger.info("\n保存微信接受信息WxMpXmlMessage:\n{}", inMessage);
WxMpXmlOutMessage outMessage = this.getWxService().route(inMessage);
if (outMessage == null) {
return "";
}
out = outMessage.toXml();
// 日志出口时,保存微信发出去的XML(给用户)
wXLogService.doSaveMsgLogOut(outMessage);
//this.logger.info("\n保存微信输出日志WxMpXmlOutMessage信息:\n{}", outMessage);
}
else if ("aes".equals(encType)) {
// aes加密的消息
WxMpXmlMessage inMessage = WxMpXmlMessage.fromEncryptedXml(requestBody, this.getWxService().getWxMpConfigStorage(), timestamp, nonce, msgSignature);
this.logger.debug("\n消息解密后内容为:\n{} ", inMessage.toString());
// 如果是密文传输 ,保存 微信接收 信息
wXLogService.doSaveReceiveLog(inMessage);
WxMpXmlOutMessage outMessage = this.getWxService().route(inMessage);
if (outMessage == null) {
return "";
}
out = outMessage.toEncryptedXml(this.getWxService().getWxMpConfigStorage());
// 日志出口时,保存微信发出去的XML(给用户)
wXLogService.doSaveMsgLogOut(outMessage);
this.logger.info("\n保存微信输出日志WxMpXmlOutMessage信息:\n{}", outMessage);
// 保存回复消息记录
//wXLogService.doSaveReplyLog(outMessage);
}
this.logger.debug("\n组装回复信息:{}", out);
return out;
}
protected WeixinService getWxService() {
return this.wxService;
}
}
所以,微信服务器配置URL应该为:http://xxxx.natapp1.cc/greenwx/wechat/portal , 其中xxxx.natapp1.cc为内网穿透的域名
如果你的之前搭建的项目框架没有问题,这里就去开启开发者模式的服务器配置,开启成功后就正常集成weixin-java-mp框架了
到此为止,接入完成,可以开始调SDK接口开发微信相关功能,因为路由和服务器验证等等接口不用我们写了,这样我们只关注功能的实现就行了,框架的对接借鉴了demo,为开发时间节点省去了不少时间,如果对此文章有不懂流程的,请评论里留下问题,我会回复;