feat: 增加用户注册功能

This commit is contained in:
tangwei 2026-04-27 13:21:29 +08:00
parent 23d639cba9
commit ac05b629ee
12 changed files with 585 additions and 1 deletions

View File

@ -0,0 +1,178 @@
package com.yfd.platform.system.controller;
import com.yfd.platform.config.ResponseResult;
import com.yfd.platform.system.domain.SmsVerifyCode;
import com.yfd.platform.system.domain.SysUser;
import com.yfd.platform.system.service.ISmsVerifyCodeService;
import com.yfd.platform.system.service.IUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.*;
import java.sql.Timestamp;
import java.util.Date;
/**
* <p>
* 短信验证码控制器
* </p>
*/
@RestController
@RequestMapping("/sms")
@Tag(name = "短信验证码管理")
public class SmsVerifyCodeController {
@Resource
private ISmsVerifyCodeService smsVerifyCodeService;
@Resource
private IUserService userService;
@Value("${rsa.private_key}")
private String privateKey;
/**
* 发送验证码
*/
@PostMapping("/sendCode")
@Operation(summary = "发送验证码")
public ResponseResult sendVerifyCode(@RequestParam String phone,
@RequestParam Integer type) {
if (phone == null || phone.isEmpty()) {
return ResponseResult.error("手机号不能为空");
}
if (type == null || (type != SmsVerifyCode.TYPE_REGISTER && type != SmsVerifyCode.TYPE_FIND_PASSWORD)) {
return ResponseResult.error("类型错误1-注册 2-找回密码");
}
if (type.equals(SmsVerifyCode.TYPE_REGISTER)) {
SysUser existUser = userService.getUserByPhone(phone);
if (existUser != null) {
return ResponseResult.error("该手机号已注册");
}
}
if (type.equals(SmsVerifyCode.TYPE_FIND_PASSWORD)) {
SysUser existUser = userService.getUserByPhone(phone);
if (existUser == null) {
return ResponseResult.error("该手机号未注册");
}
}
String code = smsVerifyCodeService.sendVerifyCode(phone, type);
if (code == null) {
return ResponseResult.error("验证码发送失败,请稍后重试");
}
return ResponseResult.success();
}
/**
* 注册用户
*/
@PostMapping("/register")
@Operation(summary = "注册用户")
public ResponseResult register(@RequestBody SysUser user,
@RequestParam String code) {
if (user.getPhone() == null || user.getPhone().isEmpty()) {
return ResponseResult.error("手机号不能为空");
}
if (user.getUsername() == null || user.getUsername().isEmpty()) {
return ResponseResult.error("用户名不能为空");
}
if (user.getPassword() == null || user.getPassword().isEmpty()) {
return ResponseResult.error("密码不能为空");
}
if (code == null || code.isEmpty()) {
return ResponseResult.error("验证码不能为空");
}
boolean verified = smsVerifyCodeService.verifyCode(user.getPhone(), code, SmsVerifyCode.TYPE_REGISTER);
if (!verified) {
return ResponseResult.error("验证码错误或已过期");
}
SysUser existUser = userService.getUserByPhone(user.getPhone());
if (existUser != null) {
return ResponseResult.error("该手机号已注册");
}
try {
com.yfd.platform.utils.RsaUtils.decryptByPrivateKey(privateKey, user.getPassword());
} catch (Exception e) {
return ResponseResult.error("密码解密失败");
}
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setRegStatus(0);
user.setRegTime(new Date());
user.setStatus(1);
user.setUsertype(1);
boolean success = userService.save(user);
if (success) {
return ResponseResult.success();
} else {
return ResponseResult.error("注册失败");
}
}
/**
* 找回密码
*/
@PostMapping("/resetPassword")
@Operation(summary = "找回密码")
public ResponseResult resetPassword(@RequestParam String phone,
@RequestParam String code,
@RequestParam String password) {
if (phone == null || phone.isEmpty()) {
return ResponseResult.error("手机号不能为空");
}
if (code == null || code.isEmpty()) {
return ResponseResult.error("验证码不能为空");
}
if (password == null || password.isEmpty()) {
return ResponseResult.error("新密码不能为空");
}
boolean verified = smsVerifyCodeService.verifyCode(phone, code, SmsVerifyCode.TYPE_FIND_PASSWORD);
if (!verified) {
return ResponseResult.error("验证码错误或已过期");
}
SysUser existUser = userService.getUserByPhone(phone);
if (existUser == null) {
return ResponseResult.error("该手机号未注册");
}
BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
String encryptedPassword = passwordEncoder.encode(password);
boolean success = userService.updatePasswordByPhone(phone, encryptedPassword);
if (success) {
return ResponseResult.success();
} else {
return ResponseResult.error("密码重置失败");
}
}
/**
* 验证验证码是否有效
*/
@GetMapping("/verifyCode")
@Operation(summary = "验证验证码")
public ResponseResult verifyCode(@RequestParam String phone,
@RequestParam String code,
@RequestParam Integer type) {
boolean valid = smsVerifyCodeService.verifyCode(phone, code, type);
if (valid) {
return ResponseResult.success();
} else {
return ResponseResult.error("验证码错误或已过期");
}
}
}

