将密保答案加密后保存
diff --git a/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBMedicalDtl.java b/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBMedicalDtl.java
index e8f32b8..bcff4b8 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBMedicalDtl.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBMedicalDtl.java
@@ -4,11 +4,11 @@
 
 @Entity
 @Table(name = "th_medicaldtl", indexes = {
-    @Index(name = "medicaldtl_idx", columnList = "uid,emergency,feeno,organizationid"),
+    @Index(name = "medicaldtl_idx", columnList = "emergency,feeno,organizationid,uid"),
     @Index(name = "medicaldtl_idx2", columnList = "uid,accdate,organizationid"),
     @Index(name = "medicaldtl_idx3", columnList = "medicaldate,paystatus,resultid,organizationid"),
     @Index(name = "medicaldtl_idx4", columnList = "patientid,organizationid"),
-    @Index(name = "medicaldtl_idx5", columnList = "hisrefundno,organizationid",unique = true)})
+    @Index(name = "medicaldtl_idx5", columnList = "hisrefundno,organizationid", unique = true)})
 public class TBMedicalDtl {
   @Id
   @Column(name = "billno", length = 32)
@@ -35,7 +35,7 @@
   /**
    * 归并费用名称
    */
-  @Column(name = "mergingname",length = 100)
+  @Column(name = "mergingname", length = 100)
   private String mergingname;
   /**
    * 加收费用描述
diff --git a/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBUserSecurity.java b/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBUserSecurity.java
index 70a9a83..e15f53f 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBUserSecurity.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBUserSecurity.java
@@ -20,7 +20,7 @@
   @Column(name = "uid", nullable = false, length = 32)
   private String uid;
 
-  @Column(name = "answer",length = 200)
+  @Column(name = "answer",length = 64)
   private String answer;
 
   public String getId() {
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/MedicalDtlDao.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/MedicalDtlDao.kt
index 1709636..372b321 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/MedicalDtlDao.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/MedicalDtlDao.kt
@@ -19,8 +19,14 @@
 
     fun findByMedicaldateBetweenAndPaystatusAndResultidIsNullAndTradeflag(beginTime: String, endTime: String, status: String, tradeflag: String, pageable: Pageable): List<TBMedicalDtl>?
 
-    fun findByUidAndEmergencyAndFeenoAndOrganizationidAndPaystatusNot(
-            uid: String, emergency: String, feeNo: String, organizationId: String, payStatus: String): TBMedicalDtl?
+    fun findByEmergencyAndFeenoAndOrganizationidAndUidAndPaystatusNot(
+            emergency: String, feeNo: String, organizationId: String, uid: String, payStatus: String): TBMedicalDtl?
+
+    fun findByEmergencyAndFeenoAndOrganizationidAndUidNotAndPaystatusIn(
+            emergency: String, feeNo: String, organizationId: String, uid: String, payStatus: List<String>): List<TBMedicalDtl>?
+
+    fun findByEmergencyAndFeenoAndOrganizationidAndPaystatus(
+            emergency: String, feeNo: String, organizationId: String, payStatus: String): TBMedicalDtl?
 
     fun findByUidAndOrganizationidAndPaystatusOrderByTransdateDescTranstimeDesc(uid: String, organizationId: String, payStatus: String, pageable: Pageable): List<TBMedicalDtl>?
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/MedicalService.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/MedicalService.kt
index 464dddd..d8197ab 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/MedicalService.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/MedicalService.kt
@@ -80,5 +80,5 @@
     fun refundMedicalDtlConfirm(originBillNo: String, refundMedicalDtl: TBMedicalDtl): TBMedicalDtl
 
     @Transactional
-    fun queryrefundResult(bean: PayRefundRequestBean): TBMedicalDtl
+    fun queryRefundResult(bean: PayRefundRequestBean): TBMedicalDtl
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/impl/MedicalServiceImpl.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/impl/MedicalServiceImpl.kt
index 1d54ab0..f09ba45 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/impl/MedicalServiceImpl.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/service/impl/MedicalServiceImpl.kt
@@ -213,32 +213,42 @@
     }
 
     override fun getUnPayedList(uid: String, organizationId: String, data: String, queryType: Int): List<UnPayedDTO> {
-        // 根据证件号或医疗卡查询未支付账单
+        // 1.根据证件号或医疗卡查询未支付账单
         val unPayedResponse = getUnPayedResponse(organizationId, data, queryType)
         val unPayedDTOList = ArrayList<UnPayedDTO>()
         val hospital = hospitalDao.findByHospitalcodeAndStatus(organizationId, TradeDict.STATUS_NORMAL)
                 ?: throw MedicineException("未找到该医院")
-        // 拆分每次就诊下的每个项目为一条展示数据(一次就诊中可能有几个项目共用一个feeNo,只能一起结算)
+        // 2.拆分每次就诊下的每个项目为一条展示数据(一次就诊中可能有几个项目共用一个feeNo,只能一起结算)
         unPayedResponse.medicalInformation.forEach { medicalInformation ->
             val map = HashMap<String, TBMedicalDtl>()
             for (mergingItems in medicalInformation.mergingItems) {
-                // 因为异步通知HIS,HIS中数据有延迟,所以本地扣费成功后的流水仍有可能被查询到
-                val localDtl = medicalDtlDao.findByUidAndEmergencyAndFeenoAndOrganizationidAndPaystatusNot(
-                        uid, medicalInformation.emergencyNumber!!, mergingItems.feeNo!!,
-                        organizationId, MedicalConstant.DTL_STATUS_FAIL)
+                // 2.1.1因为可通过输入就诊卡号查询医疗账单,同一笔账单可能被多用户支付
+                val payStatus = ArrayList<String>()
+                payStatus.addAll(arrayOf(MedicalConstant.DTL_STATUS_WIP, MedicalConstant.DTL_STATUS_SUCCESS))
+                val payedList = medicalDtlDao.findByEmergencyAndFeenoAndOrganizationidAndUidNotAndPaystatusIn(
+                        medicalInformation.emergencyNumber!!, mergingItems.feeNo!!,
+                        organizationId, uid, payStatus)
+                // 2.1.2如果有其他用户针对同一笔医疗账单的流水,且支付状态为wip或success,则不展示该条医疗账单
+                if (!payedList.isNullOrEmpty()) {
+                    continue
+                }
+
+                // 2.2因为异步通知HIS,HIS中数据有延迟,所以本地扣费成功后的流水仍有可能被查询到
+                val localDtl = medicalDtlDao.findByEmergencyAndFeenoAndOrganizationidAndUidAndPaystatusNot(
+                        medicalInformation.emergencyNumber!!, mergingItems.feeNo!!,
+                        organizationId, uid, MedicalConstant.DTL_STATUS_FAIL)
                 var skip = false
                 if (localDtl != null) {
                     if (localDtl.paystatus == MedicalConstant.DTL_STATUS_SUCCESS) {
-                        //  已扣费成功,等待通知HIS成功,此处无须处理
+                        // 已扣费成功,等待通知HIS成功,此处无须处理
                         continue
-                    } else {
-                        //  当流水为init或wip时都跳过生成本地流水
-                        skip = true
                     }
+                    // 当流水为init或wip时都跳过生成本地流水
+                    skip = true
                 }
-                // 查看该feeNo的是否已出现过
+                // 2.3查看该feeNo的是否已出现过
                 if (!map.containsKey(mergingItems.feeNo!!)) {
-                    // 该feeNo首次出现
+                    // 2.3.1该feeNo首次出现
                     if (!skip) {
                         val medicalDtl = TBMedicalDtl().apply {
                             this.billno = systemUtilService.refno
@@ -271,9 +281,9 @@
                         map[mergingItems.feeNo!!] = localDtl
                     }
                 } else {
-                    // 该feeNo已出现,将该条数据同之前的数据合并成一条流水
+                    // 2.3.2该feeNo已出现,将该条数据同之前的数据合并成一条流水
                     val medicalDtl = map[mergingItems.feeNo!!]!!
-                    // 如果本地流水已生成,此处无需处理
+                    // 2.3.3如果本地流水已生成,此处无需处理
                     if (!medicalDtl.created) {
                         // 将金额累加
                         medicalDtl.mergingsubtotal = MoneyUtil.add(medicalDtl.mergingsubtotal, mergingItems.mergingSubtotal!!)
@@ -282,7 +292,7 @@
                 }
             }
             map.forEach { (_, medicalDtl) ->
-                // 本地流水未创建,生成本地流水
+                // 本地流水未创建,保存本地流水
                 if (!medicalDtl.created) {
                     medicalDtlDao.save(medicalDtl)
                 }
@@ -368,6 +378,14 @@
     override fun medicalPayPreInit(uid: String, bean: PaymentRequestBean) {
         val medicalDtl = medicalDtlDao.findByBillnoForUpdate(bean.billno)
                 ?: throw MedicineException("未找到流水为[${bean.billno}]的流水")
+        // 手动输入就诊卡号查询流水,可能存在多个用户支付同一笔医疗账单的情况,查询该账单是否已被支付
+        val payStatus = ArrayList<String>()
+        payStatus.addAll(arrayOf(MedicalConstant.DTL_STATUS_WIP, MedicalConstant.DTL_STATUS_SUCCESS))
+        val payedList = medicalDtlDao.findByEmergencyAndFeenoAndOrganizationidAndUidNotAndPaystatusIn(
+                medicalDtl.emergency, medicalDtl.feeno, medicalDtl.organizationid, uid, payStatus)
+        if (!payedList.isNullOrEmpty()) {
+            throw MedicineException("该流水已被支付,请刷新后再试")
+        }
         if (medicalDtl.paystatus != null && medicalDtl.paystatus != MedicalConstant.DTL_STATUS_INIT) {
             throw MedicineException("该流水状态异常,请查询后再试")
         }
@@ -411,7 +429,7 @@
                 this.hasJson = true
             }
         }
-        
+
         val preFeeResponse = medicalClient.getPreCalculatedFee(preFeeRequest)
         if (preFeeResponse.payAmount != medicalDtl.mergingsubtotal || preFeeResponse.totalFee != medicalDtl.mergingsubtotal) {
             throw MedicineException("该流水缴费金额异常")
@@ -872,7 +890,7 @@
         }
     }
 
-    override fun queryrefundResult(bean: PayRefundRequestBean): TBMedicalDtl {
+    override fun queryRefundResult(bean: PayRefundRequestBean): TBMedicalDtl {
         val medicalDtl = medicalDtlDao.findByBillnoForUpdate(bean.outOrderNumber)
                 ?: throw MedicineException("未找到订单号为:${bean.outOrderNumber}的订单,请检查订单号是否正确")
         if (!medicalDtl.requestbillno.isNullOrEmpty()) {
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
index 743bb7b..982a6a2 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
@@ -442,7 +442,7 @@
             if (refundToken != bean.token) {
                 return JsonResult.error("token检验失败,请检查是否正确")
             }
-            val medicalDtl = medicalService.queryrefundResult(bean)
+            val medicalDtl = medicalService.queryRefundResult(bean)
             val map = HashMap<String,String>()
             val jsonResult = JsonResult.ok()
             if (medicalDtl.paystatus == MedicalConstant.DTL_STATUS_SUCCESS) {
@@ -479,9 +479,6 @@
                         ?: return JsonResult.error("用户不存在")
             }
             val data = secretSecurityService.getSecretSecurityList(user.uid)
-            if (data.isNullOrEmpty()) {
-                return JsonResult.error("用户未设置密保问题")
-            }
             JsonResult.ok().put("data", data)
         } catch (e: Exception) {
             if (e is PortalBusinessException) {
@@ -1699,7 +1696,7 @@
     }
 
     /**
-     * 获取密保列表
+     * 获取所有密保问题
      */
     @RequestMapping(value = ["/security/all"], method = [RequestMethod.GET])
     fun getAllSecretSecurity(): JsonResult? {
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/SecretSecurityServiceImpl.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/SecretSecurityServiceImpl.kt
index 83940d1..9c762df 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/SecretSecurityServiceImpl.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/SecretSecurityServiceImpl.kt
@@ -7,6 +7,7 @@
 import com.supwisdom.dlpay.portal.domain.TBSecretSecurity
 import com.supwisdom.dlpay.portal.service.SecretSecurityService
 import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
 import org.springframework.stereotype.Service
 
 @Service
@@ -33,8 +34,10 @@
         // 先删除用户之前所有的问题再保存
         val list = userSecurityDao.findByUid(bean.uid)
         if (!list.isNullOrEmpty()) {
-            userSecurityDao.deleteAll(list)
+            throw PortalBusinessException("您已设置密保,请先删除后再保存")
         }
+        // 对密保答案加密处理
+        val encoder = BCryptPasswordEncoder()
         answers.forEach {
             if (it.answer.isNullOrEmpty()) {
                 throw PortalBusinessException("密保答案不能为空")
@@ -43,19 +46,21 @@
                 throw PortalBusinessException("密保答案长度必须为2-20个字符")
             }
             it.uid = bean.uid
+            it.answer = encoder.encode(it.answer)
             userSecurityDao.save(it)
         }
     }
 
-    override fun checkUserSecurity(bean: UserSecurityRequestBean):String? {
+    override fun checkUserSecurity(bean: UserSecurityRequestBean): String? {
         val answers = bean.answers
         if (answers.isNullOrEmpty() || answers.size < 3) {
             throw PortalBusinessException("请至少回答三个密保问题")
         }
+        val encoder = BCryptPasswordEncoder()
         answers.forEach {
             val userSecurity = userSecurityDao.findByUidAndSsid(bean.uid, it.ssid)
                     ?: throw PortalBusinessException("未找到密保问题")
-            if (it.answer != userSecurity.answer) {
+            if (!encoder.matches(it.answer, userSecurity.answer)) {
                 return it.ssid
             }
         }