diff --git a/backend/src/main/java/com/yfd/platform/system/controller/SmsVerifyCodeController.java b/backend/src/main/java/com/yfd/platform/system/controller/SmsVerifyCodeController.java
new file mode 100644
index 0000000..65023e1
--- /dev/null
+++ b/backend/src/main/java/com/yfd/platform/system/controller/SmsVerifyCodeController.java
@@ -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;
+
+/**
+ *
+ * 短信验证码控制器
+ *
+ */
+@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("验证码错误或已过期");
+ }
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/yfd/platform/system/controller/UserController.java b/backend/src/main/java/com/yfd/platform/system/controller/UserController.java
index 914b8f7..6c9d287 100644
--- a/backend/src/main/java/com/yfd/platform/system/controller/UserController.java
+++ b/backend/src/main/java/com/yfd/platform/system/controller/UserController.java
@@ -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 page) {
+ Page result = userService.queryPendingAuditUsers(page);
+ return ResponseResult.successData(result);
+ }
}
diff --git a/backend/src/main/java/com/yfd/platform/system/domain/SmsVerifyCode.java b/backend/src/main/java/com/yfd/platform/system/domain/SmsVerifyCode.java
new file mode 100644
index 0000000..2cc1351
--- /dev/null
+++ b/backend/src/main/java/com/yfd/platform/system/domain/SmsVerifyCode.java
@@ -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;
+
+/**
+ *
+ * 短信验证码表
+ *
+ */
+@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;
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/yfd/platform/system/domain/SysUser.java b/backend/src/main/java/com/yfd/platform/system/domain/SysUser.java
index 57c5b85..ed9b24f 100644
--- a/backend/src/main/java/com/yfd/platform/system/domain/SysUser.java
+++ b/backend/src/main/java/com/yfd/platform/system/domain/SysUser.java
@@ -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 roles;
}
diff --git a/backend/src/main/java/com/yfd/platform/system/mapper/SmsVerifyCodeMapper.java b/backend/src/main/java/com/yfd/platform/system/mapper/SmsVerifyCodeMapper.java
new file mode 100644
index 0000000..714ab94
--- /dev/null
+++ b/backend/src/main/java/com/yfd/platform/system/mapper/SmsVerifyCodeMapper.java
@@ -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;
+
+/**
+ *
+ * 短信验证码表 Mapper 接口
+ *
+ */
+public interface SmsVerifyCodeMapper extends BaseMapper {
+
+ /**
+ * 根据手机号和类型查询最新的有效验证码
+ */
+ 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 selectUnusedByPhoneAndType(@Param("phone") String phone, @Param("type") Integer type);
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/yfd/platform/system/service/ISmsVerifyCodeService.java b/backend/src/main/java/com/yfd/platform/system/service/ISmsVerifyCodeService.java
new file mode 100644
index 0000000..a8ca1b8
--- /dev/null
+++ b/backend/src/main/java/com/yfd/platform/system/service/ISmsVerifyCodeService.java
@@ -0,0 +1,40 @@
+package com.yfd.platform.system.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import com.yfd.platform.system.domain.SmsVerifyCode;
+
+/**
+ *
+ * 短信验证码表 服务类
+ *
+ */
+public interface ISmsVerifyCodeService extends IService {
+
+ /**
+ * 发送验证码
+ * @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();
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/yfd/platform/system/service/IUserService.java b/backend/src/main/java/com/yfd/platform/system/service/IUserService.java
index e1ff507..f86da52 100644
--- a/backend/src/main/java/com/yfd/platform/system/service/IUserService.java
+++ b/backend/src/main/java/com/yfd/platform/system/service/IUserService.java
@@ -138,6 +138,38 @@ public interface IUserService extends IService {
************************************/
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 queryPendingAuditUsers(Page page);
}
diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/SmsVerifyCodeServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/SmsVerifyCodeServiceImpl.java
new file mode 100644
index 0000000..2b939eb
--- /dev/null
+++ b/backend/src/main/java/com/yfd/platform/system/service/impl/SmsVerifyCodeServiceImpl.java
@@ -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;
+
+/**
+ *
+ * 短信验证码表 服务实现类
+ *
+ */
+@Service
+public class SmsVerifyCodeServiceImpl extends ServiceImpl 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 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 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 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));
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java b/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java
index 7e483f1..537a57f 100644
--- a/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java
+++ b/backend/src/main/java/com/yfd/platform/system/service/impl/UserServiceImpl.java
@@ -561,6 +561,49 @@ public class UserServiceImpl extends ServiceImpl impleme
}
}
+ @Override
+ public SysUser getUserByPhone(String phone) {
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper.eq(SysUser::getPhone, phone);
+ return this.getOne(queryWrapper);
+ }
+
+ @Override
+ public boolean updatePasswordByPhone(String phone, String encryptedPassword) {
+ UpdateWrapper 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 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 queryPendingAuditUsers(Page page) {
+ LambdaQueryWrapper queryWrapper = new LambdaQueryWrapper<>();
+ queryWrapper.eq(SysUser::getRegStatus, 0);
+ queryWrapper.orderByDesc(SysUser::getRegTime);
+ return this.page(page, queryWrapper);
+ }
+
/***********************************
* 用途说明:比较登录名称是否有重复
* 参数说明
diff --git a/backend/src/main/java/com/yfd/platform/utils/DefaultSmsSender.java b/backend/src/main/java/com/yfd/platform/utils/DefaultSmsSender.java
new file mode 100644
index 0000000..506429c
--- /dev/null
+++ b/backend/src/main/java/com/yfd/platform/utils/DefaultSmsSender.java
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/backend/src/main/java/com/yfd/platform/utils/SmsSender.java b/backend/src/main/java/com/yfd/platform/utils/SmsSender.java
new file mode 100644
index 0000000..1b1b3bc
--- /dev/null
+++ b/backend/src/main/java/com/yfd/platform/utils/SmsSender.java
@@ -0,0 +1,15 @@
+package com.yfd.platform.utils;
+
+/**
+ * 短信发送接口
+ */
+public interface SmsSender {
+
+ /**
+ * 发送短信
+ * @param phone 手机号
+ * @param content 短信内容
+ * @return 是否发送成功
+ */
+ boolean send(String phone, String content);
+}
\ No newline at end of file
diff --git a/backend/src/main/resources/application.yml b/backend/src/main/resources/application.yml
index 8f42778..547f405 100644
--- a/backend/src/main/resources/application.yml
+++ b/backend/src/main/resources/application.yml
@@ -1,6 +1,6 @@
spring:
profiles:
- active: devtw
+ active: prod
jasypt:
encryptor: