shikeyin
2024-01-11 65da8373531677b1c37a98f53eaa30c892f35e5a
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
package com.iplatform.base.captcha;
 
import com.iplatform.base.PlatformRuntimeException;
import com.iplatform.base.util.RandomUtils;
import com.iplatform.base.util.VerifyImgUtil;
import com.walker.infrastructure.utils.Base64Utils;
import com.walker.web.CaptchaResult;
import com.walker.web.CaptchaType;
import com.walker.web.util.IdUtils;
 
import javax.imageio.ImageIO;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.util.Objects;
import java.util.Random;
 
/**
 * 电商模块使用的拼图验证,提供者实现。
 * @author 时克英
 * @date 2023-06-27
 */
public class BlockPuzzleCaptchaProvider extends AbstractCaptchaProvider{
 
    private final static int IMAGE_BG_SIZE = 6;
 
    public BlockPuzzleCaptchaProvider(){
//        String imageName = null;
//        try{
//            BufferedImage image = null;
//            for(int i=1; i<IMAGE_BG_SIZE+1; i++){
//                imageName = "images/jigsaw/original/" + i + ".png";
//                image = this.loadBufferedImage(imageName);
//                this.imageCacheMap.put(imageName, image);
//            }
//
//            for(int j=1; j<IMAGE_BG_SIZE+1; j++){
//                imageName = "images/jigsaw/slidingBlock/" + j + ".png";
//                image = this.loadBufferedImage(imageName);
//                this.imageCacheMap.put(imageName, image);
//            }
//            logger.info("共加载验证码拼图文件'{}'个", this.imageCacheMap.size());
//
//        } catch (Exception ex){
//            throw new ApplicationRuntimeException("验证码图片加载错误,imageName=" + imageName, ex);
//        }
    }
 
    @Override
    public CaptchaResult generateCaptcha(Object param) {
        int n = this.bgRandom.nextInt(IMAGE_BG_SIZE) + 1; //背景
        int m = this.blockRandom.nextInt(IMAGE_BG_SIZE) + 1; //滑块
        if(n > IMAGE_BG_SIZE){
            n = IMAGE_BG_SIZE;
        }
        if(m > IMAGE_BG_SIZE){
            m = IMAGE_BG_SIZE;
        }
 
        try{
            //原图
            String sourceName = "images/jigsaw/original/" + n + ".png";
            // 注意:不能重复使用缓存图片,因为需要修改图片背景,所以必须每次加载原始图片。
//        BufferedImage sourceImg = this.imageCacheMap.get(sourceName);
            BufferedImage sourceImg = this.loadBufferedImage(sourceName);
            //抠图模版
            String blockName = "images/jigsaw/slidingBlock/" + m + ".png";
//        BufferedImage templateImg = this.imageCacheMap.get(blockName);
            BufferedImage templateImg = this.loadBufferedImage(blockName);
            if(sourceImg == null || templateImg == null){
                logger.error("未加载到拼图验证图片资源:" + sourceName + ", or " + blockName);
                return null;
            }
            return this.pictureTemplatesCut(sourceImg, templateImg, VerifyImgUtil.getBase64(templateImg));
 
        } catch (Exception ex){
            throw new PlatformRuntimeException("拼图验证生成失败:" + ex.getMessage(), ex);
        }
    }
 
//    @Override
//    public boolean validateCaptcha(CaptchaResult data) {
//        return false;
//    }
 
    @Override
    public CaptchaType getCaptchaType() {
        return CaptchaType.JigsawMobile;
    }
 
    private JigsawResult pictureTemplatesCut(BufferedImage originalImage, BufferedImage jigsawImage, String jigsawImageBase64) {
        JigsawResult jigsawResult = new JigsawResult();
        try {
//            CaptchaVO dataVO = new CaptchaVO();
            int originalWidth = originalImage.getWidth();
            int originalHeight = originalImage.getHeight();
            int jigsawWidth = jigsawImage.getWidth();
            int jigsawHeight = jigsawImage.getHeight();
 
            //随机生成拼图坐标
            jigsawResult = generateJigsawPoint(originalWidth, originalHeight, jigsawWidth, jigsawHeight);
            int x = jigsawResult.getX();
            int y = jigsawResult.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();
                    String s = this.getSlidingBlock();
                    if (!jigsawImageBase64.equals(s)) {
                        interferenceByTemplate(originalImage, Objects.requireNonNull(VerifyImgUtil.getBase64StrToImage(s)), position, 0);
                        break;
                    }
                }
            }
            if (captchaInterferenceOptions > 1) {
                while (true) {
                    String s = this.getSlidingBlock();
                    if (!jigsawImageBase64.equals(s)) {
                        Integer randomInt = RandomUtils.getRandomInt(jigsawWidth, 100 - jigsawWidth);
                        interferenceByTemplate(originalImage, Objects.requireNonNull(VerifyImgUtil.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_JPG, oriImagesOs);//利用ImageIO类提供的write方法,将bi以jpg图片的数据模式写入流。
            byte[] oriCopyImages = oriImagesOs.toByteArray();
//            Base64.Encoder encoder = Base64.getEncoder();
//            dataVO.setOriginalImageBase64(encoder.encodeToString(oriCopyImages).replaceAll("\r|\n", ""));
            jigsawResult.setImageSourceBase64(Base64Utils.encode(oriCopyImages));
            //point信息不传到前端,只做后端check校验
//            dataVO.setPoint(point);
//            dataVO.setJigsawImageBase64(encoder.encodeToString(jigsawImages).replaceAll("\r|\n", ""));
            jigsawResult.setImageBlockBase64(Base64Utils.encode(jigsawImages));
//            dataVO.setToken(RandomUtils.getUUID());
            jigsawResult.setUuid(IdUtils.simpleUUID());
//            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:{}", jigsawResult.getUuid(), jigsawResult.getX(), jigsawResult.getY());
            return jigsawResult;
 
        } catch (Exception e) {
            logger.error("生成验证拼图错误:" + e.getMessage(), e);
            return null;
        }
    }
 
    private String getSlidingBlock() throws Exception{
//        String[] strings = fileNameMap.get(CaptchaBaseMapEnum.SLIDING_BLOCK.getCodeValue());
//        if (null == strings || strings.length == 0) {
//            return null;
//        }
        Integer randomInt = RandomUtils.getRandomInt(0, IMAGE_BG_SIZE);
        String blockName = "images/jigsaw/slidingBlock/" + randomInt + ".png";
//        BufferedImage image = this.imageCacheMap.get(blockName);
        BufferedImage image = this.loadBufferedImage(blockName);
        if(image == null){
            image = this.loadBufferedImage("images/jigsaw/slidingBlock/1.png");
        }
        return VerifyImgUtil.getBase64(image);
    }
 
    /**
     * 随机生成拼图坐标
     *
     * @param originalWidth
     * @param originalHeight
     * @param jigsawWidth
     * @param jigsawHeight
     * @return
     */
    private static JigsawResult 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);
        JigsawResult jigsawResult = new JigsawResult();
        jigsawResult.setX(x);
        jigsawResult.setY(y);
        return jigsawResult;
    }
 
    /**
     * @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());
                }
            }
        }
    }
 
    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();
    }
 
    /**
     * 干扰抠图处理
     *
     * @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());
                }
            }
        }
    }
}