Compare commits
4 Commits
a1d42a4b27
...
8a302db65a
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8a302db65a | ||
|
|
3964547e84 | ||
|
|
c387f81225 | ||
|
|
582beb13db |
29
src/main/java/com/xiang/common/pojo/TrackPoint.java
Normal file
29
src/main/java/com/xiang/common/pojo/TrackPoint.java
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package com.xiang.common.pojo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: xiang
|
||||||
|
* @Date: 2026-05-07 15:57
|
||||||
|
*/
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 轨迹点类
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class TrackPoint {
|
||||||
|
public int x, y, t;
|
||||||
|
public String type;
|
||||||
|
|
||||||
|
public TrackPoint(int x, int y, int t, String type) {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.t = t;
|
||||||
|
this.type = type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return String.format("{\"x\":%d,\"y\":%d,\"t\":%d,\"type\":\"%s\"}", x, y, t, type);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.xiang.common.pojo.code;
|
package com.xiang.common.pojo.code;
|
||||||
|
|
||||||
|
import com.alibaba.fastjson.annotation.JSONField;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
@@ -25,5 +26,6 @@ public class YcCodeRequest {
|
|||||||
/**
|
/**
|
||||||
* 模板图片 base64
|
* 模板图片 base64
|
||||||
*/
|
*/
|
||||||
|
@JSONField(name = "label_image")
|
||||||
private String labelImage;
|
private String labelImage;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.xiang.common.pojo.jntyzx.zlb;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: xiang
|
||||||
|
* @Date: 2026-05-07 15:44
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ZlbCaptcha {
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
private String backgroundImage;
|
||||||
|
private String templateImage;
|
||||||
|
private String backgroundImageTag;
|
||||||
|
private String templateImageTag;
|
||||||
|
private Integer backgroundImageWidth;
|
||||||
|
private Integer backgroundImageHeight;
|
||||||
|
private Integer templateImageWidth;
|
||||||
|
private Integer templateImageHeight;
|
||||||
|
private String data;
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
package com.xiang.common.pojo.jntyzx.zlb;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: xiang
|
||||||
|
* @Date: 2026-05-07 15:44
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
@NoArgsConstructor
|
||||||
|
public class ZlbCaptchaResp {
|
||||||
|
private String id;
|
||||||
|
private ZlbCaptcha captcha;
|
||||||
|
}
|
||||||
@@ -10,7 +10,6 @@ import com.xiang.common.pojo.code.YcCodeRequest;
|
|||||||
import com.xiang.common.utils.HttpService;
|
import com.xiang.common.utils.HttpService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -52,6 +51,7 @@ public class CodeServiceImpl implements ICodeService {
|
|||||||
ycCodeRequest.setImage(image);
|
ycCodeRequest.setImage(image);
|
||||||
ycCodeRequest.setToken("9LQ1ATKVEeO8Arhq-HavXzpHvkzdZz_r7ydmqlYhp9c");
|
ycCodeRequest.setToken("9LQ1ATKVEeO8Arhq-HavXzpHvkzdZz_r7ydmqlYhp9c");
|
||||||
ycCodeRequest.setLabelImage(templateImage);
|
ycCodeRequest.setLabelImage(templateImage);
|
||||||
|
ycCodeRequest.setType(YcCodeTypeEnum.YC_310700.getType());
|
||||||
|
|
||||||
String resp = HttpService.doPost(YUN_CODE_API_URL, header, JSON.toJSONString(ycCodeRequest));
|
String resp = HttpService.doPost(YUN_CODE_API_URL, header, JSON.toJSONString(ycCodeRequest));
|
||||||
YcCodeBaseResponse<YcCodeDataResp> response = JSON.parseObject(resp, new TypeReference<YcCodeBaseResponse<YcCodeDataResp>>() {
|
YcCodeBaseResponse<YcCodeDataResp> response = JSON.parseObject(resp, new TypeReference<YcCodeBaseResponse<YcCodeDataResp>>() {
|
||||||
|
|||||||
97
src/main/java/com/xiang/common/utils/Base64ImageScaler.java
Normal file
97
src/main/java/com/xiang/common/utils/Base64ImageScaler.java
Normal file
File diff suppressed because one or more lines are too long
68
src/main/java/com/xiang/common/utils/ImageUtils.java
Normal file
68
src/main/java/com/xiang/common/utils/ImageUtils.java
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package com.xiang.common.utils;
|
||||||
|
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @Author: xiang
|
||||||
|
* @Date: 2026-05-08 08:56
|
||||||
|
*/
|
||||||
|
public class ImageUtils {
|
||||||
|
/**
|
||||||
|
* 将 Base64 图片写入文件
|
||||||
|
* @param base64String Base64 编码的图片字符串
|
||||||
|
* @param outputPath 输出文件路径
|
||||||
|
*/
|
||||||
|
public static void saveBase64Image(String base64String, String outputPath) {
|
||||||
|
try {
|
||||||
|
// 处理可能包含前缀的 Base64(如 data:image/png;base64,)
|
||||||
|
String base64Data = extractBase64Data(base64String);
|
||||||
|
|
||||||
|
// 解码 Base64
|
||||||
|
byte[] imageBytes = Base64.getDecoder().decode(base64Data);
|
||||||
|
|
||||||
|
// 写入文件
|
||||||
|
try (FileOutputStream fos = new FileOutputStream(outputPath)) {
|
||||||
|
fos.write(imageBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("图片已保存至: " + outputPath);
|
||||||
|
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
System.err.println("Base64 解码失败: " + e.getMessage());
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("文件写入失败: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 提取纯 Base64 数据(去除前缀)
|
||||||
|
*/
|
||||||
|
private static String extractBase64Data(String base64String) {
|
||||||
|
if (base64String == null || base64String.isEmpty()) {
|
||||||
|
throw new IllegalArgumentException("Base64 字符串不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否包含 data:image/xxx;base64, 前缀
|
||||||
|
if (base64String.contains(",")) {
|
||||||
|
return base64String.split(",", 2)[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return base64String;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从 Base64 字符串中获取图片类型
|
||||||
|
*/
|
||||||
|
public static String getImageType(String base64String) {
|
||||||
|
if (base64String.startsWith("data:image/")) {
|
||||||
|
String type = base64String.substring(11, base64String.indexOf(";"));
|
||||||
|
return type; // 返回 png, jpeg, gif 等
|
||||||
|
}
|
||||||
|
return "png"; // 默认类型
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
198
src/main/java/com/xiang/common/utils/TrajectoryUtil.java
Normal file
198
src/main/java/com/xiang/common/utils/TrajectoryUtil.java
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
package com.xiang.common.utils;
|
||||||
|
|
||||||
|
import com.xiang.common.pojo.TrackPoint;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 鼠标轨迹生成工具类 - 开箱即用
|
||||||
|
*
|
||||||
|
* 使用方法:
|
||||||
|
* 1. 直接调用 TrajectoryUtil.generate(起点, 终点, 时长, 点数)
|
||||||
|
* 2. 或使用预设的购物流程 TrajectoryUtil.shoppingFlow()
|
||||||
|
*/
|
||||||
|
public class TrajectoryUtil {
|
||||||
|
|
||||||
|
private static final Random random = new Random();
|
||||||
|
|
||||||
|
// ==================== 核心方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成两点之间的移动轨迹
|
||||||
|
* @param fromX 起点X
|
||||||
|
* @param fromY 起点Y
|
||||||
|
* @param toX 终点X
|
||||||
|
* @param toY 终点Y
|
||||||
|
* @param duration 移动时长(毫秒)
|
||||||
|
* @param pointCount 生成点数
|
||||||
|
* @return 轨迹点列表
|
||||||
|
*/
|
||||||
|
public static List<TrackPoint> generateMove(int fromX, int fromY, int toX, int toY, int start ,int duration, int pointCount) {
|
||||||
|
List<TrackPoint> points = new ArrayList<>();
|
||||||
|
|
||||||
|
// 随机控制点(让路径弯曲)
|
||||||
|
int cx = (fromX + toX) / 2 + (random.nextInt(100) - 50);
|
||||||
|
int cy = (fromY + toY) / 2 + (random.nextInt(100) - 50);
|
||||||
|
|
||||||
|
for (int i = 0; i <= pointCount; i++) {
|
||||||
|
double t = (double) i / pointCount;
|
||||||
|
// 缓动:开始慢 -> 中间快 -> 结束慢
|
||||||
|
t = easeInOutCubic(t);
|
||||||
|
|
||||||
|
// 贝塞尔曲线
|
||||||
|
int x = (int)((1-t)*(1-t)*fromX + 2*(1-t)*t*cx + t*t*toX);
|
||||||
|
int y = (int)((1-t)*(1-t)*fromY + 2*(1-t)*t*cy + t*t*toY);
|
||||||
|
|
||||||
|
// 随机抖动
|
||||||
|
x += random.nextInt(5) - 2;
|
||||||
|
y += random.nextInt(5) - 2;
|
||||||
|
|
||||||
|
int time = start + (int)(t * duration);
|
||||||
|
points.add(new TrackPoint(x, y, time, "move"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保终点精确
|
||||||
|
points.get(points.size()-1).x = toX;
|
||||||
|
points.get(points.size()-1).y = toY;
|
||||||
|
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成点击动作
|
||||||
|
* @param x 点击X坐标
|
||||||
|
* @param y 点击Y坐标
|
||||||
|
* @param startTime 开始时间(毫秒)
|
||||||
|
* @return 轨迹点列表
|
||||||
|
*/
|
||||||
|
public static List<TrackPoint> generateClick(int x, int y, int startTime) {
|
||||||
|
List<TrackPoint> points = new ArrayList<>();
|
||||||
|
int currentTime = startTime;
|
||||||
|
|
||||||
|
// 思考时间
|
||||||
|
currentTime += 100 + random.nextInt(400);
|
||||||
|
points.add(new TrackPoint(x, y, currentTime, "hover"));
|
||||||
|
|
||||||
|
// 微调对准
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
int offsetX = x + (random.nextInt(6) - 3);
|
||||||
|
int offsetY = y + (random.nextInt(6) - 3);
|
||||||
|
currentTime += 30 + random.nextInt(50);
|
||||||
|
points.add(new TrackPoint(offsetX, offsetY, currentTime, "move"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 点击
|
||||||
|
currentTime += 50 + random.nextInt(100);
|
||||||
|
points.add(new TrackPoint(x, y, currentTime, "click"));
|
||||||
|
|
||||||
|
// 点击后停留
|
||||||
|
currentTime += 80 + random.nextInt(150);
|
||||||
|
points.add(new TrackPoint(x, y, currentTime, "hover"));
|
||||||
|
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成随机取点(只返回一个点位)
|
||||||
|
* @param fromX 起点X
|
||||||
|
* @param fromY 起点Y
|
||||||
|
* @param toX 终点X
|
||||||
|
* @param toY 终点Y
|
||||||
|
* @return 随机点位
|
||||||
|
*/
|
||||||
|
public static Point getRandomPoint(int fromX, int fromY, int toX, int toY) {
|
||||||
|
double ratio = random.nextDouble();
|
||||||
|
int x = fromX + (int)((toX - fromX) * ratio);
|
||||||
|
int y = fromY + (int)((toY - fromY) * ratio);
|
||||||
|
|
||||||
|
// 添加随机偏移
|
||||||
|
if (random.nextBoolean()) {
|
||||||
|
x += random.nextInt(30) - 15;
|
||||||
|
y += random.nextInt(30) - 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Point(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成多个随机点
|
||||||
|
* @param fromX 起点X
|
||||||
|
* @param fromY 起点Y
|
||||||
|
* @param toX 终点X
|
||||||
|
* @param toY 终点Y
|
||||||
|
* @param count 生成数量
|
||||||
|
* @return 随机点位列表
|
||||||
|
*/
|
||||||
|
public static List<Point> getRandomPoints(int fromX, int fromY, int toX, int toY, int count) {
|
||||||
|
List<Point> points = new ArrayList<>();
|
||||||
|
for (int i = 0; i < count; i++) {
|
||||||
|
points.add(getRandomPoint(fromX, fromY, toX, toY));
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==================== 工具方法 ====================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 缓动函数
|
||||||
|
*/
|
||||||
|
private static double easeInOutCubic(double t) {
|
||||||
|
return t < 0.5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出为JSON字符串
|
||||||
|
*/
|
||||||
|
public static String toJson(List<TrackPoint> points) {
|
||||||
|
StringBuilder sb = new StringBuilder("[\n");
|
||||||
|
for (int i = 0; i < points.size(); i++) {
|
||||||
|
sb.append(" ").append(points.get(i).toString());
|
||||||
|
if (i < points.size() - 1) sb.append(",");
|
||||||
|
sb.append("\n");
|
||||||
|
}
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 输出为紧凑JSON(无换行)
|
||||||
|
*/
|
||||||
|
public static String toJsonCompact(List<TrackPoint> points) {
|
||||||
|
StringBuilder sb = new StringBuilder("[");
|
||||||
|
for (int i = 0; i < points.size(); i++) {
|
||||||
|
sb.append(points.get(i).toString());
|
||||||
|
if (i < points.size() - 1) sb.append(",");
|
||||||
|
}
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打印轨迹信息
|
||||||
|
*/
|
||||||
|
public static void printInfo(List<TrackPoint> points) {
|
||||||
|
if (points.isEmpty()) {
|
||||||
|
System.out.println("轨迹为空");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int clickCount = 0;
|
||||||
|
int moveCount = 0;
|
||||||
|
for (TrackPoint p : points) {
|
||||||
|
if ("click".equals(p.type)) clickCount++;
|
||||||
|
if ("move".equals(p.type)) moveCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
System.out.println("=== 轨迹信息 ===");
|
||||||
|
System.out.println("总点数: " + points.size());
|
||||||
|
System.out.println("移动点: " + moveCount);
|
||||||
|
System.out.println("点击点: " + clickCount);
|
||||||
|
System.out.println("总时长: " + points.get(points.size()-1).t + "ms");
|
||||||
|
System.out.println("起始位置: (" + points.get(0).x + "," + points.get(0).y + ")");
|
||||||
|
System.out.println("结束位置: (" + points.get(points.size()-1).x + "," + points.get(points.size()-1).y + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,368 @@
|
|||||||
|
package com.xiang.common.utils;
|
||||||
|
|
||||||
|
import com.xiang.common.pojo.jntyzx.zlb.ZlbOrderInfo;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.awt.image.BufferedImage;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.format.DateTimeFormatter;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ZLB 验证码轨迹调试器。
|
||||||
|
* 输入原始图片和轨迹点,输出控制台预览与带标注调试图。
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
public class ZlbCaptchaTrackDebugger {
|
||||||
|
|
||||||
|
private static final DateTimeFormatter FILE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss_SSS");
|
||||||
|
private static final int DEFAULT_CONSOLE_WIDTH = 84;
|
||||||
|
private static final int DEFAULT_CONSOLE_HEIGHT = 24;
|
||||||
|
private static final Path OUTPUT_DIR = Paths.get("logs", "script", "track-debug");
|
||||||
|
private static final String DEFAULT_REQUEST_ID = "debug-demo";
|
||||||
|
private static final String DEFAULT_COORDINATE_TEXT = "226,89|76,62|125,101|26,114";
|
||||||
|
private static final Path DEFAULT_IMAGE_FILE = Paths.get("src", "main", "resources", "image.txt");
|
||||||
|
|
||||||
|
private ZlbCaptchaTrackDebugger() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DebugResult debug(String base64Image,
|
||||||
|
List<String> rawTrackList,
|
||||||
|
List<ZlbOrderInfo.ZlbData.TrackList> generatedTrackList,
|
||||||
|
String requestId) {
|
||||||
|
try {
|
||||||
|
BufferedImage originalImage = decodeBase64Image(base64Image);
|
||||||
|
List<Point> rawPoints = parseRawTrackList(rawTrackList);
|
||||||
|
String consolePreview = buildConsolePreview(originalImage, rawPoints, generatedTrackList, requestId);
|
||||||
|
String debugImagePath = exportDebugImage(originalImage, rawPoints, generatedTrackList, requestId);
|
||||||
|
return new DebugResult(consolePreview, debugImagePath);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("生成 ZLB 验证码轨迹调试信息失败", e);
|
||||||
|
return new DebugResult(null, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ExecutionResult execute(String imageBase64, String coordinateText, String requestId) {
|
||||||
|
List<String> rawTrackList = ZlbCaptchaTrackUtil.parseCoordinateText(coordinateText);
|
||||||
|
List<ZlbOrderInfo.ZlbData.TrackList> generatedTrackList = ZlbCaptchaTrackUtil.generateBezierTrackList(rawTrackList);
|
||||||
|
DebugResult debugResult = debug(imageBase64, rawTrackList, generatedTrackList, requestId);
|
||||||
|
return new ExecutionResult(generatedTrackList, debugResult.getConsolePreview(), debugResult.getDebugImagePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ExecutionResult execute(String imageBase64, List<String> coordinateText, String requestId) {
|
||||||
|
List<String> rawTrackList = coordinateText;
|
||||||
|
List<ZlbOrderInfo.ZlbData.TrackList> generatedTrackList = ZlbCaptchaTrackUtil.generateBezierTrackList(rawTrackList);
|
||||||
|
DebugResult debugResult = debug(imageBase64, rawTrackList, generatedTrackList, requestId);
|
||||||
|
return new ExecutionResult(generatedTrackList, debugResult.getConsolePreview(), debugResult.getDebugImagePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
if (args.length == 0) {
|
||||||
|
runLocalSample();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (args.length < 3) {
|
||||||
|
System.out.println("Usage: ZlbCaptchaTrackDebugger <requestId> <coordinateText> <imageBase64>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ExecutionResult result = execute(args[2], args[1], args[0]);
|
||||||
|
System.out.println("trackList=" + result.getTrackListJson());
|
||||||
|
if (result.getConsolePreview() != null) {
|
||||||
|
System.out.println(result.getConsolePreview());
|
||||||
|
}
|
||||||
|
if (result.getDebugImagePath() != null) {
|
||||||
|
System.out.println("debugImagePath=" + result.getDebugImagePath());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void runLocalSample() {
|
||||||
|
try {
|
||||||
|
String imageBase64 = loadDefaultImageBase64();
|
||||||
|
ImageUtils.saveBase64Image(imageBase64, OUTPUT_DIR.toFile() + "image.jpeg");
|
||||||
|
ExecutionResult result = execute(imageBase64, DEFAULT_COORDINATE_TEXT, DEFAULT_REQUEST_ID);
|
||||||
|
System.out.println("trackList=" + result.getTrackListJson());
|
||||||
|
if (result.getConsolePreview() != null) {
|
||||||
|
System.out.println(result.getConsolePreview());
|
||||||
|
}
|
||||||
|
if (result.getDebugImagePath() != null) {
|
||||||
|
System.out.println("debugImagePath=" + result.getDebugImagePath());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("运行本地样例失败,请先把 image base64 写入 " + DEFAULT_IMAGE_FILE.toAbsolutePath(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String loadDefaultImageBase64() throws IOException {
|
||||||
|
return new String(Files.readAllBytes(DEFAULT_IMAGE_FILE), StandardCharsets.UTF_8).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BufferedImage decodeBase64Image(String base64Image) throws IOException {
|
||||||
|
String base64Data = base64Image;
|
||||||
|
if (base64Image != null && base64Image.contains(",")) {
|
||||||
|
base64Data = base64Image.substring(base64Image.indexOf(',') + 1);
|
||||||
|
}
|
||||||
|
byte[] imageBytes = Base64.getDecoder().decode(base64Data);
|
||||||
|
try (ByteArrayInputStream inputStream = new ByteArrayInputStream(imageBytes)) {
|
||||||
|
BufferedImage image = ImageIO.read(inputStream);
|
||||||
|
if (image == null) {
|
||||||
|
throw new IOException("base64 图片解码结果为空");
|
||||||
|
}
|
||||||
|
return image;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Point> parseRawTrackList(List<String> rawTrackList) {
|
||||||
|
if (rawTrackList == null || rawTrackList.isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
List<Point> points = new ArrayList<>(rawTrackList.size());
|
||||||
|
for (String item : rawTrackList) {
|
||||||
|
if (item == null || item.trim().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String[] split = item.split(",");
|
||||||
|
if (split.length < 2) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
points.add(new Point(Integer.parseInt(split[0].trim()), Integer.parseInt(split[1].trim())));
|
||||||
|
}
|
||||||
|
return points;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildConsolePreview(BufferedImage originalImage,
|
||||||
|
List<Point> rawPoints,
|
||||||
|
List<ZlbOrderInfo.ZlbData.TrackList> generatedTrackList,
|
||||||
|
String requestId) {
|
||||||
|
int width = originalImage.getWidth();
|
||||||
|
int height = originalImage.getHeight();
|
||||||
|
int consoleWidth = Math.min(DEFAULT_CONSOLE_WIDTH, Math.max(24, width));
|
||||||
|
int consoleHeight = Math.min(DEFAULT_CONSOLE_HEIGHT, Math.max(12, (int) Math.round((double) height / width * consoleWidth * 0.5d)));
|
||||||
|
|
||||||
|
char[][] canvas = new char[consoleHeight][consoleWidth];
|
||||||
|
for (int row = 0; row < consoleHeight; row++) {
|
||||||
|
for (int col = 0; col < consoleWidth; col++) {
|
||||||
|
canvas[row][col] = '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
overlayRawPoints(canvas, rawPoints, width, height);
|
||||||
|
overlayGeneratedTrack(canvas, generatedTrackList, width, height);
|
||||||
|
|
||||||
|
StringBuilder preview = new StringBuilder();
|
||||||
|
preview.append('\n');
|
||||||
|
preview.append("===== ZLB CAPTCHA DEBUG =====").append('\n');
|
||||||
|
preview.append("requestId=").append(sanitizeFileName(requestId))
|
||||||
|
.append(", image=").append(width).append("x").append(height)
|
||||||
|
.append(", rawPoints=").append(rawPoints.size())
|
||||||
|
.append(", generatedPoints=").append(generatedTrackList == null ? 0 : generatedTrackList.size())
|
||||||
|
.append('\n');
|
||||||
|
preview.append("rawPoints=").append(buildRawPointSummary(rawPoints)).append('\n');
|
||||||
|
preview.append("generatedTrack=").append(buildTrackSummary(generatedTrackList)).append('\n');
|
||||||
|
preview.append("grid: R=raw point, C=click, M=move, B=overlap, .=empty").append('\n');
|
||||||
|
for (char[] chars : canvas) {
|
||||||
|
preview.append(chars).append('\n');
|
||||||
|
}
|
||||||
|
preview.append("================================").append('\n');
|
||||||
|
return preview.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String exportDebugImage(BufferedImage originalImage,
|
||||||
|
List<Point> rawPoints,
|
||||||
|
List<ZlbOrderInfo.ZlbData.TrackList> generatedTrackList,
|
||||||
|
String requestId) throws IOException {
|
||||||
|
BufferedImage debugImage = new BufferedImage(
|
||||||
|
originalImage.getWidth(),
|
||||||
|
originalImage.getHeight(),
|
||||||
|
BufferedImage.TYPE_INT_ARGB
|
||||||
|
);
|
||||||
|
Graphics2D graphics = debugImage.createGraphics();
|
||||||
|
try {
|
||||||
|
graphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
|
||||||
|
graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||||
|
graphics.drawImage(originalImage, 0, 0, null);
|
||||||
|
drawGeneratedTrack(graphics, generatedTrackList);
|
||||||
|
drawRawPoints(graphics, rawPoints);
|
||||||
|
drawLegend(graphics);
|
||||||
|
} finally {
|
||||||
|
graphics.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.createDirectories(OUTPUT_DIR);
|
||||||
|
String fileName = String.format(
|
||||||
|
"zlb_track_%s_%s.png",
|
||||||
|
sanitizeFileName(requestId),
|
||||||
|
FILE_TIME_FORMATTER.format(LocalDateTime.now())
|
||||||
|
);
|
||||||
|
Path outputPath = OUTPUT_DIR.resolve(fileName).toAbsolutePath();
|
||||||
|
ImageIO.write(debugImage, "png", outputPath.toFile());
|
||||||
|
return outputPath.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void overlayRawPoints(char[][] canvas, List<Point> rawPoints, int width, int height) {
|
||||||
|
for (Point rawPoint : rawPoints) {
|
||||||
|
int col = scaleToConsole(rawPoint.x, width, canvas[0].length);
|
||||||
|
int row = scaleToConsole(rawPoint.y, height, canvas.length);
|
||||||
|
paintOverlay(canvas, row, col, 'R');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void overlayGeneratedTrack(char[][] canvas,
|
||||||
|
List<ZlbOrderInfo.ZlbData.TrackList> generatedTrackList,
|
||||||
|
int width,
|
||||||
|
int height) {
|
||||||
|
if (generatedTrackList == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (ZlbOrderInfo.ZlbData.TrackList trackPoint : generatedTrackList) {
|
||||||
|
int col = scaleToConsole(trackPoint.getX(), width, canvas[0].length);
|
||||||
|
int row = scaleToConsole(trackPoint.getY(), height, canvas.length);
|
||||||
|
char mark = "click".equalsIgnoreCase(trackPoint.getType()) ? 'C' : 'M';
|
||||||
|
paintOverlay(canvas, row, col, mark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void paintOverlay(char[][] canvas, int row, int col, char mark) {
|
||||||
|
char current = canvas[row][col];
|
||||||
|
if (current == 'R' || current == 'C' || current == 'M' || current == 'B') {
|
||||||
|
canvas[row][col] = current == mark ? current : 'B';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
canvas[row][col] = mark;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int scaleToConsole(int value, int originalSize, int consoleSize) {
|
||||||
|
if (originalSize <= 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int scaled = (int) Math.round(value * (consoleSize - 1d) / (originalSize - 1d));
|
||||||
|
return Math.max(0, Math.min(consoleSize - 1, scaled));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildTrackSummary(List<ZlbOrderInfo.ZlbData.TrackList> generatedTrackList) {
|
||||||
|
if (generatedTrackList == null || generatedTrackList.isEmpty()) {
|
||||||
|
return "[]";
|
||||||
|
}
|
||||||
|
StringBuilder summary = new StringBuilder("[");
|
||||||
|
for (int i = 0; i < generatedTrackList.size(); i++) {
|
||||||
|
ZlbOrderInfo.ZlbData.TrackList trackPoint = generatedTrackList.get(i);
|
||||||
|
if (i > 0) {
|
||||||
|
summary.append(", ");
|
||||||
|
}
|
||||||
|
summary.append("{x=").append(trackPoint.getX())
|
||||||
|
.append(", y=").append(trackPoint.getY())
|
||||||
|
.append(", type=").append(trackPoint.getType())
|
||||||
|
.append(", t=").append(trackPoint.getT())
|
||||||
|
.append('}');
|
||||||
|
}
|
||||||
|
summary.append(']');
|
||||||
|
return summary.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String buildRawPointSummary(List<Point> rawPoints) {
|
||||||
|
if (rawPoints == null || rawPoints.isEmpty()) {
|
||||||
|
return "[]";
|
||||||
|
}
|
||||||
|
StringBuilder summary = new StringBuilder("[");
|
||||||
|
for (int i = 0; i < rawPoints.size(); i++) {
|
||||||
|
Point point = rawPoints.get(i);
|
||||||
|
if (i > 0) {
|
||||||
|
summary.append(", ");
|
||||||
|
}
|
||||||
|
summary.append("P").append(i)
|
||||||
|
.append("{x=").append(point.x)
|
||||||
|
.append(", y=").append(point.y)
|
||||||
|
.append('}');
|
||||||
|
}
|
||||||
|
summary.append(']');
|
||||||
|
return summary.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void drawRawPoints(Graphics2D graphics, List<Point> rawPoints) {
|
||||||
|
graphics.setFont(new Font("SansSerif", Font.BOLD, 13));
|
||||||
|
graphics.setStroke(new BasicStroke(2.5f));
|
||||||
|
for (int i = 0; i < rawPoints.size(); i++) {
|
||||||
|
Point point = rawPoints.get(i);
|
||||||
|
graphics.setColor(new Color(30, 144, 255, 210));
|
||||||
|
graphics.drawOval(point.x - 8, point.y - 8, 16, 16);
|
||||||
|
graphics.setColor(Color.WHITE);
|
||||||
|
graphics.drawString("P" + i, point.x + 8, point.y - 8);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void drawGeneratedTrack(Graphics2D graphics, List<ZlbOrderInfo.ZlbData.TrackList> generatedTrackList) {
|
||||||
|
if (generatedTrackList == null || generatedTrackList.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
graphics.setFont(new Font("SansSerif", Font.PLAIN, 12));
|
||||||
|
graphics.setStroke(new BasicStroke(2.5f));
|
||||||
|
for (int i = 0; i < generatedTrackList.size(); i++) {
|
||||||
|
ZlbOrderInfo.ZlbData.TrackList current = generatedTrackList.get(i);
|
||||||
|
if (i > 0) {
|
||||||
|
ZlbOrderInfo.ZlbData.TrackList previous = generatedTrackList.get(i - 1);
|
||||||
|
graphics.setColor(new Color(255, 99, 71, 180));
|
||||||
|
graphics.drawLine(previous.getX(), previous.getY(), current.getX(), current.getY());
|
||||||
|
}
|
||||||
|
Color pointColor = "click".equalsIgnoreCase(current.getType())
|
||||||
|
? new Color(220, 20, 60, 220)
|
||||||
|
: new Color(255, 165, 0, 220);
|
||||||
|
graphics.setColor(pointColor);
|
||||||
|
graphics.fillOval(current.getX() - 5, current.getY() - 5, 10, 10);
|
||||||
|
graphics.setColor(Color.WHITE);
|
||||||
|
graphics.drawOval(current.getX() - 5, current.getY() - 5, 10, 10);
|
||||||
|
// graphics.drawString(current.getType() + "(" + current.getT() + ")", current.getX() + 6, current.getY() + 16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void drawLegend(Graphics2D graphics) {
|
||||||
|
int boxX = 12;
|
||||||
|
int boxY = 12;
|
||||||
|
int boxWidth = 250;
|
||||||
|
int boxHeight = 62;
|
||||||
|
// graphics.setColor(new Color(0, 0, 0, 135));
|
||||||
|
// graphics.fillRoundRect(boxX, boxY, boxWidth, boxHeight, 12, 12);
|
||||||
|
// graphics.setColor(Color.WHITE);
|
||||||
|
// graphics.setFont(new Font("SansSerif", Font.BOLD, 13));
|
||||||
|
// graphics.drawString("Blue: raw points", boxX + 12, boxY + 22);
|
||||||
|
// graphics.drawString("Red: click track", boxX + 12, boxY + 40);
|
||||||
|
// graphics.drawString("Orange: move track", boxX + 12, boxY + 58);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String sanitizeFileName(String value) {
|
||||||
|
if (value == null || value.trim().isEmpty()) {
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
return value.replaceAll("[^a-zA-Z0-9_-]", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class DebugResult {
|
||||||
|
private String consolePreview;
|
||||||
|
private String debugImagePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public static class ExecutionResult {
|
||||||
|
private List<ZlbOrderInfo.ZlbData.TrackList> trackList;
|
||||||
|
private String consolePreview;
|
||||||
|
private String debugImagePath;
|
||||||
|
|
||||||
|
public String getTrackListJson() {
|
||||||
|
return com.alibaba.fastjson2.JSON.toJSONString(trackList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
120
src/main/java/com/xiang/common/utils/ZlbCaptchaTrackUtil.java
Normal file
120
src/main/java/com/xiang/common/utils/ZlbCaptchaTrackUtil.java
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
package com.xiang.common.utils;
|
||||||
|
|
||||||
|
import com.xiang.common.pojo.jntyzx.zlb.ZlbOrderInfo;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 浙里办验证码轨迹生成工具。
|
||||||
|
*/
|
||||||
|
public class ZlbCaptchaTrackUtil {
|
||||||
|
|
||||||
|
private ZlbCaptchaTrackUtil() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<ZlbOrderInfo.ZlbData.TrackList> generateBezierTrackList(List<String> trackList) {
|
||||||
|
List<ZlbOrderInfo.ZlbData.TrackList> result = new ArrayList<>();
|
||||||
|
if (trackList == null || trackList.isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < trackList.size(); i++) {
|
||||||
|
int[] currentPoint = parseTrackCoordinate(trackList.get(i));
|
||||||
|
int clickTime = result.isEmpty()
|
||||||
|
? randomBetween(1800, 2200)
|
||||||
|
: result.get(result.size() - 1).getT() + randomBetween(25, 60);
|
||||||
|
result.add(buildTrackPoint(currentPoint[0], currentPoint[1], clickTime, "click"));
|
||||||
|
|
||||||
|
if (i < trackList.size() - 1) {
|
||||||
|
int[] nextPoint = parseTrackCoordinate(trackList.get(i + 1));
|
||||||
|
int moveStartTime = result.get(result.size() - 1).getT();
|
||||||
|
int moveDuration = randomBetween(800, 1200);
|
||||||
|
List<int[]> movePoints = buildBezierMovePoints(currentPoint[0], currentPoint[1], nextPoint[0], nextPoint[1]);
|
||||||
|
// for (int moveIndex = 0; moveIndex < movePoints.size(); moveIndex++) {
|
||||||
|
// int[] movePoint = movePoints.get(moveIndex);
|
||||||
|
// int moveTime = moveStartTime + (int) Math.round((double) moveDuration * (moveIndex + 1) / (movePoints.size() + 1));
|
||||||
|
// result.add(buildTrackPoint(movePoint[0], movePoint[1], moveTime, "move"));
|
||||||
|
// }
|
||||||
|
int[] movePoint = movePoints.get(movePoints.size() / 2);
|
||||||
|
int moveTime = moveStartTime + randomBetween(800, 1200);
|
||||||
|
result.add(buildTrackPoint(movePoint[0], movePoint[1], moveTime, "move"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static List<String> parseCoordinateText(String coordinateText) {
|
||||||
|
if (coordinateText == null || coordinateText.trim().isEmpty()) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
String normalized = coordinateText.replace("\n", "|").replace(";", "|");
|
||||||
|
String[] parts = normalized.split("\\|");
|
||||||
|
List<String> result = new ArrayList<>();
|
||||||
|
for (String part : parts) {
|
||||||
|
if (part != null && !part.trim().isEmpty()) {
|
||||||
|
result.add(part.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int[] parseTrackCoordinate(String track) {
|
||||||
|
String[] coordinateArr = track.split(",");
|
||||||
|
return new int[]{
|
||||||
|
Integer.parseInt(coordinateArr[0].trim()),
|
||||||
|
Integer.parseInt(coordinateArr[1].trim())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<int[]> buildBezierMovePoints(int fromX, int fromY, int toX, int toY) {
|
||||||
|
int movePointCount = randomBetween(3, 5);
|
||||||
|
double dx = toX - fromX;
|
||||||
|
double dy = toY - fromY;
|
||||||
|
double distance = Math.max(1d, Math.hypot(dx, dy));
|
||||||
|
double normalX = -dy / distance;
|
||||||
|
double normalY = dx / distance;
|
||||||
|
double direction = ThreadLocalRandom.current().nextBoolean() ? 1d : -1d;
|
||||||
|
double offset = Math.min(28d, Math.max(6d, distance * ThreadLocalRandom.current().nextDouble(0.08d, 0.22d))) * direction;
|
||||||
|
double controlX = (fromX + toX) / 2d + normalX * offset;
|
||||||
|
double controlY = (fromY + toY) / 2d + normalY * offset;
|
||||||
|
|
||||||
|
List<int[]> movePoints = new ArrayList<>(movePointCount);
|
||||||
|
for (int i = 1; i <= movePointCount; i++) {
|
||||||
|
double progress = easeInOutCubic((double) i / (movePointCount + 1));
|
||||||
|
int x = (int) Math.round(
|
||||||
|
Math.pow(1 - progress, 2) * fromX
|
||||||
|
+ 2 * (1 - progress) * progress * controlX
|
||||||
|
+ Math.pow(progress, 2) * toX
|
||||||
|
);
|
||||||
|
int y = (int) Math.round(
|
||||||
|
Math.pow(1 - progress, 2) * fromY
|
||||||
|
+ 2 * (1 - progress) * progress * controlY
|
||||||
|
+ Math.pow(progress, 2) * toY
|
||||||
|
);
|
||||||
|
movePoints.add(new int[]{x, y});
|
||||||
|
}
|
||||||
|
return movePoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double easeInOutCubic(double value) {
|
||||||
|
return value < 0.5d
|
||||||
|
? 4d * value * value * value
|
||||||
|
: 1d - Math.pow(-2d * value + 2d, 3d) / 2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ZlbOrderInfo.ZlbData.TrackList buildTrackPoint(int x, int y, int t, String type) {
|
||||||
|
ZlbOrderInfo.ZlbData.TrackList data = new ZlbOrderInfo.ZlbData.TrackList();
|
||||||
|
data.setX(x);
|
||||||
|
data.setY(y);
|
||||||
|
data.setT(t);
|
||||||
|
data.setType(type);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int randomBetween(int minInclusive, int maxInclusive) {
|
||||||
|
return ThreadLocalRandom.current().nextInt(minInclusive, maxInclusive + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -40,6 +40,7 @@ public class ZlbUrlConstants {
|
|||||||
|
|
||||||
|
|
||||||
public static final String siteStr = "{\"stadiumId\":\"49\",\"siteItemId\":1940,\"belongDate\":\"%s\",\"channelType\":6}";
|
public static final String siteStr = "{\"stadiumId\":\"49\",\"siteItemId\":1940,\"belongDate\":\"%s\",\"channelType\":6}";
|
||||||
|
public static final String siteQMStr = "{\"stadiumId\":\"185\",\"siteItemId\":194,\"belongDate\":\"%s\",\"channelType\":6}";
|
||||||
public static final String siteWqStr = "{\"stadiumId\":\"360112\",\"siteItemId\":1148,\"belongDate\":\"%s\",\"channelType\":6}";
|
public static final String siteWqStr = "{\"stadiumId\":\"360112\",\"siteItemId\":1148,\"belongDate\":\"%s\",\"channelType\":6}";
|
||||||
public static final String refundStr = "{\"detailOrderId\":%s,\"detailOrderIds\":[%s],\"type\":2}";
|
public static final String refundStr = "{\"detailOrderId\":%s,\"detailOrderIds\":[%s],\"type\":2}";
|
||||||
public static final String cancelStr = "{\"orderId\":%s,\"type\":2}";
|
public static final String cancelStr = "{\"orderId\":%s,\"type\":2}";
|
||||||
|
|||||||
@@ -2,8 +2,10 @@ package com.xiang.service.module.jntyzx.zlb.schedule;
|
|||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.xiang.common.enums.ScheduleEnums;
|
import com.xiang.common.enums.ScheduleEnums;
|
||||||
|
import com.xiang.common.exception.BusinessException;
|
||||||
import com.xiang.common.factory.JntyzxDingTalkFactory;
|
import com.xiang.common.factory.JntyzxDingTalkFactory;
|
||||||
import com.xiang.common.factory.schedule.BaseScheduleTaskTemplate;
|
import com.xiang.common.factory.schedule.BaseScheduleTaskTemplate;
|
||||||
import com.xiang.common.pojo.jntyzx.zlb.ZlbTokenInfo;
|
import com.xiang.common.pojo.jntyzx.zlb.ZlbTokenInfo;
|
||||||
@@ -18,12 +20,9 @@ import com.xiang.service.module.jntyzx.zlb.service.ZlbService;
|
|||||||
import com.xiang.service.module.jntyzx.zlb.service.ZlbTokenInfoService;
|
import com.xiang.service.module.jntyzx.zlb.service.ZlbTokenInfoService;
|
||||||
import com.xiang.service.module.jntyzx.zlb.service.ZlbUserInfoService;
|
import com.xiang.service.module.jntyzx.zlb.service.ZlbUserInfoService;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
@@ -80,7 +79,7 @@ public class ZlbOrderTask extends BaseScheduleTaskTemplate {
|
|||||||
LambdaQueryWrapper<ZlbUserInfo> wrapper = Wrappers.lambdaQuery();
|
LambdaQueryWrapper<ZlbUserInfo> wrapper = Wrappers.lambdaQuery();
|
||||||
wrapper.eq(ZlbUserInfo::getIsBook, 0);
|
wrapper.eq(ZlbUserInfo::getIsBook, 0);
|
||||||
wrapper.eq(ZlbUserInfo::getName, "xiang");
|
wrapper.eq(ZlbUserInfo::getName, "xiang");
|
||||||
Date date = DateUtils.addDate(new Date(), 0);
|
Date date = DateUtils.addDate(new Date(), 1);
|
||||||
String day = DateUtils.format(date, DateUtils.ENUM_FORMAT_YMD);
|
String day = DateUtils.format(date, DateUtils.ENUM_FORMAT_YMD);
|
||||||
wrapper.eq(ZlbUserInfo::getWeek, DateUtils.getWeekDayTwo(day));
|
wrapper.eq(ZlbUserInfo::getWeek, DateUtils.getWeekDayTwo(day));
|
||||||
ZlbUserInfo zlbUserInfo = zlbUserInfoService.getOne(wrapper);
|
ZlbUserInfo zlbUserInfo = zlbUserInfoService.getOne(wrapper);
|
||||||
@@ -101,6 +100,10 @@ public class ZlbOrderTask extends BaseScheduleTaskTemplate {
|
|||||||
String siteOrderDetailsStr = zlbService.buildSiteOrder(zlbUserInfo, secretKey, day);
|
String siteOrderDetailsStr = zlbService.buildSiteOrder(zlbUserInfo, secretKey, day);
|
||||||
Map<String, String> headers = zlbService.getHeaders(zlbTokenInfo.getTokenId());
|
Map<String, String> headers = zlbService.getHeaders(zlbTokenInfo.getTokenId());
|
||||||
String newOrderJson = zlbService.buildNewOrder(siteOrderDetailsStr, client);
|
String newOrderJson = zlbService.buildNewOrder(siteOrderDetailsStr, client);
|
||||||
|
if (StringUtils.isBlank(newOrderJson)) {
|
||||||
|
log.info("构建订单参数异常:{}", siteOrderDetailsStr);
|
||||||
|
throw new BusinessException("");
|
||||||
|
}
|
||||||
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
|
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
|
||||||
//计算9点到现在的时间差
|
//计算9点到现在的时间差
|
||||||
//获取江体当前时间
|
//获取江体当前时间
|
||||||
@@ -108,7 +111,7 @@ public class ZlbOrderTask extends BaseScheduleTaskTemplate {
|
|||||||
LocalTime targetTime = LocalTime.parse("09:00:11.800");
|
LocalTime targetTime = LocalTime.parse("09:00:11.800");
|
||||||
Duration duration = Duration.between(currentTime, targetTime);
|
Duration duration = Duration.between(currentTime, targetTime);
|
||||||
long milliseconds = duration.toMillis();
|
long milliseconds = duration.toMillis();
|
||||||
// executorService.schedule(() -> {
|
executorService.schedule(() -> {
|
||||||
String response = null;
|
String response = null;
|
||||||
try {
|
try {
|
||||||
response = client.postJson(ZlbUrlConstants.newOrderUrl, headers, newOrderJson);
|
response = client.postJson(ZlbUrlConstants.newOrderUrl, headers, newOrderJson);
|
||||||
@@ -116,7 +119,7 @@ public class ZlbOrderTask extends BaseScheduleTaskTemplate {
|
|||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
buildOrder(name, response, placeName, siteTimeName);
|
buildOrder(name, response, placeName, siteTimeName);
|
||||||
// }, milliseconds, TimeUnit.MILLISECONDS);
|
}, milliseconds, TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
return taskResult;
|
return taskResult;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import com.xiang.service.module.jntyzx.zlb.service.ZlbService;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@@ -44,9 +43,9 @@ public class ZlbSiteTask extends BaseScheduleTaskTemplate {
|
|||||||
|
|
||||||
TaskResult taskResult = new TaskResult();
|
TaskResult taskResult = new TaskResult();
|
||||||
//获取当前时间的后一天
|
//获取当前时间的后一天
|
||||||
Date date1 = DateUtils.addDate(new Date(), 2);
|
Date date1 = DateUtils.addDate(new Date(), 0);
|
||||||
Date date2 = DateUtils.addDate(new Date(), 3);
|
Date date2 = DateUtils.addDate(new Date(), 1);
|
||||||
Date date3 = DateUtils.addDate(new Date(), 4);
|
Date date3 = DateUtils.addDate(new Date(), 2);
|
||||||
String day1 = DateUtils.format(date1, DateUtils.ENUM_FORMAT_YMD);
|
String day1 = DateUtils.format(date1, DateUtils.ENUM_FORMAT_YMD);
|
||||||
String day2 = DateUtils.format(date2, DateUtils.ENUM_FORMAT_YMD);
|
String day2 = DateUtils.format(date2, DateUtils.ENUM_FORMAT_YMD);
|
||||||
String day3 = DateUtils.format(date3, DateUtils.ENUM_FORMAT_YMD);
|
String day3 = DateUtils.format(date3, DateUtils.ENUM_FORMAT_YMD);
|
||||||
|
|||||||
@@ -30,16 +30,19 @@ public class ZlbTaskConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/zlbSiteTask")
|
@GetMapping("/zlbSiteTask")
|
||||||
|
@Scheduled(cron = "30 30 16 * * ?")
|
||||||
public void zlbSiteTask() {
|
public void zlbSiteTask() {
|
||||||
zlbSiteTask.run();
|
zlbSiteTask.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/zlbSiteDayTask")
|
@GetMapping("/zlbSiteDayTask")
|
||||||
|
@Scheduled(cron = "0 00 17 * * ?")
|
||||||
public void zlbSiteDayTask() {
|
public void zlbSiteDayTask() {
|
||||||
zlbSiteDayTask.run();
|
zlbSiteDayTask.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/zlbOrderCreateTask")
|
@GetMapping("/zlbOrderCreateTask")
|
||||||
|
@Scheduled(cron = "55 59 8 * * ?")
|
||||||
public void zlbOrderCreateTask() {
|
public void zlbOrderCreateTask() {
|
||||||
zlbOrderTask.run();
|
zlbOrderTask.run();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.xiang.service.module.jntyzx.zlb.service;
|
package com.xiang.service.module.jntyzx.zlb.service;
|
||||||
|
|
||||||
import cn.hutool.http.HttpRequest;
|
|
||||||
import com.alibaba.fastjson.serializer.SerializerFeature;
|
import com.alibaba.fastjson.serializer.SerializerFeature;
|
||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.alibaba.fastjson2.JSONArray;
|
import com.alibaba.fastjson2.JSONArray;
|
||||||
@@ -9,23 +8,40 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|||||||
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
|
||||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||||
import com.google.common.collect.Lists;
|
|
||||||
import com.xiang.common.factory.JntyzxDingTalkFactory;
|
import com.xiang.common.factory.JntyzxDingTalkFactory;
|
||||||
import com.xiang.common.pojo.jntyzx.zlb.*;
|
import com.xiang.common.pojo.jntyzx.zlb.ZlbCaptchaResp;
|
||||||
|
import com.xiang.common.pojo.jntyzx.zlb.ZlbOrderInfo;
|
||||||
|
import com.xiang.common.pojo.jntyzx.zlb.ZlbOrderJson;
|
||||||
|
import com.xiang.common.pojo.jntyzx.zlb.ZlbOrderWqInfo;
|
||||||
|
import com.xiang.common.pojo.jntyzx.zlb.ZlbOrderWqJson;
|
||||||
|
import com.xiang.common.pojo.jntyzx.zlb.ZlbSiteInfo;
|
||||||
|
import com.xiang.common.pojo.jntyzx.zlb.ZlbSiteRequest;
|
||||||
|
import com.xiang.common.pojo.jntyzx.zlb.ZlbTokenInfo;
|
||||||
|
import com.xiang.common.pojo.jntyzx.zlb.ZlbUserInfo;
|
||||||
import com.xiang.common.service.ICodeService;
|
import com.xiang.common.service.ICodeService;
|
||||||
import com.xiang.common.utils.AESECBUtils;
|
import com.xiang.common.utils.AESECBUtils;
|
||||||
|
import com.xiang.common.utils.Base64ImageScaler;
|
||||||
import com.xiang.common.utils.DateUtils;
|
import com.xiang.common.utils.DateUtils;
|
||||||
import com.xiang.common.utils.OkHttpUtil;
|
import com.xiang.common.utils.OkHttpUtil;
|
||||||
|
import com.xiang.common.utils.ZlbCaptchaTrackUtil;
|
||||||
import com.xiang.service.module.jntyzx.zlb.constants.ZlbUrlConstants;
|
import com.xiang.service.module.jntyzx.zlb.constants.ZlbUrlConstants;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.*;
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -35,6 +51,7 @@ import java.util.stream.Collectors;
|
|||||||
public class ZlbServiceImpl implements ZlbService {
|
public class ZlbServiceImpl implements ZlbService {
|
||||||
|
|
||||||
|
|
||||||
|
public static volatile boolean running = true;
|
||||||
@Resource
|
@Resource
|
||||||
private ZlbSiteInfoService zlbSiteInfoService;
|
private ZlbSiteInfoService zlbSiteInfoService;
|
||||||
@Resource
|
@Resource
|
||||||
@@ -48,7 +65,6 @@ public class ZlbServiceImpl implements ZlbService {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private ICodeService codeService;
|
private ICodeService codeService;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queryZLbSiteInfo(String ymdDate, Integer type) throws Exception {
|
public void queryZLbSiteInfo(String ymdDate, Integer type) throws Exception {
|
||||||
log.info("开始查询场地信息");
|
log.info("开始查询场地信息");
|
||||||
@@ -361,23 +377,45 @@ public class ZlbServiceImpl implements ZlbService {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String buildNewOrder(String siteOrderDetailsStr, OkHttpUtil client) throws IOException {
|
public String buildNewOrder(String siteOrderDetailsStr, OkHttpUtil client) throws IOException {
|
||||||
|
Instant startTime = Instant.now().truncatedTo(ChronoUnit.MILLIS);
|
||||||
|
|
||||||
//获取图片验证码
|
//获取图片验证码
|
||||||
String s = client.postJson(ZlbUrlConstants.captchaUrl, null, "{}");
|
String s = client.postJson(ZlbUrlConstants.captchaUrl, null, "{}");
|
||||||
JSONObject jsonObject = JSON.parseObject(s);
|
try {
|
||||||
String id = (String) jsonObject.get("id");
|
Thread.sleep(2000);
|
||||||
String captcha = JSON.toJSONString(jsonObject.get("captcha"));
|
} catch (InterruptedException e) {
|
||||||
String backgroundImage = JSON.toJSONString(JSON.parseObject(captcha).get("backgroundImage"));
|
e.printStackTrace();
|
||||||
String templateImage = JSON.toJSONString(JSON.parseObject(captcha).get("templateImage"));
|
}
|
||||||
|
ZlbCaptchaResp zlbCaptchaResp = JSON.parseObject(s, ZlbCaptchaResp.class);
|
||||||
|
if (Objects.isNull(zlbCaptchaResp)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (Objects.isNull(zlbCaptchaResp.getCaptcha())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String id = zlbCaptchaResp.getId();
|
||||||
|
String backgroundImage = zlbCaptchaResp.getCaptcha().getBackgroundImage();
|
||||||
|
String templateImage = zlbCaptchaResp.getCaptcha().getTemplateImage();
|
||||||
|
|
||||||
|
backgroundImage = Base64ImageScaler.scaleToHalf(backgroundImage);
|
||||||
List<String> trackList = codeService.codeResolve(backgroundImage, templateImage);
|
List<String> trackList = codeService.codeResolve(backgroundImage, templateImage);
|
||||||
|
|
||||||
ZlbOrderInfo orderInfo = new ZlbOrderInfo();
|
ZlbOrderInfo orderInfo = new ZlbOrderInfo();
|
||||||
orderInfo.setId(id);
|
orderInfo.setId(id);
|
||||||
//获取验证码轨迹
|
//获取验证码轨迹
|
||||||
List<ZlbOrderInfo.ZlbData.TrackList> trackListList = convert(trackList);
|
List<ZlbOrderInfo.ZlbData.TrackList> trackListList = convert(trackList);
|
||||||
|
|
||||||
|
// ZlbCaptchaTrackDebugger.ExecutionResult execute = ZlbCaptchaTrackDebugger.execute(backgroundImage, trackList, "zlb-captcha-debugger");
|
||||||
|
// List<ZlbOrderInfo.ZlbData.TrackList> trackListList = execute.getTrackList();
|
||||||
|
|
||||||
|
|
||||||
ZlbOrderInfo.ZlbData data = new ZlbOrderInfo.ZlbData();
|
ZlbOrderInfo.ZlbData data = new ZlbOrderInfo.ZlbData();
|
||||||
// data.setBgImageWidth();
|
data.setBgImageWidth(zlbCaptchaResp.getCaptcha().getBackgroundImageWidth() / 2);
|
||||||
// data.setBgImageHeight();
|
data.setBgImageHeight(zlbCaptchaResp.getCaptcha().getBackgroundImageHeight() / 2);
|
||||||
// data.setStartTime();
|
data.setStartTime(startTime.toString());
|
||||||
// data.setStopTime();
|
|
||||||
|
Integer t = trackListList.get(trackListList.size() - 1).getT();
|
||||||
|
data.setStopTime(startTime.plus(t, ChronoUnit.MILLIS).truncatedTo(ChronoUnit.MILLIS).toString());
|
||||||
data.setTrackList(trackListList);
|
data.setTrackList(trackListList);
|
||||||
|
|
||||||
orderInfo.setData(data);
|
orderInfo.setData(data);
|
||||||
@@ -388,27 +426,8 @@ public class ZlbServiceImpl implements ZlbService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<ZlbOrderInfo.ZlbData.TrackList> convert(List<String> trackList) {
|
private List<ZlbOrderInfo.ZlbData.TrackList> convert(List<String> trackList) {
|
||||||
List<ZlbOrderInfo.ZlbData.TrackList> result = Lists.newArrayList();
|
List<ZlbOrderInfo.ZlbData.TrackList> result = ZlbCaptchaTrackUtil.generateBezierTrackList(trackList);
|
||||||
|
log.info("生成的轨迹点结果:{}", JSON.toJSONString(result));
|
||||||
for (String track : trackList) {
|
|
||||||
String[] split = track.split(",");
|
|
||||||
String x = split[0];
|
|
||||||
String y = split[1];
|
|
||||||
ZlbOrderInfo.ZlbData.TrackList data = new ZlbOrderInfo.ZlbData.TrackList();
|
|
||||||
data.setX(Integer.valueOf(x));
|
|
||||||
data.setY(Integer.valueOf(y));
|
|
||||||
data.setT(1);
|
|
||||||
data.setType("click");
|
|
||||||
result.add(data);
|
|
||||||
|
|
||||||
ZlbOrderInfo.ZlbData.TrackList move = new ZlbOrderInfo.ZlbData.TrackList();
|
|
||||||
move.setX(Integer.valueOf(x));
|
|
||||||
move.setY(Integer.valueOf(y));
|
|
||||||
move.setT(1);
|
|
||||||
move.setType("move");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -431,8 +450,6 @@ public class ZlbServiceImpl implements ZlbService {
|
|||||||
redisTemplate.expire(redisKey, 1234, TimeUnit.SECONDS);
|
redisTemplate.expire(redisKey, 1234, TimeUnit.SECONDS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static volatile boolean running = true;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void jianlou(String name, String day, long time) throws Exception {
|
public void jianlou(String name, String day, long time) throws Exception {
|
||||||
jntyzxDingTalkFactory.sendMsg(name + "自定义捡漏开始捡漏时间:" + day + " 捡漏人:" + name + " 捡漏间隔:" + time + "ms");
|
jntyzxDingTalkFactory.sendMsg(name + "自定义捡漏开始捡漏时间:" + day + " 捡漏人:" + name + " 捡漏间隔:" + time + "ms");
|
||||||
@@ -641,5 +658,3 @@ public class ZlbServiceImpl implements ZlbService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1
src/main/resources/image.txt
Normal file
1
src/main/resources/image.txt
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user