Commit 3102fc1f9c81afc80ffa6984f2924dd2aa31c005

Authored by 芋道源码
Committed by Gitee
2 parents fd48795b 413eb238

!1393 feat.新增输入字母数字类型的验证码spi

Merge pull request !1393 from DanielTsui/master-jdk17-pictureWordCaptcha
yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/core/PictureWordCaptchaServiceImpl.java 0 → 100644
  1 +package cn.iocoder.yudao.module.system.framework.captcha.core;
  2 +
  3 +import com.anji.captcha.model.common.CaptchaTypeEnum;
  4 +import com.anji.captcha.model.common.RepCodeEnum;
  5 +import com.anji.captcha.model.common.ResponseModel;
  6 +import com.anji.captcha.model.vo.CaptchaVO;
  7 +import com.anji.captcha.service.impl.AbstractCaptchaService;
  8 +import com.anji.captcha.service.impl.CaptchaServiceFactory;
  9 +import com.anji.captcha.util.AESUtil;
  10 +import com.anji.captcha.util.ImageUtils;
  11 +import com.anji.captcha.util.RandomUtils;
  12 +import org.apache.commons.lang3.StringUtils;
  13 +
  14 +import java.awt.*;
  15 +import java.awt.geom.AffineTransform;
  16 +import java.awt.image.BufferedImage;
  17 +import java.util.Properties;
  18 +import java.util.Random;
  19 +
  20 +/**
  21 + * 图片文字验证码
  22 + *
  23 + * @author Tsui
  24 + * @since 2025/7/23 20:44
  25 + */
  26 +public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService {
  27 +
  28 + private static final int WIDTH = 120;
  29 + private static final int HEIGHT = 40;
  30 + private static final int LINES = 10;
  31 + private static final Random RANDOM = new Random();
  32 + private static final String CHARACTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
  33 +
  34 + @Override
  35 + public void init(Properties config) {
  36 + super.init(config);
  37 + }
  38 +
  39 + @Override
  40 + public void destroy(Properties config) {
  41 + logger.info("start-clear-history-data-{}", captchaType());
  42 + }
  43 +
  44 + @Override
  45 + public String captchaType() {
  46 + return "pictureWord";
  47 +// return CaptchaTypeEnum.PICTURE_WORD.getCodeValue();
  48 + }
  49 +
  50 + @Override
  51 + public ResponseModel get(CaptchaVO captchaVO) {
  52 + String text = generateRandomText(4);
  53 +
  54 + CaptchaVO imageData = getImageData(text);
  55 +
  56 + // pointJson不传到前端,只做后端校验,测试时放开
  57 +// imageData.setPointJson(text);
  58 +
  59 + return ResponseModel.successData(imageData);
  60 + }
  61 +
  62 + @Override
  63 + public ResponseModel check(CaptchaVO captchaVO) {
  64 + ResponseModel r = super.check(captchaVO);
  65 + if (!validatedReq(r)) {
  66 + return r;
  67 + }
  68 +
  69 + // 取出验证码
  70 + String codeKey = String.format(REDIS_CAPTCHA_KEY, captchaVO.getToken());
  71 + if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
  72 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
  73 + }
  74 + // 正确的验证码
  75 + String codeValue = CaptchaServiceFactory.getCache(cacheType).get(codeKey);
  76 + String code = getCodeByCodeValue(codeValue);
  77 + String secretKey = getSecretKeyByCodeValue(codeValue);
  78 + //验证码只用一次,即刻失效
  79 + CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
  80 +
  81 + // 用户输入的验证码(CaptchaVO中没有预留字段,暂时用pointJson,无需加解密)
  82 + String userCode = captchaVO.getPointJson();
  83 + if (!StringUtils.equalsIgnoreCase(code, userCode)) {
  84 + afterValidateFail(captchaVO);
  85 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR);
  86 + }
  87 +
  88 + //校验成功,将信息存入缓存
  89 + String value;
  90 + try {
  91 + value = AESUtil.aesEncrypt(captchaVO.getToken().concat("---").concat(userCode), secretKey);
  92 + } catch (Exception e) {
  93 + logger.error("AES加密失败", e);
  94 + afterValidateFail(captchaVO);
  95 + return ResponseModel.errorMsg(e.getMessage());
  96 + }
  97 + String secondKey = String.format(REDIS_SECOND_CAPTCHA_KEY, value);
  98 + CaptchaServiceFactory.getCache(cacheType).set(secondKey, captchaVO.getToken(), EXPIRESIN_THREE);
  99 + captchaVO.setResult(true);
  100 + captchaVO.resetClientFlag();
  101 + return ResponseModel.successData(captchaVO);
  102 + }
  103 +
  104 + @Override
  105 + public ResponseModel verification(CaptchaVO captchaVO) {
  106 + ResponseModel r = super.verification(captchaVO);
  107 + if (!validatedReq(r)) {
  108 + return r;
  109 + }
  110 + try {
  111 + String codeKey = String.format(REDIS_SECOND_CAPTCHA_KEY, captchaVO.getCaptchaVerification());
  112 + if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
  113 + return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
  114 + }
  115 + //二次校验取值后,即刻失效
  116 + CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
  117 + } catch (Exception e) {
  118 + logger.error("验证码解析失败", e);
  119 + return ResponseModel.errorMsg(e.getMessage());
  120 + }
  121 + return ResponseModel.success();
  122 + }
  123 +
  124 +
  125 + private CaptchaVO getImageData(String text) {
  126 + CaptchaVO dataVO = new CaptchaVO();
  127 + BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
  128 + Graphics2D g = image.createGraphics();
  129 +
  130 + // 设置背景色
  131 + g.setColor(getRandomColor(200, 250));
  132 + g.fillRect(0, 0, WIDTH, HEIGHT);
  133 +
  134 + // 绘制干扰线
  135 + for (int i = 0; i < LINES; i++) {
  136 + g.setColor(getRandomColor(100, 200));
  137 + int x1 = RANDOM.nextInt(WIDTH);
  138 + int y1 = RANDOM.nextInt(HEIGHT);
  139 + int x2 = RANDOM.nextInt(WIDTH);
  140 + int y2 = RANDOM.nextInt(HEIGHT);
  141 + g.drawLine(x1, y1, x2, y2);
  142 + }
  143 +
  144 + // 设置字体
  145 + g.setFont(new Font("Arial", Font.BOLD, 24));
  146 +
  147 + // 绘制验证码文本
  148 + for (int i = 0; i < text.length(); i++) {
  149 + g.setColor(getRandomColor(20, 130));
  150 +
  151 + // 文字旋转
  152 + AffineTransform affineTransform = new AffineTransform();
  153 + int x = 20 + i * 20;
  154 + int y = 24 + RANDOM.nextInt(8);
  155 + // 旋转范围 -45 ~ 45
  156 + affineTransform.setToRotation(Math.toRadians(RandomUtils.getRandomInt(-45, 45)), x, y);
  157 + g.setTransform(affineTransform);
  158 +
  159 + g.drawString(text.charAt(i) + "", x, y);
  160 + }
  161 +
  162 + // 添加噪点
  163 + for (int i = 0; i < 100; i++) {
  164 + int x = RANDOM.nextInt(WIDTH);
  165 + int y = RANDOM.nextInt(HEIGHT);
  166 + image.setRGB(x, y, getRandomColor(0, 255).getRGB());
  167 + }
  168 +
  169 + g.dispose();
  170 +
  171 + String secretKey = null;
  172 + if (captchaAesStatus) {
  173 + secretKey = AESUtil.getKey();
  174 + }
  175 + dataVO.setSecretKey(secretKey);
  176 +
  177 + dataVO.setOriginalImageBase64(ImageUtils.getImageToBase64Str(image).replaceAll("\r|\n", ""));
  178 + dataVO.setToken(RandomUtils.getUUID());
  179 +// dataVO.setSecretKey(secretKey);
  180 + //将坐标信息存入redis中
  181 + String codeKey = String.format(REDIS_CAPTCHA_KEY, dataVO.getToken());
  182 + CaptchaServiceFactory.getCache(cacheType).set(codeKey, getCodeValue(text, secretKey), EXPIRESIN_SECONDS);
  183 + return dataVO;
  184 + }
  185 +
  186 + private String getCodeValue(String text, String secretKey) {
  187 + return text + "," + secretKey;
  188 + }
  189 +
  190 + private String getCodeByCodeValue(String codeValue) {
  191 + return codeValue.split(",")[0];
  192 + }
  193 +
  194 + private String getSecretKeyByCodeValue(String codeValue) {
  195 + return codeValue.split(",")[1];
  196 + }
  197 +
  198 + private Color getRandomColor(int min, int max) {
  199 + if (min > max) {
  200 + int temp = min;
  201 + min = max;
  202 + max = temp;
  203 + }
  204 + int r = min + RANDOM.nextInt(max - min);
  205 + int g = min + RANDOM.nextInt(max - min);
  206 + int b = min + RANDOM.nextInt(max - min);
  207 + return new Color(r, g, b);
  208 + }
  209 +
  210 + /**
  211 + * 生成指定长度的随机字符串
  212 + *
  213 + * @param length 长度
  214 + * @return {@link String}
  215 + * @author Rex
  216 + * @since 2025/6/26 15:20
  217 + */
  218 + public static String generateRandomText(int length) {
  219 + StringBuilder sb = new StringBuilder();
  220 + for (int i = 0; i < length; i++) {
  221 + int index = RANDOM.nextInt(CHARACTERS.length());
  222 + sb.append(CHARACTERS.charAt(index));
  223 + }
  224 + return sb.toString();
  225 + }
  226 +
  227 +}
0 \ No newline at end of file 228 \ No newline at end of file
yudao-module-system/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaService 0 → 100644
  1 +cn.iocoder.yudao.module.system.framework.captcha.core.PictureWordCaptchaServiceImpl
0 \ No newline at end of file 2 \ No newline at end of file