Commit 0439a5505afdde71ec3543d708349bfeba776719
1 parent
15e2c094
初始化数据字典
Showing
13 changed files
with
249 additions
and
10 deletions
pom.xml
| ... | ... | @@ -19,7 +19,7 @@ |
| 19 | 19 | <url>https://github.com/YunaiV/ruoyi-vue-pro</url> |
| 20 | 20 | |
| 21 | 21 | <properties> |
| 22 | - <revision>1.0.0</revision> | |
| 22 | + <revision>1.0.0-snapshot</revision> | |
| 23 | 23 | <!-- Maven 相关 --> |
| 24 | 24 | <java.version>1.8</java.version> |
| 25 | 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 | 167 | @PreAuthorize("@ss.hasPermission('system:user:import')") |
| 168 | 168 | public CommonResult<SysUserImportRespVO> importExcel(@RequestParam("file") MultipartFile file, |
| 169 | 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 | 171 | return success(userService.importUsers(list, updateSupport)); |
| 172 | 172 | } |
| 173 | 173 | ... | ... |
yudao-dependencies/pom.xml
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 | 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 | 43 | return EasyExcel.read(file.getInputStream(), head, null) |
| 44 | 44 | .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 |
| 45 | 45 | .doReadAllSync(); | ... | ... |
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/UserServerApplication.java
| 1 | 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 | 3 | import org.springframework.boot.SpringApplication; |
| 7 | 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; |
| 8 | 5 | |
| 9 | -@SpringBootApplication(exclude = { | |
| 10 | - YudaoDictAutoConfiguration.class, | |
| 11 | -}) | |
| 6 | +@SpringBootApplication | |
| 12 | 7 | public class UserServerApplication { |
| 13 | 8 | |
| 14 | 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 | +} | ... | ... |