两码转换支持配置
diff --git a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt
index 5560a5d..a55d1e9 100644
--- a/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt
+++ b/payapi/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/qrcode_srvice_impl.kt
@@ -1,5 +1,6 @@
 package com.supwisdom.dlpay.api.service.impl
 
+import com.google.gson.Gson
 import com.supwisdom.dlpay.api.bean.ApiResponse
 import com.supwisdom.dlpay.api.bean.CitizenQrcodeKey
 import com.supwisdom.dlpay.api.bean.DoorQrcodeResponse
@@ -13,12 +14,14 @@
 import com.supwisdom.dlpay.framework.util.StringUtil
 import com.supwisdom.dlpay.framework.util.TradeDict
 import com.supwisdom.dlpay.mobile.service.MobileApiService
-import com.supwisdom.dlpay.util.ConstantUtil
+import com.supwisdom.dlpay.util.*
 import mu.KotlinLogging
 import org.apache.commons.lang.math.NumberUtils
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.data.redis.core.RedisTemplate
 import org.springframework.stereotype.Service
+import com.supwisdom.dlpay.framework.util.MD5
+import java.time.Duration
 
 @Service
 class QRCodeServiceImpl:QRCodeService{
@@ -39,16 +42,19 @@
         var step = "5"
         var offset = "3"
         var debug = "false"
+        var offlineQrcode = "false"
         val rootkey = systemUtilService.getBusinessValue("aes.cfb.rootkey")?.trim() ?: ""
         val iv = systemUtilService.getBusinessValue("aes.cfb.iv")?.trim() ?: ""
         val prefix = systemUtilService.getBusinessValue("dlsmk.qrcode.prefix")?.trim() ?: ""
-        val totpStep = systemUtilService.getBusinessValue("dlsmk.qrcode.totp.step")
+        val totpStep = systemUtilService.getBusinessValue("dlsmk.qrcode.totp.step")  //fixme: jar包写死了5s
         val totpOffset = systemUtilService.getBusinessValue("dlsmk.qrcode.totp.offset")
         val debugEnable = systemUtilService.getBusinessValue("dlsmk.qrcode.log.debug")
+        val newQrcode = systemUtilService.getBusinessValue("dlsmk.qrcode.offline.enable")
         if (NumberUtils.isDigits(totpStep)) step = totpStep.trim()
         if (NumberUtils.isDigits(totpOffset)) offset = totpOffset.trim()
         if ("true".equals(debugEnable, true)) debug = "true"
-        return mapOf("rootkey" to rootkey, "iv" to iv, "prefix" to prefix, "step" to step, "offset" to offset, "debug" to debug)
+        if ("true".equals(newQrcode, true)) offlineQrcode = "true"
+        return mapOf("rootkey" to rootkey, "iv" to iv, "prefix" to prefix, "step" to step, "offset" to offset, "debug" to debug, "offline" to offlineQrcode)
     }
 
     override fun encodeCode(uid: String): ApiResponse {
@@ -58,6 +64,7 @@
         val iv = config["iv"]
         val prefix = config["prefix"] ?: ""
         val debug = "true" == config["debug"]
+        val offlineQRCode = "true" == config["offline"]
 
         if (rootkey.isNullOrEmpty() || iv.isNullOrEmpty()) {
             resp.retcode = 1
@@ -65,12 +72,12 @@
             return resp
         }
         val muser = mobileApiService.findUserById(uid)
-        if(muser==null||TradeDict.STATUS_NORMAL!=muser.status){
+        if (muser == null || TradeDict.STATUS_NORMAL != muser.status) {
             resp.retcode = 1
             resp.retmsg = "用户不存在或状态异常"
             return resp
         }
-        if(muser.userid.isNullOrEmpty()){
+        if (muser.userid.isNullOrEmpty()) {
             resp.retcode = 1
             resp.retmsg = "用户未绑定身份"
             return resp
@@ -84,7 +91,18 @@
             resp.retcode = 1
             resp.retmsg = "银行卡未签约"
             return resp
+        } else if (TradeDict.STATUS_NORMAL != bankCard.transStatus) {
+            resp.retcode = 1
+            resp.retmsg = when (bankCard.transStatus) {
+                TradeDict.STATUS_UNUSE -> "银行卡未启用!"
+                TradeDict.STATUS_LOST -> "银行卡已挂失!"
+                TradeDict.STATUS_LOCKED -> "银行卡已锁定!"
+                TradeDict.STATUS_FROZEN -> "银行卡已冻结!"
+                else -> "银行卡状态异常!"
+            }
+            return resp
         }
+
         val cityCard = cardService.findCardByUseridAndCardphyid(bankCard.userid, ConstantUtil.CARDTYPE_CITIZENCARD, bankCard.cardphyid)
         if (null == cityCard) {
             resp.retcode = 1
@@ -106,41 +124,47 @@
             return resp
         }
 
-        try {
-            val handle = QrCode.builder()
-                    .rootKey(rootkey)
-                    .iv(iv)
-                    .uid(uid)
-                    .card(cityCard.cardno, cityCard.busCardType ?: "80")
-                    .prefix(prefix)
-                    .debug(debug)
-                    .create()
-            val qrcode = handle.qrcode()
+        if (offlineQRCode) {
+            //脱机码,新版本统一码
+            try {
+                val handle = QrCode.builder()
+                        .rootKey(rootkey)
+                        .iv(iv)
+                        .uid(uid)
+                        .card(cityCard.cardno, cityCard.busCardType ?: "80")
+                        .prefix(prefix)
+                        .debug(debug)
+                        .create()
+                val qrcode = handle.qrcode()
+                resp.retcode = 0
+                resp.retmsg = qrcode
+                return resp
+            } catch (ex: Exception) {
+                ex.printStackTrace()
+                resp.retcode = 1
+                resp.retmsg = "二维码生成失败!${ex.message}"
+                return resp
+            }
+
+        } else {
+            //老版本H5码,存redis
+            val totp = QrCodeTotpUtil.generateTOTP(muser.secertkey)
+            val rowdata = QrcodeRawData()
+            rowdata.userid = muser.userid
+            rowdata.setTotp(totp)
+            val orgData = Gson().toJson(rowdata)
+            val publicKey = RSAKeysGenerate.getPublicKey(muser.rsapublic)
+            val encdata = org.apache.commons.codec.binary.Base64.encodeBase64String(RSAKeysGenerate.encryptbyte(publicKey, orgData))
+            val qrcode = AesUtil.encryptCFB("$uid:$encdata", rootkey, iv, "AES/CFB/NoPadding")
+
+            val key = MD5.encodeByMD5ToURLSafeBase64(qrcode)
+            redisTemplate.opsForValue().set(key, qrcode, Duration.ofSeconds(20))
+
             resp.retcode = 0
-            resp.retmsg = qrcode
-            return resp
-        } catch (ex: Exception) {
-            ex.printStackTrace()
-            resp.retcode = 1
-            resp.retmsg = "二维码生成失败!${ex.message}"
+            resp.retmsg = key
             return resp
         }
 
-//        val totp = QrCodeTotpUtil.generateTOTP(muser.secertkey)
-//        val rowdata = QrcodeRawData()
-//        rowdata.userid = muser.userid
-//        rowdata.setTotp(totp)
-//        val orgData = Gson().toJson(rowdata)
-//        val publicKey = RSAKeysGenerate.getPublicKey(muser.rsapublic)
-//        val encdata = org.apache.commons.codec.binary.Base64.encodeBase64String(RSAKeysGenerate.encryptbyte(publicKey, orgData))
-//        val qrcode = AesUtil.encryptCFB("$uid:$encdata", rootkey, iv, "AES/CFB/NoPadding")
-//
-//        val key = MD5.encodeByMD5ToURLSafeBase64(qrcode)
-//        redisTemplate.opsForValue().set(key,qrcode, Duration.ofSeconds(20))
-
-//        resp.retcode = 0
-//        resp.retmsg = key
-//        return resp
     }
 
     override fun encodeCode(param: QrcodeParam): ApiResponse {
@@ -151,6 +175,7 @@
         val iv = config["iv"]
         val prefix = config["prefix"] ?: ""
         val debug = "true" == config["debug"]
+        val offlineQRCode = "true" == config["offline"]
 
         if (rootkey.isNullOrEmpty() || iv.isNullOrEmpty()) {
             resp.retcode = 1
@@ -177,7 +202,18 @@
             resp.retcode = 1
             resp.retmsg = "银行卡未签约"
             return resp
+        } else if (TradeDict.STATUS_NORMAL != bankCard.transStatus) {
+            resp.retcode = 1
+            resp.retmsg = when (bankCard.transStatus) {
+                TradeDict.STATUS_UNUSE -> "银行卡未启用!"
+                TradeDict.STATUS_LOST -> "银行卡已挂失!"
+                TradeDict.STATUS_LOCKED -> "银行卡已锁定!"
+                TradeDict.STATUS_FROZEN -> "银行卡已冻结!"
+                else -> "银行卡状态异常!"
+            }
+            return resp
         }
+
         val cityCard = cardService.findCardByUseridAndCardphyid(bankCard.userid, ConstantUtil.CARDTYPE_CITIZENCARD, bankCard.cardphyid)
         if (null == cityCard) {
             resp.retcode = 1
@@ -199,40 +235,47 @@
             return resp
         }
 
-        try {
-            val handle = QrCode.builder()
-                    .rootKey(rootkey)
-                    .iv(iv)
-                    .uid(uid)
-                    .card(cityCard.cardno, cityCard.busCardType ?: "80")
-                    .prefix(prefix)
-                    .debug(debug)
-                    .create()
-            val qrcode = handle.qrcode()
+        if (offlineQRCode) {
+            //脱机码,新版本统一码
+            try {
+                val handle = QrCode.builder()
+                        .rootKey(rootkey)
+                        .iv(iv)
+                        .uid(uid)
+                        .card(cityCard.cardno, cityCard.busCardType ?: "80")
+                        .prefix(prefix)
+                        .debug(debug)
+                        .create()
+                val qrcode = handle.qrcode()
+                resp.retcode = 0
+                resp.retmsg = qrcode
+                return resp
+            } catch (ex: Exception) {
+                ex.printStackTrace()
+                resp.retcode = 1
+                resp.retmsg = "二维码生成失败!${ex.message}"
+                return resp
+            }
+
+        } else {
+            //老版本H5码,存redis
+            val totp = QrCodeTotpUtil.generateTOTP(userSec.secertkey)
+            val rowdata = QrcodeRawData()
+            rowdata.userid = userSec.userid
+            rowdata.setTotp(totp)
+            val orgData = Gson().toJson(rowdata)
+            val publicKey = RSAKeysGenerate.getPublicKey(userSec.rsapublic)
+            val encdata = org.apache.commons.codec.binary.Base64.encodeBase64String(RSAKeysGenerate.encryptbyte(publicKey, orgData))
+            val qrcode = AesUtil.encryptCFB("$uid:$encdata", rootkey, iv, "AES/CFB/NoPadding")
+
+            val key = MD5.encodeByMD5ToURLSafeBase64(qrcode)
+            redisTemplate.opsForValue().set(key, qrcode, Duration.ofSeconds(20))
+
             resp.retcode = 0
-            resp.retmsg = qrcode
-            return resp
-        } catch (ex: Exception) {
-            ex.printStackTrace()
-            resp.retcode = 1
-            resp.retmsg = "二维码生成失败!${ex.message}"
+            resp.retmsg = key
             return resp
         }
 
-//        val totp = QrCodeTotpUtil.generateTOTP(param.secertkey)
-//        val rowdata = QrcodeRawData()
-//        rowdata.userid = param.userid
-//        rowdata.setTotp(totp)
-//        val orgData = Gson().toJson(rowdata)
-//        val publicKey = RSAKeysGenerate.getPublicKey(param.rsapublic)
-//        val encdata = org.apache.commons.codec.binary.Base64.encodeBase64String(RSAKeysGenerate.encryptbyte(publicKey, orgData))
-//        val qrcode = AesUtil.encryptCFB("$uid:$encdata", rootkey, iv, "AES/CFB/NoPadding")
-//
-//        val key = MD5.encodeByMD5ToURLSafeBase64(qrcode)
-//        redisTemplate.opsForValue().set(key,qrcode, Duration.ofSeconds(20))
-//        resp.retcode = 0
-//        resp.retmsg = key
-//        return resp
     }
 
     override fun decodeCode(qrcode: String): DoorQrcodeResponse {
@@ -242,11 +285,20 @@
         val iv = config["iv"]
         val prefix = config["prefix"] ?: ""
         val debug = "true" == config["debug"]
-        val totpoffset = config["offset"]
+        val offlineQRCode = "true" == config["offline"]
         var offset = 3
-        if (NumberUtils.isDigits(totpoffset)) {
-            offset = totpoffset!!.toInt()
+        if (offlineQRCode) {
+            val totpoffset = config["offset"]
+            if (NumberUtils.isDigits(totpoffset)) {
+                offset = totpoffset!!.toInt()
+            }
+        } else {
+            val totpoffset = systemUtilService.getBusinessValue("aes.cfb.totp.offset")
+            if (NumberUtils.isDigits(totpoffset)) {
+                offset = Integer.valueOf(totpoffset).toInt()
+            }
         }
+
         if (rootkey.isNullOrEmpty() || iv.isNullOrEmpty()) {
             resp.retcode = 1
             resp.retmsg = "秘钥未配置"
@@ -255,56 +307,144 @@
         println("vtoken=[" + qrcode + "],length=[" + qrcode.length + "]")
 
         //解密
-        var data = HashMap<String, String>(0)
-        try {
-            val handle = QrCode.builder().rootKey(rootkey).iv(iv).prefix(prefix).debug(debug).create()
-            //fixme:解码已校验totp
-            data = handle.decodeWithTotpCheck(qrcode, offset) as HashMap<String, String>
-        } catch (ex: Exception) {
-            ex.printStackTrace()
-            resp.retcode = 1
-            resp.retmsg = "二维码解析报错,${ex.message}"
+        if(offlineQRCode){
+            //脱机码,新版本统一码
+            var data = HashMap<String, String>(0)
+            try {
+                val handle = QrCode.builder().rootKey(rootkey).iv(iv).prefix(prefix).debug(debug).create()
+                //fixme:解码已校验totp
+                data = handle.decodeWithTotpCheck(qrcode, offset) as HashMap<String, String>
+            } catch (ex: Exception) {
+                ex.printStackTrace()
+                resp.retcode = 1
+                resp.retmsg = "二维码解析报错,${ex.message}"
+                return resp
+            }
+            val uid = data[QrCode.FIELD_UID]
+            val cardno = data[QrCode.FIELD_CARDNO]
+            val cardtype = data[QrCode.FIELD_CARDTYPE] //公交卡类型
+            if (uid.isNullOrEmpty() || cardno.isNullOrEmpty() || cardtype.isNullOrEmpty()) {
+                resp.retcode = 1
+                resp.retmsg = "二维码解析后数据异常!"
+                return resp
+            }
+            val userSec = mobileApiService.findUserSecretByUid(uid)
+            val cityCard = cardService.findCardByCardnoAndCardtype(cardno, ConstantUtil.CARDTYPE_CITIZENCARD)
+            if(null==userSec || null == cityCard || cityCard.userid != userSec.userid){
+                resp.retcode = 1
+                resp.retmsg = "二维码用户信息错误,请刷新二维码后重试"
+                return resp
+            }
+            val person = userService.findPersonByUserid(cityCard.userid)
+            val bankcard = mobileApiService.findCardByUseridAndCardphyid(cityCard.userid, ConstantUtil.CARDTYPE_BANKCARD, cityCard.cardphyid)
+            if (null == person || null == bankcard) {
+                resp.retcode = 8
+                resp.retmsg = "用户市民卡信息异常"
+                return resp
+            }
+
+            resp.retcode = 0
+            resp.retmsg = "OK"
+            resp.userid = person.userid
+            resp.username = person.name
+            resp.sex = person.sex
+            resp.idtype = person.idtype
+            resp.idno = person.idno
+            resp.phone = person.mobile
+
+            resp.citycardno = cityCard.cardno
+            resp.cardphyid = cityCard.cardphyid
+            resp.expiredate = cityCard.expiredate
+            resp.cardstatus = cityCard.status
+            resp.transstatus = cityCard.transStatus
+            resp.bankcardno = bankcard.cardno
             return resp
-        }
-        val uid = data[QrCode.FIELD_UID]
-        val cardno = data[QrCode.FIELD_CARDNO]
-        val cardtype = data[QrCode.FIELD_CARDTYPE] //公交卡类型
-        if (uid.isNullOrEmpty() || cardno.isNullOrEmpty() || cardtype.isNullOrEmpty()) {
-            resp.retcode = 1
-            resp.retmsg = "二维码解析后数据异常!"
-            return resp
-        }
-        val userSec = mobileApiService.findUserSecretByUid(uid)
-        val cityCard = cardService.findCardByCardnoAndCardtype(cardno, ConstantUtil.CARDTYPE_CITIZENCARD)
-        if(null==userSec || null == cityCard || cityCard.userid != userSec.userid){
-            resp.retcode = 1
-            resp.retmsg = "二维码用户信息错误,请刷新二维码后重试"
-            return resp
-        }
-        val person = userService.findPersonByUserid(cityCard.userid)
-        val bankcard = mobileApiService.findCardByUseridAndCardphyid(cityCard.userid, ConstantUtil.CARDTYPE_BANKCARD, cityCard.cardphyid)
-        if (null == person || null == bankcard) {
-            resp.retcode = 8
-            resp.retmsg = "用户市民卡信息异常"
-            return resp
+
+        }else{
+            //老版本H5码,存redis
+            try {
+                val token = redisTemplate.opsForValue().get(qrcode) //fixme: 密文在redis中
+                if (token.isNullOrEmpty()) {
+                    resp.retcode = 1
+                    resp.retmsg = "二维码不存在或已失效"
+                    return resp
+                }
+
+                val encdataBack = AesUtil.decryptCFB(token, rootkey, iv, "AES/CFB/NoPadding")
+                if (encdataBack == null) {
+                    resp.retcode = 1
+                    resp.retmsg = "二维码解析失败"
+                    return resp
+                }
+                val uid = encdataBack.substring(0, encdataBack.indexOf(":"))
+//                val muser = mobileApiService.findUserById(uid)
+                val muser = mobileApiService.findUserSecretByUid(uid) //查找userSecret
+                if (muser == null) {
+                    resp.retcode = 1
+                    resp.retmsg = "用户不存在或状态异常"
+                    return resp
+                }
+                val seed32 = muser.secertkey
+                val rawEncdata = encdataBack.substring(encdataBack.indexOf(":") + 1)
+
+                val privateKey = RSAKeysGenerate.getPrivateKey(muser.rsaprivate)
+                val rawdataBack = RSAKeysGenerate.decryptbyte(privateKey, rawEncdata)
+                logger.info("rawdata_back=$rawdataBack")
+                val rawData = Gson().fromJson(rawdataBack, QrcodeRawData::class.java)
+                logger.info("userid=" + rawData.userid)
+                if (null == rawData || rawData.userid.isNullOrEmpty()) {
+                    resp.retcode = 1
+                    resp.retmsg = "用户不存在"
+                    return resp
+                }
+
+                val person = userService.findOnePersonByUserid(rawData.userid)
+                val bankcard = mobileApiService.findCardByUserid(person.userid)
+                if (null == bankcard) {
+                    resp.retcode = 9
+                    resp.retmsg = "二维码识别错误,手机绑定的银行卡已注销"
+                    return resp
+                }
+
+                //银行卡必定存在对应的市民卡
+                val citycard = mobileApiService.findCardByUseridAndCardphyid(person.userid, ConstantUtil.CARDTYPE_CITIZENCARD, bankcard.cardphyid)
+                if (null == citycard) {
+                    resp.retcode = 8
+                    resp.retmsg = "二维码识别异常"
+                    return resp
+                }
+
+                val verifyBarcode = QrCodeTotpUtil.verifyCode(rawData.totp, seed32, offset)
+                return if (verifyBarcode) {
+                    resp.retcode = 0
+                    resp.retmsg = "OK"
+                    resp.userid = person.userid
+                    resp.username = person.name
+                    resp.sex = person.sex
+                    resp.idtype = person.idtype
+                    resp.idno = person.idno
+                    resp.phone = person.mobile
+
+                    resp.citycardno = citycard.cardno
+                    resp.cardphyid = citycard.cardphyid
+                    resp.expiredate = citycard.expiredate
+                    resp.cardstatus = citycard.status
+                    resp.transstatus = citycard.transStatus
+                    resp.bankcardno = bankcard.cardno
+                    resp
+                } else {
+                    resp.retcode = 1
+                    resp.retmsg = "二维码校验失败"
+                    resp
+                }
+            } catch (e: Exception) {
+                e.printStackTrace()
+                resp.retcode = 1
+                resp.retmsg = "二维码识别异常"
+                return resp
+            }
         }
 
-        resp.retcode = 0
-        resp.retmsg = "OK"
-        resp.userid = person.userid
-        resp.username = person.name
-        resp.sex = person.sex
-        resp.idtype = person.idtype
-        resp.idno = person.idno
-        resp.phone = person.mobile
-
-        resp.citycardno = cityCard.cardno
-        resp.cardphyid = cityCard.cardphyid
-        resp.expiredate = cityCard.expiredate
-        resp.cardstatus = cityCard.status
-        resp.transstatus = cityCard.transStatus
-        resp.bankcardno = bankcard.cardno
-        return resp
     }
 
     override fun decodeCode(qrcode: String, chkTotp: Boolean, cardno: String?, tac: String?, amount: Int?, transdate: String?, transtime: String?): DoorQrcodeResponse {
@@ -314,6 +454,7 @@
         val iv = config["iv"]
         val prefix = config["prefix"] ?: ""
         val debug = "true" == config["debug"]
+        val offlineQRCode = "true" == config["offline"]
         val totpoffset = config["offset"]
         var offset = 3
         if (NumberUtils.isDigits(totpoffset)) {
@@ -324,6 +465,9 @@
             resp.retmsg = "秘钥未配置"
             return resp
         }
+        if (!offlineQRCode) {
+            return decodeCode(qrcode) // fixme: 未启用脱机码,这里直接老版解码
+        }
         println("citizencard QRcode=[" + qrcode + "],length=[" + qrcode.length + "]")
 
         //解密