Browse Source

Merge branch 'yb/5.0' into debug

# Conflicts:
#	services/wechat/wechat.go
#	utils/constants.go
hsun 2 years ago
parent
commit
683631bcef

+ 250 - 0
controller/community/question.go

@@ -0,0 +1,250 @@
+package community
+
+import (
+	"fmt"
+	"github.com/gin-gonic/gin"
+	"hongze/hongze_yb/controller/response"
+	"hongze/hongze_yb/global"
+	"hongze/hongze_yb/models/request"
+	respond "hongze/hongze_yb/models/response"
+	"hongze/hongze_yb/services"
+	"hongze/hongze_yb/services/community"
+	"hongze/hongze_yb/services/user"
+	"hongze/hongze_yb/utils"
+	"os"
+	"path"
+	"time"
+)
+
+// QuestionList 问答列表
+// @Tags 问答社区模块
+// @Description 获取问答列表
+// @Param page_index			query int false "页码"
+// @Param page_size				query int false "每页数量"
+// @Param only_mine				query int false "只看我的"
+// @Param chart_permission_id	query int false "品种权限ID"
+// @Param reply_status			query int false "回复状态 0-全部 2-待回答 3-已回答"
+// @Param replier_user_id		query int false "回复人ID"
+// @Success 200 {object} respond.CommunityQuestionList
+// @failure 400 {string} string "获取失败"
+// @Router /question/list [get]
+func QuestionList(c *gin.Context) {
+	var req request.QuestionListReq
+	if err := c.Bind(&req); err != nil {
+		response.Fail("参数有误", c)
+		return
+	}
+	if req.PageIndex == 0 {
+		req.PageIndex = 1
+	}
+	if req.PageSize == 0 {
+		req.PageSize = utils.PageSize20
+	}
+	userinfo := user.GetInfoByClaims(c)
+	list, err := community.GetQuestionList(req.PageIndex, req.PageSize, req.OnlyMine, req.ChartPermissionId, req.ReplyStatus, req.ReplierUserId, userinfo)
+	if err != nil {
+		response.FailMsg("获取失败", "QuestionList ErrMsg:"+err.Error(), c)
+		return
+	}
+	response.OkData("获取成功", list, c)
+}
+
+// QuestionDetail 问答详情
+// @Tags 问答社区模块
+// @Description 获取问答详情
+// @Param question_id  query  int  true  "问答ID"
+// @Success 200 {object} respond.CommunityQuestionItem
+// @failure 400 {string} string "获取失败"
+// @Router /question/detail [get]
+func QuestionDetail(c *gin.Context) {
+	var req request.QuestionDetailReq
+	if err := c.Bind(&req); err != nil {
+		response.Fail("参数有误", c)
+		return
+	}
+	if req.QuestionId == 0 {
+		response.Fail("参数有误", c)
+		return
+	}
+	userinfo := user.GetInfoByClaims(c)
+	item, err := community.GetQuestionDetail(req.QuestionId, userinfo)
+	if err != nil {
+		response.FailMsg("获取失败", "QuestionDetail ErrMsg:"+err.Error(), c)
+		return
+	}
+	response.OkData("获取成功", item, c)
+}
+
+// QuestionAsk 发布提问
+// @Tags 问答社区模块
+// @Description 发布提问
+// @Param question_content  query  string  true  "问题内容"
+// @Success 200 {string} string "操作成功"
+// @failure 400 {string} string "操作失败"
+// @Router /question/ask [post]
+func QuestionAsk(c *gin.Context) {
+	var req request.QuestionAskReq
+	if err := c.ShouldBind(&req); err != nil {
+		response.Fail("参数有误", c)
+		return
+	}
+	// TODO:敏感词校验
+	if req.QuestionContent == "" {
+		response.Fail("内容不可为空", c)
+		return
+	}
+	userinfo := user.GetInfoByClaims(c)
+	if err := community.CreateQuestion(int(userinfo.UserID), userinfo.Mobile, userinfo.RealName, req.QuestionContent); err != nil {
+		response.FailMsg("提交失败", "QuestionAsk ErrMsg:"+err.Error(), c)
+		return
+	}
+	response.Ok("操作成功", c)
+}
+
+// QuestionReply 发布回复
+// @Tags 问答社区模块
+// @Description 发布回复
+// @Param question_id	query  int		true  "问答ID"
+// @Param audio_list	query  object	true  "音频列表"
+// @Success 200 {string} string "操作成功"
+// @failure 400 {string} string "操作失败"
+// @Router /question/reply [post]
+func QuestionReply(c *gin.Context) {
+	var req request.QuestionReplyReq
+	if err := c.ShouldBind(&req); err != nil {
+		response.Fail("参数有误", c)
+		return
+	}
+	if req.QuestionId == 0 {
+		response.Fail("参数有误", c)
+		return
+	}
+	if len(req.AudioList) == 0 {
+		response.Fail("音频不可为空", c)
+		return
+	}
+	userinfo := user.GetInfoByClaims(c)
+	if err := community.ReplyUserQuestion(int(userinfo.UserID), req.QuestionId, req.AudioList); err != nil {
+		response.FailMsg("提交失败", "QuestionReply ErrMsg:"+err.Error(), c)
+		return
+	}
+	response.Ok("操作成功", c)
+}
+
+// QuestionReplyRead 已读回复
+// @Tags 问答社区模块
+// @Description 已读回复
+// @Param question_id  query  int  true  "问答ID"
+// @Success 200 {string} string "操作成功"
+// @failure 400 {string} string "操作失败"
+// @Router /question/reply/read [post]
+func QuestionReplyRead(c *gin.Context) {
+	var req request.QuestionReadReq
+	if err := c.ShouldBind(&req); err != nil {
+		response.Fail("参数有误", c)
+		return
+	}
+	if req.QuestionId == 0 {
+		response.Fail("参数有误", c)
+		return
+	}
+	userinfo := user.GetInfoByClaims(c)
+	if err := community.ReadQuestionReply(int(userinfo.UserID), req.QuestionId); err != nil {
+		response.FailMsg("操作失败", "QuestionReplyRead ErrMsg:"+err.Error(), c)
+		return
+	}
+	response.Ok("操作成功", c)
+}
+
+// QuestionReplyTotal 问答列表数量统计
+// @Tags 问答社区模块
+// @Description 问答列表数量统计
+// @Param replier_user_id  query  int  true  "回复人ID"
+// @Success 200 {object} respond.CommunityReplyTotal
+// @failure 400 {string} string "获取失败"
+// @Router /question/reply/total [get]
+func QuestionReplyTotal(c *gin.Context) {
+	var req request.ReplyListTotalReq
+	if err := c.ShouldBind(&req); err != nil {
+		response.Fail("参数有误", c)
+		return
+	}
+	if req.ReplierUserId == 0 {
+		response.Fail("参数有误", c)
+		return
+	}
+	resp, err := community.GetReplyListTotal(req.ReplierUserId)
+	if err != nil {
+		response.FailMsg("获取失败", "QuestionReplyTotal ErrMsg:"+err.Error(), c)
+		return
+	}
+	response.OkData("获取成功", resp, c)
+}
+
+// QuestionUploadAudio 上传回复音频
+// @Tags 问答社区模块
+// @Description 上传回复音频
+// @Param file  query  string  true  "音频文件"
+// @Success 200 {string} string "上传成功"
+// @failure 400 {string} string "上传失败"
+// @Router /question/reply/upload_audio [post]
+func QuestionUploadAudio(c *gin.Context) {
+	file, err := c.FormFile("file")
+	if err != nil {
+		response.FailMsg("获取资源失败", "获取资源失败, Err:"+err.Error(), c)
+		return
+	}
+	ext := path.Ext(file.Filename)
+	if ext != ".mp3" {
+		response.Fail("暂仅支持mp3格式", c)
+		return
+	}
+	dateDir := time.Now().Format("20060102")
+	localDir := global.CONFIG.Serve.StaticDir + "hongze/" + dateDir
+	if err := os.MkdirAll(localDir, 0766); err != nil {
+		response.FailMsg("存储目录创建失败", "QuestionUploadAudio 存储目录创建失败, Err:"+err.Error(), c)
+		return
+	}
+	randStr := utils.GetRandStringNoSpecialChar(28)
+	filtName := randStr + ext
+	fpath := localDir + "/" + filtName
+	defer func() {
+		_ = os.Remove(fpath)
+	}()
+	// 生成文件至指定目录
+	if err := c.SaveUploadedFile(file, fpath); err != nil {
+		response.FailMsg("文件生成失败", "QuestionUploadAudio 文件生成失败, Err:"+err.Error(), c)
+		return
+	}
+	// 获取音频文件时长
+	fByte, err := os.ReadFile(fpath)
+	if err != nil {
+		response.FailMsg("读取本地文件失败", "QuestionUploadAudio 读取本地文件失败", c)
+		return
+	}
+	seconds, err := services.GetMP3PlayDuration(fByte)
+	if err != nil {
+		response.FailMsg("读取文件时长失败", "QuestionUploadAudio 读取文件时长失败", c)
+		return
+	}
+	// 音频大小MB
+	fi, err := os.Stat(fpath)
+	if err != nil {
+		response.FailMsg("读取文件大小失败", "QuestionUploadAudio 读取文件大小失败", c)
+		return
+	}
+	mb := utils.Bit2MB(fi.Size(), 2)
+	// 上传文件至阿里云
+	ossDir := "yb_wx/community_question_audio/"
+	resourceUrl, err := services.UploadAliyunToDir(filtName, fpath, ossDir)
+	if err != nil {
+		response.FailMsg("文件上传失败", "QuestionUploadAudio 文件上传失败, Err:"+err.Error(), c)
+		return
+	}
+	resp := &respond.CommunityQuestionAudioUpload{
+		AudioURL:         resourceUrl,
+		AudioPlaySeconds: fmt.Sprint(seconds),
+		AudioSize:        fmt.Sprint(mb),
+	}
+	response.OkData("上传成功", resp, c)
+}

+ 20 - 0
controller/company/company_permission.go

@@ -17,5 +17,25 @@ func GetHomeFiccPermissions(c *gin.Context) {
 		return
 	}
 
+	response.OkData("获取成功", list, c)
+}
+
+// GetPermissionTree 获取FICC品种权限列表-不校验权限
+// @Tags 客户模块
+// @Summary  获取FICC品种权限列表
+// @Description 获取FICC品种权限列表
+// @Security ApiKeyAuth
+// @Param Authorization	header string true "Bearer 31a165baebe6dec616b1f8f3207b4273"
+// @Accept  json
+// @Product json
+// @Success 200 {object} []company.FiccPermissionList
+// @failure 400 {string} string "获取失败"
+// @Router /company/permission/tree [get]
+func GetPermissionTree(c *gin.Context)  {
+	list, err := company.GetFiccPermissionList()
+	if err != nil {
+		response.FailMsg("获取失败", "GetPermissionList ErrMsg:" + err.Error(), c)
+		return
+	}
 	response.OkData("获取成功", list, c)
 }

+ 1 - 1
controller/wechat/wechat.go

