refactor: 用户权限方案重构,黑名单实现 JWT 主动注销
This commit is contained in:
@@ -58,9 +58,6 @@ youlai-boot
|
||||
├── XxlJobConfig # XXL-JOB 自动装配配置
|
||||
├── controller # 控制层
|
||||
├── converter # MapStruct 转换器
|
||||
├── core # 核心模块
|
||||
├── security # Spring Security 安全配置和扩展
|
||||
├── mybatis # Mybatis-Plus 配置和插件
|
||||
├── filter # 过滤器
|
||||
├── RequestLogFilter # 请求日志过滤器
|
||||
├── VerifyCodeFilter # 验证码过滤器
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -7,7 +7,7 @@
|
||||
<groupId>com.youlai</groupId>
|
||||
<artifactId>youlai-boot</artifactId>
|
||||
<version>2.6.0</version>
|
||||
<description>基于 Java 17 + SpringBoot 3 构建的权限管理系统。</description>
|
||||
<description>基于 Java 17 + SpringBoot 3 + Spring Security 构建的权限管理系统。</description>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -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<String, Object> 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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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<String, Object> redisTemplate;
|
||||
|
||||
private final CodeGenerator codeGenerator;
|
||||
|
||||
public CaptchaValidationFilter(RedisTemplate<String, Object> 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);
|
||||
@@ -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<String, Object> redisTemplate;
|
||||
|
||||
public JwtTokenFilter(RedisTemplate<String, Object> redisTemplate) {
|
||||
public JwtValidationFilter(RedisTemplate<String, Object> redisTemplate) {
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.youlai.system.common.constant;
|
||||
package com.youlai.system.security.constant;
|
||||
|
||||
/**
|
||||
* Security 常量
|
||||
@@ -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<String,Object> redisTemplate;
|
||||
|
||||
private final SysRoleMenuService roleMenuService;
|
||||
|
||||
|
||||
/**
|
||||
* 初始化权限缓存
|
||||
*/
|
||||
@PostConstruct
|
||||
public void initRolePermsCache() {
|
||||
refreshRolePermsCache();
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新权限缓存
|
||||
*/
|
||||
public void refreshRolePermsCache() {
|
||||
// 清理权限缓存
|
||||
redisTemplate.opsForHash().delete(CacheConstants.ROLE_PERMS_PREFIX, "*");
|
||||
|
||||
List<RolePermsBO> list = roleMenuService.getRolePermsList(null);
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
list.forEach(item -> {
|
||||
String roleCode = item.getRoleCode();
|
||||
Set<String> 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<RolePermsBO> list = roleMenuService.getRolePermsList(roleCode);
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
RolePermsBO rolePerms = list.get(0);
|
||||
if (rolePerms == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> 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<RolePermsBO> list = roleMenuService.getRolePermsList(newRoleCode);
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
RolePermsBO rolePerms = list.get(0);
|
||||
if (rolePerms == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> perms = rolePerms.getPerms();
|
||||
redisTemplate.opsForHash().put(CacheConstants.ROLE_PERMS_PREFIX, newRoleCode, perms);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取角色权限列表
|
||||
*
|
||||
* @param roleCodes 角色编码集合
|
||||
* @return 角色权限列表
|
||||
*/
|
||||
public Set<String> getRolePermsFormCache(Set<String> roleCodes) {
|
||||
// 检查输入是否为空
|
||||
if (CollectionUtil.isEmpty(roleCodes)) {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
Set<String> perms = new HashSet<>();
|
||||
// 从缓存中一次性获取所有角色的权限
|
||||
Collection<Object> roleCodesAsObjects = new ArrayList<>(roleCodes);
|
||||
List<Object> rolePermsList = redisTemplate.opsForHash().multiGet(CacheConstants.ROLE_PERMS_PREFIX, roleCodesAsObjects);
|
||||
|
||||
for (Object rolePermsObj : rolePermsList) {
|
||||
if (rolePermsObj instanceof Set) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Set<String> rolePerms = (Set<String>) 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<RolePermsBO> list = roleMenuService.getRolePermsList(null);
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
list.forEach(item -> {
|
||||
String roleCode = item.getRoleCode();
|
||||
Set<String> 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<RolePermsBO> list = roleMenuService.getRolePermsList(roleCode);
|
||||
if (CollectionUtil.isNotEmpty(list)) {
|
||||
RolePermsBO rolePerms = list.get(0);
|
||||
if (rolePerms == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Set<String> perms = rolePerms.getPerms();
|
||||
redisTemplate.opsForHash().put(CacheConstants.ROLE_PERMS_PREFIX, roleCode, perms);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<String, Object> payload = new HashMap<>();
|
||||
payload.put(JwtClaimConstants.USER_ID, userDetails.getUserId()); // 用户ID
|
||||
payload.put(JwtClaimConstants.DEPT_ID, userDetails.getDeptId()); // 部门ID
|
||||
|
||||
@@ -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<String,Object> redisTemplate;
|
||||
private final CodeGenerator codeGenerator;
|
||||
private final Font captchaFont;
|
||||
private final CaptchaProperties captchaProperties;
|
||||
|
||||
@@ -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<SysMenuMapper, SysMenu> impl
|
||||
|
||||
private final MenuConverter menuConverter;
|
||||
|
||||
private final PermissionService permissionService;
|
||||
|
||||
|
||||
/**
|
||||
* 菜单列表
|
||||
@@ -199,18 +202,17 @@ public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> 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<SysMenuMapper, SysMenu> 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<SysMenuMapper, SysMenu> impl
|
||||
@Override
|
||||
@CacheEvict(cacheNames = "menu", key = "'routes'")
|
||||
public boolean deleteMenu(Long id) {
|
||||
return this.remove(new LambdaQueryWrapper<SysMenu>()
|
||||
.eq(SysMenu::getId, id)
|
||||
.or()
|
||||
.apply("CONCAT (',',tree_path,',') LIKE CONCAT('%,',{0},',%')", id));
|
||||
boolean result = this.remove(new LambdaQueryWrapper<SysMenu>()
|
||||
.eq(SysMenu::getId, id)
|
||||
.or()
|
||||
.apply("CONCAT (',',tree_path,',') LIKE CONCAT('%,',{0},',%')", id));
|
||||
|
||||
|
||||
// 刷新角色权限缓存
|
||||
if (result) {
|
||||
permissionService.refreshRolePermsCache();
|
||||
}
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<SysRoleMapper, SysRole> 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<SysRole>()
|
||||
.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<SysRoleMapper, SysRole> impl
|
||||
*/
|
||||
@Override
|
||||
public boolean updateRoleStatus(Long roleId, Integer status) {
|
||||
return this.update(new LambdaUpdateWrapper<SysRole>()
|
||||
.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<SysRoleMapper, SysRole> 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<SysRoleMapper, SysRole> impl
|
||||
@Transactional
|
||||
@CacheEvict(cacheNames = "menu", key = "'routes'")
|
||||
public boolean assignMenusToRole(Long roleId, List<Long> menuIds) {
|
||||
SysRole role = this.getById(roleId);
|
||||
Assert.isTrue(role != null, "角色不存在");
|
||||
|
||||
// 删除角色菜单
|
||||
roleMenuService.remove(new LambdaQueryWrapper<SysRoleMenu>().eq(SysRoleMenu::getRoleId, roleId));
|
||||
roleMenuService.remove(
|
||||
new LambdaQueryWrapper<SysRoleMenu>()
|
||||
.eq(SysRoleMenu::getRoleId, roleId)
|
||||
);
|
||||
// 新增角色菜单
|
||||
if (CollectionUtil.isNotEmpty(menuIds)) {
|
||||
List<SysRoleMenu> roleMenus = menuIds
|
||||
@@ -203,6 +236,10 @@ public class SysRoleServiceImpl extends ServiceImpl<SysRoleMapper, SysRole> impl
|
||||
.toList();
|
||||
roleMenuService.saveBatch(roleMenus);
|
||||
}
|
||||
|
||||
// 刷新角色的权限缓存
|
||||
permissionService.refreshRolePermsCache(role.getCode());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<SysUserMapper, SysUser> impl
|
||||
|
||||
private final SysRoleService roleService;
|
||||
|
||||
private final RedisTemplate redisTemplate;
|
||||
private final PermissionService permissionService;
|
||||
|
||||
/**
|
||||
* 获取用户分页列表
|
||||
@@ -254,7 +254,7 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
||||
|
||||
// 用户权限集合
|
||||
if (CollectionUtil.isNotEmpty(roles)) {
|
||||
Set<String> perms = menuService.listRolePerms(roles);
|
||||
Set<String> perms = permissionService.getRolePermsFormCache(roles);
|
||||
userInfoVO.setPerms(perms);
|
||||
}
|
||||
return userInfoVO;
|
||||
|
||||
@@ -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()}'
|
||||
|
||||
Reference in New Issue
Block a user