Переглянути джерело

Merge remote-tracking branch 'origin/master' into pool/94

Roc 3 місяців тому
батько
коміт
66708d6e04
52 змінених файлів з 2350 додано та 257 видалено
  1. 26 0
      controller/base_config.go
  2. 3 1
      controller/bullet_chat/bullet_chat.go
  3. 71 0
      controller/chart/chart_common.go
  4. 25 0
      controller/chart/chart_info.go
  5. 3 1
      controller/comment/comment.go
  6. 3 1
      controller/community/comment.go
  7. 17 14
      controller/community/question.go
  8. 9 1
      controller/pc/pc.go
  9. 15 14
      controller/public.go
  10. 1 1
      controller/report/report.go
  11. 3 13
      controller/response/base.go
  12. 2 2
      controller/voice_broadcast/voice_broadcast.go
  13. 21 7
      controller/wechat/wechat.go
  14. 2 2
      go.mod
  15. 1 0
      go.sum
  16. 5 1
      init_serve/router.go
  17. 19 0
      logic/user/user.go
  18. 3 3
      logic/yb_community_question/yb_community_question_comment.go
  19. 53 0
      middleware/check_cygx_auth.go
  20. 54 17
      middleware/common.go
  21. 4 1
      middleware/token.go
  22. 5 2
      models/request/public.go
  23. 13 12
      models/request/voice_broadcast.go
  24. 5 0
      models/response/base_config.go
  25. 1 0
      models/response/voice_broadcast.go
  26. 1 1
      models/tables/banner/query.go
  27. 19 0
      models/tables/business_conf/business.conf.go
  28. 10 0
      models/tables/business_conf/query.go
  29. 7 1
      models/tables/chart_edb_mapping/query.go
  30. 113 1
      models/tables/edb_data/query.go
  31. 1 1
      models/tables/rddp/msg_code/query.go
  32. 1 0
      models/tables/voice_broadcast/voice_broadcast.go
  33. 18 2
      models/tables/yb_config/entity.go
  34. 8 0
      models/tables/yb_config/model.go
  35. 11 0
      routers/base_config.go
  36. 0 1
      routers/report.go
  37. 1 1
      routers/voice_broadcast.go
  38. 57 0
      services/base_config/business_conf.go
  39. 1171 7
      services/chart/chart_info.go
  40. 3 75
      services/chart/predict_edb_info.go
  41. 3 3
      services/comment/comment.go
  42. 19 0
      services/eta_chart_lib.go
  43. 13 17
      services/media.go
  44. 29 6
      services/report/report.go
  45. 26 6
      services/report/report_chapter.go
  46. 252 0
      services/report/report_handle.go
  47. 35 7
      services/share_poster.go
  48. 15 1
      services/sun_code.go
  49. 13 5
      services/voice_broadcast.go
  50. 104 22
      services/wx_app/wx_app.go
  51. 17 7
      utils/constants.go
  52. 39 0
      utils/gin.go

+ 26 - 0
controller/base_config.go

@@ -0,0 +1,26 @@
+package controller
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/hongze_yb/controller/response"
+	response2 "hongze/hongze_yb/models/response"
+	"hongze/hongze_yb/services/base_config"
+)
+
+// GetBusinessConf
+// @Tags 公共模块
+// @Summary  获取基本配置信息
+// @Description 获取基本配置信息
+// @Param Authorization	header string true "微信登录后获取到的token"
+// @Success 200 {object} []logic.ApplyVariety "获取成功"
+// @failure 400 {string} string "获取失败"
+// @Router /base/business_conf [get]
+func GetBusinessConf(c *gin.Context) {
+	disclaimer, err := base_config.GetBusinessConfDisclaimer()
+	if err != nil {
+		response.FailData("获取失败", "获取配置信息失败,Err:"+err.Error(), c)
+		return
+	}
+	data := response2.BusinessConfResp{Disclaimer: disclaimer}
+	response.OkData("获取成功", data, c)
+}

+ 3 - 1
controller/bullet_chat/bullet_chat.go

@@ -22,6 +22,8 @@ func Add(c *gin.Context) {
 		response.Fail("请登录后操作", c)
 		return
 	}
