Przeglądaj źródła

Merge branch 'master' into feature/eta2.6.5_kpler

# Conflicts:
#	routers/router.go
xyxie 5 dni temu
rodzic
commit
0c3a0490c1
98 zmienionych plików z 5377 dodań i 935 usunięć
  1. 3 2
      .gitignore
  2. 2 0
      controllers/bi_dashboard.go
  3. 113 108
      controllers/data_manage/chart_info.go
  4. 3 3
      controllers/data_manage/data_manage_permission/data_manage_permission.go
  5. 3 3
      controllers/data_manage/data_manage_permission/data_move.go
  6. 1 1
      controllers/data_manage/data_manage_permission/message.go
  7. 130 32
      controllers/data_manage/edb_info.go
  8. 2 2
      controllers/data_manage/edb_info_refresh.go
  9. 1 1
      controllers/data_manage/edb_info_relation.go
  10. 394 0
      controllers/data_manage/edb_inspection.go
  11. 96 0
      controllers/data_manage/edb_inspection_message.go
  12. 9 3
      controllers/data_manage/excel/excel_info.go
  13. 75 41
      controllers/data_manage/future_good/future_good_chart_info.go
  14. 9 5
      controllers/data_manage/gpr_risk_data.go
  15. 39 34
      controllers/data_manage/mysteel_chemical_data.go
  16. 17 2
      controllers/data_manage/predict_edb_info.go
  17. 11 5
      controllers/data_manage/purang_data.go
  18. 65 12
      controllers/data_stat/edb_source_stat.go
  19. 238 7
      controllers/data_stat/edb_terminal.go
  20. 74 10
      controllers/edb_monitor/edb_monitor_message.go
  21. 1 0
      controllers/eta_forum/eta_forum.go
  22. 13 4
      controllers/material/material.go
  23. 19 2
      controllers/message.go
  24. 136 18
      controllers/report_chapter.go
  25. 200 48
      controllers/report_v2.go
  26. 6 1
      controllers/sandbox/sandbox.go
  27. 8 0
      controllers/sys_role.go
  28. 12 62
      controllers/user_login.go
  29. 207 0
      global/websocket.go
  30. 1 0
      models/bi_dashboard/bi_dashboard.go
  31. 1 0
      models/bi_dashboard/bi_dashboard_detail.go
  32. 12 12
      models/data_manage/base_from_gpr_risk.go
  33. 55 55
      models/data_manage/base_from_purang.go
  34. 1 1
      models/data_manage/data_manage_permission/classify_no_auth_record.go
  35. 1 1
      models/data_manage/data_manage_permission/move.go
  36. 1 1
      models/data_manage/data_manage_permission/move_record.go
  37. 1 1
      models/data_manage/data_manage_permission/no_auth_record.go
  38. 2 2
      models/data_manage/edb_data_mysteel_chemical.go
  39. 10 1
      models/data_manage/edb_info.go
  40. 4 4
      models/data_manage/edb_info_relation.go
  41. 176 0
      models/data_manage/edb_inspection/edb_inspection_config.go
  42. 112 0
      models/data_manage/edb_inspection/edb_inspection_dashboard.go
  43. 114 0
      models/data_manage/edb_inspection/edb_inspection_date_config.go
  44. 191 0
      models/data_manage/edb_inspection/edb_inspection_message.go
  45. 112 0
      models/data_manage/edb_inspection/edb_inspection_record.go
  46. 117 6
      models/data_manage/edb_terminal.go
  47. 4 4
      models/data_manage/mysteel_chemical_classify.go
  48. 38 27
      models/data_manage/mysteel_chemical_index.go
  49. 7 6
      models/data_manage/request/mysteel_chemical_data.go
  50. 1 1
      models/data_manage/request/predict_edb_info.go
  51. 1 0
      models/data_stat/edb_info_update_stat.go
  52. 1 1
      models/db.go
  53. 28 0
      models/edb_monitor/edb_monitor_message.go
  54. 4 0
      models/manual_edb.go
  55. 5 2
      models/material/material_classify.go
  56. 7 0
      models/message.go
  57. 70 8
      models/report.go
  58. 281 0
      models/report/report_free_layout.go
  59. 20 11
      models/report_chapter.go
  60. 26 2
      models/report_v2.go
  61. 2 2
      models/system/sys_role.go
  62. 1 1
      models/system/sys_user.go
  63. 127 1
      routers/commentsRouter.go
  64. 2 0
      routers/router.go
  65. 8 7
      services/data/base_edb_lib.go
  66. 91 8
      services/data/chart_info.go
  67. 14 14
      services/data/data_manage_permission/data_move.go
  68. 1 1
      services/data/data_manage_permission/message.go
  69. 61 9
      services/data/edb_info.go
  70. 1 1
      services/data/edb_info_relation.go
  71. 397 0
      services/data/edb_inspection.go
  72. 114 0
      services/data/edb_inspection_message.go
  73. 1 1
      services/data/excel/excel_info.go
  74. 15 15
      services/data/mysteel_chemical.go
  75. 7 2
      services/data/predict_edb_info.go
  76. 190 55
      services/data/predict_edb_info_rule.go
  77. 136 39
      services/edb_monitor/edb_monitor_message.go
  78. 3 2
      services/elastic/elastic.go
  79. 59 51
      services/file.go
  80. 6 2
      services/material/material.go
  81. 56 0
      services/report_chapter.go
  82. 2 1
      services/report_rai.go
  83. 120 17
      services/report_v2.go
  84. 185 113
      services/smart_report.go
  85. 5 2
      services/task.go
  86. 367 0
      services/websocket_msg.go
  87. 0 7
      static/images/UXjAv5IXV9fSqDBqhGS3DPYcj9wb.txt
  88. 0 7
      static/images/fdUC0cafpf0LXCjpwluC0r6IzA4Z.txt
  89. BIN
      static/wind指标刷新失败处理.pdf
  90. BIN
      static/同花顺指标API方式刷新失败处理.pdf
  91. BIN
      static/钢联指标API对接刷新失败处理.pdf
  92. BIN
      static/钢联指标终端对接刷新失败处理.pdf
  93. 29 0
      utils/common.go
  94. 2 2
      utils/config.go
  95. 46 23
      utils/constants.go
  96. 1 0
      utils/redis.go
  97. 19 0
      utils/redis/cluster_redis.go
  98. 18 0
      utils/redis/standalone_redis.go

+ 3 - 2
.gitignore

@@ -3,7 +3,6 @@
 /.idea
 /routers/.DS_Store
 /rdlucklog
