Roc 2 месяцев назад
Родитель
Сommit
e3e47e6c5d

+ 2 - 2
controllers/chart.go

@@ -43,8 +43,8 @@ func (this *ChartController) ChartDetail() {
 		br.ErrMsg = "参数不能为空!"
 		return
 	}
-	url := utils.ChartLibUrl + `/chart/common/detail?UniqueCode=%s`
-	url = fmt.Sprintf(url, req.UniqueCode)
+	url := utils.ChartLibUrl + `/chart/common/detail?UniqueCode=%s&AuthToken=%s`
+	url = fmt.Sprintf(url, req.UniqueCode, req.AuthToken)
 
 	resp, e := http.Get(url)
 	if e != nil {

+ 24 - 0
controllers/english_report.go

@@ -3,6 +3,7 @@ package controllers
 import (
 	"errors"
 	"eta/eta_report/models"
+	"eta/eta_report/services"
 	"eta/eta_report/services/alarm_msg"
 	"eta/eta_report/utils"
 	"fmt"
@@ -132,6 +133,29 @@ func (this *EnglishReportShareController) EnglishReportDetail() {
 		}
 	}
 
+	var hasAuth bool
+	authToken := this.GetString("AuthToken")
+	if authToken != "" {
+		key := fmt.Sprint(utils.CACHE_REPORT_AUTH, `en:`, authToken)
+		redisReportId, err := utils.Rc.GetUInt64(key)
+		if err == nil && int(redisReportId) == report.Id {
+			hasAuth = true
+		}
+	}
+
+	// 报告内图表授权
+	if v, ok := conf[models.BusinessConfIsOpenChartExpired]; ok {
+		if v == `true` {
+			resp.IsOpenChartExpired = true
+
+			// 有权限才会添加token
+			if hasAuth {
+				tokenMap := make(map[string]string)
+				report.Content = services.HandleReportContent(report.Content, "add", tokenMap)
+			}
+		}
+	}
+
 	resp.Hz = 1
 	resp.Report = report
 	br.Ret = 200

+ 1 - 1
controllers/excel.go

@@ -37,7 +37,7 @@ func (this *ExcelController) ExcelDetail() {
 		br.ErrMsg = "参数解析失败,Err:" + err.Error()
 		return
 	}
-	url := fmt.Sprintf(utils.ChartLibUrl+`/excel_info/detail?UniqueCode=%s&FromScene=%d&ReferencedId=%d&Uuid=%s`, req.UniqueCode, req.FromScene, req.ReferencedId, req.Uuid)
+	url := fmt.Sprintf(utils.ChartLibUrl+`/excel_info/detail?UniqueCode=%s&FromScene=%d&ReferencedId=%d&Uuid=%s&AuthToken=%s`, req.UniqueCode, req.FromScene, req.ReferencedId, req.Uuid, req.AuthToken)
 
 	resp, e := http.Get(url)
 	if e != nil {

+ 32 - 0
controllers/report_share.go

@@ -3,6 +3,8 @@ package controllers
 import (
 	"encoding/json"
 	"eta/eta_report/models"
+	"eta/eta_report/services"
+	"eta/eta_report/utils"
 	"fmt"
 	"html"
 	"strings"
@@ -34,6 +36,7 @@ func (this *ReportShareController) Detail() {
 		br.ErrMsg = "参数错误,reportCode 为空"
 		return
 	}
+
 	report, err := models.GetReportByCode(reportCode)
 	if err != nil {
 		br.Msg = "该报告已删除"
@@ -163,6 +166,35 @@ func (this *ReportShareController) Detail() {
 		return
 	}
 
+	var hasAuth bool
+	authToken := this.GetString("AuthToken")
+	if authToken != "" {
+		key := fmt.Sprint(utils.CACHE_REPORT_AUTH, authToken)
+		redisReportId, err := utils.Rc.GetUInt64(key)
+		if err == nil && int(redisReportId) == report.Id {
+			hasAuth = true
+		}
+	}
+
+	// 报告内图表授权
+	if v, ok := conf[models.BusinessConfIsOpenChartExpired]; ok {
+		if v == `true` {
+			resp.IsOpenChartExpired = true
+
+			// 有权限才会添加token
+			if hasAuth {
+				tokenMap := make(map[string]string)
+				report.Content = services.HandleReportContent(report.Content, "add", tokenMap)
+				//report.ContentStruct = services.HandleReportContent(report.ContentStruct, "add", tokenMap)
+
+				for _, v := range reportChapters {
+					v.Content = services.HandleReportContent(v.Content, "add", tokenMap)
+				}
+			}
+
+		}
+	}
+
 	resp.Report.Report = report
 	resp.Report.ChapterList = reportChapters
 

+ 1 - 0
models/business_conf.go

@@ -23,6 +23,7 @@ const (
 	BusinessConfReportEnLogoShow     = "ReportEnLogoShow"     // 报告logo
 	BusinessConfDisclaimerEn         = "DisclaimerEn"         // 英文免责声明
 	BusinessConfReport2ImgReplace    = "Report2ImgReplace"    // 报告转图替换地址
+	BusinessConfIsOpenChartExpired   = "IsOpenChartExpired"   // 图表是否鉴权
 )
 
 // BusinessConf 商户配置表

+ 9 - 8
models/english_report.go

@@ -55,14 +55,15 @@ func GetEnglishReportByCode(reportCode string) (item *EnglishReportDetail, err e
 }
 
 type EnglishReportShareDetailResp struct {
-	Report           *EnglishReportDetail `description:"报告"`
-	H5ShareEnName    string               `description:"研报分享抬头"`
-	H5ReportShareImg string               `description:"研报分享图片"`
-	WatermarkChart   string               `description:"图表是否需要水印"`
-	WatermarkReport  string               `description:"报告是否需要水印"`
-	Hz               int
-	DisclaimerEn     string `description:"免责声明"`
-	ReportLogo       string `description:"报告logo"`
+	Report             *EnglishReportDetail `description:"报告"`
+	H5ShareEnName      string               `description:"研报分享抬头"`
+	H5ReportShareImg   string               `description:"研报分享图片"`
+	WatermarkChart     string               `description:"图表是否需要水印"`
+	WatermarkReport    string               `description:"报告是否需要水印"`
+	Hz                 int
+	DisclaimerEn       string `description:"免责声明"`
+	ReportLogo         string `description:"报告logo"`
+	IsOpenChartExpired bool   `description:"是否开启图表有效期鉴权/报告禁止复制"`
 }
 
 func UpdateEnglishReportCounts(reportCode string) (err error) {

+ 9 - 8
models/report.go

@@ -81,14 +81,15 @@ func GetReportByCode(reportCode string) (item *Report, err error) {
 }
 
 type ReportShareDetailResp struct {
-	Report           *ReportItem `description:"报告"`
-	Disclaimer       string      `description:"免责声明"`
-	H5ShareName      string      `description:"研报分享抬头"`
-	H5ReportShareImg string      `description:"研报分享图片"`
-	WatermarkChart   string      `description:"图表是否需要水印"`
-	WatermarkReport  string      `description:"报告是否需要水印"`
-	Hz               int
-	ReportLogo       string `description:"报告logo"`
+	Report             *ReportItem `description:"报告"`
+	Disclaimer         string      `description:"免责声明"`
+	H5ShareName        string      `description:"研报分享抬头"`
+	H5ReportShareImg   string      `description:"研报分享图片"`
+	WatermarkChart     string      `description:"图表是否需要水印"`
+	WatermarkReport    string      `description:"报告是否需要水印"`
+	Hz                 int
+	ReportLogo         string `description:"报告logo"`
+	IsOpenChartExpired bool   `description:"是否开启图表有效期鉴权/报告禁止复制"`
 }
 
 type ReportItem struct {

+ 1 - 0
services/chart.go

@@ -3,4 +3,5 @@ package services
 type ChartDetailReq struct {
 	UniqueCode string //"图表唯一编码,如果是管理后台访问,传固定字符串:7c69b590249049942070ae9dcd5bf6dc"
 	IsReplace  int    // 是否报告转长图的场景:1-是(为了兼容内网客户,需要判断配置中是否要替换地址)
+	AuthToken  string //"授权token
 }

+ 1 - 0
services/excel.go

@@ -5,4 +5,5 @@ type ExcelDetailReq struct {
 	FromScene    int    //"场景来源,1:智能研报,2:研报列表;3:英文研报;4:中文PPT;5:英文PPT"
 	ReferencedId int    `description:"被引用的ID"`
 	Uuid         string `description:"引用唯一标识"`
+	AuthToken  string //"授权token"
 }

+ 238 - 0
services/report.go

@@ -0,0 +1,238 @@
+package services
+
+import (
+	"encoding/json"
+	"eta/eta_report/utils"
+	"fmt"
+	html2 "golang.org/x/net/html"
+	"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 {
+			utils.FileLog.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 {
+			utils.FileLog.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 = utils.Rc.Put(key, uniqueCode, expireTime)
+
+	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" {
+				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
+}

+ 5 - 0
utils/constants.go

@@ -89,6 +89,7 @@ const (
 	HZ_ADMIN_WX_ACCESS_TOEKN   = "hz_admin:wx:access_token:"
 	CACHE_WX_ACCESS_TOKEN_CYGX = "xygxxzs_wxtoken" //查研观向小助手公众号 微信accessToken
 	ETA_WX_ACCESS_TOEKN        = "eta:wx:access_token:"
+	CACHE_CHART_AUTH           = "eta:chart:auth:" //图表数据授权
 )
 
 // 商户号
@@ -121,3 +122,7 @@ const (
 	DbNameAI          = "eta_ai"
 	DbNameWeekly      = "weekly_report"
 )
+
+const (
+	CACHE_REPORT_AUTH = "eta:report:auth:" //报告图表数据授权
+)

+ 2 - 0
utils/redis.go

@@ -7,6 +7,8 @@ import (
 
 type RedisClient interface {
 	Get(key string) interface{}
+	GetStr(key string) string
+	GetUInt64(key string) (uint64, error)
 	RedisBytes(key string) (data []byte, err error)
 	RedisString(key string) (data string, err error)
 	RedisInt(key string) (data int, err error)

+ 19 - 0
utils/redis/cluster_redis.go

@@ -86,6 +86,25 @@ func (rc *ClusterRedisClient) Get(key string) interface{} {
 	return data
 }
 
+// GetStr
+// @Description: 根据key获取字符串数据
+// @receiver rc
+// @param key
+// @return string
+func (rc *ClusterRedisClient) GetStr(key string) string {
+	return rc.redisClient.Get(context.TODO(), key).Val()
+}
+
+// GetUInt64
+// @Description: 根据key获取uint64数据
+// @receiver rc
+// @param key
+// @return int
+// @return error
+func (rc *ClusterRedisClient) GetUInt64(key string) (uint64, error) {
+	return rc.redisClient.Get(context.TODO(), key).Uint64()
+}
+
 // RedisBytes
 // @Description: 根据key获取字节编码数据
 // @receiver rc

+ 19 - 0
utils/redis/standalone_redis.go

@@ -78,6 +78,25 @@ func (rc *StandaloneRedisClient) Get(key string) interface{} {
 	return data
 }
 
+// GetStr
+// @Description: 根据key获取字符串数据
+// @receiver rc
+// @param key
+// @return string
+func (rc *StandaloneRedisClient) GetStr(key string) string {
+	return rc.redisClient.Get(context.TODO(), key).Val()
+}
+
+// GetUInt64
+// @Description: 根据key获取uint64数据
+// @receiver rc
+// @param key
+// @return int
+// @return error
+func (rc *StandaloneRedisClient) GetUInt64(key string) (uint64, error) {
+	return rc.redisClient.Get(context.TODO(), key).Uint64()
+}
+
 // RedisBytes
 // @Description: 根据key获取字节编码数据
 // @receiver rc