diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginRespVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginRespVO.java index 1720d073..1815153c 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginRespVO.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthLoginRespVO.java @@ -27,4 +27,6 @@ public class AuthLoginRespVO { @Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime expiresTime; + private Long tenantId; + } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java index a86cedab..d7e17854 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java @@ -52,7 +52,7 @@ public class AuthPermissionInfoRespVO { @Schema(description = "用户邮箱", example = "yudao@iocoder.cn") private String email; - + private Long tenantId; } @Schema(description = "管理后台 - 登录用户的菜单信息 Response VO") diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java index 9a90117c..a5a3ceed 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/UserController.java @@ -178,4 +178,11 @@ public class UserController { return success(userService.importUserList(list, updateSupport)); } + @PutMapping("/fix-tenant-id") + @Operation(summary = "修复用户租户ID", description = "为租户ID为空的用户设置当前租户ID") + @PreAuthorize("@ss.hasPermission('system:user:update')") + public CommonResult fixUserTenantId(@RequestParam("userId") Long userId) { + return success(userService.fixUserTenantId(userId)); + } + } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java index 648382c1..9bb24a47 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/user/vo/user/UserSaveReqVO.java @@ -68,6 +68,8 @@ public class UserSaveReqVO { private Integer status; + private Long tenantId; + // ========== 仅【创建】时,需要传递的字段 ========== @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456") diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2AccessTokenDO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2AccessTokenDO.java index ef235ed0..5dce3869 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2AccessTokenDO.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/oauth2/OAuth2AccessTokenDO.java @@ -1,6 +1,7 @@ package cn.iocoder.yudao.module.system.dal.dataobject.oauth2; import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; +import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO; import cn.iocoder.yudao.framework.tenant.core.db.TenantBaseDO; import com.baomidou.mybatisplus.annotation.KeySequence; import com.baomidou.mybatisplus.annotation.TableField; @@ -26,7 +27,7 @@ import java.util.Map; @KeySequence("system_oauth2_access_token_seq") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库,可不写。 @Data @EqualsAndHashCode(callSuper = true) -public class OAuth2AccessTokenDO extends TenantBaseDO { +public class OAuth2AccessTokenDO extends BaseDO { /** * 编号,数据库递增 @@ -71,5 +72,8 @@ public class OAuth2AccessTokenDO extends TenantBaseDO { * 过期时间 */ private LocalDateTime expiresTime; - + /** + * 多租户编号 + */ + private Long tenantId; } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java index c37d8135..20a7a1f0 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java @@ -9,7 +9,7 @@ import java.util.Map; /** * 管理后台的认证 Service 接口 - * + *

* 提供用户的登录、登出的能力 * * @author 芋道源码 @@ -36,7 +36,7 @@ public interface AdminAuthService { /** * 基于 token 退出登录 * - * @param token token + * @param token token * @param logType 登出类型 */ void logout(String token, Integer logType); @@ -87,7 +87,7 @@ public interface AdminAuthService { */ void resetPassword(AuthResetPasswordReqVO reqVO); - WxLoginRespVO getSession( WxLoginReqVO reqVO); + WxLoginRespVO getSession(WxLoginReqVO reqVO); String getAccessToken(); diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java index fa8af203..a5b59cd9 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java @@ -230,6 +230,15 @@ public class AdminAuthServiceImpl implements AdminAuthService { // 创建访问令牌 OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(), OAuth2ClientConstants.CLIENT_ID_DEFAULT, null); + + // 获取用户信息并设置租户ID + AdminUserDO user = userService.getUser(userId); + if (user != null && user.getTenantId() != null) { + accessTokenDO.setTenantId(user.getTenantId()); + } else { + log.warn("[createTokenAfterLoginSuccess][用户({}) 租户ID为空,可能导致登录后无法获取租户信息]", userId); + } + // 构建返回结果 return AuthConvert.INSTANCE.convert(accessTokenDO); } @@ -357,8 +366,8 @@ public class AdminAuthServiceImpl implements AdminAuthService { } user.setOpenid(reqVO.getOpenid()); userService.updateByid(user); - - return createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); + AuthLoginRespVO tokenAfterLoginSuccess = createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL); + return tokenAfterLoginSuccess; } catch (Exception e) { log.error("[getPhoneNumber][解密手机号失败,encryptedData: {}, sessionKey: {}, iv: {}]", reqVO.getEncryptedData(), reqVO.getSessionKey(), reqVO.getIv(), e); @@ -377,6 +386,7 @@ public class AdminAuthServiceImpl implements AdminAuthService { adminUserDO.setStatus(CommonStatusEnum.DISABLE.getStatus()); adminUserDO.setPassword("123456"); adminUserDO.setIsSynchronize(1); + adminUserDO.setTenantId(1L); userService.createUser(adminUserDO); return true; } else { @@ -388,6 +398,9 @@ public class AdminAuthServiceImpl implements AdminAuthService { @Override public AuthLoginRespVO getUserInfo(UserReqVO reqVO) { AdminUserDO user = userService.getUserByMobile(reqVO.getPhone()); + if (user == null) { + throw exception(USER_NOT_EXISTS); + } // 创建 Token 令牌,记录登录日志 return createTokenAfterLoginSuccess(user.getId(), reqVO.getPhone(), LoginLogTypeEnum.LOGIN_USERNAME); } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java index fb0e756a..265eeeee 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/oauth2/OAuth2TokenServiceImpl.java @@ -177,6 +177,7 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { .setUserId(userId).setUserType(userType) .setClientId(clientDO.getClientId()).setScopes(scopes) .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getRefreshTokenValiditySeconds())); + refreshToken.setTenantId(TenantContextHolder.getTenantId()); oauth2RefreshTokenMapper.insert(refreshToken); return refreshToken; } @@ -199,13 +200,16 @@ public class OAuth2TokenServiceImpl implements OAuth2TokenService { private Map buildUserInfo(Long userId, Integer userType) { if (userType.equals(UserTypeEnum.ADMIN.getValue())) { AdminUserDO user = adminUserService.getUser(userId); + if (user == null) { + return Collections.emptyMap(); + } return MapUtil.builder(LoginUser.INFO_KEY_NICKNAME, user.getNickname()) .put(LoginUser.INFO_KEY_DEPT_ID, StrUtil.toStringOrNull(user.getDeptId())).build(); } else if (userType.equals(UserTypeEnum.MEMBER.getValue())) { // 注意:目前 Member 暂时不读取,可以按需实现 return Collections.emptyMap(); } - return null; + return Collections.emptyMap(); } private static String generateAccessToken() { diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java index 8eb5b096..4ebaf2f6 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/permission/PermissionServiceImpl.java @@ -12,6 +12,7 @@ import cn.iocoder.yudao.module.system.dal.dataobject.permission.MenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.RoleMenuDO; import cn.iocoder.yudao.module.system.dal.dataobject.permission.UserRoleDO; +import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import cn.iocoder.yudao.module.system.dal.mysql.permission.RoleMenuMapper; import cn.iocoder.yudao.module.system.dal.mysql.permission.UserRoleMapper; import cn.iocoder.yudao.module.system.dal.redis.RedisKeyConstants; @@ -286,7 +287,10 @@ public class PermissionServiceImpl implements PermissionService { } // 获得用户的部门编号的缓存,通过 Guava 的 Suppliers 惰性求值,即有且仅有第一次发起 DB 的查询 - Supplier userDeptId = Suppliers.memoize(() -> userService.getUser(userId).getDeptId()); + Supplier userDeptId = Suppliers.memoize(() -> { + AdminUserDO user = userService.getUser(userId); + return user != null ? user.getDeptId() : null; + }); // 遍历每个角色,计算 for (RoleDO role : roles) { // 为空时,跳过 diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java index b3647b3f..72085a5a 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java @@ -215,4 +215,12 @@ public interface AdminUserService { boolean isPasswordMatch(String rawPassword, String encodedPassword); void updateByid(AdminUserDO user); + + /** + * 修复用户租户ID + * + * @param userId 用户编号 + * @return 是否修复成功 + */ + Boolean fixUserTenantId(Long userId); } diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java index e6459243..3f554d6e 100644 --- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java +++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java @@ -12,6 +12,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.framework.common.util.object.BeanUtils; import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; +import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder; import cn.iocoder.yudao.module.infra.api.config.ConfigApi; import cn.iocoder.yudao.module.system.controller.admin.auth.vo.AuthRegisterReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO; @@ -161,6 +162,13 @@ public class AdminUserServiceImpl implements AdminUserService { AdminUserDO user = BeanUtils.toBean(registerReqVO, AdminUserDO.class); user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启 user.setPassword(encodePassword(registerReqVO.getPassword())); // 加密密码 + // 设置租户ID + Long tenantId = TenantContextHolder.getTenantId(); + if (tenantId != null) { + user.setTenantId(tenantId); + } else { + log.warn("[registerUser][注册用户时租户ID为空,可能导致用户无法正常访问系统]"); + } userMapper.insert(user); return user.getId(); } @@ -586,4 +594,34 @@ public class AdminUserServiceImpl implements AdminUserService { return passwordEncoder.encode(password); } + @Override + public Boolean fixUserTenantId(Long userId) { + // 获取用户信息 + AdminUserDO user = userMapper.selectById(userId); + if (user == null) { + throw exception(USER_NOT_EXISTS); + } + + // 如果用户已经有租户ID,则不需要修复 + if (user.getTenantId() != null) { + log.info("[fixUserTenantId][用户已有租户ID,无需修复] 用户ID: {}, 租户ID: {}", userId, user.getTenantId()); + return true; + } + + // 获取当前租户ID + Long currentTenantId = TenantContextHolder.getTenantId(); + if (currentTenantId == null) { + throw exception(USER_NOT_EXISTS, "当前租户上下文为空,无法修复用户租户ID"); + } + + // 更新用户的租户ID + AdminUserDO updateUser = new AdminUserDO(); + updateUser.setId(userId); + updateUser.setTenantId(currentTenantId); + userMapper.updateById(updateUser); + + log.info("[fixUserTenantId][用户租户ID修复成功] 用户ID: {}, 原租户ID: null, 新租户ID: {}", userId, currentTenantId); + return true; + } + } diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 79117279..51a01054 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -281,7 +281,7 @@ yudao: - /admin-api/system/auth/get-session - /admin-api/system/auth/get-phone-number - /admin-api/system/auth/api/user-add - - /admin-api/system/api/user-info + - /admin-api/system/auth/api/user-info ignore-visit-urls: - /admin-api/system/user/profile/** - /admin-api/system/auth/**