View File

@ -200,4 +200,32 @@ public class UserController {
boolean ok = userService.uploadAvatar(id, multipartFile);
return ResponseResult.success();
}
@Log(module = "系统用户", value = "审核用户注册")
@PostMapping("/auditUser")
@Operation(summary = "审核用户注册")
@ResponseBody
public ResponseResult auditUser(@RequestParam String userId,
@RequestParam Integer auditStatus) {
if (userId == null || userId.isEmpty()) {
return ResponseResult.error("用户ID不能为空");
}
if (auditStatus == null || (auditStatus != 1 && auditStatus != 2)) {
return ResponseResult.error("审核状态错误1-通过 2-驳回");
}
boolean ok = userService.auditUser(userId, auditStatus);
if (ok) {
return ResponseResult.success();
} else {
return ResponseResult.error("审核失败");
}
}
@GetMapping("/queryPendingAuditUsers")
@Operation(summary = "查询待审核用户列表")
@ResponseBody
public ResponseResult queryPendingAuditUsers(Page<SysUser> page) {
Page<SysUser> result = userService.queryPendingAuditUsers(page);
return ResponseResult.successData(result);
}
}

View File

@ -0,0 +1,65 @@
package com.yfd.platform.system.domain;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.util.Date;
/**
* <p>
* 短信验证码表
* </p>
*/
@Data
@EqualsAndHashCode(callSuper = false)
@TableName("SMS_VERIFY_CODE")
public class SmsVerifyCode implements Serializable {
private static final long serialVersionUID = 1L;
public static final Integer TYPE_REGISTER = 1;
public static final Integer TYPE_FIND_PASSWORD = 2;
public static final Integer STATUS_UNUSED = 0;
public static final Integer STATUS_USED = 1;
/**
* 主键默认SYS_GUID()
*/
@TableId(type = IdType.INPUT)
private String id;
/**
* 手机号
*/
private String phone;
/**
* 验证码
*/
private String code;
/**
* 1-注册 2-找回密码
*/
private Integer type;
/**
* 过期时间
*/
private Date expireTime;
/**
* 创建时间默认当前时间
*/
private Date createTime;
/**
* 0-未使用 1-已使用
*/
private Integer status;
}

View File

@ -6,6 +6,7 @@ import lombok.EqualsAndHashCode;
import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
/**
@ -115,6 +116,31 @@ public class SysUser implements Serializable {
*/
private String custom3;
/**
* 真实姓名注册必填
*/
private String realName;
/**
* 注册状态0-待审核 1-已通过 2-已驳回
*/
private Integer regStatus;
/**
* 审核人ID
*/
private String auditUser;
/**
* 审核时间
*/
private Date auditTime;
/**
* 注册申请时间
*/
private Date regTime;
@TableField(exist = false)
List<SysRole> roles;
}

View File

