配合手机端增加文章、流水、留言相关接口
diff --git a/backend/src/main/java/com/supwisdom/dlpay/api/dao/PersonDaoRepository.java b/backend/src/main/java/com/supwisdom/dlpay/api/dao/PersonDaoRepository.java
new file mode 100644
index 0000000..d72573f
--- /dev/null
+++ b/backend/src/main/java/com/supwisdom/dlpay/api/dao/PersonDaoRepository.java
@@ -0,0 +1,12 @@
+package com.supwisdom.dlpay.api.dao;
+
+import com.supwisdom.dlpay.portal.bean.DtlGroupResultBean;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+public interface PersonDaoRepository {
+  BigDecimal getMonthTotalDtlByUserid(String userid, String month);
+
+  List<DtlGroupResultBean> getMonthDtlGroupByTransType(String userid, String month);
+}
diff --git a/backend/src/main/java/com/supwisdom/dlpay/api/dao/PersondtlDao.java b/backend/src/main/java/com/supwisdom/dlpay/api/dao/PersondtlDao.java
index 63e97ec..9c41010 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/api/dao/PersondtlDao.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/api/dao/PersondtlDao.java
@@ -8,6 +8,6 @@
 import org.springframework.stereotype.Repository;
 
 @Repository
-public interface PersondtlDao extends JpaRepository<TPersondtl, String>,JpaSpecificationExecutor<TPersondtl> {
-    Page<TPersondtl> findByUseridAndStatus(String userid, String status, Pageable pageable);
+public interface PersondtlDao extends JpaRepository<TPersondtl, String>,JpaSpecificationExecutor<TPersondtl>,PersonDaoRepository {
+    Page<TPersondtl> findByUseridAndStatusAndTransdateBetween(String userid, String status,String startdate,String enddate, Pageable pageable);
 }
diff --git a/backend/src/main/java/com/supwisdom/dlpay/api/dao/impl/PersonDaoRepositoryImpl.java b/backend/src/main/java/com/supwisdom/dlpay/api/dao/impl/PersonDaoRepositoryImpl.java
new file mode 100644
index 0000000..4a18b8f
--- /dev/null
+++ b/backend/src/main/java/com/supwisdom/dlpay/api/dao/impl/PersonDaoRepositoryImpl.java
@@ -0,0 +1,46 @@
+package com.supwisdom.dlpay.api.dao.impl;
+
+import com.supwisdom.dlpay.api.dao.PersonDaoRepository;
+import com.supwisdom.dlpay.framework.jpa.BaseRepository;
+import com.supwisdom.dlpay.portal.bean.DtlGroupResultBean;
+import org.hibernate.query.NativeQuery;
+import org.hibernate.transform.Transformers;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import java.math.BigDecimal;
+import java.util.List;
+
+public class PersonDaoRepositoryImpl extends BaseRepository implements PersonDaoRepository {
+  @PersistenceContext
+  private EntityManager em;
+
+  @SuppressWarnings("JpaQueryApiInspection")
+  @Override
+  public BigDecimal getMonthTotalDtlByUserid(String userid, String month) {
+    String sql = "select sum(cast(amount as decimal(18,2))) total from tb_persondtl where userid =:userid and transdate between :startdate and :enddate and status = 'success' and tradeflag = 'out'";
+    Query query = em.createNativeQuery(sql);
+    query.setParameter("userid", userid);
+    query.setParameter("startdate", month + "00");
+    query.setParameter("enddate", month + "32");
+    List list = query.getResultList();
+    if (list.size() > 0) {
+      return (BigDecimal) list.get(0);
+    }
+    return new BigDecimal(0);
+  }
+
+  @Override
+  @SuppressWarnings({"JpaQueryApiInspection", "deprecation", "unchecked"})
+  public List<DtlGroupResultBean> getMonthDtlGroupByTransType(String userid, String month) {
+    String sql = "select transdesc transtype,sum(cast(amount as decimal(18,2))) amount from tb_persondtl where userid =:userid and transdate between :startdate and :enddate and status = 'success' and tradeflag = 'out' group by transdesc";
+    Query query = em.createNativeQuery(sql);
+    query.setParameter("userid", userid);
+    query.setParameter("startdate", month + "00");
+    query.setParameter("enddate", month + "32");
+    NativeQuery nativeQuery = query.unwrap(NativeQuery.class);
+    nativeQuery.setResultTransformer(Transformers.aliasToBean(DtlGroupResultBean.class));
+    return nativeQuery.getResultList();
+  }
+}
diff --git a/backend/src/main/java/com/supwisdom/dlpay/portal/dao/impl/ArticleRepositoryImpl.java b/backend/src/main/java/com/supwisdom/dlpay/portal/dao/impl/ArticleRepositoryImpl.java
index 3d77024..1fc7264 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/portal/dao/impl/ArticleRepositoryImpl.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/portal/dao/impl/ArticleRepositoryImpl.java
@@ -9,10 +9,12 @@
 import com.supwisdom.dlpay.portal.domain.TBArticle;
 import org.hibernate.transform.Transformers;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.sql.Timestamp;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.util.List;
 
 public class ArticleRepositoryImpl  extends BaseRepository implements ArticleRepository {
   @NotNull
@@ -72,4 +74,14 @@
     }
     return findNative(f, Transformers.aliasToBean(TBArticle.class), pageno, pagesize);
   }
+
+  @NotNull
+  @Override
+  @SuppressWarnings("unchecked")
+  public List<TBArticle> getArticleListByColumnid(@NotNull String columnid,int pageno, int pagesize) {
+    String sql = "select * from tb_article where isdelete='0' and isdisplay='1' and status='released' and columnid =:columnid order by releasetime desc";
+    Finder f = Finder.create(sql);
+    f.setParameter("columnid", columnid);
+    return (List<TBArticle>) findNative(f, Transformers.aliasToBean(TBArticle.class), pageno, pagesize).getList();
+  }
 }
