Merge remote-tracking branch 'origin/main'

This commit is contained in:
liuzhaotian
2024-06-28 17:12:53 +08:00
5 changed files with 136 additions and 25 deletions
@@ -37,7 +37,7 @@ public class LabelController {
@PostMapping("/create") @PostMapping("/create")
@Operation(summary = "创建标签") @Operation(summary = "创建标签")
//@PreAuthorize("@ss.hasPermission('system:label:create')") @PreAuthorize("@ss.hasPermission('sysTagDesignManager:create')")
@OperateLog(enable = false) @OperateLog(enable = false)
public CommonResult<Long> createLabel(@Valid @RequestBody LabelSaveReqVO createReqVO) { public CommonResult<Long> createLabel(@Valid @RequestBody LabelSaveReqVO createReqVO) {
return success(labelService.createLabel(createReqVO)); return success(labelService.createLabel(createReqVO));
@@ -45,7 +45,7 @@ public class LabelController {
@PutMapping("/update") @PutMapping("/update")
@Operation(summary = "更新标签") @Operation(summary = "更新标签")
//@PreAuthorize("@ss.hasPermission('system:label:update')") @PreAuthorize("@ss.hasPermission('sysTagDesignManager:update')")
@OperateLog(enable = false) @OperateLog(enable = false)
public CommonResult<Boolean> updateLabel(@Valid @RequestBody LabelSaveReqVO updateReqVO) { public CommonResult<Boolean> updateLabel(@Valid @RequestBody LabelSaveReqVO updateReqVO) {
labelService.updateLabel(updateReqVO); labelService.updateLabel(updateReqVO);
@@ -55,7 +55,7 @@ public class LabelController {
@DeleteMapping("/delete") @DeleteMapping("/delete")
@Operation(summary = "删除标签") @Operation(summary = "删除标签")
@Parameter(name = "id", description = "编号", required = true) @Parameter(name = "id", description = "编号", required = true)
//@PreAuthorize("@ss.hasPermission('system:label:delete')") @PreAuthorize("@ss.hasPermission('sysTagDesignManager:delete')")
public CommonResult<Boolean> deleteLabel(@RequestParam("id") Long id) { public CommonResult<Boolean> deleteLabel(@RequestParam("id") Long id) {
labelService.deleteLabel(id); labelService.deleteLabel(id);
return success(true); return success(true);
@@ -90,7 +90,7 @@ public class LabelController {
@GetMapping("/group-list") @GetMapping("/group-list")
@Operation(summary = "获得分组标签列表") @Operation(summary = "获得分组标签列表")
//@PreAuthorize("@ss.hasPermission('system:label:query')") @PreAuthorize("@ss.hasAnyPermissions('system:label-template:query','sysTagDesignManager:query')")
@OperateLog(enable = false) @OperateLog(enable = false)
public CommonResult<Map<String, List<LabelRespVO>>> getGroupList(@RequestParam(value = "organId", required = false) Long organId) { public CommonResult<Map<String, List<LabelRespVO>>> getGroupList(@RequestParam(value = "organId", required = false) Long organId) {
return success(labelService.getGroupList(organId)); return success(labelService.getGroupList(organId));
@@ -87,7 +87,7 @@ public class LabelTemplateController {
@GetMapping("/getDefault") @GetMapping("/getDefault")
@Operation(summary = "获得默认标签模板") @Operation(summary = "获得默认标签模板")
@Parameter(name = "type", description = "标签类型", required = true, example = "1024") @Parameter(name = "type", description = "标签类型", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('system:label-template:query')") @PreAuthorize("@ss.hasAnyPermissions('system:label-template:query','sysTagDesignManager:query')")
@OperateLog(enable = false) @OperateLog(enable = false)
public CommonResult<LabelTemplateRespVO> getDefaultLabelTemplate(@RequestParam("type") String type) { public CommonResult<LabelTemplateRespVO> getDefaultLabelTemplate(@RequestParam("type") String type) {
return success(labelTemplateService.getDefaultLabelTemplate(type)); return success(labelTemplateService.getDefaultLabelTemplate(type));
@@ -17,10 +17,10 @@ public class UserSaveReqVO {
@Schema(description = "用户编号", example = "1024") @Schema(description = "用户编号", example = "1024")
private Long id; private Long id;
@Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "chenfeng") @Schema(description = "a", requiredMode = Schema.RequiredMode.REQUIRED, example = "chenfeng")
@NotBlank(message = "用户账号不能为空") @NotBlank(message = "用户不能为空")
@Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成") @Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户由 数字、字母 组成")
@Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符") @Size(min = 4, max = 30, message = "用户长度为 4-30 个字符")
private String username; private String username;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "晨丰") @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "晨丰")
@@ -1,6 +1,8 @@
package com.cf.imes.module.system.service.permission; package com.cf.imes.module.system.service.permission;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import com.cf.imes.framework.common.util.object.BeanUtils; import com.cf.imes.framework.common.util.object.BeanUtils;
import com.cf.imes.framework.organ.core.aop.OrganIgnore; import com.cf.imes.framework.organ.core.aop.OrganIgnore;
import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuListReqVO; import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
@@ -8,13 +10,18 @@ import com.cf.imes.module.system.controller.admin.permission.vo.menu.MenuSaveVO;
import com.cf.imes.module.system.dal.dataobject.permission.MenuDO; import com.cf.imes.module.system.dal.dataobject.permission.MenuDO;
import com.cf.imes.module.system.dal.mysql.permission.MenuMapper; import com.cf.imes.module.system.dal.mysql.permission.MenuMapper;
import com.cf.imes.module.system.dal.redis.RedisKeyConstants; import com.cf.imes.module.system.dal.redis.RedisKeyConstants;
import com.cf.imes.module.system.dal.redis.RedisRefreshChannelTopicConstants;
import com.cf.imes.module.system.enums.permission.MenuTypeEnum; import com.cf.imes.module.system.enums.permission.MenuTypeEnum;
import com.cf.imes.module.system.service.organ.OrganService; import com.cf.imes.module.system.service.organ.OrganService;
import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -22,6 +29,7 @@ import javax.annotation.Resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture;
import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception; import static com.cf.imes.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertList; import static com.cf.imes.framework.common.util.collection.CollectionUtils.convertList;
@@ -44,6 +52,9 @@ public class MenuServiceImpl implements MenuService {
@Lazy // 延迟,避免循环依赖报错 @Lazy // 延迟,避免循环依赖报错
private OrganService organService; private OrganService organService;
@Autowired
private RedisTemplate redisTemplate;
@Override @Override
@CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#createReqVO.permission", @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#createReqVO.permission",
condition = "#createReqVO.permission != null") condition = "#createReqVO.permission != null")
@@ -57,8 +68,13 @@ public class MenuServiceImpl implements MenuService {
MenuDO menu = BeanUtils.toBean(createReqVO, MenuDO.class); MenuDO menu = BeanUtils.toBean(createReqVO, MenuDO.class);
initMenuProperty(menu); initMenuProperty(menu);
menuMapper.insert(menu); menuMapper.insert(menu);
Long menuId = menu.getId();
// 清理缓存
flushCacheWhenOperateMenu(menuId, createReqVO.getPermission(), null);
// 返回 // 返回
return menu.getId(); return menuId;
} }
@Override @Override
@@ -66,7 +82,8 @@ public class MenuServiceImpl implements MenuService {
allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理,简单有效 allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理,简单有效
public void updateMenu(MenuSaveVO updateReqVO) { public void updateMenu(MenuSaveVO updateReqVO) {
// 校验更新的菜单是否存在 // 校验更新的菜单是否存在
if (menuMapper.selectById(updateReqVO.getId()) == null) { MenuDO menuDO = menuMapper.selectById(updateReqVO.getId());
if (menuDO == null) {
throw exception(MENU_NOT_EXISTS); throw exception(MENU_NOT_EXISTS);
} }
// 校验父菜单存在 // 校验父菜单存在
@@ -78,6 +95,13 @@ public class MenuServiceImpl implements MenuService {
MenuDO updateObj = BeanUtils.toBean(updateReqVO, MenuDO.class); MenuDO updateObj = BeanUtils.toBean(updateReqVO, MenuDO.class);
initMenuProperty(updateObj); initMenuProperty(updateObj);
menuMapper.updateById(updateObj); menuMapper.updateById(updateObj);
// 权限串发生改变清空缓存
String oldPermission = menuDO.getPermission();
String newPermission = updateReqVO.getPermission();
if (ObjectUtil.notEqual(oldPermission, newPermission)) {
flushCacheWhenOperateMenu(menuDO.getId(), newPermission, oldPermission);
}
} }
@Override @Override
@@ -90,13 +114,76 @@ public class MenuServiceImpl implements MenuService {
throw exception(MENU_EXISTS_CHILDREN); throw exception(MENU_EXISTS_CHILDREN);
} }
// 校验删除的菜单是否存在 // 校验删除的菜单是否存在
if (menuMapper.selectById(id) == null) { MenuDO menuDO = menuMapper.selectById(id);
if (menuDO == null) {
throw exception(MENU_NOT_EXISTS); throw exception(MENU_NOT_EXISTS);
} }
// 标记删除 // 标记删除
menuMapper.deleteById(id); menuMapper.deleteById(id);
// 删除授予给角色的权限 // 删除授予给角色的权限
permissionService.processMenuDeleted(id); permissionService.processMenuDeleted(id);
// 清理缓存
flushCacheWhenOperateMenu(id, null, menuDO.getPermission());
}
/**
* 菜单增删改时清理缓存
*
* @param menuId 菜单id
* @param newPermission 新权限串
* @param oldPermission 旧权限串
*/
private void flushCacheWhenOperateMenu(Long menuId, String newPermission, String oldPermission) {
CompletableFuture.runAsync(() -> {
if (ObjectUtil.isNotNull(oldPermission)) {
// 移除 USER_ROLE_ID_LIST:*:旧的permission
String oldMenuPerPattern = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.PERMISSION_MENU_ID_LIST).append(":*:%s")), oldPermission);
Long oldBatchDelPerNum = scanAndDelKeys(oldMenuPerPattern);
log.info("[updateMenu] 批量删除redis[{}]数量:{}", oldMenuPerPattern, oldBatchDelPerNum);
}
if (ObjectUtil.isNotNull(newPermission)) {
// 移除 USER_ROLE_ID_LIST:*:新的permission
String newMenuPerPattern = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.PERMISSION_MENU_ID_LIST).append(":*:%s")), newPermission);
Long newBatchDelPerNum = scanAndDelKeys(newMenuPerPattern);
log.info("[updateMenu] 批量删除redis[{}]数量:{}", newMenuPerPattern, newBatchDelPerNum);
}
if (ObjectUtil.isNotNull(menuId)) {
// 移除 MENU_ROLE_ID_LIST:*:menuId
String menuRolePattern = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.MENU_ROLE_ID_LIST).append(":*:%s")), menuId);
Long batchDelRoleNum = scanAndDelKeys(menuRolePattern);
log.info("[deleteMenu] 批量删除redis[{}]数量:{}", menuRolePattern, batchDelRoleNum);
}
// 清空本地缓存
redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, "");
}).exceptionally(e -> {
log.error("[deleteMenu] redis 清空PERMISSION_MENU_ID_LIST或message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e);
return null;
});
}
/**
* scan所有匹配的redis key并删除
*
* @param keyPattern 模糊匹配的表达式
*/
private Long scanAndDelKeys(String keyPattern) {
// 根据keyPattern scan匹配的redis key
List<String> delKeys = new ArrayList<>();
Cursor<String> cursor = redisTemplate.scan(ScanOptions.scanOptions().match(keyPattern).count(200).build());
while (cursor.hasNext()) {
delKeys.add(cursor.next());
}
cursor.close();
// 删除匹配到的key
if (CollectionUtil.isNotEmpty(delKeys)) {
return redisTemplate.delete(delKeys);
}
return 0L;
} }
@Override @Override
@@ -39,7 +39,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable; import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching; import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.Cursor;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -205,23 +207,44 @@ public class PermissionServiceImpl implements PermissionService {
// 角色菜单权限发生修改,通知刷新本地缓存 // 角色菜单权限发生修改,通知刷新本地缓存
if (CollectionUtil.isNotEmpty(createMenuIds) || CollectionUtil.isNotEmpty(deleteMenuIds)) { if (CollectionUtil.isNotEmpty(createMenuIds) || CollectionUtil.isNotEmpty(deleteMenuIds)) {
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
// 当前操作人和所操作的目标菜单机构不同时,清空MENU_ROLE_ID_LIST:目标机构id:菜单id的缓存 // 当前操作人和所操作的目标菜单机构不同时,清空MENU_ROLE_ID_LIST:*:菜单id的缓存
if (ObjectUtil.notEqual(finalOrganId, currentOrganId)) { if (ObjectUtil.notEqual(finalOrganId, currentOrganId)) {
// 获取新增和删除菜单id的并集 // 获取新增和删除菜单id的并集
Collection<Long> delMenuIds = CollUtil.union(createMenuIds, deleteMenuIds); Collection<Long> delMenuIds = CollUtil.union(createMenuIds, deleteMenuIds);
// 构建批量删除的key集合 delMenuIds.forEach(delMenuId -> {
Set<String> delKeys = delMenuIds.stream().map(menuId -> String.format(String.valueOf(new StringBuffer(RedisKeyConstants.MENU_ROLE_ID_LIST).append(":%s:%s")), finalOrganId, menuId)).collect(Collectors.toSet()); String patternKey = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.MENU_ROLE_ID_LIST).append(":*:%s")), delMenuId);
Long batchDelNum = redisTemplate.delete(delKeys); Long batchDelNum = scanAndDelKeys(patternKey);
log.info("[assignRoleMenu] 批量删除redis[MENU_ROLE_ID_LIST]数量:{}", batchDelNum); log.info("[assignRoleMenu] 批量删除redis[{}]数量:{}", patternKey, batchDelNum);
});
} }
redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, ""); redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, "");
}).exceptionally(e -> { }).exceptionally(e -> {
log.error("[assignRoleMenu] redis message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e); log.error("[assignRoleMenu] redis 清空MENU_ROLE_ID_LIST或message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e);
return null; return null;
}); });
} }
} }
/**
* scan所有匹配的redis key并删除
*
* @param keyPattern 模糊匹配的表达式
*/
private Long scanAndDelKeys(String keyPattern) {
// 根据keyPattern scan匹配的redis key
List<String> delKeys = new ArrayList<>();
Cursor<String> cursor = redisTemplate.scan(ScanOptions.scanOptions().match(keyPattern).count(200).build());
while (cursor.hasNext()) {
delKeys.add(cursor.next());
}
cursor.close();
// 删除匹配到的key
if (CollectionUtil.isNotEmpty(delKeys)) {
return redisTemplate.delete(delKeys);
}
return 0L;
}
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
@Caching(evict = { @Caching(evict = {
@@ -362,18 +385,19 @@ public class PermissionServiceImpl implements PermissionService {
// 角色菜单权限发生修改,通知刷新本地缓存 // 角色菜单权限发生修改,通知刷新本地缓存
if (CollectionUtil.isNotEmpty(createRoleUserIds) || CollectionUtil.isNotEmpty(deleteRoleUserIds)) { if (CollectionUtil.isNotEmpty(createRoleUserIds) || CollectionUtil.isNotEmpty(deleteRoleUserIds)) {
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
// 当前操作人和所操作的目标菜单机构不同时,清空USER_ROLE_ID_LIST:目标机构id:用户id的缓存 // 当前操作人和所操作的目标菜单机构不同时,清空USER_ROLE_ID_LIST:*:用户id的缓存
if (ObjectUtil.notEqual(finalOrganId, currentOrganId)) { if (ObjectUtil.notEqual(finalOrganId, currentOrganId)) {
// 获取新增和删除用户id的并集 // 获取新增和删除用户id的并集
Collection<Long> delRoleUserIds = CollUtil.union(createRoleUserIds, deleteRoleUserIds); Collection<Long> delRoleUserIds = CollUtil.union(createRoleUserIds, deleteRoleUserIds);
// 构建批量删除的key集合 delRoleUserIds.forEach(delRoleUserId -> {
Set<String> delKeys = delRoleUserIds.stream().map(userId -> String.format(String.valueOf(new StringBuffer(RedisKeyConstants.USER_ROLE_ID_LIST).append(":%s:%s")), finalOrganId, userId)).collect(Collectors.toSet()); String patternKey = String.format(String.valueOf(new StringBuffer(RedisKeyConstants.USER_ROLE_ID_LIST).append(":*:%s")), delRoleUserId);
Long batchDelNum = redisTemplate.delete(delKeys); Long batchDelNum = scanAndDelKeys(patternKey);
log.info("[batchAssignRoleUsers] 批量删除redis[USER_ROLE_ID_LIST]数量:{}", batchDelNum); log.info("[batchAssignRoleUsers] 批量删除redis[{}]数量:{}", patternKey, batchDelNum);
});
} }
redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, ""); redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, "");
}).exceptionally(e -> { }).exceptionally(e -> {
log.error("[batchAssignRoleUsers] redis message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e); log.error("[batchAssignRoleUsers] redis 清空USER_ROLE_ID_LIST或message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e);
return null; return null;
}); });
} }
@@ -455,7 +479,7 @@ public class PermissionServiceImpl implements PermissionService {
redisTemplate.delete(String.format(String.valueOf(new StringBuffer(RedisKeyConstants.USER_ROLE_ID_LIST).append(":%s:%s")), organId, userId)); redisTemplate.delete(String.format(String.valueOf(new StringBuffer(RedisKeyConstants.USER_ROLE_ID_LIST).append(":%s:%s")), organId, userId));
redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, ""); redisTemplate.convertAndSend(RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, "");
}).exceptionally(e -> { }).exceptionally(e -> {
log.error("[assignUserRole] redis 清空USER_ROLE_ID_LIST或message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e); log.error("[assignUserRole] redis message发送失败, topic:{}, 异常:{}", RedisRefreshChannelTopicConstants.PERMISSION_REFRESH, e);
return null; return null;
}); });
} }