-/etalogs
 /conf/*.conf
 /binlog/*
 /*.pdf
@@ -18,6 +17,8 @@
 eta_api.exe
 eta_api.exe~
 /static/tmpFile/*
+/static/imgs/*
+/static/ai/*
 etalogs/
 /.vscode
-/fix
+/fix

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

+ 113 - 108
controllers/data_manage/chart_info.go

@@ -20,6 +20,7 @@ import (
 	"fmt"
 	"os"
 	"os/exec"
+	"regexp"
 	"sort"
 	"strconv"
 	"strings"
@@ -876,18 +877,18 @@ func (this *ChartInfoController) ChartInfoMove() {
 		go data.EsAddOrEditChartInfo(chartInfo.ChartInfoId)
 
 		// 判断是否为精选目录
-			// 如果该目录不是精选目录,且该图表已经上架,则需撤回该图表
-			if oldClassifyId != req.ChartClassifyId {
-				parentChartClassifyInfo, err := data_manage.GetChartClassifyById(req.ChartClassifyId)
-				if err != nil {
-					br.Msg = "移动失败"
-					br.ErrMsg = "获取上级分类信息失败,Err:" + err.Error()
-					return
-				}
-				if parentChartClassifyInfo.IsSelected == 0 && chartInfo.ForumChartInfoId > 0 {
-					go eta_forum.DeleteChart(chartInfo.ChartInfoId)
-				}
+		// 如果该目录不是精选目录,且该图表已经上架,则需撤回该图表
+		if oldClassifyId != req.ChartClassifyId {
+			parentChartClassifyInfo, err := data_manage.GetChartClassifyById(req.ChartClassifyId)
+			if err != nil {
+				br.Msg = "移动失败"
+				br.ErrMsg = "获取上级分类信息失败,Err:" + err.Error()
+				return
+			}
+			if parentChartClassifyInfo.IsSelected == 0 && chartInfo.ForumChartInfoId > 0 {
+				go eta_forum.DeleteChart(chartInfo.ChartInfoId)
 			}
+		}
 	}
 
 	if err != nil {
@@ -1455,47 +1456,19 @@ func (this *ChartInfoController) PreviewChartInfoDetail() {
 			br.ErrMsg = "标识线配置异常" + err.Error()
 			return
 		}
-		for i := range markerLines {
-			if markerLines[i].EdbType == 0 && markerLines[i].TimeIntervalType == 0 && markerLines[i].Axis != 3 {
-				// 图上第一个指标且时间区间跟随图表
-				if markerLines[i].MarkLineType == 2 {
-					if edbList[0].IsAxis == 1 {
-						value, err := data.MarkerLineCalculate(markerLines[i], edbList[0].DataList, chartInfo)
-						if err != nil {
-							br.Msg = "标识线配置异常"
-							br.ErrMsg = "标识线配置异常" + err.Error()
-							return
-						}
-						markerLines[i].Value = value
-					} else {
-						// 其他的都走指标计算
-						edbInfo, err := data_manage.GetEdbInfoById(markerLines[i].EdbInfoId)
-						if err != nil {
-							br.Msg = "指标计算标识线获取指标信息异常"
-							br.ErrMsg = "指标计算标识线获取指标信息异常" + err.Error()
-							return
-						}
-						// 判断时间区间不为跟随图表的情况
-						if markerLines[i].TimeIntervalType != 0 {
-							startDate = markerLines[i].StartDate.Date
-							endDate = markerLines[i].EndDate.Date
-						}
-						dataList, err := data_manage.GetEdbDataList(edbInfo.Source, edbInfo.SubSource, edbInfo.EdbInfoId, startDate, endDate)
-						if err != nil {
-							br.Msg = "指标计算标识线获取指标数据异常"
-							br.ErrMsg = "指标计算标识线获取指标数据异常" + err.Error()
-							return
-						}
-						value, err := data.MarkerLineCalculate(markerLines[i], dataList, chartInfo)
-						if err != nil {
-							br.Msg = "标识线配置异常"
-							br.ErrMsg = "标识线配置异常" + err.Error()
-							return
-						}
-						markerLines[i].Value = value
-					}
+		for i, markerLine := range markerLines {
+			switch markerLine.MarkLineType { //1:固定 2:指标计算
+			case 2:
+				tmpMarkerLine, tmpErr := data.GetMarkerLine(markerLine, edbList, chartInfo, startDate, endDate)
+				if tmpErr != nil {
+					br.Msg = "标识线配置异常"
+					br.ErrMsg = "标识线配置异常," + tmpErr.Error()
+					return
 				}
+				markerLine = tmpMarkerLine
 			}
+
+			markerLines[i] = markerLine
 		}
 
 		markerLineStr, err := json.Marshal(markerLines)
@@ -1833,7 +1806,7 @@ func (this *ChartInfoController) ChartInfoDetailV2() {
 	}
 
 	// 图表当前分类的分类树
-	
+
 	classifyLevels := make([]string, 0)
 	{
 		list, e := data_manage.GetChartClassifyAllBySource(utils.CHART_SOURCE_DEFAULT)
@@ -2988,58 +2961,78 @@ func (this *ChartInfoController) ChartInfoBase64Upload() {
 			return
 		}
 	}
-
+	NotBackendGenerate, _ := this.GetBool("NotBackendGenerate", false)
+	if NotBackendGenerate {
+		b, _ := regexp.MatchString(`^data:\s*image\/(\w+);base64,`, imgData)
+		if !b {
+			br.Msg = "图片格式不正确"
+			br.ErrMsg = "图片格式不正确"
+			return
+		}
+		re, _ := regexp.Compile(`^data:\s*image\/(\w+);base64,`)
+		base64Str := re.ReplaceAllString(imgData, "")
+		base64Str = strings.Replace(base64Str, " ", "", -1)
+		imgData = base64Str
+	}
 	//var saveToOssPath string
 	randStr := utils.GetRandStringNoSpecialChar(28)
 	var fileName, outFileName string
 	fileName = randStr + ".txt"
-	fileName = uploadDir + fileName
-	err := utils.SaveToFile(imgData, fileName)
-	if err != nil {
-		br.Msg = "图片保存失败"
-		br.ErrMsg = "图片保存失败,Err:" + err.Error()
-		return
-	}
 	outFileName = randStr + ".png"
-
-	doneChannel := make(chan bool, 1)
-	errorChannel := make(chan error, 1)
-
-	cmd := exec.Command("highcharts-export-server", "--infile", fileName, "--constr", "Chart", "--scale", "2", "--workers", "10", "--workLimit", "3", "--outfile", outFileName)
-
-	go func() {
-		output, err := cmd.CombinedOutput()
+	fileName = uploadDir + fileName
+	if NotBackendGenerate {
+		err := utils.SaveBase64ToFile(imgData, outFileName)
 		if err != nil {
-			utils.FileLog.Info("execute command failed, output: , error: \n" + string(output) + err.Error())
-			errorChannel <- err
+			br.Msg = "图片保存失败"
+			br.ErrMsg = "图片保存失败,Err:" + err.Error()
 			return
 		}
-		doneChannel <- true
-	}()
+	} else {
+		err := utils.SaveToFile(imgData, fileName)
+		if err != nil {
+			br.Msg = "图片保存失败"
+			br.ErrMsg = "图片保存失败,Err:" + err.Error()
+			return
+		}
+		doneChannel := make(chan bool, 1)
+		errorChannel := make(chan error, 1)
 
-	select {
-	case <-time.After(30 * time.Second):
-		utils.FileLog.Info("执行超过30秒 杀死超时进程")
-		e := cmd.Process.Kill()
-		if e != nil {
-			fmt.Println("cmd kill err: ", e.Error())
-			utils.FileLog.Info(fmt.Sprintf("cmd kill err: %s", e.Error()))
-			br.Msg = "图片生成失败"
-			br.ErrMsg = "图片生成失败, 执行超时" + e.Error()
+		cmd := exec.Command("highcharts-export-server", "--infile", fileName, "--constr", "Chart", "--scale", "2", "--workers", "10", "--workLimit", "3", "--outfile", outFileName)
+
+		go func() {
+			output, err := cmd.CombinedOutput()
+			if err != nil {
+				utils.FileLog.Info("execute command failed, output: , error: \n" + string(output) + err.Error())
+				errorChannel <- err
+				return
+			}
+			doneChannel <- true
+		}()
+		select {
+		case <-time.After(30 * time.Second):
+			utils.FileLog.Info("执行超过30秒 杀死超时进程")
+			e := cmd.Process.Kill()
+			if e != nil {
+				fmt.Println("cmd kill err: ", e.Error())
+				utils.FileLog.Info(fmt.Sprintf("cmd kill err: %s", e.Error()))
+				br.Msg = "图片生成失败"
+				br.ErrMsg = "图片生成失败, 执行超时" + e.Error()
+				return
+			}
+			fmt.Println("timeout kill process")
+		case <-doneChannel:
+			fmt.Println("done")
+		case err := <-errorChannel:
+			br.Msg = "文件上传失败"
+			br.ErrMsg = fmt.Sprintf("execute command failure err: %s", err.Error())
+			fmt.Println("execute command failure err:" + err.Error())
 			return
 		}
-		fmt.Println("timeout kill process")
-	case <-doneChannel:
-		fmt.Println("done")
-	case err := <-errorChannel:
-		br.Msg = "文件上传失败"
-		br.ErrMsg = fmt.Sprintf("execute command failure err: %s", err.Error())
-		fmt.Println("execute command failure err:" + err.Error())
-		return
+		defer func() {
+			os.Remove(fileName)
+		}()
 	}
-
 	defer func() {
-		os.Remove(fileName)
 		os.Remove(outFileName)
 	}()
 
@@ -3071,7 +3064,7 @@ func (this *ChartInfoController) ChartInfoBase64Upload() {
 		br.ErrMsg = "初始化OSS服务失败"
 		return
 	}
-	resourceUrl, err = ossClient.UploadFile(outFileName, outFileName, "")
+	resourceUrl, err := ossClient.UploadFile(outFileName, outFileName, "")
 	if err != nil {
 		br.Msg = "文件上传失败"
 		br.ErrMsg = "文件上传失败,Err:" + err.Error()
@@ -3560,7 +3553,7 @@ func (this *ChartInfoController) PreviewBarChartInfo() {
 						value, err := data.MarkerLineCalculate(markerLines[i], edbList[0].DataList, chartInfo)
 						if err != nil {
 							br.Msg = "标识线配置异常"
-							br.ErrMsg = "标识线配置异常" + err.Error()
+							br.ErrMsg = "标识线配置异常," + err.Error()
 							return
 						}
 						markerLines[i].Value = value
@@ -4474,7 +4467,7 @@ func (this *ChartInfoController) UpdateToForum() {
 
 	// 更新指标数据
 	utils.Rc.LPush(utils.CACHE_KEY_EDB_DATA_UPDATE_LOG, []byte("1"))
-	
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "保存成功"
@@ -4650,6 +4643,19 @@ func (this *ChartInfoController) ChartInfoImgSetBySvg() {
 		br.ErrMsg = "图片参数错误,Img Is Empty"
 		return
 	}
+	NotBackendGenerate, _ := this.GetBool("NotBackendGenerate", false)
+	if NotBackendGenerate {
+		b, _ := regexp.MatchString(`^data:\s*image\/(\w+);base64,`, imgData)
+		if !b {
+			br.Msg = "图片格式不正确"
+			br.ErrMsg = "图片格式不正确"
+			return
+		}
+		re, _ := regexp.Compile(`^data:\s*image\/(\w+);base64,`)
+		base64Str := re.ReplaceAllString(imgData, "")
+		base64Str = strings.Replace(base64Str, " ", "", -1)
+		imgData = base64Str
+	}
 	chartInfoId, _ := this.GetInt("ChartInfoId", 0)
 	if chartInfoId <= 0 {
 		br.Msg = "图片参数错误"
@@ -4659,7 +4665,7 @@ func (this *ChartInfoController) ChartInfoImgSetBySvg() {
 	resp := new(models.ResourceResp)
 
 	// 通过svg图片生成图片资源地址
-	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData)
+	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData, NotBackendGenerate)
 	if err != nil {
 		br.Msg = errMsg
 		br.ErrMsg = err.Error()
@@ -5141,7 +5147,7 @@ func (this *ChartInfoController) ModifyChartList() {
 		br.ErrMsg = "更新图表分类失败,Err:" + err.Error()
 		return
 	}
-	
+
 	go eta_forum.ChartInfoDeleteBatchByChartInfoIds(chartIds, req.ChartClassifyId)
 
 	br.Ret = 200
@@ -5186,12 +5192,12 @@ func (this *ChartInfoController) GetChartDescriptionList() {
 	response := new(data_manage.ChartDescriptionListResponse)
 	for _, v := range chartDescriptionList {
 		list = append(list, &data_manage.ChartDescriptionList{
-			Id:               v.Id,
-			Description:      v.Description,
-			ChartInfoId:      v.ChartInfoId,
-			SysUserId:        v.SysUserId,
-			SysUserRealName:  v.SysUserRealName,
-			CreateTime:       v.CreateTime.Format(utils.FormatDateTime),
+			Id:              v.Id,
+			Description:     v.Description,
+			ChartInfoId:     v.ChartInfoId,
+			SysUserId:       v.SysUserId,
+			SysUserRealName: v.SysUserRealName,
+			CreateTime:      v.CreateTime.Format(utils.FormatDateTime),
 		})
 	}
 	response.List = list
@@ -5220,7 +5226,7 @@ func (this *ChartInfoController) AddChartDescription() {
 		br.ErrMsg = "请登录,SysUser Is Empty"
 		br.Ret = 408
 		return
-	}	
+	}
 
 	var req data_manage.ChartDescriptionAddReq
 	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
@@ -5249,12 +5255,12 @@ func (this *ChartInfoController) AddChartDescription() {
 		return
 	}
 	item := &data_manage.ChartDescription{
-		ChartInfoId: req.ChartInfoId,
-		Description: req.Description,
-		SysUserId: sysUser.AdminId,
+		ChartInfoId:     req.ChartInfoId,
+		Description:     req.Description,
+		SysUserId:       sysUser.AdminId,
 		SysUserRealName: sysUser.RealName,
-		ModifyTime: time.Now(),
-		CreateTime: time.Now(),
+		ModifyTime:      time.Now(),
+		CreateTime:      time.Now(),
 	}
 	err = data_manage.AddChartDescription(item)
 	if err != nil {
@@ -5267,4 +5273,3 @@ func (this *ChartInfoController) AddChartDescription() {
 	br.Success = true
 	br.Msg = "添加成功"
 }
-

+ 3 - 3
controllers/data_manage/data_manage_permission/data_manage_permission.go

@@ -203,7 +203,7 @@ func (c *DataMangePermissionController) SetEdbChartClassifyPermission() {
 // GetEdbChartPermission
 // @Title 指标/图表/表格权限设置接口
 // @Description 指标/图表/表格权限设置接口
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :目前作用于ETA表格,2024-3-26 14:12:09"
 // @Param   DataId   query   int  false       "资产id"
 // @Success 200 {object} data_manage.ChartListResp
@@ -262,7 +262,7 @@ func (c *DataMangePermissionController) GetEdbChartPermission() {
 // GetEdbChartClassifyPermission
 // @Title 获取指标/图表/表格分类权限设置接口
 // @Description 获取指标/图表/表格分类权限设置接口
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :目前作用于ETA表格,2024-3-26 14:12:09"
 // @Param   UserId   query   int  false       "用户id"
 // @Success 200 {object} data_manage.ChartListResp
@@ -336,7 +336,7 @@ func (c *DataMangePermissionController) GetEdbChartClassifyPermission() {
 // GetEdbChartNoPermission
 // @Title 根据资产id获取其分类没有权限的用户id列表接口
 // @Description 根据资产id获取其分类没有权限的用户id列表接口
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :目前作用于ETA表格,2024-3-26 14:12:09"
 // @Param   DataId   query   int  false       "资产id"
 // @Success 200 {object} data_manage.ChartListResp

+ 3 - 3
controllers/data_manage/data_manage_permission/data_move.go

@@ -13,7 +13,7 @@ import (
 // EdbChartClassifyList
 // @Title 获取指标/图表分类列表数据接口
 // @Description 获取指标/图表分类列表数据接口
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :ETA表格中的各种表格类型,以及图表的来源(这个是后续的扩展方向)"
 // @Success 200 {object} data_manage.ChartListResp
 // @router /edb_chart/classify [get]
@@ -63,7 +63,7 @@ func (c *DataMangePermissionController) EdbChartClassifyList() {
 // SecretEdbChartClassifyList
 // @Title 获取涉密的指标/图表分类列表数据接口
 // @Description 获取指标/图表分类列表数据接口
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :ETA表格中的各种表格类型,以及图表的来源(这个是后续的扩展方向)"
 // @Success 200 {object} data_manage.ChartListResp
 // @router /edb_chart/classify/secret [get]
@@ -133,7 +133,7 @@ func removeNodesWithNoJoinPermissionAndEmptyChildRecursively(nodes []*data_manag
 // @Description 获取指标/图表创建人列表数据接口
 // @Param   PageSize   query   int  true       "每页数据条数"
 // @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :目前作用于ETA表格,2024-3-26 14:12:09"
 // @Param   Keyword   query   string  false       "关键字,code或者名称"
 // @Param   Classify   query   string  false       "分类id"

+ 1 - 1
controllers/data_manage/data_manage_permission/message.go

@@ -172,7 +172,7 @@ func (c *DataMangePermissionController) MessageRead() {
 // @Param   MessageId			query	int		true	"消息ID"
 // @Param   PageSize   query   int  true       "每页数据条数"
 // @Param   CurrentIndex   query   int  true       "当前页页码,从1开始"
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格"
 // @Param   SubSource   query   int  false       "子来源 :目前作用于ETA表格,2024-3-26 14:12:09"
 // @Success 200 {object} data_manage_permission.MessageDetailListResp
 // @router /message/detail [get]

+ 130 - 32
controllers/data_manage/edb_info.go

@@ -21,6 +21,7 @@ import (
 	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
+	"regexp"
 	"sort"
 	"strconv"
 	"strings"
@@ -60,6 +61,10 @@ func (this *EdbInfoController) EdbInfoSearch() {
 	stockCode := this.GetString("StockCode")
 	frequency := this.GetString("Frequency")
 
+	// 日期序列参数
+	period := this.GetString("Period")
+	days := this.GetString("Days")
+	priceAdj := this.GetString("PriceAdj")
 	extraPars := this.GetString("ExtraPars")
 	extraPars = strings.TrimSpace(extraPars)
 
@@ -192,7 +197,13 @@ func (this *EdbInfoController) EdbInfoSearch() {
 					br.Msg = "请输入指标代码"
 					return
 				}
-				respItem, err := data.AddEdbDataThsDs(source, stockCode, edbCode, extraPars)
+				// 日期序列参数校验
+				pass, tips := data.CheckWindThsDsParams(source, subSource, period, days, priceAdj)
+				if !pass {
+					br.Msg = tips
+					return
+				}
+				respItem, err := data.AddEdbDataThsDs(source, stockCode, edbCode, extraPars, days, period)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -271,7 +282,13 @@ func (this *EdbInfoController) EdbInfoSearch() {
 					br.Msg = "请输入指标代码"
 					return
 				}
-				respItem, err := data.AddEdbDataWindWsd(source, stockCode, edbCode)
+				// 日期序列参数校验
+				pass, tips := data.CheckWindThsDsParams(source, subSource, period, days, priceAdj)
+				if !pass {
+					br.Msg = tips
+					return
+				}
+				respItem, err := data.AddEdbDataWindWsd(source, stockCode, edbCode, days, period, priceAdj)
 				if err != nil {
 					br.Msg = "获取失败"
 					br.ErrMsg = "获取失败,Err:" + err.Error()
@@ -1251,7 +1268,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				}
 				isAdd = true
 			}
-		} else if source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL { //钢联化工
+		} else if source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL { //上海钢联
 			dataItems, err := data_manage.GetEdbDataAllByEdbCode(edbCode, source, subSource, utils.EDB_DATA_LIMIT)
 			if err != nil && !utils.IsErrNoRow(err) {
 				br.Msg = "获取失败"
@@ -1991,6 +2008,7 @@ func (this *EdbInfoController) EdbInfoSearch() {
 			}
 		}
 		if isAdd {
+			// 日期序列
 			if subSource == utils.DATA_SUB_SOURCE_DATE {
 				if source == utils.DATA_SOURCE_WIND {
 					//wsdData := windWsdRespItem.Data
@@ -2057,8 +2075,11 @@ func (this *EdbInfoController) EdbInfoSearch() {
 
 					stockList := make([]*data_manage.StockInfo, 0)
 					edbCodeArr := strings.Split(edbCode, ",")
+					// 指标编码后缀,默认值则无需拼接
+					suffix := utils.GetWindWsdIndexCodeSuffix(period, days, priceAdj)
+
 					for _, v := range edbCodeArr {
-						indexCode := utils.WindDbWsd + stockCode + v
+						indexCode := utils.WindDbWsd + stockCode + v + suffix
 
 						dataList, err := data_manage.GetEdbDataAllByEdbCodeAndSubSource(indexCode, source, utils.DATA_SUB_SOURCE_DATE, utils.EDB_DATA_LIMIT)
 						if err != nil && !utils.IsErrNoRow(err) {
@@ -2070,6 +2091,9 @@ func (this *EdbInfoController) EdbInfoSearch() {
 						stockInfo := new(data_manage.StockInfo)
 						stockInfo.StockCode = stockCode
 						stockInfo.EdbCode = v
+						stockInfo.Days = days
+						stockInfo.Period = period
+						stockInfo.PriceAdj = priceAdj
 						stockInfo.DataList = dataList
 						stockList = append(stockList, stockInfo)
 					}
@@ -2077,8 +2101,11 @@ func (this *EdbInfoController) EdbInfoSearch() {
 				} else if source == utils.DATA_SOURCE_THS {
 					stockList := make([]*data_manage.StockInfo, 0)
 					edbCodeArr := strings.Split(edbCode, ",")
+					// 指标编码后缀,默认值则无需拼接
+					suffix := utils.GetThsDsIndexCodeSuffix(period, days)
+
 					for _, v := range edbCodeArr {
-						indexCode := utils.ThsDs + stockCode + v
+						indexCode := utils.ThsDs + stockCode + v + suffix
 
 						dataList, err := data_manage.GetEdbDataAllByEdbCodeAndSubSource(indexCode, source, utils.DATA_SUB_SOURCE_DATE, utils.EDB_DATA_LIMIT)
 						if err != nil && !utils.IsErrNoRow(err) {
@@ -2090,6 +2117,8 @@ func (this *EdbInfoController) EdbInfoSearch() {
 						stockInfo := new(data_manage.StockInfo)
 						stockInfo.StockCode = stockCode
 						stockInfo.EdbCode = v
+						stockInfo.Days = days
+						stockInfo.Period = period
 						stockInfo.DataList = dataList
 						stockList = append(stockList, stockInfo)
 					}
@@ -2410,9 +2439,9 @@ func (this *EdbInfoController) EdbInfoAdd() {
 	//	return
 	//}
 
-	// 兼容钢联与钢联化工数据
+	// 兼容钢联与上海钢联数据
 	if utils.InArrayByInt([]int{utils.DATA_SOURCE_GL, utils.DATA_SOURCE_MYSTEEL_CHEMICAL}, source) {
-		// 如果是钢联的话,那么就先判断是不是存在钢联化工
+		// 如果是钢联的话,那么就先判断是不是存在上海钢联
 		tmpInfo, err := data_manage.GetBaseFromMysteelChemicalIndexByCode(req.EdbCode)
 		if err != nil {
 			if !utils.IsErrNoRow(err) {
@@ -5293,7 +5322,7 @@ func (this *EdbInfoController) GetEdbBeforeAndAfterDateData() {
 // EdbChartAdminList
 // @Title 获取创建人员分组
 // @Description 获取创建人员分组
-// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库"
+// @Param   Source   query   int  false       "来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库"
 // @Success 200 {object} company.DepartmentGroupSellersResp
 // @router /edb_chart/adminList [get]
 func (this *EdbInfoController) EdbChartAdminList() {
@@ -5668,6 +5697,47 @@ func (this *EdbInfoController) EdbChartList() {
 					}
 				}
 			}
+			var wg sync.WaitGroup
+			wg.Add(2)
+			var relationMap =make(map[int]*data_manage.BaseRelationEdbInfo,len(list))
+			var relationList []*data_manage.BaseRelationEdbInfo
+			relationChan := make(chan RelationResult, 2)
+			var edbInfoIds []int
+			var glEdbInfoInfoIds []int
+			for _, v := range list {
+				if v.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
+					glEdbInfoInfoIds = append(glEdbInfoInfoIds, v.EdbInfoId)
+				} else {
+					edbInfoIds = append(edbInfoIds, v.EdbInfoId)
+				}
+
+			}
+			// 抽象为一个函数减少重复逻辑
+			fetchRelations := func(ids []int, source int) {
+				defer wg.Done()
+				_, relList, getErr := data.GetEdbRelationListByIds(ids, source)
+				relationChan <- RelationResult{
+					List: relList,
+					Err:  getErr,
+				}
+			}
+			go fetchRelations(glEdbInfoInfoIds, utils.DATA_SOURCE_MYSTEEL_CHEMICAL)
+			go fetchRelations(edbInfoIds, -1)
+			wg.Wait()
+			close(relationChan)
+
+			for i := 0; i < 2; i++ {
+				result := <-relationChan
+				if result.Err != nil && !utils.IsErrNoRow(result.Err) {
+					br.Msg = "获取失败"
+					br.ErrMsg = "获取数据引用信息失败,Err:" + e.Error()
+					return
+				}
+				relationList = append(relationList, result.List...)
+			}
+			for _, v := range relationList {
+				relationMap[v.EdbInfoId] = v
+			}
 
 			for _, v := range list {
 				if currClassify, ok := classifyMap[v.ClassifyId]; ok {
@@ -5682,14 +5752,7 @@ func (this *EdbInfoController) EdbChartList() {
 						v.IsSupplierStop = 1
 					}
 				}
-				var relation *data_manage.BaseRelationEdbInfo
-				relation, err = data.GetEdbRelationListById(v.EdbInfoId, v.Source)
-				if err != nil && !utils.IsErrNoRow(err) {
-					br.Msg = "获取失败"
-					br.ErrMsg = "获取数据引用信息失败,Err:" + e.Error()
-					return
-				}
-				if relation != nil && relation.RelationNum > 0 {
+				if relationMap[v.EdbInfoId] != nil && relationMap[v.EdbInfoId].RelationNum > 0 {
 					v.IsRelation = true
 				}
 			}
@@ -5707,6 +5770,11 @@ func (this *EdbInfoController) EdbChartList() {
 	br.Data = resp
 }
 
+type RelationResult struct {
+	List []*data_manage.BaseRelationEdbInfo
+	Err  error
+}
+
 // Modify
 // @Title 修改指标信息接口
 // @Description 编辑指标接口
@@ -5871,6 +5939,11 @@ func (this *EdbInfoController) EdbInfoExistCheck() {
 	subSource, _ := this.GetInt("SubSource")
 	stockCode := this.GetString("StockCode")
 
+	// 日期序列额外参数
+	period := this.GetString("Period")
+	days := this.GetString("Days")
+	priceAdj := this.GetString("PriceAdj")
+
 	if source <= 0 {
 		br.Msg = "无效的数据来源"
 		return
@@ -5889,16 +5962,24 @@ func (this *EdbInfoController) EdbInfoExistCheck() {
 		br.Msg = "无效的数据库来源"
 		return
 	}
+	pass, tips := data.CheckWindThsDsParams(source, subSource, period, days, priceAdj)
+	if !pass {
+		br.Msg = tips
+		return
+	}
 
 	var indexCodeArr []string
 	edbCodeArr := strings.Split(edbCode, ",")
 	stockCodeArr := strings.Split(stockCode, ",")
 
-	var prefix string
+	// 指标编码前后缀
+	var prefix, suffix string
 	if source == utils.DATA_SOURCE_WIND {
 		prefix = utils.WindDbWsd
+		suffix = utils.GetWindWsdIndexCodeSuffix(period, days, priceAdj)
 	} else if source == utils.DATA_SOURCE_THS {
 		prefix = utils.ThsDs
+		suffix = utils.GetThsDsIndexCodeSuffix(period, days)
 	} else {
 		br.Msg = "来源错误"
 		br.ErrMsg = "来源错误"
@@ -5906,7 +5987,7 @@ func (this *EdbInfoController) EdbInfoExistCheck() {
 	}
 	for _, sv := range stockCodeArr {
 		for _, ev := range edbCodeArr {
-			indexCode := prefix + sv + ev
+			indexCode := prefix + sv + ev + suffix
 			indexCodeArr = append(indexCodeArr, indexCode)
 		}
 	}
@@ -5951,7 +6032,7 @@ func (this *EdbInfoController) EdbInfoExistCheck() {
 	br.Data = resp
 }
 
-// EdbInfoAdd
+// EdbInfoBatchAdd
 // @Title 指标批量保存接口
 // @Description 指标批量保存接口
 // @Param	request	body data_manage.BatchAddEdbInfoReq true "type json string"
@@ -6061,11 +6142,13 @@ func (this *EdbInfoController) EdbInfoBatchAdd() {
 		edbInfoItem.Source = v.Source
 		switch v.Source {
 		case utils.DATA_SOURCE_WIND:
+			suffix := utils.GetWindWsdIndexCodeSuffix(v.Period, v.Days, v.PriceAdj)
 			edbInfoItem.SourceName = "wind"
-			edbInfoItem.EdbCode = utils.WindDbWsd + v.StockCode + v.EdbCode
+			edbInfoItem.EdbCode = utils.WindDbWsd + v.StockCode + v.EdbCode + suffix
 		case utils.DATA_SOURCE_THS:
+			suffix := utils.GetThsDsIndexCodeSuffix(v.Period, v.Days)
 			edbInfoItem.SourceName = "ths"
-			edbInfoItem.EdbCode = utils.ThsDs + v.StockCode + v.EdbCode
+			edbInfoItem.EdbCode = utils.ThsDs + v.StockCode + v.EdbCode + suffix
 		}
 		edbInfoItem.SubSource = utils.DATA_SUB_SOURCE_DATE
 		edbInfoItem.SubSourceName = "日期序列"
@@ -6082,17 +6165,20 @@ func (this *EdbInfoController) EdbInfoBatchAdd() {
 		edbInfoItem.StockCode = v.StockCode
 		edbInfoItem.TerminalCode = terminalCode
 		edbInfoItem.ServerUrl = serverUrl
+
+		// 日期序列参数
 		var extra data_manage.EdbInfoExtra
-		if v.ApiExtraPars != "" {
-			extra.ApiExtraPars = v.ApiExtraPars
-			b, e := json.Marshal(extra)
-			if e != nil {
-				br.Msg = "保存失败"
-				br.ErrMsg = fmt.Sprintf("额外参数JSON格式化失败, %v", e)
-				return
-			}
-			edbInfoItem.Extra = string(b)
+		extra.Days = v.Days
+		extra.Period = v.Period
+		extra.PriceAdj = v.PriceAdj
+		extra.ApiExtraPars = v.ApiExtraPars
+		b, e := json.Marshal(extra)
+		if e != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = fmt.Sprintf("额外参数JSON格式化失败, %v", e)
+			return
 		}
+		edbInfoItem.Extra = string(b)
 
 		// 指标入库
 		edbInfo, err, errMsg, isSendEmail := data.EdbInfoWsdAdd(edbInfoItem)
@@ -6745,13 +6831,25 @@ func (this *EdbInfoController) ChartImageSetBySvg() {
 		br.Ret = 408
 		return
 	}
-
+	NotBackendGenerate,_ := this.GetBool("NotBackendGenerate",false)
 	imgData := this.GetString("Img")
 	if imgData == "" {
 		br.Msg = "图片参数错误"
 		br.ErrMsg = "图片参数错误,Img Is Empty"
 		return
 	}
+	if NotBackendGenerate {
+		b, _ := regexp.MatchString(`^data:\s*image\/(\w+);base64,`, imgData)
+		if !b {
+			br.Msg = "图片格式不正确"
+			br.ErrMsg = "图片格式不正确"
+			return
+		}
+		re, _ := regexp.Compile(`^data:\s*image\/(\w+);base64,`)
+		base64Str := re.ReplaceAllString(imgData, "")
+		base64Str = strings.Replace(base64Str, " ", "", -1)
+		imgData = base64Str
+	}
 	edbInfoId, _ := this.GetInt("EdbInfoId", 0)
 	if edbInfoId <= 0 {
 		br.Msg = "指标参数错误"
@@ -6760,7 +6858,7 @@ func (this *EdbInfoController) ChartImageSetBySvg() {
 	}
 
 	// 通过svg图片生成图片资源地址
-	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData)
+	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData,NotBackendGenerate)
 	if err != nil {
 		br.Msg = errMsg
 		br.ErrMsg = err.Error()

+ 2 - 2
controllers/data_manage/edb_info_refresh.go

@@ -757,7 +757,7 @@ func (c *EdbInfoController) SaveRelationEdbRefreshStatus() {
 		return
 	}
 	//var edbList []*data_manage.EdbInfo
-	// 如果是钢联化工,那么需要过滤供应商暂停的指标
+	// 如果是上海钢联,那么需要过滤供应商暂停的指标
 	if req.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
 		// 获取未被供应商暂停的指标
 		tmpEdbCodeList := make([]string, 0)
@@ -816,7 +816,7 @@ func (c *EdbInfoController) SaveRelationEdbRefreshStatus() {
 	}
 
 	switch req.Source {
-	case utils.DATA_SOURCE_MYSTEEL_CHEMICAL: // 钢联化工
+	case utils.DATA_SOURCE_MYSTEEL_CHEMICAL: // 上海钢联
 		err = data_manage.ModifyMysteelChemicalUpdateStatusByEdbInfoIds(edbIdList, isStop, edbCodeList, calculateEdbIdList)
 	default:
 		err = data_manage.EdbInfoUpdateStatusByEdbInfoId(edbIdList, isStop, calculateEdbIdList)

+ 1 - 1
controllers/data_manage/edb_info_relation.go

@@ -21,7 +21,7 @@ type EdbInfoRelationController struct {
 // RelationEdbList
 // @Title 获取被引用的指标列表接口
 // @Description 获取被引用的指标列表接口
-// @Param   Source   query   int  true       "来源:2:wind,34:钢联化工"
+// @Param   Source   query   int  true       "来源:2:wind,34:上海钢联"
 // @Param   ClassifyId   query   string  false             "分类ID,支持多选,用英文,隔开"
 // @Param   SysUserId   query   string  false       "创建人,支持多选,用英文,隔开"
 // @Param   Frequency   query   string  false       "频度,支持多选,用英文,隔开"

+ 394 - 0
controllers/data_manage/edb_inspection.go

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

+ 96 - 0
controllers/data_manage/edb_inspection_message.go

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

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

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

+ 75 - 41
controllers/data_manage/future_good/future_good_chart_info.go

@@ -18,6 +18,7 @@ import (
 	"fmt"
 	"os"
 	"os/exec"
+	"regexp"
 	"sort"
 	"strconv"
 	"strings"
@@ -2614,57 +2615,77 @@ func (this *FutureGoodChartInfoController) ChartInfoBase64Upload() {
 		}
 	}
 
+	NotBackendGenerate, _ := this.GetBool("NotBackendGenerate", false)
+	if NotBackendGenerate {
+		b, _ := regexp.MatchString(`^data:\s*image\/(\w+);base64,`, imgData)
+		if !b {
+			br.Msg = "图片格式不正确"
+			br.ErrMsg = "图片格式不正确"
+			return
+		}
+		re, _ := regexp.Compile(`^data:\s*image\/(\w+);base64,`)
+		base64Str := re.ReplaceAllString(imgData, "")
+		base64Str = strings.Replace(base64Str, " ", "", -1)
+		imgData = base64Str
+	}
 	//var saveToOssPath string
 	randStr := utils.GetRandStringNoSpecialChar(28)
 	var fileName, outFileName string
-	fileName = randStr + ".txt"
-	//saveSvgPath = uploadDir + fileName
-	err := utils.SaveToFile(imgData, fileName)
-	if err != nil {
-		br.Msg = "图片保存失败"
-		br.ErrMsg = "图片保存失败,Err:" + err.Error()
-		return
-	}
 	outFileName = randStr + ".png"
-
-	doneChannel := make(chan bool, 1)
-	errorChannel := make(chan error, 1)
-
-	cmd := exec.Command("highcharts-export-server", "--infile", fileName, "--constr", "Chart", "--scale", "2", "--workers", "10", "--workLimit", "3", "--outfile", outFileName)
-
-	go func() {
-		output, err := cmd.CombinedOutput()
+	if NotBackendGenerate {
+		err := utils.SaveBase64ToFile(imgData, outFileName)
 		if err != nil {
-			utils.FileLog.Info("execute command failed, output: , error: \n" + string(output) + err.Error())
-			errorChannel <- err
+			br.Msg = "图片保存失败"
+			br.ErrMsg = "图片保存失败,Err:" + err.Error()
 			return
 		}
-		doneChannel <- true
-	}()
+	} else {
+		fileName = randStr + ".txt"
+		//saveSvgPath = uploadDir + fileName
+		err := utils.SaveToFile(imgData, fileName)
+		if err != nil {
+			br.Msg = "图片保存失败"
+			br.ErrMsg = "图片保存失败,Err:" + err.Error()
+			return
+		}
+		doneChannel := make(chan bool, 1)
+		errorChannel := make(chan error, 1)
+		cmd := exec.Command("highcharts-export-server", "--infile", fileName, "--constr", "Chart", "--scale", "2", "--workers", "10", "--workLimit", "3", "--outfile", outFileName)
+		go func() {
+			output, err := cmd.CombinedOutput()
+			if err != nil {
+				utils.FileLog.Info("execute command failed, output: , error: \n" + string(output) + err.Error())
+				errorChannel <- err
+				return
+			}
+			doneChannel <- true
+		}()
 
-	select {
-	case <-time.After(30 * time.Second):
-		utils.FileLog.Info("执行超过30秒 杀死超时进程")
-		e := cmd.Process.Kill()
-		if e != nil {
-			fmt.Println("cmd kill err: ", e.Error())
-			utils.FileLog.Info(fmt.Sprintf("cmd kill err: %s", e.Error()))
-			br.Msg = "图片生成失败"
-			br.ErrMsg = "图片生成失败, 执行超时" + e.Error()
+		select {
+		case <-time.After(30 * time.Second):
+			utils.FileLog.Info("执行超过30秒 杀死超时进程")
+			e := cmd.Process.Kill()
+			if e != nil {
+				fmt.Println("cmd kill err: ", e.Error())
+				utils.FileLog.Info(fmt.Sprintf("cmd kill err: %s", e.Error()))
+				br.Msg = "图片生成失败"
+				br.ErrMsg = "图片生成失败, 执行超时" + e.Error()
+				return
+			}
+			fmt.Println("timeout kill process")
+		case <-doneChannel:
+			fmt.Println("done")
+		case err := <-errorChannel:
+			br.Msg = "文件上传失败"
+			br.ErrMsg = fmt.Sprintf("execute command failure err: %s", err.Error())
+			fmt.Println("execute command failure err:" + err.Error())
 			return
 		}
-		fmt.Println("timeout kill process")
-	case <-doneChannel:
-		fmt.Println("done")
-	case err := <-errorChannel:
-		br.Msg = "文件上传失败"
-		br.ErrMsg = fmt.Sprintf("execute command failure err: %s", err.Error())
-		fmt.Println("execute command failure err:" + err.Error())
-		return
+		defer func() {
+			os.Remove(fileName)
+		}()
 	}
-
 	defer func() {
-		os.Remove(fileName)
 		os.Remove(outFileName)
 	}()
 
@@ -2695,7 +2716,7 @@ func (this *FutureGoodChartInfoController) ChartInfoBase64Upload() {
 		br.ErrMsg = "初始化OSS服务失败"
 		return
 	}
-	resourceUrl, err = ossClient.UploadFile(outFileName, outFileName, "")
+	resourceUrl, err := ossClient.UploadFile(outFileName, outFileName, "")
 	if err != nil {
 		br.Msg = "文件上传失败"
 		br.ErrMsg = "文件上传失败,Err:" + err.Error()
@@ -3570,6 +3591,19 @@ func (this *FutureGoodChartInfoController) ChartInfoImgSetBySvg() {
 		br.ErrMsg = "图片参数错误,Img Is Empty"
 		return
 	}
+	NotBackendGenerate, _ := this.GetBool("NotBackendGenerate", false)
+	if NotBackendGenerate {
+		b, _ := regexp.MatchString(`^data:\s*image\/(\w+);base64,`, imgData)
+		if !b {
+			br.Msg = "图片格式不正确"
+			br.ErrMsg = "图片格式不正确"
+			return
+		}
+		re, _ := regexp.Compile(`^data:\s*image\/(\w+);base64,`)
+		base64Str := re.ReplaceAllString(imgData, "")
+		base64Str = strings.Replace(base64Str, " ", "", -1)
+		imgData = base64Str
+	}
 	chartInfoId, _ := this.GetInt("ChartInfoId", 0)
 	if chartInfoId <= 0 {
 		br.Msg = "图片参数错误"
@@ -3579,7 +3613,7 @@ func (this *FutureGoodChartInfoController) ChartInfoImgSetBySvg() {
 	resp := new(models.ResourceResp)
 
 	// 通过svg图片生成图片资源地址
-	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData)
+	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData, NotBackendGenerate)
 	if err != nil {
 		br.Msg = errMsg
 		br.ErrMsg = err.Error()

+ 9 - 5
controllers/data_manage/gpr_risk_data.go

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

+ 39 - 34
controllers/data_manage/mysteel_chemical_data.go

@@ -13,6 +13,7 @@ import (
 	etaTrialService "eta/eta_api/services/eta_trial"
 	"eta/eta_api/utils"
 	"fmt"
+	"github.com/shopspring/decimal"
 	"os"
 	"path/filepath"
 	"sort"
@@ -25,8 +26,8 @@ import (
 )
 
 // MysteelChemicalClassify
-// @Title 钢联化工数据分类
-// @Description 钢联化工分类接口
+// @Title 上海钢联数据分类
+// @Description 上海钢联分类接口
 // @Success 200 {object} data_manage.BaseFromMysteelChemicalClassifyItems
 // @router /mysteel_chemical/classify [get]
 func (this *EdbInfoController) MysteelChemicalClassify() {
@@ -90,8 +91,8 @@ func (this *EdbInfoController) MysteelChemicalClassify() {
 }
 
 // MysteelChemicalIndexList
-// @Title 钢联化工数据指标列表
-// @Description 钢联化工数据指标列表接口
+// @Title 上海钢联数据指标列表
+// @Description 上海钢联数据指标列表接口
 // @Success 200 {object} data_manage.BaseFromMysteelChemicalIndexResp
 // @router /mysteel_chemical/index/list [get]
 func (this *EdbInfoController) MysteelChemicalIndexList() {
@@ -181,7 +182,7 @@ func (this *EdbClassifyController) AddMysteelChemicalClassify() {
 		for _, v := range secondClassifyList {
 			_, _, tmpErrMsg := data.AddMysteelChemicalClassify(v, mysteelChemicalClassifyInfo.BaseFromMysteelChemicalClassifyId, mysteelChemicalClassifyInfo.Level, this.SysUser.AdminId, this.SysUser.RealName, this.Lang)
 			if tmpErrMsg != `` {
-				go alarm_msg.SendAlarmMsg("钢联化工-添加一级分类时,默认添加二级分类失败,一级分类名称:"+req.ClassifyName+",二级分类名称:"+v+", Err:"+tmpErrMsg, 3)
+				go alarm_msg.SendAlarmMsg("上海钢联-添加一级分类时,默认添加二级分类失败,一级分类名称:"+req.ClassifyName+",二级分类名称:"+v+", Err:"+tmpErrMsg, 3)
 				return
 			}
 		}
@@ -343,8 +344,8 @@ func (this *EdbClassifyController) DeleteMysteelChemicalClassify() {
 }
 
 // MysteelChemicalSearch
-// @Title 钢联化工指标查询
-// @Description 钢联化工指标查询
+// @Title 上海钢联指标查询
+// @Description 上海钢联指标查询
 // @Param   BaseFromMysteelChemicalClassifyId   query   int  true       "分类id"
 // @Param   Keyword   query   string  true       "名称关键词"
 // @Success 200 {object} data_manage.LzFrequency
@@ -416,10 +417,10 @@ func (this *EdbInfoController) MysteelChemicalSearch() {
 }
 
 // MysteelChemicalFrequency
-// @Title 钢联化工数据频度
-// @Description 钢联化工数据频度接口
+// @Title 上海钢联数据频度
+// @Description 上海钢联数据频度接口
 // @Param   BaseFromMysteelChemicalClassifyId   query   int  true       "分类id"
-// @Param   BaseFromMysteelChemicalIndexId   query   int  true       "钢联化工指标id"
+// @Param   BaseFromMysteelChemicalIndexId   query   int  true       "上海钢联指标id"
 // @Param   Keyword   query   string  true       "名称关键词"
 // @Success 200 {object} data_manage.LzFrequency
 // @router /mysteel_chemical/frequency [get]
@@ -495,10 +496,10 @@ func (this *EdbInfoController) MysteelChemicalFrequency() {
 }
 
 // MysteelChemicalData
-// @Title 获取钢联化工数据
-// @Description 获取钢联化工数据
+// @Title 获取上海钢联数据
+// @Description 获取上海钢联数据
 // @Param   BaseFromMysteelChemicalClassifyId   query   int  true       "分类id"
-// @Param   BaseFromMysteelChemicalIndexId   query   int  true       "钢联化工指标id"
+// @Param   BaseFromMysteelChemicalIndexId   query   int  true       "上海钢联指标id"
 // @Param   Frequency   query   string  true       "频度名称"
 // @Param   Keyword   query   string  true       "名称关键词"
 // @Param   PageSize   query   int  true       "每页数据条数"
@@ -584,14 +585,18 @@ func (this *EdbInfoController) MysteelChemicalData() {
 		total, err := data_manage.GetMysteelChemicalIndexDataCount(v.IndexCode)
 		page := paging.GetPaging(currentIndex, pageSize, total)
 
-		dataList, err := data_manage.GetMysteelChemicalIndexData(v.IndexCode, startSize, pageSize)
+		dataList := make([]*data_manage.MysteelChemicalData, 0)
+		tmpDataList, err := data_manage.GetMysteelChemicalIndexData(v.IndexCode, startSize, pageSize)
 		if err != nil {
 			br.Msg = "获取数据失败"
 			br.ErrMsg = "获取指标数据失败,Err:" + err.Error()
 			return
 		}
-		if dataList == nil {
-			dataList = make([]*data_manage.MysteelChemicalData, 0)
+		for _, tmpData := range tmpDataList {
+			dataList = append(dataList, &data_manage.MysteelChemicalData{
+				DataTime:   tmpData.DataTime,
+				InputValue: decimal.NewFromFloat(tmpData.InputValue).String(),
+			})
 		}
 		edbInfo, err := data_manage.GetEdbInfoByEdbCode(utils.DATA_SOURCE_MYSTEEL_CHEMICAL, v.IndexCode)
 		if err != nil && !utils.IsErrNoRow(err) {
@@ -615,10 +620,10 @@ func (this *EdbInfoController) MysteelChemicalData() {
 }
 
 // MysteelChemicalExport
-// @Title 导出钢联化工数据
-// @Description 导出钢联化工数据
+// @Title 导出上海钢联数据
+// @Description 导出上海钢联数据
 // @Param   BaseFromMysteelChemicalClassifyId   query   int  true       "分类"
-// @Param   BaseFromMysteelChemicalIndexId   query   string  true       "钢联化工指标id"
+// @Param   BaseFromMysteelChemicalIndexId   query   string  true       "上海钢联指标id"
 // @Param   Keyword   query   string  true       "名称关键词"
 // @Success 200  导出成功
 // @router /mysteel_chemical/export/dataList [get]
@@ -761,7 +766,7 @@ func (this *EdbClassifyController) MysteelChemicalExport() {
 				return
 			}
 			if k == 0 {
-				windRow.AddCell().SetValue("钢联")
+				windRow.AddCell().SetValue("上海钢联")
 				secNameRow.AddCell().SetValue("指标名称")
 				indexCodeRow.AddCell().SetValue("指标ID")
 				frequencyRow.AddCell().SetValue("频率")
@@ -827,8 +832,8 @@ func (this *EdbClassifyController) MysteelChemicalExport() {
 }
 
 // AddMysteelChemical
-// @Title 新增钢联化工指标
-// @Description 新增钢联化工指标接口
+// @Title 新增上海钢联指标
+// @Description 新增上海钢联指标接口
 // @Param	request	body data_manage.AddEdbClassifyReq true "type json string"
 // @Success 200 Ret=200 保存成功
 // @router /mysteel_chemical/add [post]
@@ -853,7 +858,7 @@ func (this *EdbClassifyController) AddMysteelChemical() {
 		br.ErrMsg = "获取业务配置失败,Err:" + err.Error()
 		return
 	}
-	// 判断钢联化工的数据刷新方式
+	// 判断上海钢联的数据刷新方式
 	if conf["MySteelDataMethod"] == "api" {
 		if len(req.List) > 150 {
 			br.Msg = "添加指标失败,指标数量不能超过150条"
@@ -862,7 +867,7 @@ func (this *EdbClassifyController) AddMysteelChemical() {
 		ok, errMsg, err := data.HealthCheckMysteelChemicalApi()
 		if err != nil {
 			br.Msg = "添加指标失败"
-			br.ErrMsg = "钢联化工数据接口异常,Err:" + err.Error()
+			br.ErrMsg = "上海钢联数据接口异常,Err:" + err.Error()
 			return
 		}
 		if !ok {
@@ -1010,8 +1015,8 @@ func sortEdbFrequency(frequencyList []string) (newFrequencyList []string) {
 }
 
 // EditMysteelChemical
-// @Title 编辑钢联化工指标
-// @Description 新增钢联化工指标接口
+// @Title 编辑上海钢联指标
+// @Description 新增上海钢联指标接口
 // @Param	request	body data_manage.AddEdbClassifyReq true "type json string"
 // @Success 200 Ret=200 保存成功
 // @router /mysteel_chemical/edit [post]
@@ -1168,9 +1173,9 @@ func (this *EdbClassifyController) MoveMysteelChemical() {
 }
 
 // MysteelChemicalDetail
-// @Title 钢联化工指标详情
-// @Description 钢联化工指标详情接口
-// @Param   BaseFromMysteelChemicalIndexId   query   string  true       "钢联化工指标id"
+// @Title 上海钢联指标详情
+// @Description 上海钢联指标详情接口
+// @Param   BaseFromMysteelChemicalIndexId   query   string  true       "上海钢联指标id"
 // @Success 200 {object} data_manage.BaseFromMysteelChemicalClassifyItems
 // @router /mysteel_chemical/detail [get]
 func (this *EdbInfoController) MysteelChemicalDetail() {
@@ -1211,9 +1216,9 @@ func (this *EdbInfoController) MysteelChemicalDetail() {
 }
 
 // MysteelChemicalDetail
-// @Title 钢联化工指标详情
-// @Description 钢联化工指标详情接口
-// @Param   BaseFromMysteelChemicalIndexId   query   string  true       "钢联化工指标id"
+// @Title 上海钢联指标详情
+// @Description 上海钢联指标详情接口
+// @Param   BaseFromMysteelChemicalIndexId   query   string  true       "上海钢联指标id"
 // @Success 200 {object} data_manage.BaseFromMysteelChemicalClassifyItems
 // @router /mysteel_chemical/refresh [get]
 func (this *EdbInfoController) MysteelChemicalRefresh() {
@@ -1746,8 +1751,8 @@ func (c *EdbInfoController) AddCheck() {
 }
 
 // MysteelChemicalBatchSearch
-// @Title 钢联化工指标查询
-// @Description 钢联化工指标查询
+// @Title 上海钢联指标查询
+// @Description 上海钢联指标查询
 // @Param   BaseFromMysteelChemicalClassifyIds   query   string  true       "分类id"
 // @Param   Keyword   query   string  true       "名称关键词"
 // @Success 200 {object} data_manage.LzFrequency

+ 17 - 2
controllers/data_manage/predict_edb_info.go

@@ -13,6 +13,7 @@ import (
 	"eta/eta_api/services/elastic"
 	"eta/eta_api/utils"
 	"fmt"
+	"regexp"
 	"sort"
 	"strconv"
 	"strings"
@@ -933,7 +934,7 @@ func (this *PredictEdbInfoController) Detail() {
 		}
 		for _, v := range tmpPredictEdbConfList {
 			var tmpPredictEdbConfCalculateMappingDetail []*data_manage.PredictEdbConfCalculateMappingDetail
-			if v.RuleType == 9 || v.RuleType == 14 {
+			if v.RuleType == 9 || v.RuleType == 14 || v.RuleType == 17 || v.RuleType == 18 {
 				tmpPredictEdbConfCalculateMappingDetail, err = data_manage.GetPredictEdbConfCalculateMappingDetailListByConfigId(v.PredictEdbInfoId, v.ConfigId)
 				if err != nil && !utils.IsErrNoRow(err) {
 					br.Msg = "获取失败"
@@ -2183,6 +2184,20 @@ func (this *PredictEdbInfoController) ChartImageSetBySvg() {
 		br.ErrMsg = "图片参数错误,Img Is Empty"
 		return
 	}
+	NotBackendGenerate, _ := this.GetBool("NotBackendGenerate", false)
+	if NotBackendGenerate {
+		b, _ := regexp.MatchString(`^data:\s*image\/(\w+);base64,`, imgData)
+		if !b {
+			br.Msg = "图片格式不正确"
+			br.ErrMsg = "图片格式不正确"
+			return
+		}
+		re, _ := regexp.Compile(`^data:\s*image\/(\w+);base64,`)
+		base64Str := re.ReplaceAllString(imgData, "")
+		base64Str = strings.Replace(base64Str, " ", "", -1)
+		imgData = base64Str
+	}
+
 	edbInfoId, _ := this.GetInt("EdbInfoId", 0)
 	if edbInfoId <= 0 {
 		br.Msg = "指标参数错误"
@@ -2191,7 +2206,7 @@ func (this *PredictEdbInfoController) ChartImageSetBySvg() {
 	}
 
 	// 通过svg图片生成图片资源地址
-	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData)
+	resourceUrl, err, errMsg := services.GetResourceUrlBySvgImg(imgData,NotBackendGenerate)
 	if err != nil {
 		br.Msg = errMsg
 		br.ErrMsg = err.Error()

+ 11 - 5
controllers/data_manage/purang_data.go

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

+ 65 - 12
controllers/data_stat/edb_source_stat.go

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

+ 238 - 7
controllers/data_stat/edb_terminal.go

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

+ 74 - 10
controllers/edb_monitor/edb_monitor_message.go

@@ -3,10 +3,13 @@ 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"
+	"fmt"
 	"net/http"
 	"strconv"
 	"time"
@@ -27,11 +30,11 @@ var upgrader = websocket.Upgrader{
 }
 
 // GetMonitorLevel
-// @Title 预警管理消息
+// @Title 预警管理消息 弃用
 // @Description 预警管理消息
 // @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
 // @Success 200 {object} models.EnglishReportEmailPageListResp
-// @router /message/connect [get]
+// @router /message/connectV1 [get]
 func (m *EdbMonitorMessageController) Connect() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
@@ -47,10 +50,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 +72,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 +83,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 +104,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 +130,8 @@ func (m *EdbMonitorMessageController) Connect() {
 		}
 	}()
 
+	// 其他消息处理
+	services.DealWebSocketMsg(sysUser.AdminId)
 	for {
 		ok = utils.Rc.IsExist(connKey)
 		if !ok {
@@ -138,6 +144,64 @@ func (m *EdbMonitorMessageController) Connect() {
 	}
 }
 
+
+// GetMonitorLevel
+// @Title 预警管理消息
+// @Description 预警管理消息
+// @Param   request body request.EdbMonitorSaveRequest  true  "每页数据条数"
+// @Success 200 {object} models.EnglishReportEmailPageListResp
+// @router /message/connect [get]
+func (m *EdbMonitorMessageController) ConnectV2() {
+	// 不要在WebSocket连接中使用defer m.ServeJSON(),因为连接已被劫持
+	sysUser := m.SysUser
+	if sysUser == nil {
+		// 在升级连接前处理错误
+		br := new(models.BaseResponse).Init()
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		m.Data["json"] = br
+		m.ServeJSON()
+		return
+	}
+	
+	oldConn := global.AdminWebSocketConnMap[sysUser.AdminId]
+	if oldConn != nil {
+		oldConn.Close()
+	}
+	
+    // 建立长连接
+	var conn *websocket.Conn
+	var err error
+	conn, err = upgrader.Upgrade(m.Ctx.ResponseWriter, m.Ctx.Request, nil)
+	if err != nil {
+		// 在升级连接失败时处理错误
+		br := new(models.BaseResponse).Init()
+		br.Msg = "连接失败"
+		br.ErrMsg = "连接失败,err:" + err.Error()
+		m.Data["json"] = br
+		m.ServeJSON()
+		return
+	}
+	
+	// 为新连接创建唯一ID
+	connID := fmt.Sprintf("%s-%d", conn.RemoteAddr().String(), time.Now().UnixNano())
+			
+	// 创建新的连接对象
+	handlers := make([]func(adminId int)(err error), 0)
+	//handlers = append(handlers, edbmonitor.AutoCheckMonitorMessageListByAdminId, services.AutoCheckInspectionMessageListByAdminId)
+	//每次建立连接都启动一个协程长时间的监听,容易导致数据库连接被占满,或者超时,所以系统启动时,单独开启一个协程去监听消息
+	connection := global.NewWebSocketConn(conn, connID, sysUser.AdminId, handlers)
+	
+	// 保存连接
+	global.WebSocketConnMap[connID] = connection
+	
+	// 启动连接的协程
+	connection.Start()
+	
+	// 注意:不要在这里使用defer conn.Close(),因为连接会在WebSocketConn.Close()中关闭
+	// 连接将保持开放状态,直到客户端断开连接或通过其他方法关闭
+}
 // Close
 // @Title 预警管理消息
 // @Description 预警管理消息
@@ -158,7 +222,7 @@ func (m *EdbMonitorMessageController) Close() {
 		return
 	}
 
-	conn := edbmonitor.MonitorMessageConn[sysUser.AdminId]
+	conn := global.AdminWebSocketConnMap[sysUser.AdminId]
 	if conn != nil {
 		conn.Close()
 	}

+ 1 - 0
controllers/eta_forum/eta_forum.go

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

+ 13 - 4
controllers/material/material.go

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

+ 19 - 2
controllers/message.go

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

+ 136 - 18
controllers/report_chapter.go

@@ -8,11 +8,13 @@ import (
 	"eta/eta_api/services"
 	"eta/eta_api/services/data"
 	"eta/eta_api/utils"
+	"fmt"
 	"html"
 	"os"
 	"path"
 	"strconv"
 	"strings"
+	"sync"
 	"time"
 )
 
@@ -159,7 +161,6 @@ func (this *ReportController) AddChapter() {
 	//reportChapterInfo.CanvasColor = req.CanvasColor
 	//reportChapterInfo.HeadResourceId = req.HeadResourceId
 	//reportChapterInfo.EndResourceId = req.EndResourceId
-
 	err, errMsg := services.AddChapterBaseInfoAndPermission(reportInfo, reportChapterInfo, req.PermissionIdList, req.AdminIdList)
 	if err != nil {
 		br.Msg = "保存失败"
@@ -169,7 +170,9 @@ func (this *ReportController) AddChapter() {
 		br.ErrMsg = "保存失败,Err:" + err.Error()
 		return
 	}
-
+	if reportInfo.ReportLayout == 3 {
+		br.Data = reportInfo.FreeLayoutConfig
+	}
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "保存成功"
@@ -311,10 +314,6 @@ func (this *ReportController) EditDayWeekChapter() {
 		br.Msg = "报告章节ID有误"
 		return
 	}
-	if req.Content == "" {
-		br.Msg = "请输入内容"
-		return
-	}
 
 	// 获取章节详情
 	reportChapterInfo, err := models.GetReportChapterInfoById(reportChapterId)
@@ -331,7 +330,14 @@ func (this *ReportController) EditDayWeekChapter() {
 		br.ErrMsg = "报告信息有误, Err: " + err.Error()
 		return
 	}
-
+	if req.Content == "" && reportInfo.ReportLayout != 3 {
+		br.Msg = "请输入内容"
+		return
+	}
+	if reportInfo.ReportLayout == 3 && req.FreeLayoutConfig == "" {
+		br.Msg = "请输入自由布局配置"
+		return
+	}
 	// 操作权限校验
 	hasAuth, msg, errMsg, isSendEmail := checkOpPermission(sysUser, reportInfo, reportChapterInfo, true, this.Lang)
 	if !hasAuth {
@@ -431,11 +437,57 @@ func (this *ReportController) EditDayWeekChapter() {
 			})
 		}
 	}
-	err = models.UpdateChapterAndTicker(reportInfo, reportChapterInfo, updateCols, tickerList)
-	if err != nil {
-		br.Msg = "保存失败"
-		br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
-		return
+	if reportInfo.ReportLayout == 3 {
+		//对自由布局的数据做一个处理
+		//自由布局更新每页的数据
+		ormList := report.ToOrmViewList(req.FreeLayoutContentPages, true, reportInfo.Id, reportChapterId)
+		var wg sync.WaitGroup
+		wg.Add(len(ormList))
+		for _, v := range ormList {
+			go func(v *report.ReportFreeLayout) {
+				defer wg.Done()
+				content := v.Content
+				if content != "" {
+					// 处理关联excel的表格id
+					content = services.HandleReportContentTable(reportInfo.Id, content)
+					content = services.HandleReportContent(content, "del", nil)
+					e := utils.ContentXssCheck(content)
+					if e != nil {
+						br.Msg = "存在非法标签"
+						br.ErrMsg = "存在非法标签, Err: " + e.Error()
+						return
+					}
+					contentClean, e := services.FilterReportContentBr(content)
+					if e != nil {
+						br.Msg = "内容去除前后空格失败"
+						br.ErrMsg = "内容去除前后空格失败, Err: " + e.Error()
+						return
+					}
+					content = contentClean
+					if v.ContentStruct != `` {
+						v.ContentStruct = services.HandleReportContentStructTable(reportChapterInfo.ReportId, v.ContentStruct)
+						v.ContentStruct = services.HandleReportContentStruct(v.ContentStruct, "del", nil)
+					}
+					v.Content = html.EscapeString(content)
+					v.ContentStruct = html.EscapeString(v.ContentStruct)
+				}
+			}(v)
+		}
+		wg.Wait()
+		reportInfo.FreeLayoutConfig = req.FreeLayoutConfig
+		err = models.UpdateChapterFreeLayoutContentPage(reportInfo, reportChapterInfo, updateCols, tickerList, ormList)
+		if err != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "保存失败,Err:" + err.Error()
+			return
+		}
+	} else {
+		err = models.UpdateChapterAndTicker(reportInfo, reportChapterInfo, updateCols, tickerList)
+		if err != nil {
+			br.Msg = "保存失败"
+			br.ErrMsg = "报告章节内容保存失败, Err: " + err.Error()
+			return
+		}
 	}
 
 	// 标记更新中
@@ -537,7 +589,9 @@ func (this *ReportController) DelChapter() {
 		br.ErrMsg = "删除失败,Err:" + err.Error()
 		return
 	}
-
+	if reportInfo.ReportLayout == 3 {
+		go report.DeleteChapters(reportInfo.Id, reportChapterInfo.ReportChapterId)
+	}
 	// 备份关键数据
 	chapters := make([]*models.ReportChapter, 0)
 	chapters = append(chapters, reportChapterInfo)
@@ -778,13 +832,38 @@ func (this *ReportController) GetDayWeekChapter() {
 		br.ErrMsg = "无操作权限"
 		return
 	}
+	var pageNum int
+	var pages []*report.ContentPage
+	if reportInfo.ReportLayout == 3 {
+		pages, err = report.GetSingleFreeLayoutChapterPagesByReportId(reportInfo.Id, reportChapterId)
+		if err != nil {
+			br.Msg = "获取自由布局页面列表"
+			br.ErrMsg = "获取自由布局页面列表,Err:" + err.Error()
+			return
+		}
+		if len(pages) == 0 {
+			//获取当前章节前置章节总页数
+			pageNum, err = report.GetPrevFreeLayoutChaptersPagesByChapterId(reportInfo.Id, reportChapterId)
+			if err != nil {
+				br.Msg = "获取自由布局前置章节总页数"
+				br.ErrMsg = "获取自由布局前置章节总页数,Err:" + err.Error()
+				return
+			}
 
+		} else {
+			for _, page := range pages {
+				page.Content = html.UnescapeString(page.Content)
+				page.ContentStruct = html.UnescapeString(page.ContentStruct)
+				page.Content = services.HandleReportContentTable(page.ReportId, page.Content)
+				page.ContentStruct = services.HandleReportContentStructTable(page.ReportId, page.ContentStruct)
+			}
+		}
+	}
 	chapterItem.Content = html.UnescapeString(chapterItem.Content)
 	chapterItem.ContentSub = html.UnescapeString(chapterItem.ContentSub)
 	chapterItem.ContentStruct = html.UnescapeString(chapterItem.ContentStruct)
 	chapterItem.Content = services.HandleReportContentTable(chapterItem.ReportId, chapterItem.Content)
 	chapterItem.ContentStruct = services.HandleReportContentStructTable(chapterItem.ReportId, chapterItem.ContentStruct)
-
 	businessConf, err := models.GetBusinessConfByKey(models.BusinessConfIsOpenChartExpired)
 	if err != nil {
 		br.Msg = "获取失败"
@@ -796,6 +875,12 @@ func (this *ReportController) GetDayWeekChapter() {
 		tokenMap := make(map[string]string)
 		chapterItem.Content = services.HandleReportContent(chapterItem.Content, "add", tokenMap)
 		chapterItem.ContentStruct = services.HandleReportContentStruct(chapterItem.ContentStruct, "add", tokenMap)
+		if reportInfo.ReportLayout == 3 {
+			for _, page := range pages {
+				page.Content = services.HandleReportContent(page.Content, "add", tokenMap)
+				page.ContentStruct = services.HandleReportContentStruct(page.ContentStruct, "add", tokenMap)
+			}
+		}
 	}
 
 	// 授权用户列表map
@@ -833,9 +918,13 @@ func (this *ReportController) GetDayWeekChapter() {
 	}
 
 	resp := models.ReportChapterItemResp{
-		ReportChapterItem: *chapterItem,
-		GrandAdminIdList:  chapterGrantIdList,
-		PermissionIdList:  chapterPermissionIdList,
+		FreeLayoutContentPages: pages,
+		FreeLayoutConfig:       reportInfo.FreeLayoutConfig,
+		PreviousPagesNum:       pageNum,
+		ReportChapterItem:      *chapterItem,
+		GrandAdminIdList:       chapterGrantIdList,
+		PermissionIdList:       chapterPermissionIdList,
+		FreeReportRatio:        reportInfo.FreeReportRatio,
 	}
 
 	// 获取当前编辑状态
@@ -887,11 +976,31 @@ func (this *ReportController) ChapterMove() {
 		br.ErrMsg = "参数解析失败,Err:" + e.Error()
 		return
 	}
-
 	if req.ReportChapterId == 0 {
 		br.Msg = "请选择要移动的章节"
 		return
 	}
+	chapter, e := models.GetReportChapterInfoById(req.ReportChapterId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "章节不存在,请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取报告章节失败, %v", e)
+		return
+	}
+	reportItem, e := models.GetReportByReportId(chapter.ReportId)
+	if e != nil {
+		if e.Error() == utils.ErrNoRow() {
+			br.Msg = "报告不存在,请刷新页面"
+			return
+		}
+		br.Msg = "操作失败"
+		br.ErrMsg = fmt.Sprintf("获取报告失败, %v", e)
+		return
+	}
+
 	e, msg := services.MoveReportChapter(&req)
 	if e != nil {
 		br.Msg = msg
@@ -899,6 +1008,15 @@ func (this *ReportController) ChapterMove() {
 		return
 	}
 
+	// 如果报告的布局是自由布局,那么查询下涉及移动的章节是否均已发布,如果均已发布那么需要重新排序页脚(未发布时会重新排不用管)
+	if reportItem.ReportLayout == 3 {
+		if e = services.ResortFreeChapter(reportItem.Id); e != nil {
+			br.Msg = "操作失败"
+			br.ErrMsg = fmt.Sprintf("自由布局章节重新排序失败, %v", e)
+			return
+		}
+	}
+
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "操作成功"

+ 200 - 48
controllers/report_v2.go

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

+ 6 - 1
controllers/sandbox/sandbox.go

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

+ 8 - 0
controllers/sys_role.go

@@ -724,6 +724,14 @@ func (this *SysRoleController) SystemConfig() {
 	}, system.BusinessConf{
 		ConfKey: "LoginUrl",
 		ConfVal: conf["LoginUrl"],
+	},
+        system.BusinessConf{
+        ConfKey: "KnowledgeBaseName",
+    	ConfVal: conf["KnowledgeBaseName"],
+    },  system.BusinessConf{
+			ConfKey: "NotBackendGenerate",
+			ConfVal: conf["NotBackendGenerate"],
+
 	}, system.BusinessConf{
 		ConfKey: models.KnowledgeBaseName,
 		ConfVal: conf[models.KnowledgeBaseName],

+ 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

+ 207 - 0
global/websocket.go

@@ -0,0 +1,207 @@
+package global
+
+import (
+	"context"
+	"errors"
+	"eta/eta_api/utils"
+	"strconv"
+	"sync"
+	"time"
+
+	"github.com/gorilla/websocket"
+)
+
+var MonitorMessageConn = make(map[int]*websocket.Conn)
+
+var WebSocketConnMap = make(map[string]*WebSocketConn)
+
+var AdminWebSocketConnMap = make(map[int]*WebSocketConn)
+
+var WebSocketConnAdminIdMap = make(map[string]int)
+
+type WebSocketConn struct {
+	Conn *websocket.Conn
+	id   string
+	ctx  context.Context
+	cancel context.CancelFunc
+	wg        sync.WaitGroup
+	sendChan  chan []byte
+	closeChan chan struct{}
+	monitorHandlers []func(adminId int)(err error)
+	adminId int
+}
+
+// NewConnection 创建一个新的连接对象
+func NewWebSocketConn(conn *websocket.Conn, id string, adminId int, monitorHandlers []func(adminId int)(err error)) *WebSocketConn {
+	WebSocketConnAdminIdMap[id] = adminId
+	ctx, cancel := context.WithCancel(context.Background())
+	webSocketConn := &WebSocketConn{
+		Conn:      conn,
+		id:        id,
+		ctx:       ctx,
+		cancel:    cancel,
+		sendChan:  make(chan []byte, 100), // 发送消息的缓冲通道
+		closeChan: make(chan struct{}),
+		adminId:   adminId,
+	}
+	AdminWebSocketConnMap[adminId] = webSocketConn
+	webSocketConn.monitorHandlers = monitorHandlers
+	return webSocketConn
+}
+
+// Start 启动连接的读写协程
+func (c *WebSocketConn) Start() {
+	utils.FileLog.Info("客户端 %s 已连接", c.id)
+
+	// 启动读协程
+	c.wg.Add(1)
+	go c.readLoop()
+	
+	// 启动写协程
+	c.wg.Add(1)
+	go c.writeLoop()
+
+	if len(c.monitorHandlers) > 0 {
+		// 启动消息监听协程
+		c.wg.Add(1)
+		go c.MonitorMessageHandler(c.adminId)
+	}
+}
+
+// Close 关闭连接并清理资源
+func (c *WebSocketConn) Close() {
+	utils.FileLog.Info("关闭与客户端 %s 的连接", c.id)
+	c.cancel() // 通知所有协程退出
+	close(c.closeChan)
+	c.Conn.Close()
+	c.wg.Wait() // 等待所有协程退出
+	close(c.sendChan)
+	// 从adminWebSocketConnMap中删除
+	delete(AdminWebSocketConnMap, WebSocketConnAdminIdMap[c.id])
+	delete(WebSocketConnMap, c.id)
+	utils.FileLog.Info("客户端 %s 的所有协程已清理完毕", c.id)
+}
+
+// Send 发送消息到客户端
+func (c *WebSocketConn) Send(msg []byte) bool {
+	select {
+	case c.sendChan <- msg:
+		return true
+	case <-c.ctx.Done():
+		return false
+	default: // 通道已满,表示客户端处理速度慢
+		utils.FileLog.Info("客户端 %s 处理速度过慢,丢弃消息", c.id)
+		return false
+	}
+}
+
+// readLoop 处理来自客户端的消息
+func (c *WebSocketConn) readLoop() {
+	defer c.wg.Done()
+	defer utils.FileLog.Info("客户端 %s 的读协程已退出", c.id)
+
+	for {
+		select {
+		case <-c.ctx.Done():
+			return
+		default:
+			// 设置读取超时
+			_, msg, err := c.Conn.ReadMessage()
+			if err != nil {
+				utils.FileLog.Error("从客户端 %s 读取失败: %v", c.id, err)
+				c.cancel()
+				return
+			}
+			message := string(msg)
+			// 处理消息
+			//utils.FileLog.Info("收到客户端 %s 消息: %s", c.id, message)
+			
+			// 启动一个新的协程处理消息,避免阻塞读取循环
+			go c.handleMessage(message)
+		}
+	}
+}
+
+// handleMessage 处理收到的消息
+func (c *WebSocketConn) handleMessage(message string) {
+	defer utils.FileLog.Info("处理来自客户端 %s 的消息: %s, 结束", c.id, message)
+	// 模拟消息处理
+	//utils.FileLog.Info("处理来自客户端 %s 的消息: %s", c.id, message)
+	
+	// 如果是PING消息,回复PONG
+	if message == "ping\n" {
+		//c.Send([]byte("PONG\n"))
+		return
+	}
+	
+	// 回复消息
+	//response := fmt.Sprintf("已收到消息: %s", message)
+	//c.Send([]byte(response))
+}
+
+// writeLoop 发送消息到客户端
+func (c *WebSocketConn) writeLoop() {
+	defer c.wg.Done()
+	defer utils.FileLog.Info("客户端 %s 的写协程已退出", c.id)
+
+	for {
+		select {
+		case <-c.ctx.Done():
+			return
+		case msg, ok := <-c.sendChan:
+			if !ok {
+				return
+			}
+			err := c.Conn.WriteMessage(websocket.TextMessage, msg)
+			if err != nil {
+				utils.FileLog.Error("向客户端 %s 写入失败: %v", c.id, err)
+				c.cancel()
+				return
+			}
+		}
+	}
+}
+
+func (c *WebSocketConn) MonitorMessageHandler(adminId int) {
+	defer c.wg.Done()
+	defer utils.FileLog.Info("客户端 %s 的消息监控协程已退出", c.id)
+	for {
+		select {
+		case <-c.ctx.Done():
+			return
+		default:
+			time.Sleep(time.Second * 10)
+			for _, handler := range c.monitorHandlers {
+				//utils.FileLog.Info("处理注册的消息监控函数")
+				handler(c.adminId)
+			}
+		}
+
+	}
+}
+
+var (
+	EDB_MONITOR_MESSAGE_CONNECT_CACHE = "edb_monitor_message_cache:"
+)
+
+func EdbMonitorMessageHealth(adminId int) (isClose bool, err error) {
+	conn := MonitorMessageConn[adminId]
+	if conn == nil {
+		err = errors.New("no connection")
+		isClose = true
+		return
+	}
+	_, msg, err := conn.ReadMessage()
+	if err != nil {
+		isClose = true
+		return
+	}
+	if string(msg) == "ping" {
+		healthKey := EDB_MONITOR_MESSAGE_CONNECT_CACHE + strconv.Itoa(adminId)
+		err = utils.Rc.Put(healthKey, "1", time.Minute*1)
+		if err != nil {
+			return
+		}
+	}
+	return
+}

+ 1 - 0
models/bi_dashboard/bi_dashboard.go

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

+ 1 - 0
models/bi_dashboard/bi_dashboard_detail.go

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

+ 12 - 12
models/data_manage/base_from_gpr_risk.go

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

+ 55 - 55
models/data_manage/base_from_purang.go

@@ -14,39 +14,39 @@ import (
 
 type BaseFromPurangIndex struct {
 	BaseFromPurangIndexId int `orm:"column(base_from_purang_index_id);pk"`
-	ClassifyId             int
-	IndexCode              string
-	IndexName              string
-	Frequency              string
-	Unit                   string
-	Sort                   int
-	StartDate              time.Time `description:"开始日期"`
-	EndDate                time.Time `description:"结束日期"`
-	EndValue               float64
-	CreateTime             time.Time
-	ModifyTime             time.Time
+	ClassifyId            int
+	IndexCode             string
+	IndexName             string
+	Frequency             string
+	Unit                  string
+	Sort                  int
+	StartDate             time.Time `description:"开始日期"`
+	EndDate               time.Time `description:"结束日期"`
+	EndValue              float64
+	CreateTime            time.Time
+	ModifyTime            time.Time
 }
 
 type BaseFromPurangIndexList struct {
 	BaseFromPurangIndexId int `orm:"column(base_from_purang_index_id);pk"`
-	ClassifyId             int
-	Interface              string
-	EdbInfoId              int
-	EdbUniqueCode          string `description:"指标库唯一编码"`
-	EdbClassifyId          int    `description:"指标库分类ID"`
-	StartDate              string
-	EndDate                string
-	EndValue               float64
-	IndexCode              string
-	IndexName              string
-	Frequency              string
-	Unit                   string
-	Sort                   int
-	CreateTime             string
-	ModifyTime             string
-	EdbExist               int                    `description:"指标库是否已添加:0-否;1-是"`
-	DataList               []*BaseFromPurangData `gorm:"-"`
-	Paging                 *paging.PagingItem     `description:"分页数据" gorm:"-"`
+	ClassifyId            int
+	Interface             string
+	EdbInfoId             int
+	EdbUniqueCode         string `description:"指标库唯一编码"`
+	EdbClassifyId         int    `description:"指标库分类ID"`
+	StartDate             string
+	EndDate               string
+	EndValue              float64
+	IndexCode             string
+	IndexName             string
+	Frequency             string
+	Unit                  string
+	Sort                  int
+	CreateTime            string
+	ModifyTime            string
+	EdbExist              int                   `description:"指标库是否已添加:0-否;1-是"`
+	DataList              []*BaseFromPurangData `gorm:"-"`
+	Paging                *paging.PagingItem    `description:"分页数据" gorm:"-"`
 }
 
 func (baseFromPurangIndexList *BaseFromPurangIndexList) AfterFind(tx *gorm.DB) (err error) {
@@ -64,18 +64,18 @@ type BaseFromPurangIndexSearchList struct {
 
 type PurangSingleDataResp struct {
 	BaseFromPurangIndexId int
-	ClassifyId             int
-	EdbInfoId              int
-	IndexCode              string
-	IndexName              string
-	Frequency              string
-	Unit                   string
-	StartTime              string
-	CreateTime             string
-	ModifyTime             string
-	EdbExist               int                  `description:"指标库是否已添加:0-否;1-是"`
-	Data                   []*PurangSingleData `gorm:"-"`
-	Paging                 *paging.PagingItem   `description:"分页数据" gorm:"-"`
+	ClassifyId            int
+	EdbInfoId             int
+	IndexCode             string
+	IndexName             string
+	Frequency             string
+	Unit                  string
+	StartTime             string
+	CreateTime            string
+	ModifyTime            string
+	EdbExist              int                 `description:"指标库是否已添加:0-否;1-是"`
+	Data                  []*PurangSingleData `gorm:"-"`
+	Paging                *paging.PagingItem  `description:"分页数据" gorm:"-"`
 }
 
 type PurangSingleData struct {
@@ -191,12 +191,12 @@ func GetPurangDataDataTimeByIndexId(indexIdList []int) (items []string, err erro
 type BaseFromPurangData struct {
 	BaseFromPurangDataId  int `orm:"column(base_from_purang_data_id);pk"`
 	BaseFromPurangIndexId int
-	IndexCode              string
-	DataTime               string
-	Value                  string
-	CreateTime             string
-	ModifyTime             string
-	DataTimestamp          int64
+	IndexCode             string
+	DataTime              string
+	Value                 string
+	CreateTime            string
+	ModifyTime            string
+	DataTimestamp         int64
 }
 
 func (baseFromPurangData *BaseFromPurangData) AfterFind(tx *gorm.DB) (err error) {
@@ -208,10 +208,10 @@ func (baseFromPurangData *BaseFromPurangData) AfterFind(tx *gorm.DB) (err error)
 
 type BaseFromPurangIndexSearchItem struct {
 	BaseFromPurangIndexId int `orm:"column(base_from_purang_index_id);pk"`
-	ClassifyId             int
-	ParentClassifyId       int
-	IndexCode              string
-	IndexName              string
+	ClassifyId            int
+	ParentClassifyId      int
+	IndexCode             string
+	IndexName             string
 }
 
 // BatchCheckPurangEdbReq 指标数据结构体
@@ -224,12 +224,12 @@ type BatchCheckPurangEdbReq struct {
 }
 
 // GetPurangItemList 模糊查询Purang数据库指标列表
-func GetPurangItemList(condition string) (items []*BaseFromPurangIndexSearchItem, err error) {
+func GetPurangItemList(condition string, pars []interface{}) (items []*BaseFromPurangIndexSearchItem, err error) {
 	sql := "SELECT * FROM base_from_purang_index  WHERE 1=1"
 	if condition != "" {
 		sql += condition
 	}
-	err = global.DbMap[utils.DbNameIndex].Raw(sql).Find(&items).Error
+	err = global.DbMap[utils.DbNameIndex].Raw(sql, pars).Find(&items).Error
 	return
 }
 
@@ -271,7 +271,7 @@ func (item *BaseFromPurangIndex) Update(cols []string) (err error) {
 // EditPurangIndexInfoResp 新增指标的返回
 type EditPurangIndexInfoResp struct {
 	BaseFromPurangIndexId int    `description:"指标ID"`
-	IndexCode              string `description:"指标code"`
+	IndexCode             string `description:"指标code"`
 }
 
 type PurangIndexSource2EdbReq struct {
@@ -289,4 +289,4 @@ func GetPurangFrequencyByClassifyId(classifyId int) (items []*GlFrequency, err e
 	sql += ` GROUP BY frequency ORDER BY frequency ASC `
 	err = global.DbMap[utils.DbNameIndex].Raw(sql, classifyId).Find(&items).Error
 	return
-} 
+}

+ 1 - 1
models/data_manage/data_manage_permission/classify_no_auth_record.go

@@ -10,7 +10,7 @@ import (
 // @Description: 资产分类数据权限未授权记录表
 type DataPermissionClassifyNoAuthRecord struct {
 	DataPermissionClassifyNoAuthRecordId int64     `json:"data_permission_classify_no_auth_record_id" orm:"column(data_permission_classify_no_auth_record_id);pk" gorm:"primaryKey" ` // 资产分类数据操作记录id
-	Source                               int32     `json:"source"`                                                                                                                    // 数据来源,1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+	Source                               int32     `json:"source"`                                                                                                                    // 数据来源,1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 	SubSource                            int32     `json:"sub_source"`                                                                                                                // 子来源 :ETA表格中的各种表格类型,以及图表的来源(这个是后续的扩展方向)
 	OpUniqueCode                         string    `json:"op_unique_code"`                                                                                                            // 操作的唯一编码,主要是记录统一操作的日志
 	ClassifyId                           string    `json:"classify_id"`                                                                                                               // 资产分类id(指标、图表、表格)

+ 1 - 1
models/data_manage/data_manage_permission/move.go

@@ -49,7 +49,7 @@ func ModifyDataUserIdByOldUserId(oldUserIdList []int, userId int, userName strin
 		}
 	}()
 
-	// 钢联化工数据库
+	// 上海钢联数据库
 	if isMoveMysteelChemical {
 		sql := `UPDATE base_from_mysteel_chemical_index SET sys_user_id=?,sys_user_real_name=? WHERE sys_user_id in (` + utils.GetOrmInReplace(num) + `)  `
 		err = o.Exec(sql, userId, userName, oldUserIdList).Error

+ 1 - 1
models/data_manage/data_manage_permission/move_record.go

@@ -10,7 +10,7 @@ import (
 // @Description: 数据资产转移记录表
 type DataPermissionMoveRecord struct {
 	DataPermissionMoveRecordId int64     `json:"data_permission_move_record_id" orm:"column(data_permission_move_record_id);pk" gorm:"primaryKey" ` // 数据操作记录id
-	Source                     int32     `json:"source"`                                                                                            // 数据来源,1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+	Source                     int32     `json:"source"`                                                                                            // 数据来源,1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 	SubSource                  int32     `json:"sub_source"`                                                                                        // 子来源 :ETA表格中的各种表格类型,以及图表的来源(这个是后续的扩展方向)
 	OpUniqueCode               string    `json:"op_unique_code"`                                                                                    // 操作的唯一编码,主要是记录统一操作的日志
 	DataId                     string    `json:"data_id"`                                                                                           // 资产id(指标、图表、表格)

+ 1 - 1
models/data_manage/data_manage_permission/no_auth_record.go

@@ -10,7 +10,7 @@ import (
 // @Description: 资产数据权限设置记录表
 type DataPermissionNoAuthRecord struct {
 	DataPermissionNoAuthRecordId int64     `json:"data_permission_no_auth_record_id" orm:"column(data_permission_no_auth_record_id);pk" gorm:"primaryKey" ` // 资产数据操作记录id
-	Source                       int32     `json:"source"`                                                                                                  // 数据来源,1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+	Source                       int32     `json:"source"`                                                                                                  // 数据来源,1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 	SubSource                    int32     `json:"sub_source"`                                                                                              // 子来源 :ETA表格中的各种表格类型,以及图表的来源(这个是后续的扩展方向)
 	OpUniqueCode                 string    `json:"op_unique_code"`                                                                                          // 操作的唯一编码,主要是记录统一操作的日志
 	DataId                       string    `json:"data_id"`                                                                                                 // 资产id(指标、图表、表格)

+ 2 - 2
models/data_manage/edb_data_mysteel_chemical.go

@@ -5,7 +5,7 @@ import (
 	"eta/eta_api/utils"
 )
 
-// GetEdbDataMysteelChemicalMaxOrMinDate 根据钢联化工指标code获取最大、最小日期
+// GetEdbDataMysteelChemicalMaxOrMinDate 根据上海钢联指标code获取最大、最小日期
 func GetEdbDataMysteelChemicalMaxOrMinDate(edbCode string) (minDate, maxDate string, err error) {
 	//o := orm.NewOrmUsingDB("data")
 	sql := ` SELECT MIN(data_time) AS minDate,MAX(data_time) AS maxDate FROM edb_data_mysteel_chemical WHERE edb_code=? `
@@ -20,7 +20,7 @@ func GetEdbDataMysteelChemicalMaxOrMinDate(edbCode string) (minDate, maxDate str
 	return
 }
 
-// 更新钢联化工指标的刷新状态
+// 更新上海钢联指标的刷新状态
 func UpdateMysteelChemicalRefreshStatus(edbCode string, isStop int) (err error) {
 	//o := orm.NewOrmUsingDB("data")
 	sql := ` UPDATE base_from_mysteel_chemical_index SET is_stop = ? WHERE index_code =? and is_stop=1`

+ 10 - 1
models/data_manage/edb_info.go

@@ -174,6 +174,9 @@ type EdbInfoSearch struct {
 type StockInfo struct {
 	StockCode string
 	EdbCode   string
+	Days      string `description:"日期序列-日期类型"`
+	Period    string `description:"日期序列-周期"`
+	PriceAdj  string `description:"日期序列-复权方式"`
 	DataList  []*EdbInfoSearchData
 }
 
@@ -284,7 +287,7 @@ type BatchAddCheckReq struct {
 	IndexCodes []string `form:"IndexCodes" description:"全选为false时, 该数组为选中; 全选为true时, 该数组为不选的指标"`
 }
 
-// MysteelChemicalDataBatchAddCheckReq 钢联化工指标批量添加校验
+// MysteelChemicalDataBatchAddCheckReq 上海钢联指标批量添加校验
 type MysteelChemicalDataBatchAddCheckReq struct {
 	// MysteelChemicalDataListReq
 	IndexCodes []string `form:"IndexCodes" description:"全选为false时, 该数组为选中; 全选为true时, 该数组为不选的指标"`
@@ -1848,6 +1851,9 @@ type BatchAddEdbInfo struct {
 	ClassifyId   int    `description:"分类id"`
 	StockCode    string `description:"证券代码"`
 	EdbCode      string `description:"指标编码"`
+	Days         string `description:"日期序列-日期类型"`
+	Period       string `description:"日期序列-周期"`
+	PriceAdj     string `description:"日期序列-复权方式"`
 	ApiExtraPars string `description:"API额外参数"`
 }
 
@@ -2181,6 +2187,9 @@ func GetEdbInfoFieldList(cond string, pars []interface{}, fields []string) (item
 // EdbInfoExtra 指标额外数据-extra字段
 type EdbInfoExtra struct {
 	ApiExtraPars string `description:"API-额外参数(如同花顺日期序列)"`
+	Days         string `description:"日期序列-日期类型"`
+	Period       string `description:"日期序列-周期"`
+	PriceAdj     string `description:"日期序列-复权方式"`
 }
 
 func GetEdbInfoListByCond(condition string, pars []interface{}) (list []*EdbInfoList, err error) {

+ 4 - 4
models/data_manage/edb_info_relation.go

@@ -112,7 +112,7 @@ func AddOrUpdateEdbInfoRelation(objectId, objectType int, relationList []*EdbInf
 		}
 	}
 
-	//更新数据源钢联化工指标
+	//更新数据源上海钢联指标
 	if len(indexCodeList) > 0 {
 		// 更改数据源的更新状态
 		sql := ` UPDATE base_from_mysteel_chemical_index SET is_stop = 0 WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodeList)) + `) and is_stop=1`
@@ -180,7 +180,7 @@ func AddOrUpdateEdbInfoRelationMulti(relationList []*EdbInfoRelation, refreshEdb
 		}
 	}
 
-	//更新数据源钢联化工指标
+	//更新数据源上海钢联指标
 	if len(indexCodeList) > 0 {
 		// 更改数据源的更新状态
 		sql := ` UPDATE base_from_mysteel_chemical_index SET is_stop = 0 WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodeList)) + `) and is_stop=1`
@@ -431,7 +431,7 @@ func ReplaceRelationEdbInfoId(oldEdbInfo, newEdbInfo *EdbInfo, edbRelationIds []
 		}
 	}
 
-	//更新数据源钢联化工指标
+	//更新数据源上海钢联指标
 	if len(indexCodeList) > 0 {
 		// 更改数据源的更新状态
 		sql := ` UPDATE base_from_mysteel_chemical_index SET is_stop = 0 WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodeList)) + `) and is_stop=1`
@@ -505,7 +505,7 @@ func UpdateSecondRelationEdbInfoId(edbRelationIds []int, relationList []*EdbInfo
 		}
 	}
 
-	//更新数据源钢联化工指标
+	//更新数据源上海钢联指标
 	if len(indexCodeList) > 0 {
 		// 更改数据源的更新状态
 		sql = ` UPDATE base_from_mysteel_chemical_index SET is_stop = 0 WHERE index_code IN (` + utils.GetOrmInReplace(len(indexCodeList)) + `) and is_stop=1`

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

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

@@ -0,0 +1,191 @@
+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
+} 
+
+func GetUnreadInspectionMessageList(adminIds []int) (items []*EdbInspectionMessage, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := "SELECT * FROM edb_inspection_message WHERE admin_id IN (?) AND is_read = 0 ORDER BY create_time DESC, message_id DESC"
+	err = o.Raw(sql, adminIds).Find(&items).Error
+	return
+}
+
+func GetUnreadInspectionMessageListByAdminId(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, message_id DESC"
+	err = o.Raw(sql, adminId).Find(&items).Error
+	return
+}
+
+func SetEdbInspectionMessageRead(messageId []int64) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := "UPDATE edb_inspection_message SET is_read = 1, modify_time = ? WHERE message_id IN (?) AND is_read = 0"
+	err = o.Exec(sql, time.Now(), messageId).Error
+	return
+}

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

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

+ 117 - 6
models/data_manage/edb_terminal.go

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

+ 4 - 4
models/data_manage/mysteel_chemical_classify.go

@@ -7,7 +7,7 @@ import (
 	"time"
 )
 
-// BaseFromMysteelChemicalClassify 钢联化工分类表
+// BaseFromMysteelChemicalClassify 上海钢联分类表
 type BaseFromMysteelChemicalClassify struct {
 	BaseFromMysteelChemicalClassifyId int       `orm:"column(base_from_mysteel_chemical_classify_id);pk" gorm:"primaryKey"`
 	ClassifyName                      string    `description:"分类名称"`
@@ -21,7 +21,7 @@ type BaseFromMysteelChemicalClassify struct {
 	ClassifyNameEn                    string    `description:"英文分类名称"`
 }
 
-// AddBaseFromMysteelChemicalClassify 添加钢联化工分类
+// AddBaseFromMysteelChemicalClassify 添加上海钢联分类
 func AddBaseFromMysteelChemicalClassify(item *BaseFromMysteelChemicalClassify) (lastId int64, err error) {
 	o := global.DbMap[utils.DbNameIndex]
 	err = o.Create(item).Error
@@ -70,7 +70,7 @@ func GetBaseFromMysteelChemicalClassifyById(classifyId int) (item *BaseFromMyste
 	return
 }
 
-// EditBaseFromMysteelChemicalClassify 修改钢联化工分类
+// EditBaseFromMysteelChemicalClassify 修改上海钢联分类
 func EditBaseFromMysteelChemicalClassify(classifyId int, classifyName string) (err error) {
 	o := global.DbMap[utils.DbNameIndex]
 	sql := `UPDATE base_from_mysteel_chemical_classify SET classify_name=?,modify_time=NOW() WHERE base_from_mysteel_chemical_classify_id=? `
@@ -79,7 +79,7 @@ func EditBaseFromMysteelChemicalClassify(classifyId int, classifyName string) (e
 }
 
 // EditBaseFromMysteelChemicalClassifyEn
-// @Description: 修改钢联化工英文分类名称
+// @Description: 修改上海钢联英文分类名称
 // @author: Roc
 // @datetime 2024-04-16 16:34:53
 // @param classifyId int

+ 38 - 27
models/data_manage/mysteel_chemical_index.go

@@ -11,10 +11,10 @@ import (
 	"github.com/rdlucklib/rdluck_tools/paging"
 )
 
-// BaseFromMysteelChemicalIndex 钢联化工指标表
+// BaseFromMysteelChemicalIndex 上海钢联指标表
 type BaseFromMysteelChemicalIndex struct {
 	BaseFromMysteelChemicalIndexId    int       `orm:"column(base_from_mysteel_chemical_index_id);pk" gorm:"primaryKey"`
-	BaseFromMysteelChemicalClassifyId int       `orm:"column(base_from_mysteel_chemical_classify_id)" description:"钢联化工指标分类id"`
+	BaseFromMysteelChemicalClassifyId int       `orm:"column(base_from_mysteel_chemical_classify_id)" description:"上海钢联指标分类id"`
 	IndexCode                         string    `description:"指标编码"`
 	IndexName                         string    `description:"指标名称"`
 	Unit                              string    `description:"单位"`
@@ -89,7 +89,7 @@ var BaseFromMysteelChemicalIndexCols = struct {
 	ModifyTime:                     "modify_time",
 }
 
-// Update 更新钢联化工指标基础信息
+// Update 更新上海钢联指标基础信息
 func (item *BaseFromMysteelChemicalIndex) Update(cols []string) (err error) {
 	o := global.DbMap[utils.DbNameIndex]
 	err = o.Select(cols).Updates(item).Error
@@ -118,7 +118,7 @@ func (m *BaseFromMysteelChemicalIndex) GeItemsByCondition(condition string, pars
 	return
 }
 
-// AddBaseFromMysteelChemicalIndex 添加钢联化工指标
+// AddBaseFromMysteelChemicalIndex 添加上海钢联指标
 func AddBaseFromMysteelChemicalIndex(item *BaseFromMysteelChemicalIndex) (lastId int64, err error) {
 	o := global.DbMap[utils.DbNameIndex]
 	err = o.Create(item).Error
@@ -129,17 +129,17 @@ func AddBaseFromMysteelChemicalIndex(item *BaseFromMysteelChemicalIndex) (lastId
 	return
 }
 
-// AddBaseFromMysteelChemicalIndex 添加钢联化工指标
+// AddBaseFromMysteelChemicalIndex 添加上海钢联指标
 func BatchAddBaseFromMysteelChemicalIndex(items []*BaseFromMysteelChemicalIndex) (lastId int64, err error) {
 	o := global.DbMap[utils.DbNameIndex]
 	err = o.CreateInBatches(items, utils.MultiAddNum).Error
 	return
 }
 
-// BaseFromMysteelChemicalData 钢联化工指标数据表
+// BaseFromMysteelChemicalData 上海钢联指标数据表
 type BaseFromMysteelChemicalData struct {
 	BaseFromMysteelChemicalDataId  int       `orm:"column(base_from_mysteel_chemical_data_id);pk" gorm:"primaryKey"`
-	BaseFromMysteelChemicalIndexId int       `orm:"column(base_from_mysteel_chemical_index_id)" description:"钢联化工指标id"`
+	BaseFromMysteelChemicalIndexId int       `orm:"column(base_from_mysteel_chemical_index_id)" description:"上海钢联指标id"`
 	IndexCode                      string    `description:"指标编码"`
 	DataTime                       time.Time `description:"数据日期"`
 	Value                          float64   `description:"数据值"`
@@ -147,7 +147,7 @@ type BaseFromMysteelChemicalData struct {
 	CreateTime                     time.Time `description:"创建时间"`
 }
 
-// MysteelChemicalFrequency 钢联化工频度
+// MysteelChemicalFrequency 上海钢联频度
 type MysteelChemicalFrequency struct {
 	Frequency string `description:"频度:1-日度 2-周度 3-月度 4-季度 5-年度 99-无固定频率"`
 }
@@ -162,7 +162,7 @@ func GetMysteelChemicalIndexByClassifyId(classifyId int) (items []*BaseFromMyste
 	return
 }
 
-// MysteelChemicalFrequencyByClassifyId 根据分类id获取钢联化工频度数据列表
+// MysteelChemicalFrequencyByClassifyId 根据分类id获取上海钢联频度数据列表
 func MysteelChemicalFrequencyByClassifyId(classifyId int) (items []*MysteelChemicalFrequency, err error) {
 	o := global.DbMap[utils.DbNameIndex]
 	if classifyId == 0 {
@@ -176,7 +176,7 @@ func MysteelChemicalFrequencyByClassifyId(classifyId int) (items []*MysteelChemi
 	}
 }
 
-// GetMysteelChemicalFrequency 获取钢联化工频度数据列表
+// GetMysteelChemicalFrequency 获取上海钢联频度数据列表
 func GetMysteelChemicalFrequency(condition string, pars []interface{}) (items []*MysteelChemicalFrequency, err error) {
 	o := global.DbMap[utils.DbNameIndex]
 	sql := ` SELECT frequency FROM base_from_mysteel_chemical_index WHERE 1=1 AND frequency != '' `
@@ -188,11 +188,11 @@ func GetMysteelChemicalFrequency(condition string, pars []interface{}) (items []
 	return
 }
 
-// MysteelChemicalList 钢联化工指标列表
+// MysteelChemicalList 上海钢联指标列表
 type MysteelChemicalList struct {
 	Id                                int                    `gorm:"column:base_from_mysteel_chemical_index_id"`
-	BaseFromMysteelChemicalClassifyId int                    `gorm:"column:base_from_mysteel_chemical_classify_id" description:"钢联化工指标分类id"`
-	ParentClassifyId                  int                    `description:"钢联化工指标父级分类id"`
+	BaseFromMysteelChemicalClassifyId int                    `gorm:"column:base_from_mysteel_chemical_classify_id" description:"上海钢联指标分类id"`
+	ParentClassifyId                  int                    `description:"上海钢联指标父级分类id"`
 	IndexCode                         string                 `description:"指标编码"`
 	IndexName                         string                 `description:"指标名称"`
 	UnitName                          string                 `gorm:"column:unit"`
@@ -214,7 +214,7 @@ func (m *MysteelChemicalList) AfterFind(tx *gorm.DB) (err error) {
 	return
 }
 
-// MysteelChemicalData 钢联化工数据列表
+// MysteelChemicalData 上海钢联数据列表
 type MysteelChemicalData struct {
 	InputValue string `gorm:"column:value" description:"值"`
 	DataTime   string `gorm:"column:data_time" description:"日期"`
@@ -225,7 +225,7 @@ func (m *MysteelChemicalData) AfterFind(tx *gorm.DB) (err error) {
 	return
 }
 
-// GetMysteelChemicalIndex 根据分类id获取钢联化工频度数据列表
+// GetMysteelChemicalIndex 根据分类id获取上海钢联频度数据列表
 func GetMysteelChemicalIndex(condition string, pars []interface{}) (items []*MysteelChemicalList, err error) {
 	o := global.DbMap[utils.DbNameIndex]
 	sql := ` SELECT * FROM base_from_mysteel_chemical_index WHERE 1=1 `
@@ -238,8 +238,19 @@ func GetMysteelChemicalIndex(condition string, pars []interface{}) (items []*Mys
 	return
 }
 
-// GetMysteelChemicalIndexData 根据指标code获取钢联化工数据列表
-func GetMysteelChemicalIndexData(indexCode string, startSize, pageSize int) (items []*MysteelChemicalData, err error) {
+// MysteelChemicalData 上海钢联数据列表
+type MysteelChemicalDataItem struct {
+	InputValue float64 `gorm:"column:value" description:"值"`
+	DataTime   string  `gorm:"column:data_time" description:"日期"`
+}
+
+func (m *MysteelChemicalDataItem) AfterFind(tx *gorm.DB) (err error) {
+	m.DataTime = utils.GormDateStrToDateStr(m.DataTime)
+	return
+}
+
+// GetMysteelChemicalIndexData 根据指标code获取上海钢联数据列表
+func GetMysteelChemicalIndexData(indexCode string, startSize, pageSize int) (items []*MysteelChemicalDataItem, err error) {
 	sql := ` SELECT * FROM (
 	SELECT DISTINCT a.index_code,a.value,a.data_time FROM base_from_mysteel_chemical_data AS a WHERE index_code=? 
 	ORDER BY data_time DESC
@@ -252,7 +263,7 @@ func GetMysteelChemicalIndexData(indexCode string, startSize, pageSize int) (ite
 	return
 }
 
-// GetMysteelChemicalIndexDataCount 根据指标code获取钢联化工数据列表 获取钢联数据总数
+// GetMysteelChemicalIndexDataCount 根据指标code获取上海钢联数据列表 获取钢联数据总数
 func GetMysteelChemicalIndexDataCount(indexCode string) (count int, err error) {
 	sql := `SELECT COUNT(1) AS count FROM (
 			SELECT * FROM (
@@ -357,7 +368,7 @@ func GetBaseFromMysteelChemicalDataTimeByIndexId(indexIdList []int) (items []str
 	return
 }
 
-// GetMysteelChemicalIndexDataByCode 通过钢联化工指标code获取所有数据列表
+// GetMysteelChemicalIndexDataByCode 通过上海钢联指标code获取所有数据列表
 func GetMysteelChemicalIndexDataByCode(indexCode string) (items []*MysteelChemicalData, err error) {
 	sql := ` SELECT * FROM (
 	SELECT DISTINCT a.index_code,a.value,a.data_time FROM base_from_mysteel_chemical_data AS a WHERE index_code=? 
@@ -370,7 +381,7 @@ func GetMysteelChemicalIndexDataByCode(indexCode string) (items []*MysteelChemic
 	return
 }
 
-// MoveBaseFromMysteelChemicalIndex 移动钢联化工指标分类
+// MoveBaseFromMysteelChemicalIndex 移动上海钢联指标分类
 func MoveBaseFromMysteelChemicalIndex(chartInfoId, classifyId int) (err error) {
 	o := global.DbMap[utils.DbNameIndex]
 	sql := ` UPDATE base_from_mysteel_chemical_index
@@ -400,7 +411,7 @@ func GetFirstBaseFromMysteelChemicalIndexByClassifyId(classifyId int) (item *Bas
 	return
 }
 
-// GetMysteelChemicalIndexCount 根据条件获取钢联化工数据
+// GetMysteelChemicalIndexCount 根据条件获取上海钢联数据
 func GetMysteelChemicalIndexCount(condition string, pars []interface{}) (count int, err error) {
 	o := global.DbMap[utils.DbNameIndex]
 	sql := ` SELECT COUNT(1) AS count FROM base_from_mysteel_chemical_index WHERE 1=1 `
@@ -413,7 +424,7 @@ func GetMysteelChemicalIndexCount(condition string, pars []interface{}) (count i
 	return
 }
 
-// GetMysteelChemicalIndexList 根据分类id获取钢联化工频度数据列表
+// GetMysteelChemicalIndexList 根据分类id获取上海钢联频度数据列表
 func GetMysteelChemicalIndexList(condition string, pars []interface{}, startSize, pageSize int, orderDesc string) (items []*BaseFromMysteelChemicalIndex, err error) {
 	o := global.DbMap[utils.DbNameIndex]
 	sql := ` SELECT * FROM base_from_mysteel_chemical_index WHERE 1=1 `
@@ -527,7 +538,7 @@ type TerminalNum struct {
 	Num          int    `description:"num"`
 }
 
-// GetMysteelChemicalGroupTerminalNum 获取钢联化工指标的终端分布
+// GetMysteelChemicalGroupTerminalNum 获取上海钢联指标的终端分布
 func GetMysteelChemicalGroupTerminalNum() (items []*TerminalNum, err error) {
 	o := global.DbMap[utils.DbNameIndex]
 	sql := ` SELECT terminal_code,count(1) num FROM base_from_mysteel_chemical_index GROUP BY terminal_code ORDER BY num ASC `
@@ -539,7 +550,7 @@ func GetMysteelChemicalGroupTerminalNum() (items []*TerminalNum, err error) {
 // @Description: 刷新配置的基础指标信息结构体
 type BaseRefreshEdbInfo struct {
 	EdbInfoId       int
-	ClassifyId      int    `description:"钢联化工指标分类id"`
+	ClassifyId      int    `description:"上海钢联指标分类id"`
 	IndexCode       string `description:"指标编码"`
 	IndexName       string `description:"指标名称"`
 	EndDate         string `description:"最新日期"`
@@ -565,7 +576,7 @@ type RefreshBaseEdbInfoResp struct {
 }
 
 // GetMysteelChemicalBaseInfoList
-// @Description: 获取钢联化工数据列表
+// @Description: 获取上海钢联数据列表
 // @author: Roc
 // @datetime 2024-01-10 14:28:35
 // @param condition string
@@ -608,7 +619,7 @@ func GetMysteelChemicalBaseInfoList(condition string, pars []interface{}, orderB
 }
 
 // ModifyMysteelChemicalUpdateStatus
-// @Description:  修改钢联化工数据停更状态
+// @Description:  修改上海钢联数据停更状态
 // @author: Roc
 // @datetime 2024-01-08 16:23:31
 // @param edbIdList []int
@@ -663,7 +674,7 @@ func ModifyMysteelChemicalUpdateStatus(edbIdList []int, indexCodeList []string,
 }
 
 // ModifyMysteelChemicalUpdateStatusByEdbInfoId
-// @Description:  修改单个钢联化工指标停更状态,同时停更依赖于该指标的计算指标
+// @Description:  修改单个上海钢联指标停更状态,同时停更依赖于该指标的计算指标
 // @author: Roc
 // @datetime 2024-01-08 16:23:31
 // @param edbIdList []int

+ 7 - 6
models/data_manage/request/mysteel_chemical_data.go

@@ -1,13 +1,13 @@
 package request
 
-// AddBaseFromMysteelChemicalClassifyReq 添加钢联化工分类请求
+// AddBaseFromMysteelChemicalClassifyReq 添加上海钢联分类请求
 type AddBaseFromMysteelChemicalClassifyReq struct {
 	ParentId     int    `description:"上级id"`
 	ClassifyName string `description:"分类名称"`
 	Level        int    `description:"层级,第一级传0,其余传上一级的层级"`
 }
 
-// EditBaseFromMysteelChemicalClassifyReq 修改钢联化工分类请求
+// EditBaseFromMysteelChemicalClassifyReq 修改上海钢联分类请求
 type EditBaseFromMysteelChemicalClassifyReq struct {
 	ClassifyName                      string `description:"分类名称"`
 	BaseFromMysteelChemicalClassifyId int    `description:"分类id"`
@@ -21,12 +21,12 @@ type MoveBaseFromMysteelChemicalClassifyReq struct {
 	NextBaseFromMysteelChemicalClassifyId   int `description:"下一个兄弟节点分类id"`
 }
 
-// DelBaseFromMysteelChemicalClassifyReq 删除钢联化工分类请求
+// DelBaseFromMysteelChemicalClassifyReq 删除上海钢联分类请求
 type DelBaseFromMysteelChemicalClassifyReq struct {
 	BaseFromMysteelChemicalClassifyId int `description:"分类id"`
 }
 
-// AddBaseFromMysteelChemicalReq 添加钢联化工请求
+// AddBaseFromMysteelChemicalReq 添加上海钢联请求
 type AddBaseFromMysteelChemicalReqItem struct {
 	BaseFromMysteelChemicalIndexId    int    `description:"指标id"`
 	BaseFromMysteelChemicalClassifyId int    `description:"分类id"`
@@ -34,12 +34,13 @@ type AddBaseFromMysteelChemicalReqItem struct {
 	UpdateWeek                        string `description:"更新周期"`
 	UpdateTime                        string `description:"更新时间点,多个时间点用英文,隔开"`
 }
-// AddBaseFromMysteelChemicalReq 添加钢联化工请求
+
+// AddBaseFromMysteelChemicalReq 添加上海钢联请求
 type AddBaseFromMysteelChemicalReq struct {
 	List []AddBaseFromMysteelChemicalReqItem
 }
 
-// DelBaseFromMysteelChemicalReq 删除钢联化工请求
+// DelBaseFromMysteelChemicalReq 删除上海钢联请求
 type DelBaseFromMysteelChemicalReq struct {
 	BaseFromMysteelChemicalIndexId int `description:"指标id"`
 }

+ 1 - 1
models/data_manage/request/predict_edb_info.go

@@ -41,7 +41,7 @@ type AddPredictEdbInfoReq struct {
 
 // RuleConfig 预测规则配置
 type RuleConfig struct {
-	RuleType     int                          `description:"预测规则,1:最新,2:固定值,3:同比,4:同差,5:环比,6:环差,7:N期移动均值,8:N期段线性外推值,9:动态环差"`
+	RuleType     int                          `description:"预测规则,1:最新,2:固定值,3:同比,4:同差,5:环比,6:环差,7:N期移动均值,8:N期段线性外推值,9:动态环差,17:动态同比,18:动态同差"`
 	Value        string                       `description:"值"`
 	EmptyType    int                          `description:"空值处理类型(0查找前后35天,1不计算,2前值填充,3后值填充,4等于0)"`
 	MaxEmptyType int                          `description:"MAX、MIN公式空值处理类型(1、等于0;2、跳过空值)"`

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

+ 1 - 1
models/db.go

@@ -353,7 +353,7 @@ func initEdbData() {
 		new(data_manage.EdbInfoCalculateMapping),
 		new(data_manage.PredictEdbConf),                  //预测指标配置
 		new(data_manage.BaseFromMysteelChemicalClassify), //预测指标配置
-		new(data_manage.BaseFromMysteelChemicalIndex),    //钢联化工
+		new(data_manage.BaseFromMysteelChemicalIndex),    //上海钢联
 		new(data_manage.BaseFromEiaSteoClassify),         // Eia steo 报告指标
 		new(data_manage.BaseFromEiaSteoIndex),            // Eia steo 报告指标分类
 		new(data_manage.PredictEdbRuleData),              //预测指标,动态规则的计算数据

+ 28 - 0
models/edb_monitor/edb_monitor_message.go

@@ -80,3 +80,31 @@ func GetEdbMonitorMessagePageByAdminId(adminId, startSize, pageSize int) (items
 	err = o.Raw(sql, adminId, startSize, pageSize).Find(&items).Error
 	return
 }
+
+func GetEdbMonitorMessageUnreadListByAdminIds(adminIds []int) (items []*EdbMonitorMessage, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := "SELECT * FROM edb_monitor_message WHERE is_read = 0 AND admin_id IN (?) ORDER BY create_time DESC, edb_monitor_message_id DESC"
+	err = o.Raw(sql, adminIds).Find(&items).Error
+	return
+}
+
+func SetEdbMonitorMessageReadByIds(msgIds []int) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := "UPDATE edb_monitor_message SET is_read = 1 WHERE edb_monitor_message_id IN (?) and is_read=0"
+	err = o.Exec(sql, msgIds).Error
+	return
+}
+
+func GetEdbMonitorMessageUnreadByAdminId(adminId int) (items []*EdbMonitorMessage, err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := "SELECT * FROM edb_monitor_message WHERE admin_id =? AND is_read = 0 ORDER BY create_time DESC, edb_monitor_message_id DESC"
+	err = o.Raw(sql, adminId).Find(&items).Error
+	return
+}
+
+func SetEdbMonitorMessageReadByAdminId(adminId int, msgIds []int) (err error) {
+	o := global.DbMap[utils.DbNameIndex]
+	sql := "UPDATE edb_monitor_message SET is_read = 1 WHERE admin_id =? AND edb_monitor_message_id IN (?) and is_read=0"
+	err = o.Exec(sql, adminId, msgIds).Error
+	return
+}

+ 4 - 0
models/manual_edb.go

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

+ 5 - 2
models/material/material_classify.go

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

+ 7 - 0
models/message.go

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

+ 70 - 8
models/report.go

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

+ 281 - 0
models/report/report_free_layout.go

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

+ 20 - 11
models/report_chapter.go

@@ -134,14 +134,18 @@ func (m *ReportChapterItem) ConvDateTimeStr() {
 // @Description: 章节详情(带有一些额外的数据)
 type ReportChapterItemResp struct {
 	ReportChapterItem
-	GrandAdminIdList []int  `description:"授权的用户id列表"`
-	PermissionIdList []int  `description:"关联的品种id列表"`
-	CanEdit          bool   `description:"是否可编辑"`
-	Editor           string `description:"编辑人"`
-	HeadImg          string `description:"报告头图地址"`
-	EndImg           string `description:"报告尾图地址"`
-	HeadStyle        string `description:"版头样式"`
-	EndStyle         string `description:"版尾样式"`
+	FreeLayoutContentPages []*report.ContentPage
+	FreeLayoutConfig       string
+	PreviousPagesNum       int
+	GrandAdminIdList       []int  `description:"授权的用户id列表"`
+	PermissionIdList       []int  `description:"关联的品种id列表"`
+	CanEdit                bool   `description:"是否可编辑"`
+	Editor                 string `description:"编辑人"`
+	HeadImg                string `description:"报告头图地址"`
+	EndImg                 string `description:"报告尾图地址"`
+	HeadStyle              string `description:"版头样式"`
+	EndStyle               string `description:"版尾样式"`
+	FreeReportRatio        string `description:"自由布局页面比例:A4; 16:9; 4:3; 10:7"`
 }
 
 type ReportChapterResp struct {
@@ -222,6 +226,9 @@ type EditReportChapterReq struct {
 	CanvasColor    string `description:"画布颜色"`
 	HeadResourceId int    `description:"版头资源ID"`
 	EndResourceId  int    `description:"版尾资源ID"`
+	//自由布局研报相关
+	FreeLayoutContentPages []report.ContentPage `description:"自由布局内容"`
+	FreeLayoutConfig       string               `description:"'自由布局配置"`
 }
 
 type EditTickList struct {
@@ -504,9 +511,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

+ 26 - 2
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
+				}
+			}
+
 		}
 	}
 
@@ -353,7 +377,7 @@ func GetReportListCountByAuthorized(condition string, pars []interface{}) (count
 func GetReportListByAuthorized(condition string, pars []interface{}, startSize, pageSize int) (items []*ReportList, err error) {
 	o := global.DbMap[utils.DbNameReport]
 
-	sql := `SELECT id,classify_id_first,classify_name_first,classify_id_second,classify_name_second,classify_id_third,classify_name_third,title,stage,create_time,author,report_layout,collaborate_type,is_public_publish,abstract,has_chapter,publish_time FROM report as a WHERE 1=1  `
+	sql := `SELECT id,classify_id_first,classify_name_first,classify_id_second,classify_name_second,classify_id_third,classify_name_third,title,stage,create_time,author,report_layout,collaborate_type,is_public_publish,abstract,has_chapter,publish_time,mini_show,free_report_ratio FROM report as a WHERE 1=1  `
 	if condition != "" {
 		sql += condition
 	}

+ 2 - 2
models/system/sys_role.go

@@ -286,10 +286,10 @@ func GetSysRoleByIdList(id []int) (items []*SysRole, err error) {
 	return
 }
 
-
 // 更新所有管理员的角色信息
 func UpdateAdminRoleInfoByRoleId(roleId int, roleName, roleTypeCode string) (err error) {
 	sql := `UPDATE admin SET role_name=?, role_type_code=? WHERE role_id=?`
+	sql=utils.ReplaceDriverKeywords("", sql)
 	err = global.DbMap[utils.DbNameMaster].Exec(sql, roleName, roleTypeCode, roleId).Error
 	return
-}
+}

+ 1 - 1
models/system/sys_user.go

@@ -56,7 +56,7 @@ type Admin struct {
 	OpenId                    string    `description:"弘则部门公众号的openid"`
 	UnionId                   string    `description:"微信公众平台唯一标识"`
 	EdbPermission             int8      `description:"指标库操作权限,0:只能操作 自己的,1:所有指标可操作"`
-	MysteelChemicalPermission int8      `description:"钢联化工指标操作权限,0:只能操作 自己的,1:所有指标可操作"`
+	MysteelChemicalPermission int8      `description:"上海钢联指标操作权限,0:只能操作 自己的,1:所有指标可操作"`
 	PredictEdbPermission      int8      `description:"预测指标库操作权限,0:只能操作 自己的,1:所有预测指标可操作"`
 	Province                  string    `description:"省"`
 	ProvinceCode              string    `description:"省编码"`

+ 127 - 1
routers/commentsRouter.go

@@ -6595,6 +6595,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",
@@ -7702,6 +7801,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",
@@ -7947,13 +8064,22 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorMessageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorMessageController"],
         beego.ControllerComments{
-            Method: "Connect",
+            Method: "ConnectV2",
             Router: `/message/connect`,
             AllowHTTPMethods: []string{"get"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorMessageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorMessageController"],
+        beego.ControllerComments{
+            Method: "Connect",
+            Router: `/message/connectV1`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorMessageController"] = append(beego.GlobalControllerRouter["eta/eta_api/controllers/edb_monitor:EdbMonitorMessageController"],
         beego.ControllerComments{
             Method: "List",

+ 2 - 0
routers/router.go

@@ -208,6 +208,8 @@ func init() {
 				&data_manage.BaseFromGprRiskController{},
 				&data_manage.BaseFromPurangController{},
 				&data_manage.BaseFromRadishResearchController{},
+				&data_manage.EdbInspectionController{},
+				&data_manage.EdbInspectionMessageController{},
 				&data_manage.BaseFromKplerController{},
 			),
 		),

+ 8 - 7
services/data/base_edb_lib.go

@@ -96,27 +96,28 @@ func AddEdbData(source int, edbCode, frequency string) (resp *models.BaseRespons
 	return
 }
 
-// AddEdbData 新增指标数据
-func AddEdbDataWindWsd(source int, stockCode, edbCode string) (resp *models.BaseResponse, err error) {
+// AddEdbDataWindWsd 新增指标数据
+func AddEdbDataWindWsd(source int, stockCode, edbCode, days, period, priceAdj string) (resp *models.BaseResponse, err error) {
 	param := make(map[string]interface{})
 	param["EdbCode"] = edbCode
 	param["StockCode"] = stockCode
 	param["Source"] = source
+	param["Days"] = days
+	param["Period"] = period
+	param["PriceAdj"] = priceAdj
 	urlStr := `wind/wsd/add`
-	if urlStr == "" {
-		err = fmt.Errorf("未实现该指标的刷新接口,请联系管理员")
-		return
-	}
 	resp, err = postRefreshEdbData(param, urlStr)
 	return
 }
 
 // AddEdbDataThsDs 新增指标数据
-func AddEdbDataThsDs(source int, stockCode, edbCode, extraPars string) (resp *models.BaseResponse, err error) {
+func AddEdbDataThsDs(source int, stockCode, edbCode, extraPars, days, period string) (resp *models.BaseResponse, err error) {
 	param := make(map[string]interface{})
 	param["EdbCode"] = edbCode
 	param["StockCode"] = stockCode
 	param["Source"] = source
+	param["Days"] = days
+	param["Period"] = period
 	param["ExtraPars"] = extraPars
 	urlStr := `ths/ds/add`
 	resp, err = postRefreshEdbData(param, urlStr)

+ 91 - 8
services/data/chart_info.go

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

+ 14 - 14
services/data/data_manage_permission/data_move.go

@@ -49,7 +49,7 @@ func GetEdbChartClassifyList(source, subSource int) (resp data_manage.EdbChartCl
 			resp.List = append(resp.List, &item)
 		}
 
-	case 2: //钢联化工数据库
+	case 2: //上海钢联数据库
 		rootList, e := data_manage.GetBaseFromMysteelChemicalClassifyByParentId(0)
 		if e != nil && !utils.IsErrNoRow(e) {
 			err = e
@@ -207,7 +207,7 @@ func GetExcelMenuTreeRecursive(list []*excel.ExcelClassifyItems, parentId int) [
 }
 
 // GetMoveEdbChartList 获取待转移的指标/图表列表
-// @param source 来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+// @param source 来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 func GetMoveEdbChartList(source, subSource, userId int, keyword, classify string, startSize, pageSize int) (list []data_manage.MoveEdbChartList, total int, err error) {
 	var condition string
 	var pars []interface{}
@@ -265,7 +265,7 @@ func GetMoveEdbChartList(source, subSource, userId int, keyword, classify string
 			}
 		}
 
-	case 2: //钢联化工数据库
+	case 2: //上海钢联数据库
 		if keyword != `` {
 			condition += " AND (index_name like ? OR index_code like ? OR sys_user_real_name like ? ) "
 			pars = utils.GetLikeKeywordPars(pars, keyword, 3)
@@ -486,7 +486,7 @@ func GetMoveEdbChartList(source, subSource, userId int, keyword, classify string
 }
 
 // MoveEdbChart 转移指标/图表创建人
-// @param source 来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+// @param source 来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 func MoveEdbChart(source, subSource, oldUserId, newUserId int, isSelectAll bool, dataId, noDataId []string, keyword, classify string, opUserId int, opUserName string) (err error, errMsg string) {
 	adminInfo, err := system.GetSysAdminById(newUserId)
 	if err != nil {
@@ -564,8 +564,8 @@ func MoveEdbChart(source, subSource, oldUserId, newUserId int, isSelectAll bool,
 			}
 		}
 
-	case 2: //钢联化工数据库
-		content += `(钢联化工数据库)`
+	case 2: //上海钢联数据库
+		content += `(上海钢联数据库)`
 		tmpList, tmpErr := data_manage.GetMysteelChemicalIndexListByIndexId(dataId)
 		if tmpErr != nil {
 			err = tmpErr
@@ -775,7 +775,7 @@ func MoveEdbChart(source, subSource, oldUserId, newUserId int, isSelectAll bool,
 // @Description: 通过原创建人转移指标/图表创建人
 // @author: Roc
 // @datetime 2024-03-26 15:11:12
-// @param sourceList []int 1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+// @param sourceList []int 1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 // @param oldUserId []int
 // @param userId int
 // @param opUserId int
@@ -825,8 +825,8 @@ func MoveAllEdbChartOld(sourceList, oldUserIdList []int, userId, opUserId int) (
 				err = models.ModifyEdbinfoUserIdByOldUserId(oldUserIdList, userId)
 			}
 
-		case 2: //钢联化工数据库
-			sourceStrList = append(sourceStrList, "钢联化工数据库")
+		case 2: //上海钢联数据库
+			sourceStrList = append(sourceStrList, "上海钢联数据库")
 			tmpList, tmpErr := data_manage.GetMysteelChemicalIndexListByUserId(oldUserIdList)
 			if tmpErr != nil {
 				err = tmpErr
@@ -1027,7 +1027,7 @@ func MoveAllEdbChartOld(sourceList, oldUserIdList []int, userId, opUserId int) (
 // @Description: 通过原创建人转移指标/图表创建人
 // @author: Roc
 // @datetime 2024-03-26 15:11:12
-// @param sourceList []int 1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+// @param sourceList []int 1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 // @param oldUserId []int
 // @param userId int
 // @param opUserId int
@@ -1049,7 +1049,7 @@ func MoveAllEdbChart(sourceList, oldUserIdList []int, userId, opUserId int) (err
 	var isMoveManual, isMoveMysteelChemical, isMoveEdb, isMovePredictEdb, isMoveChart, isMoveExcel bool
 	var customAnalysisIds []int
 
-	// 遍历需要转移的模块,1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格,并找出当前需要转移的资产
+	// 遍历需要转移的模块,1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格,并找出当前需要转移的资产
 	for _, source := range sourceList {
 		switch source {
 		case 1: //手工数据指标
@@ -1080,8 +1080,8 @@ func MoveAllEdbChart(sourceList, oldUserIdList []int, userId, opUserId int) (err
 				isMoveManual = true
 			}
 
-		case 2: //钢联化工数据库
-			sourceStrList = append(sourceStrList, "钢联化工数据库")
+		case 2: //上海钢联数据库
+			sourceStrList = append(sourceStrList, "上海钢联数据库")
 			tmpList, tmpErr := data_manage.GetMysteelChemicalIndexListByUserId(oldUserIdList)
 			if tmpErr != nil {
 				err = tmpErr
@@ -1311,7 +1311,7 @@ func GetMoveEdbChartCount(userId, countType int) (sourceMap map[int]int, err err
 	}
 
 	{
-		// 钢联化工数据库
+		// 上海钢联数据库
 		var condition string
 		var pars []interface{}
 		if userId > 0 {

+ 1 - 1
services/data/data_manage_permission/message.go

@@ -7,7 +7,7 @@ import (
 
 type MessageDetailItem struct {
 	DataPermissionMoveRecordId int64  ` orm:"column(data_permission_move_record_id);pk"` // 数据操作记录id
-	Source                     int32  // 数据来源,1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+	Source                     int32  // 数据来源,1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 	SubSource                  int32  // 子来源 :ETA表格中的各种表格类型,以及图表的来源(这个是后续的扩展方向)
 	OpUniqueCode               string // 操作的唯一编码,主要是记录统一操作的日志
 	DataId                     string // 资产id(指标、图表、表格)

+ 61 - 9
services/data/edb_info.go

@@ -1819,7 +1819,7 @@ func EdbInfoAdd(source, subSource, classifyId int, edbCode, edbName, frequency,
 		utils.DATA_SOURCE_MANUAL:              "手工数据",
 		utils.DATA_SOURCE_LZ:                  "隆众",
 		utils.DATA_SOURCE_YS:                  "SMM",
-		utils.DATA_SOURCE_GL:                  "钢联",
+		utils.DATA_SOURCE_GL:                  "上海钢联",
 		utils.DATA_SOURCE_ZZ:                  "郑商所",
 		utils.DATA_SOURCE_DL:                  "大商所",
 		utils.DATA_SOURCE_SH:                  "上期所",
@@ -1829,7 +1829,7 @@ func EdbInfoAdd(source, subSource, classifyId int, edbCode, edbName, frequency,
 		utils.DATA_SOURCE_LT:                  "路透",
 		utils.DATA_SOURCE_COAL:                "中国煤炭市场网",
 		utils.DATA_SOURCE_GOOGLE_TRAVEL:       "our world in data",
-		utils.DATA_SOURCE_MYSTEEL_CHEMICAL:    "钢联",
+		utils.DATA_SOURCE_MYSTEEL_CHEMICAL:    "上海钢联",
 		utils.DATA_SOURCE_EIA_STEO:            "EIA STEO报告",
 		utils.DATA_SOURCE_COM_TRADE:           "UN",
 		utils.DATA_SOURCE_SCI:                 "SCI",
@@ -2470,7 +2470,7 @@ func determineDateRange(index, totalLength int, formulas []map[string]string) st
 }
 
 // GetEdbChartAdminList
-// @param source 来源 :1:手工数据指标 2:钢联化工数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
+// @param source 来源 :1:手工数据指标 2:上海钢联数据库 3:ETA指标库 4:ETA预测指标 5:图库 6:ETA表格
 func GetEdbChartAdminList(source int) (list []int, err error) {
 	switch source {
 	case 1: //手工数据指标
@@ -2479,7 +2479,7 @@ func GetEdbChartAdminList(source int) (list []int, err error) {
 			return
 		}
 
-	case 2: //钢联化工数据库
+	case 2: //上海钢联数据库
 		list, err = data_manage.GetMysteelChemicalIndexAdminList()
 		if err != nil {
 			return
@@ -2594,7 +2594,7 @@ func EdbInfoWsdAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo, err
 		utils.DATA_SOURCE_MANUAL:              "手工数据",
 		utils.DATA_SOURCE_LZ:                  "隆众",
 		utils.DATA_SOURCE_YS:                  "SMM",
-		utils.DATA_SOURCE_GL:                  "钢联",
+		utils.DATA_SOURCE_GL:                  "上海钢联",
 		utils.DATA_SOURCE_ZZ:                  "郑商所",
 		utils.DATA_SOURCE_DL:                  "大商所",
 		utils.DATA_SOURCE_SH:                  "上期所",
@@ -2604,7 +2604,7 @@ func EdbInfoWsdAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo, err
 		utils.DATA_SOURCE_LT:                  "路透",
 		utils.DATA_SOURCE_COAL:                "中国煤炭市场网",
 		utils.DATA_SOURCE_GOOGLE_TRAVEL:       "our world in data",
-		utils.DATA_SOURCE_MYSTEEL_CHEMICAL:    "钢联",
+		utils.DATA_SOURCE_MYSTEEL_CHEMICAL:    "上海钢联",
 		utils.DATA_SOURCE_EIA_STEO:            "EIA STEO报告",
 		utils.DATA_SOURCE_COM_TRADE:           "UN",
 		utils.DATA_SOURCE_SCI:                 "SCI",
@@ -2793,7 +2793,7 @@ func EdbInfoSmmApiAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo,
 		utils.DATA_SOURCE_MANUAL:              "手工数据",
 		utils.DATA_SOURCE_LZ:                  "隆众",
 		utils.DATA_SOURCE_YS:                  "SMM",
-		utils.DATA_SOURCE_GL:                  "钢联",
+		utils.DATA_SOURCE_GL:                  "上海钢联",
 		utils.DATA_SOURCE_ZZ:                  "郑商所",
 		utils.DATA_SOURCE_DL:                  "大商所",
 		utils.DATA_SOURCE_SH:                  "上期所",
@@ -2803,7 +2803,7 @@ func EdbInfoSmmApiAdd(item *data_manage.EdbInfo) (edbInfo *data_manage.EdbInfo,
 		utils.DATA_SOURCE_LT:                  "路透",
 		utils.DATA_SOURCE_COAL:                "中国煤炭市场网",
 		utils.DATA_SOURCE_GOOGLE_TRAVEL:       "our world in data",
-		utils.DATA_SOURCE_MYSTEEL_CHEMICAL:    "钢联",
+		utils.DATA_SOURCE_MYSTEEL_CHEMICAL:    "上海钢联",
 		utils.DATA_SOURCE_EIA_STEO:            "EIA STEO报告",
 		utils.DATA_SOURCE_COM_TRADE:           "UN",
 		utils.DATA_SOURCE_SCI:                 "SCI",
@@ -3009,7 +3009,7 @@ func GetEdbTerminalCodeBySource(source int, edbCode, stockCode string) (terminal
 // @datetime 2024-07-22 13:06:36
 // @param edbInfo *data_manage.EdbInfo
 func handleByAddEdbInfo(edbInfo *data_manage.EdbInfo) {
-	// 更新钢联化工状态为启用
+	// 更新上海钢联状态为启用
 	if edbInfo.Source == utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
 		// 启动钢联的刷新
 		_ = data_manage.UpdateMysteelChemicalRefreshStatus(edbInfo.EdbCode, 0)
@@ -3291,3 +3291,55 @@ func ResetBaseFromIndexEdbExist(source int, indexCode string) (err error) {
 	}
 	return
 }
+
+// CheckWindThsDsParams wind同花顺日期序列参数校验
+func CheckWindThsDsParams(source, subSource int, period, days, priceAdj string) (ok bool, tips string) {
+	if source != utils.DATA_SOURCE_WIND && source != utils.DATA_SOURCE_THS {
+		ok = true
+		return
+	}
+	if subSource != utils.DATA_SUB_SOURCE_DATE {
+		ok = true
+		return
+	}
+
+	if source == utils.DATA_SOURCE_WIND {
+		periodArr := []string{
+			utils.WindWsdPeriodDefault, utils.WindWsdPeriodWeek, utils.WindWsdPeriodMonth,
+			utils.WindWsdPeriodQuarter, utils.WindWsdPeriodYear,
+		}
+		if period != "" && !utils.InArrayByStr(periodArr, period) {
+			tips = "周期类型有误"
+			return
+		}
+		daysArr := []string{utils.WindWsdDaysWeekdays, utils.WindWsdDaysDefault, utils.WindWsdDaysAlldays}
+		if days != "" && !utils.InArrayByStr(daysArr, days) {
+			tips = "日期类型有误"
+			return
+		}
+		priceAdjArr := []string{utils.WindWsdRestorationFront, utils.WindWsdRestorationBack, utils.WindWsdRestorationFixed}
+		if priceAdj != "" && !utils.InArrayByStr(priceAdjArr, priceAdj) {
+			tips = "复权方式有误"
+			return
+		}
+	}
+
+	if source == utils.DATA_SOURCE_THS {
+		periodArr := []string{
+			utils.WindWsdPeriodDefault, utils.WindWsdPeriodWeek, utils.WindWsdPeriodMonth,
+			utils.WindWsdPeriodQuarter, utils.ThsDsdPeriodHalfYear, utils.WindWsdPeriodYear,
+		}
+		if period != "" && !utils.InArrayByStr(periodArr, period) {
+			tips = "周期类型有误"
+			return
+		}
+		daysArr := []string{utils.ThsDsDaysDefault, utils.ThsDsDaysAlldays}
+		if days != "" && !utils.InArrayByStr(daysArr, days) {
+			tips = "日期类型有误"
+			return
+		}
+	}
+
+	ok = true
+	return
+}

+ 1 - 1
services/data/edb_info_relation.go

@@ -55,7 +55,7 @@ func saveEdbInfoRelation(edbInfoIds []int, objectId, objectType, objectSubType i
 		err = fmt.Errorf("查询计算指标信息失败,%s", e.Error())
 		return
 	}
-	// 只统计钢联化工和wind来源的指标
+	// 只统计上海钢联和wind来源的指标
 	for _, edbInfo := range edbInfoList {
 		/*if edbInfo.Source != utils.DATA_SOURCE_WIND && edbInfo.Source != utils.DATA_SOURCE_MYSTEEL_CHEMICAL {
 			continue

+ 397 - 0
services/data/edb_inspection.go

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

+ 114 - 0
services/data/edb_inspection_message.go

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

+ 1 - 1
services/data/excel/excel_info.go

@@ -757,7 +757,7 @@ func GetEdbSourceByEdbInfoIdList(edbInfoIdList []int) (sourceNameList, sourceNam
 	}
 
 	for source, sourceName := range sourceMap {
-		if utils.InArrayByInt([]int{utils.DATA_SOURCE_MANUAL, utils.DATA_SOURCE_MYSTEEL_CHEMICAL}, source) {
+		if utils.InArrayByInt([]int{utils.DATA_SOURCE_MANUAL}, source) {
 			continue
 		}
 		sourceNameList = append(sourceNameList, sourceName)

+ 15 - 15
services/data/mysteel_chemical.go

@@ -15,7 +15,7 @@ import (
 	"github.com/rdlucklib/rdluck_tools/http"
 )
 
-// AddMysteelChemicalClassify 添加钢联化工分类
+// AddMysteelChemicalClassify 添加上海钢联分类
 func AddMysteelChemicalClassify(classifyName string, parentId, level, sysUserId int, sysUserName, lange string) (classifyInfo *data_manage.BaseFromMysteelChemicalClassify, err error, errMsg string) {
 	// 校验分类名称相同的数量
 	{
@@ -59,7 +59,7 @@ func AddMysteelChemicalClassify(classifyName string, parentId, level, sysUserId
 	return
 }
 
-// EditMysteelChemicalClassify 编辑钢联化工分类
+// EditMysteelChemicalClassify 编辑上海钢联分类
 func EditMysteelChemicalClassify(classifyId int, classifyName, lang string, sysUser *system.Admin) (err error, errMsg string) {
 	item, err := data_manage.GetBaseFromMysteelChemicalClassifyById(classifyId)
 	if err != nil {
@@ -128,7 +128,7 @@ func EditMysteelChemicalClassify(classifyId int, classifyName, lang string, sysU
 	return
 }
 
-// MoveMysteelChemicalClassify 移动钢联化工分类
+// MoveMysteelChemicalClassify 移动上海钢联分类
 func MoveMysteelChemicalClassify(classifyId, parentClassifyId, prevClassifyId, nextClassifyId int, sysUser *system.Admin) (err error, errMsg string) {
 	//判断分类是否存在
 	classifyInfo, err := data_manage.GetBaseFromMysteelChemicalClassifyById(classifyId)
@@ -228,7 +228,7 @@ func MoveMysteelChemicalClassify(classifyId, parentClassifyId, prevClassifyId, n
 	return
 }
 
-// DelMysteelChemicalClassify 删除钢联化工分类
+// DelMysteelChemicalClassify 删除上海钢联分类
 func DelMysteelChemicalClassify(classifyId int, sysUser *system.Admin) (err error, errMsg string) {
 	//判断分类是否存在
 	classifyInfo, err := data_manage.GetBaseFromMysteelChemicalClassifyById(classifyId)
@@ -277,7 +277,7 @@ func DelMysteelChemicalClassify(classifyId int, sysUser *system.Admin) (err erro
 		indexCodeList = append(indexCodeList, v.IndexCode)
 	}
 
-	// 获取已经加入到EDB指标库的钢联化工指标
+	// 获取已经加入到EDB指标库的上海钢联指标
 	edbInfoList, err := data_manage.GetEdbInfoByEdbCodeList(utils.DATA_SOURCE_MYSTEEL_CHEMICAL, indexCodeList)
 	if err != nil {
 		errMsg = "删除失败"
@@ -304,7 +304,7 @@ func DelMysteelChemicalClassify(classifyId int, sysUser *system.Admin) (err erro
 	return
 }
 
-// BatchAddMysteelChemicalIndex 批量添加钢联化工指标
+// BatchAddMysteelChemicalIndex 批量添加上海钢联指标
 func BatchAddMysteelChemicalIndex(items []*data_manage.BaseFromMysteelChemicalIndex, lang string) (baseFromMysteelChemicalIndexs []*data_manage.BaseFromMysteelChemicalIndex, err error, errMsg string) {
 	indexCodeList := make([]string, 0)
 	for _, v := range items {
@@ -391,7 +391,7 @@ type MysteelChemicalIndexSource2EdbReq struct {
 	AdminRealName string
 }
 
-// MysteelChemicalIndexSource2Edb 新增钢联化工数据源到指标库
+// MysteelChemicalIndexSource2Edb 新增上海钢联数据源到指标库
 func MysteelChemicalIndexSource2Edb(req MysteelChemicalIndexSource2EdbReq, lang string) (edb *data_manage.EdbInfo, err error, errMsg string, skip bool) {
 	if req.EdbCode == "" {
 		err = fmt.Errorf("指标ID为空")
@@ -433,7 +433,7 @@ func MysteelChemicalIndexSource2Edb(req MysteelChemicalIndexSource2EdbReq, lang
 	return
 }
 
-// AddMysteelChemicalIndex 添加钢联化工指标
+// AddMysteelChemicalIndex 添加上海钢联指标
 func AddMysteelChemicalIndex(classifyId int, indexCode, updateWeek, updateTimeStr string, sysUserId int, sysUserName, lang string) (baseFromMysteelChemicalIndex *data_manage.BaseFromMysteelChemicalIndex, err error, errMsg string) {
 	baseFromMysteelChemicalIndex, err = data_manage.GetBaseFromMysteelChemicalIndexByCode(indexCode)
 	if err != nil && !utils.IsErrNoRow(err) {
@@ -493,7 +493,7 @@ func AddMysteelChemicalIndex(classifyId int, indexCode, updateWeek, updateTimeSt
 	return
 }
 
-// EditMysteelChemicalIndex 编辑钢联化工指标
+// EditMysteelChemicalIndex 编辑上海钢联指标
 func EditMysteelChemicalIndex(indexId, classifyId int, updateWeek, updateTimeStr string, sysUser *system.Admin) (baseFromMysteelChemicalIndex *data_manage.BaseFromMysteelChemicalIndex, err error, errMsg string) {
 	baseFromMysteelChemicalIndex, err = data_manage.GetBaseFromMysteelChemicalIndexByIndexId(indexId)
 	if err != nil {
@@ -542,7 +542,7 @@ func EditMysteelChemicalIndex(indexId, classifyId int, updateWeek, updateTimeStr
 	return
 }
 
-// DelMysteelChemical 删除钢联化工指标
+// DelMysteelChemical 删除上海钢联指标
 func DelMysteelChemical(indexId int, sysUser *system.Admin) (err error, errMsg string) {
 	baseFromMysteelChemicalIndex, err := data_manage.GetBaseFromMysteelChemicalIndexByIndexId(indexId)
 	if err != nil {
@@ -561,7 +561,7 @@ func DelMysteelChemical(indexId int, sysUser *system.Admin) (err error, errMsg s
 		return
 	}
 
-	// 获取已经加入到EDB指标库的钢联化工指标
+	// 获取已经加入到EDB指标库的上海钢联指标
 	edbInfo, err := data_manage.GetEdbInfoByEdbCode(utils.DATA_SOURCE_MYSTEEL_CHEMICAL, baseFromMysteelChemicalIndex.IndexCode)
 	if err != nil && !utils.IsErrNoRow(err) {
 		errMsg = "删除失败"
@@ -585,7 +585,7 @@ func DelMysteelChemical(indexId int, sysUser *system.Admin) (err error, errMsg s
 	return
 }
 
-// MoveMysteelChemical 移动钢联化工指标
+// MoveMysteelChemical 移动上海钢联指标
 func MoveMysteelChemical(indexId, classifyId, prevIndexId, nextIndexId int, sysUser *system.Admin) (err error, errMsg string) {
 	//分类信息
 	baseFromMysteelChemicalIndex, err := data_manage.GetBaseFromMysteelChemicalIndexByIndexId(indexId)
@@ -704,7 +704,7 @@ func MoveMysteelChemical(indexId, classifyId, prevIndexId, nextIndexId int, sysU
 	return
 }
 
-// GetMysteelChemicalOpButton 获取钢联化工的操作权限
+// GetMysteelChemicalOpButton 获取上海钢联的操作权限
 func GetMysteelChemicalOpButton(sysUser *system.Admin, belongUserId int) (button data_manage.BaseFromMysteelChemicalClassifyItemsButton) {
 	// 统一跟随角色权限进行管理分类, 不对特定角色进行权限控制
 	button.AddButton = true
@@ -836,11 +836,11 @@ func RefreshMysteelChemicalData(edbCode string) {
 	var errMsg string
 	defer func() {
 		if err != nil {
-			go alarm_msg.SendAlarmMsg("根据钢联化工的code刷新指标数据失败提醒,Err"+err.Error(), 3)
+			go alarm_msg.SendAlarmMsg("根据上海钢联的code刷新指标数据失败提醒,Err"+err.Error(), 3)
 			//go utils.SendEmail(utils.APPNAME+"【"+utils.RunMode+"】"+"导入手工数据后,根据手工指标code刷新手工指标数据失败提醒", "errmsg:"+err.Error(), utils.EmailSendToUsers)
 		}
 		if errMsg != "" {
-			go alarm_msg.SendAlarmMsg("根据钢联化工的code刷新指标数据失败提醒,errMsg"+errMsg, 3)
+			go alarm_msg.SendAlarmMsg("根据上海钢联的code刷新指标数据失败提醒,errMsg"+errMsg, 3)
 		}
 	}()
 

+ 7 - 2
services/data/predict_edb_info.go

@@ -612,7 +612,7 @@ func GetChartPredictEdbInfoDataListByConfList(predictEdbConfList []data_manage.P
 
 	predictEdbInfoData = make([]*data_manage.EdbDataList, 0)
 	//dataValue := lastDataValue
-	//预测规则,1:最新,2:固定值,3:同比,4:同差,5:环比,6:环差,7:N期移动均值,8:N期段线性外推值
+	//预测规则,1:最新,2:固定值,3:同比,4:同差,5:环比,6:环差,7:N期移动均值,8:N期段线性外推值,17:动态同比,18:动态同差
 
 	for _, predictEdbConf := range predictEdbConfList {
 		dataEndTime := endDate
@@ -814,8 +814,13 @@ func GetChartPredictEdbInfoDataListByConfList(predictEdbConfList []data_manage.P
 			if err != nil {
 				return
 			}
-		}
 
+		case 17, 18:
+			predictEdbInfoData, tmpMinValue, tmpMaxValue, err = GetChartPredictEdbInfoDataListByRuleDynamicYOYComparisonOrDifference(predictEdbConf.RuleType, predictEdbConf.PredictEdbInfoId, predictEdbConf.Value, dayList, realPredictEdbInfoData, existMap)
+			if err != nil {
+				return
+			}
+		}
 		// 下一个规则的开始日期
 		{
 			lenPredictEdbInfoData := len(predictEdbInfoData)

+ 190 - 55
services/data/predict_edb_info_rule.go

@@ -9,6 +9,7 @@ import (
 	"github.com/nosixtools/solarlunar"
 	"github.com/shopspring/decimal"
 	"math"
+	"strconv"
 	"strings"
 	"time"
 )
@@ -774,6 +775,8 @@ func GetChartPredictEdbInfoDataListByRuleSeason(edbInfoId int, configValue strin
 		tmpHistoryVal := decimal.NewFromFloat(0) //往期的差值总和
 		tmpHistoryValNum := 0                    // 往期差值计算的数量
 
+		//fmt.Println(`填充后的数据长度:`, len(handleDataMap))
+
 		tmpLenAllDataList := len(allDataList)
 		tmpK := tmpLenAllDataList - 1    //上1期数据的下标
 		lastDayData := allDataList[tmpK] // 上1期的数据
@@ -783,6 +786,7 @@ func GetChartPredictEdbInfoDataListByRuleSeason(edbInfoId int, configValue strin
 		if tmpErr != nil {
 			err = errors.New("获取上期日期转换失败:" + tmpErr.Error())
 		}
+		//fmt.Println("当前需要计算的日期:", currentDate.Format(utils.FormatDate))
 		for _, year := range yearList {
 			moveDay := moveDayMap[year] //需要移动的天数
 			var tmpHistoryCurrentVal, tmpHistoryLastVal float64
@@ -790,39 +794,43 @@ func GetChartPredictEdbInfoDataListByRuleSeason(edbInfoId int, configValue strin
 
 			//前几年当日的日期
 			tmpHistoryCurrentDate := currentDate.AddDate(year-currentDate.Year(), 0, -moveDay)
-			for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找
-				tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, i)
-				if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
-					tmpHistoryCurrentVal = val
-					isFindHistoryCurrent = true
-					break
-				} else {
-					tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, -i)
-					if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
-						tmpHistoryCurrentVal = val
-						isFindHistoryCurrent = true
-						break
-					}
-				}
-			}
+			tmpHistoryCurrentVal, isFindHistoryCurrent = handleDataMap[tmpHistoryCurrentDate.Format(utils.FormatDate)]
+			//for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找(但是这个其实没有意义,因为这整个数据都通过插值法进行数据填充了, 2025-5-30 14:10:22)
+			//	tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, i)
+			//	if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
+			//		tmpHistoryCurrentVal = val
+			//		isFindHistoryCurrent = true
+			//		break
+			//	} else {
+			//		tmpDate := tmpHistoryCurrentDate.AddDate(0, 0, -i)
+			//		if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
+			//			tmpHistoryCurrentVal = val
+			//			isFindHistoryCurrent = true
+			//			break
+			//		}
+			//	}
+			//}
 
 			//前几年上一期的日期
 			tmpHistoryLastDate := lastDay.AddDate(year-lastDay.Year(), 0, -moveDay)
-			for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找
-				tmpDate := tmpHistoryLastDate.AddDate(0, 0, i)
-				if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
-					tmpHistoryLastVal = val
-					isFindHistoryLast = true
-					break
-				} else {
-					tmpDate := tmpHistoryLastDate.AddDate(0, 0, -i)
-					if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
-						tmpHistoryLastVal = val
-						isFindHistoryLast = true
-						break
-					}
-				}
-			}
+			tmpHistoryLastVal, isFindHistoryLast = handleDataMap[tmpHistoryLastDate.Format(utils.FormatDate)]
+			//for i := 0; i <= 35; i++ { // 前后35天找数据,找到最近的值,先向后面找,再往前面找(但是这个其实没有意义,因为这整个数据都通过插值法进行数据填充了, 2025-5-30 14:10:22)
+			//	tmpDate := tmpHistoryLastDate.AddDate(0, 0, i)
+			//	if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
+			//		tmpHistoryLastVal = val
+			//		isFindHistoryLast = true
+			//		break
+			//	} else {
+			//		tmpDate := tmpHistoryLastDate.AddDate(0, 0, -i)
+			//		if val, ok := handleDataMap[tmpDate.Format(utils.FormatDate)]; ok {
+			//			tmpHistoryLastVal = val
+			//			isFindHistoryLast = true
+			//			break
+			//		}
+			//	}
+			//}
+			
+			//fmt.Println("第一期日期:", tmpHistoryCurrentDate.Format(utils.FormatDate), ";第二期的日期:", tmpHistoryLastDate.Format(utils.FormatDate))
 
 			// 如果两个日期对应的数据都找到了,那么计算两期的差值
 			if isFindHistoryCurrent && isFindHistoryLast {
@@ -1536,6 +1544,158 @@ type AnnualValueInversionConf struct {
 	YearList []int   `description:"指定年份列表"`
 }
 
+func getReplaceValue(replaceValueMap map[string]float64, days, dayStepForward int,dayStepBack int, currentDate time.Time) (replaceValue decimal.Decimal, success bool) {
+	nextDateDay := currentDate
+	backDateDay := currentDate
+	for i := 0; i <= days; i++ {
+		nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+		if preValue, ok := replaceValueMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+			replaceValue = decimal.NewFromFloat(preValue)
+			success = true
+			return
+		}
+		backDateDayStr := backDateDay.Format(utils.FormatDate)
+		if backValue,ok:=replaceValueMap[backDateDayStr]; ok { //上一年同期->下一个月找到
+			replaceValue = decimal.NewFromFloat(backValue)
+			success = true
+			return
+		}
+		nextDateDay = nextDateDay.AddDate(0, 0, dayStepForward)
+		backDateDay = nextDateDay.AddDate(0, 0, dayStepBack)
+	}
+	return decimal.NewFromInt(0), false
+}
+
+// GetChartPredictEdbInfoDataListByRuleDynamicYOYComparisonOrDifference 动态同比
+// 2、指标选择范围为预测指标。
+// 3、动态同比计算方法:预测值=去年同期值*(1+同比指标预测值)
+// 4、上述“去年同期”如果没有严格对应的日期,则前后查找最近35天的值。
+// 5、选择的同比指标日期需要与预测指标未来日期对应上,对应不上的不生成预测值。
+func GetChartPredictEdbInfoDataListByRuleDynamicYOYComparisonOrDifference(ruleType, edbInfoId int, configValue string, dayList []time.Time, realPredictEdbInfoData []*data_manage.EdbDataList, existMap map[string]float64) (newPredictEdbInfoData []*data_manage.EdbDataList, minValue, maxValue float64, err error) {
+	//预测指标的去年同期数据
+	baseDynamicDataList := make(map[string]decimal.Decimal, len(dayList))
+	//动态同比同差指标
+	DynamicCalculateDataList := make(map[string]decimal.Decimal, len(dayList))
+	index := len(realPredictEdbInfoData)
+	if index <= 0 {
+		return
+	}
+	dynamicYOYComparisonIndexId, err := strconv.Atoi(configValue)
+	if err != nil {
+		return
+	}
+	newPredictEdbInfoData = make([]*data_manage.EdbDataList, 0, len(dayList))
+	// 获取同比预测指标的预测数据
+	dynamicYOYComparisonIndex, err := data_manage.GetEdbInfoById(dynamicYOYComparisonIndexId)
+	if err != nil {
+		return
+	}
+	if dynamicYOYComparisonIndex.EdbInfoType != 1 {
+		err = errors.New("选择的指标不是预测指标")
+		return
+	}
+	startDate, endDate := dayList[0].Format(utils.FormatDate), dayList[len(dayList)-1].Format(utils.FormatDate)
+	//获取动态同比指标对应预测日期的预测数据
+	dynamicYOYComparisonIndexDataList, err := data_manage.GetEdbDataList(dynamicYOYComparisonIndex.Source, dynamicYOYComparisonIndex.SubSource, dynamicYOYComparisonIndex.EdbInfoId, startDate, endDate)
+	if err != nil {
+		return
+	}
+	if len(dynamicYOYComparisonIndexDataList) <= 0 {
+		return
+	} else {
+		for _, v := range dynamicYOYComparisonIndexDataList {
+			DynamicCalculateDataList[v.DataTime] = decimal.NewFromFloat(v.Value)
+		}
+	}
+	var predictDayList []time.Time
+	//获取上一期的同期数据
+	for _, date := range dayList {
+		preDate := date.AddDate(-1, 0, 0)
+		preDateStr := preDate.Format(utils.FormatDate)
+		if preValue, ok := existMap[preDateStr]; ok { //上一年同期找到
+			baseDynamicDataList[preDateStr] = decimal.NewFromFloat(preValue)
+			predictDayList = append(predictDayList, date)
+		} else {
+			if replaceValue, replaceOk := getReplaceValue(existMap, 35, 1,-1, preDate); !replaceOk {
+				continue
+			} else {
+				baseDynamicDataList[preDateStr] = replaceValue
+				predictDayList = append(predictDayList, date)
+			}
+		}
+	}
+
+	//获取后面的预测数据
+
+	for k, currentDate := range predictDayList {
+		var calculateValue decimal.Decimal
+		var dateStr = currentDate.Format(utils.FormatDate)
+		preDate := currentDate.AddDate(-1, 0, 0)
+		preDateStr := preDate.Format(utils.FormatDate)
+		_, dynamicVal := DynamicCalculateDataList[dateStr];
+		_, baseVal := baseDynamicDataList[preDateStr];
+		if dynamicVal && baseVal{
+			switch ruleType {
+			case 17:
+				calculateValue = baseDynamicDataList[preDateStr].Mul(DynamicCalculateDataList[dateStr].Add(decimal.NewFromInt(1)))
+			case 18:
+				calculateValue = baseDynamicDataList[preDateStr].Add(DynamicCalculateDataList[dateStr])
+			default:
+				err = errors.New("计算规则不存在")
+				return
+			}
+			tmpData := &data_manage.EdbDataList{
+				EdbDataId:     edbInfoId + 100000 + index + k,
+				EdbInfoId:     edbInfoId,
+				DataTime:      currentDate.Format(utils.FormatDate),
+				DataTimestamp: currentDate.UnixNano() / 1e6,
+			}
+			var val = calculateValue.InexactFloat64()
+			tmpData.Value = val
+			newPredictEdbInfoData = append(newPredictEdbInfoData, tmpData)
+			existMap[tmpData.DataTime] = val
+			if k == 0 {
+				minValue = val
+				maxValue = val
+			} else {
+				// 最大最小值
+				if val < minValue {
+					minValue = val
+				}
+				if val > maxValue {
+					maxValue = val
+				}
+			}
+		}
+	}
+	return
+}
+
+// getYearListBySeasonConf 根据配置获取年份列表
+func getYearListBySeasonConf(configValue string) (yearList []int, seasonConf SeasonConf, err error) {
+	tmpErr := json.Unmarshal([]byte(configValue), &seasonConf)
+	if tmpErr != nil {
+		err = errors.New("年份配置信息异常:" + tmpErr.Error())
+		return
+	}
+	//选择方式,1:连续N年;2:指定年份
+	if seasonConf.YearType == 1 {
+		if seasonConf.NValue < 1 {
+			err = errors.New("连续N年不允许小于1")
+			return
+		}
+
+		currYear := time.Now().Year()
+		for i := 0; i < seasonConf.NValue; i++ {
+			yearList = append(yearList, currYear-i-1)
+		}
+	} else {
+		yearList = seasonConf.YearList
+	}
+
+	return
+}
+
 // GetChartPredictEdbInfoDataListByRuleAnnualValueInversion 根据 年度值倒推 规则获取预测数据
 // 预测指标-年度值倒推
 // 1、年度值倒推,选择同比法,支持选择多个年份(当前只可选择一个年份)。选择多个年份时,计算多个年份的余额平均,和同期平均。
@@ -1896,28 +2056,3 @@ func GetChartPredictEdbInfoDataListByRuleAnnualValueInversion(edbInfoId int, con
 
 	return
 }
-
-// getYearListBySeasonConf 根据配置获取年份列表
-func getYearListBySeasonConf(configValue string) (yearList []int, seasonConf SeasonConf, err error) {
-	tmpErr := json.Unmarshal([]byte(configValue), &seasonConf)
-	if tmpErr != nil {
-		err = errors.New("年份配置信息异常:" + tmpErr.Error())
-		return
-	}
-	//选择方式,1:连续N年;2:指定年份
-	if seasonConf.YearType == 1 {
-		if seasonConf.NValue < 1 {
-			err = errors.New("连续N年不允许小于1")
-			return
-		}
-
-		currYear := time.Now().Year()
-		for i := 0; i < seasonConf.NValue; i++ {
-			yearList = append(yearList, currYear-i-1)
-		}
-	} else {
-		yearList = seasonConf.YearList
-	}
-
-	return
-}

+ 136 - 39
services/edb_monitor/edb_monitor_message.go

@@ -1,23 +1,19 @@
 package edbmonitor
 
 import (
+	"encoding/json"
 	"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"
+	"fmt"
 	"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 +47,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 +87,33 @@ 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.AdminWebSocketConnMap[adminId]
 	if conn == nil {
-		return errors.New("no connection")
+		err = errors.New("no connection")
+		return
 	}
-	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,
+		},
+	}
+	jsonData, err := json.Marshal(resp)
+	if err != nil {
+		err = fmt.Errorf("json marshal failed, err:%s", err.Error())
+		return
 	}
-	return conn.WriteJSON(msg)
+	ok := conn.Send(jsonData)
+	if !ok {
+		err = fmt.Errorf("send message failed, err:%s", err.Error())
+		return
+	}
+	return
 }
 
 func GetHistoryMessages(adminId int) (items []*response.EdbMonitorMessageResp, err error) {
@@ -156,3 +166,90 @@ func toEdbMonitorMessageResp(items []*edbmonitor.EdbMonitorMessage) (list []*res
 	}
 	return
 }
+
+func AutoCheckMonitorMessageList(admins []int) (err error) {
+	if len(admins) == 0 {
+		return nil
+	}
+
+	//utils.FileLog.Info("检查是否有预警信息,活跃用户数: %d", len(admins))
+	
+	// 设置缓存防止重复发送,使用较短的锁定时间
+	cacheKey := fmt.Sprintf("%s", utils.CACHE_EDB_MONITOR_MESSAGE)
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		utils.FileLog.Info("其他进程正在处理预警信息,跳过本次检查")
+		return nil
+	}
+	defer func() {
+		_ = utils.Rc.Delete(cacheKey)
+	}()
+	messageList, er := edbmonitor.GetEdbMonitorMessageUnreadListByAdminIds(admins)
+	if er != nil {
+		err = fmt.Errorf("获取指标预警信息历史失败,err:%s", er.Error())
+		return
+	}
+	readList := make([]int, 0)
+	adminMsgMap := make(map[int]int)
+	for _, msg := range messageList {
+		if _, ok := adminMsgMap[msg.AdminId]; !ok {
+			adminMsgMap[msg.AdminId] = msg.EdbMonitorMessageId
+			triggerTime := utils.TimeTransferString(utils.FormatDateTime, msg.MonitorTriggerTime)
+			err := SendMessages(msg.AdminId, msg.EdbInfoId, msg.EdbInfoType, msg.EdbClassifyId, msg.EdbUniqueCode, msg.Message, triggerTime)
+			if err != nil {
+				utils.FileLog.Error("指标预警信息发送失败,err:%s, adminId:%d", err.Error(), msg.AdminId)
+			} 
+		}
+		readList = append(readList, msg.EdbMonitorMessageId)
+	}
+
+	err = edbmonitor.SetEdbMonitorMessageReadByIds(readList)
+	if err != nil {
+		err = fmt.Errorf("指标预警信息已读失败,err:%s", err.Error())
+		return
+	}
+	return
+}
+
+func AutoCheckMonitorMessageListByAdminId(adminId int) (err error){
+	// 设置缓存防止重复发送
+	cacheKey := fmt.Sprintf("%s%d", utils.CACHE_EDB_MONITOR_MESSAGE, adminId)
+	if !utils.Rc.SetNX(cacheKey, 1, 10*time.Minute) {
+		err = fmt.Errorf("系统处理中,请稍后重试!")
+		utils.FileLog.Error("指标预警信息检查失败,err:%s", err.Error())
+		return
+	}
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("指标预警信息检查失败,err:%s", err.Error())
+		}
+		_ = utils.Rc.Delete(cacheKey)
+	}()
+	utils.FileLog.Info("检查是否有预警信息")
+	messageList, er := edbmonitor.GetEdbMonitorMessageUnreadByAdminId(adminId)
+	if er != nil {
+		err = fmt.Errorf("获取指标预警信息历史失败,err:%s", er.Error())
+		return
+	}
+	if len(messageList) == 0 {
+		return
+	}
+	readList := make([]int, 0)
+	for k, msg := range messageList {
+		if k == 0 {
+			triggerTime := utils.TimeTransferString(utils.FormatDateTime, msg.MonitorTriggerTime)
+			err = SendMessages(msg.AdminId, msg.EdbInfoId, msg.EdbInfoType, msg.EdbClassifyId, msg.EdbUniqueCode, msg.Message, triggerTime)
+			if err != nil {
+				utils.FileLog.Error("指标预警信息发送失败,err:%s, adminId:%d", err.Error(), msg.AdminId)
+				return
+			} 
+		}
+		readList = append(readList, msg.EdbMonitorMessageId)
+	}
+
+	err = edbmonitor.SetEdbMonitorMessageReadByAdminId(adminId, readList)
+	if err != nil {
+		err = fmt.Errorf("指标预警信息已读失败,err:%s", err.Error())
+		return
+	}
+	return
+}

+ 3 - 2
services/elastic/elastic.go

@@ -10,9 +10,10 @@ import (
 	dataSourceModel "eta/eta_api/models/data_source"
 	"eta/eta_api/utils"
 	"fmt"
-	"github.com/olivere/elastic/v7"
 	"strconv"
 	"strings"
+
+	"github.com/olivere/elastic/v7"
 )
 
 // indexName:索引名称
@@ -1939,7 +1940,7 @@ func EsAddOrEditDataSourceIndex(indexName, docId string, item *dataSourceModel.S
 	}()
 	client := utils.EsClient
 
-	resp, e := client.Index().Index(indexName).Id(docId).BodyJson(item).Refresh("true").Do(context.Background())
+	resp, e := client.Index().Index(indexName).Id(docId).BodyJson(item).Do(context.Background())
 	if e != nil {
 		err = fmt.Errorf("resp err, %v", e)
 		return

+ 59 - 51
services/file.go

@@ -108,7 +108,7 @@ func saveToFileByHeader(fileHeader *multipart.FileHeader, tofile string) error {
 // @return resourceUrl string
 // @return err error
 // @return errMsg string
-func GetResourceUrlBySvgImg(imgData string) (resourceUrl string, err error, errMsg string) {
+func GetResourceUrlBySvgImg(imgData string, notBackendGenerate bool) (resourceUrl string, err error, errMsg string) {
 	errMsg = "图表保存失败"
 	uploadDir := "static/images/"
 	if !utils.FileIsExist(uploadDir) {
@@ -118,69 +118,77 @@ func GetResourceUrlBySvgImg(imgData string) (resourceUrl string, err error, errM
 			return
 		}
 	}
-
-	//var saveToOssPath string
-	randStr := utils.GetRandStringNoSpecialChar(28)
 	var fileName, outFileName string
-	fileName = randStr + ".txt"
-	fileName = uploadDir + fileName
-	err = utils.SaveToFile(imgData, fileName)
-	if err != nil {
-		err = errors.New("图片保存失败,Err:" + err.Error())
-		return
-	}
+	uploadDir = "static/images/"
+	randStr := utils.GetRandStringNoSpecialChar(28)
+	outFileName = randStr + ".png"
 
-	// 删除临时存储的svg文件
-	defer func() {
-		err = os.Remove(fileName)
+	if notBackendGenerate {
+		err = utils.SaveBase64ToFile(imgData, outFileName)
 		if err != nil {
-			utils.FileLog.Info("删除临时存储的svg文件失败, err: " + err.Error())
+			err = errors.New("图片保存失败,Err:" + err.Error())
+			return
 		}
-	}()
-	outFileName = randStr + ".png"
-
-	doneChannel := make(chan bool, 1)
-	errorChannel := make(chan error, 1)
-
-	cmd := exec.Command("highcharts-export-server", "--infile", fileName, "--constr", "Chart", "--scale", "2", "--workers", "10", "--workLimit", "3", "--outfile", outFileName)
-
-	go func() {
-		output, err := cmd.CombinedOutput()
+	} else {
+		//var saveToOssPath string
+		fileName = randStr + ".txt"
+		fileName = uploadDir + fileName
+		err = utils.SaveToFile(imgData, fileName)
 		if err != nil {
-			utils.FileLog.Info("execute command failed, output: , error: \n" + string(output) + err.Error())
-			errorChannel <- err
+			err = errors.New("图片保存失败,Err:" + err.Error())
 			return
 		}
-		doneChannel <- true
-	}()
+
+		// 删除临时存储的svg文件
+		defer func() {
+			err = os.Remove(fileName)
+			if err != nil {
+				utils.FileLog.Info("删除临时存储的svg文件失败, err: " + err.Error())
+			}
+		}()
+
+		doneChannel := make(chan bool, 1)
+		errorChannel := make(chan error, 1)
+
+		cmd := exec.Command("highcharts-export-server", "--infile", fileName, "--constr", "Chart", "--scale", "2", "--workers", "10", "--workLimit", "3", "--outfile", outFileName)
+
+		go func() {
+			output, err := cmd.CombinedOutput()
+			if err != nil {
+				utils.FileLog.Info("execute command failed, output: , error: \n" + string(output) + err.Error())
+				errorChannel <- err
+				return
+			}
+			doneChannel <- true
+		}()
+
+		select {
+		case <-time.After(30 * time.Second):
+			utils.FileLog.Info("执行超过30秒 杀死超时进程")
+			e := cmd.Process.Kill()
+			if e != nil {
+				fmt.Println("cmd kill err: ", e.Error())
+				utils.FileLog.Info(fmt.Sprintf("cmd kill err: %s", e.Error()))
+				errMsg = "图片生成失败"
+				err = errors.New("图片生成失败, 执行超时" + e.Error())
+				return
+			}
+			fmt.Println("timeout kill process")
+		case <-doneChannel:
+			fmt.Println("done")
+		case e := <-errorChannel:
+			errMsg = "文件上传失败"
+			err = errors.New(fmt.Sprintf("execute command failure err: %s", e.Error()))
+			fmt.Println("execute command failure err:" + e.Error())
+			return
+		}
+	}
 	defer func() {
 		_ = os.Remove(outFileName)
 		if err != nil {
 			utils.FileLog.Info("删除生产的图片文件失败, err: " + err.Error())
 		}
 	}()
-
-	select {
-	case <-time.After(30 * time.Second):
-		utils.FileLog.Info("执行超过30秒 杀死超时进程")
-		e := cmd.Process.Kill()
-		if e != nil {
-			fmt.Println("cmd kill err: ", e.Error())
-			utils.FileLog.Info(fmt.Sprintf("cmd kill err: %s", e.Error()))
-			errMsg = "图片生成失败"
-			err = errors.New("图片生成失败, 执行超时" + e.Error())
-			return
-		}
-		fmt.Println("timeout kill process")
-	case <-doneChannel:
-		fmt.Println("done")
-	case e := <-errorChannel:
-		errMsg = "文件上传失败"
-		err = errors.New(fmt.Sprintf("execute command failure err: %s", e.Error()))
-		fmt.Println("execute command failure err:" + e.Error())
-		return
-	}
-
 	//上传到阿里云 和 minio
 	ossClient := NewOssClient()
 	if ossClient == nil {

+ 6 - 2
services/material/material.go

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

+ 56 - 0
services/report_chapter.go

@@ -6,6 +6,7 @@ import (
 	"eta/eta_api/models/system"
 	"eta/eta_api/utils"
 	"fmt"
+	"sort"
 	"time"
 )
 
@@ -166,6 +167,9 @@ func moveReportChapter(reportChapter, prevReportChapter, nextReportChapter *mode
 			err = fmt.Errorf("修改失败,Err:" + err.Error())
 			return
 		}
+		go func() {
+			_ = report.SortPage(reportChapter.ReportId, nil)
+		}()
 	}
 	return
 }
@@ -237,3 +241,55 @@ func CheckChapterAuthByReportChapterInfo(sysUser *system.Admin, createAdminId in
 
 	return
 }
+
+// ResortFreeChapter 自由布局章节重新排序页脚
+func ResortFreeChapter(reportId int) (err error) {
+	// 获取排序后的章节列表
+	chapters, e := models.GetChapterListByReportId(reportId)
+	if e != nil {
+		err = fmt.Errorf("获取报告章节列表失败, %v", e)
+		return
+	}
+	sortChapter := make(map[int]int)
+	for _, v := range chapters {
+		// 如果存在任一章节未发布,那么无需重排
+		if v.PublishState != 2 {
+			return
+		}
+		sortChapter[v.ReportChapterId] = v.Sort
+	}
+
+	// 获取自由章节布局,根据章节顺序重新排序
+	layouts, e := report.GetAllReportFreeLayoutListByReportId(reportId)
+	if e != nil {
+		err = fmt.Errorf("获取章节自由布局失败, %v", e)
+		return
+	}
+	sort.Slice(layouts, func(i, j int) bool {
+		a := layouts[i]
+		b := layouts[j]
+		if a.ReportChapterId == b.ReportChapterId {
+			return a.Page < b.Page
+		}
+		return sortChapter[a.ReportChapterId] < sortChapter[b.ReportChapterId]
+	})
+
+	// 重排序并进行更新
+	updateLayouts := make([]*report.ReportFreeLayout, 0)
+	var resort int
+	for _, layout := range layouts {
+		resort += 1
+		if layout.Page != resort {
+			layout.Page = resort
+			updateLayouts = append(updateLayouts, layout)
+		}
+	}
+	if len(updateLayouts) == 0 {
+		return
+	}
+	if e = report.ResetReportFreeLayoutsPage(updateLayouts); e != nil {
+		err = fmt.Errorf("批量更新自由布局页脚失败, %v", e)
+		return
+	}
+	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

+ 120 - 17
services/report_v2.go

@@ -66,6 +66,8 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 
 	errMsg = "生成报告失败"
 
+	reportFreeLayoutList := make([]*report.ReportFreeLayout, 0)
+
 	// 报告继承
 	if inheritReportId > 0 {
 		inheritReport, tmpErr := models.GetReportByReportId(inheritReportId)
@@ -95,6 +97,7 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 			reportInfo.HeadImg = ``
 			reportInfo.EndImg = ``
 			reportInfo.EndResourceId = inheritReport.EndResourceId
+			reportInfo.FreeLayoutConfig = inheritReport.FreeLayoutConfig
 			if inheritReport.HeadResourceId > 0 {
 				reportInfo.HeadImg = inheritReport.HeadImg
 			}
@@ -102,6 +105,25 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 				reportInfo.EndImg = inheritReport.EndImg
 			}
 			reportInfo.InheritReportId = inheritReport.Id
+			pages, tmpErr := report.GetReportFreeLayoutListByReportId(inheritReport.Id, 0)
+			if tmpErr != nil {
+				errMsg = "获取自由布局内容页失败"
+				err = tmpErr
+				return
+			}
+			for _, v := range pages {
+				reportFreeLayoutList = append(reportFreeLayoutList, &report.ReportFreeLayout{
+					Id:              0,
+					ReportId:        0,
+					ReportChapterId: 0,
+					Page:            v.Page,
+					IsChapter:       v.IsChapter,
+					Content:         v.Content,
+					ContentStruct:   v.ContentStruct,
+					CreateTime:      time.Now(),
+					ModifyTime:      time.Now(),
+				})
+			}
 		}
 	}
 
@@ -110,7 +132,7 @@ func AddReportAndChapter(reportInfo *models.Report, inheritReportId int, grantAd
 
 	// 新增报告及章节
 	var reportId int64
-	reportId, err = models.AddReportAndChapter(reportInfo, allGrantUserList, addChapterList)
+	reportId, err = models.AddReportAndChapter(reportInfo, reportFreeLayoutList, allGrantUserList, addChapterList)
 	if err != nil {
 		err = errors.New("新增报告及章节失败, Err: " + err.Error())
 		return
@@ -206,7 +228,7 @@ func EditReport(reportInfo *models.Report, req models.EditReq, sysUser *system.A
 	reportInfo.State = state
 
 	//updateCols := []string{"ClassifyIdFirst", "ClassifyNameFirst", "ClassifyIdSecond", "ClassifyNameSecond", "ClassifyIdThird", "ClassifyNameThird", "Title", "Abstract", "Author", "Frequency", "Stage", "CreateTime", "IsPublicPublish", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime"}
-	updateCols := []string{"Title", "Abstract", "Author", "Frequency", "CreateTime", "IsPublicPublish", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime", "State"}
+	updateCols := []string{"Title", "Abstract", "Author", "Frequency", "CreateTime", "IsPublicPublish", "LastModifyAdminId", "LastModifyAdminName", "ModifyTime", "State", "MiniShow", "FreeReportRatio"}
 
 	if req.HeadResourceId > 0 {
 		reportInfo.HeadResourceId = req.HeadResourceId
@@ -216,6 +238,8 @@ func EditReport(reportInfo *models.Report, req models.EditReq, sysUser *system.A
 		reportInfo.EndResourceId = req.EndResourceId
 		updateCols = append(updateCols, "EndResourceId")
 	}
+	reportInfo.MiniShow = req.MiniShow
+	reportInfo.FreeReportRatio = req.FreeReportRatio
 
 	// 需要添加的报告授权数据
 	addReportAdminList := make([]*report.ReportGrant, 0)
@@ -353,6 +377,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 +538,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 +575,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 +587,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 +600,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 +646,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 +743,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 +759,27 @@ func getAddChapter(reportInfo *models.Report, minClassifyId, inheritReportId int
 		chapterList = append(chapterList, addChapterItem)
 	}
 
+	for k, chapterItem := range chapterList {
+		reportFreeLayoutList, ok := chapterFreeLayoutListMap[chapterItem.InheritReportChapterId]
+		if ok {
+			tmpReportFreeLayoutList := make([]*report.ReportFreeLayout, 0)
+			for _, v := range reportFreeLayoutList {
+				tmpReportFreeLayoutList = append(tmpReportFreeLayoutList, &report.ReportFreeLayout{
+					Id:              0,
+					ReportId:        0,
+					ReportChapterId: 0,
+					Page:            v.Page,
+					IsChapter:       v.IsChapter,
+					Content:         v.Content,
+					ContentStruct:   v.ContentStruct,
+					CreateTime:      time.Now(),
+					ModifyTime:      time.Now(),
+				})
+			}
+			chapterList[k].ReportChapterFreeLayoutList = tmpReportFreeLayoutList
+		}
+	}
+
 	//hasGrantUserMap := make(map[int]bool)
 	//for _, grantList := range typeGrantListMap {
 	//	for _, grant := range grantList {
@@ -766,7 +840,15 @@ func AddChapterBaseInfoAndPermission(reportInfo *models.Report, reportChapterInf
 			CreateTime:        time.Now(),
 		})
 	}
-
+	if reportInfo.ReportLayout == 3 {
+		//增加默认排序
+		maxSort, sortErr := reportChapterInfo.GetMaxSortByReportId(reportInfo.Id)
+		if sortErr != nil {
+			err = fmt.Errorf("获取报告章节最大排序失败, ReportId: %d, Err: %v", reportInfo.Id, sortErr)
+			return
+		}
+		reportChapterInfo.Sort = maxSort + 1
+	}
 	err = models.AddChapterBaseInfoAndPermission(reportChapterInfo, addChapterAdminList, addChapterPermissionList)
 
 	return
@@ -1196,10 +1278,28 @@ func PublishReport(reportId int, reportUrl string, sysUser *system.Admin) (tips
 	}
 
 	// 普通报告
-	if reportInfo.Content == "" {
-		errMsg = `报告内容为空,不可发布`
-		err = errors.New("报告内容为空,不需要生成,report_id:" + strconv.Itoa(reportId))
-		return
+	if reportInfo.ReportLayout != 3 {
+		if reportInfo.Content == "" {
+			errMsg = `报告内容为空,不可发布`
+			err = errors.New("报告内容为空,不需要生成,report_id:" + strconv.Itoa(reportId))
+			return
+		}
+	} else {
+		pages, pageErr := report.GetFreeLayoutChapterPagesByReportId(reportInfo.Id)
+		if pageErr != nil {
+			errMsg = "获取自由布局报告失败,不可发布"
+			err = errors.New("获取自由布局报告失败,不可发布,Err:" + pageErr.Error())
+			return
+		}
+		var content string
+		for _, page := range pages {
+			content += page.Content
+		}
+		if content == "" {
+			errMsg = "自由布局报告内容为空,不可设置定时发布"
+			err = errors.New("自由布局报告内容为空,不可设置定时发布,report_id:" + strconv.Itoa(reportInfo.Id))
+			return
+		}
 	}
 
 	// 根据审批开关及审批流判断当前报告状态
@@ -1645,6 +1745,9 @@ func GetGeneralPdfUrl(reportId int, reportCode, classifyFirstName string, report
 	case 2:
 		// 智能布局
 		pdfUrl = fmt.Sprintf("%s/reportshare_smart_pdf?code=%s", reportUrl, reportCode)
+	case 3:
+		// 智能布局
+		pdfUrl = fmt.Sprintf("%s/reportshare_free_pdf?code=%s", reportUrl, reportCode)
 	}
 
 	if pdfUrl != "" {

+ 185 - 113
services/smart_report.go

@@ -8,10 +8,12 @@ import (
 	"eta/eta_api/utils"
 	"fmt"
 	"html"
+	"math"
 	"os"
 	"os/exec"
 	"path"
 	"strconv"
+	"strings"
 	"time"
 )
 
@@ -136,7 +138,7 @@ func SmartReportElasticUpsert(smartReportId int, state int) (err error) {
 	return
 }
 
-func ReportToPdf(width int, reportUrl, filePath string) (err error) {
+func ReportToPdf(width, height int, reportUrl, filePath string, top, bottom, left, right int) (err error) {
 	pyCode := `
 import asyncio
 from pyppeteer import launch
@@ -152,7 +154,7 @@ async def main():
     page = await browser.newPage()
     await page.setViewport({
         'width': %d,
-        'height': 1697
+        'height': %d
     })
     await page.goto('%s', {
         'waitUntil': 'networkidle0',
@@ -164,14 +166,14 @@ async def main():
 
     await page.pdf({
 		'width': %d,
-        'height': 1697,
+        'height': %d,
         'path': "%s",
         'printBackground': True,
         'margin': {
-            'top': '20px',
-            'bottom': '20px',
-            'left': '20px',
-            'right': '20px'
+            'top': '%dpx',
+            'bottom': '%dpx',
+            'left': '%dpx',
+            'right': '%dpx'
         }
     })
     await browser.close()
@@ -187,7 +189,7 @@ finally:
     loop.close()
 `
 
-	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, width, reportUrl, width, filePath)
+	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, width, height, reportUrl, width+left+right, height+top+bottom, filePath, top, bottom, left, right)
 	utils.FileLog.Info("pdf pyCode: \n" + pyCode)
 	cmd := exec.Command(utils.CommandPython, "-c", pyCode)
 	output, e := cmd.CombinedOutput()
@@ -204,7 +206,7 @@ finally:
 	return
 }
 
-func ReportToJpeg(width int, reportUrl, filePath string) (err error) {
+func ReportToJpeg(width, height int, reportUrl, filePath string) (err error) {
 	pyCode := `
 import asyncio
 from pyppeteer import launch, errors
@@ -224,7 +226,7 @@ async def main():
         # 设置视口大小
         await page.setViewport({
             'width': %d,
-            'height': 1697
+            'height': %d
         })
         
         # 导航到页面
@@ -265,7 +267,7 @@ finally:
     loop.close()
 `
 
-	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, width, reportUrl, filePath)
+	pyCode = fmt.Sprintf(pyCode, utils.ChromePath, width, height, reportUrl, filePath)
 	utils.FileLog.Info("jpeg pyCode: \n" + pyCode)
 	cmd := exec.Command(utils.CommandPython, "-c", pyCode)
 	output, e := cmd.CombinedOutput()
@@ -302,14 +304,29 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 	if reportUrl == `` {
 		return
 	}
+	var report *models.ReportDetail
+
+	// 默认宽高,自由布局需要根据比例重新计算
+	var freeRatio string
+	pcWidth := 1200
+	pcHeight := 1697
+	mobileWidth := 600
+	mobileHeight := 1697
 
 	// 先清空字段
 	if reportType == 1 {
+		report, err = models.GetReportById(reportId)
+		if err != nil {
+			return
+		}
 		err = models.UpdatePdfUrlReportById(reportId)
 		if err != nil {
 			utils.FileLog.Info("清空pdf长图字段失败, Err: \n" + err.Error())
 			return
 		}
+		if report.ReportLayout == 3 {
+			freeRatio = report.FreeReportRatio
+		}
 	} else if reportType == 2 {
 		err = models.UpdatePdfUrlEnglishReportById(reportId)
 		if err != nil {
@@ -324,6 +341,23 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 		}
 	}
 
+	// 重新计算比例
+	if freeRatio != "" {
+		h, e := calculateReportPdfHeight(pcWidth, freeRatio)
+		if e != nil {
+			err = fmt.Errorf("重新计算PDF生成高度失败, %v", e)
+			return
+		}
+		pcHeight = h
+
+		hm, e := calculateReportPdfHeight(mobileWidth, freeRatio)
+		if e != nil {
+			err = fmt.Errorf("重新计算移动端PDF生成高度失败, %v", e)
+			return
+		}
+		mobileHeight = hm
+	}
+
 	reportCode := utils.MD5(strconv.Itoa(reportId))
 
 	// pc端
@@ -331,11 +365,17 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 		pdfPath := `./static/` + reportCode + "_1200.pdf"
 		jpegPath := `./static/` + reportCode + "_1200.jpg"
 
-		width := 1200
+		//width := 1200
+		top, bottom, left, right := 20, 20, 20, 20
+		if reportType == 1 {
+			if report != nil && report.ReportLayout == 3 {
+				top, bottom, left, right = 0, 0, 0, 0
+			}
+		}
 		//if reportType == 3 {
 		//	width = 800
 		//}
-		err = ReportToPdf(width, reportUrl, pdfPath)
+		err = ReportToPdf(pcWidth, pcHeight, reportUrl, pdfPath, top, bottom, left, right)
 		if err != nil {
 			utils.FileLog.Info("ReportToPdf failed: , error: \n" + err.Error())
 			go alarm_msg.SendAlarmMsg("ReportToPdf failed:"+err.Error(), 3)
@@ -395,7 +435,7 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 
 		time.Sleep(1 * time.Minute)
 
-		err = ReportToJpeg(width, reportUrl, jpegPath)
+		err = ReportToJpeg(pcWidth, pcHeight, reportUrl, jpegPath)
 		if err != nil {
 			utils.FileLog.Info("ReportToJpeg failed: , error: \n" + err.Error())
 		}
@@ -449,125 +489,157 @@ func Report2pdfAndJpeg(reportUrl string, reportId, reportType int) {
 			}
 		}
 	}()
-
-	// 移动端
-	go func() {
-		pdfPathMobile := `./static/` + reportCode + "_600.pdf"
-		jpegPathMobile := `./static/` + reportCode + "_600.jpg"
-
-		width := 600
-		err = ReportToPdf(width, reportUrl, pdfPathMobile)
-		if err != nil {
-			utils.FileLog.Info("ReportToPdf failed: , error: \n" + err.Error())
-			go alarm_msg.SendAlarmMsg("ReportToPdf failed:"+err.Error(), 3)
-		}
-
-		file, err := os.Open(pdfPathMobile)
-		if err != nil {
-			utils.FileLog.Info("Open failed: , error: \n" + err.Error())
-			go alarm_msg.SendAlarmMsg("Open failed:"+err.Error(), 3)
-			return
+	var mobilePdf = true
+	if reportType == 1 && report != nil {
+		if report.ReportLayout == 3 {
+			mobilePdf = false
 		}
+	}
+	if mobilePdf {
+		// 移动端
+		go func() {
+			pdfPathMobile := `./static/` + reportCode + "_600.pdf"
+			jpegPathMobile := `./static/` + reportCode + "_600.jpg"
+			top, bottom, left, right := 20, 20, 20, 20
+			//width := 600
+			err = ReportToPdf(mobileWidth, mobileHeight, reportUrl, pdfPathMobile, top, bottom, left, right)
+			if err != nil {
+				utils.FileLog.Info("ReportToPdf failed: , error: \n" + err.Error())
+				go alarm_msg.SendAlarmMsg("ReportToPdf failed:"+err.Error(), 3)
+			}
 
-		ext := path.Ext(file.Name())
+			file, err := os.Open(pdfPathMobile)
+			if err != nil {
+				utils.FileLog.Info("Open failed: , error: \n" + err.Error())
+				go alarm_msg.SendAlarmMsg("Open failed:"+err.Error(), 3)
+				return
+			}
 
-		randStr := utils.GetRandStringNoSpecialChar(28)
-		fileName := randStr + ext
-		defer file.Close() //关闭上传文件
+			ext := path.Ext(file.Name())
 
-		resourceUrl := ``
-		ossClient := NewOssClient()
-		if ossClient == nil {
-			utils.FileLog.Info("初始化OSS服务失败")
-			return
-		}
-		resourceUrl, err = ossClient.UploadFile(fileName, pdfPathMobile, "")
-		if err != nil {
-			utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
-			go alarm_msg.SendAlarmMsg("文件上传失败:"+err.Error(), 3)
-			return
-		}
-		defer func() {
-			_ = os.Remove(pdfPathMobile)
-		}()
+			randStr := utils.GetRandStringNoSpecialChar(28)
+			fileName := randStr + ext
+			defer file.Close() //关闭上传文件
 
-		if reportType == 3 {
-			// 更新pdf url
-			ob := new(smart_report.SmartReport)
-			ob.SmartReportId = reportId
-			ob.DetailPdfUrlMobile = resourceUrl
-			if err = ob.Update([]string{"DetailPdfUrlMobile"}); err != nil {
-				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+			resourceUrl := ``
+			ossClient := NewOssClient()
+			if ossClient == nil {
+				utils.FileLog.Info("初始化OSS服务失败")
 				return
 			}
-		} else if reportType == 2 {
-			err = models.ModifyEnglishReportPdfUrlMobile(reportId, resourceUrl)
+			resourceUrl, err = ossClient.UploadFile(fileName, pdfPathMobile, "")
 			if err != nil {
-				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
+				go alarm_msg.SendAlarmMsg("文件上传失败:"+err.Error(), 3)
 				return
 			}
-		} else if reportType == 1 {
-			err = models.ModifyReportPdfUrlMobile(reportId, resourceUrl)
-			if err != nil {
-				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
-				return
+			defer func() {
+				_ = os.Remove(pdfPathMobile)
+			}()
+
+			if reportType == 3 {
+				// 更新pdf url
+				ob := new(smart_report.SmartReport)
+				ob.SmartReportId = reportId
+				ob.DetailPdfUrlMobile = resourceUrl
+				if err = ob.Update([]string{"DetailPdfUrlMobile"}); err != nil {
+					utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+					return
+				}
+			} else if reportType == 2 {
+				err = models.ModifyEnglishReportPdfUrlMobile(reportId, resourceUrl)
+				if err != nil {
+					utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+					return
+				}
+			} else if reportType == 1 {
+				err = models.ModifyReportPdfUrlMobile(reportId, resourceUrl)
+				if err != nil {
+					utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+					return
+				}
 			}
-		}
 
-		time.Sleep(1 * time.Minute)
+			time.Sleep(1 * time.Minute)
 
-		err = ReportToJpeg(width, reportUrl, jpegPathMobile)
-		if err != nil {
-			utils.FileLog.Info("ReportToJpeg failed: , error: \n" + err.Error())
-		}
-		file, err = os.Open(jpegPathMobile)
-		if err != nil {
-			utils.FileLog.Info("open file failed: , error: \n" + err.Error())
-			return
-		}
-
-		ext = path.Ext(file.Name())
+			err = ReportToJpeg(mobileWidth, mobileHeight, reportUrl, jpegPathMobile)
+			if err != nil {
+				utils.FileLog.Info("ReportToJpeg failed: , error: \n" + err.Error())
+			}
+			file, err = os.Open(jpegPathMobile)
+			if err != nil {
+				utils.FileLog.Info("open file failed: , error: \n" + err.Error())
+				return
+			}
 
-		randStr = utils.GetRandStringNoSpecialChar(28)
-		fileName = randStr + ext
-		defer file.Close() //关闭上传文件
+			ext = path.Ext(file.Name())
 
-		resourceUrl = ``
-		ossClient = NewOssClient()
-		if ossClient == nil {
-			utils.FileLog.Info("初始化OSS服务失败")
-			return
-		}
-		resourceUrl, err = ossClient.UploadFile(fileName, jpegPathMobile, "")
-		if err != nil {
-			utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
-			return
-		}
-		defer func() {
-			_ = os.Remove(jpegPathMobile)
-		}()
+			randStr = utils.GetRandStringNoSpecialChar(28)
+			fileName = randStr + ext
+			defer file.Close() //关闭上传文件
 
-		if reportType == 3 {
-			// 更新jpeg url
-			ob := new(smart_report.SmartReport)
-			ob.SmartReportId = reportId
-			ob.DetailImgUrlMobile = resourceUrl
-			if err = ob.Update([]string{"DetailImgUrlMobile"}); err != nil {
-				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+			resourceUrl = ``
+			ossClient = NewOssClient()
+			if ossClient == nil {
+				utils.FileLog.Info("初始化OSS服务失败")
 				return
 			}
-		} else if reportType == 2 {
-			err = models.ModifyEnglishReportImgUrlMobile(reportId, resourceUrl)
+			resourceUrl, err = ossClient.UploadFile(fileName, jpegPathMobile, "")
 			if err != nil {
-				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+				utils.FileLog.Info("文件上传失败, Err: \n" + err.Error())
 				return
 			}
-		} else if reportType == 1 {
-			err = models.ModifyReportImgUrlMobile(reportId, resourceUrl)
-			if err != nil {
-				utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
-				return
+			defer func() {
+				_ = os.Remove(jpegPathMobile)
+			}()
+
+			if reportType == 3 {
+				// 更新jpeg url
+				ob := new(smart_report.SmartReport)
+				ob.SmartReportId = reportId
+				ob.DetailImgUrlMobile = resourceUrl
+				if err = ob.Update([]string{"DetailImgUrlMobile"}); err != nil {
+					utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+					return
+				}
+			} else if reportType == 2 {
+				err = models.ModifyEnglishReportImgUrlMobile(reportId, resourceUrl)
+				if err != nil {
+					utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+					return
+				}
+			} else if reportType == 1 {
+				err = models.ModifyReportImgUrlMobile(reportId, resourceUrl)
+				if err != nil {
+					utils.FileLog.Info("更新研报失败, Err: \n" + err.Error())
+					return
+				}
 			}
-		}
-	}()
+		}()
+	}
+}
+
+// calculateReportPdfHeight 自由布局-根据宽度和布局比例计算高度
+func calculateReportPdfHeight(width int, freeRatio string) (height int, err error) {
+	ratioArr := strings.Split(freeRatio, "/")
+	if len(ratioArr) != 2 {
+		err = fmt.Errorf("布局比例有误, %s", freeRatio)
+		return
+	}
+	m, e := strconv.ParseFloat(ratioArr[0], 64)
+	if e != nil {
+		err = fmt.Errorf("布局比例有误, %s", freeRatio)
+		return
+	}
+	n, e := strconv.ParseFloat(ratioArr[1], 64)
+	if e != nil {
+		err = fmt.Errorf("布局比例有误, %s", freeRatio)
+		return
+	}
+	if m <= 0 {
+		err = fmt.Errorf("布局比例有误, %s", freeRatio)
+		return
+	}
+	height = int(math.Round(float64(width) * n / m))
+	return
 }

+ 5 - 2
services/task.go

@@ -40,7 +40,7 @@ func Task() {
 	//手工数据表格导入后的指标库刷新
 	go ImportManualDataRefresh()
 
-	// 加入钢联化工指标数据之后的刷新
+	// 加入上海钢联指标数据之后的刷新
 	go MysteelChemicalDataAdd()
 
 	//修复用户关注标识
@@ -86,6 +86,9 @@ func Task() {
 	// 权益报告监听入库
 	go AutoInsertRaiReport()
 
+	// 巡检信息发送
+	go AutoCheckWebsocketMessageList()
+
 	// TODO:数据修复
 	//FixNewEs()
 	fmt.Println("task end")
@@ -131,7 +134,7 @@ func ImportManualDataRefresh() {
 	}
 }
 
-// MysteelChemicalDataAdd 加入钢联化工指标数据之后的刷新
+// MysteelChemicalDataAdd 加入上海钢联指标数据之后的刷新
 func MysteelChemicalDataAdd() {
 	defer func() {
 		if err := recover(); err != nil {

+ 367 - 0
services/websocket_msg.go

@@ -0,0 +1,367 @@
+package services
+
+import (
+	"encoding/json"
+	"eta/eta_api/global"
+	"eta/eta_api/models"
+	"eta/eta_api/models/data_manage/edb_inspection"
+	"eta/eta_api/services/data"
+	edb_monitor "eta/eta_api/services/edb_monitor"
+	"eta/eta_api/utils"
+	"fmt"
+	"runtime"
+	"sync"
+	"time"
+
+	"context"
+)
+
+func DealWebSocketMsg(adminId int) {
+	DealEdbInspectionMessageTest(adminId)
+
+	//go DealEdbInspectionMessage(adminId)
+}
+
+// 处理巡检消息
+func DealEdbInspectionMessage(adminId int) {
+	utils.FileLog.Info("创建协程, adminId:%d", adminId)
+	// 创建上下文用于控制 goroutine 生命周期
+	ctx, cancel := context.WithCancel(context.Background())
+	defer cancel()
+
+	cacheKey := fmt.Sprintf("%s%d", utils.CACHE_EDB_INSPECTION_MESSAGE, adminId)
+
+	// 添加错误恢复机制
+	defer func() {
+		if r := recover(); r != nil {
+			utils.FileLog.Error("WebSocket handler recovered from panic: %v", r)
+			// 清理资源
+			cancel()
+		}
+	}()
+	go func() {
+		ticker := time.NewTicker(time.Minute)
+		defer ticker.Stop()
+		for {
+			select {
+			case <-ticker.C:
+				utils.FileLog.Info("Current goroutine count: %d", runtime.NumGoroutine())
+			case <-ctx.Done():
+				return
+			}
+		}
+	}()
+	for {
+		select {
+		case <-ctx.Done():
+			utils.FileLog.Info("DealEdbInspectionMessage 巡检消息处理协程结束, adminId:%d", adminId)
+			return
+		default:
+			// 检查连接状态
+			conn := global.MonitorMessageConn[adminId]
+			if conn == nil {
+				utils.FileLog.Error("检查连接状态 发送消息时发现连接已断开, adminId:%d", adminId)
+				cancel()
+				return
+			}
+			// 使用带超时的 Redis 操作
+			val := utils.Rc.Get(cacheKey)
+			if val == "" {
+				//utils.FileLog.Info("巡检信息历史为空, adminId:%d", adminId)
+				continue
+			}
+				utils.FileLog.Info("收到巡检信息开始处理, adminId:%d", adminId)
+				messageList, err := data.GetHistoryInspectionMessages(adminId)
+				if err != nil {
+					utils.FileLog.Error("获取巡检信息历史失败,err:%s, adminId:%d", err.Error(), adminId)
+					return
+				}
+				if len(messageList) == 0 {
+					utils.FileLog.Info("巡检信息历史为空, adminId:%d", adminId)
+					return
+				}
+
+				readList := make([]int64, 0)
+				// 检查连接状态
+				// conn := global.MonitorMessageConn[adminId]
+				// if conn == nil {
+				// 	utils.FileLog.Error("发送消息时发现连接已断开, adminId:%d", adminId)
+				// 	cancel()
+				// 	return
+				// }
+				// 只处理第一条消息的发送,其他消息只标记为已读
+				for i, msg := range messageList {
+					if i == 0 {
+						respData, err := data.SendInspectionMessages(adminId, msg)
+						if err != nil {
+							utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d", err.Error(), adminId)
+							continue
+						}
+
+						resp := models.WebsocketMessageResponse{
+							MessageType: 1,
+							Data:       respData,
+						}
+
+						err, isClose := WriteWebSocketMessageAsync(ctx, adminId, resp)
+						if err != nil {
+							utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d", err.Error(), adminId)
+							cancel()
+							continue
+						}
+						if isClose {
+							utils.FileLog.Error("巡检信息发送失败,连接已断开, adminId:%d", adminId)
+							cancel()
+							return
+						}
+
+						utils.FileLog.Info("巡检信息发送成功,adminId:%d, messageId:%d", adminId, msg.MessageId)
+					}
+					readList = append(readList, msg.MessageId)
+				}
+
+				if len(readList) > 0 {
+					_, err = data.ReadEdbInspectionMessageList(readList, adminId)
+					if err != nil {
+						utils.FileLog.Error("巡检信息已读失败,err:%s, adminId:%d", err.Error(), adminId)
+					}
+				}
+			//})
+
+			// if err != nil && err.Error() != "redis: nil" {
+			// 	utils.FileLog.Error("Redis operation failed: %v", err)
+			// 	continue
+			// }else {
+			// 	utils.FileLog.Info("巡检信息处理完成, adminId:%d", adminId)
+			// }
+		}
+	}
+}
+
+func WriteWebSocketMessageAsync(ctx context.Context, adminId int, resp interface{}) (error, bool) {
+	errChan := make(chan error, 1)
+	var wsWriteMutex sync.Mutex
+	isClose := false
+	
+	go func() {
+		wsWriteMutex.Lock()
+		defer wsWriteMutex.Unlock()
+		
+		conn := global.MonitorMessageConn[adminId]
+		if conn == nil {
+			isClose = true
+			errChan <- fmt.Errorf("connection closed for adminId: %d", adminId)
+			return
+		}
+		
+		// 设置写超时
+		//conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
+		errChan <- conn.WriteJSON(resp)
+	}()
+	
+	select {
+	case err := <-errChan:
+		utils.FileLog.Error("WriteWebSocketMessageAsync errChan: %v", err)
+		return err, isClose
+	case <-ctx.Done():
+		utils.FileLog.Error("WriteWebSocketMessageAsync ctx.Done(): %v", ctx.Err())
+		return ctx.Err(), isClose
+	}
+}
+
+func DealEdbInspectionMessageTest(adminId int) {
+	messageList, err := data.GetHistoryInspectionMessages(adminId)
+	if err != nil {
+		utils.FileLog.Error("获取巡检信息历史失败,err:%s, adminId:%d", err.Error(), adminId)
+	}
+	if len(messageList) == 0 {
+		return
+	}
+	go func() {
+		readList := make([]int64, 0)
+		for _, msg := range messageList {
+				// 多条消息仅发送最新一条
+				respData, err := data.SendInspectionMessages(adminId, msg)
+				if err != nil {
+					utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d", err.Error(), adminId)
+					return
+				} else {
+					resp := models.WebsocketMessageResponse{
+						MessageType: 1,
+						Data: respData,
+					}
+					conn := global.MonitorMessageConn[adminId]
+					if conn == nil {
+						utils.FileLog.Error("巡检信息发送失败,连接已断开, adminId:%d", adminId)
+						return
+					}
+					err = conn.WriteJSON(resp)
+					if err != nil {
+						utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d", err.Error(), adminId)
+						return
+					} else {
+						utils.FileLog.Info("巡检信息发送成功,adminId:%d, messageId:%d", adminId, msg.MessageId)
+					}
+				}
+				readList = append(readList, msg.MessageId)
+			}
+			_, err = data.ReadEdbInspectionMessageList(readList, adminId)
+			if err != nil {
+				utils.FileLog.Error("巡检信息已读失败,err:%s, adminId:%d", err.Error(), adminId)
+				return
+			}
+	}()
+}
+
+func AutoCheckWebsocketMessageList() {
+	ticker := time.NewTicker(time.Second * 10)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-ticker.C:
+			// 获取活跃连接
+			admins := make([]int, 0)
+			for adminId, conn := range global.AdminWebSocketConnMap {
+				if conn == nil {
+					continue
+				}
+				admins = append(admins, adminId)
+			}
+			
+			// 如果没有活跃连接,继续等待下一个tick
+			if len(admins) == 0 {
+				//utils.FileLog.Info("当前没有活跃的WebSocket连接")
+				continue
+			}
+
+			// 并发处理不同类型的消息
+			var wg sync.WaitGroup
+			wg.Add(2)
+
+			// 处理巡检消息
+			go func() {
+				defer wg.Done()
+				if err := AutoCheckInspectionMessageList(admins); err != nil {
+					utils.FileLog.Error("处理巡检消息失败: %v", err)
+				}
+			}()
+
+			// 处理监控消息
+			go func() {
+				defer wg.Done()
+				if err := edb_monitor.AutoCheckMonitorMessageList(admins); err != nil {
+					utils.FileLog.Error("处理监控消息失败: %v", err)
+				}
+			}()
+
+			// 等待所有消息处理完成
+			wg.Wait()
+		}
+	}
+}
+
+func AutoCheckInspectionMessageList(admins []int) (err error) {
+	//utils.FileLog.Info("检查是否有巡检信息")
+	// 设置redis缓存,防止消息重复处理
+	cacheKey := fmt.Sprintf("%s", utils.CACHE_EDB_INSPECTION_MESSAGE)
+	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
+		err = fmt.Errorf("系统处理中,请稍后重试!")
+		utils.FileLog.Error("巡检信息检查失败,err:%s", err.Error())
+		return
+	}
+	defer func() {
+		_ = utils.Rc.Delete(cacheKey)
+	}()
+	messageList, er := edb_inspection.GetUnreadInspectionMessageList(admins)
+	if er != nil {
+		err = fmt.Errorf("获取巡检信息历史失败,err:%s", er.Error())
+		return
+	}
+	readList := make([]int64, 0)
+	for _, msg := range messageList {
+		adminId := int(msg.AdminId)
+		respData, er := data.SendInspectionMessages(adminId, msg)
+		if er != nil {
+			utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d", er.Error(), adminId)
+		} else {
+			resp := models.WebsocketMessageResponse{
+				MessageType: 1,
+				Data: respData,
+			}
+			conn := global.AdminWebSocketConnMap[int(msg.AdminId)]
+			if conn == nil {
+				utils.FileLog.Error("巡检信息发送失败,连接已断开, adminId:%d", adminId)
+				return
+			}
+			message, er := json.Marshal(resp)
+			if er != nil {
+				utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d", er.Error(), adminId)
+				return
+			}
+			ok := conn.Send(message)
+			if !ok {
+				utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d",  adminId)
+			}
+		}
+		readList = append(readList, msg.MessageId)
+	}
+
+	err = edb_inspection.SetEdbInspectionMessageRead(readList)
+	if err != nil {
+		err = fmt.Errorf("巡检信息已读失败,err:%s", err.Error())
+		return
+	}
+	return
+}
+
+// 弃用
+func AutoCheckInspectionMessageListByAdminId(adminId int) (err error) {
+	defer func() {
+		if err != nil {
+			utils.FileLog.Error("巡检信息发送失败,err:%s", err.Error())
+		}
+	}()
+	messageList, er := edb_inspection.GetUnreadInspectionMessageListByAdminId(adminId)
+	if er != nil {
+		err = fmt.Errorf("获取巡检信息历史失败,err:%s", er.Error())
+		return
+	}
+	if len(messageList) == 0 {
+		return
+	}
+	readList := make([]int64, 0)
+	for _, msg := range messageList {
+		adminId := int(msg.AdminId)
+		respData, er := data.SendInspectionMessages(adminId, msg)
+		if er != nil {
+			utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d", er.Error(), adminId)
+		} else {
+			resp := models.WebsocketMessageResponse{
+				MessageType: 1,
+				Data: respData,
+			}
+			conn := global.AdminWebSocketConnMap[adminId]
+			if conn == nil {
+				utils.FileLog.Error("巡检信息发送失败,连接已断开, adminId:%d", adminId)
+				return
+			}
+			message, er := json.Marshal(resp)
+			if er != nil {
+				utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d", er.Error(), adminId)
+				return
+			}
+			ok := conn.Send(message)
+			if !ok {
+				utils.FileLog.Error("巡检信息发送失败,err:%s, adminId:%d",  adminId)
+			}
+		}
+		readList = append(readList, msg.MessageId)
+	}
+	err = edb_inspection.SetEdbInspectionMessageRead(readList)
+	if err != nil {
+		err = fmt.Errorf("巡检信息已读失败,err:%s", err.Error())
+		return
+	}
+	return
+}

Plik diff jest za duży
+ 0 - 7
static/images/UXjAv5IXV9fSqDBqhGS3DPYcj9wb.txt


Plik diff jest za duży
+ 0 - 7
static/images/fdUC0cafpf0LXCjpwluC0r6IzA4Z.txt


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


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


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


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


+ 29 - 0
utils/common.go

@@ -3101,3 +3101,32 @@ func GetFirstNChars(s string, max int) string {
 	}
 	return string(runes[:max])
 }
+
+// GetWindWsdIndexCodeSuffix 获取wind日期序列指标编码后缀
+func GetWindWsdIndexCodeSuffix(period, days, priceAdj string) (suffix string) {
+	if period != "" && period != WindWsdPeriodDefault {
+		suffix += period
+	}
+	if days != "" && days != WindWsdDaysDefault {
+		suffixDays := map[string]string{
+			WindWsdDaysWeekdays: WindWsdSuffixWeekdays,
+			WindWsdDaysAlldays:  WindWsdSuffixAlldays,
+		}
+		suffix += suffixDays[days]
+	}
+	if priceAdj != "" {
+		suffix += priceAdj
+	}
+	return
+}
+
+// GetThsDsIndexCodeSuffix 获取同花顺日期序列指标编码后缀
+func GetThsDsIndexCodeSuffix(period, days string) (suffix string) {
+	if period != "" && period != WindWsdPeriodDefault {
+		suffix += period
+	}
+	if days == ThsDsDaysAlldays {
+		suffix += ThsDsDaysSuffixAlldays
+	}
+	return
+}

+ 2 - 2
utils/config.go

@@ -77,7 +77,7 @@ var (
 	// PublicChartHost 公共图库的host
 	PublicChartHost string
 
-	// MySteelPostUrl 钢联化工的服务地址
+	// MySteelPostUrl 上海钢联的服务地址
 	MySteelPostUrl string
 
 	// SendWxTemplateMsgUrl 模板消息推送
@@ -433,7 +433,7 @@ func init() {
 		// 图表项目域名
 		PublicChartHost = config["public_chart_host"]
 
-		// 钢联化工服务地址
+		// 上海钢联服务地址
 		MySteelPostUrl = config["mysteel_post_url"]
 
 		// 发送微信模板消息地址

+ 46 - 23
utils/constants.go

@@ -129,7 +129,7 @@ const (
 	DATA_SOURCE_PREDICT_CALCULATE                               //预测指标运算->31
 	DATA_SOURCE_PREDICT_CALCULATE_TBZ                           //预测同比值->32
 	DATA_SOURCE_PREDICT_CALCULATE_TCZ                           //预测同差值->33
-	DATA_SOURCE_MYSTEEL_CHEMICAL                                //钢联化工->34
+	DATA_SOURCE_MYSTEEL_CHEMICAL                                //上海钢联->34
 	DATA_SOURCE_CALCULATE_CJJX                                  //超季节性->35
 	DATA_SOURCE_EIA_STEO                                        //eia steo报告->36
 	DATA_SOURCE_CALCULATE_NHCC                                  //计算指标(拟合残差)->37
@@ -224,28 +224,28 @@ const (
 
 // 缓存key
 const (
-	CACHE_KEY_LOGS                    = "HZ_ADMIN_CACHE_KEY_LOGS"             //api用户操作日志队列
-	CACHE_KEY_ADMIN                   = "calendar:admin:list"                 //系统用户列表缓存key
-	CACHE_KEY_ADMIN_ID                = "calendar:admin:id:list"              //系统用户列表缓存key
-	CACHE_KEY_OLD_REPORT_PUBLISH      = "HZ_CACHE_KEY_OLD_REPORT_PUBLISH"     //老后台报告发布队列
-	CACHE_ADMIN_YB_CONFIG             = "admin:yb_config:"                    //研报配置相关缓存前缀
-	CACHE_WIND_URL                    = "CACHE_WIND_URL"                      //指标与wind服务器的绑定关系
-	CACHE_CHART_INFO_DATA             = "chart:info:data:"                    //图表数据
-	CACHE_CHART_CLASSIFY              = "chart:classify"                      //图表分类数据
-	CACHE_IMPORT_MANUAL_DATA          = "import:manual:data"                  //手工数据导入后刷新
-	CACHE_MYSTEEL_CHEMICAL_ADD_DATA   = "import:mysteelchemical:data"         //钢联化工的数据源导入后刷新
-	CACHE_ACCESS_TOKEN_LOGIN          = "pc_eta_admin:login:"                 //管理后台登录
-	CACHE_ACCESS_TOKEN_LOGIN_NO_TRUST = "pc_eta_admin:login:no_trust:"        //管理后台登录(不可信登录态)
-	CACHE_ABNORMAL_LOGIN              = "pc_eta_admin:login:abnormal:"        //管理后台登录-异常登录
-	CACHE_LOGIN_ERR_PASS              = "pc_eta_admin:login:errPass:"         //管理后台登录-输入错误密码次数
-	MOBILE_CACHE_ABNORMAL_LOGIN       = "mobile_eta_admin:login:abnormal:"        //管理后台登录-异常登录
-	MOBILE_CACHE_LOGIN_ERR_PASS             = "mobile_eta_admin:login:errPass:"         //管理后台登录-输入错误密码次数
-
-	CACHE_FIND_PASS_VERIFY            = "pc_eta_admin:findPass:verify:"       //找回密码校验成功标记
-	CACHE_KEY_MYSTEEL_REFRESH         = "mysteel_chemical:refresh"            //钢联化工刷新
-	CACHE_KEY_DAYNEW_REFRESH          = "admin:day_new:refresh"               //每日资讯拉取企业微信聊天记录
-	CACHE_KEY_DAYNEW_TRANSLATE        = "admin:day_new:translate"             //每日资讯中翻英
-	CACHE_KEY_ADMIN_OPERATE_RECORD    = "HZ_ADMIN_CACHE_ADMIN_OPERATE_RECORD" //系统用户操作日志队列
+	CACHE_KEY_LOGS                    = "HZ_ADMIN_CACHE_KEY_LOGS"          //api用户操作日志队列
+	CACHE_KEY_ADMIN                   = "calendar:admin:list"              //系统用户列表缓存key
+	CACHE_KEY_ADMIN_ID                = "calendar:admin:id:list"           //系统用户列表缓存key
+	CACHE_KEY_OLD_REPORT_PUBLISH      = "HZ_CACHE_KEY_OLD_REPORT_PUBLISH"  //老后台报告发布队列
+	CACHE_ADMIN_YB_CONFIG             = "admin:yb_config:"                 //研报配置相关缓存前缀
+	CACHE_WIND_URL                    = "CACHE_WIND_URL"                   //指标与wind服务器的绑定关系
+	CACHE_CHART_INFO_DATA             = "chart:info:data:"                 //图表数据
+	CACHE_CHART_CLASSIFY              = "chart:classify"                   //图表分类数据
+	CACHE_IMPORT_MANUAL_DATA          = "import:manual:data"               //手工数据导入后刷新
+	CACHE_MYSTEEL_CHEMICAL_ADD_DATA   = "import:mysteelchemical:data"      //上海钢联的数据源导入后刷新
+	CACHE_ACCESS_TOKEN_LOGIN          = "pc_eta_admin:login:"              //管理后台登录
+	CACHE_ACCESS_TOKEN_LOGIN_NO_TRUST = "pc_eta_admin:login:no_trust:"     //管理后台登录(不可信登录态)
+	CACHE_ABNORMAL_LOGIN              = "pc_eta_admin:login:abnormal:"     //管理后台登录-异常登录
+	CACHE_LOGIN_ERR_PASS              = "pc_eta_admin:login:errPass:"      //管理后台登录-输入错误密码次数
+	MOBILE_CACHE_ABNORMAL_LOGIN       = "mobile_eta_admin:login:abnormal:" //管理后台登录-异常登录
+	MOBILE_CACHE_LOGIN_ERR_PASS       = "mobile_eta_admin:login:errPass:"  //管理后台登录-输入错误密码次数
+
+	CACHE_FIND_PASS_VERIFY         = "pc_eta_admin:findPass:verify:"       //找回密码校验成功标记
+	CACHE_KEY_MYSTEEL_REFRESH      = "mysteel_chemical:refresh"            //上海钢联刷新
+	CACHE_KEY_DAYNEW_REFRESH       = "admin:day_new:refresh"               //每日资讯拉取企业微信聊天记录
+	CACHE_KEY_DAYNEW_TRANSLATE     = "admin:day_new:translate"             //每日资讯中翻英
+	CACHE_KEY_ADMIN_OPERATE_RECORD = "HZ_ADMIN_CACHE_ADMIN_OPERATE_RECORD" //系统用户操作日志队列
 
 	CACHE_SYNC_ADMIN        = "hz_crm_eta:sync_admin"        // 同步用户的缓存队列key
 	CACHE_SYNC_ROLE         = "hz_crm_eta:sync_role"         // 同步角色的缓存队列key
@@ -278,6 +278,8 @@ const (
 	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:"           //巡检消息队列
+	CACHE_EDB_MONITOR_MESSAGE               = "eta:edb:monitor:message:"              //指标预警消息队列
 )
 
 // 模板消息推送类型
@@ -616,3 +618,24 @@ const (
 	AI_ARTICLE_SOURCE_WECHAT     = 0 // AI文章来源(微信公众号)
 	AI_ARTICLE_SOURCE_ETA_REPORT = 1 // AI文章来源(ETA报告)
 )
+
+// 日期序列
+const (
+	WindWsdDaysDefault      = "Trading"   // 日期选项:交易日参数值
+	WindWsdDaysWeekdays     = "Weekdays"  // 日期选项:工作日参数值
+	WindWsdDaysAlldays      = "Alldays"   // 日期选项:日历日参数值
+	WindWsdSuffixWeekdays   = "W"         // 指标编码后缀:工作日
+	WindWsdSuffixAlldays    = "A"         // 指标编码后缀:日历日
+	WindWsdPeriodDefault    = "D"         // 周期:天
+	WindWsdPeriodWeek       = "W"         // 周期:周
+	WindWsdPeriodMonth      = "M"         // 周期:月
+	WindWsdPeriodQuarter    = "Q"         // 周期:季
+	WindWsdPeriodYear       = "Y"         // 周期:年
+	WindWsdRestorationFront = "F"         // 复权方式:前复权
+	WindWsdRestorationBack  = "B"         // 复权方式:后复权
+	WindWsdRestorationFixed = "T"         // 复权方式:定点复权
+	ThsDsDaysDefault        = "Tradedays" // 日期选项:交易日参数值
+	ThsDsDaysAlldays        = "Alldays"   // 日期选项:日历日参数值
+	ThsDsDaysSuffixAlldays  = "A"         // 指标编码后缀:日历日
+	ThsDsdPeriodHalfYear    = "S"         // 周期:半年(其他频度与wind一致)
+)

+ 1 - 0
utils/redis.go

@@ -19,6 +19,7 @@ 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)

+ 19 - 0
utils/redis/cluster_redis.go

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

+ 18 - 0
utils/redis/standalone_redis.go

@@ -237,6 +237,24 @@ 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

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików