Merge remote-tracking branch 'origin/6.X' into 6.X
# Conflicts: # ruoyi-admin/src/main/java/org/dromara/web/controller/CaptchaController.java # ruoyi-admin/src/main/resources/application.yml # ruoyi-common/ruoyi-common-captcha/src/main/java/org/dromara/common/captcha/config/CaptchaConfig.java
This commit is contained in:
@@ -1,41 +1,21 @@
|
||||
package org.dromara.web.controller;
|
||||
|
||||
import cloud.tianai.captcha.application.vo.ImageCaptchaVO;
|
||||
import cloud.tianai.captcha.common.response.ApiResponse;
|
||||
import cloud.tianai.captcha.validator.common.model.dto.ImageCaptchaTrack;
|
||||
import cn.dev33.satoken.annotation.SaIgnore;
|
||||
import cn.hutool.captcha.generator.CodeGenerator;
|
||||
import cn.hutool.captcha.generator.MathGenerator;
|
||||
import cn.hutool.captcha.generator.RandomGenerator;
|
||||
import cn.hutool.core.util.IdUtil;
|
||||
import cn.hutool.core.util.RandomUtil;
|
||||
import org.dromara.common.captcha.utils.CaptchaUtils;
|
||||
import org.dromara.common.captcha.utils.CaptchaUtils.CaptchaVo;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.dromara.common.core.constant.Constants;
|
||||
import org.dromara.common.core.constant.GlobalConstants;
|
||||
import org.dromara.common.core.domain.R;
|
||||
import org.dromara.common.core.exception.ServiceException;
|
||||
import org.dromara.common.core.utils.SpringUtils;
|
||||
import org.dromara.common.core.utils.StringUtils;
|
||||
import org.dromara.common.core.utils.regex.RegexValidator;
|
||||
import org.dromara.common.mail.config.properties.MailProperties;
|
||||
import org.dromara.common.mail.core.MailBuilder;
|
||||
import org.dromara.common.redis.annotation.RateLimiter;
|
||||
import org.dromara.common.redis.enums.LimitType;
|
||||
import org.dromara.common.redis.utils.RedisUtils;
|
||||
import org.dromara.common.web.config.properties.CaptchaProperties;
|
||||
import org.dromara.common.web.core.WaveAndCircleCaptcha;
|
||||
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.RestController;
|
||||
|
||||
import java.awt.*;
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
/**
|
||||
* 验证码操作处理
|
||||
@@ -47,9 +27,10 @@ import java.util.LinkedHashMap;
|
||||
@Validated
|
||||
@RequiredArgsConstructor
|
||||
@RestController
|
||||
@RequestMapping("/resource/captcha")
|
||||
public class CaptchaController {
|
||||
|
||||
private final CaptchaProperties captchaProperties;
|
||||
private final CaptchaUtils captchaUtils;
|
||||
private final MailProperties mailProperties;
|
||||
|
||||
/**
|
||||
@@ -59,25 +40,9 @@ public class CaptchaController {
|
||||
* @return 操作结果
|
||||
*/
|
||||
@RateLimiter(key = "#phoneNumber", time = 60, count = 1)
|
||||
@GetMapping("/resource/sms/code")
|
||||
public R<Void> smsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phoneNumber) {
|
||||
if (!RegexValidator.isMobile(phoneNumber)) {
|
||||
return R.fail("请输入正确的手机号!");
|
||||
}
|
||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + phoneNumber;
|
||||
String code = RandomUtil.randomNumbers(4);
|
||||
// 验证码模板id 自行处理 (查数据库或写死均可)
|
||||
String templateId = "";
|
||||
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);
|
||||
Object data = smsResponse.getData();
|
||||
return R.fail(data == null ? "验证码短信发送失败" : data.toString());
|
||||
}
|
||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
@GetMapping("/sms")
|
||||
public R<Void> sendSmsCode(@NotBlank(message = "{user.phonenumber.not.blank}") String phoneNumber) {
|
||||
captchaUtils.generateSmsCaptcha(phoneNumber);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
@@ -87,96 +52,59 @@ public class CaptchaController {
|
||||
* @param email 邮箱
|
||||
* @return 操作结果
|
||||
*/
|
||||
@GetMapping("/resource/email/code")
|
||||
public R<Void> emailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
|
||||
@GetMapping("/email")
|
||||
public R<Void> sendEmailCode(@NotBlank(message = "{user.email.not.blank}") String email) {
|
||||
if (!mailProperties.getEnabled()) {
|
||||
return R.fail("当前系统没有开启邮箱功能!");
|
||||
}
|
||||
if (!RegexValidator.isEmail(email)) {
|
||||
return R.fail("请输入正确的邮箱地址!");
|
||||
}
|
||||
SpringUtils.getAopProxy(this).emailCodeImpl(email);
|
||||
SpringUtils.getAopProxy(this).captchaUtils.generateEmailCaptcha(email);
|
||||
return R.ok();
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮箱验证码的实际执行方法,拆分出来避免开关关闭时仍触发限流。
|
||||
*
|
||||
* @param email 邮箱
|
||||
*/
|
||||
@RateLimiter(key = "#email", time = 60, count = 1)
|
||||
public void emailCodeImpl(String email) {
|
||||
String key = GlobalConstants.CAPTCHA_CODE_KEY + email;
|
||||
String code = RandomUtil.randomNumbers(4);
|
||||
try {
|
||||
MailBuilder.of()
|
||||
.to(email)
|
||||
.subject("登录验证码")
|
||||
.text("您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。")
|
||||
.send();
|
||||
RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
|
||||
} catch (Exception e) {
|
||||
log.error("验证码邮件发送异常 => {}", e.getMessage());
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取图片验证码。
|
||||
*
|
||||
* @return 验证码信息
|
||||
*/
|
||||
@GetMapping("/auth/code")
|
||||
public R<CaptchaVo> getCode() {
|
||||
boolean captchaEnabled = captchaProperties.getEnable();
|
||||
if (!captchaEnabled) {
|
||||
return R.ok(new CaptchaVo(false, null, null));
|
||||
}
|
||||
return R.ok(SpringUtils.getAopProxy(this).getCodeImpl());
|
||||
@GetMapping("/image")
|
||||
public R<CaptchaVo> getImageCaptcha() {
|
||||
return R.ok(captchaUtils.generateImageCaptcha());
|
||||
}
|
||||
|
||||
/**
|
||||
* 实际生成图片验证码并缓存结果。
|
||||
* 获取【行为滑块验证码】生成接口
|
||||
* <p>返回滑块背景图、缺口图、验证码key</p>
|
||||
*
|
||||
* @return 验证码信息
|
||||
* @return 行为验证码vo
|
||||
*/
|
||||
@RateLimiter(time = 60, count = 10, limitType = LimitType.IP)
|
||||
public CaptchaVo getCodeImpl() {
|
||||
// 保存验证码信息
|
||||
String uuid = IdUtil.simpleUUID();
|
||||
String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
|
||||
// 生成验证码
|
||||
String captchaType = captchaProperties.getType();
|
||||
CodeGenerator codeGenerator;
|
||||
if ("math".equals(captchaType)) {
|
||||
codeGenerator = new MathGenerator(captchaProperties.getNumberLength(), false);
|
||||
} else {
|
||||
codeGenerator = new RandomGenerator(captchaProperties.getCharLength());
|
||||
}
|
||||
WaveAndCircleCaptcha captcha = new WaveAndCircleCaptcha(160, 60);
|
||||
// captcha.setBackground(Color.WHITE); // 不设置就是透明底
|
||||
captcha.setFont(new Font("Arial", Font.BOLD, 45));
|
||||
captcha.setGenerator(codeGenerator);
|
||||
captcha.createCode();
|
||||
// 如果是数学验证码,使用SpEL表达式处理验证码结果
|
||||
String code = captcha.getCode();
|
||||
if ("math".equals(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));
|
||||
return new CaptchaVo(true, uuid, captcha.getImageBase64());
|
||||
@RequestMapping("/behavior/generate")
|
||||
public ApiResponse<ImageCaptchaVO> generateBehaviorCaptcha() {
|
||||
return captchaUtils.generateBehaviorCaptchaTicket();
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片验证码响应对象。
|
||||
* 校验【行为滑块验证码】
|
||||
* POST语义更合适,提交校验参数
|
||||
*
|
||||
* @param captchaEnabled 是否启用验证码
|
||||
* @param uuid 验证码标识
|
||||
* @param img Base64 图片数据
|
||||
* @param data 验证码缓存key
|
||||
* @return 校验结果
|
||||
*/
|
||||
public record CaptchaVo(Boolean captchaEnabled, String uuid, String img) {
|
||||
@PostMapping("/behavior/verify")
|
||||
public ApiResponse<?> verifyBehaviorCaptcha(@RequestBody behaviorData data) {
|
||||
return captchaUtils.verifyBehaviorCaptchaFirstStage(data.id(), data.data());
|
||||
}
|
||||
|
||||
/**
|
||||
* 行为验证码验证接口传回BO
|
||||
*
|
||||
* @param id 验证码唯一ID,请求登录接口时携带该标识,后端根据ID读取缓存验证码做校验
|
||||
* @param data 验证码业务数据,包含Base64图片、滑块滑动轨迹信息等,详见 {@link ImageCaptchaTrack}
|
||||
*/
|
||||
public record behaviorData(String id, ImageCaptchaTrack data) {
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
Binary file not shown.
Reference in New Issue
Block a user