改进下载对账文件的方案
diff --git a/config/application-devel-pg.properties b/config/application-devel-pg.properties
index 52289ce..8e2b45e 100644
--- a/config/application-devel-pg.properties
+++ b/config/application-devel-pg.properties
@@ -10,7 +10,7 @@
 spring.datasource.username=payapi
 spring.datasource.password=123456
 spring.datasource.continue-on-error=true
-#spring.datasource.initialization-mode=always
+spring.datasource.initialization-mode=always
 # Redis settings
 #spring.redis.host=ykt.supwisdom.com
 spring.redis.host=172.28.201.101
diff --git a/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/CitizenCardPayProxy.java b/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/CitizenCardPayProxy.java
index e2042d9..d68a238 100644
--- a/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/CitizenCardPayProxy.java
+++ b/payapi-sdk/src/main/java/com/supwisdom/dlpay/paysdk/proxy/CitizenCardPayProxy.java
@@ -13,7 +13,6 @@
 @FeignClient(value = "citizenCardPay", url = "${payapi.url}")
 public interface CitizenCardPayProxy {
   @RequestMapping(value = "/api/consume/citizencard/payinit", method = RequestMethod.GET)
-  @Valid
   CitizenPayResponse citizencardPayinit(@RequestBody CitizenCardPayinitParam param);
 
   @RequestMapping(value = "/api/consume/citizencard/payfinish", method = RequestMethod.GET)
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/ParamServiceImpl.java b/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/ParamServiceImpl.java
index 191697e..88e7e1b 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/ParamServiceImpl.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/system/service/impl/ParamServiceImpl.java
@@ -164,7 +164,7 @@
   }
 
   @Override
