Ver código fonte

Update 修改验证码默认样式

Yue 1 semana atrás
pai
commit
0a231ba647

+ 15 - 12
SERVER/VberAdminPlusV3/vber-admin/src/main/java/com/vber/web/controller/CaptchaController.java

@@ -1,24 +1,25 @@
 package com.vber.web.controller;
 
 import cn.dev33.satoken.annotation.SaIgnore;
-import cn.hutool.captcha.AbstractCaptcha;
 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 com.vber.common.core.constant.Constants;
 import com.vber.common.core.constant.GlobalConstants;
 import com.vber.common.core.domain.R;
 import com.vber.common.core.exception.ServiceException;
 import com.vber.common.core.utils.SpringUtils;
 import com.vber.common.core.utils.StringUtils;
-import com.vber.common.core.utils.reflect.ReflectUtils;
 import com.vber.common.mail.config.properties.MailProperties;
 import com.vber.common.mail.utils.MailUtils;
 import com.vber.common.ratelimiter.annotation.RateLimiter;
 import com.vber.common.ratelimiter.enums.LimitType;
 import com.vber.common.redis.utils.RedisUtils;
 import com.vber.common.web.config.properties.CaptchaProperties;
-import com.vber.common.web.enums.CaptchaType;
+import com.vber.common.web.core.WaveAndCircleCaptcha;
 import com.vber.web.domain.vo.CaptchaVo;
 import jakarta.validation.constraints.NotBlank;
 import lombok.RequiredArgsConstructor;
@@ -33,6 +34,7 @@ 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;
 
