Преглед на файлове

Merge branch 'eta_2.4.2_area_graph_1216@guomengyuan' into debug

gmy преди 4 дни
родител
ревизия
bc91f24892
променени са 6 файла, в които са добавени 815 реда и са изтрити 0 реда
  1. 58 0
      controllers/chart_info.go
  2. 8 0
      models/chart_info.go
  3. 592 0
      services/area_graph/processor_business_logic.go
  4. 27 0
      services/area_graph/processor_factory.go
  5. 1 0
      utils/constants.go
  6. 129 0
      utils/date_util.go

+ 58 - 0
controllers/chart_info.go

@@ -2,8 +2,10 @@ package controllers
 
 import (
 	"encoding/json"
+	"errors"
 	"eta/eta_forum_admin/models"
 	"eta/eta_forum_admin/services"
+	"eta/eta_forum_admin/services/area_graph"
 	"eta/eta_forum_admin/utils"
 	"github.com/rdlucklib/rdluck_tools/paging"
 	"strconv"
@@ -184,6 +186,17 @@ func (this *ChartInfoController) ChartInfoDetail() {
 	if len(warnEdbList) > 0 {
 		chartInfo.WarnMsg = `图表引用指标异常,异常指标:` + strings.Join(warnEdbList, ",")
 	}
+
+	// 面积图 面积堆积 数据处理
+	if chartInfo.ChartType == utils.CHART_TYPE_AREA {
+		err, errMsg = fillAreaGraphData(extraConfigStr, edbList)
+		if err != nil {
+			br.Msg = "面积图处理失败"
+			br.ErrMsg = errMsg
+			return
+		}
+	}
+
 	// 图表的指标来源
 	sourceNameList, sourceNameEnList := services.GetEdbSourceByEdbInfoIdList(edbList)
 	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
@@ -212,6 +225,51 @@ func (this *ChartInfoController) ChartInfoDetail() {
 	br.Data = resp
 }
 
+func fillAreaGraphData(extraConfigStr string, edbDataList []*models.ChartEdbInfoMapping) (err error, errMsg string) {
+
+	var tmpConfig models.AreaExtraConf
+	if extraConfigStr != `` {
+		err = json.Unmarshal([]byte(extraConfigStr), &tmpConfig)
+		if err != nil {
+			errMsg = "面积图配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+		if tmpConfig.StandardEdbInfoId <= 0 {
+			errMsg = "面积图配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+	}
+	if tmpConfig.IsHeap == 1 {
+		standardIndexMap := make(map[string]*models.EdbDataList)
+		var startDate, endDate string
+		for _, v := range edbDataList {
+			// 判断是否为基准指标
+			if v.EdbInfoId == tmpConfig.StandardEdbInfoId {
+				if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+					startDate = dataList[0].DataTime
+					endDate = dataList[len(dataList)-1].DataTime
+					for _, dataObject := range dataList {
+						standardIndexMap[dataObject.DataTime] = dataObject
+					}
+				}
+				break
+			}
+		}
+		strategy, err := area_graph.CreateStrategy(tmpConfig.NullDealWay)
+		if err != nil {
+			return err, "创建空值处理器失败"
+		}
+		err = strategy.Deal(tmpConfig, edbDataList, standardIndexMap, startDate, endDate)
+		if err != nil {
+			return err, err.Error()
+		}
+	}
+
+	return nil, ""
+}
+
 // ChartInfoSearchByEs
 // @Title 图表模糊搜索(从es获取)
 // @Description  图表模糊搜索(从es获取)

+ 8 - 0
models/chart_info.go

@@ -64,6 +64,14 @@ type ChartInfoMore struct {
 	HaveOperaAuth bool `description:"是否有数据权限,默认:false"`
 }
 
+// AreaExtraConf 面积图配置
+type AreaExtraConf struct {
+	IsHeap            int `description:"是否堆积 1-堆积 2-不堆积"`
+	HeapWay           int `description:"堆积方式 1-普通 2-百分比"`
+	StandardEdbInfoId int `description:"基准指标id"`
+	NullDealWay       int `description:"空值处理方式,1-插值填充 2-前值填充 3-后值填充 4-等于0 5-删除日期"`
+}
+
 func AddChartInfo(item *ChartInfo) (lastId int64, err error) {
 	o := orm.NewOrm()
 	lastId, err = o.Insert(item)

+ 592 - 0
services/area_graph/processor_business_logic.go

@@ -0,0 +1,592 @@
+package area_graph
+
+import (
+	"errors"
+	"eta/eta_forum_admin/models"
+	"eta/eta_forum_admin/utils"
+	"github.com/shopspring/decimal"
+	"math"
+	"sort"
+	"time"
+)
+
+type InterpolateStrategy struct{}
+
+// Deal 空值填充:插值法填充
+func (i *InterpolateStrategy) Deal(tmpConfig models.AreaExtraConf, edbDataList []*models.ChartEdbInfoMapping, standardIndexMap map[string]*models.EdbDataList, startDate string, endDate string) (err error) {
+	for _, v := range edbDataList {
+		if v.EdbInfoId != tmpConfig.StandardEdbInfoId {
+			if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+
+				// 存放补充数据
+				var replenishDataList []*models.EdbDataList
+
+				// 处理从 startDate 到第一个数据的日期补充
+				if len(dataList) > 0 {
+					firstData := dataList[0]
+					// 将 startDate 到第一个数据日期之间的自然日填充补充数据,值为 0
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+					firstDataTime, _ := time.ParseInLocation(utils.FormatDate, firstData.DataTime, time.Local)
+
+					// 计算两个日期之间的天数差
+					if !startDataTime.Equal(firstDataTime) {
+						for startDataTime.Before(firstDataTime) {
+							// 补充数据
+							nextDay := startDataTime.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: startDataTime.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 startDataTime 到下一个日期
+							startDataTime = startDataTime.AddDate(0, 0, 1)
+						}
+					}
+				}
+
+				// 插值法补充数据
+				var startEdbInfoData *models.EdbDataList
+				for index := 0; index < len(dataList)-1; index++ {
+					// 获取当前数据和下一个数据
+					beforeIndexData := dataList[index]
+					afterIndexData := dataList[index+1]
+
+					if startEdbInfoData == nil {
+						startEdbInfoData = beforeIndexData
+						continue
+					}
+
+					// 获取两条数据之间相差的天数
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startEdbInfoData.DataTime, time.Local)
+					currDataTime, _ := time.ParseInLocation(utils.FormatDate, afterIndexData.DataTime, time.Local)
+					betweenHour := int(currDataTime.Sub(startDataTime).Hours())
+					betweenDay := betweenHour / 24
+
+					// 如果相差一天,那么过滤
+					if betweenDay <= 1 {
+						startEdbInfoData = afterIndexData
+						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: afterIndexData.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()
+						nextDay := tmpDataTime.Format(utils.FormatDate)
+
+						replenishIndexData := models.EdbDataList{
+							EdbDataId:     afterIndexData.EdbDataId,
+							DataTime:      nextDay,
+							DataTimestamp: tmpDataTime.UnixMilli(),
+							Value:         val,
+						}
+
+						// 将补充数据加入补充数据列表
+						replenishDataList = append(replenishDataList, &replenishIndexData)
+					}
+					startEdbInfoData = afterIndexData
+				}
+
+				// 处理从最后一个数据到 endDate 的日期补充
+				if len(dataList) > 0 {
+					lastData := dataList[len(dataList)-1]
+					// 将最后一个数据日期到 endDate 之间的自然日填充补充数据,值为 0
+					lastDataTime, _ := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+					endDataTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+
+					// 如果 lastDataTime 不等于 endDate,进行补充
+					if !lastDataTime.Equal(endDataTime) {
+						// 补充数据直到 endDate
+						for lastDataTime.Before(endDataTime) {
+							// 补充数据
+							addDate := lastDataTime.AddDate(0, 0, 1)
+							nextDay := addDate.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: addDate.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 lastDataTime 到下一个日期
+							lastDataTime = addDate
+						}
+					}
+				}
+
+				dataList = append(dataList, replenishDataList...)
+
+				// 根据基准指标筛选出符合数据
+				var resultDataList []*models.EdbDataList
+				for _, dataObject := range dataList {
+					if _, ok := standardIndexMap[dataObject.DataTime]; ok {
+						// 存在才保留
+						resultDataList = append(resultDataList, dataObject)
+					}
+				}
+
+				// 排序
+				sort.Slice(resultDataList, func(i, j int) bool {
+					return resultDataList[i].DataTimestamp < resultDataList[j].DataTimestamp
+				})
+
+				v.DataList = resultDataList
+			}
+		}
+	}
+	return nil
+}
+
+type FillWithPreviousStrategy struct{}
+
+// Deal 空值填充:前值填充
+func (f *FillWithPreviousStrategy) Deal(tmpConfig models.AreaExtraConf, edbDataList []*models.ChartEdbInfoMapping, standardIndexMap map[string]*models.EdbDataList, startDate string, endDate string) (err error) {
+	// 按自然日补充,再根据基准指标取对应数据
+	for _, v := range edbDataList {
+		if v.EdbInfoId != tmpConfig.StandardEdbInfoId {
+			if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+				// 存放补充数据
+				var replenishDataList []*models.EdbDataList
+
+				// 处理从 startDate 到第一个数据的日期补充
+				if len(dataList) > 0 {
+					firstData := dataList[0]
+					// 将 startDate 到第一个数据日期之间的自然日填充补充数据,值为 0
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+					firstDataTime, _ := time.ParseInLocation(utils.FormatDate, firstData.DataTime, time.Local)
+
+					// 计算两个日期之间的天数差
+					if !startDataTime.Equal(firstDataTime) {
+						for startDataTime.Before(firstDataTime) {
+							// 补充数据
+							nextDay := startDataTime.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: startDataTime.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 startDataTime 到下一个日期
+							startDataTime = startDataTime.AddDate(0, 0, 1)
+						}
+					}
+				}
+
+				// 处理指标中空值数据
+				for index := 0; index < len(dataList)-1; index++ {
+					// 获取当前数据和下一个数据
+					beforeIndexData := dataList[index]
+					afterIndexData := dataList[index+1]
+
+					for utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
+						// 创建补充数据
+						nextDay := utils.GetNextDay(beforeIndexData.DataTime)
+
+						toTime := utils.StringToTime(nextDay)
+						replenishIndexData := models.EdbDataList{
+							EdbInfoId:     v.EdbInfoId,
+							DataTime:      nextDay,
+							DataTimestamp: toTime.UnixMilli(),
+							Value:         beforeIndexData.Value,
+						}
+
+						// 将补充数据加入补充数据列表
+						replenishDataList = append(replenishDataList, &replenishIndexData)
+
+						// 更新 beforeIndexData 为新创建的补充数据
+						beforeIndexData = &replenishIndexData
+					}
+				}
+
+				// 处理从最后一个数据到 endDate 的日期补充
+				if len(dataList) > 0 {
+					lastData := dataList[len(dataList)-1]
+					// 将最后一个数据日期到 endDate 之间的自然日填充补充数据,值为 0
+					lastDataTime, _ := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+					endDataTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+
+					// 如果 lastDataTime 不等于 endDate,进行补充
+					if !lastDataTime.Equal(endDataTime) {
+						// 补充数据直到 endDate
+						for lastDataTime.Before(endDataTime) {
+							// 补充数据
+							addDate := lastDataTime.AddDate(0, 0, 1)
+							nextDay := addDate.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: addDate.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 lastDataTime 到下一个日期
+							lastDataTime = addDate
+						}
+					}
+				}
+
+				dataList = append(dataList, replenishDataList...)
+
+				// 根据基准指标筛选出符合数据
+				var resultDataList []*models.EdbDataList
+				for _, dataObject := range dataList {
+					_, ok = standardIndexMap[dataObject.DataTime]
+					if ok {
+						// 存在才保留
+						resultDataList = append(resultDataList, dataObject)
+					}
+				}
+
+				// 排序
+				sort.Slice(resultDataList, func(i, j int) bool {
+					return resultDataList[i].DataTimestamp < resultDataList[j].DataTimestamp
+				})
+
+				v.DataList = resultDataList
+			}
+		}
+	}
+	return nil
+}
+
+type FillWithNextStrategy struct{}
+
+// Deal 空值填充:后值填充
+func (f *FillWithNextStrategy) Deal(tmpConfig models.AreaExtraConf, edbDataList []*models.ChartEdbInfoMapping, standardIndexMap map[string]*models.EdbDataList, startDate string, endDate string) (err error) {
+	// 按自然日补充,再根据基准指标取对应数据
+	for _, v := range edbDataList {
+		if v.EdbInfoId != tmpConfig.StandardEdbInfoId {
+			if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+
+				// 存放补充数据
+				var replenishDataList []*models.EdbDataList
+
+				// 处理从 startDate 到第一个数据的日期补充
+				if len(dataList) > 0 {
+					firstData := dataList[0]
+					// 将 startDate 到第一个数据日期之间的自然日填充补充数据,值为 0
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+					firstDataTime, _ := time.ParseInLocation(utils.FormatDate, firstData.DataTime, time.Local)
+
+					// 计算两个日期之间的天数差
+					if !startDataTime.Equal(firstDataTime) {
+						for !startDataTime.After(firstDataTime) {
+							// 补充数据
+							nextDay := startDataTime.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: startDataTime.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 startDataTime 到下一个日期
+							startDataTime = startDataTime.AddDate(0, 0, 1)
+						}
+					}
+				}
+
+				// 处理从最后一个数据到 endDate 的日期补充
+				if len(dataList) > 0 {
+					lastData := dataList[len(dataList)-1]
+					// 将最后一个数据日期到 endDate 之间的自然日填充补充数据,值为 0
+					lastDataTime, _ := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+					endDataTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+
+					// 如果 lastDataTime 不等于 endDate,进行补充
+					if !lastDataTime.Equal(endDataTime) {
+						// 补充数据直到 endDate
+						for lastDataTime.Before(endDataTime) {
+							// 补充数据
+							addDate := lastDataTime.AddDate(0, 0, 1)
+							nextDay := addDate.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: addDate.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 lastDataTime 到下一个日期
+							lastDataTime = addDate
+						}
+					}
+				}
+
+				// 将切片数据倒序
+				reverseSlice(dataList)
+
+				// 处理指标中空值数据
+				for index := 0; index < len(dataList)-1; index++ {
+					// 获取当前数据和下一个数据
+					beforeIndexData := dataList[index]
+					afterIndexData := dataList[index+1]
+
+					for utils.IsMoreThanOneDay(afterIndexData.DataTime, beforeIndexData.DataTime) {
+						// 创建补充数据
+						nextDay := utils.GetNextDay(afterIndexData.DataTime)
+
+						toTime := utils.StringToTime(nextDay)
+						replenishIndexData := models.EdbDataList{
+							EdbInfoId:     v.EdbInfoId,
+							DataTime:      nextDay,
+							DataTimestamp: toTime.UnixMilli(),
+							Value:         afterIndexData.Value,
+						}
+
+						// 将补充数据加入补充数据列表
+						replenishDataList = append(replenishDataList, &replenishIndexData)
+
+						// 更新 beforeIndexData 为新创建的补充数据
+						afterIndexData = &replenishIndexData
+					}
+				}
+
+				dataList = append(dataList, replenishDataList...)
+
+				// 根据基准指标筛选出符合数据
+				var resultDataList []*models.EdbDataList
+				for _, dataObject := range dataList {
+					_, ok = standardIndexMap[dataObject.DataTime]
+					if ok {
+						// 存在才保留
+						resultDataList = append(resultDataList, dataObject)
+					}
+				}
+
+				// 排序
+				sort.Slice(resultDataList, func(i, j int) bool {
+					return resultDataList[i].DataTimestamp < resultDataList[j].DataTimestamp
+				})
+
+				v.DataList = resultDataList
+			}
+		}
+	}
+	return nil
+}
+
+type SetToZeroStrategy struct{}
+
+// Deal 空值填充:设为0
+func (s *SetToZeroStrategy) Deal(tmpConfig models.AreaExtraConf, edbDataList []*models.ChartEdbInfoMapping, standardIndexMap map[string]*models.EdbDataList, startDate string, endDate string) (err error) {
+	// 按自然日补充,再根据基准指标取对应数据
+	for _, v := range edbDataList {
+		if v.EdbInfoId != tmpConfig.StandardEdbInfoId {
+			if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+				// 存放补充数据
+				var replenishDataList []*models.EdbDataList
+
+				// 处理从 startDate 到第一个数据的日期补充
+				if len(dataList) > 0 {
+					firstData := dataList[0]
+					// 将 startDate 到第一个数据日期之间的自然日填充补充数据,值为 0
+					startDataTime, _ := time.ParseInLocation(utils.FormatDate, startDate, time.Local)
+					firstDataTime, _ := time.ParseInLocation(utils.FormatDate, firstData.DataTime, time.Local)
+
+					// 计算两个日期之间的天数差
+					if !startDataTime.Equal(firstDataTime) {
+						for startDataTime.Before(firstDataTime) {
+							// 补充数据
+							nextDay := startDataTime.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: startDataTime.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 startDataTime 到下一个日期
+							startDataTime = startDataTime.AddDate(0, 0, 1)
+						}
+					}
+				}
+
+				// 处理指标中空值数据
+				for index := 0; index < len(dataList)-1; index++ {
+					// 获取当前数据和下一个数据
+					beforeIndexData := dataList[index]
+					afterIndexData := dataList[index+1]
+
+					for utils.IsMoreThanOneDay(beforeIndexData.DataTime, afterIndexData.DataTime) {
+						// 创建补充数据
+						nextDay := utils.GetNextDay(beforeIndexData.DataTime)
+
+						toTime := utils.StringToTime(nextDay)
+						replenishIndexData := models.EdbDataList{
+							EdbInfoId:     v.EdbInfoId,
+							DataTime:      nextDay,
+							DataTimestamp: toTime.UnixMilli(),
+							Value:         0,
+						}
+
+						// 将补充数据加入补充数据列表
+						replenishDataList = append(replenishDataList, &replenishIndexData)
+
+						// 更新 beforeIndexData 为新创建的补充数据
+						beforeIndexData = &replenishIndexData
+					}
+				}
+
+				// 处理从最后一个数据到 endDate 的日期补充
+				if len(dataList) > 0 {
+					lastData := dataList[len(dataList)-1]
+					// 将最后一个数据日期到 endDate 之间的自然日填充补充数据,值为 0
+					lastDataTime, _ := time.ParseInLocation(utils.FormatDate, lastData.DataTime, time.Local)
+					endDataTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
+
+					// 如果 lastDataTime 不等于 endDate,进行补充
+					if !lastDataTime.Equal(endDataTime) {
+						// 补充数据直到 endDate
+						for lastDataTime.Before(endDataTime) {
+							// 补充数据
+							addDate := lastDataTime.AddDate(0, 0, 1)
+							nextDay := addDate.Format(utils.FormatDate)
+
+							// 生成补充数据,值为 0
+							replenishIndexData := models.EdbDataList{
+								EdbInfoId:     v.EdbInfoId,
+								DataTime:      nextDay,
+								DataTimestamp: addDate.UnixMilli(),
+								Value:         0,
+							}
+
+							// 将补充数据加入补充数据列表
+							replenishDataList = append(replenishDataList, &replenishIndexData)
+
+							// 更新 lastDataTime 到下一个日期
+							lastDataTime = addDate
+						}
+					}
+				}
+
+				dataList = append(dataList, replenishDataList...)
+
+				// 根据基准指标筛选出符合数据
+				var resultDataList []*models.EdbDataList
+				for _, dataObject := range dataList {
+					_, ok = standardIndexMap[dataObject.DataTime]
+					if ok {
+						// 存在才保留
+						resultDataList = append(resultDataList, dataObject)
+					}
+				}
+
+				// 排序
+				sort.Slice(resultDataList, func(i, j int) bool {
+					return resultDataList[i].DataTimestamp < resultDataList[j].DataTimestamp
+				})
+
+				v.DataList = resultDataList
+			}
+		}
+	}
+	return nil
+}
+
+type DeleteDateStrategy struct{}
+
+// Deal 删除日期
+func (d *DeleteDateStrategy) Deal(tmpConfig models.AreaExtraConf, edbDataList []*models.ChartEdbInfoMapping, standardIndexMap map[string]*models.EdbDataList, startDate string, endDate string) error {
+	// 取所有指标的时间交集
+	// 创建一个 map 来保存每个时间点的出现次数
+	timeMap := make(map[string]int)
+	for _, v := range edbDataList {
+		if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+			// 遍历所有的 dataList,为每个 DataTime 增加一个计数
+			for _, dataObject := range dataList {
+				timeMap[dataObject.DataTime]++
+			}
+		}
+	}
+
+	for _, v := range edbDataList {
+		if dataList, ok := v.DataList.([]*models.EdbDataList); ok {
+			// 遍历所有的 dataList,保留所有时间点在所有指标中都存在的数据
+			var resultDataList []*models.EdbDataList
+			for _, dataObject := range dataList {
+				if timeMap[dataObject.DataTime] == len(edbDataList) {
+					// 如果该时间点在所有指标中都存在,加入到结果列表
+					resultDataList = append(resultDataList, dataObject)
+				}
+			}
+
+			// 将符合条件的数据重新赋值回 v.DataList
+			v.DataList = resultDataList
+		}
+	}
+	return nil
+}
+
+// 将列表颠倒
+func reverseSlice(dataList []*models.EdbDataList) {
+	// 使用双指针法,前后两个指针向中间逼近
+	for i, j := 0, len(dataList)-1; i < j; i, j = i+1, j-1 {
+		// 交换位置
+		dataList[i], dataList[j] = dataList[j], dataList[i]
+	}
+}

