Java微信公众号开发之接入weixin-java-mp框架开发微信公众号

时间:2024-04-01 18:24:43

微信开发者接入文档 : https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421135319

前言

       多的废话我们不说,在要做微信项目开发前,相信大家都会去了解微信公众号的类型和注册流程,以及不同公众号的功能使用权限,这个前面也有介绍: Java微信公众号开发之初步认识微信公众平台 ,这次我主要记录下怎么将开源作者班纳睿的微信SDK的demo接入到我们自己的项目中,因为里面有一套写的比较好的路由和微信接入入口,不用我们自己再去写,接下来关于写在前面的话,比较重要,请大家一定要认真阅读

写在前面的话

(1). 第一点,微信系列项目开发我们选用当下微信比较热门的框架WxJava,前名叫weixin-java-toolsWxJava (微信开发 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元每个月的隧道就行了,具体使用和配置参考:内网穿透常用的工具

Java微信公众号开发之接入weixin-java-mp框架开发微信公众号

一、接入weixin-java-mp

1、下载weixin-java-demo-springmvc的demo代码,https://github.com/Wechat-Group/weixin-java-demo-springmvc     Java微信公众号开发之接入weixin-java-mp框架开发微信公众号

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

  Java微信公众号开发之接入weixin-java-mp框架开发微信公众号

②、然后将weixin-java-mp-demo中包aop、builder、config、controller、dto、handler、service包括一个配置文件wx.properties复制到我们自己的项目中

Java微信公众号开发之接入weixin-java-mp框架开发微信公众号 Java微信公众号开发之接入weixin-java-mp框架开发微信公众号

③、在我们自己项目中配置wx.properties

  wx.properties 

#=========微信公众号开发基本配置============
#微信公众号的appid
wx_appid=xxx
#微信公众号的appsecret
wx_appsecret=xxx
#微信公众号的token
wx_token=xxx
#微信公众号的消息加解***aeskey
wx_aeskey=WoDWc9c9jseK4gYzOcZChVaGDkcLTgmoS3O2F7KZtWV



   Java微信公众号开发之接入weixin-java-mp框架开发微信公众号

④、需要在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框架了

Java微信公众号开发之接入weixin-java-mp框架开发微信公众号

 

到此为止,接入完成,可以开始调SDK接口开发微信相关功能,因为路由和服务器验证等等接口不用我们写了,这样我们只关注功能的实现就行了,框架的对接借鉴了demo,为开发时间节点省去了不少时间,如果对此文章有不懂流程的,请评论里留下问题,我会回复;