diff --git a/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java index f8defc3..20cd10f 100644 --- a/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/AbstractImageCaptchaGenerator.java @@ -57,13 +57,15 @@ public abstract class AbstractImageCaptchaGenerator implements ImageCaptchaGener if (init) { return this; } + init = true; try { + log.info("图片验证码[{}]初始化...", this.getClass().getSimpleName()); doInit(); } catch (Exception e) { + init = false; log.error("[{}]初始化失败,ex", this.getClass().getSimpleName(), e); throw e; } - init = true; return this; } diff --git a/src/main/java/cloud/tianai/captcha/generator/common/util/CaptchaImageUtils.java b/src/main/java/cloud/tianai/captcha/generator/common/util/CaptchaImageUtils.java index a9ade5c..072e5f5 100644 --- a/src/main/java/cloud/tianai/captcha/generator/common/util/CaptchaImageUtils.java +++ b/src/main/java/cloud/tianai/captcha/generator/common/util/CaptchaImageUtils.java @@ -322,7 +322,7 @@ public class CaptchaImageUtils { public static BufferedImage drawWordImg(Color fontColor, String word, Font font, - FontDesignMetrics metrics, + float fontTopCoef, int imgWidth, int imgHeight, float deg) { @@ -333,7 +333,7 @@ public class CaptchaImageUtils { g.setColor(fontColor); g.setFont(font); float left = (imgWidth - font.getSize()) / 2f; - float top = (imgHeight - font.getSize()) / 2f + metrics.getAscent() - 6; + float top = (imgHeight - font.getSize()) / 2f + font.getSize() - fontTopCoef; g.rotate(Math.toRadians(deg), imgWidth / 2f, imgHeight / 2f); g.drawString(word, left, top); g.dispose(); @@ -405,7 +405,6 @@ public class CaptchaImageUtils { * * @param data 验证码内容 * @param font 字体包 - * @param metrics FontDesignMetrics * @param width 验证码宽度 * @param height 验证码高度 * @param startX 起始X @@ -416,7 +415,6 @@ public class CaptchaImageUtils { */ public static BufferedImage genSimpleImgCaptcha(String data, Font font, - FontDesignMetrics metrics, int width, int height, float startX, diff --git a/src/main/java/cloud/tianai/captcha/generator/impl/CacheImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/impl/CacheImageCaptchaGenerator.java index 060f420..ce15468 100644 --- a/src/main/java/cloud/tianai/captcha/generator/impl/CacheImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/impl/CacheImageCaptchaGenerator.java @@ -1,6 +1,5 @@ package cloud.tianai.captcha.generator.impl; -import cloud.tianai.captcha.common.util.NamedThreadFactory; import cloud.tianai.captcha.common.util.NamedThreadFactory; import cloud.tianai.captcha.generator.ImageCaptchaGenerator; import cloud.tianai.captcha.generator.common.model.dto.GenerateParam; @@ -69,49 +68,46 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { private void init(int z) { this.size = z; // 初始化一个队列扫描 - scheduledExecutor.scheduleAtFixedRate(() -> { - queueMap.forEach((k, queue) -> { - try { - AtomicInteger pos = posMap.computeIfAbsent(k, k1 -> new AtomicInteger(0)); - int addCount = 0; - while (pos.get() < this.size) { - if (pos.get() >= size) { - return; - } - ImageCaptchaInfo slideImageInfo = target.generateCaptchaImage(k); - if (slideImageInfo != null) { - boolean addStatus = queue.offer(slideImageInfo); - addCount++; - if (addStatus) { - // 添加记录 - pos.incrementAndGet(); - } - } else { - sleep(); - } + scheduledExecutor.scheduleAtFixedRate(() -> queueMap.forEach((k, queue) -> { + try { + AtomicInteger pos = posMap.computeIfAbsent(k, k1 -> new AtomicInteger(0)); + int addCount = 0; + while (pos.get() < this.size) { + if (pos.get() >= size) { + return; } - if (addCount == 0) { - // 没有添加,检测最新更新时间 如果时间过长,直接清除数据 - Long lastUpdate = lastUpdateMap.get(k); - if (lastUpdate != null && System.currentTimeMillis() - lastUpdate > expireTime) { - queueMap.remove(k); - posMap.remove(k); - lastUpdateMap.remove(k); + ImageCaptchaInfo slideImageInfo = target.generateCaptchaImage(k); + if (slideImageInfo != null) { + boolean addStatus = queue.offer(slideImageInfo); + addCount++; + if (addStatus) { + // 添加记录 + pos.incrementAndGet(); } + } else { + sleep(); } - } catch (Exception e) { - // cache所有 - log.error("缓存队列扫描时出错, ex", e); - // 删掉它 - queueMap.remove(k); - posMap.remove(k); - lastUpdateMap.remove(k); - // 休眠 - sleep(); } - }); - - }, 0, period, TimeUnit.MILLISECONDS); + if (addCount == 0) { + // 没有添加,检测最新更新时间 如果时间过长,直接清除数据 + Long lastUpdate = lastUpdateMap.get(k); + if (lastUpdate != null && System.currentTimeMillis() - lastUpdate > expireTime) { + queueMap.remove(k); + posMap.remove(k); + lastUpdateMap.remove(k); + } + } + } catch (Exception e) { + // cache所有 + log.error("缓存队列扫描时出错, ex", e); + // 删掉它 + queueMap.remove(k); + posMap.remove(k); + lastUpdateMap.remove(k); + // 休眠 + sleep(); + } + }), 0, period, TimeUnit.MILLISECONDS); } private void sleep() { @@ -121,6 +117,11 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { } } + @Override + public ImageCaptchaGenerator init() { + return target.init(); + } + @SneakyThrows @Override public ImageCaptchaInfo generateCaptchaImage(String type) { @@ -176,20 +177,4 @@ public class CacheImageCaptchaGenerator implements ImageCaptchaGenerator { public ImageCaptchaResourceManager getImageResourceManager() { return target.getImageResourceManager(); } - - -// public static void main(String[] args) throws InterruptedException { -// SliderCaptchaTemplate captchaTemplate = new DefaultSliderCaptchaTemplate("jpeg", "png", true); -// -// captchaTemplate = new CacheSliderCaptchaTemplate(captchaTemplate, 20); -// TimeUnit.SECONDS.sleep(5); -// for (int i = 0; i < 100; i++) { -// long start = System.currentTimeMillis(); -// SliderCaptchaInfo info = captchaTemplate.getSlideImageInfo(); -// long end = System.currentTimeMillis(); -// System.out.println("耗时:" + (end - start)); -// TimeUnit.MILLISECONDS.sleep(10); -// } -// } - } diff --git a/src/main/java/cloud/tianai/captcha/generator/impl/StandardRandomWordClickImageCaptchaGenerator.java b/src/main/java/cloud/tianai/captcha/generator/impl/StandardRandomWordClickImageCaptchaGenerator.java index feaca56..48ceffc 100644 --- a/src/main/java/cloud/tianai/captcha/generator/impl/StandardRandomWordClickImageCaptchaGenerator.java +++ b/src/main/java/cloud/tianai/captcha/generator/impl/StandardRandomWordClickImageCaptchaGenerator.java @@ -13,10 +13,11 @@ import cloud.tianai.captcha.resource.impl.provider.ClassPathResourceProvider; import lombok.Getter; import lombok.Setter; import lombok.SneakyThrows; -import sun.font.FontDesignMetrics; +import javax.imageio.ImageIO; import java.awt.*; import java.awt.image.BufferedImage; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.List; @@ -36,9 +37,6 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI protected Font font; @Getter @Setter - protected FontDesignMetrics metrics; - @Getter - @Setter protected Integer clickImgWidth = 80; @Getter @Setter @@ -50,6 +48,16 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI @Setter protected int tipImageInterferencePointNum = 5; + /** + * 因为在画文字图形的时候 y 值不能准确通过 除法计算得出, 字体大小不一致中间的容错值不可估计, + * 所以通过 线性回归模型 计算出 intercept和coef 用于计算 容错值 + * 训练数据为 宋体 字体大小为 30~150 随机选择7组数据进行训练, 训练后r2结果为 0.9967106324620846 + */ + protected float intercept = 0.39583333f; + protected float coef = 0.14645833f; + + protected float currentFontTopCoef = 0.0f; + @SneakyThrows public StandardRandomWordClickImageCaptchaGenerator(ImageCaptchaResourceManager imageCaptchaResourceManager, boolean initDefaultResource) { super(imageCaptchaResourceManager, initDefaultResource); @@ -67,7 +75,8 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI Font font = Font.createFont(Font.TRUETYPE_FONT, inputStream); this.font = font.deriveFont(Font.BOLD, 70); } - this.metrics = FontDesignMetrics.getMetrics(font); + // 计算容错 + currentFontTopCoef = coef * font.getSize() + intercept; if (initDefaultResource) { initDefaultResource(); } @@ -88,20 +97,25 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI resourceStore.addResource(CaptchaTypeConstant.WORD_IMAGE_CLICK, new Resource(ClassPathResourceProvider.NAME, StandardSliderImageCaptchaGenerator.DEFAULT_SLIDER_IMAGE_RESOURCE_PATH.concat("/1.jpg"))); } + @SneakyThrows @Override public ImgWrapper genTipImage(List imageCheckDefinitions) { String tips = imageCheckDefinitions.stream().map(ClickImageCheckDefinition::getTip).collect(Collectors.joining()); // 生成随机颜色 - int fontWidth = metrics.stringWidth(tips); - int width = fontWidth + 5; - int height = metrics.getHeight() + 5; + int fontWidth = tips.length() * font.getSize(); + int width = fontWidth + 6; + int height = font.getSize() + 6; float left = (width - fontWidth) / 2f; - float top = 5 / 2f + metrics.getAscent(); + float top = 6 / 2f + font.getSize() - currentFontTopCoef; BufferedImage bufferedImage = CaptchaImageUtils.genSimpleImgCaptcha(tips, - font, metrics, width, height, left, top, tipImageInterferenceLineNum, tipImageInterferencePointNum); + font, width, height, left, top, tipImageInterferenceLineNum, tipImageInterferencePointNum); + FileOutputStream fileOutputStream = new FileOutputStream("d:/aaa/" + tips + ".png"); + ImageIO.write(bufferedImage, "png", fileOutputStream); + fileOutputStream.close(); return new ImgWrapper(bufferedImage, tips); } + @SneakyThrows @Override public ImgWrapper randomGetClickImg() { ThreadLocalRandom random = ThreadLocalRandom.current(); @@ -114,10 +128,13 @@ public class StandardRandomWordClickImageCaptchaGenerator extends AbstractClickI BufferedImage fontImage = CaptchaImageUtils.drawWordImg(randomColor, randomWord, font, - this.metrics, + currentFontTopCoef, clickImgWidth, clickImgHeight, randomDeg); + FileOutputStream fileOutputStream = new FileOutputStream("d:/aaa/" + randomWord + ".png"); + ImageIO.write(fontImage, "png", fileOutputStream); + fileOutputStream.close(); return new ImgWrapper(fontImage, randomWord); } diff --git a/src/main/test/java/example/StandardWordClickImageCaptchaGeneratorTest.java b/src/main/test/java/example/StandardWordClickImageCaptchaGeneratorTest.java index dc5bc7d..0990924 100644 --- a/src/main/test/java/example/StandardWordClickImageCaptchaGeneratorTest.java +++ b/src/main/test/java/example/StandardWordClickImageCaptchaGeneratorTest.java @@ -1,6 +1,7 @@ package example; import cloud.tianai.captcha.common.constant.CaptchaTypeConstant; +import cloud.tianai.captcha.generator.ImageCaptchaGenerator; import cloud.tianai.captcha.generator.common.model.dto.GenerateParam; import cloud.tianai.captcha.generator.common.model.dto.ImageCaptchaInfo; import cloud.tianai.captcha.generator.impl.StandardRandomWordClickImageCaptchaGenerator; @@ -15,8 +16,8 @@ public class StandardWordClickImageCaptchaGeneratorTest { public static void main(String[] args) { ImageCaptchaResourceManager imageCaptchaResourceManager = new DefaultImageCaptchaResourceManager(); - StandardRandomWordClickImageCaptchaGenerator defaultImageCaptchaResourceManager = - new StandardRandomWordClickImageCaptchaGenerator(imageCaptchaResourceManager, true); + ImageCaptchaGenerator defaultImageCaptchaResourceManager = + new StandardRandomWordClickImageCaptchaGenerator(imageCaptchaResourceManager, true).init(); GenerateParam generateParam = new GenerateParam(); generateParam.setType(CaptchaTypeConstant.WORD_IMAGE_CLICK);