浏览代码

Merge branch 'bzq/dev' of eta_mini/eta_mini_bridge into master

鲍自强 11 月之前
父节点
当前提交
d876c2a6eb
共有 47 个文件被更改,包括 4018 次插入341 次删除
  1. 1 0
      .gitignore
  2. 0 7
      conf/app.conf
  3. 144 0
      controllers/base_auth.go
  4. 25 0
      controllers/base_common.go
  5. 110 0
      controllers/chart.go
  6. 278 0
      controllers/chart_permission.go
  7. 21 0
      controllers/error.go
  8. 0 92
      controllers/object.go
  9. 443 0
      controllers/report.go
  10. 0 119
      controllers/user.go
  11. 177 0
      controllers/wechat.go
  12. 19 5
      go.mod
  13. 43 0
      main.go
  14. 39 0
      models/base.go
  15. 111 0
      models/chart.go
  16. 107 0
      models/chart_permission.go
  17. 58 0
      models/chart_permission_search_key_word_mapping.go
  18. 24 0
      models/classify.go
  19. 43 0
      models/db.go
  20. 0 53
      models/object.go
  21. 155 0
      models/report.go
  22. 5 0
      models/request/wechat.go
  23. 12 0
      models/response/chart.go
  24. 42 0
      models/response/report.go
  25. 102 61
      models/user.go
  26. 23 0
      models/user_chart_permission_mapping.go
  27. 47 0
      models/user_record.go
  28. 21 0
      models/user_template_record.go
  29. 26 0
      models/wx_token.go
  30. 0 0
      rdlucklog/202406/20240617.http.log
  31. 127 0
      routers/commentsRouter.go
  32. 22 4
      routers/router.go
  33. 30 0
      services/alarm_msg/alarm_msg.go
  34. 276 0
      services/elastic/report.go
  35. 123 0
      services/report.go
  36. 191 0
      services/template_msg.go
  37. 46 0
      services/user.go
  38. 32 0
      services/user_permission.go
  39. 103 0
      services/wechat.go
  40. 103 0
      utils/common.go
  41. 131 0
      utils/config.go
  42. 32 0
      utils/constants.go
  43. 21 0
      utils/elastic.go
  44. 114 0
      utils/logs.go
  45. 34 0
      utils/redis.go
  46. 281 0
      utils/redis/cluster_redis.go
  47. 276 0
      utils/redis/standalone_redis.go

+ 1 - 0
.gitignore

@@ -5,3 +5,4 @@
 *.exe~
 go.sum
 
+conf/app.conf

+ 0 - 7
conf/app.conf

@@ -1,7 +0,0 @@
-appname = eta_mini_bridge
-httpport = 8080
-runmode = dev
-autorender = false
-copyrequestbody = true
-EnableDocs = true
-sqlconn = 

+ 144 - 0
controllers/base_auth.go

@@ -0,0 +1,144 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_mini_bridge/models"
+	"eta/eta_mini_bridge/utils"
+	"fmt"
+	"net/http"
+	"net/url"
+
+	"github.com/beego/beego/v2/server/web"
+)
+
+type BaseAuthController struct {
+	web.Controller
+	Token string
+}
+
+func (this *BaseAuthController) Prepare() {
+	method := this.Ctx.Input.Method()
+	uri := this.Ctx.Input.URI()
+	fmt.Println("Url:", uri)
+
+	header := this.Ctx.Request.Header
+	headerBody, err := json.Marshal(header)
+	fmt.Println(err)
+	fmt.Println("header:" + string(headerBody))
+
+	if method != "HEAD" {
+		//校验签名
+		nonce := this.Ctx.Input.Header("Nonce")
+		timestamp := this.Ctx.Input.Header("Timestamp")
+		appid := this.Ctx.Input.Header("Appid")
+		signature := this.Ctx.Input.Header("Signature")
+
+		if nonce == "" {
+			errMsg := "随机字符串不能为空"
+			this.JSON(models.BaseResponse{Ret: 400, Msg: "", ErrMsg: errMsg}, false, false)
+			this.StopRun()
+			return
+		}
+
+		if timestamp == "" {
+			errMsg := "时间戳不能为空"
+			this.JSON(models.BaseResponse{Ret: 400, Msg: "", ErrMsg: errMsg}, false, false)
+			this.StopRun()
+			return
+		}
+
+		checkSign := utils.GetSign(nonce, timestamp, appid, utils.ETA_MINI_API_SECRET)
+		if signature != checkSign {
+			errMsg := "签名错误"
+			this.JSON(models.BaseResponse{Ret: 401, Msg: "", ErrMsg: errMsg}, false, false)
+			this.StopRun()
+			return
+		}
+		if method != "GET" && method != "POST" {
+			errMsg := "无效的请求方式"
+			this.JSON(models.BaseResponse{Ret: 501, Msg: "", ErrMsg: errMsg}, false, false)
+			this.StopRun()
+			return
+		}
+	} else {
+		this.JSON(models.BaseResponse{Ret: 500, Msg: "系统异常,请联系客服!", ErrMsg: "method:" + method}, false, false)
+		this.StopRun()
+		return
+	}
+}
+
+func (c *BaseAuthController) ServeJSON(encoding ...bool) {
+	//所有请求都做这么个处理吧,目前这边都是做编辑、刷新逻辑处理(新增的话,并没有指标id,不会有影响)
+	var (
+		hasIndent   = false
+		hasEncoding = false
+	)
+	if web.BConfig.RunMode == web.PROD {
+		hasIndent = false
+	}
+	if len(encoding) > 0 && encoding[0] == true {
+		hasEncoding = true
+	}
+	if c.Data["json"] == nil {
+		//go utils.SendEmail("异常提醒:", "接口:"+"URI:"+c.Ctx.Input.URI()+";无返回值", utils.EmailSendToUsers)
+		//body := "接口:" + "URI:" + c.Ctx.Input.URI() + ";无返回值"
+		//go alarm_msg.SendAlarmMsg(body, 1)
+		return
+	}
+
+	baseRes := c.Data["json"].(*models.BaseResponse)
+	if baseRes != nil && baseRes.Ret != 408 {
+		//body, _ := json.Marshal(baseRes)
+		//var requestBody string
+		//method := c.Ctx.Input.Method()
+		//if method == "GET" {
+		//	requestBody = c.Ctx.Request.RequestURI
+		//} else {
+		//	requestBody, _ = url.QueryUnescape(string(c.Ctx.Input.RequestBody))
+		//}
+		//if baseRes.Ret != 200 && baseRes.IsSendEmail {
+		//	go utils.SendEmail(utils.APP_NAME_CN+"【"+utils.RunMode+"】"+"失败提醒", "URI:"+c.Ctx.Input.URI()+"<br/> "+"Params"+requestBody+" <br/>"+"ErrMsg:"+baseRes.ErrMsg+";<br/>Msg:"+baseRes.Msg+";<br/> Body:"+string(body)+"<br/>", utils.EmailSendToUsers)
+		//}
+
+		// 记录错误日志, 并清掉错误信息避免暴露给外部
+		if baseRes.ErrMsg != "" {
+
+			utils.FileLog.Info(baseRes.ErrMsg)
+			baseRes.ErrMsg = ""
+		}
+	}
+	c.JSON(c.Data["json"], hasIndent, hasEncoding)
+}
+
+func (c *BaseAuthController) JSON(data interface{}, hasIndent bool, coding bool) error {
+	c.Ctx.Output.Header("Content-Type", "application/json; charset=utf-8")
+	var content []byte
+	var err error
+	if hasIndent {
+		content, err = json.MarshalIndent(data, "", "  ")
+	} else {
+		content, err = json.Marshal(data)
+	}
+	if err != nil {
+		http.Error(c.Ctx.Output.Context.ResponseWriter, err.Error(), http.StatusInternalServerError)
+		return err
+	}
+	ip := c.Ctx.Input.IP()
+	requestBody, err := url.QueryUnescape(string(c.Ctx.Input.RequestBody))
+	if err != nil {
+		requestBody = string(c.Ctx.Input.RequestBody)
+	}
+	if requestBody == "" {
+		requestBody = c.Ctx.Input.URI()
+	}
+	c.logUri(data, requestBody, ip)
+	if coding {
+		content = []byte(utils.StringsToJSON(string(content)))
+	}
+	return c.Ctx.Output.Body(content)
+}
+
+func (c *BaseAuthController) logUri(respContent interface{}, requestBody, ip string) {
+	utils.ApiLog.Info("uri:%s,  requestBody:%s, responseBody:%s, ip:%s", c.Ctx.Input.URI(), requestBody, respContent, ip)
+	return
+}

+ 25 - 0
controllers/base_common.go

@@ -0,0 +1,25 @@
+package controllers
+
+import (
+	"eta/eta_mini_bridge/utils"
+	"net/url"
+
+	"github.com/beego/beego/v2/server/web"
+)
+
+type BaseCommonController struct {
+	web.Controller
+}
+
+func (c *BaseCommonController) Prepare() {
+	var requestBody string
+	method := c.Ctx.Input.Method()
+	if method == "GET" {
+		requestBody = c.Ctx.Request.RequestURI
+	} else {
+		requestBody, _ = url.QueryUnescape(string(c.Ctx.Input.RequestBody))
+	}
+
+	ip := c.Ctx.Input.IP()
+	utils.ApiLog.Info("uri:%s, requestBody:%s, ip:%s", c.Ctx.Input.URI(), requestBody, ip)
+}

+ 110 - 0
controllers/chart.go

@@ -0,0 +1,110 @@
+package controllers
+
+import (
+	"eta/eta_mini_bridge/models"
+	"eta/eta_mini_bridge/models/response"
+	"eta/eta_mini_bridge/utils"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type ChartController struct {
+	BaseAuthController
+}
+
+// @Title List
+// @Description create users
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Success 200 {object} models.BaseResponse
+// @Failure 403 {object} models.BaseResponse
+// @router /list [get]
+func (this *ChartController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize2
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+	total, err := models.GetChartCount()
+	if err != nil {
+		br.Msg = "获取图表列表失败"
+		br.ErrMsg = "获取图表列表失败,系统错误,Err:" + err.Error()
+		return
+	}
+	chartList, err := models.GetChartList(startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取图表列表失败"
+		br.ErrMsg = "获取图表列表失败,系统错误,Err:" + err.Error()
+		return
+	}
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(response.ChartListResp)
+	resp.List = chartList
+	resp.Paging = page
+
+	br.Ret = 200
+	br.Msg = "获取图表列表成功"
+	br.Success = true
+	br.Data = resp
+
+}
+
+// @Title Detail
+// @Description 获得图表详情
+// @Param   ChartInfoId   query   int  true       "图表详情id"
+// @Param    UniqueCode  query   string  true       "图表唯一id"
+// @Success 200 {object} models.BaseResponse
+// @Failure 403 {object} models.BaseResponse
+// @router /detail [get]
+func (this *ChartController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	chartInfoId, _ := this.GetInt("ChartInfoId")
+	uniqueCode := this.GetString("UniqueCode")
+	if chartInfoId <= 0 && uniqueCode == "" {
+		br.Msg = "图表id错误"
+		return
+	}
+
+	var chart *models.ChartInfoView
+	var err error
+	if chartInfoId > 0 {
+		chart, err = models.GetChartById(chartInfoId)
+	} else {
+		chart, err = models.GetChartByUniqueCode(uniqueCode)
+	}
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			item := new(models.ChartInfoView)
+			br.Ret = 200
+			br.Data = item
+			br.Msg = "图表不存在"
+			br.Success = true
+			return
+		}
+		br.Msg = "获取图表详情失败"
+		br.ErrMsg = "获取图表详情失败,系统错误,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Msg = "获取图表成功"
+	br.Success = true
+	br.Data = chart
+}

+ 278 - 0
controllers/chart_permission.go

