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 getDate(String id, String name, String trade_date, HashMap map) { HashMap 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 = " \n" + " \n" + " " + requset_sn_sc + " \n" + " " + cust_id + " \n" + " " + user_id + " \n" + " " + password + " \n" + " " + tx_code + " \n" + " " + language + " \n" + " \n" + " " + dateStr + " \n" + " 1 \n" + " 1 \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " "; 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 searchMap = new HashMap<>(); searchMap.put("FUBS", "2"); List> wlConfigList = thirdSftpConfigService.findWLIF(searchMap); HashMap 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 = " \n" + " \n" + " " + requset_sn_xz + " \n" + " " + cust_id + " \n" + " " + user_id + " \n" + " " + password + " \n" + " " + txCode + " \n" + " CN \n" + " \n" + " " + fileName + " \n" + " " + filepath + " \n" + " 0 \n" + " \n" + " \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标签截断的十六进制模式 // = 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("CN")) { log.warn("字符串检测到LANGUAGE标签截断,进行字节级修复"); s_d = fixLanguageTagFromBytes(responseBytes); log.info("字符串检测修复完成,新字符串长度: " + s_d.length()); } } log.info("最终字符串长度: " + s_d.length()); // 验证修复结果 if (s_d.contains("CN") && s_d.contains("")) { log.info("XML修复成功,包含完整的LANGUAGE标签和TX根元素"); } else if (s_d.contains("CN")) { log.error("XML修复失败,LANGUAGE标签仍然截断"); // 最后尝试字符串级别的修复 s_d = s_d.replace("CNCN"); if (!s_d.contains("")) { s_d = s_d + ""; } 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 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 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列位置映射(从日志分析得出): // 终端号: C列(索引2), 发卡行: E列(索引4), 卡种: I列(索引8), // 卡号: K列(索引10), 交易日期: N列(索引13), 交易时间: O列(索引14)⭐, // 交易类型: Q列(索引16), 授权号: S列(索引18), 交易金额: 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) // 获取终端号(C列,索引2)用于判断是否为住院订单 String zdh = s1.length > 2 ? s1[2].trim() : ""; // 交易日期和时间 jyrq = s1.length > 13 ? s1[13] : ""; String jysj = s1.length > 14 ? s1[14] : ""; // 交易时间在第14列(索引14) // 如果交易时间为空,尝试从交易日期中提取时间部分 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): 交易金额 bankbillHistory.setCZdh(zdh); // C列(索引2): 终端号 // 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("建行龙支付对账单"); // 对账表名 // 根据终端号判断是否为住院订单 // 终端号为10091548或10091549的为住院订单,不参与对账 if ("10091548".equals(zdh) || "10091549".equals(zdh) || "10091546".equals(zdh) || "10091547".equals(zdh) || "10091544".equals(zdh) || "10091545".equals(zdh)) { bankbillHistory.setIsInpatient("1"); // 标记为住院订单 log.info("标记为住院订单: 终端号=" + zdh + ", 订单号=" + bankbillHistory.getCShddh()); } else { bankbillHistory.setIsInpatient("0"); // 标记为非住院订单 } 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 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(" 0) { response = response.substring(xmlStart); } // 如果没有XML声明,查找根元素开始位置 if (xmlStart == -1) { int rootStart = response.indexOf(""); 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("\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); } } // 特殊处理:检测CNCN")) { log.warn("检测到LANGUAGE标签特定截断模式"); // 直接替换为完整的标签 xml = xml.replaceAll("CN]*$", "CN"); xml = xml.replaceAll("CNCN"); log.info("修复LANGUAGE标签截断"); } // 处理XML结尾被截断的情况 if (xml.endsWith("CNCNCN"); log.info("修复XML结尾LANGUAGE标签"); } // 处理XML在LANGUAGE标签中间被截断的情况 if (xml.endsWith("") || xml.endsWith("CN") || xml.endsWith("CN<")) { log.warn("检测到LANGUAGE标签内容截断"); if (xml.endsWith("")) { xml = xml.replace("", "CN"); } else if (xml.endsWith("CN")) { xml = xml.replace("CN", "CN"); } else if (xml.endsWith("CN<")) { xml = xml.replace("CN<", "CN"); } log.info("修复LANGUAGE标签内容截断"); } // 处理标签名被截断的情况,如 xml = xml.replaceAll(""); xml = xml.replaceAll("]*)>([^<]*)$2"); // 修复LANGUAGE标签问题 - 处理各种截断情况 // 特殊处理:如果发现CN")) { xml = xml.replaceAll("CN]*", "CN"); log.info("修复CNCN]*)>CN]*", "CN"); // 处理...内容被截断的情况,保留已有内容 xml = xml.replaceAll("]*)>([^<]*?)(?=<(?!/LANGUAGE))", "$2"); xml = xml.replaceAll("]*)>([^<]*?)$", "$2"); // 特殊处理:如果LANGUAGE标签内容为空但原始响应中有CN,尝试恢复 if (xml.contains("") && xml.contains("CN")) { // 查找CN在原始XML中的位置,如果在LANGUAGE标签附近,则恢复 String originalPart = xml.substring(Math.max(0, xml.indexOf("") - 50), Math.min(xml.length(), xml.indexOf("") + 20)); if (originalPart.contains("CN")) { xml = xml.replace("", "CN"); 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"); xml = xml.replaceAll("<" + tag + "([^>]*)>([^<]*?)$", "<" + tag + "$1>$2"); } // 移除重复的XML声明 String[] parts = xml.split("\\<\\?xml[^\\>]*\\?\\>"); if (parts.length > 2) { StringBuilder sb = new StringBuilder(); sb.append(""); for (int i = 1; i < parts.length; i++) { sb.append(parts[i]); } xml = sb.toString(); } // 确保根元素完整 if (!xml.contains("") && xml.contains("")) { xml = xml + ""; } // 最后的激进修复:如果仍然有问题,尝试重构XML if (!xml.contains("") || xml.contains("CN][^>]*$")) { 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(""; 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("\n"); reconstructed.append(""); if (requestSn != null) reconstructed.append("").append(requestSn).append(""); if (custId != null) reconstructed.append("").append(custId).append(""); if (txCode != null) reconstructed.append("").append(txCode).append(""); if (returnCode != null) reconstructed.append("").append(returnCode).append(""); if (returnMsg != null) reconstructed.append("").append(returnMsg).append(""); reconstructed.append("").append(language).append(""); reconstructed.append(""); 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()); // 查找 = 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("找到= 0) { // 检查 newBytes[pos++] = 0x55; // U newBytes[pos++] = 0x41; // A newBytes[pos++] = 0x47; // G newBytes[pos++] = 0x45; // E newBytes[pos++] = 0x3E; // > // 如果 String tempResult = new String(newBytes, 0, pos, java.nio.charset.StandardCharsets.ISO_8859_1); if (!tempResult.contains("")) { log.info("添加缺失的标签"); 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("未找到CN")) { fallback = fallback.replace("CNCN"); if (!fallback.contains("")) { fallback = fallback + ""; } log.info("应急修复完成"); } return fallback; } /** * 从ZIP流中解析Excel文件 * @param zipInputStream ZIP输入流 * @param entryName 文件名 * @return 解析后的文本行列表(每行使用制表符分隔各列) */ private List parseExcelFromZip(ZipInputStream zipInputStream, String entryName) throws IOException { List 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()); // 遍历所有行 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) { // 直接使用 toString() 获取单元格显示的文本值,这样可以获取格式化后的值 // 例如时间 "00:22:49"、日期 "2025-10-23" 等都会按显示格式返回 try { cellValue = cell.toString(); // 如果是空白,设置为空字符串 if (cellValue == null) { cellValue = ""; } } catch (Exception e) { // 如果 toString 失败,尝试根据类型获取 int cellType = cell.getCellType(); try { if (cellType == Cell.CELL_TYPE_STRING) { cellValue = cell.getStringCellValue(); } else if (cellType == Cell.CELL_TYPE_NUMERIC) { 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_BLANK) { cellValue = ""; } else { cellValue = ""; } } catch (Exception e2) { 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> 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); } } } }