feat(user): 新增邮箱注册与验证码发送功能
- 新增 UserRegisterDTO 及 /user/register 接口 - 集成 MailerSend,异步发送 6 位验证码邮件 - 添加 RedisUtil 缓存验证码 10 分钟 - 补充 SEND_MAIL_FAILED、CONFIRM_PASSWORD_NOT_MATCH 错误码 - 关闭 Spring Security CSRF 与表单登录,放行 /user/register - AppleService 移除 @AllArgsConstructor,改用 @Resource 注入
This commit is contained in:
7
pom.xml
7
pom.xml
@@ -226,6 +226,13 @@
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- mailerSender 邮件服务 -->
|
||||
<dependency>
|
||||
<groupId>com.mailersend</groupId>
|
||||
<artifactId>java-sdk</artifactId>
|
||||
<version>1.4.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 添加Hibernate Validator作为Bean Validation提供程序 -->
|
||||
<dependency>
|
||||
<groupId>org.hibernate.validator</groupId>
|
||||
|
||||
@@ -30,7 +30,9 @@ public enum ErrorCode {
|
||||
FILE_NAME_ERROR(40002, "文件名错误"),
|
||||
USER_NOT_FOUND(40401, "用户不存在"),
|
||||
USER_INFO_UPDATE_FAILED(50002, "用户信息更新失败"),
|
||||
PASSWORD_OR_MAIL_ERROR(50003,"密码或邮箱错误" );
|
||||
PASSWORD_OR_MAIL_ERROR(50003,"密码或邮箱错误" ),
|
||||
SEND_MAIL_FAILED(50004,"邮件发送失败" ),
|
||||
CONFIRM_PASSWORD_NOT_MATCH(50005,"重复密码不匹配" );
|
||||
/**
|
||||
* 状态码
|
||||
*/
|
||||
|
||||
10
src/main/java/com/yolo/keyborad/config/AsyncConfig.java
Normal file
10
src/main/java/com/yolo/keyborad/config/AsyncConfig.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package com.yolo.keyborad.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
@Configuration
|
||||
@EnableAsync
|
||||
public class AsyncConfig {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package com.yolo.keyborad.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
|
||||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
@Configuration
|
||||
public class MailTaskExecutorConfig {
|
||||
|
||||
@Bean(name = "mailTaskExecutor")
|
||||
public Executor mailTaskExecutor() {
|
||||
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
|
||||
|
||||
executor.setCorePoolSize(2);
|
||||
executor.setMaxPoolSize(5);
|
||||
executor.setQueueCapacity(100);
|
||||
executor.setThreadNamePrefix("mail-sender-");
|
||||
|
||||
executor.initialize();
|
||||
return executor;
|
||||
}
|
||||
}
|
||||
40
src/main/java/com/yolo/keyborad/config/RedisConfig.java
Normal file
40
src/main/java/com/yolo/keyborad/config/RedisConfig.java
Normal file
@@ -0,0 +1,40 @@
|
||||
package com.yolo.keyborad.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* Redis配置类
|
||||
*
|
||||
* @author ziin
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
/**
|
||||
* 配置StringRedisTemplate
|
||||
* @param connectionFactory Redis连接工厂
|
||||
* @return StringRedisTemplate实例
|
||||
*/
|
||||
@Bean("redisTemplate")
|
||||
public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
StringRedisTemplate template = new StringRedisTemplate();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
// 设置key序列化方式
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
// 设置value序列化方式
|
||||
template.setValueSerializer(new StringRedisSerializer());
|
||||
// 设置hash key序列化方式
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
// 设置hash value序列化方式
|
||||
template.setHashValueSerializer(new StringRedisSerializer());
|
||||
|
||||
template.afterPropertiesSet();
|
||||
return template;
|
||||
}
|
||||
}
|
||||
@@ -74,7 +74,8 @@ public class SaTokenConfigure implements WebMvcConfigurer {
|
||||
"/user/login",
|
||||
"/character/listByUser",
|
||||
"/user/updateInfo",
|
||||
"/user/detail"
|
||||
"/user/detail",
|
||||
"/user/register"
|
||||
};
|
||||
}
|
||||
@Bean
|
||||
|
||||
@@ -2,8 +2,11 @@ package com.yolo.keyborad.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
|
||||
@Configuration
|
||||
public class SecurityConfig {
|
||||
@@ -13,4 +16,21 @@ public class SecurityConfig {
|
||||
// strength(cost)默认 10,够用了;可以视情况调高,比如 12
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||
http
|
||||
// REST 接口通常会关掉 csrf(前后端分离的话)
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
// 配置请求权限
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.anyRequest().permitAll() // 所有请求都放行
|
||||
)
|
||||
// 关掉默认的登录页表单
|
||||
.formLogin(AbstractHttpConfigurer::disable)
|
||||
// 关掉 httpBasic 弹窗登录
|
||||
.httpBasic(AbstractHttpConfigurer::disable);
|
||||
|
||||
return http.build();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import com.yolo.keyborad.common.ResultUtils;
|
||||
import com.yolo.keyborad.model.dto.AppleLoginReq;
|
||||
import com.yolo.keyborad.model.dto.user.KeyboardUserReq;
|
||||
import com.yolo.keyborad.model.dto.user.UserLoginDTO;
|
||||
import com.yolo.keyborad.model.dto.user.UserRegisterDTO;
|
||||
import com.yolo.keyborad.model.entity.KeyboardUser;
|
||||
import com.yolo.keyborad.model.vo.user.KeyboardUserInfoRespVO;
|
||||
import com.yolo.keyborad.model.vo.user.KeyboardUserRespVO;
|
||||
@@ -78,4 +79,11 @@ public class UserController {
|
||||
KeyboardUser keyboardUser = userService.getById(loginId);
|
||||
return ResultUtils.success(BeanUtil.copyProperties(keyboardUser, KeyboardUserInfoRespVO.class));
|
||||
}
|
||||
|
||||
@PostMapping("/register")
|
||||
@Operation(summary = "用户注册",description = "用户注册接口")
|
||||
public BaseResponse<Boolean> register(@RequestBody UserRegisterDTO userRegisterDTO) {
|
||||
userService.userRegister(userRegisterDTO);
|
||||
return ResultUtils.success(true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package com.yolo.keyborad.model.dto.user;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/*
|
||||
* @author: ziin
|
||||
* @date: 2025/12/3 20:56
|
||||
*/
|
||||
@Data
|
||||
public class UserRegisterDTO {
|
||||
|
||||
private String mailAddress;
|
||||
|
||||
private String password;
|
||||
|
||||
private String passwordConfirm;
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.yolo.keyborad.service;
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import com.yolo.keyborad.model.dto.user.KeyboardUserReq;
|
||||
import com.yolo.keyborad.model.dto.user.UserLoginDTO;
|
||||
import com.yolo.keyborad.model.dto.user.UserRegisterDTO;
|
||||
import com.yolo.keyborad.model.entity.KeyboardUser;
|
||||
import com.yolo.keyborad.model.vo.user.KeyboardUserRespVO;
|
||||
|
||||
@@ -19,4 +20,7 @@ public interface UserService extends IService<KeyboardUser> {
|
||||
KeyboardUserRespVO login(UserLoginDTO userLoginDTO);
|
||||
|
||||
Boolean updateUserInfo(KeyboardUserReq keyboardUser);
|
||||
|
||||
Boolean userRegister(UserRegisterDTO userRegisterDTO);
|
||||
|
||||
}
|
||||
|
||||
@@ -34,7 +34,6 @@ import java.util.Objects;
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
@AllArgsConstructor
|
||||
public class AppleServiceImpl implements IAppleService {
|
||||
|
||||
@Resource
|
||||
|
||||
@@ -12,13 +12,21 @@ import com.yolo.keyborad.exception.BusinessException;
|
||||
import com.yolo.keyborad.mapper.KeyboardUserMapper;
|
||||
import com.yolo.keyborad.model.dto.user.KeyboardUserReq;
|
||||
import com.yolo.keyborad.model.dto.user.UserLoginDTO;
|
||||
import com.yolo.keyborad.model.dto.user.UserRegisterDTO;
|
||||
import com.yolo.keyborad.model.entity.KeyboardUser;
|
||||
import com.yolo.keyborad.model.vo.user.KeyboardUserRespVO;
|
||||
import com.yolo.keyborad.service.UserService;
|
||||
import com.yolo.keyborad.utils.RedisUtil;
|
||||
import com.yolo.keyborad.utils.SendMailUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/*
|
||||
* @author: ziin
|
||||
@@ -32,7 +40,14 @@ public class UserServiceImpl extends ServiceImpl<KeyboardUserMapper, KeyboardUse
|
||||
private KeyboardUserMapper keyboardUserMapper;
|
||||
|
||||
@Resource
|
||||
private BCryptPasswordEncoder bCryptPasswordEncoder;
|
||||
private PasswordEncoder passwordEncoder;
|
||||
|
||||
@Resource
|
||||
private SendMailUtils sendMailUtils;
|
||||
|
||||
@Resource
|
||||
private RedisUtil redisUtil;
|
||||
|
||||
|
||||
@Override
|
||||
public KeyboardUser selectUserWithSubjectId(String sub) {
|
||||
@@ -47,7 +62,7 @@ public class UserServiceImpl extends ServiceImpl<KeyboardUserMapper, KeyboardUse
|
||||
KeyboardUser keyboardUser = new KeyboardUser();
|
||||
keyboardUser.setSubjectId(sub);
|
||||
keyboardUser.setUid(IdUtil.getSnowflake().nextId());
|
||||
keyboardUser.setNickName("User" + RandomUtil.randomString(6));
|
||||
keyboardUser.setNickName("User_" + RandomUtil.randomString(6));
|
||||
keyboardUserMapper.insert(keyboardUser);
|
||||
return keyboardUser;
|
||||
}
|
||||
@@ -63,7 +78,7 @@ public class UserServiceImpl extends ServiceImpl<KeyboardUserMapper, KeyboardUse
|
||||
if (keyboardUser == null) {
|
||||
throw new BusinessException(ErrorCode.USER_NOT_FOUND);
|
||||
}
|
||||
if (!bCryptPasswordEncoder.matches(userLoginDTO.getPassword(), keyboardUser.getPassword())) {
|
||||
if (!passwordEncoder.matches(userLoginDTO.getPassword(), keyboardUser.getPassword())) {
|
||||
throw new BusinessException(ErrorCode.PASSWORD_OR_MAIL_ERROR);
|
||||
}
|
||||
StpUtil.login(keyboardUser.getId());
|
||||
@@ -89,4 +104,22 @@ public class UserServiceImpl extends ServiceImpl<KeyboardUserMapper, KeyboardUse
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean userRegister(UserRegisterDTO userRegisterDTO) {
|
||||
if (!userRegisterDTO.getPassword().equals(userRegisterDTO.getPasswordConfirm())) {
|
||||
throw new BusinessException(ErrorCode.CONFIRM_PASSWORD_NOT_MATCH);
|
||||
}
|
||||
KeyboardUser keyboardUser = new KeyboardUser();
|
||||
keyboardUser.setUid(IdUtil.getSnowflake().nextId());
|
||||
keyboardUser.setNickName("User_" + RandomUtil.randomString(6));
|
||||
keyboardUser.setPassword(passwordEncoder.encode(userRegisterDTO.getPassword()));
|
||||
keyboardUser.setEmail(userRegisterDTO.getMailAddress());
|
||||
log.info(keyboardUser.toString());
|
||||
int code = RandomUtil.randomInt(100000, 999999);
|
||||
redisUtil.setEx("user:"+userRegisterDTO.getMailAddress(), String.valueOf(code),600, TimeUnit.SECONDS);
|
||||
sendMailUtils.sendEmail(keyboardUser.getNickName(),userRegisterDTO.getMailAddress(),code);
|
||||
return keyboardUserMapper.insert(keyboardUser) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
1359
src/main/java/com/yolo/keyborad/utils/RedisUtil.java
Normal file
1359
src/main/java/com/yolo/keyborad/utils/RedisUtil.java
Normal file
File diff suppressed because it is too large
Load Diff
57
src/main/java/com/yolo/keyborad/utils/SendMailUtils.java
Normal file
57
src/main/java/com/yolo/keyborad/utils/SendMailUtils.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package com.yolo.keyborad.utils;
|
||||
|
||||
import com.mailersend.sdk.MailerSend;
|
||||
import com.mailersend.sdk.MailerSendResponse;
|
||||
import com.mailersend.sdk.Recipient;
|
||||
import com.mailersend.sdk.emails.Email;
|
||||
import com.mailersend.sdk.exceptions.MailerSendException;
|
||||
import com.yolo.keyborad.common.ErrorCode;
|
||||
import com.yolo.keyborad.exception.BusinessException;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/*
|
||||
* @author: ziin
|
||||
* @date: 2025/12/3 20:43
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class SendMailUtils {
|
||||
|
||||
@Value("${mail_access_token}")
|
||||
private String accessToken;
|
||||
|
||||
@Async("mailTaskExecutor")
|
||||
public void sendEmail(String userName,String userMail,Integer code ) {
|
||||
|
||||
Email email = new Email();
|
||||
|
||||
email.setFrom("verify code", "MS_JqR6MO@no-replay.loveamorkey.com");
|
||||
|
||||
Recipient recipient = new Recipient(userName, userMail);
|
||||
|
||||
email.addRecipient(userName,userMail);
|
||||
|
||||
email.setSubject("no-replay");
|
||||
|
||||
email.setTemplateId("k68zxl28q79lj905");
|
||||
|
||||
email.addPersonalization(recipient, "code", code);
|
||||
|
||||
MailerSend ms = new MailerSend();
|
||||
|
||||
ms.setToken(accessToken);
|
||||
|
||||
try {
|
||||
MailerSendResponse response = ms.emails().send(email);
|
||||
log.info("邮件发送成功 messageId={}", response.messageId);
|
||||
} catch (MailerSendException e) {
|
||||
log.error("邮件发送失败: {}", e.getMessage());
|
||||
// 异步任务不能再抛业务异常,避免影响主业务流程
|
||||
// 如果需要,你可以把失败记录写入 DB 或重试队列
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -48,4 +48,6 @@ mybatis-plus:
|
||||
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)
|
||||
|
||||
appid: loveKeyboard
|
||||
appsecret: kZJM39HYvhxwbJkG1fmquQRVkQiLAh2H
|
||||
appsecret: kZJM39HYvhxwbJkG1fmquQRVkQiLAh2H
|
||||
|
||||
mail_access_token: mlsn.f2aafcaf4bccf01529f8636fa13a2c16c33a934d5e14be3adb0cc21c2fe40fe1
|
||||
Reference in New Issue
Block a user