refactor: 多租户开发和代码规范调整
This commit is contained in:
@@ -1,21 +1,33 @@
|
||||
package com.youlai.boot.auth.controller;
|
||||
|
||||
import com.youlai.boot.auth.model.vo.CaptchaVO;
|
||||
import com.youlai.boot.auth.model.vo.ChooseTenantVO;
|
||||
import com.youlai.boot.auth.model.dto.LoginRequest;
|
||||
import com.youlai.boot.auth.model.dto.WxMiniAppPhoneLoginDTO;
|
||||
import com.youlai.boot.common.enums.LogModuleEnum;
|
||||
import com.youlai.boot.config.property.TenantProperties;
|
||||
import com.youlai.boot.core.web.Result;
|
||||
import com.youlai.boot.auth.service.AuthService;
|
||||
import com.youlai.boot.auth.model.dto.WxMiniAppCodeLoginDTO;
|
||||
import com.youlai.boot.common.annotation.Log;
|
||||
import com.youlai.boot.core.web.ResultCode;
|
||||
import com.youlai.boot.security.model.AuthenticationToken;
|
||||
import com.youlai.boot.system.model.entity.User;
|
||||
import com.youlai.boot.system.model.vo.TenantVO;
|
||||
import com.youlai.boot.system.service.TenantService;
|
||||
import com.youlai.boot.system.service.UserService;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.validation.Valid;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
||||
/**
|
||||
* 认证控制层
|
||||
@@ -31,6 +43,10 @@ import org.springframework.web.bind.annotation.*;
|
||||
public class AuthController {
|
||||
|
||||
private final AuthService authService;
|
||||
private final UserService userService;
|
||||
private final TenantService tenantService;
|
||||
private final TenantProperties tenantProperties;
|
||||
private final PasswordEncoder passwordEncoder;
|
||||
|
||||
@Operation(summary = "获取验证码")
|
||||
@GetMapping("/captcha")
|
||||
@@ -42,12 +58,62 @@ public class AuthController {
|
||||
@Operation(summary = "账号密码登录")
|
||||
@PostMapping("/login")
|
||||
@Log(value = "登录", module = LogModuleEnum.LOGIN)
|
||||
public Result<AuthenticationToken> login(
|
||||
@Parameter(description = "用户名", example = "admin") @RequestParam String username,
|
||||
@Parameter(description = "密码", example = "123456") @RequestParam String password
|
||||
) {
|
||||
AuthenticationToken authenticationToken = authService.login(username, password);
|
||||
return Result.success(authenticationToken);
|
||||
public Result<?> login(@RequestBody @Valid LoginRequest request) {
|
||||
String username = request.getUsername();
|
||||
String password = request.getPassword();
|
||||
Long tenantId = request.getTenantId();
|
||||
|
||||
// 如果未启用多租户,直接登录
|
||||
if (tenantProperties == null || !Boolean.TRUE.equals(tenantProperties.getEnabled())) {
|
||||
AuthenticationToken authenticationToken = authService.login(username, password, null);
|
||||
return Result.success(authenticationToken);
|
||||
}
|
||||
|
||||
// 多租户模式:如果指定了租户ID,直接验证该租户下的密码
|
||||
if (tenantId != null) {
|
||||
AuthenticationToken authenticationToken = authService.login(username, password, tenantId);
|
||||
return Result.success(authenticationToken);
|
||||
}
|
||||
|
||||
// 多租户模式:未指定租户ID,查询该用户名在所有租户下的记录
|
||||
List<User> users = userService.listUsersByUsername(username);
|
||||
|
||||
if (users.isEmpty()) {
|
||||
return Result.failed("用户不存在");
|
||||
}
|
||||
|
||||
// 过滤出正常状态的用户
|
||||
List<User> activeUsers = users.stream()
|
||||
.filter(user -> user.getStatus() != null && user.getStatus() == 1)
|
||||
.toList();
|
||||
|
||||
if (activeUsers.isEmpty()) {
|
||||
return Result.failed("用户已被禁用");
|
||||
}
|
||||
|
||||
// 如果只有1个租户,尝试验证该租户下的密码(兼容性)
|
||||
if (activeUsers.size() == 1) {
|
||||
User user = activeUsers.get(0);
|
||||
// 登录(Spring Security 会验证密码)
|
||||
AuthenticationToken authenticationToken = authService.login(username, password, user.getTenantId());
|
||||
return Result.success(authenticationToken);
|
||||
}
|
||||
|
||||
// 如果多个租户,返回 choose_tenant 响应(含 tenants 列表)
|
||||
// 注意:此时不验证密码,直接返回租户列表让用户选择
|
||||
List<TenantVO> tenants = activeUsers.stream()
|
||||
.map(user -> tenantService.getTenantById(user.getTenantId()))
|
||||
.filter(tenant -> tenant != null && (tenant.getStatus() == null || tenant.getStatus() == 1))
|
||||
.distinct() // 去重(理论上不会有重复,但保险起见)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (tenants.isEmpty()) {
|
||||
return Result.failed("用户所属的租户均不可用");
|
||||
}
|
||||
|
||||
// 返回 choose_tenant 响应
|
||||
ChooseTenantVO chooseTenantVO = new ChooseTenantVO(tenants);
|
||||
return Result.failed(ResultCode.CHOOSE_TENANT, chooseTenantVO);
|
||||
}
|
||||
|
||||
@Operation(summary = "短信验证码登录")
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.youlai.boot.auth.model.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* 登录请求参数
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@Schema(description = "登录请求参数")
|
||||
@Data
|
||||
public class LoginRequest {
|
||||
|
||||
@Schema(description = "用户名", requiredMode = Schema.RequiredMode.REQUIRED, example = "admin")
|
||||
@NotBlank(message = "用户名不能为空")
|
||||
private String username;
|
||||
|
||||
@Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
|
||||
@NotBlank(message = "密码不能为空")
|
||||
private String password;
|
||||
|
||||
@Schema(description = "验证码缓存ID", example = "captcha_id_123")
|
||||
private String captchaId;
|
||||
|
||||
@Schema(description = "验证码", example = "1234")
|
||||
private String captchaCode;
|
||||
|
||||
@Schema(description = "租户ID(可选,多租户模式下用于指定租户)", example = "1")
|
||||
private Long tenantId;
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import lombok.Data;
|
||||
@Builder
|
||||
public class CaptchaVO {
|
||||
|
||||
@Schema(description = "验证码缓存 Key")
|
||||
private String captchaKey;
|
||||
@Schema(description = "验证码缓存 ID")
|
||||
private String captchaId;
|
||||
|
||||
@Schema(description = "验证码图片Base64字符串")
|
||||
private String captchaBase64;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.youlai.boot.auth.model.vo;
|
||||
|
||||
import com.youlai.boot.system.model.vo.TenantVO;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 选择租户响应VO
|
||||
*
|
||||
* @author Ray.Hao
|
||||
* @since 3.0.0
|
||||
*/
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "选择租户响应")
|
||||
public class ChooseTenantVO implements Serializable {
|
||||
|
||||
@Schema(description = "租户列表")
|
||||
private List<TenantVO> tenants;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,10 @@ public interface AuthService {
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @param tenantId 租户ID(可选,多租户模式下用于指定租户)
|
||||
* @return 登录结果
|
||||
*/
|
||||
AuthenticationToken login(String username, String password);
|
||||
AuthenticationToken login(String username, String password, Long tenantId);
|
||||
|
||||
/**
|
||||
* 登出
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.youlai.boot.security.model.WxMiniAppCodeAuthenticationToken;
|
||||
import com.youlai.boot.security.model.WxMiniAppPhoneAuthenticationToken;
|
||||
import com.youlai.boot.security.token.TokenManager;
|
||||
import com.youlai.boot.security.util.SecurityUtils;
|
||||
import com.youlai.boot.common.tenant.TenantContextHolder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
@@ -61,10 +62,16 @@ public class AuthServiceImpl implements AuthService {
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
* @param tenantId 租户ID(可选,多租户模式下用于指定租户)
|
||||
* @return 访问令牌
|
||||
*/
|
||||
@Override
|
||||
public AuthenticationToken login(String username, String password) {
|
||||
public AuthenticationToken login(String username, String password, Long tenantId) {
|
||||
// 如果指定了租户ID,需要先设置租户上下文,以便查询该租户下的用户
|
||||
if (tenantId != null) {
|
||||
com.youlai.boot.common.tenant.TenantContextHolder.setTenantId(tenantId);
|
||||
}
|
||||
|
||||
// 1. 创建用于密码认证的令牌(未认证)
|
||||
UsernamePasswordAuthenticationToken authenticationToken =
|
||||
new UsernamePasswordAuthenticationToken(username.trim(), password);
|
||||
@@ -194,16 +201,16 @@ public class AuthServiceImpl implements AuthService {
|
||||
String imageBase64Data = captcha.getImageBase64Data();
|
||||
|
||||
// 验证码文本缓存至Redis,用于登录校验
|
||||
String captchaKey = IdUtil.fastSimpleUUID();
|
||||
String captchaId = IdUtil.fastSimpleUUID();
|
||||
redisTemplate.opsForValue().set(
|
||||
StrUtil.format(RedisConstants.Captcha.IMAGE_CODE, captchaKey),
|
||||
StrUtil.format(RedisConstants.Captcha.IMAGE_CODE, captchaId),
|
||||
captchaCode,
|
||||
captchaProperties.getExpireSeconds(),
|
||||
TimeUnit.SECONDS
|
||||
);
|
||||
|
||||
return CaptchaVO.builder()
|
||||
.captchaKey(captchaKey)
|
||||
.captchaId(captchaId)
|
||||
.captchaBase64(imageBase64Data)
|
||||
.build();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user