卡管初始化TXT文件导入接口
diff --git a/payapi/build.gradle b/payapi/build.gradle
index ed8ce9b..d5c3abf 100644
--- a/payapi/build.gradle
+++ b/payapi/build.gradle
@@ -89,6 +89,7 @@
     implementation files('libs/sms.jar')
 //    implementation files('libs/ojdbc6.jar')
     implementation 'commons-dbcp:commons-dbcp:1.4'
+    implementation 'commons-beanutils:commons-beanutils:1.9.3'
 
     implementation project(':payapi-common')
     /*支付宝SDK*/
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/dao/CitycardTempDao.java b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/CitycardTempDao.java
new file mode 100644
index 0000000..fb00e85
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/api/dao/CitycardTempDao.java
@@ -0,0 +1,15 @@
+package com.supwisdom.dlpay.api.dao;
+
+import com.supwisdom.dlpay.api.domain.TCitycardTemp;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+
+import java.util.List;
+
+public interface CitycardTempDao extends JpaRepository<TCitycardTemp, String> {
+  @Query("from TCitycardTemp where id=?1 ")
+  List<TCitycardTemp> findbyTempid(String tempid);
+
+  @Query("from TCitycardTemp where lastsaved>=?1 order by lastsaved ")
+  List<TCitycardTemp> findbyUpdtime(String updtime);
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TCitycardTemp.java b/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TCitycardTemp.java
new file mode 100644
index 0000000..7d7f454
--- /dev/null
+++ b/payapi/src/main/java/com/supwisdom/dlpay/api/domain/TCitycardTemp.java
@@ -0,0 +1,188 @@
+package com.supwisdom.dlpay.api.domain;
+
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "TB_CITYCARD_TEMP")
+public class TCitycardTemp {
+  @Id
+  @GenericGenerator(name = "idGenerator", strategy = "uuid")
+  @GeneratedValue(generator = "idGenerator")
+  @Column(name = "ID", nullable = false, length = 32)
+  private String id;
+
+  @Column(name = "CARDNO", length = 32)
+  private String cardno;
+
+  @Column(name = "CARDPHYID", length = 20)
+  private String cardphyid;
+
+  @Column(name = "EXPIREDATE", length = 8)
+  private String expiredate;
+
+  @Column(name = "CARDSTATUS", length = 10)
+  private String cardstatus;
+
+  @Column(name = "BANKCARDNO", length = 32)
+  private String bankcardno;
+
+  @Column(name = "USERNAME", length = 200)
+  private String username;
+
+  @Column(name = "SEX", length = 10)
+  private String sex;
+
+  @Column(name = "IDTYPE", length = 10)
+  private String idtype;
+
+  @Column(name = "IDNO", length = 60)
+  private String idno;
+
+  @Column(name = "MOBILE", length = 30)
+  private String mobile;
+
+  @Column(name = "EMAIL", length = 60)
+  private String email;
+
+  @Column(name = "STATUS", length = 20)
+  private String status;
+
+  @Column(name = "REMARK", length = 600)
+  private String remark;
+
+  @Column(name = "LASTSAVED", length = 14)
+  private String lastsaved;
+
+  @Column(name = "tenantid", length = 20)
+  private String tenantid = "";
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(String id) {
+    this.id = id;
+  }
+
+  public String getCardno() {
+    return cardno;
+  }
+
+  public void setCardno(String cardno) {
+    this.cardno = cardno;
+  }
+
+  public String getCardphyid() {
+    return cardphyid;
+  }
+
+  public void setCardphyid(String cardphyid) {
+    this.cardphyid = cardphyid;
+  }
+
+  public String getExpiredate() {
+    return expiredate;
+  }
+
+  public void setExpiredate(String expiredate) {
+    this.expiredate = expiredate;
+  }
+
+  public String getCardstatus() {
+    return cardstatus;
+  }
+
+  public void setCardstatus(String cardstatus) {
+    this.cardstatus = cardstatus;
+  }
+
+  public String getBankcardno() {
+    return bankcardno;
+  }
+
+  public void setBankcardno(String bankcardno) {
+    this.bankcardno = bankcardno;
+  }
+
+  public String getUsername() {
+    return username;
+  }
+
+  public void setUsername(String username) {
+    this.username = username;
+  }
+
+  public String getSex() {
+    return sex;
+  }
+
+  public void setSex(String sex) {
+    this.sex = sex;
+  }
+
+  public String getIdtype() {
+    return idtype;
+  }
+
+  public void setIdtype(String idtype) {
+    this.idtype = idtype;
+  }
+
+  public String getIdno() {
+    return idno;
+  }
+
+  public void setIdno(String idno) {
+    this.idno = idno;
+  }
+
+  public String getMobile() {
+    return mobile;
+  }
+
+  public void setMobile(String mobile) {
+    this.mobile = mobile;
+  }
+
+  public String getEmail() {
+    return email;
+  }
+
+  public void setEmail(String email) {
+    this.email = email;
+  }
+
+  public String getStatus() {
+    return status;
+  }
+
+  public void setStatus(String status) {
+    this.status = status;
+  }
+
+  public String getRemark() {
+    return remark;
+  }
+
+  public void setRemark(String remark) {
+    this.remark = remark;
+  }
+
+  public String getLastsaved() {
+    return lastsaved;
+  }
+
+  public void setLastsaved(String lastsaved) {
+    this.lastsaved = lastsaved;
+  }
+
+  public String getTenantid() {
+    return tenantid;
+  }
+
+  public void setTenantid(String tenantid) {
+    this.tenantid = tenantid;
+  }
+}
diff --git a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/StringUtil.java b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/StringUtil.java
index 2657b51..1556941 100644
--- a/payapi/src/main/java/com/supwisdom/dlpay/framework/util/StringUtil.java
+++ b/payapi/src/main/java/com/supwisdom/dlpay/framework/util/StringUtil.java
@@ -3,6 +3,7 @@
 import java.util.*;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import org.apache.commons.beanutils.BeanUtils;
 
 public class StringUtil {
   /**
@@ -166,4 +167,16 @@
       return url+"/"+path;
     }
   }
+
+  public static void transforToBean(List<String> fields, List<String> columns, Object bean) throws Exception {
+    if (null == fields) throw new Exception("fields is null");
+    if (null == columns) throw new Exception("columns is null");
+    if (fields.size() < columns.size()) throw new Exception("错误的列定义");
+    Map<String, String> data = new HashMap<>(0);
+    for (int i = 0; i < fields.size(); i++) {
+      data.put(fields.get(i), columns.get(i));
+    }
+    BeanUtils.populate(bean, data);
+    return;
+  }
 }
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/dali_datasync_api_controller.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/dali_datasync_api_controller.kt
index 80a9ca9..db41364 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/dali_datasync_api_controller.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/controller/dali_datasync_api_controller.kt
@@ -1,17 +1,22 @@
 package com.supwisdom.dlpay.api.controller
 
 import com.google.gson.Gson
+import com.supwisdom.dlpay.api.bean.DaliDatasyncDetail
 import com.supwisdom.dlpay.api.bean.DaliDatasyncErrorDetail
 import com.supwisdom.dlpay.api.bean.DaliDatasyncParam
 import com.supwisdom.dlpay.api.exception.RequestParamCheckException
 import com.supwisdom.dlpay.api.service.DaliDatasyncService
 import com.supwisdom.dlpay.framework.ResponseBodyBuilder
 import com.supwisdom.dlpay.framework.service.SystemUtilService
+import com.supwisdom.dlpay.framework.util.DateUtil
 import com.supwisdom.dlpay.framework.util.StringUtil
 import com.supwisdom.dlpay.framework.util.SysparaUtil
+import mu.KotlinLogging
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.http.ResponseEntity
 import org.springframework.web.bind.annotation.*
+import java.io.*
+import java.nio.charset.Charset
 
 @RequestMapping("/api/common")
 @RestController
@@ -21,6 +26,8 @@
     @Autowired
     lateinit var daliDatasyncService: DaliDatasyncService
 
+    private val logger = KotlinLogging.logger { }
+
     /**
      * ============================================================================
      * 大理卡管系统推送市民卡信息同步接口
@@ -65,12 +72,27 @@
                         errcode = "2001"
                         errmsg = e1.message ?: "明细数据关键字段为空"
                     })
+
+                    try {
+                        daliDatasyncService.saveUpdateUserinfoErrors(detail, e1.message ?: "明细数据关键字段为空") //记录失败数据
+                    } catch (e3: Exception) {
+                        logger.error("保存错误同步数据失败" + Gson().toJson(detail))
+                        e3.printStackTrace()
+                    }
+
                 } catch (e2: Exception) {
                     errlist.add(DaliDatasyncErrorDetail().apply {
                         cardno = detail.cardno
                         errcode = "3000"
                         errmsg = e2.message ?: "明细处理错误"
                     })
+
+                    try {
+                        daliDatasyncService.saveUpdateUserinfoErrors(detail, e2.message ?: e2.javaClass.name) //记录失败数据
+                    } catch (e4: Exception) {
+                        logger.error("保存错误同步数据失败" + Gson().toJson(detail))
+                        e4.printStackTrace()
+                    }
                 }
             }
 
@@ -90,4 +112,134 @@
         }
     }
 
+    /**
+     * ============================================================================
+     * 大理卡管系统第一次初始化数据txt导入接口
+     * ============================================================================
+     * */
+    @GetMapping("/import")
+    fun importDaliInitData(@RequestParam("filename") filename: String): ResponseEntity<Any> {
+        if (StringUtil.isEmpty(filename)) {
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .fail(30001, "请输入初始化数据txt文件全路径名"))
+        } else if (!filename.endsWith(".txt")) {
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .fail(30001, "请输入初始化数据txt文件"))
+        }
+
+        try {
+            val txtFile = File(filename)
+            val batchSize = 1000
+            var sumCount = 0
+            var failCount = 0
+
+            txtFile.bufferedReader(Charset.forName("GBK")).use { reader ->
+                val header = reader.readLine()
+                val fields = header.split("|")
+
+                var datalist = ArrayList<DaliDatasyncDetail>()
+                while (true) {
+                    val line = reader.readLine() ?: break
+                    if (StringUtil.isEmpty(line)) continue
+                    val column = line.split("|")
+                    val bean = DaliDatasyncDetail()
+                    StringUtil.transforToBean(fields, column, bean)
+                    datalist.add(bean)
+                    sumCount++
+                    if (datalist.size >= batchSize) {
+                        val fcnt = doBatchSaveData(datalist) //保存
+                        failCount += fcnt
+                        datalist.clear()
+                    }
+                }
+
+                //保存最后的不足batchSize的记录
+                if (datalist.size > 0) {
+                    val fcnt = doBatchSaveData(datalist) //保存
+                    failCount += fcnt
+                }
+            }
+
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .data("totalcnt", sumCount)
+                    .data("failcnt", failCount)
+                    .data("successcnt", sumCount - failCount)
+                    .success("导入请求成功"))
+            
+        } catch (ex: Exception) {
+            ex.printStackTrace()
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .exception(4000, ex, "处理异常!" + (ex.message ?: ex.javaClass.name)))
+        }
+
+    }
+
+    private fun doBatchSaveData(list: ArrayList<DaliDatasyncDetail>): Int {
+        try {
+            daliDatasyncService.doBatchSaveOrUpdateUserInfos(list)
+            return 0  //批量保存成功,无失败
+        } catch (e1: Exception) {
+        }
+
+        //批量保存有异常,逐笔保存
+        var failcnt = 0
+        for (bean in list) {
+            try {
+                bean.checkParam()
+                daliDatasyncService.doUpdateUserInfos(bean)
+            } catch (e2: Exception) {
+                failcnt++
+                try {
+                    daliDatasyncService.saveUpdateUserinfoErrors(bean, e2.message ?: e2.javaClass.name)
+                } catch (e3: Exception) {
+                    logger.error("初始化txt文件保存错误同步数据失败" + Gson().toJson(bean))
+                    e3.printStackTrace()
+                }
+                continue
+            }
+        }
+        return failcnt
+    }
+
+    /**
+     * ============================================================================
+     * 大理卡管系统错误同步信息补处理
+     * ============================================================================
+     * */
+    @GetMapping("/dealerror")
+    fun doDealErrorRecord(@RequestParam(name = "tempid", required = false, defaultValue = "") tempid: String,
+                          @RequestParam(name = "updtime", required = false, defaultValue = "") updtime: String): ResponseEntity<Any> {
+        if (StringUtil.isEmpty(tempid) && !DateUtil.checkDatetimeValid(updtime, DateUtil.DATETIME_FMT)) {
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .fail(99, "请求参数错误[tempid和updtime不能同时为空]"))
+        }
+
+        try {
+            var totalcnt = 0
+            var failcnt = 0
+            var detailMsg = ""
+            daliDatasyncService.getErrorUpdateUserinfos(tempid, updtime).forEach {
+                totalcnt++
+                try {
+                    daliDatasyncService.dealErrorRecord(it)
+                } catch (e1: Exception) {
+                    failcnt++
+                    detailMsg += ("${it.cardno}同步出错:" + (e1.message ?: e1.javaClass.name) + "\n")
+                }
+            }
+
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .data("totalcnt", totalcnt)
+                    .data("failcnt", failcnt)
+                    .data("successcnt", totalcnt - failcnt)
+                    .data("detailmsg", detailMsg)
+                    .success("导入成功"))
+        } catch (ex: Exception) {
+            ex.printStackTrace()
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .exception(99, ex, "处理异常!" + (ex.message ?: ex.javaClass.name)))
+        }
+    }
+
+
 }
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/dali_datasync_service.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/dali_datasync_service.kt
index d5a960f..1356d46 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/dali_datasync_service.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/dali_datasync_service.kt
@@ -2,12 +2,25 @@
 
 import com.supwisdom.dlpay.api.bean.DaliDatasyncDetail
 import com.supwisdom.dlpay.api.domain.TCard
