kobe6258 1 deň pred
rodič
commit
2bd8bf0a69
79 zmenil súbory, kde vykonal 7265 pridanie a 705 odobranie
  1. 4 0
      .gitignore
  2. 84 8
      cache/llm.go
  3. 2 0
      controllers/bi_dashboard.go
  4. 394 0
      controllers/data_manage/edb_inspection.go
  5. 96 0
      controllers/data_manage/edb_inspection_message.go
  6. 65 12
      controllers/data_stat/edb_source_stat.go
  7. 257 7
      controllers/data_stat/edb_terminal.go
  8. 13 8
      controllers/edb_monitor/edb_monitor_message.go
  9. 203 215
      controllers/llm/abstract.go
  10. 47 0
      controllers/llm/kb_controller.go
  11. 6 1
      controllers/llm/llm_http/response.go
  12. 349 25
      controllers/llm/question.go
  13. 416 0
      controllers/llm/rag_eta_report_abstract.go
  14. 85 1
      controllers/llm/report.go
  15. 0 2
      controllers/llm/user_chat_controller.go
  16. 84 0
      controllers/llm/wechat_platform.go
  17. 19 2
      controllers/message.go
  18. 4 5
      controllers/sys_role.go
  19. 3 0
      controllers/sys_user.go
  20. 12 62
      controllers/user_login.go
  21. 38 0
      global/websocket.go
  22. 20 0
      models/ai_predict_model/ai_predict_model_index.go
  23. 1 0
      models/bi_dashboard/bi_dashboard.go
  24. 1 0
      models/bi_dashboard/bi_dashboard_detail.go
  25. 5 0
      models/business_conf.go
  26. 176 0
      models/data_manage/edb_inspection/edb_inspection_config.go
  27. 112 0
      models/data_manage/edb_inspection/edb_inspection_dashboard.go
  28. 114 0
      models/data_manage/edb_inspection/edb_inspection_date_config.go
  29. 170 0
      models/data_manage/edb_inspection/edb_inspection_message.go
  30. 112 0
      models/data_manage/edb_inspection/edb_inspection_record.go
  31. 86 6
      models/data_manage/edb_terminal.go
  32. 23 0
      models/data_manage/mysteel_chemical_index.go
  33. 1 0
      models/data_stat/edb_info_update_stat.go
  34. 7 0
      models/message.go
  35. 164 0
      models/rag/ai_task.go
  36. 115 0
      models/rag/ai_task_record.go
  37. 114 0
      models/rag/article_abstract_history.go
  38. 19 4
      models/rag/question.go
  39. 86 0
      models/rag/question_history.go
  40. 5 5
      models/rag/rag_eta_report.go
  41. 292 0
      models/rag/rag_eta_report_abstract.go
  42. 9 0
      models/rag/request/rag_eta_report.go
  43. 1 1
      models/rag/request/wechat_platform.go
  44. 15 0
      models/rag/response/abstract.go
  45. 5 0
      models/rag/response/question.go
  46. 23 1
      models/rag/tag.go
  47. 82 12
      models/rag/wechat_article_abstract.go
  48. 1 2
      models/report.go
  49. 29 0
      models/report/report_free_layout.go
  50. 5 3
      models/report_chapter.go
  51. 25 1
      models/report_v2.go
  52. 216 0
      routers/commentsRouter.go
  53. 3 0
      routers/router.go
  54. 5 5
      services/ai_predict_model_classify.go
  55. 9 1
      services/ai_predict_model_index.go
  56. 397 0
      services/data/edb_inspection.go
  57. 114 0
      services/data/edb_inspection_message.go
  58. 35 39
      services/edb_monitor/edb_monitor_message.go
  59. 317 0
      services/elastic/rag_eta_report_abstract.go
  60. 16 2
      services/elastic/wechat_article_abstract.go
  61. 334 0
      services/llm.go
  62. 517 0
      services/llm_report.go
  63. 2 1
      services/report_rai.go
  64. 82 11
      services/report_v2.go
  65. 291 25
      services/task.go
  66. 220 0
      services/websocket_msg.go
  67. 509 171
      services/wechat_platform.go
  68. BIN
      static/wind指标刷新失败处理.pdf
  69. BIN
      static/同花顺指标API方式刷新失败处理.pdf
  70. BIN
      static/钢联指标API对接刷新失败处理.pdf
  71. BIN
      static/钢联指标终端对接刷新失败处理.pdf
  72. 2 1
      utils/config.go
  73. 14 1
      utils/constants.go
  74. 34 23
      utils/llm/eta_llm/eta_llm_client.go
  75. 2 0
      utils/redis.go
  76. 32 0
      utils/redis/cluster_redis.go
  77. 31 0
      utils/redis/standalone_redis.go
  78. 24 19
      utils/ws/session.go
  79. 60 23
      utils/ws/session_manager.go

