diff --git a/README.md b/README.md index ba547feb..b1b79660 100644 --- a/README.md +++ b/README.md @@ -58,9 +58,6 @@ youlai-boot ├── XxlJobConfig # XXL-JOB 自动装配配置 ├── controller # 控制层 ├── converter # MapStruct 转换器 - ├── core # 核心模块 - ├── security # Spring Security 安全配置和扩展 - ├── mybatis # Mybatis-Plus 配置和插件 ├── filter # 过滤器 ├── RequestLogFilter # 请求日志过滤器 ├── VerifyCodeFilter # 验证码过滤器 diff --git a/pom.xml b/pom.xml index 20574b6d..15b60620 100644 --- a/pom.xml +++ b/pom.xml @@ -7,7 +7,7 @@ com.youlai youlai-boot 2.6.0 - 基于 Java 17 + SpringBoot 3 构建的权限管理系统。 + 基于 Java 17 + SpringBoot 3 + Spring Security 构建的权限管理系统。 org.springframework.boot diff --git a/src/main/java/com/youlai/system/common/base/BaseEntity.java b/src/main/java/com/youlai/system/common/base/BaseEntity.java index fea13d45..aead014c 100644 --- a/src/main/java/com/youlai/system/common/base/BaseEntity.java +++ b/src/main/java/com/youlai/system/common/base/BaseEntity.java @@ -6,11 +6,14 @@ import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.Data; +import java.io.Serial; import java.io.Serializable; import java.time.LocalDateTime; @Data public class BaseEntity implements Serializable { + + @Serial private static final long serialVersionUID = 1L; @TableField(fill = FieldFill.INSERT) diff --git a/src/main/java/com/youlai/system/common/base/BasePageQuery.java b/src/main/java/com/youlai/system/common/base/BasePageQuery.java index 4776ee0f..e6b54b2c 100644 --- a/src/main/java/com/youlai/system/common/base/BasePageQuery.java +++ b/src/main/java/com/youlai/system/common/base/BasePageQuery.java @@ -14,9 +14,9 @@ import lombok.Data; @Schema public class BasePageQuery { - @Schema(description = "页码", required = true, example = "1") + @Schema(description = "页码", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") private int pageNum = 1; - @Schema(description = "每页记录数", required = true, example = "10") + @Schema(description = "每页记录数", requiredMode = Schema.RequiredMode.REQUIRED, example = "10") private int pageSize = 10; } diff --git a/src/main/java/com/youlai/system/common/base/BaseVO.java b/src/main/java/com/youlai/system/common/base/BaseVO.java index c33d932e..51cc6f77 100644 --- a/src/main/java/com/youlai/system/common/base/BaseVO.java +++ b/src/main/java/com/youlai/system/common/base/BaseVO.java @@ -3,6 +3,7 @@ package com.youlai.system.common.base; import lombok.Data; import lombok.ToString; +import java.io.Serial; import java.io.Serializable; /** @@ -15,5 +16,6 @@ import java.io.Serializable; @ToString public class BaseVO implements Serializable { + @Serial private static final long serialVersionUID = 1L; } diff --git a/src/main/java/com/youlai/system/plugin/captcha/CaptchaConfig.java b/src/main/java/com/youlai/system/config/CaptchaConfig.java similarity index 94% rename from src/main/java/com/youlai/system/plugin/captcha/CaptchaConfig.java rename to src/main/java/com/youlai/system/config/CaptchaConfig.java index 198eed28..8f55637d 100644 --- a/src/main/java/com/youlai/system/plugin/captcha/CaptchaConfig.java +++ b/src/main/java/com/youlai/system/config/CaptchaConfig.java @@ -1,8 +1,9 @@ -package com.youlai.system.plugin.captcha; +package com.youlai.system.config; import cn.hutool.captcha.generator.CodeGenerator; import cn.hutool.captcha.generator.MathGenerator; import cn.hutool.captcha.generator.RandomGenerator; +import com.youlai.system.plugin.captcha.CaptchaProperties; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; diff --git a/src/main/java/com/youlai/system/config/SecurityConfig.java b/src/main/java/com/youlai/system/config/SecurityConfig.java index 2d0de6c0..ed7455ce 100644 --- a/src/main/java/com/youlai/system/config/SecurityConfig.java +++ b/src/main/java/com/youlai/system/config/SecurityConfig.java @@ -1,10 +1,11 @@ package com.youlai.system.config; -import com.youlai.system.common.constant.SecurityConstants; +import cn.hutool.captcha.generator.CodeGenerator; +import com.youlai.system.security.constant.SecurityConstants; import com.youlai.system.security.exception.MyAccessDeniedHandler; import com.youlai.system.security.exception.MyAuthenticationEntryPoint; -import com.youlai.system.filter.JwtTokenFilter; -import com.youlai.system.filter.VerifyCodeFilter; +import com.youlai.system.filter.JwtValidationFilter; +import com.youlai.system.filter.CaptchaValidationFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -37,6 +38,8 @@ public class SecurityConfig { private final MyAuthenticationEntryPoint authenticationEntryPoint; private final MyAccessDeniedHandler accessDeniedHandler; private final RedisTemplate redisTemplate; + private final CodeGenerator codeGenerator; + @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { @@ -56,9 +59,9 @@ public class SecurityConfig { ; // 验证码校验过滤器 - http.addFilterBefore(new VerifyCodeFilter(), UsernamePasswordAuthenticationFilter.class); + http.addFilterBefore(new CaptchaValidationFilter(redisTemplate,codeGenerator), UsernamePasswordAuthenticationFilter.class); // JWT 校验过滤器 - http.addFilterBefore(new JwtTokenFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class); + http.addFilterBefore(new JwtValidationFilter(redisTemplate), UsernamePasswordAuthenticationFilter.class); return http.build(); } diff --git a/src/main/java/com/youlai/system/controller/SysRoleController.java b/src/main/java/com/youlai/system/controller/SysRoleController.java index a39d321a..aff591a1 100644 --- a/src/main/java/com/youlai/system/controller/SysRoleController.java +++ b/src/main/java/com/youlai/system/controller/SysRoleController.java @@ -10,7 +10,6 @@ import com.youlai.system.model.query.RolePageQuery; import com.youlai.system.model.vo.RolePageVO; import com.youlai.system.service.SysRoleService; import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.security.SecurityRequirement; import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; @@ -76,7 +75,7 @@ public class SysRoleController { @DeleteMapping("/{ids}") @PreAuthorize("@ss.hasPerm('sys:role:delete')") public Result deleteRoles( - @Parameter(description = "删除角色,多个以英文逗号(,)分割") @PathVariable String ids + @Parameter(description = "删除角色,多个以英文逗号(,)拼接") @PathVariable String ids ) { boolean result = roleService.deleteRoles(ids); return Result.judge(result); diff --git a/src/main/java/com/youlai/system/controller/SysUserController.java b/src/main/java/com/youlai/system/controller/SysUserController.java index 86ebc436..001994b6 100644 --- a/src/main/java/com/youlai/system/controller/SysUserController.java +++ b/src/main/java/com/youlai/system/controller/SysUserController.java @@ -102,7 +102,7 @@ public class SysUserController { @Operation(summary = "修改用户密码") @PatchMapping(value = "/{userId}/password") - @PreAuthorize("@ss.hasPerm('sys:user:reset_pwd')") + @PreAuthorize("@ss.hasPerm('sys:user:password:edit')") public Result updatePassword( @Parameter(description = "用户ID") @PathVariable Long userId, @RequestParam String password diff --git a/src/main/java/com/youlai/system/filter/VerifyCodeFilter.java b/src/main/java/com/youlai/system/filter/CaptchaValidationFilter.java similarity index 68% rename from src/main/java/com/youlai/system/filter/VerifyCodeFilter.java rename to src/main/java/com/youlai/system/filter/CaptchaValidationFilter.java index d08ffdc2..f85088b1 100644 --- a/src/main/java/com/youlai/system/filter/VerifyCodeFilter.java +++ b/src/main/java/com/youlai/system/filter/CaptchaValidationFilter.java @@ -1,17 +1,16 @@ package com.youlai.system.filter; -import cn.hutool.captcha.generator.MathGenerator; +import cn.hutool.captcha.generator.CodeGenerator; import cn.hutool.core.util.StrUtil; -import cn.hutool.extra.spring.SpringUtil; import com.youlai.system.common.constant.CacheConstants; -import com.youlai.system.common.constant.SecurityConstants; +import com.youlai.system.security.constant.SecurityConstants; import com.youlai.system.common.result.ResultCode; import com.youlai.system.common.util.ResponseUtils; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.web.util.matcher.AntPathRequestMatcher; import org.springframework.web.filter.OncePerRequestFilter; @@ -19,39 +18,47 @@ import java.io.IOException; /** - * 验证码校验过滤器 + * 图形验证码校验过滤器 * * @author haoxr * @since 2022/10/1 */ -public class VerifyCodeFilter extends OncePerRequestFilter { +public class CaptchaValidationFilter extends OncePerRequestFilter { private static final AntPathRequestMatcher LOGIN_PATH_REQUEST_MATCHER = new AntPathRequestMatcher(SecurityConstants.LOGIN_PATH, "POST"); public static final String CAPTCHA_CODE_PARAM_NAME = "captchaCode"; public static final String CAPTCHA_KEY_PARAM_NAME = "captchaKey"; + private final RedisTemplate redisTemplate; + + private final CodeGenerator codeGenerator; + + public CaptchaValidationFilter(RedisTemplate redisTemplate, CodeGenerator codeGenerator) { + this.redisTemplate = redisTemplate; + this.codeGenerator = codeGenerator; + } + + @Override public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { // 检验登录接口的验证码 if (LOGIN_PATH_REQUEST_MATCHER.matches(request)) { // 请求中的验证码 - String verifyCode = request.getParameter(CAPTCHA_CODE_PARAM_NAME); + String captchaCode = request.getParameter(CAPTCHA_CODE_PARAM_NAME); // TODO 兼容没有验证码的版本(线上请移除这个判断) - if (StrUtil.isBlank(verifyCode)) { + if (StrUtil.isBlank(captchaCode)) { chain.doFilter(request, response); return; } // 缓存中的验证码 - StringRedisTemplate redisTemplate = SpringUtil.getBean("stringRedisTemplate", StringRedisTemplate.class); String verifyCodeKey = request.getParameter(CAPTCHA_KEY_PARAM_NAME); - String cacheVerifyCode = redisTemplate.opsForValue().get(CacheConstants.CAPTCHA_CODE_PREFIX + verifyCodeKey); + String cacheVerifyCode = (String) redisTemplate.opsForValue().get(CacheConstants.CAPTCHA_CODE_PREFIX + verifyCodeKey); if (cacheVerifyCode == null) { ResponseUtils.writeErrMsg(response, ResultCode.VERIFY_CODE_TIMEOUT); } else { // 验证码比对 - MathGenerator mathGenerator = new MathGenerator(); - if (mathGenerator.verify(cacheVerifyCode, verifyCode)) { + if (codeGenerator.verify(cacheVerifyCode, captchaCode)) { chain.doFilter(request, response); } else { ResponseUtils.writeErrMsg(response, ResultCode.VERIFY_CODE_ERROR); diff --git a/src/main/java/com/youlai/system/filter/JwtTokenFilter.java b/src/main/java/com/youlai/system/filter/JwtValidationFilter.java similarity index 93% rename from src/main/java/com/youlai/system/filter/JwtTokenFilter.java rename to src/main/java/com/youlai/system/filter/JwtValidationFilter.java index e5b72150..15ddfc85 100644 --- a/src/main/java/com/youlai/system/filter/JwtTokenFilter.java +++ b/src/main/java/com/youlai/system/filter/JwtValidationFilter.java @@ -22,16 +22,16 @@ import java.io.IOException; import java.util.Map; /** - * JWT token 过滤器 + * JWT token 校验过滤器 * * @author haoxr * @since 2023/9/13 */ -public class JwtTokenFilter extends OncePerRequestFilter { +public class JwtValidationFilter extends OncePerRequestFilter { private final RedisTemplate redisTemplate; - public JwtTokenFilter(RedisTemplate redisTemplate) { + public JwtValidationFilter(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } diff --git a/src/main/java/com/youlai/system/common/constant/JwtClaimConstants.java b/src/main/java/com/youlai/system/security/constant/JwtClaimConstants.java similarity index 82% rename from src/main/java/com/youlai/system/common/constant/JwtClaimConstants.java rename to src/main/java/com/youlai/system/security/constant/JwtClaimConstants.java index 3c312dcd..e662ddb9 100644 --- a/src/main/java/com/youlai/system/common/constant/JwtClaimConstants.java +++ b/src/main/java/com/youlai/system/security/constant/JwtClaimConstants.java @@ -1,4 +1,4 @@ -package com.youlai.system.common.constant; +package com.youlai.system.security.constant; /** * JWT Claims声明常量 @@ -15,11 +15,6 @@ public interface JwtClaimConstants { */ String USER_ID = "userId"; - /** - * 用户名 - */ - String USERNAME = "username"; - /** * 部门ID */ diff --git a/src/main/java/com/youlai/system/common/constant/SecurityConstants.java b/src/main/java/com/youlai/system/security/constant/SecurityConstants.java similarity index 81% rename from src/main/java/com/youlai/system/common/constant/SecurityConstants.java rename to src/main/java/com/youlai/system/security/constant/SecurityConstants.java index 89f35d92..689fcfcd 100644 --- a/src/main/java/com/youlai/system/common/constant/SecurityConstants.java +++ b/src/main/java/com/youlai/system/security/constant/SecurityConstants.java @@ -1,4 +1,4 @@ -package com.youlai.system.common.constant; +package com.youlai.system.security.constant; /** * Security 常量 diff --git a/src/main/java/com/youlai/system/security/service/PermissionService.java b/src/main/java/com/youlai/system/security/service/PermissionService.java index bd50508a..c4dbe68f 100644 --- a/src/main/java/com/youlai/system/security/service/PermissionService.java +++ b/src/main/java/com/youlai/system/security/service/PermissionService.java @@ -13,8 +13,7 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Component; import org.springframework.util.PatternMatchUtils; -import java.util.List; -import java.util.Set; +import java.util.*; /** * SpringSecurity 权限校验 @@ -27,10 +26,105 @@ import java.util.Set; @Slf4j public class PermissionService { - private final RedisTemplate redisTemplate; + private final RedisTemplate redisTemplate; private final SysRoleMenuService roleMenuService; + + /** + * 初始化权限缓存 + */ + @PostConstruct + public void initRolePermsCache() { + refreshRolePermsCache(); + } + + /** + * 刷新权限缓存 + */ + public void refreshRolePermsCache() { + // 清理权限缓存 + redisTemplate.opsForHash().delete(CacheConstants.ROLE_PERMS_PREFIX, "*"); + + List list = roleMenuService.getRolePermsList(null); + if (CollectionUtil.isNotEmpty(list)) { + list.forEach(item -> { + String roleCode = item.getRoleCode(); + Set perms = item.getPerms(); + redisTemplate.opsForHash().put(CacheConstants.ROLE_PERMS_PREFIX, roleCode, perms); + }); + } + } + + /** + * 刷新权限缓存 + */ + public void refreshRolePermsCache(String roleCode) { + // 清理权限缓存 + redisTemplate.opsForHash().delete(CacheConstants.ROLE_PERMS_PREFIX, roleCode); + + List list = roleMenuService.getRolePermsList(roleCode); + if (CollectionUtil.isNotEmpty(list)) { + RolePermsBO rolePerms = list.get(0); + if (rolePerms == null) { + return; + } + + Set perms = rolePerms.getPerms(); + redisTemplate.opsForHash().put(CacheConstants.ROLE_PERMS_PREFIX, roleCode, perms); + } + } + + /** + * 刷新权限缓存 (角色编码变更时调用) + */ + public void refreshRolePermsCache(String oldRoleCode,String newRoleCode) { + // 清理旧角色权限缓存 + redisTemplate.opsForHash().delete(CacheConstants.ROLE_PERMS_PREFIX, oldRoleCode); + + // 添加新角色权限缓存 + List list = roleMenuService.getRolePermsList(newRoleCode); + if (CollectionUtil.isNotEmpty(list)) { + RolePermsBO rolePerms = list.get(0); + if (rolePerms == null) { + return; + } + + Set perms = rolePerms.getPerms(); + redisTemplate.opsForHash().put(CacheConstants.ROLE_PERMS_PREFIX, newRoleCode, perms); + } + } + + + /** + * 获取角色权限列表 + * + * @param roleCodes 角色编码集合 + * @return 角色权限列表 + */ + public Set getRolePermsFormCache(Set roleCodes) { + // 检查输入是否为空 + if (CollectionUtil.isEmpty(roleCodes)) { + return Collections.emptySet(); + } + + Set perms = new HashSet<>(); + // 从缓存中一次性获取所有角色的权限 + Collection roleCodesAsObjects = new ArrayList<>(roleCodes); + List rolePermsList = redisTemplate.opsForHash().multiGet(CacheConstants.ROLE_PERMS_PREFIX, roleCodesAsObjects); + + for (Object rolePermsObj : rolePermsList) { + if (rolePermsObj instanceof Set) { + @SuppressWarnings("unchecked") + Set rolePerms = (Set) rolePermsObj; + perms.addAll(rolePerms); + } + } + + return perms; + } + + /** * 判断当前登录用户是否拥有操作权限 * @@ -62,7 +156,6 @@ public class PermissionService { // 匹配权限,支持通配符 hasPermission = rolePerms.stream() .anyMatch(rolePerm -> - //rolePerm=sys:user:* requiredPerm=sys:user:add 返回true PatternMatchUtils.simpleMatch(rolePerm, requiredPerm) ); @@ -77,48 +170,5 @@ public class PermissionService { return hasPermission; } - /** - * 初始化权限缓存 - */ - @PostConstruct - public void initPermissionCache() { - refreshPermissionCache(); - } - - /** - * 刷新权限缓存 - */ - public void refreshPermissionCache() { - // 清理权限缓存 - redisTemplate.opsForHash().delete(CacheConstants.ROLE_PERMS_PREFIX, "*"); - - List list = roleMenuService.getRolePermsList(null); - if (CollectionUtil.isNotEmpty(list)) { - list.forEach(item -> { - String roleCode = item.getRoleCode(); - Set perms = item.getPerms(); - redisTemplate.opsForHash().put(CacheConstants.ROLE_PERMS_PREFIX, roleCode, perms); - }); - } - } - - /** - * 刷新权限缓存 - */ - public void refreshPermissionCache(String roleCode) { - // 清理权限缓存 - redisTemplate.opsForHash().delete(CacheConstants.ROLE_PERMS_PREFIX, roleCode); - - List list = roleMenuService.getRolePermsList(roleCode); - if (CollectionUtil.isNotEmpty(list)) { - RolePermsBO rolePerms = list.get(0); - if (rolePerms == null) { - return; - } - - Set perms = rolePerms.getPerms(); - redisTemplate.opsForHash().put(CacheConstants.ROLE_PERMS_PREFIX, roleCode, perms); - } - } } diff --git a/src/main/java/com/youlai/system/security/util/JwtUtils.java b/src/main/java/com/youlai/system/security/util/JwtUtils.java index 169d0c2f..ca8fb2f4 100644 --- a/src/main/java/com/youlai/system/security/util/JwtUtils.java +++ b/src/main/java/com/youlai/system/security/util/JwtUtils.java @@ -8,7 +8,7 @@ import cn.hutool.json.JSONArray; import cn.hutool.jwt.JWT; import cn.hutool.jwt.JWTPayload; import cn.hutool.jwt.JWTUtil; -import com.youlai.system.common.constant.JwtClaimConstants; +import com.youlai.system.security.constant.JwtClaimConstants; import com.youlai.system.security.model.SysUserDetails; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -29,9 +29,15 @@ import java.util.stream.Collectors; @Component public class JwtUtils { + /** + * JWT 加解密使用的密钥 + */ private static byte[] key; + /** + * JWT Token 的有效时间(单位:秒) + */ private static int ttl; @@ -57,7 +63,6 @@ public class JwtUtils { SysUserDetails userDetails = (SysUserDetails) authentication.getPrincipal(); - Map payload = new HashMap<>(); payload.put(JwtClaimConstants.USER_ID, userDetails.getUserId()); // 用户ID payload.put(JwtClaimConstants.DEPT_ID, userDetails.getDeptId()); // 部门ID diff --git a/src/main/java/com/youlai/system/service/impl/AuthServiceImpl.java b/src/main/java/com/youlai/system/service/impl/AuthServiceImpl.java index 04bdfdfa..f7ce665f 100644 --- a/src/main/java/com/youlai/system/service/impl/AuthServiceImpl.java +++ b/src/main/java/com/youlai/system/service/impl/AuthServiceImpl.java @@ -17,7 +17,7 @@ import com.youlai.system.security.util.JwtUtils; import jakarta.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.RedisTemplate; import org.springframework.http.HttpHeaders; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -43,7 +43,7 @@ import java.util.concurrent.TimeUnit; public class AuthServiceImpl implements AuthService { private final AuthenticationManager authenticationManager; - private final StringRedisTemplate redisTemplate; + private final RedisTemplate redisTemplate; private final CodeGenerator codeGenerator; private final Font captchaFont; private final CaptchaProperties captchaProperties; diff --git a/src/main/java/com/youlai/system/service/impl/SysMenuServiceImpl.java b/src/main/java/com/youlai/system/service/impl/SysMenuServiceImpl.java index af6cb85f..395e6217 100644 --- a/src/main/java/com/youlai/system/service/impl/SysMenuServiceImpl.java +++ b/src/main/java/com/youlai/system/service/impl/SysMenuServiceImpl.java @@ -18,6 +18,7 @@ import com.youlai.system.model.form.MenuForm; import com.youlai.system.model.query.MenuQuery; import com.youlai.system.model.vo.MenuVO; import com.youlai.system.model.vo.RouteVO; +import com.youlai.system.security.service.PermissionService; import com.youlai.system.service.SysMenuService; import lombok.RequiredArgsConstructor; import org.apache.commons.lang3.StringUtils; @@ -42,6 +43,8 @@ public class SysMenuServiceImpl extends ServiceImpl impl private final MenuConverter menuConverter; + private final PermissionService permissionService; + /** * 菜单列表 @@ -199,18 +202,17 @@ public class SysMenuServiceImpl extends ServiceImpl impl @Override @CacheEvict(cacheNames = "menu", key = "'routes'") public boolean saveMenu(MenuForm menuForm) { - String path = menuForm.getPath(); + MenuTypeEnum menuType = menuForm.getType(); - // 如果是目录 - if (menuType == MenuTypeEnum.CATALOG) { + if (menuType == MenuTypeEnum.CATALOG) { // 如果是外链 + String path = menuForm.getPath(); if (menuForm.getParentId() == 0 && !path.startsWith("/")) { menuForm.setPath("/" + path); // 一级目录需以 / 开头 } menuForm.setComponent("Layout"); - } - // 如果是外链 - else if (menuType == MenuTypeEnum.EXTLINK) { + } else if (menuType == MenuTypeEnum.EXTLINK) { // 如果是目录 + menuForm.setComponent(null); } @@ -218,7 +220,14 @@ public class SysMenuServiceImpl extends ServiceImpl impl String treePath = generateMenuTreePath(menuForm.getParentId()); entity.setTreePath(treePath); - return this.saveOrUpdate(entity); + boolean result = this.saveOrUpdate(entity); + if (result) { + // 编辑刷新角色权限缓存 + if (menuForm.getId() != null) { + permissionService.refreshRolePermsCache(); + } + } + return result; } /** @@ -285,10 +294,18 @@ public class SysMenuServiceImpl extends ServiceImpl impl @Override @CacheEvict(cacheNames = "menu", key = "'routes'") public boolean deleteMenu(Long id) { - return this.remove(new LambdaQueryWrapper() - .eq(SysMenu::getId, id) - .or() - .apply("CONCAT (',',tree_path,',') LIKE CONCAT('%,',{0},',%')", id)); + boolean result = this.remove(new LambdaQueryWrapper() + .eq(SysMenu::getId, id) + .or() + .apply("CONCAT (',',tree_path,',') LIKE CONCAT('%,',{0},',%')", id)); + + + // 刷新角色权限缓存 + if (result) { + permissionService.refreshRolePermsCache(); + } + return result; + } diff --git a/src/main/java/com/youlai/system/service/impl/SysRoleServiceImpl.java b/src/main/java/com/youlai/system/service/impl/SysRoleServiceImpl.java index bfc82c7a..b80185a0 100644 --- a/src/main/java/com/youlai/system/service/impl/SysRoleServiceImpl.java +++ b/src/main/java/com/youlai/system/service/impl/SysRoleServiceImpl.java @@ -2,31 +2,33 @@ package com.youlai.system.service.impl; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; -import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.youlai.system.common.constant.SystemConstants; import com.youlai.system.common.model.Option; +import com.youlai.system.security.util.SecurityUtils; import com.youlai.system.converter.RoleConverter; -import com.youlai.system.security.service.PermissionService; import com.youlai.system.mapper.SysRoleMapper; import com.youlai.system.model.entity.SysRole; import com.youlai.system.model.entity.SysRoleMenu; import com.youlai.system.model.form.RoleForm; import com.youlai.system.model.query.RolePageQuery; import com.youlai.system.model.vo.RolePageVO; +import com.youlai.system.security.service.PermissionService; import com.youlai.system.service.SysRoleMenuService; import com.youlai.system.service.SysRoleService; import com.youlai.system.service.SysUserRoleService; -import com.youlai.system.security.util.SecurityUtils; import lombok.RequiredArgsConstructor; import org.springframework.cache.annotation.CacheEvict; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.*; +import java.util.Arrays; +import java.util.List; +import java.util.Set; /** * 角色业务实现类 @@ -100,19 +102,37 @@ public class SysRoleServiceImpl extends ServiceImpl impl public boolean saveRole(RoleForm roleForm) { Long roleId = roleForm.getId(); - String roleCode = roleForm.getCode(); + // 编辑角色时,判断角色是否存在 + SysRole oldRole = null; + if (roleId != null) { + oldRole = this.getById(roleId); + Assert.isTrue(oldRole != null, "角色不存在"); + } + + String roleCode = roleForm.getCode(); long count = this.count(new LambdaQueryWrapper() .ne(roleId != null, SysRole::getId, roleId) .and(wrapper -> wrapper.eq(SysRole::getCode, roleCode).or().eq(SysRole::getName, roleForm.getName()) )); - Assert.isTrue(count == 0, "角色名称或角色编码重复,请检查!"); + Assert.isTrue(count == 0, "角色名称或角色编码已存在,请修改后重试!"); // 实体转换 SysRole role = roleConverter.form2Entity(roleForm); - return this.saveOrUpdate(role); + boolean result = this.saveOrUpdate(role); + if (result) { + // 判断角色编码或状态是否修改,修改了则刷新权限缓存 + if (oldRole != null + && ( + !StrUtil.equals(oldRole.getCode(), roleCode) || + !ObjectUtil.equals(oldRole.getStatus(), roleForm.getStatus()) + )) { + permissionService.refreshRolePermsCache(oldRole.getCode(), roleCode); + } + } + return result; } /** @@ -136,9 +156,16 @@ public class SysRoleServiceImpl extends ServiceImpl impl */ @Override public boolean updateRoleStatus(Long roleId, Integer status) { - return this.update(new LambdaUpdateWrapper() - .eq(SysRole::getId, roleId) - .set(SysRole::getStatus, status)); + + SysRole role = this.getById(roleId); + Assert.isTrue(role != null, "角色不存在"); + + boolean result = this.updateById(role); + if (result) { + // 刷新角色的权限缓存 + permissionService.refreshRolePermsCache(role.getCode()); + } + return result; } /** @@ -163,9 +190,9 @@ public class SysRoleServiceImpl extends ServiceImpl impl Assert.isTrue(!isRoleAssigned, "角色【{}】已分配用户,请先解除关联后删除", role.getName()); boolean deleteResult = this.removeById(roleId); - if(deleteResult) { + if (deleteResult) { // 删除成功,刷新权限缓存 - permissionService.refreshPermissionCache(role.getCode()); + permissionService.refreshRolePermsCache(role.getCode()); } } return true; @@ -193,8 +220,14 @@ public class SysRoleServiceImpl extends ServiceImpl impl @Transactional @CacheEvict(cacheNames = "menu", key = "'routes'") public boolean assignMenusToRole(Long roleId, List menuIds) { + SysRole role = this.getById(roleId); + Assert.isTrue(role != null, "角色不存在"); + // 删除角色菜单 - roleMenuService.remove(new LambdaQueryWrapper().eq(SysRoleMenu::getRoleId, roleId)); + roleMenuService.remove( + new LambdaQueryWrapper() + .eq(SysRoleMenu::getRoleId, roleId) + ); // 新增角色菜单 if (CollectionUtil.isNotEmpty(menuIds)) { List roleMenus = menuIds @@ -203,6 +236,10 @@ public class SysRoleServiceImpl extends ServiceImpl impl .toList(); roleMenuService.saveBatch(roleMenus); } + + // 刷新角色的权限缓存 + permissionService.refreshRolePermsCache(role.getCode()); + return true; } diff --git a/src/main/java/com/youlai/system/service/impl/SysUserServiceImpl.java b/src/main/java/com/youlai/system/service/impl/SysUserServiceImpl.java index f75490f1..85f5814d 100644 --- a/src/main/java/com/youlai/system/service/impl/SysUserServiceImpl.java +++ b/src/main/java/com/youlai/system/service/impl/SysUserServiceImpl.java @@ -22,9 +22,9 @@ import com.youlai.system.model.query.UserPageQuery; import com.youlai.system.model.vo.UserExportVO; import com.youlai.system.model.vo.UserInfoVO; import com.youlai.system.model.vo.UserPageVO; +import com.youlai.system.security.service.PermissionService; import com.youlai.system.service.*; import lombok.RequiredArgsConstructor; -import org.springframework.data.redis.core.RedisTemplate; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -54,7 +54,7 @@ public class SysUserServiceImpl extends ServiceImpl impl private final SysRoleService roleService; - private final RedisTemplate redisTemplate; + private final PermissionService permissionService; /** * 获取用户分页列表 @@ -254,7 +254,7 @@ public class SysUserServiceImpl extends ServiceImpl impl // 用户权限集合 if (CollectionUtil.isNotEmpty(roles)) { - Set perms = menuService.listRolePerms(roles); + Set perms = permissionService.getRolePermsFormCache(roles); userInfoVO.setPerms(perms); } return userInfoVO; diff --git a/src/main/resources/mapper/SysRoleMenuMapper.xml b/src/main/resources/mapper/SysRoleMenuMapper.xml index bcee8836..51833508 100644 --- a/src/main/resources/mapper/SysRoleMenuMapper.xml +++ b/src/main/resources/mapper/SysRoleMenuMapper.xml @@ -30,7 +30,7 @@ t3.perm FROM `sys_role_menu` t1 - INNER JOIN sys_role t2 ON t1.role_id = t2.id AND t2.deleted = 0 + INNER JOIN sys_role t2 ON t1.role_id = t2.id AND t2.deleted = 0 AND t2.`status` = 1 INNER JOIN sys_menu t3 ON t1.menu_id = t3.id WHERE type = '${@com.youlai.system.common.enums.MenuTypeEnum@BUTTON.getValue()}'