下载对账单步骤
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/agent/citizencard/YnrccUtil.java b/payapi/src/main/java/com/supwisdom/dlpay/agent/citizencard/YnrccUtil.java
index 9b12298..4c8a23d 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/agent/citizencard/YnrccUtil.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/agent/citizencard/YnrccUtil.java
@@ -32,6 +32,7 @@
   public static final String CODE_SUCCESS = "0000"; //成功
   public static final String CODE_NOT_EXISTS = "0401"; //流水不存在
   public static final String CODE_EXCEPTION = "10000"; //异常
+  public static final String NO_RECORDS_TODAY = "0406"; //当日无交易明细
 
   //查询接口返回的流水状态
   public static final String DTL_STATUS_SUCCESS = "0"; //成功
@@ -41,6 +42,8 @@
 
   public static final int QUERY_MAX_COUNT = 3; //最大查询次数
 
+  public static final String YNRCC_BILLS_DOWNLOAD_LASTDATE = "ynrcc.download.bills.lastdate";
+
   public static final List<Pair<AgentCode, YnrccRespCode>> errcode = new ArrayList<>(0);
 
   static {
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/DtlQueryDao.java b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/DtlQueryDao.java
index ea6c3be..178a88c 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/DtlQueryDao.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/DtlQueryDao.java
@@ -8,6 +8,6 @@
 
 public interface DtlQueryDao extends JpaRepository<TDtlQuery, String> {
 
-  @Query("from TDtlQuery t where t.accdate=?1 and t.status=?2 and t.qcnt<=?3 order by t.lastsaved desc ")
+  @Query("from TDtlQuery t where t.accdate=?1 and t.status=?2 and t.qcnt<=?3 order by t.lastsaved ")
   List<TDtlQuery> getNeedQueryDtls(String accdate, String status, int maxQcnt);
 }
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/TransactionMainDao.java b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/TransactionMainDao.java
index 3fb3544..b72f8d2 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/TransactionMainDao.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/TransactionMainDao.java
@@ -25,4 +25,7 @@
 
   @Query("select t from TTransactionMain t where t.outTradeNo=?1 and t.outId=?2")
   TTransactionMain findByBillno(String billno, String outid);
+
+  @Query("select min(t.accdate) from TTransactionMain t where t.sourceType=?1 ")
+  String findMinAccdateBySourcetype(String sourcetype);
 }
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/framework/core/DayendSettleTask.java b/payapi/src/main/java/com/supwisdom/dlpay/framework/core/DayendSettleTask.java
index 14663ec..368bf8e 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/framework/core/DayendSettleTask.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/framework/core/DayendSettleTask.java
@@ -20,7 +20,7 @@
   private static final Logger logger = LoggerFactory.getLogger(DayendSettleTask.class);
 
   @Scheduled(cron = "${dayend.settletask.cron}")
-  @SchedulerLock(name = "DayendSettleTask")
+  @SchedulerLock(name = "DayendSettleTask", lockAtMostForString = "PT30M")
   public void doSettleTask() {
     if (logger.isDebugEnabled()) logger.debug("进入日结算任务!");
     settleLog = dayendSettleService.doCreateSettleLog(); //记录日志
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_task.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_task.kt
index 7f7ea81..35136c5 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_task.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_task.kt
@@ -29,10 +29,14 @@
     }
 
     @Scheduled(cron = "\${shopbalance.updater.cron:-}")
-    @SchedulerLock(name = "dealShopUnupdatedDtl")
+    @SchedulerLock(name = "dealShopUnupdatedDtl", lockAtMostForString = "PT10M")
     fun dealShopUnupdatedDtl() {
-        shopaccService.findUnupdatedShopDtl(100).forEach {
-            doShopBlanceUpdate(it)
+        try {
+            shopaccService.findUnupdatedShopDtl(100).forEach {
+                doShopBlanceUpdate(it)
+            }
+        } catch (ex: Exception) {
+            ex.printStackTrace()
         }
     }
 }
@@ -54,16 +58,20 @@
     private lateinit var applicationContext: ApplicationContext
 
     @Scheduled(cron = "\${query.third.transdtl.result.cron:-}")
