From e687cdcc8350e85d39641ff3417be0c48c0eaf48 Mon Sep 17 00:00:00 2001 From: dww Date: Sat, 31 Jan 2026 13:07:46 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=A8=E5=86=8C=E6=8E=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bicloud/common/utils/PasswordUtils.java | 60 ++++++++++++ .../bicloud/controller/UserController.java | 17 +++- .../java/com/bicloud/mapper/UserMapper.java | 38 ++++++++ .../com/bicloud/pojo/dto/RegisterDto.java | 55 +++++++++++ .../java/com/bicloud/pojo/entity/User.java | 20 ++++ .../java/com/bicloud/service/UserService.java | 5 +- .../bicloud/service/impl/UserServiceImpl.java | 93 +++++++++++++++++-- src/main/resources/mapper/UserMapper.xml | 42 +++++++++ src/main/resources/sql/users.sql | 18 ++++ 9 files changed, 337 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/bicloud/common/utils/PasswordUtils.java create mode 100644 src/main/java/com/bicloud/mapper/UserMapper.java create mode 100644 src/main/java/com/bicloud/pojo/dto/RegisterDto.java create mode 100644 src/main/java/com/bicloud/pojo/entity/User.java create mode 100644 src/main/resources/mapper/UserMapper.xml create mode 100644 src/main/resources/sql/users.sql diff --git a/src/main/java/com/bicloud/common/utils/PasswordUtils.java b/src/main/java/com/bicloud/common/utils/PasswordUtils.java new file mode 100644 index 0000000..0cfa86e --- /dev/null +++ b/src/main/java/com/bicloud/common/utils/PasswordUtils.java @@ -0,0 +1,60 @@ +package com.bicloud.common.utils; + +import java.nio.charset.StandardCharsets; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; +import java.util.Base64; + +/** + * 密码加密工具类 + */ +public class PasswordUtils { + + private static final SecureRandom RANDOM = new SecureRandom(); + + /** + * 生成随机盐值 + */ + public static String generateSalt() { + byte[] salt = new byte[16]; + RANDOM.nextBytes(salt); + return Base64.getEncoder().encodeToString(salt); + } + + /** + * 使用MD5加盐加密密码 + * @param password 原始密码 + * @param salt 盐值 + * @return 加密后的密码 + */ + public static String encryptPassword(String password, String salt) { + try { + // 将密码和盐值拼接 + String combined = password + salt; + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] hashBytes = md.digest(combined.getBytes(StandardCharsets.UTF_8)); + + // 转换为16进制字符串 + StringBuilder sb = new StringBuilder(); + for (byte b : hashBytes) { + sb.append(String.format("%02x", b)); + } + return sb.toString(); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException("密码加密失败", e); + } + } + + /** + * 验证密码是否正确 + * @param inputPassword 输入的密码 + * @param salt 盐值 + * @param encryptedPassword 加密后的密码 + * @return 是否匹配 + */ + public static boolean verifyPassword(String inputPassword, String salt, String encryptedPassword) { + String encrypted = encryptPassword(inputPassword, salt); + return encrypted.equals(encryptedPassword); + } +} diff --git a/src/main/java/com/bicloud/controller/UserController.java b/src/main/java/com/bicloud/controller/UserController.java index 45ddbf7..78afe2c 100644 --- a/src/main/java/com/bicloud/controller/UserController.java +++ b/src/main/java/com/bicloud/controller/UserController.java @@ -2,11 +2,13 @@ package com.bicloud.controller; import com.bicloud.common.result.Result; +import com.bicloud.pojo.dto.RegisterDto; import com.bicloud.service.UserService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; +import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -23,11 +25,20 @@ public class UserController { /** * 发送手机验证码 */ - @PostMapping("code") + @PostMapping("/code") @Operation(summary = "发送验证码") - public Result sendCode(@RequestParam("phone") String phone, HttpSession session, HttpServletRequest request) { + public Result sendCode(@RequestParam("mobile") String mobile, HttpSession session, HttpServletRequest request) { // 发送短信验证码并保存验证码 - return userService.sendCode(phone, session, request); + return userService.sendCode(mobile, session, request); + } + + /** + * 用户注册 + */ + @PostMapping("/register") + @Operation(summary = "用户注册") + public Result register(@Valid @RequestBody RegisterDto registerDto) { + return userService.register(registerDto); } } diff --git a/src/main/java/com/bicloud/mapper/UserMapper.java b/src/main/java/com/bicloud/mapper/UserMapper.java new file mode 100644 index 0000000..ffef95b --- /dev/null +++ b/src/main/java/com/bicloud/mapper/UserMapper.java @@ -0,0 +1,38 @@ +package com.bicloud.mapper; + +import com.bicloud.pojo.entity.User; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import org.apache.ibatis.annotations.Mapper; +import org.apache.ibatis.annotations.Param; + +@Mapper +public interface UserMapper { + + /** + * 根据手机号查询用户 + */ + User selectByMobile(@Param("mobile") String mobile); + + /** + * 根据邮箱查询用户 + */ + User selectByEmail(@Param("email") String email); + + /** + * 根据用户名查询用户 + */ + User selectByUsername(@Param("username") String username); + + /** + * 插入用户 + */ + int insert(User user); + + /** + * 根据ID查询用户 + */ + User selectById(@Param("id") Long id); + + +} diff --git a/src/main/java/com/bicloud/pojo/dto/RegisterDto.java b/src/main/java/com/bicloud/pojo/dto/RegisterDto.java new file mode 100644 index 0000000..303d276 --- /dev/null +++ b/src/main/java/com/bicloud/pojo/dto/RegisterDto.java @@ -0,0 +1,55 @@ +package com.bicloud.pojo.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Pattern; +import lombok.Data; + +@Data +@Schema(description = "用户注册请求DTO") +public class RegisterDto { + + @NotBlank(message = "用户名不能为空") + @Schema(description = "用户名", example = "张三") + private String username; + + @NotBlank(message = "所属单位不能为空") + @Schema(description = "所属单位", example = "某某大学") + private String company; + + @NotBlank(message = "职位不能为空") + @Schema(description = "职位", example = "研究员") + private String job; + + @NotBlank(message = "地址不能为空") + @Schema(description = "所在地址", example = "北京市海淀区") + private String address; + + @Schema(description = "感兴趣标签", example = "[\"生物信息学\", \"基因组学\"]") + private java.util.List interestTags; + + @NotBlank(message = "手机号不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号格式不正确") + @Schema(description = "手机号", example = "13800138000") + private String mobile; + + @NotBlank(message = "验证码不能为空") + @Schema(description = "验证码", example = "123123") + private String vcode; + + @NotBlank(message = "邮箱不能为空") + @Email(message = "邮箱格式不正确") + @Schema(description = "邮箱地址", example = "example@example.com") + private String email; + + @NotBlank(message = "密码不能为空") + @Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d]{8,20}$", + message = "密码必须包含大小写字母和数字,长度8-20位") + @Schema(description = "密码", example = "Password123") + private String pwd; + + @NotBlank(message = "确认密码不能为空") + @Schema(description = "确认密码", example = "Password123") + private String cpwd; +} diff --git a/src/main/java/com/bicloud/pojo/entity/User.java b/src/main/java/com/bicloud/pojo/entity/User.java new file mode 100644 index 0000000..ca3ac81 --- /dev/null +++ b/src/main/java/com/bicloud/pojo/entity/User.java @@ -0,0 +1,20 @@ +package com.bicloud.pojo.entity; + +import lombok.Data; +import java.time.LocalDateTime; + +@Data +public class User { + private Long id; + private String username; + private String company; + private String job; + private String address; + private String introduce; + private String mobile; + private String email; + private String pwd; + private String salt; + private LocalDateTime createTime; + private LocalDateTime updateTime; +} diff --git a/src/main/java/com/bicloud/service/UserService.java b/src/main/java/com/bicloud/service/UserService.java index 67d3124..813fbeb 100644 --- a/src/main/java/com/bicloud/service/UserService.java +++ b/src/main/java/com/bicloud/service/UserService.java @@ -1,9 +1,12 @@ package com.bicloud.service; import com.bicloud.common.result.Result; +import com.bicloud.pojo.dto.RegisterDto; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpSession; public interface UserService { - Result sendCode(String phone, HttpSession session, HttpServletRequest request); + Result sendCode(String mobile, HttpSession session, HttpServletRequest request); + + Result register(RegisterDto registerDto); } diff --git a/src/main/java/com/bicloud/service/impl/UserServiceImpl.java b/src/main/java/com/bicloud/service/impl/UserServiceImpl.java index ddf5dbc..da2771c 100644 --- a/src/main/java/com/bicloud/service/impl/UserServiceImpl.java +++ b/src/main/java/com/bicloud/service/impl/UserServiceImpl.java @@ -1,7 +1,11 @@ package com.bicloud.service.impl; import com.bicloud.common.result.Result; +import com.bicloud.common.utils.PasswordUtils; import com.bicloud.common.utils.RegexUtils; +import com.bicloud.mapper.UserMapper; +import com.bicloud.pojo.dto.RegisterDto; +import com.bicloud.pojo.entity.User; import com.bicloud.service.UserService; import jakarta.annotation.Resource; import jakarta.servlet.http.HttpServletRequest; @@ -9,6 +13,7 @@ import jakarta.servlet.http.HttpSession; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import lombok.extern.slf4j.Slf4j; +import java.time.LocalDateTime; import java.util.concurrent.TimeUnit; import static com.bicloud.common.utils.RedisConstants.*; @@ -20,10 +25,13 @@ public class UserServiceImpl implements UserService { @Resource private StringRedisTemplate stringRedisTemplate; + @Resource + private UserMapper userMapper; + @Override - public Result sendCode(String phone, HttpSession session, HttpServletRequest request) { + public Result sendCode(String mobile, HttpSession session, HttpServletRequest request) { // 1.校验手机号 - if (RegexUtils.isPhoneInvalid(phone)) { + if (RegexUtils.isPhoneInvalid(mobile)) { // 2.如果不符合,返回错误信息 return Result.error("手机号格式错误!"); } @@ -32,13 +40,13 @@ public class UserServiceImpl implements UserService { String clientIpAddr = request.getRemoteAddr(); // 4.如果存在锁,直接返回失败 - String phoneGetCodeLock = stringRedisTemplate.opsForValue().get(GET_CODE_LOCK + phone); + String phoneGetCodeLock = stringRedisTemplate.opsForValue().get(GET_CODE_LOCK + mobile); if (phoneGetCodeLock != null) { return Result.error("获取验证码过快,请稍后重试!"); } // 5.检查两个黑名单中的次数 - String blacklistPhoneCount = stringRedisTemplate.opsForValue().get(GET_CODE_BLACKLIST_PHONE + phone); + String blacklistPhoneCount = stringRedisTemplate.opsForValue().get(GET_CODE_BLACKLIST_PHONE + mobile); int getCodePhoneCount = (blacklistPhoneCount != null) ? Integer.parseInt(blacklistPhoneCount) : 0; if (getCodePhoneCount >= 400) { return Result.error("获取验证码次数过多,您的手机号已被限制!"); @@ -51,16 +59,16 @@ public class UserServiceImpl implements UserService { // } // 6.更新锁和黑名单 - stringRedisTemplate.opsForValue().set(GET_CODE_BLACKLIST_PHONE + phone, String.valueOf(getCodePhoneCount + 1), REFRESH_BLACKLIST_TTL, TimeUnit.HOURS); + stringRedisTemplate.opsForValue().set(GET_CODE_BLACKLIST_PHONE + mobile, String.valueOf(getCodePhoneCount + 1), REFRESH_BLACKLIST_TTL, TimeUnit.HOURS); // stringRedisTemplate.opsForValue().set(GET_CODE_BLACKLIST_IP_ADDR + clientIpAddr, String.valueOf(getCodeIpAddrCount + 1), REFRESH_BLACKLIST_TTL, TimeUnit.HOURS); - stringRedisTemplate.opsForValue().set(GET_CODE_LOCK + phone, "1", GET_CODE_LOCK_TTL, TimeUnit.MINUTES); + stringRedisTemplate.opsForValue().set(GET_CODE_LOCK + mobile, "1", GET_CODE_LOCK_TTL, TimeUnit.MINUTES); // 7.校验通过,生成验证码 // String code = RandomUtil.randomNumbers(6); String code = "123123"; //为了方便测试,改成固定值 // 8.保存验证码到 redis,并上锁 - stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES); + stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + mobile, code, LOGIN_CODE_TTL, TimeUnit.MINUTES); // 9.发送验证码 log.debug("发送短信验证码成功,验证码:{}", code); @@ -68,5 +76,76 @@ public class UserServiceImpl implements UserService { return Result.success("发送短信验证码成功,验证码:"+code); } + @Override + public Result register(RegisterDto registerDto) { + // 1. 校验两次密码是否一致 + if (!registerDto.getPwd().equals(registerDto.getCpwd())) { + return Result.error("两次密码输入不一致!"); + } + + // 2. 校验验证码 + String cacheCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + registerDto.getMobile()); + if (cacheCode == null) { + return Result.error("验证码已过期,请重新获取!"); + } + if (!cacheCode.equals(registerDto.getVcode())) { + return Result.error("验证码错误!"); + } + + // 3. 检查手机号是否已注册 + User existingUserByPhone = userMapper.selectByMobile(registerDto.getMobile()); + if (existingUserByPhone != null) { + return Result.error("该手机号已被注册!"); + } + + // 4.检查邮箱是否已注册 + User existingUserByEmail = userMapper.selectByEmail(registerDto.getEmail()); + if (existingUserByEmail != null) { + return Result.error("该邮箱已被注册!"); + } + // 检查用户名是否已注册 + User existingUserByUsername = userMapper.selectByUsername(registerDto.getUsername()); + if (existingUserByUsername != null) { + return Result.error("该用户名已被注册!"); + } + + // 5. 生成盐值 + String salt = PasswordUtils.generateSalt(); + + // 6. 加密密码 + String encryptedPassword = PasswordUtils.encryptPassword(registerDto.getPwd(), salt); + + // 7. 将感兴趣标签数组转换为字符串 + String introduce = (registerDto.getInterestTags() != null && !registerDto.getInterestTags().isEmpty()) + ? String.join(",", registerDto.getInterestTags()) + : null; + + // 8. 创建用户对象 + User user = new User(); + user.setUsername(registerDto.getUsername()); + user.setCompany(registerDto.getCompany()); + user.setJob(registerDto.getJob()); + user.setAddress(registerDto.getAddress()); + user.setIntroduce(introduce); + user.setMobile(registerDto.getMobile()); + user.setEmail(registerDto.getEmail()); + user.setPwd(encryptedPassword); + user.setSalt(salt); + user.setCreateTime(LocalDateTime.now()); + user.setUpdateTime(LocalDateTime.now()); + + // 9. 保存用户到数据库 + int result = userMapper.insert(user); + if (result <= 0) { + return Result.error("注册失败,请稍后重试!"); + } + + // 10. 删除Redis中的验证码 + stringRedisTemplate.delete(LOGIN_CODE_KEY + registerDto.getMobile()); + + log.info("用户注册成功,手机号:{}", registerDto.getMobile()); + return Result.success("注册成功!"); + } + } diff --git a/src/main/resources/mapper/UserMapper.xml b/src/main/resources/mapper/UserMapper.xml new file mode 100644 index 0000000..ab32d50 --- /dev/null +++ b/src/main/resources/mapper/UserMapper.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO users (username, company, job, address, introduce, mobile, email, pwd, salt, addtime, updtime) + VALUES (#{username}, #{company}, #{job}, #{address}, #{introduce}, #{mobile}, #{email}, #{pwd}, #{salt}, #{createTime}, #{updateTime}) + + + diff --git a/src/main/resources/sql/users.sql b/src/main/resources/sql/users.sql new file mode 100644 index 0000000..559f0b2 --- /dev/null +++ b/src/main/resources/sql/users.sql @@ -0,0 +1,18 @@ +-- 用户表 +CREATE TABLE IF NOT EXISTS `users` ( + `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '用户ID', + `username` VARCHAR(50) NOT NULL COMMENT '用户名', + `company` VARCHAR(100) NOT NULL COMMENT '所属单位', + `job` VARCHAR(50) NOT NULL COMMENT '职位', + `address` VARCHAR(200) NOT NULL COMMENT '所在地址', + `introduce` VARCHAR(500) DEFAULT NULL COMMENT '感兴趣标签', + `phone` VARCHAR(11) NOT NULL COMMENT '手机号', + `email` VARCHAR(100) NOT NULL COMMENT '邮箱地址', + `pwd` VARCHAR(64) NOT NULL COMMENT '密码(加密后)', + `salt` VARCHAR(32) NOT NULL COMMENT '盐值', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `update_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_phone` (`phone`) COMMENT '手机号唯一索引', + UNIQUE KEY `uk_email` (`email`) COMMENT '邮箱唯一索引' +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户表';