Procházet zdrojové kódy

动态切换配置文件

yh před 3 týdny
rodič
revize
89121afc33

+ 39 - 13
fs-framework/src/main/java/com/fs/framework/security/filter/JwtAuthenticationTokenFilter.java

@@ -1,26 +1,31 @@
 package com.fs.framework.security.filter;
 
-import java.io.IOException;
-import javax.servlet.FilterChain;
-import javax.servlet.ServletException;
-import javax.servlet.http.HttpServletRequest;
-import javax.servlet.http.HttpServletResponse;
-import javax.sql.DataSource;
-
-import com.alibaba.druid.pool.DruidDataSource;
+import com.alibaba.fastjson.JSONObject;
+import com.fs.common.core.domain.model.LoginUser;
 import com.fs.common.enums.DataSourceType;
+import com.fs.common.utils.SecurityUtils;
+import com.fs.common.utils.StringUtils;
+import com.fs.core.config.TenantConfigContext;
+import com.fs.core.config.WxMpProperties;
+import com.fs.core.config.WxPayProperties;
 import com.fs.framework.datasource.DynamicDataSource;
 import com.fs.framework.datasource.DynamicDataSourceContextHolder;
+import com.fs.framework.web.service.TokenService;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
 import org.springframework.stereotype.Component;
 import org.springframework.web.filter.OncePerRequestFilter;
-import com.fs.common.core.domain.model.LoginUser;
-import com.fs.common.utils.SecurityUtils;
-import com.fs.common.utils.StringUtils;
-import com.fs.framework.web.service.TokenService;
+
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.List;
 
 /**
  * token过滤器 验证token有效性
@@ -32,6 +37,10 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
 {
     @Autowired
     private TokenService tokenService;
+    @Autowired
+    private DynamicDataSource dynamicDataSource;
+    @Autowired
+    private SysConfigMapper sysConfigMapper;
 
 
     @Override
@@ -53,6 +62,15 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
                     DynamicDataSourceContextHolder.setDataSourceType(DataSourceType.MASTER.name());
                 }
 
+                SysConfig cfg = sysConfigMapper.selectConfigByConfigKey("projectConfig");
+                if (cfg != null && StringUtils.isNotBlank(cfg.getConfigValue())) {
+                    TenantConfigContext.set(JSONObject.parseObject(cfg.getConfigValue()));
+                } else {
+                    TenantConfigContext.set(null);
+                }
+
+                WxMpProperties.loadTenantConfigsFromContext();
+
                 tokenService.verifyToken(loginUser);
 
                 UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginUser, null, loginUser.getAuthorities());
@@ -62,8 +80,16 @@ public class JwtAuthenticationTokenFilter extends OncePerRequestFilter
             chain.doFilter(request, response);
         }
         finally {
+//            List<WxMpProperties.MpConfig> configs = wxMpProperties.getConfigs();
+//            for (WxMpProperties.MpConfig cfg : configs) {
+//                System.out.println("当前租户公众号 AppId = " + cfg.getAppId());
+//                System.out.println("当前租户公众号 Secret = " + cfg.getSecret());
+//                System.out.println("当前租户公众号 Token = " + cfg.getToken());
+//                System.out.println("当前租户公众号 AesKey = " + cfg.getAesKey());
+//            }
+            WxMpProperties.clearTenantConfigs();
+            TenantConfigContext.clear();
             DynamicDataSourceContextHolder.clearDataSourceType();
         }
     }
-
 }

+ 49 - 0
fs-service/src/main/java/com/fs/core/config/TenantConfigContext.java

@@ -0,0 +1,49 @@
+package com.fs.core.config;
+
+import com.alibaba.fastjson.JSONObject;
+
+public class TenantConfigContext {
+
+    private static final ThreadLocal<JSONObject> CONTEXT = new ThreadLocal<>();
+
+    public static void set(JSONObject config) {
+        CONTEXT.set(config);
+    }
+
+    public static JSONObject get() {
+        return CONTEXT.get();
+    }
+
+    public static void clear() {
+        CONTEXT.remove();
+    }
+
+    /**
+     * 支持 path:wx.pay.appId
+     */
+    public static Object getByPath(String path) {
+        JSONObject json = CONTEXT.get();
+        if (json == null || path == null) {
+            return null;
+        }
+
+        String[] keys = path.split("\\.");
+        Object cur = json;
+
+        for (String key : keys) {
+            if (!(cur instanceof JSONObject)) {
+                return null;
+            }
+            cur = ((JSONObject) cur).get(key);
+            if (cur == null) {
+                return null;
+            }
+        }
+        return cur;
+    }
+
+    public static String getString(String path) {
+        Object val = getByPath(path);
+        return val == null ? null : String.valueOf(val);
+    }
+}