diff --git a/backend/src/main/java/com/supwisdom/dlpay/portal/dao/impl/FeedbackRepositoryImpl.java b/backend/src/main/java/com/supwisdom/dlpay/portal/dao/impl/FeedbackRepositoryImpl.java
index ef6c456..131fb49 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/portal/dao/impl/FeedbackRepositoryImpl.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/portal/dao/impl/FeedbackRepositoryImpl.java
@@ -16,17 +16,18 @@
   public Pagination getFeedbackList(@NotNull FeedbackSearchBean bean) {
     StringBuilder sql = new StringBuilder("select f.*,p.name username from tb_feedback f left join tb_person p on f.userid = p.userid where 1=1 ");
     String username = bean.getUsername();
-    String content = bean.getContent();
+    String title = bean.getTitle();
     String startdate = bean.getStartdate();
     String enddate = bean.getEnddate();
     String replystatus = bean.getReplystatus();
+    String mobileid = bean.getMobileid();
     int pageno = bean.getPageno();
     int pagesize = bean.getPagesize();
     if (!StringUtil.isEmpty(username)) {
       sql.append(" and p.name like :username");
     }
-    if (!StringUtil.isEmpty(content)) {
-      sql.append(" and f.content like :content");
+    if (!StringUtil.isEmpty(title)) {
+      sql.append(" and f.title like :title");
     }
     if (!StringUtil.isEmpty(startdate)) {
       sql.append(" and f.fbtime >= :startdate");
@@ -37,13 +38,16 @@
     if (!StringUtil.isEmpty(replystatus)) {
       sql.append(" and f.replystatus = :replystatus");
     }
+    if (!StringUtil.isEmpty(mobileid)) {
+      sql.append(" and f.mobileid = :mobileid");
+    }
     sql.append(" order by f.fbtime desc");
     Finder f = Finder.create(sql.toString());
     if (!StringUtil.isEmpty(username)) {
       f.setParameter("username", "%" + username.trim() + "%");
     }
-    if (!StringUtil.isEmpty(content)) {
-      f.setParameter("content", "%" + content.trim() + "%");
+    if (!StringUtil.isEmpty(title)) {
+      f.setParameter("title", "%" + title.trim() + "%");
     }
     if (!StringUtil.isEmpty(startdate)) {
       f.setParameter("startdate", startdate + "000000");
@@ -54,6 +58,9 @@
     if (!StringUtil.isEmpty(replystatus)) {
       f.setParameter("replystatus", replystatus);
     }
+    if (!StringUtil.isEmpty(mobileid)) {
+      f.setParameter("mobileid", mobileid);
+    }
     return findNative(f, Transformers.aliasToBean(TBFeedback.class), pageno, pagesize);
   }
 }
diff --git a/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBAnnex.java b/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBAnnex.java
index 729cc71..22d8b79 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBAnnex.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBAnnex.java
@@ -18,7 +18,7 @@
   private String fbid;
   @Column(name = "picid", length = 32)
   private String picid;
-  @Column(name = "minpicid", length = 14)
+  @Column(name = "minpicid", length = 32)
   private String minpicid;
 
   public String getAnnexid() {
diff --git a/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBArticle.java b/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBArticle.java
index 9c743eb..93ee786 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBArticle.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBArticle.java
@@ -62,6 +62,9 @@
   @Transient
   private String columnname;
 
+  @Transient
+  private String author;
+
 
   public String getArticleid() {
     return articleid;
@@ -182,4 +185,12 @@
   public void setColumnname(String columnname) {
     this.columnname = columnname;
   }
+
+  public String getAuthor() {
+    return author;
+  }
+
+  public void setAuthor(String author) {
+    this.author = author;
+  }
 }
diff --git a/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBColumn.java b/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBColumn.java
index 500ac27..2e9d3ae 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBColumn.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBColumn.java
@@ -3,6 +3,7 @@
 import org.hibernate.annotations.GenericGenerator;
 
 import javax.persistence.*;
+import java.util.List;
 
 @Entity
 @Table(name = "tb_column")
@@ -49,6 +50,9 @@
   @Transient
   private String parentname;
 
+  @Transient
+  private List<TBArticle> articles;
+
   public String getColumnid() {
     return columnid;
   }
@@ -128,4 +132,12 @@
   public void setOrdernum(Integer ordernum) {
     this.ordernum = ordernum;
   }
+
+  public List<TBArticle> getArticles() {
+    return articles;
+  }
+
+  public void setArticles(List<TBArticle> articles) {
+    this.articles = articles;
+  }
 }
diff --git a/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBFeedback.java b/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBFeedback.java
index 00db202..3372084 100644
--- a/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBFeedback.java
+++ b/backend/src/main/java/com/supwisdom/dlpay/portal/domain/TBFeedback.java
@@ -7,16 +7,21 @@
 
 @Entity
 @Table(name = "tb_feedback",
-        indexes = {@Index(name = "feedback_idx1",columnList = "userid"),
-        @Index(name = "feedback_idx2",columnList = "fbtime,replystatus")})
+    indexes = {@Index(name = "feedback_idx1", columnList = "userid"),
+        @Index(name = "feedback_idx2", columnList = "title,fbtime,replystatus"),
+        @Index(name = "feedback_idx3", columnList = "mobileid")})
 public class TBFeedback {
   @Id
   @GenericGenerator(name = "idGenerator", strategy = "uuid")
   @GeneratedValue(generator = "idGenerator")
   @Column(name = "fbid", nullable = false, length = 32)
   private String fbid;
-  @Column(name = "userid", nullable = false, length = 32)
+  @Column(name = "userid", length = 32)
   private String userid;
+  @Column(name = "mobileid", length = 32)
+  private String mobileid;
+  @Column(name = "title", nullable = false, length = 20)
+  private String title;
   @Column(name = "content", nullable = false, length = 200)
   private String content;
   @Column(name = "fbtime", length = 14)
@@ -29,6 +34,8 @@
   private String username;
   @Transient
   private List<TBAnnex> pictures;
+  @Transient
+  private TBReply reply;
 
   public String getFbid() {
     return fbid;
@@ -95,4 +102,28 @@
   public void setPictures(List<TBAnnex> pictures) {
     this.pictures = pictures;
   }
+
+  public String getTitle() {
+    return title;
+  }
+
+  public void setTitle(String title) {
+    this.title = title;
+  }
+
+  public String getMobileid() {
+    return mobileid;
+  }
+
+  public void setMobileid(String mobileid) {
+    this.mobileid = mobileid;
+  }
+
+  public TBReply getReply() {
+    return reply;
+  }
+
+  public void setReply(TBReply reply) {
+    this.reply = reply;
+  }
 }
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/UploadPicService.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/UploadPicService.kt
new file mode 100644
index 0000000..c52cd29
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/UploadPicService.kt
@@ -0,0 +1,10 @@
+package com.supwisdom.dlpay.api.service
+
+import org.springframework.transaction.annotation.Transactional
+import org.springframework.web.multipart.MultipartHttpServletRequest
+
+
+interface UploadPicService {
+    @Transactional
+    fun uploadPic(request: MultipartHttpServletRequest): Map<String, String>
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/UploadPicServiceImpl.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/UploadPicServiceImpl.kt
new file mode 100644
index 0000000..4f56960
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/UploadPicServiceImpl.kt
@@ -0,0 +1,204 @@
+package com.supwisdom.dlpay.api.service.impl
+
+import com.supwisdom.dlpay.api.service.UploadPicService
+import com.supwisdom.dlpay.framework.service.SystemUtilService
+import com.supwisdom.dlpay.framework.util.StringUtil
+import com.supwisdom.dlpay.portal.util.PortalConstant
+import com.supwisdom.dlpay.portal.util.PutImageToServer
+import mu.KotlinLogging
+import net.coobird.thumbnailator.Thumbnails
+import org.springframework.beans.factory.annotation.Autowired
+import org.springframework.stereotype.Service
+import org.springframework.web.multipart.MultipartFile
+import org.springframework.web.multipart.MultipartHttpServletRequest
+import java.io.File
+import java.io.FileInputStream
+import java.io.IOException
+import java.util.HashMap
+import kotlin.math.sqrt
+
+@Service
+class UploadPicServiceImpl : UploadPicService{
+    @Autowired
+    lateinit var systemUtilService: SystemUtilService
+
+    val logger = KotlinLogging.logger { }
+
+    @Suppress("NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS",
+            "RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS")
+    override fun uploadPic(request: MultipartHttpServletRequest): Map<String, String> {
+        val map: MutableMap<String, String> = HashMap()
+        try {
+            logger.error("===put up pic")
+            val pathDir = "/files/"
+            /** 得到图片保存目录的真实路径  */
+            val logoRealPathDir = request.session.servletContext
+                    .getRealPath(pathDir)
+            /** 根据真实路径创建目录  */
+            val logoSaveFile = File(logoRealPathDir)
+            if (!logoSaveFile.exists()) logoSaveFile.mkdirs()
+            /** 页面控件的文件流  */
+            val multipartFile: MultipartFile = request.getFile("file")
+            //System.out.println(multipartFile);
+            /* 获取文件的后缀 */
+            val suffix = multipartFile.originalFilename.substring(
+                    multipartFile.originalFilename.lastIndexOf("."))
+            //* 拼成完整的文件保存路径加文件 *//*
+            val name = System.currentTimeMillis().toString() + suffix
+            val fileName = logoRealPathDir + File.separator + name
+            val file = File(fileName)
+            // String data = file.getPath();
+            try {
+                multipartFile.transferTo(file)
+            } catch (e: IllegalStateException) {
+                logger.error("error:", e)
+            } catch (e: IOException) {
+                logger.error("error:", e)
+            }
+            //savePicToDb(fileName, goodsidString);*/
+            //String filepath = logoRealPathDir + File.separator + multipartFile.getOriginalFilename();
+            val size = multipartFile.size
+            var maxSize = 200 * 1024.toLong()
+            val imageMaxConf = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_IMAGE_MAXSIZE)
+            if (imageMaxConf != null && !StringUtil.isEmpty(imageMaxConf)) {
+                try {
+                    maxSize = imageMaxConf.toLong()
+                } catch (e: Exception) {
+                }
+            }
+            var minwidth = 150
+            var minheight = 150
+            val imageMinConf = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_IMAGE_MINISIZE)
+            if (imageMinConf != null && !StringUtil.isEmpty(imageMinConf)) {
+                try {
+                    val temps = imageMinConf.split(",")
+                    if (temps.size == 2) {
+                        minwidth = temps[0].toInt()
+                        minheight = temps[1].toInt()
+                    }
+                } catch (e: Exception) {
+                    logger.error("error:", e)
+                }
+            }
+            logger.error("===put up pic 1")
+            var baseURL = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_IMAGE_URLASSIGN)
+            if (baseURL == null || StringUtil.isEmpty(baseURL)) {
+                map["retmsg"] = "系统未配置图片服务器地址,无法保存图片!"
+                map["retcode"] = "1"
+                return map
+            }
+            if (!baseURL.startsWith("http")) {
+                baseURL = "http://$baseURL"
+            }
+            if (baseURL.endsWith("/")) {
+                baseURL = baseURL.substring(0, baseURL.length - 1)
+            }
+            logger.error("===put up pic 2")
+            var minImageURL: String
+            val picid: String
+            val minpicid: String
+            var imageURL = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_IMAGE_URLPUSH)
+            if (imageURL == null || StringUtil.isEmpty(imageURL)) {
+                map["retmsg"] = "系统未配置图片服务器地址,无法保存图片!"
+                map["retcode"] = "1"
+                return map
+            }
+            logger.error("===put up pic 4")
+            if (!imageURL.startsWith("http")) {
+                imageURL = "http://$imageURL"
+            }
+            if (!imageURL.endsWith("/")) {
+                imageURL += "/"
+            }
+            minImageURL = imageURL
+            if (!minImageURL.startsWith("http")) {
+                minImageURL = "http://$minImageURL"
+            }
+            if (!minImageURL.endsWith("/")) {
+                minImageURL += "/"
+            }
+            val assign = "$baseURL/dir/assign?count=2"
+            val bean = PutImageToServer.postBefore(assign)
+            if (bean == null || StringUtil.isEmpty(bean.getFid())) {
+                map["retmsg"] = "图片上传失败,无法连接图片服务器!"
+                map["retcode"] = "1"
+                return map
+            }
+            imageURL += bean.fid
+            picid = bean.fid
+            val minImgBean = PutImageToServer.postBefore(assign)
+            if (minImgBean == null || StringUtil.isEmpty(minImgBean.getFid())) {
+                map["retmsg"] = "图片上传失败,无法连接图片服务器!"
+                map["retcode"] = "1"
+                return map
+            }
+            minpicid = minImgBean.fid
+            minImageURL += minImgBean.fid
+            //}
+            val processToFile = logoRealPathDir + File.separator + picid + ".jpg"
+            var scale = 1.0
+            if (size > 0 && size > maxSize) {
+                scale = maxSize * 1.0 / size
+            }
+            try {
+                if (scale < 0.7) {
+                    val s = sqrt(maxSize.toDouble()).toInt()
+                    Thumbnails.of(fileName).size(s, s).outputFormat("jpg").toFile(processToFile)
+                } else {
+                    Thumbnails.of(fileName).scale(1.0).outputQuality(scale).outputFormat("jpg").toFile(processToFile)
+                }
+                logger.error("===put up pic 5")
+            } catch (e: Exception) {
+                map["retmsg"] = "图片上传失败,压缩失败"
+                map["retcode"] = "1"
+                file.delete()
+                return map
+            }
+            val file1 = File(processToFile)
+            val processToMinFile = logoRealPathDir + File.separator + minpicid + ".jpg"
+            try {
+                Thumbnails.of(fileName).size(minwidth, minheight).outputFormat("jpg").toFile(processToMinFile)
+            } catch (e: Exception) {
+                map["retmsg"] = "图片上传失败,压缩失败"
+                map["retcode"] = "1"
+                file.delete()
+                file1.delete()
+                return map
+            }
+            val file2 = File(processToMinFile)
+            logger.error("==========imageurl=$imageURL")
+            if (!PutImageToServer.postImage(imageURL, FileInputStream(file1))) {
+                map["retmsg"] = "图片上传失败,请稍后再试"
+                map["retcode"] = "1"
+                file1.delete()
+                file2.delete()
+                file.delete()
+                logger.error("=step1 fail=")
+                return map
+            }
+            if (!PutImageToServer.postImage(minImageURL, FileInputStream(file2))) {
+                map["retmsg"] = "图片上传失败,请稍后再试"
+                map["retcode"] = "1"
+                file1.delete()
+                file2.delete()
+                file.delete()
+                logger.error("=step2 fail=")
+            } else {
+                map["retmsg"] = "上传成功"
+                map["retcode"] = "0"
+                file1.delete()
+                file2.delete()
+                file.delete()
+                map["picid"] = picid
+                map["minpicid"] = minpicid
+                logger.error("=step3 success=")
+            }
+        } catch (e: Exception) {
+            map["retmsg"] = "图片上传失败--" + e.message
+            map["retcode"] = "1"
+            // return map;
+            logger.error("=step fail=", e)
+        }
+        return map
+    }
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/user_service_impl.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/user_service_impl.kt
index 45c0d3f..243364b 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/user_service_impl.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/impl/user_service_impl.kt
@@ -47,9 +47,9 @@
                 ?: throw TransactionProcessException(TradeErrorCode.ACCOUNT_NOT_EXISTS, "用户<$userid>不存在")
     }
 