+ 4 - 0
.gitignore

@@ -20,6 +20,10 @@ eta_api.exe~
 etalogs/
 /.vscode
 <<<<<<< HEAD
+<<<<<<< HEAD
 =======
 /fix
 >>>>>>> pool410_shenwan_pic
+=======
+/fix
+>>>>>>> c9b9316616e9224a40c5c1bcd0b84a94746004ef

+ 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(),

+ 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对接刷新失败处理.docx", "钢联指标API对接刷新失败处理.docx")
+		}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
+}

+ 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)

+ 257 - 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,206 @@ 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")
+
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	if source <= 0 {
+		br.Msg = "来源不能为空"
+		br.ErrMsg = "来源不能为空"
+		br.IsSendEmail = false
+		return
+	}
+	
+
+	condition := ``
+	var pars []interface{}
+
+	
+	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, currentIndex, 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.EdbInfoIds) <= 0 {
+		br.Msg = "指标ID不能为空"
+		br.ErrMsg = "指标ID不能为空"
+		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
+	}
+
+	var count int64
+	edbCodeList := make([]string, 0)
+	if req.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+		// 查询对应的指标编码
+		edbIds := make([]string, 0)
+		for _, v := range req.EdbInfoIds {
+			edbIds = append(edbIds, strconv.Itoa(v))
+		}
+		edbInfoList, err := data_manage.GetEdbInfoListByEdbInfoId(edbIds)
+		if err != nil {
+			br.Msg = "设置失败"
+			br.ErrMsg = "设置失败,Err:" + err.Error()
+			return
+		}
+		count = int64(len(edbInfoList))
+		for _, v := range edbInfoList {
+			edbCodeList = append(edbCodeList, v.EdbCode)
+		}
+	}else {
+		// 更新指标的终端编码
+		condition := " and e.edb_info_id in (?) and e.source = ?"
+		pars := []interface{}{req.EdbInfoIds, req.Source}
+		count, err := data_manage.GetSimpleEdbListCountByCondition(condition, pars)
+		if err != nil {
+			br.Msg = "设置失败"
+			br.ErrMsg = "设置失败,Err:" + err.Error()
+			return
+		}
+
+		if count <= 0 {
+			br.Msg = "指标ID不存在"
+			br.ErrMsg = "指标ID不存在"
+			return
+		}
+	}
+	
+	if len(req.EdbInfoIds) != int(count) {
+		br.Msg = "指标和数据源不匹配"
+		br.ErrMsg = "指标和数据源不匹配"
+		return
+	}
+
+	// 如果是钢联指标,则还需要变更数据源里的终端编码
+	if req.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+		// 更新数据源里的终端编码
+		err = data_manage.UpdateMysteelChemicalTerminalCode(edbCodeList, req.TerminalCode)
+		if err != nil {
+			br.Msg = "设置失败"
+			br.ErrMsg = "设置失败,Err:" + err.Error()
+			return
+		}
+	}else{
+		// 更新指标的终端编码
+		err = data_manage.UpdateEdbInfoTerminal(req.EdbInfoIds, 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()
 	}

+ 203 - 215
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,85 @@ func (c *AbstractController) List() {
 	br.Data = resp
 }
 
-func getAbstractList(keyWord string, tagId int, startSize, pageSize int) (total int, viewList []rag.WechatArticleAbstractView, err error) {
-	if keyWord == `` {
-		var condition string
-		var pars []interface{}
-		condition += fmt.Sprintf(` AND c.%s = ?`, rag.WechatPlatformColumns.Enabled)
-		pars = append(pars, 1)
-
-		if keyWord != "" {
-			condition += fmt.Sprintf(` AND a.%s like ?`, rag.WechatArticleAbstractColumns.Content)
-			pars = append(pars, `%`+keyWord+`%`)
-		}
-
-		if tagId > 0 {
-			condition += fmt.Sprintf(` AND d.%s = ?`, rag.WechatPlatformTagMappingColumns.TagID)
-			pars = append(pars, tagId)
-		}
-
-		obj := new(rag.WechatArticleAbstract)
-		tmpTotal, list, tmpErr := obj.GetPageListByTagAndPlatformCondition(condition, pars, startSize, pageSize)
-		if tmpErr != nil {
-			err = tmpErr
-			return
-		}
-		total = tmpTotal
-		viewList = obj.WechatArticleAbstractItem(list)
-	} else {
-		sortMap := map[string]string{
-			//"ModifyTime":              "desc",
-			//"WechatArticleAbstractId": "desc",
-		}
+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",
+	}
 
-		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 +206,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 +248,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 +361,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
+}

+ 349 - 25
controllers/llm/question.go

@@ -17,7 +17,7 @@ import (
 )
 
 // QuestionController
-// @Description: 问题库管理
+// @Description: 提示词库管理
 type QuestionController struct {
 	controllers.BaseAuthController
 }
@@ -170,16 +170,38 @@ func (c *QuestionController) TitleList() {
 			//"ArticleCreateTime": "desc",
 			//"WechatArticleId":   "desc",
 		}
-		tmpTotal, list, err := elastic.RagQuestionEsSearch(keyWord, startSize, pageSize, sortMap)
+		tmpTotal, esList, err := elastic.RagQuestionEsSearch(keyWord, 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 +219,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 +237,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 +258,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 +278,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 +315,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 +326,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 +335,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 +350,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 +391,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 +414,7 @@ func (c *QuestionController) Del() {
 		return
 	}
 	if req.QuestionId <= 0 {
-		br.Msg = "问题id不能为空"
+		br.Msg = "提示词id不能为空"
 		br.IsSendEmail = false
 		return
 	}
@@ -380,13 +423,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 +455,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)

+ 416 - 0
controllers/llm/rag_eta_report_abstract.go

@@ -0,0 +1,416 @@
+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",
+	}
+
+	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)