@ -0,0 +1,37 @@
package com.yfd.platform.system.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yfd.platform.system.domain.SmsVerifyCode;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import java.util.List;
/**
* <p>
* 短信验证码表 Mapper 接口
* </p>
*/
public interface SmsVerifyCodeMapper extends BaseMapper<SmsVerifyCode> {
/**
* 根据手机号和类型查询最新的有效验证码
*/
SmsVerifyCode selectLatestByPhoneAndType(@Param("phone") String phone, @Param("type") Integer type);
/**
* 标记验证码为已使用
*/
@Update("UPDATE SMS_VERIFY_CODE SET STATUS = 1 WHERE ID = #{id}")
int markAsUsed(@Param("id") String id);
/**
* 删除指定手机号和类型的过期验证码
*/
int deleteExpiredByPhoneAndType(@Param("phone") String phone, @Param("type") Integer type);
/**
* 查询指定手机号和类型的未使用验证码
*/
List<SmsVerifyCode> selectUnusedByPhoneAndType(@Param("phone") String phone, @Param("type") Integer type);
}

View File

@ -0,0 +1,40 @@
package com.yfd.platform.system.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.yfd.platform.system.domain.SmsVerifyCode;
/**
* <p>
* 短信验证码表 服务类
* </p>
*/
public interface ISmsVerifyCodeService extends IService<SmsVerifyCode> {
/**
* 发送验证码
* @param phone 手机号
* @param type 1-注册 2-找回密码
* @return 验证码
*/
String sendVerifyCode(String phone, Integer type);
/**
* 验证验证码
* @param phone 手机号
* @param code 验证码
* @param type 类型 1-注册 2-找回密码
* @return 是否验证通过
*/
boolean verifyCode(String phone, String code, Integer type);
/**
* 标记验证码已使用
* @param id 验证码ID
*/
void markAsUsed(String id);
/**
* 生成6位数字验证码
*/
String generateCode();
}

View File

@ -138,6 +138,38 @@ public interface IUserService extends IService<SysUser> {
************************************/
boolean deleteUserByIds(String ids);
/***********************************
* 用途说明根据手机号查询用户
* 参数说明
*phone 手机号
* 返回值说明: 用户对象
************************************/
SysUser getUserByPhone(String phone);
/***********************************
* 用途说明根据手机号修改密码
* 参数说明
*phone 手机号
* encryptedPassword 加密后的密码
* 返回值说明: 是否修改成功
************************************/
boolean updatePasswordByPhone(String phone, String encryptedPassword);
/***********************************
* 用途说明审核用户注册
* 参数说明
*userId 用户id
* auditStatus 审核状态1-通过 2-驳回
* 返回值说明: 是否审核成功
************************************/
boolean auditUser(String userId, Integer auditStatus);
/***********************************
* 用途说明查询待审核用户列表
* 参数说明
*page 分页参数
* 返回值说明: 待审核用户分页列表
************************************/
Page<SysUser> queryPendingAuditUsers(Page<SysUser> page);
}

View File