+ 27 - 0
services/area_graph/processor_factory.go

@@ -0,0 +1,27 @@
+package area_graph
+
+import (
+	"eta/eta_forum_admin/models"
+	"fmt"
+)
+
+type NullDealStrategy interface {
+	Deal(tmpConfig models.AreaExtraConf, edbDataList []*models.ChartEdbInfoMapping, standardIndexMap map[string]*models.EdbDataList, startDate string, endDate string) (err error)
+}
+
+func CreateStrategy(dealWay int) (NullDealStrategy, error) {
+	switch dealWay {
+	case 1:
+		return &InterpolateStrategy{}, nil
+	case 2:
+		return &FillWithPreviousStrategy{}, nil
+	case 3:
+		return &FillWithNextStrategy{}, nil
+	case 4:
+		return &SetToZeroStrategy{}, nil
+	case 5:
+		return &DeleteDateStrategy{}, nil
+	default:
+		return nil, fmt.Errorf("未知的空值处理类型: %d", dealWay)
+	}
+}

+ 1 - 0
utils/constants.go

@@ -302,6 +302,7 @@ const (
 const (
 	CHART_TYPE_CURVE           = 1  //曲线图
 	CHART_TYPE_SEASON          = 2  //季节性图
+	CHART_TYPE_AREA            = 3  // 面积图
 	CHART_TYPE_BAR             = 7  //柱形图
 	CHART_TYPE_SECTION_SCATTER = 10 //截面散点图样式
 	CHART_TYPE_RADAR           = 11 //雷达图

+ 129 - 0
utils/date_util.go

@@ -0,0 +1,129 @@
+package utils
+
+import (
+	"time"
+)
+
+// 定义时间格式常量
+const (
+	YearMonthDay     = "2006-01-02"                     // yyyy-MM-dd
+	YearMonthDayTime = "2006-01-02 15:04:05"            // yyyy-MM-dd HH:mm:ss
+	MonthDay         = "01-02"                          // MM-dd
+	DayMonthYear     = "02-01-2006"                     // dd-MM-yyyy
+	YearMonth        = "2006-01"                        // yyyy-MM
+	FullDate         = "Monday, 02-Jan-06 15:04:05 PST" // 完整日期:例如:Monday, 02-Jan-06 15:04:05 PST
+)
+
+// GetPreYearTime 获取当前时间 前n年的时间 返回yyyy-MM-dd 格式的时间
+func GetPreYearTime(n int) string {
+	// 获取当前时间
+	now := time.Now()
+	// 计算前n年的时间
+	preYearTime := now.AddDate(-n, 0, 0)
+	// 格式化时间
+	return preYearTime.Format("2006-01-02")
+}
+
+// IsMoreThanOneDay 判断两个yyyy-MM-dd类型的时间,相差是否大于1天
+func IsMoreThanOneDay(startDate, endDate string) bool {
+	startTime, _ := time.Parse("2006-01-02", startDate)
+	endTime, _ := time.Parse("2006-01-02", endDate)
+	diff := endTime.Sub(startTime)
+	days := diff.Hours() / 24
+	return days > 1
+}
+
+// GetNextDay 获取 yyyy-MM-dd类型的时间的下一天
+func GetNextDay(date string) string {
+	t, _ := time.Parse("2006-01-02", date)
+	nextDay := t.AddDate(0, 0, 1)
+	return nextDay.Format("2006-01-02")
+}
+
+// GetNextDayN 获取 yyyy-MM-dd 类型的时间的下n天
+func GetNextDayN(date string, n int) string {
+	t, _ := time.Parse("2006-01-02", date)
+	nextDay := t.AddDate(0, 0, n)
+	return nextDay.Format("2006-01-02")
+}
+
+var daysOfMonth = [...]int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+
+// AddDate 解决 Go time 包 AddDate() 添加年份/月份溢出到下一个月的问题。
+// 例如:
+//
+//	2024-02-29 AddDate(1, 0, 0) 期望结果: 2025-02-28
+//	2024-08-31 AddDate(0, 1, 1) 期望结果: 2024-09-30
+func AddDate(t time.Time, years, months int) time.Time {
+	month := t.Month()
+
+	// 规范年份和月份
+	years, months = norm(years, months, 12)
+
+	// 计算目标月份
+	targetMonth := int(month) + months
+	if targetMonth <= 0 {
+		// 处理负值月份
+		targetMonth += 12 * ((-targetMonth)/12 + 1)
+	}
+	// 取余计算目标月份
+	targetMonth = (targetMonth-1)%12 + 1
+
+	// 计算目标年份
+	targetYear := t.Year() + years + (int(month)+months-1)/12
+
+	// 计算目标月份最大天数
+	maxDayOfTargetMonth := daysOfMonth[targetMonth-1]
+	if isLeap(targetYear) && targetMonth == 2 {
+		maxDayOfTargetMonth++ // 闰年2月多一天
+	}
+
+	// 计算目标日期
+	targetDay := t.Day()
+	if targetDay > maxDayOfTargetMonth {
+		// 如果目标日期超出该月的天数,设置为该月的最后一天
+		targetDay = maxDayOfTargetMonth
+	}
+
+	// 返回新的日期
+	return time.Date(targetYear, time.Month(targetMonth), targetDay, t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), t.Location())
+}
+
+// norm 规范化年份和月份
+func norm(hi, lo, base int) (nhi, nlo int) {
+	if lo < 0 {
+		n := (-lo-1)/base + 1
+		hi -= n
+		lo += n * base
+	}
+	if lo >= base {
+		n := lo / base
+		hi += n
+		lo -= n * base
+	}
+	return hi, lo
+}
+
+// isLeap 判断是否为闰年
+func isLeap(year int) bool {
+	return year%4 == 0 && (year%100 != 0 || year%400 == 0)
+}
+
+// StringToTime string 类型时间 转换为 time.Time 类型
+func StringToTime(date string) time.Time {
+	t, _ := time.Parse("2006-01-02", date)
+	return t
+}
+
+// TimeToString time.Time 类型时间 转换为 string 类型
+func TimeToString(t time.Time, format string) string {
+	formattedTime := t.Format(format)
+	return formattedTime
+}
+
+// CompareDate 判断传入的两个字符串时间的前后顺序
+func CompareDate(data1, data2 string) bool {
+	t1, _ := time.Parse("2006-01-02", data1)
+	t2, _ := time.Parse("2006-01-02", data2)
+	return !t1.After(t2)
+}