+ 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

+ 4 - 5
controllers/sys_role.go

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

+ 3 - 0
controllers/sys_user.go

@@ -407,6 +407,9 @@ func (this *SysUserController) SystemConfig() {
 	}, system.BusinessConf{
 		ConfKey: "LogoutUrl",
 		ConfVal: conf["LogoutUrl"],
+	}, system.BusinessConf{
+		ConfKey: "UserLogoutUrl",
+		ConfVal: conf["UserLogoutUrl"],
 	})
 
 	br.Data = list

+ 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
+}

+ 20 - 0
models/ai_predict_model/ai_predict_model_index.go

@@ -444,3 +444,23 @@ type AiPredictModelIndexExtraConfig struct {
 		PredictLegendName string `description:"预测图例的名称(通常为Predicted)"`
 	}
 }
+
+func (m *AiPredictModelIndex) GetSortMax() (sort int, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := `SELECT COALESCE(MAX(sort), 0) AS sort FROM ai_predict_model_index`
+	err = o.Raw(sql).Scan(&sort).Error
+	if err != nil {
+		return
+	}
+	// 查询分类的最大排序
+	sql = `SELECT COALESCE(MAX(sort), 0) AS sort FROM ai_predict_model_classify`
+	var classifySort int
+	err = o.Raw(sql).Scan(&classifySort).Error
+	if err != nil {
+		return
+	}
+	if classifySort > sort {
+		sort = classifySort
+	}
+	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

+ 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
+}
+
+
+

+ 86 - 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,67 @@ 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
+}
+
+type SetEdbInfoTerminalReq struct {
+	EdbInfoIds []int `description:"指标ID"`
+	TerminalCode string `description:"要更换的终端编码"`
+	Source int `description:"指标来源类型"`
+}
+
+
+func UpdateEdbInfoTerminal(edbInfoIds []int, terminalCode string, source int) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	err = o.Exec("UPDATE edb_info SET terminal_code = ? WHERE edb_info_id IN (?) AND source = ?", terminalCode, edbInfoIds, source).Error
+	return
+}
+
+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
+}

