用户租户体系调整,用户支持多租户数据

master
zzy 2026-04-20 23:53:52 +08:00
parent 4ce24a8b65
commit c01d68aec8
16 changed files with 542 additions and 116 deletions

View File

@ -0,0 +1,59 @@
package com.fjrcloud.community.module.system.controller.admin.auth;
import com.fjrcloud.community.framework.common.enums.UserTypeEnum;
import com.fjrcloud.community.framework.common.pojo.CommonResult;
import com.fjrcloud.community.framework.tenant.core.util.TenantUtils;
import com.fjrcloud.community.module.system.controller.admin.auth.vo.SwitchTenantReqVO;
import com.fjrcloud.community.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.fjrcloud.community.module.system.dal.dataobject.user.UserTenantRelDO;
import com.fjrcloud.community.module.system.enums.ErrorCodeConstants;
import com.fjrcloud.community.module.system.enums.oauth2.OAuth2ClientConstants;
import com.fjrcloud.community.module.system.service.oauth2.OAuth2TokenService;
import com.fjrcloud.community.module.system.service.usertenant.UserTenantRelService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.validation.Valid;
import static com.fjrcloud.community.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.fjrcloud.community.framework.common.pojo.CommonResult.success;
import static com.fjrcloud.community.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - 认证 - 租户切换")
@RestController
@RequestMapping("/system/auth")
@Validated
public class AuthTenantSwitchController {
@Resource
private UserTenantRelService userTenantRelService;
@Resource
private OAuth2TokenService oauth2TokenService;
@PostMapping("/switch-tenant")
@Operation(summary = "切换租户")
public CommonResult<String> switchTenant(@Valid @RequestBody SwitchTenantReqVO reqVO) {
Long userId = getLoginUserId();
UserTenantRelDO rel = userTenantRelService.getUserTenantRel(userId, reqVO.getTenantId());
if (rel == null) {
throw exception(ErrorCodeConstants.USER_TENANT_REL_NOT_EXISTS);
}
String newToken = TenantUtils.execute(reqVO.getTenantId(), () -> {
OAuth2AccessTokenDO newAccessToken = oauth2TokenService.createAccessToken(
userId, UserTypeEnum.ADMIN.getValue(),
OAuth2ClientConstants.CLIENT_ID_DEFAULT, null);
return newAccessToken.getAccessToken();
});
return success(newToken);
}
}

View File

