增加医疗报告及手机app绑定邮箱功能
diff --git a/backend/src/main/java/com/supwisdom/dlpay/api/bean/JsonResult.java b/backend/src/main/java/com/supwisdom/dlpay/api/bean/JsonResult.java
index dafde31..68d6eae 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/api/bean/JsonResult.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/api/bean/JsonResult.java
@@ -40,8 +40,8 @@
     /**
      * 返回失败
      */
-    public static JsonResult error(String messag) {
-        return error(500, messag);
+    public static JsonResult error(String message) {
+        return error(500, message);
     }
 
     /**
diff --git a/backend/src/main/java/com/supwisdom/dlpay/medical/dao/impl/ExamReportRepositoryImpl.java b/backend/src/main/java/com/supwisdom/dlpay/medical/dao/impl/ExamReportRepositoryImpl.java
new file mode 100644
index 0000000..49a47a7
--- /dev/null
+++ b/backend/src/main/java/com/supwisdom/dlpay/medical/dao/impl/ExamReportRepositoryImpl.java
@@ -0,0 +1,43 @@
+package com.supwisdom.dlpay.medical.dao.impl;
+
+import com.supwisdom.dlpay.framework.jpa.BaseRepository;
+import com.supwisdom.dlpay.framework.jpa.Finder;
+import com.supwisdom.dlpay.framework.jpa.page.Pagination;
+import com.supwisdom.dlpay.medical.dao.ExamReportRepository;
+import com.supwisdom.dlpay.medical.domain.TBExamReport;
+import org.hibernate.query.internal.NativeQueryImpl;
+import org.hibernate.transform.Transformers;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.persistence.Query;
+import java.util.List;
+
+public class ExamReportRepositoryImpl extends BaseRepository implements ExamReportRepository {
+  @NotNull
+  @Override
+  @SuppressWarnings("all")
+  public Pagination findByHZSFZAndYLJGDM(@NotNull String hzsfz, @NotNull String yljgdm, int pageno, int pagesize) {
+    Finder f = Finder.create("select \"JCLSH\",\"PATIENTID\",\"HZSFZ\",\"JZLSH\",\"HZXM\",\"BCH\",\"BQMC\",\"BBCYRQ\",\"JCXMMC\",\"JCDLJG\",\"JCDLJGJLDW\",\"XB\",\"BGYSXM\",\"JCRQ\",\"BGSJ\",\"SQKSMC\",\"BCKSMC\",\"JCBW\",\"YYX\",\"BCKGSJ\",\"JCZDHTS\",\"SHYSXM\",\"YLJGDM\",\"YLJGMC\",\"JCBGDH\",\"BGBZ\" from \"PT_EXAMREPORT\" where \"HZSFZ\" =:hzsfz and \"YLJGDM\" =:yljgdm order by \"BGSJ\" desc");
+    f.setParameter("hzsfz", hzsfz);
+    f.setParameter("yljgdm", yljgdm);
+    return findNative(f, Transformers.aliasToBean(TBExamReport.class), pageno, pagesize);
+  }
+
+  @Nullable
+  @Override
+  @SuppressWarnings("all")
+  public TBExamReport findByHZSFZAndJCBGDHAndYLJGDM(@NotNull String hzsfz, @NotNull String jcbgdh, @NotNull String yljgdm) {
+    String sql = "select \"JCLSH\",\"PATIENTID\",\"HZSFZ\",\"JZLSH\",\"HZXM\",\"BCH\",\"BQMC\",\"BBCYRQ\",\"JCXMMC\",\"JCDLJG\",\"JCDLJGJLDW\",\"XB\",\"BGYSXM\",\"JCRQ\",\"BGSJ\",\"SQKSMC\",\"BCKSMC\",\"JCBW\",\"YYX\",\"BCKGSJ\",\"JCZDHTS\",\"SHYSXM\",\"YLJGDM\",\"YLJGMC\",\"JCBGDH\",\"BGBZ\" from \"PT_EXAMREPORT\" where \"HZSFZ\" =:hzsfz and \"JCBGDH\"=:jcbgdh and \"YLJGDM\" =:yljgdm";
+    Query query = createNavQuery(sql);
+    query.setParameter("hzsfz", hzsfz);
+    query.setParameter("jcbgdh", jcbgdh);
+    query.setParameter("yljgdm", yljgdm);
+    query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.aliasToBean(TBExamReport.class));
+    List<TBExamReport> list = query.getResultList();
+    if (list != null && list.size() != 0) {
+      return list.get(0);
+    }
+    return null;
+  }
+}
diff --git a/backend/src/main/java/com/supwisdom/dlpay/medical/dao/impl/LabDetailRepositoryImpl.java b/backend/src/main/java/com/supwisdom/dlpay/medical/dao/impl/LabDetailRepositoryImpl.java
new file mode 100644
index 0000000..aa2492b
--- /dev/null
+++ b/backend/src/main/java/com/supwisdom/dlpay/medical/dao/impl/LabDetailRepositoryImpl.java
@@ -0,0 +1,27 @@
+package com.supwisdom.dlpay.medical.dao.impl;
+
+import com.supwisdom.dlpay.framework.jpa.BaseRepository;
+import com.supwisdom.dlpay.medical.dao.LabDetailRepository;
+import com.supwisdom.dlpay.medical.domain.TBLabDetail;
+import com.supwisdom.dlpay.medical.domain.TBLabReport;
+import org.hibernate.query.internal.NativeQueryImpl;
+import org.hibernate.transform.Transformers;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.persistence.Query;
+import java.util.List;
+
+public class LabDetailRepositoryImpl extends BaseRepository implements LabDetailRepository {
+  @Nullable
+  @Override
+  @SuppressWarnings("all")
+  public List<TBLabDetail> findByJYBGDHAndYLJGDM(@NotNull String jybgdh, @NotNull String yljgdm) {
+    String sql = "select \"JYMXLSH\",\"JZLSH\",\"JYBGDH\",\"JYJG\",\"JYDLJG\",\"JLDW\",\"CKDX\",\"JYXMMC\",\"JYXMYWMC\",\"XSSX\",\"YLJGDM\" from \"PT_LABDETAIL\" where \"JYBGDH\" =:jybgdh and \"YLJGDM\" =:yljgdm";
+    Query query = createNavQuery(sql);
+    query.setParameter("jybgdh", jybgdh);
+    query.setParameter("yljgdm", yljgdm);
+    query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.aliasToBean(TBLabDetail.class));
+    return query.getResultList();
+  }
+}
diff --git a/backend/src/main/java/com/supwisdom/dlpay/medical/dao/impl/LabReportRepositoryImpl.java b/backend/src/main/java/com/supwisdom/dlpay/medical/dao/impl/LabReportRepositoryImpl.java
new file mode 100644
index 0000000..a0f31d6
--- /dev/null
+++ b/backend/src/main/java/com/supwisdom/dlpay/medical/dao/impl/LabReportRepositoryImpl.java
@@ -0,0 +1,44 @@
+package com.supwisdom.dlpay.medical.dao.impl;
+
+import com.supwisdom.dlpay.framework.jpa.BaseRepository;
+import com.supwisdom.dlpay.framework.jpa.Finder;
+import com.supwisdom.dlpay.framework.jpa.page.Pagination;
+import com.supwisdom.dlpay.medical.dao.LabReportRepository;
+import com.supwisdom.dlpay.medical.domain.TBExamReport;
+import com.supwisdom.dlpay.medical.domain.TBLabReport;
+import org.hibernate.query.internal.NativeQueryImpl;
+import org.hibernate.transform.Transformers;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.persistence.Query;
+import java.util.List;
+
+public class LabReportRepositoryImpl extends BaseRepository implements LabReportRepository {
+  @NotNull
+  @Override
+  @SuppressWarnings("all")
+  public Pagination findByHZSFZAndYLJGDM(@NotNull String hzsfz, @NotNull String yljgdm, int pageno, int pagesize) {
+    Finder f = Finder.create("select \"JYBGDH\",\"PATIENTID\",\"HZSFZ\",\"JZLSH\",\"BRBSLB\",\"BCH\",\"BQMC\",\"HZXM\",\"XB\",\"ZDRQ\",\"BBLBDM\",\"JYBGDMC\",\"JYBGDJG\",\"JYBGJGMC\",\"JYBGJG\",\"BZ\",\"JYBGRQ\",\"SHYSXM\",\"BGYSXM\",\"JYRQ\",\"ZDJGMC\",\"YLJGDM\",\"YLJGMC\" from \"PT_LABREPORT\" where \"HZSFZ\"=:hzsfz and \"YLJGDM\"=:yljgdm order by \"JYBGRQ\" desc");
+    f.setParameter("hzsfz", hzsfz);
+    f.setParameter("yljgdm", yljgdm);
+    return findNative(f, Transformers.aliasToBean(TBLabReport.class), pageno, pagesize);
+  }
+
+  @Nullable
+  @Override
+  @SuppressWarnings("all")
+  public TBLabReport findByHZSFZAndJYBGDHAndYLJGDM(@NotNull String hzsfz, @NotNull String jybgdh, @NotNull String yljgdm) {
+    String sql = "select \"JYBGDH\",\"PATIENTID\",\"HZSFZ\",\"JZLSH\",\"BRBSLB\",\"BCH\",\"BQMC\",\"HZXM\",\"XB\",\"ZDRQ\",\"BBLBDM\",\"JYBGDMC\",\"JYBGDJG\",\"JYBGJGMC\",\"JYBGJG\",\"BZ\",\"JYBGRQ\",\"SHYSXM\",\"BGYSXM\",\"JYRQ\",\"ZDJGMC\",\"YLJGDM\",\"YLJGMC\" from \"PT_LABREPORT\" where \"HZSFZ\"=:hzsfz and \"JYBGDH\"=:jybgdh and \"YLJGDM\"=:yljgdm";
+    Query query = createNavQuery(sql);
+    query.setParameter("hzsfz", hzsfz);
+    query.setParameter("jybgdh", jybgdh);
+    query.setParameter("yljgdm", yljgdm);
+    query.unwrap(NativeQueryImpl.class).setResultTransformer(Transformers.aliasToBean(TBLabReport.class));
+    List<TBLabReport> list = query.getResultList();
+    if (list != null && list.size() != 0) {
+      return list.get(0);
+    }
+    return null;
+  }
+}
diff --git a/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBExamReport.java b/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBExamReport.java
index 40946c6..c54db5a 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBExamReport.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBExamReport.java
@@ -16,7 +16,7 @@
   @Column(name = "`JCLSH`", length = 32)
   private String JCLSH;
   @Column(name = "`PATIENTID`", length = 64)
-  private String patientId;
+  private String PATIENTID;
   /**
    * 患者身份证号码
    */
@@ -138,12 +138,12 @@
   @Column(name = "`BGBZ`", length = 500)
   private String BGBZ;
 
-  public String getPatientId() {
-    return patientId;
+  public String getPATIENTID() {
+    return PATIENTID;
   }
 
-  public void setPatientId(String patientId) {
-    this.patientId = patientId;
+  public void setPATIENTID(String PATIENTID) {
+    this.PATIENTID = PATIENTID;
   }
 
   public String getJCLSH() {
diff --git a/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBLabReport.java b/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBLabReport.java
index e02f247..5db97f6 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBLabReport.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/medical/domain/TBLabReport.java
@@ -16,7 +16,7 @@
   @Column(name = "`JYBGDH`", length = 32)
   private String JYBGDH;
   @Column(name = "`PATIENTID`", length = 64)
-  private String patientId;
+  private String PATIENTID;
   /**
    * 患者身份证
    */
@@ -60,8 +60,8 @@
   /**
    * 标本类别(字典DE04.50.134.00)
    */
-  @Column(name = "`BZLB`", length = 20)
-  private String BZLB;
+  @Column(name = "`BBLBDM`", length = 200)
+  private String BBLBDM;
   /**
    * 检验报告单—项目名称(检查目的)
    */
@@ -123,12 +123,12 @@
   @Column(name = "`YLJGMC`", length = 50)
   private String YLJGMC;
 
-  public String getPatientId() {
-    return patientId;
+  public String getPATIENTID() {
+    return PATIENTID;
   }
 
-  public void setPatientId(String patientId) {
-    this.patientId = patientId;
+  public void setPATIENTID(String PATIENTID) {
+    this.PATIENTID = PATIENTID;
   }
 
   public String getJYBGDH() {
@@ -187,12 +187,12 @@
     this.ZDRQ = ZDRQ;
   }
 
-  public String getBZLB() {
-    return BZLB;
+  public String getBBLBDM() {
+    return BBLBDM;
   }
 
-  public void setBZLB(String BZLB) {
-    this.BZLB = BZLB;
+  public void setBBLBDM(String BBLBDM) {
+    this.BBLBDM = BBLBDM;
   }
 
   public String getJYBGDJG() {
diff --git a/backend/src/main/java/com/supwisdom/dlpay/medical/util/MedicalConstant.java b/backend/src/main/java/com/supwisdom/dlpay/medical/util/MedicalConstant.java
index 1249c1e..4fed5ab 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/medical/util/MedicalConstant.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/medical/util/MedicalConstant.java
@@ -40,7 +40,18 @@
    * 医疗系统响应状态码
    */
   public final static int MEDICAL_RESPONSE_SUCCESS = 200;//成功
-
+  /**
+   * 医疗性别字典
+   */
+  public final static String HIS_GENDER_MALE = "1";
+  public final static String HIS_GENDER_FEMALE = "2";
+  /**
+   * 医疗病人标识类别字典
+   */
+  public final static String HIS_PATIENTTYPE_OUTPATIENT = "1";//门诊
+  public final static String HIS_PATIENTTYPE_HOSPITALIZED = "2";//住院
+  public final static String HIS_PATIENTTYPE_EXAM = "3";//体检
+  public final static String HIS_PATIENTTYPE_OTHER = "9";//其它
 
   /**
    * 本地流水状态
diff --git a/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBSecretSecurity.java b/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBSecretSecurity.java
new file mode 100644
index 0000000..f9b05aa
--- /dev/null
+++ b/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBSecretSecurity.java
@@ -0,0 +1,56 @@
+package com.supwisdom.dlpay.portal.domain;
+
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "tb_secret_security")
+public class TBSecretSecurity {
+  @Id
+  @GenericGenerator(name = "idGenerator", strategy = "uuid")
+  @GeneratedValue(generator = "idGenerator")
+  @Column(name = "ssid", nullable = false, length = 32)
+  private String ssid;
+
+  @Column(name = "question", length = 200)
+  private String question;
+
+  @Column(name = "sort")
+  private Integer sort;
+
+  @Column(name = "createtime", length = 14)
+  private String createtime;
+
+  public String getSsid() {
+    return ssid;
+  }
+
+  public void setSsid(String ssid) {
+    this.ssid = ssid;
+  }
+
+  public String getQuestion() {
+    return question;
+  }
+
+  public void setQuestion(String question) {
+    this.question = question;
+  }
+
+  public Integer getSort() {
+    return sort;
+  }
+
+  public void setSort(Integer sort) {
+    this.sort = sort;
+  }
+
+  public String getCreatetime() {
+    return createtime;
+  }
+
+  public void setCreatetime(String createtime) {
+    this.createtime = createtime;
+  }
+}
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
new file mode 100644
index 0000000..70a9a83
--- /dev/null
+++ b/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBUserSecurity.java
@@ -0,0 +1,57 @@
+package com.supwisdom.dlpay.portal.domain;
+
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.*;
+
+@Entity
+@Table(name = "tb_user_security",indexes = {
+    @Index(name = "user_security_idx",columnList = "uid,ssid",unique = true)})
+public class TBUserSecurity {
+  @Id
+  @GenericGenerator(name = "idGenerator", strategy = "uuid")
+  @GeneratedValue(generator = "idGenerator")
+  @Column(name = "id", nullable = false, length = 32)
+  private String id;
+
+  @Column(name = "ssid", nullable = false, length = 32)
+  private String ssid;
+
+  @Column(name = "uid", nullable = false, length = 32)
+  private String uid;
+
+  @Column(name = "answer",length = 200)
+  private String answer;
+
+  public String getId() {
+    return id;
+  }
+
+  public void setId(String id) {
+    this.id = id;
+  }
+
+  public String getSsid() {
+    return ssid;
+  }
+
+  public void setSsid(String ssid) {
+    this.ssid = ssid;
+  }
+
+  public String getUid() {
+    return uid;
+  }
+
+  public void setUid(String uid) {
+    this.uid = uid;
+  }
+
+  public String getAnswer() {
+    return answer;
+  }
+
+  public void setAnswer(String answer) {
+    this.answer = answer;
+  }
+}
diff --git a/backend/src/main/java/com/supwisdom/dlpay/portal/util/EmailUtil.java b/backend/src/main/java/com/supwisdom/dlpay/portal/util/EmailUtil.java
new file mode 100644
index 0000000..388dd61
--- /dev/null
+++ b/backend/src/main/java/com/supwisdom/dlpay/portal/util/EmailUtil.java
@@ -0,0 +1,131 @@
+package com.supwisdom.dlpay.portal.util;
+
+import com.supwisdom.dlpay.framework.service.SystemUtilService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import javax.mail.*;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import java.util.Properties;
+
+@Component
+public class EmailUtil {
+
+  private String host = "smtp.163.com";
+
+  // 发件人名称
+
+  private String user = "大理市民卡";
+
+  // 发件人地址
+
+  private String from;
+
+  // 163邮箱授权码
+
+  private String pwd;
+
+  @Autowired
+  private SystemUtilService systemUtilService;
+
+  private Logger logger = LoggerFactory.getLogger(EmailUtil.class);
+
+  /**
+   * @param addressee 收件人邮箱地址
+   * @param subject   邮件标题
+   * @param context   邮件内容
+   */
+  public boolean sendEmail(String addressee, String subject, String context) {
+    try {
+      from = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_PORTAL_EMAIL_ADDRESS);
+      pwd = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_PORTAL_EMAIL_PWD);
+      Properties props = new Properties();
+
+      //设置发送邮件的邮件服务器的属性(这里使用网易的smtp服务器)
+
+      props.put("mail.smtp.host", host);
+
+      //需要经过授权,也就是有户名和密码的校验,这样才能通过验证(一定要有这一条)
+
+      props.put("mail.smtp.auth", "true");
+
+      //用props对象构建一个session
+
+      Session session = Session.getDefaultInstance(props);
+
+      //用session为参数定义消息对象
+
+      MimeMessage message = new MimeMessage(session);
+
+
+      // 加载发件人地址
+
+      message.setFrom(new InternetAddress(from, user, "UTF-8"));
+
+      // 加载收件人地址
+      String[] TOS = new String[]{addressee};
+      InternetAddress[] sendTo = new InternetAddress[TOS.length];
+
+      for (int i = 0; i < TOS.length; i++) {
+
+        sendTo[i] = new InternetAddress(TOS[i]);
+
+      }
+
+      message.addRecipients(Message.RecipientType.TO, sendTo);
+
+      // 加载标题
+
+      message.setSubject(subject);
+
+      // 向multipart对象中添加邮件的各个部分内容,包括文本内容和附件
+
+      Multipart multipart = new MimeMultipart();
+
+      // 设置邮件的文本内容
+
+      BodyPart contentPart = new MimeBodyPart();
+
+      contentPart.setContent(context, "text/html; charset=utf-8");
+
+      multipart.addBodyPart(contentPart);
+
+      // 将multipart对象放到message中
+
+      message.setContent(multipart);
+
+      // 保存邮件
+
+      message.saveChanges();
+
+      // 发送邮件
+
+      Transport transport = session.getTransport("smtp");
+
+      // 连接服务器的邮箱
+
+      transport.connect(host, from, pwd);
+
+      // 把邮件发送出去
+
+      transport.sendMessage(message, message.getAllRecipients());
+
+      // 关闭连接
+
+      transport.close();
+
+      return true;
+
+    } catch (Exception e) {
+
+      logger.error("发送邮件异常:", e);
+      return false;
+    }
+
+  }
+}
diff --git a/backend/src/main/java/com/supwisdom/dlpay/portal/util/PortalConstant.java b/backend/src/main/java/com/supwisdom/dlpay/portal/util/PortalConstant.java
index fb80698..f9f21e0 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/portal/util/PortalConstant.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/portal/util/PortalConstant.java
@@ -18,6 +18,8 @@
   public static final String SYSPARA_PORTAL_SECRET = "portal.secret";
   public static final String SYSPARA_PORTAL_AMAPKEY = "portal.amapkey";
   public static final String SYSPARA_PORTAL_AMAPURL = "portal.amapurl";
+  public static final String SYSPARA_PORTAL_EMAIL_ADDRESS = "portal.email.address";
+  public static final String SYSPARA_PORTAL_EMAIL_PWD = "portal.email.pwd";
   public static final String SYSPARA_MEDICAL_URL = "medical.url";
   public static final String SYSPARA_MEDICAL_SHOPACCNO = "medical.shopaccno";
   public static final String SYSPARA_MEDICAL_SAVEJSON = "medical.savejson";
@@ -35,4 +37,9 @@
 
   public static final String FEEDBACK_STATUS_DELETE = "delete";
   public static final String FEEDBACK_STATUS_TOREPLY = "toreply";
+
+  public static final String REDISKEY_MOBILE_EMAIL_CODE = "m:e:c:";
+
+  public static final String MOBILE_EMAILCODE_BIND = "bind";
+  public static final String MOBILE_EMAILCODE_UNBIND = "unbind";
 }
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/MedicalApi.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/MedicalApi.kt
index 8930c75..e71bb2c 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/MedicalApi.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/MedicalApi.kt
@@ -375,7 +375,7 @@
      * 获取检验报告列表
      */
     @PostMapping("/labreport/list")
-    fun getLabReportList(hospitalcode: String): JsonResult? {
+    fun getLabReportList(hospitalcode: String, pageno: Int, pagesize: Int): JsonResult? {
         try {
             val p = SecurityContextHolder.getContext().authentication
             val user = mobileApiService.findUserById(p.name)
@@ -390,7 +390,7 @@
                 logger.error { "获取userid为${user.userid}的用户信息失败,错误信息:${userInfo.retmsg}" }
                 return JsonResult.error("获取用户信息失败")
             }
-            val list = medicalService.getLabReportList(userInfo.idno, hospitalcode)
+            val list = medicalService.getLabReportList(userInfo.idno, hospitalcode, pageno, pagesize)
             return JsonResult.ok().put("data", list)
         } catch (e: Exception) {
             logger.error("系统异常", e)
@@ -432,7 +432,7 @@
      * 获取检查报告列表
      */
     @PostMapping("/examreport/list")
-    fun getExamReportList(hospitalcode: String): JsonResult? {
+    fun getExamReportList(hospitalcode: String, pageno: Int, pagesize: Int): JsonResult? {
         try {
             val p = SecurityContextHolder.getContext().authentication
             val user = mobileApiService.findUserById(p.name)
@@ -447,7 +447,7 @@
                 logger.error { "获取userid为${user.userid}的用户信息失败,错误信息:${userInfo.retmsg}" }
                 return JsonResult.error("获取用户信息失败")
             }
-            val list = medicalService.getExamReportList(userInfo.idno, hospitalcode)
+            val list = medicalService.getExamReportList(userInfo.idno, hospitalcode, pageno, pagesize)
             return JsonResult.ok().put("data", list)
         } catch (e: Exception) {
             logger.error("系统异常", e)
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/bean/PayedBean.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/bean/PayedBean.kt
index eb25cec..b4943df 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/bean/PayedBean.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/bean/PayedBean.kt
@@ -57,4 +57,5 @@
     var notifyStatus: Boolean? = false
     var transdate: String? = ""
     var transtime: String? = ""
+    var refundflag: String? = ""
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/ExamReportDao.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/ExamReportDao.kt
index a85b1c1..42c0fa1 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/ExamReportDao.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/ExamReportDao.kt
@@ -5,7 +5,4 @@
 import org.springframework.stereotype.Repository
 
 @Repository
-interface ExamReportDao : JpaRepository<TBExamReport, String> {
-//    fun findByHZSFZAndYLJGDM(hzsfz: String, yljgdm: String): List<TBExamReport>?
-//    fun findByHZSFZAndJCBGDHAndYLJGDM(hzsfz: String, jcbgdh: String, yljgdm: String): TBExamReport?
-}
\ No newline at end of file
+interface ExamReportDao : JpaRepository<TBExamReport, String>, ExamReportRepository
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/ExamReportRepository.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/ExamReportRepository.kt
new file mode 100644
index 0000000..6bfd652
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/ExamReportRepository.kt
@@ -0,0 +1,9 @@
+package com.supwisdom.dlpay.medical.dao
+
+import com.supwisdom.dlpay.framework.jpa.page.Pagination
+import com.supwisdom.dlpay.medical.domain.TBExamReport
+
+interface ExamReportRepository {
+    fun findByHZSFZAndYLJGDM(hzsfz: String, yljgdm: String, pageno: Int, pagesize: Int): Pagination
+    fun findByHZSFZAndJCBGDHAndYLJGDM(hzsfz: String, jcbgdh: String, yljgdm: String): TBExamReport?
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabDetailDao.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabDetailDao.kt
index 970ab51..e88ab54 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabDetailDao.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabDetailDao.kt
@@ -5,7 +5,6 @@
 import org.springframework.stereotype.Repository
 
 @Repository
-interface LabDetailDao : JpaRepository<TBLabDetail, String> {
-//    fun findByJYBGDHAndYLJGDM(jybgdh: String, yljgdm: String): List<TBLabDetail>?
+interface LabDetailDao : JpaRepository<TBLabDetail, String>,LabDetailRepository {
 
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabDetailRepository.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabDetailRepository.kt
new file mode 100644
index 0000000..823839a
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabDetailRepository.kt
@@ -0,0 +1,7 @@
+package com.supwisdom.dlpay.medical.dao
+
+import com.supwisdom.dlpay.medical.domain.TBLabDetail
+
+interface LabDetailRepository {
+    fun findByJYBGDHAndYLJGDM(jybgdh: String, yljgdm: String): List<TBLabDetail>?
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabReportDao.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabReportDao.kt
index 2110463..fec3b56 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabReportDao.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabReportDao.kt
@@ -5,7 +5,4 @@
 import org.springframework.stereotype.Repository
 
 @Repository
-interface LabReportDao : JpaRepository<TBLabReport, String> {
-//    fun findByHZSFZAndYLJGDM(hzsfz: String, yljgdm: String): List<TBLabReport>?
-//    fun findByHZSFZAndJYBGDHAndYLJGDM(hzsfz: String, jybgdh: String, yljgdm: String): TBLabReport?
-}
\ No newline at end of file
+interface LabReportDao : JpaRepository<TBLabReport, String>, LabReportRepository
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabReportRepository.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabReportRepository.kt
new file mode 100644
index 0000000..3f072bc
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/dao/LabReportRepository.kt
@@ -0,0 +1,9 @@
+package com.supwisdom.dlpay.medical.dao
+
+import com.supwisdom.dlpay.framework.jpa.page.Pagination
+import com.supwisdom.dlpay.medical.domain.TBLabReport
+
+interface LabReportRepository {
+    fun findByHZSFZAndYLJGDM(hzsfz: String, yljgdm: String, pageno: Int, pagesize: Int): Pagination
+    fun findByHZSFZAndJYBGDHAndYLJGDM(hzsfz: String, jybgdh: String, yljgdm: String): TBLabReport?
+}
\ 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 47bfc2f..4da3230 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
@@ -63,13 +63,13 @@
     fun findMedicalDtlByBillNo(billNo: String, uid: String): TBMedicalDtl
 
     @Transactional
-    fun getLabReportList(idNo: String, organizationId: String): List<LabReportDTO>
+    fun getLabReportList(idNo: String, organizationId: String, pageno: Int, pagesize: Int): Pagination
 
     @Transactional
     fun getLabReportDetail(reportNo: String, organizationId: String, idNo: String): LabReportDTO
 
     @Transactional
-    fun getExamReportList(idNo: String, organizationId: String): List<ExamReportDTO>
+    fun getExamReportList(idNo: String, organizationId: String, pageno: Int, pagesize: Int): Pagination
 
     @Transactional
     fun getExamReportDetail(reportNo: String, organizationId: String, idNo: String): ExamReportDTO
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 b0a3c11..1d54ab0 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
@@ -1,6 +1,9 @@
 package com.supwisdom.dlpay.medical.service.impl
 
-import com.supwisdom.dlpay.api.bean.*
+import com.supwisdom.dlpay.api.bean.CitizenCardPayfinishParam
+import com.supwisdom.dlpay.api.bean.CitizenCardPayinitParam
+import com.supwisdom.dlpay.api.bean.ConsumePayCancelParam
+import com.supwisdom.dlpay.api.bean.QueryDtlResultParam
 import com.supwisdom.dlpay.framework.jpa.page.Pagination
 import com.supwisdom.dlpay.framework.service.SystemUtilService
 import com.supwisdom.dlpay.framework.util.MoneyUtil
@@ -8,9 +11,7 @@
 import com.supwisdom.dlpay.framework.util.TradeDict
 import com.supwisdom.dlpay.medical.bean.*
 import com.supwisdom.dlpay.medical.dao.*
-import com.supwisdom.dlpay.medical.domain.TBAppointmentDtl
-import com.supwisdom.dlpay.medical.domain.TBMedicalCard
-import com.supwisdom.dlpay.medical.domain.TBMedicalDtl
+import com.supwisdom.dlpay.medical.domain.*
 import com.supwisdom.dlpay.medical.exception.MedicineException
 import com.supwisdom.dlpay.medical.service.MedicalService
 import com.supwisdom.dlpay.medical.util.MedicalClient
@@ -188,27 +189,21 @@
         when (queryType) {
             MedicalConstant.ORDER_QUERYTYPE_IDENTITYCARD -> {
                 //  按身份类型查询
-                val unPayedIdentityCardList = ArrayList<IdentityCardBean>()
-                unPayedIdentityCardList.add(IdentityCardBean().apply {
+                upPayedRequest.patientIdentityCardList.add(IdentityCardBean().apply {
                     this.cardType = MedicalConstant.CARD_TYPE_IDCAED
                     this.cardNumber = data
                 })
-                upPayedRequest.patientIdentityCardList = unPayedIdentityCardList
             }
             MedicalConstant.ORDER_QUERYTYPE_PATIENTID -> {
                 //  根据患者id查询
-                val unPayedPatientList = ArrayList<PatientId>()
-                unPayedPatientList.add(PatientId().apply { this.patientId = data })
-                upPayedRequest.patientIdList = unPayedPatientList
+                upPayedRequest.patientIdList.add(PatientId().apply { this.patientId = data })
             }
             MedicalConstant.ORDER_QUERYTYPE_MEDICALCARD -> {
                 //  按医疗就诊卡查询
-                val unPayedMedicalCardList = ArrayList<PatientMedicalCardBean>()
-                unPayedMedicalCardList.add(PatientMedicalCardBean().apply {
+                upPayedRequest.patientMedicalCardList.add(PatientMedicalCardBean().apply {
                     patientMedicalCardType = MedicalConstant.MEDICALCARD_HOSPITAL
                     patientMedicalCardNumber = data
                 })
-                upPayedRequest.patientMedicalCardList = unPayedMedicalCardList
             }
             else -> {
                 throw MedicineException("未知的待付账单查询模式")
@@ -312,8 +307,6 @@
     override fun getPayedList(uid: String, organizationId: String, cardId: String): ArrayList<PayedDTO> {
         val medicalCard = medicalCardDao.findByCardidAndUid(cardId, uid)
                 ?: throw MedicineException("未找到该就诊卡")
-        val payedPatientIdList = ArrayList<PatientId>()
-        payedPatientIdList.add(PatientId().apply { this.patientId = medicalCard.patientid })
         //查询近三天的流水,医疗系统时间格式为 yyyy-MM-dd HH:mm:ss
         val sdf = SimpleDateFormat("yyyy-MM-dd")
         val date = systemUtilService.sysdatetime.hostdate
@@ -324,7 +317,7 @@
         val payedRequest = PayedRequest()
         payedRequest.organizationId = organizationId
         payedRequest.queryType = MedicalConstant.ORDER_QUERYTYPE_PATIENTID
-        payedRequest.patientIdList = payedPatientIdList
+        payedRequest.patientIdList.add(PatientId().apply { this.patientId = medicalCard.patientid })
         payedRequest.executeFlag = MedicalConstant.PAYED_EXECUTEFLAG_INVALID
         payedRequest.outpatientType = MedicalConstant.PAYED_OUTPATIENTTYPE_NORMAL
         payedRequest.beginTime = "$beginTime 00:00:00"
@@ -361,6 +354,7 @@
                     this.mergingname = it.mergingname
                     this.transdate = it.transdate
                     this.transtime = it.transtime
+                    this.refundflag = it.reverseflag
                     if (!StringUtil.isEmpty(it.resultid)) {
                         this.notifyStatus = true
                     }
@@ -403,23 +397,21 @@
             throw MedicineException("该流水缴费金额异常")
         }
         //2.向HIS预结算
-        val feeRecords = ArrayList<FeeRecordItem>()
-        feeRecords.add(FeeRecordItem().apply {
-            this.feeNo = mergingItems.feeNo
-            this.feeTypeCode = mergingItems.feeTypeCode
-            this.feeTypeName = mergingItems.feeTypeName
-        })
-
         val preFeeRequest = PreFeeRequest().apply {
             this.boilSign = mergingItems.boilSign!!
             this.organizationId = medicalDtl.organizationid
-            this.feeRecords = feeRecords
             this.patientId = unPayedResponse.patientId!!
             this.uid = uid
+            this.feeRecords.add(FeeRecordItem().apply {
+                this.feeNo = mergingItems.feeNo
+                this.feeTypeCode = mergingItems.feeTypeCode
+                this.feeTypeName = mergingItems.feeTypeName
+            })
             if (!medicalDtl.hisjson.isNullOrEmpty()) {
                 this.hasJson = true
             }
         }
+        
         val preFeeResponse = medicalClient.getPreCalculatedFee(preFeeRequest)
         if (preFeeResponse.payAmount != medicalDtl.mergingsubtotal || preFeeResponse.totalFee != medicalDtl.mergingsubtotal) {
             throw MedicineException("该流水缴费金额异常")
@@ -587,109 +579,151 @@
         return medicalDtl
     }
 
-    override fun getLabReportList(idNo: String, organizationId: String): List<LabReportDTO> {
-        val result = ArrayList<LabReportDTO>()
-//        val reportList = labReportDao.findByHZSFZAndYLJGDM(idNo, organizationId)
-//        if (!reportList.isNullOrEmpty()) {
-//            reportList.forEach {
-//                val reportDTO = LabReportDTO().apply {
-//                    this.projectname = it.jybgdmc
-//                    this.patientype = it.brbslb
-//                    this.reportno = it.jybgdh
-//                    this.patientname = it.hzxm
-//                    this.sex = it.xb
-//                    this.sampletype = it.bzlb
-//                    this.clinicno = it.jzlsh
-//                    this.department = it.jybgdjg
-//                }
-//                result.add(reportDTO)
-//            }
-//        }
-        return result
+    @Suppress("UNCHECKED_CAST")
+    override fun getLabReportList(idNo: String, organizationId: String, pageno: Int, pagesize: Int): Pagination {
+        val labReportDTOList = ArrayList<LabReportDTO>()
+        val pagination = labReportDao.findByHZSFZAndYLJGDM(idNo, organizationId, pageno, pagesize)
+        val reportList = pagination.list as ArrayList<TBLabReport>
+        if (!reportList.isNullOrEmpty()) {
+            reportList.forEach {
+                val reportDTO = LabReportDTO().apply {
+                    this.projectname = it.jybgdmc
+                    this.reportno = it.jybgdh
+                    this.patientname = it.hzxm
+                    this.sampletype = it.bblbdm
+                    this.clinicno = it.jzlsh
+                    this.department = it.jybgdjg
+                    if (!it.brbslb.isNullOrEmpty()) {
+                        when (it.brbslb) {
+                            MedicalConstant.HIS_PATIENTTYPE_OUTPATIENT -> this.patientype = "门诊"
+                            MedicalConstant.HIS_PATIENTTYPE_HOSPITALIZED -> this.patientype = "住院"
+                            MedicalConstant.HIS_PATIENTTYPE_EXAM -> this.patientype = "体检"
+                            MedicalConstant.HIS_PATIENTTYPE_OTHER -> this.patientype = "其他"
+                        }
+                    }
+                    if (!it.xb.isNullOrEmpty()) {
+                        if (MedicalConstant.HIS_GENDER_MALE == it.xb) {
+                            this.sex = "男"
+                        } else if (MedicalConstant.HIS_GENDER_FEMALE == it.xb) {
+                            this.sex = "女"
+                        }
+                    }
+                }
+                labReportDTOList.add(reportDTO)
+            }
+        }
+        pagination.list = labReportDTOList
+        return pagination
     }
 
     override fun getLabReportDetail(reportNo: String, organizationId: String, idNo: String): LabReportDTO {
-//        val report = labReportDao.findByHZSFZAndJYBGDHAndYLJGDM(idNo, reportNo, organizationId)
-//                ?: throw MedicineException("未找到检验报告编号为[${reportNo}]的报告")
+        val report = labReportDao.findByHZSFZAndJYBGDHAndYLJGDM(idNo, reportNo, organizationId)
+                ?: throw MedicineException("未找到检验报告编号为[${reportNo}]的报告")
         val reportDTO = LabReportDTO().apply {
-            //            this.projectname = report.jybgdmc
-//            this.patientype = report.brbslb
-//            this.reportno = report.jybgdh
-//            this.patientname = report.hzxm
-//            this.sex = report.xb
-//            this.sampletype = report.bzlb
-//            this.clinicno = report.jzlsh
-//            this.department = report.jybgdjg
-//        }
-//        val detailList = labDetailDao.findByJYBGDHAndYLJGDM(reportNo, organizationId)
-//        if (!detailList.isNullOrEmpty()) {
-//            val details = ArrayList<LabDetailDTO>()
-//            detailList.forEach {
-//                val detailDTO = LabDetailDTO().apply {
-//                    this.name = it.jyxmmc
-//                    this.result = it.jydljg
-//                    this.unit = it.jldw
-//                    this.referange = it.ckdx
-//                }
-//                details.add(detailDTO)
-//            }
-//            reportDTO.details = details
+            this.projectname = report.jybgdmc
+            this.reportno = report.jybgdh
+            this.patientname = report.hzxm
+            this.sampletype = report.bblbdm
+            this.clinicno = report.jzlsh
+            this.department = report.jybgdjg
+            if (!report.brbslb.isNullOrEmpty()) {
+                when (report.brbslb) {
+                    MedicalConstant.HIS_PATIENTTYPE_OUTPATIENT -> this.patientype = "门诊"
+                    MedicalConstant.HIS_PATIENTTYPE_HOSPITALIZED -> this.patientype = "住院"
+                    MedicalConstant.HIS_PATIENTTYPE_EXAM -> this.patientype = "体检"
+                    MedicalConstant.HIS_PATIENTTYPE_OTHER -> this.patientype = "其他"
+                }
+            }
+            if (!report.xb.isNullOrEmpty()) {
+                if (MedicalConstant.HIS_GENDER_MALE == report.xb) {
+                    this.sex = "男"
+                } else if (MedicalConstant.HIS_GENDER_FEMALE == report.xb) {
+                    this.sex = "女"
+                }
+            }
+        }
+        val detailList = labDetailDao.findByJYBGDHAndYLJGDM(reportNo, organizationId)
+        if (!detailList.isNullOrEmpty()) {
+            detailList.forEach {
+                val detailDTO = LabDetailDTO().apply {
+                    this.name = it.jyxmmc
+                    this.result = it.jyjg
+                    this.unit = it.jldw
+                    this.referange = it.ckdx
+                }
+                reportDTO.details.add(detailDTO)
+            }
         }
         return reportDTO
     }
 
-    override fun getExamReportList(idNo: String, organizationId: String): List<ExamReportDTO> {
-        val result = ArrayList<ExamReportDTO>()
-//        val reportList = examReportDao.findByHZSFZAndYLJGDM(idNo, organizationId)
-//        if (!reportList.isNullOrEmpty()) {
-//            reportList.forEach {
-//                val reportDTO = ExamReportDTO().apply {
-//                    this.examno = it.jcbgdh
-//                    this.name = it.hzxm
-//                    this.sex = it.xb
-//                    this.projectname = it.jcxmmc
-//                    if (it.jcrq != null) {
-//                        this.examdate = SimpleDateFormat("yyyyMMddHHmmss")
-//                                .format(it.jcrq)
-//                    }
-//                    this.applydep = it.sqksmc
-//                    this.examdep = it.bcksmc
-//                    this.exampart = it.jcbw
-//                    this.yinyang = it.yyx
-//                    if (it.jcrq != null) {
-//                        this.reportdate = SimpleDateFormat("yyyyMMddHHmmss")
-//                                .format(it.bgsj)
-//                    }
-//                }
-//                result.add(reportDTO)
-//            }
-//        }
-        return result
+    @Suppress("unchecked_cast")
+    override fun getExamReportList(idNo: String, organizationId: String, pageno: Int, pagesize: Int): Pagination {
+        val reportDTOList = ArrayList<ExamReportDTO>()
+        val pagination = examReportDao.findByHZSFZAndYLJGDM(idNo, organizationId, pageno, pagesize)
+        val reportList = pagination.list as ArrayList<TBExamReport>
+        if (!reportList.isNullOrEmpty()) {
+            reportList.forEach {
+                val reportDTO = ExamReportDTO().apply {
+                    this.examno = it.jcbgdh
+                    this.name = it.hzxm
+                    this.projectname = it.jcxmmc
+                    this.applydep = it.sqksmc
+                    this.examdep = it.bcksmc
+                    this.exampart = it.jcbw
+                    this.yinyang = it.yyx
+                    if (!it.xb.isNullOrEmpty()) {
+                        if (MedicalConstant.HIS_GENDER_MALE == it.xb) {
+                            this.sex = "男"
+                        } else if (MedicalConstant.HIS_GENDER_FEMALE == it.xb) {
+                            this.sex = "女"
+                        }
+                    }
+                    if (it.jcrq != null) {
+                        this.examdate = SimpleDateFormat("yyyyMMddHHmmss")
+                                .format(it.jcrq)
+                    }
+                    if (it.bgsj != null) {
+                        this.reportdate = SimpleDateFormat("yyyyMMddHHmmss")
+                                .format(it.bgsj)
+                    }
+                }
+                reportDTOList.add(reportDTO)
+            }
+        }
+        pagination.list = reportDTOList
+        return pagination
     }
 
     override fun getExamReportDetail(reportNo: String, organizationId: String, idNo: String): ExamReportDTO {
-//        val report = examReportDao.findByHZSFZAndJCBGDHAndYLJGDM(idNo, reportNo, organizationId)
-//                ?: throw MedicineException("未找到检查报告编号为[${reportNo}]的报告")
+        val report = examReportDao.findByHZSFZAndJCBGDHAndYLJGDM(idNo, reportNo, organizationId)
+                ?: throw MedicineException("未找到检查报告编号为[${reportNo}]的报告")
         return ExamReportDTO().apply {
-            //            examno = report.jcbgdh
-//            name = report.hzxm
-//            sex = report.xb
-//            projectname = report.jcxmmc
-//            applydep = report.sqksmc
-//            examdep = report.bcksmc
-//            exampart = report.jcbw
-//            yinyang = report.yyx
-//            description = report.bckgsj
-//            diagnosis = report.jczdhts
-//            remark = report.bgbz
-//            if (report.jcrq != null) {
-//                examdate = SimpleDateFormat("yyyyMMddHHmmss")
-//                        .format(report.jcrq)
-//            }
-//            if (report.jcrq != null) {
-//                reportdate = SimpleDateFormat("yyyyMMddHHmmss")
-//                        .format(report.bgsj)
-//            }
+            examno = report.jcbgdh
+            name = report.hzxm
+            projectname = report.jcxmmc
+            applydep = report.sqksmc
+            examdep = report.bcksmc
+            exampart = report.jcbw
+            yinyang = report.yyx
+            description = report.bckgsj
+            diagnosis = report.jczdhts
+            remark = report.bgbz
+            if (!report.xb.isNullOrEmpty()) {
+                if (MedicalConstant.HIS_GENDER_MALE == report.xb) {
+                    this.sex = "男"
+                } else if (MedicalConstant.HIS_GENDER_FEMALE == report.xb) {
+                    this.sex = "女"
+                }
+            }
+            if (report.jcrq != null) {
+                examdate = SimpleDateFormat("yyyyMMddHHmmss")
+                        .format(report.jcrq)
+            }
+            if (report.jcrq != null) {
+                reportdate = SimpleDateFormat("yyyyMMddHHmmss")
+                        .format(report.bgsj)
+            }
         }
     }
 
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/task/NotifyHISAsyncTask.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/task/NotifyHISAsyncTask.kt
index 0a0e1ca..ca4e2c0 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/medical/task/NotifyHISAsyncTask.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/medical/task/NotifyHISAsyncTask.kt
@@ -63,13 +63,11 @@
             this.payer = personResponse.person.name
             this.totalFee = medicalDtl.totalfee
             this.brid = medicalDtl.patientid
-            val feeRecords = ArrayList<FeeRecordItem>()
-            feeRecords.add(FeeRecordItem().apply {
+            this.feeRecords.add(FeeRecordItem().apply {
                 this.feeNo = medicalDtl.feeno
                 this.feeTypeCode = medicalDtl.feetypecode
                 this.feeTypeName = medicalDtl.feetypename
             })
-            this.feeRecords = feeRecords
         }
         val result = medicalClient.notifyPayed(notifyPayedRequest)
         if (result.code == MedicalConstant.MEDICAL_RESPONSE_SUCCESS) {
@@ -83,9 +81,7 @@
             }
         } else {
             logger.error("调用医疗结算确认接口异常,billno=[${medicalDtl.billno}],code:" + result.code + ";message:" + result.message)
-            //如果确认接口重复调用会报错,此处可以查询已支付列表中是否有该订单来确认是否已通知HIS成功
-            val payedPatientIdList = ArrayList<PatientId>()
-            payedPatientIdList.add(PatientId().apply { medicalDtl.patientid })
+            //如果确认接口重复调用会报g错,此处可以查询已支付列表中是否有该订单来确认是否已通知HIS成功
             //查询近三天的流水,医疗系统时间格式为 yyyy-MM-dd HH:mm:ss
             val sdf = SimpleDateFormat("yyyy-MM-dd")
             val currentDate = SimpleDateFormat("yyyyMMdd").parse(systemUtilService.sysdatetime.hostdate)
@@ -95,7 +91,7 @@
             val payedRequest = PayedRequest()
             payedRequest.organizationId = medicalDtl.organizationid
             payedRequest.queryType = MedicalConstant.ORDER_QUERYTYPE_PATIENTID
-            payedRequest.patientIdList = payedPatientIdList
+            payedRequest.patientIdList.add(PatientId().apply { medicalDtl.patientid })
             payedRequest.executeFlag = MedicalConstant.PAYED_EXECUTEFLAG_INVALID
             payedRequest.outpatientType = MedicalConstant.PAYED_OUTPATIENTTYPE_NORMAL
             payedRequest.beginTime = "$beginTime 00:00:00"
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/AuthLoginHandler.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/AuthLoginHandler.kt
index ae7b0ce..7209124 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/AuthLoginHandler.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/AuthLoginHandler.kt
@@ -154,6 +154,7 @@
                     ?.put("name", name)
                     ?.put("uid", user.uid)
                     ?.put("phone", StringUtil.phoneReplace(user.phone))
+                    ?.put("email", user.email)
                     ?.put("paypwdset",payseted)
                     ?.put("signed", signed)
                     ?.put("imageurl",imageUrl)
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 0dc338f..0e3605b 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
@@ -22,7 +22,9 @@
 import com.supwisdom.dlpay.mobile.service.MobileApiService
 import com.supwisdom.dlpay.paysdk.proxy.UserProxy
 import com.supwisdom.dlpay.portal.bean.FeedbackSearchBean
+import com.supwisdom.dlpay.portal.bean.UserSecurityRequestBean
 import com.supwisdom.dlpay.portal.domain.TBFeedback
+import com.supwisdom.dlpay.portal.domain.TBUserSecurity
 import com.supwisdom.dlpay.portal.service.*
 import com.supwisdom.dlpay.portal.util.PortalConstant
 import com.supwisdom.dlpay.system.service.DictionaryProxy
@@ -75,6 +77,8 @@
     lateinit var outletsService: OutletsService
     @Autowired
     lateinit var medicalService: MedicalService
+    @Autowired
+    lateinit var secretSecurityService: SecretSecurityService
     val logger = KotlinLogging.logger { }
 
     @RequestMapping("/time")
@@ -459,6 +463,88 @@
             return JsonResult.error("系统异常,请稍后重试")
         }
     }
+
+    /**
+     * 获取用户已设置密保
+     */
+    @RequestMapping(value = ["/security/list"], method = [RequestMethod.GET])
+    fun getSecretSecurityList(phone: String?): JsonResult? {
+        return try {
+            val user:TBMobileUser
+            user = if (phone.isNullOrEmpty()) {
+                val p = SecurityContextHolder.getContext().authentication
+                mobileApiService.findUserById(p.name)
+                        ?: return JsonResult.error("用户不存在,请注册")
+            } else {
+                mobileApiService.findUserByPhone(phone)
+                        ?: return JsonResult.error("用户不存在")
+            }
+            val data = secretSecurityService.getSecretSecurityList(user.uid)
+            JsonResult.ok().put("data", data)
+        } catch (e: Exception) {
+            if (e is PortalBusinessException) {
+                return JsonResult.error(e.message)
+            }
+            logger.error("获取密保问题异常", e)
+            JsonResult.error("获取密保问题失败,请稍后再试")
+        }
+    }
+
+    /**
+     * 校验密保答案
+     */
+    @RequestMapping(value = ["/security/check"], method = [RequestMethod.GET])
+    fun checkUserSecurity(@RequestBody bean: UserSecurityRequestBean): JsonResult? {
+        return try {
+            val user = mobileApiService.findUserByPhone(bean.phone)
+                    ?: return JsonResult.error("用户不存在,请注册")
+            bean.uid = user.uid
+            val error = secretSecurityService.checkUserSecurity(bean)
+            return JsonResult.ok().put("error",error)
+        } catch (e: Exception) {
+            if (e is PortalBusinessException) {
+                return JsonResult.error(e.message)
+            }
+            logger.error("验证密保异常", e)
+            JsonResult.error("验证密保失败,请稍后再试")
+        }
+    }
+
+    /**
+     * 密保改密码
+     */
+    @RequestMapping(value = ["/security/pwdset"], method = [RequestMethod.POST])
+    fun pwdSetBySecurity(@RequestBody bean: UserSecurityRequestBean): JsonResult? {
+        return try {
+            val user = mobileApiService.findUserByPhone(bean.phone)
+                    ?: return JsonResult.error("用户不存在,请注册")
+            bean.uid = user.uid
+            val error = secretSecurityService.checkUserSecurity(bean)
+            if (!error.isNullOrEmpty()) {
+                return JsonResult.error("密保答案错误")
+            }
+            if (bean.pwd != bean.repwd) {
+                return JsonResult.error("两次密码不一致")
+            }
+            if (!bean.pwd.matches(Regex("^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,32}$"))) {
+                return JsonResult.error("密码必须包含数字和字符,长度6~32位")
+            }
+            if (bean.pwd.isEmpty() || bean.pwd.length < 6) {
+                return JsonResult.error("请重新设置密码,密码不能小于6位字符")
+            }
+            val encoder = BCryptPasswordEncoder()
+            user.loginpwd = encoder.encode(bean.repwd)
+            mobileApiService.saveUser(user)
+            return JsonResult.ok("密码修改成功")
+        } catch (e: Exception) {
+            if (e is PortalBusinessException) {
+                return JsonResult.error(e.message)
+            }
+            logger.error("密保修改密码异常", e)
+            JsonResult.error("修改密码失败,请稍后再试")
+        }
+    }
+
 }
 
 
@@ -483,6 +569,8 @@
     lateinit var feedbackService: FeedbackService
     @Autowired
     lateinit var userProxy: UserProxy
+    @Autowired
+    lateinit var secretSecurityService: SecretSecurityService
     val logger = KotlinLogging.logger { }
 
     @RequestMapping("/idtypes")
@@ -1296,8 +1384,8 @@
     @RequestMapping(value = ["/feedback/release"], method = [RequestMethod.POST])
     fun releaseFeedback(@RequestBody bean: FeedbackBean,request:HttpServletRequest): JsonResult? {
         val p = SecurityContextHolder.getContext().authentication
-        val user = (mobileApiService.findUserById(p.name)
-                ?: return JsonResult.error("用户不存在,请注册"))
+        val user = mobileApiService.findUserById(p.name)
+                ?: return JsonResult.error("用户不存在,请注册")
         val feedback = TBFeedback().apply {
             this.title = bean.title
             this.content = bean.content
@@ -1317,8 +1405,8 @@
     @RequestMapping(value = ["/feedback/list"], method = [RequestMethod.GET])
     fun getFeedbackList(pagesize:Int,pageno:Int): JsonResult? {
         val p = SecurityContextHolder.getContext().authentication
-        val user = (mobileApiService.findUserById(p.name)
-                ?: return JsonResult.error("用户不存在,请注册"))
+        val user = mobileApiService.findUserById(p.name)
+                ?: return JsonResult.error("用户不存在,请注册")
         val data = feedbackService.getFeedbackList(FeedbackSearchBean().apply {
             this.pageno = pageno
             this.pagesize = pagesize
@@ -1333,8 +1421,8 @@
     @RequestMapping(value = ["/feedback/{fbid}"], method = [RequestMethod.GET])
     fun getFeedbackDetail(@PathVariable fbid:String): JsonResult? {
         val p = SecurityContextHolder.getContext().authentication
-        val user = (mobileApiService.findUserById(p.name)
-                ?: return JsonResult.error("用户不存在,请注册"))
+        val user = mobileApiService.findUserById(p.name)
+                ?: return JsonResult.error("用户不存在,请注册")
         val feedback = feedbackService.getFeedbackDetail(fbid, user.uid)
                 ?: return JsonResult.error("未找到该条留言")
         return JsonResult.ok().put("data",feedback)
@@ -1346,8 +1434,8 @@
     @RequestMapping(value = ["/feedback/delete/{fbid}"], method = [RequestMethod.POST])
     fun deleteFeedback(@PathVariable fbid:String): JsonResult? {
         val p = SecurityContextHolder.getContext().authentication
-        val user = (mobileApiService.findUserById(p.name)
-                ?: return JsonResult.error("用户不存在,请注册"))
+        val user = mobileApiService.findUserById(p.name)
+                ?: return JsonResult.error("用户不存在,请注册")
         val errorMsg = feedbackService.deleteFeedback(fbid, user.uid)
                 ?: return JsonResult.ok()
         return JsonResult.error(errorMsg)
@@ -1360,8 +1448,8 @@
     @RequestMapping(value = ["/point/total"],method = [RequestMethod.GET])
     fun getTotalPoint():JsonResult?{
         val p = SecurityContextHolder.getContext().authentication
-        val user = (mobileApiService.findUserById(p.name)
-                ?: return JsonResult.error("用户不存在,请注册"))
+        val user = mobileApiService.findUserById(p.name)
+                ?: return JsonResult.error("用户不存在,请注册")
         if (!user.userid.isNullOrEmpty()) {
             val cardResponse = userProxy.queryCard(QueryCardParam().apply {
                 this.userid = user.userid
@@ -1404,8 +1492,8 @@
     @RequestMapping(value = ["/point/flow"],method = [RequestMethod.GET])
     fun getTotalPoint(pageno:Int,pagesize:Int):JsonResult?{
         val p = SecurityContextHolder.getContext().authentication
-        val user = (mobileApiService.findUserById(p.name)
-                ?: return JsonResult.error("用户不存在,请注册"))
+        val user = mobileApiService.findUserById(p.name)
+                ?: return JsonResult.error("用户不存在,请注册")
         val emptyResult = PageResult<Any>().apply {
             this.count = 0
         }
@@ -1446,8 +1534,8 @@
     fun completePointTask(@PathVariable taskcode:String):JsonResult?{
         try {
             val p = SecurityContextHolder.getContext().authentication
-            val user = (mobileApiService.findUserById(p.name)
-                    ?: return JsonResult.error("用户不存在,请注册"))
+            val user = mobileApiService.findUserById(p.name)
+                    ?: return JsonResult.error("用户不存在,请注册")
             if (!user.userid.isNullOrEmpty()) {
                 val personResponse = userProxy.queryPerson(user.userid)
                 if (personResponse.retcode != 0) {
@@ -1474,8 +1562,8 @@
     fun completePointTask():JsonResult?{
         try {
             val p = SecurityContextHolder.getContext().authentication
-            val user = (mobileApiService.findUserById(p.name)
-                    ?: return JsonResult.error("用户不存在,请注册"))
+            val user = mobileApiService.findUserById(p.name)
+                    ?: return JsonResult.error("用户不存在,请注册")
             val result = userService.queryPointTaskStatus(user)
             return JsonResult.ok().put("data",result)
         } catch (e: Exception) {
@@ -1496,8 +1584,193 @@
             val result = mobileApiService.qrCodeUrlAuth(url)
             JsonResult.ok().put("data", result)
         } catch (e: MalformedURLException) {
-            logger.error("地址异常:[${url}]",e)
+            logger.error("地址异常:[${url}]", e)
             JsonResult.error("该二维码非大理市民卡二维码,无法访问")
         }
     }
+
+    /**
+     * 发送邮件
+     */
+    @RequestMapping(value = ["/email/send"], method = [RequestMethod.POST])
+    fun sendEmail(email: String,type: String): JsonResult? {
+        return try {
+            if (!email.matches(Regex("^[0-9a-zA-Z_.-]+[@][0-9a-zA-Z_.-]+([.][a-zA-Z]+){1,2}$"))) {
+                return JsonResult.error("邮箱格式错误,请检查后再试")
+            }
+            val redisCode = redisTemplate.opsForValue().get(PortalConstant.REDISKEY_MOBILE_EMAIL_CODE + email)
+            // 已发送则不用发送
+            if (redisCode.isNullOrEmpty()) {
+                val code = RandomUtils.randomNumber(6)
+                logger.error { code }
+                redisTemplate.opsForValue().set(PortalConstant.REDISKEY_MOBILE_EMAIL_CODE + email, code, Duration.ofMinutes(2))
+                val res = mobileApiService.sendEmailCode(email, code, type)
+                if (!res) {
+                    return JsonResult.error("发送邮件失败,请稍后再试")
+                }
+                redisTemplate.opsForValue().set(PortalConstant.REDISKEY_MOBILE_EMAIL_CODE + email, code, Duration.ofMinutes(2))
+            }
+            JsonResult.ok()
+        } catch (e: Exception) {
+            logger.error("发送邮件异常:[${email}]", e)
+            JsonResult.error("发送邮件失败,请稍后再试")
+        }
+    }
+
+    /**
+     * 绑定邮箱
+     */
+    @RequestMapping(value = ["/email/bind"], method = [RequestMethod.POST])
+    fun bindEmail(code: String, email: String): JsonResult? {
+        return try {
+            val p = SecurityContextHolder.getContext().authentication
+            val user = mobileApiService.findUserById(p.name)
+                    ?: return JsonResult.error("用户不存在,请注册")
+            if (!user.email.isNullOrEmpty()) {
+                return JsonResult.error("用户已绑定邮箱,如要更换请先解除绑定")
+            }
+            val redisCode = redisTemplate.opsForValue().get(PortalConstant.REDISKEY_MOBILE_EMAIL_CODE + email)
+            if (code != redisCode) {
+                return JsonResult.error("验证码错误,绑定失败")
+            }
+            mobileApiService.setUserEmail(user.uid, email, PortalConstant.MOBILE_EMAILCODE_BIND)
+            mobileApiService.sendSuccessEmail(email, user.phone?.substring(7))
+            JsonResult.ok()
+        } catch (e: Exception) {
+            if (e is PortalBusinessException) {
+                return JsonResult.error(e.message)
+            }
+            logger.error("绑定邮箱异常:[${email}]", e)
+            JsonResult.error("绑定邮箱失败,请稍后再试")
+        }
+    }
+
+    /**
+     * 解除邮箱绑定
+     */
+    @RequestMapping(value = ["/email/unbind"], method = [RequestMethod.POST])
+    fun unbindEmail(code: String): JsonResult? {
+        return try {
+            val p = SecurityContextHolder.getContext().authentication
+            val user = mobileApiService.findUserById(p.name)
+                    ?: return JsonResult.error("用户不存在,请注册")
+            if (user.email.isNullOrEmpty()) {
+                return JsonResult.error("用户未绑定邮箱,无需解绑邮箱")
+            }
+            val redisCode = redisTemplate.opsForValue().get(PortalConstant.REDISKEY_MOBILE_EMAIL_CODE + user.email)
+            if (code != redisCode) {
+                return JsonResult.error("验证码错误,解除绑定失败")
+            }
+            mobileApiService.setUserEmail(user.uid, null, PortalConstant.MOBILE_EMAILCODE_UNBIND)
+            JsonResult.ok()
+        } catch (e: Exception) {
+            if (e is PortalBusinessException) {
+                return JsonResult.error(e.message)
+            }
+            logger.error("解绑邮箱异常", e)
+            JsonResult.error("解绑邮箱失败,请稍后再试")
+        }
+    }
+
+    /**
+     * 获取密保列表
+     */
+    @RequestMapping(value = ["/security/all"], method = [RequestMethod.GET])
+    fun getAllSecretSecurity(): JsonResult? {
+        return try {
+            val data = secretSecurityService.getAllSecretSecurity()
+            JsonResult.ok().put("data",data)
+        } catch (e: Exception) {
+            if (e is PortalBusinessException) {
+                return JsonResult.error(e.message)
+            }
+            logger.error("获取密保问题列表异常", e)
+            JsonResult.error("获取密保问题失败,请稍后再试")
+        }
+    }
+
+    /**
+     * 设置密保答案
+     */
+    @RequestMapping(value = ["/security/save"], method = [RequestMethod.POST])
+    fun saveUserSecurity(@RequestBody bean: UserSecurityRequestBean): JsonResult? {
+        return try {
+            val p = SecurityContextHolder.getContext().authentication
+            val user = mobileApiService.findUserById(p.name)
+                    ?: return JsonResult.error("用户不存在,请注册")
+            // 校验密码
+//            val pwdtimes = user.checkLoginpwdtime()
+//            if (pwdtimes == -1) {
+//                if (!user.jti.isNullOrEmpty()) {
+//                    apiJwtRepository.deleteById(user.jti!!)
+//                }
+//                return JsonResult.error(-1, "登录密码错误次数过多,将退出系统,请重新登录系统或点击忘记密码功能找回密码")
+//            } else if (pwdtimes == 1) {
+//                mobileApiService.saveUser(user)
+//            }
+//            val encoder = BCryptPasswordEncoder()
+//            if (!encoder.matches(bean.pwd, user.loginpwd)) {
+//                user.updateLoginpwderror(false).also {
+//                    if (it) mobileApiService.saveUser(user)
+//                }
+//                return JsonResult.error("登录密码错误")
+//            } else {
+//                user.updateLoginpwderror(true)
+//            }
+//            mobileApiService.saveUser(user)
+            // 保存密保
+            bean.uid = user.uid
+            secretSecurityService.saveUserSecurity(bean)
+            JsonResult.ok()
+        } catch (e: Exception) {
+            if (e is PortalBusinessException) {
+                return JsonResult.error(e.message)
+            }
+            logger.error("保存密保异常", e)
+            JsonResult.error("保存密保失败,请稍后再试")
+        }
+    }
+
+    /**
+     * 删除密保答案
+     */
+    @RequestMapping(value = ["/security/delete"], method = [RequestMethod.POST])
+    fun deleteUserSecurity(): JsonResult? {
+        return try {
+            val p = SecurityContextHolder.getContext().authentication
+            val user = mobileApiService.findUserById(p.name)
+                    ?: return JsonResult.error("用户不存在,请注册")
+            // 校验密码
+//            val pwdtimes = user.checkLoginpwdtime()
+//            if (pwdtimes == -1) {
+//                if (!user.jti.isNullOrEmpty()) {
+//                    apiJwtRepository.deleteById(user.jti!!)
+//                }
+//                return JsonResult.error(-1, "登录密码错误次数过多,将退出系统,请重新登录系统或点击忘记密码功能找回密码")
+//            } else if (pwdtimes == 1) {
+//                mobileApiService.saveUser(user)
+//            }
+//            val encoder = BCryptPasswordEncoder()
+//            if (!encoder.matches(pwd, user.loginpwd)) {
+//                user.updateLoginpwderror(false).also {
+//                    if (it) mobileApiService.saveUser(user)
+//                }
+//                return JsonResult.error("登录密码错误")
+//            } else {
+//                user.updateLoginpwderror(true)
+//            }
+//            mobileApiService.saveUser(user)
+            // 删除密保
+            secretSecurityService.deleteUserSecurity(user.uid)
+            JsonResult.ok()
+        } catch (e: Exception) {
+            if (e is PortalBusinessException) {
+                return JsonResult.error(e.message)
+            }
+            logger.error("删除密保异常", e)
+            JsonResult.error("删除密保失败,请稍后再试")
+        }
+    }
+
+    
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt
index 3f40d0a..502d9f2 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/MobileApiService.kt
@@ -14,19 +14,25 @@
 
     fun findUserById(id: String): TBMobileUser?
 
-    fun findCardByNo(cardno: String):TCard?
+    fun findCardByNo(cardno: String): TCard?
 
-    fun findCardByUserid(userid :String) :TCard?
+    fun findCardByUserid(userid: String): TCard?
 
-    fun findCityCardByUserid(userid :String) :TCard?
+    fun findCityCardByUserid(userid: String): TCard?
 
-    fun saveCard(card:TCard):TCard
+    fun saveCard(card: TCard): TCard
 
-    fun sendSms(phone:String,code:String):BaseResp
+    fun sendSms(phone: String, code: String): BaseResp
 
-    fun findByUseridAndStatus(userid:String,status:String):List<TBMobileUser>?
+    fun findByUseridAndStatus(userid: String, status: String): List<TBMobileUser>?
 
     fun findCardByUseridAndCardphyid(userid: String, cardtype: String, cardphyid: String?): TCard?
 
-    fun qrCodeUrlAuth(url:String):Map<String,Any>
+    fun qrCodeUrlAuth(url: String): Map<String, Any>
+
+    fun sendEmailCode(addressee: String, code: String, type: String): Boolean
+
+    fun sendSuccessEmail(addressee: String, mobile: String?): Boolean
+
+    fun setUserEmail(uid: String, addressee: String?, type: String)
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt
index eeaa321..9567001 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/service/impl/MobileApiServiceImpl.kt
@@ -5,11 +5,15 @@
 import com.supwisdom.dlpay.api.dao.CardDao
 import com.supwisdom.dlpay.api.domain.TCard
 import com.supwisdom.dlpay.framework.service.SystemUtilService
+import com.supwisdom.dlpay.framework.util.TradeDict
 import com.supwisdom.dlpay.mobile.dao.MobileUserDao
 import com.supwisdom.dlpay.mobile.dao.PagesDao
 import com.supwisdom.dlpay.mobile.domain.TBMobileUser
 import com.supwisdom.dlpay.mobile.domain.TBPages
+import com.supwisdom.dlpay.mobile.exception.PortalBusinessException
 import com.supwisdom.dlpay.mobile.service.MobileApiService
+import com.supwisdom.dlpay.portal.util.EmailUtil
+import com.supwisdom.dlpay.portal.util.PortalConstant
 import com.supwisdom.dlpay.util.ConstantUtil
 import mu.KotlinLogging
 import org.springframework.beans.factory.annotation.Autowired
@@ -32,18 +36,23 @@
     @Autowired
     lateinit var systemUtilService: SystemUtilService
 
+    @Autowired
+    lateinit var emailUtil: EmailUtil
+
     companion object {
         var isMsgLogined: Boolean = false
         val pathList = ArrayList<String>()
         const val dlsmkHost = "yy.dlsmk.cn"
         const val waterPath = "/s"
         const val restaurantPath = "/restaurant"
+
         init {
             pathList.add(waterPath)
             pathList.add(restaurantPath)
         }
 
     }
+
     val logger = KotlinLogging.logger { }
 
     override fun saveUser(user: TBMobileUser): TBMobileUser {
@@ -114,7 +123,7 @@
         }
         var addserial = systemUtilService.getBusinessValue("sms.addserial")
         if (addserial.isNullOrEmpty()) {
-            addserial=""
+            addserial = ""
         }
         var sign = systemUtilService.getBusinessValue("sms.sign")
         if (sign.isNullOrEmpty()) {
@@ -136,7 +145,7 @@
         }
         var client = Client.getInstance()
         // 正式环境IP,登录验证URL,用户名,密码,集团客户名称
-        if(!isMsgLogined){
+        if (!isMsgLogined) {
             var ret = client.login(url, account, pwd, ecname)
             if (!ret) {
                 logger.error { "无法登陆短信平台,身份验证失" }
@@ -193,14 +202,14 @@
         }
     }
 
-    override fun qrCodeUrlAuth(url: String):Map<String,Any> {
+    override fun qrCodeUrlAuth(url: String): Map<String, Any> {
         val parseUrl = URL(url)
-        val result = HashMap<String,Any>()
+        val result = HashMap<String, Any>()
         result["permit"] = false
         result["url"] = url
         repeat(pathList.size) {
             if (dlsmkHost == parseUrl.host) {
-                if (parseUrl.path.startsWith(restaurantPath)){
+                if (parseUrl.path.startsWith(restaurantPath)) {
                     result["permit"] = true
                     result["action"] = "dlsmkh5"
                     return result
@@ -213,4 +222,51 @@
         }
         return result
     }
+
+    override fun sendEmailCode(addressee: String, code: String, type: String): Boolean {
+        val subject: String
+        val subContent: String
+        when {
+            PortalConstant.MOBILE_EMAILCODE_BIND == type -> {
+                subject = "绑定大理市民卡邮箱验证"
+                subContent = "欢迎绑定大理市民卡app,请将验证码填写到绑定页面。"
+            }
+            PortalConstant.MOBILE_EMAILCODE_UNBIND == type -> {
+                subject = "解绑大理市民卡邮箱验证"
+                subContent = "您正在解除大理市民卡app邮箱绑定,请将验证码填写到解绑页面。"
+            }
+            else -> {
+                throw PortalBusinessException("发送邮箱验证码失败")
+            }
+        }
+        val content = "<div style=\"line-height:1.7;color:#000000;font-size:14px;font-family:Arial\"><div style=\"text-align: left; margin: 0px;\"></div><p style=\"text-align: center; color: rgb(64, 72, 91); font-family: -apple-system, &quot;Helvetica Neue&quot;, Helvetica, &quot;Nimbus Sans L&quot;, &quot;Segoe UI&quot;, Arial, &quot;Liberation Sans&quot;, &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, &quot;Hiragino Sans GB&quot;, &quot;Wenquanyi Micro Hei&quot;, &quot;WenQuanYi Zen Hei&quot;, &quot;ST Heiti&quot;, SimHei, &quot;WenQuanYi Zen Hei Sharp&quot;, sans-serif;\"><span style=\"font-family: Arial; font-size: 16px;\">" +
+                "您好&nbsp;</span><a data-auto-link=\"1\" href=\"mailto:${addressee}\" style=\"color: rgb(254, 115, 0); font-family: Arial; font-size: 16px;\">${addressee}</a><span style=\"font-family: Arial; font-size: 16px;\">!</span></p><p style=\"text-align: center; color: rgb(64, 72, 91); font-family: -apple-system, &quot;Helvetica Neue&quot;, Helvetica, &quot;Nimbus Sans L&quot;, &quot;Segoe UI&quot;, Arial, &quot;Liberation Sans&quot;, &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, &quot;Hiragino Sans GB&quot;, &quot;Wenquanyi Micro Hei&quot;, &quot;WenQuanYi Zen Hei&quot;, &quot;ST Heiti&quot;, SimHei, &quot;WenQuanYi Zen Hei Sharp&quot;, sans-serif;\"><span style=\"font-family: Arial; font-size: 16px;\">" +
+                "${subContent}</span></p><p style=\"text-align: center; color: rgb(64, 72, 91); font-family: -apple-system, &quot;Helvetica Neue&quot;, Helvetica, &quot;Nimbus Sans L&quot;, &quot;Segoe UI&quot;, Arial, &quot;Liberation Sans&quot;, &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, &quot;Hiragino Sans GB&quot;, &quot;Wenquanyi Micro Hei&quot;, &quot;WenQuanYi Zen Hei&quot;, &quot;ST Heiti&quot;, SimHei, &quot;WenQuanYi Zen Hei Sharp&quot;, sans-serif;\"><span style=\"font-family: Arial; font-size: 16px;\">验证码:${code}</span></p></div>"
+        return emailUtil.sendEmail(addressee, subject, content)
+    }
+
+    override fun sendSuccessEmail(addressee: String, mobile: String?): Boolean {
+        val content = "<div><div style=\"text-align: center; margin: 0px;\"><span style=\"color: rgb(64, 72, 91); font-family: Arial; font-size: 16px;\">" +
+                "Hi, 手机用户${mobile}</span></div><div style=\"text-align: center; margin: 0px;\"><span style=\"color: rgb(64, 72, 91); font-family: Arial; font-size: 16px;\"><br /></span></div><div style=\"text-align: center; color: rgb(64, 72, 91); font-family: -apple-system, &quot;Helvetica Neue&quot;, Helvetica, &quot;Nimbus Sans L&quot;, &quot;Segoe UI&quot;, Arial, &quot;Liberation Sans&quot;, &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, &quot;Hiragino Sans GB&quot;, &quot;Wenquanyi Micro Hei&quot;, &quot;WenQuanYi Zen Hei&quot;, &quot;ST Heiti&quot;, SimHei, &quot;WenQuanYi Zen Hei Sharp&quot;, sans-serif; font-size: 16px; margin: 5px 0px;\"><span style=\"font-family: Arial; font-size: 18px;\"><strong>" +
+                "您的 大理市民卡 账号已成功绑定邮箱&nbsp;</strong><strong><a data-auto-link=\"1\" href=\"mailto:${addressee}\" style=\"color: rgb(254, 115, 0);\">${addressee}</a>。</strong></span></div><div style=\"text-align: center; color: rgb(64, 72, 91); font-family: -apple-system, &quot;Helvetica Neue&quot;, Helvetica, &quot;Nimbus Sans L&quot;, &quot;Segoe UI&quot;, Arial, &quot;Liberation Sans&quot;, &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, &quot;Hiragino Sans GB&quot;, &quot;Wenquanyi Micro Hei&quot;, &quot;WenQuanYi Zen Hei&quot;, &quot;ST Heiti&quot;, SimHei, &quot;WenQuanYi Zen Hei Sharp&quot;, sans-serif; font-size: 16px; margin: 5px 0px;\"><span style=\"font-family: Arial; font-size: 18px;\"><strong><br /></strong></span></div><div style=\"text-align: center; color: rgb(64, 72, 91); font-family: -apple-system, &quot;Helvetica Neue&quot;, Helvetica, &quot;Nimbus Sans L&quot;, &quot;Segoe UI&quot;, Arial, &quot;Liberation Sans&quot;, &quot;PingFang SC&quot;, &quot;Microsoft YaHei&quot;, &quot;Hiragino Sans GB&quot;, &quot;Wenquanyi Micro Hei&quot;, &quot;WenQuanYi Zen Hei&quot;, &quot;ST Heiti&quot;, SimHei, &quot;WenQuanYi Zen Hei Sharp&quot;, sans-serif; font-size: 16px; margin: 5px 0px;\"><span style=\"font-family: Arial; text-align: start; font-size: 16px;\"><strong>" +
+                "Tips:</strong>如非本人操作,请及时检查账号邮箱绑定并修改密码。</span></div><br /><br /></div>"
+        return emailUtil.sendEmail(addressee, "大理市民卡 邮箱绑定通知", content)
+    }
+
+    override fun setUserEmail(uid: String, addressee: String?, type: String) {
+        val optional = mobileUserDao.findById(uid)
+        if (!optional.isPresent) {
+            throw PortalBusinessException("用户不存在,请注册")
+        }
+        val user = optional.get()
+        if (user.status != TradeDict.STATUS_NORMAL) {
+            throw PortalBusinessException("用户状态异常,操作失败")
+        }
+        if (PortalConstant.MOBILE_EMAILCODE_BIND == type) {
+            user.email = addressee
+        } else if (PortalConstant.MOBILE_EMAILCODE_UNBIND == type) {
+            user.email = null
+        }
+        mobileUserDao.save(user)
+    }
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/bean/UserSecurityRequestBean.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/bean/UserSecurityRequestBean.kt
new file mode 100644
index 0000000..912b174
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/bean/UserSecurityRequestBean.kt
@@ -0,0 +1,11 @@
+package com.supwisdom.dlpay.portal.bean
+
+import com.supwisdom.dlpay.portal.domain.TBUserSecurity
+
+class UserSecurityRequestBean {
+    var pwd: String = ""
+    var repwd: String = ""
+    var uid: String = ""
+    var answers: List<TBUserSecurity> = ArrayList()
+    var phone: String = ""
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/SecretSecurityDao.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/SecretSecurityDao.kt
new file mode 100644
index 0000000..98c7b9f
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/SecretSecurityDao.kt
@@ -0,0 +1,16 @@
+package com.supwisdom.dlpay.portal.dao
+
+import com.supwisdom.dlpay.portal.domain.TBSecretSecurity
+import org.springframework.data.jpa.repository.JpaRepository
+import org.springframework.data.jpa.repository.Query
+import org.springframework.stereotype.Repository
+
+@Repository
+interface SecretSecurityDao : JpaRepository<TBSecretSecurity, String> {
+
+    @Query("select t from TBSecretSecurity t order by t.sort")
+    fun findAllOrderBySort(): List<TBSecretSecurity>
+
+    @Query("select s from TBUserSecurity u,TBSecretSecurity s where u.uid=:uid and u.ssid = s.ssid order by s.sort")
+    fun findAllByUid(uid: String): List<TBSecretSecurity>
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/UserSecurityDao.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/UserSecurityDao.kt
new file mode 100644
index 0000000..a635f5d
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/UserSecurityDao.kt
@@ -0,0 +1,10 @@
+package com.supwisdom.dlpay.portal.dao
+
+import com.supwisdom.dlpay.portal.domain.TBUserSecurity
+import org.springframework.data.jpa.repository.JpaRepository
+
+interface UserSecurityDao : JpaRepository<TBUserSecurity, String> {
+    fun deleteByUid(uid: String)
+    fun findByUidAndSsid(uid: String, ssid: String): TBUserSecurity?
+    fun findByUid(uis: String): List<TBUserSecurity>
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..c03aae0
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/SecretSecurityServiceImpl.kt
@@ -0,0 +1,69 @@
+package com.supwisdom.dlpay.portal.service.Impl
+
+import com.supwisdom.dlpay.mobile.exception.PortalBusinessException
+import com.supwisdom.dlpay.portal.bean.UserSecurityRequestBean
+import com.supwisdom.dlpay.portal.dao.SecretSecurityDao
+import com.supwisdom.dlpay.portal.dao.UserSecurityDao
+import com.supwisdom.dlpay.portal.domain.TBSecretSecurity
+import com.supwisdom.dlpay.portal.service.SecretSecurityService
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Service
+
+@Service
+class SecretSecurityServiceImpl : SecretSecurityService {
+
+    @Autowired
+    lateinit var secretSecurityDao: SecretSecurityDao
+    @Autowired
+    lateinit var userSecurityDao: UserSecurityDao
+
+    override fun getAllSecretSecurity(): List<TBSecretSecurity> {
+        return secretSecurityDao.findAllOrderBySort()
+    }
+
+    override fun getSecretSecurityList(uid: String): List<TBSecretSecurity> {
+        return secretSecurityDao.findAllByUid(uid)
+    }
+
+    override fun saveUserSecurity(bean: UserSecurityRequestBean) {
+        val answers = bean.answers
+        if (answers.isNullOrEmpty() || answers.size < 3) {
+            throw PortalBusinessException("密保问题至少设置三个")
+        }
+        // 先删除用户之前所有的问题再保存
+        userSecurityDao.deleteByUid(bean.uid)
+        answers.forEach {
+            if (it.answer.isNullOrEmpty()) {
+                throw PortalBusinessException("密保答案不能为空")
+            }
+            if (it.answer.length < 2 || it.answer.length > 20) {
+                throw PortalBusinessException("密保答案长度必须为2-20个字符")
+            }
+            it.uid = bean.uid
+            userSecurityDao.save(it)
+        }
+    }
+
+    override fun checkUserSecurity(bean: UserSecurityRequestBean):String? {
+        val answers = bean.answers
+        if (answers.isNullOrEmpty() || answers.size < 3) {
+            throw PortalBusinessException("请至少回答三个密保问题")
+        }
+        answers.forEach {
+            val userSecurity = userSecurityDao.findByUidAndSsid(bean.uid, it.ssid)
+                    ?: throw PortalBusinessException("未找到密保问题")
+            if (it.answer != userSecurity.answer) {
+                return it.ssid
+            }
+        }
+        return null
+    }
+
+    override fun deleteUserSecurity(uid: String) {
+        val list = userSecurityDao.findByUid(uid)
+        if (list.isNullOrEmpty()) {
+            throw PortalBusinessException("您未设置密保问题,无需删除")
+        }
+        userSecurityDao.deleteAll(list)
+    }
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/SecretSecurityService.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/SecretSecurityService.kt
new file mode 100644
index 0000000..f525d2d
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/SecretSecurityService.kt
@@ -0,0 +1,19 @@
+package com.supwisdom.dlpay.portal.service
+
+import com.supwisdom.dlpay.portal.bean.UserSecurityRequestBean
+import com.supwisdom.dlpay.portal.domain.TBSecretSecurity
+import org.springframework.transaction.annotation.Transactional
+
+
+interface SecretSecurityService {
+    @Transactional
+    fun getAllSecretSecurity(): List<TBSecretSecurity>
+    @Transactional
+    fun getSecretSecurityList(uid:String): List<TBSecretSecurity>
+    @Transactional
+    fun saveUserSecurity(bean: UserSecurityRequestBean)
+    @Transactional
+    fun checkUserSecurity(bean: UserSecurityRequestBean):String?
+    @Transactional
+    fun deleteUserSecurity(uid: String)
+}
\ No newline at end of file
diff --git a/backend/src/main/resources/data-postgresql.sql b/backend/src/main/resources/data-postgresql.sql
index 3e2e5e2..d96584a 100644
--- a/backend/src/main/resources/data-postgresql.sql
+++ b/backend/src/main/resources/data-postgresql.sql
@@ -17,6 +17,9 @@
 INSERT INTO "tb_businesspara"("parakey", "paraval", "tenantid") VALUES ('outlets.currentno', '1', '{tenentid}');
 INSERT INTO "tb_businesspara"("parakey", "paraval", "tenantid") VALUES ('portal.appid', '800001', '{tenentid}');
 INSERT INTO "tb_businesspara"("parakey", "paraval", "tenantid") VALUES ('portal.secret', '1a8905a272364ef592e61f4a1288f07d', '{tenentid}');
+INSERT INTO "tb_businesspara"("parakey", "paraval", "tenantid") VALUES ('portal.email.address', '18227591821@163.com', '{tenentid}');
+INSERT INTO "tb_businesspara"("parakey", "paraval", "tenantid") VALUES ('portal.email.pwd', 'AVIGHTZYNSINDXZX', '{tenentid}');
+
 INSERT INTO "tb_businesspara"("parakey", "paraval", "tenantid") VALUES ('aes.cfb.totp.offset', '20', '{tenantid}');
 INSERT INTO "tb_businesspara"("parakey", "paraval", "tenantid") VALUES ('aes.cfb.rootkey', 'Vbb1syh8U1+CdLmTVGdtDiVvKBQ81n4GmgBEO/ohSbU=', '{tenantid}');
 INSERT INTO "tb_businesspara"("parakey", "paraval", "tenantid") VALUES ('aes.cfb.iv', '55b6f5b3287c535f8274b99354676d0e', '{tenantid}');
@@ -61,6 +64,16 @@
 INSERT INTO "tb_role_resource"("id", "addtime", "resid", "roleid") VALUES ('5f78e1eea840497c8b991323fab4541d', '20200923170348', '7e60ec8e741441fe9a2f351b5ab8c965', '20497f2fa27a44f7841492288ab75d88');
 INSERT INTO "tb_role_resource"("id", "addtime", "resid", "roleid") VALUES ('bb2f8e1a9aa74732bf1f35828ef8a87b', '20201027172455', '67291cc80fc148ceb939d98554977bc3', '20497f2fa27a44f7841492288ab75d88');
 
+INSERT INTO "tb_secret_security"("ssid", "createtime", "question", "sort") VALUES ('daab903757cf5d13a5152e8d5c739402', '20210105144236', '您目前的姓名是?', 1);
+INSERT INTO "tb_secret_security"("ssid", "createtime", "question", "sort") VALUES ('f79beb808d7352f5b7fee7521e784aa8', '20210105144236', '您配偶的生日是?', 2);
+INSERT INTO "tb_secret_security"("ssid", "createtime", "question", "sort") VALUES ('96dd8830465d5dd887a204339d1959ca', '20210105144236', '您的学号(或工号)是?', 3);
+INSERT INTO "tb_secret_security"("ssid", "createtime", "question", "sort") VALUES ('6397cb688bb346eeba5e2658fc2f8d18', '20210106160653', '您母亲的姓名是?', 4);
+INSERT INTO "tb_secret_security"("ssid", "createtime", "question", "sort") VALUES ('63d07c87e9554c6d95217d8606853772', '20210106160653', '您父亲的姓名是?', 5);
+INSERT INTO "tb_secret_security"("ssid", "createtime", "question", "sort") VALUES ('ac3810208d384b98a3def9c4097965f5', '20210106160653', '您的出生地是?', 6);
+INSERT INTO "tb_secret_security"("ssid", "createtime", "question", "sort") VALUES ('a9284c40d45e404899faf706187f5d52', '20210106160653', '您最喜欢的书籍是?', 7);
+INSERT INTO "tb_secret_security"("ssid", "createtime", "question", "sort") VALUES ('7c649b5d5773401fbadc68b34389729f', '20210106160653', '您最喜欢的食物是?', 8);
+INSERT INTO "tb_secret_security"("ssid", "createtime", "question", "sort") VALUES ('8721e26b78044749b75dc2bc19882c9f', '20210106160653', '您最熟悉的童年好友名字是?', 9);
+INSERT INTO "tb_secret_security"("ssid", "createtime", "question", "sort") VALUES ('b46cd79c01804250a2bedea089356406', '20210106160653', '您小学班主任的名字是?', 10);
 
 
 
diff --git a/config/application-devel-pg.properties b/config/application-devel-pg.properties
index d4e2dd0..e512487 100644
--- a/config/application-devel-pg.properties
+++ b/config/application-devel-pg.properties
@@ -20,7 +20,7 @@
 spring.redis.password=
 spring.redis.database=3
 #port
-server.port=7200
+server.port=8089
 # jwt settings
 jwt.secret=Zj5taLomEbrM0lk+NMQZbHfSxaDU1wekjT+kiC3YzDw=
 # timeout seconds