Browse Source

fix(公众号账号管理): 多公众号账号初始化配置加载

fengdan 3 years ago
parent
commit
977cc19fe3

+ 0 - 145
yudao-module-wechat/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/config/WxMpConfig.java

@@ -1,145 +0,0 @@
-package cn.iocoder.yudao.module.mp.config;
-
-
-import cn.hutool.extra.spring.SpringUtil;
-import cn.iocoder.yudao.module.mp.controller.admin.account.vo.WxAccountExportReqVO;
-import cn.iocoder.yudao.module.mp.dal.dataobject.account.WxAccountDO;
-import cn.iocoder.yudao.module.mp.handler.*;
-import cn.iocoder.yudao.module.mp.service.account.WxAccountService;
-import com.google.common.collect.Maps;
-import lombok.extern.slf4j.Slf4j;
-import me.chanjar.weixin.common.api.WxConsts;
-import me.chanjar.weixin.mp.api.WxMpMessageRouter;
-import me.chanjar.weixin.mp.api.WxMpService;
-import me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;
-import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
-import me.chanjar.weixin.mp.constant.WxMpEventConstants;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.scheduling.annotation.Scheduled;
-import org.springframework.util.CollectionUtils;
-
-import javax.annotation.PostConstruct;
-import javax.annotation.Resource;
-import java.util.List;
-import java.util.Map;
-import java.util.stream.Collectors;
-
-// TODO @芋艿:思考有没更好的处理方式
-@Slf4j
-@Configuration
-public class WxMpConfig {
-
-    private static Map<String, WxMpMessageRouter> routers = Maps.newHashMap();
-    private static Map<String, WxMpService> mpServices = Maps.newHashMap();
-
-    @Resource
-    private WxAccountService wxAccountService;
-
-    private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
-
-    /**
-     * 初始化公众号配置
-     */
-    @PostConstruct
-    public synchronized void initWxConfig() {
-        WxAccountExportReqVO req = new WxAccountExportReqVO();
-        List<WxAccountDO> wxAccountList = wxAccountService.getWxAccountList(req);
-        if (CollectionUtils.isEmpty(wxAccountList)) {
-            return;
-        }
-        WxMpConfig.init(wxAccountList);
-        log.info("加载公众号配置成功");
-    }
-
-
-    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
-    public void schedulePeriodicRefresh() {
-        initWxConfig();
-    }
-
-    public static void init(List<WxAccountDO> wxAccountDOS) {
-        mpServices = wxAccountDOS.stream().map(wxAccountDO -> {
-            // TODO 亚洲:使用 WxMpInMemoryConfigStorage 的话,多节点会不会存在 accessToken 冲突
-
-            WxMpDefaultConfigImpl configStorage;
-            configStorage = new WxMpDefaultConfigImpl();
-
-            configStorage.setAppId(wxAccountDO.getAppId());
-            configStorage.setSecret(wxAccountDO.getAppSecret());
-            configStorage.setToken(wxAccountDO.getToken());
-            configStorage.setAesKey(wxAccountDO.getAesKey());
-
-            WxMpService service = new WxMpServiceImpl();
-            service.setWxMpConfigStorage(configStorage);
-            routers.put(wxAccountDO.getAppId(), newRouter(service));
-            return service;
-        }).collect(Collectors.toMap(s -> s.getWxMpConfigStorage().getAppId(), a -> a, (o, n) -> o));
-    }
-
-    public static Map<String, WxMpMessageRouter> getRouters() {
-        return routers;
-    }
-
-    public static Map<String, WxMpService> getMpServices() {
-        return mpServices;
-    }
-
-    private static WxMpMessageRouter newRouter(WxMpService wxMpService) {
-        final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
-        // 记录所有事件的日志 (异步执行)
-        newRouter.rule().handler(SpringUtil.getBean(LogHandler.class)).next();
-
-        // 接收客服会话管理事件
-        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
-                .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION)
-                .handler(SpringUtil.getBean(KfSessionHandler.class)).end();
-        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
-                .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION)
-                .handler(SpringUtil.getBean(KfSessionHandler.class))
-                .end();
-        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
-                .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION)
-                .handler(SpringUtil.getBean(KfSessionHandler.class)).end();
-
-        // 门店审核事件
-        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
-                .event(WxMpEventConstants.POI_CHECK_NOTIFY)
-                .handler(SpringUtil.getBean(StoreCheckNotifyHandler.class)).end();
-
-        // 自定义菜单事件
-        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
-                .event(WxConsts.MenuButtonType.CLICK).handler(SpringUtil.getBean(MenuHandler.class)).end();
-
-        // 点击菜单连接事件
-        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
-                .event(WxConsts.MenuButtonType.VIEW).handler(SpringUtil.getBean(NullHandler.class)).end();
-
-        // 关注事件
-        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
-                .event(WxConsts.EventType.SUBSCRIBE).handler(SpringUtil.getBean(SubscribeHandler.class))
-                .end();
-
-        // 取消关注事件
-        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
-                .event(WxConsts.EventType.UNSUBSCRIBE)
-                .handler(SpringUtil.getBean(UnsubscribeHandler.class)).end();
-
-        // 上报地理位置事件
-        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
-                .event(WxConsts.EventType.LOCATION).handler(SpringUtil.getBean(LocationHandler.class))
-                .end();
-
-        // 接收地理位置消息
-        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION)
-                .handler(SpringUtil.getBean(LocationHandler.class)).end();
-
-        // 扫码事件
-        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
-                .event(WxConsts.EventType.SCAN).handler(SpringUtil.getBean(ScanHandler.class)).end();
-
-        // 默认
-        newRouter.rule().async(false).handler(SpringUtil.getBean(MsgHandler.class)).end();
-
-        return newRouter;
-    }
-}

