refactor all
This commit is contained in:
@@ -3,14 +3,17 @@ package com.imyeyu.api.util;
|
||||
import com.imyeyu.java.TimiJava;
|
||||
import com.imyeyu.java.bean.timi.TimiCode;
|
||||
import com.imyeyu.java.bean.timi.TimiException;
|
||||
import com.imyeyu.api.bean.CaptchaFrom;
|
||||
import com.imyeyu.spring.TimiSpring;
|
||||
import com.imyeyu.spring.util.Redis;
|
||||
import com.imyeyu.utils.Calc;
|
||||
import com.imyeyu.utils.Encoder;
|
||||
import com.imyeyu.utils.Time;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
@@ -19,6 +22,9 @@ import java.awt.Graphics2D;
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.QuadCurve2D;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 验证码绘制
|
||||
@@ -28,15 +34,13 @@ import java.awt.image.BufferedImage;
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class CaptchaManager {
|
||||
|
||||
/** 会话频率限制时间,毫秒 */
|
||||
private static final int LOCK_TIME = 1000;
|
||||
|
||||
/** 会话频率限制键,插值会话 ID */
|
||||
private static final String LOCK_KEY = "CAPTCHA:LOCK:%s";
|
||||
|
||||
/** 会话值缓存键,插值验证码来源 */
|
||||
/** 会话值缓存键,插值唯一 ID */
|
||||
private static final String CACHE_KEY = "CAPTCHA:%s";
|
||||
|
||||
/** 绘制字符,移除 O,o,I,l */
|
||||
@@ -49,20 +53,22 @@ public class CaptchaManager {
|
||||
@Value("${spring.profiles.active}")
|
||||
private String env;
|
||||
|
||||
private final Redis<String, String> redisCaptcha;
|
||||
|
||||
public BufferedImage generate(CaptchaFrom from, int width, int height) throws InterruptedException {
|
||||
return generate(from, 4, width, height);
|
||||
public Result generate(int width, int height) {
|
||||
return generate(width, height, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成并缓存验证码
|
||||
*
|
||||
* @param from 来自模块
|
||||
* @param width 宽度
|
||||
* @param height 高度
|
||||
* @param length 长度
|
||||
* @return 图片流
|
||||
*/
|
||||
public BufferedImage generate(CaptchaFrom from, int length, int width, int height) throws InterruptedException {
|
||||
public Result generate(int width, int height, int length) {
|
||||
long lockTime = 1000;
|
||||
// 加锁
|
||||
String lockKey = LOCK_KEY.formatted(TimiSpring.getSession().getId());
|
||||
Long lockAt = TimiSpring.getSessionAttr(lockKey, Long.class);
|
||||
@@ -70,17 +76,22 @@ public class CaptchaManager {
|
||||
TimiSpring.setSessionAttr(lockKey, Time.now());
|
||||
if (lockAt != null) {
|
||||
long diff = Time.now() - lockAt;
|
||||
if (diff < LOCK_TIME) {
|
||||
if (diff < lockTime) {
|
||||
// 限制频率
|
||||
synchronized (this) {
|
||||
wait(LOCK_TIME - diff);
|
||||
try {
|
||||
wait(lockTime - diff);
|
||||
} catch (InterruptedException e) {
|
||||
log.error("lock error", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int fontWidth = width / 4;
|
||||
int fontSize = (int) (height * .8);
|
||||
int yOffset = (int) (height * .2);
|
||||
int fontWidth = (int) (width * .9 / 4);
|
||||
int fontSize = (int) (height * .7);
|
||||
int yOffset = (int) (height * .4);
|
||||
StringBuilder value = new StringBuilder();
|
||||
|
||||
// 图片流
|
||||
@@ -139,9 +150,19 @@ public class CaptchaManager {
|
||||
g.draw(curve);
|
||||
}
|
||||
}
|
||||
// 写入缓存
|
||||
TimiSpring.setSessionAttr(CACHE_KEY.formatted(from), value.toString());
|
||||
return image;
|
||||
|
||||
try {
|
||||
String uniqueId = UUID.randomUUID().toString();
|
||||
// 写入缓存
|
||||
redisCaptcha.set(CACHE_KEY.formatted(uniqueId), value.toString(), Time.parseToMS("3m"));
|
||||
|
||||
// 返回
|
||||
ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, "png", os);
|
||||
return new Result(uniqueId, "data:image/png;base64," + Encoder.base64(os.toByteArray()));
|
||||
} catch (IOException e) {
|
||||
throw new TimiException(TimiCode.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/** @return 错误回调图像 */
|
||||
@@ -158,30 +179,40 @@ public class CaptchaManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* <p>验证。通过验证不抛出任何异常也不返回任何内容,否则抛出相应异常
|
||||
* <p>会验证非空和期限
|
||||
* <p>测试环境总是通过验证
|
||||
* 验证
|
||||
*
|
||||
* @param captcha 提交的验证码
|
||||
* @param from 来自模块
|
||||
* <ul>
|
||||
* <li>通过验证不抛出任何异常也不返回任何内容,否则抛出相应异常</li>
|
||||
* <li>会验证非空和期限</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param uniqueId 唯一识别 ID
|
||||
* @param value 提交的验证码
|
||||
* @throws TimiException 验证异常
|
||||
*/
|
||||
public void test(String captcha, String from) {
|
||||
if (TimiJava.isEmpty(captcha)) {
|
||||
throw new TimiException(TimiCode.ARG_MISS).msgKey("captcha.miss");
|
||||
public void verify(String uniqueId, String value) {
|
||||
if (TimiJava.isEmpty(value)) {
|
||||
throw new TimiException(TimiCode.ARG_MISS, "not found value");
|
||||
}
|
||||
if (env.startsWith("dev")) {
|
||||
return;
|
||||
}
|
||||
// Session 验证
|
||||
String sessionCaptcha = TimiSpring.getSessionAttrAsString(CACHE_KEY.formatted(from));
|
||||
if (TimiJava.isEmpty(sessionCaptcha)) {
|
||||
String cacheKey = CACHE_KEY.formatted(uniqueId);
|
||||
String cacheValue = redisCaptcha.get(cacheKey);
|
||||
if (TimiJava.isEmpty(cacheValue)) {
|
||||
throw new TimiException(TimiCode.ARG_EXPIRED).msgKey("captcha.expire");
|
||||
}
|
||||
if (!captcha.trim().equalsIgnoreCase(sessionCaptcha)) {
|
||||
if (!value.trim().equalsIgnoreCase(cacheValue)) {
|
||||
throw new TimiException(TimiCode.RESULT_BAD).msgKey("captcha.error");
|
||||
}
|
||||
// 清除缓存
|
||||
TimiSpring.removeSessionAttr(CACHE_KEY.formatted(from));
|
||||
redisCaptcha.destroy(cacheKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author 夜雨
|
||||
* @since 2025-10-30 14:48
|
||||
*/
|
||||
public record Result(String id, String data) {
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user