+ 50 - 22
fs-service/src/main/java/com/fs/core/config/WxMpConfiguration.java

@@ -9,6 +9,7 @@ import me.chanjar.weixin.mp.config.impl.WxMpDefaultConfigImpl;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Scope;
 
 import java.util.List;
 import java.util.stream.Collectors;
@@ -39,36 +40,63 @@ public class WxMpConfiguration {
     private final WxMpProperties properties;
 
     @Bean
+    @Scope("prototype") // 每次获取新的 WxMpService 实例,避免单例缓存导致多租户问题
     public WxMpService wxMpService() {
-        // 代码里 getConfigs()处报错的同学,请注意仔细阅读项目说明,你的IDE需要引入lombok插件!!!!
+        // 从 WxMpProperties 获取当前请求租户配置
         final List<WxMpProperties.MpConfig> configs = this.properties.getConfigs();
-        if (configs == null) {
-            throw new RuntimeException("大哥,拜托先看下项目首页的说明(readme文件),添加下相关配置,注意别配错了!");
+
+        if (configs == null || configs.isEmpty()) {
+            throw new RuntimeException("当前租户未配置微信公众号!请检查 TenantConfigContext 或 yml 配置");
         }
 
         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 = 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)));
+
+        // 遍历 configs 构建多公众号存储
+        service.setMultiConfigStorages(
+                configs.stream().map(a -> {
+                    WxMpDefaultConfigImpl 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 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 = 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);

+ 62 - 20
fs-service/src/main/java/com/fs/core/config/WxMpProperties.java

@@ -1,8 +1,11 @@
 package com.fs.core.config;
 
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -13,6 +16,7 @@ import java.util.List;
 @Data
 @ConfigurationProperties(prefix = "wx.mp")
 public class WxMpProperties {
+
     /**
      * 是否使用redis存储access token
      */
@@ -23,6 +27,38 @@ public class WxMpProperties {
      */
     private RedisConfig redisConfig;
 
+    /**
+     * 默认 yml 配置
+     */
+    private List<MpConfig> configs;
+
+    /**
+     * 当前请求租户的配置副本,ThreadLocal
+     */
+    private static final ThreadLocal<List<MpConfig>> TENANT_CONFIGS = new ThreadLocal<>();
+
+    /**
+     * 设置当前请求租户配置
+     */
+    public static void setTenantConfigs(List<MpConfig> tenantConfigs) {
+        TENANT_CONFIGS.set(tenantConfigs);
+    }
+
+    /**
+     * 清理 ThreadLocal
+     */
+    public static void clearTenantConfigs() {
+        TENANT_CONFIGS.remove();
+    }
+
+    /**
+     * 遍历 configs 时,优先返回当前请求租户配置
+     */
+    public List<MpConfig> getConfigs() {
+        List<MpConfig> tenantCfgs = TENANT_CONFIGS.get();
+        return tenantCfgs != null ? tenantCfgs : configs;
+    }
+
     @Data
     public static class RedisConfig {
         /**
@@ -46,33 +82,39 @@ public class WxMpProperties {
         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;
     }
 
+    /**
+     * 从 TenantConfigContext 生成当前请求租户 configs 副本
+     */
+    public static void loadTenantConfigsFromContext() {
+        JSONObject tenantCfg = TenantConfigContext.get();
+        List<MpConfig> mpConfigs = new ArrayList<>();
 
+        if (tenantCfg != null) {
+            JSONObject mp = tenantCfg.getJSONObject("wx").getJSONObject("mp");
+            if (mp != null) {
+                JSONArray arr = mp.getJSONArray("configs");
+                if (arr != null) {
+                    for (int i = 0; i < arr.size(); i++) {
+                        JSONObject cfgJson = arr.getJSONObject(i);
+                        MpConfig cfg = new MpConfig();
+                        cfg.setAppId(cfgJson.getString("appId"));
+                        cfg.setSecret(cfgJson.getString("secret"));
+                        cfg.setToken(cfgJson.getString("token"));
+                        cfg.setAesKey(cfgJson.getString("aesKey"));
+                        mpConfigs.add(cfg);
+                    }
+                }
+            }
+        }
+
+        setTenantConfigs(mpConfigs);
+    }
 }

+ 11 - 0
fs-service/src/main/java/com/fs/core/config/WxOpenProperties.java

@@ -1,6 +1,7 @@
 package com.fs.core.config;
 
 import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 import org.springframework.context.annotation.Configuration;
 
@@ -12,4 +13,14 @@ public class WxOpenProperties {
     private String appId;
 
     private String secret;
+
+    public String getAppId() {
+        String tenantVal = TenantConfigContext.getString("wx.open.appId");
+        return StringUtils.isNotBlank(tenantVal) ? tenantVal : appId;
+    }
+
+    public String getSecret() {
+        String tenantVal = TenantConfigContext.getString("wx.open.secret");
+        return StringUtils.isNotBlank(tenantVal) ? tenantVal : secret;
+    }
 }

+ 30 - 28
fs-service/src/main/java/com/fs/core/config/WxPayProperties.java

@@ -1,6 +1,7 @@
 package com.fs.core.config;
 
 import lombok.Data;
+import org.apache.commons.lang3.StringUtils;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 
 @ConfigurationProperties(prefix = "wx.pay")
@@ -48,59 +49,60 @@ public class WxPayProperties {
 
   private String nativeNotifyUrl;
 
-  public String getAppId() {
-    return appId;
-  }
+  /* ===================== 动态 getter ===================== */
 
-  public void setAppId(String appId) {
-    this.appId = appId;
+  public String getAppId() {
+    return tenantOrDefault("wx.pay.appId", appId);
   }
 
   public String getMchId() {
-    return mchId;
-  }
-
-  public void setMchId(String mchId) {
-    this.mchId = mchId;
+    return tenantOrDefault("wx.pay.mchId", mchId);
   }
 
   public String getMchKey() {
-    return mchKey;
+    return tenantOrDefault("wx.pay.mchKey", mchKey);
   }
 
-  public void setMchKey(String mchKey) {
-    this.mchKey = mchKey;
+  public String getV3Key() {
+    return tenantOrDefault("wx.pay.v3Key", v3Key);
   }
 
   public String getSubAppId() {
-    return subAppId;
+    return tenantOrDefault("wx.pay.subAppId", subAppId);
   }
 
-  public void setSubAppId(String subAppId) {
-    this.subAppId = subAppId;
+  public String getSubMchId() {
+    return tenantOrDefault("wx.pay.subMchId", subMchId);
   }
 
-  public String getSubMchId() {
-    return subMchId;
+  public String getKeyPath() {
+    return tenantOrDefault("wx.pay.keyPath", keyPath);
   }
 
-  public void setSubMchId(String subMchId) {
-    this.subMchId = subMchId;
+  public String getNotifyUrl() {
+    return tenantOrDefault("wx.pay.notifyUrl", notifyUrl);
   }
 
-  public String getKeyPath() {
-    return keyPath;
+  public String getPrivateKeyPath() {
+    return tenantOrDefault("wx.pay.privateKeyPath", privateKeyPath);
   }
 
-  public void setKeyPath(String keyPath) {
-    this.keyPath = keyPath;
+  public String getPrivateCertPath() {
+    return tenantOrDefault("wx.pay.privateCertPath", privateCertPath);
   }
 
-  public String getNotifyUrl() {
-    return notifyUrl;
+  public String getCertSerialNo() {
+    return tenantOrDefault("wx.pay.certSerialNo", certSerialNo);
   }
 
-  public void setNotifyUrl(String notifyUrl) {
-    this.notifyUrl = notifyUrl;
+  public String getNativeNotifyUrl() {
+    return tenantOrDefault("wx.pay.nativeNotifyUrl", nativeNotifyUrl);
+  }
+
+  /* ===================== 公共兜底方法 ===================== */
+
+  private String tenantOrDefault(String jsonPath, String defaultValue) {
+    String tenantValue = TenantConfigContext.getString(jsonPath);
+    return StringUtils.isNotBlank(tenantValue) ? tenantValue : defaultValue;
   }
 }