+ * Created by raodeming on 2019/12/25.
+ */
+public class BlockPuzzleCaptchaServiceImpl extends AbstractCaptchaService {
+
+ @Override
+ public void init(Properties config) {
+ super.init(config);
+ }
+
+ @Override
+ public void destroy(Properties config) {
+ logger.info("start-clear-history-data-",captchaType());
+ }
+
+ @Override
+ public String captchaType() {
+ return CaptchaTypeEnum.BLOCKPUZZLE.getCodeValue();
+ }
+
+ @Override
+ public ResponseModel get(CaptchaVO captchaVO) {
+ ResponseModel r = super.get(captchaVO);
+ if(!validatedReq(r)){
+ return r;
+ }
+ //原生图片
+ BufferedImage originalImage = ImageUtils.getOriginal();
+ if (null == originalImage) {
+ logger.error("滑动底图未初始化成功,请检查路径");
+ return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_BASEMAP_NULL);
+ }
+ //设置水印
+ Graphics backgroundGraphics = originalImage.getGraphics();
+ int width = originalImage.getWidth();
+ int height = originalImage.getHeight();
+ backgroundGraphics.setFont(waterMarkFont);
+ backgroundGraphics.setColor(Color.white);
+ backgroundGraphics.drawString(waterMark, width - getEnOrChLength(waterMark), height - (HAN_ZI_SIZE / 2) + 7);
+
+ //抠图图片
+ String jigsawImageBase64 = ImageUtils.getslidingBlock();
+ BufferedImage jigsawImage = ImageUtils.getBase64StrToImage(jigsawImageBase64);
+ if (null == jigsawImage) {
+ logger.error("滑动底图未初始化成功,请检查路径");
+ return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_BASEMAP_NULL);
+ }
+ CaptchaVO captcha = pictureTemplatesCut(originalImage, jigsawImage, jigsawImageBase64);
+ if (captcha == null
+ || StringUtils.isBlank(captcha.getJigsawImageBase64())
+ || StringUtils.isBlank(captcha.getOriginalImageBase64())) {
+ return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_ERROR);
+ }
+ return ResponseModel.successData(captcha);
+ }
+
+ @Override
+ public ResponseModel check(CaptchaVO captchaVO) {
+ ResponseModel r = super.check(captchaVO);
+ if(!validatedReq(r)){
+ return r;
+ }
+ //取坐标信息
+ String codeKey = String.format(REDIS_CAPTCHA_KEY, captchaVO.getToken());
+ if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
+ return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
+ }
+ String s = CaptchaServiceFactory.getCache(cacheType).get(codeKey);
+ //验证码只用一次,即刻失效
+ CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
+ PointVO point = null;
+ PointVO point1 = null;
+ String pointJson = null;
+ try {
+ point = JsonUtil.parseObject(s, PointVO.class);
+ //aes解密
+ pointJson = decrypt(captchaVO.getPointJson(), point.getSecretKey());
+ point1 = JsonUtil.parseObject(pointJson, PointVO.class);
+ } catch (Exception e) {
+ logger.error("验证码坐标解析失败", e);
+ afterValidateFail(captchaVO);
+ return ResponseModel.errorMsg(e.getMessage());
+ }
+ if (point.x - Integer.parseInt(slipOffset) > point1.x
+ || point1.x > point.x + Integer.parseInt(slipOffset)
+ || point.y != point1.y) {
+ afterValidateFail(captchaVO);
+ return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_COORDINATE_ERROR);
+ }
+ //校验成功,将信息存入缓存
+ String secretKey = point.getSecretKey();
+ String value = null;
+ try {
+ value = AESUtil.aesEncrypt(captchaVO.getToken().concat("---").concat(pointJson), secretKey);
+ } catch (Exception e) {
+ logger.error("AES加密失败", e);
+ afterValidateFail(captchaVO);
+ return ResponseModel.errorMsg(e.getMessage());
+ }
+ String secondKey = String.format(REDIS_SECOND_CAPTCHA_KEY, value);
+ CaptchaServiceFactory.getCache(cacheType).set(secondKey, captchaVO.getToken(), EXPIRESIN_THREE);
+ captchaVO.setResult(true);
+ captchaVO.resetClientFlag();
+ return ResponseModel.successData(captchaVO);
+ }
+
+ @Override
+ public ResponseModel verification(CaptchaVO captchaVO) {
+ ResponseModel r = super.verification(captchaVO);
+ if(!validatedReq(r)){
+ return r;
+ }
+ try {
+ String codeKey = String.format(REDIS_SECOND_CAPTCHA_KEY, captchaVO.getCaptchaVerification());
+ if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
+ return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
+ }
+ //二次校验取值后,即刻失效
+ CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
+ } catch (Exception e) {
+ logger.error("验证码坐标解析失败", e);
+ return ResponseModel.errorMsg(e.getMessage());
+ }
+ return ResponseModel.success();
+ }
+
+ /**
+ * 根据模板切图
+ *
+ * @throws Exception
+ */
+ public CaptchaVO pictureTemplatesCut(BufferedImage originalImage, BufferedImage jigsawImage, String jigsawImageBase64) {
+ try {
+ CaptchaVO dataVO = new CaptchaVO();
+
+ int originalWidth = originalImage.getWidth();
+ int originalHeight = originalImage.getHeight();
+ int jigsawWidth = jigsawImage.getWidth();
+ int jigsawHeight = jigsawImage.getHeight();
+
+ //随机生成拼图坐标
+ PointVO point = generateJigsawPoint(originalWidth, originalHeight, jigsawWidth, jigsawHeight);
+ int x = point.getX();
+ int y = point.getY();
+
+ //生成新的拼图图像
+ BufferedImage newJigsawImage = new BufferedImage(jigsawWidth, jigsawHeight, jigsawImage.getType());
+ Graphics2D graphics = newJigsawImage.createGraphics();
+
+ int bold = 5;
+ //如果需要生成RGB格式,需要做如下配置,Transparency 设置透明
+ newJigsawImage = graphics.getDeviceConfiguration().createCompatibleImage(jigsawWidth, jigsawHeight, Transparency.TRANSLUCENT);
+ // 新建的图像根据模板颜色赋值,源图生成遮罩
+ cutByTemplate(originalImage, jigsawImage, newJigsawImage, x, 0);
+ if (captchaInterferenceOptions > 0) {
+ int position = 0;
+ if (originalWidth - x - 5 > jigsawWidth * 2) {
+ //在原扣图右边插入干扰图
+ position = RandomUtils.getRandomInt(x + jigsawWidth + 5, originalWidth - jigsawWidth);
+ } else {
+ //在原扣图左边插入干扰图
+ position = RandomUtils.getRandomInt(100, x - jigsawWidth - 5);
+ }
+ while (true) {
+ String s = ImageUtils.getslidingBlock();
+ if (!jigsawImageBase64.equals(s)) {
+ interferenceByTemplate(originalImage, Objects.requireNonNull(ImageUtils.getBase64StrToImage(s)), position, 0);
+ break;
+ }
+ }
+ }
+ if (captchaInterferenceOptions > 1) {
+ while (true) {
+ String s = ImageUtils.getslidingBlock();
+ if (!jigsawImageBase64.equals(s)) {
+ Integer randomInt = RandomUtils.getRandomInt(jigsawWidth, 100 - jigsawWidth);
+ interferenceByTemplate(originalImage, Objects.requireNonNull(ImageUtils.getBase64StrToImage(s)),
+ randomInt, 0);
+ break;
+ }
+ }
+ }
+
+
+ // 设置“抗锯齿”的属性
+ graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
+ graphics.setStroke(new BasicStroke(bold, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL));
+ graphics.drawImage(newJigsawImage, 0, 0, null);
+ graphics.dispose();
+
+ ByteArrayOutputStream os = new ByteArrayOutputStream();//新建流。
+ ImageIO.write(newJigsawImage, IMAGE_TYPE_PNG, os);//利用ImageIO类提供的write方法,将bi以png图片的数据模式写入流。
+ byte[] jigsawImages = os.toByteArray();
+
+ ByteArrayOutputStream oriImagesOs = new ByteArrayOutputStream();//新建流。
+ ImageIO.write(originalImage, IMAGE_TYPE_PNG, oriImagesOs);//利用ImageIO类提供的write方法,将bi以jpg图片的数据模式写入流。
+ byte[] oriCopyImages = oriImagesOs.toByteArray();
+ Base64.Encoder encoder = Base64.getEncoder();
+ dataVO.setOriginalImageBase64(encoder.encodeToString(oriCopyImages).replaceAll("\r|\n", ""));
+ //point信息不传到前端,只做后端check校验
+// dataVO.setPoint(point);
+ dataVO.setJigsawImageBase64(encoder.encodeToString(jigsawImages).replaceAll("\r|\n", ""));
+ dataVO.setToken(RandomUtils.getUUID());
+ dataVO.setSecretKey(point.getSecretKey());
+// base64StrToImage(encoder.encodeToString(oriCopyImages), "D:\\原图.png");
+// base64StrToImage(encoder.encodeToString(jigsawImages), "D:\\滑动.png");
+
+ //将坐标信息存入redis中
+ String codeKey = String.format(REDIS_CAPTCHA_KEY, dataVO.getToken());
+ CaptchaServiceFactory.getCache(cacheType).set(codeKey, JsonUtil.toJSONString(point), EXPIRESIN_SECONDS);
+ logger.debug("token:{},point:{}", dataVO.getToken(), JsonUtil.toJSONString(point));
+ return dataVO;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return null;
+ }
+ }
+
+
+ /**
+ * 随机生成拼图坐标
+ *
+ * @param originalWidth
+ * @param originalHeight
+ * @param jigsawWidth
+ * @param jigsawHeight
+ * @return
+ */
+ private static PointVO generateJigsawPoint(int originalWidth, int originalHeight, int jigsawWidth, int jigsawHeight) {
+ Random random = new Random();
+ int widthDifference = originalWidth - jigsawWidth;
+ int heightDifference = originalHeight - jigsawHeight;
+ int x, y = 0;
+ if (widthDifference <= 0) {
+ x = 5;
+ } else {
+ x = random.nextInt(originalWidth - jigsawWidth - 100) + 100;
+ }
+ if (heightDifference <= 0) {
+ y = 5;
+ } else {
+ y = random.nextInt(originalHeight - jigsawHeight) + 5;
+ }
+ String key = null;
+ if (captchaAesStatus) {
+ key = AESUtil.getKey();
+ }
+ return new PointVO(x, y, key);
+ }
+
+ /**
+ * @param oriImage 原图
+ * @param templateImage 模板图
+ * @param newImage 新抠出的小图
+ * @param x 随机扣取坐标X
+ * @param y 随机扣取坐标y
+ * @throws Exception
+ */
+ private static void cutByTemplate(BufferedImage oriImage, BufferedImage templateImage, BufferedImage newImage, int x, int y) {
+ //临时数组遍历用于高斯模糊存周边像素值
+ int[][] martrix = new int[3][3];
+ int[] values = new int[9];
+
+ int xLength = templateImage.getWidth();
+ int yLength = templateImage.getHeight();
+ // 模板图像宽度
+ for (int i = 0; i < xLength; i++) {
+ // 模板图片高度
+ for (int j = 0; j < yLength; j++) {
+ // 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中
+ int rgb = templateImage.getRGB(i, j);
+ if (rgb < 0) {
+ newImage.setRGB(i, j, oriImage.getRGB(x + i, y + j));
+
+ //抠图区域高斯模糊
+ readPixel(oriImage, x + i, y + j, values);
+ fillMatrix(martrix, values);
+ oriImage.setRGB(x + i, y + j, avgMatrix(martrix));
+ }
+
+ //防止数组越界判断
+ if (i == (xLength - 1) || j == (yLength - 1)) {
+ continue;
+ }
+ int rightRgb = templateImage.getRGB(i + 1, j);
+ int downRgb = templateImage.getRGB(i, j + 1);
+ //描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色
+ if ((rgb >= 0 && rightRgb < 0) || (rgb < 0 && rightRgb >= 0) || (rgb >= 0 && downRgb < 0) || (rgb < 0 && downRgb >= 0)) {
+ newImage.setRGB(i, j, Color.white.getRGB());
+ oriImage.setRGB(x + i, y + j, Color.white.getRGB());
+ }
+ }
+ }
+
+ }
+
+
+ /**
+ * 干扰抠图处理
+ *
+ * @param oriImage 原图
+ * @param templateImage 模板图
+ * @param x 随机扣取坐标X
+ * @param y 随机扣取坐标y
+ * @throws Exception
+ */
+ private static void interferenceByTemplate(BufferedImage oriImage, BufferedImage templateImage, int x, int y) {
+ //临时数组遍历用于高斯模糊存周边像素值
+ int[][] martrix = new int[3][3];
+ int[] values = new int[9];
+
+ int xLength = templateImage.getWidth();
+ int yLength = templateImage.getHeight();
+ // 模板图像宽度
+ for (int i = 0; i < xLength; i++) {
+ // 模板图片高度
+ for (int j = 0; j < yLength; j++) {
+ // 如果模板图像当前像素点不是透明色 copy源文件信息到目标图片中
+ int rgb = templateImage.getRGB(i, j);
+ if (rgb < 0) {
+ //抠图区域高斯模糊
+ readPixel(oriImage, x + i, y + j, values);
+ fillMatrix(martrix, values);
+ oriImage.setRGB(x + i, y + j, avgMatrix(martrix));
+ }
+ //防止数组越界判断
+ if (i == (xLength - 1) || j == (yLength - 1)) {
+ continue;
+ }
+ int rightRgb = templateImage.getRGB(i + 1, j);
+ int downRgb = templateImage.getRGB(i, j + 1);
+ //描边处理,,取带像素和无像素的界点,判断该点是不是临界轮廓点,如果是设置该坐标像素是白色
+ if ((rgb >= 0 && rightRgb < 0) || (rgb < 0 && rightRgb >= 0) || (rgb >= 0 && downRgb < 0) || (rgb < 0 && downRgb >= 0)) {
+ oriImage.setRGB(x + i, y + j, Color.white.getRGB());
+ }
+ }
+ }
+
+ }
+
+ private static void readPixel(BufferedImage img, int x, int y, int[] pixels) {
+ int xStart = x - 1;
+ int yStart = y - 1;
+ int current = 0;
+ for (int i = xStart; i < 3 + xStart; i++) {
+ for (int j = yStart; j < 3 + yStart; j++) {
+ int tx = i;
+ if (tx < 0) {
+ tx = -tx;
+
+ } else if (tx >= img.getWidth()) {
+ tx = x;
+ }
+ int ty = j;
+ if (ty < 0) {
+ ty = -ty;
+ } else if (ty >= img.getHeight()) {
+ ty = y;
+ }
+ pixels[current++] = img.getRGB(tx, ty);
+
+ }
+ }
+ }
+
+ private static void fillMatrix(int[][] matrix, int[] values) {
+ int filled = 0;
+ for (int i = 0; i < matrix.length; i++) {
+ int[] x = matrix[i];
+ for (int j = 0; j < x.length; j++) {
+ x[j] = values[filled++];
+ }
+ }
+ }
+
+ private static int avgMatrix(int[][] matrix) {
+ int r = 0;
+ int g = 0;
+ int b = 0;
+ for (int i = 0; i < matrix.length; i++) {
+ int[] x = matrix[i];
+ for (int j = 0; j < x.length; j++) {
+ if (j == 1) {
+ continue;
+ }
+ Color c = new Color(x[j]);
+ r += c.getRed();
+ g += c.getGreen();
+ b += c.getBlue();
+ }
+ }
+ return new Color(r / 8, g / 8, b / 8).getRGB();
+ }
+
+
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/impl/CaptchaCacheServiceMemImpl.java b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/impl/CaptchaCacheServiceMemImpl.java
new file mode 100644
index 0000000000..264c6c09cd
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/impl/CaptchaCacheServiceMemImpl.java
@@ -0,0 +1,47 @@
+package com.anji.captcha.service.impl;
+
+import com.anji.captcha.service.CaptchaCacheService;
+import com.anji.captcha.util.CacheUtil;
+
+/**
+ * 对于分布式部署的应用,我们建议应用自己实现CaptchaCacheService,比如用Redis,参考service/spring-boot代码示例。
+ * 如果应用是单点的,也没有使用redis,那默认使用内存。
+ * 内存缓存只适合单节点部署的应用,否则验证码生产与验证在节点之间信息不同步,导致失败。
+ * @Title: 默认使用内存当缓存
+ * @author lide1202@hotmail.com
+ * @date 2020-05-12
+ */
+public class CaptchaCacheServiceMemImpl implements CaptchaCacheService {
+ @Override
+ public void set(String key, String value, long expiresInSeconds) {
+
+ CacheUtil.set(key, value, expiresInSeconds);
+ }
+
+ @Override
+ public boolean exists(String key) {
+ return CacheUtil.exists(key);
+ }
+
+ @Override
+ public void delete(String key) {
+ CacheUtil.delete(key);
+ }
+
+ @Override
+ public String get(String key) {
+ return CacheUtil.get(key);
+ }
+
+ @Override
+ public Long increment(String key, long val) {
+ Long ret = Long.valueOf(CacheUtil.get(key))+val;
+ CacheUtil.set(key,ret+"",0);
+ return ret;
+ }
+
+ @Override
+ public String type() {
+ return "local";
+ }
+}
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/impl/CaptchaServiceFactory.java b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/impl/CaptchaServiceFactory.java
new file mode 100644
index 0000000000..6f3ff70faf
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/java/com/anji/captcha/service/impl/CaptchaServiceFactory.java
@@ -0,0 +1,60 @@
+package com.anji.captcha.service.impl;
+
+import com.anji.captcha.model.common.Const;
+import com.anji.captcha.service.CaptchaCacheService;
+import com.anji.captcha.service.CaptchaService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.ServiceLoader;
+
+/**
+ * Created by raodeming on 2020/5/26.
+ */
+public class CaptchaServiceFactory {
+
+ private static Logger logger = LoggerFactory.getLogger(CaptchaServiceFactory.class);
+
+ public static CaptchaService getInstance(Properties config) {
+ //先把所有CaptchaService初始化,通过init方法,实例字体等,add by lide1202@hotmail.com
+ /*try{
+ for(CaptchaService item: instances.values()){
+ item.init(config);
+ }
+ }catch (Exception e){
+ logger.warn("init captchaService fail:{}", e);
+ }*/
+
+ String captchaType = config.getProperty(Const.CAPTCHA_TYPE, "default");
+ CaptchaService ret = instances.get(captchaType);
+ if (ret == null) {
+ throw new RuntimeException("unsupported-[captcha.type]=" + captchaType);
+ }
+ ret.init(config);
+ return ret;
+ }
+
+ public static CaptchaCacheService getCache(String cacheType) {
+ return cacheService.get(cacheType);
+ }
+
+ public volatile static Map
+ * Created by raodeming on 2019/12/25.
+ */
+public class ClickWordCaptchaServiceImpl extends AbstractCaptchaService {
+
+ public static String HAN_ZI = "\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";
+
+ protected static String clickWordFontStr = "NotoSerif-Light.ttf";
+
+ protected Font clickWordFont;//点选文字字体
+
+ @Override
+ public String captchaType() {
+ return CaptchaTypeEnum.CLICKWORD.getCodeValue();
+ }
+
+ @Override
+ public void init(Properties config) {
+ super.init(config);
+ clickWordFontStr = config.getProperty(Const.CAPTCHA_FONT_TYPE, "SourceHanSansCN-Normal.otf");
+ try {
+ int size = Integer.valueOf(config.getProperty(Const.CAPTCHA_FONT_SIZE,HAN_ZI_SIZE+""));
+
+ if (clickWordFontStr.toLowerCase().endsWith(".ttf")
+ || clickWordFontStr.toLowerCase().endsWith(".ttc")
+ || clickWordFontStr.toLowerCase().endsWith(".otf")) {
+ this.clickWordFont = Font.createFont(Font.TRUETYPE_FONT,
+ getClass().getResourceAsStream("/fonts/" + clickWordFontStr))
+ .deriveFont(Font.BOLD, size);
+ } else {
+ int style = Integer.valueOf(config.getProperty(Const.CAPTCHA_FONT_STYLE,Font.BOLD+""));
+ this.clickWordFont = new Font(clickWordFontStr, style, size);
+ }
+ } catch (Exception ex) {
+ logger.error("load font error:{}", ex);
+ }
+ this.wordTotalCount = Integer.valueOf(config.getProperty(Const.CAPTCHA_WORD_COUNT,"4"));
+ }
+
+ @Override
+ public void destroy(Properties config) {
+ logger.info("start-clear-history-data-", captchaType());
+ }
+
+ @Override
+ public ResponseModel get(CaptchaVO captchaVO) {
+ ResponseModel r = super.get(captchaVO);
+ if (!validatedReq(r)) {
+ return r;
+ }
+ BufferedImage bufferedImage = ImageUtils.getPicClick();
+ if (null == bufferedImage) {
+ logger.error("滑动底图未初始化成功,请检查路径");
+ return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_BASEMAP_NULL);
+ }
+ CaptchaVO imageData = getImageData(bufferedImage);
+ if (imageData == null
+ || StringUtils.isBlank(imageData.getOriginalImageBase64())) {
+ return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_ERROR);
+ }
+ return ResponseModel.successData(imageData);
+ }
+
+ @Override
+ public ResponseModel check(CaptchaVO captchaVO) {
+ ResponseModel r = super.check(captchaVO);
+ if (!validatedReq(r)) {
+ return r;
+ }
+ //取坐标信息
+ String codeKey = String.format(REDIS_CAPTCHA_KEY, captchaVO.getToken());
+ if (!CaptchaServiceFactory.getCache(cacheType).exists(codeKey)) {
+ return ResponseModel.errorMsg(RepCodeEnum.API_CAPTCHA_INVALID);
+ }
+ String s = CaptchaServiceFactory.getCache(cacheType).get(codeKey);
+ //验证码只用一次,即刻失效
+ CaptchaServiceFactory.getCache(cacheType).delete(codeKey);
+ List The maximum size to which the padding constant(s) can expand. This constructor is public to permit tools that require a JavaBean
+ * instance to operate. Checks if a String is empty ("") or null. NOTE: This method changed in Lang version 2.0.
+ * It no longer trims the String.
+ * That functionality is available in isBlank(). Checks if a String is not empty ("") and not null. Checks if a String is whitespace, empty ("") or null. Checks if a String is not empty (""), not null and not whitespace only. Removes control characters (char <= 32) from both
+ * ends of this String, handling Removes control characters (char <= 32) from both
+ * ends of this String, handling The String is trimmed using {@link String#trim()}.
+ * Trim removes start and end characters <= 32.
+ * To strip whitespace use {@link #strip(String)}. To trim your choice of characters, use the
+ * {@link #strip(String, String)} methods. Removes control characters (char <= 32) from both
+ * ends of this String returning The String is trimmed using {@link String#trim()}.
+ * Trim removes start and end characters <= 32.
+ * To strip whitespace use {@link #stripToNull(String)}. Removes control characters (char <= 32) from both
+ * ends of this String returning an empty String ("") if the String
+ * is empty ("") after the trim or if it is The String is trimmed using {@link String#trim()}.
+ * Trim removes start and end characters <= 32.
+ * To strip whitespace use {@link #stripToEmpty(String)}. Strips whitespace from the start and end of a String. This is similar to {@link #trim(String)} but removes whitespace.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}. A Strips whitespace from the start and end of a String returning
+ * This is similar to {@link #trimToNull(String)} but removes whitespace.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}. Strips whitespace from the start and end of a String returning
+ * an empty String if This is similar to {@link #trimToEmpty(String)} but removes whitespace.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}. Strips any of a set of characters from the start and end of a String.
+ * This is similar to {@link String#trim()} but allows the characters
+ * to be stripped to be controlled. A If the stripChars String is Strips any of a set of characters from the start of a String. A If the stripChars String is Strips any of a set of characters from the end of a String. A If the stripChars String is Strips whitespace from the start and end of every String in an array.
+ * Whitespace is defined by {@link Character#isWhitespace(char)}. A new array is returned each time, except for length zero.
+ * A Strips any of a set of characters from the start and end of every
+ * String in an array. A new array is returned each time, except for length zero.
+ * A Compares two Strings, returning Compares two Strings, returning Finds the first index within a String, handling A Finds the first index within a String from a start position,
+ * handling A Finds the first index within a String, handling A Finds the n-th index within a String, handling A Note that 'head(String str, int n)' may be implemented as: Finds the n-th index within a String, handling A Finds the first index within a String, handling A Case in-sensitive find of the first index within a String. A Case in-sensitive find of the first index within a String
+ * from the specified position. A Finds the last index within a String, handling A Finds the last index within a String from a start position,
+ * handling A Finds the last index within a String, handling A Finds the n-th last index within a String, handling A Note that 'tail(String str, int n)' may be implemented as: Finds the first index within a String, handling A Case in-sensitive find of the last index within a String. A Case in-sensitive find of the last index within a String
+ * from the specified position. A Checks if String contains a search character, handling A Checks if String contains a search String, handling A Checks if String contains a search String irrespective of case,
+ * handling A Find the first index of any of a set of potential substrings. A Find the latest index of any of a set of potential substrings. A Gets a substring from the specified String avoiding exceptions. A negative start position can be used to start A Gets a substring from the specified String avoiding exceptions. A negative start position can be used to start/end The returned substring starts with the character in the If Gets the leftmost If Gets the rightmost If Gets If Gets the substring before the first occurrence of a separator.
+ * The separator is not returned. A If nothing is found, the string input is returned. Gets the substring after the first occurrence of a separator.
+ * The separator is not returned. A If nothing is found, the empty string is returned. Gets the substring before the last occurrence of a separator.
+ * The separator is not returned. A If nothing is found, the string input is returned. Gets the substring after the last occurrence of a separator.
+ * The separator is not returned. A If nothing is found, the empty string is returned. Gets the String that is nested in between two instances of the
+ * same String. A Gets the String that is nested in between two Strings.
+ * Only the first match is returned. A "".
+ * @since 2.0
+ */
+ public static final String EMPTY = "";
+
+ /**
+ * Represents a failed index search.
+ * @since 2.1
+ */
+ public static final int INDEX_NOT_FOUND = -1;
+
+ /**
+ * StringUtils instances should NOT be constructed in
+ * standard programming. Instead, the class should be used as
+ * StringUtils.trim(" foo ");.
+ * StringUtils.isEmpty(null) = true
+ * StringUtils.isEmpty("") = true
+ * StringUtils.isEmpty(" ") = false
+ * StringUtils.isEmpty("bob") = false
+ * StringUtils.isEmpty(" bob ") = false
+ *
+ *
+ * true if the String is empty or null
+ */
+ public static boolean isEmpty(String str) {
+ return str == null || str.length() == 0;
+ }
+
+ /**
+ *
+ * StringUtils.isNotEmpty(null) = false
+ * StringUtils.isNotEmpty("") = false
+ * StringUtils.isNotEmpty(" ") = true
+ * StringUtils.isNotEmpty("bob") = true
+ * StringUtils.isNotEmpty(" bob ") = true
+ *
+ *
+ * @param str the String to check, may be null
+ * @return true if the String is not empty and not null
+ */
+ public static boolean isNotEmpty(String str) {
+ return !StringUtils.isEmpty(str);
+ }
+
+ /**
+ *
+ * StringUtils.isBlank(null) = true
+ * StringUtils.isBlank("null") = true
+ * StringUtils.isBlank("") = true
+ * StringUtils.isBlank(" ") = true
+ * StringUtils.isBlank("bob") = false
+ * StringUtils.isBlank(" bob ") = false
+ *
+ *
+ * @param str the String to check, may be null
+ * @return true if the String is null, empty or whitespace
+ * @since 2.0
+ */
+ public static boolean isBlank(String str) {
+ int strLen;
+ if (str == null || (strLen = str.length()) == 0) {
+ return true;
+ }
+ if(equals("null", str.trim().toLowerCase())){
+ return true;
+ }
+ for (int i = 0; i < strLen; i++) {
+ if ((Character.isWhitespace(str.charAt(i)) == false)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
+ * StringUtils.isNotBlank(null) = false
+ * StringUtils.isNotBlank("null") = false
+ * StringUtils.isNotBlank("") = false
+ * StringUtils.isNotBlank(" ") = false
+ * StringUtils.isNotBlank("bob") = true
+ * StringUtils.isNotBlank(" bob ") = true
+ *
+ *
+ * @param str the String to check, may be null
+ * @return true if the String is
+ * not empty and not null and not whitespace
+ * @since 2.0
+ */
+ public static boolean isNotBlank(String str) {
+ return !StringUtils.isBlank(str);
+ }
+
+ // Trim
+ //-----------------------------------------------------------------------
+ /**
+ * null by returning
+ * an empty String ("").
+ * StringUtils.clean(null) = ""
+ * StringUtils.clean("") = ""
+ * StringUtils.clean("abc") = "abc"
+ * StringUtils.clean(" abc ") = "abc"
+ * StringUtils.clean(" ") = ""
+ *
+ *
+ * @see String#trim()
+ * @param str the String to clean, may be null
+ * @return the trimmed text, never null
+ * @deprecated Use the clearer named {@link #trimToEmpty(String)}.
+ * Method will be removed in Commons Lang 3.0.
+ */
+ public static String clean(String str) {
+ return str == null ? EMPTY : str.trim();
+ }
+
+ /**
+ * null by returning
+ * null.
+ * StringUtils.trim(null) = null
+ * StringUtils.trim("") = ""
+ * StringUtils.trim(" ") = ""
+ * StringUtils.trim("abc") = "abc"
+ * StringUtils.trim(" abc ") = "abc"
+ *
+ *
+ * @param str the String to be trimmed, may be null
+ * @return the trimmed string, null if null String input
+ */
+ public static String trim(String str) {
+ return str == null ? null : str.trim();
+ }
+
+ /**
+ * null if the String is
+ * empty ("") after the trim or if it is null.
+ *
+ *
+ * StringUtils.trimToNull(null) = null
+ * StringUtils.trimToNull("") = null
+ * StringUtils.trimToNull(" ") = null
+ * StringUtils.trimToNull("abc") = "abc"
+ * StringUtils.trimToNull(" abc ") = "abc"
+ *
+ *
+ * @param str the String to be trimmed, may be null
+ * @return the trimmed String,
+ * null if only chars <= 32, empty or null String input
+ * @since 2.0
+ */
+ public static String trimToNull(String str) {
+ String ts = trim(str);
+ return isEmpty(ts) ? null : ts;
+ }
+
+ /**
+ * null.
+ *
+ *
+ * StringUtils.trimToEmpty(null) = ""
+ * StringUtils.trimToEmpty("") = ""
+ * StringUtils.trimToEmpty(" ") = ""
+ * StringUtils.trimToEmpty("abc") = "abc"
+ * StringUtils.trimToEmpty(" abc ") = "abc"
+ *
+ *
+ * @param str the String to be trimmed, may be null
+ * @return the trimmed String, or an empty String if null input
+ * @since 2.0
+ */
+ public static String trimToEmpty(String str) {
+ return str == null ? EMPTY : str.trim();
+ }
+
+ // Stripping
+ //-----------------------------------------------------------------------
+ /**
+ * null input String returns null.
+ * StringUtils.strip(null) = null
+ * StringUtils.strip("") = ""
+ * StringUtils.strip(" ") = ""
+ * StringUtils.strip("abc") = "abc"
+ * StringUtils.strip(" abc") = "abc"
+ * StringUtils.strip("abc ") = "abc"
+ * StringUtils.strip(" abc ") = "abc"
+ * StringUtils.strip(" ab c ") = "ab c"
+ *
+ *
+ * @param str the String to remove whitespace from, may be null
+ * @return the stripped String, null if null String input
+ */
+ public static String strip(String str) {
+ return strip(str, null);
+ }
+
+ /**
+ * null if the String is empty ("") after the strip.
+ * StringUtils.stripToNull(null) = null
+ * StringUtils.stripToNull("") = null
+ * StringUtils.stripToNull(" ") = null
+ * StringUtils.stripToNull("abc") = "abc"
+ * StringUtils.stripToNull(" abc") = "abc"
+ * StringUtils.stripToNull("abc ") = "abc"
+ * StringUtils.stripToNull(" abc ") = "abc"
+ * StringUtils.stripToNull(" ab c ") = "ab c"
+ *
+ *
+ * @param str the String to be stripped, may be null
+ * @return the stripped String,
+ * null if whitespace, empty or null String input
+ * @since 2.0
+ */
+ public static String stripToNull(String str) {
+ if (str == null) {
+ return null;
+ }
+ str = strip(str, null);
+ return str.length() == 0 ? null : str;
+ }
+
+ /**
+ * null input.
+ * StringUtils.stripToEmpty(null) = ""
+ * StringUtils.stripToEmpty("") = ""
+ * StringUtils.stripToEmpty(" ") = ""
+ * StringUtils.stripToEmpty("abc") = "abc"
+ * StringUtils.stripToEmpty(" abc") = "abc"
+ * StringUtils.stripToEmpty("abc ") = "abc"
+ * StringUtils.stripToEmpty(" abc ") = "abc"
+ * StringUtils.stripToEmpty(" ab c ") = "ab c"
+ *
+ *
+ * @param str the String to be stripped, may be null
+ * @return the trimmed String, or an empty String if null input
+ * @since 2.0
+ */
+ public static String stripToEmpty(String str) {
+ return str == null ? EMPTY : strip(str, null);
+ }
+
+ /**
+ * null input String returns null.
+ * An empty string ("") input returns the empty string.null, whitespace is
+ * stripped as defined by {@link Character#isWhitespace(char)}.
+ * Alternatively use {@link #strip(String)}.
+ * StringUtils.strip(null, *) = null
+ * StringUtils.strip("", *) = ""
+ * StringUtils.strip("abc", null) = "abc"
+ * StringUtils.strip(" abc", null) = "abc"
+ * StringUtils.strip("abc ", null) = "abc"
+ * StringUtils.strip(" abc ", null) = "abc"
+ * StringUtils.strip(" abcyx", "xyz") = " abc"
+ *
+ *
+ * @param str the String to remove characters from, may be null
+ * @param stripChars the characters to remove, null treated as whitespace
+ * @return the stripped String, null if null String input
+ */
+ public static String strip(String str, String stripChars) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ str = stripStart(str, stripChars);
+ return stripEnd(str, stripChars);
+ }
+
+ /**
+ * null input String returns null.
+ * An empty string ("") input returns the empty string.null, whitespace is
+ * stripped as defined by {@link Character#isWhitespace(char)}.
+ * StringUtils.stripStart(null, *) = null
+ * StringUtils.stripStart("", *) = ""
+ * StringUtils.stripStart("abc", "") = "abc"
+ * StringUtils.stripStart("abc", null) = "abc"
+ * StringUtils.stripStart(" abc", null) = "abc"
+ * StringUtils.stripStart("abc ", null) = "abc "
+ * StringUtils.stripStart(" abc ", null) = "abc "
+ * StringUtils.stripStart("yxabc ", "xyz") = "abc "
+ *
+ *
+ * @param str the String to remove characters from, may be null
+ * @param stripChars the characters to remove, null treated as whitespace
+ * @return the stripped String, null if null String input
+ */
+ public static String stripStart(String str, String stripChars) {
+ int strLen;
+ if (str == null || (strLen = str.length()) == 0) {
+ return str;
+ }
+ int start = 0;
+ if (stripChars == null) {
+ while ((start != strLen) && Character.isWhitespace(str.charAt(start))) {
+ start++;
+ }
+ } else if (stripChars.length() == 0) {
+ return str;
+ } else {
+ while ((start != strLen) && (stripChars.indexOf(str.charAt(start)) != INDEX_NOT_FOUND)) {
+ start++;
+ }
+ }
+ return str.substring(start);
+ }
+
+ /**
+ * null input String returns null.
+ * An empty string ("") input returns the empty string.null, whitespace is
+ * stripped as defined by {@link Character#isWhitespace(char)}.
+ * StringUtils.stripEnd(null, *) = null
+ * StringUtils.stripEnd("", *) = ""
+ * StringUtils.stripEnd("abc", "") = "abc"
+ * StringUtils.stripEnd("abc", null) = "abc"
+ * StringUtils.stripEnd(" abc", null) = " abc"
+ * StringUtils.stripEnd("abc ", null) = "abc"
+ * StringUtils.stripEnd(" abc ", null) = " abc"
+ * StringUtils.stripEnd(" abcyx", "xyz") = " abc"
+ * StringUtils.stripEnd("120.00", ".0") = "12"
+ *
+ *
+ * @param str the String to remove characters from, may be null
+ * @param stripChars the set of characters to remove, null treated as whitespace
+ * @return the stripped String, null if null String input
+ */
+ public static String stripEnd(String str, String stripChars) {
+ int end;
+ if (str == null || (end = str.length()) == 0) {
+ return str;
+ }
+
+ if (stripChars == null) {
+ while ((end != 0) && Character.isWhitespace(str.charAt(end - 1))) {
+ end--;
+ }
+ } else if (stripChars.length() == 0) {
+ return str;
+ } else {
+ while ((end != 0) && (stripChars.indexOf(str.charAt(end - 1)) != INDEX_NOT_FOUND)) {
+ end--;
+ }
+ }
+ return str.substring(0, end);
+ }
+
+ // StripAll
+ //-----------------------------------------------------------------------
+ /**
+ * null array will return null.
+ * An empty array will return itself.
+ * A null array entry will be ignored.
+ * StringUtils.stripAll(null) = null
+ * StringUtils.stripAll([]) = []
+ * StringUtils.stripAll(["abc", " abc"]) = ["abc", "abc"]
+ * StringUtils.stripAll(["abc ", null]) = ["abc", null]
+ *
+ *
+ * @param strs the array to remove whitespace from, may be null
+ * @return the stripped Strings, null if null array input
+ */
+ public static String[] stripAll(String[] strs) {
+ return stripAll(strs, null);
+ }
+
+ /**
+ * null array will return null.
+ * An empty array will return itself.
+ * A null array entry will be ignored.
+ * A null stripChars will strip whitespace as defined by
+ * {@link Character#isWhitespace(char)}.
+ * StringUtils.stripAll(null, *) = null
+ * StringUtils.stripAll([], *) = []
+ * StringUtils.stripAll(["abc", " abc"], null) = ["abc", "abc"]
+ * StringUtils.stripAll(["abc ", null], null) = ["abc", null]
+ * StringUtils.stripAll(["abc ", null], "yz") = ["abc ", null]
+ * StringUtils.stripAll(["yabcz", null], "yz") = ["abc", null]
+ *
+ *
+ * @param strs the array to remove characters from, may be null
+ * @param stripChars the characters to remove, null treated as whitespace
+ * @return the stripped Strings, null if null array input
+ */
+ public static String[] stripAll(String[] strs, String stripChars) {
+ int strsLen;
+ if (strs == null || (strsLen = strs.length) == 0) {
+ return strs;
+ }
+ String[] newArr = new String[strsLen];
+ for (int i = 0; i < strsLen; i++) {
+ newArr[i] = strip(strs[i], stripChars);
+ }
+ return newArr;
+ }
+
+ // Equals
+ //-----------------------------------------------------------------------
+ /**
+ * true if they are equal.nulls are handled without exceptions. Two null
+ * references are considered to be equal. The comparison is case sensitive.
+ * StringUtils.equals(null, null) = true
+ * StringUtils.equals(null, "abc") = false
+ * StringUtils.equals("abc", null) = false
+ * StringUtils.equals("abc", "abc") = true
+ * StringUtils.equals("abc", "ABC") = false
+ *
+ *
+ * @see String#equals(Object)
+ * @param str1 the first String, may be null
+ * @param str2 the second String, may be null
+ * @return true if the Strings are equal, case sensitive, or
+ * both null
+ */
+ public static boolean equals(String str1, String str2) {
+ return str1 == null ? str2 == null : str1.equals(str2);
+ }
+
+ /**
+ * true if they are equal ignoring
+ * the case.nulls are handled without exceptions. Two null
+ * references are considered equal. Comparison is case insensitive.
+ * StringUtils.equalsIgnoreCase(null, null) = true
+ * StringUtils.equalsIgnoreCase(null, "abc") = false
+ * StringUtils.equalsIgnoreCase("abc", null) = false
+ * StringUtils.equalsIgnoreCase("abc", "abc") = true
+ * StringUtils.equalsIgnoreCase("abc", "ABC") = true
+ *
+ *
+ * @see String#equalsIgnoreCase(String)
+ * @param str1 the first String, may be null
+ * @param str2 the second String, may be null
+ * @return true if the Strings are equal, case insensitive, or
+ * both null
+ */
+ public static boolean equalsIgnoreCase(String str1, String str2) {
+ return str1 == null ? str2 == null : str1.equalsIgnoreCase(str2);
+ }
+
+ // IndexOf
+ //-----------------------------------------------------------------------
+ /**
+ * null.
+ * This method uses {@link String#indexOf(int)}.null or empty ("") String will return INDEX_NOT_FOUND (-1).
+ * StringUtils.indexOf(null, *) = -1
+ * StringUtils.indexOf("", *) = -1
+ * StringUtils.indexOf("aabaabaa", 'a') = 0
+ * StringUtils.indexOf("aabaabaa", 'b') = 2
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchChar the character to find
+ * @return the first index of the search character,
+ * -1 if no match or null string input
+ * @since 2.0
+ */
+ public static int indexOf(String str, char searchChar) {
+ if (isEmpty(str)) {
+ return INDEX_NOT_FOUND;
+ }
+ return str.indexOf(searchChar);
+ }
+
+ /**
+ * null.
+ * This method uses {@link String#indexOf(int, int)}.null or empty ("") String will return (INDEX_NOT_FOUND) -1.
+ * A negative start position is treated as zero.
+ * A start position greater than the string length returns -1.
+ * StringUtils.indexOf(null, *, *) = -1
+ * StringUtils.indexOf("", *, *) = -1
+ * StringUtils.indexOf("aabaabaa", 'b', 0) = 2
+ * StringUtils.indexOf("aabaabaa", 'b', 3) = 5
+ * StringUtils.indexOf("aabaabaa", 'b', 9) = -1
+ * StringUtils.indexOf("aabaabaa", 'b', -1) = 2
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchChar the character to find
+ * @param startPos the start position, negative treated as zero
+ * @return the first index of the search character,
+ * -1 if no match or null string input
+ * @since 2.0
+ */
+ public static int indexOf(String str, char searchChar, int startPos) {
+ if (isEmpty(str)) {
+ return INDEX_NOT_FOUND;
+ }
+ return str.indexOf(searchChar, startPos);
+ }
+
+ /**
+ * null.
+ * This method uses {@link String#indexOf(String)}.null String will return -1.
+ * StringUtils.indexOf(null, *) = -1
+ * StringUtils.indexOf(*, null) = -1
+ * StringUtils.indexOf("", "") = 0
+ * StringUtils.indexOf("", *) = -1 (except when * = "")
+ * StringUtils.indexOf("aabaabaa", "a") = 0
+ * StringUtils.indexOf("aabaabaa", "b") = 2
+ * StringUtils.indexOf("aabaabaa", "ab") = 1
+ * StringUtils.indexOf("aabaabaa", "") = 0
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStr the String to find, may be null
+ * @return the first index of the search String,
+ * -1 if no match or null string input
+ * @since 2.0
+ */
+ public static int indexOf(String str, String searchStr) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ return str.indexOf(searchStr);
+ }
+
+ /**
+ * null.
+ * This method uses {@link String#indexOf(String)}.null String will return -1.
+ * StringUtils.ordinalIndexOf(null, *, *) = -1
+ * StringUtils.ordinalIndexOf(*, null, *) = -1
+ * StringUtils.ordinalIndexOf("", "", *) = 0
+ * StringUtils.ordinalIndexOf("aabaabaa", "a", 1) = 0
+ * StringUtils.ordinalIndexOf("aabaabaa", "a", 2) = 1
+ * StringUtils.ordinalIndexOf("aabaabaa", "b", 1) = 2
+ * StringUtils.ordinalIndexOf("aabaabaa", "b", 2) = 5
+ * StringUtils.ordinalIndexOf("aabaabaa", "ab", 1) = 1
+ * StringUtils.ordinalIndexOf("aabaabaa", "ab", 2) = 4
+ * StringUtils.ordinalIndexOf("aabaabaa", "", 1) = 0
+ * StringUtils.ordinalIndexOf("aabaabaa", "", 2) = 0
+ *
+ *
+ *
+ * str.substring(0, lastOrdinalIndexOf(str, "\n", n))
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStr the String to find, may be null
+ * @param ordinal the n-th searchStr to find
+ * @return the n-th index of the search String,
+ * -1 (INDEX_NOT_FOUND) if no match or null string input
+ * @since 2.1
+ */
+ public static int ordinalIndexOf(String str, String searchStr, int ordinal) {
+ return ordinalIndexOf(str, searchStr, ordinal, false);
+ }
+
+ /**
+ * null.
+ * This method uses {@link String#indexOf(String)}.null String will return -1.searchStr to find
+ * @param lastIndex true if lastOrdinalIndexOf() otherwise false if ordinalIndexOf()
+ * @return the n-th index of the search String,
+ * -1 (INDEX_NOT_FOUND) if no match or null string input
+ */
+ // Shared code between ordinalIndexOf(String,String,int) and lastOrdinalIndexOf(String,String,int)
+ private static int ordinalIndexOf(String str, String searchStr, int ordinal, boolean lastIndex) {
+ if (str == null || searchStr == null || ordinal <= 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (searchStr.length() == 0) {
+ return lastIndex ? str.length() : 0;
+ }
+ int found = 0;
+ int index = lastIndex ? str.length() : INDEX_NOT_FOUND;
+ do {
+ if(lastIndex) {
+ index = str.lastIndexOf(searchStr, index - 1);
+ } else {
+ index = str.indexOf(searchStr, index + 1);
+ }
+ if (index < 0) {
+ return index;
+ }
+ found++;
+ } while (found < ordinal);
+ return index;
+ }
+
+ /**
+ * null.
+ * This method uses {@link String#indexOf(String, int)}.null String will return -1.
+ * A negative start position is treated as zero.
+ * An empty ("") search String always matches.
+ * A start position greater than the string length only matches
+ * an empty search String.
+ * StringUtils.indexOf(null, *, *) = -1
+ * StringUtils.indexOf(*, null, *) = -1
+ * StringUtils.indexOf("", "", 0) = 0
+ * StringUtils.indexOf("", *, 0) = -1 (except when * = "")
+ * StringUtils.indexOf("aabaabaa", "a", 0) = 0
+ * StringUtils.indexOf("aabaabaa", "b", 0) = 2
+ * StringUtils.indexOf("aabaabaa", "ab", 0) = 1
+ * StringUtils.indexOf("aabaabaa", "b", 3) = 5
+ * StringUtils.indexOf("aabaabaa", "b", 9) = -1
+ * StringUtils.indexOf("aabaabaa", "b", -1) = 2
+ * StringUtils.indexOf("aabaabaa", "", 2) = 2
+ * StringUtils.indexOf("abc", "", 9) = 3
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStr the String to find, may be null
+ * @param startPos the start position, negative treated as zero
+ * @return the first index of the search String,
+ * -1 if no match or null string input
+ * @since 2.0
+ */
+ public static int indexOf(String str, String searchStr, int startPos) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ // JDK1.2/JDK1.3 have a bug, when startPos > str.length for "", hence
+ if (searchStr.length() == 0 && startPos >= str.length()) {
+ return str.length();
+ }
+ return str.indexOf(searchStr, startPos);
+ }
+
+ /**
+ * null String will return -1.
+ * A negative start position is treated as zero.
+ * An empty ("") search String always matches.
+ * A start position greater than the string length only matches
+ * an empty search String.
+ * StringUtils.indexOfIgnoreCase(null, *) = -1
+ * StringUtils.indexOfIgnoreCase(*, null) = -1
+ * StringUtils.indexOfIgnoreCase("", "") = 0
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "a") = 0
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "b") = 2
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "ab") = 1
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStr the String to find, may be null
+ * @return the first index of the search String,
+ * -1 if no match or null string input
+ * @since 2.5
+ */
+ public static int indexOfIgnoreCase(String str, String searchStr) {
+ return indexOfIgnoreCase(str, searchStr, 0);
+ }
+
+ /**
+ * null String will return -1.
+ * A negative start position is treated as zero.
+ * An empty ("") search String always matches.
+ * A start position greater than the string length only matches
+ * an empty search String.
+ * StringUtils.indexOfIgnoreCase(null, *, *) = -1
+ * StringUtils.indexOfIgnoreCase(*, null, *) = -1
+ * StringUtils.indexOfIgnoreCase("", "", 0) = 0
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "A", 0) = 0
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 0) = 2
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "AB", 0) = 1
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 3) = 5
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "B", 9) = -1
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "B", -1) = 2
+ * StringUtils.indexOfIgnoreCase("aabaabaa", "", 2) = 2
+ * StringUtils.indexOfIgnoreCase("abc", "", 9) = 3
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStr the String to find, may be null
+ * @param startPos the start position, negative treated as zero
+ * @return the first index of the search String,
+ * -1 if no match or null string input
+ * @since 2.5
+ */
+ public static int indexOfIgnoreCase(String str, String searchStr, int startPos) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startPos < 0) {
+ startPos = 0;
+ }
+ int endLimit = (str.length() - searchStr.length()) + 1;
+ if (startPos > endLimit) {
+ return INDEX_NOT_FOUND;
+ }
+ if (searchStr.length() == 0) {
+ return startPos;
+ }
+ for (int i = startPos; i < endLimit; i++) {
+ if (str.regionMatches(true, i, searchStr, 0, searchStr.length())) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ // LastIndexOf
+ //-----------------------------------------------------------------------
+ /**
+ * null.
+ * This method uses {@link String#lastIndexOf(int)}.null or empty ("") String will return -1.
+ * StringUtils.lastIndexOf(null, *) = -1
+ * StringUtils.lastIndexOf("", *) = -1
+ * StringUtils.lastIndexOf("aabaabaa", 'a') = 7
+ * StringUtils.lastIndexOf("aabaabaa", 'b') = 5
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchChar the character to find
+ * @return the last index of the search character,
+ * -1 if no match or null string input
+ * @since 2.0
+ */
+ public static int lastIndexOf(String str, char searchChar) {
+ if (isEmpty(str)) {
+ return INDEX_NOT_FOUND;
+ }
+ return str.lastIndexOf(searchChar);
+ }
+
+ /**
+ * null.
+ * This method uses {@link String#lastIndexOf(int, int)}.null or empty ("") String will return -1.
+ * A negative start position returns -1.
+ * A start position greater than the string length searches the whole string.
+ * StringUtils.lastIndexOf(null, *, *) = -1
+ * StringUtils.lastIndexOf("", *, *) = -1
+ * StringUtils.lastIndexOf("aabaabaa", 'b', 8) = 5
+ * StringUtils.lastIndexOf("aabaabaa", 'b', 4) = 2
+ * StringUtils.lastIndexOf("aabaabaa", 'b', 0) = -1
+ * StringUtils.lastIndexOf("aabaabaa", 'b', 9) = 5
+ * StringUtils.lastIndexOf("aabaabaa", 'b', -1) = -1
+ * StringUtils.lastIndexOf("aabaabaa", 'a', 0) = 0
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchChar the character to find
+ * @param startPos the start position
+ * @return the last index of the search character,
+ * -1 if no match or null string input
+ * @since 2.0
+ */
+ public static int lastIndexOf(String str, char searchChar, int startPos) {
+ if (isEmpty(str)) {
+ return INDEX_NOT_FOUND;
+ }
+ return str.lastIndexOf(searchChar, startPos);
+ }
+
+ /**
+ * null.
+ * This method uses {@link String#lastIndexOf(String)}.null String will return -1.
+ * StringUtils.lastIndexOf(null, *) = -1
+ * StringUtils.lastIndexOf(*, null) = -1
+ * StringUtils.lastIndexOf("", "") = 0
+ * StringUtils.lastIndexOf("aabaabaa", "a") = 7
+ * StringUtils.lastIndexOf("aabaabaa", "b") = 5
+ * StringUtils.lastIndexOf("aabaabaa", "ab") = 4
+ * StringUtils.lastIndexOf("aabaabaa", "") = 8
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStr the String to find, may be null
+ * @return the last index of the search String,
+ * -1 if no match or null string input
+ * @since 2.0
+ */
+ public static int lastIndexOf(String str, String searchStr) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ return str.lastIndexOf(searchStr);
+ }
+
+ /**
+ * null.
+ * This method uses {@link String#lastIndexOf(String)}.null String will return -1.
+ * StringUtils.lastOrdinalIndexOf(null, *, *) = -1
+ * StringUtils.lastOrdinalIndexOf(*, null, *) = -1
+ * StringUtils.lastOrdinalIndexOf("", "", *) = 0
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "a", 1) = 7
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "a", 2) = 6
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "b", 1) = 5
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "b", 2) = 2
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "ab", 1) = 4
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "ab", 2) = 1
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "", 1) = 8
+ * StringUtils.lastOrdinalIndexOf("aabaabaa", "", 2) = 8
+ *
+ *
+ *
+ * str.substring(lastOrdinalIndexOf(str, "\n", n) + 1)
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStr the String to find, may be null
+ * @param ordinal the n-th last searchStr to find
+ * @return the n-th last index of the search String,
+ * -1 (INDEX_NOT_FOUND) if no match or null string input
+ * @since 2.5
+ */
+ public static int lastOrdinalIndexOf(String str, String searchStr, int ordinal) {
+ return ordinalIndexOf(str, searchStr, ordinal, true);
+ }
+
+ /**
+ * null.
+ * This method uses {@link String#lastIndexOf(String, int)}.null String will return -1.
+ * A negative start position returns -1.
+ * An empty ("") search String always matches unless the start position is negative.
+ * A start position greater than the string length searches the whole string.
+ * StringUtils.lastIndexOf(null, *, *) = -1
+ * StringUtils.lastIndexOf(*, null, *) = -1
+ * StringUtils.lastIndexOf("aabaabaa", "a", 8) = 7
+ * StringUtils.lastIndexOf("aabaabaa", "b", 8) = 5
+ * StringUtils.lastIndexOf("aabaabaa", "ab", 8) = 4
+ * StringUtils.lastIndexOf("aabaabaa", "b", 9) = 5
+ * StringUtils.lastIndexOf("aabaabaa", "b", -1) = -1
+ * StringUtils.lastIndexOf("aabaabaa", "a", 0) = 0
+ * StringUtils.lastIndexOf("aabaabaa", "b", 0) = -1
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStr the String to find, may be null
+ * @param startPos the start position, negative treated as zero
+ * @return the first index of the search String,
+ * -1 if no match or null string input
+ * @since 2.0
+ */
+ public static int lastIndexOf(String str, String searchStr, int startPos) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ return str.lastIndexOf(searchStr, startPos);
+ }
+
+ /**
+ * null String will return -1.
+ * A negative start position returns -1.
+ * An empty ("") search String always matches unless the start position is negative.
+ * A start position greater than the string length searches the whole string.
+ * StringUtils.lastIndexOfIgnoreCase(null, *) = -1
+ * StringUtils.lastIndexOfIgnoreCase(*, null) = -1
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A") = 7
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B") = 5
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB") = 4
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStr the String to find, may be null
+ * @return the first index of the search String,
+ * -1 if no match or null string input
+ * @since 2.5
+ */
+ public static int lastIndexOfIgnoreCase(String str, String searchStr) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ return lastIndexOfIgnoreCase(str, searchStr, str.length());
+ }
+
+ /**
+ * null String will return -1.
+ * A negative start position returns -1.
+ * An empty ("") search String always matches unless the start position is negative.
+ * A start position greater than the string length searches the whole string.
+ * StringUtils.lastIndexOfIgnoreCase(null, *, *) = -1
+ * StringUtils.lastIndexOfIgnoreCase(*, null, *) = -1
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 8) = 7
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 8) = 5
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "AB", 8) = 4
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 9) = 5
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", -1) = -1
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "A", 0) = 0
+ * StringUtils.lastIndexOfIgnoreCase("aabaabaa", "B", 0) = -1
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStr the String to find, may be null
+ * @param startPos the start position
+ * @return the first index of the search String,
+ * -1 if no match or null string input
+ * @since 2.5
+ */
+ public static int lastIndexOfIgnoreCase(String str, String searchStr, int startPos) {
+ if (str == null || searchStr == null) {
+ return INDEX_NOT_FOUND;
+ }
+ if (startPos > (str.length() - searchStr.length())) {
+ startPos = str.length() - searchStr.length();
+ }
+ if (startPos < 0) {
+ return INDEX_NOT_FOUND;
+ }
+ if (searchStr.length() == 0) {
+ return startPos;
+ }
+
+ for (int i = startPos; i >= 0; i--) {
+ if (str.regionMatches(true, i, searchStr, 0, searchStr.length())) {
+ return i;
+ }
+ }
+ return INDEX_NOT_FOUND;
+ }
+
+ // Contains
+ //-----------------------------------------------------------------------
+ /**
+ * null.
+ * This method uses {@link String#indexOf(int)}.null or empty ("") String will return false.
+ * StringUtils.contains(null, *) = false
+ * StringUtils.contains("", *) = false
+ * StringUtils.contains("abc", 'a') = true
+ * StringUtils.contains("abc", 'z') = false
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchChar the character to find
+ * @return true if the String contains the search character,
+ * false if not or null string input
+ * @since 2.0
+ */
+ public static boolean contains(String str, char searchChar) {
+ if (isEmpty(str)) {
+ return false;
+ }
+ return str.indexOf(searchChar) >= 0;
+ }
+
+ /**
+ * null.
+ * This method uses {@link String#indexOf(String)}.null String will return false.
+ * StringUtils.contains(null, *) = false
+ * StringUtils.contains(*, null) = false
+ * StringUtils.contains("", "") = true
+ * StringUtils.contains("abc", "") = true
+ * StringUtils.contains("abc", "a") = true
+ * StringUtils.contains("abc", "z") = false
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStr the String to find, may be null
+ * @return true if the String contains the search String,
+ * false if not or null string input
+ * @since 2.0
+ */
+ public static boolean contains(String str, String searchStr) {
+ if (str == null || searchStr == null) {
+ return false;
+ }
+ return str.indexOf(searchStr) >= 0;
+ }
+
+ /**
+ * null. Case-insensitivity is defined as by
+ * {@link String#equalsIgnoreCase(String)}.
+ *
+ * null String will return false.
+ * StringUtils.contains(null, *) = false
+ * StringUtils.contains(*, null) = false
+ * StringUtils.contains("", "") = true
+ * StringUtils.contains("abc", "") = true
+ * StringUtils.contains("abc", "a") = true
+ * StringUtils.contains("abc", "z") = false
+ * StringUtils.contains("abc", "A") = true
+ * StringUtils.contains("abc", "Z") = false
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStr the String to find, may be null
+ * @return true if the String contains the search String irrespective of
+ * case or false if not or null string input
+ */
+ public static boolean containsIgnoreCase(String str, String searchStr) {
+ if (str == null || searchStr == null) {
+ return false;
+ }
+ int len = searchStr.length();
+ int max = str.length() - len;
+ for (int i = 0; i <= max; i++) {
+ if (str.regionMatches(true, i, searchStr, 0, len)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+
+ // IndexOfAny strings
+ //-----------------------------------------------------------------------
+ /**
+ * null String will return -1.
+ * A null or zero length search array will return -1.
+ * A null search array entry will be ignored, but a search
+ * array containing "" will return 0 if str is not
+ * null. This method uses {@link String#indexOf(String)}.
+ * StringUtils.indexOfAny(null, *) = -1
+ * StringUtils.indexOfAny(*, null) = -1
+ * StringUtils.indexOfAny(*, []) = -1
+ * StringUtils.indexOfAny("zzabyycdxx", ["ab","cd"]) = 2
+ * StringUtils.indexOfAny("zzabyycdxx", ["cd","ab"]) = 2
+ * StringUtils.indexOfAny("zzabyycdxx", ["mn","op"]) = -1
+ * StringUtils.indexOfAny("zzabyycdxx", ["zab","aby"]) = 1
+ * StringUtils.indexOfAny("zzabyycdxx", [""]) = 0
+ * StringUtils.indexOfAny("", [""]) = 0
+ * StringUtils.indexOfAny("", ["a"]) = -1
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStrs the Strings to search for, may be null
+ * @return the first index of any of the searchStrs in str, -1 if no match
+ */
+ public static int indexOfAny(String str, String[] searchStrs) {
+ if ((str == null) || (searchStrs == null)) {
+ return INDEX_NOT_FOUND;
+ }
+ int sz = searchStrs.length;
+
+ // String's can't have a MAX_VALUEth index.
+ int ret = Integer.MAX_VALUE;
+
+ int tmp = 0;
+ for (int i = 0; i < sz; i++) {
+ String search = searchStrs[i];
+ if (search == null) {
+ continue;
+ }
+ tmp = str.indexOf(search);
+ if (tmp == INDEX_NOT_FOUND) {
+ continue;
+ }
+
+ if (tmp < ret) {
+ ret = tmp;
+ }
+ }
+
+ return (ret == Integer.MAX_VALUE) ? INDEX_NOT_FOUND : ret;
+ }
+
+ /**
+ * null String will return -1.
+ * A null search array will return -1.
+ * A null or zero length search array entry will be ignored,
+ * but a search array containing "" will return the length of str
+ * if str is not null. This method uses {@link String#indexOf(String)}
+ * StringUtils.lastIndexOfAny(null, *) = -1
+ * StringUtils.lastIndexOfAny(*, null) = -1
+ * StringUtils.lastIndexOfAny(*, []) = -1
+ * StringUtils.lastIndexOfAny(*, [null]) = -1
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["ab","cd"]) = 6
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["cd","ab"]) = 6
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn","op"]) = -1
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn","op"]) = -1
+ * StringUtils.lastIndexOfAny("zzabyycdxx", ["mn",""]) = 10
+ *
+ *
+ * @param str the String to check, may be null
+ * @param searchStrs the Strings to search for, may be null
+ * @return the last index of any of the Strings, -1 if no match
+ */
+ public static int lastIndexOfAny(String str, String[] searchStrs) {
+ if ((str == null) || (searchStrs == null)) {
+ return INDEX_NOT_FOUND;
+ }
+ int sz = searchStrs.length;
+ int ret = INDEX_NOT_FOUND;
+ int tmp = 0;
+ for (int i = 0; i < sz; i++) {
+ String search = searchStrs[i];
+ if (search == null) {
+ continue;
+ }
+ tmp = str.lastIndexOf(search);
+ if (tmp > ret) {
+ ret = tmp;
+ }
+ }
+ return ret;
+ }
+
+ // Substring
+ //-----------------------------------------------------------------------
+ /**
+ * n
+ * characters from the end of the String.null String will return null.
+ * An empty ("") String will return "".
+ * StringUtils.substring(null, *) = null
+ * StringUtils.substring("", *) = ""
+ * StringUtils.substring("abc", 0) = "abc"
+ * StringUtils.substring("abc", 2) = "c"
+ * StringUtils.substring("abc", 4) = ""
+ * StringUtils.substring("abc", -2) = "bc"
+ * StringUtils.substring("abc", -4) = "abc"
+ *
+ *
+ * @param str the String to get the substring from, may be null
+ * @param start the position to start from, negative means
+ * count back from the end of the String by this many characters
+ * @return substring from start position, null if null String input
+ */
+ public static String substring(String str, int start) {
+ if (str == null) {
+ return null;
+ }
+
+ // handle negatives, which means last n characters
+ if (start < 0) {
+ start = str.length() + start; // remember start is negative
+ }
+
+ if (start < 0) {
+ start = 0;
+ }
+ if (start > str.length()) {
+ return EMPTY;
+ }
+
+ return str.substring(start);
+ }
+
+ /**
+ * n
+ * characters from the end of the String.start
+ * position and ends before the end position. All position counting is
+ * zero-based -- i.e., to start at the beginning of the string use
+ * start = 0. Negative start and end positions can be used to
+ * specify offsets relative to the end of the String.start is not strictly to the left of end, ""
+ * is returned.
+ * StringUtils.substring(null, *, *) = null
+ * StringUtils.substring("", * , *) = "";
+ * StringUtils.substring("abc", 0, 2) = "ab"
+ * StringUtils.substring("abc", 2, 0) = ""
+ * StringUtils.substring("abc", 2, 4) = "c"
+ * StringUtils.substring("abc", 4, 6) = ""
+ * StringUtils.substring("abc", 2, 2) = ""
+ * StringUtils.substring("abc", -2, -1) = "b"
+ * StringUtils.substring("abc", -4, 2) = "ab"
+ *
+ *
+ * @param str the String to get the substring from, may be null
+ * @param start the position to start from, negative means
+ * count back from the end of the String by this many characters
+ * @param end the position to end at (exclusive), negative means
+ * count back from the end of the String by this many characters
+ * @return substring from start position to end positon,
+ * null if null String input
+ */
+ public static String substring(String str, int start, int end) {
+ if (str == null) {
+ return null;
+ }
+
+ // handle negatives
+ if (end < 0) {
+ end = str.length() + end; // remember end is negative
+ }
+ if (start < 0) {
+ start = str.length() + start; // remember start is negative
+ }
+
+ // check length next
+ if (end > str.length()) {
+ end = str.length();
+ }
+
+ // if start is greater than end, return ""
+ if (start > end) {
+ return EMPTY;
+ }
+
+ if (start < 0) {
+ start = 0;
+ }
+ if (end < 0) {
+ end = 0;
+ }
+
+ return str.substring(start, end);
+ }
+
+ // Left/Right/Mid
+ //-----------------------------------------------------------------------
+ /**
+ * len characters of a String.len characters are not available, or the
+ * String is null, the String will be returned without
+ * an exception. An empty String is returned if len is negative.
+ * StringUtils.left(null, *) = null
+ * StringUtils.left(*, -ve) = ""
+ * StringUtils.left("", *) = ""
+ * StringUtils.left("abc", 0) = ""
+ * StringUtils.left("abc", 2) = "ab"
+ * StringUtils.left("abc", 4) = "abc"
+ *
+ *
+ * @param str the String to get the leftmost characters from, may be null
+ * @param len the length of the required String
+ * @return the leftmost characters, null if null String input
+ */
+ public static String left(String str, int len) {
+ if (str == null) {
+ return null;
+ }
+ if (len < 0) {
+ return EMPTY;
+ }
+ if (str.length() <= len) {
+ return str;
+ }
+ return str.substring(0, len);
+ }
+
+ /**
+ * len characters of a String.len characters are not available, or the String
+ * is null, the String will be returned without an
+ * an exception. An empty String is returned if len is negative.
+ * StringUtils.right(null, *) = null
+ * StringUtils.right(*, -ve) = ""
+ * StringUtils.right("", *) = ""
+ * StringUtils.right("abc", 0) = ""
+ * StringUtils.right("abc", 2) = "bc"
+ * StringUtils.right("abc", 4) = "abc"
+ *
+ *
+ * @param str the String to get the rightmost characters from, may be null
+ * @param len the length of the required String
+ * @return the rightmost characters, null if null String input
+ */
+ public static String right(String str, int len) {
+ if (str == null) {
+ return null;
+ }
+ if (len < 0) {
+ return EMPTY;
+ }
+ if (str.length() <= len) {
+ return str;
+ }
+ return str.substring(str.length() - len);
+ }
+
+ /**
+ * len characters from the middle of a String.len characters are not available, the remainder
+ * of the String will be returned without an exception. If the
+ * String is null, null will be returned.
+ * An empty String is returned if len is negative or exceeds the
+ * length of str.
+ * StringUtils.mid(null, *, *) = null
+ * StringUtils.mid(*, *, -ve) = ""
+ * StringUtils.mid("", 0, *) = ""
+ * StringUtils.mid("abc", 0, 2) = "ab"
+ * StringUtils.mid("abc", 0, 4) = "abc"
+ * StringUtils.mid("abc", 2, 4) = "c"
+ * StringUtils.mid("abc", 4, 2) = ""
+ * StringUtils.mid("abc", -2, 2) = "ab"
+ *
+ *
+ * @param str the String to get the characters from, may be null
+ * @param pos the position to start from, negative treated as zero
+ * @param len the length of the required String
+ * @return the middle characters, null if null String input
+ */
+ public static String mid(String str, int pos, int len) {
+ if (str == null) {
+ return null;
+ }
+ if (len < 0 || pos > str.length()) {
+ return EMPTY;
+ }
+ if (pos < 0) {
+ pos = 0;
+ }
+ if (str.length() <= (pos + len)) {
+ return str.substring(pos);
+ }
+ return str.substring(pos, pos + len);
+ }
+
+ // SubStringAfter/SubStringBefore
+ //-----------------------------------------------------------------------
+ /**
+ * null string input will return null.
+ * An empty ("") string input will return the empty string.
+ * A null separator will return the input string.
+ * StringUtils.substringBefore(null, *) = null
+ * StringUtils.substringBefore("", *) = ""
+ * StringUtils.substringBefore("abc", "a") = ""
+ * StringUtils.substringBefore("abcba", "b") = "a"
+ * StringUtils.substringBefore("abc", "c") = "ab"
+ * StringUtils.substringBefore("abc", "d") = "abc"
+ * StringUtils.substringBefore("abc", "") = ""
+ * StringUtils.substringBefore("abc", null) = "abc"
+ *
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the String to search for, may be null
+ * @return the substring before the first occurrence of the separator,
+ * null if null String input
+ * @since 2.0
+ */
+ public static String substringBefore(String str, String separator) {
+ if (isEmpty(str) || separator == null) {
+ return str;
+ }
+ if (separator.length() == 0) {
+ return EMPTY;
+ }
+ int pos = str.indexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return str;
+ }
+ return str.substring(0, pos);
+ }
+
+ /**
+ * null string input will return null.
+ * An empty ("") string input will return the empty string.
+ * A null separator will return the empty string if the
+ * input string is not null.
+ * StringUtils.substringAfter(null, *) = null
+ * StringUtils.substringAfter("", *) = ""
+ * StringUtils.substringAfter(*, null) = ""
+ * StringUtils.substringAfter("abc", "a") = "bc"
+ * StringUtils.substringAfter("abcba", "b") = "cba"
+ * StringUtils.substringAfter("abc", "c") = ""
+ * StringUtils.substringAfter("abc", "d") = ""
+ * StringUtils.substringAfter("abc", "") = "abc"
+ *
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the String to search for, may be null
+ * @return the substring after the first occurrence of the separator,
+ * null if null String input
+ * @since 2.0
+ */
+ public static String substringAfter(String str, String separator) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ if (separator == null) {
+ return EMPTY;
+ }
+ int pos = str.indexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return EMPTY;
+ }
+ return str.substring(pos + separator.length());
+ }
+
+ /**
+ * null string input will return null.
+ * An empty ("") string input will return the empty string.
+ * An empty or null separator will return the input string.
+ * StringUtils.substringBeforeLast(null, *) = null
+ * StringUtils.substringBeforeLast("", *) = ""
+ * StringUtils.substringBeforeLast("abcba", "b") = "abc"
+ * StringUtils.substringBeforeLast("abc", "c") = "ab"
+ * StringUtils.substringBeforeLast("a", "a") = ""
+ * StringUtils.substringBeforeLast("a", "z") = "a"
+ * StringUtils.substringBeforeLast("a", null) = "a"
+ * StringUtils.substringBeforeLast("a", "") = "a"
+ *
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the String to search for, may be null
+ * @return the substring before the last occurrence of the separator,
+ * null if null String input
+ * @since 2.0
+ */
+ public static String substringBeforeLast(String str, String separator) {
+ if (isEmpty(str) || isEmpty(separator)) {
+ return str;
+ }
+ int pos = str.lastIndexOf(separator);
+ if (pos == INDEX_NOT_FOUND) {
+ return str;
+ }
+ return str.substring(0, pos);
+ }
+
+ /**
+ * null string input will return null.
+ * An empty ("") string input will return the empty string.
+ * An empty or null separator will return the empty string if
+ * the input string is not null.
+ * StringUtils.substringAfterLast(null, *) = null
+ * StringUtils.substringAfterLast("", *) = ""
+ * StringUtils.substringAfterLast(*, "") = ""
+ * StringUtils.substringAfterLast(*, null) = ""
+ * StringUtils.substringAfterLast("abc", "a") = "bc"
+ * StringUtils.substringAfterLast("abcba", "b") = "a"
+ * StringUtils.substringAfterLast("abc", "c") = ""
+ * StringUtils.substringAfterLast("a", "a") = ""
+ * StringUtils.substringAfterLast("a", "z") = ""
+ *
+ *
+ * @param str the String to get a substring from, may be null
+ * @param separator the String to search for, may be null
+ * @return the substring after the last occurrence of the separator,
+ * null if null String input
+ * @since 2.0
+ */
+ public static String substringAfterLast(String str, String separator) {
+ if (isEmpty(str)) {
+ return str;
+ }
+ if (isEmpty(separator)) {
+ return EMPTY;
+ }
+ int pos = str.lastIndexOf(separator);
+ if (pos == INDEX_NOT_FOUND || pos == (str.length() - separator.length())) {
+ return EMPTY;
+ }
+ return str.substring(pos + separator.length());
+ }
+
+ // Substring between
+ //-----------------------------------------------------------------------
+ /**
+ * null input String returns null.
+ * A null tag returns null.
+ * StringUtils.substringBetween(null, *) = null
+ * StringUtils.substringBetween("", "") = ""
+ * StringUtils.substringBetween("", "tag") = null
+ * StringUtils.substringBetween("tagabctag", null) = null
+ * StringUtils.substringBetween("tagabctag", "") = ""
+ * StringUtils.substringBetween("tagabctag", "tag") = "abc"
+ *
+ *
+ * @param str the String containing the substring, may be null
+ * @param tag the String before and after the substring, may be null
+ * @return the substring, null if no match
+ * @since 2.0
+ */
+ public static String substringBetween(String str, String tag) {
+ return substringBetween(str, tag, tag);
+ }
+
+ /**
+ * null input String returns null.
+ * A null open/close returns null (no match).
+ * An empty ("") open and close returns an empty string.
+ * StringUtils.substringBetween("wx[b]yz", "[", "]") = "b"
+ * StringUtils.substringBetween(null, *, *) = null
+ * StringUtils.substringBetween(*, null, *) = null
+ * StringUtils.substringBetween(*, *, null) = null
+ * StringUtils.substringBetween("", "", "") = ""
+ * StringUtils.substringBetween("", "", "]") = null
+ * StringUtils.substringBetween("", "[", "]") = null
+ * StringUtils.substringBetween("yabcz", "", "") = ""
+ * StringUtils.substringBetween("yabcz", "y", "z") = "abc"
+ * StringUtils.substringBetween("yabczyabcz", "y", "z") = "abc"
+ *
+ *
+ * @param str the String containing the substring, may be null
+ * @param open the String before the substring, may be null
+ * @param close the String after the substring, may be null
+ * @return the substring, null if no match
+ * @since 2.0
+ */
+ public static String substringBetween(String str, String open, String close) {
+ if (str == null || open == null || close == null) {
+ return null;
+ }
+ int start = str.indexOf(open);
+ if (start != INDEX_NOT_FOUND) {
+ int end = str.indexOf(close, start + open.length());
+ if (end != INDEX_NOT_FOUND) {
+ return str.substring(start + open.length(), end);
+ }
+ }
+ return null;
+ }
+
+}
\ No newline at end of file
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaService b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaService
new file mode 100644
index 0000000000..f31fbb43bb
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/services/com.anji.captcha.service.CaptchaService
@@ -0,0 +1,3 @@
+com.anji.captcha.service.impl.BlockPuzzleCaptchaServiceImpl
+com.anji.captcha.service.impl.ClickWordCaptchaServiceImpl
+com.anji.captcha.service.impl.DefaultCaptchaServiceImpl
\ No newline at end of file
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
index 8411d2cc34..12cf6229c8 100644
--- a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
+++ b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -1 +1,2 @@
+com.anji.captcha.config.AjCaptchaAutoConfiguration
cn.iocoder.yudao.framework.captcha.config.YudaoCaptchaConfiguration
\ No newline at end of file
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/1.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/1.png
new file mode 100644
index 0000000000..022aabf93c
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/1.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/2.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/2.png
new file mode 100644
index 0000000000..914908e897
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/2.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/3.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/3.png
new file mode 100644
index 0000000000..f0f3ce581b
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/3.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/4.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/4.png
new file mode 100644
index 0000000000..c5697f3cb4
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/4.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/5.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/5.png
new file mode 100644
index 0000000000..e29e7a3c1b
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/5.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/6.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/6.png
new file mode 100644
index 0000000000..2425f412df
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/6.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/bg8.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/bg8.png
new file mode 100644
index 0000000000..5ea54d482a
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/original/bg8.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/1.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/1.png
new file mode 100644
index 0000000000..1905026606
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/1.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/2.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/2.png
new file mode 100644
index 0000000000..b1482d48b4
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/2.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/3.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/3.png
new file mode 100644
index 0000000000..cdbb0b18c4
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/3.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/4.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/4.png
new file mode 100644
index 0000000000..bc69c96224
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/4.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/5.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/5.png
new file mode 100644
index 0000000000..0080a54650
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/5.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/6.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/6.png
new file mode 100644
index 0000000000..b07c3b4046
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/jigsaw/slidingBlock/6.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/1.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/1.png
new file mode 100644
index 0000000000..50dfe28ef6
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/1.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/2.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/2.png
new file mode 100644
index 0000000000..15b38ad27b
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/2.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/3.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/3.png
new file mode 100644
index 0000000000..e2e487bd41
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/3.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/4.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/4.png
new file mode 100644
index 0000000000..c34baa4048
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/4.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/5.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/5.png
new file mode 100644
index 0000000000..0b3d11a27d
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/5.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/6.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/6.png
new file mode 100644
index 0000000000..67797a11d6
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/6.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg10.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg10.png
new file mode 100644
index 0000000000..c99fbcb035
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg10.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg11.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg11.png
new file mode 100644
index 0000000000..6a951d326c
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg11.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg12.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg12.png
new file mode 100644
index 0000000000..a38ada5042
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg12.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg13.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg13.png
new file mode 100644
index 0000000000..07af86a866
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg13.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg14.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg14.png
new file mode 100644
index 0000000000..95593759d0
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg14.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg15.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg15.png
new file mode 100644
index 0000000000..cb1ebb63ed
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg15.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg16.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg16.png
new file mode 100644
index 0000000000..106b4562bf
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg16.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg17.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg17.png
new file mode 100644
index 0000000000..bcdbe76551
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg17.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg18.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg18.png
new file mode 100644
index 0000000000..ae94e09cff
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg18.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg19.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg19.png
new file mode 100644
index 0000000000..bef9318b5c
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg19.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg20.png b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg20.png
new file mode 100644
index 0000000000..36cfbdec6e
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/defaultImages/pic-click/bg20.png differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/fonts/WenQuanZhengHei.ttf b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/fonts/WenQuanZhengHei.ttf
new file mode 100644
index 0000000000..f84e9feb3c
Binary files /dev/null and b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/fonts/WenQuanZhengHei.ttf differ
diff --git a/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/fonts/license.txt b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/fonts/license.txt
new file mode 100644
index 0000000000..719f68f0be
--- /dev/null
+++ b/yudao-framework/yudao-spring-boot-starter-captcha/src/main/resources/fonts/license.txt
@@ -0,0 +1,55 @@
+文泉驿是一个开源汉字字体项目
+
+由旅美学者房骞骞(FangQ)
+
+于2004年10月创建
+
+集中力量解决GNU/Linux
+
+高质量中文字体匮乏的状况
+
+目前,文泉驿已经开发并发布了
+
+第一个完整覆盖GB18030汉字
+
+(包含27000多个汉字)
+
+的多规格点阵汉字字型文件
+
+第一个覆盖GBK字符集的
+
+开源矢量字型文件(文泉驿正黑)
+
+并提供了目前包含字符数目最多的
+
+开源字体——GNU Unifont——中
+
+绝大多数中日韩文相关的符号
+
+这些字型文件已经逐渐成为
+
+主流Linux/Unix发行版
+
+中文桌面的首选中文字体
+
+目前Ubuntu、Fedora、Slackware
+
+Magic Linux、CDLinux
+
+使用文泉驿作为默认中文字体
+
+Debian、Gentoo、Mandriva
+
+ArchLinux、Frugalware
+
+则提供了官方源支持
+
+而FreeBSD则在其ports中有提供
+
+所以,今天我们所要分享的就是
+
+文泉驿正黑体
+
+可在Linux/UNIX,Windows
+
+Mac OS和嵌入式操作系统中使用
\ No newline at end of file