+import com.supwisdom.dlpay.api.domain.TCitycardTemp
 import org.springframework.transaction.annotation.Propagation
 import org.springframework.transaction.annotation.Transactional
 
-interface DaliDatasyncService{
+interface DaliDatasyncService {
 
-    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = arrayOf(Exception::class))
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = [Exception::class])
     fun doUpdateUserInfos(bean: DaliDatasyncDetail): Boolean
 
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = [Exception::class])
+    fun doBatchSaveOrUpdateUserInfos(userlist: ArrayList<DaliDatasyncDetail>): Boolean
+
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = [Exception::class])
+    fun saveUpdateUserinfoErrors(bean: DaliDatasyncDetail, errmsg: String)
+
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = [Exception::class], readOnly = true)
+    fun getErrorUpdateUserinfos(tmpid: String, updtime: String): List<TCitycardTemp>
+
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = [Exception::class])
+    fun dealErrorRecord(temp: TCitycardTemp)
+
 }
\ No newline at end of file
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/dali_datasync_service_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/dali_datasync_service_impl.kt
index cc4cf56..59833f6 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/dali_datasync_service_impl.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/dali_datasync_service_impl.kt
@@ -3,9 +3,11 @@
 import com.supwisdom.dlpay.api.bean.DaliDatasyncDetail
 import com.supwisdom.dlpay.api.dao.AccountDao
 import com.supwisdom.dlpay.api.dao.CardDao
