mirror of
https://gitee.com/hhyykk/ipms-sjy.git
synced 2025-07-15 03:25:06 +08:00
Merge remote-tracking branch 'origin/master' into feature/springdoc
# Conflicts: # README.md # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/ProductPropertyValueController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/ProductPropertyViewRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyAndValueRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyCreateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyListReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyPageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/property/ProductPropertyUpdateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueCreateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValuePageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/property/vo/value/ProductPropertyValueUpdateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/sku/vo/ProductSkuCreateOrUpdateReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/ProductSpuController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuBaseVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuDetailRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/admin/spu/vo/ProductSpuPageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/AppProductSpuController.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageReqVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuPageRespVO.java # yudao-module-mall/yudao-module-product-biz/src/main/java/cn/iocoder/yudao/module/product/controller/app/spu/vo/AppSpuRespVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/base/property/AppProductPropertyValueDetailRespVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/AppTradeOrderController.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/AppTradeOrderPageReqVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/TradeOrderItemRespVO.java # yudao-module-mall/yudao-module-trade-biz/src/main/java/cn/iocoder/yudao/module/trade/controller/app/order/vo/TradeOrderRespVO.java # yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java # yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/vo/PayNotifyOrderReqVO.java # yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/vo/PayRefundOrderReqVO.java # yudao-server/pom.xml # yudao-server/src/main/java/cn/iocoder/yudao/module/shop/controller/app/AppShopOrderController.java
This commit is contained in:
@ -0,0 +1,5 @@
|
||||
### 获得地区树
|
||||
GET {{baseUrl}}/system/area/tree
|
||||
Authorization: Bearer {{token}}
|
||||
tenant-id: {{adminTenentId}}
|
||||
|
@ -0,0 +1,50 @@
|
||||
package cn.iocoder.yudao.module.system.controller.admin.ip;
|
||||
|
||||
import cn.hutool.core.lang.Assert;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.ip.core.Area;
|
||||
import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils;
|
||||
import cn.iocoder.yudao.framework.ip.core.utils.IPUtils;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.ip.vo.AreaNodeRespVO;
|
||||
import cn.iocoder.yudao.module.system.convert.ip.AreaConvert;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiImplicitParam;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
@Api(tags = "管理后台 - 地区")
|
||||
@RestController
|
||||
@RequestMapping("/system/area")
|
||||
@Validated
|
||||
public class AreaController {
|
||||
|
||||
@GetMapping("/tree")
|
||||
@ApiOperation("获得地区树")
|
||||
public CommonResult<List<AreaNodeRespVO>> getAreaTree() {
|
||||
Area area = AreaUtils.getArea(Area.ID_CHINA);
|
||||
Assert.notNull(area, "获取不到中国");
|
||||
return success(AreaConvert.INSTANCE.convertList(area.getChildren()));
|
||||
}
|
||||
|
||||
@GetMapping("/get-by-ip")
|
||||
@ApiOperation("获得 IP 对应的地区名")
|
||||
@ApiImplicitParam(name = "ip", value = "IP", required = true, dataTypeClass = String.class)
|
||||
public CommonResult<String> getAreaByIp(@RequestParam("ip") String ip) {
|
||||
// 获得城市
|
||||
Area area = IPUtils.getArea(ip);
|
||||
if (area == null) {
|
||||
return success("未知");
|
||||
}
|
||||
// 格式化返回
|
||||
return success(AreaUtils.format(area.getId()));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
package cn.iocoder.yudao.module.system.controller.admin.ip.vo;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ApiModel("管理后台 - 地区节点 Response VO")
|
||||
@Data
|
||||
public class AreaNodeRespVO {
|
||||
|
||||
@ApiModelProperty(value = "编号", required = true, example = "110000")
|
||||
private Integer id;
|
||||
|
||||
@ApiModelProperty(value = "名字", required = true, example = "北京")
|
||||
private String name;
|
||||
|
||||
/**
|
||||
* 子节点
|
||||
*/
|
||||
private List<AreaNodeRespVO> children;
|
||||
|
||||
}
|
@ -112,7 +112,7 @@ public class UserController {
|
||||
@GetMapping("/list-all-simple")
|
||||
@Operation(summary = "获取用户精简信息列表", description = "只包含被开启的用户,主要用于前端的下拉选项")
|
||||
public CommonResult<List<UserSimpleRespVO>> getSimpleUsers() {
|
||||
// 获用户门列表,只要开启状态的
|
||||
// 获用户列表,只要开启状态的
|
||||
List<AdminUserDO> list = userService.getUsersByStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
// 排序后,返回给前端
|
||||
return success(UserConvert.INSTANCE.convertList04(list));
|
||||
|
@ -0,0 +1,17 @@
|
||||
package cn.iocoder.yudao.module.system.convert.ip;
|
||||
|
||||
import cn.iocoder.yudao.framework.ip.core.Area;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.ip.vo.AreaNodeRespVO;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface AreaConvert {
|
||||
|
||||
AreaConvert INSTANCE = Mappers.getMapper(AreaConvert.class);
|
||||
|
||||
List<AreaNodeRespVO> convertList(List<Area> list);
|
||||
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package cn.iocoder.yudao.module.system.dal.dataobject.dept;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
|
||||
import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import com.baomidou.mybatisplus.annotation.KeySequence;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
@ -19,7 +19,7 @@ import lombok.EqualsAndHashCode;
|
||||
@KeySequence("system_dept_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DeptDO extends BaseDO {
|
||||
public class DeptDO extends TenantBaseDO {
|
||||
|
||||
/**
|
||||
* 部门ID
|
||||
|
@ -38,14 +38,15 @@ public interface DictDataMapper extends BaseMapperX<DictDataDO> {
|
||||
default PageResult<DictDataDO> selectPage(DictDataPageReqVO reqVO) {
|
||||
return selectPage(reqVO, new LambdaQueryWrapperX<DictDataDO>()
|
||||
.likeIfPresent(DictDataDO::getLabel, reqVO.getLabel())
|
||||
.likeIfPresent(DictDataDO::getDictType, reqVO.getDictType())
|
||||
.eqIfPresent(DictDataDO::getDictType, reqVO.getDictType())
|
||||
.eqIfPresent(DictDataDO::getStatus, reqVO.getStatus())
|
||||
.orderByDesc(Arrays.asList(DictDataDO::getDictType, DictDataDO::getSort)));
|
||||
}
|
||||
|
||||
default List<DictDataDO> selectList(DictDataExportReqVO reqVO) {
|
||||
return selectList(new LambdaQueryWrapperX<DictDataDO>().likeIfPresent(DictDataDO::getLabel, reqVO.getLabel())
|
||||
.likeIfPresent(DictDataDO::getDictType, reqVO.getDictType())
|
||||
return selectList(new LambdaQueryWrapperX<DictDataDO>()
|
||||
.likeIfPresent(DictDataDO::getLabel, reqVO.getLabel())
|
||||
.eqIfPresent(DictDataDO::getDictType, reqVO.getDictType())
|
||||
.eqIfPresent(DictDataDO::getStatus, reqVO.getStatus()));
|
||||
}
|
||||
|
||||
|
@ -5,9 +5,7 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypeExportReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictDataDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.dict.DictTypeDO;
|
||||
import org.apache.ibatis.annotations.Delete;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
@ -43,10 +41,8 @@ public interface DictTypeMapper extends BaseMapperX<DictTypeDO> {
|
||||
return selectOne(DictTypeDO::getName, name);
|
||||
}
|
||||
|
||||
@Update("UPDATE system_dict_type SET DELETED = 1,DELETED_TIME=#{deletedTime} WHERE id = #{id}")
|
||||
int deleteById(@Param("id") Long id, @Param("deletedTime") LocalDateTime deletedTime);
|
||||
|
||||
default int deleteById(Long id) {
|
||||
return deleteById(id, LocalDateTime.now());
|
||||
}
|
||||
@Update("UPDATE system_dict_type SET deleted = 1, deleted_time = #{deletedTime} WHERE id = #{id}")
|
||||
void updateToDelete(@Param("id") Long id, @Param("deletedTime") LocalDateTime deletedTime);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ public class SmsChannelRefreshConsumer extends AbstractChannelMessageListener<Sm
|
||||
@Override
|
||||
public void onMessage(SmsChannelRefreshMessage message) {
|
||||
log.info("[onMessage][收到 SmsChannel 刷新消息]");
|
||||
smsChannelService.initSmsClients();
|
||||
smsChannelService.initLocalCache();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,8 @@ import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
|
||||
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.context.TenantContextHolder;
|
||||
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.DeptListReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
|
||||
@ -17,7 +18,6 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
@ -73,58 +73,55 @@ public class DeptServiceImpl implements DeptService {
|
||||
@Resource
|
||||
private DeptProducer deptProducer;
|
||||
|
||||
@Resource
|
||||
@Lazy // 注入自己,所以延迟加载
|
||||
private DeptService self;
|
||||
|
||||
/**
|
||||
* 初始化 {@link #parentDeptCache} 和 {@link #deptCache} 缓存
|
||||
*/
|
||||
@Override
|
||||
@PostConstruct
|
||||
@TenantIgnore // 初始化缓存,无需租户过滤
|
||||
public synchronized void initLocalCache() {
|
||||
// 获取部门列表,如果有更新
|
||||
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());
|
||||
initLocalCacheIfUpdate(null);
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||
public void schedulePeriodicRefresh() {
|
||||
self.initLocalCache();
|
||||
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果部门发生变化,从数据库中获取最新的全量部门。
|
||||
* 如果未发生变化,则返回空
|
||||
* 刷新本地缓存
|
||||
*
|
||||
* @param maxUpdateTime 当前部门的最大更新时间
|
||||
* @return 部门列表
|
||||
* @param maxUpdateTime 最大更新时间
|
||||
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||
*/
|
||||
protected List<DeptDO> loadDeptIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步,判断是否要更新。
|
||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
||||
log.info("[loadMenuIfUpdate][首次加载全量部门]");
|
||||
} else { // 判断数据库中是否有更新的部门
|
||||
if (deptMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
return null;
|
||||
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||
TenantUtils.executeIgnore(() -> {
|
||||
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||
if (maxUpdateTime != null
|
||||
&& deptMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||
return;
|
||||
}
|
||||
log.info("[loadMenuIfUpdate][增量加载全量部门]");
|
||||
}
|
||||
// 第二步,如果有更新,则从数据库加载所有部门
|
||||
return deptMapper.selectList();
|
||||
List<DeptDO> depts = deptMapper.selectList();
|
||||
log.info("[initLocalCacheIfUpdate][缓存部门,数量为:{}]", depts.size());
|
||||
|
||||
// 第二步:构建缓存。创建或更新支付 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
|
||||
@ -202,12 +199,19 @@ public class DeptServiceImpl implements DeptService {
|
||||
if (recursiveCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获得子部门
|
||||
Collection<DeptDO> depts = parentDeptMap.get(parentId);
|
||||
if (CollUtil.isEmpty(depts)) {
|
||||
return;
|
||||
}
|
||||
// 针对多租户,过滤掉非当前租户的部门
|
||||
Long tenantId = TenantContextHolder.getTenantId();
|
||||
if (tenantId != null) {
|
||||
depts = CollUtil.filterNew(depts, dept -> tenantId.equals(dept.getTenantId()));
|
||||
}
|
||||
result.addAll(depts);
|
||||
|
||||
// 继续递归
|
||||
depts.forEach(dept -> getDeptsByParentIdFromCache(result, dept.getId(),
|
||||
recursiveCount - 1, parentDeptMap));
|
||||
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.system.service.dict;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypeCreateReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypeExportReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;
|
||||
@ -58,7 +59,8 @@ public class DictTypeServiceImpl implements DictTypeService {
|
||||
// 校验正确性
|
||||
checkCreateOrUpdate(null, reqVO.getName(), reqVO.getType());
|
||||
// 插入字典类型
|
||||
DictTypeDO dictType = DictTypeConvert.INSTANCE.convert(reqVO);
|
||||
DictTypeDO dictType = DictTypeConvert.INSTANCE.convert(reqVO)
|
||||
.setDeletedTime(LocalDateTimeUtils.EMPTY); // 唯一索引,避免 null 值
|
||||
dictTypeMapper.insert(dictType);
|
||||
return dictType.getId();
|
||||
}
|
||||
@ -81,7 +83,7 @@ public class DictTypeServiceImpl implements DictTypeService {
|
||||
throw exception(DICT_TYPE_HAS_CHILDREN);
|
||||
}
|
||||
// 删除字典类型
|
||||
dictTypeMapper.deleteById(id);
|
||||
dictTypeMapper.updateToDelete(id, LocalDateTime.now());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -24,7 +24,9 @@ import org.springframework.validation.annotation.Validated;
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
@ -74,42 +76,37 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initLocalCache() {
|
||||
// 获取客户端列表,如果有更新
|
||||
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());
|
||||
initLocalCacheIfUpdate(null);
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||
public void schedulePeriodicRefresh() {
|
||||
initLocalCache();
|
||||
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果客户端发生变化,从数据库中获取最新的全量客户端。
|
||||
* 如果未发生变化,则返回空
|
||||
* 刷新本地缓存
|
||||
*
|
||||
* @param maxUpdateTime 当前客户端的最大更新时间
|
||||
* @return 客户端列表
|
||||
* @param maxUpdateTime 最大更新时间
|
||||
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||
*/
|
||||
private List<OAuth2ClientDO> loadOAuth2ClientIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步,判断是否要更新。
|
||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
||||
log.info("[loadOAuth2ClientIfUpdate][首次加载全量客户端]");
|
||||
} else { // 判断数据库中是否有更新的客户端
|
||||
if (oauth2ClientMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
return null;
|
||||
}
|
||||
log.info("[loadOAuth2ClientIfUpdate][增量加载全量客户端]");
|
||||
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||
if (maxUpdateTime != null
|
||||
&& oauth2ClientMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||
return;
|
||||
}
|
||||
// 第二步,如果有更新,则从数据库加载所有客户端
|
||||
return oauth2ClientMapper.selectList();
|
||||
List<OAuth2ClientDO> clients = oauth2ClientMapper.selectList();
|
||||
log.info("[initLocalCacheIfUpdate][缓存 OAuth2 客户端,数量为:{}]", clients.size());
|
||||
|
||||
// 第二步:构建缓存。
|
||||
clientCache = convertMap(clients, OAuth2ClientDO::getClientId);
|
||||
|
||||
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||
this.maxUpdateTime = getMaxValue(clients, OAuth2ClientDO::getUpdateTime);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -102,6 +102,7 @@ public class MenuServiceImpl implements MenuService {
|
||||
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||
*/
|
||||
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||
if (maxUpdateTime != null
|
||||
&& menuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
@ -111,7 +112,7 @@ public class MenuServiceImpl implements MenuService {
|
||||
List<MenuDO> menuList = menuMapper.selectList();
|
||||
log.info("[initLocalCacheIfUpdate][缓存菜单,数量为:{}]", menuList.size());
|
||||
|
||||
// 构建缓存
|
||||
// 第二步:构建缓存。
|
||||
ImmutableMap.Builder<Long, MenuDO> menuCacheBuilder = ImmutableMap.builder();
|
||||
ImmutableMultimap.Builder<String, MenuDO> permMenuCacheBuilder = ImmutableMultimap.builder();
|
||||
menuList.forEach(menuDO -> {
|
||||
@ -123,7 +124,7 @@ public class MenuServiceImpl implements MenuService {
|
||||
menuCache = menuCacheBuilder.build();
|
||||
permissionMenuCache = permMenuCacheBuilder.build();
|
||||
|
||||
// 设置最新的 maxUpdateTime,用于下次的增量判断
|
||||
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||
this.maxUpdateTime = CollectionUtils.getMaxValue(menuList, MenuDO::getUpdateTime);
|
||||
}
|
||||
|
||||
|
@ -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.datapermission.core.annotation.DataPermission;
|
||||
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.dal.dataobject.dept.DeptDO;
|
||||
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.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -126,106 +126,84 @@ public class PermissionServiceImpl implements PermissionService {
|
||||
@Resource
|
||||
private PermissionProducer permissionProducer;
|
||||
|
||||
@Resource
|
||||
@Lazy // 注入自己,所以延迟加载
|
||||
private PermissionService self;
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
@TenantIgnore // 初始化缓存,无需租户过滤
|
||||
public void initLocalCache() {
|
||||
initUserRoleLocalCache();
|
||||
initRoleMenuLocalCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化 {@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());
|
||||
initLocalCacheIfUpdateForRoleMenu(null);
|
||||
initLocalCacheIfUpdateForUserRole(null);
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||
public void schedulePeriodicRefresh() {
|
||||
self.initLocalCache();
|
||||
initLocalCacheIfUpdateForRoleMenu(this.roleMenuMaxUpdateTime);
|
||||
initLocalCacheIfUpdateForUserRole(this.userRoleMaxUpdateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果角色与菜单的关联发生变化,从数据库中获取最新的全量角色与菜单的关联。
|
||||
* 如果未发生变化,则返回空
|
||||
* 刷新 RoleMenu 本地缓存
|
||||
*
|
||||
* @param maxUpdateTime 当前角色与菜单的关联的最大更新时间
|
||||
* @return 角色与菜单的关联列表
|
||||
* @param maxUpdateTime 最大更新时间
|
||||
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||
*/
|
||||
protected List<RoleMenuDO> loadRoleMenuIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步,判断是否要更新。
|
||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
||||
log.info("[loadRoleMenuIfUpdate][首次加载全量角色与菜单的关联]");
|
||||
} else { // 判断数据库中是否有更新的角色与菜单的关联
|
||||
if (roleMenuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
return null;
|
||||
@VisibleForTesting
|
||||
void initLocalCacheIfUpdateForRoleMenu(LocalDateTime maxUpdateTime) {
|
||||
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||
TenantUtils.executeIgnore(() -> {
|
||||
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||
if (maxUpdateTime != null
|
||||
&& roleMenuMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
log.info("[initLocalCacheIfUpdateForRoleMenu][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||
return;
|
||||
}
|
||||
log.info("[loadRoleMenuIfUpdate][增量加载全量角色与菜单的关联]");
|
||||
}
|
||||
// 第二步,如果有更新,则从数据库加载所有角色与菜单的关联
|
||||
return roleMenuMapper.selectList();
|
||||
List<RoleMenuDO> roleMenus = roleMenuMapper.selectList();
|
||||
log.info("[initLocalCacheIfUpdateForRoleMenu][缓存角色与菜单,数量为:{}]", roleMenus.size());
|
||||
|
||||
// 第二步:构建缓存。
|
||||
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 当前角色与菜单的关联的最大更新时间
|
||||
* @return 角色与菜单的关联列表
|
||||
* @param maxUpdateTime 最大更新时间
|
||||
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||
*/
|
||||
protected List<UserRoleDO> loadUserRoleIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步,判断是否要更新。
|
||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
||||
log.info("[loadUserRoleIfUpdate][首次加载全量用户与角色的关联]");
|
||||
} else { // 判断数据库中是否有更新的用户与角色的关联
|
||||
if (userRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
return null;
|
||||
@VisibleForTesting
|
||||
void initLocalCacheIfUpdateForUserRole(LocalDateTime maxUpdateTime) {
|
||||
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||
TenantUtils.executeIgnore(() -> {
|
||||
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||
if (maxUpdateTime != null
|
||||
&& userRoleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
log.info("[initLocalCacheIfUpdateForUserRole][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||
return;
|
||||
}
|
||||
log.info("[loadUserRoleIfUpdate][增量加载全量用户与角色的关联]");
|
||||
}
|
||||
// 第二步,如果有更新,则从数据库加载所有用户与角色的关联
|
||||
return userRoleMapper.selectList();
|
||||
List<UserRoleDO> userRoles = userRoleMapper.selectList();
|
||||
log.info("[initLocalCacheIfUpdateForUserRole][缓存用户与角色,数量为:{}]", userRoles.size());
|
||||
|
||||
// 第二步:构建缓存。
|
||||
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
|
||||
|
@ -6,8 +6,7 @@ import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
|
||||
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.RoleExportReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
|
||||
@ -15,13 +14,13 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleUp
|
||||
import cn.iocoder.yudao.module.system.convert.permission.RoleConvert;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMapper;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.RoleTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.permission.RoleProducer;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -77,53 +76,46 @@ public class RoleServiceImpl implements RoleService {
|
||||
@Resource
|
||||
private RoleProducer roleProducer;
|
||||
|
||||
@Resource
|
||||
@Lazy // 注入自己,所以延迟加载
|
||||
private RoleService self;
|
||||
|
||||
/**
|
||||
* 初始化 {@link #roleCache} 缓存
|
||||
*/
|
||||
@Override
|
||||
@PostConstruct
|
||||
@TenantIgnore // 忽略自动多租户,全局初始化缓存
|
||||
public void initLocalCache() {
|
||||
// 获取角色列表,如果有更新
|
||||
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());
|
||||
initLocalCacheIfUpdate(null);
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||
public void schedulePeriodicRefresh() {
|
||||
self.initLocalCache();
|
||||
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果角色发生变化,从数据库中获取最新的全量角色。
|
||||
* 如果未发生变化,则返回空
|
||||
* 刷新本地缓存
|
||||
*
|
||||
* @param maxUpdateTime 当前角色的最大更新时间
|
||||
* @return 角色列表
|
||||
* @param maxUpdateTime 最大更新时间
|
||||
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||
*/
|
||||
private List<RoleDO> loadRoleIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步,判断是否要更新。
|
||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
||||
log.info("[loadRoleIfUpdate][首次加载全量角色]");
|
||||
} else { // 判断数据库中是否有更新的角色
|
||||
if (roleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
return null;
|
||||
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||
TenantUtils.executeIgnore(() -> {
|
||||
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||
if (maxUpdateTime != null
|
||||
&& roleMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||
return;
|
||||
}
|
||||
log.info("[loadRoleIfUpdate][增量加载全量角色]");
|
||||
}
|
||||
// 第二步,如果有更新,则从数据库加载所有角色
|
||||
return roleMapper.selectList();
|
||||
List<RoleDO> roleList = roleMapper.selectList();
|
||||
log.info("[initLocalCacheIfUpdate][缓存角色,数量为:{}]", roleList.size());
|
||||
|
||||
// 第二步:构建缓存。
|
||||
roleCache = CollectionUtils.convertMap(roleList, RoleDO::getId);
|
||||
|
||||
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||
this.maxUpdateTime = CollectionUtils.getMaxValue(roleList, RoleDO::getUpdateTime);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -84,21 +84,42 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initLocalCache() {
|
||||
// 获取敏感词列表,如果有更新
|
||||
List<SensitiveWordDO> sensitiveWordList = loadSensitiveWordIfUpdate(maxUpdateTime);
|
||||
if (CollUtil.isEmpty(sensitiveWordList)) {
|
||||
initLocalCacheIfUpdate(null);
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
List<SensitiveWordDO> sensitiveWords = sensitiveWordMapper.selectList();
|
||||
log.info("[initLocalCacheIfUpdate][缓存敏感词,数量为:{}]", sensitiveWords.size());
|
||||
|
||||
// 第二步:构建缓存。
|
||||
// 写入 sensitiveWordTagsCache 缓存
|
||||
Set<String> tags = new HashSet<>();
|
||||
sensitiveWordList.forEach(word -> tags.addAll(word.getTags()));
|
||||
sensitiveWords.forEach(word -> tags.addAll(word.getTags()));
|
||||
sensitiveWordTagsCache = tags;
|
||||
// 写入 defaultSensitiveWordTrie、tagSensitiveWordTries 缓存
|
||||
initSensitiveWordTrie(sensitiveWordList);
|
||||
// 写入 maxUpdateTime 最大更新时间
|
||||
maxUpdateTime = CollectionUtils.getMaxValue(sensitiveWordList, SensitiveWordDO::getUpdateTime);
|
||||
log.info("[initLocalCache][初始化 敏感词 数量为 {}]", sensitiveWordList.size());
|
||||
initSensitiveWordTrie(sensitiveWords);
|
||||
|
||||
// 第三步:设置最新的 maxUpdateTime,用于下次的增量判断。
|
||||
this.maxUpdateTime = CollectionUtils.getMaxValue(sensitiveWords, SensitiveWordDO::getUpdateTime);
|
||||
}
|
||||
|
||||
private void initSensitiveWordTrie(List<SensitiveWordDO> wordDOs) {
|
||||
@ -122,33 +143,6 @@ public class SensitiveWordServiceImpl implements SensitiveWordService {
|
||||
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
|
||||
public Long createSensitiveWord(SensitiveWordCreateReqVO createReqVO) {
|
||||
// 校验唯一性
|
||||
|
@ -21,7 +21,7 @@ public interface SmsChannelService {
|
||||
/**
|
||||
* 初始化短信客户端
|
||||
*/
|
||||
void initSmsClients();
|
||||
void initLocalCache();
|
||||
|
||||
/**
|
||||
* 创建短信渠道
|
||||
|
@ -1,8 +1,6 @@
|
||||
package cn.iocoder.yudao.module.system.service.sms;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
|
||||
import cn.iocoder.yudao.framework.sms.core.property.SmsChannelProperties;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.sms.vo.channel.SmsChannelCreateReqVO;
|
||||
@ -23,14 +21,14 @@ import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
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_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* 短信渠道Service实现类
|
||||
* 短信渠道 Service 实现类
|
||||
*
|
||||
* @author zzf
|
||||
* @date 2021/1/25 9:25
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@ -61,46 +59,39 @@ public class SmsChannelServiceImpl implements SmsChannelService {
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initSmsClients() {
|
||||
// 获取短信渠道,如果有更新
|
||||
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());
|
||||
public void initLocalCache() {
|
||||
initLocalCacheIfUpdate(null);
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelay = SCHEDULER_PERIOD, initialDelay = SCHEDULER_PERIOD)
|
||||
public void schedulePeriodicRefresh() {
|
||||
initSmsClients();
|
||||
initLocalCacheIfUpdate(this.maxUpdateTime);
|
||||
}
|
||||
|
||||
/**
|
||||
* 如果短信渠道发生变化,从数据库中获取最新的全量短信渠道。
|
||||
* 如果未发生变化,则返回空
|
||||
* 刷新本地缓存
|
||||
*
|
||||
* @param maxUpdateTime 当前短信渠道的最大更新时间
|
||||
* @return 短信渠道列表
|
||||
* @param maxUpdateTime 最大更新时间
|
||||
* 1. 如果 maxUpdateTime 为 null,则“强制”刷新缓存
|
||||
* 2. 如果 maxUpdateTime 不为 null,判断自 maxUpdateTime 是否有数据发生变化,有的情况下才刷新缓存
|
||||
*/
|
||||
private List<SmsChannelDO> loadSmsChannelIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步,判断是否要更新。
|
||||
if (maxUpdateTime == null) { // 如果更新时间为空,说明 DB 一定有新数据
|
||||
log.info("[loadSmsChannelIfUpdate][首次加载全量短信渠道]");
|
||||
} else { // 判断数据库中是否有更新的短信渠道
|
||||
if (smsChannelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
return null;
|
||||
}
|
||||
log.info("[loadSmsChannelIfUpdate][增量加载全量短信渠道]");
|
||||
private void initLocalCacheIfUpdate(LocalDateTime maxUpdateTime) {
|
||||
// 第一步:基于 maxUpdateTime 判断缓存是否刷新。
|
||||
// 如果没有增量的数据变化,则不进行本地缓存的刷新
|
||||
if (maxUpdateTime != null
|
||||
&& smsChannelMapper.selectCountByUpdateTimeGt(maxUpdateTime) == 0) {
|
||||
log.info("[initLocalCacheIfUpdate][数据未发生变化({}),本地缓存不刷新]", maxUpdateTime);
|
||||
return;
|
||||
}
|
||||
// 第二步,如果有更新,则从数据库加载所有短信渠道
|
||||
return smsChannelMapper.selectList();
|
||||
List<SmsChannelDO> channels = 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
|
||||
|
@ -113,7 +113,7 @@ public class SmsSendServiceImpl implements SmsSendService {
|
||||
SmsChannelDO channelDO = smsChannelService.getSmsChannel(channelId);
|
||||
// 短信模板不存在
|
||||
if (channelDO == null) {
|
||||
throw exception(SMS_SEND_TEMPLATE_NOT_EXISTS);
|
||||
throw exception(SMS_CHANNEL_NOT_EXISTS);
|
||||
}
|
||||
return channelDO;
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.exception.ServiceException;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
||||
import cn.iocoder.yudao.module.infra.api.file.FileApi;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;
|
||||
@ -72,6 +73,10 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
@Resource
|
||||
private FileApi fileApi;
|
||||
|
||||
@Resource
|
||||
@Lazy // 循环依赖(自己依赖自己),避免报错
|
||||
private AdminUserServiceImpl self;
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createUser(UserCreateReqVO reqVO) {
|
||||
@ -83,7 +88,7 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
}
|
||||
});
|
||||
// 校验正确性
|
||||
checkCreateOrUpdate(null, reqVO.getUsername(), reqVO.getMobile(), reqVO.getEmail(),
|
||||
self.checkCreateOrUpdate(null, reqVO.getUsername(), reqVO.getMobile(), reqVO.getEmail(),
|
||||
reqVO.getDeptId(), reqVO.getPostIds());
|
||||
// 插入用户
|
||||
AdminUserDO user = UserConvert.INSTANCE.convert(reqVO);
|
||||
@ -102,7 +107,7 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void updateUser(UserUpdateReqVO reqVO) {
|
||||
// 校验正确性
|
||||
checkCreateOrUpdate(reqVO.getId(), reqVO.getUsername(), reqVO.getMobile(), reqVO.getEmail(),
|
||||
self.checkCreateOrUpdate(reqVO.getId(), reqVO.getUsername(), reqVO.getMobile(), reqVO.getEmail(),
|
||||
reqVO.getDeptId(), reqVO.getPostIds());
|
||||
// 更新用户
|
||||
AdminUserDO updateObj = UserConvert.INSTANCE.convert(reqVO);
|
||||
@ -299,7 +304,8 @@ public class AdminUserServiceImpl implements AdminUserService {
|
||||
return deptIds;
|
||||
}
|
||||
|
||||
private void checkCreateOrUpdate(Long id, String username, String mobile, String email,
|
||||
@DataPermission(enable = false) // 关闭数据权限,避免因为没有数据权限,查询不到数据,进而导致唯一校验不正确
|
||||
public void checkCreateOrUpdate(Long id, String username, String mobile, String email,
|
||||
Long deptId, Set<Long> postIds) {
|
||||
// 校验用户存在
|
||||
checkUserExists(id);
|
||||
|
@ -1,6 +1,7 @@
|
||||
package cn.iocoder.yudao.module.system.service.dept;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
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.DeptUpdateReqVO;
|
||||
@ -12,6 +13,7 @@ import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import com.google.common.collect.Multimap;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
@ -47,6 +49,12 @@ public class DeptServiceTest extends BaseDbUnitTest {
|
||||
@MockBean
|
||||
private DeptProducer deptProducer;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
// 清理租户上下文
|
||||
TenantContextHolder.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
@SuppressWarnings("unchecked")
|
||||
void testInitLocalCache() {
|
||||
|
@ -57,7 +57,7 @@ public class DictDataServiceTest extends BaseDbUnitTest {
|
||||
// 准备参数
|
||||
DictDataPageReqVO reqVO = new DictDataPageReqVO();
|
||||
reqVO.setLabel("芋");
|
||||
reqVO.setDictType("yu");
|
||||
reqVO.setDictType("yunai");
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
|
||||
// 调用
|
||||
@ -86,7 +86,7 @@ public class DictDataServiceTest extends BaseDbUnitTest {
|
||||
// 准备参数
|
||||
DictDataExportReqVO reqVO = new DictDataExportReqVO();
|
||||
reqVO.setLabel("芋");
|
||||
reqVO.setDictType("yu");
|
||||
reqVO.setDictType("yunai");
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
|
||||
// 调用
|
||||
|
@ -71,7 +71,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
||||
private PermissionProducer permissionProducer;
|
||||
|
||||
@Test
|
||||
public void testInitRoleMenuLocalCache() {
|
||||
public void testInitLocalCacheIfUpdateForRoleMenu() {
|
||||
// mock 数据
|
||||
RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(10L));
|
||||
roleMenuMapper.insert(roleMenuDO01);
|
||||
@ -79,7 +79,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
||||
roleMenuMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
permissionService.initRoleMenuLocalCache();
|
||||
permissionService.initLocalCacheIfUpdateForRoleMenu(null);
|
||||
// 断言 roleMenuCache 缓存
|
||||
assertEquals(1, permissionService.getRoleMenuCache().keySet().size());
|
||||
assertEquals(asList(10L, 20L), permissionService.getRoleMenuCache().get(1L));
|
||||
@ -93,7 +93,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitUserRoleLocalCache() {
|
||||
public void testInitLocalCacheIfUpdateForUserRole() {
|
||||
// mock 数据
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
@ -101,7 +101,7 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
||||
userRoleMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
permissionService.initUserRoleLocalCache();
|
||||
permissionService.initLocalCacheIfUpdateForUserRole(null);
|
||||
// 断言 roleMenuCache 缓存
|
||||
assertEquals(1, permissionService.getUserRoleCache().size());
|
||||
assertEquals(asSet(10L, 20L), permissionService.getUserRoleCache().get(1L));
|
||||
|
@ -57,7 +57,7 @@ public class SmsChannelServiceTest extends BaseDbUnitTest {
|
||||
smsChannelMapper.insert(smsChannelDO02);
|
||||
|
||||
// 调用
|
||||
smsChannelService.initSmsClients();
|
||||
smsChannelService.initLocalCache();
|
||||
// 校验 maxUpdateTime 属性
|
||||
LocalDateTime maxUpdateTime = (LocalDateTime) BeanUtil.getFieldValue(smsChannelService, "maxUpdateTime");
|
||||
assertEquals(max(smsChannelDO01.getUpdateTime(), smsChannelDO02.getUpdateTime()), maxUpdateTime);
|
||||
|
@ -9,7 +9,7 @@ spring:
|
||||
# 数据源配置项
|
||||
datasource:
|
||||
name: ruoyi-vue-pro
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
|
||||
url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式;DATABASE_TO_UPPER 配置表和字段使用小写
|
||||
driver-class-name: org.h2.Driver
|
||||
username: sa
|
||||
password:
|
||||
|
@ -111,6 +111,7 @@ CREATE TABLE IF NOT EXISTS "system_dict_type" (
|
||||
"updater" varchar(64) DEFAULT '',
|
||||
"update_time" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"deleted" bit NOT NULL DEFAULT FALSE,
|
||||
"deleted_time" timestamp NOT NULL,
|
||||
PRIMARY KEY ("id")
|
||||
) COMMENT '字典类型表';
|
||||
|
||||
|
Reference in New Issue
Block a user