@@ -157,7 +157,7 @@ func GetEncryptInfo(c *gin.Context) {
 // @Title 微信获取签名接口
 // @Description 微信获取签名接口
 // @Param   Url   query   string  true       "url地址"
-// @Success 200 {object} models.WechatSign
+// @Success 200 {string} string "获取成功"
 // @router /getWxJsConf [get]
 func GetWxJsConf(c *gin.Context) {
 	getUrl := c.DefaultQuery("Url", "")

File diff suppressed because it is too large
+ 794 - 47
docs/docs.go


File diff suppressed because it is too large
+ 794 - 47
docs/swagger.json


+ 513 - 3
docs/swagger.yaml

@@ -78,6 +78,8 @@ definitions:
     type: object
   admin.Admin:
     properties:
+      adminAvatar:
+        type: string
       adminName:
         type: string
       authority:
@@ -136,7 +138,7 @@ definitions:
         description: 角色编码
         type: string
     type: object
-  chart_edb_mapping.ChartEdbInfoMapping:
+  chart_edb_mapping.ChartEdbInfoMappingList:
     properties:
       chartColor:
         type: string
@@ -192,13 +194,20 @@ definitions:
       unit:
         type: string
     type: object
+  chart_info.ChartBeforeNext:
+    properties:
+      nextChart:
+        $ref: '#/definitions/chart_info.ChartSortInfo'
+      prevChart:
+        $ref: '#/definitions/chart_info.ChartSortInfo'
+    type: object
   chart_info.ChartInfoDetailResp:
     properties:
       chartInfo:
         $ref: '#/definitions/chart_info.ChartInfoView'
       edbInfoList:
         items:
-          $ref: '#/definitions/chart_edb_mapping.ChartEdbInfoMapping'
+          $ref: '#/definitions/chart_edb_mapping.ChartEdbInfoMappingList'
         type: array
     type: object
   chart_info.ChartInfoView:
@@ -215,6 +224,8 @@ definitions:
         type: integer
       chartName:
         type: string
+      chartSource:
+        type: string
       chartType:
         type: integer
       createTime:
@@ -285,6 +296,23 @@ definitions:
       minData:
         type: number
     type: object
+  chart_info.ChartSortInfo:
+    properties:
+      chartInfoId:
+        type: integer
+      myChartClassifyId:
+        type: integer
+      myChartClassifyName:
+        type: string
+      myChartId:
+        type: integer
+      sort:
+        type: integer
+      switch:
+        type: integer
+      uniqueCode:
+        type: string
+    type: object
   chart_info.SaveChartInfoReq:
     properties:
       calendar:
@@ -314,6 +342,26 @@ definitions:
       startDate:
         type: string
     type: object
+  company.FiccPermissionList:
+    properties:
+      classifyName:
+        type: string
+      hasPermission:
+        type: boolean
+      items:
+        items:
+          $ref: '#/definitions/company.PermissionItem'
+        type: array
+    type: object
+  company.PermissionItem:
+    properties:
+      hasPermission:
+        type: boolean
+      permissionId:
+        type: integer
+      permissionName:
+        type: string
+    type: object
   logic.ApplyVariety:
     properties:
       name:
@@ -395,7 +443,7 @@ definitions:
       seasonStartDate:
         type: string
       sort:
-        type: integer
+        type: number
       startDate:
         type: string
       sysUserId:
@@ -435,6 +483,85 @@ definitions:
         description: 排序字段,值越小,越靠前
         type: integer
     type: object
+  response.CommunityQuestionAudioItem:
+    properties:
+      audio_play_seconds:
+        type: string
+      audio_size:
+        type: string
+      audio_url:
+        type: string
+      community_question_id:
+        type: integer
+      sort:
+        type: integer
+    type: object
+  response.CommunityQuestionItem:
+    properties:
+      audio_list:
+        items:
+          $ref: '#/definitions/response.CommunityQuestionAudioItem'
+        type: array
+      auth_ok:
+        type: boolean
+      chart_permission_id:
+        type: integer
+      chart_permission_name:
+        type: string
+      community_question_id:
+        type: integer
+      create_time:
+        type: string
+      is_read:
+        type: integer
+      is_top:
+        type: integer
+      question_content:
+        type: string
+      replier_avatar:
+        type: string
+      replier_rank:
+        type: string
+      replier_real_name:
+        type: string
+      reply_time:
+        type: string
+      user_id:
+        type: integer
+    type: object
+  response.CommunityQuestionList:
+    properties:
+      permissionInfo:
+        $ref: '#/definitions/response.PermissionCheckInfo'
+      questionList:
+        items:
+          $ref: '#/definitions/response.CommunityQuestionItem'
+        type: array
+    type: object
+  response.CommunityReplyTotal:
+    properties:
+      replied:
+        type: integer
+      total:
+        type: integer
+      wait:
+        type: integer
+    type: object
+  response.CustomerInfo:
+    properties:
+      company_name:
+        type: string
+      has_apply:
+        type: boolean
+      is_suspend:
+        type: integer
+      mobile:
+        type: string
+      name:
+        type: string
+      status:
+        type: string
+    type: object
   response.LoginResp:
     properties:
       authorization:
@@ -444,18 +571,48 @@ definitions:
       user_id:
         type: integer
     type: object
+  response.PermissionCheckInfo:
+    properties:
+      customer_info:
+        $ref: '#/definitions/response.CustomerInfo'
+      hz_phone:
+        type: string
+      mobile:
+        type: string
+      name:
+        type: string
+      type:
+        type: string
+    type: object
+  services.SharePosterReq:
+    properties:
+      code_page:
+        type: string
+      code_scene:
+        type: string
+      pars:
+        type: string
+      source:
+        type: string
+      version:
+        type: string
+    type: object
   user.ApplyReq:
     properties:
       business_card_url:
         type: string
       company_name:
         type: string
+      from_page:
+        type: string
       permission:
         type: string
       real_name:
         type: string
       source:
         type: integer
+      source_agent:
+        type: integer
     type: object
   user.CompanyPermission:
     properties:
@@ -1005,6 +1162,47 @@ paths:
       summary: 获取图表列表
       tags:
       - 图库模块
+  /company/permission/tree:
+    get:
+      consumes:
+      - application/json
+      description: 获取FICC品种权限列表
+      parameters:
+      - description: Bearer 31a165baebe6dec616b1f8f3207b4273
+        in: header
+        name: Authorization
+        required: true
+        type: string
+      responses:
+        "200":
+          description: OK
+          schema:
+            items:
+              $ref: '#/definitions/company.FiccPermissionList'
+            type: array
+        "400":
+          description: 获取失败
+          schema:
+            type: string
+      security:
+      - ApiKeyAuth: []
+      summary: 获取FICC品种权限列表
+      tags:
+      - 客户模块
+  /getWxJsConf:
+    get:
+      description: 微信获取签名接口
+      parameters:
+      - description: url地址
+        in: query
+        name: Url
+        required: true
+        type: string
+      responses:
+        "200":
+          description: 获取成功
+          schema:
+            type: string
   /my_chart/editChartInfo:
     post:
       consumes:
@@ -1036,6 +1234,39 @@ paths:
       summary: 编辑图表信息
       tags:
       - 图库模块
+  /my_chart/getChartBeforeAndNext:
+    get:
+      consumes:
+      - application/json
+      description: 获取当前图表上一张及下一张信息
+      parameters:
+      - description: Bearer 31a165baebe6dec616b1f8f3207b4273
+        in: header
+        name: Authorization
+        required: true
+        type: string
+      - description: 我的图表ID
+        in: query
+        name: MyChartId
+        type: string
+      - description: 我的图表分类ID
+        in: query
+        name: MyChartClassifyId
+        type: string
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/chart_info.ChartBeforeNext'
+        "400":
+          description: 获取失败
+          schema:
+            type: string
+      security:
+      - ApiKeyAuth: []
+      summary: 获取当前图表上一张及下一张信息
+      tags:
+      - 图库模块
   /my_chart/getChartInfoDetail:
     get:
       consumes:
@@ -1132,6 +1363,37 @@ paths:
       summary: 移动我的图表分类
       tags:
       - 图库模块
+  /my_chart/refreshChartInfo:
+    post:
+      consumes:
+      - application/json
+      description: 刷新图表信息
+      parameters:
+      - description: Bearer 31a165baebe6dec616b1f8f3207b4273
+        in: header
+        name: Authorization
+        required: true
+        type: string
+      - description: 请求参数
+        in: body
+        name: data
+        required: true
+        schema:
+          $ref: '#/definitions/chart_info.SaveChartInfoReq'
+      responses:
+        "200":
+          description: 操作成功
+          schema:
+            type: string
+        "400":
+          description: 操作失败
+          schema:
+            type: string
+      security:
+      - ApiKeyAuth: []
+      summary: 刷新图表信息
+      tags:
+      - 图库模块
   /public/get_apply_variety_list:
     get:
       consumes:
@@ -1159,6 +1421,60 @@ paths:
       summary: 获取所有可以申请的品种权限列表
       tags:
       - 公共模块
+  /public/get_share_poster:
+    post:
+      consumes:
+      - application/json
+      description: 获取分享海报
+      parameters:
+      - description: type json string
+        in: body
+        name: request
+        required: true
+        schema:
+          $ref: '#/definitions/services.SharePosterReq'
+      responses:
+        "200":
+          description: 获取成功
+          schema:
+            type: string
+        "400":
+          description: 获取失败
+          schema:
+            type: string
+      security:
+      - ApiKeyAuth: []
+      summary: 获取分享海报
+      tags:
+      - 公共模块
+  /public/get_suncode_scene:
+    get:
+      description: 获取小程序太阳码scene值
+      parameters:
+      - description: Bearer 31a165baebe6dec616b1f8f3207b4273
+        in: header
+        name: Authorization
+        required: true
+        type: string
+      - description: scene_key值
+        in: query
+        name: scene_key
+        required: true
+        type: string
+      responses:
+        "200":
+          description: 获取成功
+          schema:
+            type: string
+        "400":
+          description: 获取失败
+          schema:
+            type: string
+      security:
+      - ApiKeyAuth: []
+      summary: 获取小程序太阳码scene值
+      tags:
+      - 公共模块
   /public/upload:
     post:
       consumes:
@@ -1188,6 +1504,170 @@ paths:
       summary: 文件上传
       tags:
       - 公共模块
+  /question/ask:
+    post:
+      description: 发布提问
+      parameters:
+      - description: 问题内容
+        in: query
+        name: question_content
+        required: true
+        type: string
+      responses:
+        "200":
+          description: 操作成功
+          schema:
+            type: string
+        "400":
+          description: 操作失败
+          schema:
+            type: string
+      tags:
+      - 问答社区模块
+  /question/detail:
+    get:
+      description: 获取问答详情
+      parameters:
+      - description: 问答ID
+        in: query
+        name: question_id
+        required: true
+        type: integer
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/response.CommunityQuestionItem'
+        "400":
+          description: 获取失败
+          schema:
+            type: string
+      tags:
+      - 问答社区模块
+  /question/list:
+    get:
+      description: 获取问答列表
+      parameters:
+      - description: 页码
+        in: query
+        name: page_index
+        type: integer
+      - description: 每页数量
+        in: query
+        name: page_size
+        type: integer
+      - description: 只看我的
+        in: query
+        name: only_mine
+        type: integer
+      - description: 品种权限ID
+        in: query
+        name: chart_permission_id
+        type: integer
+      - description: 回复状态 0-全部 2-待回答 3-已回答
+        in: query
+        name: reply_status
+        type: integer
+      - description: 回复人ID
+        in: query
+        name: replier_user_id
+        type: integer
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/response.CommunityQuestionList'
+        "400":
+          description: 获取失败
+          schema:
+            type: string
+      tags:
+      - 问答社区模块
+  /question/reply:
+    post:
+      description: 发布回复
+      parameters:
+      - description: 问答ID
+        in: query
+        name: question_id
+        required: true
+        type: integer
+      - description: 音频列表
+        in: query
+        name: audio_list
+        required: true
+        type: object
+      responses:
+        "200":
+          description: 操作成功
+          schema:
+            type: string
+        "400":
+          description: 操作失败
+          schema:
+            type: string
+      tags:
+      - 问答社区模块
+  /question/reply/read:
+    post:
+      description: 已读回复
+      parameters:
+      - description: 问答ID
+        in: query
+        name: question_id
+        required: true
+        type: integer
+      responses:
+        "200":
+          description: 操作成功
+          schema:
+            type: string
+        "400":
+          description: 操作失败
+          schema:
+            type: string
+      tags:
+      - 问答社区模块
+  /question/reply/total:
+    get:
+      description: 问答列表数量统计
+      parameters:
+      - description: 回复人ID
+        in: query
+        name: replier_user_id
+        required: true
+        type: integer
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/response.CommunityReplyTotal'
+        "400":
+          description: 获取失败
+          schema:
+            type: string
+      tags:
+      - 问答社区模块
+  /question/reply/upload_audio:
+    post:
+      description: 上传回复音频
+      parameters:
+      - description: 音频文件
+        in: query
+        name: file
+        required: true
+        type: string
+      responses:
+        "200":
+          description: 上传成功
+          schema:
+            type: string
+        "400":
+          description: 上传失败
+          schema:
+            type: string
+      tags:
+      - 问答社区模块
   /report/research_report:
     get:
       consumes:
@@ -1218,6 +1698,36 @@ paths:
       summary: 获取报告详情
       tags:
       - 报告接口
+  /report/research_report_chapter:
+    get:
+      consumes:
+      - application/json
+      description: 获取报告章节详情
+      parameters:
+      - description: Bearer 31a165baebe6dec616b1f8f3207b4273
+        in: header
+        name: Authorization
+        required: true
+        type: string
+      - description: 章节ID
+        in: query
+        name: research_report_type_id
+        required: true
+        type: integer
+      responses:
+        "200":
+          description: OK
+          schema:
+            $ref: '#/definitions/yb_activity.ActivityDetail'
+        "400":
+          description: 获取失败
+          schema:
+            type: string
+      security:
+      - ApiKeyAuth: []
+      summary: 获取报告章节详情
+      tags:
+      - 报告接口
   /user/apply:
     post:
       consumes:

+ 1 - 0
go.mod

@@ -25,6 +25,7 @@ require (
 	github.com/spf13/viper v1.9.0
 	github.com/swaggo/gin-swagger v1.3.3
 	github.com/swaggo/swag v1.7.4
+	github.com/tosone/minimp3 v1.0.1
 	golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
 	golang.org/x/image v0.0.0-20190802002840-cff245a6509b
 	golang.org/x/sys v0.0.0-20211107104306-e0b2ad06fe42 // indirect

+ 2 - 0
init_serve/router.go

@@ -50,6 +50,8 @@ func InitRouter() (r *gin.Engine) {
 	routers.InitPurchase(r)
 	// 研报相关路由
 	routers.InitReport(r)
+	// 问答社区相关路由
+	routers.InitCommunity(r)
 	// 设置静态文件夹件路径
 	r.StaticFS("/static", http.Dir("./static"))
 	//pc相关路由

+ 38 - 0
models/request/community.go

@@ -0,0 +1,38 @@
+package request
+
+type QuestionListReq struct {
+	PageIndex         int `json:"page_index" form:"page_index"`
+	PageSize          int `json:"page_size" form:"page_size"`
+	OnlyMine          int `json:"only_mine" form:"only_mine"`
+	ChartPermissionId int `json:"chart_permission_id" form:"chart_permission_id"`
+	ReplyStatus       int `json:"reply_status" form:"reply_status"`
+	ReplierUserId     int `json:"replier_user_id" form:"replier_user_id"`
+}
+
+type QuestionDetailReq struct {
+	QuestionId int `json:"question_id" form:"question_id"`
+}
+
+type QuestionAskReq struct {
+	QuestionContent string `json:"question_content"`
+}
+
+type QuestionReplyReq struct {
+	QuestionId int                  `json:"question_id" form:"question_id"`
+	AudioList  []*ReplyReqAudioList `json:"audio_list" form:"audio_list"`
+}
+
+type ReplyReqAudioList struct {
+	AudioUrl         string `json:"audio_url" form:"audio_url"`
+	AudioPlaySeconds string `json:"audio_play_seconds" form:"audio_url"`
+	AudioSize        string `json:"audio_size" form:"audio_size"`
+	Sort             int    `json:"sort" form:"sort"`
+}
+
+type QuestionReadReq struct {
+	QuestionId int `json:"question_id" form:"question_id"`
+}
+
+type ReplyListTotalReq struct {
+	ReplierUserId int `json:"replier_user_id" form:"replier_user_id"`
+}

+ 44 - 0
models/response/community.go

@@ -0,0 +1,44 @@
+package response
+
+type CommunityQuestionList struct {
+	QuestionList   []*CommunityQuestionItem
+	//PermissionInfo PermissionCheckInfo
+}
+
+type CommunityQuestionItem struct {
+	CommunityQuestionID int                           `json:"community_question_id"`
+	UserId              int                           `json:"user_id"`
+	QuestionContent     string                        `json:"question_content"`
+	ReplierRealName     string                        `json:"replier_real_name"`
+	ReplierRank         string                        `json:"replier_rank"`
+	ReplierAvatar       string                        `json:"replier_avatar"`
+	ChartPermissionID   int                           `json:"chart_permission_id"`
+	ChartPermissionName string                        `json:"chart_permission_name"`
+	IsRead              int                           `json:"is_read"`
+	CreateTime          string                        `json:"create_time"`
+	ReplyTime           string                        `json:"reply_time"`
+	IsTop               int                           `json:"is_top"`
+	AuthOk              bool                          `json:"auth_ok" description:"是否有权限"`
+	PermissionInfo      PermissionCheckInfo           `json:"permission_info"`
+	AudioList           []*CommunityQuestionAudioItem `json:"audio_list"`
+}
+
+type CommunityQuestionAudioItem struct {
+	CommunityQuestionID int    `json:"community_question_id"`
+	AudioURL            string `json:"audio_url"`
+	AudioPlaySeconds    string `json:"audio_play_seconds"`
+	AudioSize           string `json:"audio_size"`
+	Sort                int    `json:"sort"`
+}
+
+type CommunityReplyTotal struct {
+	Wait    int `json:"wait"`
+	Replied int `json:"replied"`
+	Total   int `json:"total"`
+}
+
+type CommunityQuestionAudioUpload struct {
+	AudioURL         string `json:"audio_url"`
+	AudioPlaySeconds string `json:"audio_play_seconds"`
+	AudioSize        string `json:"audio_size"`
+}

+ 1 - 0
models/tables/admin/admin.go

@@ -6,6 +6,7 @@ import "time"
 type Admin struct {
 	AdminID                 int64     `gorm:"primaryKey;column:admin_id;type:bigint(20);not null" json:"-"`
 	AdminName               string    `gorm:"uniqueIndex:un;index:name;index:admin_pass;column:admin_name;type:varchar(60);not null" json:"adminName"`
+	AdminAvatar             string    `gorm:"column:admin_avatar;type:varchar(255)" json:"adminAvatar"`
 	RealName                string    `gorm:"column:real_name;type:varchar(60)" json:"realName"`
 	Password                string    `gorm:"index:password;index:admin_pass;column:password;type:varchar(60);not null" json:"password"`
 	LastUpdatedPasswordTime time.Time `gorm:"column:last_updated_password_time;type:datetime" json:"lastUpdatedPasswordTime"`

+ 45 - 0
models/tables/user_template_record/entity.go

@@ -0,0 +1,45 @@
+package user_template_record
+
+// UserTemplateRecord [...]
+type UserTemplateRecord struct {
+	ID         int    `gorm:"primaryKey;column:id;type:int(11);not null" json:"-"`
+	UserID     int    `gorm:"column:user_id;type:int(11);default:0" json:"userId"`
+	OpenID     string `gorm:"column:open_id;type:varchar(100);default:''" json:"openId"`
+	Resource   string `gorm:"column:resource;type:varchar(20)" json:"resource"` // 资源:报告id/手机号/活动id
+	SendData   string `gorm:"column:send_data;type:text" json:"sendData"`
+	Result     string `gorm:"column:result;type:text" json:"result"`
+	CreateDate string `gorm:"column:create_date;type:date" json:"createDate"`
+	CreateTime string `gorm:"column:create_time;type:datetime" json:"createTime"`
+	SendStatus int    `gorm:"column:send_status;type:tinyint(4);default:1" json:"sendStatus"` // 1:发送成功,0:发送失败
+	SendType   int    `gorm:"column:send_type;type:tinyint(4);default:1" json:"sendType"`     // 1:报告,2:指标更新提醒,3:审批通知,4:销售领取客户通知,5:活动取消通知,6活动更改时间通知,7:关注的作者发布报告通知,8:发送日报(周报、双周报、月报)模板消息,9:活动预约/报名时间通知
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *UserTemplateRecord) TableName() string {
+	return "user_template_record"
+}
+
+// UserTemplateRecordColumns get sql column name.获取数据库列名
+var UserTemplateRecordColumns = struct {
+	ID         string
+	UserID     string
+	OpenID     string
+	Resource   string
+	SendData   string
+	Result     string
+	CreateDate string
+	CreateTime string
+	SendStatus string
+	SendType   string
+}{
+	ID:         "id",
+	UserID:     "user_id",
+	OpenID:     "open_id",
+	Resource:   "resource",
+	SendData:   "send_data",
+	Result:     "result",
+	CreateDate: "create_date",
+	CreateTime: "create_time",
+	SendStatus: "send_status",
+	SendType:   "send_type",
+}

+ 8 - 0
models/tables/user_template_record/model.go

@@ -0,0 +1,8 @@
+package user_template_record
+
+import "hongze/hongze_yb/global"
+
+func (item *UserTemplateRecord) Create() (err error) {
+	err = global.DEFAULT_MYSQL.Create(item).Error
+	return
+}

+ 100 - 0
models/tables/yb_community_question/entity.go

@@ -0,0 +1,100 @@
+package yb_community_question
+
+import (
+	"time"
+)
+
+// YbCommunityQuestion 研报-问答社区表
+type YbCommunityQuestion struct {
+	CommunityQuestionID     int       `gorm:"primaryKey;column:community_question_id;type:int(10) unsigned;not null" json:"-"`
+	UserID                  int       `gorm:"index:idx_user_id;column:user_id;type:int(10) unsigned;not null;default:0" json:"userId"`                                    // 提问用户ID
+	UserOpenid              string    `gorm:"column:user_openid;type:varchar(32);not null;default:''" json:"userOpenid"`                                                  // 提问人openid
+	Mobile                  string    `gorm:"column:mobile;type:varchar(20);not null;default:''" json:"mobile"`                                                           // 用户手机号
+	RealName                string    `gorm:"column:real_name;type:varchar(100);not null;default:''" json:"realName"`                                                     // 用户名
+	QuestionContent         string    `gorm:"column:question_content;type:varchar(255);not null;default:''" json:"questionContent"`                                       // 问题描述
+	ReplierUserID           int       `gorm:"index:idx_replier_user_id;column:replier_user_id;type:int(10) unsigned;not null;default:0" json:"replierUserId"`             // 回复人的user_id
+	ReplierOpenid           string    `gorm:"column:replier_openid;type:varchar(32);not null;default:''" json:"replierOpenid"`                                            // 回复人openid
+	ReplierAdminID          int       `gorm:"column:replier_admin_id;type:int(10) unsigned;not null;default:0" json:"replierAdminId"`                                     // 回复人关联的admin_id
+	ReplierRealName         string    `gorm:"column:replier_real_name;type:varchar(30);not null;default:''" json:"replierRealName"`                                       // 回复人姓名
+	ReplierAvatar           string    `gorm:"column:replier_avatar;type:varchar(255);not null;default:''" json:"replierAvatar"`                                           // 回复人头像
+	ResearchGroupFirstID    int       `gorm:"column:research_group_first_id;type:int(10) unsigned;not null;default:0" json:"researchGroupFirstId"`                        // 回复人研究方向一级分组ID
+	ResearchGroupSecondID   int       `gorm:"column:research_group_second_id;type:int(10) unsigned;not null;default:0" json:"researchGroupSecondId"`                      // 回复人研究方向二级分组ID
+	ResearchGroupFirstName  string    `gorm:"column:research_group_first_name;type:varchar(100);not null;default:''" json:"researchGroupFirstName"`                       // 研究方向一级分组名称
+	ResearchGroupSecondName string    `gorm:"column:research_group_second_name;type:varchar(100);not null;default:''" json:"researchGroupSecondName"`                     // 研究方向二级分组名称
+	DistributeAdminID       int       `gorm:"column:distribute_admin_id;type:int(10) unsigned;not null;default:0" json:"distributeAdminId"`                               // 分配人admin_id
+	DistributeTime          time.Time `gorm:"column:distribute_time;type:datetime" json:"distributeTime"`                                                                 // 分配时间
+	ChartPermissionID       int       `gorm:"index:idx_chart_permission_id;column:chart_permission_id;type:int(10) unsigned;not null;default:0" json:"chartPermissionId"` // 关联权限ID
+	ChartPermissionName     string    `gorm:"column:chart_permission_name;type:varchar(100);not null;default:''" json:"chartPermissionName"`                              // 关联权限name
+	IsRead                  int       `gorm:"column:is_read;type:tinyint(4) unsigned;not null;default:0" json:"isRead"`                                                   // 用户是否已读 0-未读 1-已读
+	ReplyStatus             int       `gorm:"column:reply_status;type:tinyint(4) unsigned;not null;default:0" json:"replyStatus"`                                         // 回复状态 1-待分配 2-待回答 3-已回答
+	MsgSendStatus           int       `gorm:"column:msg_send_status;type:tinyint(4) unsigned;not null;default:0" json:"msgSendStatus"`                                    // 消息推送进度 0-待推送 1-已推送回答人 2-已推送提问人
+	ReplyTime               time.Time `gorm:"column:reply_time;type:datetime" json:"replyTime"`                                                                           // 回复时间
+	CreateTime              time.Time `gorm:"column:create_time;type:datetime;default:CURRENT_TIMESTAMP" json:"createTime"`                                               // 提问时间
+	ModifyTime              time.Time `gorm:"column:modify_time;type:datetime;default:CURRENT_TIMESTAMP" json:"modifyTime"`                                               // 修改时间
+	IsDeleted               int       `gorm:"column:is_deleted;type:tinyint(4) unsigned;not null;default:0" json:"isDeleted"`                                             // 是否已删除 0-否 1-是
+	DeleteTime              time.Time `gorm:"column:delete_time;type:datetime" json:"deleteTime"`                                                                         // 删除时间
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *YbCommunityQuestion) TableName() string {
+	return "yb_community_question"
+}
+
+// YbCommunityQuestionColumns get sql column name.获取数据库列名
+var YbCommunityQuestionColumns = struct {
+	CommunityQuestionID     string
+	UserID                  string
+	UserOpenid              string
+	Mobile                  string
+	RealName                string
+	QuestionContent         string
+	ReplierUserID           string
+	ReplierOpenid           string
+	ReplierAdminID          string
+	ReplierRealName         string
+	ReplierAvatar           string
+	ResearchGroupFirstID    string
+	ResearchGroupSecondID   string
+	ResearchGroupFirstName  string
+	ResearchGroupSecondName string
+	DistributeAdminID       string
+	DistributeTime          string
+	ChartPermissionID       string
+	ChartPermissionName     string
+	IsRead                  string
+	ReplyStatus             string
+	MsgSendStatus           string
+	ReplyTime               string
+	CreateTime              string
+	ModifyTime              string
+	IsDeleted               string
+	DeleteTime              string
+}{
+	CommunityQuestionID:     "community_question_id",
+	UserID:                  "user_id",
+	UserOpenid:              "user_openid",
+	Mobile:                  "mobile",
+	RealName:                "real_name",
+	QuestionContent:         "question_content",
+	ReplierUserID:           "replier_user_id",
+	ReplierOpenid:           "replier_openid",
+	ReplierAdminID:          "replier_admin_id",
+	ReplierRealName:         "replier_real_name",
+	ReplierAvatar:           "replier_avatar",
+	ResearchGroupFirstID:    "research_group_first_id",
+	ResearchGroupSecondID:   "research_group_second_id",
+	ResearchGroupFirstName:  "research_group_first_name",
+	ResearchGroupSecondName: "research_group_second_name",
+	DistributeAdminID:       "distribute_admin_id",
+	DistributeTime:          "distribute_time",
+	ChartPermissionID:       "chart_permission_id",
+	ChartPermissionName:     "chart_permission_name",
+	IsRead:                  "is_read",
+	ReplyStatus:             "reply_status",
+	MsgSendStatus:           "msg_send_status",
+	ReplyTime:               "reply_time",
+	CreateTime:              "create_time",
+	ModifyTime:              "modify_time",
+	IsDeleted:               "is_deleted",
+	DeleteTime:              "delete_time",
+}

+ 79 - 0
models/tables/yb_community_question/model.go

@@ -0,0 +1,79 @@
+package yb_community_question
+
+import (
+	"errors"
+	"hongze/hongze_yb/global"
+	"hongze/hongze_yb/models/tables/yb_community_question_audio"
+	"hongze/hongze_yb/utils"
+)
+
+func (item *YbCommunityQuestion) Create() (err error) {
+	err = global.DEFAULT_MYSQL.Create(item).Error
+	return
+}
+
+func (item *YbCommunityQuestion) Update(updateCols []string) (err error) {
+	err = global.DEFAULT_MYSQL.Model(item).Select(updateCols).Updates(*item).Error
+	return
+}
+
+func GetPageListByCondition(where map[string]interface{}, pageIndex, pageSize int) (list []*YbCommunityQuestion, err error) {
+	cond, vals, e := utils.WhereBuild(where)
+	if e != nil {
+		err = errors.New("系统异常,生成查询语句失败")
+		return
+	}
+	offset := (pageIndex - 1) * pageSize
+	err = global.DEFAULT_MYSQL.Model(YbCommunityQuestion{}).Where(cond, vals...).Offset(offset).Limit(pageSize).Order("reply_time DESC").Scan(&list).Error
+	return
+}
+
+func GetWaitingReplyNum(replierId int) (num int64, err error) {
+	err = global.DEFAULT_MYSQL.Model(YbCommunityQuestion{}).
+		Where("replier_user_id =", replierId).
+		Count(&num).Error
+	return
+}
+
+func GetItemById(questionId int) (item *YbCommunityQuestion, err error) {
+	err = global.DEFAULT_MYSQL.Model(YbCommunityQuestion{}).
+		Where("community_question_id = ? AND is_deleted = 0", questionId).
+		First(&item).Error
+	return
+}
+
+func UpdateQuestionAndAudioList(item *YbCommunityQuestion, updateCols []string, audioList []*yb_community_question_audio.YbCommunityQuestionAudio) (err error) {
+	tx := global.DEFAULT_MYSQL.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		} else {
+			tx.Commit()
+		}
+	}()
+	if err = tx.Model(item).Select(updateCols).Updates(*item).Error; err != nil {
+		return
+	}
+	if err = tx.Where("community_question_id = ?", item.CommunityQuestionID).
+		Delete(yb_community_question_audio.YbCommunityQuestionAudio{}).Error; err != nil {
+		return
+	}
+	if err = tx.Model(yb_community_question_audio.YbCommunityQuestionAudio{}).
+		CreateInBatches(audioList, len(audioList)).Error; err != nil {
+	}
+	return
+}
+
+type ReplierQuestionCount struct {
+	ReplyStatus int `json:"reply_status"`
+	Total       int `json:"total"`
+}
+
+func GetReplierQuestionCount(replierUserId int) (list []*ReplierQuestionCount, err error) {
+	err = global.DEFAULT_MYSQL.Model(YbCommunityQuestion{}).
+		Select("reply_status, COUNT(1) total").
+		Where("replier_user_id = ? AND is_deleted = 0", replierUserId).
+		Group("reply_status").
+		Scan(&list).Error
+	return
+}

+ 40 - 0
models/tables/yb_community_question_audio/entity.go

@@ -0,0 +1,40 @@
+package yb_community_question_audio
+
+import (
+	"time"
+)
+
+// YbCommunityQuestionAudio 研报-问答社区音频表
+type YbCommunityQuestionAudio struct {
+	CommunityQuestionAudioID int       `gorm:"primaryKey;column:community_question_audio_id;type:int(10) unsigned;not null" json:"-"`
+	CommunityQuestionID      int       `gorm:"index:idx_question_id;column:community_question_id;type:int(10) unsigned;not null;default:0" json:"communityQuestionId"` // 社区问题ID
+	AudioURL                 string    `gorm:"column:audio_url;type:varchar(255);not null;default:''" json:"audioUrl"`                                                 // 音频地址
+	AudioPlaySeconds         string    `gorm:"column:audio_play_seconds;type:varchar(30);not null;default:''" json:"audioPlaySeconds"`                                 // 音频播放时长,单位s
+	AudioSize                string    `gorm:"column:audio_size;type:varchar(30);not null;default:''" json:"audioSize"`                                                // 音频大小,单位M
+	Sort                     int       `gorm:"column:sort;type:int(10) unsigned;not null;default:0" json:"sort"`                                                       // 排序
+	CreateTime               time.Time `gorm:"column:create_time;type:datetime;default:CURRENT_TIMESTAMP" json:"createTime"`                                           // 创建时间
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *YbCommunityQuestionAudio) TableName() string {
+	return "yb_community_question_audio"
+}
+
+// YbCommunityQuestionAudioColumns get sql column name.获取数据库列名
+var YbCommunityQuestionAudioColumns = struct {
+	CommunityQuestionAudioID string
+	CommunityQuestionID      string
+	AudioURL                 string
+	AudioPlaySeconds         string
+	AudioSize                string
+	Sort                     string
+	CreateTime               string
+}{
+	CommunityQuestionAudioID: "community_question_audio_id",
+	CommunityQuestionID:      "community_question_id",
+	AudioURL:                 "audio_url",
+	AudioPlaySeconds:         "audio_play_seconds",
+	AudioSize:                "audio_size",
+	Sort:                     "sort",
+	CreateTime:               "create_time",
+}

+ 33 - 0
models/tables/yb_community_question_audio/model.go

@@ -0,0 +1,33 @@
+package yb_community_question_audio
+
+import (
+	"hongze/hongze_yb/global"
+)
+
+func (item *YbCommunityQuestionAudio) Create() (err error) {
+	err = global.DEFAULT_MYSQL.Create(item).Error
+	return
+}
+
+func GetListByQuestrionIds(idArr []int) (list []*YbCommunityQuestionAudio, err error) {
+	if len(idArr) == 0 {
+		return
+	}
+	err = global.DEFAULT_MYSQL.
+		Model(YbCommunityQuestionAudio{}).
+		Select("community_question_id, audio_url, audio_play_seconds, audio_size, sort").
+		Where("community_question_id IN ?", idArr).
+		Order("community_question_id ASC, sort ASC").
+		Scan(&list).Error
+	return
+}
+
+func GetListByQuestionId(questionId int) (list []*YbCommunityQuestionAudio, err error) {
+	err = global.DEFAULT_MYSQL.
+		Model(YbCommunityQuestionAudio{}).
+		Select("community_question_id, audio_url, audio_play_seconds, audio_size, sort").
+		Where("community_question_id = ?", questionId).
+		Order("sort ASC").
+		Scan(&list).Error
+	return
+}

+ 18 - 0
routers/community.go

@@ -0,0 +1,18 @@
+package routers
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/hongze_yb/controller/community"
+	"hongze/hongze_yb/middleware"
+)
+
+func InitCommunity(r *gin.Engine)  {
+	rGroup := r.Group("api/community").Use(middleware.Token())
+	rGroup.GET("/question/list", community.QuestionList)
+	rGroup.GET("/question/detail", community.QuestionDetail)
+	rGroup.POST("/question/ask", community.QuestionAsk)
+	rGroup.POST("/question/reply", community.QuestionReply)
+	rGroup.POST("/question/reply/read", community.QuestionReplyRead)
+	rGroup.GET("/question/reply/total", community.QuestionReplyTotal)
+	rGroup.POST("/question/reply/upload_audio", community.QuestionUploadAudio)
+}

+ 1 - 0
routers/company.go

@@ -10,5 +10,6 @@ func InitCompany(r *gin.Engine) {
 	rGroup := r.Group("api/company").Use(middleware.Token())
 	{
 		rGroup.GET("/getPermissionList", company.GetHomeFiccPermissions)
+		rGroup.GET("/permission/tree", company.GetPermissionTree)
 	}
 }

+ 278 - 0
services/community/question.go

@@ -0,0 +1,278 @@
+package community
+
+import (
+	"errors"
+	"fmt"
+	"hongze/hongze_yb/models/request"
+	"hongze/hongze_yb/models/response"
+	"hongze/hongze_yb/models/tables/user_record"
+	"hongze/hongze_yb/models/tables/yb_community_question"
+	"hongze/hongze_yb/models/tables/yb_community_question_audio"
+	"hongze/hongze_yb/services/company"
+	"hongze/hongze_yb/services/user"
+	"hongze/hongze_yb/services/wechat"
+	"hongze/hongze_yb/utils"
+	"time"
+)
+
+// GetQuestionList 获取问答列表
+func GetQuestionList(pageIndex, pageSize, onlyMine, chartPermissionId, replyStatus, replierUserId int, userInfo user.UserInfo) (resp *response.CommunityQuestionList, err error) {
+	condition := make(map[string]interface{})
+	condition["is_deleted ="] = 0
+	// 用户身份
+	isAdmin, _, e := user.GetAdminByUserInfo(userInfo)
+	if e != nil {
+		err = errors.New("获取用户身份失败 Err:" + e.Error())
+		return
+	}
+	if replierUserId > 0 {
+		condition["replier_user_id ="] = replierUserId
+		// 回复列表
+		if replyStatus > 0 {
+			condition["reply_status ="] = replyStatus
+		}
+	} else {
+		// 问题列表
+		if onlyMine == 1 {
+			if isAdmin {
+				condition["replier_user_id ="] = userInfo.UserID
+			} else {
+				condition["user_id ="] = userInfo.UserID
+			}
+		} else {
+			// 默认只展示已回复的
+			condition["reply_status ="] = 3
+		}
+	}
+	if chartPermissionId > 0 {
+		condition["chart_permission_id ="] = chartPermissionId
+	}
+	// 问题列表
+	questionList, e := yb_community_question.GetPageListByCondition(condition, pageIndex, pageSize)
+	if e != nil {
+		err = errors.New("获取问题列表失败 Err:" + e.Error())
+		return
+	}
+	listLen := len(questionList)
+	if listLen == 0 {
+		return
+	}
+	idArr := make([]int, 0)
+	for i := 0; i < listLen; i++ {
+		idArr = append(idArr, questionList[i].CommunityQuestionID)
+	}
+	// 音频列表
+	audioList, e := yb_community_question_audio.GetListByQuestrionIds(idArr)
+	if e != nil {
+		err = errors.New("获取音频列表失败 Err:" + e.Error())
+		return
+	}
+	// 用户权限
+	authOk, permissionInfo, _, e := company.CheckBaseFiccPermission(userInfo.CompanyID, int(userInfo.UserID))
+	if e != nil {
+		err = errors.New("获取用户权限失败 Err:" + e.Error())
+		return
+	}
+
+	userId := int(userInfo.UserID)
+	resp = new(response.CommunityQuestionList)
+	respList := make([]*response.CommunityQuestionItem, 0)
+	for _, v := range questionList {
+		audios := make([]*response.CommunityQuestionAudioItem, 0)
+		for _, a := range audioList {
+			if a.CommunityQuestionID == v.CommunityQuestionID {
+				audios = append(audios, &response.CommunityQuestionAudioItem{
+					CommunityQuestionID: a.CommunityQuestionID,
+					AudioURL:            a.AudioURL,
+					AudioPlaySeconds:    a.AudioPlaySeconds,
+					AudioSize:           a.AudioSize,
+					Sort:                a.Sort,
+				})
+			}
+		}
+		replierRank := fmt.Sprintf("弘则%s研究员", v.ResearchGroupFirstName)
+		item := &response.CommunityQuestionItem{
+			CommunityQuestionID: v.CommunityQuestionID,
+			UserId:              v.UserID,
+			QuestionContent:     v.QuestionContent,
+			ReplierRealName:     v.ReplierRealName,
+			ReplierRank:         replierRank,
+			ReplierAvatar:       v.ReplierAvatar,
+			ChartPermissionID:   v.CommunityQuestionID,
+			ChartPermissionName: v.ChartPermissionName,
+			IsRead:              v.IsRead,
+			CreateTime:          v.CreateTime.Format(utils.FormatDateTime),
+			ReplyTime:           v.ReplyTime.Format(utils.FormatDateTime),
+			AuthOk:              authOk,
+			PermissionInfo:      permissionInfo,
+			AudioList:           audios,
+		}
+		if !isAdmin && item.IsRead == 0 && item.UserId == userId {
+			item.IsTop = 1
+		}
+		respList = append(respList, item)
+	}
+	resp.QuestionList = respList
+	//resp.PermissionInfo = permissionInfo
+	return
+}
+
+// GetQuestionDetail 获取问答详情
+func GetQuestionDetail(questionId int, userInfo user.UserInfo) (item *response.CommunityQuestionItem, err error) {
+	detail, e := yb_community_question.GetItemById(questionId)
+	if e != nil {
+		err = errors.New("获取问题详情失败 Err:" + e.Error())
+		return
+	}
+	audioList, e := yb_community_question_audio.GetListByQuestionId(questionId)
+	if e != nil {
+		err = errors.New("获取问题音频失败 Err:" + e.Error())
+		return
+	}
+	audios := make([]*response.CommunityQuestionAudioItem, 0)
+	for _, a := range audioList {
+		audios = append(audios, &response.CommunityQuestionAudioItem{
+			CommunityQuestionID: a.CommunityQuestionID,
+			AudioURL:            a.AudioURL,
+			AudioPlaySeconds:    a.AudioPlaySeconds,
+			AudioSize:           a.AudioSize,
+			Sort:                a.Sort,
+		})
+	}
+	replierRank := fmt.Sprintf("弘则%s研究员", detail.ResearchGroupFirstName)
+	// 用户权限
+	authOk, permissionInfo, _, e := company.CheckBaseFiccPermission(userInfo.CompanyID, int(userInfo.UserID))
+	if e != nil {
+		err = errors.New("获取用户权限失败 Err:" + e.Error())
+		return
+	}
+	item = &response.CommunityQuestionItem{
+		CommunityQuestionID: detail.CommunityQuestionID,
+		UserId:              detail.UserID,
+		QuestionContent:     detail.QuestionContent,
+		ReplierRealName:     detail.ReplierRealName,
+		ReplierRank:         replierRank,
+		ReplierAvatar:       detail.ReplierAvatar,
+		ChartPermissionID:   detail.ChartPermissionID,
+		ChartPermissionName: detail.ChartPermissionName,
+		IsRead:              detail.IsRead,
+		CreateTime:          detail.CreateTime.Format(utils.FormatDateTime),
+		ReplyTime:           detail.ReplyTime.Format(utils.FormatDateTime),
+		AuthOk:              authOk,
+		PermissionInfo:      permissionInfo,
+		AudioList:           audios,
+	}
+	return
+}
+
+// CreateQuestion 新增问答
+func CreateQuestion(userId int, mobile, realName, content string) (err error) {
+	// 获取用户公众号openid, 获取不到则置空
+	userRecord, e := user_record.GetByUserId(userId, utils.USER_RECORD_PLATFORM_RDDP)
+	if e != nil && e == utils.ErrNoRow {
+		err = errors.New("获取用户公众号openid失败 Err:" + e.Error())
+		return
+	}
+	openid := ""
+	if userRecord != nil {
+		openid = userRecord.OpenID
+	}
+	item := &yb_community_question.YbCommunityQuestion{
+		UserID:          userId,
+		UserOpenid:      openid,
+		Mobile:          mobile,
+		RealName:        realName,
+		QuestionContent: content,
+		ReplyStatus:     1,
+	}
+	if e := item.Create(); e != nil {
+		err = errors.New("新增问题失败 Err:" + e.Error())
+	}
+	return
+}
+
+// ReplyUserQuestion 回复问题
+func ReplyUserQuestion(replierId, questionId int, audios []*request.ReplyReqAudioList) (err error) {
+	item, e := yb_community_question.GetItemById(questionId)
+	if e != nil {
+		err = errors.New("获取提问信息失败 Err:" + e.Error())
+		return
+	}
+	if item.ReplyStatus < 2 {
+		err = errors.New("回复状态有误")
+		return
+	}
+	if item.ReplierUserID != replierId {
+		err = errors.New(fmt.Sprintf("回复人与分配人不匹配, 回复人ID: %d, 分配人ID: %d", item.ReplierUserID, replierId))
+		return
+	}
+	// 问题
+	updateCols := make([]string, 0)
+	updateCols = append(updateCols, "reply_status", "reply_time", "modify_time")
+	nowTime := time.Now().Local()
+	item.ReplyStatus = 3
+	item.ReplyTime = nowTime
+	item.ModifyTime = nowTime
+	// 音频
+	audioList := make([]*yb_community_question_audio.YbCommunityQuestionAudio, 0)
+	for _, v := range audios {
+		audioList = append(audioList, &yb_community_question_audio.YbCommunityQuestionAudio{
+			CommunityQuestionID: questionId,
+			AudioURL:            v.AudioUrl,
+			AudioPlaySeconds:    v.AudioPlaySeconds,
+			AudioSize:           v.AudioSize,
+			Sort:                v.Sort,
+			CreateTime:          nowTime,
+		})
+	}
+	if e := yb_community_question.UpdateQuestionAndAudioList(item, updateCols, audioList); e != nil {
+		err = errors.New("UpdateQuestionAndAudioList Err:" + e.Error())
+		return
+	}
+	// 推送回复消息给用户
+	go wechat.SendQuestionReplyWxMsg(item.CommunityQuestionID, item.UserID, item.UserOpenid, item.QuestionContent)
+	return
+}
+
+// ReadQuestionReply 回复已读
+func ReadQuestionReply(userId int, questionId int) (err error) {
+	item, e := yb_community_question.GetItemById(questionId)
+	if e != nil {
+		err = errors.New("获取提问信息失败 Err:" + e.Error())
+		return
+	}
+	if item.IsRead == 1 {
+		return
+	}
+	if item.UserID == userId {
+		updateCols := make([]string, 0)
+		updateCols = append(updateCols, "is_read", "modify_time")
+		item.IsRead = 1
+		item.ModifyTime = time.Now().Local()
+		if e = item.Update(updateCols); e != nil {
+			err = errors.New("更新问题已读失败 Err:" + e.Error())
+		}
+	}
+	return
+}
+
+// GetReplyListTotal 获取问答列表数量统计
+func GetReplyListTotal(replierUserId int) (resp *response.CommunityReplyTotal, err error) {
+	countList, e := yb_community_question.GetReplierQuestionCount(replierUserId)
+	if e != nil {
+		err = errors.New("获取回复人问题统计失败 Err:" + e.Error())
+		return
+	}
+	resp = new(response.CommunityReplyTotal)
+	for _, v := range countList {
+		if v.ReplyStatus == 2 {
+			resp.Wait = v.Total
+			continue
+		}
+		if v.ReplyStatus == 3 {
+			resp.Replied = v.Total
+		}
+	}
+	resp.Total = resp.Wait + resp.Replied
+	return
+}

+ 117 - 75
services/company/permission.go

@@ -21,6 +21,12 @@ import (
 	"time"
 )
 
+var (
+	CheckTypeApply   = "apply"
+	CheckTypeContact = "contact"
+	CheckTypeExpire  = "expired"
+)
+
 // GetClassNameListByProductId 根据权限id获取权限分类
 func GetClassNameListByProductId(productId int64) (list []*chart_permission.ChartPermission, err error) {
 	list, err = chart_permission.GetClassNameListByProductId(productId)
@@ -89,59 +95,70 @@ type CustomerInfo struct {
 	HasApply    bool   `json:"has_apply" description:"是否有申请过"`
 }
 
-// CheckPermissionByFicc 权限校验
-func CheckPermissionByFicc(companyId int64, permissionId int) (ok bool, permissionCheckInfo PermissionCheckInfo, err error) {
-	//非潜在客户
-	var productId int64
-	productId = 1
-	if companyId > 1 {
-		//查询是否 开通ficc的客户
-		companyProductInfo, tmpErr := company_product.GetByCompany2ProductId(companyId, productId)
-		if tmpErr != nil {
-			// 没有开通ficc的客户
-			if tmpErr == utils.ErrNoRow {
-				permissionCheckInfo.Type = "apply"
-				return
+// CheckBaseFiccPermission 校验用户基本的FICC权限
+func CheckBaseFiccPermission(companyId int64, userId int) (ok bool, checkInfo response.PermissionCheckInfo, permissionIdArr []int, err error) {
+	defer func() {
+		// 无权限查询是否有申请记录
+		if !ok {
+			if checkInfo.Type == CheckTypeApply {
+				if _, e := yb_apply_record.GetLastNotOpRecordByUserId(userId); e == nil {
+					checkInfo.CustomerInfo.HasApply = true
+				}
 			}
-			err = tmpErr
-			return
 		}
-
-		// 如果客户ficc产品的状态是流失,那么也是让去申请
-		if companyProductInfo.Status == "流失" {
-			permissionCheckInfo.Type = "apply"
+	}()
+	// 潜在用户
+	if companyId == 1 {
+		checkInfo.Type = CheckTypeApply
+		return
+	}
+	// 是否开通FICC
+	productId := int64(1)
+	productInfo, e := company_product.GetByCompany2ProductId(companyId, productId)
+	if e != nil {
+		if e == utils.ErrNoRow {
+			checkInfo.Type = CheckTypeApply
 			return
 		}
-		// 获取有效的权限id列表
-		validPermissionIdList, tmpErr := GetValidPermissionIdListByCompany2ProductId(companyId, productId)
-		if tmpErr != nil {
-			err = tmpErr
+		err = errors.New("获取用户FICC权限失败 Err:" + e.Error())
+		return
+	}
+	// 客户状态是否为流失
+	if productInfo.Status == "流失" {
+		checkInfo.Type = CheckTypeApply
+		return
+	}
+	// 是否为弘则研究已禁用的联系人
+	if companyId == 16 {
+		userInfo, e := wx_user.GetByUserId(userId)
+		if e != nil {
+			err = errors.New("用户信息异常 Err:" + e.Error())
 			return
 		}
-		// 校验在有效的权限id列表中是否存在该权限
-		for _, validPermissionId := range validPermissionIdList {
-			//如果有该权限id,那么直接返回校验通过
-			if validPermissionId == permissionId {
-				ok = true
-				return
-			}
-		}
-		//查找对应客户的销售信息
-
-		adminInfo, tmpErr := admin.GetByAdminId(companyProductInfo.SellerID)
-		if tmpErr != nil {
-			err = tmpErr
+		if userInfo.Enabled != 1 {
+			checkInfo.Type = CheckTypeApply
 			return
 		}
-		permissionCheckInfo = PermissionCheckInfo{
-			Name:   adminInfo.RealName,
-			Mobile: adminInfo.Mobile,
-			Type:   "contact",
-		}
-	} else {
-		permissionCheckInfo.Type = "apply"
 	}
-
+	// 客户对应的销售信息
+	sellerInfo, e := admin.GetByAdminId(productInfo.SellerID)
+	if e != nil {
+		err = errors.New("获取用户销售信息失败 Err:" + e.Error())
+		return
+	}
+	checkInfo.Name = sellerInfo.RealName
+	checkInfo.Mobile = sellerInfo.Mobile
+	if productInfo.Status == "冻结" || (productInfo.Status == "试用" && productInfo.IsSuspend == 1) {
+		checkInfo.Type = CheckTypeContact
+		return
+	}
+	// 正常用户, 获取有效权限
+	permissionIdArr, e = GetValidPermissionIdListByCompany2ProductId(companyId, productId)
+	if e != nil {
+		err = errors.New("获取用户有效权限列表失败 Err:" + e.Error())
+		return
+	}
+	ok = true
 	return
 }
 
@@ -152,7 +169,7 @@ func CheckPermissionByPermissionIdList2Ficc(companyId int64, userId int, permiss
 		if ok == false {
 			ok, _ = checkPermissionByPermissionIdList2Rai(companyId, userId, permissionIdList)
 		}
-		if ok == false && permissionCheckInfo.Type == "apply" {
+		if ok == false && permissionCheckInfo.Type == CheckTypeApply {
 			_, err = yb_apply_record.GetLastNotOpRecordByUserId(userId) // 从来源我的/活动申请的记录
 			if err != nil && err != utils.ErrNoRow {
 				return
@@ -181,7 +198,7 @@ func CheckPermissionByPermissionIdList2Ficc(companyId int64, userId int, permiss
 		if tmpErr != nil {
 			// 没有开通ficc的客户
 			if tmpErr == utils.ErrNoRow {
-				permissionCheckInfo.Type = "apply"
+				permissionCheckInfo.Type = CheckTypeApply
 				return
 			}
 			err = tmpErr
@@ -190,7 +207,7 @@ func CheckPermissionByPermissionIdList2Ficc(companyId int64, userId int, permiss
 
 		wxUser, tmpErr := wx_user.GetByUserId(userId)
 		if tmpErr != nil {
-			permissionCheckInfo.Type = "apply"
+			permissionCheckInfo.Type = CheckTypeApply
 			err = tmpErr
 			return
 		}
@@ -198,7 +215,7 @@ func CheckPermissionByPermissionIdList2Ficc(companyId int64, userId int, permiss
 		// 查询用户是否为弘则研究已禁用的联系人
 		if companyId == 16 {
 			if wxUser.Enabled != 1 {
-				permissionCheckInfo.Type = "apply"
+				permissionCheckInfo.Type = CheckTypeApply
 				return
 			}
 		}
@@ -208,7 +225,7 @@ func CheckPermissionByPermissionIdList2Ficc(companyId int64, userId int, permiss
 		if tmpErr != nil {
 			// 没有开通ficc的客户
 			if tmpErr == utils.ErrNoRow {
-				permissionCheckInfo.Type = "apply"
+				permissionCheckInfo.Type = CheckTypeApply
 				return
 			}
 			err = tmpErr
@@ -225,7 +242,7 @@ func CheckPermissionByPermissionIdList2Ficc(companyId int64, userId int, permiss
 
 		// 如果客户ficc产品的状态是流失,那么也是让去申请
 		if companyProductInfo.Status == "流失" {
-			permissionCheckInfo.Type = "apply"
+			permissionCheckInfo.Type = CheckTypeApply
 			return
 		}
 
@@ -240,13 +257,13 @@ func CheckPermissionByPermissionIdList2Ficc(companyId int64, userId int, permiss
 		permissionCheckInfo.Name = adminInfo.RealName
 		permissionCheckInfo.Mobile = adminInfo.Mobile
 		if companyProductInfo.Status == "冻结" {
-			permissionCheckInfo.Type = "contact"
+			permissionCheckInfo.Type = CheckTypeContact
 			return
 		}
 
 		//客户状态是:试用暂停状态(联系销售)
 		if companyProductInfo.Status == "试用" && companyProductInfo.IsSuspend == 1 {
-			permissionCheckInfo.Type = "contact"
+			permissionCheckInfo.Type = CheckTypeContact
 			return
 		}
 
@@ -264,9 +281,9 @@ func CheckPermissionByPermissionIdList2Ficc(companyId int64, userId int, permiss
 				return
 			}
 		}
-		permissionCheckInfo.Type = "contact"
+		permissionCheckInfo.Type = CheckTypeContact
 	} else {
-		permissionCheckInfo.Type = "apply"
+		permissionCheckInfo.Type = CheckTypeApply
 	}
 
 	return
@@ -278,7 +295,7 @@ func GetCheckPermission(companyId int64, userId int, permissionIdList []int) (ok
 		if ok == false {
 			ok, _ = checkPermissionByPermissionIdList2Rai(companyId, userId, permissionIdList)
 		}
-		if ok == false && permissionCheckInfo.Type == "apply" {
+		if ok == false && permissionCheckInfo.Type == CheckTypeApply {
 			_, err = yb_apply_record.GetLastNotOpRecordByUserId(userId) // 从来源我的/活动申请的记录
 			if err != nil {
 				if err == utils.ErrNoRow {
@@ -309,7 +326,7 @@ func GetCheckPermission(companyId int64, userId int, permissionIdList []int) (ok
 		if tmpErr != nil {
 			// 没有开通ficc的客户
 			if tmpErr == utils.ErrNoRow {
-				permissionCheckInfo.Type = "apply"
+				permissionCheckInfo.Type = CheckTypeApply
 				return
 			}
 			err = tmpErr
@@ -318,7 +335,7 @@ func GetCheckPermission(companyId int64, userId int, permissionIdList []int) (ok
 
 		wxUser, tmpErr := wx_user.GetByUserId(userId)
 		if tmpErr != nil {
-			permissionCheckInfo.Type = "apply"
+			permissionCheckInfo.Type = CheckTypeApply
 			err = tmpErr
 			return
 		}
@@ -328,7 +345,7 @@ func GetCheckPermission(companyId int64, userId int, permissionIdList []int) (ok
 		if tmpErr != nil {
 			// 没有开通ficc的客户
 			if tmpErr == utils.ErrNoRow {
-				permissionCheckInfo.Type = "apply"
+				permissionCheckInfo.Type = CheckTypeApply
 				return
 			}
 			err = tmpErr
@@ -345,7 +362,7 @@ func GetCheckPermission(companyId int64, userId int, permissionIdList []int) (ok
 
 		// 如果客户ficc产品的状态是流失,那么也是让去申请
 		if companyProductInfo.Status == "流失" {
-			permissionCheckInfo.Type = "apply"
+			permissionCheckInfo.Type = CheckTypeApply
 			return
 		}
 
@@ -360,13 +377,13 @@ func GetCheckPermission(companyId int64, userId int, permissionIdList []int) (ok
 		permissionCheckInfo.Name = adminInfo.RealName
 		permissionCheckInfo.Mobile = adminInfo.Mobile
 		if companyProductInfo.Status == "冻结" {
-			permissionCheckInfo.Type = "contact"
+			permissionCheckInfo.Type = CheckTypeContact
 			return
 		}
 
 		//客户状态是:试用暂停状态(联系销售)
 		if companyProductInfo.Status == "试用" && companyProductInfo.IsSuspend == 1 {
-			permissionCheckInfo.Type = "contact"
+			permissionCheckInfo.Type = CheckTypeContact
 			return
 		}
 
@@ -388,9 +405,9 @@ func GetCheckPermission(companyId int64, userId int, permissionIdList []int) (ok
 			return
 		}
 
-		permissionCheckInfo.Type = "contact"
+		permissionCheckInfo.Type = CheckTypeContact
 	} else {
-		permissionCheckInfo.Type = "apply"
+		permissionCheckInfo.Type = CheckTypeApply
 	}
 	return
 }
@@ -460,7 +477,7 @@ type ChartPermissionCheckInfo struct {
 func CheckUserChartPermission(companyId int64, userId int) (ok bool, permissionCheckInfo ChartPermissionCheckInfo, err error) {
 	defer func() {
 		// 如果无权限,那么就去查询是否申请过
-		if ok == false && permissionCheckInfo.Type == "apply" {
+		if ok == false && permissionCheckInfo.Type == CheckTypeApply {
 			_, err = yb_apply_record.GetLastNotOpRecordByUserId(userId) // 图库申请
 			// 查询是否有申请过,如果有申请过的话,那么err是nil
 			if err != nil {
@@ -482,7 +499,7 @@ func CheckUserChartPermission(companyId int64, userId int) (ok bool, permissionC
 		if tmpErr != nil {
 			// 没有开通FICC
 			if tmpErr == utils.ErrNoRow {
-				permissionCheckInfo.Type = "apply"
+				permissionCheckInfo.Type = CheckTypeApply
 				return
 			}
 			err = tmpErr
@@ -491,7 +508,7 @@ func CheckUserChartPermission(companyId int64, userId int) (ok bool, permissionC
 
 		wxUser, tmpErr := wx_user.GetByUserId(userId)
 		if tmpErr != nil {
-			permissionCheckInfo.Type = "apply"
+			permissionCheckInfo.Type = CheckTypeApply
 			err = tmpErr
 			return
 		}
@@ -499,7 +516,7 @@ func CheckUserChartPermission(companyId int64, userId int) (ok bool, permissionC
 		// 查询用户是否为弘则研究已禁用的联系人
 		if companyId == 16 {
 			if wxUser.Enabled != 1 {
-				permissionCheckInfo.Type = "apply"
+				permissionCheckInfo.Type = CheckTypeApply
 				return
 			}
 		}
@@ -509,7 +526,7 @@ func CheckUserChartPermission(companyId int64, userId int) (ok bool, permissionC
 		if tmpErr != nil {
 			// 没有开通FICC
 			if tmpErr == utils.ErrNoRow {
-				permissionCheckInfo.Type = "apply"
+				permissionCheckInfo.Type = CheckTypeApply
 				return
 			}
 			err = tmpErr
@@ -526,7 +543,7 @@ func CheckUserChartPermission(companyId int64, userId int) (ok bool, permissionC
 
 		// 如果客户FICC产品的状态是流失-申请
 		if companyProductInfo.Status == "流失" {
-			permissionCheckInfo.Type = "apply"
+			permissionCheckInfo.Type = CheckTypeApply
 			return
 		}
 
@@ -541,7 +558,7 @@ func CheckUserChartPermission(companyId int64, userId int) (ok bool, permissionC
 		permissionCheckInfo.Name = adminInfo.RealName
 		permissionCheckInfo.Mobile = adminInfo.Mobile
 		if companyProductInfo.Status == "冻结" || (companyProductInfo.Status == "试用" && companyProductInfo.IsSuspend == 1) {
-			permissionCheckInfo.Type = "contact"
+			permissionCheckInfo.Type = CheckTypeContact
 			return
 		}
 
@@ -550,7 +567,7 @@ func CheckUserChartPermission(companyId int64, userId int) (ok bool, permissionC
 		if tmpErr != nil {
 			if tmpErr == utils.ErrNoRow {
 				// 无权限-联系销售
-				permissionCheckInfo.Type = "contact"
+				permissionCheckInfo.Type = CheckTypeContact
 				return
 			}
 			err = tmpErr
@@ -559,7 +576,7 @@ func CheckUserChartPermission(companyId int64, userId int) (ok bool, permissionC
 		nowTime := time.Now()
 		if permissionInfo.Enabled != 1 {
 			// 权限被禁用-联系销售
-			permissionCheckInfo.Type = "contact"
+			permissionCheckInfo.Type = CheckTypeContact
 			return
 		}
 		endTime := permissionInfo.EndTime.Add(23*time.Hour + 59*time.Minute + 59*time.Second)
@@ -567,14 +584,14 @@ func CheckUserChartPermission(companyId int64, userId int) (ok bool, permissionC
 			ok = true
 		} else {
 			// 权限已到期
-			permissionCheckInfo.Type = "expired"
+			permissionCheckInfo.Type = CheckTypeExpire
 			return
 		}
 
 		// 都不是默认联系销售
-		permissionCheckInfo.Type = "contact"
+		permissionCheckInfo.Type = CheckTypeContact
 	} else {
-		permissionCheckInfo.Type = "apply"
+		permissionCheckInfo.Type = CheckTypeApply
 	}
 
 	return
@@ -592,6 +609,31 @@ type PermissionItem struct {
 	HasPermission  bool   `description:"是否有权限"`
 }
 
+// GetFiccPermissionList 获取FICC品种权限列表-不校验用户权限
+func GetFiccPermissionList() (list []*FiccPermissionList, err error) {
+	productId := 1
+	list = make([]*FiccPermissionList, 0)
+	for _, v := range utils.PermissionFiccClassifyArr {
+		classify := new(FiccPermissionList)
+		classify.ClassifyName = v
+		items, e := chart_permission.GetListByProductIdAndClassifyName(productId, v)
+		if e != nil {
+			err = errors.New("获取品种权限失败 Err:" + e.Error())
+			return
+		}
+		tmpItems := make([]*PermissionItem, 0)
+		for _, iv := range items {
+			item := new(PermissionItem)
+			item.PermissionId = int(iv.ChartPermissionID)
+			item.PermissionName = iv.PermissionName
+			tmpItems = append(tmpItems, item)
+		}
+		classify.Items = tmpItems
+		list = append(list, classify)
+	}
+	return
+}
+
 func GetHomeFiccPermissions(user user.UserInfo) (ret response.PermissionFiccResp, err error) {
 	var errMsg string
 	defer func() {

+ 16 - 0
services/media.go

@@ -0,0 +1,16 @@
+package services
+
+import (
+	"github.com/tosone/minimp3"
+)
+
+// GetMP3PlayDuration 获取MP3的时长
+func GetMP3PlayDuration(mp3Data []byte) (seconds int, err error) {
+	dec, _, err := minimp3.DecodeFull(mp3Data)
+	if err != nil {
+		return 0, err
+	}
+	// 音乐时长 = (文件大小(byte) - 128(ID3信息)) * 8(to bit) / (码率(kbps b:bit) * 1000)(kilo bit to bit)
+	seconds = (len(mp3Data) - 128) * 8 / (dec.Kbps * 1000)
+	return seconds, nil
+}

+ 8 - 9
services/oss.go

@@ -89,14 +89,13 @@ func UploadVideoAliyun(filename, filepath, savePath string) error {
 	return err
 }
 
-
 var (
-	Bucketname       string = "hzchart"
-	Endpoint         string = "oss-cn-shanghai.aliyuncs.com"
-	Imghost          string = "https://hzstatic.hzinsights.com/"
-	UploadDir		 string = "static/images/"
-	AccessKeyId      string = "LTAIFMZYQhS2BTvW"
-	AccessKeySecret  string = "12kk1ptCHoGWedhBnKRVW5hRJzq9Fq"
+	Bucketname      string = "hzchart"
+	Endpoint        string = "oss-cn-shanghai.aliyuncs.com"
+	ResourceHost    string = "https://hzstatic.hzinsights.com/"
+	StaticDir       string = "static/"
+	AccessKeyId     string = "LTAIFMZYQhS2BTvW"
+	AccessKeySecret string = "12kk1ptCHoGWedhBnKRVW5hRJzq9Fq"
 )
 
 // UploadAliyunToDir
@@ -112,12 +111,12 @@ func UploadAliyunToDir(filename, filepath, fileDir string) (string, error) {
 	if fileDir == "" {
 		fileDir = time.Now().Format("200601/20060102/")
 	}
-	path := UploadDir + fileDir
+	path := StaticDir + fileDir
 	path += filename
 	err = bucket.PutObjectFromFile(path, filepath)
 	if err != nil {
 		return "3", err
 	}
-	path = Imghost + path
+	path = ResourceHost + path
 	return path, err
 }

+ 1 - 1
services/share_poster.go

@@ -215,7 +215,7 @@ func CreateAndUploadSunCode(page, scene, version string) (imgUrl string, err err
 		os.Remove(fpath)
 	}()
 	// 上传OSS
-	fileDir := "yb/suncode/"
+	fileDir := "images/yb/suncode/"
 	imgUrl, err = UploadAliyunToDir(fileName, fpath, fileDir)
 	if err != nil {
 		return

+ 220 - 0
services/wechat/template_msg.go

@@ -0,0 +1,220 @@
+package wechat
+
+import (
+	"bytes"
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"hongze/hongze_yb/global"
+	"hongze/hongze_yb/models/tables/user_template_record"
+	"hongze/hongze_yb/services/alarm_msg"
+	"hongze/hongze_yb/utils"
+	"io/ioutil"
+	"net/http"
+	"time"
+)
+
+var (
+	TemplateMsgSendUrl       = "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s"
+	TemplateMsgClearQuotaUrl = "https://api.weixin.qq.com/cgi-bin/clear_quota?access_token=%s"
+)
+
+type TemplateMsgSendClient struct {
+	AccessToken string
+	Data        []byte
+}
+
+type SendTemplateResponse struct {
+	Errcode int    `json:"errcode"`
+	Errmsg  string `json:"errmsg"`
+	MsgID   int    `json:"msgid"`
+}
+
+type ClearQuotaResponse struct {
+	Errcode int    `json:"errcode"`
+	Errmsg  string `json:"errmsg"`
+}
+
+type OpenIdList struct {
+	OpenId string
+	UserId int
+}
+
+// TemplateMsgSendClient.SendMsg 推送消息
+func (c *TemplateMsgSendClient) SendMsg() (sendRes *SendTemplateResponse, err error) {
+	// 请求接口
+	sendUrl := fmt.Sprintf(TemplateMsgSendUrl, c.AccessToken)
+	client := http.Client{}
+	resp, err := client.Post(sendUrl, "application/json", bytes.NewBuffer(c.Data))
+	if err != nil {
+		return
+	}
+	defer func() {
+		_ = resp.Body.Close()
+	}()
+	body, _ := ioutil.ReadAll(resp.Body)
+	if err = json.Unmarshal(body, &sendRes); err != nil {
+		return
+	}
+	// 模板消息发送超过当日10万次限制错误处理
+	if sendRes.Errcode == 45009 {
+		// 发送提示邮件
+		go alarm_msg.SendAlarmMsg("模板消息发送超过当日10万次限制, SendTemplateResponse: "+string(body), 3)
+		// 清理限制
+		clearRes, e := c.ClearQuota()
+		if e != nil {
+			err = e
+			return
+		}
+		if clearRes.Errcode != 0 {
+			clearJson, _ := json.Marshal(clearRes)
+			go alarm_msg.SendAlarmMsg("自动清理模板消息限制接口, 调用失败, ClearQuotaResponse: "+string(clearJson), 3)
+			return
+		}
+		// 发送成功邮件
+		go alarm_msg.SendAlarmMsg("自动清理模板消息限制接口, 调用成功", 1)
+		// 重新推送
+		go func() {
+			reSend, e := c.SendMsg()
+			if e != nil {
+				reSendJson, _ := json.Marshal(reSend)
+				alarm_msg.SendAlarmMsg("重新推送模板消息失败, SendTemplateResponse: "+string(reSendJson), 3)
+			}
+		}()
+	}
+	if sendRes.Errcode != 0 {
+		err = errors.New("推送模板消息失败, SendTemplateResponse: " + string(body))
+	}
+	return
+}
+
+// TemplateMsgSendClient.ClearQuota 清除发送超过当日10万次限制
+func (c *TemplateMsgSendClient) ClearQuota() (result *ClearQuotaResponse, err error) {
+	key := "CACHE_SendTemplateMsg_ERR"
+	exists, _ := global.Redis.Exists(context.TODO(), key).Result()
+	if exists == 1 {
+		return
+	}
+	_ = global.Redis.SetEX(context.TODO(), key, 1, 6*time.Minute)
+
+	sendUrl := fmt.Sprintf(TemplateMsgClearQuotaUrl, c.AccessToken)
+	client := http.Client{}
+	clearData := make(map[string]interface{})
+	clearData["appid"] = WxAppId
+	clearJson, _ := json.Marshal(clearData)
+	resp, err := client.Post(sendUrl, "application/json", bytes.NewBuffer(clearJson))
+	if err != nil {
+		return
+	}
+	defer func() {
+		_ = resp.Body.Close()
+	}()
+	clearBody, err := ioutil.ReadAll(resp.Body)
+	err = json.Unmarshal(clearBody, &result)
+	return
+}
+
+// AddUserTemplateRecord 新增模板消息推送记录
+func AddUserTemplateRecord(userId, sendStatus, sendType int, openid, resource, sendData, result string) (err error) {
+	item := &user_template_record.UserTemplateRecord{
+		UserID:     userId,
+		OpenID:     openid,
+		Resource:   resource,
+		SendData:   sendData,
+		Result:     result,
+		CreateDate: time.Now().Format(utils.FormatDate),
+		CreateTime: time.Now().Format(utils.FormatDateTime),
+		SendStatus: sendStatus,
+		SendType:   sendType,
+	}
+	err = item.Create()
+	return
+}
+
+// SendMultiTemplateMsg 推送模板消息至多个用户
+func SendMultiTemplateMsg(sendMap map[string]interface{}, items []*OpenIdList, resource string, sendType int) (err error) {
+	ws := GetWxChat()
+	accessToken, err := ws.GetAccessToken()
+	if err != nil {
+		return
+	}
+	for _, item := range items {
+		sendMap["touser"] = item.OpenId
+		data, e := json.Marshal(sendMap)
+		if e != nil {
+			err = e
+			return
+		}
+		ts := &TemplateMsgSendClient{
+			AccessToken: accessToken,
+			Data:        data,
+		}
+		result, e := ts.SendMsg()
+		if result == nil {
+			return
+		}
+		// 推送消息记录
+		{
+			go func(v *OpenIdList) {
+				sendStatus := 1
+				if e != nil {
+					sendStatus = 0
+				}
+				resultJson, _ := json.Marshal(result)
+				_ = AddUserTemplateRecord(v.UserId, sendStatus, sendType, v.OpenId, resource, string(data), string(resultJson))
+			}(item)
+		}
+		if e != nil {
+			err = e
+			return
+		}
+	}
+	return
+}
+
+// SendQuestionReplyWxMsg 推送研报小程序模板消息-问答社区回复
+func SendQuestionReplyWxMsg(questionId, userId int, openid, questionTitle string) (err error) {
+	if userId == 0 || openid == "" {
+		return
+	}
+	var errMsg string
+	defer func() {
+		if err != nil {
+			alarmMsg := fmt.Sprintf("SendQuestionReplyWxMsg-推送问答社区回复模版消息失败; QuestionId: %d; Err: %s; Msg: %s", questionId, err.Error(), errMsg)
+			go alarm_msg.SendAlarmMsg(alarmMsg, 3)
+		}
+	}()
+	initConf()
+
+	openIdList := make([]*OpenIdList, 0)
+	openIdList = append(openIdList, &OpenIdList{
+		OpenId: openid,
+		UserId: userId,
+	})
+
+	sendMap := make(map[string]interface{})
+	sendData := make(map[string]interface{})
+
+	first := "您好,您的提问已被回复"
+	keyword1 := questionTitle
+	keyword2 := "待查看"
+	remark := "请点击详情查看回复"
+
+	sendData["first"] = map[string]interface{}{"value": first, "color": "#173177"}
+	sendData["keyword1"] = map[string]interface{}{"value": keyword1, "color": "#173177"}
+	sendData["keyword2"] = map[string]interface{}{"value": keyword2, "color": "#173177"}
+	sendData["remark"] = map[string]interface{}{"value": remark, "color": "#173177"}
+
+	sendMap["template_id"] = TemplateIdWithCommunityQuestion
+	sendMap["data"] = sendData
+
+	// TODO:小程序pagepath待定
+	wxAppPath := ""
+	//wxAppPath := fmt.Sprintf("pages-report/reportDetail?reportId=%s", "3800")
+	if wxAppPath != "" {
+		sendMap["miniprogram"] = map[string]interface{}{"appid": WxYbAppId, "pagepath": wxAppPath}
+	}
+	err = SendMultiTemplateMsg(sendMap, openIdList, wxAppPath, utils.TEMPLATE_MSG_YB_COMMUNITY_QUESTION)
+	return
+}

+ 11 - 40
services/wechat/wechat.go

@@ -19,60 +19,31 @@ import (
 )
 
 var (
-	WxId                string //微信原始ID
-	WxAppId             string
-	WxAppSecret         string
-	TemplateIdByProduct string //产品运行报告通知-模板ID
-
-	TemplateRedirectUrl                   string //模板消息跳转地址
-	DayReportTemplateRedirectUrl          string //模板消息跳转地址(晨报、周报)
-	TemplateIdByCompanyApply              string //客户申请单审批通知-模板ID
-	TemplateCompanyApplyRedirectUrl       string //审批单模板消息跳转地址
-	TemplateIdByCompanyReceive            string //销售跨部门领取客户通知-模板ID
-	WxMsgTemplateIdActivityChangeApply    string //查研观向活动变更通知-模板ID
-	WxMsgTemplateIdActivityChangeApplyXzs string //查研观向活动变更通知-模板ID(小助手)
-	TemplateIdByProductXzs                string //产品运行报告通知-模板ID(小助手)
-	WxCygxAppId                           string //查研观向小程序APPID
-	WxMsgTemplateIdAskByUser              string //查研观向用户提问新消息推送ID
-	WxCrmAppId                            string //随手办公小程序APPID
-	WxPublicIdXzs                         string //查研观向小助手公众号
-	WxPublicSecretXzs                     string //查研观向小助手公众号
-	PcWxAppId                             string //pc版AppId
-	PcWxAppSecret                         string //pc版AppSecret
+	WxId                            string //微信原始ID
+	WxAppId                         string
+	WxAppSecret                     string
+	TemplateIdWithCommunityQuestion string // 问答社区回复模板消息ID
+	WxYbAppId                       string // 研报小程序AppID
+	PcWxAppId                       string //pc版AppId
+	PcWxAppSecret                   string //pc版AppSecret
 )
 
 func initConf() {
+	WxYbAppId = "wxb059c872d79b9967"
 	if global.CONFIG.Serve.RunMode == "debug" {
 		WxAppId = "wx9b5d7291e581233a"
 		WxAppSecret = "f4d52e34021eee262dce9682b31f8861"
 		WxId = "gh_5dc508325c6f"
-		TemplateIdByProduct = "-YjuPOB7Fqd-S3ilabYa6wvjDY9aXmeEfPN6DCiy-EY"
-		TemplateRedirectUrl = "http://rddpweb.brilliantstart.cn/reportdtl?id="
-		DayReportTemplateRedirectUrl = "http://report.brilliantstart.cn/#/allindex/" //晨报模板跳转url
-		TemplateIdByCompanyApply = "eTalI1LbiT_B0mTaBeDTlfSMITenK8dQIgEB5yqjjvA"
-		TemplateCompanyApplyRedirectUrl = "http://advisoryadmin.brilliantstart.cn/approval/approval/list"
-		//销售跨部门领取客户通知
-		TemplateIdByCompanyReceive = "KAxFvX79f-DHmtC_Jw98elvqxiDPK0xP_b48LUT-Y14"
-		WxMsgTemplateIdActivityChangeApply = "CB7bOl7f3viMG4s1uhRo7WM0Jbx3WvodKuIZ8A_z8fM"
-
-		WxMsgTemplateIdAskByUser = `qfNuops-sKrfIkbA7U97A7gSrX03mUpoEpJksRUdloo`
 		PcWxAppId = "wxcba9a7ec590ee2d5"
 		PcWxAppSecret = "aa58d257e2521d768cbf1bf89989769d"
+		TemplateIdWithCommunityQuestion = "CB7bOl7f3viMG4s1uhRo7WM0Jbx3WvodKuIZ8A_z8fM"
 	} else {
 		WxAppId = "wx4a844c734d8c8e56"
 		WxAppSecret = "26c586e7ccb3c575433f0f37797b3eeb"
 		WxId = "gh_b67e0049fb8c"
-		TemplateIdByProduct = "Cp2wF8gvBtxyWV4DeYuI172oqwyYXVRSm3AyJO42d84"
-		TemplateRedirectUrl = "https://ficc.hzinsights.com/reportdtl?id="
-		DayReportTemplateRedirectUrl = "https://report.hzinsights.com/#/allindex/" //晨报模板跳转url
-		TemplateIdByCompanyApply = "ZKcOfNIWBpwHJxpptufHIK1mp2nIwkT3cxub-35cFqI"
-		TemplateCompanyApplyRedirectUrl = "https://ficc.hzinsights.com/approval/approval/list"
-		WxMsgTemplateIdActivityChangeApply = "dYg6iHooRq74PyCXmw_Ns7qdJZmbtLoKS2p2FKeaXl0"
-		//销售跨部门领取客户通知
-		TemplateIdByCompanyReceive = "A5fV-XWBcu-LIj_W-tBiOJ-D39a9WDd9GOB0WGbpoBg"
-		TemplateCompanyApplyRedirectUrl = "https://ficc.hzinsights.com/approval/approval/list"
 		PcWxAppId = "wx4da95782cfc8c5eb"
 		PcWxAppSecret = "8f82ebf2ba3aa06ce44541726385df64"
+		TemplateIdWithCommunityQuestion = "dYg6iHooRq74PyCXmw_Ns7qdJZmbtLoKS2p2FKeaXl0"
 	}
 }
 
@@ -175,4 +146,4 @@ func PcWxGetUserInfo(openId, accessToken string) (item *pc.WxUserInfo, err error
 	}
 	err = json.Unmarshal(result, &item)
 	return
-}
+}

+ 7 - 0
utils/common.go

@@ -960,3 +960,10 @@ func InArray(needle interface{}, hyStack interface{}) bool {
 	}
 	return false
 }
+
+// bit转MB 保留小数
+func Bit2MB(bitSize int64, prec int) (size float64) {
+	mb := float64(bitSize)/float64(1024*1024)
+	size, _ = strconv.ParseFloat(strconv.FormatFloat(mb,'f',prec,64), 64)
+	return
+}

+ 34 - 10
utils/constants.go

@@ -23,7 +23,7 @@ const (
 )
 
 const (
-	APPNAME = "弘则研报"
+	APPNAME          = "弘则研报"
 	EmailSendToUsers = "317699326@qq.com;984198890@qq.com;hsun@hzinsights.com;xyxie@hzinsights.com"
 )
 
@@ -73,7 +73,7 @@ const (
 
 const (
 	HZ_CHART_LIB_DETAIL = "HZ_CHART_LIB_DETAIL_"
-	HONGZEYB_ = "hongze_yb:"
+	HONGZEYB_           = "hongze_yb:"
 )
 
 //var (
@@ -87,9 +87,9 @@ const (
 
 //数据刷新频率
 const (
-	DATA_REFRESH        = 7 //7个单位,日/周/月/季度/年
+	DATA_REFRESH             = 7 //7个单位,日/周/月/季度/年
 	DATA_START_REFRESH_LIMIT = 7 //7个单位,日/周/月/季度/年
-	DATA_END_DATE_LIMIT = 4 //数据结束日期为,当前日期,加上4年时间
+	DATA_END_DATE_LIMIT      = 4 //数据结束日期为,当前日期,加上4年时间
 )
 
 const (
@@ -99,7 +99,6 @@ const (
 	EDB_DATA_LIMIT = 10
 )
 
-
 var PermissionFiccClassifyArr = [...]string{"宏观经济", "化工产业", "黑色产业", "有色产业", "市场策略"}
 
 //缓存key
@@ -109,7 +108,7 @@ const (
 
 // es相关
 const (
-	ES_INDEX_RDDP_REPORT = "research_report_v1"                //报告
+	ES_INDEX_RDDP_REPORT = "research_report_v1" //报告
 )
 
 //EDB_LIB
@@ -121,9 +120,34 @@ var (
 const ALIYUN_YBIMG_HOST = "https://hzstatic.hzinsights.com/static/yb_wx/"
 const HZ_DEFAULT_AVATAR = "https://hzstatic.hzinsights.com/static/yb_wx/hz_default_avatar.png"
 
-const HZPHONE = "057187186319"    //弘则电话
+const (
+	DEFAULT_HONGZE_USER_LOGO = "https://hzstatic.hzinsights.com/static/icon/hzyb/default_avatar.png"      //个人中心默认头像、匿名用户留言默认头像
+	DEFAULT_HONGZE_SYS_LOGO  = "https://hzstatic.hzinsights.com/static/yb_wx/hongze_sys_default_head.png" //弘则官方默认头像
+)
+
+const HZPHONE = "057187186319" //弘则电话
 
+//模板消息推送类型
 const (
-	DEFAULT_HONGZE_USER_LOGO = "https://hzstatic.hzinsights.com/static/icon/hzyb/default_avatar.png"    //个人中心默认头像、匿名用户留言默认头像
-	DEFAULT_HONGZE_SYS_LOGO = "https://hzstatic.hzinsights.com/static/yb_wx/hongze_sys_default_head.png"     //弘则官方默认头像
-)
+	TEMPLATE_MSG_REPORT               = iota + 1 //日度点评报告推送
+	TEMPLATE_MSG_INDEX                           //指标更新
+	TEMPLATE_MSG_APPLY                           //审批通知
+	TEMPLATE_MSG_RECEIVE                         //销售领取客户通知
+	TEMPLATE_MSG_CYGX_ACTIVITY_CACLE             //查研观向活动取消通知
+	TEMPLATE_MSG_CYGX_ACTIVITY_UPDATE            //查研观向活动更改时间通知
+	TEMPLATE_MSG_CYGX_ARTICLE                    //关注的作者发布报告通知
+	TEMPLATE_MSG_CYGX_DAY_REPORT                 //发送日报(周报、双周报、月报)
+	TEMPLATE_MSG_ACTIVITY_APPOINTMENT            //活动预约/报名时间通知
+	_
+	TEMPLATE_MSG_YB_COMMUNITY_QUESTION // 研报小程序-问答社区通知
+)
+
+// 微信用户user_record注册平台
+const (
+	USER_RECORD_PLATFORM_RDDP      = iota + 1 // 日度点评公众号
+	USER_RECORD_PLATFORM_BACKSTAGE            // 管理后台
+	USER_RECORD_PLATFORM_PC                   // PC端网站
+	USER_RECORD_PLATFORM_CYGX                 // 查研观向小程序
+	_
+	USER_RECORD_PLATFORM_YB // 研报小程序
+)

Some files were not shown because too many files changed in this diff