-  @Cacheable(cacheNames = "dictionary_cache", key = "#{@tenantHolder.genKey('sourcetype', #sourceType)}")
+  @Cacheable(cacheNames = "dictionary_cache", key = "@tenantHolder.genKey('sourcetype', #sourceType)")
   public TSourceType getSourceType(String sourceType) {
     if (!StringUtil.isEmpty(sourceType)) {
       return sourceTypeDao.getBySourceType(sourceType.trim());
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt
index 4139d9e..5b9745a 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/async_tasks.kt
@@ -2,7 +2,6 @@
 
 import com.supwisdom.dlpay.agent.AgentCode
 import com.supwisdom.dlpay.agent.DtlStatus
-
 import com.supwisdom.dlpay.agent.citizencard.YnrccUtil
 import com.supwisdom.dlpay.api.domain.TDtlQuery
 import com.supwisdom.dlpay.api.domain.TTransactionMain
@@ -129,8 +128,9 @@
                 }
                 AgentCode.REFNO_NOT_EXISTS ->
                     transactionService.fail(transaction.refno, "银行流水不存在") //银行返回流水不存在
-                AgentCode.REQUIRE_QUERY ->
+                AgentCode.REQUIRE_QUERY -> {
                     queryResult(transaction, qcnt + 1)  //查询次数加1
+                }
                 else -> {
                     //其他明确错误,查询失败
                     logger.error("查询refno=[${transaction.refno}]流水结果返回失败:code=[${resp.agentCode}],message=[${resp.agentMsg}]")
diff --git a/ynrcc-agent/build.gradle b/ynrcc-agent/build.gradle
index a46f4d7..a00c65d 100644
--- a/ynrcc-agent/build.gradle
+++ b/ynrcc-agent/build.gradle
@@ -49,6 +49,7 @@
     implementation group: 'taglibs', name: 'standard', version: '1.1.2'
     implementation group: 'commons-codec', name: 'commons-codec', version: '1.6'
     implementation 'org.dom4j:dom4j:2.1.1'
+    implementation 'commons-beanutils:commons-beanutils:1.9.3'
 
     testImplementation 'org.springframework.boot:spring-boot-starter-test'
     testImplementation 'io.rest-assured:rest-assured:3.3.0'
diff --git a/ynrcc-agent/src/main/java/com/supwisdom/agent/api/bean/CheckFileHeader.java b/ynrcc-agent/src/main/java/com/supwisdom/agent/api/bean/CheckFileHeader.java
new file mode 100644
index 0000000..d19df54
--- /dev/null
+++ b/ynrcc-agent/src/main/java/com/supwisdom/agent/api/bean/CheckFileHeader.java
@@ -0,0 +1,76 @@
+package com.supwisdom.agent.api.bean;
+
+/**
+ * 第一行:总笔数|总金额|代扣总笔数|代扣总金额|退款总笔数|退款总金额|
+ */
+public class CheckFileHeader {
+  private int totalCount;
+  private int totalAmount;
+  private int payCount;
+  private int payAmount;
+  private int refundCount;
+  private int refundAmount;
+
+  public int getTotalCount() {
+    return totalCount;
+  }
+
+  public void setTotalCount(int totalCount) {
+    this.totalCount = totalCount;
+  }
+
+  public int getTotalAmount() {
+    return totalAmount;
+  }
+
+  public void setTotalAmount(int totalAmount) {
+    this.totalAmount = totalAmount;
+  }
+
+  public int getPayCount() {
+    return payCount;
+  }
+
+  public void setPayCount(int payCount) {
+    this.payCount = payCount;
+  }
+
+  public int getPayAmount() {
+    return payAmount;
+  }
+
+  public void setPayAmount(int payAmount) {
+    this.payAmount = payAmount;
+  }
+
+  public int getRefundCount() {
+    return refundCount;
+  }
+
+  public void setRefundCount(int refundCount) {
+    this.refundCount = refundCount;
+  }
+
+  public int getRefundAmount() {
+    return refundAmount;
+  }
+
+  public void setRefundAmount(int refundAmount) {
+    this.refundAmount = refundAmount;
+  }
+
+  public boolean isZero() {
+    return (totalAmount == 0 && totalCount == 0
+        && payAmount == 0 && payCount == 0 && refundAmount == 0 && refundCount == 0);
+  }
+
+  @Override
+  public String toString() {
+    return totalCount +
+        ", " + totalAmount +
+        ", " + payCount +
+        ", " + payAmount +
+        ", " + refundCount +
+        ", " + refundAmount;
+  }
+}
diff --git a/ynrcc-agent/src/main/java/com/supwisdom/agent/api/bean/CheckFileLine.java b/ynrcc-agent/src/main/java/com/supwisdom/agent/api/bean/CheckFileLine.java
new file mode 100644
index 0000000..e3d9b69
--- /dev/null
+++ b/ynrcc-agent/src/main/java/com/supwisdom/agent/api/bean/CheckFileLine.java
@@ -0,0 +1,100 @@
+package com.supwisdom.agent.api.bean;
+
+/**
+ * * 第二行及以后:交易日期|交易时间|报文业务类型|业务系统流水号|银行流水号|付款银行卡号|收款商户银行卡号|核心记账日期|交易金额|交易摘要|
+ * <p>
+ * private static final String[]chkFileColumnList=new String[]{"transdate","transtime","flag","refno","agentrefno",
+ * "payerid","payeeid","agentdate","amount","summary"};
+ */
+public class CheckFileLine {
+  private String transdate;
+  private String transtime;
+  private String flag;
+  private String refno;
+  private String agentrefno;
+  private String payerid;
+  private String payeeid;
+  private String agentdate;
+  private Integer amount;
+  private String summary;
+
+  public String getTransdate() {
+    return transdate;
+  }
+
+  public void setTransdate(String transdate) {
+    this.transdate = transdate;
+  }
+
+  public String getTranstime() {
+    return transtime;
+  }
+
+  public void setTranstime(String transtime) {
+    this.transtime = transtime;
+  }
+
+  public String getFlag() {
+    return flag;
+  }
+
+  public void setFlag(String flag) {
+    this.flag = flag;
+  }
+
+  public String getRefno() {
+    return refno;
+  }
+
+  public void setRefno(String refno) {
+    this.refno = refno;
+  }
+
+  public String getAgentrefno() {
+    return agentrefno;
+  }
+
+  public void setAgentrefno(String agentrefno) {
+    this.agentrefno = agentrefno;
+  }
+
+  public String getPayerid() {
+    return payerid;
+  }
+
+  public void setPayerid(String payerid) {
+    this.payerid = payerid;
+  }
+
+  public String getPayeeid() {
+    return payeeid;
+  }
+
+  public void setPayeeid(String payeeid) {
+    this.payeeid = payeeid;
+  }
+
+  public String getAgentdate() {
+    return agentdate;
+  }
+
+  public void setAgentdate(String agentdate) {
+    this.agentdate = agentdate;
+  }
+
+  public Integer getAmount() {
+    return amount;
+  }
+
+  public void setAmount(Integer amount) {
+    this.amount = amount;
+  }
+
+  public String getSummary() {
+    return summary;
+  }
+
+  public void setSummary(String summary) {
+    this.summary = summary;
+  }
+}
diff --git a/ynrcc-agent/src/main/java/com/supwisdom/agent/api/controller/YnrccApiController.java b/ynrcc-agent/src/main/java/com/supwisdom/agent/api/controller/YnrccApiController.java
index 8c229d3..52fa611 100644
--- a/ynrcc-agent/src/main/java/com/supwisdom/agent/api/controller/YnrccApiController.java
+++ b/ynrcc-agent/src/main/java/com/supwisdom/agent/api/controller/YnrccApiController.java
@@ -4,18 +4,24 @@
 import com.supwisdom.agent.Util.DlpayUtil;
 import com.supwisdom.agent.Util.ErrorCode;
 import com.supwisdom.agent.Util.StringUtil;
+import com.supwisdom.agent.api.bean.CheckFileHeader;
+import com.supwisdom.agent.api.bean.CheckFileLine;
 import com.supwisdom.agent.api.bean.DlpayReq;
 import com.supwisdom.agent.api.bean.DlpayResp;
 import com.supwisdom.agent.api.service.YnrccApiService;
 import com.supwisdom.agent.api.service.YnrccParamCheckService;
+import org.apache.commons.beanutils.BeanUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.http.HttpStatus;
+import org.springframework.web.bind.annotation.*;
 
+import javax.servlet.http.HttpServletResponse;
 import javax.ws.rs.FormParam;
+import java.io.*;
+import java.lang.reflect.InvocationTargetException;
+import java.nio.charset.StandardCharsets;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -28,6 +34,18 @@
   @Autowired
   private YnrccParamCheckService ynrccParamCheckService;
 
+  /**
+   * 第一行:总笔数|总金额|代扣总笔数|代扣总金额|退款总笔数|退款总金额|
+   * 第二行及以后:交易日期|交易时间|报文业务类型|业务系统流水号|银行流水号|付款银行卡号|收款商户银行卡号|核心记账日期|交易金额|交易摘要|
+   */
+  private static final String[] chkFileColumnList = new String[]{"transdate", "transtime", "flag", "refno", "agentrefno",
+      "payerid", "payeeid", "agentdate", "amount", "summary"};
+  private static final String[] chkFileHeaderColumnList = new String[]{"totalCount", "totalAmount", "payCount",
+      "payAmount", "refundCount", "refundAmount"};
+  private static final String chkFileDelimiter = "|";
+  private static final String FLAG_WITHHOLD = "BC5512";
+  private static final String FLAG_REFUND = "BC5513";
+
   private static final Logger logger = LoggerFactory.getLogger(YnrccApiController.class);
 
   private boolean checkYnrccSign(Map<String, String> params, DlpayResp resp) {
@@ -375,5 +393,106 @@
     }
   }
 
+  private <T> void populate(String[] fields, String[] column, T bean) {
+    if (fields.length != column.length) {
+      throw new IllegalArgumentException("错误的列定义");
+    }
+    try {
+      Map<String, String> data = new HashMap<>();
+      for (int col = 0; col < column.length; ++col) {
+        data.put(column[col], fields[col]);
+      }
+      BeanUtils.populate(bean, data);
+    } catch (InvocationTargetException | IllegalAccessException e) {
+      e.printStackTrace();
+    }
+    throw new IllegalArgumentException("错误的列定义");
+  }
 
+  private <T> T populate(String[] fields, String[] column, Class<T> beanClass) {
+    try {
+      T bean = beanClass.newInstance();
+      populate(fields, column, bean);
+    } catch (InstantiationException e) {
+      e.printStackTrace();
+    } catch (IllegalAccessException e) {
+      e.printStackTrace();
+    }
+    throw new IllegalArgumentException("错误的列定义");
+  }
+
+  private void writeLine(OutputStream output, String delimiter, Object... columns) throws IOException {
+    for (Object item : columns) {
+      output.write(item.toString().getBytes(StandardCharsets.UTF_8));
+      output.write(delimiter.getBytes());
+    }
+    output.write("\n".getBytes());
+  }
+
+  private void parseFile(String filePath, OutputStream output) {
+    int lineno = 1;
+    try {
+      BufferedReader reader = new BufferedReader(
+          new InputStreamReader(
+              new FileInputStream(filePath), "GBK"));
+      String header = reader.readLine();
+      if (header == null) {
+        throw new IllegalArgumentException("数据文件内容为空");
+      }
+
+      CheckFileHeader fileHeader = populate(chkFileHeaderColumnList,
+          header.split(chkFileDelimiter), CheckFileHeader.class);
+
+
+      CheckFileLine record = new CheckFileLine();
+      writeLine(output, ",", "refno", "agentrefno", "agentdate", "flag", "payerid",
+          "payeeid", "amount", "summary");
+      while (true) {
+        String line = reader.readLine();
+        if (line == null) {
+          break;
+        }
+        lineno++;
+        String[] field = line.split(chkFileDelimiter);
+        populate(chkFileColumnList, field, record);
+        int amount;
+        String flag;
+        if (FLAG_WITHHOLD.equals(record.getFlag())) {
+          amount = -record.getAmount();
+          flag = "pay";
+          fileHeader.setPayCount(fileHeader.getPayCount() - 1);
+          fileHeader.setPayAmount(fileHeader.getPayAmount() - record.getAmount());
+        } else if (FLAG_REFUND.equals(record.getFlag())) {
+          amount = +record.getAmount();
+          flag = "refund";
+          fileHeader.setRefundCount(fileHeader.getRefundCount() - 1);
+          fileHeader.setRefundAmount(fileHeader.getRefundAmount() - record.getAmount());
+        } else {
+          throw new IllegalArgumentException("对账文件【报文业务类型】错误,行数 " + lineno + " 。");
+        }
+        fileHeader.setTotalCount(fileHeader.getTotalCount() - 1);
+        fileHeader.setTotalAmount(fileHeader.getTotalAmount() - record.getAmount());
+        writeLine(output, ",", record.getRefno(),
+            record.getAgentrefno(), record.getAgentdate(),
+            flag, record.getPayerid(), record.getPayeeid(),
+            amount, record.getSummary());
+      }
+      if (!fileHeader.isZero()) {
+        throw new IllegalArgumentException("对账文件总金额与笔数与明细不符");
+      }
+    } catch (IOException e) {
+      e.printStackTrace();
+      throw new IllegalArgumentException("对账文件数据格式错误,行数 " + lineno + " 。");
+    }
+  }
+
+  @GetMapping("/download")
+  public void downloadFile(@RequestParam("file") String file, HttpServletResponse response) throws IOException {
+    try {
+      parseFile(file, response.getOutputStream());
+    } catch (Exception e) {
+      e.printStackTrace();
+      response.sendError(HttpStatus.SERVICE_UNAVAILABLE.value(), e.getMessage());
+    }
+  }
 }