+ 23 - 0
models/data_manage/mysteel_chemical_index.go

@@ -793,3 +793,26 @@ func GetNoEdbMysteelChemicalIndexPageList(condition string, pars []interface{},
 	err = o.Raw(sql, pars...).Find(&items).Error
 	return
 }
+
+// 更新终端编码
+func UpdateMysteelChemicalTerminalCode(indexCodes []string, terminalCode string) (err error) {
+	// 通过事务更新
+	o := global.DbMap[utils.DbNameIndex].Begin()
+	defer func() {
+		if err != nil {
+			_ = o.Rollback()
+			return
+		}
+		_ = o.Commit()
+	}()
+	sql := ` UPDATE base_from_mysteel_chemical_index 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, utils.DATA_SOURCE_MYSTEEL_CHEMICAL, indexCodes).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
 }
 

+ 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 {

+ 1 - 2
models/report.go

@@ -1704,13 +1704,13 @@ type ReportShartUrlResp struct {
 	UrlToken string `description:"分享链接token"`
 }
 
-
 func GetAllPublishReportId() (items []int, err error) {
 	o := global.DbMap[utils.DbNameReport]
 	sql := `SELECT  a.id FROM report as a WHERE 1=1 AND state in (2,6) `
 	err = o.Raw(sql).Find(&items).Error
 	return
 }
+
 func InsertOrUpdateReportFreeLayoutContentPage(reportInfo *Report, ormList []*report.ReportFreeLayout) (err error) {
 	tx := global.DbMap[utils.DbNameReport].Begin()
 
@@ -1755,5 +1755,4 @@ func UpdateChapterFreeLayoutContentPage(reportInfo *Report, chapterInfo *ReportC
 		}
 	}
 	return report.BatchInsertOrUpdatePages(tx, ormList, true, reportInfo.Id, chapterInfo.ReportChapterId)
-
 }

+ 29 - 0
models/report/report_free_layout.go

@@ -232,3 +232,32 @@ func GetFreeLayoutPagesByReportId(id int) (list []*ContentPage, err error) {
 	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
+}

+ 5 - 3
models/report_chapter.go

@@ -510,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",
@@ -7558,6 +7657,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",
@@ -8737,6 +8854,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",
@@ -8746,6 +8872,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",
@@ -8782,6 +8926,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",
@@ -8791,6 +8944,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",
@@ -8872,6 +9070,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",
@@ -8890,6 +9097,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",

+ 5 - 5
services/ai_predict_model_classify.go

@@ -156,7 +156,7 @@ func moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, n
 	updateCol := make([]string, 0)
 
 	// 移动对象为分类, 判断分类是否存在
-	if edbClassifyInfo != nil {
+	if edbClassifyInfo != nil && edbClassifyInfo.AiPredictModelClassifyId > 0 {
 		oldParentId := edbClassifyInfo.ParentId
 		oldLevel := edbClassifyInfo.Level
 		var classifyIds []int
@@ -271,7 +271,7 @@ func moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, n
 			}
 
 			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
-			if firstClassify != nil && firstClassify.Sort == 0 {
+			if firstClassify != nil && firstClassify.AiPredictModelClassifyId > 0 && firstClassify.Sort == 0 {
 				updateSortStr := ` sort + 1 `
 				_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, firstClassify.AiPredictModelClassifyId-1, 0, updateSortStr)
 				//该分类下的所有指标也需要+1
@@ -286,7 +286,7 @@ func moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, n
 				}
 
 				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
-				if firstEdb != nil && firstEdb.Sort == 0 {
+				if firstEdb != nil && firstEdb.AiPredictModelIndexId > 0 && firstEdb.Sort == 0 {
 					updateSortStr := ` sort + 1 `
 					_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, 0, firstEdb.AiPredictModelIndexId-1, updateSortStr)
 					_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, 0, updateSortStr)
@@ -403,7 +403,7 @@ func moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, n
 			}
 
 			//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
-			if firstClassify != nil && firstClassify.Sort == 0 {
+			if firstClassify != nil && firstClassify.AiPredictModelClassifyId> 0 && firstClassify.Sort == 0 {
 				updateSortStr := ` sort + 1 `
 				_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, firstClassify.AiPredictModelClassifyId-1, 0, updateSortStr)
 				//该分类下的所有指标也需要+1
@@ -418,7 +418,7 @@ func moveAiPredictModelClassify(parentClassify, edbClassifyInfo, prevClassify, n
 				}
 
 				//如果该分类下存在其他分类,且第一个其他分类的排序等于0,那么需要调整排序
-				if firstEdb != nil && firstEdb.Sort == 0 {
+				if firstEdb != nil && firstEdb.AiPredictModelIndexId > 0 && firstEdb.Sort == 0 {
 					updateSortStr := ` sort + 1 `
 					_ = aiPredictModel.UpdateAiPredictModelIndexSortByClassifyId(parentClassifyId, 0, firstEdb.AiPredictModelIndexId-1, updateSortStr)
 					_ = aiPredictModel.UpdateAiPredictModelClassifySortByParentId(parentClassifyId, 0, 0, updateSortStr)

+ 9 - 1
services/ai_predict_model_index.go

@@ -34,6 +34,12 @@ func ImportAiPredictModelIndexAndData(imports []*aiPredictModel.AiPredictModelIm
 	updateCols := []string{indexOb.Cols().ClassifyId, indexOb.Cols().ModelFramework, indexOb.Cols().PredictDate, indexOb.Cols().PredictValue, indexOb.Cols().DirectionAccuracy, indexOb.Cols().AbsoluteDeviation, indexOb.Cols().ExtraConfig, indexOb.Cols().SysUserId, indexOb.Cols().SysUserRealName, indexOb.Cols().ModifyTime}
 	updateIndexes := make([]*aiPredictModel.AiPredictModelImportData, 0)
 	createIndexes := make([]*aiPredictModel.AiPredictModelImportData, 0)
+
+	maxSort, err := indexOb.GetSortMax()
+	if err != nil {
+		err = fmt.Errorf("获取标的最大排序失败, %v", err)
+		return
+	}
 	for _, v := range imports {
 		exist := indexNameItem[v.Index.IndexName]
 		// 编辑
@@ -53,7 +59,7 @@ func ImportAiPredictModelIndexAndData(imports []*aiPredictModel.AiPredictModelIm
 				b, _ := json.Marshal(oldConfig)
 				v.Index.ExtraConfig = string(b)
 			}
-
+			
 			v.Index.AiPredictModelIndexId = exist.AiPredictModelIndexId
 			v.Index.IndexCode = exist.IndexCode
 			updateIndexes = append(updateIndexes, v)
@@ -68,6 +74,8 @@ func ImportAiPredictModelIndexAndData(imports []*aiPredictModel.AiPredictModelIm
 		}
 		v.Index.IndexCode = indexCode
 		v.Charts = GetAiPredictCharts(v.Index.IndexName, adminId, adminRealName)
+		maxSort = maxSort + 1
+		v.Index.Sort = maxSort
 		createIndexes = append(createIndexes, v)
 	}
 

+ 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.InspectionFrequencyDay, "_", 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
+} 

+ 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
+}

+ 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
+}

+ 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, err := json.Marshal(tagIdList)
+		if err != 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 {
+		go 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
+}

+ 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

+ 82 - 11
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)
@@ -102,6 +104,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 +131,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 +374,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 +535,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 +572,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 +584,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 +597,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 +643,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 +740,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 +756,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 {

+ 291 - 25
services/task.go

@@ -70,15 +70,20 @@ func Task() {
 
 	// 定时任务进行微信文章操作
 	// 定时任务进行微信公众号添加、文章刷新操作(暂不处理了,改成其他服务处理)
-
 	//go HandleWechatArticleOp()
 
 	// 定时任务进行微信文章LLM操作
 	go HandleWechatArticleLLmOp()
 
 	// 队列任务将eta报告同步到知识库操作
+	go HandleEtaReportUpdateOp()
+
+	// 定时任务进行eta报告进行LLM操作
 	go HandleEtaReportKnowledgeLLmOp()
 
+	// 定时任务进行进行AI报告/文章的摘要任务LLM操作
+	go HandleAiArticleAbstractLlmOp()
+
 	// 权益报告监听入库
 	go AutoInsertRaiReport()
 
@@ -591,8 +596,8 @@ func ModifyEsEnglishReport() {
 //	return rnd.Float64()*11000 - 1000
 //}
 
-// HandleSearchByWechatOp
-// @Description: 处理微信爬虫
+// HandleWechatArticleOp
+// @Description: 处理ETA报告加入到知识库
 func HandleWechatArticleOp() {
 	defer func() {
 		if err := recover(); err != nil {
@@ -602,7 +607,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
@@ -632,39 +637,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
@@ -679,3 +711,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)
+
+	}
+}

+ 220 - 0
services/websocket_msg.go

@@ -0,0 +1,220 @@
+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)
+	}
+	success := make(chan int64, 10)
+	go func() {
+		defer close(success)
+		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)
+				} 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)
+						} else {
+							utils.FileLog.Info("巡检信息发送成功,adminId:%d, messageId:%d", adminId, msg.MessageId)
+							success <- msg.MessageId
+						}
+					}
+				} else {
+					success <- msg.MessageId
+				}
+			}
+	}()
+	go func() {
+		readList := make([]int64, 0)
+		for {
+			msgId, ok := <-success
+			if !ok {
+				break
+			}
+			readList = append(readList, msgId)
+		}
+		_, err = data.ReadEdbInspectionMessageList(readList, adminId)
+		if err != nil {
+			utils.FileLog.Error("巡检信息已读失败,err:%s, adminId:%d", err.Error(), adminId)
+		}
+	}()
+}

+ 509 - 171
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) {
-		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())
-		return
-	}
-	randStr := utils.GetRandStringNoSpecialChar(28)
-	fileName := randStr + `.md`
-	tmpFilePath := uploadDir + "/" + fileName
-	err = utils.SaveToFile(item.TextContent, tmpFilePath)
-	if err != nil {
-		err = fmt.Errorf("生成临时文件失败,Err:" + err.Error())
-		return
-	}
-	defer func() {
-		os.Remove(tmpFilePath)
-	}()
-
-	// 上传临时文件到LLM
-	tmpFileResp, err := llm.UploadTempDocs(tmpFilePath)
+	questionObj := rag.Question{}
+	questionList, err := questionObj.GetListByCondition(``, ` AND is_default = 1 `, []interface{}{}, 0, 100)
 	if err != nil {
-		err = fmt.Errorf("上传临时文件到LLM失败,Err:" + err.Error())
-		return
-	}
-
-	if tmpFileResp.Data.Id == `` {
-		err = fmt.Errorf("上传临时文件到LLM失败,Err:上传失败")
+		err = fmt.Errorf("获取问题列表失败,Err:" + err.Error())
 		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())
+	// 没问题就不生成了
+	if len(questionList) <= 0 {
 		return
 	}
 
-	// 添加问答记录
-	if len(addArticleChatRecordList) > 0 {
-		recordObj := rag.WechatArticleChatRecord{}
-		err = recordObj.CreateInBatches(addArticleChatRecordList)
-		if err != nil {
-			return
-		}
+	for _, question := range questionList {
+		GenerateWechatArticleAbstractByQuestion(item, question, forceGenerate)
 	}
 
-	if abstract != `` {
-		if abstract == `sorry` || strings.Index(abstract, `根据已知信息无法回答该问题`) == 0 {
-			item.AbstractStatus = 2
-			item.ModifyTime = time.Now()
-			err = item.Update([]string{"AbstractStatus", "ModifyTime"})
-			return
-		}
-		item.AbstractStatus = 1
-		item.ModifyTime = time.Now()
-		err = item.Update([]string{"AbstractStatus", "ModifyTime"})
-
-		abstractItem := &rag.WechatArticleAbstract{
-			WechatArticleAbstractId: 0,
-			WechatArticleId:         item.WechatArticleId,
-			Content:                 abstract,
-			Version:                 0,
-			VectorKey:               "",
-			ModifyTime:              time.Now(),
-			CreateTime:              time.Now(),
-		}
-		err = abstractItem.Create()
-		if err != nil {
-			return
-		}
-
-		// 数据入ES库
-		go AddOrEditEsWechatArticleAbstract(abstractItem.WechatArticleAbstractId)
-
-		AbstractToKnowledge(item, abstractItem, false)
-	}
+	return
 }
 
-// ReGenerateArticleAbstract
-// @Description: 文章摘要重新生成
+// GenerateWechatArticleAbstractByQuestion
+// @Description: 文章摘要生成(根据提示词生成)
 // @author: Roc
-// @datetime 2025-03-10 16:17:53
+// @datetime 2025-04-24 11:23:27
 // @param item *rag.WechatArticle
-func ReGenerateArticleAbstract(item *rag.WechatArticle) {
-	var err error
+// @param question *rag.Question
+// @param forceGenerate bool
+// @return err error
+func GenerateWechatArticleAbstractByQuestion(item *rag.WechatArticle, question *rag.Question, forceGenerate bool) (err error) {
 	defer func() {
 		if err != nil {
-			utils.FileLog.Error("文章转临时文件失败,err:%v", err)
-			fmt.Println("文章转临时文件失败,err:", err)
+			utils.FileLog.Error("文章摘要生成(根据提示词生成)失败,err:%v", err)
 		}
 	}()
 
-	abstractObj := rag.WechatArticleAbstract{}
-	abstractItem, err := abstractObj.GetByWechatArticleId(item.WechatArticleId)
-	if err != nil {
-		if utils.IsErrNoRow(err) {
-			// 直接生成
-			GenerateArticleAbstract(item)
-			return
-		}
-		// 异常了
+	// 内容为空,那就不需要生成摘要
+	if item.TextContent == `` {
 		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())
+	abstractObj := rag.WechatArticleAbstract{}
+	abstractItem, err := abstractObj.GetByWechatArticleIdAndQuestionId(item.WechatArticleId, question.QuestionId)
+	// 如果找到了,同时不是强制生成,那么就直接处理到知识库中
+	if err == nil && !forceGenerate {
+		// 摘要已经生成,不需要重复生成,只需要重新加入到向量库中
+		WechatArticleAbstractToKnowledge(item, abstractItem, false)
+
 		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
+
+	// 如果是没找到数据,那么就将报错置空
+	if err != nil && utils.IsErrNoRow(err) {
+		err = nil
 	}
-	defer func() {
-		os.Remove(tmpFilePath)
-	}()
 
-	// 上传临时文件到LLM
-	tmpFileResp, err := llm.UploadTempDocs(tmpFilePath)
-	if err != nil {
-		err = fmt.Errorf("上传临时文件到LLM失败,Err:" + err.Error())
+	//你现在是一名资深的期货行业分析师,请基于以下的问题进行汇总总结,如果不能正常总结出来,那么就只需要回复我:sorry
+	questionStr := fmt.Sprintf(`%s\n%s`, `你现在是一名资深的期货行业分析师,请基于以下的问题进行汇总总结,如果不能正常总结出来,那么就只需要回复我:sorry。以下是问题:`, question.QuestionContent)
+	//开始对话
+	abstract, industryTags, _, tmpErr := getAnswerByContent(item.WechatArticleId, utils.AI_ARTICLE_SOURCE_WECHAT, questionStr)
+	if tmpErr != nil {
+		err = fmt.Errorf("LLM对话失败,Err:" + tmpErr.Error())
 		return
 	}
 
-	if tmpFileResp.Data.Id == `` {
-		err = fmt.Errorf("上传临时文件到LLM失败,Err:上传失败")
+	if abstract == `` {
 		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())
+	if abstract == `sorry` || strings.Index(abstract, `根据已知信息无法回答该问题`) == 0 {
+		item.AbstractStatus = 2
+		item.ModifyTime = time.Now()
+		err = item.Update([]string{"AbstractStatus", "ModifyTime"})
 		return
 	}
 
-	// 添加问答记录
-	if len(addArticleChatRecordList) > 0 {
-		recordObj := rag.WechatArticleChatRecord{}
-		err = recordObj.CreateInBatches(addArticleChatRecordList)
+	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, err := json.Marshal(tagIdList)
 		if err != nil {
-			return
+			utils.FileLog.Info(fmt.Sprintf("标签ID序列化失败,Err:%s", tmpErr.Error()))
+		} else {
+			tagIdJsonStr = string(tagIdJsonByte)
 		}
+
+		tagNameJsonStr = strings.Join(tagNameList, `,`)
 	}
 
-	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"})
+
+	if abstractItem == nil || abstractItem.WechatArticleAbstractId <= 0 {
+		abstractItem = &rag.WechatArticleAbstract{
+			WechatArticleAbstractId: 0,
+			WechatArticleId:         item.WechatArticleId,
+			Content:                 abstract,
+			Version:                 1,
+			VectorKey:               "",
+			ModifyTime:              time.Now(),
+			CreateTime:              time.Now(),
+			QuestionId:              question.QuestionId,
+			Tags:                    tagIdJsonStr,
+			TagsName:                tagNameJsonStr,
+			QuestionContent:         question.QuestionContent,
 		}
-		item.AbstractStatus = 1
-		item.ModifyTime = time.Now()
-		err = item.Update([]string{"AbstractStatus", "ModifyTime"})
+		err = abstractItem.Create()
+	} else {
+		// 添加历史记录
+		rag.AddArticleAbstractHistoryByWechatArticleAbstract(abstractItem)
 
 		abstractItem.Content = abstract
-		abstractItem.Version = abstractObj.Version + 1
+		abstractItem.Version++
 		abstractItem.ModifyTime = time.Now()
-		err = abstractItem.Update([]string{"content", "version", "modify_time"})
-		if err != nil {
-			return
-		}
+		abstractItem.Tags = tagIdJsonStr
+		abstractItem.TagsName = tagNameJsonStr
+		abstractItem.QuestionContent = question.QuestionContent
+		err = abstractItem.Update([]string{"content", "version", "modify_time", "tags", "tags_name", "question_content"})
+	}
 
-		AbstractToKnowledge(item, abstractItem, true)
+	if err != nil {
+		return
 	}
+
+	// 数据入ES库
+	go AddOrEditEsWechatArticleAbstract(abstractItem.WechatArticleAbstractId)
+
+	WechatArticleAbstractToKnowledge(item, abstractItem, false)
+
+	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, industryTags, varietyTags []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)
+
+	// 提取标签
+	industryTags, varietyTags = 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,6 +1117,7 @@ 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,
@@ -1010,11 +1125,154 @@ func AddOrEditEsWechatArticleAbstract(articleAbstractId int) {
 		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 {
+		go 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)
 		}
 	}()
 
@@ -1094,3 +1352,83 @@ 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) (industryTags []string, varietyTags []string) {
+	reIndustry := regexp.MustCompile(`行业标签((?:【[^】]*】)+)`)
+	industryMatch := reIndustry.FindStringSubmatch(text)
+	if len(industryMatch) > 1 {
+		industryContent := industryMatch[1]
+		reSplit := regexp.MustCompile(`【([^】]*)】`)
+		industryTags = make([]string, 0)
+		for _, m := range reSplit.FindAllStringSubmatch(industryContent, -1) {
+			if len(m) > 1 {
+				industryTags = append(industryTags, m[1])
+			}
+		}
+	}
+
+	reVariety := regexp.MustCompile(`品种标签((?:【[^】]*】)+)`)
+	varietyMatch := reVariety.FindStringSubmatch(text)
+	if len(varietyMatch) > 1 {
+		varietyContent := varietyMatch[1]
+		reSplit := regexp.MustCompile(`【([^】]*)】`)
+		varietyTags = make([]string, 0)
+		for _, m := range reSplit.FindAllStringSubmatch(varietyContent, -1) {
+			if len(m) > 1 {
+				varietyTags = append(varietyTags, m[1])
+			}
+		}
+	}
+	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 // 权益报告库鉴权
 )
 

+ 14 - 1
utils/constants.go

@@ -271,10 +271,14 @@ const (
 	CACHE_DATA_SOURCE_ES_HANDLE             = "eta:data_source_es:handle"             // 数据源es处理队列
 	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:"          //巡检消息队列
 )
 
 // 模板消息推送类型
@@ -605,3 +609,12 @@ const (
 const (
 	CACHE_KEY_EDB_DATA_UPDATE_LOG = "eta_forum:edb_data_update_log" // 经济数据库数据更新日志
 )
+
+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)

+ 32 - 0
utils/redis/cluster_redis.go

@@ -249,6 +249,38 @@ 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

+ 31 - 0
utils/redis/standalone_redis.go

@@ -237,6 +237,37 @@ 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
 		}
 	}
 	// 更新最后活跃时间