feat(auth): 添加免登录切换租户功能

- 新增接口获取当前用户可切换的租户列表,支持手机号和邮箱聚合租户
- 实现免登录切换租户接口,需判断当前用户权限及目标租户状态
- 编写辅助方法查找可切换的用户账户
- 添加将用户信息转换为租户选项的工具方法
- 在切换成功后生成新的访问令牌并保存登录记录
This commit is contained in:
2026-08-03 17:54:16 +08:00
parent a8896e4523
commit 2581a77f09

View File

@@ -65,6 +65,7 @@ import java.net.URLEncoder;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@@ -1131,6 +1132,105 @@ public class MainController extends BaseController {
return success("登录成功", new LoginResult(access_token, user)); return success("登录成功", new LoginResult(access_token, user));
} }
@Operation(summary = "获取当前登录用户可切换的租户列表(免登录切换前置)")
@GetMapping("/auth/switchableTenants")
public ApiResult<?> switchableTenants() {
User current = getLoginUser();
if (current == null) {
return fail("未登录或登录已过期", null);
}
List<TenantOption> options = new ArrayList<>();
// 按手机号聚合(仅管理员账号)
if (StrUtil.isNotBlank(current.getPhone())) {
LoginParam phoneParam = new LoginParam();
phoneParam.setPhone(current.getPhone());
List<User> byPhone = userService.getAdminsByPhone(phoneParam);
if (byPhone != null) {
for (User u : byPhone) {
options.add(toTenantOption(u));
}
}
}
// 按邮箱聚合(管理员账号,按租户去重)
if (StrUtil.isNotBlank(current.getEmail())) {
List<User> byEmail = userService.findAccountsByEmail(current.getEmail());
if (byEmail != null) {
for (User u : byEmail) {
if (!Boolean.TRUE.equals(u.getIsAdmin())) {
continue;
}
final Integer tId = u.getTenantId();
if (tId != null && options.stream().noneMatch(o -> tId.equals(o.getTenantId()))) {
options.add(toTenantOption(u));
}
}
}
}
return success(options);
}
@Operation(summary = "免登录切换租户(需已登录,当前用户须为目标租户管理员)")
@PostMapping("/auth/switchTenant")
public ApiResult<LoginResult> switchTenant(@RequestBody LoginParam param, HttpServletRequest request) {
Integer targetTenantId = param.getTenantId();
if (targetTenantId == null) {
return fail("请选择租户", null);
}
User current = getLoginUser();
if (current == null) {
return fail("未登录或登录已过期", null);
}
// 校验目标租户在可切换范围内(与 switchableTenants 一致)
User target = findSwitchableUser(current, targetTenantId);
if (target == null) {
return fail("无权切换到该租户", null);
}
if (!target.getStatus().equals(0)) {
return fail("该租户账号已被冻结", null);
}
String access_token = generateToken(target);
loginRecordService.saveAsync(target.getUsername(), LoginRecord.TYPE_LOGIN, null, target.getTenantId(), request);
return success("切换成功", new LoginResult(access_token, target));
}
private User findSwitchableUser(User current, Integer targetTenantId) {
List<User> candidates = new ArrayList<>();
if (StrUtil.isNotBlank(current.getPhone())) {
LoginParam phoneParam = new LoginParam();
phoneParam.setPhone(current.getPhone());
List<User> byPhone = userService.getAdminsByPhone(phoneParam);
if (byPhone != null) {
candidates.addAll(byPhone);
}
}
if (StrUtil.isNotBlank(current.getEmail())) {
List<User> byEmail = userService.findAccountsByEmail(current.getEmail());
if (byEmail != null) {
for (User u : byEmail) {
if (Boolean.TRUE.equals(u.getIsAdmin())) {
candidates.add(u);
}
}
}
}
return candidates.stream()
.filter(u -> targetTenantId.equals(u.getTenantId()))
.findFirst()
.orElse(null);
}
private TenantOption toTenantOption(User u) {
return new TenantOption(
u.getTenantId(),
u.getTenantName(),
u.getUserId(),
u.getUsername(),
u.getNickname(),
null,
u.getAvatar()
);
}
/** /**
* 生成 JWT token 并存入 Redis * 生成 JWT token 并存入 Redis
*/ */