Files
dzpt/src/main/java/com/saye/hospitalgd/scheduler/jobMethod/BankGetDataMethodByJHLZF.java
2025-11-03 11:42:55 +08:00

1481 lines
77 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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);
}
}
}
}