@ -7,6 +7,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - 登录 Response VO")
@Data
@ -27,4 +28,7 @@ public class AuthLoginRespVO {
@Schema(description = "过期时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime expiresTime;
@Schema(description = "用户可访问的租户列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<TenantInfoVO> tenants;
}

View File

@ -0,0 +1,15 @@
package com.fjrcloud.community.module.system.controller.admin.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 切换租户 Request VO")
@Data
public class SwitchTenantReqVO {
@Schema(description = "租户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "租户ID不能为空")
private Long tenantId;
}

View File

@ -0,0 +1,24 @@
package com.fjrcloud.community.module.system.controller.admin.auth.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Schema(description = "管理后台 - 租户信息 VO")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TenantInfoVO {
@Schema(description = "租户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long tenantId;
@Schema(description = "租户名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "XX小区")
private String tenantName;
@Schema(description = "是否默认租户", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean isDefault;
}

View File

@ -0,0 +1,83 @@
package com.fjrcloud.community.module.system.controller.admin.usertenant;
import com.fjrcloud.community.framework.common.pojo.CommonResult;
import com.fjrcloud.community.module.system.controller.admin.auth.vo.TenantInfoVO;
import com.fjrcloud.community.module.system.controller.admin.usertenant.vo.UserTenantAssignReqVO;
import com.fjrcloud.community.module.system.controller.admin.usertenant.vo.UserTenantBatchAssignReqVO;
import com.fjrcloud.community.module.system.controller.admin.usertenant.vo.UserTenantRespVO;
import com.fjrcloud.community.module.system.dal.dataobject.user.UserTenantRelDO;
import com.fjrcloud.community.module.system.dal.mysql.tenant.TenantMapper;
import com.fjrcloud.community.module.system.service.usertenant.UserTenantRelService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;
import java.util.List;
import java.util.stream.Collectors;
import static com.fjrcloud.community.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - 用户租户关联")
@RestController
@RequestMapping("/system/user-tenant")
@Validated
public class UserTenantRelController {
@Resource
private UserTenantRelService userTenantRelService;
@Resource
private TenantMapper tenantMapper;
@PostMapping("/assign")
@Operation(summary = "分配用户到租户")
@PreAuthorize("@ss.hasPermission('system:user-tenant:assign')")
public CommonResult<Boolean> assignUserToTenant(@Valid @RequestBody UserTenantAssignReqVO reqVO) {
userTenantRelService.assignUserToTenant(reqVO.getUserId(), reqVO.getTenantId(), reqVO.getIsDefault());
return success(true);
}
@DeleteMapping("/remove")
@Operation(summary = "移除用户租户关联")
@Parameter(name = "userId", description = "用户ID", required = true, example = "1")
@Parameter(name = "tenantId", description = "租户ID", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('system:user-tenant:remove')")
public CommonResult<Boolean> removeUserFromTenant(@RequestParam("userId") Long userId,
@RequestParam("tenantId") Long tenantId) {
userTenantRelService.removeUserFromTenant(userId, tenantId);
return success(true);
}
@PostMapping("/batch-assign")
@Operation(summary = "批量分配用户到多个租户")
@PreAuthorize("@ss.hasPermission('system:user-tenant:assign')")
public CommonResult<Boolean> batchAssignUserToTenants(@Valid @RequestBody UserTenantBatchAssignReqVO reqVO) {
userTenantRelService.batchAssignUserToTenants(reqVO.getUserId(), reqVO.getTenantIds());
return success(true);
}
@GetMapping("/list-by-user")
@Operation(summary = "获取用户的租户列表")
@Parameter(name = "userId", description = "用户ID", required = true, example = "1")
@PreAuthorize("@ss.hasPermission('system:user-tenant:query')")
public CommonResult<List<UserTenantRespVO>> getUserTenants(@RequestParam("userId") Long userId) {
List<TenantInfoVO> tenantInfos = userTenantRelService.getUserTenants(userId);
List<UserTenantRespVO> result = tenantInfos.stream().map(info -> {
UserTenantRelDO rel = userTenantRelService.getUserTenantRel(userId, info.getTenantId());
return UserTenantRespVO.builder()
.id(rel != null ? rel.getId() : null)
.userId(userId)
.tenantId(info.getTenantId())
.tenantName(info.getTenantName())
.isDefault(info.getIsDefault())
.createTime(rel != null ? rel.getCreateTime() : null)
.build();
}).collect(Collectors.toList());
return success(result);
}
}

View File

@ -0,0 +1,22 @@
package com.fjrcloud.community.module.system.controller.admin.usertenant.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 分配用户到租户 Request VO")
@Data
public class UserTenantAssignReqVO {
@Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "用户ID不能为空")
private Long userId;
@Schema(description = "租户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "租户ID不能为空")
private Long tenantId;
@Schema(description = "是否默认租户", example = "true")
private Boolean isDefault;
}

View File

@ -0,0 +1,21 @@
package com.fjrcloud.community.module.system.controller.admin.usertenant.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.util.List;
@Schema(description = "管理后台 - 批量分配用户到租户 Request VO")
@Data
public class UserTenantBatchAssignReqVO {
@Schema(description = "用户ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "用户ID不能为空")
private Long userId;
@Schema(description = "租户ID列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1,2,3]")
@NotEmpty(message = "租户ID列表不能为空")
private List<Long> tenantIds;
}

View File

@ -0,0 +1,35 @@
package com.fjrcloud.community.module.system.controller.admin.usertenant.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - 用户租户关联 Response VO")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserTenantRespVO {
@Schema(description = "关联ID", example = "1")
private Long id;
@Schema(description = "用户ID", example = "1")
private Long userId;
@Schema(description = "租户ID", example = "1")
private Long tenantId;
@Schema(description = "租户名称", example = "XX小区")
private String tenantName;
@Schema(description = "是否默认租户", example = "true")
private Boolean isDefault;
@Schema(description = "创建时间")
private LocalDateTime createTime;
}

View File

@ -0,0 +1,27 @@
package com.fjrcloud.community.module.system.dal.dataobject.user;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fjrcloud.community.framework.mybatis.core.dataobject.BaseDO;
import lombok.*;
@TableName("system_user_tenant_rel")
@KeySequence("system_user_tenant_rel_seq")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserTenantRelDO extends BaseDO {
@TableId
private Long id;
private Long userId;
private Long tenantId;
private Boolean defaultTenant;
}

View File

@ -0,0 +1,28 @@
package com.fjrcloud.community.module.system.dal.mysql.user;
import com.fjrcloud.community.framework.mybatis.core.mapper.BaseMapperX;
import com.fjrcloud.community.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.fjrcloud.community.module.system.dal.dataobject.user.UserTenantRelDO;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserTenantRelMapper extends BaseMapperX<UserTenantRelDO> {
default List<UserTenantRelDO> selectListByUserId(Long userId) {
return selectList(UserTenantRelDO::getUserId, userId);
}
default UserTenantRelDO selectByUserIdAndTenantId(Long userId, Long tenantId) {
return selectOne(new LambdaQueryWrapperX<UserTenantRelDO>()
.eq(UserTenantRelDO::getUserId, userId)
.eq(UserTenantRelDO::getTenantId, tenantId));
}
default UserTenantRelDO selectDefaultByUserId(Long userId) {
return selectOne(new LambdaQueryWrapperX<UserTenantRelDO>()
.eq(UserTenantRelDO::getUserId, userId)
.eq(UserTenantRelDO::getDefaultTenant, true));
}
}

View File

@ -44,7 +44,11 @@ public interface ErrorCodeConstants {
ErrorCode USER_IS_DISABLE = new ErrorCode(1_002_003_006, "名字为【{}】的用户已被禁用");
ErrorCode USER_COUNT_MAX = new ErrorCode(1_002_003_008, "创建用户失败,原因:超过租户最大租户配额({})");
ErrorCode USER_IMPORT_INIT_PASSWORD = new ErrorCode(1_002_003_009, "初始密码不能为空");
ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_003_010, "该手机号尚未注册");
ErrorCode USER_MOBILE_NOT_EXISTS = new ErrorCode(1_002_010, "手机号未注册用户");
ErrorCode USER_TENANT_REL_NOT_EXISTS = new ErrorCode(1_002_011, "用户没有该租户的访问权限");
ErrorCode USER_TENANT_REL_EXISTS = new ErrorCode(1_002_012, "用户已拥有该租户的访问权限");
ErrorCode USER_REGISTER_DISABLED = new ErrorCode(1_002_003_011, "注册功能已关闭");
// ========== 部门模块 1-002-004-000 ==========

View File

@ -3,24 +3,19 @@ package com.fjrcloud.community.module.system.enums.logger;
import lombok.AllArgsConstructor;
import lombok.Getter;
/**
*
*/
@Getter
@AllArgsConstructor
public enum LoginResultEnum {
SUCCESS(0), // 成功
BAD_CREDENTIALS(10), // 账号或密码不正确
USER_DISABLED(20), // 用户被禁用
CAPTCHA_NOT_FOUND(30), // 图片验证码不存在
CAPTCHA_CODE_ERROR(31), // 图片验证码不正确
SUCCESS(0),
BAD_CREDENTIALS(10),
USER_DISABLED(20),
CAPTCHA_NOT_FOUND(30),
CAPTCHA_CODE_ERROR(31),
TENANT_PERMISSION_DENIED(40),
;
/**
*
*/
private final Integer result;
}

View File

@ -11,6 +11,8 @@ import com.fjrcloud.community.framework.common.util.object.BeanUtils;
import com.fjrcloud.community.framework.common.util.servlet.ServletUtils;
import com.fjrcloud.community.framework.common.util.validation.ValidationUtils;
import com.fjrcloud.community.framework.datapermission.core.annotation.DataPermission;
import com.fjrcloud.community.framework.tenant.core.context.TenantContextHolder;
import com.fjrcloud.community.framework.tenant.core.util.TenantUtils;
import com.fjrcloud.community.module.system.api.logger.dto.LoginLogCreateReqDTO;
import com.fjrcloud.community.module.system.api.sms.SmsCodeApi;
import com.fjrcloud.community.module.system.api.sms.dto.code.SmsCodeUseReqDTO;
@ -20,6 +22,7 @@ import com.fjrcloud.community.module.system.controller.admin.auth.vo.*;
import com.fjrcloud.community.module.system.convert.auth.AuthConvert;
import com.fjrcloud.community.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
import com.fjrcloud.community.module.system.dal.dataobject.user.AdminUserDO;
import com.fjrcloud.community.module.system.dal.dataobject.user.UserTenantRelDO;
import com.fjrcloud.community.module.system.enums.logger.LoginLogTypeEnum;
import com.fjrcloud.community.module.system.enums.logger.LoginResultEnum;
import com.fjrcloud.community.module.system.enums.oauth2.OAuth2ClientConstants;
@ -29,6 +32,7 @@ import com.fjrcloud.community.module.system.service.member.MemberService;
import com.fjrcloud.community.module.system.service.oauth2.OAuth2TokenService;
import com.fjrcloud.community.module.system.service.social.SocialUserService;
import com.fjrcloud.community.module.system.service.user.AdminUserService;
import com.fjrcloud.community.module.system.service.usertenant.UserTenantRelService;
import com.google.common.annotations.VisibleForTesting;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@ -38,17 +42,13 @@ import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import javax.validation.Validator;
import java.util.List;
import java.util.Objects;
import static com.fjrcloud.community.framework.common.exception.util.ServiceExceptionUtil.exception;
import static com.fjrcloud.community.framework.common.util.servlet.ServletUtils.getClientIP;
import static com.fjrcloud.community.module.system.enums.ErrorCodeConstants.*;
/**
* Auth Service
*
* @author
*/
@Service
@Slf4j
public class AdminAuthServiceImpl implements AdminAuthService {
@ -69,12 +69,11 @@ public class AdminAuthServiceImpl implements AdminAuthService {
private CaptchaService captchaService;
@Resource
private SmsCodeApi smsCodeApi;
@Resource
private UserTenantRelService userTenantRelService;
/**
* true
*/
@Value("${fjrcloud.captcha.enable:true}")
@Setter // 为了单测:开启或者关闭验证码
@Setter
private Boolean captchaEnable;
@Override
@ -112,13 +111,28 @@ public class AdminAuthServiceImpl implements AdminAuthService {
socialUserService.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
}
// 创建 Token 令牌,记录登录日志
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId == null) {
createLoginLog(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.BAD_CREDENTIALS);
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
}
validateUserTenantPermission(user.getId(), tenantId);
return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
}
private void validateUserTenantPermission(Long userId, Long tenantId) {
UserTenantRelDO rel = userTenantRelService.getUserTenantRel(userId, tenantId);
if (rel == null) {
createLoginLog(userId, null, LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.TENANT_PERMISSION_DENIED);
throw exception(USER_TENANT_REL_NOT_EXISTS);
}
}
@Override
public void sendSmsCode(AuthSmsSendReqVO reqVO) {
// 如果是重置密码场景,需要校验图形验证码是否正确
if (Objects.equals(SmsSceneEnum.ADMIN_MEMBER_RESET_PASSWORD.getScene(), reqVO.getScene())) {
ResponseModel response = doValidateCaptcha(reqVO);
if (!response.isSuccess()) {
@ -145,7 +159,14 @@ public class AdminAuthServiceImpl implements AdminAuthService {
throw exception(USER_NOT_EXISTS);
}
// 创建 Token 令牌,记录登录日志
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId == null) {
createLoginLog(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE, LoginResultEnum.BAD_CREDENTIALS);
throw exception(AUTH_LOGIN_BAD_CREDENTIALS);
}
validateUserTenantPermission(user.getId(), tenantId);
return createTokenAfterLoginSuccess(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);
}
@ -183,7 +204,11 @@ public class AdminAuthServiceImpl implements AdminAuthService {
throw exception(USER_NOT_EXISTS);
}
// 创建 Token 令牌,记录登录日志
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId != null) {
validateUserTenantPermission(user.getId(), tenantId);
}
return createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL);
}
@ -210,13 +235,25 @@ public class AdminAuthServiceImpl implements AdminAuthService {
}
private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) {
// 插入登陆日志
createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);
// 创建访问令牌
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(),
OAuth2ClientConstants.CLIENT_ID_DEFAULT, null);
// 构建返回结果
return BeanUtils.toBean(accessTokenDO, AuthLoginRespVO.class);
Long tenantId = TenantContextHolder.getTenantId();
OAuth2AccessTokenDO accessTokenDO;
if (tenantId != null) {
accessTokenDO = TenantUtils.execute(tenantId, () ->
oauth2TokenService.createAccessToken(userId, getUserType().getValue(),
OAuth2ClientConstants.CLIENT_ID_DEFAULT, null));
} else {
accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(),
OAuth2ClientConstants.CLIENT_ID_DEFAULT, null);
}
List<TenantInfoVO> tenants = userTenantRelService.getUserTenants(userId);
AuthLoginRespVO respVO = BeanUtils.toBean(accessTokenDO, AuthLoginRespVO.class);
respVO.setTenants(tenants);
return respVO;
}
@Override
@ -227,12 +264,10 @@ public class AdminAuthServiceImpl implements AdminAuthService {
@Override
public void logout(String token, Integer logType) {
// 删除访问令牌
OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(token);
if (accessTokenDO == null) {
return;
}
// 删除成功,则记录登出日志
createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getUserType(), logType);
}
@ -267,20 +302,16 @@ public class AdminAuthServiceImpl implements AdminAuthService {
@Override
public AuthLoginRespVO register(AuthRegisterReqVO registerReqVO) {
// 1. 校验验证码
validateCaptcha(registerReqVO);
// 2. 校验用户名是否已存在
Long userId = userService.registerUser(registerReqVO);
// 3. 创建 Token 令牌,记录登录日志
return createTokenAfterLoginSuccess(userId, registerReqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
}
@VisibleForTesting
void validateCaptcha(AuthRegisterReqVO reqVO) {
ResponseModel response = doValidateCaptcha(reqVO);
// 验证不通过
if (!response.isSuccess()) {
throw exception(AUTH_REGISTER_CAPTCHA_CODE_ERROR, response.getRepMsg());
}

View File

@ -12,6 +12,7 @@ import com.fjrcloud.community.framework.common.util.collection.CollectionUtils;
import com.fjrcloud.community.framework.common.util.object.BeanUtils;
import com.fjrcloud.community.framework.common.util.validation.ValidationUtils;
import com.fjrcloud.community.framework.datapermission.core.util.DataPermissionUtils;
import com.fjrcloud.community.framework.tenant.core.context.TenantContextHolder;
import com.fjrcloud.community.module.infra.api.config.ConfigApi;
import com.fjrcloud.community.module.system.controller.admin.auth.vo.AuthRegisterReqVO;
import com.fjrcloud.community.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;
@ -30,6 +31,7 @@ import com.fjrcloud.community.module.system.service.dept.PostService;
import com.fjrcloud.community.module.system.service.oauth2.OAuth2TokenService;
import com.fjrcloud.community.module.system.service.permission.PermissionService;
import com.fjrcloud.community.module.system.service.tenant.TenantService;
import com.fjrcloud.community.module.system.service.usertenant.UserTenantRelService;
import com.google.common.annotations.VisibleForTesting;
import com.mzt.logapi.context.LogRecordContext;
import com.mzt.logapi.service.impl.DiffParseFunction;
@ -78,10 +80,14 @@ public class AdminUserServiceImpl implements AdminUserService {
@Resource
@Lazy // 延迟,避免循环依赖报错
private TenantService tenantService;
@Resource
@Lazy // 懒加载,避免循环依赖
private OAuth2TokenService oauth2TokenService;
@Resource
private UserTenantRelService userTenantRelService;
@Resource
private UserPostMapper userPostMapper;
@ -93,52 +99,50 @@ public class AdminUserServiceImpl implements AdminUserService {
@LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = "{{#user.id}}",
success = SYSTEM_USER_CREATE_SUCCESS)
public Long createUser(UserSaveReqVO createReqVO) {
// 1.1 校验账户配合
tenantService.handleTenantInfo(tenant -> {
long count = userMapper.selectCount();
if (count >= tenant.getAccountCount()) {
throw exception(USER_COUNT_MAX, tenant.getAccountCount());
}
});
// 1.2 校验正确性
validateUserForCreateOrUpdate(null, createReqVO.getUsername(),
createReqVO.getMobile(), createReqVO.getEmail(), createReqVO.getDeptId(), createReqVO.getPostIds());
// 2.1 插入用户
AdminUserDO user = BeanUtils.toBean(createReqVO, AdminUserDO.class);
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
user.setPassword(encodePassword(createReqVO.getPassword())); // 加密密码
user.setStatus(CommonStatusEnum.ENABLE.getStatus());
user.setPassword(encodePassword(createReqVO.getPassword()));
userMapper.insert(user);
// 2.2 插入关联岗位
if (CollectionUtil.isNotEmpty(user.getPostIds())) {
userPostMapper.insertBatch(convertList(user.getPostIds(),
postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId)));
}
// 3. 记录操作日志上下文
Long tenantId = TenantContextHolder.getTenantId();
if (tenantId != null) {
userTenantRelService.assignUserToTenant(user.getId(), tenantId, true);
}
LogRecordContext.putVariable("user", user);
return user.getId();
}
@Override
public Long registerUser(AuthRegisterReqVO registerReqVO) {
// 1.1 校验是否开启注册
if (ObjUtil.notEqual(configApi.getConfigValueByKey(USER_REGISTER_ENABLED_KEY), "true")) {
throw exception(USER_REGISTER_DISABLED);
}
// 1.2 校验账户配合
tenantService.handleTenantInfo(tenant -> {
long count = userMapper.selectCount();
if (count >= tenant.getAccountCount()) {
throw exception(USER_COUNT_MAX, tenant.getAccountCount());
}
});
// 1.3 校验正确性
validateUserForCreateOrUpdate(null, registerReqVO.getUsername(), null, null, null, null);
// 2. 插入用户
AdminUserDO user = BeanUtils.toBean(registerReqVO, AdminUserDO.class);
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
user.setPassword(encodePassword(registerReqVO.getPassword())); // 加密密码
user.setStatus(CommonStatusEnum.ENABLE.getStatus());
user.setPassword(encodePassword(registerReqVO.getPassword()));
userMapper.insert(user);
return user.getId();
}
@ -148,18 +152,14 @@ public class AdminUserServiceImpl implements AdminUserService {
@LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_SUB_TYPE, bizNo = "{{#updateReqVO.id}}",
success = SYSTEM_USER_UPDATE_SUCCESS)
public void updateUser(UserSaveReqVO updateReqVO) {
updateReqVO.setPassword(null); // 特殊:此处不更新密码
// 1. 校验正确性
updateReqVO.setPassword(null);
AdminUserDO oldUser = validateUserForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getUsername(),
updateReqVO.getMobile(), updateReqVO.getEmail(), updateReqVO.getDeptId(), updateReqVO.getPostIds());
// 2.1 更新用户
AdminUserDO updateObj = BeanUtils.toBean(updateReqVO, AdminUserDO.class);
userMapper.updateById(updateObj);
// 2.2 更新岗位
updateUserPost(updateReqVO, updateObj);
// 3. 记录操作日志上下文
LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class));
LogRecordContext.putVariable("user", oldUser);
}
@ -167,11 +167,9 @@ public class AdminUserServiceImpl implements AdminUserService {
private void updateUserPost(UserSaveReqVO reqVO, AdminUserDO updateObj) {
Long userId = reqVO.getId();
Set<Long> dbPostIds = convertSet(userPostMapper.selectListByUserId(userId), UserPostDO::getPostId);
// 计算新增和删除的岗位编号
Set<Long> postIds = CollUtil.emptyIfNull(updateObj.getPostIds());
Collection<Long> createPostIds = CollUtil.subtract(postIds, dbPostIds);
Collection<Long> deletePostIds = CollUtil.subtract(dbPostIds, postIds);
// 执行新增和删除。对于已经授权的岗位,不用做任何处理
if (!CollectionUtil.isEmpty(createPostIds)) {
userPostMapper.insertBatch(convertList(createPostIds,
postId -> new UserPostDO().setUserId(userId).setPostId(postId)));
@ -188,21 +186,17 @@ public class AdminUserServiceImpl implements AdminUserService {
@Override
public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) {
// 校验正确性
validateUserExists(id);
validateEmailUnique(id, reqVO.getEmail());
validateMobileUnique(id, reqVO.getMobile());
// 执行更新
userMapper.updateById(BeanUtils.toBean(reqVO, AdminUserDO.class).setId(id));
}
@Override
public void updateUserPassword(Long id, UserProfileUpdatePasswordReqVO reqVO) {
// 校验旧密码密码
validateOldPassword(id, reqVO.getOldPassword());
// 执行更新
AdminUserDO updateObj = new AdminUserDO().setId(id);
updateObj.setPassword(encodePassword(reqVO.getNewPassword())); // 加密密码
updateObj.setPassword(encodePassword(reqVO.getNewPassword()));
userMapper.updateById(updateObj);
}
@ -210,31 +204,25 @@ public class AdminUserServiceImpl implements AdminUserService {
@LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_PASSWORD_SUB_TYPE, bizNo = "{{#id}}",
success = SYSTEM_USER_UPDATE_PASSWORD_SUCCESS)
public void updateUserPassword(Long id, String password) {
// 1. 校验用户存在
AdminUserDO user = validateUserExists(id);
// 2. 更新密码
AdminUserDO updateObj = new AdminUserDO();
updateObj.setId(id);
updateObj.setPassword(encodePassword(password)); // 加密密码
updateObj.setPassword(encodePassword(password));
userMapper.updateById(updateObj);
// 3. 记录操作日志上下文
LogRecordContext.putVariable("user", user);
LogRecordContext.putVariable("newPassword", updateObj.getPassword());
}
@Override
public void updateUserStatus(Long id, Integer status) {
// 校验用户存在
validateUserExists(id);
// 更新状态
AdminUserDO updateObj = new AdminUserDO();
updateObj.setId(id);
updateObj.setStatus(status);
userMapper.updateById(updateObj);
// 如果是禁用用户,则删除其 Token 信息
if (CommonStatusEnum.isDisable(status)) {
oauth2TokenService.removeAccessToken(id, UserTypeEnum.ADMIN.getValue());
}
@ -245,27 +233,20 @@ public class AdminUserServiceImpl implements AdminUserService {
@LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_DELETE_SUB_TYPE, bizNo = "{{#id}}",
success = SYSTEM_USER_DELETE_SUCCESS)
public void deleteUser(Long id) {
// 1. 校验用户存在
AdminUserDO user = validateUserExists(id);
// 2.1 删除用户
userMapper.deleteById(id);
// 2.2 删除用户关联数据
permissionService.processUserDeleted(id);
// 2.2 删除用户岗位
userPostMapper.deleteByUserId(id);
// 3. 记录操作日志上下文
LogRecordContext.putVariable("user", user);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void deleteUserList(List<Long> ids) {
// 1. 批量删除用户
userMapper.deleteByIds(ids);
// 2. 批量删除用户关联数据
ids.forEach(id -> {
permissionService.processUserDeleted(id);
userPostMapper.deleteByUserId(id);
@ -284,7 +265,6 @@ public class AdminUserServiceImpl implements AdminUserService {
@Override
public PageResult<AdminUserDO> getUserPage(UserPageReqVO reqVO) {
// 如果有角色编号,查询角色对应的用户编号
Set<Long> userIds = null;
if (reqVO.getRoleId() != null) {
userIds = permissionService.getUserRoleIdListByRoleId(singleton(reqVO.getRoleId()));
@ -293,7 +273,6 @@ public class AdminUserServiceImpl implements AdminUserService {
}
}
// 分页查询
return userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()), userIds);
}
@ -335,10 +314,8 @@ public class AdminUserServiceImpl implements AdminUserService {
if (CollUtil.isEmpty(ids)) {
return;
}
// 获得岗位信息
List<AdminUserDO> users = userMapper.selectByIds(ids);
Map<Long, AdminUserDO> userMap = CollectionUtils.convertMap(users, AdminUserDO::getId);
// 校验
ids.forEach(id -> {
AdminUserDO user = userMap.get(id);
if (user == null) {
@ -355,36 +332,23 @@ public class AdminUserServiceImpl implements AdminUserService {
return userMapper.selectListByNickname(nickname);
}
/**
*
*
* @param deptId
* @return
*/
private Set<Long> getDeptCondition(Long deptId) {
if (deptId == null) {
return Collections.emptySet();
}
Set<Long> deptIds = convertSet(deptService.getChildDeptList(deptId), DeptDO::getId);
deptIds.add(deptId); // 包括自身
deptIds.add(deptId);
return deptIds;
}
private AdminUserDO validateUserForCreateOrUpdate(Long id, String username, String mobile, String email,
Long deptId, Set<Long> postIds) {
// 关闭数据权限,避免因为没有数据权限,查询不到数据,进而导致唯一校验不正确
return DataPermissionUtils.executeIgnore(() -> {
// 校验用户存在
AdminUserDO user = validateUserExists(id);
// 校验用户名唯一
validateUsernameUnique(id, username);
// 校验手机号唯一
validateMobileUnique(id, mobile);
// 校验邮箱唯一
validateEmailUnique(id, email);
// 校验部门处于开启状态
deptService.validateDeptList(CollectionUtils.singleton(deptId));
// 校验岗位处于开启状态
postService.validatePostList(postIds);
return user;
});
@ -411,7 +375,6 @@ public class AdminUserServiceImpl implements AdminUserService {
if (user == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的用户
if (id == null) {
throw exception(USER_USERNAME_EXISTS);
}
@ -429,7 +392,6 @@ public class AdminUserServiceImpl implements AdminUserService {
if (user == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的用户
if (id == null) {
throw exception(USER_EMAIL_EXISTS);
}
@ -447,7 +409,6 @@ public class AdminUserServiceImpl implements AdminUserService {
if (user == null) {
return;
}
// 如果 id 为空,说明不用比较是否为相同 id 的用户
if (id == null) {
throw exception(USER_MOBILE_EXISTS);
}
@ -456,11 +417,6 @@ public class AdminUserServiceImpl implements AdminUserService {
}
}
/**
*
* @param id id
* @param oldPassword
*/
@VisibleForTesting
void validateOldPassword(Long id, String oldPassword) {
AdminUserDO user = userMapper.selectById(id);
@ -473,25 +429,21 @@ public class AdminUserServiceImpl implements AdminUserService {
}
@Override
@Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
@Transactional(rollbackFor = Exception.class)
public UserImportRespVO importUserList(List<UserImportExcelVO> importUsers, boolean isUpdateSupport) {
// 1.1 参数校验
if (CollUtil.isEmpty(importUsers)) {
throw exception(USER_IMPORT_LIST_IS_EMPTY);
}
// 1.2 初始化密码不能为空
String initPassword = configApi.getConfigValueByKey(USER_INIT_PASSWORD_KEY);
if (StrUtil.isEmpty(initPassword)) {
throw exception(USER_IMPORT_INIT_PASSWORD);
}
// 2. 遍历,逐个创建 or 更新
UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>())
.updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build();
AtomicInteger index = new AtomicInteger(1);
importUsers.forEach(importUser -> {
int currentIndex = index.getAndIncrement();
// 2.1.1 校验字段是否符合要求
try {
ValidationUtils.validate(BeanUtils.toBean(importUser, UserSaveReqVO.class).setPassword(initPassword));
} catch (ConstraintViolationException ex) {
@ -499,7 +451,6 @@ public class AdminUserServiceImpl implements AdminUserService {
respVO.getFailureUsernames().put(key, ex.getMessage());
return;
}
// 2.1.2 校验,判断是否有不符合的原因
try {
validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(),
importUser.getDeptId(), null);
@ -508,7 +459,6 @@ public class AdminUserServiceImpl implements AdminUserService {
return;
}
// 2.2.1 判断如果不存在,在进行插入
AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());
if (existUser == null) {
userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class)
@ -516,7 +466,6 @@ public class AdminUserServiceImpl implements AdminUserService {
respVO.getCreateUsernames().add(importUser.getUsername());
return;
}
// 2.2.2 如果存在,判断是否允许更新
if (!isUpdateSupport) {
respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg());
return;
@ -539,12 +488,6 @@ public class AdminUserServiceImpl implements AdminUserService {
return passwordEncoder.matches(rawPassword, encodedPassword);
}
/**
*
*
* @param password
* @return
*/
private String encodePassword(String password) {
return passwordEncoder.encode(password);
}

View File

@ -0,0 +1,21 @@
package com.fjrcloud.community.module.system.service.usertenant;
import com.fjrcloud.community.module.system.controller.admin.auth.vo.TenantInfoVO;
import com.fjrcloud.community.module.system.dal.dataobject.user.UserTenantRelDO;
import java.util.List;
public interface UserTenantRelService {
List<TenantInfoVO> getUserTenants(Long userId);
UserTenantRelDO getUserTenantRel(Long userId, Long tenantId);
UserTenantRelDO getDefaultTenant(Long userId);
void assignUserToTenant(Long userId, Long tenantId, Boolean isDefault);
void removeUserFromTenant(Long userId, Long tenantId);
void batchAssignUserToTenants(Long userId, List<Long> tenantIds);
}

View File

@ -0,0 +1,114 @@
package com.fjrcloud.community.module.system.service.usertenant;
import cn.hutool.core.collection.CollUtil;
import com.fjrcloud.community.framework.common.exception.ServiceException;
import com.fjrcloud.community.module.system.controller.admin.auth.vo.TenantInfoVO;
import com.fjrcloud.community.module.system.dal.dataobject.tenant.TenantDO;
import com.fjrcloud.community.module.system.dal.dataobject.user.UserTenantRelDO;
import com.fjrcloud.community.module.system.dal.mysql.tenant.TenantMapper;
import com.fjrcloud.community.module.system.dal.mysql.user.UserTenantRelMapper;
import com.fjrcloud.community.module.system.enums.ErrorCodeConstants;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import static com.fjrcloud.community.framework.common.exception.util.ServiceExceptionUtil.exception;
@Service
public class UserTenantRelServiceImpl implements UserTenantRelService {
@Resource
private UserTenantRelMapper userTenantRelMapper;
@Resource
private TenantMapper tenantMapper;
@Override
public List<TenantInfoVO> getUserTenants(Long userId) {
List<UserTenantRelDO> rels = userTenantRelMapper.selectListByUserId(userId);
if (CollUtil.isEmpty(rels)) {
return Collections.emptyList();
}
return rels.stream().map(rel -> {
TenantDO tenant = tenantMapper.selectById(rel.getTenantId());
return TenantInfoVO.builder()
.tenantId(rel.getTenantId())
.tenantName(tenant != null ? tenant.getName() : "")
.isDefault(rel.getDefaultTenant())
.build();
}).collect(Collectors.toList());
}
@Override
public UserTenantRelDO getUserTenantRel(Long userId, Long tenantId) {
return userTenantRelMapper.selectByUserIdAndTenantId(userId, tenantId);
}
@Override
public UserTenantRelDO getDefaultTenant(Long userId) {
return userTenantRelMapper.selectDefaultByUserId(userId);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void assignUserToTenant(Long userId, Long tenantId, Boolean isDefault) {
TenantDO tenant = tenantMapper.selectById(tenantId);
if (tenant == null) {
throw exception(ErrorCodeConstants.TENANT_NOT_EXISTS);
}
UserTenantRelDO existRel = userTenantRelMapper.selectByUserIdAndTenantId(userId, tenantId);
if (existRel != null) {
throw exception(ErrorCodeConstants.USER_TENANT_REL_EXISTS);
}
if (Boolean.TRUE.equals(isDefault)) {
clearDefaultTenant(userId);
}
UserTenantRelDO rel = new UserTenantRelDO();
rel.setUserId(userId);
rel.setTenantId(tenantId);
rel.setDefaultTenant(isDefault != null ? isDefault : false);
userTenantRelMapper.insert(rel);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void removeUserFromTenant(Long userId, Long tenantId) {
UserTenantRelDO rel = userTenantRelMapper.selectByUserIdAndTenantId(userId, tenantId);
if (rel == null) {
return;
}
userTenantRelMapper.deleteById(rel.getId());
}
@Override
@Transactional(rollbackFor = Exception.class)
public void batchAssignUserToTenants(Long userId, List<Long> tenantIds) {
if (CollUtil.isEmpty(tenantIds)) {
return;
}
for (Long tenantId : tenantIds) {
try {
assignUserToTenant(userId, tenantId, false);
} catch (ServiceException e) {
// 忽略已存在的关联
}
}
}
private void clearDefaultTenant(Long userId) {
UserTenantRelDO defaultRel = userTenantRelMapper.selectDefaultByUserId(userId);
if (defaultRel != null) {
defaultRel.setDefaultTenant(false);
userTenantRelMapper.updateById(defaultRel);
}
}
}