refactor(system): 重构验证码功能

- 移除 SystemCaptchaService 接口及其实现
- 将验证码相关功能整合到 SystemEmailService 中
- 优化验证码发送和验证逻辑
- 使用 CsResourceTemplateUtil 工具类渲染邮件模板
- 更新相关控制器和前端 API
This commit is contained in:
2025-06-22 16:53:14 +08:00
parent 8e6d0e0dcd
commit 3757f6ed33
11 changed files with 114 additions and 143 deletions
+2 -2
View File
@@ -14,9 +14,9 @@ export function sendResetPasswordCaptcha(data) {
}) })
} }
export function checkCodeAvailable(email, code, codeBi) { export function checkEmailCaptcha(email, code, codeBi) {
return request({ return request({
url: `api/code/checkCodeAvailable?email=${email}&code=${code}&codeBi=${codeBi}`, url: `api/code/checkEmailCaptcha?email=${email}&code=${code}&codeBi=${codeBi}`,
method: 'post' method: 'post'
}) })
} }
@@ -33,6 +33,7 @@ server:
spring: spring:
application: application:
name: cutejava name: cutejava
title: CuteJava后台管理系统
lifecycle: lifecycle:
# 采用java.time.Duration格式的值,如果在这个时间内,优雅停机没有停掉应用,超过这个时间就会强制停止应用 # 采用java.time.Duration格式的值,如果在这个时间内,优雅停机没有停掉应用,超过这个时间就会强制停止应用
timeout-per-shutdown-phase: 30s timeout-per-shutdown-phase: 30s
@@ -11,16 +11,16 @@ import lombok.Getter;
public enum SystemCaptchaBizEnum { public enum SystemCaptchaBizEnum {
/* 通过手机号码重置邮箱 */ /* 通过手机号码重置邮箱 */
PHONE_RESET_EMAIL_CODE("10000", "phone_reset_email_code_", "通过手机号码重置邮箱"), PHONE_RESET_EMAIL_CODE("10000", "phone_reset_email_code_", "email.ftl","通过手机号码重置邮箱"),
/* 通过旧邮箱重置邮箱 */ /* 通过旧邮箱重置邮箱 */
EMAIL_RESET_EMAIL_CODE("10001", "email_reset_email_code_", "通过旧邮箱重置邮箱"), EMAIL_RESET_EMAIL_CODE("10001", "email_reset_email_code_", "email.ftl","通过旧邮箱重置邮箱"),
/* 通过手机号码重置密码 */ /* 通过手机号码重置密码 */
PHONE_RESET_PWD_CODE("10002", "phone_reset_pwd_code_", "通过手机号码重置密码"), PHONE_RESET_PWD_CODE("10002", "phone_reset_pwd_code_", "email.ftl","通过手机号码重置密码"),
/* 通过邮箱重置密码 */ /* 通过邮箱重置密码 */
EMAIL_RESET_PWD_CODE("10003", "email_reset_pwd_code_", "通过邮箱重置密码"); EMAIL_RESET_PWD_CODE("10003", "email_reset_pwd_code_", "email.ftl","通过邮箱重置密码");
/** /**
* 业务编码 * 业务编码
@@ -30,6 +30,10 @@ public enum SystemCaptchaBizEnum {
* redis中的key * redis中的key
*/ */
private final String redisKey; private final String redisKey;
/**
* resources template中的模版名称
*/
private final String templateName;
/** /**
* 业务描述 * 业务描述
*/ */
@@ -1,7 +1,6 @@
package cn.odboy.core.controller.system; package cn.odboy.core.controller.system;
import cn.odboy.core.constant.system.SystemCaptchaBizEnum; import cn.odboy.core.constant.system.SystemCaptchaBizEnum;
import cn.odboy.core.service.system.SystemCaptchaService;
import cn.odboy.core.service.system.SystemEmailService; import cn.odboy.core.service.system.SystemEmailService;
import io.swagger.annotations.Api; import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
@@ -13,49 +12,32 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import java.util.Objects;
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@RequestMapping("/api/code") @RequestMapping("/api/code")
@Api(tags = "系统:验证码管理") @Api(tags = "系统:验证码管理")
public class SystemCaptchaController { public class SystemCaptchaController {
private final SystemCaptchaService captchaService; private final SystemEmailService systemEmailService;
private final SystemEmailService emailService;
/**
* 除了这个其他的接口都没有用上
*
* @param email 邮件地址
*/
@ApiOperation("重置邮箱,发送验证码") @ApiOperation("重置邮箱,发送验证码")
@PostMapping(value = "/sendResetEmailCaptcha") @PostMapping(value = "/sendResetEmailCaptcha")
public ResponseEntity<Void> sendResetEmailCaptcha(@RequestParam String email) { public ResponseEntity<Void> sendResetEmailCaptcha(@RequestParam String email) {
emailService.sendEmail(captchaService.renderCodeTemplate(email, SystemCaptchaBizEnum.EMAIL_RESET_EMAIL_CODE.getRedisKey())); systemEmailService.sendCaptcha(SystemCaptchaBizEnum.EMAIL_RESET_EMAIL_CODE, email);
return new ResponseEntity<>(HttpStatus.OK); return new ResponseEntity<>(HttpStatus.OK);
} }
@ApiOperation("重置密码,发送验证码") @ApiOperation("重置密码,发送验证码")
@PostMapping(value = "/sendResetPasswordCaptcha") @PostMapping(value = "/sendResetPasswordCaptcha")
public ResponseEntity<Void> sendResetPasswordCaptcha(@RequestParam String email) { public ResponseEntity<Void> sendResetPasswordCaptcha(@RequestParam String email) {
emailService.sendEmail(captchaService.renderCodeTemplate(email, SystemCaptchaBizEnum.EMAIL_RESET_PWD_CODE.getRedisKey())); systemEmailService.sendCaptcha(SystemCaptchaBizEnum.EMAIL_RESET_PWD_CODE, email);
return new ResponseEntity<>(HttpStatus.OK); return new ResponseEntity<>(HttpStatus.OK);
} }
@ApiOperation("验证码验证") @ApiOperation("验证码验证")
@PostMapping(value = "/checkCodeAvailable") @PostMapping(value = "/checkEmailCaptcha")
public ResponseEntity<Void> checkCodeAvailable(@RequestParam String email, @RequestParam String code, @RequestParam String codeBi) { public ResponseEntity<Void> checkEmailCaptcha(@RequestParam String email, @RequestParam String code, @RequestParam String codeBi) {
SystemCaptchaBizEnum biEnum = SystemCaptchaBizEnum.getByBizCode(codeBi); SystemCaptchaBizEnum biEnum = SystemCaptchaBizEnum.getByBizCode(codeBi);
switch (Objects.requireNonNull(biEnum)) { systemEmailService.checkEmailCaptcha(biEnum, email, code);
case EMAIL_RESET_EMAIL_CODE:
captchaService.checkCodeAvailable(SystemCaptchaBizEnum.EMAIL_RESET_EMAIL_CODE.getBizCode(), email, code);
break;
case EMAIL_RESET_PWD_CODE:
captchaService.checkCodeAvailable(SystemCaptchaBizEnum.EMAIL_RESET_PWD_CODE.getBizCode(), email, code);
break;
default:
break;
}
return new ResponseEntity<>(HttpStatus.OK); return new ResponseEntity<>(HttpStatus.OK);
} }
} }
@@ -27,7 +27,7 @@ public class SystemEmailController {
@ApiOperation("查询配置") @ApiOperation("查询配置")
@PostMapping(value = "/queryEmailConfig") @PostMapping(value = "/queryEmailConfig")
public ResponseEntity<SystemEmailConfigTb> queryEmailConfig() { public ResponseEntity<SystemEmailConfigTb> queryEmailConfig() {
return new ResponseEntity<>(systemEmailService.queryEmailConfig(), HttpStatus.OK); return new ResponseEntity<>(systemEmailService.getEmailConfig(), HttpStatus.OK);
} }
@ApiOperation("配置邮件") @ApiOperation("配置邮件")
@@ -47,7 +47,7 @@ public class SystemUserController {
private final SystemDataService systemDataService; private final SystemDataService systemDataService;
private final SystemDeptService systemDeptService; private final SystemDeptService systemDeptService;
private final SystemRoleService systemRoleService; private final SystemRoleService systemRoleService;
private final SystemCaptchaService verificationCodeService; private final SystemEmailService systemEmailService;
private final AppProperties properties; private final AppProperties properties;
@ApiOperation("导出用户数据") @ApiOperation("导出用户数据")
@@ -169,7 +169,7 @@ public class SystemUserController {
if (!passwordEncoder.matches(password, user.getPassword())) { if (!passwordEncoder.matches(password, user.getPassword())) {
throw new BadRequestException("密码错误"); throw new BadRequestException("密码错误");
} }
verificationCodeService.checkCodeAvailable(SystemCaptchaBizEnum.EMAIL_RESET_EMAIL_CODE.getBizCode(), resources.getEmail(), code); systemEmailService.checkEmailCaptcha(SystemCaptchaBizEnum.EMAIL_RESET_EMAIL_CODE, resources.getEmail(), code);
systemUserService.modifyUserEmailByUsername(user.getUsername(), resources.getEmail()); systemUserService.modifyUserEmailByUsername(user.getUsername(), resources.getEmail());
return new ResponseEntity<>(HttpStatus.OK); return new ResponseEntity<>(HttpStatus.OK);
} }
@@ -0,0 +1,21 @@
package cn.odboy.core.framework.templatefile;
import cn.hutool.core.lang.Dict;
import cn.hutool.extra.template.Template;
import cn.hutool.extra.template.TemplateConfig;
import cn.hutool.extra.template.TemplateEngine;
import cn.hutool.extra.template.TemplateUtil;
/**
* Resources模版工具
*
* @author odboy
* @date 2025-06-22
*/
public class CsResourceTemplateUtil {
public static String render(String filename, Dict params) {
TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
Template template = engine.getTemplate(filename);
return template.render(params);
}
}
@@ -1,24 +0,0 @@
package cn.odboy.core.service.system;
import cn.odboy.core.dal.model.system.SendSystemEmailArgs;
public interface SystemCaptchaService {
/**
* 发送验证码
*
* @param email /
* @param key /
* @return /
*/
SendSystemEmailArgs renderCodeTemplate(String email, String key);
/**
* 验证
*
* @param code /
* @param key /
*/
void checkCodeAvailable(String key, String email, String code);
}
@@ -1,60 +0,0 @@
package cn.odboy.core.service.system;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.template.Template;
import cn.hutool.extra.template.TemplateConfig;
import cn.hutool.extra.template.TemplateEngine;
import cn.hutool.extra.template.TemplateUtil;
import cn.odboy.core.dal.model.system.SendSystemEmailArgs;
import cn.odboy.exception.BadRequestException;
import cn.odboy.redis.RedisHelper;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
@Service
@RequiredArgsConstructor
public class SystemCaptchaServiceImpl implements SystemCaptchaService {
private final RedisHelper redisHelper;
@Value("${app.captcha.expireTime}")
private Long expiration;
@Override
@Transactional(rollbackFor = Exception.class)
public SendSystemEmailArgs renderCodeTemplate(String email, String key) {
SendSystemEmailArgs sendEmailRequest;
String content;
String redisKey = key + email;
// 如果不存在有效的验证码,就创建一个新的
TemplateEngine engine = TemplateUtil.createEngine(new TemplateConfig("template", TemplateConfig.ResourceMode.CLASSPATH));
Template template = engine.getTemplate("email.ftl");
String oldCode = redisHelper.get(redisKey, String.class);
if (oldCode == null) {
String code = RandomUtil.randomNumbers(6);
// 存入缓存
if (!redisHelper.set(redisKey, code, expiration)) {
throw new BadRequestException("服务异常,请联系网站负责人");
}
// 存在就再次发送原来的验证码
content = template.render(Dict.create().set("code", code));
} else {
content = template.render(Dict.create().set("code", oldCode));
}
sendEmailRequest = new SendSystemEmailArgs(Collections.singletonList(email), "CuteJava后台管理系统", content);
return sendEmailRequest;
}
@Override
public void checkCodeAvailable(String key, String email, String code) {
String value = redisHelper.get(key + email, String.class);
if (value == null || !value.equals(code)) {
throw new BadRequestException("无效验证码");
} else {
redisHelper.del(key);
}
}
}
@@ -1,5 +1,6 @@
package cn.odboy.core.service.system; package cn.odboy.core.service.system;
import cn.odboy.core.constant.system.SystemCaptchaBizEnum;
import cn.odboy.core.dal.dataobject.system.SystemEmailConfigTb; import cn.odboy.core.dal.dataobject.system.SystemEmailConfigTb;
import cn.odboy.core.dal.model.system.SendSystemEmailArgs; import cn.odboy.core.dal.model.system.SendSystemEmailArgs;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
@@ -23,11 +24,15 @@ public interface SystemEmailService extends IService<SystemEmailConfigTb> {
*/ */
void sendEmail(SendSystemEmailArgs sendEmailRequest); void sendEmail(SendSystemEmailArgs sendEmailRequest);
/**
* 校验邮箱验证码
*/
void checkEmailCaptcha(SystemCaptchaBizEnum biEnum, String email, String code);
/** /**
* 查询配置 * 发送给邮箱验证码
*
* @return EmailConfig 邮件配置
*/ */
SystemEmailConfigTb queryEmailConfig(); void sendCaptcha(SystemCaptchaBizEnum biEnum, String email);
SystemEmailConfigTb getEmailConfig();
} }
@@ -1,59 +1,67 @@
package cn.odboy.core.service.system; package cn.odboy.core.service.system;
import cn.hutool.core.lang.Dict;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.extra.mail.Mail; import cn.hutool.extra.mail.Mail;
import cn.hutool.extra.mail.MailAccount; import cn.hutool.extra.mail.MailAccount;
import cn.odboy.core.constant.system.SystemCaptchaBizEnum;
import cn.odboy.core.dal.dataobject.system.SystemEmailConfigTb; import cn.odboy.core.dal.dataobject.system.SystemEmailConfigTb;
import cn.odboy.core.dal.mysql.system.SystemEmailConfigMapper;
import cn.odboy.core.dal.model.system.SendSystemEmailArgs; import cn.odboy.core.dal.model.system.SendSystemEmailArgs;
import cn.odboy.core.dal.mysql.system.SystemEmailConfigMapper;
import cn.odboy.core.framework.templatefile.CsResourceTemplateUtil;
import cn.odboy.exception.BadRequestException; import cn.odboy.exception.BadRequestException;
import cn.odboy.redis.RedisHelper;
import cn.odboy.util.DesEncryptUtil; import cn.odboy.util.DesEncryptUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.beans.factory.annotation.Autowired; import lombok.RequiredArgsConstructor;
import org.springframework.cache.annotation.CacheConfig; import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
@Service @Service
@RequiredArgsConstructor
public class SystemEmailServiceImpl extends ServiceImpl<SystemEmailConfigMapper, SystemEmailConfigTb> implements SystemEmailService { public class SystemEmailServiceImpl extends ServiceImpl<SystemEmailConfigMapper, SystemEmailConfigTb> implements SystemEmailService {
private final RedisHelper redisHelper;
@Value("${app.captcha.expireTime}")
private Long captchaExpireTime;
@Value("${spring.application.title}")
private String applicationTitle;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void modifyEmailConfigOnPassChange(SystemEmailConfigTb emailConfig) throws Exception { public void modifyEmailConfigOnPassChange(SystemEmailConfigTb emailConfig) throws Exception {
SystemEmailConfigTb localConfig = queryEmailConfig(); SystemEmailConfigTb systemEmailConfigTb = getEmailConfig();
if (!emailConfig.getPassword().equals(localConfig.getPassword())) { if (!emailConfig.getPassword().equals(systemEmailConfigTb.getPassword())) {
// 对称加密 // 对称加密
emailConfig.setPassword(DesEncryptUtil.desEncrypt(emailConfig.getPassword())); systemEmailConfigTb.setPassword(DesEncryptUtil.desEncrypt(emailConfig.getPassword()));
} }
emailConfig.setId(1L); systemEmailConfigTb.setId(1L);
saveOrUpdate(emailConfig); saveOrUpdate(systemEmailConfigTb);
} }
@Override @Override
@Transactional(rollbackFor = Exception.class)
public void sendEmail(SendSystemEmailArgs sendEmailRequest) { public void sendEmail(SendSystemEmailArgs sendEmailRequest) {
SystemEmailConfigTb emailConfig = queryEmailConfig(); SystemEmailConfigTb systemEmailConfigTb = getEmailConfig();
if (emailConfig.getId() == null) { if (systemEmailConfigTb.getId() == null) {
throw new BadRequestException("请先配置,再操作"); throw new BadRequestException("请先配置,再操作");
} }
// 封装 // 封装
MailAccount account = new MailAccount(); MailAccount account = new MailAccount();
// 设置用户 // 设置用户
String user = emailConfig.getFromUser().split("@")[0]; String user = systemEmailConfigTb.getFromUser().split("@")[0];
account.setUser(user); account.setUser(user);
account.setHost(emailConfig.getHost()); account.setHost(systemEmailConfigTb.getHost());
account.setPort(Integer.parseInt(emailConfig.getPort())); account.setPort(Integer.parseInt(systemEmailConfigTb.getPort()));
account.setAuth(true); account.setAuth(true);
try { try {
// 对称解密 // 对称解密
account.setPass(DesEncryptUtil.desDecrypt(emailConfig.getPassword())); account.setPass(DesEncryptUtil.desDecrypt(systemEmailConfigTb.getPassword()));
} catch (Exception e) { } catch (Exception e) {
throw new BadRequestException(e.getMessage()); throw new BadRequestException(e.getMessage());
} }
account.setFrom(emailConfig.getUser() + "<" + emailConfig.getFromUser() + ">"); account.setFrom(systemEmailConfigTb.getUser() + "<" + systemEmailConfigTb.getFromUser() + ">");
// ssl方式发送 // ssl方式发送
account.setSslEnable(true); account.setSslEnable(true);
// 使用STARTTLS安全连接 // 使用STARTTLS安全连接
@@ -79,8 +87,42 @@ public class SystemEmailServiceImpl extends ServiceImpl<SystemEmailConfigMapper,
} }
@Override @Override
public SystemEmailConfigTb queryEmailConfig() { public void checkEmailCaptcha(SystemCaptchaBizEnum biEnum, String email, String code) {
SystemEmailConfigTb emailConfig = getById(1L); String redisKey = biEnum.getRedisKey() + email;
return emailConfig == null ? new SystemEmailConfigTb() : emailConfig; String value = redisHelper.get(redisKey, String.class);
if (value == null || !value.equals(code)) {
throw new BadRequestException("无效验证码");
} else {
redisHelper.del(redisKey);
}
}
@Override
public void sendCaptcha(SystemCaptchaBizEnum biEnum, String email) {
if (biEnum == null) {
throw new BadRequestException("biEnum必填");
}
String content;
String redisKey = biEnum.getRedisKey() + email;
String oldCode = redisHelper.get(redisKey, String.class);
if (oldCode == null) {
String code = RandomUtil.randomNumbers(6);
// 存入缓存
if (!redisHelper.set(redisKey, code, captchaExpireTime)) {
throw new BadRequestException("服务异常,请联系网站负责人");
}
// 存在就再次发送原来的验证码
content = CsResourceTemplateUtil.render(biEnum.getTemplateName(), Dict.create().set("code", code));
} else {
content = CsResourceTemplateUtil.render(biEnum.getTemplateName(), Dict.create().set("code", oldCode));
}
SendSystemEmailArgs sendEmailRequest = new SendSystemEmailArgs(Collections.singletonList(email), applicationTitle, content);
sendEmail(sendEmailRequest);
}
@Override
public SystemEmailConfigTb getEmailConfig() {
SystemEmailConfigTb systemEmailConfigTb = getById(1L);
return systemEmailConfigTb == null ? new SystemEmailConfigTb() : systemEmailConfigTb;
} }
} }