@@ -99,14 +101,14 @@ public class CaptchaController {
         String code = RandomUtil.randomNumbers(4);
         RedisUtils.setCacheObject(key, code, Duration.ofMinutes(Constants.CAPTCHA_EXPIRATION));
         try {
-            MailUtils.sendText(email, "登录验证码", "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
+            MailUtils.sendText(email, "登录验证码",
+                    "您本次验证码为:" + code + ",有效性为" + Constants.CAPTCHA_EXPIRATION + "分钟,请尽快填写。");
         } catch (Exception e) {
             log.error("验证码短信发送异常 => {}", e.getMessage());
             throw new ServiceException(e.getMessage());
         }
     }
 
-
     /**
      * 生成验证码
      */
@@ -131,19 +133,21 @@ public class CaptchaController {
         String uuid = IdUtil.simpleUUID();
         String verifyKey = GlobalConstants.CAPTCHA_CODE_KEY + uuid;
         // 生成验证码
-        CaptchaType captchaType = captchaProperties.getType();
+        String captchaType = captchaProperties.getType();
         CodeGenerator codeGenerator;
-        if (CaptchaType.MATH == captchaType) {
-            codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getNumberLength(), false);
+        if ("math".equals(captchaType)) {
+            codeGenerator = new MathGenerator(captchaProperties.getNumberLength(), false);
         } else {
-            codeGenerator = ReflectUtils.newInstance(captchaType.getClazz(), captchaProperties.getCharLength());
+            codeGenerator = new RandomGenerator(captchaProperties.getCharLength());
         }
-        AbstractCaptcha captcha = SpringUtils.getBean(captchaProperties.getCategory().getClazz());
+        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 (CaptchaType.MATH == captchaType) {
+        if ("math".equals(captchaType)) {
             ExpressionParser parser = new SpelExpressionParser();
             Expression exp = parser.parseExpression(StringUtils.remove(code, "="));
             code = exp.getValue(String.class);
@@ -155,5 +159,4 @@ public class CaptchaController {
         return captchaVo;
     }
 
-
 }

+ 1 - 3
SERVER/VberAdminPlusV3/vber-admin/src/main/resources/application.yml

@@ -17,9 +17,7 @@ captcha:
   # 是否启用验证码校验
   enable: false
   # 验证码类型 math 数组计算 char 字符验证
-  type: MATH
-  # line 线段干扰 circle 圆圈干扰 shear 扭曲干扰
-  category: CIRCLE
+  type: math
   # 数字验证码位数
   numberLength: 1
   # 字符验证码长度

+ 1 - 50
SERVER/VberAdminPlusV3/vber-common/vber-common-web/src/main/java/com/vber/common/web/config/CaptchaConfig.java

@@ -1,16 +1,8 @@
 package com.vber.common.web.config;
 
-import cn.hutool.captcha.CaptchaUtil;
-import cn.hutool.captcha.CircleCaptcha;
-import cn.hutool.captcha.LineCaptcha;
-import cn.hutool.captcha.ShearCaptcha;
 import com.vber.common.web.config.properties.CaptchaProperties;
 import org.springframework.boot.autoconfigure.AutoConfiguration;
 import org.springframework.boot.context.properties.EnableConfigurationProperties;
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Lazy;
-
-import java.awt.*;
 
 /**
  * 验证码配置
@@ -21,45 +13,4 @@ import java.awt.*;
 @EnableConfigurationProperties(CaptchaProperties.class)
 public class CaptchaConfig {
 
-    private static final int WIDTH = 160;
-    private static final int HEIGHT = 60;
-    private static final Color BACKGROUND = Color.LIGHT_GRAY;
-    private static final Font FONT = new Font("Arial", Font.BOLD, 48);
-
-    /**
-     * 圆圈干扰验证码
-     */
-    @Lazy
-    @Bean
-    public CircleCaptcha circleCaptcha() {
-        CircleCaptcha captcha = CaptchaUtil.createCircleCaptcha(WIDTH, HEIGHT);
-        captcha.setBackground(BACKGROUND);
-        captcha.setFont(FONT);
-        return captcha;
-    }
-
-    /**
-     * 线段干扰的验证码
-     */
-    @Lazy
-    @Bean
-    public LineCaptcha lineCaptcha() {
-        LineCaptcha captcha = CaptchaUtil.createLineCaptcha(WIDTH, HEIGHT);
-        captcha.setBackground(BACKGROUND);
-        captcha.setFont(FONT);
-        return captcha;
-    }
-
-    /**
-     * 扭曲干扰验证码
-     */
-    @Lazy
-    @Bean
-    public ShearCaptcha shearCaptcha() {
-        ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(WIDTH, HEIGHT);
-        captcha.setBackground(BACKGROUND);
-        captcha.setFont(FONT);
-        return captcha;
-    }
-
-}
+}

+ 1 - 8
SERVER/VberAdminPlusV3/vber-common/vber-common-web/src/main/java/com/vber/common/web/config/properties/CaptchaProperties.java

@@ -1,7 +1,5 @@
 package com.vber.common.web.config.properties;
 
-import com.vber.common.web.enums.CaptchaCategory;
-import com.vber.common.web.enums.CaptchaType;
 import lombok.Data;
 import org.springframework.boot.context.properties.ConfigurationProperties;
 
@@ -19,12 +17,7 @@ public class CaptchaProperties {
     /**
      * 验证码类型
      */
-    private CaptchaType type;
-
-    /**
-     * 验证码类别
-     */
-    private CaptchaCategory category;
+    private String type;
 
     /**
      * 数字验证码位数

+ 197 - 0
SERVER/VberAdminPlusV3/vber-common/vber-common-web/src/main/java/com/vber/common/web/core/WaveAndCircleCaptcha.java

@@ -0,0 +1,197 @@
+package com.vber.common.web.core;
+
+import cn.hutool.captcha.AbstractCaptcha;
+import cn.hutool.captcha.generator.CodeGenerator;
+import cn.hutool.captcha.generator.RandomGenerator;
+import cn.hutool.core.img.GraphicsUtil;
+import cn.hutool.core.img.ImgUtil;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.RandomUtil;
+
+import java.awt.*;
+import java.awt.image.BufferedImage;
+import java.io.Serial;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * 带干扰线、波浪、圆的验证码
+ *
+ * @author Iwb
+ */
+public class WaveAndCircleCaptcha extends AbstractCaptcha {
+
+    @Serial
+    private static final long serialVersionUID = 1L;
+
+    // 构造方法(略,与之前一致)
+    public WaveAndCircleCaptcha(int width, int height) {
+        this(width, height, 4);
+    }
+
+    public WaveAndCircleCaptcha(int width, int height, int codeCount) {
+        this(width, height, codeCount, 6);
+    }
+
+    public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount) {
+        this(width, height, new RandomGenerator(codeCount), interfereCount);
+    }
+
+    public WaveAndCircleCaptcha(int width, int height, CodeGenerator generator, int interfereCount) {
+        super(width, height, generator, interfereCount);
+    }
+
+    public WaveAndCircleCaptcha(int width, int height, int codeCount, int interfereCount, float size) {
+        super(width, height, new RandomGenerator(codeCount), interfereCount, size);
+    }
+
+    @Override
+    public Image createImage(String code) {
+        final BufferedImage image = new BufferedImage(
+                width,
+                height,
+                (null == this.background) ? BufferedImage.TYPE_4BYTE_ABGR : BufferedImage.TYPE_INT_RGB);
+        final Graphics2D g = ImgUtil.createGraphics(image, this.background);
+
+        try {
+            drawString(g, code);
+            // 扭曲
+            shear(g, this.width, this.height, ObjectUtil.defaultIfNull(this.background, Color.WHITE));
+            drawInterfere(g);
+        } finally {
+            g.dispose();
+        }
+
+        return image;
+    }
+
+    private void drawString(Graphics2D g, String code) {
+        // 设置抗锯齿(让字体渲染更清晰)
+        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
+
+        if (this.textAlpha != null) {
+            g.setComposite(this.textAlpha);
+        }
+
+        GraphicsUtil.drawStringColourful(g, code, this.font, this.width, this.height);
+    }
+
+    protected void drawInterfere(Graphics2D g) {
+        ThreadLocalRandom random = RandomUtil.getRandom();
+        int circleCount = Math.max(0, this.interfereCount - 1);
+
+        // 圈圈
+        for (int i = 0; i < circleCount; i++) {
+            g.setColor(ImgUtil.randomColor(random));
+            int x = random.nextInt(width);
+            int y = random.nextInt(height);
+            int w = random.nextInt(height >> 1);
+            int h = random.nextInt(height >> 1);
+            g.drawOval(x, y, w, h);
+        }
+
+        // 仅 1 条平滑波浪线
+        if (this.interfereCount >= 1) {
+            g.setColor(getRandomColor(120, 230, random));
+            drawSmoothWave(g, random);
+        }
+    }
+
+    private void drawSmoothWave(Graphics2D g, ThreadLocalRandom random) {
+        int amplitude = random.nextInt(8) + 5; // 波动幅度
+        int wavelength = random.nextInt(40) + 30; // 波长
+        double phase = random.nextDouble() * Math.PI * 2;
+
+        // ✅ 关键:限制 baseY 在中间区域
+        int centerY = height / 2;
+        int verticalJitter = Math.max(5, height / 6); // 至少偏移5像素
+        int baseY = centerY - verticalJitter + random.nextInt(verticalJitter * 2);
+
+        g.setStroke(new BasicStroke(2.5f)); // 线宽
+
+        int[] xPoints = new int[width];
+        int[] yPoints = new int[width];
+        for (int x = 0; x < width; x++) {
+            int y = baseY + (int) (amplitude * Math.sin((double) x / wavelength * 2 * Math.PI + phase));
+            // 限制 y 不要超出图像边界(可选)
+            y = Math.max(amplitude, Math.min(y, height - amplitude));
+            xPoints[x] = x;
+            yPoints[x] = y;
+        }
+        g.drawPolyline(xPoints, yPoints, width);
+    }
+
+    private Color getRandomColor(int min, int max, ThreadLocalRandom random) {
+        int range = max - min;
+        return new Color(
+                min + random.nextInt(range),
+                min + random.nextInt(range),
+                min + random.nextInt(range));
+    }
+
+    /**
+     * 扭曲
+     *
+     * @param g     {@link Graphics}
+     * @param w1    w1
+     * @param h1    h1
+     * @param color 颜色
+     */
+    private void shear(Graphics g, int w1, int h1, Color color) {
+        shearX(g, w1, h1, color);
+        shearY(g, w1, h1, color);
+    }
+
+    /**
+     * X坐标扭曲
+     *
+     * @param g     {@link Graphics}
+     * @param w1    宽
+     * @param h1    高
+     * @param color 颜色
+     */
+    private void shearX(Graphics g, int w1, int h1, Color color) {
+
+        int period = RandomUtil.randomInt(this.width);
+
+        int frames = 1;
+        int phase = RandomUtil.randomInt(2);
+
+        for (int i = 0; i < h1; i++) {
+            double d = (double) (period >> 1)
+                    * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
+            g.copyArea(0, i, w1, 1, (int) d, 0);
+            g.setColor(color);
+            g.drawLine((int) d, i, 0, i);
+            g.drawLine((int) d + w1, i, w1, i);
+        }
+
+    }
+
+    /**
+     * Y坐标扭曲
+     *
+     * @param g     {@link Graphics}
+     * @param w1    宽
+     * @param h1    高
+     * @param color 颜色
+     */
+    private void shearY(Graphics g, int w1, int h1, Color color) {
+
+        int period = RandomUtil.randomInt(this.height >> 1);
+
+        int frames = 20;
+        int phase = 7;
+        for (int i = 0; i < w1; i++) {
+            double d = (double) (period >> 1)
+                    * Math.sin((double) i / (double) period + (6.2831853071795862D * (double) phase) / (double) frames);
+            g.copyArea(i, 0, 1, h1, 0, (int) d);
+            g.setColor(color);
+            // 擦除原位置的痕迹
+            g.drawLine(i, (int) d, i, 0);
+            g.drawLine(i, (int) d + h1, i, h1);
+        }
+
+    }
+
+}

+ 0 - 35
SERVER/VberAdminPlusV3/vber-common/vber-common-web/src/main/java/com/vber/common/web/enums/CaptchaCategory.java

@@ -1,35 +0,0 @@
-package com.vber.common.web.enums;
-
-import cn.hutool.captcha.AbstractCaptcha;
-import cn.hutool.captcha.CircleCaptcha;
-import cn.hutool.captcha.LineCaptcha;
-import cn.hutool.captcha.ShearCaptcha;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-/**
- * 验证码类别
- *
- * @author Iwb
- */
-@Getter
-@AllArgsConstructor
-public enum CaptchaCategory {
-
-    /**
-     * 线段干扰
-     */
-    LINE(LineCaptcha.class),
-
-    /**
-     * 圆圈干扰
-     */
-    CIRCLE(CircleCaptcha.class),
-
-    /**
-     * 扭曲干扰
-     */
-    SHEAR(ShearCaptcha.class);
-
-    private final Class<? extends AbstractCaptcha> clazz;
-}

+ 0 - 29
SERVER/VberAdminPlusV3/vber-common/vber-common-web/src/main/java/com/vber/common/web/enums/CaptchaType.java

@@ -1,29 +0,0 @@
-package com.vber.common.web.enums;
-
-import cn.hutool.captcha.generator.CodeGenerator;
-import cn.hutool.captcha.generator.MathGenerator;
-import cn.hutool.captcha.generator.RandomGenerator;
-import lombok.AllArgsConstructor;
-import lombok.Getter;
-
-/**
- * 验证码类型
- *
- * @author Iwb
- */
-@Getter
-@AllArgsConstructor
-public enum CaptchaType {
-
-    /**
-     * 数字
-     */
-    MATH(MathGenerator.class),
-
-    /**
-     * 字符
-     */
-    CHAR(RandomGenerator.class);
-
-    private final Class<? extends CodeGenerator> clazz;
-}