han-note项目初始化完毕!

- 邮箱验证码接口完成
This commit is contained in:
Hanserwei
2025-09-30 15:36:31 +08:00
parent fe12d54c92
commit 765a1a7e4f
17 changed files with 632 additions and 0 deletions

View File

@@ -0,0 +1,31 @@
package com.hanserwei.hannote.auth.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.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisTemplateConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 设置 RedisTemplate 的连接工厂
redisTemplate.setConnectionFactory(connectionFactory);
// 使用 StringRedisSerializer 来序列化和反序列化 redis 的 key 值,确保 key 是可读的字符串
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 redis 的 value 值, 确保存储的是 JSON 格式
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
redisTemplate.setValueSerializer(serializer);
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}

View File

@@ -0,0 +1,35 @@
package com.hanserwei.hannote.auth.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import java.util.concurrent.ThreadPoolExecutor;
@Configuration
public class ThreadPoolConfig {
@Bean(name = "authTaskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心线程数
executor.setCorePoolSize(10);
//最大线程数
executor.setMaxPoolSize(50);
//队列容量
executor.setQueueCapacity(200);
//活跃时间
executor.setKeepAliveSeconds(30);
//线程名前缀
executor.setThreadNamePrefix("AuthExecutor-");
//拒绝策略:由调用线程处理,一般为主线程
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
// 等待所有任务结束后再关闭线程池
executor.setWaitForTasksToCompleteOnShutdown(true);
// 设置等待时间,如果超过这个时间还没有销毁就强制销毁,以确保应用最后能够被关闭,而不是被没有完成的任务阻塞
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}
}

View File

@@ -0,0 +1,18 @@
package com.hanserwei.hannote.auth.constant;
public class RedisKeyConstants {
/**
* 验证码 KEY 前缀
*/
private static final String VERIFICATION_CODE_KEY_PREFIX = "verification_code:";
/**
* 构建验证码 KEY
* @param email 手机号
* @return 验证码key
*/
public static String buildVerificationCodeKey(String email) {
return VERIFICATION_CODE_KEY_PREFIX + email;
}
}

View File

@@ -0,0 +1,26 @@
package com.hanserwei.hannote.auth.controller;
import com.hanserwei.framework.biz.operationlog.aspect.ApiOperationLog;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.auth.model.vo.SendVerificationCodeReqVO;
import com.hanserwei.hannote.auth.service.VerificationCodeService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@Slf4j
@RequiredArgsConstructor
public class VerificationCodeController {
private final VerificationCodeService verificationCodeService;
@PostMapping("/verification/code/send")
@ApiOperationLog(description = "发送邮件验证码")
public Response<?> send(@Validated @RequestBody SendVerificationCodeReqVO sendVerificationCodeReqVO) {
return verificationCodeService.send(sendVerificationCodeReqVO);
}
}

View File

