refactor: 拆分多租户

This commit is contained in:
Ray.Hao
2025-12-15 08:05:24 +08:00
parent 3f05f77351
commit 5817826bbd
57 changed files with 297 additions and 2291 deletions

View File

@@ -7,7 +7,7 @@ import cn.hutool.json.JSONUtil;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.common.constant.SecurityConstants;
import com.youlai.boot.core.web.ResultCode;
import com.youlai.boot.core.web.WebResponseHelper;
import com.youlai.boot.core.web.WebResponseWriter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
@@ -61,7 +61,7 @@ public class CaptchaValidationFilter extends OncePerRequestFilter {
// 仅支持 JSON 登录
String contentType = request.getContentType();
if (contentType == null || !contentType.contains(MediaType.APPLICATION_JSON_VALUE)) {
WebResponseHelper.writeError(response, ResultCode.USER_VERIFICATION_CODE_ERROR);
WebResponseWriter.writeError(response, ResultCode.USER_VERIFICATION_CODE_ERROR);
return;
}
@@ -80,7 +80,7 @@ public class CaptchaValidationFilter extends OncePerRequestFilter {
}
if (StrUtil.isBlank(captchaCode) || StrUtil.isBlank(captchaId)) {
WebResponseHelper.writeError(response, ResultCode.USER_VERIFICATION_CODE_ERROR);
WebResponseWriter.writeError(response, ResultCode.USER_VERIFICATION_CODE_ERROR);
return;
}
@@ -88,7 +88,7 @@ public class CaptchaValidationFilter extends OncePerRequestFilter {
StrUtil.format(RedisConstants.Captcha.IMAGE_CODE, captchaId)
);
if (cacheVerifyCode == null) {
WebResponseHelper.writeError(response, ResultCode.USER_VERIFICATION_CODE_EXPIRED);
WebResponseWriter.writeError(response, ResultCode.USER_VERIFICATION_CODE_EXPIRED);
return;
}
@@ -96,7 +96,7 @@ public class CaptchaValidationFilter extends OncePerRequestFilter {
HttpServletRequest repeatableRequest = new RepeatableReadRequestWrapper(requestWrapper, bodyBytes);
chain.doFilter(repeatableRequest, response);
} else {
WebResponseHelper.writeError(response, ResultCode.USER_VERIFICATION_CODE_ERROR);
WebResponseWriter.writeError(response, ResultCode.USER_VERIFICATION_CODE_ERROR);
}
}

View File

@@ -3,7 +3,7 @@ package com.youlai.boot.security.filter;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.common.constant.SecurityConstants;
import com.youlai.boot.core.web.ResultCode;
import com.youlai.boot.core.web.WebResponseHelper;
import com.youlai.boot.core.web.WebResponseWriter;
import com.youlai.boot.security.token.TokenManager;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
@@ -52,7 +52,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
// 执行令牌有效性检查(包含密码学验签和过期时间验证)
boolean isValidToken = tokenManager.validateToken(rawToken);
if (!isValidToken) {
WebResponseHelper.writeError(response, ResultCode.ACCESS_TOKEN_INVALID);
WebResponseWriter.writeError(response, ResultCode.ACCESS_TOKEN_INVALID);
return;
}
@@ -63,7 +63,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
} catch (Exception ex) {
// 安全上下文清除保障(防止上下文残留)
SecurityContextHolder.clearContext();
WebResponseHelper.writeError(response, ResultCode.ACCESS_TOKEN_INVALID);
WebResponseWriter.writeError(response, ResultCode.ACCESS_TOKEN_INVALID);
return;
}

View File

@@ -1,7 +1,7 @@
package com.youlai.boot.security.handler;
import com.youlai.boot.core.web.ResultCode;
import com.youlai.boot.core.web.WebResponseHelper;
import com.youlai.boot.core.web.WebResponseWriter;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
@@ -20,7 +20,7 @@ public class MyAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) {
WebResponseHelper.writeError(response, ResultCode.ACCESS_UNAUTHORIZED);
WebResponseWriter.writeError(response, ResultCode.ACCESS_UNAUTHORIZED);
}
}

View File

@@ -1,7 +1,7 @@
package com.youlai.boot.security.handler;
import com.youlai.boot.core.web.ResultCode;
import com.youlai.boot.core.web.WebResponseHelper;
import com.youlai.boot.core.web.WebResponseWriter;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
@@ -32,13 +32,13 @@ public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException {
if (authException instanceof BadCredentialsException) {
// 用户名或密码错误
WebResponseHelper.writeError(response, ResultCode.USER_PASSWORD_ERROR);
WebResponseWriter.writeError(response, ResultCode.USER_PASSWORD_ERROR);
} else if(authException instanceof InsufficientAuthenticationException){
// 请求头缺失Authorization、Token格式错误、Token过期、签名验证失败
WebResponseHelper.writeError(response, ResultCode.ACCESS_TOKEN_INVALID);
WebResponseWriter.writeError(response, ResultCode.ACCESS_TOKEN_INVALID);
} else {
// 其他未明确处理的认证异常(如账户被锁定、账户禁用等)
WebResponseHelper.writeError(response, ResultCode.USER_LOGIN_EXCEPTION, authException.getMessage());
WebResponseWriter.writeError(response, ResultCode.USER_LOGIN_EXCEPTION, authException.getMessage());
}
}
}

