Commit 7d1946d91e1115f4d3cb79e76524eb48472b6b91

Authored by YunaiV
1 parent 3102fc1f

reactor:【system 系统功能】优化“文字验证码”的代码

yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/framework/captcha/core/PictureWordCaptchaServiceImpl.java
1 package cn.iocoder.yudao.module.system.framework.captcha.core; 1 package cn.iocoder.yudao.module.system.framework.captcha.core;
2 2
3 -import com.anji.captcha.model.common.CaptchaTypeEnum;  
4 import com.anji.captcha.model.common.RepCodeEnum; 3 import com.anji.captcha.model.common.RepCodeEnum;
5 import com.anji.captcha.model.common.ResponseModel; 4 import com.anji.captcha.model.common.ResponseModel;
6 import com.anji.captcha.model.vo.CaptchaVO; 5 import com.anji.captcha.model.vo.CaptchaVO;
@@ -10,12 +9,12 @@ import com.anji.captcha.util.AESUtil; @@ -10,12 +9,12 @@ import com.anji.captcha.util.AESUtil;
10 import com.anji.captcha.util.ImageUtils; 9 import com.anji.captcha.util.ImageUtils;
11 import com.anji.captcha.util.RandomUtils; 10 import com.anji.captcha.util.RandomUtils;
12 import org.apache.commons.lang3.StringUtils; 11 import org.apache.commons.lang3.StringUtils;
  12 +import cn.hutool.core.util.RandomUtil;