-    override fun findPersondtlByUserid(userid: String,pageno:Int): PageResult<TPersondtl> {
+    override fun findPersondtlByUserid(userid: String,pageno:Int,month:String): PageResult<TPersondtl> {
         var pageable = PageRequest.of(pageno - 1, 10, Sort.Direction.DESC, "transdate","transtime")
-        return PageResult<TPersondtl>(persondtlDao.findByUseridAndStatus(userid,TradeDict.DTL_STATUS_SUCCESS,pageable))
+        return PageResult<TPersondtl>(persondtlDao.findByUseridAndStatusAndTransdateBetween(userid,TradeDict.DTL_STATUS_SUCCESS,month+"00",month+"32",pageable))
     }
 
     override fun findPersondtlDetailByUserid(userid: String, billno: String): TPersondtl? {
@@ -63,4 +63,17 @@
             return null
         }
     }
+
+    override fun findDtlMonthCountByUserid(userid: String, month: String): Map<String, Any>? {
+        val total = persondtlDao.getMonthTotalDtlByUserid(userid, month)
+        val group = persondtlDao.getMonthDtlGroupByTransType(userid, month)
+        val result = HashMap<String,Any>()
+        if (total == null) {
+            result["total"] = 0
+        } else {
+            result["total"] = total
+            result["group"] = group
+        }
+        return result
+    }
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/user_service.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/user_service.kt
index 302db65..810a2f7 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/user_service.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/api/service/user_service.kt
@@ -19,9 +19,11 @@
     fun findOnePersonByUserid(userid: String): TPerson
 
     @Transactional(propagation = Propagation.REQUIRED, rollbackFor = arrayOf(Exception::class), readOnly = true)
-    fun findPersondtlByUserid(userid:String, pageno :Int) : PageResult<TPersondtl>
+    fun findPersondtlByUserid(userid:String, pageno :Int,month:String) : PageResult<TPersondtl>
 
     @Transactional(propagation = Propagation.REQUIRED, rollbackFor = arrayOf(Exception::class), readOnly = true)
     fun findPersondtlDetailByUserid(userid:String, billno :String) : TPersondtl?
 
+    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = arrayOf(Exception::class), readOnly = true)
+    fun findDtlMonthCountByUserid(userid:String, month :String) : Map<String,Any>?
 }
\ No newline at end of file
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 c795cbd..1aad98a 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/AuthLoginHandler.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/AuthLoginHandler.kt
@@ -13,6 +13,7 @@
 import com.supwisdom.dlpay.mobile.domain.TBMobileUser
 import com.supwisdom.dlpay.mobile.exception.UserLoginFailException
 import com.supwisdom.dlpay.mobile.service.MobileApiService
+import com.supwisdom.dlpay.portal.util.PortalConstant
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.http.HttpStatus
 import org.springframework.security.authentication.BadCredentialsException
@@ -90,6 +91,7 @@
                     signed = TradeDict.STATUS_YES
                 }
             }
+            val imageUrl = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_IMAGE_URLPUSH)
             response.status = HttpStatus.OK.value()
             response.contentType = "application/json;charset=UTF-8"
             response.writer.write(objectMapper.writeValueAsString(JsonResult.ok()
@@ -102,6 +104,7 @@
                     ?.put("phone", StringUtil.phoneReplace(user.phone))
                     ?.put("paypwdset",payseted)
                     ?.put("signed", signed)
+                    ?.put("imageurl",imageUrl)
                     ?.put("userid",if(user.userid.isNullOrEmpty()) "" else user.userid)))
         } else {
             throw UserLoginFailException("登录错误")
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 2649949..b3149a1 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/MobileApi.kt
@@ -4,6 +4,7 @@
 import com.supwisdom.dlpay.agent.service.CitizencardPayService
 import com.supwisdom.dlpay.api.bean.JsonResult
 import com.supwisdom.dlpay.api.service.QRCodeService
+import com.supwisdom.dlpay.api.service.UploadPicService
 import com.supwisdom.dlpay.api.service.UserService
 import com.supwisdom.dlpay.api.util.MobileNumberCheck
 import com.supwisdom.dlpay.framework.core.JwtConfig
@@ -13,8 +14,17 @@
 import com.supwisdom.dlpay.framework.service.SystemUtilService
 import com.supwisdom.dlpay.framework.util.*
 import com.supwisdom.dlpay.framework.util.Dictionary
+import com.supwisdom.dlpay.mobile.bean.ActivitiesBean
+import com.supwisdom.dlpay.mobile.bean.FeedbackBean
 import com.supwisdom.dlpay.mobile.domain.TBMobileUser
+import com.supwisdom.dlpay.mobile.exception.PortalBusinessException
 import com.supwisdom.dlpay.mobile.service.MobileApiService
+import com.supwisdom.dlpay.portal.bean.FeedbackSearchBean
+import com.supwisdom.dlpay.portal.domain.TBFeedback
+import com.supwisdom.dlpay.portal.service.ArticleService
+import com.supwisdom.dlpay.portal.service.ColumnService
+import com.supwisdom.dlpay.portal.service.FeedbackService
+import com.supwisdom.dlpay.portal.util.PortalConstant
 import com.supwisdom.dlpay.system.service.DictionaryProxy
 import com.supwisdom.dlpay.util.ConstantUtil
 import com.supwisdom.dlpay.util.RSAKeysGenerate
@@ -29,13 +39,14 @@
 import org.springframework.security.core.authority.AuthorityUtils
 import org.springframework.security.core.context.SecurityContextHolder
 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
-import org.springframework.web.bind.annotation.RequestHeader
-import org.springframework.web.bind.annotation.RequestMapping
-import org.springframework.web.bind.annotation.RequestParam
-import org.springframework.web.bind.annotation.RestController
+import org.springframework.web.bind.annotation.*
 import org.springframework.web.multipart.MultipartFile
+import org.springframework.web.multipart.MultipartHttpServletRequest
 import java.time.Duration
 import java.util.*
+import java.util.stream.Collectors
+import javax.servlet.http.HttpServletRequest
+import kotlin.collections.HashMap
 
 
 @RestController
@@ -51,6 +62,10 @@
     lateinit var apiJwtRepository: ApiJwtRepository
     @Autowired
     lateinit var systemUtilService: SystemUtilService
+    @Autowired
+    lateinit var columnService: ColumnService
+    @Autowired
+    lateinit var articleService: ArticleService
     val logger = KotlinLogging.logger { }
 
     @RequestMapping("/time")
@@ -140,7 +155,7 @@
             }
             user.status = TradeDict.STATUS_NORMAL
             user.registerplatform = platform
-            if(!user.registerplatform.isNullOrEmpty()){
+            if (!user.registerplatform.isNullOrEmpty()) {
                 user.lastloginplatform = user.registerplatform!!.split(",")[1]
             }
             user.devuid = uuid
@@ -238,12 +253,72 @@
     }
 
     /**
-     *
-     * 小程序认证接口
-     *
-     * */
+     * 获取多个栏目活动
+     */
+    @RequestMapping("/activity")
+    fun getActivity(@RequestHeader("Authorization") auth: String?,
+                    @RequestBody bean: List<ActivitiesBean>): JsonResult? {
+        val mode = judgeLoginMode(auth)
+        val codes = bean.stream().map(ActivitiesBean::code).collect(Collectors.toList())
+        val columnList = columnService.getColumnListByCode(codes)
+        if (columnList == null || columnList.isEmpty()) {
+            return JsonResult.error("未知的栏目code!")
+        }
+        if (mode == "tourist") {
+            columnList.forEach {
+                if (it.ispublic == PortalConstant.NO) {
+                    return JsonResult.error("游客模式不能浏览非公开栏目,请登录!")
+                }
+            }
+        }
+        val beanMap: HashMap<String, ActivitiesBean> = HashMap()
+        bean.forEach {
+            beanMap[it.code] = it
+        }
+        val data = articleService.getArticleByColumn(columnList, beanMap)
+        return JsonResult.ok().put("data", data)
+    }
 
+    /**
+     * 获取活动详情
+     */
+    @RequestMapping("/activity/{articleno}")
+    fun getActivity(@RequestHeader("Authorization") auth: String?,
+                    @PathVariable("articleno") articleno: String): JsonResult? {
+        try {
+            val mode = judgeLoginMode(auth)
+            var article = articleService.getMobileArticle(articleno)
+            val column = columnService.getColunmByid(article.columnid)
+                    ?: return JsonResult.error("文章所属栏目不存在")
+            if (mode == "tourist") {
+                if (column.ispublic == PortalConstant.NO) {
+                    return JsonResult.error("游客模式不能浏览非公开栏目,请登录!")
+                }
+            }
+            article = articleService.addArticleHits(article)
+            return JsonResult.ok().put("data", article)
+        } catch (e: Exception) {
+            logger.error { e.message }
+            if (e is PortalBusinessException) {
+                return JsonResult.error(e.message)
+            }
+            return JsonResult.error("服务器繁忙,请稍后重试")
+        }
+    }
 
+    fun judgeLoginMode(auth: String?): String {
+        var mode = "tourist"
+        if (auth != null) {
+            val p = SecurityContextHolder.getContext().authentication
+            if (p != null) {
+                val user = mobileApiService.findUserById(p.name)
+                if (user != null) {
+                    mode = "logged"
+                }
+            }
+        }
+        return mode
+    }
 }
 
 
@@ -265,7 +340,11 @@
     @Autowired
     lateinit var jwtConfig: JwtConfig
     @Autowired
-    lateinit var qrcodeService:QRCodeService
+    lateinit var qrcodeService: QRCodeService
+    @Autowired
+    lateinit var uploadPicService: UploadPicService
+    @Autowired
+    lateinit var feedbackService: FeedbackService
     val logger = KotlinLogging.logger { }
 
     @RequestMapping("/idtypes")
@@ -294,14 +373,14 @@
         val p = SecurityContextHolder.getContext().authentication
         var user = mobileApiService.findUserById(p.name)
                 ?: return JsonResult.error("用户不存在,请注册")
