Commit 0e1c7b574f606476b1ddae0c52a30da419c2b9e3

Authored by scholar
1 parent 1c1abae5

增加用户注册功能

yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/AuthController.java
@@ -161,4 +161,11 @@ public class AuthController { @@ -161,4 +161,11 @@ public class AuthController {
161 return success(authService.socialLogin(reqVO)); 161 return success(authService.socialLogin(reqVO));
162 } 162 }
163 163
  164 + @PostMapping("/register")
  165 + @PermitAll
  166 + @Operation(summary = "注册用户")
  167 + @OperateLog(enable = false) // 避免 Post 请求被记录操作日志
  168 + public CommonResult<Long> register(@RequestBody @Valid AuthRegisterReqVO reqVO) {
  169 + return success(authService.register(reqVO));
  170 + }
164 } 171 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/controller/admin/auth/vo/AuthRegisterReqVO.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.controller.admin.auth.vo;
  2 +
  3 +import cn.hutool.core.util.ObjectUtil;
  4 +import cn.iocoder.yudao.framework.common.validation.Mobile;
  5 +import com.fasterxml.jackson.annotation.JsonIgnore;
  6 +import io.swagger.v3.oas.annotations.media.Schema;
  7 +import lombok.Data;
  8 +import org.hibernate.validator.constraints.Length;
  9 +
  10 +import jakarta.validation.constraints.*;
  11 +import java.util.Set;
  12 +
  13 +@Schema(description = "管理后台 - Register Request VO")
  14 +@Data
  15 +public class AuthRegisterReqVO {
  16 +
  17 + @Schema(description = "用户编号", example = "1024")
  18 + private Long id;
  19 +
  20 + @Schema(description = "用户账号", requiredMode = Schema.RequiredMode.REQUIRED, example = "yudao")
  21 + @NotBlank(message = "用户账号不能为空")
  22 + @Pattern(regexp = "^[a-zA-Z0-9]{4,30}$", message = "用户账号由 数字、字母 组成")
  23 + @Size(min = 4, max = 30, message = "用户账号长度为 4-30 个字符")
  24 + private String username;
  25 +
  26 + @Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋艿")
  27 + @Size(max = 30, message = "用户昵称长度不能超过30个字符")
  28 + private String nickname;
  29 +
  30 + @Schema(description = "备注", example = "我是一个用户")
  31 + private String remark;
  32 +
  33 + @Schema(description = "部门ID", example = "我是一个用户")
  34 + private Long deptId;
  35 +
  36 + @Schema(description = "岗位编号数组", example = "1")
  37 + private Set<Long> postIds;
  38 +
  39 + @Schema(description = "用户邮箱", example = "yudao@iocoder.cn")
  40 + @Email(message = "邮箱格式不正确")
  41 + @Size(max = 50, message = "邮箱长度不能超过 50 个字符")
  42 + private String email;
  43 +
  44 + @Schema(description = "手机号码", example = "15601691300")
  45 + @Mobile
  46 + private String mobile;
  47 +
  48 + @Schema(description = "用户性别,参见 SexEnum 枚举类", example = "1")
  49 + private Integer sex;
  50 +
  51 + @Schema(description = "用户头像", example = "https://www.iocoder.cn/xxx.png")
  52 + private String avatar;
  53 +
  54 + // ========== 仅【创建】时,需要传递的字段 ==========
  55 +
  56 + @Schema(description = "密码", requiredMode = Schema.RequiredMode.REQUIRED, example = "123456")
  57 + @Length(min = 4, max = 16, message = "密码长度为 4-16 位")
  58 + private String password;
  59 +
  60 + @AssertTrue(message = "密码不能为空")
  61 + @JsonIgnore
  62 + public boolean isPasswordValid() {
  63 + return id != null // 修改时,不需要传递
  64 + || (ObjectUtil.isAllNotEmpty(password)); // 新增时,必须都传递 password
  65 + }
  66 +
  67 +}
0 \ No newline at end of file 68 \ No newline at end of file
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthService.java
@@ -70,4 +70,13 @@ public interface AdminAuthService { @@ -70,4 +70,13 @@ public interface AdminAuthService {
70 */ 70 */
71 AuthLoginRespVO refreshToken(String refreshToken); 71 AuthLoginRespVO refreshToken(String refreshToken);
72 72
  73 +
  74 + /**
  75 + * 用户注册
  76 + *
  77 + * @param createReqVO 注册用户
  78 + * @return 注册结果
  79 + */
  80 + Long register(AuthRegisterReqVO createReqVO);
  81 +
73 } 82 }
yudao-module-system/yudao-module-system-biz/src/main/java/cn/iocoder/yudao/module/system/service/auth/AdminAuthServiceImpl.java
1 package cn.iocoder.yudao.module.system.service.auth; 1 package cn.iocoder.yudao.module.system.service.auth;
2 2
  3 +import cn.hutool.core.collection.CollectionUtil;