13 13
14 import java.awt.*; 14 import java.awt.*;
15 import java.awt.geom.AffineTransform; 15 import java.awt.geom.AffineTransform;
16 import java.awt.image.BufferedImage; 16 import java.awt.image.BufferedImage;
17 import java.util.Properties; 17 import java.util.Properties;
18 -import java.util.Random;  
19 18
20 /** 19 /**
21 * 图片文字验证码 20 * 图片文字验证码
@@ -25,11 +24,18 @@ import java.util.Random; @@ -25,11 +24,18 @@ import java.util.Random;
25 */ 24 */
26 public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { 25 public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService {
27 26
  27 + /**
  28 + * 验证码的基础字符
  29 + */
  30 + private static final String CHARACTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
  31 + /**
  32 + * 验证码长度
  33 + */
  34 + private static final Integer LENGTH = 4;
  35 +
28 private static final int WIDTH = 120; 36 private static final int WIDTH = 120;
29 private static final int HEIGHT = 40; 37 private static final int HEIGHT = 40;
30 private static final int LINES = 10; 38 private static final int LINES = 10;
31 - private static final Random RANDOM = new Random();  
32 - private static final String CHARACTERS = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";  
33 39
34 @Override 40 @Override
35 public void init(Properties config) { 41 public void init(Properties config) {
@@ -44,18 +50,14 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { @@ -44,18 +50,14 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService {
44 @Override 50 @Override
45 public String captchaType() { 51 public String captchaType() {
46 return "pictureWord"; 52 return "pictureWord";
47 -// return CaptchaTypeEnum.PICTURE_WORD.getCodeValue();  
48 } 53 }
49 54
50 @Override 55 @Override
51 public ResponseModel get(CaptchaVO captchaVO) { 56 public ResponseModel get(CaptchaVO captchaVO) {
52 - String text = generateRandomText(4);  
53 - 57 + String text = generateRandomText(LENGTH);
54 CaptchaVO imageData = getImageData(text); 58 CaptchaVO imageData = getImageData(text);
55 -  
56 - // pointJson不传到前端,只做后端校验,测试时放开 59 + // pointJson 不传到前端,只做后端校验,测试时放开
57 // imageData.setPointJson(text); 60 // imageData.setPointJson(text);
58 -  
59 return ResponseModel.successData(imageData); 61 return ResponseModel.successData(imageData);
60 } 62 }
61 63
@@ -75,22 +77,22 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { @@ -75,22 +77,22 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService {
75 String codeValue = CaptchaServiceFactory.getCache(cacheType).get(codeKey); 77 String codeValue = CaptchaServiceFactory.getCache(cacheType).get(codeKey);
76 String code = getCodeByCodeValue(codeValue); 78 String code = getCodeByCodeValue(codeValue);
77 String secretKey = getSecretKeyByCodeValue(codeValue); 79 String secretKey = getSecretKeyByCodeValue(codeValue);
78 - //验证码只用一次,即刻失效 80 + // 验证码只用一次,即刻失效
79 CaptchaServiceFactory.getCache(cacheType).delete(codeKey); 81 CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
80 82
81 - // 用户输入的验证码(CaptchaVO中没有预留字段,暂时用pointJson,无需加解密) 83 + // 用户输入的验证码(CaptchaVO 中 没有预留字段,暂时用 pointJson 无需加解密)
82 String userCode = captchaVO.getPointJson(); 84 String userCode = captchaVO.getPointJson();
83 if (!StringUtils.equalsIgnoreCase(code, userCode)) { 85 if (!StringUtils.equalsIgnoreCase(code, userCode)) {
84 afterValidateFail(captchaVO); 86 afterValidateFail(captchaVO);
85 return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR); 87 return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR);
86 } 88 }
87 89
88 - //校验成功,将信息存入缓存 90 + // 校验成功,将信息存入缓存
89 String value; 91 String value;
90 try { 92 try {
91 value = AESUtil.aesEncrypt(captchaVO.getToken().concat("---").concat(userCode), secretKey); 93 value = AESUtil.aesEncrypt(captchaVO.getToken().concat("---").concat(userCode), secretKey);
92 } catch (Exception e) { 94 } catch (Exception e) {
93 - logger.error("AES加密失败", e); 95 + logger.error("AES 加密失败", e);
94 afterValidateFail(captchaVO); 96 afterValidateFail(captchaVO);
95 return ResponseModel.errorMsg(e.getMessage()); 97 return ResponseModel.errorMsg(e.getMessage());
96 } 98 }
@@ -112,7 +114,7 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { @@ -112,7 +114,7 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService {
112 if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) { 114 if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
113 return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID); 115 return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
114 } 116 }
115 - //二次校验取值后,即刻失效 117 + // 二次校验取值后,即刻失效
116 CaptchaServiceFactory.getCache(cacheType).delete(codeKey); 118 CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
117 } catch (Exception e) { 119 } catch (Exception e) {
118 logger.error("验证码解析失败", e); 120 logger.error("验证码解析失败", e);
@@ -130,42 +132,35 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { @@ -130,42 +132,35 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService {
130 // 设置背景色 132 // 设置背景色
131 g.setColor(getRandomColor(200, 250)); 133 g.setColor(getRandomColor(200, 250));
132 g.fillRect(0, 0, WIDTH, HEIGHT); 134 g.fillRect(0, 0, WIDTH, HEIGHT);
133 -  
134 // 绘制干扰线 135 // 绘制干扰线
135 for (int i = 0; i < LINES; i++) { 136 for (int i = 0; i < LINES; i++) {
136 g.setColor(getRandomColor(100, 200)); 137 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); 138 + int x1 = RandomUtil.randomInt(WIDTH);
  139 + int y1 = RandomUtil.randomInt(HEIGHT);
  140 + int x2 = RandomUtil.randomInt(WIDTH);
  141 + int y2 = RandomUtil.randomInt(HEIGHT);
141 g.drawLine(x1, y1, x2, y2); 142 g.drawLine(x1, y1, x2, y2);
142 } 143 }
143 -  
144 // 设置字体 144 // 设置字体
145 g.setFont(new Font("Arial", Font.BOLD, 24)); 145 g.setFont(new Font("Arial", Font.BOLD, 24));
146 -  
147 // 绘制验证码文本 146 // 绘制验证码文本
148 for (int i = 0; i < text.length(); i++) { 147 for (int i = 0; i < text.length(); i++) {
149 g.setColor(getRandomColor(20, 130)); 148 g.setColor(getRandomColor(20, 130));
150 -  
151 // 文字旋转 149 // 文字旋转
152 AffineTransform affineTransform = new AffineTransform(); 150 AffineTransform affineTransform = new AffineTransform();
153 int x = 20 + i * 20; 151 int x = 20 + i * 20;
154 - int y = 24 + RANDOM.nextInt(8); 152 + int y = 24 + RandomUtil.randomInt(8);
155 // 旋转范围 -45 ~ 45 153 // 旋转范围 -45 ~ 45
156 - affineTransform.setToRotation(Math.toRadians(RandomUtils.getRandomInt(-45, 45)), x, y); 154 + affineTransform.setToRotation(Math.toRadians(RandomUtil.randomInt(-45, 45)), x, y);
157 g.setTransform(affineTransform); 155 g.setTransform(affineTransform);
158 -  
159 g.drawString(text.charAt(i) + "", x, y); 156 g.drawString(text.charAt(i) + "", x, y);
160 } 157 }
161 -  
162 // 添加噪点 158 // 添加噪点
163 for (int i = 0; i < 100; i++) { 159 for (int i = 0; i < 100; i++) {
164 - int x = RANDOM.nextInt(WIDTH);  
165 - int y = RANDOM.nextInt(HEIGHT); 160 + int x = RandomUtil.randomInt(WIDTH);
  161 + int y = RandomUtil.randomInt(HEIGHT);
166 image.setRGB(x, y, getRandomColor(0, 255).getRGB()); 162 image.setRGB(x, y, getRandomColor(0, 255).getRGB());
167 } 163 }
168 -  
169 g.dispose(); 164 g.dispose();
170 165
171 String secretKey = null; 166 String secretKey = null;
@@ -177,7 +172,7 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { @@ -177,7 +172,7 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService {
177 dataVO.setOriginalImageBase64(ImageUtils.getImageToBase64Str(image).replaceAll("\r|\n", "")); 172 dataVO.setOriginalImageBase64(ImageUtils.getImageToBase64Str(image).replaceAll("\r|\n", ""));
178 dataVO.setToken(RandomUtils.getUUID()); 173 dataVO.setToken(RandomUtils.getUUID());
179 // dataVO.setSecretKey(secretKey); 174 // dataVO.setSecretKey(secretKey);
180 - //将坐标信息存入redis中 175 + // 将坐标信息存入 redis 中
181 String codeKey = String.format(REDIS_CAPTCHA_KEY, dataVO.getToken()); 176 String codeKey = String.format(REDIS_CAPTCHA_KEY, dataVO.getToken());
182 CaptchaServiceFactory.getCache(cacheType).set(codeKey, getCodeValue(text, secretKey), EXPIRESIN_SECONDS); 177 CaptchaServiceFactory.getCache(cacheType).set(codeKey, getCodeValue(text, secretKey), EXPIRESIN_SECONDS);
183 return dataVO; 178 return dataVO;
@@ -196,14 +191,11 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { @@ -196,14 +191,11 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService {
196 } 191 }
197 192
198 private Color getRandomColor(int min, int max) { 193 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); 194 + int minVal = Math.min(min, max);
  195 + int maxVal = Math.max(min, max);
  196 + int r = RandomUtil.randomInt(minVal, maxVal);
  197 + int g = RandomUtil.randomInt(minVal, maxVal);
  198 + int b = RandomUtil.randomInt(minVal, maxVal);
207 return new Color(r, g, b); 199 return new Color(r, g, b);
208 } 200 }
209 201
@@ -212,16 +204,9 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService { @@ -212,16 +204,9 @@ public class PictureWordCaptchaServiceImpl extends AbstractCaptchaService {
212 * 204 *
213 * @param length 长度 205 * @param length 长度
214 * @return {@link String} 206 * @return {@link String}
215 - * @author Rex  
216 - * @since 2025/6/26 15:20  
217 */ 207 */
218 public static String generateRandomText(int length) { 208 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(); 209 + return RandomUtil.randomString(CHARACTERS, length);
225 } 210 }
226 211
227 } 212 }
228 \ No newline at end of file 213 \ No newline at end of file
yudao-server/src/main/resources/application.yaml
@@ -107,7 +107,7 @@ aj: @@ -107,7 +107,7 @@ aj:
107 cache-type: redis # 缓存 local/redis... 107 cache-type: redis # 缓存 local/redis...
108 cache-number: 1000 # local 缓存的阈值,达到这个值,清除缓存 108 cache-number: 1000 # local 缓存的阈值,达到这个值,清除缓存
109 timing-clear: 180 # local定时清除过期缓存(单位秒),设置为0代表不执行 109 timing-clear: 180 # local定时清除过期缓存(单位秒),设置为0代表不执行
110 - type: blockPuzzle # 验证码类型 default两种都实例化。 blockPuzzle 滑块拼图 clickWord 文字点选 110 + type: blockPuzzle # 验证码类型 default 三种都实例化。blockPuzzle 滑块拼图、clickWord 文字点选、pictureWord 文本输入
111 water-mark: 芋道源码 # 右下角水印文字(我的水印),可使用 https://tool.chinaz.com/tools/unicode.aspx 中文转 Unicode,Linux 可能需要转 unicode 111 water-mark: 芋道源码 # 右下角水印文字(我的水印),可使用 https://tool.chinaz.com/tools/unicode.aspx 中文转 Unicode,Linux 可能需要转 unicode
112 interference-options: 0 # 滑动干扰项(0/1/2) 112 interference-options: 0 # 滑动干扰项(0/1/2)
113 req-frequency-limit-enable: false # 接口请求次数一分钟限制是否开启 true|false 113 req-frequency-limit-enable: false # 接口请求次数一分钟限制是否开启 true|false