使用 Redis 缓存,替代本地缓存
parent
3dd4700ce4
commit
8b704ff483
|
@ -39,6 +39,8 @@ public class BannerApplicationRunner implements ApplicationRunner {
|
|||
System.out.println("[微信公众号 yudao-module-mp 教程][参考 https://cloud.iocoder.cn/mp/build/ 开启]");
|
||||
// 商城
|
||||
System.out.println("[商城系统 yudao-module-mall 教程][参考 https://cloud.iocoder.cn/mall/build/ 开启]");
|
||||
// 支付
|
||||
System.out.println("[支付系统 yudao-module-pay - 已禁用][参考 https://doc.iocoder.cn/pay/build/ 开启]");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -103,8 +103,16 @@ public interface BaseMapperX<T> extends BaseMapper<T> {
|
|||
update(update, new QueryWrapper<>());
|
||||
}
|
||||
|
||||
default void updateBatch(Collection<T> entities) {
|
||||
Db.updateBatchById(entities);
|
||||
}
|
||||
|
||||
default void updateBatch(Collection<T> entities, int size) {
|
||||
Db.updateBatchById(entities, size);
|
||||
}
|
||||
|
||||
default void saveOrUpdateBatch(Collection<T> collection) {
|
||||
Db.saveOrUpdateBatch(collection);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ import java.sql.ResultSet;
|
|||
import java.sql.SQLException;
|
||||
|
||||
/**
|
||||
* 字段字段的 TypeHandler 实现类,基于 {@link AES} 实现
|
||||
* 字段字段的 TypeHandler 实现类,基于 {@link cn.hutool.crypto.symmetric.AES} 实现
|
||||
* 可通过 jasypt.encryptor.password 配置项,设置密钥
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
|
|
@ -36,6 +36,11 @@
|
|||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-all</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.datatype</groupId>
|
||||
<artifactId>jackson-datatype-jsr310</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package cn.iocoder.yudao.framework.redis.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.redis.core.TimeoutRedisCacheManager;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.cache.CacheProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
|
@ -7,8 +9,15 @@ import org.springframework.cache.annotation.EnableCaching;
|
|||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.RedisSerializationContext;
|
||||
import org.springframework.data.redis.serializer.RedisSerializer;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
import static cn.iocoder.yudao.framework.redis.config.YudaoRedisAutoConfiguration.buildRedisSerializer;
|
||||
|
||||
/**
|
||||
* Cache 配置类,基于 Redis 实现
|
||||
|
@ -20,15 +29,19 @@ public class YudaoCacheAutoConfiguration {
|
|||
|
||||
/**
|
||||
* RedisCacheConfiguration Bean
|
||||
*
|
||||
* <p>
|
||||
* 参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 的 createConfiguration 方法
|
||||
*/
|
||||
@Bean
|
||||
@Primary
|
||||
public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
|
||||
// 设置使用 JSON 序列化方式
|
||||
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
|
||||
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(RedisSerializer.json()));
|
||||
// 设置使用 : 单冒号,而不是双 :: 冒号,避免 Redis Desktop Manager 多余空格
|
||||
// 详细可见 https://blog.csdn.net/chuixue24/article/details/103928965 博客
|
||||
config = config.computePrefixWith(cacheName -> cacheName + StrUtil.COLON);
|
||||
// 设置使用 JSON 序列化方式
|
||||
config = config.serializeValuesWith(
|
||||
RedisSerializationContext.SerializationPair.fromSerializer(buildRedisSerializer()));
|
||||
|
||||
// 设置 CacheProperties.Redis 的属性
|
||||
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
|
||||
|
@ -47,4 +60,14 @@ public class YudaoCacheAutoConfiguration {
|
|||
return config;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate,
|
||||
RedisCacheConfiguration redisCacheConfiguration) {
|
||||
// 创建 RedisCacheWriter 对象
|
||||
RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());
|
||||
RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory);
|
||||
// 创建 TenantRedisCacheManager 对象
|
||||
return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
package cn.iocoder.yudao.framework.redis.config;
|
||||
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import org.springframework.boot.autoconfigure.AutoConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
|
@ -25,9 +28,17 @@ public class YudaoRedisAutoConfiguration {
|
|||
template.setKeySerializer(RedisSerializer.string());
|
||||
template.setHashKeySerializer(RedisSerializer.string());
|
||||
// 使用 JSON 序列化方式(库是 Jackson ),序列化 VALUE 。
|
||||
template.setValueSerializer(RedisSerializer.json());
|
||||
template.setHashValueSerializer(RedisSerializer.json());
|
||||
template.setValueSerializer(buildRedisSerializer());
|
||||
template.setHashValueSerializer(buildRedisSerializer());
|
||||
return template;
|
||||
}
|
||||
|
||||
public static RedisSerializer<?> buildRedisSerializer() {
|
||||
RedisSerializer<Object> json = RedisSerializer.json();
|
||||
// 解决 LocalDateTime 的序列化
|
||||
ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, "mapper");
|
||||
objectMapper.registerModules(new JavaTimeModule());
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
package cn.iocoder.yudao.framework.redis.core;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import org.springframework.boot.convert.DurationStyle;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.data.redis.cache.RedisCache;
|
||||
import org.springframework.data.redis.cache.RedisCacheConfiguration;
|
||||
import org.springframework.data.redis.cache.RedisCacheManager;
|
||||
import org.springframework.data.redis.cache.RedisCacheWriter;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
/**
|
||||
* 支持自定义过期时间的 {@link RedisCacheManager} 实现类
|
||||
*
|
||||
* 在 {@link Cacheable#cacheNames()} 格式为 "key#ttl" 时,# 后面的 ttl 为过期时间,单位为秒
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class TimeoutRedisCacheManager extends RedisCacheManager {
|
||||
|
||||
private static final String SPLIT = "#";
|
||||
|
||||
public TimeoutRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {
|
||||
super(cacheWriter, defaultCacheConfiguration);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {
|
||||
if (StrUtil.isEmpty(name)) {
|
||||
return super.createRedisCache(name, cacheConfig);
|
||||
}
|
||||
// 如果使用 # 分隔,大小不为 2,则说明不使用自定义过期时间
|
||||
String[] names = StrUtil.splitToArray(name, SPLIT);
|
||||
if (names.length != 2) {
|
||||
return super.createRedisCache(name, cacheConfig);
|
||||
}
|
||||
|
||||
// 核心:通过修改 cacheConfig 的过期时间,实现自定义过期时间
|
||||
if (cacheConfig != null) {
|
||||
// 移除 # 后面的 : 以及后面的内容,避免影响解析
|
||||
names[1] = StrUtil.subBefore(names[1], StrUtil.COLON, false);
|
||||
// 解析时间
|
||||
Duration duration = DurationStyle.detectAndParse(names[1], ChronoUnit.SECONDS);
|
||||
cacheConfig = cacheConfig.entryTtl(duration);
|
||||
}
|
||||
return super.createRedisCache(names[0], cacheConfig);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,27 +1,20 @@
|
|||
package cn.iocoder.yudao.module.infra.controller.admin.redis;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyRegistry;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisKeyDefineRespVO;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisKeyValueRespVO;
|
||||
import cn.iocoder.yudao.module.infra.controller.admin.redis.vo.RedisMonitorRespVO;
|
||||
import cn.iocoder.yudao.module.infra.convert.redis.RedisConvert;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import org.springframework.data.redis.connection.RedisServerCommands;
|
||||
import org.springframework.data.redis.core.Cursor;
|
||||
import org.springframework.data.redis.core.RedisCallback;
|
||||
import org.springframework.data.redis.core.ScanOptions;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.Properties;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
|
||||
|
@ -47,66 +40,4 @@ public class RedisController {
|
|||
return success(RedisConvert.INSTANCE.build(info, dbSize, commandStats));
|
||||
}
|
||||
|
||||
@GetMapping("/get-key-define-list")
|
||||
@Operation(summary = "获得 Redis Key 模板列表")
|
||||
@PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
|
||||
public CommonResult<List<RedisKeyDefineRespVO>> getKeyDefineList() {
|
||||
List<RedisKeyDefine> keyDefines = RedisKeyRegistry.list();
|
||||
return success(RedisConvert.INSTANCE.convertList(keyDefines));
|
||||
}
|
||||
|
||||
@GetMapping("/get-key-list")
|
||||
@Operation(summary = "获得 Redis keys 键名列表")
|
||||
@Parameter(name = "keyTemplate", description = "Redis Key 定义", example = "true")
|
||||
@PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
|
||||
public CommonResult<Set<String>> getKeyDefineList(@RequestParam("keyTemplate") String keyTemplate) {
|
||||
return success(getKeyDefineList0(keyTemplate));
|
||||
}
|
||||
|
||||
private Set<String> getKeyDefineList0(String keyTemplate) {
|
||||
// key 格式化
|
||||
String key = StrUtil.replace(keyTemplate, "%[s|c|b|d|x|o|f|a|e|g]", parameter -> "*");
|
||||
// scan 扫描 key
|
||||
Set<String> keys = new LinkedHashSet<>();
|
||||
stringRedisTemplate.execute((RedisCallback<Set<String>>) connection -> {
|
||||
try (Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match(key).count(100).build())) {
|
||||
cursor.forEachRemaining(value -> keys.add(StrUtil.utf8Str(value)));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
return keys;
|
||||
});
|
||||
return keys;
|
||||
}
|
||||
|
||||
@GetMapping("/get-key-value")
|
||||
@Operation(summary = "获得 Redis key 内容")
|
||||
@Parameter(name = "key", description = "Redis Key", example = "oauth2_access_token:233")
|
||||
@PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
|
||||
public CommonResult<RedisKeyValueRespVO> getKeyValue(@RequestParam("key") String key) {
|
||||
String value = stringRedisTemplate.opsForValue().get(key);
|
||||
return success(new RedisKeyValueRespVO(key, value));
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-key")
|
||||
@Operation(summary = "删除 Redis Key")
|
||||
@Parameter(name = "key", description = "Redis Key", example = "oauth2_access_token:233")
|
||||
@PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
|
||||
public CommonResult<Boolean> deleteKey(@RequestParam("key") String key) {
|
||||
stringRedisTemplate.delete(key);
|
||||
return success(Boolean.TRUE);
|
||||
}
|
||||
|
||||
@DeleteMapping("/delete-keys")
|
||||
@Operation(summary = "删除 Redis Key 根据模板")
|
||||
@Parameter(name = "keyTemplate", description = "Redis Key 定义", example = "true")
|
||||
@PreAuthorize("@ss.hasPermission('infra:redis:get-key-list')")
|
||||
public CommonResult<Boolean> deleteKeys(@RequestParam("keyTemplate") String keyTemplate) {
|
||||
Set<String> keys = getKeyDefineList0(keyTemplate);
|
||||
if (CollUtil.isNotEmpty(keys)) {
|
||||
stringRedisTemplate.delete(keys);
|
||||
}
|
||||
return success(Boolean.TRUE);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ public class PermissionApiImpl implements PermissionApi {
|
|||
|
||||
@Override
|
||||
public CommonResult<Set<Long>> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
|
||||
return success(permissionService.getUserRoleIdListByRoleIds(roleIds));
|
||||
return success(permissionService.getUserRoleIdListByRoleId(roleIds));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -3,7 +3,6 @@ package cn.iocoder.yudao.module.system.controller.admin.auth;
|
|||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.SetUtils;
|
||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||
import cn.iocoder.yudao.framework.security.config.SecurityProperties;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
|
||||
|
@ -12,8 +11,8 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
|
|||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.service.auth.AdminAuthService;
|
||||
import cn.iocoder.yudao.module.system.service.permission.MenuService;
|
||||
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
|
||||
import cn.iocoder.yudao.module.system.service.permission.RoleService;
|
||||
import cn.iocoder.yudao.module.system.service.social.SocialUserService;
|
||||
|
@ -34,9 +33,9 @@ import java.util.List;
|
|||
import java.util.Set;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
|
||||
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.obtainAuthorization;
|
||||
import static java.util.Collections.singleton;
|
||||
|
||||
@Tag(name = "管理后台 - 认证")
|
||||
@RestController
|
||||
|
@ -52,6 +51,8 @@ public class AuthController {
|
|||
@Resource
|
||||
private RoleService roleService;
|
||||
@Resource
|
||||
private MenuService menuService;
|
||||
@Resource
|
||||
private PermissionService permissionService;
|
||||
@Resource
|
||||
private SocialUserService socialUserService;
|
||||
|
@ -91,33 +92,24 @@ public class AuthController {
|
|||
@GetMapping("/get-permission-info")
|
||||
@Operation(summary = "获取登录用户的权限信息")
|
||||
public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {
|
||||
// 获得用户信息
|
||||
// 1.1 获得用户信息
|
||||
AdminUserDO user = userService.getUser(getLoginUserId());
|
||||
if (user == null) {
|
||||
return null;
|
||||
}
|
||||
// 获得角色列表
|
||||
Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
|
||||
List<RoleDO> roleList = roleService.getRoleListFromCache(roleIds);
|
||||
// 获得菜单列表
|
||||
List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
|
||||
SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType(), MenuTypeEnum.BUTTON.getType()),
|
||||
singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
|
||||
// 拼接结果返回
|
||||
return success(AuthConvert.INSTANCE.convert(user, roleList, menuList));
|
||||
}
|
||||
|
||||
@GetMapping("/list-menus")
|
||||
@Operation(summary = "获得登录用户的菜单列表")
|
||||
public CommonResult<List<AuthMenuRespVO>> getMenuList() {
|
||||
// 获得角色列表
|
||||
Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(getLoginUserId(), singleton(CommonStatusEnum.ENABLE.getStatus()));
|
||||
// 获得用户拥有的菜单列表
|
||||
List<MenuDO> menuList = permissionService.getRoleMenuListFromCache(roleIds,
|
||||
SetUtils.asSet(MenuTypeEnum.DIR.getType(), MenuTypeEnum.MENU.getType()), // 只要目录和菜单类型
|
||||
singleton(CommonStatusEnum.ENABLE.getStatus())); // 只要开启的
|
||||
// 转换成 Tree 结构返回
|
||||
return success(AuthConvert.INSTANCE.buildMenuTree(menuList));
|
||||
// 1.2 获得角色列表
|
||||
Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(getLoginUserId());
|
||||
List<RoleDO> roles = roleService.getRoleList(roleIds);
|
||||
roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色
|
||||
|
||||
// 1.3 获得菜单列表
|
||||
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId));
|
||||
List<MenuDO> menuList = menuService.getMenuList(menuIds);
|
||||
menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单
|
||||
|
||||
// 2. 拼接结果返回
|
||||
return success(AuthConvert.INSTANCE.convert(user, roles, menuList));
|
||||
}
|
||||
|
||||
// ========== 短信登录相关 ==========
|
||||
|
|
|
@ -6,6 +6,7 @@ import lombok.Builder;
|
|||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@Schema(description = "管理后台 - 登录用户的权限信息 Response VO,额外包括用户信息和角色列表")
|
||||
|
@ -24,6 +25,9 @@ public class AuthPermissionInfoRespVO {
|
|||
@Schema(description = "操作权限数组", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private Set<String> permissions;
|
||||
|
||||
@Schema(description = "菜单树", required = true)
|
||||
private List<MenuVO> menus;
|
||||
|
||||
@Schema(description = "用户信息 VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
|
@ -42,4 +46,48 @@ public class AuthPermissionInfoRespVO {
|
|||
|
||||
}
|
||||
|
||||
@Schema(description = "管理后台 - 登录用户的菜单信息 Response VO")
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Builder
|
||||
public static class MenuVO {
|
||||
|
||||
@Schema(description = "菜单名称", required = true, example = "芋道")
|
||||
private Long id;
|
||||
|
||||
@Schema(description = "父菜单 ID", required = true, example = "1024")
|
||||
private Long parentId;
|
||||
|
||||
@Schema(description = "菜单名称", required = true, example = "芋道")
|
||||
private String name;
|
||||
|
||||
@Schema(description = "路由地址,仅菜单类型为菜单或者目录时,才需要传", example = "post")
|
||||
private String path;
|
||||
|
||||
@Schema(description = "组件路径,仅菜单类型为菜单时,才需要传", example = "system/post/index")
|
||||
private String component;
|
||||
|
||||
@Schema(description = "组件名", example = "SystemUser")
|
||||
private String componentName;
|
||||
|
||||
@Schema(description = "菜单图标,仅菜单类型为菜单或者目录时,才需要传", example = "/menu/list")
|
||||
private String icon;
|
||||
|
||||
@Schema(description = "是否可见", required = true, example = "false")
|
||||
private Boolean visible;
|
||||
|
||||
@Schema(description = "是否缓存", required = true, example = "false")
|
||||
private Boolean keepAlive;
|
||||
|
||||
@Schema(description = "是否总是显示", example = "false")
|
||||
private Boolean alwaysShow;
|
||||
|
||||
/**
|
||||
* 子路由
|
||||
*/
|
||||
private List<MenuVO> children;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -37,10 +37,10 @@ public class PermissionController {
|
|||
|
||||
@Operation(summary = "获得角色拥有的菜单编号")
|
||||
@Parameter(name = "roleId", description = "角色编号", required = true)
|
||||
@GetMapping("/list-role-resources")
|
||||
@GetMapping("/list-role-menus")
|
||||
@PreAuthorize("@ss.hasPermission('system:permission:assign-role-menu')")
|
||||
public CommonResult<Set<Long>> listRoleMenus(Long roleId) {
|
||||
return success(permissionService.getRoleMenuIds(roleId));
|
||||
public CommonResult<Set<Long>> getRoleMenuList(Long roleId) {
|
||||
return success(permissionService.getRoleMenuListByRoleId(roleId));
|
||||
}
|
||||
|
||||
@PostMapping("/assign-role-menu")
|
||||
|
|
|
@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
|
|||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.factory.Mappers;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -27,14 +28,16 @@ public interface AuthConvert {
|
|||
|
||||
default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {
|
||||
return AuthPermissionInfoRespVO.builder()
|
||||
.user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname())
|
||||
.avatar(user.getAvatar()).build())
|
||||
.user(AuthPermissionInfoRespVO.UserVO.builder().id(user.getId()).nickname(user.getNickname()).avatar(user.getAvatar()).build())
|
||||
.roles(convertSet(roleList, RoleDO::getCode))
|
||||
// 权限标识信息
|
||||
.permissions(convertSet(menuList, MenuDO::getPermission))
|
||||
// 菜单树
|
||||
.menus(buildMenuTree(menuList))
|
||||
.build();
|
||||
}
|
||||
|
||||
AuthMenuRespVO convertTreeNode(MenuDO menu);
|
||||
AuthPermissionInfoRespVO.MenuVO convertTreeNode(MenuDO menu);
|
||||
|
||||
/**
|
||||
* 将菜单列表,构建成菜单树
|
||||
|
@ -42,20 +45,23 @@ public interface AuthConvert {
|
|||
* @param menuList 菜单列表
|
||||
* @return 菜单树
|
||||
*/
|
||||
default List<AuthMenuRespVO> buildMenuTree(List<MenuDO> menuList) {
|
||||
default List<AuthPermissionInfoRespVO.MenuVO> buildMenuTree(List<MenuDO> menuList) {
|
||||
// 移除按钮
|
||||
menuList.removeIf(menu -> menu.getType().equals(MenuTypeEnum.BUTTON.getType()));
|
||||
// 排序,保证菜单的有序性
|
||||
menuList.sort(Comparator.comparing(MenuDO::getSort));
|
||||
|
||||
// 构建菜单树
|
||||
// 使用 LinkedHashMap 的原因,是为了排序 。实际也可以用 Stream API ,就是太丑了。
|
||||
Map<Long, AuthMenuRespVO> treeNodeMap = new LinkedHashMap<>();
|
||||
Map<Long, AuthPermissionInfoRespVO.MenuVO> treeNodeMap = new LinkedHashMap<>();
|
||||
menuList.forEach(menu -> treeNodeMap.put(menu.getId(), AuthConvert.INSTANCE.convertTreeNode(menu)));
|
||||
// 处理父子关系
|
||||
treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(ID_ROOT)).forEach(childNode -> {
|
||||
// 获得父节点
|
||||
AuthMenuRespVO parentNode = treeNodeMap.get(childNode.getParentId());
|
||||
AuthPermissionInfoRespVO.MenuVO parentNode = treeNodeMap.get(childNode.getParentId());
|
||||
if (parentNode == null) {
|
||||
LoggerFactory.getLogger(getClass()).error("[buildRouterTree][resource({}) 找不到父资源({})]",
|
||||
childNode.getId(), childNode.getParentId());
|
||||
childNode.getId(), childNode.getParentId());
|
||||
return;
|
||||
}
|
||||
// 将自己添加到父节点中
|
||||
|
|
|
@ -6,6 +6,7 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqV
|
|||
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
|
@ -25,4 +26,8 @@ public interface DeptMapper extends BaseMapperX<DeptDO> {
|
|||
return selectCount(DeptDO::getParentId, parentId);
|
||||
}
|
||||
|
||||
default List<DeptDO> selectListByParentId(Collection<Long> parentIds) {
|
||||
return selectList(DeptDO::getParentId, parentIds);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,4 +25,7 @@ public interface MenuMapper extends BaseMapperX<MenuDO> {
|
|||
.eqIfPresent(MenuDO::getStatus, reqVO.getStatus()));
|
||||
}
|
||||
|
||||
default List<MenuDO> selectListByPermission(String permission) {
|
||||
return selectList(MenuDO::getPermission, permission);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,9 +3,7 @@ package cn.iocoder.yudao.module.system.dal.mysql.permission;
|
|||
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
@ -13,14 +11,18 @@ import java.util.List;
|
|||
@Mapper
|
||||
public interface RoleMenuMapper extends BaseMapperX<RoleMenuDO> {
|
||||
|
||||
@Repository
|
||||
class BatchInsertMapper extends ServiceImpl<RoleMenuMapper, RoleMenuDO> {
|
||||
}
|
||||
|
||||
default List<RoleMenuDO> selectListByRoleId(Long roleId) {
|
||||
return selectList(RoleMenuDO::getRoleId, roleId);
|
||||
}
|
||||
|
||||
default List<RoleMenuDO> selectListByRoleId(Collection<Long> roleIds) {
|
||||
return selectList(RoleMenuDO::getRoleId, roleIds);
|
||||
}
|
||||
|
||||
default List<RoleMenuDO> selectListByMenuId(Long menuId) {
|
||||
return selectList(RoleMenuDO::getMenuId, menuId);
|
||||
}
|
||||
|
||||
default void deleteListByRoleIdAndMenuIds(Long roleId, Collection<Long> menuIds) {
|
||||
delete(new LambdaQueryWrapper<RoleMenuDO>()
|
||||
.eq(RoleMenuDO::getRoleId, roleId)
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
package cn.iocoder.yudao.module.system.dal.redis;
|
||||
|
||||
import cn.iocoder.yudao.framework.redis.core.RedisKeyDefine;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;
|
||||
|
||||
/**
|
||||
* System Redis Key 枚举类
|
||||
*
|
||||
|
@ -14,16 +9,93 @@ import static cn.iocoder.yudao.framework.redis.core.RedisKeyDefine.KeyTypeEnum.S
|
|||
*/
|
||||
public interface RedisKeyConstants {
|
||||
|
||||
RedisKeyDefine CAPTCHA_CODE = new RedisKeyDefine("验证码的缓存",
|
||||
"captcha_code:%s", // 参数为 uuid
|
||||
STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
|
||||
/**
|
||||
* 指定部门的所有子部门编号数组的缓存
|
||||
* <p>
|
||||
* KEY 格式:dept_children_ids:{id}
|
||||
* VALUE 数据类型:String 子部门编号集合
|
||||
*/
|
||||
String DEPT_CHILDREN_ID_LIST = "dept_children_ids";
|
||||
|
||||
RedisKeyDefine OAUTH2_ACCESS_TOKEN = new RedisKeyDefine("访问令牌的缓存",
|
||||
"oauth2_access_token:%s", // 参数为访问令牌 token
|
||||
STRING, OAuth2AccessTokenDO.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);
|
||||
/**
|
||||
* 角色的缓存
|
||||
* <p>
|
||||
* KEY 格式:role:{id}
|
||||
* VALUE 数据类型:String 角色信息
|
||||
*/
|
||||
String ROLE = "role";
|
||||
|
||||
RedisKeyDefine SOCIAL_AUTH_STATE = new RedisKeyDefine("社交登陆的 state", // 注意,它是被 JustAuth 的 justauth.type.prefix 使用到
|
||||
"social_auth_state:%s", // 参数为 state
|
||||
STRING, String.class, Duration.ofHours(24)); // 值为 state
|
||||
/**
|
||||
* 用户拥有的角色编号的缓存
|
||||
* <p>
|
||||
* KEY 格式:user_role_ids:{userId}
|
||||
* VALUE 数据类型:String 角色编号集合
|
||||
*/
|
||||
String USER_ROLE_ID_LIST = "user_role_ids";
|
||||
|
||||
/**
|
||||
* 拥有指定菜单的角色编号的缓存
|
||||
* <p>
|
||||
* KEY 格式:user_role_ids:{menuId}
|
||||
* VALUE 数据类型:String 角色编号集合
|
||||
*/
|
||||
String MENU_ROLE_ID_LIST = "menu_role_ids";
|
||||
|
||||
/**
|
||||
* 拥有权限对应的菜单编号数组的缓存
|
||||
* <p>
|
||||
* KEY 格式:permission_menu_ids:{permission}
|
||||
* VALUE 数据类型:String 菜单编号数组
|
||||
*/
|
||||
String PERMISSION_MENU_ID_LIST = "permission_menu_ids";
|
||||
|
||||
/**
|
||||
* OAuth2 客户端的缓存
|
||||
* <p>
|
||||
* KEY 格式:user:{id}
|
||||
* VALUE 数据类型:String 客户端信息
|
||||
*/
|
||||
String OAUTH_CLIENT = "oauth_client";
|
||||
|
||||
/**
|
||||
* 访问令牌的缓存
|
||||
* <p>
|
||||
* KEY 格式:oauth2_access_token:{token}
|
||||
* VALUE 数据类型:String 访问令牌信息 {@link OAuth2AccessTokenDO}
|
||||
* <p>
|
||||
* 由于动态过期时间,使用 RedisTemplate 操作
|
||||
*/
|
||||
String OAUTH2_ACCESS_TOKEN = "oauth2_access_token:%s";
|
||||
|
||||
/**
|
||||
* 站内信模版的缓存
|
||||
* <p>
|
||||
* KEY 格式:notify_template:{code}
|
||||
* VALUE 数据格式:String 模版信息
|
||||
*/
|
||||
String NOTIFY_TEMPLATE = "notify_template";
|
||||
|
||||
/**
|
||||
* 邮件账号的缓存
|
||||
* <p>
|
||||
* KEY 格式:sms_template:{id}
|
||||
* VALUE 数据格式:String 账号信息
|
||||
*/
|
||||
String MAIL_ACCOUNT = "mail_account";
|
||||
|
||||
/**
|
||||
* 邮件模版的缓存
|
||||
* <p>
|
||||
* KEY 格式:mail_template:{code}
|
||||
* VALUE 数据格式:String 模版信息
|
||||
*/
|
||||
String MAIL_TEMPLATE = "mail_template";
|
||||
|
||||
/**
|
||||
* 短信模版的缓存
|
||||
* <p>
|
||||
* KEY 格式:sms_template:{id}
|
||||
* VALUE 数据格式:String 模版信息
|
||||
*/
|
||||
String SMS_TEMPLATE = "sms_template";
|
||||
}
|
||||
|
|
|
@ -53,7 +53,7 @@ public class OAuth2AccessTokenRedisDAO {
|
|||
}
|
||||
|
||||
private static String formatKey(String accessToken) {
|
||||
return String.format(OAUTH2_ACCESS_TOKEN.getKeyTemplate(), accessToken);
|
||||
return String.format(OAUTH2_ACCESS_TOKEN, accessToken);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.consumer.auth;
|
||||
|
||||
import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.service.oauth2.OAuth2ClientService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link OAuth2ClientRefreshMessage} 的消费者
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class OAuth2ClientRefreshConsumer {
|
||||
|
||||
@Resource
|
||||
private OAuth2ClientService oauth2ClientService;
|
||||
|
||||
@EventListener
|
||||
public void execute(OAuth2ClientRefreshMessage message) {
|
||||
log.info("[execute][收到 OAuth2Client 刷新消息]");
|
||||
oauth2ClientService.initLocalCache();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.consumer.dept;
|
||||
|
||||
import cn.iocoder.yudao.module.system.mq.message.dept.DeptRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.service.dept.DeptService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link DeptRefreshMessage} 的消费者
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class DeptRefreshConsumer {
|
||||
|
||||
@Resource
|
||||
private DeptService deptService;
|
||||
|
||||
@EventListener
|
||||
public void execute(DeptRefreshMessage message) {
|
||||
log.info("[execute][收到 Dept 刷新消息]");
|
||||
deptService.initLocalCache();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.consumer.mail;
|
||||
|
||||
import cn.iocoder.yudao.module.system.mq.message.mail.MailAccountRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.service.mail.MailAccountService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link MailAccountRefreshMessage} 的消费者
|
||||
*
|
||||
* @author wangjingyi
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MailAccountRefreshConsumer {
|
||||
|
||||
@Resource
|
||||
private MailAccountService mailAccountService;
|
||||
|
||||
@EventListener
|
||||
public void onMessage(MailAccountRefreshMessage message) {
|
||||
log.info("[onMessage][收到 Mail Account 刷新信息]");
|
||||
mailAccountService.initLocalCache();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.consumer.mail;
|
||||
|
||||
import cn.iocoder.yudao.module.system.mq.message.mail.MailTemplateRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.service.mail.MailTemplateService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link MailTemplateRefreshMessage} 的消费者
|
||||
*
|
||||
* @author wangjingyi
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MailTemplateRefreshConsumer {
|
||||
|
||||
@Resource
|
||||
private MailTemplateService mailTemplateService;
|
||||
|
||||
@EventListener
|
||||
public void onMessage(MailTemplateRefreshMessage message) {
|
||||
log.info("[onMessage][收到 Mail Template 刷新信息]");
|
||||
mailTemplateService.initLocalCache();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.consumer.notify;
|
||||
|
||||
import cn.iocoder.yudao.module.system.mq.message.notify.NotifyTemplateRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.service.notify.NotifyTemplateService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link NotifyTemplateRefreshMessage} 的消费者
|
||||
*
|
||||
* @author xrcoder
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class NotifyTemplateRefreshConsumer {
|
||||
|
||||
@Resource
|
||||
private NotifyTemplateService notifyTemplateService;
|
||||
|
||||
@EventListener
|
||||
public void onMessage(NotifyTemplateRefreshMessage message) {
|
||||
log.info("[onMessage][收到 NotifyTemplate 刷新消息]");
|
||||
notifyTemplateService.initLocalCache();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.consumer.permission;
|
||||
|
||||
import cn.iocoder.yudao.module.system.mq.message.permission.MenuRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.service.permission.MenuService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link MenuRefreshMessage} 的消费者
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class MenuRefreshConsumer {
|
||||
|
||||
@Resource
|
||||
private MenuService menuService;
|
||||
|
||||
@EventListener
|
||||
public void execute(MenuRefreshMessage menuRefreshMessage) {
|
||||
log.info("[execute][收到 Menu 刷新消息]");
|
||||
menuService.initLocalCache();
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.consumer.permission;
|
||||
|
||||
import cn.iocoder.yudao.module.system.mq.message.permission.RoleMenuRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link RoleMenuRefreshMessage} 的消费者
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RoleMenuRefreshConsumer {
|
||||
|
||||
@Resource
|
||||
private PermissionService permissionService;
|
||||
|
||||
@EventListener
|
||||
public void execute(RoleMenuRefreshMessage roleMenuRefreshMessage) {
|
||||
log.info("[execute][收到 Role 与 Menu 的关联刷新消息]");
|
||||
permissionService.initLocalCache();
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.consumer.permission;
|
||||
|
||||
import cn.iocoder.yudao.module.system.mq.message.permission.RoleRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.service.permission.RoleService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link RoleRefreshMessage} 的消费者
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class RoleRefreshConsumer {
|
||||
|
||||
@Resource
|
||||
private RoleService roleService;
|
||||
|
||||
@EventListener
|
||||
public void execute(RoleRefreshMessage message) {
|
||||
log.info("[execute][收到 Role 刷新消息]");
|
||||
roleService.initLocalCache();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.consumer.permission;
|
||||
|
||||
import cn.iocoder.yudao.module.system.mq.message.permission.UserRoleRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.service.permission.PermissionService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link UserRoleRefreshMessage} 的消费者
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class UserRoleRefreshConsumer {
|
||||
|
||||
@Resource
|
||||
private PermissionService permissionService;
|
||||
|
||||
@EventListener
|
||||
public void execute(UserRoleRefreshMessage userRoleRefreshMessage) {
|
||||
log.info("[execute][收到 User 与 Role 的关联刷新消息]");
|
||||
permissionService.initLocalCache();
|
||||
}
|
||||
}
|
|
@ -1,29 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.consumer.sms;
|
||||
|
||||
import cn.iocoder.yudao.module.system.mq.message.sms.SmsTemplateRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.service.sms.SmsTemplateService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
||||
/**
|
||||
* 针对 {@link SmsTemplateRefreshMessage} 的消费者
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SmsTemplateRefreshConsumer {
|
||||
|
||||
@Resource
|
||||
private SmsTemplateService smsTemplateService;
|
||||
|
||||
@EventListener
|
||||
public void execute(SmsTemplateRefreshMessage message) {
|
||||
log.info("[execute][收到 SmsTemplate 刷新消息]");
|
||||
smsTemplateService.initLocalCache();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.message.auth;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
|
||||
/**
|
||||
* OAuth 2.0 客户端的数据刷新 Message
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class OAuth2ClientRefreshMessage extends RemoteApplicationEvent {
|
||||
|
||||
public OAuth2ClientRefreshMessage() {
|
||||
}
|
||||
|
||||
public OAuth2ClientRefreshMessage(Object source, String originService, String destinationService) {
|
||||
super(source, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.message.dept;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
|
||||
/**
|
||||
* 部门数据刷新 Message
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class DeptRefreshMessage extends RemoteApplicationEvent {
|
||||
|
||||
public DeptRefreshMessage() {
|
||||
}
|
||||
|
||||
public DeptRefreshMessage(Object source, String originService, String destinationService) {
|
||||
super(source, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.message.mail;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
|
||||
/**
|
||||
* 邮箱账号的数据刷新 Message
|
||||
*
|
||||
* @author wangjingyi
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MailAccountRefreshMessage extends RemoteApplicationEvent {
|
||||
|
||||
public MailAccountRefreshMessage() {
|
||||
}
|
||||
|
||||
public MailAccountRefreshMessage(Object source, String originService, String destinationService) {
|
||||
super(source, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.message.mail;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
|
||||
/**
|
||||
* 邮箱模板的数据刷新 Message
|
||||
*
|
||||
* @author wangjingyi
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class MailTemplateRefreshMessage extends RemoteApplicationEvent {
|
||||
|
||||
public MailTemplateRefreshMessage() {
|
||||
}
|
||||
|
||||
public MailTemplateRefreshMessage(Object source, String originService, String destinationService) {
|
||||
super(source, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,23 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.message.notify;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
|
||||
/**
|
||||
* 站内信模板的数据刷新 Message
|
||||
*
|
||||
* @author xrcoder
|
||||
*/
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public class NotifyTemplateRefreshMessage extends RemoteApplicationEvent {
|
||||
|
||||
public NotifyTemplateRefreshMessage() {
|
||||
}
|
||||
|
||||
public NotifyTemplateRefreshMessage(Object source, String originService, String destinationService) {
|
||||
super(source, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.message.permission;
|
||||
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
|
||||
/**
|
||||
* 菜单数据刷新 Message
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
public class MenuRefreshMessage extends RemoteApplicationEvent {
|
||||
|
||||
public MenuRefreshMessage() {
|
||||
}
|
||||
|
||||
public MenuRefreshMessage(Object source, String originService, String destinationService) {
|
||||
super(source, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.message.permission;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
|
||||
/**
|
||||
* 角色与菜单数据刷新 Message
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class RoleMenuRefreshMessage extends RemoteApplicationEvent {
|
||||
|
||||
public RoleMenuRefreshMessage() {
|
||||
}
|
||||
|
||||
public RoleMenuRefreshMessage(Object source, String originService, String destinationService) {
|
||||
super(source, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.message.permission;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
|
||||
/**
|
||||
* 角色数据刷新 Message
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class RoleRefreshMessage extends RemoteApplicationEvent {
|
||||
|
||||
public RoleRefreshMessage() {
|
||||
}
|
||||
|
||||
public RoleRefreshMessage(Object source, String originService, String destinationService) {
|
||||
super(source, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.message.permission;
|
||||
|
||||
import lombok.Data;
|
||||
import org.springframework.cloud.bus.event.RemoteApplicationEvent;
|
||||
|
||||
/**
|
||||
* 用户与角色的数据刷新 Message
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Data
|
||||
public class UserRoleRefreshMessage extends RemoteApplicationEvent {
|
||||
|
||||
public UserRoleRefreshMessage() {
|
||||
}
|
||||
|
||||
public UserRoleRefreshMessage(Object source, String originService, String destinationService) {
|
||||
super(source, originService, DEFAULT_DESTINATION_FACTORY.getDestination(destinationService));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.producer.auth;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.core.bus.AbstractBusProducer;
|
||||
import cn.iocoder.yudao.module.system.mq.message.auth.OAuth2ClientRefreshMessage;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* OAuth 2.0 客户端相关消息的 Producer
|
||||
*/
|
||||
@Component
|
||||
public class OAuth2ClientProducer extends AbstractBusProducer {
|
||||
|
||||
/**
|
||||
* 发送 {@link OAuth2ClientRefreshMessage} 消息
|
||||
*/
|
||||
public void sendOAuth2ClientRefreshMessage() {
|
||||
publishEvent(new OAuth2ClientRefreshMessage(this, getBusId(), selfDestinationService()));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.producer.dept;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.core.bus.AbstractBusProducer;
|
||||
import cn.iocoder.yudao.module.system.mq.message.dept.DeptRefreshMessage;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Dept 部门相关消息的 Producer
|
||||
*/
|
||||
@Component
|
||||
public class DeptProducer extends AbstractBusProducer {
|
||||
|
||||
/**
|
||||
* 发送 {@link DeptRefreshMessage} 消息
|
||||
*/
|
||||
public void sendDeptRefreshMessage() {
|
||||
publishEvent(new DeptRefreshMessage(this, getBusId(), selfDestinationService()));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,9 +1,7 @@
|
|||
package cn.iocoder.yudao.module.system.mq.producer.mail;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.core.bus.AbstractBusProducer;
|
||||
import cn.iocoder.yudao.module.system.mq.message.mail.MailAccountRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.mq.message.mail.MailSendMessage;
|
||||
import cn.iocoder.yudao.module.system.mq.message.mail.MailTemplateRefreshMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.stream.function.StreamBridge;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -23,20 +21,6 @@ public class MailProducer extends AbstractBusProducer {
|
|||
@Resource
|
||||
private StreamBridge streamBridge;
|
||||
|
||||
/**
|
||||
* 发送 {@link MailTemplateRefreshMessage} 消息
|
||||
*/
|
||||
public void sendMailTemplateRefreshMessage() {
|
||||
publishEvent(new MailTemplateRefreshMessage(this, getBusId(), selfDestinationService()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送 {@link MailAccountRefreshMessage} 消息
|
||||
*/
|
||||
public void sendMailAccountRefreshMessage() {
|
||||
publishEvent(new MailAccountRefreshMessage(this, getBusId(), selfDestinationService()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送 {@link MailSendMessage} 消息
|
||||
*
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.producer.notify;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.core.bus.AbstractBusProducer;
|
||||
import cn.iocoder.yudao.module.system.mq.message.notify.NotifyTemplateRefreshMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Notify 站内信相关消息的 Producer
|
||||
*
|
||||
* @author xrcoder
|
||||
* @since 2022-08-06
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class NotifyProducer extends AbstractBusProducer {
|
||||
|
||||
/**
|
||||
* 发送 {@link NotifyTemplateRefreshMessage} 消息
|
||||
*/
|
||||
public void sendNotifyTemplateRefreshMessage() {
|
||||
publishEvent(new NotifyTemplateRefreshMessage(this, getBusId(), selfDestinationService()));
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.producer.permission;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.core.bus.AbstractBusProducer;
|
||||
import cn.iocoder.yudao.module.system.mq.message.permission.MenuRefreshMessage;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Menu 菜单相关消息的 Producer
|
||||
*/
|
||||
@Component
|
||||
public class MenuProducer extends AbstractBusProducer {
|
||||
|
||||
/**
|
||||
* 发送 {@link MenuRefreshMessage} 消息
|
||||
*/
|
||||
public void sendMenuRefreshMessage() {
|
||||
publishEvent(new MenuRefreshMessage(this, getBusId(), selfDestinationService()));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.producer.permission;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.core.bus.AbstractBusProducer;
|
||||
import cn.iocoder.yudao.module.system.mq.message.permission.RoleMenuRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.mq.message.permission.UserRoleRefreshMessage;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Permission 权限相关消息的 Producer
|
||||
*/
|
||||
@Component
|
||||
public class PermissionProducer extends AbstractBusProducer {
|
||||
|
||||
/**
|
||||
* 发送 {@link RoleMenuRefreshMessage} 消息
|
||||
*/
|
||||
public void sendRoleMenuRefreshMessage() {
|
||||
publishEvent(new RoleMenuRefreshMessage(this, getBusId(), selfDestinationService()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送 {@link UserRoleRefreshMessage} 消息
|
||||
*/
|
||||
public void sendUserRoleRefreshMessage() {
|
||||
publishEvent(new UserRoleRefreshMessage(this, getBusId(), selfDestinationService()));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package cn.iocoder.yudao.module.system.mq.producer.permission;
|
||||
|
||||
import cn.iocoder.yudao.framework.mq.core.bus.AbstractBusProducer;
|
||||
import cn.iocoder.yudao.module.system.mq.message.permission.RoleRefreshMessage;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* Role 角色相关消息的 Producer
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Component
|
||||
public class RoleProducer extends AbstractBusProducer {
|
||||
|
||||
/**
|
||||
* 发送 {@link RoleRefreshMessage} 消息
|
||||
*/
|
||||
public void sendRoleRefreshMessage() {
|
||||
publishEvent(new RoleRefreshMessage(this, getBusId(), selfDestinationService()));
|
||||
}
|
||||
|
||||
}
|
|
@ -4,7 +4,6 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
|
|||
import cn.iocoder.yudao.framework.mq.core.bus.AbstractBusProducer;
|
||||
import cn.iocoder.yudao.module.system.mq.message.sms.SmsChannelRefreshMessage;
|
||||
import cn.iocoder.yudao.module.system.mq.message.sms.SmsSendMessage;
|
||||
import cn.iocoder.yudao.module.system.mq.message.sms.SmsTemplateRefreshMessage;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.stream.function.StreamBridge;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
@ -32,13 +31,6 @@ public class SmsProducer extends AbstractBusProducer {
|
|||
publishEvent(new SmsChannelRefreshMessage(this, getBusId(), selfDestinationService()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送 {@link SmsTemplateRefreshMessage} 消息
|
||||
*/
|
||||
public void sendSmsTemplateRefreshMessage() {
|
||||
publishEvent(new SmsTemplateRefreshMessage(this, getBusId(), selfDestinationService()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送 {@link SmsSendMessage} 消息
|
||||
*
|
||||
|
|
|
@ -7,10 +7,7 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqV
|
|||
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 部门 Service 接口
|
||||
|
@ -19,11 +16,6 @@ import java.util.Map;
|
|||
*/
|
||||
public interface DeptService {
|
||||
|
||||
/**
|
||||
* 初始化部门的本地缓存
|
||||
*/
|
||||
void initLocalCache();
|
||||
|
||||
/**
|
||||
* 创建部门
|
||||
*
|
||||
|
@ -47,21 +39,12 @@ public interface DeptService {
|
|||
void deleteDept(Long id);
|
||||
|
||||
/**
|
||||
* 筛选部门列表
|
||||
* 获得部门信息
|
||||
*
|
||||
* @param reqVO 筛选条件请求 VO
|
||||
* @return 部门列表
|
||||
* @param id 部门编号
|
||||
* @return 部门信息
|
||||
*/
|
||||
List<DeptDO> getDeptList(DeptListReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 获得所有子部门,从缓存中
|
||||
*
|
||||
* @param parentId 部门编号
|
||||
* @param recursive 是否递归获取所有
|
||||
* @return 子部门列表
|
||||
*/
|
||||
List<DeptDO> getDeptListByParentIdFromCache(Long parentId, boolean recursive);
|
||||
DeptDO getDept(Long id);
|
||||
|
||||
/**
|
||||
* 获得部门信息数组
|
||||
|
@ -71,6 +54,14 @@ public interface DeptService {
|
|||
*/
|
||||
List<DeptDO> getDeptList(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 筛选部门列表
|
||||
*
|
||||
* @param reqVO 筛选条件请求 VO
|
||||
* @return 部门列表
|
||||
*/
|
||||
List<DeptDO> getDeptList(DeptListReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 获得指定编号的部门 Map
|
||||
*
|
||||
|
@ -86,12 +77,20 @@ public interface DeptService {
|
|||
}
|
||||
|
||||
/**
|
||||
* 获得部门信息
|
||||
* 获得指定部门的所有子部门
|
||||
*
|
||||
* @param id 部门编号
|
||||
* @return 部门信息
|
||||
* @return 子部门列表
|
||||
*/
|
||||
DeptDO getDept(Long id);
|
||||
List<DeptDO> getChildDeptList(Long id);
|
||||
|
||||
/**
|
||||
* 获得所有子部门,从缓存中
|
||||
*
|
||||
* @param id 父部门编号
|
||||
* @return 子部门列表
|
||||
*/
|
||||
Set<Long> getChildDeptIdListFromCache(Long id);
|
||||
|
||||
/**
|
||||
* 校验部门们是否有效。如下情况,视为无效:
|
||||
|
|
|
@ -1,30 +1,29 @@
|
|||
package cn.iocoder.yudao.module.system.service.dept;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
|
||||
import cn.iocoder.yudao.framework.datapermission.core.annotation.DataPermission;
|
||||
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;
|
||||
import cn.iocoder.yudao.module.system.convert.dept.DeptConvert;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
|
||||
import cn.iocoder.yudao.module.system.enums.dept.DeptIdEnum;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.dept.DeptProducer;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import lombok.Getter;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
|
@ -37,55 +36,12 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
|||
@Slf4j
|
||||
public class DeptServiceImpl implements DeptService {
|
||||
|
||||
/**
|
||||
* 部门缓存
|
||||
* key:部门编号 {@link DeptDO#getId()}
|
||||
*
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
@Getter
|
||||
private volatile Map<Long, DeptDO> deptCache;
|
||||
/**
|
||||
* 父部门缓存
|
||||
* key:部门编号 {@link DeptDO#getParentId()}
|
||||
* value: 直接子部门列表
|
||||
*
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
@Getter
|
||||
private volatile Multimap<Long, DeptDO> parentDeptCache;
|
||||
|
||||
@Resource
|
||||
private DeptMapper deptMapper;
|
||||
|
||||
@Resource
|
||||
private DeptProducer deptProducer;
|
||||
|
||||
/**
|
||||
* 初始化 {@link #parentDeptCache} 和 {@link #deptCache} 缓存
|
||||
*/
|
||||
@Override
|
||||
@PostConstruct
|
||||
public synchronized void initLocalCache() {
|
||||
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||
TenantUtils.executeIgnore(() -> {
|
||||
// 第一步:查询数据
|
||||
List<DeptDO> depts = deptMapper.selectList();
|
||||
log.info("[initLocalCache][缓存部门,数量为:{}]", depts.size());
|
||||
|
||||
// 第二步:构建缓存
|
||||
ImmutableMap.Builder<Long, DeptDO> builder = ImmutableMap.builder();
|
||||
ImmutableMultimap.Builder<Long, DeptDO> parentBuilder = ImmutableMultimap.builder();
|
||||
depts.forEach(deptDO -> {
|
||||
builder.put(deptDO.getId(), deptDO);
|
||||
parentBuilder.put(deptDO.getParentId(), deptDO);
|
||||
});
|
||||
deptCache = builder.build();
|
||||
parentDeptCache = parentBuilder.build();
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
|
||||
public Long createDept(DeptCreateReqVO reqVO) {
|
||||
// 校验正确性
|
||||
if (reqVO.getParentId() == null) {
|
||||
|
@ -95,12 +51,12 @@ public class DeptServiceImpl implements DeptService {
|
|||
// 插入部门
|
||||
DeptDO dept = DeptConvert.INSTANCE.convert(reqVO);
|
||||
deptMapper.insert(dept);
|
||||
// 发送刷新消息
|
||||
deptProducer.sendDeptRefreshMessage();
|
||||
return dept.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
|
||||
public void updateDept(DeptUpdateReqVO reqVO) {
|
||||
// 校验正确性
|
||||
if (reqVO.getParentId() == null) {
|
||||
|
@ -110,11 +66,11 @@ public class DeptServiceImpl implements DeptService {
|
|||
// 更新部门
|
||||
DeptDO updateObj = DeptConvert.INSTANCE.convert(reqVO);
|
||||
deptMapper.updateById(updateObj);
|
||||
// 发送刷新消息
|
||||
deptProducer.sendDeptRefreshMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为操作一个部门,涉及到多个缓存
|
||||
public void deleteDept(Long id) {
|
||||
// 校验是否存在
|
||||
validateDeptExists(id);
|
||||
|
@ -124,70 +80,30 @@ public class DeptServiceImpl implements DeptService {
|
|||
}
|
||||
// 删除部门
|
||||
deptMapper.deleteById(id);
|
||||
// 发送刷新消息
|
||||
deptProducer.sendDeptRefreshMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeptDO> getDeptList(DeptListReqVO reqVO) {
|
||||
return deptMapper.selectList(reqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeptDO> getDeptListByParentIdFromCache(Long parentId, boolean recursive) {
|
||||
if (parentId == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<DeptDO> result = new ArrayList<>();
|
||||
// 递归,简单粗暴
|
||||
getDeptsByParentIdFromCache(result, parentId,
|
||||
recursive ? Integer.MAX_VALUE : 1, // 如果递归获取,则无限;否则,只递归 1 次
|
||||
parentDeptCache);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 递归获取所有的子部门,添加到 result 结果
|
||||
*
|
||||
* @param result 结果
|
||||
* @param parentId 父编号
|
||||
* @param recursiveCount 递归次数
|
||||
* @param parentDeptMap 父部门 Map,使用缓存,避免变化
|
||||
*/
|
||||
private void getDeptsByParentIdFromCache(List<DeptDO> result, Long parentId, int recursiveCount,
|
||||
Multimap<Long, DeptDO> parentDeptMap) {
|
||||
// 递归次数为 0,结束!
|
||||
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));
|
||||
}
|
||||
|
||||
private void validateForCreateOrUpdate(Long id, Long parentId, String name) {
|
||||
// 校验自己存在
|
||||
validateDeptExists(id);
|
||||
// 校验父部门的有效性
|
||||
validateParentDeptEnable(id, parentId);
|
||||
validateParentDept(id, parentId);
|
||||
// 校验部门名的唯一性
|
||||
validateDeptNameUnique(id, parentId, name);
|
||||
}
|
||||
|
||||
private void validateParentDeptEnable(Long id, Long parentId) {
|
||||
@VisibleForTesting
|
||||
void validateDeptExists(Long id) {
|
||||
if (id == null) {
|
||||
return;
|
||||
}
|
||||
DeptDO dept = deptMapper.selectById(id);
|
||||
if (dept == null) {
|
||||
throw exception(DEPT_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
void validateParentDept(Long id, Long parentId) {
|
||||
if (parentId == null || DeptIdEnum.ROOT.getId().equals(parentId)) {
|
||||
return;
|
||||
}
|
||||
|
@ -200,49 +116,71 @@ public class DeptServiceImpl implements DeptService {
|
|||
if (dept == null) {
|
||||
throw exception(DEPT_PARENT_NOT_EXITS);
|
||||
}
|
||||
// 父部门被禁用
|
||||
if (!CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) {
|
||||
throw exception(DEPT_NOT_ENABLE);
|
||||
}
|
||||
// 父部门不能是原来的子部门
|
||||
List<DeptDO> children = getDeptListByParentIdFromCache(id, true);
|
||||
List<DeptDO> children = getChildDeptList(id);
|
||||
if (children.stream().anyMatch(dept1 -> dept1.getId().equals(parentId))) {
|
||||
throw exception(DEPT_PARENT_IS_CHILD);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDeptExists(Long id) {
|
||||
if (id == null) {
|
||||
return;
|
||||
}
|
||||
DeptDO dept = deptMapper.selectById(id);
|
||||
@VisibleForTesting
|
||||
void validateDeptNameUnique(Long id, Long parentId, String name) {
|
||||
DeptDO dept = deptMapper.selectByParentIdAndName(parentId, name);
|
||||
if (dept == null) {
|
||||
throw exception(DEPT_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
private void validateDeptNameUnique(Long id, Long parentId, String name) {
|
||||
DeptDO menu = deptMapper.selectByParentIdAndName(parentId, name);
|
||||
if (menu == null) {
|
||||
return;
|
||||
}
|
||||
// 如果 id 为空,说明不用比较是否为相同 id 的岗位
|
||||
if (id == null) {
|
||||
throw exception(DEPT_NAME_DUPLICATE);
|
||||
}
|
||||
if (!menu.getId().equals(id)) {
|
||||
if (ObjectUtil.notEqual(dept.getId(), id)) {
|
||||
throw exception(DEPT_NAME_DUPLICATE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeptDO getDept(Long id) {
|
||||
return deptMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeptDO> getDeptList(Collection<Long> ids) {
|
||||
if (CollUtil.isEmpty(ids)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return deptMapper.selectBatchIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public DeptDO getDept(Long id) {
|
||||
return deptMapper.selectById(id);
|
||||
public List<DeptDO> getDeptList(DeptListReqVO reqVO) {
|
||||
return deptMapper.selectList(reqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<DeptDO> getChildDeptList(Long id) {
|
||||
List<DeptDO> children = new LinkedList<>();
|
||||
// 遍历每一层
|
||||
Collection<Long> parentIds = Collections.singleton(id);
|
||||
for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下,存在死循环
|
||||
// 查询当前层,所有的子部门
|
||||
List<DeptDO> depts = deptMapper.selectListByParentId(parentIds);
|
||||
// 1. 如果没有子部门,则结束遍历
|
||||
if (CollUtil.isEmpty(depts)) {
|
||||
break;
|
||||
}
|
||||
// 2. 如果有子部门,继续遍历
|
||||
children.addAll(depts);
|
||||
parentIds = convertSet(depts, DeptDO::getId);
|
||||
}
|
||||
return children;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataPermission(enable = false) // 禁用数据权限,避免简历不正确的缓存
|
||||
@Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, key = "#id")
|
||||
public Set<Long> getChildDeptIdListFromCache(Long id) {
|
||||
List<DeptDO> children = getChildDeptList(id);
|
||||
return convertSet(children, DeptDO::getId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -17,19 +17,6 @@ import java.util.List;
|
|||
*/
|
||||
public interface MailAccountService {
|
||||
|
||||
/**
|
||||
* 初始化邮箱账号的本地缓存
|
||||
*/
|
||||
void initLocalCache();
|
||||
|
||||
/**
|
||||
* 从缓存中获取邮箱账号
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 邮箱账号
|
||||
*/
|
||||
MailAccountDO getMailAccountFromCache(Long id);
|
||||
|
||||
/**
|
||||
* 创建邮箱账号
|
||||
*
|
||||
|
@ -60,6 +47,14 @@ public interface MailAccountService {
|
|||
*/
|
||||
MailAccountDO getMailAccount(Long id);
|
||||
|
||||
/**
|
||||
* 从缓存中获取邮箱账号
|
||||
*
|
||||
* @param id 编号
|
||||
* @return 邮箱账号
|
||||
*/
|
||||
MailAccountDO getMailAccountFromCache(Long id);
|
||||
|
||||
/**
|
||||
* 获取邮箱账号分页信息
|
||||
*
|
||||
|
|
|
@ -7,20 +7,18 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccou
|
|||
import cn.iocoder.yudao.module.system.convert.mail.MailAccountConvert;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
|
||||
import lombok.Getter;
|
||||
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
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;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS;
|
||||
|
||||
/**
|
||||
* 邮箱账号 Service 实现类
|
||||
|
@ -39,46 +37,16 @@ public class MailAccountServiceImpl implements MailAccountService {
|
|||
@Resource
|
||||
private MailTemplateService mailTemplateService;
|
||||
|
||||
@Resource
|
||||
private MailProducer mailProducer;
|
||||
|
||||
/**
|
||||
* 邮箱账号缓存
|
||||
* key:邮箱账号编码 {@link MailAccountDO#getId()}
|
||||
*
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
@Getter
|
||||
private volatile Map<Long, MailAccountDO> mailAccountCache;
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initLocalCache() {
|
||||
// 第一步:查询数据
|
||||
List<MailAccountDO> accounts = mailAccountMapper.selectList();
|
||||
log.info("[initLocalCache][缓存邮箱账号,数量:{}]", accounts.size());
|
||||
|
||||
// 第二步:构建缓存
|
||||
mailAccountCache = convertMap(accounts, MailAccountDO::getId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public MailAccountDO getMailAccountFromCache(Long id) {
|
||||
return mailAccountCache.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createMailAccount(MailAccountCreateReqVO createReqVO) {
|
||||
// 插入
|
||||
MailAccountDO account = MailAccountConvert.INSTANCE.convert(createReqVO);
|
||||
mailAccountMapper.insert(account);
|
||||
|
||||
// 发送刷新消息
|
||||
mailProducer.sendMailAccountRefreshMessage();
|
||||
return account.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = RedisKeyConstants.MAIL_ACCOUNT, key = "#updateReqVO.id")
|
||||
public void updateMailAccount(MailAccountUpdateReqVO updateReqVO) {
|
||||
// 校验是否存在
|
||||
validateMailAccountExists(updateReqVO.getId());
|
||||
|
@ -86,11 +54,10 @@ public class MailAccountServiceImpl implements MailAccountService {
|
|||
// 更新
|
||||
MailAccountDO updateObj = MailAccountConvert.INSTANCE.convert(updateReqVO);
|
||||
mailAccountMapper.updateById(updateObj);
|
||||
// 发送刷新消息
|
||||
mailProducer.sendMailAccountRefreshMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = RedisKeyConstants.MAIL_ACCOUNT, key = "#id")
|
||||
public void deleteMailAccount(Long id) {
|
||||
// 校验是否存在账号
|
||||
validateMailAccountExists(id);
|
||||
|
@ -101,8 +68,6 @@ public class MailAccountServiceImpl implements MailAccountService {
|
|||
|
||||
// 删除
|
||||
mailAccountMapper.deleteById(id);
|
||||
// 发送刷新消息
|
||||
mailProducer.sendMailAccountRefreshMessage();
|
||||
}
|
||||
|
||||
private void validateMailAccountExists(Long id) {
|
||||
|
@ -116,6 +81,12 @@ public class MailAccountServiceImpl implements MailAccountService {
|
|||
return mailAccountMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = RedisKeyConstants.MAIL_ACCOUNT, key = "#id", unless = "#result == null")
|
||||
public MailAccountDO getMailAccountFromCache(Long id) {
|
||||
return getMailAccount(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<MailAccountDO> getMailAccountPage(MailAccountPageReqVO pageReqVO) {
|
||||
return mailAccountMapper.selectPage(pageReqVO);
|
||||
|
|
|
@ -18,11 +18,6 @@ import java.util.Map;
|
|||
*/
|
||||
public interface MailTemplateService {
|
||||
|
||||
/**
|
||||
* 初始化邮件模版的本地缓存
|
||||
*/
|
||||
void initLocalCache();
|
||||
|
||||
/**
|
||||
* 邮件模版创建
|
||||
*
|
||||
|
|
|
@ -10,14 +10,14 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemp
|
|||
import cn.iocoder.yudao.module.system.convert.mail.MailTemplateConvert;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
|
||||
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
import java.util.List;
|
||||
|
@ -25,8 +25,8 @@ import java.util.Map;
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_CODE_EXISTS;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* 邮箱模版 Service 实现类
|
||||
|
@ -47,29 +47,6 @@ public class MailTemplateServiceImpl implements MailTemplateService {
|
|||
@Resource
|
||||
private MailTemplateMapper mailTemplateMapper;
|
||||
|
||||
@Resource
|
||||
private MailProducer mailProducer;
|
||||
|
||||
/**
|
||||
* 邮件模板缓存
|
||||
* key:邮件模版标识 {@link MailTemplateDO#getCode()}
|
||||
*
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
@Getter
|
||||
private volatile Map<String, MailTemplateDO> mailTemplateCache;
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initLocalCache() {
|
||||
// 第一步:查询数据
|
||||
List<MailTemplateDO> templates = mailTemplateMapper.selectList();
|
||||
log.info("[initLocalCache][缓存邮件模版,数量:{}]", templates.size());
|
||||
|
||||
// 第二步:构建缓存
|
||||
mailTemplateCache = convertMap(templates, MailTemplateDO::getCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createMailTemplate(MailTemplateCreateReqVO createReqVO) {
|
||||
// 校验 code 是否唯一
|
||||
|
@ -79,12 +56,12 @@ public class MailTemplateServiceImpl implements MailTemplateService {
|
|||
MailTemplateDO template = MailTemplateConvert.INSTANCE.convert(createReqVO)
|
||||
.setParams(parseTemplateContentParams(createReqVO.getContent()));
|
||||
mailTemplateMapper.insert(template);
|
||||
// 发送刷新消息
|
||||
mailProducer.sendMailTemplateRefreshMessage();
|
||||
return template.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为可能修改到 code 字段,不好清理
|
||||
public void updateMailTemplate(@Valid MailTemplateUpdateReqVO updateReqVO) {
|
||||
// 校验是否存在
|
||||
validateMailTemplateExists(updateReqVO.getId());
|
||||
|
@ -95,12 +72,10 @@ public class MailTemplateServiceImpl implements MailTemplateService {
|
|||
MailTemplateDO updateObj = MailTemplateConvert.INSTANCE.convert(updateReqVO)
|
||||
.setParams(parseTemplateContentParams(updateReqVO.getContent()));
|
||||
mailTemplateMapper.updateById(updateObj);
|
||||
// 发送刷新消息
|
||||
mailProducer.sendMailTemplateRefreshMessage();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void validateCodeUnique(Long id, String code) {
|
||||
void validateCodeUnique(Long id, String code) {
|
||||
MailTemplateDO template = mailTemplateMapper.selectByCode(code);
|
||||
if (template == null) {
|
||||
return;
|
||||
|
@ -113,14 +88,14 @@ public class MailTemplateServiceImpl implements MailTemplateService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 code,不好清理
|
||||
public void deleteMailTemplate(Long id) {
|
||||
// 校验是否存在
|
||||
validateMailTemplateExists(id);
|
||||
|
||||
// 删除
|
||||
mailTemplateMapper.deleteById(id);
|
||||
// 发送刷新消息
|
||||
mailProducer.sendMailTemplateRefreshMessage();
|
||||
}
|
||||
|
||||
private void validateMailTemplateExists(Long id) {
|
||||
|
@ -132,6 +107,12 @@ public class MailTemplateServiceImpl implements MailTemplateService {
|
|||
@Override
|
||||
public MailTemplateDO getMailTemplate(Long id) {return mailTemplateMapper.selectById(id);}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = RedisKeyConstants.MAIL_TEMPLATE, key = "#code", unless = "#result == null")
|
||||
public MailTemplateDO getMailTemplateByCodeFromCache(String code) {
|
||||
return mailTemplateMapper.selectByCode(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<MailTemplateDO> getMailTemplatePage(MailTemplatePageReqVO pageReqVO) {
|
||||
return mailTemplateMapper.selectPage(pageReqVO);
|
||||
|
@ -140,11 +121,6 @@ public class MailTemplateServiceImpl implements MailTemplateService {
|
|||
@Override
|
||||
public List<MailTemplateDO> getMailTemplateList() {return mailTemplateMapper.selectList();}
|
||||
|
||||
@Override
|
||||
public MailTemplateDO getMailTemplateByCodeFromCache(String code) {
|
||||
return mailTemplateCache.get(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatMailTemplateContent(String content, Map<String, Object> params) {
|
||||
return StrUtil.format(content, params);
|
||||
|
|
|
@ -16,19 +16,6 @@ import java.util.Map;
|
|||
*/
|
||||
public interface NotifyTemplateService {
|
||||
|
||||
/**
|
||||
* 初始化站内信模板的本地缓存
|
||||
*/
|
||||
void initLocalCache();
|
||||
|
||||
/**
|
||||
* 获得站内信模板,从缓存中
|
||||
*
|
||||
* @param code 模板编码
|
||||
* @return 站内信模板
|
||||
*/
|
||||
NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code);
|
||||
|
||||
/**
|
||||
* 创建站内信模版
|
||||
*
|
||||
|
@ -59,6 +46,14 @@ public interface NotifyTemplateService {
|
|||
*/
|
||||
NotifyTemplateDO getNotifyTemplate(Long id);
|
||||
|
||||
/**
|
||||
* 获得站内信模板,从缓存中
|
||||
*
|
||||
* @param code 模板编码
|
||||
* @return 站内信模板
|
||||
*/
|
||||
NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code);
|
||||
|
||||
/**
|
||||
* 获得站内信模版分页
|
||||
*
|
||||
|
|
|
@ -3,27 +3,28 @@ package cn.iocoder.yudao.module.system.service.notify;
|
|||
import cn.hutool.core.util.ReUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateCreateReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.system.convert.notify.NotifyTemplateConvert;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.notify.NotifyTemplateMapper;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.notify.NotifyProducer;
|
||||
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_CODE_DUPLICATE;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_NOT_EXISTS;
|
||||
|
||||
/**
|
||||
* 站内信模版 Service 实现类
|
||||
|
@ -43,36 +44,6 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
|
|||
@Resource
|
||||
private NotifyTemplateMapper notifyTemplateMapper;
|
||||
|
||||
@Resource
|
||||
private NotifyProducer notifyProducer;
|
||||
|
||||
/**
|
||||
* 站内信模板缓存
|
||||
* key:站内信模板编码 {@link NotifyTemplateDO#getCode()}
|
||||
* <p>
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
private volatile Map<String, NotifyTemplateDO> notifyTemplateCache;
|
||||
|
||||
/**
|
||||
* 初始化站内信模板的本地缓存
|
||||
*/
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initLocalCache() {
|
||||
// 第一步:查询数据
|
||||
List<NotifyTemplateDO> templates = notifyTemplateMapper.selectList();
|
||||
log.info("[initLocalCache][缓存站内信模版,数量为:{}]", templates.size());
|
||||
|
||||
// 第二步:构建缓存
|
||||
notifyTemplateCache = CollectionUtils.convertMap(templates, NotifyTemplateDO::getCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code) {
|
||||
return notifyTemplateCache.get(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createNotifyTemplate(NotifyTemplateCreateReqVO createReqVO) {
|
||||
// 校验站内信编码是否重复
|
||||
|
@ -82,13 +53,12 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
|
|||
NotifyTemplateDO notifyTemplate = NotifyTemplateConvert.INSTANCE.convert(createReqVO);
|
||||
notifyTemplate.setParams(parseTemplateContentParams(notifyTemplate.getContent()));
|
||||
notifyTemplateMapper.insert(notifyTemplate);
|
||||
|
||||
// 发送刷新消息
|
||||
notifyProducer.sendNotifyTemplateRefreshMessage();
|
||||
return notifyTemplate.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为可能修改到 code 字段,不好清理
|
||||
public void updateNotifyTemplate(NotifyTemplateUpdateReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateNotifyTemplateExists(updateReqVO.getId());
|
||||
|
@ -99,9 +69,6 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
|
|||
NotifyTemplateDO updateObj = NotifyTemplateConvert.INSTANCE.convert(updateReqVO);
|
||||
updateObj.setParams(parseTemplateContentParams(updateObj.getContent()));
|
||||
notifyTemplateMapper.updateById(updateObj);
|
||||
|
||||
// 发送刷新消息
|
||||
notifyProducer.sendNotifyTemplateRefreshMessage();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
|
@ -110,13 +77,13 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 code,不好清理
|
||||
public void deleteNotifyTemplate(Long id) {
|
||||
// 校验存在
|
||||
validateNotifyTemplateExists(id);
|
||||
// 删除
|
||||
notifyTemplateMapper.deleteById(id);
|
||||
// 发送刷新消息
|
||||
notifyProducer.sendNotifyTemplateRefreshMessage();
|
||||
}
|
||||
|
||||
private void validateNotifyTemplateExists(Long id) {
|
||||
|
@ -130,13 +97,20 @@ public class NotifyTemplateServiceImpl implements NotifyTemplateService {
|
|||
return notifyTemplateMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE, key = "#code",
|
||||
unless = "#result == null")
|
||||
public NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code) {
|
||||
return notifyTemplateMapper.selectByCode(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<NotifyTemplateDO> getNotifyTemplatePage(NotifyTemplatePageReqVO pageReqVO) {
|
||||
return notifyTemplateMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void validateNotifyTemplateCodeDuplicate(Long id, String code) {
|
||||
void validateNotifyTemplateCodeDuplicate(Long id, String code) {
|
||||
NotifyTemplateDO template = notifyTemplateMapper.selectByCode(code);
|
||||
if (template == null) {
|
||||
return;
|
||||
|
|
|
@ -18,11 +18,6 @@ import java.util.Collection;
|
|||
*/
|
||||
public interface OAuth2ClientService {
|
||||
|
||||
/**
|
||||
* 初始化 OAuth2Client 的本地缓存
|
||||
*/
|
||||
void initLocalCache();
|
||||
|
||||
/**
|
||||
* 创建 OAuth2 客户端
|
||||
*
|
||||
|
@ -53,6 +48,14 @@ public interface OAuth2ClientService {
|
|||
*/
|
||||
OAuth2ClientDO getOAuth2Client(Long id);
|
||||
|
||||
/**
|
||||
* 获得 OAuth2 客户端,从缓存中
|
||||
*
|
||||
* @param clientId 客户端编号
|
||||
* @return OAuth2 客户端
|
||||
*/
|
||||
OAuth2ClientDO getOAuth2ClientFromCache(String clientId);
|
||||
|
||||
/**
|
||||
* 获得 OAuth2 客户端分页
|
||||
*
|
||||
|
@ -82,7 +85,7 @@ public interface OAuth2ClientService {
|
|||
* @param redirectUri 重定向地址
|
||||
* @return 客户端
|
||||
*/
|
||||
OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret,
|
||||
String authorizedGrantType, Collection<String> scopes, String redirectUri);
|
||||
OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret, String authorizedGrantType,
|
||||
Collection<String> scopes, String redirectUri);
|
||||
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package cn.iocoder.yudao.module.system.service.oauth2;
|
|||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.common.util.string.StrUtils;
|
||||
|
@ -12,22 +13,18 @@ import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2Cl
|
|||
import cn.iocoder.yudao.module.system.convert.auth.OAuth2ClientConvert;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2ClientMapper;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer;
|
||||
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
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;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
||||
|
||||
/**
|
||||
|
@ -40,48 +37,21 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
|||
@Slf4j
|
||||
public class OAuth2ClientServiceImpl implements OAuth2ClientService {
|
||||
|
||||
/**
|
||||
* 客户端缓存
|
||||
* key:客户端编号 {@link OAuth2ClientDO#getClientId()} ()}
|
||||
*
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
@Getter // 解决单测
|
||||
@Setter // 解决单测
|
||||
private volatile Map<String, OAuth2ClientDO> clientCache;
|
||||
|
||||
@Resource
|
||||
private OAuth2ClientMapper oauth2ClientMapper;
|
||||
|
||||
@Resource
|
||||
private OAuth2ClientProducer oauth2ClientProducer;
|
||||
|
||||
/**
|
||||
* 初始化 {@link #clientCache} 缓存
|
||||
*/
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initLocalCache() {
|
||||
// 第一步:查询数据
|
||||
List<OAuth2ClientDO> clients = oauth2ClientMapper.selectList();
|
||||
log.info("[initLocalCache][缓存 OAuth2 客户端,数量为:{}]", clients.size());
|
||||
|
||||
// 第二步:构建缓存。
|
||||
clientCache = convertMap(clients, OAuth2ClientDO::getClientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createOAuth2Client(OAuth2ClientCreateReqVO createReqVO) {
|
||||
validateClientIdExists(null, createReqVO.getClientId());
|
||||
// 插入
|
||||
OAuth2ClientDO oauth2Client = OAuth2ClientConvert.INSTANCE.convert(createReqVO);
|
||||
oauth2ClientMapper.insert(oauth2Client);
|
||||
// 发送刷新消息
|
||||
oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
|
||||
return oauth2Client.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(cacheNames = RedisKeyConstants.OAUTH_CLIENT,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为可能修改到 clientId 字段,不好清理
|
||||
public void updateOAuth2Client(OAuth2ClientUpdateReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateOAuth2ClientExists(updateReqVO.getId());
|
||||
|
@ -91,18 +61,16 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
|
|||
// 更新
|
||||
OAuth2ClientDO updateObj = OAuth2ClientConvert.INSTANCE.convert(updateReqVO);
|
||||
oauth2ClientMapper.updateById(updateObj);
|
||||
// 发送刷新消息
|
||||
oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(cacheNames = RedisKeyConstants.OAUTH_CLIENT,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 key,不好清理
|
||||
public void deleteOAuth2Client(Long id) {
|
||||
// 校验存在
|
||||
validateOAuth2ClientExists(id);
|
||||
// 删除
|
||||
oauth2ClientMapper.deleteById(id);
|
||||
// 发送刷新消息
|
||||
oauth2ClientProducer.sendOAuth2ClientRefreshMessage();
|
||||
}
|
||||
|
||||
private void validateOAuth2ClientExists(Long id) {
|
||||
|
@ -131,16 +99,23 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
|
|||
return oauth2ClientMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(cacheNames = RedisKeyConstants.OAUTH_CLIENT, key = "#clientId",
|
||||
unless = "#result == null")
|
||||
public OAuth2ClientDO getOAuth2ClientFromCache(String clientId) {
|
||||
return oauth2ClientMapper.selectByClientId(clientId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<OAuth2ClientDO> getOAuth2ClientPage(OAuth2ClientPageReqVO pageReqVO) {
|
||||
return oauth2ClientMapper.selectPage(pageReqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret,
|
||||
String authorizedGrantType, Collection<String> scopes, String redirectUri) {
|
||||
public OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret, String authorizedGrantType,
|
||||
Collection<String> scopes, String redirectUri) {
|
||||
// 校验客户端存在、且开启
|
||||
OAuth2ClientDO client = clientCache.get(clientId);
|
||||
OAuth2ClientDO client = getSelf().getOAuth2ClientFromCache(clientId);
|
||||
if (client == null) {
|
||||
throw exception(OAUTH2_CLIENT_NOT_EXISTS);
|
||||
}
|
||||
|
@ -167,4 +142,13 @@ public class OAuth2ClientServiceImpl implements OAuth2ClientService {
|
|||
return client;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得自身的代理对象,解决 AOP 生效问题
|
||||
*
|
||||
* @return 自己
|
||||
*/
|
||||
private OAuth2ClientServiceImpl getSelf() {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -15,11 +15,6 @@ import java.util.List;
|
|||
*/
|
||||
public interface MenuService {
|
||||
|
||||
/**
|
||||
* 初始化菜单的本地缓存
|
||||
*/
|
||||
void initLocalCache();
|
||||
|
||||
/**
|
||||
* 创建菜单
|
||||
*
|
||||
|
@ -67,36 +62,12 @@ public interface MenuService {
|
|||
List<MenuDO> getMenuList(MenuListReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 获得所有菜单,从缓存中
|
||||
*
|
||||
* 任一参数为空时,则返回为空
|
||||
*
|
||||
* @param menuTypes 菜单类型数组
|
||||
* @param menusStatuses 菜单状态数组
|
||||
* @return 菜单列表
|
||||
*/
|
||||
List<MenuDO> getMenuListFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses);
|
||||
|
||||
/**
|
||||
* 获得指定编号的菜单数组,从缓存中
|
||||
*
|
||||
* 任一参数为空时,则返回为空
|
||||
*
|
||||
* @param menuIds 菜单编号数组
|
||||
* @param menuTypes 菜单类型数组
|
||||
* @param menusStatuses 菜单状态数组
|
||||
* @return 菜单数组
|
||||
*/
|
||||
List<MenuDO> getMenuListFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
|
||||
Collection<Integer> menusStatuses);
|
||||
|
||||
/**
|
||||
* 获得权限对应的菜单数组
|
||||
* 获得权限对应的菜单编号数组
|
||||
*
|
||||
* @param permission 权限标识
|
||||
* @return 数组
|
||||
*/
|
||||
List<MenuDO> getMenuListByPermissionFromCache(String permission);
|
||||
List<Long> getMenuIdListByPermissionFromCache(String permission);
|
||||
|
||||
/**
|
||||
* 获得菜单
|
||||
|
@ -106,4 +77,12 @@ public interface MenuService {
|
|||
*/
|
||||
MenuDO getMenu(Long id);
|
||||
|
||||
/**
|
||||
* 获得菜单数组
|
||||
*
|
||||
* @param ids 菜单编号数组
|
||||
* @return 菜单数组
|
||||
*/
|
||||
List<MenuDO> getMenuList(Collection<Long> ids);
|
||||
|
||||
}
|
||||
|
|
|
@ -1,36 +1,29 @@
|
|||
package cn.iocoder.yudao.module.system.service.permission;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuCreateReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuListReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.system.convert.permission.MenuConvert;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.permission.MenuMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.permission.MenuProducer;
|
||||
import cn.iocoder.yudao.module.system.service.tenant.TenantService;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
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.convertList;
|
||||
import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
||||
|
||||
|
@ -43,26 +36,6 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
|||
@Slf4j
|
||||
public class MenuServiceImpl implements MenuService {
|
||||
|
||||
/**
|
||||
* 菜单缓存
|
||||
* key:菜单编号
|
||||
*
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
private volatile Map<Long, MenuDO> menuCache;
|
||||
/**
|
||||
* 权限与菜单缓存
|
||||
* key:权限 {@link MenuDO#getPermission()}
|
||||
* value:MenuDO 数组,因为一个权限可能对应多个 MenuDO 对象
|
||||
*
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
private volatile Multimap<String, MenuDO> permissionMenuCache;
|
||||
|
||||
@Resource
|
||||
private MenuMapper menuMapper;
|
||||
@Resource
|
||||
|
@ -71,33 +44,8 @@ public class MenuServiceImpl implements MenuService {
|
|||
@Lazy // 延迟,避免循环依赖报错
|
||||
private TenantService tenantService;
|
||||
|
||||
@Resource
|
||||
private MenuProducer menuProducer;
|
||||
|
||||
/**
|
||||
* 初始化 {@link #menuCache} 和 {@link #permissionMenuCache} 缓存
|
||||
*/
|
||||
@Override
|
||||
@PostConstruct
|
||||
public synchronized void initLocalCache() {
|
||||
// 第一步:查询数据
|
||||
List<MenuDO> menuList = menuMapper.selectList();
|
||||
log.info("[initLocalCache][缓存菜单,数量为:{}]", menuList.size());
|
||||
|
||||
// 第二步:构建缓存
|
||||
ImmutableMap.Builder<Long, MenuDO> menuCacheBuilder = ImmutableMap.builder();
|
||||
ImmutableMultimap.Builder<String, MenuDO> permMenuCacheBuilder = ImmutableMultimap.builder();
|
||||
menuList.forEach(menuDO -> {
|
||||
menuCacheBuilder.put(menuDO.getId(), menuDO);
|
||||
if (StrUtil.isNotEmpty(menuDO.getPermission())) { // 会存在 permission 为 null 的情况,导致 put 报 NPE 异常
|
||||
permMenuCacheBuilder.put(menuDO.getPermission(), menuDO);
|
||||
}
|
||||
});
|
||||
menuCache = menuCacheBuilder.build();
|
||||
permissionMenuCache = permMenuCacheBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#reqVO.permission")
|
||||
public Long createMenu(MenuCreateReqVO reqVO) {
|
||||
// 校验父菜单存在
|
||||
validateParentMenu(reqVO.getParentId(), null);
|
||||
|
@ -108,13 +56,13 @@ public class MenuServiceImpl implements MenuService {
|
|||
MenuDO menu = MenuConvert.INSTANCE.convert(reqVO);
|
||||
initMenuProperty(menu);
|
||||
menuMapper.insert(menu);
|
||||
// 发送刷新消息
|
||||
menuProducer.sendMenuRefreshMessage();
|
||||
// 返回
|
||||
return menu.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为 permission 如果变更,涉及到新老两个 permission。直接清理,简单有效
|
||||
public void updateMenu(MenuUpdateReqVO reqVO) {
|
||||
// 校验更新的菜单是否存在
|
||||
if (menuMapper.selectById(reqVO.getId()) == null) {
|
||||
|
@ -129,34 +77,25 @@ public class MenuServiceImpl implements MenuService {
|
|||
MenuDO updateObject = MenuConvert.INSTANCE.convert(reqVO);
|
||||
initMenuProperty(updateObject);
|
||||
menuMapper.updateById(updateObject);
|
||||
// 发送刷新消息
|
||||
menuProducer.sendMenuRefreshMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void deleteMenu(Long menuId) {
|
||||
@CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为此时不知道 id 对应的 permission 是多少。直接清理,简单有效
|
||||
public void deleteMenu(Long id) {
|
||||
// 校验是否还有子菜单
|
||||
if (menuMapper.selectCountByParentId(menuId) > 0) {
|
||||
if (menuMapper.selectCountByParentId(id) > 0) {
|
||||
throw exception(MENU_EXISTS_CHILDREN);
|
||||
}
|
||||
// 校验删除的菜单是否存在
|
||||
if (menuMapper.selectById(menuId) == null) {
|
||||
if (menuMapper.selectById(id) == null) {
|
||||
throw exception(MENU_NOT_EXISTS);
|
||||
}
|
||||
// 标记删除
|
||||
menuMapper.deleteById(menuId);
|
||||
menuMapper.deleteById(id);
|
||||
// 删除授予给角色的权限
|
||||
permissionService.processMenuDeleted(menuId);
|
||||
// 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
menuProducer.sendMenuRefreshMessage();
|
||||
}
|
||||
|
||||
});
|
||||
permissionService.processMenuDeleted(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -178,33 +117,10 @@ public class MenuServiceImpl implements MenuService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<MenuDO> getMenuListFromCache(Collection<Integer> menuTypes, Collection<Integer> menusStatuses) {
|
||||
// 任一一个参数为空,则返回空
|
||||
if (CollectionUtils.isAnyEmpty(menuTypes, menusStatuses)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 创建新数组,避免缓存被修改
|
||||
return menuCache.values().stream().filter(menu -> menuTypes.contains(menu.getType())
|
||||
&& menusStatuses.contains(menu.getStatus()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MenuDO> getMenuListFromCache(Collection<Long> menuIds, Collection<Integer> menuTypes,
|
||||
Collection<Integer> menusStatuses) {
|
||||
// 任一一个参数为空,则返回空
|
||||
if (CollectionUtils.isAnyEmpty(menuIds, menuTypes, menusStatuses)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return menuCache.values().stream().filter(menu -> menuIds.contains(menu.getId())
|
||||
&& menuTypes.contains(menu.getType())
|
||||
&& menusStatuses.contains(menu.getStatus()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MenuDO> getMenuListByPermissionFromCache(String permission) {
|
||||
return new ArrayList<>(permissionMenuCache.get(permission));
|
||||
@Cacheable(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = "#permission")
|
||||
public List<Long> getMenuIdListByPermissionFromCache(String permission) {
|
||||
List<MenuDO> menus = menuMapper.selectListByPermission(permission);
|
||||
return convertList(menus, MenuDO::getId);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -212,15 +128,20 @@ public class MenuServiceImpl implements MenuService {
|
|||
return menuMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MenuDO> getMenuList(Collection<Long> ids) {
|
||||
return menuMapper.selectBatchIds(ids);
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验父菜单是否合法
|
||||
*
|
||||
* <p>
|
||||
* 1. 不能设置自己为父菜单
|
||||
* 2. 父菜单不存在
|
||||
* 3. 父菜单必须是 {@link MenuTypeEnum#MENU} 菜单类型
|
||||
*
|
||||
* @param parentId 父菜单编号
|
||||
* @param childId 当前菜单编号
|
||||
* @param childId 当前菜单编号
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void validateParentMenu(Long parentId, Long childId) {
|
||||
|
@ -238,19 +159,19 @@ public class MenuServiceImpl implements MenuService {
|
|||
}
|
||||
// 父菜单必须是目录或者菜单类型
|
||||
if (!MenuTypeEnum.DIR.getType().equals(menu.getType())
|
||||
&& !MenuTypeEnum.MENU.getType().equals(menu.getType())) {
|
||||
&& !MenuTypeEnum.MENU.getType().equals(menu.getType())) {
|
||||
throw exception(MENU_PARENT_NOT_DIR_OR_MENU);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验菜单是否合法
|
||||
*
|
||||
* <p>
|
||||
* 1. 校验相同父菜单编号下,是否存在相同的菜单名
|
||||
*
|
||||
* @param name 菜单名字
|
||||
* @param name 菜单名字
|
||||
* @param parentId 父菜单编号
|
||||
* @param id 菜单编号
|
||||
* @param id 菜单编号
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void validateMenu(Long parentId, String name, Long id) {
|
||||
|
@ -269,7 +190,7 @@ public class MenuServiceImpl implements MenuService {
|
|||
|
||||
/**
|
||||
* 初始化菜单的通用属性。
|
||||
*
|
||||
* <p>
|
||||
* 例如说,只有目录或者菜单类型的菜单,才设置 icon
|
||||
*
|
||||
* @param menu 菜单
|
||||
|
|
|
@ -1,16 +1,15 @@
|
|||
package cn.iocoder.yudao.module.system.service.permission;
|
||||
|
||||
import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.Collections.singleton;
|
||||
|
||||
/**
|
||||
* 权限 Service 接口
|
||||
*
|
||||
* <p>
|
||||
* 提供用户-角色、角色-菜单、角色-部门的关联权限处理
|
||||
*
|
||||
* @author 芋道源码
|
||||
|
@ -18,81 +17,32 @@ import java.util.Set;
|
|||
public interface PermissionService {
|
||||
|
||||
/**
|
||||
* 初始化权限的本地缓存
|
||||
* 判断是否有权限,任一一个即可
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param permissions 权限
|
||||
* @return 是否
|
||||
*/
|
||||
void initLocalCache();
|
||||
boolean hasAnyPermissions(Long userId, String... permissions);
|
||||
|
||||
/**
|
||||
* 获得角色们拥有的菜单列表,从缓存中获取
|
||||
* 判断是否有角色,任一一个即可
|
||||
*
|
||||
* 任一参数为空时,则返回为空
|
||||
*
|
||||
* @param roleIds 角色编号数组
|
||||
* @param menuTypes 菜单类型数组
|
||||
* @param menusStatuses 菜单状态数组
|
||||
* @return 菜单列表
|
||||
* @param roles 角色数组
|
||||
* @return 是否
|
||||
*/
|
||||
List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
|
||||
Collection<Integer> menusStatuses);
|
||||
boolean hasAnyRoles(Long userId, String... roles);
|
||||
|
||||
/**
|
||||
* 获得用户拥有的角色编号集合,从缓存中获取
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param roleStatuses 角色状态集合. 允许为空,为空时不过滤
|
||||
* @return 角色编号集合
|
||||
*/
|
||||
Set<Long> getUserRoleIdsFromCache(Long userId, @Nullable Collection<Integer> roleStatuses);
|
||||
|
||||
/**
|
||||
* 获得角色拥有的菜单编号集合
|
||||
*
|
||||
* @param roleId 角色编号
|
||||
* @return 菜单编号集合
|
||||
*/
|
||||
Set<Long> getRoleMenuIds(Long roleId);
|
||||
|
||||
/**
|
||||
* 获得拥有多个角色的用户编号集合
|
||||
*
|
||||
* @param roleIds 角色编号集合
|
||||
* @return 用户编号集合
|
||||
*/
|
||||
Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds);
|
||||
// ========== 角色-菜单的相关方法 ==========
|
||||
|
||||
/**
|
||||
* 设置角色菜单
|
||||
*
|
||||
* @param roleId 角色编号
|
||||
* @param roleId 角色编号
|
||||
* @param menuIds 菜单编号集合
|
||||
*/
|
||||
void assignRoleMenu(Long roleId, Set<Long> menuIds);
|
||||
|
||||
/**
|
||||
* 获得用户拥有的角色编号集合
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @return 角色编号集合
|
||||
*/
|
||||
Set<Long> getUserRoleIdListByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 设置用户角色
|
||||
*
|
||||
* @param userId 角色编号
|
||||
* @param roleIds 角色编号集合
|
||||
*/
|
||||
void assignUserRole(Long userId, Set<Long> roleIds);
|
||||
|
||||
/**
|
||||
* 设置角色的数据权限
|
||||
*
|
||||
* @param roleId 角色编号
|
||||
* @param dataScope 数据范围
|
||||
* @param dataScopeDeptIds 部门编号数组
|
||||
*/
|
||||
void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds);
|
||||
|
||||
/**
|
||||
* 处理角色删除时,删除关联授权数据
|
||||
*
|
||||
|
@ -108,28 +58,82 @@ public interface PermissionService {
|
|||
void processMenuDeleted(Long menuId);
|
||||
|
||||
/**
|
||||
* 处理用户删除是,删除关联授权数据
|
||||
* 获得角色拥有的菜单编号集合
|
||||
*
|
||||
* @param roleId 角色编号
|
||||
* @return 菜单编号集合
|
||||
*/
|
||||
default Set<Long> getRoleMenuListByRoleId(Long roleId) {
|
||||
return getRoleMenuListByRoleId(singleton(roleId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得角色们拥有的菜单编号集合
|
||||
*
|
||||
* @param roleIds 角色编号数组
|
||||
* @return 菜单编号集合
|
||||
*/
|
||||
Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds);
|
||||
|
||||
/**
|
||||
* 获得拥有指定菜单的角色编号数组,从缓存中获取
|
||||
*
|
||||
* @param menuId 菜单编号
|
||||
* @return 角色编号数组
|
||||
*/
|
||||
Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId);
|
||||
|
||||
// ========== 用户-角色的相关方法 ==========
|
||||
|
||||
/**
|
||||
* 设置用户角色
|
||||
*
|
||||
* @param userId 角色编号
|
||||
* @param roleIds 角色编号集合
|
||||
*/
|
||||
void assignUserRole(Long userId, Set<Long> roleIds);
|
||||
|
||||
/**
|
||||
* 处理用户删除时,删除关联授权数据
|
||||
*
|
||||
* @param userId 用户编号
|
||||
*/
|
||||
void processUserDeleted(Long userId);
|
||||
|
||||
/**
|
||||
* 判断是否有权限,任一一个即可
|
||||
* 获得拥有多个角色的用户编号集合
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @param permissions 权限
|
||||
* @return 是否
|
||||
* @param roleIds 角色编号集合
|
||||
* @return 用户编号集合
|
||||
*/
|
||||
boolean hasAnyPermissions(Long userId, String... permissions);
|
||||
Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds);
|
||||
|
||||
/**
|
||||
* 判断是否有角色,任一一个即可
|
||||
* 获得用户拥有的角色编号集合
|
||||
*
|
||||
* @param roles 角色数组
|
||||
* @return 是否
|
||||
* @param userId 用户编号
|
||||
* @return 角色编号集合
|
||||
*/
|
||||
boolean hasAnyRoles(Long userId, String... roles);
|
||||
Set<Long> getUserRoleIdListByUserId(Long userId);
|
||||
|
||||
/**
|
||||
* 获得用户拥有的角色编号集合,从缓存中获取
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @return 角色编号集合
|
||||
*/
|
||||
Set<Long> getUserRoleIdListByUserIdFromCache(Long userId);
|
||||
|
||||
// ========== 用户-部门的相关方法 ==========
|
||||
|
||||
/**
|
||||
* 设置角色的数据权限
|
||||
*
|
||||
* @param roleId 角色编号
|
||||
* @param dataScope 数据范围
|
||||
* @param dataScopeDeptIds 部门编号数组
|
||||
*/
|
||||
void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds);
|
||||
|
||||
/**
|
||||
* 获得登陆用户的部门数据权限
|
||||
|
|
|
@ -3,45 +3,38 @@ package cn.iocoder.yudao.module.system.service.permission;
|
|||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ArrayUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
|
||||
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;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
|
||||
import cn.iocoder.yudao.module.system.service.dept.DeptService;
|
||||
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
|
||||
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Suppliers;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Sets;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.cache.annotation.Caching;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
|
||||
import static java.util.Collections.singleton;
|
||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||
|
||||
/**
|
||||
* 权限 Service 实现类
|
||||
|
@ -52,38 +45,6 @@ import static java.util.Collections.singleton;
|
|||
@Slf4j
|
||||
public class PermissionServiceImpl implements PermissionService {
|
||||
|
||||
/**
|
||||
* 角色编号与菜单编号的缓存映射
|
||||
* key:角色编号
|
||||
* value:菜单编号的数组
|
||||
*
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
@Getter
|
||||
@Setter // 单元测试需要
|
||||
private volatile Multimap<Long, Long> roleMenuCache;
|
||||
/**
|
||||
* 菜单编号与角色编号的缓存映射
|
||||
* key:菜单编号
|
||||
* value:角色编号的数组
|
||||
*
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
@Getter
|
||||
@Setter // 单元测试需要
|
||||
private volatile Multimap<Long, Long> menuRoleCache;
|
||||
|
||||
/**
|
||||
* 用户编号与角色编号的缓存映射
|
||||
* key:用户编号
|
||||
* value:角色编号的数组
|
||||
*
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
@Getter
|
||||
@Setter // 单元测试需要
|
||||
private volatile Map<Long, Set<Long>> userRoleCache;
|
||||
|
||||
@Resource
|
||||
private RoleMenuMapper roleMenuMapper;
|
||||
@Resource
|
||||
|
@ -98,115 +59,89 @@ public class PermissionServiceImpl implements PermissionService {
|
|||
@Resource
|
||||
private AdminUserService userService;
|
||||
|
||||
@Resource
|
||||
private PermissionProducer permissionProducer;
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initLocalCache() {
|
||||
initLocalCacheForRoleMenu();
|
||||
initLocalCacheForUserRole();
|
||||
public boolean hasAnyPermissions(Long userId, String... permissions) {
|
||||
// 如果为空,说明已经有权限
|
||||
if (ArrayUtil.isEmpty(permissions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获得当前登录的角色。如果为空,说明没有权限
|
||||
List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
|
||||
if (CollUtil.isEmpty(roles)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 情况一:遍历判断每个权限,如果有一满足,说明有权限
|
||||
for (String permission : permissions) {
|
||||
if (hasAnyPermission(roles, permission)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 情况二:如果是超管,也说明有权限
|
||||
return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新 RoleMenu 本地缓存
|
||||
* 判断指定角色,是否拥有该 permission 权限
|
||||
*
|
||||
* @param roles 指定角色数组
|
||||
* @param permission 权限标识
|
||||
* @return 是否拥有
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void initLocalCacheForRoleMenu() {
|
||||
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||
TenantUtils.executeIgnore(() -> {
|
||||
// 第一步:查询数据
|
||||
List<RoleMenuDO> roleMenus = roleMenuMapper.selectList();
|
||||
log.info("[initLocalCacheForRoleMenu][缓存角色与菜单,数量为:{}]", roleMenus.size());
|
||||
private boolean hasAnyPermission(List<RoleDO> roles, String permission) {
|
||||
List<Long> menuIds = menuService.getMenuIdListByPermissionFromCache(permission);
|
||||
// 采用严格模式,如果权限找不到对应的 Menu 的话,也认为没有权限
|
||||
if (CollUtil.isEmpty(menuIds)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 第二步:构建缓存
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新 UserRole 本地缓存
|
||||
*/
|
||||
@VisibleForTesting
|
||||
void initLocalCacheForUserRole() {
|
||||
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||
TenantUtils.executeIgnore(() -> {
|
||||
// 第一步:加载数据
|
||||
List<UserRoleDO> userRoles = userRoleMapper.selectList();
|
||||
log.info("[initLocalCacheForUserRole][缓存用户与角色,数量为:{}]", 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);
|
||||
});
|
||||
// 判断是否有权限
|
||||
Set<Long> roleIds = convertSet(roles, RoleDO::getId);
|
||||
for (Long menuId : menuIds) {
|
||||
// 获得拥有该菜单的角色编号集合
|
||||
Set<Long> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menuId);
|
||||
// 如果有交集,说明有权限
|
||||
if (CollUtil.containsAny(menuRoleIds, roleIds)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<MenuDO> getRoleMenuListFromCache(Collection<Long> roleIds, Collection<Integer> menuTypes,
|
||||
Collection<Integer> menusStatuses) {
|
||||
// 任一一个参数为空时,不返回任何菜单
|
||||
if (CollectionUtils.isAnyEmpty(roleIds, menuTypes, menusStatuses)) {
|
||||
return Collections.emptyList();
|
||||
public boolean hasAnyRoles(Long userId, String... roles) {
|
||||
// 如果为空,说明已经有权限
|
||||
if (ArrayUtil.isEmpty(roles)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 判断角色是否包含超级管理员。如果是超级管理员,获取到全部
|
||||
List<RoleDO> roleList = roleService.getRoleListFromCache(roleIds);
|
||||
if (roleService.hasAnySuperAdmin(roleList)) {
|
||||
return menuService.getMenuListFromCache(menuTypes, menusStatuses);
|
||||
// 获得当前登录的角色。如果为空,说明没有权限
|
||||
List<RoleDO> roleList = getEnableUserRoleListByUserIdFromCache(userId);
|
||||
if (CollUtil.isEmpty(roleList)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获得角色拥有的菜单关联
|
||||
List<Long> menuIds = MapUtils.getList(roleMenuCache, roleIds);
|
||||
return menuService.getMenuListFromCache(menuIds, menuTypes, menusStatuses);
|
||||
// 判断是否有角色
|
||||
Set<String> userRoles = convertSet(roleList, RoleDO::getCode);
|
||||
return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> getUserRoleIdsFromCache(Long userId, Collection<Integer> roleStatuses) {
|
||||
Set<Long> cacheRoleIds = userRoleCache.get(userId);
|
||||
// 创建用户的时候没有分配角色,会存在空指针异常
|
||||
if (CollUtil.isEmpty(cacheRoleIds)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<Long> roleIds = new HashSet<>(cacheRoleIds);
|
||||
// 过滤角色状态
|
||||
if (CollectionUtil.isNotEmpty(roleStatuses)) {
|
||||
roleIds.removeIf(roleId -> {
|
||||
RoleDO role = roleService.getRoleFromCache(roleId);
|
||||
return role == null || !roleStatuses.contains(role.getStatus());
|
||||
});
|
||||
}
|
||||
return roleIds;
|
||||
}
|
||||
// ========== 角色-菜单的相关方法 ==========
|
||||
|
||||
@Override
|
||||
public Set<Long> getRoleMenuIds(Long roleId) {
|
||||
// 如果是管理员的情况下,获取全部菜单编号
|
||||
if (roleService.hasAnySuperAdmin(Collections.singleton(roleId))) {
|
||||
return convertSet(menuService.getMenuList(), MenuDO::getId);
|
||||
}
|
||||
// 如果是非管理员的情况下,获得拥有的菜单编号
|
||||
return convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
|
||||
@CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
|
||||
allEntries = true) // allEntries 清空所有缓存,主要一次更新涉及到的 menuIds 较多,反倒批量会更快
|
||||
public void assignRoleMenu(Long roleId, Set<Long> menuIds) {
|
||||
// 获得角色拥有菜单编号
|
||||
Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId),
|
||||
RoleMenuDO::getMenuId);
|
||||
Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);
|
||||
// 计算新增和删除的菜单编号
|
||||
Collection<Long> createMenuIds = CollUtil.subtract(menuIds, dbMenuIds);
|
||||
Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIds);
|
||||
// 执行新增和删除。对于已经授权的菜单,不用做任何处理
|
||||
if (!CollectionUtil.isEmpty(createMenuIds)) {
|
||||
if (CollUtil.isNotEmpty(createMenuIds)) {
|
||||
roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {
|
||||
RoleMenuDO entity = new RoleMenuDO();
|
||||
entity.setRoleId(roleId);
|
||||
|
@ -214,34 +149,57 @@ public class PermissionServiceImpl implements PermissionService {
|
|||
return entity;
|
||||
}));
|
||||
}
|
||||
if (!CollectionUtil.isEmpty(deleteMenuIds)) {
|
||||
if (CollUtil.isNotEmpty(deleteMenuIds)) {
|
||||
roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);
|
||||
}
|
||||
// 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
permissionProducer.sendRoleMenuRefreshMessage();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> getUserRoleIdListByUserId(Long userId) {
|
||||
return convertSet(userRoleMapper.selectListByUserId(userId),
|
||||
UserRoleDO::getRoleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {
|
||||
return convertSet(userRoleMapper.selectListByRoleIds(roleIds),
|
||||
UserRoleDO::getUserId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@Caching(evict = {
|
||||
@CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,
|
||||
allEntries = true), // allEntries 清空所有缓存,此处无法方便获得 roleId 对应的 menu 缓存们
|
||||
@CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST,
|
||||
allEntries = true) // allEntries 清空所有缓存,此处无法方便获得 roleId 对应的 user 缓存们
|
||||
})
|
||||
public void processRoleDeleted(Long roleId) {
|
||||
// 标记删除 UserRole
|
||||
userRoleMapper.deleteListByRoleId(roleId);
|
||||
// 标记删除 RoleMenu
|
||||
roleMenuMapper.deleteListByRoleId(roleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
|
||||
public void processMenuDeleted(Long menuId) {
|
||||
roleMenuMapper.deleteListByMenuId(menuId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) {
|
||||
if (CollUtil.isEmpty(roleIds)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
// 如果是管理员的情况下,获取全部菜单编号
|
||||
if (roleService.hasAnySuperAdmin(roleIds)) {
|
||||
return convertSet(menuService.getMenuList(), MenuDO::getId);
|
||||
}
|
||||
// 如果是非管理员的情况下,获得拥有的菜单编号
|
||||
return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = "#menuId")
|
||||
public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) {
|
||||
return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId);
|
||||
}
|
||||
|
||||
// ========== 用户-角色的相关方法 ==========
|
||||
|
||||
@Override
|
||||
@DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
|
||||
@CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
|
||||
public void assignUserRole(Long userId, Set<Long> roleIds) {
|
||||
// 获得角色拥有角色编号
|
||||
Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId),
|
||||
|
@ -261,137 +219,68 @@ public class PermissionServiceImpl implements PermissionService {
|
|||
if (!CollectionUtil.isEmpty(deleteMenuIds)) {
|
||||
userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);
|
||||
}
|
||||
// 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
permissionProducer.sendUserRoleRefreshMessage();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
|
||||
public void processUserDeleted(Long userId) {
|
||||
userRoleMapper.deleteListByUserId(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> getUserRoleIdListByUserId(Long userId) {
|
||||
return convertSet(userRoleMapper.selectListByUserId(userId), UserRoleDO::getRoleId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = "#userId")
|
||||
public Set<Long> getUserRoleIdListByUserIdFromCache(Long userId) {
|
||||
return getUserRoleIdListByUserId(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds) {
|
||||
return convertSet(userRoleMapper.selectListByRoleIds(roleIds), UserRoleDO::getUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得用户拥有的角色,并且这些角色是开启状态的
|
||||
*
|
||||
* @param userId 用户编号
|
||||
* @return 用户拥有的角色
|
||||
*/
|
||||
@VisibleForTesting
|
||||
List<RoleDO> getEnableUserRoleListByUserIdFromCache(Long userId) {
|
||||
// 获得用户拥有的角色编号
|
||||
Set<Long> roleIds = getSelf().getUserRoleIdListByUserIdFromCache(userId);
|
||||
// 获得角色数组,并移除被禁用的
|
||||
List<RoleDO> roles = roleService.getRoleListFromCache(roleIds);
|
||||
roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()));
|
||||
return roles;
|
||||
}
|
||||
|
||||
// ========== 用户-部门的相关方法 ==========
|
||||
|
||||
@Override
|
||||
public void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds) {
|
||||
roleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void processRoleDeleted(Long roleId) {
|
||||
// 标记删除 UserRole
|
||||
userRoleMapper.deleteListByRoleId(roleId);
|
||||
// 标记删除 RoleMenu
|
||||
roleMenuMapper.deleteListByRoleId(roleId);
|
||||
// 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
permissionProducer.sendRoleMenuRefreshMessage();
|
||||
permissionProducer.sendUserRoleRefreshMessage();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void processMenuDeleted(Long menuId) {
|
||||
roleMenuMapper.deleteListByMenuId(menuId);
|
||||
// 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
permissionProducer.sendRoleMenuRefreshMessage();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void processUserDeleted(Long userId) {
|
||||
userRoleMapper.deleteListByUserId(userId);
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
permissionProducer.sendUserRoleRefreshMessage();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAnyPermissions(Long userId, String... permissions) {
|
||||
// 如果为空,说明已经有权限
|
||||
if (ArrayUtil.isEmpty(permissions)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获得当前登录的角色。如果为空,说明没有权限
|
||||
Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
|
||||
if (CollUtil.isEmpty(roleIds)) {
|
||||
return false;
|
||||
}
|
||||
// 判断是否是超管。如果是,当然符合条件
|
||||
if (roleService.hasAnySuperAdmin(roleIds)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 遍历权限,判断是否有一个满足
|
||||
return Arrays.stream(permissions).anyMatch(permission -> {
|
||||
List<MenuDO> menuList = menuService.getMenuListByPermissionFromCache(permission);
|
||||
// 采用严格模式,如果权限找不到对应的 Menu 的话,认为
|
||||
if (CollUtil.isEmpty(menuList)) {
|
||||
return false;
|
||||
}
|
||||
// 获得是否拥有该权限,任一一个
|
||||
return menuList.stream().anyMatch(menu -> CollUtil.containsAny(roleIds,
|
||||
menuRoleCache.get(menu.getId())));
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAnyRoles(Long userId, String... roles) {
|
||||
// 如果为空,说明已经有权限
|
||||
if (ArrayUtil.isEmpty(roles)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 获得当前登录的角色。如果为空,说明没有权限
|
||||
Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
|
||||
if (CollUtil.isEmpty(roleIds)) {
|
||||
return false;
|
||||
}
|
||||
// 判断是否是超管。如果是,当然符合条件
|
||||
if (roleService.hasAnySuperAdmin(roleIds)) {
|
||||
return true;
|
||||
}
|
||||
Set<String> userRoles = convertSet(roleService.getRoleListFromCache(roleIds),
|
||||
RoleDO::getCode);
|
||||
return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataPermission(enable = false) // 关闭数据权限,不然就会出现递归获取数据权限的问题
|
||||
@TenantIgnore // 忽略多租户的自动过滤。如果不忽略,会导致添加租户时,因为切换租户,导致获取不到 User。即使忽略,本身该方法不存在跨租户的操作,不会存在问题。
|
||||
public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {
|
||||
// 获得用户的角色
|
||||
Set<Long> roleIds = getUserRoleIdsFromCache(userId, singleton(CommonStatusEnum.ENABLE.getStatus()));
|
||||
List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);
|
||||
|
||||
// 如果角色为空,则只能查看自己
|
||||
DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();
|
||||
if (CollUtil.isEmpty(roleIds)) {
|
||||
if (CollUtil.isEmpty(roles)) {
|
||||
result.setSelf(true);
|
||||
return result;
|
||||
}
|
||||
List<RoleDO> roles = roleService.getRoleListFromCache(roleIds);
|
||||
|
||||
// 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询
|
||||
Supplier<Long> userDeptIdCache = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
|
||||
Supplier<Long> userDeptId = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());
|
||||
// 遍历每个角色,计算
|
||||
for (RoleDO role : roles) {
|
||||
// 为空时,跳过
|
||||
|
@ -408,20 +297,19 @@ public class PermissionServiceImpl implements PermissionService {
|
|||
CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());
|
||||
// 自定义可见部门时,保证可以看到自己所在的部门。否则,一些场景下可能会有问题。
|
||||
// 例如说,登录时,基于 t_user 的 username 查询会可能被 dept_id 过滤掉
|
||||
CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
|
||||
CollUtil.addAll(result.getDeptIds(), userDeptId.get());
|
||||
continue;
|
||||
}
|
||||
// 情况三,DEPT_ONLY
|
||||
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {
|
||||
CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptIdCache.get());
|
||||
CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get());
|
||||
continue;
|
||||
}
|
||||
// 情况四,DEPT_DEPT_AND_CHILD
|
||||
if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {
|
||||
List<DeptDO> depts = deptService.getDeptListByParentIdFromCache(userDeptIdCache.get(), true);
|
||||
CollUtil.addAll(result.getDeptIds(), CollectionUtils.convertList(depts, DeptDO::getId));
|
||||
CollUtil.addAll(result.getDeptIds(), deptService.getChildDeptIdListFromCache(userDeptId.get()));
|
||||
// 添加本身部门编号
|
||||
CollUtil.addAll(result.getDeptIds(), userDeptIdCache.get());
|
||||
CollUtil.addAll(result.getDeptIds(), userDeptId.get());
|
||||
continue;
|
||||
}
|
||||
// 情况五,SELF
|
||||
|
@ -430,9 +318,18 @@ public class PermissionServiceImpl implements PermissionService {
|
|||
continue;
|
||||
}
|
||||
// 未知情况,error log 即可
|
||||
log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, JsonUtils.toJsonString(result));
|
||||
log.error("[getDeptDataPermission][LoginUser({}) role({}) 无法处理]", userId, toJsonString(result));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得自身的代理对象,解决 AOP 生效问题
|
||||
*
|
||||
* @return 自己
|
||||
*/
|
||||
private PermissionServiceImpl getSelf() {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,7 +6,6 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleEx
|
|||
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RolePageReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.permission.vo.role.RoleUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.Collection;
|
||||
|
@ -20,11 +19,6 @@ import java.util.Set;
|
|||
*/
|
||||
public interface RoleService {
|
||||
|
||||
/**
|
||||
* 初始化角色的本地缓存
|
||||
*/
|
||||
void initLocalCache();
|
||||
|
||||
/**
|
||||
* 创建角色
|
||||
*
|
||||
|
@ -65,6 +59,14 @@ public interface RoleService {
|
|||
*/
|
||||
void updateRoleDataScope(Long id, Integer dataScope, Set<Long> dataScopeDeptIds);
|
||||
|
||||
/**
|
||||
* 获得角色
|
||||
*
|
||||
* @param id 角色编号
|
||||
* @return 角色
|
||||
*/
|
||||
RoleDO getRole(Long id);
|
||||
|
||||
/**
|
||||
* 获得角色,从缓存中
|
||||
*
|
||||
|
@ -76,10 +78,10 @@ public interface RoleService {
|
|||
/**
|
||||
* 获得角色列表
|
||||
*
|
||||
* @param statuses 筛选的状态。允许空,空时不筛选
|
||||
* @param ids 角色编号数组
|
||||
* @return 角色列表
|
||||
*/
|
||||
List<RoleDO> getRoleListByStatus(@Nullable Collection<Integer> statuses);
|
||||
List<RoleDO> getRoleList(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 获得角色数组,从缓存中
|
||||
|
@ -90,30 +92,19 @@ public interface RoleService {
|
|||
List<RoleDO> getRoleListFromCache(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 判断角色数组中,是否有超级管理员
|
||||
* 获得角色列表
|
||||
*
|
||||
* @param roleList 角色数组
|
||||
* @return 是否有管理员
|
||||
* @param statuses 筛选的状态
|
||||
* @return 角色列表
|
||||
*/
|
||||
boolean hasAnySuperAdmin(Collection<RoleDO> roleList);
|
||||
List<RoleDO> getRoleListByStatus(Collection<Integer> statuses);
|
||||
|
||||
/**
|
||||
* 判断角色编号数组中,是否有管理员
|
||||
* 获得所有角色列表
|
||||
*
|
||||
* @param ids 角色编号数组
|
||||
* @return 是否有管理员
|
||||
* @return 角色列表
|
||||
*/
|
||||
default boolean hasAnySuperAdmin(Set<Long> ids) {
|
||||
return hasAnySuperAdmin(getRoleListFromCache(ids));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得角色
|
||||
*
|
||||
* @param id 角色编号
|
||||
* @return 角色
|
||||
*/
|
||||
RoleDO getRole(Long id);
|
||||
List<RoleDO> getRoleList();
|
||||
|
||||
/**
|
||||
* 获得角色分页
|
||||
|
@ -131,6 +122,14 @@ public interface RoleService {
|
|||
*/
|
||||
List<RoleDO> getRoleList(RoleExportReqVO reqVO);
|
||||
|
||||
/**
|
||||
* 判断角色编号数组中,是否有管理员
|
||||
*
|
||||
* @param ids 角色编号数组
|
||||
* @return 是否有管理员
|
||||
*/
|
||||
boolean hasAnySuperAdmin(Collection<Long> ids);
|
||||
|
||||
/**
|
||||
* 校验角色们是否有效。如下情况,视为无效:
|
||||
* 1. 角色编号不存在
|
||||
|
|
|
@ -3,9 +3,9 @@ package cn.iocoder.yudao.module.system.service.permission;
|
|||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.collection.CollectionUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
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;
|
||||
|
@ -13,26 +13,23 @@ 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.dal.redis.RedisKeyConstants;
|
||||
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.lang.Nullable;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
||||
|
||||
|
@ -45,43 +42,14 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
|||
@Slf4j
|
||||
public class RoleServiceImpl implements RoleService {
|
||||
|
||||
/**
|
||||
* 角色缓存
|
||||
* key:角色编号 {@link RoleDO#getId()}
|
||||
*
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
@Getter
|
||||
private volatile Map<Long, RoleDO> roleCache;
|
||||
|
||||
@Resource
|
||||
private PermissionService permissionService;
|
||||
|
||||
@Resource
|
||||
private RoleMapper roleMapper;
|
||||
|
||||
@Resource
|
||||
private RoleProducer roleProducer;
|
||||
|
||||
/**
|
||||
* 初始化 {@link #roleCache} 缓存
|
||||
*/
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initLocalCache() {
|
||||
// 注意:忽略自动多租户,因为要全局初始化缓存
|
||||
TenantUtils.executeIgnore(() -> {
|
||||
// 第一步:查询数据
|
||||
List<RoleDO> roleList = roleMapper.selectList();
|
||||
log.info("[initLocalCache][缓存角色,数量为:{}]", roleList.size());
|
||||
|
||||
// 第二步:构建缓存
|
||||
roleCache = convertMap(roleList, RoleDO::getId);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createRole(RoleCreateReqVO reqVO, Integer type) {
|
||||
// 校验角色
|
||||
validateRoleDuplicate(reqVO.getName(), reqVO.getCode(), null);
|
||||
|
@ -91,18 +59,12 @@ public class RoleServiceImpl implements RoleService {
|
|||
role.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
role.setDataScope(DataScopeEnum.ALL.getScope()); // 默认可查看所有数据。原因是,可能一些项目不需要项目权限
|
||||
roleMapper.insert(role);
|
||||
// 发送刷新消息
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
roleProducer.sendRoleRefreshMessage();
|
||||
}
|
||||
});
|
||||
// 返回
|
||||
return role.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = RedisKeyConstants.ROLE, key = "#reqVO.id")
|
||||
public void updateRole(RoleUpdateReqVO reqVO) {
|
||||
// 校验是否可以更新
|
||||
validateRoleForUpdate(reqVO.getId());
|
||||
|
@ -112,11 +74,10 @@ public class RoleServiceImpl implements RoleService {
|
|||
// 更新到数据库
|
||||
RoleDO updateObj = RoleConvert.INSTANCE.convert(reqVO);
|
||||
roleMapper.updateById(updateObj);
|
||||
// 发送刷新消息
|
||||
roleProducer.sendRoleRefreshMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")
|
||||
public void updateRoleStatus(Long id, Integer status) {
|
||||
// 校验是否可以更新
|
||||
validateRoleForUpdate(id);
|
||||
|
@ -124,11 +85,10 @@ public class RoleServiceImpl implements RoleService {
|
|||
// 更新状态
|
||||
RoleDO updateObj = new RoleDO().setId(id).setStatus(status);
|
||||
roleMapper.updateById(updateObj);
|
||||
// 发送刷新消息
|
||||
roleProducer.sendRoleRefreshMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")
|
||||
public void updateRoleDataScope(Long id, Integer dataScope, Set<Long> dataScopeDeptIds) {
|
||||
// 校验是否可以更新
|
||||
validateRoleForUpdate(id);
|
||||
|
@ -139,12 +99,11 @@ public class RoleServiceImpl implements RoleService {
|
|||
updateObject.setDataScope(dataScope);
|
||||
updateObject.setDataScopeDeptIds(dataScopeDeptIds);
|
||||
roleMapper.updateById(updateObject);
|
||||
// 发送刷新消息
|
||||
roleProducer.sendRoleRefreshMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@CacheEvict(value = RedisKeyConstants.ROLE, key = "#id")
|
||||
public void deleteRole(Long id) {
|
||||
// 校验是否可以更新
|
||||
validateRoleForUpdate(id);
|
||||
|
@ -152,60 +111,6 @@ public class RoleServiceImpl implements RoleService {
|
|||
roleMapper.deleteById(id);
|
||||
// 删除相关数据
|
||||
permissionService.processRoleDeleted(id);
|
||||
// 发送刷新消息. 注意,需要事务提交后,在进行发送刷新消息。不然 db 还未提交,结果缓存先刷新了
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
|
||||
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
roleProducer.sendRoleRefreshMessage();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleDO getRoleFromCache(Long id) {
|
||||
return roleCache.get(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDO> getRoleListByStatus(@Nullable Collection<Integer> statuses) {
|
||||
if (CollUtil.isEmpty(statuses)) {
|
||||
return roleMapper.selectList();
|
||||
}
|
||||
return roleMapper.selectListByStatus(statuses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDO> getRoleListFromCache(Collection<Long> ids) {
|
||||
if (CollectionUtil.isEmpty(ids)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return roleCache.values().stream().filter(roleDO -> ids.contains(roleDO.getId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAnySuperAdmin(Collection<RoleDO> roleList) {
|
||||
if (CollectionUtil.isEmpty(roleList)) {
|
||||
return false;
|
||||
}
|
||||
return roleList.stream().anyMatch(role -> RoleCodeEnum.isSuperAdmin(role.getCode()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleDO getRole(Long id) {
|
||||
return roleMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<RoleDO> getRolePage(RolePageReqVO reqVO) {
|
||||
return roleMapper.selectPage(reqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDO> getRoleList(RoleExportReqVO reqVO) {
|
||||
return roleMapper.selectList(reqVO);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -257,6 +162,69 @@ public class RoleServiceImpl implements RoleService {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RoleDO getRole(Long id) {
|
||||
return roleMapper.selectById(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Cacheable(value = RedisKeyConstants.ROLE, key = "#id",
|
||||
unless = "#result == null")
|
||||
public RoleDO getRoleFromCache(Long id) {
|
||||
return roleMapper.selectById(id);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public List<RoleDO> getRoleListByStatus(Collection<Integer> statuses) {
|
||||
return roleMapper.selectListByStatus(statuses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDO> getRoleList() {
|
||||
return roleMapper.selectList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDO> getRoleList(Collection<Long> ids) {
|
||||
if (CollectionUtil.isEmpty(ids)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return roleMapper.selectBatchIds(ids);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDO> getRoleListFromCache(Collection<Long> ids) {
|
||||
if (CollectionUtil.isEmpty(ids)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 这里采用 for 循环从缓存中获取,主要考虑 Spring CacheManager 无法批量操作的问题
|
||||
RoleServiceImpl self = getSelf();
|
||||
return convertList(ids, self::getRoleFromCache);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PageResult<RoleDO> getRolePage(RolePageReqVO reqVO) {
|
||||
return roleMapper.selectPage(reqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RoleDO> getRoleList(RoleExportReqVO reqVO) {
|
||||
return roleMapper.selectList(reqVO);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAnySuperAdmin(Collection<Long> ids) {
|
||||
if (CollectionUtil.isEmpty(ids)) {
|
||||
return false;
|
||||
}
|
||||
RoleServiceImpl self = getSelf();
|
||||
return ids.stream().anyMatch(id -> {
|
||||
RoleDO role = self.getRoleFromCache(id);
|
||||
return role != null && RoleCodeEnum.isSuperAdmin(role.getCode());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateRoleList(Collection<Long> ids) {
|
||||
if (CollUtil.isEmpty(ids)) {
|
||||
|
@ -276,4 +244,14 @@ public class RoleServiceImpl implements RoleService {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获得自身的代理对象,解决 AOP 生效问题
|
||||
*
|
||||
* @return 自己
|
||||
*/
|
||||
private RoleServiceImpl getSelf() {
|
||||
return SpringUtil.getBean(getClass());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,7 +8,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
|
|||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -16,40 +15,10 @@ import java.util.Map;
|
|||
* 短信模板 Service 接口
|
||||
*
|
||||
* @author zzf
|
||||
* @date 2021/1/25 9:24
|
||||
* @since 2021/1/25 9:24
|
||||
*/
|
||||
public interface SmsTemplateService {
|
||||
|
||||
/**
|
||||
* 初始化短信模板的本地缓存
|
||||
*/
|
||||
void initLocalCache();
|
||||
|
||||
/**
|
||||
* 获得短信模板,从缓存中
|
||||
*
|
||||
* @param code 模板编码
|
||||
* @return 短信模板
|
||||
*/
|
||||
SmsTemplateDO getSmsTemplateByCodeFromCache(String code);
|
||||
|
||||
/**
|
||||
* 格式化短信内容
|
||||
*
|
||||
* @param content 短信模板的内容
|
||||
* @param params 内容的参数
|
||||
* @return 格式化后的内容
|
||||
*/
|
||||
String formatSmsTemplateContent(String content, Map<String, Object> params);
|
||||
|
||||
/**
|
||||
* 获得短信模板
|
||||
*
|
||||
* @param code 模板编码
|
||||
* @return 短信模板
|
||||
*/
|
||||
SmsTemplateDO getSmsTemplateByCode(String code);
|
||||
|
||||
/**
|
||||
* 创建短信模板
|
||||
*
|
||||
|
@ -81,12 +50,12 @@ public interface SmsTemplateService {
|
|||
SmsTemplateDO getSmsTemplate(Long id);
|
||||
|
||||
/**
|
||||
* 获得短信模板列表
|
||||
* 获得短信模板,从缓存中
|
||||
*
|
||||
* @param ids 编号
|
||||
* @return 短信模板列表
|
||||
* @param code 模板编码
|
||||
* @return 短信模板
|
||||
*/
|
||||
List<SmsTemplateDO> getSmsTemplateList(Collection<Long> ids);
|
||||
SmsTemplateDO getSmsTemplateByCodeFromCache(String code);
|
||||
|
||||
/**
|
||||
* 获得短信模板分页
|
||||
|
@ -112,4 +81,14 @@ public interface SmsTemplateService {
|
|||
*/
|
||||
Long countByChannelId(Long channelId);
|
||||
|
||||
|
||||
/**
|
||||
* 格式化短信内容
|
||||
*
|
||||
* @param content 短信模板的内容
|
||||
* @param params 内容的参数
|
||||
* @return 格式化后的内容
|
||||
*/
|
||||
String formatSmsTemplateContent(String content, Map<String, Object> params);
|
||||
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ import cn.hutool.core.util.ReUtil;
|
|||
import cn.hutool.core.util.StrUtil;
|
||||
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.framework.sms.core.client.SmsClient;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsClientFactory;
|
||||
import cn.iocoder.yudao.framework.sms.core.client.SmsCommonResult;
|
||||
|
@ -17,16 +16,15 @@ import cn.iocoder.yudao.module.system.convert.sms.SmsTemplateConvert;
|
|||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsTemplateMapper;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
|
||||
import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import lombok.Getter;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cache.annotation.CacheEvict;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
@ -59,49 +57,6 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
|
|||
@Resource
|
||||
private SmsClientFactory smsClientFactory;
|
||||
|
||||
@Resource
|
||||
private SmsProducer smsProducer;
|
||||
|
||||
/**
|
||||
* 短信模板缓存
|
||||
* key:短信模板编码 {@link SmsTemplateDO#getCode()}
|
||||
*
|
||||
* 这里声明 volatile 修饰的原因是,每次刷新时,直接修改指向
|
||||
*/
|
||||
@Getter // 为了方便测试,这里提供 getter 方法
|
||||
private volatile Map<String, SmsTemplateDO> smsTemplateCache;
|
||||
|
||||
@Override
|
||||
@PostConstruct
|
||||
public void initLocalCache() {
|
||||
// 第一步:查询数据
|
||||
List<SmsTemplateDO> smsTemplateList = smsTemplateMapper.selectList();
|
||||
log.info("[initLocalCache][缓存短信模版,数量为:{}]", smsTemplateList.size());
|
||||
|
||||
// 第二步:构建缓存
|
||||
smsTemplateCache = CollectionUtils.convertMap(smsTemplateList, SmsTemplateDO::getCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsTemplateDO getSmsTemplateByCodeFromCache(String code) {
|
||||
return smsTemplateCache.get(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatSmsTemplateContent(String content, Map<String, Object> params) {
|
||||
return StrUtil.format(content, params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SmsTemplateDO getSmsTemplateByCode(String code) {
|
||||
return smsTemplateMapper.selectByCode(code);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public List<String> parseTemplateContentParams(String content) {
|
||||
return ReUtil.findAllGroup1(PATTERN_PARAMS, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long createSmsTemplate(SmsTemplateCreateReqVO createReqVO) {
|
||||
// 校验短信渠道
|
||||
|
@ -116,13 +71,13 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
|
|||
template.setParams(parseTemplateContentParams(template.getContent()));
|
||||
template.setChannelCode(channelDO.getCode());
|
||||
smsTemplateMapper.insert(template);
|
||||
// 发送刷新消息
|
||||
smsProducer.sendSmsTemplateRefreshMessage();
|
||||
// 返回
|
||||
return template.getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(cacheNames = RedisKeyConstants.SMS_TEMPLATE,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为可能修改到 code 字段,不好清理
|
||||
public void updateSmsTemplate(SmsTemplateUpdateReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
validateSmsTemplateExists(updateReqVO.getId());
|
||||
|
@ -138,18 +93,16 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
|
|||
updateObj.setParams(parseTemplateContentParams(updateObj.getContent()));
|
||||
updateObj.setChannelCode(channelDO.getCode());
|
||||
smsTemplateMapper.updateById(updateObj);
|
||||
// 发送刷新消息
|
||||
smsProducer.sendSmsTemplateRefreshMessage();
|
||||
}
|
||||
|
||||
@Override
|
||||
@CacheEvict(cacheNames = RedisKeyConstants.SMS_TEMPLATE,
|
||||
allEntries = true) // allEntries 清空所有缓存,因为 id 不是直接的缓存 code,不好清理
|
||||
public void deleteSmsTemplate(Long id) {
|
||||
// 校验存在
|
||||
validateSmsTemplateExists(id);
|
||||
// 更新
|
||||
smsTemplateMapper.deleteById(id);
|
||||
// 发送刷新消息
|
||||
smsProducer.sendSmsTemplateRefreshMessage();
|
||||
}
|
||||
|
||||
private void validateSmsTemplateExists(Long id) {
|
||||
|
@ -164,8 +117,10 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<SmsTemplateDO> getSmsTemplateList(Collection<Long> ids) {
|
||||
return smsTemplateMapper.selectBatchIds(ids);
|
||||
@Cacheable(cacheNames = RedisKeyConstants.SMS_TEMPLATE, key = "#code",
|
||||
unless = "#result == null")
|
||||
public SmsTemplateDO getSmsTemplateByCodeFromCache(String code) {
|
||||
return smsTemplateMapper.selectByCode(code);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -217,7 +172,7 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
|
|||
* @param apiTemplateId API 模板编号
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public void validateApiTemplate(Long channelId, String apiTemplateId) {
|
||||
void validateApiTemplate(Long channelId, String apiTemplateId) {
|
||||
// 获得短信模板
|
||||
SmsClient smsClient = smsClientFactory.getSmsClient(channelId);
|
||||
Assert.notNull(smsClient, String.format("短信客户端(%d) 不存在", channelId));
|
||||
|
@ -226,4 +181,14 @@ public class SmsTemplateServiceImpl implements SmsTemplateService {
|
|||
templateResult.checkError();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String formatSmsTemplateContent(String content, Map<String, Object> params) {
|
||||
return StrUtil.format(content, params);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
List<String> parseTemplateContentParams(String content) {
|
||||
return ReUtil.findAllGroup1(PATTERN_PARAMS, content);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@ import cn.iocoder.yudao.module.system.convert.tenant.TenantPackageConvert;
|
|||
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantDO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.tenant.TenantPackageDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.tenant.TenantPackageMapper;
|
||||
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
@ -47,7 +47,7 @@ public class TenantPackageServiceImpl implements TenantPackageService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
|
||||
public void updateTenantPackage(TenantPackageUpdateReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
TenantPackageDO tenantPackage = validateTenantPackageExists(updateReqVO.getId());
|
||||
|
|
|
@ -29,6 +29,7 @@ import cn.iocoder.yudao.module.system.service.permission.RoleService;
|
|||
import cn.iocoder.yudao.module.system.service.tenant.handler.TenantInfoHandler;
|
||||
import cn.iocoder.yudao.module.system.service.tenant.handler.TenantMenuHandler;
|
||||
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
|
||||
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
@ -95,7 +96,7 @@ public class TenantServiceImpl implements TenantService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@DSTransactional // 多数据源,使用 @DSTransactional 保证本地事务,以及数据源的切换
|
||||
public Long createTenant(TenantCreateReqVO createReqVO) {
|
||||
// 校验租户名称是否重复
|
||||
validTenantNameDuplicate(createReqVO.getName(), null);
|
||||
|
@ -137,7 +138,7 @@ public class TenantServiceImpl implements TenantService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@DSTransactional
|
||||
public void updateTenant(TenantUpdateReqVO updateReqVO) {
|
||||
// 校验存在
|
||||
TenantDO tenant = validateUpdateTenant(updateReqVO.getId());
|
||||
|
@ -170,7 +171,7 @@ public class TenantServiceImpl implements TenantService {
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
@DSTransactional
|
||||
public void updateTenantRoleMenu(Long tenantId, Set<Long> menuIds) {
|
||||
TenantUtils.execute(tenantId, () -> {
|
||||
// 获得所有角色
|
||||
|
@ -186,7 +187,7 @@ public class TenantServiceImpl implements TenantService {
|
|||
return;
|
||||
}
|
||||
// 如果是其他角色,则去掉超过套餐的权限
|
||||
Set<Long> roleMenuIds = permissionService.getRoleMenuIds(role.getId());
|
||||
Set<Long> roleMenuIds = permissionService.getRoleMenuListByRoleId(role.getId());
|
||||
roleMenuIds = CollUtil.intersectionDistinct(roleMenuIds, menuIds);
|
||||
permissionService.assignRoleMenu(role.getId(), roleMenuIds);
|
||||
log.info("[updateTenantRoleMenu][角色({}/{}) 的权限修改为({})]", role.getId(), role.getTenantId(), roleMenuIds);
|
||||
|
|
|
@ -290,8 +290,7 @@ public class AdminUserServiceImpl implements AdminUserService {
|
|||
if (deptId == null) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
Set<Long> deptIds = convertSet(deptService.getDeptListByParentIdFromCache(
|
||||
deptId, true), DeptDO::getId);
|
||||
Set<Long> deptIds = convertSet(deptService.getChildDeptList(deptId), DeptDO::getId);
|
||||
deptIds.add(deptId); // 包括自身
|
||||
return deptIds;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package cn.iocoder.yudao.module.system.service.dept;
|
||||
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.util.collection.ArrayUtils;
|
||||
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
|
||||
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptCreateReqVO;
|
||||
import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptListReqVO;
|
||||
|
@ -11,27 +9,20 @@ import cn.iocoder.yudao.module.system.controller.admin.dept.vo.dept.DeptUpdateRe
|
|||
import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.dept.DeptMapper;
|
||||
import cn.iocoder.yudao.module.system.enums.dept.DeptIdEnum;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.dept.DeptProducer;
|
||||
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;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.hutool.core.util.RandomUtil.randomEle;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* {@link DeptServiceImpl} 的单元测试类
|
||||
|
@ -45,39 +36,163 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||
private DeptServiceImpl deptService;
|
||||
@Resource
|
||||
private DeptMapper deptMapper;
|
||||
@MockBean
|
||||
private DeptProducer deptProducer;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
// 清理租户上下文
|
||||
TenantContextHolder.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitLocalCache() {
|
||||
// mock 数据
|
||||
DeptDO deptDO1 = randomDeptDO();
|
||||
deptMapper.insert(deptDO1);
|
||||
DeptDO deptDO2 = randomDeptDO();
|
||||
deptMapper.insert(deptDO2);
|
||||
public void testCreateDept() {
|
||||
// 准备参数
|
||||
DeptCreateReqVO reqVO = randomPojo(DeptCreateReqVO.class, o -> {
|
||||
o.setParentId(DeptIdEnum.ROOT.getId());
|
||||
o.setStatus(randomCommonStatus());
|
||||
});
|
||||
|
||||
// 调用
|
||||
deptService.initLocalCache();
|
||||
// 断言 deptCache 缓存
|
||||
Map<Long, DeptDO> deptCache = deptService.getDeptCache();
|
||||
assertEquals(2, deptCache.size());
|
||||
assertPojoEquals(deptDO1, deptCache.get(deptDO1.getId()));
|
||||
assertPojoEquals(deptDO2, deptCache.get(deptDO2.getId()));
|
||||
// 断言 parentDeptCache 缓存
|
||||
Multimap<Long, DeptDO> parentDeptCache = deptService.getParentDeptCache();
|
||||
assertEquals(2, parentDeptCache.size());
|
||||
assertPojoEquals(deptDO1, parentDeptCache.get(deptDO1.getParentId()));
|
||||
assertPojoEquals(deptDO2, parentDeptCache.get(deptDO2.getParentId()));
|
||||
Long deptId = deptService.createDept(reqVO);
|
||||
// 断言
|
||||
assertNotNull(deptId);
|
||||
// 校验记录的属性是否正确
|
||||
DeptDO deptDO = deptMapper.selectById(deptId);
|
||||
assertPojoEquals(reqVO, deptDO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListDepts() {
|
||||
public void testUpdateDept() {
|
||||
// mock 数据
|
||||
DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
|
||||
deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
|
||||
// 设置更新的 ID
|
||||
o.setParentId(DeptIdEnum.ROOT.getId());
|
||||
o.setId(dbDeptDO.getId());
|
||||
o.setStatus(randomCommonStatus());
|
||||
});
|
||||
|
||||
// 调用
|
||||
deptService.updateDept(reqVO);
|
||||
// 校验是否更新正确
|
||||
DeptDO deptDO = deptMapper.selectById(reqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(reqVO, deptDO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteDept_success() {
|
||||
// mock 数据
|
||||
DeptDO dbDeptDO = randomPojo(DeptDO.class);
|
||||
deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbDeptDO.getId();
|
||||
|
||||
// 调用
|
||||
deptService.deleteDept(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(deptMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteDept_exitsChildren() {
|
||||
// mock 数据
|
||||
DeptDO parentDept = randomPojo(DeptDO.class);
|
||||
deptMapper.insert(parentDept);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
DeptDO childrenDeptDO = randomPojo(DeptDO.class, o -> {
|
||||
o.setParentId(parentDept.getId());
|
||||
o.setStatus(randomCommonStatus());
|
||||
});
|
||||
// 插入子部门
|
||||
deptMapper.insert(childrenDeptDO);
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> deptService.deleteDept(parentDept.getId()), DEPT_EXITS_CHILDREN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDeptExists_notFound() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> deptService.validateDeptExists(id), DEPT_NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateParentDept_parentError() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> deptService.validateParentDept(id, id),
|
||||
DEPT_PARENT_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateParentDept_parentIsChild() {
|
||||
// mock 数据(父节点)
|
||||
DeptDO parentDept = randomPojo(DeptDO.class);
|
||||
deptMapper.insert(parentDept);
|
||||
// mock 数据(子节点)
|
||||
DeptDO childDept = randomPojo(DeptDO.class, o -> {
|
||||
o.setParentId(parentDept.getId());
|
||||
});
|
||||
deptMapper.insert(childDept);
|
||||
|
||||
// 准备参数
|
||||
Long id = parentDept.getId();
|
||||
Long parentId = childDept.getId();
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> deptService.validateParentDept(id, parentId), DEPT_PARENT_IS_CHILD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateNameUnique_duplicate() {
|
||||
// mock 数据
|
||||
DeptDO deptDO = randomPojo(DeptDO.class);
|
||||
deptMapper.insert(deptDO);
|
||||
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
Long parentId = deptDO.getParentId();
|
||||
String name = deptDO.getName();
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> deptService.validateDeptNameUnique(id, parentId, name),
|
||||
DEPT_NAME_DUPLICATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDept() {
|
||||
// mock 数据
|
||||
DeptDO deptDO = randomPojo(DeptDO.class);
|
||||
deptMapper.insert(deptDO);
|
||||
// 准备参数
|
||||
Long id = deptDO.getId();
|
||||
|
||||
// 调用
|
||||
DeptDO dbDept = deptService.getDept(id);
|
||||
// 断言
|
||||
assertEquals(deptDO, dbDept);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeptList_ids() {
|
||||
// mock 数据
|
||||
DeptDO deptDO01 = randomPojo(DeptDO.class);
|
||||
deptMapper.insert(deptDO01);
|
||||
DeptDO deptDO02 = randomPojo(DeptDO.class);
|
||||
deptMapper.insert(deptDO02);
|
||||
// 准备参数
|
||||
List<Long> ids = Arrays.asList(deptDO01.getId(), deptDO02.getId());
|
||||
|
||||
// 调用
|
||||
List<DeptDO> deptDOList = deptService.getDeptList(ids);
|
||||
// 断言
|
||||
assertEquals(2, deptDOList.size());
|
||||
assertEquals(deptDO01, deptDOList.get(0));
|
||||
assertEquals(deptDO02, deptDOList.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeptList_reqVO() {
|
||||
// mock 数据
|
||||
DeptDO dept = randomPojo(DeptDO.class, o -> { // 等会查询到
|
||||
o.setName("开发部");
|
||||
|
@ -101,216 +216,55 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testCreateDept_success() {
|
||||
public void testGetChildDeptList() {
|
||||
// mock 数据(1 级别子节点)
|
||||
DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName("1"));
|
||||
deptMapper.insert(dept1);
|
||||
DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName("2"));
|
||||
deptMapper.insert(dept2);
|
||||
// mock 数据(2 级子节点)
|
||||
DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName("1-a").setParentId(dept1.getId()));
|
||||
deptMapper.insert(dept1a);
|
||||
DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName("2-a").setParentId(dept2.getId()));
|
||||
deptMapper.insert(dept2a);
|
||||
// 准备参数
|
||||
DeptCreateReqVO reqVO = randomPojo(DeptCreateReqVO.class, o -> {
|
||||
o.setParentId(DeptIdEnum.ROOT.getId());
|
||||
o.setStatus(randomCommonStatus());
|
||||
});
|
||||
Long id = dept1.getParentId();
|
||||
|
||||
// 调用
|
||||
Long deptId = deptService.createDept(reqVO);
|
||||
List<DeptDO> result = deptService.getChildDeptList(id);
|
||||
// 断言
|
||||
assertNotNull(deptId);
|
||||
// 校验记录的属性是否正确
|
||||
DeptDO deptDO = deptMapper.selectById(deptId);
|
||||
assertPojoEquals(reqVO, deptDO);
|
||||
// 校验调用
|
||||
verify(deptProducer).sendDeptRefreshMessage();
|
||||
assertEquals(result.size(), 2);
|
||||
assertPojoEquals(dept1, result.get(0));
|
||||
assertPojoEquals(dept1a, result.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateDept_success() {
|
||||
// mock 数据
|
||||
DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
|
||||
deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据
|
||||
public void testGetChildDeptListFromCache() {
|
||||
// mock 数据(1 级别子节点)
|
||||
DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName("1"));
|
||||
deptMapper.insert(dept1);
|
||||
DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName("2"));
|
||||
deptMapper.insert(dept2);
|
||||
// mock 数据(2 级子节点)
|
||||
DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName("1-a").setParentId(dept1.getId()));
|
||||
deptMapper.insert(dept1a);
|
||||
DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName("2-a").setParentId(dept2.getId()));
|
||||
deptMapper.insert(dept2a);
|
||||
// 准备参数
|
||||
DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
|
||||
// 设置更新的 ID
|
||||
o.setParentId(DeptIdEnum.ROOT.getId());
|
||||
o.setId(dbDeptDO.getId());
|
||||
o.setStatus(randomCommonStatus());
|
||||
});
|
||||
Long id = dept1.getParentId();
|
||||
|
||||
// 调用
|
||||
deptService.updateDept(reqVO);
|
||||
// 校验是否更新正确
|
||||
DeptDO deptDO = deptMapper.selectById(reqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(reqVO, deptDO);
|
||||
// 校验调用
|
||||
verify(deptProducer).sendDeptRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteDept_success() {
|
||||
// mock 数据
|
||||
DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
|
||||
deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
Long id = dbDeptDO.getId();
|
||||
|
||||
// 调用
|
||||
deptService.deleteDept(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(deptMapper.selectById(id));
|
||||
// 校验调用
|
||||
verify(deptProducer).sendDeptRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDept_nameDuplicateForUpdate() {
|
||||
// mock 数据
|
||||
DeptDO deptDO = randomDeptDO();
|
||||
// 设置根节点部门
|
||||
deptDO.setParentId(DeptIdEnum.ROOT.getId());
|
||||
deptMapper.insert(deptDO);
|
||||
// mock 数据 稍后模拟重复它的 name
|
||||
DeptDO nameDeptDO = randomDeptDO();
|
||||
// 设置根节点部门
|
||||
nameDeptDO.setParentId(DeptIdEnum.ROOT.getId());
|
||||
deptMapper.insert(nameDeptDO);
|
||||
// 准备参数
|
||||
DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
|
||||
// 设置根节点部门
|
||||
o.setParentId(DeptIdEnum.ROOT.getId());
|
||||
// 设置更新的 ID
|
||||
o.setId(deptDO.getId());
|
||||
// 模拟 name 重复
|
||||
o.setName(nameDeptDO.getName());
|
||||
});
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> deptService.updateDept(reqVO), DEPT_NAME_DUPLICATE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDept_parentNotExitsForCreate() {
|
||||
// 准备参数
|
||||
DeptCreateReqVO reqVO = randomPojo(DeptCreateReqVO.class,
|
||||
o -> o.setStatus(randomCommonStatus()));
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> deptService.createDept(reqVO), DEPT_PARENT_NOT_EXITS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDept_notFoundForDelete() {
|
||||
// 准备参数
|
||||
Long id = randomLongId();
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> deptService.deleteDept(id), DEPT_NOT_FOUND);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDept_exitsChildrenForDelete() {
|
||||
// mock 数据
|
||||
DeptDO parentDept = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
|
||||
deptMapper.insert(parentDept);// @Sql: 先插入出一条存在的数据
|
||||
// 准备参数
|
||||
DeptDO childrenDeptDO = randomPojo(DeptDO.class, o -> {
|
||||
o.setParentId(parentDept.getId());
|
||||
o.setStatus(randomCommonStatus());
|
||||
});
|
||||
// 插入子部门
|
||||
deptMapper.insert(childrenDeptDO);
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> deptService.deleteDept(parentDept.getId()), DEPT_EXITS_CHILDREN);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDept_parentErrorForUpdate() {
|
||||
// mock 数据
|
||||
DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));
|
||||
deptMapper.insert(dbDeptDO);
|
||||
// 准备参数
|
||||
DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
|
||||
// 设置自己为父部门
|
||||
o.setParentId(dbDeptDO.getId());
|
||||
// 设置更新的 ID
|
||||
o.setId(dbDeptDO.getId());
|
||||
});
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> deptService.updateDept(reqVO), DEPT_PARENT_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDept_notEnableForCreate() {
|
||||
// mock 数据
|
||||
DeptDO deptDO = randomPojo(DeptDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
||||
deptMapper.insert(deptDO);
|
||||
// 准备参数
|
||||
DeptCreateReqVO reqVO = randomPojo(DeptCreateReqVO.class, o -> {
|
||||
// 设置未启用的部门为父部门
|
||||
o.setParentId(deptDO.getId());
|
||||
});
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> deptService.createDept(reqVO), DEPT_NOT_ENABLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCheckDept_parentIsChildForUpdate() {
|
||||
// mock 数据
|
||||
DeptDO parentDept = randomPojo(DeptDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
deptMapper.insert(parentDept);
|
||||
DeptDO childDept = randomPojo(DeptDO.class, o -> {
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setParentId(parentDept.getId());
|
||||
});
|
||||
deptMapper.insert(childDept);
|
||||
// 初始化本地缓存
|
||||
deptService.initLocalCache();
|
||||
|
||||
// 准备参数
|
||||
DeptUpdateReqVO reqVO = randomPojo(DeptUpdateReqVO.class, o -> {
|
||||
// 设置自己的子部门为父部门
|
||||
o.setParentId(childDept.getId());
|
||||
// 设置更新的 ID
|
||||
o.setId(parentDept.getId());
|
||||
});
|
||||
|
||||
// 调用, 并断言异常
|
||||
assertServiceException(() -> deptService.updateDept(reqVO), DEPT_PARENT_IS_CHILD);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeptList() {
|
||||
// mock 数据
|
||||
DeptDO deptDO01 = randomDeptDO();
|
||||
deptMapper.insert(deptDO01);
|
||||
DeptDO deptDO02 = randomDeptDO();
|
||||
deptMapper.insert(deptDO02);
|
||||
// 准备参数
|
||||
List<Long> ids = Arrays.asList(deptDO01.getId(), deptDO02.getId());
|
||||
|
||||
// 调用
|
||||
List<DeptDO> deptDOList = deptService.getDeptList(ids);
|
||||
Set<Long> result = deptService.getChildDeptIdListFromCache(id);
|
||||
// 断言
|
||||
assertEquals(2, deptDOList.size());
|
||||
assertEquals(deptDO01, deptDOList.get(0));
|
||||
assertEquals(deptDO02, deptDOList.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDept() {
|
||||
// mock 数据
|
||||
DeptDO deptDO = randomDeptDO();
|
||||
deptMapper.insert(deptDO);
|
||||
// 准备参数
|
||||
Long id = deptDO.getId();
|
||||
|
||||
// 调用
|
||||
DeptDO dbDept = deptService.getDept(id);
|
||||
// 断言
|
||||
assertEquals(deptDO, dbDept);
|
||||
assertEquals(result.size(), 2);
|
||||
assertTrue(result.contains(dept1.getId()));
|
||||
assertTrue(result.contains(dept1a.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateDeptList_success() {
|
||||
// mock 数据
|
||||
DeptDO deptDO = randomDeptDO().setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
DeptDO deptDO = randomPojo(DeptDO.class).setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
deptMapper.insert(deptDO);
|
||||
// 准备参数
|
||||
List<Long> ids = singletonList(deptDO.getId());
|
||||
|
@ -331,7 +285,7 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||
@Test
|
||||
public void testValidateDeptList_notEnable() {
|
||||
// mock 数据
|
||||
DeptDO deptDO = randomDeptDO().setStatus(CommonStatusEnum.DISABLE.getStatus());
|
||||
DeptDO deptDO = randomPojo(DeptDO.class).setStatus(CommonStatusEnum.DISABLE.getStatus());
|
||||
deptMapper.insert(deptDO);
|
||||
// 准备参数
|
||||
List<Long> ids = singletonList(deptDO.getId());
|
||||
|
@ -340,12 +294,4 @@ public class DeptServiceImplTest extends BaseDbUnitTest {
|
|||
assertServiceException(() -> deptService.validateDeptList(ids), DEPT_NOT_ENABLE, deptDO.getName());
|
||||
}
|
||||
|
||||
@SafeVarargs
|
||||
private static DeptDO randomDeptDO(Consumer<DeptDO>... consumers) {
|
||||
Consumer<DeptDO> consumer = (o) -> {
|
||||
o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围
|
||||
};
|
||||
return randomPojo(DeptDO.class, ArrayUtils.append(consumer, consumers));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,14 +7,12 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccou
|
|||
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.account.MailAccountUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailAccountDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.mail.MailAccountMapper;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
|
@ -23,14 +21,13 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
|||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* {@link MailAccountServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
* {@link MailAccountServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MailAccountServiceImpl.class)
|
||||
public class MailAccountServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
|
@ -42,23 +39,6 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
|
|||
|
||||
@MockBean
|
||||
private MailTemplateService mailTemplateService;
|
||||
@MockBean
|
||||
private MailProducer mailProducer;
|
||||
|
||||
@Test
|
||||
public void testInitLocalCache() {
|
||||
MailAccountDO accountDO1 = randomPojo(MailAccountDO.class);
|
||||
mailAccountMapper.insert(accountDO1);
|
||||
MailAccountDO accountDO02 = randomPojo(MailAccountDO.class);
|
||||
mailAccountMapper.insert(accountDO02);
|
||||
|
||||
// 调用
|
||||
mailAccountService.initLocalCache();
|
||||
// 断言 mailAccountCache 缓存
|
||||
Map<Long, MailAccountDO> mailAccountCache = mailAccountService.getMailAccountCache();
|
||||
assertPojoEquals(accountDO1, mailAccountCache.get(accountDO1.getId()));
|
||||
assertPojoEquals(accountDO02, mailAccountCache.get(accountDO02.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateMailAccount_success() {
|
||||
|
@ -72,7 +52,6 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
|
|||
// 校验记录的属性是否正确
|
||||
MailAccountDO mailAccount = mailAccountMapper.selectById(mailAccountId);
|
||||
assertPojoEquals(reqVO, mailAccount);
|
||||
verify(mailProducer).sendMailAccountRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -91,7 +70,6 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
|
|||
// 校验是否更新正确
|
||||
MailAccountDO mailAccount = mailAccountMapper.selectById(reqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(reqVO, mailAccount);
|
||||
verify(mailProducer).sendMailAccountRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -115,9 +93,8 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
|
|||
|
||||
// 调用
|
||||
mailAccountService.deleteMailAccount(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(mailAccountMapper.selectById(id));
|
||||
verify(mailProducer).sendMailAccountRefreshMessage();
|
||||
// 校验数据不存在了
|
||||
assertNull(mailAccountMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -125,7 +102,6 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
|
|||
// mock 数据
|
||||
MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class);
|
||||
mailAccountMapper.insert(dbMailAccount);// @Sql: 先插入出一条存在的数据
|
||||
mailAccountService.initLocalCache();
|
||||
// 准备参数
|
||||
Long id = dbMailAccount.getId();
|
||||
|
||||
|
@ -146,27 +122,27 @@ public class MailAccountServiceImplTest extends BaseDbUnitTest {
|
|||
|
||||
@Test
|
||||
public void testGetMailAccountPage() {
|
||||
// mock 数据
|
||||
MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class, o -> { // 等会查询到
|
||||
o.setMail("768@qq.com");
|
||||
o.setUsername("yunai");
|
||||
});
|
||||
mailAccountMapper.insert(dbMailAccount);
|
||||
// 测试 mail 不匹配
|
||||
mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setMail("788@qq.com")));
|
||||
// 测试 username 不匹配
|
||||
mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setUsername("tudou")));
|
||||
// 准备参数
|
||||
MailAccountPageReqVO reqVO = new MailAccountPageReqVO();
|
||||
reqVO.setMail("768");
|
||||
reqVO.setUsername("yu");
|
||||
// mock 数据
|
||||
MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class, o -> { // 等会查询到
|
||||
o.setMail("768@qq.com");
|
||||
o.setUsername("yunai");
|
||||
});
|
||||
mailAccountMapper.insert(dbMailAccount);
|
||||
// 测试 mail 不匹配
|
||||
mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setMail("788@qq.com")));
|
||||
// 测试 username 不匹配
|
||||
mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setUsername("tudou")));
|
||||
// 准备参数
|
||||
MailAccountPageReqVO reqVO = new MailAccountPageReqVO();
|
||||
reqVO.setMail("768");
|
||||
reqVO.setUsername("yu");
|
||||
|
||||
// 调用
|
||||
PageResult<MailAccountDO> pageResult = mailAccountService.getMailAccountPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbMailAccount, pageResult.getList().get(0));
|
||||
// 调用
|
||||
PageResult<MailAccountDO> pageResult = mailAccountService.getMailAccountPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbMailAccount, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -8,9 +8,7 @@ import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemp
|
|||
import cn.iocoder.yudao.module.system.controller.admin.mail.vo.template.MailTemplateUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.mail.MailTemplateDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.mail.MailTemplateMapper;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.mail.MailProducer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
@ -27,13 +25,12 @@ import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId
|
|||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_NOT_EXISTS;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* {@link MailTemplateServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
* {@link MailTemplateServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(MailTemplateServiceImpl.class)
|
||||
public class MailTemplateServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
|
@ -43,24 +40,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
@Resource
|
||||
private MailTemplateMapper mailTemplateMapper;
|
||||
|
||||
@MockBean
|
||||
private MailProducer mailProducer;
|
||||
|
||||
@Test
|
||||
public void testInitLocalCache() {
|
||||
MailTemplateDO templateDO01 = randomPojo(MailTemplateDO.class);
|
||||
mailTemplateMapper.insert(templateDO01);
|
||||
MailTemplateDO templateDO02 = randomPojo(MailTemplateDO.class);
|
||||
mailTemplateMapper.insert(templateDO02);
|
||||
|
||||
// 调用
|
||||
mailTemplateService.initLocalCache();
|
||||
// 断言 mailTemplateCache 缓存
|
||||
Map<String, MailTemplateDO> mailTemplateCache = mailTemplateService.getMailTemplateCache();
|
||||
assertPojoEquals(templateDO01, mailTemplateCache.get(templateDO01.getCode()));
|
||||
assertPojoEquals(templateDO02, mailTemplateCache.get(templateDO02.getCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateMailTemplate_success() {
|
||||
// 准备参数
|
||||
|
@ -73,7 +52,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
// 校验记录的属性是否正确
|
||||
MailTemplateDO mailTemplate = mailTemplateMapper.selectById(mailTemplateId);
|
||||
assertPojoEquals(reqVO, mailTemplate);
|
||||
verify(mailProducer).sendMailTemplateRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -91,7 +69,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
// 校验是否更新正确
|
||||
MailTemplateDO mailTemplate = mailTemplateMapper.selectById(reqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(reqVO, mailTemplate);
|
||||
verify(mailProducer).sendMailTemplateRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -115,7 +92,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
mailTemplateService.deleteMailTemplate(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(mailTemplateMapper.selectById(id));
|
||||
verify(mailProducer).sendMailTemplateRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -129,39 +105,39 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
|
||||
@Test
|
||||
public void testGetMailTemplatePage() {
|
||||
// mock 数据
|
||||
MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class, o -> { // 等会查询到
|
||||
o.setName("源码");
|
||||
o.setCode("test_01");
|
||||
o.setAccountId(1L);
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setCreateTime(buildTime(2023, 2, 3));
|
||||
});
|
||||
mailTemplateMapper.insert(dbMailTemplate);
|
||||
// 测试 name 不匹配
|
||||
mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setName("芋道")));
|
||||
// 测试 code 不匹配
|
||||
mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCode("test_02")));
|
||||
// 测试 accountId 不匹配
|
||||
mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setAccountId(2L)));
|
||||
// 测试 status 不匹配
|
||||
mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 测试 createTime 不匹配
|
||||
mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCreateTime(buildTime(2023, 1, 5))));
|
||||
// 准备参数
|
||||
MailTemplatePageReqVO reqVO = new MailTemplatePageReqVO();
|
||||
reqVO.setName("源");
|
||||
reqVO.setCode("est_01");
|
||||
reqVO.setAccountId(1L);
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 5));
|
||||
// mock 数据
|
||||
MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class, o -> { // 等会查询到
|
||||
o.setName("源码");
|
||||
o.setCode("test_01");
|
||||
o.setAccountId(1L);
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setCreateTime(buildTime(2023, 2, 3));
|
||||
});
|
||||
mailTemplateMapper.insert(dbMailTemplate);
|
||||
// 测试 name 不匹配
|
||||
mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setName("芋道")));
|
||||
// 测试 code 不匹配
|
||||
mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCode("test_02")));
|
||||
// 测试 accountId 不匹配
|
||||
mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setAccountId(2L)));
|
||||
// 测试 status 不匹配
|
||||
mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 测试 createTime 不匹配
|
||||
mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCreateTime(buildTime(2023, 1, 5))));
|
||||
// 准备参数
|
||||
MailTemplatePageReqVO reqVO = new MailTemplatePageReqVO();
|
||||
reqVO.setName("源");
|
||||
reqVO.setCode("est_01");
|
||||
reqVO.setAccountId(1L);
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 5));
|
||||
|
||||
// 调用
|
||||
PageResult<MailTemplateDO> pageResult = mailTemplateService.getMailTemplatePage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbMailTemplate, pageResult.getList().get(0));
|
||||
// 调用
|
||||
PageResult<MailTemplateDO> pageResult = mailTemplateService.getMailTemplatePage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbMailTemplate, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -199,7 +175,6 @@ public class MailTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
// mock 数据
|
||||
MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class);
|
||||
mailTemplateMapper.insert(dbMailTemplate);
|
||||
mailTemplateService.initLocalCache();
|
||||
// 准备参数
|
||||
String code = dbMailTemplate.getCode();
|
||||
|
||||
|
|
|
@ -8,9 +8,7 @@ import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.Notify
|
|||
import cn.iocoder.yudao.module.system.controller.admin.notify.vo.template.NotifyTemplateUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.notify.NotifyTemplateDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.notify.NotifyTemplateMapper;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.notify.NotifyProducer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
|
@ -25,13 +23,12 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
|
|||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_NOT_EXISTS;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
/**
|
||||
* {@link NotifyTemplateServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
* {@link NotifyTemplateServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(NotifyTemplateServiceImpl.class)
|
||||
public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
|
@ -41,9 +38,6 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
@Resource
|
||||
private NotifyTemplateMapper notifyTemplateMapper;
|
||||
|
||||
@MockBean
|
||||
private NotifyProducer notifyProducer;
|
||||
|
||||
@Test
|
||||
public void testCreateNotifyTemplate_success() {
|
||||
// 准备参数
|
||||
|
@ -57,7 +51,6 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
// 校验记录的属性是否正确
|
||||
NotifyTemplateDO notifyTemplate = notifyTemplateMapper.selectById(notifyTemplateId);
|
||||
assertPojoEquals(reqVO, notifyTemplate);
|
||||
verify(notifyProducer).sendNotifyTemplateRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -76,7 +69,6 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
// 校验是否更新正确
|
||||
NotifyTemplateDO notifyTemplate = notifyTemplateMapper.selectById(reqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(reqVO, notifyTemplate);
|
||||
verify(notifyProducer).sendNotifyTemplateRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -98,9 +90,8 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
|
||||
// 调用
|
||||
notifyTemplateService.deleteNotifyTemplate(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(notifyTemplateMapper.selectById(id));
|
||||
verify(notifyProducer).sendNotifyTemplateRefreshMessage();
|
||||
// 校验数据不存在了
|
||||
assertNull(notifyTemplateMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -114,35 +105,35 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
|
||||
@Test
|
||||
public void testGetNotifyTemplatePage() {
|
||||
// mock 数据
|
||||
NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class, o -> { // 等会查询到
|
||||
o.setName("芋头");
|
||||
o.setCode("test_01");
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setCreateTime(buildTime(2022, 2, 3));
|
||||
});
|
||||
notifyTemplateMapper.insert(dbNotifyTemplate);
|
||||
// 测试 name 不匹配
|
||||
notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setName("投")));
|
||||
// 测试 code 不匹配
|
||||
notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCode("test_02")));
|
||||
// 测试 status 不匹配
|
||||
notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 测试 createTime 不匹配
|
||||
notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCreateTime(buildTime(2022, 1, 5))));
|
||||
// 准备参数
|
||||
NotifyTemplatePageReqVO reqVO = new NotifyTemplatePageReqVO();
|
||||
reqVO.setName("芋");
|
||||
reqVO.setCode("est_01");
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
reqVO.setCreateTime(buildBetweenTime(2022, 2, 1, 2022, 2, 5));
|
||||
// mock 数据
|
||||
NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class, o -> { // 等会查询到
|
||||
o.setName("芋头");
|
||||
o.setCode("test_01");
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setCreateTime(buildTime(2022, 2, 3));
|
||||
});
|
||||
notifyTemplateMapper.insert(dbNotifyTemplate);
|
||||
// 测试 name 不匹配
|
||||
notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setName("投")));
|
||||
// 测试 code 不匹配
|
||||
notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCode("test_02")));
|
||||
// 测试 status 不匹配
|
||||
notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 测试 createTime 不匹配
|
||||
notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCreateTime(buildTime(2022, 1, 5))));
|
||||
// 准备参数
|
||||
NotifyTemplatePageReqVO reqVO = new NotifyTemplatePageReqVO();
|
||||
reqVO.setName("芋");
|
||||
reqVO.setCode("est_01");
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
reqVO.setCreateTime(buildBetweenTime(2022, 2, 1, 2022, 2, 5));
|
||||
|
||||
// 调用
|
||||
PageResult<NotifyTemplateDO> pageResult = notifyTemplateService.getNotifyTemplatePage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbNotifyTemplate, pageResult.getList().get(0));
|
||||
// 调用
|
||||
PageResult<NotifyTemplateDO> pageResult = notifyTemplateService.getNotifyTemplatePage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbNotifyTemplate, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -164,7 +155,6 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
// mock 数据
|
||||
NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class);
|
||||
notifyTemplateMapper.insert(dbNotifyTemplate);
|
||||
notifyTemplateService.initLocalCache();
|
||||
// 准备参数
|
||||
String code = dbNotifyTemplate.getCode();
|
||||
|
||||
|
@ -173,7 +163,7 @@ public class NotifyTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
// 断言
|
||||
assertPojoEquals(dbNotifyTemplate, notifyTemplate);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testFormatNotifyTemplateContent() {
|
||||
// 准备参数
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
package cn.iocoder.yudao.module.system.service.oauth2;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
|
@ -9,14 +9,12 @@ import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2Cl
|
|||
import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.client.OAuth2ClientUpdateReqVO;
|
||||
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2ClientDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.oauth2.OAuth2ClientMapper;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.auth.OAuth2ClientProducer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
import static cn.iocoder.yudao.framework.common.util.object.ObjectUtils.cloneIgnoreId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
|
@ -24,13 +22,14 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
|
|||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
|
||||
/**
|
||||
* {@link OAuth2ClientServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
* {@link OAuth2ClientServiceImpl} 的单元测试类
|
||||
*
|
||||
* @author 芋道源码
|
||||
*/
|
||||
@Import(OAuth2ClientServiceImpl.class)
|
||||
public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
|
||||
|
||||
|
@ -40,26 +39,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
|
|||
@Resource
|
||||
private OAuth2ClientMapper oauth2ClientMapper;
|
||||
|
||||
@MockBean
|
||||
private OAuth2ClientProducer oauth2ClientProducer;
|
||||
|
||||
@Test
|
||||
public void testInitLocalCache() {
|
||||
// mock 数据
|
||||
OAuth2ClientDO clientDO1 = randomPojo(OAuth2ClientDO.class);
|
||||
oauth2ClientMapper.insert(clientDO1);
|
||||
OAuth2ClientDO clientDO2 = randomPojo(OAuth2ClientDO.class);
|
||||
oauth2ClientMapper.insert(clientDO2);
|
||||
|
||||
// 调用
|
||||
oauth2ClientService.initLocalCache();
|
||||
// 断言 clientCache 缓存
|
||||
Map<String, OAuth2ClientDO> clientCache = oauth2ClientService.getClientCache();
|
||||
assertEquals(2, clientCache.size());
|
||||
assertPojoEquals(clientDO1, clientCache.get(clientDO1.getClientId()));
|
||||
assertPojoEquals(clientDO2, clientCache.get(clientDO2.getClientId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateOAuth2Client_success() {
|
||||
// 准备参数
|
||||
|
@ -73,7 +52,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
|
|||
// 校验记录的属性是否正确
|
||||
OAuth2ClientDO oAuth2Client = oauth2ClientMapper.selectById(oauth2ClientId);
|
||||
assertPojoEquals(reqVO, oAuth2Client);
|
||||
verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -92,7 +70,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
|
|||
// 校验是否更新正确
|
||||
OAuth2ClientDO oAuth2Client = oauth2ClientMapper.selectById(reqVO.getId()); // 获取最新的
|
||||
assertPojoEquals(reqVO, oAuth2Client);
|
||||
verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -116,7 +93,6 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
|
|||
oauth2ClientService.deleteOAuth2Client(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(oauth2ClientMapper.selectById(id));
|
||||
verify(oauth2ClientProducer).sendOAuth2ClientRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -167,62 +143,78 @@ public class OAuth2ClientServiceImplTest extends BaseDbUnitTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testGetOAuth2ClientPage() {
|
||||
// mock 数据
|
||||
OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class, o -> { // 等会查询到
|
||||
o.setName("潜龙");
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
});
|
||||
oauth2ClientMapper.insert(dbOAuth2Client);
|
||||
// 测试 name 不匹配
|
||||
oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setName("凤凰")));
|
||||
// 测试 status 不匹配
|
||||
oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 准备参数
|
||||
OAuth2ClientPageReqVO reqVO = new OAuth2ClientPageReqVO();
|
||||
reqVO.setName("龙");
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
public void testGetOAuth2ClientFromCache() {
|
||||
// mock 数据
|
||||
OAuth2ClientDO clientDO = randomPojo(OAuth2ClientDO.class);
|
||||
oauth2ClientMapper.insert(clientDO);
|
||||
// 准备参数
|
||||
String clientId = clientDO.getClientId();
|
||||
|
||||
// 调用
|
||||
PageResult<OAuth2ClientDO> pageResult = oauth2ClientService.getOAuth2ClientPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbOAuth2Client, pageResult.getList().get(0));
|
||||
// 调用,并断言
|
||||
OAuth2ClientDO dbClientDO = oauth2ClientService.getOAuth2ClientFromCache(clientId);
|
||||
assertPojoEquals(clientDO, dbClientDO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetOAuth2ClientPage() {
|
||||
// mock 数据
|
||||
OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class, o -> { // 等会查询到
|
||||
o.setName("潜龙");
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
});
|
||||
oauth2ClientMapper.insert(dbOAuth2Client);
|
||||
// 测试 name 不匹配
|
||||
oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setName("凤凰")));
|
||||
// 测试 status 不匹配
|
||||
oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 准备参数
|
||||
OAuth2ClientPageReqVO reqVO = new OAuth2ClientPageReqVO();
|
||||
reqVO.setName("龙");
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
|
||||
// 调用
|
||||
PageResult<OAuth2ClientDO> pageResult = oauth2ClientService.getOAuth2ClientPage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbOAuth2Client, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidOAuthClientFromCache() {
|
||||
// mock 方法
|
||||
OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId("default")
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
OAuth2ClientDO client02 = randomPojo(OAuth2ClientDO.class).setClientId("disable")
|
||||
.setStatus(CommonStatusEnum.DISABLE.getStatus());
|
||||
Map<String, OAuth2ClientDO> clientCache = MapUtil.<String, OAuth2ClientDO>builder()
|
||||
.put(client.getClientId(), client)
|
||||
.put(client02.getClientId(), client02).build();
|
||||
oauth2ClientService.setClientCache(clientCache);
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(OAuth2ClientServiceImpl.class)))
|
||||
.thenReturn(oauth2ClientService);
|
||||
|
||||
// 调用,并断言
|
||||
assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache(randomString(),
|
||||
null, null, null, null), OAUTH2_CLIENT_NOT_EXISTS);
|
||||
assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("disable",
|
||||
null, null, null, null), OAUTH2_CLIENT_DISABLE);
|
||||
assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
|
||||
randomString(), null, null, null), OAUTH2_CLIENT_CLIENT_SECRET_ERROR);
|
||||
assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
|
||||
null, randomString(), null, null), OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS);
|
||||
assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
|
||||
null, null, Collections.singleton(randomString()), null), OAUTH2_CLIENT_SCOPE_OVER);
|
||||
assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
|
||||
null, null, null, "test"), OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH, "test");
|
||||
// 成功调用(1:参数完整)
|
||||
OAuth2ClientDO result = oauth2ClientService.validOAuthClientFromCache(client.getClientId(), client.getSecret(),
|
||||
client.getAuthorizedGrantTypes().get(0), client.getScopes(), client.getRedirectUris().get(0));
|
||||
assertPojoEquals(client, result);
|
||||
// 成功调用(2:只有 clientId 参数)
|
||||
result = oauth2ClientService.validOAuthClientFromCache(client.getClientId());
|
||||
assertPojoEquals(client, result);
|
||||
// mock 方法
|
||||
OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId("default")
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
oauth2ClientMapper.insert(client);
|
||||
OAuth2ClientDO client02 = randomPojo(OAuth2ClientDO.class).setClientId("disable")
|
||||
.setStatus(CommonStatusEnum.DISABLE.getStatus());
|
||||
oauth2ClientMapper.insert(client02);
|
||||
|
||||
// 调用,并断言
|
||||
assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache(randomString(),
|
||||
null, null, null, null), OAUTH2_CLIENT_NOT_EXISTS);
|
||||
assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("disable",
|
||||
null, null, null, null), OAUTH2_CLIENT_DISABLE);
|
||||
assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
|
||||
randomString(), null, null, null), OAUTH2_CLIENT_CLIENT_SECRET_ERROR);
|
||||
assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
|
||||
null, randomString(), null, null), OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS);
|
||||
assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
|
||||
null, null, Collections.singleton(randomString()), null), OAUTH2_CLIENT_SCOPE_OVER);
|
||||
assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache("default",
|
||||
null, null, null, "test"), OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH, "test");
|
||||
// 成功调用(1:参数完整)
|
||||
OAuth2ClientDO result = oauth2ClientService.validOAuthClientFromCache(client.getClientId(), client.getSecret(),
|
||||
client.getAuthorizedGrantTypes().get(0), client.getScopes(), client.getRedirectUris().get(0));
|
||||
assertPojoEquals(client, result);
|
||||
// 成功调用(2:只有 clientId 参数)
|
||||
result = oauth2ClientService.validOAuthClientFromCache(client.getClientId());
|
||||
assertPojoEquals(client, result);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,10 +8,7 @@ import cn.iocoder.yudao.module.system.controller.admin.permission.vo.menu.MenuUp
|
|||
import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.permission.MenuMapper;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.MenuTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.permission.MenuProducer;
|
||||
import cn.iocoder.yudao.module.system.service.tenant.TenantService;
|
||||
import com.google.common.collect.LinkedListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
@ -26,8 +23,6 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
|
|||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;
|
||||
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
||||
import static java.util.Arrays.asList;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
|
@ -46,35 +41,12 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
|
|||
@MockBean
|
||||
private PermissionService permissionService;
|
||||
@MockBean
|
||||
private MenuProducer menuProducer;
|
||||
@MockBean
|
||||
private TenantService tenantService;
|
||||
|
||||
@Test
|
||||
public void testInitLocalCache_success() {
|
||||
MenuDO menuDO1 = randomPojo(MenuDO.class);
|
||||
menuMapper.insert(menuDO1);
|
||||
MenuDO menuDO2 = randomPojo(MenuDO.class);
|
||||
menuMapper.insert(menuDO2);
|
||||
|
||||
// 调用
|
||||
menuService.initLocalCache();
|
||||
// 校验 menuCache 缓存
|
||||
Map<Long, MenuDO> menuCache = menuService.getMenuCache();
|
||||
assertEquals(2, menuCache.size());
|
||||
assertPojoEquals(menuDO1, menuCache.get(menuDO1.getId()));
|
||||
assertPojoEquals(menuDO2, menuCache.get(menuDO2.getId()));
|
||||
// 校验 permissionMenuCache 缓存
|
||||
Multimap<String, MenuDO> permissionMenuCache = menuService.getPermissionMenuCache();
|
||||
assertEquals(2, permissionMenuCache.size());
|
||||
assertPojoEquals(menuDO1, permissionMenuCache.get(menuDO1.getPermission()));
|
||||
assertPojoEquals(menuDO2, permissionMenuCache.get(menuDO2.getPermission()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateMenu_success() {
|
||||
// mock 数据(构造父菜单)
|
||||
MenuDO menuDO = createMenuDO(MenuTypeEnum.MENU,
|
||||
MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU,
|
||||
"parent", 0L);
|
||||
menuMapper.insert(menuDO);
|
||||
Long parentId = menuDO.getId();
|
||||
|
@ -89,14 +61,12 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
|
|||
// 校验记录的属性是否正确
|
||||
MenuDO dbMenu = menuMapper.selectById(menuId);
|
||||
assertPojoEquals(reqVO, dbMenu);
|
||||
// 校验调用
|
||||
verify(menuProducer).sendMenuRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateMenu_success() {
|
||||
// mock 数据(构造父子菜单)
|
||||
MenuDO sonMenuDO = initParentAndSonMenu();
|
||||
MenuDO sonMenuDO = createParentAndSonMenu();
|
||||
Long sonId = sonMenuDO.getId();
|
||||
// 准备参数
|
||||
MenuUpdateReqVO reqVO = randomPojo(MenuUpdateReqVO.class, o -> {
|
||||
|
@ -111,8 +81,6 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
|
|||
// 校验记录的属性是否正确
|
||||
MenuDO dbMenu = menuMapper.selectById(sonId);
|
||||
assertPojoEquals(reqVO, dbMenu);
|
||||
// 校验调用
|
||||
verify(menuProducer).sendMenuRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -137,7 +105,6 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
|
|||
MenuDO dbMenuDO = menuMapper.selectById(id);
|
||||
assertNull(dbMenuDO);
|
||||
verify(permissionService).processMenuDeleted(id);
|
||||
verify(menuProducer).sendMenuRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -149,7 +116,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
|
|||
@Test
|
||||
public void testDeleteMenu_existChildren() {
|
||||
// mock 数据(构造父子菜单)
|
||||
MenuDO sonMenu = initParentAndSonMenu();
|
||||
MenuDO sonMenu = createParentAndSonMenu();
|
||||
// 准备参数
|
||||
Long parentId = sonMenu.getParentId();
|
||||
|
||||
|
@ -218,85 +185,6 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
|
|||
assertPojoEquals(menu100, result.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListMenusFromCache_withoutId() {
|
||||
// mock 缓存
|
||||
Map<Long, MenuDO> menuCache = new HashMap<>();
|
||||
// 可被匹配
|
||||
MenuDO menuDO = randomPojo(MenuDO.class, o -> o.setId(1L)
|
||||
.setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
menuCache.put(menuDO.getId(), menuDO);
|
||||
// 测试 type 不匹配
|
||||
menuCache.put(3L, randomPojo(MenuDO.class, o -> o.setId(3L)
|
||||
.setType(MenuTypeEnum.BUTTON.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus())));
|
||||
// 测试 status 不匹配
|
||||
menuCache.put(4L, randomPojo(MenuDO.class, o -> o.setId(4L)
|
||||
.setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
menuService.setMenuCache(menuCache);
|
||||
// 准备参数
|
||||
Collection<Integer> menuTypes = singletonList(MenuTypeEnum.MENU.getType());
|
||||
Collection<Integer> menusStatuses = singletonList(CommonStatusEnum.ENABLE.getStatus());
|
||||
|
||||
// 调用
|
||||
List<MenuDO> list = menuService.getMenuListFromCache(menuTypes, menusStatuses);
|
||||
// 断言
|
||||
assertEquals(1, list.size());
|
||||
assertPojoEquals(menuDO, list.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testListMenusFromCache_withId() {
|
||||
// mock 缓存
|
||||
Map<Long, MenuDO> menuCache = new HashMap<>();
|
||||
// 可被匹配
|
||||
MenuDO menuDO = randomPojo(MenuDO.class, o -> o.setId(1L)
|
||||
.setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
menuCache.put(menuDO.getId(), menuDO);
|
||||
// 测试 id 不匹配
|
||||
menuCache.put(2L, randomPojo(MenuDO.class, o -> o.setId(2L)
|
||||
.setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus())));
|
||||
// 测试 type 不匹配
|
||||
menuCache.put(3L, randomPojo(MenuDO.class, o -> o.setId(3L)
|
||||
.setType(MenuTypeEnum.BUTTON.getType()).setStatus(CommonStatusEnum.ENABLE.getStatus())));
|
||||
// 测试 status 不匹配
|
||||
menuCache.put(4L, randomPojo(MenuDO.class, o -> o.setId(4L)
|
||||
.setType(MenuTypeEnum.MENU.getType()).setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
menuService.setMenuCache(menuCache);
|
||||
// 准备参数
|
||||
Collection<Long> menuIds = asList(1L, 3L, 4L);
|
||||
Collection<Integer> menuTypes = singletonList(MenuTypeEnum.MENU.getType());
|
||||
Collection<Integer> menusStatuses = singletonList(CommonStatusEnum.ENABLE.getStatus());
|
||||
|
||||
// 调用
|
||||
List<MenuDO> list = menuService.getMenuListFromCache(menuIds, menuTypes, menusStatuses);
|
||||
// 断言
|
||||
assertEquals(1, list.size());
|
||||
assertPojoEquals(menuDO, list.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMenuListByPermissionFromCache() {
|
||||
// mock 缓存
|
||||
Multimap<String, MenuDO> permissionMenuCache = LinkedListMultimap.create();
|
||||
// 可被匹配
|
||||
MenuDO menuDO01 = randomPojo(MenuDO.class, o -> o.setId(1L).setPermission("123"));
|
||||
permissionMenuCache.put(menuDO01.getPermission(), menuDO01);
|
||||
MenuDO menuDO02 = randomPojo(MenuDO.class, o -> o.setId(2L).setPermission("123"));
|
||||
permissionMenuCache.put(menuDO02.getPermission(), menuDO02);
|
||||
// 不可匹配
|
||||
permissionMenuCache.put("456", randomPojo(MenuDO.class, o -> o.setId(3L).setPermission("456")));
|
||||
menuService.setPermissionMenuCache(permissionMenuCache);
|
||||
// 准备参数
|
||||
String permission = "123";
|
||||
|
||||
// 调用
|
||||
List<MenuDO> list = menuService.getMenuListByPermissionFromCache(permission);
|
||||
// 断言
|
||||
assertEquals(2, list.size());
|
||||
assertPojoEquals(menuDO01, list.get(0));
|
||||
assertPojoEquals(menuDO02, list.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMenu() {
|
||||
// mock 数据
|
||||
|
@ -314,7 +202,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
|
|||
@Test
|
||||
public void testValidateParentMenu_success() {
|
||||
// mock 数据
|
||||
MenuDO menuDO = createMenuDO(MenuTypeEnum.MENU, "parent", 0L);
|
||||
MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU, "parent", 0L);
|
||||
menuMapper.insert(menuDO);
|
||||
// 准备参数
|
||||
Long parentId = menuDO.getId();
|
||||
|
@ -340,7 +228,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
|
|||
@Test
|
||||
public void testValidateParentMenu_parentTypeError() {
|
||||
// mock 数据
|
||||
MenuDO menuDO = createMenuDO(MenuTypeEnum.BUTTON, "parent", 0L);
|
||||
MenuDO menuDO = buildMenuDO(MenuTypeEnum.BUTTON, "parent", 0L);
|
||||
menuMapper.insert(menuDO);
|
||||
// 准备参数
|
||||
Long parentId = menuDO.getId();
|
||||
|
@ -353,7 +241,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
|
|||
@Test
|
||||
public void testValidateMenu_success() {
|
||||
// mock 父子菜单
|
||||
MenuDO sonMenu = initParentAndSonMenu();
|
||||
MenuDO sonMenu = createParentAndSonMenu();
|
||||
// 准备参数
|
||||
Long parentId = sonMenu.getParentId();
|
||||
Long otherSonMenuId = randomLongId();
|
||||
|
@ -366,7 +254,7 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
|
|||
@Test
|
||||
public void testValidateMenu_sonMenuNameDuplicate() {
|
||||
// mock 父子菜单
|
||||
MenuDO sonMenu = initParentAndSonMenu();
|
||||
MenuDO sonMenu = createParentAndSonMenu();
|
||||
// 准备参数
|
||||
Long parentId = sonMenu.getParentId();
|
||||
Long otherSonMenuId = randomLongId();
|
||||
|
@ -380,26 +268,26 @@ public class MenuServiceImplTest extends BaseDbUnitTest {
|
|||
// ====================== 初始化方法 ======================
|
||||
|
||||
/**
|
||||
* 构造父子菜单,返回子菜单
|
||||
* 插入父子菜单,返回子菜单
|
||||
*
|
||||
* @return 子菜单
|
||||
*/
|
||||
private MenuDO initParentAndSonMenu() {
|
||||
private MenuDO createParentAndSonMenu() {
|
||||
// 构造父子菜单
|
||||
MenuDO parentMenuDO = createMenuDO(MenuTypeEnum.MENU, "parent", ID_ROOT);
|
||||
MenuDO parentMenuDO = buildMenuDO(MenuTypeEnum.MENU, "parent", ID_ROOT);
|
||||
menuMapper.insert(parentMenuDO);
|
||||
// 构建子菜单
|
||||
MenuDO sonMenuDO = createMenuDO(MenuTypeEnum.MENU, "testSonName",
|
||||
MenuDO sonMenuDO = buildMenuDO(MenuTypeEnum.MENU, "testSonName",
|
||||
parentMenuDO.getParentId());
|
||||
menuMapper.insert(sonMenuDO);
|
||||
return sonMenuDO;
|
||||
}
|
||||
|
||||
private MenuDO createMenuDO(MenuTypeEnum type, String name, Long parentId) {
|
||||
return createMenuDO(type, name, parentId, randomCommonStatus());
|
||||
private MenuDO buildMenuDO(MenuTypeEnum type, String name, Long parentId) {
|
||||
return buildMenuDO(type, name, parentId, randomCommonStatus());
|
||||
}
|
||||
|
||||
private MenuDO createMenuDO(MenuTypeEnum type, String name, Long parentId, Integer status) {
|
||||
private MenuDO buildMenuDO(MenuTypeEnum type, String name, Long parentId, Integer status) {
|
||||
return randomPojo(MenuDO.class, o -> o.setId(null).setName(name).setParentId(parentId)
|
||||
.setType(type.getType()).setStatus(status));
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package cn.iocoder.yudao.module.system.service.permission;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
import cn.iocoder.yudao.module.system.api.permission.dto.DeptDataPermissionRespDTO;
|
||||
|
@ -14,32 +14,28 @@ import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
|
|||
import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper;
|
||||
import cn.iocoder.yudao.module.system.enums.permission.DataScopeEnum;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.permission.PermissionProducer;
|
||||
import cn.iocoder.yudao.module.system.service.dept.DeptService;
|
||||
import cn.iocoder.yudao.module.system.service.user.AdminUserService;
|
||||
import com.google.common.collect.ImmutableMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.hutool.core.collection.ListUtil.toList;
|
||||
import static cn.iocoder.yudao.framework.common.util.collection.SetUtils.asSet;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertPojoEquals;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.*;
|
||||
import static java.util.Arrays.asList;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
|
||||
import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
|
||||
import static java.util.Collections.singleton;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
@Import({PermissionServiceImpl.class})
|
||||
public class PermissionServiceTest extends BaseDbUnitTest {
|
||||
|
@ -61,131 +57,74 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
|||
@MockBean
|
||||
private AdminUserService userService;
|
||||
|
||||
@MockBean
|
||||
private PermissionProducer permissionProducer;
|
||||
|
||||
@Test
|
||||
public void testInitLocalCacheForRoleMenu() {
|
||||
// mock 数据
|
||||
RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(10L));
|
||||
roleMenuMapper.insert(roleMenuDO01);
|
||||
RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(1L).setMenuId(20L));
|
||||
roleMenuMapper.insert(roleMenuDO02);
|
||||
public void testHasAnyPermissions_superAdmin() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 调用
|
||||
permissionService.initLocalCacheForRoleMenu();
|
||||
// 断言 roleMenuCache 缓存
|
||||
assertEquals(1, permissionService.getRoleMenuCache().keySet().size());
|
||||
assertEquals(asList(10L, 20L), permissionService.getRoleMenuCache().get(1L));
|
||||
// 断言 menuRoleCache 缓存
|
||||
assertEquals(2, permissionService.getMenuRoleCache().size());
|
||||
assertEquals(singletonList(1L), permissionService.getMenuRoleCache().get(10L));
|
||||
assertEquals(singletonList(1L), permissionService.getMenuRoleCache().get(20L));
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
String[] roles = new String[]{"system:user:query", "system:user:create"};
|
||||
// mock 用户登录的角色
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
|
||||
RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));
|
||||
// mock 其它方法
|
||||
when(roleService.hasAnySuperAdmin(eq(asSet(100L)))).thenReturn(true);
|
||||
|
||||
// 调用,并断言
|
||||
assertTrue(permissionService.hasAnyPermissions(userId, roles));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInitLocalCacheForUserRole() {
|
||||
// mock 数据
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
|
||||
userRoleMapper.insert(roleMenuDO02);
|
||||
public void testHasAnyPermissions_normal() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 调用
|
||||
permissionService.initLocalCacheForUserRole();
|
||||
// 断言 roleMenuCache 缓存
|
||||
assertEquals(1, permissionService.getUserRoleCache().size());
|
||||
assertEquals(asSet(10L, 20L), permissionService.getUserRoleCache().get(1L));
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
String[] roles = new String[]{"system:user:query", "system:user:create"};
|
||||
// mock 用户登录的角色
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
|
||||
RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));
|
||||
// mock 菜单
|
||||
Long menuId = 1000L;
|
||||
when(menuService.getMenuIdListByPermissionFromCache(
|
||||
eq("system:user:create"))).thenReturn(singletonList(menuId));
|
||||
roleMenuMapper.insert(randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1000L));
|
||||
|
||||
// 调用,并断言
|
||||
assertTrue(permissionService.hasAnyPermissions(userId, roles));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleMenuListFromCache_superAdmin() {
|
||||
// 准备参数
|
||||
Collection<Long> roleIds = singletonList(100L);
|
||||
Collection<Integer> menuTypes = asList(2, 3);
|
||||
Collection<Integer> menusStatuses = asList(0, 1);
|
||||
// mock 方法
|
||||
List<RoleDO> roleList = singletonList(randomPojo(RoleDO.class, o -> o.setId(100L)));
|
||||
when(roleService.getRoleListFromCache(eq(roleIds))).thenReturn(roleList);
|
||||
when(roleService.hasAnySuperAdmin(same(roleList))).thenReturn(true);
|
||||
List<MenuDO> menuList = randomPojoList(MenuDO.class);
|
||||
when(menuService.getMenuListFromCache(eq(menuTypes), eq(menusStatuses))).thenReturn(menuList);
|
||||
public void testHasAnyRoles() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 调用
|
||||
List<MenuDO> result = permissionService.getRoleMenuListFromCache(roleIds, menuTypes, menusStatuses);
|
||||
// 断言
|
||||
assertSame(menuList, result);
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
String[] roles = new String[]{"yunai", "tudou"};
|
||||
// mock 用户与角色的缓存
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
|
||||
RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L).setCode("tudou")
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));
|
||||
|
||||
// 调用,并断言
|
||||
assertTrue(permissionService.hasAnyRoles(userId, roles));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleMenuListFromCache_normal() {
|
||||
// 准备参数
|
||||
Collection<Long> roleIds = asSet(100L, 200L);
|
||||
Collection<Integer> menuTypes = asList(2, 3);
|
||||
Collection<Integer> menusStatuses = asList(0, 1);
|
||||
// mock 方法
|
||||
Multimap<Long, Long> roleMenuCache = ImmutableMultimap.<Long, Long>builder().put(100L, 1000L)
|
||||
.put(200L, 2000L).put(200L, 2001L).build();
|
||||
permissionService.setRoleMenuCache(roleMenuCache);
|
||||
List<MenuDO> menuList = randomPojoList(MenuDO.class);
|
||||
when(menuService.getMenuListFromCache(eq(asList(1000L, 2000L, 2001L)), eq(menuTypes), eq(menusStatuses))).thenReturn(menuList);
|
||||
|
||||
// 调用
|
||||
List<MenuDO> result = permissionService.getRoleMenuListFromCache(roleIds, menuTypes, menusStatuses);
|
||||
// 断言
|
||||
assertSame(menuList, result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserRoleIdsFromCache() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
Collection<Integer> roleStatuses = singleton(CommonStatusEnum.ENABLE.getStatus());
|
||||
// mock 方法
|
||||
Map<Long, Set<Long>> userRoleCache = MapUtil.<Long, Set<Long>>builder()
|
||||
.put(1L, asSet(10L, 20L)).build();
|
||||
permissionService.setUserRoleCache(userRoleCache);
|
||||
RoleDO roleDO01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleFromCache(eq(10L))).thenReturn(roleDO01);
|
||||
RoleDO roleDO02 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
||||
when(roleService.getRoleFromCache(eq(20L))).thenReturn(roleDO02);
|
||||
|
||||
// 调用
|
||||
Set<Long> roleIds = permissionService.getUserRoleIdsFromCache(userId, roleStatuses);
|
||||
// 断言
|
||||
assertEquals(asSet(10L), roleIds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleMenuIds_superAdmin() {
|
||||
// 准备参数
|
||||
Long roleId = 100L;
|
||||
// mock 方法
|
||||
when(roleService.hasAnySuperAdmin(eq(singleton(100L)))).thenReturn(true);
|
||||
List<MenuDO> menuList = singletonList(randomPojo(MenuDO.class).setId(1L));
|
||||
when(menuService.getMenuList()).thenReturn(menuList);
|
||||
|
||||
// 调用
|
||||
Set<Long> menuIds = permissionService.getRoleMenuIds(roleId);
|
||||
// 断言
|
||||
assertEquals(singleton(1L), menuIds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleMenuIds_normal() {
|
||||
// 准备参数
|
||||
Long roleId = 100L;
|
||||
// mock 数据
|
||||
RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1L);
|
||||
roleMenuMapper.insert(roleMenu01);
|
||||
RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(2L);
|
||||
roleMenuMapper.insert(roleMenu02);
|
||||
|
||||
// 调用
|
||||
Set<Long> menuIds = permissionService.getRoleMenuIds(roleId);
|
||||
// 断言
|
||||
assertEquals(asSet(1L, 2L), menuIds);
|
||||
}
|
||||
// ========== 角色-菜单的相关方法 ==========
|
||||
|
||||
@Test
|
||||
public void testAssignRoleMenu() {
|
||||
|
@ -207,75 +146,6 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
|||
assertEquals(200L, roleMenuList.get(0).getMenuId());
|
||||
assertEquals(1L, roleMenuList.get(1).getRoleId());
|
||||
assertEquals(300L, roleMenuList.get(1).getMenuId());
|
||||
verify(permissionProducer).sendRoleMenuRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAssignUserRole() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
Set<Long> roleIds = asSet(200L, 300L);
|
||||
// mock 数据
|
||||
UserRoleDO userRole01 = randomPojo(UserRoleDO.class).setUserId(1L).setRoleId(100L);
|
||||
userRoleMapper.insert(userRole01);
|
||||
UserRoleDO userRole02 = randomPojo(UserRoleDO.class).setUserId(1L).setRoleId(200L);
|
||||
userRoleMapper.insert(userRole02);
|
||||
|
||||
// 调用
|
||||
permissionService.assignUserRole(userId, roleIds);
|
||||
// 断言
|
||||
List<UserRoleDO> userRoleDOList = userRoleMapper.selectList();
|
||||
assertEquals(2, userRoleDOList.size());
|
||||
assertEquals(1L, userRoleDOList.get(0).getUserId());
|
||||
assertEquals(200L, userRoleDOList.get(0).getRoleId());
|
||||
assertEquals(1L, userRoleDOList.get(1).getUserId());
|
||||
assertEquals(300L, userRoleDOList.get(1).getRoleId());
|
||||
verify(permissionProducer).sendUserRoleRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserRoleIdListByUserId() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 数据
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
|
||||
userRoleMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
Set<Long> result = permissionService.getUserRoleIdListByUserId(userId);
|
||||
// 断言
|
||||
assertEquals(asSet(10L, 20L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetUserRoleIdListByRoleIds() {
|
||||
// 准备参数
|
||||
Collection<Long> roleIds = asSet(10L, 20L);
|
||||
// mock 数据
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(2L).setRoleId(20L));
|
||||
userRoleMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
Set<Long> result = permissionService.getUserRoleIdListByRoleIds(roleIds);
|
||||
// 断言
|
||||
assertEquals(asSet(1L, 2L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAssignRoleDataScope() {
|
||||
// 准备参数
|
||||
Long roleId = 1L;
|
||||
Integer dataScope = 2;
|
||||
Set<Long> dataScopeDeptIds = asSet(10L, 20L);
|
||||
|
||||
// 调用
|
||||
permissionService.assignRoleDataScope(roleId, dataScope, dataScopeDeptIds);
|
||||
// 断言
|
||||
verify(roleService).updateRoleDataScope(eq(roleId), eq(dataScope), eq(dataScopeDeptIds));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -303,9 +173,6 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
|||
List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();
|
||||
assertEquals(1, dbUserRoles.size());
|
||||
assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
|
||||
// 断言调用
|
||||
verify(permissionProducer).sendRoleMenuRefreshMessage();
|
||||
verify(permissionProducer).sendUserRoleRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -324,8 +191,77 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
|||
List<RoleMenuDO> dbRoleMenus = roleMenuMapper.selectList();
|
||||
assertEquals(1, dbRoleMenus.size());
|
||||
assertPojoEquals(dbRoleMenus.get(0), roleMenuDO02);
|
||||
// 断言调用
|
||||
verify(permissionProducer).sendRoleMenuRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleMenuIds_superAdmin() {
|
||||
// 准备参数
|
||||
Long roleId = 100L;
|
||||
// mock 方法
|
||||
when(roleService.hasAnySuperAdmin(eq(singleton(100L)))).thenReturn(true);
|
||||
List<MenuDO> menuList = singletonList(randomPojo(MenuDO.class).setId(1L));
|
||||
when(menuService.getMenuList()).thenReturn(menuList);
|
||||
|
||||
// 调用
|
||||
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(roleId);
|
||||
// 断言
|
||||
assertEquals(singleton(1L), menuIds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleMenuIds_normal() {
|
||||
// 准备参数
|
||||
Long roleId = 100L;
|
||||
// mock 数据
|
||||
RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1L);
|
||||
roleMenuMapper.insert(roleMenu01);
|
||||
RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(2L);
|
||||
roleMenuMapper.insert(roleMenu02);
|
||||
|
||||
// 调用
|
||||
Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(roleId);
|
||||
// 断言
|
||||
assertEquals(asSet(1L, 2L), menuIds);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetMenuRoleIdListByMenuIdFromCache() {
|
||||
// 准备参数
|
||||
Long menuId = 1L;
|
||||
// mock 数据
|
||||
RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1L);
|
||||
roleMenuMapper.insert(roleMenu01);
|
||||
RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(200L).setMenuId(1L);
|
||||
roleMenuMapper.insert(roleMenu02);
|
||||
|
||||
// 调用
|
||||
Set<Long> roleIds = permissionService.getMenuRoleIdListByMenuIdFromCache(menuId);
|
||||
// 断言
|
||||
assertEquals(asSet(100L, 200L), roleIds);
|
||||
}
|
||||
|
||||
// ========== 用户-角色的相关方法 ==========
|
||||
|
||||
@Test
|
||||
public void testAssignUserRole() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
Set<Long> roleIds = asSet(200L, 300L);
|
||||
// mock 数据
|
||||
UserRoleDO userRole01 = randomPojo(UserRoleDO.class).setUserId(1L).setRoleId(100L);
|
||||
userRoleMapper.insert(userRole01);
|
||||
UserRoleDO userRole02 = randomPojo(UserRoleDO.class).setUserId(1L).setRoleId(200L);
|
||||
userRoleMapper.insert(userRole02);
|
||||
|
||||
// 调用
|
||||
permissionService.assignUserRole(userId, roleIds);
|
||||
// 断言
|
||||
List<UserRoleDO> userRoleDOList = userRoleMapper.selectList();
|
||||
assertEquals(2, userRoleDOList.size());
|
||||
assertEquals(1L, userRoleDOList.get(0).getUserId());
|
||||
assertEquals(200L, userRoleDOList.get(0).getRoleId());
|
||||
assertEquals(1L, userRoleDOList.get(1).getUserId());
|
||||
assertEquals(300L, userRoleDOList.get(1).getRoleId());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -344,202 +280,248 @@ public class PermissionServiceTest extends BaseDbUnitTest {
|
|||
List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();
|
||||
assertEquals(1, dbUserRoles.size());
|
||||
assertPojoEquals(dbUserRoles.get(0), userRoleDO02);
|
||||
// 断言调用
|
||||
verify(permissionProducer).sendUserRoleRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasAnyPermissions_superAdmin() {
|
||||
public void testGetUserRoleIdListByUserId() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
String[] roles = new String[]{"system:user:query", "system:user:create"};
|
||||
// mock 用户与角色的缓存
|
||||
permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(100L)).build());
|
||||
RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleFromCache(eq(100L))).thenReturn(role);
|
||||
// mock 其它方法
|
||||
when(roleService.hasAnySuperAdmin(eq(asSet(100L)))).thenReturn(true);
|
||||
// mock 数据
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
|
||||
userRoleMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
boolean has = permissionService.hasAnyPermissions(userId, roles);
|
||||
Set<Long> result = permissionService.getUserRoleIdListByUserId(userId);
|
||||
// 断言
|
||||
assertTrue(has);
|
||||
assertEquals(asSet(10L, 20L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasAnyPermissions_normal() {
|
||||
public void testGetUserRoleIdListByUserIdFromCache() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
String[] roles = new String[]{"system:user:query", "system:user:create"};
|
||||
// mock 用户与角色的缓存
|
||||
permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(100L)).build());
|
||||
RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleFromCache(eq(100L))).thenReturn(role);
|
||||
// mock 其它方法
|
||||
MenuDO menu = randomPojo(MenuDO.class, o -> o.setId(1000L));
|
||||
when(menuService.getMenuListByPermissionFromCache(eq("system:user:create"))).thenReturn(singletonList(menu));
|
||||
permissionService.setMenuRoleCache(ImmutableMultimap.<Long, Long>builder().put(1000L, 100L).build());
|
||||
|
||||
// mock 数据
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
|
||||
userRoleMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
boolean has = permissionService.hasAnyPermissions(userId, roles);
|
||||
Set<Long> result = permissionService.getUserRoleIdListByUserIdFromCache(userId);
|
||||
// 断言
|
||||
assertTrue(has);
|
||||
assertEquals(asSet(10L, 20L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasAnyRoles_superAdmin() {
|
||||
public void testGetUserRoleIdsFromCache() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
String[] roles = new String[]{"yunai", "tudou"};
|
||||
// mock 用户与角色的缓存
|
||||
permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(100L)).build());
|
||||
RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleFromCache(eq(100L))).thenReturn(role);
|
||||
// mock 其它方法
|
||||
when(roleService.hasAnySuperAdmin(eq(asSet(100L)))).thenReturn(true);
|
||||
// mock 数据
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));
|
||||
userRoleMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
boolean has = permissionService.hasAnyRoles(userId, roles);
|
||||
Set<Long> result = permissionService.getUserRoleIdListByUserIdFromCache(userId);
|
||||
// 断言
|
||||
assertTrue(has);
|
||||
assertEquals(asSet(10L, 20L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHasAnyRoles_normal() {
|
||||
public void testGetUserRoleIdListByRoleId() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
String[] roles = new String[]{"yunai", "tudou"};
|
||||
// mock 用户与角色的缓存
|
||||
permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(100L)).build());
|
||||
RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L).setCode("yunai")
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleFromCache(eq(100L))).thenReturn(role);
|
||||
// mock 其它方法
|
||||
when(roleService.getRoleListFromCache(eq(asSet(100L)))).thenReturn(singletonList(role));
|
||||
Collection<Long> roleIds = asSet(10L, 20L);
|
||||
// mock 数据
|
||||
UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));
|
||||
userRoleMapper.insert(userRoleDO01);
|
||||
UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(2L).setRoleId(20L));
|
||||
userRoleMapper.insert(roleMenuDO02);
|
||||
|
||||
// 调用
|
||||
boolean has = permissionService.hasAnyRoles(userId, roles);
|
||||
Set<Long> result = permissionService.getUserRoleIdListByRoleId(roleIds);
|
||||
// 断言
|
||||
assertTrue(has);
|
||||
assertEquals(asSet(1L, 2L), result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetEnableUserRoleListByUserIdFromCache() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户登录的角色
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(200L));
|
||||
RoleDO role01 = randomPojo(RoleDO.class, o -> o.setId(100L)
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
RoleDO role02 = randomPojo(RoleDO.class, o -> o.setId(200L)
|
||||
.setStatus(CommonStatusEnum.DISABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(asSet(100L, 200L))))
|
||||
.thenReturn(toList(role01, role02));
|
||||
|
||||
// 调用
|
||||
List<RoleDO> result = permissionService.getEnableUserRoleListByUserIdFromCache(userId);
|
||||
// 断言
|
||||
assertEquals(1, result.size());
|
||||
assertPojoEquals(role01, result.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 用户-部门的相关方法 ==========
|
||||
|
||||
@Test
|
||||
public void testAssignRoleDataScope() {
|
||||
// 准备参数
|
||||
Long roleId = 1L;
|
||||
Integer dataScope = 2;
|
||||
Set<Long> dataScopeDeptIds = asSet(10L, 20L);
|
||||
|
||||
// 调用
|
||||
permissionService.assignRoleDataScope(roleId, dataScope, dataScopeDeptIds);
|
||||
// 断言
|
||||
verify(roleService).updateRoleDataScope(eq(roleId), eq(dataScope), eq(dataScopeDeptIds));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeptDataPermission_All() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
|
||||
when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertTrue(result.getAll());
|
||||
assertFalse(result.getSelf());
|
||||
assertTrue(CollUtil.isEmpty(result.getDeptIds()));
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertTrue(result.getAll());
|
||||
assertFalse(result.getSelf());
|
||||
assertTrue(CollUtil.isEmpty(result.getDeptIds()));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeptDataPermission_DeptCustom() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
|
||||
when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
|
||||
// mock 部门的返回
|
||||
when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertFalse(result.getAll());
|
||||
assertFalse(result.getSelf());
|
||||
assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size());
|
||||
assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds()));
|
||||
assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
|
||||
// mock 部门的返回
|
||||
when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),
|
||||
null, null); // 最后返回 null 的目的,看看会不会重复调用
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertFalse(result.getAll());
|
||||
assertFalse(result.getSelf());
|
||||
assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size());
|
||||
assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds()));
|
||||
assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeptDataPermission_DeptOnly() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
|
||||
when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
|
||||
// mock 部门的返回
|
||||
when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertFalse(result.getAll());
|
||||
assertFalse(result.getSelf());
|
||||
assertEquals(1, result.getDeptIds().size());
|
||||
assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
|
||||
// mock 部门的返回
|
||||
when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),
|
||||
null, null); // 最后返回 null 的目的,看看会不会重复调用
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertFalse(result.getAll());
|
||||
assertFalse(result.getSelf());
|
||||
assertEquals(1, result.getDeptIds().size());
|
||||
assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeptDataPermission_DeptAndChild() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
|
||||
when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
|
||||
// mock 部门的返回
|
||||
when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L), null, null); // 最后返回 null 的目的,看看会不会重复调用
|
||||
// mock 方法(部门)
|
||||
DeptDO deptDO = randomPojo(DeptDO.class);
|
||||
when(deptService.getDeptListByParentIdFromCache(eq(3L), eq(true)))
|
||||
.thenReturn(singletonList(deptDO));
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertFalse(result.getAll());
|
||||
assertFalse(result.getSelf());
|
||||
assertEquals(2, result.getDeptIds().size());
|
||||
assertTrue(CollUtil.contains(result.getDeptIds(), deptDO.getId()));
|
||||
assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
|
||||
// mock 部门的返回
|
||||
when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),
|
||||
null, null); // 最后返回 null 的目的,看看会不会重复调用
|
||||
// mock 方法(部门)
|
||||
DeptDO deptDO = randomPojo(DeptDO.class);
|
||||
when(deptService.getChildDeptIdListFromCache(eq(3L))).thenReturn(singleton(deptDO.getId()));
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertFalse(result.getAll());
|
||||
assertFalse(result.getSelf());
|
||||
assertEquals(2, result.getDeptIds().size());
|
||||
assertTrue(CollUtil.contains(result.getDeptIds(), deptDO.getId()));
|
||||
assertTrue(CollUtil.contains(result.getDeptIds(), 3L));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetDeptDataPermission_Self() {
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
permissionService.setUserRoleCache(MapUtil.<Long, Set<Long>>builder().put(1L, asSet(2L)).build());
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(singletonList(roleDO));
|
||||
when(roleService.getRoleFromCache(eq(2L))).thenReturn(roleDO);
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))
|
||||
.thenReturn(permissionService);
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertFalse(result.getAll());
|
||||
assertTrue(result.getSelf());
|
||||
assertTrue(CollUtil.isEmpty(result.getDeptIds()));
|
||||
// 准备参数
|
||||
Long userId = 1L;
|
||||
// mock 用户的角色编号
|
||||
userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));
|
||||
// mock 获得用户的角色
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope())
|
||||
.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));
|
||||
|
||||
// 调用
|
||||
DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);
|
||||
// 断言
|
||||
assertFalse(result.getAll());
|
||||
assertTrue(result.getSelf());
|
||||
assertTrue(CollUtil.isEmpty(result.getDeptIds()));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package cn.iocoder.yudao.module.system.service.permission;
|
||||
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
|
||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||
import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
|
||||
|
@ -11,15 +12,14 @@ 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.RoleTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.permission.RoleProducer;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import static cn.hutool.core.util.RandomUtil.randomEle;
|
||||
|
@ -33,6 +33,8 @@ import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
|
|||
import static java.util.Collections.singleton;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@Import(RoleServiceImpl.class)
|
||||
|
@ -46,26 +48,9 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
|
|||
|
||||
@MockBean
|
||||
private PermissionService permissionService;
|
||||
@MockBean
|
||||
private RoleProducer roleProducer;
|
||||
|
||||
@Test
|
||||
public void testInitLocalCache() {
|
||||
RoleDO roleDO1 = randomPojo(RoleDO.class);
|
||||
roleMapper.insert(roleDO1);
|
||||
RoleDO roleDO2 = randomPojo(RoleDO.class);
|
||||
roleMapper.insert(roleDO2);
|
||||
|
||||
// 调用
|
||||
roleService.initLocalCache();
|
||||
// 断言 roleCache 缓存
|
||||
Map<Long, RoleDO> roleCache = roleService.getRoleCache();
|
||||
assertPojoEquals(roleDO1, roleCache.get(roleDO1.getId()));
|
||||
assertPojoEquals(roleDO2, roleCache.get(roleDO2.getId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCreateRole_success() {
|
||||
public void testCreateRole() {
|
||||
// 准备参数
|
||||
RoleCreateReqVO reqVO = randomPojo(RoleCreateReqVO.class);
|
||||
|
||||
|
@ -77,12 +62,10 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
|
|||
assertEquals(RoleTypeEnum.CUSTOM.getType(), roleDO.getType());
|
||||
assertEquals(CommonStatusEnum.ENABLE.getStatus(), roleDO.getStatus());
|
||||
assertEquals(DataScopeEnum.ALL.getScope(), roleDO.getDataScope());
|
||||
// verify 发送刷新消息
|
||||
verify(roleProducer).sendRoleRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRole_success() {
|
||||
public void testUpdateRole() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));
|
||||
roleMapper.insert(roleDO);
|
||||
|
@ -95,12 +78,10 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
|
|||
// 断言
|
||||
RoleDO newRoleDO = roleMapper.selectById(id);
|
||||
assertPojoEquals(reqVO, newRoleDO);
|
||||
// verify 发送刷新消息
|
||||
verify(roleProducer).sendRoleRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRoleStatus_success() {
|
||||
public void testUpdateRoleStatus() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())
|
||||
.setType(RoleTypeEnum.CUSTOM.getType()));
|
||||
|
@ -114,12 +95,10 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
|
|||
// 断言
|
||||
RoleDO dbRoleDO = roleMapper.selectById(roleId);
|
||||
assertEquals(CommonStatusEnum.DISABLE.getStatus(), dbRoleDO.getStatus());
|
||||
// verify 发送刷新消息
|
||||
verify(roleProducer).sendRoleRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUpdateRoleDataScope_success() {
|
||||
public void testUpdateRoleDataScope() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));
|
||||
roleMapper.insert(roleDO);
|
||||
|
@ -134,12 +113,10 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
|
|||
RoleDO dbRoleDO = roleMapper.selectById(id);
|
||||
assertEquals(dataScope, dbRoleDO.getDataScope());
|
||||
assertEquals(dataScopeRoleIds, dbRoleDO.getDataScopeDeptIds());
|
||||
// verify 发送刷新消息
|
||||
verify(roleProducer).sendRoleRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteRole_success() {
|
||||
public void testDeleteRole() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));
|
||||
roleMapper.insert(roleDO);
|
||||
|
@ -152,23 +129,65 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
|
|||
assertNull(roleMapper.selectById(id));
|
||||
// verify 删除相关数据
|
||||
verify(permissionService).processRoleDeleted(id);
|
||||
// verify 发送刷新消息
|
||||
verify(roleProducer).sendRoleRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleFromCache() {
|
||||
// mock 数据(缓存)
|
||||
public void testValidateRoleDuplicate_success() {
|
||||
// 调用,不会抛异常
|
||||
roleService.validateRoleDuplicate(randomString(), randomString(), null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRoleDuplicate_nameDuplicate() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setName("role_name"));
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
String name = "role_name";
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> roleService.validateRoleDuplicate(name, randomString(), null),
|
||||
ROLE_NAME_DUPLICATE, name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRoleDuplicate_codeDuplicate() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setCode("code"));
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
String code = "code";
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> roleService.validateRoleDuplicate(randomString(), code, null),
|
||||
ROLE_CODE_DUPLICATE, code);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateUpdateRole_success() {
|
||||
RoleDO roleDO = randomPojo(RoleDO.class);
|
||||
roleMapper.insert(roleDO);
|
||||
roleService.initLocalCache();
|
||||
// 参数准备
|
||||
// 准备参数
|
||||
Long id = roleDO.getId();
|
||||
|
||||
// 调用
|
||||
RoleDO dbRoleDO = roleService.getRoleFromCache(id);
|
||||
// 断言
|
||||
assertPojoEquals(roleDO, dbRoleDO);
|
||||
// 调用,无异常
|
||||
roleService.validateRoleForUpdate(id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateUpdateRole_roleIdNotExist() {
|
||||
assertServiceException(() -> roleService.validateRoleForUpdate(randomLongId()), ROLE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateUpdateRole_systemRoleCanNotBeUpdate() {
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.SYSTEM.getType()));
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
Long id = roleDO.getId();
|
||||
|
||||
assertServiceException(() -> roleService.validateRoleForUpdate(id),
|
||||
ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -186,22 +205,21 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleListByStatus_statusNotEmpty() {
|
||||
// mock 数据
|
||||
RoleDO dbRole = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
roleMapper.insert(dbRole);
|
||||
// 测试 status 不匹配
|
||||
roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
public void testGetRoleFromCache() {
|
||||
// mock 数据(缓存)
|
||||
RoleDO roleDO = randomPojo(RoleDO.class);
|
||||
roleMapper.insert(roleDO);
|
||||
// 参数准备
|
||||
Long id = roleDO.getId();
|
||||
|
||||
// 调用
|
||||
List<RoleDO> list = roleService.getRoleListByStatus(singleton(CommonStatusEnum.ENABLE.getStatus()));
|
||||
RoleDO dbRoleDO = roleService.getRoleFromCache(id);
|
||||
// 断言
|
||||
assertEquals(1, list.size());
|
||||
assertPojoEquals(dbRole, list.get(0));
|
||||
assertPojoEquals(roleDO, dbRoleDO);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleListByStatus_statusEmpty() {
|
||||
public void testGetRoleListByStatus() {
|
||||
// mock 数据
|
||||
RoleDO dbRole01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
roleMapper.insert(dbRole01);
|
||||
|
@ -209,29 +227,33 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
|
|||
roleMapper.insert(dbRole02);
|
||||
|
||||
// 调用
|
||||
List<RoleDO> list = roleService.getRoleListByStatus(null);
|
||||
List<RoleDO> list = roleService.getRoleListByStatus(
|
||||
singleton(CommonStatusEnum.ENABLE.getStatus()));
|
||||
// 断言
|
||||
assertEquals(2, list.size());
|
||||
assertEquals(1, list.size());
|
||||
assertPojoEquals(dbRole01, list.get(0));
|
||||
assertPojoEquals(dbRole02, list.get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetRoleListFromCache() {
|
||||
// mock 数据
|
||||
RoleDO dbRole = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
roleMapper.insert(dbRole);
|
||||
// 测试 id 不匹配
|
||||
roleMapper.insert(cloneIgnoreId(dbRole, o -> {}));
|
||||
roleService.initLocalCache();
|
||||
// 准备参数
|
||||
Collection<Long> ids = singleton(dbRole.getId());
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
|
||||
.thenReturn(roleService);
|
||||
|
||||
// 调用
|
||||
List<RoleDO> list = roleService.getRoleListFromCache(ids);
|
||||
// 断言
|
||||
assertEquals(1, list.size());
|
||||
assertPojoEquals(dbRole, list.get(0));
|
||||
// mock 数据
|
||||
RoleDO dbRole = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));
|
||||
roleMapper.insert(dbRole);
|
||||
// 测试 id 不匹配
|
||||
roleMapper.insert(cloneIgnoreId(dbRole, o -> {}));
|
||||
// 准备参数
|
||||
Collection<Long> ids = singleton(dbRole.getId());
|
||||
|
||||
// 调用
|
||||
List<RoleDO> list = roleService.getRoleListFromCache(ids);
|
||||
// 断言
|
||||
assertEquals(1, list.size());
|
||||
assertPojoEquals(dbRole, list.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -296,72 +318,37 @@ public class RoleServiceImplTest extends BaseDbUnitTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void testHasAnySuperAdmin() {
|
||||
// 是超级
|
||||
assertTrue(roleService.hasAnySuperAdmin(singletonList(randomPojo(RoleDO.class,
|
||||
o -> o.setCode("super_admin")))));
|
||||
// 非超级
|
||||
assertFalse(roleService.hasAnySuperAdmin(singletonList(randomPojo(RoleDO.class,
|
||||
o -> o.setCode("tenant_admin")))));
|
||||
public void testHasAnySuperAdmin_true() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
|
||||
.thenReturn(roleService);
|
||||
|
||||
// mock 数据
|
||||
RoleDO dbRole = randomPojo(RoleDO.class).setCode("super_admin");
|
||||
roleMapper.insert(dbRole);
|
||||
// 准备参数
|
||||
Long id = dbRole.getId();
|
||||
|
||||
// 调用,并调用
|
||||
assertTrue(roleService.hasAnySuperAdmin(singletonList(id)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRoleDuplicate_success() {
|
||||
// 调用,不会抛异常
|
||||
roleService.validateRoleDuplicate(randomString(), randomString(), null);
|
||||
}
|
||||
public void testHasAnySuperAdmin_false() {
|
||||
try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {
|
||||
springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))
|
||||
.thenReturn(roleService);
|
||||
|
||||
@Test
|
||||
public void testValidateRoleDuplicate_nameDuplicate() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setName("role_name"));
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
String name = "role_name";
|
||||
// mock 数据
|
||||
RoleDO dbRole = randomPojo(RoleDO.class).setCode("tenant_admin");
|
||||
roleMapper.insert(dbRole);
|
||||
// 准备参数
|
||||
Long id = dbRole.getId();
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> roleService.validateRoleDuplicate(name, randomString(), null),
|
||||
ROLE_NAME_DUPLICATE, name);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateRoleDuplicate_codeDuplicate() {
|
||||
// mock 数据
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setCode("code"));
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
String code = "code";
|
||||
|
||||
// 调用,并断言异常
|
||||
assertServiceException(() -> roleService.validateRoleDuplicate(randomString(), code, null),
|
||||
ROLE_CODE_DUPLICATE, code);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateUpdateRole_success() {
|
||||
RoleDO roleDO = randomPojo(RoleDO.class);
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
Long id = roleDO.getId();
|
||||
|
||||
// 调用,无异常
|
||||
roleService.validateRoleForUpdate(id);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateUpdateRole_roleIdNotExist() {
|
||||
assertServiceException(() -> roleService.validateRoleForUpdate(randomLongId()), ROLE_NOT_EXISTS);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidateUpdateRole_systemRoleCanNotBeUpdate() {
|
||||
RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.SYSTEM.getType()));
|
||||
roleMapper.insert(roleDO);
|
||||
// 准备参数
|
||||
Long id = roleDO.getId();
|
||||
|
||||
assertServiceException(() -> roleService.validateRoleForUpdate(id),
|
||||
ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE);
|
||||
// 调用,并调用
|
||||
assertFalse(roleService.hasAnySuperAdmin(singletonList(id)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -18,7 +18,6 @@ import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsChannelDO;
|
|||
import cn.iocoder.yudao.module.system.dal.dataobject.sms.SmsTemplateDO;
|
||||
import cn.iocoder.yudao.module.system.dal.mysql.sms.SmsTemplateMapper;
|
||||
import cn.iocoder.yudao.module.system.enums.sms.SmsTemplateTypeEnum;
|
||||
import cn.iocoder.yudao.module.system.mq.producer.sms.SmsProducer;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
|
@ -26,7 +25,6 @@ import org.springframework.context.annotation.Import;
|
|||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import static cn.hutool.core.util.RandomUtil.randomEle;
|
||||
|
@ -55,25 +53,6 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
private SmsClientFactory smsClientFactory;
|
||||
@MockBean
|
||||
private SmsClient smsClient;
|
||||
@MockBean
|
||||
private SmsProducer smsProducer;
|
||||
|
||||
@Test
|
||||
void testInitLocalCache() {
|
||||
// mock 数据
|
||||
SmsTemplateDO smsTemplate01 = randomSmsTemplateDO();
|
||||
smsTemplateMapper.insert(smsTemplate01);
|
||||
SmsTemplateDO smsTemplate02 = randomSmsTemplateDO();
|
||||
smsTemplateMapper.insert(smsTemplate02);
|
||||
|
||||
// 调用
|
||||
smsTemplateService.initLocalCache();
|
||||
// 断言 deptCache 缓存
|
||||
Map<String, SmsTemplateDO> smsTemplateCache = smsTemplateService.getSmsTemplateCache();
|
||||
assertEquals(2, smsTemplateCache.size());
|
||||
assertPojoEquals(smsTemplate01, smsTemplateCache.get(smsTemplate01.getCode()));
|
||||
assertPojoEquals(smsTemplate02, smsTemplateCache.get(smsTemplate02.getCode()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseTemplateContentParams() {
|
||||
|
@ -116,8 +95,6 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
assertPojoEquals(reqVO, smsTemplate);
|
||||
assertEquals(Lists.newArrayList("operation", "code"), smsTemplate.getParams());
|
||||
assertEquals(channelDO.getCode(), smsTemplate.getChannelCode());
|
||||
// 校验调用
|
||||
verify(smsProducer, times(1)).sendSmsTemplateRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -151,8 +128,6 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
assertPojoEquals(reqVO, smsTemplate);
|
||||
assertEquals(Lists.newArrayList("operation", "code"), smsTemplate.getParams());
|
||||
assertEquals(channelDO.getCode(), smsTemplate.getChannelCode());
|
||||
// 校验调用
|
||||
verify(smsProducer, times(1)).sendSmsTemplateRefreshMessage();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -174,10 +149,8 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
|
||||
// 调用
|
||||
smsTemplateService.deleteSmsTemplate(id);
|
||||
// 校验数据不存在了
|
||||
assertNull(smsTemplateMapper.selectById(id));
|
||||
// 校验调用
|
||||
verify(smsProducer, times(1)).sendSmsTemplateRefreshMessage();
|
||||
// 校验数据不存在了
|
||||
assertNull(smsTemplateMapper.selectById(id));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -191,47 +164,47 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
|
||||
@Test
|
||||
public void testGetSmsTemplatePage() {
|
||||
// mock 数据
|
||||
SmsTemplateDO dbSmsTemplate = randomPojo(SmsTemplateDO.class, o -> { // 等会查询到
|
||||
o.setType(SmsTemplateTypeEnum.PROMOTION.getType());
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setCode("tudou");
|
||||
o.setContent("芋道源码");
|
||||
o.setApiTemplateId("yunai");
|
||||
o.setChannelId(1L);
|
||||
o.setCreateTime(buildTime(2021, 11, 11));
|
||||
});
|
||||
smsTemplateMapper.insert(dbSmsTemplate);
|
||||
// 测试 type 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setType(SmsTemplateTypeEnum.VERIFICATION_CODE.getType())));
|
||||
// 测试 status 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 测试 code 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCode("yuanma")));
|
||||
// 测试 content 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setContent("源码")));
|
||||
// 测试 apiTemplateId 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setApiTemplateId("nai")));
|
||||
// 测试 channelId 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setChannelId(2L)));
|
||||
// 测试 createTime 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCreateTime(buildTime(2021, 12, 12))));
|
||||
// 准备参数
|
||||
SmsTemplatePageReqVO reqVO = new SmsTemplatePageReqVO();
|
||||
reqVO.setType(SmsTemplateTypeEnum.PROMOTION.getType());
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
reqVO.setCode("tu");
|
||||
reqVO.setContent("芋道");
|
||||
reqVO.setApiTemplateId("yu");
|
||||
reqVO.setChannelId(1L);
|
||||
reqVO.setCreateTime(buildBetweenTime(2021, 11, 1, 2021, 12, 1));
|
||||
// mock 数据
|
||||
SmsTemplateDO dbSmsTemplate = randomPojo(SmsTemplateDO.class, o -> { // 等会查询到
|
||||
o.setType(SmsTemplateTypeEnum.PROMOTION.getType());
|
||||
o.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
o.setCode("tudou");
|
||||
o.setContent("芋道源码");
|
||||
o.setApiTemplateId("yunai");
|
||||
o.setChannelId(1L);
|
||||
o.setCreateTime(buildTime(2021, 11, 11));
|
||||
});
|
||||
smsTemplateMapper.insert(dbSmsTemplate);
|
||||
// 测试 type 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setType(SmsTemplateTypeEnum.VERIFICATION_CODE.getType())));
|
||||
// 测试 status 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));
|
||||
// 测试 code 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCode("yuanma")));
|
||||
// 测试 content 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setContent("源码")));
|
||||
// 测试 apiTemplateId 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setApiTemplateId("nai")));
|
||||
// 测试 channelId 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setChannelId(2L)));
|
||||
// 测试 createTime 不匹配
|
||||
smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCreateTime(buildTime(2021, 12, 12))));
|
||||
// 准备参数
|
||||
SmsTemplatePageReqVO reqVO = new SmsTemplatePageReqVO();
|
||||
reqVO.setType(SmsTemplateTypeEnum.PROMOTION.getType());
|
||||
reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());
|
||||
reqVO.setCode("tu");
|
||||
reqVO.setContent("芋道");
|
||||
reqVO.setApiTemplateId("yu");
|
||||
reqVO.setChannelId(1L);
|
||||
reqVO.setCreateTime(buildBetweenTime(2021, 11, 1, 2021, 12, 1));
|
||||
|
||||
// 调用
|
||||
PageResult<SmsTemplateDO> pageResult = smsTemplateService.getSmsTemplatePage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbSmsTemplate, pageResult.getList().get(0));
|
||||
// 调用
|
||||
PageResult<SmsTemplateDO> pageResult = smsTemplateService.getSmsTemplatePage(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, pageResult.getTotal());
|
||||
assertEquals(1, pageResult.getList().size());
|
||||
assertPojoEquals(dbSmsTemplate, pageResult.getList().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -271,11 +244,11 @@ public class SmsTemplateServiceImplTest extends BaseDbUnitTest {
|
|||
reqVO.setChannelId(1L);
|
||||
reqVO.setCreateTime(buildBetweenTime(2021, 11, 1, 2021, 12, 1));
|
||||
|
||||
// 调用
|
||||
List<SmsTemplateDO> list = smsTemplateService.getSmsTemplateList(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, list.size());
|
||||
assertPojoEquals(dbSmsTemplate, list.get(0));
|
||||
// 调用
|
||||
List<SmsTemplateDO> list = smsTemplateService.getSmsTemplateList(reqVO);
|
||||
// 断言
|
||||
assertEquals(1, list.size());
|
||||
assertPojoEquals(dbSmsTemplate, list.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -199,7 +199,7 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
|
|||
role101.setTenantId(dbTenant.getId());
|
||||
when(roleService.getRoleListByStatus(isNull())).thenReturn(asList(role100, role101));
|
||||
// mock 每个角色的权限
|
||||
when(permissionService.getRoleMenuIds(eq(101L))).thenReturn(asSet(201L, 202L));
|
||||
when(permissionService.getRoleMenuListByRoleId(eq(101L))).thenReturn(asSet(201L, 202L));
|
||||
|
||||
// 调用
|
||||
tenantService.updateTenant(reqVO);
|
||||
|
@ -454,7 +454,7 @@ public class TenantServiceImplTest extends BaseDbUnitTest {
|
|||
TenantContextHolder.setTenantId(dbTenant.getId());
|
||||
// mock 菜单
|
||||
when(menuService.getMenuList()).thenReturn(Arrays.asList(randomPojo(MenuDO.class, o -> o.setId(100L)),
|
||||
randomPojo(MenuDO.class, o -> o.setId(101L))));
|
||||
randomPojo(MenuDO.class, o -> o.setId(101L))));
|
||||
|
||||
// 调用
|
||||
tenantService.handleTenantMenu(handler);
|
||||
|
|
|
@ -345,7 +345,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
|||
reqVO.setDeptId(1L); // 其中,1L 是 2L 的父部门
|
||||
// mock 方法
|
||||
List<DeptDO> deptList = newArrayList(randomPojo(DeptDO.class, o -> o.setId(2L)));
|
||||
when(deptService.getDeptListByParentIdFromCache(eq(reqVO.getDeptId()), eq(true))).thenReturn(deptList);
|
||||
when(deptService.getChildDeptList(eq(reqVO.getDeptId()))).thenReturn(deptList);
|
||||
|
||||
// 调用
|
||||
PageResult<AdminUserDO> pageResult = userService.getUserPage(reqVO);
|
||||
|
@ -368,7 +368,7 @@ public class AdminUserServiceImplTest extends BaseDbUnitTest {
|
|||
reqVO.setDeptId(1L); // 其中,1L 是 2L 的父部门
|
||||
// mock 方法
|
||||
List<DeptDO> deptList = newArrayList(randomPojo(DeptDO.class, o -> o.setId(2L)));
|
||||
when(deptService.getDeptListByParentIdFromCache(eq(reqVO.getDeptId()), eq(true))).thenReturn(deptList);
|
||||
when(deptService.getChildDeptList(eq(reqVO.getDeptId()))).thenReturn(deptList);
|
||||
|
||||
// 调用
|
||||
List<AdminUserDO> list = userService.getUserList(reqVO);
|
||||
|
|
Loading…
Reference in New Issue