Переглянути джерело

update:admin 租户点播配置

ct 1 тиждень тому
батько
коміт
50ed46cbde

+ 5 - 0
fs-admin/pom.xml

@@ -72,6 +72,11 @@
             <artifactId>mysql-connector-java</artifactId>
         </dependency>
 
+        <dependency>
+            <groupId>org.springframework.boot</groupId>
+            <artifactId>spring-boot-starter-cache</artifactId>
+        </dependency>
+
         <!-- 核心模块-->
         <dependency>
             <groupId>com.fs</groupId>

+ 2 - 0
fs-admin/src/main/java/com/fs/FSApplication.java

@@ -6,6 +6,7 @@ import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
 import org.redisson.spring.starter.RedissonAutoConfiguration;
 import org.springframework.context.annotation.ComponentScan;
 import org.springframework.context.annotation.FilterType;
+import org.springframework.cache.annotation.EnableCaching;
 import org.springframework.scheduling.annotation.EnableAsync;
 import org.springframework.scheduling.annotation.EnableScheduling;
 import org.springframework.transaction.annotation.Transactional;
@@ -21,6 +22,7 @@ import org.springframework.transaction.annotation.Transactional;
     }
 )
 @Transactional
+@EnableCaching
 @EnableAsync
 @EnableScheduling
 public class FSApplication {

+ 10 - 0
fs-admin/src/main/java/com/fs/admin/controller/CompanyAdminController.java

@@ -128,6 +128,16 @@ public class CompanyAdminController extends BaseController {
         return AjaxResult.success(list);
     }
 
+    /**
+     * 查询租户 id、名称、编码(轻量列表,供下拉/登录页等)
+     * 支持 pageNum、pageSize 分页
+     */
+    @GetMapping("/tenantList")
+    public TableDataInfo tenantList(TenantInfo tenantInfo) {
+        startPage();
+        return getDataTable(tenantInfoService.tenantList(tenantInfo));
+    }
+
     /**
      * 租户统计信息
      */

+ 13 - 10
fs-admin/src/main/java/com/fs/web/controller/system/SysConfigController.java

@@ -14,6 +14,7 @@ import com.fs.common.utils.SecurityUtils;
 import com.fs.common.utils.poi.ExcelUtil;
 import com.fs.system.domain.SysConfig;
 import com.fs.system.service.ISysConfigService;
+import com.fs.tenant.config.service.TenantSysConfigService;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
@@ -30,6 +31,8 @@ public class SysConfigController extends BaseController {
     @Autowired
     private ISysConfigService configService;
     @Autowired
+    private TenantSysConfigService tenantSysConfigService;
+    @Autowired
     public RedisCache redisCache;
 
     /**
@@ -120,24 +123,24 @@ public class SysConfigController extends BaseController {
         return AjaxResult.success();
     }
 
+    /**
+     * 按 configKey 查询参数。tenantId 有则查租户库,无则查主库;先 Redis 后库。
+     */
     @GetMapping(value = "/getConfigByKey/{configKey:.+}")
-    public AjaxResult getConfigByKey(@PathVariable String configKey) {
-        SysConfig config = configService.selectConfigByConfigKey(configKey);
-        return AjaxResult.success(config);
+    public AjaxResult getConfigByKey(@PathVariable String configKey,
+                                     @RequestParam(value = "tenantId", required = false) Long tenantId) {
+        return AjaxResult.success(tenantSysConfigService.getConfigByKey(tenantId, configKey));
     }
 
+    /**
+     * 按 configKey 新增或更新。body.tenantId 有则写租户库,无则写主库;写后同步 Redis。
+     */
     @PostMapping(value = "/updateConfigByKey")
     @Log(title = "更改参数", businessType = BusinessType.UPDATE)
     @RepeatSubmit
     public AjaxResult updateConfigByKey(@Validated @RequestBody SysConfig config) {
         config.setCreateBy(SecurityUtils.getUsername());
-        //修复只能更新的BUG
-        if (null != config.getConfigId()) {
-            return toAjax(configService.updateConfig(config));
-        } else {
-            return toAjax(configService.insertConfig(config));
-        }
-
+        return toAjax(tenantSysConfigService.updateConfigByKey(config));
     }
 
 

+ 30 - 0
fs-common/src/main/java/com/fs/common/config/RedisConfig.java

@@ -3,17 +3,22 @@ package com.fs.common.config;
 import com.fasterxml.jackson.annotation.JsonAutoDetect;
 import com.fasterxml.jackson.annotation.PropertyAccessor;
 import com.fasterxml.jackson.databind.ObjectMapper;
+import org.springframework.cache.CacheManager;
 import org.springframework.cache.annotation.CachingConfigurerSupport;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Primary;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
 import org.springframework.data.redis.connection.RedisConnectionFactory;
 import org.springframework.data.redis.core.RedisTemplate;
 import org.springframework.data.redis.core.script.DefaultRedisScript;
 import org.springframework.data.redis.serializer.GenericToStringSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
 import org.springframework.data.redis.serializer.RedisSerializer;
 
 import java.math.BigDecimal;
+import java.time.Duration;
 
 /**
  * Redis 配置类
@@ -38,6 +43,31 @@ public class RedisConfig extends CachingConfigurerSupport {
         return new TenantKeyRedisSerializer();
     }
 
+    /**
+     * Spring Cache(@Cacheable / @CacheEvict)使用与 RedisTemplate 相同的租户 Key 前缀
+     */
+    @Bean
+    public CacheManager cacheManager(
+            RedisConnectionFactory connectionFactory,
+            RedisSerializer<String> tenantKeySerializer) {
+        FastJson2JsonRedisSerializer<Object> valueSerializer =
+                new FastJson2JsonRedisSerializer<>(Object.class);
+        ObjectMapper mapper = new ObjectMapper();
+        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
+        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
+        valueSerializer.setObjectMapper(mapper);
+
+        RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
+                .entryTtl(Duration.ofDays(7))
+                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(tenantKeySerializer))
+                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer))
+                .disableCachingNullValues();
+
+        return RedisCacheManager.builder(connectionFactory)
+                .cacheDefaults(cacheConfiguration)
+                .build();
+    }
+
     /**
      * 通用 RedisTemplate(Object -> Object)
      * 用于大多数业务缓存场景

+ 13 - 0
fs-service/src/main/java/com/fs/system/domain/SysConfig.java

@@ -37,6 +37,19 @@ public class SysConfig extends BaseEntity
     @Excel(name = "系统内置", readConverterExp = "Y=是,N=否")
     private String configType;
 
+    /** 租户ID(非表字段,多租户读写 sys_config 时使用) */
+    private Long tenantId;
+
+    public Long getTenantId()
+    {
+        return tenantId;
+    }
+
+    public void setTenantId(Long tenantId)
+    {
+        this.tenantId = tenantId;
+    }
+
     public Long getConfigId()
     {
         return configId;

+ 31 - 0
fs-service/src/main/java/com/fs/tenant/config/SysConfigCacheDelegate.java

@@ -0,0 +1,31 @@
+package com.fs.tenant.config;
+
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cache.annotation.CacheEvict;
+import org.springframework.cache.annotation.Cacheable;
+import org.springframework.stereotype.Service;
+
+/**
+ * 参数配置 Redis 缓存代理(须独立 Bean,@Cacheable 才生效)。
+ * 租户前缀由 {@link com.fs.common.config.RedisTenantContext} + {@link com.fs.common.config.TenantKeyRedisSerializer} 处理。
+ */
+@Service
+public class SysConfigCacheDelegate {
+
+    public static final String CACHE_NAME = "sysConfigByKey";
+
+    @Autowired
+    private SysConfigMapper configMapper;
+
+    @Cacheable(value = CACHE_NAME, key = "#configKey", unless = "#result == null")
+    public SysConfig loadFromDb(String configKey) {
+        return configMapper.selectConfigByConfigKey(configKey);
+    }
+
+    @CacheEvict(value = CACHE_NAME, key = "#configKey")
+    public void evict(String configKey) {
+        // AOP 清理缓存 todo
+    }
+}

+ 19 - 0
fs-service/src/main/java/com/fs/tenant/config/service/TenantSysConfigService.java

@@ -0,0 +1,19 @@
+package com.fs.tenant.config.service;
+
+import com.fs.system.domain.SysConfig;
+
+/**
+ * 多租户参数配置(总后台按 tenantId 读写租户库/主库,带 Redis 缓存)
+ */
+public interface TenantSysConfigService {
+
+    /**
+     * 按 configKey 查询配置。tenantId 为空查主库,否则查对应租户库。
+     */
+    SysConfig getConfigByKey(Long tenantId, String configKey);
+
+    /**
+     * 按 configKey 新增或更新。config.tenantId 为空则操作主库,否则操作对应租户库。
+     */
+    int updateConfigByKey(SysConfig config);
+}

+ 94 - 0
fs-service/src/main/java/com/fs/tenant/config/service/impl/TenantSysConfigServiceImpl.java

@@ -0,0 +1,94 @@
+package com.fs.tenant.config.service.impl;
+
+import com.fs.common.constant.Constants;
+import com.fs.common.constant.UserConstants;
+import com.fs.common.core.redis.RedisCache;
+import com.fs.common.exception.ServiceException;
+import com.fs.common.utils.StringUtils;
+import com.fs.system.domain.SysConfig;
+import com.fs.system.mapper.SysConfigMapper;
+import com.fs.tenant.config.SysConfigCacheDelegate;
+import com.fs.tenant.config.service.TenantSysConfigService;
+import com.fs.tenant.dict.helper.TenantDictContextHelper;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+/**
+ * 多租户 sys_config 读写:统一切库 / Redis 租户上下文,查询走 @Cacheable,更新后驱逐并回写值缓存。
+ */
+@Service
+public class TenantSysConfigServiceImpl implements TenantSysConfigService {
+
+    @Autowired
+    private TenantDictContextHelper contextHelper;
+
+    @Autowired
+    private SysConfigCacheDelegate configCacheDelegate;
+
+    @Autowired
+    private SysConfigMapper configMapper;
+
+    @Autowired
+    private RedisCache redisCache;
+
+    @Override
+    public SysConfig getConfigByKey(Long tenantId, String configKey) {
+        if (StringUtils.isEmpty(configKey)) {
+            throw new ServiceException("参数键名不能为空");
+        }
+        if (tenantId == null) {
+            return contextHelper.executeInMaster(() -> configCacheDelegate.loadFromDb(configKey));
+        }
+        return contextHelper.executeInTenant(tenantId, () -> configCacheDelegate.loadFromDb(configKey));
+    }
+
+    @Override
+    public int updateConfigByKey(SysConfig config) {
+        if (config == null || StringUtils.isEmpty(config.getConfigKey())) {
+            throw new ServiceException("参数键名不能为空");
+        }
+        Long tenantId = config.getTenantId();
+        if (tenantId == null) {
+            return contextHelper.executeInMaster(() -> saveOrUpdateInContext(config));
+        }
+        return contextHelper.executeInTenant(tenantId, () -> saveOrUpdateInContext(config));
+    }
+
+    private int saveOrUpdateInContext(SysConfig config) {
+        if (UserConstants.NOT_UNIQUE.equals(checkConfigKeyUnique(config))) {
+            throw new ServiceException("参数键名'" + config.getConfigKey() + "'已存在");
+        }
+        int row;
+        if (config.getConfigId() != null) {
+            row = configMapper.updateConfig(config);
+        } else {
+            row = configMapper.insertConfig(config);
+        }
+        if (row > 0) {
+            syncRedisAfterWrite(config.getConfigKey(), config.getConfigValue());
+        }
+        return row;
+    }
+
+    private void syncRedisAfterWrite(String configKey, String configValue) {
+        configCacheDelegate.evict(configKey);
+        if (StringUtils.isNotEmpty(configValue)) {
+            redisCache.setCacheObject(valueCacheKey(configKey), configValue);
+        } else {
+            redisCache.deleteObject(valueCacheKey(configKey));
+        }
+    }
+
+    private String checkConfigKeyUnique(SysConfig config) {
+        Long configId = StringUtils.isNull(config.getConfigId()) ? -1L : config.getConfigId();
+        SysConfig info = configMapper.checkConfigKeyUnique(config.getConfigKey());
+        if (StringUtils.isNotNull(info) && info.getConfigId().longValue() != configId.longValue()) {
+            return UserConstants.NOT_UNIQUE;
+        }
+        return UserConstants.UNIQUE;
+    }
+
+    private static String valueCacheKey(String configKey) {
+        return Constants.SYS_CONFIG_KEY + configKey;
+    }
+}