+import com.supwisdom.dlpay.api.dao.CitycardTempDao
 import com.supwisdom.dlpay.api.dao.PersonDao
 import com.supwisdom.dlpay.api.domain.TAccount
 import com.supwisdom.dlpay.api.domain.TCard
+import com.supwisdom.dlpay.api.domain.TCitycardTemp
 import com.supwisdom.dlpay.api.domain.TPerson
 import com.supwisdom.dlpay.api.service.DaliDatasyncService
 import com.supwisdom.dlpay.api.types.IDTypes
@@ -26,6 +28,8 @@
     @Autowired
     lateinit var cardDao: CardDao
     @Autowired
+    lateinit var citycardTempDao: CitycardTempDao
+    @Autowired
     lateinit var systemUtilService: SystemUtilService
 
     override fun doUpdateUserInfos(bean: DaliDatasyncDetail): Boolean {
@@ -79,7 +83,7 @@
                 person = personDao.save(person)
             }
         } else {
-            //修改
+            //新增
             person = TPerson().apply {
                 name = bean.username
                 this.sex = sex
@@ -110,8 +114,8 @@
                 lastdayDpsamt = 0.00
                 opendate = systime.hostdate
             }
-            account.generateTac()
             account.tenantid = TenantContext.getTenantSchema()
+            account.generateTac()
             accountDao.save(account)
         }
 
