@@ -1,8 +1,10 @@
package cn.iocoder.dashboard.modules.system.service.dict.impl ;
import cn.hutool.core.collection.CollUtil ;
import cn.iocoder.dashboard.common.enums.CommonStatusEnum ;
import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil ;
import cn.iocoder.dashboard.common.pojo.PageResult ;
import cn.iocoder.dashboard.framework.mybatis.core.dataobject.BaseDO ;
import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataCreateReqVO ;
import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataExportReqVO ;
import cn.iocoder.dashboard.modules.system.controller.dict.vo.data.SysDictDataPageReqVO ;
@@ -11,15 +13,19 @@ import cn.iocoder.dashboard.modules.system.convert.dict.SysDictDataConvert;
import cn.iocoder.dashboard.modules.system.dal.mysql.dao.dict.SysDictDataMapper ;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dict.SysDictDataDO ;
import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dict.SysDictTypeDO ;
import cn.iocoder.dashboard.modules.system.mq.producer.dict.SysDictDataProducer ;
import cn.iocoder.dashboard.modules.system.service.dict.SysDictDataService ;
import cn.iocoder.dashboard.modules.system.service.dict.SysDictTypeService ;
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.ArrayList ;
import java.util.Comparator ;
import java.util.Date ;
import java.util.List ;
import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.* ;
@@ -30,12 +36,22 @@ import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*;
* @author ruoyi
*/
@Service
@Slf4j
public class SysDictDataServiceImpl implements SysDictDataService {
/**
* 排序 dictType > sort
*/
private static final Comparator < SysDictDataDO > COMPARATOR_TYPE_AND_SORT = Comparator
. comparing ( SysDictDataDO : : getDictType )
. thenComparingInt ( SysDictDataDO : : getSort ) ;
/**
* 定时执行 {@link #schedulePeriodicRefresh()} 的周期
* 因为已经通过 Redis Pub/Sub 机制,所以频率不需要高
*/
private static final long SCHEDULER_PERIOD = 5 * 60 * 1000L ;
/**
* 字典数据缓存,第二个 key 使用 label
*
@@ -50,6 +66,10 @@ public class SysDictDataServiceImpl implements SysDictDataService {
* key2: 字典值 value
*/
private ImmutableTable < String , String , SysDictDataDO > valueDictDataCache ;
/**
* 缓存字典数据的最大更新时间,用于后续的增量轮询,判断是否有更新
*/
private volatile Date maxUpdateTime ;
@Resource
private SysDictTypeService dictTypeService ;
@@ -57,20 +77,56 @@ public class SysDictDataServiceImpl implements SysDictDataService {
@Resource
private SysDictDataMapper dictDataMapper ;
@Resource
private SysDictDataProducer dictDataProducer ;
@Override
@PostConstruct
public void init ( ) {
// 获得 字典数据
List < SysDictDataDO > l ist = this . list DictDatas ( ) ;
public void initLocalCache ( ) {
// 获取 字典数据列表,如果有更新
List < SysDictDataDO > dataL ist = this . load DictDataIfUpdate ( maxUpdateTime ) ;
if ( CollUtil . isEmpty ( dataList ) ) {
return ;
}
// 构建缓存
ImmutableTable . Builder < String , String , SysDictDataDO > labelDictDataBuilder = ImmutableTable . builder ( ) ;
ImmutableTable . Builder < String , String , SysDictDataDO > valueDictDataBuilder = ImmutableTable . builder ( ) ;
l ist. forEach ( dictData - > {
dataL ist. 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 ( " [init][缓存字典数据,数量为:{}] " , dataList . size ( ) ) ;
}
@Scheduled ( fixedDelay = SCHEDULER_PERIOD , initialDelay = SCHEDULER_PERIOD )
public void schedulePeriodicRefresh ( ) {
initLocalCache ( ) ;
}
/**
* 如果字典数据发生变化,从数据库中获取最新的全量字典数据。
* 如果未发生变化,则返回空
*
* @param maxUpdateTime 当前字典数据的最大更新时间
* @return 字典数据列表
*/
private List < SysDictDataDO > loadDictDataIfUpdate ( Date maxUpdateTime ) {
// 第一步,判断是否要更新。
if ( maxUpdateTime = = null ) { // 如果更新时间为空,说明 DB 一定有新数据
log . info ( " [loadDictDataIfUpdate][首次加载全量字典数据] " ) ;
} else { // 判断数据库中是否有更新的字典数据
if ( ! dictDataMapper . selectExistsByUpdateTimeAfter ( maxUpdateTime ) ) {
return null ;
}
log . info ( " [loadDictDataIfUpdate][增量加载全量字典数据] " ) ;
}
// 第二步,如果有更新,则从数据库加载所有字典数据
return dictDataMapper . selectList ( ) ;
}
@Override
@@ -104,6 +160,8 @@ public class SysDictDataServiceImpl implements SysDictDataService {
// 插入字典类型
SysDictDataDO dictData = SysDictDataConvert . INSTANCE . convert ( reqVO ) ;
dictDataMapper . insert ( dictData ) ;
// 发送消息
dictDataProducer . sendMenuRefreshMessage ( ) ;
return dictData . getId ( ) ;
}
@@ -114,6 +172,8 @@ public class SysDictDataServiceImpl implements SysDictDataService {
// 更新字典类型
SysDictDataDO updateObj = SysDictDataConvert . INSTANCE . convert ( reqVO ) ;
dictDataMapper . updateById ( updateObj ) ;
// 发送消息
dictDataProducer . sendMenuRefreshMessage ( ) ;
}
@Override
@@ -122,6 +182,8 @@ public class SysDictDataServiceImpl implements SysDictDataService {
this . checkDictDataExists ( id ) ;
// 删除字典数据
dictDataMapper . deleteById ( id ) ;
// 发送消息
dictDataProducer . sendMenuRefreshMessage ( ) ;
}
@Override