Browse Source

Merge branch 'master' into feature/eta_forum3_chart_classify

# Conflicts:
#	utils/constants.go
xyxie 2 months ago
parent
commit
cadba66ea3
100 changed files with 14562 additions and 867 deletions
  1. 53 0
      cache/wechat_platform.go
  2. 1152 0
      controllers/data_manage/base_from_radish_research.go
  3. 518 0
      controllers/data_manage/base_from_radish_research_classify.go
  4. 60 0
      controllers/data_manage/chart_common.go
  5. 2 2
      controllers/data_manage/cross_variety/tag.go
  6. 2 2
      controllers/data_manage/cross_variety/variety.go
  7. 1 1
      controllers/data_manage/edb_info.go
  8. 1 1
      controllers/data_manage/edb_info_refresh.go
  9. 1045 0
      controllers/data_manage/purang_data.go
  10. 1 1
      controllers/data_manage/supply_analysis/variety.go
  11. 24 0
      controllers/data_source/data_source.go
  12. 12 0
      controllers/english_report/email.go
  13. 93 4
      controllers/english_report/report.go
  14. 420 0
      controllers/llm/abstract.go
  15. 186 0
      controllers/llm/chat_ws_controller.go
  16. 51 0
      controllers/llm/kb_controller.go
  17. 39 0
      controllers/llm/llm_http/request.go
  18. 28 0
      controllers/llm/llm_http/response.go
  19. 294 0
      controllers/llm/promote_controller.go
  20. 241 0
      controllers/llm/question.go
  21. 334 0
      controllers/llm/user_chat_controller.go
  22. 804 0
      controllers/llm/wechat_platform.go
  23. 54 54
      controllers/ppt_english.go
  24. 59 59
      controllers/ppt_v2.go
  25. 10 2
      controllers/report_approve/report_approve.go
  26. 15 0
      controllers/report_chapter.go
  27. 126 4
      controllers/report_v2.go
  28. 85 14
      controllers/sys_group.go
  29. 5 1
      controllers/sys_role.go
  30. 75 12
      controllers/sys_team.go
  31. 1 0
      controllers/user_login.go
  32. 1 0
      go.mod
  33. 1 0
      go.sum
  34. 0 2
      main.go
  35. 24 2
      models/ai_predict_model/ai_predict_model_index.go
  36. 79 33
      models/business_conf.go
  37. 292 0
      models/data_manage/base_from_purang.go
  38. 139 0
      models/data_manage/base_from_purang_classify.go
  39. 309 0
      models/data_manage/base_from_radish_research_classify.go
  40. 192 0
      models/data_manage/base_from_radish_research_data.go
  41. 531 0
      models/data_manage/base_from_radish_research_index.go
  42. 1 1
      models/data_manage/edb_data_base.go
  43. 1 2
      models/data_manage/edb_info_relation.go
  44. 33 13
      models/english_report.go
  45. 40 0
      models/llm/user_chat_record.go
  46. 73 0
      models/llm/user_llm_chat.go
  47. 45 2
      models/ppt_english/ppt_english_group_mapping.go
  48. 5 0
      models/ppt_v2.go
  49. 9 9
      models/ppt_v2_group.go
  50. 53 6
      models/ppt_v2_group_mapping.go
  51. 37 0
      models/rag/article_kb_mapping.go
  52. 65 0
      models/rag/promote_train_record.go
  53. 135 0
      models/rag/question.go
  54. 33 0
      models/rag/request/wechat_platform.go
  55. 11 0
      models/rag/response/abstract.go
  56. 11 0
      models/rag/response/question.go
  57. 30 0
      models/rag/response/wechat_platform.go
  58. 83 0
      models/rag/tag.go
  59. 307 0
      models/rag/wechat_article.go
  60. 255 0
      models/rag/wechat_article_abstract.go
  61. 66 0
      models/rag/wechat_article_chat_record.go
  62. 184 0
      models/rag/wechat_platform.go
  63. 100 0
      models/rag/wechat_platform_tag_mapping.go
  64. 88 0
      models/rag/wechat_platform_user_mapping.go
  65. 64 39
      models/report.go
  66. 2 0
      models/report_approve/report_approve.go
  67. 21 19
      models/sandbox/sandbox_classify.go
  68. 2 0
      models/smart_report/smart_report.go
  69. 13 2
      models/system/sys_group.go
  70. 1 0
      models/system/sys_user.go
  71. 522 0
      routers/commentsRouter.go
  72. 17 0
      routers/router.go
  73. 49 0
      services/data/base_from_purang.go
  74. 611 0
      services/data/base_from_radish_research_classify.go
  75. 172 117
      services/data/chart_info.go
  76. 63 51
      services/data/chart_info_excel_balance.go
  77. 1 1
      services/data/chart_theme.go
  78. 23 0
      services/data/edb_info.go
  79. 1 1
      services/data/edb_info_refresh.go
  80. 3 3
      services/data/mysteel_chemical.go
  81. 5 2
      services/data/stl/stl.go
  82. 302 0
      services/elastic/wechat_article.go
  83. 317 0
      services/elastic/wechat_article_abstract.go
  84. 42 0
      services/english_report.go
  85. 212 0
      services/llm/base_wechat_lib.go
  86. 304 0
      services/llm/chat.go
  87. 213 0
      services/llm/chat_service.go
  88. 11 0
      services/llm/facade/bus_response/bus_response.go
  89. 18 0
      services/llm/facade/bus_response/eta_response.go
  90. 209 0
      services/llm/facade/llm_service.go
  91. 30 0
      services/llm/promote_service.go
  92. 185 116
      services/ppt/ppt_english_group.go
  93. 188 183
      services/ppt/ppt_group.go
  94. 1 1
      services/report_approve.go
  95. 419 3
      services/report_v2.go
  96. 221 97
      services/smart_report.go
  97. 43 0
      services/system.go
  98. 83 5
      services/task.go
  99. 1036 0
      services/wechat_platform.go
  100. 134 0
      services/ws_service.go

+ 53 - 0
cache/wechat_platform.go

@@ -0,0 +1,53 @@
+package cache
+
+import (
+	"eta/eta_api/utils"
+	"fmt"
+)
+
+type WechatArticleOp struct {
+	Source           string
+	WechatPlatformId int
+}
+
+// AddWechatArticleOpToCache
+// @Description: 将公众号文章操作加入缓存
+// @param wechatPlatformId
+// @param source
+// @return bool
+func AddWechatArticleOpToCache(wechatPlatformId int, source string) bool {
+	record := new(WechatArticleOp)
+	record.Source = source
+	record.WechatPlatformId = wechatPlatformId
+	if utils.Re == nil {
+		err := utils.Rc.LPush(utils.CACHE_WECHAT_PLATFORM_ARTICLE, record)
+
+		utils.FileLog.Info(fmt.Sprintf("将公众号文章操作 加入缓存 AddWechatArticleOpToCache LPush: 操作类型:%s,公众号id:%d", source, wechatPlatformId))
+		if err != nil {
+			fmt.Println("AddWechatArticleOpToCache LPush Err:" + err.Error())
+		}
+		return true
+	}
+	return false
+}
+
+// AddWechatArticleLlmOpToCache
+// @Description: 将公众号文章llm操作加入缓存
+// @param wechatPlatformId
+// @param source
+// @return bool
+func AddWechatArticleLlmOpToCache(wechatPlatformId int, source string) bool {
+	record := new(WechatArticleOp)
+	record.Source = source
+	record.WechatPlatformId = wechatPlatformId
+	if utils.Re == nil {
+		err := utils.Rc.LPush(utils.CACHE_WECHAT_PLATFORM_ARTICLE_KNOWLEDGE, record)
+
+		utils.FileLog.Info(fmt.Sprintf("将公众号文章llm操作加入缓存 加入缓存 AddWechatArticleLlmOpToCache LPush: 操作类型:%s,公众号id:%d", source, wechatPlatformId))
+		if err != nil {
+			fmt.Println("AddWechatArticleOpToCache LPush Err:" + err.Error())
+		}
+		return true
+	}
+	return false
+}

+ 1152 - 0
controllers/data_manage/base_from_radish_research.go

@@ -0,0 +1,1152 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	dataSourceModel "eta/eta_api/models/data_source"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/data"
+	"eta/eta_api/services/elastic"
+	etaTrialService "eta/eta_api/services/eta_trial"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/tealeg/xlsx"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// BaseFromRadishResearchController 萝卜投研
+type BaseFromRadishResearchController struct {
+	controllers.BaseAuthController
+}
+
+// IndexPageList
+// @Title 指标列表-分页
+// @Description 指标列表-分页
+// @Success 200 {object} data_manage.RadishResearchIndexPageListResp
+// @router /radish_research/index/page_list [get]
+func (this *BaseFromRadishResearchController) IndexPageList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var params data_manage.RadishResearchIndexListForm
+	if e := this.ParseForm(&params); e != nil {
+		br.Msg = "参数解析失败"
+		br.ErrMsg = fmt.Sprintf("参数解析失败, err: %v", e)
+		return
+	}
+	resp := new(data_manage.RadishResearchIndexPageListResp)
+	resp.List = make([]*data_manage.BaseFromRadishResearchIndexItem, 0)
+	classifyId, _ := this.GetInt("ClassifyId", -1)
+
+	// 分页查询
+	var startSize int
+	if params.PageSize <= 0 {
+		params.PageSize = utils.PageSize20
+	}
+	if params.CurrentIndex <= 0 {
+		params.CurrentIndex = 1
+	}
+	startSize = utils.StartIndex(params.CurrentIndex, params.PageSize)
+
+	// 筛选项
+	indexOb := new(data_manage.BaseFromRadishResearchIndex)
+	var (
+		cond        string
+		pars        []interface{}
+		classifyIds []int
+	)
+	// 未分类
+	if classifyId == 0 {
+		cond += fmt.Sprintf(` AND %s = ?`, indexOb.Cols().ClassifyId)
+		pars = append(pars, classifyId)
+	}
+	// 包含所有子分类的指标
+	if classifyId > 0 {
+		classifyOb := new(data_manage.BaseFromRadishResearchClassify)
+		classifies, e := classifyOb.GetItemsByCondition(fmt.Sprintf(" AND FIND_IN_SET(%d, %s)", classifyId, classifyOb.Cols().LevelPath), make([]interface{}, 0), []string{classifyOb.Cols().PrimaryId}, fmt.Sprintf("%s ASC, %s ASC", classifyOb.Cols().ParentId, classifyOb.Cols().Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取子分类IDs失败, %v", e)
+			return
+		}
+		for _, v := range classifies {
+			if v.BaseFromRadishResearchClassifyId <= 0 {
+				continue
+			}
+			classifyIds = append(classifyIds, v.BaseFromRadishResearchClassifyId)
+		}
+		if len(classifyIds) > 0 {
+			cond += fmt.Sprintf(` AND %s IN ?`, indexOb.Cols().ClassifyId)
+			pars = append(pars, classifyIds)
+		}
+	}
+	// 分类多选
+	params.ClassifyIds = strings.TrimSpace(params.ClassifyIds)
+	if params.ClassifyIds != "" {
+		idsArr := strings.Split(params.ClassifyIds, ",")
+		for _, v := range idsArr {
+			id, _ := strconv.Atoi(v)
+			if id > 0 {
+				classifyIds = append(classifyIds, id)
+			}
+		}
+		if len(classifyIds) > 0 {
+			cond += fmt.Sprintf(` AND %s IN ?`, indexOb.Cols().ClassifyId)
+			pars = append(pars, classifyIds)
+		}
+	}
+	// 频度多选
+	params.Frequencies = strings.TrimSpace(params.Frequencies)
+	if params.Frequencies != "" {
+		freArr := strings.Split(params.Frequencies, ",")
+		if len(freArr) > 0 {
+			cond += fmt.Sprintf(` AND %s IN ?`, indexOb.Cols().Frequency)
+			pars = append(pars, freArr)
+		}
+	}
+	// 关键词
+	params.Keyword = strings.TrimSpace(params.Keyword)
+	if params.Keyword != "" {
+		kw := fmt.Sprint("%", params.Keyword, "%")
+		cond += fmt.Sprintf(` AND (%s LIKE ? OR %s LIKE ?)`, indexOb.Cols().IndexName, indexOb.Cols().IndexCode)
+		pars = append(pars, kw, kw)
+	}
+	// 是否忽略已加入指标库的
+	if params.IgnoreEdbExist {
+		cond += fmt.Sprintf(` AND %s = 0`, indexOb.Cols().EdbExist)
+	}
+
+	// 列表合计
+	total, e := indexOb.GetCountByCondition(cond, pars)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取指标总数失败, %v", e)
+		return
+	}
+	if total <= 0 {
+		page := paging.GetPaging(params.CurrentIndex, params.PageSize, 0)
+		resp.Paging = page
+		br.Data = resp
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "获取成功"
+		return
+	}
+
+	items, e := indexOb.GetPageItemsByCondition(cond, pars, []string{}, "", startSize, params.PageSize)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取指标列表失败, %v", e)
+		return
+	}
+	for _, v := range items {
+		t := v.Format2Item()
+		resp.List = append(resp.List, t)
+	}
+
+	page := paging.GetPaging(params.CurrentIndex, params.PageSize, total)
+	resp.Paging = page
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// IndexDetail
+// @Title 指标详情
+// @Description 指标详情
+// @Param   IndexId  query  string  true  "指标ID"
+// @Success 200 {object} data_manage.BaseFromRadishResearchIndexDetail
+// @router /radish_research/index/detail [get]
+func (this *BaseFromRadishResearchController) IndexDetail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	indexId, _ := this.GetInt("IndexId")
+	if indexId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, IndexId: %d", indexId)
+		return
+	}
+	resp := new(data_manage.BaseFromRadishResearchIndexDetail)
+
+	indexOb := new(data_manage.BaseFromRadishResearchIndex)
+	item, e := indexOb.GetItemById(indexId)
+	if e != nil {
+		if utils.IsErrNoRow(e) {
+			br.Msg = "指标不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取指标失败, %v", e)
+		return
+	}
+	if item != nil && item.BaseFromRadishResearchIndexId <= 0 {
+		br.Msg = "指标不存在, 请刷新页面"
+		return
+	}
+	resp.BaseFromRadishResearchIndexItem = item.Format2Item()
+	resp.DataList = make([]*data_manage.BaseFromRadishResearchDataItem, 0)
+
+	dataOb := new(data_manage.BaseFromRadishResearchData)
+	cond := fmt.Sprintf(` AND %s = ?`, dataOb.Cols().IndexCode)
+	pars := make([]interface{}, 0)
+	pars = append(pars, item.IndexCode)
+	dataList, e := dataOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s DESC", dataOb.Cols().DataTime))
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取指标数据失败, %v", e)
+		return
+	}
+	for _, v := range dataList {
+		resp.DataList = append(resp.DataList, v.Format2Item())
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// IndexEdit
+// @Title 编辑指标
+// @Description 编辑指标
+// @Success 200 {object} data_manage.RadishResearchIndexEditReq
+// @router /radish_research/index/edit [post]
+func (this *BaseFromRadishResearchController) IndexEdit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.RadishResearchIndexEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.IndexId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, IndexId: %d", req.IndexId)
+		return
+	}
+
+	indexOb := new(data_manage.BaseFromRadishResearchIndex)
+	item, e := indexOb.GetItemById(req.IndexId)
+	if e != nil {
+		if utils.IsErrNoRow(e) {
+			br.Msg = "指标不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取指标失败, %v", e)
+		return
+	}
+	if item != nil && item.BaseFromRadishResearchIndexId <= 0 {
+		br.Msg = "指标不存在, 请刷新页面"
+		return
+	}
+
+	// 更新指标(这里不多限制,允许移到未分类)
+	item.ClassifyId = req.ClassifyId
+	item.ModifyTime = time.Now().Local()
+	updateCols := []string{indexOb.Cols().ClassifyId, indexOb.Cols().ModifyTime}
+	if e = item.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("更新指标分类失败, %v", e)
+		return
+	}
+
+	// 更新ES
+	go func() {
+		indexItem := new(dataSourceModel.SearchDataSource)
+		indexItem.PrimaryId = item.BaseFromRadishResearchIndexId
+		indexItem.IndexCode = item.IndexCode
+		indexItem.IndexName = item.IndexName
+		indexItem.ClassifyId = item.ClassifyId
+		indexItem.Unit = item.Unit
+		indexItem.Frequency = item.Frequency
+		indexItem.StartDate = item.StartDate.Format(utils.FormatDate)
+		indexItem.EndDate = item.EndDate.Format(utils.FormatDate)
+		indexItem.LatestValue = fmt.Sprint(item.LatestValue)
+		indexItem.Source = utils.DATA_SOURCE_RADISH_RESEARCH
+		indexItem.SourceName = utils.DATA_SOURCE_NAME_RADISH_RESEARCH
+		indexItem.IsDeleted = 0
+		indexItem.CreateTime = item.CreateTime.Format(utils.FormatDateTime)
+		indexItem.ModifyTime = item.ModifyTime.Format(utils.FormatDateTime)
+
+		docId := fmt.Sprintf("%d-%d", utils.DATA_SOURCE_RADISH_RESEARCH, item.BaseFromRadishResearchIndexId)
+		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+			utils.FileLog.Warning("RadishResearch-写入指标ES失败, %v", e)
+			return
+		}
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// IndexRemove
+// @Title 删除指标
+// @Description 删除指标
+// @Success 200 {object} data_manage.RadishResearchIndexRemoveReq
+// @router /radish_research/index/remove [post]
+func (this *BaseFromRadishResearchController) IndexRemove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.RadishResearchIndexRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if req.IndexId <= 0 {
+		br.Msg = "参数有误"
+		br.ErrMsg = fmt.Sprintf("参数有误, IndexId: %d", req.IndexId)
+		return
+	}
+
+	indexOb := new(data_manage.BaseFromRadishResearchIndex)
+	item, e := indexOb.GetItemById(req.IndexId)
+	if e != nil {
+		if utils.IsErrNoRow(e) {
+			br.Msg = "指标不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取指标失败, %v", e)
+		return
+	}
+	if item != nil && item.BaseFromRadishResearchIndexId <= 0 {
+		br.Msg = "指标不存在, 请刷新页面"
+		return
+	}
+	if item.EdbExist == 1 {
+		br.Msg = "指标已被引用, 不允许删除"
+		return
+	}
+	if e = indexOb.RemoveIndexAndData(item.IndexCode); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("删除指标和数据失败, %v", e)
+		return
+	}
+
+	// 更新ES
+	go func() {
+		indexItem := new(dataSourceModel.SearchDataSource)
+		indexItem.PrimaryId = item.BaseFromRadishResearchIndexId
+		indexItem.IndexCode = item.IndexCode
+		indexItem.IndexName = item.IndexName
+		indexItem.ClassifyId = item.ClassifyId
+		indexItem.Unit = item.Unit
+		indexItem.Frequency = item.Frequency
+		indexItem.StartDate = item.StartDate.Format(utils.FormatDate)
+		indexItem.EndDate = item.EndDate.Format(utils.FormatDate)
+		indexItem.LatestValue = fmt.Sprint(item.LatestValue)
+		indexItem.Source = utils.DATA_SOURCE_RADISH_RESEARCH
+		indexItem.SourceName = utils.DATA_SOURCE_NAME_RADISH_RESEARCH
+		indexItem.IsDeleted = 1 // 标记已删除
+		indexItem.CreateTime = item.CreateTime.Format(utils.FormatDateTime)
+		indexItem.ModifyTime = item.ModifyTime.Format(utils.FormatDateTime)
+
+		docId := fmt.Sprintf("%d-%d", utils.DATA_SOURCE_RADISH_RESEARCH, item.BaseFromRadishResearchIndexId)
+		if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+			utils.FileLog.Warning("RadishResearch-写入指标ES失败, %v", e)
+			return
+		}
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// IndexExport
+// @Title 导出指标数据
+// @Description 导出指标数据
+// @Param   ClassifyId  query  int  false  "分类Id"
+// @Success 200  导出成功
+// @router /radish_research/index/export [get]
+func (this *BaseFromRadishResearchController) IndexExport() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	classifyId, _ := this.GetInt("ClassifyId", -1)
+
+	var (
+		cond        string
+		pars        []interface{}
+		classifyIds []int
+	)
+	// 未分类
+	indexOb := new(data_manage.BaseFromRadishResearchIndex)
+	if classifyId == 0 {
+		cond += fmt.Sprintf(` AND %s = ?`, indexOb.Cols().ClassifyId)
+		pars = append(pars, classifyId)
+	}
+	// 包含所有子分类的指标
+	if classifyId > 0 {
+		classifyOb := new(data_manage.BaseFromRadishResearchClassify)
+		classifies, e := classifyOb.GetItemsByCondition(fmt.Sprintf(" AND FIND_IN_SET(%d, %s)", classifyId, classifyOb.Cols().LevelPath), make([]interface{}, 0), []string{classifyOb.Cols().PrimaryId}, fmt.Sprintf("%s ASC, %s ASC", classifyOb.Cols().ParentId, classifyOb.Cols().Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取子分类IDs失败, %v", e)
+			return
+		}
+		for _, v := range classifies {
+			if v.BaseFromRadishResearchClassifyId <= 0 {
+				continue
+			}
+			classifyIds = append(classifyIds, v.BaseFromRadishResearchClassifyId)
+		}
+		if len(classifyIds) > 0 {
+			cond += fmt.Sprintf(` AND %s IN ?`, indexOb.Cols().ClassifyId)
+			pars = append(pars, classifyIds)
+		}
+	}
+
+	dir, _ := os.Executable()
+	exPath := filepath.Dir(dir)
+	downFile := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	xlsxFile := xlsx.NewFile()
+
+	// 获取指标数据
+	indexes, e := indexOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC, %s ASC", indexOb.Cols().Sort, indexOb.Cols().PrimaryId))
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取指标失败, %v", e)
+		return
+	}
+	if len(indexes) == 0 {
+		// 无数据返回空文件
+		if e := xlsxFile.Save(downFile); e != nil {
+			sheet, e := xlsxFile.AddSheet("无数据")
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("新增Sheet失败, %v", e)
+				return
+			}
+			rowSecName := sheet.AddRow()
+			celSecName := rowSecName.AddCell()
+			celSecName.SetValue("")
+			if e = xlsxFile.Save(downFile); e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("保存文件失败, %v", e)
+				return
+			}
+		}
+		fileName := fmt.Sprintf("%s%s%s", utils.DATA_SOURCE_NAME_RADISH_RESEARCH, time.Now().Format("06.01.02"), ".xlsx")
+		this.Ctx.Output.Download(downFile, fileName)
+		defer func() {
+			_ = os.Remove(downFile)
+		}()
+		return
+	}
+
+	// 划分指标频度
+	frequencyIndex := make(map[string][]*data_manage.BaseFromRadishResearchIndex)
+	frequencyIndexIds := make(map[string][]int)
+	for _, v := range indexes {
+		if frequencyIndex[v.Frequency] == nil {
+			frequencyIndex[v.Frequency] = make([]*data_manage.BaseFromRadishResearchIndex, 0)
+		}
+		if frequencyIndexIds[v.Frequency] == nil {
+			frequencyIndexIds[v.Frequency] = make([]int, 0)
+		}
+		frequencyIndexIds[v.Frequency] = append(frequencyIndexIds[v.Frequency], v.BaseFromRadishResearchIndexId)
+		frequencyIndex[v.Frequency] = append(frequencyIndex[v.Frequency], v)
+	}
+
+	frequencyArr := []string{"日度", "周度", "旬度", "月度", "季度", "半年度", "年度"}
+	//frequencyMap := map[string]string{
+	//	"日度":  "Daily",
+	//	"周度":  "Weekly",
+	//	"旬度":  "ten-day",
+	//	"月度":  "Monthly",
+	//	"季度":  "Quarterly",
+	//	"半年度": "Semi-annual",
+	//	"年度":  "Annual",
+	//}
+	dataOb := new(data_manage.BaseFromRadishResearchData)
+	for _, frequency := range frequencyArr {
+		// 获取对应频度指标
+		secNameList := frequencyIndex[frequency]
+		if len(secNameList) == 0 {
+			continue
+		}
+
+		//sheetName := fmt.Sprintf("%s(%s)", frequency, frequencyMap[frequency])
+		sheetNew, e := xlsxFile.AddSheet(frequency)
+		if e != nil {
+			utils.FileLog.Warning(fmt.Sprintf("萝卜投研导出-AddSheet err: %v", e))
+			continue
+		}
+		secNameRow := sheetNew.AddRow()
+		frequencyRow := sheetNew.AddRow()
+		unitRow := sheetNew.AddRow()
+		updateTimeRow := sheetNew.AddRow()
+
+		// 指标日期序列
+		indexIds := frequencyIndexIds[frequency]
+		dataTimeList, e := dataOb.GetDataTimeByIndexIds(indexIds)
+		if e != nil {
+			utils.FileLog.Warning(fmt.Sprintf("萝卜投研导出-GetDataTimeByIndexIds err: %v", e))
+			continue
+		}
+
+		// 添加excel左侧指标日期
+		setRowIndex := 4
+		for rk, dv := range dataTimeList {
+			rowIndex := setRowIndex + rk
+			row := sheetNew.Row(rowIndex)
+			displayDate, _ := time.Parse(utils.FormatDate, dv)
+			displayDateCell := row.AddCell()
+			style := new(xlsx.Style)
+			style.ApplyAlignment = true
+			style.Alignment.WrapText = true
+			displayDateCell.SetStyle(style)
+			displayDateCell.SetDate(displayDate)
+		}
+		for k, sv := range secNameList {
+			// 获取数据
+			dataCond := fmt.Sprintf(` AND %s = ?`, dataOb.Cols().IndexCode)
+			dataPars := make([]interface{}, 0)
+			dataPars = append(dataPars, sv.IndexCode)
+			dataList, e := dataOb.GetItemsByCondition(dataCond, dataPars, []string{}, fmt.Sprintf("%s DESC", dataOb.Cols().DataTime))
+			if e != nil {
+				utils.FileLog.Warning(fmt.Sprintf("萝卜投研导出-GetIndexDataByCondition err: %v", e))
+				continue
+			}
+
+			if k == 0 {
+				secNameRow.AddCell().SetValue("指标名称")
+				frequencyRow.AddCell().SetValue("频度")
+				unitRow.AddCell().SetValue("单位")
+				updateTimeRow.AddCell().SetValue("更新时间")
+				minCol := k * 3
+				sheetNew.SetColWidth(minCol, minCol, 15)
+			}
+			if len(dataList) == 0 {
+				continue
+			}
+			secNameRow.AddCell().SetValue(sv.IndexName)
+			frequencyRow.AddCell().SetValue(sv.Frequency)
+			unitRow.AddCell().SetValue(sv.Unit)
+
+			updateTimeRow.AddCell().SetValue(sv.ModifyTime)
+			dataInfoMap := make(map[string]*data_manage.BaseFromRadishResearchData)
+			for _, v := range dataList {
+				dt := v.DataTime.Format(utils.FormatDate)
+				dataInfoMap[dt] = v
+			}
+
+			for rk, dtv := range dataTimeList {
+				rowIndex := setRowIndex + rk
+				row := sheetNew.Row(rowIndex)
+				displayDateCell := row.AddCell()
+				tmpData, ok := dataInfoMap[dtv]
+				if ok {
+					displayDateCell.SetValue(tmpData.Value)
+				}
+			}
+		}
+	}
+
+	// 保存文件出错返回空文件
+	if e := xlsxFile.Save(downFile); e != nil {
+		sheet, e := xlsxFile.AddSheet("无数据")
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("新增Sheet失败, %v", e)
+			return
+		}
+		rowSecName := sheet.AddRow()
+		celSecName := rowSecName.AddCell()
+		celSecName.SetValue("")
+		if e = xlsxFile.Save(downFile); e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("保存文件失败, %v", e)
+			return
+		}
+	}
+	fileName := fmt.Sprintf("%s%s%s", utils.DATA_SOURCE_NAME_RADISH_RESEARCH, time.Now().Format("06.01.02"), ".xlsx")
+	this.Ctx.Output.Download(downFile, fileName)
+	defer func() {
+		_ = os.Remove(downFile)
+	}()
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "success"
+
+}
+
+// IndexSelect
+// @Title 批量加入指标库-选择指标
+// @Description 批量加入指标库-选择指标
+// @Success 200 {object} data_manage.BaseFromRadishResearchIndexItem
+// @router /radish_research/index/select [post]
+func (this *BaseFromRadishResearchController) IndexSelect() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.RadishResearchIndexSelectReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	resp := make([]*data_manage.BaseFromRadishResearchIndexItem, 0)
+
+	indexOb := new(data_manage.BaseFromRadishResearchIndex)
+	var (
+		cond string
+		pars []interface{}
+	)
+	// 忽略已加入指标库的
+	cond += fmt.Sprintf(` AND %s = 0`, indexOb.Cols().EdbExist)
+	if len(req.ClassifyIds) > 0 {
+		cond += fmt.Sprintf(` AND %s IN ?`, indexOb.Cols().ClassifyId)
+		pars = append(pars, req.ClassifyIds)
+	}
+	if len(req.Frequencies) > 0 {
+		cond += fmt.Sprintf(` AND %s IN ?`, indexOb.Cols().Frequency)
+		pars = append(pars, req.Frequencies)
+	}
+	req.Keyword = strings.TrimSpace(req.Keyword)
+	if req.Keyword != "" {
+		kw := fmt.Sprint("%", req.Keyword, "%")
+		cond += fmt.Sprintf(` AND (%s LIKE ? OR %s LIKE ?)`, indexOb.Cols().IndexName, indexOb.Cols().IndexCode)
+		pars = append(pars, kw, kw)
+	}
+	// 列表全选-SelectAll-true: IndexCodes为排除的指标, SelectAll-false: IndexCodes为选择的指标
+	if len(req.IndexCodes) > 0 {
+		if req.SelectAll {
+			cond += fmt.Sprintf(` AND %s NOT IN ?`, indexOb.Cols().IndexCode)
+		} else {
+			cond += fmt.Sprintf(` AND %s IN ?`, indexOb.Cols().IndexCode)
+		}
+		pars = append(pars, req.IndexCodes)
+	}
+
+	items, e := indexOb.GetItemsByCondition(cond, pars, []string{}, "")
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取指标列表失败, %v", e)
+		return
+	}
+	if len(items) > 30 {
+		br.Msg = "批量添加指标数量不得超过30个"
+		return
+	}
+	for _, v := range items {
+		t := v.Format2Item()
+		resp = append(resp, t)
+	}
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// EdbAdd
+// @Title 加入指标库
+// @Description 加入指标库
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /radish_research/edb/add [post]
+func (this *BaseFromRadishResearchController) EdbAdd() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	deleteCache := true
+	cacheKey := fmt.Sprintf("CACHE_EDB_INFO_ADD_%d_%d", utils.DATA_SOURCE_RADISH_RESEARCH, sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			_ = utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试"
+		return
+	}
+	var req data_manage.AddEdbInfoReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析失败, %v", e)
+		return
+	}
+	req.EdbCode = strings.TrimSpace(req.EdbCode)
+	req.EdbName = strings.TrimSpace(req.EdbName)
+	if req.EdbCode == "" {
+		br.Msg = "指标编码不能为空"
+		return
+	}
+	if req.EdbName == "" {
+		br.Msg = "指标名称不能为空"
+		return
+	}
+	if req.Frequency == "" {
+		br.Msg = "频率不能为空"
+		return
+	}
+	if req.Unit == "" {
+		br.Msg = "单位不能为空"
+		return
+	}
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+
+	// 是否加入过指标库
+	exist, e := data_manage.GetEdbInfoByEdbCode(utils.DATA_SOURCE_RADISH_RESEARCH, req.EdbCode)
+	if e != nil && !utils.IsErrNoRow(e) {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("校验是否加入过指标库失败, %v", e)
+		return
+	}
+	if exist != nil && exist.EdbInfoId > 0 {
+		br.Msg = "指标库已存在,请刷新页面"
+		return
+	}
+
+	// 指标入库
+	edbInfo, err, errMsg, isSendEmail := data.EdbInfoAdd(utils.DATA_SOURCE_RADISH_RESEARCH, utils.DATA_SUB_SOURCE_EDB, req.ClassifyId, req.EdbCode, req.EdbName, req.Frequency, req.Unit, req.StartDate, req.EndDate, sysUser.AdminId, sysUser.RealName, this.Lang)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	// 刷新指标数据
+	refreshRes, e := data.RefreshEdbData(edbInfo.EdbInfoId, edbInfo.Source, edbInfo.SubSource, edbInfo.EdbCode, "")
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("刷新指标数据失败, %v", e)
+		return
+	}
+	if refreshRes != nil && refreshRes.Ret != 200 {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("刷新指标数据失败, Ret: %d, Msg: %s, ErrMsg: %s", refreshRes.Ret, refreshRes.Msg, refreshRes.ErrMsg)
+		return
+	}
+
+	// 更新指标EdbExist
+	indexOb := new(data_manage.BaseFromRadishResearchIndex)
+	if e := indexOb.UpdateEdbExists(1, []string{req.EdbCode}); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("更新数据源EdbExist失败, %v", e)
+		return
+	}
+
+	//新增操作日志
+	{
+		edbLog := new(data_manage.EdbInfoLog)
+		edbLog.EdbInfoId = edbInfo.EdbInfoId
+		edbLog.SourceName = edbInfo.SourceName
+		edbLog.Source = edbInfo.Source
+		edbLog.EdbCode = edbInfo.EdbCode
+		edbLog.EdbName = edbInfo.EdbName
+		edbLog.ClassifyId = edbInfo.ClassifyId
+		edbLog.SysUserId = sysUser.AdminId
+		edbLog.SysUserRealName = sysUser.RealName
+		edbLog.CreateTime = time.Now()
+		edbLog.Content = string(this.Ctx.Input.RequestBody)
+		edbLog.Status = "新增指标"
+		edbLog.Method = this.Ctx.Input.URI()
+		go data_manage.AddEdbInfoLog(edbLog)
+	}
+
+	// 更新es
+	go data.AddOrEditEdbInfoToEs(edbInfo.EdbInfoId)
+
+	// 试用平台更新用户累计新增指标数
+	if utils.BusinessCode == utils.BusinessCodeSandbox {
+		go func() {
+			adminItem, e := system.GetSysAdminById(sysUser.AdminId)
+			if e != nil {
+				return
+			}
+			if adminItem != nil && adminItem.AdminId <= 0 {
+				return
+			}
+			if adminItem.DepartmentName != "ETA试用客户" {
+				return
+			}
+
+			var r etaTrialService.EtaTrialUserReq
+			r.Mobile = adminItem.Mobile
+			_, _ = etaTrialService.UpdateUserIndexNum(r)
+		}()
+	}
+
+	resp := new(data_manage.AddEdbInfoResp)
+	resp.EdbInfoId = edbInfo.EdbInfoId
+	resp.UniqueCode = edbInfo.UniqueCode
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// EdbNameCheck
+// @Title 批量加入指标库-重名校验
+// @Description 批量加入指标库-重名校验
+// @Success 200 {object} data_manage.EdbNameCheckResult
+// @router /radish_research/edb/name_check [post]
+func (this *BaseFromRadishResearchController) EdbNameCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req []*data_manage.NameCheckEdbInfoReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if len(req) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	codeMaxT := 30
+	codeLen := len(req)
+	if codeLen > codeMaxT {
+		br.Msg = "批量添加指标数量不得超过30个"
+		return
+	}
+
+	indexNames := make([]string, 0)
+	resp := make([]*data_manage.EdbNameCheckResult, 0)
+	nameCount := make(map[string]int)
+	for _, v := range req {
+		v.EdbCode = strings.TrimSpace(v.EdbCode)
+		if v.EdbCode == "" {
+			br.Msg = "指标ID不可为空"
+			return
+		}
+		v.EdbName = strings.TrimSpace(v.EdbName)
+		if v.EdbName == "" {
+			br.Msg = "请输入指标名称"
+			return
+		}
+		nameCount[v.EdbName] += 1
+
+		indexNames = append(indexNames, v.EdbName)
+		resp = append(resp, &data_manage.EdbNameCheckResult{
+			EdbCode: v.EdbCode,
+			EdbName: v.EdbName,
+		})
+	}
+	// 本次提交的名称中也不允许重复
+	for _, v := range resp {
+		if nameCount[v.EdbName] > 1 {
+			v.Exist = true
+		}
+	}
+
+	// 重名校验
+	edbList, e := data_manage.GetEdbInfoByNameArr(indexNames, utils.EDB_INFO_TYPE)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取重名指标失败, Err: " + e.Error()
+		return
+	}
+	nameExists := make(map[string]bool)
+	for _, v := range edbList {
+		nameExists[v.EdbName] = true
+	}
+	if len(nameExists) > 0 {
+		for _, v := range resp {
+			v.Exist = nameExists[v.EdbName]
+		}
+	}
+
+	br.Data = resp
+	br.Msg = "校验成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// EdbMultiAdd
+// @Title 批量加入指标库
+// @Description 批量加入指标库
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /radish_research/edb/multi_add [post]
+func (this *BaseFromRadishResearchController) EdbMultiAdd() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	deleteCache := true
+	cacheKey := fmt.Sprintf("CACHE_EDB_INFO_BATCH_ADD_%d_%d", utils.DATA_SOURCE_RADISH_RESEARCH, sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			_ = utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+	var req []*data_manage.AddEdbInfoReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if len(req) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	if len(req) > 30 {
+		br.Msg = "批量添加指标数量不得超过30个"
+		return
+	}
+	for _, v := range req {
+		v.EdbCode = strings.TrimSpace(v.EdbCode)
+		if v.EdbCode == "" {
+			br.Msg = "指标ID不可为空"
+			return
+		}
+		v.EdbName = strings.TrimSpace(v.EdbName)
+		if v.EdbName == "" {
+			br.Msg = "请输入指标名称"
+			return
+		}
+		v.Frequency = strings.TrimSpace(v.Frequency)
+		if v.Frequency == "" {
+			br.Msg = "请选择频度"
+			return
+		}
+		v.Unit = strings.TrimSpace(v.Unit)
+		if v.Unit == "" {
+			br.Msg = "请输入单位"
+			return
+		}
+		if v.ClassifyId <= 0 {
+			br.Msg = "请选择分类"
+			return
+		}
+	}
+
+	// 限定同一时间最多批量新增30个指标
+	var successCodes []string
+	for _, v := range req {
+		// 是否加入过指标库
+		exist, e := data_manage.GetEdbInfoByEdbCode(utils.DATA_SOURCE_RADISH_RESEARCH, v.EdbCode)
+		if e != nil && !utils.IsErrNoRow(e) {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("校验是否加入过指标库失败, %v", e)
+			return
+		}
+		if exist != nil && exist.EdbInfoId > 0 {
+			// 加入过指标库这里直接忽略掉
+			continue
+		}
+
+		// 指标入库
+		edbInfo, err, errMsg, isSendEmail := data.EdbInfoAdd(utils.DATA_SOURCE_RADISH_RESEARCH, utils.DATA_SUB_SOURCE_EDB, v.ClassifyId, v.EdbCode, v.EdbName, v.Frequency, v.Unit, v.StartDate, v.EndDate, sysUser.AdminId, sysUser.RealName, this.Lang)
+		if err != nil {
+			br.Msg = "保存失败"
+			if errMsg != `` {
+				br.Msg = errMsg
+			}
+			br.ErrMsg = err.Error()
+			br.IsSendEmail = isSendEmail
+			return
+		}
+		successCodes = append(successCodes, v.EdbCode)
+
+		// 刷新指标数据
+		refreshRes, e := data.RefreshEdbData(edbInfo.EdbInfoId, edbInfo.Source, edbInfo.SubSource, edbInfo.EdbCode, "")
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("刷新指标数据失败, %v", e)
+			return
+		}
+		if refreshRes != nil && refreshRes.Ret != 200 {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("刷新指标数据失败, Ret: %d, Msg: %s, ErrMsg: %s", refreshRes.Ret, refreshRes.Msg, refreshRes.ErrMsg)
+			return
+		}
+
+		// 试用平台更新用户累计新增指标数
+		if utils.BusinessCode == utils.BusinessCodeSandbox {
+			go func() {
+				adminItem, e := system.GetSysAdminById(sysUser.AdminId)
+				if e != nil {
+					tips := fmt.Sprintf("试用平台更新用户累计新增指标数-获取用户失败, Err: " + e.Error())
+					utils.FileLog.Info(tips)
+					return
+				}
+				if adminItem.DepartmentName != "ETA试用客户" {
+					return
+				}
+				var ur etaTrialService.EtaTrialUserReq
+				ur.Mobile = adminItem.Mobile
+				_, _ = etaTrialService.UpdateUserIndexNum(ur)
+			}()
+		}
+
+		// 新增操作日志
+		{
+			edbLog := new(data_manage.EdbInfoLog)
+			edbLog.EdbInfoId = edbInfo.EdbInfoId
+			edbLog.SourceName = edbInfo.SourceName
+			edbLog.Source = edbInfo.Source
+			edbLog.EdbCode = edbInfo.EdbCode
+			edbLog.EdbName = edbInfo.EdbName
+			edbLog.ClassifyId = edbInfo.ClassifyId
+			edbLog.SysUserId = sysUser.AdminId
+			edbLog.SysUserRealName = sysUser.RealName
+			edbLog.CreateTime = time.Now()
+			edbLog.Content = string(this.Ctx.Input.RequestBody)
+			edbLog.Status = "新增指标"
+			edbLog.Method = this.Ctx.Input.URI()
+			go data_manage.AddEdbInfoLog(edbLog)
+		}
+
+		// 更新es
+		go data.AddOrEditEdbInfoToEs(edbInfo.EdbInfoId)
+	}
+
+	// 更新指标EdbExist
+	if len(successCodes) > 0 {
+		indexOb := new(data_manage.BaseFromRadishResearchIndex)
+		if e := indexOb.UpdateEdbExists(1, successCodes); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("更新数据源EdbExist失败, %v", e)
+			return
+		}
+	}
+
+	br.Msg = "操作成功"
+	br.Ret = 200
+	br.Success = true
+	br.IsAddLog = true
+}

+ 518 - 0
controllers/data_manage/base_from_radish_research_classify.go

@@ -0,0 +1,518 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// ClassifyList
+// @Title 分类列表-含指标
+// @Description 分类列表-含指标
+// @Param   ParentId  query  int  false  "父级ID"
+// @Success 200 {object} data_manage.RadishResearchSearchEdbResp
+// @router /radish_research/classify/list [get]
+func (this *BaseFromRadishResearchController) ClassifyList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	parentId, _ := this.GetInt("ParentId", -1)
+
+	resp := make([]*data_manage.BaseFromRadishResearchClassifyListItem, 0)
+
+	// 默认查询未分类及一级分类
+	var classifyId int
+	if parentId == -1 {
+		resp = append(resp, &data_manage.BaseFromRadishResearchClassifyListItem{
+			ClassifyName: "未分类",
+			Level:        1,
+			Sort:         -100,
+			UniqueCode:   "unclassified",
+		})
+	}
+	// 查询未分类下的指标
+	if parentId == 0 {
+		classifyId = -1
+	}
+	if parentId > 0 {
+		classifyId = parentId
+	}
+
+	// 查询分类
+	classifyOb := new(data_manage.BaseFromRadishResearchClassify)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ParentId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, classifyId)
+		list, e := classifyOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", classifyOb.Cols().Sort))
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取子分类失败, %v", e)
+			return
+		}
+		for _, v := range list {
+			resp = append(resp, &data_manage.BaseFromRadishResearchClassifyListItem{
+				ClassifyId:   v.BaseFromRadishResearchClassifyId,
+				ClassifyName: v.ClassifyName,
+				ParentId:     v.ParentId,
+				Level:        v.Level,
+				Sort:         v.Sort,
+				UniqueCode:   v.UniqueCode,
+			})
+		}
+	}
+
+	// 查询指标
+	if parentId > -1 {
+		indexOb := new(data_manage.BaseFromRadishResearchIndex)
+		{
+			cond := fmt.Sprintf(" AND %s = ?", indexOb.Cols().ClassifyId)
+			pars := make([]interface{}, 0)
+			pars = append(pars, parentId)
+			list, e := indexOb.GetItemsByCondition(cond, pars, []string{}, fmt.Sprintf("%s ASC", indexOb.Cols().Sort))
+			if e != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = fmt.Sprintf("获取分类下指标失败, %v", e)
+				return
+			}
+			for _, v := range list {
+				resp = append(resp, &data_manage.BaseFromRadishResearchClassifyListItem{
+					NodeType:   1,
+					IndexId:    v.BaseFromRadishResearchIndexId,
+					IndexCode:  v.IndexCode,
+					IndexName:  v.IndexName,
+					ParentId:   parentId,
+					Sort:       v.Sort,
+					UniqueCode: v.IndexCode,
+				})
+			}
+		}
+	}
+	sort.Slice(resp, func(i, j int) bool {
+		return resp[i].Sort < resp[j].Sort
+	})
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// ClassifyTree
+// @Title 分类树
+// @Description 分类树
+// @Success 200 {object} data_manage.RadishResearchSearchEdbResp
+// @router /radish_research/classify/tree [get]
+func (this *BaseFromRadishResearchController) ClassifyTree() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	// 获取所有分类
+	classifyOb := new(data_manage.BaseFromRadishResearchClassify)
+	list, e := classifyOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, fmt.Sprintf("%s ASC, %s ASC", classifyOb.Cols().ParentId, classifyOb.Cols().Sort))
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取分类列表失败, %v", e)
+		return
+	}
+	items := make([]*data_manage.BaseFromRadishResearchClassifyItem, 0)
+	for _, v := range list {
+		items = append(items, v.Format2Item())
+	}
+	tree := data.GetRadishResearchClassifyTreeRecursive(items, 0)
+
+	// 未分类置顶
+	resp := make([]*data_manage.BaseFromRadishResearchClassifyItem, 0)
+	resp = append(resp, &data_manage.BaseFromRadishResearchClassifyItem{
+		ClassifyName: "未分类",
+		Level:        1,
+		Sort:         -100,
+		UniqueCode:   "unclassified",
+	})
+	resp = append(resp, tree...)
+
+	br.Data = resp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
+// ClassifyAdd
+// @Title 新增分类
+// @Description 新增分类
+// @Param	request	body data_manage.RadishResearchClassifyAddReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /radish_research/classify/add [post]
+func (this *BaseFromRadishResearchController) ClassifyAdd() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.RadishResearchClassifyAddReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	req.ClassifyName = strings.TrimSpace(req.ClassifyName)
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		return
+	}
+	if req.ParentId < 0 {
+		br.Msg = "请选择上级分类"
+		return
+	}
+	if req.Level > 6 {
+		br.Msg = "目前只支持6级目录"
+		return
+	}
+
+	// 校验分类名称
+	classifyOb := new(data_manage.BaseFromRadishResearchClassify)
+	{
+		cond := fmt.Sprintf(" AND %s = ? AND %s = ?", classifyOb.Cols().ParentId, classifyOb.Cols().ClassifyName)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ParentId, req.ClassifyName)
+		count, e := classifyOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取分类名称重复数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "分类名称已存在"
+			return
+		}
+	}
+
+	// 层级路径
+	var levelPath string
+	var rootId int
+	if req.ParentId > 0 {
+		parent, e := classifyOb.GetItemById(req.ParentId)
+		if e != nil {
+			br.Msg = "上级分类有误"
+			br.ErrMsg = fmt.Sprintf("获取上级分类失败, %v", e)
+			return
+		}
+		levelPath = parent.LevelPath
+		rootId = parent.RootId
+	}
+
+	sortMax, e := classifyOb.GetSortMax(req.ParentId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取分类最大排序失败, %v", e)
+		return
+	}
+	timestamp := strconv.FormatInt(time.Now().UnixNano(), 10)
+	classifyOb.ParentId = req.ParentId
+	classifyOb.ClassifyName = req.ClassifyName
+	classifyOb.Level = req.Level + 1
+	classifyOb.Sort = sortMax + 1
+	classifyOb.SysUserId = sysUser.AdminId
+	classifyOb.SysUserRealName = sysUser.RealName
+	classifyOb.UniqueCode = utils.MD5(classifyOb.TableName() + "_" + timestamp)
+	classifyOb.CreateTime = time.Now().Local()
+	classifyOb.ModifyTime = time.Now().Local()
+	if e = classifyOb.Create(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("新增分类失败, %v", e)
+		return
+	}
+	if req.ParentId > 0 {
+		// 用英文逗号拼接方便查询
+		classifyOb.LevelPath = fmt.Sprintf("%s,%d", levelPath, classifyOb.BaseFromRadishResearchClassifyId)
+		classifyOb.RootId = rootId
+	} else {
+		classifyOb.LevelPath = fmt.Sprint(classifyOb.BaseFromRadishResearchClassifyId)
+		classifyOb.RootId = classifyOb.BaseFromRadishResearchClassifyId
+	}
+	if e = classifyOb.Update([]string{classifyOb.Cols().LevelPath, classifyOb.Cols().RootId}); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("更新分类失败, %v", e)
+		return
+	}
+
+	br.Data = classifyOb.BaseFromRadishResearchClassifyId
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// ClassifyEdit
+// @Title 编辑分类
+// @Description 编辑分类
+// @Param	request	body data_manage.RadishResearchClassifyEditReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /radish_research/classify/edit [post]
+func (this *BaseFromRadishResearchController) ClassifyEdit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.RadishResearchClassifyEditReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId < 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+	req.ClassifyName = strings.TrimSpace(req.ClassifyName)
+	if req.ClassifyName == "" {
+		br.Msg = "请输入分类名称"
+		return
+	}
+
+	classifyOb := new(data_manage.BaseFromRadishResearchClassify)
+	classifyItem, e := classifyOb.GetItemById(req.ClassifyId)
+	if e != nil {
+		if utils.IsErrNoRow(e) {
+			br.Msg = "分类不存在, 请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取分类失败, %v", e)
+		return
+	}
+	if classifyItem != nil && classifyItem.BaseFromRadishResearchClassifyId <= 0 {
+		br.Msg = "分类不存在, 请刷新页面"
+		return
+	}
+
+	// 校验分类名称
+	{
+		cond := fmt.Sprintf(" AND %s <> ? AND %s = ?", classifyOb.Cols().PrimaryId, classifyOb.Cols().ClassifyName)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ClassifyId, req.ClassifyName)
+		count, e := classifyOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取分类名称重复数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "分类名称已存在"
+			return
+		}
+	}
+	classifyItem.ClassifyName = req.ClassifyName
+	classifyItem.ModifyTime = time.Now().Local()
+	updateCols := []string{classifyOb.Cols().ClassifyName, classifyOb.Cols().ModifyTime}
+	if e = classifyItem.Update(updateCols); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("更新分类失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// ClassifyRemove
+// @Title 删除分类
+// @Description 删除分类
+// @Param	request	body data_manage.RadishResearchClassifyRemoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /radish_research/classify/remove [post]
+func (this *BaseFromRadishResearchController) ClassifyRemove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.RadishResearchClassifyRemoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId < 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+
+	classifyOb := new(data_manage.BaseFromRadishResearchClassify)
+	classifyItem, e := classifyOb.GetItemById(req.ClassifyId)
+	if e != nil {
+		if utils.IsErrNoRow(e) {
+			br.Ret = 200
+			br.Success = true
+			br.Msg = "操作成功"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取分类失败, %v", e)
+		return
+	}
+	if classifyItem != nil && classifyItem.BaseFromRadishResearchClassifyId <= 0 {
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "操作成功"
+		return
+	}
+
+	// 查询子分类
+	{
+		cond := fmt.Sprintf(" AND %s = ?", classifyOb.Cols().ParentId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ClassifyId)
+		count, e := classifyOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取子分类数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "该分类下含有子分类, 不允许删除"
+			return
+		}
+	}
+
+	// 查询指标
+	indexOb := new(data_manage.BaseFromRadishResearchIndex)
+	{
+		cond := fmt.Sprintf(" AND %s = ?", indexOb.Cols().ClassifyId)
+		pars := make([]interface{}, 0)
+		pars = append(pars, req.ClassifyId)
+		count, e := indexOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("获取分类下指标数失败, %v", e)
+			return
+		}
+		if count > 0 {
+			br.Msg = "该分类下含有指标, 不允许删除"
+			return
+		}
+	}
+
+	if e := classifyItem.Remove(); e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("删除分类失败, %v", e)
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}
+
+// ClassifyMove
+// @Title 移动分类
+// @Description 移动分类
+// @Param	request	body data_manage.BaseFromRadishResearchClassifyMoveReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /radish_research/classify/move [post]
+func (this *BaseFromRadishResearchController) ClassifyMove() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.BaseFromRadishResearchClassifyMoveReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常"
+		br.ErrMsg = fmt.Sprintf("参数解析异常, %v", e)
+		return
+	}
+	if req.ClassifyId <= 0 && req.ItemId <= 0 {
+		br.Msg = "请选择分类或指标"
+		return
+	}
+
+	err, errMsg := data.RadishResearchMoveClassify(req, sysUser)
+	if errMsg != `` {
+		br.Msg = errMsg
+		br.ErrMsg = errMsg
+		if err != nil {
+			br.ErrMsg = err.Error()
+		} else {
+			br.IsSendEmail = false
+		}
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "操作成功"
+}

+ 60 - 0
controllers/data_manage/chart_common.go

@@ -11,6 +11,7 @@ import (
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage"
 	"eta/eta_api/models/system"
+	"eta/eta_api/services"
 	"eta/eta_api/services/alarm_msg"
 	"eta/eta_api/services/data"
 	"eta/eta_api/services/data/excel"
@@ -351,3 +352,62 @@ func getBalanceChartInfoDetailFromUniqueCode(chartInfo *data_manage.ChartInfoVie
 
 	return
 }
+
+// GeneralChartToken
+// @Title 根据图表唯一code生成token
+// @Description 根据编码获取图表详情接口
+// @Param   UniqueCode   query   string  true       "图表/表格唯一编码"
+// @Param   Source   query   string  true       "来源,枚举值:chart、table"
+// @Success 200 {object} data_manage.ChartInfoDetailFromUniqueCodeResp
+// @router /chart_info/common/general_token [get]
+func (this *ChartInfoController) GeneralChartToken() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	uniqueCode := this.GetString("UniqueCode")
+	if uniqueCode == "" {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,uniqueCode is empty"
+		return
+	}
+	source := this.GetString("Source", "chart")
+
+	businessConf, err := models.GetBusinessConfByKey(models.BusinessConfIsOpenChartExpired)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取配置失败,Err:" + err.Error()
+		return
+	}
+
+	var token string
+	if businessConf.ConfVal == `true` {
+		// 缓存key
+		sourceType := source
+		if source == `table` {
+			sourceType = source
+		}
+		token, err = services.GeneralChartToken(sourceType, uniqueCode)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败"
+			return
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = token
+
+	return
+}

+ 2 - 2
controllers/data_manage/cross_variety/tag.go

@@ -50,7 +50,7 @@ func (c *TagController) Add() {
 		br.ErrMsg = "添加失败,Err:" + err.Error()
 		return
 	}
-	if item != nil {
+	if item != nil && item.ChartTagId > 0 {
 		br.Msg = "添加失败,标签名称不能重复"
 		br.IsSendEmail = false
 		return
@@ -115,7 +115,7 @@ func (c *TagController) Edit() {
 		br.ErrMsg = "添加失败,Err:" + err.Error()
 		return
 	}
-	if item != nil && item.ChartTagId != req.ChartTagId {
+	if item != nil && item.ChartTagId != req.ChartTagId && item.ChartTagId > 0 {
 		br.Msg = "添加失败,标签名称不能重复"
 		br.IsSendEmail = false
 		return

+ 2 - 2
controllers/data_manage/cross_variety/variety.go

@@ -49,7 +49,7 @@ func (c *VarietyController) Add() {
 		br.ErrMsg = "添加失败,Err:" + err.Error()
 		return
 	}
-	if item != nil {
+	if item != nil && item.ChartVarietyId > 0 {
 		br.Msg = "添加失败,品种名称不能重复"
 		br.IsSendEmail = false
 		return
@@ -114,7 +114,7 @@ func (c *VarietyController) Edit() {
 		br.ErrMsg = "添加失败,Err:" + err.Error()
 		return
 	}
-	if item != nil && item.ChartVarietyId != req.ChartVarietyId {
+	if item != nil && item.ChartVarietyId != req.ChartVarietyId && item.ChartVarietyId > 0 {
 		br.Msg = "添加失败,品种名称不能重复"
 		br.IsSendEmail = false
 		return

+ 1 - 1
controllers/data_manage/edb_info.go

@@ -2123,7 +2123,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 
 				//edb指标信息
 				edbInfoItem, err := data_manage.GetEdbInfoByEdbCode(source, edbCode)
-				if edbInfoItem != nil {
+				if edbInfoItem != nil && edbInfoItem.EdbInfoId > 0 {
 					searchItem.EdbName = edbInfoItem.EdbName
 				}
 			}

+ 1 - 1
controllers/data_manage/edb_info_refresh.go

@@ -368,7 +368,7 @@ func (c *EdbInfoController) GetEdbRefreshDefaultConfig() {
 	}
 
 	// 非有色的来源,频度不能为空
-	if source != utils.DATA_SOURCE_YS && frequency == `` {
+	if source != utils.DATA_SOURCE_YS && source != utils.DATA_SOURCE_RADISH_RESEARCH && frequency == `` {
 		br.Msg = "频度不能为空"
 		br.IsSendEmail = false
 		return

+ 1045 - 0
controllers/data_manage/purang_data.go

@@ -0,0 +1,1045 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/data"
+	etaTrialService "eta/eta_api/services/eta_trial"
+	"eta/eta_api/utils"
+	"fmt"
+	"os"
+	"path/filepath"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"github.com/tealeg/xlsx"
+)
+
+type BaseFromPurangController struct {
+	controllers.BaseAuthController
+}
+
+// PurangClassify
+// @Title Purang数据分类
+// @Description Purang数据分类接口
+// @Success 200 {object} data_manage.BaseFromPurangClassify
+// @router /purang/classify [get]
+func (this *BaseFromPurangController) PurangClassify() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	classifyAll, err := data_manage.GetAllBaseFromPurangClassify()
+	if err != nil && !utils.IsErrNoRow(err) {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	//组装一级分类
+	rootMap := make(map[int][]*data_manage.BaseFromPurangClassifyItems)
+	list := make([]*data_manage.BaseFromPurangClassifyItems, 0)
+	for _, classify := range classifyAll {
+		classify.UniqueCode = strconv.Itoa(classify.ClassifyId)
+		if classify.ParentId == 0 {
+			if _, ok := rootMap[classify.ClassifyId]; !ok {
+				rootMap[classify.ClassifyId] = make([]*data_manage.BaseFromPurangClassifyItems, 0)
+				list = append(list, classify)
+
+			}
+		} else {
+			child, ok := rootMap[classify.ParentId]
+			if ok {
+				child = append(child, classify)
+				rootMap[classify.ParentId] = child
+			}
+		}
+	}
+
+	for k, v := range list {
+		child, ok := rootMap[v.ClassifyId]
+		if ok {
+			list[k].Children = child
+		}
+	}
+	//组装二级分类
+	var ret data_manage.BaseFromPurangClassifyResp
+	ret.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = ret
+}
+
+// PurangIndexData
+// @Title 获取Purang数据
+// @Description 获取Purang数据接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   ClassifyId   query   string  true       "分类id"
+// @Success 200 {object} data_manage.LzFrequency
+// @router /purang/index/data [get]
+func (this *BaseFromPurangController) PurangIndexData() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+	var startSize int
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	classifyId, _ := this.GetInt("ClassifyId")
+	if classifyId < 0 {
+		br.Msg = "请选择分类"
+		br.ErrMsg = "请选择分类"
+		return
+	}
+	// 增加频度请求入参
+	frequency := this.GetString("Frequency")
+
+	//获取指标
+	var condition string
+	var pars []interface{}
+
+	if classifyId > 0 {
+		condition += ` AND classify_id=? `
+		pars = append(pars, classifyId)
+	}
+	if frequency != "" {
+		condition += ` AND frequency=? `
+		pars = append(pars, frequency)
+	}
+
+	purangList, err := data_manage.GetPurangIndex(condition, pars)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+
+	edbCodeList := make([]string, 0)
+	for _, v := range purangList {
+		edbCodeList = append(edbCodeList, v.IndexCode)
+	}
+	edbInfoMap := make(map[string]*data_manage.EdbInfo)
+	dataMap := make(map[string][]*data_manage.BaseFromPurangData)
+	total := 0
+	if len(edbCodeList) > 0 {
+		edbInfoList, err := data_manage.GetEdbInfoByEdbCodeList(utils.DATA_SOURCE_PURANG, edbCodeList)
+		if err != nil {
+			br.Msg = "获取数据源失败"
+			br.ErrMsg = "获取指标数据失败,Err:" + err.Error()
+			return
+		}
+		for _, v := range edbInfoList {
+			edbInfoMap[v.EdbCode] = v
+		}
+		// 首先对分类下的指标按照日期进行分页,再针对日期,进行排序
+		dataTimes, err := data_manage.GetPurangIndexDataTimePageByCodes(edbCodeList, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取指标数据日期信息失败,Err:" + err.Error()
+			return
+		}
+		if len(dataTimes) > 0 {
+			startDate := utils.GormDateStrToDateStr(dataTimes[len(dataTimes)-1])
+			endDate := utils.GormDateStrToDateStr(dataTimes[0])
+			// 把截止日往后加1天
+			endDateT, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+			endDate = endDateT.AddDate(0, 0, 1).Format(utils.FormatDate)
+			dataList, e := data_manage.GetPurangIndexDataByDataTime(edbCodeList, startDate, endDate)
+			if e != nil {
+				br.Msg = "获取数据失败"
+				br.ErrMsg = "获取指标数据失败,Err:" + e.Error()
+				return
+			}
+			//将数据按照指标进行分类
+			for _, v := range dataList {
+				dataMap[v.IndexCode] = append(dataMap[v.IndexCode], v)
+			}
+		}
+
+		total, err = data_manage.GetPurangIndexDataTimePageCount(edbCodeList)
+		if err != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取指标数据失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+
+	resultList := make([]*data_manage.BaseFromPurangIndexList, 0)
+
+	for _, v := range purangList {
+		product := new(data_manage.BaseFromPurangIndexList)
+		product.BaseFromPurangIndexId = v.BaseFromPurangIndexId
+		product.Unit = v.Unit
+		product.IndexCode = v.IndexCode
+		product.IndexName = v.IndexName
+		product.Frequency = v.Frequency
+		product.ModifyTime = v.ModifyTime
+		product.ClassifyId = v.ClassifyId
+		if edb, ok := edbInfoMap[v.IndexCode]; ok {
+			product.EdbInfoId = edb.EdbInfoId
+			product.EdbExist = 1
+		}
+
+		dataListTmp, ok := dataMap[v.IndexCode]
+		if !ok {
+			dataListTmp = make([]*data_manage.BaseFromPurangData, 0)
+		}
+		product.DataList = dataListTmp
+		product.Paging = page
+		resultList = append(resultList, product)
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resultList
+}
+
+// PurangSearchList
+// @Title Purang模糊搜索
+// @Description Purang模糊搜索
+// @Param   Keyword   query   string  ture       "关键字搜索"
+// @Success 200 {object} models.BaseResponse
+// @router /purang/search_list [get]
+func (this *BaseFromPurangController) PurangSearchList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请重新登录"
+		return
+	}
+
+	list := make([]*data_manage.BaseFromPurangIndexSearchItem, 0)
+	var err error
+	//关键字
+	keyword := this.GetString("Keyword")
+	if keyword != "" {
+		keyWordArr := strings.Split(keyword, " ")
+
+		if len(keyWordArr) > 0 {
+			condition := ""
+			for _, v := range keyWordArr {
+				condition += ` AND CONCAT(index_name,index_code) LIKE '%` + v + `%'`
+			}
+			list, err = data_manage.GetPurangItemList(condition)
+			if err != nil {
+				br.ErrMsg = "获取失败,Err:" + err.Error()
+				br.Msg = "获取失败"
+				return
+			}
+		}
+
+	} else {
+		list, err = data_manage.GetPurangItemList("")
+		if err != nil {
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			br.Msg = "获取失败"
+			return
+		}
+	}
+	classifyIds := make([]int, 0)
+	for _, v := range list {
+		classifyIds = append(classifyIds, v.ClassifyId)
+	}
+	classifyList, err := data_manage.GetBaseFromPurangClassifyByIds(classifyIds)
+	if err != nil {
+		br.Msg = "搜索失败"
+		br.ErrMsg = "获取分类信息失败,Err:" + err.Error()
+		return
+	}
+	classifyMap := make(map[int]int)
+	for _, v := range classifyList {
+		classifyMap[v.ClassifyId] = v.ParentId
+	}
+	for _, v := range list {
+		v.ParentClassifyId = classifyMap[v.ClassifyId]
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// PurangSingleData
+// @Title 获取Purang数据
+// @Description 获取Purang单条数据接口
+// @Param   IndexCode   query   string  true       "指标唯一编码"
+// @Success 200 {object} models.BaseResponse
+// @router /purang/single_data [get]
+func (this *BaseFromPurangController) PurangSingleData() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	indexCode := this.GetString("IndexCode")
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
+
+	var total int64
+	page := paging.GetPaging(currentIndex, pageSize, int(total))
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = paging.StartIndex(currentIndex, pageSize)
+
+	indexInfo, err := data_manage.GetBaseFromPurangIndexByIndexCode(indexCode)
+	if err != nil {
+		br.Msg = "获取指标信息失败"
+		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+		return
+	}
+	total, err = data_manage.GetPurangIndexDataTotalByCode(indexCode)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	page = paging.GetPaging(currentIndex, pageSize, int(total))
+	dataTmpList, err := data_manage.GetPurangIndexDataByCode(indexCode, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取数据失败"
+		br.ErrMsg = "获取数据失败,Err:" + err.Error()
+		return
+	}
+	edbInfo, err := data_manage.GetEdbInfoByEdbCode(utils.DATA_SOURCE_PURANG, indexCode)
+	if err != nil && !utils.IsErrNoRow(err) {
+		br.Msg = "获取数据源失败"
+		br.ErrMsg = "获取数据源失败,Err:" + err.Error()
+		return
+	}
+
+	var ret data_manage.PurangSingleDataResp
+	var dataList []*data_manage.PurangSingleData
+
+	if edbInfo != nil {
+		ret.EdbInfoId = edbInfo.EdbInfoId
+		ret.EdbExist = 1
+	}
+	ret.ClassifyId = indexInfo.ClassifyId
+	ret.BaseFromPurangIndexId = indexInfo.BaseFromPurangIndexId
+	ret.IndexCode = indexInfo.IndexCode
+	ret.IndexName = indexInfo.IndexName
+	ret.Frequency = indexInfo.Frequency
+	ret.CreateTime = indexInfo.CreateTime.Format(utils.FormatDateTime)
+	ret.ModifyTime = indexInfo.ModifyTime.Format(utils.FormatDateTime)
+	ret.Unit = indexInfo.Unit
+	for _, v := range dataTmpList {
+		tmp := &data_manage.PurangSingleData{
+			Value:    v.Value,
+			DataTime: v.DataTime,
+		}
+		dataList = append(dataList, tmp)
+	}
+	ret.Data = dataList
+	ret.Paging = page
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = ret
+}
+
+// PurangIndexList
+// @Title Purang指标列表
+// @Description Purang指标列表
+// @Param   ClassifyId   query   int  true       "分类id"
+// @Success 200 {object} data_manage.BaseFromPurangClassifyResp
+// @router /purang/classify/index/list [get]
+func (this *BaseFromPurangController) PurangIndexList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	classifyId, _ := this.GetInt("ClassifyId", 0)
+	indexList, err := data_manage.GetPurangIndexByClassifyId(classifyId)
+	if err != nil && !utils.IsErrNoRow(err) {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取指标信息失败,Err:" + err.Error()
+		return
+	}
+
+	var ret data_manage.BaseFromPurangClassifyResp
+	list := make([]*data_manage.BaseFromPurangClassifyItems, 0)
+	for _, v := range indexList {
+		classify := new(data_manage.BaseFromPurangClassifyItems)
+		classify.ClassifyId = classifyId
+		classify.BaseFromPurangIndexId = v.BaseFromPurangIndexId
+		classify.IndexCode = v.IndexCode
+		classify.ClassifyName = v.IndexName
+		classify.UniqueCode = fmt.Sprintf("%d_%d", classifyId, v.BaseFromPurangIndexId)
+		list = append(list, classify)
+	}
+	ret.List = list
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = ret
+}
+
+// PurangNameCheck
+// @Title 加入指标库的重名检测
+// @Description 加入指标库的重名检测
+// @Param   ClassifyIds   query   string  true       "分类id, 多个分类用英文"
+// @Param   Keyword   query   string  true       "关键词, 指标ID/指标名称"
+// @Success 200 {object} NameCheckResult
+// @router /purang/edb_info/name_check [post]
+func (this *BaseFromPurangController) PurangNameCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req []*data_manage.NameCheckEdbInfoReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	if len(req) == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	codeMaxT := 30
+	codeLen := len(req)
+	if codeLen > codeMaxT {
+		br.Msg = "批量添加指标数量不得超过30个"
+		return
+	}
+
+	indexNames := make([]string, 0)
+	resp := make([]*data_manage.EdbNameCheckResult, 0)
+	for _, v := range req {
+		v.EdbCode = strings.TrimSpace(v.EdbCode)
+		if v.EdbCode == "" {
+			br.Msg = "指标ID不可为空"
+			return
+		}
+		v.EdbName = strings.TrimSpace(v.EdbName)
+		if v.EdbName == "" {
+			br.Msg = "请输入指标名称"
+			return
+		}
+		indexNames = append(indexNames, v.EdbName)
+		resp = append(resp, &data_manage.EdbNameCheckResult{
+			EdbCode: v.EdbCode,
+			EdbName: v.EdbName,
+		})
+		dataItems, err := data_manage.GetEdbDataAllByEdbCode(v.EdbCode, utils.DATA_SOURCE_PURANG, 0, utils.EDB_DATA_LIMIT)
+		if err != nil && !utils.IsErrNoRow(err) {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取钢联已存在信息失败,Err:" + err.Error()
+			return
+		}
+		if len(dataItems) <= 0 {
+			respItem, err := data.AddEdbData(utils.DATA_SOURCE_PURANG, v.EdbCode, v.Frequency)
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取失败,Err:" + err.Error()
+				return
+			}
+			if respItem.Ret != 200 {
+				br.Msg = "未搜索到该指标"
+				br.ErrMsg = respItem.ErrMsg + ";EdbCode:" + v.EdbCode
+				return
+			}
+		}
+	}
+
+	// 重名校验
+	edbList, e := data_manage.GetEdbInfoByNameArr(indexNames, utils.EDB_INFO_TYPE)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取重名指标失败, Err: " + e.Error()
+		return
+	}
+	nameExists := make(map[string]bool)
+	for _, v := range edbList {
+		nameExists[v.EdbName] = true
+	}
+	if len(nameExists) > 0 {
+		for _, v := range resp {
+			v.Exist = nameExists[v.EdbName]
+		}
+	}
+
+	br.Data = resp
+	br.Msg = "校验成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// PurangAddCheck
+// @Title 加入指标库指标Id检测
+// @Description 加入指标库指标Id检测
+// @Param	request	body request.BatchAddCheckReq true "type json string"
+// @Success 200 string "操作成功"
+// @router /purang/edb_info/add_check [post]
+func (c *BaseFromPurangController) PurangAddCheck() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req data_manage.BatchAddCheckReq
+	if e := json.Unmarshal(c.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + e.Error()
+		return
+	}
+	codeMaxT := 30
+	codeLen := len(req.IndexCodes)
+
+	// 获取指标库已有指标
+	existsEdb, e := data_manage.GetEdbCodesBySource(utils.DATA_SOURCE_PURANG)
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取自有数据已添加的指标失败, Err: " + e.Error()
+		return
+	}
+	existMap := make(map[string]*data_manage.EdbInfo)
+	for _, v := range existsEdb {
+		existMap[v.EdbCode] = v
+	}
+
+	if codeLen == 0 {
+		br.Msg = "请选择指标"
+		return
+	}
+	if codeLen > codeMaxT {
+		br.Msg = fmt.Sprintf("最多只能选择%d个指标", codeMaxT)
+		return
+	}
+
+	// 查询选中的指标
+	cond := fmt.Sprintf(` AND index_code IN (%s)`, utils.GetOrmInReplace(codeLen))
+	pars := make([]interface{}, 0)
+	pars = append(pars, req.IndexCodes)
+	list, err := data_manage.GetPurangIndex(cond, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取钢联已存在信息失败,Err:" + err.Error()
+		return
+	}
+
+	if len(list) > codeMaxT {
+		br.Msg = fmt.Sprintf("最多只能选择%d个指标", codeMaxT)
+		return
+	}
+
+	resp := make([]*data_manage.BaseFromPurangIndexList, 0)
+	for _, v := range list {
+		if edb, ok := existMap[v.IndexCode]; ok {
+			v.EdbInfoId = edb.EdbInfoId
+			v.EdbClassifyId = edb.ClassifyId
+			v.EdbUniqueCode = edb.UniqueCode
+			v.EdbExist = 1
+		}
+		resp = append(resp, v)
+	}
+
+	br.Data = resp
+	br.Msg = "校验成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// PurangEdbInfoAdd
+// @Title 新增指标接口
+// @Description 新增指标接口
+// @Param	request	body data_manage.AddEdbInfoReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /purang/edb_info/add [post]
+func (this *BaseFromPurangController) PurangEdbInfoAdd() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	deleteCache := true
+	cacheKey := "CACHE_EDB_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
+	defer func() {
+		if deleteCache {
+			utils.Rc.Delete(cacheKey)
+		}
+	}()
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		deleteCache = false
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+	var req data_manage.AddEdbInfoReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	req.EdbName = strings.Trim(req.EdbName, " ")
+	req.EdbCode = strings.Trim(req.EdbCode, " ")
+
+	if req.EdbCode == "" {
+		br.Msg = "指标ID不能为空"
+		return
+	}
+
+	if req.EdbName == "" {
+		br.Msg = "指标名称不能为空"
+		return
+	}
+
+	if req.Frequency == "" {
+		br.Msg = "频率不能为空"
+		return
+	}
+
+	if req.Unit == "" {
+		br.Msg = "单位不能为空"
+		return
+	}
+
+	if req.ClassifyId <= 0 {
+		br.Msg = "请选择分类"
+		return
+	}
+
+	count, err := data_manage.GetPurangIndexDataCount(req.EdbCode)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	if count == 0 {
+		br.Msg = "指标不存在"
+	}
+
+	// 指标入库
+	edbInfo, err, errMsg, isSendEmail := data.EdbInfoAdd(utils.DATA_SOURCE_PURANG, utils.DATA_SUB_SOURCE_EDB, req.ClassifyId, req.EdbCode, req.EdbName, req.Frequency, req.Unit, req.StartDate, req.EndDate, sysUser.AdminId, sysUser.RealName, this.Lang)
+	if err != nil {
+		br.Msg = "保存失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	// 试用平台更新用户累计新增指标数
+	adminItem, e := system.GetSysAdminById(sysUser.AdminId)
+	if e != nil {
+		br.Msg = "操作失败"
+		br.ErrMsg = "获取系统用户数据失败,Err:" + e.Error()
+		return
+	}
+	if utils.BusinessCode == utils.BusinessCodeSandbox && adminItem.DepartmentName == "ETA试用客户" {
+		go func() {
+			var r etaTrialService.EtaTrialUserReq
+			r.Mobile = adminItem.Mobile
+			_, _ = etaTrialService.UpdateUserIndexNum(r)
+		}()
+	}
+
+	//新增操作日志
+	{
+		edbLog := new(data_manage.EdbInfoLog)
+		edbLog.EdbInfoId = edbInfo.EdbInfoId
+		edbLog.SourceName = edbInfo.SourceName
+		edbLog.Source = edbInfo.Source
+		edbLog.EdbCode = edbInfo.EdbCode
+		edbLog.EdbName = edbInfo.EdbName
+		edbLog.ClassifyId = edbInfo.ClassifyId
+		edbLog.SysUserId = sysUser.AdminId
+		edbLog.SysUserRealName = sysUser.RealName
+		edbLog.CreateTime = time.Now()
+		edbLog.Content = string(this.Ctx.Input.RequestBody)
+		edbLog.Status = "新增指标"
+		edbLog.Method = this.Ctx.Input.URI()
+		go data_manage.AddEdbInfoLog(edbLog)
+	}
+
+	// 更新es
+	go data.AddOrEditEdbInfoToEs(edbInfo.EdbInfoId)
+
+	resp := new(data_manage.AddEdbInfoResp)
+	resp.EdbInfoId = edbInfo.EdbInfoId
+	resp.UniqueCode = edbInfo.UniqueCode
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+	br.Data = resp
+	br.IsAddLog = true
+}
+
+// ExportPurangList
+// @Title 导出Purang数据
+// @Description 导出Purang数据
+// @Param   ClassifyId   query   int  true       "关键字搜索"
+// @Param   IndexCode   query   string  true       "指标编码"
+// @Success 200  导出成功
+// @router /purang/export [get]
+func (this *BaseFromPurangController) ExportPurangList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请重新登录"
+		return
+	}
+
+	classifyId, _ := this.GetInt("ClassifyId")
+	indexCode := this.GetString("IndexCode")
+
+	if classifyId <= 0 && indexCode == "" {
+		br.Msg = "请选择分类或者指标"
+		return
+	}
+	dir, _ := os.Executable()
+	exPath := filepath.Dir(dir)
+	downLoadnFilePath := exPath + "/" + time.Now().Format(utils.FormatDateTimeUnSpace) + ".xlsx"
+	xlsxFile := xlsx.NewFile()
+
+	var condition string
+	var pars []interface{}
+	var classifyName string
+	if classifyId > 0 {
+		classifyInfo, err := data_manage.GetBaseFromPurangClassifyById(classifyId)
+		if err != nil {
+			if utils.IsErrNoRow(err) {
+				br.Msg = "分类不存在"
+				return
+			}
+			br.Msg = "下载失败"
+			br.ErrMsg = "获取分类失败,Err:" + err.Error()
+			return
+		}
+		classifyName = classifyInfo.ClassifyName
+		childClassify, err := data_manage.GetBaseFromPurangClassifyByParentId(classifyId)
+		if err != nil {
+			br.Msg = "下载失败"
+			br.ErrMsg = "获取分类失败,Err:" + err.Error()
+			return
+		}
+
+		if len(childClassify) > 0 {
+			condition += `AND classify_id IN (` + utils.GetOrmInReplace(len(childClassify)) + `)`
+			for _, child := range childClassify {
+				pars = append(pars, child.ClassifyId)
+			}
+		} else {
+			condition += ` AND classify_id=?`
+			pars = append(pars, classifyId)
+		}
+	}
+	if indexCode != "" {
+		condition += ` AND index_code=? `
+		pars = append(pars, indexCode)
+	}
+
+	indexList, err := data_manage.GetPurangIndex(condition, pars)
+	if err != nil {
+		br.Msg = "下载失败"
+		br.ErrMsg = "获取指标失败,Err:" + err.Error()
+		fmt.Println("获取数据失败,Err:" + err.Error())
+		return
+	}
+	if len(indexList) <= 0 {
+		fmt.Println("indexList 为空")
+		br.Ret = 200
+		br.Success = true
+		br.Msg = "success"
+		return
+	}
+
+	codeList := make([]string, 0)
+	frequenciesMap := make(map[string][]*data_manage.BaseFromPurangIndexList)
+	for _, v := range indexList {
+		codeList = append(codeList, v.IndexCode)
+		frequenciesMap[v.Frequency] = append(frequenciesMap[v.Frequency], v)
+	}
+	dataListMap := make(map[string][]*data_manage.BaseFromPurangData)
+	if len(indexList) > 0 {
+		allDataList, e := data_manage.GetPurangIndexDataByCodes(codeList)
+		if e != nil {
+			br.Msg = "获取数据失败"
+			br.ErrMsg = "获取数据失败,Err:" + e.Error()
+			return
+		}
+		for _, v := range allDataList {
+			dataListMap[v.IndexCode] = append(dataListMap[v.IndexCode], v)
+		}
+	}
+	// 按照频率分组排序
+	frequencies := []string{
+		"日度", "周度", "旬度", "月度", "季度", "半年度", "年度",
+	}
+	for _, frequency := range frequencies {
+		//获取指标
+		indexCodeList, ok := frequenciesMap[frequency]
+		if !ok {
+			continue
+		}
+		if len(indexCodeList) <= 0 {
+			fmt.Printf("sheet:%s, 不存在指标", frequency)
+			return
+		}
+		var sheetName string
+		switch frequency {
+		case "日度":
+			sheetName = "日度(Daily)"
+		case "周度":
+			sheetName = "周度(Weekly)"
+		case "旬度":
+			sheetName = "旬度(ten-day)"
+		case "月度":
+			sheetName = "月度(Monthly)"
+		case "季度":
+			sheetName = "季度(Quarterly)"
+		case "半年度":
+			sheetName = "半年度(Semi-annual)"
+		case "年度":
+			sheetName = "年度(Annual)"
+		default:
+			sheetName = "其他数据"
+		}
+		sheetNew, err := xlsxFile.AddSheet(sheetName)
+		if err != nil {
+			fmt.Println("新增Sheet失败", err.Error())
+			return
+		}
+		secNameRow := sheetNew.AddRow()
+		frequencyRow := sheetNew.AddRow()
+		unitRow := sheetNew.AddRow()
+		lastModifyDateRow := sheetNew.AddRow()
+
+		var indexIdList []int
+		for _, idx := range frequenciesMap[frequency] {
+			indexIdList = append(indexIdList, idx.BaseFromPurangIndexId)
+		}
+		dataTimeList, err := data_manage.GetPurangDataDataTimeByIndexId(indexIdList)
+		if err != nil {
+			br.Msg = "下载失败"
+			br.ErrMsg = "获取数据时间失败,Err:" + err.Error()
+			fmt.Println("获取数据时间失败", err.Error())
+			return
+		}
+
+		// 添加excel左侧指标日期
+		setRowIndex := 4
+		for rk, dv := range dataTimeList {
+			rowIndex := setRowIndex + rk
+			row := sheetNew.Row(rowIndex)
+			displayDate, _ := time.Parse(utils.FormatDate, dv)
+			displayDateCell := row.AddCell()
+			style := new(xlsx.Style)
+			style.ApplyAlignment = true
+			style.Alignment.WrapText = true
+			displayDateCell.SetStyle(style)
+			displayDateCell.SetDate(displayDate)
+
+		}
+		for k, icl := range indexCodeList {
+			// 获取数据
+			dataList, ok := dataListMap[icl.IndexCode]
+			if !ok {
+				continue
+			}
+			if k == 0 {
+				secNameRow.AddCell().SetValue("指标名称/Metric Name")
+				frequencyRow.AddCell().SetValue("频度/Frequency")
+				unitRow.AddCell().SetValue("单位/Unit")
+				lastModifyDateRow.AddCell().SetValue("更新时间/Update Time")
+				min := k * 3
+				sheetNew.SetColWidth(min, min, 15)
+			}
+			if len(dataList) == 0 {
+				continue
+			}
+			secNameRow.AddCell().SetValue(icl.IndexName)
+			frequencyRow.AddCell().SetValue(icl.Frequency)
+			unitRow.AddCell().SetValue(icl.Unit)
+
+			timeDate, err := time.Parse(utils.FormatDateTime, dataList[0].ModifyTime)
+			if err != nil {
+				continue
+			}
+			lastModifyDateRow.AddCell().SetValue(timeDate.Format(utils.FormatDate))
+			dataInfoMap := make(map[string]*data_manage.BaseFromPurangData)
+			for _, v := range dataList {
+				dataInfoMap[v.DataTime] = v
+			}
+
+			for rk, dtv := range dataTimeList {
+				rowIndex := setRowIndex + rk
+				row := sheetNew.Row(rowIndex)
+				displayDateCell := row.AddCell()
+				tmpData, ok := dataInfoMap[dtv]
+				if ok {
+					displayDateCell.SetValue(tmpData.Value)
+				}
+			}
+		}
+	}
+
+	err = xlsxFile.Save(downLoadnFilePath)
+	if err != nil {
+		//有指标无数据时先导出一遍空表
+		sheet, err := xlsxFile.AddSheet("无数据")
+		if err != nil {
+			br.Msg = "新增Sheet失败"
+			br.ErrMsg = "新增Sheet失败,Err:" + err.Error()
+			return
+		}
+		rowSecName := sheet.AddRow()
+		celSecName := rowSecName.AddCell()
+		celSecName.SetValue("")
+		e := xlsxFile.Save(downLoadnFilePath)
+		if e != nil {
+			br.Msg = "保存文件失败"
+			br.ErrMsg = "保存文件失败"
+			return
+		}
+	}
+
+	fileName := classifyName
+	if indexCode != "" && len(indexList) == 1 {
+		fileName = indexList[0].IndexName
+	}
+	fileName = strings.Replace(fileName, ": ", "_", -1)
+	fileName = strings.Replace(fileName, ", ", "_", -1)
+	fileName = strings.Replace(fileName, " ", "_", -1)
+	fileName += time.Now().Format("06.01.02") + `.xlsx` //文件名称
+	fmt.Println(fileName)
+	this.Ctx.Output.Download(downLoadnFilePath, fileName)
+	defer func() {
+		os.Remove(downLoadnFilePath)
+	}()
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "success"
+}
+
+// GetFrequency
+// @Title Purang数据频度
+// @Description Purang数据频度接口
+// @Param   ClassifyId   query   string  true       "分类Id"
+// @Success 200 {object} data_manage.LzFrequency
+// @router /purang/frequency [get]
+func (this *BaseFromPurangController) GetFrequency() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	classifyId, _ := this.GetInt("ClassifyId")
+	if classifyId < 0 {
+		br.Msg = "请选择分类"
+		br.ErrMsg = "请选择分类"
+		return
+	}
+
+	frequencyList, err := data_manage.GetPurangFrequencyByClassifyId(classifyId)
+	if err != nil {
+		br.Msg = "获取频度失败"
+		br.ErrMsg = "获取频度失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = frequencyList
+} 

+ 1 - 1
controllers/data_manage/supply_analysis/variety.go

@@ -224,7 +224,7 @@ func (this *VarietyController) Edit() {
 		br.ErrMsg = "添加失败,Err:" + err.Error()
 		return
 	}
-	if item != nil && item.VarietyId != req.VarietyId {
+	if item != nil && item.VarietyId != req.VarietyId && item.VarietyId > 0 {
 		br.Msg = "添加失败,品种名称不能重复"
 		br.IsSendEmail = false
 		return

+ 24 - 0
controllers/data_source/data_source.go

@@ -260,6 +260,21 @@ func (c *DataSourceController) SearchByEs() {
 	//	}
 	//}
 
+	// 萝卜投研-LevelPath用作前端定位
+	levelPathMap := make(map[int]string)
+	if source == utils.DATA_SOURCE_RADISH_RESEARCH {
+		classifyOb := new(data_manage.BaseFromRadishResearchClassify)
+		classifies, e := classifyOb.GetItemsByCondition("", make([]interface{}, 0), []string{classifyOb.Cols().PrimaryId, classifyOb.Cols().LevelPath}, "")
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = fmt.Sprintf("获取萝卜投研分类失败, %v", e)
+			return
+		}
+		for _, v := range classifies {
+			levelPathMap[v.BaseFromRadishResearchClassifyId] = v.LevelPath
+		}
+	}
+
 	for _, v := range listMap {
 		classifyId, ok := v[classifyIdKey].(int)
 		if !ok {
@@ -271,6 +286,15 @@ func (c *DataSourceController) SearchByEs() {
 		v["StartDate"] = utils.GormDateStrToDateStr(startDate)
 		endDate := v["EndDate"].(string)
 		v["EndDate"] = utils.GormDateStrToDateStr(endDate)
+
+		if source == utils.DATA_SOURCE_RADISH_RESEARCH {
+			// 未分类
+			if classifyId == 0 {
+				v["ClassifyLevelPath"] = "0"
+				continue
+			}
+			v["ClassifyLevelPath"] = levelPathMap[classifyId]
+		}
 	}
 
 	page := paging.GetPaging(currentIndex, pageSize, total)

+ 12 - 0
controllers/english_report/email.go

@@ -595,6 +595,18 @@ func (this *EnglishReportEmailController) Send() {
 			noCompanyIdsMap[v] = struct{}{}
 		}
 	}
+
+	// 加个缓存吧,避免重复点击
+	cacheKey := "CACHE_ENGLISH_REPORT_SEND_" + strconv.Itoa(req.ReportId)
+	if !utils.Rc.SetNX(cacheKey, 1, 300*time.Second) {
+		br.Msg = "系统处理中,请稍后重试!"
+		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
+		return
+	}
+	defer func() {
+		_ = utils.Rc.Delete(cacheKey)
+	}()
+
 	// 指定品种的客户
 	sendCompanyIds := make([]int, 0)
 	if len(req.EnPermissions) > 0 {

+ 93 - 4
controllers/english_report/report.go

@@ -66,6 +66,8 @@ func (this *EnglishReportController) Add() {
 
 	var contentSub string
 	if req.Content != "" {
+		req.Content = services.HandleReportContent(req.Content, "del", nil)
+
 		e := utils.ContentXssCheck(req.Content)
 		if e != nil {
 			br.Msg = "存在非法标签"
@@ -226,6 +228,7 @@ func (this *EnglishReportController) Edit() {
 	var contentSub string
 	if req.Content != "" {
 		req.Content = services.HandleReportContentTable(int(req.ReportId), req.Content)
+		req.Content = services.HandleReportContent(req.Content, "del", nil)
 		e := utils.ContentXssCheck(req.Content)
 		if e != nil {
 			br.Msg = "存在非法标签"
@@ -351,6 +354,17 @@ func (this *EnglishReportController) Detail() {
 	// 处理关联excel的表格id
 	item.Content = services.HandleReportContentTable(item.Id, item.Content)
 
+	businessConf, err := models.GetBusinessConfByKey(models.BusinessConfIsOpenChartExpired)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取配置失败,Err:" + err.Error()
+		return
+	}
+	if businessConf.ConfVal == `true` {
+		tokenMap := make(map[string]string)
+		item.Content = services.HandleReportContent(item.Content, "add", tokenMap)
+	}
+
 	classifyNameMap := make(map[int]*models.EnglishClassifyFullName)
 	if item.ClassifyIdSecond > 0 {
 		nameList, tErr := models.GetEnglishClassifyFullNameByIds([]int{item.ClassifyIdSecond})
@@ -736,8 +750,9 @@ func (this *EnglishReportController) PublishReport() {
 			}()
 
 			// 生成报告pdf和长图
-			if req.ReportUrl != "" {
-				go services.Report2pdfAndJpeg(req.ReportUrl, report.Id, 2)
+			pdfUrl := services.GetGeneralEnglishReportPdfUrl(report.Id, report.ReportCode)
+			if pdfUrl != "" {
+				go services.Report2pdfAndJpeg(pdfUrl, report.Id, 2)
 			}
 		} else {
 			// 从无审批切换为有审批, 状态重置
@@ -839,8 +854,9 @@ func (this *EnglishReportController) PrePublishReport() {
 	}
 
 	// 生成报告pdf和长图
-	if req.ReportUrl != "" {
-		go services.Report2pdfAndJpeg(req.ReportUrl, report.Id, 2)
+	pdfUrl := services.GetGeneralEnglishReportPdfUrl(report.Id, report.ReportCode)
+	if pdfUrl != "" {
+		go services.Report2pdfAndJpeg(pdfUrl, report.Id, 2)
 	}
 
 	br.Ret = 200
@@ -999,6 +1015,7 @@ func (this *EnglishReportController) SaveReportContent() {
 	}
 
 	if noChangeFlag != 1 {
+		req.Content = services.HandleReportContent(req.Content, "del", nil)
 		content := req.Content
 		if content == "" {
 			content = this.GetString("Content")
@@ -1080,6 +1097,18 @@ func (this *EnglishReportController) ClassifyIdDetail() {
 		item.Content = html.UnescapeString(item.Content)
 		item.ContentSub = html.UnescapeString(item.ContentSub)
 
+		businessConf, err := models.GetBusinessConfByKey(models.BusinessConfIsOpenChartExpired)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取配置失败,Err:" + err.Error()
+			return
+		}
+
+		if businessConf.ConfVal == `true` {
+			tokenMap := make(map[string]string)
+			item.Content = services.HandleReportContent(item.Content, "add", tokenMap)
+		}
+
 		classifyNameMap := make(map[int]*models.EnglishClassifyFullName)
 		if item.ClassifyIdSecond > 0 {
 			nameList, tErr := models.GetEnglishClassifyFullNameByIds([]int{item.ClassifyIdSecond})
@@ -1502,3 +1531,63 @@ func (this *EnglishReportController) CancelApprove() {
 	br.Success = true
 	br.Msg = "操作成功"
 }
+
+// @Title 获取报告分享链接
+// @Description 获取报告分享链接
+// @Param   ReportId   query   int  true       "报告id"
+// @Success 200 {object} models.EnglishReportDetailView
+// @router /share_url [get]
+func (this *EnglishReportController) GetShareUrl() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	/*var req models.ReportDetailReq
+	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.ReportId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}*/
+	reportId, err := this.GetInt("ReportId")
+	if err != nil {
+		br.Msg = "获取参数失败!"
+		br.ErrMsg = "获取参数失败,Err:" + err.Error()
+		return
+	}
+	if reportId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+	item, err := models.GetEnglishReportById(reportId)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	token, err := services.GetEnglishReportToken(reportId, item.ReportCode)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "报告已被删除"
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	br.Data = token
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 420 - 0
controllers/llm/abstract.go

@@ -0,0 +1,420 @@
+package llm
+
+import (
+	"encoding/json"
+	"eta/eta_api/cache"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/rag"
+	"eta/eta_api/models/rag/request"
+	"eta/eta_api/models/rag/response"
+	"eta/eta_api/services"
+	"eta/eta_api/services/elastic"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+// AbstractController
+// @Description: 摘要管理
+type AbstractController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 列表
+// @Description 列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Success 200 {object} []*rag.QuestionListListResp
+// @router /abstract/list [get]
+func (c *AbstractController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	keyWord := c.GetString("KeyWord")
+	tagId, _ := c.GetInt("TagId")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	// 获取列表
+	total, viewList, err := getAbstractList(keyWord, tagId, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := response.AbstractListListResp{
+		List:   viewList,
+		Paging: page,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+func getAbstractList(keyWord string, tagId int, startSize, pageSize int) (total int, viewList []rag.WechatArticleAbstractView, err error) {
+	if keyWord == `` {
+		var condition string
+		var pars []interface{}
+		condition += fmt.Sprintf(` AND c.%s = ?`, rag.WechatPlatformColumns.Enabled)
+		pars = append(pars, 1)
+
+		if keyWord != "" {
+			condition += fmt.Sprintf(` AND a.%s like ?`, rag.WechatArticleAbstractColumns.Content)
+			pars = append(pars, `%`+keyWord+`%`)
+		}
+
+		if tagId > 0 {
+			condition += fmt.Sprintf(` AND d.%s = ?`, rag.WechatPlatformTagMappingColumns.TagID)
+			pars = append(pars, tagId)
+		}
+
+		obj := new(rag.WechatArticleAbstract)
+		tmpTotal, list, tmpErr := obj.GetPageListByTagAndPlatformCondition(condition, pars, startSize, pageSize)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		total = tmpTotal
+		viewList = obj.WechatArticleAbstractItem(list)
+	} else {
+		sortMap := map[string]string{
+			//"ModifyTime":              "desc",
+			//"WechatArticleAbstractId": "desc",
+		}
+
+		obj := new(rag.WechatPlatform)
+		platformList, tmpErr := obj.GetListByCondition(` AND enabled = 1 `, []interface{}{}, 0, 100000)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		platformIdList := make([]int, 0)
+		for _, v := range platformList {
+			platformIdList = append(platformIdList, v.WechatPlatformId)
+		}
+		tagList := make([]int, 0)
+		if tagId > 0 {
+			tagList = append(tagList, tagId)
+		}
+		tmpTotal, list, tmpErr := elastic.WechatArticleAbstractEsSearch(keyWord, tagList, platformIdList, startSize, pageSize, sortMap)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		total = int(tmpTotal)
+		if list != nil && len(list) > 0 {
+			viewList = list[0].ToViewList(list)
+		}
+	}
+
+	return
+}
+
+// Del
+// @Title 删除摘要
+// @Description 删除摘要
+// @Param	request	body request.BeachOpAbstractReq true "type json string"
+// @Success 200 Ret=200 新增成功
+// @router /abstract/del [post]
+func (c *AbstractController) Del() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.BeachOpAbstractReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req.WechatArticleAbstractIdList) <= 0 && !req.IsSelectAll {
+		br.Msg = "请选择摘要"
+		br.IsSendEmail = false
+		return
+	}
+
+	vectorKeyList := make([]string, 0)
+	wechatArticleAbstractIdList := make([]int, 0)
+
+	obj := rag.WechatArticleAbstract{}
+
+	if !req.IsSelectAll {
+		list, err := obj.GetByIdList(req.WechatArticleAbstractIdList)
+		if err != nil {
+			br.Msg = "修改失败"
+			br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+			if utils.IsErrNoRow(err) {
+				br.Msg = "问题不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+		if len(list) > 0 {
+			for _, v := range list {
+				// 有加入到向量库,那么就加入到待删除的向量库list中
+				if v.VectorKey != `` {
+					vectorKeyList = append(vectorKeyList, v.VectorKey)
+				}
+				wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
+			}
+		}
+	} else {
+		notIdMap := make(map[int]bool)
+		for _, v := range req.NotWechatArticleAbstractIdList {
+			notIdMap[v] = true
+		}
+
+		_, list, err := getAbstractList(req.KeyWord, req.TagId, 0, 100000)
+		if err != nil {
+			br.Msg = "修改失败"
+			br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+			if utils.IsErrNoRow(err) {
+				br.Msg = "问题不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+		if len(list) > 0 {
+			for _, v := range list {
+				if notIdMap[v.WechatArticleAbstractId] {
+					continue
+				}
+				// 有加入到向量库,那么就加入到待删除的向量库list中
+				if v.VectorKey != `` {
+					vectorKeyList = append(vectorKeyList, v.VectorKey)
+				}
+				wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
+			}
+		}
+	}
+
+	// 删除向量库
+	err = services.DelLlmDoc(vectorKeyList, wechatArticleAbstractIdList)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除向量库失败,Err:" + err.Error()
+		return
+	}
+
+	// 删除摘要
+	err = obj.DelByIdList(wechatArticleAbstractIdList)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	// 删除es数据
+	for _, wechatArticleAbstractId := range wechatArticleAbstractIdList {
+		go services.DelEsWechatArticleAbstract(wechatArticleAbstractId)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `删除成功`
+}
+
+// VectorDel
+// @Title 删除摘要向量库
+// @Description 删除摘要向量库
+// @Param	request	body request.BeachOpAbstractReq true "type json string"
+// @Success 200 Ret=200 新增成功
+// @router /abstract/vector/del [post]
+func (c *AbstractController) VectorDel() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.BeachOpAbstractReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req.WechatArticleAbstractIdList) <= 0 && !req.IsSelectAll {
+		br.Msg = "请选择摘要"
+		br.IsSendEmail = false
+		return
+	}
+
+	vectorKeyList := make([]string, 0)
+	wechatArticleAbstractIdList := make([]int, 0)
+
+	obj := rag.WechatArticleAbstract{}
+
+	if !req.IsSelectAll {
+		list, err := obj.GetByIdList(req.WechatArticleAbstractIdList)
+		if err != nil {
+			br.Msg = "修改失败"
+			br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+			if utils.IsErrNoRow(err) {
+				br.Msg = "问题不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+		if len(list) > 0 {
+			for _, v := range list {
+				// 有加入到向量库,那么就加入到待删除的向量库list中
+				if v.VectorKey != `` {
+					vectorKeyList = append(vectorKeyList, v.VectorKey)
+				}
+				wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
+			}
+		}
+	} else {
+		notIdMap := make(map[int]bool)
+		for _, v := range req.NotWechatArticleAbstractIdList {
+			notIdMap[v] = true
+		}
+		_, list, err := getAbstractList(req.KeyWord, req.TagId, 0, 100000)
+		if err != nil {
+			br.Msg = "修改失败"
+			br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+			if utils.IsErrNoRow(err) {
+				br.Msg = "问题不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+		if len(list) > 0 {
+			for _, v := range list {
+				if notIdMap[v.WechatArticleAbstractId] {
+					continue
+				}
+
+				// 有加入到向量库,那么就加入到待删除的向量库list中
+				if v.VectorKey != `` {
+					vectorKeyList = append(vectorKeyList, v.VectorKey)
+				}
+				wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
+			}
+		}
+	}
+
+	// 删除摘要库
+	err = services.DelLlmDoc(vectorKeyList, wechatArticleAbstractIdList)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	// 修改ES数据
+	for _, wechatArticleAbstractId := range wechatArticleAbstractIdList {
+		go services.AddOrEditEsWechatArticleAbstract(wechatArticleAbstractId)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `删除成功`
+}
+
+// AddVector
+// @Title 删除摘要向量库
+// @Description 删除摘要向量库
+// @Param	request	body request.BeachOpAbstractReq true "type json string"
+// @Success 200 Ret=200 新增成功
+// @router /abstract/vector/add [post]
+func (c *AbstractController) AddVector() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.BeachOpAbstractReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req.WechatArticleAbstractIdList) <= 0 && !req.IsSelectAll {
+		br.Msg = "请选择摘要"
+		br.IsSendEmail = false
+		return
+	}
+
+	wechatArticleAbstractIdList := make([]int, 0)
+
+	obj := rag.WechatArticleAbstract{}
+
+	if !req.IsSelectAll {
+		list, err := obj.GetByIdList(req.WechatArticleAbstractIdList)
+		if err != nil {
+			br.Msg = "修改失败"
+			br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+			if utils.IsErrNoRow(err) {
+				br.Msg = "问题不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+		if len(list) > 0 {
+			for _, v := range list {
+				wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
+			}
+		}
+	} else {
+		notIdMap := make(map[int]bool)
+		for _, v := range req.NotWechatArticleAbstractIdList {
+			notIdMap[v] = true
+		}
+
+		_, list, err := getAbstractList(req.KeyWord, req.TagId, 0, 100000)
+		if err != nil {
+			br.Msg = "修改失败"
+			br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+			if utils.IsErrNoRow(err) {
+				br.Msg = "问题不存在"
+				br.IsSendEmail = false
+			}
+			return
+		}
+		if len(list) > 0 {
+			for _, v := range list {
+				if notIdMap[v.WechatArticleAbstractId] {
+					continue
+				}
+				wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
+			}
+		}
+	}
+
+	for _, wechatArticleAbstractId := range wechatArticleAbstractIdList {
+		cache.AddWechatArticleLlmOpToCache(wechatArticleAbstractId, ``)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `添加向量库中,请稍后查看`
+}

+ 186 - 0
controllers/llm/chat_ws_controller.go

@@ -0,0 +1,186 @@
+package llm
+
+import (
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/llm/facade"
+	"eta/eta_api/utils"
+	"eta/eta_api/utils/ws"
+	"fmt"
+	"github.com/gorilla/websocket"
+	"net"
+	"net/http"
+	"strings"
+	"time"
+)
+
+type ChatWsController struct {
+	controllers.BaseAuthController
+}
+
+func (cc *ChatWsController) Prepare() {
+	method := cc.Ctx.Input.Method()
+	uri := cc.Ctx.Input.URI()
+	if method == "GET" {
+		authorization := cc.Ctx.Input.Header("authorization")
+		if authorization == "" {
+			authorization = cc.Ctx.Input.Header("Authorization")
+		}
+		if strings.Contains(authorization, ";") {
+			authorization = strings.Replace(authorization, ";", "$", 1)
+		}
+		if authorization == "" {
+			strArr := strings.Split(uri, "?")
+			for k, v := range strArr {
+				fmt.Println(k, v)
+			}
+			if len(strArr) > 1 {
+				authorization = strArr[1]
+				authorization = strings.Replace(authorization, "Authorization", "authorization", -1)
+			}
+		}
+		if authorization == "" {
+			utils.FileLog.Error("authorization为空,未授权")
+			cc.Ctx.ResponseWriter.WriteHeader(http.StatusUnauthorized)
+			return
+		}
+		tokenStr := authorization
+		tokenArr := strings.Split(tokenStr, "=")
+		token := tokenArr[1]
+
+		session, err := system.GetSysSessionByToken(token)
+		if err != nil {
+			if utils.IsErrNoRow(err) {
+				utils.FileLog.Error("authorization已过期")
+				cc.Ctx.ResponseWriter.WriteHeader(http.StatusUnauthorized)
+				return
+			}
+			utils.FileLog.Error("authorization查询用户信息失败")
+			cc.Ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
+			return
+		}
+		if session == nil {
+			utils.FileLog.Error("会话不存在")
+			cc.Ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
+			return
+		}
+		//校验token是否合法
+		// JWT校验Token和Account
+		account := utils.MD5(session.UserName)
+		if !utils.CheckToken(account, token) {
+			utils.FileLog.Error("authorization校验不合法")
+			cc.Ctx.ResponseWriter.WriteHeader(http.StatusUnauthorized)
+			return
+		}
+		if time.Now().After(session.ExpiredTime) {
+			utils.FileLog.Error("authorization过期法")
+			cc.Ctx.ResponseWriter.WriteHeader(http.StatusUnauthorized)
+			return
+		}
+		admin, err := system.GetSysUserById(session.SysUserId)
+		if err != nil {
+			if utils.IsErrNoRow(err) {
+				utils.FileLog.Error("权限不够")
+				cc.Ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
+				return
+			}
+			utils.FileLog.Error("获取用户信息失败")
+			cc.Ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
+			return
+		}
+		if admin == nil {
+			utils.FileLog.Error("权限不够")
+			cc.Ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
+			return
+		}
+		//如果不是启用状态
+		if admin.Enabled != 1 {
+			utils.FileLog.Error("用户被禁用")
+			cc.Ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
+			return
+		}
+
+		//接口权限校验
+		roleId := admin.RoleId
+		list, e := system.GetMenuButtonApisByRoleId(roleId)
+		if e != nil {
+			utils.FileLog.Error("接口权限查询出错", e)
+			cc.Ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
+			return
+		}
+		var api string
+		for _, v := range list {
+			if v.Api != "" {
+				api += v.Api + "&"
+			}
+		}
+		api += "&" + models.BusinessConfMap["PublicApi"]
+		//处理uri请求,去除前缀和参数
+		api = strings.TrimRight(api, "&")
+		uri = strings.Replace(uri, "/adminapi", "", 1)
+		uris := strings.Split(uri, "?")
+		uri = uris[0]
+		//fmt.Println("uri:", uri)
+		apis := strings.Split(api, "&")
+		apiMap := make(map[string]bool, 0)
+		for _, s := range apis {
+			apiMap[s] = true
+		}
+		if !apiMap[uri] {
+			utils.FileLog.Error("用户无权访问")
+			cc.Ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
+			return
+		}
+		cc.SysUser = admin
+	} else {
+		utils.FileLog.Error("请求方法类型错误")
+		cc.Ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
+		return
+	}
+}
+
+// ChatConnect @Title 知识库问答创建对话连接
+// @Description 知识库问答创建对话连接
+// @Success 101 {object} response.ListResp
+// @router /chat/connect [get]
+func (cc *ChatWsController) ChatConnect() {
+	if !ws.Allow(cc.SysUser.AdminId, ws.CONNECT_LIMITER) {
+		utils.FileLog.Error("WebSocket连接太频繁,主动拒绝链接")
+		cc.Ctx.ResponseWriter.WriteHeader(http.StatusTooManyRequests)
+		return
+	}
+	wsCon, err := webSocketHandler(cc.Ctx.ResponseWriter, cc.Ctx.Request)
+	if err != nil {
+		utils.FileLog.Error("WebSocket连接失败:", err)
+		cc.Ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
+		return
+	}
+	facade.AddSession(cc.SysUser.AdminId, wsCon)
+}
+
+// upGrader 用于将HTTP连接升级为WebSocket连接
+var upGrader = websocket.Upgrader{
+	ReadBufferSize:  1024,
+	WriteBufferSize: 1024,
+	CheckOrigin: func(r *http.Request) bool {
+		return true
+	},
+}
+
+// WebSocketHandler 处理WebSocket连接
+func webSocketHandler(w http.ResponseWriter, r *http.Request) (conn *websocket.Conn, err error) {
+	conn, err = upGrader.Upgrade(w, r, nil)
+	if err != nil {
+		utils.FileLog.Error("升级协议失败:WebSocket:%s", err.Error())
+		return
+	}
+	// 获取底层 TCP 连接并设置保活
+	if tcpConn, ok := conn.NetConn().(*net.TCPConn); ok {
+		_ = tcpConn.SetKeepAlive(true)
+		_ = tcpConn.SetKeepAlivePeriod(ws.TcpTimeout)
+		utils.FileLog.Info("TCP KeepAlive 已启用")
+	}
+	_ = conn.SetReadDeadline(time.Now().Add(ws.ReadTimeout))
+	return
+}

+ 51 - 0
controllers/llm/kb_controller.go

@@ -0,0 +1,51 @@
+package llm
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/services/llm/facade"
+)
+
+type KbController struct {
+	controllers.BaseAuthController
+}
+
+// SearchDocs  @Title 搜索知识库文档
+// @Description 搜索知识库文档
+// @Success 101 {object} response.ListResp
+// @router /knowledge_base/searchDocs [post]
+func (kbctrl *KbController) SearchDocs() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		if br.ErrMsg == "" {
+			br.IsSendEmail = false
+		}
+		kbctrl.Data["json"] = br
+		kbctrl.ServeJSON()
+	}()
+	sysUser := kbctrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req facade.LLMKnowledgeSearch
+	err := json.Unmarshal(kbctrl.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	searchResp, err := facade.LLMKnowledgeBaseSearchDocs(req)
+	if err != nil {
+		br.Msg = "搜索知识库失败"
+		br.ErrMsg = "搜索知识库失败:" + err.Error()
+		return
+	}
+	br.Data = searchResp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}

+ 39 - 0
controllers/llm/llm_http/request.go

@@ -0,0 +1,39 @@
+package llm_http
+
+import "encoding/json"
+
+type LLMQuestionReq struct {
+	Question      string `description:"提问"`
+	KnowledgeBase string `description:"知识库"`
+	SessionId     string `description:"会话ID"`
+}
+
+type UserChatReq struct {
+	ChatId    int    `json:"ChatId"`
+	ChatTitle string `json:"ChatTitle" description:"会话名称"`
+}
+
+type UserChatRecordReq struct {
+	Id           int    `json:"Id"`
+	ChatId       int    `json:"ChatId"`
+	Content      string `json:"Content" description:"会话名称"`
+	ChatUserType string `json:"ChatUserType" description:"用户类型"`
+	SendTime     string `json:"SendTime" description:"发送时间"`
+}
+
+type GenerateContentReq struct {
+	WechatArticleId int    `json:"WechatArticleId" description:"公众号Id"`
+	Promote         string `json:"Promote" description:"提示词"`
+}
+type SaveContentReq struct {
+	WechatArticleId int             `json:"WechatArticleId" description:"公众号Id"`
+	Title           string          `json:"Title" description:"标题"`
+	Promote         json.RawMessage `json:"Promote" description:"提示词"`
+	AigcContent     json.RawMessage `json:"AigcContent" description:"生成内容"`
+}
+type DeleteContentReq struct {
+	RecordId int
+}
+type ContentListReq struct {
+	WechatArticleId int
+}

+ 28 - 0
controllers/llm/llm_http/response.go

@@ -0,0 +1,28 @@
+package llm_http
+
+import "eta/eta_api/models/llm"
+
+type UserChatListResp struct {
+	TodayList     []llm.UserLlmChatListViewItem
+	YesterdayList []llm.UserLlmChatListViewItem
+	WeekList      []llm.UserLlmChatListViewItem
+}
+type UserChatResp struct {
+	ChatId    int
+	ChatTitle string
+	SendTime  string
+}
+type UserChatAddResp struct {
+	SendTime string
+}
+
+type AIGCResp struct {
+	Promote Content
+	Answer Content
+}
+
+type Content struct {
+	Role     string
+	Content  string
+	SendTime string
+}

+ 294 - 0
controllers/llm/promote_controller.go

@@ -0,0 +1,294 @@
+package llm
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/controllers/llm/llm_http"
+	"eta/eta_api/models"
+	"eta/eta_api/models/rag"
+	"eta/eta_api/services/llm/facade"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+type PromoteController struct {
+	controllers.BaseAuthController
+}
+
+// GenerateContent @Title 生成问答内容
+// @Description 生成问答内容
+// @Success 101 {object} response.ListResp
+// @router /promote/generate_content [post]
+func (pCtrl *PromoteController) GenerateContent() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		pCtrl.Data["json"] = br
+		pCtrl.ServeJSON()
+	}()
+	var gcReq llm_http.GenerateContentReq
+	err := json.Unmarshal(pCtrl.Ctx.Input.RequestBody, &gcReq)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	sysUser := pCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	if gcReq.Promote == "" {
+		br.Msg = "提示词不能为空"
+		br.ErrMsg = "提示词不能为空"
+		return
+	}
+	if gcReq.WechatArticleId <= 0 {
+		br.Msg = "公众号文章编号非法"
+		br.ErrMsg = "公众号文章编号非法"
+		return
+	}
+	userSendTime := time.Now()
+	userContent := llm_http.Content{
+		Content:  gcReq.Promote,
+		Role:     "user",
+		SendTime: userSendTime.Format(utils.FormatDateTime),
+	}
+	article, err := rag.GetArticleById(gcReq.WechatArticleId)
+	if err != nil {
+		br.Msg = "获取公众号内容失败"
+		br.ErrMsg = "获取公众号内容失败,Err:" + err.Error()
+		return
+	}
+	if article.TextContent == "" {
+		br.Msg = "暂不支持纯文本以外的内容生成"
+		br.ErrMsg = "暂不支持纯文本以外的内容生成"
+		return
+	}
+	res, err := facade.AIGCBaseOnPromote(facade.AIGC{
+		Promote:   gcReq.Promote,
+		ArticleId: gcReq.WechatArticleId,
+	})
+	if err != nil {
+		br.Msg = err.Error()
+		br.ErrMsg = "内容生成失败,Err:" + err.Error()
+		return
+	}
+	aiSendTime := time.Now()
+	aiContent := llm_http.Content{
+		Content:  res.Answer,
+		Role:     "assistant",
+		SendTime: aiSendTime.Format(utils.FormatDateTime),
+	}
+	br.Data = llm_http.AIGCResp{
+		Promote: userContent,
+		Answer:  aiContent,
+	}
+	saveContentReq := rag.PromoteTrainRecord{
+		WechatArticleId: gcReq.WechatArticleId,
+		Title:           userContent.Content,
+		AigcContent:     res.Answer,
+		AigcSendTime:    aiSendTime,
+		TemplatePromote: userContent.Content,
+		PromoteSendTime: userSendTime,
+		CreatedTime:     time.Now(),
+	}
+	err = saveContentReq.SaveContent()
+	if err != nil {
+		br.Msg = "保存内容失败"
+		br.ErrMsg = "保存内容失败,err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "内容生成成功"
+}
+
+// SavePromoteContent @Title 保存问答内容
+// @Description 生成问答内容
+// @Success 101 {object} response.ListResp
+// @router /promote/save_content [post]
+func (pCtrl *PromoteController) SavePromoteContent() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		pCtrl.Data["json"] = br
+		pCtrl.ServeJSON()
+	}()
+	var gcReq llm_http.SaveContentReq
+	err := json.Unmarshal(pCtrl.Ctx.Input.RequestBody, &gcReq)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	sysUser := pCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	if gcReq.Promote == nil {
+		br.Msg = "提示词内容不能为空"
+		br.ErrMsg = "提示词不能为空"
+		return
+	}
+	if gcReq.AigcContent == nil {
+		br.Msg = "回答内容不能为空"
+		br.ErrMsg = "提示词不能为空"
+		return
+	}
+	if gcReq.WechatArticleId <= 0 {
+		br.Msg = "公众号文章编号非法"
+		br.ErrMsg = "公众号文章编号非法"
+		return
+	}
+	var userContent, assistantContent llm_http.Content
+	parseErr := json.Unmarshal(gcReq.AigcContent, &assistantContent)
+	if parseErr != nil {
+		br.Msg = "内容参数解析异常!"
+		br.ErrMsg = "内容参数解析异,err" + parseErr.Error()
+		return
+	}
+	parseErr = json.Unmarshal(gcReq.Promote, &userContent)
+	if parseErr != nil {
+		br.Msg = "内容参数解析异常!"
+		br.ErrMsg = "内容参数解析异,err" + parseErr.Error()
+		return
+	}
+	var titile string
+	if gcReq.Title != "" {
+		titile = gcReq.Title
+	} else {
+		titile = userContent.Content
+	}
+	var userSendTime, assistantSendTime time.Time
+	if userContent.SendTime != "" {
+		userSendTime, parseErr = time.ParseInLocation(utils.FormatDateTime, userContent.SendTime, time.Local)
+		if parseErr != nil {
+			br.Msg = "用户发送时间解析异常!"
+			br.ErrMsg = "用户发送时间解析异常,err" + parseErr.Error()
+			return
+		}
+	} else {
+		br.Msg = "用户发送时间不能为空!"
+		br.ErrMsg = "用户发送时间不能为空"
+		return
+	}
+	if assistantContent.SendTime != "" {
+		assistantSendTime, parseErr = time.ParseInLocation(utils.FormatDateTime, assistantContent.SendTime, time.Local)
+		if parseErr != nil {
+			br.Msg = "AI生成时间解析异常!"
+			br.ErrMsg = "AI生成时间解析异常,err" + parseErr.Error()
+			return
+		}
+	} else {
+		br.Msg = "AI生成时间不能为空!"
+		br.ErrMsg = "AI生成时间不能为空"
+		return
+	}
+	saveContentReq := rag.PromoteTrainRecord{
+		WechatArticleId: gcReq.WechatArticleId,
+		Title:           titile,
+		AigcContent:     assistantContent.Content,
+		AigcSendTime:    assistantSendTime,
+		TemplatePromote: userContent.Content,
+		PromoteSendTime: userSendTime,
+		CreatedTime:     time.Now(),
+	}
+	err = saveContentReq.SaveContent()
+	if err != nil {
+		br.Msg = "保存内容失败"
+		br.ErrMsg = "保存内容失败,err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存内容成功"
+}
+
+// DeletePromoteContent @Title 删除问答内容
+// @Description 生成问答内容
+// @Success 101 {object} response.ListResp
+// @router /promote/delete_content [post]
+func (pCtrl *PromoteController) DeletePromoteContent() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		pCtrl.Data["json"] = br
+		pCtrl.ServeJSON()
+	}()
+	var gcReq llm_http.DeleteContentReq
+	err := json.Unmarshal(pCtrl.Ctx.Input.RequestBody, &gcReq)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	sysUser := pCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	if gcReq.RecordId <= 0 {
+		br.Msg = "记录编号非法"
+		br.ErrMsg = "记录编号非法"
+		return
+	}
+
+	err = rag.DeleteContent(gcReq.RecordId)
+	if err != nil {
+		br.Msg = "删除内容失败"
+		br.ErrMsg = "删除内容失败,err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "删除内容成功"
+}
+
+// PromoteContentList @Title 删除问答内容
+// @Description 生成问答内容
+// @Success 101 {object} response.ListResp
+// @router /promote/content_list [get]
+func (pCtrl *PromoteController) PromoteContentList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		pCtrl.Data["json"] = br
+		pCtrl.ServeJSON()
+	}()
+	wechatArticleId, _ := pCtrl.GetInt("WechatArticleId")
+	sysUser := pCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+
+	if wechatArticleId <= 0 {
+		br.Msg = "记录编号非法"
+		br.ErrMsg = "记录编号非法"
+		return
+	}
+
+	list, err := rag.GetRecordList(wechatArticleId)
+	if err != nil {
+		br.Msg = "查询列表失败"
+		br.ErrMsg = "查询列表失败,err:" + err.Error()
+		return
+	}
+	total := len(list)
+	for _, item := range list {
+		item.Title = fmt.Sprintf("%d.%s", total, item.Title)
+		total--
+	}
+	br.Data = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "查询列表成功"
+}

+ 241 - 0
controllers/llm/question.go

@@ -0,0 +1,241 @@
+package llm
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/rag"
+	"eta/eta_api/models/rag/request"
+	"eta/eta_api/models/rag/response"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"strings"
+	"time"
+)
+
+// QuestionController
+// @Description: 问题库管理
+type QuestionController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title 列表
+// @Description 列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Success 200 {object} []*rag.QuestionListListResp
+// @router /question/list [get]
+func (c *QuestionController) List() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	keyWord := c.GetString("KeyWord")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	var condition string
+	var pars []interface{}
+
+	if keyWord != "" {
+		condition += fmt.Sprintf(` AND %s like ?`, rag.QuestionColumns.QuestionContent)
+		pars = append(pars, `%`+keyWord+`%`)
+	}
+
+	obj := new(rag.Question)
+	total, list, err := obj.GetPageListByCondition(condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	viewList := obj.ListToViewList(list)
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := response.QuestionListListResp{
+		List:   viewList,
+		Paging: page,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Add
+// @Title 新增问题
+// @Description 新增问题
+// @Param	request	body request.AddQuestionReq true "type json string"
+// @Success 200 Ret=200 新增成功
+// @router /question/add [post]
+func (c *QuestionController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.AddQuestionReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.Content = strings.TrimSpace(req.Content)
+	if req.Content == "" {
+		br.Msg = "请输入问题"
+		br.IsSendEmail = false
+		return
+	}
+	obj := rag.Question{}
+	_, err = obj.GetByCondition(` AND question_content = ? `, []interface{}{req.Content})
+	if err == nil {
+		br.Msg = "问题已入库,请不要重复添加"
+		br.IsSendEmail = false
+		return
+	}
+
+	item := &rag.Question{
+		QuestionId:      0,
+		QuestionContent: req.Content,
+		Sort:            0,
+		ModifyTime:      time.Now(),
+		CreateTime:      time.Now(),
+	}
+	err = item.Create()
+	if err != nil {
+		br.Msg = "添加失败"
+		br.ErrMsg = "添加失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `添加成功`
+}
+
+// Edit
+// @Title 编辑问题
+// @Description 编辑问题
+// @Param	request	body request.EditQuestionReq true "type json string"
+// @Success 200 Ret=200 新增成功
+// @router /question/edit [post]
+func (c *QuestionController) Edit() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.EditQuestionReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.QuestionId <= 0 {
+		br.Msg = "问题id不能为空"
+		br.IsSendEmail = false
+		return
+	}
+	req.Content = strings.TrimSpace(req.Content)
+	if req.Content == "" {
+		br.Msg = "请输入问题"
+		br.IsSendEmail = false
+		return
+	}
+
+	obj := rag.Question{}
+	item, err := obj.GetByID(req.QuestionId)
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+		if utils.IsErrNoRow(err) {
+			br.Msg = "问题不存在"
+			br.IsSendEmail = false
+		}
+		return
+	}
+	item.QuestionContent = req.Content
+	item.ModifyTime = time.Now()
+	err = item.Update([]string{"question_content", "modify_time"})
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `添加成功`
+}
+
+// Del
+// @Title 删除问题
+// @Description 删除问题
+// @Param	request	body request.EditQuestionReq true "type json string"
+// @Success 200 Ret=200 新增成功
+// @router /question/del [post]
+func (c *QuestionController) Del() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.EditQuestionReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.QuestionId <= 0 {
+		br.Msg = "问题id不能为空"
+		br.IsSendEmail = false
+		return
+	}
+
+	obj := rag.Question{}
+	item, err := obj.GetByID(req.QuestionId)
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+		if utils.IsErrNoRow(err) {
+			br.Msg = "问题不存在"
+			br.IsSendEmail = false
+		}
+		return
+	}
+	err = item.Del()
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `删除成功`
+}

+ 334 - 0
controllers/llm/user_chat_controller.go

@@ -0,0 +1,334 @@
+package llm
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/controllers/llm/llm_http"
+	"eta/eta_api/models"
+	"eta/eta_api/models/llm"
+	llmService "eta/eta_api/services/llm"
+	"eta/eta_api/utils"
+	"time"
+)
+
+type UserChatController struct {
+	controllers.BaseAuthController
+}
+
+// NewChat @Title 新建对话框
+// @Description 新建对话框
+// @Success 101 {object} response.ListResp
+// @router /chat/new_chat [post]
+func (ucCtrl *UserChatController) NewChat() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		ucCtrl.Data["json"] = br
+		ucCtrl.ServeJSON()
+	}()
+	var req llm_http.UserChatReq
+	err := json.Unmarshal(ucCtrl.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	sysUser := ucCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	if req.ChatTitle == "" {
+		req.ChatTitle = "新会话"
+	}
+	session := llm.UserLlmChat{
+		UserId:      sysUser.AdminId,
+		CreatedTime: time.Now(),
+		ChatTitle:   req.ChatTitle,
+	}
+	var chatResp = new(llm_http.UserChatResp)
+	chatResp.ChatTitle = req.ChatTitle
+	chatResp.ChatId, err = session.CreateChatSession()
+	chatResp.SendTime = time.Now().Format(utils.FormatDateTime)
+	if err != nil {
+		br.Msg = "创建失败"
+		br.ErrMsg = "创建失败,Err:" + err.Error()
+		return
+	}
+	_ = llmService.AddChatRecord(&llm.UserChatRecordRedis{
+		ChatId:       session.Id,
+		ChatUserType: "user",
+		Content:      req.ChatTitle,
+		SendTime:     chatResp.SendTime,
+	})
+	br.Data = chatResp
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "创建成功"
+}
+
+// RenameChat @Title 新建对话框
+// @Description 新建对话框
+// @Success 101 {object} response.ListResp
+// @router /chat/rename_chat [post]
+func (ucCtrl *UserChatController) RenameChat() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		ucCtrl.Data["json"] = br
+		ucCtrl.ServeJSON()
+	}()
+	var req llm_http.UserChatReq
+	err := json.Unmarshal(ucCtrl.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	sysUser := ucCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	if req.ChatId <= 0 {
+		br.Msg = "非法的对话框Id"
+		br.ErrMsg = "非法的对话框Id"
+		return
+	}
+	if req.ChatTitle == "" {
+		br.Msg = "重命名不能为空"
+		br.ErrMsg = "重命名不能为空"
+		return
+	}
+	session := llm.UserLlmChat{
+		Id:         req.ChatId,
+		UpdateTime: time.Now(),
+		UserId:     sysUser.AdminId,
+		ChatTitle:  req.ChatTitle,
+	}
+	err = session.RenameChatSession()
+	if err != nil {
+		br.Msg = "重命名失败"
+		br.ErrMsg = "重命名失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "重命名成功"
+}
+
+// DeleteChat @Title 删除对话框
+// @Description 删除对话框
+// @Success 101 {object} response.ListResp
+// @router /chat/delete_chat [post]
+func (ucCtrl *UserChatController) DeleteChat() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		ucCtrl.Data["json"] = br
+		ucCtrl.ServeJSON()
+	}()
+	var req llm_http.UserChatReq
+	err := json.Unmarshal(ucCtrl.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	sysUser := ucCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	if req.ChatId <= 0 {
+		br.Msg = "非法的对话框Id"
+		br.ErrMsg = "非法的对话框Id"
+		return
+	}
+
+	session := llm.UserLlmChat{
+		Id:         req.ChatId,
+		UpdateTime: time.Now(),
+		IsDeleted:  1,
+	}
+	err = session.DeleteChatSession()
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "删除成功"
+}
+
+// GetUserChatList @Title 获取用户对话框列表
+// @Description  获取用户对话框列表
+// @Success 101 {object} response.ListResp
+// @router /chat/user_chat_list [get]
+func (ucCtrl *UserChatController) GetUserChatList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		ucCtrl.Data["json"] = br
+		ucCtrl.ServeJSON()
+	}()
+	sysUser := ucCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	//周日是0,周六是6
+	weekDay := time.Now().Weekday()
+	offset := int(time.Monday - weekDay)
+	if offset > 0 {
+		offset -= 7
+	}
+	today := time.Now().Format(utils.FormatDate)
+	monDay := time.Now().AddDate(0, 0, offset).Format(utils.FormatDate)
+	yesterday := time.Now().AddDate(0, 0, -1).Format(utils.FormatDate)
+	chatList, err := llm.GetUserChatList(sysUser.AdminId, monDay, time.Now().Format(utils.FormatDate))
+	if err != nil {
+		br.Msg = "获取用户聊天列表失败"
+		br.ErrMsg = "获取用户聊天列表失败,Err:" + err.Error()
+		return
+	}
+	data := new(llm_http.UserChatListResp)
+	data.WeekList = make([]llm.UserLlmChatListViewItem, 0)
+	data.YesterdayList = make([]llm.UserLlmChatListViewItem, 0)
+	data.TodayList = make([]llm.UserLlmChatListViewItem, 0)
+	for _, v := range chatList {
+		list, _ := llmService.GetChatRecordsFromRedis(v.Id)
+		item := llm.CovertItemToView(v)
+		item.RecordCount = len(list)
+		if v.CreatedTime.Format(utils.FormatDate) == today {
+			data.TodayList = append(data.TodayList, item)
+		} else if v.CreatedTime.Format(utils.FormatDate) == yesterday {
+			data.YesterdayList = append(data.YesterdayList, item)
+		} else {
+			data.WeekList = append(data.WeekList, item)
+		}
+	}
+
+	br.Data = data
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取用户聊天列表成功"
+}
+
+// ChatRecordAdd @Title 保存聊天记录
+// @Description 保存聊天记录
+// @Success 101 {object} response.ListResp
+// @router /chat/chat_record_save [post]
+func (ucCtrl *UserChatController) ChatRecordAdd() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		ucCtrl.Data["json"] = br
+		ucCtrl.ServeJSON()
+	}()
+	var req llm_http.UserChatRecordReq
+	err := json.Unmarshal(ucCtrl.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	sysUser := ucCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	if req.ChatId <= 0 {
+		br.Msg = "非法的对话框Id"
+		br.ErrMsg = "非法的对话框Id"
+		return
+	}
+	if req.Content == "" {
+		br.Msg = "聊天记录不能为空"
+		br.ErrMsg = "聊天记录不能为空"
+		return
+	}
+	if req.Id < 0 {
+		br.Msg = "非法的Id"
+		br.ErrMsg = "非法的Id"
+		return
+	}
+	if req.ChatUserType != "user" && req.ChatUserType != "assistant" {
+		br.Msg = "非法的用户类型"
+		br.ErrMsg = "非法的用户类型,用户类型支持:user/assistant"
+		return
+	}
+	if req.SendTime == "" {
+		req.SendTime = time.Now().Format(utils.FormatDateTime)
+	} else {
+		_, err = time.Parse(utils.FormatDateTime, req.SendTime)
+		if err != nil {
+			br.Msg = "非法的发送时间"
+			br.ErrMsg = "非法的发送时间,Err:" + err.Error()
+			return
+		}
+	}
+	record := llm.UserChatRecordRedis{
+		ChatId:       req.ChatId,
+		ChatUserType: req.ChatUserType,
+		Content:      req.Content,
+		SendTime:     req.SendTime,
+	}
+	err = llmService.AddChatRecord(&record)
+	if err != nil {
+		br.Msg = "添加聊天记录失败"
+		br.ErrMsg = "添加聊天记录失败,Err:" + err.Error()
+		return
+	}
+
+	br.Data = llm_http.UserChatAddResp{
+		SendTime: record.SendTime,
+	}
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "添加聊天记录成功"
+}
+
+// ChatRecordList @Title 获取聊天记录
+// @Description 获取聊天记录
+// @Success 101 {object} response.ListResp
+// @router /chat/chat_record_list [get]
+func (ucCtrl *UserChatController) ChatRecordList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		ucCtrl.Data["json"] = br
+		ucCtrl.ServeJSON()
+	}()
+	sysUser := ucCtrl.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	chatId, _ := ucCtrl.GetInt("ChatId", 0)
+	if chatId <= 0 {
+		br.Msg = "非法的对话Id"
+		br.ErrMsg = "非法的对话Id"
+		return
+	}
+
+	list, err := llmService.GetChatRecordsFromRedis(chatId)
+	if err != nil {
+		br.Msg = "获取聊天记录失败"
+		br.ErrMsg = "获取聊天记录失败,Err:" + err.Error()
+		return
+	}
+	br.Data = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取聊天记录成功"
+}
+
+

+ 804 - 0
controllers/llm/wechat_platform.go

@@ -0,0 +1,804 @@
+package llm
+
+import (
+	"encoding/json"
+	"eta/eta_api/cache"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/rag"
+	"eta/eta_api/models/rag/request"
+	"eta/eta_api/models/rag/response"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services"
+	"eta/eta_api/services/elastic"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
+	"html"
+	"strings"
+	"time"
+)
+
+// WechatPlatformController
+// @Description: 微信公众号管理
+type WechatPlatformController struct {
+	controllers.BaseAuthController
+}
+
+// TagList
+// @Title 获取ppt列表
+// @Description 获取ppt列表接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Success 200 {object} models.TagListResp
+// @router /tag/list [get]
+func (c *WechatPlatformController) TagList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	keyWord := c.GetString("KeyWord")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	var condition string
+	var pars []interface{}
+
+	if keyWord != "" {
+		condition += fmt.Sprintf(` AND %s like ?`, rag.WechatPlatformColumns.Nickname)
+		pars = append(pars, `%`+keyWord+`%`)
+	}
+
+	obj := new(rag.Tag)
+	total, list, err := obj.GetPageListByCondition(condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := new(response.TagListResp)
+	resp.Paging = page
+	resp.List = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// Add
+// @Title 新增公众号
+// @Description 新增公众号
+// @Param	request	body request.AddWechatPlatformReq true "type json string"
+// @Success 200 Ret=200 新增成功
+// @router /wechat_platform/add [post]
+func (c *WechatPlatformController) Add() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.AddWechatPlatformReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	req.Name = strings.TrimSpace(req.Name)
+	if req.Name == "" {
+		br.Msg = "请输入公众号名称"
+		br.IsSendEmail = false
+		return
+	}
+	req.Link = strings.TrimSpace(req.Link)
+	if req.Link == "" {
+		br.Msg = "请输入文章链接"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 名称校验
+	{
+		var condition string
+		var pars []interface{}
+		condition += fmt.Sprintf(` AND %s = ?`, rag.WechatPlatformColumns.Nickname)
+		pars = append(pars, req.Name)
+		obj := new(rag.WechatPlatform)
+		item, err := obj.GetByCondition(condition, pars)
+		if err != nil && !utils.IsErrNoRow(err) {
+			br.Msg = "公众号信息获取失败"
+			br.ErrMsg = "公众号信息获取失败,Err:" + err.Error()
+			return
+		}
+
+		if item.WechatPlatformId > 0 {
+			br.Msg = "公众号名称重复"
+			br.IsSendEmail = false
+			return
+		}
+	}
+
+	// 链接校验
+	{
+		var condition string
+		var pars []interface{}
+		condition += fmt.Sprintf(` AND %s = ?`, rag.WechatPlatformColumns.ArticleLink)
+		pars = append(pars, req.Link)
+		obj := new(rag.WechatPlatform)
+		item, err := obj.GetByCondition(condition, pars)
+		if err != nil && !utils.IsErrNoRow(err) {
+			br.Msg = "公众号信息获取失败"
+			br.ErrMsg = "公众号信息获取失败,Err:" + err.Error()
+			return
+		}
+
+		if item.WechatPlatformId > 0 {
+			br.Msg = "公众号链接重复"
+			br.IsSendEmail = false
+			return
+		}
+	}
+
+	item := &rag.WechatPlatform{
+		WechatPlatformId: 0,
+		FakeId:           "",
+		Nickname:         req.Name,
+		Alias:            "",
+		RoundHeadImg:     "",
+		ServiceType:      0,
+		Signature:        "",
+		Verified:         0,
+		ArticleLink:      req.Link,
+		Enabled:          0,
+		SysUserId:        c.SysUser.AdminId,
+		ModifyTime:       time.Now(),
+		CreateTime:       time.Now(),
+	}
+	err = item.Add(req.TagIdList)
+	if err != nil {
+		br.Msg = "添加失败"
+		br.ErrMsg = "添加失败,Err:" + err.Error()
+		return
+	}
+
+	// 异步新增公众号
+	go cache.AddWechatArticleOpToCache(item.WechatPlatformId, `add`)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `添加成功`
+}
+
+// FollowList
+// @Title 我关注的接口
+// @Description 我关注的接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Success 200 {object} []*rag.WechatPlatform
+// @router /wechat_platform/list/follow [get]
+func (c *WechatPlatformController) FollowList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	keyWord := c.GetString("KeyWord")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	var condition string
+	var pars []interface{}
+
+	if keyWord != "" {
+		condition += fmt.Sprintf(` AND %s like ?`, rag.WechatPlatformColumns.Nickname)
+		pars = append(pars, `%`+keyWord+`%`)
+	}
+
+	condition += fmt.Sprintf(` AND b.%s = ?`, rag.WechatPlatformUserMappingColumns.SysUserID)
+	pars = append(pars, c.SysUser.AdminId)
+
+	obj := new(rag.WechatPlatformUserMapping)
+	list, err := obj.GetListByCondition(condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// PublicList
+// @Title 公共列表
+// @Description 公共列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Success 200 {object} models.WechatPlatformListResp
+// @router /wechat_platform/list/public [get]
+func (c *WechatPlatformController) PublicList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	keyWord := c.GetString("KeyWord")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	var condition string
+	var pars []interface{}
+
+	if keyWord != "" {
+		condition += fmt.Sprintf(` AND %s like ?`, rag.WechatPlatformColumns.Nickname)
+		pars = append(pars, `%`+keyWord+`%`)
+	}
+
+	obj := new(rag.WechatPlatformUserMapping)
+	list, err := obj.GetListByCondition(condition, pars, startSize, 100000)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	resp := make([]response.WechatPlatformPublicListResp, 0)
+
+	if list != nil && len(list) > 0 {
+		userIdList := make([]int, 0)
+		uerIdMap := make(map[int]bool)
+		userFollowIndexMap := make(map[int]int)
+		for _, v := range list {
+			if _, ok := uerIdMap[v.FollowUserId]; !ok {
+				userIdList = append(userIdList, v.FollowUserId)
+				uerIdMap[v.FollowUserId] = true
+			}
+
+			index, ok := userFollowIndexMap[v.FollowUserId]
+			if !ok {
+				userFollowIndexMap[v.FollowUserId] = len(resp)
+
+				resp = append(resp, response.WechatPlatformPublicListResp{
+					UserId: v.FollowUserId,
+					List:   []*rag.UserFollowWechatPlatform{v},
+				})
+			} else {
+				resp[index].List = append(resp[index].List, v)
+			}
+		}
+
+		userList, err := system.GetAdminListByIdList(userIdList)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+		userNameMap := make(map[int]*system.Admin)
+		for _, v := range userList {
+			userNameMap[v.AdminId] = v
+		}
+
+		for k, v := range resp {
+			userInfo, ok := userNameMap[v.UserId]
+			if !ok {
+				continue
+			}
+			resp[k].Name = userInfo.RealName + `关注`
+		}
+
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// PublicList
+// @Title 公共列表
+// @Description 公共列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Success 200 {object} models.WechatPlatformListResp
+// @router /wechat_platform/op [post]
+func (c *WechatPlatformController) Op() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+
+	var req request.OpWechatPlatformReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.WechatPlatformId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+
+	if req.Status < 0 || req.Status > 1 {
+		br.Msg = "参数错误"
+		return
+	}
+	obj := rag.WechatPlatform{}
+	wechatPlatform, err := obj.GetById(req.WechatPlatformId)
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,Err:" + err.Error()
+		if utils.IsErrNoRow(err) {
+			br.Msg = "公众号不存在"
+			br.IsSendEmail = false
+		}
+		return
+	}
+	wechatPlatform.Enabled = req.Status
+	wechatPlatform.ModifyTime = time.Now()
+	err = wechatPlatform.Update([]string{"enabled", `modify_time`})
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,Err:" + err.Error()
+		return
+	}
+
+	// 异步处理公众号下面的文章
+	go services.AddOrEditEsWechatPlatformId(wechatPlatform.WechatPlatformId)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "修改成功"
+}
+
+// Refresh
+// @Title 公共列表
+// @Description 公共列表
+// @Success 200 {object} models.WechatPlatformListResp
+// @router /wechat_platform/refresh [post]
+func (c *WechatPlatformController) Refresh() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+
+	var req request.RefreshWechatPlatformReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if req.WechatPlatformId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+
+	obj := rag.WechatPlatform{}
+	wechatPlatform, err := obj.GetById(req.WechatPlatformId)
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "修改失败,Err:" + err.Error()
+		if utils.IsErrNoRow(err) {
+			br.Msg = "公众号不存在"
+			br.IsSendEmail = false
+		}
+		return
+	}
+	if wechatPlatform.FakeId != `` {
+		br.Msg = "公众号已添加成功"
+		br.ErrMsg = "公众号已添加成功"
+		br.IsSendEmail = false
+		return
+	}
+
+	go cache.AddWechatArticleOpToCache(wechatPlatform.WechatPlatformId, `add`)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "刷新成功"
+}
+
+// ArticleList
+// @Title 我关注的接口
+// @Description 我关注的接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   WechatPlatformId   query   int  true       "微信公众号id"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Success 200 {object} []*rag.WechatPlatform
+// @router /wechat_platform/article/list [get]
+func (c *WechatPlatformController) ArticleList() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+	wechatPlatformId, _ := c.GetInt("WechatPlatformId")
+	keyWord := c.GetString("KeyWord")
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	//var condition string
+	//var pars []interface{}
+	//
+	//if keyWord != "" {
+	//	condition += fmt.Sprintf(` AND (b.%s like ? or a.%s like ? ) `, rag.WechatPlatformColumns.Nickname, rag.WechatArticleColumns.Title)
+	//	pars = append(pars, `%`+keyWord+`%`, `%`+keyWord+`%`)
+	//}
+	//
+	//if wechatPlatformId > 0 {
+	//	condition += fmt.Sprintf(` AND a.%s = ?`, rag.WechatArticleColumns.WechatPlatformID)
+	//	pars = append(pars, wechatPlatformId)
+	//}
+	//
+	//condition += fmt.Sprintf(` AND b.%s = ? `, rag.WechatPlatformColumns.Enabled)
+	//pars = append(pars, 1)
+
+	var total int
+	viewList := make([]rag.WechatArticleView, 0)
+
+	if keyWord == `` {
+		var condition string
+		var pars []interface{}
+
+		if keyWord != "" {
+			condition += fmt.Sprintf(` AND (b.%s like ? or a.%s like ? ) `, rag.WechatPlatformColumns.Nickname, rag.WechatArticleColumns.Title)
+			pars = append(pars, `%`+keyWord+`%`, `%`+keyWord+`%`)
+		}
+
+		if wechatPlatformId > 0 {
+			condition += fmt.Sprintf(` AND a.%s = ?`, rag.WechatArticleColumns.WechatPlatformID)
+			pars = append(pars, wechatPlatformId)
+		}
+
+		//condition += fmt.Sprintf(` AND b.%s = ? `, rag.WechatPlatformColumns.Enabled)
+		//pars = append(pars, 1)
+
+		obj := new(rag.WechatArticle)
+		tmpTotal, list, err := obj.GetPageListByPlatformCondition(condition, pars, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+		total = tmpTotal
+
+		if list != nil && len(list) > 0 {
+			viewList = obj.ArticleAndPlatformListToViewList(list)
+		}
+	} else {
+		sortMap := map[string]string{
+			//"ArticleCreateTime": "desc",
+			//"WechatArticleId":   "desc",
+		}
+		tmpTotal, list, err := elastic.WechatArticleEsSearch(keyWord, wechatPlatformId, startSize, pageSize, sortMap)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+		total = int(tmpTotal)
+		if list != nil && len(list) > 0 {
+			viewList = list[0].ArticleAndPlatformListToViewList(list)
+		}
+	}
+
+	// 标签处理
+	{
+		wechatPlateIdList := make([]int, 0)
+		for _, v := range viewList {
+			wechatPlateIdList = append(wechatPlateIdList, v.WechatPlatformId)
+		}
+		obj := rag.WechatPlatformTagMapping{}
+		mappingList, err := obj.GetWechatPlatformTagListByWechatPlatformIdList(wechatPlateIdList)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+		wechatPlatformIdMap := make(map[int]*rag.WechatPlatformTagItem)
+		for _, v := range mappingList {
+			wechatPlatformIdMap[v.WechatPlatformId] = v
+		}
+		for k, v := range viewList {
+			item, ok := wechatPlatformIdMap[v.WechatPlatformId]
+			if !ok {
+				continue
+			}
+			viewList[k].TagName = item.TagName
+			viewList[k].TagId = item.TagId
+		}
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := response.WechatArticleListListResp{
+		List:   viewList,
+		Paging: page,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// ArticleList
+// @Title 我关注的接口
+// @Description 我关注的接口
+// @Param   WechatArticleId   query   int  true       "文章id"
+// @Success 200 {object} []*rag.WechatArticle
+// @router /wechat_platform/article/detail [get]
+func (c *WechatPlatformController) ArticleDetail() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	wechatArticleId, _ := c.GetInt("WechatArticleId")
+	if wechatArticleId <= 0 {
+		br.Msg = "请选择文章"
+		br.IsSendEmail = false
+		return
+	}
+	obj := new(rag.WechatArticle)
+	item, err := obj.GetById(wechatArticleId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	if item.IsDeleted == 1 {
+		br.Msg = "文章已删除"
+		br.IsSendEmail = false
+		return
+	}
+	resp := item.ToView()
+	resp.Content = html.UnescapeString(item.Content)
+
+	// 获取摘要信息
+	{
+		abstractObj := rag.WechatArticleAbstract{}
+		abstractItem, err := abstractObj.GetByWechatArticleId(wechatArticleId)
+		if err != nil && !utils.IsErrNoRow(err) {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+		resp.Abstract = abstractItem.Content
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// ArticleList
+// @Title 我关注的接口
+// @Description 我关注的接口
+// @Param   WechatArticleId   query   int  true       "文章id"
+// @Success 200 {object} []*rag.WechatPlatform
+// @router /wechat_platform/article/del [get]
+func (c *WechatPlatformController) ArticleDel() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	sysUser := c.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		return
+	}
+	wechatArticleId, _ := c.GetInt("WechatArticleId")
+	if wechatArticleId <= 0 {
+		br.Msg = "请选择文章"
+		br.IsSendEmail = false
+		return
+	}
+	obj := new(rag.WechatArticle)
+	item, err := obj.GetById(wechatArticleId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	if item.IsDeleted == 1 {
+		br.Msg = "文章已删除"
+		br.IsSendEmail = false
+		return
+	}
+	item.IsDeleted = 1
+	err = item.Update([]string{"is_deleted"})
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	// 修改ES信息
+	services.AddOrEditEsWechatArticle(item.WechatArticleId)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "删除成功"
+}
+
+//func init() {
+//	//obj := rag.WechatPlatform{}
+//	//item, _ := obj.GetByID(2)
+//	//fmt.Println(llm.BeachAddWechatPlatform(item))
+//
+//	obj := rag.WechatArticle{}
+//	//item, _ := obj.GetById(30)
+//	list, _ := obj.GetListByCondition(``, ` `, []interface{}{}, 0, 1000)
+//	//llm.ArticleToTmpFile(item.TextContent)
+//	for _, item := range list {
+//		//llm.ArticleToKnowledge(item)
+//		llm.GenerateArticleAbstract(item)
+//	}
+//}
+
+//func init() {
+//	//obj := rag.WechatPlatform{}
+//	//item, _ := obj.GetByID(2)
+//	//fmt.Println(llm.BeachAddWechatPlatform(item))
+//
+//	//obj := rag.WechatArticle{}
+//	//list, _ := obj.GetListByCondition(`wechat_article_id,content`, `  `, []interface{}{}, 0, 1)
+//	////obj := rag.WechatPlatform{}
+//	////list, _ := obj.GetListByCondition(` AND wechat_platform_id !=1 `, []interface{}{}, 0, 100)
+//	////llm.ArticleToTmpFile(item.TextContent)
+//	//for _, item := range list {
+//	//	//llm.ArticleToKnowledge(item)
+//	//	services.ReGenerateArticleAbstract(item)
+//	//}
+//
+//	// 重新生成摘要
+//	{
+//		obj := rag.WechatArticle{}
+//		list, _ := obj.GetListByCondition(``, ` AND text_content !='' AND abstract_status = 0`, []interface{}{}, 0, 300)
+//		for _, item := range list {
+//			services.GenerateArticleAbstract(item)
+//		}
+//	}
+//
+//	//// 删除摘要向量库
+//	//{
+//	//	obj := rag.WechatArticleAbstract{}
+//	//	list, _ := obj.GetListByCondition(`vector_key,wechat_article_abstract_id`, ` AND wechat_article_id in (25,27) `, []interface{}{}, 0, 10000)
+//	//	fmt.Println(services.DelDoc(list))
+//	//}
+//
+//	fmt.Println("修复结束")
+//}
+
+//func init() {
+//	//// 微信文章加到es
+//	//{
+//	//	obj := rag.WechatArticle{}
+//	//	list, _ := obj.GetListByCondition(` wechat_article_id `, ` `, []interface{}{}, 0, 10000)
+//	//	total := len(list)
+//	//	for k, item := range list {
+//	//		fmt.Println(k, "/", total)
+//	//		services.AddOrEditEsWechatArticle(item.WechatArticleId)
+//	//	}
+//	//
+//	//	fmt.Println("结束了")
+//	//}
+//
+//	//// 微信文章加到es
+//	{
+//		obj := rag.WechatArticleAbstract{}
+//		list, _ := obj.GetListByCondition(` wechat_article_abstract_id `, ` `, []interface{}{}, 0, 10000)
+//		total := len(list)
+//		for k, item := range list {
+//			fmt.Println(k, "/", total)
+//			services.AddOrEditEsWechatArticleAbstract(item.WechatArticleAbstractId)
+//		}
+//
+//		fmt.Println("结束了")
+//	}
+//
+//}

+ 54 - 54
controllers/ppt_english.go

@@ -310,33 +310,33 @@ func (this *PptEnglishController) EditPpt() {
 		return
 	}
 
-	pptMap, err := ppt_english.GetPptMappingByPptId(req.PptId)
-	if err != nil {
-		br.Msg = `该PPT信息不存在, 保存失败`
-		br.ErrMsg = `该PPT信息不存在, 保存失败, Err` + err.Error()
-		br.IsSendEmail = false
-		return
-	}
-	pptMapList, err := ppt_english.GetPptMappingListByGroupIdDesc(pptMap.GroupId)
-	if err != nil {
-		br.ErrMsg = "PPT目录信息异常"
-		return
-	}
-	count, err := ppt_english.GetPptMappingCountByGroupPptId(pptMap.GroupPptId, this.SysUser.AdminId)
-	if err != nil {
-		br.Msg = "获取英文PPT和用户权限关系失败"
-		br.ErrMsg = "获取英文PPT和用户权限关系失败, Err:" + err.Error()
-		return
-	}
-	if !pptMap.IsMoved && len(pptMapList) > 1 && count > 0 {
-		// 如果没有人为移动位置, 默认将当前ppt置顶
-		err = ppt.MoveGroupPptEnglish(pptMap.GroupId, pptMap.GroupPptId, pptMapList[0].GroupPptId, 0, this.SysUser.AdminId)
-		if err != nil {
-			br.Msg = err.Error()
-			br.ErrMsg = "移动失败,Err:" + err.Error()
-			return
-		}
-	}
+	//pptMap, err := ppt_english.GetPptMappingByPptId(req.PptId)
+	//if err != nil {
+	//	br.Msg = `该PPT信息不存在, 保存失败`
+	//	br.ErrMsg = `该PPT信息不存在, 保存失败, Err` + err.Error()
+	//	br.IsSendEmail = false
+	//	return
+	//}
+	//pptMapList, err := ppt_english.GetPptMappingListByGroupIdDesc(pptMap.GroupId)
+	//if err != nil {
+	//	br.ErrMsg = "PPT目录信息异常"
+	//	return
+	//}
+	//count, err := ppt_english.GetPptMappingCountByGroupPptId(pptMap.GroupPptId, this.SysUser.AdminId)
+	//if err != nil {
+	//	br.Msg = "获取英文PPT和用户权限关系失败"
+	//	br.ErrMsg = "获取英文PPT和用户权限关系失败, Err:" + err.Error()
+	//	return
+	//}
+	//if !pptMap.IsMoved && len(pptMapList) > 1 && count > 0 {
+	//	// 如果没有人为移动位置, 默认将当前ppt置顶
+	//	err = ppt.MoveGroupPptEnglish(pptMap.GroupId, pptMap.GroupPptId, pptMapList[0].GroupPptId, 0, this.SysUser.AdminId)
+	//	if err != nil {
+	//		br.Msg = err.Error()
+	//		br.ErrMsg = "移动失败,Err:" + err.Error()
+	//		return
+	//	}
+	//}
 
 	// 日志记录
 	{
@@ -752,33 +752,33 @@ func (this *PptEnglishController) SaveLog() {
 	pptItem.PptPage = len(pageContent)
 	err = pptItem.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "ppt_page"})
 
-	pptMap, err := ppt_english.GetPptMappingByPptId(req.PptId)
-	if err != nil {
-		br.Msg = `该PPT信息不存在, 保存失败`
-		br.ErrMsg = `该PPT信息不存在, 保存失败, Err` + err.Error()
-		br.IsSendEmail = false
-		return
-	}
-	pptMapList, err := ppt_english.GetPptMappingListByGroupIdDesc(pptMap.GroupId)
-	if err != nil {
-		br.ErrMsg = "PPT目录信息异常"
-		return
-	}
-	count, err := ppt_english.GetPptMappingCountByGroupPptId(pptMap.GroupPptId, this.SysUser.AdminId)
-	if err != nil {
-		br.Msg = "获取英文PPT和用户权限关系失败"
-		br.ErrMsg = "获取英文PPT和用户权限关系失败, Err:" + err.Error()
-		return
-	}
-	if !pptMap.IsMoved && len(pptMapList) > 1 && count > 0 {
-		// 如果没有人为移动位置, 且当前用户有权限, 默认将当前ppt置顶
-		err = ppt.MoveGroupPptEnglish(pptMap.GroupId, pptMap.GroupPptId, pptMapList[0].GroupPptId, 0, this.SysUser.AdminId)
-		if err != nil {
-			br.Msg = err.Error()
-			br.ErrMsg = "移动失败,Err:" + err.Error()
-			return
-		}
-	}
+	//pptMap, err := ppt_english.GetPptMappingByPptId(req.PptId)
+	//if err != nil {
+	//	br.Msg = `该PPT信息不存在, 保存失败`
+	//	br.ErrMsg = `该PPT信息不存在, 保存失败, Err` + err.Error()
+	//	br.IsSendEmail = false
+	//	return
+	//}
+	//pptMapList, err := ppt_english.GetPptMappingListByGroupIdDesc(pptMap.GroupId)
+	//if err != nil {
+	//	br.ErrMsg = "PPT目录信息异常"
+	//	return
+	//}
+	//count, err := ppt_english.GetPptMappingCountByGroupPptId(pptMap.GroupPptId, this.SysUser.AdminId)
+	//if err != nil {
+	//	br.Msg = "获取英文PPT和用户权限关系失败"
+	//	br.ErrMsg = "获取英文PPT和用户权限关系失败, Err:" + err.Error()
+	//	return
+	//}
+	//if !pptMap.IsMoved && len(pptMapList) > 1 && count > 0 {
+	//	// 如果没有人为移动位置, 且当前用户有权限, 默认将当前ppt置顶
+	//	err = ppt.MoveGroupPptEnglish(pptMap.GroupId, pptMap.GroupPptId, pptMapList[0].GroupPptId, 0, this.SysUser.AdminId)
+	//	if err != nil {
+	//		br.Msg = err.Error()
+	//		br.ErrMsg = "移动失败,Err:" + err.Error()
+	//		return
+	//	}
+	//}
 
 	//日志记录
 	logInfo := &ppt_english.PptEnglishSaveLog{

+ 59 - 59
controllers/ppt_v2.go

@@ -310,33 +310,33 @@ func (this *PptV2Controller) EditPpt() {
 		return
 	}
 
-	pptMap, err := models.GetPptMappingByPptId(req.PptId)
-	if err != nil {
-		br.Msg = `该PPT信息不存在, 保存失败`
-		br.ErrMsg = `该PPT信息不存在, 保存失败, Err` + err.Error()
-		br.IsSendEmail = false
-		return
-	}
-	pptMapList, err := models.GetPptMappingListByGroupId(pptMap.GroupId)
-	if err != nil {
-		br.ErrMsg = "PPT目录信息异常"
-		return
-	}
-	count, err := models.GetPptMappingByGroupPptCountId(pptMap.GroupPptId, this.SysUser.AdminId)
-	if err != nil {
-		br.Msg = "查询映射关系失败"
-		br.ErrMsg = "查询映射关系失败, 保存失败, Err:" + err.Error()
-		return
-	}
-	if !pptMap.IsMoved && len(pptMapList) > 1 && count > 0 {
-		// 如果没有人为移动位置, 默认将当前ppt置顶
-		err = ppt.MoveGroupPpt(pptMap.GroupId, pptMap.GroupPptId, pptMapList[0].GroupPptId, 0, this.SysUser.AdminId)
-		if err != nil {
-			br.Msg = err.Error()
-			br.ErrMsg = "移动失败,Err:" + err.Error()
-			return
-		}
-	}
+	//pptMap, err := models.GetPptMappingByPptId(req.PptId)
+	//if err != nil {
+	//	br.Msg = `该PPT信息不存在, 保存失败`
+	//	br.ErrMsg = `该PPT信息不存在, 保存失败, Err` + err.Error()
+	//	br.IsSendEmail = false
+	//	return
+	//}
+	//pptMapList, err := models.GetPptMappingListByGroupId(pptMap.GroupId)
+	//if err != nil {
+	//	br.ErrMsg = "PPT目录信息异常"
+	//	return
+	//}
+	//count, err := models.GetPptMappingByGroupPptCountId(pptMap.GroupPptId, this.SysUser.AdminId)
+	//if err != nil {
+	//	br.Msg = "查询映射关系失败"
+	//	br.ErrMsg = "查询映射关系失败, 保存失败, Err:" + err.Error()
+	//	return
+	//}
+	//if !pptMap.IsMoved && len(pptMapList) > 1 && count > 0 {
+	//	// 如果没有人为移动位置, 默认将当前ppt置顶
+	//	err = ppt.MoveGroupPpt(pptMap.GroupId, pptMap.GroupPptId, pptMapList[0].GroupPptId, 0, this.SysUser.AdminId)
+	//	if err != nil {
+	//		br.Msg = err.Error()
+	//		br.ErrMsg = "移动失败,Err:" + err.Error()
+	//		return
+	//	}
+	//}
 
 	// 日志记录
 	{
@@ -722,38 +722,38 @@ func (this *PptV2Controller) SaveLog() {
 	err = pptItem.Update([]string{"TemplateType", "BackgroundImg", "Title", "ReportType", "PptDate", "Content", "ModifyTime", "TitleSetting", "ppt_page"})
 
 	// 将更新后的PPT, 置顶
-	pptMap, err := models.GetPptMappingByPptId(int64(req.PptId))
-	if err != nil {
-		if utils.IsErrNoRow(err) {
-			br.Msg = `该PPT信息不存在, 保存失败`
-			br.ErrMsg = `该PPT信息不存在, 保存失败, Err` + err.Error()
-			br.IsSendEmail = false
-			return
-		}
-		br.Msg = "保存草稿失败"
-		br.ErrMsg = fmt.Sprintf("保存草稿失败, %v", err)
-		return
-	}
-	pptMapList, err := models.GetPptMappingListByGroupId(pptMap.GroupId)
-	if err != nil {
-		br.ErrMsg = "PPT目录信息异常"
-		return
-	}
-	count, err := models.GetPptMappingByGroupPptCountId(pptMap.GroupPptId, this.SysUser.AdminId)
-	if err != nil {
-		br.Msg = "查询映射关系失败"
-		br.ErrMsg = "查询映射关系失败, 保存失败, Err:" + err.Error()
-		return
-	}
-	if !pptMap.IsMoved && len(pptMapList) > 1 && count > 0 {
-		// 如果没有人为移动位置, 且当前用户有权限, 默认将当前ppt置顶
-		err = ppt.MoveGroupPpt(pptMap.GroupId, pptMap.GroupPptId, pptMapList[0].GroupPptId, 0, this.SysUser.AdminId)
-		if err != nil {
-			br.Msg = err.Error()
-			br.ErrMsg = "移动失败,Err:" + err.Error()
-			return
-		}
-	}
+	//pptMap, err := models.GetPptMappingByPptId(int64(req.PptId))
+	//if err != nil {
+	//	if utils.IsErrNoRow(err) {
+	//		br.Msg = `该PPT信息不存在, 保存失败`
+	//		br.ErrMsg = `该PPT信息不存在, 保存失败, Err` + err.Error()
+	//		br.IsSendEmail = false
+	//		return
+	//	}
+	//	br.Msg = "保存草稿失败"
+	//	br.ErrMsg = fmt.Sprintf("保存草稿失败, %v", err)
+	//	return
+	//}
+	//pptMapList, err := models.GetPptMappingListByGroupId(pptMap.GroupId)
+	//if err != nil {
+	//	br.ErrMsg = "PPT目录信息异常"
+	//	return
+	//}
+	//count, err := models.GetPptMappingByGroupPptCountId(pptMap.GroupPptId, this.SysUser.AdminId)
+	//if err != nil {
+	//	br.Msg = "查询映射关系失败"
+	//	br.ErrMsg = "查询映射关系失败, 保存失败, Err:" + err.Error()
+	//	return
+	//}
+	//if !pptMap.IsMoved && len(pptMapList) > 1 && count > 0 {
+	//	// 如果没有人为移动位置, 且当前用户有权限, 默认将当前ppt置顶
+	//	err = ppt.MoveGroupPpt(pptMap.GroupId, pptMap.GroupPptId, pptMapList[0].GroupPptId, 0, this.SysUser.AdminId)
+	//	if err != nil {
+	//		br.Msg = err.Error()
+	//		br.ErrMsg = "移动失败,Err:" + err.Error()
+	//		return
+	//	}
+	//}
 
 	//日志记录
 	logInfo := &models.PptV2SaveLog{

+ 10 - 2
controllers/report_approve/report_approve.go

@@ -359,6 +359,8 @@ func (this *ReportApproveController) List() {
 	// 审批通过的报告显示下载按钮
 	reportImg := make(map[string]string)
 	reportPdf := make(map[string]string)
+	reportImgMobile := make(map[string]string)
+	reportPdfMobile := make(map[string]string)
 	{
 		var reportIds, enReportIds []int
 		for _, v := range ormList {
@@ -373,7 +375,7 @@ func (this *ReportApproveController) List() {
 			}
 		}
 		if len(reportIds) > 0 {
-			reports, e := models.GetReportFieldsByIds(reportIds, []string{"id", "detail_img_url", "detail_pdf_url"})
+			reports, e := models.GetReportFieldsByIds(reportIds, []string{"id", "detail_img_url", "detail_pdf_url", "detail_img_url_mobile", "detail_pdf_url_mobile"})
 			if e != nil {
 				br.Msg = "获取失败"
 				br.ErrMsg = fmt.Sprintf("GetReportFieldsByIds, %v", e)
@@ -383,10 +385,12 @@ func (this *ReportApproveController) List() {
 				k := fmt.Sprintf("%d-%d", report_approve.FlowReportTypeChinese, r.Id)
 				reportImg[k] = r.DetailImgUrl
 				reportPdf[k] = r.DetailPdfUrl
+				reportImgMobile[k] = r.DetailImgUrlMobile
+				reportPdfMobile[k] = r.DetailPdfUrlMobile
 			}
 		}
 		if len(enReportIds) > 0 {
-			enReports, e := models.GetEnglishReportFieldsByIds(enReportIds, []string{"id", "detail_img_url", "detail_pdf_url"})
+			enReports, e := models.GetEnglishReportFieldsByIds(enReportIds, []string{"id", "detail_img_url", "detail_pdf_url", "detail_img_url_mobile", "detail_pdf_url_mobile"})
 			if e != nil {
 				br.Msg = "获取失败"
 				br.ErrMsg = fmt.Sprintf("GetEnglishReportFieldsByIds, %v", e)
@@ -396,6 +400,8 @@ func (this *ReportApproveController) List() {
 				k := fmt.Sprintf("%d-%d", report_approve.FlowReportTypeEnglish, r.Id)
 				reportImg[k] = r.DetailImgUrl
 				reportPdf[k] = r.DetailPdfUrl
+				reportImgMobile[k] = r.DetailImgUrlMobile
+				reportPdfMobile[k] = r.DetailPdfUrlMobile
 			}
 		}
 	}
@@ -439,6 +445,8 @@ func (this *ReportApproveController) List() {
 		k := fmt.Sprintf("%d-%d", t.ReportType, t.ReportId)
 		t.DetailImgUrl = reportImg[k]
 		t.DetailPdfUrl = reportPdf[k]
+		t.DetailImgUrlMobile = reportImgMobile[k]
+		t.DetailPdfUrlMobile = reportPdfMobile[k]
 
 		respList = append(respList, t)
 	}

+ 15 - 0
controllers/report_chapter.go

@@ -366,6 +366,7 @@ func (this *ReportController) EditDayWeekChapter() {
 	if req.Content != "" {
 		// 处理关联excel的表格id
 		req.Content = services.HandleReportContentTable(reportInfo.Id, req.Content)
+		req.Content = services.HandleReportContent(req.Content, "del", nil)
 		e := utils.ContentXssCheck(req.Content)
 		if e != nil {
 			br.Msg = "存在非法标签"
@@ -408,6 +409,7 @@ func (this *ReportController) EditDayWeekChapter() {
 	reportChapterInfo.ContentModifyTime = time.Now()
 	if req.ContentStruct != `` {
 		req.ContentStruct = services.HandleReportContentStructTable(reportChapterInfo.ReportId, req.ContentStruct)
+		req.ContentStruct = services.HandleReportContentStruct(req.ContentStruct, "del", nil)
 	}
 	reportChapterInfo.ContentStruct = html.EscapeString(req.ContentStruct)
 
@@ -783,6 +785,19 @@ func (this *ReportController) GetDayWeekChapter() {
 	chapterItem.Content = services.HandleReportContentTable(chapterItem.ReportId, chapterItem.Content)
 	chapterItem.ContentStruct = services.HandleReportContentStructTable(chapterItem.ReportId, chapterItem.ContentStruct)
 
+	businessConf, err := models.GetBusinessConfByKey(models.BusinessConfIsOpenChartExpired)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取配置失败,Err:" + err.Error()
+		return
+	}
+
+	if businessConf.ConfVal == `true` {
+		tokenMap := make(map[string]string)
+		chapterItem.Content = services.HandleReportContent(chapterItem.Content, "add", tokenMap)
+		chapterItem.ContentStruct = services.HandleReportContentStruct(chapterItem.ContentStruct, "add", tokenMap)
+	}
+
 	// 授权用户列表map
 	chapterGrantIdList := make([]int, 0)
 	// 关联品种id列表map

+ 126 - 4
controllers/report_v2.go

@@ -12,13 +12,14 @@ import (
 	"eta/eta_api/services/data"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/rdlucklib/rdluck_tools/paging"
 	"html"
 	"io"
 	"os"
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // ListReport
@@ -393,6 +394,7 @@ func (this *ReportController) Add() {
 
 	var contentSub string
 	if req.Content != "" {
+		req.Content = services.HandleReportContent(req.Content, "del", nil)
 		e := utils.ContentXssCheck(req.Content)
 		if e != nil {
 			br.Msg = "存在非法标签"
@@ -414,6 +416,10 @@ func (this *ReportController) Add() {
 		}
 	}
 
+	if req.ContentStruct != `` {
+		req.ContentStruct = services.HandleReportContentStruct(req.ContentStruct, "del", nil)
+	}
+
 	// 报告期数
 	maxStage, err := models.GetReportStage(req.ClassifyIdFirst, req.ClassifyIdSecond, req.ClassifyIdThird)
 	if err != nil {
@@ -592,6 +598,8 @@ func (this *ReportController) Edit() {
 
 	req.Content = services.HandleReportContentTable(int(req.ReportId), req.Content)
 	req.ContentStruct = services.HandleReportContentStructTable(int(req.ReportId), req.ContentStruct)
+	req.Content = services.HandleReportContent(req.Content, "del", nil)
+	req.ContentStruct = services.HandleReportContentStruct(req.ContentStruct, "del", nil)
 
 	// 编辑报告信息
 	err, errMsg := services.EditReport(reportInfo, req, sysUser)
@@ -711,6 +719,24 @@ func (this *ReportController) Detail() {
 		v.ContentStruct = services.HandleReportContentStructTable(item.Id, v.ContentStruct)
 	}
 
+	businessConf, err := models.GetBusinessConfByKey(models.BusinessConfIsOpenChartExpired)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取配置失败,Err:" + err.Error()
+		return
+	}
+
+	if businessConf.ConfVal == `true` {
+		tokenMap := make(map[string]string)
+		item.Content = services.HandleReportContent(item.Content, "add", tokenMap)
+		item.ContentStruct = services.HandleReportContentStruct(item.ContentStruct, "add", tokenMap)
+		for _, v := range chapterList {
+			v.Content = services.HandleReportContent(v.Content, "add", tokenMap)
+			v.ContentStruct = services.HandleReportContentStruct(v.ContentStruct, "add", tokenMap)
+		}
+
+	}
+
 	resp := &models.ReportDetailView{
 		ReportDetail: item,
 		ChapterList:  chapterList,
@@ -789,14 +815,15 @@ func (this *ReportController) SaveReportContent() {
 		if content == "" {
 			content = this.GetString("Content")
 		}
+		content = services.HandleReportContent(content, "del", nil)
 		if content != "" {
-			e := utils.ContentXssCheck(req.Content)
+			e := utils.ContentXssCheck(content)
 			if e != nil {
 				br.Msg = "存在非法标签"
 				br.ErrMsg = "存在非法标签, Err: " + e.Error()
 				return
 			}
-			contentClean, e := services.FilterReportContentBr(req.Content)
+			contentClean, e := services.FilterReportContentBr(content)
 			if e != nil {
 				br.Msg = "内容去除前后空格失败"
 				br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
@@ -804,6 +831,8 @@ func (this *ReportController) SaveReportContent() {
 			}
 			content = contentClean
 
+			req.ContentStruct = services.HandleReportContentStruct(req.ContentStruct, "del", nil)
+
 			contentSub, err := services.GetReportContentSub(content)
 			if err != nil {
 				go alarm_msg.SendAlarmMsg("解析 ContentSub 失败,Err:"+err.Error(), 3)
@@ -1462,7 +1491,7 @@ func (this *ReportController) PrePublishReport() {
 
 	// 生成报告pdf和长图
 	{
-		reportPdfUrl := services.GetGeneralPdfUrl(reportDetail.ReportCode, reportDetail.ClassifyNameFirst, reportDetail.ReportLayout)
+		reportPdfUrl := services.GetGeneralPdfUrl(reportDetail.Id, reportDetail.ReportCode, reportDetail.ClassifyNameFirst, reportDetail.ReportLayout)
 		go services.Report2pdfAndJpeg(reportPdfUrl, reportDetail.Id, 1)
 	}
 
@@ -1689,6 +1718,99 @@ func (this *ReportController) CancelApprove() {
 	br.Msg = "操作成功"
 }
 
+// ShareGenerate
+// @Title 获取复制链接
+// @Description 获取复制链接
+// @Param	request	body models.ReportShartLinkReq true "type json string"
+// @Success 200 Ret=200 操作成功
+// @router /share/generate [post]
+func (this *ReportController) ShareGenerate() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req models.ReportShartUrlReq
+	if e := json.Unmarshal(this.Ctx.Input.RequestBody, &req); e != nil {
+		br.Msg = "参数有误"
+		br.ErrMsg = "参数解析失败, Err: " + e.Error()
+		return
+	}
+	var title string
+	reportItem, _ := models.GetReportByReportId(req.ReportId)
+	if reportItem != nil && reportItem.Title != "" {
+		title = reportItem.Title
+	}
+
+	link, err := services.GetReportShareUrlToken(req, this.SysUser.AdminId)
+	if err != nil || link == "" {
+		br.Msg = "复制链接失败"
+		br.ErrMsg = "获取复制链接失败, Err: " + err.Error()
+		return
+	}
+
+	resp := new(models.ReportShartUrlResp)
+	resp.UrlToken = fmt.Sprint(link, " ", title)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+// ShareTransform
+// @Title 获取原始链接
+// @Description 获取原始链接
+// @Param	Token   query   string  true    "复制链接的token"
+// @Success 200 Ret=200 操作成功
+// @router /share/link [get]
+func (this *ReportCommonController) ShareTransform() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	token := this.GetString("Token")
+	tokenArr := strings.Split(token, " ")
+	if len(tokenArr) > 0 {
+		token = tokenArr[0]
+	}
+
+	link, msg, err := services.TransfromToOriginUrl(token)
+	if err != nil {
+		if msg == "" {
+			msg = "获取失败"
+		}
+
+		htmlTpl := `
+		<!DOCTYPE html>
+		<html lang="zh">
+		<head>
+		    <meta charset="UTF-8">
+		    <title>链接失效</title>
+		</head>
+		<body>
+		    <h1>链接失效</h1>
+		    <p>%s</p>
+		</body>
+		</html>
+		`
+		htmlTpl = fmt.Sprintf(htmlTpl, msg)
+		this.Ctx.Output.SetStatus(404)
+		this.Ctx.Output.Body([]byte(htmlTpl))
+		utils.FileLog.Info("获取复制链接失败, Err: " + err.Error())
+		return
+	}
+
+	this.Ctx.Redirect(302, link)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+}
+
 // init
 // @Description: 修复历史报告数据
 // @author: Roc

+ 85 - 14
controllers/sys_group.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"eta/eta_api/models"
 	"eta/eta_api/models/system"
+	"eta/eta_api/services"
 	"eta/eta_api/services/eta_forum"
 	"eta/eta_api/utils"
 	"strings"
@@ -38,22 +39,75 @@ func (this *SysGroupController) Add() {
 		br.ErrMsg = "部门ID不可为空"
 		return
 	}
-	if req.GroupName == "" {
-		br.Msg = "分组名称不能为空"
+	//构建部门的分组树
+	groupList,err:=system.GetGroupByDepartmentId(req.DepartmentId)
+	if err!=nil{
+		br.Msg = "获取数据失败,获取当前部门的分组列表失败"
+		br.ErrMsg = "获取当前部门的分组列表失败,Err:" + err.Error()
 		return
 	}
+	//获取传入部门下的分组名称,分组名称为唯一标识,不会重复
 	groupNameArr := strings.Split(req.GroupName, ",")
-	groupIds := make([]int, 0)
+	root := new(services.GroupNode)
+	services.BuildGroupTree(groupList,0,2,root)
+	//现有的分组名称
+	existGroupMap:=make(map[string]bool,len(root.Child))
+	for _, v := range root.Child {
+		existGroupMap[v.GroupName] = false
+	}
+	//传入的分组名称
+	reqGroupMap:=make(map[string]int,len(groupNameArr))
 	for _, v := range groupNameArr {
-		count, err := system.GetSysGroupCount(req.DepartmentId, v)
-		if err != nil {
-			br.Msg = "获取数据失败"
-			br.ErrMsg = "获取数据失败,Err:" + err.Error()
-			return
+		reqGroupMap[v] = 1
+	}
+	//新增分组名称
+	newGroupMap := make(map[string]int)
+	groupIds := make([]int, 0)
+	if len(reqGroupMap)>0{
+		for k, _ := range reqGroupMap {
+			if _, ok := existGroupMap[k]; !ok {
+				newGroupMap[k] = 1
+				groupIds = append(groupIds, 0)
+			}else{
+				existGroupMap[k] = true
+			}
 		}
-		if count <= 0 {
+	}
+	var deleteIds []int
+	var addGroup bool
+	//删除所有分组即可
+	if req.GroupName == "" {
+		addGroup=false
+		for _, node := range root.Child {
+			//分组Id
+			deleteIds = append(deleteIds, node.GroupId)
+			//teamId
+			for _, subNode := range node.Child {
+				deleteIds=append(deleteIds, subNode.GroupId)
+			}
+		}
+	}else{
+		addGroup=true
+		for k, v := range existGroupMap {
+			if !v{
+				for _, node := range root.Child {
+					if node.GroupName == k{
+						//分组Id
+						deleteIds = append(deleteIds, node.GroupId)
+						//teamId
+						for _, subNode := range node.Child {
+							deleteIds=append(deleteIds, subNode.GroupId)
+						}
+					}
+				}
+			}
+		}
+	}
+	if addGroup{
+		//新增分组
+		for k, _ := range newGroupMap {
 			item := new(system.SysGroup)
-			item.GroupName = v
+			item.GroupName = k
 			item.DepartmentId = req.DepartmentId
 			item.CreateTime = time.Now()
 			groupId, e := system.AddSysGroup(item)
@@ -62,7 +116,6 @@ func (this *SysGroupController) Add() {
 				br.ErrMsg = "新增失败,Err:" + e.Error()
 				return
 			}
-
 			// 同步分组缓存
 			if utils.BusinessCode == utils.BusinessCodeRelease {
 				var syncData system.SyncGroupData
@@ -72,12 +125,30 @@ func (this *SysGroupController) Add() {
 			}
 			groupIds = append(groupIds, int(groupId))
 		}
+		go eta_forum.GroupSave(groupIds)
+		//删除分组合他的子分组,写死只有2层分组,因此逻辑代码也就写死2层,一层分组,一层team
+	}
+	if len(deleteIds)>0 {
+		err =services.DeleteSysGroupByIds(deleteIds)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+		// 同步分组缓存
+		if utils.BusinessCode == utils.BusinessCodeRelease {
+			for _, groupId := range deleteIds {
+				var syncData system.SyncGroupData
+				syncData.Source = utils.SOURCE_ETA_FLAG
+				syncData.GroupId = groupId
+				_ = utils.Rc.LPush(utils.CACHE_SYNC_GROUP, syncData)
+				go eta_forum.GroupDelete(groupId)
+			}
+		}
 	}
-
-	go eta_forum.GroupSave(groupIds)
 	br.Ret = 200
 	br.Success = true
-	br.Msg = "新增成功"
+	br.Msg = "保存成功"
 }
 
 // @Title 修改分组

+ 5 - 1
controllers/sys_role.go

@@ -712,7 +712,11 @@ func (this *SysRoleController) SystemConfig() {
 	}, system.BusinessConf{
 		ConfKey: "LoginUrl",
 		ConfVal: conf["LoginUrl"],
-	})
+	},
+        system.BusinessConf{
+        ConfKey: "KnowledgeBaseName",
+    	ConfVal: conf["KnowledgeBaseName"],
+    })
 
 	osc := system.BusinessConf{
 		ConfKey: "ObjectStorageClient",

+ 75 - 12
controllers/sys_team.go

@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"eta/eta_api/models"
 	"eta/eta_api/models/system"
+	"eta/eta_api/services"
 	"eta/eta_api/utils"
 	"strings"
 	"time"
@@ -37,21 +38,67 @@ func (this *SysTeamController) Add() {
 		br.ErrMsg = "大组ID不可为空"
 		return
 	}
-	if req.TeamName == "" {
-		br.Msg = "分组名称不能为空"
+	//构建部门的分组树
+	teamList, err := system.GetChildSysGroupByGroupId(req.GroupId)
+	teamNameArr := strings.Split(req.TeamName, ",")
+	if err != nil {
+		br.Msg = "获取数据失败,获取当前分组的team列表失败"
+		br.ErrMsg = "获取当前部门的分组列表失败,Err:" + err.Error()
 		return
 	}
-	teamNameArr := strings.Split(req.TeamName, ",")
+	//现有的分组名称
+	existTeamMap := make(map[string]bool, len(teamList))
+	for _, v := range teamList {
+		existTeamMap[v.GroupName] = false
+	}
+	//传入的分组名称
+	reqTeamMap := make(map[string]int, len(teamNameArr))
 	for _, v := range teamNameArr {
-		count, err := system.GetSysTeamCount(req.GroupId, v)
-		if err != nil {
-			br.Msg = "获取数据失败"
-			br.ErrMsg = "获取数据失败,Err:" + err.Error()
-			return
+		reqTeamMap[v] = 1
+	}
+	//新增分组名称
+	newTeamMap := make(map[string]int)
+	groupIds := make([]int, 0)
+	if len(reqTeamMap) > 0 {
+		for k, _ := range reqTeamMap {
+			if _, ok := existTeamMap[k]; !ok {
+				newTeamMap[k] = 1
+				groupIds = append(groupIds, 0)
+			} else {
+				existTeamMap[k] = true
+			}
+		}
+	}
+	var deleteIds []int
+	var addGroup bool
+	//if req.TeamName == "" {
+	//	br.Msg = "分组名称不能为空"
+	//	return
+	//}
+	//删除所有team即可
+	if req.TeamName == "" {
+		addGroup = false
+		for _, node := range teamList {
+			//teamId
+			deleteIds = append(deleteIds, node.GroupId)
 		}
-		if count <= 0 {
+	} else {
+		addGroup = true
+		for k, v := range existTeamMap {
+			if !v {
+				for _, node := range teamList {
+					if node.GroupName == k {
+						//分组Id
+						deleteIds = append(deleteIds, node.GroupId)
+					}
+				}
+			}
+		}
+	}
+	if addGroup {
+		for k, _ := range newTeamMap {
 			item := new(system.SysGroup)
-			item.GroupName = v
+			item.GroupName = k
 			item.DepartmentId = req.DepartmentId
 			item.ParentId = req.GroupId
 			item.CreateTime = time.Now()
@@ -61,7 +108,6 @@ func (this *SysTeamController) Add() {
 				br.ErrMsg = "新增失败,Err:" + e.Error()
 				return
 			}
-
 			// 同步分组缓存
 			if utils.BusinessCode == utils.BusinessCodeRelease {
 				var syncData system.SyncGroupData
@@ -71,9 +117,26 @@ func (this *SysTeamController) Add() {
 			}
 		}
 	}
+	if len(deleteIds)>0 {
+		err =services.DeleteSysGroupByIds(deleteIds)
+		if err != nil {
+			br.Msg = "删除失败"
+			br.ErrMsg = "删除失败,Err:" + err.Error()
+			return
+		}
+		// 同步分组缓存
+		if utils.BusinessCode == utils.BusinessCodeRelease {
+			for _, groupId := range deleteIds {
+				var syncData system.SyncGroupData
+				syncData.Source = utils.SOURCE_ETA_FLAG
+				syncData.GroupId = groupId
+				_ = utils.Rc.LPush(utils.CACHE_SYNC_GROUP, syncData)
+			}
+		}
+	}
 	br.Ret = 200
 	br.Success = true
-	br.Msg = "新增成功"
+	br.Msg = "保存成功"
 }
 
 // @Title 修改分组

+ 1 - 0
controllers/user_login.go

@@ -560,6 +560,7 @@ func (this *UserLoginController) Login() {
 	}
 	resp.ProductName = productName
 	resp.Authority = sysUser.Authority
+	resp.Mobile = sysUser.Mobile
 
 	// 设置redis缓存
 	{

+ 1 - 0
go.mod

@@ -40,6 +40,7 @@ require (
 	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/shopspring/decimal v1.3.1
 	github.com/silenceper/wechat/v2 v2.1.6
+	github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72
 	github.com/spf13/viper v1.7.0
 	github.com/tealeg/xlsx v1.0.5
 	github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/asr v1.0.873

+ 1 - 0
go.sum

@@ -570,6 +570,7 @@ github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1
 github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo=
 github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
 github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72 h1:qLC7fQah7D6K1B0ujays3HV9gkFtllcxhzImRR7ArPQ=
 github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
 github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=

+ 0 - 2
main.go

@@ -12,7 +12,6 @@ import (
 	_ "eta/eta_api/routers"
 	"eta/eta_api/services"
 	"eta/eta_api/utils"
-
 	"github.com/beego/beego/v2/adapter/logs"
 	"github.com/beego/beego/v2/server/web"
 	"github.com/beego/beego/v2/server/web/context"
@@ -23,7 +22,6 @@ func main() {
 		web.BConfig.WebConfig.DirectoryIndex = true
 		web.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
 	}
-
 	go services.Task()
 
 	// 异常处理

+ 24 - 2
models/ai_predict_model/ai_predict_model_index.go

@@ -304,15 +304,37 @@ func (m *AiPredictModelIndex) ImportIndexAndData(createIndexes, updateIndexes []
 				err = fmt.Errorf("update index err: %v", e)
 				return
 			}
+			var hasDaily, hasMonthly bool
 			for _, d := range v.Data {
 				d.AiPredictModelIndexId = v.Index.AiPredictModelIndexId
 				d.IndexCode = v.Index.IndexCode
 				d.DataTimestamp = d.DataTime.UnixNano() / 1e6
+				if d.Source == ModelDataSourceDaily {
+					hasDaily = true
+				}
+				if d.Source == ModelDataSourceMonthly {
+					hasMonthly = true
+				}
+			}
+			// 哪个有数据就先清空然后重新写入,没数据就保留旧数据, 都没就忽略
+			if !hasDaily && !hasMonthly {
+				continue
+			}
+			removeCond := ``
+			removePars := make([]interface{}, 0)
+			removePars = append(removePars, v.Index.AiPredictModelIndexId)
+			if hasDaily && !hasMonthly {
+				removeCond += ` AND source = ?`
+				removePars = append(removePars, ModelDataSourceDaily)
+			}
+			if !hasDaily && hasMonthly {
+				removeCond += ` AND source = ?`
+				removePars = append(removePars, ModelDataSourceMonthly)
 			}
 
 			// 清空指标并新增
-			sql := `DELETE FROM ai_predict_model_data WHERE ai_predict_model_index_id = ?`
-			e = tx.Exec(sql, v.Index.AiPredictModelIndexId).Error
+			sql := fmt.Sprintf(`DELETE FROM ai_predict_model_data WHERE ai_predict_model_index_id = ? %s`, removeCond)
+			e = tx.Exec(sql, removePars...).Error
 			if e != nil {
 				err = fmt.Errorf("clear index data err: %v", e)
 				return

+ 79 - 33
models/business_conf.go

@@ -1,10 +1,12 @@
 package models
 
 import (
+	"encoding/json"
 	"eta/eta_api/global"
 	"eta/eta_api/utils"
 	"fmt"
 	"html"
+	"strconv"
 	"strings"
 	"time"
 )
@@ -14,39 +16,40 @@ var (
 )
 
 const (
-	BusinessConfUseXf                        = "UseXf"
-	BusinessConfXfAppid                      = "XfAppid"
-	BusinessConfXfApiKey                     = "XfApiKey"
-	BusinessConfXfApiSecret                  = "XfApiSecret"
-	BusinessConfXfVcn                        = "XfVcn"
-	BusinessConfEnPptCoverImgs               = "EnPptCoverImgs"
-	BusinessConfIsReportApprove              = "IsReportApprove"
-	BusinessConfReportApproveType            = "ReportApproveType"
-	BusinessConfCompanyName                  = "CompanyName"
-	BusinessConfCompanyWatermark             = "CompanyWatermark"
-	BusinessConfWatermarkChart               = "WatermarkChart"
-	BusinessConfLoginSmsTpId                 = "LoginSmsTpId"
-	BusinessConfLoginSmsGjTpId               = "LoginSmsGjTpId"
-	BusinessConfSmsJhgnAppKey                = "SmsJhgnAppKey"
-	BusinessConfSmsJhgjAppKey                = "SmsJhgjAppKey"
-	BusinessConfLdapHost                     = "LdapHost"
-	BusinessConfLdapBase                     = "LdapBase"
-	BusinessConfLdapPort                     = "LdapPort"
-	BusinessConfEmailClient                  = "EmailClient"
-	BusinessConfEmailServerHost              = "EmailServerHost"
-	BusinessConfEmailServerPort              = "EmailServerPort"
-	BusinessConfEmailSender                  = "EmailSender"
-	BusinessConfEmailSenderUserName          = "EmailSenderUserName"
-	BusinessConfEmailSenderPassword          = "EmailSenderPassword"
-	BusinessConfSmsClient                    = "SmsClient"
-	BusinessConfNanHuaSmsAppKey              = "NanHuaSmsAppKey"
-	BusinessConfNanHuaSmsAppSecret           = "NanHuaSmsAppSecret"
-	BusinessConfNanHuaSmsApiHost             = "NanHuaSmsApiHost"
-	BusinessConfLoginSmsTplContent           = "LoginSmsTplContent"
-	BusinessConfLoginEmailTemplateSubject    = "LoginEmailTemplateSubject"
-	BusinessConfLoginEmailTemplateContent    = "LoginEmailTemplateContent"
-	BusinessConfLdapBindUserSuffix           = "LdapBindUserSuffix"
-	BusinessConfLdapUserFilter               = "LdapUserFilter"
+	BusinessConfUseXf                     = "UseXf"
+	BusinessConfXfAppid                   = "XfAppid"
+	BusinessConfXfApiKey                  = "XfApiKey"
+	BusinessConfXfApiSecret               = "XfApiSecret"
+	BusinessConfXfVcn                     = "XfVcn"
+	BusinessConfEnPptCoverImgs            = "EnPptCoverImgs"
+	BusinessConfIsReportApprove           = "IsReportApprove"
+	BusinessConfReportApproveType         = "ReportApproveType"
+	BusinessConfCompanyName               = "CompanyName"
+	BusinessConfCompanyWatermark          = "CompanyWatermark"
+	BusinessConfWatermarkChart            = "WatermarkChart"
+	BusinessConfLoginSmsTpId              = "LoginSmsTpId"
+	BusinessConfLoginSmsGjTpId            = "LoginSmsGjTpId"
+	BusinessConfSmsJhgnAppKey             = "SmsJhgnAppKey"
+	BusinessConfSmsJhgjAppKey             = "SmsJhgjAppKey"
+	BusinessConfLdapHost                  = "LdapHost"
+	BusinessConfLdapBase                  = "LdapBase"
+	BusinessConfLdapPort                  = "LdapPort"
+	BusinessConfEmailClient               = "EmailClient"
+	BusinessConfEmailServerHost           = "EmailServerHost"
+	BusinessConfEmailServerPort           = "EmailServerPort"
+	BusinessConfEmailSender               = "EmailSender"
+	BusinessConfEmailSenderUserName       = "EmailSenderUserName"
+	BusinessConfEmailSenderPassword       = "EmailSenderPassword"
+	BusinessConfSmsClient                 = "SmsClient"
+	BusinessConfNanHuaSmsAppKey           = "NanHuaSmsAppKey"
+	BusinessConfNanHuaSmsAppSecret        = "NanHuaSmsAppSecret"
+	BusinessConfNanHuaSmsApiHost          = "NanHuaSmsApiHost"
+	BusinessConfLoginSmsTplContent        = "LoginSmsTplContent"
+	BusinessConfLoginEmailTemplateSubject = "LoginEmailTemplateSubject"
+	BusinessConfLoginEmailTemplateContent = "LoginEmailTemplateContent"
+	BusinessConfLdapBindUserSuffix        = "LdapBindUserSuffix"
+	BusinessConfLdapUserFilter            = "LdapUserFilter"
+
 	BusinessConfTencentApiSecretId           = "TencentApiSecretId"           // 腾讯云API-密钥对
 	BusinessConfTencentApiSecretKey          = "TencentApiSecretKey"          // 腾讯云API-密钥对
 	BusinessConfTencentApiRecTaskCallbackUrl = "TencentApiRecTaskCallbackUrl" // 腾讯云API-语音识别回调地址
@@ -56,6 +59,13 @@ const (
 	BusinessConfReportViewUrl                = "ReportViewUrl"                // 报告详情地址
 	BusinessConfEsIndexNameExcel             = "EsIndexNameExcel"             // ES索引名称-表格
 	BusinessConfEsIndexNameDataSource        = "EsIndexNameDataSource"        // ES索引名称-数据源
+	LLMInitConfig                            = "llmInitConfig"
+	KnowledgeBaseName                        = "KnowledgeBaseName"                // 摘要库
+	KnowledgeArticleName                     = "KnowledgeArticleName"             // 原文库
+	BusinessConfEsWechatArticle              = "EsIndexNameWechatArticle"         // ES索引名称-微信文章
+	BusinessConfEsWechatArticleAbstract      = "EsIndexNameWechatArticleAbstract" // ES索引名称-微信文章摘要
+	BusinessConfIsOpenChartExpired     = "IsOpenChartExpired"     // 是否开启图表有效期鉴权/报告禁止复制
+	BusinessConfReportChartExpiredTime = "ReportChartExpiredTime" // 图表有效期鉴权时间,单位:分钟
 )
 
 const (
@@ -63,6 +73,7 @@ const (
 	BusinessConfReportApproveTypeOther = "other"
 	BusinessConfClientFlagNanHua       = "nhqh" // 南华标记
 	BusinessConfEmailClientSmtp        = "smtp" // 普通邮箱标记
+
 )
 
 // FromSceneMap 数据源名称与数据源ID的对应关系
@@ -241,6 +252,11 @@ func InitUseMongoConf() {
 	}
 }
 
+type LLMConfig struct {
+	LlmAddress string `json:"llm_server"`
+	LlmModel   string `json:"llm_model"`
+}
+
 func InitBusinessConf() {
 	var e error
 	BusinessConfMap, e = GetBusinessConf()
@@ -254,4 +270,34 @@ func InitBusinessConf() {
 	if BusinessConfMap[BusinessConfEsIndexNameDataSource] != "" {
 		utils.EsDataSourceIndexName = BusinessConfMap[BusinessConfEsIndexNameDataSource]
 	}
+	// ES索引名称
+	if BusinessConfMap[BusinessConfEsWechatArticle] != "" {
+		utils.EsWechatArticleName = BusinessConfMap[BusinessConfEsWechatArticle]
+	}
+	if BusinessConfMap[BusinessConfEsWechatArticleAbstract] != "" {
+		utils.EsWechatArticleAbstractName = BusinessConfMap[BusinessConfEsWechatArticleAbstract]
+	}
+	confStr := BusinessConfMap[LLMInitConfig]
+	if confStr != "" {
+		var config LLMConfig
+		err := json.Unmarshal([]byte(confStr), &config)
+		if err != nil {
+			utils.FileLog.Error("LLM配置错误")
+		}
+
+		utils.LLM_MODEL = config.LlmModel
+		utils.LLM_SERVER = config.LlmAddress
+	}
+
+	// 图表有效期的过期时间
+	if BusinessConfMap[BusinessConfReportChartExpiredTime] != "" {
+		reportChartExpiredTime, _ := strconv.Atoi(BusinessConfMap[BusinessConfReportChartExpiredTime])
+		if reportChartExpiredTime <= 0 {
+			reportChartExpiredTime = 30
+		}
+		utils.BusinessConfReportChartExpiredTime = time.Duration(reportChartExpiredTime) * time.Minute
+	} else {
+		utils.BusinessConfReportChartExpiredTime = 30 * time.Minute
+	}
+
 }

+ 292 - 0
models/data_manage/base_from_purang.go

@@ -0,0 +1,292 @@
+package data_manage
+
+import (
+	"eta/eta_api/utils"
+	"time"
+
+	"gorm.io/gorm"
+
+	"database/sql"
+	"eta/eta_api/global"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type BaseFromPurangIndex struct {
+	BaseFromPurangIndexId int `orm:"column(base_from_purang_index_id);pk"`
+	ClassifyId             int
+	IndexCode              string
+	IndexName              string
+	Frequency              string
+	Unit                   string
+	Sort                   int
+	StartDate              time.Time `description:"开始日期"`
+	EndDate                time.Time `description:"结束日期"`
+	EndValue               float64
+	CreateTime             time.Time
+	ModifyTime             time.Time
+}
+
+type BaseFromPurangIndexList struct {
+	BaseFromPurangIndexId int `orm:"column(base_from_purang_index_id);pk"`
+	ClassifyId             int
+	Interface              string
+	EdbInfoId              int
+	EdbUniqueCode          string `description:"指标库唯一编码"`
+	EdbClassifyId          int    `description:"指标库分类ID"`
+	StartDate              string
+	EndDate                string
+	EndValue               float64
+	IndexCode              string
+	IndexName              string
+	Frequency              string
+	Unit                   string
+	Sort                   int
+	CreateTime             string
+	ModifyTime             string
+	EdbExist               int                    `description:"指标库是否已添加:0-否;1-是"`
+	DataList               []*BaseFromPurangData `gorm:"-"`
+	Paging                 *paging.PagingItem     `description:"分页数据" gorm:"-"`
+}
+
+func (baseFromPurangIndexList *BaseFromPurangIndexList) AfterFind(tx *gorm.DB) (err error) {
+	baseFromPurangIndexList.CreateTime = utils.GormDateStrToDateTimeStr(baseFromPurangIndexList.CreateTime)
+	baseFromPurangIndexList.ModifyTime = utils.GormDateStrToDateTimeStr(baseFromPurangIndexList.ModifyTime)
+	baseFromPurangIndexList.StartDate = utils.GormDateStrToDateStr(baseFromPurangIndexList.StartDate)
+	baseFromPurangIndexList.EndDate = utils.GormDateStrToDateStr(baseFromPurangIndexList.EndDate)
+	return
+}
+
+type BaseFromPurangIndexSearchList struct {
+	List   []*BaseFromPurangIndexList
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type PurangSingleDataResp struct {
+	BaseFromPurangIndexId int
+	ClassifyId             int
+	EdbInfoId              int
+	IndexCode              string
+	IndexName              string
+	Frequency              string
+	Unit                   string
+	StartTime              string
+	CreateTime             string
+	ModifyTime             string
+	EdbExist               int                  `description:"指标库是否已添加:0-否;1-是"`
+	Data                   []*PurangSingleData `gorm:"-"`
+	Paging                 *paging.PagingItem   `description:"分页数据" gorm:"-"`
+}
+
+type PurangSingleData struct {
+	Value    string `orm:"column(value)" description:"日期"`
+	DataTime string `orm:"column(data_time)" description:"值"`
+}
+
+func GetPurangIndexByClassifyId(classifyId int) (items []*BaseFromPurangIndex, err error) {
+	sql := ` SELECT base_from_purang_index_id, classify_id, index_code, index_name FROM base_from_purang_index WHERE classify_id=? ORDER BY sort ASC, base_from_purang_index_id ASC `
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, classifyId).Find(&items).Error
+	return
+}
+
+func GetPurangIndex(condition string, pars []interface{}) (items []*BaseFromPurangIndexList, err error) {
+	sql := ` SELECT * FROM base_from_purang_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY sort ASC, base_from_purang_index_id asc`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars...).Find(&items).Error
+	return
+}
+
+func GetPurangIndexPage(condition string, pars []interface{}, startSize, pageSize int) (items []*BaseFromPurangIndexList, err error) {
+	sql := ` SELECT * FROM base_from_purang_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY sort ASC, base_from_purang_index_id asc LIMIT ?,?`
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars...).Find(&items).Error
+	return
+}
+
+func GetPurangIndexPageCount(condition string, pars []interface{}) (count int, err error) {
+	sql := ` SELECT COUNT(1) AS count  FROM base_from_purang_index WHERE 1=1  `
+	if condition != "" {
+		sql += condition
+	}
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars...).Scan(&count).Error
+	return
+}
+
+func GetPurangIndexDataCount(indexCode string) (count int, err error) {
+	sql := ` SELECT COUNT(1) AS count  FROM base_from_purang_data WHERE index_code=? `
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, indexCode).Scan(&count).Error
+	return
+}
+
+func GetPurangIndexDataByDataTime(indexCodes []string, startDate, endDate string) (items []*BaseFromPurangData, err error) {
+	sql := ` SELECT *  FROM base_from_purang_data WHERE  index_code in (` + utils.GetOrmInReplace(len(indexCodes)) + `) and data_time >=? and data_time <? ORDER BY data_time DESC `
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, indexCodes, startDate, endDate).Find(&items).Error
+	return
+}
+
+func GetPurangIndexDataTimePageByCodes(indexCodes []string, startSize, pageSize int) (dataTimes []string, err error) {
+	sql := ` SELECT data_time FROM base_from_purang_data WHERE index_code in ? GROUP BY data_time ORDER BY data_time DESC LIMIT ?,? `
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, indexCodes, startSize, pageSize).Find(&dataTimes).Error
+	return
+}
+
+func GetPurangIndexDataTimePageCount(indexCodes []string) (count int, err error) {
+	sql := ` SELECT COUNT(DISTINCT data_time) AS count  FROM base_from_purang_data WHERE index_code in (` + utils.GetOrmInReplace(len(indexCodes)) + `) `
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, indexCodes).Scan(&count).Error
+	return
+}
+
+func GetPurangIndexDataByCodes(indexCode []string) (items []*BaseFromPurangData, err error) {
+	if len(indexCode) == 0 {
+		return
+	}
+	sql := ` SELECT *  FROM base_from_purang_data WHERE index_code in (` + utils.GetOrmInReplace(len(indexCode)) + `) ORDER BY data_time DESC  `
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, indexCode).Find(&items).Error
+	return
+}
+
+// GetPurangByConditionAndFrequency 根据条件获取普瑞指标列表
+func GetPurangByConditionAndFrequency(condition, frequency string, pars []interface{}) (items []*BaseFromPurangIndex, err error) {
+	sql := ` SELECT * FROM base_from_purang_index WHERE 1=1 `
+
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` AND frequency=?`
+	sql += ` ORDER BY sort ASC, base_from_purang_index_id ASC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars, frequency).Find(&items).Error
+	return
+}
+
+func GetPurangFrequencyByCondition(condition string, pars []interface{}) (items []string, err error) {
+	sql := `SELECT DISTINCT frequency FROM base_from_purang_index WHERE 1=1 `
+	if condition != "" {
+		sql += condition
+	}
+	sql += ` ORDER BY FIELD(frequency,'日度','周度','旬度','月度','季度','半年度','年度') `
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars...).Find(&items).Error
+	return
+}
+
+// GetPurangDataDataTimeByIndexId 根据指标id获取指标数据的日期列表
+func GetPurangDataDataTimeByIndexId(indexIdList []int) (items []string, err error) {
+	if len(indexIdList) == 0 {
+		return
+	}
+	sql := ` SELECT DISTINCT data_time FROM base_from_purang_data WHERE base_from_purang_index_id IN (` + utils.GetOrmInReplace(len(indexIdList)) + `) ORDER BY data_time DESC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, indexIdList).Find(&items).Error
+	for i, item := range items {
+		items[i] = utils.GormDateStrToDateStr(item)
+	}
+	return
+}
+
+type BaseFromPurangData struct {
+	BaseFromPurangDataId  int `orm:"column(base_from_purang_data_id);pk"`
+	BaseFromPurangIndexId int
+	IndexCode              string
+	DataTime               string
+	Value                  string
+	CreateTime             string
+	ModifyTime             string
+	DataTimestamp          int64
+}
+
+func (baseFromPurangData *BaseFromPurangData) AfterFind(tx *gorm.DB) (err error) {
+	baseFromPurangData.ModifyTime = utils.GormDateStrToDateTimeStr(baseFromPurangData.ModifyTime)
+	baseFromPurangData.CreateTime = utils.GormDateStrToDateTimeStr(baseFromPurangData.CreateTime)
+	baseFromPurangData.DataTime = utils.GormDateStrToDateStr(baseFromPurangData.DataTime)
+	return
+}
+
+type BaseFromPurangIndexSearchItem struct {
+	BaseFromPurangIndexId int `orm:"column(base_from_purang_index_id);pk"`
+	ClassifyId             int
+	ParentClassifyId       int
+	IndexCode              string
+	IndexName              string
+}
+
+// BatchCheckPurangEdbReq 指标数据结构体
+type BatchCheckPurangEdbReq struct {
+	Frequencies   string `description:"频度;枚举值:日度、周度、月度、季度、半年度、年度"`
+	Keyword       string `description:"关键字"`
+	ClassifyIds   string `description:"所选品种id列表"`
+	ListAll       bool   `form:"ListAll" json:"ListAll" description:"列表全选"`
+	TradeCodeList string `form:"TradeCodeList" json:"TradeCodeList" description:"全选为false时, 该数组为选中; 全选为true时, 该数组为不选的指标"`
+}
+
+// GetPurangItemList 模糊查询Purang数据库指标列表
+func GetPurangItemList(condition string) (items []*BaseFromPurangIndexSearchItem, err error) {
+	sql := "SELECT * FROM base_from_purang_index  WHERE 1=1"
+	if condition != "" {
+		sql += condition
+	}
+	err = global.DbMap[utils.DbNameIndex].Raw(sql).Find(&items).Error
+	return
+}
+
+func GetPurangIndexDataByCode(indexCode string, pageIndex, pageSize int) (list []*BaseFromPurangData, err error) {
+	sql := `SELECT * FROM base_from_purang_data WHERE index_code=? order by data_time desc limit ?,?`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, indexCode, pageIndex, pageSize).Find(&list).Error
+	return
+}
+
+func GetPurangIndexDataTotalByCode(indexCode string) (total int64, err error) {
+	sqlStr := `SELECT count(*) FROM base_from_purang_data WHERE index_code=?`
+	var totalNull sql.NullInt64
+	err = global.DbMap[utils.DbNameIndex].Raw(sqlStr, indexCode).Scan(&totalNull).Error
+	if !totalNull.Valid {
+		total = 0
+	} else {
+		total = totalNull.Int64
+	}
+	return
+}
+
+func GetBaseFromPurangIndexByIndexCode(indexCode string) (list *BaseFromPurangIndex, err error) {
+	sql := ` SELECT * FROM base_from_purang_index WHERE index_code=? `
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, indexCode).First(&list).Error
+	return
+}
+
+type BaseFromPurangIndexType struct {
+	Type2 string `gorm:"column:type_2"`
+	Type3 string `gorm:"column:type_3"`
+}
+
+// Update 更新Purang指标基础信息
+func (item *BaseFromPurangIndex) Update(cols []string) (err error) {
+	err = global.DbMap[utils.DbNameIndex].Select(cols).Updates(item).Error
+	return
+}
+
+// EditPurangIndexInfoResp 新增指标的返回
+type EditPurangIndexInfoResp struct {
+	BaseFromPurangIndexId int    `description:"指标ID"`
+	IndexCode              string `description:"指标code"`
+}
+
+type PurangIndexSource2EdbReq struct {
+	EdbCode       string
+	EdbName       string
+	Frequency     string
+	Unit          string
+	ClassifyId    int
+	AdminId       int
+	AdminRealName string
+}
+
+func GetPurangFrequencyByClassifyId(classifyId int) (items []*GlFrequency, err error) {
+	sql := ` SELECT frequency FROM base_from_purang_index WHERE classify_id = ? `
+	sql += ` GROUP BY frequency ORDER BY frequency ASC `
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, classifyId).Find(&items).Error
+	return
+} 

+ 139 - 0
models/data_manage/base_from_purang_classify.go

@@ -0,0 +1,139 @@
+package data_manage
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"time"
+)
+
+// BaseFromPurangClassify Purang原始数据分类表
+type BaseFromPurangClassify struct {
+	ClassifyId      int       `orm:"column(classify_id);pk" gorm:"column:classify_id;primary_key"`
+	ClassifyName    string    `description:"分类名称"`
+	ParentId        int       `description:"父级id"`
+	SysUserId       int       `description:"创建人id"`
+	SysUserRealName string    `description:"创建人姓名"`
+	Level           int       `description:"层级"`
+	Sort            int       `description:"排序字段,越小越靠前,默认值:10"`
+	ModifyTime      time.Time `description:"修改时间"`
+	CreateTime      time.Time `description:"创建时间"`
+}
+
+// GetBaseFromPurangClassifyCount 获取分类名称的个数
+func GetBaseFromPurangClassifyCount(classifyName string, parentId int) (count int, err error) {
+	sql := `SELECT COUNT(1) AS count FROM base_from_purang_classify WHERE classify_name=? AND parent_id=? `
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, classifyName, parentId).Scan(&count).Error
+	return
+}
+
+// GetBaseFromPurangClassifyById 通过分类id的获取分类信息
+func GetBaseFromPurangClassifyById(classifyId int) (item *BaseFromPurangClassify, err error) {
+	sql := `SELECT * FROM base_from_purang_classify WHERE classify_id=? `
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, classifyId).Find(&item).Error
+	return
+}
+
+// GetBaseFromPurangClassifyByIds 通过分类id的获取分类信息
+func GetBaseFromPurangClassifyByIds(classifyIds []int) (items []*BaseFromPurangClassify, err error) {
+	if len(classifyIds) == 0 {
+		return
+	}
+	sql := `SELECT * FROM base_from_purang_classify WHERE classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `) `
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, classifyIds).Find(&items).Error
+	return
+}
+
+// EditBaseFromPurangClassify 修改Purang原始数据分类
+func EditBaseFromPurangClassify(classifyId int, classifyName string) (err error) {
+	sql := `UPDATE base_from_purang_classify SET classify_name=?,modify_time=NOW() WHERE classify_id=? `
+	err = global.DbMap[utils.DbNameIndex].Exec(sql, classifyName, classifyId).Error
+	return
+}
+
+// UpdateBaseFromPurangClassifySort 修改Purang原始数据分类的排序
+func UpdateBaseFromPurangClassifySort(classifyId int) (err error) {
+	sql := `UPDATE base_from_purang_classify SET sort=classify_id, modify_time=NOW() WHERE classify_id=? `
+	err = global.DbMap[utils.DbNameIndex].Exec(sql, classifyId).Error
+	return
+}
+
+type BaseFromPurangClassifyItems struct {
+	ClassifyId          int                          `description:"分类ID"`
+	BaseFromPurangIndexId int                        `description:"指标类型ID"`
+	IndexCode           string                       `description:"指标唯一编码"`
+	ClassifyName        string                       `description:"分类名称"`
+	ClassifyNameEn      string                       `description:"分类名称"`
+	UniqueCode          string                       `description:"分类唯一编码"`
+	ParentId            int                          `description:"父级id"`
+	Level               int                          `description:"层级"`
+	Sort                int                          `description:"排序字段,越小越靠前,默认值:10"`
+	Children            []*BaseFromPurangClassifyItems `gorm:"-"`
+}
+
+type BaseFromPurangClassifyNameItems struct {
+	ClassifyId   int    `description:"分类ID"`
+	ClassifyName string `description:"分类名称"`
+	ParentId     int    `description:"父级id"`
+}
+
+type BaseFromPurangClassifyResp struct {
+	List []*BaseFromPurangClassifyItems
+}
+
+type BaseFromPurangClassifyNameResp struct {
+	List []*BaseFromPurangClassifyNameItems
+}
+
+type BaseFromPurangClassifyItemsButton struct {
+	AddButton    bool `description:"是否可添加"`
+	OpButton     bool `description:"是否可编辑"`
+	DeleteButton bool `description:"是否可删除"`
+	MoveButton   bool `description:"是否可移动"`
+}
+
+// GetBaseFromPurangClassifyByParentId 根据上级id获取当下的分类列表数据
+func GetBaseFromPurangClassifyByParentId(parentId int) (items []*BaseFromPurangClassifyItems, err error) {
+	sql := ` SELECT * FROM base_from_purang_classify WHERE parent_id=? order by sort asc,classify_id asc`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, parentId).Find(&items).Error
+	return
+}
+
+// GetAllBaseFromPurangClassify 获取所有的分类列表数据
+func GetAllBaseFromPurangClassify() (items []*BaseFromPurangClassifyItems, err error) {
+	sql := ` SELECT * FROM base_from_purang_classify order by parent_id asc, sort asc,classify_id asc`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql).Find(&items).Error
+	return
+}
+
+type DeleteBaseFromPurangClassifyReq struct {
+	ClassifyId int `description:"分类id"`
+	EdbInfoId  int `description:"指标id"`
+}
+
+type BaseFromPurangClassifyListResp struct {
+	AllNodes      []*BaseFromPurangClassifyItems
+	CanOpClassify bool `description:"是否允许操作分类"`
+}
+
+type BaseFromPurangClassifySimplify struct {
+	ClassifyId   int    `description:"分类id"`
+	ClassifyName string `description:"分类名称"`
+	ParentId     int
+}
+
+// GetFirstBaseFromPurangClassify 获取当前分类下,且排序数相同 的排序第一条的数据
+func GetFirstBaseFromPurangClassify() (item *BaseFromPurangClassify, err error) {
+	sql := ` SELECT * FROM base_from_purang_classify order by sort asc,classify_id asc limit 1`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql).First(&item).Error
+	return
+}
+
+// Update 更新分类基础信息
+func (BaseFromPurangClassify *BaseFromPurangClassify) Update(cols []string) (err error) {
+	err = global.DbMap[utils.DbNameIndex].Select(cols).Updates(BaseFromPurangClassify).Error
+	return
+}
+
+type AddPurangClassifyResp struct {
+	ClassifyId int
+} 

+ 309 - 0
models/data_manage/base_from_radish_research_classify.go

@@ -0,0 +1,309 @@
+package data_manage
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"strings"
+	"time"
+)
+
+// BaseFromRadishResearchClassify 萝卜投研原始数据分类表
+type BaseFromRadishResearchClassify struct {
+	BaseFromRadishResearchClassifyId int       `orm:"column(base_from_radish_research_classify_id);pk" gorm:"primaryKey"`
+	ClassifyName                     string    `description:"分类名称"`
+	ParentId                         int       `description:"父级id"`
+	Level                            int       `description:"层级"`
+	Sort                             int       `description:"排序字段,越小越靠前,默认值:10"`
+	SysUserId                        int       `description:"创建人id"`
+	SysUserRealName                  string    `description:"创建人姓名"`
+	LevelPath                        string    `description:"层级路径"`
+	RootId                           int       `description:"顶级分类ID"`
+	UniqueCode                       string    `description:"唯一编码"`
+	ModifyTime                       time.Time `description:"修改时间"`
+	CreateTime                       time.Time `description:"创建时间"`
+}
+
+func (m *BaseFromRadishResearchClassify) TableName() string {
+	return "base_from_radish_research_classify"
+}
+
+type BaseFromRadishResearchClassifyCols struct {
+	PrimaryId       string
+	ClassifyName    string
+	ParentId        string
+	SysUserId       string
+	SysUserRealName string
+	Level           string
+	Sort            string
+	RootId          string
+	LevelPath       string
+	UniqueCode      string
+	CreateTime      string
+	ModifyTime      string
+}
+
+func (m *BaseFromRadishResearchClassify) Cols() BaseFromRadishResearchClassifyCols {
+	return BaseFromRadishResearchClassifyCols{
+		PrimaryId:       "base_from_radish_research_classify_id",
+		ClassifyName:    "classify_name",
+		ParentId:        "parent_id",
+		SysUserId:       "sys_user_id",
+		SysUserRealName: "sys_user_real_name",
+		Level:           "level",
+		Sort:            "sort",
+		RootId:          "root_id",
+		LevelPath:       "level_path",
+		UniqueCode:      "unique_code",
+		CreateTime:      "create_time",
+		ModifyTime:      "modify_time",
+	}
+}
+
+func (m *BaseFromRadishResearchClassify) Create() (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.Create(m).Error
+	return
+}
+
+func (m *BaseFromRadishResearchClassify) CreateMulti(items []*BaseFromRadishResearchClassify) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.CreateInBatches(items, utils.MultiAddNum).Error
+	return
+}
+
+func (m *BaseFromRadishResearchClassify) Update(cols []string) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.Select(cols).Updates(m).Error
+	return
+}
+
+func (m *BaseFromRadishResearchClassify) Remove() (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Exec(sql, m.BaseFromRadishResearchClassifyId).Error
+	return
+}
+
+func (m *BaseFromRadishResearchClassify) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	err = o.Exec(sql, ids).Error
+	return
+}
+
+func (m *BaseFromRadishResearchClassify) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	err = o.Exec(sql, pars...).Error
+	return
+}
+
+func (m *BaseFromRadishResearchClassify) GetItemById(id int) (item *BaseFromRadishResearchClassify, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).First(&item).Error
+	return
+}
+
+func (m *BaseFromRadishResearchClassify) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *BaseFromRadishResearchClassify, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars...).First(&item).Error
+	return
+}
+
+func (m *BaseFromRadishResearchClassify) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars...).Scan(&count).Error
+	return
+}
+
+func (m *BaseFromRadishResearchClassify) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BaseFromRadishResearchClassify, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	err = o.Raw(sql, pars...).Find(&items).Error
+	return
+}
+
+func (m *BaseFromRadishResearchClassify) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*BaseFromRadishResearchClassify, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	pars = append(pars, startSize, pageSize)
+	err = o.Raw(sql, pars...).Find(&items).Error
+	return
+}
+
+// GetBaseFromRadishResearchClassify 获取所有分类
+func GetBaseFromRadishResearchClassify() (items []*BaseFromRadishResearchClassify, err error) {
+	sql := ` SELECT * FROM base_from_radish_research_classify ORDER BY parent_id ASC, sort ASC, base_from_radish_research_classify_id ASC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql).Find(&items).Error
+	return
+}
+
+// BaseFromRadishResearchClassifyItem 萝卜投研数据分类信息
+type BaseFromRadishResearchClassifyItem struct {
+	ClassifyId   int                                   `description:"分类ID"`
+	ClassifyName string                                `description:"分类名称"`
+	ParentId     int                                   `description:"父级id"`
+	Level        int                                   `description:"层级"`
+	Sort         int                                   `description:"排序字段"`
+	LevelPath    string                                `description:"层级路径"`
+	UniqueCode   string                                `description:"唯一编码"`
+	CreateTime   string                                `description:"创建时间"`
+	ModifyTime   string                                `description:"修改时间"`
+	Children     []*BaseFromRadishResearchClassifyItem `description:"子分类"`
+}
+
+func (m *BaseFromRadishResearchClassify) Format2Item() (item *BaseFromRadishResearchClassifyItem) {
+	item = new(BaseFromRadishResearchClassifyItem)
+	item.ClassifyId = m.BaseFromRadishResearchClassifyId
+	item.ClassifyName = m.ClassifyName
+	item.ParentId = m.ParentId
+	item.Level = m.Level
+	item.Sort = m.Sort
+	item.LevelPath = m.LevelPath
+	item.UniqueCode = m.UniqueCode
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, m.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, m.ModifyTime)
+	return
+}
+
+type RadishResearchClassifyAddReq struct {
+	ClassifyName string `description:"分类名称"`
+	ParentId     int    `description:"父级ID, 第一级传0"`
+	Level        int    `description:"层级, 第一级传0, 其余传上一级的层级"`
+}
+
+type RadishResearchClassifyEditReq struct {
+	ClassifyId   int    `description:"分类ID"`
+	ClassifyName string `description:"分类名称"`
+}
+
+func (m *BaseFromRadishResearchClassify) GetSortMax(parentId int) (sort int, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`SELECT COALESCE(Max(%s), 0) AS sort FROM %s WHERE %s = ?`, m.Cols().Sort, m.TableName(), m.Cols().ParentId)
+	err = o.Raw(sql, parentId).Scan(&sort).Error
+	return
+}
+
+type RadishResearchClassifyRemoveReq struct {
+	ClassifyId int `description:"分类ID"`
+}
+
+type BaseFromRadishResearchClassifyListItem struct {
+	NodeType     int                                       `description:"节点类型: 0-分类; 1-指标"`
+	ClassifyId   int                                       `description:"分类ID"`
+	ClassifyName string                                    `description:"分类名称"`
+	IndexId      int                                       `description:"指标ID"`
+	IndexCode    string                                    `description:"指标编码"`
+	IndexName    string                                    `description:"指标名称"`
+	ParentId     int                                       `description:"父级ID"`
+	Level        int                                       `description:"层级"`
+	Sort         int                                       `description:"排序"`
+	UniqueCode   string                                    `description:"唯一编码, 指标的话用indexCode"`
+	Children     []*BaseFromRadishResearchClassifyListItem `description:"子分类"`
+}
+
+type BaseFromRadishResearchClassifyMoveReq struct {
+	ClassifyId       int `description:"分类ID"`
+	ParentClassifyId int `description:"父级分类ID"`
+	PrevClassifyId   int `description:"上一个兄弟节点分类ID"`
+	NextClassifyId   int `description:"下一个兄弟节点分类ID"`
+	ItemId           int `description:"指标ID, 如果指标ID有值,则移动对象为指标,否则认为移动对象为分类"`
+	PrevItemId       int `description:"上一个指标ID"`
+	NextItemId       int `description:"下一个指标ID"`
+}
+
+func GetRadishResearchClassifyById(classifyId int) (item *BaseFromRadishResearchClassify, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := `SELECT * FROM base_from_radish_research_classify WHERE base_from_radish_research_classify_id = ?`
+	err = o.Raw(sql, classifyId).First(&item).Error
+	return
+}
+
+func GetRadishResearchClassifyByRootIdLevel(rootId int, orderStr string) (items []*BaseFromRadishResearchClassify, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := ` SELECT * FROM base_from_radish_research_classify WHERE root_id = ? `
+	if orderStr != "" {
+		sql += orderStr
+	} else {
+		sql += ` order by level desc, sort asc, base_from_radish_research_classify_id asc`
+	}
+	err = o.Raw(sql, rootId).Find(&items).Error
+	return
+}
+
+// UpdateRadishResearchClassifySortByParentId 根据父类id更新排序
+func UpdateRadishResearchClassifySortByParentId(parentId, classifyId, nowSort int, updateSort string) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := ` update base_from_radish_research_classify set sort = ` + updateSort + ` WHERE parent_id = ? AND sort > ? `
+	if classifyId > 0 {
+		sql += ` or ( base_from_radish_research_classify_id > ` + fmt.Sprint(classifyId) + ` and sort = ` + fmt.Sprint(nowSort) + `)`
+	}
+	err = o.Exec(sql, parentId, nowSort).Error
+	return
+}
+
+// GetFirstRadishResearchClassifyByParentId 获取当前父级分类下,且排序数相同 的排序第一条的数据
+func GetFirstRadishResearchClassifyByParentId(parentId int) (item *BaseFromRadishResearchClassify, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := ` SELECT * FROM base_from_radish_research_classify WHERE parent_id = ? ORDER BY sort ASC,base_from_radish_research_classify_id ASC LIMIT 1`
+	err = o.Raw(sql, parentId).First(&item).Error
+	return
+}
+
+func UpdateRadishResearchClassifyChildByParentClassifyId(classifyIds []int, rootId int, levelStep int) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	var pars []interface{}
+	pars = append(pars, rootId, levelStep)
+	pars = append(pars, classifyIds)
+	// 更新相关联的二级分类的parentId,和classify_name_second
+	sql := `update base_from_radish_research_classify SET root_id = ?, level = level+? where base_from_radish_research_classify_id IN (` + utils.GetOrmInReplace(len(classifyIds)) + `)`
+	err = o.Exec(sql, pars...).Error
+	return
+}
+
+// UpdateLevelPath 更新层级路径
+func (m *BaseFromRadishResearchClassify) UpdateLevelPath(classifyId int, levelPath string) (err error) {
+	sql := fmt.Sprintf(`UPDATE %s SET %s = ? WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().LevelPath, m.Cols().PrimaryId)
+	err = global.DbMap[utils.DbNameIndex].Exec(sql, levelPath, classifyId).Error
+	return
+}
+
+// GetRadishResearchClassifyByParentIdAndName 根据父级ID和名称获取分类
+func GetRadishResearchClassifyByParentIdAndName(parentId int, classifyName string, classifyId int) (item *EdbClassify, err error) {
+	sql := `SELECT * FROM base_from_radish_research_classify WHERE parent_id = ? AND classify_name = ? AND base_from_radish_research_classify_id <> ? LIMIT 1`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, parentId, classifyName, classifyId).First(&item).Error
+	return
+}

+ 192 - 0
models/data_manage/base_from_radish_research_data.go

@@ -0,0 +1,192 @@
+package data_manage
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"strings"
+	"time"
+)
+
+// BaseFromRadishResearchData 萝卜投研-指标数据
+type BaseFromRadishResearchData struct {
+	BaseFromRadishResearchDataId  int       `orm:"column(base_from_radish_research_data_id);pk" gorm:"primaryKey"`
+	BaseFromRadishResearchIndexId int       `description:"指标ID"`
+	IndexCode                     string    `description:"指标编码"`
+	DataTime                      time.Time `description:"数据日期"`
+	Value                         float64   `description:"数据值"`
+	CreateTime                    time.Time `description:"创建时间"`
+	ModifyTime                    time.Time `description:"修改时间"`
+	DataTimestamp                 int64     `description:"数据日期时间戳"`
+}
+
+func (m *BaseFromRadishResearchData) TableName() string {
+	return "base_from_radish_research_data"
+}
+
+type BaseFromRadishResearchDataCols struct {
+	PrimaryId                     string
+	BaseFromRadishResearchIndexId string
+	IndexCode                     string
+	DataTime                      string
+	Value                         string
+	UniqueCode                    string
+	CreateTime                    string
+	ModifyTime                    string
+	DataTimestamp                 string
+}
+
+func (m *BaseFromRadishResearchData) Cols() BaseFromRadishResearchDataCols {
+	return BaseFromRadishResearchDataCols{
+		PrimaryId:                     "base_from_radish_research_data_id",
+		BaseFromRadishResearchIndexId: "base_from_radish_research_index_id",
+		IndexCode:                     "index_code",
+		DataTime:                      "data_time",
+		Value:                         "value",
+		UniqueCode:                    "unique_code",
+		CreateTime:                    "create_time",
+		ModifyTime:                    "modify_time",
+		DataTimestamp:                 "data_timestamp",
+	}
+}
+
+func (m *BaseFromRadishResearchData) Create() (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.Create(m).Error
+	return
+}
+
+func (m *BaseFromRadishResearchData) CreateMulti(items []*BaseFromRadishResearchData) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.CreateInBatches(items, utils.MultiAddNum).Error
+	return
+}
+
+func (m *BaseFromRadishResearchData) Update(cols []string) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.Select(cols).Updates(m).Error
+	return
+}
+
+func (m *BaseFromRadishResearchData) Remove() (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Exec(sql, m.BaseFromRadishResearchDataId).Error
+	return
+}
+
+func (m *BaseFromRadishResearchData) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	err = o.Exec(sql, ids).Error
+	return
+}
+
+func (m *BaseFromRadishResearchData) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	err = o.Exec(sql, pars...).Error
+	return
+}
+
+func (m *BaseFromRadishResearchData) GetItemById(id int) (item *BaseFromRadishResearchData, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).First(&item).Error
+	return
+}
+
+func (m *BaseFromRadishResearchData) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *BaseFromRadishResearchData, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars...).First(&item).Error
+	return
+}
+
+func (m *BaseFromRadishResearchData) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars...).Scan(&count).Error
+	return
+}
+
+func (m *BaseFromRadishResearchData) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BaseFromRadishResearchData, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	err = o.Raw(sql, pars...).Find(&items).Error
+	return
+}
+
+func (m *BaseFromRadishResearchData) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*BaseFromRadishResearchData, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	pars = append(pars, startSize, pageSize)
+	err = o.Raw(sql, pars...).Find(&items).Error
+	return
+}
+
+// BaseFromRadishResearchDataItem 萝卜投研数据信息
+type BaseFromRadishResearchDataItem struct {
+	DataId        int     `description:"数据ID"`
+	IndexId       int     `description:"指标ID"`
+	IndexCode     string  `description:"指标编码"`
+	DataTime      string  `description:"数据日期"`
+	Value         float64 `description:"数据值"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+}
+
+func (m *BaseFromRadishResearchData) Format2Item() (item *BaseFromRadishResearchDataItem) {
+	item = new(BaseFromRadishResearchDataItem)
+	item.DataId = m.BaseFromRadishResearchDataId
+	item.IndexId = m.BaseFromRadishResearchIndexId
+	item.IndexCode = m.IndexCode
+	item.DataTime = utils.TimeTransferString(utils.FormatDate, m.DataTime)
+	item.Value = m.Value
+	item.DataTimestamp = m.DataTimestamp
+	return
+}
+
+func (m *BaseFromRadishResearchData) GetDataTimeByIndexIds(indexIds []int) (items []string, err error) {
+	if len(indexIds) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`SELECT DISTINCT %s FROM %s WHERE %s IN ? ORDER BY %s DESC`, m.Cols().DataTime, m.TableName(), m.Cols().BaseFromRadishResearchIndexId, m.Cols().DataTime)
+	err = o.Raw(sql, indexIds).Find(&items).Error
+	if err != nil {
+		return
+	}
+	for i, v := range items {
+		items[i] = utils.GormDateStrToDateStr(v)
+	}
+	return
+}

+ 531 - 0
models/data_manage/base_from_radish_research_index.go

@@ -0,0 +1,531 @@
+package data_manage
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+// BaseFromRadishResearchIndex 萝卜投研数据
+type BaseFromRadishResearchIndex struct {
+	BaseFromRadishResearchIndexId int       `orm:"column(base_from_radish_research_index_id);pk" gorm:"primaryKey"`
+	IndexCode                     string    `description:"指标编码"`
+	IndexName                     string    `description:"指标名称"`
+	ClassifyId                    int       `description:"分类ID"`
+	Unit                          string    `description:"单位"`
+	Source                        string    `description:"数据来源"`
+	Frequency                     string    `description:"频度"`
+	StartDate                     time.Time `description:"开始日期"`
+	EndDate                       time.Time `description:"结束日期"`
+	Sort                          int       `description:"排序"`
+	IsStop                        int       `description:"是否停更:0-否;1-停更"`
+	TerminalCode                  string    `description:"所属终端编码"`
+	EdbExist                      int       `description:"指标库是否已添加指标:0-否;1-是"`
+	FilePath                      string    `description:"文件存储路径"`
+	LatestValue                   float64   `description:"数据最新值"`
+	CreateTime                    time.Time `description:"创建时间"`
+	ModifyTime                    time.Time `description:"修改时间"`
+}
+
+func (m *BaseFromRadishResearchIndex) TableName() string {
+	return "base_from_radish_research_index"
+}
+
+type BaseFromRadishResearchIndexCols struct {
+	PrimaryId    string
+	IndexCode    string
+	IndexName    string
+	ClassifyId   string
+	Unit         string
+	Source       string
+	Frequency    string
+	StartDate    string
+	EndDate      string
+	Sort         string
+	IsStop       string
+	TerminalCode string
+	EdbExist     string
+	FilePath     string
+	LatestValue  string
+	CreateTime   string
+	ModifyTime   string
+}
+
+func (m *BaseFromRadishResearchIndex) Cols() BaseFromRadishResearchIndexCols {
+	return BaseFromRadishResearchIndexCols{
+		PrimaryId:    "base_from_radish_research_index_id",
+		IndexCode:    "index_code",
+		IndexName:    "index_name",
+		ClassifyId:   "classify_id",
+		Unit:         "unit",
+		Source:       "source",
+		Frequency:    "frequency",
+		StartDate:    "start_date",
+		EndDate:      "end_date",
+		Sort:         "sort",
+		IsStop:       "is_stop",
+		EdbExist:     "edb_exist",
+		TerminalCode: "terminal_code",
+		FilePath:     "file_path",
+		LatestValue:  "latest_value",
+		CreateTime:   "create_time",
+		ModifyTime:   "modify_time",
+	}
+}
+
+func (m *BaseFromRadishResearchIndex) Create() (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.Create(m).Error
+	return
+}
+
+func (m *BaseFromRadishResearchIndex) CreateMulti(items []*BaseFromRadishResearchIndex) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.CreateInBatches(items, utils.MultiAddNum).Error
+	return
+}
+
+func (m *BaseFromRadishResearchIndex) Update(cols []string) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.Select(cols).Updates(m).Error
+	return
+}
+
+func (m *BaseFromRadishResearchIndex) Remove() (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Exec(sql, m.BaseFromRadishResearchIndexId).Error
+	return
+}
+
+func (m *BaseFromRadishResearchIndex) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	err = o.Exec(sql, ids).Error
+	return
+}
+
+func (m *BaseFromRadishResearchIndex) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	err = o.Exec(sql, pars...).Error
+	return
+}
+
+func (m *BaseFromRadishResearchIndex) GetItemById(id int) (item *BaseFromRadishResearchIndex, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).First(&item).Error
+	return
+}
+
+func (m *BaseFromRadishResearchIndex) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *BaseFromRadishResearchIndex, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars...).First(&item).Error
+	return
+}
+
+func (m *BaseFromRadishResearchIndex) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars...).Scan(&count).Error
+	return
+}
+
+func (m *BaseFromRadishResearchIndex) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*BaseFromRadishResearchIndex, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	err = o.Raw(sql, pars...).Find(&items).Error
+	return
+}
+
+func (m *BaseFromRadishResearchIndex) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*BaseFromRadishResearchIndex, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	pars = append(pars, startSize, pageSize)
+	err = o.Raw(sql, pars...).Find(&items).Error
+	return
+}
+
+// BaseFromRadishResearchIndexItem 萝卜投研数据信息
+type BaseFromRadishResearchIndexItem struct {
+	IndexId     int     `description:"指标ID"`
+	ClassifyId  int     `description:"分类ID"`
+	IndexCode   string  `description:"指标编码"`
+	IndexName   string  `description:"指标名称"`
+	Unit        string  `description:"单位"`
+	Source      string  `description:"数据来源"`
+	Frequency   string  `description:"频度"`
+	StartDate   string  `description:"开始日期"`
+	EndDate     string  `description:"结束日期"`
+	Sort        int     `description:"排序"`
+	LatestValue float64 `description:"最新值"`
+	EdbExist    int     `description:"是否加入指标:0-未加入;1-已加入"`
+	CreateTime  string  `description:"创建时间"`
+	ModifyTime  string  `description:"修改时间"`
+}
+
+func (m *BaseFromRadishResearchIndex) Format2Item() (item *BaseFromRadishResearchIndexItem) {
+	item = new(BaseFromRadishResearchIndexItem)
+	item.ClassifyId = m.ClassifyId
+	item.IndexId = m.BaseFromRadishResearchIndexId
+	item.IndexCode = m.IndexCode
+	item.IndexName = m.IndexName
+	item.Unit = m.Unit
+	item.Source = m.Source
+	item.Frequency = m.Frequency
+	item.StartDate = utils.TimeTransferString(utils.FormatDate, m.StartDate)
+	item.EndDate = utils.TimeTransferString(utils.FormatDate, m.EndDate)
+	item.Sort = m.Sort
+	item.LatestValue = m.LatestValue
+	item.EdbExist = m.EdbExist
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, m.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, m.ModifyTime)
+	return
+}
+
+// BaseFromRadishResearchIndexDetail 指标详情
+type BaseFromRadishResearchIndexDetail struct {
+	*BaseFromRadishResearchIndexItem
+	DataList []*BaseFromRadishResearchDataItem
+}
+
+func (m *BaseFromRadishResearchIndex) UpdateClassifyMulti(ids []int, classifyId int) (err error) {
+	if len(ids) == 0 || classifyId <= 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`UPDATE %s SET %s = ?, %s = NOW() WHERE %s IN (%s)`, m.TableName(), m.Cols().ClassifyId, m.Cols().ModifyTime, m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	err = o.Exec(sql, classifyId, ids).Error
+	return
+}
+
+// RadishResearchSearchEdbReq 搜索指标请求体
+//type RadishResearchSearchEdbReq struct {
+//	StockCode string `form:"StockCode" description:"证券代码" `
+//	EdbCode   string `form:"EdbCode" description:"指标代码"`
+//	StartTime string `form:"StartTime" description:"每日数据开始时间"`
+//	EndTime   string `form:"EndTime" description:"每日数据结束时间"`
+//	Interval  int    `form:"Interval" description:"时间周期"`
+//	Fill      string `form:"Fill" description:"非交易间隔处理"`
+//	CPS       string `form:"CPS" description:"复权方式"`
+//	BaseDate  string `form:"BaseDate" description:"复权基点"`
+//}
+
+// RadishResearchExistCheckResp 指标存在校验响应
+//type RadishResearchExistCheckResp struct {
+//	ExistAll   bool                            `description:"指标是否全部存在: true-是; false-否"`
+//	ExistIndex []RadishResearchExistCheckIndex `description:"已存在的指标信息"`
+//}
+
+// RadishResearchExistCheckIndex 指标存在校验-指标信息
+//type RadishResearchExistCheckIndex struct {
+//	IndexId   int    `description:"指标ID"`
+//	IndexCode string `description:"指标编码"`
+//	IndexName string `description:"指标名称"`
+//}
+
+// RadishResearchSearchEdbResp 响应体
+//type RadishResearchSearchEdbResp struct {
+//	StockCode string                        `description:"证券代码" `
+//	EdbCode   string                        `description:"指标代码"`
+//	IndexName string                        `description:"指标名称"`
+//	Frequency int                           `description:"频度: 1-60"`
+//	IndexData []RadishResearchSearchEdbData `description:"指标数据"`
+//}
+
+//type RadishResearchSearchEdbData struct {
+//	DataTime string
+//	Value    string
+//}
+
+// RadishResearchIndexWithData 萝卜投研指标
+//type RadishResearchIndexWithData struct {
+//	StockCode string                     `description:"证券代码"`
+//	EdbCode   string                     `description:"指标代码"`
+//	IndexData []*RadishResearchIndexData `description:"指标数据"`
+//}
+
+// RadishResearchIndexData 萝卜投研指标数据
+//type RadishResearchIndexData struct {
+//	DataTime time.Time `description:"数据时间"`
+//	Value    float64   `description:"数据值"`
+//}
+
+// RadishResearchIndexDataLibResp 萝卜投研指标-指标库响应
+//type RadishResearchIndexDataLibResp struct {
+//	Ret     int
+//	Msg     string
+//	ErrMsg  string
+//	ErrCode string
+//	Data    []*RadishResearchIndexWithData
+//	Success bool `description:"true 执行成功,false 执行失败"`
+//}
+
+// RadishResearchAddEdbReq 新增指标请求体
+//type RadishResearchAddEdbReq struct {
+//	StartTime string                            `description:"每日数据开始时间"`
+//	EndTime   string                            `description:"每日数据结束时间"`
+//	Interval  int                               `description:"时间周期"`
+//	Fill      string                            `description:"非交易间隔处理"`
+//	CPS       string                            `description:"复权方式"`
+//	BaseDate  string                            `description:"复权基点"`
+//	IndexList []*RadishResearchBaseAddIndexItem `description:"指标信息"`
+//}
+
+//type RadishResearchBaseAddIndexItem struct {
+//	ClassifyId int    `description:"分类ID"`
+//	Unit       string `description:"单位"`
+//	IndexName  string `description:"指标名称"`
+//	Frequency  string `description:"频度"`
+//	StockCode  string `description:"证券代码"`
+//	EdbCode    string `description:"指标代码"`
+//}
+
+//type RadishResearchBaseAddReq struct {
+//	StartTime                      string `description:"每日数据开始时间"`
+//	EndTime                        string `description:"每日数据结束时间"`
+//	Interval                       int    `description:"时间周期"`
+//	Fill                           string `description:"非交易间隔处理"`
+//	CPS                            string `description:"复权方式"`
+//	BaseDate                       string `description:"复权基点"`
+//	SysAdminId                     int    `description:"创建人ID"`
+//	SysAdminName                   string `description:"创建人姓名"`
+//	RadishResearchBaseAddIndexItem `description:"指标信息"`
+//}
+
+// RadishResearchIndexEditReq 编辑指标请求
+type RadishResearchIndexEditReq struct {
+	IndexId    int `description:"指标ID"`
+	ClassifyId int `description:"分类ID"`
+}
+
+// RadishResearchIndexRemoveReq 删除指标请求
+type RadishResearchIndexRemoveReq struct {
+	IndexId int `description:"指标ID"`
+}
+
+// RadishResearchIndexListChoiceReq 指标列表选择请求
+type RadishResearchIndexListChoiceReq struct {
+	ClassifyId   string `form:"ClassifyId" description:"分类ID(多选)"`
+	IncludeChild bool   `form:"IncludeChild" description:"是否包含子分类"`
+	Frequency    string `form:"Frequency" description:"频度(多选)"`
+	SysAdminId   string `form:"SysAdminId" description:"创建人ID(多选)"`
+	Keywords     string `form:"Keywords" description:"关键词: 指标ID/指标名称"`
+	ListIds      string `form:"ListIds" description:"列表选择项/排除项(全选为true时为排除项)"`
+	SelectAll    bool   `form:"SelectAll" description:"是否全选: true/false"`
+}
+
+// RadishResearchIndexListChoiceItem 指标列表选择响应
+type RadishResearchIndexListChoiceItem struct {
+	IndexId   int    `description:"指标ID"`
+	IndexCode string `description:"指标编码"`
+	IndexName string `description:"指标名称"`
+}
+
+// RadishResearchIndexMultiOptReq 指标批量操作请求
+type RadishResearchIndexMultiOptReq struct {
+	IndexIds       []int `description:"指标IDs"`
+	OptType        int   `description:"操作类型: 1-移动分类; 2-删除; 3-刷新"`
+	MoveClassifyId int   `description:"移动至分类ID"`
+	RefreshType    int   `description:"刷新类型: 1-最近6小时; 2-全部刷新"`
+}
+
+// RadishResearchIndexListForm 指标列表表单
+type RadishResearchIndexListForm struct {
+	PageSize       int    `form:"PageSize" description:"每页数据量"`
+	CurrentIndex   int    `form:"CurrentIndex" description:"页码"`
+	ClassifyId     int    `form:"ClassifyId" description:"分类ID(查询结果包含所有子分类指标)"`
+	ClassifyIds    string `form:"ClassifyIds" description:"分类IDs"`
+	IgnoreEdbExist bool   `form:"IgnoreEdbExist" description:"忽略已加入指标库的"`
+	Frequencies    string `form:"Frequencies" description:"频度(多选)"`
+	Keyword        string `form:"Keyword" description:"关键词-指标ID/指标名称"`
+}
+
+type RadishResearchIndexPageListResp struct {
+	List   []*BaseFromRadishResearchIndexItem
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+func GetRadishResearchIndexById(indexId int) (item *BaseFromRadishResearchIndex, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := ` SELECT * FROM base_from_radish_research_index WHERE base_from_radish_research_index_id = ?`
+	err = o.Raw(sql, indexId).First(&item).Error
+	return
+}
+
+// UpdateRadishResearchIndexSortByClassifyId 根据分类id更新排序
+func UpdateRadishResearchIndexSortByClassifyId(classifyId, nowSort int, prevEdbInfoId int, updateSort string) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := ` update base_from_radish_research_index set sort = ` + updateSort + ` WHERE classify_id=?`
+	if prevEdbInfoId > 0 {
+		sql += ` AND ( sort > ? or ( base_from_radish_research_index_id > ` + fmt.Sprint(prevEdbInfoId) + ` and sort=` + fmt.Sprint(nowSort) + ` )) `
+	} else {
+		sql += ` AND ( sort > ? )`
+	}
+	err = o.Exec(sql, classifyId, nowSort).Error
+	return
+}
+
+// GetRadishResearchIndexMaxSortByClassifyId 获取分类下指标的最大的排序数
+func GetRadishResearchIndexMaxSortByClassifyId(classifyId int) (sort int, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := `SELECT COALESCE(MAX(sort), 0) AS sort FROM base_from_radish_research_index WHERE classify_id = ?`
+	err = o.Raw(sql, classifyId).Scan(&sort).Error
+	return
+}
+
+// GetFirstRadishResearchIndexByClassifyId 获取当前分类下,且排序数相同 的排序第一条的数据
+func GetFirstRadishResearchIndexByClassifyId(classifyId int) (item *BaseFromRadishResearchIndex, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := ` SELECT * FROM base_from_radish_research_index WHERE classify_id = ? ORDER BY sort ASC,base_from_radish_research_index_id ASC LIMIT 1`
+	err = o.Raw(sql, classifyId).First(&item).Error
+	return
+}
+
+type RadishResearchIndexConvert2EdbRule struct {
+	ConvertType  int `description:"转换类型: 1-指定时间值; 2-区间计算值"`
+	ConvertFixed struct {
+		FixedDay  int    `description:"指定时间值日期: 1-当日; 2-前一日"`
+		FixedTime string `description:"指定时间值时点(HH:mm:ss)"`
+	} `description:"指定时间值"`
+	ConvertArea struct {
+		StartDay      int    `description:"起始时间日期: 1-当日; 2-前一日"`
+		StartTime     string `description:"起始时间时点(HH:mm:ss)"`
+		EndDay        int    `description:"截止时间日期: 1-当日; 2-前一日"`
+		EndTime       string `description:"截止时间时点(HH:mm:ss)"`
+		CalculateType int    `description:"计算类型: 1-区间均值; 2-最大值; 3-最小值"`
+	} `description:"区间计算值"`
+}
+
+// RadishResearchIndexMultiSave2EdbPreReq 批量添加指标库请求
+type RadishResearchIndexMultiSave2EdbPreReq struct {
+	ConvertRule RadishResearchIndexConvert2EdbRule
+	IndexIds    []int `description:"指标IDs"`
+}
+
+type RadishResearchIndexMultiSave2EdbReq struct {
+	ConvertRule RadishResearchIndexConvert2EdbRule
+	NewIndexes  []*RadishResearchIndexMultiSave2EdbPreItem `description:"新增指标"`
+}
+
+type RadishResearchIndexMultiSave2EdbLibReq struct {
+	ConvertRule RadishResearchIndexConvert2EdbRule
+	NewIndex    *RadishResearchIndexMultiSave2EdbPreItem `description:"新增指标"`
+}
+
+// RadishResearchIndexMultiSave2EdbPreItem 批量新增指标库信息
+type RadishResearchIndexMultiSave2EdbPreItem struct {
+	IndexId      int    `description:"指标ID"`
+	IndexCode    string `description:"指标编码"`
+	IndexName    string `description:"原指标名称"`
+	NewIndexName string `description:"新指标名称"`
+	StockCode    string `description:"证券代码"`
+	EdbCode      string `description:"指标代码"`
+	Unit         string `description:"单位"`
+	Frequency    string `description:"原频度"`
+	NewFrequency string `description:"新频度(固定日度)"`
+	ClassifyId   int    `description:"指标库分类ID"`
+	SysAdminId   int    `description:"创建人ID"`
+	SysAdminName string `description:"创建人姓名"`
+	Tips         string `description:"提示信息"`
+	ErrMsg       string `description:"错误信息"`
+}
+
+type RadishResearchIndexMultiSave2EdbResp struct {
+	Exist   []*RadishResearchIndexMultiSave2EdbPreItem `description:"已存在的指标"`
+	Success []*RadishResearchIndexMultiSave2EdbPreItem `description:"添加成功的指标"`
+	Fail    []*RadishResearchIndexMultiSave2EdbPreItem `description:"添加失败的指标"`
+}
+
+type RadishResearchIndexMultiOptResp struct {
+	Success []*RadishResearchIndexBaseInfo `description:"操作成功的指标"`
+	Fail    []*RadishResearchIndexBaseInfo `description:"操作失败的指标"`
+}
+
+type RadishResearchIndexBaseInfo struct {
+	IndexId   int    `description:"指标ID"`
+	IndexCode string `description:"指标编码"`
+	IndexName string `description:"指标名称"`
+}
+
+func (m *BaseFromRadishResearchIndex) RemoveIndexAndData(indexCode string) (err error) {
+	tx := global.DbMap[utils.DbNameIndex].Begin()
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().IndexCode)
+	e := tx.Exec(sql, indexCode).Error
+	if e != nil {
+		err = fmt.Errorf("delete index err: %s", e.Error())
+		return
+	}
+
+	dataOb := new(BaseFromRadishResearchData)
+	sql = fmt.Sprintf(`DELETE FROM %s WHERE %s = ?`, dataOb.TableName(), dataOb.Cols().IndexCode)
+	e = tx.Exec(sql, indexCode).Error
+	if e != nil {
+		err = fmt.Errorf("delete index data err: %s", e.Error())
+		return
+	}
+	return
+}
+
+// RadishResearchIndexSelectReq 批量加入指标-选择指标
+type RadishResearchIndexSelectReq struct {
+	ClassifyIds []int    `description:"分类IDs"`
+	Frequencies []string `description:"频度"`
+	Keyword     string   `description:"关键词-指标ID/指标名称"`
+	IndexCodes  []string `description:"SelectAll-true: IndexCodes为排除的指标, SelectAll-false: IndexCodes为选择的指标"`
+	SelectAll   bool     `description:"列表全选"`
+}
+
+// UpdateEdbExists 更新是否加入指标库字段
+func (m *BaseFromRadishResearchIndex) UpdateEdbExists(exist int, indexCodes []string) (err error) {
+	if len(indexCodes) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sql := fmt.Sprintf(`UPDATE %s SET %s = ? WHERE %s IN ?`, m.TableName(), m.Cols().EdbExist, m.Cols().IndexCode)
+	err = o.Exec(sql, exist, indexCodes).Error
+	return
+}

+ 1 - 1
models/data_manage/edb_data_base.go

@@ -353,7 +353,7 @@ func GetBaseIndexInfoByEdbCode(edbCode string, source int) (item *BaseIndexInfo,
 
 	sql = fmt.Sprintf(sql, tableName)
 	//err = o.Raw(sql, pars).QueryRow(&item)
-	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars...).Find(&item).Error
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars...).First(&item).Error
 	return
 }
 

+ 1 - 2
models/data_manage/edb_info_relation.go

@@ -3,10 +3,9 @@ package data_manage
 import (
 	"eta/eta_api/global"
 	"eta/eta_api/utils"
+	"github.com/rdlucklib/rdluck_tools/paging"
 	"gorm.io/gorm"
 	"time"
-
-	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 type EdbInfoRelation struct {

+ 33 - 13
models/english_report.go

@@ -51,6 +51,8 @@ type EnglishReport struct {
 	ApproveId          int       `description:"审批ID"`
 	DetailImgUrl       string    `description:"报告详情长图地址"`
 	DetailPdfUrl       string    `description:"报告详情PDF地址"`
+	DetailImgUrlMobile string    `description:"报告详情长图地址-手机端"`
+	DetailPdfUrlMobile string    `description:"报告详情PDF地址-手机端"`
 	EmailHasFail       int       `description:"是否存在邮件发送失败的记录: 0-否; 1-是"`
 }
 
@@ -133,7 +135,7 @@ type AddEnglishReportReq struct {
 	Content            string                     `description:"内容"`
 	CreateTime         string                     `description:"创建时间"`
 	Overview           string                     `description:"英文概述部分"`
-	ExcelReferences    []excel.ExcelReferencesReq `description:"引用的Excel信息"`
+	ExcelReferences    []excel.ExcelReferencesReq `gorm:"-" description:"引用的Excel信息"`
 }
 
 type AddEnglishReportResp struct {
@@ -250,18 +252,18 @@ type EnglishReportDetail struct {
 }
 
 func (obj *EnglishReportDetail) AfterFind(tx *gorm.DB) (err error) {
-			obj.CreateTime = utils.GormDateStrToDateTimeStr(obj.CreateTime)
-			obj.ModifyTime = utils.GormDateStrToDateTimeStr(obj.ModifyTime)
-			obj.PublishTime = utils.GormDateStrToDateTimeStr(obj.PublishTime)
-			obj.PrePublishTime = utils.GormDateStrToDateTimeStr(obj.PrePublishTime)
+	obj.CreateTime = utils.GormDateStrToDateTimeStr(obj.CreateTime)
+	obj.ModifyTime = utils.GormDateStrToDateTimeStr(obj.ModifyTime)
+	obj.PublishTime = utils.GormDateStrToDateTimeStr(obj.PublishTime)
+	obj.PrePublishTime = utils.GormDateStrToDateTimeStr(obj.PrePublishTime)
 	return
 }
 
 func (obj *EnglishReportDetail) ConvertTimeStr() {
-			obj.CreateTime = utils.GormDateStrToDateTimeStr(obj.CreateTime)
-			obj.ModifyTime = utils.GormDateStrToDateTimeStr(obj.ModifyTime)
-			obj.PublishTime = utils.GormDateStrToDateTimeStr(obj.PublishTime)
-			obj.PrePublishTime = utils.GormDateStrToDateTimeStr(obj.PrePublishTime)
+	obj.CreateTime = utils.GormDateStrToDateTimeStr(obj.CreateTime)
+	obj.ModifyTime = utils.GormDateStrToDateTimeStr(obj.ModifyTime)
+	obj.PublishTime = utils.GormDateStrToDateTimeStr(obj.PublishTime)
+	obj.PrePublishTime = utils.GormDateStrToDateTimeStr(obj.PrePublishTime)
 	return
 }
 
@@ -324,6 +326,8 @@ type EnglishReportList struct {
 	ApproveTime        string    `description:"审批时间"`
 	DetailImgUrl       string    `description:"报告详情长图地址"`
 	DetailPdfUrl       string    `description:"报告详情PDF地址"`
+	DetailImgUrlMobile string    `description:"报告详情长图地址-手机端"`
+	DetailPdfUrlMobile string    `description:"报告详情PDF地址-手机端"`
 }
 
 type EnglishReportListResp struct {
@@ -482,10 +486,10 @@ type EnglishClassifyList struct {
 	ShowType      int       `description:"展示类型:1-列表 2-专栏"`
 	IsShow        int       `description:"是否在小程序显示:1-显示 0-隐藏"`
 	//ClassifyType  int       `description:"分类类型:0英文报告,1英文线上路演"`
-	EnPermissions  []int `description:"英文权限IDs"`
-	Enabled        int   `description:"是否可用,1可用,0禁用"`
-	IsEnableDelete int   `description:"是否允许删除: 1-允许 0-不允许"`
-	Child          []*EnglishClassifyList
+	EnPermissions  []int                  `description:"英文权限IDs"`
+	Enabled        int                    `description:"是否可用,1可用,0禁用"`
+	IsEnableDelete int                    `description:"是否允许删除: 1-允许 0-不允许"`
+	Child          []*EnglishClassifyList `gorm:"-"`
 }
 
 type EnglishClassifyListResp struct {
@@ -1043,6 +1047,20 @@ func ModifyEnglishReportImgUrl(reportId int, detailImgUrl string) (err error) {
 	return
 }
 
+func ModifyEnglishReportPdfUrlMobile(reportId int, detailPdfUrlMobile string) (err error) {
+	o := global.DbMap[utils.DbNameReport]
+	sql := `UPDATE english_report SET detail_pdf_url_mobile=? WHERE id=? `
+	err = o.Exec(sql, detailPdfUrlMobile, reportId).Error
+	return
+}
+
+func ModifyEnglishReportImgUrlMobile(reportId int, detailImgUrlMobile string) (err error) {
+	o := global.DbMap[utils.DbNameReport]
+	sql := `UPDATE english_report SET detail_img_url_mobile=? WHERE id=? `
+	err = o.Exec(sql, detailImgUrlMobile, reportId).Error
+	return
+}
+
 func FormatEnglishReport2ListItem(origin *EnglishReport) (item *EnglishReportList) {
 	if origin == nil {
 		return
@@ -1087,6 +1105,8 @@ func FormatEnglishReport2ListItem(origin *EnglishReport) (item *EnglishReportLis
 	item.ApproveTime = utils.TimeTransferString(utils.FormatDateTime, origin.ApproveTime)
 	item.DetailImgUrl = origin.DetailImgUrl
 	item.DetailPdfUrl = origin.DetailPdfUrl
+	item.DetailImgUrlMobile = origin.DetailImgUrlMobile
+	item.DetailPdfUrlMobile = origin.DetailPdfUrlMobile
 	return
 }
 

+ 40 - 0
models/llm/user_chat_record.go

@@ -0,0 +1,40 @@
+package llm
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"gorm.io/gorm"
+	"gorm.io/gorm/clause"
+	"time"
+)
+
+// UserChatRecord 定义用户聊天记录结构体
+type UserChatRecord struct {
+	Id           int       `gorm:"primaryKey;autoIncrement;comment:主键"`
+	ChatId       int       `gorm:"chat_id;comment:会话id"`
+	ChatUserType string    `gorm:"type:enum('user','assistant');comment:用户方"`
+	Content      string    `gorm:"content:内容"`
+	SendTime     time.Time `gorm:"comment:发送时间"`
+	CreatedTime  time.Time `gorm:"comment:创建时间"`
+	UpdateTime   time.Time `gorm:"autoUpdateTime;comment:更新时间"`
+}
+type UserChatRecordRedis struct {
+	Id           int
+	ChatId       int
+	ChatUserType string
+	Content      string
+	SendTime     string
+}
+
+func (u *UserChatRecord) TableName() string {
+	return "user_chat_record"
+}
+
+func BatchInsertRecords(list []*UserChatRecord) (err error) {
+	o := global.DbMap[utils.DbNameAI]
+	err = o.Clauses(clause.OnConflict{
+		Columns:   []clause.Column{{Name: "chat_id"}, {Name: "chat_user_type"}, {Name: "send_time"}},
+		DoUpdates: clause.Assignments(map[string]interface{}{"update_time": gorm.Expr("VALUES(update_time)")}),
+	}).CreateInBatches(list, utils.MultiAddNum).Error
+	return
+}

+ 73 - 0
models/llm/user_llm_chat.go

@@ -0,0 +1,73 @@
+package llm
+
+import (
+	"errors"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"time"
+)
+
+type UserLlmChat struct {
+	Id          int       `gorm:"primaryKey;autoIncrement;comment:会话主键"`
+	UserId      int       `gorm:"comment:用户id"`
+	ChatTitle   string    `gorm:"comment:会话标题"`
+	IsDeleted   int       `gorm:"comment:是否删除"`
+	CreatedTime time.Time `gorm:"comment:创建时间"`
+	UpdateTime  time.Time `gorm:"autoUpdateTime;comment:更新时间"`
+}
+
+
+type UserLlmChatListViewItem struct {
+	Id          int    `gorm:"primaryKey;autoIncrement;comment:会话主键"`
+	UserId      int    `gorm:"comment:用户id"`
+	ChatTitle   string `gorm:"comment:会话标题"`
+	CreatedTime string `gorm:"comment:创建时间"`
+	RecordCount int    `gorm:"comment:会话记录数"`
+}
+
+func CovertItemToView(item UserLlmChat) UserLlmChatListViewItem {
+	return UserLlmChatListViewItem{
+		Id:          item.Id,
+		UserId:      item.UserId,
+		ChatTitle:   item.ChatTitle,
+		CreatedTime: item.CreatedTime.Format(utils.FormatDateTime),
+	}
+
+}
+func (u *UserLlmChat) TableName() string {
+	return "user_llm_chat"
+}
+func (u *UserLlmChat) CreateChatSession() (chatId int, err error) {
+	o := global.DbMap[utils.DbNameAI]
+	err = o.Create(u).Error
+	if err != nil {
+		return
+	}
+	chatId = u.Id
+	return
+}
+func (u *UserLlmChat) RenameChatSession() (err error) {
+	o := global.DbMap[utils.DbNameAI]
+	var exists bool
+	err = o.Model(&u).Select("1").Where("id = ?", u.Id).Scan(&exists).Error
+	if err != nil {
+		return
+	}
+	if !exists {
+		err = errors.New("当前会话不存在")
+		return
+	}
+	err = o.Select("chat_title").Updates(u).Error
+	return
+}
+func (u *UserLlmChat) DeleteChatSession() (err error) {
+	o := global.DbMap[utils.DbNameAI]
+	err = o.Select("is_deleted").Updates(u).Error
+	return
+}
+func GetUserChatList(userId int, monDay, toDay string) (chatList []UserLlmChat, err error) {
+	o := global.DbMap[utils.DbNameAI]
+	sql := `select ulc.id AS id ,ulc.user_id as user_id,ulc.chat_title as chat_title,ulc.created_time from user_llm_chat ulc  where ulc.user_id=? and ` + utils.GenerateQuerySql(utils.ToDate, &utils.QueryParam{Column: "ulc.created_time"}) + ` BETWEEN ? and ? AND is_deleted=0 GROUP BY ulc.id order by ulc.created_time desc`
+	err = o.Raw(sql, userId, monDay, toDay).Find(&chatList).Error
+	return
+}

+ 45 - 2
models/ppt_english/ppt_english_group_mapping.go

@@ -1,6 +1,7 @@
 package ppt_english
 
 import (
+	sql2 "database/sql"
 	"eta/eta_api/global"
 	"eta/eta_api/utils"
 	"time"
@@ -10,7 +11,7 @@ import (
 type PptEnglishGroupMapping struct {
 	GroupPptId      int64     `orm:"column(group_ppt_id);pk" gorm:"primaryKey" description:"自增序号"`
 	GroupId         int64     `description:"ppt目录ID"`
-	PptSort         int64     `description:"Ppt的排序"`
+	PptSort         float64   `description:"Ppt的排序"`
 	PptId           int64     `description:"ppt ID"`
 	CreateTime      time.Time `orm:"auto_now_add;type(datetime)" description:"创建时间"`
 	ModifyTime      time.Time `orm:"auto_now;type(datetime)" description:"修改时间"`
@@ -67,7 +68,7 @@ func GetPptMappingByPptId(pptId int64) (item *PptEnglishGroupMapping, err error)
 	return
 }
 
-// GetPptMappingListByGroupId 查询目录下,ppt列表, 降序排列
+// GetPptMappingListByGroupIdDesc 查询目录下,ppt列表, 降序排列
 func GetPptMappingListByGroupIdDesc(groupId int64) (list []*PptEnglishGroupMapping, err error) {
 	o := global.DbMap[utils.DbNameReport]
 	sql := `select group_ppt_id, group_id, ppt_id, ppt_sort, admin_id, admin_real_name, create_time from ppt_english_group_mapping where group_id=? order by ppt_sort desc, group_ppt_id desc `
@@ -166,3 +167,45 @@ func GetPublicGroupPptByPptIds(pptIds []string) (list []*PptEnglishGroupMapping,
 	err = o.Raw(sql).Find(&list).Error
 	return
 }
+
+// GetMaxSortByGroupId
+// @Description: 根据分组id获取最大排序
+// @author: Roc
+// @datetime 2025-03-07 18:22:56
+// @param groupPptId int64
+// @return pptSort float64
+// @return err error
+func GetMaxSortByEnglishGroupId(groupPptId int64) (pptSort float64, err error) {
+	o := global.DbMap[utils.DbNameReport]
+	sql := `select MAX(ppt_sort) AS count from ppt_english_group_mapping where group_id=?`
+	var sortNull sql2.NullFloat64
+	err = o.Raw(sql, groupPptId).Scan(&sortNull).Error
+	if err != nil {
+		return
+	}
+	if sortNull.Valid {
+		pptSort = sortNull.Float64
+	}
+	return
+}
+
+// GetMinSortByGroupId
+// @Description: 根据分组id获取最大排序
+// @author: Roc
+// @datetime 2025-03-07 18:22:56
+// @param groupPptId int64
+// @return pptSort float64
+// @return err error
+func GetMinSortByEnglishGroupId(groupPptId int64) (pptSort float64, err error) {
+	o := global.DbMap[utils.DbNameReport]
+	sql := `select MIN(ppt_sort) AS count from ppt_english_group_mapping where group_id=?`
+	var sortNull sql2.NullFloat64
+	err = o.Raw(sql, groupPptId).Scan(&sortNull).Error
+	if err != nil {
+		return
+	}
+	if sortNull.Valid {
+		pptSort = sortNull.Float64
+	}
+	return
+}

+ 5 - 0
models/ppt_v2.go

@@ -178,6 +178,11 @@ func GetPptV2ByIds(pptIds []int) (list []*PptV2, err error) {
 	return
 }
 
+func GetOrderPptV2ByIdList(pptIds []int) (list []*PptV2, err error) {
+	err = global.DbMap[utils.DbNameReport].Table("ppt_v2").Where("ppt_id IN ?", pptIds).Order(``).Find(&list).Error
+	return
+}
+
 //func GetPptV2ByTitle(title string) (item *PptV2, err error) {
 //	o := global.DbMap[utils.DbNameReport]
 //	sql := `SELECT * FROM ppt_v2 WHERE 1=1 AND title=? `

+ 9 - 9
models/ppt_v2_group.go

@@ -8,15 +8,15 @@ import (
 )
 
 type PptV2Group struct {
-	GroupId    int64     `gorm:"column:group_id;primaryKey" description:"ppt目录ID"`
-	GroupName  string    `description:"目录名称"`
-	AdminId    int       `description:"目录创建者账号ID"`
-	IsShare    int8      `description:"是否共享,0私有,1共享"`
-	GroupSort  int64     `description:"目录排序标识"`
-	IsShareAdd int8      `description:"是否是通过单个ppt共享生成的目录,0不是,1-是"`
-	CreateTime time.Time `orm:"auto_now_add;type(datetime)" description:"创建时间"`
-	ModifyTime time.Time `orm:"auto_now;type(datetime)" description:"修改时间"`
-	ShareTime  time.Time `description:"分享时间"`
+	GroupId    int64     `gorm:"column:group_id;type:int(11) UNSIGNED;comment:ppt目录ID;primaryKey;not null;" description:"ppt目录ID"`
+	GroupName  string    `gorm:"column:group_name;type:varchar(100);comment:目录名称;not null;" description:"目录名称"`
+	AdminId    int       `gorm:"column:admin_id;type:int(11);comment:目录创建者账号ID;not null;" description:"目录创建者账号ID"`
+	IsShare    int8      `gorm:"column:is_share;type:tinyint(2);comment:是否共享,0私有,1共享;not null;default:0;" description:"是否共享,0私有,1共享"`
+	GroupSort  int64     `gorm:"column:group_sort;type:int(11);comment:目录排序标识;not null;" description:"目录排序标识"`
+	IsShareAdd int8      `gorm:"column:is_share_add;type:tinyint(2);comment:是否是通过单个ppt共享生成的目录,0不是,1-是;not null;default:0;"  description:"是否是通过单个ppt共享生成的目录,0不是,1-是"`
+	CreateTime time.Time `gorm:"column:create_time;type:datetime;comment:创建时间;not null;default:CURRENT_TIMESTAMP;" description:"创建时间"`
+	ModifyTime time.Time `gorm:"column:modify_time;type:datetime;comment:修改时间;not null;default:CURRENT_TIMESTAMP;" description:"修改时间"`
+	ShareTime  time.Time `gorm:"column:share_time;type:datetime;comment:共享时间;default:NULL;" description:"分享时间"`
 }
 
 // GetPrivatePptGroups 获取私有目录

+ 53 - 6
models/ppt_v2_group_mapping.go

@@ -11,10 +11,10 @@ import (
 type PptV2GroupMapping struct {
 	GroupPptId      int64     `gorm:"column:group_ppt_id;primaryKey;autoIncrement" description:"自增序号"`
 	GroupId         int64     `description:"ppt目录ID"`
-	PptSort         int64     `description:"Ppt的排序"`
+	PptSort         float64   `description:"Ppt的排序"`
 	PptId           int64     `description:"ppt ID"`
-	CreateTime      time.Time `orm:"auto_now_add;type(datetime)" description:"创建时间"`
-	ModifyTime      time.Time `orm:"auto_now;type(datetime)" description:"修改时间"`
+	CreateTime      time.Time `gorm:"column:create_time;type:datetime;comment:创建时间;not null;default:CURRENT_TIMESTAMP;"  description:"创建时间"`
+	ModifyTime      time.Time `gorm:"column:modify_time;type:datetime;comment:修改时间;not null;default:CURRENT_TIMESTAMP;" description:"修改时间"`
 	AdminId         int       `description:"移动ppt到该目录的系统用户id"`
 	AdminRealName   string    `description:"系统用户名称"`
 	ChildGroupPptId int64     `description:"设置共享后的新映射ID"`
@@ -50,14 +50,19 @@ func GetPptMappingCountByGroupId(groupId int64) (total int64, err error) {
 // GetPptMappingListByGroupId 查询目录下,ppt列表
 func GetPptMappingListByGroupId(groupId int64) (list []*PptV2GroupMapping, err error) {
 	o := global.DbMap[utils.DbNameReport]
-	sql := `select group_ppt_id, group_id, ppt_id, ppt_sort, admin_id, admin_real_name, create_time from ppt_v2_group_mapping where group_id=? order by ppt_sort asc, group_ppt_id asc `
+	sql := `select a.group_ppt_id, a.group_id, a.ppt_id, a.ppt_sort, a.admin_id, a.admin_real_name, a.create_time from ppt_v2_group_mapping AS a
+            JOIN ppt_v2 b on a.ppt_id = b.ppt_id where a.group_id=? order by a.ppt_sort desc, b.modify_time desc `
 	err = o.Raw(sql, groupId).Find(&list).Error
+
 	return
 }
 
 // GetPptMappingListByGroupIds 根据分组ID查找
 func GetPptMappingListByGroupIds(groupIds []int64) (list []*PptV2GroupMapping, err error) {
-	err = global.DbMap[utils.DbNameReport].Table("ppt_v2_group_mapping").Where("group_id in ?", groupIds).Find(&list).Error
+	sql := `select a.group_ppt_id, a.group_id, a.ppt_id, a.ppt_sort, a.admin_id, a.admin_real_name, a.create_time from ppt_v2_group_mapping AS a
+            JOIN ppt_v2 b on a.ppt_id = b.ppt_id where a.group_id in (?) order by a.ppt_sort desc, b.modify_time desc `
+	err = global.DbMap[utils.DbNameReport].Raw(sql, groupIds).Find(&list).Error
+
 	return
 }
 
@@ -171,9 +176,51 @@ func GetPublicGroupPptByPptIds(pptIds []string) (list []*PptV2GroupMapping, err
 	// // 导出 SQL 语句
 	// sql := qb.String()
 	sql := `select p.* from ppt_v2_group as g inner join ppt_v2_group_mapping as p on g.group_id = p.group_id where g.is_share = 1 and p.ppt_id in (?) order by g.share_time asc, p.ppt_sort asc`
-	
+
 	// 执行 SQL 语句
 	o := global.DbMap[utils.DbNameReport]
 	err = o.Raw(sql, pptIds).Find(&list).Error
 	return
 }
+
+// GetMaxSortByGroupId
+// @Description: 根据分组id获取最大排序
+// @author: Roc
+// @datetime 2025-03-07 18:22:56
+// @param groupPptId int64
+// @return pptSort float64
+// @return err error
+func GetMaxSortByGroupId(groupPptId int64) (pptSort float64, err error) {
+	o := global.DbMap[utils.DbNameReport]
+	sql := `select MAX(ppt_sort) AS count from ppt_v2_group_mapping where group_id=?`
+	var sortNull sql2.NullFloat64
+	err = o.Raw(sql, groupPptId).Scan(&sortNull).Error
+	if err != nil {
+		return
+	}
+	if sortNull.Valid {
+		pptSort = sortNull.Float64
+	}
+	return
+}
+
+// GetMinSortByGroupId
+// @Description: 根据分组id获取最大排序
+// @author: Roc
+// @datetime 2025-03-07 18:22:56
+// @param groupPptId int64
+// @return pptSort float64
+// @return err error
+func GetMinSortByGroupId(groupPptId int64) (pptSort float64, err error) {
+	o := global.DbMap[utils.DbNameReport]
+	sql := `select MIN(ppt_sort) AS count from ppt_v2_group_mapping where group_id=?`
+	var sortNull sql2.NullFloat64
+	err = o.Raw(sql, groupPptId).Scan(&sortNull).Error
+	if err != nil {
+		return
+	}
+	if sortNull.Valid {
+		pptSort = sortNull.Float64
+	}
+	return
+}

+ 37 - 0
models/rag/article_kb_mapping.go

@@ -0,0 +1,37 @@
+package rag
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"gorm.io/gorm/clause"
+	"time"
+)
+
+type ArticleKbMapping struct {
+	Id              int `gorm:"id;primaryKey"`
+	WechatArticleId int
+	KbId            string
+	CreatedTime     time.Time
+	UpdateTime      time.Time
+}
+
+func (a *ArticleKbMapping) TableName() string {
+	return "article_kb_mapping"
+}
+
+func GetArticleKbMapping(articleId int) (articleKbMapping *ArticleKbMapping, err error) {
+	err = global.DbMap[utils.DbNameAI].Where("wechat_article_id = ?", articleId).First(&articleKbMapping).Error
+	return
+}
+
+func CreateArticleKbMapping(articleKbMapping ArticleKbMapping) (err error) {
+	db := global.DbMap[utils.DbNameAI]
+	db.Clauses(
+		clause.OnConflict{
+			DoNothing: true,
+			Columns:   []clause.Column{{Name: "wechat_article_id"}},
+		},
+	)
+	err = global.DbMap[utils.DbNameAI].Create(&articleKbMapping).Error
+	return
+}

+ 65 - 0
models/rag/promote_train_record.go

@@ -0,0 +1,65 @@
+package rag
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"time"
+)
+
+type PromoteTrainRecord struct {
+	Id              int       `gorm:"id;primaryKey"`
+	Title           string    `gorm:"title"`
+	WechatArticleId int       `gorm:"wechat_article_id"`
+	TemplatePromote string    `gorm:"template_promote"`
+	PromoteSendTime time.Time `gorm:"promote_send_time"`
+	AigcContent     string    `gorm:"aigc_content"`
+	AigcSendTime    time.Time `gorm:"aigc_send_time"`
+	IsDeleted       bool      `gorm:"is_deleted"`
+	CreatedTime     time.Time `gorm:"created_time"`
+	UpdateTime      time.Time `gorm:"update_time"`
+}
+
+func (p *PromoteTrainRecord) ToView() *PromoteTrainRecordView {
+	return &PromoteTrainRecordView{
+		Id:              p.Id,
+		Title:           p.Title,
+		WechatArticleId: p.WechatArticleId,
+		TemplatePromote: p.TemplatePromote,
+		PromoteSendTime: p.PromoteSendTime.Format(utils.FormatDateTime),
+		AigcContent:     p.AigcContent,
+		AigcSendTime:    p.AigcSendTime.Format(utils.FormatDateTime),
+	}
+}
+
+type PromoteTrainRecordView struct {
+	Id              int
+	Title           string
+	WechatArticleId int
+	TemplatePromote string
+	PromoteSendTime string
+	AigcContent     string
+	AigcSendTime    string
+}
+
+func (p *PromoteTrainRecord) TableName() string {
+	return "promote_train_record"
+}
+
+func (p *PromoteTrainRecord) SaveContent() error {
+	return global.DbMap[utils.DbNameAI].Create(p).Error
+}
+func DeleteContent(id int) error {
+	return global.DbMap[utils.DbNameAI].Model(&PromoteTrainRecord{}).Where("id = ?", id).Update("is_deleted", true).Error
+}
+
+func GetRecordList(wechatArticleId int) (list []*PromoteTrainRecordView, err error) {
+	var ormList []PromoteTrainRecord
+	err = global.DbMap[utils.DbNameAI].Model(&PromoteTrainRecord{}).Where("wechat_article_id = ? and is_deleted=?", wechatArticleId, false).Order(`created_time DESC`).Find(&ormList).Error
+	if err != nil {
+		return
+	}
+	for _, item := range ormList {
+		list = append(list, item.ToView())
+	}
+	return
+}

+ 135 - 0
models/rag/question.go

@@ -0,0 +1,135 @@
+package rag
+
+import (
+	"database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+type Question struct {
+	QuestionId      int       `gorm:"column:question_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"question_id"`
+	QuestionContent string    `gorm:"column:question_content;type:varchar(255);comment:问题内容;" description:"问题内容"`
+	Sort            int       `gorm:"column:sort;type:int(11);comment:排序;default:0;" description:"排序"`
+	ModifyTime      time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
+	CreateTime      time.Time `gorm:"column:create_time;type:datetime;default:NULL;" description:"create_time"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *Question) TableName() string {
+	return "question"
+}
+
+// QuestionColumns get sql column name.获取数据库列名
+var QuestionColumns = struct {
+	QuestionID      string
+	QuestionContent string
+	Sort            string
+	ModifyTime      string
+	CreateTime      string
+}{
+	QuestionID:      "question_id",
+	QuestionContent: "question_content",
+	Sort:            "sort",
+	ModifyTime:      "modify_time",
+	CreateTime:      "create_time",
+}
+
+func (m *Question) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+func (m *Question) Update(updateCols []string) (err error) {
+	err = global.DbMap[utils.DbNameAI].Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *Question) Del() (err error) {
+	err = global.DbMap[utils.DbNameAI].Delete(&m).Error
+
+	return
+}
+
+type QuestionView struct {
+	QuestionId      int    `gorm:"column:question_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"question_id"`
+	QuestionContent string `gorm:"column:question_content;type:varchar(255);comment:问题内容;" description:"问题内容"` //
+	Sort            int    `gorm:"column:sort;type:int(11);comment:排序;default:0;" description:"sort"`          // 排序
+	ModifyTime      string `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
+	CreateTime      string `gorm:"column:create_time;type:datetime;default:NULL;" description:"create_time"`
+}
+
+func (m *Question) ToView() QuestionView {
+	var modifyTime, createTime string
+
+	if !m.CreateTime.IsZero() {
+		createTime = m.CreateTime.Format(utils.FormatDateTime)
+	}
+	if !m.ModifyTime.IsZero() {
+		modifyTime = m.ModifyTime.Format(utils.FormatDateTime)
+	}
+	return QuestionView{
+		QuestionId:      m.QuestionId,
+		QuestionContent: m.QuestionContent,
+		Sort:            m.Sort,
+		ModifyTime:      modifyTime,
+		CreateTime:      createTime,
+	}
+}
+
+func (m *Question) ListToViewList(list []*Question) (wechatArticleViewList []QuestionView) {
+	wechatArticleViewList = make([]QuestionView, 0)
+
+	for _, v := range list {
+		wechatArticleViewList = append(wechatArticleViewList, v.ToView())
+	}
+	return
+}
+
+func (m *Question) GetByID(id int) (item *Question, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", QuestionColumns.QuestionID), id).First(&item).Error
+
+	return
+}
+
+func (m *Question) GetByCondition(condition string, pars []interface{}) (item *Question, err error) {
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).First(&item).Error
+
+	return
+}
+
+func (m *Question) GetListByCondition(condition string, pars []interface{}, startSize, pageSize int) (items []*Question, err error) {
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s order by question_id desc LIMIT ?,?`, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *Question) GetCountByCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+func (m *Question) GetPageListByCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*Question, err error) {
+
+	total, err = m.GetCountByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByCondition(condition, pars, startSize, pageSize)
+	}
+
+	return
+}

+ 33 - 0
models/rag/request/wechat_platform.go

@@ -0,0 +1,33 @@
+package request
+
+type AddWechatPlatformReq struct {
+	Name      string `description:"公众号名称"`
+	Link      string `description:"公众号文章链接"`
+	TagIdList []int  `description:"标签列表"`
+}
+
+type OpWechatPlatformReq struct {
+	Status           int `description:"0:禁用,1:启用"`
+	WechatPlatformId int `description:"公众号id"`
+}
+
+type RefreshWechatPlatformReq struct {
+	WechatPlatformId int `description:"公众号id"`
+}
+
+type AddQuestionReq struct {
+	Content string `description:"公众号名称"`
+}
+
+type EditQuestionReq struct {
+	QuestionId int    `description:"问题id"`
+	Content    string `description:"公众号名称"`
+}
+
+type BeachOpAbstractReq struct {
+	WechatArticleAbstractIdList    []int  `description:"摘要id"`
+	NotWechatArticleAbstractIdList []int  `description:"不需要的摘要id"`
+	KeyWord                        string `description:"关键字"`
+	TagId                          int    `description:"标签id"`
+	IsSelectAll                    bool   `description:"是否选择所有摘要"`
+}

+ 11 - 0
models/rag/response/abstract.go

@@ -0,0 +1,11 @@
+package response
+
+import (
+	"eta/eta_api/models/rag"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type AbstractListListResp struct {
+	List   []rag.WechatArticleAbstractView
+	Paging *paging.PagingItem `description:"分页数据"`
+}

+ 11 - 0
models/rag/response/question.go

@@ -0,0 +1,11 @@
+package response
+
+import (
+	"eta/eta_api/models/rag"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type QuestionListListResp struct {
+	List   []rag.QuestionView
+	Paging *paging.PagingItem `description:"分页数据"`
+}

+ 30 - 0
models/rag/response/wechat_platform.go

@@ -0,0 +1,30 @@
+package response
+
+import (
+	"eta/eta_api/models/rag"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type WechatPlatformListResp struct {
+	List   []*rag.WechatPlatform
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type WechatPlatformPublicListResp struct {
+	UserId int    `description:"用户id"`
+	Name   string `description:"研究员名称"`
+	List   []*rag.UserFollowWechatPlatform
+	//Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type TagListResp struct {
+	List   []*rag.Tag
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type WechatArticleListListResp struct {
+	UserId int    `description:"用户id"`
+	Name   string `description:"研究员名称"`
+	List   []rag.WechatArticleView
+	Paging *paging.PagingItem `description:"分页数据"`
+}

+ 83 - 0
models/rag/tag.go

@@ -0,0 +1,83 @@
+package rag
+
+import (
+	"database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// Tag 品种标签
+type Tag struct {
+	TagId      int       `gorm:"column:tag_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"tag_id"`
+	TagName    string    `gorm:"column:tag_name;type:varchar(255);comment:标签名称;" description:"tag_name"`                // 标签名称
+	Sort       int       `gorm:"column:sort;type:int(9);comment:排序字段;default:0;" description:"sort"`                    // 排序字段
+	ModifyTime time.Time `gorm:"column:modify_time;type:datetime;comment:修改时间;default:NULL;" description:"modify_time"` // 修改时间
+	CreateTime time.Time `gorm:"column:create_time;type:datetime;comment:添加时间;default:NULL;" description:"create_time"` // 添加时间
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *Tag) TableName() string {
+	return "tag"
+}
+
+// TagColumns get sql column name.获取数据库列名
+var TagColumns = struct {
+	TagID      string
+	TagName    string
+	Sort       string
+	ModifyTime string
+	CreateTime string
+}{
+	TagID:      "tag_id",
+	TagName:    "tag_name",
+	Sort:       "sort",
+	ModifyTime: "modify_time",
+	CreateTime: "create_time",
+}
+
+func (m *Tag) GetByID(TagId int) (item *Tag, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", TagColumns.TagID), TagId).First(&item).Error
+
+	return
+}
+
+func (m *Tag) GetByCondition(condition string, pars []interface{}) (item *Tag, err error) {
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).First(&item).Error
+
+	return
+}
+
+func (m *Tag) GetListByCondition(condition string, pars []interface{}, startSize, pageSize int) (items []*Tag, err error) {
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT ?,?`, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *Tag) GetCountByCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+func (m *Tag) GetPageListByCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*Tag, err error) {
+
+	total, err = m.GetCountByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByCondition(condition, pars, startSize, pageSize)
+	}
+
+	return
+}

+ 307 - 0
models/rag/wechat_article.go

@@ -0,0 +1,307 @@
+package rag
+
+import (
+	"database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+type WechatArticle struct {
+	WechatArticleId   int       `gorm:"column:wechat_article_id;type:int(10) UNSIGNED;primaryKey;not null;" description:""`
+	WechatPlatformId  int       `gorm:"column:wechat_platform_id;type:int(11);comment:归属公众号id;default:0;" description:"归属公众号id"`
+	FakeId            string    `gorm:"column:fake_id;type:varchar(255);comment:公众号唯一id;" description:"公众号唯一id"`
+	Title             string    `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
+	Link              string    `gorm:"column:link;type:varchar(255);comment:链接;" description:"链接"`
+	CoverUrl          string    `gorm:"column:cover_url;type:varchar(255);comment:公众号封面;" description:"公众号封面"`
+	Description       string    `gorm:"column:description;type:varchar(255);comment:描述;" description:"描述"`
+	Content           string    `gorm:"column:content;type:longtext;comment:报告详情;" description:"报告详情"`
+	TextContent       string    `gorm:"column:text_content;type:text;comment:文本内容;" description:"文本内容"`
+	AbstractStatus    int       `gorm:"column:abstract_status;type:tinyint(4);comment:摘要生成情况,-1:生成失败,0:待生成,1:已生成;default:0;" description:"摘要生成情况,-1:生成失败,0:待生成,1:已生成"`
+	Country           string    `gorm:"column:country;type:varchar(255);comment:国家;" description:"国家"`
+	Province          string    `gorm:"column:province;type:varchar(255);comment:省;" description:"省"`
+	City              string    `gorm:"column:city;type:varchar(255);comment:市;" description:"市"`
+	ArticleCreateTime time.Time `gorm:"column:article_create_time;type:datetime;comment:报告创建时间;default:NULL;" description:"报告创建时间"`
+	VectorKey         string    `gorm:"column:vector_key;type:varchar(255);comment:向量key标识;" description:"向量key标识"`
+	IsDeleted         int       `gorm:"column:is_deleted;type:tinyint(4);comment:是否删除,0:未删除,1: 已删除;default:0;" description:"是否删除,0:未删除,1: 已删除"`
+	ModifyTime        time.Time `gorm:"column:modify_time;type:datetime;comment:修改时间;default:NULL;" description:"修改时间"`
+	CreateTime        time.Time `gorm:"column:create_time;type:datetime;comment:入库时间;default:NULL;" description:"入库时间"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *WechatArticle) TableName() string {
+	return "wechat_article"
+}
+
+// WechatArticleColumns get sql column name.获取数据库列名
+var WechatArticleColumns = struct {
+	WechatArticleID   string
+	WechatPlatformID  string
+	FakeID            string
+	Title             string
+	Link              string
+	CoverURL          string
+	Description       string
+	Content           string
+	TextContent       string
+	AbstractStatus    string
+	Country           string
+	Province          string
+	City              string
+	ArticleCreateTime string
+	IsDeleted         string
+	ModifyTime        string
+	CreateTime        string
+}{
+	WechatArticleID:   "wechat_article_id",
+	WechatPlatformID:  "wechat_platform_id",
+	FakeID:            "fake_id",
+	Title:             "title",
+	Link:              "link",
+	CoverURL:          "cover_url",
+	Description:       "description",
+	Content:           "content",
+	TextContent:       "text_content",
+	AbstractStatus:    "abstract_status",
+	Country:           "country",
+	Province:          "province",
+	City:              "city",
+	ArticleCreateTime: "article_create_time",
+	IsDeleted:         "is_deleted",
+	ModifyTime:        "modify_time",
+	CreateTime:        "create_time",
+}
+
+type WechatArticleView struct {
+	WechatArticleId            int    `gorm:"column:wechat_article_id;type:int(10) UNSIGNED;primaryKey;not null;" description:""`
+	WechatPlatformId           int    `gorm:"column:wechat_platform_id;type:int(11);comment:归属公众号id;default:0;" description:"归属公众号id"`
+	FakeId                     string `gorm:"column:fake_id;type:varchar(255);comment:公众号唯一id;" description:"公众号唯一id"`
+	Title                      string `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
+	Link                       string `gorm:"column:link;type:varchar(255);comment:链接;" description:"链接"`
+	CoverUrl                   string `gorm:"column:cover_url;type:varchar(255);comment:公众号封面;" description:"公众号封面"`
+	Description                string `gorm:"column:description;type:varchar(255);comment:描述;" description:"描述"`
+	Content                    string `gorm:"column:content;type:longtext;comment:报告详情;" description:"报告详情"`
+	TextContent                string `gorm:"column:text_content;type:text;comment:文本内容;" description:"文本内容"`
+	AbstractStatus             int    `gorm:"column:abstract_status;type:tinyint(4);comment:摘要生成情况,-1:生成失败,0:待生成,1:已生成;default:0;" description:"摘要生成情况,-1:生成失败,0:待生成,1:已生成"`
+	Abstract                   string `gorm:"column:abstract;type:text;comment:摘要;" description:"摘要"`
+	Country                    string `gorm:"column:country;type:varchar(255);comment:国家;" description:"国家"`
+	Province                   string `gorm:"column:province;type:varchar(255);comment:省;" description:"省"`
+	City                       string `gorm:"column:city;type:varchar(255);comment:市;" description:"市"`
+	ArticleCreateTime          string `gorm:"column:article_create_time;type:datetime;comment:报告创建时间;default:NULL;" description:"报告创建时间"`
+	ModifyTime                 string `gorm:"column:modify_time;type:datetime;comment:修改时间;default:NULL;" description:"修改时间"`
+	CreateTime                 string `gorm:"column:create_time;type:datetime;comment:入库时间;default:NULL;" description:"入库时间"`
+	WechatPlatformName         string `gorm:"column:title;type:varchar(255);comment:标题;" description:"微信公众号名称"`
+	WechatPlatformRoundHeadImg string `gorm:"column:round_head_img;type:varchar(255);comment:头像;" description:"微信公众号头像"`
+	TagName                    string `gorm:"column:tag_name;type:varchar(255);comment:标签名称;" description:"标签名称"`
+	TagId                      int    `gorm:"column:tag_id;type:varchar(255);comment:标签id;" description:"标签id"`
+}
+
+func (m *WechatArticle) ToView() WechatArticleView {
+	var articleCreateTime, modifyTime, createTime string
+
+	if !m.ArticleCreateTime.IsZero() {
+		articleCreateTime = m.ArticleCreateTime.Format(utils.FormatDateTime)
+	}
+	if !m.CreateTime.IsZero() {
+		createTime = m.CreateTime.Format(utils.FormatDateTime)
+	}
+	if !m.ModifyTime.IsZero() {
+		modifyTime = m.ModifyTime.Format(utils.FormatDateTime)
+	}
+	return WechatArticleView{
+		WechatArticleId:            m.WechatArticleId,
+		WechatPlatformId:           m.WechatPlatformId,
+		FakeId:                     m.FakeId,
+		Title:                      m.Title,
+		Link:                       m.Link,
+		CoverUrl:                   m.CoverUrl,
+		Description:                m.Description,
+		Content:                    m.Content,
+		TextContent:                m.TextContent,
+		AbstractStatus:             m.AbstractStatus,
+		Country:                    m.Country,
+		Province:                   m.Province,
+		City:                       m.City,
+		ArticleCreateTime:          articleCreateTime,
+		ModifyTime:                 modifyTime,
+		CreateTime:                 createTime,
+		WechatPlatformName:         "",
+		WechatPlatformRoundHeadImg: "",
+	}
+}
+
+func (m *WechatArticle) ListToViewList(list []*WechatArticle) (wechatArticleViewList []WechatArticleView) {
+	wechatArticleViewList = make([]WechatArticleView, 0)
+
+	for _, v := range list {
+		wechatArticleViewList = append(wechatArticleViewList, v.ToView())
+	}
+	return
+}
+
+func (m *WechatArticle) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+func (m *WechatArticle) Update(updateCols []string) (err error) {
+	err = global.DbMap[utils.DbNameAI].Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *WechatArticle) GetById(id int) (item *WechatArticle, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", WechatArticleColumns.WechatArticleID), id).First(&item).Error
+
+	return
+}
+func GetArticleById(id int) (item *WechatArticle, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", WechatArticleColumns.WechatArticleID), id).First(&item).Error
+	return
+}
+func (m *WechatArticle) GetByLink(link string) (item *WechatArticle, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", WechatArticleColumns.Link), link).First(&item).Error
+
+	return
+}
+
+func (m *WechatArticle) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*WechatArticle, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 AND is_deleted=0 %s  order by article_create_time desc,wechat_article_id desc LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *WechatArticle) GetCountByCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s WHERE 1=1 AND is_deleted=0 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+func (m *WechatArticle) GetPageListByCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*WechatArticle, err error) {
+
+	total, err = m.GetCountByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByCondition(`wechat_article_id,wechat_platform_id,fake_id,title,link,cover_url,description,country,province,city,article_create_time,modify_time,create_time`, condition, pars, startSize, pageSize)
+	}
+
+	return
+}
+
+type WechatArticleAndPlatform struct {
+	WechatArticleId   int       `gorm:"column:wechat_article_id;type:int(10) UNSIGNED;primaryKey;not null;" description:""`
+	WechatPlatformId  int       `gorm:"column:wechat_platform_id;type:int(11);comment:归属公众号id;default:0;" description:"归属公众号id"`
+	FakeId            string    `gorm:"column:fake_id;type:varchar(255);comment:公众号唯一id;" description:"公众号唯一id"`
+	Title             string    `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
+	Link              string    `gorm:"column:link;type:varchar(255);comment:链接;" description:"链接"`
+	CoverUrl          string    `gorm:"column:cover_url;type:varchar(255);comment:公众号封面;" description:"公众号封面"`
+	Description       string    `gorm:"column:description;type:varchar(255);comment:描述;" description:"描述"`
+	Content           string    `gorm:"column:content;type:longtext;comment:报告详情;" description:"报告详情"`
+	TextContent       string    `gorm:"column:text_content;type:text;comment:文本内容;" description:"文本内容"`
+	Abstract          string    `gorm:"column:abstract;type:text;comment:摘要;" description:"摘要"`
+	Country           string    `gorm:"column:country;type:varchar(255);comment:国家;" description:"国家"`
+	Province          string    `gorm:"column:province;type:varchar(255);comment:省;" description:"省"`
+	City              string    `gorm:"column:city;type:varchar(255);comment:市;" description:"市"`
+	ArticleCreateTime time.Time `gorm:"column:article_create_time;type:datetime;comment:报告创建时间;default:NULL;" description:"报告创建时间"`
+	IsDeleted         int       `gorm:"column:is_deleted;type:tinyint(4);comment:是否删除,0:未删除,1: 已删除;default:0;" description:"是否删除,0:未删除,1: 已删除"`
+	ModifyTime        time.Time `gorm:"column:modify_time;type:datetime;comment:修改时间;default:NULL;" description:"修改时间"`
+	CreateTime        time.Time `gorm:"column:create_time;type:datetime;comment:入库时间;default:NULL;" description:"入库时间"`
+	Nickname          string    `gorm:"column:nickname;type:varchar(255);comment:公众号名称;" description:"nickname"`          // 公众号名称
+	Alias             string    `gorm:"column:alias;type:varchar(255);comment:别名;" description:"alias"`                   // 别名
+	RoundHeadImg      string    `gorm:"column:round_head_img;type:varchar(255);comment:头像;" description:"round_head_img"` // 头像
+}
+
+func (m *WechatArticleAndPlatform) ToView() WechatArticleView {
+	var articleCreateTime, modifyTime, createTime string
+
+	if !m.ArticleCreateTime.IsZero() {
+		articleCreateTime = m.ArticleCreateTime.Format(utils.FormatDateTime)
+	}
+	if !m.CreateTime.IsZero() {
+		createTime = m.CreateTime.Format(utils.FormatDateTime)
+	}
+	if !m.ModifyTime.IsZero() {
+		modifyTime = m.ModifyTime.Format(utils.FormatDateTime)
+	}
+	return WechatArticleView{
+		WechatArticleId:            m.WechatArticleId,
+		WechatPlatformId:           m.WechatPlatformId,
+		FakeId:                     m.FakeId,
+		Title:                      m.Title,
+		Link:                       m.Link,
+		CoverUrl:                   m.CoverUrl,
+		Description:                m.Description,
+		Content:                    m.Content,
+		TextContent:                m.TextContent,
+		Abstract:                   m.Abstract,
+		Country:                    m.Country,
+		Province:                   m.Province,
+		City:                       m.City,
+		ArticleCreateTime:          articleCreateTime,
+		ModifyTime:                 modifyTime,
+		CreateTime:                 createTime,
+		WechatPlatformName:         m.Nickname,
+		WechatPlatformRoundHeadImg: m.RoundHeadImg,
+	}
+}
+
+func (m *WechatArticle) ArticleAndPlatformListToViewList(list []*WechatArticleAndPlatform) (wechatArticleViewList []WechatArticleView) {
+	wechatArticleViewList = make([]WechatArticleView, 0)
+
+	for _, v := range list {
+		wechatArticleViewList = append(wechatArticleViewList, v.ToView())
+	}
+	return
+}
+
+func (m *WechatArticle) GetListByPlatformCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*WechatArticleAndPlatform, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s AS a 
+          JOIN wechat_platform AS b ON a.wechat_platform_id=b.wechat_platform_id
+          WHERE 1=1 AND a.is_deleted=0 %s  order by a.article_create_time DESC,a.wechat_article_id DESC LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *WechatArticle) GetCountByPlatformCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s AS a 
+          JOIN wechat_platform AS b ON a.wechat_platform_id=b.wechat_platform_id 
+          WHERE 1=1 AND a.is_deleted=0 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+func (m *WechatArticle) GetPageListByPlatformCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*WechatArticleAndPlatform, err error) {
+	total, err = m.GetCountByPlatformCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByPlatformCondition(`a.wechat_article_id,a.wechat_platform_id,a.fake_id,a.title,a.link,a.cover_url,a.description,a.country,a.province,a.city,a.article_create_time,a.modify_time,a.create_time,b.nickname,b.round_head_img`, condition, pars, startSize, pageSize)
+	}
+
+	return
+}

+ 255 - 0
models/rag/wechat_article_abstract.go

@@ -0,0 +1,255 @@
+package rag
+
+import (
+	"database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+type WechatArticleAbstract struct {
+	WechatArticleAbstractId int       `gorm:"column:wechat_article_abstract_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"wechat_article_abstract_id"`
+	WechatArticleId         int       `gorm:"column:wechat_article_id;type:int(9) UNSIGNED;comment:关联的微信报告id;default:0;" description:"关联的微信报告id"`
+	Content                 string    `gorm:"column:content;type:longtext;comment:摘要内容;" description:"content"` // 摘要内容
+	Version                 int       `gorm:"column:version;type:int(10) UNSIGNED;comment:版本号;default:1;" description:"版本号"`
+	VectorKey               string    `gorm:"column:vector_key;type:varchar(255);comment:向量key标识;" description:"向量key标识"`
+	ModifyTime              time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
+	CreateTime              time.Time `gorm:"column:create_time;type:datetime;default:NULL;" description:"create_time"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *WechatArticleAbstract) TableName() string {
+	return "wechat_article_abstract"
+}
+
+// WechatArticleAbstractColumns get sql column name.获取数据库列名
+var WechatArticleAbstractColumns = struct {
+	WechatArticleAbstractID string
+	WechatArticleID         string
+	Content                 string
+	Version                 string
+	ModifyTime              string
+	CreateTime              string
+}{
+	WechatArticleAbstractID: "wechat_article_abstract_id",
+	WechatArticleID:         "wechat_article_id",
+	Content:                 "content",
+	Version:                 "version",
+	ModifyTime:              "modify_time",
+	CreateTime:              "create_time",
+}
+
+func (m *WechatArticleAbstract) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+func (m *WechatArticleAbstract) Update(updateCols []string) (err error) {
+	err = global.DbMap[utils.DbNameAI].Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *WechatArticleAbstract) Del() (err error) {
+	err = global.DbMap[utils.DbNameAI].Delete(&m).Error
+
+	return
+}
+
+func (m *WechatArticleAbstract) GetById(id int) (item *WechatArticleAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", WechatArticleAbstractColumns.WechatArticleAbstractID), id).First(&item).Error
+
+	return
+}
+
+func (m *WechatArticleAbstract) GetByIdList(idList []int) (items []*WechatArticleAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s in (?) ", WechatArticleAbstractColumns.WechatArticleAbstractID), idList).Find(&items).Error
+
+	return
+}
+
+func (m *WechatArticleAbstract) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*WechatArticleAbstract, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s  order by wechat_article_abstract_id desc LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *WechatArticleAbstract) DelByIdList(idList []int) (err error) {
+	if len(idList) <= 0 {
+		return
+	}
+	sqlStr := fmt.Sprintf(`delete from %s where %s in (?)`, m.TableName(), WechatArticleAbstractColumns.WechatArticleAbstractID)
+	err = global.DbMap[utils.DbNameAI].Exec(sqlStr, idList).Error
+
+	return
+}
+
+// GetByWechatArticleId
+// @Description: 根据报告id获取摘要
+// @author: Roc
+// @receiver m
+// @datetime 2025-03-07 10:00:59
+// @param id int
+// @return item *WechatArticleAbstract
+// @return err error
+func (m *WechatArticleAbstract) GetByWechatArticleId(id int) (item *WechatArticleAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", WechatArticleAbstractColumns.WechatArticleID), id).Order(fmt.Sprintf(`%s DESC`, WechatArticleAbstractColumns.WechatArticleAbstractID)).First(&item).Error
+
+	return
+}
+
+type WechatArticleAbstractView struct {
+	WechatArticleAbstractId int    `gorm:"column:wechat_article_abstract_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"wechat_article_abstract_id"`
+	WechatArticleId         int    `gorm:"column:wechat_article_id;type:int(9) UNSIGNED;comment:关联的微信报告id;default:0;" description:"关联的微信报告id"`
+	WechatPlatformId        int    `gorm:"column:wechat_platform_id;type:int(11);comment:归属公众号id;default:0;" description:"归属公众号id"`
+	Abstract                string `gorm:"column:abstract;type:longtext;comment:摘要内容;" description:"摘要内容"` //
+	Version                 int    `gorm:"column:version;type:int(10) UNSIGNED;comment:版本号;default:1;" description:"版本号"`
+	VectorKey               string `gorm:"column:vector_key;type:varchar(255);comment:向量key标识;" description:"向量key标识"`
+	ModifyTime              string `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
+	CreateTime              string `gorm:"column:create_time;type:datetime;default:NULL;" description:"create_time"`
+	Title                   string `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
+	Link                    string `gorm:"column:link;type:varchar(255);comment:链接;" description:"链接"`
+	TagId                   int    `gorm:"column:tag_id;type:int(9) UNSIGNED;comment:品种id;default:0;" description:"品种id"`
+}
+
+type WechatArticleAbstractItem struct {
+	WechatArticleAbstractId int       `gorm:"column:wechat_article_abstract_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"wechat_article_abstract_id"`
+	WechatArticleId         int       `gorm:"column:wechat_article_id;type:int(9) UNSIGNED;comment:关联的微信报告id;default:0;" description:"关联的微信报告id"`
+	Abstract                string    `gorm:"column:abstract;type:longtext;comment:摘要内容;" description:"摘要内容"` //
+	Version                 int       `gorm:"column:version;type:int(10) UNSIGNED;comment:版本号;default:1;" description:"版本号"`
+	VectorKey               string    `gorm:"column:vector_key;type:varchar(255);comment:向量key标识;" description:"向量key标识"`
+	ModifyTime              time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
+	CreateTime              time.Time `gorm:"column:create_time;type:datetime;default:NULL;" description:"create_time"`
+	Title                   string    `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
+	Link                    string    `gorm:"column:link;type:varchar(255);comment:链接;" description:"链接"`
+	TagId                   int       `gorm:"column:tag_id;type:int(9) UNSIGNED;comment:品种id;default:0;" description:"品种id"`
+}
+
+func (m *WechatArticleAbstractItem) ToView() WechatArticleAbstractView {
+	return WechatArticleAbstractView{
+		WechatArticleAbstractId: m.WechatArticleAbstractId,
+		WechatArticleId:         m.WechatArticleId,
+		Abstract:                m.Abstract,
+		Version:                 m.Version,
+		VectorKey:               m.VectorKey,
+		ModifyTime:              utils.DateStrToDateTimeStr(m.ModifyTime),
+		CreateTime:              utils.DateStrToDateTimeStr(m.CreateTime),
+		Title:                   m.Title,
+		Link:                    m.Link,
+		TagId:                   m.TagId,
+	}
+}
+
+func (m *WechatArticleAbstract) WechatArticleAbstractItem(list []*WechatArticleAbstractItem) (wechatArticleViewList []WechatArticleAbstractView) {
+	wechatArticleViewList = make([]WechatArticleAbstractView, 0)
+
+	for _, v := range list {
+		wechatArticleViewList = append(wechatArticleViewList, v.ToView())
+	}
+	return
+}
+
+func (m *WechatArticleAbstract) GetListByPlatformCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*WechatArticleAbstractItem, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s AS a 
+          JOIN wechat_article AS b ON a.wechat_article_id=b.wechat_article_id
+          JOIN wechat_platform AS c ON b.wechat_platform_id=c.wechat_platform_id
+          WHERE 1=1 AND b.is_deleted=0 %s  order by a.modify_time DESC,a.wechat_article_abstract_id DESC LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *WechatArticleAbstract) GetCountByPlatformCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s AS a 
+          JOIN wechat_article AS b ON a.wechat_article_id=b.wechat_article_id
+          JOIN wechat_platform AS c ON b.wechat_platform_id=c.wechat_platform_id
+          WHERE 1=1 AND b.is_deleted=0 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+func (m *WechatArticleAbstract) GetPageListByPlatformCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*WechatArticleAbstractItem, err error) {
+
+	total, err = m.GetCountByPlatformCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByPlatformCondition(`a.wechat_article_abstract_id,a.wechat_article_id,a.content AS abstract,a.version,a.vector_key,b.title,b.link,a.modify_time,a.create_time`, condition, pars, startSize, pageSize)
+	}
+
+	return
+}
+
+func (m *WechatArticleAbstract) GetListByTagAndPlatformCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*WechatArticleAbstractItem, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s AS a 
+          JOIN wechat_article AS b ON a.wechat_article_id=b.wechat_article_id
+          JOIN wechat_platform AS c ON b.wechat_platform_id=c.wechat_platform_id
+          JOIN wechat_platform_tag_mapping AS d ON c.wechat_platform_id=d.wechat_platform_id
+          WHERE 1=1 AND b.is_deleted=0 %s  order by a.modify_time DESC,a.wechat_article_abstract_id DESC LIMIT ?,?`, field, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *WechatArticleAbstract) GetCountByTagAndPlatformCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s AS a 
+          JOIN wechat_article AS b ON a.wechat_article_id=b.wechat_article_id
+          JOIN wechat_platform AS c ON b.wechat_platform_id=c.wechat_platform_id
+          JOIN wechat_platform_tag_mapping AS d ON c.wechat_platform_id=d.wechat_platform_id
+          WHERE 1=1 AND b.is_deleted=0 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+func (m *WechatArticleAbstract) GetPageListByTagAndPlatformCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*WechatArticleAbstractItem, err error) {
+
+	total, err = m.GetCountByTagAndPlatformCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByTagAndPlatformCondition(`a.wechat_article_abstract_id,a.wechat_article_id,a.content AS abstract,a.version,a.vector_key,a.modify_time,a.create_time,b.title,b.link,d.tag_id`, condition, pars, startSize, pageSize)
+	}
+
+	return
+}
+
+// DelVectorKey
+// @Description: 批量删除向量库
+// @author: Roc
+// @receiver m
+// @datetime 2025-03-12 16:47:52
+// @param wechatArticleAbstractIdList []int
+// @return err error
+func (m *WechatArticleAbstract) DelVectorKey(wechatArticleAbstractIdList []int) (err error) {
+	sqlStr := fmt.Sprintf(`UPDATE %s set vector_key = '' WHERE wechat_article_abstract_id IN (?)`, m.TableName())
+	err = global.DbMap[utils.DbNameAI].Exec(sqlStr, wechatArticleAbstractIdList).Error
+
+	return
+}

+ 66 - 0
models/rag/wechat_article_chat_record.go

@@ -0,0 +1,66 @@
+package rag
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+type WechatArticleChatRecord struct {
+	WechatArticleChatRecordId int       `gorm:"column:wechat_article_chat_record_id;type:int(11);comment:主键;primaryKey;not null;" json:"wechat_article_chat_record_id"` // 主键
+	WechatArticleId           int       `gorm:"column:wechat_article_id;type:int(11);comment:文章id;default:NULL;" json:"wechat_article_id"`                              // 文章id
+	ChatUserType              string    `gorm:"column:chat_user_type;type:enum('user', 'assistant');comment:用户方;default:NULL;" json:"chat_user_type"`                   // 用户方
+	Content                   string    `gorm:"column:content;type:longtext;comment:对话内容;" json:"content"`                                                              // 对话内容
+	SendTime                  time.Time `gorm:"column:send_time;type:datetime;comment:发送时间;default:NULL;" json:"send_time"`                                             // 发送时间
+	CreatedTime               time.Time `gorm:"column:created_time;type:datetime;comment:创建时间;default:NULL;" json:"created_time"`                                       // 创建时间
+	UpdateTime                time.Time `gorm:"column:update_time;type:datetime;comment:更新时间;default:NULL;" json:"update_time"`                                         // 更新时间
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *WechatArticleChatRecord) TableName() string {
+	return "wechat_article_chat_record"
+}
+
+// WechatArticleChatRecordColumns get sql column name.获取数据库列名
+var WechatArticleChatRecordColumns = struct {
+	WechatArticleChatRecordID string
+	WechatArticleID           string
+	ChatUserType              string
+	Content                   string
+	SendTime                  string
+	CreatedTime               string
+	UpdateTime                string
+}{
+	WechatArticleChatRecordID: "wechat_article_chat_record_id",
+	WechatArticleID:           "wechat_article_id",
+	ChatUserType:              "chat_user_type",
+	Content:                   "content",
+	SendTime:                  "send_time",
+	CreatedTime:               "created_time",
+	UpdateTime:                "update_time",
+}
+
+func (m *WechatArticleChatRecord) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+func (m *WechatArticleChatRecord) CreateInBatches(items []*WechatArticleChatRecord) (err error) {
+	err = global.DbMap[utils.DbNameAI].CreateInBatches(items, utils.MultiAddNum).Error
+
+	return
+}
+
+func (m *WechatArticleChatRecord) Update(updateCols []string) (err error) {
+	err = global.DbMap[utils.DbNameAI].Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *WechatArticleChatRecord) GetById(id int) (item *WechatArticle, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", WechatArticleChatRecordColumns.WechatArticleChatRecordID), id).First(&item).Error
+
+	return
+}

+ 184 - 0
models/rag/wechat_platform.go

@@ -0,0 +1,184 @@
+package rag
+
+import (
+	"database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+type WechatPlatform struct {
+	WechatPlatformId int       `gorm:"column:wechat_platform_id;type:int(10) UNSIGNED;primaryKey;not null;" description:"wechat_platform_id"`
+	FakeId           string    `gorm:"column:fake_id;type:varchar(255);comment:公众号唯一id;" description:"fake_id"`                             // 公众号唯一id
+	Nickname         string    `gorm:"column:nickname;type:varchar(255);comment:公众号名称;" description:"nickname"`                             // 公众号名称
+	Alias            string    `gorm:"column:alias;type:varchar(255);comment:别名;" description:"alias"`                                      // 别名
+	RoundHeadImg     string    `gorm:"column:round_head_img;type:varchar(255);comment:头像;" description:"round_head_img"`                    // 头像
+	ServiceType      int       `gorm:"column:service_type;type:int(11);comment:类型;default:0;" description:"service_type"`                   // 类型
+	Signature        string    `gorm:"column:signature;type:varchar(255);comment:签名;" description:"signature"`                              // 签名
+	Verified         int       `gorm:"column:verified;type:int(11);comment:是否认证,0:未认证,1:已认证;这个我不确定,再核实下;default:0;" description:"verified"` // 是否认证,0:未认证,1:已认证;这个我不确定,再核实下
+	ArticleLink      string    `gorm:"column:article_link;type:varchar(255);comment:添加公众时的文章链接;" description:"article_link"`                // 添加公众时的文章链接
+	Enabled          int       `gorm:"column:enabled;type:tinyint(9);comment:是否启用,0:禁用,1:启用;default:1;" description:"enabled"`              // 是否启用,0:禁用,1:启用
+	SysUserId        int       `gorm:"column:sys_user_id;type:int(9) UNSIGNED;comment:用户id;default:0;" description:"sys_user_id"`           // 用户id
+	ModifyTime       time.Time `gorm:"column:modify_time;type:datetime;comment:最后一次修改时间;default:NULL;" description:"modify_time"`           // 最后一次修改时间
+	CreateTime       time.Time `gorm:"column:create_time;type:datetime;comment:添加时间;default:NULL;" description:"create_time"`               // 添加时间
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *WechatPlatform) TableName() string {
+	return "wechat_platform"
+}
+
+// WechatPlatformColumns get sql column name.获取数据库列名
+var WechatPlatformColumns = struct {
+	WechatPlatformID string
+	FakeID           string
+	Nickname         string
+	Alias            string
+	RoundHeadImg     string
+	ServiceType      string
+	Signature        string
+	Verified         string
+	ArticleLink      string
+	Enabled          string
+	SysUserID        string
+	ModifyTime       string
+	CreateTime       string
+}{
+	WechatPlatformID: "wechat_platform_id",
+	FakeID:           "fake_id",
+	Nickname:         "nickname",
+	Alias:            "alias",
+	RoundHeadImg:     "round_head_img",
+	ServiceType:      "service_type",
+	Signature:        "signature",
+	Verified:         "verified",
+	ArticleLink:      "article_link",
+	Enabled:          "enabled",
+	SysUserID:        "sys_user_id",
+	ModifyTime:       "modify_time",
+	CreateTime:       "create_time",
+}
+
+func (m *WechatPlatform) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+func (m *WechatPlatform) Update(updateCols []string) (err error) {
+	err = global.DbMap[utils.DbNameAI].Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *WechatPlatform) Del() (err error) {
+	err = global.DbMap[utils.DbNameAI].Delete(&m).Error
+
+	return
+}
+
+func (m *WechatPlatform) GetById(wechatPlatformId int) (item *WechatPlatform, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", WechatPlatformColumns.WechatPlatformID), wechatPlatformId).First(&item).Error
+
+	return
+}
+
+func (m *WechatPlatform) GetByCondition(condition string, pars []interface{}) (item *WechatPlatform, err error) {
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).First(&item).Error
+
+	return
+}
+
+func (m *WechatPlatform) GetListByCondition(condition string, pars []interface{}, startSize, pageSize int) (items []*WechatPlatform, err error) {
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT ?,?`, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *WechatPlatform) GetCountByCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+func (m *WechatPlatform) GetPageListByCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*WechatPlatform, err error) {
+
+	total, err = m.GetCountByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByCondition(condition, pars, startSize, pageSize)
+	}
+
+	return
+}
+
+func (m *WechatPlatform) GetByFakeID(fakeId string) (item *WechatPlatform, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", WechatPlatformColumns.FakeID), fakeId).First(&item).Error
+
+	return
+}
+
+// Add
+// @Description: 添加一个新的公众号
+// @author: Roc
+// @receiver m
+// @datetime 2025-03-04 17:48:30
+// @param tagIdList []int
+// @return err error
+func (m *WechatPlatform) Add(tagIdList []int) (err error) {
+	tx := global.DbMap[utils.DbNameAI].Begin()
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+		} else {
+			_ = tx.Commit()
+		}
+	}()
+
+	err = tx.Create(&m).Error
+	if err != nil {
+		return
+	}
+
+	// 标签与公众号关系
+	if len(tagIdList) > 0 {
+		addTagMappingList := make([]*WechatPlatformTagMapping, 0)
+
+		for _, tagId := range tagIdList {
+			addTagMappingList = append(addTagMappingList, &WechatPlatformTagMapping{
+				WechatPlatformTagMappingId: 0,
+				WechatPlatformId:           m.WechatPlatformId,
+				TagId:                      tagId,
+				ModifyTime:                 time.Now(),
+				CreateTime:                 time.Now(),
+			})
+		}
+		err = tx.CreateInBatches(addTagMappingList, utils.MultiAddNum).Error
+		if err != nil {
+			return
+		}
+	}
+
+	// 用户与公众号关系
+	userMapping := &WechatPlatformUserMapping{
+		WechatPlatformUserMappingId: 0,
+		WechatPlatformId:            m.WechatPlatformId,
+		SysUserId:                   m.SysUserId,
+		ModifyTime:                  time.Now(),
+		CreateTime:                  time.Now(),
+	}
+	err = tx.Create(userMapping).Error
+
+	return
+}

+ 100 - 0
models/rag/wechat_platform_tag_mapping.go

@@ -0,0 +1,100 @@
+package rag
+
+import (
+	"database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+type WechatPlatformTagMapping struct {
+	WechatPlatformTagMappingId int       `gorm:"column:wechat_platform_tag_mapping_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"wechat_platform_tag_mapping_id"`
+	WechatPlatformId           int       `gorm:"column:wechat_platform_id;type:int(9) UNSIGNED;comment:微信公众号id;default:0;" description:"wechat_platform_id"` // 微信公众号id
+	TagId                      int       `gorm:"column:tag_id;type:int(9) UNSIGNED;comment:品种id;default:0;" description:"tag_id"`                            // 品种id
+	ModifyTime                 time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
+	CreateTime                 time.Time `gorm:"column:create_time;type:datetime;default:NULL;" description:"create_time"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *WechatPlatformTagMapping) TableName() string {
+	return "wechat_platform_tag_mapping"
+}
+
+// WechatPlatformTagMappingColumns get sql column name.获取数据库列名
+var WechatPlatformTagMappingColumns = struct {
+	WechatPlatformTagMappingID string
+	WechatPlatformID           string
+	TagID                      string
+	ModifyTime                 string
+	CreateTime                 string
+}{
+	WechatPlatformTagMappingID: "wechat_platform_tag_mapping_id",
+	WechatPlatformID:           "wechat_platform_id",
+	TagID:                      "tag_id",
+	ModifyTime:                 "modify_time",
+	CreateTime:                 "create_time",
+}
+
+func (m *WechatPlatformTagMapping) GetByID(WechatPlatformTagMappingColumnsId int) (item *WechatPlatformTagMapping, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", WechatPlatformTagMappingColumns.WechatPlatformTagMappingID), WechatPlatformTagMappingColumnsId).First(&item).Error
+
+	return
+}
+
+func (m *WechatPlatformTagMapping) GetByCondition(condition string, pars []interface{}) (item *WechatPlatformTagMapping, err error) {
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Where(condition, pars).First(&item).Error
+
+	return
+}
+
+func (m *WechatPlatformTagMapping) GetListByCondition(condition string, pars []interface{}, startSize, pageSize int) (items []*WechatPlatformTagMapping, err error) {
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s LIMIT ?,?`, m.TableName(), condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *WechatPlatformTagMapping) GetCountByCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+func (m *WechatPlatformTagMapping) GetPageListByCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*WechatPlatformTagMapping, err error) {
+
+	total, err = m.GetCountByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByCondition(condition, pars, startSize, pageSize)
+	}
+
+	return
+}
+
+type WechatPlatformTagItem struct {
+	TagId            int    `json:"tag_id"`
+	TagName          string `json:"tag_name"`
+	WechatPlatformId int    `json:"tag_article"`
+}
+
+func (m *WechatPlatformTagMapping) GetWechatPlatformTagListByWechatPlatformIdList(wechatPlatformIdList []int) (items []*WechatPlatformTagItem, err error) {
+	num := len(wechatPlatformIdList)
+	if num <= 0 {
+		return
+	}
+	sqlStr := `SELECT a.wechat_platform_id,b.tag_id,b.tag_name FROM wechat_platform_tag_mapping AS a 
+    JOIN tag AS b on a.tag_id = b.tag_id  WHERE a.wechat_platform_id IN (?) GROUP BY a.wechat_platform_id `
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, wechatPlatformIdList).Find(&items).Error
+
+	return
+}

+ 88 - 0
models/rag/wechat_platform_user_mapping.go

@@ -0,0 +1,88 @@
+package rag
+
+import (
+	"database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+type WechatPlatformUserMapping struct {
+	WechatPlatformUserMappingId int       `gorm:"column:wechat_platform_user_mapping_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"wechat_platform_user_mapping_id"`
+	WechatPlatformId            int       `gorm:"column:wechat_platform_id;type:int(9) UNSIGNED;comment:微信公众号id;default:0;" description:"wechat_platform_id"` // 微信公众号id
+	SysUserId                   int       `gorm:"column:sys_user_id;type:int(9) UNSIGNED;comment:用户id;default:0;" description:"sys_user_id"`                  // 用户id
+	ModifyTime                  time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
+	CreateTime                  time.Time `gorm:"column:create_time;type:datetime;default:NULL;" description:"create_time"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *WechatPlatformUserMapping) TableName() string {
+	return "wechat_platform_user_mapping"
+}
+
+// WechatPlatformUserMappingColumns get sql column name.获取数据库列名
+var WechatPlatformUserMappingColumns = struct {
+	WechatPlatformUserMappingID string
+	WechatPlatformID            string
+	SysUserID                   string
+	ModifyTime                  string
+	CreateTime                  string
+}{
+	WechatPlatformUserMappingID: "wechat_platform_user_mapping_id",
+	WechatPlatformID:            "wechat_platform_id",
+	SysUserID:                   "sys_user_id",
+	ModifyTime:                  "modify_time",
+	CreateTime:                  "create_time",
+}
+
+type UserFollowWechatPlatform struct {
+	WechatPlatformId int       `gorm:"column:wechat_platform_id;type:int(10) UNSIGNED;primaryKey;not null;" description:"wechat_platform_id"`
+	FakeId           string    `gorm:"column:fake_id;type:varchar(255);comment:公众号唯一id;" description:"公众号唯一id"`
+	Nickname         string    `gorm:"column:nickname;type:varchar(255);comment:公众号名称;" description:"公众号名称"`
+	Alias            string    `gorm:"column:alias;type:varchar(255);comment:别名;" description:"别名"`
+	RoundHeadImg     string    `gorm:"column:round_head_img;type:varchar(255);comment:头像;" description:"头像"`
+	ServiceType      int       `gorm:"column:service_type;type:int(11);comment:类型;default:0;" description:"类型"`
+	Signature        string    `gorm:"column:signature;type:varchar(255);comment:签名;" description:"签名"`
+	Verified         int       `gorm:"column:verified;type:int(11);comment:是否认证,0:未认证,1:已认证;这个我不确定,再核实下;default:0;" description:"是否认证,0:未认证,1:已认证;这个我不确定,再核实下"`
+	ArticleLink      string    `gorm:"column:article_link;type:varchar(255);comment:添加公众时的文章链接;" description:"添加公众时的文章链接"`
+	Enabled          int       `gorm:"column:enabled;type:tinyint(9);comment:是否启用,0:禁用,1:启用;default:1;" description:"是否启用,0:禁用,1:启用"`
+	SysUserId        int       `gorm:"column:sys_user_id;type:int(9) UNSIGNED;comment:用户id;default:0;" description:"用户id"`
+	ModifyTime       time.Time `gorm:"column:modify_time;type:datetime;comment:最后一次修改时间;default:NULL;" description:"最后一次修改时间"`
+	CreateTime       time.Time `gorm:"column:create_time;type:datetime;comment:添加时间;default:NULL;" description:"添加时间"`
+	FollowUserId     int       `gorm:"column:follow_user_id;type:int(9) UNSIGNED;comment:关注的用户id;default:0;" description:"关注的用户id"`
+}
+
+func (m *WechatPlatformUserMapping) GetListByCondition(condition string, pars []interface{}, startSize, pageSize int) (items []*UserFollowWechatPlatform, err error) {
+	sqlStr := fmt.Sprintf(`SELECT a.wechat_platform_id,a.fake_id,a.nickname,a.alias,a.round_head_img,a.service_type,a.signature,a.verified,a.article_link,a.enabled,a.sys_user_id,a.modify_time,a.create_time,b.sys_user_id as follow_user_id FROM wechat_platform a 
+    JOIN wechat_platform_user_mapping b on a.wechat_platform_id=b.wechat_platform_id 
+                                                                                                                                                                                                                   WHERE 1=1 %s LIMIT ?,?`, condition)
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *WechatPlatformUserMapping) GetCountByCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM wechat_platform a JOIN wechat_platform_user_mapping b on a.wechat_platform_id=b.wechat_platform_id WHERE 1=1 `, condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Scan(&intNull).Error
+	if err == nil && intNull.Valid {
+		total = int(intNull.Int64)
+	}
+
+	return
+}
+
+func (m *WechatPlatformUserMapping) GetPageListByCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*UserFollowWechatPlatform, err error) {
+
+	total, err = m.GetCountByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByCondition(condition, pars, startSize, pageSize)
+	}
+
+	return
+}

+ 64 - 39
models/report.go

@@ -6,11 +6,10 @@ import (
 	"eta/eta_api/global"
 	"eta/eta_api/utils"
 	"fmt"
-	"strings"
-	"time"
-
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"gorm.io/gorm"
+	"strings"
+	"time"
 )
 
 // 报告状态
@@ -34,42 +33,43 @@ const (
 )
 
 type Report struct {
-	Id                 int       `gorm:"column:id;primaryKey;autoIncrement" 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         time.Time `description:"修改时间"`
-	State              int       `description:"1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过"`
-	PublishTime        time.Time `description:"发布时间"`
-	Stage              int       `description:"期数"`
-	MsgIsSend          int       `description:"消息是否已发送,0:否,1:是"`
-	ThsMsgIsSend       int       `description:"客户群消息是否已发送,0:否,1:是"`
-	Content            string    `description:"内容"`
-	VideoUrl           string    `description:"音频文件URL"`
-	VideoName          string    `description:"音频文件名称"`
-	VideoPlaySeconds   string    `description:"音频播放时长"`
-	VideoSize          string    `description:"音频文件大小,单位M"`
-	ContentSub         string    `description:"内容前两个章节"`
-	ReportCode         string    `description:"报告唯一编码"`
-	ReportVersion      int       `description:"1:旧版,2:新版"`
-	HasChapter         int       `description:"是否有章节 0-否 1-是"`
-	ChapterType        string    `description:"章节类型 day-晨报 week-周报"`
-	OldReportId        int       `description:"research_report表ID, 大于0则表示该报告为老后台同步过来的"`
-	MsgSendTime        time.Time `description:"模版消息发送时间"`
-	AdminId            int       `description:"创建者账号"`
-	AdminRealName      string    `description:"创建者姓名"`
-	ApproveTime        time.Time `description:"审批时间"`
-	ApproveId          int       `description:"审批ID"`
-	DetailImgUrl       string    `description:"报告详情长图地址"`
-	DetailPdfUrl       string    `description:"报告详情PDF地址"`
-
+	Id                  int       `orm:"column(id)" 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          time.Time `description:"修改时间"`
+	State               int       `description:"1:未发布;2:已发布;3-待提交;4-待审批;5-已驳回;6-已通过"`
+	PublishTime         time.Time `description:"发布时间"`
+	Stage               int       `description:"期数"`
+	MsgIsSend           int       `description:"消息是否已发送,0:否,1:是"`
+	ThsMsgIsSend        int       `description:"客户群消息是否已发送,0:否,1:是"`
+	Content             string    `description:"内容"`
+	VideoUrl            string    `description:"音频文件URL"`
+	VideoName           string    `description:"音频文件名称"`
+	VideoPlaySeconds    string    `description:"音频播放时长"`
+	VideoSize           string    `description:"音频文件大小,单位M"`
+	ContentSub          string    `description:"内容前两个章节"`
+	ReportCode          string    `description:"报告唯一编码"`
+	ReportVersion       int       `description:"1:旧版,2:新版"`
+	HasChapter          int       `description:"是否有章节 0-否 1-是"`
+	ChapterType         string    `description:"章节类型 day-晨报 week-周报"`
+	OldReportId         int       `description:"research_report表ID, 大于0则表示该报告为老后台同步过来的"`
+	MsgSendTime         time.Time `description:"模版消息发送时间"`
+	AdminId             int       `description:"创建者账号"`
+	AdminRealName       string    `description:"创建者姓名"`
+	ApproveTime         time.Time `description:"审批时间"`
+	ApproveId           int       `description:"审批ID"`
+	DetailImgUrl        string    `description:"报告详情长图地址"`
+	DetailPdfUrl        string    `description:"报告详情PDF地址"`
+	DetailImgUrlMobile  string    `description:"报告详情长图地址-手机端"`
+	DetailPdfUrlMobile  string    `description:"报告详情PDF地址-手机端"`
 	ContentStruct       string    `description:"内容组件"`
 	LastModifyAdminId   int       `description:"最后更新人ID"`
 	LastModifyAdminName string    `description:"最后更新人姓名"`
@@ -147,6 +147,8 @@ type ReportList struct {
 	ApproveTime        string                    `description:"审批时间"`
 	DetailImgUrl       string                    `description:"报告详情长图地址"`
 	DetailPdfUrl       string                    `description:"报告详情PDF地址"`
+	DetailImgUrlMobile string                    `description:"报告详情长图地址-手机端"`
+	DetailPdfUrlMobile string                    `description:"报告详情PDF地址-手机端"`
 
 	CollaborateType     int8      `description:"协作方式,1:个人,2:多人协作。默认:1"`
 	ReportLayout        int8      `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
@@ -1570,6 +1572,20 @@ func ModifyReportImgUrl(reportId int, detailImgUrl string) (err error) {
 	return
 }
 
+func ModifyReportPdfUrlMobile(reportId int, detailPdfUrlMobile string) (err error) {
+	o := global.DbMap[utils.DbNameReport]
+	sql := `UPDATE report SET detail_pdf_url_mobile=? WHERE id=? `
+	err = o.Exec(sql, detailPdfUrlMobile, reportId).Error
+	return
+}
+
+func ModifyReportImgUrlMobile(reportId int, detailImgUrlMobile string) (err error) {
+	o := global.DbMap[utils.DbNameReport]
+	sql := `UPDATE report SET detail_img_url_mobile=? WHERE id=? `
+	err = o.Exec(sql, detailImgUrlMobile, reportId).Error
+	return
+}
+
 // UpdatePdfUrlReportById 清空pdf相关字段
 func UpdatePdfUrlReportById(reportId int) (err error) {
 	o := global.DbMap[utils.DbNameReport]
@@ -1664,3 +1680,12 @@ func FindReportListByCondition(condition string, pars []interface{}) (items []*R
 	err = o.Raw(sql, pars...).Find(&items).Error
 	return
 }
+
+type ReportShartUrlReq struct {
+	Url      string `description:"分享链接"`
+	ReportId int    `description:"报告ID"`
+}
+
+type ReportShartUrlResp struct {
+	UrlToken string `description:"分享链接token"`
+}

+ 2 - 0
models/report_approve/report_approve.go

@@ -200,6 +200,8 @@ type ReportApproveItem struct {
 	ModifyTime            string `description:"修改时间"`
 	DetailImgUrl          string `description:"报告详情长图地址"`
 	DetailPdfUrl          string `description:"报告详情PDF地址"`
+	DetailImgUrlMobile    string `description:"报告详情长图地址-手机端"`
+	DetailPdfUrlMobile    string `description:"报告详情PDF地址-手机端"`
 }
 
 // FormatReportApproveOrm2Item 格式化报告审批

+ 21 - 19
models/sandbox/sandbox_classify.go

@@ -129,25 +129,27 @@ func GetSandboxInfoCountByClassifyId(classifyId int) (count int, err error) {
 	var sql string
 	var pars []interface{}
 	if utils.DbDriverName == utils.DbDriverByDm {
-		sql = `WITH RECURSIVE sandbox_classify_cte (sandbox_classify_id, parent_id) AS (
-			SELECT sandbox_classify_id, parent_id
-			FROM sandbox_classify
-			WHERE parent_id = ?
-			UNION ALL
-			SELECT c.sandbox_classify_id, c.parent_id
-			FROM sandbox_classify c
-			INNER JOIN sandbox_classify_cte ct ON c.parent_id = ct.sandbox_classify_id
-		)
-		SELECT COUNT(1) AS count
-		FROM sandbox a
-		WHERE a.sandbox_classify_id IN (
-			SELECT sandbox_classify_id
-			FROM sandbox_classify_cte
-			UNION
-			SELECT sandbox_classify_id
-			FROM sandbox_classify
-			WHERE sandbox_classify_id = ?
-		)`
+		sql = `WITH RECURSIVE classify_tree AS (
+    -- 基础查询:获取起始节点
+    SELECT sandbox_classify_id, parent_id, 1 as level
+    FROM sandbox_classify
+    WHERE sandbox_classify_id = ?
+    
+    UNION ALL
+    
+    -- 递归查询:获取所有子节点
+    SELECT c.sandbox_classify_id, c.parent_id, t.level + 1
+    FROM sandbox_classify c
+    INNER JOIN classify_tree t ON c.parent_id = t.sandbox_classify_id
+    WHERE c.parent_id IS NOT NULL
+)
+SELECT COUNT(1) AS count 
+FROM sandbox a
+WHERE a.sandbox_classify_id IN (
+    SELECT sandbox_classify_id 
+    FROM classify_tree
+) 
+AND a.is_delete = 0`
 		pars = append(pars, classifyId, classifyId)
 	}else{
 		sql = ` SELECT COUNT(1) AS count FROM sandbox AS a

+ 2 - 0
models/smart_report/smart_report.go

@@ -51,6 +51,8 @@ type SmartReport struct {
 	MsgSendTime         time.Time `description:"模版消息发送时间"`
 	DetailImgUrl        string    `description:"报告详情长图地址"`
 	DetailPdfUrl        string    `description:"报告详情PDF地址"`
+	DetailImgUrlMobile  string    `description:"报告详情长图地址-手机端"`
+	DetailPdfUrlMobile  string    `description:"报告详情PDF地址-手机端"`
 	CreateTime          time.Time `description:"创建时间"`
 	ModifyTime          time.Time `description:"修改时间"`
 	HeadImg             string    `description:"报告头图地址"`

+ 13 - 2
models/system/sys_group.go

@@ -3,6 +3,7 @@ package system
 import (
 	"eta/eta_api/global"
 	"eta/eta_api/utils"
+	"gorm.io/gorm"
 	"time"
 )
 
@@ -84,7 +85,12 @@ func DeleteSysGroup(groupId int) (err error) {
 	err = o.Exec(sql, groupId).Error
 	return
 }
-
+func DeleteSysGroupByIds(tx *gorm.DB,groupIds []int) (err error) {
+	sql := `DELETE FROM sys_group WHERE group_id in ?`
+	//o := global.DbMap[utils.DbNameMaster]
+	err = tx.Exec(sql, groupIds).Error
+	return
+}
 // 因前端显示需要,TopId字段用来当做一级部门id,DepartmentId为当前分组id
 type SysGroupList struct {
 	GroupId      int            `orm:"column(group_id);pk" gorm:"primaryKey" json:"DepartmentId" description:"分组ID"`
@@ -118,7 +124,12 @@ func ClearSysUserGroup(groupId int) (err error) {
 	err = o.Exec(sql, groupId).Error
 	return
 }
-
+func ClearSysUserGroupByIds(tx *gorm.DB,groupIds []int) (err error) {
+	sql := `UPDATE admin SET group_id=0,group_name='' WHERE group_id in ? `
+	sql = utils.ReplaceDriverKeywords("", sql)
+	err = tx.Exec(sql, groupIds).Error
+	return
+}
 func GetSysGroupByDirectorId(directorId int) (items []*SysGroupList, err error) {
 	sql := `SELECT * FROM sys_group WHERE group_id=? ORDER BY sort ASC, create_time ASC`
 	o := global.DbMap[utils.DbNameMaster]

+ 1 - 0
models/system/sys_user.go

@@ -23,6 +23,7 @@ type LoginResp struct {
 	SysRoleTypeCode string `description:"角色类型编码"`
 	AdminId         int    `description:"系统用户id"`
 	ProductName     string `description:"产品名称:admin,ficc,权益"`
+	Mobile          string `description:"手机号"`
 	Authority       int    `description:"管理权限,0:无,1:部门负责人,2:小组负责人,或者ficc销售主管,4:ficc销售组长"`
 }
 

+ 522 - 0
routers/commentsRouter.go

@@ -2914,6 +2914,231 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"],
+        beego.ControllerComments{
+            Method: "PurangClassify",
+            Router: `/purang/classify`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"],
+        beego.ControllerComments{
+            Method: "PurangIndexList",
+            Router: `/purang/classify/index/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"],
+        beego.ControllerComments{
+            Method: "PurangEdbInfoAdd",
+            Router: `/purang/edb_info/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"],
+        beego.ControllerComments{
+            Method: "PurangAddCheck",
+            Router: `/purang/edb_info/add_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"],
+        beego.ControllerComments{
+            Method: "PurangNameCheck",
+            Router: `/purang/edb_info/name_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"],
+        beego.ControllerComments{
+            Method: "ExportPurangList",
+            Router: `/purang/export`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"],
+        beego.ControllerComments{
+            Method: "GetFrequency",
+            Router: `/purang/frequency`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"],
+        beego.ControllerComments{
+            Method: "PurangIndexData",
+            Router: `/purang/index/data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"],
+        beego.ControllerComments{
+            Method: "PurangSearchList",
+            Router: `/purang/search_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromPurangController"],
+        beego.ControllerComments{
+            Method: "PurangSingleData",
+            Router: `/purang/single_data`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "ClassifyAdd",
+            Router: `/radish_research/classify/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "ClassifyEdit",
+            Router: `/radish_research/classify/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "ClassifyList",
+            Router: `/radish_research/classify/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "ClassifyMove",
+            Router: `/radish_research/classify/move`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "ClassifyRemove",
+            Router: `/radish_research/classify/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "ClassifyTree",
+            Router: `/radish_research/classify/tree`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "EdbAdd",
+            Router: `/radish_research/edb/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "EdbMultiAdd",
+            Router: `/radish_research/edb/multi_add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "EdbNameCheck",
+            Router: `/radish_research/edb/name_check`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "IndexDetail",
+            Router: `/radish_research/index/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "IndexEdit",
+            Router: `/radish_research/index/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "IndexExport",
+            Router: `/radish_research/index/export`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "IndexPageList",
+            Router: `/radish_research/index/page_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "IndexRemove",
+            Router: `/radish_research/index/remove`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRadishResearchController"],
+        beego.ControllerComments{
+            Method: "IndexSelect",
+            Router: `/radish_research/index/select`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:BaseFromRzdIndexController"],
         beego.ControllerComments{
             Method: "RzdClassify",
@@ -3571,6 +3796,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
+        beego.ControllerComments{
+            Method: "GeneralChartToken",
+            Router: `/chart_info/common/general_token`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:ChartInfoController"],
         beego.ControllerComments{
             Method: "ChartInfoConvertDetail",
@@ -8062,6 +8296,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportController"],
+        beego.ControllerComments{
+            Method: "GetShareUrl",
+            Router: `/share_url`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportEmailCallBackController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/english_report:EnglishReportEmailCallBackController"],
         beego.ControllerComments{
             Method: "SendCallBack",
@@ -8386,6 +8629,267 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:AbstractController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:AbstractController"],
+        beego.ControllerComments{
+            Method: "Del",
+            Router: `/abstract/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:AbstractController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:AbstractController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/abstract/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:AbstractController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:AbstractController"],
+        beego.ControllerComments{
+            Method: "AddVector",
+            Router: `/abstract/vector/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:AbstractController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:AbstractController"],
+        beego.ControllerComments{
+            Method: "VectorDel",
+            Router: `/abstract/vector/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:ChatWsController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:ChatWsController"],
+        beego.ControllerComments{
+            Method: "ChatConnect",
+            Router: `/chat/connect`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:KbController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:KbController"],
+        beego.ControllerComments{
+            Method: "SearchDocs",
+            Router: `/knowledge_base/searchDocs`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"],
+        beego.ControllerComments{
+            Method: "PromoteContentList",
+            Router: `/promote/content_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"],
+        beego.ControllerComments{
+            Method: "DeletePromoteContent",
+            Router: `/promote/delete_content`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"],
+        beego.ControllerComments{
+            Method: "GenerateContent",
+            Router: `/promote/generate_content`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:PromoteController"],
+        beego.ControllerComments{
+            Method: "SavePromoteContent",
+            Router: `/promote/save_content`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/question/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
+        beego.ControllerComments{
+            Method: "Del",
+            Router: `/question/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
+        beego.ControllerComments{
+            Method: "Edit",
+            Router: `/question/edit`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/question/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"],
+        beego.ControllerComments{
+            Method: "ChatRecordList",
+            Router: `/chat/chat_record_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"],
+        beego.ControllerComments{
+            Method: "ChatRecordAdd",
+            Router: `/chat/chat_record_save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"],
+        beego.ControllerComments{
+            Method: "DeleteChat",
+            Router: `/chat/delete_chat`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"],
+        beego.ControllerComments{
+            Method: "NewChat",
+            Router: `/chat/new_chat`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"],
+        beego.ControllerComments{
+            Method: "RenameChat",
+            Router: `/chat/rename_chat`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"],
+        beego.ControllerComments{
+            Method: "GetUserChatList",
+            Router: `/chat/user_chat_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"],
+        beego.ControllerComments{
+            Method: "TagList",
+            Router: `/tag/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"],
+        beego.ControllerComments{
+            Method: "Add",
+            Router: `/wechat_platform/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"],
+        beego.ControllerComments{
+            Method: "ArticleDel",
+            Router: `/wechat_platform/article/del`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"],
+        beego.ControllerComments{
+            Method: "ArticleDetail",
+            Router: `/wechat_platform/article/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"],
+        beego.ControllerComments{
+            Method: "ArticleList",
+            Router: `/wechat_platform/article/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"],
+        beego.ControllerComments{
+            Method: "FollowList",
+            Router: `/wechat_platform/list/follow`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"],
+        beego.ControllerComments{
+            Method: "PublicList",
+            Router: `/wechat_platform/list/public`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"],
+        beego.ControllerComments{
+            Method: "Op",
+            Router: `/wechat_platform/op`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"],
+        beego.ControllerComments{
+            Method: "Refresh",
+            Router: `/wechat_platform/refresh`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/material:MaterialController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/material:MaterialController"],
         beego.ControllerComments{
             Method: "BatchAdd",
@@ -11302,6 +11806,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportCommonController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportCommonController"],
+        beego.ControllerComments{
+            Method: "ShareTransform",
+            Router: `/share/link`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
             Method: "CheckDayWeekReportChapterVideo",
@@ -11725,6 +12238,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
+        beego.ControllerComments{
+            Method: "ShareGenerate",
+            Router: `/share/generate`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers:ReportController"],
         beego.ControllerComments{
             Method: "ThsSendTemplateMsg",

+ 17 - 0
routers/router.go

@@ -30,6 +30,7 @@ import (
 	"eta/eta_api/controllers/eta_forum"
 	"eta/eta_api/controllers/eta_trial"
 	"eta/eta_api/controllers/fe_calendar"
+	"eta/eta_api/controllers/llm"
 	"eta/eta_api/controllers/material"
 	"eta/eta_api/controllers/report_approve"
 	"eta/eta_api/controllers/residual_analysis"
@@ -39,6 +40,8 @@ import (
 	"eta/eta_api/controllers/smart_report"
 	"eta/eta_api/controllers/speech_recognition"
 	"eta/eta_api/controllers/trade_analysis"
+	"eta/eta_api/services"
+
 	"github.com/beego/beego/v2/server/web"
 	"github.com/beego/beego/v2/server/web/filter/cors"
 )
@@ -52,6 +55,7 @@ func init() {
 		ExposeHeaders:    []string{"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"},
 		AllowCredentials: true,
 	}))
+	web.InsertFilter("/adminapi/share/*", web.BeforeRouter, services.FilterShareUrl())
 	ns := web.NewNamespace("/adminapi",
 		web.NSNamespace("/sysuser",
 			web.NSInclude(
@@ -66,6 +70,17 @@ func init() {
 				&controllers.ClassifyController{},
 			),
 		),
+		web.NSNamespace("/llm",
+			web.NSInclude(
+				&llm.ChatWsController{},
+				&llm.UserChatController{},
+				&llm.KbController{},
+				&llm.WechatPlatformController{},
+				&llm.QuestionController{},
+				&llm.AbstractController{},
+				&llm.PromoteController{},
+			),
+		),
 		web.NSNamespace("/banner",
 			web.NSInclude(
 				&controllers.BannerController{},
@@ -189,6 +204,8 @@ func init() {
 				&data_manage.BaseFromRzdIndexController{},
 				&data_manage.ClarksonsDataController{},
 				&data_manage.BaseFromGprRiskController{},
+				&data_manage.BaseFromPurangController{},
+				&data_manage.BaseFromRadishResearchController{},
 			),
 		),
 		web.NSNamespace("/my_chart",

+ 49 - 0
services/data/base_from_purang.go

@@ -0,0 +1,49 @@
+package data
+
+import (
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/utils"
+	"fmt"
+)
+
+// PurangIndexSource2Edb 新增普瑞数据源到指标库
+func PurangIndexSource2Edb(req data_manage.PurangIndexSource2EdbReq, lang string) (edb *data_manage.EdbInfo, err error, errMsg string, skip bool) {
+	if req.EdbCode == "" {
+		err = fmt.Errorf("指标ID为空")
+		return
+	}
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("PurangIndexSource2Edb新增失败, Err: %s", err.Error())
+			fmt.Println(tips)
+			utils.FileLog.Info(tips)
+		}
+	}()
+	source := utils.DATA_SOURCE_PURANG
+
+	// 是否新增过指标
+	exist, e := data_manage.GetEdbInfoByEdbCode(source, req.EdbCode)
+	if e != nil && !utils.IsErrNoRow(e) {
+		err = fmt.Errorf("获取指标是否存在失败, err: %s", e.Error())
+		return
+	}
+	if exist != nil && exist.EdbInfoId > 0 {
+		skip = true
+		return
+	}
+
+	// 开始结束时间
+	var startDate, endDate string
+
+	// 新增指标库
+	edbInfo, e, msg, _ := EdbInfoAdd(source, utils.DATA_SUB_SOURCE_EDB, req.ClassifyId, req.EdbCode, req.EdbName, req.Frequency, req.Unit, startDate, endDate, req.AdminId, req.AdminRealName, lang)
+	if e != nil {
+		errMsg = msg
+		err = fmt.Errorf("EdbInfo: 新增指标失败, err: %s", e.Error())
+		return
+	}
+
+	edb = edbInfo
+
+	return
+} 

+ 611 - 0
services/data/base_from_radish_research_classify.go

@@ -0,0 +1,611 @@
+package data
+
+import (
+	"errors"
+	"eta/eta_api/models/data_manage"
+	dataSourceModel "eta/eta_api/models/data_source"
+	"eta/eta_api/models/system"
+	"eta/eta_api/services/elastic"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// GetRadishResearchClassifyTreeRecursive 递归获取分类树形结构
+func GetRadishResearchClassifyTreeRecursive(list []*data_manage.BaseFromRadishResearchClassifyItem, parentId int) []*data_manage.BaseFromRadishResearchClassifyItem {
+	res := make([]*data_manage.BaseFromRadishResearchClassifyItem, 0)
+	for _, v := range list {
+		if v.ParentId == parentId {
+			t := GetRadishResearchClassifyTreeRecursive(list, v.ClassifyId)
+			v.Children = nil // 这一步是方便前端组件判断null...
+			if len(t) > 0 {
+				v.Children = t
+			}
+			res = append(res, v)
+		}
+	}
+	return res
+}
+
+// RadishResearchMoveClassify 移动指标分类
+func RadishResearchMoveClassify(req data_manage.BaseFromRadishResearchClassifyMoveReq, sysUser *system.Admin) (err error, errMsg string) {
+	// req.ClassifyId, req.ParentClassifyId, req.PrevClassifyId, req.NextClassifyId
+	classifyId := req.ClassifyId
+	parentClassifyId := req.ParentClassifyId
+	prevClassifyId := req.PrevClassifyId
+	nextClassifyId := req.NextClassifyId
+
+	itemId := req.ItemId
+	prevItemId := req.PrevItemId
+	nextItemId := req.NextItemId
+
+	//首先确定移动的对象是分类还是指标
+	//判断上一个节点是分类还是指标
+	//判断下一个节点是分类还是指标
+	//同时更新分类目录下的分类sort和指标sort
+	//更新当前移动的分类或者指标sort
+
+	var parentEdbClassifyInfo *data_manage.BaseFromRadishResearchClassify
+	if parentClassifyId > 0 {
+		parentEdbClassifyInfo, err = data_manage.GetRadishResearchClassifyById(parentClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上级分类信息失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	//如果有传入 上一个兄弟节点分类id
+	var (
+		edbClassifyInfo *data_manage.BaseFromRadishResearchClassify
+		prevClassify    *data_manage.BaseFromRadishResearchClassify
+		nextClassify    *data_manage.BaseFromRadishResearchClassify
+
+		edbInfo     *data_manage.BaseFromRadishResearchIndex
+		prevEdbInfo *data_manage.BaseFromRadishResearchIndex
+		nextEdbInfo *data_manage.BaseFromRadishResearchIndex
+		prevSort    int
+		nextSort    int
+	)
+
+	// 移动对象为分类, 判断权限
+	if itemId == 0 {
+		edbClassifyInfo, err = data_manage.GetRadishResearchClassifyById(classifyId)
+		if err != nil {
+			if utils.IsErrNoRow(err) {
+				errMsg = "当前分类不存在"
+				err = errors.New("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+		if parentClassifyId > 0 && parentEdbClassifyInfo.Level == 6 {
+			errMsg = "最高只支持添加6级分类"
+			err = errors.New(errMsg)
+			return
+		}
+		// 如果是移动目录, 那么校验一下父级目录下是否有重名目录
+		exists, e := data_manage.GetRadishResearchClassifyByParentIdAndName(parentClassifyId, edbClassifyInfo.ClassifyName, classifyId)
+		if e != nil && !utils.IsErrNoRow(e) {
+			errMsg = "移动失败"
+			err = fmt.Errorf("获取父级分类下的同名分类失败, Err: %s", e.Error())
+			return
+		}
+		if exists != nil && exists.ClassifyId > 0 {
+			errMsg = "移动失败,分类名称已存在"
+			return
+		}
+
+	} else {
+		edbInfo, err = data_manage.GetRadishResearchIndexById(req.ItemId)
+		if err != nil {
+			if utils.IsErrNoRow(err) {
+				errMsg = "当前指标不存在"
+				err = errors.New("获取分类信息失败,Err:" + err.Error())
+				return
+			}
+			errMsg = "移动失败"
+			err = errors.New("获取分类信息失败,Err:" + err.Error())
+			return
+		}
+		if edbInfo != nil && edbInfo.BaseFromRadishResearchIndexId <= 0 {
+			errMsg = "当前指标不存在"
+			err = errors.New("获取指标信息失败, Err:" + err.Error())
+			return
+		}
+		if parentClassifyId == 0 {
+			errMsg = "移动失败,指标必须挂在分类下"
+			err = errors.New(errMsg)
+			return
+		}
+
+		//// 移动权限校验
+		//button := GetEdbOpButton(sysUser, edbInfo.SysUserId, edbInfo.EdbType, edbInfo.EdbInfoType, haveOperaAuth)
+		//if !button.MoveButton {
+		//	errMsg = "无操作权限"
+		//	err = errors.New(errMsg)
+		//	return
+		//}
+	}
+
+	if prevClassifyId > 0 {
+		prevClassify, err = data_manage.GetRadishResearchClassifyById(prevClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevClassify.Sort
+	} else if prevItemId > 0 {
+		prevEdbInfo, err = data_manage.GetRadishResearchIndexById(prevItemId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取上一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		prevSort = prevEdbInfo.Sort
+	}
+
+	if nextClassifyId > 0 {
+		//下一个兄弟节点
+		nextClassify, err = data_manage.GetRadishResearchClassifyById(nextClassifyId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextClassify.Sort
+	} else if nextItemId > 0 {
+		//下一个兄弟节点
+		nextEdbInfo, err = data_manage.GetRadishResearchIndexById(nextItemId)
+		if err != nil {
+			errMsg = "移动失败"
+			err = errors.New("获取下一个兄弟节点分类信息失败,Err:" + err.Error())
+			return
+		}
+		nextSort = nextEdbInfo.Sort
+	}
+
+	err, errMsg = radishResearchMoveEdbClassify(parentEdbClassifyInfo, edbClassifyInfo, prevClassify, nextClassify, edbInfo, prevEdbInfo, nextEdbInfo, parentClassifyId, prevSort, nextSort)
+	return
+}
+
+// radishResearchMoveEdbClassify 移动指标分类
+func radishResearchMoveEdbClassify(parentEdbClassifyInfo, edbClassifyInfo, prevClassify, nextClassify *data_manage.BaseFromRadishResearchClassify, edbInfo, prevEdbInfo, nextEdbInfo *data_manage.BaseFromRadishResearchIndex, parentClassifyId int, prevSort, nextSort int) (err error, errMsg string) {
+	updateCol := make([]string, 0)
+	var moveParent bool  // 是否移动分类的父级目录
+	var moveParentId int // 被改变父级分类的目录ID
+
+	indexOb := new(data_manage.BaseFromRadishResearchIndex)
+
+	// 移动对象为分类, 判断分类是否存在
+	if edbClassifyInfo != nil {
+		oldParentId := edbClassifyInfo.ParentId
+		oldLevel := edbClassifyInfo.Level
+		var classifyIds []int
+		if oldParentId != parentClassifyId {
+			//更新子分类对应的level
+			childList, e, m := GetRadishResearchChildClassifyByClassifyId(edbClassifyInfo.BaseFromRadishResearchClassifyId)
+			if e != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询子分类失败,Err:" + e.Error() + m)
+				return
+			}
+
+			if len(childList) > 0 {
+				for _, v := range childList {
+					if v.BaseFromRadishResearchClassifyId == edbClassifyInfo.BaseFromRadishResearchClassifyId {
+						continue
+					}
+					classifyIds = append(classifyIds, v.BaseFromRadishResearchClassifyId)
+				}
+			}
+		}
+		//判断上级id是否一致,如果不一致的话,那么需要移动该分类层级
+		if edbClassifyInfo.ParentId != parentClassifyId && parentClassifyId != 0 {
+			if edbClassifyInfo.Level != parentEdbClassifyInfo.Level+1 { //禁止层级调整
+				errMsg = "移动失败"
+				err = errors.New("不支持目录层级变更")
+				return
+			}
+			edbClassifyInfo.ParentId = parentEdbClassifyInfo.BaseFromRadishResearchClassifyId
+			edbClassifyInfo.RootId = parentEdbClassifyInfo.RootId
+			edbClassifyInfo.Level = parentEdbClassifyInfo.Level + 1
+			edbClassifyInfo.LevelPath = fmt.Sprintf("%s,%d", parentEdbClassifyInfo.LevelPath, edbClassifyInfo.BaseFromRadishResearchClassifyId) // 注意更新层级路径
+			edbClassifyInfo.ModifyTime = time.Now()
+			// 更改层级路径
+			edbClassifyInfo.LevelPath = fmt.Sprintf("%s,%d", parentEdbClassifyInfo.LevelPath, edbClassifyInfo.BaseFromRadishResearchClassifyId)
+			updateCol = append(updateCol, "ParentId", "RootId", "Level", "LevelPath", "ModifyTime", "LevelPath")
+			moveParent = true
+			moveParentId = edbClassifyInfo.BaseFromRadishResearchClassifyId
+		} else if edbClassifyInfo.ParentId != parentClassifyId && parentClassifyId == 0 {
+			errMsg = "移动失败"
+			err = errors.New("不支持目录层级变更")
+			return
+		}
+
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == edbClassifyInfo.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更分类
+					if prevClassify != nil {
+						_ = data_manage.UpdateRadishResearchClassifySortByParentId(parentClassifyId, prevClassify.BaseFromRadishResearchClassifyId, prevClassify.Sort, updateSortStr)
+					} else {
+						_ = data_manage.UpdateRadishResearchClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr)
+					}
+
+					//变更指标
+					if prevEdbInfo != nil {
+						//变更兄弟节点的排序
+						_ = data_manage.UpdateRadishResearchIndexSortByClassifyId(parentClassifyId, prevSort, prevEdbInfo.BaseFromRadishResearchIndexId, updateSortStr)
+					} else {
+						_ = data_manage.UpdateRadishResearchIndexSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+
+						//变更分类
+						if prevClassify != nil {
+							_ = data_manage.UpdateRadishResearchClassifySortByParentId(parentClassifyId, prevClassify.BaseFromRadishResearchClassifyId, prevSort, updateSortStr)
+						} else {
+							_ = data_manage.UpdateRadishResearchClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr)
+						}
+
+						//变更指标
+						if prevEdbInfo != nil {
+							//变更兄弟节点的排序
+							_ = data_manage.UpdateRadishResearchIndexSortByClassifyId(parentClassifyId, prevSort, prevEdbInfo.BaseFromRadishResearchIndexId, updateSortStr)
+						} else {
+							_ = data_manage.UpdateRadishResearchIndexSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr)
+						}
+
+					}
+				}
+			}
+
+			edbClassifyInfo.Sort = prevSort + 1
+			edbClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else if prevClassify == nil && nextClassify == nil && prevEdbInfo == nil && nextEdbInfo == nil && parentClassifyId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			maxSort, err = GetRadishResearchClassifyMaxSort(parentClassifyId)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			edbClassifyInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+			edbClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		} else {
+			// 拖动到父级分类的第一位
+			firstClassify, tmpErr := data_manage.GetFirstRadishResearchClassifyByParentId(parentClassifyId)
+			if tmpErr != nil && !utils.IsErrNoRow(tmpErr) {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+			if firstClassify != nil && firstClassify.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = data_manage.UpdateRadishResearchClassifySortByParentId(parentClassifyId, firstClassify.BaseFromRadishResearchClassifyId-1, 0, updateSortStr)
+				//该分类下的所有指标也需要+1
+				_ = data_manage.UpdateRadishResearchIndexSortByClassifyId(parentClassifyId, 0, 0, updateSortStr)
+			} else {
+				//如果该分类下存在指标,且第一个指标的排序等于0,那么需要调整排序
+				firstEdb, tErr := data_manage.GetFirstRadishResearchIndexByClassifyId(parentClassifyId)
+				if tErr != nil && !utils.IsErrNoRow(tErr) {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+				if firstEdb != nil && firstEdb.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = data_manage.UpdateRadishResearchIndexSortByClassifyId(parentClassifyId, 0, firstEdb.BaseFromRadishResearchIndexId-1, updateSortStr)
+					_ = data_manage.UpdateRadishResearchClassifySortByParentId(parentClassifyId, 0, 0, updateSortStr)
+				}
+			}
+
+			edbClassifyInfo.Sort = 0 //那就是排在第一位
+			edbClassifyInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, "Sort", "ModifyTime")
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = edbClassifyInfo.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+			//更新对应分类的root_id和层级
+			if oldParentId != parentClassifyId {
+				if len(classifyIds) > 0 {
+					levelStep := edbClassifyInfo.Level - oldLevel
+					err = data_manage.UpdateRadishResearchClassifyChildByParentClassifyId(classifyIds, edbClassifyInfo.RootId, levelStep)
+					if err != nil {
+						errMsg = "移动失败"
+						err = errors.New("更新子分类失败,Err:" + err.Error())
+						return
+					}
+				}
+			}
+		}
+	} else {
+		if edbInfo == nil {
+			errMsg = "当前指标不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		//如果改变了分类,那么移动该指标数据
+		if edbInfo.ClassifyId != parentClassifyId {
+			edbInfo.ClassifyId = parentClassifyId
+			edbInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, indexOb.Cols().ClassifyId, indexOb.Cols().ModifyTime)
+		}
+		if prevSort > 0 {
+			//如果是移动在两个兄弟节点之间
+			if nextSort > 0 {
+				//下一个兄弟节点
+				//如果上一个兄弟与下一个兄弟的排序权重是一致的,那么需要将下一个兄弟(以及下个兄弟的同样排序权重)的排序权重+2,自己变成上一个兄弟的排序权重+1
+				if prevSort == nextSort || prevSort == edbInfo.Sort {
+					//变更兄弟节点的排序
+					updateSortStr := `sort + 2`
+
+					//变更分类
+					if prevClassify != nil {
+						_ = data_manage.UpdateRadishResearchClassifySortByParentId(parentClassifyId, prevClassify.BaseFromRadishResearchClassifyId, prevClassify.Sort, updateSortStr)
+					} else {
+						_ = data_manage.UpdateRadishResearchClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr)
+					}
+
+					//变更指标
+					if prevEdbInfo != nil {
+						//变更兄弟节点的排序
+						_ = data_manage.UpdateRadishResearchIndexSortByClassifyId(parentClassifyId, prevSort, prevEdbInfo.BaseFromRadishResearchIndexId, updateSortStr)
+					} else {
+						_ = data_manage.UpdateRadishResearchIndexSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr)
+					}
+				} else {
+					//如果下一个兄弟的排序权重正好是上个兄弟节点的下一层,那么需要再加一层了
+					if nextSort-prevSort == 1 {
+						//变更兄弟节点的排序
+						updateSortStr := `sort + 1`
+						//变更分类
+						if prevClassify != nil {
+							_ = data_manage.UpdateRadishResearchClassifySortByParentId(parentClassifyId, prevClassify.BaseFromRadishResearchClassifyId, prevSort, updateSortStr)
+						} else {
+							_ = data_manage.UpdateRadishResearchClassifySortByParentId(parentClassifyId, 0, prevSort, updateSortStr)
+						}
+
+						//变更指标
+						if prevEdbInfo != nil {
+							//变更兄弟节点的排序
+							_ = data_manage.UpdateRadishResearchIndexSortByClassifyId(parentClassifyId, prevSort, prevEdbInfo.BaseFromRadishResearchIndexId, updateSortStr)
+						} else {
+							_ = data_manage.UpdateRadishResearchIndexSortByClassifyId(parentClassifyId, prevSort, 0, updateSortStr)
+						}
+					}
+				}
+			}
+
+			edbInfo.Sort = prevSort + 1
+			edbInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, indexOb.Cols().Sort, indexOb.Cols().ModifyTime)
+		} else if prevClassify == nil && nextClassify == nil && prevEdbInfo == nil && nextEdbInfo == nil && parentClassifyId > 0 {
+			//处理只拖动到目录里,默认放到目录底部的情况
+			var maxSort int
+			classifyOb := new(data_manage.BaseFromRadishResearchClassify)
+			maxSort, err = classifyOb.GetSortMax(parentClassifyId)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("查询组内排序信息失败,Err:" + err.Error())
+				return
+			}
+			edbInfo.Sort = maxSort + 1 //那就是排在组内最后一位
+			edbInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, indexOb.Cols().Sort, indexOb.Cols().ModifyTime)
+		} else {
+			// 拖动到父级分类的第一位
+			firstClassify, tmpErr := data_manage.GetFirstRadishResearchClassifyByParentId(parentClassifyId)
+			if tmpErr != nil && !utils.IsErrNoRow(tmpErr) {
+				errMsg = "移动失败"
+				err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tmpErr.Error())
+				return
+			}
+
+			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+			if firstClassify != nil && firstClassify.Sort == 0 {
+				updateSortStr := ` sort + 1 `
+				_ = data_manage.UpdateRadishResearchClassifySortByParentId(parentClassifyId, firstClassify.BaseFromRadishResearchClassifyId-1, 0, updateSortStr)
+				//该分类下的所有指标也需要+1
+				_ = data_manage.UpdateRadishResearchIndexSortByClassifyId(parentClassifyId, 0, 0, updateSortStr)
+			} else {
+				//如果该分类下存在指标,且第一个指标的排序等于0,那么需要调整排序
+				firstEdb, tErr := data_manage.GetFirstRadishResearchIndexByClassifyId(parentClassifyId)
+				if tErr != nil && !utils.IsErrNoRow(tErr) {
+					errMsg = "移动失败"
+					err = errors.New("获取获取当前父级分类下的排序第一条的分类信息失败,Err:" + tErr.Error())
+					return
+				}
+
+				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
+				if firstEdb != nil && firstEdb.Sort == 0 {
+					updateSortStr := ` sort + 1 `
+					_ = data_manage.UpdateRadishResearchIndexSortByClassifyId(parentClassifyId, 0, firstEdb.BaseFromRadishResearchIndexId-1, updateSortStr)
+					_ = data_manage.UpdateRadishResearchClassifySortByParentId(parentClassifyId, 0, 0, updateSortStr)
+				}
+			}
+
+			edbInfo.Sort = 0 //那就是排在第一位
+			edbInfo.ModifyTime = time.Now()
+			updateCol = append(updateCol, indexOb.Cols().Sort, indexOb.Cols().ModifyTime)
+		}
+
+		//更新
+		if len(updateCol) > 0 {
+			err = edbInfo.Update(updateCol)
+			if err != nil {
+				errMsg = "移动失败"
+				err = errors.New("修改失败,Err:" + err.Error())
+				return
+			}
+
+			// 更新ES
+			go func() {
+				indexItem := new(dataSourceModel.SearchDataSource)
+				indexItem.PrimaryId = edbInfo.BaseFromRadishResearchIndexId
+				indexItem.IndexCode = edbInfo.IndexCode
+				indexItem.IndexName = edbInfo.IndexName
+				indexItem.ClassifyId = edbInfo.ClassifyId
+				indexItem.Unit = edbInfo.Unit
+				indexItem.Frequency = edbInfo.Frequency
+				indexItem.StartDate = edbInfo.StartDate.Format(utils.FormatDate)
+				indexItem.EndDate = edbInfo.EndDate.Format(utils.FormatDate)
+				indexItem.LatestValue = fmt.Sprint(edbInfo.LatestValue)
+				indexItem.Source = utils.DATA_SOURCE_RADISH_RESEARCH
+				indexItem.SourceName = utils.DATA_SOURCE_NAME_RADISH_RESEARCH
+				indexItem.IsDeleted = 0
+				indexItem.CreateTime = edbInfo.CreateTime.Format(utils.FormatDateTime)
+				indexItem.ModifyTime = edbInfo.ModifyTime.Format(utils.FormatDateTime)
+
+				docId := fmt.Sprintf("%d-%d", utils.DATA_SOURCE_RADISH_RESEARCH, edbInfo.BaseFromRadishResearchIndexId)
+				if e := elastic.EsAddOrEditDataSourceIndex(utils.EsDataSourceIndexName, docId, indexItem); e != nil {
+					utils.FileLog.Warning("RadishResearch-写入指标ES失败, %v", e)
+					return
+				}
+			}()
+		}
+	}
+
+	// 更新子分类的LevelPath
+	if moveParent {
+		UpdateRadishResearchClassifyLevelPathRecursive(moveParentId)
+	}
+	return
+}
+
+func GetRadishResearchChildClassifyByClassifyId(targetClassifyId int) (targetList []*data_manage.BaseFromRadishResearchClassify, err error, errMsg string) {
+	//判断是否是挂在顶级目录下
+	targetClassify, err := data_manage.GetRadishResearchClassifyById(targetClassifyId)
+	if err != nil {
+		if utils.IsErrNoRow(err) {
+			errMsg = "当前分类不存在"
+			err = errors.New(errMsg)
+			return
+		}
+		errMsg = "获取失败"
+		err = errors.New("获取分类信息失败,Err:" + err.Error())
+		return
+	}
+	orderStr := ` order by level asc, sort asc, base_from_radish_research_classify_id asc`
+	tmpList, err := data_manage.GetRadishResearchClassifyByRootIdLevel(targetClassify.RootId, orderStr)
+	if err != nil && !utils.IsErrNoRow(err) {
+		errMsg = "获取失败"
+		err = errors.New("获取数据失败,Err:" + err.Error())
+		return
+	}
+	idMap := make(map[int]struct{})
+	if len(tmpList) > 0 {
+		for _, v := range tmpList {
+			if v.BaseFromRadishResearchClassifyId == targetClassify.BaseFromRadishResearchClassifyId {
+				idMap[v.BaseFromRadishResearchClassifyId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.ParentId]; ok {
+				idMap[v.BaseFromRadishResearchClassifyId] = struct{}{}
+			}
+		}
+		for _, v := range tmpList {
+			if _, ok := idMap[v.BaseFromRadishResearchClassifyId]; ok {
+				targetItem := new(data_manage.BaseFromRadishResearchClassify)
+				targetItem.BaseFromRadishResearchClassifyId = v.BaseFromRadishResearchClassifyId
+				targetItem.ParentId = v.ParentId
+				targetItem.RootId = v.RootId
+				//targetItem.UniqueCode = v.UniqueCode
+				targetItem.Level = v.Level
+				targetItem.ClassifyName = v.ClassifyName
+				//targetItem.IsJoinPermission = v.IsJoinPermission
+				targetList = append(targetList, targetItem)
+			}
+		}
+	}
+
+	return
+}
+
+func GetRadishResearchClassifyMaxSort(parentId int) (maxSort int, err error) {
+	//获取该层级下最大的排序数
+	classifyOb := new(data_manage.BaseFromRadishResearchClassify)
+	classifyMaxSort, err := classifyOb.GetSortMax(parentId)
+	if err != nil {
+		return
+	}
+	maxSort = classifyMaxSort
+	edbMaxSort, err := data_manage.GetRadishResearchIndexMaxSortByClassifyId(parentId)
+	if err != nil {
+		return
+	}
+	if maxSort < edbMaxSort {
+		maxSort = edbMaxSort
+	}
+	return
+}
+
+// UpdateRadishResearchClassifyLevelPathRecursive 分类父级ID变更后, 递归更新分类LevelPath
+func UpdateRadishResearchClassifyLevelPathRecursive(parentId int) {
+	fmt.Println("ParentId", parentId)
+	classifyOb := new(data_manage.BaseFromRadishResearchClassify)
+	cond := fmt.Sprintf(` AND %s = ?`, classifyOb.Cols().ParentId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, parentId)
+	classifies, e := classifyOb.GetItemsByCondition(cond, pars, []string{}, "")
+	if e != nil {
+		utils.FileLog.Warning("UpdateRadishResearchClassifyLevelPathRecursive, errMsg: %v", e)
+		return
+	}
+	if len(classifies) == 0 {
+		return
+	}
+
+	// 批量更新子分类的LevelPath
+	parentClassify, e := classifyOb.GetItemById(parentId)
+	if e != nil {
+		if utils.IsErrNoRow(e) {
+			return
+		}
+		utils.FileLog.Warning("UpdateRadishResearchClassifyLevelPathRecursive, parent errMsg: %v", e)
+		return
+	}
+	if parentClassify != nil && parentClassify.BaseFromRadishResearchClassifyId <= 0 {
+		return
+	}
+
+	// 继续递归处理
+	for _, v := range classifies {
+		e = classifyOb.UpdateLevelPath(v.BaseFromRadishResearchClassifyId, fmt.Sprintf("%s,%d", parentClassify.LevelPath, v.BaseFromRadishResearchClassifyId))
+		if e != nil {
+			utils.FileLog.Warning("UpdateRadishResearchClassifyLevelPathRecursive, update errMsg: %v", e)
+			continue
+		}
+		UpdateRadishResearchClassifyLevelPathRecursive(v.BaseFromRadishResearchClassifyId)
+	}
+	return
+}

+ 172 - 117
services/data/chart_info.go

@@ -620,7 +620,16 @@ func GetEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, seasonExtraConfig string) (edbDataListMap map[int][]*data_manage.EdbDataList, edbList []*data_manage.ChartEdbInfoMapping, err error) {
 	// 指标对应的所有数据
 	edbDataListMap = make(map[int][]*data_manage.EdbDataList)
-
+	// 设置季节性图的左右轴
+	seasonXStartDateWithYear := ""
+	seasonXEndDateWithYear := ""
+	// 如果是季节性图则进入特殊排序
+	if chartType == 2 {
+		// 根据设置的左右轴,对mappingList进行排序,1左轴排在前面,0右轴排在前面
+		sort.Slice(mappingList, func(i, j int) bool {
+			return mappingList[i].IsAxis > mappingList[j].IsAxis
+		})
+	}
 	for _, v := range mappingList {
 		//fmt.Println("v:", v.EdbInfoId)
 		item := new(data_manage.ChartEdbInfoMapping)
@@ -818,77 +827,87 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 						err = errors.New("获取农历数据失败,Err:" + tmpErr.Error())
 						return
 					}
-					quarterDataList, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
+					quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
 					if tErr != nil {
 						err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 						return
 					}
 					item.DataList = quarterDataList
+					seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+					seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 				}
 
 			} else {
-				quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
+				quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
 				if tErr != nil {
 					err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 					return
 				}
 				item.DataList = quarterDataList
+				seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+				seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 			}
 
 		} else if chartType == 2 && item.IsAxis == 0 {
 			// 右轴数据处理
-			xStartDate := "01-01"
-
-			jumpYear := 0
-			var seasonExtra data_manage.SeasonExtraItem
-			if seasonExtraConfig != "" {
-				err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
-				if err != nil {
-					return
-				}
-			}
-
-			if seasonExtra.XStartDate != "" {
-				xStartDate = seasonExtra.XStartDate
-				jumpYear = seasonExtra.JumpYear
-			}
-
-			length := len(dataList)
-			if length == 0 {
-				return
-			}
-			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
-			if tmpErr != nil {
-				//item.DataList = dataList
-				item.IsNullData = true
-				edbList = append(edbList, item)
-				continue
-				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
-				return
-			}
-
-			var rightAxisDate time.Time
-			if jumpYear == 1 {
-				latestDate = latestDate.AddDate(-1, 0, 0)
-			}
-			latestDateStr := fmt.Sprintf("%d-%s", latestDate.Year(), xStartDate)
-			rightAxisDate, err = time.Parse(utils.FormatDate, latestDateStr)
-			if err != nil {
-				return
-			}
-			nowYear := time.Now().Year()
+			// xStartDate := "01-01"
+
+			// jumpYear := 0
+			// var seasonExtra data_manage.SeasonExtraItem
+			// if seasonExtraConfig != "" {
+			// 	err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
+			// 	if err != nil {
+			// 		return
+			// 	}
+			// }
+
+			// if seasonExtra.XStartDate != "" {
+			// 	xStartDate = seasonExtra.XStartDate
+			// 	jumpYear = seasonExtra.JumpYear
+			// }
+
+			// length := len(dataList)
+			// if length == 0 {
+			// 	return
+			// }
+			// latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			// if tmpErr != nil {
+			// 	//item.DataList = dataList
+			// 	item.IsNullData = true
+			// 	edbList = append(edbList, item)
+			// 	continue
+			// 	err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
+			// 	return
+			// }
+
+			// var rightAxisDate time.Time
+			// if jumpYear == 1 {
+			// 	latestDate = latestDate.AddDate(-1, 0, 0)
+			// }
+			// latestDateStr := fmt.Sprintf("%d-%s", latestDate.Year(), xStartDate)
+			// rightAxisDate, err = time.Parse(utils.FormatDate, latestDateStr)
+			// if err != nil {
+			// 	return
+			// }
+			// nowYear := time.Now().Year()
 			newDataList := make([]*data_manage.EdbDataList, 0)
+			seasonXStartDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXStartDateWithYear)
+			seasonXEndDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXEndDateWithYear)
 			for _, v := range dataList {
 				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
 				if e != nil {
 					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
 					return
 				}
-				dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
-				year := dataTimeT.Year()
-				newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
-				v.DataTimestamp = newItemDate.UnixNano() / 1e6
-				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
+				//dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
+				// year := dataTimeT.Year()
+				// newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
+				// v.DataTimestamp = newItemDate.UnixNano() / 1e6
+				// if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
+				// 	newDataList = append(newDataList, v)
+				// }
+				// 如果数据时间在横轴的开始日期和结束日期之间,则加入到数据列表中
+				if (dataTime.After(seasonXStartDateWithYearT) && dataTime.Before(seasonXEndDateWithYearT)) || dataTime.Equal(seasonXStartDateWithYearT) || dataTime.Equal(seasonXEndDateWithYearT) {
 					newDataList = append(newDataList, v)
 				}
 			}
@@ -905,7 +924,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 }
 
 // GetSeasonEdbInfoDataListByXDate 季节性图的指标数据根据横轴展示
-func GetSeasonEdbInfoDataListByXDate(dataList []*data_manage.EdbDataList, latestDate time.Time, seasonExtraConfig string) (quarterDataListSort data_manage.QuarterDataList, err error) {
+func GetSeasonEdbInfoDataListByXDate(dataList []*data_manage.EdbDataList, latestDate time.Time, seasonExtraConfig string) (quarterDataListSort data_manage.QuarterDataList, xStartDateWithYear string, xEndDateWithYear string, err error) {
 	xStartDate := "01-01"
 	xEndDate := "12-31"
 	jumpYear := 0
@@ -955,7 +974,7 @@ func GetSeasonEdbInfoDataListByXDate(dataList []*data_manage.EdbDataList, latest
 		return
 	}
 	endYear := lastDateT.Year()
-	nowYear := time.Now().Year()
+	nowYear := endYear
 	chartLegendMaxYear := 0
 	dataMap := make(map[string]data_manage.QuarterXDateItem, 0)
 
@@ -1007,6 +1026,8 @@ func GetSeasonEdbInfoDataListByXDate(dataList []*data_manage.EdbDataList, latest
 		chartLegendMap[name] = idx
 		idx++
 		fmt.Println("年份" + showName + "日期" + startStr + " " + endStr)
+		xStartDateWithYear = startStr
+		xEndDateWithYear = endStr
 		if lastDateT.Before(endT) {
 			//如果最新的日期在起始日之前,则跳出循环
 			break
@@ -1102,7 +1123,7 @@ func GetSeasonEdbInfoDataListByXDate(dataList []*data_manage.EdbDataList, latest
 }
 
 // GetSeasonEdbInfoDataListByXDateNong 季节性图的指标数据根据横轴选择农历时展示
-func GetSeasonEdbInfoDataListByXDateNong(result *data_manage.EdbDataResult, latestDate time.Time, seasonExtraConfig string, calendarPreYear int) (quarterDataListSort data_manage.QuarterDataList, err error) {
+func GetSeasonEdbInfoDataListByXDateNong(result *data_manage.EdbDataResult, latestDate time.Time, seasonExtraConfig string, calendarPreYear int) (quarterDataListSort data_manage.QuarterDataList, xStartDateWithYear string, xEndDateWithYear string, err error) {
 	xStartDate := "01-01"
 	xEndDate := "12-31"
 	jumpYear := 0
@@ -1154,7 +1175,7 @@ func GetSeasonEdbInfoDataListByXDateNong(result *data_manage.EdbDataResult, late
 		return
 	}
 	endYear := lastDateT.Year()
-	nowYear := time.Now().Year()
+	nowYear := endYear
 	chartLegendMaxYear := 0
 	dataMap := make(map[string]data_manage.QuarterXDateItem, 0)
 
@@ -1207,6 +1228,8 @@ func GetSeasonEdbInfoDataListByXDateNong(result *data_manage.EdbDataResult, late
 		endTmpT = endT
 		chartLegendMap[showName] = idx
 		idx++
+		xStartDateWithYear = startStr
+		xEndDateWithYear = endStr
 		if lastDateT.Before(endT) {
 			//如果最新的日期在起始日之前,则跳出循环
 			break
@@ -1273,10 +1296,10 @@ func GetSeasonEdbInfoDataListByXDateNong(result *data_manage.EdbDataResult, late
 				Value:         item.Value,
 			}
 			dataTimeT, _ := time.Parse(utils.FormatDate, item.DataTime)
-			year := dataTimeT.Year()
-			newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
-			timestamp := newItemDate.UnixNano() / 1e6
-			tmpVal.DataTimestamp = timestamp
+			// year := dataTimeT.Year()
+			// newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
+			// timestamp := newItemDate.UnixNano() / 1e6
+			// tmpVal.DataTimestamp = timestamp
 			if (startTmpT.Before(dataTimeT) && endTmpT.After(dataTimeT)) || startTmpT == dataTimeT || endTmpT == dataTimeT {
 				tmpV := &tmpVal
 				if findVal, ok := quarterMap[name]; !ok {
@@ -3315,7 +3338,15 @@ func GetChartConvertEdbData(chartInfoId, chartType int, calendar, startDate, end
 func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, seasonExtraConfig string, isAxis int) (edbDataListMap map[int][]*data_manage.EdbDataList, edbList []*data_manage.ChartEdbInfoMapping, err error) {
 	// 指标对应的所有数据
 	edbDataListMap = make(map[int][]*data_manage.EdbDataList)
-
+	seasonXStartDateWithYear := ""
+	seasonXEndDateWithYear := ""
+	// 如果是季节性图则进入特殊排序
+	if chartType == 2 {
+		// 根据设置的左右轴,对mappingList进行排序,1左轴排在前面,0右轴排在前面
+		sort.Slice(mappingList, func(i, j int) bool {
+			return mappingList[i].IsAxis > mappingList[j].IsAxis
+		})
+	}
 	for _, v := range mappingList {
 		//fmt.Println("v:", v.EdbInfoId)
 		item := new(data_manage.ChartEdbInfoMapping)
@@ -3506,34 +3537,39 @@ func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, e
 						err = errors.New("获取农历数据失败,Err:" + tmpErr.Error())
 						return
 					}
-					quarterDataList, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
+					quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
 					if tErr != nil {
 						err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 						return
 					}
 					item.DataList = quarterDataList
+					seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+					seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 				}
 
 			} else {
-				quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
+				quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
 				if tErr != nil {
 					err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 					return
 				}
 				item.DataList = quarterDataList
+				seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+				seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 			}
 
 		} else if chartType == 2 && isAxis == 0 {
 			// 右轴数据处理,只要最新一年
-			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
-			if tmpErr != nil {
-				//item.DataList = dataList
-				item.IsNullData = true
-				edbList = append(edbList, item)
-				continue
-				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
-				return
-			}
+			// latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			// if tmpErr != nil {
+			// 	//item.DataList = dataList
+			// 	item.IsNullData = true
+			// 	edbList = append(edbList, item)
+			// 	continue
+			// 	err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
+			// 	return
+			// }
+
 			newDataList := make([]*data_manage.EdbDataList, 0)
 			for _, v := range dataList {
 				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
@@ -3541,7 +3577,9 @@ func getEdbConvertDataMapList(chartInfoId, chartType int, calendar, startDate, e
 					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
 					return
 				}
-				if dataTime.Year() == latestDate.Year() {
+				seasonXStartDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXStartDateWithYear)
+				seasonXEndDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXEndDateWithYear)
+				if (dataTime.After(seasonXStartDateWithYearT) && dataTime.Before(seasonXEndDateWithYearT)) || dataTime.Equal(seasonXStartDateWithYearT) || dataTime.Equal(seasonXEndDateWithYearT) {
 					newDataList = append(newDataList, v)
 				}
 			}
@@ -5010,6 +5048,13 @@ func CalculatePercentile(x float64, min float64, max float64) float64 {
 func getEdbDataMapListForSeason(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, seasonExtraConfig string) (edbDataListMap map[int][]*data_manage.EdbDataList, edbList []*data_manage.ChartEdbInfoMapping, err error) {
 	// 指标对应的所有数据
 	edbDataListMap = make(map[int][]*data_manage.EdbDataList)
+	seasonXStartDateWithYear := ""
+	seasonXEndDateWithYear := ""
+	if chartType == 2 {
+		sort.Slice(mappingList, func(i, j int) bool {
+			return mappingList[i].IsAxis > mappingList[j].IsAxis
+		})
+	}
 
 	for _, v := range mappingList {
 		//fmt.Println("v:", v.EdbInfoId)
@@ -5213,66 +5258,70 @@ func getEdbDataMapListForSeason(chartInfoId, chartType int, calendar, startDate,
 						err = errors.New("获取农历数据失败,Err:" + tmpErr.Error())
 						return
 					}
-					quarterDataList, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
+					quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
 					if tErr != nil {
 						err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 						return
 					}
+					seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+					seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 					item.DataList = quarterDataList
 				}
 
 			} else {
-				quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(newDataList, latestDate, seasonExtraConfig)
+				quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDate(newDataList, latestDate, seasonExtraConfig)
 				if tErr != nil {
 					err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 					return
 				}
+				seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+				seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 				item.DataList = quarterDataList
 			}
 
 		} else if chartType == 2 && item.IsAxis == 0 {
 			// 右轴数据处理
-			xStartDate := "01-01"
-
-			jumpYear := 0
-			var seasonExtra data_manage.SeasonExtraItem
-			if seasonExtraConfig != "" {
-				err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
-				if err != nil {
-					return
-				}
-			}
-
-			if seasonExtra.XStartDate != "" {
-				xStartDate = seasonExtra.XStartDate
-				jumpYear = seasonExtra.JumpYear
-			}
-
-			length := len(newDataList)
-			if length == 0 {
-				return
-			}
-			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
-			if tmpErr != nil {
-				//item.DataList = newDataList
-				item.IsNullData = true
-				edbList = append(edbList, item)
-				continue
-				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
-				return
-			}
-
-			var rightAxisDate time.Time
-			if jumpYear == 1 {
-				latestDate = latestDate.AddDate(-1, 0, 0)
-			}
-			latestDateStr := fmt.Sprintf("%d-%s", latestDate.Year(), xStartDate)
-			rightAxisDate, err = time.Parse(utils.FormatDate, latestDateStr)
-			if err != nil {
-				return
-			}
-
-			nowYear := time.Now().Year()
+			// xStartDate := "01-01"
+
+			// jumpYear := 0
+			// var seasonExtra data_manage.SeasonExtraItem
+			// if seasonExtraConfig != "" {
+			// 	err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
+			// 	if err != nil {
+			// 		return
+			// 	}
+			// }
+
+			// if seasonExtra.XStartDate != "" {
+			// 	xStartDate = seasonExtra.XStartDate
+			// 	jumpYear = seasonExtra.JumpYear
+			// }
+
+			// length := len(newDataList)
+			// if length == 0 {
+			// 	return
+			// }
+			// latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			// if tmpErr != nil {
+			// 	//item.DataList = newDataList
+			// 	item.IsNullData = true
+			// 	edbList = append(edbList, item)
+			// 	continue
+			// 	err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
+			// 	return
+			// }
+
+			// var rightAxisDate time.Time
+			// if jumpYear == 1 {
+			// 	latestDate = latestDate.AddDate(-1, 0, 0)
+			// }
+			// latestDateStr := fmt.Sprintf("%d-%s", latestDate.Year(), xStartDate)
+			// rightAxisDate, err = time.Parse(utils.FormatDate, latestDateStr)
+			// if err != nil {
+			// 	return
+			// }
+
+			// nowYear := time.Now().Year()
 			newList := make([]*data_manage.EdbDataList, 0)
 			for _, v := range dataList {
 				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
@@ -5280,11 +5329,17 @@ func getEdbDataMapListForSeason(chartInfoId, chartType int, calendar, startDate,
 					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
 					return
 				}
-				dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
-				year := dataTimeT.Year()
-				newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
-				v.DataTimestamp = newItemDate.UnixNano() / 1e6
-				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
+				// dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
+				// year := dataTimeT.Year()
+				// newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
+				// v.DataTimestamp = newItemDate.UnixNano() / 1e6
+				// if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
+				// 	newList = append(newList, v)
+				// }
+				seasonXStartDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXStartDateWithYear)
+				seasonXEndDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXEndDateWithYear)
+
+				if (dataTime.After(seasonXStartDateWithYearT) && dataTime.Before(seasonXEndDateWithYearT)) || dataTime.Equal(seasonXStartDateWithYearT) || dataTime.Equal(seasonXEndDateWithYearT) {
 					newList = append(newList, v)
 				}
 			}

+ 63 - 51
services/data/chart_info_excel_balance.go

@@ -771,7 +771,13 @@ func GetBalanceExcelChartDetail(chartInfo *data_manage.ChartInfoView, mappingLis
 func GetBalanceExcelEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*data_manage.ChartEdbInfoMapping, seasonExtraConfig string, dataListMap map[int][]*data_manage.EdbDataList) (edbDataListMap map[int][]*data_manage.EdbDataList, edbList []*data_manage.ChartEdbInfoMapping, err error) {
 	// 指标对应的所有数据
 	edbDataListMap = make(map[int][]*data_manage.EdbDataList)
-
+	seasonXStartDateWithYear := ""
+	seasonXEndDateWithYear := ""
+	if chartType == 2 {
+		sort.Slice(mappingList, func(i, j int) bool {
+			return mappingList[i].IsAxis > mappingList[j].IsAxis
+		})
+	}
 	for _, v := range mappingList {
 		//fmt.Println("v:", v.EdbInfoId)
 		item := new(data_manage.ChartEdbInfoMapping)
@@ -962,66 +968,70 @@ func GetBalanceExcelEdbDataMapList(chartInfoId, chartType int, calendar, startDa
 						err = errors.New("获取农历数据失败,Err:" + tmpErr.Error())
 						return
 					}
-					quarterDataList, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
+					quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
 					if tErr != nil {
 						err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 						return
 					}
+					seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+					seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 					item.DataList = quarterDataList
 				}
 
 			} else {
-				quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
+				quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
 				if tErr != nil {
 					err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 					return
 				}
+				seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+				seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 				item.DataList = quarterDataList
 			}
 
 		} else if chartType == 2 && item.IsAxis == 0 {
 			// 右轴数据处理
-			xStartDate := "01-01"
-
-			jumpYear := 0
-			var seasonExtra data_manage.SeasonExtraItem
-			if seasonExtraConfig != "" {
-				err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
-				if err != nil {
-					return
-				}
-			}
-
-			if seasonExtra.XStartDate != "" {
-				xStartDate = seasonExtra.XStartDate
-				jumpYear = seasonExtra.JumpYear
-			}
-
-			length := len(dataList)
-			if length == 0 {
-				return
-			}
-			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
-			if tmpErr != nil {
-				//item.DataList = dataList
-				item.IsNullData = true
-				edbList = append(edbList, item)
-				continue
-				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
-				return
-			}
-
-			var rightAxisDate time.Time
-			if jumpYear == 1 {
-				latestDate = latestDate.AddDate(-1, 0, 0)
-				latestDateStr := fmt.Sprintf("%d-%s", latestDate.Year(), xStartDate)
-				rightAxisDate, err = time.Parse(utils.FormatDate, latestDateStr)
-				if err != nil {
-					return
-				}
-			}
-
-			nowYear := time.Now().Year()
+			// xStartDate := "01-01"
+
+			// jumpYear := 0
+			// var seasonExtra data_manage.SeasonExtraItem
+			// if seasonExtraConfig != "" {
+			// 	err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
+			// 	if err != nil {
+			// 		return
+			// 	}
+			// }
+
+			// if seasonExtra.XStartDate != "" {
+			// 	xStartDate = seasonExtra.XStartDate
+			// 	jumpYear = seasonExtra.JumpYear
+			// }
+
+			// length := len(dataList)
+			// if length == 0 {
+			// 	return
+			// }
+			// latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			// if tmpErr != nil {
+			// 	//item.DataList = dataList
+			// 	item.IsNullData = true
+			// 	edbList = append(edbList, item)
+			// 	continue
+			// 	err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
+			// 	return
+			// }
+
+			// var rightAxisDate time.Time
+			// if jumpYear == 1 {
+			// 	latestDate = latestDate.AddDate(-1, 0, 0)
+			// 	latestDateStr := fmt.Sprintf("%d-%s", latestDate.Year(), xStartDate)
+			// 	rightAxisDate, err = time.Parse(utils.FormatDate, latestDateStr)
+			// 	if err != nil {
+			// 		return
+			// 	}
+			// }
+
+			// nowYear := time.Now().Year()
 			newDataList := make([]*data_manage.EdbDataList, 0)
 			for _, v := range dataList {
 				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
@@ -1029,11 +1039,13 @@ func GetBalanceExcelEdbDataMapList(chartInfoId, chartType int, calendar, startDa
 					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
 					return
 				}
-				dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
-				year := dataTimeT.Year()
-				newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
-				v.DataTimestamp = newItemDate.UnixNano() / 1e6
-				if dataTime.Equal(rightAxisDate) || dataTime.After(rightAxisDate) {
+				// dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
+				// year := dataTimeT.Year()
+				// newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
+				// v.DataTimestamp = newItemDate.UnixNano() / 1e6
+				seasonXStartDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXStartDateWithYear)
+				seasonXEndDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXEndDateWithYear)
+				if dataTime.Equal(seasonXStartDateWithYearT) || (dataTime.After(seasonXStartDateWithYearT) && dataTime.Before(seasonXEndDateWithYearT)) || dataTime.Equal(seasonXEndDateWithYearT) {
 					newDataList = append(newDataList, v)
 				}
 			}
@@ -1231,7 +1243,7 @@ func GetBalanceExcelSeasonChartLegendPreview(dataList []*data_manage.EdbDataList
 				err = errors.New("获取农历数据失败,Err:" + tmpErr.Error())
 				return
 			}
-			quarterDataList, err = GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
+			quarterDataList, _, _, err = GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
 			if err != nil {
 				err = errors.New("获取季节性图表数据失败,Err:" + err.Error())
 				return
@@ -1239,7 +1251,7 @@ func GetBalanceExcelSeasonChartLegendPreview(dataList []*data_manage.EdbDataList
 		}
 
 	} else {
-		quarterDataList, err = GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
+		quarterDataList, _, _, err = GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
 		if err != nil {
 			err = errors.New("获取季节性图表数据失败,Err:" + err.Error())
 			return

+ 1 - 1
services/data/chart_theme.go

@@ -300,7 +300,7 @@ func getThemePreviewEdbDataMapList(chartType int, calendar, startDate, endDate s
 				continue
 			}
 
-			quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
+			quarterDataList, _, _, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
 			if tErr != nil {
 				err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 				return

+ 23 - 0
services/data/edb_info.go

@@ -3045,6 +3045,9 @@ func handleByDelEdbInfo(edbInfo *data_manage.EdbInfo) {
 		// 删除图表中的指标引用
 		_ = data_manage.DeleteEdbRelationByObjectId(edbInfo.EdbInfoId, utils.EDB_RELATION_PREDICT_EDB)
 	}
+
+	// 如果数据源表中有EdbExist字段,删除指标后字段要置为0
+	_ = ResetBaseFromIndexEdbExist(edbInfo.Source, edbInfo.EdbCode)
 }
 
 func EditBaseEdbInfo(req data_manage.EditEdbInfoReq, sysUser *system.Admin, lang, requestBody, uri string) (isSendEmail bool, err error, errMsg string) {
@@ -3268,3 +3271,23 @@ func GetMySteelSourceByEdbCode(edbCode string) (source int, item *data_manage.Ed
 
 	return
 }
+
+// ResetBaseFromIndexEdbExist 重置数据源指标表EdbExist
+func ResetBaseFromIndexEdbExist(source int, indexCode string) (err error) {
+	defer func() {
+		if err != nil {
+			tips := fmt.Sprintf("ResetBaseFromIndexEdbExist, Source: %d, IndexCode: %s, ErrMsg: %v", source, indexCode, err)
+			utils.FileLog.Warning(tips)
+			go alarm_msg.SendAlarmMsg(tips, 2)
+		}
+	}()
+	switch source {
+	case utils.DATA_SOURCE_RADISH_RESEARCH:
+		indexOb := new(data_manage.BaseFromRadishResearchIndex)
+		if e := indexOb.UpdateEdbExists(0, []string{indexCode}); e != nil {
+			err = fmt.Errorf("更新EdbExist失败, %v", e)
+			return
+		}
+	}
+	return
+}

+ 1 - 1
services/data/edb_info_refresh.go

@@ -72,7 +72,7 @@ func SaveEdbRefreshDefaultConfig(source, subSource int, frequency string, list [
 	}
 
 	// 非有色的来源,频度不能为空
-	if source != utils.DATA_SOURCE_YS && frequency == `` {
+	if source != utils.DATA_SOURCE_YS && source != utils.DATA_SOURCE_RADISH_RESEARCH && frequency == `` {
 		errMsg = "频度不能为空"
 		err = errors.New(errMsg)
 		isSendEmail = false

+ 3 - 3
services/data/mysteel_chemical.go

@@ -206,7 +206,7 @@ func MoveMysteelChemicalClassify(classifyId, parentClassifyId, prevClassifyId, n
 		}
 
 		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
-		if firstClassify != nil && firstClassify.Sort == 0 {
+		if firstClassify != nil && firstClassify.BaseFromMysteelChemicalClassifyId > 0 && firstClassify.Sort == 0 {
 			updateSortStr := ` sort + 1 `
 			_ = data_manage.UpdateBaseFromMysteelChemicalClassifySortByClassifyId(parentClassifyId, firstClassify.BaseFromMysteelChemicalClassifyId-1, 0, updateSortStr)
 		}
@@ -440,7 +440,7 @@ func AddMysteelChemicalIndex(classifyId int, indexCode, updateWeek, updateTimeSt
 		errMsg = `获取数据失败`
 		return
 	}
-	if baseFromMysteelChemicalIndex != nil {
+	if baseFromMysteelChemicalIndex != nil && baseFromMysteelChemicalIndex.BaseFromMysteelChemicalIndexId > 0 {
 		if lang == utils.EnLangVersion {
 			errMsg = `Metric ID:` + indexCode + ` already exists, please re-enter.`
 		} else {
@@ -675,7 +675,7 @@ func MoveMysteelChemical(indexId, classifyId, prevIndexId, nextIndexId int, sysU
 		}
 
 		//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
-		if firstClassify != nil && firstClassify.Sort == 0 {
+		if firstClassify != nil && firstClassify.BaseFromMysteelChemicalClassifyId > 0 && firstClassify.Sort == 0 {
 			updateSortStr := ` sort + 1 `
 			_ = data_manage.UpdateBaseFromMysteelChemicalIndexByClassifyId(firstClassify.BaseFromMysteelChemicalClassifyId, 0, firstClassify.BaseFromMysteelChemicalIndexId-1, updateSortStr)
 		}

+ 5 - 2
services/data/stl/stl.go

@@ -1006,6 +1006,9 @@ func SaveStlEdbInfo(req *request.SaveStlEdbInfoReq, adminId int, adminRealName,
 		}
 		pars = append(pars, req.EdbName)
 
+		condition += " AND edb_info_id !=? "
+		pars = append(pars, req.EdbInfoId)
+
 		existEdbInfo, er := data_manage.GetEdbInfoByCondition(condition, pars)
 		if er != nil && !utils.IsErrNoRow(er) {
 			msg = "获取失败"
@@ -1013,13 +1016,13 @@ func SaveStlEdbInfo(req *request.SaveStlEdbInfoReq, adminId int, adminRealName,
 		}
 		switch lang {
 		case utils.EnLangVersion:
-			if existEdbInfo != nil && existEdbInfo.EdbNameEn == req.EdbName && req.EdbInfoId != existEdbInfo.EdbInfoId {
+			if existEdbInfo != nil && existEdbInfo.EdbInfoId > 0 && existEdbInfo.EdbNameEn == req.EdbName && req.EdbInfoId != existEdbInfo.EdbInfoId {
 				msg = "指标名称已存在"
 				err = fmt.Errorf("指标名称已存在")
 				return
 			}
 		default:
-			if existEdbInfo != nil && existEdbInfo.EdbName == req.EdbName && req.EdbInfoId != existEdbInfo.EdbInfoId {
+			if existEdbInfo != nil && existEdbInfo.EdbInfoId > 0 && existEdbInfo.EdbName == req.EdbName && req.EdbInfoId != existEdbInfo.EdbInfoId {
 				msg = "指标名称已存在"
 				err = fmt.Errorf("指标名称已存在")
 				return

+ 302 - 0
services/elastic/wechat_article.go

@@ -0,0 +1,302 @@
+package elastic
+
+import (
+	"context"
+	"encoding/json"
+	"eta/eta_api/models/rag"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/olivere/elastic/v7"
+	"time"
+)
+
+// WechatArticleAndPlatform
+// @Description: 存入ES的数据
+type WechatArticleAndPlatform struct {
+	WechatArticleId  int    `gorm:"column:wechat_article_id;type:int(10) UNSIGNED;primaryKey;not null;" description:""`
+	WechatPlatformId int    `gorm:"column:wechat_platform_id;type:int(11);comment:归属公众号id;default:0;" description:"归属公众号id"`
+	FakeId           string `gorm:"column:fake_id;type:varchar(255);comment:公众号唯一id;" description:"公众号唯一id"`
+	Title            string `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
+	Link             string `gorm:"column:link;type:varchar(255);comment:链接;" description:"链接"`
+	CoverUrl         string `gorm:"column:cover_url;type:varchar(255);comment:公众号封面;" description:"公众号封面"`
+	Description      string `gorm:"column:description;type:varchar(255);comment:描述;" description:"描述"`
+	Content          string `gorm:"column:content;type:longtext;comment:报告详情;" description:"报告详情"`
+	TextContent      string `gorm:"column:text_content;type:text;comment:文本内容;" description:"文本内容"`
+	//Abstract          string    `gorm:"column:abstract;type:text;comment:摘要;" description:"摘要"`
+	Country           string    `gorm:"column:country;type:varchar(255);comment:国家;" description:"国家"`
+	Province          string    `gorm:"column:province;type:varchar(255);comment:省;" description:"省"`
+	City              string    `gorm:"column:city;type:varchar(255);comment:市;" description:"市"`
+	ArticleCreateTime time.Time `gorm:"column:article_create_time;type:datetime;comment:报告创建时间;default:NULL;" description:"报告创建时间"`
+	IsDeleted         int       `gorm:"column:is_deleted;type:tinyint(4);comment:是否删除,0:未删除,1: 已删除;default:0;" description:"是否删除,0:未删除,1: 已删除"`
+	ModifyTime        time.Time `gorm:"column:modify_time;type:datetime;comment:修改时间;default:NULL;" description:"修改时间"`
+	CreateTime        time.Time `gorm:"column:create_time;type:datetime;comment:入库时间;default:NULL;" description:"入库时间"`
+	Nickname          string    `gorm:"column:nickname;type:varchar(255);comment:公众号名称;" description:"nickname"`          // 公众号名称
+	Alias             string    `gorm:"column:alias;type:varchar(255);comment:别名;" description:"alias"`                   // 别名
+	RoundHeadImg      string    `gorm:"column:round_head_img;type:varchar(255);comment:头像;" description:"round_head_img"` // 头像
+}
+
+func (m *WechatArticleAndPlatform) ToView() rag.WechatArticleView {
+	var articleCreateTime, modifyTime, createTime string
+
+	if !m.ArticleCreateTime.IsZero() {
+		articleCreateTime = m.ArticleCreateTime.Format(utils.FormatDateTime)
+	}
+	if !m.CreateTime.IsZero() {
+		createTime = m.CreateTime.Format(utils.FormatDateTime)
+	}
+	if !m.ModifyTime.IsZero() {
+		modifyTime = m.ModifyTime.Format(utils.FormatDateTime)
+	}
+	return rag.WechatArticleView{
+		WechatArticleId:  m.WechatArticleId,
+		WechatPlatformId: m.WechatPlatformId,
+		FakeId:           m.FakeId,
+		Title:            m.Title,
+		Link:             m.Link,
+		CoverUrl:         m.CoverUrl,
+		Description:      m.Description,
+		Content:          m.Content,
+		TextContent:      m.TextContent,
+		//Abstract:                   m.Abstract,
+		Country:                    m.Country,
+		Province:                   m.Province,
+		City:                       m.City,
+		ArticleCreateTime:          articleCreateTime,
+		ModifyTime:                 modifyTime,
+		CreateTime:                 createTime,
+		WechatPlatformName:         m.Nickname,
+		WechatPlatformRoundHeadImg: m.RoundHeadImg,
+	}
+}
+
+func (m *WechatArticleAndPlatform) ArticleAndPlatformListToViewList(list []*WechatArticleAndPlatform) (wechatArticleViewList []rag.WechatArticleView) {
+	wechatArticleViewList = make([]rag.WechatArticleView, 0)
+
+	for _, v := range list {
+		wechatArticleViewList = append(wechatArticleViewList, v.ToView())
+	}
+	return
+}
+
+// WechatArticleEsAddOrEdit
+// @Description: 新增/编辑微信文章
+// @author: Roc
+// @datetime 2025-03-13 10:24:05
+// @param docId string
+// @param item WechatArticleAndPlatform
+// @return err error
+func WechatArticleEsAddOrEdit(docId string, item WechatArticleAndPlatform) (err error) {
+	if docId == "" {
+		return
+	}
+	if utils.EsWechatArticleName == `` {
+		return
+	}
+	defer func() {
+		if err != nil {
+			fmt.Println("WechatArticleEsAddOrEdit Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+
+	resp, err := client.Index().Index(utils.EsWechatArticleName).Id(docId).BodyJson(item).Refresh("true").Do(context.Background())
+	if err != nil {
+		fmt.Println("新增失败:", err.Error())
+		return err
+	}
+	if resp.Status == 0 {
+		fmt.Println("新增成功", resp.Result)
+		err = nil
+	} else {
+		fmt.Println("WechatArticleEsAddOrEdit", resp.Status, resp.Result)
+	}
+
+	return
+}
+
+// WechatArticleEsDel
+// @Description: 删除微信文章
+// @author: Roc
+// @datetime 2025-03-13 10:23:55
+// @param docId string
+// @return err error
+func WechatArticleEsDel(docId string) (err error) {
+	if docId == "" {
+		return
+	}
+	if utils.EsWechatArticleName == `` {
+		return
+	}
+	defer func() {
+		if err != nil {
+			fmt.Println("EsDeleteEdbInfoData Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+
+	resp, err := client.Delete().Index(utils.EsWechatArticleName).Id(docId).Refresh(`true`).Do(context.Background())
+	if err != nil {
+		return
+	}
+	if resp.Status == 0 {
+		fmt.Println("删除成功")
+	} else {
+		fmt.Println("WechatArticleEsDel", resp.Status, resp.Result)
+	}
+
+	return
+}
+
+func WechatArticleEsSearch(keywordStr string, wechatPlatformId, from, size int, sortMap map[string]string) (total int64, list []*WechatArticleAndPlatform, err error) {
+	indexName := utils.EsWechatArticleName
+	list = make([]*WechatArticleAndPlatform, 0)
+	defer func() {
+		if err != nil {
+			fmt.Println("SearchEdbInfoData Err:", err.Error())
+		}
+	}()
+
+	query := elastic.NewBoolQuery()
+
+	if wechatPlatformId > 0 {
+		query = query.Must(elastic.NewTermQuery("WechatPlatformId", wechatPlatformId))
+	}
+
+	// 名字匹配
+	if keywordStr != `` {
+		query = query.Must(elastic.NewMultiMatchQuery(keywordStr, "Title"))
+	}
+
+	// 排序
+	sortList := make([]*elastic.FieldSort, 0)
+	// 如果没有关键字,那么就走指标id倒序
+
+	for orderKey, orderType := range sortMap {
+		switch orderType {
+		case "asc":
+			sortList = append(sortList, elastic.NewFieldSort(orderKey).Asc())
+		case "desc":
+			sortList = append(sortList, elastic.NewFieldSort(orderKey).Desc())
+
+		}
+
+	}
+
+	return searchWechatArticle(indexName, query, sortList, from, size)
+}
+
+// searchEdbInfoDataV2 查询es中的数据
+func searchWechatArticle(indexName string, query elastic.Query, sortList []*elastic.FieldSort, from, size int) (total int64, list []*WechatArticleAndPlatform, err error) {
+	total, err = searchWechatArticleTotal(indexName, query)
+	if err != nil {
+		return
+	}
+
+	// 获取列表数据
+	list, err = searchWechatArticleList(indexName, query, sortList, from, size)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// searchEdbInfoDataTotal
+// @Description: 查询es中的数量
+// @author: Roc
+// @datetime 2024-12-23 11:19:04
+// @param indexName string
+// @param query elastic.Query
+// @return total int64
+// @return err error
+func searchWechatArticleTotal(indexName string, query elastic.Query) (total int64, err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("searchEdbInfoDataTotal Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+
+	//根据条件数量统计
+	requestTotalHits := client.Count(indexName).Query(query)
+	total, err = requestTotalHits.Do(context.Background())
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// searchEdbInfoDataList
+// @Description: 查询es中的明细数据
+// @author: Roc
+// @datetime 2024-12-23 11:18:48
+// @param indexName string
+// @param query elastic.Query
+// @param sortList []*elastic.FieldSort
+// @param from int
+// @param size int
+// @return list []*data_manage.EdbInfoList
+// @return err error
+func searchWechatArticleList(indexName string, query elastic.Query, sortList []*elastic.FieldSort, from, size int) (list []*WechatArticleAndPlatform, err error) {
+	list = make([]*WechatArticleAndPlatform, 0)
+	defer func() {
+		if err != nil {
+			fmt.Println("searchEdbInfoDataList Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+	// 高亮
+	highlight := elastic.NewHighlight()
+	highlight = highlight.Fields(elastic.NewHighlighterField("Title"))
+	highlight = highlight.PreTags("<font color='red'>").PostTags("</font>")
+
+	//request := client.Search(indexName).Highlight(highlight).From(from).Size(size) // sets the JSON request
+	request := client.Search(indexName).From(from).Size(size) // sets the JSON request
+
+	// 如果有指定排序,那么就按照排序来
+	if len(sortList) > 0 {
+		for _, v := range sortList {
+			request = request.SortBy(v)
+		}
+	}
+
+	searchMap := make(map[string]string)
+
+	searchResp, err := request.Query(query).Do(context.Background())
+	if err != nil {
+		return
+	}
+	//fmt.Println(searchResp)
+	//fmt.Println(searchResp.Status)
+	if searchResp.Status != 0 {
+		return
+	}
+	//total = searchResp.TotalHits()
+	if searchResp.Hits != nil {
+		for _, v := range searchResp.Hits.Hits {
+			if _, ok := searchMap[v.Id]; !ok {
+				itemJson, tmpErr := v.Source.MarshalJSON()
+				if tmpErr != nil {
+					err = tmpErr
+					fmt.Println("movieJson err:", err)
+					return
+				}
+				item := new(WechatArticleAndPlatform)
+				tmpErr = json.Unmarshal(itemJson, &item)
+				if tmpErr != nil {
+					fmt.Println("json.Unmarshal movieJson err:", tmpErr)
+					err = tmpErr
+					return
+				}
+				if len(v.Highlight["Title"]) > 0 {
+					item.Title = v.Highlight["Title"][0]
+				}
+				list = append(list, item)
+				searchMap[v.Id] = v.Id
+			}
+		}
+	}
+
+	return
+}

+ 317 - 0
services/elastic/wechat_article_abstract.go

@@ -0,0 +1,317 @@
+package elastic
+
+import (
+	"context"
+	"encoding/json"
+	"eta/eta_api/models/rag"
+	"eta/eta_api/utils"
+	"fmt"
+	"github.com/olivere/elastic/v7"
+	"time"
+)
+
+// 摘要索引
+var EsWechatArticleAbstractName = utils.EsWechatArticleAbstractName
+
+type WechatArticleAbstractItem struct {
+	WechatArticleAbstractId int       `gorm:"column:wechat_article_abstract_id;type:int(9) UNSIGNED;primaryKey;not null;" description:"wechat_article_abstract_id"`
+	WechatArticleId         int       `gorm:"column:wechat_article_id;type:int(9) UNSIGNED;comment:关联的微信报告id;default:0;" description:"关联的微信报告id"`
+	WechatPlatformId        int       `gorm:"column:wechat_platform_id;type:int(9) UNSIGNED;comment:微信公众号id;default:0;" description:"微信公众号id"`
+	Abstract                string    `gorm:"column:abstract;type:longtext;comment:摘要内容;" description:"摘要内容"` //
+	Version                 int       `gorm:"column:version;type:int(10) UNSIGNED;comment:版本号;default:1;" description:"版本号"`
+	VectorKey               string    `gorm:"column:vector_key;type:varchar(255);comment:向量key标识;" description:"向量key标识"`
+	ModifyTime              time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
+	CreateTime              time.Time `gorm:"column:create_time;type:datetime;default:NULL;" description:"create_time"`
+	Title                   string    `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
+	Link                    string    `gorm:"column:link;type:varchar(255);comment:链接;" description:"链接"`
+	TagIdList               []int     `description:"品种id列表"`
+}
+
+func (m *WechatArticleAbstractItem) ToView() rag.WechatArticleAbstractView {
+	var modifyTime, createTime string
+
+	if !m.CreateTime.IsZero() {
+		createTime = m.CreateTime.Format(utils.FormatDateTime)
+	}
+	if !m.ModifyTime.IsZero() {
+		modifyTime = m.ModifyTime.Format(utils.FormatDateTime)
+	}
+
+	tagId := 0
+	if len(m.TagIdList) > 0 {
+		tagId = m.TagIdList[0]
+	}
+	return rag.WechatArticleAbstractView{
+		WechatArticleAbstractId: m.WechatArticleAbstractId,
+		WechatArticleId:         m.WechatArticleId,
+		WechatPlatformId:        m.WechatPlatformId,
+		Abstract:                m.Abstract,
+		Version:                 m.Version,
+		VectorKey:               m.VectorKey,
+		ModifyTime:              modifyTime,
+		CreateTime:              createTime,
+		Title:                   m.Title,
+		Link:                    m.Link,
+		TagId:                   tagId,
+	}
+}
+
+func (m *WechatArticleAbstractItem) ToViewList(list []*WechatArticleAbstractItem) (wechatArticleViewList []rag.WechatArticleAbstractView) {
+	wechatArticleViewList = make([]rag.WechatArticleAbstractView, 0)
+
+	for _, v := range list {
+		wechatArticleViewList = append(wechatArticleViewList, v.ToView())
+	}
+	return
+}
+
+// WechatArticleEsAddOrEdit
+// @Description: 新增/编辑微信文章
+// @author: Roc
+// @datetime 2025-03-13 10:24:05
+// @param docId string
+// @param item WechatArticleAndPlatform
+// @return err error
+func WechatArticleAbstractEsAddOrEdit(docId string, item WechatArticleAbstractItem) (err error) {
+	if docId == "" {
+		return
+	}
+	if EsWechatArticleAbstractName == `` {
+		return
+	}
+	defer func() {
+		if err != nil {
+			fmt.Println("WechatArticleEsAddOrEdit Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+
+	resp, err := client.Index().Index(EsWechatArticleAbstractName).Id(docId).BodyJson(item).Refresh("true").Do(context.Background())
+	if err != nil {
+		fmt.Println("新增失败:", err.Error())
+		return err
+	}
+	if resp.Status == 0 {
+		fmt.Println("新增成功", resp.Result)
+		err = nil
+	} else {
+		fmt.Println("WechatArticleEsAddOrEdit", resp.Status, resp.Result)
+	}
+
+	return
+}
+
+// WechatArticleEsDel
+// @Description: 删除微信文章
+// @author: Roc
+// @datetime 2025-03-13 10:23:55
+// @param docId string
+// @return err error
+func WechatArticleAbstractEsDel(docId string) (err error) {
+	if docId == "" {
+		return
+	}
+	if EsWechatArticleAbstractName == `` {
+		return
+	}
+	defer func() {
+		if err != nil {
+			fmt.Println("EsDeleteEdbInfoData Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+
+	resp, err := client.Delete().Index(EsWechatArticleAbstractName).Id(docId).Refresh(`true`).Do(context.Background())
+	if err != nil {
+		return
+	}
+	if resp.Status == 0 {
+		fmt.Println("删除成功")
+	} else {
+		fmt.Println("WechatArticleEsDel", resp.Status, resp.Result)
+	}
+
+	return
+}
+
+// WechatArticleAbstractEsSearch
+// @Description: 搜索
+// @author: Roc
+// @datetime 2025-03-13 19:54:54
+// @param keywordStr string
+// @param tagIdList []int
+// @param platformIdList []int
+// @param from int
+// @param size int
+// @param sortMap map[string]string
+// @return total int64
+// @return list []*WechatArticleAbstractItem
+// @return err error
+func WechatArticleAbstractEsSearch(keywordStr string, tagIdList, platformIdList []int, from, size int, sortMap map[string]string) (total int64, list []*WechatArticleAbstractItem, err error) {
+	indexName := EsWechatArticleAbstractName
+	list = make([]*WechatArticleAbstractItem, 0)
+	defer func() {
+		if err != nil {
+			fmt.Println("SearchEdbInfoData Err:", err.Error())
+		}
+	}()
+
+	query := elastic.NewBoolQuery()
+
+	if len(tagIdList) > 0 {
+		termsList := make([]interface{}, 0)
+		for _, v := range tagIdList {
+			termsList = append(termsList, v)
+		}
+		query = query.Must(elastic.NewTermsQuery("TagIdList", termsList...))
+	}
+	if len(platformIdList) <= 0 {
+		return
+	}
+
+	{
+		termsList := make([]interface{}, 0)
+		for _, v := range platformIdList {
+			termsList = append(termsList, v)
+		}
+		query = query.Must(elastic.NewTermsQuery("WechatPlatformId", termsList...))
+	}
+
+	// 名字匹配
+	if keywordStr != `` {
+		query = query.Must(elastic.NewMultiMatchQuery(keywordStr, "Abstract"))
+	}
+
+	// 排序
+	sortList := make([]*elastic.FieldSort, 0)
+	// 如果没有关键字,那么就走指标id倒序
+
+	for orderKey, orderType := range sortMap {
+		switch orderType {
+		case "asc":
+			sortList = append(sortList, elastic.NewFieldSort(orderKey).Asc())
+		case "desc":
+			sortList = append(sortList, elastic.NewFieldSort(orderKey).Desc())
+
+		}
+
+	}
+
+	return searchWechatArticleAbstract(indexName, query, sortList, from, size)
+}
+
+// searchEdbInfoDataV2 查询es中的数据
+func searchWechatArticleAbstract(indexName string, query elastic.Query, sortList []*elastic.FieldSort, from, size int) (total int64, list []*WechatArticleAbstractItem, err error) {
+	total, err = searchWechatArticleAbstractTotal(indexName, query)
+	if err != nil {
+		return
+	}
+
+	// 获取列表数据
+	list, err = searchWechatArticleAbstractList(indexName, query, sortList, from, size)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// searchEdbInfoDataTotal
+// @Description: 查询es中的数量
+// @author: Roc
+// @datetime 2024-12-23 11:19:04
+// @param indexName string
+// @param query elastic.Query
+// @return total int64
+// @return err error
+func searchWechatArticleAbstractTotal(indexName string, query elastic.Query) (total int64, err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("searchEdbInfoDataTotal Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+
+	//根据条件数量统计
+	requestTotalHits := client.Count(indexName).Query(query)
+	total, err = requestTotalHits.Do(context.Background())
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// searchEdbInfoDataList
+// @Description: 查询es中的明细数据
+// @author: Roc
+// @datetime 2024-12-23 11:18:48
+// @param indexName string
+// @param query elastic.Query
+// @param sortList []*elastic.FieldSort
+// @param from int
+// @param size int
+// @return list []*data_manage.EdbInfoList
+// @return err error
+func searchWechatArticleAbstractList(indexName string, query elastic.Query, sortList []*elastic.FieldSort, from, size int) (list []*WechatArticleAbstractItem, err error) {
+	list = make([]*WechatArticleAbstractItem, 0)
+	defer func() {
+		if err != nil {
+			fmt.Println("searchEdbInfoDataList Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+	// 高亮
+	highlight := elastic.NewHighlight()
+	highlight = highlight.Fields(elastic.NewHighlighterField("Content"))
+	highlight = highlight.PreTags("<font color='red'>").PostTags("</font>")
+
+	//request := client.Search(indexName).Highlight(highlight).From(from).Size(size) // sets the JSON request
+	request := client.Search(indexName).From(from).Size(size) // sets the JSON request
+
+	// 如果有指定排序,那么就按照排序来
+	if len(sortList) > 0 {
+		for _, v := range sortList {
+			request = request.SortBy(v)
+		}
+	}
+
+	searchMap := make(map[string]string)
+
+	searchResp, err := request.Query(query).Do(context.Background())
+	if err != nil {
+		return
+	}
+	//fmt.Println(searchResp)
+	//fmt.Println(searchResp.Status)
+	if searchResp.Status != 0 {
+		return
+	}
+	//total = searchResp.TotalHits()
+	if searchResp.Hits != nil {
+		for _, v := range searchResp.Hits.Hits {
+			if _, ok := searchMap[v.Id]; !ok {
+				itemJson, tmpErr := v.Source.MarshalJSON()
+				if tmpErr != nil {
+					err = tmpErr
+					fmt.Println("movieJson err:", err)
+					return
+				}
+				item := new(WechatArticleAbstractItem)
+				tmpErr = json.Unmarshal(itemJson, &item)
+				if tmpErr != nil {
+					fmt.Println("json.Unmarshal movieJson err:", tmpErr)
+					err = tmpErr
+					return
+				}
+				if len(v.Highlight["Content"]) > 0 {
+					item.Abstract = v.Highlight["Content"][0]
+				}
+				list = append(list, item)
+				searchMap[v.Id] = v.Id
+			}
+		}
+	}
+
+	return
+}

+ 42 - 0
services/english_report.go

@@ -988,3 +988,45 @@ func UpdateEnglishCompanyEnabledByCompanyId(companyId int) (err error) {
 	}
 	return
 }
+
+// GetEnglishReportToken
+// @Description: 获取token
+// @author: Roc
+// @datetime 2025-03-18 10:35:11
+// @param reportId int
+// @param reportCode string
+// @return token string
+// @return err error
+func GetEnglishReportToken(reportId int, reportCode string) (token string, err error) {
+	// 图表授权token
+	token = utils.MD5(fmt.Sprint(reportCode, time.Now().UnixNano()/1e6))
+	err = generalReportAuthToken(token, `en:`, reportId)
+
+	return
+}
+
+func GetGeneralEnglishReportPdfUrl(reportId int, reportCode string) (pdfUrl string) {
+	// 优先取Report2ImgUrl(用于兼容内外网环境的), 没有的话取报告详情地址
+	var reportUrl string
+	conf, _ := models.GetBusinessConfByKey(models.BusinessConfReport2ImgUrl)
+	if conf != nil && conf.ConfVal != "" {
+		reportUrl = conf.ConfVal
+	}
+	if reportUrl == "" {
+		conf, e := models.GetBusinessConfByKey(models.BusinessConfReportViewUrl)
+		if e != nil {
+			return
+		}
+		reportUrl = conf.ConfVal
+	}
+
+	token := utils.MD5(fmt.Sprint(pdfUrl, time.Now().UnixNano()/1e6))
+	e := generalReportAuthToken(token, `en:`, reportId)
+	if e == nil {
+		pdfUrl = fmt.Sprintf("%s&authToken=%s", pdfUrl, token)
+	}
+
+	pdfUrl = fmt.Sprintf("%s/reportshare_pdf_en?code=%s&authToken=%s", reportUrl, reportCode, token)
+
+	return
+}

+ 212 - 0
services/llm/base_wechat_lib.go

@@ -0,0 +1,212 @@
+package llm
+
+import (
+	"encoding/json"
+	"eta/eta_api/utils"
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+)
+
+type BaseResp struct {
+	Code int    `json:"code"`
+	Msg  string `json:"msg"`
+}
+
+type WechatPlatformListResp struct {
+	BaseResp
+	Data []WechatPlatformResp `json:"data"`
+}
+
+type WechatPlatformResp struct {
+	Fakeid       string `json:"fakeid"`
+	Nickname     string `json:"nickname"`
+	Alias        string `json:"alias"`
+	RoundHeadImg string `json:"round_head_img"`
+	ServiceType  int    `json:"service_type"`
+	Signature    string `json:"signature"`
+	Verified     bool   `json:"verified"`
+}
+
+// SearchByWechat
+// @Description: 公众号列表
+// @author: Roc
+// @datetime 2025-03-04 18:09:01
+// @param name string
+// @return resp WechatPlatformResp
+// @return err error
+func SearchByWechat(name string) (items []WechatPlatformResp, err error) {
+	if utils.ETA_WX_CRAWLER_URL == "" {
+		err = fmt.Errorf("ETA微信爬虫服务地址为空")
+		return
+	}
+	getUrl := utils.ETA_WX_CRAWLER_URL + `/api/wechat_platform/search_by_wechat?name=` + name
+	result, err := HttpGet(getUrl)
+	if err != nil {
+		err = fmt.Errorf("调用ETA微信爬虫服务接口失败 error:%s", err.Error())
+		return
+	}
+
+	var resp WechatPlatformListResp
+	err = json.Unmarshal(result, &resp)
+	if err != nil {
+		return
+	}
+
+	items = resp.Data
+
+	return
+}
+
+type WechatArticleResp struct {
+	BaseResp
+	Data WechatArticleDataResp `json:"data"`
+}
+
+type WechatArticleDataResp struct {
+	HtmlContent      string `json:"HtmlContent"`
+	TextContent      string `json:"TextContent"`
+	RoundHeadImg     string `json:"RoundHeadImg"`
+	ProfileSignature string `json:"ProfileSignature"`
+	Appuin           string `json:"Appuin"`
+	Nickname         string `json:"Nickname"`
+	UserName         string `json:"UserName"`
+	Title            string `json:"Title"`
+	Desc             string `json:"Desc"`
+	CoverUrl         string `json:"CoverUrl"`
+	CreateAt         string `json:"CreateAt"`
+	CountryName      string `json:"CountryName"`
+	ProvinceName     string `json:"ProvinceName"`
+	CityName         string `json:"CityName"`
+}
+
+// SearchByWechatArticle
+// @Description: 获取报告详情
+// @author: Roc
+// @datetime 2025-03-04 18:08:45
+// @param link string
+// @return resp WechatArticleResp
+// @return err error
+func SearchByWechatArticle(link string) (wechatArticle WechatArticleDataResp, err error) {
+	if utils.ETA_WX_CRAWLER_URL == "" {
+		err = fmt.Errorf("ETA微信爬虫服务地址为空")
+		return
+	}
+	getUrl := utils.ETA_WX_CRAWLER_URL + `/api/wechat_platform/article/info/search_by_wechat?link=` + link
+	result, err := HttpGet(getUrl)
+	if err != nil {
+		err = fmt.Errorf("调用ETA微信爬虫服务接口失败 error:%s", err.Error())
+		return
+	}
+
+	var resp WechatArticleResp
+	err = json.Unmarshal(result, &resp)
+	if err != nil {
+		return
+	}
+
+	wechatArticle = resp.Data
+
+	return
+}
+
+type WechatArticleListResp struct {
+	BaseResp
+	Data WechatArticleMenuPage `json:"data"`
+}
+
+type WechatArticleMenuPage struct {
+	List  []ArticleMenu `json:"List"`
+	Total int           `json:"Total"`
+}
+
+type ArticleMenu struct {
+	Aid        string `json:"Aid"`
+	Title      string `json:"Title"`
+	Link       string `json:"Link"`
+	Cover      string `json:"Cover"`
+	Digest     string `json:"Digest"`
+	UpdateTime int    `json:"UpdateTime"`
+	CreateTime int    `json:"CreateTime"`
+	AppMsgId   int64  `json:"AppMsgId"`
+	AuthorName string `json:"AuthorName"`
+	Content    string `json:"Content"`
+}
+
+func SearchByWechatArticleList(fakeId string, num int) (items WechatArticleMenuPage, err error) {
+	if utils.ETA_WX_CRAWLER_URL == "" {
+		err = fmt.Errorf("ETA微信爬虫服务地址为空")
+		return
+	}
+	getUrl := fmt.Sprintf(`%s/api/wechat_platform/article/list/search_by_wechat?fakeid=%s&num=%d`, utils.ETA_WX_CRAWLER_URL, fakeId, num)
+	result, err := HttpGet(getUrl)
+	if err != nil {
+		err = fmt.Errorf("调用ETA微信爬虫服务接口失败 error:%s", err.Error())
+		return
+	}
+
+	var resp WechatArticleListResp
+	err = json.Unmarshal(result, &resp)
+	if err != nil {
+		return
+	}
+
+	items = resp.Data
+
+	return
+}
+
+func HttpGet(url string) ([]byte, error) {
+	client := &http.Client{}
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Set("authorization", utils.MD5(utils.ETA_FORUM_HUB_NAME_EN+utils.ETA_FORUM_HUB_MD5_KEY))
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	result, err := io.ReadAll(resp.Body)
+	if err != nil {
+		return nil, err
+	}
+	utils.FileLog.Debug("HttpPost:" + string(result))
+
+	var baseResp BaseResp
+	err = json.Unmarshal(result, &baseResp)
+	if err != nil {
+		return nil, err
+	}
+	if baseResp.Code != 200 {
+		return nil, fmt.Errorf("code:%d,msg:%s", baseResp.Code, baseResp.Msg)
+	}
+
+	return result, err
+}
+
+func HttpPost(url, postData, lang string, params ...string) ([]byte, error) {
+	body := io.NopCloser(strings.NewReader(postData))
+	client := &http.Client{}
+	req, err := http.NewRequest("POST", url, body)
+	if err != nil {
+		return nil, err
+	}
+	contentType := "application/x-www-form-urlencoded;charset=utf-8"
+	if len(params) > 0 && params[0] != "" {
+		contentType = params[0]
+	}
+	req.Header.Set("Content-Type", contentType)
+	req.Header.Set("Lang", lang)
+	req.Header.Set("authorization", utils.MD5(utils.APP_EDB_LIB_NAME_EN+utils.EDB_LIB_Md5_KEY))
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	b, err := io.ReadAll(resp.Body)
+	utils.FileLog.Debug("HttpPost:" + string(b))
+	return b, err
+}

+ 304 - 0
services/llm/chat.go

@@ -0,0 +1,304 @@
+package llm
+
+import (
+	"bytes"
+	"encoding/json"
+	"eta/eta_api/utils"
+	"eta/eta_api/utils/llm/eta_llm"
+	"eta/eta_api/utils/llm/eta_llm/eta_llm_http"
+	"fmt"
+	"io"
+	"mime/multipart"
+	"net/http"
+	"os"
+	"strings"
+)
+
+var (
+	llmService = eta_llm.GetInstance()
+)
+
+type UploadTempDocsResp struct {
+	Code int                   `json:"code"`
+	Msg  string                `json:"msg"`
+	Data UploadTempDocDataResp `json:"data"`
+}
+
+type UploadTempDocDataResp struct {
+	Id          string        `json:"id"`
+	FailedFiles []interface{} `json:"failed_files"`
+}
+
+// UploadTempDocs
+// @Description: 上传到临时知识库
+// @author: Roc
+// @datetime 2025-03-10 14:42:18
+// @param filePath string
+// @return resp UploadDocsResp
+// @return err error
+func UploadTempDocs(filePath string) (resp UploadTempDocsResp, err error) {
+	postUrl := utils.LLM_SERVER + "/knowledge_base/upload_temp_docs"
+
+	params := make(map[string]string)
+	//params[`prev_id`] = ``
+	params[`chunk_size`] = `750`
+	params[`chunk_overlap`] = `150`
+	params[`zh_title_enhance`] = `true`
+
+	files := make(map[string]string)
+	files[`files`] = filePath
+
+	result, err := PostFormData(postUrl, params, files)
+	if err != nil {
+		return
+	}
+
+	str := string(result)
+	fmt.Println(str)
+
+	err = json.Unmarshal(result, &resp)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+type LlmBaseResp struct {
+	Code int    `json:"code"`
+	Msg  string `json:"msg"`
+}
+
+type UploadDocsResp struct {
+	LlmBaseResp
+	Data UploadDocDataResp `json:"data"`
+}
+
+type UploadDocDataResp struct {
+	Id          string            `json:"id"`
+	FailedFiles map[string]string `json:"failed_files"`
+}
+
+// UploadDocsToKnowledge
+// @Description: 上传文章到知识库
+// @author: Roc
+// @datetime 2025-03-10 14:40:44
+// @param filePath string
+// @param knowledgeName string
+// @return resp UploadTempDocsResp
+// @return err error
+func UploadDocsToKnowledge(filePath, knowledgeName string) (updateResp UploadDocDataResp, err error) {
+	postUrl := utils.LLM_SERVER + "/knowledge_base/upload_docs"
+
+	params := make(map[string]string)
+	params[`knowledge_base_name`] = knowledgeName
+	params[`override`] = `true`              // 覆盖已有文件
+	params[`to_vector_store`] = `true`       // 上传文件后是否进行向量化
+	params[`chunk_size`] = `750`             // 知识库中单段文本最大长度
+	params[`chunk_overlap`] = `150`          // 知识库中相邻文本重合长度
+	params[`zh_title_enhance`] = `true`      // 是否开启中文标题加强
+	params[`docs`] = ``                      // 自定义的docs,需要转为json字符串
+	params[`not_refresh_vs_cache`] = `false` // 暂不保存向量库(用于FAISS)
+
+	files := make(map[string]string)
+	files[`files`] = filePath
+
+	result, err := PostFormData(postUrl, params, files)
+	if err != nil {
+		return
+	}
+
+	str := string(result)
+	fmt.Println(str)
+
+	var resp UploadDocsResp
+	err = json.Unmarshal(result, &resp)
+	if err != nil {
+		return
+	}
+
+	if resp.Code != 200 {
+		err = fmt.Errorf(`上传文件失败: %s`, resp.Msg)
+		return
+	}
+	updateResp = resp.Data
+
+	return
+}
+
+// DelDocsToKnowledge
+// @Description: 从知识库中删除文件
+// @author: Roc
+// @datetime 2025-03-12 15:03:19
+// @param knowledgeName string
+// @param filePathList []string
+// @return resp LlmBaseResp
+// @return err error
+func DelDocsToKnowledge(knowledgeName string, filePathList []string) (resp LlmBaseResp, err error) {
+	postUrl := utils.LLM_SERVER + "/knowledge_base/delete_docs"
+
+	params := make(map[string]interface{})
+	params[`knowledge_base_name`] = knowledgeName
+	params[`file_names`] = filePathList
+	params[`delete_content`] = `true`        //
+	params[`not_refresh_vs_cache`] = `false` //
+	postData, err := json.Marshal(params)
+	if err != nil {
+		return
+	}
+
+	result, err := LlmHttpPost(postUrl, string(postData))
+	if err != nil {
+		return
+	}
+	utils.FileLog.Info("DelDocsToKnowledge:" + postUrl + ";" + string(postData) + ";result:" + string(result))
+	err = json.Unmarshal(result, &resp)
+	if err != nil {
+		return
+	}
+
+	if resp.Code != 200 {
+		err = fmt.Errorf(`上传文件失败: %s`, resp.Msg)
+		return
+	}
+
+	return
+}
+
+// ChatResp 问答响应
+type ChatResp struct {
+	Answer string   `json:"answer"`
+	Docs   []string `json:"docs"`
+}
+
+type HistoryContent struct {
+	Content string `json:"content"`
+	Role    string `json:"role"`
+}
+
+func ChatByFile(knowledgeId, question string, historyList []eta_llm_http.HistoryContent) (answerStr string, answer ChatResp, err error) {
+	// 没有问题那就直接返回
+	if question == `` {
+		return
+	}
+
+	history := make([]json.RawMessage, 0)
+	for _, v := range historyList {
+		tmpHistory, tmpErr := json.Marshal(v)
+		if tmpErr != nil {
+			return
+		}
+		history = append(history, json.RawMessage(string(tmpHistory)))
+	}
+
+	resp, err := llmService.DocumentChat(question, knowledgeId, history, false)
+	if err != nil {
+		return
+	}
+	defer func() {
+		if resp != nil && resp.Body != nil {
+			_ = resp.Body.Close()
+		}
+	}()
+	if resp == nil {
+		err = fmt.Errorf(`知识库问答失败: 无应答`)
+		return
+	}
+	result, err := io.ReadAll(resp.Body)
+	if err != nil {
+		err = fmt.Errorf(`知识库问答数据解析失败: %s`, err.Error())
+		return
+	}
+
+	answerStr = string(result)
+	// 找到"data:"关键字的位置
+	dataIndex := bytes.Index([]byte(answerStr), []byte("data: "))
+	if dataIndex == -1 {
+		err = fmt.Errorf(`未找到"data:"关键字`)
+		return
+	}
+
+	// 提取"data:"关键字之后的部分
+	answerStr = answerStr[dataIndex+len("data: "):]
+
+	// 解析JSON数据
+	err = json.Unmarshal([]byte(answerStr), &answer)
+	if err != nil {
+		err = fmt.Errorf(`解析JSON数据失败: %s`, err.Error())
+		return
+	}
+
+	return
+}
+
+// PostFormData sends a POST request with form-data
+func PostFormData(url string, params map[string]string, files map[string]string) ([]byte, error) {
+
+	body := &bytes.Buffer{}
+	writer := multipart.NewWriter(body)
+
+	for key, val := range params {
+		if err := writer.WriteField(key, val); err != nil {
+			return nil, err
+		}
+	}
+
+	for fieldName, filePath := range files {
+		file, err := os.Open(filePath)
+		if err != nil {
+			return nil, err
+		}
+		defer file.Close()
+
+		part, err := writer.CreateFormFile(fieldName, filePath)
+		if err != nil {
+			return nil, err
+		}
+		_, err = io.Copy(part, file)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	err := writer.Close()
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("POST", url, body)
+	if err != nil {
+		return nil, err
+	}
+	//req.Header.Set("accept", `application/json`)
+	req.Header.Set("Content-Type", writer.FormDataContentType())
+
+	client := &http.Client{}
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	result, err := io.ReadAll(resp.Body)
+
+	return result, nil
+}
+
+func LlmHttpPost(url, postData string) ([]byte, error) {
+	body := io.NopCloser(strings.NewReader(postData))
+	client := &http.Client{}
+	req, err := http.NewRequest("POST", url, body)
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Set("Content-Type", "application/json")
+	req.Header.Set("authorization", utils.MD5(utils.APP_EDB_LIB_NAME_EN+utils.EDB_LIB_Md5_KEY))
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+	b, err := io.ReadAll(resp.Body)
+	utils.FileLog.Debug("HttpPost:" + string(b))
+	return b, err
+}

+ 213 - 0
services/llm/chat_service.go

@@ -0,0 +1,213 @@
+package llm
+
+import (
+	"encoding/json"
+	"eta/eta_api/global"
+	"eta/eta_api/models/llm"
+	"eta/eta_api/utils"
+	"eta/eta_api/utils/lock"
+	"eta/eta_api/utils/redis"
+	"fmt"
+	"github.com/google/uuid"
+	"strconv"
+	"strings"
+	"sync"
+	"time"
+)
+
+const (
+	redisChatPrefix = "chat:zet:"
+	redisTTL        = 24 * time.Hour // Redis 缓存过期时间
+)
+
+// AddChatRecord 添加聊天记录到 Redis
+func AddChatRecord(record *llm.UserChatRecordRedis) error {
+	key := fmt.Sprintf("%s%d", redisChatPrefix, record.ChatId)
+	holder, _ := uuid.NewRandom()
+	holderStr := fmt.Sprintf("user_%s", holder.String())
+	if err := lock.TryLock(key, 10, holderStr, 10*time.Second); err == nil {
+		defer func() {
+			lock.ReleaseLock(key, holderStr)
+		}()
+		data, parseErr := json.Marshal(record)
+		if parseErr != nil {
+			return fmt.Errorf("序列化聊天记录失败: %w", parseErr)
+		}
+		zSet, _ := utils.Rc.ZRangeWithScores(key)
+		if len(zSet) == 0 {
+			// 设置过期时间
+			_ = utils.Rc.Expire(key, 24*time.Hour)
+		}
+		zSet = append(zSet, &redis.Zset{
+			Member: data,
+			Score:  float64(time.Now().Unix()),
+		})
+
+		err = utils.Rc.ZAdd(key, zSet...)
+		if err != nil {
+			return fmt.Errorf("保存聊天记录到 Redis 失败: %w", err)
+		}
+		return nil
+	}
+	return fmt.Errorf("获取锁失败,请稍后重试")
+}
+
+// GetChatRecordsFromRedis 从 Redis 获取聊天记录
+func GetChatRecordsFromRedis(chatId int) (redisList []*llm.UserChatRecordRedis, err error) {
+	key := fmt.Sprintf("%s%d", redisChatPrefix, chatId)
+	zSet, _ := utils.Rc.ZRangeWithScores(key)
+	if len(zSet) == 0 {
+		// 缓存不存在,从数据库拉取数据
+		records, dbErr := GetChatRecordsFromDB(chatId)
+		if dbErr != nil {
+			err = fmt.Errorf("从数据库获取聊天记录失败: %w", dbErr)
+			return
+		}
+		// 将数据保存到 Redis
+		for _, record := range records {
+			redisRecord := &llm.UserChatRecordRedis{
+				Id:           record.Id,
+				ChatId:       chatId,
+				ChatUserType: record.ChatUserType,
+				Content:      record.Content,
+				SendTime:     record.SendTime.Format(utils.FormatDateTime),
+			}
+			redisList = append(redisList, redisRecord)
+		}
+		return
+	}
+	for _, z := range zSet {
+		var redisRecord llm.UserChatRecordRedis
+		if err = json.Unmarshal([]byte(z.Member.(string)), &redisRecord); err != nil {
+			return nil, fmt.Errorf("解析聊天记录失败: %w", err)
+		}
+		redisList = append(redisList, &redisRecord)
+	}
+	return
+}
+
+func flushRecordsToRedis(chatId int) (err error) {
+	key := fmt.Sprintf("%s%d", redisChatPrefix, chatId)
+	zSet, _ := utils.Rc.ZRangeWithScores(key)
+	if len(zSet) == 0 {
+		// 缓存不存在,从数据库拉取数据
+		records, dbErr := GetChatRecordsFromDB(chatId)
+		if dbErr != nil {
+			err = fmt.Errorf("从数据库获取聊天记录失败: %w", dbErr)
+			return
+		}
+		var zet []*redis.Zset
+		// 将数据保存到 Redis
+		for _, record := range records {
+			redisRecord := &llm.UserChatRecordRedis{
+				Id:           record.Id,
+				ChatId:       chatId,
+				ChatUserType: record.ChatUserType,
+				Content:      record.Content,
+				SendTime:     record.SendTime.Format(utils.FormatDateTime),
+			}
+			data, parseErr := json.Marshal(&redisRecord)
+			if parseErr != nil {
+				utils.FileLog.Error("解析聊天记录失败: %w", err)
+			}
+			zet = append(zet, &redis.Zset{
+				Member: data,
+				Score:  float64(record.SendTime.Unix()),
+			})
+		}
+		_ = utils.Rc.ZAdd(key, zet...)
+	}
+	return
+}
+
+// SaveChatRecordsToDB 将 Redis 中的聊天记录保存到数据库
+func SaveChatRecordsToDB(chatId int) error {
+	list, err := GetChatRecordsFromRedis(chatId)
+	if err != nil {
+		return err
+	}
+	var newRecords []*llm.UserChatRecord
+	for _, record := range list {
+		if record.Id == 0 {
+			sendTime, parseErr := time.ParseInLocation(utils.FormatDateTime, record.SendTime, time.Local)
+			if parseErr != nil {
+				sendTime = time.Now()
+			}
+			newRecords = append(newRecords, &llm.UserChatRecord{
+				Id:           record.Id,
+				ChatId:       record.ChatId,
+				ChatUserType: record.ChatUserType,
+				Content:      record.Content,
+				SendTime:     sendTime,
+				CreatedTime:  time.Now(),
+			})
+		}
+	}
+	key := fmt.Sprintf("%s%d", redisChatPrefix, chatId)
+	holder, _ := uuid.NewRandom()
+	holderStr := fmt.Sprintf("sys_%s", holder.String())
+	defer func() {
+		lock.ReleaseLock(key, holderStr)
+	}()
+	if lock.AcquireLock(key, 10, holderStr) {
+		//先删除redis中的缓存
+		_ = RemoveChatRecord(chatId)
+		err = llm.BatchInsertRecords(newRecords)
+		if err != nil {
+			utils.FileLog.Error("批量插入记录失败:", err.Error())
+			return fmt.Errorf("批量插入记录失败: %w", err)
+		}
+		_ = RemoveChatRecord(chatId)
+		//重新加载数据
+		_ = flushRecordsToRedis(chatId)
+	}
+	return nil
+}
+
+// SaveAllChatRecordsToDB 定时任务保存所有 Redis 中的聊天记录到数据库
+func SaveAllChatRecordsToDB() {
+	for {
+		keys, err := utils.Rc.Keys(redisChatPrefix + "*")
+		if err != nil {
+			utils.FileLog.Error("获取 Redis 键失败: %v", err)
+			return
+		}
+		var wg sync.WaitGroup
+		wg.Add(len(keys))
+		for _, key := range keys {
+			go func(key string) {
+				defer wg.Done()
+				chatIdStr := strings.TrimPrefix(key, redisChatPrefix)
+				chatId, parseErr := strconv.Atoi(chatIdStr)
+				if parseErr != nil {
+					utils.FileLog.Error("解析聊天ID失败: %v", err)
+					return
+				}
+				if err = SaveChatRecordsToDB(chatId); err != nil {
+					utils.FileLog.Error("解析聊天ID失败: %v", err)
+				}
+			}(key)
+		}
+		wg.Wait()
+		time.Sleep(10 * time.Minute)
+	}
+}
+
+// RemoveChatRecord 从 Redis 删除聊天记录
+func RemoveChatRecord(chatId int) error {
+	key := fmt.Sprintf("%s%d", redisChatPrefix, chatId)
+	err := utils.Rc.Delete(key)
+	if err != nil {
+		return fmt.Errorf("删除 Redis 缓存失败: %w", err)
+	}
+	return nil
+}
+
+func GetChatRecordsFromDB(chatId int) ([]*llm.UserChatRecord, error) {
+	o := global.DbMap[utils.DbNameAI]
+	var records []*llm.UserChatRecord
+	if err := o.Where("chat_id = ?", chatId).Find(&records).Error; err != nil {
+		return nil, fmt.Errorf("从数据库获取聊天记录失败: %w", err)
+	}
+	return records, nil
+}

+ 11 - 0
services/llm/facade/bus_response/bus_response.go

@@ -0,0 +1,11 @@
+package bus_response
+
+import "eta/eta_api/utils/llm/eta_llm/eta_llm_http"
+
+type KnowledgeBaseChatResponse struct {
+	PageContent string                `json:"page_content"`
+	Metadata    eta_llm_http.Metadata `json:"metadata"`
+	Type        string                `json:"type"`
+	Id          string                `json:"id"`
+	Score       float32               `json:"score"`
+}

+ 18 - 0
services/llm/facade/bus_response/eta_response.go

@@ -0,0 +1,18 @@
+package bus_response
+
+import "eta/eta_api/utils/llm/eta_llm/eta_llm_http"
+
+type SearchDocsEtaResponse struct {
+	Content string
+	Docs    []eta_llm_http.SearchDocsResponse
+}
+type AIGCEtaResponse struct {
+	Answer string   `json:"answer"`
+	Docs   []string `json:"docs"`
+}
+
+type FileChatBaseResponse struct {
+	Data AIGCEtaResponse `json:"data"`
+	Msg  string          `json:"msg"`
+	Code int             `json:"code"`
+}

+ 209 - 0
services/llm/facade/llm_service.go

@@ -0,0 +1,209 @@
+package facade
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_api/models/rag"
+	localService "eta/eta_api/services/llm"
+	"eta/eta_api/services/llm/facade/bus_response"
+	"eta/eta_api/utils"
+	"eta/eta_api/utils/llm"
+	"eta/eta_api/utils/llm/eta_llm/eta_llm_http"
+	"eta/eta_api/utils/ws"
+	"fmt"
+	"github.com/gorilla/websocket"
+	"github.com/rdlucklib/rdluck_tools/uuid"
+	"gorm.io/gorm"
+	"os"
+	"strings"
+	"time"
+)
+
+var (
+	llmService, _ = llm.GetInstance(llm.ETA_LLM_CLIENT)
+)
+
+func generateSessionCode() (code string) {
+	return fmt.Sprintf("%s%s", "llm_session_", uuid.NewUUID().Hex32())
+}
+
+// AddSession 创建会话session
+func AddSession(userId int, conn *websocket.Conn) {
+	sessionId := generateSessionCode()
+	session := ws.NewSession(userId, sessionId, conn)
+	ws.Manager().AddSession(session)
+}
+
+// LLMKnowledgeBaseSearchDocs 搜索知识库
+func LLMKnowledgeBaseSearchDocs(search LLMKnowledgeSearch) (resp bus_response.SearchDocsEtaResponse, err error) {
+	docs, err := llmService.SearchKbDocs(search.Query, search.KnowledgeBaseName)
+	if err != nil {
+		return
+	}
+	for _, doc := range docs.([]eta_llm_http.SearchDocsResponse) {
+		resp.Content = resp.Content + doc.PageContent
+	}
+	resp.Docs = docs.([]eta_llm_http.SearchDocsResponse)
+	return
+}
+
+// AIGCBaseOnPromote aigc 生成内容
+func AIGCBaseOnPromote(aigc AIGC) (resp bus_response.AIGCEtaResponse, err error) {
+	mapping, queryErr := rag.GetArticleKbMapping(aigc.ArticleId)
+	if queryErr != nil && !errors.Is(queryErr, gorm.ErrRecordNotFound) {
+		utils.FileLog.Error("获取文章知识库信息失败,err: %v", queryErr)
+		err = fmt.Errorf("获取文章知识库信息失败,err: %v", queryErr)
+		return
+	} else {
+		var kbId string
+		var file *os.File
+		if mapping.Id == 0 || mapping.KbId == "" {
+			article, fileErr := rag.GetArticleById(aigc.ArticleId)
+			if fileErr != nil {
+				// 找不到就处理失败
+				utils.FileLog.Error("公众号文章不存在")
+				err = fmt.Errorf("公众号文章不存在")
+				return
+			}
+			if article.TextContent == "" {
+				utils.FileLog.Error("暂不支持纯文本以外的内容生成")
+				err = fmt.Errorf("暂不支持纯文本以外的内容生成")
+				return
+			}
+			// 文章加入到知识库
+			path, fileErr := localService.CreateArticleFile(article)
+			if fileErr != nil {
+				utils.FileLog.Error("创建文章文件失败,err: %v", fileErr)
+				err = fmt.Errorf("创建文章文件失败,err: %v", fileErr)
+				return
+			}
+			defer func() {
+				_ = os.Remove(path)
+			}()
+			file, err = os.Open(path)
+			if err != nil {
+				utils.FileLog.Error("打开文件失败,err:", err)
+				return
+			}
+			uploadResp, httpErr := llmService.UploadFileToTemplate([]*os.File{file}, nil)
+			if httpErr != nil {
+				utils.FileLog.Error("上传文件失败,err:", err.Error())
+				err = fmt.Errorf("上传文件失败,err:%v", httpErr)
+				return
+			}
+			data := uploadResp.(eta_llm_http.UploadDocsResponse)
+			//保存映射关系到数据库
+			if data.Id == "" {
+				utils.FileLog.Error("上传文件失败,向量库Id获取失败")
+				err = fmt.Errorf("上传文件失败,向量库Id获取失败")
+				return
+			}
+			err = rag.CreateArticleKbMapping(rag.ArticleKbMapping{
+				WechatArticleId: aigc.ArticleId,
+				KbId:            data.Id,
+				CreatedTime:     time.Now(),
+			})
+			if err != nil {
+				utils.FileLog.Warn("创建文章知识库映射关系失败,err:", err.Error())
+			}
+			kbId = data.Id
+		} else {
+			kbId = mapping.KbId
+		}
+		//知识库对话
+		response, httpErr := llmService.FileChat(aigc.Promote, kbId, nil)
+		if httpErr != nil {
+			utils.FileLog.Error("内容生成失败,err:", err.Error())
+			err = fmt.Errorf("内容生成失败,err:%v", httpErr)
+			return
+		}
+
+		gcResp, gcErr := dealFileChatResp(response)
+		if gcErr != nil {
+			utils.FileLog.Error("内容生成失败,err:%v", gcErr.Error())
+			err = fmt.Errorf("内容生成失败,err:%v", gcErr)
+			return
+		}
+		if gcResp.Code == 200 {
+			resp = gcResp.Data
+			return
+		}
+		if gcResp.Code == 404 {
+			response, httpErr = llmService.FileChat(aigc.Promote, kbId, nil)
+			if httpErr != nil {
+				utils.FileLog.Error("内容生成失败,err:%v", httpErr.Error())
+				err = fmt.Errorf("内容生成失败,err:%v", httpErr)
+				return
+			}
+			gcResp, gcErr = dealFileChatResp(response)
+			if gcErr != nil {
+				utils.FileLog.Error("内容生成失败,err:%v", gcErr.Error())
+				err = fmt.Errorf("内容生成失败,err:%v", gcErr)
+				return
+			}
+			if gcResp.Code == 200 {
+				resp = gcResp.Data
+				return
+			}
+			utils.FileLog.Error("内容生成失败,err:%v", gcResp.Code, gcResp.Msg)
+			err = fmt.Errorf("内容生成失败,code:%v,err:%v", gcResp.Code, gcResp.Msg)
+			return
+		} else {
+			utils.FileLog.Error("内容生成失败,code:%v,msg:%v", gcResp.Code, gcResp.Msg)
+			err = fmt.Errorf("内容生成失败,err:%v", gcResp.Msg)
+			return
+		}
+	}
+}
+
+type LLMKnowledgeSearch struct {
+	Query             string `json:"Query"`
+	KnowledgeBaseName string `json:"KnowledgeBaseName"`
+}
+
+type AIGC struct {
+	Promote   string
+	ArticleId int
+}
+
+func dealFileChatResp(response eta_llm_http.BaseResponse) (httpResponse bus_response.FileChatBaseResponse, err error) {
+	if !response.Success {
+		utils.FileLog.Error("内容生成失败,code:%v,msg:%v", response.Ret, response.Msg)
+		err = fmt.Errorf("内容生成失败,code:%v,msg:%v", response.Ret, response.Msg)
+		return
+	} else {
+		var dataStr string
+		// 按行分割输入
+		lines := strings.Split(string(response.Data), "\n")
+		// 遍历每一行,提取以 "data:" 开头的内容
+		for _, line := range lines {
+			if !strings.HasPrefix(line, ": ping") && strings.TrimSpace(line) != "" {
+				// 去掉 "data:" 前缀
+				dataStr += line
+			}
+		}
+		// 去除 "data: " 前缀
+		if strings.HasPrefix(dataStr, "data: ") {
+			dataStr = strings.TrimPrefix(dataStr, "data: ")
+			var streamResponse bus_response.AIGCEtaResponse
+			parseErr := json.Unmarshal([]byte(dataStr), &streamResponse)
+			if parseErr != nil {
+				utils.FileLog.Error("内容生成失败,code:%v,msg:%v", parseErr)
+				err = fmt.Errorf("内容生成失败,err:%v", parseErr)
+				return
+			}
+			httpResponse.Data = streamResponse
+			httpResponse.Msg = "返回成功"
+			httpResponse.Code = 200
+			return
+		} else {
+			parseErr := json.Unmarshal([]byte(dataStr), &httpResponse)
+			if parseErr != nil {
+				utils.FileLog.Error("内容生成失败,code:%v,msg:%v", parseErr)
+				err = fmt.Errorf("内容生成失败,err:%v", parseErr)
+				return
+			}
+			return
+		}
+	}
+}

+ 30 - 0
services/llm/promote_service.go

@@ -0,0 +1,30 @@
+package llm
+
+import (
+	"eta/eta_api/models/rag"
+	"eta/eta_api/utils"
+	"fmt"
+	"os"
+)
+
+func CreateArticleFile(item *rag.WechatArticle) (tmpFilePath string, err error) {
+	if item.TextContent == `` {
+		err = fmt.Errorf("生成文章原文文本失败,文章内容为空")
+		return
+	}
+	// 生成临时文件
+	uploadDir := utils.STATIC_DIR + "ai/article"
+	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
+	if err != nil {
+		err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
+		return
+	}
+	fileName := utils.RemoveSpecialChars(item.Title) + `.md`
+	tmpFilePath = uploadDir + "/" + fileName
+	err = utils.SaveToFile(item.TextContent, tmpFilePath)
+	if err != nil {
+		err = fmt.Errorf("生成临时文件失败,Err:" + err.Error())
+		return
+	}
+	return
+}

+ 185 - 116
services/ppt/ppt_english_group.go

@@ -8,6 +8,7 @@ import (
 	"eta/eta_api/services"
 	"eta/eta_api/utils"
 	"fmt"
+	"github.com/shopspring/decimal"
 	"sort"
 	"strconv"
 	"strings"
@@ -61,11 +62,23 @@ func AddGroupPptEnglishMapping(pptId int64, groupId int64, adminId int, adminRea
 		err = errors.New("目录查询出错:" + err.Error())
 		return
 	}
+
+	// 获取当前目录下最大的排序
+	pptSort, err := ppt_english.GetMaxSortByEnglishGroupId(groupId)
+	if err != nil {
+		err = errors.New("查询最大排序出错:" + err.Error())
+		return
+	}
+	newSort, _ := decimal.NewFromFloat(pptSort).Add(decimal.NewFromInt(1)).Float64()
+
 	bindInfo := &ppt_english.PptEnglishGroupMapping{
 		GroupId:       groupId,
 		PptId:         pptId,
 		AdminId:       adminId,
 		AdminRealName: adminRealName,
+		PptSort:       newSort,
+		CreateTime:    time.Now(),
+		ModifyTime:    time.Now(),
 	}
 
 	newId, err = ppt_english.AddPptGroupMapping(bindInfo)
@@ -74,13 +87,13 @@ func AddGroupPptEnglishMapping(pptId int64, groupId int64, adminId int, adminRea
 		return
 	}
 
-	bindInfo.GroupPptId = newId
-	bindInfo.PptSort = newId
-	err = bindInfo.Update([]string{"ppt_sort"})
-	if err != nil {
-		err = errors.New("更新排序失败:" + err.Error())
-		return
-	}
+	//bindInfo.GroupPptId = newId
+	//bindInfo.PptSort = newId
+	//err = bindInfo.Update([]string{"ppt_sort"})
+	//if err != nil {
+	//	err = errors.New("更新排序失败:" + err.Error())
+	//	return
+	//}
 	return
 }
 
@@ -177,7 +190,7 @@ func CopyEnglishGroup(groupId int64, adminId int, adminRealName string) (err err
 		return
 	}
 	//查询该目录下面是否存在ppt,如果存在则批量复制ppt到新目录下
-	mappingList, err := ppt_english.GetPptMappingListByGroupId(groupId)
+	mappingList, err := ppt_english.GetPptMappingListByGroupIdDesc(groupId)
 	if err != nil {
 		err = errors.New("查询目录里的ppt列表出错:" + err.Error())
 		return
@@ -195,6 +208,11 @@ func CopyEnglishGroup(groupId int64, adminId int, adminRealName string) (err err
 		err = errors.New("查询ppt列表出错:" + err.Error())
 		return
 	}
+	pptMap := make(map[int]*ppt_english.PptEnglish)
+	for _, pptItem := range pptList {
+		pptMap[pptItem.PptId] = pptItem
+	}
+
 	pptNames, err := ppt_english.GetAllPptTitle()
 	if err != nil {
 		err = errors.New("查询ppt标题出错:" + err.Error())
@@ -202,24 +220,32 @@ func CopyEnglishGroup(groupId int64, adminId int, adminRealName string) (err err
 	}
 	//批量复制ppt,更新作者为当前账号,并返回新的pptID
 	newPptList := make([]*ppt_english.PptEnglish, 0)
-	for _, v := range pptList {
+	for _, mapping := range mappingList {
+		pptItem, ok := pptMap[int(mapping.PptId)]
+		// 不存在该ppt,那么就过滤
+		if !ok {
+			continue
+		}
+
 		tmp := &ppt_english.PptEnglish{
-			TemplateType:  v.TemplateType,
-			BackgroundImg: v.BackgroundImg,
-			Title:         generateCopyName(v.Title, 1, pptNames),
-			ReportType:    v.ReportType,
-			PptDate:       v.PptDate,
-			Content:       v.Content,
-			CoverContent:  v.CoverContent,
+			TemplateType:  pptItem.TemplateType,
+			BackgroundImg: pptItem.BackgroundImg,
+			Title:         generateCopyName(pptItem.Title, 1, pptNames),
+			ReportType:    pptItem.ReportType,
+			PptDate:       pptItem.PptDate,
+			Content:       pptItem.Content,
+			CoverContent:  pptItem.CoverContent,
 			CreateTime:    time.Now(),
 			ModifyTime:    time.Now(),
 			AdminId:       adminId,
 			AdminRealName: adminRealName,
-			TitleSetting:  v.TitleSetting,
+			TitleSetting:  pptItem.TitleSetting,
 		}
 		newPptList = append(newPptList, tmp)
 	}
-	if len(newPptList) > 0 {
+
+	pptNum := len(newPptList)
+	if pptNum > 0 {
 		err = ppt_english.AddPptEnglishMulti(newPptList)
 		if err != nil {
 			err = errors.New("复制目录里的ppt出错:" + err.Error())
@@ -227,12 +253,13 @@ func CopyEnglishGroup(groupId int64, adminId int, adminRealName string) (err err
 		}
 
 		newMappings := make([]*ppt_english.PptEnglishGroupMapping, 0)
-		for _, v := range newPptList {
+		for k, v := range newPptList {
 			tmp := &ppt_english.PptEnglishGroupMapping{
 				GroupId:       newGroupId,
 				PptId:         int64(v.PptId),
 				CreateTime:    time.Now(),
 				ModifyTime:    time.Now(),
+				PptSort:       float64(pptNum - k),
 				AdminId:       adminId,
 				AdminRealName: adminRealName,
 			}
@@ -244,15 +271,15 @@ func CopyEnglishGroup(groupId int64, adminId int, adminRealName string) (err err
 			return
 		}
 		//批量更新排序字段
-		var newGroupPptIds []int64
-		for _, v := range newMappings {
-			newGroupPptIds = append(newGroupPptIds, v.GroupPptId)
-		}
-		err = ppt_english.UpdatePptGroupMappingSortMulti(newGroupPptIds)
-		if err != nil {
-			err = errors.New("更新排序标识出错:" + err.Error())
-			return
-		}
+		//var newGroupPptIds []int64
+		//for _, v := range newMappings {
+		//	newGroupPptIds = append(newGroupPptIds, v.GroupPptId)
+		//}
+		//err = ppt_english.UpdatePptGroupMappingSortMulti(newGroupPptIds)
+		//if err != nil {
+		//	err = errors.New("更新排序标识出错:" + err.Error())
+		//	return
+		//}
 	}
 	return
 }
@@ -292,68 +319,6 @@ func RenameEnglishGroupName(groupId int64, groupName string, adminId int) (err e
 	return
 }
 
-// ShareSingleEnglishGroupPpt 共享单个ppt/取消共享单个ppt,返回共享状态
-func ShareSingleEnglishGroupPpt(groupPptId int64, adminId int, adminRealName string) (ret ppt_english.RespPptShare, err error) {
-	//判断当前登录者是否有共享的权限
-	groupPpt, err := ppt_english.GetPptMappingByGroupPptId(groupPptId, adminId)
-	if err != nil {
-		if utils.IsErrNoRow(err) {
-			err = errors.New("当前目录下的ppt不存在")
-			return
-		}
-		err = errors.New("目录下的ppt查询出错:" + err.Error())
-		return
-	}
-	groupInfo, err := ppt_english.GetPptGroupByGroupIdAdminId(groupPpt.GroupId, adminId)
-	if err != nil {
-		if utils.IsErrNoRow(err) {
-			err = errors.New("目录不存在")
-			return
-		}
-		err = errors.New("目录查询出错:" + err.Error())
-		return
-	}
-	//判断当前的共享状态
-	if groupPpt.ChildGroupPptId > 0 {
-		err = CancelShareSingleGroupPptEnglish(groupPpt, adminId)
-		if err != nil {
-			err = errors.New(err.Error())
-			return
-		}
-		ret.IsSingleShare = 0
-	} else {
-		//如果是未共享状态,则共享该ppt,类似复制流程,并更新共享标识
-		//新增公共目录。目录名称在原来的基础上带上复制序号
-		groupNames, tErr := ppt_english.GetPptGroupNamesByAdminId(adminId)
-		if tErr != nil {
-			err = errors.New("目录查询出错:" + tErr.Error())
-			return
-		}
-		newGroupName := generateCopyName(groupInfo.GroupName, 1, groupNames)
-		newGroupId, tErr := AddGroup(newGroupName, adminId, 1, 1)
-		if tErr != nil {
-			err = errors.New(tErr.Error())
-			return
-		}
-		//新增公共的目录和ppt之间的映射关系
-		newGroupPptId, tErr := AddGroupPptMapping(groupPpt.PptId, newGroupId, adminId, adminRealName)
-		if tErr != nil {
-			err = errors.New(tErr.Error())
-			return
-		}
-		//更新共享标识
-		groupPpt.ChildGroupPptId = newGroupPptId
-		err = groupPpt.Update([]string{"child_group_ppt_id"})
-		if err != nil {
-			err = errors.New("更新共享标识失败" + err.Error())
-			return
-		}
-		ret.IsSingleShare = 1
-	}
-	ret.GroupPptId = groupPptId
-	return
-}
-
 // GetGroupPptEnglishList 公共目录下的ppt列表或者单个目录里的ppt列表
 func GetGroupPptEnglishList(groupId int64, adminId int) (ret ppt_english.RespGroupPptList, err error) {
 	var groupPptList []*ppt_english.PptEnglishGroupMapping
@@ -464,6 +429,102 @@ func GetGroupPptEnglishList(groupId int64, adminId int) (ret ppt_english.RespGro
 	return
 }
 
+// MoveGroupPptEnglish 移动ppt操作
+//func MoveGroupPptEnglish(groupId, groupPptId, prevGroupPptId, nextGroupPptId int64, adminId int) (err error) {
+//	//查询当前映射是否存在
+//	//判断当前登录者是否有共享的权限oo
+//	groupPpt, err := ppt_english.GetPptMappingByGroupPptId(groupPptId, adminId)
+//	if err != nil {
+//		if utils.IsErrNoRow(err) {
+//			err = errors.New("当前目录下的ppt不存在")
+//			return
+//		}
+//		err = errors.New("目录下的ppt查询出错:" + err.Error())
+//		return
+//	}
+//	var updateStr []string
+//	//如果更换了目录,默认当前排序值为0
+//	var currentSort, prevSort, nextSort int64
+//	//判断是否更换group
+//	if groupPpt.GroupId != groupId {
+//		_, err = ppt_english.GetPptGroupByGroupIdAdminId(groupId, adminId)
+//		if err != nil {
+//			if utils.IsErrNoRow(err) {
+//				err = errors.New("目录不存在")
+//				return
+//			}
+//			err = errors.New("目录查询出错:" + err.Error())
+//			return
+//		}
+//		//判断是否需要解除当个ppt共享
+//		//解除共享操作
+//		err = CancelShareSingleGroupPptEnglish(groupPpt, adminId)
+//		if err != nil {
+//			err = errors.New(err.Error())
+//			return
+//		}
+//		groupPpt.GroupId = groupId
+//		updateStr = append(updateStr, "group_id")
+//	}
+//	currentSort = groupPpt.PptSort
+//
+//	var prevGroupPpt *ppt_english.PptEnglishGroupMapping
+//	var nextGroupPpt *ppt_english.PptEnglishGroupMapping
+//	if prevGroupPptId > 0 {
+//		prevGroupPpt, err = ppt_english.GetPptMappingByGroupPptId(prevGroupPptId, adminId)
+//		if err != nil {
+//			if utils.IsErrNoRow(err) {
+//				err = errors.New("目录下的ppt不存在")
+//				return
+//			}
+//			err = errors.New("目录下的ppt查询出错:" + err.Error())
+//			return
+//		}
+//		prevSort = prevGroupPpt.PptSort
+//	}
+//
+//	if nextGroupPptId > 0 {
+//		nextGroupPpt, err = ppt_english.GetPptMappingByGroupPptId(nextGroupPptId, adminId)
+//		if err != nil {
+//			if utils.IsErrNoRow(err) {
+//				err = errors.New("目录下的ppt不存在")
+//				return
+//			}
+//			err = errors.New("目录下的ppt查询出错:" + err.Error())
+//			return
+//		}
+//		nextSort = nextGroupPpt.PptSort
+//	}
+//
+//	updateStr = append(updateStr, "ppt_sort")
+//
+//	//移到两个排序值中间操作
+//	if prevSort >= currentSort {
+//		//往下移动
+//		err = ppt_english.MoveDownGroupPptBySort(groupId, prevSort, currentSort)
+//		if err != nil {
+//			err = errors.New("向下移动ppt出错:" + err.Error())
+//			return
+//		}
+//		groupPpt.PptSort = prevSort
+//	} else if nextSort <= currentSort && nextSort != 0 {
+//		//往上移动
+//		err = ppt_english.MoveUpGroupPptBySort(groupId, nextSort, currentSort)
+//		if err != nil {
+//			err = errors.New("向上移动ppt出错:" + err.Error())
+//			return
+//		}
+//		groupPpt.PptSort = nextSort
+//	}
+//	//更新当前排序
+//	err = groupPpt.Update(updateStr)
+//	if err != nil {
+//		err = errors.New("移动ppt出错:" + err.Error())
+//		return
+//	}
+//	return
+//}
+
 // MoveGroupPptEnglish 移动ppt操作
 func MoveGroupPptEnglish(groupId, groupPptId, prevGroupPptId, nextGroupPptId int64, adminId int) (err error) {
 	//查询当前映射是否存在
@@ -478,8 +539,6 @@ func MoveGroupPptEnglish(groupId, groupPptId, prevGroupPptId, nextGroupPptId int
 		return
 	}
 	var updateStr []string
-	//如果更换了目录,默认当前排序值为0
-	var currentSort, prevSort, nextSort int64
 	//判断是否更换group
 	if groupPpt.GroupId != groupId {
 		_, err = ppt_english.GetPptGroupByGroupIdAdminId(groupId, adminId)
@@ -501,7 +560,6 @@ func MoveGroupPptEnglish(groupId, groupPptId, prevGroupPptId, nextGroupPptId int
 		groupPpt.GroupId = groupId
 		updateStr = append(updateStr, "group_id")
 	}
-	currentSort = groupPpt.PptSort
 
 	var prevGroupPpt *ppt_english.PptEnglishGroupMapping
 	var nextGroupPpt *ppt_english.PptEnglishGroupMapping
@@ -515,7 +573,6 @@ func MoveGroupPptEnglish(groupId, groupPptId, prevGroupPptId, nextGroupPptId int
 			err = errors.New("目录下的ppt查询出错:" + err.Error())
 			return
 		}
-		prevSort = prevGroupPpt.PptSort
 	}
 
 	if nextGroupPptId > 0 {
@@ -528,29 +585,31 @@ func MoveGroupPptEnglish(groupId, groupPptId, prevGroupPptId, nextGroupPptId int
 			err = errors.New("目录下的ppt查询出错:" + err.Error())
 			return
 		}
-		nextSort = nextGroupPpt.PptSort
 	}
 
-	updateStr = append(updateStr, "ppt_sort")
-
-	//移到两个排序值中间操作
-	if prevSort >= currentSort {
-		//往下移动
-		err = ppt_english.MoveDownGroupPptBySort(groupId, prevSort, currentSort)
-		if err != nil {
-			err = errors.New("向下移动ppt出错:" + err.Error())
+	pptSort := groupPpt.PptSort
+	if prevGroupPpt != nil && prevGroupPpt.PptId > 0 && nextGroupPpt != nil && nextGroupPpt.PptId > 0 { // 两个之间
+		pptSort, _ = decimal.NewFromFloat(prevGroupPpt.PptSort).Add(decimal.NewFromFloat(nextGroupPpt.PptSort)).Div(decimal.NewFromInt(2)).Float64()
+	} else if prevGroupPpt != nil && prevGroupPpt.PptId > 0 {
+		// 最下面
+		maxSort, tmpErr := ppt_english.GetMinSortByEnglishGroupId(prevGroupPpt.GroupId)
+		if tmpErr != nil {
+			err = errors.New("获取最小排序失败:" + tmpErr.Error())
 			return
 		}
-		groupPpt.PptSort = prevSort
-	} else if nextSort <= currentSort && nextSort != 0 {
-		//往上移动
-		err = ppt_english.MoveUpGroupPptBySort(groupId, nextSort, currentSort)
-		if err != nil {
-			err = errors.New("向上移动ppt出错:" + err.Error())
+		pptSort, _ = decimal.NewFromFloat(maxSort).Sub(decimal.NewFromInt(1)).Float64()
+	} else if nextGroupPpt != nil && nextGroupPpt.PptId > 0 {
+		// 最上面
+		minSort, tmpErr := ppt_english.GetMaxSortByEnglishGroupId(nextGroupPpt.GroupId)
+		if tmpErr != nil {
+			err = errors.New("获取最小排序失败:" + tmpErr.Error())
 			return
 		}
-		groupPpt.PptSort = nextSort
+		pptSort, _ = decimal.NewFromFloat(minSort).Add(decimal.NewFromInt(1)).Float64()
 	}
+
+	groupPpt.PptSort = pptSort
+	updateStr = append(updateStr, "ppt_sort")
 	//更新当前排序
 	err = groupPpt.Update(updateStr)
 	if err != nil {
@@ -762,6 +821,15 @@ func CopyPptEnglish(pptId int, groupId int64, adminId int, adminRealName string)
 		err = errors.New("复制目录里的ppt出错:" + err.Error())
 		return
 	}
+
+	// 获取当前目录下最大的排序
+	pptSort, err := ppt_english.GetMaxSortByEnglishGroupId(groupId)
+	if err != nil {
+		err = errors.New("查询最大排序出错:" + err.Error())
+		return
+	}
+	newSort, _ := decimal.NewFromFloat(pptSort).Add(decimal.NewFromInt(1)).Float64()
+
 	var newMappings []*ppt_english.PptEnglishGroupMapping
 	newGroupPpt := &ppt_english.PptEnglishGroupMapping{
 		GroupId:       groupId,
@@ -770,6 +838,7 @@ func CopyPptEnglish(pptId int, groupId int64, adminId int, adminRealName string)
 		ModifyTime:    time.Now(),
 		AdminId:       adminId,
 		AdminRealName: adminRealName,
+		PptSort:       newSort,
 	}
 	newMappings = append(newMappings, newGroupPpt)
 
@@ -778,12 +847,12 @@ func CopyPptEnglish(pptId int, groupId int64, adminId int, adminRealName string)
 		err = errors.New("复制目录里的ppt出错:" + err.Error())
 		return
 	}
-	//批量更新排序字段
-	err = ppt_english.UpdatePptGroupMappingSortMulti([]int64{newGroupPpt.GroupPptId})
-	if err != nil {
-		err = errors.New("更新排序标识出错:" + err.Error())
-		return
-	}
+	////批量更新排序字段
+	//err = ppt_english.UpdatePptGroupMappingSortMulti([]int64{newGroupPpt.GroupPptId})
+	//if err != nil {
+	//	err = errors.New("更新排序标识出错:" + err.Error())
+	//	return
+	//}
 	var pptPage int
 	if newPpt.Content != "" {
 		contents := make([]PageContent, 0)

+ 188 - 183
services/ppt/ppt_group.go

@@ -8,6 +8,7 @@ import (
 	"eta/eta_api/services"
 	"eta/eta_api/utils"
 	"fmt"
+	"github.com/shopspring/decimal"
 	"sort"
 	"strconv"
 	"strings"
@@ -194,6 +195,8 @@ func AddGroup(groupName string, adminId int, isShare int8, isSharedAdd int8) (ne
 		AdminId:    adminId,
 		IsShare:    isShare,
 		IsShareAdd: isSharedAdd,
+		CreateTime: time.Now(),
+		ModifyTime: time.Now(),
 	}
 	if isShare == 1 {
 		groupInfo.ShareTime = time.Now()
@@ -225,11 +228,23 @@ func AddGroupPptMapping(pptId int64, groupId int64, adminId int, adminRealName s
 		err = errors.New("目录查询出错:" + err.Error())
 		return
 	}
+
+	// 获取当前目录下最大的排序
+	pptSort, err := models.GetMaxSortByGroupId(groupId)
+	if err != nil {
+		err = errors.New("查询最大排序出错:" + err.Error())
+		return
+	}
+	newSort, _ := decimal.NewFromFloat(pptSort).Add(decimal.NewFromInt(1)).Float64()
+
 	bindInfo := &models.PptV2GroupMapping{
 		GroupId:       groupId,
 		PptId:         pptId,
 		AdminId:       adminId,
 		AdminRealName: adminRealName,
+		CreateTime:    time.Now(),
+		ModifyTime:    time.Now(),
+		PptSort:       newSort,
 	}
 
 	newId, err = models.AddPptGroupMapping(bindInfo)
@@ -239,12 +254,12 @@ func AddGroupPptMapping(pptId int64, groupId int64, adminId int, adminRealName s
 	}
 
 	bindInfo.GroupPptId = newId
-	bindInfo.PptSort = newId
-	err = bindInfo.Update([]string{"ppt_sort"})
-	if err != nil {
-		err = errors.New("更新排序失败:" + err.Error())
-		return
-	}
+	//bindInfo.PptSort = newId
+	//err = bindInfo.Update([]string{"ppt_sort"})
+	//if err != nil {
+	//	err = errors.New("更新排序失败:" + err.Error())
+	//	return
+	//}
 	return
 }
 
@@ -359,6 +374,11 @@ func CopyGroup(groupId int64, adminId int, adminRealName string) (err error) {
 		err = errors.New("查询ppt列表出错:" + err.Error())
 		return
 	}
+	pptMap := make(map[int]*models.PptV2)
+	for _, pptItem := range pptList {
+		pptMap[pptItem.PptId] = pptItem
+	}
+
 	pptNames, err := models.GetAllPptTitle()
 	if err != nil {
 		err = errors.New("查询ppt标题出错:" + err.Error())
@@ -366,30 +386,37 @@ func CopyGroup(groupId int64, adminId int, adminRealName string) (err error) {
 	}
 	//批量复制ppt,更新作者为当前账号,并返回新的pptID
 	newPptList := make([]*models.PptV2, 0)
-	for _, v := range pptList {
-		if v.PptVersion != 2 { //只复制新版的ppt,旧版本的ppt忽略
+	for _, mapping := range mappingList {
+		pptItem, ok := pptMap[int(mapping.PptId)]
+		// 不存在该ppt,那么就过滤
+		if !ok {
+			continue
+		}
+
+		if pptItem.PptVersion != 2 { //只复制新版的ppt,旧版本的ppt忽略
 			continue
 		}
 		tmp := &models.PptV2{
-			TemplateType:  v.TemplateType,
-			BackgroundImg: v.BackgroundImg,
-			Title:         generateCopyName(v.Title, 1, pptNames),
-			ReportType:    v.ReportType,
-			PptDate:       v.PptDate,
-			Content:       v.Content,
-			CoverContent:  v.CoverContent,
-			PptUrl:        v.PptUrl,
-			PptxUrl:       v.PptxUrl,
+			TemplateType:  pptItem.TemplateType,
+			BackgroundImg: pptItem.BackgroundImg,
+			Title:         generateCopyName(pptItem.Title, 1, pptNames),
+			ReportType:    pptItem.ReportType,
+			PptDate:       pptItem.PptDate,
+			Content:       pptItem.Content,
+			CoverContent:  pptItem.CoverContent,
+			PptUrl:        pptItem.PptUrl,
+			PptxUrl:       pptItem.PptxUrl,
 			CreateTime:    time.Now(),
 			ModifyTime:    time.Now(),
 			AdminId:       adminId,
 			AdminRealName: adminRealName,
-			PptVersion:    v.PptVersion,
-			TitleSetting:  v.TitleSetting,
+			PptVersion:    pptItem.PptVersion,
+			TitleSetting:  pptItem.TitleSetting,
 		}
 		newPptList = append(newPptList, tmp)
 	}
-	if len(newPptList) > 0 {
+	pptNum := len(newPptList)
+	if pptNum > 0 {
 		err = models.AddPptV2Multi(newPptList)
 		if err != nil {
 			err = errors.New("复制目录里的ppt出错:" + err.Error())
@@ -397,7 +424,7 @@ func CopyGroup(groupId int64, adminId int, adminRealName string) (err error) {
 		}
 
 		newMappings := make([]*models.PptV2GroupMapping, 0)
-		for _, v := range newPptList {
+		for k, v := range newPptList {
 			tmp := &models.PptV2GroupMapping{
 				GroupId:       newGroupId,
 				PptId:         int64(v.PptId),
@@ -405,6 +432,7 @@ func CopyGroup(groupId int64, adminId int, adminRealName string) (err error) {
 				ModifyTime:    time.Now(),
 				AdminId:       adminId,
 				AdminRealName: adminRealName,
+				PptSort:       float64(pptNum - k),
 			}
 			newMappings = append(newMappings, tmp)
 		}
@@ -414,15 +442,15 @@ func CopyGroup(groupId int64, adminId int, adminRealName string) (err error) {
 			return
 		}
 		//批量更新排序字段
-		var newGroupPptIds []int64
-		for _, v := range newMappings {
-			newGroupPptIds = append(newGroupPptIds, v.GroupPptId)
-		}
-		err = models.UpdatePptGroupMappingSortMulti(newGroupPptIds)
-		if err != nil {
-			err = errors.New("更新排序标识出错:" + err.Error())
-			return
-		}
+		//var newGroupPptIds []int64
+		//for _, v := range newMappings {
+		//	newGroupPptIds = append(newGroupPptIds, v.GroupPptId)
+		//}
+		//err = models.UpdatePptGroupMappingSortMulti(newGroupPptIds)
+		//if err != nil {
+		//	err = errors.New("更新排序标识出错:" + err.Error())
+		//	return
+		//}
 	}
 	return
 }
@@ -652,6 +680,101 @@ func GetGroupPptList(groupId int64, adminId int) (ret models.RespGroupPptList, e
 }
 
 // MoveGroupPpt 移动ppt操作
+//func MoveGroupPpt(groupId, groupPptId, prevGroupPptId, nextGroupPptId int64, adminId int) (err error) {
+//	//查询当前映射是否存在
+//	//判断当前登录者是否有共享的权限oo
+//	groupPpt, err := models.GetPptMappingByGroupPptId(groupPptId, adminId)
+//	if err != nil {
+//		if utils.IsErrNoRow(err) {
+//			err = errors.New("当前目录下的ppt不存在")
+//			return
+//		}
+//		err = errors.New("目录下的ppt查询出错:" + err.Error())
+//		return
+//	}
+//	var updateStr []string
+//	//如果更换了目录,默认当前排序值为0
+//	var currentSort, prevSort, nextSort int64
+//	//判断是否更换group
+//	if groupPpt.GroupId != groupId {
+//		_, err = models.GetPptGroupByGroupIdAdminId(groupId, adminId)
+//		if err != nil {
+//			if utils.IsErrNoRow(err) {
+//				err = errors.New("目录不存在")
+//				return
+//			}
+//			err = errors.New("目录查询出错:" + err.Error())
+//			return
+//		}
+//		//判断是否需要解除当个ppt共享
+//		//解除共享操作
+//		err = CancelShareSingleGroupPpt(groupPpt, adminId)
+//		if err != nil {
+//			err = errors.New(err.Error())
+//			return
+//		}
+//		groupPpt.GroupId = groupId
+//		updateStr = append(updateStr, "group_id")
+//	}
+//	currentSort = groupPpt.PptSort
+//
+//	var prevGroupPpt *models.PptV2GroupMapping
+//	var nextGroupPpt *models.PptV2GroupMapping
+//	if prevGroupPptId > 0 {
+//		prevGroupPpt, err = models.GetPptMappingByGroupPptId(prevGroupPptId, adminId)
+//		if err != nil {
+//			if utils.IsErrNoRow(err) {
+//				err = errors.New("目录下的ppt不存在")
+//				return
+//			}
+//			err = errors.New("目录下的ppt查询出错:" + err.Error())
+//			return
+//		}
+//		prevSort = prevGroupPpt.PptSort
+//	}
+//
+//	if nextGroupPptId > 0 {
+//		nextGroupPpt, err = models.GetPptMappingByGroupPptId(nextGroupPptId, adminId)
+//		if err != nil {
+//			if utils.IsErrNoRow(err) {
+//				err = errors.New("目录下的ppt不存在")
+//				return
+//			}
+//			err = errors.New("目录下的ppt查询出错:" + err.Error())
+//			return
+//		}
+//		nextSort = nextGroupPpt.PptSort
+//	}
+//
+//	updateStr = append(updateStr, "ppt_sort")
+//
+//	//移到两个排序值中间操作
+//	if prevSort >= currentSort {
+//		//往下移动
+//		err = models.MoveDownGroupPptBySort(groupId, prevSort, currentSort)
+//		if err != nil {
+//			err = errors.New("向下移动ppt出错:" + err.Error())
+//			return
+//		}
+//		groupPpt.PptSort = prevSort
+//	} else if nextSort <= currentSort && nextSort != 0 {
+//		//往上移动
+//		err = models.MoveUpGroupPptBySort(groupId, nextSort, currentSort)
+//		if err != nil {
+//			err = errors.New("向上移动ppt出错:" + err.Error())
+//			return
+//		}
+//		groupPpt.PptSort = nextSort
+//	}
+//	//更新当前排序
+//	err = groupPpt.Update(updateStr)
+//	if err != nil {
+//		err = errors.New("移动ppt出错:" + err.Error())
+//		return
+//	}
+//	return
+//}
+
 func MoveGroupPpt(groupId, groupPptId, prevGroupPptId, nextGroupPptId int64, adminId int) (err error) {
 	//查询当前映射是否存在
 	//判断当前登录者是否有共享的权限oo
@@ -665,8 +788,6 @@ func MoveGroupPpt(groupId, groupPptId, prevGroupPptId, nextGroupPptId int64, adm
 		return
 	}
 	var updateStr []string
-	//如果更换了目录,默认当前排序值为0
-	var currentSort, prevSort, nextSort int64
 	//判断是否更换group
 	if groupPpt.GroupId != groupId {
 		_, err = models.GetPptGroupByGroupIdAdminId(groupId, adminId)
@@ -688,7 +809,6 @@ func MoveGroupPpt(groupId, groupPptId, prevGroupPptId, nextGroupPptId int64, adm
 		groupPpt.GroupId = groupId
 		updateStr = append(updateStr, "group_id")
 	}
-	currentSort = groupPpt.PptSort
 
 	var prevGroupPpt *models.PptV2GroupMapping
 	var nextGroupPpt *models.PptV2GroupMapping
@@ -702,7 +822,6 @@ func MoveGroupPpt(groupId, groupPptId, prevGroupPptId, nextGroupPptId int64, adm
 			err = errors.New("目录下的ppt查询出错:" + err.Error())
 			return
 		}
-		prevSort = prevGroupPpt.PptSort
 	}
 
 	if nextGroupPptId > 0 {
@@ -715,29 +834,31 @@ func MoveGroupPpt(groupId, groupPptId, prevGroupPptId, nextGroupPptId int64, adm
 			err = errors.New("目录下的ppt查询出错:" + err.Error())
 			return
 		}
-		nextSort = nextGroupPpt.PptSort
 	}
 
-	updateStr = append(updateStr, "ppt_sort")
-
-	//移到两个排序值中间操作
-	if prevSort >= currentSort {
-		//往下移动
-		err = models.MoveDownGroupPptBySort(groupId, prevSort, currentSort)
-		if err != nil {
-			err = errors.New("向下移动ppt出错:" + err.Error())
+	pptSort := groupPpt.PptSort
+	if prevGroupPpt != nil && prevGroupPpt.PptId > 0 && nextGroupPpt != nil && nextGroupPpt.PptId > 0 { // 两个之间
+		pptSort, _ = decimal.NewFromFloat(prevGroupPpt.PptSort).Add(decimal.NewFromFloat(nextGroupPpt.PptSort)).Div(decimal.NewFromInt(2)).Float64()
+	} else if prevGroupPpt != nil && prevGroupPpt.PptId > 0 {
+		// 最下面
+		maxSort, tmpErr := models.GetMinSortByGroupId(prevGroupPpt.GroupId)
+		if tmpErr != nil {
+			err = errors.New("获取最小排序失败:" + tmpErr.Error())
 			return
 		}
-		groupPpt.PptSort = prevSort
-	} else if nextSort <= currentSort && nextSort != 0 {
-		//往上移动
-		err = models.MoveUpGroupPptBySort(groupId, nextSort, currentSort)
-		if err != nil {
-			err = errors.New("向上移动ppt出错:" + err.Error())
+		pptSort, _ = decimal.NewFromFloat(maxSort).Sub(decimal.NewFromInt(1)).Float64()
+	} else if nextGroupPpt != nil && nextGroupPpt.PptId > 0 {
+		// 最上面
+		minSort, tmpErr := models.GetMaxSortByGroupId(nextGroupPpt.GroupId)
+		if tmpErr != nil {
+			err = errors.New("获取最小排序失败:" + tmpErr.Error())
 			return
 		}
-		groupPpt.PptSort = nextSort
+		pptSort, _ = decimal.NewFromFloat(minSort).Add(decimal.NewFromInt(1)).Float64()
 	}
+
+	groupPpt.PptSort = pptSort
+	updateStr = append(updateStr, "ppt_sort")
 	//更新当前排序
 	err = groupPpt.Update(updateStr)
 	if err != nil {
@@ -809,12 +930,14 @@ func MoveGroup(groupId, prevGroupId, nextGroupId int64, adminId int) (err error)
 		}
 		groupInfo.GroupSort = nextSort
 	}
+
 	//更新当前排序
 	err = groupInfo.Update([]string{"group_sort"})
 	if err != nil {
 		err = errors.New("移动目录出错:" + err.Error())
 		return
 	}
+
 	return
 }
 
@@ -956,6 +1079,15 @@ func CopyPpt(pptId int, groupId int64, adminId int, adminRealName string) (resp
 		err = errors.New("复制目录里的ppt出错:" + err.Error())
 		return
 	}
+
+	// 获取当前目录下最大的排序
+	pptSort, err := models.GetMaxSortByGroupId(groupId)
+	if err != nil {
+		err = errors.New("查询最大排序出错:" + err.Error())
+		return
+	}
+	newSort, _ := decimal.NewFromFloat(pptSort).Add(decimal.NewFromInt(1)).Float64()
+
 	var newMappings []*models.PptV2GroupMapping
 	newGroupPpt := &models.PptV2GroupMapping{
 		GroupId:       groupId,
@@ -964,6 +1096,7 @@ func CopyPpt(pptId int, groupId int64, adminId int, adminRealName string) (resp
 		ModifyTime:    time.Now(),
 		AdminId:       adminId,
 		AdminRealName: adminRealName,
+		PptSort:       newSort,
 	}
 	newMappings = append(newMappings, newGroupPpt)
 
@@ -973,11 +1106,11 @@ func CopyPpt(pptId int, groupId int64, adminId int, adminRealName string) (resp
 		return
 	}
 	//批量更新排序字段
-	err = models.UpdatePptGroupMappingSortMulti([]int64{newGroupPpt.GroupPptId})
-	if err != nil {
-		err = errors.New("更新排序标识出错:" + err.Error())
-		return
-	}
+	//err = models.UpdatePptGroupMappingSortMulti([]int64{newGroupPpt.GroupPptId})
+	//if err != nil {
+	//	err = errors.New("更新排序标识出错:" + err.Error())
+	//	return
+	//}
 	pptPage := 0
 	// 因之前并没有存储PPT页数字段,所以此处读取PPT内容的长度
 	if newPpt.Content != "" {
@@ -1061,134 +1194,6 @@ func SearchGroupPpt(keyWord string) (ret models.RespSearchGroupPptList, err erro
 	return
 }
 
-// InitPptGroup 初始化目录分组
-func InitPptGroup() (err error) {
-	//查询所有的ppt
-	now := time.Now()
-	pptList, err := models.GetPptV2ByCondition("", []interface{}{})
-	if err != nil {
-		errors.New("查询所有的ppt出错" + err.Error())
-		return
-	}
-	pptAdminMap := make(map[int]string, 0)
-	adminPptListMap := make(map[int][]*models.PptV2)
-	oldPptAdminMap := make(map[int]string, 0)
-	oldAdminPptListMap := make(map[int][]*models.PptV2)
-	//查询所有的ppt作者
-	for _, v := range pptList {
-		if v.PptVersion == 2 {
-			if _, ok := pptAdminMap[v.AdminId]; !ok {
-				pptAdminMap[v.AdminId] = v.AdminRealName
-			}
-			adminPptListMap[v.AdminId] = append(adminPptListMap[v.AdminId], v)
-		} else {
-			if _, ok := oldPptAdminMap[v.AdminId]; !ok {
-				oldPptAdminMap[v.AdminId] = v.AdminRealName
-			}
-			oldAdminPptListMap[v.AdminId] = append(oldAdminPptListMap[v.AdminId], v)
-		}
-	}
-	//批量生成历史目录,共享目录
-	newOldGroupList := make([]*models.PptV2Group, 0)
-	//批量生成目录,共享目录
-	newGroupList := make([]*models.PptV2Group, 0)
-	if len(pptAdminMap) > 0 {
-		for k, v := range pptAdminMap {
-			tmp := &models.PptV2Group{
-				GroupName:  v + "的PPT",
-				AdminId:    k,
-				IsShare:    1,
-				CreateTime: now,
-				ModifyTime: now,
-				ShareTime:  now,
-			}
-			newGroupList = append(newGroupList, tmp)
-		}
-		//批量把对应的ppt放到目录当中
-		err = models.AddPptGroupMulti(newGroupList)
-		if err != nil {
-			err = errors.New("创建目录出错:" + err.Error())
-			return
-		}
-	}
-	if len(oldPptAdminMap) > 0 {
-		for k, v := range oldPptAdminMap {
-			tmp := &models.PptV2Group{
-				GroupName:  v + "的历史PPT",
-				AdminId:    k,
-				IsShare:    1,
-				CreateTime: now,
-				ModifyTime: now,
-				ShareTime:  now,
-			}
-			newOldGroupList = append(newOldGroupList, tmp)
-		}
-		//批量把对应的ppt放到目录当中
-		err = models.AddPptGroupMulti(newOldGroupList)
-		if err != nil {
-			err = errors.New("创建目录出错:" + err.Error())
-			return
-		}
-	}
-	//批量更新排序字段
-	var newGroupIds []int64
-	var newMappings []*models.PptV2GroupMapping
-	for _, v := range newGroupList {
-		newGroupIds = append(newGroupIds, v.GroupId)
-		if ppts, ok := adminPptListMap[v.AdminId]; ok {
-			for _, p := range ppts {
-				tmp := &models.PptV2GroupMapping{
-					GroupId:       v.GroupId,
-					PptId:         int64(p.PptId),
-					CreateTime:    now,
-					ModifyTime:    now,
-					AdminId:       p.AdminId,
-					AdminRealName: p.AdminRealName,
-				}
-				newMappings = append(newMappings, tmp)
-			}
-		}
-	}
-	for _, v := range newOldGroupList {
-		newGroupIds = append(newGroupIds, v.GroupId)
-		if oldPpts, ok1 := oldAdminPptListMap[v.AdminId]; ok1 {
-			for _, p := range oldPpts {
-				tmp := &models.PptV2GroupMapping{
-					GroupId:       v.GroupId,
-					PptId:         int64(p.PptId),
-					CreateTime:    now,
-					ModifyTime:    now,
-					AdminId:       p.AdminId,
-					AdminRealName: p.AdminRealName,
-				}
-				newMappings = append(newMappings, tmp)
-			}
-		}
-	}
-	err = models.UpdatePptGroupSortMulti(newGroupIds)
-	if err != nil {
-		err = errors.New("更新目录排序标识出错:" + err.Error())
-		return
-	}
-
-	err = models.AddPptGroupMappingMulti(newMappings)
-	if err != nil {
-		err = errors.New("创建目录里的ppt出错:" + err.Error())
-		return
-	}
-	//批量更新排序字段
-	var newGroupPptIds []int64
-	for _, v := range newMappings {
-		newGroupPptIds = append(newGroupPptIds, v.GroupPptId)
-	}
-	err = models.UpdatePptGroupMappingSortMulti(newGroupPptIds)
-	if err != nil {
-		err = errors.New("更新排序标识出错:" + err.Error())
-		return
-	}
-	return
-}
-
 // GetGroupsByAdminIdV2 查询ppt目录列表
 // @Author roc
 // @Time 2022-08-29 15:22:20

+ 1 - 1
services/report_approve.go

@@ -860,7 +860,7 @@ func AfterReportApprovePass(reportType, reportId int) (err error) {
 
 		// 生成报告pdf和长图
 		{
-			reportPdfUrl := GetGeneralPdfUrl(reportInfo.ReportCode, reportInfo.ClassifyNameFirst, reportInfo.ReportLayout)
+			reportPdfUrl := GetGeneralPdfUrl(reportInfo.Id, reportInfo.ReportCode, reportInfo.ClassifyNameFirst, reportInfo.ReportLayout)
 			go Report2pdfAndJpeg(reportPdfUrl, reportId, 1)
 		}
 

+ 419 - 3
services/report_v2.go

@@ -24,6 +24,10 @@ import (
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/beego/beego/v2/server/web"
+	"github.com/beego/beego/v2/server/web/context"
+	"github.com/go-redis/redis/v8"
 )
 
 // AddReportAndChapter
@@ -1221,7 +1225,7 @@ func PublishReport(reportId int, reportUrl string, sysUser *system.Admin) (tips
 
 	// 生成报告pdf和长图
 	{
-		reportPdfUrl := GetGeneralPdfUrl(reportInfo.ReportCode, reportInfo.ClassifyNameFirst, reportInfo.ReportLayout)
+		reportPdfUrl := GetGeneralPdfUrl(reportInfo.Id, reportInfo.ReportCode, reportInfo.ClassifyNameFirst, reportInfo.ReportLayout)
 		go Report2pdfAndJpeg(reportPdfUrl, reportId, 1)
 	}
 
@@ -1348,7 +1352,7 @@ func PublishChapterReport(reportInfo *models.Report, reportUrl string, sysUser *
 
 	// 生成报告pdf和长图
 	{
-		reportPdfUrl := GetGeneralPdfUrl(reportInfo.ReportCode, reportInfo.ClassifyNameFirst, reportInfo.ReportLayout)
+		reportPdfUrl := GetGeneralPdfUrl(reportInfo.Id, reportInfo.ReportCode, reportInfo.ClassifyNameFirst, reportInfo.ReportLayout)
 		go Report2pdfAndJpeg(reportPdfUrl, reportId, 1)
 	}
 
@@ -1586,7 +1590,7 @@ func UpdateReportVideo(reportInfo *models.Report) {
 // @param reportCode string
 // @param reportLayout int8
 // @return pdfUrl string
-func GetGeneralPdfUrl(reportCode, classifyFirstName string, reportLayout int8) (pdfUrl string) {
+func GetGeneralPdfUrl(reportId int, reportCode, classifyFirstName string, reportLayout int8) (pdfUrl string) {
 	// 如果是弘则,且是晨、周报,那么就不返回
 	if utils.InArrayByStr([]string{utils.BusinessCodeRelease, utils.BusinessCodeSandbox, utils.BusinessCodeDebug}, utils.BusinessCode) && utils.InArrayByStr([]string{"晨报", "周报"}, classifyFirstName) {
 		return
@@ -1615,6 +1619,14 @@ func GetGeneralPdfUrl(reportCode, classifyFirstName string, reportLayout int8) (
 		pdfUrl = fmt.Sprintf("%s/reportshare_smart_pdf?code=%s", reportUrl, reportCode)
 	}
 
+	if pdfUrl != "" {
+		token := utils.MD5(fmt.Sprint(pdfUrl, time.Now().UnixNano()/1e6))
+		e := generalReportAuthToken(token, ``, reportId)
+		if e == nil {
+			pdfUrl = fmt.Sprintf("%s&authToken=%s", pdfUrl, token)
+		}
+	}
+
 	return
 }
 
@@ -1909,3 +1921,407 @@ func processMapTable(data map[string]interface{}, reportId, fromScene int) error
 	}
 	return nil
 }
+
+// HandleReportContent
+// @Description: 处理报告内容(动态图表/表格添加授权token)
+// @author: Roc
+// @datetime 2025-01-07 10:03:15
+// @param body string
+// @return newBody string
+func HandleReportContent(body string, opType string, tokenMap map[string]string) (newBody string) {
+	if body == `` {
+		return
+	}
+	newBody = body
+
+	// 解析HTML
+	doc, err := html2.Parse(strings.NewReader(body))
+	if err != nil {
+		fmt.Println("Error parsing HTML:", err)
+		return
+	}
+
+	replaceIframeSrc(doc, opType, tokenMap)
+
+	// 输出修改后的HTML
+	var modifiedHtml strings.Builder
+	err = html2.Render(&modifiedHtml, doc)
+	if err != nil {
+		fmt.Println("Error rendering HTML:", err)
+		return
+	}
+
+	newBody = modifiedHtml.String()
+	fmt.Println(newBody)
+
+	return
+}
+
+// replaceIframeSrc 遍历HTML节点,替换iframe的src属性
+func replaceIframeSrc(n *html2.Node, opType string, tokenMap map[string]string) {
+	if n.Type == html2.ElementNode && n.Data == "iframe" {
+		for i, attr := range n.Attr {
+			if attr.Key == "src" {
+				newLink := attr.Val
+				// 处理链接
+				switch opType {
+				case `add`:
+					newLink = linkAddToken(attr.Val, tokenMap)
+				case `del`:
+					newLink = linkDelToken(attr.Val)
+				}
+				// 替换原来的链接
+				n.Attr[i].Val = newLink
+				break
+			}
+		}
+	}
+	// 递归处理子节点
+	for c := n.FirstChild; c != nil; c = c.NextSibling {
+		replaceIframeSrc(c, opType, tokenMap)
+	}
+}
+
+// linkAddToken 链接添加token
+func linkAddToken(link string, tokenMap map[string]string) string {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Info("处理链接失败,ERR:" + err.Error())
+		}
+	}()
+	if link == `` {
+		return link
+	}
+	parsedURL, err := url.Parse(link)
+	if err != nil {
+		return link
+	}
+
+	// 获取查询参数
+	queryParams := parsedURL.Query()
+
+	// 先移除authToken参数,避免莫名其妙的这个值入库了
+	queryParams.Del("authToken")
+
+	// 获取code参数
+	code := queryParams.Get("code")
+	if code == "" {
+		return link
+	}
+
+	showType := `chart`
+	if strings.Contains(parsedURL.Path, "sheetshow") {
+		showType = `excel`
+	}
+
+	// 避免报告里面一个图表/表格重复生成token
+	key := fmt.Sprint(showType, `:`, code)
+	if tokenMap != nil {
+		if token, ok := tokenMap[key]; ok {
+			// 在链接后面添加一个token值
+			return link + "&authToken=" + token
+		}
+	}
+
+	token, err := GeneralChartToken(showType, code)
+	if err != nil {
+		return link
+	}
+
+	if tokenMap != nil {
+		tokenMap[key] = token
+	}
+
+	// 在链接后面添加一个token值
+	return link + "&authToken=" + token
+}
+
+// linkDelToken 链接添加token
+func linkDelToken(link string) string {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Info("处理链接失败,ERR:" + err.Error())
+		}
+	}()
+	parsedURL, err := url.Parse(link)
+	if err != nil {
+		return link
+	}
+
+	// 获取查询参数
+	queryParams := parsedURL.Query()
+
+	// 移除authToken参数
+	queryParams.Del("authToken")
+
+	// 更新URL的查询参数
+	parsedURL.RawQuery = queryParams.Encode()
+
+	return parsedURL.String()
+}
+
+// GeneralChartToken
+// @Description: 生成图表/表格授权token
+// @author: Roc
+// @datetime 2025-01-07 10:41:36
+// @param showType string
+// @param uniqueCode string
+// @return token string
+// @return err error
+func GeneralChartToken(showType, uniqueCode string) (token string, err error) {
+	// 缓存key
+	token = utils.MD5(fmt.Sprint(showType+`:`, uniqueCode, time.Now().UnixNano()/1e6))
+	key := fmt.Sprint(utils.CACHE_CHART_AUTH, token)
+	err = utils.Rc.Put(key, uniqueCode, utils.BusinessConfReportChartExpiredTime)
+
+	return
+}
+
+// GeneralReportToken
+// @Description: 生成报告授权token
+// @author: Roc
+// @datetime 2025-01-07 10:41:36
+// @param uniqueCode string
+// @return token string
+// @return err error
+func GeneralReportToken(linkToken string, reportId int) (token string, err error) {
+	// 图表授权token
+	token = utils.MD5(fmt.Sprint(linkToken, time.Now().UnixNano()/1e6))
+
+	// 缓存key
+	reportKey := getReportShareTokenKey(linkToken)
+	err = utils.Rc.Put(reportKey, token, utils.BusinessConfReportChartExpiredTime)
+	if err != nil {
+		return
+	}
+
+	// 生成报告的图表授权token
+	err = generalReportAuthToken(token, ``, reportId)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// generalReportAuthToken
+// @Description: 生成报告的图表授权token
+// @author: Roc
+// @datetime 2025-03-17 17:47:07
+// @param token string
+// @param reportId int
+// @return err error
+func generalReportAuthToken(token, source string, reportId int) (err error) {
+	// 缓存key
+	reportTokenKey := getReportTokenKey(token, source)
+	err = utils.Rc.Put(reportTokenKey, reportId, utils.BusinessConfReportChartExpiredTime)
+
+	return
+}
+
+// getReportShareTokenKey
+// @Description:
+// @author: Roc
+// @datetime 2025-03-17 14:00:14
+// @param linkToken string
+// @return string
+func getReportShareTokenKey(linkToken string) string {
+	return fmt.Sprint(utils.CACHE_REPORT_SHARE_AUTH, utils.MD5(linkToken))
+}
+
+// GetReportAuthToken
+// @Description: 获取报告token
+// @author: Roc
+// @datetime 2025-03-17 16:48:38
+// @param linkToken string
+// @return string
+func GetReportAuthToken(linkToken string) string {
+	key := getReportShareTokenKey(linkToken)
+	return utils.Rc.GetStr(key)
+
+}
+
+// getReportTokenKey
+// @Description:
+// @author: Roc
+// @datetime 2025-03-17 14:00:14
+// @param linkToken string
+// @return string
+func getReportTokenKey(token, source string) string {
+	return fmt.Sprint(utils.CACHE_REPORT_AUTH, source, token)
+}
+
+// HandleReportContentStruct
+// @Description: 处理内容组件的链接
+// @author: Roc
+// @datetime 2025-01-07 13:38:39
+// @param body string
+// @param opType string
+// @return newBody string
+func HandleReportContentStruct(body string, opType string, tokenMap map[string]string) (newBody string) {
+	if body == `` {
+		return
+	}
+	newBody = body
+
+	// 解析JSON数据到map[string]interface{}
+	var jsonData []map[string]interface{}
+	if err := json.Unmarshal([]byte(body), &jsonData); err != nil {
+		fmt.Println("Error parsing JSON:", err)
+		return
+	}
+
+	// 处理每个组件
+	for i := range jsonData {
+		if err := processMap(jsonData[i], opType, tokenMap); err != nil {
+			fmt.Println("Error processing component:", err)
+			return
+		}
+	}
+
+	// 将处理后的数据转换回JSON字符串
+	modifiedJSON, err := json.MarshalIndent(jsonData, "", "  ")
+	if err != nil {
+		fmt.Println("Error marshaling JSON:", err)
+		return
+	}
+	newBody = string(modifiedJSON)
+
+	return
+}
+
+// processMap 递归处理map中的content字段
+func processMap(data map[string]interface{}, opType string, tokenMap map[string]string) error {
+	for key, value := range data {
+		switch v := value.(type) {
+		case string:
+			if key == "content" {
+				contentSource, ok := data["compType"]
+				if !ok {
+					continue
+				}
+				contentSourceType, ok := contentSource.(string)
+				if !ok {
+					continue
+				}
+				if !utils.InArrayByStr([]string{`sheet`, `chart`}, contentSourceType) {
+					continue
+				}
+
+				newContent := v
+				// 处理链接
+				switch opType {
+				case `add`:
+					newContent = linkAddToken(v, tokenMap)
+				case `del`:
+					newContent = linkDelToken(v)
+				}
+				data[key] = newContent
+			}
+		case map[string]interface{}:
+			if err := processMap(v, opType, tokenMap); err != nil {
+				return err
+			}
+		case []interface{}:
+			for i := range v {
+				if m, ok := v[i].(map[string]interface{}); ok {
+					if err := processMap(m, opType, tokenMap); err != nil {
+						return err
+					}
+				}
+			}
+		}
+	}
+	return nil
+}
+
+// GetReportShareUrlToken 获取报告分享链接token
+func GetReportShareUrlToken(req models.ReportShartUrlReq, adminId int) (linkToken string, err error) {
+	defer func() {
+		if err == nil && linkToken != `` {
+			GeneralReportToken(linkToken, req.ReportId)
+		}
+	}()
+	cacheLinkKey := utils.CACHE_REPORT_SHARE_SHORT_Url + strconv.Itoa(req.ReportId) + "userId:" + strconv.Itoa(adminId)
+	linkToken, _ = utils.Rc.RedisString(cacheLinkKey)
+	if linkToken != "" && utils.Rc.IsExist(fmt.Sprint(utils.CACHE_REPORT_SHARE_ORIGIN_Url, utils.MD5(linkToken))) {
+		return
+	}
+	var tokenKey string
+
+	var ok bool
+	// 冲突检测
+	for i := 0; i < 3; i++ {
+		linkToken = req.Url
+		if i > 0 {
+			linkToken += "_" + utils.GetRandDigit(3)
+		}
+		hashUrl := utils.MurmurHash64([]byte(linkToken))
+		linkToken = utils.ConvertNumToBase62(hashUrl)
+		// 拼上报告标题
+		//linkToken = fmt.Sprintf("%s %s", linkToken, req.Title)
+
+		tokenKey = fmt.Sprint(utils.CACHE_REPORT_SHARE_ORIGIN_Url, utils.MD5(linkToken))
+		ok = utils.Rc.IsExist(tokenKey)
+		if !ok {
+			break
+		}
+	}
+	if !ok {
+		after := time.Now().AddDate(0, 0, 7)
+		err = utils.Rc.Put(cacheLinkKey, linkToken, time.Until(after))
+		if err != nil {
+			return
+		}
+		err = utils.Rc.Put(tokenKey, req.Url, time.Until(after))
+		if err != nil {
+			return
+		}
+	} else {
+		linkToken = ""
+		err = errors.New("生成链接失败")
+	}
+	return
+}
+
+func TransfromToOriginUrl(linkToken string) (originLink string, msg string, err error) {
+	cacheLinkKey := fmt.Sprint(utils.CACHE_REPORT_SHARE_ORIGIN_Url, utils.MD5(linkToken))
+	originLink, err = utils.Rc.RedisString(cacheLinkKey)
+	if err != nil {
+		if err == redis.Nil {
+			msg = "链接已失效, 请重新获取"
+			return
+		}
+		msg = "获取链接失败"
+		return
+	}
+	if originLink == "" {
+		msg = "链接已失效, 请重新获取"
+		return
+	}
+
+	reportToken := GetReportAuthToken(linkToken)
+	if reportToken != "" {
+		originLink += `&authToken=` + reportToken
+	}
+
+	return
+}
+
+func FilterShareUrl() web.FilterFunc {
+	return func(c *context.Context) {
+		path := c.Input.Context.Request.URL.Path
+		tokenArr := strings.Split(path, "/")
+		token := tokenArr[len(tokenArr)-1]
+
+		newPath := "/adminapi/report/share/link"
+		q := c.Input.Context.Request.URL.Query()
+		q.Add("Token", token)
+		c.Input.Context.Request.URL.Path = newPath
+		c.Input.Context.Request.URL.RawQuery = q.Encode()
+
+		utils.ApiLog.Info(fmt.Sprintf("原始请求为:%s, 已修改请求路径为:%s?%s", path, newPath, q.Encode()))
+	}
+}

+ 221 - 97
services/smart_report.go

@@ -167,10 +167,10 @@ async def main():
         'path': "%s",
         'printBackground': True,
         'margin': {
-            'top': '10mm',
-            'bottom': '10mm',
-            'left': '10mm',
-            'right': '10mm'
+            'top': '20px',
+            'bottom': '20px',
+            'left': '20px',
+            'right': '20px'
         }
     })
     await browser.close()
@@ -325,124 +325,248 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 
 	reportCode := utils.MD5(strconv.Itoa(reportId))
 
-	pdfPath := `./static/` + reportCode + ".pdf"
-	jpegPath := `./static/` + reportCode + ".jpg"
+	// pc端
+	go func() {
+		pdfPath := `./static/` + reportCode + "_1200.pdf"
+		jpegPath := `./static/` + reportCode + "_1200.jpg"
 
-	width := 1200
-	//if reportType == 3 {
-	//	width = 800
-	//}
-	err = ReportToPdf(width, reportUrl, pdfPath)
-	if err != nil {
-		utils.FileLog.Info("ReportToPdf failed: , error: \n" + err.Error())
-		go alarm_msg.SendAlarmMsg("ReportToPdf failed:"+err.Error(), 3)
-	}
-
-	file, err := os.Open(pdfPath)
-	if err != nil {
-		utils.FileLog.Info("Open failed: , error: \n" + err.Error())
-		go alarm_msg.SendAlarmMsg("Open failed:"+err.Error(), 3)
-		return
-	}
+		width := 1200
+		//if reportType == 3 {
+		//	width = 800
+		//}
+		err = ReportToPdf(width, reportUrl, pdfPath)
+		if err != nil {
+			utils.FileLog.Info("ReportToPdf failed: , error: \n" + err.Error())
+			go alarm_msg.SendAlarmMsg("ReportToPdf failed:"+err.Error(), 3)
+		}
 
-	ext := path.Ext(file.Name())
+		file, err := os.Open(pdfPath)
+		if err != nil {
+			utils.FileLog.Info("Open failed: , error: \n" + err.Error())
+			go alarm_msg.SendAlarmMsg("Open failed:"+err.Error(), 3)
+			return
+		}
 
-	randStr := utils.GetRandStringNoSpecialChar(28)
-	fileName := randStr + ext
-	defer file.Close() //关闭上传文件
+		ext := path.Ext(file.Name())
 
-	resourceUrl := ``
-	ossClient := NewOssClient()
-	if ossClient == nil {
-		utils.FileLog.Info("初始化OSS服务失败")
-		return
-	}
-	resourceUrl, err = ossClient.UploadFile(fileName, pdfPath, "")
-	if err != nil {
-		utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
-		go alarm_msg.SendAlarmMsg("文件上传失败:"+err.Error(), 3)
-		return
-	}
-	defer func() {
-		_ = os.Remove(pdfPath)
-	}()
+		randStr := utils.GetRandStringNoSpecialChar(28)
+		fileName := randStr + ext
+		defer file.Close() //关闭上传文件
 
-	if reportType == 3 {
-		// 更新pdf url
-		ob := new(smart_report.SmartReport)
-		ob.SmartReportId = reportId
-		ob.DetailPdfUrl = resourceUrl
-		if err = ob.Update([]string{"DetailPdfUrl"}); err != nil {
-			utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+		resourceUrl := ``
+		ossClient := NewOssClient()
+		if ossClient == nil {
+			utils.FileLog.Info("初始化OSS服务失败")
 			return
 		}
-	} else if reportType == 2 {
-		err = models.ModifyEnglishReportPdfUrl(reportId, resourceUrl)
+		resourceUrl, err = ossClient.UploadFile(fileName, pdfPath, "")
 		if err != nil {
-			utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+			utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
+			go alarm_msg.SendAlarmMsg("文件上传失败:"+err.Error(), 3)
 			return
 		}
-	} else if reportType == 1 {
-		err = models.ModifyReportPdfUrl(reportId, resourceUrl)
+		defer func() {
+			_ = os.Remove(pdfPath)
+		}()
+
+		if reportType == 3 {
+			// 更新pdf url
+			ob := new(smart_report.SmartReport)
+			ob.SmartReportId = reportId
+			ob.DetailPdfUrl = resourceUrl
+			if err = ob.Update([]string{"DetailPdfUrl"}); err != nil {
+				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				return
+			}
+		} else if reportType == 2 {
+			err = models.ModifyEnglishReportPdfUrl(reportId, resourceUrl)
+			if err != nil {
+				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				return
+			}
+		} else if reportType == 1 {
+			err = models.ModifyReportPdfUrl(reportId, resourceUrl)
+			if err != nil {
+				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				return
+			}
+		}
+
+		time.Sleep(1 * time.Minute)
+
+		err = ReportToJpeg(width, reportUrl, jpegPath)
+		if err != nil {
+			utils.FileLog.Info("ReportToJpeg failed: , error: \n" + err.Error())
+		}
+		file, err = os.Open(jpegPath)
 		if err != nil {
-			utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+			utils.FileLog.Info("open file failed: , error: \n" + err.Error())
 			return
 		}
-	}
 
-	time.Sleep(1 * time.Minute)
+		ext = path.Ext(file.Name())
 
-	err = ReportToJpeg(width, reportUrl, jpegPath)
-	if err != nil {
-		utils.FileLog.Info("ReportToJpeg failed: , error: \n" + err.Error())
-	}
-	file, err = os.Open(jpegPath)
-	if err != nil {
-		utils.FileLog.Info("open file failed: , error: \n" + err.Error())
-		return
-	}
+		randStr = utils.GetRandStringNoSpecialChar(28)
+		fileName = randStr + ext
+		defer file.Close() //关闭上传文件
+
+		resourceUrl = ``
+		ossClient = NewOssClient()
+		if ossClient == nil {
+			utils.FileLog.Info("初始化OSS服务失败")
+			return
+		}
+		resourceUrl, err = ossClient.UploadFile(fileName, jpegPath, "")
+		if err != nil {
+			utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
+			return
+		}
+		defer func() {
+			_ = os.Remove(jpegPath)
+		}()
+
+		if reportType == 3 {
+			// 更新jpeg url
+			ob := new(smart_report.SmartReport)
+			ob.SmartReportId = reportId
+			ob.DetailImgUrl = resourceUrl
+			if err = ob.Update([]string{"DetailImgUrl"}); err != nil {
+				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				return
+			}
+		} else if reportType == 2 {
+			err = models.ModifyEnglishReportImgUrl(reportId, resourceUrl)
+			if err != nil {
+				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				return
+			}
+		} else if reportType == 1 {
+			err = models.ModifyReportImgUrl(reportId, resourceUrl)
+			if err != nil {
+				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				return
+			}
+		}
+	}()
 
-	ext = path.Ext(file.Name())
+	// 移动端
+	go func() {
+		pdfPathMobile := `./static/` + reportCode + "_600.pdf"
+		jpegPathMobile := `./static/` + reportCode + "_600.jpg"
 
-	randStr = utils.GetRandStringNoSpecialChar(28)
-	fileName = randStr + ext
-	defer file.Close() //关闭上传文件
+		width := 600
+		err = ReportToPdf(width, reportUrl, pdfPathMobile)
+		if err != nil {
+			utils.FileLog.Info("ReportToPdf failed: , error: \n" + err.Error())
+			go alarm_msg.SendAlarmMsg("ReportToPdf failed:"+err.Error(), 3)
+		}
 
-	resourceUrl = ``
-	ossClient = NewOssClient()
-	if ossClient == nil {
-		utils.FileLog.Info("初始化OSS服务失败")
-		return
-	}
-	resourceUrl, err = ossClient.UploadFile(fileName, jpegPath, "")
-	if err != nil {
-		utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
-		return
-	}
-	defer func() {
-		_ = os.Remove(jpegPath)
-	}()
+		file, err := os.Open(pdfPathMobile)
+		if err != nil {
+			utils.FileLog.Info("Open failed: , error: \n" + err.Error())
+			go alarm_msg.SendAlarmMsg("Open failed:"+err.Error(), 3)
+			return
+		}
+
+		ext := path.Ext(file.Name())
 
-	if reportType == 3 {
-		// 更新jpeg url
-		ob := new(smart_report.SmartReport)
-		ob.SmartReportId = reportId
-		ob.DetailImgUrl = resourceUrl
-		if err = ob.Update([]string{"DetailImgUrl"}); err != nil {
-			utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+		randStr := utils.GetRandStringNoSpecialChar(28)
+		fileName := randStr + ext
+		defer file.Close() //关闭上传文件
+
+		resourceUrl := ``
+		ossClient := NewOssClient()
+		if ossClient == nil {
+			utils.FileLog.Info("初始化OSS服务失败")
 			return
 		}
-	} else if reportType == 2 {
-		err = models.ModifyEnglishReportImgUrl(reportId, resourceUrl)
+		resourceUrl, err = ossClient.UploadFile(fileName, pdfPathMobile, "")
 		if err != nil {
-			utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+			utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
+			go alarm_msg.SendAlarmMsg("文件上传失败:"+err.Error(), 3)
 			return
 		}
-	} else if reportType == 1 {
-		err = models.ModifyReportImgUrl(reportId, resourceUrl)
+		defer func() {
+			_ = os.Remove(pdfPathMobile)
+		}()
+
+		if reportType == 3 {
+			// 更新pdf url
+			ob := new(smart_report.SmartReport)
+			ob.SmartReportId = reportId
+			ob.DetailPdfUrlMobile = resourceUrl
+			if err = ob.Update([]string{"DetailPdfUrlMobile"}); err != nil {
+				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				return
+			}
+		} else if reportType == 2 {
+			err = models.ModifyEnglishReportPdfUrlMobile(reportId, resourceUrl)
+			if err != nil {
+				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				return
+			}
+		} else if reportType == 1 {
+			err = models.ModifyReportPdfUrlMobile(reportId, resourceUrl)
+			if err != nil {
+				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				return
+			}
+		}
+
+		time.Sleep(1 * time.Minute)
+
+		err = ReportToJpeg(width, reportUrl, jpegPathMobile)
+		if err != nil {
+			utils.FileLog.Info("ReportToJpeg failed: , error: \n" + err.Error())
+		}
+		file, err = os.Open(jpegPathMobile)
 		if err != nil {
-			utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+			utils.FileLog.Info("open file failed: , error: \n" + err.Error())
 			return
 		}
-	}
+
+		ext = path.Ext(file.Name())
+
+		randStr = utils.GetRandStringNoSpecialChar(28)
+		fileName = randStr + ext
+		defer file.Close() //关闭上传文件
+
+		resourceUrl = ``
+		ossClient = NewOssClient()
+		if ossClient == nil {
+			utils.FileLog.Info("初始化OSS服务失败")
+			return
+		}
+		resourceUrl, err = ossClient.UploadFile(fileName, jpegPathMobile, "")
+		if err != nil {
+			utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
+			return
+		}
+		defer func() {
+			_ = os.Remove(jpegPathMobile)
+		}()
+
+		if reportType == 3 {
+			// 更新jpeg url
+			ob := new(smart_report.SmartReport)
+			ob.SmartReportId = reportId
+			ob.DetailImgUrlMobile = resourceUrl
+			if err = ob.Update([]string{"DetailImgUrlMobile"}); err != nil {
+				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				return
+			}
+		} else if reportType == 2 {
+			err = models.ModifyEnglishReportImgUrlMobile(reportId, resourceUrl)
+			if err != nil {
+				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				return
+			}
+		} else if reportType == 1 {
+			err = models.ModifyReportImgUrlMobile(reportId, resourceUrl)
+			if err != nil {
+				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				return
+			}
+		}
+	}()
 }

+ 43 - 0
services/system.go

@@ -1,6 +1,7 @@
 package services
 
 import (
+	"eta/eta_api/global"
 	"eta/eta_api/models/company"
 	"eta/eta_api/models/roadshow"
 	"eta/eta_api/models/system"
@@ -120,3 +121,45 @@ func GetMenuTreeRecursive(list []*system.SysMenuItem, parentId int) []*system.Sy
 	}
 	return res
 }
+
+
+type GroupNode struct{
+	GroupId int
+	GroupName string
+	Child []*GroupNode
+}
+
+func BuildGroupTree(list []*system.SysFullGroup,current,depth int,node *GroupNode) {
+	if current == depth {
+		utils.FileLog.Info("到达组织最深层,停止递归")
+		return
+	}
+	for _, v := range list {
+		if v.ParentId == node.GroupId {
+			subNode:= &GroupNode{
+				GroupId: v.GroupId,
+				GroupName: v.GroupName,
+				Child: make([]*GroupNode, 0),
+			}
+			node.Child = append(node.Child, subNode)
+			BuildGroupTree(list, current+1, depth, subNode)
+		}
+	}
+}
+
+func DeleteSysGroupByIds(ids []int)(err error) {
+	tx:=global.DbMap[utils.DbNameMaster].Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+		}else{
+			tx.Commit()
+		}
+	}()
+ err=system.DeleteSysGroupByIds(tx,ids)
+ if err!=nil{
+	 return
+ }
+ err=system.ClearSysUserGroupByIds(tx,ids)
+ return
+}

+ 83 - 5
services/task.go

@@ -1,8 +1,14 @@
 package services
 
 import (
+	"encoding/json"
+	"eta/eta_api/cache"
 	"eta/eta_api/models"
+	"eta/eta_api/models/rag"
+	"eta/eta_api/services/binlog"
 	"eta/eta_api/services/data"
+	edbmonitor "eta/eta_api/services/edb_monitor"
+	"eta/eta_api/services/llm"
 	"eta/eta_api/utils"
 	"fmt"
 	"strings"
@@ -50,14 +56,23 @@ func Task() {
 
 	// TODO:监听这里需要找下达梦的方案,主流程先跑起来再处理
 	// 监听binlog
-	if utils.MYSQL_DATA_BINLOG_URL != "" {
-		//go binlog.ListenMysql()
+	if utils.MYSQL_DATA_BINLOG_URL != "" && utils.DbDriverName == `mysql` {
+		go binlog.ListenMysql()
+
+		go edbmonitor.HandleEdbMonitorEdbInfo()
+
+		// 监听数据源binlog写入es
+		go binlog.HandleDataSourceChange2Es()
 	}
+	go StartSessionManager()
+
+	go llm.SaveAllChatRecordsToDB()
 
-	//go edbmonitor.HandleEdbMonitorEdbInfo()
+	// 定时任务进行微信文章操作
+	go HandleWechatArticleOp()
 
-	// 监听数据源binlog写入es
-	//go binlog.HandleDataSourceChange2Es()
+	// 定时任务进行微信文章LLM操作
+	go HandleWechatArticleLLmOp()
 
 	// TODO:数据修复
 	//FixNewEs()
@@ -567,3 +582,66 @@ func ModifyEsEnglishReport() {
 //
 //	return rnd.Float64()*11000 - 1000
 //}
+
+// HandleSearchByWechatOp
+// @Description: 处理微信爬虫
+func HandleWechatArticleOp() {
+	defer func() {
+		if err := recover(); err != nil {
+			fmt.Println("[HandleWechatArticleOp]", err)
+		}
+	}()
+	obj := rag.WechatPlatform{}
+	for {
+		utils.Rc.Brpop(utils.CACHE_WECHAT_PLATFORM_ARTICLE, func(b []byte) {
+			wechatArticleOp := new(cache.WechatArticleOp)
+			if err := json.Unmarshal(b, &wechatArticleOp); err != nil {
+				fmt.Println("json unmarshal wrong!")
+				return
+			}
+			item, tmpErr := obj.GetById(wechatArticleOp.WechatPlatformId)
+			if tmpErr != nil {
+				// 找不到就处理失败
+				return
+			}
+
+			switch wechatArticleOp.Source {
+			case `add`:
+				AddWechatPlatform(item)
+			case `refresh`:
+				BeachAddWechatArticle(item, 2)
+
+			}
+		})
+	}
+}
+
+// HandleWechatArticleLLmOp
+// @Description: 处理微信文章加入知识库
+func HandleWechatArticleLLmOp() {
+	defer func() {
+		if err := recover(); err != nil {
+			fmt.Println("[HandleWechatArticleLLmOp]", err)
+		}
+	}()
+	obj := rag.WechatArticle{}
+	for {
+		utils.Rc.Brpop(utils.CACHE_WECHAT_PLATFORM_ARTICLE_KNOWLEDGE, func(b []byte) {
+			wechatArticleOp := new(cache.WechatArticleOp)
+			if err := json.Unmarshal(b, &wechatArticleOp); err != nil {
+				fmt.Println("json unmarshal wrong!")
+				return
+			}
+			item, tmpErr := obj.GetById(wechatArticleOp.WechatPlatformId)
+			if tmpErr != nil {
+				// 找不到就处理失败
+				return
+			}
+
+			// 文章加入到知识库
+			ArticleToKnowledge(item)
+			// 生成摘要
+			//GenerateArticleAbstract(item)
+		})
+	}
+}

+ 1036 - 0
services/wechat_platform.go

@@ -0,0 +1,1036 @@
+package services
+
+import (
+	"bytes"
+	"eta/eta_api/cache"
+	"eta/eta_api/models"
+	"eta/eta_api/models/rag"
+	"eta/eta_api/services/elastic"
+	"eta/eta_api/services/llm"
+	"eta/eta_api/utils"
+	"eta/eta_api/utils/llm/eta_llm/eta_llm_http"
+	"fmt"
+	html2 "golang.org/x/net/html"
+	"html"
+	"os"
+	"path"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// AddWechatPlatform
+// @Description: 添加新的公众号
+// @param item
+func AddWechatPlatform(item *rag.WechatPlatform) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("公众号入库后查找最新记录失败,err:%v", err)
+		}
+	}()
+	if item.FakeId != `` {
+		return
+	}
+
+	if item.ArticleLink == `` {
+		return
+	}
+
+	articleLink := item.ArticleLink
+
+	articleDetail, err := llm.SearchByWechatArticle(item.ArticleLink)
+	if err != nil {
+		return
+	}
+
+	if articleDetail.Appuin == `` {
+		err = fmt.Errorf("文章内未匹配到公众号唯一标识")
+		return
+	}
+
+	wechatPlatform := new(rag.WechatPlatform)
+	// 查找是否存在这个公众号id的
+	wechatPlatformInfo, tmpErr := wechatPlatform.GetByFakeID(articleDetail.Appuin)
+	if tmpErr != nil && !utils.IsErrNoRow(tmpErr) {
+		err = tmpErr
+		return
+	}
+	if tmpErr == nil {
+		// 如果找到了,那么需要将当前的给移除掉
+		err = item.Del()
+		if err != nil {
+			return
+		}
+
+		// 并将查出来的微信公众号摘出来的数据重新赋值
+		item = wechatPlatformInfo
+
+	} else if utils.IsErrNoRow(tmpErr) {
+		// 如果没找到,那么就变更当前的信息
+		item.FakeId = articleDetail.Appuin
+		item.Nickname = articleDetail.Nickname
+		//item.Alias = req.Alias
+		item.RoundHeadImg = articleDetail.RoundHeadImg
+		//item.ServiceType = req.ServiceType
+		item.Signature = articleDetail.ProfileSignature
+		//item.Verified = verified
+		item.ModifyTime = time.Now()
+
+		err = item.Update([]string{rag.WechatPlatformColumns.FakeID, rag.WechatPlatformColumns.Nickname, rag.WechatPlatformColumns.RoundHeadImg, rag.WechatPlatformColumns.Signature, rag.WechatPlatformColumns.ModifyTime})
+		if err != nil {
+			return
+		}
+
+		// 修改公众号头像
+		go replaceWechatPlatformPic(item)
+	}
+
+	// 把刚搜索的文章加入到文章库中
+	AddWechatArticle(item, articleLink, articleDetail, nil)
+
+	BeachAddWechatArticle(item, 10)
+	fmt.Println("公众号入库完成")
+
+	return
+}
+
+// AddWechatArticle
+// @Description: 添加公众号文章入库
+// @author: Roc
+// @datetime 2025-03-05 13:24:14
+// @param item *rag.WechatPlatform
+// @param link string
+// @param articleDetail WechatArticleDataResp
+func AddWechatArticle(item *rag.WechatPlatform, articleLink string, articleDetail llm.WechatArticleDataResp, articleMenu *llm.ArticleMenu) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("公众号文章入库失败,文章链接:%s ,err:%v", articleLink, err)
+		}
+	}()
+	obj := new(rag.WechatArticle)
+
+	_, err = obj.GetByLink(articleLink)
+	if err == nil {
+		// 文章已经入库了,不需要重复入库
+		return
+	}
+
+	// 如果不是 ErrNoRow 的时候,那么就是查询数据库出问题了,需要直接返回
+	if !utils.IsErrNoRow(err) {
+		return
+	}
+
+	// 这个时候,说明数据库中没有这个文章,那么需要文章入库
+	err = nil
+
+	var publishAt time.Time
+	if articleDetail.CreateAt != `` {
+		createAtInt, tmpErr := strconv.Atoi(articleDetail.CreateAt)
+		if tmpErr == nil {
+			publishAt = time.Unix(int64(createAtInt), 1000)
+		}
+	} else if articleMenu != nil {
+		publishAt = time.Unix(int64(articleMenu.UpdateTime), 1000)
+	}
+
+	content := articleDetail.HtmlContent
+	// 图片下载下来到本地,如果成功了,那么就用新的
+	tmpContent, err := ReplaceHtmlImg(content)
+	if tmpContent != `` {
+		content = tmpContent
+	}
+
+	obj = &rag.WechatArticle{
+		WechatArticleId:  0,
+		WechatPlatformId: item.WechatPlatformId,
+		FakeId:           item.FakeId,
+		Title:            articleDetail.Title,
+		Link:             articleLink,
+		CoverUrl:         articleDetail.CoverUrl,
+		Description:      articleDetail.Desc,
+		Content:          html.EscapeString(content),
+		TextContent:      articleDetail.TextContent,
+		Country:          articleDetail.CountryName,
+		Province:         articleDetail.ProvinceName,
+		City:             articleDetail.CityName,
+		//Abstract:          "",
+		//ArticleCreateTime: createAt,
+		ModifyTime: time.Now(),
+		CreateTime: time.Now(),
+	}
+	if !publishAt.IsZero() {
+		obj.ArticleCreateTime = publishAt
+	}
+
+	if articleMenu != nil {
+		obj.Title = articleMenu.Title
+		//obj.Link = articleMenu.Link
+		obj.CoverUrl = articleMenu.Cover
+		obj.Description = articleMenu.Digest
+	}
+	err = obj.Create()
+
+	// 修改文章封面图
+	go replaceWechatArticleCoverPic(obj)
+
+	// 文章入库成功后,需要将相关信息入摘要库
+	go cache.AddWechatArticleLlmOpToCache(obj.WechatArticleId, ``)
+
+}
+
+// BeachAddWechatArticle
+// @Description: 批量添加公众号文章
+// @param item
+// @param num
+// @return err
+func BeachAddWechatArticle(item *rag.WechatPlatform, num int) {
+	var err error
+	defer func() {
+		//fmt.Println("公众号文章批量入库完成")
+		if err != nil {
+			utils.FileLog.Error("公众号文章批量入库失败,err:%v", err)
+			fmt.Println("公众号文章批量入库失败,err:", err)
+		}
+	}()
+	if item.FakeId == `` {
+		return
+	}
+
+	wechatArticleObj := new(rag.WechatArticle)
+
+	// 获取公众号的文章列表
+	articleListResp, err := llm.SearchByWechatArticleList(item.FakeId, num)
+	if err != nil {
+		return
+	}
+	for _, articleMenu := range articleListResp.List {
+		// 判断文章是否已经入库,如果已经入库了,那么就过滤,不去重复查询微信了
+		_, err = wechatArticleObj.GetByLink(articleMenu.Link)
+		if err == nil {
+			// 文章已经入库了,不需要重复入库
+			continue
+		}
+		if !utils.IsErrNoRow(err) {
+			return
+		}
+		err = nil
+
+		articleDetail, tmpErr := llm.SearchByWechatArticle(articleMenu.Link)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		// 把刚搜索的文章加入到指标库
+		AddWechatArticle(item, articleMenu.Link, articleDetail, &articleMenu)
+
+		//time.Sleep(10 * time.Second)
+
+		// 随机休眠,至少大于10s
+		sleepTimeInt := utils.GetRandInt(10, 20)
+		if sleepTimeInt < 10 {
+			sleepTimeInt = 10
+		}
+		time.Sleep(time.Duration(sleepTimeInt) * time.Second)
+	}
+	return
+}
+
+// GenerateArticleAbstract
+// @Description: 文章摘要生成
+// @author: Roc
+// @datetime 2025-03-10 16:17:53
+// @param item *rag.WechatArticle
+func GenerateArticleAbstract(item *rag.WechatArticle) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("文章转临时文件失败,err:%v", err)
+			fmt.Println("文章转临时文件失败,err:", err)
+		}
+	}()
+
+	// 内容为空,那就不需要生成摘要
+	if item.TextContent == `` {
+		return
+	}
+
+	abstractObj := rag.WechatArticleAbstract{}
+	tmpAbstractItem, err := abstractObj.GetByWechatArticleId(item.WechatArticleId)
+	if err == nil {
+		// 摘要已经生成,不需要重复生成
+		AbstractToKnowledge(item, tmpAbstractItem, false)
+
+		return
+	}
+	if !utils.IsErrNoRow(err) {
+		return
+	}
+
+	// 生成临时文件
+	dateDir := time.Now().Format("20060102")
+	uploadDir := utils.STATIC_DIR + "ai/" + dateDir
+	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
+	if err != nil {
+		err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
+		return
+	}
+	randStr := utils.GetRandStringNoSpecialChar(28)
+	fileName := randStr + `.md`
+	tmpFilePath := uploadDir + "/" + fileName
+	err = utils.SaveToFile(item.TextContent, tmpFilePath)
+	if err != nil {
+		err = fmt.Errorf("生成临时文件失败,Err:" + err.Error())
+		return
+	}
+	defer func() {
+		os.Remove(tmpFilePath)
+	}()
+
+	// 上传临时文件到LLM
+	tmpFileResp, err := llm.UploadTempDocs(tmpFilePath)
+	if err != nil {
+		err = fmt.Errorf("上传临时文件到LLM失败,Err:" + err.Error())
+		return
+	}
+
+	if tmpFileResp.Data.Id == `` {
+		err = fmt.Errorf("上传临时文件到LLM失败,Err:上传失败")
+		return
+	}
+	tmpDocId := tmpFileResp.Data.Id
+
+	//tmpDocId := `c4d2ee902808408c8b8ed398b33be103` // 钢材
+	//tmpDocId := `2dde8afe62d24525a814e74e0a5e35e4` // 钢材
+	//tmpDocId := `7634cc1086c04b3687682220a2cf1a48` //
+
+	//开始对话
+	abstract, addArticleChatRecordList, tmpErr := getAnswerByContent(item.WechatArticleId, tmpDocId)
+	if tmpErr != nil {
+		err = fmt.Errorf("LLM对话失败,Err:" + tmpErr.Error())
+		return
+	}
+
+	// 添加问答记录
+	if len(addArticleChatRecordList) > 0 {
+		recordObj := rag.WechatArticleChatRecord{}
+		err = recordObj.CreateInBatches(addArticleChatRecordList)
+		if err != nil {
+			return
+		}
+	}
+
+	if abstract != `` {
+		if abstract == `sorry` || strings.Index(abstract, `根据已知信息无法回答该问题`) == 0 {
+			item.AbstractStatus = 2
+			item.ModifyTime = time.Now()
+			err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+			return
+		}
+		item.AbstractStatus = 1
+		item.ModifyTime = time.Now()
+		err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+
+		abstractItem := &rag.WechatArticleAbstract{
+			WechatArticleAbstractId: 0,
+			WechatArticleId:         item.WechatArticleId,
+			Content:                 abstract,
+			Version:                 0,
+			VectorKey:               "",
+			ModifyTime:              time.Now(),
+			CreateTime:              time.Now(),
+		}
+		err = abstractItem.Create()
+		if err != nil {
+			return
+		}
+
+		// 数据入ES库
+		go AddOrEditEsWechatArticleAbstract(abstractItem.WechatArticleAbstractId)
+
+		AbstractToKnowledge(item, abstractItem, false)
+	}
+}
+
+// ReGenerateArticleAbstract
+// @Description: 文章摘要重新生成
+// @author: Roc
+// @datetime 2025-03-10 16:17:53
+// @param item *rag.WechatArticle
+func ReGenerateArticleAbstract(item *rag.WechatArticle) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("文章转临时文件失败,err:%v", err)
+			fmt.Println("文章转临时文件失败,err:", err)
+		}
+	}()
+
+	abstractObj := rag.WechatArticleAbstract{}
+	abstractItem, err := abstractObj.GetByWechatArticleId(item.WechatArticleId)
+	if err != nil {
+		if utils.IsErrNoRow(err) {
+			// 直接生成
+			GenerateArticleAbstract(item)
+			return
+		}
+		// 异常了
+		return
+	}
+
+	// 生成临时文件
+	dateDir := time.Now().Format("20060102")
+	uploadDir := utils.STATIC_DIR + "ai/" + dateDir
+	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
+	if err != nil {
+		err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
+		return
+	}
+	randStr := utils.GetRandStringNoSpecialChar(28)
+	fileName := randStr + `.md`
+	tmpFilePath := uploadDir + "/" + fileName
+	err = utils.SaveToFile(item.TextContent, tmpFilePath)
+	if err != nil {
+		err = fmt.Errorf("生成临时文件失败,Err:" + err.Error())
+		return
+	}
+	defer func() {
+		os.Remove(tmpFilePath)
+	}()
+
+	// 上传临时文件到LLM
+	tmpFileResp, err := llm.UploadTempDocs(tmpFilePath)
+	if err != nil {
+		err = fmt.Errorf("上传临时文件到LLM失败,Err:" + err.Error())
+		return
+	}
+
+	if tmpFileResp.Data.Id == `` {
+		err = fmt.Errorf("上传临时文件到LLM失败,Err:上传失败")
+		return
+	}
+	tmpDocId := tmpFileResp.Data.Id
+
+	//tmpDocId := `c4d2ee902808408c8b8ed398b33be103` // 钢材
+	//tmpDocId := `2dde8afe62d24525a814e74e0a5e35e4` // 钢材
+	//tmpDocId := `7634cc1086c04b3687682220a2cf1a48` //
+
+	//开始对话
+	abstract, addArticleChatRecordList, tmpErr := getAnswerByContent(item.WechatArticleId, tmpDocId)
+	if tmpErr != nil {
+		err = fmt.Errorf("LLM对话失败,Err:" + tmpErr.Error())
+		return
+	}
+
+	// 添加问答记录
+	if len(addArticleChatRecordList) > 0 {
+		recordObj := rag.WechatArticleChatRecord{}
+		err = recordObj.CreateInBatches(addArticleChatRecordList)
+		if err != nil {
+			return
+		}
+	}
+
+	if abstract != `` {
+		if abstract == `sorry` || strings.Index(abstract, `根据已知信息无法回答该问题`) == 0 {
+			item.AbstractStatus = 2
+			item.ModifyTime = time.Now()
+			err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+			return
+		}
+		item.AbstractStatus = 1
+		item.ModifyTime = time.Now()
+		err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+
+		abstractItem.Content = abstract
+		abstractItem.Version = abstractObj.Version + 1
+		abstractItem.ModifyTime = time.Now()
+		err = abstractItem.Update([]string{"content", "version", "modify_time"})
+		if err != nil {
+			return
+		}
+
+		AbstractToKnowledge(item, abstractItem, true)
+	}
+}
+
+// DelDoc
+// @Description: 删除摘要向量库
+// @author: Roc
+// @datetime 2025-03-12 16:55:05
+// @param wechatArticleAbstractList []*rag.WechatArticleAbstract
+// @return err error
+func DelDoc(wechatArticleAbstractList []*rag.WechatArticleAbstract) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("删除摘要向量库文件失败,err:%v", err)
+			fmt.Println("删除摘要向量库文件失败,err:", err)
+		}
+	}()
+
+	vectorKeyList := make([]string, 0)
+	wechatArticleAbstractIdList := make([]int, 0)
+
+	for _, v := range wechatArticleAbstractList {
+		if v.VectorKey == `` {
+			continue
+		}
+		vectorKeyList = append(vectorKeyList, v.VectorKey)
+		wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
+	}
+
+	// 没有就不删除
+	if len(vectorKeyList) <= 0 {
+		return
+	}
+
+	_, err = llm.DelDocsToKnowledge(models.BusinessConfMap[models.KnowledgeBaseName], vectorKeyList)
+	if err != nil {
+		err = fmt.Errorf("删除LLM摘要向量库文件失败,Err:" + err.Error())
+		return
+	}
+	//fmt.Println(resp)
+	obj := rag.WechatArticleAbstract{}
+	err = obj.DelVectorKey(wechatArticleAbstractIdList)
+
+	return
+}
+
+// DelLlmDoc
+// @Description: 删除摘要向量库
+// @author: Roc
+// @datetime 2025-03-12 16:55:05
+// @param wechatArticleAbstractList []*rag.WechatArticleAbstract
+// @return err error
+func DelLlmDoc(vectorKeyList []string, wechatArticleAbstractIdList []int) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("删除摘要向量库文件失败,err:%v", err)
+			fmt.Println("删除摘要向量库文件失败,err:", err)
+		}
+	}()
+
+	// 没有就不删除
+	if len(vectorKeyList) <= 0 {
+		return
+	}
+
+	_, err = llm.DelDocsToKnowledge(models.BusinessConfMap[models.KnowledgeBaseName], vectorKeyList)
+	if err != nil {
+		err = fmt.Errorf("删除LLM摘要向量库文件失败,Err:" + err.Error())
+		return
+	}
+	//fmt.Println(resp)
+	obj := rag.WechatArticleAbstract{}
+	err = obj.DelVectorKey(wechatArticleAbstractIdList)
+
+	return
+}
+
+func getAnswerByContent(wechatArticleId int, docId string) (answer string, addArticleChatRecordList []*rag.WechatArticleChatRecord, err error) {
+	historyList := make([]eta_llm_http.HistoryContent, 0)
+	addArticleChatRecordList = make([]*rag.WechatArticleChatRecord, 0)
+
+	questionObj := rag.Question{}
+	questionList, err := questionObj.GetListByCondition(``, []interface{}{}, 0, 100)
+	if err != nil {
+		err = fmt.Errorf("获取问题列表失败,Err:" + err.Error())
+		return
+	}
+
+	// 没问题就不生成了
+	if len(questionList) <= 0 {
+		return
+	}
+
+	//你现在是一名资深的期货行业分析师,请基于以下的问题进行汇总总结,如果不能正常总结出来,那么就只需要回复我:sorry
+	questionStrList := []string{`你现在是一名资深的期货行业分析师,请基于以下的问题进行汇总总结,如果不能正常总结出来,那么就只需要回复我:sorry。以下是问题:`}
+	for _, v := range questionList {
+		questionStrList = append(questionStrList, v.QuestionContent)
+	}
+	questionStr := strings.Join(questionStrList, "\n")
+
+	originalAnswer, result, err := llm.ChatByFile(docId, questionStr, historyList)
+	fmt.Println(result)
+	if err != nil {
+		err = fmt.Errorf("LLM对话失败,Err:" + err.Error())
+		return
+	}
+
+	// 提取 </think> 后面的内容
+	thinkEndIndex := strings.Index(result.Answer, "</think>")
+	if thinkEndIndex != -1 {
+		answer = strings.TrimSpace(result.Answer[thinkEndIndex+len("</think>"):])
+	} else {
+		answer = result.Answer
+	}
+
+	answer = strings.TrimSpace(answer)
+
+	// 待入库的数据
+	addArticleChatRecordList = append(addArticleChatRecordList, &rag.WechatArticleChatRecord{
+		WechatArticleChatRecordId: 0,
+		WechatArticleId:           wechatArticleId,
+		ChatUserType:              "user",
+		Content:                   questionStr,
+		SendTime:                  time.Now(),
+		CreatedTime:               time.Now(),
+		UpdateTime:                time.Now(),
+	}, &rag.WechatArticleChatRecord{
+		WechatArticleChatRecordId: 0,
+		WechatArticleId:           wechatArticleId,
+		ChatUserType:              "assistant",
+		Content:                   originalAnswer,
+		SendTime:                  time.Now(),
+		CreatedTime:               time.Now(),
+		UpdateTime:                time.Now(),
+	})
+
+	return
+}
+
+// ArticleToKnowledge
+// @Description: 原文入向量库
+// @author: Roc
+// @datetime 2025-03-10 16:13:16
+// @param item *rag.WechatArticle
+func ArticleToKnowledge(item *rag.WechatArticle) {
+	if item.TextContent == `` {
+		return
+	}
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("上传文章原文到知识库失败,err:%v", err)
+			fmt.Println("上传文章原文到知识库失败,err:", err)
+		}
+	}()
+
+	// 生成临时文件
+	//dateDir := time.Now().Format("20060102")
+	//uploadDir := utils.STATIC_DIR + "ai/article/" + dateDir
+	uploadDir := utils.STATIC_DIR + "ai/article"
+	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
+	if err != nil {
+		err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
+		return
+	}
+	fileName := utils.RemoveSpecialChars(item.Title) + `.md`
+	tmpFilePath := uploadDir + "/" + fileName
+	err = utils.SaveToFile(item.TextContent, tmpFilePath)
+	if err != nil {
+		err = fmt.Errorf("生成临时文件失败,Err:" + err.Error())
+		return
+	}
+	defer func() {
+		os.Remove(tmpFilePath)
+	}()
+
+	knowledgeArticleName := models.BusinessConfMap[models.KnowledgeArticleName]
+	// 上传临时文件到LLM
+	uploadFileResp, err := llm.UploadDocsToKnowledge(tmpFilePath, knowledgeArticleName)
+	if err != nil {
+		err = fmt.Errorf("上传文章原文到知识库失败,Err:" + err.Error())
+		return
+	}
+
+	if len(uploadFileResp.FailedFiles) > 0 {
+		for _, v := range uploadFileResp.FailedFiles {
+			err = fmt.Errorf("上传文章原文到知识库失败,Err:" + v)
+		}
+	}
+
+	item.VectorKey = tmpFilePath
+	item.ModifyTime = time.Now()
+	err = item.Update([]string{"vector_key", "modify_time"})
+
+}
+
+// AbstractToKnowledge
+// @Description: 摘要入向量库
+// @author: Roc
+// @datetime 2025-03-10 16:14:59
+// @param wechatArticleItem *rag.WechatArticle
+// @param abstractItem *rag.WechatArticleAbstract
+func AbstractToKnowledge(wechatArticleItem *rag.WechatArticle, abstractItem *rag.WechatArticleAbstract, isReUpload bool) {
+	if abstractItem.Content == `` {
+		return
+	}
+	// 已经生成了,那就不处理了
+	if abstractItem.VectorKey != `` && !isReUpload {
+		return
+	}
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("摘要入向量库失败,err:%v", err)
+			fmt.Println("摘要入向量库失败,err:", err)
+		}
+
+		// 数据入ES库
+		go AddOrEditEsWechatArticleAbstract(abstractItem.WechatArticleAbstractId)
+	}()
+
+	// 生成临时文件
+	//dateDir := time.Now().Format("20060102")
+	//uploadDir := utils.STATIC_DIR + "ai/article/" + dateDir
+	uploadDir := utils.STATIC_DIR + "ai/abstract"
+	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
+	if err != nil {
+		err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
+		return
+	}
+	fileName := utils.RemoveSpecialChars(wechatArticleItem.Title) + `.md`
+	tmpFilePath := uploadDir + "/" + fileName
+	err = utils.SaveToFile(abstractItem.Content, tmpFilePath)
+	if err != nil {
+		err = fmt.Errorf("生成临时文件失败,Err:" + err.Error())
+		return
+	}
+	defer func() {
+		os.Remove(tmpFilePath)
+	}()
+
+	knowledgeArticleName := models.BusinessConfMap[models.KnowledgeBaseName]
+	// 上传临时文件到LLM
+	uploadFileResp, err := llm.UploadDocsToKnowledge(tmpFilePath, knowledgeArticleName)
+	if err != nil {
+		err = fmt.Errorf("上传文章原文到知识库失败,Err:" + err.Error())
+		return
+	}
+
+	if len(uploadFileResp.FailedFiles) > 0 {
+		for _, v := range uploadFileResp.FailedFiles {
+			err = fmt.Errorf("上传文章原文到知识库失败,Err:" + v)
+		}
+	}
+
+	abstractItem.VectorKey = tmpFilePath
+	abstractItem.ModifyTime = time.Now()
+	err = abstractItem.Update([]string{"vector_key", "modify_time"})
+
+}
+
+// replaceWechatPlatformPic
+// @Description: 替换公众号头像
+// @author: Roc
+// @datetime 2025-03-11 09:38:24
+// @param item *rag.WechatPlatform
+func replaceWechatPlatformPic(item *rag.WechatPlatform) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("替换公众号头像失败,err:%v", err)
+			fmt.Println("替换公众号头像失败,err:", err)
+		}
+	}()
+	if item.RoundHeadImg == `` {
+		return
+	}
+	resourceUrl, err := downloadWxPicAndUploadToOss(item.RoundHeadImg, `head_img`)
+	if err != nil {
+		return
+	}
+	item.RoundHeadImg = resourceUrl
+	err = item.Update([]string{"round_head_img"})
+
+}
+
+// replaceWechatArticleCoverPic
+// @Description: 替换文章封面图
+// @author: Roc
+// @datetime 2025-03-11 09:38:35
+// @param item *rag.WechatArticle
+func replaceWechatArticleCoverPic(item *rag.WechatArticle) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("替换公众号头像失败,err:%v", err)
+			fmt.Println("替换公众号头像失败,err:", err)
+		}
+
+		// 数据入ES库
+		AddOrEditEsWechatArticle(item.WechatArticleId)
+	}()
+	if item.CoverUrl == `` {
+		return
+	}
+	resourceUrl, err := downloadWxPicAndUploadToOss(item.CoverUrl, `cover_url`)
+	if err != nil {
+		return
+	}
+	item.CoverUrl = resourceUrl
+	err = item.Update([]string{"cover_url"})
+
+}
+
+// replaceWechatArticlePic
+// @Description: 替换文章内容图
+// @author: Roc
+// @datetime 2025-03-11 09:38:35
+// @param item *rag.WechatArticle
+func ReplaceWechatArticlePic(item *rag.WechatArticle) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("替换公众号头像失败,err:%v", err)
+			fmt.Println("替换公众号头像失败,err:", err)
+		}
+	}()
+	if item.Content == `` {
+		return
+	}
+
+	content, err := ReplaceHtmlImg(html.UnescapeString(item.Content))
+	if err != nil {
+		return
+	}
+	item.Content = html.EscapeString(content)
+	err = item.Update([]string{"content"})
+
+	return
+}
+
+// downloadWxPicAndUploadToOss
+// @Description: 下载微信图片并上传到OSS
+// @author: Roc
+// @datetime 2025-03-11 09:28:49
+// @param wxPicUrl string
+// @return resourceUrl string
+// @return err error
+func downloadWxPicAndUploadToOss(wxPicUrl, source string) (resourceUrl string, err error) {
+	localFilePath, err := utils.DownloadWxImage(wxPicUrl)
+	if err != nil {
+		return
+	}
+	defer func() {
+		os.Remove(localFilePath)
+	}()
+	ossClient := NewOssClient()
+	if ossClient == nil {
+		err = fmt.Errorf(`初始化OSS服务失败`)
+		return
+	}
+	ext := path.Ext(localFilePath)
+	fileName := fmt.Sprintf(`%s%s%s`, time.Now().Format(utils.FormatShortDateTimeUnSpace), utils.GetRandStringNoSpecialChar(16), ext)
+	//savePath := utils.UploadDir + `wx/wx_article/` + time.Now().Format("200601/20060102/") + fileName
+	savePath := fmt.Sprintf(`%swx/%s/%s%s`, utils.UploadDir, source, time.Now().Format("200601/20060102/"), fileName)
+	resourceUrl, err = ossClient.UploadFile(fileName, localFilePath, savePath)
+	if err != nil {
+		err = fmt.Errorf("文件上传失败,Err:" + err.Error())
+		return
+	}
+
+	return
+
+}
+
+// ReplaceHtmlImg
+// @Description: 将html中的图片替换成自己的
+// @author: Roc
+// @datetime 2025-03-11 14:32:00
+// @param htmlStr string
+// @return newHtml string
+// @return err error
+func ReplaceHtmlImg(htmlStr string) (newHtml string, err error) {
+	doc, err := html2.Parse(strings.NewReader(htmlStr))
+	if err != nil {
+		return
+	}
+	if err != nil {
+		return
+	}
+	handleNode(doc)
+
+	// 将处理后的HTML节点重新渲染为HTML字符串
+	var buf bytes.Buffer
+	if err = html2.Render(&buf, doc); err != nil {
+		fmt.Println(err)
+		return
+	}
+	newHtml = buf.String()
+
+	return
+}
+
+// handleNode
+// @Description: html节点处理
+// @author: Roc
+// @datetime 2025-03-11 14:32:45
+// @param n *html2.Node
+func handleNode(n *html2.Node) {
+	if n.Type == html2.ElementNode {
+		if n.Data == "img" {
+			for k, attr := range n.Attr {
+				// 新增代码:如果标签是img且存在data-src属性,则将data-src的值赋给src
+				if n.Data == "img" && attr.Key == "src" {
+					resourceUrl, tmpErr := downloadWxPicAndUploadToOss(attr.Val, `article`)
+					if tmpErr != nil {
+						continue
+					}
+					attr.Val = resourceUrl
+				}
+				n.Attr[k] = attr
+			}
+		}
+
+	}
+	for c := n.FirstChild; c != nil; c = c.NextSibling {
+		handleNode(c)
+	}
+}
+
+// AddOrEditEsWechatPlatformId
+// @Description: 批量处理某个公众号下的文章到ES
+// @author: Roc
+// @datetime 2025-03-13 11:01:28
+// @param articleId int
+func AddOrEditEsWechatPlatformId(wechatPlatformId int) {
+	if utils.EsWechatArticleName == `` {
+		return
+	}
+	obj := rag.WechatArticle{}
+	list, _ := obj.GetListByCondition(` wechat_article_id `, ` AND wechat_platform_id = ? `, []interface{}{wechatPlatformId}, 0, 1000000)
+	for _, item := range list {
+		AddOrEditEsWechatArticle(item.WechatArticleId)
+	}
+}
+
+// AddOrEditEsWechatArticle
+// @Description: 新增/编辑微信文章入ES
+// @author: Roc
+// @datetime 2025-03-13 11:01:28
+// @param articleId int
+func AddOrEditEsWechatArticle(articleId int) {
+	if utils.EsWechatArticleName == `` {
+		return
+	}
+
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("添加公众号微信信息到ES失败,err:%v", err)
+			fmt.Println("添加公众号微信信息到ES失败,err:", err)
+		}
+	}()
+	obj := rag.WechatArticle{}
+	articleInfo, err := obj.GetById(articleId)
+	if err != nil {
+		err = fmt.Errorf("获取公众号文章信息失败,Err:" + err.Error())
+		return
+	}
+	platformObj := rag.WechatPlatform{}
+	platformInfo, err := platformObj.GetById(articleInfo.WechatPlatformId)
+	if err != nil {
+		err = fmt.Errorf("获取公众号平台信息失败,Err:" + err.Error())
+		return
+	}
+
+	esItem := elastic.WechatArticleAndPlatform{
+		WechatArticleId:  articleInfo.WechatArticleId,
+		WechatPlatformId: articleInfo.WechatPlatformId,
+		FakeId:           articleInfo.FakeId,
+		Title:            articleInfo.Title,
+		Link:             articleInfo.Link,
+		CoverUrl:         articleInfo.CoverUrl,
+		Description:      articleInfo.Description,
+		//Content:          articleInfo.Content,
+		//TextContent: articleInfo.TextContent,
+		//AbstractStatus:          articleInfo.AbstractStatus,
+		Country:           articleInfo.Country,
+		Province:          articleInfo.Province,
+		City:              articleInfo.City,
+		ArticleCreateTime: articleInfo.ArticleCreateTime,
+		IsDeleted:         articleInfo.IsDeleted,
+		ModifyTime:        articleInfo.ModifyTime,
+		CreateTime:        articleInfo.CreateTime,
+		Nickname:          platformInfo.Nickname,
+		Alias:             platformInfo.Alias,
+		RoundHeadImg:      platformInfo.RoundHeadImg,
+	}
+
+	err = elastic.WechatArticleEsAddOrEdit(strconv.Itoa(articleInfo.WechatArticleId), esItem)
+}
+
+// AddOrEditEsWechatArticleAbstract
+// @Description: 新增/编辑微信文章摘要入ES
+// @author: Roc
+// @datetime 2025-03-13 14:13:47
+// @param articleAbstractId int
+func AddOrEditEsWechatArticleAbstract(articleAbstractId int) {
+	if utils.EsWechatArticleAbstractName == `` {
+		return
+	}
+
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("添加公众号微信信息到ES失败,err:%v", err)
+			fmt.Println("添加公众号微信信息到ES失败,err:", err)
+		}
+	}()
+	obj := rag.WechatArticleAbstract{}
+	abstractInfo, err := obj.GetById(articleAbstractId)
+	if err != nil {
+		err = fmt.Errorf("获取公众号文章信息失败,Err:" + err.Error())
+		return
+	}
+	articleObj := rag.WechatArticle{}
+	articleInfo, err := articleObj.GetById(abstractInfo.WechatArticleId)
+	if err != nil {
+		err = fmt.Errorf("获取公众号文章信息失败,Err:" + err.Error())
+		return
+	}
+
+	// 公众号平台关联的标签品种
+	tagObj := rag.WechatPlatformTagMapping{}
+	tagMappingList, err := tagObj.GetListByCondition(` AND wechat_platform_id = ? `, []interface{}{articleInfo.WechatPlatformId}, 0, 10000)
+	if err != nil {
+		err = fmt.Errorf("获取公众号平台关联的品种信息失败,Err:" + err.Error())
+		return
+	}
+
+	tagIdList := make([]int, 0)
+	for _, v := range tagMappingList {
+		tagIdList = append(tagIdList, v.TagId)
+	}
+
+	esItem := elastic.WechatArticleAbstractItem{
+		WechatArticleAbstractId: abstractInfo.WechatArticleAbstractId,
+		WechatArticleId:         abstractInfo.WechatArticleId,
+		WechatPlatformId:        articleInfo.WechatPlatformId,
+		Abstract:                abstractInfo.Content,
+		Version:                 abstractInfo.Version,
+		VectorKey:               abstractInfo.VectorKey,
+		ModifyTime:              articleInfo.ModifyTime,
+		CreateTime:              articleInfo.CreateTime,
+		Title:                   articleInfo.Title,
+		Link:                    articleInfo.Link,
+		TagIdList:               tagIdList,
+	}
+
+	err = elastic.WechatArticleAbstractEsAddOrEdit(strconv.Itoa(articleAbstractId), esItem)
+}
+
+// AddOrEditEsWechatArticleAbstract
+// @Description: 新增/编辑微信文章摘要入ES
+// @author: Roc
+// @datetime 2025-03-13 14:13:47
+// @param articleAbstractId int
+func DelEsWechatArticleAbstract(articleAbstractId int) {
+	if utils.EsWechatArticleAbstractName == `` {
+		return
+	}
+
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("添加公众号微信信息到ES失败,err:%v", err)
+			fmt.Println("添加公众号微信信息到ES失败,err:", err)
+		}
+	}()
+
+	err = elastic.WechatArticleAbstractEsDel(strconv.Itoa(articleAbstractId))
+}

+ 134 - 0
services/ws_service.go

@@ -0,0 +1,134 @@
+package services
+
+import (
+	"eta/eta_api/utils/ws"
+)
+
+var ()
+
+//func WsAuthenticate() web.FilterFunc {
+//	return func(ctx *context.Context) {
+//		method := ctx.Input.Method()
+//		uri := ctx.Input.URI()
+//		if method == "GET" {
+//			authorization := ctx.Input.Header("authorization")
+//			if authorization == "" {
+//				authorization = ctx.Input.Header("Authorization")
+//			}
+//			if strings.Contains(authorization, ";") {
+//				authorization = strings.Replace(authorization, ";", "$", 1)
+//			}
+//			if authorization == "" {
+//				strArr := strings.Split(uri, "?")
+//				for k, v := range strArr {
+//					fmt.Println(k, v)
+//				}
+//				if len(strArr) > 1 {
+//					authorization = strArr[1]
+//					authorization = strings.Replace(authorization, "Authorization", "authorization", -1)
+//				}
+//			}
+//			if authorization == "" {
+//				utils.FileLog.Error("authorization为空,未授权")
+//				ctx.ResponseWriter.WriteHeader(http.StatusUnauthorized)
+//				return
+//			}
+//			tokenStr := authorization
+//			tokenArr := strings.Split(tokenStr, "=")
+//			token := tokenArr[1]
+//
+//			session, err := system.GetSysSessionByToken(token)
+//			if err != nil {
+//				if utils.IsErrNoRow(err) {
+//					utils.FileLog.Error("authorization已过期")
+//					ctx.ResponseWriter.WriteHeader(http.StatusUnauthorized)
+//					return
+//				}
+//				utils.FileLog.Error("authorization查询用户信息失败")
+//				ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
+//				return
+//			}
+//			if session == nil {
+//				utils.FileLog.Error("会话不存在")
+//				ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
+//				return
+//			}
+//			//校验token是否合法
+//			// JWT校验Token和Account
+//			account := utils.MD5(session.UserName)
+//			if !utils.CheckToken(account, token) {
+//				utils.FileLog.Error("authorization校验不合法")
+//				ctx.ResponseWriter.WriteHeader(http.StatusUnauthorized)
+//				return
+//			}
+//			if time.Now().After(session.ExpiredTime) {
+//				utils.FileLog.Error("authorization过期法")
+//				ctx.ResponseWriter.WriteHeader(http.StatusUnauthorized)
+//				return
+//			}
+//			admin, err := system.GetSysUserById(session.SysUserId)
+//			if err != nil {
+//				if utils.IsErrNoRow(err) {
+//					utils.FileLog.Error("权限不够")
+//					ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
+//					return
+//				}
+//				utils.FileLog.Error("获取用户信息失败")
+//				ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
+//				return
+//			}
+//			if admin == nil {
+//				utils.FileLog.Error("权限不够")
+//				ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
+//				return
+//			}
+//			//如果不是启用状态
+//			if admin.Enabled != 1 {
+//				utils.FileLog.Error("用户被禁用")
+//				ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
+//				return
+//			}
+//
+//			//接口权限校验
+//			roleId := admin.RoleId
+//			list, e := system.GetMenuButtonApisByRoleId(roleId)
+//			if e != nil {
+//				utils.FileLog.Error("接口权限查询出错", e)
+//				ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
+//				return
+//			}
+//			var api string
+//			for _, v := range list {
+//				if v.Api != "" {
+//					api += v.Api + "&"
+//				}
+//			}
+//			api += "&" + models.BusinessConfMap["PublicApi"]
+//			//处理uri请求,去除前缀和参数
+//			api = strings.TrimRight(api, "&")
+//			uri = strings.Replace(uri, "/adminapi", "", 1)
+//			uris := strings.Split(uri, "?")
+//			uri = uris[0]
+//			//fmt.Println("uri:", uri)
+//			apis := strings.Split(api, "&")
+//			apiMap := make(map[string]bool, 0)
+//			for _, s := range apis {
+//				apiMap[s] = true
+//			}
+//			if !apiMap[uri] {
+//				utils.FileLog.Error("用户无权访问")
+//				ctx.ResponseWriter.WriteHeader(http.StatusForbidden)
+//				return
+//			}
+//			ctx.Input.SetData("admin", admin)
+//		} else {
+//			utils.FileLog.Error("请求方法类型错误")
+//			ctx.ResponseWriter.WriteHeader(http.StatusBadRequest)
+//			return
+//		}
+//	}
+//}
+
+func StartSessionManager() {
+	ws.GetInstance().Start()
+}

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