3 import cn.hutool.core.util.ObjectUtil; 4 import cn.hutool.core.util.ObjectUtil;
  5 +import cn.hutool.core.util.StrUtil;
4 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum; 6 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
5 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum; 7 import cn.iocoder.yudao.framework.common.enums.UserTypeEnum;
6 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils; 8 import cn.iocoder.yudao.framework.common.util.monitor.TracerUtils;
  9 +import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
7 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils; 10 import cn.iocoder.yudao.framework.common.util.servlet.ServletUtils;
8 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils; 11 import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
  12 +import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
9 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO; 13 import cn.iocoder.yudao.module.system.api.logger.dto.LoginLogCreateReqDTO;
10 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi; 14 import cn.iocoder.yudao.module.system.api.sms.SmsCodeApi;
11 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO; 15 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserBindReqDTO;
12 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO; 16 import cn.iocoder.yudao.module.system.api.social.dto.SocialUserRespDTO;
13 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*; 17 import cn.iocoder.yudao.module.system.controller.admin.auth.vo.*;
14 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert; 18 import cn.iocoder.yudao.module.system.convert.auth.AuthConvert;
  19 +import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO;
15 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO; 20 import cn.iocoder.yudao.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;
16 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; 21 import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO;
  22 +import cn.iocoder.yudao.module.system.dal.mysql.dept.UserPostMapper;
  23 +import cn.iocoder.yudao.module.system.dal.mysql.user.AdminUserMapper;
17 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum; 24 import cn.iocoder.yudao.module.system.enums.logger.LoginLogTypeEnum;
18 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum; 25 import cn.iocoder.yudao.module.system.enums.logger.LoginResultEnum;
19 import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants; 26 import cn.iocoder.yudao.module.system.enums.oauth2.OAuth2ClientConstants;
@@ -22,6 +29,7 @@ import cn.iocoder.yudao.module.system.service.logger.LoginLogService; @@ -22,6 +29,7 @@ import cn.iocoder.yudao.module.system.service.logger.LoginLogService;
22 import cn.iocoder.yudao.module.system.service.member.MemberService; 29 import cn.iocoder.yudao.module.system.service.member.MemberService;
23 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService; 30 import cn.iocoder.yudao.module.system.service.oauth2.OAuth2TokenService;
24 import cn.iocoder.yudao.module.system.service.social.SocialUserService; 31 import cn.iocoder.yudao.module.system.service.social.SocialUserService;
  32 +import cn.iocoder.yudao.module.system.service.tenant.TenantService;
25 import cn.iocoder.yudao.module.system.service.user.AdminUserService; 33 import cn.iocoder.yudao.module.system.service.user.AdminUserService;
26 import com.google.common.annotations.VisibleForTesting; 34 import com.google.common.annotations.VisibleForTesting;
27 import com.xingyuv.captcha.model.common.ResponseModel; 35 import com.xingyuv.captcha.model.common.ResponseModel;
@@ -29,13 +37,17 @@ import com.xingyuv.captcha.model.vo.CaptchaVO; @@ -29,13 +37,17 @@ import com.xingyuv.captcha.model.vo.CaptchaVO;
29 import com.xingyuv.captcha.service.CaptchaService; 37 import com.xingyuv.captcha.service.CaptchaService;
30 import lombok.extern.slf4j.Slf4j; 38 import lombok.extern.slf4j.Slf4j;
31 import org.springframework.beans.factory.annotation.Value; 39 import org.springframework.beans.factory.annotation.Value;
  40 +import org.springframework.context.annotation.Lazy;
  41 +import org.springframework.security.crypto.password.PasswordEncoder;
32 import org.springframework.stereotype.Service; 42 import org.springframework.stereotype.Service;
33 43
34 import jakarta.annotation.Resource; 44 import jakarta.annotation.Resource;
35 import jakarta.validation.Validator; 45 import jakarta.validation.Validator;
36 import java.util.Objects; 46 import java.util.Objects;
  47 +import java.util.Set;
37 48
38 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; 49 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
  50 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
