1481 lines
77 KiB
Java
1481 lines
77 KiB
Java
package com.saye.hospitalgd.scheduler.jobMethod;
|
||
|
||
import cn.hutool.core.io.FileUtil;
|
||
import cn.hutool.core.util.StrUtil;
|
||
import com.saye.hospitalgd.commons.date.DateDUtil;
|
||
import com.saye.hospitalgd.commons.getBean.GetBeanUtil;
|
||
import com.saye.hospitalgd.commons.log.LogUtil;
|
||
import com.saye.hospitalgd.commons.string.StringDUtil;
|
||
import com.saye.hospitalgd.entity.BankbillHistory;
|
||
import com.saye.hospitalgd.service.BankbillGetinfoService;
|
||
import com.saye.hospitalgd.service.BankbillHistoryService;
|
||
import com.saye.hospitalgd.service.ThirdSftpConfigService;
|
||
import com.saye.hospitalgd.service.impl.BankbillGetinfoServiceImpl;
|
||
import com.saye.hospitalgd.service.impl.BankbillHistoryServiceImpl;
|
||
import com.saye.hospitalgd.service.impl.ThirdSftpConfigServiceImpl;
|
||
import com.saye.hospitalgd.service.system.ServiceParamsService;
|
||
import com.saye.hospitalgd.service.system.impl.ServiceParamsServiceImpl;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.apache.commons.lang3.StringUtils;
|
||
import org.apache.http.HttpEntity;
|
||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||
import org.apache.http.client.methods.HttpPost;
|
||
import org.apache.http.entity.StringEntity;
|
||
import org.apache.http.impl.client.CloseableHttpClient;
|
||
import org.apache.http.impl.client.HttpClients;
|
||
import org.apache.http.util.EntityUtils;
|
||
import org.dom4j.Document;
|
||
import org.dom4j.DocumentHelper;
|
||
import org.dom4j.Element;
|
||
import org.springframework.util.CollectionUtils;
|
||
|
||
import org.apache.poi.ss.usermodel.*;
|
||
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
|
||
|
||
import java.io.*;
|
||
import java.net.URI;
|
||
import java.nio.charset.Charset;
|
||
import java.nio.charset.StandardCharsets;
|
||
import java.util.*;
|
||
import java.util.zip.ZipEntry;
|
||
import java.util.zip.ZipInputStream;
|
||
|
||
/**
|
||
* @author thuang
|
||
* @version 1.0
|
||
* @description: 获取商户POS通对账单 调用dmz服务器部署的接口
|
||
* @date 2021/9/13 14:46
|
||
*/
|
||
@Slf4j
|
||
public class BankGetDataMethodByJHLZF {
|
||
|
||
public HashMap<Object, Object> getDate(String id, String name, String trade_date, HashMap<Object, Object> map) {
|
||
HashMap<Object, Object> resultMap = new HashMap<>();
|
||
String errCode = "0";
|
||
String errMsg = "";
|
||
|
||
BankbillHistoryService bankbillHistoryService = GetBeanUtil.getBean(BankbillHistoryServiceImpl.class);
|
||
|
||
BankbillGetinfoService bankbillGetinfoService = GetBeanUtil.getBean(BankbillGetinfoServiceImpl.class);
|
||
|
||
ServiceParamsService serviceParamsService = GetBeanUtil.getBean(ServiceParamsServiceImpl.class);
|
||
ThirdSftpConfigService thirdSftpConfigService = GetBeanUtil.getBean(ThirdSftpConfigServiceImpl.class);
|
||
|
||
boolean isOk = false;
|
||
|
||
|
||
//重新执行最多10次 还没获取到肯定有问题 不执行了
|
||
//如果有传入时间 那就只执行一次 获取不到就还是获取不到
|
||
int num = "".equals(trade_date) ? 0 : 9;
|
||
while (!isOk && num < 10) {
|
||
num++;
|
||
|
||
String dateStr = "";
|
||
//如果有传入时间 那就使用传入时间
|
||
if ("".equals(trade_date)) {
|
||
Calendar calendar = Calendar.getInstance();
|
||
calendar.setTime(new Date());
|
||
calendar.add(Calendar.DATE, -1);
|
||
Date time = calendar.getTime();
|
||
|
||
trade_date = DateDUtil.DateToStr(DateDUtil.yyyy_MM_dd, time);
|
||
dateStr = DateDUtil.DateToStr(DateDUtil.yyyyMMdd, time);
|
||
} else {
|
||
Date date = DateDUtil.strToDate(DateDUtil.yyyy_MM_dd, trade_date);
|
||
dateStr = DateDUtil.DateToStr(DateDUtil.yyyyMMdd, date);
|
||
}
|
||
|
||
|
||
//开始请求海南外联接口,生成账单文件
|
||
|
||
String cust_id = StringDUtil.changeNullToEmpty(map.get("CUST_ID"));
|
||
String user_id = StringDUtil.changeNullToEmpty(map.get("USER_ID"));
|
||
String tx_code = StringDUtil.changeNullToEmpty(map.get("TX_CODE"));
|
||
String password = StringDUtil.changeNullToEmpty(map.get("PASSWORD"));
|
||
String language = StringDUtil.changeNullToEmpty(map.get("LANGUAGE"));
|
||
String wlip = StringDUtil.changeNullToEmpty(map.get("WLIP"));
|
||
String wlport = StringDUtil.changeNullToEmpty(map.get("WLPORT"));
|
||
|
||
// 验证和清理XML字段,防止XML解析错误
|
||
if (language == null || language.trim().isEmpty()) {
|
||
language = "CN"; // 默认值
|
||
} else {
|
||
// 清理可能导致XML格式错误的字符
|
||
language = language.replaceAll("[<>&\"']", "").trim();
|
||
if (language.isEmpty()) {
|
||
language = "CN";
|
||
}
|
||
}
|
||
|
||
// 清理其他可能包含特殊字符的字段
|
||
cust_id = cust_id.replaceAll("[<>&\"']", "");
|
||
user_id = user_id.replaceAll("[<>&\"']", "");
|
||
tx_code = tx_code.replaceAll("[<>&\"']", "");
|
||
password = password.replaceAll("[<>&\"']", "");
|
||
|
||
String requset_sn_sc = String.valueOf(System.currentTimeMillis());
|
||
|
||
log.info("传去的时间是dateStr:" + dateStr);
|
||
String requestXml = "<?xml version=\"1.0\" encoding=\"GB2312\" standalone=\"yes\" ?> \n" +
|
||
"<TX> \n" +
|
||
" <REQUEST_SN>" + requset_sn_sc + "</REQUEST_SN> \n" +
|
||
" <CUST_ID>" + cust_id + "</CUST_ID> \n" +
|
||
" <USER_ID>" + user_id + "</USER_ID> \n" +
|
||
" <PASSWORD>" + password + "</PASSWORD> \n" +
|
||
" <TX_CODE>" + tx_code + "</TX_CODE> \n" +
|
||
" <LANGUAGE>" + language + "</LANGUAGE> \n" +
|
||
" <TX_INFO> \n" +
|
||
" <DATE>" + dateStr + "</DATE> \n" +
|
||
" <KIND>1</KIND> \n" +
|
||
" <FILETYPE>1</FILETYPE> \n" +
|
||
" <TYPE></TYPE> \n" +
|
||
" <NORDERBY></NORDERBY> \n" +
|
||
" <POS_CODE></POS_CODE> \n" +
|
||
" <ORDER></ORDER> \n" +
|
||
" <STATUS></STATUS> \n" +
|
||
" <BILL_FLAG></BILL_FLAG> \n" +
|
||
" <Mrch_No></Mrch_No> \n" +
|
||
" <GROUP_FLAG></GROUP_FLAG> \n" +
|
||
" <TXN_TPCD></TXN_TPCD> \n" +
|
||
" </TX_INFO> \n" +
|
||
"</TX> ";
|
||
String params = "requestXml=" + requestXml;
|
||
String ipAdress = wlip + ":" + wlport;
|
||
log.info("拼接的ip是:" + ipAdress);
|
||
String fileName = "";
|
||
|
||
try {
|
||
URI uri = new URI(ipAdress.trim());
|
||
CloseableHttpClient closeableHttpClient = HttpClients.createDefault();
|
||
HttpPost httpPost = new HttpPost(uri);
|
||
httpPost.setHeader("content-type", "application/x-www-form-urlencoded");
|
||
httpPost.setHeader("Connection", "close");
|
||
StringEntity entity = new StringEntity(params, "GB18030");
|
||
log.info("发送的数据是entity:" + entity);
|
||
httpPost.setEntity(entity);
|
||
|
||
CloseableHttpResponse response = closeableHttpClient.execute(httpPost);
|
||
|
||
// 增强响应读取,防止截断
|
||
String s;
|
||
try {
|
||
HttpEntity responseEntity = response.getEntity();
|
||
if (responseEntity != null) {
|
||
long contentLength = responseEntity.getContentLength();
|
||
log.info("主接口响应内容长度: " + contentLength);
|
||
|
||
// 使用流式读取,避免截断
|
||
try (InputStream inputStream = responseEntity.getContent();
|
||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||
|
||
byte[] buffer = new byte[1024]; // 1KB缓冲区,更小更精确
|
||
int bytesRead;
|
||
int totalBytesRead = 0;
|
||
|
||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||
outputStream.write(buffer, 0, bytesRead);
|
||
totalBytesRead += bytesRead;
|
||
}
|
||
|
||
// 获取原始字节数组
|
||
byte[] responseBytes = outputStream.toByteArray();
|
||
log.info("主接口流式读取完成,总字节数: " + totalBytesRead + ", 字节数组长度: " + responseBytes.length);
|
||
|
||
// 使用ISO-8859-1编码确保字节数和字符数一致
|
||
s = new String(responseBytes, "ISO-8859-1");
|
||
log.info("主接口使用ISO-8859-1编码,字符串长度: " + s.length());
|
||
|
||
// 检查是否可能被截断
|
||
if (contentLength > 0 && totalBytesRead < contentLength) {
|
||
log.warn("主接口响应可能被截断,期望长度: " + contentLength + ", 实际读取: " + totalBytesRead);
|
||
}
|
||
}
|
||
} else {
|
||
s = "";
|
||
log.warn("主接口响应实体为空");
|
||
}
|
||
} finally {
|
||
// 确保响应被完全消费
|
||
EntityUtils.consumeQuietly(response.getEntity());
|
||
}
|
||
|
||
log.info("接受到的数据是s:" + s);
|
||
|
||
// 增强XML格式修复和验证
|
||
Document document;
|
||
try {
|
||
// 首先清理响应数据,移除可能的非XML内容
|
||
s = cleanXmlResponse(s);
|
||
|
||
// 尝试解析XML
|
||
document = DocumentHelper.parseText(s);
|
||
} catch (Exception e) {
|
||
log.error("XML解析错误,原始响应: " + s, e);
|
||
try {
|
||
// 尝试修复常见的XML格式问题
|
||
s = fixXmlFormat(s);
|
||
log.info("修复后的XML: " + s);
|
||
document = DocumentHelper.parseText(s);
|
||
} catch (Exception e2) {
|
||
log.error("XML修复后仍然解析失败", e2);
|
||
throw new RuntimeException("XML解析失败,无法处理响应数据: " + s, e2);
|
||
}
|
||
}
|
||
|
||
Iterator iterator = document.nodeIterator();
|
||
|
||
|
||
Element tx = (Element) iterator.next();
|
||
Element return_code_F = tx.element("RETURN_CODE");
|
||
Element return_msg_F = tx.element("RETURN_MSG");
|
||
if (return_code_F != null) {
|
||
if (!return_code_F.getText().equals("000000")) {
|
||
errCode = "999";
|
||
errMsg = return_msg_F.getText();
|
||
log.info("获取到的返回code是" + return_code_F);
|
||
} else {
|
||
Iterator tx_info = tx.elementIterator("TX_INFO");
|
||
Element txInfo = (Element) tx_info.next();
|
||
|
||
Element file_name = txInfo.element("FILE_NAME");
|
||
fileName = file_name.getText();
|
||
|
||
boolean exist = FileUtil.exist("C:" + File.separator + "CCB_EBSClient" + File.separator + "download" + File.separator + fileName);
|
||
|
||
Boolean fileIsExist = false;
|
||
|
||
if (!exist) { //文件不存在开始下载
|
||
log.info("文件不存在,开始下载: " + fileName);
|
||
HashMap<Object, Object> searchMap = new HashMap<>();
|
||
searchMap.put("FUBS", "2");
|
||
List<HashMap<Object, Object>> wlConfigList = thirdSftpConfigService.findWLIF(searchMap);
|
||
HashMap<Object, Object> configMap = wlConfigList.get(0);
|
||
String txCode = StringDUtil.changeNullToEmpty(configMap.get("TX_CODE"));
|
||
String filepath = StringDUtil.changeNullToEmpty(configMap.get("FILEPATH"));
|
||
String requset_sn_xz = String.valueOf(System.currentTimeMillis());
|
||
String re = "<?xml version=\"1.0\" encoding=\"GB2312\" standalone=\"yes\" ?> \n" +
|
||
"<TX> \n" +
|
||
" <REQUEST_SN>" + requset_sn_xz + "</REQUEST_SN> \n" +
|
||
" <CUST_ID>" + cust_id + "</CUST_ID> \n" +
|
||
" <USER_ID>" + user_id + "</USER_ID> \n" +
|
||
" <PASSWORD>" + password + "</PASSWORD> \n" +
|
||
" <TX_CODE>" + txCode + "</TX_CODE> \n" +
|
||
" <LANGUAGE>CN</LANGUAGE> \n" +
|
||
" <TX_INFO> \n" +
|
||
" <SOURCE>" + fileName + "</SOURCE> \n" +
|
||
" <FILEPATH>" + filepath + "</FILEPATH> \n" +
|
||
" <LOCAL_REMOTE>0</LOCAL_REMOTE> \n" +
|
||
" </TX_INFO> \n" +
|
||
"</TX> \n";
|
||
String ps = "requestXml=" + re;
|
||
|
||
Element return_code = null;
|
||
Element return_msg = null;
|
||
URI uri_d = new URI(ipAdress.trim());
|
||
CloseableHttpClient closeableHttpClientD = HttpClients.createDefault();
|
||
HttpPost httpPostD = new HttpPost(uri_d);
|
||
httpPostD.setHeader("content-type", "application/x-www-form-urlencoded");
|
||
httpPostD.setHeader("Connection", "close");
|
||
StringEntity entityD = new StringEntity(ps, "GB18030");
|
||
httpPostD.setEntity(entityD);
|
||
|
||
CloseableHttpResponse responseD = closeableHttpClientD.execute(httpPostD);
|
||
|
||
// 增强响应读取,防止截断
|
||
String s_d;
|
||
try {
|
||
HttpEntity responseEntityD = responseD.getEntity();
|
||
if (responseEntityD != null) {
|
||
long contentLength = responseEntityD.getContentLength();
|
||
log.info("下载接口响应内容长度: " + contentLength);
|
||
|
||
// 使用流式读取,避免截断
|
||
try (InputStream inputStream = responseEntityD.getContent();
|
||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
|
||
|
||
byte[] buffer = new byte[1024]; // 1KB缓冲区,更小更精确
|
||
int bytesRead;
|
||
int totalBytesRead = 0;
|
||
|
||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||
outputStream.write(buffer, 0, bytesRead);
|
||
totalBytesRead += bytesRead;
|
||
log.debug("读取了 " + bytesRead + " 字节,累计: " + totalBytesRead);
|
||
}
|
||
|
||
// 获取原始字节数组
|
||
byte[] responseBytes = outputStream.toByteArray();
|
||
log.info("流式读取完成,总字节数: " + totalBytesRead + ", 字节数组长度: " + responseBytes.length);
|
||
|
||
// 首先检查十六进制模式,确定是否需要字节级修复
|
||
StringBuilder hexCheck = new StringBuilder();
|
||
for (int i = Math.max(0, responseBytes.length - 20); i < responseBytes.length; i++) {
|
||
hexCheck.append(String.format("%02X ", responseBytes[i]));
|
||
}
|
||
String hexStr = hexCheck.toString();
|
||
log.info("响应尾部20字节(HEX): " + hexStr);
|
||
|
||
// 检查是否存在LANGUAGE标签截断的十六进制模式
|
||
// </LANG = 3C 2F 4C 41 4E 47
|
||
// 完整的</LANGUAGE> = 3C 2F 4C 41 4E 47 55 41 47 45 3E
|
||
if (hexStr.contains("3C 2F 4C 41 4E 47") && !hexStr.contains("3C 2F 4C 41 4E 47 55 41 47 45 3E")) {
|
||
log.warn("通过十六进制检测到LANGUAGE标签截断,进行字节级修复");
|
||
s_d = fixLanguageTagFromBytes(responseBytes);
|
||
log.info("字节级修复完成,新字符串长度: " + s_d.length());
|
||
} else {
|
||
// 没有截断,直接使用ISO-8859-1编码(字节数和字符数一致)
|
||
s_d = new String(responseBytes, "ISO-8859-1");
|
||
log.info("使用ISO-8859-1编码,字符串长度: " + s_d.length());
|
||
|
||
// 再次检查字符串内容是否有截断
|
||
if (s_d.contains("<LANGUAGE>CN</LANG") && !s_d.contains("</LANGUAGE>")) {
|
||
log.warn("字符串检测到LANGUAGE标签截断,进行字节级修复");
|
||
s_d = fixLanguageTagFromBytes(responseBytes);
|
||
log.info("字符串检测修复完成,新字符串长度: " + s_d.length());
|
||
}
|
||
}
|
||
|
||
log.info("最终字符串长度: " + s_d.length());
|
||
|
||
// 验证修复结果
|
||
if (s_d.contains("<LANGUAGE>CN</LANGUAGE>") && s_d.contains("</TX>")) {
|
||
log.info("XML修复成功,包含完整的LANGUAGE标签和TX根元素");
|
||
} else if (s_d.contains("<LANGUAGE>CN</LANG") && !s_d.contains("</LANGUAGE>")) {
|
||
log.error("XML修复失败,LANGUAGE标签仍然截断");
|
||
// 最后尝试字符串级别的修复
|
||
s_d = s_d.replace("<LANGUAGE>CN</LANG", "<LANGUAGE>CN</LANGUAGE>");
|
||
if (!s_d.contains("</TX>")) {
|
||
s_d = s_d + "</TX>";
|
||
}
|
||
log.info("应急字符串修复完成");
|
||
}
|
||
|
||
// 检查是否可能被截断
|
||
if (contentLength > 0 && totalBytesRead < contentLength) {
|
||
log.warn("响应可能被截断,期望长度: " + contentLength + ", 实际读取: " + totalBytesRead);
|
||
}
|
||
}
|
||
} else {
|
||
s_d = "";
|
||
log.warn("下载接口响应实体为空");
|
||
}
|
||
} finally {
|
||
// 确保响应被完全消费
|
||
EntityUtils.consumeQuietly(responseD.getEntity());
|
||
}
|
||
|
||
log.info("下载接口响应: " + s_d);
|
||
// 增强XML格式修复和验证
|
||
Document documentD;
|
||
try {
|
||
// 首先清理响应数据,移除可能的非XML内容
|
||
s_d = cleanXmlResponse(s_d);
|
||
|
||
// 尝试解析XML
|
||
documentD = DocumentHelper.parseText(s_d);
|
||
} catch (Exception e) {
|
||
log.error("下载XML解析错误,原始响应: " + s_d, e);
|
||
try {
|
||
// 尝试修复常见的XML格式问题
|
||
s_d = fixXmlFormat(s_d);
|
||
log.info("修复后的下载XML: " + s_d);
|
||
documentD = DocumentHelper.parseText(s_d);
|
||
} catch (Exception e2) {
|
||
log.error("下载XML修复后仍然解析失败", e2);
|
||
throw new RuntimeException("下载XML解析失败,无法处理响应数据: " + s_d, e2);
|
||
}
|
||
}
|
||
Iterator iteratorD = documentD.nodeIterator();
|
||
Element txD = (Element) iteratorD.next();
|
||
return_code = txD.element("RETURN_CODE");
|
||
return_msg = txD.element("RETURN_MSG");
|
||
if (return_code != null) {
|
||
if (!return_code.getText().equals("000000")) {
|
||
errCode = "999";
|
||
errMsg = "文件下载失败: " + return_msg.getText();
|
||
log.error("文件下载失败,返回码: " + return_code.getText() + ", 错误信息: " + return_msg.getText());
|
||
} else {
|
||
log.info("下载接口返回成功,准备从中专服务器下载ZIP文件...");
|
||
|
||
// 调用外联平台下载接口成功后,从中专服务器下载ZIP文件到本地
|
||
try {
|
||
log.info("准备调用中专服务器下载接口获取ZIP文件: " + fileName);
|
||
boolean downloadSuccess = downloadZipFromIntermediaryServer(fileName, serviceParamsService);
|
||
if (downloadSuccess) {
|
||
log.info("从中专服务器下载ZIP文件成功: " + fileName);
|
||
fileIsExist = true; // 标记文件已存在
|
||
} else {
|
||
log.warn("从中专服务器下载ZIP文件失败,等待本地文件生成: " + fileName);
|
||
// 如果中专服务器下载失败,等待本地文件生成
|
||
fileIsExist = waitForFileGeneration(fileName, 60);
|
||
if (!fileIsExist) {
|
||
errCode = "999";
|
||
errMsg = "文件下载超时,文件未生成: " + fileName;
|
||
log.error("文件下载超时: " + fileName);
|
||
}
|
||
}
|
||
} catch (Exception downloadEx) {
|
||
log.error("调用中专服务器下载接口异常,等待本地文件生成: " + fileName, downloadEx);
|
||
// 如果中专服务器下载异常,等待本地文件生成
|
||
fileIsExist = waitForFileGeneration(fileName, 60);
|
||
if (!fileIsExist) {
|
||
errCode = "999";
|
||
errMsg = "文件下载超时,文件未生成: " + fileName;
|
||
log.error("文件下载超时: " + fileName);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (fileIsExist || exist) { //确定文件存在,开始读取数据
|
||
String filePath = "C:" + File.separator + "CCB_EBSClient" + File.separator + "download" + File.separator + fileName;
|
||
log.info("开始读取文件: " + filePath);
|
||
|
||
// 再次验证文件是否真实存在且可读
|
||
File file = new File(filePath);
|
||
if (!file.exists()) {
|
||
errCode = "999";
|
||
errMsg = "文件不存在: " + filePath;
|
||
log.error("文件不存在: " + filePath);
|
||
continue; // 继续下一次循环重试
|
||
}
|
||
|
||
if (!file.canRead()) {
|
||
errCode = "999";
|
||
errMsg = "文件无法读取: " + filePath;
|
||
log.error("文件无法读取: " + filePath);
|
||
continue; // 继续下一次循环重试
|
||
}
|
||
|
||
if (file.length() == 0) {
|
||
errCode = "999";
|
||
errMsg = "文件为空: " + filePath;
|
||
log.error("文件为空: " + filePath);
|
||
continue; // 继续下一次循环重试
|
||
}
|
||
|
||
log.info("文件验证通过,大小: " + file.length() + " 字节");
|
||
|
||
List<String> txtList = null;
|
||
FileInputStream stream = null;
|
||
ZipInputStream zipInputStream = null;
|
||
BufferedReader br = null;
|
||
|
||
try {
|
||
stream = new FileInputStream(filePath);
|
||
zipInputStream = new ZipInputStream(new BufferedInputStream(stream), Charset.forName("GBK"));
|
||
|
||
ZipEntry nextEntry = zipInputStream.getNextEntry();
|
||
if (nextEntry == null) {
|
||
throw new RuntimeException("ZIP文件中没有找到任何条目");
|
||
}
|
||
|
||
String entryName = nextEntry.getName();
|
||
log.info("读取的ZIP条目: " + entryName + ", 大小: " + nextEntry.getSize());
|
||
|
||
// 判断文件类型
|
||
if (entryName.toLowerCase().endsWith(".xlsx") || entryName.toLowerCase().endsWith(".xls")) {
|
||
log.info("检测到Excel文件,使用POI解析: " + entryName);
|
||
txtList = parseExcelFromZip(zipInputStream, entryName);
|
||
log.info("成功从Excel解析 " + (txtList != null ? txtList.size() : 0) + " 行数据");
|
||
} else {
|
||
// 原来的文本文件解析逻辑
|
||
log.info("检测到文本文件,使用文本解析: " + entryName);
|
||
br = new BufferedReader(new InputStreamReader(zipInputStream, StandardCharsets.UTF_8));
|
||
|
||
String line;
|
||
txtList = new ArrayList<>();
|
||
int lineCount = 0;
|
||
//内容不为空,输出
|
||
while ((line = br.readLine()) != null) {
|
||
txtList.add(line);
|
||
lineCount++;
|
||
}
|
||
log.info("成功读取 " + lineCount + " 行数据");
|
||
}
|
||
|
||
} catch (Exception e) {
|
||
log.error("文件读取失败: " + filePath, e);
|
||
errCode = "999";
|
||
errMsg = "文件读取失败: " + e.getMessage();
|
||
continue; // 继续下一次循环重试
|
||
} finally {
|
||
// 确保资源正确关闭
|
||
try {
|
||
if (br != null) br.close();
|
||
if (zipInputStream != null) zipInputStream.close();
|
||
if (stream != null) stream.close();
|
||
} catch (Exception e) {
|
||
log.warn("关闭文件流时出错", e);
|
||
}
|
||
}
|
||
log.info("继续执行程序3");
|
||
List<BankbillHistory> bankbillHistoryList = new ArrayList<>();
|
||
|
||
// 跳过前24行(Excel的前24行是标题和说明,第24行是表头,从第25行开始是数据)
|
||
for (int i = 24; i < txtList.size(); i++) {
|
||
try {
|
||
String d = txtList.get(i);
|
||
|
||
// 跳过空行
|
||
if (d == null || d.trim().isEmpty()) {
|
||
continue;
|
||
}
|
||
|
||
String[] s1 = d.split("\t");
|
||
|
||
// 跳过列数不足的行(至少需要10列才能有交易金额)
|
||
if (s1.length < 10) {
|
||
log.debug("第 " + (i + 1) + " 行列数不足,跳过");
|
||
continue;
|
||
}
|
||
|
||
// 检查是否是汇总行(包含"小计"、"合计"、"终端小计"等关键字)
|
||
String rowText = d.toLowerCase();
|
||
if (rowText.contains("小计") || rowText.contains("合计") ||
|
||
rowText.contains("终端小计") || rowText.contains("pos编号") ||
|
||
rowText.contains("笔数") ||
|
||
s1.length > 6 && (s1[6].contains("小计") || s1[6].contains("合计"))) {
|
||
log.debug("第 " + (i + 1) + " 行是汇总行,跳过: " + (s1.length > 6 ? s1[6] : ""));
|
||
continue;
|
||
}
|
||
|
||
// 检查第13列(交易日期,Excel的N列)是否为空
|
||
if (s1.length <= 13 || s1[13] == null || s1[13].trim().isEmpty()) {
|
||
log.debug("第 " + (i + 1) + " 行交易日期为空,跳过");
|
||
continue;
|
||
}
|
||
|
||
String jyrq = s1[13].trim();
|
||
|
||
// 检查第13列是否包含日期(简单检查是否包含"-")
|
||
if (!jyrq.contains("-")) {
|
||
log.debug("第 " + (i + 1) + " 行交易日期不包含日期分隔符,跳过");
|
||
continue;
|
||
}
|
||
|
||
// 检查交易金额列(第20列,对应Excel的U列)是否有效
|
||
String amountStr = s1.length > 20 ? s1[20].trim() : "";
|
||
if (amountStr.isEmpty() || amountStr.equals("null") || amountStr.equals("0") || amountStr.equals("0.00")) {
|
||
log.debug("第 " + (i + 1) + " 行交易金额无效: [" + amountStr + "],跳过");
|
||
continue;
|
||
}
|
||
|
||
// 尝试解析金额,确保是有效数字
|
||
try {
|
||
double amount = Double.parseDouble(amountStr);
|
||
if (Math.abs(amount) < 0.01) {
|
||
log.debug("第 " + (i + 1) + " 行交易金额太小: " + amount + ",跳过");
|
||
continue;
|
||
}
|
||
} catch (NumberFormatException e) {
|
||
log.debug("第 " + (i + 1) + " 行交易金额不是有效数字: [" + amountStr + "],跳过");
|
||
continue;
|
||
}
|
||
|
||
log.info("正在处理第 " + (i + 1) + " 行数据");
|
||
|
||
BankbillHistory bankbillHistory = new BankbillHistory();
|
||
|
||
// 根据你提供的Excel列位置映射:
|
||
// 终端号: CD列(索引2-3), 发卡行: EFGH列(索引4-7), 卡种: IJ列(索引8-9),
|
||
// 卡号: KLM列(索引10-12), 交易日期: N列(索引13), 交易时间: P列(索引15),
|
||
// 交易类型: QR列(索引16-17), 授权号: ST列(索引18-19), 交易金额: U列(索引20)⭐,
|
||
// 小费: VW列(索引21-22), 分期期数: X列(索引23), 银行手续费: Y列(索引24),
|
||
// DCC返还手续费: Z列(索引25), 划账金额: AA列(索引26), 凭证号: AB列(索引27),
|
||
// 批次号: AC列(索引28), POS交易序号: AD列(索引29)⭐, 结算账号: AE列(索引30),
|
||
// 订单号: AF列(索引31), 柜台编号: AG列(索引32), 系统参考号: AH列(索引33),
|
||
// 持卡人姓名: AI列(索引34), 付款凭证号: AJ列(索引35), 备注1: AK列(索引36), 备注2: AL列(索引37)
|
||
|
||
// 交易日期和时间
|
||
jyrq = s1.length > 13 ? s1[13] : "";
|
||
String jysj = s1.length > 15 ? s1[15] : "";
|
||
|
||
// 如果交易时间为空,尝试从交易日期中提取时间部分
|
||
if (jysj == null || jysj.trim().isEmpty()) {
|
||
// 检查交易日期是否包含时间(格式如:2025-10-23 14:30:15)
|
||
if (jyrq != null && jyrq.contains(" ")) {
|
||
String[] dateTimeParts = jyrq.split(" ", 2);
|
||
if (dateTimeParts.length == 2) {
|
||
jyrq = dateTimeParts[0]; // 日期部分
|
||
jysj = dateTimeParts[1]; // 时间部分
|
||
}
|
||
} else {
|
||
// 如果还是空,设置默认值
|
||
jysj = "00:00:00";
|
||
}
|
||
}
|
||
|
||
bankbillHistory.setCJyrq(jyrq); // N列(索引13): 交易日期
|
||
bankbillHistory.setCJysj(jysj); // P列(索引15): 交易时间
|
||
bankbillHistory.setCJyje(s1.length > 20 ? s1[20] : "0"); // U列(索引20): 交易金额
|
||
|
||
// QR列(索引16): 支付方式(Excel中就是微信支付、支付宝等)
|
||
String zffs = s1.length > 16 ? s1[16].trim() : "建行龙支付";
|
||
if (zffs.isEmpty()) {
|
||
zffs = "建行龙支付"; // 默认值
|
||
}
|
||
bankbillHistory.setCZffs(zffs); // 支付方式(直接从Excel读取)
|
||
|
||
// 交易类型:根据交易金额正负判断
|
||
String jylx = "消费"; // 默认交易类型
|
||
try {
|
||
double amount = Double.parseDouble(s1.length > 20 ? s1[20] : "0");
|
||
if (amount < 0) {
|
||
jylx = "退款";
|
||
}
|
||
} catch (Exception e) {
|
||
// 解析失败,使用默认值
|
||
}
|
||
bankbillHistory.setCJylx(jylx); // 交易类型(根据金额正负判断)
|
||
|
||
// AD列(索引29): POS交易序号 (作为流水号和银商订单号)
|
||
bankbillHistory.setCLsh(s1.length > 29 ? s1[29] : ""); // 流水号
|
||
bankbillHistory.setCYsddh(s1.length > 29 ? s1[29] : ""); // 银商订单号
|
||
|
||
// AF列(索引31): 订单号 (作为商户订单号)
|
||
bankbillHistory.setCShddh(s1.length > 31 ? s1[31] : ""); // 商户订单号
|
||
|
||
// AH列(索引33): 系统参考号
|
||
bankbillHistory.setCCkh(s1.length > 33 ? s1[33] : ""); // 参考号
|
||
|
||
// KLM列(索引10): 卡号-序列号
|
||
bankbillHistory.setCCard(s1.length > 10 ? s1[10] : ""); // 卡号
|
||
|
||
// IJ列(索引8): 卡种
|
||
bankbillHistory.setCKlx(s1.length > 8 ? s1[8] : ""); // 卡类型
|
||
|
||
// EFGH列(索引4): 发卡行
|
||
bankbillHistory.setCFkh(s1.length > 4 ? s1[4] : ""); // 发卡行
|
||
|
||
// 支付方式已经在前面设置(从第16列读取),这里不再覆盖
|
||
|
||
// VW列(索引21): 小费
|
||
bankbillHistory.setCSxf(s1.length > 21 ? s1[21] : "0"); // 小费
|
||
|
||
// X列(索引23): 分期期数
|
||
bankbillHistory.setCFqqs(s1.length > 23 ? s1[23] : ""); // 分期期数
|
||
|
||
// Y列(索引24): 银行手续费
|
||
bankbillHistory.setCFqsxf(s1.length > 24 ? s1[24] : "0"); // 分期手续费
|
||
|
||
// AA列(索引26): 划账金额 (作为清算金额和实际支付金额)
|
||
String haje = s1.length > 26 ? s1[26] : (s1.length > 20 ? s1[20] : "0");
|
||
bankbillHistory.setCQsje(haje); // 清算金额
|
||
bankbillHistory.setCSjzfje(haje); // 实际支付金额
|
||
|
||
// 清算日期 = 交易日期
|
||
bankbillHistory.setCQsrq(s1.length > 13 ? s1[13] : ""); // 清算日期
|
||
|
||
// 备注字段 (AK列:备注1, AL列:备注2)
|
||
String bz = "";
|
||
if (s1.length > 36 && s1[36] != null && !s1[36].trim().isEmpty()) {
|
||
bz = s1[36];
|
||
}
|
||
if (s1.length > 37 && s1[37] != null && !s1[37].trim().isEmpty()) {
|
||
bz += (bz.isEmpty() ? "" : ";") + s1[37];
|
||
}
|
||
bankbillHistory.setCBzzd(bz); // 备注字段
|
||
|
||
// 其他固定字段
|
||
bankbillHistory.setCQbyhje("0"); // 钱包优惠金额
|
||
bankbillHistory.setCShyhje("0"); // 商户优惠金额
|
||
bankbillHistory.setCYjylsh(""); // 原交易流水号(空)
|
||
bankbillHistory.setCFqfwf(""); // 分期服务方(空)
|
||
bankbillHistory.setCFqfxf(""); // 分期付息方(空)
|
||
bankbillHistory.setCQtyhje("0"); // 其他优惠金额
|
||
bankbillHistory.setCThddh(""); // 退货订单号(空)
|
||
bankbillHistory.setCFkfy(""); // 付款附言(空)
|
||
bankbillHistory.setCFdjc(""); // 分店简称(空)
|
||
bankbillHistory.setCZddh(""); // 子订单号(空)
|
||
bankbillHistory.setBillTableName("建行龙支付对账单"); // 对账表名
|
||
|
||
bankbillHistoryList.add(bankbillHistory);
|
||
log.info("成功解析第 " + (i + 1) + " 行数据");
|
||
|
||
} catch (Exception e) {
|
||
log.error("处理第 " + (i + 1) + " 行数据时出错,跳过该行", e);
|
||
continue;
|
||
}
|
||
}
|
||
if (!CollectionUtils.isEmpty(bankbillHistoryList)) {
|
||
log.info("成功解析 " + bankbillHistoryList.size() + " 条建行龙支付对账数据");
|
||
// 先存一份原始的 这份只是留底查询
|
||
bankbillHistoryService.insertBankBillOriginal(bankbillHistoryList, trade_date, "建行龙支付对账单");
|
||
log.info("已保存原始对账数据到数据库");
|
||
// 再存一份修改的用于对账
|
||
bankbillHistoryService.insertAllBankHistory(bankbillHistoryList, trade_date, "建行龙支付对账单");
|
||
log.info("已保存对账数据到对账表");
|
||
isOk = true;
|
||
} else {
|
||
errCode = "999";
|
||
errMsg = "未查询到有效数据(可能是文件格式不正确)";
|
||
log.error("解析建行龙支付对账单失败: " + errMsg);
|
||
isOk = false;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
} catch (Exception e) {
|
||
errCode = "999";
|
||
if (StringUtils.isEmpty(errMsg)) {
|
||
errMsg = "外联平台接口调用失败!!!";
|
||
}
|
||
e.printStackTrace();
|
||
}
|
||
//海南外联接口调用成功后会生成账单文件,在请求一次外联接口下载到服务器。
|
||
log.info("继续执行程序4");
|
||
//记录数据获取记录
|
||
HashMap<Object, Object> addMap = new HashMap<>();
|
||
addMap.put("trade_date", trade_date);
|
||
addMap.put("quartz_id", id);
|
||
addMap.put("quartz_name", name);
|
||
addMap.put("bill_table_name", "建行龙支付对账单");
|
||
addMap.put("thirdConfigId", "999");
|
||
|
||
if (isOk) {
|
||
addMap.put("is_ok", "1");
|
||
} else {
|
||
addMap.put("is_ok", "0");
|
||
}
|
||
addMap.put("modify_time", DateDUtil.getCurrentDate(DateDUtil.yyyy_MM_dd_HH_mm_ss));
|
||
bankbillGetinfoService.insertBankbillGetinfo(addMap);
|
||
|
||
//判断是否成功 如果失败 执行休眠一小时
|
||
if (!isOk && num < 10) {
|
||
try {
|
||
//记录日志
|
||
LogUtil.error(this.getClass(), errMsg);
|
||
|
||
System.out.println("获取对账记录没有成功,1小时后重新执行");
|
||
Thread.sleep(1000 * 60 * 60);
|
||
} catch (Exception e) {
|
||
e.printStackTrace();
|
||
}
|
||
}
|
||
}
|
||
|
||
resultMap.put("errCode", errCode);
|
||
resultMap.put("errMsg", errMsg);
|
||
|
||
return resultMap;
|
||
}
|
||
|
||
/**
|
||
* 清理XML响应数据,移除可能的非XML内容
|
||
*/
|
||
private String cleanXmlResponse(String response) {
|
||
if (response == null || response.trim().isEmpty()) {
|
||
return response;
|
||
}
|
||
|
||
// 移除BOM标记
|
||
if (response.startsWith("\uFEFF")) {
|
||
response = response.substring(1);
|
||
}
|
||
|
||
// 查找XML声明的开始位置
|
||
int xmlStart = response.indexOf("<?xml");
|
||
if (xmlStart > 0) {
|
||
response = response.substring(xmlStart);
|
||
}
|
||
|
||
// 如果没有XML声明,查找根元素开始位置
|
||
if (xmlStart == -1) {
|
||
int rootStart = response.indexOf("<TX>");
|
||
if (rootStart > 0) {
|
||
response = response.substring(rootStart);
|
||
}
|
||
}
|
||
|
||
// 移除可能的尾部非XML内容
|
||
int lastTagEnd = response.lastIndexOf(">");
|
||
if (lastTagEnd > 0 && lastTagEnd < response.length() - 1) {
|
||
String afterLastTag = response.substring(lastTagEnd + 1).trim();
|
||
// 如果最后一个标签后还有非空白字符,可能是垃圾数据
|
||
if (!afterLastTag.isEmpty() && !afterLastTag.matches("\\s*")) {
|
||
response = response.substring(0, lastTagEnd + 1);
|
||
}
|
||
}
|
||
|
||
return response.trim();
|
||
}
|
||
|
||
/**
|
||
* 修复常见的XML格式问题
|
||
*/
|
||
private String fixXmlFormat(String xml) {
|
||
if (xml == null || xml.trim().isEmpty()) {
|
||
return xml;
|
||
}
|
||
|
||
log.info("开始修复XML,原始长度: " + xml.length());
|
||
|
||
// 确保有XML声明
|
||
if (!xml.trim().startsWith("<?xml")) {
|
||
xml = "<?xml version=\"1.0\" encoding=\"GB2312\"?>\n" + xml;
|
||
}
|
||
|
||
// 处理严重截断的情况 - 检查XML是否在标签中间被截断
|
||
if (xml.endsWith("<") || xml.matches(".*<[^>]*$")) {
|
||
log.warn("检测到XML在标签中被截断,尝试修复");
|
||
// 移除最后一个不完整的标签
|
||
int lastLessThan = xml.lastIndexOf('<');
|
||
if (lastLessThan > 0) {
|
||
xml = xml.substring(0, lastLessThan);
|
||
log.info("移除不完整标签后的XML: " + xml);
|
||
}
|
||
}
|
||
|
||
// 特殊处理:检测<LANGUAGE>CN</LANG这种特定截断模式
|
||
if (xml.contains("<LANGUAGE>CN</LANG") && !xml.contains("</LANGUAGE>")) {
|
||
log.warn("检测到LANGUAGE标签特定截断模式");
|
||
// 直接替换为完整的标签
|
||
xml = xml.replaceAll("<LANGUAGE>CN</LANG[^>]*$", "<LANGUAGE>CN</LANGUAGE>");
|
||
xml = xml.replaceAll("<LANGUAGE>CN</LANG(?![A-Z])", "<LANGUAGE>CN</LANGUAGE>");
|
||
log.info("修复LANGUAGE标签截断");
|
||
}
|
||
|
||
// 处理XML结尾被截断的情况
|
||
if (xml.endsWith("<LANGUAGE>CN</LANG")) {
|
||
log.warn("检测到XML结尾LANGUAGE标签截断");
|
||
xml = xml.replace("<LANGUAGE>CN</LANG", "<LANGUAGE>CN</LANGUAGE>");
|
||
log.info("修复XML结尾LANGUAGE标签");
|
||
}
|
||
|
||
// 处理XML在LANGUAGE标签中间被截断的情况
|
||
if (xml.endsWith("<LANGUAGE>") || xml.endsWith("<LANGUAGE>CN") || xml.endsWith("<LANGUAGE>CN<")) {
|
||
log.warn("检测到LANGUAGE标签内容截断");
|
||
if (xml.endsWith("<LANGUAGE>")) {
|
||
xml = xml.replace("<LANGUAGE>", "<LANGUAGE>CN</LANGUAGE>");
|
||
} else if (xml.endsWith("<LANGUAGE>CN")) {
|
||
xml = xml.replace("<LANGUAGE>CN", "<LANGUAGE>CN</LANGUAGE>");
|
||
} else if (xml.endsWith("<LANGUAGE>CN<")) {
|
||
xml = xml.replace("<LANGUAGE>CN<", "<LANGUAGE>CN</LANGUAGE>");
|
||
}
|
||
log.info("修复LANGUAGE标签内容截断");
|
||
}
|
||
|
||
// 处理标签名被截断的情况,如 </LANG 应该是 </LANGUAGE>
|
||
xml = xml.replaceAll("</LANG(?!UAGE)", "</LANGUAGE>");
|
||
xml = xml.replaceAll("<LANGUAGE([^>]*)>([^<]*)</LANG(?!UAGE)", "<LANGUAGE$1>$2</LANGUAGE>");
|
||
|
||
// 修复LANGUAGE标签问题 - 处理各种截断情况
|
||
// 特殊处理:如果发现CN</LANG模式,说明LANGUAGE标签被截断
|
||
if (xml.contains("CN</LANG") && !xml.contains("CN</LANGUAGE>")) {
|
||
xml = xml.replaceAll("CN</LANG[^>]*", "CN</LANGUAGE>");
|
||
log.info("修复CN</LANG截断问题");
|
||
}
|
||
|
||
// 处理<LANGUAGE>CN</LANG这种截断
|
||
xml = xml.replaceAll("<LANGUAGE([^>]*)>CN</LANG[^>]*", "<LANGUAGE$1>CN</LANGUAGE>");
|
||
|
||
// 处理<LANGUAGE>...内容被截断的情况,保留已有内容
|
||
xml = xml.replaceAll("<LANGUAGE([^>]*)>([^<]*?)(?=<(?!/LANGUAGE))", "<LANGUAGE$1>$2</LANGUAGE>");
|
||
xml = xml.replaceAll("<LANGUAGE([^>]*)>([^<]*?)$", "<LANGUAGE$1>$2</LANGUAGE>");
|
||
|
||
// 特殊处理:如果LANGUAGE标签内容为空但原始响应中有CN,尝试恢复
|
||
if (xml.contains("<LANGUAGE></LANGUAGE>") && xml.contains("CN")) {
|
||
// 查找CN在原始XML中的位置,如果在LANGUAGE标签附近,则恢复
|
||
String originalPart = xml.substring(Math.max(0, xml.indexOf("<LANGUAGE>") - 50),
|
||
Math.min(xml.length(), xml.indexOf("</LANGUAGE>") + 20));
|
||
if (originalPart.contains("CN")) {
|
||
xml = xml.replace("<LANGUAGE></LANGUAGE>", "<LANGUAGE>CN</LANGUAGE>");
|
||
log.info("恢复LANGUAGE标签中的CN内容");
|
||
}
|
||
}
|
||
|
||
// 修复其他可能未关闭的标签
|
||
String[] commonTags = {"REQUEST_SN", "CUST_ID", "USER_ID", "PASSWORD", "TX_CODE",
|
||
"RETURN_CODE", "RETURN_MSG", "FILE_NAME", "SOURCE", "FILEPATH", "TX_INFO"};
|
||
|
||
for (String tag : commonTags) {
|
||
// 修复未关闭的标签
|
||
xml = xml.replaceAll("<" + tag + "([^>]*)>([^<]*?)(?=<(?!/" + tag + "))",
|
||
"<" + tag + "$1>$2</" + tag + ">");
|
||
xml = xml.replaceAll("<" + tag + "([^>]*)>([^<]*?)$",
|
||
"<" + tag + "$1>$2</" + tag + ">");
|
||
}
|
||
|
||
// 移除重复的XML声明
|
||
String[] parts = xml.split("\\<\\?xml[^\\>]*\\?\\>");
|
||
if (parts.length > 2) {
|
||
StringBuilder sb = new StringBuilder();
|
||
sb.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>");
|
||
for (int i = 1; i < parts.length; i++) {
|
||
sb.append(parts[i]);
|
||
}
|
||
xml = sb.toString();
|
||
}
|
||
|
||
// 确保根元素完整
|
||
if (!xml.contains("</TX>") && xml.contains("<TX>")) {
|
||
xml = xml + "</TX>";
|
||
}
|
||
|
||
// 最后的激进修复:如果仍然有问题,尝试重构XML
|
||
if (!xml.contains("</TX>") || xml.contains("<LANGUAGE>CN</LANG")) {
|
||
log.warn("尝试激进修复:重构XML结构");
|
||
xml = reconstructXml(xml);
|
||
}
|
||
|
||
// 最后检查:如果仍然有未关闭的标签,尝试智能修复
|
||
if (xml.matches(".*<[^/>][^>]*$")) {
|
||
log.warn("仍有未关闭的标签,进行最终修复");
|
||
// 查找最后一个未关闭的标签
|
||
int lastOpenTag = xml.lastIndexOf('<');
|
||
if (lastOpenTag > 0) {
|
||
String beforeTag = xml.substring(0, lastOpenTag);
|
||
String tagPart = xml.substring(lastOpenTag);
|
||
|
||
// 如果是开始标签但没有结束,尝试关闭它
|
||
if (tagPart.matches("<[A-Z_]+[^>]*") && !tagPart.startsWith("</")) {
|
||
String tagName = tagPart.replaceAll("<([A-Z_]+).*", "$1");
|
||
xml = beforeTag + "<" + tagName + "></" + tagName + ">";
|
||
log.info("智能修复标签: " + tagName);
|
||
}
|
||
}
|
||
}
|
||
|
||
log.info("XML修复完成,修复后长度: " + xml.length());
|
||
return xml;
|
||
}
|
||
|
||
/**
|
||
* 重构XML结构 - 激进修复方法
|
||
*/
|
||
private String reconstructXml(String xml) {
|
||
log.info("开始重构XML结构");
|
||
|
||
// 提取关键信息
|
||
String requestSn = extractValue(xml, "REQUEST_SN");
|
||
String custId = extractValue(xml, "CUST_ID");
|
||
String txCode = extractValue(xml, "TX_CODE");
|
||
String returnCode = extractValue(xml, "RETURN_CODE");
|
||
String returnMsg = extractValue(xml, "RETURN_MSG");
|
||
String language = "CN"; // 默认值
|
||
|
||
// 如果能找到LANGUAGE标签的内容,使用它
|
||
String langValue = extractValue(xml, "LANGUAGE");
|
||
if (langValue != null && !langValue.isEmpty()) {
|
||
language = langValue;
|
||
}
|
||
|
||
// 重构完整的XML
|
||
StringBuilder reconstructed = new StringBuilder();
|
||
reconstructed.append("<?xml version=\"1.0\" encoding=\"GB18030\"?>\n");
|
||
reconstructed.append("<TX>");
|
||
|
||
if (requestSn != null) reconstructed.append("<REQUEST_SN>").append(requestSn).append("</REQUEST_SN>");
|
||
if (custId != null) reconstructed.append("<CUST_ID>").append(custId).append("</CUST_ID>");
|
||
if (txCode != null) reconstructed.append("<TX_CODE>").append(txCode).append("</TX_CODE>");
|
||
if (returnCode != null) reconstructed.append("<RETURN_CODE>").append(returnCode).append("</RETURN_CODE>");
|
||
if (returnMsg != null) reconstructed.append("<RETURN_MSG>").append(returnMsg).append("</RETURN_MSG>");
|
||
|
||
reconstructed.append("<LANGUAGE>").append(language).append("</LANGUAGE>");
|
||
reconstructed.append("</TX>");
|
||
|
||
String result = reconstructed.toString();
|
||
log.info("重构完成,新XML长度: " + result.length());
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 从XML中提取指定标签的值
|
||
*/
|
||
private String extractValue(String xml, String tagName) {
|
||
try {
|
||
String pattern = "<" + tagName + "([^>]*)>([^<]*?)(?=<|$)";
|
||
java.util.regex.Pattern p = java.util.regex.Pattern.compile(pattern);
|
||
java.util.regex.Matcher m = p.matcher(xml);
|
||
if (m.find()) {
|
||
String value = m.group(2);
|
||
log.debug("提取 " + tagName + ": " + value);
|
||
return value;
|
||
}
|
||
} catch (Exception e) {
|
||
log.warn("提取 " + tagName + " 失败: " + e.getMessage());
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* 专门修复LANGUAGE标签截断的字节级方法
|
||
*/
|
||
private String fixLanguageTagFromBytes(byte[] responseBytes) {
|
||
log.info("开始字节级修复LANGUAGE标签,原始字节数: " + responseBytes.length);
|
||
|
||
// 输出最后20个字节的十六进制用于调试
|
||
StringBuilder debugHex = new StringBuilder();
|
||
for (int i = Math.max(0, responseBytes.length - 20); i < responseBytes.length; i++) {
|
||
debugHex.append(String.format("%02X ", responseBytes[i]));
|
||
}
|
||
log.info("待修复的尾部字节: " + debugHex.toString());
|
||
|
||
// 查找 </LANG 的字节位置(从后往前找,更准确)
|
||
// </LANG = 3C 2F 4C 41 4E 47
|
||
byte[] langEndPattern = {0x3C, 0x2F, 0x4C, 0x41, 0x4E, 0x47};
|
||
|
||
int langEndPos = -1;
|
||
// 从后往前搜索,找到最后一个匹配位置
|
||
for (int i = responseBytes.length - langEndPattern.length; i >= 0; i--) {
|
||
boolean match = true;
|
||
for (int j = 0; j < langEndPattern.length; j++) {
|
||
if (responseBytes[i + j] != langEndPattern[j]) {
|
||
match = false;
|
||
break;
|
||
}
|
||
}
|
||
if (match) {
|
||
langEndPos = i;
|
||
log.info("找到</LANG位置: " + langEndPos + " (从字节 " + i + " 开始)");
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (langEndPos >= 0) {
|
||
// 检查</LANG后面是否还有其他字节
|
||
int afterLangPos = langEndPos + langEndPattern.length;
|
||
log.info("</LANG后的位置: " + afterLangPos + ", 剩余字节数: " + (responseBytes.length - afterLangPos));
|
||
|
||
// 创建新的字节数组,确保有足够空间
|
||
byte[] newBytes = new byte[responseBytes.length + 10];
|
||
|
||
// 复制到</LANG结束位置
|
||
System.arraycopy(responseBytes, 0, newBytes, 0, afterLangPos);
|
||
int pos = afterLangPos;
|
||
|
||
// 插入缺失的UAGE>
|
||
newBytes[pos++] = 0x55; // U
|
||
newBytes[pos++] = 0x41; // A
|
||
newBytes[pos++] = 0x47; // G
|
||
newBytes[pos++] = 0x45; // E
|
||
newBytes[pos++] = 0x3E; // >
|
||
|
||
// 如果</LANG后面还有字节,复制它们
|
||
if (afterLangPos < responseBytes.length) {
|
||
System.arraycopy(responseBytes, afterLangPos, newBytes, pos, responseBytes.length - afterLangPos);
|
||
pos += (responseBytes.length - afterLangPos);
|
||
log.info("复制了 " + (responseBytes.length - afterLangPos) + " 个剩余字节");
|
||
}
|
||
|
||
// 检查是否需要添加</TX>
|
||
String tempResult = new String(newBytes, 0, pos, java.nio.charset.StandardCharsets.ISO_8859_1);
|
||
if (!tempResult.contains("</TX>")) {
|
||
log.info("添加缺失的</TX>标签");
|
||
newBytes[pos++] = 0x3C; // <
|
||
newBytes[pos++] = 0x2F; // /
|
||
newBytes[pos++] = 0x54; // T
|
||
newBytes[pos++] = 0x58; // X
|
||
newBytes[pos++] = 0x3E; // >
|
||
}
|
||
|
||
// 转换为字符串
|
||
String result = new String(newBytes, 0, pos, java.nio.charset.StandardCharsets.ISO_8859_1);
|
||
log.info("字节级修复成功,新长度: " + pos);
|
||
log.info("修复后内容尾部: " + result.substring(Math.max(0, result.length() - 100)));
|
||
|
||
return result;
|
||
}
|
||
|
||
log.warn("未找到</LANG模式,尝试应急修复");
|
||
// 应急修复:直接在字符串级别处理
|
||
String fallback = new String(responseBytes, java.nio.charset.StandardCharsets.ISO_8859_1);
|
||
if (fallback.contains("<LANGUAGE>CN</LANG") && !fallback.contains("</LANGUAGE>")) {
|
||
fallback = fallback.replace("<LANGUAGE>CN</LANG", "<LANGUAGE>CN</LANGUAGE>");
|
||
if (!fallback.contains("</TX>")) {
|
||
fallback = fallback + "</TX>";
|
||
}
|
||
log.info("应急修复完成");
|
||
}
|
||
return fallback;
|
||
}
|
||
|
||
/**
|
||
* 从ZIP流中解析Excel文件
|
||
* @param zipInputStream ZIP输入流
|
||
* @param entryName 文件名
|
||
* @return 解析后的文本行列表(每行使用制表符分隔各列)
|
||
*/
|
||
private List<String> parseExcelFromZip(ZipInputStream zipInputStream, String entryName) throws IOException {
|
||
List<String> result = new ArrayList<>();
|
||
Workbook workbook = null;
|
||
|
||
try {
|
||
// 将ZipInputStream的内容读取到ByteArrayInputStream中
|
||
// 因为POI需要支持随机访问的流
|
||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||
byte[] buffer = new byte[1024];
|
||
int len;
|
||
while ((len = zipInputStream.read(buffer)) > 0) {
|
||
baos.write(buffer, 0, len);
|
||
}
|
||
|
||
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
|
||
log.info("Excel文件字节数: " + baos.size());
|
||
|
||
// 根据文件扩展名选择合适的Workbook类型
|
||
if (entryName.toLowerCase().endsWith(".xlsx")) {
|
||
workbook = new XSSFWorkbook(bais);
|
||
} else {
|
||
// 对于.xls文件,使用HSSFWorkbook(需要poi-ooxml依赖)
|
||
workbook = WorkbookFactory.create(bais);
|
||
}
|
||
|
||
// 获取第一个工作表
|
||
Sheet sheet = workbook.getSheetAt(0);
|
||
log.info("Excel工作表名称: " + sheet.getSheetName() + ", 行数: " + sheet.getLastRowNum());
|
||
|
||
// 输出Excel的基本信息
|
||
log.info("Excel总行数: " + sheet.getLastRowNum());
|
||
|
||
// 输出第24行的表头信息(真正的列标题)
|
||
Row headerRow24 = sheet.getRow(23); // 第24行,索引是23
|
||
if (headerRow24 != null) {
|
||
StringBuilder headerLine = new StringBuilder();
|
||
for (int col = 0; col < headerRow24.getLastCellNum(); col++) {
|
||
Cell cell = headerRow24.getCell(col);
|
||
if (cell != null) {
|
||
headerLine.append("[列").append(col).append("]=");
|
||
try {
|
||
headerLine.append(cell.toString());
|
||
} catch (Exception e) {
|
||
headerLine.append("ERROR");
|
||
}
|
||
headerLine.append(" | ");
|
||
}
|
||
}
|
||
log.info("第24行(真正的表头): " + headerLine.toString());
|
||
}
|
||
|
||
// 输出第25行的第一条数据示例
|
||
Row dataRow25 = sheet.getRow(24); // 第25行,索引是24
|
||
if (dataRow25 != null) {
|
||
StringBuilder dataLine = new StringBuilder();
|
||
for (int col = 0; col < Math.min(20, dataRow25.getLastCellNum()); col++) {
|
||
Cell cell = dataRow25.getCell(col);
|
||
if (cell != null) {
|
||
dataLine.append("[列").append(col).append("]=");
|
||
try {
|
||
dataLine.append(cell.toString());
|
||
} catch (Exception e) {
|
||
dataLine.append("ERROR");
|
||
}
|
||
dataLine.append(" | ");
|
||
}
|
||
}
|
||
log.info("第25行(第1条数据): " + dataLine.toString());
|
||
}
|
||
|
||
// 遍历所有行
|
||
for (int rowIndex = 0; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
|
||
Row row = sheet.getRow(rowIndex);
|
||
if (row == null) {
|
||
continue;
|
||
}
|
||
|
||
StringBuilder lineBuilder = new StringBuilder();
|
||
int lastCellNum = row.getLastCellNum();
|
||
|
||
// 遍历该行的所有列
|
||
for (int cellIndex = 0; cellIndex < lastCellNum; cellIndex++) {
|
||
Cell cell = row.getCell(cellIndex);
|
||
String cellValue = "";
|
||
|
||
if (cell != null) {
|
||
// 根据单元格类型获取值(JDK 8兼容方式)
|
||
int cellType = cell.getCellType();
|
||
|
||
if (cellType == Cell.CELL_TYPE_STRING) {
|
||
cellValue = cell.getStringCellValue();
|
||
} else if (cellType == Cell.CELL_TYPE_NUMERIC) {
|
||
// 检查是否为日期格式
|
||
if (DateUtil.isCellDateFormatted(cell)) {
|
||
try {
|
||
// 使用SimpleDateFormat格式化日期时间
|
||
java.util.Date dateValue = cell.getDateCellValue();
|
||
|
||
// 检查是否只有时间(日期部分为1899-12-31或1900-01-01)
|
||
java.util.Calendar cal = java.util.Calendar.getInstance();
|
||
cal.setTime(dateValue);
|
||
int year = cal.get(java.util.Calendar.YEAR);
|
||
|
||
if (year == 1899 || year == 1900) {
|
||
// 只有时间,格式化为 HH:mm:ss
|
||
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("HH:mm:ss");
|
||
cellValue = sdf.format(dateValue);
|
||
} else {
|
||
// 完整的日期时间,格式化为 yyyy-MM-dd HH:mm:ss
|
||
java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
||
cellValue = sdf.format(dateValue);
|
||
}
|
||
} catch (Exception e) {
|
||
cellValue = cell.getDateCellValue().toString();
|
||
}
|
||
} else {
|
||
// 数字格式,避免科学计数法
|
||
cellValue = String.valueOf(cell.getNumericCellValue());
|
||
// 如果是整数,去掉小数点
|
||
if (cellValue.endsWith(".0")) {
|
||
cellValue = cellValue.substring(0, cellValue.length() - 2);
|
||
}
|
||
}
|
||
} else if (cellType == Cell.CELL_TYPE_BOOLEAN) {
|
||
cellValue = String.valueOf(cell.getBooleanCellValue());
|
||
} else if (cellType == Cell.CELL_TYPE_FORMULA) {
|
||
try {
|
||
cellValue = String.valueOf(cell.getNumericCellValue());
|
||
} catch (Exception e) {
|
||
try {
|
||
cellValue = cell.getStringCellValue();
|
||
} catch (Exception e2) {
|
||
cellValue = "";
|
||
}
|
||
}
|
||
} else if (cellType == Cell.CELL_TYPE_BLANK) {
|
||
cellValue = "";
|
||
} else {
|
||
cellValue = "";
|
||
}
|
||
}
|
||
|
||
// 添加制表符分隔
|
||
if (cellIndex > 0) {
|
||
lineBuilder.append("\t");
|
||
}
|
||
lineBuilder.append(cellValue);
|
||
}
|
||
|
||
result.add(lineBuilder.toString());
|
||
}
|
||
|
||
log.info("Excel解析完成,共 " + result.size() + " 行");
|
||
|
||
} catch (Exception e) {
|
||
log.error("解析Excel文件失败: " + entryName, e);
|
||
throw new IOException("解析Excel文件失败", e);
|
||
} finally {
|
||
if (workbook != null) {
|
||
try {
|
||
workbook.close();
|
||
} catch (IOException e) {
|
||
log.warn("关闭Excel工作簿时出错", e);
|
||
}
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* 等待文件生成
|
||
* @param fileName 文件名
|
||
* @param timeoutSeconds 超时时间(秒)
|
||
* @return 文件是否生成成功
|
||
*/
|
||
private boolean waitForFileGeneration(String fileName, int timeoutSeconds) {
|
||
String filePath = "C:" + File.separator + "CCB_EBSClient" + File.separator + "download" + File.separator + fileName;
|
||
File file = new File(filePath);
|
||
|
||
int waitTime = 0;
|
||
int checkInterval = 2; // 每2秒检查一次
|
||
|
||
// 检查目录是否存在
|
||
File downloadDir = new File("C:" + File.separator + "CCB_EBSClient" + File.separator + "download");
|
||
if (!downloadDir.exists()) {
|
||
log.error("下载目录不存在: " + downloadDir.getAbsolutePath());
|
||
return false;
|
||
}
|
||
|
||
log.info("开始等待文件生成: " + fileName + ", 超时时间: " + timeoutSeconds + " 秒");
|
||
log.info("文件完整路径: " + filePath);
|
||
log.info("下载目录: " + downloadDir.getAbsolutePath() + ", 存在: " + downloadDir.exists());
|
||
|
||
while (waitTime < timeoutSeconds) {
|
||
// 刷新文件对象状态
|
||
file = new File(filePath);
|
||
|
||
if (file.exists()) {
|
||
long fileSize = file.length();
|
||
log.info("文件已存在: " + fileName + ", 当前大小: " + fileSize + " 字节");
|
||
|
||
if (fileSize > 0) {
|
||
log.info("文件生成成功: " + filePath + ", 最终大小: " + fileSize + " 字节");
|
||
|
||
// 额外等待2秒确保文件写入完成
|
||
try {
|
||
Thread.sleep(2000);
|
||
// 再次检查文件大小是否稳定
|
||
long finalSize = file.length();
|
||
if (finalSize == fileSize) {
|
||
log.info("文件写入完成,大小稳定: " + finalSize + " 字节");
|
||
return true;
|
||
} else {
|
||
log.info("文件仍在写入中,大小变化: " + fileSize + " -> " + finalSize + " 字节");
|
||
}
|
||
} catch (InterruptedException e) {
|
||
Thread.currentThread().interrupt();
|
||
}
|
||
}
|
||
} else {
|
||
// 列出目录中的文件,帮助调试
|
||
if (waitTime % 10 == 0) { // 每10秒打印一次目录内容
|
||
File[] files = downloadDir.listFiles();
|
||
if (files != null && files.length > 0) {
|
||
log.info("下载目录中的文件: ");
|
||
for (File f : files) {
|
||
log.info(" - " + f.getName() + " (大小: " + f.length() + " 字节)");
|
||
}
|
||
} else {
|
||
log.info("下载目录为空");
|
||
}
|
||
}
|
||
}
|
||
|
||
try {
|
||
log.info("等待文件生成: " + fileName + ", 已等待 " + waitTime + " 秒 / " + timeoutSeconds + " 秒");
|
||
Thread.sleep(checkInterval * 1000);
|
||
waitTime += checkInterval;
|
||
} catch (InterruptedException e) {
|
||
Thread.currentThread().interrupt();
|
||
log.error("等待文件生成被中断", e);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
log.error("文件生成超时: " + fileName + ", 等待了 " + timeoutSeconds + " 秒");
|
||
|
||
// 超时后再次检查目录状态
|
||
File[] files = downloadDir.listFiles();
|
||
if (files != null && files.length > 0) {
|
||
log.error("超时时下载目录中的文件: ");
|
||
for (File f : files) {
|
||
log.error(" - " + f.getName() + " (大小: " + f.length() + " 字节)");
|
||
}
|
||
} else {
|
||
log.error("超时时下载目录为空");
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* 从中专服务器下载ZIP文件到本地目录
|
||
* @param fileName 文件名
|
||
* @param serviceParamsService 服务参数服务
|
||
* @return 下载是否成功
|
||
*/
|
||
private boolean downloadZipFromIntermediaryServer(String fileName, ServiceParamsService serviceParamsService) {
|
||
FileOutputStream fos = null;
|
||
InputStream inputStream = null;
|
||
CloseableHttpResponse response = null;
|
||
|
||
try {
|
||
// 1. 获取中专服务器地址
|
||
List<HashMap<Object, Object>> serviceParams = serviceParamsService.findParamValByParamCode("hgd_dmz");
|
||
if (serviceParams == null || serviceParams.isEmpty()) {
|
||
log.error("未配置中转服务地址(hgd_dmz)");
|
||
return false;
|
||
}
|
||
|
||
String dmzUrl = StringDUtil.changeNullToEmpty(serviceParams.get(0).get("PARAM_VAL"));
|
||
if (dmzUrl.isEmpty()) {
|
||
log.error("中转服务地址(hgd_dmz)为空");
|
||
return false;
|
||
}
|
||
|
||
// 2. 构建下载接口URL
|
||
String downloadUrl = dmzUrl + "/download/" + fileName;
|
||
log.info("中专服务器下载接口URL: " + downloadUrl);
|
||
|
||
// 3. 创建HTTP客户端并发送GET请求
|
||
CloseableHttpClient httpClient = HttpClients.createDefault();
|
||
org.apache.http.client.methods.HttpGet httpGet = new org.apache.http.client.methods.HttpGet(downloadUrl);
|
||
httpGet.setHeader("Connection", "close");
|
||
|
||
response = httpClient.execute(httpGet);
|
||
|
||
// 4. 检查响应状态码
|
||
int statusCode = response.getStatusLine().getStatusCode();
|
||
log.info("中专服务器响应状态码: " + statusCode);
|
||
|
||
if (statusCode != 200) {
|
||
log.error("下载失败,HTTP状态码: " + statusCode + ", 原因: " + response.getStatusLine().getReasonPhrase());
|
||
return false;
|
||
}
|
||
|
||
// 5. 获取响应实体
|
||
HttpEntity entity = response.getEntity();
|
||
if (entity == null) {
|
||
log.error("响应实体为空");
|
||
return false;
|
||
}
|
||
|
||
// 6. 确保目标目录存在
|
||
String targetDir = "C:" + File.separator + "CCB_EBSClient" + File.separator + "download";
|
||
File dir = new File(targetDir);
|
||
if (!dir.exists()) {
|
||
boolean created = dir.mkdirs();
|
||
log.info("创建目标目录: " + targetDir + ", 结果: " + created);
|
||
}
|
||
|
||
// 7. 构建目标文件路径
|
||
String targetFilePath = targetDir + File.separator + fileName;
|
||
File targetFile = new File(targetFilePath);
|
||
|
||
// 8. 如果文件已存在,先删除
|
||
if (targetFile.exists()) {
|
||
boolean deleted = targetFile.delete();
|
||
log.info("删除已存在的文件: " + targetFilePath + ", 结果: " + deleted);
|
||
}
|
||
|
||
// 9. 将响应流写入本地文件
|
||
inputStream = entity.getContent();
|
||
fos = new FileOutputStream(targetFile);
|
||
|
||
byte[] buffer = new byte[4096];
|
||
int bytesRead;
|
||
long totalBytesRead = 0;
|
||
|
||
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||
fos.write(buffer, 0, bytesRead);
|
||
totalBytesRead += bytesRead;
|
||
}
|
||
|
||
fos.flush();
|
||
|
||
log.info("文件下载完成: " + targetFilePath + ", 总大小: " + totalBytesRead + " 字节");
|
||
|
||
// 10. 验证文件是否下载成功
|
||
if (targetFile.exists() && targetFile.length() > 0) {
|
||
log.info("文件验证成功,大小: " + targetFile.length() + " 字节");
|
||
return true;
|
||
} else {
|
||
log.error("文件下载失败,文件不存在或大小为0");
|
||
return false;
|
||
}
|
||
|
||
} catch (Exception e) {
|
||
log.error("从中专服务器下载ZIP文件异常: " + fileName, e);
|
||
return false;
|
||
} finally {
|
||
// 11. 关闭所有资源
|
||
try {
|
||
if (fos != null) {
|
||
fos.close();
|
||
}
|
||
if (inputStream != null) {
|
||
inputStream.close();
|
||
}
|
||
if (response != null) {
|
||
EntityUtils.consumeQuietly(response.getEntity());
|
||
response.close();
|
||
}
|
||
} catch (IOException e) {
|
||
log.warn("关闭资源时出错", e);
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|