Java SpringBoot 集成微信公众号

本文涉及的产品
云原生内存数据库 Tair,内存型 2GB
云数据库 Redis 版,社区版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Redis 版,经济版 1GB 1个月
简介: Java SpringBoot 集成微信公众号

申请公众号

注册公众测试号点我

https://mp.weixin.qq.com/debug/cgi-bin/sandboxinfo?action=showinfo&t=sandbox/index


引入依赖

<!-- 主要依赖 -->
<dependency>
    <groupId>com.github.binarywang</groupId>
    <artifactId>weixin-java-mp</artifactId>
    <version>4.4.0</version>
</dependency> 

yml配置

wx:
  mp:
    useRedis: false
    redisConfig:
      host: 127.0.0.1
      port: 6379
    configs:
      - appId: 1111 # 第一个公众号的appid
        secret: 11 # 公众号的appsecret
        token: 111 # 接口配置里的Token值
        aesKey: 111 # 接口配置里的EncodingAESKey值
      - appId: 2222 # 第二个公众号的appid,以下同上
        secret: 1111
        token: 111
        aesKey: 111

读取配置文件类WxMaProperties

/**
 * wechat mp properties
 *
 * Astar(一颗星)
 */
@Data
@ConfigurationProperties(prefix = "wx.mp")
public class WxMpProperties {
    /**
     * 是否使用redis存储access token
     */
    private boolean useRedis;
    /**
     * redis 配置
     */
    private RedisConfig redisConfig;
    @Data
    public static class RedisConfig {
        /**
         * redis服务器 主机地址
         */
        private String host;
        /**
         * redis服务器 端口号
         */
        private Integer port;
        /**
         * redis服务器 密码
         */
        private String password;
        /**
         * redis 服务连接超时时间
         */
        private Integer timeout;
    }
    /**
     * 多个公众号配置信息
     */
    private List<MpConfig> configs;
    @Data
    public static class MpConfig {
        /**
         * 设置微信公众号的appid
         */
        private String appId;
        /**
         * 设置微信公众号的app secret
         */
        private String secret;
        /**
         * 设置微信公众号的token
         */
        private String token;
        /**
         * 设置微信公众号的EncodingAESKey
         */
        private String aesKey;
    }
    @Override
    public String toString() {
        return JsonUtils.toJson(this);
    }
}

配置文件加载

/**
 * wechat mp configuration
 *
 * Astar(一颗星)
 */
@AllArgsConstructor
@Configuration
@EnableConfigurationProperties(WxMpProperties.class)
public class WxMpConfiguration {
    private final LogHandler logHandler;
    private final NullHandler nullHandler;
    private final KfSessionHandler kfSessionHandler;
    private final StoreCheckNotifyHandler storeCheckNotifyHandler;
    private final LocationHandler locationHandler;
    private final MenuHandler menuHandler;
    private final MsgHandler msgHandler;
    private final UnsubscribeHandler unsubscribeHandler;
    private final SubscribeHandler subscribeHandler;
    private final ScanHandler scanHandler;
    private final WxMpProperties properties;
    @Bean
    public WxMpService wxMpService() {
        // 代码里 getConfigs()处报错的同学,请注意仔细阅读项目说明,你的IDE需要引入lombok插件!!!!
        final List<WxMpProperties.MpConfig> configs = this.properties.getConfigs();
        if (configs == null) {
            throw new RuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!");
        }
        WxMpService service = new WxMpServiceImpl();
        service.setMultiConfigStorages(configs
            .stream().map(a -> {
                WxMpDefaultConfigImpl configStorage;
                if (this.properties.isUseRedis()) {
                    final WxMpProperties.RedisConfig redisConfig = this.properties.getRedisConfig();
                    JedisPoolConfig poolConfig = new JedisPoolConfig();
                    JedisPool jedisPool = new JedisPool(poolConfig, redisConfig.getHost(), redisConfig.getPort(),
                        redisConfig.getTimeout(), redisConfig.getPassword());
                    configStorage = new WxMpRedisConfigImpl(new JedisWxRedisOps(jedisPool), a.getAppId());
                } else {
                    configStorage = new WxMpDefaultConfigImpl();
                }
                configStorage.setAppId(a.getAppId());
                configStorage.setSecret(a.getSecret());
                configStorage.setToken(a.getToken());
                configStorage.setAesKey(a.getAesKey());
                return configStorage;
            }).collect(Collectors.toMap(WxMpDefaultConfigImpl::getAppId, a -> a, (o, n) -> o)));
        return service;
    }
    @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(KF_CREATE_SESSION)
            .handler(this.kfSessionHandler).end();
        newRouter.rule().async(false).msgType(EVENT).event(KF_CLOSE_SESSION)
            .handler(this.kfSessionHandler).end();
        newRouter.rule().async(false).msgType(EVENT).event(KF_SWITCH_SESSION)
            .handler(this.kfSessionHandler).end();
        // 门店审核事件
        newRouter.rule().async(false).msgType(EVENT).event(POI_CHECK_NOTIFY).handler(this.storeCheckNotifyHandler).end();
        // 自定义菜单事件
        newRouter.rule().async(false).msgType(EVENT).event(EventType.CLICK).handler(this.menuHandler).end();
        // 点击菜单连接事件
        newRouter.rule().async(false).msgType(EVENT).event(EventType.VIEW).handler(this.nullHandler).end();
        // 关注事件
        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(EVENT).event(EventType.LOCATION).handler(this.locationHandler).end();
        // 接收地理位置消息
        newRouter.rule().async(false).msgType(XmlMsgType.LOCATION).handler(this.locationHandler).end();
        // 扫码事件
        newRouter.rule().async(false).msgType(EVENT).event(EventType.SCAN).handler(this.scanHandler).end();
        // 默认
        newRouter.rule().async(false).handler(this.msgHandler).end();
        return newRouter;
    }
}

