From eaf44e1b4faa4ccaad7d3fa53232716be28145db Mon Sep 17 00:00:00 2001 From: ayi <2294931964@qq.com> Date: Tue, 3 Feb 2026 16:09:47 +0800 Subject: [PATCH] =?UTF-8?q?SaToken=E6=8E=A5=E5=8F=A3=E9=89=B4=E6=9D=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/constant/RedisKeyConstants.java | 6 +- .../auth/domain/mappers/RoleMapper.java | 2 + .../domain/mappers/UserRoleRelMapper.java | 5 +- .../auth/domain/mappings/RoleMapping.xml | 6 ++ .../domain/mappings/UserRoleRelMapping.xml | 3 - .../PushRolePermissions2RedisRunner.java | 12 +-- .../auth/service/impl/UserServiceImpl.java | 18 +++-- xiaoyi-gateway/pom.xml | 12 +++ .../xiaoyi/gateway/auth/SaTokenConfigure.java | 1 - .../xiaoyi/gateway/auth/StpInterfaceImpl.java | 80 ++++++++++++++++++- .../gateway/config/RedisTemplateConfig.java | 41 ++++++++++ .../gateway/constant/RedisKeyConstants.java | 42 ++++++++++ 12 files changed, 204 insertions(+), 24 deletions(-) create mode 100644 xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/config/RedisTemplateConfig.java create mode 100644 xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/constant/RedisKeyConstants.java diff --git a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/constant/RedisKeyConstants.java b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/constant/RedisKeyConstants.java index 810a1c3..d841be4 100644 --- a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/constant/RedisKeyConstants.java +++ b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/constant/RedisKeyConstants.java @@ -58,11 +58,11 @@ public class RedisKeyConstants { /** * 构建角色权限数据 KEY * - * @param roleId 角色 ID + * @param roleKey 角色 ID * @return 角色权限数据 KEY */ - public static String buildRolePermissionsKey(Long roleId) { - return ROLE_PERMISSIONS_KEY_PREFIX + roleId; + public static String buildRolePermissionsKey(String roleKey) { + return ROLE_PERMISSIONS_KEY_PREFIX + roleKey; } } diff --git a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappers/RoleMapper.java b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappers/RoleMapper.java index bef5362..866f76d 100644 --- a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappers/RoleMapper.java +++ b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappers/RoleMapper.java @@ -21,5 +21,7 @@ public interface RoleMapper extends BaseMapper { */ List selectEnabledRoleList(); + List selectRoleKeyByUserId(Long userId); + } diff --git a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappers/UserRoleRelMapper.java b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappers/UserRoleRelMapper.java index 971104c..40ca46a 100644 --- a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappers/UserRoleRelMapper.java +++ b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappers/UserRoleRelMapper.java @@ -1,9 +1,7 @@ package top.crushtj.xiaoyi.auth.domain.mappers; -import top.crushtj.xiaoyi.auth.domain.entity.UserRoleRelEntity; import com.baomidou.mybatisplus.core.mapper.BaseMapper; - -import java.util.List; +import top.crushtj.xiaoyi.auth.domain.entity.UserRoleRelEntity; /** * @author ayi @@ -14,6 +12,5 @@ import java.util.List; */ public interface UserRoleRelMapper extends BaseMapper { - List selectByUserId(Long userId); } diff --git a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappings/RoleMapping.xml b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappings/RoleMapping.xml index da0b4c9..fe73123 100644 --- a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappings/RoleMapping.xml +++ b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappings/RoleMapping.xml @@ -20,4 +20,10 @@ WHERE status = 1 AND is_deleted = 0 + diff --git a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappings/UserRoleRelMapping.xml b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappings/UserRoleRelMapping.xml index d43332c..83a45a8 100644 --- a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappings/UserRoleRelMapping.xml +++ b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/domain/mappings/UserRoleRelMapping.xml @@ -10,8 +10,5 @@ - diff --git a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/runner/PushRolePermissions2RedisRunner.java b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/runner/PushRolePermissions2RedisRunner.java index 1a3f8b6..7ce4934 100644 --- a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/runner/PushRolePermissions2RedisRunner.java +++ b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/runner/PushRolePermissions2RedisRunner.java @@ -88,27 +88,27 @@ public class PushRolePermissions2RedisRunner implements ApplicationRunner { .collect(Collectors.toMap(PermissionEntity::getId, permissionEntity -> permissionEntity)); // 构建角色 ID 和权限 DO 列表的映射关系 - Map> roleIdPermissionMap = new HashMap<>(); + Map> roleIdPermissionMap = new HashMap<>(); roleList.forEach(role -> { Long roleId = role.getId(); //获取角色对应的权限id列表 List permissionIds = roleIdPermissionIdsMap.get(roleId); if (CollUtil.isNotEmpty(permissionIds)) { - List perDOS = Lists.newArrayList(); + List permissionKeys = Lists.newArrayList(); permissionIds.forEach(permissionId -> { // 根据权限 ID 获取具体的权限 DO 对象 PermissionEntity permissionDO = permissionIdEntityMap.get(permissionId); if (Objects.nonNull(permissionDO)) { - perDOS.add(permissionDO); + permissionKeys.add(permissionDO.getPermissionKey()); } }); - roleIdPermissionMap.put(roleId, perDOS); + roleIdPermissionMap.put(role.getRoleKey(), permissionKeys); } }); // 将角色 ID 和权限 DO 列表的映射关系写入 Redis - roleIdPermissionMap.forEach((roleId, permission) -> { - String key = RedisKeyConstants.buildRolePermissionsKey(roleId); + roleIdPermissionMap.forEach((roleKey, permission) -> { + String key = RedisKeyConstants.buildRolePermissionsKey(roleKey); redisTemplate.opsForValue() .set(key, JsonUtils.toJsonString(permission)); }); diff --git a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/service/impl/UserServiceImpl.java b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/service/impl/UserServiceImpl.java index b526940..9b9c40c 100644 --- a/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/service/impl/UserServiceImpl.java +++ b/xiaoyi-auth/src/main/java/top/crushtj/xiaoyi/auth/service/impl/UserServiceImpl.java @@ -18,8 +18,10 @@ import top.crushtj.framework.common.utils.IdGenerator; import top.crushtj.framework.common.utils.JsonUtils; import top.crushtj.framework.common.utils.MaskUtils; import top.crushtj.xiaoyi.auth.constant.RedisKeyConstants; +import top.crushtj.xiaoyi.auth.domain.entity.RoleEntity; import top.crushtj.xiaoyi.auth.domain.entity.UserEntity; import top.crushtj.xiaoyi.auth.domain.entity.UserRoleRelEntity; +import top.crushtj.xiaoyi.auth.domain.mappers.RoleMapper; import top.crushtj.xiaoyi.auth.domain.mappers.UserMapper; import top.crushtj.xiaoyi.auth.domain.mappers.UserRoleRelMapper; import top.crushtj.xiaoyi.auth.enums.LoginTypeEnum; @@ -55,6 +57,9 @@ public class UserServiceImpl extends ServiceImpl impleme @Resource private UserRoleRelMapper userRoleRelMapper; + @Resource + private RoleMapper roleMapper; + @Resource private TransactionTemplate transactionTemplate; @@ -154,8 +159,9 @@ public class UserServiceImpl extends ServiceImpl impleme userRoleRelMapper.insert(userRoleRel); // 将用户角色信息存储到 Redis 中 - List roles = new ArrayList<>(); - roles.add(COMMON_USER_ROLE_ID); + RoleEntity role = roleMapper.selectById(COMMON_USER_ROLE_ID); + List roles = new ArrayList<>(1); + roles.add(role.getRoleKey()); String userRolesKey = RedisKeyConstants.buildUserRolesKey(userId); redisTemplate.opsForValue() .set(userRolesKey, JsonUtils.toJsonString(roles)); @@ -167,7 +173,8 @@ public class UserServiceImpl extends ServiceImpl impleme // 回滚 redis 自增ID Long decrement = Long.valueOf(Objects.requireNonNull(redisTemplate.opsForValue() .get(XIAOYI_ID_GENERATOR_KEY)) - .toString().trim()); + .toString() + .trim()); if (decrement.compareTo(XIAOYI_ID_INITIAL_VALUE) > 0) { log.error("==> 用户注册异常,回滚redis自增ID: {}", decrement); redisTemplate.opsForValue() @@ -187,8 +194,9 @@ public class UserServiceImpl extends ServiceImpl impleme String userRolesKey = RedisKeyConstants.buildUserRolesKey(userId); Boolean hasKey = redisTemplate.hasKey(userRolesKey); if (!hasKey) { - List roles = userRoleRelMapper.selectByUserId(userId); - redisTemplate.opsForValue().set(userRolesKey,JsonUtils.toJsonString(roles)); + List roles = roleMapper.selectRoleKeyByUserId(userId); + redisTemplate.opsForValue() + .set(userRolesKey, JsonUtils.toJsonString(roles)); } } } diff --git a/xiaoyi-gateway/pom.xml b/xiaoyi-gateway/pom.xml index a31a5ff..8269fc5 100644 --- a/xiaoyi-gateway/pom.xml +++ b/xiaoyi-gateway/pom.xml @@ -68,6 +68,18 @@ org.springframework.boot spring-boot-starter-actuator + + + + org.springframework.boot + spring-boot-starter-data-redis + + + + + top.crushtj + xiaoyi-spring-boot-starter-jackson + diff --git a/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/auth/SaTokenConfigure.java b/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/auth/SaTokenConfigure.java index 79edfdd..92f2d30 100644 --- a/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/auth/SaTokenConfigure.java +++ b/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/auth/SaTokenConfigure.java @@ -32,7 +32,6 @@ public class SaTokenConfigure { .check(r -> StpUtil.checkLogin()) // 校验是否登录 ; - SaRouter.match("/auth/user/logout", r -> StpUtil.checkPermission("user")); // 权限认证 -- 不同模块, 校验不同权限 // SaRouter.match("/user/**", r -> StpUtil.checkPermission("user")); // SaRouter.match("/admin/**", r -> StpUtil.checkPermission("admin")); diff --git a/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/auth/StpInterfaceImpl.java b/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/auth/StpInterfaceImpl.java index 3ffca72..f06123b 100644 --- a/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/auth/StpInterfaceImpl.java +++ b/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/auth/StpInterfaceImpl.java @@ -1,10 +1,21 @@ package top.crushtj.xiaoyi.gateway.auth; import cn.dev33.satoken.stp.StpInterface; +import cn.hutool.core.collection.CollUtil; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.Lists; +import jakarta.annotation.Resource; +import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; +import top.crushtj.xiaoyi.gateway.constant.RedisKeyConstants; import java.util.List; +import java.util.Objects; /** * @author ayi @@ -17,15 +28,80 @@ import java.util.List; @Slf4j @Component public class StpInterfaceImpl implements StpInterface { + + @Resource + private RedisTemplate redisTemplate; + @Resource + private ObjectMapper objectMapper; + + @SneakyThrows @Override public List getPermissionList(Object loginId, String loginType) { log.info("## 获取用户权限列表, loginId: {}", loginId); - return List.of(); + // 构建 用户-角色 Redis Key + String userRolesKey = RedisKeyConstants.buildUserRoleKey(Long.valueOf(loginId.toString())); + + // 根据用户 ID ,从 Redis 中获取该用户的角色集合 + String useRolesValue = redisTemplate.opsForValue() + .get(userRolesKey); + + if (StringUtils.isBlank(useRolesValue)) { + return null; + } + + // 将 JSON 字符串转换为 List 角色集合 + List userRoleKeys = objectMapper.readValue(useRolesValue, new TypeReference<>() { + }); + + if (CollUtil.isNotEmpty(userRoleKeys)) { + // 查询这些角色对应的权限 + // 构建 角色-权限 Redis Key 集合 + List rolePermissionsKeys = userRoleKeys.stream() + .map(RedisKeyConstants::buildRolePermissionsKey) + .toList(); + + // 通过 key 集合批量查询权限,提升查询性能。 + List rolePermissionsValues = Objects.requireNonNull(redisTemplate.opsForValue() + .multiGet(rolePermissionsKeys)) + .stream() + .filter(Objects::nonNull) + .toList(); + + if (CollUtil.isNotEmpty(rolePermissionsValues)) { + List permissions = Lists.newArrayList(); + + // 遍历所有角色的权限集合,统一添加到 permissions 集合中 + rolePermissionsValues.forEach(jsonValue -> { + try { + // 将 JSON 字符串转换为 List 权限集合 + List rolePermissions = objectMapper.readValue(jsonValue, new TypeReference<>() { + }); + permissions.addAll(rolePermissions); + } catch (JsonProcessingException e) { + log.error("==> JSON 解析错误: ", e); + } + }); + + // 返回此用户所拥有的权限 + return permissions; + } + } + return null; } + @SneakyThrows @Override public List getRoleList(Object loginId, String loginType) { log.info("## 获取用户角色列表, loginId: {}", loginId); - return List.of(); + String userRolesKey = RedisKeyConstants.buildUserRoleKey(Long.valueOf(loginId.toString())); + String useRolesValue = redisTemplate.opsForValue() + .get(userRolesKey); + if (StringUtils.isBlank(useRolesValue)) { + return null; + } + + // 将 JSON 字符串转换为 List 集合 + return objectMapper.readValue(useRolesValue, new TypeReference<>() { + }); } } diff --git a/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/config/RedisTemplateConfig.java b/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/config/RedisTemplateConfig.java new file mode 100644 index 0000000..9f94096 --- /dev/null +++ b/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/config/RedisTemplateConfig.java @@ -0,0 +1,41 @@ +package top.crushtj.xiaoyi.gateway.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; +import org.springframework.data.redis.serializer.StringRedisSerializer; +import org.springframework.lang.NonNull; + +/** + * + * @author ayi + * @version V1.0 + * @title RedisTemplateConfig + * @description Redis 配置类 + * @date 2026/01/12 19:13 + */ + +@Configuration +public class RedisTemplateConfig { + + @Bean + public RedisTemplate redisTemplate(@NonNull RedisConnectionFactory connectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + // 设置 RedisTemplate 的连接工厂 + redisTemplate.setConnectionFactory(connectionFactory); + + // 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值,确保 key 是可读的字符串 + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + + // 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值, 确保存储的是 JSON 格式 + Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class); + redisTemplate.setValueSerializer(serializer); + redisTemplate.setHashValueSerializer(serializer); + + redisTemplate.afterPropertiesSet(); + return redisTemplate; + } +} diff --git a/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/constant/RedisKeyConstants.java b/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/constant/RedisKeyConstants.java new file mode 100644 index 0000000..a85c645 --- /dev/null +++ b/xiaoyi-gateway/src/main/java/top/crushtj/xiaoyi/gateway/constant/RedisKeyConstants.java @@ -0,0 +1,42 @@ +package top.crushtj.xiaoyi.gateway.constant; + +/** + * @author ayi + * @version V1.0 + * @title RedisKeyConstants + * @date 2026/2/3 15:24 + * @description Redis key 常量 + */ + +public class RedisKeyConstants { + + /** + * 用户对应角色集合 KEY 前缀 + */ + private static final String USER_ROLES_KEY_PREFIX = "user:roles:"; + + /** + * 角色对应的权限集合 KEY 前缀 + */ + private static final String ROLE_PERMISSIONS_KEY_PREFIX = "role:permissions:"; + + /** + * 构建角色对应的权限集合 KEY + * + * @param roleKey 角色 key + * @return 角色对应的权限集合 KEY + */ + public static String buildRolePermissionsKey(String roleKey) { + return ROLE_PERMISSIONS_KEY_PREFIX + roleKey; + } + + /** + * 构建用户-角色 KEY + * + * @param userId 用户 ID + * @return 用户-角色 KEY + */ + public static String buildUserRoleKey(Long userId) { + return USER_ROLES_KEY_PREFIX + userId; + } +}