Commit 7d1946d91e1115f4d3cb79e76524eb48472b6b91
1 parent
3102fc1f
reactor:【system 系统功能】优化“文字验证码”的代码
Showing
2 changed files
with
33 additions
and
48 deletions
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 |