@ -0,0 +1,99 @@
package com.yfd.platform.system.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yfd.platform.system.domain.SmsVerifyCode;
import com.yfd.platform.system.mapper.SmsVerifyCodeMapper;
import com.yfd.platform.system.service.ISmsVerifyCodeService;
import com.yfd.platform.utils.SmsSender;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Date;
import java.util.Random;
/**
* <p>
* 短信验证码表 服务实现类
* </p>
*/
@Service
public class SmsVerifyCodeServiceImpl extends ServiceImpl<SmsVerifyCodeMapper, SmsVerifyCode> implements ISmsVerifyCodeService {
private static final int CODE_VALID_MINUTES = 5;
private static final Random RANDOM = new Random();
@Resource
private SmsSender smsSender;
@Override
@Transactional(rollbackFor = Exception.class)
public String sendVerifyCode(String phone, Integer type) {
String code = generateCode();
LambdaQueryWrapper<SmsVerifyCode> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SmsVerifyCode::getPhone, phone)
.eq(SmsVerifyCode::getType, type)
.eq(SmsVerifyCode::getStatus, SmsVerifyCode.STATUS_UNUSED);
this.remove(queryWrapper);
SmsVerifyCode verifyCode = new SmsVerifyCode();
verifyCode.setId(java.util.UUID.randomUUID().toString().replace("-", ""));
verifyCode.setPhone(phone);
verifyCode.setCode(code);
verifyCode.setType(type);
verifyCode.setStatus(SmsVerifyCode.STATUS_UNUSED);
verifyCode.setCreateTime(new Date());
Date expireTime = new Date(System.currentTimeMillis() + CODE_VALID_MINUTES * 60 * 1000L);
verifyCode.setExpireTime(expireTime);
this.save(verifyCode);
String content = "您的验证码为:" + code + "" + CODE_VALID_MINUTES + "分钟内有效,请勿泄露给他人。";
boolean sent = smsSender.send(phone, content);
if (!sent) {
return null;
}
return code;
}
@Override
public boolean verifyCode(String phone, String code, Integer type) {
LambdaQueryWrapper<SmsVerifyCode> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SmsVerifyCode::getPhone, phone)
.eq(SmsVerifyCode::getCode, code)
.eq(SmsVerifyCode::getType, type)
.eq(SmsVerifyCode::getStatus, SmsVerifyCode.STATUS_UNUSED)
.gt(SmsVerifyCode::getExpireTime, new Date());
SmsVerifyCode verifyCode = this.getOne(queryWrapper);
if (verifyCode == null) {
return false;
}
verifyCode.setStatus(SmsVerifyCode.STATUS_USED);
this.updateById(verifyCode);
return true;
}
@Override
public void markAsUsed(String id) {
LambdaQueryWrapper<SmsVerifyCode> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SmsVerifyCode::getId, id);
SmsVerifyCode verifyCode = this.getOne(queryWrapper);
if (verifyCode != null) {
verifyCode.setStatus(SmsVerifyCode.STATUS_USED);
this.updateById(verifyCode);
}
}
@Override
public String generateCode() {
return String.format("%06d", RANDOM.nextInt(1000000));
}
}

View File

@ -561,6 +561,49 @@ public class UserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impleme
}
}
@Override
public SysUser getUserByPhone(String phone) {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getPhone, phone);
return this.getOne(queryWrapper);
}
@Override
public boolean updatePasswordByPhone(String phone, String encryptedPassword) {
UpdateWrapper<SysUser> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("phone", phone)
.set("password", encryptedPassword)
.set("pwdresettime", new Timestamp(System.currentTimeMillis()))
.set("lastmodifydate", new Timestamp(System.currentTimeMillis()))
.set("lastmodifier", getUsername());
return this.update(updateWrapper);
}
@Override
public boolean auditUser(String userId, Integer auditStatus) {
UpdateWrapper<SysUser> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id", userId)
.set("reg_status", auditStatus)
.set("audit_user", getUsername())
.set("audit_time", new Timestamp(System.currentTimeMillis()))
.set("lastmodifydate", new Timestamp(System.currentTimeMillis()))
.set("lastmodifier", getUsername());
if (auditStatus == 1) {
updateWrapper.set("status", 1);
} else if (auditStatus == 2) {
updateWrapper.set("status", 0);
}
return this.update(updateWrapper);
}
@Override
public Page<SysUser> queryPendingAuditUsers(Page<SysUser> page) {
LambdaQueryWrapper<SysUser> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(SysUser::getRegStatus, 0);
queryWrapper.orderByDesc(SysUser::getRegTime);
return this.page(page, queryWrapper);
}
/***********************************
* 用途说明:比较登录名称是否有重复
* 参数说明

View File

@ -0,0 +1,21 @@
package com.yfd.platform.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
/**
* 默认短信发送实现模拟实现
* 实际使用时替换为真实的短信网关
*/
@Slf4j
@Component
@ConditionalOnProperty(name = "sms.enabled", havingValue = "false", matchIfMissing = true)
public class DefaultSmsSender implements SmsSender {
@Override
public boolean send(String phone, String content) {
log.info("【模拟短信发送】手机号: {}, 内容: {}", phone, content);
return true;
}
}

View File

@ -0,0 +1,15 @@
package com.yfd.platform.utils;
/**
* 短信发送接口
*/
public interface SmsSender {
/**
* 发送短信
* @param phone 手机号
* @param content 短信内容
* @return 是否发送成功
*/
boolean send(String phone, String content);
}

View File

@ -1,6 +1,6 @@
spring:
profiles:
active: devtw
active: prod
jasypt:
encryptor: