Last active
May 30, 2020 08:51
-
-
Save AdrianJiao/7c0740b605a6326f37723b6c6edb5d5b to your computer and use it in GitHub Desktop.
滑块、点选验证码
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.central.user.service.impl; | |
import cn.jmfen.base.enums.ErrorCodeEnum; | |
import com.central.user.bo.CaptchaBaseBo; | |
import com.central.user.bo.CaptchaCheckBo; | |
import com.central.user.exceptions.PassportBizException; | |
import com.central.user.utils.ImageProcessUtil; | |
import com.central.user.utils.RandomUtils; | |
import com.central.user.utils.UploadUtil; | |
import lombok.extern.slf4j.Slf4j; | |
import org.apache.commons.lang3.StringUtils; | |
import org.springframework.core.io.Resource; | |
import org.springframework.core.io.support.PathMatchingResourcePatternResolver; | |
import org.springframework.core.io.support.ResourcePatternResolver; | |
import org.springframework.util.Base64Utils; | |
import javax.imageio.ImageIO; | |
import java.awt.image.BufferedImage; | |
import java.io.ByteArrayInputStream; | |
import java.io.ByteArrayOutputStream; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.UUID; | |
import java.util.concurrent.ConcurrentHashMap; | |
/** | |
* @author jsf | |
* @description: 图行验证码抽象方法 | |
* @date 2019/10/8 19:32 | |
*/ | |
@Slf4j | |
public abstract class AbstractCaptchaService { | |
/** | |
* 阿里云文件路径前缀 | |
*/ | |
private static final String FILE_PATH_PREFIX = "avatar/captcha"; | |
/** | |
* 文件资源数组 | |
*/ | |
private static ConcurrentHashMap<String, List<BufferedImage>> imageCache = new ConcurrentHashMap<>(); | |
/** | |
* 创建行为验证码 | |
* | |
* @return | |
*/ | |
public abstract CaptchaBaseBo createCaptcha(); | |
/** | |
* 验证行为验证码 | |
* | |
* @return | |
*/ | |
public abstract boolean verification(CaptchaCheckBo checkBo); | |
/** | |
* 获取原生图片 | |
* | |
* @param imageDir 图片目录 | |
* @return | |
*/ | |
protected static BufferedImage getBufferedImage(String imageDir) { | |
if (StringUtils.isBlank(imageDir)) { | |
throw new PassportBizException(ErrorCodeEnum.GLOBAL_ERROR.getCode(), "imageDir is empty."); | |
} | |
List<BufferedImage> bufferedImages = imageCache.get(imageDir); | |
int randomNum = RandomUtils.getRandomRange(0, bufferedImages.size() - 1); | |
BufferedImage bufferedImage = bufferedImages.get(randomNum); | |
return ImageProcessUtil.copyImagePixel(bufferedImage); | |
} | |
/** | |
* 初始化图片数组 | |
* @param imageDir 图片目录 | |
*/ | |
protected static void initImageCache(String imageDir){ | |
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(); | |
try { | |
log.info("行为验证码->initImageCache"); | |
long start = System.currentTimeMillis(); | |
Resource[] resources = resolver.getResources("classpath*:" + imageDir + "/*"); | |
LinkedList<BufferedImage> bufferedImages = new LinkedList<>(); | |
for (int i = 0; i < resources.length; i++) { | |
log.info("ImageIO.read开始"); | |
long start2 = System.currentTimeMillis(); | |
BufferedImage bufferedImage = ImageIO.read(resources[i].getInputStream()); | |
long end2 = System.currentTimeMillis(); | |
log.info("ImageIO.read结束,耗时:{}",end2-start2); | |
bufferedImages.add(bufferedImage); | |
} | |
imageCache.put(imageDir,bufferedImages); | |
long end = System.currentTimeMillis(); | |
log.info("行为验证码->initImageCache,耗时:{}",end-start); | |
} catch (IOException e) { | |
log.error("imageDir error.", e); | |
throw new PassportBizException(ErrorCodeEnum.GLOBAL_ERROR.getCode(), "imageDir error."); | |
} | |
} | |
/** | |
* 图片转base64 字符串 | |
* | |
* @param templateImage 图片buffer | |
* @return | |
*/ | |
protected static String getImageToBase64Str(BufferedImage templateImage,String imageType) { | |
//输出流 | |
ByteArrayOutputStream baos = new ByteArrayOutputStream(); | |
try { | |
ImageIO.write(templateImage, imageType, baos); | |
} catch (IOException e) { | |
throw new PassportBizException(ErrorCodeEnum.GLOBAL_ERROR.getCode(), "ImageIO.write is error"); | |
} | |
byte[] bytes = baos.toByteArray(); | |
//去除base64加密后的换行和制表符 | |
return Base64Utils.encodeToString(bytes).trim().replaceAll("[\\s*\t\n\r]", ""); | |
} | |
/** | |
* 上传文件 | |
* | |
* @param templateImage 文件流 | |
* @param uploadUtil 上传工具 | |
* @return | |
*/ | |
protected static String getImageUrl(BufferedImage templateImage, UploadUtil uploadUtil, String imageType) { | |
String path = FILE_PATH_PREFIX + "/" + UUID.randomUUID().toString() + "." + imageType; | |
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); | |
try { | |
log.info("ImageIO.write开始"); | |
long start = System.currentTimeMillis(); | |
ImageIO.write(templateImage, imageType, outputStream); | |
long end = System.currentTimeMillis(); | |
log.info("ImageIO.write结束,耗时:{}",end-start); | |
} catch (IOException e) { | |
log.error("ImageIO.write error!", e); | |
throw new PassportBizException(ErrorCodeEnum.GLOBAL_ERROR.getCode(), "ImageIO.write is error"); | |
} | |
InputStream inputStream = new ByteArrayInputStream(outputStream.toByteArray()); | |
return uploadUtil.uploadSimpleImage(path, inputStream); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.central.user.service.impl; | |
import cn.jmfen.base.enums.ErrorCodeEnum; | |
import com.central.user.bo.BlockPuzzleCaptchaBo; | |
import com.central.user.bo.CaptchaBaseBo; | |
import com.central.user.bo.CaptchaCheckBo; | |
import com.central.user.bo.ImageBo; | |
import com.central.user.config.CaptchaConfig; | |
import com.central.user.exceptions.PassportBizException; | |
import com.central.user.utils.ImageProcessUtil; | |
import com.central.user.utils.RandomUtils; | |
import com.central.user.utils.UploadUtil; | |
import lombok.extern.slf4j.Slf4j; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Service; | |
import javax.annotation.PostConstruct; | |
import java.awt.*; | |
import java.awt.image.BufferedImage; | |
/** | |
* @author jsf | |
* @description: 滑块拼图验证码 | |
* @date 2019/10/8 19:37 | |
*/ | |
@Service | |
@Slf4j | |
public class BlockPuzzleCaptchaServiceImpl extends AbstractCaptchaService { | |
@Autowired | |
private CaptchaConfig config; | |
@Autowired | |
private UploadUtil uploadUtil; | |
@PostConstruct | |
public void init() { | |
//初始化图片数组 | |
initImageCache(config.getJigsawDir()); | |
initImageCache(config.getBlockImageDir()); | |
} | |
@Override | |
public CaptchaBaseBo createCaptcha() { | |
BufferedImage originalImage = getBufferedImage(config.getJigsawDir()); | |
BufferedImage blockImage = getBufferedImage(config.getBlockImageDir()); | |
return pictureTemplatesCut(originalImage, blockImage); | |
} | |
@Override | |
public boolean verification(CaptchaCheckBo checkBo) { | |
Double distance = checkBo.getDistance(); | |
Double rawDistance = checkBo.getRawDistance(); | |
if (null == distance || null == rawDistance) { | |
log.error("滑动验证码距离为空,distance:{},rawDistance:{}", distance, rawDistance); | |
return false; | |
} | |
log.info("滑块验证码检验精度: {}", config.getSlideValidatePercent()); | |
if (Math.abs(rawDistance - distance) > getBlockWidth() * config.getSlideValidatePercent()) { | |
return false; | |
} | |
return true; | |
} | |
/** | |
* 获取抠图滑块宽度 | |
* | |
* @return 抠图滑块宽度 | |
*/ | |
private int getBlockWidth() { | |
return getBufferedImage(config.getBlockImageDir()).getWidth(); | |
} | |
/** | |
* 根据模板切图 | |
*/ | |
private CaptchaBaseBo pictureTemplatesCut(BufferedImage originalImage, BufferedImage blockImage) { | |
try { | |
BlockPuzzleCaptchaBo dataBo = new BlockPuzzleCaptchaBo(); | |
int originalWidth = originalImage.getWidth(); | |
int originalHeight = originalImage.getHeight(); | |
int blockWidth = blockImage.getWidth(); | |
int blockHeight = blockImage.getHeight(); | |
//X轴距离右端targetWidth Y轴距离底部targetHeight以上 | |
int xMargin = blockWidth / 2; | |
int yMargin = blockHeight / 2; | |
int widthRandom = RandomUtils.getRandomRange(blockWidth + xMargin, originalWidth - blockWidth - xMargin); | |
int heightRandom = RandomUtils.getRandomRange(yMargin, originalHeight - blockHeight - yMargin); | |
int[][] templateData = ImageProcessUtil.getImagePixelData(blockImage); | |
originalImage = copyOriImage(originalImage); | |
blockImage = new BufferedImage(blockImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); | |
cutByTemplate(originalImage, blockImage, templateData, widthRandom, heightRandom); | |
blockImage = ImageProcessUtil.addShadow(blockImage, 2.0f, -1.3f, -1.3f, 1f, Color.BLACK.getRGB()); | |
originalImage = ImageProcessUtil.addShadow(originalImage, 2.0f, -1.3f, -1.3f, 1f, Color.BLACK.getRGB()); | |
//设置结果集 | |
ImageBo jigsawImageBo = new ImageBo(); | |
jigsawImageBo.setWidth(blockImage.getWidth()); | |
jigsawImageBo.setHeight(blockImage.getHeight()); | |
jigsawImageBo.setImage(getImageUrl(blockImage, uploadUtil, "png")); | |
ImageBo originImageBo = new ImageBo(); | |
originImageBo.setWidth(originalImage.getWidth()); | |
originImageBo.setHeight(originalImage.getHeight()); | |
originImageBo.setImage(getImageUrl(originalImage, uploadUtil, "png")); | |
dataBo.setJigsawImage(jigsawImageBo); | |
dataBo.setOriginalImage(originImageBo); | |
dataBo.setDistance((double) widthRandom); | |
return dataBo; | |
} catch (Exception e) { | |
log.error("模板切图异常!", e); | |
throw new PassportBizException(ErrorCodeEnum.GLOBAL_ERROR.getCode(), "模板切图异常!"); | |
} | |
} | |
/** | |
* 生成抠图后背景图 | |
* | |
* @param oriImage 源背景图 | |
* @param blockImage 抠图滑块 | |
* @param templateData 抠图模板 | |
* @param x 滑块横坐标 | |
* @param y 滑块纵坐标 | |
* @return 生成抠图后背景图 | |
*/ | |
private static void cutByTemplate(BufferedImage oriImage, BufferedImage blockImage, int[][] templateData, int x, int y) { | |
for (int i = 0; i < templateData.length; i++) { | |
for (int j = 0; j < templateData[0].length; j++) { | |
int rgb = templateData[i][j]; | |
if (rgb != Color.WHITE.getRGB() && rgb <= 0) { | |
// 滑块复制源图 | |
int rgbOri = oriImage.getRGB(x + i, y + j); | |
blockImage.setRGB(i, y + j, rgbOri); | |
// 源图(x+i,y+j)坐标透明处理 | |
int r = (0xff & rgbOri); | |
int g = (0xff & (rgbOri >> 8)); | |
int b = (0xff & (rgbOri >> 16)); | |
rgbOri = r + (g << 8) + (b << 16) + (15 << 24); | |
oriImage.setRGB(x + i, y + j, rgbOri); | |
} | |
} | |
} | |
} | |
/** | |
* 获取不透明源图拷贝 | |
* | |
* @param oriImage 源图 | |
* @return 不透明源图拷贝 | |
*/ | |
private static BufferedImage copyOriImage(BufferedImage oriImage) { | |
BufferedImage copyImage = new BufferedImage(oriImage.getWidth(), oriImage.getHeight(), BufferedImage.TYPE_4BYTE_ABGR); | |
for (int i = 0; i < oriImage.getWidth(); i++) { | |
for (int j = 0; j < oriImage.getHeight(); j++) { | |
int rgb = oriImage.getRGB(i, j); | |
int r = (0xff & rgb); | |
int g = (0xff & (rgb >> 8)); | |
int b = (0xff & (rgb >> 16)); | |
//源图不透明处理 | |
rgb = r + (g << 8) + (b << 16) + (255 << 24); | |
copyImage.setRGB(i, j, rgb); | |
} | |
} | |
return copyImage; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.central.user.config; | |
import lombok.Getter; | |
import lombok.Setter; | |
import org.springframework.beans.factory.annotation.Value; | |
import org.springframework.cloud.context.config.annotation.RefreshScope; | |
import org.springframework.context.annotation.Configuration; | |
/** | |
* @author jsf | |
* @description: 行为验证码配置类 | |
* @date 2019/10/14 14:11 | |
*/ | |
@Configuration | |
@RefreshScope | |
@Setter | |
@Getter | |
public class CaptchaConfig { | |
/** | |
* 是否打开行为验证码 | |
*/ | |
@Value("${captcha.switch.isOpenCaptcha}") | |
private boolean isOpenCaptcha; | |
/** | |
* 是否发送验证码 | |
*/ | |
@Value("${captcha.switch.isSendCode}") | |
private boolean isSendCode; | |
/** | |
* 是否返回验证码数据 | |
*/ | |
@Value("${captcha.switch.isTest}") | |
private boolean isTest; | |
/** | |
* 滑块图片目录 | |
*/ | |
@Value("${captcha.image.jigsawDir}") | |
private String jigsawDir; | |
/** | |
* 点选底图目录 | |
*/ | |
@Value("${captcha.image.clickImageDir}") | |
private String clickImageDir; | |
/** | |
* 抠图滑块图片目录 | |
*/ | |
@Value("${captcha.image.blockImageDir}") | |
private String blockImageDir; | |
/** | |
* 字体路径 | |
*/ | |
@Value("${captcha.font.path}") | |
private String fontPath; | |
/** | |
* 滑动验证码校验精度 | |
*/ | |
@Value("${captcha.validate.slide}") | |
private double slideValidatePercent; | |
/** | |
* 点选验证码校验精度 | |
*/ | |
@Value("${captcha.validate.click}") | |
private double clickValidatePercent; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.central.user.service.impl; | |
import com.central.user.bo.CaptchaBaseBo; | |
import com.central.user.bo.CaptchaCheckBo; | |
import com.central.user.bo.ClickWordCaptchaBo; | |
import com.central.user.bo.ImageBo; | |
import com.central.user.config.CaptchaConfig; | |
import com.central.user.utils.FontUtil; | |
import com.central.user.utils.RandomUtils; | |
import com.central.user.utils.UploadUtil; | |
import lombok.extern.slf4j.Slf4j; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.stereotype.Service; | |
import javax.annotation.PostConstruct; | |
import java.awt.*; | |
import java.awt.geom.AffineTransform; | |
import java.awt.image.BufferedImage; | |
import java.util.*; | |
import java.util.List; | |
/** | |
* @author jsf | |
* @description: 点选文字验证码 | |
* @date 2019/10/8 19:40 | |
*/ | |
@Service | |
@Slf4j | |
public class ClickWordCaptchaServiceImpl extends AbstractCaptchaService { | |
/** | |
* 点选文字 | |
*/ | |
private final static String CHINESE_WORD = "\u7684\u4e00\u4e86\u662f\u6211\u4e0d\u5728\u4eba\u4eec\u6709\u6765\u4ed6\u8fd9\u4e0a\u7740\u4e2a\u5730\u5230\u5927\u91cc\u8bf4\u5c31\u53bb\u5b50\u5f97\u4e5f\u548c\u90a3\u8981\u4e0b\u770b\u5929\u65f6\u8fc7\u51fa\u5c0f\u4e48\u8d77\u4f60\u90fd\u628a\u597d\u8fd8\u591a\u6ca1\u4e3a\u53c8\u53ef\u5bb6\u5b66\u53ea\u4ee5\u4e3b\u4f1a\u6837\u5e74\u60f3\u751f\u540c\u8001\u4e2d\u5341\u4ece\u81ea\u9762\u524d\u5934\u9053\u5b83\u540e\u7136\u8d70\u5f88\u50cf\u89c1\u4e24\u7528\u5979\u56fd\u52a8\u8fdb\u6210\u56de\u4ec0\u8fb9\u4f5c\u5bf9\u5f00\u800c\u5df1\u4e9b\u73b0\u5c71\u6c11\u5019\u7ecf\u53d1\u5de5\u5411\u4e8b\u547d\u7ed9\u957f\u6c34\u51e0\u4e49\u4e09\u58f0\u4e8e\u9ad8\u624b\u77e5\u7406\u773c\u5fd7\u70b9\u5fc3\u6218\u4e8c\u95ee\u4f46\u8eab\u65b9\u5b9e\u5403\u505a\u53eb\u5f53\u4f4f\u542c\u9769\u6253\u5462\u771f\u5168\u624d\u56db\u5df2\u6240\u654c\u4e4b\u6700\u5149\u4ea7\u60c5\u8def\u5206\u603b\u6761\u767d\u8bdd\u4e1c\u5e2d\u6b21\u4eb2\u5982\u88ab\u82b1\u53e3\u653e\u513f\u5e38\u6c14\u4e94\u7b2c\u4f7f\u5199\u519b\u5427\u6587\u8fd0\u518d\u679c\u600e\u5b9a\u8bb8\u5feb\u660e\u884c\u56e0\u522b\u98de\u5916\u6811\u7269\u6d3b\u90e8\u95e8\u65e0\u5f80\u8239\u671b\u65b0\u5e26\u961f\u5148\u529b\u5b8c\u5374\u7ad9\u4ee3\u5458\u673a\u66f4\u4e5d\u60a8\u6bcf\u98ce\u7ea7\u8ddf\u7b11\u554a\u5b69\u4e07\u5c11\u76f4\u610f\u591c\u6bd4\u9636\u8fde\u8f66\u91cd\u4fbf\u6597\u9a6c\u54ea\u5316\u592a\u6307\u53d8\u793e\u4f3c\u58eb\u8005\u5e72\u77f3\u6ee1\u65e5\u51b3\u767e\u539f\u62ff\u7fa4\u7a76\u5404\u516d\u672c\u601d\u89e3\u7acb\u6cb3\u6751\u516b\u96be\u65e9\u8bba\u5417\u6839\u5171\u8ba9\u76f8\u7814\u4eca\u5176\u4e66\u5750\u63a5\u5e94\u5173\u4fe1\u89c9\u6b65\u53cd\u5904\u8bb0\u5c06\u5343\u627e\u4e89\u9886\u6216\u5e08\u7ed3\u5757\u8dd1\u8c01\u8349\u8d8a\u5b57\u52a0\u811a\u7d27\u7231\u7b49\u4e60\u9635\u6015\u6708\u9752\u534a\u706b\u6cd5\u9898\u5efa\u8d76\u4f4d\u5531\u6d77\u4e03\u5973\u4efb\u4ef6\u611f\u51c6\u5f20\u56e2\u5c4b\u79bb\u8272\u8138\u7247\u79d1\u5012\u775b\u5229\u4e16\u521a\u4e14\u7531\u9001\u5207\u661f\u5bfc\u665a\u8868\u591f\u6574\u8ba4\u54cd\u96ea\u6d41\u672a\u573a\u8be5\u5e76\u5e95\u6df1\u523b\u5e73\u4f1f\u5fd9\u63d0\u786e\u8fd1\u4eae\u8f7b\u8bb2\u519c\u53e4\u9ed1\u544a\u754c\u62c9\u540d\u5440\u571f\u6e05\u9633\u7167\u529e\u53f2\u6539\u5386\u8f6c\u753b\u9020\u5634\u6b64\u6cbb\u5317\u5fc5\u670d\u96e8\u7a7f\u5185\u8bc6\u9a8c\u4f20\u4e1a\u83dc\u722c\u7761\u5174\u5f62\u91cf\u54b1\u89c2\u82e6\u4f53\u4f17\u901a\u51b2\u5408\u7834\u53cb\u5ea6\u672f\u996d\u516c\u65c1\u623f\u6781\u5357\u67aa\u8bfb\u6c99\u5c81\u7ebf\u91ce\u575a\u7a7a\u6536\u7b97\u81f3\u653f\u57ce\u52b3\u843d\u94b1\u7279\u56f4\u5f1f\u80dc\u6559\u70ed\u5c55\u5305\u6b4c\u7c7b\u6e10\u5f3a\u6570\u4e61\u547c\u6027\u97f3\u7b54\u54e5\u9645\u65e7\u795e\u5ea7\u7ae0\u5e2e\u5566\u53d7\u7cfb\u4ee4\u8df3\u975e\u4f55\u725b\u53d6\u5165\u5cb8\u6562\u6389\u5ffd\u79cd\u88c5\u9876\u6025\u6797\u505c\u606f\u53e5\u533a\u8863\u822c\u62a5\u53f6\u538b\u6162\u53d4\u80cc\u7ec6"; | |
/** | |
* 点选文字大小 | |
*/ | |
private final static int CHINESE_WORD_SIZE = 30; | |
/** | |
* 点选文字 字体总个数 | |
*/ | |
private final static int WORD_TOTAL_COUNT = 4; | |
/** | |
* 点选文字 字体颜色是否随机 | |
*/ | |
private final boolean FONT_COLOR_RANDOM = Boolean.TRUE; | |
/** | |
* 字体 | |
*/ | |
private Font font; | |
@Autowired | |
private CaptchaConfig config; | |
@Autowired | |
private UploadUtil uploadUtil; | |
@PostConstruct | |
public void init() { | |
log.info("点选验证码->读取字体开始"); | |
long start = System.currentTimeMillis(); | |
font = FontUtil.getFont(config.getFontPath(), Font.BOLD, CHINESE_WORD_SIZE); | |
long end = System.currentTimeMillis(); | |
log.info("点选验证码->读取字体结束,耗时:{}", end - start); | |
//初始化图片数组 | |
initImageCache(config.getClickImageDir()); | |
} | |
@Override | |
public CaptchaBaseBo createCaptcha() { | |
BufferedImage bufferedImage = getBufferedImage(config.getClickImageDir()); | |
return getImageData(bufferedImage); | |
} | |
@Override | |
public boolean verification(CaptchaCheckBo checkBo) { | |
List<Point> pointList = checkBo.getPointList(); | |
List<Point> userPointList = checkBo.getRawPointList(); | |
if (pointList == null | |
|| userPointList == null | |
|| pointList.size() == 0 | |
|| pointList.size() != userPointList.size()) { | |
log.error("点选文字验证码坐标信息为空或者不相等,pointList:{},userPointList:{}", pointList, userPointList); | |
return false; | |
} | |
log.info("点选验证码检验精度: {}" , config.getClickValidatePercent()); | |
for (int i = 0; i < pointList.size(); i++) { | |
Point p1 = pointList.get(i); | |
Point p2 = userPointList.get(i); | |
//两点之间距离 | |
double distance = Math.sqrt(Math.abs((p1.getX() - p2.getX()) * (p1.getX() - p2.getX()) + | |
(p1.getY() - p2.getY()) * (p1.getY() - p2.getY()))); | |
if (distance > CHINESE_WORD_SIZE * config.getClickValidatePercent()) { | |
return false; | |
} | |
} | |
return true; | |
} | |
private CaptchaBaseBo getImageData(BufferedImage backgroundImage) { | |
ClickWordCaptchaBo dataBo = new ClickWordCaptchaBo(); | |
java.util.List<String> wordList = new ArrayList<>(); | |
List<Point> pointList = new ArrayList<>(); | |
Graphics backgroundGraphics = backgroundImage.getGraphics(); | |
int width = backgroundImage.getWidth(); | |
int height = backgroundImage.getHeight(); | |
//定义随机1到arr.length某一个字不参与校验 | |
int num = RandomUtils.getRandomRange(1, WORD_TOTAL_COUNT); | |
List<String> currentWords = new LinkedList<>(); | |
for (int i = 0; i < WORD_TOTAL_COUNT; i++) { | |
String word; | |
do { | |
word = getRandomHan(); | |
currentWords.add(word); | |
} while (!currentWords.contains(word)); | |
//随机字体坐标 | |
Point point = randomWordPoint(width, height, i); | |
//随机字体颜色 | |
if (FONT_COLOR_RANDOM) { | |
backgroundGraphics.setColor(new Color(RandomUtils.getRandomRange(0, 255), RandomUtils.getRandomRange(0, 255), RandomUtils.getRandomRange(0, 255))); | |
} else { | |
backgroundGraphics.setColor(Color.BLACK); | |
} | |
//设置角度 | |
AffineTransform affineTransform = new AffineTransform(); | |
affineTransform.rotate(Math.toRadians(RandomUtils.getRandomRange(20, 80)), 0, 0); | |
Font rotatedFont = font.deriveFont(affineTransform); | |
backgroundGraphics.setFont(rotatedFont); | |
backgroundGraphics.drawString(word, (int) point.getX(), (int) point.getY()); | |
if ((num - 1) != i) { | |
wordList.add(word); | |
pointList.add(point); | |
} | |
} | |
//创建合并图片 | |
BufferedImage combinedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); | |
Graphics combinedGraphics = combinedImage.getGraphics(); | |
combinedGraphics.drawImage(backgroundImage, 0, 0, null); | |
ImageBo originImageBo = new ImageBo(); | |
originImageBo.setWidth(backgroundImage.getWidth()); | |
originImageBo.setHeight(backgroundImage.getHeight()); | |
originImageBo.setImage(getImageUrl(backgroundImage, uploadUtil,"jpg")); | |
dataBo.setOriginalImage(originImageBo); | |
dataBo.setPointList(pointList); | |
dataBo.setWordList(wordList); | |
return dataBo; | |
} | |
/** | |
* 随机字体循环排序下标 | |
* | |
* @param imageWidth 图片宽度 | |
* @param imageHeight 图片高度 | |
* @param wordSortIndex 字体循环排序下标(i) | |
* @return 随机坐标 | |
*/ | |
private static Point randomWordPoint(int imageWidth, int imageHeight, int wordSortIndex) { | |
int avgWidth = imageWidth / (WORD_TOTAL_COUNT + 1); | |
int halfFontSize = CHINESE_WORD_SIZE / 2; | |
int x, y; | |
if (avgWidth < CHINESE_WORD_SIZE) { | |
x = RandomUtils.getRandomRange(1, imageWidth); | |
} else { | |
x = RandomUtils.getRandomRange(halfFontSize + avgWidth * wordSortIndex, avgWidth * (wordSortIndex + 1) - halfFontSize); | |
} | |
y = RandomUtils.getRandomRange(CHINESE_WORD_SIZE, imageHeight - CHINESE_WORD_SIZE); | |
return new Point(x, y); | |
} | |
/** | |
* 获取随机中文 | |
* @return 随机汉字 | |
*/ | |
private static String getRandomHan() { | |
return CHINESE_WORD.charAt(new Random().nextInt(CHINESE_WORD.length() + 1)) + ""; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment