数据同步接口定义
diff --git a/build.gradle b/build.gradle
index d9f0ca9..a0654a6 100644
--- a/build.gradle
+++ b/build.gradle
@@ -59,6 +59,7 @@
     implementation 'org.springframework.social:spring-social-web:1.1.6.RELEASE'
     implementation 'org.jetbrains.kotlin:kotlin-reflect'
     implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
+    implementation 'commons-codec:commons-codec:1.12'
 
     implementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery:2.1.2.RELEASE'
 
diff --git a/src/main/java/com/supwisdom/dlpay/framework/util/SysparaUtil.java b/src/main/java/com/supwisdom/dlpay/framework/util/SysparaUtil.java
index c6788d8..3ccb2a7 100644
--- a/src/main/java/com/supwisdom/dlpay/framework/util/SysparaUtil.java
+++ b/src/main/java/com/supwisdom/dlpay/framework/util/SysparaUtil.java
@@ -12,7 +12,11 @@
 
   public static final int SYSPARAID_NO1 = 1; //系统默认最大余额限制的ID
   public static final int SYSPARAID_NO2 = 2; //paraid=2
-  public static final int SYSPARAID_NO3 = 3; //用户过期时间,单位秒
+
+  public static final int SYSPARAID_NO2019 = 2019; //与卡管系统对接的应用ID
+  public static final int SYSPARAID_NO2020 = 2020; //与卡管系统对接的应用appkey
+  public static final int SYSPARAID_NO2021 = 2021; //与卡管系统对接的业务参数deskey
+
   public static final double SYSPARA_NO1_DEFAULT = 10000.0; // 系统默认最大余额限制
 
 }
diff --git a/src/main/java/com/supwisdom/dlpay/util/DESedeUtil.java b/src/main/java/com/supwisdom/dlpay/util/DESedeUtil.java
new file mode 100644
index 0000000..0d0e4cf
--- /dev/null
+++ b/src/main/java/com/supwisdom/dlpay/util/DESedeUtil.java
@@ -0,0 +1,107 @@
+package com.supwisdom.dlpay.util;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.lang.StringUtils;
+
+import javax.crypto.*;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+import java.io.UnsupportedEncodingException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * 加密/解密工具类
+ */
+public class DESedeUtil {
+  /**
+   * 加密工具类唯一实例
+   */
+  private static DESedeUtil instance = null;
+  /**
+   *
+   */
+  private Cipher cipher;
+  /**
+   *
+   */
+  private SecretKey secretKey;
+  /**
+   *
+   */
+  private IvParameterSpec ivSpec;
+
+  /**
+   * 初始化、创建密钥数据
+   *
+   * @throws NoSuchPaddingException
+   * @throws NoSuchAlgorithmException
+   */
+  private DESedeUtil(String deskey) throws NoSuchAlgorithmException, NoSuchPaddingException {
+    byte[] keyData = Base64.decodeBase64(deskey);
+    String fullAlg = "DESede/CBC/PKCS5Padding";
+    cipher = Cipher.getInstance(fullAlg);
+    int blockSize = cipher.getBlockSize();
+    byte[] iv = new byte[blockSize];
+    for (int i = 0; i < blockSize; ++i) {
+      iv[i] = 0;
+    }
+    secretKey = new SecretKeySpec(keyData, StringUtils.substringBefore(fullAlg, "/"));
+    ivSpec = new IvParameterSpec(iv);
+  }
+
+  /**
+   * 获取唯一实例
+   *
+   * @return
+   * @throws NoSuchPaddingException
+   * @throws NoSuchAlgorithmException
+   */
+  public static DESedeUtil getInstance(String deskey) throws NoSuchAlgorithmException, NoSuchPaddingException {
+    if (null == instance) {
+      synchronized (DESedeUtil.class) {
+        if (null == instance) {
+          instance = new DESedeUtil(deskey);
+        }
+      }
+    }
+    return instance;
+  }
+
+  /**
+   * 加密
+   *
+   * @param s 待加密字符串
+   * @return 返回BASE64编码加密字符串
+   * @throws InvalidAlgorithmParameterException
+   * @throws InvalidKeyException
+   * @throws BadPaddingException
+   * @throws IllegalBlockSizeException
+   * @throws UnsupportedEncodingException
+   */
+  public String encode(String s) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
+    cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
+    byte[] cipherBytes = cipher.doFinal(s.getBytes("UTF-8"));
+    return new String(Base64.encodeBase64(cipherBytes));
+  }
+
+  /**
+   * 解密
+   *
+   * @param s 待解密字符串
+   * @return 返回明文
+   * @throws InvalidAlgorithmParameterException
+   * @throws InvalidKeyException
+   * @throws BadPaddingException
+   * @throws IllegalBlockSizeException
+   * @throws UnsupportedEncodingException
+   */
+  public String decode(String s) throws InvalidKeyException, InvalidAlgorithmParameterException, IllegalBlockSizeException, BadPaddingException, UnsupportedEncodingException {
+    cipher.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
+    byte[] resultBytes = cipher.doFinal(Base64.decodeBase64(s));
+    return new String(resultBytes, "UTF-8");
+  }
+
+}
+
diff --git a/src/main/kotlin/com/supwisdom/dlpay/api/bean/api_request_param.kt b/src/main/kotlin/com/supwisdom/dlpay/api/bean/api_request_param.kt
index 447a3f2..5568b6f 100644
--- a/src/main/kotlin/com/supwisdom/dlpay/api/bean/api_request_param.kt
+++ b/src/main/kotlin/com/supwisdom/dlpay/api/bean/api_request_param.kt
@@ -1,8 +1,11 @@
 package com.supwisdom.dlpay.api.bean
 
+import com.google.gson.Gson
+import com.google.gson.reflect.TypeToken
 import com.supwisdom.dlpay.exception.RequestParamCheckException
 import com.supwisdom.dlpay.framework.util.*
 import com.supwisdom.dlpay.util.ConstantUtil
+import com.supwisdom.dlpay.util.DESedeUtil
 
 // ============================ USER ============================ //
 class OpenUserParam : APIRequestParam() {
@@ -355,3 +358,53 @@
 }
 
 
+class DaliDatasyncParam {
+    var app_id: String = ""
+    var data: String = ""
+    var count: Int = 0
+    var timestamp: String = ""
+    var sign_type: String = ""
+    var sign: String = ""
+
+    fun checkParam(): Boolean {
+        if (StringUtil.isEmpty(app_id)) throw RequestParamCheckException(2000, "请求参数错误[应用ID为空]")
+        if (StringUtil.isEmpty(data)) throw RequestParamCheckException(2000, "请求参数错误[业务参数报文为空]")
+        if (null == count || count < 1) throw RequestParamCheckException(2000, "请求参数错误[业务明细至少有一条]")
+        if (!DateUtil.checkDatetimeValid(timestamp, DateUtil.DATETIME_FMT)) throw RequestParamCheckException(2000, "请求参数错误[时间戳]")
+        if (StringUtil.isEmpty(sign_type) || !"HmacSHA256".equals(sign_type, true)) throw RequestParamCheckException(2000, "请求参数错误[签名算法]")
+        if (StringUtil.isEmpty(sign)) throw RequestParamCheckException(2000, "请求参数错误[签名为空]")
+
+        return true
+    }
+
+    fun checkSign(key: String): Boolean {
+        val signData = "app_id=$app_id&count=$count&data=$data&timestamp=$timestamp"
+        return sign.equals(HmacUtil.HMACSHA256(signData, key), true)
+    }
+
+    fun decData(deskey: String): ArrayList<DaliDatasyncDetail> {
+        try {
+            val listType = object : TypeToken<ArrayList<ConsumeFeetype>>() {}.type
+            val decstr = DESedeUtil.getInstance(deskey).decode(data)
+            return Gson().fromJson(decstr, listType)
+        } catch (e: Exception) {
+            throw RequestParamCheckException(1001, "数据解密失败!")
+        }
+    }
+
+}
+
+class DaliDatasyncDetail {
+    var cardno: String = ""
+    var cardphyid: String = ""
+    var expiredate: String = ""
+    var cardstatus: String = ""
+    var bankcardno: String? = null
+    var username: String = ""
+    var idtype: String = ""
+    var idno: String = ""
+    var mobile: String? = null
+    var email: String? = null
+}
+
+
diff --git a/src/main/kotlin/com/supwisdom/dlpay/api/controller/dali_datasync_api_controller.kt b/src/main/kotlin/com/supwisdom/dlpay/api/controller/dali_datasync_api_controller.kt
new file mode 100644
index 0000000..9756233
--- /dev/null
+++ b/src/main/kotlin/com/supwisdom/dlpay/api/controller/dali_datasync_api_controller.kt
@@ -0,0 +1,63 @@
+package com.supwisdom.dlpay.api.controller
+
+import com.supwisdom.dlpay.api.bean.DaliDatasyncParam
+import com.supwisdom.dlpay.exception.RequestParamCheckException
+import com.supwisdom.dlpay.framework.ResponseBodyBuilder
+import com.supwisdom.dlpay.framework.service.SystemUtilService
+import com.supwisdom.dlpay.framework.util.StringUtil
+import com.supwisdom.dlpay.framework.util.SysparaUtil
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.http.ResponseEntity
+import org.springframework.web.bind.annotation.*
+
+@RequestMapping("/api/common")
+class DaliDatasyncApiController {
+    @Autowired
+    lateinit var systemUtilService: SystemUtilService
+
+    /**
+     * ============================================================================
+     * 大理卡管系统推送市民卡信息同步接口
+     * ============================================================================
+     * */
+    @PostMapping("/datasync")
+    @ResponseBody
+    fun daliDatasync(@RequestBody param: DaliDatasyncParam): ResponseEntity<Any> {
+        try {
+            param.checkParam()
+            val appid = systemUtilService.getSysparaValue(SysparaUtil.SYSPARAID_NO2019)
+            val appkey = systemUtilService.getSysparaValue(SysparaUtil.SYSPARAID_NO2020)
+            val deskey = systemUtilService.getSysparaValue(SysparaUtil.SYSPARAID_NO2021)
+            if (StringUtil.isEmpty(appid) || StringUtil.isEmpty(appkey) || StringUtil.isEmpty(deskey)) {
+                return ResponseEntity.ok(ResponseBodyBuilder.create()
+                        .fail(1000, "系统参数未配置"))
+            } else if (appid != param.app_id) {
+                throw RequestParamCheckException(2000, "请求参数错误[应用ID错误]")
+            }
+            if (!param.checkSign(appkey)) {
+                return ResponseEntity.ok(ResponseBodyBuilder.create()
+                        .fail(2001, "签名错误"))
+            }
+
+            val datalist = param.decData(deskey)
+            if (param.count != datalist.size) {
+                return ResponseEntity.ok(ResponseBodyBuilder.create()
+                        .fail(2002, "请求参数错误[数据条数不匹配]"))
+            }
+
+            datalist.forEach {
+                TODO("更新数据逻辑")
+            }
+
+            return return ResponseEntity.ok(mapOf("retcode" to "0000", "retmsg" to "SUCCESS"))
+        } catch (ex: RequestParamCheckException) {
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .fail(ex.errCode, ex.message ?: "请求参数错误"))
+        } catch (e: Exception) {
+            e.printStackTrace()
+            return ResponseEntity.ok(ResponseBodyBuilder.create()
+                    .fail(4000, "系统处理错误"))
+        }
+    }
+
+}
\ No newline at end of file