@@ -125,25 +129,25 @@
             }
 
             //fixme: bean.cardstatus 判断修改状态
-            if(TradeDict.STATUS_CLOSED == cardStatus){
+            if (TradeDict.STATUS_CLOSED == cardStatus) {
                 //注销卡片
-                if(cityCard.status != TradeDict.STATUS_CLOSED){
+                if (cityCard.status != TradeDict.STATUS_CLOSED) {
                     cardUpdateFlag = true
                     cityCard.status = TradeDict.STATUS_CLOSED
                 }
-            }else{
+            } else {
                 //卡片的其他状态,代表交易状态
-                if(TradeDict.STATUS_NORMAL != cityCard.transStatus){
+                if (TradeDict.STATUS_NORMAL != cityCard.transStatus) {
                     cardUpdateFlag = true
                     cityCard.status = TradeDict.STATUS_NORMAL
                 }
-                if(cardStatus != cityCard.transStatus){
+                if (cardStatus != cityCard.transStatus) {
                     cardUpdateFlag = true
-                    cityCard.transStatus=cardStatus
+                    cityCard.transStatus = cardStatus
                 }
             }
 
-            if(cardUpdateFlag){
+            if (cardUpdateFlag) {
                 cityCard.lastsaved = systime.hostdatetime
                 cardDao.save(cityCard) //更新
             }
@@ -201,4 +205,60 @@
         return true
     }
 
+    override fun doBatchSaveOrUpdateUserInfos(userlist: ArrayList<DaliDatasyncDetail>): Boolean {
+        for (bean in userlist) {
+            bean.checkParam()
+            doUpdateUserInfos(bean)
+        }
+        return true
+    }
+
+    override fun saveUpdateUserinfoErrors(bean: DaliDatasyncDetail, errmsg: String) {
+        citycardTempDao.save(TCitycardTemp().apply {
+            this.cardno = bean.cardno
+            this.cardphyid = bean.cardphyid
+            this.expiredate = bean.expiredate
+            this.cardstatus = bean.cardstatus
+            this.bankcardno = bean.bankcardno
+            this.username = bean.username
+            this.sex = bean.sex
+            this.idtype = bean.idtype
+            this.idno = bean.idno
+            this.mobile = bean.mobile
+            this.email = bean.email
+            this.status = TradeDict.DTL_STATUS_FAIL
+            this.remark = errmsg
+            this.lastsaved = systemUtilService.sysdatetime.hostdatetime
+            this.tenantid = TenantContext.getTenantSchema()
+        })
+    }
+
+    override fun getErrorUpdateUserinfos(tmpid: String, updtime: String): List<TCitycardTemp> {
+        if (!StringUtil.isEmpty(tmpid)) {
+            return citycardTempDao.findbyTempid(tmpid.trim()) ?: ArrayList<TCitycardTemp>()
+        } else {
+            return citycardTempDao.findbyUpdtime(updtime) ?: ArrayList<TCitycardTemp>()
+        }
+    }
+
+    override fun dealErrorRecord(temp: TCitycardTemp) {
+        val bean = DaliDatasyncDetail().apply {
+            this.cardno = temp.cardno
+            this.cardphyid = temp.cardphyid
+            this.expiredate = temp.expiredate
+            this.cardstatus = temp.cardstatus
+            this.bankcardno = temp.bankcardno
+            this.username = temp.username
+            this.sex = temp.sex
+            this.idtype = temp.idtype
+            this.idno = temp.idno
+            this.mobile = temp.mobile
+            this.email = temp.email
+        }
+        bean.checkParam()
+        if (doUpdateUserInfos(bean)) {
+            citycardTempDao.delete(temp) //删除
+        }
+    }
+
 }
\ No newline at end of file
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 2c4ca84..2b788c2 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
@@ -435,8 +435,9 @@
 
   private void parseFile(String filePath, OutputStream output) {
     int lineno = 1;
+    BufferedReader reader = null;
     try {
-      BufferedReader reader = new BufferedReader(
+      reader = new BufferedReader(
           new InputStreamReader(
               new FileInputStream(filePath), "GBK"));
       String header = reader.readLine();
@@ -488,6 +489,13 @@
     } catch (IOException e) {
       e.printStackTrace();
       throw new IllegalArgumentException("对账文件数据格式错误,行数 " + lineno + " 。");
+    } finally {
+      try {
+        if (null != reader) {
+          reader.close();
+        }
+      } catch (Exception ex) {
+      }
     }
   }