View File

@@ -38,11 +38,6 @@ public class OnlineUser {
*/
private Integer dataScope;
/**
* 租户ID
*/
private Long tenantId;
/**
* 角色权限集合
*/

View File

@@ -56,11 +56,6 @@ public class SysUserDetails implements UserDetails {
*/
private Integer dataScope;
/**
* 租户ID
*/
private Long tenantId;
/**
* 用户角色权限集合
*/
@@ -78,7 +73,6 @@ public class SysUserDetails implements UserDetails {
this.enabled = ObjectUtil.equal(user.getStatus(), 1);
this.deptId = user.getDeptId();
this.dataScope = user.getDataScope();
this.tenantId = user.getTenantId();
// 初始化角色权限集合
this.authorities = CollectionUtil.isNotEmpty(user.getRoles())

View File

@@ -54,9 +54,4 @@ public class UserAuthCredentials {
*/
private Integer dataScope;
/**
* 租户ID从登录上下文中获取
*/
private Long tenantId;
}

View File

@@ -3,8 +3,6 @@ package com.youlai.boot.security.service;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.youlai.boot.common.constant.RedisConstants;
import com.youlai.boot.common.tenant.TenantContextHolder;
import com.youlai.boot.config.property.TenantProperties;
import com.youlai.boot.security.util.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@@ -26,7 +24,6 @@ import java.util.*;
public class PermissionService {
private final RedisTemplate<String, Object> redisTemplate;
private final TenantProperties tenantProperties;
/**
* 判断当前登录用户是否拥有操作权限
@@ -70,20 +67,7 @@ public class PermissionService {
/**
* 构建租户权限缓存key
*
* @param tenantId 租户ID
* @return 缓存key
*/
private String buildRolePermsCacheKey(Long tenantId) {
if (!tenantProperties.getEnabled() || tenantId == null) {
return RedisConstants.System.ROLE_PERMS;
}
return RedisConstants.System.ROLE_PERMS + ":" + tenantId;
}
/**
* 从缓存中获取角色权限列表(兼容单租户和多租户)
* 从缓存中获取角色权限列表
*
* @param roleCodes 角色编码集合
* @return 角色权限列表
@@ -93,9 +77,8 @@ public class PermissionService {
return Collections.emptySet();
}
// 获取当前租户ID并构建缓存Key
Long tenantId = TenantContextHolder.getTenantId();
String cacheKey = buildRolePermsCacheKey(tenantId);
// 构建缓存Key
String cacheKey = RedisConstants.System.ROLE_PERMS;
Set<String> perms = new HashSet<>();
Collection<Object> roleCodesAsObjects = new ArrayList<>(roleCodes);

View File

@@ -1,6 +1,5 @@
package com.youlai.boot.security.service;
import com.youlai.boot.common.tenant.TenantContextHolder;
import com.youlai.boot.security.model.SysUserDetails;
import com.youlai.boot.security.model.UserAuthCredentials;
import com.youlai.boot.system.service.UserService;
@@ -38,8 +37,6 @@ public class SysUserDetailsService implements UserDetailsService {
if (userAuthCredentials == null) {
throw new UsernameNotFoundException(username);
}
// 将当前上下文中的租户ID写入认证凭证便于后续 Token 携带租户信息
userAuthCredentials.setTenantId(TenantContextHolder.getTenantId());
return new SysUserDetails(userAuthCredentials);
} catch (Exception e) {
// 记录异常日志

View File

@@ -91,7 +91,6 @@ public class JwtTokenManager implements TokenManager {
userDetails.setUserId(payloads.getLong(JwtClaimConstants.USER_ID)); // 用户ID
userDetails.setDeptId(payloads.getLong(JwtClaimConstants.DEPT_ID)); // 部门ID
userDetails.setDataScope(payloads.getInt(JwtClaimConstants.DATA_SCOPE)); // 数据权限范围
userDetails.setTenantId(payloads.getLong(JwtClaimConstants.TENANT_ID)); // 租户ID
userDetails.setUsername(payloads.getStr(JWTPayload.SUBJECT)); // 用户名
// 角色集合
@@ -276,7 +275,6 @@ public class JwtTokenManager implements TokenManager {
payload.put(JwtClaimConstants.USER_ID, userDetails.getUserId()); // 用户ID
payload.put(JwtClaimConstants.DEPT_ID, userDetails.getDeptId()); // 部门ID
payload.put(JwtClaimConstants.DATA_SCOPE, userDetails.getDataScope()); // 数据权限范围
payload.put(JwtClaimConstants.TENANT_ID, userDetails.getTenantId()); // 租户ID
// claims 中添加角色信息
Set<String> roles = authentication.getAuthorities().stream()

View File

@@ -61,7 +61,6 @@ public class RedisTokenManager implements TokenManager {
user.getUsername(),
user.getDeptId(),
user.getDataScope(),
user.getTenantId(),
user.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.toSet())
@@ -269,7 +268,6 @@ public class RedisTokenManager implements TokenManager {
userDetails.setUsername(onlineUser.getUsername());
userDetails.setDeptId(onlineUser.getDeptId());
userDetails.setDataScope(onlineUser.getDataScope());
userDetails.setTenantId(onlineUser.getTenantId());
userDetails.setAuthorities(authorities);
return userDetails;
}