Browse Source

图表详情

xyxie 10 months ago
parent
commit
f939f59cc3

+ 64 - 51
controllers/chart_classify.go

@@ -7,7 +7,10 @@ import (
 	"eta/eta_forum_admin/services"
 	"eta/eta_forum_admin/utils"
 	"fmt"
+	"github.com/rdlucklib/rdluck_tools/paging"
 	"sort"
+	"strconv"
+	"strings"
 	"time"
 )
 
@@ -198,12 +201,17 @@ func (this *ChartClassifyController) SimpleList() {
 	} else {
 		parentId = reqParentId
 	}
-	rootList, err := models.GetChartClassifyByParentId(parentId, utils.CHART_SOURCE_DEFAULT)
-	if err != nil && err.Error() != utils.ErrNoRow() {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取数据失败,Err:" + err.Error()
-		return
+	rootList := make([]*models.ChartClassifyItems, 0)
+	var err error
+	if parentId > 0 {
+		rootList, err = models.GetChartClassifyByParentId(parentId, utils.CHART_SOURCE_DEFAULT)
+		if err != nil && err.Error() != utils.ErrNoRow() {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取数据失败,Err:" + err.Error()
+			return
+		}
 	}
+
 	var sortList models.ChartClassifyItemList
 
 	if reqParentId >= 0 {
@@ -218,7 +226,6 @@ func (this *ChartClassifyController) SimpleList() {
 		if len(allChartInfo) > 0 {
 			for _, v := range allChartInfo {
 				v.Children = make([]*models.ChartClassifyItems, 0)
-				v.ParentId = parentId
 				nodeAll = append(nodeAll, v)
 			}
 		}
@@ -784,10 +791,10 @@ func getChartClassifyListForMeV2(adminInfo system.Admin, resp *models.ChartClass
 // ChartClassifyChartListV2
 // @Title 根据图表分类获取图表列表
 // @Description 根据图表分类获取图表列表接口
-// @Param   IsShowMe   query   bool  true       "是否只看我的,true、false"
+// @Param   SysUserIds   query   string  true       "根据创建人查询"
 // @Param   ChartClassifyId   query   bool  true       "图片分类id"
 // @Success 200 {object} models.ChartClassifyListResp
-// @router /classify/chart/list [get]
+// @router /classify/chart_list [get]
 func (this *ChartClassifyController) ChartClassifyChartListV2() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
@@ -806,65 +813,71 @@ func (this *ChartClassifyController) ChartClassifyChartListV2() {
 	resp := new(models.ChartClassifyListResp)
 
 	chartClassifyId, _ := this.GetInt("ChartClassifyId")
-	if chartClassifyId <= 0 {
+	if chartClassifyId < 0 && chartClassifyId != -1 {
 		br.Msg = "参数错误"
 		return
 	}
+	pageSize, _ := this.GetInt("PageSize")
+	currentIndex, _ := this.GetInt("CurrentIndex")
 
-	/*// 获取当前账号的不可见指标
-	noPermissionChartIdMap := make(map[int]bool)
-	{
-		obj := models.EdbInfoNoPermissionAdmin{}
-		confList, err := obj.GetAllChartListByAdminId(this.SysUser.AdminId)
+	var startSize int
+	if pageSize <= 0 {
+		pageSize = utils.PageSize20
+	}
+	if currentIndex <= 0 {
+		currentIndex = 1
+	}
+	startSize = utils.StartIndex(currentIndex, pageSize)
+
+	sysUserIds := this.GetString("SysUserIds")
+	allChartInfo := make([]*models.ChartClassifyItems, 0)
+	var err error
+	var total int64
+	if sysUserIds != "" {
+		adminIds := strings.Split(sysUserIds, ",")
+		if len(adminIds) == 0 {
+			br.Msg = "请选择正确的创建人"
+			return
+		}
+		adminIdsSlice := make([]int, 0)
+		for _, adminId := range adminIds {
+			adminIdInt, e := strconv.Atoi(adminId)
+			if e != nil {
+				br.Msg = "请选择正确的创建人"
+				return
+			}
+			adminIdsSlice = append(adminIdsSlice, adminIdInt)
+		}
+		allChartInfo, err = models.GetChartInfoByAdminIdAndClassify([]int{utils.CHART_SOURCE_DEFAULT}, adminIdsSlice, chartClassifyId, startSize, pageSize)
 		if err != nil && err.Error() != utils.ErrNoRow() {
 			br.Msg = "获取失败"
-			br.ErrMsg = "获取不可见指标配置数据失败,Err:" + err.Error()
+			br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
 			return
 		}
-		for _, v := range confList {
-			noPermissionChartIdMap[v.ChartInfoId] = true
+		total, err = models.GetChartInfoTotalByAdminIdAndClassify([]int{utils.CHART_SOURCE_DEFAULT}, adminIdsSlice, chartClassifyId)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表总数失败,Err:" + err.Error()
+			return
 		}
-	}*/
-
-	isShowMe, _ := this.GetBool("IsShowMe")
-	if isShowMe {
-		allChartInfo, err := models.GetChartInfoByAdminIdAndClassify([]int{utils.CHART_SOURCE_DEFAULT}, sysUser.AdminId, chartClassifyId)
+	} else {
+		allChartInfo, err = models.GetChartInfoByClassifyId(utils.CHART_SOURCE_DEFAULT, chartClassifyId, startSize, pageSize)
 		if err != nil && err.Error() != utils.ErrNoRow() {
 			br.Msg = "获取失败"
-			br.ErrMsg = "获取全部数据失败,Err:" + err.Error()
+			br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+			return
+		}
+		total, err = models.GetChartInfoTotalByClassifyId(utils.CHART_SOURCE_DEFAULT, chartClassifyId)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表总数失败,Err:" + err.Error()
 			return
 		}
-		// 移除没有权限的图表
-		//allNodes := services.HandleNoPermissionChart(allChartInfo, noPermissionChartIdMap, this.SysUser.AdminId)
-		resp.AllNodes = allChartInfo
-
-		br.Ret = 200
-		br.Success = true
-		br.Msg = "获取成功"
-		br.Data = resp
-		fmt.Println("source my classify")
-		return
-	}
-
-	allChartInfo, err := models.GetChartInfoAllByClassifyId(utils.CHART_SOURCE_DEFAULT, chartClassifyId)
-	if err != nil && err.Error() != utils.ErrNoRow() {
-		br.Msg = "获取失败"
-		br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
-		return
 	}
-	/*// 移除没有权限的图表
-	allNodes := services.HandleNoPermissionChart(allChartInfo, noPermissionChartIdMap, this.SysUser.AdminId)
-
-	for k, item := range allNodes {
-		item.Button = services.GetChartOpButton(this.SysUser, item.SysUserId, item.HaveOperaAuth)
-		item.Button.AddButton = false
-		item.Button.OpButton = false
-		item.Button.DeleteButton = false
-		allNodes[k] = item
-	}*/
 
+	page := paging.GetPaging(currentIndex, pageSize, int(total))
 	resp.AllNodes = allChartInfo
-
+	resp.Paging = page
 	br.Ret = 200
 	br.Success = true
 	br.Msg = "获取成功"

+ 165 - 161
controllers/chart_info.go

@@ -4,208 +4,212 @@ import (
 	"encoding/json"
 	"eta/eta_forum_admin/models"
 	"eta/eta_forum_admin/services"
+	"eta/eta_forum_admin/utils"
+	"fmt"
 	"strings"
+	"time"
 )
 
 type ChartInfoController struct {
-	BaseCommonController
+	BaseAuthController
 }
 
-// Update
-// @Title 图表-更新
-// @Description 图表-更新
-// @Param	request	body data_manage.AddChartReq true "type json string"
-// @Success 200 {object} data_manage.AddChartInfoResp
-// @router /update [post]
-func (this *ChartInfoController) Update() {
+// ChartInfoDetail
+// @Title 获取图表详情
+// @Description 获取图表详情接口
+// @Param   ChartInfoId   query   int  true       "图表id"
+// @Param   DateType   query   int  true       "日期类型:1:00年至今,2:10年至今,3:15年至今,4:年初至今,5:自定义时间"
+// @Param   StartDate   query   string  true       "自定义开始日期"
+// @Param   EndDate   query   string  true       "自定义结束日期"
+// @Param   Calendar   query   string  true       "公历/农历"
+// @Param   SeasonStartDate   query   string  true       "季节性图开始日期"
+// @Param   SeasonEndDate   query   string  true       "季节性图结束日期"
+// @Param   EdbInfoId   query   string  true       "指标ID,多个用英文逗号隔开"
+// @Param   ChartType   query   int  true       "生成样式:1:曲线图,2:季节性图"
+// @Success 200 {object} models.ChartInfoDetailResp
+// @router /detail [get]
+func (this *ChartInfoController) ChartInfoDetail() {
 	br := new(models.BaseResponse).Init()
 	defer func() {
 		this.Data["json"] = br
 		this.ServeJSON()
 	}()
-
-	/*deleteCache := true
-	cacheKey := "CACHE_CHART_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
-	defer func() {
-		if deleteCache {
-			utils.Rc.Delete(cacheKey)
-		}
-	}()
-	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
-		deleteCache = false
-		br.Msg = "系统处理中,请稍后重试!"
-		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
-		return
-	}*/
-	var req models.AddChartReq
-	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
-	if err != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + err.Error()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
 		return
 	}
+	chartInfoId, _ := this.GetInt("ChartInfoId")
 
-	if req.ChartInfo == nil {
-		br.Msg = "请选择图表"
-		return
-	}
-	if req.ChartInfo.ChartInfoId <= 0 {
-		br.Msg = "请选择图表"
-		return
-	}
-	//校验图表名称是否重复
-	req.ChartInfo.ChartName = strings.Trim(req.ChartInfo.ChartName, " ")
-	if req.ChartInfo.ChartName == "" {
-		br.Msg = "请填写图表名称!"
-		return
+	dateType, _ := this.GetInt("DateType")
+	fmt.Println("dateType:", dateType)
+	if dateType <= 0 {
+		dateType = 3
 	}
 
-	if len(req.EdbInfoList) <= 0 {
-		br.Msg = "指标信息不能为空"
-		return
-	}
-	for _, edbInfo := range req.EdbInfoList {
-		if edbInfo.EdbName == "" {
-			br.Msg = "指标名称不能为空"
-			return
-		}
-	}
+	startDate := this.GetString("StartDate")
+	endDate := this.GetString("EndDate")
+	startYear, _ := this.GetInt("StartYear")
 
-	if len(req.ChartEdbMapping) <= 0 {
-		br.Msg = "图表指标映射不能为空"
-		return
-	}
+	edbInfoId := this.GetString("EdbInfoId")
+	chartType, _ := this.GetInt("ChartType")
 
-	chartInfo, err, errMsg, _ := services.UpdateChartInfoAndEdbInfo(&req, 0, "")
-	if err != nil {
-		br.Msg = errMsg
-		br.ErrMsg = err.Error()
-		return
+	calendar := this.GetString("Calendar")
+	if calendar == "" {
+		calendar = "公历"
 	}
 
-	br.Ret = 200
-	br.Success = true
-	br.Msg = "保存成功"
-	br.Data = models.AddChartInfoResp{
-		ChartInfoId: chartInfo.ChartInfoId,
-		UniqueCode:  chartInfo.UniqueCode,
-		ChartType:   chartInfo.ChartType,
-	}
-	return
-}
+	var err error
+	chartInfo := new(models.ChartInfoView)
+	chartInfo.HaveOperaAuth = true
 
-// Save
-// @Title 图表-保存
-// @Description 图表-保存
-// @Param	request	body data_manage.AddChartReq true "type json string"
-// @Success 200 {object} data_manage.AddChartInfoResp
-// @router /save [post]
-func (this *ChartInfoController) Save() {
-	br := new(models.BaseResponse).Init()
-	defer func() {
-		this.Data["json"] = br
-		this.ServeJSON()
-	}()
+	if chartInfoId > 0 {
+		chartInfo, err = models.GetChartInfoViewById(chartInfoId)
+		if err != nil {
+			if err.Error() == utils.ErrNoRow() {
+				br.Msg = "该图表已删除,自动查看下一图表"
+				br.ErrMsg = "该图表已删除,自动查看下一图表,Err:" + err.Error()
+				br.Ret = 406
+				return
+			}
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表信息失败,Err:" + err.Error()
+			return
+		}
+		chartType = chartInfo.ChartType
 
-	/*deleteCache := true
-	cacheKey := "CACHE_CHART_INFO_ADD_" + strconv.Itoa(sysUser.AdminId)
-	defer func() {
-		if deleteCache {
-			utils.Rc.Delete(cacheKey)
+		// 获取主题样式
+		chartTheme, err := services.GetChartThemeConfig(chartInfo.ChartThemeId, chartInfo.Source, chartInfo.ChartType)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取主题信息失败,Err:" + err.Error()
+			return
 		}
-	}()
-	if !utils.Rc.SetNX(cacheKey, 1, 30*time.Second) {
-		deleteCache = false
-		br.Msg = "系统处理中,请稍后重试!"
-		br.ErrMsg = "系统处理中,请稍后重试!" + sysUser.RealName + ";data:" + string(this.Ctx.Input.RequestBody)
-		return
-	}*/
-	var req models.AddChartReq
-	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
-	if err != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + err.Error()
-		return
+		chartInfo.ChartThemeStyle = chartTheme.Config
+		chartInfo.ChartThemeId = chartTheme.ChartThemeId
 	}
 
-	if req.ChartInfo == nil {
-		br.Msg = "请选择图表"
-		return
-	}
-	//校验图表名称是否重复
-	req.ChartInfo.ChartName = strings.Trim(req.ChartInfo.ChartName, " ")
-	if req.ChartInfo.ChartName == "" {
-		br.Msg = "请填写图表名称!"
-		return
-	}
+	resp := new(models.ChartInfoDetailResp)
 
-	if len(req.EdbInfoList) <= 0 {
-		br.Msg = "指标信息不能为空"
-		return
-	}
-	for _, edbInfo := range req.EdbInfoList {
-		if edbInfo.EdbName == "" {
-			br.Msg = "指标名称不能为空"
+	mappingList := make([]*models.ChartEdbInfoMapping, 0)
+	if chartInfoId > 0 {
+		mappingList, err = models.GetChartEdbMappingList(chartInfoId)
+		if err != nil {
+			br.Msg = "获取失败"
+			br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
 			return
 		}
+	} else {
+		if edbInfoId != "" {
+			edbInfoIds := strings.Split(edbInfoId, ",")
+			mappingList, err = models.GetChartEdbMappingListByEdbInfoId(edbInfoIds)
+			if err != nil {
+				br.Msg = "获取失败"
+				br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
+				return
+			}
+		}
 	}
 
-	if len(req.ChartEdbMapping) <= 0 {
-		br.Msg = "图表指标映射不能为空"
-		return
+	// 图表额外数据参数
+	extraConfigStr := chartInfo.ExtraConfig
+	// 柱方图的一些配置
+	var barConfig models.BarChartInfoReq
+	if chartInfo != nil && chartInfo.ChartType == 7 {
+		if chartInfo.BarConfig == `` {
+			br.Msg = "柱方图未配置"
+			br.ErrMsg = "柱方图未配置"
+			return
+		}
+		err := json.Unmarshal([]byte(chartInfo.BarConfig), &barConfig)
+		if err != nil {
+			br.Msg = "柱方图配置异常"
+			br.ErrMsg = "柱方图配置异常"
+			return
+		}
+		extraConfigStr = chartInfo.BarConfig
+	}
+	yearMax := 0
+	if dateType == utils.DateTypeNYears {
+		for _, v := range mappingList {
+			if v.LatestDate != "" {
+				lastDateT, tErr := time.Parse(utils.FormatDate, v.LatestDate)
+				if tErr != nil {
+					br.Msg = "获取失败"
+					br.ErrMsg = "获取图表日期信息失败,Err:" + tErr.Error()
+					return
+				}
+				if lastDateT.Year() > yearMax {
+					yearMax = lastDateT.Year()
+				}
+			}
+		}
 	}
+	// 开始/结束日期
+	startDate, endDate = utils.GetDateByDateTypeV2(dateType, startDate, endDate, startYear, yearMax)
 
-	chartInfo, err, errMsg, _ := services.AddChartInfo(&req, 0, "")
+	// 获取图表中的指标数据
+	edbList, xEdbIdValue, yDataList, dataResp, err, errMsg := services.GetChartEdbData(chartInfoId, chartType, calendar, startDate, endDate, mappingList, extraConfigStr, chartInfo.SeasonExtraConfig)
 	if err != nil {
-		br.Msg = errMsg
-		br.ErrMsg = err.Error()
+		br.Msg = "获取失败"
+		if errMsg != `` {
+			br.Msg = errMsg
+		}
+		br.ErrMsg = "获取图表,指标信息失败,Err:" + err.Error()
 		return
 	}
-
-	br.Ret = 200
-	br.Success = true
-	br.Msg = "保存成功"
-	br.Data = models.AddChartInfoResp{
-		ChartInfoId: chartInfo.ChartInfoId,
-		UniqueCode:  chartInfo.UniqueCode,
-		ChartType:   chartInfo.ChartType,
+	// 单位
+	if chartType == utils.CHART_TYPE_BAR && len(yDataList) > 0 {
+		chartInfo.Unit = yDataList[0].Unit
+		chartInfo.UnitEn = yDataList[0].UnitEn
 	}
-	return
-}
-
-// Delete
-// @Title 图表-删除
-// @Description 图表-删除
-// @Param	request	body data_manage.DeleteChartReq true "type json string"
-// @Success 200 {object}
-// @router /delete [post]
-func (this *ChartInfoController) Delete() {
-	br := new(models.BaseResponse).Init()
-	defer func() {
-		this.Data["json"] = br
-		this.ServeJSON()
-	}()
-
-	var req models.DeleteChartReq
-	err := json.Unmarshal(this.Ctx.Input.RequestBody, &req)
-	if err != nil {
-		br.Msg = "参数解析异常!"
-		br.ErrMsg = "参数解析失败,Err:" + err.Error()
-		return
+	warnEdbList := make([]string, 0)
+	for _, v := range edbList {
+		if v.IsNullData {
+			warnEdbList = append(warnEdbList, v.EdbName+"("+v.EdbCode+")")
+		}
 	}
-	if req.ChartInfoId <= 0 {
-		br.Msg = "请选择图表"
-		return
+	if len(warnEdbList) > 0 {
+		chartInfo.WarnMsg = `图表引用指标异常,异常指标:` + strings.Join(warnEdbList, ",")
+	}
+	if chartInfoId > 0 && chartInfo != nil {
+		if chartInfo.ChartType == 2 {
+			if chartInfo.SeasonStartDate != "" {
+				chartInfo.StartDate = chartInfo.SeasonStartDate
+				chartInfo.EndDate = chartInfo.SeasonEndDate
+				if chartInfo.DateType == 3 {
+					chartInfo.DateType = 5
+				}
+			}
+		}
 	}
 
-	err, errMsg := services.DeleteChart(req.ChartInfoId)
-	if err != nil {
-		br.Msg = errMsg
-		br.ErrMsg = err.Error()
-		return
+	// 图表的指标来源
+	sourceNameList, sourceNameEnList := services.GetEdbSourceByEdbInfoIdList(edbList)
+	chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+	chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
+	//判断是否需要展示英文标识
+	chartInfo.IsEnChart = services.CheckIsEnChart(chartInfo.ChartNameEn, edbList, chartInfo.Source, chartInfo.ChartType)
+
+	resp.EdbInfoList = edbList
+	resp.XEdbIdValue = xEdbIdValue
+	resp.YDataList = yDataList
+	resp.DataResp = dataResp
+
+	chartInfo.Button = models.ChartViewButton{
+		IsEdit:    chartInfo.IsEdit,
+		IsEnChart: chartInfo.IsEnChart,
+		IsAdd:     chartInfo.IsAdd,
+		IsCopy:    true,
+		IsSetName: chartInfo.IsSetName,
 	}
 
+	resp.ChartInfo = chartInfo
+	resp.BarChartInfo = barConfig
 	br.Ret = 200
 	br.Success = true
-	br.Msg = "删除成功"
+	br.Msg = "获取成功"
+	br.Data = resp
 }

+ 68 - 0
controllers/edb_info.go

@@ -0,0 +1,68 @@
+package controllers
+
+import (
+	"eta/eta_forum_admin/models"
+	"eta/eta_forum_admin/services"
+	"eta/eta_forum_admin/utils"
+)
+
+// EdbInfoController 数据管理
+type EdbInfoController struct {
+	BaseAuthController
+}
+
+// TraceEdbInfo
+// @Title 指标溯源接口
+// @Description 指标溯源接口
+// @Param   UniqueCode   query   int  true       "指标唯一编码,如果是管理后台访问,传固定字符串:7c69b590249049942070ae9dcd5bf6dc"
+// @Success 200 {object} data_manage.ChartListResp
+// @router /trace [get]
+func (this *EdbInfoController) TraceEdbInfo() {
+	br := new(models.BaseResponse).Init()
+	defer func() {
+		this.Data["json"] = br
+		this.ServeJSON()
+	}()
+	sysUser := this.SysUser
+	if sysUser == nil {
+		br.Msg = "请登录"
+		br.ErrMsg = "请登录,SysUser Is Empty"
+		br.Ret = 408
+		return
+	}
+	//edbInfoId, _ := this.GetInt("EdbInfoId")
+	//if edbInfoId <= 0 {
+	//	br.Msg = "参数错误"
+	//	br.ErrMsg = "参数错误"
+	//	return
+	//}
+
+	uniqueCode := this.GetString("UniqueCode")
+	if uniqueCode == `` {
+		br.Msg = "参数错误"
+		br.ErrMsg = "参数错误"
+		return
+	}
+
+	//  根据UniqueCode获取指标信息
+	edbInfo, err := models.GetEdbInfoByUniqueCode(uniqueCode)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.Msg = "获取失败,Err:" + err.Error()
+		if err.Error() == utils.ErrNoRow() {
+			br.Msg = "指标不存在"
+		}
+		return
+	}
+
+	resp, err := services.TraceEdbInfoByEdbInfoId(edbInfo.EdbInfoId, this.SysUser.AdminId)
+	if err != nil {
+		br.Msg = "获取失败"
+		br.Msg = "获取失败,Err:" + err.Error()
+		return
+	}
+	br.Ret = 200
+	br.Data = resp
+	br.Success = true
+	br.Msg = "查询成功"
+}

+ 2 - 1
go.mod

@@ -9,7 +9,9 @@ require (
 	github.com/go-redis/redis/v8 v8.11.5
 	github.com/go-sql-driver/mysql v1.7.0
 	github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b
+	github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19
 	github.com/olivere/elastic/v7 v7.0.32
+	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/shopspring/decimal v1.3.1
 	go.mongodb.org/mongo-driver v1.15.0
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
@@ -32,7 +34,6 @@ require (
 	github.com/klauspost/compress v1.13.6 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/mailru/easyjson v0.7.7 // indirect
-	github.com/mattn/go-sqlite3 v2.0.3+incompatible // indirect
 	github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
 	github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect

+ 145 - 0
go.sum

@@ -1,35 +1,83 @@
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
+github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk=
+github.com/astaxie/beego v1.12.3/go.mod h1:p3qIm0Ryx7zeBHLljmd7omloyca1s4yu1a8kM1FkpIA=
 github.com/beego/bee/v2 v2.1.0 h1:4WngbAnkvVOyKy74WXcRH3clon76wkjhuzrV2mx2fQU=
 github.com/beego/bee/v2 v2.1.0/go.mod h1:wDhKy5TNxv46LHKsK2gyxo38ObCOm9PbCN89lWHK3EU=
 github.com/beego/beego/v2 v2.1.0 h1:Lk0FtQGvDQCx5V5yEu4XwDsIgt+QOlNjt5emUa3/ZmA=
 github.com/beego/beego/v2 v2.1.0/go.mod h1:6h36ISpaxNrrpJ27siTpXBG8d/Icjzsc7pU1bWpp0EE=
+github.com/beego/goyaml2 v0.0.0-20130207012346-5545475820dd/go.mod h1:1b+Y/CofkYwXMUU0OhQqGvsY2Bvgr4j6jfT699wyZKQ=
+github.com/beego/x2j v0.0.0-20131220205130-a0352aadc542/go.mod h1:kSeGC/p1AbBiEp5kat81+DSQrZenVBZXklMLaELspWU=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
+github.com/bradfitz/gomemcache v0.0.0-20180710155616-bc664df96737/go.mod h1:PmM6Mmwb0LSuEubjR8N7PtNe1KxZLtOUHtbeikc5h60=
+github.com/casbin/casbin v1.7.0/go.mod h1:c67qKN6Oum3UF5Q1+BByfFxkwKvhwW57ITjqwtzR1KE=
+github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
 github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h4xcZ5GoxqC5SDxFQ8gwyZPKQoEzownBlhI80=
+github.com/couchbase/go-couchbase v0.0.0-20200519150804-63f3cdb75e0d/go.mod h1:TWI8EKQMs5u5jLKW/tsb9VwauIrMIxQG1r5fMsswK5U=
+github.com/couchbase/gomemcached v0.0.0-20200526233749-ec430f949808/go.mod h1:srVSlQLB8iXBVXHgnqemxUXqN6FCvClgCMPCsjBDR7c=
+github.com/couchbase/goutils v0.0.0-20180530154633-e865a1461c8a/go.mod h1:BQwMFlJzDjFDG3DJUdU0KORxn88UlsOULuxLExMh3Hs=
 github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
+github.com/cupcake/rdb v0.0.0-20161107195141-43ba34106c76/go.mod h1:vYwsqCOLxGiisLwp9rITslkFNpZD5rz43tf41QFkTWY=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
+github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
+github.com/elastic/go-elasticsearch/v6 v6.8.5/go.mod h1:UwaDJsD3rWLM5rKNFzv9hgox93HoX8utj1kxD9aFUcI=
+github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
 github.com/elazarl/go-bindata-assetfs v1.0.1 h1:m0kkaHRKEu7tUIUFVwhGGGYClXvyl4RE03qmvRTNfbw=
 github.com/elazarl/go-bindata-assetfs v1.0.1/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4=
 github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
 github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
 github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4=
 github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
+github.com/garyburd/redigo v1.6.3/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw=
+github.com/glendc/gopher-json v0.0.0-20170414221815-dc4743023d0c/go.mod h1:Gja1A+xZ9BoviGJNA2E9vFkPjjsl+CoJxSXiQM1UXtw=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-redis/redis v6.14.2+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
 github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI=
 github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo=
+github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
+github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
 github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
 github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
 github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
+github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
+github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
+github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
+github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
+github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
+github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
 github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
+github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
 github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
+github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
 github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac h1:Q0Jsdxl5jbxouNs1TQYt0gxesYMU4VXRbsTlgDloZ50=
 github.com/gonum/blas v0.0.0-20181208220705-f22b278b28ac/go.mod h1:P32wAyui1PQ58Oce/KYkOqQv8cVw1zAapXOl+dRFGbc=
 github.com/gonum/floats v0.0.0-20181209220543-c233463c7e82 h1:EvokxLQsaaQjcWVWSV38221VAK7qc2zhaO17bKys/18=
@@ -44,86 +92,167 @@ github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9 h1:V2IgdyerlBa/MxaEFR
 github.com/gonum/matrix v0.0.0-20181209220409-c518dec07be9/go.mod h1:0EXg4mc1CNP0HCqCz+K4ts155PXIlUywf0wqN+GfPZw=
 github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b h1:fbskpz/cPqWH8VqkQ7LJghFkl2KPAiIFUHrTJ2O3RGk=
 github.com/gonum/stat v0.0.0-20181125101827-41a0da705a5b/go.mod h1:Z4GIJBJO3Wa4gD4vbwQxXXZ+WHmW6E9ixmNrwvs0iZs=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
+github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
 github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
+github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
 github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
 github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
 github.com/klauspost/compress v1.13.6 h1:P76CopJELS0TiO2mebmnzgWaajssP/EszplttgQxcgc=
 github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
+github.com/ledisdb/ledisdb v0.0.0-20200510135210-d35789ec47e6/go.mod h1:n931TsDuKuq+uX4v1fulaMbA/7ZLLhjc85h7chZGBCQ=
+github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
+github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/lib/pq v1.10.5 h1:J+gdV2cUmX7ZqL2B0lFcW0m+egaHC2V3lpO8nWxyYiQ=
 github.com/lib/pq v1.10.5/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible h1:gXHsfypPkaMZrKbD5209QV9jbUTJKjyR5WD3HYQSd+U=
 github.com/mattn/go-sqlite3 v2.0.3+incompatible/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
 github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe h1:iruDEfMl2E6fbMZ9s0scYfZQ84/6SPL6zC8ACM2oIL0=
 github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
+github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19 h1:LhWT2dBuNkYexwRSsPpYh67e0ikmH1ebBDaVkGHoMts=
+github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19/go.mod h1:LjhyrWzOLJ9l1azMoNr9iCvfNrHEREqvJHzSLQcD0/o=
 github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
 github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=
 github.com/olivere/elastic/v7 v7.0.32 h1:R7CXvbu8Eq+WlsLgxmKVKPox0oOwAE/2T9Si5BnvK6E=
 github.com/olivere/elastic/v7 v7.0.32/go.mod h1:c7PVmLe3Fxq77PIfY/bZmxY/TAamBhCzZ8xDOE09a9k=
+github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
+github.com/onsi/ginkgo v1.12.0/go.mod h1:oUhWkIvk5aDxtKvDDuw8gItl8pKl42LzjC9KZE0HfGg=
 github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE=
 github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU=
+github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
 github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE=
 github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
+github.com/pelletier/go-toml v1.0.1/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/peterh/liner v1.0.1-0.20171122030339-3681c2a91233/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
 github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
+github.com/prometheus/client_golang v1.7.0/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
 github.com/prometheus/client_golang v1.15.1 h1:8tXpTmJbyH5lydzFPoxSIJ0J46jdh3tylbvM1xCv0LI=
 github.com/prometheus/client_golang v1.15.1/go.mod h1:e9yaBhRPU2pPNsZwE+JdQl0KEt1N9XgF6zxWmaC0xOk=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/prometheus/client_model v0.3.0 h1:UBgGFHqYdG/TPFD1B1ogZywDqEkwp3fBMvqdiQ7Xew4=
 github.com/prometheus/client_model v0.3.0/go.mod h1:LDGWKZIo7rky3hgvBe+caln+Dr3dPggB5dvjtD7w9+w=
+github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
 github.com/prometheus/common v0.42.0 h1:EKsfXEYo4JpWMHH5cg+KOUWeuJSov1Id8zGR8eeI1YM=
 github.com/prometheus/common v0.42.0/go.mod h1:xBwqVerjNdUDjgODMpudtOMwlOwf2SaTr1yjz4b7Zbc=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
 github.com/prometheus/procfs v0.9.0 h1:wzCHvIvM5SxWqYvwgVL7yJY8Lz3PKn49KQtpgMYJfhI=
 github.com/prometheus/procfs v0.9.0/go.mod h1:+pB4zwohETzFnmlpe6yd2lSc+0/46IYZRB/chUwxUZY=
+github.com/rdlucklib/rdluck_tools v1.0.3 h1:iOtK2QPlPQ6CL6c1htCk5VnFCHzyG6DCfJtunrMswK0=
+github.com/rdlucklib/rdluck_tools v1.0.3/go.mod h1:9Onw9o4w19C8KE5lxb8GyxgRBbZweRVkQSc79v38EaA=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
+github.com/shiena/ansicolor v0.0.0-20151119151921-a422bbe96644/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
 github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik=
 github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18/go.mod h1:nkxAfR/5quYxwPZhyDxgasBMnRtBZd0FCEpawpjMUFg=
 github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
 github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
+github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw=
+github.com/siddontang/goredis v0.0.0-20150324035039-760763f78400/go.mod h1:DDcKzU3qCuvj/tPnimWSsZZzvk9qvkvrIL5naVBPh5s=
+github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z92TR1JKMkLLoaOQk++LVnOKL3ScbJ8GNGA=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/ssdb/gossdb v0.0.0-20180723034631-88f6b59b84ec/go.mod h1:QBvMkMya+gXctz3kmljlUCu/yB3GZ6oee+dUozsezQE=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
+github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
+github.com/syndtr/goleveldb v0.0.0-20181127023241-353a9fca669c/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0=
+github.com/ugorji/go v0.0.0-20171122102828-84cb69a8af83/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
+github.com/wendal/errors v0.0.0-20130201093226-f66c77a7882b/go.mod h1:Q12BUT7DqIlHRmgv3RskH+UCM/4eqVMgI0EMmlSpAXc=
 github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
 github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI=
 github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY=
 github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4=
 github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8=
 github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM=
+github.com/ylywyn/jpush-api-go-client v0.0.0-20190906031852-8c4466c6e369/go.mod h1:Nv7wKD2/bCdKUFNKcJRa99a+1+aSLlCRJFriFYdjz/I=
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
 github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
 github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
+github.com/yuin/gopher-lua v0.0.0-20171031051903-609c9cd26973/go.mod h1:aEV29XrmTYFr3CiRxZeGHpkvbwq+prZduBqMaascyCU=
 go.mongodb.org/mongo-driver v1.15.0 h1:rJCKC8eEliewXjZGf0ddURtl7tTVy1TK3bfl0gkUSLc=
 go.mongodb.org/mongo-driver v1.15.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
 golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
+golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
 golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o=
 golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@@ -143,19 +272,35 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
+google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
+google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
+google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
+google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
+google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
 google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc h1:2gGKlE2+asNV9m7xrywl36YYNnBG5ZQ0r/BOOxqPpmk=
 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
+gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df h1:n7WqCuqOuCbNr617RXOY0AWRXxgwEyPp2z+p0+hgMuE=
 gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw=
+gopkg.in/mgo.v2 v2.0.0-20190816093944-a6b53ec6cb22/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
 gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
 gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

+ 4 - 2
models/chart_classify.go

@@ -4,6 +4,7 @@ import (
 	"eta/eta_forum_admin/utils"
 	"fmt"
 	"github.com/beego/beego/v2/client/orm"
+	"github.com/rdlucklib/rdluck_tools/paging"
 	"time"
 )
 
@@ -179,8 +180,9 @@ type ChartClassifyItemsButton struct {
 
 type ChartClassifyListResp struct {
 	AllNodes      []*ChartClassifyItems
-	Language      string `description:"指标的展示语言,CN:中文,EN:英文"`
-	CanOpClassify bool   `description:"是否允许操作分类"`
+	Language      string             `description:"指标的展示语言,CN:中文,EN:英文"`
+	CanOpClassify bool               `description:"是否允许操作分类"`
+	Paging        *paging.PagingItem `description:"分页数据"`
 }
 
 type ChartClassifyDeleteCheckResp struct {

+ 98 - 3
models/chart_info.go

@@ -1823,6 +1823,23 @@ func GetChartInfoAllByClassifyId(source, classifyId int) (items []*ChartClassify
 	return
 }
 
+func GetChartInfoByClassifyId(source, classifyId, startSize, pageSize int) (items []*ChartClassifyItems, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT chart_info_id,chart_classify_id,chart_name AS chart_classify_name,chart_name_en AS chart_classify_name_en,
+             unique_code,sys_user_id,sys_user_real_name,date_type,start_date,end_date,chart_type,calendar,season_start_date,season_end_date,is_join_permission
+            FROM chart_info WHERE chart_classify_id = ? AND source = ?  ORDER BY sort asc,create_time DESC  LIMIT ?,? `
+	_, err = o.Raw(sql, classifyId, source, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+func GetChartInfoTotalByClassifyId(source, classifyId int) (total int64, err error) {
+	o := orm.NewOrm()
+	sql := ` SELECT count(*)
+            FROM chart_info WHERE chart_classify_id = ? AND source = ?  ORDER BY sort asc,create_time DESC `
+	err = o.Raw(sql, classifyId, source).QueryRow(&total)
+	return
+}
+
 // ProfitFutureGoodChartResp 商品利润图
 type ProfitFutureGoodChartResp struct {
 	XDataList    []XData
@@ -1839,7 +1856,7 @@ func FIXChartClassifyId(newId, oldId int) (err error) {
 }
 
 // GetChartInfoByAdminIdAndClassify 获取所有我创建的图表,用于分类展示
-func GetChartInfoByAdminIdAndClassify(sourceList []int, adminId, classifyId int) (items []*ChartClassifyItems, err error) {
+func GetChartInfoByAdminIdAndClassify(sourceList []int, adminIds []int, classifyId, startSize, pageSize int) (items []*ChartClassifyItems, err error) {
 	num := len(sourceList)
 	if num <= 0 {
 		return
@@ -1848,8 +1865,22 @@ func GetChartInfoByAdminIdAndClassify(sourceList []int, adminId, classifyId int)
 	o := orm.NewOrm()
 	sql := ` SELECT chart_info_id,chart_classify_id,chart_name AS chart_classify_name,
              unique_code,sys_user_id,sys_user_real_name,date_type,start_date,end_date,chart_type,calendar,season_start_date,season_end_date,is_join_permission
-            FROM chart_info where source in (` + utils.GetOrmInReplace(num) + `)  AND sys_user_id = ? AND chart_classify_id = ? ORDER BY sort asc,create_time ASC `
-	_, err = o.Raw(sql, sourceList, adminId, classifyId).QueryRows(&items)
+            FROM chart_info where source in (` + utils.GetOrmInReplace(num) + `)  AND sys_user_id in (` + utils.GetOrmInReplace(len(adminIds)) + `) AND chart_classify_id = ? ORDER BY sort asc,create_time ASC limit ?,? `
+	_, err = o.Raw(sql, sourceList, adminIds, classifyId, startSize, pageSize).QueryRows(&items)
+	return
+}
+
+// GetChartInfoTotalByAdminIdAndClassify 获取所有我创建的图表,用于分类展示
+func GetChartInfoTotalByAdminIdAndClassify(sourceList []int, adminIds []int, classifyId int) (total int64, err error) {
+	num := len(sourceList)
+	if num <= 0 {
+		return
+	}
+
+	o := orm.NewOrm()
+	sql := ` SELECT count(*)
+            FROM chart_info where source in (` + utils.GetOrmInReplace(num) + `)  AND sys_user_id in (` + utils.GetOrmInReplace(len(adminIds)) + `) AND chart_classify_id = ? `
+	err = o.Raw(sql, sourceList, adminIds, classifyId).QueryRow(&total)
 	return
 }
 
@@ -1926,3 +1957,67 @@ type RadarYData struct {
 	Name  string    `description:"别名"`
 	Value []float64 `description:"每个指标的值"`
 }
+
+// GetChartInfoRefreshData 获取图表关联的指标信息
+func GetChartInfoRefreshData(chartInfoId int) (baseEdbInfoArr, calculateInfoArr []*EdbInfo, err error) {
+	o := orm.NewOrm()
+
+	sql := ` SELECT b.* FROM  chart_edb_mapping AS a
+		   INNER JOIN edb_info AS b ON a.edb_info_id=b.edb_info_id 
+		   WHERE a.chart_info_id=? `
+	edbInfoList := make([]*EdbInfo, 0)
+
+	_, err = o.Raw(sql, chartInfoId).QueryRows(&edbInfoList)
+	if err != nil {
+		return
+	}
+	for _, v := range edbInfoList {
+		fmt.Println(v.EdbInfoId, v.EdbType)
+		if v.EdbType == 1 {
+			baseEdbInfoArr = append(baseEdbInfoArr, v)
+		} else {
+			calculateInfoArr = append(calculateInfoArr, v)
+			// todo 刷新数据
+			//	newBaseEdbInfoArr, newCalculateInfoArr, err := GetChartRefreshEdbInfo(v.EdbInfoId, v.Source, 0)
+			if err != nil {
+				return baseEdbInfoArr, calculateInfoArr, err
+			}
+			//	baseEdbInfoArr = append(baseEdbInfoArr, newBaseEdbInfoArr...)
+			//	calculateInfoArr = append(calculateInfoArr, newCalculateInfoArr...)
+		}
+	}
+	return
+}
+
+// GetChartInfoRefreshDataByChartInfoIdSlice 根据图表id切片获取对应的指标数据
+func GetChartInfoRefreshDataByChartInfoIdSlice(chartInfoIdSlice []string) (baseEdbInfoArr, calculateInfoArr []*EdbInfo, err error) {
+	o := orm.NewOrm()
+
+	if len(chartInfoIdSlice) <= 0 {
+		return
+	}
+
+	sql := ` SELECT b.* FROM  chart_edb_mapping AS a
+		   INNER JOIN edb_info AS b ON a.edb_info_id=b.edb_info_id 
+		   WHERE a.chart_info_id in (` + strings.Join(chartInfoIdSlice, ",") + `) `
+	edbInfoList := make([]*EdbInfo, 0)
+
+	_, err = o.Raw(sql).QueryRows(&edbInfoList)
+	if err != nil {
+		return
+	}
+	for _, v := range edbInfoList {
+		if v.EdbType == 1 {
+			baseEdbInfoArr = append(baseEdbInfoArr, v)
+		} else {
+			calculateInfoArr = append(calculateInfoArr, v)
+			//newBaseEdbInfoArr, newCalculateInfoArr, err := GetChartRefreshEdbInfo(v.EdbInfoId, v.Source, 0)
+			if err != nil {
+				return baseEdbInfoArr, calculateInfoArr, err
+			}
+			//baseEdbInfoArr = append(baseEdbInfoArr, newBaseEdbInfoArr...)
+			//calculateInfoArr = append(calculateInfoArr, newCalculateInfoArr...)
+		}
+	}
+	return
+}

+ 1 - 1
models/chart_info_log.go

@@ -20,7 +20,7 @@ type ChartInfoLog struct {
 }
 
 func AddChartInfoLog(item *ChartInfoLog) (lastId int64, err error) {
-	o := orm.NewOrmUsingDB("data")
+	o := orm.NewOrm()
 	lastId, err = o.Insert(item)
 	return
 }

+ 1 - 1
models/chart_theme_default_data.go

@@ -77,7 +77,7 @@ func GetChartThemeDefaultDataItemList(endInfoId int, startDate string) (list []*
 // @param edbInfoIdList []int
 // @return list []*models.ChartEdbInfoMapping
 // @return err error
-func GetChartEdbMappingListByEdbInfoId(edbInfoIdList []int) (list []*ChartEdbInfoMapping, err error) {
+func GetChartEdbMappingListByEdbInfoId(edbInfoIdList []string) (list []*ChartEdbInfoMapping, err error) {
 	num := len(edbInfoIdList)
 	if num <= 0 {
 		return

+ 191 - 0
models/data/edb_data_quarter.go

@@ -0,0 +1,191 @@
+package data
+
+import (
+	"eta/eta_forum_admin/models"
+	"eta/eta_forum_admin/utils"
+	"fmt"
+	"github.com/nosixtools/solarlunar"
+	"strconv"
+	"time"
+)
+
+type EdbDataItems struct {
+	Items                []*models.EdbDataList
+	Year                 int
+	BetweenDay           int   `json:"-" description:"公历与农历之间相差的天数"`
+	CuttingDataTimestamp int64 `description:"切割的时间戳"`
+}
+
+type EdbDataResult struct {
+	List []*EdbDataItems
+}
+
+// AddCalculateQuarterV6 指标季度数据计算(季节性图表)
+func AddCalculateQuarterV6(dataList []*models.EdbDataList) (result *EdbDataResult, err error) {
+	var errMsg string
+	defer func() {
+		if errMsg != "" {
+			fmt.Println("errMsg:", errMsg)
+		}
+	}()
+
+	endDate := dataList[len(dataList)-1].DataTime
+	endDateForm, err := time.Parse(utils.FormatDate, endDate)
+	if err != nil {
+		return result, err
+	}
+	thisMonth := int(endDateForm.Month())
+
+	result = new(EdbDataResult)
+	var yearArr []int
+	yearMap := make(map[int]int)
+	var cureentDate time.Time
+	if thisMonth < 11 {
+		for k, v := range dataList {
+			dateTime, err := time.Parse(utils.FormatDate, v.DataTime)
+			if err != nil {
+				errMsg = "time.Parse Err:" + err.Error() + ";DataTime:" + v.DataTime
+				return result, err
+			}
+			if k == len(dataList)-1 {
+				cureentDate = dateTime
+			}
+			year := dateTime.Year()
+			if _, ok := yearMap[year]; !ok {
+				yearArr = append(yearArr, year)
+			}
+			yearMap[year] = year
+		}
+	} else {
+		for k, v := range dataList {
+			dateTime, err := time.Parse(utils.FormatDate, v.DataTime)
+			if err != nil {
+				errMsg = "time.Parse Err:" + err.Error() + ";DataTime:" + v.DataTime
+				return result, err
+			}
+			if k == len(dataList)-1 {
+				cureentDate = dateTime
+			}
+			year := dateTime.Year() + 1
+			if _, ok := yearMap[year]; !ok {
+				yearArr = append(yearArr, year)
+			}
+			yearMap[year] = year
+		}
+	}
+	//排序
+	fmt.Println("yearArr:", yearArr)
+	thisYear := cureentDate.Year()
+	//thisMonth := int(cureentDate.Month())
+
+	fmt.Println("thisMonth:", thisMonth)
+	for ky, vy := range yearArr {
+		fmt.Printf("line 432:ky:%d, vy:%d, thisYear:%d, thisMonth:%d", ky, vy, thisYear, thisMonth)
+		fmt.Println("")
+		if thisMonth < 11 {
+			currentYearCjnl := strconv.Itoa(thisYear) + "-01-01"               //当前年份春节农历
+			currentYearCjgl := solarlunar.LunarToSolar(currentYearCjnl, false) //当前年份春节公历
+			currentYearCjglDate, err := time.Parse(utils.FormatDate, currentYearCjgl)
+			if err != nil {
+				errMsg = "生成当前春节失败,Err:" + err.Error()
+				return result, err
+			}
+
+			preYear := vy
+			preYearCjnl := strconv.Itoa(preYear) + "-01-01"            //之前年份春节农历
+			preYearCjgl := solarlunar.LunarToSolar(preYearCjnl, false) //之前年份春节公历
+			preYearCjglDate, err := time.Parse(utils.FormatDate, preYearCjgl)
+			if err != nil {
+				errMsg = "生成历史年份春节失败,Err:" + err.Error()
+				return result, err
+			}
+			day := currentYearCjglDate.Sub(preYearCjglDate).Hours() / float64(24)
+
+			fmt.Println("day:", day, currentYearCjglDate, preYearCjglDate)
+
+			items := new(EdbDataItems)
+			items.BetweenDay = int(day) //公历日期换算成农历,需要减除的天数
+			items.Year = preYear
+			for _, v := range dataList {
+				dateTime, err := time.Parse(utils.FormatDate, v.DataTime)
+				if err != nil {
+					errMsg = "time.Parse Err:" + err.Error() + ";DataTime:" + v.DataTime
+					return result, err
+				}
+				newDate := dateTime.AddDate(0, 0, int(day))
+				timestamp := newDate.UnixNano() / 1e6
+				item := new(models.EdbDataList)
+				item.DataTime = newDate.Format(utils.FormatDate)
+				item.EdbInfoId = v.EdbInfoId
+				item.Value = v.Value
+				item.EdbDataId = v.EdbDataId
+				item.DataTimestamp = timestamp
+				items.Items = append(items.Items, item)
+			}
+			result.List = append(result.List, items)
+		} else {
+			nextYear := thisYear + 1
+			nextYearCjnl := strconv.Itoa(nextYear) + "-01-01"            //当前年份春节农历
+			nextYearCjgl := solarlunar.LunarToSolar(nextYearCjnl, false) //当前年份春节公历
+
+			nextYearCjglDate, err := time.Parse(utils.FormatDate, nextYearCjgl)
+			if err != nil {
+				errMsg = "生成当前春节失败,Err:" + err.Error()
+				return result, err
+			}
+			preYear := vy
+			preYearCjnl := strconv.Itoa(preYear) + "-01-01"            //之前年份春节农历
+			preYearCjgl := solarlunar.LunarToSolar(preYearCjnl, false) //之前年份春节公历
+			preYearCjglDate, err := time.Parse(utils.FormatDate, preYearCjgl)
+			if err != nil {
+				errMsg = "生成历史年份春节失败,Err:" + err.Error()
+				return result, err
+			}
+			day := nextYearCjglDate.Sub(preYearCjglDate).Hours() / float64(24)
+
+			fmt.Println("day:", day, nextYearCjglDate, preYearCjglDate)
+
+			items := new(EdbDataItems)
+			items.BetweenDay = int(day) //公历日期换算成农历,需要减除的天数
+			items.Year = preYear
+			fmt.Println("preYear:", preYear, "ky:", ky, "yearArrLen:", len(yearArr))
+			//if ky+1 < len(yearArr) {
+			for _, v := range dataList {
+				dateTime, err := time.Parse(utils.FormatDate, v.DataTime)
+				if err != nil {
+					errMsg = "time.Parse Err:" + err.Error() + ";DataTime:" + v.DataTime
+					return result, err
+				}
+				newDate := dateTime.AddDate(0, 0, int(day))
+				timestamp := newDate.UnixNano() / 1e6
+				item := new(models.EdbDataList)
+				item.DataTime = newDate.Format(utils.FormatDate)
+				item.EdbInfoId = v.EdbInfoId
+				item.Value = v.Value
+				item.EdbDataId = v.EdbDataId
+				item.DataTimestamp = timestamp
+				items.Items = append(items.Items, item)
+			}
+			result.List = append(result.List, items)
+			/*} else {
+				for _, v := range dataList {
+					dateTime, err := time.Parse(utils.FormatDate, v.DataTime)
+					if err != nil {
+						errMsg = "time.Parse Err:" + err.Error() + ";DataTime:" + v.DataTime
+						return result, err
+					}
+					timestamp := dateTime.UnixNano() / 1e6
+					item := new(EdbDataList)
+					item.DataTime = dateTime.Format(utils.FormatDate)
+					item.EdbInfoId = v.EdbInfoId
+					item.Value = v.Value
+					item.EdbDataId = v.EdbDataId
+					item.DataTimestamp = timestamp
+					items.Items = append(items.Items, item)
+				}
+				result.List = append(result.List, items)
+			}*/
+		}
+	}
+	return
+}

+ 32 - 0
models/edb_info.go

@@ -307,3 +307,35 @@ type EdbData struct {
 type EdbInfoListResp struct {
 	Item *EdbInfoList
 }
+
+func GetEdbInfoCalculateMap(edbInfoId, source int) (list []*EdbInfo, err error) {
+	o := orm.NewOrm()
+
+	//calculateTableName := GetEdbInfoCalculateTableName(source)
+
+	//sql := ` SELECT b.* FROM %s AS a
+	//		INNER JOIN edb_info AS b ON a.from_edb_info_id=b.edb_info_id
+	//		WHERE a.edb_info_id=? ORDER BY sort ASC `
+
+	//sql = fmt.Sprintf(sql, calculateTableName)
+
+	sql := ` SELECT b.* FROM edb_info_calculate_mapping AS a
+			INNER JOIN edb_info AS b ON a.from_edb_info_id=b.edb_info_id
+			WHERE a.edb_info_id=? ORDER BY sort ASC `
+	_, err = o.Raw(sql, edbInfoId).QueryRows(&list)
+	return
+}
+
+// TraceEdbInfoResp 指标追溯数据返回
+type TraceEdbInfoResp struct {
+	EdbInfoId   int                `description:"指标id"`
+	EdbInfoType int                `description:"指标类型: 0-普通指标; 1-预测指标"`
+	EdbName     string             `description:"指标名称"`
+	EdbType     int                `description:"指标类型: 1-基础指标; 2-计算指标"`
+	RuleTitle   string             `description:"指标规则"`
+	UniqueCode  string             `description:"唯一编码"`
+	ClassifyId  int                `description:"分类ID"`
+	Child       []TraceEdbInfoResp `description:"下级来源"`
+	IsStop      int8               `description:"是否终止"`
+	EdbInfo     *EdbInfo           `description:"指标信息" json:"-"`
+}

+ 36 - 0
models/mgodb/edb_data_base.go

@@ -81,6 +81,42 @@ func GetEdbDataBaseByEdbCode(edbCode string) (items []*EdbDataBase, err error) {
 	return
 }
 
+// GetEdbDataList 获取指标的数据(日期正序返回)
+func GetEdbDataList(endInfoId int, startDate, endDate string) (list []*EdbDataBase, err error) {
+	findOptions := options.Find()
+	findOptions.SetSort(bson.D{{"data_time", 1}})
+	db := NewMgo(utils.MONGODB_COMMUNITY, "edb_data_base", MgoClient)
+	filter := bson.D{{"edb_info_id", endInfoId}}
+	if startDate != "" {
+		filter = append(filter, bson.E{"data_time", bson.M{"$gte": startDate}})
+	}
+	if endDate != "" {
+		filter = append(filter, bson.E{"data_time", bson.M{"$lte": endDate}})
+	}
+	ctx := context.TODO()
+	cur, err := db.Find(filter, findOptions)
+	if err != nil {
+		return
+	}
+	// Close the cursor once finished
+	defer cur.Close(ctx)
+	for cur.Next(ctx) {
+		// create a value into which the single document can be decoded
+		var elem EdbDataBase
+		err = cur.Decode(&elem)
+		if err != nil {
+			return
+		}
+
+		list = append(list, &elem)
+	}
+
+	if err = cur.Err(); err != nil {
+		return
+	}
+	return
+}
+
 func InsertBatch(items []interface{}) (err error) {
 	db := NewMgo(utils.MONGODB_COMMUNITY, "edb_data_base", MgoClient)
 	_, err = db.InsertMany(items)

+ 8 - 17
routers/commentsRouter.go

@@ -46,7 +46,7 @@ func init() {
     beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:ChartClassifyController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:ChartClassifyController"],
         beego.ControllerComments{
             Method: "ChartClassifyChartListV2",
-            Router: `/classify/chart/list`,
+            Router: `/classify/chart_list`,
             AllowHTTPMethods: []string{"get"},
             MethodParams: param.Make(),
             Filters: nil,
@@ -90,27 +90,18 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:ChartInfoController"],
         beego.ControllerComments{
-            Method: "Delete",
-            Router: `/delete`,
-            AllowHTTPMethods: []string{"post"},
-            MethodParams: param.Make(),
-            Filters: nil,
-            Params: nil})
-
-    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:ChartInfoController"],
-        beego.ControllerComments{
-            Method: "Save",
-            Router: `/save`,
-            AllowHTTPMethods: []string{"post"},
+            Method: "ChartInfoDetail",
+            Router: `/detail`,
+            AllowHTTPMethods: []string{"get"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
 
-    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:ChartInfoController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:ChartInfoController"],
+    beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:EdbInfoController"] = append(beego.GlobalControllerRouter["eta/eta_forum_admin/controllers:EdbInfoController"],
         beego.ControllerComments{
-            Method: "Update",
-            Router: `/update`,
-            AllowHTTPMethods: []string{"post"},
+            Method: "TraceEdbInfo",
+            Router: `/trace`,
+            AllowHTTPMethods: []string{"get"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})

+ 5 - 0
routers/router.go

@@ -20,6 +20,11 @@ func init() {
 				&controllers.ChartClassifyController{},
 			),
 		),
+		web.NSNamespace("/edb",
+			web.NSInclude(
+				&controllers.EdbInfoController{},
+			),
+		),
 		web.NSNamespace("/login",
 			web.NSInclude(
 				&controllers.LoginController{},

+ 1593 - 0
services/chart_info_show.go

@@ -0,0 +1,1593 @@
+package services
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_forum_admin/models"
+	"eta/eta_forum_admin/models/data"
+	"eta/eta_forum_admin/utils"
+	"fmt"
+	"github.com/shopspring/decimal"
+	"math"
+	"sort"
+	"strconv"
+	"time"
+)
+
+type ChartInfoReq struct {
+	ChartInfoId int `description:"图表id,新增时传0"`
+}
+
+// DeleteChartInfoDataRedis 清除图表缓存
+func DeleteChartInfoDataRedis(bodyByte []byte) (err error) {
+	var req ChartInfoReq
+	err = json.Unmarshal(bodyByte, &req)
+	if err != nil {
+		return
+	}
+	if req.ChartInfoId > 0 {
+		err = utils.Rc.Delete(GetChartInfoDataKey(req.ChartInfoId))
+	}
+	return
+}
+
+// DeleteChartClassifyRedis 清除图表分类缓存
+func DeleteChartClassifyRedis(bodyByte []byte) (err error) {
+	err = utils.Rc.Delete(utils.CACHE_CHART_CLASSIFY)
+	return
+}
+
+// GetChartInfoDataKey 获取图表缓存的key
+func GetChartInfoDataKey(chartInfoId int) string {
+	key := fmt.Sprint(utils.CACHE_CHART_INFO_DATA, chartInfoId)
+	return key
+}
+
+// GetChartEdbData 获取图表的指标数据
+func GetChartEdbData(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*models.ChartEdbInfoMapping, extraConfigStr string, seasonExtraConfig string) (edbList []*models.ChartEdbInfoMapping, xEdbIdValue []int, yDataList []models.YData, dataResp interface{}, err error, errMsg string) {
+	edbList = make([]*models.ChartEdbInfoMapping, 0)
+	xEdbIdValue = make([]int, 0)
+	yDataList = make([]models.YData, 0)
+
+	var extraConfig interface{}
+	switch chartType {
+	case 7: // 柱形图
+		var barConfig models.BarChartInfoReq
+		if extraConfigStr == `` {
+			errMsg = "柱方图未配置"
+			err = errors.New(errMsg)
+			return
+		}
+		err = json.Unmarshal([]byte(extraConfigStr), &barConfig)
+		if err != nil {
+			errMsg = "柱方图配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+		extraConfig = barConfig
+	case 10: // 截面散点图
+		var tmpExtraConfig models.SectionScatterReq
+		if extraConfigStr == `` {
+			errMsg = "截面散点图未配置"
+			err = errors.New(errMsg)
+			return
+		}
+		err = json.Unmarshal([]byte(extraConfigStr), &tmpExtraConfig)
+		if err != nil {
+			errMsg = "截面散点配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+
+		extraConfig = tmpExtraConfig
+	case utils.CHART_TYPE_RADAR:
+		var barConfig models.RadarChartInfoReq
+		if extraConfigStr == `` {
+			errMsg = "雷达图未配置"
+			err = errors.New(errMsg)
+			return
+		}
+		err = json.Unmarshal([]byte(extraConfigStr), &barConfig)
+		if err != nil {
+			errMsg = "雷达图配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+		extraConfig = barConfig
+	default:
+		xEdbIdValue = make([]int, 0)
+		yDataList = make([]models.YData, 0)
+	}
+
+	// 指标对应的所有数据
+	edbDataListMap, edbList, err := getEdbDataMapList(chartInfoId, chartType, calendar, startDate, endDate, mappingList, seasonExtraConfig)
+	if err != nil {
+		return
+	}
+
+	// 特殊图形数据处理
+	switch chartType {
+	case 7: // 柱形图
+		barChartConf := extraConfig.(models.BarChartInfoReq)
+		xEdbIdValue, yDataList, err = BarChartData(mappingList, edbDataListMap, barChartConf.DateList, barChartConf.Sort)
+
+		for k := range yDataList {
+			yDataList[k].Unit = barChartConf.Unit
+			yDataList[k].UnitEn = barChartConf.UnitEn
+		}
+
+		for _, v := range edbList {
+			// 指标别名
+			if barChartConf.EdbInfoIdList != nil && len(barChartConf.EdbInfoIdList) > 0 {
+				for _, reqEdb := range barChartConf.EdbInfoIdList {
+					if v.EdbInfoId == reqEdb.EdbInfoId {
+						v.EdbAliasName = reqEdb.Name
+					}
+				}
+			}
+		}
+	case 10: // 截面散点图
+		sectionScatterConf := extraConfig.(models.SectionScatterReq)
+		xEdbIdValue, dataResp, err = GetSectionScatterChartData(chartInfoId, mappingList, edbDataListMap, sectionScatterConf)
+
+		var tmpExtraConfig models.SectionScatterReq
+		if extraConfigStr == `` {
+			errMsg = "截面散点图未配置"
+			err = errors.New(errMsg)
+			return
+		}
+		err = json.Unmarshal([]byte(extraConfigStr), &tmpExtraConfig)
+		if err != nil {
+			errMsg = "截面散点配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+
+		// 这个数据没有必要返回给前端
+		for _, v := range edbList {
+			v.DataList = nil
+		}
+	case utils.CHART_TYPE_RADAR: //雷达图
+		radarConf := extraConfig.(models.RadarChartInfoReq)
+		xEdbIdValue, dataResp, err = RadarChartData(mappingList, edbDataListMap, radarConf)
+	}
+	return
+}
+
+// GetEdbDataMapList 获取指标最后的基础数据
+func GetEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*models.ChartEdbInfoMapping, seasonExtra string) (edbDataListMap map[int][]*models.EdbDataList, edbList []*models.ChartEdbInfoMapping, err error) {
+	edbDataListMap, edbList, err = getEdbDataMapList(chartInfoId, chartType, calendar, startDate, endDate, mappingList, seasonExtra)
+	return
+}
+
+// getEdbDataMapList 获取指标最后的基础数据
+func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*models.ChartEdbInfoMapping, seasonExtraConfig string) (edbDataListMap map[int][]*models.EdbDataList, edbList []*models.ChartEdbInfoMapping, err error) {
+	// 指标对应的所有数据
+	edbDataListMap = make(map[int][]*models.EdbDataList)
+
+	for _, v := range mappingList {
+		//fmt.Println("v:", v.EdbInfoId)
+		item := new(models.ChartEdbInfoMapping)
+		item.EdbInfoId = v.EdbInfoId
+		item.SourceName = v.SourceName
+		item.Source = v.Source
+		item.EdbCode = v.EdbCode
+		item.EdbName = v.EdbName
+		item.EdbNameEn = v.EdbNameEn
+		item.Frequency = v.Frequency
+		item.EdbType = v.EdbType
+		item.FrequencyEn = GetFrequencyEn(v.Frequency)
+		if v.Unit != `无` {
+			item.Unit = v.Unit
+		}
+		item.UnitEn = v.UnitEn
+		item.StartDate = v.StartDate
+		item.EndDate = v.EndDate
+		item.ModifyTime = v.ModifyTime
+		item.EdbInfoCategoryType = v.EdbInfoCategoryType
+		item.PredictChartColor = v.PredictChartColor
+		item.ClassifyId = v.ClassifyId
+		if chartInfoId <= 0 {
+			item.IsAxis = 1
+			item.LeadValue = 0
+			item.LeadUnit = ""
+			item.ChartEdbMappingId = 0
+			item.ChartInfoId = 0
+			item.IsOrder = false
+			item.EdbInfoType = 1
+			item.ChartStyle = ""
+			item.ChartColor = ""
+			item.ChartWidth = 0
+			item.MaxData = v.MaxValue
+			item.MinData = v.MinValue
+		} else {
+			item.IsAxis = v.IsAxis
+			item.EdbInfoType = v.EdbInfoType
+			item.LeadValue = v.LeadValue
+			item.LeadUnit = v.LeadUnit
+			item.LeadUnitEn = GetLeadUnitEn(v.LeadUnit)
+			item.ChartEdbMappingId = v.ChartEdbMappingId
+			item.ChartInfoId = v.ChartInfoId
+			item.ChartStyle = v.ChartStyle
+			item.ChartColor = v.ChartColor
+			item.ChartWidth = v.ChartWidth
+			item.IsOrder = v.IsOrder
+			item.MaxData = v.MaxData
+			item.MinData = v.MinData
+		}
+		item.LatestValue = v.LatestValue
+		item.LatestDate = v.LatestDate
+		item.UniqueCode = v.UniqueCode
+		item.MoveLatestDate = v.LatestDate
+		item.EdbAliasName = v.EdbAliasName
+		item.IsConvert = v.IsConvert
+		item.ConvertType = v.ConvertType
+		item.ConvertValue = v.ConvertValue
+		item.ConvertUnit = v.ConvertUnit
+		item.ConvertEnUnit = v.ConvertEnUnit
+		item.IsJoinPermission = v.IsJoinPermission
+
+		var startDateReal string
+		var diffSeconds int64
+		if chartType == 2 { //季节性图
+			startDateReal = startDate
+		} else {
+			if v.EdbInfoType == 0 && v.LeadUnit != "" && v.LeadValue > 0 { //领先指标
+				var startTimeRealTemp time.Time
+				startDateParse, _ := time.Parse(utils.FormatDate, startDate)
+				switch v.LeadUnit {
+				case "天":
+					startTimeRealTemp = startDateParse.AddDate(0, 0, -v.LeadValue)
+				case "月":
+					startTimeRealTemp = startDateParse.AddDate(0, -v.LeadValue, 0)
+				case "季":
+					startTimeRealTemp = startDateParse.AddDate(0, -3*v.LeadValue, 0)
+				case "周":
+					startTimeRealTemp = startDateParse.AddDate(0, 0, -7*v.LeadValue)
+				case "年":
+					startTimeRealTemp = startDateParse.AddDate(-v.LeadValue, 0, 0)
+				}
+				if startTimeRealTemp.Before(startDateParse) {
+					startDateReal = startTimeRealTemp.Format(utils.FormatDate)
+					diffSeconds = (int64(startTimeRealTemp.UnixNano()) - int64(startDateParse.UnixNano())) / 1e6
+				} else {
+					startDateReal = startDate
+					diffSeconds = 0
+				}
+
+				// todo 预测指标的开始日期也要偏移
+				/*{
+					day, tmpErr := utils.GetDaysBetween2Date(utils.FormatDate, startDate, startDateReal)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					moveLatestDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, item.MoveLatestDate, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					item.MoveLatestDate = moveLatestDateTime.AddDate(0, 0, day).Format(utils.FormatDate)
+				}*/
+			} else {
+				startDateReal = startDate
+			}
+		}
+		//fmt.Println("line 1011 chart:", v.Source, v.EdbInfoId, startDateReal, endDate)
+		calendarPreYear := 0
+		if calendar == "农历" {
+			newStartDateReal, err := time.Parse(utils.FormatDate, startDateReal)
+			if err != nil {
+				fmt.Println("time.Parse:" + err.Error())
+			}
+			calendarPreYear = newStartDateReal.Year() - 1
+			newStartDateReal = newStartDateReal.AddDate(-1, 0, 0)
+			startDateReal = newStartDateReal.Format(utils.FormatDate)
+		}
+		dataList := make([]*models.EdbDataList, 0)
+		//fmt.Println("chart:", v.Source, v.EdbInfoId, startDateReal, endDate)
+		//fmt.Println("calendarPreYear:", calendarPreYear)
+		//var newEdbInfo *models.EdbInfo
+		switch v.EdbInfoCategoryType {
+		case 0:
+			dataList, err = GetEdbDataList(v.EdbInfoId, startDateReal, endDate)
+		case 1:
+		//	_, dataList, _, _, err, _ = GetPredictDataListByPredictEdbInfoId(v.EdbInfoId, startDateReal, endDate, true)
+		default:
+			err = errors.New(fmt.Sprint("获取失败,指标类型异常", v.EdbInfoCategoryType))
+		}
+		if err != nil {
+			return
+		}
+
+		if v.IsConvert == 1 {
+			switch v.ConvertType {
+			case 1:
+				for i, data := range dataList {
+					dataList[i].Value = data.Value * v.ConvertValue
+				}
+				//item.MaxData = item.MaxData * v.ConvertValue
+				//item.MinData = item.MinData * v.ConvertValue
+			case 2:
+				for i, data := range dataList {
+					dataList[i].Value = data.Value / v.ConvertValue
+				}
+				//item.MaxData = item.MaxData / v.ConvertValue
+				//item.MinData = item.MinData / v.ConvertValue
+			case 3:
+				for i, data := range dataList {
+					if data.Value <= 0 {
+						err = errors.New("数据中含有负数或0,无法对数运算")
+						return
+					}
+					dataList[i].Value = math.Log(data.Value) / math.Log(v.ConvertValue)
+				}
+				//item.MaxData = math.Log(item.MaxData) / math.Log(v.ConvertValue)
+				//item.MinData = math.Log(item.MinData) / math.Log(v.ConvertValue)
+			}
+		}
+
+		edbDataListMap[v.EdbInfoId] = dataList
+
+		if diffSeconds != 0 && v.EdbInfoType == 0 {
+			dataListLen := len(dataList)
+			for i := 0; i < dataListLen; i++ {
+				dataList[i].DataTimestamp = dataList[i].DataTimestamp - diffSeconds
+			}
+		}
+
+		if chartType == 2 {
+			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
+			}
+
+			if calendar == "农历" {
+				if len(dataList) <= 0 {
+					result := new(data.EdbDataResult)
+					item.DataList = result
+				} else {
+					result, tmpErr := data.AddCalculateQuarterV6(dataList)
+					if tmpErr != nil {
+						err = errors.New("获取农历数据失败,Err:" + tmpErr.Error())
+						return
+					}
+					quarterDataList, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
+					if tErr != nil {
+						err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
+						return
+					}
+					item.DataList = quarterDataList
+				}
+
+			} else {
+				quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
+				if tErr != nil {
+					err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
+					return
+				}
+				item.DataList = quarterDataList
+			}
+
+		} else if chartType == 7 || chartType == utils.CHART_TYPE_RADAR { //柱方图
+			//item.DataList = dataList
+		} else {
+			item.DataList = dataList
+		}
+		edbList = append(edbList, item)
+	}
+
+	return
+}
+
+// GetSeasonEdbInfoDataListByXDate 季节性图的指标数据根据横轴展示
+func GetSeasonEdbInfoDataListByXDate(dataList []*models.EdbDataList, latestDate time.Time, seasonExtraConfig string) (quarterDataListSort models.QuarterDataList, err error) {
+	xStartDate := "01-01"
+	xEndDate := "12-31"
+	jumpYear := 0
+	legends := make([]models.SeasonChartLegend, 0)
+	var seasonExtra models.SeasonExtraItem
+	if seasonExtraConfig != "" {
+		err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
+		if err != nil {
+			return
+		}
+	}
+
+	if seasonExtra.XStartDate != "" {
+		xStartDate = seasonExtra.XStartDate
+		xEndDate = seasonExtra.XEndDate
+		jumpYear = seasonExtra.JumpYear
+		legends = seasonExtra.ChartLegend
+	}
+
+	length := len(dataList)
+	if length == 0 {
+		return
+	}
+	legendMap := make(map[string]string, 0)
+	if len(legends) > 0 {
+		for _, v := range legends {
+			legendMap[v.Name] = v.Value
+		}
+	}
+	latestDateStr := latestDate.Format(utils.FormatDate)
+
+	//判断横轴的两个时间之间是不是跨年了,如果跨年了,则横轴截止年份比起始年份+1,如果不跨年,截止年份等于起始年份
+	//根据数据确定最早的年份,和最近年份
+	//根据横轴的日期,汇总所有的年份
+	startDate := dataList[0].DataTime
+	startDateT, tmpErr := time.Parse(utils.FormatDate, startDate)
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	startYear := startDateT.Year()
+	//获取数据的最新日期
+	lastDate := dataList[length-1].DataTime
+	lastDateT, tmpErr := time.Parse(utils.FormatDate, lastDate)
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	endYear := lastDateT.Year()
+	nowYear := time.Now().Year()
+	dataMap := make(map[string]models.QuarterXDateItem, 0)
+
+	quarterDataList := make([]*models.QuarterData, 0)
+	quarterMap := make(map[string][]*models.EdbDataList, 0)
+
+	//整理出日期
+	idx := 1
+	chartLegendMap := make(map[string]int, 0)
+	for currentStartYear := startYear; currentStartYear <= endYear; currentStartYear++ {
+		startStr := fmt.Sprintf("%d-%s", currentStartYear, xStartDate)
+		currentEndYear := currentStartYear
+		if jumpYear == 1 {
+			currentEndYear = currentStartYear + 1
+		}
+		endStr := fmt.Sprintf("%d-%s", currentEndYear, xEndDate)
+		name := fmt.Sprintf("%s_%s", startStr, endStr)
+		showName := fmt.Sprintf("%d_%d", currentStartYear, currentEndYear)
+
+		startT, tEr := time.Parse(utils.FormatDate, startStr)
+		if tEr != nil {
+			err = tEr
+			return
+		}
+
+		endT, tEr := time.Parse(utils.FormatDate, endStr)
+		if tEr != nil {
+			err = tEr
+			return
+		}
+
+		if lastDateT.Before(startT) {
+			//如果最新的日期在起始日之前,则跳出循环
+			break
+		}
+
+		if endT.Year() > nowYear {
+			//如果最新的日期比真实年份要大,则数据全部按照最大的年份补齐
+			nowYear = endT.Year()
+		}
+
+		item := models.QuarterXDateItem{
+			StartDate: startT,
+			EndDate:   endT,
+			ShowName:  showName,
+		}
+		dataMap[name] = item
+		chartLegendMap[name] = idx
+		idx++
+		fmt.Println("年份" + showName + "日期" + startStr + " " + endStr)
+		if lastDateT.Before(endT) {
+			//如果最新的日期在起始日之前,则跳出循环
+			break
+		}
+	}
+	lenYear := len(dataMap)
+	for k, v := range dataMap {
+		if i, ok := chartLegendMap[k]; ok {
+			v.ChartLegend = strconv.Itoa(endYear - lenYear + i)
+		}
+		dataMap[k] = v
+	}
+
+	for _, v := range dataList {
+		dataTimeT, _ := time.Parse(utils.FormatDate, v.DataTime)
+		year := dataTimeT.Year()
+		newItemDate := dataTimeT.AddDate(nowYear-year, 0, 0)
+		for k, dateItem := range dataMap {
+			tmpVal := models.EdbDataList{
+				EdbDataId:     v.EdbDataId,
+				EdbInfoId:     v.EdbInfoId,
+				DataTime:      v.DataTime,
+				DataTimestamp: v.DataTimestamp,
+				Value:         v.Value,
+			}
+			if (dateItem.StartDate.Before(dataTimeT) && dateItem.EndDate.After(dataTimeT)) || dateItem.StartDate == dataTimeT || dateItem.EndDate == dataTimeT {
+				if jumpYear == 1 {
+					//计算前一年最大的日期, 只补齐数据到去年
+					beforeYearMaxDate := fmt.Sprintf("%d-12-31", dateItem.StartDate.Year())
+					beforeYearMaxDateT, _ := time.Parse(utils.FormatDate, beforeYearMaxDate)
+					if dataTimeT.Before(beforeYearMaxDateT) || dataTimeT == beforeYearMaxDateT {
+						newItemDate = dataTimeT.AddDate(nowYear-year-1, 0, 0)
+					} else {
+						newItemDate = dataTimeT.AddDate(nowYear-year, 0, 0)
+					}
+				} else {
+					newItemDate = dataTimeT.AddDate(nowYear-year, 0, 0)
+				}
+				timestamp := newItemDate.UnixNano() / 1e6
+				tmpVal.DataTimestamp = timestamp
+				tmpV := &tmpVal
+				if findVal, ok := quarterMap[k]; !ok {
+					findVal = append(findVal, tmpV)
+					quarterMap[k] = findVal
+				} else {
+					findVal = append(findVal, tmpV)
+					quarterMap[k] = findVal
+				}
+
+				if v.DataTime == latestDateStr {
+					dateItem.CuttingDataTimestamp = timestamp
+					dataMap[k] = dateItem
+				}
+				//break
+			}
+		}
+	}
+	for k, v := range dataMap {
+		itemList := quarterMap[k]
+		quarterItem := new(models.QuarterData)
+		quarterItem.Years = v.ShowName
+		quarterItem.ChartLegend = v.ChartLegend
+		if le, ok := legendMap[v.ShowName]; ok {
+			if le != strconv.Itoa(v.StartDate.Year()) && le != strconv.Itoa(v.EndDate.Year()) {
+				quarterItem.ChartLegend = le
+			}
+		}
+		quarterItem.DataList = itemList
+		quarterItem.CuttingDataTimestamp = v.CuttingDataTimestamp
+
+		//如果等于最后的实际日期,那么将切割时间戳记录
+		if quarterItem.CuttingDataTimestamp == 0 {
+			//如果大于最后的实际日期,那么第一个点就是切割的时间戳
+			if latestDate.Before(v.StartDate) && len(itemList) > 0 {
+				quarterItem.CuttingDataTimestamp = itemList[0].DataTimestamp - 100
+			}
+		}
+		quarterDataList = append(quarterDataList, quarterItem)
+	}
+
+	if len(quarterDataList) > 0 {
+		quarterDataListSort = quarterDataList
+		sort.Sort(quarterDataListSort)
+	}
+	return
+}
+
+// GetSeasonEdbInfoDataListByXDateNong 季节性图的指标数据根据横轴选择农历时展示
+func GetSeasonEdbInfoDataListByXDateNong(result *data.EdbDataResult, latestDate time.Time, seasonExtraConfig string, calendarPreYear int) (quarterDataListSort models.QuarterDataList, err error) {
+	xStartDate := "01-01"
+	xEndDate := "12-31"
+	jumpYear := 0
+	legends := make([]models.SeasonChartLegend, 0)
+	var seasonExtra models.SeasonExtraItem
+	if seasonExtraConfig != "" {
+		err = json.Unmarshal([]byte(seasonExtraConfig), &seasonExtra)
+		if err != nil {
+			return
+		}
+	}
+
+	if seasonExtra.XStartDate != "" {
+		xStartDate = seasonExtra.XStartDate
+		xEndDate = seasonExtra.XEndDate
+		jumpYear = seasonExtra.JumpYear
+		legends = seasonExtra.ChartLegend
+	}
+
+	length := len(result.List)
+	if length == 0 {
+		return
+	}
+	legendMap := make(map[string]string, 0)
+	if len(legends) > 0 {
+		for _, v := range legends {
+			legendMap[v.Name] = v.Value
+		}
+	}
+	latestDateYear := latestDate.Year()
+	//判断横轴的两个时间之间是不是跨年了,如果跨年了,则横轴截止年份比起始年份+1,如果不跨年,截止年份等于起始年份
+	//根据数据确定最早的年份,和最近年份
+	//根据横轴的日期,汇总所有的年份
+
+	startYear := result.List[0].Year
+	/*if jumpYear == 1 {
+		if startYear != calendarPreYear {
+			startYear = startYear - 1
+		}
+	}*/
+
+	itemLength := len(result.List[length-1].Items)
+	//获取数据的最新日期
+	lastDate := result.List[length-1].Items[itemLength-1].DataTime
+	maxY := result.List[length-1].Year
+	lastDateT, tmpErr := time.Parse(utils.FormatDate, lastDate)
+	if tmpErr != nil {
+		err = tmpErr
+		return
+	}
+	endYear := lastDateT.Year()
+	nowYear := time.Now().Year()
+	dataMap := make(map[string]models.QuarterXDateItem, 0)
+
+	quarterDataList := make([]*models.QuarterData, 0)
+	resultData := make([]*models.QuarterData, 0)
+	quarterMap := make(map[string][]*models.EdbDataList, 0)
+
+	//整理出日期
+	var startTmpT, endTmpT time.Time
+	idx := 1
+	chartLegendMap := make(map[string]int, 0)
+	for currentStartYear := startYear; currentStartYear <= endYear; currentStartYear++ {
+		startStr := fmt.Sprintf("%d-%s", currentStartYear, xStartDate)
+		currentEndYear := currentStartYear
+		if jumpYear == 1 {
+			currentEndYear = currentStartYear + 1
+		}
+		endStr := fmt.Sprintf("%d-%s", currentEndYear, xEndDate)
+		showName := fmt.Sprintf("%d_%d", currentStartYear, currentEndYear)
+
+		startT, tEr := time.Parse(utils.FormatDate, startStr)
+		if tEr != nil {
+			err = tEr
+			return
+		}
+
+		endT, tEr := time.Parse(utils.FormatDate, endStr)
+		if tEr != nil {
+			err = tEr
+			return
+		}
+
+		if lastDateT.Before(startT) {
+			//如果最新的日期在起始日之前,则跳出循环
+			break
+		}
+		if endT.Year() > nowYear {
+			//如果最新的日期比真实年份要大,则数据全部按照最大的年份补齐
+			nowYear = endT.Year()
+		}
+		item := models.QuarterXDateItem{
+			StartDate: startT,
+			EndDate:   endT,
+			ShowName:  showName,
+		}
+		dataMap[showName] = item
+		fmt.Println("年份" + showName + "日期" + startStr + " " + endStr)
+		startTmpT = startT
+		endTmpT = endT
+		chartLegendMap[showName] = idx
+		idx++
+		if lastDateT.Before(endT) {
+			//如果最新的日期在起始日之前,则跳出循环
+			break
+		}
+	}
+	lenYear := len(dataMap)
+	for k, v := range dataMap {
+		if i, ok := chartLegendMap[k]; ok {
+			v.ChartLegend = strconv.Itoa(endYear - lenYear + i)
+		}
+		dataMap[k] = v
+	}
+
+	yearDataListMap := make(map[int]*data.EdbDataItems, 0)
+
+	for _, lv := range result.List {
+		yearDataListMap[lv.Year] = lv
+	}
+
+	//判断哪些点应该落在同一条时间线上
+	/*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)
+	for name, dateItem := range dataMap {
+		tY := dateItem.EndDate.Year()
+		if lastDateT.Month() >= 11 {
+			if maxY > endTmpT.Year() {
+				tY = tY + 1
+			}
+		}
+		lv, ok1 := yearDataListMap[tY]
+		fmt.Printf("name %s yearDataListMap[%d]\n", name, tY)
+		if !ok1 {
+			continue
+		}
+		for _, item := range lv.Items {
+			tmpVal := models.EdbDataList{
+				EdbDataId:     item.EdbDataId,
+				EdbInfoId:     item.EdbInfoId,
+				DataTime:      item.DataTime,
+				DataTimestamp: item.DataTimestamp,
+				Value:         item.Value,
+			}
+			dataTimeT, _ := time.Parse(utils.FormatDate, item.DataTime)
+			if (startTmpT.Before(dataTimeT) && endTmpT.After(dataTimeT)) || startTmpT == dataTimeT || endTmpT == dataTimeT {
+				tmpV := &tmpVal
+				if findVal, ok := quarterMap[name]; !ok {
+					findVal = append(findVal, tmpV)
+					quarterMap[name] = findVal
+				} else {
+					findVal = append(findVal, tmpV)
+					quarterMap[name] = findVal
+				}
+				if lv.Year >= latestDateYear {
+					// 切割的日期时间字符串
+					cuttingDataTimeStr := latestDate.AddDate(0, 0, lv.BetweenDay).Format(utils.FormatDate)
+					if item.DataTime == cuttingDataTimeStr {
+						dateItem.CuttingDataTimestamp = tmpVal.DataTimestamp
+						dataMap[name] = dateItem
+					}
+				}
+			}
+		}
+	}
+
+	for k, v := range dataMap {
+		itemList := quarterMap[k]
+		quarterItem := new(models.QuarterData)
+		quarterItem.Years = v.ShowName
+		quarterItem.ChartLegend = v.ChartLegend
+		if le, ok := legendMap[v.ShowName]; ok {
+			if le != strconv.Itoa(v.StartDate.Year()) && le != strconv.Itoa(v.EndDate.Year()) {
+				quarterItem.ChartLegend = le
+			}
+		}
+		quarterItem.DataList = itemList
+		quarterItem.CuttingDataTimestamp = v.CuttingDataTimestamp
+		//如果等于最后的实际日期,那么将切割时间戳记录
+		if quarterItem.CuttingDataTimestamp == 0 {
+			//如果大于最后的实际日期,那么第一个点就是切割的时间戳
+			if latestDate.Before(v.StartDate) && len(itemList) > 0 {
+				quarterItem.CuttingDataTimestamp = itemList[0].DataTimestamp - 100
+			}
+		}
+		quarterDataList = append(quarterDataList, quarterItem)
+	}
+
+	if result.List[0].Year != calendarPreYear {
+		itemList := make([]*models.EdbDataList, 0)
+		items := new(models.QuarterData)
+		//items.Year = calendarPreYear
+		items.DataList = itemList
+
+		newResult := make([]*models.QuarterData, 0)
+		newResult = append(newResult, items)
+		newResult = append(newResult, quarterDataList...)
+		resultData = newResult
+	} else {
+		resultData = quarterDataList
+	}
+
+	if len(quarterDataList) > 0 {
+		quarterDataListSort = resultData
+		sort.Sort(quarterDataListSort)
+	}
+	return
+}
+
+// BarChartData 柱方图的数据处理
+func BarChartData(mappingList []*models.ChartEdbInfoMapping, edbDataListMap map[int][]*models.EdbDataList, barChartInfoDateList []models.BarChartInfoDateReq, barChartInfoSort models.BarChartInfoSortReq) (edbIdList []int, yDataList []models.YData, err error) {
+	// 指标数据数组(10086:{"2022-12-02":100.01,"2022-12-01":102.3})
+	edbDataMap := make(map[int]map[string]float64)
+	for edbInfoId, edbDataList := range edbDataListMap {
+		edbDateData := make(map[string]float64)
+		for _, edbData := range edbDataList {
+			edbDateData[edbData.DataTime] = edbData.Value
+		}
+		edbDataMap[edbInfoId] = edbDateData
+	}
+
+	// edbIdList 指标展示顺序;x轴的指标顺序
+	edbIdList = make([]int, 0)
+	//Sort int    `description:"排序类型,0:默认,1:升序,2:降序"`
+	dateData := make(map[int]float64)
+	if barChartInfoSort.Sort == 0 {
+		for _, v := range mappingList {
+			edbIdList = append(edbIdList, v.EdbInfoId)
+		}
+	} else {
+		lenBarChartInfoDateList := len(barChartInfoDateList)
+		if barChartInfoSort.DateIndex >= lenBarChartInfoDateList {
+			err = errors.New("排序日期异常")
+			return
+		}
+
+		notDataEdbIdList := make([]int, 0) //没有数据的指标id
+		// 日期配置
+		barChartInfoDate := barChartInfoDateList[barChartInfoSort.DateIndex]
+		for edbInfoId, dataList := range edbDataListMap {
+			if len(dataList) <= 0 {
+				// 没有数据的指标id
+				notDataEdbIdList = append(notDataEdbIdList, edbInfoId)
+				continue
+			}
+			findDate := barChartInfoDate.Date
+			switch barChartInfoDate.Type {
+			case 1: //最新值
+				findDate = dataList[len(dataList)-1].DataTime
+			case 2: //近期几天
+				findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[len(dataList)-1].DataTime, time.Local)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+				findDateTime = findDateTime.AddDate(0, 0, -barChartInfoDate.Value)
+
+				lenData := len(dataList) - 1
+
+				for i := lenData; i >= 0; i-- {
+					currDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[i].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					if currDateTime.Equal(findDateTime) || currDateTime.Before(findDateTime) {
+						findDate = dataList[i].DataTime
+						break
+					}
+				}
+			case 3: // 固定日期
+				//最早的日期
+				minDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[0].DataTime, time.Local)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+				//寻找固定日期的数据
+				findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, barChartInfoDate.Date, time.Local)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+				for tmpDateTime := findDateTime; tmpDateTime.After(minDateTime) || tmpDateTime.Equal(minDateTime); tmpDateTime = tmpDateTime.AddDate(0, 0, -1) {
+					tmpDate := tmpDateTime.Format(utils.FormatDate)
+					if _, ok := edbDataMap[edbInfoId][tmpDate]; ok { //如果能找到数据,那么就返回
+						findDate = tmpDate
+						break
+					}
+				}
+			default:
+				err = errors.New(fmt.Sprint("日期类型异常,Type:", barChartInfoDate.Type))
+				return
+			}
+			if tmpValue, ok := edbDataMap[edbInfoId][findDate]; ok {
+				dateData[edbInfoId] = tmpValue
+			} else {
+				// 没有数据的指标id
+				notDataEdbIdList = append(notDataEdbIdList, edbInfoId)
+			}
+		}
+
+		//Sort int    `description:"排序类型,0:默认,1:升序,2:降序"`
+		// 排序
+		dateDataSort := utils.NewMapSorter(dateData)
+		sort.Sort(dateDataSort)
+		if barChartInfoSort.Sort == 1 {
+			// 先将没有数据的指标id放在最前面
+			if len(notDataEdbIdList) > 0 {
+				edbIdList = append(edbIdList, notDataEdbIdList...)
+			}
+			for _, v := range dateDataSort {
+				edbIdList = append(edbIdList, v.Key)
+			}
+		} else {
+			for i := len(dateDataSort) - 1; i >= 0; i-- {
+				edbIdList = append(edbIdList, dateDataSort[i].Key)
+			}
+			// 再将没有数据的指标id放在最后面
+			if len(notDataEdbIdList) > 0 {
+				edbIdList = append(edbIdList, notDataEdbIdList...)
+			}
+		}
+	}
+
+	yDataList = make([]models.YData, 0) //y轴的数据列表
+
+	for _, barChartInfoDate := range barChartInfoDateList {
+		var maxDate time.Time
+
+		findDataList := make([]float64, 0) // 当前日期的数据值
+		for _, edbInfoId := range edbIdList {
+			findDate := barChartInfoDate.Date     //需要的日期值
+			dataList := edbDataListMap[edbInfoId] //指标的所有数据值
+			if len(dataList) <= 0 {
+				// 没有数据的指标id
+				findDataList = append(findDataList, 0)
+				continue
+			}
+			switch barChartInfoDate.Type {
+			case 1: //最新值
+				dataList := edbDataListMap[edbInfoId]
+				findDate = dataList[len(dataList)-1].DataTime
+			case 2: //近期几天
+				findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[len(dataList)-1].DataTime, time.Local)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+				findDateTime = findDateTime.AddDate(0, 0, -barChartInfoDate.Value)
+
+				lenData := len(dataList) - 1
+				for i := lenData; i >= 0; i-- {
+					currDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[i].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					if currDateTime.Equal(findDateTime) || currDateTime.Before(findDateTime) {
+						findDate = dataList[i].DataTime
+						break
+					}
+				}
+			case 3: // 固定日期
+				//最早的日期
+				minDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[0].DataTime, time.Local)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+				//寻找固定日期的数据
+				findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, barChartInfoDate.Date, time.Local)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+				for tmpDateTime := findDateTime; tmpDateTime.After(minDateTime) || tmpDateTime.Equal(minDateTime); tmpDateTime = tmpDateTime.AddDate(0, 0, -1) {
+					tmpDate := tmpDateTime.Format(utils.FormatDate)
+					if _, ok := edbDataMap[edbInfoId][tmpDate]; ok { //如果能找到数据,那么就返回
+						findDate = tmpDate
+						break
+					}
+				}
+			default:
+				err = errors.New(fmt.Sprint("日期类型异常,Type:", barChartInfoDate.Type))
+				return
+			}
+			findDateTime, _ := time.ParseInLocation(utils.FormatDate, findDate, time.Local)
+			if maxDate.IsZero() {
+				maxDate = findDateTime
+			} else {
+				if findDateTime.After(maxDate) {
+					maxDate = findDateTime
+				}
+			}
+			if tmpValue, ok := edbDataMap[edbInfoId][findDate]; ok {
+				tmpValue, _ = decimal.NewFromFloat(tmpValue).Round(4).Float64()
+				findDataList = append(findDataList, tmpValue)
+			} else {
+				findDataList = append(findDataList, 0)
+			}
+		}
+
+		yDate := "0000-00-00"
+		if !maxDate.IsZero() {
+			yDate = maxDate.Format(utils.FormatDate)
+		}
+		yDataList = append(yDataList, models.YData{
+			Date:  yDate,
+			Value: findDataList,
+			Color: barChartInfoDate.Color,
+			Name:  barChartInfoDate.Name,
+		})
+	}
+
+	return
+}
+
+func CheckIsEnChart(chartNameEn string, edbList []*models.ChartEdbInfoMapping, source, chartType int) bool {
+	// 相关性图表不判断指标
+	if utils.InArrayByInt([]int{utils.CHART_SOURCE_CORRELATION, utils.CHART_SOURCE_ROLLING_CORRELATION, utils.CHART_SOURCE_LINE_EQUATION}, source) && chartNameEn != "" {
+		return true
+	}
+	if chartNameEn != "" && len(edbList) <= 0 {
+		return true
+	}
+	if chartNameEn == "" {
+		return false
+	}
+
+	// 截面散点图的话,肯定是有英文配置的
+	if chartType == utils.CHART_TYPE_SECTION_SCATTER {
+		return true
+	}
+
+	for _, v := range edbList {
+		if v.EdbNameEn == "" {
+			return false
+		}
+		if v.Unit != "无" && v.Unit != "" && v.UnitEn == "" {
+			return false
+		}
+	}
+	return true
+}
+
+func CheckIsEnEdb(edbNameEn, unit, unitEn string) bool {
+	if edbNameEn == "" {
+		return false
+	}
+	if unit != "无" && unit != "" && unitEn == "" {
+		return false
+	}
+	return true
+}
+
+// CheckChartExtraConfig 校验图表额外配置的信息,并且获取相关联的指标id
+func CheckChartExtraConfig(chartType int, extraConfigStr string) (edbIdList []int, err error, errMsg string) {
+	switch chartType {
+	case 10: //截面散点
+		var extraConfig models.SectionScatterReq
+		err = json.Unmarshal([]byte(extraConfigStr), &extraConfig)
+		if err != nil {
+			return
+		}
+
+		// 判断是否有配置日期序列
+		if len(extraConfig.SeriesList) <= 0 {
+			errMsg = `请配置序列`
+			err = errors.New(errMsg)
+			return
+		}
+
+		// 判断是否有填写指标
+		if len(extraConfig.SeriesList[0].EdbInfoList) <= 0 {
+			errMsg = `请选择指标`
+			err = errors.New(errMsg)
+			return
+		}
+
+		// 遍历指标列表获取指标id
+		edbIdMap := make(map[int]int)
+		for _, v := range extraConfig.SeriesList[0].EdbInfoList {
+			// X 轴的指标id
+			if _, ok := edbIdMap[v.XEdbInfoId]; !ok {
+				edbIdMap[v.XEdbInfoId] = v.XEdbInfoId
+				edbIdList = append(edbIdList, v.XEdbInfoId)
+			}
+
+			// Y 轴的指标id
+			if _, ok := edbIdMap[v.YEdbInfoId]; !ok {
+				edbIdMap[v.YEdbInfoId] = v.YEdbInfoId
+				edbIdList = append(edbIdList, v.YEdbInfoId)
+			}
+		}
+	case utils.CHART_TYPE_RADAR:
+		var extraConfig models.RadarChartInfoReq
+		if extraConfigStr == `` {
+			errMsg = "雷达图未配置"
+			err = errors.New(errMsg)
+			return
+		}
+		err = json.Unmarshal([]byte(extraConfigStr), &extraConfig)
+		if err != nil {
+			errMsg = "雷达图配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+	}
+	return
+}
+
+// GetSectionScatterChartData 截面散点图的数据处理
+func GetSectionScatterChartData(chartInfoId int, mappingList []*models.ChartEdbInfoMapping, edbDataListMap map[int][]*models.EdbDataList, extraConfig models.SectionScatterReq) (edbIdList []int, chartDataResp models.SectionScatterInfoResp, err error) {
+	// 指标数据数组(10086:{"2022-12-02":100.01,"2022-12-01":102.3})
+	edbDataMap := make(map[int]map[string]float64)
+	for edbInfoId, edbDataList := range edbDataListMap {
+		edbDateData := make(map[string]float64)
+		for _, edbData := range edbDataList {
+			edbDateData[edbData.DataTime] = edbData.Value
+		}
+		edbDataMap[edbInfoId] = edbDateData
+	}
+
+	// edbIdList 指标展示顺序;x轴的指标顺序
+	edbIdList = make([]int, 0)
+	edbMappingMap := make(map[int]*models.ChartEdbInfoMapping)
+	for _, v := range mappingList {
+		edbIdList = append(edbIdList, v.EdbInfoId)
+		edbMappingMap[v.EdbInfoId] = v
+	}
+	//SectionScatterSeriesInfoResp
+
+	dataListResp := make([]models.SectionScatterSeriesItemResp, 0) //y轴的数据列表
+
+	for _, seriesItem := range extraConfig.SeriesList {
+		var maxDate time.Time
+		// 系列中的指标数据
+		tmpSeriesEdbInfoList := make([]models.SectionScatterEdbItemResp, 0)
+
+		var minXVal, maxXVal, minYVal, maxYVal float64
+		for _, edbConf := range seriesItem.EdbInfoList {
+			tmpItem := models.SectionScatterEdbItemResp{
+				IsShow: edbConf.IsShow,
+				Name:   edbConf.Name,
+				NameEn: edbConf.NameEn,
+			} //单个坐标点的数据
+
+			//X轴的数据
+			{
+				edbInfoId := edbConf.XEdbInfoId //X轴的指标
+				edbMappingInfo, ok := edbMappingMap[edbInfoId]
+				if !ok {
+					continue
+				}
+				findDate := edbConf.XDate             //需要的日期值
+				dataList := edbDataListMap[edbInfoId] //指标的所有数据值
+				if len(dataList) <= 0 {
+					// 没有数据的指标id
+					//findDataList = append(findDataList, 0)
+					continue
+				}
+
+				tmpItem.XEdbInfoId = edbInfoId
+				tmpItem.XName = edbMappingInfo.EdbName
+				tmpItem.XNameEn = edbMappingInfo.EdbNameEn
+
+				switch edbConf.XDateType {
+				case 1: //最新值
+					findDate = dataList[len(dataList)-1].DataTime
+				case 2: //近期几天
+					findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[len(dataList)-1].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					findDateTime = findDateTime.AddDate(0, 0, -edbConf.XDateValue)
+
+					lenData := len(dataList) - 1
+					for i := lenData; i >= 0; i-- {
+						currDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[i].DataTime, time.Local)
+						if tmpErr != nil {
+							err = tmpErr
+							return
+						}
+						if currDateTime.Equal(findDateTime) || currDateTime.Before(findDateTime) {
+							findDate = dataList[i].DataTime
+							break
+						}
+					}
+				case 3: // 固定日期
+					//最早的日期
+					minDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[0].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					//寻找固定日期的数据
+					findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, edbConf.XDate, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					for tmpDateTime := findDateTime; tmpDateTime.After(minDateTime) || tmpDateTime.Equal(minDateTime); tmpDateTime = tmpDateTime.AddDate(0, 0, -1) {
+						tmpDate := tmpDateTime.Format(utils.FormatDate)
+						if _, ok := edbDataMap[edbInfoId][tmpDate]; ok { //如果能找到数据,那么就返回
+							findDate = tmpDate
+							break
+						}
+					}
+				default:
+					err = errors.New(fmt.Sprint("日期类型异常,Type:", edbConf.XDate))
+					return
+				}
+				findDateTime, _ := time.ParseInLocation(utils.FormatDate, findDate, time.Local)
+				if maxDate.IsZero() {
+					maxDate = findDateTime
+				} else {
+					if findDateTime.After(maxDate) {
+						maxDate = findDateTime
+					}
+				}
+				if tmpValue, ok := edbDataMap[edbInfoId][findDate]; ok {
+					tmpItem.XDate = findDate
+					tmpItem.XValue = tmpValue
+				} else {
+					continue
+				}
+			}
+
+			//Y轴的数据
+			{
+				edbInfoId := edbConf.YEdbInfoId //Y轴的指标
+				edbMappingInfo, ok := edbMappingMap[edbInfoId]
+				if !ok {
+					continue
+				}
+				findDate := edbConf.YDate             //需要的日期值
+				dataList := edbDataListMap[edbInfoId] //指标的所有数据值
+				if len(dataList) <= 0 {
+					// 没有数据的指标id
+					//findDataList = append(findDataList, 0)
+					continue
+				}
+
+				tmpItem.YEdbInfoId = edbInfoId
+				tmpItem.YName = edbMappingInfo.EdbName
+				tmpItem.YNameEn = edbMappingInfo.EdbNameEn
+
+				switch edbConf.YDateType {
+				case 1: //最新值
+					findDate = dataList[len(dataList)-1].DataTime
+				case 2: //近期几天
+					findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[len(dataList)-1].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					findDateTime = findDateTime.AddDate(0, 0, -edbConf.YDateValue)
+
+					lenData := len(dataList) - 1
+					for i := lenData; i >= 0; i-- {
+						currDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[i].DataTime, time.Local)
+						if tmpErr != nil {
+							err = tmpErr
+							return
+						}
+						if currDateTime.Equal(findDateTime) || currDateTime.Before(findDateTime) {
+							findDate = dataList[i].DataTime
+							break
+						}
+					}
+				case 3: // 固定日期
+					//最早的日期
+					minDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[0].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					//寻找固定日期的数据
+					findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, edbConf.YDate, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					for tmpDateTime := findDateTime; tmpDateTime.After(minDateTime) || tmpDateTime.Equal(minDateTime); tmpDateTime = tmpDateTime.AddDate(0, 0, -1) {
+						tmpDate := tmpDateTime.Format(utils.FormatDate)
+						if _, ok := edbDataMap[edbInfoId][tmpDate]; ok { //如果能找到数据,那么就返回
+							findDate = tmpDate
+							break
+						}
+					}
+				default:
+					err = errors.New(fmt.Sprint("日期类型异常,Type:", edbConf.YDate))
+					return
+				}
+				findDateTime, _ := time.ParseInLocation(utils.FormatDate, findDate, time.Local)
+				if maxDate.IsZero() {
+					maxDate = findDateTime
+				} else {
+					if findDateTime.After(maxDate) {
+						maxDate = findDateTime
+					}
+				}
+				if tmpValue, ok := edbDataMap[edbInfoId][findDate]; ok {
+					tmpItem.YDate = findDate
+					tmpItem.YValue = tmpValue
+				} else {
+					continue
+				}
+			}
+
+			// 获取当前系列的X轴的最大最小值
+			{
+				if tmpItem.XValue < minXVal {
+					minXVal = tmpItem.XValue
+				}
+				if tmpItem.XValue > maxXVal {
+					maxXVal = tmpItem.XValue
+				}
+				if tmpItem.YValue < minYVal {
+					minYVal = tmpItem.YValue
+				}
+				if tmpItem.YValue > maxYVal {
+					maxYVal = tmpItem.YValue
+				}
+			}
+			tmpSeriesEdbInfoList = append(tmpSeriesEdbInfoList, tmpItem)
+		}
+
+		trendLimitData := make([]models.CoordinatePoint, 0) //趋势线的前后坐标点
+		var trendLine, rSquare string
+		// 生成线性方程式
+		var a, b float64
+		{
+			coordinateData := make([]utils.Coordinate, 0)
+			for _, tmpSeriesEdbInfo := range tmpSeriesEdbInfoList {
+				tmpCoordinate1 := utils.Coordinate{
+					X: tmpSeriesEdbInfo.XValue,
+					Y: tmpSeriesEdbInfo.YValue,
+				}
+				coordinateData = append(coordinateData, tmpCoordinate1)
+			}
+
+			// 只有存在两个坐标点的时候,才能去计算线性方程和R平方
+			if len(coordinateData) >= 2 {
+				a, b = utils.GetLinearResult(coordinateData)
+				if !math.IsNaN(a) && !math.IsNaN(b) {
+					if b > 0 {
+						trendLine = fmt.Sprintf("y=%sx+%s", utils.SubFloatToString(a, 4), utils.SubFloatToString(b, 4))
+					} else {
+						trendLine = fmt.Sprintf("y=%sx%s", utils.SubFloatToString(a, 4), utils.SubFloatToString(b, 4))
+					}
+
+					minYVal, _ = decimal.NewFromFloat(a).Mul(decimal.NewFromFloat(minXVal)).Add(decimal.NewFromFloat(b)).Round(4).Float64()
+					maxYVal, _ = decimal.NewFromFloat(a).Mul(decimal.NewFromFloat(maxXVal)).Add(decimal.NewFromFloat(b)).Round(4).Float64()
+				}
+
+				// 计算R平方
+				rSquare = fmt.Sprint(utils.CalculationDecisive(coordinateData))
+			}
+
+			trendLimitData = append(trendLimitData, models.CoordinatePoint{
+				X: minXVal,
+				Y: minYVal,
+			}, models.CoordinatePoint{
+				X: maxXVal,
+				Y: maxYVal,
+			})
+		}
+
+		dataListResp = append(dataListResp, models.SectionScatterSeriesItemResp{
+			Name:            seriesItem.Name,
+			NameEn:          seriesItem.NameEn,
+			IsNameDefault:   seriesItem.IsNameDefault,
+			Color:           seriesItem.Color,
+			EdbInfoList:     tmpSeriesEdbInfoList,
+			ShowTrendLine:   seriesItem.ShowTrendLine,
+			ShowFitEquation: seriesItem.ShowFitEquation,
+			ShowRSquare:     seriesItem.ShowRSquare,
+			TrendLine:       trendLine,
+			RSquare:         rSquare,
+			TrendLimitData:  trendLimitData,
+		})
+	}
+
+	// 截面散点图点击详情时自动更新系列名
+	if len(extraConfig.SeriesList) > 0 {
+		// 默认名字的时候才自动更新
+		if extraConfig.SeriesList[0].IsNameDefault {
+			firstXEdbInfoId := extraConfig.SeriesList[0].EdbInfoList[0].XEdbInfoId
+			needUpdate := false
+			if v, ok := edbMappingMap[firstXEdbInfoId]; ok {
+				extraConfig.SeriesList[0].Name = v.LatestDate
+				extraConfig.SeriesList[0].NameEn = v.LatestDate
+				dataListResp[0].Name = v.LatestDate
+				dataListResp[0].NameEn = v.LatestDate
+				needUpdate = true
+			}
+
+			extraConfigByte, e := json.Marshal(extraConfig)
+			if e != nil {
+				errMsg := "截面散点系列更新异常"
+				err = errors.New(errMsg)
+				return
+			}
+			extraConfigStr := string(extraConfigByte)
+
+			if needUpdate {
+				err = models.EditChartInfoExtraConfig(chartInfoId, extraConfigStr)
+				if err != nil {
+					errMsg := "截面散点系列更新异常"
+					err = errors.New(errMsg)
+					return
+				}
+			}
+		}
+	}
+
+	chartDataResp = models.SectionScatterInfoResp{
+		XName:       extraConfig.XName,
+		XNameEn:     extraConfig.XNameEn,
+		XUnitName:   extraConfig.XUnitName,
+		XUnitNameEn: extraConfig.XUnitNameEn,
+		YName:       extraConfig.YName,
+		YNameEn:     extraConfig.YNameEn,
+		YUnitName:   extraConfig.YUnitName,
+		YUnitNameEn: extraConfig.YUnitNameEn,
+		XMinValue:   extraConfig.XMinValue,
+		XMaxValue:   extraConfig.XMaxValue,
+		YMinValue:   extraConfig.YMinValue,
+		YMaxValue:   extraConfig.YMaxValue,
+		DataList:    dataListResp,
+	}
+	return
+}
+
+// GetEdbSourceByEdbInfoIdList 获取关联指标的来源
+func GetEdbSourceByEdbInfoIdList(chartEdbInfoMappingList []*models.ChartEdbInfoMapping) (sourceNameList, sourceNameEnList []string) {
+	sourceNameList = make([]string, 0)
+	sourceNameEnList = make([]string, 0)
+	sourceMap := make(map[int]string)
+	for _, v := range chartEdbInfoMappingList {
+		// 指标类型:1:基础指标,2:计算指标
+		if v.EdbType == 2 || v.EdbInfoCategoryType == 1 {
+			//sourceMap[0] = "弘则研究"
+			baseEdbInfoArr, _, _ := GetRefreshEdbInfoFromBase(v.EdbInfoId, v.Source)
+			for _, baseEdbInfo := range baseEdbInfoArr {
+				if baseEdbInfo.EdbInfoType == 0 { //普通指标才参与,预测指标不参与
+					sourceMap[baseEdbInfo.Source] = baseEdbInfo.SourceName
+				}
+			}
+		} else {
+			sourceMap[v.Source] = v.SourceName
+		}
+	}
+
+	for source, sourceName := range sourceMap {
+		if utils.InArrayByInt([]int{utils.DATA_SOURCE_MANUAL, utils.DATA_SOURCE_MYSTEEL_CHEMICAL}, source) {
+			continue
+		}
+		sourceNameList = append(sourceNameList, sourceName)
+
+		sourceNameEn, ok := utils.DataSourceEnMap[source]
+		if !ok {
+			sourceNameEn = sourceName
+		}
+		sourceNameEnList = append(sourceNameEnList, sourceNameEn)
+	}
+	//sourceNameList = append(sourceNameList, utils.ChartDefaultNameCn)
+	//sourceNameEnList = append(sourceNameEnList, utils.ChartDefaultNameEn)
+
+	// 图表来源
+	/*conf, e := models.GetBusinessConf()
+	if e != nil {
+		return
+	}
+	if conf[models.BusinessConfCompanyName] != "" {
+		sourceNameList = append(sourceNameList, conf[models.BusinessConfCompanyName])
+		sourceNameEnList = append(sourceNameEnList, conf[models.BusinessConfCompanyName])
+	}*/
+	return
+}
+
+// RadarChartData 雷达图的数据处理
+func RadarChartData(mappingList []*models.ChartEdbInfoMapping, edbDataListMap map[int][]*models.EdbDataList, extraConfig models.RadarChartInfoReq) (edbIdList []int, chartDataResp models.RadarChartInfoResp, err error) {
+	// 指标数据数组(10086:{"2022-12-02":100.01,"2022-12-01":102.3})
+	edbDataMap := make(map[int]map[string]float64)
+	for edbInfoId, edbDataList := range edbDataListMap {
+		edbDateData := make(map[string]float64)
+		for _, edbData := range edbDataList {
+			edbDateData[edbData.DataTime] = edbData.Value
+		}
+		edbDataMap[edbInfoId] = edbDateData
+	}
+	// edbIdList 指标展示顺序;x轴的指标顺序
+	edbIdList = make([]int, 0)
+	for _, v := range mappingList {
+		edbIdList = append(edbIdList, v.EdbInfoId)
+	}
+	chartDateList := extraConfig.DateList
+
+	yDataList := make([]models.RadarYData, 0) //y轴的数据列表
+
+	for _, chartDate := range chartDateList {
+		var maxDate time.Time
+
+		findDataList := make([]float64, 0) // 当前日期的数据值
+		for _, edbInfoId := range edbIdList {
+			findDate := chartDate.Date            //需要的日期值
+			dataList := edbDataListMap[edbInfoId] //指标的所有数据值
+			if len(dataList) <= 0 {
+				// 没有数据的指标id
+				findDataList = append(findDataList, 0)
+				continue
+			}
+			switch chartDate.Type {
+			case 1: //最新值
+				findDate = dataList[len(dataList)-1].DataTime
+			case 2: //近期几天
+				findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[len(dataList)-1].DataTime, time.Local)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+				findDateTime = findDateTime.AddDate(0, 0, -chartDate.Value)
+
+				lenData := len(dataList) - 1
+				for i := lenData; i >= 0; i-- {
+					currDateTime, e := time.ParseInLocation(utils.FormatDate, dataList[i].DataTime, time.Local)
+					if e != nil {
+						err = e
+						return
+					}
+					if currDateTime.Equal(findDateTime) || currDateTime.Before(findDateTime) {
+						findDate = dataList[i].DataTime
+						break
+					}
+				}
+			case 3: // 固定日期
+				//最早的日期
+				minDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[0].DataTime, time.Local)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+				//寻找固定日期的数据
+				findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, chartDate.Date, time.Local)
+				if tmpErr != nil {
+					err = tmpErr
+					return
+				}
+				for tmpDateTime := findDateTime; tmpDateTime.After(minDateTime) || tmpDateTime.Equal(minDateTime); tmpDateTime = tmpDateTime.AddDate(0, 0, -1) {
+					tmpDate := tmpDateTime.Format(utils.FormatDate)
+					if _, ok := edbDataMap[edbInfoId][tmpDate]; ok { //如果能找到数据,那么就返回
+						findDate = tmpDate
+						break
+					}
+				}
+			default:
+				err = errors.New(fmt.Sprint("日期类型异常,Type:", chartDate.Type))
+				return
+			}
+			findDateTime, _ := time.ParseInLocation(utils.FormatDate, findDate, time.Local)
+			if maxDate.IsZero() {
+				maxDate = findDateTime
+			} else {
+				if findDateTime.After(maxDate) {
+					maxDate = findDateTime
+				}
+			}
+			if tmpValue, ok := edbDataMap[edbInfoId][findDate]; ok {
+				tmpValue, _ = decimal.NewFromFloat(tmpValue).Round(4).Float64()
+				findDataList = append(findDataList, tmpValue)
+			} else {
+				findDataList = append(findDataList, 0)
+			}
+		}
+
+		yDate := "0000-00-00"
+		if !maxDate.IsZero() {
+			yDate = maxDate.Format(utils.FormatDate)
+		}
+		yDataList = append(yDataList, models.RadarYData{
+			Date:  yDate,
+			Value: findDataList,
+			Color: chartDate.Color,
+			Name:  chartDate.Name,
+		})
+	}
+
+	chartDataResp = models.RadarChartInfoResp{
+		YDataList:   yDataList,
+		XEdbIdValue: edbIdList,
+	}
+	return
+}

+ 610 - 0
services/chart_theme.go

@@ -0,0 +1,610 @@
+package services
+
+import (
+	"encoding/json"
+	"errors"
+	"eta/eta_forum_admin/models"
+	"eta/eta_forum_admin/utils"
+	"fmt"
+	"github.com/shopspring/decimal"
+	"math"
+	"time"
+)
+
+// GetThemePreviewChartEdbData 获取图表的指标数据
+func GetThemePreviewChartEdbData(chartType int, calendar, startDate, endDate string, mappingList []*models.ChartEdbInfoMapping, extraConfigStr string, seasonExtraConfig string) (edbList []*models.ChartEdbInfoMapping, xEdbIdValue []int, yDataList []models.YData, dataResp interface{}, err error, errMsg string) {
+	edbList = make([]*models.ChartEdbInfoMapping, 0)
+	xEdbIdValue = make([]int, 0)
+	yDataList = make([]models.YData, 0)
+
+	var extraConfig interface{}
+	switch chartType {
+	case 7: // 柱形图
+		var barConfig models.BarChartInfoReq
+		if extraConfigStr == `` {
+			errMsg = "柱方图未配置"
+			err = errors.New(errMsg)
+			return
+		}
+		err = json.Unmarshal([]byte(extraConfigStr), &barConfig)
+		if err != nil {
+			errMsg = "柱方图配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+		extraConfig = barConfig
+	case 10: // 截面散点图
+		var tmpExtraConfig models.SectionScatterReq
+		if extraConfigStr == `` {
+			errMsg = "截面散点图未配置"
+			err = errors.New(errMsg)
+			return
+		}
+		err = json.Unmarshal([]byte(extraConfigStr), &tmpExtraConfig)
+		if err != nil {
+			errMsg = "截面散点配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+
+		extraConfig = tmpExtraConfig
+	case utils.CHART_TYPE_RADAR:
+		var barConfig models.RadarChartInfoReq
+		if extraConfigStr == `` {
+			errMsg = "雷达图未配置"
+			err = errors.New(errMsg)
+			return
+		}
+		err = json.Unmarshal([]byte(extraConfigStr), &barConfig)
+		if err != nil {
+			errMsg = "雷达图配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+		extraConfig = barConfig
+	default:
+		xEdbIdValue = make([]int, 0)
+		yDataList = make([]models.YData, 0)
+	}
+
+	// 指标对应的所有数据
+	edbDataListMap, edbList, err := getThemePreviewEdbDataMapList(chartType, calendar, startDate, endDate, mappingList, seasonExtraConfig)
+	if err != nil {
+		return
+	}
+
+	// 特殊图形数据处理
+	switch chartType {
+	case 7: // 柱形图
+		barChartConf := extraConfig.(models.BarChartInfoReq)
+		xEdbIdValue, yDataList, err = BarChartData(mappingList, edbDataListMap, barChartConf.DateList, barChartConf.Sort)
+
+		for k := range yDataList {
+			yDataList[k].Unit = barChartConf.Unit
+			yDataList[k].UnitEn = barChartConf.UnitEn
+		}
+
+		for _, v := range edbList {
+			// 指标别名
+			if barChartConf.EdbInfoIdList != nil && len(barChartConf.EdbInfoIdList) > 0 {
+				for _, reqEdb := range barChartConf.EdbInfoIdList {
+					if v.EdbInfoId == reqEdb.EdbInfoId {
+						v.EdbAliasName = reqEdb.Name
+					}
+				}
+			}
+		}
+	case 10: // 截面散点图
+		sectionScatterConf := extraConfig.(models.SectionScatterReq)
+		xEdbIdValue, dataResp, err = getThemePreviewSectionScatterChartData(mappingList, edbDataListMap, sectionScatterConf)
+
+		var tmpExtraConfig models.SectionScatterReq
+		if extraConfigStr == `` {
+			errMsg = "截面散点图未配置"
+			err = errors.New(errMsg)
+			return
+		}
+		err = json.Unmarshal([]byte(extraConfigStr), &tmpExtraConfig)
+		if err != nil {
+			errMsg = "截面散点配置异常"
+			err = errors.New(errMsg)
+			return
+		}
+
+		// 这个数据没有必要返回给前端
+		for _, v := range edbList {
+			v.DataList = nil
+		}
+
+	case utils.CHART_TYPE_RADAR: //雷达图
+		radarConf := extraConfig.(models.RadarChartInfoReq)
+		xEdbIdValue, dataResp, err = RadarChartData(mappingList, edbDataListMap, radarConf)
+	}
+	return
+}
+
+func getThemePreviewEdbDataMapList(chartType int, calendar, startDate, endDate string, mappingList []*models.ChartEdbInfoMapping, seasonExtraConfig string) (edbDataListMap map[int][]*models.EdbDataList, edbList []*models.ChartEdbInfoMapping, err error) {
+	// 指标对应的所有数据
+	edbDataListMap = make(map[int][]*models.EdbDataList)
+
+	for _, v := range mappingList {
+		//fmt.Println("v:", v.EdbInfoId)
+		item := new(models.ChartEdbInfoMapping)
+		item.EdbInfoId = v.EdbInfoId
+		item.SourceName = v.SourceName
+		item.Source = v.Source
+		item.EdbCode = v.EdbCode
+		item.EdbName = v.EdbName
+		item.EdbNameEn = v.EdbNameEn
+		item.Frequency = v.Frequency
+		item.EdbType = v.EdbType
+		item.FrequencyEn = GetFrequencyEn(v.Frequency)
+		if v.Unit != `无` {
+			item.Unit = v.Unit
+		}
+		item.UnitEn = v.UnitEn
+		item.StartDate = v.StartDate
+		item.EndDate = v.EndDate
+		item.ModifyTime = v.ModifyTime
+		item.EdbInfoCategoryType = v.EdbInfoCategoryType
+		item.PredictChartColor = v.PredictChartColor
+		item.ClassifyId = v.ClassifyId
+		item.IsAxis = 1
+		item.EdbInfoType = 1
+		item.LeadValue = v.LeadValue
+		item.LeadUnit = v.LeadUnit
+		item.LeadUnitEn = GetLeadUnitEn(v.LeadUnit)
+		item.ChartEdbMappingId = v.ChartEdbMappingId
+		item.ChartInfoId = v.ChartInfoId
+		item.ChartStyle = v.ChartStyle
+		item.ChartColor = v.ChartColor
+		item.ChartWidth = 0
+		item.IsOrder = false
+		item.MaxData = v.MaxValue
+		item.MinData = v.MinValue
+		item.LatestValue = v.LatestValue
+		item.LatestDate = v.LatestDate
+		item.UniqueCode = v.UniqueCode
+		item.MoveLatestDate = v.LatestDate
+
+		var startDateReal string
+		var diffSeconds int64
+		if chartType == 2 { //季节性图
+			startDateReal = startDate
+		} else {
+			if v.EdbInfoType == 0 && v.LeadUnit != "" && v.LeadValue > 0 { //领先指标
+				var startTimeRealTemp time.Time
+				startDateParse, _ := time.Parse(utils.FormatDate, startDate)
+				switch v.LeadUnit {
+				case "天":
+					startTimeRealTemp = startDateParse.AddDate(0, 0, -v.LeadValue)
+				case "月":
+					startTimeRealTemp = startDateParse.AddDate(0, -v.LeadValue, 0)
+				case "季":
+					startTimeRealTemp = startDateParse.AddDate(0, -3*v.LeadValue, 0)
+				case "周":
+					startTimeRealTemp = startDateParse.AddDate(0, 0, -7*v.LeadValue)
+				case "年":
+					startTimeRealTemp = startDateParse.AddDate(-v.LeadValue, 0, 0)
+				}
+				if startTimeRealTemp.Before(startDateParse) {
+					startDateReal = startTimeRealTemp.Format(utils.FormatDate)
+					diffSeconds = (int64(startTimeRealTemp.UnixNano()) - int64(startDateParse.UnixNano())) / 1e6
+				} else {
+					startDateReal = startDate
+					diffSeconds = 0
+				}
+
+				// 预测指标的开始日期也要偏移
+				{
+					day, tmpErr := utils.GetDaysBetween2Date(utils.FormatDate, startDate, startDateReal)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					moveLatestDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, item.MoveLatestDate, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					item.MoveLatestDate = moveLatestDateTime.AddDate(0, 0, day).Format(utils.FormatDate)
+				}
+			} else {
+				startDateReal = startDate
+			}
+		}
+
+		dataList := make([]*models.EdbDataList, 0)
+		dataList, err = models.GetChartThemeDefaultDataItemList(v.EdbInfoId, startDateReal)
+		if err != nil {
+			return
+		}
+		edbDataListMap[v.EdbInfoId] = dataList
+
+		if diffSeconds != 0 && v.EdbInfoType == 0 {
+			dataListLen := len(dataList)
+			for i := 0; i < dataListLen; i++ {
+				dataList[i].DataTimestamp = dataList[i].DataTimestamp - diffSeconds
+			}
+		}
+
+		if chartType == 2 {
+			latestDate, tmpErr := time.Parse(utils.FormatDate, v.LatestDate)
+			if tmpErr != nil {
+				//item.DataList = dataList
+				item.IsNullData = true
+				edbList = append(edbList, item)
+				continue
+			}
+
+			quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
+			if tErr != nil {
+				err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
+				return
+			}
+			item.DataList = quarterDataList
+		} else if chartType == 7 || chartType == utils.CHART_TYPE_RADAR { //柱方图
+			//item.DataList = dataList
+		} else {
+			item.DataList = dataList
+		}
+		edbList = append(edbList, item)
+	}
+
+	return
+}
+
+func getThemePreviewSectionScatterChartData(mappingList []*models.ChartEdbInfoMapping, edbDataListMap map[int][]*models.EdbDataList, extraConfig models.SectionScatterReq) (edbIdList []int, chartDataResp models.SectionScatterInfoResp, err error) {
+	// 指标数据数组(10086:{"2022-12-02":100.01,"2022-12-01":102.3})
+	edbDataMap := make(map[int]map[string]float64)
+	for edbInfoId, edbDataList := range edbDataListMap {
+		edbDateData := make(map[string]float64)
+		for _, edbData := range edbDataList {
+			edbDateData[edbData.DataTime] = edbData.Value
+		}
+		edbDataMap[edbInfoId] = edbDateData
+	}
+
+	// edbIdList 指标展示顺序;x轴的指标顺序
+	edbIdList = make([]int, 0)
+	edbMappingMap := make(map[int]*models.ChartEdbInfoMapping)
+	for _, v := range mappingList {
+		edbIdList = append(edbIdList, v.EdbInfoId)
+		edbMappingMap[v.EdbInfoId] = v
+	}
+	//SectionScatterSeriesInfoResp
+
+	dataListResp := make([]models.SectionScatterSeriesItemResp, 0) //y轴的数据列表
+
+	for _, seriesItem := range extraConfig.SeriesList {
+		var maxDate time.Time
+		// 系列中的指标数据
+		tmpSeriesEdbInfoList := make([]models.SectionScatterEdbItemResp, 0)
+
+		var minXVal, maxXVal, minYVal, maxYVal float64
+		for _, edbConf := range seriesItem.EdbInfoList {
+			tmpItem := models.SectionScatterEdbItemResp{
+				IsShow: edbConf.IsShow,
+				Name:   edbConf.Name,
+				NameEn: edbConf.NameEn,
+			} //单个坐标点的数据
+
+			//X轴的数据
+			{
+				edbInfoId := edbConf.XEdbInfoId //X轴的指标
+				edbMappingInfo, ok := edbMappingMap[edbInfoId]
+				if !ok {
+					continue
+				}
+				findDate := edbConf.XDate             //需要的日期值
+				dataList := edbDataListMap[edbInfoId] //指标的所有数据值
+				if len(dataList) <= 0 {
+					// 没有数据的指标id
+					//findDataList = append(findDataList, 0)
+					continue
+				}
+
+				tmpItem.XEdbInfoId = edbInfoId
+				tmpItem.XName = edbMappingInfo.EdbName
+				tmpItem.XNameEn = edbMappingInfo.EdbNameEn
+
+				switch edbConf.XDateType {
+				case 1: //最新值
+					findDate = dataList[len(dataList)-1].DataTime
+				case 2: //近期几天
+					findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[len(dataList)-1].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					findDateTime = findDateTime.AddDate(0, 0, -edbConf.XDateValue)
+
+					lenData := len(dataList) - 1
+					for i := lenData; i >= 0; i-- {
+						currDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[i].DataTime, time.Local)
+						if tmpErr != nil {
+							err = tmpErr
+							return
+						}
+						if currDateTime.Equal(findDateTime) || currDateTime.Before(findDateTime) {
+							findDate = dataList[i].DataTime
+							break
+						}
+					}
+				case 3: // 固定日期
+					//最早的日期
+					minDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[0].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					//寻找固定日期的数据
+					findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, edbConf.XDate, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					for tmpDateTime := findDateTime; tmpDateTime.After(minDateTime) || tmpDateTime.Equal(minDateTime); tmpDateTime = tmpDateTime.AddDate(0, 0, -1) {
+						tmpDate := tmpDateTime.Format(utils.FormatDate)
+						if _, ok := edbDataMap[edbInfoId][tmpDate]; ok { //如果能找到数据,那么就返回
+							findDate = tmpDate
+							break
+						}
+					}
+				default:
+					err = errors.New(fmt.Sprint("日期类型异常,Type:", edbConf.XDate))
+					return
+				}
+				findDateTime, _ := time.ParseInLocation(utils.FormatDate, findDate, time.Local)
+				if maxDate.IsZero() {
+					maxDate = findDateTime
+				} else {
+					if findDateTime.After(maxDate) {
+						maxDate = findDateTime
+					}
+				}
+				if tmpValue, ok := edbDataMap[edbInfoId][findDate]; ok {
+					tmpItem.XDate = findDate
+					tmpItem.XValue = tmpValue
+				} else {
+					continue
+				}
+			}
+
+			//Y轴的数据
+			{
+				edbInfoId := edbConf.YEdbInfoId //Y轴的指标
+				edbMappingInfo, ok := edbMappingMap[edbInfoId]
+				if !ok {
+					continue
+				}
+				findDate := edbConf.YDate             //需要的日期值
+				dataList := edbDataListMap[edbInfoId] //指标的所有数据值
+				if len(dataList) <= 0 {
+					// 没有数据的指标id
+					//findDataList = append(findDataList, 0)
+					continue
+				}
+
+				tmpItem.YEdbInfoId = edbInfoId
+				tmpItem.YName = edbMappingInfo.EdbName
+				tmpItem.YNameEn = edbMappingInfo.EdbNameEn
+
+				switch edbConf.YDateType {
+				case 1: //最新值
+					findDate = dataList[len(dataList)-1].DataTime
+				case 2: //近期几天
+					findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[len(dataList)-1].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					findDateTime = findDateTime.AddDate(0, 0, -edbConf.YDateValue)
+
+					lenData := len(dataList) - 1
+					for i := lenData; i >= 0; i-- {
+						currDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[i].DataTime, time.Local)
+						if tmpErr != nil {
+							err = tmpErr
+							return
+						}
+						if currDateTime.Equal(findDateTime) || currDateTime.Before(findDateTime) {
+							findDate = dataList[i].DataTime
+							break
+						}
+					}
+				case 3: // 固定日期
+					//最早的日期
+					minDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, dataList[0].DataTime, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					//寻找固定日期的数据
+					findDateTime, tmpErr := time.ParseInLocation(utils.FormatDate, edbConf.YDate, time.Local)
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					for tmpDateTime := findDateTime; tmpDateTime.After(minDateTime) || tmpDateTime.Equal(minDateTime); tmpDateTime = tmpDateTime.AddDate(0, 0, -1) {
+						tmpDate := tmpDateTime.Format(utils.FormatDate)
+						if _, ok := edbDataMap[edbInfoId][tmpDate]; ok { //如果能找到数据,那么就返回
+							findDate = tmpDate
+							break
+						}
+					}
+				default:
+					err = errors.New(fmt.Sprint("日期类型异常,Type:", edbConf.YDate))
+					return
+				}
+				findDateTime, _ := time.ParseInLocation(utils.FormatDate, findDate, time.Local)
+				if maxDate.IsZero() {
+					maxDate = findDateTime
+				} else {
+					if findDateTime.After(maxDate) {
+						maxDate = findDateTime
+					}
+				}
+				if tmpValue, ok := edbDataMap[edbInfoId][findDate]; ok {
+					tmpItem.YDate = findDate
+					tmpItem.YValue = tmpValue
+				} else {
+					continue
+				}
+			}
+
+			// 获取当前系列的X轴的最大最小值
+			{
+				if tmpItem.XValue < minXVal {
+					minXVal = tmpItem.XValue
+				}
+				if tmpItem.XValue > maxXVal {
+					maxXVal = tmpItem.XValue
+				}
+				if tmpItem.YValue < minYVal {
+					minYVal = tmpItem.YValue
+				}
+				if tmpItem.YValue > maxYVal {
+					maxYVal = tmpItem.YValue
+				}
+			}
+			tmpSeriesEdbInfoList = append(tmpSeriesEdbInfoList, tmpItem)
+		}
+
+		trendLimitData := make([]models.CoordinatePoint, 0) //趋势线的前后坐标点
+		var trendLine, rSquare string
+		// 生成线性方程式
+		var a, b float64
+		{
+			coordinateData := make([]utils.Coordinate, 0)
+			for _, tmpSeriesEdbInfo := range tmpSeriesEdbInfoList {
+				tmpCoordinate1 := utils.Coordinate{
+					X: tmpSeriesEdbInfo.XValue,
+					Y: tmpSeriesEdbInfo.YValue,
+				}
+				coordinateData = append(coordinateData, tmpCoordinate1)
+			}
+
+			// 只有存在两个坐标点的时候,才能去计算线性方程和R平方
+			if len(coordinateData) >= 2 {
+				a, b = utils.GetLinearResult(coordinateData)
+				if !math.IsNaN(a) && !math.IsNaN(b) {
+					if b > 0 {
+						trendLine = fmt.Sprintf("y=%sx+%s", utils.SubFloatToString(a, 4), utils.SubFloatToString(b, 4))
+					} else {
+						trendLine = fmt.Sprintf("y=%sx%s", utils.SubFloatToString(a, 4), utils.SubFloatToString(b, 4))
+					}
+
+					minYVal, _ = decimal.NewFromFloat(a).Mul(decimal.NewFromFloat(minXVal)).Add(decimal.NewFromFloat(b)).Round(4).Float64()
+					maxYVal, _ = decimal.NewFromFloat(a).Mul(decimal.NewFromFloat(maxXVal)).Add(decimal.NewFromFloat(b)).Round(4).Float64()
+				}
+
+				// 计算R平方
+				rSquare = fmt.Sprint(utils.CalculationDecisive(coordinateData))
+			}
+
+			trendLimitData = append(trendLimitData, models.CoordinatePoint{
+				X: minXVal,
+				Y: minYVal,
+			}, models.CoordinatePoint{
+				X: maxXVal,
+				Y: maxYVal,
+			})
+		}
+
+		dataListResp = append(dataListResp, models.SectionScatterSeriesItemResp{
+			Name:            seriesItem.Name,
+			NameEn:          seriesItem.NameEn,
+			IsNameDefault:   seriesItem.IsNameDefault,
+			Color:           seriesItem.Color,
+			EdbInfoList:     tmpSeriesEdbInfoList,
+			ShowTrendLine:   seriesItem.ShowTrendLine,
+			ShowFitEquation: seriesItem.ShowFitEquation,
+			ShowRSquare:     seriesItem.ShowRSquare,
+			TrendLine:       trendLine,
+			RSquare:         rSquare,
+			TrendLimitData:  trendLimitData,
+		})
+	}
+
+	// 截面散点图点击详情时自动更新系列名
+	if len(extraConfig.SeriesList) > 0 {
+		// 默认名字的时候才自动更新
+		if extraConfig.SeriesList[0].IsNameDefault {
+			firstXEdbInfoId := extraConfig.SeriesList[0].EdbInfoList[0].XEdbInfoId
+			if v, ok := edbMappingMap[firstXEdbInfoId]; ok {
+				extraConfig.SeriesList[0].Name = v.LatestDate
+				extraConfig.SeriesList[0].NameEn = v.LatestDate
+				dataListResp[0].Name = v.LatestDate
+				dataListResp[0].NameEn = v.LatestDate
+			}
+
+		}
+	}
+
+	chartDataResp = models.SectionScatterInfoResp{
+		XName:       extraConfig.XName,
+		XNameEn:     extraConfig.XNameEn,
+		XUnitName:   extraConfig.XUnitName,
+		XUnitNameEn: extraConfig.XUnitNameEn,
+		YName:       extraConfig.YName,
+		YNameEn:     extraConfig.YNameEn,
+		YUnitName:   extraConfig.YUnitName,
+		YUnitNameEn: extraConfig.YUnitNameEn,
+		XMinValue:   extraConfig.XMinValue,
+		XMaxValue:   extraConfig.XMaxValue,
+		YMinValue:   extraConfig.YMinValue,
+		YMaxValue:   extraConfig.YMaxValue,
+		DataList:    dataListResp,
+	}
+	return
+}
+
+// GetChartThemeConfig
+// @Description: 根据主题id获取主题信息,如果获取不到的话,那么就获取默认的主题
+// @author: Roc
+// @datetime 2023-12-19 14:31:17
+// @param chartThemeId int
+// @param chartType int
+// @param source int
+// @return chartTheme *models.ChartTheme
+// @return err error
+func GetChartThemeConfig(chartThemeId, source, chartType int) (chartTheme *models.ChartTheme, err error) {
+	chartTheme, err = models.GetChartThemeId(chartThemeId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		return
+	}
+
+	err = nil
+
+	// 如果找到了,那么就返回
+	if chartTheme != nil {
+		return
+	}
+
+	// 没有找到的话,那么就找默认的主题
+
+	// 查找主题类型id
+	chartThemeType, err := models.GetChartThemeTypeByChartTypeAndSource(chartType, source)
+	if err != nil {
+		return
+	}
+
+	// 寻找默认的主题id
+	chartTheme, err = models.GetChartThemeId(chartThemeType.DefaultChartThemeId)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		return
+	}
+
+	err = nil
+
+	// 如果找到了,那么就返回
+	if chartTheme != nil {
+		return
+	}
+
+	// 如果还是没找到,那就系统的主题id
+	chartTheme, err = models.GetSystemChartTheme(chartThemeType.ChartThemeTypeId)
+
+	return
+}

+ 25 - 0
services/edb_data.go

@@ -4,6 +4,7 @@ import (
 	"eta/eta_forum_admin/models"
 	"eta/eta_forum_admin/models/mgodb"
 	"fmt"
+	"strconv"
 )
 
 func BatchAddOrUpdateEdbData(req []*models.AddEdbDataReq) (err error) {
@@ -46,3 +47,27 @@ func AddOrUpdateEdbData(edbCode string, dataList []*mgodb.EdbDataBase) (err erro
 	}
 	return
 }
+
+// GetEdbDataList 获取指标的数据(日期正序返回)
+func GetEdbDataList(endInfoId int, startDate, endDate string) (list []*models.EdbDataList, err error) {
+	dataList, err := mgodb.GetEdbDataList(endInfoId, startDate, endDate)
+	if err != nil {
+		err = fmt.Errorf("查询指标数据出错 error, %v", err)
+		return
+	}
+	list = make([]*models.EdbDataList, 0)
+	for _, v := range dataList {
+		// 字符串转成浮点数
+		f, _ := strconv.ParseFloat(v.Value, 64)
+		list = append(list, &models.EdbDataList{
+			EdbDataId:     0,
+			EdbInfoId:     v.EdbInfoId,
+			DataTime:      v.DataTime,
+			DataTimestamp: v.DataTimestamp,
+			Value:         f,
+		},
+		)
+	}
+
+	return
+}

+ 477 - 0
services/edb_info.go

@@ -1,11 +1,14 @@
 package services
 
 import (
+	"encoding/json"
 	"errors"
 	"eta/eta_forum_admin/models"
 	"eta/eta_forum_admin/models/mgodb"
 	"eta/eta_forum_admin/utils"
+	"fmt"
 	"strconv"
+	"strings"
 	"time"
 )
 
@@ -229,3 +232,477 @@ func DeleteEdbInfo(edbInfoId int) (err error, errMsg string) {
 	}
 	return
 }
+
+// TraceEdbInfoByEdbInfoId 指标追溯
+func TraceEdbInfoByEdbInfoId(edbInfoId, sysUserId int) (traceEdbInfo models.TraceEdbInfoResp, err error) {
+	edbInfo, err := models.GetEdbInfoById(edbInfoId)
+	if err != nil {
+		return
+	}
+	edbInfoRuleMap := make(map[int]string, 0)
+	edbMappingMap := make(map[int][]*models.EdbInfoCalculateMappingInfo)
+	//edbInfoRuleMap[edbInfoId] = getEdbRuleTitle(edbInfo)
+	traceEdbInfo = models.TraceEdbInfoResp{
+		//EdbInfoId: edbInfo.EdbInfoId,
+		EdbInfoId:   edbInfoId,
+		EdbInfoType: edbInfo.EdbInfoType,
+		EdbName:     edbInfo.EdbName,
+		EdbType:     edbInfo.EdbType,
+		//Source:      edbInfo.Source,
+		UniqueCode: edbInfo.UniqueCode,
+		ClassifyId: edbInfo.ClassifyId,
+		IsStop:     edbInfo.NoUpdate,
+		EdbInfo:    edbInfo,
+	}
+	findIdMap := make(map[int]int)
+	findIdMap[edbInfoId] = edbInfoId
+	existMap := make(map[int]models.TraceEdbInfoResp)
+	traceEdbInfo.Child, err = traceEdbInfoByEdbInfoId(edbInfoId, traceEdbInfo, edbInfoRuleMap, findIdMap, existMap, edbMappingMap)
+
+	edbInfoIdList := make([]int, 0)
+	for _, v := range findIdMap {
+		edbInfoIdList = append(edbInfoIdList, v)
+	}
+	classifyIdList := make([]int, 0)
+	edbInfoList, err := models.GetEdbInfoByIdList(edbInfoIdList)
+	if err != nil {
+		return
+	}
+	edbInfoMap := make(map[int]*models.EdbInfo)
+	for _, tmpEdbInfo := range edbInfoList {
+		edbInfoMap[tmpEdbInfo.EdbInfoId] = tmpEdbInfo
+		classifyIdList = append(classifyIdList, tmpEdbInfo.ClassifyId)
+	}
+
+	traceEdbInfo, err = handleTraceEdbInfo(traceEdbInfo, 0, edbInfoMap, edbMappingMap)
+
+	// 权限校验
+
+	return
+}
+
+// TraceEdbInfoByEdbInfoIdList 指标追溯
+func TraceEdbInfoByEdbInfoIdList(edbInfoIdList []int) (traceEdbInfoList []models.TraceEdbInfoResp, err error) {
+	traceEdbInfoList = make([]models.TraceEdbInfoResp, 0)
+	edbInfoList, err := models.GetEdbInfoByIdList(edbInfoIdList)
+	if err != nil {
+		return
+	}
+	edbInfoRuleMap := make(map[int]string, 0)
+	edbMappingMap := make(map[int][]*models.EdbInfoCalculateMappingInfo)
+
+	findIdMap := make(map[int]int)
+	existMap := make(map[int]models.TraceEdbInfoResp)
+
+	for _, edbInfo := range edbInfoList {
+		findIdMap[edbInfo.EdbInfoId] = edbInfo.EdbInfoId
+		//edbInfoRuleMap[edbInfoId] = getEdbRuleTitle(edbInfo)
+		traceEdbInfo := models.TraceEdbInfoResp{
+			//EdbInfoId: edbInfo.EdbInfoId,
+			EdbInfoId:   edbInfo.EdbInfoId,
+			EdbInfoType: edbInfo.EdbInfoType,
+			EdbName:     edbInfo.EdbName,
+			EdbType:     edbInfo.EdbType,
+			//Source:      edbInfo.Source,
+			UniqueCode: edbInfo.UniqueCode,
+			ClassifyId: edbInfo.ClassifyId,
+			IsStop:     edbInfo.NoUpdate,
+			EdbInfo:    edbInfo,
+		}
+		traceEdbInfo.Child, err = traceEdbInfoByEdbInfoId(edbInfo.EdbInfoId, traceEdbInfo, edbInfoRuleMap, findIdMap, existMap, edbMappingMap)
+		traceEdbInfoList = append(traceEdbInfoList, traceEdbInfo)
+	}
+
+	//findEdbInfoIdList := make([]int, 0)
+	//for _, v := range findIdMap {
+	//	findEdbInfoIdList = append(findEdbInfoIdList, v)
+	//}
+	//findEdbInfoList, err := models.GetEdbInfoByIdList(findEdbInfoIdList)
+	//if err != nil {
+	//	return
+	//}
+	//edbInfoMap := make(map[int]*models.EdbInfo)
+	//for _, tmpEdbInfo := range findEdbInfoList {
+	//	edbInfoMap[tmpEdbInfo.EdbInfoId] = tmpEdbInfo
+	//}
+	//for k, traceEdbInfo := range traceEdbInfoList {
+	//	traceEdbInfoList[k], err = handleTraceEdbInfo(traceEdbInfo, 0, edbInfoMap, edbMappingMap)
+	//}
+	return
+}
+
+// traceEdbInfoByEdbInfoId 指标追溯
+func traceEdbInfoByEdbInfoId(edbInfoId int, traceEdbInfo models.TraceEdbInfoResp, edbInfoRuleMap map[int]string, findIdMap map[int]int, existMap map[int]models.TraceEdbInfoResp, edbMappingMap map[int][]*models.EdbInfoCalculateMappingInfo) (child []models.TraceEdbInfoResp, err error) {
+	traceEdbInfo, ok := existMap[edbInfoId]
+	if ok {
+		return
+	}
+	child = make([]models.TraceEdbInfoResp, 0)
+
+	edbInfoMappingList, e := models.GetEdbInfoCalculateMappingListByEdbInfoId(edbInfoId)
+	if e != nil {
+		err = fmt.Errorf("GetEdbInfoCalculateMappingListByEdbInfoId err: %s", e.Error())
+		return
+	}
+
+	// 指标信息map
+	edbInfoMap := make(map[int]*models.EdbInfo)
+	if len(edbInfoMappingList) > 0 {
+		fromEdbInfoIdList := make([]int, 0)
+		for _, v := range edbInfoMappingList {
+			fromEdbInfoIdList = append(fromEdbInfoIdList, v.FromEdbInfoId)
+		}
+		edbInfoList, tmpErr := models.GetEdbInfoByIdList(fromEdbInfoIdList)
+		if tmpErr != nil {
+			err = fmt.Errorf("traceEdbInfoByEdbInfoId GetEdbInfoByIdList err: %s", tmpErr.Error())
+			return
+		}
+		for _, v := range edbInfoList {
+			edbInfoMap[v.EdbInfoId] = v
+		}
+
+	}
+
+	edbMappingMap[edbInfoId] = edbInfoMappingList
+	for _, v := range edbInfoMappingList {
+		tmpEdbInfoId := v.FromEdbInfoId
+		tmpTraceEdbInfo := models.TraceEdbInfoResp{
+			EdbInfoId:   tmpEdbInfoId,
+			EdbInfoType: v.FromEdbInfoType,
+			EdbType:     v.FromEdbType,
+			UniqueCode:  v.FromUniqueCode,
+			ClassifyId:  v.FromClassifyId,
+			IsStop:      v.NoUpdate,
+			EdbInfo:     edbInfoMap[v.FromEdbInfoId],
+		}
+
+		// 计算指标/预测指标继续溯源
+		if edbInfoId != v.FromEdbInfoId && (v.FromEdbType == 2 || v.FromEdbInfoType == 1) {
+			// 查过了就不查了
+			if _, ok2 := findIdMap[tmpEdbInfoId]; !ok2 {
+				tmpTraceEdbInfo.Child, e = traceEdbInfoByEdbInfoId(tmpEdbInfoId, tmpTraceEdbInfo, edbInfoRuleMap, findIdMap, existMap, edbMappingMap)
+				if e != nil {
+					err = fmt.Errorf("traceEdbInfoByEdbInfoId err: %s", e.Error())
+					return
+				}
+			}
+		}
+		child = append(child, tmpTraceEdbInfo)
+		findIdMap[tmpEdbInfoId] = tmpEdbInfoId
+	}
+	existMap[edbInfoId] = traceEdbInfo
+	return
+}
+
+func handleTraceEdbInfo(traceEdbInfoResp models.TraceEdbInfoResp, parentEdbInfoId int, edbInfoMap map[int]*models.EdbInfo, edbMappingMap map[int][]*models.EdbInfoCalculateMappingInfo) (newTraceEdbInfoResp models.TraceEdbInfoResp, err error) {
+	edbInfo, ok := edbInfoMap[traceEdbInfoResp.EdbInfoId]
+	if !ok {
+		err = errors.New("指标异常")
+		return
+	}
+
+	var parentEdbInfo *models.EdbInfo
+	if parentEdbInfoId > 0 {
+		parentEdbInfo, ok = edbInfoMap[parentEdbInfoId]
+		if !ok {
+			err = errors.New("指标异常")
+			return
+		}
+	}
+
+	//traceEdbInfoResp.EdbName = edbInfo.EdbName
+	traceEdbInfoResp.EdbName, traceEdbInfoResp.RuleTitle = getEdbRuleTitle(edbInfo, parentEdbInfo, traceEdbInfoResp.Child, edbInfoMap, edbMappingMap)
+
+	if traceEdbInfoResp.Child != nil && len(traceEdbInfoResp.Child) > 0 {
+		for k, v := range traceEdbInfoResp.Child {
+			traceEdbInfoResp.Child[k], err = handleTraceEdbInfo(v, traceEdbInfoResp.EdbInfoId, edbInfoMap, edbMappingMap)
+			if err != nil {
+				return
+			}
+		}
+	}
+
+	newTraceEdbInfoResp = traceEdbInfoResp
+
+	return
+}
+
+// 获取频度的英文版
+func GetFrequencyEn(frequency string) (frequencyEn string) {
+	switch frequency {
+	case "日度":
+		frequencyEn = "day"
+		return
+	case "周度":
+		frequencyEn = "week"
+		return
+	case "旬度":
+		frequencyEn = "ten days"
+		return
+	case "月度":
+		frequencyEn = "month"
+		return
+	case "季度":
+		frequencyEn = "quarter"
+		return
+	case "年度":
+		frequencyEn = "year"
+		return
+	}
+	return
+}
+
+func GetLeadUnitEn(unit string) (unitEn string) {
+	switch unit {
+	case "天":
+		unitEn = "day"
+		return
+	case "周":
+		unitEn = "week"
+		return
+	case "月":
+		unitEn = "month"
+		return
+	case "季":
+		unitEn = "quarter"
+		return
+	case "年":
+		unitEn = "year"
+		return
+	}
+	return
+}
+
+func GetRefreshEdbInfoFromBase(edbInfoId, source int) (baseEdbInfoArr, calculateInfoArr []*models.EdbInfo, err error) {
+	calculateList, err := models.GetEdbInfoCalculateMap(edbInfoId, source)
+	if err != nil && err.Error() != utils.ErrNoRow() {
+		return
+	}
+	for _, item := range calculateList {
+		if item.EdbInfoId == edbInfoId { //	如果查出来关联的指标就是自己的话,那么就过滤
+			continue
+		}
+		if item.EdbType == 1 {
+			baseEdbInfoArr = append(baseEdbInfoArr, item)
+		} else {
+			calculateInfoArr = append(calculateInfoArr, item)
+			newBaseEdbInfoArr, newCalculateInfoArr, _ := GetRefreshEdbInfoFromBase(item.EdbInfoId, item.Source)
+			baseEdbInfoArr = append(baseEdbInfoArr, newBaseEdbInfoArr...)
+			calculateInfoArr = append(calculateInfoArr, newCalculateInfoArr...)
+		}
+	}
+	return
+}
+
+// getEdbRule 获取规则名称
+func getEdbRuleTitle(edbInfo, parentEdbInfo *models.EdbInfo, childList []models.TraceEdbInfoResp, edbInfoMap map[int]*models.EdbInfo, edbMappingMap map[int][]*models.EdbInfoCalculateMappingInfo) (edbName, ruleTitle string) {
+	edbName = edbInfo.EdbName
+	ruleTitle = `来源于` + edbInfo.SourceName
+
+	if parentEdbInfo != nil {
+		edbMappingList, ok := edbMappingMap[parentEdbInfo.EdbInfoId]
+		if !ok {
+			edbMappingList = []*models.EdbInfoCalculateMappingInfo{}
+		}
+		// 指标名称
+		switch parentEdbInfo.Source {
+		case utils.DATA_SOURCE_CALCULATE, utils.DATA_SOURCE_PREDICT_CALCULATE:
+			for _, v := range edbMappingList {
+				if v.FromEdbInfoId == edbInfo.EdbInfoId {
+					edbName = fmt.Sprintf("%s(%s)", edbInfo.EdbName, v.FromTag)
+				}
+			}
+
+		case utils.DATA_SOURCE_CALCULATE_ZJPJ, utils.DATA_SOURCE_PREDICT_CALCULATE_ZJPJ, utils.DATA_SOURCE_CALCULATE_LJZTBPJ, utils.DATA_SOURCE_PREDICT_CALCULATE_LJZTBPJ: // 直接拼接 ,累计值同比拼接
+			for _, v := range edbMappingList {
+				if v.FromEdbInfoId == edbInfo.EdbInfoId {
+					tmpName := ``
+					if v.FromTag == `A` {
+						tmpName = `拼接日期前`
+					} else if v.FromTag == `B` {
+						tmpName = `拼接日期后`
+					}
+					edbName = fmt.Sprintf("%s(%s)", edbInfo.EdbName, tmpName)
+				}
+			}
+		case utils.DATA_SOURCE_CALCULATE_NHCC, utils.DATA_SOURCE_PREDICT_CALCULATE_NHCC: //计算指标(拟合残差)
+			for _, v := range edbMappingList {
+				//(需对上游指标+自变量,领先10天/因变量)
+				if v.FromEdbInfoId == edbInfo.EdbInfoId {
+					tmpName := ``
+					if v.FromTag == `A` {
+						tmpName = fmt.Sprintf(`自变量,领先%d天`, v.MoveValue)
+					} else if v.FromTag == `B` {
+						tmpName = `因变量`
+					}
+					edbName = fmt.Sprintf("%s(%s)", edbInfo.EdbName, tmpName)
+				}
+			}
+		case utils.DATA_SOURCE_CALCULATE_CORRELATION: // 滚动相关性
+			for _, v := range edbMappingList {
+				if v.FromEdbInfoId == edbInfo.EdbInfoId {
+					edbName = fmt.Sprintf("%s(%s)", edbInfo.EdbName, v.FromTag)
+				}
+			}
+
+		}
+
+	}
+
+	if edbInfo.EdbType == 1 {
+		// 基础指标的话,直接返回来源名称
+		//ruleTitle = `来源于`+edbInfo.SourceName
+
+		return
+	}
+
+	// 规则
+	switch edbInfo.Source {
+	case utils.DATA_SOURCE_CALCULATE, utils.DATA_SOURCE_PREDICT_CALCULATE:
+		ruleTitle = "=" + edbInfo.CalculateFormula
+	case utils.DATA_SOURCE_CALCULATE_LJZZY, utils.DATA_SOURCE_PREDICT_CALCULATE_LJZZY:
+		ruleTitle = `累计转月值计算`
+	case utils.DATA_SOURCE_CALCULATE_TBZ, utils.DATA_SOURCE_PREDICT_CALCULATE_TBZ:
+		ruleTitle = `同比值计算`
+	case utils.DATA_SOURCE_CALCULATE_TCZ, utils.DATA_SOURCE_PREDICT_CALCULATE_TCZ:
+		ruleTitle = `同差值计算`
+	case utils.DATA_SOURCE_CALCULATE_NSZYDPJJS, utils.DATA_SOURCE_PREDICT_CALCULATE_NSZYDPJJS:
+		ruleTitle = fmt.Sprintf("N数值移动均值计算(N=%s)", edbInfo.CalculateFormula)
+	case utils.DATA_SOURCE_CALCULATE_HBZ, utils.DATA_SOURCE_PREDICT_CALCULATE_HBZ:
+		ruleTitle = fmt.Sprintf("N数值环比值计算(N=%s)", edbInfo.CalculateFormula)
+	case utils.DATA_SOURCE_CALCULATE_HCZ, utils.DATA_SOURCE_PREDICT_CALCULATE_HCZ:
+		ruleTitle = fmt.Sprintf("N数值环差值计算(N=%s)", edbInfo.CalculateFormula)
+	case utils.DATA_SOURCE_CALCULATE_TIME_SHIFT, utils.DATA_SOURCE_PREDICT_CALCULATE_TIME_SHIFT:
+		moveType := `领先`
+		if edbInfo.MoveType == 2 {
+			moveType = "滞后"
+		}
+		ruleTitle = fmt.Sprintf("时间移位计算(%s%s%s)", moveType, edbInfo.CalculateFormula, edbInfo.MoveFrequency)
+	case utils.DATA_SOURCE_CALCULATE_BP, utils.DATA_SOURCE_PREDICT_CALCULATE_BP: // 变频
+		childFrequency := ``
+		if len(childList) > 0 {
+			if childEdbInfo, ok := edbInfoMap[childList[0].EdbInfoId]; ok {
+				childFrequency = childEdbInfo.Frequency
+			}
+
+		}
+		ruleTitle = fmt.Sprintf("升频计算(%s转%s)", childFrequency, edbInfo.Frequency)
+	case utils.DATA_SOURCE_CALCULATE_ZJPJ, utils.DATA_SOURCE_PREDICT_CALCULATE_ZJPJ: // 直接拼接
+		ruleTitle = fmt.Sprintf("直接拼接计算(%s)", edbInfo.CalculateFormula)
+	case utils.DATA_SOURCE_CALCULATE_LJZTBPJ, utils.DATA_SOURCE_PREDICT_CALCULATE_LJZTBPJ: // 累计值同比拼
+		ruleTitle = fmt.Sprintf("累计值同比值拼接计算(%s)", edbInfo.CalculateFormula)
+	case utils.DATA_SOURCE_PYTHON:
+		ruleTitle = `代码运算`
+	case utils.DATA_SOURCE_CALCULATE_CJJX, utils.DATA_SOURCE_PREDICT_CALCULATE_CJJX:
+		ruleTitle = fmt.Sprintf("超季节性计算(N=%s,%s)", edbInfo.CalculateFormula, edbInfo.Calendar)
+	case utils.DATA_SOURCE_CALCULATE_NHCC, utils.DATA_SOURCE_PREDICT_CALCULATE_NHCC: //计算指标(拟合残差)
+		var startDate, endDate string
+		dateList := strings.Split(edbInfo.CalculateFormula, ",")
+		if len(dateList) == 2 {
+			startDate = dateList[0]
+			endDate = dateList[1]
+		}
+		ruleTitle = fmt.Sprintf("拟合残差计算(%s至%s)", startDate, endDate)
+	case utils.DATA_SOURCE_CALCULATE_ADJUST:
+		ruleTitle = `数据调整`
+	case utils.DATA_SOURCE_CALCULATE_NH, utils.DATA_SOURCE_PREDICT_CALCULATE_NH:
+		ruleTitle = `年化计算`
+	case utils.DATA_SOURCE_CALCULATE_KSZS, utils.DATA_SOURCE_PREDICT_CALCULATE_KSZS: // 扩散指数->53
+		type KszsConfig struct {
+			DateType  int      `description:"扩散指标日期;1:全部指标日期并集;2:部分指标日期并集"`
+			CheckList []string `description:"选中的数据,A,B,C"`
+		}
+		var config KszsConfig
+		err := json.Unmarshal([]byte(edbInfo.CalculateFormula), &config)
+		if err != nil {
+			return
+		}
+		var startDate, endDate time.Time
+
+		childEdbInfoIdList := make([]int, 0)
+		if config.DateType == 1 {
+			for _, v := range childList {
+				childEdbInfoIdList = append(childEdbInfoIdList, v.EdbInfoId)
+			}
+
+		} else {
+			if parentEdbInfo != nil {
+				edbMappingList, ok := edbMappingMap[parentEdbInfo.EdbInfoId]
+				if !ok {
+					edbMappingList = []*models.EdbInfoCalculateMappingInfo{}
+				}
+				tagMap := make(map[string]int)
+				for _, v := range edbMappingList {
+					tagMap[v.FromTag] = v.FromEdbInfoId
+				}
+				for _, v := range config.CheckList {
+					if tmpEdbInfoId, ok := tagMap[v]; ok {
+						childEdbInfoIdList = append(childEdbInfoIdList, tmpEdbInfoId)
+					}
+				}
+			}
+		}
+		for _, v := range childEdbInfoIdList {
+			if childEdbInfo, ok := edbInfoMap[v]; ok {
+				tmpStartDate, tmpErr := time.ParseInLocation(utils.FormatDate, childEdbInfo.StartDate, time.Local)
+				if tmpErr != nil {
+					return
+				}
+				if startDate.IsZero() || startDate.After(tmpStartDate) {
+					startDate = tmpStartDate
+				}
+
+				tmpEndDate, tmpErr := time.ParseInLocation(utils.FormatDate, childEdbInfo.EndDate, time.Local)
+				if tmpErr != nil {
+					return
+				}
+				if endDate.IsZero() || endDate.Before(tmpEndDate) {
+					endDate = tmpEndDate
+				}
+			}
+		}
+		ruleTitle = fmt.Sprintf("扩散指数计算(%s至%s)", startDate.Format(utils.FormatDate), endDate.Format(utils.FormatDate))
+	case utils.DATA_SOURCE_STOCK_PLANT:
+		ruleTitle = `来源于装置分析`
+	case utils.DATA_SOURCE_CALCULATE_CORRELATION:
+		type EdbCalculateFormula struct {
+			BaseCalculateValue int    `description:"基础计算窗口"`
+			BaseCalculateUnit  string `description:"基础计算频度"`
+			LeadValue          int    `description:"领先期数"`
+			LeadUnit           string `description:"频度"`
+			CalculateValue     int    `description:"计算窗口"`
+			CalculateUnit      string `description:"计算频度"`
+		}
+		var correlationConf EdbCalculateFormula
+		err := json.Unmarshal([]byte(edbInfo.CalculateFormula), &correlationConf)
+		if err != nil {
+			return
+		}
+		ruleTitle = fmt.Sprintf("滚动相关性(计算窗口%d%s,B领先A%d%s)", correlationConf.CalculateValue, correlationConf.CalculateUnit, correlationConf.LeadValue, correlationConf.LeadUnit)
+	case utils.DATA_SOURCE_CALCULATE_JP, utils.DATA_SOURCE_PREDICT_CALCULATE_JP:
+		childFrequency := ``
+		if len(childList) > 0 {
+			if childEdbInfo, ok := edbInfoMap[childList[0].EdbInfoId]; ok {
+				childFrequency = childEdbInfo.Frequency
+			}
+
+		}
+		ruleTitle = fmt.Sprintf("降频计算(%s转%s,%s)", childFrequency, edbInfo.Frequency, edbInfo.CalculateFormula)
+	case utils.DATA_SOURCE_CALCULATE_STANDARD_DEVIATION:
+		ruleTitle = fmt.Sprintf("标准差(滚动%s期)", edbInfo.CalculateFormula)
+	case utils.DATA_SOURCE_CALCULATE_PERCENTILE, utils.DATA_SOURCE_PREDICT_CALCULATE_PERCENTILE:
+		type TempCalculate struct {
+			CalculateValue int    `description:"计算窗口"`
+			CalculateUnit  string `description:"计算频度"`
+		}
+		cf := TempCalculate{}
+		if e := json.Unmarshal([]byte(edbInfo.CalculateFormula), &cf); e != nil {
+			return
+		}
+		ruleTitle = fmt.Sprintf("百分位(时间长度%d%s)", cf.CalculateValue, cf.CalculateUnit)
+	case utils.DATA_SOURCE_CALCULATE_ZSXY, utils.DATA_SOURCE_PREDICT_CALCULATE_ZSXY:
+		ruleTitle = `指数修匀计算`
+	}
+
+	return
+}

+ 106 - 0
utils/common.go

@@ -1200,3 +1200,109 @@ func CheckFrequency(leftFrequency, rightFrequency string) int {
 func GetLikeKeyword(keyword string) string {
 	return `%` + keyword + `%`
 }
+
+func GetDateByDateTypeV2(dateType int, tmpStartDate, tmpEndDate string, startYear, yearMax int) (startDate, endDate string) {
+	startDate = tmpStartDate
+	endDate = tmpEndDate
+	switch dateType {
+	case 1:
+		startDate = "2000-01-01"
+		endDate = ""
+	case 2:
+		startDate = "2010-01-01"
+		endDate = ""
+	case 3:
+		startDate = "2015-01-01"
+		endDate = ""
+	case 4:
+		//startDate = strconv.Itoa(time.Now().Year()) + "-01-01"
+		startDate = "2021-01-01"
+		endDate = ""
+	case 5:
+		//startDate = startDate + "-01"
+		//endDate = endDate + "-01"
+	case 6:
+		//startDate = startDate + "-01"
+		endDate = ""
+	case 7:
+		startDate = "2018-01-01"
+		endDate = ""
+	case 8:
+		startDate = "2019-01-01"
+		endDate = ""
+	case 9:
+		startDate = "2020-01-01"
+		endDate = ""
+	case 11:
+		startDate = "2022-01-01"
+		endDate = ""
+	case DateTypeNYears:
+		if startYear == 0 { //默认取最近5年
+			startYear = 5
+		}
+		if yearMax == 0 {
+			return
+		}
+		startYear = startYear - 1
+		baseDate, _ := time.Parse(FormatDate, fmt.Sprintf("%d-01-01", yearMax))
+		startDate = baseDate.AddDate(-startYear, 0, 0).Format(FormatDate)
+		endDate = ""
+	}
+
+	// 兼容日期错误
+	{
+		if strings.Count(startDate, "-") == 1 {
+			startDate = startDate + "-01"
+		}
+		if strings.Count(endDate, "-") == 1 {
+			endDate = endDate + "-01"
+		}
+	}
+
+	return
+}
+
+// MapSorter 对于map 排序
+type MapSorter []Item
+
+type Item struct {
+	Key int
+	Val float64
+}
+
+func NewMapSorter(m map[int]float64) MapSorter {
+	ms := make(MapSorter, 0, len(m))
+	for k, v := range m {
+		ms = append(ms, Item{k, v})
+	}
+	return ms
+}
+
+func (ms MapSorter) Len() int {
+	return len(ms)
+}
+
+func (ms MapSorter) Less(i, j int) bool {
+	return ms[i].Val > ms[j].Val // 按值排序
+	//return ms[i].Key < ms[j].Key // 按键排序
+}
+
+func (ms MapSorter) Swap(i, j int) {
+	ms[i], ms[j] = ms[j], ms[i]
+}
+
+// GetDaysBetween2Date 计算两个日期之间相差几天
+func GetDaysBetween2Date(format, date1Str, date2Str string) (int, error) {
+	// 将字符串转化为Time格式
+	date1, err := time.ParseInLocation(format, date1Str, time.Local)
+	if err != nil {
+		return 0, err
+	}
+	// 将字符串转化为Time格式
+	date2, err := time.ParseInLocation(format, date2Str, time.Local)
+	if err != nil {
+		return 0, err
+	}
+	//计算相差天数
+	return int(date1.Sub(date2).Hours() / 24), nil
+}

+ 50 - 10
utils/constants.go

@@ -19,7 +19,7 @@ const (
 	PageSize20                 = 20
 	PageSize30                 = 30
 )
-
+const DateTypeNYears = 20 //时间类型为最近N年
 // 数据来源渠道
 const (
 	DATA_SOURCE_THS                                  = iota + 1 //同花顺
@@ -209,15 +209,6 @@ const (
 	CACHE_STOCK_PLANT_CALCULATE = "CACHE_STOCK_PLANT_CALCULATE_" // 库存装置减产计算
 )
 
-// 图表类型
-const (
-	CHART_SOURCE_DEFAULT             = 1
-	CHART_SOURCE_FUTURE_GOOD         = 2
-	CHART_SOURCE_CORRELATION         = 3 // 相关性图表
-	CHART_SOURCE_ROLLING_CORRELATION = 4 // 滚动相关性图表
-	CHART_SOURCE_FUTURE_GOOD_PROFIT  = 5 // 商品利润曲线
-)
-
 // MonthQuarterMap 月份与季度的map
 var MonthQuarterMap = map[int]int{
 	1:  1,
@@ -303,3 +294,52 @@ const (
 	CACHE_CRM_AUTH_CODE_PREFIX   = "eta_forum:crm_auth_code:"   // 免密登录Code-CRM
 	CACHE_FORUM_AUTH_CODE_PREFIX = "eta_forum:forum_auth_code:" // 免密登录Code-ETA
 )
+
+// 图表样式类型
+const (
+	CHART_TYPE_CURVE           = 1  //曲线图
+	CHART_TYPE_BAR             = 7  //柱形图
+	CHART_TYPE_SECTION_SCATTER = 10 //截面散点图样式
+	CHART_TYPE_RADAR           = 11 //雷达图
+)
+
+// 图表类型
+const (
+	CHART_SOURCE_DEFAULT                         = 1
+	CHART_SOURCE_FUTURE_GOOD                     = 2
+	CHART_SOURCE_CORRELATION                     = 3  // 相关性图表
+	CHART_SOURCE_ROLLING_CORRELATION             = 4  // 滚动相关性图表
+	CHART_SOURCE_FUTURE_GOOD_PROFIT              = 5  // 商品利润曲线
+	CHART_SOURCE_LINE_EQUATION                   = 6  // 拟合方程图表
+	CHART_SOURCE_LINE_FEATURE_STANDARD_DEVIATION = 7  // 统计特征-标准差图表
+	CHART_SOURCE_LINE_FEATURE_PERCENTILE         = 8  // 统计特征-百分位图表
+	CHART_SOURCE_LINE_FEATURE_FREQUENCY          = 9  // 统计特征-频率分布图表
+	CHART_SOURCE_CROSS_HEDGING                   = 10 // 跨品种分析图表
+)
+
+// DataSourceEnMap 指标来源的英文名称
+var DataSourceEnMap = map[int]string{
+	DATA_SOURCE_WIND:             "Wind",
+	DATA_SOURCE_THS:              "iFind",
+	DATA_SOURCE_PB:               "Bloomberg",
+	DATA_SOURCE_PB_FINANCE:       "Bloomberg Finance",
+	DATA_SOURCE_LT:               "Reuters",
+	DATA_SOURCE_MANUAL:           "Horizon Insights",
+	DATA_SOURCE_LZ:               "OilChem",
+	DATA_SOURCE_YS:               "SMM",
+	DATA_SOURCE_GL:               "MySteel",
+	DATA_SOURCE_ZZ:               "Zhengzhou Commodity Exchange",
+	DATA_SOURCE_DL:               "Dalian Commodity Exchange",
+	DATA_SOURCE_SH:               "Shanghai Futures Exchange",
+	DATA_SOURCE_CFFEX:            "China Financial Futures Exchange",
+	DATA_SOURCE_SHFE:             "Shanghai International Energy Exchange",
+	DATA_SOURCE_GIE:              "Eurostat",
+	DATA_SOURCE_COAL:             "China Coal Transport & Distribution Association",
+	DATA_SOURCE_GOOGLE_TRAVEL:    "Our World in Data",
+	DATA_SOURCE_EIA_STEO:         "Energy Information Administration",
+	DATA_SOURCE_COM_TRADE:        "United Nations",
+	DATA_SOURCE_SCI:              "Sublime China Information",
+	DATA_SOURCE_BAIINFO:          "BAIINFO",
+	DATA_SOURCE_MYSTEEL_CHEMICAL: "Horizon Insights",
+	DATA_SOURCE_FUBAO:            "FuBao",
+}