BMS TMS 计费批量数据测试接口 demo

This commit is contained in:
caojia
2026-09-10 17:57:05 +08:00
parent a968d67b2e
commit e6d57a9000
@@ -0,0 +1,288 @@
package com.ruoyi.web.controller.tool;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamReader;
/**
* BMS TMS 计费接口 demo(单文件,纯 JDK,无第三方依赖)
*
* 功能:读取 D:\code\view\orders_10000.xlsx 的每一行(跳过表头),
* 组装为 JSON 后 POST 到本地 BMS 计费接口。
*
* 接口:POST http://127.0.0.1:8080/api/tms/price/bill
* 鉴权:Header X-Api-Code
*
* 编译运行:
* javac TmsPriceBillDemo.java
* java TmsPriceBillDemo
*/
public class TmsPriceBillDemo {
// ===== 可配置项 =====
private static final String BASE_URL = "http://127.0.0.1:8080";
private static final String BILL_PATH = "/api/tms/price/bill";
private static final String API_CODE = "BMS.TMS.ZYaOvlQzpcfau8TUaCFfO3Hp8ieHIqGw";
private static final String EXCEL_PATH = "D:\\code\\view\\orders_10000.xlsx";
private static final int CONNECT_TIMEOUT_MS = 10_000;
private static final int READ_TIMEOUT_MS = 30_000;
/** 每多少条打印一次进度 */
private static final int PROGRESS_STEP = 200;
/** 数值字段:JSON 中不加引号,直接输出 Excel 中的原始数字字符串 */
private static final Set<String> NUMERIC_FIELDS = new HashSet<>(Arrays.asList(
"weight", "pieces", "actualWeight", "volume", "volumeWeight",
"pkgLength", "pkgWidth", "pkgHeight", "tmsId"
));
public static void main(String[] args) throws Exception {
// 可通过命令行参数覆盖 Excel 路径
String excel = (args.length > 0 && !args[0].isEmpty()) ? args[0] : EXCEL_PATH;
System.out.println("[config] base=" + BASE_URL + BILL_PATH);
System.out.println("[config] xApiCode=" + API_CODE);
System.out.println("[config] excel=" + excel);
long start = System.currentTimeMillis();
int ok = 0, fail = 0, rowIdx = 0;
try (ZipFile zf = new ZipFile(excel)) {
List<String> sst = readSharedStrings(zf);
System.out.println("[read] sharedStrings uniqueCount=" + sst.size());
SheetReader sr = new SheetReader(zf, sst);
String[] headers = null;
List<String> row;
while ((row = sr.nextRow()) != null) {
rowIdx++;
if (headers == null) {
headers = row.toArray(new String[0]);
System.out.println("[read] headers=" + row);
continue;
}
String json = buildJson(row, headers);
String resp;
try {
resp = postJson(BASE_URL + BILL_PATH, API_CODE, json);
} catch (Exception e) {
resp = "ERR: " + e.getMessage();
}
if (resp.contains("\"code\":200") || resp.contains("\"code\": 200")) {
ok++;
} else {
fail++;
}
if (rowIdx % PROGRESS_STEP == 0 || rowIdx == 1) {
String snippet = resp.length() > 120 ? resp.substring(0, 120) + "..." : resp;
System.out.printf("[progress] %d ok=%d fail=%d last=%s%n", rowIdx, ok, fail, snippet);
}
}
System.out.printf("[done] rows=%d ok=%d fail=%d cost=%dms%n",
rowIdx, ok, fail, System.currentTimeMillis() - start);
}
}
// ===== JSON 构造 =====
private static String buildJson(List<String> row, String[] headers) {
StringBuilder sb = new StringBuilder(256);
sb.append('{');
for (int i = 0; i < headers.length; i++) {
if (i > 0) sb.append(',');
String k = headers[i];
String v = (i < row.size()) ? row.get(i) : "";
sb.append('"').append(escape(k)).append("\":");
boolean isNumeric = NUMERIC_FIELDS.contains(k)
&& v != null && !v.isEmpty() && !"null".equals(v);
if (isNumeric) {
sb.append(v);
} else if (v == null || v.isEmpty() || "null".equals(v)) {
sb.append("null");
} else {
sb.append('"').append(escape(v)).append('"');
}
}
sb.append('}');
return sb.toString();
}
private static String escape(String s) {
if (s == null) return "";
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
switch (c) {
case '"': sb.append("\\\""); break;
case '\\': sb.append("\\\\"); break;
case '\n': sb.append("\\n"); break;
case '\r': sb.append("\\r"); break;
case '\t': sb.append("\\t"); break;
default:
if (c < 0x20) sb.append(String.format("\\u%04x", (int) c));
else sb.append(c);
}
}
return sb.toString();
}
// ===== HTTP =====
private static String postJson(String url, String apiCode, String json) throws Exception {
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(url).openConnection();
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setConnectTimeout(CONNECT_TIMEOUT_MS);
conn.setReadTimeout(READ_TIMEOUT_MS);
conn.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
conn.setRequestProperty("X-Api-Code", apiCode);
conn.setRequestProperty("Accept", "application/json");
byte[] body = json.getBytes(StandardCharsets.UTF_8);
conn.setRequestProperty("Content-Length", String.valueOf(body.length));
try (OutputStream os = conn.getOutputStream()) {
os.write(body);
os.flush();
}
int code = conn.getResponseCode();
InputStream is = (code >= 200 && code < 300) ? conn.getInputStream() : conn.getErrorStream();
String respBody = readAll(is);
return "HTTP " + code + " " + respBody;
} finally {
if (conn != null) conn.disconnect();
}
}
private static String readAll(InputStream is) throws Exception {
if (is == null) return "";
StringBuilder sb = new StringBuilder();
try (BufferedReader r = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8))) {
String line;
while ((line = r.readLine()) != null) {
if (sb.length() > 0) sb.append('\n');
sb.append(line);
}
}
return sb.toString();
}
// ===== XLSX 解析(纯 JDK) =====
/** 读取 xl/sharedStrings.xml,返回共享字符串表 */
private static List<String> readSharedStrings(ZipFile zf) throws Exception {
ZipEntry e = zf.getEntry("xl/sharedStrings.xml");
if (e == null) return Collections.emptyList();
List<String> out = new ArrayList<>();
try (InputStream in = zf.getInputStream(e)) {
XMLInputFactory f = XMLInputFactory.newInstance();
// 禁用 DTD 以避免 XXE
f.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
f.setProperty(XMLInputFactory.SUPPORT_DTD, false);
XMLStreamReader r = f.createXMLStreamReader(in);
StringBuilder text = new StringBuilder();
boolean inT = false;
while (r.hasNext()) {
int ev = r.next();
if (ev == XMLStreamConstants.START_ELEMENT) {
if ("t".equals(r.getLocalName())) {
inT = true;
text.setLength(0);
}
} else if (ev == XMLStreamConstants.CHARACTERS) {
if (inT) text.append(r.getText());
} else if (ev == XMLStreamConstants.END_ELEMENT) {
String ln = r.getLocalName();
if ("t".equals(ln)) {
inT = false;
} else if ("si".equals(ln)) {
out.add(text.toString());
text.setLength(0);
}
}
}
r.close();
}
return out;
}
/** 流式读取 xl/worksheets/sheet1.xml,nextRow() 逐行返回 */
static class SheetReader {
private final XMLStreamReader r;
private final List<String> sst;
private final InputStream stream;
SheetReader(ZipFile zf, List<String> sst) throws Exception {
ZipEntry e = zf.getEntry("xl/worksheets/sheet1.xml");
this.stream = zf.getInputStream(e);
this.sst = sst;
XMLInputFactory f = XMLInputFactory.newInstance();
f.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
f.setProperty(XMLInputFactory.SUPPORT_DTD, false);
this.r = f.createXMLStreamReader(stream);
}
List<String> nextRow() throws Exception {
List<String> row = null;
String curType = null;
StringBuilder curVal = new StringBuilder();
boolean inV = false;
while (r.hasNext()) {
int ev = r.next();
if (ev == XMLStreamConstants.START_ELEMENT) {
String ln = r.getLocalName();
if ("row".equals(ln)) {
row = new ArrayList<>();
} else if ("c".equals(ln) && row != null) {
curType = r.getAttributeValue(null, "t");
curVal.setLength(0);
} else if ("v".equals(ln) && row != null) {
inV = true;
} else if ("t".equals(ln) && row != null) {
inV = true; // inlineStr 的 <t>
}
} else if (ev == XMLStreamConstants.CHARACTERS) {
if (inV && row != null) curVal.append(r.getText());
} else if (ev == XMLStreamConstants.END_ELEMENT) {
String ln = r.getLocalName();
if ("v".equals(ln) || "t".equals(ln)) {
inV = false;
} else if ("c".equals(ln) && row != null) {
String v = curVal.toString();
String val;
if ("s".equals(curType)) {
int idx = Integer.parseInt(v.trim());
val = idx < sst.size() ? sst.get(idx) : "";
} else if ("b".equals(curType)) {
val = "1".equals(v.trim()) ? "true" : "false";
} else {
// 数值、str、inlineStr、空等,直接取原值
val = v;
}
row.add(val);
} else if ("row".equals(ln) && row != null) {
return row;
}
}
}
try { r.close(); stream.close(); } catch (Exception ignored) {}
return null;
}
}
}