钱德旺系统适配
This commit is contained in:
898
src/test/java/com/qdw/buyerTest.java
Normal file
898
src/test/java/com/qdw/buyerTest.java
Normal file
@@ -0,0 +1,898 @@
|
||||
package com.qdw;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.http.HttpClient;
|
||||
import java.net.http.HttpRequest;
|
||||
import java.net.http.HttpResponse;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 企得物(QDW)卖家后台 - 登录 + 货物列表获取
|
||||
*
|
||||
* 流程:
|
||||
* 1. POST /api/user/login 登录获取Token
|
||||
* 2. GET /api/home/on-sell-orders 获取在售货物列表
|
||||
*/
|
||||
public class buyerTest {
|
||||
|
||||
private static final String LOGIN_URL = "https://web.qdwsmgs.com/api/user/login";
|
||||
private static final String GOODS_LIST_URL = "https://web.qdwsmgs.com/api/home/on-sell-orders";
|
||||
private static final String CLAIM_ORDER_URL = "https://web.qdwsmgs.com/api/order/%d/claim";
|
||||
private static final String PHONE = "15313852029";
|
||||
private static final String PASSWORD = "123456";
|
||||
private static final int MAX_PRICE = 38000;
|
||||
private static final int MAX_BUY_NUM = 2;
|
||||
|
||||
private static final int PAGE_SIZE = 20;
|
||||
|
||||
/**
|
||||
* 主流程:登录 → 分页获取所有货物 → 按价格排序 → 输出
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
try {
|
||||
System.out.println("============================================");
|
||||
System.out.println(" 企得物卖家后台 - 登录 & 全量货物列表(按价格排序)");
|
||||
System.out.println("============================================");
|
||||
|
||||
// 步骤1: 登录
|
||||
System.out.println("\n[1] 正在登录...");
|
||||
LoginResult loginResult = doLogin(PHONE, PASSWORD, true);
|
||||
|
||||
if (!loginResult.isSuccess()) {
|
||||
System.out.println("登录失败: " + loginResult.getMessage());
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("登录成功! 用户: " + loginResult.getNickname()
|
||||
+ " (ID: " + loginResult.getUserId() + ")");
|
||||
|
||||
String token = loginResult.getToken();
|
||||
|
||||
// 步骤2: 分页获取所有货物
|
||||
System.out.println("\n[2] 正在分页获取全部货物...");
|
||||
List<GoodsItem> allGoods = fetchAllGoods(token);
|
||||
|
||||
if (allGoods.isEmpty()) {
|
||||
System.out.println("未获取到任何货物数据");
|
||||
return;
|
||||
}
|
||||
|
||||
System.out.println("\n共获取 " + allGoods.size() + " 条货物记录");
|
||||
|
||||
// 步骤3: 按价格倒序排序
|
||||
System.out.println("\n[3] 按价格倒序排序...");
|
||||
Collections.sort(allGoods, Comparator.comparingDouble(GoodsItem::getPrice).reversed());
|
||||
|
||||
// 步骤4: 输出排序后的列表
|
||||
System.out.println("\n[4] 按价格排序后的货物列表:");
|
||||
System.out.println("──────────────────────────────────────────────────────────────────────────────");
|
||||
System.out.printf("%-6s %-8s %-10s %-12s %-30s %-12s%n",
|
||||
"序号", "ID", "价格", "商品原价", "商品标题", "创建时间");
|
||||
System.out.println("──────────────────────────────────────────────────────────────────────────────");
|
||||
|
||||
int index = 1;
|
||||
for (GoodsItem g : allGoods) {
|
||||
System.out.printf("%-6d %-8d %-10.2f %-12s %-30s %-12s%n",
|
||||
index++,
|
||||
g.getId(),
|
||||
g.getPrice(),
|
||||
g.getProductPriceStr(),
|
||||
truncate(g.getProductTitle(), 28),
|
||||
g.getCreatedAt());
|
||||
}
|
||||
|
||||
System.out.println("──────────────────────────────────────────────────────────────────────────────");
|
||||
System.out.println("总计: " + allGoods.size() + " 条记录");
|
||||
|
||||
// 统计信息
|
||||
double minPrice = allGoods.get(0).getPrice();
|
||||
double maxPrice = allGoods.get(allGoods.size() - 1).getPrice();
|
||||
double sum = 0;
|
||||
for (GoodsItem g : allGoods) sum += g.getPrice();
|
||||
double avgPrice = sum / allGoods.size();
|
||||
|
||||
System.out.println("\n价格统计:");
|
||||
System.out.printf(" 最低价: %.2f%n", minPrice);
|
||||
System.out.printf(" 最高价: %.2f%n", maxPrice);
|
||||
System.out.printf(" 平均价: %.2f%n", avgPrice);
|
||||
System.out.printf(" 总价: %.2f%n", sum);
|
||||
|
||||
// 步骤5: 筛选价格 < MAX_PRICE 的货物,按价格升序,依次下单(最多 MAX_BUY_NUM 单)
|
||||
System.out.println("\n[5] 筛选价格 < " + MAX_PRICE + " 的货物并尝试下单(最多" + MAX_BUY_NUM + "单)...");
|
||||
List<GoodsItem> buyCandidates = new ArrayList<>();
|
||||
for (GoodsItem g : allGoods) {
|
||||
if (g.getPrice() < MAX_PRICE) {
|
||||
buyCandidates.add(g);
|
||||
}
|
||||
}
|
||||
// 按价格从大到小排序,高价优先
|
||||
buyCandidates.sort(Comparator.comparingDouble(GoodsItem::getPrice).reversed());
|
||||
System.out.println(" 符合条件货物数: " + buyCandidates.size());
|
||||
|
||||
int boughtCount = 0;
|
||||
for (GoodsItem target : buyCandidates) {
|
||||
if (boughtCount >= MAX_BUY_NUM) {
|
||||
System.out.println("\n 已达最大购买数量 " + MAX_BUY_NUM + ",停止下单");
|
||||
break;
|
||||
}
|
||||
System.out.println("\n 第 " + (boughtCount + 1) + " 单 → ID=" + target.getId()
|
||||
+ " 价格=" + String.format("%.2f", target.getPrice())
|
||||
+ " 标题=" + truncate(target.getProductTitle(), 24));
|
||||
|
||||
ClaimResult claim = claimOrder(token, target.getId(), true);
|
||||
System.out.println(" 下单结果: " + claim);
|
||||
|
||||
if (claim.isSuccess()) {
|
||||
boughtCount++;
|
||||
System.out.println(" 下单成功! 已购买 " + boughtCount + "/" + MAX_BUY_NUM);
|
||||
} else if ("3004".equals(claim.getCode())) {
|
||||
System.out.println("\n 业务侧返回 code=3004 (活动已结束),结束程序");
|
||||
return;
|
||||
} else {
|
||||
System.out.println(" 下单失败,尝试下一个");
|
||||
}
|
||||
}
|
||||
System.out.println("\n[完成] 共成功下单 " + boughtCount + " 单");
|
||||
|
||||
} catch (Exception e) {
|
||||
System.err.println("执行异常: " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页获取所有货物,返回全部记录
|
||||
*/
|
||||
private static List<GoodsItem> fetchAllGoods(String token) throws Exception {
|
||||
List<GoodsItem> allGoods = new ArrayList<>();
|
||||
int page = 1;
|
||||
int total = 0;
|
||||
|
||||
while (true) {
|
||||
System.out.printf(" 正在获取第 %d 页 (pageSize=%d)...", page, PAGE_SIZE);
|
||||
String json = fetchGoodsList(token, page, PAGE_SIZE, true);
|
||||
|
||||
if (json == null || json.isEmpty()) {
|
||||
System.out.println(" 失败(无响应)");
|
||||
break;
|
||||
}
|
||||
|
||||
// 解析本页数据
|
||||
String dataJson = extractObject(json, "\"data\":");
|
||||
if (dataJson == null) {
|
||||
System.out.println(" 失败(无data)");
|
||||
break;
|
||||
}
|
||||
|
||||
// 第一页时读取 total
|
||||
if (page == 1) {
|
||||
String totalStr = extractValue(dataJson, "\"total\":", ",");
|
||||
if (totalStr != null) {
|
||||
try { total = Integer.parseInt(totalStr); } catch (NumberFormatException ignored) {}
|
||||
}
|
||||
System.out.println(" 总记录数=" + total);
|
||||
} else {
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
// 解析 records 数组
|
||||
String listJson = extractArray(dataJson, "\"records\":");
|
||||
if (listJson == null) {
|
||||
listJson = extractArray(dataJson, "\"list\":");
|
||||
}
|
||||
if (listJson == null) {
|
||||
System.out.println(" 第" + page + "页未找到记录数组");
|
||||
break;
|
||||
}
|
||||
|
||||
List<String> items = splitArrayElements(listJson);
|
||||
for (String item : items) {
|
||||
GoodsItem goods = parseGoodsItem(item);
|
||||
if (goods != null) {
|
||||
allGoods.add(goods);
|
||||
}
|
||||
}
|
||||
|
||||
// 判断是否还有下一页
|
||||
if (items.isEmpty() || allGoods.size() >= total) {
|
||||
break;
|
||||
}
|
||||
page++;
|
||||
}
|
||||
|
||||
return allGoods;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析单条货物为 GoodsItem 对象
|
||||
*/
|
||||
private static GoodsItem parseGoodsItem(String json) {
|
||||
GoodsItem g = new GoodsItem();
|
||||
List<String[]> fields = parseAllFields(json);
|
||||
for (String[] f : fields) {
|
||||
String key = f[0];
|
||||
String val = f[1];
|
||||
switch (key) {
|
||||
case "id":
|
||||
try { g.setId(Integer.parseInt(val)); } catch (NumberFormatException ignored) {}
|
||||
break;
|
||||
case "sellerId":
|
||||
try { g.setSellerId(Integer.parseInt(val)); } catch (NumberFormatException ignored) {}
|
||||
break;
|
||||
case "productId":
|
||||
try { g.setProductId(Integer.parseInt(val)); } catch (NumberFormatException ignored) {}
|
||||
break;
|
||||
case "productTitle": g.setProductTitle(val); break;
|
||||
case "productThumbnail": g.setProductThumbnail(val); break;
|
||||
case "price": g.setPrice(parseDouble(val)); break;
|
||||
case "productPrice": g.setProductPriceStr(val); break;
|
||||
case "isDisplay":
|
||||
try { g.setDisplay(Integer.parseInt(val)); } catch (NumberFormatException ignored) {}
|
||||
break;
|
||||
case "status":
|
||||
try { g.setStatus(Integer.parseInt(val)); } catch (NumberFormatException ignored) {}
|
||||
break;
|
||||
case "createdAt": g.setCreatedAt(val); break;
|
||||
case "updatedAt": g.setUpdatedAt(val); break;
|
||||
}
|
||||
}
|
||||
return g;
|
||||
}
|
||||
|
||||
private static String truncate(String s, int max) {
|
||||
if (s == null) return "";
|
||||
return s.length() > max ? s.substring(0, max) + "…" : s;
|
||||
}
|
||||
|
||||
// ==================== 登录相关 ====================
|
||||
|
||||
/**
|
||||
* 执行登录请求
|
||||
*/
|
||||
public static LoginResult doLogin(String phone, String password, boolean rememberMe) throws Exception {
|
||||
HttpClient client = buildHttpClient();
|
||||
|
||||
String body = String.format("{\"phone\":\"%s\",\"password\":\"%s\",\"rememberMe\":%b}",
|
||||
phone, password, rememberMe);
|
||||
|
||||
HttpRequest request = HttpRequest.newBuilder()
|
||||
.uri(URI.create(LOGIN_URL))
|
||||
.timeout(java.time.Duration.ofSeconds(60))
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Accept", "application/json, text/plain, */*")
|
||||
.header("Origin", "https://web.qdwsmgs.com")
|
||||
.header("Referer", "https://web.qdwsmgs.com/login")
|
||||
.header("User-Agent", buildUserAgent())
|
||||
.POST(HttpRequest.BodyPublishers.ofString(body, StandardCharsets.UTF_8))
|
||||
.build();
|
||||
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
return parseLoginResponse(response.body());
|
||||
}
|
||||
|
||||
// ==================== 货物列表相关 ====================
|
||||
|
||||
/**
|
||||
* 获取在售货物列表
|
||||
*/
|
||||
private static String fetchGoodsList(String token, int page, int pageSize, boolean withSign) throws Exception {
|
||||
HttpClient client = buildHttpClient();
|
||||
|
||||
String url = GOODS_LIST_URL + "?page=" + page + "&pageSize=" + pageSize;
|
||||
|
||||
HttpRequest.Builder reqBuilder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.timeout(java.time.Duration.ofSeconds(60))
|
||||
.header("Accept", "application/json, text/plain, */*")
|
||||
.header("Origin", "https://web.qdwsmgs.com")
|
||||
.header("Referer", "https://web.qdwsmgs.com/exchange/list")
|
||||
.header("User-Agent", buildUserAgent())
|
||||
.header("Authorization", "Bearer " + token);
|
||||
|
||||
if (withSign) {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||
String nonce = generateNonce();
|
||||
String sign = md5(nonce + timestamp);
|
||||
reqBuilder.header("X-Nonce", nonce)
|
||||
.header("X-Sign", sign)
|
||||
.header("X-Timestamp", timestamp);
|
||||
}
|
||||
|
||||
HttpRequest request = reqBuilder.GET().build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
if (response.statusCode() == 200) {
|
||||
return response.body();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 下单(认领订单):POST /api/order/{orderId}/claim
|
||||
* 请求体为空(Content-Length: 0),Authorization + 签名头
|
||||
*/
|
||||
private static ClaimResult claimOrder(String token, int orderId, boolean withSign) throws Exception {
|
||||
HttpClient client = buildHttpClient();
|
||||
|
||||
String url = String.format(CLAIM_ORDER_URL, orderId);
|
||||
HttpRequest.Builder reqBuilder = HttpRequest.newBuilder()
|
||||
.uri(URI.create(url))
|
||||
.timeout(java.time.Duration.ofSeconds(60))
|
||||
.header("Accept", "application/json, text/plain, */*")
|
||||
.header("Origin", "https://web.qdwsmgs.com")
|
||||
.header("Referer", "https://web.qdwsmgs.com/exchange/list")
|
||||
.header("User-Agent", buildUserAgent())
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header("Content-Type", "application/json")
|
||||
.POST(HttpRequest.BodyPublishers.noBody());
|
||||
|
||||
if (withSign) {
|
||||
String timestamp = String.valueOf(System.currentTimeMillis());
|
||||
String nonce = generateNonce();
|
||||
String sign = md5(nonce + timestamp);
|
||||
reqBuilder.header("X-Nonce", nonce)
|
||||
.header("X-Sign", sign)
|
||||
.header("X-Timestamp", timestamp);
|
||||
}
|
||||
|
||||
HttpRequest request = reqBuilder.build();
|
||||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
|
||||
|
||||
ClaimResult result = new ClaimResult();
|
||||
result.setHttpStatus(response.statusCode());
|
||||
String body = response.body();
|
||||
result.setRawJson(body == null ? "" : body);
|
||||
|
||||
if (body != null && !body.isEmpty()) {
|
||||
String code = extractValue(body, "\"code\":", ",");
|
||||
String msg = extractStringValue(body, "\"message\":");
|
||||
String dataPart = extractObject(body, "\"data\":");
|
||||
result.setCode(code == null ? "" : code);
|
||||
result.setMessage(msg == null ? "" : msg);
|
||||
result.setSuccess("200".equals(code) || "0".equals(code)
|
||||
|| (code == null && response.statusCode() == 200));
|
||||
result.setData(dataPart == null ? "" : dataPart);
|
||||
} else {
|
||||
result.setSuccess(response.statusCode() == 200);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析并打印货物列表
|
||||
*/
|
||||
private static void parseAndPrintGoodsList(String json) {
|
||||
System.out.println("\n========== 接口原始响应(前500字符) ==========");
|
||||
String preview = json.length() > 500 ? json.substring(0, 500) + "..." : json;
|
||||
System.out.println(preview);
|
||||
System.out.println("=============================================\n");
|
||||
|
||||
// 解析 code
|
||||
String code = extractValue(json, "\"code\":", ",");
|
||||
String message = extractStringValue(json, "\"message\":");
|
||||
System.out.println("接口状态码: " + code);
|
||||
System.out.println("接口消息: " + message);
|
||||
|
||||
if (!"200".equals(code) && !"0".equals(code)) {
|
||||
System.out.println("接口返回错误,无法解析货物列表");
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析 data 部分
|
||||
String dataJson = extractObject(json, "\"data\":");
|
||||
if (dataJson == null) {
|
||||
// 可能直接返回列表
|
||||
if (json.startsWith("[")) {
|
||||
dataJson = json;
|
||||
} else {
|
||||
System.out.println("未找到 data 字段");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 解析分页信息
|
||||
String total = extractValue(dataJson, "\"total\":", ",");
|
||||
String pageSize = extractValue(dataJson, "\"pageSize\":", ",");
|
||||
String page = extractValue(dataJson, "\"page\":", ",");
|
||||
System.out.println("\n分页信息:");
|
||||
System.out.println(" 总数量: " + total);
|
||||
System.out.println(" 当前页: " + page);
|
||||
System.out.println(" 每页大小: " + pageSize);
|
||||
|
||||
// 解析货物列表
|
||||
String listJson = extractArray(dataJson, "\"list\":");
|
||||
if (listJson == null) {
|
||||
listJson = extractArray(dataJson, "\"records\":");
|
||||
}
|
||||
if (listJson == null) {
|
||||
listJson = extractArray(dataJson, "\"items\":");
|
||||
}
|
||||
|
||||
if (listJson == null) {
|
||||
System.out.println("\n未找到列表字段(list/records/items)");
|
||||
System.out.println("data 部分内容预览: " + (dataJson.length() > 300
|
||||
? dataJson.substring(0, 300) + "..." : dataJson));
|
||||
return;
|
||||
}
|
||||
|
||||
List<String> goodsItems = splitArrayElements(listJson);
|
||||
System.out.println("\n========== 在售货物列表 (共" + goodsItems.size() + "条) ==========");
|
||||
|
||||
int index = 1;
|
||||
for (String item : goodsItems) {
|
||||
System.out.println("\n--- 货物 #" + index++ + " ---");
|
||||
printGoodsItem(item);
|
||||
}
|
||||
|
||||
System.out.println("\n=============================================");
|
||||
}
|
||||
|
||||
/**
|
||||
* 打印单条货物信息 - 解析所有字段
|
||||
*/
|
||||
private static void printGoodsItem(String item) {
|
||||
// 遍历 JSON 对象中所有 key-value 对
|
||||
List<String[]> fields = parseAllFields(item);
|
||||
for (String[] field : fields) {
|
||||
String key = field[0];
|
||||
String val = field[1];
|
||||
// 跳过 null 值
|
||||
if ("null".equals(val) || val.isEmpty()) continue;
|
||||
System.out.println(" " + key + ": " + val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 解析 JSON 对象中所有顶层 key-value 对
|
||||
* 返回 [[key, value], [key, value], ...]
|
||||
*/
|
||||
private static List<String[]> parseAllFields(String json) {
|
||||
List<String[]> fields = new ArrayList<>();
|
||||
if (json == null || !json.startsWith("{")) return fields;
|
||||
|
||||
int pos = 1; // skip '{'
|
||||
int len = json.length() - 1; // skip '}'
|
||||
|
||||
while (pos < len) {
|
||||
// 跳过空白和逗号
|
||||
while (pos < len && (json.charAt(pos) == ',' || json.charAt(pos) == ' ' || json.charAt(pos) == '\n')) {
|
||||
pos++;
|
||||
}
|
||||
if (pos >= len) break;
|
||||
|
||||
// 读取 key (必须是字符串)
|
||||
if (json.charAt(pos) != '"') { pos++; continue; }
|
||||
String key = readJsonString(json, pos);
|
||||
pos += key.length() + 2; // skip closing quote
|
||||
|
||||
// 跳过冒号
|
||||
while (pos < len && json.charAt(pos) != ':') pos++;
|
||||
pos++; // skip ':'
|
||||
|
||||
// 跳过空白
|
||||
while (pos < len && (json.charAt(pos) == ' ' || json.charAt(pos) == '\n')) pos++;
|
||||
|
||||
// 读取 value
|
||||
String value;
|
||||
char c = json.charAt(pos);
|
||||
if (c == '"') {
|
||||
// 字符串值
|
||||
value = readJsonString(json, pos);
|
||||
pos += value.length() + 2;
|
||||
} else if (c == '{' || c == '[') {
|
||||
// 嵌套对象或数组 - 跳到匹配位置
|
||||
int end = findMatchingBracket(json, pos);
|
||||
value = json.substring(pos, end + 1);
|
||||
pos = end + 1;
|
||||
} else {
|
||||
// 数字、布尔、null
|
||||
int start = pos;
|
||||
while (pos < len && json.charAt(pos) != ',' && json.charAt(pos) != '}') {
|
||||
pos++;
|
||||
}
|
||||
value = json.substring(start, pos).trim();
|
||||
}
|
||||
|
||||
fields.add(new String[]{key, value});
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取 JSON 字符串值(处理转义)
|
||||
*/
|
||||
private static String readJsonString(String json, int pos) {
|
||||
// pos 指向开头的 "
|
||||
int i = pos + 1;
|
||||
StringBuilder sb = new StringBuilder();
|
||||
while (i < json.length()) {
|
||||
char c = json.charAt(i);
|
||||
if (c == '\\' && i + 1 < json.length()) {
|
||||
char next = json.charAt(i + 1);
|
||||
switch (next) {
|
||||
case '"': sb.append('"'); i += 2; break;
|
||||
case '\\': sb.append('\\'); i += 2; break;
|
||||
case '/': sb.append('/'); i += 2; break;
|
||||
case 'n': sb.append('\n'); i += 2; break;
|
||||
case 't': sb.append('\t'); i += 2; break;
|
||||
case 'r': sb.append('\r'); i += 2; break;
|
||||
default: sb.append(c); i++; break;
|
||||
}
|
||||
} else if (c == '"') {
|
||||
return sb.toString();
|
||||
} else {
|
||||
sb.append(c);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 找到匹配的括号位置
|
||||
*/
|
||||
private static int findMatchingBracket(String json, int pos) {
|
||||
char open = json.charAt(pos);
|
||||
char close = open == '{' ? '}' : ']';
|
||||
int depth = 0;
|
||||
boolean inString = false;
|
||||
for (int i = pos; i < json.length(); i++) {
|
||||
char c = json.charAt(i);
|
||||
if (c == '\\' && inString) { i++; continue; }
|
||||
if (c == '"') inString = !inString;
|
||||
if (inString) continue;
|
||||
if (c == open) depth++;
|
||||
else if (c == close) {
|
||||
depth--;
|
||||
if (depth == 0) return i;
|
||||
}
|
||||
}
|
||||
return json.length() - 1;
|
||||
}
|
||||
|
||||
// ==================== HTTP 工具方法 ====================
|
||||
|
||||
private static HttpClient buildHttpClient() {
|
||||
return HttpClient.newBuilder()
|
||||
.connectTimeout(java.time.Duration.ofSeconds(30))
|
||||
.followRedirects(HttpClient.Redirect.NORMAL)
|
||||
.build();
|
||||
}
|
||||
|
||||
private static String buildUserAgent() {
|
||||
return "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) " +
|
||||
"AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1";
|
||||
}
|
||||
|
||||
// ==================== 登录响应解析 ====================
|
||||
|
||||
private static LoginResult parseLoginResponse(String json) {
|
||||
LoginResult result = new LoginResult();
|
||||
try {
|
||||
result.setRawJson(json);
|
||||
String code = extractValue(json, "\"code\":", ",");
|
||||
result.setSuccess("200".equals(code) || "0".equals(code));
|
||||
result.setMessage(extractStringValue(json, "\"message\":"));
|
||||
|
||||
String tokenJson = extractObject(json, "\"data\":");
|
||||
if (tokenJson != null) {
|
||||
result.setToken(extractStringValue(tokenJson, "\"token\":"));
|
||||
}
|
||||
|
||||
String userJson = extractObject(json, "\"user\":");
|
||||
if (userJson != null) {
|
||||
result.setUserId(extractValue(userJson, "\"id\":", ","));
|
||||
result.setPhone(extractStringValue(userJson, "\"phone\":"));
|
||||
result.setNickname(extractStringValue(userJson, "\"nickname\":"));
|
||||
result.setLevelId(extractValue(userJson, "\"levelId\":", ","));
|
||||
result.setLevelName(extractStringValue(userJson, "\"levelName\":"));
|
||||
result.setPromotionCode(extractStringValue(userJson, "\"promotionCode\":"));
|
||||
result.setProfitBalance(parseDouble(extractValue(userJson, "\"profitBalance\":", ",")));
|
||||
result.setCouponBalance(parseDouble(extractValue(userJson, "\"couponBalance\":", ",")));
|
||||
}
|
||||
result.setTimestamp(extractValue(json, "\"timestamp\":", "}"));
|
||||
} catch (Exception e) {
|
||||
result.setMessage("响应解析失败: " + e.getMessage());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ==================== 签名工具 ====================
|
||||
|
||||
private static String generateNonce() {
|
||||
String chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < 28; i++) {
|
||||
sb.append(chars.charAt((int) (Math.random() * chars.length())));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
private static String md5(String input) {
|
||||
try {
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
byte[] digest = md.digest(input.getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : digest) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("MD5计算失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== JSON 解析辅助方法 ====================
|
||||
|
||||
private static String extractStringValue(String json, String key) {
|
||||
int keyIdx = json.indexOf(key);
|
||||
if (keyIdx < 0) return null;
|
||||
int start = keyIdx + key.length();
|
||||
if (start < json.length() && json.charAt(start) == '"') {
|
||||
start++;
|
||||
int end = json.indexOf('"', start);
|
||||
if (end > start) {
|
||||
return json.substring(start, end);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static String extractValue(String json, String key, String delimiter) {
|
||||
int keyIdx = json.indexOf(key);
|
||||
if (keyIdx < 0) return null;
|
||||
int start = keyIdx + key.length();
|
||||
int end = json.indexOf(delimiter, start);
|
||||
if (end > start) {
|
||||
return json.substring(start, end).trim();
|
||||
}
|
||||
String sub = json.substring(start).trim();
|
||||
if (sub.endsWith("}")) sub = sub.substring(0, sub.length() - 1);
|
||||
return sub.trim();
|
||||
}
|
||||
|
||||
private static String extractObject(String json, String key) {
|
||||
int keyIdx = json.indexOf(key);
|
||||
if (keyIdx < 0) return null;
|
||||
int start = json.indexOf('{', keyIdx);
|
||||
if (start < 0) return null;
|
||||
int depth = 0;
|
||||
for (int i = start; i < json.length(); i++) {
|
||||
char c = json.charAt(i);
|
||||
if (c == '{') depth++;
|
||||
else if (c == '}') {
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
return json.substring(start, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取 JSON 数组
|
||||
*/
|
||||
private static String extractArray(String json, String key) {
|
||||
int keyIdx = json.indexOf(key);
|
||||
if (keyIdx < 0) return null;
|
||||
int start = json.indexOf('[', keyIdx);
|
||||
if (start < 0) return null;
|
||||
int depth = 0;
|
||||
for (int i = start; i < json.length(); i++) {
|
||||
char c = json.charAt(i);
|
||||
if (c == '[') depth++;
|
||||
else if (c == ']') {
|
||||
depth--;
|
||||
if (depth == 0) {
|
||||
return json.substring(start, i + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 JSON 数组拆分为单个元素
|
||||
*/
|
||||
private static List<String> splitArrayElements(String arrayJson) {
|
||||
List<String> elements = new ArrayList<>();
|
||||
if (arrayJson == null || arrayJson.length() < 2) return elements;
|
||||
|
||||
// 去掉首尾的 [ 和 ]
|
||||
String inner = arrayJson.substring(1, arrayJson.length() - 1).trim();
|
||||
if (inner.isEmpty()) return elements;
|
||||
|
||||
// 按顶级花括号拆分
|
||||
int depth = 0;
|
||||
int start = -1;
|
||||
boolean inString = false;
|
||||
boolean escape = false;
|
||||
|
||||
for (int i = 0; i < inner.length(); i++) {
|
||||
char c = inner.charAt(i);
|
||||
|
||||
if (escape) {
|
||||
escape = false;
|
||||
continue;
|
||||
}
|
||||
if (c == '\\') {
|
||||
escape = true;
|
||||
continue;
|
||||
}
|
||||
if (c == '"') {
|
||||
inString = !inString;
|
||||
continue;
|
||||
}
|
||||
if (inString) continue;
|
||||
|
||||
if (c == '{') {
|
||||
if (depth == 0) start = i;
|
||||
depth++;
|
||||
} else if (c == '}') {
|
||||
depth--;
|
||||
if (depth == 0 && start >= 0) {
|
||||
elements.add(inner.substring(start, i + 1));
|
||||
start = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 如果没有找到对象,尝试按逗号拆分基本类型
|
||||
if (elements.isEmpty() && inner.startsWith("\"")) {
|
||||
// 字符串数组
|
||||
int s = 0;
|
||||
for (int i = 0; i < inner.length(); i++) {
|
||||
if (inner.charAt(i) == '"' && (i == 0 || inner.charAt(i - 1) != '\\')) {
|
||||
if (s == 0) { s = i + 1; }
|
||||
else {
|
||||
elements.add("\"" + inner.substring(s, i) + "\"");
|
||||
s = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
private static double parseDouble(String val) {
|
||||
if (val == null || val.isEmpty()) return 0.0;
|
||||
try {
|
||||
return Double.parseDouble(val);
|
||||
} catch (NumberFormatException e) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 数据类 ====================
|
||||
|
||||
public static class LoginResult {
|
||||
private boolean success;
|
||||
private String message;
|
||||
private String token;
|
||||
private String userId;
|
||||
private String phone;
|
||||
private String nickname;
|
||||
private String levelId;
|
||||
private String levelName;
|
||||
private String promotionCode;
|
||||
private double profitBalance;
|
||||
private double couponBalance;
|
||||
private String timestamp;
|
||||
private String rawJson;
|
||||
|
||||
public boolean isSuccess() { return success; }
|
||||
public void setSuccess(boolean success) { this.success = success; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
public String getToken() { return token; }
|
||||
public void setToken(String token) { this.token = token; }
|
||||
public String getUserId() { return userId; }
|
||||
public void setUserId(String userId) { this.userId = userId; }
|
||||
public String getPhone() { return phone; }
|
||||
public void setPhone(String phone) { this.phone = phone; }
|
||||
public String getNickname() { return nickname; }
|
||||
public void setNickname(String nickname) { this.nickname = nickname; }
|
||||
public String getLevelId() { return levelId; }
|
||||
public void setLevelId(String levelId) { this.levelId = levelId; }
|
||||
public String getLevelName() { return levelName; }
|
||||
public void setLevelName(String levelName) { this.levelName = levelName; }
|
||||
public String getPromotionCode() { return promotionCode; }
|
||||
public void setPromotionCode(String promotionCode) { this.promotionCode = promotionCode; }
|
||||
public double getProfitBalance() { return profitBalance; }
|
||||
public void setProfitBalance(double profitBalance) { this.profitBalance = profitBalance; }
|
||||
public double getCouponBalance() { return couponBalance; }
|
||||
public void setCouponBalance(double couponBalance) { this.couponBalance = couponBalance; }
|
||||
public String getTimestamp() { return timestamp; }
|
||||
public void setTimestamp(String timestamp) { this.timestamp = timestamp; }
|
||||
public String getRawJson() { return rawJson; }
|
||||
public void setRawJson(String rawJson) { this.rawJson = rawJson; }
|
||||
}
|
||||
|
||||
/**
|
||||
* 货物项数据类
|
||||
*/
|
||||
public static class GoodsItem {
|
||||
private int id;
|
||||
private int sellerId;
|
||||
private int productId;
|
||||
private String productTitle;
|
||||
private String productThumbnail;
|
||||
private double price;
|
||||
private String productPriceStr;
|
||||
private int display;
|
||||
private int status;
|
||||
private String createdAt;
|
||||
private String updatedAt;
|
||||
|
||||
public int getId() { return id; }
|
||||
public void setId(int id) { this.id = id; }
|
||||
public int getSellerId() { return sellerId; }
|
||||
public void setSellerId(int sellerId) { this.sellerId = sellerId; }
|
||||
public int getProductId() { return productId; }
|
||||
public void setProductId(int productId) { this.productId = productId; }
|
||||
public String getProductTitle() { return productTitle; }
|
||||
public void setProductTitle(String productTitle) { this.productTitle = productTitle; }
|
||||
public String getProductThumbnail() { return productThumbnail; }
|
||||
public void setProductThumbnail(String productThumbnail) { this.productThumbnail = productThumbnail; }
|
||||
public double getPrice() { return price; }
|
||||
public void setPrice(double price) { this.price = price; }
|
||||
public String getProductPriceStr() { return productPriceStr; }
|
||||
public void setProductPriceStr(String productPriceStr) { this.productPriceStr = productPriceStr; }
|
||||
public int getDisplay() { return display; }
|
||||
public void setDisplay(int display) { this.display = display; }
|
||||
public int getStatus() { return status; }
|
||||
public void setStatus(int status) { this.status = status; }
|
||||
public String getCreatedAt() { return createdAt; }
|
||||
public void setCreatedAt(String createdAt) { this.createdAt = createdAt; }
|
||||
public String getUpdatedAt() { return updatedAt; }
|
||||
public void setUpdatedAt(String updatedAt) { this.updatedAt = updatedAt; }
|
||||
}
|
||||
|
||||
/**
|
||||
* 下单(claim)结果
|
||||
*/
|
||||
public static class ClaimResult {
|
||||
private int httpStatus;
|
||||
private String code;
|
||||
private String message;
|
||||
private boolean success;
|
||||
private String data;
|
||||
private String rawJson;
|
||||
|
||||
public int getHttpStatus() { return httpStatus; }
|
||||
public void setHttpStatus(int httpStatus) { this.httpStatus = httpStatus; }
|
||||
public String getCode() { return code; }
|
||||
public void setCode(String code) { this.code = code; }
|
||||
public String getMessage() { return message; }
|
||||
public void setMessage(String message) { this.message = message; }
|
||||
public boolean isSuccess() { return success; }
|
||||
public void setSuccess(boolean success) { this.success = success; }
|
||||
public String getData() { return data; }
|
||||
public void setData(String data) { this.data = data; }
|
||||
public String getRawJson() { return rawJson; }
|
||||
public void setRawJson(String rawJson) { this.rawJson = rawJson; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append("HTTP=").append(httpStatus)
|
||||
.append(", success=").append(success)
|
||||
.append(", code=").append(code == null ? "" : code)
|
||||
.append(", msg=").append(message == null ? "" : message);
|
||||
if (data != null && !data.isEmpty()) {
|
||||
String dv = data.length() > 120 ? data.substring(0, 120) + "…" : data;
|
||||
sb.append(", data=").append(dv);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user