han-note项目初始化完毕!
- 邮箱验证码接口完成
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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", "模板渲染错误")
|
||||
;
|
||||
|
||||
// 异常码
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user