Files
fishery-back/intc-admin/src/main/java/com/intc/web/controller/CaptchaController.java

216 lines
8.7 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.intc.web.controller;
import cn.dev33.satoken.annotation.SaIgnore;
import cn.hutool.captcha.AbstractCaptcha;
import cn.hutool.captcha.generator.CodeGenerator;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import com.intc.common.core.constant.Constants;
import com.intc.common.core.constant.GlobalConstants;
import com.intc.common.core.domain.R;
import com.intc.common.core.exception.ServiceException;
import com.intc.common.core.utils.SpringUtils;
import com.intc.common.core.utils.StringUtils;
import com.intc.common.core.utils.reflect.ReflectUtils;
import com.intc.common.mail.config.properties.MailProperties;
import com.intc.common.mail.utils.MailUtils;
import com.intc.common.ratelimiter.annotation.RateLimiter;
import com.intc.common.ratelimiter.enums.LimitType;
import com.intc.common.redis.utils.RedisUtils;
import com.intc.common.sms.config.properties.SmsProperties;
import com.intc.common.web.config.properties.CaptchaProperties;
import com.intc.common.web.enums.CaptchaType;
import com.intc.web.domain.vo.CaptchaVo;
import com.intc.fishery.domain.bo.ReqVerifySmsCode;
import jakarta.validation.constraints.NotBlank;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.sms4j.api.SmsBlend;
import org.dromara.sms4j.api.entity.SmsResponse;
import org.dromara.sms4j.core.factory.SmsFactory;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.LinkedHashMap;
/**
* 验证码操作处理
*
* @author Lion Li
*/
@SaIgnore
@Slf4j
@Validated
@RequiredArgsConstructor
@RestController
public class CaptchaController {
private final CaptchaProperties captchaProperties;
private final MailProperties mailProperties;
private final SmsProperties smsProperties;
/**
* 短信验证码
*
* @param phonenumber 用户手机号
*/
@RateLimiter(key = "#phonenumber", time = 60, count = 1)
@GetMapping("/resource/sms/code")
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phonenumber) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + phonenumber;
String code = RandomUtil.randomNumbers(6);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
// 验证码模板id 从配置文件读取
String templateId = smsProperties.getCodeTemplateId();
if (StringUtils.isBlank(templateId)) {
log.error("短信验证码模板ID未配置请在配置文件中设置 sms.plus.code-template-id");
return R.fail("短信服务配置错误,请联系管理员");
}
LinkedHashMap<String, String> map = new LinkedHashMap<>(1);
map.put("code", code);
SmsBlend smsBlend = SmsFactory.getSmsBlend("config1");
SmsResponse smsResponse = smsBlend.sendMessage(phonenumber, templateId, map);
if (!smsResponse.isSuccess()) {
log.error("验证码短信发送异常 => {}", smsResponse);
// 解析错误信息
String errorMsg = "短信发送失败";
if (smsResponse.getData() != null) {
String data = smsResponse.getData().toString();
if (data.contains("SMS_TEMPLATE_ILLEGAL")) {
errorMsg = "短信模板不存在请联系管理员配置正确的模板ID";
} else if (data.contains("SMS_SIGNATURE_ILLEGAL")) {
errorMsg = "短信签名不存在,请联系管理员配置正确的签名";
} else if (data.contains("Message")) {
// 尝试提取Message字段
try {
int msgStart = data.indexOf("Message\":\"") + 10;
int msgEnd = data.indexOf("\"", msgStart);
if (msgStart > 10 && msgEnd > msgStart) {
String message = data.substring(msgStart, msgEnd);
errorMsg = "短信发送失败:" + message;
}
} catch (Exception e) {
log.warn("解析短信错误信息失败", e);
}
} else if (data.contains("isv.")) {
errorMsg = "短信服务配置错误,请联系管理员";
}
}
return R.fail(errorMsg);
}
return R.ok();
}
/**
* 验证短信验证码
*
* @param request 手机号和验证码数据
* @return 验证结果
*/
@PostMapping("/resource/sms/verify")
public R<Void> verifySmsCode(@Validated @RequestBody ReqVerifySmsCode request) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + request.getMobilePhone();
String cachedCode = RedisUtils.getCacheObject(key);
if (StringUtils.isBlank(cachedCode)) {
return R.fail("短信验证码已过期");
}
if (!cachedCode.equals(request.getSmsCode())) {
return R.fail("短信验证码错误");
}
// 验证成功后删除验证码(根据 C# 代码中的 true 参数)
RedisUtils.deleteObject(key);
return R.ok();
}
/**
* 邮箱验证码
*
* @param email 邮箱
*/
@GetMapping("/resource/email/code")
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
if (!mailProperties.getEnabled()) {
return R.fail("当前系统没有开启邮箱功能!");
}
SpringUtils.getAopProxy(this).emailCodeImpl(email);
return R.ok();
}
/**
* 邮箱验证码
* 独立方法避免验证码关闭之后仍然走限流
*/
@RateLimiter(key = "#email", time = 60, count = 1)
public void emailCodeImpl(String email) {
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
String code = RandomUtil.randomNumbers(4);
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
try {
MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
} catch (Exception e) {
log.error("验证码短信发送异常 => {}", e.getMessage());
throw new ServiceException(e.getMessage());
}
}
/**
* 生成验证码
*/
@GetMapping("/auth/code")
public R<CaptchaVo> getCode() {
boolean captchaEnabled = captchaProperties.getEnable();
if (!captchaEnabled) {
CaptchaVo captchaVo = new CaptchaVo();
captchaVo.setCaptchaEnabled(false);
return R.ok(captchaVo);
}
return R.ok(SpringUtils.getAopProxy(this).getCodeImpl());
}
/**
* 生成验证码
* 独立方法避免验证码关闭之后仍然走限流
*/
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
public CaptchaVo getCodeImpl() {
// 保存验证码信息
String uuid = IdUtil.simpleUUID();
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
// 生成验证码
CaptchaType captchaType = captchaProperties.getType();
CodeGenerator codeGenerator;
if (CaptchaType.MATH == captchaType) {
codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getNumberLength(), false);
} else {
codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getCharLength());
}
AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
captcha.setGenerator(codeGenerator);
captcha.createCode();
// 如果是数学验证码使用SpEL表达式处理验证码结果
String code = captcha.getCode();
if (CaptchaType.MATH == captchaType) {
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
code = exp.getValue(String.class);
}
RedisUtils.setCacheObject(verifyKey, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
CaptchaVo captchaVo = new CaptchaVo();
captchaVo.setUuid(uuid);
captchaVo.setImg(captcha.getImageBase64());
return captchaVo;
}
}