+ 0 - 47
yudao-module-wechat/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/config/WxMpProperties.java

@@ -1,47 +0,0 @@
-package cn.iocoder.yudao.module.mp.config;
-
-import lombok.Data;
-import org.springframework.boot.context.properties.ConfigurationProperties;
-
-/**
- * wechat mp properties
- */
-@Data
-//@ConfigurationProperties(prefix = "wx.mp")
-public class WxMpProperties {
-    /**
-     * 是否使用redis存储access token
-     */
-    private boolean useRedis;
-
-    private String defaultContent;
-
-    /**
-     * 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;
-    }
-
-}

+ 12 - 0
yudao-module-wechat/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/dal/dataobject/account/WxAccountDO.java

@@ -4,7 +4,11 @@ import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
 import lombok.*;
+import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
+import me.chanjar.weixin.mp.config.WxMpConfigStorage;
+import me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;
 
 // TODO 亚洲:这个模块的相关类,使用 Mp 作为前缀哈
 /**
@@ -64,4 +68,12 @@ public class WxAccountDO extends BaseDO {
      */
     private String remark;
 
+    public WxMpConfigStorage toWxMpConfigStorage(RedisTemplateWxRedisOps redisTemplateWxRedisOps, WxMpProperties wxMpProperties) {
+        WxMpRedisConfigImpl wxMpRedisConfig = new WxMpRedisConfigImpl(redisTemplateWxRedisOps, wxMpProperties.getConfigStorage().getKeyPrefix());
+        wxMpRedisConfig.setAppId(appId);
+        wxMpRedisConfig.setSecret(appSecret);
+        wxMpRedisConfig.setToken(token);
+        wxMpRedisConfig.setAesKey(aesKey);
+        return wxMpRedisConfig;
+    }
 }

+ 106 - 0
yudao-module-wechat/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/framework/weixin/WxMpMessageRouterConfiguration.java

@@ -0,0 +1,106 @@
+package cn.iocoder.yudao.module.mp.framework.weixin;
+
+
+import cn.iocoder.yudao.module.mp.handler.*;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.api.WxConsts;
+import me.chanjar.weixin.mp.api.WxMpMessageRouter;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.constant.WxMpEventConstants;
+import org.springframework.context.annotation.Bean;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * @author fengdan
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class WxMpMessageRouterConfiguration {
+
+    @Resource
+    private WxMpService wxMpService;
+    @Resource
+    private LogHandler logHandler;
+    @Resource
+    private KfSessionHandler kfSessionHandler;
+    @Resource
+    private StoreCheckNotifyHandler storeCheckNotifyHandler;
+    @Resource
+    private MenuHandler menuHandler;
+    @Resource
+    private NullHandler nullHandler;
+    @Resource
+    private SubscribeHandler subscribeHandler;
+    @Resource
+    private UnsubscribeHandler unsubscribeHandler;
+    @Resource
+    private LocationHandler locationHandler;
+    @Resource
+    private ScanHandler scanHandler;
+    @Resource
+    private MsgHandler msgHandler;
+
+    @Bean
+    public WxMpMessageRouter messageRouter() {
+        final WxMpMessageRouter newRouter = new WxMpMessageRouter(wxMpService);
+        // 记录所有事件的日志 (异步执行)
+        newRouter.rule().handler(logHandler).next();
+
+        // 接收客服会话管理事件
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION)
+                .handler(kfSessionHandler).end();
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION)
+                .handler(kfSessionHandler)
+                .end();
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION)
+                .handler(kfSessionHandler).end();
+
+        // 门店审核事件
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxMpEventConstants.POI_CHECK_NOTIFY)
+                .handler(storeCheckNotifyHandler).end();
+
+        // 自定义菜单事件
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxConsts.MenuButtonType.CLICK).handler(menuHandler).end();
+
+        // 点击菜单连接事件
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxConsts.MenuButtonType.VIEW).handler(nullHandler).end();
+
+        // 关注事件
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxConsts.EventType.SUBSCRIBE).handler(subscribeHandler)
+                .end();
+
+        // 取消关注事件
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxConsts.EventType.UNSUBSCRIBE)
+                .handler(unsubscribeHandler).end();
+
+        // 上报地理位置事件
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxConsts.EventType.LOCATION).handler(locationHandler)
+                .end();
+
+        // 接收地理位置消息
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION)
+                .handler(locationHandler).end();
+
+        // 扫码事件
+        newRouter.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)
+                .event(WxConsts.EventType.SCAN).handler(scanHandler).end();
+
+        // 默认
+        newRouter.rule().async(false).handler(msgHandler).end();
+
+        return newRouter;
+    }
+}

