优化本地缓存的刷新实现,数据变更时,强制刷新
This commit is contained in:
parent
81340a636b
commit
6f8ca56e16
@ -1,6 +1,7 @@
|
|||||||
package cn.iocoder.yudao.framework.tenant.core.aop;
|
package cn.iocoder.yudao.framework.tenant.core.aop;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||||
|
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.aspectj.lang.ProceedingJoinPoint;
|
import org.aspectj.lang.ProceedingJoinPoint;
|
||||||
import org.aspectj.lang.annotation.Around;
|
import org.aspectj.lang.annotation.Around;
|
||||||
@ -11,6 +12,8 @@ import org.aspectj.lang.annotation.Aspect;
|
|||||||
* 例如说,一个定时任务,读取所有数据,进行处理。
|
* 例如说,一个定时任务,读取所有数据,进行处理。
|
||||||
* 又例如说,读取所有数据,进行缓存。
|
* 又例如说,读取所有数据,进行缓存。
|
||||||
*
|
*
|
||||||
|
* 整体逻辑的实现,和 {@link TenantUtils#executeIgnore(Runnable)} 需要保持一致
|
||||||
|
*
|
||||||
* @author 芋道源码
|
* @author 芋道源码
|
||||||
*/
|
*/
|
||||||
@Aspect
|
@Aspect
|
||||||
|
@ -34,6 +34,22 @@ public class TenantUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 忽略租户,执行对应的逻辑
|
||||||
|
*
|
||||||
|
* @param runnable 逻辑
|
||||||
|
*/
|
||||||
|
public static void executeIgnore(Runnable runnable) {
|
||||||
|
Boolean oldIgnore = TenantContextHolder.isIgnore();
|
||||||
|
try {
|
||||||
|
TenantContextHolder.setIgnore(true);
|
||||||
|
// 执行逻辑
|
||||||
|
runnable.run();
|
||||||
|
} finally {
|
||||||
|
TenantContextHolder.setIgnore(oldIgnore);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 使用指定租户,执行对应的逻辑
|
* 使用指定租户,执行对应的逻辑
|
||||||
*
|
*
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package cn.iocoder.yudao.module.infra.service.file;
|
package cn.iocoder.yudao.module.infra.service.file;
|
||||||
|
|
||||||
import cn.hutool.core.collection.CollUtil;
|
|
||||||
import cn.hutool.core.io.resource.ResourceUtil;
|
import cn.hutool.core.io.resource.ResourceUtil;
|
||||||
import cn.hutool.core.util.IdUtil;
|
import cn.hutool.core.util.IdUtil;
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
@ -20,7 +19,6 @@ import cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper;
|
|||||||
import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
|
import cn.iocoder.yudao.module.infra.mq.producer.file.FileConfigProducer;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.annotation.Lazy;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@ -79,20 +77,36 @@ public class FileConfigServiceImpl implements FileConfigService {
|
|||||||
@Resource
|
@Resource
|
||||||
private Validator validator;
|
private Validator validator;
|
||||||
|
|
||||||
@Resource
|
|
||||||
@Lazy // 注入自己,所以延迟加载
|
|
||||||
private FileConfigService self;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void initFileClients() {
|
public void initFileClients() {
|
||||||
// 获取文件配置,如果有更新
|
initLocalCacheIfUpdate(null);
|
||||||
List<FileConfigDO> configs = loadFileConfigIfUpdate(maxUpdateTime);
|
}
|
||||||
if (CollUtil.isEmpty(configs)) {
|
|
||||||
|
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||||
|
public void schedulePeriodicRefresh() {
|
||||||
|
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新本地缓存
|
||||||
|
*
|
||||||
|
* @param maxUpdateTime 最大更新时间
|
||||||
|
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||||
|
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||||
|
*/
|
||||||
|
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||||
|
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||||
|
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||||
|
if (maxUpdateTime != null
|
||||||
|
&& fileConfigMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||||
|
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
List<FileConfigDO> configs = fileConfigMapper.selectList();
|
||||||
|
log.info("[initLocalCacheIfUpdate][缓存文件配置,数量为:{}]", configs.size());
|
||||||
|
|
||||||
// 创建或更新支付 Client
|
// 第二步:构建缓存。创建或更新文件 Client
|
||||||
configs.forEach(config -> {
|
configs.forEach(config -> {
|
||||||
fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
|
fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());
|
||||||
// 如果是 master,进行设置
|
// 如果是 master,进行设置
|
||||||
@ -101,35 +115,8 @@ public class FileConfigServiceImpl implements FileConfigService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// 写入缓存
|
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||||
maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime);
|
this.maxUpdateTime = CollectionUtils.getMaxValue(configs, FileConfigDO::getUpdateTime);
|
||||||
log.info("[initFileClients][初始化 FileConfig 数量为 {}]", configs.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
|
||||||
public void schedulePeriodicRefresh() {
|
|
||||||
self.initFileClients();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 如果文件配置发生变化,从数据库中获取最新的全量文件配置。
|
|
||||||
* 如果未发生变化,则返回空
|
|
||||||
*
|
|
||||||
* @param maxUpdateTime 当前文件配置的最大更新时间
|
|
||||||
* @return 文件配置列表
|
|
||||||
*/
|
|
||||||
private List<FileConfigDO> loadFileConfigIfUpdate(LocalDateTime maxUpdateTime) {
|
|
||||||
// 第一步,判断是否要更新。
|
|
||||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
|
||||||
log.info("[loadFileConfigIfUpdate][首次加载全量文件配置]");
|
|
||||||
} else { // 判断数据库中是否有更新的文件配置
|
|
||||||
if (fileConfigMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
log.info("[loadFileConfigIfUpdate][增量加载全量文件配置]");
|
|
||||||
}
|
|
||||||
// 第二步,如果有更新,则从数据库加载所有文件配置
|
|
||||||
return fileConfigMapper.selectList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -240,7 +240,7 @@ public class FileConfigServiceImplTest extends BaseDbUnitTest {
|
|||||||
// mock 获得 Client
|
// mock 获得 Client
|
||||||
FileClient fileClient = mock(FileClient.class);
|
FileClient fileClient = mock(FileClient.class);
|
||||||
when(fileClientFactory.getFileClient(eq(id))).thenReturn(fileClient);
|
when(fileClientFactory.getFileClient(eq(id))).thenReturn(fileClient);
|
||||||
when(fileClient.upload(any(), any())).thenReturn("https://www.iocoder.cn");
|
when(fileClient.upload(any(), any(), any())).thenReturn("https://www.iocoder.cn");
|
||||||
|
|
||||||
// 调用,并断言
|
// 调用,并断言
|
||||||
assertEquals("https://www.iocoder.cn", fileConfigService.testFileConfig(id));
|
assertEquals("https://www.iocoder.cn", fileConfigService.testFileConfig(id));
|
||||||
|
@ -78,7 +78,7 @@ public class FileServiceTest extends BaseDbUnitTest {
|
|||||||
FileClient client = mock(FileClient.class);
|
FileClient client = mock(FileClient.class);
|
||||||
when(fileConfigService.getMasterFileClient()).thenReturn(client);
|
when(fileConfigService.getMasterFileClient()).thenReturn(client);
|
||||||
String url = randomString();
|
String url = randomString();
|
||||||
when(client.upload(same(content), same(path))).thenReturn(url);
|
when(client.upload(same(content), same(path), same("image/jpeg"))).thenReturn(url);
|
||||||
when(client.getId()).thenReturn(10L);
|
when(client.getId()).thenReturn(10L);
|
||||||
String name = "单测文件名";
|
String name = "单测文件名";
|
||||||
// 调用
|
// 调用
|
||||||
|
@ -23,7 +23,7 @@ public class SmsChannelRefreshConsumer {
|
|||||||
@EventListener
|
@EventListener
|
||||||
public void execute(SmsChannelRefreshMessage message) {
|
public void execute(SmsChannelRefreshMessage message) {
|
||||||
log.info("[execute][收到 SmsChannel 刷新消息]");
|
log.info("[execute][收到 SmsChannel 刷新消息]");
|
||||||
smsChannelService.initSmsClients();
|
smsChannelService.initLocalCache();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import cn.hutool.core.collection.CollUtil;
|
|||||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||||
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
||||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
|
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
|
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
|
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
|
||||||
@ -17,7 +17,6 @@ import com.google.common.collect.ImmutableMap;
|
|||||||
import com.google.common.collect.ImmutableMultimap;
|
import com.google.common.collect.ImmutableMultimap;
|
||||||
import com.google.common.collect.Multimap;
|
import com.google.common.collect.Multimap;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.annotation.Lazy;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
@ -73,58 +72,55 @@ public class DeptServiceImpl implements DeptService {
|
|||||||
@Resource
|
@Resource
|
||||||
private DeptProducer deptProducer;
|
private DeptProducer deptProducer;
|
||||||
|
|
||||||
@Resource
|
/**
|
||||||
@Lazy // 注入自己,所以延迟加载
|
* 初始化 {@link #parentDeptCache} 和 {@link #deptCache} 缓存
|
||||||
private DeptService self;
|
*/
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
@TenantIgnore // 初始化缓存,无需租户过滤
|
|
||||||
public synchronized void initLocalCache() {
|
public synchronized void initLocalCache() {
|
||||||
// 获取部门列表,如果有更新
|
initLocalCacheIfUpdate(null);
|
||||||
List<DeptDO> deptList = loadDeptIfUpdate(maxUpdateTime);
|
|
||||||
if (CollUtil.isEmpty(deptList)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建缓存
|
|
||||||
ImmutableMap.Builder<Long, DeptDO> builder = ImmutableMap.builder();
|
|
||||||
ImmutableMultimap.Builder<Long, DeptDO> parentBuilder = ImmutableMultimap.builder();
|
|
||||||
deptList.forEach(sysRoleDO -> {
|
|
||||||
builder.put(sysRoleDO.getId(), sysRoleDO);
|
|
||||||
parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO);
|
|
||||||
});
|
|
||||||
// 设置缓存
|
|
||||||
deptCache = builder.build();
|
|
||||||
parentDeptCache = parentBuilder.build();
|
|
||||||
maxUpdateTime = CollectionUtils.getMaxValue(deptList, DeptDO::getUpdateTime);
|
|
||||||
log.info("[initLocalCache][初始化 Dept 数量为 {}]", deptList.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||||
public void schedulePeriodicRefresh() {
|
public void schedulePeriodicRefresh() {
|
||||||
self.initLocalCache();
|
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 如果部门发生变化,从数据库中获取最新的全量部门。
|
* 刷新本地缓存
|
||||||
* 如果未发生变化,则返回空
|
|
||||||
*
|
*
|
||||||
* @param maxUpdateTime 当前部门的最大更新时间
|
* @param maxUpdateTime 最大更新时间
|
||||||
* @return 部门列表
|
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||||
|
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||||
*/
|
*/
|
||||||
protected List<DeptDO> loadDeptIfUpdate(LocalDateTime maxUpdateTime) {
|
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||||
// 第一步,判断是否要更新。
|
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
TenantUtils.executeIgnore(() -> {
|
||||||
log.info("[loadMenuIfUpdate][首次加载全量部门]");
|
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||||
} else { // 判断数据库中是否有更新的部门
|
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||||
if (deptMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
if (maxUpdateTime != null
|
||||||
return null;
|
&& deptMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||||
|
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
log.info("[loadMenuIfUpdate][增量加载全量部门]");
|
List<DeptDO> depts = deptMapper.selectList();
|
||||||
}
|
log.info("[initLocalCacheIfUpdate][缓存部门,数量为:{}]", depts.size());
|
||||||
// 第二步,如果有更新,则从数据库加载所有部门
|
|
||||||
return deptMapper.selectList();
|
// 第二步:构建缓存。创建或更新支付 Client
|
||||||
|
// 构建缓存
|
||||||
|
ImmutableMap.Builder<Long, DeptDO> builder = ImmutableMap.builder();
|
||||||
|
ImmutableMultimap.Builder<Long, DeptDO> parentBuilder = ImmutableMultimap.builder();
|
||||||
|
depts.forEach(sysRoleDO -> {
|
||||||
|
builder.put(sysRoleDO.getId(), sysRoleDO);
|
||||||
|
parentBuilder.put(sysRoleDO.getParentId(), sysRoleDO);
|
||||||
|
});
|
||||||
|
// 设置缓存
|
||||||
|
deptCache = builder.build();
|
||||||
|
parentDeptCache = parentBuilder.build();
|
||||||
|
|
||||||
|
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||||
|
this.maxUpdateTime = CollectionUtils.getMaxValue(depts, DeptDO::getUpdateTime);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -25,7 +25,6 @@ import javax.annotation.PostConstruct;
|
|||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -77,42 +76,37 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
|
|||||||
@Override
|
@Override
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void initLocalCache() {
|
public void initLocalCache() {
|
||||||
// 获取客户端列表,如果有更新
|
initLocalCacheIfUpdate(null);
|
||||||
List<OAuth2ClientDO> tenantList = loadOAuth2ClientIfUpdate(maxUpdateTime);
|
|
||||||
if (CollUtil.isEmpty(tenantList)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入缓存
|
|
||||||
clientCache = convertMap(tenantList, OAuth2ClientDO::getClientId);
|
|
||||||
maxUpdateTime = getMaxValue(tenantList, OAuth2ClientDO::getUpdateTime);
|
|
||||||
log.info("[initLocalCache][初始化 OAuth2Client 数量为 {}]", tenantList.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||||
public void schedulePeriodicRefresh() {
|
public void schedulePeriodicRefresh() {
|
||||||
initLocalCache();
|
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 如果客户端发生变化,从数据库中获取最新的全量客户端。
|
* 刷新本地缓存
|
||||||
* 如果未发生变化,则返回空
|
|
||||||
*
|
*
|
||||||
* @param maxUpdateTime 当前客户端的最大更新时间
|
* @param maxUpdateTime 最大更新时间
|
||||||
* @return 客户端列表
|
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||||
|
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||||
*/
|
*/
|
||||||
private List<OAuth2ClientDO> loadOAuth2ClientIfUpdate(LocalDateTime maxUpdateTime) {
|
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||||
// 第一步,判断是否要更新。
|
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||||
log.info("[loadOAuth2ClientIfUpdate][首次加载全量客户端]");
|
if (maxUpdateTime != null
|
||||||
} else { // 判断数据库中是否有更新的客户端
|
&& oauth2ClientMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||||
if (oauth2ClientMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||||
return null;
|
return;
|
||||||
}
|
|
||||||
log.info("[loadOAuth2ClientIfUpdate][增量加载全量客户端]");
|
|
||||||
}
|
}
|
||||||
// 第二步,如果有更新,则从数据库加载所有客户端
|
List<OAuth2ClientDO> clients = oauth2ClientMapper.selectList();
|
||||||
return oauth2ClientMapper.selectList();
|
log.info("[initLocalCacheIfUpdate][缓存 OAuth2 客户端,数量为:{}]", clients.size());
|
||||||
|
|
||||||
|
// 第二步:构建缓存。
|
||||||
|
clientCache = convertMap(clients, OAuth2ClientDO::getClientId);
|
||||||
|
|
||||||
|
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||||
|
this.maxUpdateTime = getMaxValue(clients, OAuth2ClientDO::getUpdateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -86,13 +86,33 @@ public class MenuServiceImpl implements MenuService {
|
|||||||
@Override
|
@Override
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public synchronized void initLocalCache() {
|
public synchronized void initLocalCache() {
|
||||||
// 获取菜单列表,如果有更新
|
initLocalCacheIfUpdate(null);
|
||||||
List<MenuDO> menuList = this.loadMenuIfUpdate(maxUpdateTime);
|
}
|
||||||
if (CollUtil.isEmpty(menuList)) {
|
|
||||||
|
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||||
|
public void schedulePeriodicRefresh() {
|
||||||
|
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新本地缓存
|
||||||
|
*
|
||||||
|
* @param maxUpdateTime 最大更新时间
|
||||||
|
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||||
|
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||||
|
*/
|
||||||
|
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||||
|
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||||
|
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||||
|
if (maxUpdateTime != null
|
||||||
|
&& menuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||||
|
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
List<MenuDO> menuList = menuMapper.selectList();
|
||||||
|
log.info("[initLocalCacheIfUpdate][缓存菜单,数量为:{}]", menuList.size());
|
||||||
|
|
||||||
// 构建缓存
|
// 第二步:构建缓存。
|
||||||
ImmutableMap.Builder<Long, MenuDO> menuCacheBuilder = ImmutableMap.builder();
|
ImmutableMap.Builder<Long, MenuDO> menuCacheBuilder = ImmutableMap.builder();
|
||||||
ImmutableMultimap.Builder<String, MenuDO> permMenuCacheBuilder = ImmutableMultimap.builder();
|
ImmutableMultimap.Builder<String, MenuDO> permMenuCacheBuilder = ImmutableMultimap.builder();
|
||||||
menuList.forEach(menuDO -> {
|
menuList.forEach(menuDO -> {
|
||||||
@ -103,34 +123,9 @@ public class MenuServiceImpl implements MenuService {
|
|||||||
});
|
});
|
||||||
menuCache = menuCacheBuilder.build();
|
menuCache = menuCacheBuilder.build();
|
||||||
permissionMenuCache = permMenuCacheBuilder.build();
|
permissionMenuCache = permMenuCacheBuilder.build();
|
||||||
maxUpdateTime = CollectionUtils.getMaxValue(menuList, MenuDO::getUpdateTime);
|
|
||||||
log.info("[initLocalCache][缓存菜单,数量为:{}]", menuList.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||||
public void schedulePeriodicRefresh() {
|
this.maxUpdateTime = CollectionUtils.getMaxValue(menuList, MenuDO::getUpdateTime);
|
||||||
initLocalCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 如果菜单发生变化,从数据库中获取最新的全量菜单。
|
|
||||||
* 如果未发生变化,则返回空
|
|
||||||
*
|
|
||||||
* @param maxUpdateTime 当前菜单的最大更新时间
|
|
||||||
* @return 菜单列表
|
|
||||||
*/
|
|
||||||
private List<MenuDO> loadMenuIfUpdate(LocalDateTime maxUpdateTime) {
|
|
||||||
// 第一步,判断是否要更新。
|
|
||||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
|
||||||
log.info("[loadMenuIfUpdate][首次加载全量菜单]");
|
|
||||||
} else { // 判断数据库中是否有更新的菜单
|
|
||||||
if (menuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
log.info("[loadMenuIfUpdate][增量加载全量菜单]");
|
|
||||||
}
|
|
||||||
// 第二步,如果有更新,则从数据库加载所有菜单
|
|
||||||
return menuMapper.selectList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
|
|||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
||||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||||
|
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||||
import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
|
import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
|
||||||
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
|
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
|
||||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
|
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
|
||||||
@ -31,7 +32,6 @@ import com.google.common.collect.Sets;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.context.annotation.Lazy;
|
|
||||||
import org.springframework.scheduling.annotation.Scheduled;
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
@ -126,106 +126,84 @@ public class PermissionServiceImpl implements PermissionService {
|
|||||||
@Resource
|
@Resource
|
||||||
private PermissionProducer permissionProducer;
|
private PermissionProducer permissionProducer;
|
||||||
|
|
||||||
@Resource
|
|
||||||
@Lazy // 注入自己,所以延迟加载
|
|
||||||
private PermissionService self;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
@TenantIgnore // 初始化缓存,无需租户过滤
|
|
||||||
public void initLocalCache() {
|
public void initLocalCache() {
|
||||||
initUserRoleLocalCache();
|
initLocalCacheIfUpdateForRoleMenu(null);
|
||||||
initRoleMenuLocalCache();
|
initLocalCacheIfUpdateForUserRole(null);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化 {@link #roleMenuCache} 和 {@link #menuRoleCache} 缓存
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
void initRoleMenuLocalCache() {
|
|
||||||
// 获取角色与菜单的关联列表,如果有更新
|
|
||||||
List<RoleMenuDO> roleMenuList = loadRoleMenuIfUpdate(roleMenuMaxUpdateTime);
|
|
||||||
if (CollUtil.isEmpty(roleMenuList)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化 roleMenuCache 和 menuRoleCache 缓存
|
|
||||||
ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder();
|
|
||||||
ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
|
|
||||||
roleMenuList.forEach(roleMenuDO -> {
|
|
||||||
roleMenuCacheBuilder.put(roleMenuDO.getRoleId(), roleMenuDO.getMenuId());
|
|
||||||
menuRoleCacheBuilder.put(roleMenuDO.getMenuId(), roleMenuDO.getRoleId());
|
|
||||||
});
|
|
||||||
roleMenuCache = roleMenuCacheBuilder.build();
|
|
||||||
menuRoleCache = menuRoleCacheBuilder.build();
|
|
||||||
roleMenuMaxUpdateTime = getMaxValue(roleMenuList, RoleMenuDO::getUpdateTime);
|
|
||||||
log.info("[initRoleMenuLocalCache][初始化角色与菜单的关联数量为 {}]", roleMenuList.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化 {@link #userRoleCache} 缓存
|
|
||||||
*/
|
|
||||||
@VisibleForTesting
|
|
||||||
void initUserRoleLocalCache() {
|
|
||||||
// 获取用户与角色的关联列表,如果有更新
|
|
||||||
List<UserRoleDO> userRoleList = loadUserRoleIfUpdate(userRoleMaxUpdateTime);
|
|
||||||
if (CollUtil.isEmpty(userRoleList)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化 userRoleCache 缓存
|
|
||||||
ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
|
|
||||||
userRoleList.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
|
|
||||||
userRoleCache = CollectionUtils.convertMultiMap2(userRoleList, UserRoleDO::getUserId, UserRoleDO::getRoleId);
|
|
||||||
userRoleMaxUpdateTime = getMaxValue(userRoleList, UserRoleDO::getUpdateTime);
|
|
||||||
log.info("[initUserRoleLocalCache][初始化用户与角色的关联数量为 {}]", userRoleList.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||||
public void schedulePeriodicRefresh() {
|
public void schedulePeriodicRefresh() {
|
||||||
self.initLocalCache();
|
initLocalCacheIfUpdateForRoleMenu(this.roleMenuMaxUpdateTime);
|
||||||
|
initLocalCacheIfUpdateForUserRole(this.userRoleMaxUpdateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 如果角色与菜单的关联发生变化,从数据库中获取最新的全量角色与菜单的关联。
|
* 刷新 RoleMenu 本地缓存
|
||||||
* 如果未发生变化,则返回空
|
|
||||||
*
|
*
|
||||||
* @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
|
* @param maxUpdateTime 最大更新时间
|
||||||
* @return 角色与菜单的关联列表
|
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||||
|
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||||
*/
|
*/
|
||||||
protected List<RoleMenuDO> loadRoleMenuIfUpdate(LocalDateTime maxUpdateTime) {
|
@VisibleForTesting
|
||||||
// 第一步,判断是否要更新。
|
void initLocalCacheIfUpdateForRoleMenu(LocalDateTime maxUpdateTime) {
|
||||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||||
log.info("[loadRoleMenuIfUpdate][首次加载全量角色与菜单的关联]");
|
TenantUtils.executeIgnore(() -> {
|
||||||
} else { // 判断数据库中是否有更新的角色与菜单的关联
|
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||||
if (roleMenuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||||
return null;
|
if (maxUpdateTime != null
|
||||||
|
&& roleMenuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||||
|
log.info("[initLocalCacheIfUpdateForRoleMenu][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
log.info("[loadRoleMenuIfUpdate][增量加载全量角色与菜单的关联]");
|
List<RoleMenuDO> roleMenus = roleMenuMapper.selectList();
|
||||||
}
|
log.info("[initLocalCacheIfUpdateForRoleMenu][缓存角色与菜单,数量为:{}]", roleMenus.size());
|
||||||
// 第二步,如果有更新,则从数据库加载所有角色与菜单的关联
|
|
||||||
return roleMenuMapper.selectList();
|
// 第二步:构建缓存。
|
||||||
|
ImmutableMultimap.Builder<Long, Long> roleMenuCacheBuilder = ImmutableMultimap.builder();
|
||||||
|
ImmutableMultimap.Builder<Long, Long> menuRoleCacheBuilder = ImmutableMultimap.builder();
|
||||||
|
roleMenus.forEach(roleMenuDO -> {
|
||||||
|
roleMenuCacheBuilder.put(roleMenuDO.getRoleId(), roleMenuDO.getMenuId());
|
||||||
|
menuRoleCacheBuilder.put(roleMenuDO.getMenuId(), roleMenuDO.getRoleId());
|
||||||
|
});
|
||||||
|
roleMenuCache = roleMenuCacheBuilder.build();
|
||||||
|
menuRoleCache = menuRoleCacheBuilder.build();
|
||||||
|
|
||||||
|
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||||
|
this.roleMenuMaxUpdateTime = getMaxValue(roleMenus, RoleMenuDO::getUpdateTime);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 如果用户与角色的关联发生变化,从数据库中获取最新的全量用户与角色的关联。
|
* 刷新 UserRole 本地缓存
|
||||||
* 如果未发生变化,则返回空
|
|
||||||
*
|
*
|
||||||
* @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
|
* @param maxUpdateTime 最大更新时间
|
||||||
* @return 角色与菜单的关联列表
|
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||||
|
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||||
*/
|
*/
|
||||||
protected List<UserRoleDO> loadUserRoleIfUpdate(LocalDateTime maxUpdateTime) {
|
@VisibleForTesting
|
||||||
// 第一步,判断是否要更新。
|
void initLocalCacheIfUpdateForUserRole(LocalDateTime maxUpdateTime) {
|
||||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||||
log.info("[loadUserRoleIfUpdate][首次加载全量用户与角色的关联]");
|
TenantUtils.executeIgnore(() -> {
|
||||||
} else { // 判断数据库中是否有更新的用户与角色的关联
|
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||||
if (userRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||||
return null;
|
if (maxUpdateTime != null
|
||||||
|
&& userRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||||
|
log.info("[initLocalCacheIfUpdateForUserRole][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
log.info("[loadUserRoleIfUpdate][增量加载全量用户与角色的关联]");
|
List<UserRoleDO> userRoles = userRoleMapper.selectList();
|
||||||
}
|
log.info("[initLocalCacheIfUpdateForUserRole][缓存用户与角色,数量为:{}]", userRoles.size());
|
||||||
// 第二步,如果有更新,则从数据库加载所有用户与角色的关联
|
|
||||||
return userRoleMapper.selectList();
|
// 第二步:构建缓存。
|
||||||
|
ImmutableMultimap.Builder<Long, Long> userRoleCacheBuilder = ImmutableMultimap.builder();
|
||||||
|
userRoles.forEach(userRoleDO -> userRoleCacheBuilder.put(userRoleDO.getUserId(), userRoleDO.getRoleId()));
|
||||||
|
userRoleCache = CollectionUtils.convertMultiMap2(userRoles, UserRoleDO::getUserId, UserRoleDO::getRoleId);
|
||||||
|
|
||||||
|
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||||
|
this.userRoleMaxUpdateTime = getMaxValue(userRoles, UserRoleDO::getUpdateTime);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
|||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||||
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
|
||||||
|
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleCreateReqVO;
|
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleCreateReqVO;
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO;
|
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleExportReqVO;
|
||||||
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
|
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
|
||||||
@ -86,44 +87,41 @@ public class RoleServiceImpl implements RoleService {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
@TenantIgnore // 忽略自动多租户,全局初始化缓存
|
|
||||||
public void initLocalCache() {
|
public void initLocalCache() {
|
||||||
// 获取角色列表,如果有更新
|
initLocalCacheIfUpdate(null);
|
||||||
List<RoleDO> roleList = loadRoleIfUpdate(maxUpdateTime);
|
|
||||||
if (CollUtil.isEmpty(roleList)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 写入缓存
|
|
||||||
roleCache = CollectionUtils.convertMap(roleList, RoleDO::getId);
|
|
||||||
maxUpdateTime = CollectionUtils.getMaxValue(roleList, RoleDO::getUpdateTime);
|
|
||||||
log.info("[initLocalCache][初始化 Role 数量为 {}]", roleList.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||||
public void schedulePeriodicRefresh() {
|
public void schedulePeriodicRefresh() {
|
||||||
self.initLocalCache();
|
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 如果角色发生变化,从数据库中获取最新的全量角色。
|
* 刷新本地缓存
|
||||||
* 如果未发生变化,则返回空
|
|
||||||
*
|
*
|
||||||
* @param maxUpdateTime 当前角色的最大更新时间
|
* @param maxUpdateTime 最大更新时间
|
||||||
* @return 角色列表
|
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||||
|
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||||
*/
|
*/
|
||||||
private List<RoleDO> loadRoleIfUpdate(LocalDateTime maxUpdateTime) {
|
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||||
// 第一步,判断是否要更新。
|
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
TenantUtils.executeIgnore(() -> {
|
||||||
log.info("[loadRoleIfUpdate][首次加载全量角色]");
|
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||||
} else { // 判断数据库中是否有更新的角色
|
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||||
if (roleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
if (maxUpdateTime != null
|
||||||
return null;
|
&& roleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||||
|
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
log.info("[loadRoleIfUpdate][增量加载全量角色]");
|
List<RoleDO> roleList = roleMapper.selectList();
|
||||||
}
|
log.info("[initLocalCacheIfUpdate][缓存角色,数量为:{}]", roleList.size());
|
||||||
// 第二步,如果有更新,则从数据库加载所有角色
|
|
||||||
return roleMapper.selectList();
|
// 第二步:构建缓存。
|
||||||
|
roleCache = CollectionUtils.convertMap(roleList, RoleDO::getId);
|
||||||
|
|
||||||
|
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||||
|
this.maxUpdateTime = CollectionUtils.getMaxValue(roleList, RoleDO::getUpdateTime);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -84,21 +84,42 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
|
|||||||
@Override
|
@Override
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void initLocalCache() {
|
public void initLocalCache() {
|
||||||
// 获取敏感词列表,如果有更新
|
initLocalCacheIfUpdate(null);
|
||||||
List<SensitiveWordDO> sensitiveWordList = loadSensitiveWordIfUpdate(maxUpdateTime);
|
}
|
||||||
if (CollUtil.isEmpty(sensitiveWordList)) {
|
|
||||||
|
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||||
|
public void schedulePeriodicRefresh() {
|
||||||
|
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 刷新本地缓存
|
||||||
|
*
|
||||||
|
* @param maxUpdateTime 最大更新时间
|
||||||
|
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||||
|
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||||
|
*/
|
||||||
|
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||||
|
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||||
|
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||||
|
if (maxUpdateTime != null
|
||||||
|
&& sensitiveWordMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||||
|
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
List<SensitiveWordDO> sensitiveWords = sensitiveWordMapper.selectList();
|
||||||
|
log.info("[initLocalCacheIfUpdate][缓存敏感词,数量为:{}]", sensitiveWords.size());
|
||||||
|
|
||||||
|
// 第二步:构建缓存。
|
||||||
// 写入 sensitiveWordTagsCache 缓存
|
// 写入 sensitiveWordTagsCache 缓存
|
||||||
Set<String> tags = new HashSet<>();
|
Set<String> tags = new HashSet<>();
|
||||||
sensitiveWordList.forEach(word -> tags.addAll(word.getTags()));
|
sensitiveWords.forEach(word -> tags.addAll(word.getTags()));
|
||||||
sensitiveWordTagsCache = tags;
|
sensitiveWordTagsCache = tags;
|
||||||
// 写入 defaultSensitiveWordTrie、tagSensitiveWordTries 缓存
|
// 写入 defaultSensitiveWordTrie、tagSensitiveWordTries 缓存
|
||||||
initSensitiveWordTrie(sensitiveWordList);
|
initSensitiveWordTrie(sensitiveWords);
|
||||||
// 写入 maxUpdateTime 最大更新时间
|
|
||||||
maxUpdateTime = CollectionUtils.getMaxValue(sensitiveWordList, SensitiveWordDO::getUpdateTime);
|
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||||
log.info("[initLocalCache][初始化 敏感词 数量为 {}]", sensitiveWordList.size());
|
this.maxUpdateTime = CollectionUtils.getMaxValue(sensitiveWords, SensitiveWordDO::getUpdateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void initSensitiveWordTrie(List<SensitiveWordDO> wordDOs) {
|
private void initSensitiveWordTrie(List<SensitiveWordDO> wordDOs) {
|
||||||
@ -122,33 +143,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
|
|||||||
this.tagSensitiveWordTries = tagSensitiveWordTries;
|
this.tagSensitiveWordTries = tagSensitiveWordTries;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
|
||||||
public void schedulePeriodicRefresh() {
|
|
||||||
initLocalCache();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 如果敏感词发生变化,从数据库中获取最新的全量敏感词。
|
|
||||||
* 如果未发生变化,则返回空
|
|
||||||
*
|
|
||||||
* @param maxUpdateTime 当前敏感词的最大更新时间
|
|
||||||
* @return 敏感词列表
|
|
||||||
*/
|
|
||||||
private List<SensitiveWordDO> loadSensitiveWordIfUpdate(LocalDateTime maxUpdateTime) {
|
|
||||||
// 第一步,判断是否要更新。
|
|
||||||
// 如果更新时间为空,说明 DB 一定有新数据
|
|
||||||
if (maxUpdateTime == null) {
|
|
||||||
log.info("[loadSensitiveWordIfUpdate][首次加载全量敏感词]");
|
|
||||||
} else { // 判断数据库中是否有更新的敏感词
|
|
||||||
if (sensitiveWordMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
log.info("[loadSensitiveWordIfUpdate][增量加载全量敏感词]");
|
|
||||||
}
|
|
||||||
// 第二步,如果有更新,则从数据库加载所有敏感词
|
|
||||||
return sensitiveWordMapper.selectList();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) {
|
public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) {
|
||||||
// 校验唯一性
|
// 校验唯一性
|
||||||
|
@ -21,7 +21,7 @@ public interface SmsChannelService {
|
|||||||
/**
|
/**
|
||||||
* 初始化短信客户端
|
* 初始化短信客户端
|
||||||
*/
|
*/
|
||||||
void initSmsClients();
|
void initLocalCache();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建短信渠道
|
* 创建短信渠道
|
||||||
|
@ -23,6 +23,7 @@ import java.time.LocalDateTime;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.getMaxValue;
|
||||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
|
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;
|
||||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
|
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;
|
||||||
|
|
||||||
@ -30,7 +31,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.SMS_CHANNE
|
|||||||
* 短信渠道Service实现类
|
* 短信渠道Service实现类
|
||||||
*
|
*
|
||||||
* @author zzf
|
* @author zzf
|
||||||
* @date 2021/1/25 9:25
|
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@ -61,46 +61,39 @@ public class SmsChannelServiceImpl implements SmsChannelService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void initSmsClients() {
|
public void initLocalCache() {
|
||||||
// 获取短信渠道,如果有更新
|
initLocalCacheIfUpdate(null);
|
||||||
List<SmsChannelDO> smsChannels = this.loadSmsChannelIfUpdate(maxUpdateTime);
|
|
||||||
if (CollUtil.isEmpty(smsChannels)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建或更新短信 Client
|
|
||||||
List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList02(smsChannels);
|
|
||||||
propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
|
|
||||||
|
|
||||||
// 写入缓存
|
|
||||||
maxUpdateTime = CollectionUtils.getMaxValue(smsChannels, SmsChannelDO::getUpdateTime);
|
|
||||||
log.info("[initSmsClients][初始化 SmsChannel 数量为 {}]", smsChannels.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||||
public void schedulePeriodicRefresh() {
|
public void schedulePeriodicRefresh() {
|
||||||
initSmsClients();
|
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 如果短信渠道发生变化,从数据库中获取最新的全量短信渠道。
|
* 刷新本地缓存
|
||||||
* 如果未发生变化,则返回空
|
|
||||||
*
|
*
|
||||||
* @param maxUpdateTime 当前短信渠道的最大更新时间
|
* @param maxUpdateTime 最大更新时间
|
||||||
* @return 短信渠道列表
|
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||||
|
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||||
*/
|
*/
|
||||||
private List<SmsChannelDO> loadSmsChannelIfUpdate(LocalDateTime maxUpdateTime) {
|
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||||
// 第一步,判断是否要更新。
|
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||||
log.info("[loadSmsChannelIfUpdate][首次加载全量短信渠道]");
|
if (maxUpdateTime != null
|
||||||
} else { // 判断数据库中是否有更新的短信渠道
|
&& smsChannelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||||
if (smsChannelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||||
return null;
|
return;
|
||||||
}
|
|
||||||
log.info("[loadSmsChannelIfUpdate][增量加载全量短信渠道]");
|
|
||||||
}
|
}
|
||||||
// 第二步,如果有更新,则从数据库加载所有短信渠道
|
List<SmsChannelDO> channels = smsChannelMapper.selectList();
|
||||||
return smsChannelMapper.selectList();
|
log.info("[initLocalCacheIfUpdate][缓存短信渠道,数量为:{}]", channels.size());
|
||||||
|
|
||||||
|
// 第二步:构建缓存。创建或更新短信 Client
|
||||||
|
List<SmsChannelProperties> propertiesList = SmsChannelConvert.INSTANCE.convertList02(channels);
|
||||||
|
propertiesList.forEach(properties -> smsClientFactory.createOrUpdateSmsClient(properties));
|
||||||
|
|
||||||
|
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||||
|
this.maxUpdateTime = getMaxValue(channels, SmsChannelDO::getUpdateTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -71,7 +71,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
|||||||
private PermissionProducer permissionProducer;
|
private PermissionProducer permissionProducer;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInitRoleMenuLocalCache() {
|
public void testInitLocalCacheIfUpdateForRoleMenu() {
|
||||||
// mock 数据
|
// mock 数据
|
||||||
RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(10L));
|
RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(10L));
|
||||||
roleMenuMapper.insert(roleMenuDO01);
|
roleMenuMapper.insert(roleMenuDO01);
|
||||||
@ -79,7 +79,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
|||||||
roleMenuMapper.insert(roleMenuDO02);
|
roleMenuMapper.insert(roleMenuDO02);
|
||||||
|
|
||||||
// 调用
|
// 调用
|
||||||
permissionService.initRoleMenuLocalCache();
|
permissionService.initLocalCacheIfUpdateForRoleMenu(null);
|
||||||
// 断言 roleMenuCache 缓存
|
// 断言 roleMenuCache 缓存
|
||||||
assertEquals(1, permissionService.getRoleMenuCache().keySet().size());
|
assertEquals(1, permissionService.getRoleMenuCache().keySet().size());
|
||||||
assertEquals(asList(10L, 20L), permissionService.getRoleMenuCache().get(1L));
|
assertEquals(asList(10L, 20L), permissionService.getRoleMenuCache().get(1L));
|
||||||
@ -93,7 +93,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInitUserRoleLocalCache() {
|
public void testInitLocalCacheIfUpdateForUserRole() {
|
||||||
// mock 数据
|
// mock 数据
|
||||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
||||||
userRoleMapper.insert(userRoleDO01);
|
userRoleMapper.insert(userRoleDO01);
|
||||||
@ -101,7 +101,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
|||||||
userRoleMapper.insert(roleMenuDO02);
|
userRoleMapper.insert(roleMenuDO02);
|
||||||
|
|
||||||
// 调用
|
// 调用
|
||||||
permissionService.initUserRoleLocalCache();
|
permissionService.initLocalCacheIfUpdateForUserRole(null);
|
||||||
// 断言 roleMenuCache 缓存
|
// 断言 roleMenuCache 缓存
|
||||||
assertEquals(1, permissionService.getUserRoleCache().size());
|
assertEquals(1, permissionService.getUserRoleCache().size());
|
||||||
assertEquals(asSet(10L, 20L), permissionService.getUserRoleCache().get(1L));
|
assertEquals(asSet(10L, 20L), permissionService.getUserRoleCache().get(1L));
|
||||||
|
@ -57,7 +57,7 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
|
|||||||
smsChannelMapper.insert(smsChannelDO02);
|
smsChannelMapper.insert(smsChannelDO02);
|
||||||
|
|
||||||
// 调用
|
// 调用
|
||||||
smsChannelService.initSmsClients();
|
smsChannelService.initLocalCache();
|
||||||
// 校验 maxUpdateTime 属性
|
// 校验 maxUpdateTime 属性
|
||||||
LocalDateTime maxUpdateTime = (LocalDateTime) BeanUtil.getFieldValue(smsChannelService, "maxUpdateTime");
|
LocalDateTime maxUpdateTime = (LocalDateTime) BeanUtil.getFieldValue(smsChannelService, "maxUpdateTime");
|
||||||
assertEquals(max(smsChannelDO01.getUpdateTime(), smsChannelDO02.getUpdateTime()), maxUpdateTime);
|
assertEquals(max(smsChannelDO01.getUpdateTime(), smsChannelDO02.getUpdateTime()), maxUpdateTime);
|
||||||
|
Loading…
Reference in New Issue
Block a user