Browse Source

Merge branch 'master' into feature/eta1.9.5_future_good

xyxie 8 months ago
parent
commit
9de308f046

+ 2 - 1
.gitignore

@@ -8,4 +8,5 @@
 /eta_chart_lib.exe~
 .Ds_Store
 eta_chart_lib
-*.gz
+*.gz
+.vscode/

+ 124 - 0
controllers/chart.go

@@ -8,6 +8,7 @@ import (
 	"eta/eta_chart_lib/models/data_manage/excel"
 	"eta/eta_chart_lib/services/data"
 	"eta/eta_chart_lib/services/data/cross_variety"
+	dwmini "eta/eta_chart_lib/services/dw_mini"
 	"eta/eta_chart_lib/utils"
 	"fmt"
 	"strings"
@@ -33,6 +34,8 @@ func (this *ChartController) ChartInfoDetail() {
 	}()
 
 	uniqueCode := this.GetString("UniqueCode")
+	token := this.GetString("Token")
+	source, _ := this.GetInt("Source")
 	if uniqueCode == "" {
 		br.Msg = "参数错误"
 		br.ErrMsg = "参数错误,uniqueCode is empty"
@@ -48,6 +51,16 @@ func (this *ChartController) ChartInfoDetail() {
 		br.ErrMsg = "获取配置信息失败, Err: " + e.Error()
 		return
 	}
+	var isCollect bool
+	if source == utils.CHART_SOURCE_DW && token != "" {
+		tmpIsCollect, err := dwmini.GetMyChartIsCollect(token, uniqueCode)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取收藏状态失败,Err:" + err.Error()
+			return
+		}
+		isCollect = tmpIsCollect
+	}
 
 	//判断是否有缓存
 	if utils.Re == nil {
@@ -58,6 +71,9 @@ func (this *ChartController) ChartInfoDetail() {
 					if conf[models.BusinessConfWatermarkChart] == "true" && conf[models.BusinessConfCompanyWatermark] != "" {
 						resp.WaterMark = conf[models.BusinessConfCompanyWatermark]
 					}
+					if isCollect {
+						resp.IsCollect = isCollect
+					}
 					br.Ret = 200
 					br.Success = true
 					br.Msg = "获取成功"
@@ -116,6 +132,9 @@ func (this *ChartController) ChartInfoDetail() {
 		br.ErrMsg = errMsg
 		return
 	}
+	if isCollect {
+		resp.IsCollect = isCollect
+	}
 
 	if conf[models.BusinessConfWatermarkChart] == "true" && conf[models.BusinessConfCompanyWatermark] != "" {
 		resp.WaterMark = conf[models.BusinessConfCompanyWatermark]
@@ -222,6 +241,111 @@ func (this *ChartController) ChartInfoRefresh() {
 	br.Msg = "刷新成功"
 }
 
+// CollectCancel
+// @Title 东吴小程序图表取消收藏接口
+// @Description 东吴小程序图表取消收藏接口
+// @Param	request	body models.ChartDwCollectReq true "type json string"
+// @Success Ret=200 取消收藏成功
+// @router /dw/collectCancel [post]
+func (this *ChartController) CollectCancel() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req models.ChartDwCollectReq
+	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,Err:" + err.Error()
+		return
+	}
+	if req.UniqueCode == "" {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,UniqueCode is empty"
+		return
+	}
+	if req.Token == "" {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,Token is empty"
+		return
+	}
+	result, err := dwmini.MyChartCollectCancel(req.Token, req.UniqueCode)
+	if err != nil {
+		br.Msg = "取消收藏失败"
+		br.ErrMsg = "取消收藏失败,Err:" + err.Error()
+		return
+	}
+	if result.Ret != 200 {
+		br.Msg = result.Msg
+		br.ErrMsg = "取消收藏失败,Err:" + result.ErrMsg
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "取消收藏成功"
+}
+
+// Collect
+// @Title 东吴小程序图表收藏接口
+// @Description 东吴小程序图表收藏接口
+// @Param   request	body models.ChartDwCollectReq true "type json string"
+// @Success Ret=200 收藏成功
+// @router /dw/collect [post]
+func (this *ChartController) Collect() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+
+	var req models.ChartDwCollectReq
+	if err := json.Unmarshal(this.Ctx.Input.RequestBody, &req); err != nil {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,Err:" + err.Error()
+		return
+	}
+	if req.UniqueCode == "" {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,UniqueCode is empty"
+		return
+	}
+	if req.Token == "" {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误,Token is empty"
+		return
+	}
+
+	chartInfo, err := models.GetChartInfoByUniqueCode(req.UniqueCode)
+	if err != nil {
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "该图已被删除,请刷新页面"
+			br.ErrMsg = "该图已被删除,请刷新页面,Err:" + err.Error()
+			return
+		}
+		br.Msg = "获取失败"
+		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+		return
+	}
+
+	result, err := dwmini.MyChartCollect(req.Token, req.UniqueCode, chartInfo.ChartName, chartInfo.ChartImage, chartInfo.ChartInfoId)
+	if err != nil {
+		br.Msg = "收藏失败"
+		br.ErrMsg = "收藏失败,Err:" + err.Error()
+		return
+	}
+	if result.Ret != 200 {
+		br.Msg = result.Msg
+		br.ErrMsg = "收藏失败,Err:" + result.ErrMsg
+		return
+	}
+
+	br.Ret = 200
+	br.Success = true
+	br.Msg = "收藏成功"
+}
+
 // 获取频度的英文版
 func GetFrequencyEn(frequency string) (frequencyEn string) {
 	switch frequency {

+ 54 - 20
controllers/chart_common.go

@@ -17,6 +17,7 @@ import (
 	future_goodServ "eta/eta_chart_lib/services/data/future_good"
 	"eta/eta_chart_lib/services/data/line_equation"
 	lineFeatureServ "eta/eta_chart_lib/services/data/line_feature"
+	dwmini "eta/eta_chart_lib/services/dw_mini"
 	"eta/eta_chart_lib/utils"
 	"fmt"
 	"strconv"
@@ -29,6 +30,8 @@ import (
 // @Description 根据编码获取图表详情接口
 // @Param   UniqueCode   query   int  true       "图表唯一编码,如果是管理后台访问,传固定字符串:7c69b590249049942070ae9dcd5bf6dc"
 // @Param   IsCache   query   bool  true       "是否走缓存,默认false"
+// @Param   Token   query   string  true       "东吴小程序token"
+// @Param   Source   query   int  true       "查询来源 1:东吴"
 // @Success 200 {object} data_manage.ChartInfoDetailFromUniqueCodeResp
 // @router /common/detail [get]
 func (this *ChartController) CommonChartInfoDetailFromUniqueCode() {
@@ -39,6 +42,8 @@ func (this *ChartController) CommonChartInfoDetailFromUniqueCode() {
 	}()
 
 	uniqueCode := this.GetString("UniqueCode")
+	token := this.GetString("Token")
+	source, _ := this.GetInt("Source")
 	if uniqueCode == "" {
 		br.Msg = "参数错误"
 		br.ErrMsg = "参数错误,uniqueCode is empty"
@@ -54,7 +59,16 @@ func (this *ChartController) CommonChartInfoDetailFromUniqueCode() {
 		br.ErrMsg = "获取配置信息失败, Err: " + e.Error()
 		return
 	}
-
+	var isCollect bool
+	if source == utils.CHART_SOURCE_DW && token != "" {
+		tmpIsCollect, err := dwmini.GetMyChartIsCollect(token, uniqueCode)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取收藏状态失败,Err:" + err.Error()
+			return
+		}
+		isCollect = tmpIsCollect
+	}
 	//判断是否有缓存
 	if utils.Re == nil {
 		if utils.Re == nil && utils.Rc.IsExist(key) {
@@ -64,6 +78,9 @@ func (this *ChartController) CommonChartInfoDetailFromUniqueCode() {
 					if conf[models.BusinessConfWatermarkChart] == "true" && conf[models.BusinessConfCompanyWatermark] != "" {
 						resp.WaterMark = conf[models.BusinessConfCompanyWatermark]
 					}
+					if isCollect {
+						resp.IsCollect = isCollect
+					}
 					br.Ret = 200
 					br.Success = true
 					br.Msg = "获取成功"
@@ -123,6 +140,10 @@ func (this *ChartController) CommonChartInfoDetailFromUniqueCode() {
 		return
 	}
 
+	if isCollect {
+		resp.IsCollect = isCollect
+	}
+
 	if conf[models.BusinessConfWatermarkChart] == "true" && conf[models.BusinessConfCompanyWatermark] != "" {
 		resp.WaterMark = conf[models.BusinessConfCompanyWatermark]
 	}
@@ -766,36 +787,48 @@ func GetCorrelationChartInfoDetailFromUniqueCode(chartInfo *models.ChartInfo, ke
 		errMsg = "获取相关性图表, A指标mapping信息失败, Err:" + e.Error()
 		return
 	}
-	edbInfoMappingB, e := models.GetChartEdbMappingByEdbInfoId(correlationChart.EdbInfoIdSecond)
-	if e != nil {
-		msg = "获取失败"
-		errMsg = "获取相关性图表, B指标mapping信息失败, Err:" + e.Error()
-		return
+	edbInfoMappingB := new(models.ChartEdbInfoMapping)
+	if correlationChart.AnalysisMode != 1 {
+		edbInfoMappingB, e = models.GetChartEdbMappingByEdbInfoId(correlationChart.EdbInfoIdSecond)
+		if e != nil {
+			msg = "获取失败"
+			errMsg = "获取相关性图表, B指标mapping信息失败, Err:" + e.Error()
+			return
+		}
 	}
 
 	var dataResp interface{} // 绘图数据返回(目前是滚动相关性的图)
 	var xEdbIdValue []int
 	var yDataList []models.YData
-	switch chartInfo.Source {
-	case utils.CHART_SOURCE_CORRELATION: // 相关性图
-		moveUnitDays, ok := utils.FrequencyDaysMap[correlationChart.CalculateUnit]
-		if !ok {
-			msg = "错误的分析周期"
-			errMsg = "相关性图表数据有误"
-			return
+	if correlationChart.AnalysisMode != 1 {
+		switch chartInfo.Source {
+		case utils.CHART_SOURCE_CORRELATION: // 相关性图
+			moveUnitDays, ok := utils.FrequencyDaysMap[correlationChart.CalculateUnit]
+			if !ok {
+				msg = "错误的分析周期"
+				errMsg = "相关性图表数据有误"
+				return
+			}
+			st := time.Now().AddDate(0, 0, -correlationChart.CalculateValue*moveUnitDays).Format(utils.FormatDate)
+			ed := time.Now().Format(utils.FormatDate)
+
+			xEdbIdValue, yDataList, e = correlationServ.GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, st, ed, chartInfo.ExtraConfig)
+			if e != nil {
+				msg = "获取失败"
+				errMsg = "获取相关性图表, 图表计算值失败, Err:" + e.Error()
+				return
+			}
+		case utils.CHART_SOURCE_ROLLING_CORRELATION: // 滚动相关性图
+			st, ed := utils.GetDateByDateType(correlationChart.DateType, correlationChart.StartDate.Format(utils.FormatDate), correlationChart.EndDate.Format(utils.FormatDate))
+			dataResp, e = correlationServ.GetRollingCorrelationChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, correlationChart.CalculateValue, correlationChart.CalculateUnit, st, ed, chartInfo.ChartName, chartInfo.ChartNameEn)
 		}
-		startDate := time.Now().AddDate(0, 0, -correlationChart.CalculateValue*moveUnitDays).Format(utils.FormatDate)
-		endDate := time.Now().Format(utils.FormatDate)
-
-		xEdbIdValue, yDataList, e = correlationServ.GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, startDate, endDate)
+	} else {
+		xEdbIdValue, yDataList, e = correlationServ.GetFactorChartDataByChartId(chartInfoId, chartInfo.ExtraConfig)
 		if e != nil {
 			msg = "获取失败"
 			errMsg = "获取相关性图表, 图表计算值失败, Err:" + e.Error()
 			return
 		}
-	case utils.CHART_SOURCE_ROLLING_CORRELATION: // 滚动相关性图
-		startDate, endDate := utils.GetDateByDateType(correlationChart.DateType, correlationChart.StartDate.Format(utils.FormatDate), correlationChart.EndDate.Format(utils.FormatDate))
-		dataResp, e = correlationServ.GetRollingCorrelationChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB, correlationChart.LeadValue, correlationChart.LeadUnit, correlationChart.CalculateValue, correlationChart.CalculateUnit, startDate, endDate, chartInfo.ChartName, chartInfo.ChartNameEn)
 	}
 
 	// 完善指标信息
@@ -819,6 +852,7 @@ func GetCorrelationChartInfoDetailFromUniqueCode(chartInfo *models.ChartInfo, ke
 	correlationInfo.LeadValue = correlationChart.LeadValue
 	correlationInfo.EdbInfoIdFirst = correlationChart.EdbInfoIdFirst
 	correlationInfo.EdbInfoIdSecond = correlationChart.EdbInfoIdSecond
+	correlationInfo.AnalysisMode = correlationChart.AnalysisMode
 
 	resp.ChartInfo = chartInfo
 	resp.EdbInfoList = edbList

+ 1 - 1
controllers/excel_info.go

@@ -150,7 +150,7 @@ func (this *ExcelInfoController) GetTableDetail() {
 			br.ErrMsg = "获取最新的数据失败,Err:" + tmpErr.Error()
 			return
 		}
-		tableData, err = excel.GetTableDataByMixedTableData(newResult)
+		tableData, err = excel.GetTableDataByMixedTableData(newResult, true)
 		if err != nil {
 			br.Msg = "获取失败"
 			br.ErrMsg = "转换成table失败,Err:" + err.Error()

+ 16 - 0
models/business_conf.go

@@ -1,6 +1,7 @@
 package models
 
 import (
+	"eta/eta_chart_lib/utils"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
 	"html"
@@ -61,3 +62,18 @@ func GetBusinessConfByKey(key string) (item *BusinessConf, err error) {
 	err = o.Raw(sql, key).QueryRow(&item)
 	return
 }
+
+// InitUseMongoConf
+// @Description:
+// @author: Roc
+// @datetime 2024-07-01 13:49:09
+func InitUseMongoConf() {
+	useMongo, e := GetBusinessConfByKey("UseMongo")
+	if e != nil {
+		return
+	}
+
+	if useMongo.ConfVal == `true` {
+		utils.UseMongo = true
+	}
+}

+ 214 - 7
models/chart.go

@@ -5,11 +5,12 @@ import (
 	"eta/eta_chart_lib/models/mgo"
 	"eta/eta_chart_lib/utils"
 	"fmt"
-	"github.com/beego/beego/v2/client/orm"
-	"go.mongodb.org/mongo-driver/bson"
 	"strconv"
 	"time"
 
+	"github.com/beego/beego/v2/client/orm"
+	"go.mongodb.org/mongo-driver/bson"
+
 	"github.com/nosixtools/solarlunar"
 )
 
@@ -144,9 +145,12 @@ type EdbDataList struct {
 // GetEdbDataList 获取指标的数据(日期正序返回)
 func GetEdbDataList(source, subSource, edbInfoId int, startDate, endDate string) (list []*EdbDataList, err error) {
 	// 自有数据需要额外处理(从mongo获取)
-	if source == utils.DATA_SOURCE_BUSINESS {
+	if source == utils.DATA_SOURCE_BUSINESS && utils.UseMongo {
 		return getEdbDataListByMongo(source, subSource, edbInfoId, startDate, endDate)
 	}
+	if source == utils.DATA_SOURCE_THS && subSource == utils.DATA_SUB_SOURCE_HIGH_FREQUENCY && utils.UseMongo {
+		return getThsHfEdbDataListByMongo(source, subSource, edbInfoId, startDate, endDate)
+	}
 
 	return getEdbDataListByMysql(source, subSource, edbInfoId, startDate, endDate)
 }
@@ -281,6 +285,7 @@ type ChartInfoDetailResp struct {
 	CorrelationChartInfo *CorrelationInfo `description:"相关性图表信息"`
 	DataResp             interface{}      `description:"图表数据,根据图的类型而定的,没有确定的数据格式"`
 	WaterMark            string           `description:"水印"`
+	IsCollect            bool             `description:"是否收藏"`
 }
 
 // XData 商品价格曲线的的x轴数据
@@ -304,6 +309,10 @@ type YData struct {
 	M              []int           `description:"对应开始日期的间隔值" json:"-"`
 	NameList       []string        `description:"每个值对应的名称"`
 	EnNameList     []string        `description:"每个值对应的英文名称"`
+	SeriesEdb      struct {
+		SeriesId  int `description:"因子指标系列ID"`
+		EdbInfoId int `description:"指标ID"`
+	} `description:"对应的系列指标"`
 }
 
 // RollingCorrelationChartDataResp 滚动相关性图
@@ -671,13 +680,122 @@ type CorrelationInfo struct {
 	EdbInfoIdSecond int    `description:"B指标ID"`
 	PeriodData      string `description:"X轴-期数数据"`
 	CorrelationData string `description:"Y轴-相关性系数"`
+	AnalysisMode    int    `description:"分析模式: 0-单因子; 1-多因子"`
 }
 
 type SeasonExtraItem struct {
-	ChartLegend []SeasonChartLegend `description:"自定义的图例名称"`
-	XStartDate  string              `description:"横坐标显示的起始日"`
-	XEndDate    string              `description:"横坐标显示的截止日"`
-	JumpYear    int                 `description:"横坐标日期是否跨年,1跨年,0不跨年"`
+	ChartLegend                 []SeasonChartLegend         `description:"自定义的图例名称"`
+	XStartDate                  string                      `description:"横坐标显示的起始日"`
+	XEndDate                    string                      `description:"横坐标显示的截止日"`
+	JumpYear                    int                         `description:"横坐标日期是否跨年,1跨年,0不跨年"`
+	RightAxis                   SeasonRightAxis             `description:"自定义右轴指标"`
+	MaxMinLimits                MaxMinLimits                `description:"自定义同期上下限"`
+	SamePeriodAverage           SamePeriodAverage           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviation `description:"自定义同期标准差"`
+}
+
+// 自定义右轴指标
+type SeasonRightAxis struct {
+	IndicatorType int    `description:"右轴指标类型 1:左轴指标同比,2:指标库,3:预测指标 "`
+	Style         string `description:"生成样式"`
+	Shape         string `description:"形状"`
+	ChartColor    string `description:"图表颜色"`
+	Size          int    `description:"大小"`
+	Legend        string `description:"图例名称"`
+	NumFormat     int    `description:"数值格式 1:百分比 2:小数"`
+	IsConnected   int    `description:"是否连接 0不连接 1连接"`
+	LineColor     string `description:"线条颜色"`
+	LineWidth     int    `description:"线条宽度"`
+	LineStyle     string `description:"线条样式"`
+	IsShow        bool   `description:"是否显示"`
+	IsAdd         bool   `description:"是否添加"`
+}
+
+// 自定义同期上下限
+type MaxMinLimits struct {
+	Color  string `description:"颜色"`
+	Year   int    `description:"上下限取值范围"`
+	Legend string `description:"图例名称"`
+	IsShow bool   `description:"是否显示"`
+	IsAdd  bool   `description:"是否添加"`
+}
+
+// 自定义同期均线
+type SamePeriodAverage struct {
+	Color     string `description:"颜色"`
+	Year      int    `description:"均线取值范围"`
+	Legend    string `description:"图例名称"`
+	LineType  string `description:"线型"`
+	LineWidth int    `description:"线宽"`
+	IsShow    bool   `description:"是否显示"`
+	IsAdd     bool   `description:"是否添加"`
+}
+
+// 自定义同期均线
+type SamePeriodAverageResp struct {
+	Color     string                   `description:"颜色"`
+	Year      int                      `description:"均线取值范围"`
+	Legend    string                   `description:"图例名称"`
+	LineType  string                   `description:"线型"`
+	LineWidth int                      `description:"线宽"`
+	IsShow    bool                     `description:"是否显示"`
+	List      []*SamePeriodAverageData `description:"自定义均线列表"`
+	IsAdd     bool                     `description:"是否添加"`
+}
+
+type SamePeriodAverageData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	Value         float64 `description:"均值"`
+}
+
+// 自定义同期标准差
+type SamePeriodStandardDeviation struct {
+	Color    string  `description:"颜色"`
+	Year     int     `description:"标准差取值范围"`
+	Legend   string  `description:"图例名称"`
+	Multiple float64 `description:"标准差倍数"`
+	IsShow   bool    `description:"是否显示"`
+	IsAdd    bool    `description:"是否添加"`
+}
+
+type SamePeriodStandardDeviationResp struct {
+	Color    string              `description:"颜色"`
+	Year     int                 `description:"标准差取值范围"`
+	Legend   string              `description:"图例名称"`
+	Multiple float64             `description:"标准差倍数"`
+	IsShow   bool                `description:"是否显示"`
+	List     []*MaxMinLimitsData `description:"自定义标准差列表"`
+	IsAdd    bool                `description:"是否添加"`
+}
+
+type SeasonChartResp struct {
+	MaxMinLimits                MaxMinLimitsResp                `description:"自定义上下限"`
+	SamePeriodAverage           SamePeriodAverageResp           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviationResp `description:"自定义同期标准差线"`
+	RightAxis                   SeasonRightAxisResp             `description:"自定义右轴指标"`
+}
+
+// 自定义右轴指标
+type SeasonRightAxisResp struct {
+	SeasonRightAxis
+	EdbInfoList []*ChartEdbInfoMapping
+}
+
+type MaxMinLimitsResp struct {
+	List   []*MaxMinLimitsData `description:"自定义上下限列表"`
+	Color  string              `description:"颜色"`
+	Year   int                 `description:"上下限取值范围"`
+	Legend string              `description:"图例名称"`
+	IsShow bool                `description:"是否显示"`
+	IsAdd  bool                `description:"是否添加"`
+}
+
+type MaxMinLimitsData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	MaxValue      float64 `description:"最大值"`
+	MinValue      float64 `description:"最小值"`
 }
 
 type SeasonChartLegend struct {
@@ -698,3 +816,92 @@ func (m QuarterDataList) Less(i, j int) bool {
 func (m QuarterDataList) Swap(i, j int) {
 	m[i], m[j] = m[j], m[i]
 }
+
+type ChartDwCollectReq struct {
+	UniqueCode string
+	Token      string
+}
+
+type MarkersLine struct {
+	Axis             int             `json:"axis" description:"1左轴 2右轴 3横轴"`
+	AxisName         string          `json:"axisName" description:"轴的名称,例如'左轴'"`
+	MarkerType       string          `json:"markerType" description:"标识线或标识区"`
+	MarkLineType     int             `json:"markLineType" description:"1:固定 2:指标计算"`
+	Value            string          `json:"value" description:"连线指向的数值,例如'4000'"`
+	FromValue        string          `json:"fromValue" description:"连线的起始点,可以为空"`
+	ToValue          string          `json:"toValue" description:"连线的结束点,可以为空"`
+	LineWidth        int             `json:"lineWidth" description:"连线的宽度"`
+	DashStyle        string          `json:"dashStyle" description:"连线的虚线样式,例如'ShortDashDot'"`
+	Color            string          `json:"color" description:"连线的颜色"`
+	Text             string          `json:"text" description:"连线旁边显示的文本"`
+	TextPosition     string          `json:"textPosition" description:"文本的显示位置,例如'bottom'"`
+	TextColor        string          `json:"textColor" description:"文本颜色"`
+	TextFontSize     int             `json:"textFontSize" description:"文本的字号大小"`
+	IsShow           bool            `json:"isShow" description:"是否显示连线及文本"`
+	EdbType          int             `json:"edbType" description:"指标类型 0图中第一个指标 1其他指标 前端回显用"`
+	EdbInfoId        int             `json:"edbInfoId" description:"指标id"`
+	Calculation      int             `json:"calculation" description:"计算方式 1区间均值 2区间均值加N倍标准差 3区间个数分位 4区间数值分位"`
+	CalculationValue int             `json:"calculationValue" description:"计算方式对应的值 2就是几倍标准差 3就是分位值 4就是数值值·"`
+	TimeIntervalType int             `json:"timeInterval" description:"时间区间 0跟随图表 1自定义"`
+	StartDate        MarkersLineTime `json:"startTime" description:"开始时间"`
+	EndDate          MarkersLineTime `json:"endTime" description:"结束时间"`
+}
+
+type MarkersLineTime struct {
+	TimeType int               `json:"timeType" description:"时间类型 1固定 2动态 3至今(仅结束时间)"`
+	Date     string            `json:"date" description:"日期"`
+	Conf     EdbDateChangeConf `json:"conf" description:"动态时间配置"`
+}
+
+// EdbDateExtraConf
+// @Description: 导入指标日期前移和日期变换
+type EdbDateChangeConf struct {
+	MoveForward int `description:"前移的期数"`
+	BaseDate    int `description:"基准日期 0系统日期 1指标最新日期"`
+	DateChange  []*EdbDateConfDateChange
+}
+
+type EdbDateConfDateChange struct {
+	Year         int
+	Month        int
+	Day          int
+	Frequency    string `description:"频度变换"`
+	FrequencyDay string `description:"频度的固定日期"`
+	ChangeType   int    `description:"日期变换类型1日期位移,2指定频率"`
+}
+
+func getThsHfEdbDataListByMongo(source, subSource, edbInfoId int, startDate, endDate string) (list []*EdbDataList, err error) {
+	list = make([]*EdbDataList, 0)
+
+	mogDataObj := mgo.EdbDataThsHf{}
+	// 构建查询条件
+	queryConditions := bson.M{
+		"edb_info_id": edbInfoId,
+	}
+
+	// 数据日期
+	dateCondition, err := mgo.BuildDateCondition(startDate, endDate)
+	if err != nil {
+		return
+	}
+	if len(dateCondition) > 0 {
+		queryConditions["data_time"] = dateCondition
+	}
+
+	// 获取列表数据
+	tmpDataList, tmpErr := mogDataObj.GetAllDataList(queryConditions, []string{"data_time"})
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	for k, v := range tmpDataList {
+		list = append(list, &EdbDataList{
+			EdbDataId:     k + 1,
+			EdbInfoId:     v.EdbInfoId,
+			DataTime:      v.DataTime.Format(utils.FormatDate),
+			DataTimestamp: v.DataTimestamp,
+			Value:         v.Value,
+		})
+	}
+	return
+}

+ 29 - 0
models/data_manage/chart_info_correlation.go

@@ -25,6 +25,7 @@ type ChartInfoCorrelation struct {
 	CorrelationData        string    `description:"Y轴-相关性系数"`
 	CreateTime             time.Time `description:"创建时间"`
 	ModifyTime             time.Time `description:"更新时间"`
+	AnalysisMode           int       `description:"分析模式: 0-单因子; 1-多因子"`
 }
 
 func (m *ChartInfoCorrelation) TableName() string {
@@ -82,3 +83,31 @@ func (m *ChartInfoCorrelation) GetItemsByCondition(condition string, pars []inte
 	_, err = o.Raw(sql, pars).QueryRows(&items)
 	return
 }
+
+// FactorCorrelationConfig 因子指标系列-相关性配置
+type FactorCorrelationConfig struct {
+	LeadValue      int                       `description:"领先期数"`
+	LeadUnit       string                    `description:"频度"`
+	CalculateValue int                       `description:"计算窗口"`
+	CalculateUnit  string                    `description:"计算频度"`
+	SeriesEdb      []CorrelationSeriesEdbReq `description:"关联系列指标"`
+}
+
+// CorrelationChartLegend 相关性图表图例
+type CorrelationChartLegend struct {
+	LegendName string `description:"图例名称"`
+	Color      string `description:"图例颜色"`
+	EdbInfoId  int    `description:"指标ID"`
+	SeriesId   int    `description:"因子指标系列ID"`
+}
+
+// CorrelationSeriesEdbReq 指标系列
+type CorrelationSeriesEdbReq struct {
+	EdbInfoId int `description:"指标ID"`
+	SeriesId  int `description:"因子指标系列ID"`
+}
+
+// CorrelationChartInfoExtraConfig 相关性图表额外设置
+type CorrelationChartInfoExtraConfig struct {
+	LegendConfig []*CorrelationChartLegend `description:"图例设置"`
+}

+ 1 - 0
models/data_manage/edb_info.go

@@ -35,6 +35,7 @@ type EdbInfo struct {
 	LatestValue      float64 `description:"数据最新值"`
 	MoveType         int     `description:"移动方式:1:领先(默认),2:滞后"`
 	MoveFrequency    string  `description:"移动频度"`
+	NoUpdate         int8    `description:"是否停止更新,0:继续更新;1:停止更新"`
 	DataDateType     string  `orm:"column(data_date_type);size(255);null;default(交易日)"`
 	SubSource        int     `description:"子数据来源:0:经济数据库,1:日期序列"`
 	SubSourceName    string  `description:"子数据来源名称"`

+ 350 - 0
models/data_manage/factor_edb_series.go

@@ -0,0 +1,350 @@
+package data_manage
+
+import (
+	"encoding/json"
+	"eta/eta_chart_lib/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const (
+	FactorEdbSeriesCalculateNone = 0
+	FactorEdbSeriesCalculating   = 1
+	FactorEdbSeriesCalculated    = 2
+)
+
+// FactorEdbSeries 因子指标系列表
+type FactorEdbSeries struct {
+	FactorEdbSeriesId int       `orm:"column(factor_edb_series_id);pk"`
+	SeriesName        string    `description:"系列名称"`
+	EdbInfoType       int       `description:"关联指标类型:0-普通指标;1-预测指标"`
+	CalculateStep     string    `description:"计算步骤-JSON"`
+	CalculateState    int       `description:"计算状态: 0-无计算; 1-计算中; 2-计算完成"`
+	CreateTime        time.Time `description:"创建时间"`
+	ModifyTime        time.Time `description:"修改时间"`
+}
+
+func (m *FactorEdbSeries) TableName() string {
+	return "factor_edb_series"
+}
+
+type FactorEdbSeriesCols struct {
+	PrimaryId      string
+	SeriesName     string
+	EdbInfoType    string
+	CalculateStep  string
+	CalculateState string
+	CreateTime     string
+	ModifyTime     string
+}
+
+func (m *FactorEdbSeries) Cols() FactorEdbSeriesCols {
+	return FactorEdbSeriesCols{
+		PrimaryId:      "factor_edb_series_id",
+		SeriesName:     "series_name",
+		EdbInfoType:    "edb_info_type",
+		CalculateStep:  "calculate_step",
+		CalculateState: "calculate_state",
+		CreateTime:     "create_time",
+		ModifyTime:     "modify_time",
+	}
+}
+
+func (m *FactorEdbSeries) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.FactorEdbSeriesId = int(id)
+	return
+}
+
+func (m *FactorEdbSeries) CreateMulti(items []*FactorEdbSeries) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *FactorEdbSeries) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *FactorEdbSeries) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sql, m.FactorEdbSeriesId).Exec()
+	return
+}
+
+func (m *FactorEdbSeries) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func (m *FactorEdbSeries) GetItemById(id int) (item *FactorEdbSeries, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeries) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *FactorEdbSeries, err error) {
+	o := orm.NewOrmUsingDB("data")
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeries) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *FactorEdbSeries) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*FactorEdbSeries, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *FactorEdbSeries) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*FactorEdbSeries, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// FactorEdbSeriesItem 多因子系列信息
+type FactorEdbSeriesItem struct {
+	SeriesId      int                            `description:"多因子系列ID"`
+	SeriesName    string                         `description:"系列名称"`
+	EdbInfoType   int                            `description:"关联指标类型:0-普通指标;1-预测指标"`
+	CalculateStep []FactorEdbSeriesCalculatePars `description:"计算步骤-JSON"`
+	CreateTime    string                         `description:"创建时间"`
+	ModifyTime    string                         `description:"修改时间"`
+}
+
+func (m *FactorEdbSeries) Format2Item() (item *FactorEdbSeriesItem) {
+	item = new(FactorEdbSeriesItem)
+	item.SeriesId = m.FactorEdbSeriesId
+	item.SeriesName = m.SeriesName
+	item.EdbInfoType = m.EdbInfoType
+	if m.CalculateStep != "" {
+		_ = json.Unmarshal([]byte(m.CalculateStep), &item.CalculateStep)
+	}
+	item.CreateTime = utils.TimeTransferString(utils.FormatDateTime, m.CreateTime)
+	item.ModifyTime = utils.TimeTransferString(utils.FormatDateTime, m.ModifyTime)
+	return
+}
+
+// FactorEdbSeriesCalculatePars 计算参数
+type FactorEdbSeriesCalculatePars struct {
+	Formula       interface{} `description:"N值/移动天数/指数修匀alpha值/计算公式等"`
+	Calendar      string      `description:"公历/农历"`
+	Frequency     string      `description:"需要转换的频度"`
+	MoveType      int         `description:"移动方式: 1-领先(默认); 2-滞后"`
+	MoveFrequency string      `description:"移动频度"`
+	FromFrequency string      `description:"来源的频度"`
+	Source        int         `description:"计算方式来源(不是指标来源)"`
+	Sort          int         `description:"计算顺序"`
+}
+
+// CreateSeriesAndMapping 新增系列和指标关联
+func (m *FactorEdbSeries) CreateSeriesAndMapping(item *FactorEdbSeries, mappings []*FactorEdbSeriesMapping) (seriesId int, err error) {
+	if item == nil {
+		err = fmt.Errorf("series is nil")
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	id, e := tx.Insert(item)
+	if e != nil {
+		err = fmt.Errorf("insert series err: %v", e)
+		return
+	}
+	seriesId = int(id)
+	item.FactorEdbSeriesId = seriesId
+
+	if len(mappings) > 0 {
+		for _, v := range mappings {
+			v.FactorEdbSeriesId = seriesId
+		}
+		_, e = tx.InsertMulti(200, mappings)
+		if e != nil {
+			err = fmt.Errorf("insert multi mapping err: %v", e)
+			return
+		}
+	}
+	return
+}
+
+// EditSeriesAndMapping 编辑系列和指标关联
+func (m *FactorEdbSeries) EditSeriesAndMapping(item *FactorEdbSeries, mappings []*FactorEdbSeriesMapping, updateCols []string) (err error) {
+	if item == nil {
+		err = fmt.Errorf("series is nil")
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	tx, e := o.Begin()
+	if e != nil {
+		err = fmt.Errorf("orm begin err: %v", e)
+		return
+	}
+	defer func() {
+		if err != nil {
+			_ = tx.Rollback()
+			return
+		}
+		_ = tx.Commit()
+	}()
+
+	_, e = tx.Update(item, updateCols...)
+	if e != nil {
+		err = fmt.Errorf("update series err: %v", e)
+		return
+	}
+
+	// 清除原指标关联
+	mappingOb := new(FactorEdbSeriesMapping)
+	cond := fmt.Sprintf("%s = ?", mappingOb.Cols().FactorEdbSeriesId)
+	pars := make([]interface{}, 0)
+	pars = append(pars, item.FactorEdbSeriesId)
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, mappingOb.TableName(), cond)
+	_, e = tx.Raw(sql, pars).Exec()
+	if e != nil {
+		err = fmt.Errorf("remove mapping err: %v", e)
+		return
+	}
+
+	if len(mappings) > 0 {
+		for _, v := range mappings {
+			v.FactorEdbSeriesId = item.FactorEdbSeriesId
+		}
+		_, e = tx.InsertMulti(200, mappings)
+		if e != nil {
+			err = fmt.Errorf("insert multi mapping err: %v", e)
+			return
+		}
+	}
+	return
+}
+
+// FactorEdbSeriesStepCalculateResp 批量计算响应
+type FactorEdbSeriesStepCalculateResp struct {
+	SeriesId int                                  `description:"多因子指标系列ID"`
+	Fail     []FactorEdbSeriesStepCalculateResult `description:"计算失败的指标"`
+	Success  []FactorEdbSeriesStepCalculateResult `description:"计算成功的指标"`
+}
+
+// FactorEdbSeriesStepCalculateResult 批量计算结果
+type FactorEdbSeriesStepCalculateResult struct {
+	EdbInfoId int    `description:"指标ID"`
+	EdbCode   string `description:"指标编码"`
+	Msg       string `description:"提示信息"`
+	ErrMsg    string `description:"错误信息"`
+}
+
+// FactorEdbSeriesDetail 因子指标系列-详情
+type FactorEdbSeriesDetail struct {
+	*FactorEdbSeriesItem
+	EdbMappings []*FactorEdbSeriesMappingItem
+}
+
+// FactorEdbSeriesCorrelationMatrixResp 因子指标系列-相关性矩阵响应
+type FactorEdbSeriesCorrelationMatrixResp struct {
+	Fail    []FactorEdbSeriesCorrelationMatrixItem `description:"计算失败的指标"`
+	Success []FactorEdbSeriesCorrelationMatrixItem `description:"计算成功的指标"`
+}
+
+// FactorEdbSeriesCorrelationMatrixItem 因子指标系列-相关性矩阵信息
+type FactorEdbSeriesCorrelationMatrixItem struct {
+	SeriesId   int                                      `description:"因子指标系列ID"`
+	EdbInfoId  int                                      `description:"指标ID"`
+	EdbCode    string                                   `description:"指标编码"`
+	EdbName    string                                   `description:"指标名称"`
+	Values     []FactorEdbSeriesCorrelationMatrixValues `description:"X轴和Y轴数据"`
+	Msg        string                                   `description:"提示信息"`
+	ErrMsg     string                                   `description:"错误信息"`
+	Used       bool                                     `description:"是否选中"`
+	SourceName string                                   `description:"指标来源名称"`
+}
+
+// FactorEdbSeriesCorrelationMatrixValues 因子指标系列-相关性矩阵XY值
+type FactorEdbSeriesCorrelationMatrixValues struct {
+	XData int     `description:"X轴数据"`
+	YData float64 `description:"Y轴数据"`
+}
+
+// FactorEdbSeriesCorrelationMatrixOrder 排序规则[0 1 2 3 -1 -2 -3]
+type FactorEdbSeriesCorrelationMatrixOrder []FactorEdbSeriesCorrelationMatrixValues
+
+func (a FactorEdbSeriesCorrelationMatrixOrder) Len() int {
+	return len(a)
+}
+
+func (a FactorEdbSeriesCorrelationMatrixOrder) Swap(i, j int) {
+	a[i], a[j] = a[j], a[i]
+}
+
+func (a FactorEdbSeriesCorrelationMatrixOrder) Less(i, j int) bool {
+	// 非负数优先
+	if a[i].XData >= 0 && a[j].XData < 0 {
+		return true
+	}
+	if a[i].XData < 0 && a[j].XData >= 0 {
+		return false
+	}
+	// 非负数升序排序
+	if a[i].XData >= 0 {
+		return a[i].XData < a[j].XData
+	}
+	// 负数按绝对值的降序排序(即数值的升序)
+	return a[i].XData > a[j].XData
+}

+ 167 - 0
models/data_manage/factor_edb_series_chart_mapping.go

@@ -0,0 +1,167 @@
+package data_manage
+
+import (
+	"eta/eta_chart_lib/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+const (
+	FactorEdbSeriesChartCalculateTypeCorrelation = 1 // 相关性计算
+)
+
+// FactorEdbSeriesChartMapping 因子指标系列-图表关联
+type FactorEdbSeriesChartMapping struct {
+	FactorEdbSeriesChartMappingId int       `orm:"column(factor_edb_series_chart_mapping_id);pk"`
+	ChartInfoId                   int       `description:"图表ID"`
+	Source                        int       `description:"图表来源, 同chart_info表source"`
+	CalculateType                 int       `description:"计算方式: 1-相关性"`
+	CalculatePars                 string    `description:"计算参数-JSON(如计算窗口等)"`
+	CalculateData                 string    `description:"计算数据-JSON(如相关性矩阵等)"`
+	FactorEdbSeriesId             int       `description:"因子指标系列ID"`
+	EdbInfoId                     int       `description:"指标ID"`
+	EdbUsed                       int       `description:"指标是否使用: 0-否; 1-是"`
+	CreateTime                    time.Time `description:"创建时间"`
+	ModifyTime                    time.Time `description:"修改时间"`
+}
+
+func (m *FactorEdbSeriesChartMapping) TableName() string {
+	return "factor_edb_series_chart_mapping"
+}
+
+type MultipleFactorSeriesChartMappingCols struct {
+	PrimaryId         string
+	ChartInfoId       string
+	Source            string
+	CalculateType     string
+	CalculatePars     string
+	CalculateData     string
+	FactorEdbSeriesId string
+	EdbInfoId         string
+	EdbUsed           string
+	CreateTime        string
+	ModifyTime        string
+}
+
+func (m *FactorEdbSeriesChartMapping) Cols() MultipleFactorSeriesChartMappingCols {
+	return MultipleFactorSeriesChartMappingCols{
+		PrimaryId:         "factor_edb_series_chart_mapping_id",
+		ChartInfoId:       "chart_info_id",
+		Source:            "source",
+		CalculateType:     "calculate_type",
+		CalculatePars:     "calculate_pars",
+		CalculateData:     "calculate_data",
+		FactorEdbSeriesId: "factor_edb_series_id",
+		EdbInfoId:         "edb_info_id",
+		EdbUsed:           "edb_used",
+		CreateTime:        "create_time",
+		ModifyTime:        "modify_time",
+	}
+}
+
+func (m *FactorEdbSeriesChartMapping) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.FactorEdbSeriesChartMappingId = int(id)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) CreateMulti(items []*FactorEdbSeriesChartMapping) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sql, m.FactorEdbSeriesChartMappingId).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) GetItemById(id int) (item *FactorEdbSeriesChartMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *FactorEdbSeriesChartMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*FactorEdbSeriesChartMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *FactorEdbSeriesChartMapping) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*FactorEdbSeriesChartMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// GetDistinctSeriesIdByChartId 获取图表关联的系列ID
+func (m *FactorEdbSeriesChartMapping) GetDistinctSeriesIdByChartId(chartId int) (seriesIds []int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT DISTINCT %s FROM %s WHERE %s = ?`, m.Cols().FactorEdbSeriesId, m.TableName(), m.Cols().ChartInfoId)
+	_, err = o.Raw(sql, chartId).QueryRows(&seriesIds)
+	return
+}

+ 210 - 0
models/data_manage/factor_edb_series_mapping.go

@@ -0,0 +1,210 @@
+package data_manage
+
+import (
+	"eta/eta_chart_lib/utils"
+	"fmt"
+	"github.com/beego/beego/v2/client/orm"
+	"strings"
+	"time"
+)
+
+// FactorEdbSeriesMapping 因子指标系列-指标关联表
+type FactorEdbSeriesMapping struct {
+	FactorEdbSeriesMappingId int       `orm:"column(factor_edb_series_mapping_id);pk"`
+	FactorEdbSeriesId        int       `description:"因子指标系列ID"`
+	EdbInfoId                int       `description:"指标ID"`
+	EdbCode                  string    `description:"指标编码"`
+	CreateTime               time.Time `description:"创建时间"`
+	ModifyTime               time.Time `description:"修改时间"`
+}
+
+func (m *FactorEdbSeriesMapping) TableName() string {
+	return "factor_edb_series_mapping"
+}
+
+type FactorEdbSeriesMappingCols struct {
+	PrimaryId         string
+	FactorEdbSeriesId string
+	EdbInfoId         string
+	EdbCode           string
+	CreateTime        string
+	ModifyTime        string
+}
+
+func (m *FactorEdbSeriesMapping) Cols() FactorEdbSeriesMappingCols {
+	return FactorEdbSeriesMappingCols{
+		PrimaryId:         "factor_edb_series_mapping_id",
+		FactorEdbSeriesId: "factor_edb_series_id",
+		EdbInfoId:         "edb_info_id",
+		EdbCode:           "edb_code",
+		CreateTime:        "create_time",
+		ModifyTime:        "modify_time",
+	}
+}
+
+func (m *FactorEdbSeriesMapping) Create() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	id, err := o.Insert(m)
+	if err != nil {
+		return
+	}
+	m.FactorEdbSeriesMappingId = int(id)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) CreateMulti(items []*FactorEdbSeriesMapping) (err error) {
+	if len(items) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.InsertMulti(len(items), items)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) Update(cols []string) (err error) {
+	o := orm.NewOrmUsingDB("data")
+	_, err = o.Update(m, cols...)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) Remove() (err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	_, err = o.Raw(sql, m.FactorEdbSeriesMappingId).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesMapping) MultiRemove(ids []int) (err error) {
+	if len(ids) == 0 {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s IN (%s)`, m.TableName(), m.Cols().PrimaryId, utils.GetOrmInReplace(len(ids)))
+	_, err = o.Raw(sql, ids).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesMapping) RemoveByCondition(condition string, pars []interface{}) (err error) {
+	if condition == "" {
+		return
+	}
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`DELETE FROM %s WHERE %s`, m.TableName(), condition)
+	_, err = o.Raw(sql, pars).Exec()
+	return
+}
+
+func (m *FactorEdbSeriesMapping) GetItemById(id int) (item *FactorEdbSeriesMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ? LIMIT 1`, m.TableName(), m.Cols().PrimaryId)
+	err = o.Raw(sql, id).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) GetItemByCondition(condition string, pars []interface{}, orderRule string) (item *FactorEdbSeriesMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	order := ``
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT * FROM %s WHERE 1=1 %s %s LIMIT 1`, m.TableName(), condition, order)
+	err = o.Raw(sql, pars).QueryRow(&item)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) GetCountByCondition(condition string, pars []interface{}) (count int, err error) {
+	o := orm.NewOrmUsingDB("data")
+	sql := fmt.Sprintf(`SELECT COUNT(1) FROM %s WHERE 1=1 %s`, m.TableName(), condition)
+	err = o.Raw(sql, pars).QueryRow(&count)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) GetItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string) (items []*FactorEdbSeriesMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars).QueryRows(&items)
+	return
+}
+
+func (m *FactorEdbSeriesMapping) GetPageItemsByCondition(condition string, pars []interface{}, fieldArr []string, orderRule string, startSize, pageSize int) (items []*FactorEdbSeriesMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	fields := strings.Join(fieldArr, ",")
+	if len(fieldArr) == 0 {
+		fields = `*`
+	}
+	order := fmt.Sprintf(`ORDER BY %s DESC`, m.Cols().CreateTime)
+	if orderRule != "" {
+		order = ` ORDER BY ` + orderRule
+	}
+	sql := fmt.Sprintf(`SELECT %s FROM %s WHERE 1=1 %s %s LIMIT ?,?`, fields, m.TableName(), condition, order)
+	_, err = o.Raw(sql, pars, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// FactorEdbSeriesMappingItem 因子指标系列-指标关联信息
+type FactorEdbSeriesMappingItem struct {
+	SeriesId  int    `description:"因子指标系列ID"`
+	EdbInfoId int    `description:"指标ID"`
+	EdbCode   string `description:"指标编码"`
+	EdbName   string `description:"指标名称"`
+	EdbNameEn string `description:"指标名称-英文"`
+}
+
+func (m *FactorEdbSeriesMapping) Format2Item() (item *FactorEdbSeriesMappingItem) {
+	item = new(FactorEdbSeriesMappingItem)
+	item.SeriesId = m.FactorEdbSeriesId
+	item.EdbInfoId = m.EdbInfoId
+	item.EdbCode = m.EdbCode
+	return
+}
+
+// FactorEdbSeriesMappingUpdateCalculate 更新计算数据
+//type FactorEdbSeriesMappingUpdateCalculate struct {
+//	SeriesId      int    `description:"因子指标系列ID"`
+//	EdbInfoId     int    `description:"指标ID"`
+//	CalculateData string `description:"计算数据-JSON"`
+//}
+//
+//func (m *FactorEdbSeriesMapping) UpdateCalculateData(updates []*FactorEdbSeriesMappingUpdateCalculate) (err error) {
+//	if len(updates) == 0 {
+//		return
+//	}
+//	o := orm.NewOrm()
+//	sql := fmt.Sprintf(`UPDATE %s SET %s = ? WHERE %s = ? AND %s = ?`, m.TableName(), m.Cols().CalculateData, m.Cols().FactorEdbSeriesId, m.Cols().EdbInfoId)
+//	p, e := o.Raw(sql).Prepare()
+//	if e != nil {
+//		err = fmt.Errorf("sql prepare err: %v", e)
+//		return
+//	}
+//	defer func() {
+//		_ = p.Close()
+//	}()
+//	for _, v := range updates {
+//		_, e = p.Exec(v.CalculateData, v.SeriesId, v.EdbInfoId)
+//		if e != nil {
+//			err = fmt.Errorf("update exec err: %v", e)
+//			return
+//		}
+//	}
+//	return
+//}
+
+// GetChartUsedFactorSeriesEdb 获取图表引用的系列指标
+func GetChartUsedFactorSeriesEdb(chartId int) (items []*FactorEdbSeriesMapping, err error) {
+	o := orm.NewOrmUsingDB("data")
+	chartOb := new(FactorEdbSeriesChartMapping)
+	edbOb := new(FactorEdbSeriesMapping)
+	sql := fmt.Sprintf(`SELECT b.* FROM %s AS a
+	JOIN %s AS b ON a.%s = b.%s AND a.%s = b.%s
+	WHERE a.%s = ? ORDER BY %s ASC`, chartOb.TableName(), edbOb.TableName(), chartOb.Cols().FactorEdbSeriesId, edbOb.Cols().FactorEdbSeriesId, chartOb.Cols().EdbInfoId, edbOb.Cols().EdbInfoId, chartOb.Cols().ChartInfoId, edbOb.Cols().FactorEdbSeriesId)
+	_, err = o.Raw(sql, chartId).QueryRows(&items)
+	return
+}

+ 13 - 1
models/db.go

@@ -43,7 +43,7 @@ func init() {
 	initFutureGood()
 
 	// 初始化部分数据表变量(直接init会有顺序问题=_=!)
-	data_manage.InitEdbSourceVar()
+	afterInitTable()
 }
 
 // initFutureGood 注册期货数据 数据表
@@ -55,3 +55,15 @@ func initFutureGood() {
 		new(future_good.ChartInfoFutureGoodProfit), //期货利润图的扩展表
 	)
 }
+
+// afterInitTable
+// @Description: 初始化表结构的的后置操作
+// @author: Roc
+// @datetime 2024-07-01 13:31:09
+func afterInitTable() {
+	// 初始化指标来源配置
+	data_manage.InitEdbSourceVar()
+
+	// 初始化是否启用mongo配置
+	InitUseMongoConf()
+}

+ 6 - 4
models/edb_data_base.go

@@ -8,11 +8,13 @@ import (
 func GetEdbDataTableName(source, subSource int) (tableName string) {
 	switch source {
 	case utils.DATA_SOURCE_THS:
-		tableName = "edb_data_ths"
-		if subSource == utils.DATA_SUB_SOURCE_EDB {
-			tableName = "edb_data_ths"
-		} else {
+		switch subSource {
+		case utils.DATA_SUB_SOURCE_DATE:
 			tableName = "edb_data_ths_ds"
+		case utils.DATA_SUB_SOURCE_HIGH_FREQUENCY:
+			tableName = "edb_data_ths_hf"
+		default:
+			tableName = "edb_data_ths"
 		}
 	case utils.DATA_SOURCE_WIND:
 		if subSource == utils.DATA_SUB_SOURCE_EDB {

+ 510 - 0
models/mgo/edb_data_ths_hf.go

@@ -0,0 +1,510 @@
+package mgo
+
+import (
+	"context"
+	"errors"
+	"eta/eta_chart_lib/utils"
+	"fmt"
+	"github.com/qiniu/qmgo"
+	"go.mongodb.org/mongo-driver/bson"
+	"go.mongodb.org/mongo-driver/bson/primitive"
+	"time"
+)
+
+// EdbDataThsHf
+// @Description: 同花顺高频集合(指标库)
+type EdbDataThsHf struct {
+	ID            primitive.ObjectID `json:"_id" bson:"_id,omitempty" `            // 文档id
+	EdbInfoId     int                `json:"edb_info_id" bson:"edb_info_id"`       // 指标ID
+	EdbCode       string             `json:"edb_code" bson:"edb_code"`             // 指标编码
+	DataTime      time.Time          `json:"data_time" bson:"data_time"`           // 数据日期
+	Value         float64            `json:"value" bson:"value"`                   // 数据值
+	CreateTime    time.Time          `json:"create_time" bson:"create_time"`       // 创建时间
+	ModifyTime    time.Time          `json:"modify_time" bson:"modify_time"`       // 修改时间
+	DataTimestamp int64              `json:"data_timestamp" bson:"data_timestamp"` // 数据日期时间戳
+}
+
+// CollectionName
+// @Description:  获取集合名称
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 13:41:36
+// @return string
+func (m *EdbDataThsHf) CollectionName() string {
+	return "edb_data_ths_hf"
+}
+
+// DataBaseName
+// @Description: 获取数据库名称
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 13:41:33
+// @return string
+func (m *EdbDataThsHf) DataBaseName() string {
+	return utils.MgoDataDbName
+}
+
+// GetCollection
+// @Description: 获取mongodb集合的句柄
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 13:41:33
+// @return string
+func (m *EdbDataThsHf) GetCollection() *qmgo.Collection {
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	return db.Collection(m.CollectionName())
+}
+
+// GetItem
+// @Description: 根据条件获取单条数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-05-09 10:00:49
+// @param whereParams interface{}
+// @return item *EdbDataThsHf
+// @return err error
+func (m *EdbDataThsHf) GetItem(whereParams interface{}) (item *EdbDataThsHf, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+
+	return m.GetItemByColl(coll, whereParams)
+}
+
+// GetItemByColl
+// @Description: 根据条件获取单条数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-05-09 13:22:06
+// @param coll *qmgo.Collection
+// @param whereParams interface{}
+// @return item *EdbDataThsHf
+// @return err error
+func (m *EdbDataThsHf) GetItemByColl(coll *qmgo.Collection, whereParams interface{}) (item *EdbDataThsHf, err error) {
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Find(ctx, whereParams).One(&item)
+	if err != nil {
+		return
+	}
+
+	item.DataTime = item.DataTime.In(time.Local)
+	item.CreateTime = item.CreateTime.In(time.Local)
+	item.ModifyTime = item.ModifyTime.In(time.Local)
+
+	return
+}
+
+// GetAllDataList
+// @Description: 根据条件获取所有数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 13:42:19
+// @param whereParams interface{}
+// @param sort []string
+// @return result []EdbDataThsHf
+// @return err error
+func (m *EdbDataThsHf) GetAllDataList(whereParams interface{}, sort []string) (result []*EdbDataThsHf, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Find(ctx, whereParams).Sort(sort...).All(&result)
+	if err != nil {
+		return
+	}
+
+	for _, v := range result {
+		v.DataTime = v.DataTime.In(time.Local)
+		v.CreateTime = v.CreateTime.In(time.Local)
+		v.ModifyTime = v.ModifyTime.In(time.Local)
+	}
+
+	return
+}
+
+// GetLimitDataList
+// @Description: 根据条件获取指定数量数据列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-05-06 17:08:32
+// @param whereParams interface{}
+// @param size int64
+// @param sort []string
+// @return result []*BaseFromBusinessData
+// @return err error
+func (m *EdbDataThsHf) GetLimitDataList(whereParams interface{}, size int64, sort []string) (result []*EdbDataThsHf, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Find(ctx, whereParams).Sort(sort...).Limit(size).All(&result)
+	if err != nil {
+		return
+	}
+
+	for _, v := range result {
+		v.DataTime = v.DataTime.In(time.Local)
+		v.CreateTime = v.CreateTime.In(time.Local)
+		v.ModifyTime = v.ModifyTime.In(time.Local)
+	}
+
+	return
+}
+
+// GetPageDataList
+// @Description: 根据条件获取分页数据列表
+// @author: Roc
+// @receiver m
+// @datetime 2024-05-07 10:21:07
+// @param whereParams interface{}
+// @param startSize int64
+// @param size int64
+// @param sort []string
+// @return result []*EdbDataThsHf
+// @return err error
+func (m *EdbDataThsHf) GetPageDataList(whereParams interface{}, startSize, size int64, sort []string) (result []*EdbDataThsHf, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Find(ctx, whereParams).Sort(sort...).Skip(startSize).Limit(size).All(&result)
+	if err != nil {
+		return
+	}
+
+	for _, v := range result {
+		v.DataTime = v.DataTime.In(time.Local)
+		v.CreateTime = v.CreateTime.In(time.Local)
+		v.ModifyTime = v.ModifyTime.In(time.Local)
+	}
+
+	return
+}
+
+// GetCountDataList
+// @Description:  根据条件获取数据列表总数
+// @author: Roc
+// @receiver m
+// @datetime 2024-05-07 10:29:00
+// @param whereParams interface{}
+// @return count int64
+// @return err error
+func (m *EdbDataThsHf) GetCountDataList(whereParams interface{}) (count int64, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	count, err = coll.Find(ctx, whereParams).Count()
+
+	return
+}
+
+// InsertDataByColl
+// @Description: 写入单条数据(外部传入集合)
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 14:22:18
+// @param addData interface{}
+// @return err error
+func (m *EdbDataThsHf) InsertDataByColl(coll *qmgo.Collection, addData interface{}) (err error) {
+	ctx := context.TODO()
+	_, err = coll.InsertOne(ctx, addData)
+	if err != nil {
+		fmt.Println("InsertDataByColl:Err:" + err.Error())
+		return
+	}
+
+	return
+}
+
+// BatchInsertData
+// @Description: 批量写入数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 14:22:18
+// @param bulk int 每次请求保存的数据量
+// @param dataList []interface{}
+// @return err error
+func (m *EdbDataThsHf) BatchInsertData(bulk int, dataList []interface{}) (err error) {
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+
+	return m.BatchInsertDataByColl(coll, bulk, dataList)
+}
+
+// BatchInsertDataByColl
+// @Description: 批量写入数据(外部传入集合)
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 14:22:18
+// @param coll *qmgo.Collection
+// @param bulk int 每次请求保存的数据量
+// @param dataList []interface{}
+// @return err error
+func (m *EdbDataThsHf) BatchInsertDataByColl(coll *qmgo.Collection, bulk int, dataList []interface{}) (err error) {
+	ctx := context.TODO()
+	dataNum := len(dataList)
+	if dataNum <= 0 {
+		return
+	}
+
+	// 不设置每次保存切片数量大小,或者实际数据量小于设置的切片数量大小,那么就直接保存吧
+	if bulk <= 0 || dataNum <= bulk {
+		_, err = coll.InsertMany(ctx, dataList)
+		if err != nil {
+			fmt.Println("BatchInsertData:Err:" + err.Error())
+			return
+		}
+		return
+	}
+
+	// 分批保存
+	i := 0
+	tmpAddDataList := make([]interface{}, 0)
+	for _, v := range dataList {
+		tmpAddDataList = append(tmpAddDataList, v)
+		i++
+		if i >= bulk {
+			_, err = coll.InsertMany(ctx, tmpAddDataList)
+			if err != nil {
+				fmt.Println("BatchInsertData:Err:" + err.Error())
+				return
+			}
+			i = 0
+			tmpAddDataList = make([]interface{}, 0)
+		}
+	}
+
+	if len(tmpAddDataList) > 0 {
+		_, err = coll.InsertMany(ctx, tmpAddDataList)
+		if err != nil {
+			fmt.Println("BatchInsertData:Err:" + err.Error())
+			return
+		}
+	}
+
+	return
+}
+
+// UpdateData
+// @Description: 单条数据修改
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 15:01:51
+// @param whereParams interface{}
+// @param updateParams interface{}
+// @return err error
+func (m *EdbDataThsHf) UpdateData(whereParams, updateParams interface{}) (err error) {
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+
+	return m.UpdateDataByColl(coll, whereParams, updateParams)
+}
+
+// UpdateDataByColl
+// @Description: 单条数据修改(外部传入集合)
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-26 15:01:51
+// @param whereParams interface{}
+// @param updateParams interface{}
+// @return err error
+func (m *EdbDataThsHf) UpdateDataByColl(coll *qmgo.Collection, whereParams, updateParams interface{}) (err error) {
+	ctx := context.TODO()
+	err = coll.UpdateOne(ctx, whereParams, updateParams)
+	if err != nil {
+		fmt.Println("UpdateDataByColl:Err:" + err.Error())
+		return
+	}
+
+	return
+}
+
+// RemoveMany
+// @Description: 根据条件删除多条数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-30 13:17:02
+// @param whereParams interface{}
+// @return err error
+func (m *EdbDataThsHf) RemoveMany(whereParams interface{}) (err error) {
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+
+	return m.RemoveManyByColl(coll, whereParams)
+}
+
+// RemoveManyByColl
+// @Description: 根据条件删除多条数据(外部传入集合)
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-30 13:18:42
+// @param coll *qmgo.Collection
+// @param whereParams interface{}
+// @return err error
+func (m *EdbDataThsHf) RemoveManyByColl(coll *qmgo.Collection, whereParams interface{}) (err error) {
+	ctx := context.TODO()
+	_, err = coll.RemoveAll(ctx, whereParams)
+	if err != nil {
+		fmt.Println("RemoveManyByColl:Err:" + err.Error())
+		return
+	}
+
+	return
+}
+
+// HandleData
+// @Description: 事务处理数据
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-30 10:39:01
+// @param addDataList []AddEdbDataThsHf
+// @param updateDataList []EdbDataThsHf
+// @return result interface{}
+// @return err error
+func (m *EdbDataThsHf) HandleData(addDataList, updateDataList []EdbDataThsHf) (result interface{}, err error) {
+
+	ctx := context.TODO()
+
+	callback := func(sessCtx context.Context) (interface{}, error) {
+		// 重要:确保事务中的每一个操作,都使用传入的sessCtx参数
+
+		db := utils.MgoDataCli.Database(m.DataBaseName())
+		coll := db.Collection(m.CollectionName())
+
+		// 插入数据
+		if len(addDataList) > 0 {
+			_, err = coll.InsertMany(sessCtx, addDataList)
+			if err != nil {
+				return nil, err
+			}
+		}
+
+		// 修改
+
+		if len(updateDataList) > 0 {
+			for _, v := range updateDataList {
+				err = coll.UpdateOne(ctx, bson.M{"_id": v.ID}, bson.M{"$set": bson.M{"value": v.Value, "modify_time": v.ModifyTime}})
+				if err != nil {
+					fmt.Println("BatchInsertData:Err:" + err.Error())
+					return nil, err
+				}
+			}
+		}
+
+		return nil, nil
+	}
+	result, err = utils.MgoDataCli.DoTransaction(ctx, callback)
+
+	return
+}
+
+// EdbInfoMaxAndMinInfo 指标最新数据记录结构体
+//type EdbInfoMaxAndMinInfo struct {
+//	MinDate     time.Time `description:"最小日期" bson:"min_date"`
+//	MaxDate     time.Time `description:"最大日期" bson:"max_date"`
+//	MinValue    float64   `description:"最小值" bson:"min_value"`
+//	MaxValue    float64   `description:"最大值" bson:"max_value"`
+//	LatestValue float64   `description:"最新值" bson:"latest_value"`
+//	LatestDate  time.Time `description:"实际数据最新日期" bson:"latest_date"`
+//	EndValue    float64   `description:"最新值" bson:"end_value"`
+//}
+
+// GetEdbInfoMaxAndMinInfo
+// @Description: 获取当前指标的最大最小值
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-30 17:15:39
+// @param whereParams interface{}
+// @return result EdbInfoMaxAndMinInfo
+// @return err error
+func (m *EdbDataThsHf) GetEdbInfoMaxAndMinInfo(whereParams interface{}) (result EdbInfoMaxAndMinInfo, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+	err = coll.Aggregate(ctx, whereParams).One(&result)
+	if err != nil {
+		return
+	}
+	result.MinDate = result.MinDate.In(time.Local)
+	result.MaxDate = result.MaxDate.In(time.Local)
+	result.LatestDate = result.LatestDate.In(time.Local)
+
+	return
+}
+
+// LatestValue 指标最新数据记录结构体
+//type LatestValue struct {
+//	Value float64 `description:"值" bson:"value"`
+//}
+
+// GetLatestValue
+// @Description: 获取当前指标的最新数据记录
+// @author: Roc
+// @receiver m
+// @datetime 2024-04-30 17:16:15
+// @param whereParams interface{}
+// @param selectParam interface{}
+// @return latestValue LatestValue
+// @return err error
+func (m *EdbDataThsHf) GetLatestValue(whereParams, selectParam interface{}) (latestValue LatestValue, err error) {
+	if utils.MgoDataCli == nil {
+		err = errors.New("mongodb连接失败")
+		return
+	}
+	db := utils.MgoDataCli.Database(m.DataBaseName())
+	coll := db.Collection(m.CollectionName())
+	ctx := context.TODO()
+	if err != nil {
+		fmt.Println("MgoGetColl Err:", err.Error())
+		return
+	}
+
+	//var result interface{}
+	//err = coll.Find(ctx, whereParams).Select(selectParam).One(&result)
+	err = coll.Find(ctx, whereParams).Select(selectParam).One(&latestValue)
+	return
+}

+ 10 - 0
models/request/mixed_table.go

@@ -44,6 +44,16 @@ type MixedTableCellDataReq struct {
 	Extra           string `description:"额外参数"`
 	ShowStyle       string `description:"展示的样式配置"`
 	ShowFormatValue string `description:"样式处理后的值"`
+	MerData         *struct {
+		Type string `json:"type"`
+		Mer  struct {
+			SKey    string `json:"sKey"`
+			Rowspan int    `json:"rowspan"`
+			Colspan int    `json:"colspan"`
+			Row     int    `json:"row"`
+			Col     int    `json:"col"`
+		} `json:"mer"`
+	} `json:"merData" description:"合并单元格"`
 }
 
 // CellRelationConf

+ 18 - 0
routers/commentsRouter.go

@@ -43,6 +43,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_chart_lib/controllers:ChartController"] = append(beego.GlobalControllerRouter["eta/eta_chart_lib/controllers:ChartController"],
+        beego.ControllerComments{
+            Method: "Collect",
+            Router: `/dw/collect`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_chart_lib/controllers:ChartController"] = append(beego.GlobalControllerRouter["eta/eta_chart_lib/controllers:ChartController"],
+        beego.ControllerComments{
+            Method: "CollectCancel",
+            Router: `/dw/collectCancel`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_chart_lib/controllers:ChartController"] = append(beego.GlobalControllerRouter["eta/eta_chart_lib/controllers:ChartController"],
         beego.ControllerComments{
             Method: "FutureGoodChartInfoRefresh",

+ 7 - 5
services/data/base_edb_lib.go

@@ -75,11 +75,13 @@ func RefreshEdbData(edbInfoId, source, subSource int, edbCode, startDate string)
 	urlStr := ``
 	switch source {
 	case utils.DATA_SOURCE_THS:
-		urlStr = "ths/refresh"
-		if subSource == 0 {
-			urlStr = "ths/refresh"
-		} else {
+		switch subSource {
+		case utils.DATA_SUB_SOURCE_DATE:
 			urlStr = "ths/ds/refresh"
+		case utils.DATA_SUB_SOURCE_HIGH_FREQUENCY:
+			urlStr = "ths/hf/edb/refresh"
+		default:
+			urlStr = "ths/refresh"
 		}
 	case utils.DATA_SOURCE_WIND:
 		if subSource == 0 {
@@ -137,7 +139,7 @@ func RefreshEdbData(edbInfoId, source, subSource int, edbCode, startDate string)
 		urlStr = "gz/refresh"
 	case utils.DATA_SOURCE_ICPI:
 		urlStr = "icpi/refresh"
-		case utils.DATA_SOURCE_SCI99:
+	case utils.DATA_SOURCE_SCI99:
 		urlStr = "sci99/refresh"
 	default:
 		edbSource := data_manage.EdbSourceIdMap[source]

+ 352 - 3
services/data/chart_info.go

@@ -124,6 +124,10 @@ func GetChartEdbData(chartInfoId, chartType int, calendar, startDate, endDate st
 
 	// 特殊图形数据处理
 	switch chartType {
+	case 2: // 季节性图
+		if seasonExtraConfig != "" {
+			dataResp, err = SeasonChartData(edbList, seasonExtraConfig)
+		}
 	case 7: // 柱形图
 		barChartConf := extraConfig.(data_manage.BarChartInfoReq)
 		xEdbIdValue, yDataList, err = BarChartData(mappingList, edbDataListMap, barChartConf.DateList, barChartConf.Sort)
@@ -230,6 +234,9 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 		var diffSeconds int64
 		if chartType == 2 { //季节性图
 			startDateReal = startDate
+			if len(mappingList) > 1 {
+				item.IsAxis = v.IsAxis
+			}
 		} else {
 			if v.EdbInfoType == 0 && v.LeadUnit != "" && v.LeadValue > 0 { //领先指标
 				var startTimeRealTemp time.Time
@@ -329,7 +336,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			}
 		}
 
-		if chartType == 2 {
+		if chartType == 2 && item.IsAxis == 1 {
 			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
 			if tmpErr != nil {
 				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
@@ -361,6 +368,29 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 				}
 				item.DataList = quarterDataList
 			}
+		} else if chartType == 2 && item.IsAxis == 0 {
+			// 右轴数据处理,只要最新一年
+			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			if tmpErr != nil {
+				//item.DataList = dataList
+				item.IsNullData = true
+				edbList = append(edbList, item)
+				continue
+				err = errors.New(fmt.Sprint("获取最后实际数据的日期失败,Err:" + tmpErr.Error() + ";LatestDate:" + v.LatestDate))
+				return
+			}
+			newDataList := make([]*models.EdbDataList, 0)
+			for _, v := range dataList {
+				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
+				if e != nil {
+					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
+					return
+				}
+				if dataTime.Year() == latestDate.Year() {
+					newDataList = append(newDataList, v)
+				}
+			}
+			item.DataList = newDataList
 		} else if chartType == 7 || chartType == utils.CHART_TYPE_RADAR { //柱方图
 			//item.DataList = dataList
 		} else {
@@ -424,6 +454,7 @@ func GetSeasonEdbInfoDataListByXDate(dataList []*models.EdbDataList, latestDate
 	}
 	endYear := lastDateT.Year()
 	nowYear := time.Now().Year()
+	chartLegendMaxYear := 0
 	dataMap := make(map[string]models.QuarterXDateItem, 0)
 
 	quarterDataList := make([]*models.QuarterData, 0)
@@ -463,23 +494,30 @@ func GetSeasonEdbInfoDataListByXDate(dataList []*models.EdbDataList, latestDate
 			//如果最新的日期比真实年份要大,则数据全部按照最大的年份补齐
 			nowYear = endT.Year()
 		}
+
 		item := models.QuarterXDateItem{
 			StartDate: startT,
 			EndDate:   endT,
 			ShowName:  showName,
 		}
+		chartLegendMaxYear = endT.Year()
 		dataMap[name] = item
 		chartLegendMap[name] = idx
 		idx++
+		fmt.Println("年份" + showName + "日期" + startStr + " " + endStr)
 		if lastDateT.Before(endT) {
 			//如果最新的日期在起始日之前,则跳出循环
 			break
 		}
 	}
 	lenYear := len(dataMap)
+	if chartLegendMaxYear > endYear {
+		chartLegendMaxYear = endYear
+	}
 	for k, v := range dataMap {
 		if i, ok := chartLegendMap[k]; ok {
-			v.ChartLegend = strconv.Itoa(endYear - lenYear + i)
+			//v.ChartLegend = strconv.Itoa(endYear - lenYear + i)
+			v.ChartLegend = strconv.Itoa(chartLegendMaxYear - lenYear + i)
 		}
 		dataMap[k] = v
 	}
@@ -613,6 +651,7 @@ func GetSeasonEdbInfoDataListByXDateNong(result *models.EdbDataResult, latestDat
 	}
 	endYear := lastDateT.Year()
 	nowYear := time.Now().Year()
+	chartLegendMaxYear := 0
 	dataMap := make(map[string]models.QuarterXDateItem, 0)
 
 	quarterDataList := make([]*models.QuarterData, 0)
@@ -657,6 +696,7 @@ func GetSeasonEdbInfoDataListByXDateNong(result *models.EdbDataResult, latestDat
 			EndDate:   endT,
 			ShowName:  showName,
 		}
+		chartLegendMaxYear = endT.Year()
 		dataMap[showName] = item
 		fmt.Println("年份" + showName + "日期" + startStr + " " + endStr)
 		startTmpT = startT
@@ -669,9 +709,13 @@ func GetSeasonEdbInfoDataListByXDateNong(result *models.EdbDataResult, latestDat
 		}
 	}
 	lenYear := len(dataMap)
+	if chartLegendMaxYear > endYear {
+		chartLegendMaxYear = endYear
+	}
 	for k, v := range dataMap {
 		if i, ok := chartLegendMap[k]; ok {
-			v.ChartLegend = strconv.Itoa(endYear - lenYear + i)
+			//v.ChartLegend = strconv.Itoa(endYear - lenYear + i)
+			v.ChartLegend = strconv.Itoa(chartLegendMaxYear - lenYear + i)
 		}
 		dataMap[k] = v
 	}
@@ -683,6 +727,24 @@ func GetSeasonEdbInfoDataListByXDateNong(result *models.EdbDataResult, latestDat
 	}
 
 	//判断哪些点应该落在同一条时间线上
+	/*maxY := lastDateT.Year()
+	changeFlag := false
+	if lastDateT.Month() >= 11 {
+		maxY = maxY + 1
+	}
+	if maxY < nowYear {
+		changeFlag = true
+		maxY = nowYear
+	}*/
+	/*endTmp := fmt.Sprintf("%d-%s", maxY, xEndDate)
+	endTmpT, _ := time.Parse(utils.FormatDate, endTmp)
+	minY := maxY
+	if jumpYear == 1 {
+		minY = maxY - 1
+	}
+	startTmp := fmt.Sprintf("%d-%s", minY, xStartDate)
+	startTmpT, _ := time.Parse(utils.FormatDate, startTmp)*/
+
 	fmt.Println("横轴截取日" + startTmpT.Format(utils.FormatDate) + " " + endTmpT.Format(utils.FormatDate))
 	fmt.Printf("lastDateT.Year() 为%d \n", lastDateT.Year())
 	fmt.Printf("maxY 为%d \n", maxY)
@@ -1512,3 +1574,290 @@ func GetChartEdbDataV2(chartInfoId, chartType int, calendar, startDate, endDate
 
 	return
 }
+
+// SeasonChartData 季节性图的数据处理
+func SeasonChartData(dataList []*models.ChartEdbInfoMapping, seasonExtraConfig string) (dataResp models.SeasonChartResp, err error) {
+	var seasonConfig models.SeasonExtraItem
+	err = json.Unmarshal([]byte(seasonExtraConfig), &seasonConfig)
+	if err != nil {
+		err = errors.New("季节性图配置异常, Err:" + err.Error())
+		return
+	}
+
+	for _, mappingItem := range dataList {
+		if mappingItem.IsAxis == 0 {
+			continue
+		}
+		quarterDataList := mappingItem.DataList.(models.QuarterDataList)
+		// 上下限区间
+		if seasonConfig.MaxMinLimits.Year > 0 {
+			dataResp.MaxMinLimits.List = make([]*models.MaxMinLimitsData, 0)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeList := make([]string, 0)
+			maxValueMap := make(map[time.Time]float64)
+			minValueMap := make(map[time.Time]float64)
+
+			maxMinDataList := make([]*models.MaxMinLimitsData, 0)
+			// 日度 周度插值
+			for _, v := range quarterDataList {
+				if mappingItem.Frequency == "日度" || mappingItem.Frequency == "周度" {
+					handleDataMap := make(map[string]float64)
+					dataTimeList, _, err = HandleDataByLinearRegressionToList(v.DataList, handleDataMap)
+					if err != nil {
+						err = errors.New("插值处理数据异常, Err:" + err.Error())
+						return
+					}
+					for _, date := range dataTimeList {
+						dateTime, e := time.Parse(utils.FormatDate, date)
+						if e != nil {
+							err = errors.New("时间格式化异常, Err:" + e.Error())
+							return
+						}
+						newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+						// 处理上下限列表
+						if value, ok := maxValueMap[newDate]; ok {
+							if value < handleDataMap[date] {
+								maxValueMap[newDate] = handleDataMap[date]
+							}
+						} else {
+							maxValueMap[newDate] = handleDataMap[date]
+						}
+
+						if value, ok := minValueMap[newDate]; ok {
+							if value > handleDataMap[date] {
+								minValueMap[newDate] = handleDataMap[date]
+							}
+						} else {
+							minValueMap[newDate] = handleDataMap[date]
+						}
+
+						dataTimeMap[newDate] = newDate
+					}
+				} else {
+					// 旬度、月度、季度、半年度 不插值,需要先把日期列表和数据map取出来
+					for _, vv := range v.DataList {
+						dateTime, e := time.Parse(utils.FormatDate, vv.DataTime)
+						if e != nil {
+							err = errors.New("时间格式化异常, Err:" + e.Error())
+							return
+						}
+						newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+
+						if value, ok := maxValueMap[newDate]; ok {
+							if value < vv.Value {
+								maxValueMap[newDate] = vv.Value
+							}
+						} else {
+							maxValueMap[newDate] = vv.Value
+						}
+
+						if value, ok := minValueMap[newDate]; ok {
+							if value > vv.Value {
+								minValueMap[newDate] = vv.Value
+							}
+						} else {
+							minValueMap[newDate] = vv.Value
+						}
+						dataTimeMap[newDate] = newDate
+					}
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				maxMinItem := &models.MaxMinLimitsData{}
+				if maxValue, ok := maxValueMap[v]; ok {
+					maxMinItem.MaxValue = maxValue
+				} else {
+					maxMinItem.MaxValue = minValueMap[v]
+				}
+
+				if minValue, ok := minValueMap[v]; ok {
+					maxMinItem.MinValue = minValue
+				} else {
+					maxMinItem.MinValue = maxValueMap[v]
+				}
+				maxMinItem.DataTime = v.Format(utils.FormatDate)
+				maxMinItem.DataTimestamp = v.UnixNano() / 1e6
+
+				maxMinDataList = append(maxMinDataList, maxMinItem)
+
+			}
+
+			// 排序
+			sort.Slice(maxMinDataList, func(i, j int) bool {
+				return maxMinDataList[i].DataTime < maxMinDataList[j].DataTime
+			})
+
+			dataResp.MaxMinLimits.List = maxMinDataList
+			dataResp.MaxMinLimits.Color = seasonConfig.MaxMinLimits.Color
+			dataResp.MaxMinLimits.Legend = seasonConfig.MaxMinLimits.Legend
+			dataResp.MaxMinLimits.IsShow = seasonConfig.MaxMinLimits.IsShow
+			dataResp.MaxMinLimits.IsAdd = seasonConfig.MaxMinLimits.IsAdd
+			dataResp.MaxMinLimits.Year = seasonConfig.MaxMinLimits.Year
+		}
+
+		// 自定义同期均线
+		if seasonConfig.SamePeriodAverage.Year > 0 {
+			handleDataMap := make(map[string]float64)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeList := make([]string, 0)
+			valueMap := make(map[time.Time]float64)
+
+			averageDataList := make([]*models.SamePeriodAverageData, 0)
+			for i := len(quarterDataList) - 1; i > len(quarterDataList)-seasonConfig.SamePeriodAverage.Year && i > 0; i-- {
+				// 插值成日度
+				dataTimeList, _, err = HandleDataByLinearRegressionToList(quarterDataList[i].DataList, handleDataMap)
+				if err != nil {
+					err = errors.New("插值处理数据异常, Err:" + err.Error())
+					return
+				}
+				for _, date := range dataTimeList {
+					dateTime, e := time.Parse(utils.FormatDate, date)
+					if e != nil {
+						err = errors.New("时间格式化异常, Err:" + e.Error())
+						return
+					}
+					// 不包含今年
+					if dateTime.Year() == time.Now().Year() {
+						continue
+					}
+					newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+					// 处理均值
+					if value, ok := valueMap[newDate]; ok {
+						valueMap[newDate] = (handleDataMap[date] + value) / 2
+					} else {
+						valueMap[newDate] = handleDataMap[date]
+					}
+
+					dataTimeMap[newDate] = newDate
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				averageItem := &models.SamePeriodAverageData{}
+				if value, ok := valueMap[v]; ok {
+					averageItem.Value = value
+				}
+				averageItem.DataTime = v.Format(utils.FormatDate)
+				averageItem.DataTimestamp = v.UnixNano() / 1e6
+
+				averageDataList = append(averageDataList, averageItem)
+
+			}
+
+			// 排序
+			sort.Slice(averageDataList, func(i, j int) bool {
+				return averageDataList[i].DataTime < averageDataList[j].DataTime
+			})
+
+			dataResp.SamePeriodAverage.List = averageDataList
+			dataResp.SamePeriodAverage.Year = seasonConfig.SamePeriodAverage.Year
+			dataResp.SamePeriodAverage.Color = seasonConfig.SamePeriodAverage.Color
+			dataResp.SamePeriodAverage.Legend = seasonConfig.SamePeriodAverage.Legend
+			dataResp.SamePeriodAverage.IsShow = seasonConfig.SamePeriodAverage.IsShow
+			dataResp.SamePeriodAverage.LineType = seasonConfig.SamePeriodAverage.LineType
+			dataResp.SamePeriodAverage.LineWidth = seasonConfig.SamePeriodAverage.LineWidth
+			dataResp.SamePeriodAverage.IsAdd = seasonConfig.SamePeriodAverage.IsAdd
+
+		}
+
+		// 自定义同期标准差
+		if seasonConfig.SamePeriodStandardDeviation.Year > 1 && seasonConfig.SamePeriodStandardDeviation.Multiple > 0 {
+			// 先算均值,再算标准差
+			handleDataMap := make(map[string]float64)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeValueMap := make(map[time.Time][]float64)
+			dataTimeList := make([]string, 0)
+			valueMap := make(map[time.Time]float64)
+
+			samePeriodStandardDeviationList := make([]*models.MaxMinLimitsData, 0)
+			for i := len(quarterDataList) - 1; i > len(quarterDataList)-seasonConfig.SamePeriodAverage.Year-1 && i > 0; i-- {
+				// 插值成日度
+				dataTimeList, _, err = HandleDataByLinearRegressionToListV2(quarterDataList[i].DataList, handleDataMap)
+				if err != nil {
+					err = errors.New("插值处理数据异常, Err:" + err.Error())
+					return
+				}
+				for _, date := range dataTimeList {
+					dateTime, e := time.Parse(utils.FormatDate, date)
+					if e != nil {
+						err = errors.New("时间格式化异常, Err:" + e.Error())
+						return
+					}
+					// 不包含今年
+					if dateTime.Year() == time.Now().Year() {
+						continue
+					}
+					newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+					// 处理均值
+					if value, ok := valueMap[newDate]; ok {
+						valueMap[newDate] = (handleDataMap[date] + value) / 2
+					} else {
+						valueMap[newDate] = handleDataMap[date]
+					}
+
+					dataTimeMap[newDate] = newDate
+					valueList := dataTimeValueMap[newDate]
+					valueList = append(valueList, handleDataMap[date])
+					dataTimeValueMap[newDate] = valueList
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				valueList := dataTimeValueMap[v]
+				stdev := utils.CalculateStandardDeviation(valueList)
+				stdev, _ = decimal.NewFromFloat(stdev).Round(4).Float64()
+
+				maxMinItem := &models.MaxMinLimitsData{}
+
+				if value, ok := valueMap[v]; ok {
+					maxMinItem.MaxValue = value + stdev*seasonConfig.SamePeriodStandardDeviation.Multiple
+					maxMinItem.MinValue = value - stdev*seasonConfig.SamePeriodStandardDeviation.Multiple
+				}
+
+				maxMinItem.DataTime = v.Format(utils.FormatDate)
+				maxMinItem.DataTimestamp = v.UnixNano() / 1e6
+
+				samePeriodStandardDeviationList = append(samePeriodStandardDeviationList, maxMinItem)
+
+			}
+
+			// 排序
+			sort.Slice(samePeriodStandardDeviationList, func(i, j int) bool {
+				return samePeriodStandardDeviationList[i].DataTime < samePeriodStandardDeviationList[j].DataTime
+			})
+
+			dataResp.SamePeriodStandardDeviation.List = samePeriodStandardDeviationList
+			dataResp.SamePeriodStandardDeviation.Color = seasonConfig.SamePeriodStandardDeviation.Color
+			dataResp.SamePeriodStandardDeviation.Legend = seasonConfig.SamePeriodStandardDeviation.Legend
+			dataResp.SamePeriodStandardDeviation.IsShow = seasonConfig.SamePeriodStandardDeviation.IsShow
+			dataResp.SamePeriodStandardDeviation.Multiple = seasonConfig.SamePeriodStandardDeviation.Multiple
+			dataResp.SamePeriodStandardDeviation.Year = seasonConfig.SamePeriodStandardDeviation.Year
+			dataResp.SamePeriodStandardDeviation.IsAdd = seasonConfig.SamePeriodStandardDeviation.IsAdd
+		}
+
+		// 自定义右轴
+		if seasonConfig.RightAxis.IndicatorType != 0 {
+			if seasonConfig.RightAxis.IndicatorType == 1 {
+				startTime, _ := time.Parse(utils.FormatDate, mappingItem.StartDate)
+				for i := len(quarterDataList) - 1; i > len(quarterDataList)-2 && i > 0; i-- {
+					var rightMappingItem models.ChartEdbInfoMapping
+					rightMappingItem = *mappingItem
+					// 计算同比值
+					tbzDataList, minValue, maxValue, e := GetEdbDataTbzForSeason(mappingItem.Frequency, quarterDataList[i].DataList, startTime)
+					if e != nil {
+						err = errors.New("计算同比值失败, Err:" + e.Error())
+						return
+					}
+					rightMappingItem.DataList = tbzDataList
+					rightMappingItem.MaxData = maxValue
+					rightMappingItem.MinData = minValue
+					dataResp.RightAxis.EdbInfoList = append(dataResp.RightAxis.EdbInfoList, &rightMappingItem)
+				}
+			}
+			dataResp.RightAxis.SeasonRightAxis = seasonConfig.RightAxis
+		}
+	}
+
+	return
+}

+ 9 - 0
services/data/chart_theme.go

@@ -29,6 +29,9 @@ func GetChartThemeConfig(chartThemeId, source, chartType int) (chartTheme *chart
 		newConfig, e := ConvertOldChartOptions(chartTheme.Config)
 		if e == nil {
 			chartTheme.Config = newConfig
+		} else {
+			err = e
+			return
 		}
 		return
 	}
@@ -55,6 +58,9 @@ func GetChartThemeConfig(chartThemeId, source, chartType int) (chartTheme *chart
 		newConfig, e := ConvertOldChartOptions(chartTheme.Config)
 		if e == nil {
 			chartTheme.Config = newConfig
+		} else {
+			err = e
+			return
 		}
 		return
 	}
@@ -66,6 +72,9 @@ func GetChartThemeConfig(chartThemeId, source, chartType int) (chartTheme *chart
 	newConfig, e := ConvertOldChartOptions(chartTheme.Config)
 	if e == nil {
 		chartTheme.Config = newConfig
+	} else {
+		err = e
+		return
 	}
 	return
 }

+ 95 - 5
services/data/correlation/chart_info.go

@@ -1,8 +1,10 @@
 package correlation
 
 import (
+	"encoding/json"
 	"errors"
 	"eta/eta_chart_lib/models"
+	"eta/eta_chart_lib/models/data_manage"
 	"eta/eta_chart_lib/services/data"
 	"eta/eta_chart_lib/utils"
 	"fmt"
@@ -194,7 +196,7 @@ func GetChartEdbInfoFormat(chartInfoId int, edbInfoMappingA, edbInfoMappingB *mo
 }
 
 // GetChartDataByEdbInfo 相关性图表-根据指标信息获取x轴和y轴
-func GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB *models.ChartEdbInfoMapping, leadValue int, leadUnit, startDate, endDate string) (xEdbIdValue []int, yDataList []models.YData, err error) {
+func GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB *models.ChartEdbInfoMapping, leadValue int, leadUnit, startDate, endDate, extraConfig string) (xEdbIdValue []int, yDataList []models.YData, err error) {
 	xData := make([]int, 0)
 	yData := make([]float64, 0)
 	if leadValue == 0 {
@@ -370,13 +372,30 @@ func GetChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB *models.ChartEdbInfo
 		}
 	}
 
+	// 图例
+	var extra data_manage.CorrelationChartInfoExtraConfig
+	legend := new(data_manage.CorrelationChartLegend)
+	if extraConfig != "" {
+		if e := json.Unmarshal([]byte(extraConfig), &extra); e != nil {
+			err = fmt.Errorf("图例解析异常, err: %v", e)
+			return
+		}
+		if len(extra.LegendConfig) > 0 {
+			legend = extra.LegendConfig[0]
+		}
+	}
+
 	xEdbIdValue = xData
 	yDataList = make([]models.YData, 0)
 	yDate := "0000-00-00"
-	yDataList = append(yDataList, models.YData{
-		Date:  yDate,
-		Value: yData,
-	})
+	var y models.YData
+	y.Date = yDate
+	y.Value = yData
+	if legend != nil {
+		y.Name = legend.LegendName
+		y.Color = legend.Color
+	}
+	yDataList = append(yDataList, y)
 	return
 }
 
@@ -555,3 +574,74 @@ func GetRollingCorrelationChartDataByEdbInfo(edbInfoMappingA, edbInfoMappingB *m
 
 	return
 }
+
+// GetFactorChartDataByChartId 获取多因子相关性图表数据
+func GetFactorChartDataByChartId(chartInfoId int, extraConfig string) (xEdbIdValue []int, yDataList []models.YData, err error) {
+	if chartInfoId <= 0 {
+		return
+	}
+	// 指标对应的图例
+	extra := new(data_manage.CorrelationChartInfoExtraConfig)
+	if extraConfig != "" {
+		if e := json.Unmarshal([]byte(extraConfig), extra); e != nil {
+			err = fmt.Errorf("解析图表额外配置失败, err: %v", e)
+			return
+		}
+	}
+	legends := make(map[string]*data_manage.CorrelationChartLegend)
+	if extra != nil {
+		for _, v := range extra.LegendConfig {
+			s := fmt.Sprintf("%d-%d", v.SeriesId, v.EdbInfoId)
+			legends[s] = v
+		}
+	}
+
+	fmt.Println(chartInfoId)
+
+	// 获取图表引用到的系列指标
+	chartMappingOb := new(data_manage.FactorEdbSeriesChartMapping)
+	cond := fmt.Sprintf(" AND %s = ? AND %s = 1", chartMappingOb.Cols().ChartInfoId, chartMappingOb.Cols().EdbUsed)
+	pars := make([]interface{}, 0)
+	pars = append(pars, chartInfoId)
+	chartMappings, e := chartMappingOb.GetItemsByCondition(cond, pars, []string{}, "")
+	if e != nil {
+		err = fmt.Errorf("获取图表引用系列指标失败")
+		return
+	}
+
+	// 取出计算结果
+	yDataList = make([]models.YData, 0)
+	yDate := "0000-00-00"
+	for k, m := range chartMappings {
+		var values []data_manage.FactorEdbSeriesCorrelationMatrixValues
+		if m.CalculateData != "" {
+			e = json.Unmarshal([]byte(m.CalculateData), &values)
+			if e != nil {
+				err = fmt.Errorf("系列指标计算数据有误, err: %v", e)
+				return
+			}
+		}
+		var y []float64
+		for _, v := range values {
+			if k == 0 {
+				xEdbIdValue = append(xEdbIdValue, v.XData)
+			}
+			y = append(y, v.YData)
+		}
+		var yData models.YData
+		yData.Date = yDate
+		yData.Value = y
+		yData.SeriesEdb.SeriesId = m.FactorEdbSeriesId
+		yData.SeriesEdb.EdbInfoId = m.EdbInfoId
+
+		// 图例
+		s := fmt.Sprintf("%d-%d", m.FactorEdbSeriesId, m.EdbInfoId)
+		legend := legends[s]
+		if legend != nil {
+			yData.Name = legend.LegendName
+			yData.Color = legend.Color
+		}
+		yDataList = append(yDataList, yData)
+	}
+	return
+}

+ 113 - 0
services/data/edb_data.go

@@ -0,0 +1,113 @@
+package data
+
+import (
+	"eta/eta_chart_lib/models"
+	"eta/eta_chart_lib/utils"
+	"time"
+)
+
+// GetEdbDataTbzForSeason 获取指标的同比值数据
+func GetEdbDataTbzForSeason(frequency string, tmpDataList []*models.EdbDataList, startDateTime time.Time) (dataList []*models.EdbDataList, minValue, maxValue float64, err error) {
+	dataList = make([]*models.EdbDataList, 0)
+
+	// 数据处理
+	var dateArr []string
+	dataMap := make(map[string]*models.EdbDataList)
+	for _, v := range tmpDataList {
+		dateArr = append(dateArr, v.DataTime)
+		dataMap[v.DataTime] = v
+	}
+	for _, av := range dateArr {
+		currentItem, ok := dataMap[av]
+		// 如果找不到当前日期的数据,那么终止当前循环,进入下一循环
+		if !ok {
+			continue
+		}
+		tmpItem := *currentItem
+		var isOk bool //是否计算出来结果
+
+		//当前日期
+		currentDate, tmpErr := time.ParseInLocation(utils.FormatDate, av, time.Local)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		// 如果存在开始日期,同时,当前日期早于开始日期,那么终止当前循环,进入下一循环
+		if !startDateTime.IsZero() && currentDate.Before(startDateTime) {
+			continue
+		}
+		//上一年的日期
+		preDate := currentDate.AddDate(-1, 0, 0)
+		preDateStr := preDate.Format(utils.FormatDate)
+		if findItem, ok := dataMap[preDateStr]; ok { //上一年同期找到
+			tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+			isOk = true
+		} else {
+			if frequency == "月度" { //向上和向下,各找一个月
+				for i := 0; i <= 35; i++ {
+					nextDateDay := preDate.AddDate(0, 0, i)
+					nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+					if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+						tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+						isOk = true
+						break
+					} else {
+						preDateDay := preDate.AddDate(0, 0, -i)
+						preDateDayStr := preDateDay.Format(utils.FormatDate)
+						if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
+							tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+							isOk = true
+							break
+						}
+					}
+				}
+			} else if frequency == "季度" || frequency == "年度" {
+				if findItem, ok := dataMap[preDateStr]; ok { //上一年同期->下一个月找到
+					tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+					isOk = true
+					break
+				}
+			} else {
+				nextDateDay := preDate.AddDate(0, 0, 1)
+				nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+
+				preDateDay := preDate.AddDate(0, 0, -1)
+				preDateDayStr := preDateDay.Format(utils.FormatDate)
+
+				for i := 0; i < 35; i++ {
+					if i >= 1 {
+						nextDateDay = nextDateDay.AddDate(0, 0, i)
+						nextDateDayStr = nextDateDay.Format(utils.FormatDate)
+					}
+					if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+						tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+						isOk = true
+						break
+					} else {
+						if i >= 1 {
+							preDateDay = preDate.AddDate(0, 0, -i)
+							preDateDayStr = nextDateDay.Format(utils.FormatDate)
+						}
+						if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
+							tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+							isOk = true
+							break
+						}
+					}
+				}
+			}
+		}
+
+		if isOk {
+			if tmpItem.Value > maxValue {
+				maxValue = tmpItem.Value
+			}
+			if tmpItem.Value < minValue {
+				minValue = tmpItem.Value
+			}
+			dataList = append(dataList, &tmpItem)
+		}
+	}
+
+	return
+}

+ 153 - 3
services/data/edb_info.go

@@ -7,6 +7,8 @@ import (
 	"eta/eta_chart_lib/services/alarm_msg"
 	"eta/eta_chart_lib/utils"
 	"fmt"
+	"github.com/shopspring/decimal"
+	"math"
 	"sort"
 	"time"
 )
@@ -959,6 +961,9 @@ func EdbInfoRefreshAllFromBase(edbInfoIdList []int, refreshAll bool) (err error,
 		// 普通基础指标
 		for _, edbInfo := range tmpBaseEdbInfoArr {
 			if _, ok := newBaseMap[edbInfo.EdbInfoId]; !ok {
+				if edbInfo.NoUpdate == 1 {
+					continue
+				}
 				newBaseMap[edbInfo.EdbInfoId] = edbInfo
 				newBaseEdbInfoArr = append(newBaseEdbInfoArr, edbInfo)
 			}
@@ -975,6 +980,9 @@ func EdbInfoRefreshAllFromBase(edbInfoIdList []int, refreshAll bool) (err error,
 		// 普通计算指标
 		for _, edbInfo := range tmpCalculateMap {
 			if _, ok := newCalculateMap[edbInfo.EdbInfoId]; !ok {
+				if edbInfo.NoUpdate == 1 {
+					continue
+				}
 				newCalculateMap[edbInfo.EdbInfoId] = edbInfo
 				calculateArr = append(calculateArr, edbInfo.EdbInfoId)
 			}
@@ -995,7 +1003,10 @@ func EdbInfoRefreshAllFromBase(edbInfoIdList []int, refreshAll bool) (err error,
 	sort.Ints(predictCalculateArr)
 
 	// 需要刷新的指标数量
-	//totalEdbInfo := len(newBaseEdbInfoArr) + len(calculateArr) + len(predictCalculateArr)
+	totalEdbInfo := len(newBaseEdbInfoArr) + len(calculateArr) + len(predictCalculateArr) + len(newBasePredictEdbInfoArr)
+	if totalEdbInfo == 0 {
+		return
+	}
 	//if totalEdbInfo <= 20{
 	//	err,errMsg = edbInfoRefreshAll(refreshAll, newBaseEdbInfoArr, newBasePredictEdbInfoArr, newCalculateMap, newPredictCalculateMap, calculateArr, predictCalculateArr)
 	//} else {
@@ -1205,9 +1216,8 @@ func traceEdbInfoByEdbInfoId(edbInfoId int, traceEdbInfo data_manage.TraceEdbInf
 	return
 }
 
-
 // GetEdbSourceByEdbInfoIdListForExcel 获取关联指标的来源
-func GetEdbSourceByEdbInfoIdListForExcel(edbInfoIdList []int) (sourceNameList, sourceNameEnList []string,err error) {
+func GetEdbSourceByEdbInfoIdListForExcel(edbInfoIdList []int) (sourceNameList, sourceNameEnList []string, err error) {
 	sourceNameList = make([]string, 0)
 	sourceNameEnList = make([]string, 0)
 	sourceMap := make(map[int]string)
@@ -1255,5 +1265,145 @@ func GetEdbSourceByEdbInfoIdListForExcel(edbInfoIdList []int) (sourceNameList, s
 		sourceNameList = append(sourceNameList, conf[models.BusinessConfCompanyName])
 		sourceNameEnList = append(sourceNameEnList, conf[models.BusinessConfCompanyName])
 	}
+	return
+}
+
+
+// HandleDataByLinearRegressionToList 插值法补充数据(线性方程式)
+func HandleDataByLinearRegressionToList (edbInfoDataList []*models.EdbDataList, handleDataMap map[string]float64) (dataTimeList []string,valueList []float64, err error) {
+	if len(edbInfoDataList) < 2 {
+		return
+	}
+
+	var startEdbInfoData *models.EdbDataList
+	for _, v := range edbInfoDataList {
+		handleDataMap[v.DataTime] = v.Value
+		dataTimeList = append(dataTimeList, v.DataTime)
+		// 第一个数据就给过滤了,给后面的试用
+		if startEdbInfoData == nil {
+			startEdbInfoData = v
+			//startEdbInfoData.DataTime = startEdbInfoData.DataTime[:5]+ "01-01"
+			continue
+		}
+
+		// 获取两条数据之间相差的天数
+		startDataTime, _ := time.ParseInLocation(utils.FormatDate, startEdbInfoData.DataTime, time.Local)
+		currDataTime, _ := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+		betweenHour := int(currDataTime.Sub(startDataTime).Hours())
+		betweenDay := betweenHour / 24
+
+		// 如果相差一天,那么过滤
+		if betweenDay <= 1 {
+			startEdbInfoData = v
+			continue
+		}
+
+		// 生成线性方程式
+		var a, b float64
+		{
+			coordinateData := make([]utils.Coordinate, 0)
+			tmpCoordinate1 := utils.Coordinate{
+				X: 1,
+				Y: startEdbInfoData.Value,
+			}
+			coordinateData = append(coordinateData, tmpCoordinate1)
+			tmpCoordinate2 := utils.Coordinate{
+				X: float64(betweenDay) + 1,
+				Y: v.Value,
+			}
+			coordinateData = append(coordinateData, tmpCoordinate2)
+
+			a, b = utils.GetLinearResult(coordinateData)
+			if math.IsNaN(a) || math.IsNaN(b) {
+				err = errors.New("线性方程公式生成失败")
+				return
+			}
+		}
+
+		// 生成对应的值
+		{
+			for i := 1; i < betweenDay; i++ {
+				tmpDataTime := startDataTime.AddDate(0, 0, i)
+				aDecimal := decimal.NewFromFloat(a)
+				xDecimal := decimal.NewFromInt(int64(i) + 1)
+				bDecimal := decimal.NewFromFloat(b)
+
+				val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64()
+				handleDataMap[tmpDataTime.Format(utils.FormatDate)] = val
+				dataTimeList = append(dataTimeList, tmpDataTime.Format(utils.FormatDate))
+				valueList = append(valueList, val)
+			}
+		}
+
+		startEdbInfoData = v
+	}
+
+	return
+}
+
+// HandleDataByLinearRegressionToList 保证生成365个数据点的线性插值法
+func HandleDataByLinearRegressionToListV2(edbInfoDataList []*models.EdbDataList, handleDataMap map[string]float64) (dataTimeList []string, valueList []float64, err error) {
+	if len(edbInfoDataList) < 2 {
+		return
+	}
+
+	// 确保至少有两天数据来生成线性方程
+	if len(edbInfoDataList) < 2 {
+		err = errors.New("至少需要两天的数据来执行线性插值")
+		return
+	}
+
+	// 对数据按日期排序,确保顺序正确
+	sort.Slice(edbInfoDataList, func(i, j int) bool {
+		t1, _ := time.ParseInLocation(utils.FormatDate, edbInfoDataList[i].DataTime, time.Local)
+		t2, _ := time.ParseInLocation(utils.FormatDate, edbInfoDataList[j].DataTime, time.Local)
+		return t1.Before(t2)
+	})
+
+	startEdbInfoData := edbInfoDataList[0]
+	endEdbInfoData := edbInfoDataList[len(edbInfoDataList)-1]
+
+	// 计算起始和结束日期间实际的天数
+	startDate, _ := time.ParseInLocation(utils.FormatDate, startEdbInfoData.DataTime, time.Local)
+	endDate, _ := time.ParseInLocation(utils.FormatDate, endEdbInfoData.DataTime, time.Local)
+	actualDays := endDate.Sub(startDate).Hours() / 24
+
+	// 生成365个数据点,首先处理已有数据
+	for _, v := range edbInfoDataList {
+		handleDataMap[v.DataTime] = v.Value
+		dataTimeList = append(dataTimeList, v.DataTime)
+		valueList = append(valueList, v.Value)
+	}
+
+	// 如果已有数据跨越天数不足365天,则对缺失的日期进行线性插值
+	if actualDays < 365 {
+		// 使用已有数据点生成线性方程(这里简化处理,实际可能需更细致处理边界情况)
+		var a, b float64
+		coordinateData := []utils.Coordinate{
+			{X: 1, Y: startEdbInfoData.Value},
+			{X: float64(len(edbInfoDataList)), Y: endEdbInfoData.Value},
+		}
+		a, b = utils.GetLinearResult(coordinateData)
+		if math.IsNaN(a) || math.IsNaN(b) {
+			err = errors.New("线性方程公式生成失败")
+			return
+		}
+
+		// 对剩余日期进行插值
+		for i := 1; i < 365; i++ {
+			day := startDate.AddDate(0, 0, i)
+			if _, exists := handleDataMap[day.Format(utils.FormatDate)]; !exists {
+				aDecimal := decimal.NewFromFloat(a)
+				xDecimal := decimal.NewFromInt(int64(i) + 1)
+				bDecimal := decimal.NewFromFloat(b)
+
+				val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64()
+				handleDataMap[day.Format(utils.FormatDate)] = val
+				dataTimeList = append(dataTimeList, day.Format(utils.FormatDate))
+				valueList = append(valueList, val)
+			}
+		}
+	}
+
 	return
 }

+ 16 - 12
services/data/excel/balance_table.go

@@ -178,8 +178,8 @@ func GetBalanceExcelEdbData(excelEdbMappingItem *excel.ExcelChartEdb, newMixedTa
 			}
 
 			// 长度固定,避免一直申请内存空间
-			dateList = make([]string, endColumn-startColumn+1)
-
+			//dateList = make([]string, endColumn-startColumn+1)
+			dateList = make([]string, 0)
 			i := 0
 			for currColumn := startColumn; currColumn <= endColumn; currColumn++ {
 				currCell, ok := newMixedTableCellDataListMap[startNum][currColumn]
@@ -188,7 +188,8 @@ func GetBalanceExcelEdbData(excelEdbMappingItem *excel.ExcelChartEdb, newMixedTa
 					err = errors.New(errMsg)
 					return
 				}
-				dateList[i] = currCell.ShowValue
+				dateList = append(dateList, currCell.ShowValue)
+				//dateList[i] = currCell.ShowValue
 				i++
 			}
 
@@ -212,7 +213,8 @@ func GetBalanceExcelEdbData(excelEdbMappingItem *excel.ExcelChartEdb, newMixedTa
 				endNum = maxRow - 1
 			}
 			// 长度固定,避免一直申请内存空间
-			dateList = make([]string, endNum-startNum+1)
+			//dateList = make([]string, endNum-startNum+1)
+			dateList = make([]string, 0)
 			i := 0
 			for currRow := startNum; currRow <= endNum; currRow++ {
 				currCell, ok := newMixedTableCellDataListMap[currRow][startColumn]
@@ -221,8 +223,8 @@ func GetBalanceExcelEdbData(excelEdbMappingItem *excel.ExcelChartEdb, newMixedTa
 					err = errors.New(errMsg)
 					return
 				}
-				//dateList = append(dateList, currCell.Value)
-				dateList[i] = currCell.ShowValue
+				dateList = append(dateList, currCell.ShowValue)
+				//dateList[i] = currCell.ShowValue
 				i++
 			}
 		}
@@ -271,7 +273,8 @@ func GetBalanceExcelEdbData(excelEdbMappingItem *excel.ExcelChartEdb, newMixedTa
 				endColumn = maxCol - 1
 			}
 			// 长度固定,避免一直申请内存空间
-			dataList = make([]string, endColumn-startColumn+1)
+			//dataList = make([]string, endColumn-startColumn+1)
+			dataList = make([]string, 0)
 			i := 0
 			for currColumn := startColumn; currColumn <= endColumn; currColumn++ {
 				currCell, ok := newMixedTableCellDataListMap[startNum][currColumn]
@@ -280,8 +283,8 @@ func GetBalanceExcelEdbData(excelEdbMappingItem *excel.ExcelChartEdb, newMixedTa
 					err = errors.New(errMsg)
 					return
 				}
-				//dataList = append(dataList, currCell.Value)
-				dataList[i] = currCell.ShowValue
+				dataList = append(dataList, currCell.ShowValue)
+				//dataList[i] = currCell.ShowValue
 				i++
 			}
 
@@ -306,7 +309,8 @@ func GetBalanceExcelEdbData(excelEdbMappingItem *excel.ExcelChartEdb, newMixedTa
 			}
 
 			// 长度固定,避免一直申请内存空间
-			dataList = make([]string, endNum-startNum+1)
+			//dataList = make([]string, endNum-startNum+1)
+			dataList = make([]string, 0)
 			i := 0
 			for currRow := startNum; currRow <= endNum; currRow++ {
 				currCell, ok := newMixedTableCellDataListMap[currRow][startColumn]
@@ -315,8 +319,8 @@ func GetBalanceExcelEdbData(excelEdbMappingItem *excel.ExcelChartEdb, newMixedTa
 					err = errors.New(errMsg)
 					return
 				}
-				//dataList = append(dataList, currCell.Value)
-				dataList[i] = currCell.ShowValue
+				dataList = append(dataList, currCell.ShowValue)
+				//dataList[i] = currCell.ShowValue
 				i++
 			}
 		}

+ 2 - 1
services/data/excel/mixed_table.go

@@ -1259,7 +1259,8 @@ func changePointDecimalPlaces(str string, changeNum int, numberType string, isPe
 		val, _ = decimal.NewFromFloat(val).Round(int32(decimalPlaces)).Float64()
 		newStr = strconv.FormatFloat(val, 'f', decimalPlaces, 64)
 	} else {
-		newStr = fmt.Sprintf("%v", val)
+		// 此处用%.f避免科学计数法, 从而导致后面出现很多位0
+		newStr = fmt.Sprintf("%.f", val)
 	}
 	// 计算小数位数
 	decimalPlaces = 0

+ 29 - 0
services/dw_mini/base_mini_lib.go

@@ -0,0 +1,29 @@
+package dwmini
+
+import (
+	"eta/eta_chart_lib/utils"
+	"fmt"
+	"io"
+	"net/http"
+	"strings"
+)
+
+func HttpPost(url, postData, token string) ([]byte, error) {
+	body := io.NopCloser(strings.NewReader(postData))
+	client := &http.Client{}
+	req, err := http.NewRequest("POST", url, body)
+	if err != nil {
+		return nil, err
+	}
+	req.Header.Add("Authorization", token)
+	req.Header.Set("Content-Type", "application/json")
+	resp, err := client.Do(req)
+	defer resp.Body.Close()
+	b, err := io.ReadAll(resp.Body)
+	fmt.Println("HttpPost:" + string(b))
+	str := string(b)
+	str = strings.Trim(str, `"`)
+	b = utils.DesBase64Decrypt([]byte(str), utils.ETA_MINI_DES_KEY)
+	return b, err
+
+}

+ 89 - 0
services/dw_mini/dw_mini.go

@@ -0,0 +1,89 @@
+package dwmini
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_chart_lib/models"
+	"eta/eta_chart_lib/utils"
+)
+
+func GetMyChartIsCollect(token string, uniqueCode string) (isCollect bool, err error) {
+	url := utils.ETA_MINI_URL + "mychart/isCollect"
+	type MyChartIsCollectReq struct {
+		UniqueCode string
+	}
+	req := MyChartIsCollectReq{UniqueCode: uniqueCode}
+	reqBody, _ := json.Marshal(req)
+	result, err := HttpPost(url, string(reqBody), token)
+	if err != nil {
+		return
+	}
+	type MyChartIsCollect struct {
+		IsCollect bool
+	}
+	type BaseResponse struct {
+		Ret     int
+		Msg     string
+		ErrMsg  string
+		ErrCode string
+		Data    MyChartIsCollect
+	}
+	var resp BaseResponse
+	if err = json.Unmarshal(result, &resp); err != nil {
+		return
+	}
+	if resp.Ret != 200 {
+		err = errors.New(resp.ErrMsg)
+		return
+	}
+	isCollect = resp.Data.IsCollect
+	return
+}
+
+func MyChartCollect(token, uniqueCode, chartName, chartImage string, chartInfoId int) (resp *models.BaseResponse, err error) {
+	url := utils.ETA_MINI_URL + "mychart/collect"
+	type MyChartCollectReq struct {
+		UniqueCode  string
+		ChartName   string
+		ChartImage  string
+		ChartInfoId int
+	}
+	req := MyChartCollectReq{
+		UniqueCode:  uniqueCode,
+		ChartName:   chartName,
+		ChartImage:  chartImage,
+		ChartInfoId: chartInfoId,
+	}
+	reqBody, _ := json.Marshal(req)
+	result, err := HttpPost(url, string(reqBody), token)
+	if err != nil {
+		return
+	}
+	if err = json.Unmarshal(result, &resp); err != nil {
+		return
+	}
+	if resp.Ret != 200 {
+		return
+	}
+	return
+}
+
+func MyChartCollectCancel(token string, uniqueCode string) (resp *models.BaseResponse, err error) {
+	url := utils.ETA_MINI_URL + "mychart/collectCancel"
+	type MyChartCollectCancelReq struct {
+		UniqueCode string
+	}
+	req := MyChartCollectCancelReq{UniqueCode: uniqueCode}
+	reqBody, _ := json.Marshal(req)
+	result, err := HttpPost(url, string(reqBody), token)
+	if err != nil {
+		return
+	}
+	if err = json.Unmarshal(result, &resp); err != nil {
+		return
+	}
+	if resp.Ret != 200 {
+		return
+	}
+	return
+}

+ 11 - 1
services/excel/lucky_sheet.go

@@ -1186,7 +1186,7 @@ func GetTableDataByCustomData(excelType int, data request.TableDataReq, lang str
 }
 
 // GetTableDataByMixedTableData 通过混合表格数据获取表格数据
-func GetTableDataByMixedTableData(config [][]request.MixedTableCellDataReq) (selfTableData TableData, err error) {
+func GetTableDataByMixedTableData(config [][]request.MixedTableCellDataReq, hideMerged bool) (selfTableData TableData, err error) {
 	tableDataList := make([][]LuckySheetDataValue, 0)
 	mergeList := make([]TableDataMerge, 0)
 
@@ -1200,6 +1200,16 @@ func GetTableDataByMixedTableData(config [][]request.MixedTableCellDataReq) (sel
 					Monitor:   cell.ShowValue,
 					MergeCell: LuckySheetDataConfigMerge{},
 				}
+				// 前端需要隐藏被合并的单元格, 混合表格/平衡表通过这个字段判断, 不通过HandleTableCell方法隐藏
+				if cell.MerData != nil {
+					if hideMerged && cell.MerData.Type == "merged" {
+						continue
+					}
+					tmp.MergeCell.Rs = cell.MerData.Mer.Rowspan
+					tmp.MergeCell.Cs = cell.MerData.Mer.Colspan
+					tmp.MergeCell.Row = cell.MerData.Mer.Row
+					tmp.MergeCell.Column = cell.MerData.Mer.Col
+				}
 				if cell.ShowStyle != "" {
 					tmp.Monitor = cell.ShowFormatValue
 				}

+ 50 - 1
utils/common.go

@@ -2,13 +2,14 @@ package utils
 
 import (
 	"bufio"
+	"crypto/cipher"
+	"crypto/des"
 	"crypto/md5"
 	"crypto/sha1"
 	"encoding/base64"
 	"encoding/hex"
 	"encoding/json"
 	"fmt"
-	"github.com/shopspring/decimal"
 	"image"
 	"image/png"
 	"io"
@@ -23,6 +24,8 @@ import (
 	"strconv"
 	"strings"
 	"time"
+
+	"github.com/shopspring/decimal"
 )
 
 // 随机数种子
@@ -1164,3 +1167,49 @@ func DateConvMysqlConvMongo(dateCon string) string {
 	}
 	return cond
 }
+
+func DesBase64Decrypt(crypted []byte, desKey string) []byte {
+	result, _ := base64.StdEncoding.DecodeString(string(crypted))
+	remain := len(result) % 8
+	if remain > 0 {
+		mod := 8 - remain
+		for i := 0; i < mod; i++ {
+			result = append(result, 0)
+		}
+	}
+	origData, err := TripleDesDecrypt(result, []byte(desKey))
+	if err != nil {
+		panic(any(err))
+	}
+	return origData
+}
+
+// // 3DES解密
+func TripleDesDecrypt(crypted, key []byte) ([]byte, error) {
+	block, err := des.NewTripleDESCipher(key)
+	if err != nil {
+		return nil, err
+	}
+	blockMode := cipher.NewCBCDecrypter(block, key[:8])
+	origData := make([]byte, len(crypted))
+	// origData := crypted
+	blockMode.CryptBlocks(origData, crypted)
+	origData = PKCS5UnPadding(origData)
+	// origData = ZeroUnPadding(origData)
+	return origData, nil
+}
+
+func PKCS5UnPadding(origData []byte) []byte {
+	length := len(origData)
+	// 去掉最后一个字节 unpadding 次
+	unpadding := int(origData[length-1])
+	return origData[:(length - unpadding)]
+}
+
+func TimeTransferString(format string, t time.Time) string {
+	str := t.Format(format)
+	if t.IsZero() {
+		return ""
+	}
+	return str
+}

+ 12 - 3
utils/config.go

@@ -2,13 +2,14 @@ package utils
 
 import (
 	"fmt"
+	"math"
+	"strconv"
+	"strings"
+
 	beeLogger "github.com/beego/bee/v2/logger"
 	"github.com/beego/beego/v2/server/web"
 	"github.com/qiniu/qmgo"
 	"github.com/shopspring/decimal"
-	"math"
-	"strconv"
-	"strings"
 )
 
 var (
@@ -47,6 +48,8 @@ var (
 	EDB_LIB_URL         string
 	APP_EDB_LIB_NAME_EN string
 	EDB_LIB_Md5_KEY     string
+	ETA_MINI_URL        string
+	ETA_MINI_DES_KEY    string
 )
 
 var (
@@ -59,6 +62,10 @@ var (
 	LogMaxDays int //日志最大保留天数
 )
 
+var (
+	UseMongo bool // 是否使用mongo
+)
+
 func init() {
 	tmpRunMode, err := web.AppConfig.String("run_mode")
 	if err != nil {
@@ -134,6 +141,8 @@ func init() {
 		EDB_LIB_URL = config["edb_lib_url"]
 		APP_EDB_LIB_NAME_EN = config["app_edb_lib_name_en"]
 		EDB_LIB_Md5_KEY = config["edb_lib_md5_key"]
+		ETA_MINI_URL = config["eta_mini_url"]
+		ETA_MINI_DES_KEY = config["eta_mini_des_key"]
 	}
 	//日志配置
 	{

+ 8 - 2
utils/constants.go

@@ -143,6 +143,11 @@ const (
 	CHART_SOURCE_BALANCE_EXCEL                   = 11 // 平衡表图表
 )
 
+// 图表来源
+const (
+	CHART_SOURCE_DW = 1 // 东吴
+)
+
 // ETA表格
 const (
 	EXCEL_DEFAULT         = 1 // 自定义excel
@@ -204,8 +209,9 @@ var DataSourceEnMap = map[int]string{
 
 // 子数据来源渠道
 const (
-	DATA_SUB_SOURCE_EDB  = iota //经济数据库
-	DATA_SUB_SOURCE_DATE        //日期序列
+	DATA_SUB_SOURCE_EDB            = iota //经济数据库
+	DATA_SUB_SOURCE_DATE                  //日期序列
+	DATA_SUB_SOURCE_HIGH_FREQUENCY        //高频数据
 )
 
 const (