json返回工具类

public class JsonUtils {
    public static String toJson(Object obj) {
        Gson gson = new GsonBuilder()
            .setPrettyPrinting()
            .disableHtmlEscaping()
            .create();
        return gson.toJson(obj);
    }
}

控制层

主要控制层,认证信息

@Slf4j
@AllArgsConstructor
@RestController
@RequestMapping("/wx/portal/{appid}")
public class WxPortalController {
    private final WxMpService wxService;
    private final WxMpMessageRouter messageRouter;
    @GetMapping(produces = "text/plain;charset=utf-8")
    public String authGet(@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接收到来自微信服务器的认证消息:[{}, {}, {}, {}]", signature,
            timestamp, nonce, echostr);
        if (StringUtils.isAnyBlank(signature, timestamp, nonce, echostr)) {
            throw new IllegalArgumentException("请求参数非法,请核实!");
        }
        if (!this.wxService.switchover(appid)) {
            throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid));
        }
        if (wxService.checkSignature(timestamp, nonce, signature)) {
            return echostr;
        }
        return "非法请求";
    }
    @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接收微信请求:[openid=[{}], [signature=[{}], encType=[{}], msgSignature=[{}],"
                + " timestamp=[{}], nonce=[{}], requestBody=[\n{}\n] ",
            openid, signature, encType, msgSignature, timestamp, nonce, requestBody);
        if (!this.wxService.switchover(appid)) {
            throw new IllegalArgumentException(String.format("未找到对应appid=[%s]的配置,请核实!", appid));
        }
        if (!wxService.checkSignature(timestamp, nonce, signature)) {
            throw new IllegalArgumentException("非法请求,可能属于伪造的请求!");
        }
        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, wxService.getWxMpConfigStorage(),
                timestamp, nonce, msgSignature);
            log.debug("\n消息解密后内容为:\n{} ", inMessage.toString());
            WxMpXmlOutMessage outMessage = this.route(inMessage);
            if (outMessage == null) {
                return "";
            }
            out = outMessage.toEncryptedXml(wxService.getWxMpConfigStorage());
        }
        log.debug("\n组装回复信息:{}", out);
        return out;
    }
    private WxMpXmlOutMessage route(WxMpXmlMessage message) {
        try {
            return this.messageRouter.route(message);
        } catch (Exception e) {
            log.error("路由消息时出现异常!", e);
        }
        return null;
    }
}


测试号配置

配置微信公众号中的接口地址:http://公网可访问域名/wx/portal/xxxxx (注意,xxxxx为对应公众号的appid值)


5be1e2624b784bfaa9f1e8f97b513060.png


以上就完成了哦,简单教程


Gitee开源地址

点我下载项目https://gitee.com/wy521a/astar-weixin-mp


记得点个star哦


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
3天前
|
运维 监控 Java
Java中的持续集成与持续部署最佳实践
Java中的持续集成与持续部署最佳实践
|
2天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的马拉松报名系统微信小程附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的马拉松报名系统微信小程附带文章源码部署视频讲解等
12 4
|
2天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的微信小程序的网上商城附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的微信小程序的网上商城附带文章源码部署视频讲解等
11 3
|
2天前
|
Java jenkins 持续交付
Java中的版本控制与持续集成实践
Java中的版本控制与持续集成实践
|
2天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的大学生校园兼职微信小程序附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的大学生校园兼职微信小程序附带文章源码部署视频讲解等
10 1
|
1天前
|
JavaScript Java 测试技术
基于SpringBoot+Vue的“狮子狗”二手交易微信小程序的详细设计和实现(源码+lw+部署文档+讲解等)
基于SpringBoot+Vue的“狮子狗”二手交易微信小程序的详细设计和实现(源码+lw+部署文档+讲解等)
9 0
|
2天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的微信小程序跑腿平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的微信小程序跑腿平台附带文章源码部署视频讲解等
6 0
|
2天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的停车场微信小程序附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的停车场微信小程序附带文章源码部署视频讲解等
6 0
|
2天前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp的生活小助手微信小程序附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp的生活小助手微信小程序附带文章源码部署视频讲解等
7 0
|
2天前
|
JSON Java 数据格式
支付系统---微信支付15------创建SpringBoot项目
支付系统---微信支付15------创建SpringBoot项目