@@ -13,6 +13,9 @@ public enum ResponseCodeEnum implements BaseExceptionInterface {
PARAM_NOT_VALID("AUTH-10001", "参数错误!!!"),
// ----------- 业务异常状态码 -----------
VERIFICATION_CODE_SEND_FREQUENTLY("AUTH-20000", "请求太频繁请3分钟后再试"),
MAIL_SEND_ERROR("AUTH-20001", "邮件发送失败,请稍后再试"),
TEMPLATE_RENDER_ERROR("AUTH-20002", "模板渲染错误")
;
// 异常码

View File

@@ -0,0 +1,18 @@
package com.hanserwei.hannote.auth.model.vo;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
public class SendVerificationCodeReqVO {
@NotBlank(message = "邮箱不能为空")
private String email;
}

View File

@@ -0,0 +1,15 @@
package com.hanserwei.hannote.auth.service;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.auth.model.vo.SendVerificationCodeReqVO;
public interface VerificationCodeService {
/**
* 发送短信验证码
*
* @param sendVerificationCodeReqVO 发送验证码VO
* @return 返回响应
*/
Response<?> send(SendVerificationCodeReqVO sendVerificationCodeReqVO);
}

View File

@@ -0,0 +1,57 @@
package com.hanserwei.hannote.auth.service.impl;
import cn.hutool.core.util.RandomUtil;
import com.hanserwei.framework.common.exception.ApiException;
import com.hanserwei.framework.common.response.Response;
import com.hanserwei.hannote.auth.constant.RedisKeyConstants;
import com.hanserwei.hannote.auth.enums.ResponseCodeEnum;
import com.hanserwei.hannote.auth.model.vo.SendVerificationCodeReqVO;
import com.hanserwei.hannote.auth.service.VerificationCodeService;
import com.hanserwei.hannote.auth.utils.MailHelper;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@RequiredArgsConstructor
@Service
@Slf4j
public class VerificationCodeServiceImpl implements VerificationCodeService {
private final RedisTemplate<String, Object> redisTemplate;
private final MailHelper mailHelper;
@Resource(name = "authTaskExecutor")
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
/**
* 发送短信验证码!
*
* @param sendVerificationCodeReqVO 发送验证码VO
* @return 响应
*/
@Override
public Response<?> send(SendVerificationCodeReqVO sendVerificationCodeReqVO) {
// 邮箱
String email = sendVerificationCodeReqVO.getEmail();
//构建Redis的Key
String codeKey = RedisKeyConstants.buildVerificationCodeKey(email);
// 判断是否发送!
Boolean hasKey = redisTemplate.hasKey(codeKey);
if (hasKey) {
//若之前发送的验证码未过期,则提示发送频繁
throw new ApiException(ResponseCodeEnum.VERIFICATION_CODE_SEND_FREQUENTLY);
}
//生成六位数随机验证码
String verificationCode = RandomUtil.randomNumbers(6);
threadPoolTaskExecutor.submit(() -> mailHelper.sendMail(verificationCode, email));
log.info("==> 邮箱: {}, 已发送验证码:【{}】", email, verificationCode);
// 存储验证码到 redis, 并设置过期时间为 3 分钟
redisTemplate.opsForValue().set(codeKey, verificationCode, 3, TimeUnit.MINUTES);
return Response.success();
}
}

View File

@@ -0,0 +1,66 @@
package com.hanserwei.hannote.auth.utils;
import com.hanserwei.framework.common.exception.ApiException;
import com.hanserwei.hannote.auth.enums.ResponseCodeEnum;
import jakarta.annotation.Resource;
import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Component;
import org.thymeleaf.TemplateEngine;
import org.thymeleaf.context.Context;
import java.util.Arrays;
import java.util.Date;
@Slf4j
@Component
public class MailHelper {
@Resource
private JavaMailSender mailSender;
@Resource
private TemplateEngine templateEngine;
@Value("${spring.mail.username}")
private String username;
public boolean sendMail(String verificationCode, String email) {
Context context = new Context();
context.setVariable("verifyCode", Arrays.asList(verificationCode.split("")));
String process;
try {
// 确保这里的 templateEngine 能够正确处理 context
process = templateEngine.process("EmailVerificationCode.html", context);
} catch (Exception e) {
// 处理模板渲染失败的异常
throw new ApiException(ResponseCodeEnum.TEMPLATE_RENDER_ERROR);
}
MimeMessage mimeMessage = mailSender.createMimeMessage();
try {
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true);
helper.setSubject("【Han-note】验证码");
helper.setFrom(username);
helper.setTo(email);
helper.setSentDate(new Date());
helper.setText(process, true);
mailSender.send(mimeMessage); // 可能会抛出 MailException (RuntimeException)
log.info("邮件发送成功!");
} catch (MessagingException | org.springframework.mail.MailException e) {
// 捕获 MimeMessageHelper 配置异常 和 mailSender 发送异常
log.error("邮件发送失败:{}", e.getMessage());
throw new ApiException(ResponseCodeEnum.MAIL_SEND_ERROR);
}
return true;
}
}