+ 1 - 4
yudao-module-wechat/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/handler/MsgHandler.java

@@ -6,13 +6,10 @@ import cn.hutool.http.HttpUtil;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.module.infra.api.file.FileApi;
 import cn.iocoder.yudao.module.mp.builder.TextBuilder;
-import cn.iocoder.yudao.module.mp.config.WxMpProperties;
 import cn.iocoder.yudao.module.mp.controller.admin.fansmsg.vo.WxFansMsgCreateReqVO;
 import cn.iocoder.yudao.module.mp.dal.dataobject.account.WxAccountDO;
 import cn.iocoder.yudao.module.mp.service.account.WxAccountService;
 import cn.iocoder.yudao.module.mp.service.fansmsg.WxFansMsgService;
-import cn.iocoder.yudao.module.mp.service.receivetext.WxReceiveTextService;
-import cn.iocoder.yudao.module.mp.service.texttemplate.WxTextTemplateService;
 import lombok.extern.slf4j.Slf4j;
 import me.chanjar.weixin.common.api.WxConsts;
 import me.chanjar.weixin.common.error.WxErrorException;
@@ -102,7 +99,7 @@ public class MsgHandler implements WxMpMessageHandler {
                             if (!downloadDir.exists()) {
                                 downloadDir.mkdirs();
                             }
-                            String filepath = downloadDirStr + String.valueOf(System.currentTimeMillis()) + ".png";
+                            String filepath = downloadDirStr + System.currentTimeMillis() + ".png";
                             //微信pic url下载到本地,防止失效
                             long size = HttpUtil.downloadFile(wxMessage.getPicUrl(), FileUtil.file(filepath));
                             log.info("download pic size : {}", size);

+ 5 - 5
yudao-module-wechat/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/costomer/WxConfigDataRefreshConsumer.java

@@ -1,8 +1,8 @@
-package cn.iocoder.yudao.module.mp.mq.costomer.dict;
+package cn.iocoder.yudao.module.mp.mq.costomer;
 
 import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessageListener;
-import cn.iocoder.yudao.module.mp.config.WxMpConfig;
-import cn.iocoder.yudao.module.mp.mq.message.dict.WxConfigDataRefreshMessage;
+import cn.iocoder.yudao.module.mp.mq.message.WxConfigDataRefreshMessage;
+import cn.iocoder.yudao.module.mp.service.account.WxAccountService;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Component;
 
@@ -18,12 +18,12 @@ import javax.annotation.Resource;
 public class WxConfigDataRefreshConsumer extends AbstractChannelMessageListener<WxConfigDataRefreshMessage> {
 
     @Resource
-    private WxMpConfig wxMpConfig;
+    private WxAccountService wxAccountService;
 
     @Override
     public void onMessage(WxConfigDataRefreshMessage message) {
         log.info("[onMessage][收到 WxConfigData 刷新消息]");
-        wxMpConfig.initWxConfig();
+        wxAccountService.initLoadWxMpConfigStorages();
     }
 
 }

+ 1 - 1
yudao-module-wechat/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/message/WxConfigDataRefreshMessage.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.module.mp.mq.message.dict;
+package cn.iocoder.yudao.module.mp.mq.message;
 
 import cn.iocoder.yudao.framework.mq.core.pubsub.AbstractChannelMessage;
 import lombok.Data;

+ 2 - 2
yudao-module-wechat/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/mq/producer/WxMpConfigDataProducer.java

@@ -1,7 +1,7 @@
-package cn.iocoder.yudao.module.mp.mq.producer.dict;
+package cn.iocoder.yudao.module.mp.mq.producer;
 
 import cn.iocoder.yudao.framework.mq.core.RedisMQTemplate;
-import cn.iocoder.yudao.module.mp.mq.message.dict.WxConfigDataRefreshMessage;
+import cn.iocoder.yudao.module.mp.mq.message.WxConfigDataRefreshMessage;
 import org.springframework.stereotype.Component;
 
 import javax.annotation.Resource;

+ 5 - 0
yudao-module-wechat/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/WxAccountService.java

@@ -81,4 +81,9 @@ public interface WxAccountService {
      * @return
      */
     WxAccountDO findBy(SFunction<WxAccountDO, ?> field, Object val);
+
+    /**
+     * 初始化
+     */
+    void initLoadWxMpConfigStorages();
 }

+ 65 - 3
yudao-module-wechat/yudao-module-mp-biz/src/main/java/cn/iocoder/yudao/module/mp/service/account/WxAccountServiceImpl.java

@@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.mp.service.account;
 
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.WxAccountCreateReqVO;
 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.WxAccountExportReqVO;
 import cn.iocoder.yudao.module.mp.controller.admin.account.vo.WxAccountPageReqVO;
@@ -10,30 +11,52 @@ import cn.iocoder.yudao.module.mp.convert.account.WxAccountConvert;
 import cn.iocoder.yudao.module.mp.dal.dataobject.account.WxAccountDO;
 import cn.iocoder.yudao.module.mp.dal.mysql.account.WxAccountMapper;
 import cn.iocoder.yudao.module.mp.enums.ErrorCodeConstants;
-import cn.iocoder.yudao.module.mp.mq.producer.dict.WxMpConfigDataProducer;
+import cn.iocoder.yudao.module.mp.mq.producer.WxMpConfigDataProducer;
 import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
+import com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;
+import lombok.extern.slf4j.Slf4j;
+import me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;
+import me.chanjar.weixin.mp.api.WxMpService;
+import me.chanjar.weixin.mp.config.WxMpConfigStorage;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.scheduling.annotation.Scheduled;
 import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
 import org.springframework.validation.annotation.Validated;
 
+import javax.annotation.PostConstruct;
 import javax.annotation.Resource;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 
 /**
  * 公众号账户 Service 实现类
  *
- * @author 芋道源码
+ * @author fengdan
  */
+@Slf4j
 @Service
 @Validated
 public class WxAccountServiceImpl implements WxAccountService {
 
+    private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L;
     @Resource
     private WxAccountMapper wxAccountMapper;
-
     @Resource
     private WxMpConfigDataProducer wxMpConfigDataProducer;
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+    @Resource
+    private WxMpService wxMpService;
+    @Resource
+    private WxMpProperties wxMpProperties;
+    @Resource
+    @Lazy // 注入自己,所以延迟加载
+    private WxAccountService self;
 
     @Override
     public Long createWxAccount(WxAccountCreateReqVO createReqVO) {
@@ -94,4 +117,43 @@ public class WxAccountServiceImpl implements WxAccountService {
     public WxAccountDO findBy(SFunction<WxAccountDO, ?> field, Object val) {
         return wxAccountMapper.selectOne(field, val);
     }
+
+    @PostConstruct
+    @TenantIgnore
+    @Override
+    public void initLoadWxMpConfigStorages() {
+        List<WxAccountDO> wxAccountList = this.wxAccountMapper.selectList();
+        if (CollectionUtils.isEmpty(wxAccountList)) {
+            log.info("未读取到公众号配置,请在管理后台添加");
+            return;
+        }
+        log.info("加载到{}条公众号配置", wxAccountList.size());
+        wxAccountList.forEach(account -> addAccountToRuntime(account, new RedisTemplateWxRedisOps(stringRedisTemplate)));
+        log.info("公众号配置加载完成");
+    }
+
+    /**
+     * 添加账号到当前程序,如首次添加需初始化configStorageMap
+     *
+     * @param account 公众号
+     */
+    private synchronized void addAccountToRuntime(WxAccountDO account, RedisTemplateWxRedisOps redisOps) {
+        String appId = account.getAppId();
+        WxMpConfigStorage wxMpRedisConfig = account.toWxMpConfigStorage(redisOps, wxMpProperties);
+        try {
+            wxMpService.addConfigStorage(appId, wxMpRedisConfig);
+        } catch (NullPointerException e) {
+            log.info("需初始化configStorageMap...");
+            Map<String, WxMpConfigStorage> configStorages = new HashMap<>(4);
+            configStorages.put(appId, wxMpRedisConfig);
+            wxMpService.setMultiConfigStorages(configStorages, appId);
+        }
+    }
+
+    @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
+    public void schedulePeriodicRefresh() {
+        self.initLoadWxMpConfigStorages();
+    }
+
+
 }