kobe6258 hace 3 días
padre
commit
5babaa0413
Se han modificado 99 ficheros con 8384 adiciones y 1065 borrados
  1. 84 8
      cache/llm.go
  2. 2 0
      controllers/bi_dashboard.go
  3. 12 40
      controllers/data_manage/chart_info.go
  4. 47 8
      controllers/data_manage/edb_info.go
  5. 394 0
      controllers/data_manage/edb_inspection.go
  6. 96 0
      controllers/data_manage/edb_inspection_message.go
  7. 9 3
      controllers/data_manage/excel/excel_info.go
  8. 9 5
      controllers/data_manage/gpr_risk_data.go
  9. 11 5
      controllers/data_manage/purang_data.go
  10. 65 12
      controllers/data_stat/edb_source_stat.go
  11. 238 7
      controllers/data_stat/edb_terminal.go
  12. 13 8
      controllers/edb_monitor/edb_monitor_message.go
  13. 1 0
      controllers/eta_forum/eta_forum.go
  14. 24 3
      controllers/fe_calendar/fe_calendar_matter.go
  15. 206 212
      controllers/llm/abstract.go
  16. 47 0
      controllers/llm/kb_controller.go
  17. 6 1
      controllers/llm/llm_http/response.go
  18. 367 27
      controllers/llm/question.go
  19. 422 0
      controllers/llm/rag_eta_report_abstract.go
  20. 85 1
      controllers/llm/report.go
  21. 0 2
      controllers/llm/user_chat_controller.go
  22. 84 0
      controllers/llm/wechat_platform.go
  23. 13 4
      controllers/material/material.go
  24. 19 2
      controllers/message.go
  25. 104 17
      controllers/report_chapter.go
  26. 198 48
      controllers/report_v2.go
  27. 6 1
      controllers/sandbox/sandbox.go
  28. 7 0
      controllers/sys_role.go
  29. 15 0
      controllers/sys_user.go
  30. 12 62
      controllers/user_login.go
  31. 38 0
      global/websocket.go
  32. 1 0
      models/bi_dashboard/bi_dashboard.go
  33. 1 0
      models/bi_dashboard/bi_dashboard_detail.go
  34. 5 0
      models/business_conf.go
  35. 7 0
      models/chart_permission.go
  36. 12 12
      models/data_manage/base_from_gpr_risk.go
  37. 55 55
      models/data_manage/base_from_purang.go
  38. 176 0
      models/data_manage/edb_inspection/edb_inspection_config.go
  39. 112 0
      models/data_manage/edb_inspection/edb_inspection_dashboard.go
  40. 114 0
      models/data_manage/edb_inspection/edb_inspection_date_config.go
  41. 170 0
      models/data_manage/edb_inspection/edb_inspection_message.go
  42. 112 0
      models/data_manage/edb_inspection/edb_inspection_record.go
  43. 117 6
      models/data_manage/edb_terminal.go
  44. 2 0
      models/data_manage/mysteel_chemical_index.go
  45. 1 0
      models/data_stat/edb_info_update_stat.go
  46. 4 0
      models/manual_edb.go
  47. 5 2
      models/material/material_classify.go
  48. 7 0
      models/message.go
  49. 164 0
      models/rag/ai_task.go
  50. 115 0
      models/rag/ai_task_record.go
  51. 114 0
      models/rag/article_abstract_history.go
  52. 19 4
      models/rag/question.go
  53. 86 0
      models/rag/question_history.go
  54. 5 5
      models/rag/rag_eta_report.go
  55. 292 0
      models/rag/rag_eta_report_abstract.go
  56. 9 0
      models/rag/request/rag_eta_report.go
  57. 1 1
      models/rag/request/wechat_platform.go
  58. 15 0
      models/rag/response/abstract.go
  59. 5 0
      models/rag/response/question.go
  60. 23 1
      models/rag/tag.go
  61. 82 12
      models/rag/wechat_article_abstract.go
  62. 58 6
      models/report.go
  63. 266 0
      models/report/report_free_layout.go
  64. 19 11
      models/report_chapter.go
  65. 25 1
      models/report_v2.go
  66. 216 0
      routers/commentsRouter.go
  67. 3 0
      routers/router.go
  68. 1 0
      services/crm_eta.go
  69. 90 7
      services/data/chart_info.go
  70. 397 0
      services/data/edb_inspection.go
  71. 114 0
      services/data/edb_inspection_message.go
  72. 37 30
      services/data/predict_edb_info_rule.go
  73. 35 39
      services/edb_monitor/edb_monitor_message.go
  74. 317 0
      services/elastic/rag_eta_report_abstract.go
  75. 7 1
      services/elastic/rag_question.go
  76. 16 2
      services/elastic/wechat_article_abstract.go
  77. 334 0
      services/llm.go
  78. 9 5
      services/llm/facade/llm_service.go
  79. 517 0
      services/llm_report.go
  80. 6 2
      services/material/material.go
  81. 3 0
      services/report_chapter.go
  82. 2 1
      services/report_rai.go
  83. 117 16
      services/report_v2.go
  84. 124 106
      services/smart_report.go
  85. 291 24
      services/task.go
  86. 211 0
      services/websocket_msg.go
  87. 506 173
      services/wechat_platform.go
  88. BIN
      static/wind指标刷新失败处理.pdf
  89. BIN
      static/同花顺指标API方式刷新失败处理.pdf
  90. BIN
      static/钢联指标API对接刷新失败处理.pdf
  91. BIN
      static/钢联指标终端对接刷新失败处理.pdf
  92. 2 1
      utils/config.go
  93. 15 1
      utils/constants.go
  94. 34 23
      utils/llm/eta_llm/eta_llm_client.go
  95. 2 0
      utils/redis.go
  96. 31 0
      utils/redis/cluster_redis.go
  97. 30 0
      utils/redis/standalone_redis.go
  98. 24 19
      utils/ws/session.go
  99. 60 23
      utils/ws/session_manager.go

+ 84 - 8
cache/llm.go

@@ -5,13 +5,15 @@ import (
 	"fmt"
 )
 
-type WechatArticleOp struct {
+// WechatPlatformOp
+// @Description: 微信公众号操作请求
+type WechatPlatformOp struct {
 	Source           string
 	WechatPlatformId int
 }
 
 // AddWechatArticleOpToCache
-// @Description: 将公众号文章操作加入缓存
+// @Description: 将公众号操作加入缓存
 // @param wechatPlatformId
 // @param source
 // @return bool
@@ -21,7 +23,7 @@ func AddWechatArticleOpToCache(wechatPlatformId int, source string) bool {
 		return true
 	}
 
-	record := new(WechatArticleOp)
+	record := new(WechatPlatformOp)
 	record.Source = source
 	record.WechatPlatformId = wechatPlatformId
 	if utils.Re == nil {
@@ -36,23 +38,32 @@ func AddWechatArticleOpToCache(wechatPlatformId int, source string) bool {
 	return false
 }
 
+// WechatArticleOp
+// @Description: 微信公众号文章操作
+type WechatArticleOp struct {
+	Source          string
+	WechatArticleId int
+	QuestionId      int
+}
+
 // AddWechatArticleLlmOpToCache
 // @Description: 将公众号文章llm操作加入缓存
 // @param wechatPlatformId
 // @param source
 // @return bool
-func AddWechatArticleLlmOpToCache(wechatPlatformId int, source string) bool {
+func AddWechatArticleLlmOpToCache(wechatArticleId, questionId int, source string) bool {
 	// 如果不在发布和调试模式,那么就不加入缓存
 	if !utils.InArrayByStr([]string{utils.BusinessCodeRelease, utils.BusinessCodeDebug}, utils.BusinessCode) {
 		return true
 	}
 	record := new(WechatArticleOp)
 	record.Source = source
-	record.WechatPlatformId = wechatPlatformId
+	record.WechatArticleId = wechatArticleId
+	record.QuestionId = questionId
 	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))
+		utils.FileLog.Info(fmt.Sprintf("将公众号文章llm操作加入缓存 加入缓存 AddWechatArticleLlmOpToCache LPush: 操作类型:%s,公众号稳扎id:%d", source, wechatArticleId))
 		if err != nil {
 			fmt.Println("AddWechatArticleOpToCache LPush Err:" + err.Error())
 		}
@@ -61,7 +72,7 @@ func AddWechatArticleLlmOpToCache(wechatPlatformId int, source string) bool {
 	return false
 }
 
-type RagEtaReportOpOp struct {
+type RagEtaReportOp struct {
 	Source          string
 	ReportId        int
 	ReportChapterId int
@@ -76,7 +87,7 @@ type RagEtaReportOpOp struct {
 // @param source string
 // @return bool
 func RagEtaReportOpToCache(reportId, reportChapterId int, source string) bool {
-	record := new(RagEtaReportOpOp)
+	record := new(RagEtaReportOp)
 	record.Source = source
 	record.ReportId = reportId
 	record.ReportChapterId = reportChapterId
@@ -91,3 +102,68 @@ func RagEtaReportOpToCache(reportId, reportChapterId int, source string) bool {
 	}
 	return false
 }
+
+// RagEtaReportLlmOp
+// @Description:
+type RagEtaReportLlmOp struct {
+	RagEtaReportId int
+	QuestionId     int
+	ForceGenerate  bool
+}
+
+// AddRagEtaReportLlmOpToCache
+// @Description: 将ETA报告llm操作加入缓存
+// @author: Roc
+// @datetime 2025-04-24 13:59:16
+// @param ragEtaReportId int
+// @param questionId int
+// @return bool
+func AddRagEtaReportLlmOpToCache(ragEtaReportId, questionId int, forceGenerate bool) bool {
+	// 如果不在发布和调试模式,那么就不加入缓存
+	if !utils.InArrayByStr([]string{utils.BusinessCodeRelease, utils.BusinessCodeDebug}, utils.BusinessCode) {
+		return true
+	}
+	record := new(RagEtaReportLlmOp)
+	record.RagEtaReportId = ragEtaReportId
+	record.QuestionId = questionId
+	record.ForceGenerate = forceGenerate
+	if utils.Re != nil {
+		return false
+	}
+
+	err := utils.Rc.LPush(utils.CACHE_ETA_REPORT_KNOWLEDGE_LLM, record)
+	utils.FileLog.Info(fmt.Sprintf("将eta报告llm操作加入缓存 加入缓存 RagEtaReportLlmOpToCache LPush: ETA报告id:%d", ragEtaReportId))
+	if err != nil {
+		fmt.Println("RagEtaReportLlmOpToCache LPush Err:" + err.Error())
+	}
+	return true
+}
+
+type AiTaskRecordOp struct {
+	AiTaskRecordId int
+}
+
+// AddAiTaskRecordOpToCache
+// @Description: AI任务操作调度入队列
+// @author: Roc
+// @datetime 2025-04-24 09:41:11
+// @param aiTaskRecordId int
+// @return bool
+func AddAiTaskRecordOpToCache(aiTaskRecordId int) bool {
+	// 如果不在发布和调试模式,那么就不加入缓存
+	if !utils.InArrayByStr([]string{utils.BusinessCodeRelease, utils.BusinessCodeDebug}, utils.BusinessCode) {
+		return true
+	}
+	record := new(AiTaskRecordOp)
+	record.AiTaskRecordId = aiTaskRecordId
+	if utils.Re == nil {
+		err := utils.Rc.LPush(utils.CACHE_AI_ARTICLE_ABSTRACT_LLM_TASK, record)
+
+		utils.FileLog.Info(fmt.Sprintf("将AI任务操作调度入队列 加入缓存 AddAiTaskRecordOpToCache LPush: 记录id:%d", aiTaskRecordId))
+		if err != nil {
+			fmt.Println("AddAiTaskRecordOpToCache LPush Err:" + err.Error())
+		}
+		return true
+	}
+	return false
+}

+ 2 - 0
controllers/bi_dashboard.go

@@ -103,6 +103,7 @@ func (this *BIDaShboardController) AddDashboard() {
 			BiDashboardId: int(id),
 			Type:          v.Type,
 			UniqueCode:    v.UniqueCode,
+			Conf:          v.Conf,
 			Sort:          i + 1,
 			CreateTime:    time.Now(),
 			ModifyTime:    time.Now(),
@@ -176,6 +177,7 @@ func (this *BIDaShboardController) EditDashboard() {
 			BiDashboardId: req.BiDashboardId,
 			Type:          v.Type,
 			UniqueCode:    v.UniqueCode,
+			Conf:          v.Conf,
 			Sort:          v.Sort,
 			CreateTime:    time.Now(),
 			ModifyTime:    time.Now(),

+ 12 - 40
controllers/data_manage/chart_info.go

@@ -1456,47 +1456,19 @@ func (this *ChartInfoController) PreviewChartInfoDetail() {
 			br.ErrMsg = "标识线配置异常" + err.Error()
 			return
 		}
-		for i := range markerLines {
-			if markerLines[i].EdbType == 0 && markerLines[i].TimeIntervalType == 0 && markerLines[i].Axis != 3 {
-				// 图上第一个指标且时间区间跟随图表
-				if markerLines[i].MarkLineType == 2 {
-					if edbList[0].IsAxis == 1 {
-						value, err := data.MarkerLineCalculate(markerLines[i], edbList[0].DataList, chartInfo)
-						if err != nil {
-							br.Msg = "标识线配置异常"
-							br.ErrMsg = "标识线配置异常" + err.Error()
-							return
-						}
-						markerLines[i].Value = value
-					} else {
-						// 其他的都走指标计算
-						edbInfo, err := data_manage.GetEdbInfoById(markerLines[i].EdbInfoId)
-						if err != nil {
-							br.Msg = "指标计算标识线获取指标信息异常"
-							br.ErrMsg = "指标计算标识线获取指标信息异常" + err.Error()
-							return
-						}
-						// 判断时间区间不为跟随图表的情况
-						if markerLines[i].TimeIntervalType != 0 {
-							startDate = markerLines[i].StartDate.Date
-							endDate = markerLines[i].EndDate.Date
-						}
-						dataList, err := data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, startDate, endDate)
-						if err != nil {
-							br.Msg = "指标计算标识线获取指标数据异常"
-							br.ErrMsg = "指标计算标识线获取指标数据异常" + err.Error()
-							return
-						}
-						value, err := data.MarkerLineCalculate(markerLines[i], dataList, chartInfo)
-						if err != nil {
-							br.Msg = "标识线配置异常"
-							br.ErrMsg = "标识线配置异常" + err.Error()
-							return
-						}
-						markerLines[i].Value = value
-					}
+		for i, markerLine := range markerLines {
+			switch markerLine.MarkLineType { //1:固定 2:指标计算
+			case 2:
+				tmpMarkerLine, tmpErr := data.GetMarkerLine(markerLine, edbList, chartInfo, startDate, endDate)
+				if tmpErr != nil {
+					br.Msg = "标识线配置异常"
+					br.ErrMsg = "标识线配置异常," + tmpErr.Error()
+					return
 				}
+				markerLine = tmpMarkerLine
 			}
+
+			markerLines[i] = markerLine
 		}
 
 		markerLineStr, err := json.Marshal(markerLines)
@@ -3581,7 +3553,7 @@ func (this *ChartInfoController) PreviewBarChartInfo() {
 						value, err := data.MarkerLineCalculate(markerLines[i], edbList[0].DataList, chartInfo)
 						if err != nil {
 							br.Msg = "标识线配置异常"
-							br.ErrMsg = "标识线配置异常" + err.Error()
+							br.ErrMsg = "标识线配置异常," + err.Error()
 							return
 						}
 						markerLines[i].Value = value

+ 47 - 8
controllers/data_manage/edb_info.go

@@ -5669,6 +5669,47 @@ func (this *EdbInfoController) EdbChartList() {
 					}
 				}
 			}
+			var wg sync.WaitGroup
+			wg.Add(2)
+			var relationMap =make(map[int]*data_manage.BaseRelationEdbInfo,len(list))
+			var relationList []*data_manage.BaseRelationEdbInfo
+			relationChan := make(chan RelationResult, 2)
+			var edbInfoIds []int
+			var glEdbInfoInfoIds []int
+			for _, v := range list {
+				if v.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+					glEdbInfoInfoIds = append(glEdbInfoInfoIds, v.EdbInfoId)
+				} else {
+					edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+				}
+
+			}
+			// 抽象为一个函数减少重复逻辑
+			fetchRelations := func(ids []int, source int) {
+				defer wg.Done()
+				_, relList, getErr := data.GetEdbRelationListByIds(ids, source)
+				relationChan <- RelationResult{
+					List: relList,
+					Err:  getErr,
+				}
+			}
+			go fetchRelations(glEdbInfoInfoIds, utils.DATA_SOURCE_MYSTEEL_CHEMICAL)
+			go fetchRelations(edbInfoIds, -1)
+			wg.Wait()
+			close(relationChan)
+
+			for i := 0; i < 2; i++ {
+				result := <-relationChan
+				if result.Err != nil && !utils.IsErrNoRow(result.Err) {
+					br.Msg = "获取失败"
+					br.ErrMsg = "获取数据引用信息失败,Err:" + e.Error()
+					return
+				}
+				relationList = append(relationList, result.List...)
+			}
+			for _, v := range relationList {
+				relationMap[v.EdbInfoId] = v
+			}
 
 			for _, v := range list {
 				if currClassify, ok := classifyMap[v.ClassifyId]; ok {
@@ -5683,14 +5724,7 @@ func (this *EdbInfoController) EdbChartList() {
 						v.IsSupplierStop = 1
 					}
 				}
-				var relation *data_manage.BaseRelationEdbInfo
-				relation, err = data.GetEdbRelationListById(v.EdbInfoId, v.Source)
-				if err != nil && !utils.IsErrNoRow(err) {
-					br.Msg = "获取失败"
-					br.ErrMsg = "获取数据引用信息失败,Err:" + e.Error()
-					return
-				}
-				if relation != nil && relation.RelationNum > 0 {
+				if relationMap[v.EdbInfoId] != nil && relationMap[v.EdbInfoId].RelationNum > 0 {
 					v.IsRelation = true
 				}
 			}
@@ -5708,6 +5742,11 @@ func (this *EdbInfoController) EdbChartList() {
 	br.Data = resp
 }
 
+type RelationResult struct {
+	List []*data_manage.BaseRelationEdbInfo
+	Err  error
+}
+
 // Modify
 // @Title 修改指标信息接口
 // @Description 编辑指标接口

+ 394 - 0
controllers/data_manage/edb_inspection.go

@@ -0,0 +1,394 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage/edb_inspection"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"time"
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+type EdbInspectionController struct {
+	controllers.BaseAuthController
+}
+
+// InspectionSourceList
+// @Title 获取巡检配置的来源接口
+// @Description 获取巡检配置的来源接口
+// @Success Ret=200 获取成功
+// @router /edb_inspection/source_list [get]
+func (c *EdbInspectionController) InspectionSourceList() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	// 这里可以添加获取巡检来源的逻辑
+	// 目前暂时返回空列表
+	list := make([]interface{}, 0)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// InspectionConfigList
+// @Title 获取巡检配置列表接口
+// @Description 获取巡检配置列表接口
+// @Param   Source   query   int  true       "来源"
+// @Param   TerminalCode   query   string  false       "终端编码"
+// @Success Ret=200 获取成功
+// @router /edb_inspection/config/list [get]
+func (c *EdbInspectionController) InspectionConfigList() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	source, _ := c.GetInt("Source")
+	terminalCode := c.GetString("TerminalCode")
+
+	list, err, errMsg, isSendEmail := data.GetConfigList(source, terminalCode)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// SaveInspectionConfig
+// @Title 设置巡检配置接口
+// @Description 设置巡检配置接口
+// @Param	request	body edb_inspection.EdbInspectionConfigAddReq true "type json string"
+// @Success Ret=200 保存成功
+// @router /edb_inspection/config/save [post]
+func (c *EdbInspectionController) SaveInspectionConfig() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	var req edb_inspection.EdbInspectionConfigAddReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	// 保存
+	err, errMsg, isSendEmail := data.SaveEdbInspectionConfig(&req)
+	if err != nil {
+		br.Msg = errMsg
+		br.ErrMsg = "保存失败,Err:" + err.Error()
+		br.IsSendEmail = isSendEmail
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "保存成功"
+}
+
+// UpdateInspectionConfigStatus
+// @Title 更新巡检配置状态接口
+// @Description 更新巡检配置状态接口
+// @Param   ConfigId   query   int64  true       "配置ID"
+// @Param   Status   query   int8  true       "状态"
+// @Success Ret=200 更新成功
+// @router /edb_inspection/config/status/update [post]
+func (c *EdbInspectionController) UpdateInspectionConfigStatus() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req edb_inspection.EdbInspectionConfigStatusReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	configId := req.ConfigId
+	status := req.Status
+	if status != 1 && status != 0 {
+		br.Msg = "状态错误"
+		br.ErrMsg = "状态错误,请输入1或0"
+		br.IsSendEmail = false
+		return
+	}
+
+	if configId <= 0 {
+		br.Msg = "配置ID不能为空"
+		br.IsSendEmail = false
+		return
+	}
+
+	config := &edb_inspection.EdbInspectionConfig{
+		ConfigId: configId,
+	}
+
+	err = config.UpdateStatus(status)
+	if err != nil {
+		br.Msg = "更新失败"
+		br.ErrMsg = "更新失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "更新成功"
+}
+
+// DeleteInspectionConfig
+// @Title 删除巡检配置接口
+// @Description 删除巡检配置接口
+// @Param   ConfigId   query   int64  true       "配置ID"
+// @Success Ret=200 删除成功
+// @router /edb_inspection/config/delete [post]
+func (c *EdbInspectionController) DeleteInspectionConfig() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	var req edb_inspection.EdbInspectionConfigDeleteReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	configId := req.ConfigId
+	if configId <= 0 {
+		br.Msg = "配置ID不能为空"
+		br.IsSendEmail = false
+		return
+	}
+
+	config := &edb_inspection.EdbInspectionConfig{
+		ConfigId: configId,
+	}
+
+	err = config.Delete()
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	_ = edb_inspection.DeleteEdbInspectionDateConfigByConfigId(configId)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "删除成功"
+}
+
+// GetInspectionConfigDetail
+// @Title 获取巡检配置详情接口
+// @Description 获取巡检配置详情接口
+// @Param   ConfigId   query   int64  true       "配置ID"
+// @Success Ret=200 获取成功
+// @router /edb_inspection/config/detail [get]
+func (c *EdbInspectionController) GetInspectionConfigDetail() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	configId, _ := c.GetInt64("ConfigId")
+
+	if configId <= 0 {
+		br.Msg = "配置ID不能为空"
+		br.IsSendEmail = false
+		return
+	}
+
+	detail, err := data.GetConfigDetail(configId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = detail
+} 
+
+// 查询看板列表
+// @Title 查询看板列表接口
+// @Description 查询看板列表接口
+// @Success Ret=200 获取成功
+// @router /edb_inspection/dashboard [get]
+func (c *EdbInspectionController) GetDashboardList() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	list, err := edb_inspection.GetDashboardList()
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = list
+}
+
+// 查询看板详情
+// @Title 查询看板详情接口
+// @Description 查询看板详情接口
+// @Success Ret=200 获取成功
+// @router /edb_inspection/record [get]
+func (c *EdbInspectionController) GetInspectionRecordDetail() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	// 根据source和terminalCode查询巡检记录
+	source, _ := c.GetInt("Source")
+	terminalCode := c.GetString("TerminalCode")
+	startDate := c.GetString("StartDate")
+	endDate := c.GetString("EndDate")
+
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize := utils.StartIndex(currentIndex, pageSize)
+
+	condition := ``
+	var pars []interface{}
+
+	if source > 0 {
+		condition += " AND r.source = ?"
+		pars = append(pars, source)
+	}
+	if terminalCode != "" {
+		condition += " AND r.terminal_code = ?"
+		pars = append(pars, terminalCode)
+	}
+	if startDate != "" {
+		// 检查是否是时间格式
+		_, err := time.Parse(utils.FormatDate, startDate)
+		if err != nil {
+			br.Msg = "开始时间格式错误"
+			br.ErrMsg = "开始时间格式错误,请输入正确的时间格式"
+			return
+		}
+		condition += " AND r.inspection_time >= ?"
+		pars = append(pars, startDate)
+	}
+	if endDate != "" {
+		// 检查是否是时间格式
+		_, err := time.Parse(utils.FormatDate, endDate)
+		if err != nil {
+			br.Msg = "结束时间格式错误"
+			br.ErrMsg = "结束时间格式错误,请输入正确的时间格式"
+			return
+		}
+		endTime := endDate + " 23:59:59"
+		condition += " AND r.inspection_time <= ?"
+		pars = append(pars, endTime)
+	}
+
+	list, err := edb_inspection.GetInspectionRecordListByCondition(condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	// 查询总数
+	count, err := edb_inspection.GetInspectionRecordCountByCondition(condition, pars)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, int(count))
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = map[string]interface{}{
+		"List": list,
+		"Paging": page,
+	}
+	
+}
+
+// HelpWordDownload
+// @Title 下载错误处理文档
+// @Description 下载错误处理文档
+// @Success 200 {object} models.EdbdataClassifyResp
+// @Param   Source   query   int  false       "来源:1:同花顺;2:wind;34:钢联"
+// @Param   IsApi   query   int  false       "是否api:1:是;0:否"
+// @router /edb_inspection/help_word [get]
+func (c *EdbInspectionController) HelpWordDownload() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	source, _ := c.GetInt("Source")
+	isApi, _ := c.GetInt("IsApi")
+	switch source {
+	case utils.DATA_SOURCE_THS:
+		if isApi == 1 {
+			c.Ctx.Output.Download("./static/同花顺指标API方式刷新失败处理.pdf", "同花顺指标API方式刷新失败处理.pdf")
+		}
+	case utils.DATA_SOURCE_WIND:
+		c.Ctx.Output.Download("./static/wind指标刷新失败处理.pdf", "wind指标刷新失败处理.pdf")
+	case utils.DATA_SOURCE_MYSTEEL_CHEMICAL:
+		if isApi == 1 {
+			c.Ctx.Output.Download("./static/钢联指标API对接刷新失败处理.pdf", "钢联指标API对接刷新失败处理.pdf")
+		}else {
+			c.Ctx.Output.Download("./static/钢联指标终端对接刷新失败处理.pdf", "钢联指标终端对接刷新失败处理.pdf")
+		}
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "下载成功"
+}

+ 96 - 0
controllers/data_manage/edb_inspection_message.go

@@ -0,0 +1,96 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_api/controllers"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage/edb_inspection"
+	"eta/eta_api/services/data"
+)
+
+type EdbInspectionMessageController struct {
+	controllers.BaseAuthController
+}
+
+
+// List
+// @Title 巡检消息列表
+// @Description 巡检消息列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Success 200 {object} response.EdbInspectionMessageListResp
+// @router /edb_inspection/message/list [get]
+func (c *EdbInspectionMessageController) 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"
+		br.Ret = 408
+		return
+	}
+
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")	
+	resp, err := data.GetInspectionMessageList(sysUser.AdminId, currentIndex, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = err.Error()
+		return
+	}
+
+	br.Data = resp
+	br.Msg = "获取成功"
+	br.Ret = 200
+	br.Success = true
+}
+
+// Read
+// @Title 巡检消息已读
+// @Description 巡检消息已读
+// @Param   request body request.EdbInspectionMessageReadReq  true  "消息ID"
+// @Success 200 {object} models.BaseResponse
+// @router /edb_inspection/message/read [post]
+func (m *EdbInspectionMessageController) Read() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		m.Data["json"] = br
+		m.ServeJSON()
+	}()
+
+	sysUser := m.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	var req edb_inspection.EdbInspectionMessageReadReq
+	if err := json.Unmarshal(m.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,err:" + err.Error()
+		return
+	}
+	if req.MessageId <= 0 {
+		br.Msg = "参数错误"
+		return
+	}
+
+	msg, err := data.ReadEdbInspectionMessage(req.MessageId, sysUser.AdminId)
+	if err != nil {
+		if msg == "" {
+			msg = "系统错误"
+		}
+		br.Msg = msg
+		br.ErrMsg = "读取消息失败,err:" + err.Error()
+		return
+	}
+
+	br.Msg = "已读成功"
+	br.Ret = 200
+	br.Success = true
+}

+ 9 - 3
controllers/data_manage/excel/excel_info.go

@@ -466,14 +466,20 @@ func (c *ExcelInfoController) List() {
 		newKeyWord := strings.Split(keyword, " ")
 		keywordStr := strings.Replace(keyword, " ", "", -1)
 
-		condition += " AND ( "
-		condition += ` excel_name LIKE '%` + keywordStr + `%' OR`
+		likeKey := `%` + keywordStr + `%`
 
+		condition += " AND ( "
+		//condition += ` excel_name LIKE '%` + keywordStr + `%' OR`
+		condition += ` excel_name LIKE ? OR`
+		pars = append(pars, likeKey)
 		keyWordArr = append(keyWordArr, newKeyWord...)
 		if len(keyWordArr) > 0 {
 			for _, v := range keyWordArr {
 				if v != "" {
-					condition += ` excel_name LIKE '%` + v + `%' OR`
+					//condition += ` excel_name LIKE '%` + v + `%' OR`
+					likeKey := `%` + v + `%`
+					condition += ` excel_name LIKE ? OR`
+					pars = append(pars, likeKey)
 				}
 			}
 		}

+ 9 - 5
controllers/data_manage/gpr_risk_data.go

@@ -256,20 +256,24 @@ func (this *BaseFromGprRiskController) GprRiskSearchList() {
 		keyWordArr := strings.Split(keyword, " ")
 
 		if len(keyWordArr) > 0 {
-			condition := ""
+			var condition string
+			var pars []interface{}
 			for _, v := range keyWordArr {
-				condition += ` AND CONCAT(index_name,index_code) LIKE '%` + v + `%'`
+				likeKey := `%` + v + `%`
+				condition += ` AND CONCAT(index_name,index_code) LIKE ? `
+				pars = append(pars, likeKey)
 			}
-			list, err = data_manage.GetGprRiskItemList(condition)
+			list, err = data_manage.GetGprRiskItemList(condition, pars)
 			if err != nil {
 				br.ErrMsg = "获取失败,Err:" + err.Error()
 				br.Msg = "获取失败"
 				return
 			}
 		}
-
 	} else {
-		list, err = data_manage.GetGprRiskItemList("")
+		var condition string
+		var pars []interface{}
+		list, err = data_manage.GetGprRiskItemList(condition, pars)
 		if err != nil {
 			br.ErrMsg = "获取失败,Err:" + err.Error()
 			br.Msg = "获取失败"

+ 11 - 5
controllers/data_manage/purang_data.go

@@ -256,11 +256,15 @@ func (this *BaseFromPurangController) PurangSearchList() {
 		keyWordArr := strings.Split(keyword, " ")
 
 		if len(keyWordArr) > 0 {
-			condition := ""
+			var condition string
+			var pars []interface{}
 			for _, v := range keyWordArr {
-				condition += ` AND CONCAT(index_name,index_code) LIKE '%` + v + `%'`
+				//condition += ` AND CONCAT(index_name,index_code) LIKE '%` + v + `%'`
+				likeKey := `%` + v + `%`
+				condition += ` AND CONCAT(index_name,index_code) LIKE ? `
+				pars = append(pars, likeKey)
 			}
-			list, err = data_manage.GetPurangItemList(condition)
+			list, err = data_manage.GetPurangItemList(condition, pars)
 			if err != nil {
 				br.ErrMsg = "获取失败,Err:" + err.Error()
 				br.Msg = "获取失败"
@@ -269,7 +273,9 @@ func (this *BaseFromPurangController) PurangSearchList() {
 		}
 
 	} else {
-		list, err = data_manage.GetPurangItemList("")
+		var condition string
+		var pars []interface{}
+		list, err = data_manage.GetPurangItemList(condition, pars)
 		if err != nil {
 			br.ErrMsg = "获取失败,Err:" + err.Error()
 			br.Msg = "获取失败"
@@ -1042,4 +1048,4 @@ func (this *BaseFromPurangController) GetFrequency() {
 	br.Success = true
 	br.Msg = "获取成功"
 	br.Data = frequencyList
-} 
+}

+ 65 - 12
controllers/data_stat/edb_source_stat.go

@@ -41,6 +41,7 @@ func (this *EdbSourceStatController) Column() {
 		br.Msg = "请选择表类型"
 		return
 	}
+	isApi, _ := this.GetInt("IsApi", 0)
 	tmpList, err := data_stat.GetStatColumn(columnType)
 	if err != nil {
 		br.Msg = "获取自定义列失败"
@@ -49,6 +50,9 @@ func (this *EdbSourceStatController) Column() {
 	}
 	var list []*data_stat.EdbInfoStatColumnListItem
 	for _, v := range tmpList {
+		if isApi == 1 && v.ColumnKey == "InitSourceName" {
+			continue
+		}
 		tmp := new(data_stat.EdbInfoStatColumnListItem)
 		tmp.ColumnKey = v.ColumnKey
 		tmp.IsShow = v.IsShow
@@ -165,7 +169,11 @@ func (this *EdbSourceStatController) EdbDeleteLog() {
 		br.Ret = 408
 		return
 	}
-
+	source, _ := this.GetInt("Source", -1)
+	if source < 0 {
+		br.Msg = "请选择数据源"
+		return
+	}
 	sortParamReq := this.GetString("SortParam", "")
 	sortType := this.GetString("SortType", "desc")
 	createTime := this.GetString("CreateTime", "")
@@ -185,7 +193,7 @@ func (this *EdbSourceStatController) EdbDeleteLog() {
 
 	condition := " and source = ?"
 	var pars []interface{}
-	pars = append(pars, utils.DATA_SOURCE_MYSTEEL_CHEMICAL)
+	pars = append(pars, source)
 
 	if createTime != "" {
 		startT, err := time.ParseInLocation(utils.FormatDate, createTime, time.Local)
@@ -314,7 +322,11 @@ func (this *EdbSourceStatController) EdbUpdateLog() {
 		br.Ret = 408
 		return
 	}
-
+	source, _ := this.GetInt("Source", -1)
+	if source < 0 {
+		br.Msg = "请选择数据源"
+		return
+	}
 	sortParamReq := this.GetString("SortParam", "")
 	sortType := this.GetString("SortType", "desc")
 	createTime := this.GetString("CreateTime", "")
@@ -335,7 +347,7 @@ func (this *EdbSourceStatController) EdbUpdateLog() {
 
 	condition := " and source = ? and (data_update_result=1 or data_update_result=0)"
 	var pars []interface{}
-	pars = append(pars, utils.DATA_SOURCE_MYSTEEL_CHEMICAL)
+	pars = append(pars, source)
 
 	if createTime != "" {
 		startT, err := time.ParseInLocation(utils.FormatDate, createTime, time.Local)
@@ -469,6 +481,11 @@ func (this *EdbSourceStatController) EdbUpdateStat() {
 
 	sortParamReq := this.GetString("SortParam", "")
 	sortType := this.GetString("SortType", "desc")
+	source, _ := this.GetInt("Source", -1)
+	if source < 0 {
+		br.Msg = "请选择数据源"
+		return
+	}
 	terminalCode := this.GetString("TerminalCode", "")
 	sysUserId := this.GetString("SysUserId", "")
 	frequency := this.GetString("Frequency", "")
@@ -478,7 +495,17 @@ func (this *EdbSourceStatController) EdbUpdateStat() {
 
 	pageSize, _ := this.GetInt("PageSize")
 	currentIndex, _ := this.GetInt("CurrentIndex")
+	isApi, _ := this.GetInt("IsApi", 0)
 
+	// 区分终端和API的统计
+	// 查询类型为API的终端编码
+	terminalCodeList, err := data_manage.GetTerminalCodeBySourceAndIsApi(source, isApi)
+	if err != nil {
+		br.Msg = "获取终端编码失败"
+		br.ErrMsg = "获取终端编码失败,Err:" + err.Error()
+		return
+	}
+	
 	var startSize int
 	if pageSize <= 0 {
 		pageSize = utils.PageSize20
@@ -489,9 +516,10 @@ func (this *EdbSourceStatController) EdbUpdateStat() {
 
 	startSize = paging.StartIndex(currentIndex, pageSize)
 
-	condition := " and source = ?"
+	condition := " and source = ? and terminal_code in (" + utils.GetOrmInReplace(len(terminalCodeList)) + ")"
 	var pars []interface{}
-	pars = append(pars, utils.DATA_SOURCE_MYSTEEL_CHEMICAL)
+	pars = append(pars, source)
+	pars = append(pars, terminalCodeList)
 	if createTime != "" {
 		startT, err := time.ParseInLocation(utils.FormatDate, createTime, time.Local)
 		if err != nil {
@@ -666,14 +694,18 @@ func (this *EdbSourceStatController) EdbSourceStat() {
 		br.Ret = 408
 		return
 	}
-
+	source, _ := this.GetInt("Source", -1)
+	if source < 0 {
+		br.Msg = "请选择数据源"
+		return
+	}
 	sortParamReq := this.GetString("SortParam", "")
 	sortType := this.GetString("SortType", "desc")
 	createTime := this.GetString("CreateTime", "")
 
 	pageSize, _ := this.GetInt("PageSize")
 	currentIndex, _ := this.GetInt("CurrentIndex")
-
+	isApi, _ := this.GetInt("IsApi", 0)
 	var startSize int
 	if pageSize <= 0 {
 		pageSize = utils.PageSize20
@@ -684,9 +716,20 @@ func (this *EdbSourceStatController) EdbSourceStat() {
 
 	startSize = paging.StartIndex(currentIndex, pageSize)
 
-	condition := " and source = ?"
+	// 区分终端和API的统计
+	// 查询类型为API的终端编码
+	terminalCodeList, err := data_manage.GetTerminalCodeBySourceAndIsApi(source, isApi)
+	if err != nil {
+		br.Msg = "获取终端编码失败"
+		br.ErrMsg = "获取终端编码失败,Err:" + err.Error()
+		return
+	}
+	
+
+	condition := " and source = ? and terminal_code in (" + utils.GetOrmInReplace(len(terminalCodeList)) + ")"
 	var pars []interface{}
-	pars = append(pars, utils.DATA_SOURCE_MYSTEEL_CHEMICAL)
+	pars = append(pars, source)
+	pars = append(pars, terminalCodeList)
 
 	if createTime != "" {
 		startT, err := time.ParseInLocation(utils.FormatDate, createTime, time.Local)
@@ -801,13 +844,18 @@ func (this *EdbSourceStatController) EdbUpdateFailedList() {
 		br.Ret = 408
 		return
 	}
+	source, _ := this.GetInt("Source", -1)
+	if source < 0 {
+		br.Msg = "请选择数据源"
+		return
+	}
 
 	terminalCode := this.GetString("TerminalCode", "")
 	createTime := this.GetString("CreateTime", "")
 
 	condition := " and source = ? and terminal_code = ?"
 	var pars []interface{}
-	pars = append(pars, utils.DATA_SOURCE_MYSTEEL_CHEMICAL, terminalCode)
+	pars = append(pars, source, terminalCode)
 
 	terminalName := ""
 	terminalDir := ""
@@ -889,6 +937,11 @@ func (this *EdbSourceStatController) EdbUpdateFailedDetailList() {
 		br.Ret = 408
 		return
 	}
+	source, _ := this.GetInt("Source", -1)
+	if source < 0 {
+		br.Msg = "请选择数据源"
+		return
+	}
 	pageSize, _ := this.GetInt("PageSize")
 	currentIndex, _ := this.GetInt("CurrentIndex")
 
@@ -919,7 +972,7 @@ func (this *EdbSourceStatController) EdbUpdateFailedDetailList() {
 
 	condition := " and source = ? AND terminal_code = ? and frequency=? and data_update_failed_reason=? and data_update_result = 2"
 	var pars []interface{}
-	pars = append(pars, utils.DATA_SOURCE_MYSTEEL_CHEMICAL, terminalCode, frequency, sourceUpdateFailedReason)
+	pars = append(pars, source, terminalCode, frequency, sourceUpdateFailedReason)
 
 	if createTime != "" {
 		startT, err := time.ParseInLocation(utils.FormatDate, createTime, time.Local)

+ 238 - 7
controllers/data_stat/edb_terminal.go

@@ -8,6 +8,9 @@ import (
 	"eta/eta_api/services/data_stat"
 	"eta/eta_api/utils"
 	"fmt"
+	"strconv"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
 // EdbTerminalController 数据源终端管理
@@ -49,10 +52,10 @@ func (this *EdbTerminalController) Save() {
 		br.Msg = "请输入终端地址或者token"
 		return
 	}*/
-	if req.Num <= 0 {
-		br.Msg = "请输入指标数据量"
-		return
-	}
+	// if req.Num <= 0 {
+	// 	br.Msg = "请输入指标数据量"
+	// 	return
+	// }
 	if req.Source == 0 {
 		br.Msg = "请输入终端类型"
 		return
@@ -130,7 +133,7 @@ func (this *EdbTerminalController) List() {
 	br.IsSendEmail = false
 	defer func() {
 		this.Data["json"] = br
-		this.ServeJSON()
+		this.ServeJSON()   
 	}()
 	sysUser := this.SysUser
 	if sysUser == nil {
@@ -139,13 +142,56 @@ func (this *EdbTerminalController) List() {
 		br.Ret = 408
 		return
 	}
+	source, _ := this.GetInt("Source")
 
-	list, err := data_manage.GetEdbTerminalList()
+	list, err := data_manage.GetEdbTerminalList(source)
 	if err != nil {
 		br.Msg = "获取终端列表失败"
 		br.ErrMsg = "获取终端列表失败 ErrMsg:" + err.Error()
 		return
 	}
+
+	// 计算已使用额度
+	// 根据source查找对应的终端信息,比如source为34,则查询对应的数据源里的指标
+	// 获取数据源详细信息
+	for _, v := range list {
+		num, subNumList, err := data_manage.GetIndexNumBySource(v.Source, v.TerminalCode)
+		if err != nil {
+			br.Msg = "获取终端列表失败"
+			br.ErrMsg = "获取终端列表失败 ErrMsg:" + err.Error()
+			return
+		}
+		v.UsedQuota = strconv.Itoa(num)
+		if v.Source == utils.DATA_SOURCE_THS {
+			edbNum := 0
+			dateNum := 0
+			hfNum := 0
+			for _, subNum := range subNumList {
+				if subNum.SubSource == utils.DATA_SUB_SOURCE_HIGH_FREQUENCY {
+					hfNum = subNum.Num
+				} else if subNum.SubSource == utils.DATA_SUB_SOURCE_EDB {
+					edbNum = subNum.Num
+				} else if subNum.SubSource == utils.DATA_SUB_SOURCE_DATE {
+					dateNum = subNum.Num
+				}
+			}
+			v.UsedQuota = fmt.Sprintf("EDB:%d\n日期序列:%d\n高频序列:%d", edbNum, dateNum, hfNum)
+		}else if v.Source == utils.DATA_SOURCE_WIND {
+			edbNum := 0
+			dateNum := 0
+			for _, subNum := range subNumList {
+				if subNum.SubSource == utils.DATA_SUB_SOURCE_EDB {
+					edbNum = subNum.Num
+				} else if subNum.SubSource == utils.DATA_SUB_SOURCE_DATE {
+					dateNum = subNum.Num
+				}
+			}
+			v.UsedQuota = fmt.Sprintf("EDB:%d\n日期序列:%d", edbNum, dateNum)
+		}else if len(subNumList) == 1 {
+			v.UsedQuota = strconv.Itoa(subNumList[0].Num)
+		}
+	}
+
 	resp := &data_manage.EdbTerminalListResp{
 		List: list,
 	}
@@ -175,7 +221,8 @@ func (this *EdbTerminalController) TerminalCodeList() {
 		return
 	}
 	source, _ := this.GetInt("Source", utils.DATA_SOURCE_MYSTEEL_CHEMICAL)
-	list, err := data_manage.GetEdbTerminalBySource(source)
+	isApi, _ := this.GetInt("IsApi", 0)
+	list, err := data_manage.GetEdbTerminalBySourceAndIsApi(source, isApi)
 	if err != nil {
 		br.Msg = "获取终端列表失败"
 		br.ErrMsg = "获取终端列表失败 ErrMsg:" + err.Error()
@@ -240,3 +287,187 @@ func (this *EdbTerminalController) TerminalIndexDirInfo() {
 	br.Msg = "获取成功"
 	br.Data = info
 }
+
+// 查询指标列表
+// @Title 查询指标列表接口
+// @Description 查询指标列表接口
+// @Success Ret=200 获取成功
+// @router /terminal/edb_info/list [get]
+func (c *EdbTerminalController) GetEdbInfoList() {
+	br := new(models.BaseResponse).Init()
+
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+
+	source, _ := c.GetInt("Source")
+	terminalCode := c.GetString("TerminalCode")
+	keyword := c.GetString("Keyword")
+	
+	pageSize, _ := c.GetInt("PageSize")
+	currentIndex, _ := c.GetInt("CurrentIndex")
+
+	var startSize int
+	
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	if source <= 0 {
+		br.Msg = "来源不能为空"
+		br.ErrMsg = "来源不能为空"
+		br.IsSendEmail = false
+		return
+	}
+
+	condition := ``
+	var pars []interface{}
+	var list []*data_manage.EdbInfoTerminalList
+	var count int64
+	var err error
+	indexTableName := data_manage.EdbSourceIdMap[source].IndexTableName
+	if indexTableName != "" {
+		if terminalCode != "" {
+			condition += " AND e.terminal_code = ?"
+			pars = append(pars, terminalCode)
+		}
+		if keyword != "" {
+			condition += " AND (e.index_name like ? or e.index_code like ?)"
+			pars = append(pars, "%"+keyword+"%")
+			pars = append(pars, "%"+keyword+"%")
+		}
+		list, err = data_manage.GetSimpleBaseIndexListPageByCondition(indexTableName, condition, pars, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+		count, err = data_manage.GetSimpleBaseIndexListCountByCondition(indexTableName, condition, pars)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+		
+	}else{
+		condition += " AND e.source = ?"
+		pars = append(pars, source)
+		if terminalCode != "" {
+			condition += " AND e.terminal_code = ?"
+			pars = append(pars, terminalCode)
+		}
+		if keyword != "" {
+			condition += " AND (e.edb_name like ? or e.edb_code like ?)"
+			pars = append(pars, "%"+keyword+"%")
+			pars = append(pars, "%"+keyword+"%")
+		}
+	
+		list, err = data_manage.GetSimpleEdbListPageByCondition(condition, pars, startSize, pageSize)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+		
+		count, err = data_manage.GetSimpleEdbListCountByCondition(condition, pars)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取失败,Err:" + err.Error()
+			return
+		}
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, int(count))	
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = map[string]interface{}{
+		"List": list,
+		"Paging": page,
+	}
+	return
+}
+
+
+// 设置指标终端
+// @Title 设置指标终端
+// @Description 设置指标终端
+// @Success 200 string "操作成功"
+// @router /terminal/edb_info/set [post]
+func (c *EdbTerminalController) SetEdbInfoTerminal() {
+	br := new(models.BaseResponse).Init()
+	br.IsSendEmail = false
+	defer func() {
+		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.SetEdbInfoTerminalReq
+	if err := json.Unmarshal(c.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+
+	if req.Source <= 0 {
+		br.Msg = "来源不能为空"
+		br.ErrMsg = "来源不能为空"
+		return
+	}
+
+	if req.TerminalCode == "" {
+		br.Msg = "终端编码不能为空"
+		br.ErrMsg = "终端编码不能为空"
+		return
+	}
+
+	if len(req.EdbCodes) <= 0 {
+		br.Msg = "指标编码不能为空"
+		br.ErrMsg = "指标编码不能为空"
+		return
+	}
+	
+	// 校验终端编码是否存在
+	terminal, err := data_manage.GetEdbTerminalByTerminalCode(req.TerminalCode)
+	if err != nil {
+		br.Msg = "终端编码不存在"
+		br.ErrMsg = "终端编码不存在"
+		return
+	}
+
+	if req.Source != terminal.Source {
+		br.Msg = "终端来源不匹配"
+		br.ErrMsg = "终端来源不匹配"
+		return
+	}
+
+	edbCodeList := req.EdbCodes
+
+	
+	// 更新数据源里的终端编码
+	err = data_manage.UpdatBaseIndexTerminalCode(edbCodeList, req.TerminalCode, req.Source)
+	if err != nil {
+		br.Msg = "设置失败"
+		br.ErrMsg = "设置失败,Err:" + err.Error()
+		return
+	}
+	
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "设置成功"
+	return
+}

+ 13 - 8
controllers/edb_monitor/edb_monitor_message.go

@@ -3,8 +3,10 @@ package edb_monitor
 import (
 	"encoding/json"
 	"eta/eta_api/controllers"
+	"eta/eta_api/global"
 	"eta/eta_api/models"
 	"eta/eta_api/models/edb_monitor/request"
+	"eta/eta_api/services"
 	edbmonitor "eta/eta_api/services/edb_monitor"
 	"eta/eta_api/utils"
 	"net/http"
@@ -47,10 +49,10 @@ func (m *EdbMonitorMessageController) Connect() {
 	}
 
 	var conn *websocket.Conn
-	connKey := edbmonitor.EDB_MONITOR_MESSAGE_CONNECT_CACHE + strconv.Itoa(sysUser.AdminId)
+	connKey := global.EDB_MONITOR_MESSAGE_CONNECT_CACHE + strconv.Itoa(sysUser.AdminId)
 	ok := utils.Rc.IsExist(connKey)
 	if !ok {
-		conn = edbmonitor.MonitorMessageConn[sysUser.AdminId]
+		conn = global.MonitorMessageConn[sysUser.AdminId]
 		if conn != nil {
 			conn.Close()
 		}
@@ -69,9 +71,10 @@ func (m *EdbMonitorMessageController) Connect() {
 	}
 	defer conn.Close()
 
-	edbmonitor.MonitorMessageConn[sysUser.AdminId] = conn
+	global.MonitorMessageConn[sysUser.AdminId] = conn
 	conn.SetCloseHandler(func(code int, text string) error {
-		delete(edbmonitor.MonitorMessageConn, sysUser.AdminId)
+		utils.FileLog.Info("连接关闭SetCloseHandler, adminId:%d", sysUser.AdminId)
+		delete(global.MonitorMessageConn, sysUser.AdminId)
 		utils.Rc.Delete(connKey)
 		return nil
 	})
@@ -79,12 +82,13 @@ func (m *EdbMonitorMessageController) Connect() {
 	go func() {
 		// 心跳检测
 		for {
-			isClose, err := edbmonitor.EdbMonitorMessageHealth(sysUser.AdminId)
+			isClose, err := global.EdbMonitorMessageHealth(sysUser.AdminId)
 			if err != nil {
 				utils.FileLog.Error("指标预警信息健康检查失败,err:%s, adminId:%d", err.Error(), sysUser.AdminId)
 				return
 			}
 			if isClose {
+				conn.Close()
 				return
 			}
 		}
@@ -99,8 +103,7 @@ func (m *EdbMonitorMessageController) Connect() {
 		defer close(success)
 		for i, msg := range messageList {
 			if i == 0 {
-				// 多条消息仅发送最新一条
-				err = edbmonitor.SendMessages(sysUser.AdminId, msg.EdbInfoId, msg.EdbInfoType, msg.EdbClassifyId, msg.EdbUniqueCode, msg.Message, msg.TriggerTime)
+				err := edbmonitor.SendMessages(sysUser.AdminId, msg.EdbInfoId, msg.EdbInfoType, msg.EdbClassifyId, msg.EdbUniqueCode, msg.Message, msg.TriggerTime)
 				if err != nil {
 					utils.FileLog.Error("指标预警信息发送失败,err:%s, adminId:%d", err.Error(), sysUser.AdminId)
 				} else {
@@ -126,6 +129,8 @@ func (m *EdbMonitorMessageController) Connect() {
 		}
 	}()
 
+	// 其他消息处理
+	services.DealWebSocketMsg(sysUser.AdminId)
 	for {
 		ok = utils.Rc.IsExist(connKey)
 		if !ok {
@@ -158,7 +163,7 @@ func (m *EdbMonitorMessageController) Close() {
 		return
 	}
 
-	conn := edbmonitor.MonitorMessageConn[sysUser.AdminId]
+	conn := global.MonitorMessageConn[sysUser.AdminId]
 	if conn != nil {
 		conn.Close()
 	}

+ 1 - 0
controllers/eta_forum/eta_forum.go

@@ -247,6 +247,7 @@ func (this *EtaForumController) CommonChartInfoDetailFromUniqueCode() {
 	resp.Status = status
 	resp.DataResp = forumResp.DataResp
 	resp.EdbInfoList = forumResp.EdbInfoList
+	resp.XEdbIdValue = forumResp.XEdbIdValue
 	resp.XDataList = forumResp.XDataList
 	resp.YDataList = forumResp.YDataList
 	br.Ret = 200

+ 24 - 3
controllers/fe_calendar/fe_calendar_matter.go

@@ -351,7 +351,8 @@ func (this *FeCalendarMatterController) PermissionList() {
 		br.ErrMsg = "获取品种列表失败, Err: " + e.Error()
 		return
 	}
-	resp := make([]*models.SimpleChartPermission, 0)
+	var resp models.FaCalendarPermissionResp
+	list := make([]*models.SimpleChartPermission, 0)
 	parentPermissions := make(map[int][]*models.SimpleChartPermission, 0)
 	for _, v := range permissions {
 		if v.ParentId > 0 {
@@ -361,12 +362,32 @@ func (this *FeCalendarMatterController) PermissionList() {
 			parentPermissions[v.ParentId] = append(parentPermissions[v.ParentId], models.FormatChartPermission2Simple(v))
 			continue
 		}
-		resp = append(resp, models.FormatChartPermission2Simple(v))
+		list = append(list, models.FormatChartPermission2Simple(v))
 	}
-	for _, v := range resp {
+	for _, v := range list {
 		v.Children = parentPermissions[v.ChartPermissionId]
 	}
+	lastEditPermissionId := 0
+	lastEditPermissionName := ""
+	// 查询最近被编辑过的品种ID
+	matterOb := new(fe_calendar.FeCalendarMatter)
+	cond := ""
+	pars := make([]interface{}, 0)
+	order := fmt.Sprintf(`%s Desc`, fe_calendar.FeCalendarMatterCols.ModifyTime)
+	matter, e := matterOb.GetItemByCondition(cond, pars, order)
+	if e != nil && e.Error() != utils.ErrNoRow() {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取事项列表失败, Err: " + e.Error()
+		return
+	}
+	if e == nil {
+		lastEditPermissionId = matter.ChartPermissionId
+		lastEditPermissionName = matter.ChartPermissionName
+	}
 
+	resp.CheckedPermissionId = lastEditPermissionId
+	resp.CheckedPermissionName = lastEditPermissionName
+	resp.List = list
 	br.Data = resp
 	br.Ret = 200
 	br.Success = true

+ 206 - 212
controllers/llm/abstract.go

@@ -13,6 +13,8 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
+	"strconv"
+	"strings"
 )
 
 // AbstractController
@@ -45,7 +47,26 @@ func (c *AbstractController) List() {
 	pageSize, _ := c.GetInt("PageSize")
 	currentIndex, _ := c.GetInt("CurrentIndex")
 	keyWord := c.GetString("KeyWord")
-	tagId, _ := c.GetInt("TagId")
+	tagIdStr := c.GetString("TagId")
+	questionId, _ := c.GetInt("QuestionId")
+
+	tagIdList := make([]int, 0)
+	if tagIdStr != `` {
+		tagIdStrList := strings.Split(tagIdStr, `,`)
+		for _, v := range tagIdStrList {
+			if v == `0` {
+				continue
+			}
+
+			tagId, tmpErr := strconv.Atoi(v)
+			if tmpErr != nil {
+				br.Msg = "标签ID有误"
+				br.ErrMsg = fmt.Sprintf("标签ID有误, %s", v)
+				return
+			}
+			tagIdList = append(tagIdList, tagId)
+		}
+	}
 
 	var startSize int
 	if pageSize <= 0 {
@@ -57,7 +78,7 @@ func (c *AbstractController) List() {
 	startSize = utils.StartIndex(currentIndex, pageSize)
 
 	// 获取列表
-	total, viewList, err := getAbstractList(keyWord, tagId, startSize, pageSize)
+	total, viewList, err := getAbstractList(keyWord, tagIdList, questionId, startSize, pageSize)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -76,60 +97,91 @@ func (c *AbstractController) List() {
 	br.Data = resp
 }
 
-func getAbstractList(keyWord string, tagId int, startSize, pageSize int) (total int, viewList []rag.WechatArticleAbstractView, err error) {
+func getAbstractList(keyWord string, tagList []int, questionId 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)
+	//	}
+	//}
+
+	sortMap := map[string]string{
+		//"ModifyTime":              "desc",
+		//"WechatArticleAbstractId": "desc",
+	}
 	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",
+		sortMap = map[string]string{
+			"CreateTime": "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)
-		}
+	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)
+	}
+	tmpTotal, list, tmpErr := elastic.WechatArticleAbstractEsSearch(keyWord, tagList, platformIdList, questionId, startSize, pageSize, sortMap)
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	total = int(tmpTotal)
+	if list != nil && len(list) > 0 {
+		viewList = list[0].ToViewList(list)
 	}
 
 	return
@@ -160,82 +212,14 @@ func (c *AbstractController) Del() {
 		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)
+	err = services.DelWechatArticleAbstract(req.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 = `删除成功`
@@ -270,57 +254,76 @@ func (c *AbstractController) VectorDel() {
 	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
+	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
 		}
-		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)
+		return
+	}
+	if len(list) > 0 {
+		for _, v := range list {
+			// 有加入到向量库,那么就加入到待删除的向量库list中
+			if v.VectorKey != `` {
+				vectorKeyList = append(vectorKeyList, v.VectorKey)
 			}
+			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.WechatArticleAbstractId)
 		}
 	}
 
+	//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 {
@@ -364,57 +367,48 @@ func (c *AbstractController) AddVector() {
 		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)
-			}
+	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
 	}
-
-	for _, wechatArticleAbstractId := range wechatArticleAbstractIdList {
-		cache.AddWechatArticleLlmOpToCache(wechatArticleAbstractId, ``)
+	for _, v := range list {
+		cache.AddWechatArticleLlmOpToCache(v.WechatArticleId, v.QuestionId, ``)
 	}
 
 	br.Ret = 200
 	br.Success = true
 	br.Msg = `添加向量库中,请稍后查看`
 }
+
+//func init() {
+//	//微信文章
+//	//{
+//	//	obj := rag.WechatArticle{}
+//	//	item, tmpErr := obj.GetById(1722)
+//	//	if tmpErr != nil {
+//	//		// 找不到就处理失败
+//	//		return
+//	//	}
+//	//	services.GenerateWechatArticleAbstract(item, false)
+//	//}
+//
+//	// ETA报告
+//	{
+//		obj := rag.RagEtaReport{}
+//		item, tmpErr := obj.GetById(1)
+//		if tmpErr != nil {
+//			// 找不到就处理失败
+//			return
+//		}
+//		services.GenerateRagEtaReportAbstract(item, false)
+//	}
+//
+//	fmt.Println("结束")
+//}

+ 47 - 0
controllers/llm/kb_controller.go

@@ -3,6 +3,7 @@ package llm
 import (
 	"encoding/json"
 	"eta/eta_api/controllers"
+	"eta/eta_api/controllers/llm/llm_http"
 	"eta/eta_api/models"
 	"eta/eta_api/services/llm/facade"
 )
@@ -49,3 +50,49 @@ func (kbctrl *KbController) SearchDocs() {
 	br.Success = true
 	br.Msg = "获取成功"
 }
+
+// KnowledgeList
+// @Title 获取知识库列表
+// @Description  获取知识库列表
+// @Success 101 {object} response.ListResp
+// @router /knowledge/list [get]
+func (ucCtrl *UserChatController) KnowledgeList() {
+	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
+	}
+	// 获取基础配置, 若未配置则直接返回
+	conf, e := models.GetBusinessConf()
+	if e != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取基础配置失败, Err: " + e.Error()
+		return
+	}
+
+	list := make([]llm_http.KnowledgeList, 0)
+	if conf[models.KnowledgeBaseName] != "" {
+		list = append(list, llm_http.KnowledgeList{
+			KnowledgeName: conf[models.KnowledgeBaseName],
+			Name:          "弘则公共知识库",
+		})
+	}
+	if conf[models.PrivateKnowledgeBaseName] != "" {
+		list = append(list, llm_http.KnowledgeList{
+			KnowledgeName: conf[models.PrivateKnowledgeBaseName],
+			Name:          "弘则私有知识库",
+		})
+	}
+
+	br.Data = list
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取知识库列表成功"
+}

+ 6 - 1
controllers/llm/llm_http/response.go

@@ -18,7 +18,7 @@ type UserChatAddResp struct {
 
 type AIGCResp struct {
 	Promote Content
-	Answer Content
+	Answer  Content
 }
 
 type Content struct {
@@ -26,3 +26,8 @@ type Content struct {
 	Content  string
 	SendTime string
 }
+
+type KnowledgeList struct {
+	Name          string
+	KnowledgeName string
+}

+ 367 - 27
controllers/llm/question.go

@@ -17,7 +17,7 @@ import (
 )
 
 // QuestionController
-// @Description: 问题库管理
+// @Description: 提示词库管理
 type QuestionController struct {
 	controllers.BaseAuthController
 }
@@ -28,6 +28,7 @@ type QuestionController struct {
 // @Param   PageSize   query   int  true       "每页数据条数"
 // @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
 // @Param   KeyWord   query   string  true       "搜索关键词"
+// @Param   IsQueryDefault   query   bool  true       "是否默认:true,或者false"
 // @Success 200 {object} []*rag.QuestionListListResp
 // @router /question/list [get]
 func (c *QuestionController) List() {
@@ -46,6 +47,7 @@ func (c *QuestionController) List() {
 	pageSize, _ := c.GetInt("PageSize")
 	currentIndex, _ := c.GetInt("CurrentIndex")
 	keyWord := c.GetString("KeyWord")
+	isQueryDefault, _ := c.GetBool("IsQueryDefault")
 
 	var startSize int
 	if pageSize <= 0 {
@@ -64,9 +66,15 @@ func (c *QuestionController) List() {
 		var pars []interface{}
 
 		if keyWord != "" {
-			condition += fmt.Sprintf(` AND %s like ?`, rag.QuestionColumns.QuestionContent)
+			condition += fmt.Sprintf(` AND %s like ? `, rag.QuestionColumns.QuestionContent)
 			pars = append(pars, `%`+keyWord+`%`)
 		}
+
+		if isQueryDefault {
+			condition += fmt.Sprintf(` AND %s = ? `, rag.QuestionColumns.IsDefault)
+			pars = append(pars, 1)
+		}
+
 		obj := new(rag.Question)
 		tmpTotal, list, err := obj.GetPageListByCondition(condition, pars, startSize, pageSize)
 		if err != nil {
@@ -84,7 +92,7 @@ func (c *QuestionController) List() {
 			//"ArticleCreateTime": "desc",
 			//"WechatArticleId":   "desc",
 		}
-		tmpTotal, list, err := elastic.RagQuestionEsSearch(keyWord, startSize, pageSize, sortMap)
+		tmpTotal, list, err := elastic.RagQuestionEsSearch(keyWord, isQueryDefault, startSize, pageSize, sortMap)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -114,6 +122,7 @@ func (c *QuestionController) List() {
 // @Param   PageSize   query   int  true       "每页数据条数"
 // @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
 // @Param   KeyWord   query   string  true       "搜索关键词"
+// @Param   IsQueryDefault   query   bool  true       "是否默认:true,或者false"
 // @Success 200 {object} []*rag.QuestionListListResp
 // @router /question/title/list [get]
 func (c *QuestionController) TitleList() {
@@ -132,6 +141,7 @@ func (c *QuestionController) TitleList() {
 	pageSize, _ := c.GetInt("PageSize")
 	currentIndex, _ := c.GetInt("CurrentIndex")
 	keyWord := c.GetString("KeyWord")
+	isQueryDefault, _ := c.GetBool("IsQueryDefault")
 
 	var startSize int
 	if pageSize <= 0 {
@@ -153,6 +163,12 @@ func (c *QuestionController) TitleList() {
 			condition += fmt.Sprintf(` AND %s like ?`, rag.QuestionColumns.QuestionContent)
 			pars = append(pars, `%`+keyWord+`%`)
 		}
+
+		if isQueryDefault {
+			condition += fmt.Sprintf(` AND %s = ? `, rag.QuestionColumns.IsDefault)
+			pars = append(pars, 1)
+		}
+
 		obj := new(rag.Question)
 		tmpTotal, list, err := obj.GetTitlePageListByCondition(condition, pars, startSize, pageSize)
 		if err != nil {
@@ -170,16 +186,38 @@ func (c *QuestionController) TitleList() {
 			//"ArticleCreateTime": "desc",
 			//"WechatArticleId":   "desc",
 		}
-		tmpTotal, list, err := elastic.RagQuestionEsSearch(keyWord, startSize, pageSize, sortMap)
+		tmpTotal, esList, err := elastic.RagQuestionEsSearch(keyWord, isQueryDefault, 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].ToViewList(list)
+
+		if total > 0 {
+			questionIdList := make([]int, 0)
+			for _, v := range esList {
+				questionIdList = append(questionIdList, v.QuestionId)
+			}
+			var condition string
+			var pars []interface{}
+
+			condition += fmt.Sprintf(` AND %s in (?)`, rag.QuestionColumns.QuestionId)
+			pars = append(pars, `%`+keyWord+`%`)
+
+			obj := new(rag.Question)
+			tmpTotal, list, err := obj.GetTitlePageListByCondition(condition, pars, startSize, pageSize)
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取失败,Err:" + err.Error()
+				return
+			}
+			if list != nil && len(list) > 0 {
+				viewList = list[0].ListToViewList(list)
+			}
+			total = tmpTotal
 		}
+
 	}
 
 	page := paging.GetPaging(currentIndex, pageSize, total)
@@ -197,7 +235,7 @@ func (c *QuestionController) TitleList() {
 // Detail
 // @Title 列表
 // @Description 列表
-// @Param   QuestionId   query   int  true       "问题id"
+// @Param   QuestionId   query   int  true       "提示词id"
 // @Success 200 {object} []*rag.QuestionListListResp
 // @router /question/detail [get]
 func (c *QuestionController) Detail() {
@@ -215,8 +253,8 @@ func (c *QuestionController) Detail() {
 	}
 	questionId, _ := c.GetInt("QuestionId")
 	if questionId <= 0 {
-		br.Msg = "问题id不能为空"
-		br.ErrMsg = "问题id不能为空"
+		br.Msg = "提示词id不能为空"
+		br.ErrMsg = "提示词id不能为空"
 		return
 	}
 
@@ -236,8 +274,8 @@ func (c *QuestionController) Detail() {
 }
 
 // Add
-// @Title 新增问题
-// @Description 新增问题
+// @Title 新增提示词
+// @Description 新增提示词
 // @Param	request	body request.AddQuestionReq true "type json string"
 // @Success 200 Ret=200 新增成功
 // @router /question/add [post]
@@ -256,14 +294,14 @@ func (c *QuestionController) Add() {
 	}
 	req.Content = strings.TrimSpace(req.Content)
 	if req.Content == "" {
-		br.Msg = "请输入问题"
+		br.Msg = "请输入提示词"
 		br.IsSendEmail = false
 		return
 	}
 	//obj := rag.Question{}
 	//_, err = obj.GetByCondition(` AND question_content = ? `, []interface{}{req.Content})
 	//if err == nil {
-	//	br.Msg = "问题已入库,请不要重复添加"
+	//	br.Msg = "提示词已入库,请不要重复添加"
 	//	br.IsSendEmail = false
 	//	return
 	//}
@@ -293,8 +331,8 @@ func (c *QuestionController) Add() {
 }
 
 // Edit
-// @Title 编辑问题
-// @Description 编辑问题
+// @Title 编辑提示词
+// @Description 编辑提示词
 // @Param	request	body request.EditQuestionReq true "type json string"
 // @Success 200 Ret=200 新增成功
 // @router /question/edit [post]
@@ -304,6 +342,7 @@ func (c *QuestionController) Edit() {
 		c.Data["json"] = br
 		c.ServeJSON()
 	}()
+
 	var req request.EditQuestionReq
 	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
 	if err != nil {
@@ -312,13 +351,13 @@ func (c *QuestionController) Edit() {
 		return
 	}
 	if req.QuestionId <= 0 {
-		br.Msg = "问题id不能为空"
+		br.Msg = "提示词id不能为空"
 		br.IsSendEmail = false
 		return
 	}
 	req.Content = strings.TrimSpace(req.Content)
 	if req.Content == "" {
-		br.Msg = "请输入问题"
+		br.Msg = "请输入提示词"
 		br.IsSendEmail = false
 		return
 	}
@@ -327,17 +366,37 @@ func (c *QuestionController) Edit() {
 	item, err := obj.GetByID(req.QuestionId)
 	if err != nil {
 		br.Msg = "修改失败"
-		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+		br.ErrMsg = "修改失败,查找提示词失败,Err:" + err.Error()
 		if utils.IsErrNoRow(err) {
-			br.Msg = "问题不存在"
+			br.Msg = "提示词不存在"
 			br.IsSendEmail = false
 		}
 		return
 	}
+
+	// 编辑提示词:
+	if item.IsDefault == 1 {
+		total, err := services.GetNotFinishGenerateAbstractTaskNumByQuestionId(item.QuestionId)
+		if err != nil {
+			br.Msg = "修改失败"
+			br.ErrMsg = "权限校验失败,Err:" + err.Error()
+			return
+		}
+		if total > 0 {
+			br.Msg = "当前提示词正在生成摘要,请稍后再修改"
+			return
+		}
+	}
+
+	// 添加问题的历史记录
+	rag.AddQuestionHistoryByQuestion(item)
+
 	item.QuestionTitle = utils.GetFirstNChars(req.Content, 20)
 	item.QuestionContent = req.Content
+	item.Version++
+	item.GenerateStatus = `undo`
 	item.ModifyTime = time.Now()
-	err = item.Update([]string{"question_title", "question_content", "modify_time"})
+	err = item.Update([]string{"question_title", "question_content", `version`, `generate_status`, "modify_time"})
 	if err != nil {
 		br.Msg = "修改失败"
 		br.ErrMsg = "修改失败,Err:" + err.Error()
@@ -348,12 +407,12 @@ func (c *QuestionController) Edit() {
 
 	br.Ret = 200
 	br.Success = true
-	br.Msg = `添加成功`
+	br.Msg = `修改成功`
 }
 
 // Del
-// @Title 删除问题
-// @Description 删除问题
+// @Title 删除提示词
+// @Description 删除提示词
 // @Param	request	body request.EditQuestionReq true "type json string"
 // @Success 200 Ret=200 新增成功
 // @router /question/del [post]
@@ -371,7 +430,7 @@ func (c *QuestionController) Del() {
 		return
 	}
 	if req.QuestionId <= 0 {
-		br.Msg = "问题id不能为空"
+		br.Msg = "提示词id不能为空"
 		br.IsSendEmail = false
 		return
 	}
@@ -380,13 +439,23 @@ func (c *QuestionController) Del() {
 	item, err := obj.GetByID(req.QuestionId)
 	if err != nil {
 		br.Msg = "修改失败"
-		br.ErrMsg = "修改失败,查找问题失败,Err:" + err.Error()
+		br.ErrMsg = "修改失败,查找提示词失败,Err:" + err.Error()
 		if utils.IsErrNoRow(err) {
-			br.Msg = "问题不存在"
+			br.Msg = "提示词不存在"
 			br.IsSendEmail = false
 		}
 		return
 	}
+
+	// 删除提示词:若删除默认提示词,提示:当前提示词不允许删除;若删除非默认提示词:提示删除成功(项目eta4.0,时间:2025-4-16 17:39:38)
+	if item.IsDefault == 1 {
+		br.Msg = "当前提示词不允许删除!"
+		return
+	}
+
+	// 添加问题的历史记录
+	rag.AddQuestionHistoryByQuestion(item)
+
 	err = item.Del()
 	if err != nil {
 		br.Msg = "删除失败"
@@ -402,8 +471,279 @@ func (c *QuestionController) Del() {
 	br.Msg = `删除成功`
 }
 
+// SetDefault
+// @Title 设置默认提示词
+// @Description 设置默认提示词
+// @Param	request	body request.EditQuestionReq true "type json string"
+// @Success 200 Ret=200 设置成功
+// @router /question/default/set [post]
+func (c *QuestionController) SetDefault() {
+	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
+	}
+
+	// 判断是否正在取消默认提示词(删除历史摘要)
+	{
+		cacheKey := services.GetDelAbstractByQuestionIdCacheKey(item.QuestionId)
+		if utils.Rc.IsExist(cacheKey) {
+			br.Msg = "取消设置默认提示词后,删除历史摘要中,请稍后再试!"
+			br.IsSendEmail = false
+			return
+		}
+	}
+
+	if item.IsDefault == 1 {
+		br.Msg = "该提示词已经是默认提示词,无需设置"
+		br.IsSendEmail = false
+		return
+	}
+	item.IsDefault = 1
+	item.GenerateStatus = `undo`
+	item.ModifyTime = time.Now()
+	err = item.Update([]string{"is_default", "generate_status", "modify_time"})
+	if err != nil {
+		br.Msg = "设置失败"
+		br.ErrMsg = "设置失败,Err:" + err.Error()
+		return
+	}
+	// 新增/编辑ES数据
+	go services.AddOrEditEsRagQuestion(item.QuestionId)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `设置成功`
+}
+
+// UnSetDefault
+// @Title 取消设置默认提示词
+// @Description 取消设置默认提示词
+// @Param	request	body request.EditQuestionReq true "type json string"
+// @Success 200 Ret=200 设置成功
+// @router /question/default/unset [post]
+func (c *QuestionController) UnSetDefault() {
+	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
+	}
+
+	if item.IsDefault == 0 {
+		br.Msg = "该提示词不是默认提示词,无需取消"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 如果是取消已经设置成默认的提示词,那么需要判断是否有正在生成摘要的提示词任务,如果存在的话,那么就不允许取消
+	auth, err := services.CheckOpQuestionAuth()
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "权限校验失败,Err:" + err.Error()
+		return
+	}
+	if !auth {
+		br.Msg = "当前有提示词正在生成摘要,请稍后再修改"
+		return
+	}
+
+	item.IsDefault = 0
+	item.GenerateStatus = `undo`
+	item.ModifyTime = time.Now()
+	err = item.Update([]string{"is_default", "generate_status", "modify_time"})
+	if err != nil {
+		br.Msg = "取消设置失败"
+		br.ErrMsg = "取消设置失败,Err:" + err.Error()
+		return
+	}
+	// 新增/编辑ES数据
+	go services.AddOrEditEsRagQuestion(item.QuestionId)
+
+	// 对应的提示词生成的摘要库和向量库内容也取消,同时需要加锁,不允许重复操作
+	go services.DelAbstractByQuestionId(item.QuestionId)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `取消设置成功`
+}
+
+// GenerateAbstract
+// @Title 生成摘要
+// @Description 生成摘要
+// @Param	request	body request.EditQuestionReq true "type json string"
+// @Success 200 Ret=200 设置成功
+// @router /question/abstract/generate [post]
+func (c *QuestionController) GenerateAbstract() {
+	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
+	}
+
+	if item.IsDefault != 1 {
+		br.Msg = "该提示词不是默认提示词,不允许生成"
+		br.IsSendEmail = false
+		return
+	}
+	if item.GenerateStatus != `undo` {
+		br.Msg = "该提示词已经生成过摘要,不允许重复生成"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 如果是需要对提示词做摘要的生成,那么需要判断是否有正在生成摘要的提示词任务,如果存在的话,那么就不允许生成(暂定,后面可以改成加到任务中去,等上一个批次的任务完成后,继续该任务)
+	auth, err := services.CheckOpQuestionAuth()
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "权限校验失败,Err:" + err.Error()
+		return
+	}
+	if !auth {
+		br.Msg = "当前有提示词正在生成摘要,请稍后再重新生成"
+		return
+	}
+
+	// 标记摘要生成状态,避免重复生成
+	item.GenerateStatus = `done`
+	item.ModifyTime = time.Now()
+	err = item.Update([]string{"generate_status", "modify_time"})
+	if err != nil {
+		br.Msg = "取消设置失败"
+		br.ErrMsg = "取消设置失败,Err:" + err.Error()
+		return
+	}
+	// 新增/编辑ES数据
+	go services.AddOrEditEsRagQuestion(item.QuestionId)
+
+	// 添加任务
+	services.AddGenerateAbstractTask(item, c.SysUser)
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `摘要生成中`
+}
+
+// CheckOpAuth
+// @Title 获取
+// @Description 列表
+// @Success 200 {object} []*rag.QuestionListListResp
+// @router /question/op_auth/check [get]
+func (c *QuestionController) CheckOpAuth() {
+	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
+	}
+
+	// 如果是需要对提示词做摘要的生成,那么需要判断是否有正在生成摘要的提示词任务,如果存在的话,那么就不允许生成(暂定,后面可以改成加到任务中去,等上一个批次的任务完成后,继续该任务)
+	auth, err := services.CheckOpQuestionAuth()
+	if err != nil {
+		br.Msg = "修改失败"
+		br.ErrMsg = "权限校验失败,Err:" + err.Error()
+		return
+	}
+
+	var status, tip string
+	if auth {
+		status = `done`
+		tip = `新摘要生成成功!`
+	} else {
+		status = `processing`
+		tip = `新摘要生成中...`
+	}
+
+	resp := response.QuestionOpAuthResp{
+		Status: status,
+		Tip:    tip,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
 //func init() {
-//	// 问题加到es
+//	// 提示词加到es
 //	{
 //		obj := rag.Question{}
 //		list, _ := obj.GetListByCondition(``, ` `, []interface{}{}, 0, 10000)

+ 422 - 0
controllers/llm/rag_eta_report_abstract.go

@@ -0,0 +1,422 @@
+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"
+	"strconv"
+	"strings"
+)
+
+// RagEtaReportAbstractController
+// @Description: ETA报告摘要管理
+type RagEtaReportAbstractController struct {
+	controllers.BaseAuthController
+}
+
+// List
+// @Title ETA报告摘要列表
+// @Description ETA报告摘要列表
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   KeyWord   query   string  true       "搜索关键词"
+// @Success 200 {object} []*rag.QuestionListListResp
+// @router /abstract/eta_report/list [get]
+func (c *RagEtaReportAbstractController) 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")
+	tagIdStr := c.GetString("TagId")
+	questionId, _ := c.GetInt("QuestionId")
+
+	tagIdList := make([]int, 0)
+	if tagIdStr != `` {
+		tagIdStrList := strings.Split(tagIdStr, `,`)
+		for _, v := range tagIdStrList {
+			if v == `0` {
+				continue
+			}
+
+			tagId, tmpErr := strconv.Atoi(v)
+			if tmpErr != nil {
+				br.Msg = "标签ID有误"
+				br.ErrMsg = fmt.Sprintf("标签ID有误, %s", v)
+				return
+			}
+			tagIdList = append(tagIdList, tagId)
+		}
+	}
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	// 获取列表
+	total, viewList, err := getRagEtaReportAbstractList(keyWord, tagIdList, questionId, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := response.RagEtaReportAbstractListListResp{
+		List:   viewList,
+		Paging: page,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
+func getRagEtaReportAbstractList(keyWord string, tagList []int, questionId, startSize, pageSize int) (total int, viewList []rag.RagEtaReportAbstractView, 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)
+	//	}
+	//}
+
+	sortMap := map[string]string{
+		//"ModifyTime":              "desc",
+		//"WechatArticleAbstractId": "desc",
+	}
+	if keyWord == `` {
+		sortMap = map[string]string{
+			"CreateTime": "desc",
+			//"WechatArticleAbstractId": "desc",
+		}
+	}
+
+	tmpTotal, list, tmpErr := elastic.RagEtaReportAbstractEsSearch(keyWord, tagList, questionId, 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 删除ETA报告摘要摘要
+// @Description 删除ETA报告摘要摘要
+// @Param	request	body request.BeachOpRagEtaReportAbstractReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /abstract/eta_report/del [post]
+func (c *RagEtaReportAbstractController) Del() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.BeachOpRagEtaReportAbstractReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req.RagEtaReportAbstractIdList) <= 0 && !req.IsSelectAll {
+		br.Msg = "请选择摘要"
+		br.IsSendEmail = false
+		return
+	}
+
+	// 删除摘要
+	err = services.DelRagEtaReportAbstract(req.RagEtaReportAbstractIdList)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `删除成功`
+}
+
+// VectorDel
+// @Title 删除ETA报告摘要向量库
+// @Description 删除ETA报告摘要向量库
+// @Param	request	body request.BeachOpRagEtaReportAbstractReq true "type json string"
+// @Success 200 Ret=200 删除成功
+// @router /abstract/eta_report/vector/del [post]
+func (c *RagEtaReportAbstractController) VectorDel() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.BeachOpRagEtaReportAbstractReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req.RagEtaReportAbstractIdList) <= 0 && !req.IsSelectAll {
+		br.Msg = "请选择摘要"
+		br.IsSendEmail = false
+		return
+	}
+
+	vectorKeyList := make([]string, 0)
+	wechatArticleAbstractIdList := make([]int, 0)
+
+	obj := rag.RagEtaReportAbstract{}
+	list, err := obj.GetByIdList(req.RagEtaReportAbstractIdList)
+	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.RagEtaReportAbstractId)
+		}
+	}
+
+	//if !req.IsSelectAll {
+	//	list, err := obj.GetByIdList(req.RagEtaReportAbstractIdList)
+	//	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.RagEtaReportAbstractId)
+	//		}
+	//	}
+	//} else {
+	//	notIdMap := make(map[int]bool)
+	//	for _, v := range req.NotRagEtaReportAbstractIdList {
+	//		notIdMap[v] = true
+	//	}
+	//	_, list, err := getRagEtaReportAbstractList(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.RagEtaReportAbstractId] {
+	//				continue
+	//			}
+	//
+	//			// 有加入到向量库,那么就加入到待删除的向量库list中
+	//			if v.VectorKey != `` {
+	//				vectorKeyList = append(vectorKeyList, v.VectorKey)
+	//			}
+	//			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.RagEtaReportAbstractId)
+	//		}
+	//	}
+	//}
+
+	// 删除摘要库
+	err = services.DelRagReportLlmDoc(vectorKeyList, wechatArticleAbstractIdList)
+	if err != nil {
+		br.Msg = "删除失败"
+		br.ErrMsg = "删除失败,Err:" + err.Error()
+		return
+	}
+
+	// 修改ES数据
+	for _, wechatArticleAbstractId := range wechatArticleAbstractIdList {
+		go services.AddOrEditEsRagEtaReportAbstract(wechatArticleAbstractId)
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `删除成功`
+}
+
+// AddVector
+// @Title 新增ETA报告摘要向量库
+// @Description 新增ETA报告摘要向量库
+// @Param	request	body request.BeachOpRagEtaReportAbstractReq true "type json string"
+// @Success 200 Ret=200 新增成功
+// @router /abstract/eta_report/vector/add [post]
+func (c *RagEtaReportAbstractController) AddVector() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		c.Data["json"] = br
+		c.ServeJSON()
+	}()
+	var req request.BeachOpRagEtaReportAbstractReq
+	err := json.Unmarshal(c.Ctx.Input.RequestBody, &req)
+	if err != nil {
+		br.Msg = "参数解析异常!"
+		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+		return
+	}
+	if len(req.RagEtaReportAbstractIdList) <= 0 && !req.IsSelectAll {
+		br.Msg = "请选择摘要"
+		br.IsSendEmail = false
+		return
+	}
+
+	obj := rag.RagEtaReportAbstract{}
+	list, err := obj.GetByIdList(req.RagEtaReportAbstractIdList)
+	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 {
+			cache.AddRagEtaReportLlmOpToCache(v.RagEtaReportId, v.QuestionId, false)
+		}
+	}
+
+	//if !req.IsSelectAll {
+	//	list, err := obj.GetByIdList(req.RagEtaReportAbstractIdList)
+	//	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.RagEtaReportAbstractId)
+	//		}
+	//	}
+	//} else {
+	//	notIdMap := make(map[int]bool)
+	//	for _, v := range req.NotRagEtaReportAbstractIdList {
+	//		notIdMap[v] = true
+	//	}
+	//
+	//	_, list, err := getRagEtaReportAbstractList(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.RagEtaReportAbstractId] {
+	//				continue
+	//			}
+	//			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.RagEtaReportAbstractId)
+	//		}
+	//	}
+	//}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = `添加向量库中,请稍后查看`
+}

+ 85 - 1
controllers/llm/report.go

@@ -68,7 +68,7 @@ func (c *RagEtaReportController) ArticleList() {
 	}
 
 	obj := new(rag.RagEtaReport)
-	tmpTotal, list, err := obj.GetPageListByCondition(condition, pars, startSize, pageSize)
+	tmpTotal, list, err := obj.GetPageListByCondition(``, condition, pars, startSize, pageSize)
 	if err != nil {
 		br.Msg = "获取失败"
 		br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -235,6 +235,90 @@ func (c *RagEtaReportController) ArticleDel() {
 	br.Msg = "删除成功"
 }
 
+// AbstractList
+// @Title 摘要列表
+// @Description 我关注的接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   RagEtaReportId   query   int  true       "知识库与eta报告关联的id"
+// @Success 200 {object} []*rag.WechatPlatform
+// @router /eta_report/article/abstract/list [get]
+func (c *RagEtaReportController) AbstractList() {
+	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")
+	ragEtaReportId, _ := c.GetInt("RagEtaReportId")
+
+	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
+
+	var condition string
+	var pars []interface{}
+
+	condition += fmt.Sprintf(` AND a.%s  = ?  `, rag.RagEtaReportAbstractColumns.RagEtaReportId)
+	pars = append(pars, ragEtaReportId)
+
+	obj := new(rag.RagEtaReportAbstract)
+	tmpTotal, tmpList, err := obj.GetPageListByPlatformCondition(condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	total = tmpTotal
+
+	list := make([]rag.RagEtaReportAbstractView, 0)
+	for _, v := range tmpList {
+		list = append(list, v.ToView())
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := response.RagEtaReportItemAbstractListListResp{
+		List:   list,
+		Paging: page,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
 //// 修复历史ETA报告到知识库
 //func init() {
 //	idList, err := models.GetAllPublishReportId()

+ 0 - 2
controllers/llm/user_chat_controller.go

@@ -330,5 +330,3 @@ func (ucCtrl *UserChatController) ChatRecordList() {
 	br.Success = true
 	br.Msg = "获取聊天记录成功"
 }
-
-

+ 84 - 0
controllers/llm/wechat_platform.go

@@ -725,6 +725,90 @@ func (c *WechatPlatformController) ArticleDel() {
 	br.Msg = "删除成功"
 }
 
+// AbstractList
+// @Title 摘要列表
+// @Description 我关注的接口
+// @Param   PageSize   query   int  true       "每页数据条数"
+// @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
+// @Param   WechatArticleId   query   int  true       "文章id"
+// @Success 200 {object} []*rag.WechatPlatform
+// @router /wechat_platform/article/abstract/list [get]
+func (c *WechatPlatformController) AbstractList() {
+	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")
+	wechatArticleId, _ := c.GetInt("WechatArticleId")
+
+	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
+
+	var condition string
+	var pars []interface{}
+
+	condition += fmt.Sprintf(` AND a.%s  = ?  `, rag.WechatArticleAbstractColumns.WechatArticleID)
+	pars = append(pars, wechatArticleId)
+
+	obj := new(rag.WechatArticleAbstract)
+	tmpTotal, tmpList, err := obj.GetPageListByPlatformCondition(condition, pars, startSize, pageSize)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取失败,Err:" + err.Error()
+		return
+	}
+	total = tmpTotal
+
+	list := make([]rag.WechatArticleAbstractView, 0)
+	for _, v := range tmpList {
+		list = append(list, v.ToView())
+	}
+
+	page := paging.GetPaging(currentIndex, pageSize, total)
+	resp := response.WechatArticleItemAbstractListListResp{
+		List:   list,
+		Paging: page,
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "获取成功"
+	br.Data = resp
+}
+
 //func init() {
 //	//obj := rag.WechatPlatform{}
 //	//item, _ := obj.GetByID(2)

+ 13 - 4
controllers/material/material.go

@@ -786,22 +786,31 @@ func (this *MaterialController) List() {
 		switch this.Lang {
 		case utils.LANG_EN:
 			if len(keywordList) == 1 {
-				condition += ` AND  ( material_name_en LIKE '%` + keyword + `%' )`
+				likeKey := `%` + keyword + `%`
+
+				condition += ` AND  ( material_name_en LIKE ? )`
+				pars = append(pars, likeKey)
 			} else {
 				condition += ` AND  (`
 				for _, key := range keywordList {
-					condition += ` material_name_en LIKE '%` + key + `%' AND`
+					likeKey := `%` + key + `%`
+					condition += ` material_name_en LIKE ? AND`
+					pars = append(pars, likeKey)
 				}
 				condition = strings.TrimSuffix(condition, "AND")
 				condition += ` )`
 			}
 		default:
 			if len(keywordList) == 1 {
-				condition += ` AND  ( material_name LIKE '%` + keyword + `%' )`
+				likeKey := `%` + keyword + `%`
+				condition += ` AND  ( material_name LIKE ? )`
+				pars = append(pars, likeKey)
 			} else {
 				condition += ` AND  (`
 				for _, key := range keywordList {
-					condition += ` material_name LIKE '%` + key + `%' AND`
+					likeKey := `%` + key + `%`
+					condition += ` material_name LIKE ? AND `
+					pars = append(pars, likeKey)
 				}
 				condition = strings.TrimSuffix(condition, "AND")
 				condition += ` )`

+ 19 - 2
controllers/message.go

@@ -3,6 +3,7 @@ package controllers
 import (
 	"eta/eta_api/models"
 	"eta/eta_api/models/data_manage/data_manage_permission"
+	"eta/eta_api/models/data_manage/edb_inspection"
 	edbmonitor "eta/eta_api/models/edb_monitor"
 	"eta/eta_api/models/report_approve"
 	"fmt"
@@ -36,7 +37,7 @@ func (c *MessageController) UnReadMessageNum() {
 		return
 	}
 
-	var unReadReportNum, unReadDataPermissionNum, unReadEdbMonitorNum int
+	var unReadReportNum, unReadDataPermissionNum, unReadEdbMonitorNum, unReadEdbInspectionNum int
 
 	// 获取报告审批消息
 	{
@@ -93,8 +94,24 @@ func (c *MessageController) UnReadMessageNum() {
 		unReadEdbMonitorNum = unreadTotal
 	}
 
+	// 获取巡检消息
+	{
+		cond := ` AND admin_id = ? AND is_read = ?`
+		pars := make([]interface{}, 0)
+		pars = append(pars, sysUser.AdminId, 0)
+
+		messageOb := new(edb_inspection.EdbInspectionMessage)
+		unreadTotal, e := messageOb.GetCountByCondition(cond, pars)
+		if e != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取资产消息列表总数失败, Err: " + e.Error()
+			return
+		}
+		unReadEdbInspectionNum = int(unreadTotal)
+	}
+
 	// 汇总数
-	num := unReadReportNum + unReadDataPermissionNum + unReadEdbMonitorNum
+	num := unReadReportNum + unReadDataPermissionNum + unReadEdbMonitorNum + unReadEdbInspectionNum
 
 	br.Data = num
 	br.Ret = 200

+ 104 - 17
controllers/report_chapter.go

@@ -13,6 +13,7 @@ import (
 	"path"
 	"strconv"
 	"strings"
+	"sync"
 	"time"
 )
 
@@ -159,7 +160,6 @@ func (this *ReportController) AddChapter() {
 	//reportChapterInfo.CanvasColor = req.CanvasColor
 	//reportChapterInfo.HeadResourceId = req.HeadResourceId
 	//reportChapterInfo.EndResourceId = req.EndResourceId
-
 	err, errMsg := services.AddChapterBaseInfoAndPermission(reportInfo, reportChapterInfo, req.PermissionIdList, req.AdminIdList)
 	if err != nil {
 		br.Msg = "保存失败"
@@ -169,7 +169,9 @@ func (this *ReportController) AddChapter() {
 		br.ErrMsg = "保存失败,Err:" + err.Error()
 		return
 	}
-
+	if reportInfo.ReportLayout == 3 {
+		br.Data = reportInfo.FreeLayoutConfig
+	}
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "保存成功"
@@ -311,10 +313,6 @@ func (this *ReportController) EditDayWeekChapter() {
 		br.Msg = "报告章节ID有误"
 		return
 	}
-	if req.Content == "" {
-		br.Msg = "请输入内容"
-		return
-	}
 
 	// 获取章节详情
 	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
@@ -331,7 +329,14 @@ func (this *ReportController) EditDayWeekChapter() {
 		br.ErrMsg = "报告信息有误, Err: " + err.Error()
 		return
 	}
-
+	if req.Content == "" && reportInfo.ReportLayout != 3 {
+		br.Msg = "请输入内容"
+		return
+	}
+	if reportInfo.ReportLayout == 3 && req.FreeLayoutConfig == "" {
+		br.Msg = "请输入自由布局配置"
+		return
+	}
 	// 操作权限校验
 	hasAuth, msg, errMsg, isSendEmail := checkOpPermission(sysUser, reportInfo, reportChapterInfo, true, this.Lang)
 	if !hasAuth {
@@ -431,11 +436,57 @@ func (this *ReportController) EditDayWeekChapter() {
 			})
 		}
 	}
-	err = models.UpdateChapterAndTicker(reportInfo, reportChapterInfo, updateCols, tickerList)
-	if err != nil {
-		br.Msg = "保存失败"
-		br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
-		return
+	if reportInfo.ReportLayout == 3 {
+		//对自由布局的数据做一个处理
+		//自由布局更新每页的数据
+		ormList := report.ToOrmViewList(req.FreeLayoutContentPages, true, reportInfo.Id, reportChapterId)
+		var wg sync.WaitGroup
+		wg.Add(len(ormList))
+		for _, v := range ormList {
+			go func(v *report.ReportFreeLayout) {
+				defer wg.Done()
+				content := v.Content
+				if content != "" {
+					// 处理关联excel的表格id
+					content = services.HandleReportContentTable(reportInfo.Id, content)
+					content = services.HandleReportContent(content, "del", nil)
+					e := utils.ContentXssCheck(content)
+					if e != nil {
+						br.Msg = "存在非法标签"
+						br.ErrMsg = "存在非法标签, Err: " + e.Error()
+						return
+					}
+					contentClean, e := services.FilterReportContentBr(content)
+					if e != nil {
+						br.Msg = "内容去除前后空格失败"
+						br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+						return
+					}
+					content = contentClean
+					if v.ContentStruct != `` {
+						v.ContentStruct = services.HandleReportContentStructTable(reportChapterInfo.ReportId, v.ContentStruct)
+						v.ContentStruct = services.HandleReportContentStruct(v.ContentStruct, "del", nil)
+					}
+					v.Content = html.EscapeString(content)
+					v.ContentStruct = html.EscapeString(v.ContentStruct)
+				}
+			}(v)
+		}
+		wg.Wait()
+		reportInfo.FreeLayoutConfig = req.FreeLayoutConfig
+		err = models.UpdateChapterFreeLayoutContentPage(reportInfo, reportChapterInfo, updateCols, tickerList, ormList)
+		if err != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "保存失败,Err:" + err.Error()
+			return
+		}
+	} else {
+		err = models.UpdateChapterAndTicker(reportInfo, reportChapterInfo, updateCols, tickerList)
+		if err != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
+			return
+		}
 	}
 
 	// 标记更新中
@@ -537,7 +588,9 @@ func (this *ReportController) DelChapter() {
 		br.ErrMsg = "删除失败,Err:" + err.Error()
 		return
 	}
-
+	if reportInfo.ReportLayout == 3 {
+		go report.DeleteChapters(reportInfo.Id, reportChapterInfo.ReportChapterId)
+	}
 	// 备份关键数据
 	chapters := make([]*models.ReportChapter, 0)
 	chapters = append(chapters, reportChapterInfo)
@@ -778,13 +831,38 @@ func (this *ReportController) GetDayWeekChapter() {
 		br.ErrMsg = "无操作权限"
 		return
 	}
+	var pageNum int
+	var pages []*report.ContentPage
+	if reportInfo.ReportLayout == 3 {
+		pages, err = report.GetSingleFreeLayoutChapterPagesByReportId(reportInfo.Id, reportChapterId)
+		if err != nil {
+			br.Msg = "获取自由布局页面列表"
+			br.ErrMsg = "获取自由布局页面列表,Err:" + err.Error()
+			return
+		}
+		if len(pages) == 0 {
+			//获取当前章节前置章节总页数
+			pageNum, err = report.GetPrevFreeLayoutChaptersPagesByChapterId(reportInfo.Id, reportChapterId)
+			if err != nil {
+				br.Msg = "获取自由布局前置章节总页数"
+				br.ErrMsg = "获取自由布局前置章节总页数,Err:" + err.Error()
+				return
+			}
 
+		} else {
+			for _, page := range pages {
+				page.Content = html.UnescapeString(page.Content)
+				page.ContentStruct = html.UnescapeString(page.ContentStruct)
+				page.Content = services.HandleReportContentTable(page.ReportId, page.Content)
+				page.ContentStruct = services.HandleReportContentStructTable(page.ReportId, page.ContentStruct)
+			}
+		}
+	}
 	chapterItem.Content = html.UnescapeString(chapterItem.Content)
 	chapterItem.ContentSub = html.UnescapeString(chapterItem.ContentSub)
 	chapterItem.ContentStruct = html.UnescapeString(chapterItem.ContentStruct)
 	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 = "获取失败"
@@ -796,6 +874,12 @@ func (this *ReportController) GetDayWeekChapter() {
 		tokenMap := make(map[string]string)
 		chapterItem.Content = services.HandleReportContent(chapterItem.Content, "add", tokenMap)
 		chapterItem.ContentStruct = services.HandleReportContentStruct(chapterItem.ContentStruct, "add", tokenMap)
+		if reportInfo.ReportLayout == 3 {
+			for _, page := range pages {
+				page.Content = services.HandleReportContent(page.Content, "add", tokenMap)
+				page.ContentStruct = services.HandleReportContentStruct(page.ContentStruct, "add", tokenMap)
+			}
+		}
 	}
 
 	// 授权用户列表map
@@ -833,9 +917,12 @@ func (this *ReportController) GetDayWeekChapter() {
 	}
 
 	resp := models.ReportChapterItemResp{
-		ReportChapterItem: *chapterItem,
-		GrandAdminIdList:  chapterGrantIdList,
-		PermissionIdList:  chapterPermissionIdList,
+		FreeLayoutContentPages: pages,
+		FreeLayoutConfig:       reportInfo.FreeLayoutConfig,
+		PreviousPagesNum:       pageNum,
+		ReportChapterItem:      *chapterItem,
+		GrandAdminIdList:       chapterGrantIdList,
+		PermissionIdList:       chapterPermissionIdList,
 	}
 
 	// 获取当前编辑状态

+ 198 - 48
controllers/report_v2.go

@@ -18,6 +18,7 @@ import (
 	"os"
 	"strconv"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/rdlucklib/rdluck_tools/paging"
@@ -476,7 +477,6 @@ func (this *ReportController) Add() {
 	item.ReportVersion = req.ReportVersion
 	item.AdminId = sysUser.AdminId
 	item.AdminRealName = sysUser.RealName
-
 	item.ClassifyIdThird = req.ClassifyIdThird
 	item.ClassifyNameThird = classifyMap[req.ClassifyIdThird]
 
@@ -612,7 +612,6 @@ func (this *ReportController) Edit() {
 		br.ErrMsg = "保存失败,Err:" + err.Error()
 		return
 	}
-
 	reportCode := utils.MD5(strconv.Itoa(int(req.ReportId)))
 	resp := new(models.EditResp)
 	resp.ReportId = req.ReportId
@@ -667,6 +666,7 @@ func (this *ReportController) Detail() {
 		return
 	}
 	chapterList := make([]*models.ReportChapter, 0)
+	pageList := make([]*report.ContentPage, 0)
 	if item.HasChapter == 1 {
 		// 获取章节内容
 		tmpChapterList, err := models.GetPublishedChapterListByReportId(item.Id)
@@ -685,7 +685,40 @@ func (this *ReportController) Detail() {
 			}
 		}
 
+		if item.ReportLayout == 3 {
+			var chapterMap = make(map[int]bool)
+			for _, chapter := range tmpChapterList {
+				chapterMap[chapter.ReportChapterId] = true
+			}
+			pages, err := report.GetFreeLayoutChapterPagesByReportId(item.Id)
+			if err != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = "获取自由布局内容页失败, Err: " + err.Error()
+				return
+			}
+			for _, page := range pages {
+				if chapterMap[page.ReportChapterId] {
+					page.Content = html.UnescapeString(page.Content)
+					page.ContentStruct = html.UnescapeString(page.ContentStruct)
+					pageList = append(pageList, page)
+				}
+			}
+		}
 		//item.Abstract = item.Title
+	} else {
+		if item.ReportLayout == 3 {
+			pages, err := report.GetFreeLayoutPagesByReportId(item.Id)
+			if err != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = "获取自由布局内容页失败, Err: " + err.Error()
+				return
+			}
+			for _, page := range pages {
+				page.Content = html.UnescapeString(page.Content)
+				page.ContentStruct = html.UnescapeString(page.ContentStruct)
+				pageList = append(pageList, page)
+			}
+		}
 	}
 	item.Content = html.UnescapeString(item.Content)
 	item.ContentSub = html.UnescapeString(item.ContentSub)
@@ -739,12 +772,18 @@ func (this *ReportController) Detail() {
 			v.Content = services.HandleReportContent(v.Content, "add", tokenMap)
 			v.ContentStruct = services.HandleReportContentStruct(v.ContentStruct, "add", tokenMap)
 		}
-
+		if item.ReportLayout == 3 {
+			for _, page := range pageList {
+				page.Content = services.HandleReportContent(page.Content, "add", tokenMap)
+				page.ContentStruct = services.HandleReportContentStruct(page.ContentStruct, "add", tokenMap)
+			}
+		}
 	}
 
 	resp := &models.ReportDetailView{
-		ReportDetail: item,
-		ChapterList:  chapterList,
+		ReportDetail:           item,
+		ChapterList:            chapterList,
+		FreeLayoutContentPages: pageList,
 	}
 	br.Ret = 200
 	br.Success = true
@@ -800,7 +839,12 @@ func (this *ReportController) SaveReportContent() {
 		br.IsSendEmail = false
 		return
 	}
-
+	if reportInfo.ReportLayout == 3 && req.FreeLayoutConfig == "" {
+		br.Msg = "自由布局配置为空"
+		br.ErrMsg = "自由布局配置为空"
+		br.IsSendEmail = false
+		return
+	}
 	// 标记更新中
 	{
 		markStatus, err := services.UpdateReportEditMark(req.ReportId, 0, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
@@ -821,7 +865,7 @@ func (this *ReportController) SaveReportContent() {
 			content = this.GetString("Content")
 		}
 		content = services.HandleReportContent(content, "del", nil)
-		if content != "" {
+		if content != "" || reportInfo.ReportLayout == 3 {
 			e := utils.ContentXssCheck(content)
 			if e != nil {
 				br.Msg = "存在非法标签"
@@ -843,6 +887,7 @@ func (this *ReportController) SaveReportContent() {
 				go alarm_msg.SendAlarmMsg("解析 ContentSub 失败,Err:"+err.Error(), 3)
 				//utils.SendEmail(utils.APPNAME+"失败提醒", "解析 ContentSub 失败,Err:"+err.Error(), utils.EmailSendToUsers)
 			}
+
 			reportInfo.Content = html.EscapeString(content)
 			reportInfo.ContentSub = html.EscapeString(contentSub)
 			reportInfo.ContentStruct = html.EscapeString(req.ContentStruct)
@@ -853,15 +898,58 @@ func (this *ReportController) SaveReportContent() {
 			reportInfo.EndResourceId = req.EndResourceId
 			reportInfo.ModifyTime = time.Now()
 			reportInfo.ContentModifyTime = time.Now()
-			updateCols := []string{"Content", "ContentSub", "ContentStruct", "HeadImg", "EndImg", "CanvasColor", "HeadResourceId", "EndResourceId", "ModifyTime", "ContentModifyTime"}
-			err = reportInfo.UpdateReport(updateCols)
-			if err != nil {
-				br.Msg = "保存失败"
-				br.ErrMsg = "保存失败,Err:" + err.Error()
-				return
+			if reportInfo.ReportLayout == 3 {
+				reportInfo.FreeLayoutConfig = req.FreeLayoutConfig
+				//自由布局更新每页的数据
+				ormList := report.ToOrmViewList(req.FreeLayoutContentPages, false, reportInfo.Id, 0)
+				var wg sync.WaitGroup
+				wg.Add(len(ormList))
+				for _, v := range ormList {
+					go func(v *report.ReportFreeLayout) {
+						defer wg.Done()
+						pageContent := v.Content
+						pageContent = services.HandleReportContent(pageContent, "del", nil)
+						if pageContent != "" {
+							pageErr := utils.ContentXssCheck(pageContent)
+							if pageErr != nil {
+								br.Msg = "存在非法标签"
+								br.ErrMsg = "存在非法标签, Err: " + pageErr.Error()
+								return
+							}
+							var pageContentClean string
+							pageContentClean, pageErr = services.FilterReportContentBr(pageContent)
+							if pageErr != nil {
+								br.Msg = "内容去除前后空格失败"
+								br.ErrMsg = "内容去除前后空格失败, Err: " + pageErr.Error()
+								return
+							}
+							pageContent = pageContentClean
+							v.ContentStruct = services.HandleReportContentStruct(v.ContentStruct, "del", nil)
+							v.Content = html.EscapeString(pageContent)
+							v.ContentStruct = html.EscapeString(v.ContentStruct)
+						}
+					}(v)
+				}
+				wg.Wait()
+				err = models.InsertOrUpdateReportFreeLayoutContentPage(reportInfo, ormList)
+				if err != nil {
+					br.Msg = "保存失败"
+					br.ErrMsg = "保存失败,Err:" + err.Error()
+					return
+				}
+			} else {
+				updateCols := []string{"Content", "ContentSub", "ContentStruct", "HeadImg", "EndImg", "CanvasColor", "HeadResourceId", "EndResourceId", "ModifyTime", "ContentModifyTime"}
+				err = reportInfo.UpdateReport(updateCols)
+				if err != nil {
+					br.Msg = "保存失败"
+					br.ErrMsg = "保存失败,Err:" + err.Error()
+					return
+				}
 			}
+
 			go models.AddReportSaveLog(reportId, this.SysUser.AdminId, reportInfo.Content, reportInfo.ContentSub, reportInfo.ContentStruct, reportInfo.CanvasColor, this.SysUser.AdminName, reportInfo.HeadResourceId, reportInfo.EndResourceId)
 		}
+
 	}
 
 	resp := new(models.SaveReportContentResp)
@@ -1027,17 +1115,21 @@ func (this *ReportController) BaseDetail() {
 		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
-	}*/
+	/*
+	   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 = "获取参数失败!"
@@ -1064,7 +1156,28 @@ func (this *ReportController) BaseDetail() {
 
 	reportInfo.Content = html.UnescapeString(reportInfo.Content)
 	reportInfo.ContentSub = html.UnescapeString(reportInfo.ContentSub)
-
+	if reportInfo.ReportLayout == 3 {
+		if reportInfo.HeadResourceId > 0 {
+			headResource, err := smart_report.GetResourceItemById(reportInfo.HeadResourceId)
+			if err != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = "获取资源库版头失败, Err: " + err.Error()
+				return
+			}
+			reportInfo.HeadImg = headResource.ImgUrl
+			reportInfo.HeadStyle = headResource.Style
+		}
+		if reportInfo.EndResourceId > 0 {
+			headResource, err := smart_report.GetResourceItemById(reportInfo.EndResourceId)
+			if err != nil {
+				br.Msg = "操作失败"
+				br.ErrMsg = "获取资源库版尾失败, Err: " + err.Error()
+				return
+			}
+			reportInfo.EndImg = headResource.ImgUrl
+			reportInfo.EndStyle = headResource.Style
+		}
+	}
 	grandAdminList := make([]models.ReportDetailViewAdmin, 0)
 	permissionList := make([]models.ReportDetailViewPermission, 0)
 
@@ -1182,11 +1295,12 @@ func (this *ReportController) EditLayoutImg() {
 		br.ErrMsg = "参数解析失败,Err:" + err.Error()
 		return
 	}
-	//if req.Content == "" {
-	//	br.Msg = "报告内容不能为空"
-	//	return
-	//}
-	//更新标记key
+	//	if req.Content == "" {
+	//		br.Msg = "报告内容不能为空"
+	//		return
+	//	}
+	//
+	// 更新标记key
 	markStatus, err := services.UpdateReportEditMark(int(req.ReportId), 0, sysUser.AdminId, 1, sysUser.RealName, this.Lang)
 	if err != nil {
 		br.Msg = err.Error()
@@ -1194,7 +1308,7 @@ func (this *ReportController) EditLayoutImg() {
 	}
 	if markStatus.Status == 1 {
 		br.Msg = markStatus.Msg
-		//br.Ret = 202 //202 服务器已接受请求,但尚未处理。
+		// br.Ret = 202 //202 服务器已接受请求,但尚未处理。
 		return
 	}
 
@@ -1466,10 +1580,28 @@ func (this *ReportController) PrePublishReport() {
 			}
 		}
 	} else {
-		if reportDetail.Content == "" {
-			br.Msg = "报告内容为空,不可设置定时发布"
-			br.ErrMsg = "报告内容为空,不可设置定时发布,report_id:" + strconv.Itoa(reportDetail.Id)
-			return
+		if reportDetail.ReportLayout != 3 {
+			if reportDetail.Content == "" {
+				br.Msg = "报告内容为空,不可设置定时发布"
+				br.ErrMsg = "报告内容为空,不可设置定时发布,report_id:" + strconv.Itoa(reportDetail.Id)
+				return
+			}
+		} else {
+			pages, err := report.GetFreeLayoutChapterPagesByReportId(reportDetail.Id)
+			if err != nil {
+				br.Msg = "获取自由布局报告失败,不可设置定时发布"
+				br.ErrMsg = "获取自由布局报告失败,不可设置定时发布,Err:" + err.Error()
+				return
+			}
+			var content string
+			for _, page := range pages {
+				content += page.Content
+			}
+			if content == "" {
+				br.Msg = "自由布局报告内容为空,不可设置定时发布"
+				br.ErrMsg = "自由布局报告内容为空,不可设置定时发布,report_id:" + strconv.Itoa(reportDetail.Id)
+				return
+			}
 		}
 	}
 
@@ -1568,10 +1700,28 @@ func (this *ReportController) SubmitApprove() {
 			}
 		}
 	} else {
-		if reportItem.Content == "" {
-			br.Msg = "报告内容为空,不可提交"
-			br.ErrMsg = "报告内容为空,不可提交,report_id:" + strconv.Itoa(reportItem.Id)
-			return
+		if reportItem.ReportLayout != 3 {
+			if reportItem.Content == "" {
+				br.Msg = "报告内容为空,不可提交"
+				br.ErrMsg = "报告内容为空,不可提交,report_id:" + strconv.Itoa(reportItem.Id)
+				return
+			}
+		} else {
+			pages, err := report.GetFreeLayoutChapterPagesByReportId(reportItem.Id)
+			if err != nil {
+				br.Msg = "获取自由布局报告失败,不可提交"
+				br.ErrMsg = "获取自由布局报告失败,不可提交,Err:" + err.Error()
+				return
+			}
+			var content string
+			for _, page := range pages {
+				content += page.Content
+			}
+			if content == "" {
+				br.Msg = "自由布局报告内容为空,不可提交"
+				br.ErrMsg = "自由布局报告内容为空,不可提交,report_id:" + strconv.Itoa(reportItem.Id)
+				return
+			}
 		}
 	}
 
@@ -1827,10 +1977,10 @@ func (this *ReportCommonController) ShareTransform() {
 // @author: Roc
 // @datetime 2024-06-21 09:19:05
 func init() {
-	//fixApproveRecord()
-	//fixChapterPermission()
-	//fixReportEs()
-	//fixSmartReport()
+	// fixApproveRecord()
+	// fixChapterPermission()
+	// fixReportEs()
+	// fixSmartReport()
 }
 
 // 修复研报审批数据
@@ -1846,7 +1996,7 @@ func fixApproveRecord() {
 		return
 	}
 	for _, recordItem := range list {
-		//fmt.Println(recordItem)
+		// fmt.Println(recordItem)
 		recordItem.NodeState = recordItem.State
 		recordItem.NodeApproveUserId = recordItem.ApproveUserId
 		recordItem.NodeApproveUserName = recordItem.ApproveUserName
@@ -1896,7 +2046,7 @@ func fixChapterPermission() {
 		}
 	}
 
-	//notIdList := []int{9675, 9675, 9740, 9749, 9768, 9773, 9791, 9792, 9793, 9850, 9851, 9852, 9852, 9852, 9853, 9854, 9856, 9857, 9857, 9858, 9859, 9860, 9861, 9862, 9862, 9863, 9866}
+	// notIdList := []int{9675, 9675, 9740, 9749, 9768, 9773, 9791, 9792, 9793, 9850, 9851, 9852, 9852, 9852, 9853, 9854, 9856, 9857, 9857, 9858, 9859, 9860, 9861, 9862, 9862, 9863, 9866}
 	notIdList := []int{}
 	allReportChapterList, err := models.GetAllReportChapter()
 	if err != nil {
@@ -2019,7 +2169,7 @@ func fixSmartReport() {
 	for _, v := range list {
 		fmt.Println(v)
 		addList = append(addList, &models.Report{
-			//Id:                  0,
+			// Id:                  0,
 			AddType:            1,
 			ClassifyIdFirst:    v.ClassifyIdFirst,
 			ClassifyNameFirst:  v.ClassifyNameFirst,
@@ -2035,7 +2185,7 @@ func fixSmartReport() {
 			PublishTime:        v.PublishTime,
 			Stage:              v.Stage,
 			MsgIsSend:          v.MsgIsSend,
-			//ThsMsgIsSend:        v.Tha,
+			// ThsMsgIsSend:        v.Tha,
 			Content:             v.Content,
 			VideoUrl:            v.VideoUrl,
 			VideoName:           v.VideoName,
@@ -2131,7 +2281,7 @@ func fixSmartReport() {
 func initPdf() {
 	inFile := "anNNgk3Bbi4LRULwcJgNOPrREYh5.pdf"
 	f2, err := services.GeneralWaterMarkPdf(inFile, "颜鹏 - 18170239278")
-	//f2, err := services.GeneralWaterMarkPdf(inFile, "上周美国馏分油库存累库95万桶,馏分油表需环比下降(-25.6万桶/日)。本期馏分油产量继续抬升,在供增需减的环比变动下库存持续累库。馏分油供应的增加我们认为可能和进口的油种有关,今年以来美国进口的中重质原油占比不断走高,尤其是5")
+	// f2, err := services.GeneralWaterMarkPdf(inFile, "上周美国馏分油库存累库95万桶,馏分油表需环比下降(-25.6万桶/日)。本期馏分油产量继续抬升,在供增需减的环比变动下库存持续累库。馏分油供应的增加我们认为可能和进口的油种有关,今年以来美国进口的中重质原油占比不断走高,尤其是5")
 	if err != nil {
 		fmt.Println("生成失败,ERR:", err)
 		return

+ 6 - 1
controllers/sandbox/sandbox.go

@@ -1727,7 +1727,12 @@ func (this *SandboxController) ListV2() {
 		//pars = append(pars, chartClassifyId)
 	}
 	if keyWord != "" {
-		condition += ` AND  ( name LIKE '%` + keyWord + `%' )`
+
+		//condition += ` AND  ( name LIKE '%` + keyWord + `%' )`
+		likeKey := `%` + keyWord + `%`
+
+		condition += ` AND  name LIKE ? `
+		pars = append(pars, likeKey)
 	}
 
 	//只看我的

+ 7 - 0
controllers/sys_role.go

@@ -724,6 +724,7 @@ func (this *SysRoleController) SystemConfig() {
 	}, system.BusinessConf{
 		ConfKey: "LoginUrl",
 		ConfVal: conf["LoginUrl"],
+
 	},
         system.BusinessConf{
         ConfKey: "KnowledgeBaseName",
@@ -733,6 +734,12 @@ func (this *SysRoleController) SystemConfig() {
 			ConfVal: conf["NotBackendGenerate"],
 		})
 
+	}, system.BusinessConf{
+		ConfKey: models.KnowledgeBaseName,
+		ConfVal: conf[models.KnowledgeBaseName],
+	})
+
+
 	osc := system.BusinessConf{
 		ConfKey: "ObjectStorageClient",
 		ConfVal: utils.ObjectStorageClient,

+ 15 - 0
controllers/sys_user.go

@@ -371,6 +371,21 @@ func (this *SysUserController) AuthCodeLogin() {
 		return
 	}
 
+	// 查询一下用户是否被禁用
+	sysAdmin, e := system.GetSysUserById(data.AdminId)
+	if e != nil && !utils.IsErrNoRow(e) {
+		br.Msg = "获取失败"
+		br.ErrMsg = fmt.Sprintf("获取用户信息失败, %v", e)
+		return
+	}
+	if sysAdmin != nil && sysAdmin.Enabled != 1 {
+		br.Ret = 408
+		br.Msg = "您的账号已被禁用,如需登录,请联系管理员"
+		j, _ := json.Marshal(data)
+		br.ErrMsg = fmt.Sprintf("AuthCodeLogin, 账户信息异常:%s", j)
+		return
+	}
+
 	br.Data = data
 	br.Ret = 200
 	br.Success = true

+ 12 - 62
controllers/user_login.go

@@ -1012,73 +1012,23 @@ func (this *UserLoginController) BaseInfo() {
 		this.ServeJSON()
 	}()
 
-	icp, e := models.GetBusinessConfByKey("ICPLicense")
-	if e != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取商家配置失败, Err: " + e.Error()
-		return
-	}
-
-	title, e := models.GetBusinessConfByKey("ETATitle")
-	if e != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取商家配置失败, Err: " + e.Error()
-		return
+	configKeys := []string{
+		"ICPLicense", "ETATitle", "TabName", "LogoCN", "LogoEN", "LogoCNMini", "LogoENMini", "LoginLeftImg",
+		"ETASubTitleCN", "ETASubTitleEN",
 	}
-
-	tabName, e := models.GetBusinessConfByKey("TabName")
-	if e != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取商家配置失败, Err: " + e.Error()
-		return
-	}
-
-	logoCn, e := models.GetBusinessConfByKey("LogoCN")
+	configOb := new(models.BusinessConf)
+	list, e := configOb.GetItemsByCondition(``, make([]interface{}, 0), []string{}, "")
 	if e != nil {
 		br.Msg = "获取失败"
-		br.ErrMsg = "获取商家配置失败, Err: " + e.Error()
+		br.ErrMsg = fmt.Sprintf("获取配置失败, %v", e)
 		return
 	}
-
-	logoEn, e := models.GetBusinessConfByKey("LogoEN")
-	if e != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取商家配置失败, Err: " + e.Error()
-		return
-	}
-
-	logoCnMini, e := models.GetBusinessConfByKey("LogoCNMini")
-	if e != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取商家配置失败, Err: " + e.Error()
-		return
-	}
-
-	logoEnMini, e := models.GetBusinessConfByKey("LogoENMini")
-	if e != nil {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取商家配置失败, Err: " + e.Error()
-		return
-	}
-
-	type BaseInfoResp struct {
-		Icp        *models.BusinessConf `description:"Icp信息"`
-		ETATitle   *models.BusinessConf `description:"eta系统名称"`
-		TabName    *models.BusinessConf `description:"tab页名称"`
-		LogoCn     *models.BusinessConf `description:"中文logo"`
-		LogoEn     *models.BusinessConf `description:"英文logo"`
-		LogoCnMini *models.BusinessConf `description:"中文logoMini"`
-		LogoEnMini *models.BusinessConf `description:"英文logoMini"`
-	}
-
-	resp := BaseInfoResp{
-		Icp:        icp,
-		ETATitle:   title,
-		TabName:    tabName,
-		LogoCn:     logoCn,
-		LogoEn:     logoEn,
-		LogoCnMini: logoCnMini,
-		LogoEnMini: logoEnMini,
+	resp := make(map[string]*models.BusinessConf)
+	for _, v := range list {
+		if !utils.InArrayByStr(configKeys, v.ConfKey) {
+			continue
+		}
+		resp[v.ConfKey] = v
 	}
 
 	br.Data = resp

+ 38 - 0
global/websocket.go

@@ -0,0 +1,38 @@
+package global
+
+import (
+	"errors"
+	"eta/eta_api/utils"
+	"strconv"
+	"time"
+
+	"github.com/gorilla/websocket"
+)
+
+var MonitorMessageConn = make(map[int]*websocket.Conn)
+
+var (
+	EDB_MONITOR_MESSAGE_CONNECT_CACHE = "edb_monitor_message_cache:"
+)
+
+func EdbMonitorMessageHealth(adminId int) (isClose bool, err error) {
+	conn := MonitorMessageConn[adminId]
+	if conn == nil {
+		err = errors.New("no connection")
+		isClose = true
+		return
+	}
+	_, msg, err := conn.ReadMessage()
+	if err != nil {
+		isClose = true
+		return
+	}
+	if string(msg) == "ping" {
+		healthKey := EDB_MONITOR_MESSAGE_CONNECT_CACHE + strconv.Itoa(adminId)
+		err = utils.Rc.Put(healthKey, "1", time.Minute*1)
+		if err != nil {
+			return
+		}
+	}
+	return
+}

+ 1 - 0
models/bi_dashboard/bi_dashboard.go

@@ -108,6 +108,7 @@ type AddDashboardListReq struct {
 	Type       int
 	UniqueCode string
 	Sort       int
+	Conf       string
 }
 
 type EditDashboardReq struct {

+ 1 - 0
models/bi_dashboard/bi_dashboard_detail.go

@@ -10,6 +10,7 @@ type BiDashboardDetail struct {
 	BiDashboardDetailId int       `orm:"column(bi_dashboard_detail_id);pk" gorm:"primaryKey" ` // bi看板id
 	BiDashboardId       int       `gorm:"column:bi_dashboard_id" `                             // 看板id
 	Type                int       `gorm:"column:type" `                                        // 1图表 2表格
+	Conf                string    `gorm:"column:conf" `                                        // 配置信息
 	UniqueCode          string    `gorm:"column:unique_code;size:32;not null" `                // 报告唯一编码
 	Sort                int       `gorm:"column:sort" `                                        // 排序字段
 	CreateTime          time.Time `gorm:"column:create_time" `                                 // 创建时间

+ 5 - 0
models/business_conf.go

@@ -61,10 +61,12 @@ const (
 	BusinessConfEsIndexNameDataSource        = "EsIndexNameDataSource"        // ES索引名称-数据源
 	LLMInitConfig                            = "llmInitConfig"
 	KnowledgeBaseName                        = "KnowledgeBaseName"                // 摘要库
+	PrivateKnowledgeBaseName                 = "PrivateKnowledgeBaseName"         // 私有摘要库
 	KnowledgeArticleName                     = "KnowledgeArticleName"             // 原文库
 	BusinessConfEsWechatArticle              = "EsIndexNameWechatArticle"         // ES索引名称-微信文章
 	BusinessConfEsWechatArticleAbstract      = "EsIndexNameWechatArticleAbstract" // ES索引名称-微信文章摘要
 	BusinessConfEsRagQuestion                = "EsIndexNameRagQuestion"           // ES索引名称-知识库问题
+	BusinessConfEsRagEtaReportAbstract       = "EsIndexNameRagEtaReportAbstract"  // ES索引名称-eta报告摘要
 	BusinessConfIsOpenChartExpired           = "IsOpenChartExpired"               // 是否开启图表有效期鉴权/报告禁止复制
 	BusinessConfReportChartExpiredTime       = "ReportChartExpiredTime"           // 图表有效期鉴权时间,单位:分钟
 	BusinessConfOssUrlReplace                = "OssUrlReplace"                    // OSS地址替换-兼容内网客户用
@@ -282,6 +284,9 @@ func InitBusinessConf() {
 	if BusinessConfMap[BusinessConfEsRagQuestion] != "" {
 		utils.EsRagQuestionName = BusinessConfMap[BusinessConfEsRagQuestion]
 	}
+	if BusinessConfMap[BusinessConfEsRagEtaReportAbstract] != "" {
+		utils.EsRagEtaReportAbstractName = BusinessConfMap[BusinessConfEsRagEtaReportAbstract]
+	}
 	confStr := BusinessConfMap[LLMInitConfig]
 	if confStr != "" {
 		var config LLMConfig

+ 7 - 0
models/chart_permission.go

@@ -284,6 +284,7 @@ type SimpleChartPermission struct {
 	ChartPermissionName string                   `description:"品种名称"`
 	Sort                int                      `description:"排序"`
 	Children            []*SimpleChartPermission `description:"子分类"`
+	//IsLatestEdit        bool                     `description:"是否是最后一级"`
 }
 
 func FormatChartPermission2Simple(origin *ChartPermission) (item *SimpleChartPermission) {
@@ -321,3 +322,9 @@ func GetChartPermissionByIdList(permissionIdList []int) (items []*ChartPermissio
 	err = global.DbMap[utils.DbNameReport].Raw(sql, permissionIdList).Find(&items).Error
 	return
 }
+
+type FaCalendarPermissionResp struct {
+	List                  []*SimpleChartPermission
+	CheckedPermissionId   int
+	CheckedPermissionName string
+}

+ 12 - 12
models/data_manage/base_from_gpr_risk.go

@@ -48,10 +48,10 @@ type BaseFromGprRiskIndexList struct {
 }
 
 func (baseFromGprRiskIndexList *BaseFromGprRiskIndexList) AfterFind(tx *gorm.DB) (err error) {
-			baseFromGprRiskIndexList.CreateTime = utils.GormDateStrToDateTimeStr(baseFromGprRiskIndexList.CreateTime)
-			baseFromGprRiskIndexList.ModifyTime = utils.GormDateStrToDateTimeStr(baseFromGprRiskIndexList.ModifyTime)
-			baseFromGprRiskIndexList.StartDate = utils.GormDateStrToDateStr(baseFromGprRiskIndexList.StartDate)
-			baseFromGprRiskIndexList.EndDate = utils.GormDateStrToDateStr(baseFromGprRiskIndexList.EndDate)
+	baseFromGprRiskIndexList.CreateTime = utils.GormDateStrToDateTimeStr(baseFromGprRiskIndexList.CreateTime)
+	baseFromGprRiskIndexList.ModifyTime = utils.GormDateStrToDateTimeStr(baseFromGprRiskIndexList.ModifyTime)
+	baseFromGprRiskIndexList.StartDate = utils.GormDateStrToDateStr(baseFromGprRiskIndexList.StartDate)
+	baseFromGprRiskIndexList.EndDate = utils.GormDateStrToDateStr(baseFromGprRiskIndexList.EndDate)
 	return
 }
 
@@ -180,9 +180,9 @@ func GetGprRiskDataDataTimeByIndexId(indexIdList []int) (items []string, err err
 	}
 	sql := ` SELECT DISTINCT data_time FROM base_from_gpr_risk_data WHERE base_from_gpr_risk_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)
-		}
+	for i, item := range items {
+		items[i] = utils.GormDateStrToDateStr(item)
+	}
 	return
 }
 
@@ -198,9 +198,9 @@ type BaseFromGprRiskData struct {
 }
 
 func (baseFromGprRiskData *BaseFromGprRiskData) AfterFind(tx *gorm.DB) (err error) {
-			baseFromGprRiskData.ModifyTime = utils.GormDateStrToDateTimeStr(baseFromGprRiskData.ModifyTime)
-			baseFromGprRiskData.CreateTime = utils.GormDateStrToDateTimeStr(baseFromGprRiskData.CreateTime)
-			baseFromGprRiskData.DataTime = utils.GormDateStrToDateStr(baseFromGprRiskData.DataTime)
+	baseFromGprRiskData.ModifyTime = utils.GormDateStrToDateTimeStr(baseFromGprRiskData.ModifyTime)
+	baseFromGprRiskData.CreateTime = utils.GormDateStrToDateTimeStr(baseFromGprRiskData.CreateTime)
+	baseFromGprRiskData.DataTime = utils.GormDateStrToDateStr(baseFromGprRiskData.DataTime)
 	return
 }
 
@@ -223,12 +223,12 @@ type BatchCheckGprRiskEdbReq struct {
 }
 
 // GetGprRiskItemList 模糊查询GprRisk数据库指标列表
-func GetGprRiskItemList(condition string) (items []*BaseFromGprRiskIndexSearchItem, err error) {
+func GetGprRiskItemList(condition string, pars []interface{}) (items []*BaseFromGprRiskIndexSearchItem, err error) {
 	sql := "SELECT * FROM base_from_gpr_risk_index  WHERE 1=1"
 	if condition != "" {
 		sql += condition
 	}
-	err = global.DbMap[utils.DbNameIndex].Raw(sql).Find(&items).Error
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars).Find(&items).Error
 	return
 }
 

+ 55 - 55
models/data_manage/base_from_purang.go

@@ -14,39 +14,39 @@ import (
 
 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
+	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:"-"`
+	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) {
@@ -64,18 +64,18 @@ type BaseFromPurangIndexSearchList struct {
 
 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:"-"`
+	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 {
@@ -191,12 +191,12 @@ func GetPurangDataDataTimeByIndexId(indexIdList []int) (items []string, err erro
 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
+	IndexCode             string
+	DataTime              string
+	Value                 string
+	CreateTime            string
+	ModifyTime            string
+	DataTimestamp         int64
 }
 
 func (baseFromPurangData *BaseFromPurangData) AfterFind(tx *gorm.DB) (err error) {
@@ -208,10 +208,10 @@ func (baseFromPurangData *BaseFromPurangData) AfterFind(tx *gorm.DB) (err error)
 
 type BaseFromPurangIndexSearchItem struct {
 	BaseFromPurangIndexId int `orm:"column(base_from_purang_index_id);pk"`
-	ClassifyId             int
-	ParentClassifyId       int
-	IndexCode              string
-	IndexName              string
+	ClassifyId            int
+	ParentClassifyId      int
+	IndexCode             string
+	IndexName             string
 }
 
 // BatchCheckPurangEdbReq 指标数据结构体
@@ -224,12 +224,12 @@ type BatchCheckPurangEdbReq struct {
 }
 
 // GetPurangItemList 模糊查询Purang数据库指标列表
-func GetPurangItemList(condition string) (items []*BaseFromPurangIndexSearchItem, err error) {
+func GetPurangItemList(condition string, pars []interface{}) (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
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars).Find(&items).Error
 	return
 }
 
@@ -271,7 +271,7 @@ func (item *BaseFromPurangIndex) Update(cols []string) (err error) {
 // EditPurangIndexInfoResp 新增指标的返回
 type EditPurangIndexInfoResp struct {
 	BaseFromPurangIndexId int    `description:"指标ID"`
-	IndexCode              string `description:"指标code"`
+	IndexCode             string `description:"指标code"`
 }
 
 type PurangIndexSource2EdbReq struct {
@@ -289,4 +289,4 @@ func GetPurangFrequencyByClassifyId(classifyId int) (items []*GlFrequency, err e
 	sql += ` GROUP BY frequency ORDER BY frequency ASC `
 	err = global.DbMap[utils.DbNameIndex].Raw(sql, classifyId).Find(&items).Error
 	return
-} 
+}

+ 176 - 0
models/data_manage/edb_inspection/edb_inspection_config.go

@@ -0,0 +1,176 @@
+package edb_inspection
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"time"
+
+	"gorm.io/gorm"
+)
+
+// EdbInspectionConfig
+// @Description: 数据源巡检配置表
+type EdbInspectionConfig struct {
+	ConfigId          int64     `gorm:"column:config_id;primaryKey;autoIncrement"`
+	Source           int       `gorm:"column:source"`
+	TerminalCode     string    `gorm:"column:terminal_code"`
+	DateType   int8      `gorm:"column:date_type"`
+	StartTime        string    `gorm:"column:start_time"`
+	IntervalTime     int       `gorm:"column:interval_time"`
+	NotifyUsers      string    `gorm:"column:notify_users"`
+	Status           int8      `gorm:"column:status"`
+	CreateTime       time.Time `gorm:"column:create_time"`
+	ModifyTime       time.Time `gorm:"column:modify_time"`
+	InspectionTime   string    `gorm:"-"` // 用于显示巡检时间,不存储到数据库
+}
+
+// Add
+// @Description: 添加巡检配置
+// @receiver m
+// @return err error
+func (m *EdbInspectionConfig) Add() (err error) {
+	m.CreateTime = time.Now()
+	m.ModifyTime = time.Now()
+	err = global.DbMap[utils.DbNameIndex].Create(m).Error
+	return
+}
+
+// Update
+// @Description: 更新巡检配置
+// @receiver m
+// @param cols []string
+// @return err error
+func (m *EdbInspectionConfig) Update(cols []string) (err error) {
+	m.ModifyTime = time.Now()
+	err = global.DbMap[utils.DbNameIndex].Select(cols).Updates(m).Error
+	return
+}
+
+// Delete
+// @Description: 删除巡检配置
+// @receiver m
+// @return err error
+func (m *EdbInspectionConfig) Delete() (err error) {
+	sql := `DELETE FROM edb_inspection_config WHERE config_id = ?`
+	err = global.DbMap[utils.DbNameIndex].Exec(sql, m.ConfigId).Error
+	return
+}
+
+// UpdateStatus
+// @Description: 更新巡检配置状态
+// @receiver m
+// @param status int8
+// @return err error
+func (m *EdbInspectionConfig) UpdateStatus(status int8) (err error) {
+	sql := `UPDATE edb_inspection_config SET status = ?, modify_time = ? WHERE config_id = ?`
+	err = global.DbMap[utils.DbNameIndex].Exec(sql, status, time.Now(), m.ConfigId).Error
+	return
+}
+
+// GetListByTerminalCode
+// @Description: 根据终端编码获取巡检配置列表
+// @param terminalCode string
+// @return list []*EdbInspectionConfig
+// @return err error
+func GetListByTerminalCode(terminalCode string) (list []*EdbInspectionConfig, err error) {
+	sql := `SELECT * FROM edb_inspection_config WHERE terminal_code = ? ORDER BY config_id ASC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, terminalCode).Find(&list).Error
+	return
+}
+
+// GetById
+// @Description: 根据ID获取巡检配置
+// @param configId int64
+// @return item *EdbInspectionConfig
+// @return err error
+func GetById(configId int64) (item *EdbInspectionConfig, err error) {
+	sql := `SELECT * FROM edb_inspection_config WHERE config_id = ?`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, configId).First(&item).Error
+	return
+}
+
+// GetListBySource
+// @Description: 根据来源获取巡检配置列表
+// @param source int
+// @return list []*EdbInspectionConfig
+// @return err error
+func GetListBySource(source int) (list []*EdbInspectionConfig, err error) {
+	sql := `SELECT * FROM edb_inspection_config WHERE source = ? ORDER BY config_id ASC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, source).Find(&list).Error
+	return
+}
+
+func GetConfigListBySourceAndTerminalCode(source int, terminalCode string) (list []*EdbInspectionConfigItem, err error) {
+	condition := " 1=1 "
+	var pars []interface{}
+
+	if source > 0 {
+		condition += " AND c.source = ? "
+		pars = append(pars, source)
+	}
+
+	if terminalCode != "" {
+		condition += " AND c.terminal_code = ? "
+		pars = append(pars, terminalCode)
+	}
+
+	sql := `SELECT c.*, t.name AS terminal_name, s.source_name FROM edb_inspection_config c left join edb_terminal t on c.terminal_code = t.terminal_code left join edb_source s on c.source = s.edb_source_id WHERE ` + condition + ` ORDER BY c.modify_time DESC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars...).Find(&list).Error
+	return
+}
+func(c *EdbInspectionConfigItem) AfterFind(tx *gorm.DB) (err error) {
+	c.CreateTime = utils.GormDateStrToDateTimeStr(c.CreateTime)
+	c.ModifyTime = utils.GormDateStrToDateTimeStr(c.ModifyTime)
+	return
+}
+
+type EdbInspectionConfigAddReq struct {
+	ConfigId          int64    
+	Source           int       
+	TerminalCode     string    
+	NotifyUsers         string `description:"通知用户"`
+	//Status              int8   `description:"状态"` 
+	DateType   int8      
+	StartTime         string 
+	IntervalTime      int   
+	List      []InspectionConfigReq `description:"刷新配置项"`
+}
+
+// @Description: 刷新时间配置项
+type InspectionConfigReq struct {
+	InspectionFrequency string `description:"巡检频率"`
+	InspectionFrequencyDay        int    `description:"具体刷新的日期"`
+	InspectionDate      string `description:"巡检日期"`
+	InspectionTime      string `description:"巡检时间"`
+}
+
+type EdbInspectionConfigDetailResp struct {
+	*EdbInspectionConfig
+	List      []InspectionConfigReq `description:"刷新配置项"`
+}
+
+type EdbInspectionConfigItem struct {
+	ConfigId          int64    
+	Source           int       
+	SourceName       string
+	TerminalCode     string    
+	TerminalName     string
+	StartTime        string `description:"开始时间"`
+	IntervalTime     int    `description:"间隔时间"`
+	NotifyUsers         string `description:"通知用户"`
+	NotifyUsersName   string `description:"通知用户名称"`
+	Status              int8   `description:"状态"` 
+	DateType   int8      
+	InspectionTime   string `description:"巡检时间"`
+	CreateTime       string `description:"创建时间"`
+	ModifyTime       string `description:"修改时间"`
+}
+
+type EdbInspectionConfigStatusReq struct {
+	ConfigId int64 `description:"配置ID"`
+	Status   int8  `description:"状态"`
+}
+
+type EdbInspectionConfigDeleteReq struct {
+	ConfigId int64 `description:"配置ID"`
+}

+ 112 - 0
models/data_manage/edb_inspection/edb_inspection_dashboard.go

@@ -0,0 +1,112 @@
+package edb_inspection
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"time"
+
+	"gorm.io/gorm"
+)
+
+// EdbInspectionDashboard
+// @Description: 巡检看板表
+type EdbInspectionDashboard struct {
+	DashboardId         int64     `gorm:"column:dashboard_id;primaryKey;autoIncrement" description:"巡检看板ID"`
+	Source             int       `gorm:"column:source" description:"数据源ID"`
+	TerminalCode       string    `gorm:"column:terminal_code" description:"终端编码"`
+	InspectionRecordId int64     `gorm:"column:inspection_record_id" description:"巡检记录ID"`
+	InspectionTime     time.Time `gorm:"column:inspection_time" description:"巡检时间"`
+	InspectionResult   int8      `gorm:"column:inspection_result" description:"巡检结果(1:成功,2:失败)"`
+	ErrorReason        string    `gorm:"column:error_reason" description:"错误原因"`
+	CreateTime         time.Time `gorm:"column:create_time" description:"创建时间"`
+	ModifyTime         time.Time `gorm:"column:modify_time" description:"修改时间"`
+}
+
+// Add
+// @Description: 添加巡检看板记录
+// @receiver m
+// @return err error
+func (m *EdbInspectionDashboard) Add() (err error) {
+	m.CreateTime = time.Now()
+	m.ModifyTime = time.Now()
+	err = global.DbMap[utils.DbNameIndex].Create(m).Error
+	return
+}
+
+// Update
+// @Description: 更新巡检看板记录
+// @receiver m
+// @param cols []string
+// @return err error
+func (m *EdbInspectionDashboard) Update(cols []string) (err error) {
+	m.ModifyTime = time.Now()
+	err = global.DbMap[utils.DbNameIndex].Select(cols).Updates(m).Error
+	return
+}
+
+// GetById
+// @Description: 根据ID获取巡检看板记录
+// @param dashboardId int64
+// @return item *EdbInspectionDashboard
+// @return err error
+func GetDashboardById(dashboardId int64) (item *EdbInspectionDashboard, err error) {
+	sql := `SELECT * FROM edb_inspection_dashboard WHERE dashboard_id = ?`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, dashboardId).First(&item).Error
+	return
+}
+
+// GetListByTerminalCode
+// @Description: 根据终端编码获取巡检看板记录列表
+// @param terminalCode string
+// @return list []*EdbInspectionDashboard
+// @return err error
+func GetDashboardListByTerminalCode(terminalCode string) (list []*EdbInspectionDashboard, err error) {
+	sql := `SELECT * FROM edb_inspection_dashboard WHERE terminal_code = ? ORDER BY inspection_time DESC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, terminalCode).Find(&list).Error
+	return
+}
+
+// GetListByInspectionRecordId
+// @Description: 根据巡检记录ID获取巡检看板记录列表
+// @param inspectionRecordId int64
+// @return list []*EdbInspectionDashboard
+// @return err error
+func GetDashboardListByInspectionRecordId(inspectionRecordId int64) (list []*EdbInspectionDashboard, err error) {
+	sql := `SELECT * FROM edb_inspection_dashboard WHERE inspection_record_id = ? ORDER BY inspection_time DESC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, inspectionRecordId).Find(&list).Error
+	return
+} 
+
+// GetDashboardBySourceAndTerminalCode
+// @Description: 根据源和终端编码获取巡检看板记录
+// @param source int
+// @param terminalCode string
+// @return item *EdbInspectionDashboard
+// @return err error
+func GetDashboardBySourceAndTerminalCode(source int, terminalCode string) (item *EdbInspectionDashboard, err error) {
+	sql := `SELECT * FROM edb_inspection_dashboard WHERE source = ? AND terminal_code = ?`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, source, terminalCode).First(&item).Error
+	return
+}
+
+type DashboardList struct {
+	DashboardId         int64     `gorm:"column:dashboard_id;primaryKey;autoIncrement"`
+	Source             int       `gorm:"column:source"`
+	TerminalCode       string    `gorm:"column:terminal_code"`
+	TerminalName       string    `gorm:"column:terminal_name"`
+	InspectionRecordId int64     `gorm:"column:inspection_record_id"`
+	InspectionTime     string    `gorm:"column:inspection_time"`
+	InspectionResult   int8      `gorm:"column:inspection_result"`
+	ErrorReason        string    `gorm:"column:error_reason"`
+}
+
+// 查询列表,安装状态排序,失败的排在前面,状态相同,按照source排序,查询终端名称
+func GetDashboardList() (list []*DashboardList, err error) {
+	sql := `SELECT edb_inspection_dashboard.*, edb_terminal.name as terminal_name FROM edb_inspection_dashboard left join edb_terminal on edb_inspection_dashboard.terminal_code = edb_terminal.terminal_code ORDER BY inspection_result DESC, source ASC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql).Find(&list).Error
+	return
+}
+func (m *DashboardList) AfterFind(scope *gorm.DB) (err error) {
+	m.InspectionTime = utils.GormDateStrToDateTimeStr(m.InspectionTime)
+	return
+}

+ 114 - 0
models/data_manage/edb_inspection/edb_inspection_date_config.go

@@ -0,0 +1,114 @@
+package edb_inspection
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"time"
+)
+
+// EdbInspectionDateConfig
+// @Description: 数据源巡检配置表
+type EdbInspectionDateConfig struct {
+	DateConfigId        int64     `orm:"column(date_config_id);pk" gorm:"primaryKey" `
+	InspectionFrequency string    `description:"巡检频率,枚举值:每自然日、每交易日、每周"`
+	InspectionFrequencyDay int    `description:"具体刷新的日期"`
+	InspectionDate      string    `description:"巡检日期(每周几/每月几号)"`
+	InspectionTime      string    `description:"巡检时间,具体到时分"`
+	ConfigId            int64     `description:"关联的巡检配置ID"`
+	CreateTime          time.Time `description:"创建时间"`
+	ModifyTime          time.Time `description:"修改时间"`
+}
+
+// Add
+// @Description: 添加
+// @author: Roc
+// @receiver m
+// @datetime 2024-01-10 16:11:10
+// @return err error
+func AddEdbInspectionDateConfigList(list []*EdbInspectionDateConfig, configId int64) (err error) {
+	err = global.DbMap[utils.DbNameIndex].CreateInBatches(list, utils.MultiAddNum).Error
+	return
+}
+
+// Update
+// @Description: 更新
+// @author: Roc
+// @receiver m
+// @datetime 2024-01-10 16:11:10
+// @param cols []string
+// @return err error
+func (m *EdbInspectionDateConfig) Update(cols []string) (err error) {
+	err = global.DbMap[utils.DbNameIndex].Select(cols).Updates(m).Error
+	return
+}
+
+// Delete
+// @Description: 删除
+// @author: Roc
+// @receiver m
+// @datetime 2024-01-10 16:11:10
+// @return err error
+func (m *EdbInspectionDateConfig) Delete() (err error) {
+	sql := ` DELETE FROM edb_inspection_date_config WHERE date_config_id=?`
+	err = global.DbMap[utils.DbNameIndex].Exec(sql, m.DateConfigId).Error
+	return
+}
+
+// 删除配置关联的所有巡检日期配置
+func DeleteEdbInspectionDateConfigByConfigId(configId int64) (err error) {
+	sql := `DELETE FROM edb_inspection_date_config WHERE config_id = ?`
+	err = global.DbMap[utils.DbNameIndex].Exec(sql, configId).Error
+	return
+}
+
+// GetEdbInspectionDateConfigListByCondition
+// @Description: 根据条件获取巡检配置列表
+// @author: Roc
+// @datetime 2024-01-10 16:11:10
+// @param inspectionFrequency string
+// @param inspectionFrequencyDay int
+// @param inspectionDate string
+// @param inspectionTime string
+// @param configId int64
+// @return item *EdbInspectionDateConfig
+// @return err error
+func GetEdbInspectionDateConfigListByCondition(inspectionFrequency string, inspectionFrequencyDay int, inspectionDate, inspectionTime string, configId int64) (item *EdbInspectionDateConfig, err error) {
+	sql := `SELECT * FROM edb_inspection_date_config
+	     WHERE inspection_frequency = ? AND inspection_frequency_day = ? AND inspection_date = ? AND inspection_time = ? AND config_id = ? ORDER BY date_config_id ASC `
+
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, inspectionFrequency, inspectionFrequencyDay, inspectionDate, inspectionTime, configId).First(&item).Error
+	return
+}
+
+// GetEdbInspectionDateConfigListByConfigId
+// @Description: 根据配置ID获取巡检日期配置列表
+// @author: Roc
+// @datetime 2024-01-10 16:11:10
+// @param configId int64
+// @return list []*EdbInspectionDateConfig
+// @return err error
+func GetEdbInspectionDateConfigListByConfigId(configId int64) (list []*EdbInspectionDateConfig, err error) {
+	sql := `SELECT * FROM edb_inspection_date_config WHERE config_id = ? ORDER BY date_config_id ASC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, configId).Find(&list).Error
+	return
+}
+
+func GetEdbInspectionDateConfigListByConfigIdList(configIdList []int64) (list []*EdbInspectionDateConfig, err error) {
+	sql := `SELECT * FROM edb_inspection_date_config WHERE config_id IN (?) ORDER BY date_config_id ASC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, configIdList).Find(&list).Error
+	return
+}
+
+// GetEdbInspectionDateConfigListBySourceAndTerminalCode
+// @Description: 根据来源和终端编码获取巡检日期配置列表
+// @author: Roc
+// @datetime 2024-01-10 16:11:10
+// @param source int
+// @param terminalCode string
+// @return list []*EdbInspectionDateConfig
+// @return err error
+func GetEdbInspectionDateConfigListBySourceAndTerminalCode(source int, terminalCode string) (list []*EdbInspectionDateConfig, err error) {
+	sql := `SELECT * FROM edb_inspection_date_config WHERE source = ? AND terminal_code = ? ORDER BY date_config_id ASC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, source, terminalCode).Find(&list).Error
+	return
+}	

+ 170 - 0
models/data_manage/edb_inspection/edb_inspection_message.go

@@ -0,0 +1,170 @@
+package edb_inspection
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+// EdbInspectionMessage
+// @Description: 巡检消息表
+type EdbInspectionMessage struct {
+	MessageId          int64     `gorm:"column:message_id;primaryKey;autoIncrement"`
+	InspectionRecordId int64     `gorm:"column:inspection_record_id"`
+	AdminId            int64     `gorm:"column:admin_id"`
+	Message            string    `gorm:"column:message"`
+	IsRead             int8      `gorm:"column:is_read"`
+	Source             int8      `gorm:"column:source"`
+	TerminalCode       string    `gorm:"column:terminal_code"`
+	InspectionTime     time.Time `gorm:"column:inspection_time"`
+	CreateTime         time.Time `gorm:"column:create_time"`
+	ModifyTime         time.Time `gorm:"column:modify_time"`
+}
+
+// Add
+// @Description: 添加巡检消息
+// @receiver m
+// @return err error
+func (m *EdbInspectionMessage) Add() (err error) {
+	m.CreateTime = time.Now()
+	m.ModifyTime = time.Now()
+	err = global.DbMap[utils.DbNameIndex].Create(m).Error
+	return
+}
+
+// Update
+// @Description: 更新巡检消息
+// @receiver m
+// @param cols []string
+// @return err error
+func (m *EdbInspectionMessage) Update(cols []string) (err error) {
+	m.ModifyTime = time.Now()
+	err = global.DbMap[utils.DbNameIndex].Select(cols).Updates(m).Error
+	return
+}
+
+// GetById
+// @Description: 根据ID获取巡检消息
+// @param messageId int64
+// @return item *EdbInspectionMessage
+// @return err error
+func GetMessageById(messageId int64) (item *EdbInspectionMessage, err error) {
+	sql := `SELECT * FROM edb_inspection_message WHERE message_id = ?`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, messageId).First(&item).Error
+	return
+}
+
+// GetListByInspectionRecordId
+// @Description: 根据巡检记录ID获取巡检消息列表
+// @param inspectionRecordId int64
+// @return list []*EdbInspectionMessage
+// @return err error
+func GetMessageListByInspectionRecordId(inspectionRecordId int64) (list []*EdbInspectionMessage, err error) {
+	sql := `SELECT * FROM edb_inspection_message WHERE inspection_record_id = ? ORDER BY create_time DESC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, inspectionRecordId).Find(&list).Error
+	return
+}
+
+// GetListBySendStatus
+// @Description: 根据发送状态获取巡检消息列表
+// @param sendStatus int8
+// @return list []*EdbInspectionMessage
+// @return err error
+func GetMessageListBySendStatus(sendStatus int8) (list []*EdbInspectionMessage, err error) {
+	sql := `SELECT * FROM edb_inspection_message WHERE send_status = ? ORDER BY create_time DESC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, sendStatus).Find(&list).Error
+	return
+}
+
+// UpdateSendStatus
+// @Description: 更新消息发送状态
+// @receiver m
+// @param sendStatus int8
+// @return err error
+func (m *EdbInspectionMessage) UpdateSendStatus(sendStatus int8) (err error) {
+	sql := `UPDATE edb_inspection_message SET send_status = ?, modify_time = ? WHERE message_id = ?`
+	err = global.DbMap[utils.DbNameIndex].Exec(sql, sendStatus, time.Now(), m.MessageId).Error
+	return
+}
+
+// GetCountByCondition
+// @Description: 根据条件获取巡检消息数量
+// @param cond string
+// @param pars []interface{}
+// @return int64
+// @return err error
+func (m *EdbInspectionMessage) GetCountByCondition(cond string, pars []interface{}) (count int64, err error) {
+	sql := `SELECT COUNT(*) FROM edb_inspection_message WHERE 1=1` + cond
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars...).Scan(&count).Error
+	return
+}
+
+type EdbInspectionMessageResp struct {
+	MessageId          int64
+	AdminId            int64
+	InspectionRecordId int64
+	Content            string    
+	Remark             string    
+	IsRead             int8      
+	Source             int8
+	TerminalCode       string
+	InspectionTime     string
+}
+
+type EdbInspectionMessageListResp struct {
+	List   []*EdbInspectionMessageResp 
+	Paging *paging.PagingItem    
+	UnreadTotal int
+}
+
+type EdbInspectionMessageReadReq struct {
+	MessageId int64 
+}
+
+
+func BatchModifyEdbInspectionMessageIsRead(ids []int64, adminId int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := global.DbMap[utils.DbNameIndex]
+	sql := `UPDATE edb_inspection_message SET is_read =1, modify_time = ? WHERE admin_id =? AND is_read = 0 AND message_id IN (` + utils.GetOrmInReplace(len(ids)) + `)`
+	err = o.Exec(sql, time.Now(), adminId, ids).Error
+	return
+}
+
+func GetEdbInspectionMessageById(id int) (item *EdbInspectionMessage, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := "SELECT * FROM edb_inspection_message WHERE message_id =?"
+	err = o.Raw(sql, id).First(&item).Error
+	return
+}
+
+func GetEdbInspectionMessageByAdminId(adminId int) (items []*EdbInspectionMessage, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := "SELECT * FROM edb_inspection_message WHERE admin_id =? AND is_read = 0 ORDER BY create_time DESC"
+	err = o.Raw(sql, adminId).Find(&items).Error
+	return
+}
+
+func GetEdbInspectionMessageUnreadCountByAdminId(adminId int) (count int, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := "SELECT COUNT(*) FROM edb_inspection_message WHERE admin_id =? AND is_read = 0 ORDER BY is_read ASC, create_time DESC"
+	err = o.Raw(sql, adminId).Scan(&count).Error
+	return
+}
+
+func GetEdbInspectionMessageCountByAdminId(adminId int) (count int, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := "SELECT COUNT(*) FROM edb_inspection_message WHERE admin_id =? ORDER BY is_read ASC, create_time DESC"
+	err = o.Raw(sql, adminId).Scan(&count).Error
+	return
+}
+
+func GetEdbInspectionMessagePageByAdminId(adminId, startSize, pageSize int) (items []*EdbInspectionMessage, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := "SELECT * FROM edb_inspection_message WHERE admin_id =? ORDER BY is_read ASC, create_time DESC LIMIT ?,?"
+	err = o.Raw(sql, adminId, startSize, pageSize).Find(&items).Error
+	return
+} 

+ 112 - 0
models/data_manage/edb_inspection/edb_inspection_record.go

@@ -0,0 +1,112 @@
+package edb_inspection
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"time"
+
+	"gorm.io/gorm"
+)
+
+// EdbInspectionRecord
+// @Description: 巡检记录表
+type EdbInspectionRecord struct {
+	InspectionRecordId int64     `gorm:"column:inspection_record_id;primaryKey;autoIncrement"`
+	EdbInfoId         int       `gorm:"column:edb_info_id"`
+	Source            int       `gorm:"column:source"`
+	TerminalCode      string    `gorm:"column:terminal_code"`
+	InspectionTime    time.Time `gorm:"column:inspection_time"`
+	InspectionResult  int8      `gorm:"column:inspection_result"`
+	ErrorReason       string    `gorm:"column:error_reason"`
+	CreateTime        time.Time `gorm:"column:create_time"`
+	ModifyTime        time.Time `gorm:"column:modify_time"`
+}
+
+// Add
+// @Description: 添加巡检记录
+// @receiver m
+// @return err error
+func (m *EdbInspectionRecord) Add() (err error) {
+	m.CreateTime = time.Now()
+	m.ModifyTime = time.Now()
+	err = global.DbMap[utils.DbNameIndex].Create(m).Error
+	return
+}
+
+// Update
+// @Description: 更新巡检记录
+// @receiver m
+// @param cols []string
+// @return err error
+func (m *EdbInspectionRecord) Update(cols []string) (err error) {
+	m.ModifyTime = time.Now()
+	err = global.DbMap[utils.DbNameIndex].Select(cols).Updates(m).Error
+	return
+}
+
+// GetById
+// @Description: 根据ID获取巡检记录
+// @param inspectionRecordId int64
+// @return item *EdbInspectionRecord
+// @return err error
+func GetInspectionRecordById(inspectionRecordId int64) (item *EdbInspectionRecord, err error) {
+	sql := `SELECT * FROM edb_inspection_record WHERE inspection_record_id = ?`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, inspectionRecordId).First(&item).Error
+	return
+}
+
+// GetListByTerminalCode
+// @Description: 根据终端编码获取巡检记录列表
+// @param terminalCode string
+// @return list []*EdbInspectionRecord
+// @return err error
+func GetInspectionRecordListByTerminalCode(terminalCode string) (list []*EdbInspectionRecord, err error) {
+	sql := `SELECT * FROM edb_inspection_record WHERE terminal_code = ? ORDER BY inspection_time DESC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, terminalCode).Find(&list).Error
+	return
+}
+
+// GetListBySource
+// @Description: 根据来源获取巡检记录列表
+// @param source int
+// @return list []*EdbInspectionRecord
+// @return err error
+func GetInspectionRecordListBySource(source int) (list []*EdbInspectionRecord, err error) {
+	sql := `SELECT * FROM edb_inspection_record WHERE source = ? ORDER BY inspection_time DESC`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, source).Find(&list).Error
+	return
+} 
+
+type InspectionRecordList struct {
+	EdbInspectionRecord
+	TerminalName string `gorm:"column:terminal_name"`
+	InspectionTime string `gorm:"column:inspection_time"`
+	SourceName string `gorm:"column:source_name"`
+}
+
+// 创建afterfind
+func (m *InspectionRecordList) AfterFind(scope *gorm.DB) (err error) {
+	m.InspectionTime = utils.GormDateStrToDateTimeStr(m.InspectionTime)
+	return
+}
+
+func GetInspectionRecordListByCondition(condition string, pars []interface{}, startSize int, pageSize int) (list []*InspectionRecordList, err error) {
+	sql := `SELECT r.*, t.name as terminal_name, s.source_name FROM edb_inspection_record r left join edb_terminal t on r.terminal_code = t.terminal_code 
+	left join edb_source s on r.source = s.edb_source_id
+	WHERE 1=1 ` + condition + ` ORDER BY inspection_time DESC LIMIT ?,?`
+	pars = append(pars, startSize, pageSize)
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars...).Find(&list).Error
+	return
+}
+
+// 获取分页总数
+func GetInspectionRecordCountByCondition(condition string, pars []interface{}) (count int64, err error) {
+	sql := `SELECT COUNT(*) FROM edb_inspection_record r left join edb_terminal t on r.terminal_code = t.terminal_code 
+		left join edb_source s on r.source = s.edb_source_id
+	WHERE 1=1 ` + condition
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars...).Count(&count).Error
+	return
+}
+
+
+

+ 117 - 6
models/data_manage/edb_terminal.go

@@ -21,11 +21,15 @@ type EdbTerminal struct {
 	Value        string    `description:"终端相关的token"`
 	ModifyTime   time.Time `description:"修改时间"`
 	CreateTime   time.Time `description:"创建时间"`
+	AccountQuota string       `description:"账号额度"`
+	IsApi        int8      `description:"获取类型(1,接口类型,0终端类型)"`
+	
 }
 
 type EdbTerminalItem struct {
 	TerminalId   int    `orm:"column(terminal_id);pk" gorm:"primaryKey"`
 	Source       int    `description:"指标来源类型"`
+	SourceName   string `description:"数据源类型名称"`
 	Name         string `description:"终端别名"`
 	TerminalCode string `description:"终端编码,用于配置在机器上"`
 	ServerUrl    string `description:"终端地址"`
@@ -35,6 +39,9 @@ type EdbTerminalItem struct {
 	Value        string `description:"终端相关的token"`
 	ModifyTime   string `description:"修改时间"`
 	CreateTime   string `description:"创建时间"`
+	AccountQuota string    `description:"账号额度"`
+	IsApi        int8   `description:"获取类型(1,接口类型,0终端类型)"`
+	UsedQuota    string    `description:"已使用额度"`
 }
 
 func (e *EdbTerminalItem) AfterFind(db *gorm.DB) (err error) {
@@ -80,6 +87,8 @@ type AddEdbTerminalListReq struct {
 	Num       int    `description:"终端最大指标数"`
 	//Status    int    `description:"状态,1启用,2禁用"`
 	Value string `description:"终端相关的token"`
+	AccountQuota string `description:"账号额度"`
+	IsApi        int8 `description:"获取类型(1,接口类型,0终端类型)"`
 }
 
 type SetEdbTerminalStatusReq struct {
@@ -110,17 +119,24 @@ func GetEdbTerminalByTerminalCode(terminalCode string) (item *EdbTerminal, err e
 	return
 }
 
-func GetEdbTerminalList() (item []*EdbTerminalItem, err error) {
+func GetEdbTerminalList(source int) (item []*EdbTerminalItem, err error) {
 	o := global.DbMap[utils.DbNameIndex]
-	sql := ` SELECT * FROM edb_terminal ORDER BY terminal_id ASC`
-	err = o.Raw(sql).Find(&item).Error
+	var sql string
+	if source == 0 {
+		sql = ` SELECT t.*, s.source_name FROM edb_terminal t left join edb_source s on t.source = s.edb_source_id ORDER BY terminal_id ASC`
+		err = o.Raw(sql).Find(&item).Error
+	} else {
+		sql = ` SELECT t.*, s.source_name FROM edb_terminal t left join edb_source s on t.source = s.edb_source_id WHERE t.source = ? ORDER BY terminal_id ASC`
+		err = o.Raw(sql, source).Find(&item).Error
+	}
 	return
+	
 }
 
-func GetEdbTerminalBySource(source int) (item []*EdbTerminalItem, err error) {
+func GetEdbTerminalBySourceAndIsApi(source int, isApi int) (item []*EdbTerminalItem, err error) {
 	o := global.DbMap[utils.DbNameIndex]
-	sql := ` SELECT * FROM edb_terminal where source=? ORDER BY terminal_id ASC`
-	err = o.Raw(sql, source).Find(&item).Error
+	sql := ` SELECT * FROM edb_terminal where source=? and is_api=? ORDER BY terminal_id ASC`
+	err = o.Raw(sql, source, isApi).Find(&item).Error
 	return
 }
 
@@ -164,3 +180,98 @@ type EdbTerminalDirInfo struct {
 	DirPath      string `description:"终端存放的文件夹路径"`
 	FilePath     string `description:"文件夹路径"`
 }
+
+type EdbInfoTerminalList struct {
+	//EdbInfoId    int    `gorm:"column:edb_info_id"`
+	EdbCode  string `gorm:"column:edb_code"`
+	EdbName  string `gorm:"column:edb_name"`
+	TerminalCode string `gorm:"column:terminal_code"`
+	TerminalName string `gorm:"column:terminal_name"`
+}
+
+func GetSimpleEdbListPageByCondition(condition string, pars []interface{}, pageIndex int, pageSize int) (list []*EdbInfoTerminalList, err error) {
+	sql := `SELECT e.*, t.name as terminal_name FROM edb_info e left join edb_terminal t on e.terminal_code = t.terminal_code WHERE 1=1 ` + condition + ` ORDER BY e.edb_info_id ASC LIMIT ?, ?`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, append(pars, pageIndex, pageSize)...).Find(&list).Error
+	return
+}
+
+func GetSimpleEdbListCountByCondition(condition string, pars []interface{}) (count int64, err error) {
+	sql := `SELECT COUNT(*) FROM edb_info e left join edb_terminal t on e.terminal_code = t.terminal_code WHERE 1=1 ` + condition
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars...).Count(&count).Error
+	return
+}
+
+func GetSimpleBaseIndexListPageByCondition(indexTableName string, condition string, pars []interface{}, pageIndex int, pageSize int) (list []*EdbInfoTerminalList, err error) {
+	sql := `SELECT e.index_code as edb_code, e.index_name as edb_name, e.terminal_code, t.name as terminal_name FROM ` + indexTableName + ` e left join edb_terminal t on e.terminal_code = t.terminal_code WHERE 1=1 ` + condition + ` LIMIT ?, ?`
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, append(pars, pageIndex, pageSize)...).Find(&list).Error
+	return
+}
+
+func GetSimpleBaseIndexListCountByCondition(indexTableName string, condition string, pars []interface{}) (count int64, err error) {
+	sql := `SELECT COUNT(*) FROM ` + indexTableName + ` e left join edb_terminal t on e.terminal_code = t.terminal_code WHERE 1=1 ` + condition
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars...).Count(&count).Error
+	return
+}
+
+type SetEdbInfoTerminalReq struct {
+	EdbCodes []string `description:"指标编码"`
+	TerminalCode string `description:"要更换的终端编码"`
+	Source int `description:"指标来源类型"`
+}
+
+type IndexNumBySource struct {
+	SubSource int
+	Num int
+}
+
+func GetIndexNumBySource(source int, terminalCode string) (num int, list []*IndexNumBySource, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	tableName := EdbSourceIdMap[source].IndexTableName
+	var pars []interface{}
+	if tableName != "" {
+		sql := fmt.Sprintf(`SELECT COUNT(*) FROM %s WHERE terminal_code = ?`, tableName)
+		pars = append(pars, terminalCode)
+		err = o.Raw(sql, pars...).Scan(&num).Error
+		return
+	} else {
+		sql := `SELECT COUNT(*) as num, sub_source FROM edb_info WHERE terminal_code = ? and source = ? group by sub_source`
+		pars = append(pars, terminalCode, source)
+		err = o.Raw(sql, pars...).Find(&list).Error
+		return
+	}
+	
+}
+
+// 根据source和is_api获取终端编码
+func GetTerminalCodeBySourceAndIsApi(source int, isApi int) (terminalCode []string, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := `SELECT terminal_code FROM edb_terminal WHERE source = ? AND is_api = ?`
+	err = o.Raw(sql, source, isApi).Find(&terminalCode).Error
+	return
+}
+
+// 更新终端编码
+func UpdatBaseIndexTerminalCode(indexCodes []string, terminalCode string, source int) (err error) {
+	tableName := EdbSourceIdMap[source].IndexTableName
+	var sql string
+	// 通过事务更新
+	o := global.DbMap[utils.DbNameIndex].Begin()
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+			return
+		}
+		_ = o.Commit()
+	}()
+	if tableName != "" {
+		sql = ` UPDATE ` + tableName + ` SET terminal_code = ? WHERE index_code in (?) `
+		err = o.Exec(sql, terminalCode, indexCodes).Error
+		if err != nil {
+			return
+		}
+	}
+	// 更新edb_info的终端编码
+	sql = ` UPDATE edb_info SET terminal_code = ? WHERE source = ? AND edb_code in (?) `
+	err = o.Exec(sql, terminalCode, source, indexCodes).Error
+	return
+}

+ 2 - 0
models/data_manage/mysteel_chemical_index.go

@@ -793,3 +793,5 @@ func GetNoEdbMysteelChemicalIndexPageList(condition string, pars []interface{},
 	err = o.Raw(sql, pars...).Find(&items).Error
 	return
 }
+
+

+ 1 - 0
models/data_stat/edb_info_update_stat.go

@@ -50,6 +50,7 @@ func (e *EdbInfoUpdateStat) AfterFind(db *gorm.DB) (err error) {
 	e.LatestDate = utils.GormDateStrToDateStr(e.LatestDate)
 	e.StartDate = utils.GormDateStrToDateStr(e.StartDate)
 	e.EndDate = utils.GormDateStrToDateStr(e.EndDate)
+	e.UpdateTime = utils.GormDateStrToDateTimeStr(e.UpdateTime)
 	return
 }
 

+ 4 - 0
models/manual_edb.go

@@ -167,6 +167,10 @@ func GetEdbInfoSortList(condition string, pars []interface{}, startSize, pageSiz
 		orderType = "DESC"
 	}
 	sql += ` ORDER BY a.` + orderField + ` ` + orderType
+	//sql += ` ORDER BY ? ?`
+	//
+	//pars = append(pars, orderField)
+	//pars = append(pars, orderType)
 
 	if pageSize > 0 {
 		sql += ` LIMIT ?,? `

+ 5 - 2
models/material/material_classify.go

@@ -213,7 +213,10 @@ type SandboxLinkCheckResp struct {
 
 func GetMaterialClassifyByLevelPath(levelPath string) (items []*MaterialClassify, err error) {
 	o := global.DbMap[utils.DbNameReport]
-	sql := `SELECT * FROM material_classify where level_path like '` + levelPath + `%'`
-	err = o.Raw(sql).Find(&items).Error
+	//sql := `SELECT * FROM material_classify where level_path like '` + levelPath + `%'`
+	likeKey := `%` + levelPath + `%`
+	sql := `SELECT * FROM material_classify where level_path LIKE ? `
+
+	err = o.Raw(sql, likeKey).Find(&items).Error
 	return
 }

+ 7 - 0
models/message.go

@@ -0,0 +1,7 @@
+package models
+
+type WebsocketMessageResponse struct {
+	MessageType int `description:"消息类型:0-预警消息;1-巡检消息"`
+	Data  interface{} `description:"消息数据"`
+}
+

+ 164 - 0
models/rag/ai_task.go

@@ -0,0 +1,164 @@
+package rag
+
+import (
+	"database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// AiTask ai这边的任务表
+type AiTask struct {
+	AiTaskID                int       `gorm:"primaryKey;column:ai_task_id" description:"-"`
+	TaskName                string    `gorm:"column:task_name" description:"任务名称"`
+	TaskType                string    `gorm:"column:task_type" description:"任务类型"`
+	Status                  string    `gorm:"column:status" description:"任务状态"`
+	StartTime               time.Time `gorm:"column:start_time" description:"开始时间"`
+	EndTime                 time.Time `gorm:"column:end_time" description:"结束时间"`
+	CreateTime              time.Time `gorm:"column:create_time" description:"创建时间"`
+	UpdateTime              time.Time `gorm:"column:update_time" description:"更新时间"`
+	Parameters              string    `gorm:"column:parameters" description:"执行参数"`
+	Logs                    string    `gorm:"column:logs" description:"日志"`
+	Errormessage            string    `gorm:"column:ErrorMessage" description:"错误信息"`
+	Priority                int       `gorm:"column:priority" description:"优先级"`
+	RetryCount              int       `gorm:"column:retry_count" description:"重试次数"`
+	EstimatedCompletionTime time.Time `gorm:"column:estimated_completion_time" description:"预计完成时间"`
+	ActualCompletitonTime   time.Time `gorm:"column:actual_completiton_time" description:"实际完成时间"`
+	Remark                  string    `gorm:"column:remark" description:"备注"`
+	SysUserID               int       `gorm:"column:sys_user_id" description:"任务创建人id"`
+	SysUserRealName         string    `gorm:"column:sys_user_real_name" description:"任务创建人名称"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *AiTask) TableName() string {
+	return "ai_task"
+}
+
+// AiTaskColumns get sql column name.获取数据库列名
+var AiTaskColumns = struct {
+	AiTaskID                string
+	TaskName                string
+	TaskType                string
+	Status                  string
+	StartTime               string
+	EndTime                 string
+	CreateTime              string
+	UpdateTime              string
+	Parameters              string
+	Logs                    string
+	Errormessage            string
+	Priority                string
+	RetryCount              string
+	EstimatedCompletionTime string
+	ActualCompletitonTime   string
+	Remark                  string
+	SysUserID               string
+	SysUserRealName         string
+}{
+	AiTaskID:                "ai_task_id",
+	TaskName:                "task_name",
+	TaskType:                "task_type",
+	Status:                  "status",
+	StartTime:               "start_time",
+	EndTime:                 "end_time",
+	CreateTime:              "create_time",
+	UpdateTime:              "update_time",
+	Parameters:              "parameters",
+	Logs:                    "logs",
+	Errormessage:            "ErrorMessage",
+	Priority:                "priority",
+	RetryCount:              "retry_count",
+	EstimatedCompletionTime: "estimated_completion_time",
+	ActualCompletitonTime:   "actual_completiton_time",
+	Remark:                  "remark",
+	SysUserID:               "sys_user_id",
+	SysUserRealName:         "sys_user_real_name",
+}
+
+func (m *AiTask) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+func (m *AiTask) Update(updateCols []string) (err error) {
+	err = global.DbMap[utils.DbNameAI].Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *AiTask) Del() (err error) {
+	err = global.DbMap[utils.DbNameAI].Delete(&m).Error
+
+	return
+}
+
+func (m *AiTask) GetByID(id int) (item *AiTask, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", AiTaskColumns.AiTaskID), id).First(&item).Error
+
+	return
+}
+
+func (m *AiTask) GetByCondition(condition string, pars []interface{}) (item *AiTask, 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 *AiTask) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*AiTask, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s order by ai_task_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 *AiTask) 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
+}
+
+// AddAiTask
+// @Description: 添加Ai模块的任务
+// @author: Roc
+// @datetime 2025-04-16 16:55:36
+// @param aiTask *AiTask
+// @param aiRecordList []*AiTaskRecord
+// @return err error
+func AddAiTask(aiTask *AiTask, aiRecordList []*AiTaskRecord) (err error) {
+	to := global.DbMap[utils.DbNameAI].Begin()
+	defer func() {
+		if err != nil {
+			_ = to.Rollback()
+		} else {
+			_ = to.Commit()
+		}
+	}()
+
+	err = to.Create(aiTask).Error
+	if err != nil {
+		return
+	}
+
+	for _, aiTaskRecord := range aiRecordList {
+		aiTaskRecord.AiTaskID = aiTask.AiTaskID
+	}
+
+	err = to.CreateInBatches(aiRecordList, utils.MultiAddNum).Error
+	if err != nil {
+		return
+	}
+
+	return
+}

+ 115 - 0
models/rag/ai_task_record.go

@@ -0,0 +1,115 @@
+package rag
+
+import (
+	"database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// AiTaskRecord AI任务的子记录
+type AiTaskRecord struct {
+	AiTaskRecordID int       `gorm:"primaryKey;column:ai_task_record_id" json:"-"` // 任务记录id
+	AiTaskID       int       `gorm:"column:ai_task_id" json:"aiTaskId"`            // 任务id
+	Parameters     string    `gorm:"column:parameters" json:"parameters"`          // 子任务参数
+	Status         string    `gorm:"column:status" json:"status"`                  // 状态
+	Remark         string    `gorm:"column:remark" json:"remark"`                  // 备注
+	ModifyTime     time.Time `gorm:"column:modify_time" json:"modifyTime"`         // 最后一次修改时间
+	CreateTime     time.Time `gorm:"column:create_time" json:"createTime"`         // 任务创建时间
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *AiTaskRecord) TableName() string {
+	return "ai_task_record"
+}
+
+// AiTaskRecordColumns get sql column name.获取数据库列名
+var AiTaskRecordColumns = struct {
+	AiTaskRecordID string
+	AiTaskID       string
+	Parameters     string
+	Status         string
+	Remark         string
+	ModifyTime     string
+	CreateTime     string
+}{
+	AiTaskRecordID: "ai_task_record_id",
+	AiTaskID:       "ai_task_id",
+	Parameters:     "parameters",
+	Status:         "status",
+	Remark:         "remark",
+	ModifyTime:     "modify_time",
+	CreateTime:     "create_time",
+}
+
+func (m *AiTaskRecord) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+func (m *AiTaskRecord) Update(updateCols []string) (err error) {
+	err = global.DbMap[utils.DbNameAI].Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *AiTaskRecord) Del() (err error) {
+	err = global.DbMap[utils.DbNameAI].Delete(&m).Error
+
+	return
+}
+
+func (m *AiTaskRecord) GetByID(id int) (item *AiTaskRecord, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", AiTaskRecordColumns.AiTaskRecordID), id).First(&item).Error
+
+	return
+}
+
+func (m *AiTaskRecord) GetByCondition(condition string, pars []interface{}) (item *AiTaskRecord, 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 *AiTaskRecord) GetAllListByCondition(field, condition string, pars []interface{}) (items []*AiTaskRecord, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s order by ai_task_record_id desc `, field, m.TableName(), condition)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, pars...).Find(&items).Error
+
+	return
+}
+
+func (m *AiTaskRecord) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*AiTaskRecord, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s order by ai_task_record_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 *AiTaskRecord) 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
+}
+
+// QuestionGenerateAbstractParam
+// @Description:
+type QuestionGenerateAbstractParam struct {
+	QuestionId  int    `json:"questionId"`
+	ArticleType string `json:"articleType"`
+	ArticleId   int    `json:"articleId"`
+}

+ 114 - 0
models/rag/article_abstract_history.go

@@ -0,0 +1,114 @@
+package rag
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"time"
+)
+
+// ArticleAbstractHistory 文章/报告摘要历史记录表
+type ArticleAbstractHistory struct {
+	ArticleAbstractHistoryID int       `gorm:"primaryKey;column:article_abstract_history_id" description:"-"`
+	Source                   int8      `gorm:"column:source" description:"来源,0:公众号文章,1:eta报告"`
+	ArticleAbstractID        int       `gorm:"column:article_abstract_id" description:"文章/报告摘要id"`
+	ArticleID                int       `gorm:"column:article_id" description:"文章/报告Id"`
+	QuestionId               int       `gorm:"column:question_id" description:"提示词Id"`
+	Tags                     string    `gorm:"column:tags" description:"标签"`
+	TagsName                 string    `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	QuestionContent          string    `gorm:"column:question_content" description:"questionContent"`
+	Content                  string    `gorm:"column:content" description:"摘要内容"`
+	Version                  int       `gorm:"column:version" description:"版本号"`
+	VectorKey                string    `gorm:"column:vector_key" description:"向量key标识"`
+	ModifyTime               time.Time `gorm:"column:modify_time" description:"modifyTime"`
+	CreateTime               time.Time `gorm:"column:create_time" description:"createTime"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *ArticleAbstractHistory) TableName() string {
+	return "article_abstract_history"
+}
+
+// ArticleAbstractHistoryColumns get sql column name.获取数据库列名
+var ArticleAbstractHistoryColumns = struct {
+	ArticleAbstractHistoryID string
+	Source                   string
+	ArticleAbstractID        string
+	ArticleID                string
+	QuestionId               string
+	Tags                     string
+	TagsName                 string
+	QuestionContent          string
+	Content                  string
+	Version                  string
+	VectorKey                string
+	ModifyTime               string
+	CreateTime               string
+}{
+	ArticleAbstractHistoryID: "article_abstract_history_id",
+	Source:                   "source",
+	ArticleAbstractID:        "article_abstract_id",
+	ArticleID:                "article_id",
+	QuestionId:               "question_id",
+	Tags:                     "tags",
+	TagsName:                 "tags_name",
+	QuestionContent:          "question_content",
+	Content:                  "content",
+	Version:                  "version",
+	VectorKey:                "vector_key",
+	ModifyTime:               "modify_time",
+	CreateTime:               "create_time",
+}
+
+func (m *ArticleAbstractHistory) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+// AddArticleAbstractHistoryByWechatArticleAbstract
+// @Description: 根据eta报告摘要添加历史记录
+// @author: Roc
+// @datetime 2025-04-17 14:05:10
+// @param item *WechatArticleAbstract
+func AddArticleAbstractHistoryByWechatArticleAbstract(item *WechatArticleAbstract) {
+	history := &ArticleAbstractHistory{
+		ArticleAbstractHistoryID: 0,
+		Source:                   0,
+		ArticleAbstractID:        item.WechatArticleAbstractId,
+		ArticleID:                item.WechatArticleId,
+		QuestionId:               item.QuestionId,
+		Tags:                     item.Tags,
+		TagsName:                 item.TagsName,
+		QuestionContent:          item.QuestionContent,
+		Content:                  item.Content,
+		Version:                  item.Version,
+		VectorKey:                item.VectorKey,
+		ModifyTime:               time.Now(),
+		CreateTime:               time.Now(),
+	}
+	_ = history.Create()
+}
+
+// AddArticleAbstractHistoryByWechatArticleAbstract
+// @Description: 根据eta报告摘要添加历史记录
+// @author: Roc
+// @datetime 2025-04-17 14:05:10
+// @param item *WechatArticleAbstract
+func AddArticleAbstractHistoryByRagEtaReportAbstract(item *RagEtaReportAbstract) {
+	history := &ArticleAbstractHistory{
+		ArticleAbstractHistoryID: 0,
+		Source:                   0,
+		ArticleAbstractID:        item.RagEtaReportAbstractId,
+		ArticleID:                item.RagEtaReportId,
+		QuestionId:               item.QuestionId,
+		Tags:                     item.Tags,
+		TagsName:                 item.TagsName,
+		QuestionContent:          item.QuestionContent,
+		Content:                  item.Content,
+		Version:                  item.Version,
+		VectorKey:                item.VectorKey,
+		ModifyTime:               time.Now(),
+		CreateTime:               time.Now(),
+	}
+	_ = history.Create()
+}

+ 19 - 4
models/rag/question.go

@@ -13,6 +13,9 @@ type Question struct {
 	QuestionTitle   string    `gorm:"column:question_title;type:varchar(255);comment:问题标题;" description:"问题标题"`
 	QuestionContent string    `gorm:"column:question_content;type:varchar(255);comment:问题内容;" description:"问题内容"`
 	Sort            int       `gorm:"column:sort;type:int(11);comment:排序;default:0;" description:"排序"`
+	Version         int       `gorm:"column:version" description:"问题版本"`
+	GenerateStatus  string    `gorm:"column:generate_status;type:enum('undo', 'done');comment:生成摘要状态;default:NULL;" description:"生成摘要状态"`
+	IsDefault       int       `gorm:"column:is_default;type:int(1);comment:是否默认提示词;default:NULL;" description:"是否默认提示词"`
 	SysUserId       int       `gorm:"column:sys_user_id;type:int(11);comment:添加人id;default:0;" description:"添加人id"`
 	SysUserRealName string    `gorm:"column:sys_user_real_name;type:varchar(255);comment:添加人真实名称;" description:"添加人真实名称"`
 	ModifyTime      time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
@@ -26,17 +29,23 @@ func (m *Question) TableName() string {
 
 // QuestionColumns get sql column name.获取数据库列名
 var QuestionColumns = struct {
-	QuestionID      string
+	QuestionId      string
 	QuestionTitle   string
 	QuestionContent string
 	Sort            string
+	Version         string
+	GenerateStatus  string
+	IsDefault       string
 	ModifyTime      string
 	CreateTime      string
 }{
-	QuestionID:      "question_id",
+	QuestionId:      "question_id",
 	QuestionTitle:   "question_title",
 	QuestionContent: "question_content",
 	Sort:            "sort",
+	Version:         "version",
+	GenerateStatus:  "generate_status",
+	IsDefault:       "is_default",
 	ModifyTime:      "modify_time",
 	CreateTime:      "create_time",
 }
@@ -64,6 +73,9 @@ type QuestionView struct {
 	QuestionTitle   string `gorm:"column:question_title;type:varchar(255);comment:问题标题;" description:"问题标题"`
 	QuestionContent string `gorm:"column:question_content;type:varchar(255);comment:问题内容;" description:"问题内容"`
 	Sort            int    `gorm:"column:sort;type:int(11);comment:排序;default:0;" description:"排序"`
+	Version         int    `gorm:"column:version" description:"问题版本"`
+	GenerateStatus  string `gorm:"column:generate_status;type:enum('undo', 'done');comment:生成摘要状态;default:NULL;" description:"生成摘要状态"`
+	IsDefault       int    `gorm:"column:is_default;type:int(1);comment:是否默认提示词;default:NULL;" description:"是否默认提示词"`
 	SysUserId       int    `gorm:"column:sys_user_id;type:int(11);comment:添加人id;default:0;" description:"添加人id"`
 	SysUserRealName string `gorm:"column:sys_user_real_name;type:varchar(255);comment:添加人真实名称;" description:"添加人真实名称"`
 	ModifyTime      string `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
@@ -84,6 +96,9 @@ func (m *Question) ToView() QuestionView {
 		QuestionTitle:   m.QuestionTitle,
 		QuestionContent: m.QuestionContent,
 		Sort:            m.Sort,
+		Version:         m.Version,
+		GenerateStatus:  m.GenerateStatus,
+		IsDefault:       m.IsDefault,
 		SysUserId:       m.SysUserId,
 		SysUserRealName: m.SysUserRealName,
 		ModifyTime:      modifyTime,
@@ -101,7 +116,7 @@ func (m *Question) ListToViewList(list []*Question) (wechatArticleViewList []Que
 }
 
 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
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", QuestionColumns.QuestionId), id).First(&item).Error
 
 	return
 }
@@ -154,7 +169,7 @@ func (m *Question) GetTitlePageListByCondition(condition string, pars []interfac
 		return
 	}
 	if total > 0 {
-		items, err = m.GetListByCondition(`question_id,question_title,sort`, condition, pars, startSize, pageSize)
+		items, err = m.GetListByCondition(`question_id,question_title,sort,version,generate_status,is_default`, condition, pars, startSize, pageSize)
 	}
 
 	return

+ 86 - 0
models/rag/question_history.go

@@ -0,0 +1,86 @@
+package rag
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"time"
+)
+
+// QuestionHistory 问题历史列表
+type QuestionHistory struct {
+	QuestionHistoryID int       `gorm:"primaryKey;column:question_history_id" description:"-"`
+	QuestionId        int       `gorm:"column:question_id" description:"问题ID"`
+	QuestionTitle     string    `gorm:"column:question_title" description:"问题标题"`
+	QuestionContent   string    `gorm:"column:question_content" description:"问题内容"`
+	Sort              int       `gorm:"column:sort" description:"排序"`
+	Version           int       `gorm:"column:version" description:"问题版本"`
+	GenerateStatus    string    `gorm:"column:generate_status" description:"生成摘要状态"`
+	IsDefault         int       `gorm:"column:is_default" description:"是否默认提示词"`
+	SysUserID         int       `gorm:"column:sys_user_id" description:"添加人id"`
+	SysUserRealName   string    `gorm:"column:sys_user_real_name" description:"添加人名称"`
+	ModifyTime        time.Time `gorm:"column:modify_time" description:"modifyTime"`
+	CreateTime        time.Time `gorm:"column:create_time" description:"createTime"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *QuestionHistory) TableName() string {
+	return "question_history"
+}
+
+// QuestionHistoryColumns get sql column name.获取数据库列名
+var QuestionHistoryColumns = struct {
+	QuestionHistoryID string
+	QuestionId        string
+	QuestionTitle     string
+	QuestionContent   string
+	Sort              string
+	Version           string
+	GenerateStatus    string
+	IsDefault         string
+	SysUserID         string
+	SysUserRealName   string
+	ModifyTime        string
+	CreateTime        string
+}{
+	QuestionHistoryID: "question_history_id",
+	QuestionId:        "question_id",
+	QuestionTitle:     "question_title",
+	QuestionContent:   "question_content",
+	Sort:              "sort",
+	Version:           "version",
+	GenerateStatus:    "generate_status",
+	IsDefault:         "is_default",
+	SysUserID:         "sys_user_id",
+	SysUserRealName:   "sys_user_real_name",
+	ModifyTime:        "modify_time",
+	CreateTime:        "create_time",
+}
+
+func (m *QuestionHistory) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+// AddQuestionHistoryByQuestion
+// @Description: 根据提示词创建提示词历史记录
+// @author: Roc
+// @datetime 2025-04-17 10:44:15
+// @param item *Question
+func AddQuestionHistoryByQuestion(item *Question) {
+	history := &QuestionHistory{
+		QuestionHistoryID: 0,
+		QuestionId:        item.QuestionId,
+		QuestionTitle:     item.QuestionTitle,
+		QuestionContent:   item.QuestionContent,
+		Sort:              item.Sort,
+		Version:           item.Version,
+		GenerateStatus:    item.GenerateStatus,
+		IsDefault:         item.IsDefault,
+		SysUserID:         item.SysUserId,
+		SysUserRealName:   item.SysUserRealName,
+		ModifyTime:        time.Now(),
+		CreateTime:        time.Now(),
+	}
+	_ = history.Create()
+}

+ 5 - 5
models/rag/rag_eta_report.go

@@ -31,7 +31,7 @@ func (m *RagEtaReport) TableName() string {
 
 // RagEtaReportColumns get sql column name.获取数据库列名
 var RagEtaReportColumns = struct {
-	RagEtaReportID  string
+	RagEtaReportId  string
 	ReportID        string
 	ReportChapterID string
 	Title           string
@@ -44,7 +44,7 @@ var RagEtaReportColumns = struct {
 	ModifyTime      string
 	CreateTime      string
 }{
-	RagEtaReportID:  "rag_eta_report_id",
+	RagEtaReportId:  "rag_eta_report_id",
 	ReportID:        "report_id",
 	ReportChapterID: "report_chapter_id",
 	Title:           "title",
@@ -121,7 +121,7 @@ func (m *RagEtaReport) Update(updateCols []string) (err error) {
 }
 
 func (m *RagEtaReport) GetById(id int) (item *RagEtaReport, err error) {
-	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", RagEtaReportColumns.RagEtaReportID), id).First(&item).Error
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", RagEtaReportColumns.RagEtaReportId), id).First(&item).Error
 
 	return
 }
@@ -154,14 +154,14 @@ func (m *RagEtaReport) GetCountByCondition(condition string, pars []interface{})
 	return
 }
 
-func (m *RagEtaReport) GetPageListByCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*RagEtaReport, err error) {
+func (m *RagEtaReport) GetPageListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (total int, items []*RagEtaReport, err error) {
 
 	total, err = m.GetCountByCondition(condition, pars)
 	if err != nil {
 		return
 	}
 	if total > 0 {
-		items, err = m.GetListByCondition(``, condition, pars, startSize, pageSize)
+		items, err = m.GetListByCondition(field, condition, pars, startSize, pageSize)
 	}
 
 	return

+ 292 - 0
models/rag/rag_eta_report_abstract.go

@@ -0,0 +1,292 @@
+package rag
+
+import (
+	"database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// RagEtaReportAbstract 报告摘要
+type RagEtaReportAbstract struct {
+	RagEtaReportAbstractId int       `gorm:"primaryKey;column:rag_eta_report_abstract_id" description:"-"`
+	RagEtaReportId         int       `gorm:"column:rag_eta_report_id" description:"ETA报告id"`
+	Content                string    `gorm:"column:content" description:"摘要内容"`
+	QuestionId             int       `gorm:"column:question_id" description:"提示词Id"`
+	QuestionContent        string    `gorm:"column:question_content" description:"questionContent"`
+	Version                int       `gorm:"column:version" description:"版本号"`
+	Tags                   string    `gorm:"column:tags" description:"标签"`
+	TagsName               string    `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	VectorKey              string    `gorm:"column:vector_key" description:"向量key标识"`
+	ModifyTime             time.Time `gorm:"column:modify_time" description:"modifyTime"`
+	CreateTime             time.Time `gorm:"column:create_time" description:"createTime"`
+}
+
+// TableName get sql table name.获取数据库表名
+func (m *RagEtaReportAbstract) TableName() string {
+	return "rag_eta_report_abstract"
+}
+
+// RagEtaReportAbstractColumns get sql column name.获取数据库列名
+var RagEtaReportAbstractColumns = struct {
+	RagEtaReportAbstractId string
+	RagEtaReportId         string
+	Content                string
+	QuestionId             string
+	QuestionContent        string
+	Version                string
+	Tags                   string
+	TagsName               string
+	VectorKey              string
+	ModifyTime             string
+	CreateTime             string
+}{
+	RagEtaReportAbstractId: "rag_eta_report_abstract_id",
+	RagEtaReportId:         "rag_eta_report_id",
+	Content:                "content",
+	QuestionId:             "question_id",
+	QuestionContent:        "question_content",
+	Version:                "version",
+	Tags:                   "tags",
+	TagsName:               "tags_name",
+	VectorKey:              "vector_key",
+	ModifyTime:             "modify_time",
+	CreateTime:             "create_time",
+}
+
+func (m *RagEtaReportAbstract) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) Update(updateCols []string) (err error) {
+	err = global.DbMap[utils.DbNameAI].Select(updateCols).Updates(&m).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) Del() (err error) {
+	err = global.DbMap[utils.DbNameAI].Delete(&m).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetById(id int) (item *RagEtaReportAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", RagEtaReportAbstractColumns.RagEtaReportAbstractId), id).First(&item).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetByIdList(idList []int) (items []*RagEtaReportAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s in (?) ", RagEtaReportAbstractColumns.RagEtaReportAbstractId), idList).Find(&items).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetListByQuestionId(questionId int) (items []*RagEtaReportAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ? ", RagEtaReportAbstractColumns.QuestionId), questionId).Find(&items).Error
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*RagEtaReportAbstract, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s  order by rag_eta_report_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 *RagEtaReportAbstract) DelByIdList(idList []int) (err error) {
+	if len(idList) <= 0 {
+		return
+	}
+	sqlStr := fmt.Sprintf(`delete from %s where %s in (?)`, m.TableName(), RagEtaReportAbstractColumns.RagEtaReportAbstractId)
+	err = global.DbMap[utils.DbNameAI].Exec(sqlStr, idList).Error
+
+	return
+}
+
+// GetByRagEtaReportId
+// @Description: 根据报告id获取摘要
+// @author: Roc
+// @receiver m
+// @datetime 2025-03-07 10:00:59
+// @param id int
+// @return item *RagEtaReportAbstract
+// @return err error
+func (m *RagEtaReportAbstract) GetByRagEtaReportId(id int) (item *RagEtaReportAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ?", RagEtaReportAbstractColumns.RagEtaReportId), id).Order(fmt.Sprintf(`%s DESC`, RagEtaReportAbstractColumns.RagEtaReportAbstractId)).First(&item).Error
+
+	return
+}
+
+// GetByRagEtaReportIdAndQuestionId
+// @Description: 根据报告id和提示词ID获取摘要
+// @author: Roc
+// @receiver m
+// @datetime 2025-04-17 17:39:27
+// @param articleId int
+// @param questionId int
+// @return item *RagEtaReportAbstract
+// @return err error
+func (m *RagEtaReportAbstract) GetByRagEtaReportIdAndQuestionId(articleId, questionId int) (item *RagEtaReportAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ? AND %s = ? ", RagEtaReportAbstractColumns.RagEtaReportId, RagEtaReportAbstractColumns.QuestionId), articleId, questionId).Order(fmt.Sprintf(`%s DESC`, RagEtaReportAbstractColumns.RagEtaReportAbstractId)).First(&item).Error
+
+	return
+}
+
+type RagEtaReportAbstractView struct {
+	RagEtaReportAbstractId int    `gorm:"primaryKey;column:rag_eta_report_abstract_id" description:"-"`
+	RagEtaReportId         int    `gorm:"column:rag_eta_report_id" description:"ETA报告id"`
+	Abstract               string `gorm:"column:abstract;type:longtext;comment:摘要内容;" description:"摘要内容"`
+	QuestionId             int    `gorm:"column:question_id" description:"提示词Id"`
+	QuestionContent        string `gorm:"column:question_content" description:"questionContent"`
+	Version                int    `gorm:"column:version" description:"版本号"`
+	Tags                   string `gorm:"column:tags" description:"标签"`
+	TagsName               string `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	VectorKey              string `gorm:"column:vector_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:"标题"`
+}
+
+type RagEtaReportAbstractItem struct {
+	RagEtaReportAbstractId int       `gorm:"primaryKey;column:rag_eta_report_abstract_id" description:"-"`
+	RagEtaReportId         int       `gorm:"column:rag_eta_report_id" description:"ETA报告id"`
+	Content                string    `gorm:"column:content" description:"摘要内容"`
+	QuestionId             int       `gorm:"column:question_id" description:"提示词Id"`
+	QuestionContent        string    `gorm:"column:question_content" description:"questionContent"`
+	Version                int       `gorm:"column:version" description:"版本号"`
+	Tags                   string    `gorm:"column:tags" description:"标签"`
+	TagsName               string    `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	VectorKey              string    `gorm:"column:vector_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:"标题"`
+}
+
+func (m *RagEtaReportAbstractItem) ToView() RagEtaReportAbstractView {
+	return RagEtaReportAbstractView{
+		RagEtaReportAbstractId: m.RagEtaReportAbstractId,
+		RagEtaReportId:         m.RagEtaReportId,
+		Abstract:               m.Content,
+		Version:                m.Version,
+		VectorKey:              m.VectorKey,
+		ModifyTime:             utils.DateStrToDateTimeStr(m.ModifyTime),
+		CreateTime:             utils.DateStrToDateTimeStr(m.CreateTime),
+		Title:                  m.Title,
+		QuestionId:             m.QuestionId,
+		Tags:                   m.Tags,
+		TagsName:               m.TagsName,
+		QuestionContent:        m.QuestionContent,
+	}
+}
+
+func (m *RagEtaReportAbstract) EtaReportAbstractItem(list []*RagEtaReportAbstractItem) (etaReportAbstractViewList []RagEtaReportAbstractView) {
+	etaReportAbstractViewList = make([]RagEtaReportAbstractView, 0)
+
+	for _, v := range list {
+		etaReportAbstractViewList = append(etaReportAbstractViewList, v.ToView())
+	}
+	return
+}
+
+func (m *RagEtaReportAbstract) GetListByPlatformCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*RagEtaReportAbstractItem, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s AS a 
+          WHERE 1=1  %s order by a.modify_time DESC,a.rag_eta_report_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 *RagEtaReportAbstract) GetCountByPlatformCondition(condition string, pars []interface{}) (total int, err error) {
+	var intNull sql.NullInt64
+	sqlStr := fmt.Sprintf(`SELECT COUNT(1) total FROM %s AS a 
+          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 *RagEtaReportAbstract) GetPageListByPlatformCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*RagEtaReportAbstractItem, err error) {
+
+	total, err = m.GetCountByPlatformCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByPlatformCondition(``, condition, pars, startSize, pageSize)
+	}
+
+	return
+}
+
+func (m *RagEtaReportAbstract) GetListByTagAndPlatformCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*RagEtaReportAbstractItem, err error) {
+	if field == "" {
+		field = "*"
+	}
+	sqlStr := fmt.Sprintf(`SELECT %s FROM %s AS a 
+          JOIN wechat_article AS b ON a.rag_eta_report_id=b.rag_eta_report_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.rag_eta_report_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 *RagEtaReportAbstract) 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.rag_eta_report_id=b.rag_eta_report_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 *RagEtaReportAbstract) GetPageListByTagAndPlatformCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*RagEtaReportAbstractItem, err error) {
+
+	total, err = m.GetCountByTagAndPlatformCondition(condition, pars)
+	if err != nil {
+		return
+	}
+	if total > 0 {
+		items, err = m.GetListByTagAndPlatformCondition(`a.rag_eta_report_abstract_id,a.rag_eta_report_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 ragEtaReportAbstractIdList []int
+// @return err error
+func (m *RagEtaReportAbstract) DelVectorKey(ragEtaReportAbstractIdList []int) (err error) {
+	sqlStr := fmt.Sprintf(`UPDATE %s set vector_key = '' WHERE rag_eta_report_abstract_id IN (?)`, m.TableName())
+	err = global.DbMap[utils.DbNameAI].Exec(sqlStr, ragEtaReportAbstractIdList).Error
+
+	return
+}

+ 9 - 0
models/rag/request/rag_eta_report.go

@@ -0,0 +1,9 @@
+package request
+
+type BeachOpRagEtaReportAbstractReq struct {
+	RagEtaReportAbstractIdList    []int  `description:"摘要id"`
+	NotRagEtaReportAbstractIdList []int  `description:"不需要的摘要id"`
+	KeyWord                       string `description:"关键字"`
+	TagId                         string `description:"标签id"`
+	IsSelectAll                   bool   `description:"是否选择所有摘要"`
+}

+ 1 - 1
models/rag/request/wechat_platform.go

@@ -28,6 +28,6 @@ type BeachOpAbstractReq struct {
 	WechatArticleAbstractIdList    []int  `description:"摘要id"`
 	NotWechatArticleAbstractIdList []int  `description:"不需要的摘要id"`
 	KeyWord                        string `description:"关键字"`
-	TagId                          int    `description:"标签id"`
+	TagId                          string `description:"标签id"`
 	IsSelectAll                    bool   `description:"是否选择所有摘要"`
 }

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

@@ -9,3 +9,18 @@ type AbstractListListResp struct {
 	List   []rag.WechatArticleAbstractView
 	Paging *paging.PagingItem `description:"分页数据"`
 }
+
+type RagEtaReportAbstractListListResp struct {
+	List   []rag.RagEtaReportAbstractView
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type RagEtaReportItemAbstractListListResp struct {
+	List   []rag.RagEtaReportAbstractView
+	Paging *paging.PagingItem `description:"分页数据"`
+}
+
+type WechatArticleItemAbstractListListResp struct {
+	List   []rag.WechatArticleAbstractView
+	Paging *paging.PagingItem `description:"分页数据"`
+}

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

@@ -9,3 +9,8 @@ type QuestionListListResp struct {
 	List   []rag.QuestionView
 	Paging *paging.PagingItem `description:"分页数据"`
 }
+
+type QuestionOpAuthResp struct {
+	Status string `description:"状态"`
+	Tip    string `description:"提示信息"`
+}

+ 23 - 1
models/rag/tag.go

@@ -5,6 +5,7 @@ import (
 	"eta/eta_api/global"
 	"eta/eta_api/utils"
 	"fmt"
+	"strings"
 	"time"
 )
 
@@ -37,6 +38,12 @@ var TagColumns = struct {
 	CreateTime: "create_time",
 }
 
+func (m *Tag) Create() (err error) {
+	err = global.DbMap[utils.DbNameAI].Create(&m).Error
+
+	return
+}
+
 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
 
@@ -70,7 +77,6 @@ func (m *Tag) GetCountByCondition(condition string, pars []interface{}) (total i
 }
 
 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
@@ -81,3 +87,19 @@ func (m *Tag) GetPageListByCondition(condition string, pars []interface{}, start
 
 	return
 }
+
+var aiAbstractTagMap map[string]int
+
+func (m *Tag) GetTagIdByName(tagName string) (tagId int, err error) {
+	tagName = strings.TrimSpace(tagName)
+	tagId, ok := aiAbstractTagMap[tagName]
+	if ok {
+		return
+	}
+
+	var item *Tag
+	sqlStr := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? `, m.TableName(), TagColumns.TagName)
+	err = global.DbMap[utils.DbNameAI].Raw(sqlStr, tagName).First(&item).Error
+
+	return
+}

+ 82 - 12
models/rag/wechat_article_abstract.go

@@ -11,11 +11,15 @@ import (
 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"` // 摘要内容
+	Content                 string    `gorm:"column:content;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"`
+	QuestionId              int       `gorm:"column:question_id" description:"提示词Id"`
+	Tags                    string    `gorm:"column:tags" description:"标签"`
+	TagsName                string    `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	QuestionContent         string    `gorm:"column:question_content" description:"提示词内容"`
 }
 
 // TableName get sql table name.获取数据库表名
@@ -27,6 +31,10 @@ func (m *WechatArticleAbstract) TableName() string {
 var WechatArticleAbstractColumns = struct {
 	WechatArticleAbstractID string
 	WechatArticleID         string
+	QuestionId              string
+	Tags                    string
+	TagsName                string
+	QuestionContent         string
 	Content                 string
 	Version                 string
 	ModifyTime              string
@@ -34,6 +42,9 @@ var WechatArticleAbstractColumns = struct {
 }{
 	WechatArticleAbstractID: "wechat_article_abstract_id",
 	WechatArticleID:         "wechat_article_id",
+	QuestionId:              "question_id",
+	TagsName:                "tags_name",
+	QuestionContent:         "question_content",
 	Content:                 "content",
 	Version:                 "version",
 	ModifyTime:              "modify_time",
@@ -70,6 +81,12 @@ func (m *WechatArticleAbstract) GetByIdList(idList []int) (items []*WechatArticl
 	return
 }
 
+func (m *WechatArticleAbstract) GetListByQuestionId(questionId int) (items []*WechatArticleAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ? ", WechatArticleAbstractColumns.QuestionId), questionId).Find(&items).Error
+
+	return
+}
+
 func (m *WechatArticleAbstract) GetListByCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*WechatArticleAbstract, err error) {
 	if field == "" {
 		field = "*"
@@ -105,6 +122,21 @@ func (m *WechatArticleAbstract) GetByWechatArticleId(id int) (item *WechatArticl
 	return
 }
 
+// GetByWechatArticleIdAndQuestionId
+// @Description: 根据报告id和提示词ID获取摘要
+// @author: Roc
+// @receiver m
+// @datetime 2025-04-17 17:39:27
+// @param articleId int
+// @param questionId int
+// @return item *WechatArticleAbstract
+// @return err error
+func (m *WechatArticleAbstract) GetByWechatArticleIdAndQuestionId(articleId, questionId int) (item *WechatArticleAbstract, err error) {
+	err = global.DbMap[utils.DbNameAI].Where(fmt.Sprintf("%s = ? AND %s = ? ", WechatArticleAbstractColumns.WechatArticleID, WechatArticleAbstractColumns.QuestionId), articleId, questionId).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"`
@@ -117,12 +149,16 @@ type WechatArticleAbstractView struct {
 	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"`
+	QuestionId              int    `gorm:"column:question_id" description:"提示词Id"`
+	Tags                    string `gorm:"column:tags" description:"标签"`
+	TagsName                string `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	QuestionContent         string `gorm:"column:question_content" description:"提示词内容"`
 }
 
 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:"摘要内容"` //
+	Content                 string    `gorm:"column:content;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"`
@@ -130,13 +166,17 @@ type WechatArticleAbstractItem struct {
 	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"`
+	QuestionId              int       `gorm:"column:question_id" description:"提示词Id"`
+	Tags                    string    `gorm:"column:tags" description:"标签"`
+	TagsName                string    `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	QuestionContent         string    `gorm:"column:question_content" description:"提示词内容"`
 }
 
 func (m *WechatArticleAbstractItem) ToView() WechatArticleAbstractView {
 	return WechatArticleAbstractView{
 		WechatArticleAbstractId: m.WechatArticleAbstractId,
 		WechatArticleId:         m.WechatArticleId,
-		Abstract:                m.Abstract,
+		Abstract:                m.Content,
 		Version:                 m.Version,
 		VectorKey:               m.VectorKey,
 		ModifyTime:              utils.DateStrToDateTimeStr(m.ModifyTime),
@@ -144,16 +184,47 @@ func (m *WechatArticleAbstractItem) ToView() WechatArticleAbstractView {
 		Title:                   m.Title,
 		Link:                    m.Link,
 		TagId:                   m.TagId,
+		QuestionId:              m.QuestionId,
+		Tags:                    m.Tags,
+		TagsName:                m.TagsName,
+		QuestionContent:         m.QuestionContent,
 	}
 }
 
-func (m *WechatArticleAbstract) WechatArticleAbstractItem(list []*WechatArticleAbstractItem) (wechatArticleViewList []WechatArticleAbstractView) {
-	wechatArticleViewList = make([]WechatArticleAbstractView, 0)
+type WechatPlatArticleAbstractItem 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"`
+	QuestionId              int       `gorm:"column:question_id" description:"提示词Id"`
+	Tags                    string    `gorm:"column:tags" description:"标签"`
+	TagsName                string    `gorm:"column:tags_name" description:"标签名,多个用英文逗号隔开"`
+	QuestionContent         string    `gorm:"column:question_content" description:"提示词内容"`
+}
 
-	for _, v := range list {
-		wechatArticleViewList = append(wechatArticleViewList, v.ToView())
+func (m *WechatPlatArticleAbstractItem) 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,
+		QuestionId:              m.QuestionId,
+		Tags:                    m.Tags,
+		TagsName:                m.TagsName,
+		QuestionContent:         m.QuestionContent,
 	}
-	return
 }
 
 func (m *WechatArticleAbstract) GetListByPlatformCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*WechatArticleAbstractItem, err error) {
@@ -185,19 +256,18 @@ func (m *WechatArticleAbstract) GetCountByPlatformCondition(condition string, pa
 }
 
 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)
+		items, err = m.GetListByPlatformCondition(`a.*`, condition, pars, startSize, pageSize)
 	}
 
 	return
 }
 
-func (m *WechatArticleAbstract) GetListByTagAndPlatformCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*WechatArticleAbstractItem, err error) {
+func (m *WechatArticleAbstract) GetListByTagAndPlatformCondition(field, condition string, pars []interface{}, startSize, pageSize int) (items []*WechatPlatArticleAbstractItem, err error) {
 	if field == "" {
 		field = "*"
 	}
@@ -227,7 +297,7 @@ func (m *WechatArticleAbstract) GetCountByTagAndPlatformCondition(condition stri
 	return
 }
 
-func (m *WechatArticleAbstract) GetPageListByTagAndPlatformCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*WechatArticleAbstractItem, err error) {
+func (m *WechatArticleAbstract) GetPageListByTagAndPlatformCondition(condition string, pars []interface{}, startSize, pageSize int) (total int, items []*WechatPlatArticleAbstractItem, err error) {
 
 	total, err = m.GetCountByTagAndPlatformCondition(condition, pars)
 	if err != nil {

+ 58 - 6
models/report.go

@@ -4,6 +4,7 @@ import (
 	sql2 "database/sql"
 	"errors"
 	"eta/eta_api/global"
+	"eta/eta_api/models/report"
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/rdlucklib/rdluck_tools/paging"
@@ -91,6 +92,7 @@ type Report struct {
 	InheritReportId     int       `description:"待继承的报告ID"`
 	VoiceGenerateType   int       `description:"音频生成方式,0:系统生成,1:人工上传"`
 	RaiReportId         int       `description:"RAI报告ID"`
+	FreeLayoutConfig    string    `description:"'自由布局配置"`
 }
 
 func (m *Report) AfterFind(db *gorm.DB) (err error) {
@@ -447,6 +449,7 @@ type ReportDetail struct {
 	IsPublicPublish     int8      `description:"是否公开发布,1:是,2:否"`
 	ReportCreateTime    time.Time `description:"报告时间创建时间"`
 	RaiReportId         int       `description:"RAI报告ID"`
+	FreeLayoutConfig    string    `description:"'自由布局配置"`
 }
 
 func (m *ReportDetail) AfterFind(db *gorm.DB) (err error) {
@@ -615,7 +618,7 @@ type AddReq struct {
 	HeadResourceId     int    `description:"版头资源ID"`
 	EndResourceId      int    `description:"版尾资源ID"`
 	CollaborateType    int8   `description:"协作方式,1:个人,2:多人协作。默认:1"`
-	ReportLayout       int8   `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	ReportLayout       int8   `description:"报告布局,1:常规布局,2:智能布局,3:自由布局。默认:1"`
 	IsPublicPublish    int8   `description:"是否公开发布,1:是,2:否"`
 	InheritReportId    int    `description:"待继承的报告ID"`
 	GrantAdminIdList   []int  `description:"授权用户id列表"`
@@ -666,7 +669,7 @@ type EditReq struct {
 	HeadResourceId     int    `description:"版头资源ID"`
 	EndResourceId      int    `description:"版尾资源ID"`
 	//CollaborateType    int8   `description:"协作方式,1:个人,2:多人协作。默认:1"`
-	//ReportLayout       int8   `description:"报告布局,1:常规布局,2:智能布局。默认:1"`
+	//ReportLayout       int8   `description:"报告布局,1:常规布局,2:智能布局,3:自由布局。默认:1"`
 	IsPublicPublish  int8  `description:"是否公开发布,1:是,2:否"`
 	GrantAdminIdList []int `description:"授权用户id列表"`
 }
@@ -815,7 +818,6 @@ type SaveReportContent struct {
 	Content  string `description:"内容"`
 	ReportId int    `description:"报告id"`
 	NoChange int    `description:"内容是否未改变:1:内容未改变"`
-
 	// 以下是智能研报相关
 	ContentStruct  string `description:"内容组件"`
 	HeadImg        string `description:"报告头图地址"`
@@ -824,6 +826,9 @@ type SaveReportContent struct {
 	NeedSplice     int    `description:"是否拼接版头版位的标记,主要是为了兼容历史报告。0-不需要 1-需要"`
 	HeadResourceId int    `description:"版头资源ID"`
 	EndResourceId  int    `description:"版尾资源ID"`
+	//自由布局相关
+	FreeLayoutContentPages []report.ContentPage `description:"自由布局页面数据"`
+	FreeLayoutConfig       string               `description:"自由布局配置"`
 }
 
 //func EditReportContent(reportId int, content, contentSub string) (err error) {
@@ -953,9 +958,10 @@ func (reportInfo *Report) UpdateReport(cols []string) (err error) {
 // @Description: 晨周报详情
 type ReportDetailView struct {
 	*ReportDetail
-	ChapterList    []*ReportChapter
-	GrandAdminList []ReportDetailViewAdmin
-	PermissionList []ReportDetailViewPermission
+	ChapterList            []*ReportChapter
+	GrandAdminList         []ReportDetailViewAdmin
+	PermissionList         []ReportDetailViewPermission
+	FreeLayoutContentPages []*report.ContentPage
 }
 
 // ReportDetailViewAdmin
@@ -1700,3 +1706,49 @@ func GetAllPublishReportId() (items []int, err error) {
 	err = o.Raw(sql).Find(&items).Error
 	return
 }
+
+func InsertOrUpdateReportFreeLayoutContentPage(reportInfo *Report, ormList []*report.ReportFreeLayout) (err error) {
+	tx := global.DbMap[utils.DbNameReport].Begin()
+
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+	reportUpdateCols := []string{"Content", "ContentSub", "ContentStruct", "HeadImg", "EndImg", "CanvasColor", "HeadResourceId", "EndResourceId", "ModifyTime", "ContentModifyTime","FreeLayoutConfig"}
+	err = tx.Model(&reportInfo).Select(reportUpdateCols).Updates(reportInfo).Error
+	return report.BatchInsertOrUpdatePages(tx, ormList, false, reportInfo.Id, 0)
+}
+func UpdateChapterFreeLayoutContentPage(reportInfo *Report, chapterInfo *ReportChapter, updateCols []string, tickerList []*ReportChapterTicker, ormList []*report.ReportFreeLayout) (err error) {
+	tx := global.DbMap[utils.DbNameReport].Begin()
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	if err = tx.Model(&reportInfo).Select([]string{"LastModifyAdminId", "LastModifyAdminName", "ModifyTime","FreeLayoutConfig"}).Updates(reportInfo).Error; err != nil {
+		return
+	}
+	// 更新章节
+	if err = tx.Model(&chapterInfo).Select(updateCols).Updates(chapterInfo).Error; err != nil {
+		return
+	}
+	sql := ` DELETE FROM report_chapter_ticker WHERE report_chapter_id = ? `
+	// 清空并新增章节ticker
+	if err = tx.Exec(sql, chapterInfo.ReportChapterId).Error; err != nil {
+		return
+	}
+	tickerLen := len(tickerList)
+	if tickerLen > 0 {
+		err = tx.CreateInBatches(tickerList, len(tickerList)).Error
+		if err != nil {
+			return
+		}
+	}
+	return report.BatchInsertOrUpdatePages(tx, ormList, true, reportInfo.Id, chapterInfo.ReportChapterId)
+}

+ 266 - 0
models/report/report_free_layout.go

@@ -0,0 +1,266 @@
+package report
+
+import (
+	sql2 "database/sql"
+	"eta/eta_api/global"
+	"eta/eta_api/utils"
+	"gorm.io/gorm"
+	"gorm.io/gorm/clause"
+	"time"
+)
+
+type ReportFreeLayout struct {
+	Id              int       `gorm:"primaryKey;autoIncrement;column:id"` // 主键
+	ReportId        int       `gorm:"column:report_id"`                   // 研报Id
+	ReportChapterId int       `gorm:"column:report_chapter_id"`           // 章节Id
+	Page            int       `gorm:"column:page"`                        // 页码
+	IsChapter       int       `gorm:"column:is_chapter"`                  // 是否多章节
+	Content         string    `gorm:"column:content;size:255"`            // 内容
+	ContentStruct   string    `gorm:"column:content_struct;size:255"`     // 内容
+	CreateTime      time.Time `gorm:"column:create_time"`                 // 创建时间
+	ModifyTime      time.Time `gorm:"column:modify_time"`                 // 修改时间
+}
+type PagePositionEnum string
+
+const (
+	Left   PagePositionEnum = "left"
+	Right  PagePositionEnum = "right"
+	Center PagePositionEnum = "center"
+)
+
+type ContentPage struct {
+	Id              int    `json:"Id"`
+	Page            int    `json:"Page"`
+	Content         string `json:"Content"`
+	ContentStruct   string `json:"ContentStruct"`
+	ReportId        int    `json:"ChapterId"`
+	ReportChapterId int    `json:"ReportChapterId"`
+}
+
+func (cp *ContentPage) ToView(isChapter bool, ReportId int, ReportChapterId int) *ReportFreeLayout {
+	if isChapter {
+		return &ReportFreeLayout{
+			ReportId:        ReportId,
+			ReportChapterId: ReportChapterId,
+			Page:            cp.Page,
+			IsChapter:       1,
+			Content:         cp.Content,
+			ContentStruct:   cp.ContentStruct,
+			CreateTime:      time.Now(),
+		}
+	} else {
+		return &ReportFreeLayout{
+			ReportId:        ReportId,
+			ReportChapterId: ReportChapterId,
+			Page:            cp.Page,
+			IsChapter:       0,
+			Content:         cp.Content,
+			ContentStruct:   cp.ContentStruct,
+			CreateTime:      time.Now(),
+		}
+	}
+}
+func (cp *ReportFreeLayout) ToPageView() *ContentPage {
+	return &ContentPage{
+		Page:            cp.Page,
+		Content:         cp.Content,
+		ContentStruct:   cp.ContentStruct,
+		ReportId:        cp.ReportId,
+		ReportChapterId: cp.ReportChapterId,
+	}
+}
+func ToOrmViewList(srcList []ContentPage, isChapter bool, ReportId int, ReportChapterId int) (list []*ReportFreeLayout) {
+	for _, v := range srcList {
+		list = append(list, v.ToView(isChapter, ReportId, ReportChapterId))
+	}
+	return
+}
+
+func ToPageViewList(srcList []*ReportFreeLayout) (list []*ContentPage) {
+	for _, v := range srcList {
+		list = append(list, v.ToPageView())
+	}
+	return
+}
+
+// TableName 设置表名
+func (*ReportFreeLayout) TableName() string {
+	return "report_free_layout"
+}
+
+func SortPage(reportId int, tx *gorm.DB) (err error) {
+	if tx == nil {
+		tx = global.DbMap[utils.DbNameReport].Begin()
+		defer func() {
+			if err != nil {
+				_ = tx.Rollback()
+				return
+			}
+			_ = tx.Commit()
+		}()
+	}
+	sql := `select * from report_free_layout where report_id = ?  and is_chapter=1   order by page asc`
+	var ormList []*ReportFreeLayout
+	err = tx.Raw(sql, reportId).Find(&ormList).Error
+	if err != nil {
+		return
+	}
+	if len(ormList) == 0 {
+		return
+	}
+	chapterPages := make(map[int][]*ReportFreeLayout)
+	for _, v := range ormList {
+		chapterPages[v.ReportChapterId] = append(chapterPages[v.ReportChapterId], v)
+	}
+
+	chapterSql := `select report_chapter_id from report_chapter where report_id =? order by sort asc`
+	var chapterIds []int
+	err = tx.Raw(chapterSql, reportId).Scan(&chapterIds).Error
+	if err != nil {
+		return
+	}
+	initPage := 1
+	for _, chapter := range chapterIds {
+		chapterList := chapterPages[chapter]
+		for _, v := range chapterList {
+			v.Page = initPage
+			initPage++
+		}
+
+	}
+	var updateList []*ReportFreeLayout
+	for _, chapterList := range chapterPages {
+		updateList = append(updateList, chapterList...)
+	}
+	err = tx.Model(&ReportFreeLayout{}).Clauses(clause.OnConflict{
+		Columns:   []clause.Column{{Name: "Id"}},
+		DoUpdates: clause.AssignmentColumns([]string{"Page"}),
+	}).CreateInBatches(updateList, len(updateList)).Error
+	return
+}
+
+func DeleteChapters(reportId int, chapterId int) (err error) {
+	tx := global.DbMap[utils.DbNameReport].Begin()
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+	err = tx.Exec("delete from report_free_layout where   report_id = ?  and report_chapter_id=? and is_chapter=1", reportId, chapterId).Error
+	if err != nil {
+		return
+	}
+	err = SortPage(reportId, tx)
+	return
+}
+func BatchInsertOrUpdatePages(tx *gorm.DB, list []*ReportFreeLayout, isChapter bool, reportId, chapterId int) (err error) {
+	if isChapter {
+		err = tx.Exec("delete from report_free_layout where   report_id = ?  and report_chapter_id=? and is_chapter=1", reportId, chapterId).Error
+		if err != nil {
+			return
+		}
+		//err = tx.Model(&ReportFreeLayout{}).Clauses(clause.OnConflict{
+		//	Columns:   []clause.Column{{Name: "id"}},
+		//	DoUpdates: clause.AssignmentColumns([]string{"content", "content_struct", "modify_time"}),
+		//}).CreateInBatches(list, len(list)).Error
+		err = tx.Model(&ReportFreeLayout{}).CreateInBatches(list, len(list)).Error
+		if err != nil {
+			return
+		}
+		err = SortPage(reportId, tx)
+		return
+	} else {
+		err = tx.Exec("delete from  report_free_layout where report_id = ? and  is_chapter=0", reportId).Error
+		if err != nil {
+			return
+		}
+		err = tx.Model(&ReportFreeLayout{}).CreateInBatches(list, len(list)).Error
+		//.Clauses(clause.OnConflict{
+		//	Columns:   []clause.Column{{Name: "id"}},
+		//	DoUpdates: clause.AssignmentColumns([]string{"content", "content_struct", "page", "modify_time"}),
+		//})
+
+	}
+	return
+}
+func GetPrevFreeLayoutChaptersPagesByChapterId(reportId int, chapterId int) (pageNum int, err error) {
+	var pageNumNullable sql2.NullInt64
+	sql := `SELECT count(*) 
+FROM report_free_layout rfl
+JOIN report_chapter rc ON rc.report_id = rfl.report_id and rc.report_chapter_id=rfl.report_chapter_id
+WHERE rfl.report_id = ?
+  AND rc.sort < (
+    SELECT sort
+    FROM report_chapter
+    WHERE report_id = ? and report_chapter_id=?
+  )`
+	err = global.DbMap[utils.DbNameReport].Raw(sql, reportId, reportId, chapterId).Scan(&pageNumNullable).Error
+	if err != nil {
+		return
+	}
+	if pageNumNullable.Valid {
+		pageNum = int(pageNumNullable.Int64)
+	}
+	return
+}
+func GetFreeLayoutChapterPagesByReportId(reportId int) (list []*ContentPage, err error) {
+	var ormList []*ReportFreeLayout
+	sql := `select rfl.*,rc.sort from report_free_layout rfl LEFT JOIN report_chapter rc on rc.report_id=rfl.report_id and rc.report_chapter_id=rfl.report_chapter_id where rfl.report_id =? order by rc.sort,rfl.page asc`
+	err = global.DbMap[utils.DbNameReport].Raw(sql, reportId).Find(&ormList).Error
+	if err != nil {
+		return nil, err
+	}
+	list = ToPageViewList(ormList)
+	return
+}
+func GetSingleFreeLayoutChapterPagesByReportId(reportId, chapterId int) (list []*ContentPage, err error) {
+	var ormList []*ReportFreeLayout
+	sql := `select * from report_free_layout where report_id =? and report_chapter_id=? order by page asc`
+	err = global.DbMap[utils.DbNameReport].Raw(sql, reportId, chapterId).Find(&ormList).Error
+	if err != nil {
+		return nil, err
+	}
+	list = ToPageViewList(ormList)
+	return
+}
+func GetFreeLayoutPagesByReportId(id int) (list []*ContentPage, err error) {
+	var ormList []*ReportFreeLayout
+	sql := `select * from report_free_layout  where report_id =? and is_chapter=0 order by page asc`
+	err = global.DbMap[utils.DbNameReport].Raw(sql, id).Find(&ormList).Error
+	if err != nil {
+		return nil, err
+	}
+	list = ToPageViewList(ormList)
+	return
+}
+
+// GetReportFreeLayoutListByReportId
+// @Description: 根据报告ID和章节ID获取所有的布局列表
+// @author: Roc
+// @datetime 2025-04-16 13:46:38
+// @param reportId int
+// @param chapterId int
+// @return list []*ReportFreeLayout
+// @return err error
+func GetReportFreeLayoutListByReportId(reportId, chapterId int) (list []*ReportFreeLayout, err error) {
+	sql := `select * from report_free_layout  where report_id =? and report_chapter_id=? order by page asc`
+	err = global.DbMap[utils.DbNameReport].Raw(sql, reportId, chapterId).Find(&list).Error
+
+	return
+}
+
+// GetAllReportFreeLayoutListByReportId
+// @Description: 根据报告id获取所有的报告自由布局列表(含章节的)
+// @author: Roc
+// @datetime 2025-04-16 13:46:24
+// @param reportId int
+// @return list []*ReportFreeLayout
+// @return err error
+func GetAllReportFreeLayoutListByReportId(reportId int) (list []*ReportFreeLayout, err error) {
+	sql := `select * from report_free_layout  where report_id =? order by page asc`
+	err = global.DbMap[utils.DbNameReport].Raw(sql, reportId).Find(&list).Error
+
+	return
+}

+ 19 - 11
models/report_chapter.go

@@ -134,14 +134,17 @@ func (m *ReportChapterItem) ConvDateTimeStr() {
 // @Description: 章节详情(带有一些额外的数据)
 type ReportChapterItemResp struct {
 	ReportChapterItem
-	GrandAdminIdList []int  `description:"授权的用户id列表"`
-	PermissionIdList []int  `description:"关联的品种id列表"`
-	CanEdit          bool   `description:"是否可编辑"`
-	Editor           string `description:"编辑人"`
-	HeadImg          string `description:"报告头图地址"`
-	EndImg           string `description:"报告尾图地址"`
-	HeadStyle        string `description:"版头样式"`
-	EndStyle         string `description:"版尾样式"`
+	FreeLayoutContentPages []*report.ContentPage
+	FreeLayoutConfig       string
+	PreviousPagesNum       int
+	GrandAdminIdList       []int  `description:"授权的用户id列表"`
+	PermissionIdList       []int  `description:"关联的品种id列表"`
+	CanEdit                bool   `description:"是否可编辑"`
+	Editor                 string `description:"编辑人"`
+	HeadImg                string `description:"报告头图地址"`
+	EndImg                 string `description:"报告尾图地址"`
+	HeadStyle              string `description:"版头样式"`
+	EndStyle               string `description:"版尾样式"`
 }
 
 type ReportChapterResp struct {
@@ -222,6 +225,9 @@ type EditReportChapterReq struct {
 	CanvasColor    string `description:"画布颜色"`
 	HeadResourceId int    `description:"版头资源ID"`
 	EndResourceId  int    `description:"版尾资源ID"`
+	//自由布局研报相关
+	FreeLayoutContentPages []report.ContentPage `description:"自由布局内容"`
+	FreeLayoutConfig       string               `description:"'自由布局配置"`
 }
 
 type EditTickList struct {
@@ -504,9 +510,11 @@ func CountReportChapterByTypeId(typeId int) (count int, err error) {
 // AddReportChapter
 // @Description: 待添加的报告章节
 type AddReportChapter struct {
-	ReportChapter       *ReportChapter
-	GrantList           []*report.ReportChapterGrant
-	GrantPermissionList []*report.ReportChapterPermissionMapping
+	ReportChapter               *ReportChapter
+	GrantList                   []*report.ReportChapterGrant
+	GrantPermissionList         []*report.ReportChapterPermissionMapping
+	ReportChapterFreeLayoutList []*report.ReportFreeLayout
+	InheritReportChapterId      int `description:"继承的章节id"`
 }
 
 // EditReportChapterBaseInfoAndPermissionReq

+ 25 - 1
models/report_v2.go

@@ -17,7 +17,7 @@ import (
 // @param addReportChapterList []AddReportChapter
 // @return reportId int64
 // @return err error
-func AddReportAndChapter(reportItem *Report, allGrantUserList []*report.ReportGrant, addReportChapterList []AddReportChapter) (reportId int64, err error) {
+func AddReportAndChapter(reportItem *Report, reportFreeLayoutList []*report.ReportFreeLayout, allGrantUserList []*report.ReportGrant, addReportChapterList []AddReportChapter) (reportId int64, err error) {
 	to := global.DbMap[utils.DbNameReport].Begin()
 	defer func() {
 		if err != nil {
@@ -34,6 +34,17 @@ func AddReportAndChapter(reportItem *Report, allGrantUserList []*report.ReportGr
 	}
 	reportId = int64(reportItem.Id)
 
+	// 新增报告分页内容
+	if len(reportFreeLayoutList) > 0 {
+		for _, reportFreeLayout := range reportFreeLayoutList {
+			reportFreeLayout.ReportId = int(reportId)
+		}
+		err = to.CreateInBatches(reportFreeLayoutList, utils.MultiAddNum).Error
+		if err != nil {
+			return
+		}
+	}
+
 	// 新增报告授权
 	if len(allGrantUserList) > 0 {
 		for _, v := range allGrantUserList {
@@ -81,6 +92,19 @@ func AddReportAndChapter(reportItem *Report, allGrantUserList []*report.ReportGr
 				}
 			}
 
+			// 新增报告章节分页内容
+			if len(addReportChapter.ReportChapterFreeLayoutList) > 0 {
+				reportChapterFreeLayoutList := addReportChapter.ReportChapterFreeLayoutList
+				for _, reportChapterFreeLayout := range reportChapterFreeLayoutList {
+					reportChapterFreeLayout.ReportId = int(reportId)
+					reportChapterFreeLayout.ReportChapterId = chapterItem.ReportChapterId
+				}
+				err = to.CreateInBatches(reportChapterFreeLayoutList, utils.MultiAddNum).Error
+				if err != nil {
+					return
+				}
+			}
+
 		}
 	}
 

+ 216 - 0
routers/commentsRouter.go

@@ -6442,6 +6442,105 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"],
+        beego.ControllerComments{
+            Method: "DeleteInspectionConfig",
+            Router: `/edb_inspection/config/delete`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"],
+        beego.ControllerComments{
+            Method: "GetInspectionConfigDetail",
+            Router: `/edb_inspection/config/detail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"],
+        beego.ControllerComments{
+            Method: "InspectionConfigList",
+            Router: `/edb_inspection/config/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"],
+        beego.ControllerComments{
+            Method: "SaveInspectionConfig",
+            Router: `/edb_inspection/config/save`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"],
+        beego.ControllerComments{
+            Method: "UpdateInspectionConfigStatus",
+            Router: `/edb_inspection/config/status/update`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"],
+        beego.ControllerComments{
+            Method: "GetDashboardList",
+            Router: `/edb_inspection/dashboard`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"],
+        beego.ControllerComments{
+            Method: "HelpWordDownload",
+            Router: `/edb_inspection/help_word`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"],
+        beego.ControllerComments{
+            Method: "GetInspectionRecordDetail",
+            Router: `/edb_inspection/record`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionController"],
+        beego.ControllerComments{
+            Method: "InspectionSourceList",
+            Router: `/edb_inspection/source_list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionMessageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionMessageController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/edb_inspection/message/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionMessageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:EdbInspectionMessageController"],
+        beego.ControllerComments{
+            Method: "Read",
+            Router: `/edb_inspection/message/read`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:FactorEdbSeriesController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_manage:FactorEdbSeriesController"],
         beego.ControllerComments{
             Method: "Add",
@@ -7549,6 +7648,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_stat:EdbTerminalController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_stat:EdbTerminalController"],
+        beego.ControllerComments{
+            Method: "GetEdbInfoList",
+            Router: `/terminal/edb_info/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/data_stat:EdbTerminalController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_stat:EdbTerminalController"],
+        beego.ControllerComments{
+            Method: "SetEdbInfoTerminal",
+            Router: `/terminal/edb_info/set`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/data_stat:EdbTerminalController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/data_stat:EdbTerminalController"],
         beego.ControllerComments{
             Method: "TerminalIndexDirInfo",
@@ -8728,6 +8845,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
+        beego.ControllerComments{
+            Method: "GenerateAbstract",
+            Router: `/question/abstract/generate`,
+            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",
@@ -8737,6 +8863,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
+        beego.ControllerComments{
+            Method: "SetDefault",
+            Router: `/question/default/set`,
+            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: "UnSetDefault",
+            Router: `/question/default/unset`,
+            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",
@@ -8773,6 +8917,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:QuestionController"],
+        beego.ControllerComments{
+            Method: "CheckOpAuth",
+            Router: `/question/op_auth/check`,
+            AllowHTTPMethods: []string{"get"},
+            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: "TitleList",
@@ -8782,6 +8935,51 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"],
+        beego.ControllerComments{
+            Method: "Del",
+            Router: `/abstract/eta_report/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"],
+        beego.ControllerComments{
+            Method: "List",
+            Router: `/abstract/eta_report/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"],
+        beego.ControllerComments{
+            Method: "AddVector",
+            Router: `/abstract/eta_report/vector/add`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportAbstractController"],
+        beego.ControllerComments{
+            Method: "VectorDel",
+            Router: `/abstract/eta_report/vector/del`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportController"],
+        beego.ControllerComments{
+            Method: "AbstractList",
+            Router: `/eta_report/article/abstract/list`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:RagEtaReportController"],
         beego.ControllerComments{
             Method: "ArticleDel",
@@ -8863,6 +9061,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:UserChatController"],
+        beego.ControllerComments{
+            Method: "KnowledgeList",
+            Router: `/knowledge/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",
@@ -8881,6 +9088,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/llm:WechatPlatformController"],
+        beego.ControllerComments{
+            Method: "AbstractList",
+            Router: `/wechat_platform/article/abstract/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: "ArticleDel",

+ 3 - 0
routers/router.go

@@ -80,6 +80,7 @@ func init() {
 				&llm.AbstractController{},
 				&llm.PromoteController{},
 				&llm.RagEtaReportController{},
+				&llm.RagEtaReportAbstractController{},
 			),
 		),
 		web.NSNamespace("/banner",
@@ -207,6 +208,8 @@ func init() {
 				&data_manage.BaseFromGprRiskController{},
 				&data_manage.BaseFromPurangController{},
 				&data_manage.BaseFromRadishResearchController{},
+				&data_manage.EdbInspectionController{},
+				&data_manage.EdbInspectionMessageController{},
 			),
 		),
 		web.NSNamespace("/my_chart",

+ 1 - 0
services/crm_eta.go

@@ -121,6 +121,7 @@ type GetCrmTokenData struct {
 	AdminId         int    `description:"系统用户id"`
 	ProductName     string `description:"产品名称:admin,ficc,权益"`
 	Authority       int    `description:"管理权限,0:无,1:部门负责人,2:小组负责人,或者ficc销售主管,4:ficc销售组长"`
+	Enabled         int    `description:"禁启用状态:0-禁用;1-启用"`
 }
 
 // CodeLoginFromMiddleServer 中间服务-编码登录

+ 90 - 7
services/data/chart_info.go

@@ -4388,6 +4388,10 @@ func SeasonChartData(dataList []*data_manage.ChartEdbInfoMapping, seasonExtraCon
 
 			for _, v := range dataTimeMap {
 				valueList := dataTimeValueMap[v]
+				if len(valueList) <= 0 {
+					err = errors.New(`数据为空`)
+					return
+				}
 				stdev := utils.CalculateStandardDeviation(valueList)
 				stdev, _ = decimal.NewFromFloat(stdev).Round(4).Float64()
 
@@ -4517,8 +4521,10 @@ func MarkerLineCalculate(markerLine data_manage.MarkersLine, dataList interface{
 						length += 1
 					}
 				}
-				averge = averge / float64(length)
-				value = fmt.Sprintf("%.2f", averge)
+				if length > 0 {
+					averge = averge / float64(length)
+					value = fmt.Sprintf("%.2f", averge)
+				}
 			}
 		} else {
 			dataList := dataList.([]*data_manage.EdbDataList)
@@ -4560,9 +4566,11 @@ func MarkerLineCalculate(markerLine data_manage.MarkersLine, dataList interface{
 					length += 1
 				}
 			}
-			averge = averge / float64(length)
+			if length > 0 {
+				averge = averge / float64(length)
+				value = fmt.Sprintf("%.2f", averge)
+			}
 
-			value = fmt.Sprintf("%.2f", averge)
 		}
 	} else if markerLine.Calculation == 2 {
 		// 区间均值加N倍标准差
@@ -4614,7 +4622,14 @@ func MarkerLineCalculate(markerLine data_manage.MarkersLine, dataList interface{
 						length += 1
 					}
 				}
-				averge = averge / float64(length)
+
+				if length > 0 {
+					averge = averge / float64(length)
+				}
+				if len(faloatList) <= 0 {
+					err = errors.New(`数据为空`)
+					return
+				}
 				stdev := utils.CalculateStandardDeviation(faloatList)
 				stdev, _ = decimal.NewFromFloat(stdev).Round(4).Float64()
 
@@ -4664,8 +4679,13 @@ func MarkerLineCalculate(markerLine data_manage.MarkersLine, dataList interface{
 					length += 1
 				}
 			}
-			averge = averge / float64(length)
-
+			if length > 0 {
+				averge = averge / float64(length)
+			}
+			if len(floatList) <= 0 {
+				err = errors.New(`数据为空`)
+				return
+			}
 			stdev := utils.CalculateStandardDeviation(floatList)
 			stdev, _ = decimal.NewFromFloat(stdev).Round(4).Float64()
 
@@ -5354,3 +5374,66 @@ func getEdbDataMapListForSeason(chartInfoId, chartType int, calendar, startDate,
 
 	return
 }
+
+// GetMarkerLine
+// @Description: 获取标识线
+// @author: Roc
+// @datetime 2025-05-16 18:42:59
+// @param markerLine data_manage.MarkersLine
+// @param edbList []*data_manage.ChartEdbInfoMapping
+// @param chartInfo *data_manage.ChartInfoView
+// @param startDate string
+// @param endDate string
+// @return newMarkerLine data_manage.MarkersLine
+// @return err error
+func GetMarkerLine(markerLine data_manage.MarkersLine, edbList []*data_manage.ChartEdbInfoMapping, chartInfo *data_manage.ChartInfoView, startDate, endDate string) (newMarkerLine data_manage.MarkersLine, err error) {
+	newMarkerLine = markerLine
+
+	// 如果是横轴,那么直接返回
+	if markerLine.Axis == 3 {
+		return
+	}
+
+	var dataList interface{}
+	switch markerLine.EdbType {
+	case 0: // 图中第一个指标
+		dataList = edbList[0].DataList
+
+	case 1: // 其他指标
+		edbInfo, tmpErr := data_manage.GetEdbInfoById(markerLine.EdbInfoId)
+		if tmpErr != nil {
+			err = fmt.Errorf("指标计算标识线获取指标信息异常" + tmpErr.Error())
+			return
+		}
+		// 判断时间区间不为跟随图表的情况
+		if markerLine.TimeIntervalType != 0 {
+			startDate = markerLine.StartDate.Date
+			endDate = markerLine.EndDate.Date
+		}
+		dataList, err = data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, startDate, endDate)
+		if err != nil {
+			err = fmt.Errorf("指标计算标识线获取指标数据异常" + err.Error())
+			return
+		}
+	}
+
+	switch markerLine.TimeIntervalType {
+	// 0跟随图表 1自定义
+	case 0: // 0跟随图表
+		value, tmpErr := MarkerLineCalculate(markerLine, dataList, chartInfo)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		newMarkerLine.Value = value
+	case 1: // 自定义
+		value, tmpErr := MarkerLineCalculate(markerLine, dataList, chartInfo)
+		if tmpErr != nil {
+			err = fmt.Errorf("标识线配置异常" + err.Error())
+			return
+		}
+		newMarkerLine.Value = value
+	}
+
+	return
+}

+ 397 - 0
services/data/edb_inspection.go

@@ -0,0 +1,397 @@
+package data
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_api/models/data_manage/edb_inspection"
+	"eta/eta_api/models/data_manage"
+	"eta/eta_api/models/system"
+	"eta/eta_api/utils"
+	"fmt"
+	"strconv"
+	"strings"
+	"time"
+)
+
+// 所有巡检配置key
+var allDefaultEdbInspectionConfigKey = `edb_inspection_config:default:all:`
+
+// GetAllDefaultEdbInspectionConfigListBySource
+// @Description: 获取默认的所有巡检配置列表
+// @author: Roc
+// @datetime 2024-01-10 15:03:36
+// @param source int
+// @return list []*edb_inspection.EdbInspectionConfig
+// @return err error
+func GetAllDefaultEdbInspectionConfigListBySource(source int) (list []*edb_inspection.EdbInspectionConfig, err error) {
+	key := getAllDefaultEdbInspectionConfigKey(source)
+	if utils.Re == nil {
+		if utils.Re == nil && utils.Rc.IsExist(key) {
+			if data, err1 := utils.Rc.RedisBytes(key); err1 == nil {
+				err = json.Unmarshal(data, &list)
+				return
+			}
+		}
+	}
+
+	list, err = edb_inspection.GetListBySource(source)
+	if err != nil {
+		return
+	}
+
+	// 将数据加入缓存
+	if utils.Re == nil {
+		data, _ := json.Marshal(list)
+		utils.Rc.Put(key, data, 2*time.Hour)
+	}
+
+	return
+}
+
+// SaveEdbInspectionConfig
+// @Description: 设置巡检配置接口
+// @author: Roc
+// @datetime 2024-01-10 15:11:19
+// @param req *edb_inspection.EdbInspectionConfigAddReq
+// @return err error
+// @return errMsg string
+// @return isSendEmail bool
+func SaveEdbInspectionConfig(req *edb_inspection.EdbInspectionConfigAddReq) (err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+	errMsg = `保存失败`
+
+	if req.Source <= 0 {
+		errMsg = "来源不能为空"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	if req.TerminalCode == "" {
+		errMsg = "终端编码不能为空"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	// 判断终端是否存在
+	terminal, e := data_manage.GetEdbTerminalByCode(req.TerminalCode)
+	if e != nil {
+		errMsg = "终端不存在"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	if terminal.TerminalCode != "" && terminal.Source != req.Source {
+		errMsg = "数据源不匹配"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+
+	//判断该终端配置是否已存在
+	if req.ConfigId <= 0 {
+		inspectionConfig, e := edb_inspection.GetListByTerminalCode(req.TerminalCode)
+		if e != nil && !utils.IsErrNoRow(e) {
+			errMsg = "查询终端配置失败"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+		if e == nil && len(inspectionConfig) > 0 {
+			errMsg = "终端配置已存在"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+	}
+
+	lenConf := len(req.List)
+	if req.DateType == 1 && lenConf == 0 {
+		errMsg = "至少需要一个巡检配置"
+		err = errors.New(errMsg)
+		isSendEmail = false
+		return
+	}
+	// if req.DateType == 1 && lenConf > 5 {
+	// 	errMsg = "巡检时间设置最多不超过5个"
+	// 	err = errors.New(errMsg)
+	// 	isSendEmail = false
+	// 	return
+	// }
+
+	tmpArr := []string{"每自然日", "每交易日", "每周"}
+	// 配置的map,避免同一种类型配置同一个时间
+	configMap := make(map[string]string)
+	for _, v := range req.List {
+		if !utils.InArrayByStr(tmpArr, v.InspectionFrequency) {
+			errMsg = "巡检频率不合法"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+
+		if v.InspectionTime == "" {
+			errMsg = "请选择具体时间"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+
+		// 配置的map,避免同一种类型配置同一个时间
+		key := fmt.Sprint(v.InspectionFrequency, "_", v.InspectionDate, "_", v.InspectionTime)
+		if _, ok := configMap[key]; ok {
+			errMsg = "巡检频率和日期不能重复"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+		configMap[key] = key
+	}
+
+
+
+	configId := req.ConfigId
+	if configId > 0 {
+		// 查询配置
+		inspectionConfig, e := edb_inspection.GetById(configId)
+		if e != nil {
+			errMsg = "配置不存在"
+			err = errors.New(errMsg)
+			isSendEmail = false
+			return
+		}
+		inspectionConfig.ConfigId = req.ConfigId
+		updateCols := []string{"source", "terminal_code", "date_type", "start_time", "interval_time", "notify_users", "modify_time"}
+		inspectionConfig.Source = req.Source
+		inspectionConfig.TerminalCode = req.TerminalCode
+		inspectionConfig.DateType = req.DateType
+		inspectionConfig.StartTime = req.StartTime
+		inspectionConfig.IntervalTime = req.IntervalTime
+		inspectionConfig.NotifyUsers = req.NotifyUsers
+		inspectionConfig.ModifyTime = time.Now()
+		err = inspectionConfig.Update(updateCols)
+	} else {
+		// 创建巡检配置
+		inspectionConfig := &edb_inspection.EdbInspectionConfig{
+			Source:       req.Source,
+			TerminalCode: req.TerminalCode,
+			DateType:     req.DateType,
+			StartTime:    req.StartTime,
+			Status:       1,
+			IntervalTime: req.IntervalTime,
+			NotifyUsers:  req.NotifyUsers,
+			CreateTime:   time.Now(),
+			ModifyTime:   time.Now(),
+		}
+		err = inspectionConfig.Add()
+		if err != nil {
+			return
+		}
+		configId = inspectionConfig.ConfigId
+	}
+	if err != nil {
+		return
+	}
+
+	// 删除原先的配置
+	err = edb_inspection.DeleteEdbInspectionDateConfigByConfigId(configId)
+	if err != nil {
+		return
+	}
+
+	// 创建巡检日期配置
+	dateConfigList := make([]*edb_inspection.EdbInspectionDateConfig, 0)
+	if req.DateType == 1 {
+		for _, v := range req.List {
+			dateConfig := &edb_inspection.EdbInspectionDateConfig{
+				InspectionFrequency:    v.InspectionFrequency,
+				InspectionFrequencyDay: v.InspectionFrequencyDay,
+				InspectionDate:         v.InspectionDate,
+				InspectionTime:         v.InspectionTime,
+				ConfigId:              configId,
+				CreateTime:            time.Now(),
+				ModifyTime:            time.Now(),
+			}
+			dateConfigList = append(dateConfigList, dateConfig)
+		}
+
+		if len(dateConfigList) > 0 {
+			err = edb_inspection.AddEdbInspectionDateConfigList(dateConfigList, configId)
+			if err != nil {
+				return
+			}
+		}
+	}
+
+	// 清除缓存
+	{
+		key := getAllDefaultEdbInspectionConfigKey(req.Source)
+		if utils.Re == nil {
+			_ = utils.Rc.Delete(key)
+		}
+	}
+
+	return
+}
+
+// HandleInspectionTime
+// @Description: 处理巡检时间的显示
+// @author: Roc
+// @datetime 2024-01-10 17:00:03
+// @param source int
+// @param terminalCode string
+// @return list []*edb_inspection.EdbInspectionConfig
+// @return err error
+// @return errMsg string
+// @return isSendEmail bool
+func GetConfigList(source int, terminalCode string) (list []*edb_inspection.EdbInspectionConfigItem, err error, errMsg string, isSendEmail bool) {
+	isSendEmail = true
+	errMsg = "获取失败"
+
+	list, err = edb_inspection.GetConfigListBySourceAndTerminalCode(source, terminalCode)
+	if err != nil {
+		return
+	}
+	if len(list) <= 0 {
+		return
+	}
+	configIdList := make([]int64, 0)
+	adminIds := make([]int, 0)
+	for _, config := range list {
+		configIdList = append(configIdList, config.ConfigId)
+		adminIdSlice := strings.Split(config.NotifyUsers, ",")
+		for _, adminId := range adminIdSlice {
+			if adminId != "" {
+				id, _ := strconv.Atoi(adminId)
+				adminIds = append(adminIds, id)
+			}
+		}
+	}
+	adminNameMap := make(map[int]string)
+	if len(adminIds) > 0 {
+		adminList, e := system.GetAdminItemByIdList(adminIds)
+		if e != nil {
+			errMsg = "获取通知用户失败"
+			err = errors.New(errMsg+e.Error())
+			isSendEmail = false
+			return
+		}
+		for _, admin := range adminList {
+			adminNameMap[admin.AdminId] = admin.RealName
+		}
+	}
+
+	// 获取每个配置的日期配置
+	dateConfigs, tmpErr := edb_inspection.GetEdbInspectionDateConfigListByConfigIdList(configIdList)
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+
+	// 处理巡检时间显示
+	inspectionTimeList := make(map[int64][]string, 0)
+	for _, dateConfig := range dateConfigs {
+		inspectionTimeList[dateConfig.ConfigId] = append(inspectionTimeList[dateConfig.ConfigId], GetInspectionStr(dateConfig.InspectionFrequency, dateConfig.InspectionFrequencyDay, dateConfig.InspectionTime))
+	}
+	for _, config := range list {
+		if config.NotifyUsers != "" {
+			adminIdSlice := strings.Split(config.NotifyUsers, ",")
+			nameList := make([]string, 0)
+			for _, adminId := range adminIdSlice {
+				if adminId != "" {
+					id, _ := strconv.Atoi(adminId)
+					nameList = append(nameList, adminNameMap[id])
+				}
+			}
+			config.NotifyUsersName = strings.Join(nameList, ",")
+		}
+		if config.DateType == 1 {
+			tmpList, ok := inspectionTimeList[config.ConfigId]
+			if ok {
+				config.InspectionTime = strings.Join(tmpList, ",")
+			}
+		} else {
+			config.InspectionTime = fmt.Sprintf("%s开始每间隔%d小时", config.StartTime, config.IntervalTime)
+		}
+	}
+
+	return
+}
+
+func GetConfigDetail(configId int64) (detail *edb_inspection.EdbInspectionConfigDetailResp, err error) {
+	item, err := edb_inspection.GetById(configId)
+	if err != nil {
+		return
+	}
+	dateConfigs, err := edb_inspection.GetEdbInspectionDateConfigListByConfigId(configId)
+	if err != nil {
+		return
+	}
+	list := make([]edb_inspection.InspectionConfigReq, 0)
+	for _, dateConfig := range dateConfigs {
+		list = append(list, edb_inspection.InspectionConfigReq{
+			InspectionFrequency: dateConfig.InspectionFrequency,
+			InspectionFrequencyDay: dateConfig.InspectionFrequencyDay,
+			InspectionDate: dateConfig.InspectionDate,
+			InspectionTime: dateConfig.InspectionTime,
+		})
+	}
+	detail = &edb_inspection.EdbInspectionConfigDetailResp{
+		EdbInspectionConfig: item,
+		List:   list,
+	}
+
+	return
+}
+
+// getAllDefaultEdbInspectionConfigKey
+// @Description: 获取默认的所有巡检配置key
+// @author: Roc
+// @datetime 2024-01-10 15:02:49
+// @param source int
+// @return string
+func getAllDefaultEdbInspectionConfigKey(source int) string {
+	return allDefaultEdbInspectionConfigKey + fmt.Sprintf("%d", source)
+}
+
+// GetInspectionStr
+// @Description: 获取巡检配置的中文字符串
+// @author: Roc
+// @datetime 2024-01-10 16:05:10
+// @param inspectionFrequency string
+// @param inspectionFrequencyDay int
+// @param inspectionTime string
+// @return string
+func GetInspectionStr(inspectionFrequency string, inspectionFrequencyDay int, inspectionTime string) string {
+	inspectionDayStr := ``
+	switch inspectionFrequency {
+	case "每自然日", "每交易日":
+	case "每周":
+		switch inspectionFrequencyDay {
+		case 0:
+			inspectionDayStr = "日"
+		case 1:
+			inspectionDayStr = "一"
+		case 2:
+			inspectionDayStr = "二"
+		case 3:
+			inspectionDayStr = "三"
+		case 4:
+			inspectionDayStr = "四"
+		case 5:
+			inspectionDayStr = "五"
+		case 6:
+			inspectionDayStr = "六"
+		case 7:
+			inspectionDayStr = "日"
+		}
+	default:
+		if inspectionFrequencyDay > 0 {
+			inspectionDayStr = fmt.Sprintf("第%d天", inspectionFrequencyDay)
+		} else {
+			inspectionDayStr = `最后一天`
+		}
+	}
+	return inspectionFrequency + inspectionDayStr + " " + inspectionTime
+}

+ 114 - 0
services/data/edb_inspection_message.go

@@ -0,0 +1,114 @@
+package data
+
+import (
+	"errors"
+	"eta/eta_api/models/data_manage/edb_inspection"
+	"eta/eta_api/utils"
+	"time"
+
+	"github.com/rdlucklib/rdluck_tools/paging"
+)
+
+func ReadEdbInspectionMessage(messageId int64, adminId int) (msg string, err error) {
+	message, err := edb_inspection.GetMessageById(int64(messageId))
+	if err != nil {
+		if utils.IsErrNoRow(err) {
+			msg = "消息不存在"
+			return
+		}
+		msg = "获取消息失败"
+		return
+	}
+	if message.AdminId != int64(adminId) {
+		msg = "您没有权限查看该消息"
+		err = errors.New("no permission")
+		return
+	}
+	message.IsRead = 1
+	message.ModifyTime = time.Now()
+	err = message.Update([]string{"IsRead", "ModifyTime"})
+	if err != nil {
+		msg = "已读失败"
+		return
+	}
+	return
+}
+
+func ReadEdbInspectionMessageList(messageId []int64, adminId int) (msg string, err error) {
+	err = edb_inspection.BatchModifyEdbInspectionMessageIsRead(messageId, adminId)
+	if err != nil {
+		msg = "已读失败"
+		return
+	}
+	return
+}
+
+func SendInspectionMessages(adminId int, message *edb_inspection.EdbInspectionMessage) (data *edb_inspection.EdbInspectionMessageResp, err error) {
+	resp := edb_inspection.EdbInspectionMessageResp{
+		MessageId: message.MessageId,
+		Content: "巡检状态异常",
+		Remark: message.Message,
+		IsRead: message.IsRead,
+		Source: message.Source,
+		TerminalCode: message.TerminalCode,
+		InspectionTime: message.InspectionTime.Format(utils.FormatDateTime),
+	}
+	return &resp, nil
+}
+
+func GetHistoryInspectionMessages(adminId int) (items []*edb_inspection.EdbInspectionMessage, err error) {
+	messageList, err := edb_inspection.GetEdbInspectionMessageByAdminId(adminId)
+	if err != nil {
+		return
+	}
+
+	items = messageList
+	return
+}
+
+func GetInspectionMessageList(adminid int, currentIndex, pageSize int) (resp edb_inspection.EdbInspectionMessageListResp, err error) {
+	startSize := utils.StartIndex(currentIndex, pageSize)
+
+	total, err := edb_inspection.GetEdbInspectionMessageCountByAdminId(adminid)
+	if err != nil {
+		return
+	}
+	if total == 0 {
+		resp.List = make([]*edb_inspection.EdbInspectionMessageResp, 0)
+		resp.Paging = paging.GetPaging(currentIndex, pageSize, total)
+		return
+	}
+
+	messageList, err := edb_inspection.GetEdbInspectionMessagePageByAdminId(adminid, startSize, pageSize)
+	if err != nil {
+		return
+	}
+
+	unreadTotal, err := edb_inspection.GetEdbInspectionMessageUnreadCountByAdminId(adminid)
+	if err != nil {
+		return
+	}
+	resp.List = toEdbInspectionMessageResp(messageList)
+	resp.Paging = paging.GetPaging(currentIndex, pageSize, total)
+	resp.UnreadTotal = unreadTotal
+	return
+}
+
+func toEdbInspectionMessageResp(items []*edb_inspection.EdbInspectionMessage) (list []*edb_inspection.EdbInspectionMessageResp) {
+	list = make([]*edb_inspection.EdbInspectionMessageResp, 0)
+	for _, message := range items {
+		item := edb_inspection.EdbInspectionMessageResp{
+			MessageId: message.MessageId,
+			InspectionRecordId: message.InspectionRecordId,
+			AdminId: message.AdminId,
+			Content: "巡检状态异常",
+			Remark: message.Message,
+			IsRead: message.IsRead,
+			Source: message.Source,
+			TerminalCode: message.TerminalCode,
+			InspectionTime: message.InspectionTime.Format(utils.FormatDateTime),
+		}
+		list = append(list, &item)
+	}
+	return
+} 

+ 37 - 30
services/data/predict_edb_info_rule.go

@@ -774,6 +774,8 @@ func GetChartPredictEdbInfoDataListByRuleSeason(edbInfoId int, configValue strin
 		tmpHistoryVal := decimal.NewFromFloat(0) //往期的差值总和
 		tmpHistoryValNum := 0                    // 往期差值计算的数量
 
+		//fmt.Println(`填充后的数据长度:`, len(handleDataMap))
+
 		tmpLenAllDataList := len(allDataList)
 		tmpK := tmpLenAllDataList - 1    //上1期数据的下标
 		lastDayData := allDataList[tmpK] // 上1期的数据
@@ -783,6 +785,7 @@ func GetChartPredictEdbInfoDataListByRuleSeason(edbInfoId int, configValue strin
 		if tmpErr != nil {
 			err = errors.New("获取上期日期转换失败:" + tmpErr.Error())
 		}
+		//fmt.Println("当前需要计算的日期:", currentDate.Format(utils.FormatDate))
 		for _, year := range yearList {
 			moveDay := moveDayMap[year] //需要移动的天数
 			var tmpHistoryCurrentVal, tmpHistoryLastVal float64
@@ -790,39 +793,43 @@ func GetChartPredictEdbInfoDataListByRuleSeason(edbInfoId int, configValue strin
 
 			//前几年当日的日期
 			tmpHistoryCurrentDate := currentDate.AddDate(year-currentDate.Year(), 0, -moveDay)
-			for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找
-				tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, i)
-				if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
-					tmpHistoryCurrentVal = val
-					isFindHistoryCurrent = true
-					break
-				} else {
-					tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, -i)
-					if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
-						tmpHistoryCurrentVal = val
-						isFindHistoryCurrent = true
-						break
-					}
-				}
-			}
+			tmpHistoryCurrentVal, isFindHistoryCurrent = handleDataMap[tmpHistoryCurrentDate.Format(utils.FormatDate)]
+			//for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找(但是这个其实没有意义,因为这整个数据都通过插值法进行数据填充了, 2025-5-30 14:10:22)
+			//	tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, i)
+			//	if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
+			//		tmpHistoryCurrentVal = val
+			//		isFindHistoryCurrent = true
+			//		break
+			//	} else {
+			//		tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, -i)
+			//		if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
+			//			tmpHistoryCurrentVal = val
+			//			isFindHistoryCurrent = true
+			//			break
+			//		}
+			//	}
+			//}
 
 			//前几年上一期的日期
 			tmpHistoryLastDate := lastDay.AddDate(year-lastDay.Year(), 0, -moveDay)
-			for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找
-				tmpDate := tmpHistoryLastDate.AddDate(0, 0, i)
-				if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
-					tmpHistoryLastVal = val
-					isFindHistoryLast = true
-					break
-				} else {
-					tmpDate := tmpHistoryLastDate.AddDate(0, 0, -i)
-					if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
-						tmpHistoryLastVal = val
-						isFindHistoryLast = true
-						break
-					}
-				}
-			}
+			tmpHistoryLastVal, isFindHistoryLast = handleDataMap[tmpHistoryLastDate.Format(utils.FormatDate)]
+			//for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找(但是这个其实没有意义,因为这整个数据都通过插值法进行数据填充了, 2025-5-30 14:10:22)
+			//	tmpDate := tmpHistoryLastDate.AddDate(0, 0, i)
+			//	if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
+			//		tmpHistoryLastVal = val
+			//		isFindHistoryLast = true
+			//		break
+			//	} else {
+			//		tmpDate := tmpHistoryLastDate.AddDate(0, 0, -i)
+			//		if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
+			//			tmpHistoryLastVal = val
+			//			isFindHistoryLast = true
+			//			break
+			//		}
+			//	}
+			//}
+			
+			//fmt.Println("第一期日期:", tmpHistoryCurrentDate.Format(utils.FormatDate), ";第二期的日期:", tmpHistoryLastDate.Format(utils.FormatDate))
 
 			// 如果两个日期对应的数据都找到了,那么计算两期的差值
 			if isFindHistoryCurrent && isFindHistoryLast {

+ 35 - 39
services/edb_monitor/edb_monitor_message.go

@@ -2,22 +2,15 @@ package edbmonitor
 
 import (
 	"errors"
+	"eta/eta_api/global"
+	"eta/eta_api/models"
 	edbmonitor "eta/eta_api/models/edb_monitor"
 	"eta/eta_api/models/edb_monitor/response"
 	"eta/eta_api/utils"
-	"strconv"
 	"time"
-
-	"github.com/gorilla/websocket"
 	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
-var (
-	EDB_MONITOR_MESSAGE_CONNECT_CACHE = "edb_monitor_message_cache:"
-)
-
-var MonitorMessageConn = make(map[int]*websocket.Conn)
-
 func ReadEdbMonitorMessage(messageId, adminId int) (msg string, err error) {
 	message, err := edbmonitor.GetEdbMonitorMessageById(messageId)
 	if err != nil {
@@ -51,27 +44,27 @@ func ReadEdbMonitorMessageList(messageId []int, adminId int) (msg string, err er
 	return
 }
 
-func EdbMonitorMessageHealth(adminId int) (isClose bool, err error) {
-	conn := MonitorMessageConn[adminId]
-	if conn == nil {
-		err = errors.New("no connection")
-		isClose = true
-		return
-	}
-	_, msg, err := conn.ReadMessage()
-	if err != nil {
-		isClose = true
-		return
-	}
-	if string(msg) == "ping" {
-		healthKey := EDB_MONITOR_MESSAGE_CONNECT_CACHE + strconv.Itoa(adminId)
-		err = utils.Rc.Put(healthKey, "1", time.Minute*1)
-		if err != nil {
-			return
-		}
-	}
-	return
-}
+// func EdbMonitorMessageHealth(adminId int) (isClose bool, err error) {
+// 	conn := MonitorMessageConn[adminId]
+// 	if conn == nil {
+// 		err = errors.New("no connection")
+// 		isClose = true
+// 		return
+// 	}
+// 	_, msg, err := conn.ReadMessage()
+// 	if err != nil {
+// 		isClose = true
+// 		return
+// 	}
+// 	if string(msg) == "ping" {
+// 		healthKey := EDB_MONITOR_MESSAGE_CONNECT_CACHE + strconv.Itoa(adminId)
+// 		err = utils.Rc.Put(healthKey, "1", time.Minute*1)
+// 		if err != nil {
+// 			return
+// 		}
+// 	}
+// 	return
+// }
 
 func LogMessage(content, uniqueCode string, triggerTime time.Time, edbInfoId, edbInfoType, adminId, isRead, classifyId int) (err error) {
 	message := &edbmonitor.EdbMonitorMessage{
@@ -91,19 +84,22 @@ func LogMessage(content, uniqueCode string, triggerTime time.Time, edbInfoId, ed
 }
 
 func SendMessages(adminId, edbInfoId, edbInfoType int, classifyId int, edbUniqueCode, message string, triggerTime string) (err error) {
-	conn := MonitorMessageConn[adminId]
+	conn := global.MonitorMessageConn[adminId]
 	if conn == nil {
 		return errors.New("no connection")
 	}
-	msg := response.EdbMonitorMessageResp{
-		EdbInfoId:     edbInfoId,
-		EdbInfoType:   edbInfoType,
-		EdbUniqueCode: edbUniqueCode,
-		EdbClassifyId: classifyId,
-		Message:       message,
-		TriggerTime:   triggerTime,
+	resp := models.WebsocketMessageResponse{
+		MessageType: 0,
+		Data: response.EdbMonitorMessageResp{
+			EdbInfoId:     edbInfoId,
+			EdbInfoType:   edbInfoType,
+			EdbUniqueCode: edbUniqueCode,
+			EdbClassifyId: classifyId,
+			Message:       message,
+			TriggerTime:   triggerTime,
+		},
 	}
-	return conn.WriteJSON(msg)
+	return conn.WriteJSON(resp)
 }
 
 func GetHistoryMessages(adminId int) (items []*response.EdbMonitorMessageResp, err error) {

+ 317 - 0
services/elastic/rag_eta_report_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"
+	"strings"
+	"time"
+)
+
+// 摘要索引
+var EsRagEtaReportAbstractName = utils.EsRagEtaReportAbstractName
+
+type RagEtaReportAbstractItem struct {
+	RagEtaReportAbstractId int       `gorm:"primaryKey;column:rag_eta_report_abstract_id" description:"-"`
+	RagEtaReportId         int       `gorm:"column:rag_eta_report_id" description:"ETA报告id"`
+	Abstract               string    `gorm:"column:abstract;type:longtext;comment:摘要内容;" description:"摘要内容"`
+	QuestionId             int       `gorm:"column:question_id" description:"提示词Id"`
+	Version                int       `gorm:"column:version" description:"版本号"`
+	VectorKey              string    `gorm:"column:vector_key" description:"向量key标识"`
+	ModifyTime             time.Time `gorm:"column:modify_time" description:"modifyTime"`
+	CreateTime             time.Time `gorm:"column:create_time" description:"createTime"`
+	Title                  string    `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
+	TagIdList              []int     `description:"品种id列表"`
+	TagNameList            []string  `description:"品种名称列表"`
+}
+
+func (m *RagEtaReportAbstractItem) ToView() rag.RagEtaReportAbstractView {
+	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]
+	//}
+	tagsName := ``
+	if len(m.TagNameList) > 0 {
+		tagsName = strings.Join(m.TagNameList, `,`)
+	}
+
+	return rag.RagEtaReportAbstractView{
+		RagEtaReportAbstractId: m.RagEtaReportAbstractId,
+		RagEtaReportId:         m.RagEtaReportId,
+		Abstract:               m.Abstract,
+		QuestionId:             m.QuestionId,
+		//QuestionContent:        m.,
+		Version: m.Version,
+		//Tags:                   m.Ta,
+		VectorKey:  m.VectorKey,
+		ModifyTime: modifyTime,
+		CreateTime: createTime,
+		Title:      m.Title,
+		TagsName:   tagsName,
+	}
+}
+
+func (m *RagEtaReportAbstractItem) ToViewList(list []*RagEtaReportAbstractItem) (wechatArticleViewList []rag.RagEtaReportAbstractView) {
+	wechatArticleViewList = make([]rag.RagEtaReportAbstractView, 0)
+
+	for _, v := range list {
+		wechatArticleViewList = append(wechatArticleViewList, v.ToView())
+	}
+	return
+}
+
+// RagEtaReportEsAddOrEdit
+// @Description: 新增/编辑微信文章
+// @author: Roc
+// @datetime 2025-03-13 10:24:05
+// @param docId string
+// @param item RagEtaReportAndPlatform
+// @return err error
+func RagEtaReportAbstractEsAddOrEdit(docId string, item RagEtaReportAbstractItem) (err error) {
+	if docId == "" {
+		return
+	}
+	if EsRagEtaReportAbstractName == `` {
+		return
+	}
+	defer func() {
+		if err != nil {
+			fmt.Println("RagEtaReportEsAddOrEdit Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+
+	resp, err := client.Index().Index(EsRagEtaReportAbstractName).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("RagEtaReportEsAddOrEdit", resp.Status, resp.Result)
+	}
+
+	return
+}
+
+// RagEtaReportEsDel
+// @Description: 删除微信文章
+// @author: Roc
+// @datetime 2025-03-13 10:23:55
+// @param docId string
+// @return err error
+func RagEtaReportAbstractEsDel(docId string) (err error) {
+	if docId == "" {
+		return
+	}
+	if EsRagEtaReportAbstractName == `` {
+		return
+	}
+	defer func() {
+		if err != nil {
+			fmt.Println("EsDeleteEdbInfoData Err:", err.Error())
+		}
+	}()
+	client := utils.EsClient
+
+	resp, err := client.Delete().Index(EsRagEtaReportAbstractName).Id(docId).Refresh(`true`).Do(context.Background())
+	if err != nil {
+		return
+	}
+	if resp.Status == 0 {
+		fmt.Println("删除成功")
+	} else {
+		fmt.Println("RagEtaReportEsDel", resp.Status, resp.Result)
+	}
+
+	return
+}
+
+// RagEtaReportAbstractEsSearch
+// @Description: 搜索
+// @author: Roc
+// @datetime 2025-03-13 19:54:54
+// @param keywordStr string
+// @param tagIdList []int
+// @param from int
+// @param size int
+// @param sortMap map[string]string
+// @return total int64
+// @return list []*RagEtaReportAbstractItem
+// @return err error
+func RagEtaReportAbstractEsSearch(keywordStr string, tagIdList []int, questionId, from, size int, sortMap map[string]string) (total int64, list []*RagEtaReportAbstractItem, err error) {
+	indexName := EsRagEtaReportAbstractName
+	list = make([]*RagEtaReportAbstractItem, 0)
+	defer func() {
+		if err != nil {
+			fmt.Println("RagEtaReportAbstractEsSearch 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...))
+	}
+
+	// 提示词id
+	if questionId > 0 {
+		query = query.Must(elastic.NewTermsQuery("QuestionId", questionId))
+	}
+
+	// 名字匹配
+	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 searchRagEtaReportAbstract(indexName, query, sortList, from, size)
+}
+
+// searchEdbInfoDataV2 查询es中的数据
+func searchRagEtaReportAbstract(indexName string, query elastic.Query, sortList []*elastic.FieldSort, from, size int) (total int64, list []*RagEtaReportAbstractItem, err error) {
+	total, err = searchRagEtaReportAbstractTotal(indexName, query)
+	if err != nil {
+		return
+	}
+
+	// 获取列表数据
+	list, err = searchRagEtaReportAbstractList(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 searchRagEtaReportAbstractTotal(indexName string, query elastic.Query) (total int64, err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("searchRagEtaReportAbstractTotal 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 searchRagEtaReportAbstractList(indexName string, query elastic.Query, sortList []*elastic.FieldSort, from, size int) (list []*RagEtaReportAbstractItem, err error) {
+	list = make([]*RagEtaReportAbstractItem, 0)
+	defer func() {
+		if err != nil {
+			fmt.Println("searchRagEtaReportAbstractList 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(RagEtaReportAbstractItem)
+				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
+}

+ 7 - 1
services/elastic/rag_question.go

@@ -18,6 +18,7 @@ type RagQuestionItem struct {
 	QuestionTitle   string    `gorm:"column:question_title;type:varchar(255);comment:问题标题;" description:"问题标题"`
 	QuestionContent string    `gorm:"column:question_content;type:varchar(255);comment:问题内容;" description:"问题内容"`
 	Sort            int       `gorm:"column:sort;type:int(11);comment:排序;default:0;" description:"排序"`
+	IsDefault       int       `gorm:"column:is_default;type:int(1);comment:是否默认提示词;default:NULL;" description:"是否默认提示词"`
 	SysUserId       int       `gorm:"column:sys_user_id;type:int(11);comment:添加人id;default:0;" description:"添加人id"`
 	SysUserRealName string    `gorm:"column:sys_user_real_name;type:varchar(255);comment:添加人真实名称;" description:"添加人真实名称"`
 	ModifyTime      time.Time `gorm:"column:modify_time;type:datetime;default:NULL;" description:"modify_time"`
@@ -39,6 +40,7 @@ func (m *RagQuestionItem) ToView() rag.QuestionView {
 		QuestionTitle:   m.QuestionTitle,
 		QuestionContent: m.QuestionContent,
 		Sort:            m.Sort,
+		IsDefault:       m.IsDefault,
 		SysUserId:       m.SysUserId,
 		SysUserRealName: m.SysUserRealName,
 		ModifyTime:      modifyTime,
@@ -137,7 +139,7 @@ func RagQuestionEsDel(docId string) (err error) {
 // @return total int64
 // @return list []*RagQuestionItem
 // @return err error
-func RagQuestionEsSearch(keywordStr string, from, size int, sortMap map[string]string) (total int64, list []*RagQuestionItem, err error) {
+func RagQuestionEsSearch(keywordStr string, isQueryDefault bool, from, size int, sortMap map[string]string) (total int64, list []*RagQuestionItem, err error) {
 	indexName := EsRagQuestionName
 	list = make([]*RagQuestionItem, 0)
 	defer func() {
@@ -153,6 +155,10 @@ func RagQuestionEsSearch(keywordStr string, from, size int, sortMap map[string]s
 		query = query.Must(elastic.NewMultiMatchQuery(keywordStr, "QuestionContent"))
 	}
 
+	if isQueryDefault {
+		query = query.Must(elastic.NewTermQuery("IsDefault", 1))
+	}
+
 	// 排序
 	sortList := make([]*elastic.FieldSort, 0)
 	// 如果没有关键字,那么就走指标id倒序

+ 16 - 2
services/elastic/wechat_article_abstract.go

@@ -7,6 +7,7 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"github.com/olivere/elastic/v7"
+	"strings"
 	"time"
 )
 
@@ -17,7 +18,8 @@ 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:"摘要内容"` //
+	Abstract                string    `gorm:"column:abstract;type:longtext;comment:摘要内容;" description:"摘要内容"`
+	QuestionId              int       `gorm:"column:question_id" description:"提示词Id"`
 	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"`
@@ -25,6 +27,7 @@ type WechatArticleAbstractItem struct {
 	Title                   string    `gorm:"column:title;type:varchar(255);comment:标题;" description:"标题"`
 	Link                    string    `gorm:"column:link;type:varchar(255);comment:链接;" description:"链接"`
 	TagIdList               []int     `description:"品种id列表"`
+	TagNameList             []string  `description:"品种名称列表"`
 }
 
 func (m *WechatArticleAbstractItem) ToView() rag.WechatArticleAbstractView {
@@ -41,18 +44,24 @@ func (m *WechatArticleAbstractItem) ToView() rag.WechatArticleAbstractView {
 	if len(m.TagIdList) > 0 {
 		tagId = m.TagIdList[0]
 	}
+	tagsName := ``
+	if len(m.TagNameList) > 0 {
+		tagsName = strings.Join(m.TagNameList, `,`)
+	}
 	return rag.WechatArticleAbstractView{
 		WechatArticleAbstractId: m.WechatArticleAbstractId,
 		WechatArticleId:         m.WechatArticleId,
 		WechatPlatformId:        m.WechatPlatformId,
 		Abstract:                m.Abstract,
 		Version:                 m.Version,
+		QuestionId:              m.QuestionId,
 		VectorKey:               m.VectorKey,
 		ModifyTime:              modifyTime,
 		CreateTime:              createTime,
 		Title:                   m.Title,
 		Link:                    m.Link,
 		TagId:                   tagId,
+		TagsName:                tagsName,
 	}
 }
 
@@ -147,7 +156,7 @@ func WechatArticleAbstractEsDel(docId string) (err error) {
 // @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) {
+func WechatArticleAbstractEsSearch(keywordStr string, tagIdList, platformIdList []int, questionId, from, size int, sortMap map[string]string) (total int64, list []*WechatArticleAbstractItem, err error) {
 	indexName := EsWechatArticleAbstractName
 	list = make([]*WechatArticleAbstractItem, 0)
 	defer func() {
@@ -177,6 +186,11 @@ func WechatArticleAbstractEsSearch(keywordStr string, tagIdList, platformIdList
 		query = query.Must(elastic.NewTermsQuery("WechatPlatformId", termsList...))
 	}
 
+	// 提示词id
+	if questionId > 0 {
+		query = query.Must(elastic.NewTermsQuery("QuestionId", questionId))
+	}
+
 	// 名字匹配
 	if keywordStr != `` {
 		query = query.Must(elastic.NewMultiMatchQuery(keywordStr, "Abstract"))

+ 334 - 0
services/llm.go

@@ -0,0 +1,334 @@
+package services
+
+import (
+	"encoding/json"
+	"eta/eta_api/cache"
+	"eta/eta_api/models/rag"
+	"eta/eta_api/models/system"
+	"eta/eta_api/utils"
+	"fmt"
+	"time"
+)
+
+// AddGenerateAbstractTask
+// @Description: 添加全部报告(微信文章/ETA报告)生成摘要任务
+// @author: Roc
+// @datetime 2025-04-16 17:02:18
+// @param question *rag.Question
+// @param sysUser *system.Admin
+func AddGenerateAbstractTask(question *rag.Question, sysUser *system.Admin) {
+	// 找出所有公众号文章Id
+	wechatArticleIdList, err := getAllWechatArticleIdList()
+	if err != nil {
+		return
+	}
+
+	// 找出所有Eta报告
+	ragEtaReportIdList, err := getAllEtaReportIdList()
+	if err != nil {
+		return
+	}
+
+	taskName := fmt.Sprintf("自动生成摘要%s-%s", time.Now().Format(utils.FormatShortDateTimeUnSpace), question.QuestionTitle)
+
+	aiTask := &rag.AiTask{
+		AiTaskID: 0,
+		TaskName: taskName,
+		TaskType: utils.AI_TASK_TYPE_GENERATE_ABSTRACT,
+		Status:   "init",
+		//StartTime:               time.Time{},
+		//EndTime:                 time.Time{},
+		CreateTime:   time.Now(),
+		UpdateTime:   time.Now(),
+		Parameters:   fmt.Sprint(question.QuestionId),
+		Logs:         "",
+		Errormessage: "",
+		Priority:     0,
+		RetryCount:   0,
+		//EstimatedCompletionTime: time.Time{},
+		//ActualCompletitonTime:   time.Time{},
+		Remark:          "",
+		SysUserID:       sysUser.AdminId,
+		SysUserRealName: sysUser.RealName,
+	}
+
+	taskRecordList := make([]*rag.AiTaskRecord, 0)
+	// 微信文章
+	for _, wechatArticleId := range wechatArticleIdList {
+		param := rag.QuestionGenerateAbstractParam{
+			QuestionId:  question.QuestionId,
+			ArticleType: `wechat_article`,
+			ArticleId:   wechatArticleId,
+		}
+		paramByte, tmpErr := json.Marshal(param)
+		if tmpErr != nil {
+			return
+		}
+		taskRecord := &rag.AiTaskRecord{
+			AiTaskRecordID: 0,
+			AiTaskID:       0,
+			Parameters:     string(paramByte),
+			Status:         "待处理",
+			Remark:         "",
+			ModifyTime:     time.Now(),
+			CreateTime:     time.Now(),
+		}
+		taskRecordList = append(taskRecordList, taskRecord)
+	}
+
+	// eta报告
+	for _, ragEtaReportId := range ragEtaReportIdList {
+		param := rag.QuestionGenerateAbstractParam{
+			QuestionId:  question.QuestionId,
+			ArticleType: `rag_eta_report`,
+			ArticleId:   ragEtaReportId,
+		}
+		paramByte, tmpErr := json.Marshal(param)
+		if tmpErr != nil {
+			return
+		}
+		taskRecord := &rag.AiTaskRecord{
+			AiTaskRecordID: 0,
+			AiTaskID:       0,
+			Parameters:     string(paramByte),
+			Status:         "待处理",
+			Remark:         "",
+			ModifyTime:     time.Now(),
+			CreateTime:     time.Now(),
+		}
+		taskRecordList = append(taskRecordList, taskRecord)
+	}
+
+	// 创建AI模块的任务,用于后面的任务调度去生成摘要
+	err = rag.AddAiTask(aiTask, taskRecordList)
+	if err != nil {
+		return
+	}
+
+	// 添加到缓存队列中
+	go addTaskToCache(aiTask.AiTaskID)
+
+	return
+}
+
+func addTaskToCache(aiTaskId int) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("addTaskToCache error: %v", err)
+		}
+	}()
+	obj := rag.AiTaskRecord{}
+	list, err := obj.GetAllListByCondition("*", ` AND ai_task_id = ? `, []interface{}{aiTaskId})
+	if err != nil {
+		return
+	}
+	for _, item := range list {
+		cache.AddAiTaskRecordOpToCache(item.AiTaskRecordID)
+	}
+}
+
+// getAllWechatArticleIdList
+// @Description: 获取所有的微信文章Id列表
+// @author: Roc
+// @datetime 2025-04-16 17:18:31
+// @return wechatArticleIdList []int
+// @return err error
+func getAllWechatArticleIdList() (wechatArticleIdList []int, err error) {
+	wechatArticleIdList = make([]int, 0)
+	pageSize := 10000
+	currentIndex := 1
+
+	// 注意,默认是10000条,如果超过10000条,需要分页查询
+	// 避免死循环
+	for {
+		tmpWechatArticleIdList, tmpErr := getWechatArticleIdList(currentIndex, pageSize)
+		if tmpErr != nil {
+			return
+		}
+		wechatArticleIdList = append(wechatArticleIdList, tmpWechatArticleIdList...)
+		if len(tmpWechatArticleIdList) < pageSize {
+			return
+		}
+		currentIndex++
+
+		// 超过100次,那么也退出,避免死循环
+		if currentIndex > 100 {
+			return
+		}
+	}
+
+}
+
+// getWechatArticleIdList
+// @Description: 分页获取微信文章Id列表
+// @author: Roc
+// @datetime 2025-04-16 17:18:44
+// @param currentIndex int
+// @param pageSize int
+// @return wechatArticleIdList []int
+// @return err error
+func getWechatArticleIdList(currentIndex, pageSize int) (wechatArticleIdList []int, err error) {
+	wechatArticleIdList = make([]int, 0)
+	var condition string
+	var pars []interface{}
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	condition += fmt.Sprintf(` AND %s = ? `, rag.WechatArticleColumns.IsDeleted)
+	pars = append(pars, 0)
+
+	obj := new(rag.WechatArticle)
+	list, err := obj.GetListByCondition(` wechat_article_id `, condition, pars, startSize, pageSize)
+	if err != nil {
+		return
+	}
+	for _, item := range list {
+		wechatArticleIdList = append(wechatArticleIdList, item.WechatArticleId)
+	}
+
+	return
+}
+
+// getAllEtaReportIdList
+// @Description: 获取所有的eta报告Id列表
+// @author: Roc
+// @datetime 2025-04-16 17:19:29
+// @return ragEtaReportIdList []int
+// @return err error
+func getAllEtaReportIdList() (ragEtaReportIdList []int, err error) {
+	ragEtaReportIdList = make([]int, 0)
+	pageSize := 10000
+	currentIndex := 1
+
+	// 注意,默认是10000条,如果超过10000条,需要分页查询
+	// 避免死循环
+	for {
+		tmpRagEtaReportIdList, tmpErr := getEtaReportIdList(currentIndex, pageSize)
+		if tmpErr != nil {
+			return
+		}
+		ragEtaReportIdList = append(ragEtaReportIdList, tmpRagEtaReportIdList...)
+		if len(tmpRagEtaReportIdList) < pageSize {
+			return
+		}
+		currentIndex++
+
+		// 超过100次,那么也退出,避免死循环
+		if currentIndex > 100 {
+			return
+		}
+	}
+
+}
+
+// getEtaReportIdList
+// @Description: 分页获取eta报告Id列表
+// @author: Roc
+// @datetime 2025-04-16 17:19:14
+// @param currentIndex int
+// @param pageSize int
+// @return ragEtaReportIdList []int
+// @return err error
+func getEtaReportIdList(currentIndex, pageSize int) (ragEtaReportIdList []int, err error) {
+	ragEtaReportIdList = make([]int, 0)
+	var condition string
+	var pars []interface{}
+
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	condition += fmt.Sprintf(` AND %s = ? AND %s = ? `, rag.RagEtaReportColumns.IsDeleted, rag.RagEtaReportColumns.IsPublished)
+	pars = append(pars, 0, 1)
+
+	obj := new(rag.RagEtaReport)
+	list, err := obj.GetListByCondition(` rag_eta_report_id `, condition, pars, startSize, pageSize)
+	if err != nil {
+		return
+	}
+	for _, item := range list {
+		ragEtaReportIdList = append(ragEtaReportIdList, item.RagEtaReportId)
+	}
+
+	return
+}
+
+// CheckOpQuestionAuth
+// @Description: 校验是否有权限操作提示词
+// @author: Roc
+// @datetime 2025-04-16 17:33:01
+// @return auth bool
+// @return err error
+func CheckOpQuestionAuth() (auth bool, err error) {
+	total, err := getNotFinishGenerateAbstractTaskNum()
+	if err != nil {
+		return
+	}
+	// 存在未完成的任务,则无权限
+	if total > 0 {
+		return
+	}
+
+	auth = true
+
+	return
+}
+
+// getNotFinishGenerateAbstractTaskNum
+// @Description: 获取未完成的生成摘要任务的数量
+// @author: Roc
+// @datetime 2025-04-16 17:31:12
+// @return total int
+// @return err error
+func getNotFinishGenerateAbstractTaskNum() (total int, err error) {
+	obj := rag.AiTask{}
+
+	var condition string
+	var pars []interface{}
+
+	condition += fmt.Sprintf(` AND %s NOT IN (?)  AND %s = ? `, rag.AiTaskColumns.Status, rag.AiTaskColumns.TaskType)
+	pars = append(pars, []string{`done`, `failed`}, utils.AI_TASK_TYPE_GENERATE_ABSTRACT)
+
+	total, err = obj.GetCountByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+
+	return
+}
+
+// GetNotFinishGenerateAbstractTaskNumByQuestionId
+// @Description: 根据提示词ID获取未完成的生成摘要任务的数量
+// @author: Roc
+// @datetime 2025-04-16 17:31:12
+// @return total int
+// @return err error
+func GetNotFinishGenerateAbstractTaskNumByQuestionId(questionId int) (total int, err error) {
+	obj := rag.AiTask{}
+
+	var condition string
+	var pars []interface{}
+
+	condition += fmt.Sprintf(` AND %s NOT IN (?)  AND %s = ?  AND %s = ? `, rag.AiTaskColumns.Status, rag.AiTaskColumns.TaskType, rag.AiTaskColumns.Parameters)
+	pars = append(pars, []string{`done`, `failed`}, utils.AI_TASK_TYPE_GENERATE_ABSTRACT, fmt.Sprint(questionId))
+
+	total, err = obj.GetCountByCondition(condition, pars)
+	if err != nil {
+		return
+	}
+
+	return
+}

+ 9 - 5
services/llm/facade/llm_service.go

@@ -98,10 +98,12 @@ func AIGCBaseOnPromote(aigc AIGC) (resp bus_response.AIGCEtaResponse, err error)
 				err = fmt.Errorf("创建文章文件失败,err: %v", fileErr)
 				return
 			}
+			file, err = os.Open(path)
 			defer func() {
-				_ = os.Remove(path)
+				_ = file.Close()
+				err = os.Remove(path)
+				fmt.Println(err)
 			}()
-			file, err = os.Open(path)
 			if err != nil {
 				utils.FileLog.Error("打开文件失败,err:", err)
 				return
@@ -188,14 +190,16 @@ func AIGCBaseOnPromote(aigc AIGC) (resp bus_response.AIGCEtaResponse, err error)
 				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
 			}
+			defer func() {
+				_ = file.Close()
+				_ = os.Remove(path)
+			}()
 			_, httpErr = llmService.UploadFileToTemplate([]*os.File{file}, param)
 			if httpErr != nil {
 				utils.FileLog.Error("上传文件失败,err:", err.Error())

+ 517 - 0
services/llm_report.go

@@ -1,13 +1,20 @@
 package services
 
 import (
+	"encoding/json"
+	"errors"
+	"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"
 	"fmt"
 	"golang.org/x/net/html"
 	"golang.org/x/net/html/atom"
+	"os"
 	"regexp"
+	"strconv"
 	"strings"
 	"time"
 )
@@ -179,6 +186,8 @@ func handleReportAddOrModifyKnowledge(reportId, reportChapterId int, title, auth
 		err = item.Create()
 	}
 
+	cache.AddRagEtaReportLlmOpToCache(item.RagEtaReportId, 0, true)
+
 	return
 }
 
@@ -244,6 +253,9 @@ func ReportUnPublishedKnowledgeByReportId(reportId int) {
 			errList = append(errList, fmt.Sprintf("第%d章:%s,异常:\n%s", item.ReportChapterId, item.Title, err.Error()))
 			continue
 		}
+
+		// 删除摘要
+		err = DelRagEtaReportAbstract([]int{item.RagEtaReportId})
 	}
 
 	return
@@ -270,3 +282,508 @@ func getArticleContent(content *strings.Builder, htmlContentNode *html.Node) {
 		getArticleContent(content, c)
 	}
 }
+
+// GenerateRagEtaReportAbstract
+// @Description: 文章摘要生成(默认提示词批量生成)
+// @author: Roc
+// @datetime 2025-04-24 11:24:53
+// @param item *rag.RagEtaReport
+// @param forceGenerate bool
+func GenerateRagEtaReportAbstract(item *rag.RagEtaReport, forceGenerate bool) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("文章摘要生成(默认提示词批量生成)失败,err:%v", err)
+		}
+	}()
+	// 内容为空,那就不需要生成摘要
+	if item.TextContent == `` {
+		return
+	}
+
+	questionObj := rag.Question{}
+	questionList, err := questionObj.GetListByCondition(``, ` AND is_default = 1 `, []interface{}{}, 0, 100)
+	if err != nil {
+		err = fmt.Errorf("获取问题列表失败,Err:" + err.Error())
+		return
+	}
+
+	// 没问题就不生成了
+	if len(questionList) <= 0 {
+		return
+	}
+
+	for _, question := range questionList {
+		GenerateRagEtaReportAbstractByQuestion(item, question, forceGenerate)
+	}
+
+	return
+}
+
+// GenerateRagEtaReportAbstractByQuestion
+// @Description: ETA报告摘要生成(根据提示词生成)
+// @author: Roc
+// @datetime 2025-04-24 11:23:49
+// @param item *rag.RagEtaReport
+// @param question *rag.Question
+// @param forceGenerate bool
+// @return err error
+func GenerateRagEtaReportAbstractByQuestion(item *rag.RagEtaReport, question *rag.Question, forceGenerate bool) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("文章摘要生成(根据提示词生成)失败,err:%v", err)
+		}
+	}()
+
+	// 内容为空,那就不需要生成摘要
+	if item.TextContent == `` {
+		return
+	}
+
+	abstractObj := rag.RagEtaReportAbstract{}
+	abstractItem, err := abstractObj.GetByRagEtaReportIdAndQuestionId(item.RagEtaReportId, question.QuestionId)
+	// 如果找到了,同时不是强制生成,那么就直接处理到知识库中
+	if err == nil && !forceGenerate {
+		// 摘要已经生成,不需要重复生成,只需要重新加入到向量库中
+		ReportAbstractToKnowledge(item, abstractItem, false)
+
+		return
+	}
+	// 如果是没找到数据,那么就将报错置空
+	if err != nil && utils.IsErrNoRow(err) {
+		err = nil
+	}
+
+	//你现在是一名资深的期货行业分析师,请基于以下的问题进行汇总总结,如果不能正常总结出来,那么就只需要回复我:sorry
+	questionStr := fmt.Sprintf(`%s\n%s`, `你现在是一名资深的期货行业分析师,请基于以下的问题进行汇总总结,如果不能正常总结出来,那么就只需要回复我:sorry。以下是问题:`, question.QuestionContent)
+	//开始对话
+	abstract, industryTags, tmpErr := getAnswerByContent(item.RagEtaReportId, utils.AI_ARTICLE_SOURCE_ETA_REPORT, questionStr)
+	if tmpErr != nil {
+		err = fmt.Errorf("LLM对话失败,Err:" + tmpErr.Error())
+		return
+	}
+
+	// 添加问答记录
+	//if len(addArticleChatRecordList) > 0 {
+	//	recordObj := rag.RagEtaReportChatRecord{}
+	//	err = recordObj.CreateInBatches(addArticleChatRecordList)
+	//	if err != nil {
+	//		return
+	//	}
+	//}
+
+	if abstract == `` {
+		return
+	}
+	if abstract == `sorry` || strings.Index(abstract, `根据已知信息无法回答该问题`) == 0 {
+		return
+	}
+
+	//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"})
+
+	var tagIdJsonStr string
+	var tagNameJsonStr string
+	// 标签ID
+	{
+		tagIdList := make([]int, 0)
+		tagNameList := make([]string, 0)
+		tagIdMap := make(map[int]bool)
+
+		if abstractItem != nil && abstractItem.Tags != `` {
+			tmpErr = json.Unmarshal([]byte(abstractItem.Tags), &tagIdList)
+			if tmpErr != nil {
+				utils.FileLog.Info(fmt.Sprintf("json.Unmarshal 失败,标签数据:%s,Err:%s", abstractItem.Tags, tmpErr.Error()))
+			} else {
+				for _, tagId := range tagIdList {
+					tagIdMap[tagId] = true
+				}
+			}
+			if abstractItem.TagsName != `` {
+				tagNameList = strings.Split(abstractItem.TagsName, ",")
+			}
+		}
+		for _, tagName := range industryTags {
+			tagId, tmpErr := GetTagIdByName(tagName)
+			if tmpErr != nil {
+				utils.FileLog.Info(fmt.Sprintf("获取标签ID失败,标签名称:%s,Err:%s", tagName, tmpErr.Error()))
+			}
+			if _, ok := tagIdMap[tagId]; !ok {
+				tagIdList = append(tagIdList, tagId)
+				tagNameList = append(tagNameList, tagName)
+				tagIdMap[tagId] = true
+			}
+		}
+		//for _, tagName := range varietyTags {
+		//	tagId, tmpErr := GetTagIdByName(tagName)
+		//	if tmpErr != nil {
+		//		utils.FileLog.Info(fmt.Sprintf("获取标签ID失败,标签名称:%s,Err:%s", tagName, tmpErr.Error()))
+		//	}
+		//	if _, ok := tagIdMap[tagId]; !ok {
+		//		tagIdList = append(tagIdList, tagId)
+		//		tagIdMap[tagId] = true
+		//	}
+		//}
+
+		tagIdJsonByte, tmpErr := json.Marshal(tagIdList)
+		if tmpErr != nil {
+			utils.FileLog.Info(fmt.Sprintf("标签ID序列化失败,Err:%s", tmpErr.Error()))
+		} else {
+			tagIdJsonStr = string(tagIdJsonByte)
+		}
+
+		tagNameJsonStr = strings.Join(tagNameList, `,`)
+	}
+
+	if abstractItem == nil || abstractItem.RagEtaReportAbstractId <= 0 {
+		abstractItem = &rag.RagEtaReportAbstract{
+			RagEtaReportAbstractId: 0,
+			RagEtaReportId:         item.RagEtaReportId,
+			Content:                abstract,
+			QuestionId:             question.QuestionId,
+			QuestionContent:        question.QuestionContent,
+			Version:                1,
+			Tags:                   tagIdJsonStr,
+			TagsName:               tagNameJsonStr,
+			VectorKey:              "",
+			ModifyTime:             time.Now(),
+			CreateTime:             time.Now(),
+		}
+		err = abstractItem.Create()
+	} else {
+		// 添加历史记录
+		rag.AddArticleAbstractHistoryByRagEtaReportAbstract(abstractItem)
+
+		abstractItem.Content = abstract
+		abstractItem.Version++
+		abstractItem.ModifyTime = time.Now()
+		abstractItem.Tags = tagIdJsonStr
+		abstractItem.TagsName = tagNameJsonStr
+		abstractItem.QuestionContent = question.QuestionContent
+		err = abstractItem.Update([]string{"content", "version", "modify_time", "tags", "tags_name", "question_content"})
+	}
+
+	if err != nil {
+		return
+	}
+
+	// 数据入ES库
+	go AddOrEditEsRagEtaReportAbstract(abstractItem.RagEtaReportAbstractId)
+
+	ReportAbstractToKnowledge(item, abstractItem, false)
+
+	return
+}
+
+// AddOrEditEsRagEtaReportAbstract
+// @Description: 新增/编辑微信文章摘要入ES
+// @author: Roc
+// @datetime 2025-03-13 14:13:47
+// @param articleAbstractId int
+func AddOrEditEsRagEtaReportAbstract(ragEtaReportAbstractId int) {
+	if utils.EsRagEtaReportAbstractName == `` {
+		return
+	}
+
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("添加ETA报告微信信息到ES失败,err:%v", err)
+			fmt.Println("添加ETA报告微信信息到ES失败,err:", err)
+		}
+	}()
+	obj := rag.RagEtaReportAbstract{}
+	abstractInfo, err := obj.GetById(ragEtaReportAbstractId)
+	if err != nil {
+		err = fmt.Errorf("获取ETA报告文章信息失败,Err:" + err.Error())
+		return
+	}
+	ragEtaReportObj := rag.RagEtaReport{}
+	articleInfo, err := ragEtaReportObj.GetById(abstractInfo.RagEtaReportAbstractId)
+	if err != nil {
+		err = fmt.Errorf("获取ETA报告文章信息失败,Err:" + err.Error())
+		return
+	}
+
+	tagIdList := make([]int, 0)
+	if abstractInfo.Tags != `` {
+		err = json.Unmarshal([]byte(abstractInfo.Tags), &tagIdList)
+		if err != nil {
+			err = fmt.Errorf("报告标签ID转int失败,Err:" + err.Error())
+			utils.FileLog.Info(fmt.Sprintf("json.Unmarshal 报告标签ID转int失败,标签数据:%s,Err:%s", abstractInfo.Tags, err.Error()))
+		}
+	}
+
+	tagNameList := make([]string, 0)
+	if abstractInfo.TagsName != `` {
+		tagNameList = strings.Split(abstractInfo.TagsName, ",")
+	}
+
+	esItem := elastic.RagEtaReportAbstractItem{
+		RagEtaReportAbstractId: abstractInfo.RagEtaReportAbstractId,
+		RagEtaReportId:         abstractInfo.RagEtaReportId,
+		Abstract:               abstractInfo.Content,
+		QuestionId:             abstractInfo.QuestionId,
+		Version:                abstractInfo.Version,
+		VectorKey:              abstractInfo.VectorKey,
+		ModifyTime:             abstractInfo.ModifyTime,
+		CreateTime:             abstractInfo.CreateTime,
+		Title:                  articleInfo.Title,
+		TagIdList:              tagIdList,
+		TagNameList:            tagNameList,
+	}
+
+	err = elastic.RagEtaReportAbstractEsAddOrEdit(strconv.Itoa(abstractInfo.RagEtaReportAbstractId), esItem)
+}
+
+// DelEsRagEtaReportAbstract
+// @Description: 删除ES中的ETA报告
+// @author: Roc
+// @datetime 2025-04-21 11:08:09
+// @param articleAbstractId int
+func DelEsRagEtaReportAbstract(articleAbstractId int) {
+	if utils.EsRagEtaReportAbstractName == `` {
+		return
+	}
+
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("删除ES中的ETA报告失败,err:%v", err)
+			fmt.Println("删除ES中的ETA报告失败,err:", err)
+		}
+	}()
+
+	err = elastic.RagEtaReportAbstractEsDel(strconv.Itoa(articleAbstractId))
+}
+
+// WechatArticleAbstractToKnowledge
+// @Description: 摘要入向量库
+// @author: Roc
+// @datetime 2025-03-10 16:14:59
+// @param wechatArticleItem *rag.RagEtaReport
+// @param abstractItem *rag.RagEtaReportAbstract
+func ReportAbstractToKnowledge(ragEtaReport *rag.RagEtaReport, abstractItem *rag.RagEtaReportAbstract, 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 AddOrEditEsRagEtaReportAbstract(abstractItem.RagEtaReportAbstractId)
+	}()
+
+	// 生成临时文件
+	//dateDir := time.Now().Format("20060102")
+	//uploadDir :=  + "./static/ai/article/" + dateDir
+	uploadDir := "./static/ai/abstract"
+	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
+	if err != nil {
+		err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
+		return
+	}
+	fileName := utils.MD5(fmt.Sprintf("%d_%d", utils.AI_ARTICLE_SOURCE_ETA_REPORT, ragEtaReport.RagEtaReportId)) + `.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.PrivateKnowledgeBaseName]
+	// 上传临时文件到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"})
+
+}
+
+// DelRagReportLlmDoc
+// @Description: 删除ETA报告的摘要向量库
+// @author: Roc
+// @datetime 2025-04-23 13:24:51
+// @param vectorKeyList []string
+// @param abstractIdList []int
+// @return err error
+func DelRagReportLlmDoc(vectorKeyList []string, abstractIdList []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.PrivateKnowledgeBaseName], vectorKeyList)
+	obj := rag.RagEtaReportAbstract{}
+	err = obj.DelVectorKey(abstractIdList)
+
+	return
+}
+
+// DelRagEtaReportAbstract
+// @Description: 删除ETA报告摘要
+// @author: Roc
+// @datetime 2025-04-23 17:36:22
+// @param abstractIdList []int
+// @return err error
+func DelRagEtaReportAbstract(abstractIdList []int) (err error) {
+	obj := rag.RagEtaReportAbstract{}
+
+	list, err := obj.GetByIdList(abstractIdList)
+	if err != nil {
+		if !utils.IsErrNoRow(err) {
+			err = errors.New("删除向量库失败,Err:" + err.Error())
+		} else {
+			err = nil
+		}
+		return
+	}
+
+	err = delRagEtaReportAbstract(list)
+
+	return
+}
+
+// DelRagEtaReportAbstractByQuestionId
+// @Description: 根据提示词ID删除ETA报告摘要
+// @author: Roc
+// @datetime 2025-04-23 17:36:22
+// @param abstractIdList []int
+// @return err error
+func DelRagEtaReportAbstractByQuestionId(questionId int) (err error) {
+	obj := rag.RagEtaReportAbstract{}
+
+	list, err := obj.GetListByQuestionId(questionId)
+	if err != nil {
+		if !utils.IsErrNoRow(err) {
+			err = errors.New("删除向量库失败,Err:" + err.Error())
+		} else {
+			err = nil
+		}
+		return
+	}
+
+	err = delRagEtaReportAbstract(list)
+
+	return
+}
+
+// delRagEtaReportAbstract
+// @Description: 删除摘要
+// @author: Roc
+// @datetime 2025-04-24 15:19:19
+// @param list []*rag.RagEtaReportAbstract
+// @return err error
+func delRagEtaReportAbstract(list []*rag.RagEtaReportAbstract) (err error) {
+	obj := rag.RagEtaReportAbstract{}
+
+	vectorKeyList := make([]string, 0)
+	newAbstractIdList := make([]int, 0)
+
+	if len(list) > 0 {
+		for _, v := range list {
+			// 有加入到向量库,那么就加入到待删除的向量库list中
+			if v.VectorKey != `` {
+				vectorKeyList = append(vectorKeyList, v.VectorKey)
+			}
+			newAbstractIdList = append(newAbstractIdList, v.RagEtaReportAbstractId)
+		}
+	}
+	// 删除向量库
+	err = DelRagReportLlmDoc(vectorKeyList, newAbstractIdList)
+	if err != nil {
+		err = errors.New("删除向量库失败,Err:" + err.Error())
+		return
+	}
+
+	// 删除摘要
+	err = obj.DelByIdList(newAbstractIdList)
+	if err != nil {
+		err = errors.New("删除失败,Err:" + err.Error())
+		return
+	}
+
+	// 删除es数据
+	for _, wechatArticleAbstractId := range newAbstractIdList {
+		DelEsRagEtaReportAbstract(wechatArticleAbstractId)
+	}
+
+	return
+}
+
+// GetDelAbstractByQuestionIdCacheKey
+// @Description: 获取删除微信文章/ETA报告摘要的缓存key
+// @author: Roc
+// @datetime 2025-04-24 15:44:41
+// @param questionId int
+// @return string
+func GetDelAbstractByQuestionIdCacheKey(questionId int) string {
+	return fmt.Sprintf("%s%d", utils.CACHE_AI_ARTICLE_ABSTRACT_DEL, questionId)
+}
+
+// DelAbstractByQuestionId
+// @Description: 根据提示词ID删除微信文章/报告摘要
+// @author: Roc
+// @datetime 2025-04-24 15:37:28
+// @param questionId int
+func DelAbstractByQuestionId(questionId int) {
+	cacheKey := GetDelAbstractByQuestionIdCacheKey(questionId)
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Minute) {
+		utils.FileLog.Error("根据提示词删除摘要失败,提示词ID:%d,系统处理中,请稍后重试!", questionId)
+		return
+	}
+
+	defer func() {
+		utils.Rc.Delete(cacheKey)
+	}()
+
+	// 删除微信文章摘要
+	err := DelWechatArticleAbstractByQuestionId(questionId)
+	if err != nil {
+		utils.FileLog.Error("根据提示词摘要删除微信文章摘要失败,提示词ID:%d;原因:%s", questionId, err.Error())
+	}
+
+	// 删除ETA报告摘要
+	err = DelRagEtaReportAbstractByQuestionId(questionId)
+	if err != nil {
+		utils.FileLog.Error("根据提示词删除ETA报告摘要失败,提示词ID:%d;原因:%s", questionId, err.Error())
+	}
+
+	return
+}

+ 6 - 2
services/material/material.go

@@ -527,9 +527,13 @@ func GetBatchSelectedMaterialList(classifyId int, keyword string, isShowMe bool,
 	if keyword != "" {
 		switch lang {
 		case utils.LANG_EN:
-			condition += ` AND  ( material_name_en LIKE '%` + keyword + `%' )`
+			likeKey := `%` + keyword + `%`
+			condition += ` AND  ( material_name_en LIKE ? )`
+			pars = append(pars, likeKey)
 		default:
-			condition += ` AND  ( material_name LIKE '%` + keyword + `%' )`
+			likeKey := `%` + keyword + `%`
+			condition += ` AND  ( material_name LIKE ? )`
+			pars = append(pars, likeKey)
 		}
 	}
 

+ 3 - 0
services/report_chapter.go

@@ -166,6 +166,9 @@ func moveReportChapter(reportChapter, prevReportChapter, nextReportChapter *mode
 			err = fmt.Errorf("修改失败,Err:" + err.Error())
 			return
 		}
+		go func() {
+			_ = report.SortPage(reportChapter.ReportId, nil)
+		}()
 	}
 	return
 }

+ 2 - 1
services/report_rai.go

@@ -290,7 +290,8 @@ func handleInsertRaiReport(articleResult models.ArticleResultApidate) (err error
 			// 新增报告及章节
 			var reportId int64
 			allGrantUserList := make([]*report.ReportGrant, 0)
-			reportId, err = models.AddReportAndChapter(item, allGrantUserList, []models.AddReportChapter{})
+			reportFreeLayoutList := make([]*report.ReportFreeLayout, 0)
+			reportId, err = models.AddReportAndChapter(item, reportFreeLayoutList, allGrantUserList, []models.AddReportChapter{})
 			if err != nil {
 				err = fmt.Errorf("新增报告及章节失败, Err: " + err.Error())
 				return

+ 117 - 16
services/report_v2.go

@@ -66,6 +66,8 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 
 	errMsg = "生成报告失败"
 
+	reportFreeLayoutList := make([]*report.ReportFreeLayout, 0)
+
 	// 报告继承
 	if inheritReportId > 0 {
 		inheritReport, tmpErr := models.GetReportByReportId(inheritReportId)
@@ -95,6 +97,7 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 			reportInfo.HeadImg = ``
 			reportInfo.EndImg = ``
 			reportInfo.EndResourceId = inheritReport.EndResourceId
+			reportInfo.FreeLayoutConfig= inheritReport.FreeLayoutConfig
 			if inheritReport.HeadResourceId > 0 {
 				reportInfo.HeadImg = inheritReport.HeadImg
 			}
@@ -102,6 +105,25 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 				reportInfo.EndImg = inheritReport.EndImg
 			}
 			reportInfo.InheritReportId = inheritReport.Id
+			pages, tmpErr := report.GetReportFreeLayoutListByReportId(inheritReport.Id, 0)
+			if tmpErr != nil {
+				errMsg = "获取自由布局内容页失败"
+				err = tmpErr
+				return
+			}
+			for _, v := range pages {
+				reportFreeLayoutList = append(reportFreeLayoutList, &report.ReportFreeLayout{
+					Id:              0,
+					ReportId:        0,
+					ReportChapterId: 0,
+					Page:            v.Page,
+					IsChapter:       v.IsChapter,
+					Content:         v.Content,
+					ContentStruct:   v.ContentStruct,
+					CreateTime:      time.Now(),
+					ModifyTime:      time.Now(),
+				})
+			}
 		}
 	}
 
@@ -110,7 +132,7 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 
 	// 新增报告及章节
 	var reportId int64
-	reportId, err = models.AddReportAndChapter(reportInfo, allGrantUserList, addChapterList)
+	reportId, err = models.AddReportAndChapter(reportInfo, reportFreeLayoutList, allGrantUserList, addChapterList)
 	if err != nil {
 		err = errors.New("新增报告及章节失败, Err: " + err.Error())
 		return
@@ -353,6 +375,24 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 		return
 	}
 
+	// 每篇章节对应的分页内容页
+	chapterFreeLayoutListMap := make(map[int][]*report.ReportFreeLayout)
+	{
+		allReportFreeLayoutList, tmpErr := report.GetAllReportFreeLayoutListByReportId(inheritReportId)
+		if tmpErr != nil {
+			errMsg = "获取自由布局内容页失败"
+			err = tmpErr
+			return
+		}
+		for _, reportFreeLayout := range allReportFreeLayoutList {
+			chapterFreeLayoutList, ok := chapterFreeLayoutListMap[reportFreeLayout.ReportChapterId]
+			if !ok {
+				chapterFreeLayoutList = make([]*report.ReportFreeLayout, 0)
+			}
+			chapterFreeLayoutListMap[reportFreeLayout.ReportChapterId] = append(chapterFreeLayoutList, reportFreeLayout)
+		}
+	}
+
 	// 待添加的章节
 	chapterTypeList := make([]*models.ReportChapterType, 0)
 	// 待添加的章节类型id列表
@@ -496,20 +536,22 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 		// 继承的报告章节内容
 		for i := 0; i < len(inheritReportChapters); i++ {
 			customChapter := inheritReportChapters[i]
+			// 继承的报告章节ID
+			inheritReportChapterId := customChapter.ReportChapterId
 
 			// 授权用户列表
-			tmpGrantList, ok := grantListMap[customChapter.ReportChapterId]
+			tmpGrantList, ok := grantListMap[inheritReportChapterId]
 			if !ok {
 				tmpGrantList = make([]*report.ReportChapterGrant, 0)
 			}
-			oldChapterIdGrantListMap[customChapter.ReportChapterId] = tmpGrantList
+			oldChapterIdGrantListMap[inheritReportChapterId] = tmpGrantList
 
 			// 关联品种列表
-			chapterPermissionList, ok := chapterPermissionListMap[customChapter.ReportChapterId]
+			chapterPermissionList, ok := chapterPermissionListMap[inheritReportChapterId]
 			if !ok {
 				chapterPermissionList = make([]*report.ReportChapterPermissionMapping, 0)
 			}
-			oldChapterPermissionListMap[customChapter.ReportChapterId] = chapterPermissionList
+			oldChapterPermissionListMap[inheritReportChapterId] = chapterPermissionList
 
 			// 判断该章节是否是系统章节,如果是的话,那就是需要额外创建的
 			if customChapter.TypeId > 0 {
@@ -531,9 +573,10 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 			customChapter.ContentModifyTime = time.Now()
 
 			customAddChapter := models.AddReportChapter{
-				ReportChapter:       customChapter,
-				GrantList:           tmpGrantList,
-				GrantPermissionList: chapterPermissionList,
+				ReportChapter:          customChapter,
+				GrantList:              tmpGrantList,
+				GrantPermissionList:    chapterPermissionList,
+				InheritReportChapterId: inheritReportChapterId,
 			}
 			customAddChapterList = append(customAddChapterList, customAddChapter)
 		}
@@ -542,6 +585,9 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 	// 最大排序
 	var maxSort int
 	for _, typeItem := range chapterTypeList {
+		// 继承的章节ID
+		inheritReportChapterId := 0
+
 		v, ok := inheritChapterMap[typeItem.ReportChapterTypeId]
 
 		// 章节授权用户
@@ -552,6 +598,8 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 		chapterItem := new(models.ReportChapter)
 
 		if ok && v != nil {
+			inheritReportChapterId = v.ReportChapterId
+
 			// 如果存在继承的章节,那么就从继承的章节内容中获取
 			chapterItem.AddType = 2
 			chapterItem.Title = v.Title
@@ -596,6 +644,8 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 					return
 				}
 				if chapterNewest != nil {
+					inheritReportChapterId = chapterNewest.ReportChapterId
+
 					chapterItem.AddType = 2
 					chapterItem.Title = chapterNewest.Title
 					chapterItem.ReportType = chapterNewest.ReportType
@@ -691,9 +741,10 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 		}
 
 		addChapter := models.AddReportChapter{
-			ReportChapter:       chapterItem,
-			GrantList:           tmpGrantList,
-			GrantPermissionList: tmpChapterPermissionList,
+			ReportChapter:          chapterItem,
+			GrantList:              tmpGrantList,
+			GrantPermissionList:    tmpChapterPermissionList,
+			InheritReportChapterId: inheritReportChapterId,
 		}
 
 		chapterList = append(chapterList, addChapter)
@@ -706,6 +757,27 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 		chapterList = append(chapterList, addChapterItem)
 	}
 
+	for k, chapterItem := range chapterList {
+		reportFreeLayoutList, ok := chapterFreeLayoutListMap[chapterItem.InheritReportChapterId]
+		if ok {
+			tmpReportFreeLayoutList := make([]*report.ReportFreeLayout, 0)
+			for _, v := range reportFreeLayoutList {
+				tmpReportFreeLayoutList = append(tmpReportFreeLayoutList, &report.ReportFreeLayout{
+					Id:              0,
+					ReportId:        0,
+					ReportChapterId: 0,
+					Page:            v.Page,
+					IsChapter:       v.IsChapter,
+					Content:         v.Content,
+					ContentStruct:   v.ContentStruct,
+					CreateTime:      time.Now(),
+					ModifyTime:      time.Now(),
+				})
+			}
+			chapterList[k].ReportChapterFreeLayoutList = tmpReportFreeLayoutList
+		}
+	}
+
 	//hasGrantUserMap := make(map[int]bool)
 	//for _, grantList := range typeGrantListMap {
 	//	for _, grant := range grantList {
@@ -766,7 +838,15 @@ func AddChapterBaseInfoAndPermission(reportInfo *models.Report, reportChapterInf
 			CreateTime:        time.Now(),
 		})
 	}
-
+	if reportInfo.ReportLayout == 3 {
+		//增加默认排序
+		maxSort, sortErr := reportChapterInfo.GetMaxSortByReportId(reportInfo.Id)
+		if sortErr != nil {
+			err = fmt.Errorf("获取报告章节最大排序失败, ReportId: %d, Err: %v", reportInfo.Id, sortErr)
+			return
+		}
+		reportChapterInfo.Sort = maxSort + 1
+	}
 	err = models.AddChapterBaseInfoAndPermission(reportChapterInfo, addChapterAdminList, addChapterPermissionList)
 
 	return
@@ -1196,10 +1276,28 @@ func PublishReport(reportId int, reportUrl string, sysUser *system.Admin) (tips
 	}
 
 	// 普通报告
-	if reportInfo.Content == "" {
-		errMsg = `报告内容为空,不可发布`
-		err = errors.New("报告内容为空,不需要生成,report_id:" + strconv.Itoa(reportId))
-		return
+	if reportInfo.ReportLayout != 3 {
+		if reportInfo.Content == "" {
+			errMsg = `报告内容为空,不可发布`
+			err = errors.New("报告内容为空,不需要生成,report_id:" + strconv.Itoa(reportId))
+			return
+		}
+	} else {
+		pages, pageErr := report.GetFreeLayoutChapterPagesByReportId(reportInfo.Id)
+		if pageErr != nil {
+			errMsg = "获取自由布局报告失败,不可发布"
+			err = errors.New("获取自由布局报告失败,不可发布,Err:" + pageErr.Error())
+			return
+		}
+		var content string
+		for _, page := range pages {
+			content += page.Content
+		}
+		if content == "" {
+			errMsg = "自由布局报告内容为空,不可设置定时发布"
+			err = errors.New("自由布局报告内容为空,不可设置定时发布,report_id:" + strconv.Itoa(reportInfo.Id))
+			return
+		}
 	}
 
 	// 根据审批开关及审批流判断当前报告状态
@@ -1645,6 +1743,9 @@ func GetGeneralPdfUrl(reportId int, reportCode, classifyFirstName string, report
 	case 2:
 		// 智能布局
 		pdfUrl = fmt.Sprintf("%s/reportshare_smart_pdf?code=%s", reportUrl, reportCode)
+	case 3:
+		// 智能布局
+		pdfUrl = fmt.Sprintf("%s/reportshare_free_pdf?code=%s", reportUrl, reportCode)
 	}
 
 	if pdfUrl != "" {

+ 124 - 106
services/smart_report.go

@@ -136,7 +136,7 @@ func SmartReportElasticUpsert(smartReportId int, state int) (err error) {
 	return
 }
 
-func ReportToPdf(width int, reportUrl, filePath string) (err error) {
+func ReportToPdf(width int, reportUrl, filePath string, top, bottom, left, right int) (err error) {
 	pyCode := `
 import asyncio
 from pyppeteer import launch
@@ -168,10 +168,10 @@ async def main():
         'path': "%s",
         'printBackground': True,
         'margin': {
-            'top': '20px',
-            'bottom': '20px',
-            'left': '20px',
-            'right': '20px'
+            'top': '%dpx',
+            'bottom': '%dpx',
+            'left': '%dpx',
+            'right': '%dpx'
         }
     })
     await browser.close()
@@ -187,7 +187,7 @@ finally:
     loop.close()
 `
 
-	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, width, reportUrl, width, filePath)
+	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, width, reportUrl, width+left+right, filePath, top, bottom, left, right)
 	utils.FileLog.Info("pdf pyCode: \n" + pyCode)
 	cmd := exec.Command(utils.CommandPython, "-c", pyCode)
 	output, e := cmd.CombinedOutput()
@@ -302,9 +302,14 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 	if reportUrl == `` {
 		return
 	}
+	var report *models.ReportDetail
 
 	// 先清空字段
 	if reportType == 1 {
+		report, err = models.GetReportById(reportId)
+		if err != nil {
+			return
+		}
 		err = models.UpdatePdfUrlReportById(reportId)
 		if err != nil {
 			utils.FileLog.Info("清空pdf长图字段失败, Err: \n" + err.Error())
@@ -332,10 +337,16 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 		jpegPath := `./static/` + reportCode + "_1200.jpg"
 
 		width := 1200
+		top, bottom, left, right := 20, 20, 20, 20
+		if reportType == 1 {
+			if report != nil && report.ReportLayout == 3 {
+				top, bottom, left, right = 0, 0, 0, 0
+			}
+		}
 		//if reportType == 3 {
 		//	width = 800
 		//}
-		err = ReportToPdf(width, reportUrl, pdfPath)
+		err = ReportToPdf(width, reportUrl, pdfPath, top, bottom, left, right)
 		if err != nil {
 			utils.FileLog.Info("ReportToPdf failed: , error: \n" + err.Error())
 			go alarm_msg.SendAlarmMsg("ReportToPdf failed:"+err.Error(), 3)
@@ -449,125 +460,132 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 			}
 		}
 	}()
-
-	// 移动端
-	go func() {
-		pdfPathMobile := `./static/` + reportCode + "_600.pdf"
-		jpegPathMobile := `./static/` + reportCode + "_600.jpg"
-
-		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)
+	var mobilePdf = true
+	if reportType == 1 && report != nil {
+		if report.ReportLayout == 3 {
+			mobilePdf = false
 		}
+	}
+	if mobilePdf {
+		// 移动端
+		go func() {
+			pdfPathMobile := `./static/` + reportCode + "_600.pdf"
+			jpegPathMobile := `./static/` + reportCode + "_600.jpg"
+			top, bottom, left, right := 20, 20, 20, 20
+			width := 600
+			err = ReportToPdf(width, reportUrl, pdfPathMobile, top, bottom, left, right)
+			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(pdfPathMobile)
-		if err != nil {
-			utils.FileLog.Info("Open failed: , error: \n" + err.Error())
-			go alarm_msg.SendAlarmMsg("Open failed:"+err.Error(), 3)
-			return
-		}
+			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())
+			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, pdfPathMobile, "")
-		if err != nil {
-			utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
-			go alarm_msg.SendAlarmMsg("文件上传失败:"+err.Error(), 3)
-			return
-		}
-		defer func() {
-			_ = os.Remove(pdfPathMobile)
-		}()
+			randStr := utils.GetRandStringNoSpecialChar(28)
+			fileName := randStr + ext
+			defer file.Close() //关闭上传文件
 
-		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())
+			resourceUrl := ``
+			ossClient := NewOssClient()
+			if ossClient == nil {
+				utils.FileLog.Info("初始化OSS服务失败")
 				return
 			}
-		} else if reportType == 2 {
-			err = models.ModifyEnglishReportPdfUrlMobile(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.ModifyReportPdfUrlMobile(reportId, resourceUrl)
-			if err != nil {
-				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
-				return
+			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("open file failed: , error: \n" + err.Error())
-			return
-		}
+			time.Sleep(1 * time.Minute)
 
-		ext = path.Ext(file.Name())
+			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("open file failed: , error: \n" + err.Error())
+				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, jpegPathMobile, "")
-		if err != nil {
-			utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
-			return
-		}
-		defer func() {
-			_ = os.Remove(jpegPathMobile)
-		}()
+			randStr = utils.GetRandStringNoSpecialChar(28)
+			fileName = randStr + ext
+			defer file.Close() //关闭上传文件
 
-		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())
+			resourceUrl = ``
+			ossClient = NewOssClient()
+			if ossClient == nil {
+				utils.FileLog.Info("初始化OSS服务失败")
 				return
 			}
-		} else if reportType == 2 {
-			err = models.ModifyEnglishReportImgUrlMobile(reportId, resourceUrl)
+			resourceUrl, err = ossClient.UploadFile(fileName, jpegPathMobile, "")
 			if err != nil {
-				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				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
+			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
+				}
 			}
-		}
-	}()
+		}()
+	}
 }

+ 291 - 24
services/task.go

@@ -75,8 +75,14 @@ func Task() {
 	go HandleWechatArticleLLmOp()
 
 	// 队列任务将eta报告同步到知识库操作
+	go HandleEtaReportUpdateOp()
+
+	// 定时任务进行eta报告进行LLM操作
 	go HandleEtaReportKnowledgeLLmOp()
 
+	// 定时任务进行进行AI报告/文章的摘要任务LLM操作
+	go HandleAiArticleAbstractLlmOp()
+
 	// 权益报告监听入库
 	go AutoInsertRaiReport()
 
@@ -589,8 +595,8 @@ func ModifyEsEnglishReport() {
 //	return rnd.Float64()*11000 - 1000
 //}
 
-// HandleSearchByWechatOp
-// @Description: 处理微信爬虫
+// HandleWechatArticleOp
+// @Description: 处理ETA报告加入到知识库
 func HandleWechatArticleOp() {
 	defer func() {
 		if err := recover(); err != nil {
@@ -600,7 +606,7 @@ func HandleWechatArticleOp() {
 	obj := rag.WechatPlatform{}
 	for {
 		utils.Rc.Brpop(utils.CACHE_WECHAT_PLATFORM_ARTICLE, func(b []byte) {
-			wechatArticleOp := new(cache.WechatArticleOp)
+			wechatArticleOp := new(cache.WechatPlatformOp)
 			if err := json.Unmarshal(b, &wechatArticleOp); err != nil {
 				fmt.Println("json unmarshal wrong!")
 				return
@@ -630,39 +636,66 @@ func HandleWechatArticleLLmOp() {
 			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
-			}
+		utils.Rc.Brpop(utils.CACHE_WECHAT_PLATFORM_ARTICLE_KNOWLEDGE, handleWechatArticleLLmOp)
+	}
+}
 
-			// 文章加入到知识库
-			ArticleToKnowledge(item)
-			// 生成摘要
-			//GenerateArticleAbstract(item)
-		})
+// handleWechatArticleLLmOp
+// @Description: 处理微信文章加入知识库
+// @author: Roc
+// @datetime 2025-04-24 13:33:08
+// @param b []byte
+func handleWechatArticleLLmOp(b []byte) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("[handleWechatArticleLLmOp] params:%s;err:%s", string(b), err.Error())
+		}
+	}()
+	obj := rag.WechatArticle{}
+	wechatArticleOp := new(cache.WechatArticleOp)
+	if err = json.Unmarshal(b, &wechatArticleOp); err != nil {
+		fmt.Println("json unmarshal wrong!")
+		return
+	}
+	item, err := obj.GetById(wechatArticleOp.WechatArticleId)
+	if err != nil {
+		// 找不到就处理失败
+		return
+	}
+
+	// 文章加入到知识库
+	ArticleToKnowledge(item)
+
+	// 生成摘要
+	if wechatArticleOp.QuestionId <= 0 {
+		// 全部摘要生成
+		GenerateWechatArticleAbstract(item, false)
+	} else {
+		questionObj := rag.Question{}
+		questionInfo, tmpErr := questionObj.GetByID(wechatArticleOp.QuestionId)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		// 指定指定摘要生成
+		err = GenerateWechatArticleAbstractByQuestion(item, questionInfo, false)
 	}
 }
 
-// HandleEtaReportKnowledgeLLmOp
+// HandleEtaReportUpdateOp
 // @Description: 处理eta报告加入知识库操作
-func HandleEtaReportKnowledgeLLmOp() {
+func HandleEtaReportUpdateOp() {
 	defer func() {
 		if err := recover(); err != nil {
-			fmt.Println("[HandleEtaReportKnowledgeLLmOp]", err)
+			fmt.Println("[HandleEtaReportUpdateOp]", err)
 		}
 	}()
 	for {
 		utils.Rc.Brpop(utils.CACHE_ETA_REPORT_KNOWLEDGE, func(b []byte) {
-			ragEtaReportOpOp := new(cache.RagEtaReportOpOp)
+			ragEtaReportOpOp := new(cache.RagEtaReportOp)
 			if err := json.Unmarshal(b, &ragEtaReportOpOp); err != nil {
 				fmt.Println("json unmarshal wrong!")
 				return
@@ -677,3 +710,237 @@ func HandleEtaReportKnowledgeLLmOp() {
 		})
 	}
 }
+
+// HandleEtaReportKnowledgeLLmOp
+// @Description: 处理微信文章加入知识库
+func HandleEtaReportKnowledgeLLmOp() {
+	defer func() {
+		if err := recover(); err != nil {
+			fmt.Println("[HandleEtaReportKnowledgeLLmOp]", err)
+		}
+	}()
+
+	for {
+		utils.Rc.Brpop(utils.CACHE_ETA_REPORT_KNOWLEDGE_LLM, handleEtaReportKnowledgeLLmOp)
+	}
+}
+
+// handleEtaReportKnowledgeLLmOp
+// @Description: 处理微信文章加入知识库操作
+// @author: Roc
+// @datetime 2025-04-24 14:04:10
+// @param b []byte
+func handleEtaReportKnowledgeLLmOp(b []byte) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("[handleEtaReportKnowledgeLLmOp] params:%s;err:%s", string(b), err.Error())
+		}
+	}()
+
+	obj := rag.RagEtaReport{}
+	wechatArticleOp := new(cache.RagEtaReportLlmOp)
+	if err = json.Unmarshal(b, &wechatArticleOp); err != nil {
+		fmt.Println("json unmarshal wrong!")
+		return
+	}
+	item, err := obj.GetById(wechatArticleOp.RagEtaReportId)
+	if err != nil {
+		// 找不到就处理失败
+		return
+	}
+
+	// 已经删除的就不做操作了
+	if item.IsDeleted == 1 {
+		return
+	}
+
+	// 未发布的就不操作了
+	if item.IsPublished != 1 {
+		return
+	}
+
+	// 文章加入到知识库
+	//ArticleToKnowledge(item)
+
+	// 生成摘要
+
+	if wechatArticleOp.QuestionId <= 0 {
+		// 全部提示词摘要生成
+		GenerateRagEtaReportAbstract(item, wechatArticleOp.ForceGenerate)
+	} else {
+		questionObj := rag.Question{}
+		questionInfo, tmpErr := questionObj.GetByID(wechatArticleOp.QuestionId)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+
+		// 全部提示词摘要生成
+		err = GenerateRagEtaReportAbstractByQuestion(item, questionInfo, wechatArticleOp.ForceGenerate)
+	}
+}
+
+// HandleAiArticleAbstractLlmOp
+// @Description: 处理AI库的报告摘要生成(批量任务)
+// @author: Roc
+// @datetime 2025-04-24 10:25:51
+func HandleAiArticleAbstractLlmOp() {
+	defer func() {
+		if err := recover(); err != nil {
+			fmt.Println("[HandleAiArticleAbstractLlmOp]", err)
+		}
+	}()
+	for {
+		utils.Rc.Brpop(utils.CACHE_AI_ARTICLE_ABSTRACT_LLM_TASK, handleAiArticleAbstractLlmOp)
+	}
+}
+
+var aiTaskHandleIdMap = map[int]bool{}
+
+// todo 任务开始时间
+
+// handleAiArticleAbstractLlmOp
+// @Description: 处理AI库的报告摘要生成(批量任务)
+// @author: Roc
+// @datetime 2025-04-24 11:26:05
+// @param b []byte
+func handleAiArticleAbstractLlmOp(b []byte) {
+	var err error
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("[handleAiArticleAbstractLlmOp] params:%s;err:%s", string(b), err.Error())
+		}
+	}()
+	obj := rag.AiTaskRecord{}
+	aiTaskRecordOp := new(cache.AiTaskRecordOp)
+	if err = json.Unmarshal(b, &aiTaskRecordOp); err != nil {
+		fmt.Println("json unmarshal wrong!")
+		return
+	}
+	item, err := obj.GetByID(aiTaskRecordOp.AiTaskRecordId)
+	if err != nil {
+		err = fmt.Errorf("查找任务记录状态失败, err: %s", err.Error())
+		return
+	}
+
+	// 如果没有处理过该任务,那么就标记该任务开始
+	if _, ok := aiTaskHandleIdMap[item.AiTaskID]; !ok {
+		aiTaskObj := rag.AiTask{}
+		aiTaskInfo, tmpErr := aiTaskObj.GetByID(item.AiTaskID)
+		if tmpErr != nil {
+			err = fmt.Errorf("查找任务失败, err: %s", tmpErr.Error())
+			return
+		}
+		// 如果任务是初始化,那么就标记开始
+		if aiTaskInfo.Status == `init` {
+			aiTaskInfo.StartTime = time.Now()
+			aiTaskInfo.Status = `processing`
+			aiTaskInfo.UpdateTime = time.Now()
+			tmpErr = aiTaskInfo.Update([]string{`start_time`, "status", "update_time"})
+			if tmpErr != nil {
+				utils.FileLog.Error("标记任务开始状态失败, err: %s", tmpErr.Error())
+			}
+		}
+
+	}
+
+	// 处理完成后标记任务状态
+	defer func() {
+		// 修改任务状态
+		todoCount, tmpErr := obj.GetCountByCondition(fmt.Sprintf(` AND %s = ? AND %s = ? `, rag.AiTaskColumns.AiTaskID, rag.AiTaskColumns.Status), []interface{}{item.AiTaskID, `待处理`})
+		if tmpErr != nil {
+			err = fmt.Errorf("查找剩余任务数量失败, err: %s", tmpErr.Error())
+			return
+		}
+		if todoCount <= 0 {
+			aiTaskObj := rag.AiTask{}
+			aiTaskInfo, tmpErr := aiTaskObj.GetByID(item.AiTaskID)
+			if tmpErr != nil {
+				err = fmt.Errorf("查找任务失败, err: %s", tmpErr.Error())
+				return
+			}
+			aiTaskInfo.EndTime = time.Now()
+			aiTaskInfo.Status = `done`
+			aiTaskInfo.UpdateTime = time.Now()
+			tmpErr = aiTaskInfo.Update([]string{`end_time`, "status", "update_time"})
+			if tmpErr != nil {
+				utils.FileLog.Error("标记任务状态失败, err: %s", tmpErr.Error())
+			}
+		}
+
+		return
+	}()
+
+	// 不是待处理就不处理
+	if item.Status != `待处理` {
+		return
+	}
+
+	// 处理完成后标记记录状态
+	defer func() {
+		status := `处理成功`
+		remark := ``
+		if err != nil {
+			status = `处理失败`
+			remark = err.Error()
+		}
+		item.Status = status
+		item.Remark = remark
+		item.ModifyTime = time.Now()
+		tmpErr := item.Update([]string{"status", "remark", "modify_time"})
+		if tmpErr != nil {
+			utils.FileLog.Error("标记任务记录状态失败, err: %s", tmpErr.Error())
+		}
+	}()
+
+	var params rag.QuestionGenerateAbstractParam
+	if err = json.Unmarshal([]byte(item.Parameters), &params); err != nil {
+		fmt.Println("json unmarshal wrong!")
+		return
+	}
+
+	// 查找提示词
+	questionObj := rag.Question{}
+	questionInfo, tmpErr := questionObj.GetByID(params.QuestionId)
+	if tmpErr != nil {
+		// 找不到就处理失败
+		err = fmt.Errorf("查找提示词失败, err: %s", err.Error())
+		return
+	}
+
+	switch params.ArticleType {
+	case `wechat_article`:
+		articleObj := rag.WechatArticle{}
+		articleInfo, tmpErr := articleObj.GetById(params.ArticleId)
+		if tmpErr != nil {
+			err = tmpErr
+			// 找不到就处理失败
+			return
+		}
+		// 生成摘要
+		err = GenerateWechatArticleAbstractByQuestion(articleInfo, questionInfo, true)
+	case `rag_eta_report`:
+		articleObj := rag.RagEtaReport{}
+		articleInfo, tmpErr := articleObj.GetById(params.ArticleId)
+		if tmpErr != nil {
+			err = tmpErr
+			// 找不到就处理失败
+			return
+		}
+
+		// 已经删除的就不做操作了
+		if articleInfo.IsDeleted == 1 {
+			return
+		}
+
+		// 未发布的就不操作了
+		if articleInfo.IsPublished != 1 {
+			return
+		}
+
+		// 生成摘要
+		err = GenerateRagEtaReportAbstractByQuestion(articleInfo, questionInfo, true)
+
+	}
+}

+ 211 - 0
services/websocket_msg.go

@@ -0,0 +1,211 @@
+package services
+
+import (
+	"eta/eta_api/global"
+	"eta/eta_api/models"
+	"eta/eta_api/services/data"
+	"eta/eta_api/utils"
+	"fmt"
+	"runtime"
+	"sync"
+	"time"
+
+	"context"
+)
+
+func DealWebSocketMsg(adminId int) {
+	DealEdbInspectionMessageTest(adminId)
+
+	//go DealEdbInspectionMessage(adminId)
+}
+
+// 处理巡检消息
+func DealEdbInspectionMessage(adminId int) {
+	utils.FileLog.Info("创建协程, adminId:%d", adminId)
+	// 创建上下文用于控制 goroutine 生命周期
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	cacheKey := fmt.Sprintf("%s%d", utils.CACHE_EDB_INSPECTION_MESSAGE, adminId)
+
+	// 添加错误恢复机制
+	defer func() {
+		if r := recover(); r != nil {
+			utils.FileLog.Error("WebSocket handler recovered from panic: %v", r)
+			// 清理资源
+			cancel()
+		}
+	}()
+	go func() {
+		ticker := time.NewTicker(time.Minute)
+		defer ticker.Stop()
+		for {
+			select {
+			case <-ticker.C:
+				utils.FileLog.Info("Current goroutine count: %d", runtime.NumGoroutine())
+			case <-ctx.Done():
+				return
+			}
+		}
+	}()
+	for {
+		select {
+		case <-ctx.Done():
+			utils.FileLog.Info("DealEdbInspectionMessage 巡检消息处理协程结束, adminId:%d", adminId)
+			return
+		default:
+			// 检查连接状态
+			conn := global.MonitorMessageConn[adminId]
+			if conn == nil {
+				utils.FileLog.Error("检查连接状态 发送消息时发现连接已断开, adminId:%d", adminId)
+				cancel()
+				return
+			}
+			// 使用带超时的 Redis 操作
+			val := utils.Rc.Get(cacheKey)
+			if val == "" {
+				//utils.FileLog.Info("巡检信息历史为空, adminId:%d", adminId)
+				continue
+			}
+				utils.FileLog.Info("收到巡检信息开始处理, adminId:%d", adminId)
+				messageList, err := data.GetHistoryInspectionMessages(adminId)
+				if err != nil {
+					utils.FileLog.Error("获取巡检信息历史失败,err:%s, adminId:%d", err.Error(), adminId)
+					return
+				}
+				if len(messageList) == 0 {
+					utils.FileLog.Info("巡检信息历史为空, adminId:%d", adminId)
+					return
+				}
+
+				readList := make([]int64, 0)
+				// 检查连接状态
+				// conn := global.MonitorMessageConn[adminId]
+				// if conn == nil {
+				// 	utils.FileLog.Error("发送消息时发现连接已断开, adminId:%d", adminId)
+				// 	cancel()
+				// 	return
+				// }
+				// 只处理第一条消息的发送,其他消息只标记为已读
+				for i, msg := range messageList {
+					if i == 0 {
+						respData, err := data.SendInspectionMessages(adminId, msg)
+						if err != nil {
+							utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d", err.Error(), adminId)
+							continue
+						}
+
+						resp := models.WebsocketMessageResponse{
+							MessageType: 1,
+							Data:       respData,
+						}
+
+						err, isClose := WriteWebSocketMessageAsync(ctx, adminId, resp)
+						if err != nil {
+							utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d", err.Error(), adminId)
+							cancel()
+							continue
+						}
+						if isClose {
+							utils.FileLog.Error("巡检信息发送失败,连接已断开, adminId:%d", adminId)
+							cancel()
+							return
+						}
+
+						utils.FileLog.Info("巡检信息发送成功,adminId:%d, messageId:%d", adminId, msg.MessageId)
+					}
+					readList = append(readList, msg.MessageId)
+				}
+
+				if len(readList) > 0 {
+					_, err = data.ReadEdbInspectionMessageList(readList, adminId)
+					if err != nil {
+						utils.FileLog.Error("巡检信息已读失败,err:%s, adminId:%d", err.Error(), adminId)
+					}
+				}
+			//})
+
+			// if err != nil && err.Error() != "redis: nil" {
+			// 	utils.FileLog.Error("Redis operation failed: %v", err)
+			// 	continue
+			// }else {
+			// 	utils.FileLog.Info("巡检信息处理完成, adminId:%d", adminId)
+			// }
+		}
+	}
+}
+
+func WriteWebSocketMessageAsync(ctx context.Context, adminId int, resp interface{}) (error, bool) {
+	errChan := make(chan error, 1)
+	var wsWriteMutex sync.Mutex
+	isClose := false
+	
+	go func() {
+		wsWriteMutex.Lock()
+		defer wsWriteMutex.Unlock()
+		
+		conn := global.MonitorMessageConn[adminId]
+		if conn == nil {
+			isClose = true
+			errChan <- fmt.Errorf("connection closed for adminId: %d", adminId)
+			return
+		}
+		
+		// 设置写超时
+		//conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
+		errChan <- conn.WriteJSON(resp)
+	}()
+	
+	select {
+	case err := <-errChan:
+		utils.FileLog.Error("WriteWebSocketMessageAsync errChan: %v", err)
+		return err, isClose
+	case <-ctx.Done():
+		utils.FileLog.Error("WriteWebSocketMessageAsync ctx.Done(): %v", ctx.Err())
+		return ctx.Err(), isClose
+	}
+}
+
+func DealEdbInspectionMessageTest(adminId int) {
+	messageList, err := data.GetHistoryInspectionMessages(adminId)
+	if err != nil {
+		utils.FileLog.Error("获取巡检信息历史失败,err:%s, adminId:%d", err.Error(), adminId)
+	}
+	if len(messageList) == 0 {
+		return
+	}
+	go func() {
+		readList := make([]int64, 0)
+		for _, msg := range messageList {
+				// 多条消息仅发送最新一条
+				respData, err := data.SendInspectionMessages(adminId, msg)
+				if err != nil {
+					utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d", err.Error(), adminId)
+					return
+				} else {
+					resp := models.WebsocketMessageResponse{
+						MessageType: 1,
+						Data: respData,
+					}
+					conn := global.MonitorMessageConn[adminId]
+					if conn == nil {
+						utils.FileLog.Error("巡检信息发送失败,连接已断开, adminId:%d", adminId)
+						return
+					}
+					err = conn.WriteJSON(resp)
+					if err != nil {
+						utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d", err.Error(), adminId)
+						return
+					} else {
+						utils.FileLog.Info("巡检信息发送成功,adminId:%d, messageId:%d", adminId, msg.MessageId)
+					}
+				}
+				readList = append(readList, msg.MessageId)
+			}
+			_, err = data.ReadEdbInspectionMessageList(readList, adminId)
+			if err != nil {
+				utils.FileLog.Error("巡检信息已读失败,err:%s, adminId:%d", err.Error(), adminId)
+				return
+			}
+		}()
+}

+ 506 - 173
services/wechat_platform.go

@@ -2,11 +2,14 @@ package services
 
 import (
 	"bytes"
+	"encoding/json"
+	"errors"
 	"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/services/llm/facade"
 	"eta/eta_api/utils"
 	"eta/eta_api/utils/llm/eta_llm/eta_llm_http"
 	"fmt"
@@ -14,6 +17,7 @@ import (
 	"html"
 	"os"
 	"path"
+	"regexp"
 	"strconv"
 	"strings"
 	"time"
@@ -176,7 +180,7 @@ func AddWechatArticle(item *rag.WechatPlatform, articleLink string, articleDetai
 	go replaceWechatArticleCoverPic(obj)
 
 	// 文章入库成功后,需要将相关信息入摘要库
-	go cache.AddWechatArticleLlmOpToCache(obj.WechatArticleId, ``)
+	go cache.AddWechatArticleLlmOpToCache(obj.WechatArticleId, 0, ``)
 
 }
 
@@ -238,12 +242,93 @@ func BeachAddWechatArticle(item *rag.WechatPlatform, num int) {
 	return
 }
 
+//
+//// GenerateArticleAbstract
+//// @Description: 文章摘要生成
+//// @author: Roc
+//// @datetime 2025-03-10 16:17:53
+//// @param item *rag.WechatArticle
+//func GenerateArticleAbstract(item *rag.WechatArticle, forceGenerate bool) {
+//	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 && !forceGenerate {
+//		// 摘要已经生成,不需要重复生成,只需要重新加入到向量库中
+//		WechatArticleAbstractToKnowledge(item, tmpAbstractItem, false)
+//
+//		return
+//	}
+//	if !utils.IsErrNoRow(err) {
+//		return
+//	}
+//
+//	//开始对话
+//	abstract, addArticleChatRecordList, tmpErr := getAnswerByContent(item.WechatArticleId, utils.AI_ARTICLE_SOURCE_ETA_REPORT)
+//	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)
+//
+//		WechatArticleAbstractToKnowledge(item, abstractItem, false)
+//	}
+//}
+
 // GenerateArticleAbstract
-// @Description: 文章摘要生成
+// @Description: 文章摘要生成(默认提示词批量生成)
 // @author: Roc
 // @datetime 2025-03-10 16:17:53
 // @param item *rag.WechatArticle
-func GenerateArticleAbstract(item *rag.WechatArticle) {
+func GenerateWechatArticleAbstract(item *rag.WechatArticle, forceGenerate bool) {
 	var err error
 	defer func() {
 		if err != nil {
@@ -257,203 +342,175 @@ func GenerateArticleAbstract(item *rag.WechatArticle) {
 		return
 	}
 
-	abstractObj := rag.WechatArticleAbstract{}
-	tmpAbstractItem, err := abstractObj.GetByWechatArticleId(item.WechatArticleId)
-	if err == nil {
-		// 摘要已经生成,不需要重复生成
-		AbstractToKnowledge(item, tmpAbstractItem, false)
-
-		return
-	}
-	if !utils.IsErrNoRow(err) {
+	questionObj := rag.Question{}
+	questionList, err := questionObj.GetListByCondition(``, ` AND is_default = 1 `, []interface{}{}, 0, 100)
+	if err != nil {
+		err = fmt.Errorf("获取问题列表失败,Err:" + err.Error())
 		return
 	}
 
-	// 生成临时文件
-	dateDir := time.Now().Format("20060102")
-	uploadDir := "./static/ai/" + dateDir
-	err = os.MkdirAll(uploadDir, utils.DIR_MOD)
-	if err != nil {
-		err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
+	// 没问题就不生成了
+	if len(questionList) <= 0 {
 		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
+
+	for _, question := range questionList {
+		GenerateWechatArticleAbstractByQuestion(item, question, forceGenerate)
 	}
+
+	return
+}
+
+// GenerateWechatArticleAbstractByQuestion
+// @Description: 文章摘要生成(根据提示词生成)
+// @author: Roc
+// @datetime 2025-04-24 11:23:27
+// @param item *rag.WechatArticle
+// @param question *rag.Question
+// @param forceGenerate bool
+// @return err error
+func GenerateWechatArticleAbstractByQuestion(item *rag.WechatArticle, question *rag.Question, forceGenerate bool) (err error) {
 	defer func() {
-		os.Remove(tmpFilePath)
+		if err != nil {
+			utils.FileLog.Error("文章摘要生成(根据提示词生成)失败,err:%v", err)
+		}
 	}()
 
-	// 上传临时文件到LLM
-	tmpFileResp, err := llm.UploadTempDocs(tmpFilePath)
-	if err != nil {
-		err = fmt.Errorf("上传临时文件到LLM失败,Err:" + err.Error())
+	// 内容为空,那就不需要生成摘要
+	if item.TextContent == `` {
 		return
 	}
 
-	if tmpFileResp.Data.Id == `` {
-		err = fmt.Errorf("上传临时文件到LLM失败,Err:上传失败")
+	abstractObj := rag.WechatArticleAbstract{}
+	abstractItem, err := abstractObj.GetByWechatArticleIdAndQuestionId(item.WechatArticleId, question.QuestionId)
+	// 如果找到了,同时不是强制生成,那么就直接处理到知识库中
+	if err == nil && !forceGenerate {
+		// 摘要已经生成,不需要重复生成,只需要重新加入到向量库中
+		WechatArticleAbstractToKnowledge(item, abstractItem, false)
+
 		return
 	}
-	tmpDocId := tmpFileResp.Data.Id
 
-	//tmpDocId := `c4d2ee902808408c8b8ed398b33be103` // 钢材
-	//tmpDocId := `2dde8afe62d24525a814e74e0a5e35e4` // 钢材
-	//tmpDocId := `7634cc1086c04b3687682220a2cf1a48` //
+	// 如果是没找到数据,那么就将报错置空
+	if err != nil && utils.IsErrNoRow(err) {
+		err = nil
+	}
 
+	//你现在是一名资深的期货行业分析师,请基于以下的问题进行汇总总结,如果不能正常总结出来,那么就只需要回复我:sorry
+	questionStr := fmt.Sprintf(`%s\n%s`, `你现在是一名资深的期货行业分析师,请基于以下的问题进行汇总总结,如果不能正常总结出来,那么就只需要回复我:sorry。以下是问题:`, question.QuestionContent)
 	//开始对话
-	abstract, addArticleChatRecordList, tmpErr := getAnswerByContent(item.WechatArticleId, tmpDocId)
+	abstract, industryTags, tmpErr := getAnswerByContent(item.WechatArticleId, utils.AI_ARTICLE_SOURCE_WECHAT, questionStr)
 	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 == `` {
+		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
+	if abstract == `sorry` || strings.Index(abstract, `根据已知信息无法回答该问题`) == 0 {
+		item.AbstractStatus = 2
 		item.ModifyTime = time.Now()
 		err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+		return
+	}
+
+	var tagIdJsonStr string
+	var tagNameJsonStr string
+	// 标签ID
+	{
+		tagIdList := make([]int, 0)
+		tagNameList := make([]string, 0)
+		tagIdMap := make(map[int]bool)
+
+		if abstractItem != nil && abstractItem.Tags != `` {
+			tmpErr = json.Unmarshal([]byte(abstractItem.Tags), &tagIdList)
+			if tmpErr != nil {
+				utils.FileLog.Info(fmt.Sprintf("json.Unmarshal Tags 失败,标签数据:%s,Err:%s", abstractItem.Tags, tmpErr.Error()))
+			} else {
+				for _, tagId := range tagIdList {
+					tagIdMap[tagId] = true
+				}
+			}
+		}
+		if abstractItem.TagsName != `` {
+			tagNameList = strings.Split(abstractItem.TagsName, ",")
+		}
+		for _, tagName := range industryTags {
+			tagId, tmpErr := GetTagIdByName(tagName)
+			if tmpErr != nil {
+				utils.FileLog.Info(fmt.Sprintf("获取标签ID失败,标签名称:%s,Err:%s", tagName, tmpErr.Error()))
+			}
+			if _, ok := tagIdMap[tagId]; !ok {
+				tagIdList = append(tagIdList, tagId)
+				tagNameList = append(tagNameList, tagName)
+				tagIdMap[tagId] = true
+			}
+		}
+		//for _, tagName := range varietyTags {
+		//	tagId, tmpErr := GetTagIdByName(tagName)
+		//	if tmpErr != nil {
+		//		utils.FileLog.Info(fmt.Sprintf("获取标签ID失败,标签名称:%s,Err:%s", tagName, tmpErr.Error()))
+		//	}
+		//	if _, ok := tagIdMap[tagId]; !ok {
+		//		tagIdList = append(tagIdList, tagId)
+		//		tagIdMap[tagId] = true
+		//	}
+		//}
+
+		tagIdJsonByte, tmpErr := json.Marshal(tagIdList)
+		if tmpErr != nil {
+			utils.FileLog.Info(fmt.Sprintf("标签ID序列化失败,Err:%s", tmpErr.Error()))
+		} else {
+			tagIdJsonStr = string(tagIdJsonByte)
+		}
+
+		tagNameJsonStr = strings.Join(tagNameList, `,`)
+	}
 
-		abstractItem := &rag.WechatArticleAbstract{
+	item.AbstractStatus = 1
+	item.ModifyTime = time.Now()
+	err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+
+	if abstractItem == nil || abstractItem.WechatArticleAbstractId <= 0 {
+		abstractItem = &rag.WechatArticleAbstract{
 			WechatArticleAbstractId: 0,
 			WechatArticleId:         item.WechatArticleId,
 			Content:                 abstract,
-			Version:                 0,
+			Version:                 1,
 			VectorKey:               "",
 			ModifyTime:              time.Now(),
 			CreateTime:              time.Now(),
+			QuestionId:              question.QuestionId,
+			Tags:                    tagIdJsonStr,
+			TagsName:                tagNameJsonStr,
+			QuestionContent:         question.QuestionContent,
 		}
 		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
-	}
+	} else {
+		// 添加历史记录
+		rag.AddArticleAbstractHistoryByWechatArticleAbstract(abstractItem)
 
-	// 生成临时文件
-	dateDir := time.Now().Format("20060102")
-	uploadDir := "./static/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
+		abstractItem.Content = abstract
+		abstractItem.Version++
+		abstractItem.ModifyTime = time.Now()
+		abstractItem.Tags = tagIdJsonStr
+		abstractItem.TagsName = tagNameJsonStr
+		abstractItem.QuestionContent = question.QuestionContent
+		err = abstractItem.Update([]string{"content", "version", "modify_time", "tags", "tags_name", "question_content"})
 	}
-	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"})
+	// 数据入ES库
+	go AddOrEditEsWechatArticleAbstract(abstractItem.WechatArticleAbstractId)
 
-		abstractItem.Content = abstract
-		abstractItem.Version = abstractObj.Version + 1
-		abstractItem.ModifyTime = time.Now()
-		err = abstractItem.Update([]string{"content", "version", "modify_time"})
-		if err != nil {
-			return
-		}
+	WechatArticleAbstractToKnowledge(item, abstractItem, false)
 
-		AbstractToKnowledge(item, abstractItem, true)
-	}
+	return
 }
 
 // DelDoc
@@ -529,7 +586,62 @@ func DelLlmDoc(vectorKeyList []string, wechatArticleAbstractIdList []int) (err e
 	return
 }
 
-func getAnswerByContent(wechatArticleId int, docId string) (answer string, addArticleChatRecordList []*rag.WechatArticleChatRecord, err error) {
+func getAnswerByContent(articleId int, source int, questionStr string) (answer string, tagNameList []string, err error) {
+	//addArticleChatRecordList = make([]*rag.WechatArticleChatRecord, 0)
+
+	result, err := facade.AIGCBaseOnPromote(facade.AIGC{
+		Promote:   questionStr,
+		Source:    source,
+		ArticleId: articleId,
+		LLMModel:  `deepseek-r1:32b`,
+	})
+	if err != nil {
+		return
+	}
+
+	// JSON字符串转字节
+	//answerByte, err := json.Marshal(result)
+	//if err != nil {
+	//	return
+	//}
+	//originalAnswer := string(answerByte)
+
+	// 提取 </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)
+
+	// 提取标签
+	tagNameList = extractLabels(answer)
+
+	//// 待入库的数据
+	//addArticleChatRecordList = append(addArticleChatRecordList, &rag.WechatArticleChatRecord{
+	//	WechatArticleChatRecordId: 0,
+	//	WechatArticleId:           articleId,
+	//	ChatUserType:              "user",
+	//	Content:                   questionStr,
+	//	SendTime:                  time.Now(),
+	//	CreatedTime:               time.Now(),
+	//	UpdateTime:                time.Now(),
+	//}, &rag.WechatArticleChatRecord{
+	//	WechatArticleChatRecordId: 0,
+	//	WechatArticleId:           articleId,
+	//	ChatUserType:              "assistant",
+	//	Content:                   originalAnswer,
+	//	SendTime:                  time.Now(),
+	//	CreatedTime:               time.Now(),
+	//	UpdateTime:                time.Now(),
+	//})
+
+	return
+}
+
+func getAnswerByContentBak(wechatArticleId int, docId string) (answer string, addArticleChatRecordList []*rag.WechatArticleChatRecord, err error) {
 	historyList := make([]eta_llm_http.HistoryContent, 0)
 	addArticleChatRecordList = make([]*rag.WechatArticleChatRecord, 0)
 
@@ -649,13 +761,13 @@ func ArticleToKnowledge(item *rag.WechatArticle) {
 
 }
 
-// AbstractToKnowledge
+// WechatArticleAbstractToKnowledge
 // @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) {
+func WechatArticleAbstractToKnowledge(wechatArticleItem *rag.WechatArticle, abstractItem *rag.WechatArticleAbstract, isReUpload bool) {
 	if abstractItem.Content == `` {
 		return
 	}
@@ -683,7 +795,7 @@ func AbstractToKnowledge(wechatArticleItem *rag.WechatArticle, abstractItem *rag
 		err = fmt.Errorf("存储目录创建失败,Err:" + err.Error())
 		return
 	}
-	fileName := utils.RemoveSpecialChars(wechatArticleItem.Title) + `.md`
+	fileName := utils.MD5(fmt.Sprintf("%d_%d", utils.AI_ARTICLE_SOURCE_WECHAT, wechatArticleItem.WechatArticleId)) + `.md`
 	tmpFilePath := uploadDir + "/" + fileName
 	err = utils.SaveToFile(abstractItem.Content, tmpFilePath)
 	if err != nil {
@@ -985,17 +1097,19 @@ func AddOrEditEsWechatArticleAbstract(articleAbstractId int) {
 		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
+	// 标签ID
+	tagIdList := make([]int, 0)
+	if abstractInfo.Tags != `` {
+		err = json.Unmarshal([]byte(abstractInfo.Tags), &tagIdList)
+		if err != nil {
+			err = fmt.Errorf("报告标签ID转int失败,Err:" + err.Error())
+			utils.FileLog.Info(fmt.Sprintf("json.Unmarshal 报告标签ID转int失败,标签数据:%s,Err:%s", abstractInfo.Tags, err.Error()))
+		}
 	}
 
-	tagIdList := make([]int, 0)
-	for _, v := range tagMappingList {
-		tagIdList = append(tagIdList, v.TagId)
+	tagNameList := make([]string, 0)
+	if abstractInfo.TagsName != `` {
+		tagNameList = strings.Split(abstractInfo.TagsName, ",")
 	}
 
 	esItem := elastic.WechatArticleAbstractItem{
@@ -1003,18 +1117,162 @@ func AddOrEditEsWechatArticleAbstract(articleAbstractId int) {
 		WechatArticleId:         abstractInfo.WechatArticleId,
 		WechatPlatformId:        articleInfo.WechatPlatformId,
 		Abstract:                abstractInfo.Content,
+		QuestionId:              abstractInfo.QuestionId,
 		Version:                 abstractInfo.Version,
 		VectorKey:               abstractInfo.VectorKey,
-		ModifyTime:              articleInfo.ModifyTime,
-		CreateTime:              articleInfo.CreateTime,
+		ModifyTime:              abstractInfo.ModifyTime,
+		CreateTime:              abstractInfo.CreateTime,
 		Title:                   articleInfo.Title,
 		Link:                    articleInfo.Link,
 		TagIdList:               tagIdList,
+		TagNameList:             tagNameList,
 	}
 
 	err = elastic.WechatArticleAbstractEsAddOrEdit(strconv.Itoa(articleAbstractId), esItem)
 }
 
+// DelWechatArticleAbstract
+// @Description: 删除微信文章摘要
+// @author: Roc
+// @datetime 2025-04-23 17:36:22
+// @param abstractIdList []int
+// @return err error
+func DelWechatArticleAbstract(abstractIdList []int) (err error) {
+	obj := rag.WechatArticleAbstract{}
+
+	list, err := obj.GetByIdList(abstractIdList)
+	if err != nil {
+		if !utils.IsErrNoRow(err) {
+			err = errors.New("删除向量库失败,Err:" + err.Error())
+		} else {
+			err = nil
+		}
+		return
+	}
+
+	err = delWechatArticleAbstract(list)
+
+	return
+}
+
+// DelWechatArticleAbstract
+// @Description: 删除微信文章摘要
+// @author: Roc
+// @datetime 2025-04-23 17:36:22
+// @param abstractIdList []int
+// @return err error
+func DelWechatArticleAbstractByQuestionId(questionId int) (err error) {
+	obj := rag.WechatArticleAbstract{}
+
+	list, err := obj.GetListByQuestionId(questionId)
+	if err != nil {
+		if !utils.IsErrNoRow(err) {
+			err = errors.New("删除向量库失败,Err:" + err.Error())
+		} else {
+			err = nil
+		}
+		return
+	}
+
+	err = delWechatArticleAbstract(list)
+
+	return
+}
+
+// delRagEtaReportAbstract
+// @Description: 删除摘要
+// @author: Roc
+// @datetime 2025-04-24 15:19:19
+// @param list []*rag.RagEtaReportAbstract
+// @return err error
+func delWechatArticleAbstract(list []*rag.WechatArticleAbstract) (err error) {
+	obj := rag.RagEtaReportAbstract{}
+
+	vectorKeyList := make([]string, 0)
+	newAbstractIdList := make([]int, 0)
+
+	if len(list) > 0 {
+		for _, v := range list {
+			// 有加入到向量库,那么就加入到待删除的向量库list中
+			if v.VectorKey != `` {
+				vectorKeyList = append(vectorKeyList, v.VectorKey)
+			}
+			newAbstractIdList = append(newAbstractIdList, v.WechatArticleAbstractId)
+		}
+	}
+
+	//if !req.IsSelectAll {
+	//	list, err := obj.GetByIdList(req.RagEtaReportAbstractIdList)
+	//	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.RagEtaReportAbstractId)
+	//		}
+	//	}
+	//} else {
+	//	notIdMap := make(map[int]bool)
+	//	for _, v := range req.NotRagEtaReportAbstractIdList {
+	//		notIdMap[v] = true
+	//	}
+	//
+	//	_, list, err := getRagEtaReportAbstractList(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.RagEtaReportAbstractId] {
+	//				continue
+	//			}
+	//			// 有加入到向量库,那么就加入到待删除的向量库list中
+	//			if v.VectorKey != `` {
+	//				vectorKeyList = append(vectorKeyList, v.VectorKey)
+	//			}
+	//			wechatArticleAbstractIdList = append(wechatArticleAbstractIdList, v.RagEtaReportAbstractId)
+	//		}
+	//	}
+	//}
+
+	// 删除向量库
+	err = DelLlmDoc(vectorKeyList, newAbstractIdList)
+	if err != nil {
+		err = errors.New("删除向量库失败,Err:" + err.Error())
+		return
+	}
+
+	// 删除摘要
+	err = obj.DelByIdList(newAbstractIdList)
+	if err != nil {
+		err = errors.New("删除失败,Err:" + err.Error())
+		return
+	}
+
+	// 删除es数据
+	for _, wechatArticleAbstractId := range newAbstractIdList {
+		DelEsWechatArticleAbstract(wechatArticleAbstractId)
+	}
+
+	return
+}
+
 // DelEsWechatArticleAbstract
 // @Description: 删除ES中的微信文章摘要
 // @author: Roc
@@ -1028,8 +1286,8 @@ func DelEsWechatArticleAbstract(articleAbstractId int) {
 	var err error
 	defer func() {
 		if err != nil {
-			utils.FileLog.Error("添加公众号微信信息到ES失败,err:%v", err)
-			fmt.Println("添加公众号微信信息到ES失败,err:", err)
+			utils.FileLog.Error("删除公众号微信信息到ES失败,err:%v", err)
+			fmt.Println("删除公众号微信信息到ES失败,err:", err)
 		}
 	}()
 
@@ -1065,6 +1323,7 @@ func AddOrEditEsRagQuestion(questionId int) {
 		QuestionTitle:   questionInfo.QuestionTitle,
 		QuestionContent: questionInfo.QuestionContent,
 		Sort:            questionInfo.Sort,
+		IsDefault:       questionInfo.IsDefault,
 		SysUserId:       questionInfo.SysUserId,
 		SysUserRealName: questionInfo.SysUserRealName,
 		ModifyTime:      questionInfo.ModifyTime,
@@ -1094,3 +1353,77 @@ func DelEsRagQuestion(questionId int) {
 
 	err = elastic.RagQuestionEsDel(strconv.Itoa(questionId))
 }
+
+// extractLabels
+// @Description: 提取摘要中的标签并去重
+// @author: Roc
+// @datetime 2025-04-18 17:16:05
+// @param text string
+// @return industryTags []string
+// @return varietyTags []string
+func extractLabels(text string) (tags []string) {
+	reTag := regexp.MustCompile(`【([^】]*)】`)
+
+	// 提取所有标签
+	tagMatches := reTag.FindAllStringSubmatch(text, -1)
+	tagSet := make(map[string]bool)
+	for _, match := range tagMatches {
+		if len(match) > 1 {
+			tagSet[match[1]] = true
+		}
+	}
+
+	// 将去重后的标签转换为切片
+	for tag := range tagSet {
+		// 为空串就不处理
+		if tag == `` {
+			continue
+		}
+		tags = append(tags, tag)
+	}
+	return
+}
+
+var aiAbstractTagMap = map[string]int{}
+
+// GetTagIdByName
+// @Description: 获取标签ID
+// @author: Roc
+// @datetime 2025-04-18 17:25:46
+// @param tagName string
+// @return tagId int
+// @return err error
+func GetTagIdByName(tagName string) (tagId int, err error) {
+	tagName = strings.TrimSpace(tagName)
+	tagId, ok := aiAbstractTagMap[tagName]
+	if ok {
+		return
+	}
+
+	obj := rag.Tag{}
+	item, err := obj.GetByCondition(fmt.Sprintf(` AND  %s = ? `, rag.TagColumns.TagName), []interface{}{tagName})
+	if err != nil {
+		if !utils.IsErrNoRow(err) {
+			err = fmt.Errorf("获取标签失败,Err:" + err.Error())
+			return
+		}
+
+		item = &rag.Tag{
+			TagId:      0,
+			TagName:    tagName,
+			Sort:       0,
+			ModifyTime: time.Now(),
+			CreateTime: time.Now(),
+		}
+		err = item.Create()
+		if err != nil {
+			err = fmt.Errorf("添加标签失败,Err:" + err.Error())
+			return
+		}
+	}
+
+	tagId = item.TagId
+	aiAbstractTagMap[tagName] = tagId
+
+	return
+}

BIN
static/wind指标刷新失败处理.pdf


BIN
static/同花顺指标API方式刷新失败处理.pdf


BIN
static/钢联指标API对接刷新失败处理.pdf


BIN
static/钢联指标终端对接刷新失败处理.pdf


+ 2 - 1
utils/config.go

@@ -149,6 +149,7 @@ var (
 	EsWechatArticleName            string // ES索引名称-微信文章
 	EsWechatArticleAbstractName    string // ES索引名称-微信文章摘要
 	EsRagQuestionName              string // ES索引名称-知识库问题
+	EsRagEtaReportAbstractName     string // ES索引名称-ETA报告摘要
 )
 
 var (
@@ -303,7 +304,7 @@ var (
 )
 
 var (
-	RaiReportLibUrl string // 权益报告库地址
+	RaiReportLibUrl           string // 权益报告库地址
 	RaiReportLibAuthorization string // 权益报告库鉴权
 )
 

+ 15 - 1
utils/constants.go

@@ -270,10 +270,14 @@ const (
 	CACHE_EXCEL_REFRESH                     = "CACHE_EXCEL_REFRESH"                   // 表格刷新
 	CACHE_WECHAT_PLATFORM_ARTICLE           = "wechat_platform:article:op:"           //微信文章处理
 	CACHE_WECHAT_PLATFORM_ARTICLE_KNOWLEDGE = "wechat_platform:article:knowledge:op:" //微信文章入知识库处理
-	CACHE_ETA_REPORT_KNOWLEDGE              = "eta:report:knowledge:op:"              //eta报告入知识库处理
+	CACHE_ETA_REPORT_KNOWLEDGE              = "eta:report:knowledge:op:"              //eta报告入AI库处理
+	CACHE_ETA_REPORT_KNOWLEDGE_LLM          = "eta:report:knowledge:llm:op:"          //eta报告入知识库处理
+	CACHE_AI_ARTICLE_ABSTRACT_LLM_TASK      = "eta:ai:article:abstract:llm:task:op:"  //微信文章/eta报告的摘要重新生成处理(任务调度)
+	CACHE_AI_ARTICLE_ABSTRACT_DEL           = "eta:ai:article:abstract:del:op:"       //微信文章/eta报告的摘要删删除缓存,避免有人在删除的过程中,又将该提示词做摘要生成
 	CACHE_CHART_AUTH                        = "eta:chart:auth:"                       //图表数据授权
 	CACHE_REPORT_SHARE_AUTH                 = "eta:report:auth:share:"                //报告短链与报告图表授权映射key
 	CACHE_REPORT_AUTH                       = "eta:report:auth:"                      //报告图表数据授权
+	CACHE_EDB_INSPECTION_MESSAGE            = "eta:edb:inspection:message:"          //巡检消息队列
 )
 
 // 模板消息推送类型
@@ -588,6 +592,7 @@ const (
 const (
 	FICC_ARTICLE_UPDATE_KEY = "FICC_ARTICLE_UPDATE_KEY" //权益报告通知给FICC这边的缓存key
 )
+
 // 图表分类设置精选资源分类
 const (
 	ChartClassifyIsSelected            = 1 // 图表分类设置精选资源分类
@@ -602,3 +607,12 @@ const (
 const (
 	DATA_SOURCE_NAME_RADISH_RESEARCH = "萝卜投研" // 萝卜投研 -> 105
 )
+
+const (
+	AI_TASK_TYPE_GENERATE_ABSTRACT = `question_generate_abstract` // AI任务去批量生成摘要
+)
+
+const (
+	AI_ARTICLE_SOURCE_WECHAT     = 0 // AI文章来源(微信公众号)
+	AI_ARTICLE_SOURCE_ETA_REPORT = 1 // AI文章来源(ETA报告)
+)

+ 34 - 23
utils/llm/eta_llm/eta_llm_client.go

@@ -341,48 +341,59 @@ func parseResponse(response *http.Response) (baseResp eta_llm_http.BaseResponse,
 	baseResp.Data = bodyBytes
 	return
 }
-func ParseStreamResponse(response *http.Response) (contentChan chan string, errChan chan error, closeChan chan struct{}) {
+func ParseStreamResponse(response *http.Response) (contentChan chan string, errChan chan error, closeChan chan struct{}, closeLlmChan chan bool) {
 	contentChan = make(chan string, 10)
 	errChan = make(chan error, 10)
 	closeChan = make(chan struct{})
+	closeLlmChan = make(chan bool, 1)
 	go func() {
 		defer close(contentChan)
 		defer close(errChan)
 		defer close(closeChan)
+		defer close(closeLlmChan)
+
 		scanner := bufio.NewScanner(response.Body)
 		scanner.Split(bufio.ScanLines)
+
 		for scanner.Scan() {
-			line := scanner.Text()
-			if line == "" {
-				continue
-			}
-			// 忽略 "ping" 行
-			if strings.HasPrefix(line, ": ping") {
-				continue
-			}
-			// 去除 "data: " 前缀
-			if strings.HasPrefix(line, "data: ") {
-				line = strings.TrimPrefix(line, "data: ")
-			}
-			var chunk eta_llm_http.ChunkResponse
-			if err := json.Unmarshal([]byte(line), &chunk); err != nil {
-				fmt.Println("解析错误的line:" + line)
-				errChan <- fmt.Errorf("解析 JSON 块失败: %w", err)
+			select {
+			case <-closeLlmChan:
 				return
-			}
-			// 处理每个 chunk
-			if chunk.Choices != nil && len(chunk.Choices) > 0 {
-				for _, choice := range chunk.Choices {
-					if choice.Delta.Content != "" {
-						contentChan <- choice.Delta.Content
+			default:
+				line := scanner.Text()
+				if line == "" {
+					continue
+				}
+				// 忽略 "ping" 行
+				if strings.HasPrefix(line, ": ping") {
+					continue
+				}
+				// 去除 "data: " 前缀
+				if strings.HasPrefix(line, "data: ") {
+					line = strings.TrimPrefix(line, "data: ")
+				}
+				var chunk eta_llm_http.ChunkResponse
+				if err := json.Unmarshal([]byte(line), &chunk); err != nil {
+					fmt.Println("解析错误的line:" + line)
+					errChan <- fmt.Errorf("解析 JSON 块失败: %w", err)
+					return
+				}
+				// 处理每个 chunk
+				if chunk.Choices != nil && len(chunk.Choices) > 0 {
+					for _, choice := range chunk.Choices {
+						if choice.Delta.Content != "" {
+							contentChan <- choice.Delta.Content
+						}
 					}
 				}
 			}
+
 		}
 		if err := scanner.Err(); err != nil {
 			errChan <- fmt.Errorf("读取响应体失败: %w", err)
 			return
 		}
+
 	}()
 	return
 }

+ 2 - 0
utils/redis.go

@@ -19,6 +19,8 @@ type RedisClient interface {
 	IsExist(key string) bool
 	LPush(key string, val interface{}) error
 	Brpop(key string, callback func([]byte))
+	BrpopWithTimeout(key string, timeout time.Duration, callback func([]byte)) error
+	LLen(key string) (int64, error)
 	GetRedisTTL(key string) time.Duration
 	Incrby(key string, num int) (interface{}, error)
 	Do(commandName string, args ...interface{}) (reply interface{}, err error)

+ 31 - 0
utils/redis/cluster_redis.go

@@ -249,6 +249,37 @@ func (rc *ClusterRedisClient) Brpop(key string, callback func([]byte)) {
 
 }
 
+// BrpopWithTimeout
+// @Description: 从list中读取
+// @receiver rc
+// @param key
+// @param timeout
+// @param callback
+func (rc *ClusterRedisClient) BrpopWithTimeout(key string, timeout time.Duration, callback func([]byte)) (err error) {
+	values, err := rc.redisClient.BRPop(context.TODO(), timeout, key).Result()
+	if err != nil {
+		return
+	}
+	if len(values) < 2 {
+		err = errors.New("redis brpop timeout")
+		return
+	}
+
+	callback([]byte(values[1]))
+	return
+}
+// LLen
+// @Description: 获取list中剩余的数据数
+// @author: Roc
+// @receiver rc
+// @datetime 2025-04-25 10:58:25
+// @param key string
+// @return int64
+// @return error
+func (rc *ClusterRedisClient) LLen(key string) (int64, error) {
+	return rc.redisClient.LLen(context.TODO(), key).Result()
+}
+
 // GetRedisTTL
 // @Description: 获取key的过期时间
 // @receiver rc

+ 30 - 0
utils/redis/standalone_redis.go

@@ -237,6 +237,36 @@ func (rc *StandaloneRedisClient) Brpop(key string, callback func([]byte)) {
 
 }
 
+// BrpopWithTimeout
+// @Description: 从list中读取
+// @receiver rc
+// @param key
+// @param timeout
+// @param callback
+func (rc *StandaloneRedisClient) BrpopWithTimeout(key string, timeout time.Duration, callback func([]byte)) (err error) {
+	values, err := rc.redisClient.BRPop(context.TODO(), timeout, key).Result()
+	if err != nil {
+		return err
+	}
+	if len(values) < 2 {
+		err = errors.New("redis brpop timeout")
+		return
+	}
+	callback([]byte(values[1]))
+	return
+}
+// LLen
+// @Description: 获取list中剩余的数据数
+// @author: Roc
+// @receiver rc
+// @datetime 2025-04-25 10:58:25
+// @param key string
+// @return int64
+// @return error
+func (rc *StandaloneRedisClient) LLen(key string) (int64, error) {
+	return rc.redisClient.LLen(context.TODO(), key).Result()
+}
+
 // GetRedisTTL
 // @Description: 获取key的过期时间
 // @receiver rc

+ 24 - 19
utils/ws/session.go

@@ -12,22 +12,25 @@ import (
 
 // Session 会话结构
 type Session struct {
-	Id          string
-	UserId      int
-	Conn        *websocket.Conn
-	LastActive  time.Time
-	Latency     *LatencyMeasurer
-	History     []json.RawMessage
-	CloseChan   chan struct{}
-	MessageChan chan string
-	mu          sync.RWMutex
-	sessionOnce sync.Once
+	Id           string
+	UserId       int
+	Conn         *websocket.Conn
+	LastActive   time.Time
+	Latency      *LatencyMeasurer
+	History      []json.RawMessage
+	CloseChan    chan struct{}
+	MessageChan  chan string
+	mu           sync.RWMutex
+	sessionOnce  sync.Once
+	CloseLlmChan *chan bool
+	LLMStatus    int8 // llm提问状态,0:未提问,1:提问中,-1:暂停提问
 }
 
 type Message struct {
-	KbName string `json:"KbName"`
-	Query  string `json:"Query"`
-	ChatId int    `json:"ChatId"`
+	KbName      string `json:"KbName"`
+	Query       string `json:"Query"`
+	ChatId      int    `json:"ChatId"`
+	MessageType string `json:"MessageType"`
 	//LastTopics []json.RawMessage `json:"LastTopics"`
 }
 
@@ -48,13 +51,15 @@ func (s *Session) readPump() {
 		}
 		// 更新活跃时间
 		s.UpdateActivity()
+
 		// 处理消息
-		if err = manager.HandleMessage(s.UserId, s.Id, message); err != nil {
-			//写应答
-			_ = s.writeWithTimeout("<think></think>")
-			_ = s.writeWithTimeout(err.Error())
-			_ = s.writeWithTimeout("<EOF/>")
-		}
+		//if err = manager.HandleMessage(s.UserId, s.Id, message); err != nil {
+		//	//写应答
+		//	_ = s.writeWithTimeout("<think></think>")
+		//	_ = s.writeWithTimeout(err.Error())
+		//	_ = s.writeWithTimeout("<EOF/>")
+		//}
+		go manager.HandleMessage(s.UserId, s.Id, message)
 	}
 }
 

+ 60 - 23
utils/ws/session_manager.go

@@ -55,29 +55,53 @@ func Manager() *ConnectionManager {
 }
 
 // HandleMessage 消息处理核心逻辑
-func (manager *ConnectionManager) HandleMessage(userID int, sessionID string, message []byte) error {
-
+func (manager *ConnectionManager) HandleMessage(userID int, sessionID string, message []byte) {
+	var err error
 	session, exists := manager.GetSession(sessionID)
 	if !exists {
-		return errors.New("session not found")
+		err = errors.New("session not found")
+		return
 	}
+
 	if strings.ToLower(string(message)) == "pong" {
 		session.UpdateActivity()
 		fmt.Printf("收到心跳消息,续期长连接:%v", session.LastActive)
-		return nil
-	}
-	if !Allow(userID, QA_LIMITER) {
-		_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("<think></think>"))
-		_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("您提问的太频繁了,请稍后再试"))
-		_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("<EOF/>"))
-		return nil
+		return
 	}
+	defer func() {
+		if err != nil {
+			//写应答
+			_ = session.writeWithTimeout("<think></think>")
+			_ = session.writeWithTimeout(err.Error())
+			_ = session.writeWithTimeout("<EOF/>")
+		}
+	}()
 	var userMessage Message
-	err := json.Unmarshal(message, &userMessage)
+	err = json.Unmarshal(message, &userMessage)
 	if err != nil {
 		utils.FileLog.Error(fmt.Sprintf("消息格式错误:%s", string(message)))
 		fmt.Printf("消息格式错误:%s", string(message))
-		return errors.New("消息格式错误:" + err.Error())
+		err = errors.New("消息格式错误:" + err.Error())
+		return
+	}
+
+	if userMessage.MessageType == `stop` {
+		if session.LLMStatus == 1 {
+			// 标记llm提问状态:暂停提问
+			session.LLMStatus = -1
+		}
+		if session.CloseLlmChan != nil {
+			*session.CloseLlmChan <- true
+		}
+		return
+	}
+
+	// 限流
+	if !Allow(userID, QA_LIMITER) {
+		_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("<think></think>"))
+		_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("您提问的太频繁了,请稍后再试"))
+		_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("<EOF/>"))
+		return
 	}
 	// 处理业务逻辑
 	//session.History = append(session.History, userMessage.LastTopics...)
@@ -104,29 +128,37 @@ func (manager *ConnectionManager) HandleMessage(userID int, sessionID string, me
 	}()
 	if resp == nil {
 		utils.FileLog.Error("知识库问答失败: 无应答")
-		return errors.New("知识库问答失败: 无应答")
+		err = errors.New("知识库问答失败: 无应答")
+		return
 	}
 	if err != nil {
 		utils.FileLog.Error(fmt.Sprintf("知识库问答失败: httpCode:%d,错误信息:%s", resp.StatusCode, http.StatusText(resp.StatusCode)))
 		err = errors.New(fmt.Sprintf("知识库问答失败: httpCode:%d,错误信息:%s", resp.StatusCode, http.StatusText(resp.StatusCode)))
-		return err
+		return
 	}
 
 	if resp.StatusCode != http.StatusOK {
 		utils.FileLog.Error(fmt.Sprintf("知识库问答失败: httpCode:%d,错误信息:%s", resp.StatusCode, http.StatusText(resp.StatusCode)))
 		err = errors.New(fmt.Sprintf("知识库问答失败: httpCode:%d,错误信息:%s", resp.StatusCode, http.StatusText(resp.StatusCode)))
-		return err
+		return
 	}
+
 	// 解析流式响应
-	contentChan, errChan, closeChan := eta_llm.ParseStreamResponse(resp)
+	contentChan, errChan, closeChan, closeLlmChan := eta_llm.ParseStreamResponse(resp)
+	session.CloseLlmChan = &closeLlmChan
+	// 标记llm提问状态:提问中
+	session.LLMStatus = 1
 	emptyContent := true
 	// 处理流式数据并发送到 WebSocket
 	for {
 		select {
 		case content, ok := <-contentChan:
-			if !ok {
-				err = errors.New("未知的错误异常")
-				return err
+			if !ok && session.LLMStatus != -1 {
+				err = errors.New("未知的内容错误异常")
+
+				// 标记llm提问状态:未提问
+				session.LLMStatus = 0
+				return
 			}
 			session.UpdateActivity()
 			if emptyContent {
@@ -135,20 +167,25 @@ func (manager *ConnectionManager) HandleMessage(userID int, sessionID string, me
 			// 发送消息到 WebSocket
 			_ = session.Conn.WriteMessage(websocket.TextMessage, []byte(content))
 		case chanErr, ok := <-errChan:
-			if !ok {
+			if !ok && session.LLMStatus != -1 {
 				err = errors.New("未知的错误异常")
-			} else {
+			} else if chanErr != nil {
 				err = errors.New(chanErr.Error())
 			}
+			// 标记llm提问状态:未提问
+			session.LLMStatus = 0
 			// 发送错误消息到 WebSocket
-			return err
+			return
 		case <-closeChan:
 			if emptyContent {
 				_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("<think></think>"))
 				_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("暂时找不到答案"))
 			}
 			_ = session.Conn.WriteMessage(websocket.TextMessage, []byte("<EOF/>"))
-			return nil
+			// 标记llm提问状态:未提问
+			session.LLMStatus = 0
+
+			return
 		}
 	}
 	// 更新最后活跃时间