修改为登陆后选择租户

This commit is contained in:
陈博文
2025-06-20 17:37:15 +08:00
parent a07b4c5419
commit 8f6d56a71a
12 changed files with 58 additions and 18 deletions

View File

@@ -1,7 +1,6 @@
package cn.iocoder.yudao.framework.tenant.core.web; package cn.iocoder.yudao.framework.tenant.core.web;
import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.ObjUtil;
import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService; import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
@@ -14,8 +13,6 @@ import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.web.servlet.HandlerInterceptor; import org.springframework.web.servlet.HandlerInterceptor;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
public class TenantVisitContextInterceptor implements HandlerInterceptor { public class TenantVisitContextInterceptor implements HandlerInterceptor {
@@ -42,10 +39,10 @@ public class TenantVisitContextInterceptor implements HandlerInterceptor {
return true; return true;
} }
// 校验用户是否可切换租户 // // 校验用户是否可切换租户
if (!securityFrameworkService.hasAnyPermissions(PERMISSION)) { // if (!securityFrameworkService.hasAnyPermissions(PERMISSION)) {
throw exception0(GlobalErrorCodeConstants.FORBIDDEN.getCode(), "您无权切换租户"); // throw exception0(GlobalErrorCodeConstants.FORBIDDEN.getCode(), "您无权切换租户");
} // }
// 【重点】切换租户编号 // 【重点】切换租户编号
loginUser.setVisitTenantId(visitTenantId); loginUser.setVisitTenantId(visitTenantId);

View File

@@ -20,6 +20,7 @@ public class LoginUser {
public static final String INFO_KEY_NICKNAME = "nickname"; public static final String INFO_KEY_NICKNAME = "nickname";
public static final String INFO_KEY_DEPT_ID = "deptId"; public static final String INFO_KEY_DEPT_ID = "deptId";
public static final String INFO_KEY_TENANT_ID = "tenantId";
/** /**
* 用户编号 * 用户编号

View File

@@ -2,6 +2,8 @@ package cn.iocoder.yudao.framework.security.core.filter;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import cn.iocoder.yudao.framework.common.exception.ServiceException; import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.pojo.CommonResult; import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
@@ -11,8 +13,6 @@ import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils; import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler; import cn.iocoder.yudao.framework.web.core.handler.GlobalExceptionHandler;
import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils; import cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.OAuth2TokenCommonApi;
import cn.iocoder.yudao.framework.common.biz.system.oauth2.dto.OAuth2AccessTokenCheckRespDTO;
import jakarta.servlet.FilterChain; import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException; import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
@@ -82,6 +82,7 @@ public class TokenAuthenticationFilter extends OncePerRequestFilter {
private LoginUser buildLoginUserByToken(String token, Integer userType) { private LoginUser buildLoginUserByToken(String token, Integer userType) {
try { try {
// 校验访问令牌 // 校验访问令牌
OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token).getCheckedData(); OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token).getCheckedData();
if (accessToken == null) { if (accessToken == null) {
return null; return null;

View File

@@ -12,7 +12,7 @@ spring:
metadata: metadata:
version: 1.0.0 # 服务实例的版本号,可用于灰度发布 version: 1.0.0 # 服务实例的版本号,可用于灰度发布
config: # 【注册中心】配置项 config: # 【注册中心】配置项
namespace: dev # 命名空间。这里使用 dev 开发环境 namespace: local # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
# 日志文件配置 # 日志文件配置

View File

@@ -13,7 +13,7 @@ spring:
metadata: metadata:
version: 1.0.0 # 服务实例的版本号,可用于灰度发布 version: 1.0.0 # 服务实例的版本号,可用于灰度发布
config: # 【注册中心】配置项 config: # 【注册中心】配置项
namespace: dev # 命名空间。这里使用 dev 开发环境 namespace: local # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
--- #################### 数据库相关配置 #################### --- #################### 数据库相关配置 ####################

View File

@@ -55,4 +55,6 @@ public class TenantRespVO {
@ExcelProperty("创建时间") @ExcelProperty("创建时间")
private LocalDateTime createTime; private LocalDateTime createTime;
@Schema(description = "是否可选,如果为 false 不允许在下拉选项中出现")
private Boolean optional = true;
} }

View File

@@ -146,4 +146,15 @@ public class UserTenantController {
return success(userTenantService.getTenantBelongSimpleList()); return success(userTenantService.getTenantBelongSimpleList());
} }
/**
* 未关联组织机构租户 simpleList 查询接口
*/
@GetMapping("/independent-tenant-simple-list")
@Operation(summary = "获得租户 simpleList 列表")
// 不使用默认的租户查询方式,此处需要获取租户以及下属租户的相关数据,在 mapper 自行实现
@TenantIgnore
public CommonResult<List<TenantRespVO>> getIndependentTenantSimpleList() {
return success(userTenantService.getIndependentTenantSimpleList());
}
} }

View File