-        var tk= ""
+        var tk = ""
         if (!user.jti.isNullOrEmpty()) {
             var opt = apiJwtRepository.findById(user.jti!!)
-            if(opt.isPresent){
-                var jwt =  opt.get()
+            if (opt.isPresent) {
+                var jwt = opt.get()
                 val cur = System.currentTimeMillis()
                 //token 小于12个小时,则更新它
-                if(jwt.expiration-cur<1000*60*60*12){
+                if (jwt.expiration - cur < 1000 * 60 * 60 * 12) {
                     val token = JwtTokenUtil(jwtConfig).generateToken(
                             mapOf("uid" to user.uid, "issuer" to "payapi",
                                     "audience" to user.loginid,
@@ -328,6 +407,7 @@
         return JsonResult.ok("OK").put("now", System.currentTimeMillis())
                 ?.put("token", tk)!!
     }
+
     /**
      * 验证码生成,内部校验
      * */
@@ -418,9 +498,9 @@
             return JsonResult.error(resp.message)
         }
         var needupdate = false
-        if (resp.sinstatus == YnrccUtil.TRANSTYPE_SIGNCARD ) {
+        if (resp.sinstatus == YnrccUtil.TRANSTYPE_SIGNCARD) {
             signed = TradeDict.STATUS_YES
-            if(!card.signed){
+            if (!card.signed) {
                 card.signed = true
                 mobileApiService.saveCard(card)
             }
@@ -429,12 +509,12 @@
             needupdate = true;
 
         }
-        if( user.userid.isNullOrEmpty()){
+        if (user.userid.isNullOrEmpty()) {
             user.userid = person.userid
             user.bindtime = DateUtil.getNow()
-            needupdate=true
+            needupdate = true
         }
-        if(needupdate){
+        if (needupdate) {
             mobileApiService.saveUser(user)
         }
         var payseted = false
@@ -449,6 +529,7 @@
 
 
     }
+
     /**
      * 绑卡
      * */
@@ -464,16 +545,16 @@
                 ?: return JsonResult.error("卡片不存在,请重新绑定")
         //call sign api
         val person = userService.findOnePersonByUserid(card.userid)
-        var signed=""
+        var signed = ""
         //call api
         var resp = citizencardPayService.bindCard(card.cardno, person.name, person.idtype, person.idno, user.phone!!)
         if (resp.code != "0000") {
             return JsonResult.error(resp.message)
         }
 
-        if (resp.sinstatus == YnrccUtil.TRANSTYPE_SIGNCARD ) {
+        if (resp.sinstatus == YnrccUtil.TRANSTYPE_SIGNCARD) {
             signed = TradeDict.STATUS_YES
-            if(!card.signed){
+            if (!card.signed) {
                 card.signed = true
                 mobileApiService.saveCard(card)
             }
@@ -600,7 +681,7 @@
             //call sign api
             val person = userService.findOnePersonByUserid(card.userid)
             val captcha = agree//此处为验证码,暂由此参数代替
-            var resp = citizencardPayService.signCard(card.cardno, person.name, person.idtype, person.idno, user.phone!!, YnrccUtil.TRANSTYPE_SIGNCARD,captcha)
+            var resp = citizencardPayService.signCard(card.cardno, person.name, person.idtype, person.idno, user.phone!!, YnrccUtil.TRANSTYPE_SIGNCARD, captcha)
             if (resp.code != "0000") {
                 return JsonResult.error(resp.message)
             }
@@ -620,7 +701,7 @@
      * 查询账单
      * */
     @RequestMapping("/bills")
-    fun bills(pageno: Int,platform: String?,type:String?): JsonResult {
+    fun bills(pageno: Int, month: String, platform: String?, type: String?): JsonResult {
         val p = SecurityContextHolder.getContext().authentication
         val user = mobileApiService.findUserById(p.name)
                 ?: return JsonResult.error("用户不存在,请注册")
@@ -665,58 +746,58 @@
                 signed = TradeDict.STATUS_YES
             }
         }
-        var version:String?=""
-        var minversion:String?=""
-        var versionmsg:String?=""
-        var versionurl:String?=""
-        if(!platform.isNullOrEmpty()){
-            var map= mutableMapOf<String,Any>()
-            var key=""
-            if(platform.toLowerCase().contains("android")){
-                key="androidapp"
-                if(!type.isNullOrEmpty()){
-                    key+= "_$type"
+        var version: String? = ""
+        var minversion: String? = ""
+        var versionmsg: String? = ""
+        var versionurl: String? = ""
+        if (!platform.isNullOrEmpty()) {
+            val map: MutableMap<String, Any>
+            var key: String
+            if (platform.toLowerCase().contains("android")) {
+                key = "androidapp"
+                if (!type.isNullOrEmpty()) {
+                    key += "_$type"
                 }
                 dictionaryProxy.refreshDictionary(key)
                 map = dictionaryProxy.getDictionaryAsMap(key)
-                if(map["androidversion"]!=null){
+                if (map["androidversion"] != null) {
                     version = map["androidversion"] as String
                 }
-                if(map["androidminversion"]!=null){
+                if (map["androidminversion"] != null) {
                     minversion = map["androidminversion"] as String
                 }
-                if(map["androidversionmsg"]!=null){
+                if (map["androidversionmsg"] != null) {
                     versionmsg = map["androidversionmsg"] as String
                 }
-                if(map["androidversionurl"]!=null){
+                if (map["androidversionurl"] != null) {
                     versionurl = map["androidversionurl"] as String
                 }
 
-            }else if(platform.toLowerCase().contains("iphone")){
-                key="iosapp"
-                if(!type.isNullOrEmpty()){
-                    key+= "_$type"
+            } else if (platform.toLowerCase().contains("iphone")) {
+                key = "iosapp"
+                if (!type.isNullOrEmpty()) {
+                    key += "_$type"
                 }
                 dictionaryProxy.refreshDictionary(key)
                 map = dictionaryProxy.getDictionaryAsMap(key)
-                if(map["iosversion"]!=null){
+                if (map["iosversion"] != null) {
                     version = map["iosversion"] as String
                 }
-                if(map["iosminversion"]!=null){
+                if (map["iosminversion"] != null) {
                     minversion = map["iosminversion"] as String
                 }
-                if(map["iosversionmsg"]!=null){
+                if (map["iosversionmsg"] != null) {
                     versionmsg = map["iosversionmsg"] as String
                 }
-                if(map["iosversionurl"]!=null){
+                if (map["iosversionurl"] != null) {
                     versionurl = map["iosversionurl"] as String
                 }
             }
 
         }
 
-        var name = person.name
-        val page = userService.findPersondtlByUserid(user.userid!!, no)
+        val name = person.name
+        val page = userService.findPersondtlByUserid(user.userid!!, no, month)
         return JsonResult.ok("OK").put("page", page)
                 ?.put("today", today)
                 ?.put("yesterday", yester)
@@ -726,14 +807,29 @@
                 ?.put("name", name)
                 ?.put("needrebind", needrebind)
                 ?.put("signed", signed)
-                ?.put("version",version)
-                ?.put("minversion",minversion)
-                ?.put("versionmsg",versionmsg)
-                ?.put("versionurl",versionurl)
+                ?.put("version", version)
+                ?.put("minversion", minversion)
+                ?.put("versionmsg", versionmsg)
+                ?.put("versionurl", versionurl)
                 ?.put("userid", if (user.userid.isNullOrEmpty()) "" else user.userid)!!.put("t", t)!!
     }
 
     /**
+     * 获取某月支出统计
+     */
+    @RequestMapping("/billcount")
+    fun billCount(month: String): JsonResult? {
+        val p = SecurityContextHolder.getContext().authentication
+        val user: TBMobileUser = mobileApiService.findUserById(p.name)
+                ?: return JsonResult.error("用户不存在,请注册")
+        if (user.userid.isNullOrEmpty()) {
+            return JsonResult.ok("OK")
+        }
+        val data = userService.findDtlMonthCountByUserid(user.userid!!, month)
+        return JsonResult.ok().put("data", data)
+    }
+
+    /**
      * 账单明细
      * */
     @RequestMapping("/billdetail")
@@ -862,9 +958,9 @@
         val user = mobileApiService.findUserById(p.name)
                 ?: return JsonResult.error("用户不存在,请注册")
         val resp = qrcodeService.encodeCode(user.uid)
-        return if(resp.retcode==0){
+        return if (resp.retcode == 0) {
             JsonResult.ok("ok").put("qrcode", resp.retmsg)!!
-        }else{
+        } else {
             JsonResult.error(resp.retmsg)
         }
     }
@@ -885,7 +981,7 @@
             //call sign api
             val person = userService.findOnePersonByUserid(card.userid)
             val captcha = ""//此处为验证码,暂由此参数代替
-            var resp = citizencardPayService.signCard(card.cardno, person.name, person.idtype, person.idno, user.phone!!, YnrccUtil.TRANSTYPE_UNSIGNCARD,captcha)
+            var resp = citizencardPayService.signCard(card.cardno, person.name, person.idtype, person.idno, user.phone!!, YnrccUtil.TRANSTYPE_UNSIGNCARD, captcha)
             if (resp.code != "0000") {
                 return JsonResult.error(resp.message)
             }
@@ -907,7 +1003,7 @@
         val p = SecurityContextHolder.getContext().authentication
         val user = mobileApiService.findUserById(p.name)
                 ?: return JsonResult.error("用户不存在,请注册")
-        if(user.paypwd.isNullOrEmpty()){
+        if (user.paypwd.isNullOrEmpty()) {
             return JsonResult.error("支付密码未设置,请先设置")
         }
         val paypwdtimes = user.checkPaypwdtime()
@@ -943,7 +1039,7 @@
     @RequestMapping("/uploadphoto")
     fun uploadPhoto(file: MultipartFile): JsonResult {
         val p = SecurityContextHolder.getContext().authentication
-        val user = mobileApiService.findUserById(p.name)
+        mobileApiService.findUserById(p.name)
                 ?: return JsonResult.error("用户不存在,请注册")
         /**
          * TODO
@@ -951,7 +1047,71 @@
          * 2.没有头像上传
          * 3.返回图片ID
          *
-        */
+         */
         return JsonResult.ok("OK")
     }
+
+    /**
+     * 上传图片接口
+     */
+    @RequestMapping(value = ["/uploadpic"], method = [RequestMethod.POST])
+    fun uploadPic(request: MultipartHttpServletRequest): JsonResult? {
+        val map = uploadPicService.uploadPic(request)
+        return if (map["retcode"] == "0") {
+            JsonResult.ok().put("picid", map["picid"])
+                    ?.put("minpicid", map["minpicid"])
+        } else {
+            JsonResult.error(map["retmsg"])
+        }
+    }
+
+    /**
+     * 发布留言
+     */
+    @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 feedback = TBFeedback().apply {
+            this.title = bean.title
+            this.content = bean.content
+            this.fbip = request.remoteAddr
+            this.mobileid = user.uid
+        }
+        if (!user.userid.isNullOrEmpty()) {
+            feedback.userid = user.userid
+        }
+        feedbackService.saveFeedback(feedback,bean.pictures)
+        return JsonResult.ok()
+    }
+
+    /**
+     * 查询留言列表接口
+     */
+    @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 data = feedbackService.getFeedbackList(FeedbackSearchBean().apply {
+            this.pageno = pageno
+            this.pagesize = pagesize
+            this.mobileid = user.uid
+        })
+        return JsonResult.ok().put("data",data)
+    }
+
+    /**
+     * 查询单条留言详情
+    */
+    @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 feedback = feedbackService.getFeedbackDetail(fbid, user.uid)
+                ?: return JsonResult.error("未找到该条留言")
+        return JsonResult.ok().put("data",feedback)
+    }
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/bean/ActivitiesBean.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/bean/ActivitiesBean.kt
new file mode 100644
index 0000000..5b772b9
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/bean/ActivitiesBean.kt
@@ -0,0 +1,7 @@
+package com.supwisdom.dlpay.mobile.bean
+
+class ActivitiesBean {
+    var code: String = ""
+    var pagesize: Int = 0
+    var pageno:Int = 0
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/bean/FeedbackBean.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/bean/FeedbackBean.kt
new file mode 100644
index 0000000..c2d8c05
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/bean/FeedbackBean.kt
@@ -0,0 +1,7 @@
+package com.supwisdom.dlpay.mobile.bean
+
+class FeedbackBean {
+    var title: String = ""
+    var content: String = ""
+    var pictures: List<PictureBean> = ArrayList()
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/bean/PictureBean.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/bean/PictureBean.kt
new file mode 100644
index 0000000..c760794
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/bean/PictureBean.kt
@@ -0,0 +1,6 @@
+package com.supwisdom.dlpay.mobile.bean
+
+class PictureBean {
+    var picid: String = ""
+    var minpicid: String = ""
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/exception/PortalBusinessException.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/exception/PortalBusinessException.kt
new file mode 100644
index 0000000..a6dc025
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/mobile/exception/PortalBusinessException.kt
@@ -0,0 +1,6 @@
+package com.supwisdom.dlpay.mobile.exception
+
+import java.lang.RuntimeException
+
+class PortalBusinessException(msg: String) : RuntimeException(msg) {
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/PortalApi.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/PortalApi.kt
index 3971f40..8860dc1 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/PortalApi.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/PortalApi.kt
@@ -1,12 +1,14 @@
 package com.supwisdom.dlpay.portal
 
 import com.supwisdom.dlpay.api.bean.JsonResult
+import com.supwisdom.dlpay.api.service.UploadPicService
 import com.supwisdom.dlpay.framework.core.JwtConfig
 import com.supwisdom.dlpay.framework.core.JwtTokenUtil
 import com.supwisdom.dlpay.framework.redisrepo.ApiJwtRepository
 import com.supwisdom.dlpay.framework.service.OperatorDetailService
 import com.supwisdom.dlpay.framework.service.SystemUtilService
 import com.supwisdom.dlpay.framework.util.StringUtil
+import com.supwisdom.dlpay.mobile.exception.PortalBusinessException
 import com.supwisdom.dlpay.portal.bean.*
 import com.supwisdom.dlpay.portal.domain.TBArticle
 import com.supwisdom.dlpay.portal.domain.TBColumn
@@ -46,6 +48,8 @@
     lateinit var articleService: ArticleService
     @Autowired
     lateinit var columnService: ColumnService
+    @Autowired
+    lateinit var uploadPicService: UploadPicService
     val logger = KotlinLogging.logger { }
 
     @RequestMapping("/test")
@@ -184,7 +188,7 @@
 
     @RequestMapping(value = ["/article/uploadpic"], method = [RequestMethod.POST])
     fun uploadPic(request: MultipartHttpServletRequest): JsonResult? {
-        val map = articleService.uploadPic(request)
+        val map = uploadPicService.uploadPic(request)
         return JsonResult.ok()
     }
 
@@ -251,15 +255,18 @@
 
     @RequestMapping(value = ["/article/release"], method = [RequestMethod.POST])
     fun releaseArticle(@RequestBody article: TBArticle): JsonResult? {
-        return try {
+         try {
             val p = SecurityContextHolder.getContext().authentication
             val oper = operatorDetailService.findByOperid(p.name)
             article.operid = oper.operid
             articleService.releaseArticle(article)
-            JsonResult.ok()
+             return JsonResult.ok()
         } catch (e: Exception) {
             logger.error { e.message }
-            JsonResult.error("发布文章异常")
+            if (e is PortalBusinessException) {
+                return JsonResult.error(e.message)
+            }
+             return JsonResult.error("发布文章异常")
         }
     }
 
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/bean/DtlGroupResultBean.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/bean/DtlGroupResultBean.kt
new file mode 100644
index 0000000..0833091
--- /dev/null
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/bean/DtlGroupResultBean.kt
@@ -0,0 +1,8 @@
+package com.supwisdom.dlpay.portal.bean
+
+import java.math.BigDecimal
+
+class DtlGroupResultBean {
+    var transtype:String = ""
+    var amount:BigDecimal = BigDecimal(0)
+}
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/bean/FeedbackSearchBean.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/bean/FeedbackSearchBean.kt
index 3b15a48..d15c56b 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/bean/FeedbackSearchBean.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/bean/FeedbackSearchBean.kt
@@ -2,10 +2,11 @@
 
 class FeedbackSearchBean {
     var username: String = ""
-    var content: String = ""
+    var title: String = ""
     var startdate: String = ""
     var enddate: String = ""
     var replystatus: String = ""
     var pageno: Int = 0
     var pagesize: Int = 10
+    var mobileid: String = ""
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/ArticleDao.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/ArticleDao.kt
index 3e4ca6b..0515fde 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/ArticleDao.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/ArticleDao.kt
@@ -6,5 +6,6 @@
 
 @Repository
 interface ArticleDao : JpaRepository<TBArticle, String>, ArticleRepository {
-    fun findByArticlenoAndIsdelete(articleno: String,isdelete:String): TBArticle?
+    fun findByArticlenoAndIsdelete(articleno: String, isdelete: String): TBArticle?
+    fun findByArticlenoAndStatusAndIsdisplay(articleno: String, status: String, isdisplay: String): TBArticle?
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/ArticleRepository.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/ArticleRepository.kt
index 9c27094..a1379da 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/ArticleRepository.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/ArticleRepository.kt
@@ -2,9 +2,12 @@
 
 import com.supwisdom.dlpay.framework.jpa.page.Pagination
 import com.supwisdom.dlpay.portal.bean.ArticleSearchBean
+import com.supwisdom.dlpay.portal.domain.TBArticle
 import java.text.ParseException
 
 interface ArticleRepository {
     @Throws(ParseException::class)
-    fun getArticleList(bean:ArticleSearchBean): Pagination
+    fun getArticleList(bean: ArticleSearchBean): Pagination
+
+    fun getArticleListByColumnid(columnid: String, pageno: Int, pagesize: Int): List<TBArticle>
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/ColumnDao.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/ColumnDao.kt
index fa53b3c..1283cc1 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/ColumnDao.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/ColumnDao.kt
@@ -8,4 +8,5 @@
 interface ColumnDao: JpaRepository<TBColumn, String>,ColumnRepository {
     fun findByNameAndColumnidNot(name: String,columnid:String): TBColumn?
     fun findByCodeAndColumnidNot(code:String,columnid:String): TBColumn?
+    fun findByCodeIn(codes:List<String>):List<TBColumn>?
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/FeedbackDao.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/FeedbackDao.kt
index 9d55724..58d4d5f 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/FeedbackDao.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/dao/FeedbackDao.kt
@@ -5,6 +5,6 @@
 import org.springframework.stereotype.Repository
 
 @Repository
-interface FeedbackDao :JpaRepository<TBFeedback,String>,FeedbackRepository{
-
+interface FeedbackDao : JpaRepository<TBFeedback, String>, FeedbackRepository {
+    fun getByFbidAndMobileid(fbid: String, mobileid: String):TBFeedback?
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/ArticleService.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/ArticleService.kt
index 7eaa432..89409d6 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/ArticleService.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/ArticleService.kt
@@ -1,15 +1,15 @@
 package com.supwisdom.dlpay.portal.service
 
 import com.supwisdom.dlpay.framework.jpa.page.Pagination
+import com.supwisdom.dlpay.mobile.bean.ActivitiesBean
 import com.supwisdom.dlpay.portal.bean.ArticleSearchBean
 import com.supwisdom.dlpay.portal.domain.TBArticle
+import com.supwisdom.dlpay.portal.domain.TBColumn
 import org.springframework.transaction.annotation.Transactional
 import org.springframework.web.multipart.MultipartHttpServletRequest
 
 interface ArticleService {
     @Transactional
-    fun uploadPic(request: MultipartHttpServletRequest): Map<String, String>
-    @Transactional
     fun saveArticle(article: TBArticle)
     @Transactional
     fun releaseArticle(article: TBArticle)
@@ -23,4 +23,10 @@
     fun switchDisplay(articleno:String,value:String):String
     @Transactional
     fun getArticle(articleno:String):TBArticle
+    @Transactional
+    fun getArticleByColumn(columnList:List<TBColumn>,map:Map<String, ActivitiesBean>):List<TBColumn>
+    @Transactional
+    fun getMobileArticle(articleno:String):TBArticle
+    @Transactional
+    fun addArticleHits(article:TBArticle):TBArticle
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/ColumnService.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/ColumnService.kt
index ea3db3e..563faf7 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/ColumnService.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/ColumnService.kt
@@ -7,9 +7,17 @@
 
 interface ColumnService {
     @Transactional
-    fun saveColumn(column:TBColumn):String?
+    fun saveColumn(column: TBColumn): String?
+
     @Transactional
     fun getColumnList(bean: ColumnSearchBean): Pagination
+
     @Transactional
-    fun getAllColumnList():List<TBColumn>?
+    fun getAllColumnList(): List<TBColumn>?
+
+    @Transactional
+    fun getColumnListByCode(codes: List<String>): List<TBColumn>?
+
+    @Transactional
+    fun getColunmByid(columnid: String): TBColumn?
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/FeedbackService.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/FeedbackService.kt
index 2b8e221..5a22ae8 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/FeedbackService.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/FeedbackService.kt
@@ -1,15 +1,26 @@
 package com.supwisdom.dlpay.portal.service
 
 import com.supwisdom.dlpay.framework.jpa.page.Pagination
+import com.supwisdom.dlpay.mobile.bean.FeedbackBean
+import com.supwisdom.dlpay.mobile.bean.PictureBean
 import com.supwisdom.dlpay.portal.bean.FeedbackSearchBean
+import com.supwisdom.dlpay.portal.domain.TBFeedback
 import com.supwisdom.dlpay.portal.domain.TBReply
 import org.springframework.transaction.annotation.Transactional
 
 interface FeedbackService {
     @Transactional
     fun getFeedbackList(bean: FeedbackSearchBean): Pagination
+
     @Transactional
     fun getReplyListByFbId(fbId: String): List<TBReply>
+
     @Transactional
-    fun saveReplyListByFbId(reply: TBReply):String?
+    fun saveReplyListByFbId(reply: TBReply): String?
+
+    @Transactional
+    fun saveFeedback(feedback: TBFeedback, pictures: List<PictureBean>)
+
+    @Transactional
+    fun getFeedbackDetail(fbid: String, mobileid: String): TBFeedback?
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/ArticleServiceImpl.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/ArticleServiceImpl.kt
index 2d45cb6..f10cb67 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/ArticleServiceImpl.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/ArticleServiceImpl.kt
@@ -2,29 +2,24 @@
 
 import com.supwisdom.dlpay.framework.dao.BusinessparaDao
 import com.supwisdom.dlpay.framework.jpa.page.Pagination
+import com.supwisdom.dlpay.framework.service.OperatorDetailService
 import com.supwisdom.dlpay.framework.service.SystemUtilService
 import com.supwisdom.dlpay.framework.util.StringUtil
+import com.supwisdom.dlpay.mobile.bean.ActivitiesBean
+import com.supwisdom.dlpay.mobile.exception.PortalBusinessException
 import com.supwisdom.dlpay.portal.bean.ArticleSearchBean
 import com.supwisdom.dlpay.portal.dao.ArticleAuthDao
 import com.supwisdom.dlpay.portal.dao.ArticleDao
 import com.supwisdom.dlpay.portal.dao.ColumnDao
 import com.supwisdom.dlpay.portal.domain.TBArticle
 import com.supwisdom.dlpay.portal.domain.TBArticleAuth
+import com.supwisdom.dlpay.portal.domain.TBColumn
 import com.supwisdom.dlpay.portal.service.ArticleService
 import com.supwisdom.dlpay.portal.util.PortalConstant
-import com.supwisdom.dlpay.portal.util.PutImageToServer
 import mu.KotlinLogging
-import net.coobird.thumbnailator.Thumbnails
 import org.springframework.beans.factory.annotation.Autowired
 import org.springframework.stereotype.Service
-import org.springframework.web.multipart.MultipartFile
-import org.springframework.web.multipart.MultipartHttpServletRequest
-import java.io.File
-import java.io.FileInputStream
-import java.io.IOException
 import java.sql.Timestamp
-import java.util.*
-import kotlin.math.sqrt
 
 @Service
 class ArticleServiceImpl : ArticleService {
@@ -38,6 +33,8 @@
     lateinit var articleAuthDao: ArticleAuthDao
     @Autowired
     lateinit var columnDao: ColumnDao
+    @Autowired
+    lateinit var operatorDetailService: OperatorDetailService
 
     val logger = KotlinLogging.logger { }
 
@@ -47,10 +44,10 @@
         }
         val timestamp = systemUtilService.sysdatetime.sysdate
         article.status = PortalConstant.ARTICLE_STATUS_SAVE
-        saveArticle(article,timestamp)
+        saveArticle(article, timestamp)
     }
 
-    fun saveArticle(article: TBArticle,timestamp:Timestamp): TBArticle {
+    fun saveArticle(article: TBArticle, timestamp: Timestamp): TBArticle {
         if (StringUtil.isEmpty(article.articleid)) {
             val currentNoBusiness = businessparaDao.findByParakey(PortalConstant.SYSPARA_ARTICLE_CURRENTNO)
                     ?: throw RuntimeException("文章编号参数未配置")
@@ -72,21 +69,24 @@
         if (article.status != null &&
                 article.status != PortalConstant.ARTICLE_STATUS_SAVE &&
                 article.status != PortalConstant.ARTICLE_STATUS_PASS) {
-            throw RuntimeException("文章状态异常")
+            throw PortalBusinessException("文章状态异常")
         }
         val timestamp = systemUtilService.sysdatetime.sysdate
         article.releasetime = timestamp
         article.status = PortalConstant.ARTICLE_STATUS_RELEASED
-        val savedArticle = saveArticle(article,timestamp)
+        val savedArticle = saveArticle(article, timestamp)
         val optional = columnDao.findById(savedArticle.columnid)
         if (!optional.isPresent) {
-            throw RuntimeException("文章栏目未找到")
+            throw PortalBusinessException("文章栏目未找到")
         }
         val column = optional.get()
+        if (column.publishable == PortalConstant.NO) {
+            throw PortalBusinessException("该栏目下不能发布文章")
+        }
         if (column.needreview == PortalConstant.YES) {
             articleAuthDao.findByArticlenoAndStatus(savedArticle.articleno,
                     PortalConstant.ARTICLE_STATUS_PASS)
-                    ?: throw RuntimeException("文章未通过审核,不能发布")
+                    ?: throw PortalBusinessException("文章未通过审核,不能发布")
         }
     }
 
@@ -97,7 +97,7 @@
         }
         val timestamp = systemUtilService.sysdatetime.sysdate
         article.status = PortalConstant.ARTICLE_STATUS_REVIEW
-        val savedArticle = saveArticle(article,timestamp)
+        val savedArticle = saveArticle(article, timestamp)
         val optional = columnDao.findById(savedArticle.columnid)
         if (!optional.isPresent) {
             throw RuntimeException("文章栏目未找到")
@@ -138,180 +138,35 @@
                 ?: throw RuntimeException("编号为:[${articleno}]的文章未找到")
     }
 
-    override fun uploadPic(request: MultipartHttpServletRequest): Map<String, String> {
-        val map: MutableMap<String, String> = HashMap()
-        try {
-            logger.error("===put up pic")
-            val pathDir = "/files/"
-            /** 得到图片保存目录的真实路径  */
-            val logoRealPathDir = request.session.servletContext
-                    .getRealPath(pathDir)
-            /** 根据真实路径创建目录  */
-            val logoSaveFile = File(logoRealPathDir)
-            if (!logoSaveFile.exists()) logoSaveFile.mkdirs()
-            /** 页面控件的文件流  */
-            val multipartFile: MultipartFile
-            multipartFile = request.getFile("file")
-            //System.out.println(multipartFile);
-            /* 获取文件的后缀 */
-            val suffix = multipartFile.originalFilename.substring(
-                    multipartFile.originalFilename.lastIndexOf("."))
-            //* 拼成完整的文件保存路径加文件 *//*
-            val name = System.currentTimeMillis().toString() + suffix
-            val fileName = logoRealPathDir + File.separator + name
-            val file = File(fileName)
-            // String data = file.getPath();
-            try {
-                multipartFile.transferTo(file)
-            } catch (e: IllegalStateException) {
-                logger.error("error:", e)
-            } catch (e: IOException) {
-                logger.error("error:", e)
-            }
-            //savePicToDb(fileName, goodsidString);*/
-            //String filepath = logoRealPathDir + File.separator + multipartFile.getOriginalFilename();
-            val size = multipartFile.size
-            var maxSize = 200 * 1024.toLong()
-            val imageMaxConf = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_IMAGE_MAXSIZE)
-            if (imageMaxConf != null && !StringUtil.isEmpty(imageMaxConf)) {
-                try {
-                    maxSize = imageMaxConf.toLong()
-                } catch (e: Exception) {
+    override fun getArticleByColumn(columnList: List<TBColumn>, map: Map<String, ActivitiesBean>): List<TBColumn> {
+        columnList.forEach {
+            val page = map.getOrElse(it.code) {
+                ActivitiesBean().apply {
+                    this.pageno = 1
+                    this.pagesize = 1
                 }
             }
-            var minwidth = 150
-            var minheight = 150
-            val imageMinConf = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_IMAGE_MINISIZE)
-            if (imageMinConf != null && !StringUtil.isEmpty(imageMinConf)) {
-                try {
-                    val temps = imageMinConf.split(",")
-                    if (temps.size == 2) {
-                        minwidth = temps[0].toInt()
-                        minheight = temps[1].toInt()
-                    }
-                } catch (e: Exception) {
-                    logger.error("error:", e)
-                }
-            }
-            logger.error("===put up pic 1")
-            var baseURL = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_IMAGE_URLASSIGN)
-            if (baseURL == null || StringUtil.isEmpty(baseURL)) {
-                map["retmsg"] = "系统未配置图片服务器地址,无法保存图片!"
-                map["retcode"] = "1"
-                return map
-            }
-            if (!baseURL.startsWith("http")) {
-                baseURL = "http://$baseURL"
-            }
-            if (baseURL.endsWith("/")) {
-                baseURL = baseURL.substring(0, baseURL.length - 1)
-            }
-            logger.error("===put up pic 2")
-            var minImageURL: String
-            val picid: String
-            val minpicid: String
-            var imageURL = systemUtilService.getBusinessValue(PortalConstant.SYSPARA_IMAGE_URLPUSH)
-            if (imageURL == null || StringUtil.isEmpty(imageURL)) {
-                map["retmsg"] = "系统未配置图片服务器地址,无法保存图片!"
-                map["retcode"] = "1"
-                return map
-            }
-            logger.error("===put up pic 4")
-            if (!imageURL.startsWith("http")) {
-                imageURL = "http://$imageURL"
-            }
-            if (!imageURL.endsWith("/")) {
-                imageURL += "/"
-            }
-            minImageURL = imageURL
-            if (!minImageURL.startsWith("http")) {
-                minImageURL = "http://$minImageURL"
-            }
-            if (!minImageURL.endsWith("/")) {
-                minImageURL += "/"
-            }
-            val assign = "$baseURL/dir/assign?count=2"
-            val bean = PutImageToServer.postBefore(assign)
-            if (bean == null || StringUtil.isEmpty(bean.getFid())) {
-                map["retmsg"] = "图片上传失败,无法连接图片服务器!"
-                map["retcode"] = "1"
-                return map
-            }
-            imageURL += bean.fid
-            picid = bean.fid
-            val minImgBean = PutImageToServer.postBefore(assign)
-            if (minImgBean == null || StringUtil.isEmpty(minImgBean.getFid())) {
-                map["retmsg"] = "图片上传失败,无法连接图片服务器!"
-                map["retcode"] = "1"
-                return map
-            }
-            minpicid = minImgBean.fid
-            minImageURL += minImgBean.fid
-            //}
-            val processToFile = logoRealPathDir + File.separator + picid + ".jpg"
-            var scale = 1.0
-            if (size > 0 && size > maxSize) {
-                scale = maxSize * 1.0 / size
-            }
-            try {
-                if (scale < 0.7) {
-                    val s = sqrt(maxSize.toDouble()).toInt()
-                    Thumbnails.of(fileName).size(s, s).outputFormat("jpg").toFile(processToFile)
-                } else {
-                    Thumbnails.of(fileName).scale(1.0).outputQuality(scale).outputFormat("jpg").toFile(processToFile)
-                }
-                logger.error("===put up pic 5")
-            } catch (e: Exception) {
-                map["retmsg"] = "图片上传失败,压缩失败"
-                map["retcode"] = "1"
-                file.delete()
-                return map
-            }
-            val file1 = File(processToFile)
-            val processToMinFile = logoRealPathDir + File.separator + minpicid + ".jpg"
-            try {
-                Thumbnails.of(fileName).size(minwidth, minheight).outputFormat("jpg").toFile(processToMinFile)
-            } catch (e: Exception) {
-                map["retmsg"] = "图片上传失败,压缩失败"
-                map["retcode"] = "1"
-                file.delete()
-                file1.delete()
-                return map
-            }
-            val file2 = File(processToMinFile)
-            logger.error("==========imageurl=$imageURL")
-            if (!PutImageToServer.postImage(imageURL, FileInputStream(file1))) {
-                map["retmsg"] = "图片上传失败,请稍后再试"
-                map["retcode"] = "1"
-                file1.delete()
-                file2.delete()
-                file.delete()
-                logger.error("=step1 fail=")
-                return map
-            }
-            if (!PutImageToServer.postImage(minImageURL, FileInputStream(file2))) {
-                map["retmsg"] = "图片上传失败,请稍后再试"
-                map["retcode"] = "1"
-                file1.delete()
-                file2.delete()
-                file.delete()
-                logger.error("=step2 fail=")
-            } else {
-                map["retmsg"] = "上传成功"
-                map["retcode"] = "0"
-                file1.delete()
-                file2.delete()
-                file.delete()
-                map["picid"] = picid
-                map["minpicid"] = minpicid
-                logger.error("=step3 success=")
-            }
-        } catch (e: Exception) {
-            map["retmsg"] = "图片上传失败--" + e.message
-            map["retcode"] = "1"
-            // return map;
-            logger.error("=step fail=", e)
+            val articleList = articleDao.getArticleListByColumnid(it.columnid, page.pageno, page.pagesize)
+            it.articles = articleList
         }
-        return map
+        return columnList
+    }
+
+    override fun getMobileArticle(articleno: String): TBArticle {
+        val article = articleDao.findByArticlenoAndStatusAndIsdisplay(articleno, PortalConstant.ARTICLE_STATUS_RELEASED, PortalConstant.YES)
+                ?: throw PortalBusinessException("该文章不存在")
+        if (article.isdelete == PortalConstant.YES) {
+            throw PortalBusinessException("文章已删除")
+        }
+        val oper = operatorDetailService.findByOperid(article.operid)
+        if (oper != null) {
+            article.author = oper.opername
+        }
+        return article
+    }
+
+    override fun addArticleHits(article: TBArticle): TBArticle {
+        article.hits += 1
+        return articleDao.save(article)
     }
 }
\ No newline at end of file
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/ColumnServiceImpl.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/ColumnServiceImpl.kt
index f83e441..59ae3bc 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/ColumnServiceImpl.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/ColumnServiceImpl.kt
@@ -23,6 +23,15 @@
         return columnDao.findAll()
     }
 
+    override fun getColumnListByCode(codes: List<String>): List<TBColumn>? {
+        return columnDao.findByCodeIn(codes)
+    }
+
+    override fun getColunmByid(columnid: String): TBColumn? {
+        val optional = columnDao.findById(columnid)
+        return optional.orElseGet(null)
+    }
+
     override fun saveColumn(column: TBColumn):String? {
         var columnid = "####"
         if (!StringUtil.isEmpty(column.columnid)) {
diff --git a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/FeedbackServiceImpl.kt b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/FeedbackServiceImpl.kt
index d690911..dfa359d 100644
--- a/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/FeedbackServiceImpl.kt
+++ b/backend/src/main/kotlin/com/supwisdom/dlpay/portal/service/Impl/FeedbackServiceImpl.kt
@@ -3,10 +3,14 @@
 import com.supwisdom.dlpay.framework.jpa.page.Pagination
 import com.supwisdom.dlpay.framework.service.SystemUtilService
 import com.supwisdom.dlpay.framework.util.StringUtil
+import com.supwisdom.dlpay.mobile.bean.FeedbackBean
+import com.supwisdom.dlpay.mobile.bean.PictureBean
+import com.supwisdom.dlpay.mobile.service.MobileUserService
 import com.supwisdom.dlpay.portal.bean.FeedbackSearchBean
 import com.supwisdom.dlpay.portal.dao.AnnexDao
 import com.supwisdom.dlpay.portal.dao.FeedbackDao
 import com.supwisdom.dlpay.portal.dao.ReplyDao
+import com.supwisdom.dlpay.portal.domain.TBAnnex
 import com.supwisdom.dlpay.portal.domain.TBFeedback
 import com.supwisdom.dlpay.portal.domain.TBReply
 import com.supwisdom.dlpay.portal.service.FeedbackService
@@ -23,16 +27,23 @@
     @Autowired
     lateinit var systemUtilService: SystemUtilService
     @Autowired
+    lateinit var mobileUserService: MobileUserService
+    @Autowired
     lateinit var annexDao: AnnexDao
 
+    @Suppress("UNCHECKED_CAST")
     override fun getFeedbackList(bean: FeedbackSearchBean): Pagination {
         val page = feedbackDao.getFeedbackList(bean)
         val list = page.list as List<TBFeedback>
-        list.forEach { feedback ->
-            run {
-                feedback.pictures = annexDao.getByFbid(feedback.fbid)
+        list.forEach {
+            it.pictures = annexDao.getByFbid(it.fbid)
+            if (it.userid.isNullOrEmpty()) {
+                val mobileUser = mobileUserService.getByUid(it.mobileid)
+                if (mobileUser != null) {
+                    it.username = "用户" + mobileUser.phone!!.substring(7)
+                }
             }
-         }
+        }
         return page
     }
 
@@ -53,4 +64,36 @@
             "回复的留言不存在"
         }
     }
+
+    override fun saveFeedback(feedback: TBFeedback, pictures: List<PictureBean>) {
+        feedback.apply {
+            this.fbtime = systemUtilService.sysdatetime.hostdatetime
+            this.replystatus = PortalConstant.NO
+        }
+        val saved = feedbackDao.save(feedback)
+        if (pictures.isNotEmpty()) {
+            val annexList = ArrayList<TBAnnex>()
+            pictures.forEach {
+                val annex = TBAnnex().apply {
+                    this.minpicid = it.minpicid
+                    this.picid = it.picid
+                    this.fbid = saved.fbid
+                }
+                annexList.add(annex)
+            }
+            annexDao.saveAll(annexList)
+        }
+    }
+
+    override fun getFeedbackDetail(fbid: String, mobileid: String): TBFeedback? {
+        val feedback = feedbackDao.getByFbidAndMobileid(fbid,mobileid)
+                ?:return null
+        if (feedback.replystatus == PortalConstant.YES) {
+            val replayList = replyDao.getAllByFbid(fbid)
+            if (replayList.isNotEmpty()) {
+                feedback.reply = replayList[0]
+            }
+        }
+        return feedback
+    }
 }
\ No newline at end of file
diff --git a/frontend/src/assets/icon/iconfont.css b/frontend/src/assets/icon/iconfont.css
new file mode 100644
index 0000000..63c387c
--- /dev/null
+++ b/frontend/src/assets/icon/iconfont.css
@@ -0,0 +1,21 @@
+@font-face {font-family: "iconfont";
+  src: url('iconfont.eot?t=1599460043694'); /* IE9 */
+  src: url('iconfont.eot?t=1599460043694#iefix') format('embedded-opentype'), /* IE6-IE8 */
+  url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAALYAAsAAAAABngAAAKLAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCCcAp8gRYBNgIkAwgLBgAEIAWEbQc4G8QFERWMR7KfB7bzlEZQCL2C9ItjsqG9Uhwb3Pfg+Xe/7L4kM00zBVbEtKyIUW33eEa3mhUAOpR/a7oO9xgTobokw7LVtJ35704vTRnncRqlEBIhaYAAgrFtRVV11SfuHf8F9EFaDyjHtTdN6gLq4kAKaG+MIivGhKVh7AKX8BgCOBLlk/bO/jFYDNYqAWT+nFlTsLkQBsOyFsFfc9Agh/Fh1R51H4tD4fflJ1EWFD6NVTdiZsd0mr8stvP0Zm+z8b6F3fM5AHYMNJAPDMiY2tQgGmGsxqUMqdhXtcCXxZ7nW5C9GhH201mVIQDEQ9E9iV3JrWYVwG2NJB00jPovQNLX7X10FKdCxT19vMePe/SoJ08cOXHsaOzj93/60pMPkpaVdG3LHj7UHRPXdXZe939V9s0c7P2VuS53zZ3Dm2W7ujecuXH8+lvPVtUXsq9cS7qa+TAu9Ebsdb7fVDQD4K3WTzQhgMCd6Vv+xeH1f+3MAPBy3e974RJvtUEvAuzu3QK/Sdyzy+hSy6yMKq7tV6SfvsHhAA/si/JmrC72nnKCJXaEJ4iE1KexMykMZPLhI0A5/Fi4TXDybI4HRGMuWTCQ6w0gInyghLkNLcKXMMwPPnH+8IvIhTOu6AsDMqtUG2MqWED2giqnXjtWUVH1Cc3UppyVhpwb8lhFIfSCYnaDPfIcW8a7iUQ0aKYO1uA5bFuCganGXLxSZJj7vq57k5dTZyltGEoJVACZF1DJkZ72BwtV+vwJMiatFLd01IRvEBtV44OQJ+hBbFR9r457eWV0Z0SE0IDGSAeswTzUahEw1M+roZzwlCMSgznf7qX7qrz1Nd3XHQIcS6+yJ2jCXmI5VbFbnGZpH1oWAAA=') format('woff2'),
+  url('iconfont.woff?t=1599460043694') format('woff'),
+  url('iconfont.ttf?t=1599460043694') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
+  url('iconfont.svg?t=1599460043694#iconfont') format('svg'); /* iOS 4.1- */
+}
+
+.iconfont {
+  font-family: "iconfont" !important;
+  font-size: 16px;
+  font-style: normal;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+}
+
+.iconchehui_huaban1:before {
+  content: "\e670";
+}
+
diff --git a/frontend/src/assets/icon/iconfont.eot b/frontend/src/assets/icon/iconfont.eot
new file mode 100644
index 0000000..761f4e7
--- /dev/null
+++ b/frontend/src/assets/icon/iconfont.eot
Binary files differ
diff --git a/frontend/src/assets/icon/iconfont.js b/frontend/src/assets/icon/iconfont.js
new file mode 100644
index 0000000..b8807a3
--- /dev/null
+++ b/frontend/src/assets/icon/iconfont.js
@@ -0,0 +1 @@
+!function(e){var t,n,o,i,d,c,a,s='<svg><symbol id="iconchehui_huaban1" viewBox="0 0 1024 1024"><path d="M502.70473274 78.69756672a458.32815083 458.32815083 0 0 0-260.98248816 81.51233893v-143.00410282h-71.50205226v275.99791896h277.42796114v-71.50205223H277.47326987a393.26128489 393.26128489 0 1 1 225.23146287 715.02051752A394.69132538 394.69132538 0 0 1 126.60394192 655.00410283a36.46604637 36.46604637 0 0 0-43.61625211-22.16563492 35.75102528 35.75102528 0 0 0-23.59567704 44.33127151 464.76333549 464.76333549 0 1 0 444.74276211-598.4721727z"  ></path></symbol></svg>',l=(t=document.getElementsByTagName("script"))[t.length-1].getAttribute("data-injectcss");if(l&&!e.__iconfont__svg__cssinject__){e.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(e){console&&console.log(e)}}function r(){c||(c=!0,i())}n=function(){var e,t,n,o,i,d=document.createElement("div");d.innerHTML=s,s=null,(e=d.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",t=e,(n=document.body).firstChild?(o=t,(i=n.firstChild).parentNode.insertBefore(o,i)):n.appendChild(t))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(n,0):(o=function(){document.removeEventListener("DOMContentLoaded",o,!1),n()},document.addEventListener("DOMContentLoaded",o,!1)):document.attachEvent&&(i=n,d=e.document,c=!1,(a=function(){try{d.documentElement.doScroll("left")}catch(e){return void setTimeout(a,50)}r()})(),d.onreadystatechange=function(){"complete"==d.readyState&&(d.onreadystatechange=null,r())})}(window);
\ No newline at end of file
diff --git a/frontend/src/assets/icon/iconfont.json b/frontend/src/assets/icon/iconfont.json
new file mode 100644
index 0000000..f41db51
--- /dev/null
+++ b/frontend/src/assets/icon/iconfont.json
@@ -0,0 +1,16 @@
+{
+  "id": "2050059",
+  "name": "dali-portal",
+  "font_family": "iconfont",
+  "css_prefix_text": "icon",
+  "description": "",
+  "glyphs": [
+    {
+      "icon_id": "14421605",
+      "name": "撤回操作",
+      "font_class": "chehui_huaban1",
+      "unicode": "e670",
+      "unicode_decimal": 58992
+    }
+  ]
+}
diff --git a/frontend/src/assets/icon/iconfont.svg b/frontend/src/assets/icon/iconfont.svg
new file mode 100644
index 0000000..dc74a2d
--- /dev/null
+++ b/frontend/src/assets/icon/iconfont.svg
@@ -0,0 +1,29 @@
+<?xml version="1.0" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
+<!--
+2013-9-30: Created.
+-->
+<svg>
+<metadata>
+Created by iconfont
+</metadata>
+<defs>
+
+<font id="iconfont" horiz-adv-x="1024" >
+  <font-face
+    font-family="iconfont"
+    font-weight="500"
+    font-stretch="normal"
+    units-per-em="1024"
+    ascent="896"
+    descent="-128"
+  />
+    <missing-glyph />
+    
+    <glyph glyph-name="chehui_huaban1" unicode="&#58992;" d="M502.70473274 817.3024332800001a458.32815083 458.32815083 0 0 1-260.98248816-81.51233893v143.00410282h-71.50205226v-275.99791896h277.42796114v71.50205223H277.47326987a393.26128489 393.26128489 0 1 0 225.23146287-715.02051752A394.69132538 394.69132538 0 0 0 126.60394192 240.99589717000003a36.46604637 36.46604637 0 0 1-43.61625211 22.16563492 35.75102528 35.75102528 0 0 1-23.59567704-44.33127151 464.76333549 464.76333549 0 1 1 444.74276211 598.4721727z"  horiz-adv-x="1024" />
+
+    
+
+
+  </font>
+</defs></svg>
diff --git a/frontend/src/assets/icon/iconfont.ttf b/frontend/src/assets/icon/iconfont.ttf
new file mode 100644
index 0000000..a2735b0
--- /dev/null
+++ b/frontend/src/assets/icon/iconfont.ttf
Binary files differ
diff --git a/frontend/src/assets/icon/iconfont.woff b/frontend/src/assets/icon/iconfont.woff
new file mode 100644
index 0000000..b9afbd5
--- /dev/null
+++ b/frontend/src/assets/icon/iconfont.woff
Binary files differ
diff --git a/frontend/src/assets/icon/iconfont.woff2 b/frontend/src/assets/icon/iconfont.woff2
new file mode 100644
index 0000000..5a34691
--- /dev/null
+++ b/frontend/src/assets/icon/iconfont.woff2
Binary files differ
diff --git a/frontend/src/components/Tinymce/index.vue b/frontend/src/components/Tinymce/index.vue
index 7a9877f..68ef381 100644
--- a/frontend/src/components/Tinymce/index.vue
+++ b/frontend/src/components/Tinymce/index.vue
@@ -1,5 +1,5 @@
 <template>
-  <div :class="{fullscreen:fullscreen}" class="tinymce-container" :style="{width:containerWidth}">
+  <div :class="{fullscreen:fullscreen}" class="tinymce-container" style="width:600px">
     <textarea :id="tinymceId" class="tinymce-textarea" />
     <div class="editor-custom-btn-container">
       <editorImage color="#1890ff" class="editor-upload-btn" @successCBK="imageSuccessCBK" />
diff --git a/frontend/src/main.js b/frontend/src/main.js
index e750af9..b9225cf 100644
--- a/frontend/src/main.js
+++ b/frontend/src/main.js
@@ -20,6 +20,8 @@
 
 import * as filters from './filters' // global filters
 
+import './assets/icon/iconfont.css' // iconfont
+
 /**
  * If you don't want to use mock-server
  * you want to use MockJs for mock api
diff --git a/frontend/src/views/article/components/ArticleDetail.vue b/frontend/src/views/article/components/ArticleDetail.vue
index ec84f4b..9496019 100644
--- a/frontend/src/views/article/components/ArticleDetail.vue
+++ b/frontend/src/views/article/components/ArticleDetail.vue
@@ -267,6 +267,8 @@
           showClose: true,
           duration: 1000
         })
+        this.$store.dispatch('tagsView/delView', this.$route)
+        this.$router.go(-1)
       }).catch(error => {
         this.$message({
           message: error.msg || '请求异常',
diff --git a/frontend/src/views/article/list.vue b/frontend/src/views/article/list.vue
index 5fb208d..084f69e 100644
--- a/frontend/src/views/article/list.vue
+++ b/frontend/src/views/article/list.vue
@@ -107,7 +107,7 @@
           />
         </template>
       </el-table-column>
-      <el-table-column width="120px" label="操作" align="center">
+      <el-table-column width="160px" label="操作" align="center">
         <template slot-scope="{row}">
           <el-tooltip v-if="row.status==='save'" class="item" effect="dark" content="编辑" placement="bottom">
             <router-link :to="'/article/edit/'+row.articleno">
@@ -123,6 +123,11 @@
           <el-tooltip class="item" effect="dark" content="删除" placement="bottom">
             <el-button type="primary" icon="el-icon-delete" circle size="mini" @click="deleteArticle(row)" />
           </el-tooltip>
+          <!-- <el-tooltip v-if="row.status!=='save' && row.status!=='released'" class="item" effect="dark" content="撤回" placement="bottom">
+            <el-button type="primary" size="mini" circle>
+              <i class="iconfont iconchehui_huaban1" style="font-size:12px" />
+            </el-button>
+          </el-tooltip> -->
         </template>
       </el-table-column>
     </el-table>
diff --git a/frontend/src/views/feedback/index.vue b/frontend/src/views/feedback/index.vue
index 125e87f..868b6dc 100644
--- a/frontend/src/views/feedback/index.vue
+++ b/frontend/src/views/feedback/index.vue
@@ -8,10 +8,10 @@
         style="width: 350px;margin-right:50px"
         class="filter-item"
       />
-      <div class="filter-item" style="margin-right:15px">留言内容</div>
+      <div class="filter-item" style="margin-right:15px">留言标题</div>
       <el-input
-        v-model="formData.content"
-        placeholder="留言关键字"
+        v-model="formData.title"
+        placeholder="留言标题"
         style="width: 300px;margin-right:50px"
         class="filter-item"
       />
@@ -68,9 +68,9 @@
           <span>{{ row.username }}</span>
         </template>
       </el-table-column>
-      <el-table-column label="留言内容" align="center">
+      <el-table-column label="留言标题" align="center">
         <template slot-scope="{row}">
-          <span>{{ row.content }}</span>
+          <span>{{ row.title }}</span>
         </template>
       </el-table-column>
       <el-table-column label="状态" align="center" width="100">
@@ -249,7 +249,7 @@
       queryDate: null,
       formData: {
         username: '',
-        content: '',
+        title: '',
         replystatus: '',
         startdate: '',
         enddate: '',
@@ -336,7 +336,7 @@
     },
     clearFilter() {
       this.formData.username = ''
-      this.formData.content = ''
+      this.formData.title = ''
       this.formData.replystatus = ''
       this.queryDate = null
     },