@@ -0,0 +1,278 @@
+package controllers
+
+import (
+	"eta/eta_mini_bridge/models"
+	"eta/eta_mini_bridge/utils"
+	"fmt"
+	"html"
+	"sort"
+	"strconv"
+)
+
+type ChartPermissionController struct {
+	BaseAuthController
+}
+
+// List
+// @Title 系统品种一级列表
+// @Description 系统品种一级列表
+// @Success 200 {object} models.LoginResp
+// @router /list [get]
+func (this *ChartPermissionController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	items, err := models.GetChildChartPermissionListById(0)
+	if err != nil {
+		br.Msg = "权限列表获取失败"
+		br.ErrMsg = "权限列表获取失败,系统错误,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Data = items
+	br.Msg = "列表获取成功"
+	br.Success = true
+}
+
+// SecondList
+// @Title 系统品种二级列表
+// @Description 系统品种二级列表
+// @Param   chartPermissonId   query   int  true       "品种ID"
+// @Success 200 {object} []models.ChartPermission
+// @router /second/list [get]
+func (this *ChartPermissionController) SecondList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	id, _ := this.GetInt("chartPermissonId", 0)
+	if id <= 0 {
+		br.Msg = "品种id错误,获取失败"
+		return
+	}
+
+	items, err := models.GetChildChartPermissionListById(id)
+	if err != nil {
+		br.Msg = "权限列表获取失败"
+		br.ErrMsg = "权限列表获取失败,系统错误,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Data = items
+	br.Msg = "列表获取成功"
+	br.Success = true
+}
+
+// AllList
+// @Title 系统所有品种列表
+// @Description 系统所有品种列表
+// @Success 200 {object} []models.ChartPermission
+// @router /allList [get]
+func (this *ChartPermissionController) AllList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	items, err := models.GetChartPermissionList()
+	if err != nil {
+		br.Msg = "权限列表获取失败"
+		br.ErrMsg = "权限列表获取失败,系统错误,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Data = items
+	br.Msg = "列表获取成功"
+	br.Success = true
+}
+
+// Public
+// @Title 获取公有共有权限列表
+// @Description 获取所有公有权限列表
+// @Success 200 {object} []models.ChartPermission
+// @router /public/list [get]
+func (this *ChartPermissionController) Public() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if err := recover(); err != nil {
+			fmt.Println(err)
+			return
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	items, err := models.GetChartPermissionList()
+	if err != nil {
+		br.Msg = "权限列表获取失败"
+		br.ErrMsg = "权限列表获取失败,系统错误,Err:" + err.Error()
+		return
+	}
+	rootMap := make(map[int]*models.ChartPermission)
+	publicList := make([]*models.ChartPermission, 0)
+	for _, item := range items {
+		if item.ParentId == 0 {
+			rootMap[item.ChartPermissionId] = item
+		} else if item.IsPublic == 1 {
+			publicList = append(publicList, item)
+		}
+	}
+	// 公有权限
+	publicView := getChartPermissionTree(rootMap, publicList)
+	chartList := make([]*models.ChartPermissionView, 0)
+
+	for _, v := range publicView {
+		chartList = append(chartList, v)
+	}
+	sort.Sort(models.BySortChartPermissionView(chartList))
+	br.Msg = "列表获取成功"
+	br.Data = chartList
+	br.Success = true
+	br.Ret = 200
+
+}
+
+// Private
+// @Title 获取私有共有权限列表
+// @Description 获取所有私有权限列表
+// @Success 200 {object} []models.ChartPermission
+// @router /private/list [get]
+func (this *ChartPermissionController) Private() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if err := recover(); err != nil {
+			utils.ApiLog.Error("私有权限列表获取失败,系统错误,Err:%s", err)
+			return
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	items, err := models.GetChartPermissionList()
+	if err != nil {
+		br.Msg = "权限列表获取失败"
+		br.ErrMsg = "权限列表获取失败,系统错误,Err:" + err.Error()
+		return
+	}
+	rootMap := make(map[int]*models.ChartPermission)
+	privateList := make([]*models.ChartPermission, 0)
+	for _, item := range items {
+		if item.ParentId == 0 {
+			rootMap[item.ChartPermissionId] = item
+		} else if item.IsPublic == 0 {
+			privateList = append(privateList, item)
+		}
+	}
+	// 私有权限
+	privateView := getChartPermissionTree(rootMap, privateList)
+	chartList := make([]*models.ChartPermissionView, 0)
+	for _, v := range privateView {
+		chartList = append(chartList, v)
+	}
+
+	sort.Sort(models.BySortChartPermissionView(chartList))
+	br.Msg = "列表获取成功"
+	br.Data = chartList
+	br.Success = true
+	br.Ret = 200
+
+}
+
+// Private
+// @Title 获取研报的品种权限列表
+// @Description 获取研报的品种权限列表
+// @Param   ReportId   query   int  true       "研报id"
+// @Success 200 {object} []models.ChartPermission
+// @router /detail [get]
+func (this *ChartPermissionController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if err := recover(); err != nil {
+			utils.ApiLog.Error("获取报告列表获取失败,系统错误,Err:%s", err)
+			return
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	reportId, _ := this.GetInt("ReportId")
+	if reportId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id小于等于0"
+		return
+	}
+	report, err := models.GetReportById(reportId)
+	if err != nil {
+		br.Msg = "该报告已删除"
+		br.ErrMsg = "获取报告详情失败,Err:" + err.Error()
+		return
+	}
+	report.ContentSub = html.UnescapeString(report.ContentSub)
+	report.Content = html.UnescapeString(report.Content)
+	if report == nil {
+		br.Msg = "报告不存在"
+		return
+	}
+	reportChartPermissionIds, err := models.GetChartPermissionIdsListByClassifyId(report.ClassifyIdSecond)
+	if err != nil {
+		br.Msg = "获取研报权限失败"
+		br.ErrMsg = "获取研报权限失败,Err:" + err.Error()
+		return
+	}
+	chartPermissionList, err := models.GetChartPermissionIdsByIds(reportChartPermissionIds)
+	if err != nil {
+		br.Msg = "获取研报权限失败"
+		br.ErrMsg = "获取研报权限失败,Err:" + err.Error()
+		return
+	}
+
+	br.Data = chartPermissionList
+	br.Msg = "权限列表获取成功"
+	br.Success = true
+	br.Ret = 200
+}
+
+// getChartPermissionTree获得品种权限的树形结构
+func getChartPermissionTree(rootMap map[int]*models.ChartPermission, childChartPermissionList []*models.ChartPermission) map[int]*models.ChartPermissionView {
+	resultMap := make(map[int]*models.ChartPermissionView)
+	for _, v := range childChartPermissionList {
+		if perm, ok := resultMap[v.ParentId]; ok {
+			perm.Child = append(perm.Child, &models.ChartPermissionView{
+				ChartPermissionId: v.ChartPermissionId,
+				PermissionName:    v.PermissionName,
+				IsPublic:          v.IsPublic,
+				ParentId:          v.ParentId,
+				Sort:              v.Sort,
+			})
+		} else {
+			if root, ok := rootMap[v.ParentId]; ok {
+				resultMap[v.ParentId] = &models.ChartPermissionView{
+					ChartPermissionId: root.ChartPermissionId,
+					PermissionName:    root.PermissionName,
+					IsPublic:          root.IsPublic,
+					Sort:              root.Sort,
+					ParentId:          root.ParentId,
+					Child:             make([]*models.ChartPermissionView, 0),
+				}
+				resultMap[v.ParentId].Child = append(resultMap[v.ParentId].Child, &models.ChartPermissionView{
+					ChartPermissionId: v.ChartPermissionId,
+					PermissionName:    v.PermissionName,
+					IsPublic:          v.IsPublic,
+					ParentId:          v.ParentId,
+					Sort:              v.Sort,
+				})
+			} else {
+				utils.ApiLog.Info("私有权限列表获取失败,未找到根节点,权限ID:" + strconv.Itoa(v.ChartPermissionId))
+			}
+		}
+	}
+	return resultMap
+}

+ 21 - 0
controllers/error.go

@@ -0,0 +1,21 @@
+package controllers
+
+import "eta/eta_mini_bridge/models"
+
+// ErrorController
+// @Description: 该控制器处理页面错误请求
+type ErrorController struct {
+	BaseCommonController
+}
+
+func (c *ErrorController) Error404() {
+	c.Data["content"] = "很抱歉您访问的地址或者方法不存在"
+	//c.TplName = "error/404.html"
+
+	br := new(models.BaseResponse).Init()
+
+	br.Msg = "您访问的资源不存在"
+	br.Ret = 404
+	c.Data["json"] = br
+	c.ServeJSON()
+}

+ 0 - 92
controllers/object.go

@@ -1,92 +0,0 @@
-package controllers
-
-import (
-	"eta/eta_mini_bridge/models"
-	"encoding/json"
-
-	beego "github.com/beego/beego/v2/server/web"
-)
-
-// Operations about object
-type ObjectController struct {
-	beego.Controller
-}
-
-// @Title Create
-// @Description create object
-// @Param	body		body 	models.Object	true		"The object content"
-// @Success 200 {string} models.Object.Id
-// @Failure 403 body is empty
-// @router / [post]
-func (o *ObjectController) Post() {
-	var ob models.Object
-	json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
-	objectid := models.AddOne(ob)
-	o.Data["json"] = map[string]string{"ObjectId": objectid}
-	o.ServeJSON()
-}
-
-// @Title Get
-// @Description find object by objectid
-// @Param	objectId		path 	string	true		"the objectid you want to get"
-// @Success 200 {object} models.Object
-// @Failure 403 :objectId is empty
-// @router /:objectId [get]
-func (o *ObjectController) Get() {
-	objectId := o.Ctx.Input.Param(":objectId")
-	if objectId != "" {
-		ob, err := models.GetOne(objectId)
-		if err != nil {
-			o.Data["json"] = err.Error()
-		} else {
-			o.Data["json"] = ob
-		}
-	}
-	o.ServeJSON()
-}
-
-// @Title GetAll
-// @Description get all objects
-// @Success 200 {object} models.Object
-// @Failure 403 :objectId is empty
-// @router / [get]
-func (o *ObjectController) GetAll() {
-	obs := models.GetAll()
-	o.Data["json"] = obs
-	o.ServeJSON()
-}
-
-// @Title Update
-// @Description update the object
-// @Param	objectId		path 	string	true		"The objectid you want to update"
-// @Param	body		body 	models.Object	true		"The body"
-// @Success 200 {object} models.Object
-// @Failure 403 :objectId is empty
-// @router /:objectId [put]
-func (o *ObjectController) Put() {
-	objectId := o.Ctx.Input.Param(":objectId")
-	var ob models.Object
-	json.Unmarshal(o.Ctx.Input.RequestBody, &ob)
-
-	err := models.Update(objectId, ob.Score)
-	if err != nil {
-		o.Data["json"] = err.Error()
-	} else {
-		o.Data["json"] = "update success!"
-	}
-	o.ServeJSON()
-}
-
-// @Title Delete
-// @Description delete the object
-// @Param	objectId		path 	string	true		"The objectId you want to delete"
-// @Success 200 {string} delete success!
-// @Failure 403 objectId is empty
-// @router /:objectId [delete]
-func (o *ObjectController) Delete() {
-	objectId := o.Ctx.Input.Param(":objectId")
-	models.Delete(objectId)
-	o.Data["json"] = "delete success!"
-	o.ServeJSON()
-}
-

+ 443 - 0
controllers/report.go

@@ -0,0 +1,443 @@
+package controllers
+
+import (
+	"eta/eta_mini_bridge/models"
+	"eta/eta_mini_bridge/models/response"
+	"eta/eta_mini_bridge/services"
+	"eta/eta_mini_bridge/utils"
+	"html"
+	"strconv"
+	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type ReportController struct {
+	BaseAuthController
+}
+
+// @Title List
+// @Description create users
+// @Param   ChartPermissionId   query   int  true       "品种ID"
+// @Param   Level   query   int  true       "品种层级"
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   RangeType   query   string  true       "范围类型,1-一天内,2-一周内,3-半年内"
+// @Success 200 {int} models.User.Id
+// @Failure 403 body is empty
+// @router /list [get]
+func (this *ReportController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	chartPermissionId, _ := this.GetInt("ChartPermissionId")
+	level, _ := this.GetInt("Level")
+	rangeType, _ := this.GetInt("RangeType")
+
+	if chartPermissionId <= 0 {
+		br.Msg = "品种参数错误"
+		return
+	}
+	var condition string
+	switch rangeType {
+	case 1:
+		condition += ` AND DATE(a.modify_time)=DATE(NOW()) `
+	case 2:
+		condition += ` AND DATE(a.modify_time) BETWEEN DATE_SUB(NOW(),INTERVAL 1 WEEK) AND NOW() `
+	case 3:
+		condition += ` AND DATE(a.modify_time) BETWEEN DATE_SUB(NOW(),INTERVAL 6 MONTH) AND NOW() `
+	}
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	var total int
+	var reportList []*models.ReportList
+	switch level {
+	case 2:
+		classifyIds, err := models.GetClassifyIdsListById(chartPermissionId)
+		if err != nil {
+			br.Msg = "获取报告列表失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		if len(classifyIds) == 0 {
+			br.Msg = "该品种下没有绑定分类"
+			br.ErrMsg = "获取数据失败,品种id:" + strconv.Itoa(chartPermissionId)
+			return
+		}
+		tmptotal, err := models.GetReportCountByClassifyIds(classifyIds, condition)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		total = tmptotal
+		tmpReportList, err := models.GetReportListByClassifyIds(classifyIds, condition, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取报告列表失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		reportList = tmpReportList
+	case 1:
+		chartPermissionIds, err := models.GetChildChartPermissionIdsById(chartPermissionId)
+		if err != nil {
+			br.Msg = "获取报告列表失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		if len(chartPermissionIds) == 0 {
+			br.Msg = "该品种下没有绑定分类"
+			br.ErrMsg = "获取数据失败,品种id:" + strconv.Itoa(chartPermissionId)
+			return
+		}
+		classifyIds, err := models.GetClassifyIdsListByIds(chartPermissionIds)
+		if err != nil {
+			br.Msg = "获取报告列表失败"
+			br.ErrMsg = "获取报告列表失败,Err:" + err.Error()
+			return
+		}
+		if len(classifyIds) == 0 {
+			br.Msg = "该品种下没有绑定分类"
+			return
+		}
+		tmptotal, err := models.GetReportCountByClassifyIds(classifyIds, condition)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
+		total = tmptotal
+		tmpReportList, err := models.GetReportListByClassifyIds(classifyIds, condition, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取报告列表失败"
+			br.ErrMsg = "获取报告列表失败,Err:" + err.Error()
+			return
+		}
+		reportList = tmpReportList
+	default:
+		br.Msg = "层级参数错误"
+		return
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(response.ReportListResp)
+	resp.Paging = page
+	resp.List = reportList
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title 研报详情
+// @Description 研报详情接口
+// @Param   ReportId   query   int  true       "报告id"
+// @Param   UserId   query   int  true       "用户id"
+// @Success 200 {object} models.ReportDetailResp
+// @router /detail [get]
+func (this *ReportController) Detail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	reportId, err := this.GetInt("ReportId")
+	userId, err := this.GetInt("UserId")
+	if err != nil {
+		br.Msg = "参数获取失败"
+		br.ErrMsg = "参数获取失败,Err:" + err.Error()
+		return
+	}
+	if reportId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id小于等于0"
+		return
+	}
+	if userId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,用户id小于等于0"
+		return
+	}
+	user, err := models.GetUserById(userId)
+	if err != nil {
+		br.Msg = "用户不存在"
+		br.ErrMsg = "用户不存在,系统异常,Err:" + err.Error()
+		return
+	}
+	// 有效期是否到期
+	IsVail := user.ValidEndTime.After(time.Now())
+
+	report, err := models.GetReportById(reportId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Ret = 200
+			br.Data = new(response.ReportDetailResp)
+			br.Success = true
+			br.Msg = "该报告已删除或不存在"
+			return
+		}
+		br.Msg = "该报告已删除"
+		br.ErrMsg = "获取报告详情失败,Err:" + err.Error()
+		return
+	}
+	report.ContentSub = html.UnescapeString(report.ContentSub)
+	report.Content = html.UnescapeString(report.Content)
+	if report == nil {
+		br.Msg = "报告不存在"
+		return
+	}
+	reportChartPermissionIds, err := models.GetChartPermissionIdsListByClassifyId(report.ClassifyIdSecond)
+	if err != nil {
+		br.Msg = "获取研报权限失败"
+		br.ErrMsg = "获取研报权限失败,Err:" + err.Error()
+		return
+	}
+	chartPermissionList, err := models.GetChartPermissionIdsByIds(reportChartPermissionIds)
+	if err != nil {
+		br.Msg = "获取研报权限失败"
+		br.ErrMsg = "获取研报权限失败,Err:" + err.Error()
+		return
+	}
+	var IsHas bool
+	var IsPublic bool
+	for _, v := range chartPermissionList {
+		if v.IsPublic == 1 {
+			IsPublic = true
+			break
+		}
+	}
+
+	resp := new(response.ReportDetailResp)
+	if !IsPublic {
+		// 如果被禁用或是潜在用户,直接返回无阅读报告权限
+		if user.Status == 0 || user.Status == 1 {
+			resp.Report = report
+			resp.Status = utils.ReportPermissionStatusNo
+			report.Content = ""
+			br.Ret = 408
+			br.Data = resp
+			br.Msg = "用户权限不足"
+			return
+		}
+		// 如果是私有报告,用户权限过期直接返回有效期已过
+		if !IsVail {
+			resp.Report = report
+			resp.Status = utils.ReportPermissionStatusExpired
+			report.Content = ""
+			br.Ret = 408
+			br.Data = resp
+			br.Msg = "用户权限不足"
+			return
+		}
+		chartPermissionIds, err := models.GetChartPermissionIdByUserId(userId)
+		if err != nil {
+			br.Msg = "获取用户权限失败"
+			br.ErrMsg = "获取用户权限失败,Err:" + err.Error()
+			return
+		}
+		if len(chartPermissionIds) <= 0 {
+			resp.Report = report
+			resp.Status = utils.ReportPermissionStatusNo
+			report.Content = ""
+			br.Ret = 408
+			br.Data = resp
+			br.Msg = "用户权限不足"
+			return
+		}
+		classifyIds, err := models.GetClassifyIdsListByIds(chartPermissionIds)
+		if err != nil {
+			br.Msg = "获取用户权限失败"
+			br.ErrMsg = "获取用户分类权限失败,Err:" + err.Error()
+			return
+		}
+		if len(classifyIds) <= 0 {
+			resp.Report = report
+			resp.Status = utils.ReportPermissionStatusNoPermission
+			report.Content = ""
+			br.Ret = 408
+			br.Data = resp
+			br.Msg = "用户权限不足"
+			return
+		}
+		reportClassifyIdStr := strconv.Itoa(report.ClassifyIdSecond)
+		for _, v := range classifyIds {
+			if v == reportClassifyIdStr {
+				IsHas = true
+			}
+		}
+		if !IsHas {
+			resp.Report = report
+			resp.Status = utils.ReportPermissionStatusNoPermission
+			report.Content = ""
+			br.Ret = 408
+			br.Data = resp
+			br.Msg = "用户权限不足"
+			return
+		}
+	} else {
+		report.IsPublic = IsPublic
+	}
+	resp.Report = report
+	resp.Status = utils.ReportPermissionStatusHas
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// @Title List
+// @Description create users
+// @Param	body		body 	models.User	true		"body for user content"
+// @Success 200 {int} models.User.Id
+// @Failure 403 body is empty
+// @router /daily/list [get]
+func (this *ReportController) Today() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize30
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	total, err := models.GetReportDailyListCount()
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	list, err := models.GetReportDailyList(startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	classifyIds := make([]string, 0)
+	for _, v := range list {
+		classifyIds = append(classifyIds, strconv.Itoa(v.ClassifyIdSecond))
+	}
+	classifyIds = utils.Unique(classifyIds)
+	// 获取二级分类和二级品种权限的映射
+	chartPermissionMapping, err := models.GetChartPermissionIdsListByClassifyIds(classifyIds)
+	if err != nil {
+		br.Msg = "获取研报权限失败"
+		br.ErrMsg = "获取研报权限失败,Err:" + err.Error()
+		return
+	}
+	classifyToPermissionMap2 := make(map[int][]int)
+	chartPermissionIds := make([]string, 0)
+	for _, v := range chartPermissionMapping {
+		classifyToPermissionMap2[v.ClassifyId] = append(classifyToPermissionMap2[v.ClassifyId], v.ChartPermissionId)
+		chartPermissionIds = append(chartPermissionIds, strconv.Itoa(v.ChartPermissionId))
+	}
+	// 获取二级品种的权限,并建立映射
+	chartPermissionList2, err := models.GetParentChartPermissionListByIds(chartPermissionIds)
+	if err != nil {
+		br.Msg = "获取研报二级品种权限失败"
+		br.ErrMsg = "获取研报二级品种权限失败,Err:" + err.Error()
+		return
+	}
+	chartPermissionViewMap2 := make(map[int]*models.ChartPermission)
+	for _, v := range chartPermissionList2 {
+		chartPermissionViewMap2[v.ChartPermissionId] = v
+	}
+	// 获取一级品种的权限,并建立映射
+	chartPermissionList1, err := models.GetChildChartPermissionListById(0)
+	if err != nil {
+		br.Msg = "获取研报一级品种权限失败"
+		br.ErrMsg = "获取研报一级品种权限失败,Err:" + err.Error()
+		return
+	}
+	chartPermissionMap1 := make(map[int]*models.ChartPermission)
+	for _, v := range chartPermissionList1 {
+		chartPermissionMap1[v.ChartPermissionId] = v
+	}
+	// 组合数据
+	for _, v := range list {
+		var permissionNames []string
+		for _, vv := range classifyToPermissionMap2[v.ClassifyIdSecond] {
+			parent2 := chartPermissionViewMap2[vv].ParentId
+			permissionNames = append(permissionNames, chartPermissionMap1[parent2].PermissionName)
+		}
+		v.PermissionNames = utils.Unique(permissionNames)
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+
+	resp := new(response.ReportListResp)
+	resp.Paging = page
+	resp.List = list
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// @Title 研报搜索
+// @Description 研报搜索
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   KeyWord   query   string  true       "关键字"
+// @Success 200 {object} models.ReportDetailResp
+// @router /search [get]
+func (this *ReportController) Search() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	keyWord := this.GetString("KeyWord")
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	if pageSize <= 0 {
+		pageSize = utils.PageSize30
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+
+	if keyWord == "" {
+		br.Msg = "请输入关键字"
+		return
+	}
+
+	reportList, err, errMsg := services.SearchReport(keyWord, currentIndex, pageSize)
+	if err != nil {
+		br.Msg = "研报搜索失败"
+		br.ErrMsg = errMsg + ",Err:" + err.Error()
+		return
+	}
+
+	br.Data = reportList
+	br.Msg = "查询成功"
+	br.Ret = 200
+	br.Success = true
+}

+ 0 - 119
controllers/user.go

@@ -1,119 +0,0 @@
-package controllers
-
-import (
-	"eta/eta_mini_bridge/models"
-	"encoding/json"
-
-	beego "github.com/beego/beego/v2/server/web"
-)
-
-// Operations about Users
-type UserController struct {
-	beego.Controller
-}
-
-// @Title CreateUser
-// @Description create users
-// @Param	body		body 	models.User	true		"body for user content"
-// @Success 200 {int} models.User.Id
-// @Failure 403 body is empty
-// @router / [post]
-func (u *UserController) Post() {
-	var user models.User
-	json.Unmarshal(u.Ctx.Input.RequestBody, &user)
-	uid := models.AddUser(user)
-	u.Data["json"] = map[string]string{"uid": uid}
-	u.ServeJSON()
-}
-
-// @Title GetAll
-// @Description get all Users
-// @Success 200 {object} models.User
-// @router / [get]
-func (u *UserController) GetAll() {
-	users := models.GetAllUsers()
-	u.Data["json"] = users
-	u.ServeJSON()
-}
-
-// @Title Get
-// @Description get user by uid
-// @Param	uid		path 	string	true		"The key for staticblock"
-// @Success 200 {object} models.User
-// @Failure 403 :uid is empty
-// @router /:uid [get]
-func (u *UserController) Get() {
-	uid := u.GetString(":uid")
-	if uid != "" {
-		user, err := models.GetUser(uid)
-		if err != nil {
-			u.Data["json"] = err.Error()
-		} else {
-			u.Data["json"] = user
-		}
-	}
-	u.ServeJSON()
-}
-
-// @Title Update
-// @Description update the user
-// @Param	uid		path 	string	true		"The uid you want to update"
-// @Param	body		body 	models.User	true		"body for user content"
-// @Success 200 {object} models.User
-// @Failure 403 :uid is not int
-// @router /:uid [put]
-func (u *UserController) Put() {
-	uid := u.GetString(":uid")
-	if uid != "" {
-		var user models.User
-		json.Unmarshal(u.Ctx.Input.RequestBody, &user)
-		uu, err := models.UpdateUser(uid, &user)
-		if err != nil {
-			u.Data["json"] = err.Error()
-		} else {
-			u.Data["json"] = uu
-		}
-	}
-	u.ServeJSON()
-}
-
-// @Title Delete
-// @Description delete the user
-// @Param	uid		path 	string	true		"The uid you want to delete"
-// @Success 200 {string} delete success!
-// @Failure 403 uid is empty
-// @router /:uid [delete]
-func (u *UserController) Delete() {
-	uid := u.GetString(":uid")
-	models.DeleteUser(uid)
-	u.Data["json"] = "delete success!"
-	u.ServeJSON()
-}
-
-// @Title Login
-// @Description Logs user into the system
-// @Param	username		query 	string	true		"The username for login"
-// @Param	password		query 	string	true		"The password for login"
-// @Success 200 {string} login success
-// @Failure 403 user not exist
-// @router /login [get]
-func (u *UserController) Login() {
-	username := u.GetString("username")
-	password := u.GetString("password")
-	if models.Login(username, password) {
-		u.Data["json"] = "login success"
-	} else {
-		u.Data["json"] = "user not exist"
-	}
-	u.ServeJSON()
-}
-
-// @Title logout
-// @Description Logs out current logged in user session
-// @Success 200 {string} logout success
-// @router /logout [get]
-func (u *UserController) Logout() {
-	u.Data["json"] = "logout success"
-	u.ServeJSON()
-}
-

+ 177 - 0
controllers/wechat.go

@@ -0,0 +1,177 @@
+package controllers
+
+import (
+	"encoding/json"
+	"eta/eta_mini_bridge/models"
+	"eta/eta_mini_bridge/services"
+	"eta/eta_mini_bridge/services/alarm_msg"
+	"eta/eta_mini_bridge/utils"
+	"fmt"
+	"html"
+	"strconv"
+)
+
+type WeChatController struct {
+	BaseAuthController
+}
+
+// @Title 发送微信模板接口
+// @Description 发送微信模板接口
+// @Param	ReportId query int true "报告id"
+// @Success 200 {object} models.WechatSign
+// @router /send_template_msg [get]
+func (this *WeChatController) SendTemplateMsg() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.Ret != 200 {
+			b, _ := json.Marshal(br)
+			alarm_msg.SendAlarmMsg(string(b), 1)
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	reportId, _ := this.GetInt("ReportId")
+	if reportId <= 0 {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,报告id小于等于0"
+		return
+	}
+	report, err := models.GetReportById(reportId)
+	if err != nil {
+		br.Msg = "该报告已删除"
+		br.ErrMsg = "获取报告详情失败,Err:" + err.Error()
+		return
+	}
+	report.ContentSub = html.UnescapeString(report.ContentSub)
+	report.Content = html.UnescapeString(report.Content)
+	if report == nil {
+		br.Msg = "报告不存在"
+		return
+	}
+	reportChartPermissionIdStrs, err := models.GetChartPermissionIdsListByClassifyId(report.ClassifyIdSecond)
+	if err != nil {
+		br.Msg = "获取研报权限失败"
+		br.ErrMsg = "获取研报权限失败,Err:" + err.Error()
+		return
+	}
+	if len(reportChartPermissionIdStrs) == 0 {
+		br.Msg = "该研报所在分类未关联品种权限,不能推送"
+		return
+	}
+	chartPermissionList, err := models.GetChartPermissionIdsByIds(reportChartPermissionIdStrs)
+	if err != nil {
+		br.Msg = "获取研报权限失败"
+		br.ErrMsg = "获取研报权限失败,Err:" + err.Error()
+		return
+	}
+	permissionMapping, err := models.GetUserChartPermissionMapping()
+	if err != nil {
+		br.Msg = "获取用户权限失败"
+		br.ErrMsg = "获取用户权限失败,Err:" + err.Error()
+		return
+	}
+	// 获取不同二级品种分类下的的小程序用户列表映射
+	permissionMap := make(map[int][]int)
+	for _, v := range permissionMapping {
+		if _, ok := permissionMap[v.ChartPermissionId]; !ok {
+			permissionMap[v.ChartPermissionId] = make([]int, 0)
+		} else {
+			permissionMap[v.ChartPermissionId] = append(permissionMap[v.ChartPermissionId], v.UserId)
+		}
+	}
+	var IsPublic bool
+	for _, v := range chartPermissionList {
+		if v.IsPublic == 1 {
+			IsPublic = true
+			break
+		}
+	}
+	var openIds []*services.OpenIdList
+	if IsPublic {
+		userList, err := models.GetUserBySubscribe()
+		if err != nil {
+			br.Msg = "获取用户列表失败"
+			br.ErrMsg = "获取用户列表失败,Err:" + err.Error()
+			return
+		}
+		userUnionMap := make(map[int]string)
+		for _, v := range userList {
+			userUnionMap[v.UserId] = v.UnionId
+		}
+		var unionIds []string
+		for _, v := range userList {
+			unionIds = append(unionIds, v.UnionId)
+		}
+		// 给所有人,发送模板消息
+		records, err := models.GetUserRecordByUnionids(unionIds)
+		if err != nil {
+			br.Msg = "获取用户记录失败"
+			br.ErrMsg = "获取用户记录失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range records {
+			openIds = append(openIds, &services.OpenIdList{
+				OpenId: v.OpenId,
+				UserId: v.UserId,
+			})
+		}
+	} else {
+		// 给指定用户,发送模板消息
+		// 报告所属的二级品种分类
+		userList, err := models.GetUserBySubscribeAndFormatUser()
+		if err != nil {
+			br.Msg = "获取用户列表失败"
+			br.ErrMsg = "获取用户列表失败,Err:" + err.Error()
+			return
+		}
+		userUnionMap := make(map[int]string)
+		for _, v := range userList {
+			userUnionMap[v.UserId] = v.UnionId
+		}
+		var permissionIds []int
+		for _, v := range reportChartPermissionIdStrs {
+			vv, _ := strconv.Atoi(v)
+			permissionIds = append(permissionIds, vv)
+		}
+		var sendUserIds []int
+		// 获取报告所属的二级品种分类对应的用户列表
+		for _, v := range permissionIds {
+			if _, ok := permissionMap[v]; ok {
+				sendUserIds = append(sendUserIds, permissionMap[v]...)
+			}
+		}
+		sendUserIds = utils.Unique(sendUserIds)
+		var unionIds []string
+		for _, v := range sendUserIds {
+			if id, ok := userUnionMap[v]; ok {
+				unionIds = append(unionIds, id)
+			}
+		}
+		records, err := models.GetUserRecordByUnionids(unionIds)
+		if err != nil {
+			br.Msg = "获取用户记录失败"
+			br.ErrMsg = "获取用户记录失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range records {
+			openIds = append(openIds, &services.OpenIdList{
+				OpenId: v.OpenId,
+				UserId: v.UserId,
+			})
+		}
+	}
+	sendData := make(map[string]interface{})
+	sendData["keyword1"] = map[string]interface{}{"value": report.ClassifyNameSecond, "color": "#173177"}
+	sendData["keyword2"] = map[string]interface{}{"value": report.Title, "color": "#173177"}
+	sendData["keyword3"] = map[string]interface{}{"value": report.PublishTime, "color": "#173177"}
+	sendData["keyword4"] = map[string]interface{}{"value": report.Abstract, "color": "#173177"}
+	// 推送模板消息
+	go func(sendData map[string]interface{}, items []*services.OpenIdList, sendType, reportId int) {
+		fmt.Println("推送模板消息:", reportId)
+		_ = services.SendMultiTemplateMsg(sendData, openIds, 1, reportId)
+	}(sendData, openIds, 1, reportId)
+
+	br.Ret = 200
+	br.Msg = "发送成功"
+	br.Success = true
+}

+ 19 - 5
go.mod

@@ -4,16 +4,26 @@ go 1.21
 
 require github.com/beego/beego/v2 v2.1.0
 
-require github.com/smartystreets/goconvey v1.6.4
+require (
+	github.com/beego/bee/v2 v2.1.0
+	github.com/go-redis/redis/v8 v8.11.5
+	github.com/go-sql-driver/mysql v1.7.0
+	github.com/olivere/elastic/v7 v7.0.32
+	github.com/rdlucklib/rdluck_tools v1.0.3
+	github.com/silenceper/wechat/v2 v2.1.6
+)
 
 require (
 	github.com/beorn7/perks v1.0.1 // indirect
+	github.com/bradfitz/gomemcache v0.0.0-20220106215444-fb4bf637b56d // indirect
 	github.com/cespare/xxhash/v2 v2.2.0 // indirect
+	github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
+	github.com/fatih/structs v1.1.0 // indirect
 	github.com/golang/protobuf v1.5.3 // indirect
-	github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
-	github.com/jtolds/gls v4.20.0+incompatible // indirect
+	github.com/josharian/intern v1.0.0 // indirect
 	github.com/kr/text v0.2.0 // indirect
+	github.com/mailru/easyjson v0.7.7 // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
@@ -22,8 +32,12 @@ require (
 	github.com/prometheus/common v0.42.0 // indirect
 	github.com/prometheus/procfs v0.9.0 // indirect
 	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
-	github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d // indirect
-	golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
+	github.com/sirupsen/logrus v1.9.0 // indirect
+	github.com/spf13/cast v1.4.1 // indirect
+	github.com/tidwall/gjson v1.14.1 // indirect
+	github.com/tidwall/match v1.1.1 // indirect
+	github.com/tidwall/pretty v1.2.0 // indirect
+	golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
 	golang.org/x/net v0.7.0 // indirect
 	golang.org/x/sys v0.6.0 // indirect
 	golang.org/x/text v0.7.0 // indirect

+ 43 - 0
main.go

@@ -1,9 +1,14 @@
 package main
 
 import (
+	"eta/eta_mini_bridge/controllers"
 	_ "eta/eta_mini_bridge/routers"
+	"fmt"
+	"runtime"
 
+	"github.com/beego/beego/v2/core/logs"
 	"github.com/beego/beego/v2/server/web"
+	"github.com/beego/beego/v2/server/web/context"
 )
 
 func main() {
@@ -11,5 +16,43 @@ func main() {
 		web.BConfig.WebConfig.DirectoryIndex = true
 		web.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
 	}
+
+	web.ErrorController(&controllers.ErrorController{})
+	// 内存调整
+	web.BConfig.MaxMemory = 1024 * 1024 * 128
+
+	web.BConfig.RecoverFunc = Recover
+
 	web.Run()
 }
+func Recover(ctx *context.Context, conf *web.Config) {
+	if err := recover(); err != nil {
+		if err == web.ErrAbort {
+			return
+		}
+		if !web.BConfig.RecoverPanic {
+			panic(err)
+		}
+		stack := ""
+		msg := fmt.Sprintf("The request url is  %v", ctx.Input.URL())
+		stack += msg + "</br>"
+		logs.Critical(msg)
+		msg = fmt.Sprintf("The request data is %v", string(ctx.Input.RequestBody))
+		stack += msg + "</br>"
+		logs.Critical(msg)
+		msg = fmt.Sprintf("Handler crashed with error %v", err)
+		stack += msg + "</br>"
+		logs.Critical(msg)
+		for i := 1; ; i++ {
+			_, file, line, ok := runtime.Caller(i)
+			if !ok {
+				break
+			}
+			logs.Critical(fmt.Sprintf("%s:%d", file, line))
+			stack = stack + fmt.Sprintln(fmt.Sprintf("%s:%d</br>", file, line))
+		}
+		//go utils.SendEmail(utils.APPNAME+"崩了"+time.Now().Format("2006-01-02 15:04:05"), stack, utils.EmailSendToUsers)
+		// go alarm_msg.SendAlarmMsg(utils.APPNAME+"崩了"+time.Now().Format("2006-01-02 15:04:05")+"<br/>"+stack, 3)
+	}
+	return
+}

+ 39 - 0
models/base.go

@@ -0,0 +1,39 @@
+package models
+
+type BaseResponse struct {
+	Ret         int
+	Msg         string
+	ErrMsg      string
+	ErrCode     string
+	Data        interface{}
+	Success     bool `description:"true 执行成功,false 执行失败"`
+	IsSendEmail bool `json:"-" description:"true 发送邮件,false 不发送邮件"`
+	IsAddLog    bool `json:"-" description:"true 新增操作日志,false 不新增操作日志" `
+}
+
+type BaseResponseRef struct {
+	Ret     int
+	Msg     string
+	ErrMsg  string
+	ErrCode string
+	Data    string
+}
+
+type BaseResponseResult struct {
+	Ret     int    `description:"状态:200 成功,408 重新登录,403:为失败"`
+	Msg     string `description:"提示信息,对用户展示"`
+	ErrMsg  string `description:"错误信息,供开发定位问题"`
+	ErrCode string `description:"错误编码,预留"`
+	Data    string `description:"返回数据,json格式字符串"`
+}
+
+func (r *BaseResponse) Init() *BaseResponse {
+	return &BaseResponse{Ret: 403, IsSendEmail: true}
+}
+
+type BaseRequest struct {
+}
+
+func (br *BaseRequest) Init() *BaseRequest {
+	return &BaseRequest{}
+}

+ 111 - 0
models/chart.go

@@ -0,0 +1,111 @@
+package models
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type ChartInfoView struct {
+	ChartInfoId       int    `orm:"column(chart_info_id);pk"`
+	ChartName         string `description:"来源名称"`
+	ChartNameEn       string `description:"英文图表名称"`
+	Unit              string `description:"中文单位名称"`
+	UnitEn            string `description:"英文单位名称"`
+	ChartClassifyId   int    `description:"图表分类id"`
+	ChartClassifyName string `description:"图表名称"`
+	SysUserId         int
+	SysUserRealName   string
+	UniqueCode        string `description:"图表唯一编码"`
+	CreateTime        time.Time
+	ModifyTime        time.Time
+	DateType          int    `description:"日期类型:1:00年至今,2:10年至今,3:15年至今,4:年初至今,5:自定义时间"`
+	StartDate         string `description:"自定义开始日期"`
+	EndDate           string `description:"自定义结束日期"`
+	IsSetName         int    `description:"设置名称"`
+	EdbInfoIds        string `description:"指标id"`
+	ChartType         int    `description:"生成样式:1:曲线图,2:季节性图"`
+	Calendar          string `description:"公历/农历"`
+	SeasonStartDate   string `description:"季节性图开始日期"`
+	SeasonEndDate     string `description:"季节性图开始日期"`
+	ChartImage        string `description:"图表图片"`
+	Sort              int    `description:"排序字段,数字越小越排前面"`
+	IsAdd             bool   `description:"true:已加入我的图库,false:未加入我的图库"`
+	MyChartId         int
+	MyChartClassifyId string `description:"我的图表分类,多个用逗号隔开"`
+	ChartClassify     []*ChartClassifyView
+	EdbEndDate        string `description:"指标最新更新日期"`
+	XMin              string `description:"图表X轴最小值"`
+	XMax              string `description:"图表X轴最大值"`
+	LeftMin           string `description:"图表左侧最小值"`
+	LeftMax           string `description:"图表左侧最大值"`
+	RightMin          string `description:"图表右侧最小值"`
+	RightMax          string `description:"图表右侧最大值"`
+	Right2Min         string `description:"图表右侧最小值"`
+	Right2Max         string `description:"图表右侧最大值"`
+	MinMaxSave        int    `description:"是否手动保存过上下限:0-否;1-是"`
+	IsEdit            bool   `description:"是否有编辑权限"`
+	IsEnChart         bool   `description:"是否展示英文标识"`
+	WarnMsg           string `description:"错误信息"`
+	Disabled          int    `description:"是否禁用,0:启用,1:禁用,默认:0"`
+	BarConfig         string `description:"柱方图的配置,json数据" json:"-"`
+	Source            int    `description:"1:ETA图库;2:商品价格曲线;3:相关性图表"`
+	//CorrelationLeadUnit string `description:"相关性图表-领先单位"`
+	ExtraConfig       string          `description:"图表额外配置,json数据"`
+	ChartSource       string          `description:"图表来源str"`
+	ChartSourceEn     string          `description:"图表来源(英文)"`
+	Button            ChartViewButton `description:"操作按钮"`
+	SeasonExtraConfig string          `description:"季节性图表中的配置,json数据"`
+	StartYear         int             `description:"当选择的日期类型为最近N年类型时,即date_type=20, 用start_year表示N"`
+	ChartThemeId      int             `description:"图表应用主题ID"`
+	ChartThemeStyle   string          `description:"图表应用主题样式"`
+	SourcesFrom       string          `description:"图表来源"`
+	Instructions      string          `description:"图表说明"`
+	MarkersLines      string          `description:"标识线"`
+	MarkersAreas      string          `description:"标识区"`
+	IsJoinPermission  int             `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
+	HaveOperaAuth     bool            `description:"是否有数据权限,默认:false"`
+	ForumChartInfoId  int             `description:"社区的图表ID"`
+}
+
+type ChartClassifyView struct {
+	ChartClassifyId   int    `orm:"column(chart_classify_id);pk"`
+	ChartClassifyName string `description:"分类名称"`
+	ParentId          int    `description:"父级id"`
+}
+
+type ChartViewButton struct {
+	IsEdit    bool `description:"是否有编辑权限"`
+	IsEnChart bool `description:"是否展示英文标识"`
+	IsAdd     bool `description:"true:已加入我的图库,false:未加入我的图库"`
+	IsCopy    bool `description:"是否有另存为按钮"`
+	IsSetName int  `description:"设置名称"`
+}
+
+func GetChartCount() (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT COUNT(*) AS count FROM chart_info WHERE 1=1 `
+	err = o.Raw(sql).QueryRow(&count)
+	return
+}
+
+func GetChartList(startSize, pageSize int) (item []*ChartInfoView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_info WHERE 1=1  ORDER BY create_time DESC LIMIT ?,? `
+	_, err = o.Raw(sql, startSize, pageSize).QueryRows(&item)
+	return
+}
+
+func GetChartById(chartInfoId int) (item *ChartInfoView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_info WHERE chart_info_id=?`
+	err = o.Raw(sql, chartInfoId).QueryRow(&item)
+	return
+}
+
+func GetChartByUniqueCode(uniqueCode string) (item *ChartInfoView, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := ` SELECT * FROM chart_info WHERE unique_code=?`
+	err = o.Raw(sql, uniqueCode).QueryRow(&item)
+	return
+}

+ 107 - 0
models/chart_permission.go

@@ -0,0 +1,107 @@
+package models
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type ChartPermission struct {
+	ChartPermissionId     int       `orm:"pk" description:"权限ID"`
+	ChartPermissionName   string    `description:"名称"`
+	PermissionName        string    `description:"权限名"`
+	Sort                  int       `description:"排序"`
+	Enabled               int       `description:"是否可用"`
+	CreatedTime           time.Time `description:"创建时间"`
+	LastUpdatedTime       time.Time `description:"更新时间"`
+	TeleconferenceSort    int       `description:"电话会类型排序"`
+	Remark                string    `description:"备注"`
+	ClassifyName          string    `description:"分类名称"`
+	ProductName           string    `description:"产品名称" `
+	ProductId             int       `description:"产品ID"`
+	ImageUrl              string    `description:"图片地址"`
+	ShowType              int       `description:"1:查研观向小程序展示"`
+	IsOther               int       `description:"是否是其他,用于查研观向小程序后台展示"`
+	IsReport              int       `description:"是否是报告,用于查研观向小程序前台报告展示"`
+	CygxAuth              int       `description:"是否是权限,用于查研观向小程序前台权限校验"`
+	PermissionType        int       `description:"1主观,2客观"`
+	YbImgUrl              string    `description:"研报小程序报告列表icon"`
+	ProductPermissionName string    `description:"种类权限名称"`
+	PriceDrivenState      int       `description:"品种价格驱动开启状态 0-关闭 1-开启"`
+	ImageUrlM             string    `description:"图片地址(查研观向移动端)"`
+	ParentId              int       `description:"父级权限id"`
+	IsPublic              int       `description:"是否是公有权限1:公有权限,0私有权限"`
+}
+
+type ChartPermissionView struct {
+	ChartPermissionId int                    `orm:"pk" description:"权限ID"`
+	PermissionName    string                 `description:"权限名"`
+	Child             []*ChartPermissionView `description:"子权限"`
+	IsPublic          int                    `description:"是否是公有权限1:公有权限,0私有权限"`
+	ParentId          int                    `description:"父级权限id"`
+	Sort              int                    `description:"排序"`
+}
+
+type BySortChartPermissionView []*ChartPermissionView
+
+func (a BySortChartPermissionView) Len() int           { return len(a) }
+func (a BySortChartPermissionView) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a BySortChartPermissionView) Less(i, j int) bool { return a[i].Sort < a[j].Sort }
+
+// GetChartPermissionList 获取全部权限品种权限列表
+func GetChartPermissionList() (items []*ChartPermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission WHERE enabled=1 AND product_id=1 ORDER BY sort ASC`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// GetChildChartPermissionListById 获取子品种权限列表
+func GetChildChartPermissionListById(id int) (items []*ChartPermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission WHERE enabled=1 AND product_id=1 AND parent_id=? ORDER BY sort ASC`
+	_, err = o.Raw(sql, id).QueryRows(&items)
+	return
+}
+
+// GetChartPermissionListByIds 根据品种ids获取品种权限列表
+func GetChartPermissionIdsByIds(chartPermissionIds []string) (items []*ChartPermission, err error) {
+	// 没有品种权限就返回空
+	if len(chartPermissionIds) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission WHERE enabled=1 AND product_id=1 `
+	if len(chartPermissionIds) > 0 {
+		sql += fmt.Sprintf(" AND chart_permission_id IN (%s) ", strings.Join(chartPermissionIds, ","))
+	}
+	sql += ` ORDER BY sort ASC `
+
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+// GetChildChartPermissionListById 获取品种权限id列表
+func GetChildChartPermissionIdsById(chartPermissionId int) (items []string, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT chart_permission_id FROM chart_permission WHERE enabled=1 AND product_id=1 AND parent_id=? ORDER BY sort ASC`
+	_, err = o.Raw(sql, chartPermissionId).QueryRows(&items)
+	return
+}
+
+// GetChildChartPermissionListById 获取品种权限id列表
+func GetParentChartPermissionListByIds(chartPermissionIds []string) (items []*ChartPermission, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM chart_permission 
+	WHERE enabled=1 AND product_id=1 `
+	var idsStr string
+	if len(chartPermissionIds) > 0 {
+		idsStr = strings.Join(chartPermissionIds, ",")
+		sql += fmt.Sprintf(" AND chart_permission_id in (%s) ", idsStr)
+	}
+	sql += ` ORDER BY sort ASC`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}

+ 58 - 0
models/chart_permission_search_key_word_mapping.go

@@ -0,0 +1,58 @@
+package models
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type ChartPermissionSearchKeyWordMapping struct {
+	Id                int `orm:"pk" description:"id"`
+	ChartPermissionId int `description:"品种id"`
+	ClassifyId        int `description:"分类id"`
+}
+
+func GetClassifyIdsListById(chartPermissionId int) (classifyIds []string, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT classify_id
+		FROM chart_permission_search_key_word_mapping	
+		WHERE chart_permission_id = ? `
+	_, err = o.Raw(sql, chartPermissionId).QueryRows(&classifyIds)
+	return
+}
+
+func GetChartPermissionIdsListByClassifyId(classifyId int) (chartPermissionIds []string, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT chart_permission_id
+		FROM chart_permission_search_key_word_mapping	
+		WHERE classify_id = ? `
+	_, err = o.Raw(sql, classifyId).QueryRows(&chartPermissionIds)
+	return
+}
+
+func GetChartPermissionIdsListByClassifyIds(classifyIds []string) (items []*ChartPermissionSearchKeyWordMapping, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT * FROM chart_permission_search_key_word_mapping	WHERE 1=1 `
+	var idsStr string
+	if len(classifyIds) > 0 {
+		idsStr = strings.Join(classifyIds, ",")
+		sql += fmt.Sprintf(" AND classify_id in (%s) ", idsStr)
+	}
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}
+
+func GetClassifyIdsListByIds(chartPermissionIds []string) (classifyIds []string, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT classify_id
+		FROM chart_permission_search_key_word_mapping	
+		WHERE 1=1 `
+	var idsStr string
+	if len(chartPermissionIds) > 0 {
+		idsStr = strings.Join(chartPermissionIds, ",")
+		sql += fmt.Sprintf(" AND chart_permission_id in (%s) ", idsStr)
+	}
+	_, err = o.Raw(sql).QueryRows(&classifyIds)
+	return
+}

+ 24 - 0
models/classify.go

@@ -0,0 +1,24 @@
+package models
+
+import (
+	"time"
+)
+
+type ClassifyDetail struct {
+	ClassifyId     int       `description:"分类id"`
+	ClassifyName   string    `description:"分类名称"`
+	Sort           int       `json:"-"`
+	ParentId       int       `description:"父级分类id"`
+	CreateTime     time.Time `description:"创建时间"`
+	ModifyTime     time.Time `description:"修改时间"`
+	Abstract       string    `description:"栏目简介"`
+	Descript       string    `description:"分享描述"`
+	ReportAuthor   string    `description:"栏目作者"`
+	AuthorDescript string    `description:"作者简介"`
+	ColumnImgUrl   string    `description:"栏目配图"`
+	HeadImgUrl     string    `description:"头部banner"`
+	AvatarImgUrl   string    `description:"头像"`
+	ReportImgUrl   string    `description:"报告配图"`
+	HomeImgUrl     string    `description:"首页配图"`
+	Stage          int       `description:"最新期数"`
+}

+ 43 - 0
models/db.go

@@ -0,0 +1,43 @@
+package models
+
+import (
+	"eta/eta_mini_bridge/utils"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+	_ "github.com/go-sql-driver/mysql"
+)
+
+func init() {
+	_ = orm.RegisterDataBase("rddp", "mysql", utils.MYSQL_URL_RDDP)
+	orm.SetMaxIdleConns("rddp", 50)
+	orm.SetMaxOpenConns("rddp", 100)
+
+	report_db, _ := orm.GetDB("rddp")
+	report_db.SetConnMaxLifetime(10 * time.Minute)
+
+	_ = orm.RegisterDataBase("data", "mysql", utils.MYSQL_URL_DATA)
+	orm.SetMaxIdleConns("data", 50)
+	orm.SetMaxOpenConns("data", 100)
+
+	data_db, _ := orm.GetDB("data")
+	data_db.SetConnMaxLifetime(10 * time.Minute)
+
+	_ = orm.RegisterDataBase("default", "mysql", utils.MYSQL_URL_MASTER)
+	orm.SetMaxIdleConns("default", 50)
+	orm.SetMaxOpenConns("default", 100)
+
+	master_db, _ := orm.GetDB("default")
+	master_db.SetConnMaxLifetime(10 * time.Minute)
+
+	orm.Debug = true
+	orm.DebugLog = orm.NewLog(utils.BinLog)
+
+	// register model
+	orm.RegisterModel(
+		new(User),
+		new(UserRecord),
+		new(UserTemplateRecord),
+	)
+
+}

+ 0 - 53
models/object.go

@@ -1,53 +0,0 @@
-package models
-
-import (
-	"errors"
-	"strconv"
-	"time"
-)
-
-var (
-	Objects map[string]*Object
-)
-
-type Object struct {
-	ObjectId   string
-	Score      int64
-	PlayerName string
-}
-
-func init() {
-	Objects = make(map[string]*Object)
-	Objects["hjkhsbnmn123"] = &Object{"hjkhsbnmn123", 100, "astaxie"}
-	Objects["mjjkxsxsaa23"] = &Object{"mjjkxsxsaa23", 101, "someone"}
-}
-
-func AddOne(object Object) (ObjectId string) {
-	object.ObjectId = "astaxie" + strconv.FormatInt(time.Now().UnixNano(), 10)
-	Objects[object.ObjectId] = &object
-	return object.ObjectId
-}
-
-func GetOne(ObjectId string) (object *Object, err error) {
-	if v, ok := Objects[ObjectId]; ok {
-		return v, nil
-	}
-	return nil, errors.New("ObjectId Not Exist")
-}
-
-func GetAll() map[string]*Object {
-	return Objects
-}
-
-func Update(ObjectId string, Score int64) (err error) {
-	if v, ok := Objects[ObjectId]; ok {
-		v.Score = Score
-		return nil
-	}
-	return errors.New("ObjectId Not Exist")
-}
-
-func Delete(ObjectId string) {
-	delete(Objects, ObjectId)
-}
-

+ 155 - 0
models/report.go

@@ -0,0 +1,155 @@
+package models
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type ReportList struct {
+	Id                 int       `description:"报告Id"`
+	AddType            int       `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int       `description:"一级分类id"`
+	ClassifyNameFirst  string    `description:"一级分类名称"`
+	ClassifyIdSecond   int       `description:"二级分类id"`
+	ClassifyNameSecond string    `description:"二级分类名称"`
+	PermissionNames    []string  `description:"二级分类名称"`
+	Title              string    `description:"标题"`
+	Abstract           string    `description:"摘要"`
+	Author             string    `description:"作者"`
+	Frequency          string    `description:"频度"`
+	CreateTime         string    `description:"创建时间"`
+	ModifyTime         time.Time `description:"修改时间"`
+	State              int       `description:"1:未发布,2:已发布"`
+	PublishTime        string    `description:"发布时间"`
+	Stage              int       `description:"期数"`
+	MsgIsSend          int       `description:"消息是否已发送,0:否,1:是"`
+	Content            string    `description:"内容"`
+	VideoUrl           string    `description:"音频文件URL"`
+	VideoName          string    `description:"音频文件名称"`
+	VideoPlaySeconds   string    `description:"音频播放时长"`
+	VideoSize          string    `description:"音频文件大小,单位M"`
+	HasPermission      int       `description:"是否拥有报告权限,1:拥有,0:没有"`
+	TitleType          string    `description:"标题类型,FICC或者权益"`
+	IsCurrentDate      int       `description:"是否当前日期:1是,0不是"`
+	ClassifyDetail
+}
+
+// GetReportDailyListCount 获得今日报告数量
+func GetReportDailyListCount() (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT COUNT(*) AS count FROM report WHERE 1=1 AND (state=2 OR state=6) AND DATE(modify_time)=DATE(NOW()) `
+	err = o.Raw(sql).QueryRow(&count)
+	return
+}
+
+func GetReportDailyList(startSize, pageSize int) (items []*ReportList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := ` SELECT a.id,a.add_type,a.classify_id_first,a.classify_name_first,a.classify_id_second,a.classify_name_second,a.title,a.abstract,a.author,a.frequency,
+			a.create_time,a.modify_time,a.state,a.publish_time,a.stage,a.msg_is_send,b.id AS classify_id,b.classify_name,b.descript,b.report_author,b.author_descript,
+            b.report_img_url,b.head_img_url,b.avatar_img_url,b.column_img_url,a.video_url,a.video_name,a.video_play_seconds,a.video_size,
+            CASE WHEN DATE(a.modify_time)=DATE(NOW()) THEN 1 ELSE 0 END AS is_current_date
+            FROM report AS a
+			INNER JOIN  classify AS b ON a.classify_id_second=b.id
+			WHERE (a.state=2 OR a.state=6) AND DATE(a.modify_time)=DATE(NOW()) AND a.classify_id_second IN (
+				SELECT DISTINCT classify_id
+				FROM chart_permission_search_key_word_mapping
+			)
+			ORDER BY  a.publish_time DESC LIMIT ?,?  `
+	_, err = o.Raw(sql, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetReportCountByClassifyIds(classifyIds []string, condition string) (count int, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT  COUNT(*) AS count  FROM report AS a
+			WHERE (a.state=2 OR a.state=6) AND a.classify_id_second IN (%s) `
+	var reportIdsStr string
+	if len(classifyIds) > 0 {
+		reportIdsStr = strings.Join(classifyIds, ",")
+		sql = fmt.Sprintf(sql, reportIdsStr)
+	}
+	if condition != "" {
+		sql += condition
+	}
+	err = o.Raw(sql).QueryRow(&count)
+	return
+}
+
+func GetReportListByClassifyIds(classifyIds []string, condition string, startSize, pageSize int) (items []*ReportList, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT a.id,a.add_type,a.classify_id_first,a.classify_name_first,a.classify_id_second,a.classify_name_second,a.title,a.abstract,a.author,a.frequency,
+			a.create_time,a.modify_time,a.state,a.publish_time,a.stage,a.msg_is_send,b.id AS classify_id,b.classify_name,b.descript,b.report_author,b.author_descript,
+            b.report_img_url,b.head_img_url,b.avatar_img_url,b.column_img_url,a.video_url,a.video_name,a.video_play_seconds,a.video_size,
+            CASE WHEN DATE(a.modify_time)=DATE(NOW()) THEN 1 ELSE 0 END AS is_current_date
+            FROM report AS a
+			INNER JOIN  classify AS b ON a.classify_id_second=b.id
+			WHERE (a.state=2 OR a.state=6) AND a.classify_id_second IN (%s) `
+
+	var reportIdsStr string
+	if len(classifyIds) > 0 {
+		reportIdsStr = strings.Join(classifyIds, ",")
+		sql = fmt.Sprintf(sql, reportIdsStr)
+	}
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY  a.publish_time DESC LIMIT ?,? `
+	_, err = o.Raw(sql, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+type ReportDetail struct {
+	Id                 int    `description:"报告Id"`
+	AddType            int    `description:"新增方式:1:新增报告,2:继承报告"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	Author             string `description:"作者"`
+	Frequency          string `description:"频度"`
+	CreateTime         string `description:"创建时间"`
+	ModifyTime         string `description:"修改时间"`
+	State              int    `description:"1:未发布,2:已发布"`
+	PublishTime        string `description:"发布时间"`
+	Stage              int    `description:"期数"`
+	MsgIsSend          int    `description:"消息是否已发送,0:否,1:是"`
+	Content            string `description:"内容"`
+	VideoUrl           string `description:"音频文件URL"`
+	VideoName          string `description:"音频文件名称"`
+	VideoPlaySeconds   string `description:"音频播放时长"`
+	VideoSize          string `description:"音频文件大小,单位M"`
+	ContentSub         string `description:"内容前两个章节"`
+	IsShowNewLabel     int    `description:"是否显示新标签"`
+	IsCurrentDate      int    `description:"是否当前日期"`
+	ClassifyName       string `description:"分类名称"`
+	TitleType          string `description:"标题类型,FICC或者权益"`
+	IsPublic           bool   `description:"是否是公共报告 "`
+}
+
+func GetReportById(reportId int) (item *ReportDetail, err error) {
+	o := orm.NewOrmUsingDB("rddp")
+	sql := `SELECT * FROM report WHERE id=?`
+	err = o.Raw(sql, reportId).QueryRow(&item)
+	return
+}
+
+type ReportCollectListItem struct {
+	ReportId            int       `description:"报告Id"`
+	ReportChapterId     int       `description:"报告章节Id"`
+	ClassifyIdFirst     int       `description:"一级分类id"`
+	ClassifyNameFirst   string    `description:"一级分类名称" `
+	ClassifyIdSecond    int       `description:"二级分类id"`
+	ClassifyNameSecond  string    `description:"二级分类名称"`
+	ReportChapterTypeId int       `description:"报告章节类型id"`
+	PublishTime         time.Time `description:"发布时间"`
+	Title               string    `description:"标题"`
+	ContentSub          string    `description:"内容前两个章节"`
+	Abstract            string    `description:"摘要"`
+	Stage               string    `description:"期数"`
+	Author              string    `description:"作者"`
+}

+ 5 - 0
models/request/wechat.go

@@ -0,0 +1,5 @@
+package request
+
+type SendWxTemplateReq struct {
+	ReportId int
+}

+ 12 - 0
models/response/chart.go

@@ -0,0 +1,12 @@
+package response
+
+import (
+	"eta/eta_mini_bridge/models"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type ChartListResp struct {
+	List   []*models.ChartInfoView
+	Paging *paging.PagingItem
+}

+ 42 - 0
models/response/report.go

@@ -0,0 +1,42 @@
+package response
+
+import (
+	"eta/eta_mini_bridge/models"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type ReportListResp struct {
+	List   []*models.ReportList
+	Paging *paging.PagingItem
+}
+
+//	type ReportDetailResp struct {
+//		Report   *models.ReportDetail   `description:"报告"`
+//		Classify *models.ClassifyDetail `description:"对应专栏"`
+//	}
+type ReportDetailResp struct {
+	Report *models.ReportDetail `description:"报告"`
+	Status int                  `description:"报告状态"`
+}
+type ReportSearchResp struct {
+	Paging *paging.PagingItem
+	List   []*models.ReportCollectListItem
+}
+
+type EsReportItem struct {
+	Author             string `description:"作者"`
+	BodyContent        string `description:"内容"`
+	Categories         string `description:"品种名称"`
+	ClassifyIdFirst    int    `description:"一级分类id"`
+	ClassifyNameFirst  string `description:"一级分类名称"`
+	ClassifyIdSecond   int    `description:"二级分类id"`
+	ClassifyNameSecond string `description:"二级分类名称"`
+	PublishState       int    `description:"1:未发布,2:已发布"`
+	PublishTime        string `description:"发布时间"`
+	ReportChapterId    int    `description:"报告章节Id"`
+	ReportId           int    `description:"报告Id"`
+	Title              string `description:"标题"`
+	Abstract           string `description:"摘要"`
+	StageStr           string `description:"期数"`
+}

+ 102 - 61
models/user.go

@@ -1,86 +1,127 @@
 package models
 
 import (
-	"errors"
-	"strconv"
+	"fmt"
+	"strings"
 	"time"
-)
 
-var (
-	UserList map[string]*User
+	"github.com/beego/beego/v2/client/orm"
 )
 
-func init() {
-	UserList = make(map[string]*User)
-	u := User{"user_11111", "astaxie", "11111", Profile{"male", 20, "Singapore", "astaxie@gmail.com"}}
-	UserList["user_11111"] = &u
+type User struct {
+	UserId         int       `orm:"pk" description:"用户id"`
+	OpenId         string    `description:"openid"`
+	UnionId        string    `description:"unionid"`
+	RealName       string    `description:"姓名"`
+	Phone          string    `description:"手机号"`
+	AreaCode       string    `description:"区号"`
+	Email          string    `description:"邮箱"`
+	SellerId       int       `description:"销售id(SysUserId)"`
+	Company        string    `description:"所属公司"`
+	ValidStartTime time.Time `description:"有效期开始时间"`
+	ValidEndTime   time.Time `description:"有效期结束时间"`
+	Status         int       `description:"用户类型: 0表示禁用,1表示潜在客户,2表示正式客户"`
+	CreateTime     time.Time `description:"系统中首次新增用户的时间"`
+	ModifyTime     time.Time `description:"系统中用户信息变更的时间"`
+	RegisterTime   time.Time `description:"用户首次登录小程序的时间"`
+	IsSubscribed   bool      `description:"是否关注公众号: 0表示没有关注,1表示关注"`
+	IsRegistered   bool      `description:"是否注册: 0表示没有注册,1表示注册"`
 }
 
-type User struct {
-	Id       string
-	Username string
-	Password string
-	Profile  Profile
+func (u *User) Insert() (insertId int64, err error) {
+	o := orm.NewOrm()
+	insertId, err = o.Insert(u)
+	return
 }
 
-type Profile struct {
-	Gender  string
-	Age     int
-	Address string
-	Email   string
+type UserItem struct {
+	UserId       int       `description:"用户id"`
+	OpenId       string    `description:"open_id"`
+	UnionId      string    `description:"union_id"`
+	NickName     string    `description:"用户昵称"`
+	RealName     string    `description:"用户实际名称"`
+	Phone        string    `description:"手机号码"`
+	Componey     string    `description:"所属公司"`
+	AreaCode     string    `description:"区号"`
+	SellerId     int       `description:"销售id"`
+	Email        string    `description:"邮箱"`
+	Headimgurl   string    `description:"用户头像,最后一个数值代表正方形头像大小(有0、46、64、96、132数值可选,0代表640*640正方形头像),用户没有头像时该项为空"`
+	ValidEndTime time.Time `description:"服务截至时间"`
+	RegisterTime time.Time `description:"登录时间,用户首次登录小程序的时间"`
+	CreateTime   time.Time `description:"系统中新增用户的时间"`
+	ModifyTime   time.Time `description:"系统中用户信息更新的时间"`
+	IsRegistered bool      `description:"是否注册:1:已注册,0:未注册"`
 }
 
-func AddUser(u User) string {
-	u.Id = "user_" + strconv.FormatInt(time.Now().UnixNano(), 10)
-	UserList[u.Id] = &u
-	return u.Id
+// 变更联系人是否已注册状态
+func ModifyUserRegisterStatus(userId int, status bool, registerTime, modifyTime time.Time) (err error) {
+	o := orm.NewOrm()
+	sql := `UPDATE user SET is_registered=?, register_time=?, modify_time=? WHERE user_id = ? `
+	_, err = o.Raw(sql, status, registerTime, modifyTime, userId).Exec()
+	return
 }
 
-func GetUser(uid string) (u *User, err error) {
-	if u, ok := UserList[uid]; ok {
-		return u, nil
-	}
-	return nil, errors.New("User not exists")
+func GetUserBySubscribe() (user []*User, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM user WHERE is_subscribed=1 AND is_registered=1 AND union_id IS NOT NULL `
+	_, err = o.Raw(sql).QueryRows(&user)
+	return
 }
 
-func GetAllUsers() map[string]*User {
-	return UserList
+// GetUserBySubscribeAndFormatUser 查询已经注册和关注公众号的正式用户
+func GetUserBySubscribeAndFormatUser() (user []*User, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM user WHERE is_subscribed=1 AND is_registered=1 AND union_id IS NOT NULL AND status=2`
+	_, err = o.Raw(sql).QueryRows(&user)
+	return
 }
 
-func UpdateUser(uid string, uu *User) (a *User, err error) {
-	if u, ok := UserList[uid]; ok {
-		if uu.Username != "" {
-			u.Username = uu.Username
-		}
-		if uu.Password != "" {
-			u.Password = uu.Password
-		}
-		if uu.Profile.Age != 0 {
-			u.Profile.Age = uu.Profile.Age
-		}
-		if uu.Profile.Address != "" {
-			u.Profile.Address = uu.Profile.Address
-		}
-		if uu.Profile.Gender != "" {
-			u.Profile.Gender = uu.Profile.Gender
-		}
-		if uu.Profile.Email != "" {
-			u.Profile.Email = uu.Profile.Email
-		}
-		return u, nil
-	}
-	return nil, errors.New("User Not Exist")
+func GetUserById(userId int) (item *User, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM user WHERE user_id=? `
+	err = o.Raw(sql, userId).QueryRow(&item)
+	return
 }
 
-func Login(username, password string) bool {
-	for _, u := range UserList {
-		if u.Username == username && u.Password == password {
-			return true
-		}
+func GetUserList(condition string, pars []interface{}) (items []*User, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM user WHERE 1=1 `
+	if condition != "" {
+		sql += condition
 	}
-	return false
+	_, err = o.Raw(sql, pars...).QueryRows(&items)
+	return
 }
 
-func DeleteUser(uid string) {
-	delete(UserList, uid)
+func GetUserItemByPhone(phone string) (item *UserItem, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM user WHERE phone=? `
+	err = o.Raw(sql, phone).QueryRow(&item)
+	return
+}
+
+func GetUserItemByEmail(email string) (item *UserItem, err error) {
+	sql := `SELECT * FROM user WHERE email=? `
+	err = orm.NewOrm().Raw(sql, email).QueryRow(&item)
+	return
+}
+func GetUserItemByUserId(userId int) (item *UserItem, err error) {
+	sql := `SELECT * FROM user WHERE user_id=? `
+	err = orm.NewOrm().Raw(sql, userId).QueryRow(&item)
+	return
+}
+
+func GetUserUnionIdListByIds(userIds []int) (items []string, err error) {
+	sql := `SELECT union_id FROM user WHERE user_id IN (%s) AND union_id IS NOT NULL`
+	var idsStr string
+	if len(userIds) > 0 {
+		var userIdsStr []string
+		for _, userId := range userIds {
+			userIdsStr = append(userIdsStr, fmt.Sprint(userId))
+		}
+		idsStr = strings.Join(userIdsStr, ",")
+		sql = fmt.Sprintf(sql, idsStr)
+	}
+	_, err = orm.NewOrm().Raw(sql).QueryRows(&items)
+	return
 }

+ 23 - 0
models/user_chart_permission_mapping.go

@@ -0,0 +1,23 @@
+package models
+
+import "github.com/beego/beego/v2/client/orm"
+
+type UserChartPermissionMapping struct {
+	UserChartPermissionMappingId int `orm:"pk" description:"id"`
+	UserId                       int `description:"用户id"`
+	ChartPermissionId            int `description:"品种id"`
+}
+
+func GetChartPermissionIdByUserId(UserId int) (items []string, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT chart_permission_id FROM user_chart_permission_mapping WHERE user_id=?`
+	_, err = o.Raw(sql, UserId).QueryRows(&items)
+	return
+}
+
+func GetUserChartPermissionMapping() (items []*UserChartPermissionMapping, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM user_chart_permission_mapping`
+	_, err = o.Raw(sql).QueryRows(&items)
+	return
+}

+ 47 - 0
models/user_record.go

@@ -0,0 +1,47 @@
+package models
+
+import (
+	"time"
+
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type UserRecord struct {
+	UserRecordId  int       `orm:"column(user_record_id);pk"`
+	OpenId        string    `description:"用户openid,最大长度:32"`
+	UnionId       string    `description:"用户unionid,最大长度:64"`
+	Subscribe     int       `description:"是否关注"`
+	SubscribeTime time.Time `description:""`
+	NickName      string    `descritpion:"用户昵称,最大长度:32"`
+	RealName      string    `descritpion:"用户实际名称,最大长度:32"`
+	Sex           int       `descritpion:"普通用户性别,1为男性,2为女性"`
+	Province      string    `description:"普通用户个人资料填写的省份,最大长度:30"`
+	City          string    `description:"普通用户个人资料填写的城市,最大长度:30"`
+	Country       string    `description:"国家,如中国为CN,最大长度:30"`
+	Headimgurl    string    `description:"用户第三方(微信)头像,最大长度:512"`
+	CreateTime    time.Time `description:"创建时间,关系添加时间、用户授权时间"`
+	SessionKey    string    `description:"微信小程序会话密钥,最大长度:255"`
+	UserId        int       `description:"用户id"`
+}
+
+// 根据用户id和平台id获取用户关系
+func GetUserRecordByUserId(userId int) (item *UserRecord, err error) {
+	sql := `SELECT * FROM user_record WHERE user_id=? `
+	err = orm.NewOrm().Raw(sql, userId).QueryRow(&item)
+	return
+}
+
+func GetUserRecordByUnionids(unionIds []string) (items []*UserRecord, err error) {
+	if len(unionIds) == 0 {
+		return
+	}
+	sql := `SELECT * FROM user_record WHERE union_id IN (`
+	for range unionIds {
+		sql += "?,"
+	}
+	sql = sql[:len(sql)-1]
+	sql += `) `
+	_, err = orm.NewOrm().Raw(sql, unionIds).QueryRows(&items)
+	return
+
+}

+ 21 - 0
models/user_template_record.go

@@ -0,0 +1,21 @@
+package models
+
+import "github.com/beego/beego/v2/client/orm"
+
+type UserTemplateRecord struct {
+	Id         int    `orm:"pk" description:"id"`
+	UserId     int    `description:"用户id"`
+	OpenId     string `description:"用户openid"`
+	SendData   string `description:"发送内容"`
+	Result     string `description:"响应结果"`
+	CreateDate string `description:"创建日期"`
+	CreateTime string `description:"创建时间"`
+	SendStatus int    `description:"发送状态"`   // 1:发送成功,0:发送失败
+	SendType   int    `description:"发送消息类型"` // 1:报告模板消息
+}
+
+func (u *UserTemplateRecord) Insert() (err error) {
+	o := orm.NewOrm()
+	_, err = o.Insert(u)
+	return
+}

+ 26 - 0
models/wx_token.go

@@ -0,0 +1,26 @@
+package models
+
+import (
+	"github.com/beego/beego/v2/client/orm"
+)
+
+type WxToken struct {
+	AccessToken string `description:"微信token"` //  微信token
+	ExpiresIn   int64  `description:"过期时间"`    // 过期时间
+	Id          int    `description:"id"`      // id
+}
+
+// Update 更新对应字段数据
+func (w *WxToken) Update(cols []string) (err error) {
+	o := orm.NewOrm()
+	_, err = o.Update(w, cols...)
+	return
+}
+
+// GetById 根据id获取accessToken信息
+func GetWxTokenById() (info WxToken, err error) {
+	o := orm.NewOrm()
+	sql := `SELECT * FROM wx_token WHERE id = ?`
+	err = o.Raw(sql, 0).QueryRow(&info)
+	return
+}

+ 0 - 0
rdlucklog/202406/20240617.http.log


+ 127 - 0
routers/commentsRouter.go

@@ -0,0 +1,127 @@
+package routers
+
+import (
+	beego "github.com/beego/beego/v2/server/web"
+	"github.com/beego/beego/v2/server/web/context/param"
+)
+
+func init() {
+
+    beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartController"] = append(beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartController"] = append(beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartPermissionController"],
+        beego.ControllerComments{
+            Method: "AllList",
+            Router: `/allList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartPermissionController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartPermissionController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartPermissionController"],
+        beego.ControllerComments{
+            Method: "Private",
+            Router: `/private/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartPermissionController"],
+        beego.ControllerComments{
+            Method: "Public",
+            Router: `/public/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartPermissionController"] = append(beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ChartPermissionController"],
+        beego.ControllerComments{
+            Method: "SecondList",
+            Router: `/second/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "Today",
+            Router: `/daily/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "Detail",
+            Router: `/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "Search",
+            Router: `/search`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:WeChatController"] = append(beego.GlobalControllerRouter["eta/eta_mini_bridge/controllers:WeChatController"],
+        beego.ControllerComments{
+            Method: "SendTemplateMsg",
+            Router: `/send_template_msg`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+}

+ 22 - 4
routers/router.go

@@ -11,18 +11,36 @@ import (
 	"eta/eta_mini_bridge/controllers"
 
 	"github.com/beego/beego/v2/server/web"
+	"github.com/beego/beego/v2/server/web/filter/cors"
 )
 
 func init() {
+	web.InsertFilter("*", web.BeforeRouter, cors.Allow(&cors.Options{
+		AllowAllOrigins:  true,
+		AllowMethods:     []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
+		AllowHeaders:     []string{"Origin", "Authorization", "Uuid", "Accesstoken", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
+		ExposeHeaders:    []string{"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
+		AllowCredentials: true,
+	}))
 	ns := web.NewNamespace("/v1",
-		web.NSNamespace("/object",
+		web.NSNamespace("/report",
 			web.NSInclude(
-				&controllers.ObjectController{},
+				&controllers.ReportController{},
 			),
 		),
-		web.NSNamespace("/user",
+		web.NSNamespace("/chart_permission",
 			web.NSInclude(
-				&controllers.UserController{},
+				&controllers.ChartPermissionController{},
+			),
+		),
+		web.NSNamespace("/chart",
+			web.NSInclude(
+				&controllers.ChartController{},
+			),
+		),
+		web.NSNamespace("/wechat",
+			web.NSInclude(
+				&controllers.WeChatController{},
 			),
 		),
 	)

+ 30 - 0
services/alarm_msg/alarm_msg.go

@@ -0,0 +1,30 @@
+package alarm_msg
+
+import (
+	"encoding/json"
+	"eta/eta_mini_bridge/utils"
+
+	"github.com/rdlucklib/rdluck_tools/http"
+)
+
+// SendAlarmMsg
+// projectName-项目名称
+// runMode-运行模式
+// msgBody-消息内容
+// level:消息基本,1:提示消息,2:警告消息,3:严重错误信息,默认为1 提示消息
+func SendAlarmMsg(msgBody string, level int) {
+	if utils.AlarmMsgUrl == `` {
+		return
+	}
+	params := make(map[string]interface{})
+	params["ProjectName"] = utils.APPNAME
+	params["RunMode"] = utils.RunMode
+	params["MsgBody"] = msgBody
+	params["Level"] = level
+	param, err := json.Marshal(params)
+	if err != nil {
+		utils.FileLog.Info("SendAlarmMsg json.Marshal Err:" + err.Error())
+		return
+	}
+	http.Post(utils.AlarmMsgUrl, string(param), "application/json")
+}

+ 276 - 0
services/elastic/report.go

@@ -0,0 +1,276 @@
+package elastic
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"eta/eta_mini_bridge/utils"
+	"fmt"
+
+	"github.com/olivere/elastic/v7"
+)
+
+// 首页搜索
+func SearchReport(keyWord string, classifyIdFirsts []int, classifyIdSeconds []int, pageIndex, pageSize int) (searchResp *elastic.SearchResult, total int64, err error) {
+	indexName := utils.EsReportIndexName
+	var must []map[string]interface{}
+
+	shouldSub := []map[string]interface{}{
+		map[string]interface{}{
+			"match_phrase": map[string]interface{}{
+				"Title": map[string]interface{}{
+					"query": keyWord,
+					//"slop": "50",
+					"boost": 5,
+				},
+			},
+		},
+		// map[string]interface{}{
+		// 	"match_phrase": map[string]interface{}{
+		// 		"Categories": map[string]interface{}{
+		// 			"query": keyWord,
+		// 			"boost": 4,
+		// 		},
+		// 	},
+		// },
+		// map[string]interface{}{
+		// 	"match_phrase": map[string]interface{}{
+		// 		"BodyContent": map[string]interface{}{
+		// 			"query": keyWord,
+		// 			"boost": 3,
+		// 		},
+		// 	},
+		// },
+	}
+	mustMap := map[string]interface{}{
+		"bool": map[string]interface{}{
+			"should": shouldSub,
+		},
+	}
+	must = append(must, mustMap)
+	filterMust := []map[string]interface{}{
+		map[string]interface{}{
+			"term": map[string]interface{}{
+				"PublishState": 2, //必须是已发布的报告
+			},
+		},
+		// map[string]interface{}{
+		// 	"terms": map[string]interface{}{
+		// 		"ClassifyIdFirst": classifyIdFirsts, //分类必须是正常显示状态
+		// 	},
+		// },
+		// map[string]interface{}{
+		// 	"terms": map[string]interface{}{
+		// 		"ClassifyIdSecond": classifyIdSeconds, //分类必须是正常显示状态
+		// 	},
+		// },
+	}
+	// filterMustNot := []map[string]interface{}{
+	// 	map[string]interface{}{
+	// 		"term": map[string]interface{}{
+	// 			"BodyContent.keyword": map[string]interface{}{
+	// 				"value": "", //过滤没有内容的报告(晨报和周报)bodyContent 不能为空
+	// 			},
+	// 		},
+	// 	},
+	// }
+	filterMap := map[string]interface{}{
+		"bool": map[string]interface{}{
+			"must": filterMust,
+			// "must_not": filterMustNot,
+		},
+	}
+	source := map[string]interface{}{
+		"query": map[string]interface{}{
+			"bool": map[string]interface{}{
+				"must":   must,
+				"filter": filterMap,
+			},
+		},
+	}
+	source["from"] = (pageIndex - 1) * pageSize
+	source["size"] = pageSize
+	source["highlight"] = map[string]interface{}{
+		"fields": map[string]interface{}{
+			"Title": map[string]interface{}{},
+			// "Categories":  map[string]interface{}{},
+			// "BodyContent": map[string]interface{}{
+			// 	//	"pre_tags" : "{{highlight}}",
+			// 	//	"post_tags": "{{/highlight}}",
+			// },
+		},
+		// "pre_tags":  "<span style=\"color:#E3B377\">",
+		"pre_tags":  "<span class=\"report-title_color\">",
+		"post_tags": "</span>",
+	}
+
+	source["sort"] = []map[string]interface{}{
+		map[string]interface{}{
+			"PublishTime.keyword": map[string]interface{}{
+				"order": "desc",
+			},
+		},
+		map[string]interface{}{
+			"_score": map[string]interface{}{
+				"order": "desc",
+			},
+		},
+	}
+	jsonstr, err := json.Marshal(source)
+	fmt.Printf("%s", jsonstr)
+
+	request := utils.EsClient.Search(indexName).Source(source) // sets the JSON request
+
+	searchResp, err = request.Do(context.Background())
+	if err != nil {
+		fmt.Print("结果err:")
+		fmt.Println(err.Error())
+		return
+	}
+
+	fmt.Print("结果正常:")
+	fmt.Println(searchResp.Status)
+	if searchResp.Status != 0 {
+		err = errors.New("查询失败")
+	}
+	total = searchResp.TotalHits()
+	return
+}
+
+// ReportListSearch 报告列表页的搜索
+func ReportListSearch(keyWord string, classifyIdFirst int, classifyIdSeconds []int, pageIndex, pageSize int) (searchResp *elastic.SearchResult, total int64, err error) {
+	indexName := utils.EsReportIndexName
+	var must []map[string]interface{}
+	shouldSub := []map[string]interface{}{
+		map[string]interface{}{
+			"match": map[string]interface{}{
+				"Title": map[string]interface{}{
+					"query": keyWord,
+					"boost": 3,
+				},
+			},
+		},
+		map[string]interface{}{
+			"match": map[string]interface{}{
+				"StageStr": map[string]interface{}{
+					"query": keyWord,
+					"boost": 2,
+				},
+			},
+		},
+		map[string]interface{}{
+			"match": map[string]interface{}{
+				"Abstract": map[string]interface{}{
+					"query": keyWord,
+					"boost": 2,
+				},
+			},
+		},
+		// map[string]interface{}{
+		// 	"match": map[string]interface{}{
+		// 		"ClassifyNameSecond": map[string]interface{}{
+		// 			"query": keyWord,
+		// 			"boost": 2,
+		// 		},
+		// 	},
+		// },
+		map[string]interface{}{
+			"match_phrase": map[string]interface{}{
+				"Title": map[string]interface{}{
+					"query": keyWord,
+					//"slop": "50",
+					"boost": 5,
+				},
+			},
+		},
+		map[string]interface{}{
+			"match_phrase": map[string]interface{}{
+				"Abstract": map[string]interface{}{
+					"query": keyWord,
+					//"slop": "50",
+					"boost": 5,
+				},
+			},
+		},
+	}
+	mustMap := map[string]interface{}{
+		"bool": map[string]interface{}{
+			"should": shouldSub,
+		},
+	}
+	must = append(must, mustMap)
+
+	filter := []map[string]interface{}{
+		map[string]interface{}{
+			"term": map[string]interface{}{
+				"PublishState": 2,
+			},
+		},
+		map[string]interface{}{
+			"term": map[string]interface{}{
+				"ReportChapterId": 0, //排除章节内容
+			},
+		},
+		// map[string]interface{}{
+		// 	"term": map[string]interface{}{
+		// 		"ClassifyIdFirst": classifyIdFirst,
+		// 	},
+		// },
+		// map[string]interface{}{
+		// 	"terms": map[string]interface{}{
+		// 		"ClassifyIdSecond": classifyIdSeconds,
+		// 	},
+		// },
+	}
+	source := map[string]interface{}{
+		"query": map[string]interface{}{
+			"bool": map[string]interface{}{
+				"must":   must,
+				"filter": filter,
+			},
+		},
+	}
+
+	// source["from"] = (pageIndex - 1) * pageSize
+	// source["size"] = pageSize
+	// source["highlight"] = map[string]interface{}{
+	// 	"fields": map[string]interface{}{
+	// 		"Title":              map[string]interface{}{},
+	// 		"Abstract":           map[string]interface{}{},
+	// 		"StageStr":           map[string]interface{}{},
+	// 		"ClassifyNameSecond": map[string]interface{}{},
+	// 	},
+	// 	"pre_tags":  "<span style=\"color:#E3B377\">",
+	// 	"post_tags": "</span>",
+	// }
+
+	// source["sort"] = []map[string]interface{}{
+	// 	map[string]interface{}{
+	// 		"PublishTime.keyword": map[string]interface{}{
+	// 			"order": "desc",
+	// 		},
+	// 	},
+	// 	map[string]interface{}{
+	// 		"_score": map[string]interface{}{
+	// 			"order": "desc",
+	// 		},
+	// 	},
+	// }
+	jsonstr, err := json.Marshal(source)
+	fmt.Printf("%s", jsonstr)
+	request := utils.EsClient.Search(indexName).Source(source) // sets the JSON request
+	searchResp, err = request.Do(context.Background())
+	if err != nil {
+		fmt.Print("结果err:")
+		fmt.Println(err.Error())
+		return
+	}
+
+	fmt.Print("结果正常:")
+	fmt.Println(searchResp.Status)
+	if searchResp.Status != 0 {
+		err = errors.New("查询失败")
+	}
+	total = searchResp.TotalHits()
+	return
+}

+ 123 - 0
services/report.go

@@ -0,0 +1,123 @@
+package services
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_mini_bridge/models"
+	"eta/eta_mini_bridge/models/response"
+	"eta/eta_mini_bridge/services/elastic"
+	"eta/eta_mini_bridge/utils"
+	"html"
+	"strings"
+	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+// addAliasToKeyword 品种别名
+func addAliasToKeyword(keyword string) string {
+	if keyword == "" {
+		return ""
+	}
+	keywordArr := make([]string, 0)
+	keywordArr = append(keywordArr, keyword)
+	if strings.Contains(keyword, "沥青") || strings.Contains(keyword, "BU") {
+		keywordArr = append(keywordArr, "沥青", "BU")
+	}
+	if strings.Contains(keyword, "MEG") || strings.Contains(keyword, "EG") || strings.Contains(keyword, "乙二醇") {
+		keywordArr = append(keywordArr, "MEG", "EG", "乙二醇")
+	}
+	if strings.Contains(keyword, "聚酯") || strings.Contains(keyword, "长丝") || strings.Contains(keyword, "短纤") || strings.Contains(keyword, "瓶片") {
+		keywordArr = append(keywordArr, "聚酯", "长丝", "短纤", "瓶片")
+	}
+	if strings.Contains(keyword, "纯苯") || strings.Contains(keyword, "苯乙烯") || strings.Contains(keyword, "EB") {
+		keywordArr = append(keywordArr, "纯苯", "苯乙烯", "EB")
+	}
+	if strings.Contains(keyword, "甲醇") || strings.Contains(keyword, "MA") {
+		keywordArr = append(keywordArr, "甲醇", "MA")
+	}
+	if strings.Contains(keyword, "橡胶") || strings.Contains(keyword, "RU") {
+		keywordArr = append(keywordArr, "橡胶", "RU")
+	}
+	if strings.Contains(keyword, "聚乙烯") || strings.Contains(keyword, "PP") || strings.Contains(keyword, "PE") {
+		keywordArr = append(keywordArr, "聚乙烯", "PP", "PE")
+	}
+	if strings.Contains(keyword, "玻璃") || strings.Contains(keyword, "纯碱") || strings.Contains(keyword, "FG") || strings.Contains(keyword, "SA") {
+		keywordArr = append(keywordArr, "玻璃", "纯碱", "FG", "SA")
+	}
+	keyword = strings.Join(keywordArr, ",")
+	return keyword
+}
+
+func SearchReport(keyWord string, pageIndex, pageSize int) (ret *response.ReportSearchResp, err error, errMsg string) {
+	//查询正常状态的分类
+	keyWord = addAliasToKeyword(keyWord)
+	searchResp, total, err := elastic.SearchReport(keyWord, []int{}, []int{}, pageIndex, pageSize)
+	if err != nil {
+		errMsg = err.Error()
+		err = errors.New("查询失败")
+		return
+	}
+	var reportList []*models.ReportCollectListItem
+	if searchResp.Hits != nil {
+		for _, v := range searchResp.Hits.Hits {
+			temp := new(models.ReportCollectListItem)
+			itemJson, tmpErr := v.Source.MarshalJSON()
+			if tmpErr != nil {
+				errMsg = tmpErr.Error()
+				err = errors.New("解析出错")
+				return
+			}
+			reportItem := new(response.EsReportItem)
+			tmpErr = json.Unmarshal(itemJson, &reportItem)
+			if tmpErr != nil {
+				errMsg = tmpErr.Error()
+				err = errors.New("解析json出错")
+				return
+			}
+			temp.ReportId = reportItem.ReportId
+			temp.ReportChapterId = reportItem.ReportChapterId
+			temp.ClassifyIdFirst = reportItem.ClassifyIdFirst
+			temp.ClassifyNameFirst = reportItem.ClassifyNameFirst
+			temp.ClassifyIdSecond = reportItem.ClassifyIdSecond
+			temp.ClassifyNameSecond = reportItem.ClassifyNameSecond
+			temp.Title = reportItem.Title
+			temp.ContentSub = reportItem.BodyContent
+			temp.PublishTime, err = time.ParseInLocation(utils.FormatDateTime, reportItem.PublishTime, time.Local)
+			temp.Abstract = reportItem.Abstract
+			temp.Author = reportItem.Author
+			temp.Stage = reportItem.StageStr
+
+			if len(v.Highlight["Title"]) > 0 {
+				temp.Title = v.Highlight["Title"][0]
+			}
+			if len(v.Highlight["BodyContent"]) > 0 {
+				temp.ContentSub = v.Highlight["BodyContent"][0]
+			}
+			//if len(v.Highlight["Categories"]) > 0 {
+			//	if temp.ClassifyNameSecond != "" {
+			//		temp.ClassifyNameSecond = "<span style=\"color:#E3B377\">"+temp.ClassifyNameSecond+"</span>"
+			//	}
+			//	if temp.ClassifyNameFirst != "" {
+			//		temp.ClassifyNameFirst = "<span style=\"color:#E3B377\">"+temp.ClassifyNameFirst+"</span>"
+			//	}
+			//}
+			temp.ContentSub = GetReportContentSub(temp.ContentSub, false)
+			reportList = append(reportList, temp)
+		}
+	}
+	ret = new(response.ReportSearchResp)
+	ret.List = reportList
+	ret.Paging = paging.GetPaging(pageIndex, pageSize, int(total))
+	return
+}
+
+// GetReportContentSub 特殊处理contentSub
+func GetReportContentSub(content string, scapeFlag bool) (contentSub string) {
+	if scapeFlag {
+		content = html.UnescapeString(content)
+		content = utils.TrimHtml(content) //只展示纯文本
+	}
+	contentSub = "<div style=\"-webkit-line-clamp: 3;-webkit-box-orient: vertical;display: -webkit-box;overflow: hidden;text-overflow: ellipsis;\">" + content + "</div>"
+	return
+}

+ 191 - 0
services/template_msg.go

@@ -0,0 +1,191 @@
+package services
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"eta/eta_mini_bridge/models"
+	"eta/eta_mini_bridge/services/alarm_msg"
+	"eta/eta_mini_bridge/utils"
+	"fmt"
+	"io"
+	"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.ClearQuota 清除发送超过当日10万次限制
+func (c *TemplateMsgSendClient) ClearQuota() (result *ClearQuotaResponse, err error) {
+	key := "CACHE_SendTemplateMsg_ERR"
+	exists := utils.Rc.IsExist(key)
+	if exists {
+		return
+	}
+	_ = utils.Rc.SetEX(key, 1, 6*time.Minute)
+
+	sendUrl := fmt.Sprintf(TemplateMsgClearQuotaUrl, c.AccessToken)
+	client := http.Client{}
+	clearData := make(map[string]interface{})
+	clearData["appid"] = utils.DW_WX_APPID
+	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 := io.ReadAll(resp.Body)
+	err = json.Unmarshal(clearBody, &result)
+	return
+}
+
+// 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, _ := io.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, er := json.Marshal(clearRes)
+			if er != nil {
+				return nil, er
+			}
+			fmt.Println("自动清理模板消息限制接口, 调用失败, ClearQuotaResponse: " + string(clearJson))
+			// go alarm_msg.SendAlarmMsg("自动清理模板消息限制接口, 调用失败, ClearQuotaResponse: "+string(clearJson), 3)
+			return
+		}
+		// 发送成功邮件
+		// go alarm_msg.SendAlarmMsg("自动清理模板消息限制接口, 调用成功", 1)
+		// 重新推送
+		go func() {
+			_, e := c.SendMsg()
+			if e != nil {
+				return
+				// reSendJson, _ := json.Marshal(reSend)
+				// alarm_msg.SendAlarmMsg("重新推送模板消息失败, SendTemplateResponse: "+string(reSendJson), 3)
+			}
+		}()
+	}
+	if sendRes.Errcode != 0 {
+		err = errors.New("推送模板消息失败, SendTemplateResponse: " + string(body))
+	}
+	return
+}
+
+// AddUserTemplateRecord 新增模板消息推送记录
+func AddUserTemplateRecord(userId, sendStatus, sendType int, openid, sendData, result string) (err error) {
+	item := &models.UserTemplateRecord{
+		UserId:     userId,
+		OpenId:     openid,
+		SendData:   sendData,
+		Result:     result,
+		CreateDate: time.Now().Format(utils.FormatDate),
+		CreateTime: time.Now().Format(utils.FormatDateTime),
+		SendStatus: sendStatus,
+		SendType:   sendType,
+	}
+	err = item.Insert()
+	return
+}
+
+// SendMultiTemplateMsg 推送模板消息至多个用户
+func SendMultiTemplateMsg(sendData map[string]interface{}, items []*OpenIdList, sendType, reportId int) (err error) {
+	ws := GetWxChat()
+	accessToken, err := ws.GetAccessToken()
+	if err != nil {
+		alarm_msg.SendAlarmMsg("获取微信token失败, Err:"+err.Error(), 1)
+		return
+	}
+
+	var wxAppPath string
+	if utils.RunMode == "debug" {
+		wxAppPath = fmt.Sprintf("pages-question/answerDetail?id=%d", 3800)
+	} else {
+		wxAppPath = fmt.Sprintf("pages-report/reportDetail/index?id=%d", reportId)
+	}
+	sendMap := make(map[string]interface{})
+	for _, item := range items {
+		sendMap["template_id"] = utils.TEMPLATE_ID_BY_PRODUCT
+		sendMap["miniprogram"] = map[string]interface{}{
+			"appid":    utils.WX_MINI_APPID,
+			"pagepath": wxAppPath,
+		}
+		sendMap["touser"] = item.OpenId
+		sendMap["data"] = sendData
+		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, string(data), string(resultJson))
+			}(item)
+		}
+		if e != nil {
+			err = e
+			return
+		}
+	}
+	return
+}

+ 46 - 0
services/user.go

@@ -0,0 +1,46 @@
+package services
+
+import (
+	"eta/eta_mini_bridge/models"
+	"eta/eta_mini_bridge/utils"
+)
+
+// 根据用户id和平台id获取用户信息
+func GetUserItemByUserId(userId int) (UserItem *models.UserItem, err error) {
+	//获取用户信息
+	UserItem, wxUserErr := models.GetUserItemByUserId(userId)
+	if wxUserErr != nil {
+		err = wxUserErr
+		return
+	}
+	//格式化返回用户数据
+	formatWxUser(UserItem)
+	return
+}
+
+// 通过用户 用户记录  和  来源平台  格式化返回 用户数据
+func formatWxUser(wxUser *models.UserItem) {
+	//根据用户id和平台id获取用户关系
+	userRecord, userRecordErr := models.GetUserRecordByUserId(wxUser.UserId)
+	if userRecordErr != nil {
+		if userRecordErr.Error() != utils.ErrNoRow() {
+			return
+		}
+		if userRecordErr.Error() == utils.ErrNoRow() {
+			return
+		}
+	}
+
+	//该openid在系统中没有关联关系
+	if userRecord == nil {
+		return
+	}
+
+	wxUser.OpenId = userRecord.OpenId
+	wxUser.UnionId = userRecord.UnionId
+	wxUser.NickName = userRecord.NickName
+	//wxUser.RealName = userRecord.RealName
+	//wxUser.BindAccount = userRecord.BindAccount
+	wxUser.Headimgurl = userRecord.Headimgurl
+	return
+}

+ 32 - 0
services/user_permission.go

@@ -0,0 +1,32 @@
+package services
+
+import (
+	"errors"
+	"eta/eta_mini_bridge/utils"
+	"strconv"
+)
+
+func CheckUserPermission(userId int) (status int, err error) {
+	if userId > 0 {
+		user, err := GetUserItemByUserId(userId)
+		if err != nil {
+			status = 40001
+			if err.Error() == utils.ErrNoRow() {
+				err = errors.New("用户信息不存在:userId:" + strconv.Itoa(userId))
+				return status, err
+			}
+			err = errors.New("获取用户信息失败:userId:" + strconv.Itoa(userId) + ";Err:" + err.Error())
+			return status, err
+		}
+		if user == nil {
+			status = 40001
+			err = errors.New("获取用户信息失败:userId:" + strconv.Itoa(userId))
+			return status, err
+		}
+
+	} else {
+		status = 40001
+		err = errors.New("用户id错误")
+	}
+	return
+}

+ 103 - 0
services/wechat.go

@@ -0,0 +1,103 @@
+package services
+
+import (
+	"errors"
+	"eta/eta_mini_bridge/utils"
+	"fmt"
+	"time"
+
+	"github.com/silenceper/wechat/v2"
+	"github.com/silenceper/wechat/v2/cache"
+	"github.com/silenceper/wechat/v2/credential"
+	"github.com/silenceper/wechat/v2/officialaccount"
+	"github.com/silenceper/wechat/v2/officialaccount/config"
+	"github.com/silenceper/wechat/v2/officialaccount/js"
+	"github.com/silenceper/wechat/v2/officialaccount/user"
+)
+
+type WechatAccessToken struct {
+}
+
+func GetWxChat() (officialAccount *officialaccount.OfficialAccount) {
+	wc := wechat.NewWechat()
+	memory := cache.NewMemory()
+	conf := &config.Config{
+		AppID:          utils.DW_WX_APPID,
+		AppSecret:      utils.DW_WX_APP_SECRET,
+		Token:          "",
+		EncodingAESKey: "",
+		Cache:          memory,
+	}
+	officialAccount = wc.GetOfficialAccount(conf)
+	wechatAccessToken := &WechatAccessToken{}
+	officialAccount.SetAccessTokenHandle(wechatAccessToken)
+	return
+}
+
+// GetAccessToken 获取accessToken
+func (wechat WechatAccessToken) GetAccessToken() (accessToken string, err error) {
+	accessToken, err = utils.Rc.RedisString(utils.CACHE_WX_ACCESS_TOKEN_HZ)
+	if accessToken != "" {
+		return
+	}
+
+	// 缓存中没有取到数据,那么就需要强制刷新的accessToken
+	tmpAccessToken, expires, tmpErr := getTokenFromServer(utils.DW_WX_APPID, utils.DW_WX_APP_SECRET)
+	if tmpAccessToken == "" {
+		err = errors.New("获取微信token失败,Err:" + tmpErr.Error())
+		return
+	}
+	redisTimeExpire := time.Duration(expires-600) * time.Second
+	err = utils.Rc.Put(utils.CACHE_WX_ACCESS_TOKEN_HZ, tmpAccessToken, redisTimeExpire)
+	if err != nil {
+		err = errors.New("更新微信token失败")
+		return
+	}
+	accessToken = tmpAccessToken
+	return
+}
+
+// //如果300s就要过期了,那么就去刷新accessToken
+// if wxToken.ExpiresIn < time.Now().Unix()+300 {
+// 	tmpAccessToken, expires, tmpErr := getTokenFromServer(WxAppId, WxAppSecret)
+// 	if tmpErr != nil {
+// 		err = tmpErr
+// 		return
+// 	}
+
+// 	var updateCols = []string{"access_token", "expires_in"}
+// 	wxToken.AccessToken = tmpAccessToken
+// 	wxToken.ExpiresIn = expires - 600 //快过期前10分钟就刷新掉
+// 	wxToken.Update(updateCols)
+// }
+// accessToken = wxToken.AccessToken
+// return refreshWxAccessToken(wxAppId, wxAppSecret)
+
+// getTokenFromServer 服务端获取accessToken
+func getTokenFromServer(appid, wxSecret string) (accessToken string, expires int64, err error) {
+	apiUrl := "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s"
+	resAccessToken, err := credential.GetTokenFromServer(fmt.Sprintf(apiUrl, appid, wxSecret))
+	if err != nil {
+		return
+	}
+
+	expires = resAccessToken.ExpiresIn
+	accessToken = resAccessToken.AccessToken
+	return
+}
+
+// GetUserInfo 获取微信用户详情
+func GetUserInfo(openid string) (userInfo *user.Info, err error) {
+	wechatClient := GetWxChat()
+	userClient := wechatClient.GetUser()
+	userInfo, err = userClient.GetUserInfo(openid)
+	return
+}
+
+// GetJsConfig 获取公众号jsConfig
+func GetJsConfig(signUrl string) (jsConf *js.Config, err error) {
+	wechatClient := GetWxChat()
+	j := wechatClient.GetJs()
+	jsConf, err = j.GetConfig(signUrl)
+	return
+}

+ 103 - 0
utils/common.go

@@ -0,0 +1,103 @@
+package utils
+
+import (
+	"crypto/hmac"
+	"crypto/sha256"
+	"encoding/base64"
+	"fmt"
+	"regexp"
+	"sort"
+	"strconv"
+	"strings"
+)
+
+// HmacSha256 计算HmacSha256
+// key 是加密所使用的key
+// data 是加密的内容
+func HmacSha256(key string, data string) []byte {
+	mac := hmac.New(sha256.New, []byte(key))
+	_, _ = mac.Write([]byte(data))
+
+	return mac.Sum(nil)
+}
+
+// 数据没有记录
+func ErrNoRow() string {
+	return "<QuerySeter> no row found"
+}
+
+// HmacSha256ToBase64 将加密后的二进制转Base64字符串
+func HmacSha256ToBase64(key string, data string) string {
+	return base64.URLEncoding.EncodeToString(HmacSha256(key, data))
+}
+
+func GetSign(nonce, timestamp, appId, secret string) (sign string) {
+	signStrMap := map[string]string{
+		"nonce":     nonce,
+		"timestamp": timestamp,
+		"appid":     appId,
+	}
+	keys := make([]string, 0, len(signStrMap))
+	for k := range signStrMap {
+		keys = append(keys, k)
+	}
+	sort.Strings(keys)
+	var signStr string
+	for _, k := range keys {
+		signStr += k + "=" + signStrMap[k] + "&"
+	}
+	signStr = strings.Trim(signStr, "&")
+	fmt.Println("signStr:" + signStr)
+	sign = HmacSha256ToBase64(secret, signStr)
+	return
+}
+
+func StringsToJSON(str string) string {
+	rs := []rune(str)
+	jsons := ""
+	for _, r := range rs {
+		rint := int(r)
+		if rint < 128 {
+			jsons += string(r)
+		} else {
+			jsons += "\\u" + strconv.FormatInt(int64(rint), 16) // json
+		}
+	}
+	return jsons
+}
+
+func StartIndex(page, pagesize int) int {
+	if page > 1 {
+		return (page - 1) * pagesize
+	}
+	return 0
+}
+
+// TrimHtml
+func TrimHtml(src string) string {
+	//将HTML标签全转换成小写
+	re, _ := regexp.Compile("\\<[\\S\\s]+?\\>")
+	src = re.ReplaceAllStringFunc(src, strings.ToLower)
+
+	re, _ = regexp.Compile("\\<img[\\S\\s]+?\\>")
+	src = re.ReplaceAllString(src, "")
+
+	re, _ = regexp.Compile("class[\\S\\s]+?>")
+	src = re.ReplaceAllString(src, "")
+	re, _ = regexp.Compile("\\<[\\S\\s]+?\\>")
+	src = re.ReplaceAllString(src, "")
+	return strings.TrimSpace(src)
+}
+
+func Unique[T comparable](slice []T) []T {
+	seen := make(map[T]struct{})
+	var unique []T
+
+	for _, v := range slice {
+		if _, exists := seen[v]; !exists {
+			unique = append(unique, v)
+			seen[v] = struct{}{}
+		}
+	}
+	return unique
+}

+ 131 - 0
utils/config.go

@@ -0,0 +1,131 @@
+package utils
+
+import (
+	"fmt"
+
+	beeLogger "github.com/beego/bee/v2/logger"
+	"github.com/beego/beego/v2/server/web"
+)
+
+// 数据库配置
+var (
+	RunMode          string // 运行模式
+	MYSQL_URL_MASTER string // 数据库地址
+	MYSQL_URL_RDDP   string // 数据库地址
+	MYSQL_URL_DATA   string
+
+	REDIS_CACHE string      //缓存地址
+	Rc          RedisClient //redis缓存
+
+)
+
+var (
+	LogPath    string //调用过程中的日志存放地址
+	LogFile    string
+	ApiLogPath string // 接口请求地址和接口返回值日志存放地址
+	ApiLogFile string
+	BinLogPath string // 数据库相关的日志存放地址
+	BinLogFile string
+	LogMaxDays int // 日志最大保留天数
+)
+
+// 小程序服务通信密钥
+var (
+	ETA_MINI_API_APPID  string
+	ETA_MINI_API_SECRET string
+)
+
+// 报警地址
+var AlarmMsgUrl string
+
+// 微信相关
+var (
+	WX_MINI_APPID          string // 小程序appid
+	WX_MINI_APP_SECRET     string
+	DW_WX_Id               string //微信原始ID
+	DW_WX_APPID            string // 东吴公众号的appid
+	DW_WX_APP_SECRET       string
+	TEMPLATE_ID_BY_PRODUCT string // 模板id
+)
+
+// ES配置
+var (
+	ES_URL      string // ES服务器地址
+	ES_USERNAME string // ES账号
+	ES_PASSWORD string // ES密码
+)
+
+// ES索引配置
+var (
+	EsReportIndexName              string //研报ES索引
+	EsSemanticAnalysisDocIndexName string //ES语义分析文档索引名
+	SmartReportIndexName           string //智能研报ES索引
+)
+
+func init() {
+	tmpRunMode, err := web.AppConfig.String("run_mode")
+	if err != nil {
+		panic(any("配置文件读取run_mode错误 " + err.Error()))
+	}
+	RunMode = tmpRunMode
+	fmt.Println("RunMode:", RunMode)
+	if RunMode == "" {
+		RunMode = "debug"
+		configPath := `/home/code/config/eta_mini_bridge/conf/app.conf`
+		fmt.Println("configPath:", configPath)
+		err = web.LoadAppConfig("ini", configPath)
+		if err != nil {
+			fmt.Println("web.LoadAppConfig Err:" + err.Error())
+		}
+	}
+
+	config, err := web.AppConfig.GetSection(RunMode)
+	if err != nil {
+		panic(any("配置文件读取错误 " + err.Error()))
+	}
+	beeLogger.Log.Info(RunMode + " 模式")
+
+	ETA_MINI_API_APPID = config["eta_mini_api_appid"]
+	ETA_MINI_API_SECRET = config["eta_mini_api_secret"]
+	// 数据库配置
+	MYSQL_URL_RDDP = config["mysql_url_rddp"]
+	MYSQL_URL_MASTER = config["mysql_url_master"]
+	MYSQL_URL_DATA = config["mysql_url_data"]
+
+	// 微信配置
+	WX_MINI_APPID = config["wx_mini_appid"]
+	WX_MINI_APP_SECRET = config["wx_mini_app_secret"]
+	DW_WX_Id = config["dw_wx_id"]
+	DW_WX_APPID = config["dw_wx_appid"]
+	DW_WX_APP_SECRET = config["dw_wx_app_secret"]
+	TEMPLATE_ID_BY_PRODUCT = config["template_id_by_product"]
+
+	// redis缓存配置
+	REDIS_CACHE = config["beego_cache"]
+	if len(REDIS_CACHE) <= 0 {
+		panic(any("redis链接参数没有配置"))
+	}
+	// 初始化缓存
+	redisClient, err := initRedis(config["redis_type"], config["beego_cache"])
+	if err != nil {
+		fmt.Println("redis链接异常:", err)
+		panic(any("redis链接参数没有配置"))
+	}
+
+	Rc = redisClient
+
+	// ES配置
+	{
+		ES_URL = config["es_url"]
+		ES_USERNAME = config["es_username"]
+		ES_PASSWORD = config["es_password"]
+	}
+	// ES 索引
+	{
+		EsReportIndexName = config["es_report_index_name"]
+		EsSemanticAnalysisDocIndexName = config["es_semantic_analysis_doc_index_name"]
+		SmartReportIndexName = config["es_smart_report_index_name"]
+	}
+	initEs()
+
+}

+ 32 - 0
utils/constants.go

@@ -0,0 +1,32 @@
+package utils
+
+// 常量定义
+const (
+	FormatTime            = "15:04:05"                //时间格式
+	FormatDate            = "2006-01-02"              //日期格式
+	FormatDateTime        = "2006-01-02 15:04:05"     //完整时间格式
+	HlbFormatDateTime     = "2006-01-02_15:04:05.999" //完整时间格式
+	FormatDateTimeUnSpace = "20060102150405"          //完整时间格式
+	PageSize15            = 15                        //列表页每页数据量
+	PageSize5             = 5
+	PageSize2             = 2
+	PageSize10            = 10
+	PageSize20            = 20
+	PageSize30            = 30
+)
+
+// 报告权限状态定义
+const (
+	ReportPermissionStatusExpired      = 1 //已过期
+	ReportPermissionStatusNoPermission = 2 //没有该品种权限
+	ReportPermissionStatusNo           = 3 //没有权限
+	ReportPermissionStatusHas          = 4 //有该品种权限
+)
+
+// 缓存key
+const (
+	CACHE_WX_ACCESS_TOKEN_HZ = "wx:accesstoken:hzyj" //弘则研究公众号 微信accessToken
+)
+const (
+	APPNAME = "东吴小程序桥接服务"
+)

+ 21 - 0
utils/elastic.go

@@ -0,0 +1,21 @@
+package utils
+
+import (
+	"github.com/olivere/elastic/v7"
+)
+
+// EsClient es客户端
+var EsClient *elastic.Client
+
+func initEs() {
+	client, err := elastic.NewClient(
+		elastic.SetURL(ES_URL),
+		elastic.SetBasicAuth(ES_USERNAME, ES_PASSWORD),
+		elastic.SetSniff(false))
+	EsClient = client
+	if err != nil {
+		panic("ElasticSearch连接失败,err:" + err.Error())
+		//go alarm_msg.SendAlarmMsg("ElasticSearch连接失败", 2)
+	}
+	return
+}

+ 114 - 0
utils/logs.go

@@ -0,0 +1,114 @@
+package utils
+
+import (
+	"encoding/json"
+	"os"
+	"path"
+
+	"github.com/beego/beego/v2/core/logs"
+)
+
+const (
+	DefaultLogPath    = "./etalogs/filelog"
+	DefaultBinlogPath = "./etalogs/binlog"
+	DefaultApiLogPath = "./etalogs/apilog"
+)
+
+var (
+	BinLog  *logs.BeeLogger
+	ApiLog  *logs.BeeLogger
+	FileLog *logs.BeeLogger
+)
+
+func init() {
+	if LogMaxDays == 0 {
+		LogMaxDays = 30
+	}
+	logPath := LogPath
+	if logPath == "" {
+		logPath = DefaultLogPath
+	}
+	logFile := LogFile
+	if logFile == "" {
+		logFile = "filelog.log"
+	}
+	os.MkdirAll(logPath, os.ModePerm)
+
+	// 打开文件
+	logFileName := path.Join(logPath, logFile)
+	FileLog = logs.NewLogger(1000000)
+	logConf := getDefaultLogConfig()
+
+	logConf.FileName = logFileName
+	b, _ := json.Marshal(logConf)
+	FileLog.SetLogger(logs.AdapterFile, string(b))
+	FileLog.EnableFuncCallDepth(true)
+	initApiLog()
+	initBinLog()
+}
+
+// 初始化apilog日志
+func initApiLog() {
+	apilogPath := ApiLogPath
+	if apilogPath == "" {
+		apilogPath = DefaultApiLogPath
+	}
+	apilogFile := ApiLogFile
+	if apilogFile == "" {
+		apilogFile = "apilog.log"
+	}
+	os.MkdirAll(apilogPath, os.ModePerm)
+	logFileName := path.Join(apilogPath, apilogFile)
+	ApiLog = logs.NewLogger(1000000)
+	logConf := getDefaultLogConfig()
+	logConf.FileName = logFileName
+
+	b, _ := json.Marshal(logConf)
+	ApiLog.SetLogger(logs.AdapterFile, string(b))
+	ApiLog.EnableFuncCallDepth(true)
+}
+
+// 初始化binlog日志
+func initBinLog() {
+	binlogPath := BinLogPath
+	if binlogPath == "" {
+		binlogPath = DefaultBinlogPath
+	}
+	binlogFile := BinLogFile
+	if binlogFile == "" {
+		binlogFile = "binlog.log"
+	}
+	os.MkdirAll(binlogPath, os.ModePerm)
+	logFileName := path.Join(binlogPath, binlogFile)
+	BinLog = logs.NewLogger(1000000)
+	logConf := getDefaultLogConfig()
+	logConf.FileName = logFileName
+
+	b, _ := json.Marshal(logConf)
+	BinLog.SetLogger(logs.AdapterFile, string(b))
+	BinLog.EnableFuncCallDepth(true)
+}
+
+type logConfig struct {
+	FileName string `json:"filename" description:"保存的文件名"`
+	MaxLines int    `json:"maxlines"  description:"每个文件保存的最大行数,默认值 1000000"`
+	MaxSize  int    `json:"maxsize" description:"每个文件保存的最大尺寸,默认值是 1 << 28, //256 MB"`
+	Daily    bool   `json:"daily" description:"是否按照每天 logrotate,默认是 true"`
+	MaxDays  int    `json:"maxdays" description:"文件最多保存多少天,默认保存 7 天"`
+	Rotate   bool   `json:"rotate" description:"是否开启 logrotate,默认是 true"`
+	Level    int    `json:"level" description:"日志保存的时候的级别,默认是 Trace 级别"`
+	Color    bool   `json:"color" description:"日志是否输出颜色"`
+}
+
+func getDefaultLogConfig() logConfig {
+	return logConfig{
+		FileName: "",
+		MaxLines: 10000000,
+		MaxSize:  1 << 28,
+		Daily:    true,
+		MaxDays:  LogMaxDays,
+		Rotate:   true,
+		Level:    logs.LevelTrace,
+		//Perm:     "",
+	}
+}

+ 34 - 0
utils/redis.go

@@ -0,0 +1,34 @@
+package utils
+
+import (
+	"eta/eta_mini_bridge/utils/redis"
+	"time"
+)
+
+type RedisClient interface {
+	Get(key string) interface{}
+	RedisBytes(key string) (data []byte, err error)
+	RedisString(key string) (data string, err error)
+	RedisInt(key string) (data int, err error)
+	Put(key string, val interface{}, timeout time.Duration) error
+	SetNX(key string, val interface{}, timeout time.Duration) bool
+	SetEX(key string, val interface{}, timeout time.Duration) bool
+	Delete(key string) error
+	IsExist(key string) bool
+	LPush(key string, val interface{}) error
+	Brpop(key string, callback func([]byte))
+	GetRedisTTL(key string) time.Duration
+	Incrby(key string, num int) (interface{}, error)
+	Do(commandName string, args ...interface{}) (reply interface{}, err error)
+}
+
+func initRedis(redisType string, conf string) (redisClient RedisClient, err error) {
+	switch redisType {
+	case "cluster": // 集群
+		redisClient, err = redis.InitClusterRedis(conf)
+	default: // 默认走单机
+		redisClient, err = redis.InitStandaloneRedis(conf)
+	}
+
+	return
+}

+ 281 - 0
utils/redis/cluster_redis.go

@@ -0,0 +1,281 @@
+package redis
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/go-redis/redis/v8"
+)
+
+// ClusterRedisClient
+// @Description: 集群的redis客户端
+type ClusterRedisClient struct {
+	redisClient *redis.ClusterClient
+}
+
+var DefaultKey = "zcmRedis"
+
+// InitClusterRedis
+// @Description: 初始化集群redis客户端
+// @param config
+// @return clusterRedisClient
+// @return err
+func InitClusterRedis(config string) (clusterRedisClient *ClusterRedisClient, err error) {
+	var cf map[string]string
+	err = json.Unmarshal([]byte(config), &cf)
+	if err != nil {
+		return
+	}
+
+	// 集群地址
+	connList := make([]string, 0)
+	if _, ok := cf["conn"]; !ok {
+		err = errors.New("config has no conn key")
+		return
+	} else {
+		connList = strings.Split(cf["conn"], ",")
+		if len(connList) <= 1 {
+			err = errors.New("conn address less than or equal to 1")
+			return
+		}
+	}
+
+	// 密码
+	if _, ok := cf["password"]; !ok {
+		cf["password"] = ""
+	}
+
+	// 创建 Redis 客户端配置对象
+	clusterOptions := &redis.ClusterOptions{
+		Addrs:    connList, // 设置 Redis 节点的 IP 地址和端口号
+		Password: cf["password"],
+	}
+
+	// 创建 Redis 集群客户端
+	client := redis.NewClusterClient(clusterOptions)
+
+	// 测试连接并获取信息
+	_, err = client.Ping(context.TODO()).Result()
+	if err != nil {
+		err = errors.New("redis 链接失败:" + err.Error())
+		return
+	}
+
+	clusterRedisClient = &ClusterRedisClient{redisClient: client}
+
+	return
+}
+
+// Get
+// @Description: 根据key获取数据(其实是返回的字节编码)
+// @receiver rc
+// @param key
+// @return interface{}
+func (rc *ClusterRedisClient) Get(key string) interface{} {
+	data, err := rc.redisClient.Get(context.TODO(), key).Bytes()
+	if err != nil {
+		return nil
+	}
+
+	return data
+}
+
+// RedisBytes
+// @Description: 根据key获取字节编码数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *ClusterRedisClient) RedisBytes(key string) (data []byte, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Bytes()
+
+	return
+}
+
+// RedisString
+// @Description: 根据key获取字符串数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *ClusterRedisClient) RedisString(key string) (data string, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Result()
+
+	return
+}
+
+// RedisInt
+// @Description: 根据key获取int数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *ClusterRedisClient) RedisInt(key string) (data int, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Int()
+
+	return
+}
+
+// Put
+// @Description: put一个数据到redis
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return error
+func (rc *ClusterRedisClient) Put(key string, val interface{}, timeout time.Duration) error {
+	var err error
+	err = rc.redisClient.SetEX(context.TODO(), key, val, timeout).Err()
+	if err != nil {
+		return err
+	}
+
+	err = rc.redisClient.HSet(context.TODO(), DefaultKey, key, true).Err()
+
+	return err
+}
+
+// SetNX
+// @Description: 设置一个会过期时间的值
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return bool
+func (rc *ClusterRedisClient) SetNX(key string, val interface{}, timeout time.Duration) bool {
+	result, err := rc.redisClient.SetNX(context.TODO(), key, val, timeout).Result()
+	if err != nil {
+		return false
+	}
+
+	return result
+}
+
+// SetEX
+// @Description: 设置一个会过期时间的值
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return bool
+func (rc *ClusterRedisClient) SetEX(key string, val interface{}, timeout time.Duration) (ok bool) {
+	result, err := rc.redisClient.SetEX(context.TODO(), key, val, timeout).Result()
+	if err != nil {
+		return false
+	}
+	if result == "OK" {
+		ok = true
+	}
+	return
+}
+
+// Delete
+// @Description: 删除redis中的键值对
+// @receiver rc
+// @param key
+// @return error
+func (rc *ClusterRedisClient) Delete(key string) error {
+	var err error
+
+	err = rc.redisClient.Del(context.TODO(), key).Err()
+	if err != nil {
+		return err
+	}
+
+	err = rc.redisClient.HDel(context.TODO(), DefaultKey, key).Err()
+
+	return err
+}
+
+// IsExist
+// @Description: 根据key判断是否写入缓存中
+// @receiver rc
+// @param key
+// @return bool
+func (rc *ClusterRedisClient) IsExist(key string) bool {
+	result, err := rc.redisClient.Exists(context.TODO(), key).Result()
+	if err != nil {
+		return false
+	}
+	if result == 0 {
+		_ = rc.redisClient.HDel(context.TODO(), DefaultKey, key).Err()
+		return false
+	}
+
+	return true
+}
+
+// LPush
+// @Description: 写入list
+// @receiver rc
+// @param key
+// @param val
+// @return error
+func (rc *ClusterRedisClient) LPush(key string, val interface{}) error {
+	data, _ := json.Marshal(val)
+	err := rc.redisClient.LPush(context.TODO(), key, data).Err()
+
+	return err
+}
+
+// Brpop
+// @Description: 从list中读取
+// @receiver rc
+// @param key
+// @param callback
+func (rc *ClusterRedisClient) Brpop(key string, callback func([]byte)) {
+	values, err := rc.redisClient.BRPop(context.TODO(), 1*time.Second, key).Result()
+	if err != nil {
+		return
+	}
+	if len(values) < 2 {
+		fmt.Println("assert is wrong")
+		return
+	}
+
+	callback([]byte(values[1]))
+
+}
+
+// GetRedisTTL
+// @Description: 获取key的过期时间
+// @receiver rc
+// @param key
+// @return time.Duration
+func (rc *ClusterRedisClient) GetRedisTTL(key string) time.Duration {
+	value, err := rc.redisClient.TTL(context.TODO(), key).Result()
+	if err != nil {
+		return 0
+	}
+
+	return value
+
+}
+
+// Incrby
+// @Description: 设置自增值
+// @receiver rc
+// @param key
+// @param num
+// @return interface{}
+// @return error
+func (rc *ClusterRedisClient) Incrby(key string, num int) (interface{}, error) {
+	return rc.redisClient.IncrBy(context.TODO(), key, int64(num)).Result()
+}
+
+// Do
+// @Description: cmd执行redis命令
+// @receiver rc
+// @param commandName
+// @param args
+// @return reply
+// @return err
+func (rc *ClusterRedisClient) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
+	newArgs := []interface{}{commandName}
+	newArgs = append(newArgs, args...)
+	return rc.redisClient.Do(context.TODO(), newArgs...).Result()
+}

+ 276 - 0
utils/redis/standalone_redis.go

@@ -0,0 +1,276 @@
+package redis
+
+import (
+	"context"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"strconv"
+	"time"
+
+	"github.com/go-redis/redis/v8"
+)
+
+// StandaloneRedisClient
+// @Description: 单机redis客户端
+type StandaloneRedisClient struct {
+	redisClient *redis.Client
+}
+
+func InitStandaloneRedis(config string) (standaloneRedis *StandaloneRedisClient, err error) {
+	var cf map[string]string
+	err = json.Unmarshal([]byte(config), &cf)
+	if err != nil {
+		return
+	}
+	//if _, ok := cf["key"]; !ok {
+	//	cf["key"] = DefaultKey
+	//}
+
+	if _, ok := cf["conn"]; !ok {
+		err = errors.New("config has no conn key")
+		return
+	}
+
+	// db库
+	dbNum := 0
+	// 如果指定了db库
+	if _, ok := cf["dbNum"]; ok {
+		dbNum, err = strconv.Atoi(cf["dbNum"])
+		if err != nil {
+			return
+		}
+	}
+
+	// 密码
+	if _, ok := cf["password"]; !ok {
+		cf["password"] = ""
+	}
+
+	client := redis.NewClient(&redis.Options{
+		Addr:     cf["conn"],
+		Password: cf["password"],
+		DB:       dbNum,
+		//PoolSize: 10, //连接池最大socket连接数,默认为10倍CPU数, 10 * runtime.NumCPU(暂不配置)
+	})
+
+	_, err = client.Ping(context.TODO()).Result()
+	if err != nil {
+		err = errors.New("redis 链接失败:" + err.Error())
+		return
+	}
+
+	standaloneRedis = &StandaloneRedisClient{redisClient: client}
+
+	return
+}
+
+// Get
+// @Description: 根据key获取数据(其实是返回的字节编码)
+// @receiver rc
+// @param key
+// @return interface{}
+func (rc *StandaloneRedisClient) Get(key string) interface{} {
+	data, err := rc.redisClient.Get(context.TODO(), key).Bytes()
+	if err != nil {
+		return nil
+	}
+
+	return data
+}
+
+// RedisBytes
+// @Description: 根据key获取字节编码数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *StandaloneRedisClient) RedisBytes(key string) (data []byte, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Bytes()
+
+	return
+}
+
+// RedisString
+// @Description: 根据key获取字符串数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *StandaloneRedisClient) RedisString(key string) (data string, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Result()
+
+	return
+}
+
+// RedisInt
+// @Description: 根据key获取int数据
+// @receiver rc
+// @param key
+// @return data
+// @return err
+func (rc *StandaloneRedisClient) RedisInt(key string) (data int, err error) {
+	data, err = rc.redisClient.Get(context.TODO(), key).Int()
+
+	return
+}
+
+// Put
+// @Description: put一个数据到redis
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return error
+func (rc *StandaloneRedisClient) Put(key string, val interface{}, timeout time.Duration) error {
+	var err error
+	err = rc.redisClient.SetEX(context.TODO(), key, val, timeout).Err()
+	if err != nil {
+		return err
+	}
+
+	err = rc.redisClient.HSet(context.TODO(), DefaultKey, key, true).Err()
+
+	return err
+}
+
+// SetNX
+// @Description: 设置一个会过期时间的值
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return bool
+func (rc *StandaloneRedisClient) SetNX(key string, val interface{}, timeout time.Duration) bool {
+	result, err := rc.redisClient.SetNX(context.TODO(), key, val, timeout).Result()
+	if err != nil {
+		return false
+	}
+
+	return result
+}
+
+// SetEX
+// @Description: 设置一个会过期时间的值
+// @receiver rc
+// @param key
+// @param val
+// @param timeout
+// @return bool
+func (rc *StandaloneRedisClient) SetEX(key string, val interface{}, timeout time.Duration) (ok bool) {
+	result, err := rc.redisClient.SetEX(context.TODO(), key, val, timeout).Result()
+	if err != nil {
+		return false
+	}
+	if result == "OK" {
+		ok = true
+	}
+	return
+}
+
+// Delete
+// @Description: 删除redis中的键值对
+// @receiver rc
+// @param key
+// @return error
+func (rc *StandaloneRedisClient) Delete(key string) error {
+	var err error
+
+	err = rc.redisClient.Del(context.TODO(), key).Err()
+	if err != nil {
+		return err
+	}
+
+	err = rc.redisClient.HDel(context.TODO(), DefaultKey, key).Err()
+
+	return err
+}
+
+// IsExist
+// @Description: 根据key判断是否写入缓存中
+// @receiver rc
+// @param key
+// @return bool
+func (rc *StandaloneRedisClient) IsExist(key string) bool {
+	result, err := rc.redisClient.Exists(context.TODO(), key).Result()
+	if err != nil {
+		return false
+	}
+	if result == 0 {
+		_ = rc.redisClient.HDel(context.TODO(), DefaultKey, key).Err()
+		return false
+	}
+
+	return true
+}
+
+// LPush
+// @Description: 写入list
+// @receiver rc
+// @param key
+// @param val
+// @return error
+func (rc *StandaloneRedisClient) LPush(key string, val interface{}) error {
+	data, _ := json.Marshal(val)
+	err := rc.redisClient.LPush(context.TODO(), key, data).Err()
+
+	return err
+}
+
+// Brpop
+// @Description: 从list中读取
+// @receiver rc
+// @param key
+// @param callback
+func (rc *StandaloneRedisClient) Brpop(key string, callback func([]byte)) {
+	values, err := rc.redisClient.BRPop(context.TODO(), 1*time.Second, key).Result()
+	if err != nil {
+		return
+	}
+	if len(values) < 2 {
+		fmt.Println("assert is wrong")
+		return
+	}
+
+	callback([]byte(values[1]))
+
+}
+
+// GetRedisTTL
+// @Description: 获取key的过期时间
+// @receiver rc
+// @param key
+// @return time.Duration
+func (rc *StandaloneRedisClient) GetRedisTTL(key string) time.Duration {
+	value, err := rc.redisClient.TTL(context.TODO(), key).Result()
+	if err != nil {
+		return 0
+	}
+
+	return value
+
+}
+
+// Incrby
+// @Description: 设置自增值
+// @receiver rc
+// @param key
+// @param num
+// @return interface{}
+// @return error
+func (rc *StandaloneRedisClient) Incrby(key string, num int) (interface{}, error) {
+	return rc.redisClient.IncrBy(context.TODO(), key, int64(num)).Result()
+}
+
+// Do
+// @Description: cmd执行redis命令
+// @receiver rc
+// @param commandName
+// @param args
+// @return reply
+// @return err
+func (rc *StandaloneRedisClient) Do(commandName string, args ...interface{}) (reply interface{}, err error) {
+	newArgs := []interface{}{commandName}
+	newArgs = append(newArgs, args...)
+	return rc.redisClient.Do(context.TODO(), newArgs...).Result()
+}