@@ -1,7 +1,7 @@
package cn.iocoder.yudao.module.system.dal.dataobject.user; package cn.iocoder.yudao.module.system.dal.dataobject.user;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO;
import cn.iocoder.yudao.module.system.enums.common.SexEnum; import cn.iocoder.yudao.module.system.enums.common.SexEnum;
import com.baomidou.mybatisplus.annotation.*; import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler; import com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;
@@ -23,7 +23,7 @@ import java.util.Set;
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class AdminUserDO extends BaseDO { public class AdminUserDO extends TenantBaseDO {
/** /**
* 用户ID * 用户ID

View File

@@ -11,7 +11,6 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.date.DateUtils; import cn.iocoder.yudao.framework.common.util.date.DateUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils; import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;
import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
@@ -159,13 +158,14 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
} }
private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) { private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {
Map<String, String> userInfo = buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType());
OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken()) OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken())
.setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType()) .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType())
.setUserInfo(buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType())) .setUserInfo(userInfo)
.setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes()) .setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes())
.setRefreshToken(refreshTokenDO.getRefreshToken()) .setRefreshToken(refreshTokenDO.getRefreshToken())
.setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds())); .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds()));
accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号,避免缓存到 Redis 的时候,无对应的租户编号 accessTokenDO.setTenantId(Long.parseLong(userInfo.getOrDefault(LoginUser.INFO_KEY_TENANT_ID,"0")));
oauth2AccessTokenMapper.insert(accessTokenDO); oauth2AccessTokenMapper.insert(accessTokenDO);
// 记录到 Redis 中 // 记录到 Redis 中
oauth2AccessTokenRedisDAO.set(accessTokenDO); oauth2AccessTokenRedisDAO.set(accessTokenDO);
@@ -200,7 +200,8 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService {
if (userType.equals(UserTypeEnum.ADMIN.getValue())) { if (userType.equals(UserTypeEnum.ADMIN.getValue())) {
AdminUserDO user = adminUserService.getUser(userId); AdminUserDO user = adminUserService.getUser(userId);
return MapUtil.builder(LoginUser.INFO_KEY_NICKNAME, user.getNickname()) return MapUtil.builder(LoginUser.INFO_KEY_NICKNAME, user.getNickname())
.put(LoginUser.INFO_KEY_DEPT_ID, StrUtil.toStringOrNull(user.getDeptId())).build(); .put(LoginUser.INFO_KEY_DEPT_ID, StrUtil.toStringOrNull(user.getDeptId()))
.put(LoginUser.INFO_KEY_TENANT_ID, user.getTenantId().toString()).build();
} else if (userType.equals(UserTypeEnum.MEMBER.getValue())) { } else if (userType.equals(UserTypeEnum.MEMBER.getValue())) {
// 注意:目前 Member 暂时不读取,可以按需实现 // 注意:目前 Member 暂时不读取,可以按需实现
return Collections.emptyMap(); return Collections.emptyMap();

View File

@@ -81,5 +81,10 @@ public interface UserTenantService {
* @return 租户列表(只含 id、name * @return 租户列表(只含 id、name
*/ */
List<TenantRespVO> getTenantBelongSimpleList(); List<TenantRespVO> getTenantBelongSimpleList();
/**
* 未关联组织机构租户 simpleList 查询接口 列表
* @return 租户列表(只含 id、name
*/
List<TenantRespVO> getIndependentTenantSimpleList();
} }

View File

@@ -4,6 +4,7 @@ import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.framework.security.core.LoginUser; import cn.iocoder.yudao.framework.security.core.LoginUser;
import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantRespVO; import cn.iocoder.yudao.module.system.controller.admin.tenant.vo.tenant.TenantRespVO;
import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSimpleRespVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSimpleRespVO;
@@ -180,6 +181,27 @@ public class UserTenantServiceImpl implements UserTenantService {
return BeanUtils.toBean(tenants, TenantRespVO.class); return BeanUtils.toBean(tenants, TenantRespVO.class);
} }
/**
* 未关联组织机构租户 simpleList 查询接口 列表
*
* @return 租户列表(只含 id、name
*/
@Override
public List<TenantRespVO> getIndependentTenantSimpleList() {
// 查询所有已关联组织的租户
List<DeptDO> bondDepts = deptMapper.selectList(DeptDO::getIsTenant, true);
Set<Long> tenantIdSet = convertSet(bondDepts,DeptDO::getTenantId);
// 查询 not in tenantIdSet 的租户信息
List<TenantDO> bondTenants = tenantMapper.selectList(new LambdaQueryWrapperX<TenantDO>().in(TenantDO::getId, tenantIdSet));
List<TenantDO> tenants = tenantMapper.selectList(new LambdaQueryWrapperX<TenantDO>().notIn(TenantDO::getId, tenantIdSet));
List<TenantRespVO> result = BeanUtils.toBean(tenants, TenantRespVO.class);
// 不可选择的下拉
List<TenantRespVO> notOptionalResult = BeanUtils.toBean(bondTenants, TenantRespVO.class);
notOptionalResult.forEach(notOptional -> notOptional.setOptional(false));
result.addAll(notOptionalResult);
return result;
}
/** /**
* 递归获取所有下属部门(不含自身) * 递归获取所有下属部门(不含自身)
*/ */

View File

@@ -12,7 +12,7 @@ spring:
metadata: metadata:
version: 1.0.0 # 服务实例的版本号,可用于灰度发布 version: 1.0.0 # 服务实例的版本号,可用于灰度发布
config: # 【注册中心】配置项 config: # 【注册中心】配置项
namespace: dev # 命名空间。这里使用 dev 开发环境 namespace: local # 命名空间。这里使用 dev 开发环境
group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP group: DEFAULT_GROUP # 使用的 Nacos 配置分组,默认为 DEFAULT_GROUP
--- #################### 数据库相关配置 #################### --- #################### 数据库相关配置 ####################