-    @SchedulerLock(name = "DtlQueryResultSchedulerTask")
+    @SchedulerLock(name = "DtlQueryResultSchedulerTask", lockAtMostForString = "PT10M")
     fun queryThirdTransdtlResult() {
-        //仅查询当天数据,查询次数在规定次数之下
-        dtlQueryResultService.getNeedQueryRecords(systemUtilService.accdate, ConstantUtil.QUERY_MAX_COUNT).forEach {
-            try {
-                doQuery(it)
-            } catch (exp: Exception) {
-                it.qcnt = it.qcnt + 1
-                dtlQueryResultService.saveOrUpdateDtlQuery(it) //次数加一
+        try {
+            //仅查询当天数据,查询次数在规定次数之下
+            dtlQueryResultService.getNeedQueryRecords(systemUtilService.accdate, ConstantUtil.QUERY_MAX_COUNT).forEach {
+                try {
+                    doQuery(it)
+                } catch (exp: Exception) {
+                    it.qcnt = it.qcnt + 1
+                    dtlQueryResultService.saveOrUpdateDtlQuery(it) //次数加一
+                }
             }
+        } catch (ex: Exception) {
+            ex.printStackTrace()
         }
     }
 
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_ynrccchk_task.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_ynrccchk_task.kt
new file mode 100644
index 0000000..04683c0
--- /dev/null
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/scheduler_ynrccchk_task.kt
@@ -0,0 +1,63 @@
+package com.supwisdom.dlpay.api
+
+import com.supwisdom.dlpay.agent.citizencard.YnrccUtil
+import com.supwisdom.dlpay.agent.service.CitizencardPayService
+import com.supwisdom.dlpay.api.service.YnrccBusinessService
+import com.supwisdom.dlpay.framework.service.SystemUtilService
+import com.supwisdom.dlpay.framework.util.DateUtil
+import mu.KotlinLogging
+import net.javacrumbs.shedlock.core.SchedulerLock
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.scheduling.annotation.Scheduled
+import org.springframework.stereotype.Component
+
+/**
+ * 第三方对账任务
+ * */
+@Component
+class DownloadYnrccChkfileTask {
+    @Autowired
+    private lateinit var systemUtilService: SystemUtilService
+    @Autowired
+    private lateinit var ynrccBusinessService: YnrccBusinessService
+    @Autowired
+    private lateinit var citizencardPayService: CitizencardPayService
+
+    private val logger = KotlinLogging.logger { }
+
+
+    @Scheduled(cron = "\${download.ynrcc.chkfile.cron:-}")
+    @SchedulerLock(name = "DownloadYnrccChkfileSchedulerTask", lockAtMostForString = "PT10M")
+    fun doDownloadYnrccChkfile() {
+        try {
+            //下载对账单逻辑
+            val hostdate = systemUtilService.sysdatetime.hostdate
+            val downloadLastdate = ynrccBusinessService.getLastDownloadBillDate()
+            val diffDays = DateUtil.getIntervalDay(downloadLastdate, hostdate).toInt()
+            logger.info("大理农商行对账单下载:downloadLastdate=$downloadLastdate, hostdate=$hostdate, diffDays=$diffDays ")
+
+            for (i in 1 until diffDays) {
+                val billDate = DateUtil.getNewDay(downloadLastdate, i) //要取对账单的日期
+
+                val resp = citizencardPayService.getChkfilename(billDate, null)
+                if (YnrccUtil.CODE_SUCCESS == resp.code) {
+                    val chkfilename = resp.filename
+                    //根据filename 取文件数据
+
+
+                } else if (YnrccUtil.NO_RECORDS_TODAY == resp.code) {
+                    //当日无交易明细,也创建空记录
+
+                } else {
+                    //报错,退出对账单拉取
+                    logger.error("大理农商行对账单下载[$billDate]报错:${resp.message}")
+                    break
+                }
+            }
+
+        } catch (ex: Exception) {
+            ex.printStackTrace()
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/ynrcc_business_service_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/ynrcc_business_service_impl.kt
new file mode 100644
index 0000000..99b0566
--- /dev/null
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/ynrcc_business_service_impl.kt
@@ -0,0 +1,46 @@
+package com.supwisdom.dlpay.api.service.impl
+
+import com.supwisdom.dlpay.agent.citizencard.YnrccUtil
+import com.supwisdom.dlpay.api.dao.TransactionMainDao
+import com.supwisdom.dlpay.api.service.YnrccBusinessService
+import com.supwisdom.dlpay.framework.dao.BusinessparaDao
+import com.supwisdom.dlpay.framework.domain.TBusinesspara
+import com.supwisdom.dlpay.framework.service.SystemUtilService
+import com.supwisdom.dlpay.framework.tenant.TenantContext
+import com.supwisdom.dlpay.framework.util.DateUtil
+import com.supwisdom.dlpay.framework.util.TradeDict
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Service
+
+@Service
+class YnrccBusinessServiceImpl : YnrccBusinessService {
+    @Autowired
+    private lateinit var businessparaDao: BusinessparaDao
+    @Autowired
+    private lateinit var systemUtilService: SystemUtilService
+    @Autowired
+    private lateinit var transactionMainDao: TransactionMainDao
+
+
+    override fun getLastDownloadBillDate(): String {
+        var businesspara = businessparaDao.findByParakeyForUpdate(YnrccUtil.YNRCC_BILLS_DOWNLOAD_LASTDATE)
+        if (null != businesspara && DateUtil.checkDatetimeValid(businesspara.paraval, DateUtil.DATE_FMT)) {
+            return businesspara.paraval
+        }
+        val startdate = transactionMainDao.findMinAccdateBySourcetype(TradeDict.PAYTYPE_CITIZEN_CARD)
+                ?: systemUtilService.sysdatetime.hostdate
+
+        if (null != businesspara) {
+            businesspara.paraval = DateUtil.getNewDay(startdate, -1)
+        } else {
+            businesspara = TBusinesspara().apply {
+                this.parakey = YnrccUtil.YNRCC_BILLS_DOWNLOAD_LASTDATE
+                this.paraval = DateUtil.getNewDay(startdate, -1)
+                this.tenantId = TenantContext.getTenantSchema()
+            }
+        }
+        businesspara = businessparaDao.save(businesspara)
+        return businesspara.paraval
+    }
+
+}
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/ynrcc_business_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/ynrcc_business_service.kt
new file mode 100644
index 0000000..8a33cfd
--- /dev/null
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/ynrcc_business_service.kt
@@ -0,0 +1,9 @@
+package com.supwisdom.dlpay.api.service
+
+import org.springframework.transaction.annotation.Transactional
+
+interface YnrccBusinessService {
+    @Transactional(rollbackFor = [Exception::class])
+    fun getLastDownloadBillDate(): String
+
+}
\ No newline at end of file
diff --git a/payapi/src/main/resources/application.properties b/payapi/src/main/resources/application.properties
index 5ca4e24..9f662ab 100644
--- a/payapi/src/main/resources/application.properties
+++ b/payapi/src/main/resources/application.properties
@@ -33,6 +33,7 @@
 shopbalance.updater.cron=10/* * * * * ?
 dayend.settletask.cron=0 3/30 2-3 * * ?
 query.third.transdtl.result.cron=7 0/3 * * * ?
+download.ynrcc.chkfile.cron =3 0/10 1 * * ?
 #dayend.settletask.cron = 0 0/2 * * * ?
 ################################################
 # user password
diff --git a/ynrcc-agent/build.gradle b/ynrcc-agent/build.gradle
index 4387052..7f8d180 100644
--- a/ynrcc-agent/build.gradle
+++ b/ynrcc-agent/build.gradle
@@ -50,6 +50,7 @@
     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'
+    implementation 'commons-net:commons-net:3.6'
 
     implementation project(':payapi-common')
 
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 508b297..9775635 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
@@ -466,7 +466,7 @@
           fileHeader.setPayCount(fileHeader.getPayCount() - 1);
           fileHeader.setPayAmount(fileHeader.getPayAmount() - record.getAmount());
         } else if (FLAG_REFUND.equals(record.getFlag())) {
-          amount = +record.getAmount();
+          amount = -record.getAmount();
           flag = Constant.FLAG_REFUND;
           fileHeader.setRefundCount(fileHeader.getRefundCount() - 1);
           fileHeader.setRefundAmount(fileHeader.getRefundAmount() - record.getAmount());
@@ -489,9 +489,10 @@
   }
 
   @GetMapping("/download")
-  public void downloadFile(@RequestParam("file") String file, HttpServletResponse response) throws IOException {
+  public void downloadFile(@RequestParam("filename") String filename, HttpServletResponse response) throws IOException {
     try {
-      parseFile(file, response.getOutputStream());
+      String loaclfile = ynrccApiService.getChkfilePath(filename);
+      parseFile(loaclfile, response.getOutputStream());
     } catch (Exception e) {
       e.printStackTrace();
       response.sendError(HttpStatus.SERVICE_UNAVAILABLE.value(), e.getMessage());
diff --git a/ynrcc-agent/src/main/java/com/supwisdom/agent/api/service/YnrccApiService.java b/ynrcc-agent/src/main/java/com/supwisdom/agent/api/service/YnrccApiService.java
index 78241a1..a17cb6c 100644
--- a/ynrcc-agent/src/main/java/com/supwisdom/agent/api/service/YnrccApiService.java
+++ b/ynrcc-agent/src/main/java/com/supwisdom/agent/api/service/YnrccApiService.java
@@ -5,4 +5,6 @@
 
 public interface YnrccApiService {
   DlpayResp sendToYnrcc(String optype, DlpayReq req) throws Exception;
+
+  String getChkfilePath(String filename) throws Exception;
 }
diff --git a/ynrcc-agent/src/main/java/com/supwisdom/agent/api/service/impl/YnrccApiServiceImpl.java b/ynrcc-agent/src/main/java/com/supwisdom/agent/api/service/impl/YnrccApiServiceImpl.java
index 94eb9f7..87fd35e 100644
--- a/ynrcc-agent/src/main/java/com/supwisdom/agent/api/service/impl/YnrccApiServiceImpl.java
+++ b/ynrcc-agent/src/main/java/com/supwisdom/agent/api/service/impl/YnrccApiServiceImpl.java
@@ -8,11 +8,15 @@
 import com.supwisdom.agent.api.bean.DlpayResp;
 import com.supwisdom.agent.api.service.YnrccApiService;
 import com.supwisdom.agent.config.YnrccSocketConfig;
+import org.apache.commons.net.ftp.FTP;
+import org.apache.commons.net.ftp.FTPClient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 
+import java.io.*;
+
 @Service
 public class YnrccApiServiceImpl implements YnrccApiService {
   @Autowired
@@ -78,4 +82,57 @@
     }
   }
 
+  @Override
+  public String getChkfilePath(String filename) throws Exception {
+    //本地服务器上
+    if (!ynrccSocketConfig.getChkfileIsftp()) {
+      String localFilePath = ynrccSocketConfig.getChkfilePath();
+      if (localFilePath.endsWith("/")) {
+        return localFilePath + filename;
+      } else {
+        return localFilePath + "/" + filename;
+      }
+    }
+
+    //ftp取对账文件
+    String ftpIp = ynrccSocketConfig.getFtpIp();
+    Integer ftpPort = ynrccSocketConfig.getFtpPort();
+    String ftpUsername = ynrccSocketConfig.getFtpUsername();
+    String ftpUserpwd = ynrccSocketConfig.getFtpPassword();
+    String targetPath = ynrccSocketConfig.getFtpTargetPath();
+    String localFilePath = ynrccSocketConfig.getChkfilePath();
+    if (StringUtil.isEmpty(ftpIp) || null == ftpPort || StringUtil.isEmpty(ftpUsername) || StringUtil.isEmpty(ftpUserpwd) || StringUtil.isEmpty(targetPath)) {
+      throw new Exception("ftp参数未配置");
+    }
+
+    FTPClient ftpClient = new FTPClient();
+    try {
+      ftpClient.connect(ftpIp, ftpPort);
+      ftpClient.login(ftpUsername, ftpUserpwd);
+      ftpClient.enterLocalPassiveMode();
+      ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
+
+      String savePath = localFilePath.endsWith("/") ? (localFilePath + filename) : (localFilePath + "/" + filename);
+      String targetFile = targetPath.endsWith("/") ? (targetPath + filename) : (targetPath + "/" + filename);
+      //本地文件
+      File localFile = new File(savePath);
+      File parentDir = localFile.getParentFile();
+      if (!parentDir.exists()) {
+        parentDir.mkdir();
+      }
+
+      OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(localFile));
+      boolean success = ftpClient.retrieveFile(targetFile, outputStream);
+      outputStream.close();
+
+      if (success) {
+        return savePath;
+      }
+    } catch (IOException ex) {
+      ex.printStackTrace();
+    }
+
+    throw new Exception("获取对账文件失败!");
+  }
+
 }
diff --git a/ynrcc-agent/src/main/java/com/supwisdom/agent/config/YnrccSocketConfig.java b/ynrcc-agent/src/main/java/com/supwisdom/agent/config/YnrccSocketConfig.java
index e11398c..240f133 100644
--- a/ynrcc-agent/src/main/java/com/supwisdom/agent/config/YnrccSocketConfig.java
+++ b/ynrcc-agent/src/main/java/com/supwisdom/agent/config/YnrccSocketConfig.java
@@ -7,13 +7,37 @@
 public class YnrccSocketConfig {
   @Value("${ynrcc.socket.ip}")
   private String ip;
+
   @Value("${ynrcc.socket.port}")
   private Integer port;
+
   @Value("${ynrcc.socket.timeout}")
   private int timeout = 10;
+
   @Value("${ynrcc.md5.key}")
   private String md5Key;
 
+  @Value("${ynrcc.chkfile.ftp.enabled}")
+  private Boolean chkfileIsftp;
+
+  @Value("${ynrcc.chkfile.ftp.ip}")
+  private String ftpIp;
+
+  @Value("${ynrcc.chkfile.ftp.port}")
+  private Integer ftpPort;
+
+  @Value("${ynrcc.chkfile.local.path}")
+  private String chkfilePath;
+
+  @Value("${ynrcc.chkfile.ftp.username}")
+  private String ftpUsername;
+
+  @Value("${ynrcc.chkfile.ftp.userpwd}")
+  private String ftpPassword;
+
+  @Value("${ynrcc.chkfile.ftp.targetpath}")
+  private String ftpTargetPath;
+
   public String getIp() {
     return ip;
   }
@@ -29,4 +53,32 @@
   public String getMd5Key() {
     return md5Key;
   }
+
+  public Boolean getChkfileIsftp() {
+    return chkfileIsftp;
+  }
+
+  public String getFtpIp() {
+    return ftpIp;
+  }
+
+  public Integer getFtpPort() {
+    return ftpPort;
+  }
+
+  public String getChkfilePath() {
+    return chkfilePath;
+  }
+
+  public String getFtpUsername() {
+    return ftpUsername;
+  }
+
+  public String getFtpPassword() {
+    return ftpPassword;
+  }
+
+  public String getFtpTargetPath() {
+    return ftpTargetPath;
+  }
 }
diff --git a/ynrcc-agent/src/main/resources/application.properties b/ynrcc-agent/src/main/resources/application.properties
index 167f4ac..18f790d 100644
--- a/ynrcc-agent/src/main/resources/application.properties
+++ b/ynrcc-agent/src/main/resources/application.properties
@@ -13,7 +13,6 @@
 #spring.redis.port=16379
 #spring.redis.password=kingstar
 #spring.redis.database=0
-
 # logging
 logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} %-5level %logger{36} - %msg%n
 logging.level.org.hibernate.SQL=debug
@@ -27,10 +26,18 @@
 ##################################################
 ## quartz task scheduler
 #dayend.settletask.cron = 0 0/2 * * * ?
-
 ############# YNRCC SOCKET ###############
 ynrcc.socket.ip=127.0.0.1
 ynrcc.socket.port=8089
 ## 超时时间(分钟)
-ynrcc.socket.timeout = 10
+ynrcc.socket.timeout=10
 ynrcc.md5.key=80816b7947ed016bff8079557735006e
+ynrcc.chkfile.ftp.enabled=false
+ynrcc.chkfile.local.path=/opt/supwisdom/chkfile
+ynrcc.chkfile.ftp.ip=
+ynrcc.chkfile.ftp.port=
+ynrcc.chkfile.ftp.username=
+ynrcc.chkfile.ftp.userpwd=
+ynrcc.chkfile.ftp.targetpath=/
+
+