Commit 0439a5505afdde71ec3543d708349bfeba776719
1 parent
15e2c094
初始化数据字典
Showing
13 changed files
with
249 additions
and
10 deletions
pom.xml
| @@ -19,7 +19,7 @@ | @@ -19,7 +19,7 @@ | ||
| 19 | <url>https://github.com/YunaiV/ruoyi-vue-pro</url> | 19 | <url>https://github.com/YunaiV/ruoyi-vue-pro</url> |
| 20 | 20 | ||
| 21 | <properties> | 21 | <properties> |
| 22 | - <revision>1.0.0</revision> | 22 | + <revision>1.0.0-snapshot</revision> |
| 23 | <!-- Maven 相关 --> | 23 | <!-- Maven 相关 --> |
| 24 | <java.version>1.8</java.version> | 24 | <java.version>1.8</java.version> |
| 25 | <maven.compiler.source>${java.version}</maven.compiler.source> | 25 | <maven.compiler.source>${java.version}</maven.compiler.source> |
yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/user/SysUserController.java
| @@ -167,7 +167,7 @@ public class SysUserController { | @@ -167,7 +167,7 @@ public class SysUserController { | ||
| 167 | @PreAuthorize("@ss.hasPermission('system:user:import')") | 167 | @PreAuthorize("@ss.hasPermission('system:user:import')") |
| 168 | public CommonResult<SysUserImportRespVO> importExcel(@RequestParam("file") MultipartFile file, | 168 | public CommonResult<SysUserImportRespVO> importExcel(@RequestParam("file") MultipartFile file, |
| 169 | @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception { | 169 | @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception { |
| 170 | - List<SysUserImportExcelVO> list = ExcelUtils.raed(file, SysUserImportExcelVO.class); | 170 | + List<SysUserImportExcelVO> list = ExcelUtils.read(file, SysUserImportExcelVO.class); |
| 171 | return success(userService.importUsers(list, updateSupport)); | 171 | return success(userService.importUsers(list, updateSupport)); |
| 172 | } | 172 | } |
| 173 | 173 |
yudao-dependencies/pom.xml
| @@ -14,7 +14,7 @@ | @@ -14,7 +14,7 @@ | ||
| 14 | <url>https://github.com/YunaiV/ruoyi-vue-pro</url> | 14 | <url>https://github.com/YunaiV/ruoyi-vue-pro</url> |
| 15 | 15 | ||
| 16 | <properties> | 16 | <properties> |
| 17 | - <revision>1.0.0</revision> | 17 | + <revision>1.1.0-snapshot</revision> |
| 18 | <!-- 统一依赖管理 --> | 18 | <!-- 统一依赖管理 --> |
| 19 | <spring.boot.version>2.4.5</spring.boot.version> | 19 | <spring.boot.version>2.4.5</spring.boot.version> |
| 20 | <!-- Web 相关 --> | 20 | <!-- Web 相关 --> |
yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
| @@ -39,7 +39,7 @@ public class ExcelUtils { | @@ -39,7 +39,7 @@ public class ExcelUtils { | ||
| 39 | response.setContentType("application/vnd.ms-excel;charset=UTF-8"); | 39 | response.setContentType("application/vnd.ms-excel;charset=UTF-8"); |
| 40 | } | 40 | } |
| 41 | 41 | ||
| 42 | - public static <T> List<T> raed(MultipartFile file, Class<T> head) throws IOException { | 42 | + public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException { |
| 43 | return EasyExcel.read(file.getInputStream(), head, null) | 43 | return EasyExcel.read(file.getInputStream(), head, null) |
| 44 | .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 | 44 | .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 |
| 45 | .doReadAllSync(); | 45 | .doReadAllSync(); |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/UserServerApplication.java
| 1 | package cn.iocoder.yudao.userserver; | 1 | package cn.iocoder.yudao.userserver; |
| 2 | 2 | ||
| 3 | -import cn.iocoder.yudao.framework.dict.config.YudaoDictAutoConfiguration; | ||
| 4 | -import cn.iocoder.yudao.framework.security.config.YudaoSecurityAutoConfiguration; | ||
| 5 | -import cn.iocoder.yudao.framework.security.config.YudaoWebSecurityConfigurerAdapter; | ||
| 6 | import org.springframework.boot.SpringApplication; | 3 | import org.springframework.boot.SpringApplication; |
| 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; | 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; |
| 8 | 5 | ||
| 9 | -@SpringBootApplication(exclude = { | ||
| 10 | - YudaoDictAutoConfiguration.class, | ||
| 11 | -}) | 6 | +@SpringBootApplication |
| 12 | public class UserServerApplication { | 7 | public class UserServerApplication { |
| 13 | 8 | ||
| 14 | public static void main(String[] args) { | 9 | public static void main(String[] args) { |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/convert/dict/SysDictDataConvert.java
0 → 100644
| 1 | +package cn.iocoder.yudao.userserver.modules.system.convert.dict; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO; | ||
| 4 | +import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.dict.SysDictDataDO; | ||
| 5 | +import org.mapstruct.Mapper; | ||
| 6 | +import org.mapstruct.factory.Mappers; | ||
| 7 | + | ||
| 8 | +import java.util.Collection; | ||
| 9 | +import java.util.List; | ||
| 10 | + | ||
| 11 | +@Mapper | ||
| 12 | +public interface SysDictDataConvert { | ||
| 13 | + | ||
| 14 | + SysDictDataConvert INSTANCE = Mappers.getMapper(SysDictDataConvert.class); | ||
| 15 | + | ||
| 16 | + DictDataRespDTO convert02(SysDictDataDO bean); | ||
| 17 | + | ||
| 18 | + List<DictDataRespDTO> convertList03(Collection<SysDictDataDO> list); | ||
| 19 | + | ||
| 20 | +} |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/convert/package-info.java
0 → 100644
| 1 | +package cn.iocoder.yudao.userserver.modules.system.convert; |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/dict/SysDictDataDO.java
0 → 100644
| 1 | +package cn.iocoder.yudao.userserver.modules.system.dal.dataobject.dict; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; | ||
| 4 | +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||
| 5 | +import com.baomidou.mybatisplus.annotation.TableId; | ||
| 6 | +import com.baomidou.mybatisplus.annotation.TableName; | ||
| 7 | +import lombok.Data; | ||
| 8 | +import lombok.EqualsAndHashCode; | ||
| 9 | + | ||
| 10 | +/** | ||
| 11 | + * 字典数据表 | ||
| 12 | + * | ||
| 13 | + * @author ruoyi | ||
| 14 | + */ | ||
| 15 | +@TableName("sys_dict_data") | ||
| 16 | +@Data | ||
| 17 | +@EqualsAndHashCode(callSuper = true) | ||
| 18 | +public class SysDictDataDO extends BaseDO { | ||
| 19 | + | ||
| 20 | + /** | ||
| 21 | + * 字典数据编号 | ||
| 22 | + */ | ||
| 23 | + @TableId | ||
| 24 | + private Long id; | ||
| 25 | + /** | ||
| 26 | + * 字典排序 | ||
| 27 | + */ | ||
| 28 | + private Integer sort; | ||
| 29 | + /** | ||
| 30 | + * 字典标签 | ||
| 31 | + */ | ||
| 32 | + private String label; | ||
| 33 | + /** | ||
| 34 | + * 字典值 | ||
| 35 | + */ | ||
| 36 | + private String value; | ||
| 37 | + /** | ||
| 38 | + * 字典类型 | ||
| 39 | + * | ||
| 40 | + * 冗余 {@link SysDictDataDO#getDictType()} | ||
| 41 | + */ | ||
| 42 | + private String dictType; | ||
| 43 | + /** | ||
| 44 | + * 状态 | ||
| 45 | + * | ||
| 46 | + * 枚举 {@link CommonStatusEnum} | ||
| 47 | + */ | ||
| 48 | + private Integer status; | ||
| 49 | + /** | ||
| 50 | + * 备注 | ||
| 51 | + */ | ||
| 52 | + private String remark; | ||
| 53 | + | ||
| 54 | +} |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/package-info.java
0 → 100644
| 1 | +package cn.iocoder.yudao.userserver.modules.system.dal.dataobject; |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/dict/SysDictDataMapper.java
0 → 100644
| 1 | +package cn.iocoder.yudao.userserver.modules.system.dal.mysql.dict; | ||
| 2 | + | ||
| 3 | + | ||
| 4 | +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; | ||
| 5 | +import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.dict.SysDictDataDO; | ||
| 6 | +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; | ||
| 7 | +import org.apache.ibatis.annotations.Mapper; | ||
| 8 | + | ||
| 9 | +import java.util.Date; | ||
| 10 | + | ||
| 11 | +@Mapper | ||
| 12 | +public interface SysDictDataMapper extends BaseMapperX<SysDictDataDO> { | ||
| 13 | + | ||
| 14 | + default SysDictDataDO selectByDictTypeAndValue(String dictType, String value) { | ||
| 15 | + return selectOne(new QueryWrapper<SysDictDataDO>().eq("dict_type", dictType) | ||
| 16 | + .eq("value", value)); | ||
| 17 | + } | ||
| 18 | + | ||
| 19 | + default int selectCountByDictType(String dictType) { | ||
| 20 | + return selectCount("dict_type", dictType); | ||
| 21 | + } | ||
| 22 | + | ||
| 23 | + default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) { | ||
| 24 | + return selectOne(new QueryWrapper<SysDictDataDO>().select("id") | ||
| 25 | + .gt("update_time", maxUpdateTime).last("LIMIT 1")) != null; | ||
| 26 | + } | ||
| 27 | + | ||
| 28 | +} |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/package-info.java
0 → 100644
| 1 | +package cn.iocoder.yudao.userserver.modules.system.dal.mysql; |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dict/SysDictDataService.java
0 → 100644
| 1 | +package cn.iocoder.yudao.userserver.modules.system.dict; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.dict.core.service.DictDataFrameworkService; | ||
| 4 | + | ||
| 5 | +/** | ||
| 6 | + * 字典数据 Service 接口 | ||
| 7 | + * | ||
| 8 | + * @author ruoyi | ||
| 9 | + */ | ||
| 10 | +public interface SysDictDataService extends DictDataFrameworkService { | ||
| 11 | + | ||
| 12 | + /** | ||
| 13 | + * 初始化字典数据的本地缓存 | ||
| 14 | + */ | ||
| 15 | + void initLocalCache(); | ||
| 16 | + | ||
| 17 | +} |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dict/impl/SysDictDataServiceImpl.java
0 → 100644
| 1 | +package cn.iocoder.yudao.userserver.modules.system.dict.impl; | ||
| 2 | + | ||
| 3 | +import cn.hutool.core.collection.CollUtil; | ||
| 4 | +import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO; | ||
| 5 | +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; | ||
| 6 | +import cn.iocoder.yudao.userserver.modules.system.convert.dict.SysDictDataConvert; | ||
| 7 | +import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.dict.SysDictDataDO; | ||
| 8 | +import cn.iocoder.yudao.userserver.modules.system.dal.mysql.dict.SysDictDataMapper; | ||
| 9 | +import cn.iocoder.yudao.userserver.modules.system.dict.SysDictDataService; | ||
| 10 | +import com.google.common.collect.ImmutableTable; | ||
| 11 | +import lombok.extern.slf4j.Slf4j; | ||
| 12 | +import org.springframework.scheduling.annotation.Scheduled; | ||
| 13 | +import org.springframework.stereotype.Service; | ||
| 14 | + | ||
| 15 | +import javax.annotation.PostConstruct; | ||
| 16 | +import javax.annotation.Resource; | ||
| 17 | +import java.util.Comparator; | ||
| 18 | +import java.util.Date; | ||
| 19 | +import java.util.List; | ||
| 20 | + | ||
| 21 | +/** | ||
| 22 | + * 字典数据 Service 实现类 | ||
| 23 | + * | ||
| 24 | + * @author ruoyi | ||
| 25 | + */ | ||
| 26 | +@Service | ||
| 27 | +@Slf4j | ||
| 28 | +public class SysDictDataServiceImpl implements SysDictDataService { | ||
| 29 | + | ||
| 30 | + /** | ||
| 31 | + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期 | ||
| 32 | + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高 | ||
| 33 | + */ | ||
| 34 | + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L; | ||
| 35 | + | ||
| 36 | + /** | ||
| 37 | + * 字典数据缓存,第二个 key 使用 label | ||
| 38 | + * | ||
| 39 | + * key1:字典类型 dictType | ||
| 40 | + * key2:字典标签 label | ||
| 41 | + */ | ||
| 42 | + private ImmutableTable<String, String, SysDictDataDO> labelDictDataCache; | ||
| 43 | + /** | ||
| 44 | + * 字典数据缓存,第二个 key 使用 value | ||
| 45 | + * | ||
| 46 | + * key1:字典类型 dictType | ||
| 47 | + * key2:字典值 value | ||
| 48 | + */ | ||
| 49 | + private ImmutableTable<String, String, SysDictDataDO> valueDictDataCache; | ||
| 50 | + /** | ||
| 51 | + * 缓存字典数据的最大更新时间,用于后续的增量轮询,判断是否有更新 | ||
| 52 | + */ | ||
| 53 | + private volatile Date maxUpdateTime; | ||
| 54 | + | ||
| 55 | + @Resource | ||
| 56 | + private SysDictDataMapper dictDataMapper; | ||
| 57 | + | ||
| 58 | + @Override | ||
| 59 | + @PostConstruct | ||
| 60 | + public synchronized void initLocalCache() { | ||
| 61 | + // 获取字典数据列表,如果有更新 | ||
| 62 | + List<SysDictDataDO> dataList = this.loadDictDataIfUpdate(maxUpdateTime); | ||
| 63 | + if (CollUtil.isEmpty(dataList)) { | ||
| 64 | + return; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + // 构建缓存 | ||
| 68 | + ImmutableTable.Builder<String, String, SysDictDataDO> labelDictDataBuilder = ImmutableTable.builder(); | ||
| 69 | + ImmutableTable.Builder<String, String, SysDictDataDO> valueDictDataBuilder = ImmutableTable.builder(); | ||
| 70 | + dataList.forEach(dictData -> { | ||
| 71 | + labelDictDataBuilder.put(dictData.getDictType(), dictData.getLabel(), dictData); | ||
| 72 | + valueDictDataBuilder.put(dictData.getDictType(), dictData.getValue(), dictData); | ||
| 73 | + }); | ||
| 74 | + labelDictDataCache = labelDictDataBuilder.build(); | ||
| 75 | + valueDictDataCache = valueDictDataBuilder.build(); | ||
| 76 | + assert dataList.size() > 0; // 断言,避免告警 | ||
| 77 | + maxUpdateTime = dataList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); | ||
| 78 | + log.info("[initLocalCache][缓存字典数据,数量为:{}]", dataList.size()); | ||
| 79 | + } | ||
| 80 | + | ||
| 81 | + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) | ||
| 82 | + public void schedulePeriodicRefresh() { | ||
| 83 | + initLocalCache(); | ||
| 84 | + } | ||
| 85 | + | ||
| 86 | + /** | ||
| 87 | + * 如果字典数据发生变化,从数据库中获取最新的全量字典数据。 | ||
| 88 | + * 如果未发生变化,则返回空 | ||
| 89 | + * | ||
| 90 | + * @param maxUpdateTime 当前字典数据的最大更新时间 | ||
| 91 | + * @return 字典数据列表 | ||
| 92 | + */ | ||
| 93 | + private List<SysDictDataDO> loadDictDataIfUpdate(Date maxUpdateTime) { | ||
| 94 | + // 第一步,判断是否要更新。 | ||
| 95 | + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据 | ||
| 96 | + log.info("[loadDictDataIfUpdate][首次加载全量字典数据]"); | ||
| 97 | + } else { // 判断数据库中是否有更新的字典数据 | ||
| 98 | + if (!dictDataMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) { | ||
| 99 | + return null; | ||
| 100 | + } | ||
| 101 | + log.info("[loadDictDataIfUpdate][增量加载全量字典数据]"); | ||
| 102 | + } | ||
| 103 | + // 第二步,如果有更新,则从数据库加载所有字典数据 | ||
| 104 | + return dictDataMapper.selectList(); | ||
| 105 | + } | ||
| 106 | + | ||
| 107 | + @Override | ||
| 108 | + public DictDataRespDTO getDictDataFromCache(String type, String value) { | ||
| 109 | + return SysDictDataConvert.INSTANCE.convert02(valueDictDataCache.get(type, value)); | ||
| 110 | + } | ||
| 111 | + | ||
| 112 | + @Override | ||
| 113 | + public DictDataRespDTO parseDictDataFromCache(String type, String label) { | ||
| 114 | + return SysDictDataConvert.INSTANCE.convert02(labelDictDataCache.get(type, label)); | ||
| 115 | + } | ||
| 116 | + | ||
| 117 | + @Override | ||
| 118 | + public List<DictDataRespDTO> listDictDatasFromCache(String type) { | ||
| 119 | + return SysDictDataConvert.INSTANCE.convertList03(labelDictDataCache.row(type).values()); | ||
| 120 | + } | ||
| 121 | + | ||
| 122 | +} |