+	// 是否为备用小程序
+	copyYb := c.Request.Header.Get("CopyYb")
 
 	var req request.BulletChatAddReq
 	if c.ShouldBind(&req) != nil {
@@ -43,7 +45,7 @@ func Add(c *gin.Context) {
 	// 敏感词校验
 	// TODO:如果备用小程序提上master的话,此处要有调整(2022/11/08)
 	if userInfo.RecordInfo.OpenID != "" && userInfo.RecordInfo.CreatePlatform == 6 {
-		checkResult, e := wx_app.MsgSecCheck(userInfo.RecordInfo.OpenID, req.Content)
+		checkResult, e := wx_app.MsgSecCheck(userInfo.RecordInfo.OpenID, req.Content, copyYb)
 		if e == nil {
 			if checkResult.Result != nil && checkResult.Result.Suggest != "pass" {
 				errMsg := "含有违禁词, 不允许发布: " + checkResult.Result.Suggest + ", 命中标签: " + strconv.Itoa(checkResult.Result.Label)

+ 71 - 0
controller/chart/chart_common.go

@@ -16,6 +16,7 @@ import (
 	"hongze/hongze_yb/models/tables/chart_info_future_good_profit"
 	"hongze/hongze_yb/models/tables/chart_info_log"
 	"hongze/hongze_yb/models/tables/yb_my_chart"
+	"hongze/hongze_yb/services"
 	"hongze/hongze_yb/services/alarm_msg"
 	"hongze/hongze_yb/services/chart"
 	"hongze/hongze_yb/services/chart/correlation"
@@ -114,6 +115,8 @@ func CommonChartInfoDetailFromUniqueCode(c *gin.Context) {
 		resp, isOk, msg, errMsg = getLineFeatureChartInfoDetailFromUniqueCode(chartInfo, myChartClassifyId, user.GetInfoByClaims(c))
 	case utils.CHART_SOURCE_CROSS_HEDGING:
 		resp, isOk, msg, errMsg = GetCrossVarietyChartInfoDetailFromUniqueCode(chartInfo, myChartClassifyId, user.GetInfoByClaims(c))
+	case utils.CHART_SOURCE_RANGE_ANALYSIS:
+		resp, isOk, msg, errMsg = getRangeAnalysisChartInfoDetail(chartInfo, myChartClassifyId, user.GetInfoByClaims(c))
 	default:
 		msg := "错误的图表"
 		errMsg := "错误的图表"
@@ -1210,3 +1213,71 @@ func RefreshCorrelationChartInfo(c *gin.Context) {
 	}
 	response.OkData("刷新成功", "", c)
 }
+
+func getRangeAnalysisChartInfoDetail(chartInfo *chartInfoModel.ChartInfoView, myChartClassifyId int, userInfo user.UserInfo) (resp *chart_info.ChartInfoDetailResp, isOk bool, msg, errMsg string) {
+	resp = new(chart_info.ChartInfoDetailResp)
+	chartInfoId := chartInfo.ChartInfoId
+	//调用接口
+	chartData, e := services.GetRangeChartChartDetail(chartInfo.UniqueCode)
+	if e != nil {
+		msg = "获取失败"
+		errMsg = "获取图表信息失败, Err:" + e.Error()
+		return
+	}
+	chartDataResp := new(chart_info.ChartInfoDetailResp)
+	// 兼容返回值类型
+	chartDataString, _ := json.Marshal(chartData)
+	err := json.Unmarshal(chartDataString, chartDataResp)
+	if err != nil {
+		msg = "获取失败"
+		errMsg = "图表信息解析失败, Err:" + e.Error()
+		return
+	}
+	chartInfo = chartDataResp.ChartInfo
+	chartInfo.ChartInfoId = chartInfoId
+	yDataList := chartDataResp.YDataList
+	edbList := chartDataResp.EdbInfoList
+	xEdbIdValue := chartDataResp.XEdbIdValue
+	dataResp := chartDataResp.DataResp
+	// 访问记录-仅普通用户记录
+	ok, _, _ := user.GetAdminByUserInfo(userInfo)
+	if !ok {
+		go chart.SaveChartVisitLog(userInfo, chartInfo, myChartClassifyId)
+	}
+
+	// 用户是否有收藏该图表
+	{
+		ob := new(yb_my_chart.YbMyChart)
+		cond := `user_id = ? AND chart_info_id = ?`
+		pars := make([]interface{}, 0)
+		pars = append(pars, userInfo.UserID, chartInfo.ChartInfoId)
+		exists, e := ob.FetchByCondition(cond, pars)
+		if e != nil && e != utils.ErrNoRow {
+			msg = `操作失败`
+			errMsg = "获取用户图表失败, Err: " + e.Error()
+			return
+		}
+		myChartInfo := new(responseModel.MyChartItem)
+		if exists != nil && exists.MyChartID > 0 {
+			myChartInfo.MyChartID = exists.MyChartID
+			myChartInfo.MyChartClassifyID = exists.MyChartClassifyID
+			myChartInfo.ChartInfoID = exists.ChartInfoID
+			myChartInfo.ChartName = exists.ChartName
+			myChartInfo.UniqueCode = exists.UniqueCode
+			myChartInfo.ChartImage = exists.ChartImage
+			myChartInfo.UserID = exists.UserID
+			myChartInfo.ReportID = exists.ReportID
+			myChartInfo.ReportChapterID = exists.ReportChapterID
+			myChartInfo.CreateTime = utils.TimeTransferString(utils.FormatDateTime, exists.CreateTime)
+		}
+
+		resp.MyChartInfo = myChartInfo
+	}
+	resp.ChartInfo = chartInfo
+	resp.EdbInfoList = edbList
+	resp.XEdbIdValue = xEdbIdValue
+	resp.YDataList = yDataList
+	resp.DataResp = dataResp
+	isOk = true
+	return
+}

+ 25 - 0
controller/chart/chart_info.go

@@ -106,6 +106,31 @@ func GetChartInfoDetail(c *gin.Context) {
 		sourceArr = append(sourceArr, "平衡表")
 		dataResp = chartDataResp.DataResp
 
+	} else if chartInfo.Source == utils.CHART_SOURCE_RANGE_ANALYSIS {
+		//调用接口
+		chartData, e := services.GetRangeChartChartDetail(chartInfo.UniqueCode)
+		if e != nil {
+			response.FailMsg("获取失败", "获取图表信息失败, Err:"+e.Error(), c)
+			return
+		}
+		chartDataResp := new(chart_info.ChartInfoDetailResp)
+		// 兼容返回值类型
+		chartDataString, _ := json.Marshal(chartData)
+		err = json.Unmarshal(chartDataString, chartDataResp)
+		if err != nil {
+			response.FailMsg("获取失败", "获取图表信息失败, Err:"+err.Error(), c)
+			return
+		}
+		chartInfo = chartDataResp.ChartInfo
+		chartInfo.ChartInfoId = chartInfoId
+		yDataList = chartDataResp.YDataList
+		edbList = chartDataResp.EdbInfoList
+		xEdbIdValue = chartDataResp.XEdbIdValue
+		dataResp = chartDataResp.DataResp
+		// 图表的指标来源
+		sourceNameList, sourceNameEnList := chart.GetEdbSourceByEdbInfoIdList(edbList)
+		chartInfo.ChartSource = strings.Join(sourceNameList, ",")
+		chartInfo.ChartSourceEn = strings.Join(sourceNameEnList, ",")
 	} else {
 
 		// 获取主题样式

+ 3 - 1
controller/comment/comment.go

@@ -12,13 +12,15 @@ import (
 
 // Comment 发布留言
 func Comment(c *gin.Context)  {
+	// 是否为备用小程序
+	copyYb := c.Request.Header.Get("CopyYb")
 	var req reqComment.ReqComment
 	if c.ShouldBind(&req) !=nil {
 		response.Fail("入参错误", c)
 		return
 	}
 	userInfo := userService.GetInfoByClaims(c)
-	data, err := commentService.Comment(userInfo, req)
+	data, err := commentService.Comment(userInfo, req, copyYb)
 	if err != nil {
 		response.Fail(err.Error(),c)
 		return

+ 3 - 1
controller/community/comment.go

@@ -14,6 +14,8 @@ import (
 
 // Comment 发布留言
 func Comment(c *gin.Context) {
+	// 是否为备用小程序
+	copyYb := c.Request.Header.Get("CopyYb")
 	var req request.ReqComment
 	if c.ShouldBind(&req) != nil {
 		response.Fail("入参错误", c)
@@ -39,7 +41,7 @@ func Comment(c *gin.Context) {
 		req.Source = 1
 	}
 
-	ybCommunityQuestionComment, err, errMsg := yb_community_question.Comment(userInfo, req.CommunityQuestionID, req.Content, req.SourceAgent, req.IsShowName, req.Source, qaAvatarUrl)
+	ybCommunityQuestionComment, err, errMsg := yb_community_question.Comment(userInfo, req.CommunityQuestionID, req.Content, req.SourceAgent, req.IsShowName, req.Source, qaAvatarUrl, copyYb)
 	if err != nil {
 		response.FailMsg(errMsg, err.Error(), c)
 		return

+ 17 - 14
controller/community/question.go

@@ -20,7 +20,6 @@ import (
 	userService "hongze/hongze_yb/services/user"
 	"hongze/hongze_yb/services/wx_app"
 	"hongze/hongze_yb/utils"
-	"io/ioutil"
 	"os"
 	"path"
 	"strconv"
@@ -108,6 +107,8 @@ func QuestionDetail(c *gin.Context) {
 // @failure 400 {string} string "操作失败"
 // @Router /question/ask [post]
 func QuestionAsk(c *gin.Context) {
+	// 是否为备用小程序
+	copyYb := c.Request.Header.Get("CopyYb")
 	var req request.QuestionAskReq
 	if err := c.ShouldBind(&req); err != nil {
 		response.Fail("参数有误", c)
@@ -132,8 +133,8 @@ func QuestionAsk(c *gin.Context) {
 	}
 	// 敏感词校验, 只有小程序的用户才能走敏感词过滤接口
 	userinfo := user.GetInfoByClaims(c)
-	if userinfo.RecordInfo.OpenID != "" && userinfo.RecordInfo.CreatePlatform == 6 {
-		checkResult, e := wx_app.MsgSecCheck(userinfo.RecordInfo.OpenID, req.QuestionContent)
+	if userinfo.RecordInfo.OpenID != "" && (userinfo.RecordInfo.CreatePlatform == utils.USER_RECORD_PLATFORM_YB || userinfo.RecordInfo.CreatePlatform == utils.USER_RECORD_PLATFORM_COPY_YB) {
+		checkResult, e := wx_app.MsgSecCheck(userinfo.RecordInfo.OpenID, req.QuestionContent, copyYb)
 		if e == nil {
 			if checkResult.Result != nil && checkResult.Result.Suggest != "pass" {
 				errMsg := "含有违禁词, 不允许发布: " + checkResult.Result.Suggest + ", 命中标签: " + strconv.Itoa(checkResult.Result.Label)
@@ -267,16 +268,16 @@ func QuestionUploadAudio(c *gin.Context) {
 		return
 	}
 	// 获取音频文件时长
-	fByte, err := ioutil.ReadFile(fpath)
-	if err != nil {
-		response.FailMsg("读取本地文件失败", "QuestionUploadAudio 读取本地文件失败", c)
-		return
-	}
-	if len(fByte) <= 0 {
-		response.FailMsg("文件大小有误", "QuestionUploadAudio 文件大小有误", c)
-		return
-	}
-	seconds, err := services.GetMP3PlayDuration(fByte)
+	//fByte, err := ioutil.ReadFile(fpath)
+	//if err != nil {
+	//	response.FailMsg("读取本地文件失败", "QuestionUploadAudio 读取本地文件失败", c)
+	//	return
+	//}
+	//if len(fByte) <= 0 {
+	//	response.FailMsg("文件大小有误", "QuestionUploadAudio 文件大小有误", c)
+	//	return
+	//}
+	seconds, err := utils.GetVideoPlaySeconds(fpath) //services.GetMP3PlayDuration(fByte)
 	if err != nil {
 		response.FailMsg("读取文件时长失败", "QuestionUploadAudio 读取文件时长失败", c)
 		return
@@ -482,6 +483,8 @@ func QuestionTransfer(c *gin.Context) {
 // @failure 400 {string} string "操作失败"
 // @Router /question/stop [post]
 func QuestionStop(c *gin.Context) {
+	// 是否为备用小程序
+	copyYb := c.Request.Header.Get("CopyYb")
 	var req request.QuestionStopReq
 	if err := c.ShouldBind(&req); err != nil {
 		response.Fail("参数有误", c)
@@ -498,7 +501,7 @@ func QuestionStop(c *gin.Context) {
 	// 敏感词校验, 只有小程序的用户才能走敏感词过滤接口
 	userinfo := user.GetInfoByClaims(c)
 	if userinfo.RecordInfo.OpenID != "" && userinfo.RecordInfo.CreatePlatform == 6 {
-		checkResult, e := wx_app.MsgSecCheck(userinfo.RecordInfo.OpenID, req.Reason)
+		checkResult, e := wx_app.MsgSecCheck(userinfo.RecordInfo.OpenID, req.Reason, copyYb)
 		if e == nil {
 			if checkResult.Result != nil && checkResult.Result.Suggest != "pass" {
 				errMsg := "含有违禁词, 不允许发布: " + checkResult.Result.Suggest + ", 命中标签: " + strconv.Itoa(checkResult.Result.Label)

+ 9 - 1
controller/pc/pc.go

@@ -493,7 +493,7 @@ func GetSunCode(c *gin.Context) {
 	if sunCodeUrl == "" {
 		sunCodeUrl, err = services.PcCreateAndUploadSunCode(req.CodeScene, req.CodePage)
 		if err != nil {
-			response.FailMsg("生成太阳码失败", err.Error(), c)
+			response.FailMsg("生成太阳码失败:err:"+err.Error(), err.Error(), c)
 			return
 		}
 	}
@@ -702,6 +702,10 @@ func PcLogin(c *gin.Context) {
 			response.Fail("手机验证码错误,请重新输入", c)
 			return
 		}
+		if item.Code != req.SmsCode {
+			response.Fail("验证码失效,请重新最新验证码", c)
+			return
+		}
 
 		wxUser, err := wx_user.GetByMobile(req.Mobile)
 		if err != nil {
@@ -898,6 +902,8 @@ func GetSmsCode(c *gin.Context) {
 	if err != nil {
 		if errMsg != "" {
 			errMsg = "获取验证码失败"
+		} else {
+			errMsg = err.Error()
 		}
 		response.Fail(errMsg, c)
 		return
@@ -933,6 +939,8 @@ func GetEmailCode(c *gin.Context) {
 	if err != nil {
 		if errMsg != "" {
 			errMsg = "获取验证码失败"
+		} else {
+			errMsg = err.Error()
 		}
 		response.Fail(errMsg, c)
 		return

+ 15 - 14
controller/public.go

@@ -24,7 +24,6 @@ import (
 	"hongze/hongze_yb/services/user"
 	"hongze/hongze_yb/services/wx_app"
 	"hongze/hongze_yb/utils"
-	"io/ioutil"
 	"net/url"
 	"os"
 	"path"
@@ -119,6 +118,8 @@ func Upload(c *gin.Context) {
 // @failure 400 {string} string "获取失败"
 // @Router /public/get_share_poster [post]
 func GetSharePoster(c *gin.Context) {
+	// 是否为备用小程序
+	copyYb := c.Request.Header.Get("CopyYb")
 	var req services.SharePosterReq
 	if c.ShouldBind(&req) != nil {
 		response.Fail("参数异常", c)
@@ -128,7 +129,7 @@ func GetSharePoster(c *gin.Context) {
 		response.Fail("来源有误", c)
 		return
 	}
-	imgUrl, err := services.CreatePosterFromSourceV2(req.CodePage, req.CodeScene, req.Source, req.Version, req.Pars)
+	imgUrl, err := services.CreatePosterFromSourceV2(req.CodePage, req.CodeScene, req.Source, req.Version, req.Pars, copyYb)
 	if err != nil {
 		response.FailData("获取分享海报失败", "获取分享海报失败, Err: "+err.Error(), c)
 		return
@@ -239,17 +240,17 @@ func UploadAudio(c *gin.Context) {
 		response.FailMsg("文件生成失败", "UploadAudio 文件生成失败, Err:"+err.Error(), c)
 		return
 	}
-	// 获取音频文件时长
-	fByte, err := ioutil.ReadFile(fpath)
-	if err != nil {
-		response.FailMsg("读取本地文件失败", "UploadAudio 读取本地文件失败", c)
-		return
-	}
-	if len(fByte) <= 0 {
-		response.FailMsg("文件大小有误", "UploadAudio 文件大小有误", c)
-		return
-	}
-	seconds, err := services.GetMP3PlayDuration(fByte)
+	//// 获取音频文件时长
+	//fByte, err := ioutil.ReadFile(fpath)
+	//if err != nil {
+	//	response.FailMsg("读取本地文件失败", "UploadAudio 读取本地文件失败", c)
+	//	return
+	//}
+	//if len(fByte) <= 0 {
+	//	response.FailMsg("文件大小有误", "UploadAudio 文件大小有误", c)
+	//	return
+	//}
+	seconds, err := utils.GetVideoPlaySeconds(fpath) //services.GetMP3PlayDuration(fByte)
 	if err != nil {
 		response.FailMsg("读取文件时长失败", "UploadAudio 读取文件时长失败", c)
 		return
@@ -464,7 +465,7 @@ func BannerList(c *gin.Context) {
 	cond := " enable = 1 "
 
 	if isHomepage != 1 {
-		cond += " AND id <> 9999"
+		cond += " AND remark <> '调研合集' "
 	}
 
 	list, err := banner.GetBannerList(cond, page, limit)

+ 1 - 1
controller/report/report.go

@@ -199,7 +199,7 @@ func RddpShareImg(c *gin.Context) {
 		response.OkData("获取成功", defaultImg, c)
 		return
 	}
-	imgUrl, err := services.GetDynamicShareImg(req.Source, req.Pars)
+	imgUrl, err := services.GetDynamicShareImg(req.Source, req.Pars, req.ReportId, req.ReportChapterId, req.Version)
 	if err != nil {
 		response.FailData("获取分享海报失败", "获取分享海报失败, Err: "+err.Error(), c)
 		return

+ 3 - 13
controller/response/base.go

@@ -27,30 +27,20 @@ type ResultData struct {
 
 func result(code int, resultData ResultData, c *gin.Context) {
 	jsonByte, _ := json.Marshal(resultData)
-	token := c.Request.Header.Get("Authorization")
-	if token == "" {
-		token = c.DefaultQuery("authorization", "")
-		if token == "" {
-			token = c.DefaultQuery("Authorization", "")
-		}
-	}
-	logSlice := make([]string, 0)
-	logSlice = append(logSlice, fmt.Sprint("Url:", c.Request.RequestURI))
-	logSlice = append(logSlice, fmt.Sprint("Token:", token))
-	logSlice = append(logSlice, fmt.Sprint("resultData:", string(jsonByte)))
+	logSlice := utils.GetContextLogListByClaims(c)
+	logSlice = append(logSlice, fmt.Sprint("ResultData:", string(jsonByte)))
 
 	//记录错误日志
 	if resultData.ErrMsg != "" {
 		logSlice = append(logSlice, fmt.Sprint("ErrMsg:", resultData.ErrMsg))
 		//global.LOG.Info(strings.Join(logSlice, ";"))
 	}
+	global.LOG.Info(strings.Join(logSlice, "\n"))
 
 	// 测试环境不加密
 	if global.CONFIG.Serve.RunMode == "debug" {
-		global.LOG.Info(strings.Join(logSlice, ";"))
 		c.JSON(code, resultData)
 	} else {
-		global.LOG.Info(strings.Join(logSlice, ";"))
 		encryptResult := utils.DesBase64Encrypt(jsonByte)
 		c.JSON(code, string(encryptResult))
 	}

+ 2 - 2
controller/voice_broadcast/voice_broadcast.go

@@ -109,7 +109,7 @@ func AddBroadcast(c *gin.Context) {
 	userInfo := user.GetInfoByClaims(c)
 	// 新增
 	resp, e := services.CreateVoiceBroadcast(req.SectionId, req.VarietyId, req.AuthorId, req.BroadcastName, req.SectionName, req.VarietyName,
-		req.Author, req.VoiceSeconds, req.VoiceSize, req.VoiceUrl, req.Imgs, userInfo)
+		req.Author, req.VoiceSeconds, req.VoiceSize, req.VoiceUrl, req.Imgs, req.CentralArguments, userInfo)
 	if e != nil {
 		response.FailMsg("新增失败", e.Error(), c)
 		return
@@ -175,7 +175,7 @@ func EditBroadcast(c *gin.Context) {
 	userInfo := user.GetInfoByClaims(c)
 	// 编辑
 	resp, e := services.EditVoiceBroadcast(req.BroadcastId, req.SectionId, req.VarietyId, req.AuthorId, req.BroadcastName, req.SectionName, req.VarietyName,
-		req.Author, req.VoiceSeconds, req.VoiceSize, req.VoiceUrl, req.Imgs, userInfo)
+		req.Author, req.VoiceSeconds, req.VoiceSize, req.VoiceUrl, req.Imgs, req.CentralArguments, userInfo)
 	if e != nil {
 		response.FailMsg("新增失败", e.Error(), c)
 		return

+ 21 - 7
controller/wechat/wechat.go

@@ -9,6 +9,7 @@ import (
 	userService "hongze/hongze_yb/services/user"
 	"hongze/hongze_yb/services/wechat"
 	"hongze/hongze_yb/services/wx_app"
+	"hongze/hongze_yb/utils"
 	"strconv"
 )
 
@@ -29,9 +30,11 @@ func GetUserInfo(c *gin.Context) {
 
 func GetUserSession(c *gin.Context) {
 	code, _ := c.GetQuery("code")
+	// 是否为备用小程序
+	copyYb := c.Request.Header.Get("CopyYb")
 	//c.Sho
 	//fmt.Println(c.Request.)
-	userInfo, err := wx_app.GetSession(code)
+	userInfo, err := wx_app.GetSession(code, copyYb)
 	if err != nil {
 		response.Fail("获取失败,Err:"+err.Error(), c)
 		return
@@ -51,12 +54,20 @@ func GetUserSession(c *gin.Context) {
 func Login(c *gin.Context) {
 	//code, _ := c.GetQuery("code")
 	code := c.DefaultQuery("code", "")
-	wxUserInfo, err := wx_app.GetSession(code)
+	// 是否为备用小程序
+	copyYb := c.Request.Header.Get("CopyYb")
+
+	wxUserInfo, err := wx_app.GetSession(code, copyYb)
 	if err != nil {
 		response.Fail("获取失败,Err:"+err.Error(), c)
 		return
 	}
-	token, userId, isBind, err := user.WxLogin(wx_app.WxPlatform, wxUserInfo)
+	wxPlatform := utils.USER_RECORD_PLATFORM_YB
+	if copyYb == "true" {
+		wxPlatform = utils.USER_RECORD_PLATFORM_COPY_YB
+	}
+
+	token, userId, isBind, err := user.WxLogin(wxPlatform, wxUserInfo)
 	if err != nil {
 		response.Fail("登录失败,Err:"+err.Error(), c)
 		return
@@ -71,9 +82,10 @@ func Login(c *gin.Context) {
 
 // 消息解密请求参数
 type EncryptReq struct {
-	EncryptedData string
-	Iv            string
-	IsBind        bool `json:"isBind" description:"是否需要去授权绑定"`
+	Code			string	`description:"手机号获取凭证"`
+	EncryptedData 	string
+	Iv            	string
+	IsBind        	bool `json:"isBind" description:"是否需要去授权绑定"`
 }
 
 // GetEncryptInfo 消息解密
@@ -87,6 +99,8 @@ type EncryptReq struct {
 // @Success 200 {string} string "获取成功"
 // @Router /wechat/getEncryptInfo [post]
 func GetEncryptInfo(c *gin.Context) {
+	// 是否为备用小程序
+	copyYb := c.Request.Header.Get("CopyYb")
 	var req EncryptReq
 	err := c.ShouldBind(&req)
 	if err != nil {
@@ -107,7 +121,7 @@ func GetEncryptInfo(c *gin.Context) {
 		response.Fail("请重新登录", c)
 		return
 	}
-	decryptData, err := wx_app.GetDecryptInfo(userInfo.RecordInfo.SessionKey, req.EncryptedData, req.Iv)
+	decryptData, err := wx_app.GetDecryptInfo(userInfo.RecordInfo.SessionKey, req.EncryptedData, req.Iv, copyYb)
 	if err != nil {
 		response.Fail("获取失败,Err:"+err.Error(), c)
 		return

+ 2 - 2
go.mod

@@ -15,6 +15,7 @@ require (
 	github.com/nosixtools/solarlunar v0.0.0-20211112060703-1b6dea7b4a19
 	github.com/olivere/elastic/v7 v7.0.32
 	github.com/op/go-logging v0.0.0-20160315200505-970db520ece7
+	github.com/qiniu/qmgo v1.1.8
 	github.com/rdlucklib/rdluck_tools v1.0.3
 	github.com/shopspring/decimal v1.3.1
 	github.com/silenceper/wechat/v2 v2.1.4
@@ -25,6 +26,7 @@ require (
 	github.com/swaggo/swag v1.8.1
 	github.com/tosone/minimp3 v1.0.1
 	github.com/wenzhenxi/gorsa v0.0.0-20220418014903-15feec0f05a6
+	go.mongodb.org/mongo-driver v1.15.0
 	golang.org/x/image v0.3.0
 	gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df
 	gorm.io/driver/mysql v1.4.5
@@ -75,7 +77,6 @@ require (
 	github.com/pelletier/go-toml v1.9.5 // indirect
 	github.com/pelletier/go-toml/v2 v2.0.5 // indirect
 	github.com/pkg/errors v0.9.1 // indirect
-	github.com/qiniu/qmgo v1.1.8 // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
 	github.com/spf13/afero v1.9.2 // indirect
 	github.com/spf13/cast v1.5.0 // indirect
@@ -90,7 +91,6 @@ require (
 	github.com/xdg-go/scram v1.1.2 // indirect
 	github.com/xdg-go/stringprep v1.0.4 // indirect
 	github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d // indirect
-	go.mongodb.org/mongo-driver v1.15.0 // indirect
 	golang.org/x/crypto v0.17.0 // indirect
 	golang.org/x/net v0.10.0 // indirect
 	golang.org/x/sync v0.1.0 // indirect

+ 1 - 0
go.sum

@@ -533,6 +533,7 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=

+ 5 - 1
init_serve/router.go

@@ -22,7 +22,9 @@ func InitRouter() (r *gin.Engine) {
 	r.Use(middleware.Recover())
 
 	// 公共的中间件
-	r.Use(middleware.Common())
+	common := middleware.Common{}
+	r.Use(common.RequestLog)
+	r.Use(common.Page)
 
 	//swagger界面访问地址
 	r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler))
@@ -85,5 +87,7 @@ func InitRouter() (r *gin.Engine) {
 	routers.InitFeCalendar(r)
 	// 指标信息
 	routers.InitEdbInfo(r)
+	// 基本配置信息
+	routers.InitBaseConfig(r)
 	return
 }

+ 19 - 0
logic/user/user.go

@@ -1,7 +1,9 @@
 package user
 
 import (
+	"context"
 	"errors"
+	"hongze/hongze_yb/global"
 	userReq "hongze/hongze_yb/models/request/user"
 	admin2 "hongze/hongze_yb/models/tables/admin"
 	"hongze/hongze_yb/models/tables/company"
@@ -432,6 +434,13 @@ func GetUserTabBar(userInfo user.UserInfo, version string) (list []string, err e
 
 // PcSendSmsCode 发送手机短信
 func PcSendSmsCode(mobile, areaNum string) (err error, errMsg string) {
+	cacheKey := utils.HONGZEYB_ + "REPORT_SMS_CODE_PC:GetSmsCode:" + areaNum + ":" + mobile
+	smsCodeStr, _ := global.Redis.Get(context.TODO(), cacheKey).Result()
+	if smsCodeStr != "" {
+		err = errors.New("请勿频繁发送验证码")
+		return
+	}
+
 	if mobile == "" {
 		err = errors.New("请输入手机号")
 		return
@@ -447,6 +456,7 @@ func PcSendSmsCode(mobile, areaNum string) (err error, errMsg string) {
 	}
 	//发送成功
 	if result {
+		// 验证码存库
 		item := &msg_code.MsgCode{
 			Mobile:          mobile,
 			Code:            msgCode,
@@ -456,6 +466,7 @@ func PcSendSmsCode(mobile, areaNum string) (err error, errMsg string) {
 			LastUpdatedTime: time.Time{},
 		}
 		err = item.Create()
+		global.Redis.Set(context.TODO(), cacheKey, msgCode, 60*time.Second)
 	} else {
 		err = errors.New("短信发送失败")
 	}
@@ -472,6 +483,13 @@ func PcSendEmailCode(email string) (err error, errMsg string) {
 		err = errors.New("邮箱格式错误,请重新输入")
 		return
 	}
+	cacheKey := utils.HONGZEYB_ + "REPORT_SMS_CODE_PC:PcSendEmailCode:" + email
+	smsCodeStr, _ := global.Redis.Get(context.TODO(), cacheKey).Result()
+	if smsCodeStr != "" {
+		err = errors.New("请勿频繁发送验证码")
+		return
+	}
+
 	msgCode := utils.GetRandDigit(4)
 	content := "尊敬的用户:</br>您好,感谢您使用弘则研究,您正在进行邮箱验证,本次请求的验证码为:" + msgCode + "(为了保障您账号的安全性,请在15分钟内完成验证。)</br>弘则研究团队 </br>" + time.Now().Format(utils.FormatDateCN)
 	title := "弘则研究登陆验证"
@@ -488,6 +506,7 @@ func PcSendEmailCode(email string) (err error, errMsg string) {
 			LastUpdatedTime: time.Time{},
 		}
 		err = item.Create()
+		global.Redis.Set(context.TODO(), cacheKey, msgCode, 60*time.Second)
 	} else {
 		err = errors.New("发送失败")
 	}

+ 3 - 3
logic/yb_community_question/yb_community_question_comment.go

@@ -27,7 +27,7 @@ import (
 )
 
 // Comment 发布留言
-func Comment(user user.UserInfo, communityQuestionID uint32, content string, sourceAgent, isShowName, source int8, qaAvatarUrl string) (ybCommunityQuestionComment *yb_community_question_comment.YbCommunityQuestionComment, err error, errMsg string) {
+func Comment(user user.UserInfo, communityQuestionID uint32, content string, sourceAgent, isShowName, source int8, qaAvatarUrl, copyYb string) (ybCommunityQuestionComment *yb_community_question_comment.YbCommunityQuestionComment, err error, errMsg string) {
 	errMsg = "发布留言失败"
 	defer func() {
 		if err != nil {
@@ -62,8 +62,8 @@ func Comment(user user.UserInfo, communityQuestionID uint32, content string, sou
 	}
 
 	// 敏感词过滤
-	if user.RecordInfo.OpenID != "" && user.RecordInfo.CreatePlatform == 6 { //只有小程序的用户才能走敏感词过滤接口
-		checkResult, tErr := wx_app.MsgSecCheck(user.RecordInfo.OpenID, content)
+	if user.RecordInfo.OpenID != "" && (user.RecordInfo.CreatePlatform == utils.USER_RECORD_PLATFORM_YB || user.RecordInfo.CreatePlatform == utils.USER_RECORD_PLATFORM_COPY_YB) { //只有小程序的用户才能走敏感词过滤接口
+		checkResult, tErr := wx_app.MsgSecCheck(user.RecordInfo.OpenID, content, copyYb)
 		/*if tErr != nil {
 			errMsg = "敏感词过滤失败" + tErr.Error()
 			err = errors.New("敏感词过滤失败")

+ 53 - 0
middleware/check_cygx_auth.go

@@ -0,0 +1,53 @@
+package middleware
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/hongze_yb/controller/response"
+	"hongze/hongze_yb/models/tables/wx_user"
+	"hongze/hongze_yb/services/company"
+	"hongze/hongze_yb/services/user"
+	"hongze/hongze_yb/utils"
+	"strconv"
+)
+
+// CheckCygxAuth 如果带thirdCode就可以看,如果有权限也给其他权限
+func CheckCygxAuth() gin.HandlerFunc {
+
+	return func(c *gin.Context) {
+
+		userInfo := user.GetInfoByClaims(c)
+
+		thirdCodeEncrypt := c.Request.Header.Get("thirdCode")
+
+
+		if thirdCodeEncrypt != "" {
+			thirdCode := utils.DesBase64Decrypt([]byte(thirdCodeEncrypt))
+			userId, err := strconv.Atoi(string(thirdCode))
+			if err != nil {
+				response.FailMsg("参数错误", "参数错误", c)
+				c.Abort()
+				return
+			}
+			_, err = wx_user.GetByUserId(userId)
+			if err != nil {
+				response.FailMsg("获取用户信息失败", "获取用户信息失败", c)
+				c.Abort()
+				return
+			}
+		} else {
+			ok, checkInfo, _, err := company.CheckBaseFiccPermission(userInfo.CompanyID, int(userInfo.UserID))
+			if err != nil {
+				response.FailMsg("用户权限验证失败", "CheckBaseAuth-用户权限验证失败"+err.Error(), c)
+				c.Abort()
+				return
+			}
+			if !ok {
+				response.AuthError(checkInfo, "暂无权限", c)
+				c.Abort()
+				return
+			}
+		}
+
+		c.Next()
+	}
+}

+ 54 - 17
middleware/common.go

@@ -1,27 +1,64 @@
 package middleware
 
 import (
+	"bytes"
+	"fmt"
 	"github.com/gin-gonic/gin"
+	"hongze/hongze_yb/utils"
+	"io"
+	"net/http"
 	"strconv"
 )
 
 // Common 公共中间件
-func Common() gin.HandlerFunc {
-	return func(c *gin.Context) {
-		var currPage, pageSize int
-		reqPage := c.DefaultQuery("curr_page", "0")
-		currPage, _ = strconv.Atoi(reqPage)
-		if currPage <= 0 {
-			currPage = 1
-		}
-
-		reqPageSize := c.DefaultQuery("page_size", "0")
-		pageSize, _ = strconv.Atoi(reqPageSize)
-		if pageSize <= 0 {
-			pageSize = 20
-		}
-		c.Set("curr_page", currPage)
-		c.Set("page_size", pageSize)
-		c.Next()
+type Common struct{}
+
+// RequestLog
+// @Description: 请求参数日志
+// @author: Roc
+// @receiver common
+// @datetime 2024-10-31 10:19:36
+// @param c *gin.Context
+func (common *Common) RequestLog(c *gin.Context) {
+	// 读取请求体
+	body, err := io.ReadAll(c.Request.Body)
+	if err != nil {
+		//log.Printf("Error reading request body: %v", err)
+		c.AbortWithStatus(http.StatusInternalServerError)
+		return
+	}
+
+	// 将请求地址添加到上下文的日志中
+	utils.SetContextLogListByClaims(c, fmt.Sprint("Url:", c.Request.RequestURI))
+	// 将请求参数添加到上下文的日志中
+	utils.SetContextLogListByClaims(c, fmt.Sprint("RequestBody:", string(body)))
+
+	// 将请求体恢复到原始状态
+	c.Request.Body = io.NopCloser(bytes.NewBuffer(body))
+
+	c.Next()
+}
+
+// Page
+// @Description: 分页参数
+// @author: Roc
+// @receiver common
+// @datetime 2024-10-31 10:19:36
+// @param c *gin.Context
+func (common *Common) Page(c *gin.Context) {
+	var currPage, pageSize int
+	reqPage := c.DefaultQuery("curr_page", "0")
+	currPage, _ = strconv.Atoi(reqPage)
+	if currPage <= 0 {
+		currPage = 1
+	}
+
+	reqPageSize := c.DefaultQuery("page_size", "0")
+	pageSize, _ = strconv.Atoi(reqPageSize)
+	if pageSize <= 0 {
+		pageSize = 20
 	}
+	c.Set("curr_page", currPage)
+	c.Set("page_size", pageSize)
+	c.Next()
 }

+ 4 - 1
middleware/token.go

@@ -1,6 +1,7 @@
 package middleware
 
 import (
+	"fmt"
 	"github.com/gin-gonic/gin"
 	"hongze/hongze_yb/controller/response"
 	"hongze/hongze_yb/models/tables/rddp/session"
@@ -22,6 +23,8 @@ func Token() gin.HandlerFunc {
 			c.Abort()
 			return
 		}
+		utils.SetContextLogListByClaims(c, fmt.Sprint("Token:", token))
+
 		sessionInfo, err := session.GetTokenByToken(token)
 		if err != nil {
 			if err == utils.ErrNoRow {
@@ -46,7 +49,7 @@ func Token() gin.HandlerFunc {
 			tmpUserInfo, tmpErr := services.GetWxUserItemByOpenId(sessionInfo.OpenID)
 			userInfo = tmpUserInfo
 			err = tmpErr
-			if err != nil && err != services.ERR_NO_USER_RECORD && err != services.ERR_USER_NOT_BIND{
+			if err != nil && err != services.ERR_NO_USER_RECORD && err != services.ERR_USER_NOT_BIND {
 				response.TokenError(nil, "数据异常!", "openid查询用户信息错误", c)
 				c.Abort()
 				return

+ 5 - 2
models/request/public.go

@@ -1,8 +1,11 @@
 package request
 
 type RddpReportShareImgReq struct {
-	Source string `description:"来源" json:"source"`
-	Pars   string `description:"动态信息-JSON格式" json:"pars"`
+	Source          string `description:"来源" json:"source"`
+	Pars            string `description:"动态信息-JSON格式" json:"pars"`
+	ReportId        int    `description:"报告ID" json:"report_id"`
+	ReportChapterId int    `description:"报告章节ID" json:"report_chapter_id"`
+	Version         string `description:"版本号" json:"version"`
 }
 
 type WechatWarningReq struct {

+ 13 - 12
models/request/voice_broadcast.go

@@ -23,18 +23,19 @@ type BroadcastMsgSendReq struct {
 }
 
 type SaveBroadcastReq struct {
-	BroadcastId   int    `json:"broadcast_id" form:"broadcast_id" description:"语音播报ID"`
-	BroadcastName string `json:"broadcast_name" description:"语音标题"`
-	SectionId     int    `json:"section_id" form:"section_id" description:"板块ID"`
-	SectionName   string `json:"section_name" form:"section_name" description:"板块名称"`
-	VarietyId     int    `json:"variety_id" form:"variety_id" description:"品种ID"`
-	VarietyName   string `json:"variety_name" form:"variety_name" description:"品种名称"`
-	AuthorId      int    `json:"author_id" form:"author_id" description:"作者ID"`
-	Author        string `json:"author" form:"author" description:"作者名称"`
-	Imgs          string `json:"imgs" form:"imgs" description:"图片,英文逗号拼接"`
-	VoiceSeconds  string `json:"voice_seconds" form:"voice_seconds" description:"音频时长"`
-	VoiceSize     string `json:"voice_size" form:"voice_size" description:"音频大小"`
-	VoiceUrl      string `json:"voice_url" form:"voice_url" description:"音频文件地址"`
+	BroadcastId      int    `json:"broadcast_id" form:"broadcast_id" description:"语音播报ID"`
+	BroadcastName    string `json:"broadcast_name" description:"语音标题"`
+	SectionId        int    `json:"section_id" form:"section_id" description:"板块ID"`
+	SectionName      string `json:"section_name" form:"section_name" description:"板块名称"`
+	VarietyId        int    `json:"variety_id" form:"variety_id" description:"品种ID"`
+	VarietyName      string `json:"variety_name" form:"variety_name" description:"品种名称"`
+	AuthorId         int    `json:"author_id" form:"author_id" description:"作者ID"`
+	Author           string `json:"author" form:"author" description:"作者名称"`
+	Imgs             string `json:"imgs" form:"imgs" description:"图片,英文逗号拼接"`
+	VoiceSeconds     string `json:"voice_seconds" form:"voice_seconds" description:"音频时长"`
+	VoiceSize        string `json:"voice_size" form:"voice_size" description:"音频大小"`
+	VoiceUrl         string `json:"voice_url" form:"voice_url" description:"音频文件地址"`
+	CentralArguments string `json:"central_arguments" form:"central_arguments" description:"核心观点"`
 }
 
 type PublishBroadcastReq struct {

+ 5 - 0
models/response/base_config.go

@@ -0,0 +1,5 @@
+package response
+
+type BusinessConfResp struct {
+	Disclaimer string `json:"disclaimer"`
+}

+ 1 - 0
models/response/voice_broadcast.go

@@ -27,6 +27,7 @@ type Broadcast struct {
 	PublishTime      string   `description:"发布时间"`
 	PrePublishTime   string   `description:"定时发布时间"`
 	Imgs             []string `description:"图片"`
+	CentralArguments string   `description:"核心观点"`
 }
 
 type VarietyList struct {

+ 1 - 1
models/tables/banner/query.go

@@ -4,7 +4,7 @@ import "hongze/hongze_yb/global"
 
 // GetBannerList
 func GetBannerList(cond string, page, limit int) (list []*Banner, err error) {
-	err = global.DEFAULT_MYSQL.Where(cond).Limit(limit).Offset((page - 1) * limit).Find(&list).Error
+	err = global.DEFAULT_MYSQL.Where(cond).Order("create_time desc").Limit(limit).Offset((page - 1) * limit).Find(&list).Error
 	return
 }
 

+ 19 - 0
models/tables/business_conf/business.conf.go

@@ -0,0 +1,19 @@
+package business_conf
+
+import (
+	"time"
+)
+
+type BusinessConf struct {
+	Id         int       `gorm:"column:id;primary_key;AUTO_INCREMENT;NOT NULL" json:"id"`
+	ConfKey    string    `gorm:"column:conf_key;default:;NOT NULL;comment:'配置Key'" json:"conf_key"`
+	ConfVal    string    `gorm:"column:conf_val;comment:'配置值'" json:"conf_val"`
+	ValType    int       `gorm:"column:val_type;default:0;NOT NULL;comment:'1-字符串;2-数值;3-数组;4-富文本;'" json:"val_type"`
+	Necessary  int       `gorm:"column:necessary;default:0;NOT NULL;comment:'是否必填:0-否;1-是'" json:"necessary"`
+	Remark     string    `gorm:"column:remark;default:;NOT NULL;comment:'备注'" json:"remark"`
+	CreateTime time.Time `gorm:"column:create_time;default:NULL;comment:'创建时间'" json:"create_time"`
+}
+
+func (b *BusinessConf) TableName() string {
+	return "business_conf"
+}

+ 10 - 0
models/tables/business_conf/query.go

@@ -0,0 +1,10 @@
+package business_conf
+
+import "hongze/hongze_yb/global"
+
+// GetItemByConfKey 获取配置项
+func (b *BusinessConf) GetItemByConfKey(key string) (item *BusinessConf, err error) {
+	sql := `SELECT * FROM business_conf WHERE conf_key = ? `
+	err = global.MYSQL["eta"].Raw(sql, key).First(&item).Error
+	return
+}

+ 7 - 1
models/tables/chart_edb_mapping/query.go

@@ -35,6 +35,7 @@ type ChartEdbInfoMapping struct {
 	ChartStyle        string    `description:"图表类型"`
 	ChartColor        string    `description:"颜色"`
 	ChartWidth        float64   `description:"线条大小"`
+	ChartScale        float64   `description:"参考刻度线"`
 	LatestDate        time.Time `description:"数据最新日期"`
 	LatestValue       float64   `description:"数据最新值"`
 	UniqueCode        string    `description:"指标唯一编码"`
@@ -55,6 +56,8 @@ type ChartEdbInfoMapping struct {
 	ConvertValue        float64   `description:"数据转换值"`
 	ConvertUnit         string    `description:"数据转换单位"`
 	ConvertEnUnit       string    `description:"数据转换单位"`
+	ClassifyId          int       `description:"分类id"`
+	IsJoinPermission    int       `description:"是否加入权限管控,0:不加入;1:加入;默认:0"`
 }
 
 type ChartEdbInfoMappingList struct {
@@ -81,6 +84,9 @@ func (ct *ChartLibEdbInfoMappingTime) UnmarshalJSON(data []byte) error {
 	if s[0] == '"' {
 		s = s[1 : len(s)-1]
 	}
+	if s == "" {
+		return nil
+	}
 	t, err := time.ParseInLocation(utils.FormatDate, s, time.Local)
 	if err != nil {
 		return err
@@ -91,7 +97,7 @@ func (ct *ChartLibEdbInfoMappingTime) UnmarshalJSON(data []byte) error {
 
 // GetMappingListByChartInfoId 根据图表ID获取指标映射列表
 func GetMappingListByChartInfoId(chartInfoId int) (list []*ChartEdbInfoMapping, err error) {
-	aField := `a.chart_edb_mapping_id,a.chart_info_id,a.edb_info_id,a.create_time,a.modify_time,a.unique_code,a.max_data,a.min_data,a.is_order,a.is_axis,a.edb_info_type,a.lead_value,a.lead_unit,a.chart_style,a.chart_color,a.predict_chart_color,a.chart_width,a.source as mapping_source`
+	aField := `a.chart_edb_mapping_id,a.chart_info_id,a.edb_info_id,a.create_time,a.modify_time,a.unique_code,a.max_data,a.min_data,a.is_order,a.is_axis,a.edb_info_type,a.lead_value,a.lead_unit,a.chart_style,a.chart_color,a.predict_chart_color,a.chart_width,a.source as mapping_source,a.chart_scale`
 
 	sql := ` SELECT ` + aField + `,b.source_name,b.source,b.sub_source,b.edb_code,b.edb_name,b.edb_name_en,b.frequency,b.unit,b.unit_en,b.start_date,b.end_date,b.modify_time,b.latest_date,b.latest_value,b.unique_code,b.edb_info_type AS edb_info_category_type,b.edb_type,
 a.is_convert, a.convert_type, a.convert_value, a.convert_unit, a.convert_en_unit 

+ 113 - 1
models/tables/edb_data/query.go

@@ -3,13 +3,15 @@ package edb_data
 import (
 	"errors"
 	"fmt"
-	"go.mongodb.org/mongo-driver/bson"
 	"hongze/hongze_yb/global"
 	"hongze/hongze_yb/models/mgo"
+	"hongze/hongze_yb/models/tables/chart_edb_mapping"
 	"hongze/hongze_yb/models/tables/edb_source"
 	"hongze/hongze_yb/utils"
 	"strconv"
 	"time"
+
+	"go.mongodb.org/mongo-driver/bson"
 )
 
 // GetEdbDataTableName 指标数据->存储表
@@ -172,6 +174,8 @@ func GetEdbDataTableName(source, subSource int) (tableName string) {
 		tableName = "edb_data_calculate_zdyfx" // 自定义分析->74
 	case utils.DATA_SOURCE_CALCULATE_RJZ: //日均值->75
 		tableName = "edb_data_calculate_rjz"
+	case utils.DATA_SOURCE_PREDICT: // 基础预测指标->30
+		tableName = "edb_data_predict_base"
 	default:
 		edbSource, _ := edb_source.GetEdbSourceBySource(source)
 		if edbSource != nil {
@@ -221,6 +225,10 @@ type SeasonExtraItem struct {
 	XStartDate  string              `description:"横坐标显示的起始日"`
 	XEndDate    string              `description:"横坐标显示的截止日"`
 	JumpYear    int                 `description:"横坐标日期是否跨年,1跨年,0不跨年"`
+	RightAxis                   SeasonRightAxis             `description:"自定义右轴指标"`
+	MaxMinLimits                MaxMinLimits                `description:"自定义同期上下限"`
+	SamePeriodAverage           SamePeriodAverage           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviation `description:"自定义同期标准差"`
 }
 
 type SeasonChartLegend struct {
@@ -383,3 +391,107 @@ func getThsHfEdbDataListByMongo(source, subSource, edbInfoId int, startDate, end
 
 	return
 }
+
+// 自定义同期均线
+type SamePeriodAverageResp struct {
+	Color     string                   `description:"颜色"`
+	Year      int                      `description:"均线取值范围"`
+	Legend    string                   `description:"图例名称"`
+	LineType  string                   `description:"线型"`
+	LineWidth float64                  `description:"线宽"`
+	IsShow    bool                     `description:"是否显示"`
+	List      []*SamePeriodAverageData `description:"自定义均线列表"`
+	IsAdd     bool                     `description:"是否添加"`
+}
+
+type SamePeriodAverageData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	Value         float64 `description:"均值"`
+}
+
+// 自定义同期标准差
+type SamePeriodStandardDeviation struct {
+	Color    string  `description:"颜色"`
+	Year     int     `description:"标准差取值范围"`
+	Legend   string  `description:"图例名称"`
+	Multiple float64 `description:"标准差倍数"`
+	IsShow   bool    `description:"是否显示"`
+	IsAdd    bool    `description:"是否添加"`
+}
+
+type SamePeriodStandardDeviationResp struct {
+	Color    string              `description:"颜色"`
+	Year     int                 `description:"标准差取值范围"`
+	Legend   string              `description:"图例名称"`
+	Multiple float64             `description:"标准差倍数"`
+	IsShow   bool                `description:"是否显示"`
+	List     []*MaxMinLimitsData `description:"自定义标准差列表"`
+	IsAdd    bool                `description:"是否添加"`
+}
+
+type SeasonChartResp struct {
+	MaxMinLimits                MaxMinLimitsResp                `description:"自定义上下限"`
+	SamePeriodAverage           SamePeriodAverageResp           `description:"自定义同期均线"`
+	SamePeriodStandardDeviation SamePeriodStandardDeviationResp `description:"自定义同期标准差线"`
+	RightAxis                   SeasonRightAxisResp             `description:"自定义右轴指标"`
+}
+
+// 自定义右轴指标
+type SeasonRightAxisResp struct {
+	SeasonRightAxis
+	EdbInfoList []*chart_edb_mapping.ChartEdbInfoMappingList
+}
+
+type MaxMinLimitsResp struct {
+	List   []*MaxMinLimitsData `description:"自定义上下限列表"`
+	Color  string              `description:"颜色"`
+	Year   int                 `description:"上下限取值范围"`
+	Legend string              `description:"图例名称"`
+	IsShow bool                `description:"是否显示"`
+	IsAdd  bool                `description:"是否添加"`
+}
+
+type MaxMinLimitsData struct {
+	DataTime      string  `description:"数据日期"`
+	DataTimestamp int64   `description:"数据日期时间戳"`
+	MaxValue      float64 `description:"最大值"`
+	MinValue      float64 `description:"最小值"`
+}
+
+// 自定义右轴指标
+type SeasonRightAxis struct {
+	IndicatorType int     `description:"右轴指标类型 1:左轴指标同比,2:指标库,3:预测指标 "`
+	Style         string  `description:"生成样式"`
+	Shape         string  `description:"形状"`
+	ChartColor    string  `description:"图表颜色"`
+	Size          float64 `description:"大小"`
+	Legend        string  `description:"图例名称"`
+	NumFormat     int     `description:"数值格式 1:百分比 2:小数"`
+	IsConnected   int     `description:"是否连接 0不连接 1连接"`
+	LineColor     string  `description:"线条颜色"`
+	LineWidth     float64 `description:"线条宽度"`
+	LineStyle     string  `description:"线条样式"`
+	IsShow        bool    `description:"是否显示"`
+	IsAdd         bool    `description:"是否添加"`
+}
+
+// 自定义同期上下限
+type MaxMinLimits struct {
+	Color  string `description:"颜色"`
+	Year   int    `description:"上下限取值范围"`
+	Legend string `description:"图例名称"`
+	IsShow bool   `description:"是否显示"`
+	IsAdd  bool   `description:"是否添加"`
+}
+
+// 自定义同期均线
+type SamePeriodAverage struct {
+	Color     string  `description:"颜色"`
+	Year      int     `description:"均线取值范围"`
+	Legend    string  `description:"图例名称"`
+	LineType  string  `description:"线型"`
+	LineWidth float64 `description:"线宽"`
+	IsShow    bool    `description:"是否显示"`
+	IsAdd     bool    `description:"是否添加"`
+}

+ 1 - 1
models/tables/rddp/msg_code/query.go

@@ -6,6 +6,6 @@ import (
 
 // GetMsgCode 根据token获取信息
 func GetMsgCode(mobile, code string) (item *MsgCode, err error) {
-	err = rddp.GetDb().Where("mobile = ? and code = ? and FROM_UNIXTIME(expired_in)>=NOW() ", mobile, code).First(&item).Error
+	err = rddp.GetDb().Where("mobile = ? and FROM_UNIXTIME(expired_in)>=NOW() ", mobile).Order("msg_code_id DESC").First(&item).Error
 	return
 }

+ 1 - 0
models/tables/voice_broadcast/voice_broadcast.go

@@ -20,6 +20,7 @@ type VoiceBroadcast struct {
 	PrePublishTime   string `description:"预发布时间"`
 	MsgState         int    `description:"消息推送状态:0-待推送 1-已推送"`
 	MsgTime          string `description:"消息推送时间"`
+	CentralArguments string `description:"核心观点"`
 }
 
 // TableName get sql table name.获取数据库表名

+ 18 - 2
models/tables/yb_config/entity.go

@@ -16,8 +16,24 @@ func (m *YbConfig) TableName() string {
 	return "yb_config"
 }
 
+// YbConfigColumns get sql column name.获取数据库列名
+var YbConfigColumns = struct {
+	ConfigID    string
+	ConfigCode  string
+	ConfigValue string
+	Remark      string
+	CreateTime  string
+}{
+	ConfigID:    "config_id",
+	ConfigCode:  "config_code",
+	ConfigValue: "config_value",
+	Remark:      "remark",
+	CreateTime:  "create_time",
+}
+
 const (
-	UserChartCollectMax   = "user_chart_collect_max"   // 用户图表收藏上限
-	TelAreaList           = "tel_area_list"            // 手机号区号配置
+	KeyUseCopyYb        = "use_copy_yb"
+	UserChartCollectMax = "user_chart_collect_max" // 用户图表收藏上限
+	TelAreaList         = "tel_area_list"
 	DefaultSpeakerHeadPic = "default_speaker_head_pic" // 默认演讲人头像
 )

+ 8 - 0
models/tables/yb_config/model.go

@@ -2,6 +2,14 @@ package yb_config
 
 import "hongze/hongze_yb/global"
 
+// GetConfigByCode 查询配置详情
+func GetConfigByCode(configCode string) (item *YbConfig, err error) {
+	err = global.DEFAULT_MYSQL.Model(YbConfig{}).
+		Where("config_code = ?", configCode).
+		First(&item).Error
+	return
+}
+
 func (m *YbConfig) Create() (err error) {
 	err = global.DEFAULT_MYSQL.Create(m).Error
 	return

+ 11 - 0
routers/base_config.go

@@ -0,0 +1,11 @@
+package routers
+
+import (
+	"github.com/gin-gonic/gin"
+	"hongze/hongze_yb/controller"
+)
+
+func InitBaseConfig(r *gin.Engine) {
+	rGroupNotLogin := r.Group("api/base")
+	rGroupNotLogin.GET("/business_conf", controller.GetBusinessConf)
+}

+ 0 - 1
routers/report.go

@@ -27,5 +27,4 @@ func InitReport(r *gin.Engine) {
 	rGroup2.GET("/detail", report.ClassifyDetail)
 	rGroup2.GET("/detail/reports", report.ClassifyDetailReports)
 	rGroup2.GET("/menu/list", report.ClassifyMenuList)
-
 }

+ 1 - 1
routers/voice_broadcast.go

@@ -15,7 +15,7 @@ func InitVoiceBroadcast(r *gin.Engine) {
 	rGroup.GET("/delete", voice_broadcast.DelBroadcast)
 	rGroup.POST("/statistics/add", voice_broadcast.AddStatistics)
 	// 权限校验
-	rGroup2 := r.Group("api/voice/broadcast").Use(middleware.Token(), middleware.CheckBaseAuth())
+	rGroup2 := r.Group("api/voice/broadcast").Use(middleware.Token(), middleware.CheckCygxAuth())
 	rGroup2.POST("/list", voice_broadcast.BroadcastList)
 	rGroup2.GET("/detail", voice_broadcast.BroadcastDetail)
 	rGroup2.POST("/msg_send", voice_broadcast.MsgSend)

+ 57 - 0
services/base_config/business_conf.go

@@ -0,0 +1,57 @@
+package base_config
+
+import (
+	"context"
+	"fmt"
+	"hongze/hongze_yb/global"
+	"hongze/hongze_yb/models/tables/business_conf"
+	"hongze/hongze_yb/utils"
+	"html"
+	"time"
+)
+
+func GetBusinessConfDisclaimer() (disclaimer string, err error) {
+	cacheKey := utils.HONGZEYB_ + "business_conf:disclaimer"
+	disclaimer, _ = global.Redis.Get(context.TODO(), cacheKey).Result()
+	if disclaimer != "" {
+		disclaimer = html.UnescapeString(disclaimer)
+		return
+	}
+	obj := new(business_conf.BusinessConf)
+	item, err := obj.GetItemByConfKey("Disclaimer")
+	if err != nil {
+		err = fmt.Errorf("GetBusinessConfDisclaimer err:%v", err)
+		return
+	}
+	if item.Id <= 0 {
+		err = fmt.Errorf("研报声明未配置")
+		return
+	}
+	disclaimer = item.ConfVal
+	_ = global.Redis.Set(context.TODO(), cacheKey, disclaimer, 24*time.Hour)
+	disclaimer = html.UnescapeString(disclaimer)
+	return
+}
+
+// CheckIsOpenChartExpired
+// @Description: 判断是否开启图表授权
+// @author: Roc
+// @datetime 2025-01-07 16:51:34
+// @return isOpenChartExpired bool
+// @return err error
+func CheckIsOpenChartExpired() (isOpenChartExpired bool, err error) {
+	obj := new(business_conf.BusinessConf)
+	item, err := obj.GetItemByConfKey("IsOpenChartExpired")
+	if err != nil {
+		err = fmt.Errorf("checkBusinessConfIsOpenChartExpired err:%v", err)
+		return
+	}
+	if item.Id <= 0 {
+		return
+	}
+	if item.ConfVal == `true` {
+		isOpenChartExpired = true
+	}
+
+	return
+}

+ 1171 - 7
services/chart/chart_info.go

@@ -652,6 +652,19 @@ func GetChartEdbData(chartInfoId, chartType int, calendar, startDate, endDate st
 			}
 		}
 		dataResp = chart_info.ChartTimeCombineDataResp{IsHeap: timeConf.IsHeap}
+	case 2: // 季节性图
+		if seasonExtraConfig != "" {
+			// 季节性图计算不管图上数据时间,拿所有数据
+			_, tempEdbList, e := getEdbDataMapListForSeason(chartInfoId, chartType, calendar, "1990-01-01", "", mappingList, seasonExtraConfig)
+			if e != nil {
+				err = e
+				return
+			}
+			dataResp, err = SeasonChartData(tempEdbList, seasonExtraConfig)
+		} else {
+			// 兼容无配置的老图
+			dataResp = new(edbDataModel.SeasonChartResp)
+		}
 	case 7: // 柱形图
 		barChartConf := extraConfig.(request.BarChartInfoReq)
 		xEdbIdValue, yDataList, err = BarChartData(mappingList, edbDataListMap, barChartConf.DateList, barChartConf.Sort)
@@ -699,6 +712,800 @@ func GetChartEdbData(chartInfoId, chartType int, calendar, startDate, endDate st
 	return
 }
 
+// getEdbDataMapListForSeason 获取指标最后的基础数据-插值补充好数据-季节性图用
+func getEdbDataMapListForSeason(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*chartEdbMappingModel.ChartEdbInfoMapping, seasonExtraConfig string) (edbDataListMap map[int][]*edbDataModel.EdbDataList, edbList []*chartEdbMappingModel.ChartEdbInfoMappingList, err error) {
+	// 指标对应的所有数据
+	edbDataListMap = make(map[int][]*edbDataModel.EdbDataList)
+	seasonXStartDateWithYear := ""
+	seasonXEndDateWithYear := ""
+	if chartType == 2 {
+		sort.Slice(mappingList, func(i, j int) bool {
+			return mappingList[i].IsAxis > mappingList[j].IsAxis
+		})
+	}
+	for _, v := range mappingList {
+		//fmt.Println("v:", v.EdbInfoId)
+		item := new(chartEdbMappingModel.ChartEdbInfoMappingList)
+		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
+			if len(mappingList) > 1 {
+				item.IsAxis = v.IsAxis
+			}
+		} 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 := item.MoveLatestDate.Local()
+					if tmpErr != nil {
+						err = tmpErr
+						return
+					}
+					item.MoveLatestDate = moveLatestDateTime.AddDate(0, 0, day)
+				}
+			} 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([]*edbDataModel.EdbDataList, 0)
+		//fmt.Println("chart:", v.Source, v.EdbInfoId, startDateReal, endDate)
+		//fmt.Println("calendarPreYear:", calendarPreYear)
+		//var newEdbInfo *data_manage.EdbInfo
+		switch v.EdbInfoCategoryType {
+		case 0:
+			dataList, err = edbDataModel.GetEdbDataList(v.Source, v.SubSource, 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
+		}
+
+		handleDataMap := make(map[string]float64)
+		newDataList, _, _, e := HandleDataByLinearRegressionToListV3(dataList, handleDataMap)
+		if e != nil {
+			err = e
+			return
+		}
+
+		if v.IsConvert == 1 {
+			switch v.ConvertType {
+			case 1:
+				for i, data := range newDataList {
+					newDataList[i].Value = data.Value * v.ConvertValue
+				}
+				//item.MaxData = item.MaxData * v.ConvertValue
+				//item.MinData = item.MinData * v.ConvertValue
+			case 2:
+				for i, data := range newDataList {
+					newDataList[i].Value = data.Value / v.ConvertValue
+				}
+				//item.MaxData = item.MaxData / v.ConvertValue
+				//item.MinData = item.MinData / v.ConvertValue
+			case 3:
+				for i, data := range newDataList {
+					if data.Value <= 0 {
+						err = errors.New("数据中含有负数或0,无法对数运算")
+						return
+					}
+					newDataList[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] = newDataList
+
+		if diffSeconds != 0 && v.EdbInfoType == 0 {
+			newDataListLen := len(newDataList)
+			for i := 0; i < newDataListLen; i++ {
+				newDataList[i].DataTimestamp = newDataList[i].DataTimestamp - diffSeconds
+			}
+		}
+
+		if chartType == 2 && item.IsAxis == 1 {
+			latestDate := v.LatestDate
+			if latestDate.IsZero() {
+				//item.DataList = newDataList
+				item.IsNullData = true
+				edbList = append(edbList, item)
+				continue
+			}
+
+			if calendar == "农历" {
+				if len(newDataList) <= 0 {
+					result := new(edbDataModel.EdbDataResult)
+					item.DataList = result
+				} else {
+					result, tmpErr := edbDataService.AddCalculateQuarterV6(newDataList)
+					if tmpErr != nil {
+						err = errors.New("获取农历数据失败,Err:" + tmpErr.Error())
+						return
+					}
+					quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
+					if tErr != nil {
+						err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
+						return
+					}
+					item.DataList = quarterDataList
+					seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+					seasonXEndDateWithYear = seasonXEndDateWithYearTmp
+				}
+
+			} else {
+				quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDate(newDataList, latestDate, seasonExtraConfig)
+				if tErr != nil {
+					err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
+					return
+				}
+				item.DataList = quarterDataList
+				seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+				seasonXEndDateWithYear = seasonXEndDateWithYearTmp
+			}
+
+		} else if chartType == 2 && item.IsAxis == 0 {
+			newDataList := make([]*edbDataModel.EdbDataList, 0)
+			for _, v := range dataList {
+				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
+				if e != nil {
+					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
+					return
+				}
+				seasonXStartDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXStartDateWithYear)
+				seasonXEndDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXEndDateWithYear)
+				if dataTime.Equal(seasonXStartDateWithYearT) || (dataTime.After(seasonXStartDateWithYearT) && dataTime.Before(seasonXEndDateWithYearT)) || dataTime.Equal(seasonXEndDateWithYearT) {
+					newDataList = append(newDataList, v)
+				}
+			}
+			item.DataList = newDataList
+		} else if chartType == 7 || chartType == utils.CHART_TYPE_RADAR { //柱方图
+			//item.DataList = newDataList
+		} else {
+			item.DataList = newDataList
+		}
+		edbList = append(edbList, item)
+	}
+
+	return
+}
+
+// SeasonChartData 季节性图的数据处理
+func SeasonChartData(dataList []*chartEdbMappingModel.ChartEdbInfoMappingList, seasonExtraConfig string) (dataResp edbDataModel.SeasonChartResp, err error) {
+	var seasonConfig edbDataModel.SeasonExtraItem
+	err = json.Unmarshal([]byte(seasonExtraConfig), &seasonConfig)
+	if err != nil {
+		err = errors.New("季节性图配置异常, Err:" + err.Error())
+		return
+	}
+
+	for _, mappingItem := range dataList {
+		if mappingItem.IsAxis == 0 {
+			continue
+		}
+		quarterDataList, ok := mappingItem.DataList.(edbDataModel.QuarterDataList)
+		if !ok {
+			continue
+		}
+		// 上下限区间
+		if seasonConfig.MaxMinLimits.Year > 0 {
+			startYear := time.Now().AddDate(-seasonConfig.MaxMinLimits.Year, 0, 0).Year()
+			dataResp.MaxMinLimits.List = make([]*edbDataModel.MaxMinLimitsData, 0)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeList := make([]string, 0)
+			maxValueMap := make(map[time.Time]float64)
+			minValueMap := make(map[time.Time]float64)
+
+			maxMinDataList := make([]*edbDataModel.MaxMinLimitsData, 0)
+			var startDateStr string
+			var endDateStr string
+			var newDate time.Time
+
+			startDateStr = fmt.Sprintf("%d-%s", time.Now().Year(), seasonConfig.XStartDate)
+			endDateStr = fmt.Sprintf("%d-%s", time.Now().Year(), seasonConfig.XEndDate)
+			startDate, e := time.Parse(utils.FormatDate, startDateStr)
+			if e != nil {
+				err = e
+				return
+			}
+			endDate, e := time.Parse(utils.FormatDate, endDateStr)
+			if e != nil {
+				err = e
+				return
+			}
+			// 日度 周度插值
+			for _, v := range quarterDataList {
+				if mappingItem.Frequency == "日度" || mappingItem.Frequency == "周度" {
+					handleDataMap := make(map[string]float64)
+					dataTimeList, _, err = HandleDataByLinearRegressionToList(v.DataList, handleDataMap)
+					if err != nil {
+						err = errors.New("插值处理数据异常, Err:" + err.Error())
+						return
+					}
+					// 不包含当年
+					if v.ChartLegend == strconv.Itoa(time.Now().Year()) {
+						continue
+					}
+					// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
+					chartLegend, _ := strconv.Atoi(v.ChartLegend)
+					if chartLegend < startYear {
+						continue
+					}
+					for _, date := range dataTimeList {
+						dateTime, e := time.Parse(utils.FormatDate, date)
+						if e != nil {
+							err = errors.New("时间格式化异常, Err:" + e.Error())
+							return
+						}
+						if dateTime.Year() < startYear {
+							continue
+						}
+						// 不包含2月29号
+						if dateTime.Month() == 2 && dateTime.Day() == 29 {
+							continue
+						}
+						newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+						if seasonConfig.JumpYear == 1 {
+							if startDate.After(endDate) {
+								// 如果跨年且不到一年
+								// 全年截取一部分
+								if newDate.Before(startDate.AddDate(0, 0, 1)) && newDate.After(endDate) {
+									continue
+								}
+								if newDate.After(startDate.AddDate(0, 0, -1)) {
+									// 减一年
+									newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year()-1, 0, 0)
+								}
+								// 处理上下限列表
+								if value, ok := maxValueMap[newDate]; ok {
+									if value < handleDataMap[date] {
+										maxValueMap[newDate] = handleDataMap[date]
+									}
+								} else {
+									maxValueMap[newDate] = handleDataMap[date]
+								}
+
+								if value, ok := minValueMap[newDate]; ok {
+									if value > handleDataMap[date] {
+										minValueMap[newDate] = handleDataMap[date]
+									}
+								} else {
+									minValueMap[newDate] = handleDataMap[date]
+								}
+
+								dataTimeMap[newDate] = newDate
+							} else {
+								// 如果跨年且大于等于一年
+								// double后截取
+								if newDate.After(startDate) {
+									// 处理上下限列表
+									if value, ok := maxValueMap[newDate]; ok {
+										if value < handleDataMap[date] {
+											maxValueMap[newDate] = handleDataMap[date]
+										}
+									} else {
+										maxValueMap[newDate] = handleDataMap[date]
+									}
+
+									if value, ok := minValueMap[newDate]; ok {
+										if value > handleDataMap[date] {
+											minValueMap[newDate] = handleDataMap[date]
+										}
+									} else {
+										minValueMap[newDate] = handleDataMap[date]
+									}
+
+									dataTimeMap[newDate] = newDate
+								}
+
+								newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year()+1, 0, 0)
+								newEndDate := endDate.AddDate(1, 0, 0)
+								if newDate.Before(newEndDate) {
+									// 处理上下限列表
+									if value, ok := maxValueMap[newDate]; ok {
+										if value < handleDataMap[date] {
+											maxValueMap[newDate] = handleDataMap[date]
+										}
+									} else {
+										maxValueMap[newDate] = handleDataMap[date]
+									}
+
+									if value, ok := minValueMap[newDate]; ok {
+										if value > handleDataMap[date] {
+											minValueMap[newDate] = handleDataMap[date]
+										}
+									} else {
+										minValueMap[newDate] = handleDataMap[date]
+									}
+
+									dataTimeMap[newDate] = newDate
+								}
+
+							}
+						} else {
+							// 如果不跨年 正常获取
+							// 获取当前日期所在区间
+							// 处理上下限列表
+							if value, ok := maxValueMap[newDate]; ok {
+								if value < handleDataMap[date] {
+									maxValueMap[newDate] = handleDataMap[date]
+								}
+							} else {
+								maxValueMap[newDate] = handleDataMap[date]
+							}
+
+							if value, ok := minValueMap[newDate]; ok {
+								if value > handleDataMap[date] {
+									minValueMap[newDate] = handleDataMap[date]
+								}
+							} else {
+								minValueMap[newDate] = handleDataMap[date]
+							}
+
+							dataTimeMap[newDate] = newDate
+						}
+					}
+				} else {
+					// 旬度、月度、季度、半年度 不插值,需要先把日期列表和数据map取出来
+					for _, vv := range v.DataList {
+						dateTime, e := time.Parse(utils.FormatDate, vv.DataTime)
+						if e != nil {
+							err = errors.New("时间格式化异常, Err:" + e.Error())
+							return
+						}
+
+						// 不包含当年
+						if v.ChartLegend == strconv.Itoa(time.Now().Year()) {
+							continue
+						}
+
+						if dateTime.Year() < startYear {
+							continue
+						}
+						// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
+						chartLegend, _ := strconv.Atoi(v.ChartLegend)
+						if chartLegend < startYear {
+							continue
+						}
+						// 月度的2月29号日期改为2月28日
+						if dateTime.Month() == 2 && dateTime.Day() == 29 {
+							dateTime = dateTime.AddDate(0, 0, -1)
+						}
+						newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+						if seasonConfig.JumpYear == 1 {
+							if startDate.After(endDate) {
+								// 如果跨年且不到一年
+								// 全年截取一部分
+								if newDate.Before(startDate.AddDate(0, 0, 1)) && newDate.After(endDate) {
+									continue
+								}
+								if newDate.After(startDate.AddDate(0, 0, -1)) {
+									// 减一年
+									newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year()-1, 0, 0)
+								}
+								// 处理上下限列表
+								if value, ok := maxValueMap[newDate]; ok {
+									if value < vv.Value {
+										maxValueMap[newDate] = vv.Value
+									}
+								} else {
+									maxValueMap[newDate] = vv.Value
+								}
+
+								if value, ok := minValueMap[newDate]; ok {
+									if value > vv.Value {
+										minValueMap[newDate] = vv.Value
+									}
+								} else {
+									minValueMap[newDate] = vv.Value
+								}
+
+								dataTimeMap[newDate] = newDate
+							} else {
+								// 如果跨年且大于等于一年
+								// double后截取
+								if newDate.After(startDate) {
+									// 处理上下限列表
+									if value, ok := maxValueMap[newDate]; ok {
+										if value < vv.Value {
+											maxValueMap[newDate] = vv.Value
+										}
+									} else {
+										maxValueMap[newDate] = vv.Value
+									}
+
+									if value, ok := minValueMap[newDate]; ok {
+										if value > vv.Value {
+											minValueMap[newDate] = vv.Value
+										}
+									} else {
+										minValueMap[newDate] = vv.Value
+									}
+
+									dataTimeMap[newDate] = newDate
+								}
+
+								newDate = dateTime.AddDate(time.Now().Year()-dateTime.Year()+1, 0, 0)
+								newEndDate := endDate.AddDate(1, 0, 0)
+								if newDate.Before(newEndDate) {
+									// 处理上下限列表
+									if value, ok := maxValueMap[newDate]; ok {
+										if value < vv.Value {
+											maxValueMap[newDate] = vv.Value
+										}
+									} else {
+										maxValueMap[newDate] = vv.Value
+									}
+
+									if value, ok := minValueMap[newDate]; ok {
+										if value > vv.Value {
+											minValueMap[newDate] = vv.Value
+										}
+									} else {
+										minValueMap[newDate] = vv.Value
+									}
+
+									dataTimeMap[newDate] = newDate
+								}
+
+							}
+						} else {
+							// 如果不跨年 正常获取
+							// 获取当前日期所在区间
+							// 处理上下限列表
+							if value, ok := maxValueMap[newDate]; ok {
+								if value < vv.Value {
+									maxValueMap[newDate] = vv.Value
+								}
+							} else {
+								maxValueMap[newDate] = vv.Value
+							}
+
+							if value, ok := minValueMap[newDate]; ok {
+								if value > vv.Value {
+									minValueMap[newDate] = vv.Value
+								}
+							} else {
+								minValueMap[newDate] = vv.Value
+							}
+
+							dataTimeMap[newDate] = newDate
+						}
+
+					}
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				maxMinItem := &edbDataModel.MaxMinLimitsData{}
+				if maxValue, ok := maxValueMap[v]; ok {
+					maxMinItem.MaxValue = maxValue
+				} else {
+					maxMinItem.MaxValue = minValueMap[v]
+				}
+
+				if minValue, ok := minValueMap[v]; ok {
+					maxMinItem.MinValue = minValue
+				} else {
+					maxMinItem.MinValue = maxValueMap[v]
+				}
+
+				maxMinItem.DataTime = v.Format(utils.FormatDate)
+				maxMinItem.DataTimestamp = v.UnixNano() / 1e6
+
+				maxMinDataList = append(maxMinDataList, maxMinItem)
+
+			}
+
+			// 排序
+			sort.Slice(maxMinDataList, func(i, j int) bool {
+				return maxMinDataList[i].DataTime < maxMinDataList[j].DataTime
+			})
+
+			dataResp.MaxMinLimits.List = maxMinDataList
+			dataResp.MaxMinLimits.Color = seasonConfig.MaxMinLimits.Color
+			dataResp.MaxMinLimits.Legend = seasonConfig.MaxMinLimits.Legend
+			dataResp.MaxMinLimits.IsShow = seasonConfig.MaxMinLimits.IsShow
+			dataResp.MaxMinLimits.IsAdd = seasonConfig.MaxMinLimits.IsAdd
+			dataResp.MaxMinLimits.Year = seasonConfig.MaxMinLimits.Year
+		}
+
+		// 自定义同期均线
+		if seasonConfig.SamePeriodAverage.Year > 0 {
+			handleDataMap := make(map[string]float64)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeList := make([]string, 0)
+			valueMap := make(map[time.Time]float64)
+			averageDataList := make([]*edbDataModel.SamePeriodAverageData, 0)
+			dateNumMap := make(map[time.Time]float64)
+			for i := len(quarterDataList) - 1; i >= 0; i-- {
+				// 插值成日度
+				dataTimeList, _, err = HandleDataByLinearRegressionToList(quarterDataList[i].DataList, handleDataMap)
+				if err != nil {
+					err = errors.New("插值处理数据异常, Err:" + err.Error())
+					return
+				}
+				for _, date := range dataTimeList {
+					dateTime, e := time.Parse(utils.FormatDate, date)
+					if e != nil {
+						err = errors.New("时间格式化异常, Err:" + e.Error())
+						return
+					}
+					startYear := time.Now().AddDate(-seasonConfig.SamePeriodAverage.Year, 0, 0).Year()
+					if dateTime.Year() < startYear {
+						continue
+					}
+					// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
+					chartLegend, _ := strconv.Atoi(quarterDataList[i].ChartLegend)
+					if chartLegend < startYear {
+						continue
+					}
+					// 不包含2月29号
+					if dateTime.Month() == 2 && dateTime.Day() == 29 {
+						continue
+					}
+					// 不包含当年
+					if quarterDataList[i].ChartLegend == strconv.Itoa(time.Now().Year()) {
+						continue
+					}
+					newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+					// 处理均值
+					if value, ok := valueMap[newDate]; ok {
+						valueMap[newDate] = handleDataMap[date] + value
+					} else {
+						valueMap[newDate] = handleDataMap[date]
+					}
+					dateNumMap[newDate] += 1
+
+					dataTimeMap[newDate] = newDate
+				}
+			}
+
+			//year := float64(seasonConfig.SamePeriodAverage.Year)
+			for _, v := range dataTimeMap {
+				averageItem := &edbDataModel.SamePeriodAverageData{}
+				year := dateNumMap[v]
+				if value, ok := valueMap[v]; ok {
+					if year > 0 {
+						averageItem.Value = value / year
+					} else {
+						averageItem.Value = value
+					}
+				}
+				averageItem.DataTime = v.Format(utils.FormatDate)
+				averageItem.DataTimestamp = v.UnixNano() / 1e6
+
+				averageDataList = append(averageDataList, averageItem)
+
+			}
+
+			// 排序
+			sort.Slice(averageDataList, func(i, j int) bool {
+				return averageDataList[i].DataTime < averageDataList[j].DataTime
+			})
+
+			dataResp.SamePeriodAverage.List = averageDataList
+			dataResp.SamePeriodAverage.Year = seasonConfig.SamePeriodAverage.Year
+			dataResp.SamePeriodAverage.Color = seasonConfig.SamePeriodAverage.Color
+			dataResp.SamePeriodAverage.Legend = seasonConfig.SamePeriodAverage.Legend
+			dataResp.SamePeriodAverage.IsShow = seasonConfig.SamePeriodAverage.IsShow
+			dataResp.SamePeriodAverage.LineType = seasonConfig.SamePeriodAverage.LineType
+			dataResp.SamePeriodAverage.LineWidth = seasonConfig.SamePeriodAverage.LineWidth
+			dataResp.SamePeriodAverage.IsAdd = seasonConfig.SamePeriodAverage.IsAdd
+
+		}
+
+		// 自定义同期标准差
+		if seasonConfig.SamePeriodStandardDeviation.Year > 1 && seasonConfig.SamePeriodStandardDeviation.Multiple > 0 {
+			startYear := time.Now().AddDate(-seasonConfig.SamePeriodStandardDeviation.Year, 0, 0).Year()
+
+			// 先算均值,再算标准差
+			handleDataMap := make(map[string]float64)
+			dataTimeMap := make(map[time.Time]time.Time)
+			dataTimeValueMap := make(map[time.Time][]float64)
+			dataTimeList := make([]string, 0)
+			valueMap := make(map[time.Time]float64)
+
+			samePeriodStandardDeviationList := make([]*edbDataModel.MaxMinLimitsData, 0)
+			for i := len(quarterDataList) - 1; i > len(quarterDataList)-seasonConfig.SamePeriodAverage.Year-1 && i > 0; i-- {
+				// 插值成日度
+				dataTimeList, _, err = HandleDataByLinearRegressionToListV2(quarterDataList[i].DataList, handleDataMap)
+				if err != nil {
+					err = errors.New("插值处理数据异常, Err:" + err.Error())
+					return
+				}
+				for _, date := range dataTimeList {
+					dateTime, e := time.Parse(utils.FormatDate, date)
+					if e != nil {
+						err = errors.New("时间格式化异常, Err:" + e.Error())
+						return
+					}
+					if dateTime.Year() < startYear {
+						continue
+					}
+					// 农历时dataTimeList的年份为最新年份,图例时间的年份才是真实年份
+					chartLegend, _ := strconv.Atoi(quarterDataList[i].ChartLegend)
+					if chartLegend < startYear {
+						continue
+					}
+					// 不包含2月29号
+					if dateTime.Month() == 2 && dateTime.Day() == 29 {
+						continue
+					}
+					newDate := dateTime.AddDate(time.Now().Year()-dateTime.Year(), 0, 0)
+					// 处理均值
+					if value, ok := valueMap[newDate]; ok {
+						valueMap[newDate] = (handleDataMap[date] + value) / 2
+					} else {
+						valueMap[newDate] = handleDataMap[date]
+					}
+
+					dataTimeMap[newDate] = newDate
+					valueList := dataTimeValueMap[newDate]
+					valueList = append(valueList, handleDataMap[date])
+					dataTimeValueMap[newDate] = valueList
+				}
+			}
+
+			for _, v := range dataTimeMap {
+				valueList := dataTimeValueMap[v]
+				stdev := utils.CalculateStandardDeviation(valueList)
+				stdev, _ = decimal.NewFromFloat(stdev).Round(4).Float64()
+
+				maxMinItem := &edbDataModel.MaxMinLimitsData{}
+
+				if value, ok := valueMap[v]; ok {
+					maxMinItem.MaxValue = value + stdev*seasonConfig.SamePeriodStandardDeviation.Multiple
+					maxMinItem.MinValue = value - stdev*seasonConfig.SamePeriodStandardDeviation.Multiple
+				}
+
+				maxMinItem.DataTime = v.Format(utils.FormatDate)
+				maxMinItem.DataTimestamp = v.UnixNano() / 1e6
+
+				samePeriodStandardDeviationList = append(samePeriodStandardDeviationList, maxMinItem)
+
+			}
+
+			// 排序
+			sort.Slice(samePeriodStandardDeviationList, func(i, j int) bool {
+				return samePeriodStandardDeviationList[i].DataTime < samePeriodStandardDeviationList[j].DataTime
+			})
+
+			dataResp.SamePeriodStandardDeviation.List = samePeriodStandardDeviationList
+			dataResp.SamePeriodStandardDeviation.Color = seasonConfig.SamePeriodStandardDeviation.Color
+			dataResp.SamePeriodStandardDeviation.Legend = seasonConfig.SamePeriodStandardDeviation.Legend
+			dataResp.SamePeriodStandardDeviation.IsShow = seasonConfig.SamePeriodStandardDeviation.IsShow
+			dataResp.SamePeriodStandardDeviation.Multiple = seasonConfig.SamePeriodStandardDeviation.Multiple
+			dataResp.SamePeriodStandardDeviation.Year = seasonConfig.SamePeriodStandardDeviation.Year
+			dataResp.SamePeriodStandardDeviation.IsAdd = seasonConfig.SamePeriodStandardDeviation.IsAdd
+		}
+
+		// 自定义右轴
+		if seasonConfig.RightAxis.IndicatorType != 0 {
+			if seasonConfig.RightAxis.IndicatorType == 1 {
+				startTime, _ := time.Parse(utils.FormatDate, mappingItem.StartDate)
+				for i := len(quarterDataList) - 1; i > len(quarterDataList)-2 && i > 0; i-- {
+					var rightMappingItem chartEdbMappingModel.ChartEdbInfoMappingList
+					rightMappingItem = *mappingItem
+					// 计算同比值
+					tbzDataList, minValue, maxValue, e := GetEdbDataTbzForSeason(mappingItem.Frequency, quarterDataList[i].DataList, startTime)
+					if e != nil {
+						err = errors.New("计算同比值失败, Err:" + e.Error())
+						return
+					}
+					rightMappingItem.DataList = tbzDataList
+					rightMappingItem.MaxData = maxValue
+					rightMappingItem.MinData = minValue
+					dataResp.RightAxis.EdbInfoList = append(dataResp.RightAxis.EdbInfoList, &rightMappingItem)
+				}
+			}
+			dataResp.RightAxis.SeasonRightAxis = seasonConfig.RightAxis
+		}
+	}
+
+	return
+}
+
 // GetEdbDataMapList 获取指标最后的基础数据
 func GetEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate string, mappingList []*chartEdbMappingModel.ChartEdbInfoMapping, seasonExtraConfig string) (edbDataListMap map[int][]*edbDataModel.EdbDataList, edbList []*chartEdbMappingModel.ChartEdbInfoMappingList, sourceArr []string, err error) {
 	edbDataListMap, edbList, sourceArr, err = getEdbDataMapList(chartInfoId, chartType, calendar, startDate, endDate, mappingList, seasonExtraConfig)
@@ -711,6 +1518,14 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 	sourceArr = make([]string, 0)
 	// 指标对应的所有数据
 	edbDataListMap = make(map[int][]*edbDataModel.EdbDataList)
+	seasonXStartDateWithYear := ""
+	seasonXEndDateWithYear := ""
+	if chartType == 2 {
+		sort.Slice(mappingList, func(i, j int) bool {
+			return mappingList[i].IsAxis > mappingList[j].IsAxis
+		})
+	}
+
 	for _, v := range mappingList {
 		//fmt.Println("v:", v.EdbInfoId)
 		item := new(chartEdbMappingModel.ChartEdbInfoMappingList)
@@ -749,6 +1564,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			item.ChartStyle = ""
 			item.ChartColor = ""
 			item.ChartWidth = 0
+			item.ChartScale = 0
 			item.MaxData = v.MaxValue
 			item.MinData = v.MinValue
 		} else {
@@ -762,6 +1578,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			item.ChartStyle = v.ChartStyle
 			item.ChartColor = v.ChartColor
 			item.ChartWidth = v.ChartWidth
+			item.ChartScale = v.ChartScale
 			item.IsOrder = v.IsOrder
 			item.MaxData = v.MaxData
 			item.MinData = v.MinData
@@ -882,7 +1699,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 			}
 		}
 
-		if chartType == 2 {
+		if chartType == 2 && v.IsAxis == 1 {
 			//latestDateStr := v.LatestDate.Format(utils.FormatDate) //实际数据的截止日期
 			latestDate := v.LatestDate
 
@@ -896,22 +1713,41 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 						err = errors.New("获取农历数据失败,Err:" + tmpErr.Error())
 						return
 					}
-					quarterDataList, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
+					quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDateNong(result, latestDate, seasonExtraConfig, calendarPreYear)
 					if tErr != nil {
 						err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 						return
 					}
 					item.DataList = quarterDataList
+					seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+					seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 				}
 
 			} else {
-				quarterDataList, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
+				quarterDataList, seasonXStartDateWithYearTmp, seasonXEndDateWithYearTmp, tErr := GetSeasonEdbInfoDataListByXDate(dataList, latestDate, seasonExtraConfig)
 				if tErr != nil {
 					err = errors.New("获取季节性图表数据失败,Err:" + tErr.Error())
 					return
 				}
 				item.DataList = quarterDataList
+				seasonXStartDateWithYear = seasonXStartDateWithYearTmp
+				seasonXEndDateWithYear = seasonXEndDateWithYearTmp
 			}
+		} else if chartType == 2 && v.IsAxis == 0 { //季节性图的非横轴指标
+			newDataList := make([]*edbDataModel.EdbDataList, 0)
+			for _, v := range dataList {
+				dataTime, e := time.Parse(utils.FormatDate, v.DataTime)
+				if e != nil {
+					err = errors.New("季节性图处理右轴指标数据转换日期失败,Err:" + e.Error())
+					return
+				}
+				seasonXStartDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXStartDateWithYear)
+				seasonXEndDateWithYearT, _ := time.Parse(utils.FormatDate, seasonXEndDateWithYear)
+				if dataTime.Equal(seasonXStartDateWithYearT) || (dataTime.After(seasonXStartDateWithYearT) && dataTime.Before(seasonXEndDateWithYearT)) || dataTime.Equal(seasonXEndDateWithYearT) {
+					newDataList = append(newDataList, v)
+				}
+			}
+			item.DataList = newDataList
 		} else if chartType == 7 || chartType == utils.CHART_TYPE_RADAR { //柱方图
 			//item.DataList = dataList
 		} else {
@@ -924,7 +1760,7 @@ func getEdbDataMapList(chartInfoId, chartType int, calendar, startDate, endDate
 }
 
 // GetSeasonEdbInfoDataListByXDate 季节性图的指标数据根据横轴展示
-func GetSeasonEdbInfoDataListByXDate(dataList []*edbDataModel.EdbDataList, latestDate time.Time, seasonExtraConfig string) (quarterDataListSort edbDataModel.QuarterDataList, err error) {
+func GetSeasonEdbInfoDataListByXDate(dataList []*edbDataModel.EdbDataList, latestDate time.Time, seasonExtraConfig string) (quarterDataListSort edbDataModel.QuarterDataList, xStartDateWithYear string, xEndDateWithYear string, err error) {
 	xStartDate := "01-01"
 	xEndDate := "12-31"
 	jumpYear := 0
@@ -974,7 +1810,7 @@ func GetSeasonEdbInfoDataListByXDate(dataList []*edbDataModel.EdbDataList, lates
 		return
 	}
 	endYear := lastDateT.Year()
-	nowYear := time.Now().Year()
+	nowYear := endYear
 	chartLegendMaxYear := 0
 	dataMap := make(map[string]edbDataModel.QuarterXDateItem, 0)
 
@@ -1024,6 +1860,8 @@ func GetSeasonEdbInfoDataListByXDate(dataList []*edbDataModel.EdbDataList, lates
 		dataMap[name] = item
 		chartLegendMap[name] = idx
 		idx++
+		xStartDateWithYear = startStr
+		xEndDateWithYear = endStr
 		if lastDateT.Before(endT) {
 			//如果最新的日期在起始日之前,则跳出循环
 			break
@@ -1117,7 +1955,7 @@ func GetSeasonEdbInfoDataListByXDate(dataList []*edbDataModel.EdbDataList, lates
 }
 
 // GetSeasonEdbInfoDataListByXDateNong 季节性图的指标数据根据横轴选择农历时展示
-func GetSeasonEdbInfoDataListByXDateNong(result *edbDataModel.EdbDataResult, latestDate time.Time, seasonExtraConfig string, calendarPreYear int) (quarterDataListSort edbDataModel.QuarterDataList, err error) {
+func GetSeasonEdbInfoDataListByXDateNong(result *edbDataModel.EdbDataResult, latestDate time.Time, seasonExtraConfig string, calendarPreYear int) (quarterDataListSort edbDataModel.QuarterDataList, xStartDateWithYear string, xEndDateWithYear string, err error) {
 	xStartDate := "01-01"
 	xEndDate := "12-31"
 	jumpYear := 0
@@ -1169,7 +2007,7 @@ func GetSeasonEdbInfoDataListByXDateNong(result *edbDataModel.EdbDataResult, lat
 		return
 	}
 	endYear := lastDateT.Year()
-	nowYear := time.Now().Year()
+	nowYear := endYear
 	chartLegendMaxYear := 0
 	dataMap := make(map[string]edbDataModel.QuarterXDateItem, 0)
 
@@ -1222,6 +2060,8 @@ func GetSeasonEdbInfoDataListByXDateNong(result *edbDataModel.EdbDataResult, lat
 		endTmpT = endT
 		chartLegendMap[showName] = idx
 		idx++
+		xStartDateWithYear = startStr
+		xEndDateWithYear = endStr
 		if lastDateT.Before(endT) {
 			//如果最新的日期在起始日之前,则跳出循环
 			break
@@ -2071,3 +2911,327 @@ func RadarChartData(mappingList []*chartEdbMappingModel.ChartEdbInfoMapping, edb
 	}
 	return
 }
+// HandleDataByLinearRegressionToList 插值法补充数据(线性方程式)
+func HandleDataByLinearRegressionToList(edbInfoDataList []*edbDataModel.EdbDataList, handleDataMap map[string]float64) (dataTimeList []string, valueList []float64, err error) {
+	if len(edbInfoDataList) < 2 {
+		return
+	}
+
+	var startEdbInfoData *edbDataModel.EdbDataList
+	for _, v := range edbInfoDataList {
+		handleDataMap[v.DataTime] = v.Value
+		dataTimeList = append(dataTimeList, v.DataTime)
+		// 第一个数据就给过滤了,给后面的试用
+		if startEdbInfoData == nil {
+			startEdbInfoData = v
+			//startEdbInfoData.DataTime = startEdbInfoData.DataTime[:5]+ "01-01"
+			continue
+		}
+
+		// 获取两条数据之间相差的天数
+		startDataTime, _ := time.ParseInLocation(utils.FormatDate, startEdbInfoData.DataTime, time.Local)
+		currDataTime, _ := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+		betweenHour := int(currDataTime.Sub(startDataTime).Hours())
+		betweenDay := betweenHour / 24
+
+		// 如果相差一天,那么过滤
+		if betweenDay <= 1 {
+			startEdbInfoData = v
+			continue
+		}
+
+		// 生成线性方程式
+		var a, b float64
+		{
+			coordinateData := make([]utils.Coordinate, 0)
+			tmpCoordinate1 := utils.Coordinate{
+				X: 1,
+				Y: startEdbInfoData.Value,
+			}
+			coordinateData = append(coordinateData, tmpCoordinate1)
+			tmpCoordinate2 := utils.Coordinate{
+				X: float64(betweenDay) + 1,
+				Y: v.Value,
+			}
+			coordinateData = append(coordinateData, tmpCoordinate2)
+
+			a, b = utils.GetLinearResult(coordinateData)
+			if math.IsNaN(a) || math.IsNaN(b) {
+				err = errors.New("线性方程公式生成失败")
+				return
+			}
+		}
+
+		// 生成对应的值
+		{
+			for i := 1; i < betweenDay; i++ {
+				tmpDataTime := startDataTime.AddDate(0, 0, i)
+				aDecimal := decimal.NewFromFloat(a)
+				xDecimal := decimal.NewFromInt(int64(i) + 1)
+				bDecimal := decimal.NewFromFloat(b)
+
+				val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64()
+				handleDataMap[tmpDataTime.Format(utils.FormatDate)] = val
+				dataTimeList = append(dataTimeList, tmpDataTime.Format(utils.FormatDate))
+				valueList = append(valueList, val)
+			}
+		}
+
+		startEdbInfoData = v
+	}
+
+	return
+}
+
+// HandleDataByLinearRegressionToList 保证生成365个数据点的线性插值法
+func HandleDataByLinearRegressionToListV2(edbInfoDataList []*edbDataModel.EdbDataList, handleDataMap map[string]float64) (dataTimeList []string, valueList []float64, err error) {
+	if len(edbInfoDataList) < 2 {
+		return
+	}
+
+	// 确保至少有两天数据来生成线性方程
+	if len(edbInfoDataList) < 2 {
+		err = errors.New("至少需要两天的数据来执行线性插值")
+		return
+	}
+
+	// 对数据按日期排序,确保顺序正确
+	sort.Slice(edbInfoDataList, func(i, j int) bool {
+		t1, _ := time.ParseInLocation(utils.FormatDate, edbInfoDataList[i].DataTime, time.Local)
+		t2, _ := time.ParseInLocation(utils.FormatDate, edbInfoDataList[j].DataTime, time.Local)
+		return t1.Before(t2)
+	})
+
+	startEdbInfoData := edbInfoDataList[0]
+	endEdbInfoData := edbInfoDataList[len(edbInfoDataList)-1]
+
+	// 计算起始和结束日期间实际的天数
+	startDate, _ := time.ParseInLocation(utils.FormatDate, startEdbInfoData.DataTime, time.Local)
+	endDate, _ := time.ParseInLocation(utils.FormatDate, endEdbInfoData.DataTime, time.Local)
+	actualDays := endDate.Sub(startDate).Hours() / 24
+
+	// 生成365个数据点,首先处理已有数据
+	for _, v := range edbInfoDataList {
+		handleDataMap[v.DataTime] = v.Value
+		dataTimeList = append(dataTimeList, v.DataTime)
+		valueList = append(valueList, v.Value)
+	}
+
+	// 如果已有数据跨越天数不足365天,则对缺失的日期进行线性插值
+	if actualDays < 365 {
+		// 使用已有数据点生成线性方程(这里简化处理,实际可能需更细致处理边界情况)
+		var a, b float64
+		coordinateData := []utils.Coordinate{
+			{X: 1, Y: startEdbInfoData.Value},
+			{X: float64(len(edbInfoDataList)), Y: endEdbInfoData.Value},
+		}
+		a, b = utils.GetLinearResult(coordinateData)
+		if math.IsNaN(a) || math.IsNaN(b) {
+			err = errors.New("线性方程公式生成失败")
+			return
+		}
+
+		// 对剩余日期进行插值
+		for i := 1; i < 365; i++ {
+			day := startDate.AddDate(0, 0, i)
+			if _, exists := handleDataMap[day.Format(utils.FormatDate)]; !exists {
+				aDecimal := decimal.NewFromFloat(a)
+				xDecimal := decimal.NewFromInt(int64(i) + 1)
+				bDecimal := decimal.NewFromFloat(b)
+
+				val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64()
+				handleDataMap[day.Format(utils.FormatDate)] = val
+				dataTimeList = append(dataTimeList, day.Format(utils.FormatDate))
+				valueList = append(valueList, val)
+			}
+		}
+	}
+
+	return
+}
+
+// HandleDataByLinearRegressionToListV3 插值法补充数据(线性方程式)-直接补充指标起始日期间的所有数据
+func HandleDataByLinearRegressionToListV3(edbInfoDataList []*edbDataModel.EdbDataList, handleDataMap map[string]float64) (newEdbInfoDataList []*edbDataModel.EdbDataList, dataTimeList []string, valueList []float64, err error) {
+	if len(edbInfoDataList) < 2 {
+		return
+	}
+
+	var startEdbInfoData *edbDataModel.EdbDataList
+	for _, v := range edbInfoDataList {
+		handleDataMap[v.DataTime] = v.Value
+		newEdbInfoDataList = append(newEdbInfoDataList, v)
+		dataTimeList = append(dataTimeList, v.DataTime)
+		// 第一个数据就给过滤了,给后面的试用
+		if startEdbInfoData == nil {
+			startEdbInfoData = v
+			//startEdbInfoData.DataTime = startEdbInfoData.DataTime[:5]+ "01-01"
+			continue
+		}
+
+		// 获取两条数据之间相差的天数
+		startDataTime, _ := time.ParseInLocation(utils.FormatDate, startEdbInfoData.DataTime, time.Local)
+		currDataTime, _ := time.ParseInLocation(utils.FormatDate, v.DataTime, time.Local)
+		betweenHour := int(currDataTime.Sub(startDataTime).Hours())
+		betweenDay := betweenHour / 24
+
+		// 如果相差一天,那么过滤
+		if betweenDay <= 1 {
+			startEdbInfoData = v
+			continue
+		}
+
+		// 生成线性方程式
+		var a, b float64
+		{
+			coordinateData := make([]utils.Coordinate, 0)
+			tmpCoordinate1 := utils.Coordinate{
+				X: 1,
+				Y: startEdbInfoData.Value,
+			}
+			coordinateData = append(coordinateData, tmpCoordinate1)
+			tmpCoordinate2 := utils.Coordinate{
+				X: float64(betweenDay) + 1,
+				Y: v.Value,
+			}
+			coordinateData = append(coordinateData, tmpCoordinate2)
+
+			a, b = utils.GetLinearResult(coordinateData)
+			if math.IsNaN(a) || math.IsNaN(b) {
+				err = errors.New("线性方程公式生成失败")
+				return
+			}
+		}
+
+		// 生成对应的值
+		{
+			for i := 1; i < betweenDay; i++ {
+				tmpDataTime := startDataTime.AddDate(0, 0, i)
+				aDecimal := decimal.NewFromFloat(a)
+				xDecimal := decimal.NewFromInt(int64(i) + 1)
+				bDecimal := decimal.NewFromFloat(b)
+
+				val, _ := aDecimal.Mul(xDecimal).Add(bDecimal).Round(4).Float64()
+				handleDataMap[tmpDataTime.Format(utils.FormatDate)] = val
+				dataTimeList = append(dataTimeList, tmpDataTime.Format(utils.FormatDate))
+				valueList = append(valueList, val)
+				newEdbInfoDataList = append(newEdbInfoDataList, &edbDataModel.EdbDataList{
+					EdbDataId:     v.EdbDataId,
+					EdbInfoId:     v.EdbInfoId,
+					DataTime:      tmpDataTime.Format(utils.FormatDate),
+					DataTimestamp: tmpDataTime.UnixNano() / 1e6,
+					Value:         val,
+				})
+			}
+		}
+
+		startEdbInfoData = v
+	}
+
+	return
+}
+
+// GetEdbDataTbzForSeason 获取指标的同比值数据
+func GetEdbDataTbzForSeason(frequency string, tmpDataList []*edbDataModel.EdbDataList, startDateTime time.Time) (dataList []*edbDataModel.EdbDataList, minValue, maxValue float64, err error) {
+	dataList = make([]*edbDataModel.EdbDataList, 0)
+
+	// 数据处理
+	var dateArr []string
+	dataMap := make(map[string]*edbDataModel.EdbDataList)
+	for _, v := range tmpDataList {
+		dateArr = append(dateArr, v.DataTime)
+		dataMap[v.DataTime] = v
+	}
+	for _, av := range dateArr {
+		currentItem, ok := dataMap[av]
+		// 如果找不到当前日期的数据,那么终止当前循环,进入下一循环
+		if !ok {
+			continue
+		}
+		tmpItem := *currentItem
+		var isOk bool //是否计算出来结果
+
+		//当前日期
+		currentDate, tmpErr := time.ParseInLocation(utils.FormatDate, av, time.Local)
+		if tmpErr != nil {
+			err = tmpErr
+			return
+		}
+		// 如果存在开始日期,同时,当前日期早于开始日期,那么终止当前循环,进入下一循环
+		if !startDateTime.IsZero() && currentDate.Before(startDateTime) {
+			continue
+		}
+		//上一年的日期
+		preDate := currentDate.AddDate(-1, 0, 0)
+		preDateStr := preDate.Format(utils.FormatDate)
+		if findItem, ok := dataMap[preDateStr]; ok { //上一年同期找到
+			tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+			isOk = true
+		} else {
+			if frequency == "月度" { //向上和向下,各找一个月
+				for i := 0; i <= 35; i++ {
+					nextDateDay := preDate.AddDate(0, 0, i)
+					nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+					if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+						tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+						isOk = true
+						break
+					} else {
+						preDateDay := preDate.AddDate(0, 0, -i)
+						preDateDayStr := preDateDay.Format(utils.FormatDate)
+						if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
+							tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+							isOk = true
+							break
+						}
+					}
+				}
+			} else if frequency == "季度" || frequency == "年度" {
+				if findItem, ok := dataMap[preDateStr]; ok { //上一年同期->下一个月找到
+					tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+					isOk = true
+					break
+				}
+			} else {
+				nextDateDay := preDate.AddDate(0, 0, 1)
+				nextDateDayStr := nextDateDay.Format(utils.FormatDate)
+
+				preDateDay := preDate.AddDate(0, 0, -1)
+				preDateDayStr := preDateDay.Format(utils.FormatDate)
+
+				for i := 0; i < 35; i++ {
+					if i >= 1 {
+						nextDateDay = nextDateDay.AddDate(0, 0, i)
+						nextDateDayStr = nextDateDay.Format(utils.FormatDate)
+					}
+					if findItem, ok := dataMap[nextDateDayStr]; ok { //上一年同期->下一个月找到
+						tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+						isOk = true
+						break
+					} else {
+						if i >= 1 {
+							preDateDay = preDate.AddDate(0, 0, -i)
+							preDateDayStr = nextDateDay.Format(utils.FormatDate)
+						}
+						if findItem, ok := dataMap[preDateDayStr]; ok { //上一年同期->上一个月找到
+							tmpItem.Value = TbzDiv(currentItem.Value, findItem.Value)
+							isOk = true
+							break
+						}
+					}
+				}
+			}
+		}
+
+		if isOk {
+			if tmpItem.Value > maxValue {
+				maxValue = tmpItem.Value
+			}
+			if tmpItem.Value < minValue {
+				minValue = tmpItem.Value
+			}
+			dataList = append(dataList, &tmpItem)
+		}
+	}
+
+	return
+}

+ 3 - 75
services/chart/predict_edb_info.go

@@ -352,82 +352,10 @@ func GetPredictDataListByPredictEdbInfoId(edbInfoId int, startDate, endDate stri
 // GetPredictDataListByPredictEdbInfo 根据预测指标信息获取预测指标的数据
 func GetPredictDataListByPredictEdbInfo(edbInfo *edbInfoModel.EdbInfo, startDate, endDate string, isTimeBetween bool) (dataList []*edbDataModel.EdbDataList, sourceEdbInfoItem *edbInfoModel.EdbInfo, predictEdbConf *predictEdbConfModel.PredictEdbConf, err error, errMsg string) {
 	// 非计算指标,直接从表里获取数据
-	if edbInfo.EdbType != 1 {
-		if !isTimeBetween {
-			endDate = ``
-		}
-		return GetPredictCalculateDataListByPredictEdbInfo(edbInfo, startDate, endDate)
+	if !isTimeBetween {
+		endDate = ``
 	}
-	// 查找该预测指标配置
-	predictEdbConfList, err := predictEdbConfModel.GetPredictEdbConfListById(edbInfo.EdbInfoId)
-	if err != nil {
-		errMsg = "获取预测指标配置信息失败"
-		return
-	}
-	if len(predictEdbConfList) == 0 {
-		errMsg = "获取预测指标配置信息失败"
-		err = errors.New(errMsg)
-		return
-	}
-	predictEdbConf = predictEdbConfList[0]
-
-	// 来源指标
-	sourceEdbInfoItem, err = edbInfoModel.GetEdbInfoById(int(predictEdbConf.SourceEdbInfoID))
-	if err != nil {
-		if err == utils.ErrNoRow {
-			errMsg = "找不到来源指标信息"
-			err = errors.New(errMsg)
-		}
-		return
-	}
-
-	allDataList := make([]*edbDataModel.EdbDataList, 0)
-	//获取指标数据(实际已生成)
-	dataList, err = edbDataModel.GetEdbDataList(sourceEdbInfoItem.Source, sourceEdbInfoItem.SubSource, sourceEdbInfoItem.EdbInfoId, startDate, endDate)
-	if err != nil {
-		return
-	}
-	// 如果选择了日期,那么需要筛选所有的数据,用于未来指标的生成
-	if startDate != `` {
-		allDataList, err = edbDataModel.GetEdbDataList(sourceEdbInfoItem.Source, sourceEdbInfoItem.SubSource, sourceEdbInfoItem.EdbInfoId, "", "")
-		if err != nil {
-			return
-		}
-	} else {
-		allDataList = dataList
-	}
-
-	// 获取预测指标未来的数据
-	predictDataList := make([]*edbDataModel.EdbDataList, 0)
-
-	endDateStr := edbInfo.EndDate.Format(utils.FormatDate) //预测指标的结束日期
-
-	if isTimeBetween { //如果是时间区间,那么
-		reqEndDateTime, _ := time.ParseInLocation(utils.FormatDate, endDate, time.Local)
-		// 如果选择的时间区间结束日期 晚于 当天,那么预测数据截止到当天
-		if reqEndDateTime.Before(edbInfo.EndDate) {
-			endDateStr = endDate
-		}
-	}
-	//predictDataList, err = GetChartPredictEdbInfoDataList(*predictEdbConf, startDate, sourceEdbInfoItem.LatestDate.Format(utils.FormatDate), sourceEdbInfoItem.LatestValue, endDateStr, edbInfo.Frequency)
-	var predictMinValue, predictMaxValue float64
-	predictDataList, predictMinValue, predictMaxValue, err = GetChartPredictEdbInfoDataListByConfList(predictEdbConfList, startDate, sourceEdbInfoItem.LatestDate.Format(utils.FormatDate), endDateStr, edbInfo.Frequency, edbInfo.DataDateType, allDataList)
-	if err != nil {
-		return
-	}
-	dataList = append(dataList, predictDataList...)
-	if len(predictDataList) > 0 {
-		// 如果最小值 大于 预测值,那么将预测值作为最小值数据返回
-		if edbInfo.MinValue > predictMinValue {
-			edbInfo.MinValue = predictMinValue
-		}
-
-		// 如果最大值 小于 预测值,那么将预测值作为最大值数据返回
-		if edbInfo.MaxValue < predictMaxValue {
-			edbInfo.MaxValue = predictMaxValue
-		}
-	}
-	return
+	return GetPredictCalculateDataListByPredictEdbInfo(edbInfo, startDate, endDate)
 }
 
 // GetPredictCalculateDataListByPredictEdbInfo 根据预测运算指标信息获取预测指标的数据

+ 3 - 3
services/comment/comment.go

@@ -17,7 +17,7 @@ import (
 )
 
 // Comment 发布留言
-func Comment(user user.UserInfo, req reqComment.ReqComment) (ret response.RespCommentAdd, err error) {
+func Comment(user user.UserInfo, req reqComment.ReqComment, copyYb string) (ret response.RespCommentAdd, err error) {
 	var errMsg string
 	defer func() {
 		if err != nil {
@@ -49,8 +49,8 @@ func Comment(user user.UserInfo, req reqComment.ReqComment) (ret response.RespCo
 		return
 	}
 	// 敏感词过滤
-	if user.RecordInfo.OpenID != "" && user.RecordInfo.CreatePlatform == 6 {   //只有小程序的用户才能走敏感词过滤接口
-		checkResult, tErr := wx_app.MsgSecCheck(user.RecordInfo.OpenID, req.Content)
+	if user.RecordInfo.OpenID != "" && (user.RecordInfo.CreatePlatform == utils.USER_RECORD_PLATFORM_YB || user.RecordInfo.CreatePlatform == utils.USER_RECORD_PLATFORM_COPY_YB) {   //只有小程序的用户才能走敏感词过滤接口
+		checkResult, tErr := wx_app.MsgSecCheck(user.RecordInfo.OpenID, req.Content, copyYb)
 		/*if tErr != nil {
 			errMsg = "敏感词过滤失败" + tErr.Error()
 			err = errors.New("敏感词过滤失败")

+ 19 - 0
services/eta_chart_lib.go

@@ -54,6 +54,25 @@ func GetBalanceChartDetail(uniqueCode string) (respData *chart_info.ChartLibChar
 	return
 }
 
+// GetRangeChartChartDetail 获取区间计算图表详情接口
+func GetRangeChartChartDetail(uniqueCode string) (respData *chart_info.ChartLibChartInfoDetailResp, err error) {
+	postUrl := fmt.Sprintf("%s/chart_auth/detail?UniqueCode=%s", global.CONFIG.EtaChartLib.ServerUrl, uniqueCode)
+	result, err := httpGet(postUrl)
+	if err != nil {
+		return
+	}
+	var resp ChartCommonDetailResp
+	if err = json.Unmarshal(result, &resp); err != nil {
+		return
+	}
+	if resp.Ret != 200 {
+		err = fmt.Errorf("%s,%s", resp.Msg, resp.ErrMsg)
+		return
+	}
+	respData = resp.Data
+	return
+}
+
 // post请求
 func httpPost(url, postData string, params ...string) ([]byte, error) {
 	body := ioutil.NopCloser(strings.NewReader(postData))

+ 13 - 17
services/media.go

@@ -1,19 +1,15 @@
 package services
 
-import (
-	"github.com/tosone/minimp3"
-)
-
-// GetMP3PlayDuration 获取MP3的时长
-func GetMP3PlayDuration(mp3Data []byte) (seconds int, err error) {
-	dec, _, err := minimp3.DecodeFull(mp3Data)
-	if err != nil {
-		return 0, err
-	}
-	if dec.Kbps <= 0 {
-		return 0, err
-	}
-	// 音乐时长 = (文件大小(byte) - 128(ID3信息)) * 8(to bit) / (码率(kbps b:bit) * 1000)(kilo bit to bit)
-	seconds = (len(mp3Data) - 128) * 8 / (dec.Kbps * 1000)
-	return seconds, nil
-}
+//// GetMP3PlayDuration 获取MP3的时长
+//func GetMP3PlayDuration(mp3Data []byte) (seconds int, err error) {
+//	dec, _, err := minimp3.DecodeFull(mp3Data)
+//	if err != nil {
+//		return 0, err
+//	}
+//	if dec.Kbps <= 0 {
+//		return 0, err
+//	}
+//	// 音乐时长 = (文件大小(byte) - 128(ID3信息)) * 8(to bit) / (码率(kbps b:bit) * 1000)(kilo bit to bit)
+//	seconds = (len(mp3Data) - 128) * 8 / (dec.Kbps * 1000)
+//	return seconds, nil
+//}

+ 29 - 6
services/report/report.go

@@ -25,6 +25,7 @@ import (
 	"hongze/hongze_yb/models/tables/user_report_chapter_set"
 	"hongze/hongze_yb/models/tables/yb_road_video"
 	"hongze/hongze_yb/services"
+	"hongze/hongze_yb/services/base_config"
 	"hongze/hongze_yb/services/collection"
 	"hongze/hongze_yb/services/company"
 	elasticService "hongze/hongze_yb/services/elastic"
@@ -461,22 +462,26 @@ func GetReportDetail(userinfo user.UserInfo, reportId int) (reportDetail respons
 	{
 		if reportInfo.HeadResourceId > 0 {
 			headResource, tmpErr := smart_report_resource.GetResourceItemById(reportInfo.HeadResourceId)
-			if tmpErr != nil {
+			if tmpErr != nil && tmpErr != utils.ErrNoRow {
 				err = tmpErr
 				return
 			}
-			reportItem.HeadImg = headResource.ImgURL
-			reportItem.HeadStyle = headResource.Style
+			if headResource != nil && headResource.ResourceID > 0 {
+				reportItem.HeadImg = headResource.ImgURL
+				reportItem.HeadStyle = headResource.Style
+			}
 		}
 
 		if reportInfo.EndResourceId > 0 {
 			endResource, tmpErr := smart_report_resource.GetResourceItemById(reportInfo.EndResourceId)
-			if tmpErr != nil {
+			if tmpErr != nil && tmpErr != utils.ErrNoRow {
 				err = tmpErr
 				return
 			}
-			reportItem.EndImg = endResource.ImgURL
-			reportItem.EndStyle = endResource.Style
+			if endResource != nil && endResource.ResourceID > 0 {
+				reportItem.EndImg = endResource.ImgURL
+				reportItem.EndStyle = endResource.Style
+			}
 		}
 	}
 
@@ -628,6 +633,24 @@ func GetReportDetail(userinfo user.UserInfo, reportId int) (reportDetail respons
 		}
 	}
 
+	// 图表授权token
+	{
+		isOpenChartExpired, e := base_config.CheckIsOpenChartExpired()
+		if e != nil {
+			err = errors.New("获取图表是否开启鉴权失败,err:" + e.Error())
+			return
+		}
+
+		if isOpenChartExpired {
+			tokenMap := make(map[string]string)
+			reportDetail.ReportInfo.Content = HandleReportContent(reportDetail.ReportInfo.Content, "add", tokenMap)
+			reportDetail.ReportInfo.ContentSub = HandleReportContent(reportDetail.ReportInfo.ContentSub, "add", tokenMap)
+			for _, v := range reportTypeList {
+				v.Content = HandleReportContent(v.Content, "add", tokenMap)
+			}
+		}
+	}
+
 	// 收藏
 	collectionId, e := collection.GetUserCollectByItem(int(userinfo.UserID), collection.CollectionTypeReport, reportId, 0)
 	if e != nil {

+ 26 - 6
services/report/report_chapter.go

@@ -16,6 +16,7 @@ import (
 	"hongze/hongze_yb/models/tables/report_chapter_type"
 	"hongze/hongze_yb/models/tables/user_report_chapter_set"
 	"hongze/hongze_yb/services"
+	"hongze/hongze_yb/services/base_config"
 	"hongze/hongze_yb/services/collection"
 	"hongze/hongze_yb/services/company"
 	"hongze/hongze_yb/services/user"
@@ -262,6 +263,21 @@ func GetChapterDetail(user user.UserInfo, reportChapterId int) (reportChapterDet
 	reportChapterItem.EndImg = reportInfo.EndImg
 	reportChapterItem.CanvasColor = reportInfo.CanvasColor
 
+	// 图表授权token
+	{
+		isOpenChartExpired, e := base_config.CheckIsOpenChartExpired()
+		if e != nil {
+			err = errors.New("获取图表是否开启鉴权失败,err:" + e.Error())
+			return
+		}
+
+		if isOpenChartExpired {
+			tokenMap := make(map[string]string)
+			reportChapterItem.Content = HandleReportContent(reportChapterItem.Content, "add", tokenMap)
+			reportChapterItem.ContentSub = HandleReportContent(reportChapterItem.ContentSub, "add", tokenMap)
+		}
+	}
+
 	// 手工上传的才返回
 	if reportInfo.VoiceGenerateType == 1 {
 		reportChapterItem.ReportVideoUrl = reportInfo.VideoUrl
@@ -271,22 +287,26 @@ func GetChapterDetail(user user.UserInfo, reportChapterId int) (reportChapterDet
 	{
 		if reportInfo.HeadResourceId > 0 {
 			headResource, tmpErr := smart_report_resource.GetResourceItemById(reportInfo.HeadResourceId)
-			if tmpErr != nil {
+			if tmpErr != nil && tmpErr != utils.ErrNoRow {
 				err = tmpErr
 				return
 			}
-			reportChapterItem.HeadImg = headResource.ImgURL
-			reportChapterItem.HeadStyle = headResource.Style
+			if headResource != nil && headResource.ResourceID > 0 {
+				reportChapterItem.HeadImg = headResource.ImgURL
+				reportChapterItem.HeadStyle = headResource.Style
+			}
 		}
 
 		if reportInfo.EndResourceId > 0 {
 			endResource, tmpErr := smart_report_resource.GetResourceItemById(reportInfo.EndResourceId)
-			if tmpErr != nil {
+			if tmpErr != nil && tmpErr != utils.ErrNoRow {
 				err = tmpErr
 				return
 			}
-			reportChapterItem.EndImg = endResource.ImgURL
-			reportChapterItem.EndStyle = endResource.Style
+			if endResource != nil && endResource.ResourceID > 0 {
+				reportChapterItem.EndImg = endResource.ImgURL
+				reportChapterItem.EndStyle = endResource.Style
+			}
 		}
 	}
 

+ 252 - 0
services/report/report_handle.go

@@ -0,0 +1,252 @@
+package report
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	html2 "golang.org/x/net/html"
+	"hongze/hongze_yb/global"
+	"hongze/hongze_yb/utils"
+	"net/url"
+	"strings"
+	"time"
+)
+
+// HandleReportContent
+// @Description: 处理报告内容(动态图表/表格添加授权token)
+// @author: Roc
+// @datetime 2025-01-07 10:03:15
+// @param body string
+// @return newBody string
+func HandleReportContent(body string, opType string, tokenMap map[string]string) (newBody string) {
+	if body == `` {
+		return
+	}
+	newBody = body
+
+	// 解析HTML
+	doc, err := html2.Parse(strings.NewReader(body))
+	if err != nil {
+		fmt.Println("Error parsing HTML:", err)
+		return
+	}
+
+	replaceIframeSrc(doc, opType, tokenMap)
+
+	// 输出修改后的HTML
+	var modifiedHtml strings.Builder
+	err = html2.Render(&modifiedHtml, doc)
+	if err != nil {
+		fmt.Println("Error rendering HTML:", err)
+		return
+	}
+
+	newBody = modifiedHtml.String()
+	fmt.Println(newBody)
+
+	return
+}
+
+// replaceIframeSrc 遍历HTML节点,替换iframe的src属性
+func replaceIframeSrc(n *html2.Node, opType string, tokenMap map[string]string) {
+	if n.Type == html2.ElementNode && n.Data == "iframe" {
+		for i, attr := range n.Attr {
+			if attr.Key == "src" {
+				newLink := attr.Val
+				// 处理链接
+				switch opType {
+				case `add`:
+					newLink = linkAddToken(attr.Val, tokenMap)
+				case `del`:
+					newLink = linkDelToken(attr.Val)
+				}
+				// 替换原来的链接
+				n.Attr[i].Val = newLink
+				break
+			}
+		}
+	}
+	// 递归处理子节点
+	for c := n.FirstChild; c != nil; c = c.NextSibling {
+		replaceIframeSrc(c, opType, tokenMap)
+	}
+}
+
+// linkAddToken 链接添加token
+func linkAddToken(link string, tokenMap map[string]string) string {
+	var err error
+	defer func() {
+		if err != nil {
+			global.FILE_LOG.Info("处理链接失败,ERR:" + err.Error())
+		}
+	}()
+	parsedURL, err := url.Parse(link)
+	if err != nil {
+		return link
+	}
+
+	// 获取查询参数
+	queryParams := parsedURL.Query()
+
+	// 先移除authToken参数,避免莫名其妙的这个值入库了
+	queryParams.Del("authToken")
+
+	// 获取code参数
+	code := queryParams.Get("code")
+	if code == "" {
+		return link
+	}
+
+	showType := `chart`
+	if strings.Contains(parsedURL.Path, "sheetshow") {
+		showType = `excel`
+	}
+
+	// 避免报告里面一个图表/表格重复生成token
+	key := fmt.Sprint(showType, `:`, code)
+	if tokenMap != nil {
+		if token, ok := tokenMap[key]; ok {
+			// 在链接后面添加一个token值
+			return link + "&authToken=" + token
+		}
+	}
+
+	token, err := GeneralChartToken(showType, code, 30*time.Minute)
+	if err != nil {
+		return link
+	}
+
+	if tokenMap != nil {
+		tokenMap[key] = token
+	}
+
+	// 在链接后面添加一个token值
+	return link + "&authToken=" + token
+}
+
+// linkDelToken 链接添加token
+func linkDelToken(link string) string {
+	var err error
+	defer func() {
+		if err != nil {
+			global.FILE_LOG.Info("处理链接失败,ERR:" + err.Error())
+		}
+	}()
+	parsedURL, err := url.Parse(link)
+	if err != nil {
+		return link
+	}
+
+	// 获取查询参数
+	queryParams := parsedURL.Query()
+
+	// 移除authToken参数
+	queryParams.Del("authToken")
+
+	// 更新URL的查询参数
+	parsedURL.RawQuery = queryParams.Encode()
+
+	return parsedURL.String()
+}
+
+// GeneralChartToken
+// @Description: 生产图表/表格授权token
+// @author: Roc
+// @datetime 2025-01-07 10:41:36
+// @param showType string
+// @param uniqueCode string
+// @param expireTime time.Duration
+// @return token string
+// @return err error
+func GeneralChartToken(showType, uniqueCode string, expireTime time.Duration) (token string, err error) {
+	// 缓存key
+	token = utils.MD5(fmt.Sprint(showType+`:`, uniqueCode, time.Now().UnixNano()/1e6))
+	key := fmt.Sprint(utils.CACHE_CHART_AUTH, token)
+	err = global.Redis.SetEX(context.TODO(), key, uniqueCode, expireTime).Err()
+
+	return
+}
+
+// HandleReportContentStruct
+// @Description: 处理内容组件的链接
+// @author: Roc
+// @datetime 2025-01-07 13:38:39
+// @param body string
+// @param opType string
+// @return newBody string
+func HandleReportContentStruct(body string, opType string, tokenMap map[string]string) (newBody string) {
+	if body == `` {
+		return
+	}
+	newBody = body
+
+	// 解析JSON数据到map[string]interface{}
+	var jsonData []map[string]interface{}
+	if err := json.Unmarshal([]byte(body), &jsonData); err != nil {
+		fmt.Println("Error parsing JSON:", err)
+		return
+	}
+
+	// 处理每个组件
+	for i := range jsonData {
+		if err := processMap(jsonData[i], opType, tokenMap); err != nil {
+			fmt.Println("Error processing component:", err)
+			return
+		}
+	}
+
+	// 将处理后的数据转换回JSON字符串
+	modifiedJSON, err := json.MarshalIndent(jsonData, "", "  ")
+	if err != nil {
+		fmt.Println("Error marshaling JSON:", err)
+		return
+	}
+	newBody = string(modifiedJSON)
+
+	return
+}
+
+// processMap 递归处理map中的content字段
+func processMap(data map[string]interface{}, opType string, tokenMap map[string]string) error {
+	for key, value := range data {
+		switch v := value.(type) {
+		case string:
+			if key == "content" {
+				contentSource, ok := data["compType"]
+				if !ok {
+					continue
+				}
+				contentSourceType, ok := contentSource.(string)
+				if !ok {
+					continue
+				}
+				if !utils.InArrayByStr([]string{`sheet`, `chart`}, contentSourceType) {
+					continue
+				}
+
+				newContent := v
+				// 处理链接
+				switch opType {
+				case `add`:
+					newContent = linkAddToken(v, tokenMap)
+				case `del`:
+					newContent = linkDelToken(v)
+				}
+				data[key] = newContent
+			}
+		case map[string]interface{}:
+			if err := processMap(v, opType, tokenMap); err != nil {
+				return err
+			}
+		case []interface{}:
+			for i := range v {
+				if m, ok := v[i].(map[string]interface{}); ok {
+					if err := processMap(m, opType, tokenMap); err != nil {
+						return err
+					}
+				}
+			}
+		}
+	}
+	return nil
+}

+ 35 - 7
services/share_poster.go

@@ -83,7 +83,7 @@ func Html2ImgHttpPost(url, postData string, params ...string) ([]byte, error) {
 }
 
 // CreateAndUploadSunCode 生成太阳码并上传OSS
-func CreateAndUploadSunCode(page, scene, version string) (imgUrl string, err error) {
+func CreateAndUploadSunCode(page, scene, version, copyYb string) (imgUrl string, err error) {
 	if page == "" {
 		err = errors.New("page不能为空")
 		return
@@ -101,7 +101,7 @@ func CreateAndUploadSunCode(page, scene, version string) (imgUrl string, err err
 	if scene != "" {
 		sceneMD5 = utils.MD5(scene)
 	}
-	picByte, err := wx_app.GetSunCode(page, sceneMD5)
+	picByte, err := wx_app.GetSunCode(page, sceneMD5, copyYb)
 	if err != nil {
 		return
 	}
@@ -162,7 +162,7 @@ func CreateAndUploadSunCode(page, scene, version string) (imgUrl string, err err
 }
 
 // CreatePosterFromSourceV2 生成分享海报(通过配置获取相关信息)
-func CreatePosterFromSourceV2(codePage, codeScene, source, version, pars string) (imgUrl string, err error) {
+func CreatePosterFromSourceV2(codePage, codeScene, source, version, pars, copyYb string) (imgUrl string, err error) {
 	var errMsg string
 	defer func() {
 		if err != nil {
@@ -202,7 +202,7 @@ func CreatePosterFromSourceV2(codePage, codeScene, source, version, pars string)
 	width := ybPosterConfig.Width
 	height := ybPosterConfig.Hight
 	//生成太阳码
-	sunCodeUrl, err := CreateAndUploadSunCode(codePage, codeScene, version)
+	sunCodeUrl, err := CreateAndUploadSunCode(codePage, codeScene, version, copyYb)
 	if err != nil {
 		return
 	}
@@ -391,17 +391,33 @@ func fillContent2HtmlV2(source, pars, sunCodeUrl string, height float64, ybPoste
 }
 
 // GetDynamicShareImg 生成动态分享图
-func GetDynamicShareImg(source, pars string) (imgUrl string, err error) {
+func GetDynamicShareImg(source, pars string, reportId, reportChapterId int, version string) (imgUrl string, err error) {
 	if source == "" {
 		err = errors.New("图片来源有误")
 		return
 	}
+
+	// 报告章节详情无需重复生成
+	var path string
+	if reportId > 0 {
+		path = fmt.Sprintf("reportDetailCover?ReportId=%d&ReportChapterId=%d", reportId, reportChapterId)
+		poster, e := yb_poster_resource.GetPosterByCondition(path, "poster", version)
+		if e != nil && e != utils.ErrNoRow {
+			err = fmt.Errorf("获取报告已生成海报失败, %e", e)
+			return
+		}
+		if poster != nil && poster.ImgURL != "" {
+			imgUrl = poster.ImgURL
+			return
+		}
+	}
+
+	// 生成海报
 	imgConfig, e := yb_poster_config.GetBySource(source)
 	if e != nil {
 		err = errors.New("获取图片配置失败")
 		return
 	}
-	// 填充html内容
 	content, newHeight, e := fillContent2HtmlV2(source, pars, "", imgConfig.Hight, *imgConfig)
 	if e != nil {
 		err = errors.New("html内容有误")
@@ -421,6 +437,18 @@ func GetDynamicShareImg(source, pars string) (imgUrl string, err error) {
 		return
 	}
 	imgUrl = res.Data
+
+	// 报告详情-记录海报信息
+	if reportId > 0 {
+		newPoster := &yb_poster_resource.YbPosterResource{
+			Path:       path,
+			ImgURL:     imgUrl,
+			Type:       "poster",
+			Version:    version,
+			CreateTime: time.Now(),
+		}
+		err = newPoster.Create()
+	}
 	return
 }
 
@@ -496,4 +524,4 @@ finally:
 		cmd.Process.Kill()
 	}()
 	return
-}
+}

+ 15 - 1
services/sun_code.go

@@ -3,6 +3,7 @@ package services
 import (
 	"errors"
 	"fmt"
+	"hongze/hongze_yb/models/tables/yb_config"
 	"hongze/hongze_yb/models/tables/yb_pc_suncode"
 	"hongze/hongze_yb/models/tables/yb_resource"
 	"hongze/hongze_yb/models/tables/yb_suncode_pars"
@@ -20,6 +21,11 @@ type SunCodeReq struct {
 
 // PcCreateAndUploadSunCode 生成太阳码并上传OSS
 func PcCreateAndUploadSunCode(scene, page string) (imgUrl string, err error) {
+	defer func() {
+		if err != nil {
+			fmt.Println("PcCreateAndUploadSunCode Err:" + err.Error())
+		}
+	}()
 	if page == "" {
 		err = errors.New("page不能为空")
 		return
@@ -30,7 +36,15 @@ func PcCreateAndUploadSunCode(scene, page string) (imgUrl string, err error) {
 	if scene != "" {
 		sceneMD5 = utils.MD5(scene)
 	}
-	picByte, err := wx_app.GetSunCode(page, sceneMD5)
+
+	// 根据配置选择小程序
+	conf, e := yb_config.GetConfigByCode(yb_config.KeyUseCopyYb)
+	if e != nil {
+		err = errors.New("获取小程序配置失败, Err: " + e.Error())
+		return
+	}
+
+	picByte, err := wx_app.GetSunCode(page, sceneMD5, conf.ConfigValue)
 	if err != nil {
 		return
 	}

+ 13 - 5
services/voice_broadcast.go

@@ -212,7 +212,7 @@ func SendBroadcastMsg(broadcastId, userId int) (errMsg string, err error) {
 	}
 	// 推送模板消息
 	go func() {
-		_ = wechat.SendVoiceBroadcastWxMsg(broadcast.BroadcastId,broadcast.VarietyId, broadcast.SectionName, broadcast.BroadcastName)
+		_ = wechat.SendVoiceBroadcastWxMsg(broadcast.BroadcastId, broadcast.VarietyId, broadcast.SectionName, broadcast.BroadcastName)
 	}()
 	// 推送客群消息
 	//go func() {
@@ -222,8 +222,13 @@ func SendBroadcastMsg(broadcastId, userId int) (errMsg string, err error) {
 }
 
 // CreateVoiceBroadcast 新增语音播报
-func CreateVoiceBroadcast(sectionId, varietyId, authorId int, broadcastName, sectionName, varietyName, author, voiceSeconds, voiceSize, voiceUrl, imgs string, userInfo user.UserInfo) (resp response.Broadcast, err error) {
+func CreateVoiceBroadcast(sectionId, varietyId, authorId int, broadcastName, sectionName, varietyName, author, voiceSeconds, voiceSize, voiceUrl, imgs, centralArguments string, userInfo user.UserInfo) (resp response.Broadcast, err error) {
 	nowTime := time.Now().Local()
+	if userInfo.UserID > 0 {
+		authorId = int(userInfo.UserID)
+		author = userInfo.RealName
+	}
+
 	item := &voice_broadcast.VoiceBroadcast{
 		BroadcastName:    broadcastName,
 		SectionId:        sectionId,
@@ -235,6 +240,7 @@ func CreateVoiceBroadcast(sectionId, varietyId, authorId int, broadcastName, sec
 		VoiceUrl:         voiceUrl,
 		VoicePlaySeconds: voiceSeconds,
 		VoiceSize:        voiceSize,
+		CentralArguments: centralArguments,
 		CreateTime:       nowTime.Format(utils.FormatDateTime),
 		ModifyTime:       nowTime.Format(utils.FormatDateTime),
 	}
@@ -268,7 +274,7 @@ func CreateVoiceBroadcast(sectionId, varietyId, authorId int, broadcastName, sec
 }
 
 // EditVoiceBroadcast 编辑语音播报
-func EditVoiceBroadcast(broadcastId, sectionId, varietyId, authorId int, broadcastName, sectionName, varietyName, author, voiceSeconds, voiceSize, voiceUrl, imgs string, userInfo user.UserInfo) (resp response.Broadcast, err error) {
+func EditVoiceBroadcast(broadcastId, sectionId, varietyId, authorId int, broadcastName, sectionName, varietyName, author, voiceSeconds, voiceSize, voiceUrl, imgs, centralArguments string, userInfo user.UserInfo) (resp response.Broadcast, err error) {
 	if broadcastId <= 0 {
 		return
 	}
@@ -279,7 +285,7 @@ func EditVoiceBroadcast(broadcastId, sectionId, varietyId, authorId int, broadca
 	}
 	nowTime := time.Now().Local()
 	updateCols := []string{"BroadcastName", "SectionId", "SectionName", "VarietyId", "VarietyName", "AuthorId", "Author", "VoiceUrl",
-		"VoicePlaySeconds", "VoiceSize", "ModifyTime"}
+		"VoicePlaySeconds", "VoiceSize", "ModifyTime", "CentralArguments"}
 	item.BroadcastName = broadcastName
 	item.SectionId = sectionId
 	item.SectionName = sectionName
@@ -291,6 +297,7 @@ func EditVoiceBroadcast(broadcastId, sectionId, varietyId, authorId int, broadca
 	item.VoicePlaySeconds = voiceSeconds
 	item.VoiceSize = voiceSize
 	item.ModifyTime = nowTime.Format(utils.FormatDateTime)
+	item.CentralArguments = centralArguments
 	// 图片
 	imgList := make([]*voice_broadcast_img.YbVoiceBroadcastImg, 0)
 	if imgs != "" {
@@ -387,7 +394,7 @@ func createVoiceBroadcastShareImg(baseImg, sectionName, createTime string) (shar
 		err = e
 		return
 	}
-	shareImg, e = GetDynamicShareImg(VoiceBroadcastShareImgSource, string(parsByte))
+	shareImg, e = GetDynamicShareImg(VoiceBroadcastShareImgSource, string(parsByte), 0, 0, "")
 	if e != nil {
 		err = e
 		return
@@ -455,6 +462,7 @@ func handleBroadcastItem(userInfo user.UserInfo, item *voice_broadcast.VoiceBroa
 		imgArr = append(imgArr, imgs[i].ImgUrl)
 	}
 	resp.Imgs = imgArr
+	resp.CentralArguments = item.CentralArguments
 	return
 }
 

+ 104 - 22
services/wx_app/wx_app.go

@@ -1,6 +1,7 @@
 package wx_app
 
 import (
+	"errors"
 	"fmt"
 	wechat "github.com/silenceper/wechat/v2"
 	"github.com/silenceper/wechat/v2/cache"
@@ -9,8 +10,11 @@ import (
 	"github.com/silenceper/wechat/v2/miniprogram/config"
 	"github.com/silenceper/wechat/v2/miniprogram/encryptor"
 	"github.com/silenceper/wechat/v2/miniprogram/qrcode"
+	"github.com/silenceper/wechat/v2/util"
 	"hongze/hongze_yb/global"
+	"hongze/hongze_yb/models/tables/yb_config"
 	"hongze/hongze_yb/services/wx_app/security"
+	"hongze/hongze_yb/utils"
 )
 
 // 微信小程序配置信息
@@ -37,60 +41,100 @@ func init() {
 	}
 }
 
-func GetWxApp() (miniprogram *miniprogram.MiniProgram) {
+// 注: 研报小程序及备用小程序在同时运行, 基础微信API通过前端请求Header判断来源
+// 模板消息等API则用配置控制具体使用哪一个小程序, 同一时间仅一个小程序可推消息
+func GetWxApp(copyYb string) (mp *miniprogram.MiniProgram) {
 	wc := wechat.NewWechat()
 	memory := cache.NewMemory()
 	//memory := cache.NewRedis(global.Redis)
+
+	mp = new(miniprogram.MiniProgram)
+
+	appConf := getWxAppConfByRequest(copyYb)
+
 	cfg := &config.Config{
-		AppID:     WxAppId,
-		AppSecret: WxAppSecret,
+		AppID:     appConf.WxAppId,
+		AppSecret: appConf.WxAppSecret,
 		Cache:     memory,
 	}
 
-	miniprogram = wc.GetMiniProgram(cfg)
-	return
-}
-
-// GetSession 获取用户详情
-func GetSession(code string) (userInfo auth.ResCode2Session, err error) {
-	wechatClient := GetWxApp()
-	authClient := wechatClient.GetAuth()
-	userInfo, err = authClient.Code2Session(code)
+	mp = wc.GetMiniProgram(cfg)
 	return
 }
 
 // GetSession 获取用户详情
-func GetUserInfo(code string) (userInfo auth.ResCode2Session, err error) {
-	wechatClient := GetWxApp()
+func GetSession(code, copyYb string) (userInfo auth.ResCode2Session, err error) {
+	wechatClient := GetWxApp(copyYb)
 	authClient := wechatClient.GetAuth()
-	fmt.Println("code:", code)
 	userInfo, err = authClient.Code2Session(code)
 	return
 }
 
 // 获取解密信息 GetDecryptInfo
-func GetDecryptInfo(sessionKey, encryptedData, iv string) (decryptData *encryptor.PlainData, err error) {
-	wechatClient := GetWxApp()
+func GetDecryptInfo(sessionKey, encryptedData, iv, copyYb string) (decryptData *encryptor.PlainData, err error) {
+	wechatClient := GetWxApp(copyYb)
 	encryptorClient := wechatClient.GetEncryptor()
 	decryptData, err = encryptorClient.Decrypt(sessionKey, encryptedData, iv)
 	return
 }
 
+// 微信小程序新版code获取手机号
+const (
+	getPhoneNumberURL = "https://api.weixin.qq.com/wxa/business/getuserphonenumber?access_token=%s"
+)
+
+type GetPhoneNumberResponse struct {
+	util.CommonError
+	PhoneInfo PhoneInfo `json:"phone_info"`
+}
+
+type PhoneInfo struct {
+	PhoneNumber     string `json:"phoneNumber"`     // 用户绑定的手机号
+	PurePhoneNumber string `json:"purePhoneNumber"` // 没有区号的手机号
+	CountryCode     string `json:"countryCode"`     // 区号
+	WaterMark       struct {
+		Timestamp int64  `json:"timestamp"`
+		AppID     string `json:"appid"`
+	} `json:"watermark"` // 数据水印
+}
+
+func GetPhoneNumber(code, copyYb string) (result GetPhoneNumberResponse, err error) {
+	var response []byte
+	var (
+		at string
+	)
+	wechatClient := GetWxApp(copyYb)
+	authClient := wechatClient.GetAuth()
+	if at, err = authClient.GetAccessToken(); err != nil {
+		return
+	}
+	body := map[string]interface{}{
+		"code": code,
+	}
+	if response, err = util.PostJSON(fmt.Sprintf(getPhoneNumberURL, at), body); err != nil {
+		return
+	}
+	if err = util.DecodeWithError(response, &result, "phonenumber.getPhoneNumber"); err != nil {
+		return
+	}
+	return
+}
+
 // GetSunCode 获取太阳码
-func GetSunCode(page, scene string) (resp []byte, err error) {
+func GetSunCode(page, scene, copyYb string) (resp []byte, err error) {
 	codePars := qrcode.QRCoder{
 		Page:       page,
 		Scene:      scene,
 		EnvVersion: EnvVersion,
 	}
-	wechatClient := GetWxApp()
+	wechatClient := GetWxApp(copyYb)
 	qr := wechatClient.GetQRCode()
 	return qr.GetWXACodeUnlimit(codePars)
 }
 
 // MsgSecCheck 检查一段文本是否含有违法违规内容。
-func MsgSecCheck(openid string, content string) (result security.Result, err error) {
-	wechatClient := GetWxApp()
+func MsgSecCheck(openid, content, copyYb string) (result security.Result, err error) {
+	wechatClient := GetWxApp(copyYb)
 	myMiniprogram := security.NewMyMiniprogram(wechatClient)
 	bodyContent := &security.BodyContent{
 		Version: 2,
@@ -101,6 +145,44 @@ func MsgSecCheck(openid string, content string) (result security.Result, err err
 	return myMiniprogram.MsgSecCheckWithResult(bodyContent)
 }
 
+type WxAppConf struct {
+	WxId              string `description:"微信原始ID"`
+	WxAppId           string
+	WxAppSecret       string
+	WxPlatform        int `description:"app来源: 6-研报小程序; 9-研报备用小程序"`
+	MsgRedirectTarget int `description:"公共模板消息跳转类型:1-研报小程序; 4-备用研报小程序"`
+}
+
+// GetWxAppConf 获取小程序配置
+func GetWxAppConf() (appConf *WxAppConf, err error) {
+	// 根据配置选择小程序
+	conf, e := yb_config.GetConfigByCode(yb_config.KeyUseCopyYb)
+	if e != nil {
+		err = errors.New("获取小程序配置失败, Err: " + e.Error())
+		return
+	}
+	appConf = getWxAppConfByRequest(conf.ConfigValue)
+	return
+}
+
+func getWxAppConfByRequest(copyYb string) *WxAppConf {
+	appConf := &WxAppConf{
+		WxId:              `gh_75abb562a946`,
+		WxAppId:           `wxb059c872d79b9967`,
+		WxAppSecret:       `1737c73e9f69a21de1a345b8f0800258`,
+		WxPlatform:        utils.USER_RECORD_PLATFORM_YB,
+		MsgRedirectTarget: 1,
+	}
+	// 备用小程序
+	if copyYb == "true" {
+		appConf.WxAppId = `wx9a2a9b49a00513a0`
+		appConf.WxAppSecret = `4dea76ad9482bdd4e71cf305f669d09f`
+		appConf.WxPlatform = utils.USER_RECORD_PLATFORM_COPY_YB
+		appConf.MsgRedirectTarget = 4
+	}
+	return appConf
+}
+
 // GetSunCode 获取太阳码
 func GetSunCodeV2(page string) (resp []byte, err error) {
 	codePars := qrcode.QRCoder{
@@ -108,7 +190,7 @@ func GetSunCodeV2(page string) (resp []byte, err error) {
 		EnvVersion: EnvVersion,
 		Width:      256,
 	}
-	wechatClient := GetWxApp()
+	wechatClient := GetWxApp("false")
 	qr := wechatClient.GetQRCode()
 	return qr.GetWXACode(codePars)
 }

+ 17 - 7
utils/constants.go

@@ -120,6 +120,8 @@ const (
 	DATA_SOURCE_CALCULATE_ZDYFX                                 // 自定义分析->74
 	DATA_SOURCE_CALCULATE_RJZ                                   // 日均值计算->75
 	DATA_SOURCE_BUSINESS                             = 84       // 来源于自有数据
+	DATA_SOURCE_CALCULATE_RANGEANLYSIS               = 87       //区间计算->87
+	DATA_SOURCE_PREDICT_CALCULATE_RANGEANLYSIS       = 90       // 预测指标区间计算->90
 )
 
 const (
@@ -156,6 +158,7 @@ var PermissionFiccClassifyArr = [...]string{"宏观经济", "化工产业", "建
 // 缓存key
 const (
 	CACHE_KEY_USER_VIEW = "user_view_record" //用户阅读数据
+	CACHE_CHART_AUTH    = "eta:chart:auth:"  //图表数据授权
 )
 
 // es相关
@@ -172,6 +175,12 @@ var (
 const ALIYUN_YBIMG_HOST = "https://hzstatic.hzinsights.com/static/yb_wx/"
 const HZ_DEFAULT_AVATAR = "https://hzstatic.hzinsights.com/static/yb_wx/hz_default_avatar.png"
 
+const (
+	DEFAULT_HONGZE_USER_LOGO      = "https://hzstatic.hzinsights.com/static/icon/hzyb/default_avatar.png"      //个人中心默认头像、匿名用户留言默认头像
+	DEFAULT_HONGZE_USER_LOGO_GRAY = "https://hzstatic.hzinsights.com/static/icon/hzyb/default_avatar_gray.png" //默认头像-未登录状态
+	DEFAULT_HONGZE_SYS_LOGO       = "https://hzstatic.hzinsights.com/static/yb_wx/hongze_sys_default_head.png" //弘则官方默认头像
+)
+
 const HZPHONE = "057187186319" //弘则电话
 const HZ_ADMIN_WX_ACCESS_TOEKN = "hz_admin:wx:access_token:"
 
@@ -188,13 +197,10 @@ const (
 	USER_RECORD_PLATFORM_PC                   // PC端网站
 	USER_RECORD_PLATFORM_CYGX                 // 查研观向小程序
 	_
-	USER_RECORD_PLATFORM_YB // 研报小程序
-)
-
-const (
-	DEFAULT_HONGZE_USER_LOGO      = "https://hzstatic.hzinsights.com/static/icon/hzyb/default_avatar.png"      //个人中心默认头像、匿名用户留言默认头像
-	DEFAULT_HONGZE_USER_LOGO_GRAY = "https://hzstatic.hzinsights.com/static/icon/hzyb/default_avatar_gray.png" //默认头像-未登录状态
-	DEFAULT_HONGZE_SYS_LOGO       = "https://hzstatic.hzinsights.com/static/yb_wx/hongze_sys_default_head.png" //弘则官方默认头像
+	USER_RECORD_PLATFORM_YB      // 研报小程序-6
+	_                            // 查研观向开发平台-7
+	_                            // 查研观向小助手公众号-8
+	USER_RECORD_PLATFORM_COPY_YB // 研报备用小程序-9
 )
 
 const (
@@ -266,6 +272,9 @@ var SystemSourceList = []int{
 	DATA_SOURCE_CALCULATE_PERCENTILE,                 //百分位->68
 	DATA_SOURCE_PREDICT_CALCULATE_STANDARD_DEVIATION, //预测标准差->69
 	DATA_SOURCE_PREDICT_CALCULATE_PERCENTILE,         //预测百分位->70
+	DATA_SOURCE_CALCULATE_RANGEANLYSIS,               //区间计算->87
+	DATA_SOURCE_PREDICT_CALCULATE_RANGEANLYSIS,       // 预测指标区间计算->90
+
 }
 
 const (
@@ -298,6 +307,7 @@ const (
 	CHART_SOURCE_LINE_FEATURE_FREQUENCY          = 9  // 统计特征-频率分布图表
 	CHART_SOURCE_CROSS_HEDGING                   = 10 // 跨品种分析图表
 	CHART_SOURCE_BALANCE_EXCEL                   = 11 // 平衡表图表
+	CHART_SOURCE_RANGE_ANALYSIS                  = 12 // 	区间分析图表
 )
 
 // 图表类型

+ 39 - 0
utils/gin.go

@@ -0,0 +1,39 @@
+package utils
+
+import "github.com/gin-gonic/gin"
+
+// ContextLogName 上下文日志的名称
+const ContextLogName = `context_custom_log`
+
+// SetContextLogListByClaims
+// @Description: 设置上下文中日志
+// @author: Roc
+// @datetime 2024-10-31 10:55:15
+// @param c *gin.Context
+// @param logStr string
+func SetContextLogListByClaims(c *gin.Context, logStr string) {
+	bridgeLogList := GetContextLogListByClaims(c)
+	bridgeLogList = append(bridgeLogList, logStr)
+	c.Set(ContextLogName, bridgeLogList)
+
+	return
+}
+
+// GetContextLogListByClaims
+// @Description: 从Claims中获取当前日志
+// @author: Roc
+// @datetime 2024-10-31 10:35:04
+// @param c *gin.Context
+// @return bridgeLogList []string
+func GetContextLogListByClaims(c *gin.Context) (bridgeLogList []string) {
+	//获取jwt数据失败
+	claims, exists := c.Get(ContextLogName)
+	// 如果不存在,那么就是空切片
+	if !exists {
+		bridgeLogList = []string{}
+		return
+	}
+	bridgeLogList = claims.([]string)
+
+	return
+}