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

3.1发版安全漏洞修复

kobe6258 3 місяців тому
батько
коміт
84a98d0ac4

+ 5 - 0
common/component/config/ht_biz_config.go

@@ -6,6 +6,7 @@ import "eta/eta_mini_ht_api/common/contants"
 type HTOpts struct {
 	ReportIndex       string
 	MediaIndex        string
+	ChartIndex        string
 	Encode            string
 	DesCode           string
 	Task              string
@@ -32,6 +33,9 @@ func (e *HTBizConfig) GetReportIndex() string {
 func (e *HTBizConfig) GetMediaIndex() string {
 	return e.opts.MediaIndex
 }
+func (e *HTBizConfig) GetChartIndex() string {
+	return e.opts.ChartIndex
+}
 
 func (e *HTBizConfig) GetAccountInfoUrl() string {
 	return e.opts.AccountInfoUrl
@@ -84,6 +88,7 @@ func (e *HTBizConfig) InitConfig() {
 	opts := HTOpts{
 		ReportIndex:       e.GetString("es_report_index"),
 		MediaIndex:        e.GetString("es_media_index"),
+		ChartIndex:        e.GetString("es_chart_index"),
 		Encode:            e.GetString("response.encode"),
 		DesCode:           e.GetString("response.des_code"),
 		Task:              e.GetString("task"),

+ 139 - 13
common/component/es/es.go

@@ -4,6 +4,7 @@ import (
 	"bytes"
 	"context"
 	"encoding/json"
+	"errors"
 	"eta/eta_mini_ht_api/common/component/config"
 	logger "eta/eta_mini_ht_api/common/component/log"
 	"eta/eta_mini_ht_api/common/contants"
@@ -35,15 +36,16 @@ const (
 	MatchAll = "match_all"
 	Match    = "match"
 
-	CountWithDocIds            = "count_with_doc_ids"
-	Range                      = "range"
-	MatchAllByCondition        = "match_all_by_condition"
-	RangeByCondition           = "range_by_condition"
-	RangeByConditionWithDocIds = "range_by_condition_with_doc_ids"
-
-	RangeWithDocIds = "range_with_doc_ids"
-	LimitByScore    = "limit_by_score"
-	HomeSearch      = "home_search"
+	CountWithDocIds                          = "count_with_doc_ids"
+	Range                                    = "range"
+	MatchAllByCondition                      = "match_all_by_condition"
+	RangeByCondition                         = "range_by_condition"
+	RangeByConditionWithDocIds               = "range_by_condition_with_doc_ids"
+	RangeByConditionWithDocIdsNoLimit        = "range_by_condition_with_doc_ids_no_limit"
+	RangeByConditionWithDocIdsNoLimitByScore = "range_by_condition_with_doc_ids_no_limit_by_score"
+	RangeWithDocIds                          = "range_with_doc_ids"
+	LimitByScore                             = "limit_by_score"
+	HomeSearch                               = "home_search"
 )
 
 func GetInstance() *ESClient {
@@ -152,6 +154,16 @@ type Hit struct {
 	Highlight json.RawMessage `json:"highlight"`
 }
 
+type Doc struct {
+	Index       string          `json:"_index"`
+	Type        string          `json:"_type"`
+	ID          string          `json:"_id"`
+	Version     float64         `json:"_version"`
+	SeqNo       float64         `json:"_seq_no"`
+	PrimaryTerm float64         `json:"_primary_term"`
+	Found       bool            `json:"found"`
+	Source      json.RawMessage `json:"_source"`
+}
 type ShardsInfo struct {
 	Total      int `json:"total"`
 	Successful int `json:"successful"`
@@ -415,6 +427,61 @@ func (req *ESQueryRequest) parseJsonQuery() (queryMap map[string]interface{}) {
 			},
 		}
 		return
+	case RangeByConditionWithDocIdsNoLimit:
+		queryMap = map[string]interface{}{
+			"query": map[string]interface{}{
+				"match": map[string]interface{}{
+					req.Column: req.Key,
+				},
+			},
+			"highlight": map[string]interface{}{
+				"fields": map[string]interface{}{
+					req.Column: map[string]interface{}{},
+				},
+				"pre_tags":  []string{"<span style='color:#0078E8'>"},
+				"post_tags": []string{"</span>"},
+			},
+			"post_filter": map[string]interface{}{
+				"bool": map[string]interface{}{
+					"must": []map[string]interface{}{
+						{
+							"terms": map[string]interface{}{
+								"_id": req.DocIds,
+							},
+						},
+					},
+				},
+			},
+		}
+		return
+	case RangeByConditionWithDocIdsNoLimitByScore:
+		queryMap = map[string]interface{}{
+			"query": map[string]interface{}{
+				"match": map[string]interface{}{
+					req.Column: req.Key,
+				},
+			},
+			"highlight": map[string]interface{}{
+				"fields": map[string]interface{}{
+					req.Column: map[string]interface{}{},
+				},
+				"pre_tags":  []string{"<span style='color:#0078E8'>"},
+				"post_tags": []string{"</span>"},
+			},
+			"post_filter": map[string]interface{}{
+				"bool": map[string]interface{}{
+					"must": []map[string]interface{}{
+						{
+							"terms": map[string]interface{}{
+								"_id": req.DocIds,
+							},
+						},
+					},
+				},
+			},
+			"min_score": req.MinScore,
+		}
+		return
 	case LimitByScore:
 		queryMap = map[string]interface{}{
 			"query": map[string]interface{}{
@@ -640,6 +707,13 @@ func (es *ESClient) Update(indexName string, id int, doc interface{}) bool {
 	fmt.Printf("%s\n", string(body))
 	return true
 }
+func (es *ESClient) InsertOrUpdate(indexName string, id int, doc interface{}) (success bool) {
+	if exist, existErr := es.Exist(indexName, id); existErr == nil && exist {
+		return es.Update(indexName, id, doc)
+	} else {
+		return es.CreateDocument(indexName, id, doc)
+	}
+}
 
 // Delete *
 // 删除
@@ -688,25 +762,27 @@ func (es *ESClient) Exist(indexName string, docId int) (exist bool, err error) {
 	res, err := getRequest.Do(context.Background(), es.es())
 	if err != nil {
 		logger.Error("es获取文档是否存在失败: %v", err)
+		return
 	}
 	defer res.Body.Close()
-
 	// 检查文档是否存在
 	if res.IsError() {
 		// 如果文档不存在,通常返回 404 Not Found
 		if res.StatusCode == 404 {
 			logger.Info("文档不存在.")
-			return false, nil
+			err = errors.New("ES文档不存在")
+			return
 		} else {
 			// 其他错误
 			var e map[string]interface{}
 			if err = json.NewDecoder(res.Body).Decode(&e); err != nil {
 				logger.Error("解析es应答失败: %v", err)
-				return false, err
+				return
 			} else {
 				// Print the response status and error information.
 				logger.Error("[%s] %s: %s\n", res.Status(), e["error"].(map[string]interface{})["type"], e["error"].(map[string]interface{})["原因"])
-				return false, nil
+				err = errors.New("获取ES记录失败")
+				return
 			}
 		}
 	} else {
@@ -716,6 +792,56 @@ func (es *ESClient) Exist(indexName string, docId int) (exist bool, err error) {
 	}
 }
 
+func (es *ESClient) Get(indexName string, docId int) (doc Doc, err error) {
+
+	getRequest := esapi.GetRequest{
+		Index:      indexName,
+		DocumentID: strconv.Itoa(docId),
+	}
+	// 执行请求
+	res, err := getRequest.Do(context.Background(), es.es())
+	if err != nil {
+		logger.Error("es获取文档是否存在失败: %v", err)
+		return
+	}
+	defer res.Body.Close()
+	// 检查文档是否存在
+	if res.IsError() {
+		// 如果文档不存在,通常返回 404 Not Found
+		if res.StatusCode == 404 {
+			logger.Info("文档不存在.")
+			err = errors.New("ES文档不存在")
+			return
+		} else {
+			// 其他错误
+			var e map[string]interface{}
+			if err = json.NewDecoder(res.Body).Decode(&e); err != nil {
+				logger.Error("解析es应答失败: %v", err)
+				return
+			} else {
+				// Print the response status and error information.
+				logger.Error("[%s] %s: %s\n", res.Status(), e["error"].(map[string]interface{})["type"], e["error"].(map[string]interface{})["原因"])
+				err = errors.New("获取ES记录失败")
+				return
+			}
+		}
+	} else {
+		// 如果文档存在
+		body, readErr := io.ReadAll(res.Body)
+		if readErr != nil {
+			logger.Error("获取es应答失败: %v", err)
+			err = readErr
+			return
+		}
+		err = json.Unmarshal(body, &doc)
+		if err != nil {
+			logger.Error("反序列化es应答失败: %v", err)
+			return
+		}
+		return
+	}
+}
+
 //
 //func CreateIndex(indexName string) error {
 //	resp, err := esClient.es().Indices.

+ 9 - 6
common/contants/sys_config_enum.go

@@ -12,16 +12,19 @@ const (
 )
 const (
 	// configCode
-	HTCPELoginUrl = "HtCpeLoginUrl"
-	ShowMediaView = "ShowMediaView"
-	PaymentWay    = "PaymentWay"
+
+	HTCPELoginUrl     = "HtCpeLoginUrl"
+	ChartLibUrlPrefix = "ChartLibUrlPrefix"
+	ShowMediaView     = "ShowMediaView"
+	PaymentWay        = "PaymentWay"
 )
 
 // SysConfigMap 用于存储错误码和错误信息的映射
 var SysConfigMap = map[string]*Config{
-	HTCPELoginUrl: {ConfigId: 1000, ConfigType: ConfigTypeStr},
-	ShowMediaView: {ConfigId: 1001, ConfigType: ConfigTypeInt},
-	PaymentWay:    {ConfigId: 1003, ConfigType: ConfigTypeStr},
+	HTCPELoginUrl:     {ConfigId: 1000, ConfigType: ConfigTypeStr},
+	ShowMediaView:     {ConfigId: 1001, ConfigType: ConfigTypeInt},
+	ChartLibUrlPrefix: {ConfigId: 1002, ConfigType: ConfigTypeStr},
+	PaymentWay:        {ConfigId: 1003, ConfigType: ConfigTypeStr},
 }
 
 func GetConfig(code string) *Config {

+ 26 - 0
common/exception/exc_enums.go

@@ -88,6 +88,7 @@ const (
 	GetOrderListFailed
 	GetOrderDetailFailed
 	CloseOrderFailed
+	GetBookMarkListFailed
 )
 
 // WechatErrCode 微信
@@ -112,6 +113,7 @@ const (
 	GetReportSearchRangeFailed
 	SearchKeyEmptyError
 	ReportRiskLevelUnSet
+	ReportDeleted
 )
 
 const (
@@ -119,6 +121,9 @@ const (
 	MediaFoundFailed
 	GetMediaListFailed
 	GetAnalystMediaListFailed
+	ChartImageEmptyError
+	IllegalChartId
+	UpdateChartImageFailed
 )
 
 const (
@@ -163,6 +168,15 @@ const (
 	RefundDealFail
 )
 
+const (
+	BookMarkErrCode int = iota + 120000 // iota 自动递增,从 1 开始
+	IllegalSourceType
+	IllegalSearchKeyword
+	IllegalSourceId
+	BookMarkFailed
+	BookMarkListFailed
+)
+
 // ErrorMap 用于存储错误码和错误信息的映射
 var ErrorMap = map[int]string{
 	SysError:              "系统异常",
@@ -224,6 +238,7 @@ var ErrorMap = map[int]string{
 	GetOrderListFailed:                 "获取产品订单列表失败",
 	GetOrderDetailFailed:               "获取订单详情失败",
 	CloseOrderFailed:                   "关闭订单失败",
+	GetBookMarkListFailed:              "获取收藏列表失败",
 	//微信
 	WeChatServerError:    "微信服务器发生错误",
 	WechatUserInfoFailed: "获取微信用户信息失败",
@@ -239,6 +254,7 @@ var ErrorMap = map[int]string{
 	QueryReportPageFailed:        "分页查询报告列表失败",
 	SearchReportPageFailed:       "分页搜索报告列表失败",
 	GetReportFailed:              "获取研报详情失败",
+	ReportDeleted:                "研报已删除",
 	GetReportSearchRangeFailed:   "设置报告搜索范围失败",
 	SearchKeyEmptyError:          "搜索关键字不能为空",
 	ReportRiskLevelUnSet:         "报告未设置风险等级",
@@ -247,6 +263,9 @@ var ErrorMap = map[int]string{
 	GetMediaListFailed:        "查询媒体列表失败",
 	GetAnalystMediaListFailed: "查询研究员媒体列表失败",
 	BindMobileFailed:          "绑定手机号失败",
+	ChartImageEmptyError:      "图表缩略图url不能为空",
+	IllegalChartId:            "非法的图表ID",
+	UpdateChartImageFailed:    "更新图表缩略图失败",
 	//商户
 	ProductInfoError:      "获取商品信息失败",
 	IllegalProductId:      "非法的产品ID",
@@ -273,6 +292,13 @@ var ErrorMap = map[int]string{
 	PaymentProcessingError:   "支付订单处理中",
 	PaymentDoneError:         "订单已完成支付",
 	RefundDealFail:           "处理退款应答失败",
+
+	//收藏
+	IllegalSourceType:    "非法的资源类型",
+	IllegalSourceId:      "非法的资源ID",
+	BookMarkFailed:       "收藏失败",
+	BookMarkListFailed:   "获取收藏列表失败",
+	IllegalSearchKeyword: "搜索关键字不能为空",
 }
 
 func Equals(code int, message string) bool {

+ 48 - 0
controllers/chart/chart_controller.go

@@ -0,0 +1,48 @@
+package chart
+
+import (
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/exception"
+	"eta/eta_mini_ht_api/controllers"
+	chart "eta/eta_mini_ht_api/service/media"
+)
+
+type ChartController struct {
+	controllers.ListController
+}
+type ChartEsUpdateReq struct {
+	ChartImage  string `json:"chartImage"`
+	ChartInfoId int    `json:"chartInfoId"`
+}
+
+// UpdateChartImage 更新图表缩略图
+// @Description 更新图表缩略图
+// @Success 200 {object}
+// @router /updateChartImage [post]
+func (m *ChartController) UpdateChartImage() {
+	controllers.Wrap(&m.BaseController, func() (result *controllers.WrapData, err error) {
+		result = m.InitWrapData("更新图表缩略图成功")
+		var chartEsUpdateReq ChartEsUpdateReq
+		m.GetPostParams(&chartEsUpdateReq)
+		if chartEsUpdateReq.ChartImage == "" {
+			err = exception.New(exception.ChartImageEmptyError)
+			m.FailedResult("更新图表缩略图失败", result)
+			return
+		}
+		if chartEsUpdateReq.ChartInfoId == 0 {
+			err = exception.New(exception.IllegalChartId)
+			m.FailedResult("更新图表缩略图失败", result)
+			return
+		}
+		success := chart.UpdateChartImage(chartEsUpdateReq.ChartImage, chartEsUpdateReq.ChartInfoId)
+		if !success {
+			err = exception.New(exception.UpdateChartImageFailed)
+			logger.Error("更新图表缩略图失败,chartInfoId:%d", chartEsUpdateReq.ChartInfoId)
+			m.FailedResult("更新图表缩略图失败", result)
+			return
+		}
+
+		m.SuccessResult("更新图表缩略图成功", nil, result)
+		return
+	})
+}

+ 19 - 2
controllers/report/report_controller.go

@@ -377,23 +377,40 @@ func (r *ReportController) GetReport(reportId int, productId int) {
 			logger.Info("当前用户未登录,展示部分详情")
 		}
 		var subscribeStatus string
+		var wg sync.WaitGroup
 		if productId > 0 {
-			var wg sync.WaitGroup
 			wg.Add(1)
 			go func() {
 				defer wg.Done()
 				subscribeStatus = user.GetUserScribeStatus(productId, userInfo.Id)
 			}()
+
 		} else {
 			subscribeStatus = user.UnSubscribe
 		}
+		wg.Add(1)
+		var isCollect bool
+		go func() {
+			defer wg.Done()
+			isCollect, err = user.GetReportBookMarked(reportId, userInfo.Id)
+			if err != nil {
+				logger.Error("获取研报收藏状态失败:%v", err)
+			}
+		}()
+		wg.Wait()
 		reportDetail, err := report.GetReportById(reportId, isLogin(detailType), userInfo.Id)
 		if err != nil {
-			r.FailedResult("获取研报详情失败", result)
+			if exception.Equals(exception.ReportDeleted, err.Error()) {
+				r.FailedResult("报告已被删除,请返回重试", result)
+				err = exception.New(exception.ReportDeleted)
+				return
+			}
+			r.FailedResult("报告状态异常,请返回重试", result)
 			err = exception.New(exception.GetReportFailed)
 			return
 		}
 		reportDetail.SubscribeStatus = subscribeStatus
+		reportDetail.IsCollect = isCollect
 		r.SuccessResult("获取研报详情成功", reportDetail, result)
 		return
 	})

+ 578 - 0
controllers/user/bookmark_controller.go

@@ -0,0 +1,578 @@
+package user
+
+import (
+	"errors"
+	"eta/eta_mini_ht_api/common/component/cache"
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/exception"
+	"eta/eta_mini_ht_api/common/utils/page"
+	"eta/eta_mini_ht_api/controllers"
+	permissionService "eta/eta_mini_ht_api/domian/config"
+	reportDomian "eta/eta_mini_ht_api/domian/report"
+	chartService "eta/eta_mini_ht_api/service/media"
+	"eta/eta_mini_ht_api/service/report"
+	"eta/eta_mini_ht_api/service/user"
+	userService "eta/eta_mini_ht_api/service/user"
+	"gorm.io/gorm"
+	"sync"
+)
+
+// BookMarkController Operations about bookmark
+type BookMarkController struct {
+	controllers.ListController
+	redis *cache.RedisCache
+}
+
+const (
+	Chart  = "chart"
+	Report = "report"
+)
+
+func (bk *BookMarkController) Prepare() {
+	bk.ListController.Prepare()
+	bk.redis = cache.GetInstance()
+}
+
+type BookMarkReq struct {
+	SourceType  string `json:"sourceType"`
+	SourceId    int    `json:"sourceId"`
+	ChartImage  string `json:"chartImage"`
+	ChartInfoId int    `json:"chartInfoId"`
+	ChartName   string `json:"chartName"`
+	UniqueCode  string `json:"uniqueCode"`
+}
+
+// BookMark  收藏
+// @Summary 收藏
+// @Description 收藏
+// @Success 200 {object} controllers.BaseResponse
+// @router /bookMark [post]
+func (bk *BookMarkController) BookMark() {
+	controllers.Wrap(&bk.BaseController, func() (result *controllers.WrapData, err error) {
+		result = bk.InitWrapData("收藏失败")
+		bookMark := new(BookMarkReq)
+		bk.GetPostParams(bookMark)
+		var userInfo user.User
+		userInfo = bk.Data["user"].(user.User)
+		if bookMark.SourceType == "" {
+			bk.FailedResult("收藏失败", result)
+			err = exception.New(exception.IllegalSourceType)
+			return
+		}
+		if bookMark.SourceId == 0 {
+			bk.FailedResult("收藏失败", result)
+			err = exception.New(exception.IllegalSourceId)
+			return
+		}
+		err = userService.BookMark(userInfo.Id, bookMark.SourceId, bookMark.SourceType)
+		if err != nil {
+			err = exception.NewWithException(exception.FeedBackError, err.Error())
+			bk.FailedResult("收藏失败", result)
+			return
+		}
+		//将图表加入es
+		if bookMark.SourceType == Chart {
+			chartService.AddChartToEs(chartService.ChartInfo{
+				ChartImage:  bookMark.ChartImage,
+				ChartInfoId: bookMark.ChartInfoId,
+				ChartName:   bookMark.ChartName,
+				UniqueCode:  bookMark.UniqueCode,
+			})
+		}
+		bk.SuccessResult("收藏成功", nil, result)
+		return
+	})
+}
+
+// UnBookMark  取消收藏
+// @Summary 取消收藏
+// @Description 取消收藏
+// @Success 200 {object} controllers.BaseResponse
+// @router /unBookMark [post]
+func (bk *BookMarkController) UnBookMark() {
+	controllers.Wrap(&bk.BaseController, func() (result *controllers.WrapData, err error) {
+		result = bk.InitWrapData("取消收藏失败")
+		bookMark := new(BookMarkReq)
+		bk.GetPostParams(bookMark)
+		var userInfo user.User
+		userInfo = bk.Data["user"].(user.User)
+		if bookMark.SourceType == "" {
+			bk.FailedResult("取消收藏失败", result)
+			err = exception.New(exception.IllegalSourceType)
+			return
+		}
+		if bookMark.SourceId == 0 {
+			bk.FailedResult("取消收藏失败", result)
+			err = exception.New(exception.IllegalSourceId)
+			return
+		}
+		err = userService.UnBookMark(userInfo.Id, bookMark.SourceId, bookMark.SourceType)
+		if err != nil {
+			err = exception.NewWithException(exception.FeedBackError, err.Error())
+			bk.FailedResult("取消收藏失败", result)
+			return
+		}
+		bk.SuccessResult("收藏成功", nil, result)
+		return
+	})
+}
+
+type CheckBookMarkResp struct {
+	IsBookMarked bool `json:"isBookMarked"`
+}
+
+// CheckBookMark  取消收藏
+// @Summary 取消收藏
+// @Description 取消收藏
+// @Success 200 {object} controllers.BaseResponse
+// @router /checkBookMark [post]
+func (bk *BookMarkController) CheckBookMark() {
+	controllers.Wrap(&bk.BaseController, func() (result *controllers.WrapData, err error) {
+		result = bk.InitWrapData("取消收藏失败")
+		bookMark := new(BookMarkReq)
+		bk.GetPostParams(bookMark)
+		var userInfo user.User
+		userInfo = bk.Data["user"].(user.User)
+		if bookMark.SourceType == "" {
+			bk.FailedResult("获取是否收藏失败", result)
+			err = exception.New(exception.IllegalSourceType)
+			return
+		}
+		if bookMark.SourceId == 0 {
+			bk.FailedResult("获取是否收藏失败", result)
+			err = exception.New(exception.IllegalSourceId)
+			return
+		}
+		isBookMarked, err := userService.CheckBookMarkStatus(userInfo.Id, bookMark.SourceId, bookMark.SourceType)
+		if err != nil {
+			err = exception.NewWithException(exception.FeedBackError, err.Error())
+			bk.FailedResult("获取是否收藏失败", result)
+			return
+		}
+		bk.SuccessResult("获取是否收藏成功", &CheckBookMarkResp{
+			IsBookMarked: isBookMarked,
+		}, result)
+		return
+	})
+}
+
+// BookMarkSearch 搜索收藏列表
+// @Description 搜索报告列表
+// @Success 200 {object}
+// @router /bookMarkSearch [get]
+func (bk *BookMarkController) BookMarkSearch(key string) {
+	controllers.Wrap(&bk.BaseController, func() (result *controllers.WrapData, err error) {
+		result = bk.InitWrapData("分页搜索报告列表失败")
+		if key == "" {
+			err = exception.New(exception.SearchKeyEmptyError)
+			bk.FailedResult("分页搜索报告列表失败", result)
+			return
+		}
+		userInfo := bk.Data["user"].(user.User)
+		pageRes := page.Page{
+			Current:  bk.PageInfo.Current,
+			PageSize: bk.PageInfo.PageSize,
+		}
+		//获取当前可以被搜索的报告原始ID
+		//先要限制查询的id范围
+		var reportIds []int
+		var mappingRiskLevel string
+		var userRiskStatus string
+		pageRes.Total, pageRes.LatestId, reportIds, _, mappingRiskLevel, userRiskStatus, err = report.RangeSearch(key, true, userInfo.Id)
+		if err != nil {
+			logger.Error("搜索收藏列表失败%v", err)
+			err = exception.NewWithException(exception.GetBookMarkListFailed, err.Error())
+			bk.FailedResult("分页搜索收藏列表失败", result)
+			return
+		}
+		if len(reportIds) == 0 {
+			reports := new(page.PageResult)
+			reports.Data = []reportDomian.ReportDTO{}
+			reports.Page = pageRes
+			logger.Info("没有可以查询的报告列表")
+			bk.SuccessResult("分页搜索报告列表成功", reports, result)
+			return
+		}
+
+		if bk.PageInfo.LatestId == 0 {
+			//pageRes.Total, pageRes.LatestId = report.SearchMaxReportId(key)
+			bk.PageInfo.LatestId = pageRes.LatestId
+			bk.PageInfo.Total = pageRes.Total
+		} else {
+			pageRes.LatestId = bk.PageInfo.LatestId
+			pageRes.Total = bk.PageInfo.Total
+		}
+		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
+		list := make([]reportDomian.ReportDTO, 0)
+		if pageRes.LatestId > 0 {
+			list, err = report.SearchReportList(key, reportIds, bk.PageInfo, true, userInfo.Id, mappingRiskLevel, userRiskStatus)
+			if err != nil {
+				bk.FailedResult("分页搜索报告列表失败", result)
+				return
+			}
+		}
+		reports := new(page.PageResult)
+		reports.Data = list
+		reports.Page = pageRes
+		bk.SuccessResult("分页搜索报告列表成功", reports, result)
+		return
+	})
+}
+
+type BookMarkListReq struct {
+	SourceType string `json:"source_type"`
+}
+
+// SearchBookMark 搜索收藏列表
+// @Description 搜索收藏列表
+// @Success 200 {object}
+// @router /searchBookMark [get]
+func (bk *BookMarkController) SearchBookMark(sourceType string, key string) {
+	controllers.Wrap(&bk.BaseController, func() (result *controllers.WrapData, err error) {
+		result = bk.InitWrapData("分页搜索收藏列表失败")
+		pageRes := page.Page{
+			Current:  bk.PageInfo.Current,
+			PageSize: bk.PageInfo.PageSize,
+		}
+		if sourceType == "" || (sourceType != Report && sourceType != Chart) {
+			err = exception.New(exception.IllegalSourceType)
+			bk.FailedResult("分页搜索收藏列表失败", result)
+			return
+		}
+		if key == "" {
+			err = exception.New(exception.IllegalSearchKeyword)
+			bk.FailedResult("分页搜索收藏列表失败", result)
+			return
+		}
+		userInfo := bk.Data["user"].(user.User)
+		var sourceIds []int
+		pageRes.Total, sourceIds, err = user.GetTotalBookMarkPageBySourceType(userInfo.Id, sourceType)
+		if err != nil {
+			logger.Error("获取收藏列表失败%v", err)
+			err = exception.NewWithException(exception.GetBookMarkListFailed, err.Error())
+			bk.FailedResult("分页查询收藏列表失败", result)
+			return
+		}
+		//隐藏品种信息未设置风险等级的报告
+		if sourceType == Report {
+			pageRes.Total, sourceIds, err = report.FilterReportIds(sourceIds)
+		}
+		if pageRes.Total == 0 {
+			bookMarks := new(page.PageResult)
+			bookMarks.Data = []interface{}{}
+			bookMarks.Page = pageRes
+			bk.SuccessResult("分页搜索收藏列表列表成功", bookMarks, result)
+			return
+		}
+		pageRes.Total = bk.PageInfo.Total
+		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
+		var bookMarkList []userService.BookMarkInterface
+		switch sourceType {
+		case Report:
+			reportList, reportErr := report.SearchReportBookMark(key, sourceIds, bk.PageInfo, true, userInfo.Id)
+			if reportErr != nil {
+				logger.Error("搜索研报列表失败%v", err)
+				err = exception.NewWithException(exception.GetBookMarkListFailed, reportErr.Error())
+				bk.FailedResult("分页搜索收藏列表失败", result)
+				return
+			}
+			for _, reportInfo := range reportList {
+				bookMarkList = append(bookMarkList, ConvertToBookMarkReport(reportInfo))
+			}
+		case Chart:
+			chartList, chartErr := chartService.SearchChartList(key, sourceIds, bk.PageInfo)
+			if chartErr != nil {
+				logger.Error("搜索研报列表失败%v", err)
+				err = exception.NewWithException(exception.GetBookMarkListFailed, chartErr.Error())
+				bk.FailedResult("分页搜索收藏列表失败", result)
+				return
+			}
+			for _, chart := range chartList {
+				bookMarkList = append(bookMarkList, ConvertToBookMarkChart(chart))
+			}
+		default:
+			err = exception.NewWithException(exception.GetBookMarkListFailed, "不支持的收藏类型")
+			bk.FailedResult("分页搜索收藏列表失败", result)
+			return
+		}
+		//bookMarkList, err = user.SearchBookMark(key, sourceType, sourceIds, bk.PageInfo, userInfo.Id)
+		bookMarks := new(page.PageResult)
+		bookMarks.Data = bookMarkList
+		bookMarks.Page = pageRes
+		bk.SuccessResult("分页搜索收藏列表成功", bookMarks, result)
+		return
+	})
+}
+func ConvertToBookMarkChart(chart chartService.ChartInfo) userService.BookMarkChart {
+	return userService.BookMarkChart{
+		ChartName:   chart.ChartName,
+		ChartImage:  chart.ChartImage,
+		UniqueCode:  chart.UniqueCode,
+		ChartInfoId: chart.ChartInfoId,
+	}
+}
+
+func ConvertToBookMarkReport(report reportDomian.ReportDTO) userService.BookMarkReport {
+	return userService.BookMarkReport{
+		Abstract:         report.Abstract,
+		Author:           report.Author,
+		AuthorInfo:       report.AuthorInfo,
+		ClassifyId:       report.ClassifyId,
+		CoverSrc:         report.CoverSrc,
+		CoverUrl:         report.CoverUrl,
+		Detail:           report.Detail,
+		Highlight:        report.Highlight,
+		IsFree:           report.IsFree,
+		IsPackage:        report.IsPackage,
+		IsSubscribe:      report.IsSubscribe,
+		Login:            report.Login,
+		OrgId:            report.OrgId,
+		PdfUrl:           report.PdfUrl,
+		PermissionNames:  report.PermissionNames,
+		Permissions:      report.Permissions,
+		PlateName:        report.PlateName,
+		Price:            report.Price,
+		ProductId:        report.ProductId,
+		PublishedTime:    report.PublishedTime,
+		ReportID:         report.ReportID,
+		RiskLevel:        report.RiskLevel,
+		RiskLevelStatus:  report.RiskLevelStatus,
+		Score:            report.Score,
+		SecondPermission: report.SecondPermission,
+		Show:             report.Show,
+		Source:           report.Source,
+		SubscribeStatus:  report.SubscribeStatus,
+		Title:            report.Title,
+		Type:             report.Type,
+	}
+}
+
+// BookMarkList 获取收藏列表
+// @Description 获取收藏列表
+// @Success 200 {object}
+// @router /bookMarkList [get]
+func (bk *BookMarkController) BookMarkList(sourceType string) {
+	controllers.Wrap(&bk.BaseController, func() (result *controllers.WrapData, err error) {
+		result = bk.InitWrapData("分页查询收藏列表失败")
+		pageRes := page.Page{
+			Current:  bk.PageInfo.Current,
+			PageSize: bk.PageInfo.PageSize,
+		}
+		if sourceType == "" || (sourceType != Report && sourceType != Chart) {
+			err = exception.New(exception.IllegalSourceType)
+			bk.FailedResult("分页查询收藏列表失败", result)
+			return
+		}
+		userInfo := bk.Data["user"].(user.User)
+		var sourceIds []int
+		pageRes.Total, sourceIds, err = user.GetTotalBookMarkPageBySourceType(userInfo.Id, sourceType)
+		if err != nil {
+			logger.Error("获取收藏列表失败%v", err)
+			err = exception.NewWithException(exception.GetBookMarkListFailed, err.Error())
+			bk.FailedResult("分页查询收藏列表失败", result)
+			return
+		}
+		//隐藏品种信息未设置风险等级的报告
+		if sourceType == Report {
+			pageRes.Total, sourceIds, err = report.FilterReportIds(sourceIds)
+		}
+		if pageRes.Total == 0 {
+			bookMarks := new(page.PageResult)
+			bookMarks.Data = []interface{}{}
+			bookMarks.Page = pageRes
+			bk.SuccessResult("分页查询收藏列表成功", bookMarks, result)
+			return
+		}
+		pageRes.Total = bk.PageInfo.Total
+		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
+		switch sourceType {
+		case Report:
+			var list []userService.BookMarkReport
+			list, err = getReportList(bk.PageInfo, userInfo.Id, sourceIds)
+			if err != nil {
+				err = exception.NewWithException(exception.GetBookMarkListFailed, err.Error())
+				bk.FailedResult("分页查询收藏列表失败", result)
+				return
+			}
+			bookMarks := new(page.PageResult)
+			bookMarks.Data = list
+			bookMarks.Page = pageRes
+			bk.SuccessResult("分页查询收藏列表成功", bookMarks, result)
+			return
+		case Chart:
+			var list []userService.BookMarkChart
+			list, err = getChartList(bk.PageInfo, userInfo.Id)
+			if err != nil {
+				err = exception.NewWithException(exception.GetBookMarkListFailed, err.Error())
+				bk.FailedResult("分页查询收藏列表失败", result)
+				return
+			}
+			bookMarks := new(page.PageResult)
+			bookMarks.Data = list
+			bookMarks.Page = pageRes
+			bk.SuccessResult("分页查询收藏列表成功", bookMarks, result)
+			return
+		default:
+			err = exception.New(exception.IllegalSourceType)
+			bk.FailedResult("分页查询收藏列表失败", result)
+			return
+		}
+	})
+}
+
+// ChartList 获取收藏列表
+// @Description 获取收藏列表
+// @Success 200 {object}
+// @router /bookmark/chartList [get]
+func (bk *BookMarkController) ChartList(key string) {
+	controllers.Wrap(&bk.BaseController, func() (result *controllers.WrapData, err error) {
+		result = bk.InitWrapData("分页查询收藏列表失败")
+		userInfo := bk.Data["user"].(user.User)
+		if key == "" {
+			var list []userService.BookMarkChart
+			list, err = getAllChartList(userInfo.Id)
+			if err != nil {
+				err = exception.NewWithException(exception.GetBookMarkListFailed, err.Error())
+				bk.FailedResult("分页查询收藏列表失败", result)
+				return
+			}
+			//bookMarks := new(page.PageResult)
+			//bookMarks.Data = list
+			bk.SuccessResult("分页查询收藏列表成功", list, result)
+			return
+		} else {
+			if key == "" {
+				err = exception.New(exception.IllegalSearchKeyword)
+				bk.FailedResult("分页搜索收藏列表失败", result)
+				return
+			}
+			var sourceIds []int
+			_, sourceIds, err = user.GetTotalBookMarkPageBySourceType(userInfo.Id, Chart)
+			var bookMarkList []userService.BookMarkInterface
+			chartList, chartErr := chartService.SearchAllChartList(key, sourceIds)
+			if chartErr != nil {
+				logger.Error("搜索研报列表失败%v", err)
+				err = exception.NewWithException(exception.GetBookMarkListFailed, chartErr.Error())
+				bk.FailedResult("分页搜索收藏列表失败", result)
+				return
+			}
+			for _, chart := range chartList {
+				bookMarkList = append(bookMarkList, ConvertToBookMarkChart(chart))
+			}
+			//bookMarkList, err = user.SearchBookMark(key, sourceType, sourceIds, bk.PageInfo, userInfo.Id)
+			bk.SuccessResult("分页搜索收藏列表成功", bookMarkList, result)
+			return
+		}
+	})
+}
+
+func getReportList(info page.PageInfo, templateUserId int, sourceIds []int) (list []userService.BookMarkReport, err error) {
+	sourceIds, err = userService.GetBookMarkPageRangeBySourceType(templateUserId, info, Report, sourceIds)
+	if err != nil {
+		return
+	}
+	userProfile, userErr := user.GetUserProfile(templateUserId)
+	if userErr != nil {
+		if errors.Is(userErr, gorm.ErrRecordNotFound) {
+			err = exception.New(exception.TemplateUserNotFound)
+		} else {
+			err = exception.New(exception.TemplateUserFoundFailed)
+		}
+		logger.Error("获取临时客户信息失败:%v", err)
+		return
+	}
+	var mappingRiskLevel, userRiskStatus string
+	userRiskStatus = userProfile.RiskLevelStatus
+	if userProfile.RiskLevel != "" {
+		var mapping permissionService.CustomerProductRiskMappingDTO
+		mapping, err = permissionService.GetRiskMappingByCustomerRiskLevel(userProfile.RiskLevel)
+		if err != nil {
+			logger.Error("查询产品风险等级映射失败:%v", err)
+			return
+		}
+		mappingRiskLevel = mapping.ProductRiskLevel
+	}
+	reports, err := report.GetReportListById(templateUserId, sourceIds, mappingRiskLevel, userRiskStatus)
+	if err != nil {
+		return nil, err
+	}
+	list = make([]userService.BookMarkReport, len(sourceIds))
+	// 并发获取数据
+	for index, sourceId := range sourceIds {
+		for _, reportDTO := range reports {
+			if reportDTO.ReportID == sourceId {
+				reportInfo := ConvertToBookMarkReport(reportDTO)
+				list[index] = reportInfo
+			}
+		}
+	}
+	return
+}
+
+func getChartList(info page.PageInfo, templateUserId int) (list []userService.BookMarkChart, err error) {
+	sourceIds, err := userService.GetBookMarkPageBySourceType(templateUserId, info, Chart)
+	if err != nil {
+		return
+	}
+	// 创建一个切片来存储结果,长度与 sourceIds 相同
+	list = make([]userService.BookMarkChart, len(sourceIds))
+	// 使用 WaitGroup 来等待所有 goroutine 完成
+	var wg sync.WaitGroup
+	wg.Add(len(sourceIds))
+	// 使用 Mutex 来保护对 list 的写操作
+	var mu sync.Mutex
+	// 并发获取数据
+	for index, sourceId := range sourceIds {
+		go func(index int, id int) {
+			defer wg.Done()
+			var data chartService.ChartInfo
+			data, err = chartService.GetChartById(id)
+			chartInfo := ConvertToBookMarkChart(data)
+			if err != nil {
+				logger.Error("获取数据失败: %v", err)
+			}
+			// 使用 Mutex 保护对 list 的写操作
+			mu.Lock()
+			list[index] = chartInfo
+			mu.Unlock()
+		}(index, sourceId)
+
+	}
+	// 等待所有 goroutine 完成
+	wg.Wait()
+	return
+}
+
+func getAllChartList(templateUserId int) (list []userService.BookMarkChart, err error) {
+	sourceIds, err := userService.GetBookMarkListBySourceType(templateUserId, Chart)
+	if err != nil {
+		return
+	}
+	// 创建一个切片来存储结果,长度与 sourceIds 相同
+	list = make([]userService.BookMarkChart, len(sourceIds))
+	// 使用 WaitGroup 来等待所有 goroutine 完成
+	var wg sync.WaitGroup
+	wg.Add(len(sourceIds))
+	// 使用 Mutex 来保护对 list 的写操作
+	var mu sync.Mutex
+	// 并发获取数据
+	for index, sourceId := range sourceIds {
+		go func(index int, id int) {
+			defer wg.Done()
+			var data chartService.ChartInfo
+			data, err = chartService.GetChartById(id)
+			chartInfo := ConvertToBookMarkChart(data)
+			if err != nil {
+				logger.Error("获取数据失败: %v", err)
+			}
+			// 使用 Mutex 保护对 list 的写操作
+			mu.Lock()
+			list[index] = chartInfo
+			mu.Unlock()
+		}(index, sourceId)
+
+	}
+	// 等待所有 goroutine 完成
+	wg.Wait()
+	return
+}

+ 2 - 0
domian/financial_analyst/financial_analyst_service.go

@@ -14,6 +14,7 @@ type FinancialAnalystDTO struct {
 	InvestmentCertificate   string
 	ProfessionalCertificate string
 	HeadImgUrl              string
+	HeadOriginImgUrl        string
 	Introduction            string
 	Status                  bool
 	Deleted                 bool
@@ -77,6 +78,7 @@ func convertToBaseDTO(financialAnalyst financialAnalystDao.CrmFinancialAnalyst)
 		Id:                      financialAnalyst.Id,
 		ETAId:                   financialAnalyst.ETAId,
 		HeadImgUrl:              financialAnalyst.HeadImgURL,
+		HeadOriginImgUrl:        financialAnalyst.HeadOriginImgUrl,
 		Name:                    financialAnalyst.Name,
 		Introduction:            financialAnalyst.Introduction,
 		ProfessionalCertificate: financialAnalyst.ProfessionalCertificate,

+ 155 - 0
domian/media/chart_service.go

@@ -0,0 +1,155 @@
+package media
+
+import (
+	"encoding/json"
+	"eta/eta_mini_ht_api/common/component/es"
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"strconv"
+)
+
+const (
+	ChartESColumn      = "chartName"
+	ChartESRangeColumn = "chartInfoId"
+	ChartEsColumn      = "chartName"
+)
+
+//	func SearchMediaList(_ string, key string, mediaIds []int, from int, size int, max int64) (medias []MediaDTO, err error) {
+//		//同步es
+//		var docIds []string
+//		for _, id := range mediaIds {
+//			docIds = append(docIds, strconv.Itoa(id))
+//		}
+//		sorts := append(sortField, "publishedTime:desc")
+//		request := matchRangeWithDocIds(key, from, size, max, sorts, docIds)
+//		re, err := elastic().Search(request)
+//		if err != nil {
+//			logger.Error("es搜索失败:%v", err)
+//		}
+//		hits := elastic().GetSource(re.Hits)
+//		for _, hit := range hits {
+//			var content map[string][]string
+//			err = json.Unmarshal(hit.Highlight, &content)
+//			media := MediaDTO{}
+//			err = json.Unmarshal(hit.Source, &media)
+//			if err != nil {
+//				logger.Error("解析媒体数据失败:%v", err)
+//				continue
+//			}
+//			media.Highlight = content[ESColumn]
+//			media.PublishedTime = media.PublishedTime[:10]
+//			media.MediaTitle = media.Highlight[0]
+//			medias = append(medias, media)
+//		}
+//		return
+//	}
+type EsChartInfo struct {
+	ChartInfoId int    `json:"chartInfoId"`
+	ChartName   string `json:"chartName"`
+	ChartImage  string `json:"chartImage"`
+	UniqueCode  string `json:"uniqueCode"`
+}
+
+// 同步es
+func AddChartToEs(esChart EsChartInfo) bool {
+	return elastic().InsertOrUpdate(htConfig.GetChartIndex(), esChart.ChartInfoId, esChart)
+}
+func matchRangeWithDocIdsNoLimit(key string, from int, to int, sorts []string, docIds []string) (request *es.ESQueryRequest) {
+	req := new(es.ESQueryRequest)
+	return req.CreateESQueryRequest(htConfig.GetChartIndex(), ChartEsColumn, key, from, to, sorts, es.RangeByConditionWithDocIdsNoLimit).WithDocs(docIds)
+}
+func matchRangeWithDocIdsNoLimitByScore(key string, from int, to int, sorts []string, docIds []string, score float64) (request *es.ESQueryRequest) {
+	req := new(es.ESQueryRequest)
+	return req.CreateESQueryRequest(htConfig.GetChartIndex(), ChartEsColumn, key, from, to, sorts, es.RangeByConditionWithDocIdsNoLimitByScore).WithScore(score).WithDocs(docIds)
+}
+func SearchChartList(key string, ids []int, from int, size int) (charts []EsChartInfo, err error) {
+	//同步es
+	var docIds []string
+	for _, id := range ids {
+		docIds = append(docIds, strconv.Itoa(id))
+	}
+	sorts := append(sortField)
+	request := matchRangeWithDocIdsNoLimit(key, from, size, sorts, docIds)
+	re, err := elastic().Search(request)
+	if err != nil {
+		logger.Error("es搜索异常:%v", err)
+	}
+	hits := elastic().GetSource(re.Hits)
+	if len(hits) == 0 {
+		return
+	}
+	for _, hit := range hits {
+		var content map[string][]string
+		err = json.Unmarshal(hit.Highlight, &content)
+		chart := EsChartInfo{}
+		err = json.Unmarshal(hit.Source, &chart)
+		if err != nil {
+			logger.Error("解析研报数据失败:%v", err)
+			continue
+		}
+		charts = append(charts, chart)
+	}
+	return
+}
+
+func SearchAllChartList(key string, ids []int) (charts []EsChartInfo, err error) {
+	//同步es
+	var docIds []string
+	for _, id := range ids {
+		docIds = append(docIds, strconv.Itoa(id))
+	}
+	sorts := append(sortField)
+	//兜底500, 避免es查询超时
+	request := matchRangeWithDocIdsNoLimitByScore(key, 0, 5000, sorts, docIds, 0.5)
+	re, err := elastic().Search(request)
+	if err != nil {
+		logger.Error("es搜索异常:%v", err)
+	}
+	hits := elastic().GetSource(re.Hits)
+	if len(hits) == 0 {
+		return
+	}
+	for _, hit := range hits {
+		var content map[string][]string
+		err = json.Unmarshal(hit.Highlight, &content)
+		chart := EsChartInfo{}
+		err = json.Unmarshal(hit.Source, &chart)
+		if err != nil {
+			logger.Error("解析研报数据失败:%v", err)
+			continue
+		}
+		charts = append(charts, chart)
+	}
+	return
+}
+func UpdateChartImage(image string, id int) bool {
+	doc, err := elastic().Get(htConfig.GetChartIndex(), id)
+	if err != nil {
+		return false
+	}
+	var chartInfo EsChartInfo
+	err = json.Unmarshal(doc.Source, &chartInfo)
+	if err != nil {
+		logger.Error("es获取图表数据失败:%v", err)
+		return false
+	}
+	if chartInfo.ChartInfoId == 0 {
+		logger.Error("es获取图表数据失败,chartInfoId:%d", id)
+		return false
+	}
+	chartInfo.ChartImage = image
+	return elastic().Update(htConfig.GetChartIndex(), chartInfo.ChartInfoId, chartInfo)
+}
+
+func GetChartById(id int) (chartInfo EsChartInfo, err error) {
+	doc, err := elastic().Get(htConfig.GetChartIndex(), id)
+	if err != nil {
+		logger.Error("es获取图表数据失败:%v", err)
+		return
+	}
+	err = json.Unmarshal(doc.Source, &chartInfo)
+	if err != nil {
+		logger.Error("序列化json数据失败,获取图表数据失败:%v", err)
+		return
+	}
+	return
+}

+ 1 - 0
domian/report/eta_report_service.go

@@ -78,6 +78,7 @@ func GetETAReport(id int) (detail ETAReportDTO, err error) {
 	report, err := eta.GetETAReportById(id)
 	if err != nil {
 		logger.Error("获取ETA研报信息失败:%v", err)
+		return
 	}
 	var wg sync.WaitGroup
 	detail = convertToETAReportDTO(report)

+ 39 - 5
domian/report/report_service.go

@@ -95,6 +95,7 @@ type ReportDTO struct {
 	IsPackage       bool            `json:"isPackage"`
 	Score           float64         `json:"score"`
 	Show            bool            `json:"-"`
+	IsCollect       bool            `json:"isCollected"`
 }
 
 type Detail struct {
@@ -341,7 +342,12 @@ func SearchReportList(key string, ids []int, from int, size int, max int64) (rep
 		docIds = append(docIds, strconv.Itoa(id))
 	}
 	sorts := append(sortField, "publishedTime:desc")
-	request := matchRangeByDocId(key, from, size, max, sorts, docIds)
+	var request *es.ESQueryRequest
+	if max == -1 {
+		request = matchRangeWithDocIds(key, from, size, sorts, docIds)
+	} else {
+		request = matchRangeByDocId(key, from, size, max, sorts, docIds)
+	}
 	re, err := elastic().Search(request)
 	if err != nil {
 		logger.Error("es搜索异常:%v", err)
@@ -620,7 +626,7 @@ func syncESAndSendMessage(reports []reportDao.Report) (err error) {
 			if !success {
 				logger.Error("更新es失败,reportId::%d,err:%v", report.ID, err)
 			}
-			if report.Status == reportDao.StatusUnPublish {
+			if report.Status == reportDao.StatusUnPublish || report.Status == reportDao.StatusDeleted {
 				//隐藏热度搜索
 				err = userDao.HiddenFlows(report.ID, message.ReportSourceType)
 				if err != nil {
@@ -659,8 +665,9 @@ func syncESAndSendMessage(reports []reportDao.Report) (err error) {
 	//生产meta信息
 	logger.Info("生成推送META信息")
 	for _, report := range reports {
-		if report.Status == reportDao.StatusUnPublish {
-			logger.Info("报告取消发布,不需要生成推送消息")
+		if report.Status == reportDao.StatusUnPublish || report.Status == reportDao.StatusDeleted {
+			logger.Info("报告取消发布或删除" +
+				",不需要生成推送消息")
 			continue
 		}
 		var From string
@@ -786,7 +793,10 @@ func InitHTReportList(list []ht.HTReport) (noRecord bool, err error) {
 }
 
 func htStatus(status int, isDelete int) reportDao.ReportStatus {
-	if isDelete == 1 || status != ht.Publish {
+	if isDelete == 1 {
+		return reportDao.StatusDeleted
+	}
+	if status != ht.Publish {
 		return reportDao.StatusUnPublish
 	}
 	return reportDao.StatusPublish
@@ -1099,6 +1109,11 @@ func matchRangeByDocId(key string, from int, to int, max int64, sorts []string,
 	req := new(es.ESQueryRequest)
 	return req.CreateESQueryRequest(htConfig.GetReportIndex(), ESColumn, key, from, to, sorts, es.RangeByConditionWithDocIds).Range(0, max, ESRangeColumn).ByCondition("status", "PUBLISH").WithDocs(docIds)
 }
+func matchRangeWithDocIds(key string, from int, to int, sorts []string, docIds []string) (request *es.ESQueryRequest) {
+	req := new(es.ESQueryRequest)
+	return req.CreateESQueryRequest(htConfig.GetReportIndex(), ESColumn, key, from, to, sorts, es.RangeByConditionWithDocIdsNoLimit).ByCondition("status", "PUBLISH").WithDocs(docIds)
+}
+
 func CountByDocId(key string, sorts []string, docIds []string) (request *es.ESQueryRequest) {
 	req := new(es.ESQueryRequest)
 	return req.CreateESQueryRequest(htConfig.GetReportIndex(), ESColumn, key, 0, 1, sorts, es.CountWithDocIds).WithDocs(docIds)
@@ -1150,3 +1165,22 @@ type ProductSearchDTO struct {
 func CountPermissionWeight(ids []int) (list []configDao.PermissionWeight, err error) {
 	return reportDao.CountPermissionWeight(ids)
 }
+
+func FilterReportIds(sourceIds []int) (total int64, reportIds []int, err error) {
+	return reportDao.FilterReportIds(sourceIds)
+}
+
+func GetReportListById(reportIds []int) (reportList []ReportDTO, err error) {
+	reports, err := reportDao.GetReportListById(reportIds)
+	if err != nil {
+		return
+	}
+	for _, report := range reports {
+		reportList = append(reportList, convertReportDTO(report, false))
+	}
+	return
+}
+
+func DeleteReport(reportId int) (err error) {
+	return reportDao.DeleteReport(reportId)
+}

+ 131 - 0
domian/user/bookmark_service.go

@@ -0,0 +1,131 @@
+package user
+
+import (
+	"errors"
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/utils/page"
+	userDao "eta/eta_mini_ht_api/models/user"
+	"gorm.io/gorm"
+	"time"
+)
+
+var (
+	sourceTransMap = map[string]userDao.SourceType{
+		"chart":  userDao.Chart,
+		"report": userDao.Report,
+	}
+)
+
+type BookMarkDTO struct {
+	ID         int
+	UserID     int
+	SourceID   int
+	SourceType string
+	Status     string
+	MarkedTime string
+}
+
+func BookMark(templateUserId int, sourceId int, sourceType string) (err error) {
+	sourceTypeItem := sourceTransMap[sourceType]
+	if sourceTypeItem == "" {
+		logger.Error("非法的资源类型")
+		err = errors.New("非法的资源类型")
+		return
+	}
+	return userDao.BookMark(templateUserId, sourceId, sourceTypeItem)
+}
+
+func UnBookMark(templateUserId int, sourceId int, sourceType string) (err error) {
+	sourceTypeItem := sourceTransMap[sourceType]
+	if sourceTypeItem == "" {
+		logger.Error("非法的资源类型")
+		err = errors.New("非法的资源类型")
+		return
+	}
+	return userDao.UnBookMark(templateUserId, sourceId, sourceTypeItem)
+}
+
+func CheckBookMarkStatus(templateUserId int, sourceId int, sourceType string) (status string, err error) {
+	sourceTypeItem := sourceTransMap[sourceType]
+	if sourceTypeItem == "" {
+		logger.Error("非法的资源类型")
+		err = errors.New("非法的资源类型")
+		return
+	}
+	bookmark, err := userDao.CheckBookMarkStatus(templateUserId, sourceId, sourceTypeItem)
+	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+		logger.Error("获取用户收藏状态失败:%v", err)
+		return
+	}
+	if errors.Is(err, gorm.ErrRecordNotFound) {
+		status = string(userDao.Unmark)
+		return
+	}
+	status = string(bookmark.Status)
+	return
+}
+
+func GetTotalBookMarkPageBySourceType(userId int, sourceType string) (total int64, sourceIds []int, err error) {
+	sourceTypeItem := sourceTransMap[sourceType]
+	if sourceTypeItem == "" {
+		logger.Error("非法的资源类型")
+		err = errors.New("非法的资源类型")
+		return
+	}
+	return userDao.GetTotalBookMarkPageBySourceType(userId, sourceTypeItem)
+}
+
+func GetBookMarkPageBySourceType(templateUserId int, sourceType string, info page.PageInfo) (sourceIds []int, err error) {
+	sourceTypeItem := sourceTransMap[sourceType]
+	if sourceTypeItem == "" {
+		logger.Error("非法的资源类型")
+		err = errors.New("非法的资源类型")
+		return
+	}
+	offset := page.StartIndex(info.Current, info.PageSize)
+	return userDao.GetBookMarkPageBySourceType(templateUserId, sourceTypeItem, offset, info.PageSize)
+}
+func GetBookMarkListBySourceType(templateUserId int, sourceType string) (sourceIds []int, err error) {
+	sourceTypeItem := sourceTransMap[sourceType]
+	if sourceTypeItem == "" {
+		logger.Error("非法的资源类型")
+		err = errors.New("非法的资源类型")
+		return
+	}
+	return userDao.GetBookMarkListBySourceType(templateUserId, sourceTypeItem)
+}
+func GetBookMarkPageRangeBySourceType(templateUserId int, sourceType string, info page.PageInfo, sourceIds []int) (filterSourceIds []int, err error) {
+	sourceTypeItem := sourceTransMap[sourceType]
+	if sourceTypeItem == "" {
+		logger.Error("非法的资源类型")
+		err = errors.New("非法的资源类型")
+		return
+	}
+	offset := page.StartIndex(info.Current, info.PageSize)
+	return userDao.GetBookMarkPageRangeBySourceType(templateUserId, sourceTypeItem, offset, info.PageSize, sourceIds)
+}
+
+func GetBookMarkedBySource(sourceId int, templateUserId int, sourceType string) (bookMarkDTO BookMarkDTO, err error) {
+	sourceTypeItem := sourceTransMap[sourceType]
+	if sourceTypeItem == "" {
+		logger.Error("非法的资源类型")
+		err = errors.New("非法的资源类型")
+		return
+	}
+	bookMark, err := userDao.GetBookMarkedBySource(sourceId, templateUserId, sourceTypeItem)
+	if err != nil {
+		logger.Error("获取用户收藏记录失败:%v", err)
+		return
+	}
+	bookMarkDTO = convertUserBookmark(bookMark)
+	return
+}
+func convertUserBookmark(bookmark userDao.UserBookmark) (bookMark BookMarkDTO) {
+	bookMark.ID = bookmark.ID
+	bookMark.UserID = bookmark.UserID
+	bookMark.SourceID = bookmark.SourceID
+	bookMark.SourceType = string(bookmark.SourceType)
+	bookMark.Status = string(bookmark.Status)
+	bookMark.MarkedTime = bookmark.MarkedTime.Format(time.DateTime)
+	return
+}

+ 6 - 0
middleware/auth_middleware.go

@@ -61,6 +61,7 @@ var publicRoutes = []string{
 	"/user/bind_gzh",
 	"/user/wx/notify",
 	"/webhook/*",
+	"/chart/updateChartImage",
 }
 var privateRoutes = []string{
 	"/user/profile",
@@ -69,6 +70,11 @@ var privateRoutes = []string{
 	"/user/followingAnalystList",
 	"/user/readMessages",
 	"/user/readMessage",
+	"/user/bookMark",
+	"/user/unBookMark",
+	"/user/checkBookMark",
+	"/user/bookMarkList",
+	"/user/bookMarkSearch",
 	"/user/feedback",
 	"/webhook/*",
 	"/user/checkFollowStatus",

+ 15 - 1
models/eta/eta_report.go

@@ -1,10 +1,12 @@
 package eta
 
 import (
+	"errors"
 	"eta/eta_mini_ht_api/common/component/cache"
 	logger "eta/eta_mini_ht_api/common/component/log"
 	"eta/eta_mini_ht_api/common/utils/redis"
 	"eta/eta_mini_ht_api/models"
+	"gorm.io/gorm"
 	"strings"
 	"time"
 )
@@ -99,8 +101,20 @@ func GetUpdateETAReports() (filterList []ETAReport, err error) {
 }
 
 func GetETAReportById(id int) (report ETAReport, err error) {
-	err = models.ETA().Table("report").Select(detailColumn).Where("id = ?", id).Where("state =? or state=?", Published, Passed).First(&report).Error
+	//err = models.ETA().Table("report").Select(detailColumn).Where("id = ?", id).Where("state =? or state=?", Published, Passed).First(&report).Error
+	//
+	err = models.ETA().Table("report").Select(detailColumn).Where("id = ?", id).First(&report).Error
 	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			logger.Error("查询eta报告失败,报告已删除,报告ID:%d", id)
+		} else {
+			logger.Error("查询eta报告失败:%v", err)
+		}
+		return
+	}
+	if report.State != Published && report.State != Passed {
+		logger.Error("eta报告未发布,ID:%d", id)
+		err = errors.New("eta报告未发布")
 		return
 	}
 	return

+ 2 - 1
models/financial_analyst/financial_analyst.go

@@ -14,7 +14,7 @@ type AnalystStatus string
 const (
 	AnalystStatusEnabled  AnalystStatus = "enabled"
 	AnalystStatusDisabled AnalystStatus = "disabled"
-	columns                             = "id,eta_id,name,head_img_url,introduction,position,investment_certificate,professional_certificate"
+	columns                             = "id,eta_id,name,head_img_url,head_origin_img_url,introduction,position,investment_certificate,professional_certificate"
 )
 
 type CrmFinancialAnalyst struct {
@@ -23,6 +23,7 @@ type CrmFinancialAnalyst struct {
 	HTId                    int           `gorm:"column:ht_id"`
 	Name                    string        `gorm:"column:name"`
 	HeadImgURL              string        `gorm:"column:head_img_url"`
+	HeadOriginImgUrl        string        `gorm:"column:head_origin_img_url"`
 	Position                string        `gorm:"column:position"`
 	InvestmentCertificate   string        `gorm:"column:investment_certificate"`
 	ProfessionalCertificate string        `gorm:"column:professional_certificate"`

+ 43 - 4
models/report/report.go

@@ -24,10 +24,10 @@ const (
 	SourceHT        ReportSource = "HT"
 	StatusPublish                = "PUBLISH"
 	StatusUnPublish ReportStatus = "UNPUBLISH"
-
-	MaxBatchNum   = 1000
-	CommonColumns = "id,org_id,author,abstract,title,source,cover_src,published_time,status,plate_name,classify_id"
-	taskColumns   = "id,author,published_time,status,plate_name"
+	StatusDeleted   ReportStatus = "DELETED"
+	MaxBatchNum                  = 1000
+	CommonColumns                = "id,org_id,author,abstract,title,source,cover_src,published_time,status,plate_name,classify_id"
+	taskColumns                  = "id,author,published_time,status,plate_name"
 )
 
 type Report struct {
@@ -417,3 +417,42 @@ func GetHiddenReportIds(classifyIds []int, plateNames []string) (reportIds []int
 	err = exc.Scan(&reportIds).Error
 	return
 }
+
+func FilterReportIds(ids []int) (total int64, reportIds []int, err error) {
+	db := models.Main()
+	etaSubQuery := `
+		select a.classify_id 
+		from (
+			select classify_id, IFNULL( GROUP_CONCAT(permissions.risk_level SEPARATOR ','),'') as risks 
+			from permission_classify_mapping 
+			left join permissions on permissions.permission_id = permission_classify_mapping.permission_id 
+			group by classify_id
+		) a  
+		where a.risks = ''
+	`
+	htSubQuery := `
+		select a.name from (SELECT name,IFNULL(risk_level,'') as risk_level  FROM permissions) a  WHERE a.risk_level =''
+	`
+	err = db.Model(&Report{}).Select("id").
+		Where("id in ?", ids).
+		Where("(classify_id not in (?) and source=?) or (plate_name not in (?) and source=?)", gorm.Expr(etaSubQuery), SourceETA, gorm.Expr(htSubQuery), SourceHT).
+		Where("Status = ?", StatusPublish).
+		Scan(&reportIds).Error
+	if err != nil {
+		logger.Error("查询过滤后的报告失败: %v", err)
+	}
+	total = int64(len(reportIds))
+	return
+}
+
+func GetReportListById(ids []int) (reports []Report, err error) {
+	db := models.Main()
+	err = db.Select(CommonColumns).
+		Where("id in ?", ids).Find(&reports).Error
+	return
+}
+
+func DeleteReport(id int) error {
+	db := models.Main()
+	return db.Model(&Report{}).Where("id=?", id).Update("status", StatusDeleted).Error
+}

+ 90 - 0
models/user/user_bookmark.go

@@ -0,0 +1,90 @@
+package user
+
+import (
+	"eta/eta_mini_ht_api/models"
+	"gorm.io/gorm/clause"
+	"time"
+)
+
+type Status string
+type SourceType string
+
+const (
+	Marked Status     = "marked"
+	Unmark Status     = "unmark"
+	Report SourceType = "report"
+	Chart  SourceType = "chart"
+)
+
+type UserBookmark struct {
+	ID          int        `gorm:"primaryKey;column:id;type:int(11);not null;comment:主键"`
+	UserID      int        `gorm:"column:user_id;type:int(11);not null;comment:用户Id"`
+	SourceID    int        `gorm:"column:source_id;type:int(11);not null;comment:资源Id"`
+	SourceType  SourceType `gorm:"column:source_type;type:enum('report','chart');not null;comment:资源类型:report-研报 chart-图表"`
+	Status      Status     `gorm:"column:status;type:enum('marked','unmark');not null;comment:是否收藏 :marked-收藏 ,unmark-取消收藏"`
+	MarkedTime  time.Time  `gorm:"column:marked_time;type:datetime;comment:收藏时间"`
+	CreatedTime time.Time  `gorm:"column:created_time;type:datetime;comment:创建时间"`
+	UpdatedTime time.Time  `gorm:"column:updated_time;type:datetime;default:null;onUpdate:CURRENT_TIMESTAMP;comment:更新时间"`
+}
+
+func (UserBookmark) TableName() string {
+	return "user_bookmarks"
+}
+
+func BookMark(templateUserId int, sourceId int, sourceType SourceType) (err error) {
+	db := models.Main()
+	OnConflictFunc := clause.OnConflict{
+		Columns:   []clause.Column{{Name: "source_id"}, {Name: "source_type"}},
+		DoUpdates: clause.AssignmentColumns([]string{"status", "marked_time"}),
+	}
+	err = db.Clauses(OnConflictFunc).Create(&UserBookmark{
+		UserID:      templateUserId,
+		SourceID:    sourceId,
+		SourceType:  sourceType,
+		MarkedTime:  time.Now(),
+		Status:      Marked,
+		CreatedTime: time.Now(),
+	}).Error
+	return
+}
+
+func UnBookMark(templateUserId int, sourceId int, sourceType SourceType) (err error) {
+	db := models.Main()
+	err = db.Model(&UserBookmark{}).Where("user_id = ? AND source_id = ? AND source_type = ?", templateUserId, sourceId, sourceType).Update("status", Unmark).Error
+	return
+}
+
+func CheckBookMarkStatus(templateUserId int, sourceId int, sourceType SourceType) (bookmark UserBookmark, err error) {
+	db := models.Main()
+	err = db.Model(&UserBookmark{}).Select("*").Where("user_id = ? AND source_id = ? AND source_type = ?", templateUserId, sourceId, sourceType).First(&bookmark).Error
+	return
+}
+
+func GetTotalBookMarkPageBySourceType(templateUserId int, sourceType SourceType) (total int64, sourceIds []int, err error) {
+	db := models.Main()
+	err = db.Model(&UserBookmark{}).Select("source_id").Where("user_id = ?  AND source_type = ? and status =?", templateUserId, sourceType, Marked).Order("marked_time DESC").Scan(&sourceIds).Error
+	total = int64(len(sourceIds))
+	return
+}
+
+func GetBookMarkPageBySourceType(templateUserId int, sourceType SourceType, offset int, limit int) (sourceIds []int, err error) {
+	db := models.Main()
+	err = db.Model(&UserBookmark{}).Select("source_id").Where("user_id = ?  AND source_type = ? and status =?", templateUserId, sourceType, Marked).Order("marked_time DESC").Offset(offset).Limit(limit).Scan(&sourceIds).Error
+	return
+}
+func GetBookMarkListBySourceType(templateUserId int, sourceType SourceType) (sourceIds []int, err error) {
+	db := models.Main()
+	err = db.Model(&UserBookmark{}).Select("source_id").Where("user_id = ?  AND source_type = ? and status =?", templateUserId, sourceType, Marked).Order("marked_time DESC").Scan(&sourceIds).Error
+	return
+}
+func GetBookMarkPageRangeBySourceType(templateUserId int, sourceType SourceType, offset int, limit int, sourceIds []int) (filterSourceIds []int, err error) {
+	db := models.Main()
+	err = db.Model(&UserBookmark{}).Select("source_id").Where("user_id = ?  AND source_type = ? and source_id in ? and status =? ", templateUserId, sourceType, sourceIds, Marked).Order("marked_time DESC").Offset(offset).Limit(limit).Scan(&filterSourceIds).Error
+	return
+}
+
+func GetBookMarkedBySource(sourceId int, templateUserId int, sourceType SourceType) (bookMark UserBookmark, err error) {
+	db := models.Main()
+	err = db.Model(&UserBookmark{}).Select("*").Where("user_id = ?  AND source_type = ? and source_id =?", templateUserId, sourceType, sourceId).First(&bookMark).Error
+	return
+}

+ 81 - 0
routers/commentsRouter.go

@@ -7,6 +7,15 @@ import (
 
 func init() {
 
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/chart:ChartController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/chart:ChartController"],
+        beego.ControllerComments{
+            Method: "UpdateChartImage",
+            Router: `/updateChartImage`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/home:HomeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/home:HomeController"],
         beego.ControllerComments{
             Method: "Search",
@@ -413,6 +422,78 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"],
+        beego.ControllerComments{
+            Method: "BookMark",
+            Router: `/bookMark`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"],
+        beego.ControllerComments{
+            Method: "BookMarkList",
+            Router: `/bookMarkList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("sourceType"),
+			),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"],
+        beego.ControllerComments{
+            Method: "BookMarkSearch",
+            Router: `/bookMarkSearch`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("key"),
+			),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"],
+        beego.ControllerComments{
+            Method: "ChartList",
+            Router: `/bookmark/chartList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("key"),
+			),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"],
+        beego.ControllerComments{
+            Method: "CheckBookMark",
+            Router: `/checkBookMark`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"],
+        beego.ControllerComments{
+            Method: "SearchBookMark",
+            Router: `/searchBookMark`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("sourceType"),
+				param.New("key"),
+			),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:BookMarkController"],
+        beego.ControllerComments{
+            Method: "UnBookMark",
+            Router: `/unBookMark`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:UserController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:UserController"],
         beego.ControllerComments{
             Method: "Get",

+ 7 - 0
routers/router.go

@@ -2,6 +2,7 @@ package routers
 
 import (
 	"eta/eta_mini_ht_api/controllers"
+	"eta/eta_mini_ht_api/controllers/chart"
 	"eta/eta_mini_ht_api/controllers/home"
 	"eta/eta_mini_ht_api/controllers/media"
 	"eta/eta_mini_ht_api/controllers/order"
@@ -35,6 +36,7 @@ func init() {
 		web.NSNamespace("/user",
 			web.NSInclude(
 				&user.UserController{},
+				&user.BookMarkController{},
 			),
 		),
 		web.NSNamespace("/home",
@@ -100,6 +102,11 @@ func init() {
 				&payment.PaymentController{},
 			),
 		),
+		web.NSNamespace("/chart",
+			web.NSInclude(
+				&chart.ChartController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 83 - 0
service/media/chart_service.go

@@ -0,0 +1,83 @@
+package media
+
+import (
+	"eta/eta_mini_ht_api/common/component/config"
+	"eta/eta_mini_ht_api/common/component/es"
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/contants"
+	"eta/eta_mini_ht_api/common/utils/page"
+	chartService "eta/eta_mini_ht_api/domian/media"
+)
+
+type ChartInfo struct {
+	ChartInfoId int
+	ChartName   string
+	ChartImage  string
+	UniqueCode  string
+}
+
+const (
+	ESColumn = "chartName"
+)
+
+var (
+	sortField = []string{"_score:desc"}
+	htConfig  = config.GetConfig(contants.HT).(*config.HTBizConfig)
+)
+
+func elastic() *es.ESClient {
+	return es.GetInstance()
+}
+func AddChartToEs(chartInfo ChartInfo) {
+	success := chartService.AddChartToEs(chartService.EsChartInfo{
+		ChartInfoId: chartInfo.ChartInfoId,
+		ChartName:   chartInfo.ChartName,
+		ChartImage:  chartInfo.ChartImage,
+		UniqueCode:  chartInfo.UniqueCode,
+	})
+	if !success {
+		logger.Error("新增图表到ES失败, chartInfoId:%d,uniqueCode:%s", chartInfo.ChartInfoId, chartInfo.UniqueCode)
+	}
+}
+func SearchChartList(key string, ids []int, pageInfo page.PageInfo) (charts []ChartInfo, err error) {
+	offset := page.StartIndex(pageInfo.Current, pageInfo.PageSize)
+	esChart, err := chartService.SearchChartList(key, ids, offset, pageInfo.PageSize)
+	if err != nil {
+		return
+	}
+	for _, chart := range esChart {
+		charts = append(charts, convertChartInfo(chart))
+	}
+	return
+}
+func SearchAllChartList(key string, ids []int) (charts []ChartInfo, err error) {
+	esChart, err := chartService.SearchAllChartList(key, ids)
+	if err != nil {
+		return
+	}
+	for _, chart := range esChart {
+		charts = append(charts, convertChartInfo(chart))
+	}
+	return
+}
+
+func UpdateChartImage(image string, id int) bool {
+	return chartService.UpdateChartImage(image, id)
+}
+
+func GetChartById(chartInfoId int) (info ChartInfo, err error) {
+	chart, err := chartService.GetChartById(chartInfoId)
+	if err != nil {
+		return
+	}
+	info = convertChartInfo(chart)
+	return
+}
+func convertChartInfo(chart chartService.EsChartInfo) ChartInfo {
+	return ChartInfo{
+		ChartInfoId: chart.ChartInfoId,
+		ChartName:   chart.ChartName,
+		ChartImage:  chart.ChartImage,
+		UniqueCode:  chart.UniqueCode,
+	}
+}

+ 55 - 1
service/report/report_service.go

@@ -174,6 +174,16 @@ func GetReportById(reportId int, login bool, userId int) (report *reportService.
 	err = getReportContent(report, login)
 	if err != nil {
 		logger.Error("获取研报失败:%v,研报ID:%d", err, report.ReportID)
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			//逻辑删除系统研报
+			deleteErr := reportService.DeleteReport(report.ReportID)
+			if deleteErr != nil {
+				logger.Error("报告删除失败:%v", deleteErr)
+			}
+			logger.Error("ETA报告删除")
+			err = exception.NewWithException(exception.ReportDeleted, err.Error())
+			return
+		}
 		err = exception.NewWithException(exception.GetReportFailed, err.Error())
 	}
 	return
@@ -195,7 +205,37 @@ func SearchReportList(key string, Ids []int, pageInfo page.PageInfo, isLogin boo
 	}
 	return
 }
-
+func SearchReportBookMark(key string, Ids []int, pageInfo page.PageInfo, isLogin bool, userId int) (list []reportService.ReportDTO, err error) {
+	userProfile, userErr := user.GetUserProfile(userId)
+	if userErr != nil {
+		if errors.Is(userErr, gorm.ErrRecordNotFound) {
+			err = exception.New(exception.TemplateUserNotFound)
+		} else {
+			err = exception.New(exception.TemplateUserFoundFailed)
+		}
+		logger.Error("获取临时客户信息失败:%v", err)
+		return
+	}
+	var mappingRiskLevel, userRiskStatus string
+	userRiskStatus = userProfile.RiskLevelStatus
+	if userProfile.RiskLevel != "" {
+		var mapping permissionService.CustomerProductRiskMappingDTO
+		mapping, err = permissionService.GetRiskMappingByCustomerRiskLevel(userProfile.RiskLevel)
+		if err != nil {
+			logger.Error("查询产品风险等级映射失败:%v", err)
+			return
+		}
+		mappingRiskLevel = mapping.ProductRiskLevel
+	}
+	offset := page.StartIndex(pageInfo.Current, pageInfo.PageSize)
+	var reports []reportService.ReportDTO
+	reports, err = reportService.SearchReportList(key, Ids, offset, pageInfo.PageSize, -1)
+	list, err = dealReportInfo(reports, isLogin, userId, mappingRiskLevel, userRiskStatus)
+	if err != nil {
+		err = exception.New(exception.SearchReportPageFailed)
+	}
+	return
+}
 func SearchReportProduct(key string, docIds []int) (list []reportService.ReportDTO, err error) {
 	list, err = reportService.SearchReportProduct(key, 100, 0, docIds)
 	if err != nil {
@@ -418,6 +458,16 @@ func GetReportPage(pageInfo page.PageInfo, orgIds map[string][]int, discardIds [
 	}
 	return
 }
+
+func GetReportListById(userId int, reportIds []int, mappingRiskLevel, userRiskStatus string) (reports []reportService.ReportDTO, err error) {
+	var list []reportService.ReportDTO
+	list, err = reportService.GetReportListById(reportIds)
+	reports, err = dealReportInfo(list, true, userId, mappingRiskLevel, userRiskStatus)
+	if err != nil {
+		err = exception.New(exception.QueryReportPageFailed)
+	}
+	return
+}
 func GetReportPageByAnalyst(pageInfo page.PageInfo, analyst string, reportIds []int, templateUserId int, mappingRiskLevel, userRiskStatus string) (list []reportService.ReportDTO, err error) {
 	list, err = reportService.GetReportPageByAnalyst(pageInfo, analyst, reportIds)
 	list, err = dealReportInfo(list, true, templateUserId, mappingRiskLevel, userRiskStatus)
@@ -700,3 +750,7 @@ func CountPermissionWeight(ids []int) (permissionMap map[int]int) {
 	}
 	return
 }
+
+func FilterReportIds(reportIds []int) (total int64, filterReportIds []int, err error) {
+	return reportService.FilterReportIds(reportIds)
+}

+ 116 - 0
service/user/user_service.go

@@ -1,13 +1,17 @@
 package user
 
 import (
+	"encoding/json"
 	"errors"
 	logger "eta/eta_mini_ht_api/common/component/log"
 	"eta/eta_mini_ht_api/common/exception"
+	"eta/eta_mini_ht_api/common/utils/page"
 	permissionService "eta/eta_mini_ht_api/domian/config"
 	analystService "eta/eta_mini_ht_api/domian/financial_analyst"
+	reportDomian "eta/eta_mini_ht_api/domian/report"
 	userService "eta/eta_mini_ht_api/domian/user"
 	merchantDao "eta/eta_mini_ht_api/models/merchant"
+	userDao "eta/eta_mini_ht_api/models/user"
 	"eta/eta_mini_ht_api/service/config"
 	"gorm.io/gorm"
 	"sort"
@@ -23,6 +27,9 @@ const (
 	SubscribeExpired = "expired"
 	UnSubscribe      = "unSubscribe"
 	Subscribing      = "Subscribing"
+
+	ReportBookMark = "report"
+	ChartBookMark  = "chart"
 )
 
 type User struct {
@@ -36,6 +43,7 @@ type User struct {
 type AnalystDetail struct {
 	AnalystName             string `json:"analystName"`
 	HeadImgUrl              string `json:"headImgUrl"`
+	HeadOriginImgUrl        string `json:"headOriginImgUrl"`
 	Introduction            string `json:"introduction"`
 	Followed                string `json:"followed"`
 	Position                string `json:"position"`
@@ -237,6 +245,7 @@ func convertToAnalystDetail(dto analystService.FinancialAnalystDTO) AnalystDetai
 	return AnalystDetail{
 		AnalystName:             dto.Name,
 		HeadImgUrl:              dto.HeadImgUrl,
+		HeadOriginImgUrl:        dto.HeadOriginImgUrl,
 		Introduction:            dto.Introduction,
 		Position:                dto.Position,
 		InvestmentCertificate:   dto.InvestmentCertificate,
@@ -485,3 +494,110 @@ func GetUserScribeStatus(productId int, templateUserId int) (subscribe string) {
 		return UnSubscribe
 	}
 }
+
+func BookMark(templateUserId int, sourceId int, sourceType string) error {
+	return userService.BookMark(templateUserId, sourceId, sourceType)
+}
+
+func UnBookMark(templateUserId int, sourceId int, sourceType string) error {
+	return userService.UnBookMark(templateUserId, sourceId, sourceType)
+}
+
+func CheckBookMarkStatus(templateUserId int, sourceId int, sourceType string) (isBookMarked bool, err error) {
+	status, err := userService.CheckBookMarkStatus(templateUserId, sourceId, sourceType)
+	if err != nil {
+		logger.Error("获取收藏状态失败:%v", err)
+		return
+	}
+	isBookMarked = status == string(userDao.Marked)
+	return
+}
+
+func GetTotalBookMarkPageBySourceType(userId int, sourceType string) (total int64, sourceIds []int, err error) {
+	return userService.GetTotalBookMarkPageBySourceType(userId, sourceType)
+}
+
+func GetBookMarkPageBySourceType(userId int, pageInfo page.PageInfo, sourceType string) (sourceIds []int, err error) {
+	return userService.GetBookMarkPageBySourceType(userId, sourceType, pageInfo)
+}
+func GetBookMarkListBySourceType(userId int, sourceType string) (sourceIds []int, err error) {
+	return userService.GetBookMarkListBySourceType(userId, sourceType)
+}
+
+func GetBookMarkPageRangeBySourceType(userId int, pageInfo page.PageInfo, sourceType string, sourceIds []int) (filterSourceIds []int, err error) {
+	return userService.GetBookMarkPageRangeBySourceType(userId, sourceType, pageInfo, sourceIds)
+}
+
+func GetReportBookMarked(sourceId int, templateUserId int) (collected bool, err error) {
+	bookMark, err := userService.GetBookMarkedBySource(sourceId, templateUserId, ReportBookMark)
+	if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+		return
+	}
+	if errors.Is(err, gorm.ErrRecordNotFound) {
+		err = nil
+		return
+	}
+	collected = bookMark.Status == string(userDao.Marked)
+	return
+}
+
+type BookMarkChart struct {
+	ChartName   string `json:"chartName"`
+	ChartImage  string `json:"chartImage"`
+	UniqueCode  string `json:"uniqueCode"`
+	ChartInfoId int    `json:"chartInfoId"`
+}
+type BookMarkInterface interface {
+	GetID() int
+	GetSourceType() string
+}
+type BookMarkReport struct {
+	Type             string                `json:"type"`
+	ReportID         int                   `json:"reportId"`
+	OrgId            int                   `json:"orgId"`
+	Title            string                `json:"title"`
+	Author           string                `json:"author"`
+	AuthorInfo       []reportDomian.Anthor `json:"authorInfo"`
+	Source           string                `json:"source"`
+	Abstract         string                `json:"abstract"`
+	PublishedTime    string                `json:"publishedTime"`
+	RiskLevel        string                `json:"riskLevel"`
+	PlateName        string                `json:"-"`
+	ClassifyId       int                   `json:"-"`
+	SecondPermission map[int]string        `json:"-"`
+	Permissions      map[int]string        `json:"-"`
+	PermissionNames  interface {
+	} `json:"permissionNames"`
+	Highlight       []string        `json:"highlight"`
+	Detail          json.RawMessage `json:"detail"`
+	PdfUrl          string          `json:"pdfUrl"`
+	CoverSrc        int             `json:"coverSrc"`
+	CoverUrl        string          `json:"coverUrl"`
+	Login           bool            `json:"login"`
+	RiskLevelStatus string          `json:"riskLevelStatus"`
+	IsFree          bool            `json:"isFree"`
+	IsSubscribe     bool            `json:"isSubscribe"`
+	SubscribeStatus string          `json:"subscribeStatus"`
+	Price           string          `json:"price"`
+	ProductId       int             `json:"productId"`
+	IsPackage       bool            `json:"isPackage"`
+	Score           float64         `json:"score"`
+	Show            bool            `json:"-"`
+}
+
+func (bk BookMarkReport) GetID() int {
+	return bk.ReportID
+}
+func (bk BookMarkReport) GetSourceType() string {
+	return ReportBookMark
+}
+func (bk BookMarkChart) GetID() int {
+	return bk.ChartInfoId
+}
+func (bk BookMarkChart) GetSourceType() string {
+	return ChartBookMark
+}
+
+// func SearchBookMark(key string, sourceType string, ids []int, pageInfo page.PageInfo, userId int) (list []BookMarkInterface, err error) {
+//
+// }