|
@@ -921,58 +921,98 @@ public class LiLinServiceImpl implements LiLinService {
|
|
|
accountNo = accountNo.trim();
|
|
accountNo = accountNo.trim();
|
|
|
|
|
|
|
|
log.info("开始对账单同步,accountNo: {}", accountNo);
|
|
log.info("开始对账单同步,accountNo: {}", accountNo);
|
|
|
-
|
|
|
|
|
- // 准备时间范围
|
|
|
|
|
|
|
+// 准备时间范围
|
|
|
String dayFromId = LocalDate.now().minusDays(8).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
|
String dayFromId = LocalDate.now().minusDays(8).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
|
|
String dayToId = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
|
String dayToId = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
|
|
|
|
+ // 准备时间范围
|
|
|
|
|
+// String dayFromId = LocalDate.of(2026, 2, 26).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
|
|
|
|
+// String dayToId = LocalDate.of(2026, 2, 26).format(DateTimeFormatter.ofPattern("yyyyMMdd"));
|
|
|
|
|
+
|
|
|
|
|
+ // ========== 分页查询 ==========
|
|
|
|
|
+ int pageSize = 20;
|
|
|
|
|
+ int pageNow = 1;
|
|
|
|
|
+ boolean hasMore = true;
|
|
|
|
|
+ List<JSONObject> allStatementList = new ArrayList<>();
|
|
|
|
|
+
|
|
|
|
|
+ while (hasMore) {
|
|
|
|
|
+ // 构建 POST 请求体
|
|
|
|
|
+ MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
|
|
|
|
|
+ String requestBodyStr = "securityCode=" + securityCode +
|
|
|
|
|
+ "&accountNo=" + accountNo +
|
|
|
|
|
+ "&dayFromId=" + dayFromId +
|
|
|
|
|
+ "&dayToId=" + dayToId +
|
|
|
|
|
+ "&instructionIdFlag=1" +
|
|
|
|
|
+ "&pageNow=" + pageNow +
|
|
|
|
|
+ "&trxFlag=R" +
|
|
|
|
|
+ "&pageSize=" + pageSize;
|
|
|
|
|
+
|
|
|
|
|
+ RequestBody body = RequestBody.create(mediaType, requestBodyStr);
|
|
|
|
|
+ log.info("请求体: {},当前页: {}", requestBodyStr, pageNow);
|
|
|
|
|
+
|
|
|
|
|
+ Request request = new Request.Builder()
|
|
|
|
|
+ .url("https://x.xencio.com/c4c3/api/bs/list")
|
|
|
|
|
+ .post(body)
|
|
|
|
|
+ .addHeader("x-xencio-client-id", "7dc3a31209b94a91ba40a44358fe70eb")
|
|
|
|
|
+ .addHeader("content-type", "application/x-www-form-urlencoded")
|
|
|
|
|
+ .build();
|
|
|
|
|
+
|
|
|
|
|
+ response = client.newCall(request).execute();
|
|
|
|
|
+
|
|
|
|
|
+ if (!response.isSuccessful()) {
|
|
|
|
|
+ log.error("银行接口调用失败,HTTP状态码: {}", response.code());
|
|
|
|
|
+ break; // 当前账号查询失败,跳出分页循环
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- // 构建 POST 请求体
|
|
|
|
|
- MediaType mediaType = MediaType.parse("application/x-www-form-urlencoded");
|
|
|
|
|
- String requestBodyStr = "securityCode=" + securityCode +
|
|
|
|
|
- "&accountNo=" + accountNo +
|
|
|
|
|
- "&dayFromId=" + dayFromId +
|
|
|
|
|
- "&dayToId=" + dayToId +
|
|
|
|
|
- "&instructionIdFlag=1" +
|
|
|
|
|
- "&pageNow=1" +
|
|
|
|
|
- "&trxFlag=R" +
|
|
|
|
|
- "&pageSize=20";
|
|
|
|
|
-
|
|
|
|
|
- RequestBody body = RequestBody.create(mediaType, requestBodyStr);
|
|
|
|
|
-
|
|
|
|
|
- Request request = new Request.Builder()
|
|
|
|
|
- .url("https://x.xencio.com/c4c3/api/bs/list")
|
|
|
|
|
- .post(body)
|
|
|
|
|
- .addHeader("x-xencio-client-id", "7dc3a31209b94a91ba40a44358fe70eb")
|
|
|
|
|
- .addHeader("content-type", "application/x-www-form-urlencoded")
|
|
|
|
|
- .build();
|
|
|
|
|
-
|
|
|
|
|
- response = client.newCall(request).execute();
|
|
|
|
|
-
|
|
|
|
|
- if (!response.isSuccessful()) {
|
|
|
|
|
- log.error("银行接口调用失败,HTTP状态码: {}", response.code());
|
|
|
|
|
- continue; // 跳过当前账号
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ String jsonStr = response.body().string();
|
|
|
|
|
+ log.info("银行接口响应原始数据: {}", jsonStr);
|
|
|
|
|
|
|
|
- String jsonStr = response.body().string();
|
|
|
|
|
- log.debug("银行接口响应原始数据: {}", jsonStr);
|
|
|
|
|
|
|
+ JSONObject jsonObject = JSON.parseObject(jsonStr);
|
|
|
|
|
+ JSONObject data = jsonObject.getJSONObject("data");
|
|
|
|
|
+ if (data == null) {
|
|
|
|
|
+ log.info("响应中缺少 'data' 字段, accountNo: {}", accountNo);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
|
|
|
- JSONObject jsonObject = JSON.parseObject(jsonStr);
|
|
|
|
|
- JSONObject data = jsonObject.getJSONObject("data");
|
|
|
|
|
- if (data == null) {
|
|
|
|
|
- log.error("响应中缺少 'data' 字段, accountNo: {}", accountNo);
|
|
|
|
|
- continue;
|
|
|
|
|
|
|
+ JSONArray statementList = data.getJSONArray("searchBankStatementList");
|
|
|
|
|
+ if (statementList == null || statementList.isEmpty()) {
|
|
|
|
|
+ log.info("银行接口返回空列表,accountNo: {},pageNow: {}", accountNo, pageNow);
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 将当前页数据添加到总列表中
|
|
|
|
|
+ for (int i = 0; i < statementList.size(); i++) {
|
|
|
|
|
+ allStatementList.add(statementList.getJSONObject(i));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 判断是否还有下一页
|
|
|
|
|
+ // 方式1:如果返回的数据量小于 pageSize,说明是最后一页
|
|
|
|
|
+ if (statementList.size() < pageSize) {
|
|
|
|
|
+ hasMore = false;
|
|
|
|
|
+ log.info("已查询完所有数据,总页数: {}", pageNow);
|
|
|
|
|
+ } else {
|
|
|
|
|
+ pageNow++;
|
|
|
|
|
+ // 添加适当延迟,避免请求过快
|
|
|
|
|
+ Thread.sleep(100);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 关闭旧的response,准备下一次请求
|
|
|
|
|
+ if (response != null) {
|
|
|
|
|
+ response.close();
|
|
|
|
|
+ response = null;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- JSONArray statementList = data.getJSONArray("searchBankStatementList");
|
|
|
|
|
- if (statementList == null || statementList.isEmpty()) {
|
|
|
|
|
- log.info("银行接口返回空列表,accountNo: {}", accountNo);
|
|
|
|
|
|
|
+ log.info("账号 {} 共查询到 {} 条对账单记录", accountNo, allStatementList.size());
|
|
|
|
|
+
|
|
|
|
|
+ // ========== 处理所有分页数据 ==========
|
|
|
|
|
+ if (allStatementList.isEmpty()) {
|
|
|
|
|
+ log.info("账号 {} 无数据需要处理", accountNo);
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
List<Map<String, String>> extractedList = new ArrayList<>();
|
|
List<Map<String, String>> extractedList = new ArrayList<>();
|
|
|
|
|
|
|
|
- for (int i = 0; i < statementList.size(); i++) {
|
|
|
|
|
- JSONObject item = statementList.getJSONObject(i);
|
|
|
|
|
|
|
+ for (JSONObject item : allStatementList) {
|
|
|
if (item == null) continue;
|
|
if (item == null) continue;
|
|
|
|
|
|
|
|
Map<String, String> extractedItem = new HashMap<>();
|
|
Map<String, String> extractedItem = new HashMap<>();
|
|
@@ -981,12 +1021,12 @@ public class LiLinServiceImpl implements LiLinService {
|
|
|
String crAmount = getStringValue(item, "crAmount");
|
|
String crAmount = getStringValue(item, "crAmount");
|
|
|
String trxDate = getStringValue(item, "trxDate");
|
|
String trxDate = getStringValue(item, "trxDate");
|
|
|
String createDate = getStringValue(item, "createDate");
|
|
String createDate = getStringValue(item, "createDate");
|
|
|
- String accountName = getStringValue(item, "accountName"); // 本方账号名称
|
|
|
|
|
|
|
+ String accountName = getStringValue(item, "accountName");
|
|
|
String userRemarks = getStringValue(item, "userRemarks");
|
|
String userRemarks = getStringValue(item, "userRemarks");
|
|
|
String userMemo = getStringValue(item, "userMemo");
|
|
String userMemo = getStringValue(item, "userMemo");
|
|
|
String transFlag = getStringValue(item, "transFlag");
|
|
String transFlag = getStringValue(item, "transFlag");
|
|
|
String catalogName = getStringValue(item, "catalogName");
|
|
String catalogName = getStringValue(item, "catalogName");
|
|
|
- String customerName = getStringValue(item, "customerName"); // 对手方账户名称
|
|
|
|
|
|
|
+ String customerName = getStringValue(item, "customerName");
|
|
|
|
|
|
|
|
Long timestamp = null;
|
|
Long timestamp = null;
|
|
|
if (trxDate != null && !trxDate.isEmpty()) {
|
|
if (trxDate != null && !trxDate.isEmpty()) {
|
|
@@ -994,7 +1034,7 @@ public class LiLinServiceImpl implements LiLinService {
|
|
|
timestamp = LocalDateTime.parse(trxDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
|
timestamp = LocalDateTime.parse(trxDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
|
|
.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
|
.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
- log.warn("日期解析失败: {}", trxDate);
|
|
|
|
|
|
|
+ log.info("日期解析失败: {}", trxDate);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
Long timestamp1 = null;
|
|
Long timestamp1 = null;
|
|
@@ -1003,7 +1043,7 @@ public class LiLinServiceImpl implements LiLinService {
|
|
|
timestamp1 = LocalDateTime.parse(createDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
|
timestamp1 = LocalDateTime.parse(createDate, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))
|
|
|
.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
|
.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli();
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
- log.warn("日期解析失败: {}", createDate);
|
|
|
|
|
|
|
+ log.info("日期解析失败: {}", createDate);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1019,7 +1059,7 @@ public class LiLinServiceImpl implements LiLinService {
|
|
|
// 检查是否已存在该对账单记录
|
|
// 检查是否已存在该对账单记录
|
|
|
boolean exists = false;
|
|
boolean exists = false;
|
|
|
int maxRetries = 3;
|
|
int maxRetries = 3;
|
|
|
- long retryDelayMs = 1000; // 1秒后重试
|
|
|
|
|
|
|
+ long retryDelayMs = 1000;
|
|
|
|
|
|
|
|
for (int attempt = 1; attempt <= maxRetries; attempt++) {
|
|
for (int attempt = 1; attempt <= maxRetries; attempt++) {
|
|
|
try {
|
|
try {
|
|
@@ -1034,11 +1074,10 @@ public class LiLinServiceImpl implements LiLinService {
|
|
|
).getData();
|
|
).getData();
|
|
|
|
|
|
|
|
exists = list != null && !list.isEmpty();
|
|
exists = list != null && !list.isEmpty();
|
|
|
- break; // ✅ 查询成功,跳出重试循环
|
|
|
|
|
|
|
+ break;
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
String errorMsg = e.getMessage();
|
|
String errorMsg = e.getMessage();
|
|
|
- // 🔁 判断是否是“可重试”的临时错误
|
|
|
|
|
boolean isTransientError = errorMsg != null &&
|
|
boolean isTransientError = errorMsg != null &&
|
|
|
(errorMsg.contains("temporary failure") ||
|
|
(errorMsg.contains("temporary failure") ||
|
|
|
errorMsg.contains("500") ||
|
|
errorMsg.contains("500") ||
|
|
@@ -1051,30 +1090,27 @@ public class LiLinServiceImpl implements LiLinService {
|
|
|
attempt, bankStatementId, e.getMessage());
|
|
attempt, bankStatementId, e.getMessage());
|
|
|
|
|
|
|
|
if (attempt == maxRetries) {
|
|
if (attempt == maxRetries) {
|
|
|
- // ❌ 最后一次重试仍失败,才抛出异常
|
|
|
|
|
- log.error("查询宜搭系统最终失败,已重试 {} 次,bankStatementId: {}",
|
|
|
|
|
|
|
+ log.info("查询宜搭系统最终失败,已重试 {} 次,bankStatementId: {}",
|
|
|
maxRetries, bankStatementId, e);
|
|
maxRetries, bankStatementId, e);
|
|
|
throw new RuntimeException("查询宜搭失败,bankStatementId=" + bankStatementId, e);
|
|
throw new RuntimeException("查询宜搭失败,bankStatementId=" + bankStatementId, e);
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (isTransientError) {
|
|
if (isTransientError) {
|
|
|
- // ✅ 是临时错误,等待后重试
|
|
|
|
|
try {
|
|
try {
|
|
|
- Thread.sleep(retryDelayMs * attempt); // 指数退避:1s, 2s, 3s...
|
|
|
|
|
|
|
+ Thread.sleep(retryDelayMs * attempt);
|
|
|
} catch (InterruptedException ie) {
|
|
} catch (InterruptedException ie) {
|
|
|
Thread.currentThread().interrupt();
|
|
Thread.currentThread().interrupt();
|
|
|
throw new RuntimeException("重试等待被中断", ie);
|
|
throw new RuntimeException("重试等待被中断", ie);
|
|
|
}
|
|
}
|
|
|
} else {
|
|
} else {
|
|
|
- // ❌ 非临时错误(如参数错误、表单不存在),直接放弃重试
|
|
|
|
|
- log.error("查询失败:非临时性错误,不再重试,bankStatementId: {}", bankStatementId, e);
|
|
|
|
|
|
|
+ log.info("查询失败:非临时性错误,不再重试,bankStatementId: {}", bankStatementId, e);
|
|
|
throw new RuntimeException("查询宜搭失败(非临时错误):" + errorMsg, e);
|
|
throw new RuntimeException("查询宜搭失败(非临时错误):" + errorMsg, e);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
if (exists) {
|
|
if (exists) {
|
|
|
- log.debug("记录已存在,跳过: bankStatementId={}", bankStatementId);
|
|
|
|
|
|
|
+ log.info("记录已存在,跳过: bankStatementId={}", bankStatementId);
|
|
|
continue;
|
|
continue;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1108,7 +1144,6 @@ public class LiLinServiceImpl implements LiLinService {
|
|
|
khaccountName = customerName;
|
|
khaccountName = customerName;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
-
|
|
|
|
|
if (khformInstanceId == null || khaccountName == null) {
|
|
if (khformInstanceId == null || khaccountName == null) {
|
|
|
log.warn("未找到匹配的客户信息: customerName={}", customerName);
|
|
log.warn("未找到匹配的客户信息: customerName={}", customerName);
|
|
|
}
|
|
}
|
|
@@ -1158,10 +1193,12 @@ public class LiLinServiceImpl implements LiLinService {
|
|
|
|
|
|
|
|
} catch (IOException e) {
|
|
} catch (IOException e) {
|
|
|
log.error("网络IO异常: accountNo={}", accountNo, e);
|
|
log.error("网络IO异常: accountNo={}", accountNo, e);
|
|
|
|
|
+ } catch (InterruptedException e) {
|
|
|
|
|
+ log.error("线程中断异常: accountNo={}", accountNo, e);
|
|
|
|
|
+ Thread.currentThread().interrupt();
|
|
|
} catch (Exception e) {
|
|
} catch (Exception e) {
|
|
|
log.error("处理账号异常: accountNo={}", accountNo, e);
|
|
log.error("处理账号异常: accountNo={}", accountNo, e);
|
|
|
} finally {
|
|
} finally {
|
|
|
- // 确保 response 被安全关闭
|
|
|
|
|
if (response != null) {
|
|
if (response != null) {
|
|
|
try {
|
|
try {
|
|
|
response.close();
|
|
response.close();
|