diff --git a/pom.xml b/pom.xml index d6bb8301a..24c7d6741 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ https://github.com/YunaiV/ruoyi-vue-pro - 1.0.0 + 1.0.0-snapshot 1.8 ${java.version} diff --git a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/user/SysUserController.java b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/user/SysUserController.java index df54e7980..6de3f6b0c 100644 --- a/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/user/SysUserController.java +++ b/yudao-admin-server/src/main/java/cn/iocoder/yudao/adminserver/modules/system/controller/user/SysUserController.java @@ -167,7 +167,7 @@ public class SysUserController { @PreAuthorize("@ss.hasPermission('system:user:import')") public CommonResult importExcel(@RequestParam("file") MultipartFile file, @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception { - List list = ExcelUtils.raed(file, SysUserImportExcelVO.class); + List list = ExcelUtils.read(file, SysUserImportExcelVO.class); return success(userService.importUsers(list, updateSupport)); } diff --git a/yudao-dependencies/pom.xml b/yudao-dependencies/pom.xml index 5556b6be2..725fd1327 100644 --- a/yudao-dependencies/pom.xml +++ b/yudao-dependencies/pom.xml @@ -14,7 +14,7 @@ https://github.com/YunaiV/ruoyi-vue-pro - 1.0.0 + 1.1.0-snapshot 2.4.5 diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java index 0e9bfb4de..67d558f6b 100644 --- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java +++ b/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 { response.setContentType("application/vnd.ms-excel;charset=UTF-8"); } - public static List raed(MultipartFile file, Class head) throws IOException { + public static List read(MultipartFile file, Class head) throws IOException { return EasyExcel.read(file.getInputStream(), head, null) .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 .doReadAllSync(); diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/UserServerApplication.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/UserServerApplication.java index a26de72cc..edfbc63dc 100644 --- a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/UserServerApplication.java +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/UserServerApplication.java @@ -1,14 +1,9 @@ package cn.iocoder.yudao.userserver; -import cn.iocoder.yudao.framework.dict.config.YudaoDictAutoConfiguration; -import cn.iocoder.yudao.framework.security.config.YudaoSecurityAutoConfiguration; -import cn.iocoder.yudao.framework.security.config.YudaoWebSecurityConfigurerAdapter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; -@SpringBootApplication(exclude = { - YudaoDictAutoConfiguration.class, -}) +@SpringBootApplication public class UserServerApplication { public static void main(String[] args) { diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/convert/dict/SysDictDataConvert.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/convert/dict/SysDictDataConvert.java new file mode 100644 index 000000000..02155f698 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/convert/dict/SysDictDataConvert.java @@ -0,0 +1,20 @@ +package cn.iocoder.yudao.userserver.modules.system.convert.dict; + +import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO; +import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.dict.SysDictDataDO; +import org.mapstruct.Mapper; +import org.mapstruct.factory.Mappers; + +import java.util.Collection; +import java.util.List; + +@Mapper +public interface SysDictDataConvert { + + SysDictDataConvert INSTANCE = Mappers.getMapper(SysDictDataConvert.class); + + DictDataRespDTO convert02(SysDictDataDO bean); + + List convertList03(Collection list); + +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/convert/package-info.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/convert/package-info.java new file mode 100644 index 000000000..65ad8dc88 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/convert/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.userserver.modules.system.convert; diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/dict/SysDictDataDO.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/dict/SysDictDataDO.java new file mode 100644 index 000000000..eacec758f --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/dict/SysDictDataDO.java @@ -0,0 +1,54 @@ +package cn.iocoder.yudao.userserver.modules.system.dal.dataobject.dict; + +import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + * 字典数据表 + * + * @author ruoyi + */ +@TableName("sys_dict_data") +@Data +@EqualsAndHashCode(callSuper = true) +public class SysDictDataDO extends BaseDO { + + /** + * 字典数据编号 + */ + @TableId + private Long id; + /** + * 字典排序 + */ + private Integer sort; + /** + * 字典标签 + */ + private String label; + /** + * 字典值 + */ + private String value; + /** + * 字典类型 + * + * 冗余 {@link SysDictDataDO#getDictType()} + */ + private String dictType; + /** + * 状态 + * + * 枚举 {@link CommonStatusEnum} + */ + private Integer status; + /** + * 备注 + */ + private String remark; + +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/package-info.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/package-info.java new file mode 100644 index 000000000..0c99dcc95 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/dataobject/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.userserver.modules.system.dal.dataobject; diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/dict/SysDictDataMapper.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/dict/SysDictDataMapper.java new file mode 100644 index 000000000..928a53f7d --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/dict/SysDictDataMapper.java @@ -0,0 +1,28 @@ +package cn.iocoder.yudao.userserver.modules.system.dal.mysql.dict; + + +import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX; +import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.dict.SysDictDataDO; +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import org.apache.ibatis.annotations.Mapper; + +import java.util.Date; + +@Mapper +public interface SysDictDataMapper extends BaseMapperX { + + default SysDictDataDO selectByDictTypeAndValue(String dictType, String value) { + return selectOne(new QueryWrapper().eq("dict_type", dictType) + .eq("value", value)); + } + + default int selectCountByDictType(String dictType) { + return selectCount("dict_type", dictType); + } + + default boolean selectExistsByUpdateTimeAfter(Date maxUpdateTime) { + return selectOne(new QueryWrapper().select("id") + .gt("update_time", maxUpdateTime).last("LIMIT 1")) != null; + } + +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/package-info.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/package-info.java new file mode 100644 index 000000000..a1bdeadcd --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dal/mysql/package-info.java @@ -0,0 +1 @@ +package cn.iocoder.yudao.userserver.modules.system.dal.mysql; diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dict/SysDictDataService.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dict/SysDictDataService.java new file mode 100644 index 000000000..2efffe19e --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dict/SysDictDataService.java @@ -0,0 +1,17 @@ +package cn.iocoder.yudao.userserver.modules.system.dict; + +import cn.iocoder.yudao.framework.dict.core.service.DictDataFrameworkService; + +/** + * 字典数据 Service 接口 + * + * @author ruoyi + */ +public interface SysDictDataService extends DictDataFrameworkService { + + /** + * 初始化字典数据的本地缓存 + */ + void initLocalCache(); + +} diff --git a/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dict/impl/SysDictDataServiceImpl.java b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dict/impl/SysDictDataServiceImpl.java new file mode 100644 index 000000000..6e062eca7 --- /dev/null +++ b/yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/system/dict/impl/SysDictDataServiceImpl.java @@ -0,0 +1,122 @@ +package cn.iocoder.yudao.userserver.modules.system.dict.impl; + +import cn.hutool.core.collection.CollUtil; +import cn.iocoder.yudao.framework.dict.core.dto.DictDataRespDTO; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; +import cn.iocoder.yudao.userserver.modules.system.convert.dict.SysDictDataConvert; +import cn.iocoder.yudao.userserver.modules.system.dal.dataobject.dict.SysDictDataDO; +import cn.iocoder.yudao.userserver.modules.system.dal.mysql.dict.SysDictDataMapper; +import cn.iocoder.yudao.userserver.modules.system.dict.SysDictDataService; +import com.google.common.collect.ImmutableTable; +import lombok.extern.slf4j.Slf4j; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.util.Comparator; +import java.util.Date; +import java.util.List; + +/** + * 字典数据 Service 实现类 + * + * @author ruoyi + */ +@Service +@Slf4j +public class SysDictDataServiceImpl implements SysDictDataService { + + /** + * 定时执行 {@link #schedulePeriodicRefresh()} 的周期 + * 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高 + */ + private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L; + + /** + * 字典数据缓存,第二个 key 使用 label + * + * key1:字典类型 dictType + * key2:字典标签 label + */ + private ImmutableTable labelDictDataCache; + /** + * 字典数据缓存,第二个 key 使用 value + * + * key1:字典类型 dictType + * key2:字典值 value + */ + private ImmutableTable valueDictDataCache; + /** + * 缓存字典数据的最大更新时间,用于后续的增量轮询,判断是否有更新 + */ + private volatile Date maxUpdateTime; + + @Resource + private SysDictDataMapper dictDataMapper; + + @Override + @PostConstruct + public synchronized void initLocalCache() { + // 获取字典数据列表,如果有更新 + List dataList = this.loadDictDataIfUpdate(maxUpdateTime); + if (CollUtil.isEmpty(dataList)) { + return; + } + + // 构建缓存 + ImmutableTable.Builder labelDictDataBuilder = ImmutableTable.builder(); + ImmutableTable.Builder valueDictDataBuilder = ImmutableTable.builder(); + dataList.forEach(dictData -> { + labelDictDataBuilder.put(dictData.getDictType(), dictData.getLabel(), dictData); + valueDictDataBuilder.put(dictData.getDictType(), dictData.getValue(), dictData); + }); + labelDictDataCache = labelDictDataBuilder.build(); + valueDictDataCache = valueDictDataBuilder.build(); + assert dataList.size() > 0; // 断言,避免告警 + maxUpdateTime = dataList.stream().max(Comparator.comparing(BaseDO::getUpdateTime)).get().getUpdateTime(); + log.info("[initLocalCache][缓存字典数据,数量为:{}]", dataList.size()); + } + + @Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD) + public void schedulePeriodicRefresh() { + initLocalCache(); + } + + /** + * 如果字典数据发生变化,从数据库中获取最新的全量字典数据。 + * 如果未发生变化,则返回空 + * + * @param maxUpdateTime 当前字典数据的最大更新时间 + * @return 字典数据列表 + */ + private List loadDictDataIfUpdate(Date maxUpdateTime) { + // 第一步,判断是否要更新。 + if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据 + log.info("[loadDictDataIfUpdate][首次加载全量字典数据]"); + } else { // 判断数据库中是否有更新的字典数据 + if (!dictDataMapper.selectExistsByUpdateTimeAfter(maxUpdateTime)) { + return null; + } + log.info("[loadDictDataIfUpdate][增量加载全量字典数据]"); + } + // 第二步,如果有更新,则从数据库加载所有字典数据 + return dictDataMapper.selectList(); + } + + @Override + public DictDataRespDTO getDictDataFromCache(String type, String value) { + return SysDictDataConvert.INSTANCE.convert02(valueDictDataCache.get(type, value)); + } + + @Override + public DictDataRespDTO parseDictDataFromCache(String type, String label) { + return SysDictDataConvert.INSTANCE.convert02(labelDictDataCache.get(type, label)); + } + + @Override + public List listDictDatasFromCache(String type) { + return SysDictDataConvert.INSTANCE.convertList03(labelDictDataCache.row(type).values()); + } + +}