39 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP; 51 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
40 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*; 52 import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
41 53
@@ -64,12 +76,21 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -64,12 +76,21 @@ public class AdminAuthServiceImpl implements AdminAuthService {
64 private CaptchaService captchaService; 76 private CaptchaService captchaService;
65 @Resource 77 @Resource
66 private SmsCodeApi smsCodeApi; 78 private SmsCodeApi smsCodeApi;
67 - 79 + @Resource
  80 + @Lazy // 延迟,避免循环依赖报错
  81 + private TenantService tenantService;
  82 + @Resource
  83 + private AdminUserMapper userMapper;
  84 + @Resource
  85 + private PasswordEncoder passwordEncoder;
68 /** 86 /**
69 * 验证码的开关,默认为 true 87 * 验证码的开关,默认为 true
70 */ 88 */
71 @Value("${yudao.captcha.enable:true}") 89 @Value("${yudao.captcha.enable:true}")
72 private Boolean captchaEnable; 90 private Boolean captchaEnable;
  91 + @Resource
  92 + private UserPostMapper userPostMapper;
  93 +
73 94
74 @Override 95 @Override
75 public AdminUserDO authenticate(String username, String password) { 96 public AdminUserDO authenticate(String username, String password) {
@@ -247,4 +268,78 @@ public class AdminAuthServiceImpl implements AdminAuthService { @@ -247,4 +268,78 @@ public class AdminAuthServiceImpl implements AdminAuthService {
247 return UserTypeEnum.ADMIN; 268 return UserTypeEnum.ADMIN;
248 } 269 }
249 270
  271 +
  272 + public Long register(AuthRegisterReqVO registerReqVO) {
  273 + // 校验账户配合
  274 + tenantService.handleTenantInfo(tenant -> {
  275 + long count = userMapper.selectCount();
  276 + if (count >= tenant.getAccountCount()) {
  277 + throw exception(USER_COUNT_MAX, tenant.getAccountCount());
  278 + }
  279 + });
  280 + // 校验正确性
  281 + validateUserForRegister(null, registerReqVO.getUsername(),
  282 + registerReqVO.getMobile(), registerReqVO.getEmail(), registerReqVO.getDeptId(), registerReqVO.getPostIds());
  283 + // 插入用户
  284 + AdminUserDO user = BeanUtils.toBean(registerReqVO, AdminUserDO.class);
  285 + user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
  286 + user.setPassword(encodePassword(registerReqVO.getPassword())); // 加密密码
  287 + userMapper.insert(user);
  288 + // 插入关联岗位
  289 + if (CollectionUtil.isNotEmpty(user.getPostIds())) {
  290 + userPostMapper.insertBatch(convertList(user.getPostIds(),
  291 + postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId)));
  292 + }
  293 + return user.getId();
  294 + }
  295 +
  296 + private void validateUserForRegister(Long id, String username, String mobile, String email,
  297 + Long deptId, Set<Long> postIds) {
  298 + // 关闭数据权限,避免因为没有数据权限,查询不到数据,进而导致唯一校验不正确
  299 + DataPermissionUtils.executeIgnore(() -> {
  300 + // 校验用户存在
  301 + validateUserExists(id);
  302 + // 校验用户名唯一
  303 + validateUsernameUnique(id, username);
  304 + });
  305 + }
  306 +
  307 + @VisibleForTesting
  308 + void validateUserExists(Long id) {
  309 + if (id == null) {
  310 + return;
  311 + }
  312 + AdminUserDO user = userMapper.selectById(id);
  313 + if (user == null) {
  314 + throw exception(USER_NOT_EXISTS);
  315 + }
  316 + }
  317 +
  318 + @VisibleForTesting
  319 + void validateUsernameUnique(Long id, String username) {
  320 + if (StrUtil.isBlank(username)) {
  321 + return;
  322 + }
  323 + AdminUserDO user = userMapper.selectByUsername(username);
  324 + if (user == null) {
  325 + return;
  326 + }
  327 + // 如果 id 为空,说明不用比较是否为相同 id 的用户
  328 + if (id == null) {
  329 + throw exception(USER_USERNAME_EXISTS);
  330 + }
  331 + if (!user.getId().equals(id)) {
  332 + throw exception(USER_USERNAME_EXISTS);
  333 + }
  334 + }
  335 +
  336 + /**
  337 + * 对密码进行加密
  338 + *
  339 + * @param password 密码
  340 + * @return 加密后的密码
  341 + */
  342 + private String encodePassword(String password) {
  343 + return passwordEncoder.encode(password);
  344 + }
250 } 345 }