kobe6258 5 сар өмнө
parent
commit
0a6ae04f56
85 өөрчлөгдсөн 5330 нэмэгдсэн , 1891 устгасан
  1. 8 8
      api/ht_api.go
  2. 88 0
      api/payment_api.go
  3. 8 0
      common/component/cache/redis.go
  4. 20 1
      common/component/config/ht_biz_config.go
  5. 106 2
      common/component/es/es.go
  6. 41 2
      common/exception/exc_enums.go
  7. 58 0
      common/utils/lock/distrubtLock.go
  8. 12 9
      common/utils/page/page_utils.go
  9. 9 0
      common/utils/redis/key_generator.go
  10. 1 0
      common/utils/string/string_utils.go
  11. 77 0
      controllers/home/home_controller.go
  12. 11 0
      controllers/list_contoller.go
  13. 26 9
      controllers/media/media_controller.go
  14. 138 0
      controllers/order/order_controller.go
  15. 69 64
      controllers/order/subscribe_controller.go
  16. 109 2
      controllers/payment/payment_controller.go
  17. 170 1
      controllers/product/product_controller.go
  18. 42 18
      controllers/report/report_controller.go
  19. 67 0
      controllers/user/account_controller.go
  20. 8 4
      controllers/user/analyst_controller.go
  21. 0 43
      controllers/user/order_controller.go
  22. 1 1
      controllers/user/user_controller.go
  23. 130 0
      controllers/web_hook/htfutures_trade_controller.go
  24. 30 0
      domian/config/permission_service.go
  25. 18 12
      domian/financial_analyst/financial_analyst_service.go
  26. 194 64
      domian/media/media_service.go
  27. 127 5
      domian/merchant/merchant_product.go
  28. 132 0
      domian/merchant/user_access_service.go
  29. 2 9
      domian/message/meta_info.go
  30. 222 65
      domian/order/product_order.go
  31. 251 0
      domian/order/trade_order.go
  32. 3 48
      domian/report/eta_report_service.go
  33. 2 82
      domian/report/ht_report_service.go
  34. 0 1
      domian/report/permission_service.go
  35. 315 114
      domian/report/report_service.go
  36. 57 17
      domian/user/user_message_service.go
  37. 41 21
      domian/user/user_source_click_flow_service.go
  38. 21 3
      domian/user/user_subscription_service.go
  39. 10 4
      go.mod
  40. 21 0
      go.sum
  41. 37 0
      main.go
  42. 5 1
      middleware/auth_middleware.go
  43. 4 0
      middleware/webhook_middleware.go
  44. 13 1
      models/config/permission.go
  45. 17 14
      models/financial_analyst/financial_analyst.go
  46. 1 1
      models/image/images_sources.go
  47. 5 4
      models/media/media.go
  48. 11 1
      models/media/media_permission_mapping.go
  49. 10 1
      models/merchant/merchant.go
  50. 0 20
      models/merchant/merchant_package.go
  51. 243 26
      models/merchant/merchant_product.go
  52. 36 0
      models/merchant/user_subscription_access_flow.go
  53. 142 0
      models/merchant/user_subscription_access_list.go
  54. 15 2
      models/message/meta_info.go
  55. 94 38
      models/order/product_order.go
  56. 25 0
      models/order/refund_deal_flow.go
  57. 133 0
      models/order/trade_order.go
  58. 49 28
      models/report/report.go
  59. 1 1
      models/sys/sys_config.go
  60. 1 1
      models/user/template_user.go
  61. 0 35
      models/user/userSubscriptionAccessList.go
  62. 16 12
      models/user/user_analyst_follow_list.go
  63. 31 17
      models/user/user_message.go
  64. 18 16
      models/user/user_source_click_flow.go
  65. 148 42
      routers/commentsRouter.go
  66. 16 2
      routers/router.go
  67. 16 10
      service/analyst/analyst_service.go
  68. 47 0
      service/config/config.go
  69. 12 5
      service/facade/ht_account_service.go
  70. 38 0
      service/facade/ht_trade_service.go
  71. 132 0
      service/home/home_service.go
  72. 170 486
      service/media/media_service.go
  73. 144 16
      service/order/order_service.go
  74. 41 0
      service/payment/payment_service.go
  75. 417 9
      service/product/product_service.go
  76. 68 0
      service/product/subscribe_servcie.go
  77. 301 474
      service/report/report_service.go
  78. 174 7
      service/user/user_service.go
  79. 1 1
      task/eta/author/eta_author_task.go
  80. 1 1
      task/eta/permission/eta_permission_task.go
  81. 4 4
      task/message/notice_task.go
  82. 5 5
      task/order/product_order_close_task.go
  83. 40 0
      task/product/product_expire_task.go
  84. 1 1
      task/report/report_update_task.go
  85. 2 0
      task/task_starter.go

+ 8 - 8
api/ht_account_api.go → api/ht_api.go

@@ -24,22 +24,22 @@ const (
 
 var (
 	htFacadeOnce sync.Once
-
-	htFacade   *HTApi
-	redisUtils = cache.GetInstance()
+	htFacade     *HTApi
 )
 
 type HTApi struct {
 	htConfig *config.HTBizConfig
 	// HTTP请求客户端
-	client *client.HttpClient
+	client     *client.HttpClient
+	redisUtils *cache.RedisCache
 }
 
 func GetInstance() *HTApi {
 	htFacadeOnce.Do(func() {
 		htFacade = &HTApi{
-			htConfig: config.GetConfig(contants.HT).(*config.HTBizConfig),
-			client:   client.DefaultClient()}
+			htConfig:   config.GetConfig(contants.HT).(*config.HTBizConfig),
+			client:     client.DefaultClient(),
+			redisUtils: cache.GetInstance()}
 	})
 	return htFacade
 }
@@ -88,7 +88,7 @@ type EncodeReq struct {
 
 func (f *HTApi) getToken() (token string, err error) {
 	accessToken := redis.GenerateCAPAccessTokenKey()
-	token = redisUtils.GetString(accessToken)
+	token = f.redisUtils.GetString(accessToken)
 	var tokenInfo TokenInfo
 	if token == "" {
 		tokenInfo, err = f.GetAccessToken()
@@ -105,7 +105,7 @@ func (f *HTApi) getToken() (token string, err error) {
 		}
 		duration := expireTime.Sub(time.Now()).Seconds()
 		token = tokenInfo.AccessToken
-		_ = redisUtils.SetString(accessToken, tokenInfo.AccessToken, int(duration)-5)
+		_ = f.redisUtils.SetString(accessToken, tokenInfo.AccessToken, int(duration)-5)
 	}
 	return
 }

+ 88 - 0
api/payment_api.go

@@ -0,0 +1,88 @@
+package api
+
+import (
+	"encoding/json"
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/exception"
+	"io"
+	"sync"
+)
+
+const (
+	queryPaymentUrl  = "/paygateway/query.json"
+	createPaymentUrl = "/paygateway/createpay.json"
+)
+
+var (
+	paymentFacadeOnce sync.Once
+	paymentFacade     *HTPaymentApi
+)
+
+type HTPaymentApi struct {
+	*HTApi
+}
+
+func GetPaymentInstance() *HTPaymentApi {
+	paymentFacadeOnce.Do(func() {
+		paymentFacade = &HTPaymentApi{
+			GetInstance(),
+		}
+	})
+	return paymentFacade
+}
+
+type PaymentOrderRequest struct {
+	AppId           string `json:"appId"`           // 必须 业务系统编号
+	Sign            string `json:"sign"`            // 必须 加密字符串
+	ChannelTypeCode string `json:"channelTypeCode"` // 必须 支付渠道编码(6:华为支付,7:微信小程序)
+	Subject         string `json:"subject"`         // 必须 商品标题
+	Body            string `json:"body"`            // 必须 商品描述
+	TotalFee        string `json:"totalFee"`        // 必须 订单价格
+	BillNo          string `json:"billNo"`          // 必须 商户系统内部的订单号
+	BillTypeCode    string `json:"billTypeCode"`    // 非必须 业务类型code
+	BillType        string `json:"billType"`        // 非必须 业务类型
+	TimeoutExpress  string `json:"timeoutExpress"`  // 必须 订单失效时间(秒)
+	Optional        string `json:"optional"`        // 必须 扩展类参数
+	Mobile          string `json:"mobile"`          // 非必须 手机号
+	IapProductId    string `json:"iapProductId"`    // 非必须 苹果内购产品ID
+}
+type PaymentOrderResponse struct {
+	Success    bool   `json:"success"`    // 必须 接口调用结果
+	ResultCode string `json:"resultCode"` // 必须 结果码
+	ResultDesc string `json:"resultDesc"` // 必须 结果描述
+	ResultView string `json:"resultView"` // 必须 错误结果显示
+	PrePayStr  string `json:"prePayStr"`  // 必须 预支付订单信息
+}
+
+func (f *HTPaymentApi) CreatePaymentOrder(req PaymentOrderRequest) (tradeNo string, err error) {
+	url := f.htConfig.GetPaymentApiUrl() + createPaymentUrl
+	req.AppId = f.htConfig.GetPaymentAppId()
+	req.Sign = f.htConfig.GetPaymentSign()
+	resp, err := f.client.Post(url, req)
+	if err != nil {
+		logger.Error("调用支付创单接口失败:[%v]", err)
+		return
+	}
+	defer func(Body io.ReadCloser) {
+		closeErr := Body.Close()
+		if closeErr != nil {
+			logger.Error("关闭Response失败:%v", closeErr)
+		}
+	}(resp.Body)
+	body, _ := io.ReadAll(resp.Body)
+	var paymentOrderResponse PaymentOrderResponse
+	err = json.Unmarshal(body, &paymentOrderResponse)
+	if err != nil {
+		logger.Warn("[支付接口调用]解析支付创建订单接口应答失败:%v", err)
+		err = exception.New(exception.CreatePaymentOrderFailed)
+		return
+	}
+	if !paymentOrderResponse.Success {
+		logger.Warn("[支付接口调用] 创建订单失败:[code:%v, msg:%v]", paymentOrderResponse.ResultCode, paymentOrderResponse.ResultDesc)
+		err = exception.NewWithException(exception.CreatePaymentOrderFailed, paymentOrderResponse.ResultView)
+		return
+	}
+	logger.Info("[支付接口调用] 创建订单成功:[%v]", paymentOrderResponse.PrePayStr)
+	tradeNo = paymentOrderResponse.PrePayStr
+	return
+}

+ 8 - 0
common/component/cache/redis.go

@@ -35,6 +35,7 @@ func GetInstance() *RedisCache {
 				if err != nil {
 					log.Fatalf("redis 链接失败:%v", err)
 				}
+				logger.Info("redis地址:%v", redisConf.GetHost())
 			} else {
 				logger.Info("Redis configuration is incomplete: Host is missing")
 				return
@@ -101,6 +102,9 @@ func (r *RedisCache) SetString(key string, val string, expired int) error {
 
 // SetStringWithContext  设置一个值
 func (r *RedisCache) SetStringWithContext(ctx context.Context, key string, val string, expired int) error {
+	if expired <= 0 {
+		return r.redisTemplate.Set(ctx, key, val, time.Duration(expired)).Err()
+	}
 	return r.redisTemplate.SetEX(ctx, key, val, time.Duration(expired)*time.Second).Err()
 }
 
@@ -143,3 +147,7 @@ func (r *RedisCache) DoWithContext(ctx context.Context, commandName string, args
 	newArgs = append(newArgs, args...)
 	return r.redisTemplate.Do(ctx, newArgs...).Result()
 }
+
+func (r *RedisCache) RedisClient() redis.UniversalClient {
+	return r.redisTemplate
+}

+ 20 - 1
common/component/config/ht_biz_config.go

@@ -10,11 +10,15 @@ type HTOpts struct {
 	DesCode           string
 	Task              string
 	AccountApiUrl     string
+	PaymentApiUrl     string
 	WebhookPrivateKey string
 	WebhookPublicKey  string
 	AccountInfoUrl    string
 	CapAppId          string
 	CapSecretKey      string
+	PaymentAppId      string
+	PaymentSign       string
+	MerchantId        string
 }
 type HTBizConfig struct {
 	BaseConfig
@@ -55,7 +59,18 @@ func (e *HTBizConfig) GetWebhookPublicKey() string {
 func (e *HTBizConfig) GetAccountApiUrl() string {
 	return e.opts.AccountApiUrl
 }
-
+func (e *HTBizConfig) GetMerchantId() string {
+	return e.opts.MerchantId
+}
+func (e *HTBizConfig) GetPaymentApiUrl() string {
+	return e.opts.PaymentApiUrl
+}
+func (e *HTBizConfig) GetPaymentAppId() string {
+	return e.opts.PaymentAppId
+}
+func (e *HTBizConfig) GetPaymentSign() string {
+	return e.opts.PaymentSign
+}
 func (e *HTBizConfig) GetAppId() string {
 	return e.opts.CapAppId
 }
@@ -76,8 +91,12 @@ func (e *HTBizConfig) InitConfig() {
 		WebhookPrivateKey: e.GetString("webhook.private_key"),
 		WebhookPublicKey:  e.GetString("webhook.public_key"),
 		AccountInfoUrl:    e.GetString("api.account_url"),
+		PaymentApiUrl:     e.GetString("api.payment_url"),
 		CapAppId:          e.GetString("api.app_id"),
 		CapSecretKey:      e.GetString("api.secret_key"),
+		PaymentAppId:      e.GetString("api.payment_app_id"),
+		PaymentSign:       e.GetString("api.payment_secret_key"),
+		MerchantId:        e.GetString("merchant_id"),
 	}
 	e.opts = opts
 }

+ 106 - 2
common/component/es/es.go

@@ -42,6 +42,8 @@ const (
 	RangeByConditionWithDocIds = "range_by_condition_with_doc_ids"
 
 	RangeWithDocIds = "range_with_doc_ids"
+	LimitByScore    = "limit_by_score"
+	HomeSearch      = "home_search"
 )
 
 func GetInstance() *ESClient {
@@ -51,6 +53,7 @@ func GetInstance() *ESClient {
 			logger.Info("初始化es")
 			// 这里可以添加初始化Redis的逻辑
 			esClient = newEs(esConf)
+			logger.Info("es地址:%v", esConf.GetUrl())
 		}
 	})
 	return esClient
@@ -169,6 +172,7 @@ type ESQueryRequest struct {
 	DocIds         []string
 	Max            interface{}
 	Min            interface{}
+	MinScore       float64
 }
 
 func (req *ESQueryRequest) CreateESQueryRequest(index string, column string, key string, from int, size int, sorts []string, searchType SearchType) *ESQueryRequest {
@@ -182,13 +186,25 @@ func (req *ESQueryRequest) CreateESQueryRequest(index string, column string, key
 		Sorts:     sorts,
 	}
 }
-
+func (req *ESQueryRequest) Limit(limit int) *ESQueryRequest {
+	req.Size = limit
+	return req
+}
+func (req *ESQueryRequest) WithScore(score float64) *ESQueryRequest {
+	req.MinScore = score
+	return req
+}
 func (req *ESQueryRequest) Range(from int64, to int64, column string) *ESQueryRequest {
 	req.RangeColumn = column
 	req.Max = to
 	req.Min = from
 	return req
 }
+func (req *ESQueryRequest) Before(max interface{}, column string) *ESQueryRequest {
+	req.RangeColumn = column
+	req.Max = max
+	return req
+}
 func (req *ESQueryRequest) ByCondition(column string, value string) *ESQueryRequest {
 	req.Condition = column
 	req.ConditionValue = value
@@ -399,6 +415,93 @@ func (req *ESQueryRequest) parseJsonQuery() (queryMap map[string]interface{}) {
 			},
 		}
 		return
+	case LimitByScore:
+		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 HomeSearch:
+		queryMap = map[string]interface{}{
+			"query": map[string]interface{}{
+				"bool": map[string]interface{}{
+					"should": []map[string]interface{}{
+						{
+							"bool": map[string]interface{}{
+								"must": []map[string]interface{}{
+									{
+										"match": map[string]interface{}{
+											"title": req.Key,
+										},
+									},
+									{
+										"term": map[string]interface{}{
+											"status": "PUBLISH",
+										},
+									},
+									{
+										"range": map[string]interface{}{
+											"publishedTime": map[string]interface{}{
+												"lte": req.Max,
+											},
+										},
+									},
+								},
+							},
+						},
+						{
+							"bool": map[string]interface{}{
+								"must": []map[string]interface{}{
+									{
+										"match": map[string]interface{}{
+											"mediaName": req.Key,
+										},
+									},
+									{
+										"range": map[string]interface{}{
+											req.RangeColumn: map[string]interface{}{
+												"lte": req.Max,
+											},
+										},
+									},
+								},
+							},
+						},
+					},
+				},
+			},
+			"highlight": map[string]interface{}{
+				"fields": map[string]interface{}{
+					"title":     map[string]interface{}{},
+					"mediaName": map[string]interface{}{},
+				},
+				"pre_tags":  []string{"<span style='color:#0078E8'>"},
+				"post_tags": []string{"</span>"},
+			},
+		}
+		return
 	default:
 		queryMap = map[string]interface{}{}
 		return
@@ -448,8 +551,9 @@ func (es *ESClient) Search(params *ESQueryRequest) (response ESResponse, err err
 	queryMap := params.parseJsonQuery()
 	jsonQuery, _ := json.Marshal(queryMap)
 	logger.Info("查询语句: %s", string(jsonQuery))
+	indexes := strings.Split(params.IndexName, ",")
 	request := esapi.SearchRequest{
-		Index: []string{params.IndexName},
+		Index: indexes,
 		Body:  strings.NewReader(string(jsonQuery)),
 		From:  &params.From,
 		Size:  &params.Size,

+ 41 - 2
common/exception/exc_enums.go

@@ -78,10 +78,16 @@ const (
 	RiskUnMatchError
 	OfficialUserFoundError
 	OfficialUserNotFound
+	IllegalAccountStatus
+	AccountNotOpen
+	IDExpired
 	SubscribeFailed
+	DuplicateSubscribe
 	GenerateOrderNoFailed
 	IllegalOrderNo
 	GetOrderListFailed
+	GetOrderDetailFailed
+	CloseOrderFailed
 )
 
 // WechatErrCode 微信
@@ -121,6 +127,11 @@ const (
 	IllegalProductId
 
 	IllegalProductOrderId
+
+	MerchantInfoNotConfig
+
+	GetProductListFailed
+	IllegalProductType
 )
 const (
 	WebhookErrCode int = iota + 80000
@@ -133,11 +144,23 @@ const (
 	OrderErrorCode int = iota + 90000
 	IllegalOrderStatus
 	OrderPayTimeoutError
+	PayTradeOrderFailed
 )
 
 const (
 	ProductErrorCode int = iota + 100000
 	ProductOffSale
+	ProductNotFound
+	ProductTypeError
+	ProductGetFailed
+)
+const (
+	PaymentErrCode int = iota + 110000
+
+	CreatePaymentOrderFailed
+	PaymentProcessingError
+	PaymentDoneError
+	RefundDealFail
 )
 
 // ErrorMap 用于存储错误码和错误信息的映射
@@ -193,10 +216,14 @@ var ErrorMap = map[int]string{
 	RiskUnMatchError:                   "客户风险测评不匹配",
 	OfficialUserFoundError:             "获取正式用户信息失败",
 	OfficialUserNotFound:               "用户未开户",
+	IllegalAccountStatus:               "非法的开户状态",
 	SubscribeFailed:                    "订阅失败",
+	DuplicateSubscribe:                 "重复订阅",
 	GenerateOrderNoFailed:              "生成订单号",
-	IllegalOrderNo:                     "非法的品订单号",
+	IllegalOrderNo:                     "非法的品订单号",
 	GetOrderListFailed:                 "获取产品订单列表失败",
+	GetOrderDetailFailed:               "获取订单详情失败",
+	CloseOrderFailed:                   "关闭订单失败",
 	//微信
 	WeChatServerError:    "微信服务器发生错误",
 	WechatUserInfoFailed: "获取微信用户信息失败",
@@ -224,6 +251,9 @@ var ErrorMap = map[int]string{
 	ProductInfoError:      "获取商品信息失败",
 	IllegalProductId:      "非法的产品ID",
 	IllegalProductOrderId: "非法的产品订单ID",
+	MerchantInfoNotConfig: "商户信息未配置",
+	GetProductListFailed:  "获取产品列表失败",
+	IllegalProductType:    "非法的产品类型",
 	//webhook
 	SyncRiskError:               "同步风险等级失败",
 	GetCapTokenFailed:           "获取cap token失败",
@@ -232,8 +262,17 @@ var ErrorMap = map[int]string{
 	//order
 	IllegalOrderStatus:   "非法的订单状态",
 	OrderPayTimeoutError: "订单支付超时",
+	PayTradeOrderFailed:  "订单支付失败",
 	//product
-	ProductOffSale: "商品已下架",
+	ProductOffSale:   "商品已下架",
+	ProductTypeError: "非法的产品类型",
+	ProductGetFailed: "获取商品信息失败",
+	ProductNotFound:  "商品不存在",
+	//支付
+	CreatePaymentOrderFailed: "创建支付订单失败",
+	PaymentProcessingError:   "支付订单处理中",
+	PaymentDoneError:         "订单已完成支付",
+	RefundDealFail:           "处理退款应答失败",
 }
 
 func Equals(code int, message string) bool {

+ 58 - 0
common/utils/lock/distrubtLock.go

@@ -0,0 +1,58 @@
+package lock
+
+import (
+	"context"
+	"eta/eta_mini_ht_api/common/component/cache"
+	"fmt"
+	"github.com/go-redis/redis/v8"
+)
+
+const (
+	lockName = "lock:"
+)
+
+var (
+	ctx = context.Background()
+	rdb = cache.GetInstance()
+)
+
+func AcquireLock(key string, expiration int, Holder string) bool {
+	script := redis.NewScript(`local key = KEYS[1]
+			local clientId = ARGV[1]
+			local expiration = tonumber(ARGV[2])
+			if redis.call("EXISTS", key) == 0 then
+				redis.call("SET", key, clientId, "EX", expiration)
+				return 1
+			else
+				return 0
+			end`)
+
+	lockey := fmt.Sprintf("%s%s", lockName, key)
+	result, err := script.Run(ctx, rdb.RedisClient(), []string{lockey}, Holder, expiration).Int()
+	if err != nil {
+		return false
+	}
+	if result == 1 {
+		return true
+	}
+	return false
+}
+
+func ReleaseLock(key string, holder string) bool {
+	script := redis.NewScript(`
+	   if redis.call("get", KEYS[1]) == ARGV[1] then
+	       return redis.call("del", KEYS[1])
+	   else
+	       return 0
+	   end
+	`)
+	lockey := fmt.Sprintf("%s%s", lockName, key)
+	result, err := script.Run(ctx, rdb.RedisClient(), []string{lockey}, holder).Int()
+	if err != nil {
+		return false
+	}
+	if result == 1 {
+		return true
+	}
+	return false
+}

+ 12 - 9
common/utils/page/page_utils.go

@@ -3,21 +3,24 @@ package page
 import "math"
 
 type PageInfo struct {
-	LatestId int64
-	Current  int
-	PageSize int
-	Total    int64
+	LatestId  int64
+	Current   int
+	PageSize  int
+	Total     int64
+	TimeStamp int64
 }
 type PageResult struct {
 	Page Page
 	Data interface{}
 }
 type Page struct {
-	LatestId  int64 `json:"latestId,omitempty"`
-	Current   int   `json:"current"`
-	PageSize  int   `json:"pageSize"`
-	Total     int64 `json:"total"`
-	TotalPage int   `json:"totalPage"`
+	LatestId  int64  `json:"latestId,omitempty"`
+	Current   int    `json:"current"`
+	PageSize  int    `json:"pageSize"`
+	Total     int64  `json:"total"`
+	Range     string `json:"range"`
+	TotalPage int    `json:"totalPage"`
+	TimeStamp int64  `json:"timeStamp"`
 }
 
 func StartIndex(page, pagesize int) int {

+ 9 - 0
common/utils/redis/key_generator.go

@@ -4,12 +4,14 @@ import "fmt"
 
 const (
 	SmsKeyPrefix      = "sms_code:"
+	PaymentKeyPrefix  = "payment:"
 	LoginTokenPrefix  = "login:token:"
 	CAPAccessTokenKey = "cap:access_token"
 )
 
 const (
 	ValidAreaCode = "areaCode:valid:list"
+	MerchantKey   = "merchant:id"
 )
 
 func GenerateSmsKey(mobile string) string {
@@ -26,3 +28,10 @@ func GenerateCAPAccessTokenKey() string {
 func GenerateReportRefreshKey(source string, id int, modifyTime int64) string {
 	return fmt.Sprintf("%s:%d:%d", source, id, modifyTime)
 }
+
+func GeneratePaymentKey(orderNo string) string {
+	return fmt.Sprint(PaymentKeyPrefix, orderNo)
+}
+func GenerateMerchantKey() string {
+	return MerchantKey
+}

+ 1 - 0
common/utils/string/string_utils.go

@@ -26,6 +26,7 @@ func StringToIntSlice(strSlice []string) ([]int, error) {
 	}
 	return intSlice, nil
 }
+
 func StringsToJSON(str string) string {
 	rs := []rune(str)
 	jsons := ""

+ 77 - 0
controllers/home/home_controller.go

@@ -0,0 +1,77 @@
+package home
+
+import (
+	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"
+	homeService "eta/eta_mini_ht_api/service/home"
+	"eta/eta_mini_ht_api/service/user"
+	"time"
+)
+
+type HomeController struct {
+	controllers.ListController
+}
+
+func isLogin(listType string) bool {
+	if listType == "logout" {
+		logger.Info("当前用户未登录,展示部分详情")
+		return false
+	}
+	if listType == "login" {
+		return true
+	}
+	return false
+}
+
+// Search 搜索报告列表
+// @Description 搜索报告列表
+// @Success 200 {object}
+// @router /search [get]
+func (r *HomeController) Search(key string) {
+	controllers.Wrap(&r.BaseController, func() (result *controllers.WrapData, err error) {
+		result = r.InitWrapData("分页搜索列表失败")
+		if key == "" {
+			err = exception.New(exception.SearchKeyEmptyError)
+			r.FailedResult("分页搜索报告列表失败", result)
+			return
+		}
+		detailType := r.Data["detailType"].(string)
+		userInfo := r.Data["user"].(user.User)
+		pageRes := page.Page{
+			Current:  r.PageInfo.Current,
+			PageSize: r.PageInfo.PageSize,
+		}
+
+		if r.PageInfo.TimeStamp == 0 {
+			r.PageInfo.TimeStamp = time.Now().Unix()
+		} else {
+			pageRes.LatestId = r.PageInfo.LatestId
+			pageRes.Total = r.PageInfo.Total
+		}
+		var total int64
+		var list []homeService.HomeSearch
+		list, total, err = homeService.Search(key, r.PageInfo, isLogin(detailType), userInfo.Id)
+		if err != nil {
+			r.FailedResult("分页搜索报告列表失败", result)
+			return
+		}
+		var dataList []interface{}
+		for _, item := range list {
+			switch item.ResultType {
+			case homeService.ReportResultType:
+				dataList = append(dataList, item.Report)
+			case homeService.MediaResultType:
+				dataList = append(dataList, item.Media)
+			}
+		}
+		r.PageInfo.Total = total
+		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
+		reports := new(page.PageResult)
+		reports.Data = dataList
+		reports.Page = pageRes
+		r.SuccessResult("分页搜索报告列表成功", reports, result)
+		return
+	})
+}

+ 11 - 0
controllers/list_contoller.go

@@ -15,6 +15,11 @@ type ListController struct {
 const (
 	video = "video"
 	audio = "audio"
+
+	productVideo   = "video"
+	productAudio   = "audio"
+	productReport  = "report"
+	productPackage = "package"
 )
 
 func (l *ListController) Prepare() {
@@ -60,6 +65,12 @@ func (l *ListController) CheckMediaType(mediaType string) bool {
 	}
 	return false
 }
+func (l *ListController) CheckProductType(productType string) bool {
+	if productType == productReport || productType == productPackage || productType == productVideo || productType == productAudio {
+		return true
+	}
+	return false
+}
 
 //func (l *ListController) Finish() {
 //	l.PageInfo.Reset()

+ 26 - 9
controllers/media/media_controller.go

@@ -8,6 +8,7 @@ import (
 	reportService "eta/eta_mini_ht_api/domian/report"
 	"eta/eta_mini_ht_api/service/media"
 	"eta/eta_mini_ht_api/service/user"
+	"sync"
 )
 
 type MediaController struct {
@@ -45,8 +46,10 @@ func (m *MediaController) Search(mediaType string, key string) {
 		}
 		//获取当前可以被搜索的报告原始ID
 		var mediaIds []int
+		var mappingRiskLevel string
+		var userRiskStatus string
 		//先要限制查询的id范围
-		pageRes.Total, pageRes.LatestId, mediaIds = media.RangeSearch(mediaType, isLogin(detailType), userInfo.Id)
+		pageRes.Total, pageRes.LatestId, mediaIds, mappingRiskLevel, userRiskStatus = media.RangeSearch(mediaType, isLogin(detailType), userInfo.Id)
 		if len(mediaIds) == 0 {
 			reports := new(page.PageResult)
 			reports.Data = []reportService.ReportDTO{}
@@ -63,7 +66,7 @@ func (m *MediaController) Search(mediaType string, key string) {
 			pageRes.Total = m.PageInfo.Total
 		}
 		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
-		list, err := media.SearchMediaList(mediaType, key, mediaIds, m.PageInfo, isLogin(detailType), userInfo.Id)
+		list, err := media.SearchMediaList(mediaType, key, mediaIds, m.PageInfo, isLogin(detailType), userInfo.Id, mappingRiskLevel, userRiskStatus)
 		if err != nil {
 			m.FailedResult("分页搜索媒体列表失败", result)
 			return
@@ -102,7 +105,9 @@ func (m *MediaController) List(mediaType string, permissionIds string) {
 		detailType := m.Data["detailType"].(string)
 		userInfo := m.Data["user"].(user.User)
 		var mediaIds []int
-		pageRes.Total, pageRes.LatestId, mediaIds = media.GetTotalPageCountByPermissionIds(mediaType, permissionIdList, isLogin(detailType), userInfo.Id)
+		var mappingRiskLevel string
+		var userRiskStatus string
+		pageRes.Total, pageRes.LatestId, mediaIds, mappingRiskLevel, userRiskStatus = media.GetTotalPageCountByPermissionIds(mediaType, permissionIdList, isLogin(detailType), userInfo.Id)
 		if err != nil {
 			logger.Error("分页查询媒体列表失败:%v", err)
 			m.FailedResult("分页查询媒体列表失败", result)
@@ -116,7 +121,7 @@ func (m *MediaController) List(mediaType string, permissionIds string) {
 			pageRes.Total = m.PageInfo.Total
 		}
 		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
-		list, err := media.GetMediaPageByIds(mediaType, m.PageInfo, mediaIds, isLogin(detailType), userInfo.Id)
+		list, err := media.GetMediaPageByIds(mediaType, m.PageInfo, mediaIds, isLogin(detailType), userInfo.Id, mappingRiskLevel, userRiskStatus)
 		if err != nil {
 			m.FailedResult("分页查询媒体列表失败", result)
 			return
@@ -130,9 +135,10 @@ func (m *MediaController) List(mediaType string, permissionIds string) {
 }
 
 type RecordCountReq struct {
-	MediaId   int    `json:"MediaId"`
-	TraceId   string `json:"TraceId"`
-	MediaType string `json:"MediaType"`
+	MediaId   int    `json:"mediaId"`
+	MediaName string `json:"mediaName"`
+	TraceId   string `json:"traceId"`
+	MediaType string `json:"mediaType"`
 }
 
 // Count 获取品种列表
@@ -172,7 +178,7 @@ func (m *MediaController) Count() {
 // @Description 获取媒体详情
 // @Success 200 {object}
 // @router /media [get]
-func (m *MediaController) GetMedia(mediaType string, mediaId int) {
+func (m *MediaController) GetMedia(mediaType string, mediaId int, productId int) {
 	controllers.Wrap(&m.BaseController, func() (result *controllers.WrapData, err error) {
 		result = m.InitWrapData("获取媒体详情失败")
 		if mediaType == "" || !m.CheckMediaType(mediaType) {
@@ -182,12 +188,23 @@ func (m *MediaController) GetMedia(mediaType string, mediaId int) {
 		}
 		userInfo := m.Data["user"].(user.User)
 		detailType := m.Data["detailType"].(string)
+		var subscribeStatus string
+		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
+		}
 		mediaDetail, err := media.GetMediaById(mediaType, mediaId, isLogin(detailType), userInfo.Id)
-
 		if err != nil {
 			m.FailedResult("获取媒体详情失败", result)
 			return
 		}
+		mediaDetail.SubscribeStatus = subscribeStatus
 		m.SuccessResult("获取媒体详情成功", mediaDetail, result)
 		return
 	})

+ 138 - 0
controllers/order/order_controller.go

@@ -0,0 +1,138 @@
+package order
+
+import (
+	"eta/eta_mini_ht_api/common/exception"
+	"eta/eta_mini_ht_api/common/utils/page"
+	"eta/eta_mini_ht_api/controllers"
+	"eta/eta_mini_ht_api/service/order"
+	"eta/eta_mini_ht_api/service/user"
+)
+
+type OrderController struct {
+	controllers.BaseController
+}
+
+type PreviewOrderRequest struct {
+	ProductId int `json:"productId"`
+}
+
+// PreviewProductOrder  预览订单
+// @Summary 预览订单
+// @Description 预览订单
+// @Success 200 {object} controllers.BaseResponse
+// @router /previewProductOrder [get]
+func (o *OrderController) PreviewProductOrder(productId int) {
+	controllers.Wrap(&o.BaseController, func() (result *controllers.WrapData, err error) {
+		result = o.InitWrapData("预览订单创建失败")
+		var userInfo user.User
+		userInfo = o.Data["user"].(user.User)
+		if productId <= 0 {
+			err = exception.New(exception.IllegalProductId)
+			o.FailedResult("预览订单失败,产品编号非法", result)
+			return
+		}
+		//创单
+		//返回订单信息、商品信息
+		orderInfo, err := order.PreViewProductOrder(userInfo, productId)
+		if err != nil {
+			o.FailedResult("创建预览订单失败", result)
+			return
+		}
+		o.SuccessResult("预览订单创建成功", orderInfo, result)
+		return
+	})
+}
+
+// OrderDetail  获取订单详情
+// @Summary  获取订单详情
+// @Description  获取订单详情
+// @Success 200 {object} controllers.BaseResponse
+// @router /orderDetail [get]
+func (sc *SubscribeController) GetOrderDetail(orderNo string) {
+	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
+		result = sc.InitWrapData("获取订单号失败")
+		userInfo := sc.Data["user"].(user.User)
+		if orderNo == "" {
+			err = exception.New(exception.IllegalProductOrderId)
+			sc.FailedResult("获取订单详情失败,产品编号非法", result)
+			return
+		}
+		orderDetail, err := order.GetOrderDetail(orderNo, userInfo.Id)
+
+		if err != nil {
+			sc.FailedResult("获取订单详情失败", result)
+			err = exception.NewWithException(exception.GetOrderListFailed, err.Error())
+			return
+		}
+		sc.SuccessResult("获取订单详情成功", orderDetail, result)
+		return
+	})
+}
+
+// GetOrderList  获取用户订单号列表
+// @Summary   获取用户订单号列表
+// @Description   获取用户订单号列表
+// @Success 200 {object} controllers.BaseResponse
+// @router /orderList [get]
+func (sc *SubscribeController) GetOrderList(orderStatus string) {
+	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
+		result = sc.InitWrapData("分页查询订单列表失败")
+		userInfo := sc.Data["user"].(user.User)
+		pageRes := page.Page{
+			Current:  sc.PageInfo.Current,
+			PageSize: sc.PageInfo.PageSize,
+		}
+		pageRes.Total, pageRes.LatestId = order.GetTotalPageCountByUserId(userInfo.Id, orderStatus)
+		if sc.PageInfo.LatestId == 0 {
+			sc.PageInfo.LatestId = pageRes.LatestId
+		} else {
+			pageRes.LatestId = sc.PageInfo.LatestId
+			pageRes.Total = sc.PageInfo.Total
+		}
+		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
+		list, err := order.GetOrderPage(sc.PageInfo, userInfo.Id, orderStatus)
+		if err != nil {
+			sc.FailedResult("分页查询订单列表失败", result)
+			return
+		}
+		orders := new(page.PageResult)
+		orders.Data = list
+		orders.Page = pageRes
+		sc.SuccessResult("查询订单列表成功", orders, result)
+		return
+	})
+}
+
+type OrderRequest struct {
+	ProductOrderNo string `json:"productOrderNo"`
+}
+
+// CloseOrder  获取用户订单号列表
+// @Summary   获取用户订单号列表
+// @Description   获取用户订单号列表
+// @Success 200 {object} controllers.BaseResponse
+// @router /closeOrder [post]
+func (sc *SubscribeController) CloseOrder() {
+	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
+		result = sc.InitWrapData("关闭订单失败")
+		userInfo := sc.Data["user"].(user.User)
+		orderReq := new(OrderRequest)
+		sc.GetPostParams(orderReq)
+		if orderReq.ProductOrderNo == "" {
+			sc.FailedResult("关闭订单失败,非法的订单编号", result)
+			err = exception.New(exception.IllegalOrderNo)
+			return
+		}
+		err = order.CloseProductOrder(userInfo.Id, orderReq.ProductOrderNo)
+		if err != nil {
+			sc.FailedResult("关闭订单失败", result)
+			err = exception.NewWithException(exception.CloseOrderFailed, err.Error())
+			return
+		}
+		closeDTO := map[string]string{
+			"orderNo": orderReq.ProductOrderNo,
+		}
+		sc.SuccessResult("关闭订单成功", closeDTO, result)
+		return
+	})
+}

+ 69 - 64
controllers/user/subscribe_controller.go → controllers/order/subscribe_controller.go

@@ -1,11 +1,13 @@
-package user
+package order
 
 import (
+	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"
 	"eta/eta_mini_ht_api/service/facade"
 	"eta/eta_mini_ht_api/service/order"
+	"eta/eta_mini_ht_api/service/product"
 	"eta/eta_mini_ht_api/service/user"
 	"golang.org/x/time/rate"
 	"net/http"
@@ -87,7 +89,7 @@ const (
 // @Summary  获取订单号
 // @Description  获取订单号
 // @Success 200 {object} controllers.BaseResponse
-// @router /subscribe/orderNo [get]
+// @router /orderNo [get]
 func (sc *SubscribeController) GetOrderNo() {
 	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
 		result = sc.InitWrapData("获取订单号失败")
@@ -104,69 +106,11 @@ func (sc *SubscribeController) GetOrderNo() {
 	})
 }
 
-// OrderDetail  获取订单号
-// @Summary  获取订单号
-// @Description  获取订单号
-// @Success 200 {object} controllers.BaseResponse
-// @router /subscribe/orderDetail [get]
-func (sc *SubscribeController) GetOrderDetail(orderId int) {
-	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
-		result = sc.InitWrapData("获取订单号失败")
-		userInfo := sc.Data["user"].(user.User)
-		if orderId <= 0 {
-			err = exception.New(exception.IllegalProductOrderId)
-			sc.FailedResult("获取订单详情失败,产品编号非法", result)
-			return
-		}
-		orderDetail, err := order.GetOrderDetail(orderId, userInfo.Id)
-		if err != nil {
-			sc.FailedResult("获取订单详情失败", result)
-		}
-		sc.SuccessResult("获取订单详情成功", orderDetail, result)
-		return
-	})
-}
-
-// GetOrderList  获取用户订单号列表
-// @Summary   获取用户订单号列表
-// @Description   获取用户订单号列表
-// @Success 200 {object} controllers.BaseResponse
-// @router /subscribe/orderList [get]
-func (sc *SubscribeController) GetOrderList(orderStatus string) {
-	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
-		result = sc.InitWrapData("分页查询订单列表失败")
-		userInfo := sc.Data["user"].(user.User)
-		pageRes := page.Page{
-			Current:  sc.PageInfo.Current,
-			PageSize: sc.PageInfo.PageSize,
-		}
-		pageRes.Total, pageRes.LatestId = order.GetTotalPageCountByUserId(userInfo.Id, orderStatus)
-		if sc.PageInfo.LatestId == 0 {
-			sc.PageInfo.LatestId = pageRes.LatestId
-		} else {
-			pageRes.LatestId = sc.PageInfo.LatestId
-			pageRes.Total = sc.PageInfo.Total
-		}
-		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
-
-		list, err := order.GetOrderPage(sc.PageInfo, userInfo.Id, orderStatus)
-		if err != nil {
-			sc.FailedResult("分页查询订单列表失败", result)
-			return
-		}
-		orders := new(page.PageResult)
-		orders.Data = list
-		orders.Page = pageRes
-		sc.SuccessResult("查询报告列表成功", orders, result)
-		return
-	})
-}
-
 // SubscribeProduct  订阅产品
 // @Summary 订阅产品
 // @Description 订阅产品
 // @Success 200 {object} controllers.BaseResponse
-// @router /subscribe/createOrder [post]
+// @router /subscribe [post]
 func (sc *SubscribeController) SubscribeProduct() {
 	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
 		result = sc.InitWrapData("订阅产品失败")
@@ -195,6 +139,7 @@ func (sc *SubscribeController) SubscribeProduct() {
 		officialUser, err := user.GetUserByTemplateUserId(userInfo.Id)
 		if err != nil {
 			if err.Error() == exception.GetMsg(exception.OfficialUserNotFound) {
+				err = exception.New(exception.OfficialUserNotFound)
 				result.Ret = AccountNotOpen
 				sc.FailedResult("用户未开通账户", result)
 			} else {
@@ -202,15 +147,26 @@ func (sc *SubscribeController) SubscribeProduct() {
 			}
 			return
 		}
-		//开户中
-		if officialUser.AccountStatus == "opening" {
+		switch officialUser.AccountStatus {
+		case "unopen":
+			result.Ret = AccountNotOpen
+			err = exception.New(exception.AccountNotOpen)
+			sc.FailedResult("用户未开通账户", result)
+			return
+		case "opening":
 			result.Ret = AccountOpening
+			err = exception.New(exception.AccountNotOpen)
 			sc.FailedResult("用户开户中", result)
 			return
+		case "opened":
+		default:
+			err = exception.New(exception.IllegalAccountStatus)
+			sc.FailedResult(result.Msg, result)
+			return
 		}
-
 		//证件信息是否过期
 		if !officialUser.IDValid {
+			err = exception.New(exception.IDExpired)
 			result.Ret = IDExpired
 			sc.FailedResult("用户证件有效期过期", result)
 			return
@@ -226,6 +182,10 @@ func (sc *SubscribeController) SubscribeProduct() {
 		//返回订单信息、商品信息
 		orderInfo, err := order.CreateProductOrder(userInfo, subscribeReq.ProductId, subscribeReq.OrderNo)
 		if err != nil {
+			if err.Error() == exception.GetMsg(exception.DuplicateSubscribe) {
+				sc.FailedResult("请勿重复订阅", result)
+				return
+			}
 			sc.FailedResult("创建订单失败", result)
 			return
 		}
@@ -234,6 +194,51 @@ func (sc *SubscribeController) SubscribeProduct() {
 	})
 }
 
+// SubscribeList  用户订阅列表
+// @Summary  用户订阅列表
+// @Description  用户订阅列表
+// @Success 200 {object} controllers.BaseResponse
+// @router /subscribeList [get]
+func (sc *SubscribeController) SubscribeList(productType string) {
+	controllers.Wrap(&sc.BaseController, func() (result *controllers.WrapData, err error) {
+		result = sc.InitWrapData("分页查询订阅列表失败")
+		userInfo := sc.Data["user"].(user.User)
+		if productType == "" || !sc.CheckProductType(productType) {
+			err = exception.New(exception.ProductTypeError)
+			sc.FailedResult("分页查询订阅列表失败", result)
+			return
+		}
+		pageRes := page.Page{
+			Current:  sc.PageInfo.Current,
+			PageSize: sc.PageInfo.PageSize,
+		}
+		pageRes.Total, pageRes.LatestId = product.GetTotalUserPageCountByProductType(productType, userInfo.Id)
+		if err != nil {
+			logger.Error("分页查询订阅列表失败:%v", err)
+			sc.FailedResult("分页查询订阅列表失败", result)
+			return
+		}
+		if sc.PageInfo.LatestId == 0 {
+			sc.PageInfo.LatestId = pageRes.LatestId
+			sc.PageInfo.Total = pageRes.Total
+		} else {
+			pageRes.LatestId = sc.PageInfo.LatestId
+			pageRes.Total = sc.PageInfo.Total
+		}
+		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
+		list, err := product.SubscribeList(userInfo.Id, productType, sc.PageInfo)
+		if err != nil {
+			sc.FailedResult("分页查询订阅列表失败", result)
+			return
+		}
+		mediaList := new(page.PageResult)
+		mediaList.Data = list
+		mediaList.Page = pageRes
+		sc.SuccessResult("分页查询订阅列表成功", mediaList, result)
+		return
+	})
+}
+
 type SubscribeRequest struct {
 	ProductId int    `json:"productId"`
 	OrderNo   string `json:"orderNo"`

+ 109 - 2
controllers/payment/payment_controller.go

@@ -1,12 +1,119 @@
 package payment
 
-import "eta/eta_mini_ht_api/controllers"
+import (
+	"errors"
+	"eta/eta_mini_ht_api/common/exception"
+	"eta/eta_mini_ht_api/common/utils/lock"
+	"eta/eta_mini_ht_api/common/utils/redis"
+	"eta/eta_mini_ht_api/controllers"
+	orderDomian "eta/eta_mini_ht_api/domian/order"
+	userDomian "eta/eta_mini_ht_api/domian/user"
+	"eta/eta_mini_ht_api/service/facade"
+	"eta/eta_mini_ht_api/service/order"
+	"gorm.io/gorm"
+)
+import "eta/eta_mini_ht_api/service/user"
 
 type PaymentController struct {
 	controllers.BaseController
 }
 
-//支付
+// 支付
+type PayRequest struct {
+	ProductOrderNo string `json:"productOrderNo"`
+}
+type PayResponse struct {
+	ProductOrderNo string `json:"productOrderNo"`
+	TradeOrderNo   string `json:"tradeOrderNo"`
+}
+
+// PayOrder  支付订单
+// @Summary 支付订单
+// @Description 支付订单
+// @Success 200 {object} controllers.BaseResponse
+// @router /payOrder [post]
+func (pc *PaymentController) PayOrder() {
+	controllers.Wrap(&pc.BaseController, func() (result *controllers.WrapData, err error) {
+		result = pc.InitWrapData("创建支付订单失败")
+		payRequest := new(PayRequest)
+		pc.GetPostParams(payRequest)
+		if payRequest.ProductOrderNo == "" {
+			pc.FailedResult("订单编号不能为空", result)
+			err = exception.New(exception.IllegalOrderNo)
+			return
+		}
+		userInfo := pc.Data["user"].(user.User)
+		defer func() {
+			lock.ReleaseLock(redis.GeneratePaymentKey(payRequest.ProductOrderNo), pc.Data["threadId"].(string))
+		}()
+		if lock.AcquireLock(redis.GeneratePaymentKey(payRequest.ProductOrderNo), 10, pc.Data["threadId"].(string)) {
+			//校验商品订单状态
+			var productOrder orderDomian.ProductOrderDTO
+			productOrder, err = order.GetProductOrderByUser(userInfo.Id, payRequest.ProductOrderNo)
+			if err != nil {
+				if errors.Is(err, gorm.ErrRecordNotFound) {
+					pc.FailedResult("支付失败,商品订单信息不存在", result)
+					err = exception.New(exception.IllegalOrderNo)
+				} else {
+					pc.FailedResult("支付失败,查询商品订单失败", result)
+					err = exception.New(exception.IllegalOrderNo)
+				}
+				return
+			}
+			if productOrder.Status != "pending" {
+				if productOrder.Status == "closed" {
+					pc.FailedResult("支付失败,商品订单已关闭", result)
+					err = exception.New(exception.IllegalOrderStatus)
+				} else {
+					pc.FailedResult("支付失败,商品订单状态异常", result)
+					err = exception.New(exception.IllegalOrderStatus)
+				}
+				return
+			}
+			//1、未发起支付,2、支付到一半退出了,3、支付中
+			if productOrder.TradeNo != "" {
+				tradeOrder, tradeErr := order.GetTradeOrderByNo(productOrder.TradeNo)
+				if tradeErr != nil {
+					err = exception.NewWithException(exception.PayTradeOrderFailed, tradeErr.Error())
+					pc.FailedResult("支付失败,获取原支付订单信息失败", result)
+					return
+				}
+				if tradeOrder.PaymentStatus != "failed" {
+					err = exception.New(exception.PayTradeOrderFailed)
+					pc.FailedResult("支付失败,商品订单支付中,请勿重复支付", result)
+					return
+				}
+			}
+			//检查商品状态
+			err = order.CheckProductStatus(productOrder)
+			if err != nil {
+				pc.FailedResult("支付失败,商品状态异常", result)
+				return
+			}
+			var officialUser userDomian.OfficialUserDTO
+			officialUser, err = user.GetUserByTemplateUserId(userInfo.Id)
+			if err != nil {
+				pc.FailedResult("支付失败,正式用户信息不存在", result)
+			}
+			var tradeOrderNo string
+			tradeOrderNo, err = facade.PayOrder(officialUser.ID, userInfo.Id, productOrder)
+			if err != nil {
+				pc.FailedResult("支付订单创建失败", result)
+				err = exception.NewWithException(exception.CreatePaymentOrderFailed, err.Error())
+				return
+			}
+			pc.SuccessResult("支付订单创建成功", PayResponse{
+				ProductOrderNo: productOrder.OrderID,
+				TradeOrderNo:   tradeOrderNo,
+			}, result)
+			return
+		} else {
+			pc.FailedResult("商品订单处理中,请稍后再试", result)
+			err = exception.New(exception.PaymentProcessingError)
+			return
+		}
+	})
+}
 
 //退款
 

+ 170 - 1
controllers/product/product_controller.go

@@ -1,13 +1,27 @@
 package product
 
 import (
+	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"
 	productService "eta/eta_mini_ht_api/service/product"
+	"eta/eta_mini_ht_api/service/user"
+	"sync/atomic"
+)
+
+var (
+	productMap = map[string]bool{
+		"audio":   true,
+		"video":   true,
+		"media":   true,
+		"report":  true,
+		"package": true,
+	}
 )
 
 type ProductController struct {
-	controllers.BaseController
+	controllers.ListController
 }
 
 // GetProductInfo  获取商品信息
@@ -32,3 +46,158 @@ func (p *ProductController) GetProductInfo(productId int) {
 		return
 	})
 }
+
+// RelatePackage  关联套餐搜索
+// @Summary 关联套餐搜索
+// @Description 关联套餐搜索
+// @Success 200 {object} controllers.BaseResponse
+// @router /relatePackage [get]
+func (p *ProductController) RelatePackage(productId int) {
+	controllers.Wrap(&p.BaseController, func() (result *controllers.WrapData, err error) {
+		result = p.InitWrapData("获取关联套餐信息失败")
+		if productId <= 0 {
+			err = exception.New(exception.IllegalProductId)
+			p.FailedResult("获取关联套餐信息失败", result)
+			return
+		}
+		productInfo, err := productService.GetProductInfoById(productId)
+		if err != nil {
+			p.FailedResult("获取关联套餐信息失败", result)
+			err = exception.New(exception.ProductTypeError)
+			return
+		}
+		if productInfo.Type == "package" {
+			p.FailedResult("该商品为套餐,无法获取想关联套餐商品", result)
+			err = exception.New(exception.ProductTypeError)
+		}
+		switch productInfo.Type {
+		case "audio", "video":
+			productInfo.MediaId = productInfo.SourceId
+		case "report":
+			productInfo.ReportId = productInfo.SourceId
+		}
+		productList, err := productService.GetRelatePackage(productInfo)
+		productList = append(productList, productInfo)
+		if err != nil {
+			p.FailedResult("获取关联套餐信息失败", result)
+			return
+		}
+		p.SuccessResult("获取关联套餐信息成功", productList, result)
+		return
+	})
+}
+
+// ProductSearch  搜索产品
+// @Summary 搜索产品
+// @Description 搜索产品
+// @Success 200 {object} controllers.BaseResponse
+// @router /productSearch [get]
+func (p *ProductController) ProductSearch(key string, isSignal bool) {
+	controllers.Wrap(&p.BaseController, func() (result *controllers.WrapData, err error) {
+		result = p.InitWrapData("搜索产品信息失败")
+		userInfo := p.Data["user"].(user.User)
+		var productList []productService.ProductDTO
+		var permissionWeightMap map[int]*atomic.Int32
+		pageRes := page.Page{
+			Current:  p.PageInfo.Current,
+			PageSize: p.PageInfo.PageSize,
+		}
+		productIdMap, err := productService.RangeProductList()
+		if err != nil {
+			logger.Error("搜索产品信息失败")
+			err = exception.NewWithException(exception.GetProductListFailed, err.Error())
+			return
+		}
+		productPage := new(page.PageResult)
+		if isSignal {
+			if len(productIdMap["report"]) == 0 && len(productIdMap["audio"]) == 0 && len(productIdMap["video"]) == 0 {
+				logger.Info("没有相关的产品类型产品")
+				productPage.Page = pageRes
+				p.SuccessResult("搜索产品信息成功", productPage, result)
+				return
+			}
+		} else {
+			if len(productIdMap["package"]) == 0 {
+				logger.Info("没有相关的产品类型产品")
+				productPage.Page = pageRes
+				p.SuccessResult("搜索产品信息成功", productList, result)
+				return
+			}
+		}
+		//搜索出相关的报告和音视频
+		productSearchList, searchErr := productService.SearchRelateProduct(key, productIdMap)
+		if searchErr != nil {
+			logger.Error("搜索报告产品失败")
+			return
+		}
+		if len(productSearchList) == 0 {
+			logger.Info("没有相关的产品类型产品")
+			productPage.Page = pageRes
+			p.SuccessResult("搜索产品信息成功", productList, result)
+			return
+		}
+		if isSignal {
+			pageRes.Total = int64(len(productSearchList))
+			pageRes.LatestId = productService.LatestId()
+		} else {
+			pageRes.Total, pageRes.LatestId, permissionWeightMap = productService.CountSearchPackageList(productSearchList)
+		}
+		if p.PageInfo.LatestId == 0 {
+			p.PageInfo.LatestId = pageRes.LatestId
+			p.PageInfo.Total = pageRes.Total
+		} else {
+			pageRes.LatestId = p.PageInfo.LatestId
+			pageRes.Total = p.PageInfo.Total
+		}
+		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
+		productList, err = productService.ProductListBySort(isSignal, productSearchList, permissionWeightMap, userInfo.Id, p.PageInfo)
+		productPage.Data = productList
+		productPage.Page = pageRes
+		p.SuccessResult("搜索产品信息成功", productPage, result)
+		return
+	})
+}
+
+// ProductList  产品列表
+// @Summary  产品列表
+// @Description  产品列表
+// @Success 200 {object} controllers.BaseResponse
+// @router /ProductList [get]
+func (p *ProductController) ProductList(productType string, permissionIds string) {
+	controllers.Wrap(&p.BaseController, func() (result *controllers.WrapData, err error) {
+		result = p.InitWrapData("分页查询产品列表失败")
+		userInfo := p.Data["user"].(user.User)
+		if !productMap[productType] {
+			err = exception.New(exception.ProductTypeError)
+			p.FailedResult("分页查询产品列表失败", result)
+			return
+		}
+		permissionIdList, err := p.TransPermissionIds(permissionIds)
+		if err != nil {
+			logger.Error("品种列表解析错误:%v", err)
+			p.FailedResult("分页查询产品列表失败", result)
+			err = exception.New(exception.GetProductListFailed)
+			return
+		}
+		pageRes := page.Page{
+			Current:  p.PageInfo.Current,
+			PageSize: p.PageInfo.PageSize,
+		}
+		var productIds []int
+		pageRes.Total, pageRes.LatestId, productIds = productService.GetProductListByProductType(productType, permissionIdList, userInfo.Id)
+		if p.PageInfo.LatestId == 0 {
+			p.PageInfo.LatestId = pageRes.LatestId
+			p.PageInfo.Total = pageRes.Total
+		} else {
+			pageRes.LatestId = p.PageInfo.LatestId
+			pageRes.Total = p.PageInfo.Total
+		}
+		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
+		list, err := productService.ProductList(productIds, userInfo.Id, p.PageInfo)
+		productList := new(page.PageResult)
+		productList.Data = list
+		productList.Page = pageRes
+		p.SuccessResult("分页查询产品列表成功", productList, result)
+		return
+	})
+}

+ 42 - 18
controllers/report/report_controller.go

@@ -6,9 +6,10 @@ import (
 	"eta/eta_mini_ht_api/common/exception"
 	"eta/eta_mini_ht_api/common/utils/page"
 	"eta/eta_mini_ht_api/controllers"
-	reportService "eta/eta_mini_ht_api/domian/report"
+	reportDomian "eta/eta_mini_ht_api/domian/report"
 	"eta/eta_mini_ht_api/service/report"
 	"eta/eta_mini_ht_api/service/user"
+	"sync"
 )
 
 type ReportController struct {
@@ -36,7 +37,9 @@ func (r *ReportController) Search(key string) {
 		//获取当前可以被搜索的报告原始ID
 		//先要限制查询的id范围
 		var reportIds []int
-		pageRes.Total, pageRes.LatestId, reportIds, err = report.RangeSearch(key, isLogin(detailType), userInfo.Id)
+		var mappingRiskLevel string
+		var userRiskStatus string
+		pageRes.Total, pageRes.LatestId, reportIds, _, mappingRiskLevel, userRiskStatus, err = report.RangeSearch(key, isLogin(detailType), userInfo.Id)
 		if err != nil {
 			logger.Error("获取报告原始ID列表失败:%v", err)
 			r.FailedResult("分页搜索报告列表失败", result)
@@ -44,7 +47,7 @@ func (r *ReportController) Search(key string) {
 		}
 		if len(reportIds) == 0 {
 			reports := new(page.PageResult)
-			reports.Data = []reportService.ReportDTO{}
+			reports.Data = []reportDomian.ReportDTO{}
 			reports.Page = pageRes
 			logger.Info("没有可以查询的报告列表")
 			r.SuccessResult("分页搜索报告列表成功", reports, result)
@@ -60,10 +63,9 @@ func (r *ReportController) Search(key string) {
 			pageRes.Total = r.PageInfo.Total
 		}
 		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
-		list := make([]reportService.ReportDTO, 0)
+		list := make([]reportDomian.ReportDTO, 0)
 		if pageRes.LatestId > 0 {
-			//订阅 TODO
-			list, err = report.SearchReportList(key, reportIds, r.PageInfo, isLogin(detailType), userInfo.Id)
+			list, err = report.SearchReportList(key, reportIds, r.PageInfo, isLogin(detailType), userInfo.Id, mappingRiskLevel, userRiskStatus)
 			if err != nil {
 				r.FailedResult("分页搜索报告列表失败", result)
 				return
@@ -98,11 +100,15 @@ func (r *ReportController) List(permissionIds string) {
 			return
 		}
 		var reportOrgIds map[string][]int
-		pageRes.Total, pageRes.LatestId, reportOrgIds = report.GetTotalPageCountByPermissionIds(permissionIdList, isLogin(detailType), userInfo.Id)
-		if err != nil {
-			logger.Error("分页查询报告列表失败:%v", err)
-			r.FailedResult("分页查询报告列表失败", result)
-			return
+		var discardIds []int
+		var mappingRiskLevel string
+		var UserRiskStatus string
+		pageRes.Total, pageRes.LatestId, reportOrgIds, discardIds, mappingRiskLevel, UserRiskStatus = report.GetTotalPageCountByPermissionIds(permissionIdList, isLogin(detailType), userInfo.Id)
+		if pageRes.Total == 0 {
+			reports := new(page.PageResult)
+			reports.Data = []reportDomian.ReportDTO{}
+			reports.Page = pageRes
+			r.SuccessResult("查询报告列表成功", reports, result)
 		}
 		if r.PageInfo.LatestId == 0 {
 			r.PageInfo.LatestId = pageRes.LatestId
@@ -114,12 +120,12 @@ func (r *ReportController) List(permissionIds string) {
 		if len(reportOrgIds) == 0 {
 			logger.Info("没有搜索到相关品种的报告,返回空列表")
 			reports := new(page.PageResult)
-			reports.Data = []reportService.ReportDTO{}
+			reports.Data = []reportDomian.ReportDTO{}
 			reports.Page = pageRes
 			r.SuccessResult("查询报告列表成功", reports, result)
 			return
 		}
-		list, err := report.GetReportPage(r.PageInfo, reportOrgIds, false, isLogin(detailType), userInfo.Id)
+		list, err := report.GetReportPage(r.PageInfo, reportOrgIds, discardIds, isLogin(detailType), userInfo.Id, mappingRiskLevel, UserRiskStatus)
 		if err != nil {
 			r.FailedResult("分页查询报告列表失败", result)
 			return
@@ -155,7 +161,7 @@ func (r *ReportController) HotRanked(permissionIds string, limit int) {
 		detailType := r.Data["detailType"].(string)
 		userInfo := r.Data["user"].(user.User)
 		//
-		permissionIdsWithRisk, pdRiskLevel, err := report.RangePermissionIds(isLogin(detailType), userInfo.Id)
+		permissionIdsWithRisk, pdRiskLevel, userRiskStatus, err := report.RangePermissionIds(isLogin(detailType), userInfo.Id)
 		if err != nil {
 			logger.Error("获取带有风险等级的品种列表错误:%v", err)
 			r.FailedResult("获取最热报告列表失败", result)
@@ -166,7 +172,7 @@ func (r *ReportController) HotRanked(permissionIds string, limit int) {
 			r.SuccessResult("获取本周最热报告列表成功", []report.HotRankedReport{}, result)
 			return
 		}
-		list, err := report.GetRandedReportByWeeklyHot(limit, isLogin(detailType), userInfo.Id, pdRiskLevel)
+		list, err := report.GetRandedReportByWeeklyHot(50, isLogin(detailType), userInfo.Id, pdRiskLevel, userRiskStatus)
 		//二级品种
 		permissionIdList, err := r.TransPermissionIds(permissionIds)
 		if err != nil {
@@ -202,6 +208,9 @@ func (r *ReportController) HotRanked(permissionIds string, limit int) {
 				}
 			}
 		}
+		if len(filterList) > limit {
+			filterList = filterList[0:limit]
+		}
 		if err != nil {
 			r.FailedResult("获取本周最热报告列表成功", result)
 			return
@@ -224,7 +233,7 @@ func (r *ReportController) PublishRanked(permissionIds string, limit int, week b
 		detailType := r.Data["detailType"].(string)
 		userInfo := r.Data["user"].(user.User)
 		//
-		permissionIdsWithRisk, pdRiskLevel, err := report.RangePermissionIds(isLogin(detailType), userInfo.Id)
+		permissionIdsWithRisk, pdRiskLevel, userRiskStatus, err := report.RangePermissionIds(isLogin(detailType), userInfo.Id)
 		if err != nil {
 			logger.Error("获取带有风险等级的品种列表错误:%v", err)
 			r.FailedResult("获取最新发布报告列表失败", result)
@@ -236,7 +245,7 @@ func (r *ReportController) PublishRanked(permissionIds string, limit int, week b
 			return
 		}
 		//获取最新的报告列表
-		list, err := report.GetRandedReportByPublishTimeWeekly(limit, week, isLogin(detailType), userInfo.Id, pdRiskLevel)
+		list, err := report.GetRandedReportByPublishTimeWeekly(50, week, isLogin(detailType), userInfo.Id, pdRiskLevel, userRiskStatus)
 		if err != nil {
 			r.FailedResult("获取最新发布报告列表失败", result)
 			return
@@ -277,6 +286,9 @@ func (r *ReportController) PublishRanked(permissionIds string, limit int, week b
 				}
 			}
 		}
+		if len(filterList) > limit {
+			filterList = filterList[0:limit]
+		}
 		r.SuccessResult("获取最新发布报告列表成功", filterList, result)
 		return
 	})
@@ -347,7 +359,7 @@ func (r *ReportController) Count() {
 // @Description 获取研报详情
 // @Success 200 {object}
 // @router /report [get]
-func (r *ReportController) GetReport(reportId int) {
+func (r *ReportController) GetReport(reportId int, productId int) {
 	controllers.Wrap(&r.BaseController, func() (result *controllers.WrapData, err error) {
 		result = r.InitWrapData("获取研报详情失败")
 		userInfo := r.Data["user"].(user.User)
@@ -355,12 +367,24 @@ func (r *ReportController) GetReport(reportId int) {
 		if !isLogin(detailType) {
 			logger.Info("当前用户未登录,展示部分详情")
 		}
+		var subscribeStatus string
+		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
+		}
 		reportDetail, err := report.GetReportById(reportId, isLogin(detailType), userInfo.Id)
 		if err != nil {
 			r.FailedResult("获取研报详情失败", result)
 			err = exception.New(exception.GetReportFailed)
 			return
 		}
+		reportDetail.SubscribeStatus = subscribeStatus
 		r.SuccessResult("获取研报详情成功", reportDetail, result)
 		return
 	})

+ 67 - 0
controllers/user/account_controller.go

@@ -2,6 +2,7 @@ package user
 
 import (
 	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/exception"
 	"eta/eta_mini_ht_api/controllers"
 	"eta/eta_mini_ht_api/service/facade"
 	"eta/eta_mini_ht_api/service/user"
@@ -22,6 +23,20 @@ const (
 	MDCTraceId     = "traceId"
 )
 
+const (
+	AccountNotOpen = 430
+	IDExpired      = 431
+	RiskUnTest     = 432
+	RiskNotMatch   = 433
+	RiskExpired    = 434
+	AccountOpening = 435
+
+	orderPending = "pending"
+	orderPaid    = "paid"
+	orderRefund  = "refund"
+	orderClosed  = "closed"
+)
+
 func (a *AccountController) Prepare() {
 	var requestBody string
 	uri := a.Ctx.Input.URI()
@@ -65,3 +80,55 @@ func (a *AccountController) GetRiskInfoToken() {
 	})
 
 }
+
+// CheckUserStatus  获取风险测评问卷请求Token
+// @Summary 获取风险测评问卷请求Token
+// @Description 获取风险测评问卷请求Token
+// @Success 200 {object} controllers.BaseResponse
+// @router /checkUserStatus/ [post]
+func (a *AccountController) CheckUserStatus() {
+	controllers.Wrap(&a.BaseController, func() (result *controllers.WrapData, err error) {
+		result = a.InitWrapData("获取风险测评问卷请求Token失败")
+		userInfo := a.Data["user"].(user.User)
+		//校验是否已经购买并且在有效期内
+		//是否开户
+		//未开户
+		officialUser, err := user.GetUserByTemplateUserId(userInfo.Id)
+		if err != nil {
+			if err.Error() == exception.GetMsg(exception.OfficialUserNotFound) {
+				err = exception.New(exception.OfficialUserNotFound)
+				result.Ret = AccountNotOpen
+				a.FailedResult("用户未开通账户", result)
+			} else {
+				a.FailedResult("获取用户账户信息失败", result)
+			}
+			return
+		}
+		switch officialUser.AccountStatus {
+		case "unopen":
+			result.Ret = AccountNotOpen
+			err = exception.New(exception.AccountNotOpen)
+			a.FailedResult("用户未开通账户", result)
+			return
+		case "opening":
+			result.Ret = AccountOpening
+			err = exception.New(exception.AccountNotOpen)
+			a.FailedResult("用户开户中", result)
+			return
+		case "opened":
+		default:
+			err = exception.New(exception.IllegalAccountStatus)
+			a.FailedResult(result.Msg, result)
+			return
+		}
+		//证件信息是否过期
+		if !officialUser.IDValid {
+			err = exception.New(exception.IDExpired)
+			result.Ret = IDExpired
+			a.FailedResult("用户证件有效期过期", result)
+			return
+		}
+		a.SuccessResult("校验用户信息成功", nil, result)
+		return
+	})
+}

+ 8 - 4
controllers/user/analyst_controller.go

@@ -83,7 +83,9 @@ func (an *AnalystController) AnalystReportList(analystName string) {
 		}
 		userInfo := an.Data["user"].(user.User)
 		var reportIds []int
-		pageRes.Total, pageRes.LatestId, reportIds = report.RangeSearchByAnalyst(analystName, userInfo.Id)
+		var mappingRiskLevel string
+		var userRiskStatus string
+		pageRes.Total, pageRes.LatestId, reportIds, mappingRiskLevel, userRiskStatus = report.RangeSearchByAnalyst(analystName, userInfo.Id)
 		if len(reportIds) == 0 {
 			reports := new(page.PageResult)
 			reports.Data = []interface{}{}
@@ -98,7 +100,7 @@ func (an *AnalystController) AnalystReportList(analystName string) {
 			pageRes.Total = an.PageInfo.Total
 		}
 		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
-		list, err := report.GetReportPageByAnalyst(an.PageInfo, analystName, reportIds)
+		list, err := report.GetReportPageByAnalyst(an.PageInfo, analystName, reportIds, userInfo.Id, mappingRiskLevel, userRiskStatus)
 		if err != nil {
 			an.FailedResult("分页获取研究员报告列表失败", result)
 			return
@@ -129,7 +131,9 @@ func (an *AnalystController) MediaList(mediaType string, analystId int) {
 		}
 		userInfo := an.Data["user"].(user.User)
 		var mediaIds []int
-		pageRes.Total, pageRes.LatestId, mediaIds = media.RangeSearchByAnalyst(mediaType, analystId, userInfo.Id)
+		var mappingRiskLevel string
+		var userRiskStatus string
+		pageRes.Total, pageRes.LatestId, mediaIds, mappingRiskLevel, userRiskStatus = media.RangeSearchByAnalyst(mediaType, analystId, userInfo.Id)
 		if len(mediaIds) == 0 {
 			mediaList := new(page.PageResult)
 			mediaList.Data = []interface{}{}
@@ -145,7 +149,7 @@ func (an *AnalystController) MediaList(mediaType string, analystId int) {
 			pageRes.Total = an.PageInfo.Total
 		}
 		pageRes.TotalPage = page.TotalPages(pageRes.Total, pageRes.PageSize)
-		list, err := media.GetMediaPageByAnalystId(mediaType, an.PageInfo, analystId, mediaIds)
+		list, err := media.GetMediaPageByAnalystId(mediaType, an.PageInfo, analystId, mediaIds, userInfo.Id, mappingRiskLevel, userRiskStatus)
 		if err != nil {
 			an.FailedResult("分页查询研究员媒体列表失败", result)
 			return

+ 0 - 43
controllers/user/order_controller.go

@@ -1,43 +0,0 @@
-package user
-
-import (
-	"eta/eta_mini_ht_api/common/exception"
-	"eta/eta_mini_ht_api/controllers"
-)
-
-type OrderController struct {
-	controllers.BaseController
-}
-
-type ProductOrderReq struct {
-	ProductId int `json:"productId"`
-}
-
-// CreateProductOrder  创建商品订单
-// @Summary 创建商品订单
-// @Description 创建商品订单
-// @Success 200 {object} controllers.BaseResponse
-// @router /createProductOrder [post]
-func (o *OrderController) CreateProductOrder() {
-	controllers.Wrap(&o.BaseController, func() (result *controllers.WrapData, err error) {
-		result = o.InitWrapData("获取商品详情失败")
-		err = exception.New(exception.IllegalProductId)
-		o.FailedResult("获取商品详情失败", result)
-		//客戶是否开户,是否有风险等级,是否风险等级过期
-
-		//报告的风险等级 套餐的风险等级
-		//创单
-		//facade.CheckUserRiskLevel()
-		//productInfo, err := productService.GetProductInfoById(productId)
-		//if err != nil {
-		//	o.FailedResult("获取商品详情失败", result)
-		//	return
-		//}
-		//o.SuccessResult("获取商品详情成功", productInfo, result)
-		return
-	})
-	//
-	//获取用户信息
-	//userInfo := o.Data["user"].(user.User)
-
-}

+ 1 - 1
controllers/user/user_controller.go

@@ -500,7 +500,7 @@ func (u *UserController) Notify() {
 
 		if item.MsgType == "event" {
 			switch item.Event {
-			case "subscribe":
+			case "order":
 				fmt.Println("关注")
 				go auth.BindWxGzhByOpenId(openId)
 			case "unsubscribe":

+ 130 - 0
controllers/web_hook/htfutures_trade_controller.go

@@ -0,0 +1,130 @@
+package web_hook
+
+import (
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/exception"
+	"eta/eta_mini_ht_api/controllers"
+	orderService "eta/eta_mini_ht_api/domian/order"
+	"eta/eta_mini_ht_api/service/product"
+)
+
+type HTFuturesTradeController struct {
+	controllers.WebHookController
+}
+
+type ss struct {
+	Success      bool   `json:"success"`
+	User         int    `json:"user"`
+	TradeOrderNo string `json:"tradeOrderNo"`
+}
+
+// InformPaymentResult  支付通知接口
+// @Summary 支付通知接口
+// @Description 支付通知接口
+// @Success 200 {object} controllers.BaseResponse
+// @router /v1/payment/informPaymentResult [post]
+func (h *HTFuturesTradeController) InformPaymentResult() {
+	controllers.WrapWebhook(&h.WebHookController, func() (result *controllers.WrapData, err error) {
+		result = h.InitWrapData("同步风险等级")
+		s := new(ss)
+		h.GetPostParams(s)
+		logger.Info("支付结果通知: %v", s)
+		if s.Success {
+			productOrder, _ := orderService.DealPayment(s.TradeOrderNo, orderService.PaySuccess)
+			//privateKey, err := authUtils.ParsePrivateKey(htConfig.GetWebhookPrivateKey())
+			_ = product.OpenProduct(productOrder)
+		} else {
+			_, _ = orderService.DealPayment(s.TradeOrderNo, orderService.PayFail)
+		}
+		//htConfig := config.GetConfig(contants.HT).(*config.HTBizConfig)
+		//webhookRequest := new(WebhookRequest)
+		//h.GetPostParams(webhookRequest)
+		//privateKey, err := authUtils.ParsePrivateKey(htConfig.GetWebhookPrivateKey())
+		//if err != nil {
+		//	err = exception.NewWithException(exception.SysError, err.Error())
+		//	logger.Error("解析私钥失败: %v", err)
+		//	h.FailedResult("解析私钥失败", result)
+		//	return
+		//}
+		//decodeData, err := authUtils.DecryptWithRSA(privateKey, webhookRequest.Data)
+		//if err != nil {
+		//	err = exception.NewWithException(exception.SysError, err.Error())
+		//	logger.Error("解密请求体失败: %v", err)
+		//	h.FailedResult("解密请求体失败", result)
+		//	return
+		//}
+		//syncCustomerRiskLevelReq := new(SyncCustomerRiskLevelReq)
+		//err = json.Unmarshal(decodeData, syncCustomerRiskLevelReq)
+		//if err != nil {
+		//	err = exception.NewWithException(exception.SyncRiskError, err.Error())
+		//	logger.Error("解析请求体失败: %v", err)
+		//	h.FailedResult("解析请求体失败", result)
+		//	return
+		//}
+		result = h.InitWrapData("支付结果应答成功")
+		h.SuccessResult("success", nil, result)
+		return
+	})
+}
+
+// InformRefundResult  退款通知接口
+// @Summary 退款通知接口
+// @Description 退款通知接口
+// @Success 200 {object} controllers.BaseResponse
+// @router /v1/payment/informRefundResult [post]
+func (h *HTFuturesTradeController) InformRefundResult() {
+	controllers.WrapWebhook(&h.WebHookController, func() (result *controllers.WrapData, err error) {
+		result = h.InitWrapData("同步风险等级")
+		s := new(ss)
+		h.GetPostParams(s)
+		if s.Success {
+			productOrder, RefundErr := orderService.DealRefund(s.TradeOrderNo, orderService.RefundSuccess)
+			if RefundErr != nil {
+				logger.Error("退款订单处理失败: %v", err)
+				err = exception.NewWithException(exception.RefundDealFail, err.Error())
+				h.FailedResult("退款订单处理失败", result)
+				return
+			}
+
+			//处理退款订单
+			_ = product.CloseProduct(productOrder)
+		} else {
+			_, RefundErr := orderService.DealRefund(s.TradeOrderNo, orderService.RefundFail)
+			if RefundErr != nil {
+				logger.Error("退款订单处理失败: %v", err)
+				err = exception.NewWithException(exception.RefundDealFail, RefundErr.Error())
+				h.FailedResult("退款订单处理失败", result)
+				return
+			}
+		}
+		//创建meta_info
+		//htConfig := config.GetConfig(contants.HT).(*config.HTBizConfig)
+		//webhookRequest := new(WebhookRequest)
+		//h.GetPostParams(webhookRequest)
+		//privateKey, err := authUtils.ParsePrivateKey(htConfig.GetWebhookPrivateKey())
+		//if err != nil {
+		//	err = exception.NewWithException(exception.SysError, err.Error())
+		//	logger.Error("解析私钥失败: %v", err)
+		//	h.FailedResult("解析私钥失败", result)
+		//	return
+		//}
+		//decodeData, err := authUtils.DecryptWithRSA(privateKey, webhookRequest.Data)
+		//if err != nil {
+		//	err = exception.NewWithException(exception.SysError, err.Error())
+		//	logger.Error("解密请求体失败: %v", err)
+		//	h.FailedResult("解密请求体失败", result)
+		//	return
+		//}
+		//syncCustomerRiskLevelReq := new(SyncCustomerRiskLevelReq)
+		//err = json.Unmarshal(decodeData, syncCustomerRiskLevelReq)
+		//if err != nil {
+		//	err = exception.NewWithException(exception.SyncRiskError, err.Error())
+		//	logger.Error("解析请求体失败: %v", err)
+		//	h.FailedResult("解析请求体失败", result)
+		//	return
+		//}
+		result = h.InitWrapData("退款结果应答成功")
+		h.SuccessResult("success", nil, result)
+		return
+	})
+}

+ 30 - 0
domian/config/permission_service.go

@@ -127,3 +127,33 @@ func SyncPermissionClassifyMapping(list []PermissionClassifyMappingDTO) error {
 	}
 	return permissionDao.BatchInsertOrUpdateMapping(permissionMappingList)
 }
+
+func GetPermissionByName(name string) (dto PermissionDTO, err error) {
+	permission, err := permissionDao.GetPermissionByName(name)
+	if err != nil {
+		return
+	}
+	dto = convertPermissionToDTO(permission)
+	return
+}
+
+func convertPermissionToDTO(permission permissionDao.Permission) PermissionDTO {
+	return PermissionDTO{
+		PermissionId:   permission.PermissionId,
+		PermissionName: permission.Name,
+		ParentId:       permission.ParentId,
+		RiskLevel:      permission.RiskLevel,
+		Sort:           permission.Sort,
+	}
+}
+
+func GetSecondPermissionsByClassifyId(id int) (dtoList []PermissionDTO, err error) {
+	permissionList, err := permissionDao.GetSecondPermissionsByClassifyID(id)
+	if err != nil {
+		return
+	}
+	for _, permission := range permissionList {
+		dtoList = append(dtoList, convertPermissionToDTO(permission))
+	}
+	return
+}

+ 18 - 12
domian/financial_analyst/financial_analyst_service.go

@@ -7,13 +7,16 @@ import (
 )
 
 type FinancialAnalystDTO struct {
-	Id           int
-	ETAId        int
-	Name         string
-	HeadImgUrl   string
-	Introduction string
-	Status       bool
-	Deleted      bool
+	Id                      int
+	ETAId                   int
+	Name                    string
+	Position                string
+	InvestmentCertificate   string
+	ProfessionalCertificate string
+	HeadImgUrl              string
+	Introduction            string
+	Status                  bool
+	Deleted                 bool
 }
 
 func GetAnalystList(pageInfo page.PageInfo) (analystsDTO []FinancialAnalystDTO, err error) {
@@ -71,10 +74,13 @@ func convert(dto FinancialAnalystDTO) (financialAnalyst financialAnalystDao.CrmF
 }
 func convertToBaseDTO(financialAnalyst financialAnalystDao.CrmFinancialAnalyst) (dto FinancialAnalystDTO) {
 	return FinancialAnalystDTO{
-		Id:           financialAnalyst.Id,
-		ETAId:        financialAnalyst.ETAId,
-		HeadImgUrl:   financialAnalyst.HeadImgURL,
-		Name:         financialAnalyst.Name,
-		Introduction: financialAnalyst.Introduction,
+		Id:                      financialAnalyst.Id,
+		ETAId:                   financialAnalyst.ETAId,
+		HeadImgUrl:              financialAnalyst.HeadImgURL,
+		Name:                    financialAnalyst.Name,
+		Introduction:            financialAnalyst.Introduction,
+		ProfessionalCertificate: financialAnalyst.ProfessionalCertificate,
+		InvestmentCertificate:   financialAnalyst.InvestmentCertificate,
+		Position:                financialAnalyst.Position,
 	}
 }

+ 194 - 64
domian/media/media_service.go

@@ -7,15 +7,15 @@ import (
 	logger "eta/eta_mini_ht_api/common/component/log"
 	"eta/eta_mini_ht_api/common/contants"
 	"eta/eta_mini_ht_api/common/utils/page"
-	configService "eta/eta_mini_ht_api/domian/config"
+	configDomain "eta/eta_mini_ht_api/domian/config"
 	reportService "eta/eta_mini_ht_api/domian/report"
 	"eta/eta_mini_ht_api/models"
+	configDao "eta/eta_mini_ht_api/models/config"
+	"eta/eta_mini_ht_api/models/image"
 	mediaDao "eta/eta_mini_ht_api/models/media"
-	productDao "eta/eta_mini_ht_api/models/merchant"
-	"fmt"
+	merchantDao "eta/eta_mini_ht_api/models/merchant"
 	"sort"
 	"strconv"
-	"strings"
 	"time"
 )
 
@@ -32,7 +32,13 @@ const (
 )
 
 var (
-	sortField = []string{"_score:desc"}
+	sortField       = []string{"_score:desc"}
+	transProductMap = map[string]merchantDao.MerchantProductType{
+		"report":  merchantDao.Report,
+		"video":   merchantDao.Video,
+		"audio":   merchantDao.Audio,
+		"package": merchantDao.Package,
+	}
 )
 
 func elastic() *es.ESClient {
@@ -40,6 +46,7 @@ func elastic() *es.ESClient {
 }
 
 type MediaDTO struct {
+	Type                  string   `json:"type"`
 	MediaId               int      `json:"mediaId"`
 	AuthorId              int      `json:"authorId"`
 	AuthorName            string   `json:"authorName,omitempty"`
@@ -48,6 +55,7 @@ type MediaDTO struct {
 	MediaName             string   `json:"mediaName"`
 	MediaTitle            string   `json:"mediaTitle,omitempty"`
 	CoverSrc              string   `json:"coverSrc"`
+	CoverUrl              string   `json:"coverUrl"`
 	SourceType            string   `json:"sourceType"`
 	MediaPlayMilliseconds int      `json:"mediaPlayMilliseconds"`
 	PublishedTime         string   `json:"publishedTime"`
@@ -59,10 +67,14 @@ type MediaDTO struct {
 	IsFree                bool     `json:"isFree"`
 	Price                 string   `json:"price"`
 	IsSubscribe           bool     `json:"isSubscribe"`
+	SubscribeStatus       string   `json:"subscribeStatus"`
+	IsPackage             bool     `json:"isPackage"`
 	RiskLevel             string   `json:"riskLevel"`
+	ProductId             int      `json:"productId"`
+	Score                 float64  `json:"score"`
 }
 
-func SearchMediaList(_ string, key string, mediaIds []int, from int, size int, max int64) (reports []MediaDTO, err error) {
+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 {
@@ -87,7 +99,7 @@ func SearchMediaList(_ string, key string, mediaIds []int, from int, size int, m
 		media.Highlight = content[ESColumn]
 		media.PublishedTime = media.PublishedTime[:10]
 		media.MediaTitle = media.Highlight[0]
-		reports = append(reports, media)
+		medias = append(medias, media)
 	}
 	return
 }
@@ -122,78 +134,94 @@ func SearchMaxMediaId(mediaType string, key string) (total int64, latestId int64
 	}
 	return
 }
-func GetMediaPermissionMappingByPermissionIds(mediaType string, permissionIds []int, riskLevel string) (total int64, latestId int64, ids []int) {
-	ids, err := mediaDao.GetMediaPermissionMappingByPermissionId(mediaType, permissionIds)
+func GetMediaPermissionMappingByPermissionIds(mediaType string, permissionIds []int) (total int64, latestId int64, ids []int) {
+	mediaIds, err := mediaDao.GetMediaPermissionMappingByPermissionId(mediaType, permissionIds)
 	if err != nil {
 		logger.Error("获取配置品种的媒体列表信息失败:%v", err)
 		return 0, 0, ids
 	}
-	if riskLevel != "" {
-		var productList []productDao.MerchantProduct
-		//现加入没有产品的报告
-		productList, err = productDao.GetProductListBySourceIds(ids, productDao.Audio, productDao.Video)
-		for _, mediaId := range ids {
-			find := false
-			for _, product := range productList {
-				if product.SourceID == mediaId {
-					find = true
-					break
-				}
+	//获取一下下架的报告产品
+	var offSaleProducts []merchantDao.MerchantProduct
+	offSaleProducts, err = merchantDao.GetOffSaleProducts([]merchantDao.MerchantProductType{transProductMap[mediaType], merchantDao.Package})
+	var disCardMediaIds []int
+	var ProductPermissionIds []int
+	var filterIds []int
+	if err != nil {
+		logger.Error("获取下架的报告产品失败:%v", err)
+	} else {
+		for _, product := range offSaleProducts {
+			if product.Type == "package" {
+				ProductPermissionIds = append(ProductPermissionIds, product.SourceId)
 			}
-			if !find {
-				ids = append(ids, mediaId)
+			if product.Type == transProductMap[mediaType] {
+				disCardMediaIds = append(disCardMediaIds, product.SourceId)
 			}
 		}
-		riskNum, parseErr := parseRiskLevel(riskLevel)
-		if parseErr != nil {
-			logger.Error("解析风险等级失败:%v", err)
-			return
+	}
+	if len(ProductPermissionIds) > 0 {
+		var disCardIds []int
+		disCardIds, err = mediaDao.GetMediaPermissionMappingByPermissionId(mediaType, ProductPermissionIds)
+		if len(disCardIds) > 0 {
+			disCardMediaIds = append(disCardMediaIds, disCardIds...)
+		}
+	}
+	//对数据去重
+	disCardMediaIds = uniqueArray(disCardMediaIds)
+	//获取媒体中还包含上架套餐的id
+	if len(disCardMediaIds) > 0 {
+		mediaIdsSalePackage, _ := merchantDao.GetMediaOnSalePackageIds(disCardMediaIds)
+		mediaIdsSaleProduct, _ := merchantDao.GetOnSaleMediaIds(disCardMediaIds, transProductMap[mediaType])
+		showReportMap := make(map[int]bool)
+		disCardMap := make(map[int]bool)
+		for _, reportId := range mediaIdsSalePackage {
+			showReportMap[reportId] = true
+		}
+		for _, reportId := range mediaIdsSaleProduct {
+			showReportMap[reportId] = true
 		}
-		//再把符合风险等级的产品加入
-		for _, mediaId := range ids {
-			for _, product := range productList {
-				if product.SourceID == mediaId {
-					pdRiskNum, pdErr := parseRiskLevel(product.RiskLevel)
-					if pdErr != nil {
-						logger.Error("解析产品风险等级失败:%v,产品id:%v", pdErr, product.ID)
-						continue
-					}
-					if pdRiskNum <= riskNum {
-						ids = append(ids, mediaId)
-					}
-				}
+		var filterDisCardReportIds []int
+		for _, id := range disCardMediaIds {
+			if _, ok := showReportMap[id]; !ok {
+				filterDisCardReportIds = append(filterDisCardReportIds, id)
+				disCardMap[id] = true
 			}
 		}
+		disCardMediaIds = filterDisCardReportIds
+		for _, id := range mediaIds {
+			if _, ok := disCardMap[id]; !ok {
+				filterIds = append(filterIds, id)
+			}
+		}
+	} else {
+		filterIds = mediaIds
 	}
-	if len(ids) > 0 {
-		sort.Slice(ids, func(i, j int) bool {
-			return ids[i] > ids[j]
+	if len(filterIds) > 0 {
+		sort.Slice(filterIds, func(i, j int) bool {
+			return filterIds[i] > filterIds[j]
 		})
-		return int64(len(ids)), int64(ids[0]), ids
+		return int64(len(filterIds)), int64(filterIds[0]), filterIds
 	}
 	return 0, 0, []int{}
 }
-
-func parseRiskLevel(level string) (int, error) {
-	parts := strings.Split(level, "R")
-	if len(parts) < 2 {
-		return 0, fmt.Errorf("无效的风险等级: %s", level)
-	}
-	numberStr := parts[1]
-	number, err := strconv.Atoi(numberStr)
-	if err != nil {
-		return 0, fmt.Errorf("无法将风险等级转换为数字: %s", err)
+func uniqueArray(arr []int) []int {
+	uniqueMap := make(map[int]bool)
+	var result []int
+	for _, value := range arr {
+		if _, exists := uniqueMap[value]; !exists {
+			uniqueMap[value] = true
+			result = append(result, value)
+		}
 	}
-	return number, nil
-}
 
+	return result
+}
 func GetAnalystMediaPermissionMappingByPermissionIds(mediaType string, permissionIds []int, analystId int) (total int64, latestId int64, ids []int) {
-	ids, err := mediaDao.GetMediaPermissionMappingByPermissionId(mediaType, permissionIds)
+	meidaIds, err := mediaDao.GetMediaPermissionMappingByPermissionId(mediaType, permissionIds)
 	if err != nil {
 		logger.Error("获取当前最大媒体id失败:%v", err)
 		return 0, 0, ids
 	}
-	ids, err = mediaDao.GetAnalystMediaRangeReportIds(mediaType, ids, analystId)
+	ids, err = mediaDao.GetAnalystMediaRangeMediaIds(mediaType, ids, analystId)
 	if err != nil {
 		logger.Error("根据研究员过滤媒体列表id失败:%v", err)
 		return 0, 0, ids
@@ -202,10 +230,69 @@ func GetAnalystMediaPermissionMappingByPermissionIds(mediaType string, permissio
 		logger.Info("根据研究员过滤媒体列表id为空")
 		return 0, 0, ids
 	}
-	sort.Slice(ids, func(i, j int) bool {
-		return ids[i] > ids[j]
-	})
-	return int64(len(ids)), int64(ids[0]), ids
+	var offSaleProducts []merchantDao.MerchantProduct
+	offSaleProducts, err = merchantDao.GetOffSaleProducts([]merchantDao.MerchantProductType{transProductMap[mediaType], merchantDao.Package})
+	var disCardMediaIds []int
+	var filterPermissionIds []int
+	var ProductPermissionIds []int
+	var filterIds []int
+	if err != nil {
+		logger.Error("获取下架的报告产品失败:%v", err)
+	} else {
+		for _, product := range offSaleProducts {
+			if product.Type == "package" {
+				ProductPermissionIds = append(ProductPermissionIds, product.SourceId)
+			}
+			if product.Type == transProductMap[mediaType] {
+				disCardMediaIds = append(disCardMediaIds, product.SourceId)
+			}
+		}
+	}
+	if len(ProductPermissionIds) > 0 {
+		var disCardIds []int
+		disCardIds, err = mediaDao.GetMediaPermissionMappingByPermissionId(mediaType, filterPermissionIds)
+		if len(disCardMediaIds) > 0 {
+			disCardMediaIds = append(disCardMediaIds, disCardIds...)
+		}
+	}
+	//对数据去重
+	disCardMediaIds = uniqueArray(disCardMediaIds)
+
+	//获取媒体中还包含上架套餐的id
+	if len(disCardMediaIds) > 0 {
+		reportIdsSalePackage, _ := merchantDao.GetMediaOnSalePackageIds(disCardMediaIds)
+		reportIdsSaleProduct, _ := merchantDao.GetOnSaleMediaIds(disCardMediaIds, transProductMap[mediaType])
+		showReportMap := make(map[int]bool)
+		disCardMap := make(map[int]bool)
+		for _, reportId := range reportIdsSalePackage {
+			showReportMap[reportId] = true
+		}
+		for _, reportId := range reportIdsSaleProduct {
+			showReportMap[reportId] = true
+		}
+		var filterDisCardReportIds []int
+		for _, id := range disCardMediaIds {
+			if _, ok := showReportMap[id]; !ok {
+				filterDisCardReportIds = append(filterDisCardReportIds, id)
+				disCardMap[id] = true
+			}
+		}
+		disCardMediaIds = filterDisCardReportIds
+		for _, id := range meidaIds {
+			if _, ok := disCardMap[id]; !ok {
+				filterIds = append(filterIds, id)
+			}
+		}
+	} else {
+		filterIds = meidaIds
+	}
+	if len(filterIds) > 0 {
+		sort.Slice(filterIds, func(i, j int) bool {
+			return filterIds[i] > filterIds[j]
+		})
+		return int64(len(filterIds)), int64(filterIds[0]), filterIds
+	}
+	return 0, 0, []int{}
 }
 func GetTotalPageCount(mediaType string) (count int64, latestId int64) {
 	return mediaDao.GetCountByMediaType(mediaType)
@@ -247,6 +334,7 @@ func convertMediaDTO(media mediaDao.Media, fullTime bool) MediaDTO {
 			MediaName:             media.MediaName,
 			SourceType:            media.SourceType,
 			CoverSrc:              media.CoverSrc,
+			CoverUrl:              media.CoverSrc,
 			MediaPlayMilliseconds: media.MediaPlayMilliseconds,
 			PermissionIDs:         media.PermissionIDs,
 			PublishedTime:         media.PublishedTime.Format(time.DateTime),
@@ -261,6 +349,7 @@ func convertMediaDTO(media mediaDao.Media, fullTime bool) MediaDTO {
 			MediaName:             media.MediaName,
 			SourceType:            media.SourceType,
 			CoverSrc:              media.CoverSrc,
+			CoverUrl:              media.CoverSrc,
 			MediaPlayMilliseconds: media.MediaPlayMilliseconds,
 			PermissionIDs:         media.PermissionIDs,
 			PublishedTime:         media.PublishedTime.Format(time.DateOnly),
@@ -276,10 +365,10 @@ func GetMediaById(mediaType string, mediaId int) (mediaDTO MediaDTO, err error)
 	mediaDTO = convertMediaDTO(media, true)
 	return
 }
-func GetPermissionsByIds(ids []int) (permissionDTOs []configService.PermissionDTO, err error) {
+func GetPermissionsByIds(ids []int) (permissionDTOs []configDomain.PermissionDTO, err error) {
 	return reportService.GetFirstPermissionsByIds(ids)
 }
-func GetSecondPermissionsByIds(ids []int) (permissionDTOs []configService.PermissionDTO, err error) {
+func GetSecondPermissionsByIds(ids []int) (permissionDTOs []configDomain.PermissionDTO, err error) {
 	return reportService.GetPermissionsByPermissionIds(ids)
 }
 func matchAllByCondition(sorts []string, key string, column string, value string) (request *es.ESQueryRequest) {
@@ -303,6 +392,47 @@ func matchRangeWithDocIds(key string, from int, to int, max int64, sorts []strin
 	//return req.CreateESQueryRequest(htConfig.GetMediaIndex(), ESColumn, key, from, to, sorts, es.RangeByCondition).Range(0, max, ESRangeColumn).ByCondition(column, value)
 	return req.CreateESQueryRequest(htConfig.GetMediaIndex(), ESColumn, key, from, to, sorts, es.Range).Range(0, max, ESRangeColumn).WithDocs(docIds)
 }
+func matchLimitByScore(key string, limit int, score float64, docIds []string) (request *es.ESQueryRequest) {
+	req := new(es.ESQueryRequest)
+	return req.CreateESQueryRequest(htConfig.GetMediaIndex(), ESColumn, key, 0, limit, sortField, es.LimitByScore).WithScore(score).WithDocs(docIds)
+}
 func GetImageSrc(id int) (src string, err error) {
-	return mediaDao.GetImageSrc(id)
+	return image.GetImageSrc(id)
+}
+
+func SearchMediaProduct(key string, limit int, score float64, docIds []int) (medias []MediaDTO, err error) {
+	var docStrIds []string
+	for _, docId := range docIds {
+		docStrIds = append(docStrIds, strconv.Itoa(docId))
+	}
+	request := matchLimitByScore(key, limit, score, docStrIds)
+	re, err := elastic().Search(request)
+	if err != nil {
+		logger.Error("es搜索异常:%v", err)
+	}
+	hits := elastic().GetSource(re.Hits)
+	if len(hits) == 0 {
+		medias = []MediaDTO{}
+		return
+	}
+	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.Score = hit.Score
+		media.Highlight = content[ESColumn]
+		media.PublishedTime = media.PublishedTime[:10]
+		media.MediaTitle = media.Highlight[0]
+		medias = append(medias, media)
+	}
+	return
+}
+
+func CountPermissionWeight(ids []int) (list []configDao.PermissionWeight, err error) {
+	return mediaDao.CountPermissionWeight(ids)
 }

+ 127 - 5
domian/merchant/merchant_product.go

@@ -3,9 +3,14 @@ package merchant
 import (
 	"errors"
 	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/utils/page"
+	stringUtils "eta/eta_mini_ht_api/common/utils/string"
+	reportDomain "eta/eta_mini_ht_api/domian/report"
 	merchantDao "eta/eta_mini_ht_api/models/merchant"
-	"github.com/shopspring/decimal"
 	"gorm.io/gorm"
+	"strings"
+	"sync/atomic"
+	"time"
 )
 
 var (
@@ -15,19 +20,31 @@ var (
 		merchantDao.Audio:   "单个音频",
 		merchantDao.Package: "套餐",
 	}
+	typeKindTransfer = map[string]merchantDao.MerchantProductType{
+		"report":  merchantDao.Report,
+		"video":   merchantDao.Video,
+		"audio":   merchantDao.Audio,
+		"package": merchantDao.Package,
+	}
 )
 
 type MerchantProductDTO struct {
 	Id          int
 	Title       string
-	CoverSrc    string
+	CoverSrc    int
+	CoverUrl    string
 	Description string
-	Price       decimal.Decimal
+	Price       string
+	ProductTile string
 	RiskLevel   string
 	Type        string
+	TypeCN      string
 	IsPermanent bool
 	ValidDays   int
 	SaleStatus  string
+	SourceId    int
+	CreatedTime time.Time
+	Deleted     bool
 }
 
 func GetMerchantProductById(id int) (productDTO MerchantProductDTO, err error) {
@@ -44,16 +61,24 @@ func GetMerchantProductById(id int) (productDTO MerchantProductDTO, err error) {
 }
 func convertToDTO(product merchantDao.MerchantProduct) MerchantProductDTO {
 	return MerchantProductDTO{
-		Id:          product.ID,
+		Id:          product.Id,
 		Title:       product.Title,
 		CoverSrc:    product.CoverSrc,
+		CoverUrl:    product.CoverUrl,
 		Description: product.Description,
+		ProductTile: product.Title,
 		Price:       product.Price,
 		RiskLevel:   product.RiskLevel,
-		Type:        typeTransfer[product.Type],
+		Type:        string(product.Type),
+		TypeCN:      typeTransfer[product.Type],
 		IsPermanent: product.IsPermanent,
 		ValidDays:   product.ValidDays,
+		SourceId:    product.SourceId,
+		SaleStatus:  string(product.SaleStatus),
+		CreatedTime: product.CreatedTime,
+		Deleted:     product.Deleted > 0,
 	}
+
 }
 
 func GetProductBySourceId(id int, productType merchantDao.MerchantProductType) (product MerchantProductDTO, err error) {
@@ -69,3 +94,100 @@ func GetProductBySourceId(id int, productType merchantDao.MerchantProductType) (
 	product = convertToDTO(productDao)
 	return
 }
+
+func GetProductListBySourceIds(ids []int, productType string) (productDTOS []MerchantProductDTO, err error) {
+	productList, err := merchantDao.GetProductListBySourceIds(ids, true, typeKindTransfer[productType])
+	if err != nil {
+		logger.Error("获取商品列表失败[sourceIds:%v,type:%v],err:%v", ids, productType, err)
+		return
+	}
+	for _, product := range productList {
+
+		productDTO := convertToDTO(product)
+		productDTOS = append(productDTOS, productDTO)
+	}
+	return
+}
+
+func GetProductListByProductType(productType string) (list []MerchantProductDTO, err error) {
+	productList, err := merchantDao.GetProductListByProductType(typeKindTransfer[productType], true)
+	for _, product := range productList {
+		productDTO := convertToDTO(product)
+		list = append(list, productDTO)
+	}
+	return
+}
+
+func GetProductPageByProductType(productIds []int, info page.PageInfo) (dtoList []MerchantProductDTO, err error) {
+	offset := page.StartIndex(info.Current, info.PageSize)
+	productList, err := merchantDao.GetProductPageByProductType(productIds, info.LatestId, offset, info.PageSize)
+	if err != nil {
+		logger.Error("获取商品列表失败[productIds:%v,pageInfo:%v],err:%v", productIds, info, err)
+		return
+	}
+	for _, productInfo := range productList {
+		productDTO := convertToDTO(productInfo)
+		dtoList = append(dtoList, productDTO)
+	}
+	return
+}
+
+func GetProductByProductType() (productIdMap map[string][]int, err error) {
+	productIdStrMap, err := merchantDao.GetProductByProductType()
+	if err != nil {
+		logger.Error("根据类型分类获取商品ID失败,err:%v", err)
+		return
+	}
+	productIdMap = make(map[string][]int, len(productIdStrMap))
+	for productType, productIds := range productIdStrMap {
+		idStr := strings.Split(productIds, ",")
+		productIdMap[productType], _ = stringUtils.StringToIntSlice(idStr)
+	}
+	return
+}
+
+func LatestId() (latestId int64) {
+
+	return merchantDao.LatestId()
+}
+
+func ProductListBySort(isSignal bool, list []reportDomain.ProductSearchDTO, weightMap map[int]*atomic.Int32, info page.PageInfo) (productDTOS []MerchantProductDTO, err error) {
+	offset := page.StartIndex(info.Current, info.PageSize)
+	var productList []merchantDao.MerchantProduct
+	if isSignal {
+		var searchList []merchantDao.ProductDTO
+		for _, product := range list {
+			searchList = append(searchList, merchantDao.ProductDTO{
+				SourceId:   product.SourceId,
+				SourceType: product.SourceType,
+				Score:      product.Score,
+			})
+		}
+		productList, err = merchantDao.ProductListBySort(searchList, info.LatestId, offset, info.PageSize)
+		if err != nil {
+			return
+		}
+
+	} else {
+		productList, err = merchantDao.PackageListBySort(weightMap, info.LatestId, offset, info.PageSize)
+		if err != nil {
+			return
+		}
+	}
+	for _, productInfo := range productList {
+		productDTOS = append(productDTOS, convertToDTO(productInfo))
+	}
+	return
+}
+
+func GetOffSaleProducts(productTypeList []string) (dtoList []MerchantProductDTO, err error) {
+	var productTypeQuery []merchantDao.MerchantProductType
+	for _, productType := range productTypeList {
+		productTypeQuery = append(productTypeQuery, typeKindTransfer[productType])
+	}
+	list, err := merchantDao.GetOffSaleProducts(productTypeQuery)
+	for _, product := range list {
+		dtoList = append(dtoList, convertToDTO(product))
+	}
+	return
+}

+ 132 - 0
domian/merchant/user_access_service.go

@@ -0,0 +1,132 @@
+package merchant
+
+import (
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/utils/page"
+	"eta/eta_mini_ht_api/domian/user"
+	"eta/eta_mini_ht_api/models/merchant"
+	subscribeDao "eta/eta_mini_ht_api/models/merchant"
+	"sync"
+	"time"
+)
+
+type UserAccessDTO struct {
+	ID             int    `json:"id"`
+	TemplateUserId int    `json:"templateUserId"`
+	ProductID      int    `json:"productID"`
+	ProductType    string `json:"productType"`
+	BeginDate      string `json:"beginDate"`
+	EndDate        string `json:"endDate"`
+	Status         string `json:"status"`
+}
+
+var (
+	productMap = map[string]merchant.MerchantProductType{
+		"package": merchant.Package,
+		"video":   merchant.Video,
+		"audio":   merchant.Audio,
+		"report":  merchant.Report,
+	}
+)
+
+func convertToUserAccessDTO(userSubscriptionAccessList merchant.UserSubscriptionAccessList) UserAccessDTO {
+	return UserAccessDTO{
+		ID:             userSubscriptionAccessList.ID,
+		TemplateUserId: userSubscriptionAccessList.TemplateUserId,
+		ProductID:      userSubscriptionAccessList.ProductID,
+		ProductType:    string(userSubscriptionAccessList.ProductType),
+		BeginDate:      userSubscriptionAccessList.BeginDate.Format(time.DateOnly),
+		EndDate:        userSubscriptionAccessList.EndDate.Format(time.DateOnly),
+		Status:         string(userSubscriptionAccessList.Status),
+	}
+}
+func OpenProduct(templateUserId int, productOrderNo string, product MerchantProductDTO) error {
+	userAccess := merchant.UserSubscriptionAccessList{
+		TemplateUserId: templateUserId,
+		ProductID:      product.Id,
+		ProductName:    product.Title,
+		ProductType:    productMap[product.Type],
+		BeginDate:      time.Now(),
+		Status:         merchant.SubscribeValid,
+		ProductOrderNo: productOrderNo,
+	}
+	if !product.IsPermanent {
+		userAccess.EndDate = time.Now().Add(time.Duration(product.ValidDays) * 24 * time.Hour)
+	}
+	return merchant.InsertOrUpdateUserSubscribe(userAccess)
+}
+func ExpireProduct(deadLine string) (err error) {
+	total := user.GetNeedExpiredAccessCount(deadLine)
+	if total == 0 {
+		logger.Info("没有需要过期的产品")
+		return
+	}
+	for total > 0 {
+		list, _ := user.GetExpiredAccessList(100, deadLine)
+		if len(list) > 0 {
+			var wg sync.WaitGroup
+			wg.Add(len(list))
+			for _, access := range list {
+				go func(access subscribeDao.UserSubscriptionAccessList) {
+					defer wg.Done()
+					access.Status = subscribeDao.SubscribeExpired
+					expireErr := subscribeDao.ExpireUserAccess(access)
+					if expireErr != nil {
+						logger.Error("更新产品过期状态失败:%v,用户:%d,产品:%d", expireErr, access.TemplateUserId, access.ProductID)
+					}
+				}(access)
+			}
+			wg.Wait()
+		}
+		total = user.GetNeedExpiredAccessCount(deadLine)
+	}
+
+	//if err != nil {
+	//	logger.Error("获取产品订单信息失败:%v", err)
+	//	return
+	//}
+	//product, err := merchantService.GetMerchantProductById(productOrder.ProductID)
+	//if err != nil {
+	//	logger.Error("获取产品信息失败:%v", err)
+	//	return
+	//}
+	//return merchantService.CloseProduct(productOrderNo, product)
+	return
+}
+func CloseProduct(templateUserId int, productOrderNo string, product MerchantProductDTO) error {
+	userAccess := merchant.UserSubscriptionAccessList{
+		TemplateUserId: templateUserId,
+		ProductID:      product.Id,
+		ProductName:    product.Title,
+		ProductType:    productMap[product.Type],
+		Status:         merchant.SubscribeClose,
+		ProductOrderNo: productOrderNo,
+	}
+	return merchant.CloseUserAccess(userAccess)
+}
+
+func SubscribeList(templateUserId int, productType string, pageInfo page.PageInfo) (list []UserAccessDTO, err error) {
+	offset := page.StartIndex(pageInfo.Current, pageInfo.PageSize)
+	var subscribeList []merchant.UserSubscriptionAccessList
+	subscribeList, err = merchant.SubscribeList(templateUserId, productType, pageInfo.LatestId, offset, pageInfo.PageSize)
+	if err != nil {
+		return
+	}
+	for _, userSubscriptionAccessList := range subscribeList {
+		list = append(list, convertToUserAccessDTO(userSubscriptionAccessList))
+	}
+	return
+}
+func GetUserSubscribe(templateUserId int, productId int) (dto UserAccessDTO, err error) {
+	var subscribe merchant.UserSubscriptionAccessList
+	subscribe, err = merchant.GetUserSubscribeById(templateUserId, productId)
+	if err != nil {
+		return
+	}
+	dto = convertToUserAccessDTO(subscribe)
+	return
+}
+
+func GetTotalUserPageCountByProductType(productType string, id int) (int64, int64) {
+	return merchant.GetTotalUserPageCountByProductType(productType, id)
+}

+ 2 - 9
domian/user/meta_info.go → domian/message/meta_info.go

@@ -1,7 +1,7 @@
-package user
+package message
 
 import (
-	userDao "eta/eta_mini_ht_api/models/user"
+	userDao "eta/eta_mini_ht_api/models/message"
 )
 
 type MetaInfoDTO struct {
@@ -13,13 +13,6 @@ type MetaInfoDTO struct {
 	MetaType   string `json:"MetaType,omitempty"`
 }
 
-type MetaData struct {
-	SourceId      int    `json:"reportId"`
-	AuthorId      int    `json:"AuthorId"`
-	AuthorName    string `json:"authorName"`
-	PublishedTime string `json:"publishedTime"`
-}
-
 func CreateMetaInfo(dto MetaInfoDTO) (err error) {
 	return userDao.CreateMetaInfo(convertToMetaInfo(dto))
 }

+ 222 - 65
domian/order/product_order.go

@@ -4,35 +4,55 @@ import (
 	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/domian/merchant"
+	productDao "eta/eta_mini_ht_api/models/merchant"
 	orderDao "eta/eta_mini_ht_api/models/order"
 	"fmt"
 	"math/rand"
+	"sync"
 	"time"
 )
 
 type ProductOrderDTO struct {
-	ID                int
-	OrderID           string
-	UserID            int
-	ProductID         int
-	TotalAmount       string
-	ProductPrice      string
-	ProductName       string
-	PaymentWay        string
-	PaymentTimeRemain int64
-	Status            string
-	CreatedTime       string
+	ID                 int
+	OrderID            string
+	UserID             int
+	TemplateUserID     int
+	RealName           string
+	TradeNo            string
+	AreaCode           string
+	Mobile             string
+	ProductID          int
+	ProductType        string
+	TotalAmount        string
+	ProductPrice       string
+	ProductName        string
+	ProductDescription string
+	PaymentWay         string
+	PaymentTimeRemain  int64
+	Status             string
+	StatusCN           string
+	CreatedTime        string
+	UpdatedTime        string
 }
 
 type ProductOrderDetailDTO struct {
 	ProductOrderDTO
-	TransactionID int
-	PaymentTime   string
-	RefundStatus  string
-	Remark        string
+	ValidDuration    string
+	TransactionID    int
+	PaymentTime      string
+	RefundFinishTime string
+	RefundStatus     string
+	Remark           string
 }
 
 var (
+	typeTransfer = map[productDao.MerchantProductType]string{
+		productDao.Report:  "单篇报告",
+		productDao.Video:   "单个视频",
+		productDao.Audio:   "单个音频",
+		productDao.Package: "套餐",
+	}
 	transProductOrderStatusMap = map[orderDao.OrderStatus]string{
 		orderDao.OrderStatusClosed:  "已关闭",
 		orderDao.OrderStatusPaid:    "已支付",
@@ -46,7 +66,18 @@ var (
 		orderDao.RefundStatusProcessing: "退款中",
 		orderDao.RefundStatusSuccess:    "退款成功",
 	}
-
+	transProductMap = map[string]productDao.MerchantProductType{
+		"report":  productDao.Report,
+		"video":   productDao.Video,
+		"audio":   productDao.Audio,
+		"package": productDao.Package,
+	}
+	productTransMap = map[productDao.MerchantProductType]string{
+		productDao.Package: "package",
+		productDao.Video:   "video",
+		productDao.Audio:   "audio",
+		productDao.Report:  "report",
+	}
 	productOrderStatusMap = map[string]orderDao.OrderStatus{
 		"closed":  orderDao.OrderStatusClosed,
 		"paid":    orderDao.OrderStatusPaid,
@@ -65,64 +96,128 @@ func GenerateProductOrderNo() string {
 	return orderNumber
 }
 
-func CreateProductOrder(orderDTO ProductOrderDTO) (orderNo string, err error) {
+func CreateProductOrder(orderDTO ProductOrderDTO) (orderDBDTO ProductOrderDTO, err error) {
 	order := convertProductOrder(orderDTO)
-	orderNo, err = orderDao.CreateProductOrder(order)
+	if order.ProductType == "" {
+		err = exception.New(exception.IllegalProductType)
+		return
+	}
+	order, err = orderDao.CreateProductOrder(order)
+	orderDBDTO = ConvertProductOrderDTO(order)
 	return
 }
 
-func convertProductOrderDTO(order orderDao.ProductOrder) (orderDTO ProductOrderDTO) {
+func ConvertProductOrderDTO(order orderDao.ProductOrder) (orderDTO ProductOrderDTO) {
 	orderDTO = ProductOrderDTO{
-		ID:          order.ID,
-		OrderID:     order.OrderID,
-		UserID:      order.UserID,
-		ProductID:   order.ProductID,
-		TotalAmount: order.TotalAmount,
-		PaymentWay:  string(order.PaymentWay),
-		Status:      transProductOrderStatusMap[order.Status],
-		CreatedTime: order.CreatedTime.Format(time.DateTime),
+		ID:             order.ID,
+		OrderID:        order.OrderID,
+		UserID:         order.UserID,
+		TemplateUserID: order.TemplateUserID,
+		ProductID:      order.ProductID,
+		TradeNo:        order.TradeNO,
+		TotalAmount:    order.TotalAmount,
+		PaymentWay:     string(order.PaymentWay),
+		Status:         string(order.Status),
+		StatusCN:       transProductOrderStatusMap[order.Status],
+		CreatedTime:    order.CreatedTime.Format(time.DateTime),
 	}
 	if order.Status == orderDao.OrderStatusRefund {
-		orderDTO.Status = transRefundStatusMap[order.RefundStatus]
+		orderDTO.Status = string(order.RefundStatus)
+		orderDTO.StatusCN = transRefundStatusMap[order.RefundStatus]
 	}
 	if order.Status == orderDao.OrderStatusPending {
-		duration := time.Now().Sub(order.CreatedTime)
+		duration := time.Now().Sub(order.ExpiredTime)
 		timeout := duration - 15*time.Minute
 		if timeout > 0 {
 			logger.Info("订单已超时:%v", order.OrderID)
-			orderDTO.Status = transProductOrderStatusMap[orderDao.OrderStatusClosed]
+			orderDTO.Status = string(orderDao.OrderStatusClosed)
+			orderDTO.StatusCN = transProductOrderStatusMap[orderDao.OrderStatusClosed]
 		} else {
 			orderDTO.PaymentTimeRemain = int64(duration.Seconds())
 		}
 	}
 	return
 }
-func convertProductOrderDetailDTO(order orderDao.ProductOrder) ProductOrderDetailDTO {
-	return ProductOrderDetailDTO{
+func convertProductOrderDetailDTO(order orderDao.ProductOrder) (orderDetailDTO ProductOrderDetailDTO) {
+	orderDetailDTO = ProductOrderDetailDTO{
 		ProductOrderDTO: ProductOrderDTO{
-			ID:          order.ID,
-			OrderID:     order.OrderID,
-			UserID:      order.UserID,
-			ProductID:   order.ProductID,
-			TotalAmount: order.TotalAmount,
-			PaymentWay:  string(order.PaymentWay),
-			Status:      transProductOrderStatusMap[order.Status],
-			CreatedTime: order.CreatedTime.Format(time.DateTime),
+			ID:             order.ID,
+			OrderID:        order.OrderID,
+			UserID:         order.UserID,
+			TemplateUserID: order.TemplateUserID,
+			ProductID:      order.ProductID,
+			ProductType:    typeTransfer[order.ProductType],
+			TotalAmount:    order.TotalAmount,
+			PaymentWay:     string(order.PaymentWay),
+			Status:         string(order.Status),
+			StatusCN:       transProductOrderStatusMap[order.Status],
+			CreatedTime:    order.CreatedTime.Format(time.DateTime),
+			Mobile:         order.Mobile,
+			RealName:       order.RealName,
 		},
-		TransactionID: order.TransactionID,
-		PaymentTime:   order.PaymentTime.Format(time.DateTime),
+		TransactionID: order.TradeId,
 		RefundStatus:  string(order.RefundStatus),
 		Remark:        order.Remark,
 	}
+	if order.Status == orderDao.OrderStatusRefund {
+		if order.RefundStatus == orderDao.RefundStatusSuccess {
+			orderDetailDTO.RefundFinishTime = order.RefundFinishTime.Format(time.DateTime)
+		}
+		orderDetailDTO.StatusCN = transRefundStatusMap[order.RefundStatus]
+	}
+	if order.Status == orderDao.OrderStatusPending {
+		duration := time.Now().Sub(order.ExpiredTime)
+		timeout := duration - 15*time.Minute
+		if timeout > 0 {
+			logger.Info("订单已超时:%v", order.OrderID)
+			orderDetailDTO.Status = string(orderDao.OrderStatusClosed)
+			orderDetailDTO.StatusCN = transProductOrderStatusMap[orderDao.OrderStatusClosed]
+		} else {
+			orderDetailDTO.PaymentTimeRemain = int64(duration.Seconds())
+			product, err := productDao.GetMerchantProductById(order.ProductID)
+			if err != nil {
+				logger.Error("获取产品信息失败:%v,productId:%d", err, order.ProductID)
+			}
+			if product.Type != productDao.Package {
+				orderDetailDTO.ValidDuration = "永久有效"
+			} else {
+				beginDate := time.Now()
+				endDate := time.Now().Add(time.Duration(product.ValidDays * 24))
+				orderDetailDTO.ValidDuration = fmt.Sprintf("%s~%s", beginDate.Format(time.DateOnly), endDate.Format(time.DateOnly))
+			}
+		}
+	}
+	if order.Status == orderDao.OrderStatusPaid {
+		access, err := merchant.GetUserSubscribe(order.TemplateUserID, order.ProductID)
+		if err != nil {
+			logger.Error("获取用户订阅信息失败:%v,templateUserId:%d,productId :%d", err, order.TemplateUserID, order.ProductID)
+		} else {
+			if access.ProductType == productTransMap[productDao.Package] {
+				orderDetailDTO.ValidDuration = "永久有效"
+			} else {
+				orderDetailDTO.ValidDuration = fmt.Sprintf("%s~%s", access.BeginDate, access.EndDate)
+			}
+		}
+	}
+	if order.TradeNO != "" {
+		orderDetailDTO.PaymentTime = order.PaymentTime.Format(time.DateTime)
+	}
+	return
 }
 
 func convertProductOrder(order ProductOrderDTO) orderDao.ProductOrder {
 	return orderDao.ProductOrder{
-		ID:          order.ID,
-		OrderID:     order.OrderID,
-		UserID:      order.UserID,
-		ProductID:   order.ProductID,
-		TotalAmount: order.TotalAmount,
+		ID:             order.ID,
+		OrderID:        order.OrderID,
+		UserID:         order.UserID,
+		TemplateUserID: order.TemplateUserID,
+		RealName:       order.RealName,
+		AreaCode:       order.AreaCode,
+		Mobile:         order.Mobile,
+		ProductID:      order.ProductID,
+		ProductType:    transProductMap[order.ProductType],
+		ProductName:    order.ProductName,
+		TotalAmount:    order.TotalAmount,
 	}
 }
 
@@ -154,35 +249,97 @@ func GetOrderPage(pageInfo page.PageInfo, userId int, orderStatus string) (order
 		return
 	}
 	for _, order := range orderList {
-		orderDTOS = append(orderDTOS, convertProductOrderDTO(order))
+		orderDTOS = append(orderDTOS, ConvertProductOrderDTO(order))
 	}
 	return
 }
 
-func GetOrderDetail(orderId int, userId int) (orderDTO ProductOrderDetailDTO, err error) {
+func GetUserOrderByProduct(productId int, userId int, status string) (orderDTO ProductOrderDTO, err error) {
+	order, err := orderDao.GetUserOrderByProduct(productId, userId, productOrderStatusMap[status])
+	if err != nil {
+		logger.Error("查询订单详情失败:%v", err)
+		return
+	}
+	product, err := productDao.GetMerchantProductById(order.ProductID)
+	if err != nil {
+		logger.Error("获取产品信息失败:%v", err)
+		return
+	}
+	orderDTO = ConvertProductOrderDTO(order)
+	orderDTO.ProductName = product.Title
+	orderDTO.ProductDescription = product.Description
+	return
+}
+func GetOrderDetail(orderId string, userId int) (orderDTO ProductOrderDetailDTO, err error) {
 	order, err := orderDao.GetOrderDetail(orderId, userId)
 	if err != nil {
 		logger.Error("查询订单详情失败:%v", err)
 		return
 	}
+	product, err := productDao.GetMerchantProductById(order.ProductID)
+	if err != nil {
+		logger.Error("获取产品信息失败:%v", err)
+		return
+	}
 	orderDTO = convertProductOrderDetailDTO(order)
+	orderDTO.ProductName = product.Title
+	orderDTO.ProductDescription = product.Description
 	return
 }
-
 func BatchCloseOrder() (err error) {
-	//total, minId := orderDao.GetUnPendingOrderCount()
-	//if total > 500 {
-	//	chunk := (total + 99) / 100
-	//	var wg sync.WaitGroup
-	//	wg.Add(chunk)
-	//	for i := 0; i < chunk; i++ {
-	//		go func() {
-	//			defer wg.Done()
-	//			var ids []int
-	//			ids, minId = orderDao.GetPendingOrderIds(minId, 500)
-	//			err = orderDao.batchCloseOrder(ids)
-	//		}()
-	//	}
-	//}
-	return nil
+	var total int
+	var minId int
+	total, minId = orderDao.GetUnPendingOrderCount()
+	var ids []int
+	for total > 0 {
+		ids = orderDao.GetExpiredPendingOrderIds(minId, 500)
+		var wg sync.WaitGroup
+		chunks := chunkSlice(ids, 100)
+		wg.Add(len(chunks))
+		for _, chunk := range chunks {
+			go func(ids []int) {
+				defer wg.Done()
+				err = orderDao.BatchCloseOrder(ids)
+			}(chunk)
+		}
+		wg.Wait()
+		total, minId = orderDao.GetUnPendingOrderCount()
+	}
+	return
+}
+
+// ChunkSlice 将切片分块
+func chunkSlice[T any](slice []T, chunkSize int) [][]T {
+	if chunkSize <= 0 {
+		return nil
+	}
+
+	var chunks [][]T
+	n := len(slice)
+	for i := 0; i < n; i += chunkSize {
+		end := i + chunkSize
+		if end > n {
+			end = n
+		}
+		chunks = append(chunks, slice[i:end])
+	}
+	return chunks
+}
+
+func GetOrderByUser(templateUserId int, orderNo string) (orderDTO ProductOrderDTO, err error) {
+	var order orderDao.ProductOrder
+	order, err = orderDao.GetOrderByUser(templateUserId, orderNo)
+	product, err := productDao.GetMerchantProductById(order.ProductID)
+	if err != nil {
+		logger.Error("获取产品信息失败:%v", err)
+		return
+	}
+	orderDTO = ConvertProductOrderDTO(order)
+	orderDTO.ProductName = product.Title
+	orderDTO.ProductDescription = product.Description
+	return
+}
+
+func CloseProductOrder(templateUserId int, productOrderNo string) (err error) {
+	return orderDao.CloseProductOrder(templateUserId, productOrderNo)
 }

+ 251 - 0
domian/order/trade_order.go

@@ -0,0 +1,251 @@
+package order
+
+import (
+	"encoding/json"
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/models"
+	merchantDao "eta/eta_mini_ht_api/models/merchant"
+	"eta/eta_mini_ht_api/models/message"
+	"eta/eta_mini_ht_api/models/order"
+	"fmt"
+	"math/rand"
+	"strconv"
+	"time"
+)
+
+const (
+	RMB                      = "CNY"
+	AliPayWay     PaymentWay = "alipay"
+	WechatPay     PaymentWay = "wechat"
+	RefundSuccess            = "success"
+	RefundFail               = "fail"
+	PaySuccess               = "success"
+	PayFail                  = "fail"
+)
+
+type PaymentWay string
+
+type TradeOrderDTO struct {
+	ID             int       `gorm:"column:id;primaryKey"`
+	TransactionID  string    `gorm:"column:transaction_id;type:varchar(255);comment:第三方平台ID"`
+	ProductOrderID string    `gorm:"column:product_order_id;type:varchar(255);comment:商品订单号"`
+	PaymentAccount string    `gorm:"column:payment_account;type:varchar(255);comment:支付账号"`
+	PaymentWay     string    `gorm:"column:payment_way;type:enum('wechat');comment:支付渠道"`
+	Amount         string    `gorm:"column:amount;type:varchar(20);comment:支付金额"`
+	Currency       string    `gorm:"column:currency;type:varchar(255);comment:货币"`
+	MerchantID     string    `gorm:"column:merchant_id;type:int(11);comment:商户id"`
+	UserID         int       `gorm:"column:user_id;type:int(11);comment:用户id"`
+	TemplateUserID int       `gorm:"column:template_user_id;type:int(11);comment:临时用户id"`
+	PaymentType    string    `gorm:"column:payment_type;type:enum('pay','refund');comment:订单类型"`
+	PaymentStatus  string    `gorm:"column:payment_status;type:enum('pending','processing','done','failed');comment:支付状态"`
+	DealTime       time.Time `gorm:"column:deal_time;type:datetime;comment:完成时间"`
+}
+
+func GenerateTradeOrderNo() string {
+	timestamp := time.Now().UnixNano() / 1000000 // 毫秒级时间戳
+	// 生成随机数
+	rand.New(rand.NewSource(time.Now().UnixNano()))
+	randomPart := rand.Intn(999999)
+	// 格式化订单号
+	orderNumber := fmt.Sprintf("T%d%06d", timestamp, randomPart)
+	return orderNumber
+}
+
+func CreateTradeOrder(userId, templateUserId int, productOrderNo, tradeOrderNo, merchantNo string) (err error) {
+	db := models.Main()
+	productOrder, err := order.GetOrderByUser(templateUserId, productOrderNo)
+	if err != nil {
+		logger.Error("获取商品订单信息失败%v", err)
+		return
+	}
+	tx := db.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+			return
+		}
+		tx.Commit()
+	}()
+	tradeOrder := order.TradeOrder{
+		ProductOrderID: productOrderNo,
+		ProductName:    productOrder.ProductName,
+		TransactionID:  tradeOrderNo,
+		Amount:         productOrder.TotalAmount,
+		Mobile:         productOrder.Mobile,
+		AreaCode:       productOrder.AreaCode,
+		RealName:       productOrder.RealName,
+		PaymentWay:     order.WechatPayWay,
+		Currency:       RMB,
+		MerchantID:     merchantNo,
+		UserID:         userId,
+		TemplateUserID: templateUserId,
+		PaymentType:    order.PaymentTypePay,
+	}
+	err = tx.Create(&tradeOrder).Error
+	if err != nil {
+		logger.Error("创建支付订单失败%v", err)
+		return
+	}
+	err = tx.Model(&order.ProductOrder{}).Where("id = ?", productOrder.ID).Updates(map[string]interface{}{
+		"trade_id":       tradeOrder.ID,
+		"trade_no":       tradeOrder.TransactionID,
+		"payment_time":   time.Now(),
+		"payment_amount": tradeOrder.Amount,
+		"payment_way":    tradeOrder.PaymentWay,
+	}).Error
+	return
+}
+func convertToDTO(tradeOrder order.TradeOrder) TradeOrderDTO {
+	return TradeOrderDTO{
+		ID:             tradeOrder.ID,
+		TransactionID:  tradeOrder.TransactionID,
+		ProductOrderID: tradeOrder.ProductOrderID,
+		PaymentAccount: tradeOrder.PaymentAccount,
+		PaymentWay:     string(tradeOrder.PaymentWay),
+		Amount:         tradeOrder.Amount,
+		Currency:       tradeOrder.Currency,
+		MerchantID:     tradeOrder.MerchantID,
+		UserID:         tradeOrder.UserID,
+		TemplateUserID: tradeOrder.UserID,
+		PaymentType:    string(tradeOrder.PaymentType),
+		PaymentStatus:  string(tradeOrder.PaymentStatus),
+		DealTime:       tradeOrder.DealTime,
+	}
+}
+func GetUnFailedTradeFlowByProductOrder(productOrderNo string) (dtoList []TradeOrderDTO, err error) {
+	var tradeOrderList []order.TradeOrder
+	tradeOrderList, err = order.GetUnFailedTradeFlowByProductOrder(productOrderNo)
+	for _, tradeOrder := range tradeOrderList {
+		dtoList = append(dtoList, convertToDTO(tradeOrder))
+	}
+	return
+}
+func GetTradeOrderByNo(tradeOrderNo string) (dto TradeOrderDTO, err error) {
+	var tradeOrder order.TradeOrder
+	tradeOrder, err = order.GetTradeOrderByNo(tradeOrderNo, order.PaymentTypePay)
+	dto = convertToDTO(tradeOrder)
+	return
+}
+
+type RefundDealFlowDTO struct {
+	OperatorUserID int    `gorm:"column:operator_user_id"`
+	ProductOrderNo string `gorm:"column:product_order_no"`
+	RefundOrderNo  string `gorm:"column:refund_order_no"`
+}
+
+func convertRefundDTO(flow order.RefundDealFlow) RefundDealFlowDTO {
+	return RefundDealFlowDTO{
+		OperatorUserID: flow.OperatorUserID,
+		ProductOrderNo: flow.ProductOrderNo,
+		RefundOrderNo:  flow.RefundOrderNo,
+	}
+
+}
+
+func createRefundMetaInfo(sysUserId int, productOrderNo string, flag string) (err error) {
+	productOrder, err := order.GetOrderByOrderNo(productOrderNo)
+	if err != nil {
+		logger.Error("生成退款消息通知失败,获取订单信息失败:v,订单编号:%s", err, productOrderNo)
+		return
+	}
+	var result string
+	switch flag {
+	case RefundSuccess:
+		result = "成功"
+	case RefundFail:
+		result = "失败"
+	default:
+		logger.Error("生成退款消息通知失败,未知的退款状态%s,订单编号:%s", flag, productOrderNo)
+		return
+	}
+	refundInfo := message.RefundMetaData{
+		RealName:       productOrder.RealName,
+		ProductOrderNo: productOrderNo,
+		Result:         result,
+	}
+
+	bytes, err := json.Marshal(refundInfo)
+	if err != nil {
+		logger.Error("生成退款消息通知失败,序列化退款信息失败:%v,订单编号:%s", err, productOrderNo)
+		return
+	}
+	metaInfo := message.MetaInfo{
+		Meta:       string(bytes),
+		MetaType:   message.SysUserNoticeType,
+		SourceType: message.RefundSourceType,
+		Status:     message.InitStatusType,
+		From:       "HTTradePlate",
+		To:         strconv.Itoa(sysUserId),
+	}
+	return message.CreateMetaInfo(metaInfo)
+}
+
+func DealRefund(refundOrderNo string, flag string) (productOrderDTO ProductOrderDTO, err error) {
+	flow, err := order.GetRefundFlow(refundOrderNo)
+	if err != nil {
+		logger.Error("获取退款流水失败%v,退款订单:%s", err, refundOrderNo)
+		return
+	}
+	//处理退款订单
+	tradeOrder, err := order.GetTradeOrderByNo(flow.RefundOrderNo, order.PaymentTypeRefund)
+	if err != nil {
+		logger.Error("获取退款订单失败%v,退款订单:%s", err, refundOrderNo)
+		return
+	}
+	isSuccess := false
+	if flag == RefundSuccess {
+		isSuccess = true
+	}
+	err = order.DealRefundOrder(tradeOrder, isSuccess)
+	if err != nil {
+		logger.Error("处理退款结果失败%v,退款订单:%s", err, refundOrderNo)
+		return
+	}
+	var productOrder order.ProductOrder
+	productOrder, err = order.GetOrderByOrderNo(tradeOrder.ProductOrderID)
+	if err != nil {
+		logger.Error("获取产品订单失败%v,退款订单:%s", err, refundOrderNo)
+		return
+	}
+	productOrderDTO = ConvertProductOrderDTO(productOrder)
+	_ = createRefundMetaInfo(flow.OperatorUserID, flow.ProductOrderNo, flag)
+	return
+}
+func DealPayment(tradeOrderNo string, flag string) (productOrderDTO ProductOrderDTO, err error) {
+	//处理退款订单
+	tradeOrder, err := order.GetTradeOrderByNo(tradeOrderNo, order.PaymentTypePay)
+	if err != nil {
+		logger.Error("获取支付订单失败%v,支付订单:%s", err, tradeOrderNo)
+		return
+	}
+	isSuccess := false
+	if flag == PaySuccess {
+		isSuccess = true
+	}
+	var productOrder order.ProductOrder
+	productOrder, err = order.GetOrderByOrderNo(tradeOrder.ProductOrderID)
+	if err != nil {
+		logger.Error("获取产品订单失败%v,支付订单:%s", err, tradeOrderNo)
+		return
+	}
+	product, err := merchantDao.GetMerchantProductById(productOrder.ProductID)
+	if err != nil {
+		logger.Error("获取产品信息失败:%v,productId:%d", err, productOrder.ProductID)
+	}
+	var validDuration string
+	if product.Type == merchantDao.Package {
+		beginDate := time.Now().Format(time.DateOnly)
+		endDate := time.Now().Add(time.Duration(product.ValidDays * 24)).Format(time.DateOnly)
+		validDuration = fmt.Sprintf("%s~%s", beginDate, endDate)
+	} else {
+		validDuration = "永久有效"
+	}
+	err = order.DealPaymentOrder(tradeOrder, isSuccess, validDuration)
+	if err != nil {
+		logger.Error("处理支付结果失败%v,支付订单:%s", err, tradeOrderNo)
+		return
+	}
+
+	productOrderDTO = ConvertProductOrderDTO(productOrder)
+	return
+}

+ 3 - 48
domian/report/eta_report_service.go

@@ -3,7 +3,6 @@ package report
 import (
 	logger "eta/eta_mini_ht_api/common/component/log"
 	"eta/eta_mini_ht_api/models/eta"
-	productDao "eta/eta_mini_ht_api/models/merchant"
 	reportDao "eta/eta_mini_ht_api/models/report"
 	"fmt"
 	"html"
@@ -55,7 +54,7 @@ func GetETAReportIdsByPermissionIds(permissionIds []int) (ids []int, err error)
 	}
 	return reportDao.GetETAReportIdsByClassifyIds(classifyIds)
 }
-func GetETAReportIdsByPermissionIdsWithRiskLevel(permissionIds []int, riskLevel string) (orgIds []int, err error) {
+func GetETAReportIdsByPermissionIdsWithRiskLevel(permissionIds []int) (orgIds []int, err error) {
 	classifyIds, err := GetClassifyIdsByPermissionIds(permissionIds)
 	if err != nil {
 		logger.Error("品种获取研报分类列表失败:%v", err)
@@ -69,53 +68,9 @@ func GetETAReportIdsByPermissionIdsWithRiskLevel(permissionIds []int, riskLevel
 		logger.Error("获取ETA研报列表失败:%v", err)
 		return
 	}
-	if riskLevel == "" {
-		for _, report := range reports {
-			orgIds = append(orgIds, report.OrgID)
-		}
-		return
-	}
-	//根据riskLevel过滤ids
-	var ids []int
-	for _, report := range reports {
-		ids = append(ids, report.ID)
-	}
-	var productList []productDao.MerchantProduct
-	//现加入没有产品的报告
-	productList, err = productDao.GetProductListBySourceIds(ids, productDao.Report)
-	for _, report := range reports {
-		find := false
-		for _, product := range productList {
-			if product.SourceID == report.ID {
-				find = true
-				break
-			}
-		}
-		if !find {
-			orgIds = append(orgIds, report.OrgID)
-		}
-
-	}
-	riskNum, err := parseRiskLevel(riskLevel)
-	if err != nil {
-		logger.Error("解析风险等级失败:%v", err)
-		return
-	}
-	//再把符合风险等级的产品报告加入
-	//productList, err = productDao.GetProductListBySourceIds(ids, productDao.Report)
+	//if riskLevel == "" {
 	for _, report := range reports {
-		for _, product := range productList {
-			if product.SourceID == report.ID {
-				pdRiskNum, pdErr := parseRiskLevel(product.RiskLevel)
-				if pdErr != nil {
-					logger.Error("解析产品风险等级失败:%v,产品id:%v", pdErr, product.ID)
-					continue
-				}
-				if pdRiskNum <= riskNum {
-					orgIds = append(orgIds, report.OrgID)
-				}
-			}
-		}
+		orgIds = append(orgIds, report.OrgID)
 	}
 	return
 }

+ 2 - 82
domian/report/ht_report_service.go

@@ -3,25 +3,10 @@ package report
 import (
 	logger "eta/eta_mini_ht_api/common/component/log"
 	"eta/eta_mini_ht_api/models/ht"
-	productDao "eta/eta_mini_ht_api/models/merchant"
 	reportDao "eta/eta_mini_ht_api/models/report"
-	"fmt"
-	"strconv"
-	"strings"
 )
 
-func GetHTReportIdsByPermissionIds(permissionIds []int) (ids []int, err error) {
-	//品种名字筛选
-	permissionNames, err := GetPermissionNamesByPermissionIds(permissionIds)
-	if err != nil {
-		logger.Error("获取品种名称列表失败:%v", err)
-		return
-	}
-	//更具query查询ids TODO
-	ids, err = reportDao.GetIdsByPlateNames(permissionNames)
-	return
-}
-func GetHTReportIdsByPermissionIdsWithRiskLevel(permissionIds []int, riskLevel string) (orgIds []int, err error) {
+func GetHTReportIdsByPermissionIdsWithRiskLevel(permissionIds []int) (orgIds []int, err error) {
 	//品种名字筛选
 	var permissionNames []string
 	permissionNames, err = GetPermissionNamesByPermissionIds(permissionIds)
@@ -30,73 +15,8 @@ func GetHTReportIdsByPermissionIdsWithRiskLevel(permissionIds []int, riskLevel s
 		return
 	}
 	//更具query查询ids
-	var reports []reportDao.Report
-	reports, err = reportDao.GetReportByPlateNames(permissionNames)
-	if err != nil {
-		logger.Error("获取HT研报列表失败:%v", err)
-		return
-	}
-	if riskLevel == "" {
-		for _, report := range reports {
-			orgIds = append(orgIds, report.OrgID)
-		}
-		return
-	}
-	//根据riskLevel过滤ids
-	var ids []int
-	for _, report := range reports {
-		ids = append(ids, report.ID)
-	}
-	var productList []productDao.MerchantProduct
-	//现加入没有产品的报告
-	productList, err = productDao.GetProductListBySourceIds(ids, productDao.Report)
-	for _, report := range reports {
-		find := false
-		for _, product := range productList {
-			if product.SourceID == report.ID {
-				find = true
-			}
-		}
-		if !find {
-			orgIds = append(orgIds, report.OrgID)
-		}
-	}
-	riskNum, err := parseRiskLevel(riskLevel)
-	if err != nil {
-		logger.Error("解析风险等级失败:%v", err)
-		return
-	}
-	//再把符合风险等级的产品报告加入
-	//productList, err = productDao.GetProductListBySourceIds(ids, productDao.Report)
-	for _, report := range reports {
-		for _, product := range productList {
-			if product.SourceID == report.ID {
-				pdRiskNum, pdErr := parseRiskLevel(product.RiskLevel)
-				if pdErr != nil {
-					logger.Error("解析产品风险等级失败:%v,产品id:%v", pdErr, product.ID)
-					continue
-				}
-				if pdRiskNum <= riskNum {
-					orgIds = append(orgIds, report.OrgID)
-				}
-			}
-		}
-	}
-	return
+	return reportDao.GetOrgIdsByPlateNames(permissionNames)
 }
 func GetHtReport(id int) (pdf string, err error) {
 	return ht.GetPDFUrl(id)
 }
-
-func parseRiskLevel(level string) (int, error) {
-	parts := strings.Split(level, "R")
-	if len(parts) < 2 {
-		return 0, fmt.Errorf("无效的风险等级: %s", level)
-	}
-	numberStr := parts[1]
-	number, err := strconv.Atoi(numberStr)
-	if err != nil {
-		return 0, fmt.Errorf("无法将风险等级转换为数字: %s", err)
-	}
-	return number, nil
-}

+ 0 - 1
domian/report/permission_service.go

@@ -12,7 +12,6 @@ func GetClassifyIdsByPermissionIds(permissionIds []int) (classifyIds []int, err
 
 func GetPermissionNamesByPermissionIds(permissionIds []int) (permissionNames []string, err error) {
 	return permissionDao.GetPermissionNamesByPermissionIds(permissionIds)
-	//return etaDao.GetPermissionNamesByPermissionIds(permissionIds)
 }
 func GetPermissionsByPermissionIds(permissionIds []int) (permissionDTOs []configService.PermissionDTO, err error) {
 	permission, err := permissionDao.GetPermissionsByPermissionIds(permissionIds)

+ 315 - 114
domian/report/report_service.go

@@ -10,18 +10,24 @@ import (
 	stringUtils "eta/eta_mini_ht_api/common/utils/string"
 	configService "eta/eta_mini_ht_api/domian/config"
 	analystService "eta/eta_mini_ht_api/domian/financial_analyst"
+	messageDomian "eta/eta_mini_ht_api/domian/message"
 	userService "eta/eta_mini_ht_api/domian/user"
 	"eta/eta_mini_ht_api/models"
+	configDao "eta/eta_mini_ht_api/models/config"
 	permissionDao "eta/eta_mini_ht_api/models/config"
 	"eta/eta_mini_ht_api/models/eta"
 	etaDao "eta/eta_mini_ht_api/models/eta"
 	"eta/eta_mini_ht_api/models/ht"
-	mediaDao "eta/eta_mini_ht_api/models/media"
+	mediaDao "eta/eta_mini_ht_api/models/image"
+	merchantDao "eta/eta_mini_ht_api/models/merchant"
+	"eta/eta_mini_ht_api/models/message"
+	messageDao "eta/eta_mini_ht_api/models/message"
 	reportDao "eta/eta_mini_ht_api/models/report"
 	userDao "eta/eta_mini_ht_api/models/user"
 	"math/rand"
 	"strconv"
 	"strings"
+	"sync"
 	"time"
 )
 
@@ -58,28 +64,37 @@ type ESReport struct {
 }
 
 type ReportDTO struct {
-	ReportID         int             `json:"reportId"`
-	OrgId            int             `json:"orgId"`
-	Title            string          `json:"title"`
-	Author           string          `json:"author"`
-	AuthorInfo       []Anthor        `json:"authorInfo"`
-	Source           string          `json:"source"`
-	Abstract         string          `json:"abstract"`
-	PublishedTime    string          `json:"publishedTime"`
-	RiskLevel        string          `json:"riskLevel"`
-	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"`
-	Price            string          `json:"price"`
+	Type             string         `json:"type"`
+	ReportID         int            `json:"reportId"`
+	OrgId            int            `json:"orgId"`
+	Title            string         `json:"title"`
+	Author           string         `json:"author"`
+	AuthorInfo       []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:"-"`
 }
 
 type Detail struct {
@@ -89,9 +104,10 @@ type Anthor struct {
 	Id         int    `json:"id"`
 	Name       string `json:"name"`
 	HeadImgUrl string `json:"headImgUrl"`
+	Following  string `json:"following"`
 }
 
-func GetGetReportById(reportId int) (ReportDTO ReportDTO, err error) {
+func GetReportById(reportId int, userId int) (ReportDTO ReportDTO, err error) {
 	report, err := reportDao.GetReportById(reportId)
 	if err != nil {
 		return
@@ -111,12 +127,14 @@ func GetGetReportById(reportId int) (ReportDTO ReportDTO, err error) {
 					Id:         0,
 					Name:       name,
 					HeadImgUrl: "",
+					Following:  string(userDao.Unfollowed),
 				}
 			} else {
 				item = Anthor{
 					Id:         author.Id,
 					Name:       author.Name,
 					HeadImgUrl: author.HeadImgUrl,
+					Following:  userDao.GetFollowing(userId, author.Id),
 				}
 			}
 			authorList = append(authorList, item)
@@ -125,31 +143,41 @@ func GetGetReportById(reportId int) (ReportDTO ReportDTO, err error) {
 	ReportDTO.AuthorInfo = authorList
 	return
 }
-
-func GetTotalPageCount() (total int64, latestId int64, err error) {
-	return reportDao.GetTotalPageCount()
-}
-func GetTotalPageCountByAnalyst(analyst string, permissionIds []int, riskLevel string) (total int64, latestId int64, ids []int) {
+func GetTotalPageCountByAnalyst(analyst string, permissionIds []int) (total int64, latestId int64, ids []int) {
 	ids, err := reportDao.GetReportsByAnalyst(analyst)
 	if err != nil {
 		logger.Error("查询研究研报告列表id失败:%v", err)
 		return
 	}
-	//查询这些包含在列表中的权限的报告ids
-	htOrgIds, err := GetHTReportIdsByPermissionIds(permissionIds)
-	if err != nil {
-		logger.Error("品种筛选ht报告id失败:%v", err)
-		htOrgIds = []int{}
-	}
-	etaOrgIds, err := GetETAReportIdsByPermissionIds(permissionIds)
-	if err != nil {
-		logger.Error("品种筛选eta报告id失败:%v", err)
-		etaOrgIds = []int{}
+	var wg sync.WaitGroup
+	wg.Add(2)
+	var htOrgIds []int
+	var etaOrgIds []int
+	go func() {
+		defer wg.Done()
+		htOrgIds, err = getHtOrgIds(permissionIds)
+		if err != nil {
+			logger.Error("品种筛选ht报告id失败:%v", err)
+		}
+	}()
+	go func() {
+		defer wg.Done()
+		etaOrgIds, err = getEtaOrgIds(permissionIds)
+		if err != nil {
+			logger.Error("品种筛选eta报告id失败:%v", err)
+		}
+	}()
+	wg.Wait()
+	totalCol := int64(len(etaOrgIds) + len(htOrgIds))
+	if totalCol == 0 {
+		latestId = 0
+		return
 	}
 	if len(etaOrgIds) == 0 && len(htOrgIds) == 0 {
 		logger.Info("没有符合权限的研报")
 		return
 	}
+
 	orgIds := make(map[string][]int, 2)
 	if len(etaOrgIds) == 0 {
 		orgIds["ETA"] = []int{}
@@ -161,6 +189,7 @@ func GetTotalPageCountByAnalyst(analyst string, permissionIds []int, riskLevel s
 	} else {
 		orgIds["HT"] = htOrgIds
 	}
+
 	permitReportIds, err := reportDao.GetReportIdListByOrgIds(orgIds)
 	if err != nil {
 		logger.Error("根据原始报告id获取报告id列表失败:%v", err)
@@ -179,6 +208,79 @@ func GetTotalPageCountByAnalyst(analyst string, permissionIds []int, riskLevel s
 		return
 	}
 	ids = filterReportIds
+	//获取一下下架的报告产品
+	var disCardReportIds []int
+	offSaleProducts, err := merchantDao.GetOffSaleProducts([]merchantDao.MerchantProductType{merchantDao.Report, merchantDao.Package})
+	if err != nil {
+		logger.Error("获取下架的报告产品失败:%v", err)
+		return
+	}
+
+	var ProductPermissionIds []int
+	for _, product := range offSaleProducts {
+		if product.Type == "package" {
+			ProductPermissionIds = append(ProductPermissionIds, product.SourceId)
+		}
+		if product.Type == "report" {
+			disCardReportIds = append(disCardReportIds, product.SourceId)
+		}
+	}
+	if len(ProductPermissionIds) > 0 {
+		wg.Add(2)
+		var permissionNames []string
+		var classifyIds []int
+		go func() {
+			defer wg.Done()
+			var permissionErr error
+			permissionNames, permissionErr = GetPermissionNamesByPermissionIds(ProductPermissionIds)
+			if permissionErr != nil {
+				logger.Error("获取ETA品种名称失败:%v", err)
+			}
+		}()
+		go func() {
+			defer wg.Done()
+			var classifyErr error
+			classifyIds, classifyErr = permissionDao.GetClassifyIdsByPermissionIds(ProductPermissionIds)
+			if classifyErr != nil {
+				logger.Error("获取ETA报告分类id失败:%v", err)
+			}
+		}()
+		wg.Wait()
+		disCardIds, _ := reportDao.GetHiddenReportIds(classifyIds, permissionNames)
+		if len(disCardIds) > 0 {
+			disCardReportIds = append(disCardReportIds, disCardIds...)
+		}
+	}
+	//对数据去重
+	disCardReportIds = uniqueArray(disCardReportIds)
+	//获取报告中还包含上架套餐的id
+	if len(disCardReportIds) > 0 {
+		reportIdsSalePackage, _ := merchantDao.GetReportOnSalePackageIds(disCardReportIds)
+		reportIdsSaleProduct, _ := merchantDao.GetOnSaleReportIds(disCardReportIds)
+		showReportMap := make(map[int]bool)
+		disCardMap := make(map[int]bool)
+		for _, reportId := range reportIdsSalePackage {
+			showReportMap[reportId] = true
+		}
+		for _, reportId := range reportIdsSaleProduct {
+			showReportMap[reportId] = true
+		}
+		var filterDisCardReportIds []int
+		for _, id := range disCardReportIds {
+			if _, ok := showReportMap[id]; !ok {
+				filterDisCardReportIds = append(filterDisCardReportIds, id)
+				disCardMap[id] = true
+			}
+		}
+		disCardReportIds = filterDisCardReportIds
+		var cardReportIds []int
+		for _, id := range filterReportIds {
+			if _, ok := disCardMap[id]; !ok {
+				cardReportIds = append(cardReportIds, id)
+			}
+		}
+		filterReportIds = cardReportIds
+	}
 	total = int64(len(filterReportIds))
 	latestId = int64(findMax(filterReportIds))
 	return
@@ -200,34 +302,6 @@ func findMax(nums []int) (max int) {
 	}
 	return
 }
-func SearchMaxReportId(key string) (total int64, reportId int64) {
-	sort := []string{"reportId:desc"}
-	request := matchAll(sort, key)
-	//同步es
-	re, err := elastic().Count(request)
-	if err != nil {
-		logger.Error("es搜索异常:%v", err)
-	}
-	count := re.Count
-	total = int64(count)
-	if total > 0 {
-		request = match(key, 0, count, sort)
-		re, err = elastic().Search(request)
-		if err != nil {
-			logger.Error("es搜索异常:%v", err)
-		}
-		hits := elastic().GetSource(re.Hits)
-		data := hits[0].Source
-		report := ReportDTO{}
-		err = json.Unmarshal(data, &report)
-		if err != nil {
-			logger.Error("获取当前最大研报id失败:%v", err)
-			return
-		}
-		reportId = int64(report.ReportID)
-	}
-	return
-}
 func SearchMaxReportIdWithRange(key string, reportIds []int) (total int64) {
 	sort := []string{"reportId:desc"}
 	var docIds []string
@@ -309,9 +383,9 @@ func GetReportPageByAnalyst(pageInfo page.PageInfo, analyst string, reportIds []
 	}
 	return
 }
-func GetReportPageByOrgIds(pageInfo page.PageInfo, orgIds map[string][]int, searchAll bool) (list []ReportDTO, err error) {
+func GetReportPageByOrgIds(pageInfo page.PageInfo, orgIds map[string][]int, discardIds []int) (list []ReportDTO, err error) {
 	offset := page.StartIndex(pageInfo.Current, pageInfo.PageSize)
-	reports, err := reportDao.GetReportPageByOrgIds(pageInfo.LatestId, pageInfo.PageSize, offset, orgIds, searchAll)
+	reports, err := reportDao.GetReportPageByOrgIds(pageInfo.LatestId, pageInfo.PageSize, offset, orgIds, discardIds)
 	if err != nil {
 		logger.Error("分页查询报告列表失败:%v", err)
 		return
@@ -327,32 +401,6 @@ func GetReportPageByOrgIds(pageInfo page.PageInfo, orgIds map[string][]int, sear
 	return
 }
 
-func GetNewReportByPublishTime(time time.Time) (reports []ReportDTO) {
-	list := reportDao.GetNewReportByPublishTime(time)
-	if list != nil {
-		for _, report := range list {
-			dto := convertReportDTO(report, false)
-			reports = append(reports, dto)
-		}
-	}
-	return
-}
-func GetReportPage(pageInfo page.PageInfo) (list []ReportDTO, err error) {
-	offset := page.StartIndex(pageInfo.Current, pageInfo.PageSize)
-	reports, err := reportDao.GetReportPage(pageInfo.LatestId, pageInfo.PageSize, offset)
-	if err != nil {
-		logger.Error("分页查询报告列表失败:%v", err)
-		return
-	}
-	list = make([]ReportDTO, 0)
-	if reports != nil {
-		for _, report := range reports {
-			dto := convertReportDTO(report, false)
-			list = append(list, dto)
-		}
-	}
-	return
-}
 func getETAReportFirstPermissions(id int) (permissionDTOs []configService.PermissionDTO) {
 	classifyId, err := etaDao.GetReportClassifyById(id)
 	if err != nil || classifyId == 0 {
@@ -360,7 +408,6 @@ func getETAReportFirstPermissions(id int) (permissionDTOs []configService.Permis
 		return
 	}
 	permissions, err := permissionDao.GetFirstPermissionsByClassifyID(classifyId)
-	//permissions, err := etaDao.GetFirstPermissionsByClassifyID(classifyId)
 	if err != nil {
 		logger.Error("获取研报一级品种信息失败:%v", err)
 		return
@@ -575,12 +622,12 @@ func syncESAndSendMessage(reports []reportDao.Report) (err error) {
 			}
 			if report.Status == reportDao.StatusUnPublish {
 				//隐藏热度搜索
-				err = userDao.HiddenFlows(report.ID, userDao.ReportSourceType)
+				err = userDao.HiddenFlows(report.ID, message.ReportSourceType)
 				if err != nil {
 					logger.Error("隐藏热度搜索失败,reportId::%d,err:%v", report.ID, err)
 				}
 			} else {
-				err = userDao.ShowFlows(report.ID, userDao.ReportSourceType)
+				err = userDao.ShowFlows(report.ID, message.ReportSourceType)
 				if err != nil {
 					logger.Error("重置热度搜索失败,reportId::%d,err:%v", report.ID, err)
 				}
@@ -639,7 +686,7 @@ func syncESAndSendMessage(reports []reportDao.Report) (err error) {
 						continue
 					}
 					usersStr := stringUtils.IntToStringSlice(userIds)
-					Meta := userService.MetaData{
+					Meta := messageDao.MetaData{
 						AuthorName:    author.Name,
 						AuthorId:      author.Id,
 						SourceId:      report.ID,
@@ -647,14 +694,14 @@ func syncESAndSendMessage(reports []reportDao.Report) (err error) {
 					}
 					metaStr, _ := json.Marshal(Meta)
 					toStr := strings.Join(usersStr, ",")
-					metaContent := userService.MetaInfoDTO{
+					metaContent := messageDomian.MetaInfoDTO{
 						From:       From,
 						Meta:       string(metaStr),
 						MetaType:   "USER_NOTICE",
 						SourceType: "REPORT",
 						To:         toStr,
 					}
-					err = userService.CreateMetaInfo(metaContent)
+					err = messageDomian.CreateMetaInfo(metaContent)
 					if err != nil {
 						logger.Error("创建Meta信息失败:%v", err)
 						return err
@@ -832,19 +879,35 @@ func GetListByCondition[T any](column string, ids []T) (dtoList []ReportDTO, err
 func GetReportByIdListByOrgIds(orgIds map[string][]int) (ids []int, err error) {
 	return reportDao.GetReportIdListByOrgIds(orgIds)
 }
+func getHtOrgIds(permissionIds []int) (htOrgIds []int, err error) {
+	return GetHTReportIdsByPermissionIdsWithRiskLevel(permissionIds)
+}
 
-func GetTotalPageCountByPermissionIds(permissionIds []int, riskLevel string) (total int64, latestId int64, ids map[string][]int) {
-
-	htOrgIds, err := GetHTReportIdsByPermissionIdsWithRiskLevel(permissionIds, riskLevel)
-	if err != nil {
-		logger.Error("品种筛选ht报告id失败:%v", err)
-		htOrgIds = []int{}
-	}
-	etaOrgIds, err := GetETAReportIdsByPermissionIdsWithRiskLevel(permissionIds, riskLevel)
-	if err != nil {
-		logger.Error("品种筛选eta报告id失败:%v", err)
-		etaOrgIds = []int{}
-	}
+func getEtaOrgIds(permissionIds []int) (htOrgIds []int, err error) {
+	return GetETAReportIdsByPermissionIdsWithRiskLevel(permissionIds)
+}
+func GetTotalPageCountByPermissionIds(permissionIds []int) (total int64, latestId int64, ids map[string][]int, disCardReportIds []int) {
+	var wg sync.WaitGroup
+	wg.Add(2)
+	var htOrgIds []int
+	var etaOrgIds []int
+	go func() {
+		defer wg.Done()
+		var err error
+		htOrgIds, err = getHtOrgIds(permissionIds)
+		if err != nil {
+			logger.Error("品种筛选ht报告id失败:%v", err)
+		}
+	}()
+	go func() {
+		defer wg.Done()
+		var err error
+		etaOrgIds, err = getEtaOrgIds(permissionIds)
+		if err != nil {
+			logger.Error("品种筛选eta报告id失败:%v", err)
+		}
+	}()
+	wg.Wait()
 	totalCol := int64(len(etaOrgIds) + len(htOrgIds))
 	if totalCol == 0 {
 		latestId = 0
@@ -861,14 +924,103 @@ func GetTotalPageCountByPermissionIds(permissionIds []int, riskLevel string) (to
 	} else {
 		ids["HT"] = htOrgIds
 	}
+	//获取一下下架的报告产品
+	offSaleProducts, err := merchantDao.GetOffSaleProducts([]merchantDao.MerchantProductType{merchantDao.Report, merchantDao.Package})
+	if err != nil {
+		logger.Error("获取下架的报告产品失败:%v", err)
+		return
+	}
+	var ProductPermissionIds []int
+	for _, product := range offSaleProducts {
+		if product.Type == "package" {
+			ProductPermissionIds = append(ProductPermissionIds, product.SourceId)
+		}
+		if product.Type == "report" {
+			disCardReportIds = append(disCardReportIds, product.SourceId)
+		}
+	}
+	if len(ProductPermissionIds) > 0 {
+		wg.Add(2)
+		var permissionNames []string
+		var classifyIds []int
+		go func() {
+			defer wg.Done()
+			var permissionErr error
+			permissionNames, permissionErr = GetPermissionNamesByPermissionIds(ProductPermissionIds)
+			if permissionErr != nil {
+				logger.Error("获取ETA品种名称失败:%v", err)
+			}
+		}()
+		go func() {
+			defer wg.Done()
+			var classifyErr error
+			classifyIds, classifyErr = permissionDao.GetClassifyIdsByPermissionIds(ProductPermissionIds)
+			if classifyErr != nil {
+				logger.Error("获取ETA报告分类id失败:%v", err)
+			}
+		}()
+		wg.Wait()
+		disCardIds, _ := reportDao.GetHiddenReportIds(classifyIds, permissionNames)
+		if len(disCardIds) > 0 {
+			disCardReportIds = append(disCardReportIds, disCardIds...)
+		}
+	}
+
+	//对数据去重
+	disCardReportIds = uniqueArray(disCardReportIds)
+	//获取报告中还包含上架套餐的id
+	if len(disCardReportIds) > 0 {
+		reportIdsSalePackage, _ := merchantDao.GetReportOnSalePackageIds(disCardReportIds)
+		reportIdsSaleProduct, _ := merchantDao.GetOnSaleReportIds(disCardReportIds)
+		showReportMap := make(map[int]bool)
+		for _, reportId := range reportIdsSalePackage {
+			showReportMap[reportId] = true
+		}
+		for _, reportId := range reportIdsSaleProduct {
+			showReportMap[reportId] = true
+		}
+		var filterDisCardReportIds []int
+		for _, id := range disCardReportIds {
+			if _, ok := showReportMap[id]; !ok {
+				filterDisCardReportIds = append(filterDisCardReportIds, id)
+			}
+		}
+		disCardReportIds = filterDisCardReportIds
+	}
 
-	total, latestId, err = reportDao.GetMaxIdByPermissionIds(ids)
+	//获取这些id的产品
+	total, latestId, err = reportDao.GetMaxIdByPermissionIds(ids, disCardReportIds)
 	if err != nil {
 		logger.Error("获取筛选报告的最大记录和记录数失败:%v", err)
 		return
 	}
 	return
 }
+
+type etaReport struct {
+	Id            int   `json:"id"`
+	ClassifyId    int   `json:"classifyId"`
+	PermissionIds []int `json:"permissionIds"`
+}
+type htReport struct {
+	Id           int    `json:"id"`
+	PlateName    string `json:"plateName"`
+	PermissionId []int  `json:"permissionId"`
+}
+
+func uniqueArray(arr []int) []int {
+	uniqueMap := make(map[int]bool)
+	var result []int
+
+	for _, value := range arr {
+		if _, exists := uniqueMap[value]; !exists {
+			uniqueMap[value] = true
+			result = append(result, value)
+		}
+	}
+
+	return result
+}
 func convertEtaReport(etaRp eta.ETAReport, status reportDao.ReportStatus) reportDao.Report {
 	return reportDao.Report{
 		OrgID:         etaRp.ID,
@@ -920,6 +1072,8 @@ func convertReportDTO(report reportDao.Report, fullTime bool) (reportDTO ReportD
 		CoverSrc:      report.CoverSrc,
 		Abstract:      report.Abstract,
 		PublishedTime: report.PublishedTime,
+		PlateName:     report.PlateName,
+		ClassifyId:    report.ClassifyId,
 	}
 	publishDate, err := time.Parse(time.DateTime, reportDTO.PublishedTime)
 	if err == nil && !fullTime {
@@ -949,3 +1103,50 @@ func CountByDocId(key string, sorts []string, docIds []string) (request *es.ESQu
 	req := new(es.ESQueryRequest)
 	return req.CreateESQueryRequest(htConfig.GetReportIndex(), ESColumn, key, 0, 1, sorts, es.CountWithDocIds).WithDocs(docIds)
 }
+func matchLimitByScore(key string, limit int, score float64, docIds []string) (request *es.ESQueryRequest) {
+	req := new(es.ESQueryRequest)
+	return req.CreateESQueryRequest(htConfig.GetReportIndex(), ESColumn, key, 0, limit, sortField, es.LimitByScore).WithScore(score).WithDocs(docIds)
+}
+func SearchReportProduct(key string, limit int, score float64, docIds []int) (reports []ReportDTO, err error) {
+	var docStrIds []string
+	for _, id := range docIds {
+		docStrIds = append(docStrIds, strconv.Itoa(id))
+	}
+	request := matchLimitByScore(key, limit, score, docStrIds)
+	re, err := elastic().Search(request)
+	if err != nil {
+		logger.Error("es搜索异常:%v", err)
+	}
+	hits := elastic().GetSource(re.Hits)
+	if len(hits) == 0 {
+		reports = []ReportDTO{}
+		return
+	}
+	for _, hit := range hits {
+		var content map[string][]string
+		err = json.Unmarshal(hit.Highlight, &content)
+		report := ReportDTO{}
+		err = json.Unmarshal(hit.Source, &report)
+		if err != nil {
+			logger.Error("解析研报数据失败:%v", err)
+			continue
+		}
+		report.Score = hit.Score
+		report.Highlight = content[ESColumn]
+		report.Title = report.Highlight[0]
+		report.PublishedTime = report.PublishedTime[:10]
+		reports = append(reports, report)
+	}
+	return
+}
+
+type ProductSearchDTO struct {
+	HighLight  string
+	SourceId   int
+	SourceType string
+	Score      float64
+}
+
+func CountPermissionWeight(ids []int) (list []configDao.PermissionWeight, err error) {
+	return reportDao.CountPermissionWeight(ids)
+}

+ 57 - 17
domian/user/user_message_service.go

@@ -1,12 +1,16 @@
 package user
 
 import (
+	"bytes"
 	"encoding/json"
 	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/domian/message"
+	messageDao "eta/eta_mini_ht_api/models/message"
 	userDao "eta/eta_mini_ht_api/models/user"
 	"fmt"
 	"strconv"
 	"strings"
+	"text/template"
 )
 
 type MessageDTO struct {
@@ -28,38 +32,64 @@ const (
 	ReportMessageTemplate = "您关注的研究员%v更新了一篇报告"
 	VideoMessageTemplate  = "您关注的研究员%v更新了一个视频"
 	AudioMessageTemplate  = "您关注的研究员%v更新了一个音频"
+
+	RefundMessageTemplate = "订单:{{.ProductOrderNo}},姓名:{{.RealName}},退款{{.Result}}!"
 )
 
 type MessageInfo struct {
 	analyst string
 }
 
-func CreateMessage(meta MetaInfoDTO) (err error) {
-	messageType := userDao.SourceType(meta.SourceType)
+func createRefundMessage(meta message.MetaInfoDTO) (err error) {
+	var content messageDao.RefundMetaData
+	err = json.Unmarshal([]byte(meta.Meta), &content)
+	messageInfo, _ := generateMessage(content, RefundMessageTemplate)
+	userId, _ := strconv.Atoi(meta.To)
+	userMessage := userDao.UserMessage{
+		UserId:   userId,
+		Message:  messageInfo,
+		UserType: userDao.Admin,
+		Type:     messageDao.RefundSourceType,
+		Status:   userDao.UnReadStatus,
+	}
+	if err = userDao.CreateMessage(userMessage); err == nil {
+		messageDao.FinishMetaInfo(meta.Id)
+	} else {
+		logger.Error("生成消息信息失败:%v", err)
+		messageDao.FailedMetaInfo(meta.Id)
+	}
+	return
+}
+func CreateMessage(meta message.MetaInfoDTO) (err error) {
+	if messageDao.SourceType(meta.SourceType) == messageDao.RefundSourceType {
+		return createRefundMessage(meta)
+	}
+	messageType := messageDao.SourceType(meta.SourceType)
 	var messageList []userDao.UserMessage
 	users := strings.Split(meta.To, ",")
-	var message string
-	var content MetaData
+	var messageInfo string
+	var content messageDao.MetaData
 	err = json.Unmarshal([]byte(meta.Meta), &content)
 	if err != nil {
 		logger.Error("生成消息信息失败:%v", err)
-		userDao.FailedMetaInfo(meta.Id)
+		messageDao.FailedMetaInfo(meta.Id)
 	}
-	switch userDao.SourceType(meta.SourceType) {
-	case userDao.ReportSourceType:
-		message = fmt.Sprintf(ReportMessageTemplate, content.AuthorName)
-		messageType = userDao.ReportSourceType
-	case userDao.VideoSourceType:
-		message = fmt.Sprintf(VideoMessageTemplate, content.AuthorName)
-	case userDao.AudioSourceType:
-		message = fmt.Sprintf(AudioMessageTemplate, content.AuthorName)
+	switch messageDao.SourceType(meta.SourceType) {
+	case messageDao.ReportSourceType:
+		messageInfo = fmt.Sprintf(ReportMessageTemplate, content.AuthorName)
+		messageType = messageDao.ReportSourceType
+	case messageDao.VideoSourceType:
+		messageInfo = fmt.Sprintf(VideoMessageTemplate, content.AuthorName)
+	case messageDao.AudioSourceType:
+		messageInfo = fmt.Sprintf(AudioMessageTemplate, content.AuthorName)
 	}
 	for _, user := range users {
 		id, _ := strconv.Atoi(user)
 		userMessage := userDao.UserMessage{
 			AnalystId: content.AuthorId,
 			UserId:    id,
-			Message:   message,
+			UserType:  userDao.Customer,
+			Message:   messageInfo,
 			SourceId:  content.SourceId,
 			Type:      messageType,
 			Status:    userDao.UnReadStatus,
@@ -67,14 +97,24 @@ func CreateMessage(meta MetaInfoDTO) (err error) {
 		messageList = append(messageList, userMessage)
 	}
 	if userDao.BatchInsertMessage(messageList) {
-		userDao.FinishMetaInfo(meta.Id)
+		messageDao.FinishMetaInfo(meta.Id)
 	} else {
 		logger.Error("生成消息信息失败:%v", err)
-		userDao.FailedMetaInfo(meta.Id)
+		messageDao.FailedMetaInfo(meta.Id)
 	}
 	return
 }
-
+func generateMessage(data interface{}, tmpl string) (message string, err error) {
+	t := template.Must(template.New("messageTemplate").Parse(tmpl))
+	var buffer bytes.Buffer
+	err = t.Execute(&buffer, data)
+	if err != nil {
+		logger.Error("生成消息模板失败:%v", err)
+		return
+	}
+	message = buffer.String()
+	return
+}
 func NeedNotice(userId int, analystId int) bool {
 	return userDao.NeedNotice(userId, analystId)
 }

+ 41 - 21
domian/user/user_source_click_flow_service.go

@@ -3,21 +3,23 @@ package user
 import (
 	"errors"
 	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/models/message"
 	userDao "eta/eta_mini_ht_api/models/user"
 	"fmt"
 	"time"
 )
 
 type RecordCountDTO struct {
-	UserId     int
-	TraceId    string
-	Mobile     string
-	SourceId   int
-	SourceType userDao.SourceType
-	IpAddress  string
-	Location   string
-	Referer    string
-	Additional string
+	UserId      int
+	TraceId     string
+	Mobile      string
+	SourceId    int
+	SourceTitle string
+	SourceType  message.SourceType
+	IpAddress   string
+	Location    string
+	Referer     string
+	Additional  string
 }
 
 const (
@@ -25,12 +27,12 @@ const (
 	audio = "audio"
 )
 
-func getSourceType(mediaType string) userDao.SourceType {
+func getSourceType(mediaType string) message.SourceType {
 	switch mediaType {
 	case video:
-		return userDao.VideoSourceType
+		return message.VideoSourceType
 	case audio:
-		return userDao.AudioSourceType
+		return message.AudioSourceType
 	default:
 		return ""
 	}
@@ -52,15 +54,24 @@ func CountReport(record RecordCountDTO) (traceId string, err error) {
 			logger.Error("更新用户研报点击记录失败:%v", err)
 			return
 		}
+		if dbRecord.ReadDurationSeconds > 0 {
+			logger.Warn("重复用户研报点击记录,不做处理,traceId:%s", traceId)
+			return
+		}
 		currTime := time.Now()
+		fmt.Printf("更新用户研报点击记录,traceId:%s", traceId)
+		fmt.Printf("当前时间%v:", currTime)
+		fmt.Printf("点击时间%v:", dbRecord.ClickTime)
 		dbRecord.ReadDurationSeconds = currTime.Sub(dbRecord.ClickTime).Milliseconds()
 		err = userDao.UpdateSourceClicks(dbRecord)
+		logger.Info("插入用户研报点击记录")
+		_ = GetUserAndCountReadTimes(record.UserId)
 	}
 	if err != nil {
 		logger.Error("插入用户研报点击记录失败:%v", err)
 		return
 	}
-	_ = GetUserAndCountReadTimes(record.UserId)
+
 	return
 }
 
@@ -85,15 +96,22 @@ func CountMedia(record RecordCountDTO, mediaType string) (traceId string, err er
 			logger.Error("更新用户研报点击记录失败:%v", err)
 			return
 		}
+		if dbRecord.ReadDurationSeconds == 0 {
+			_ = GetUserAndCountReadTimes(record.UserId)
+		}
 		currTime := time.Now()
+		fmt.Printf("更新用户研报点击记录,traceId:%s", traceId)
+		fmt.Printf("当前时间%v:", currTime)
+		fmt.Printf("点击时间%v:", dbRecord.ClickTime)
 		dbRecord.ReadDurationSeconds = currTime.Sub(dbRecord.ClickTime).Milliseconds()
 		err = userDao.UpdateSourceClicks(dbRecord)
+		logger.Info("插入用户媒体点击记录")
+
 	}
 	if err != nil {
 		logger.Error("插入用户媒体点击记录失败:%v", err)
 		return
 	}
-	_ = GetUserAndCountReadTimes(record.UserId)
 	return
 }
 
@@ -103,7 +121,7 @@ type HotReportDTO struct {
 }
 
 func GetHotReports(begin string, end string, limit int) (dtoList []HotReportDTO) {
-	counts, err := userDao.GetTimeDurationReportCountsById(begin, end, limit, userDao.ReportSourceType)
+	counts, err := userDao.GetTimeDurationReportCountsById(begin, end, limit, message.ReportSourceType)
 	if err != nil {
 		logger.Error("获取最热研报数据失败:%v", err)
 		return
@@ -121,8 +139,9 @@ func convertUserToReportFlow(record RecordCountDTO) userDao.UserSourceClickFlow
 		UserID:         record.UserId,
 		Mobile:         record.Mobile,
 		SourceId:       record.SourceId,
+		SourceTitle:    record.SourceTitle,
 		TraceId:        record.TraceId,
-		SourceType:     userDao.ReportSourceType,
+		SourceType:     message.ReportSourceType,
 		IPAddress:      record.IpAddress,
 		Location:       record.Location,
 		Referer:        record.Referer,
@@ -131,11 +150,12 @@ func convertUserToReportFlow(record RecordCountDTO) userDao.UserSourceClickFlow
 }
 func convertUserToMediaFlow(media RecordCountDTO) userDao.UserSourceClickFlow {
 	return userDao.UserSourceClickFlow{
-		UserID:     media.UserId,
-		TraceId:    media.TraceId,
-		Mobile:     media.Mobile,
-		SourceId:   media.SourceId,
-		SourceType: media.SourceType,
+		UserID:      media.UserId,
+		TraceId:     media.TraceId,
+		Mobile:      media.Mobile,
+		SourceId:    media.SourceId,
+		SourceTitle: media.SourceTitle,
+		SourceType:  media.SourceType,
 	}
 }
 func convertUserToHotReportDTO(flow userDao.CountClickFlowById) HotReportDTO {

+ 21 - 3
domian/user/user_subscription_service.go

@@ -1,7 +1,25 @@
 package user
 
-import subscribeDao "eta/eta_mini_ht_api/models/user"
+import (
+	subscribeDao "eta/eta_mini_ht_api/models/merchant"
+)
 
-func GetUserSubscribe(productId int, userId int) (subscribeDao.UserSubscriptionAccessList, error) {
-	return subscribeDao.GetUserSubscribe(productId, userId)
+func GetUserSubscribe(productIds []int, userId int) (list []subscribeDao.UserSubscriptionAccessList, err error) {
+	return subscribeDao.GetUserSubscribe(productIds, userId)
+}
+func GetNeedExpiredAccessCount(deadLine string) (count int) {
+	return subscribeDao.GetNeedExpiredAccessCount(deadLine)
+}
+
+type accessDTO struct {
+	ID             int
+	TemplateUserId int
+	ProductID      int
+	ProductName    string
+	ProductType    string
+	Status         string
+}
+
+func GetExpiredAccessList(num int, deadLine string) (list []subscribeDao.UserSubscriptionAccessList, err error) {
+	return subscribeDao.GetExpiredAccessList(num, deadLine)
 }

+ 10 - 4
go.mod

@@ -28,10 +28,13 @@ require (
 	github.com/jinzhu/now v1.1.5 // indirect
 	github.com/kr/text v0.2.0 // indirect
 	github.com/mitchellh/mapstructure v1.5.0 // indirect
+	github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect
 	github.com/prometheus/client_golang v1.19.0 // indirect
 	github.com/prometheus/client_model v0.5.0 // indirect
 	github.com/prometheus/common v0.48.0 // indirect
 	github.com/prometheus/procfs v0.12.0 // indirect
+	github.com/richardlehane/mscfb v1.0.4 // indirect
+	github.com/richardlehane/msoleps v1.0.4 // indirect
 	github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 // indirect
 	github.com/shopspring/decimal v1.4.0 // indirect
 	github.com/sirupsen/logrus v1.9.0 // indirect
@@ -39,10 +42,13 @@ require (
 	github.com/tidwall/gjson v1.14.1 // indirect
 	github.com/tidwall/match v1.1.1 // indirect
 	github.com/tidwall/pretty v1.2.0 // indirect
-	golang.org/x/crypto v0.24.0 // indirect
-	golang.org/x/net v0.23.0 // indirect
-	golang.org/x/sys v0.21.0 // indirect
-	golang.org/x/text v0.16.0 // indirect
+	github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d // indirect
+	github.com/xuri/excelize/v2 v2.9.0 // indirect
+	github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 // indirect
+	golang.org/x/crypto v0.28.0 // indirect
+	golang.org/x/net v0.30.0 // indirect
+	golang.org/x/sys v0.26.0 // indirect
+	golang.org/x/text v0.19.0 // indirect
 	golang.org/x/time v0.7.0 // indirect
 	google.golang.org/protobuf v1.34.1 // indirect
 	gopkg.in/yaml.v3 v3.0.1 // indirect

+ 21 - 0
go.sum

@@ -75,6 +75,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
 github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw=
+github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
 github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@@ -100,6 +102,11 @@ github.com/prometheus/common v0.48.0 h1:QO8U2CdOzSn1BBsmXJXduaaW+dY/5QLjfB8svtSz
 github.com/prometheus/common v0.48.0/go.mod h1:0/KsvlIEfPQCQ5I2iNSAWKPZziNCvRs5EC6ILDTlAPc=
 github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
 github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
+github.com/richardlehane/mscfb v1.0.4 h1:WULscsljNPConisD5hR0+OyZjwK46Pfyr6mPu5ZawpM=
+github.com/richardlehane/mscfb v1.0.4/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk=
+github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
+github.com/richardlehane/msoleps v1.0.4 h1:WuESlvhX3gH2IHcd8UqyCuFY5yiq/GR/yqaSM/9/g00=
+github.com/richardlehane/msoleps v1.0.4/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg=
 github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
 github.com/shiena/ansicolor v0.0.0-20200904210342-c7312218db18 h1:DAYUYH5869yV94zvCES9F51oYtN5oGlwjxJJz7ZCnik=
@@ -127,6 +134,12 @@ github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
 github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
 github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs=
 github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
+github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d h1:llb0neMWDQe87IzJLS4Ci7psK/lVsjIS2otl+1WyRyY=
+github.com/xuri/efp v0.0.0-20240408161823-9ad904a10d6d/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
+github.com/xuri/excelize/v2 v2.9.0 h1:1tgOaEq92IOEumR1/JfYS/eR0KHOCsRv/rYXXh6YJQE=
+github.com/xuri/excelize/v2 v2.9.0/go.mod h1:uqey4QBZ9gdMeWApPLdhm9x+9o2lq4iVmjiLfBS5hdE=
+github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7 h1:hPVCafDV85blFTabnqKgNhDCkJX25eik94Si9cTER4A=
+github.com/xuri/nfp v0.0.0-20240318013403-ab9948c2c4a7/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
 github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
 github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64 h1:5mLPGnFdSsevFRFc9q3yYbBkB6tsm4aCwwQV/j1JQAQ=
 github.com/yuin/gopher-lua v0.0.0-20220504180219-658193537a64/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
@@ -136,6 +149,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh
 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI=
 golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM=
+golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
+golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -146,6 +161,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
 golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs=
 golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
+golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
+golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -167,12 +184,16 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws=
 golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
+golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4=
 golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI=
+golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
+golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
 golang.org/x/time v0.7.0 h1:ntUhktv3OPE6TgYxXWv9vKvUSJyIFJlyohwbkEwPrKQ=
 golang.org/x/time v0.7.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

+ 37 - 0
main.go

@@ -12,6 +12,7 @@ import (
 	"eta/eta_mini_ht_api/domian/report"
 	"eta/eta_mini_ht_api/models/eta"
 	"eta/eta_mini_ht_api/models/ht"
+	merchantDao "eta/eta_mini_ht_api/models/merchant"
 	_ "eta/eta_mini_ht_api/routers"
 	_ "eta/eta_mini_ht_api/task"
 	"github.com/beego/beego/v2/server/web"
@@ -41,6 +42,10 @@ func main() {
 		}
 		//初始化第三方AccessToken
 		initThirdPartyAccessToken()
+		//初始化商户信息
+		initMerchant(htConfig.GetMerchantId())
+		//初始化上架产品
+
 	}()
 	logger.Info("初始化成功")
 	web.Run()
@@ -137,7 +142,39 @@ func initReport() {
 	wg.Wait()
 	logger.Info("初始化研报库完成")
 }
+func initMerchant(merchantId string) {
+	merchantList, err := merchantDao.GetEnablerMerchants()
+	if err != nil {
+		logger.Error("加载商户信息失败:%v", err)
+	}
+	if len(merchantList) == 0 {
+		logger.Warn("未配置商户信息,请配置商户信息")
+	}
+	if merchantId != "" {
+		find := false
+		for _, merchant := range merchantList {
+			if merchant.MerchantID == merchantId {
+				err = redisUtils.SetString(redis.GenerateMerchantKey(), merchant.MerchantID, 0)
+				if err != nil {
+					logger.Error("redis商户ID添加失败:%v", err)
+					return
+				}
+				find = true
+				break
+			}
+			if !find {
+				logger.Error("未找到启动的商户信息,请配置商户信息,商户ID:[" + merchantId + "]")
+			}
+		}
+	} else {
+		err = redisUtils.SetString(redis.GenerateMerchantKey(), merchantList[0].MerchantID, -1)
+		if err != nil {
+			logger.Error("redis商户ID添加失败:%v", err)
+			return
+		}
+	}
 
+}
 func getPermissionNameById(id int, currentName *string) (err error) {
 	plate, err := ht.GetPermissionNameById(id)
 	if err != nil {

+ 5 - 1
middleware/auth_middleware.go

@@ -11,6 +11,7 @@ import (
 	"eta/eta_mini_ht_api/service/user"
 	"github.com/beego/beego/v2/server/web"
 	"github.com/beego/beego/v2/server/web/context"
+	"github.com/google/uuid"
 	"strings"
 )
 
@@ -46,6 +47,7 @@ var detailRoutes = []string{
 	"/report/search",
 	"/report/hotRankedList",
 	"/report/publishRankedList",
+	"/home/search",
 }
 var publicRoutes = []string{
 	"/auth/areaCodes",
@@ -78,11 +80,13 @@ var privateRoutes = []string{
 	"/report/count",
 	"/product/*",
 	"/order/*",
-	"/user/subscribe/*",
+	"/user/order/*",
 }
 
 func AuthMiddleware() web.FilterFunc {
 	return func(ctx *context.Context) {
+		threadId := strings.ReplaceAll(uuid.New().String(), "-", "")
+		ctx.Input.SetData("threadId", threadId)
 		path := ctx.Input.URL()
 		logger.Info("请求路径:%v", path)
 		if !allowed(path) {

+ 4 - 0
middleware/webhook_middleware.go

@@ -9,6 +9,7 @@ import (
 	"fmt"
 	"github.com/beego/beego/v2/server/web"
 	"github.com/beego/beego/v2/server/web/context"
+	"strings"
 )
 
 var (
@@ -17,6 +18,9 @@ var (
 
 func WebHookAuthMiddleware() web.FilterFunc {
 	return func(ctx *context.Context) {
+		if strings.Contains(ctx.Input.URL(), "payment") {
+			return
+		}
 		body := ctx.Input.RequestBody
 		webhookRequest := new(WebhookRequest)
 		err := json.Unmarshal(body, &webhookRequest)

+ 13 - 1
models/config/permission.go

@@ -93,7 +93,7 @@ func GetSecondPermissionsByClassifyID(classifyId int) (permissionList []Permissi
 	//sql := "select chart_permission_id, permission_name from chart_permission WHERE chart_permission_id in( select DISTINCT cpskwp.chart_permission_id from chart_permission_search_key_word_mapping cpskwp  where cpskwp.classify_id=? order by cpskwp.chart_permission_id)"
 	//err = doSql(sql, &chartPermissionList, classifyID)
 	db := models.Main()
-	err = db.Model(&Permission{}).Select("permission_id, name").Where("permission_id in ( select DISTINCT pcm.permission_id from permission_classify_mapping pcm  where pcm.classify_id=? order by pcm.permission_id)", classifyId).Find(&permissionList).Error
+	err = db.Model(&Permission{}).Select("permission_id, name,risk_level").Where("permission_id in ( select DISTINCT pcm.permission_id from permission_classify_mapping pcm  where pcm.classify_id=? order by pcm.permission_id)", classifyId).Find(&permissionList).Error
 	return
 }
 
@@ -103,3 +103,15 @@ func GetFirstPermissionsByClassifyID(classifyId int) (permissionList []Permissio
 	err = db.Model(&Permission{}).Select("permission_id, name,parent_id").Where("permission_id  in ( select DISTINCT ps.parent_id from permission_classify_mapping pcm left JOIN permissions ps on ps.permission_id =pcm.permission_id where pcm.classify_id=? order by ps.parent_id)", classifyId).Find(&permissionList).Error
 	return
 }
+
+func PermissionsByPermissionId(permissionId int) (permission Permission, err error) {
+	db := models.Main()
+	//sql := "select chart_permission_id, permission_name,parent_id from chart_permission WHERE chart_permission_id in( select DISTINCT cp.parent_id from chart_permission_search_key_word_mapping cpskwp left JOIN chart_permission cp on cp.chart_permission_id =cpskwp.chart_permission_id where cpskwp.classify_id=? order by cp.parent_id)"
+	err = db.Model(&Permission{}).Select(Columns).Where("permission_id  =? ", permissionId).First(&permission).Error
+	return
+}
+
+type PermissionWeight struct {
+	PermissionId int
+	Weight       int
+}

+ 17 - 14
models/financial_analyst/financial_analyst.go

@@ -14,20 +14,23 @@ type AnalystStatus string
 const (
 	AnalystStatusEnabled  AnalystStatus = "enabled"
 	AnalystStatusDisabled AnalystStatus = "disabled"
-	columns                             = "id,eta_id,name,head_img_url,introduction"
+	columns                             = "id,eta_id,name,head_img_url,introduction,position,investment_certificate,professional_certificate"
 )
 
 type CrmFinancialAnalyst struct {
-	Id           int           `gorm:"primaryKey;autoIncrement;column:id;comment:主键"`
-	ETAId        int           `gorm:"column:eta_id"`
-	HTId         int           `gorm:"column:ht_id"`
-	Name         string        `gorm:"column:name"`
-	HeadImgURL   string        `gorm:"column:head_img_url"`
-	Introduction string        `gorm:"column:introduction"`
-	Status       AnalystStatus `gorm:"column:status"`
-	Deleted      bool          `gorm:"column:deleted"`
-	CreatedTime  time.Time     `gorm:"column:created_time;type:timestamps;comment:创建时间"`
-	UpdatedTime  time.Time     `gorm:"column:updated_time;type:timestamps;comment:更新时间"`
+	Id                      int           `gorm:"primaryKey;autoIncrement;column:id;comment:主键"`
+	ETAId                   int           `gorm:"column:eta_id"`
+	HTId                    int           `gorm:"column:ht_id"`
+	Name                    string        `gorm:"column:name"`
+	HeadImgURL              string        `gorm:"column:head_img_url"`
+	Position                string        `gorm:"column:position"`
+	InvestmentCertificate   string        `gorm:"column:investment_certificate"`
+	ProfessionalCertificate string        `gorm:"column:professional_certificate"`
+	Introduction            string        `gorm:"column:introduction"`
+	Status                  AnalystStatus `gorm:"column:status"`
+	Deleted                 bool          `gorm:"column:deleted"`
+	CreatedTime             time.Time     `gorm:"column:created_time;type:timestamps;comment:创建时间"`
+	UpdatedTime             time.Time     `gorm:"column:updated_time;type:timestamps;comment:更新时间"`
 }
 
 func BatchInsertOrUpdate(list []CrmFinancialAnalyst) (err error) {
@@ -61,12 +64,12 @@ func GetAnalystByName(name string) (analyst CrmFinancialAnalyst, err error) {
 
 func GetCount() (total int64, latestId int64) {
 	db := models.Main()
-	err := db.Model(&CrmFinancialAnalyst{}).Select("count(*) count").Order("id asc").Scan(&total).Error
+	err := db.Model(&CrmFinancialAnalyst{}).Select("count(*) count").Where("deleted = ? and  investment_certificate !=''", false).Scan(&total).Error
 	if err != nil {
 		logger.Error("查询研究员列表失败,%v", err)
 		return 0, 0
 	}
-	err = db.Model(&CrmFinancialAnalyst{}).Select("MAX(id) id").Scan(&latestId).Order("id asc").Error
+	err = db.Model(&CrmFinancialAnalyst{}).Select("MAX(id) id").Where("deleted = ? and  investment_certificate !=''", false).Scan(&latestId).Error
 	if err != nil {
 		logger.Error("查询研究员列表失败,%v", err)
 		return 0, 0
@@ -85,6 +88,6 @@ func GetAnalystList(latestId int64, offset int, limit int) (analysts []CrmFinanc
 		logger.Error("非法的limit参数:%d", limit)
 	}
 	db := models.Main()
-	err = db.Select(columns).Where(" id<= ? ", latestId).Order("created_time desc").Offset(offset).Limit(limit).Find(&analysts).Error
+	err = db.Select(columns).Where(" id<= ? ", latestId).Where("deleted = ? and  investment_certificate !=''", false).Order("created_time desc").Offset(offset).Limit(limit).Find(&analysts).Error
 	return
 }

+ 1 - 1
models/media/images_sources.go → models/image/images_sources.go

@@ -1,4 +1,4 @@
-package media
+package image
 
 import (
 	"eta/eta_mini_ht_api/models"

+ 5 - 4
models/media/media.go

@@ -4,6 +4,7 @@ import (
 	"errors"
 	logger "eta/eta_mini_ht_api/common/component/log"
 	"eta/eta_mini_ht_api/models"
+	"eta/eta_mini_ht_api/models/config"
 	"eta/eta_mini_ht_api/models/report"
 	"gorm.io/gorm"
 	"time"
@@ -105,7 +106,7 @@ func GetMediaPageByIds(latestId int64, limit int, offset int, mediaType string,
 	return
 }
 
-func GetAnalystMediaRangeReportIds(mediaType string, srcIds []int, analystId int) (mediaIds []int, err error) {
+func GetAnalystMediaRangeMediaIds(mediaType string, srcIds []int, analystId int) (mediaIds []int, err error) {
 	if len(srcIds) == 0 {
 		logger.Info("过滤的媒体ID为空")
 		return
@@ -134,9 +135,9 @@ func GetMediaById(mediaType string, mediaId int) (media Media, err error) {
 	return
 }
 
-// 测试造数据
-func GetlIST() (media []Media) {
+func CountPermissionWeight(ids []int) (list []config.PermissionWeight, err error) {
 	db := models.Main()
-	_ = db.Model(&Media{}).Where("deleted = ?", false).Find(&media).Error
+	sql := `select permission_id,count(*) num from(select mpm.permission_id from  media m LEFT JOIN (select permission_id,media_id from media_permission_mappings where deleted=0) mpm on mpm.media_id=m.id where m.id in (?)) a GROUP BY a.permission_id`
+	err = db.Raw(sql, ids).Find(&list).Error
 	return
 }

+ 11 - 1
models/media/media_permission_mapping.go

@@ -14,6 +14,16 @@ type MediaPermissionMapping struct {
 
 func GetMediaPermissionMappingByPermissionId(mediaType string, permissionIds []int) (mediaIds []int, err error) {
 	db := models.Main()
-	err = db.Model(&MediaPermissionMapping{}).Select("DISTINCT media_id").Where("media_type = ? and deleted =? and permission_id in ?", mediaType, false, permissionIds).Scan(&mediaIds).Error
+	query := db.Model(&MediaPermissionMapping{}).Select("DISTINCT media_id").Where("deleted =? and permission_id in ?", false, permissionIds)
+	if mediaType != "" {
+		query.Where("media_type = ?", mediaType)
+	}
+	err = query.Scan(&mediaIds).Error
+	return
+}
+
+func GetMediaPermissionMappingByMedia(mediaType string, mediaId int) (permissionIds []int, err error) {
+	db := models.Main()
+	err = db.Model(&MediaPermissionMapping{}).Select("DISTINCT permission_id").Where(" deleted =? and media_id = ?  and media_type = ?", false, mediaId, mediaType).Scan(&permissionIds).Error
 	return
 }

+ 10 - 1
models/merchant/merchant.go

@@ -1,6 +1,9 @@
 package merchant
 
-import "time"
+import (
+	"eta/eta_mini_ht_api/models"
+	"time"
+)
 
 // Merchant 商户信息结构体
 type Merchant struct {
@@ -16,3 +19,9 @@ type Merchant struct {
 func (Merchant) TableName() string {
 	return "merchants"
 }
+
+func GetEnablerMerchants() (merchant []Merchant, err error) {
+	db := models.Main()
+	err = db.Select("*").Where("enabled=?", 1).Find(&merchant).Error
+	return
+}

+ 0 - 20
models/merchant/merchant_package.go

@@ -1,20 +0,0 @@
-package merchant
-
-import (
-	"time"
-)
-
-// MerchantPackage 商户套餐信息结构体
-type MerchantPackage struct {
-	ID              int       `gorm:"column:id;primary_key;autoIncrement;comment:主键"`
-	ReportSourceIds string    `gorm:"column:report_source_ids;type:varchar(255);comment:报告来源"`
-	VideoSourceIds  string    `gorm:"column:video_source_ids;type:varchar(255);comment:视频来源"`
-	AudioSourceIds  string    `gorm:"column:audio_source_ids;type:varchar(255);comment:音频来源"`
-	CreatedTime     time.Time `gorm:"column:created_time;type:datetime;comment:创建时间"`
-	UpdatedTime     time.Time `gorm:"column:updated_time;type:datetime;comment:更新时间"`
-}
-
-// TableName 指定表名
-func (MerchantPackage) TableName() string {
-	return "merchant_packages"
-}

+ 243 - 26
models/merchant/merchant_product.go

@@ -1,9 +1,13 @@
 package merchant
 
 import (
+	"errors"
+	stringUtils "eta/eta_mini_ht_api/common/utils/string"
 	"eta/eta_mini_ht_api/models"
-	"github.com/shopspring/decimal"
-	"gorm.io/gorm"
+	"fmt"
+	"sort"
+	"strings"
+	"sync/atomic"
 	"time"
 )
 
@@ -11,8 +15,9 @@ type SaleStatus string
 type MerchantProductType string
 
 const (
-	detailColumns  = "id,title,price,is_permanent,valid_days,type,risk_level,stale_status"
-	sourceIdColumn = "id,risk_level,source_id"
+	detailColumns         = "id,source_id,title,cover_src,cover_url,description,price,type,is_permanent,valid_days,sale_status,created_time,updated_time,deleted"
+	detailColumnsWithRisk = "merchant_products.id,source_id,title,cover_src,cover_url,description,price,type,is_permanent,valid_days,sale_status,created_time,updated_time,deleted,permissions.risk_level as risk_level"
+	sourceIdColumn        = "id,source_id"
 )
 const (
 	OnSale  SaleStatus          = "on_sale"  //上架
@@ -25,18 +30,19 @@ const (
 
 // MerchantProduct 商户产品信息结构体
 type MerchantProduct struct {
-	ID          int                 `gorm:"column:id;primary_key;autoIncrement;comment:主键"`
-	SourceID    int                 `gorm:"column:source_id;type:int(11);comment:单品或者套餐对应的主键"`
+	Id          int                 `gorm:"column:id;primary_key;autoIncrement;comment:主键"`
+	SourceId    int                 `gorm:"column:source_id;type:int(11);comment:单品或者套餐对应的主键"`
 	Title       string              `gorm:"column:title;type:varchar(255);comment:标题"`
-	CoverSrc    string              `gorm:"column:cover_src;type:varchar(255);comment:封面图片"`
+	CoverSrc    int                 `gorm:"column:cover_src;type:int(11);comment:封面图片资源库id"`
+	CoverUrl    string              `gorm:"column:cover_url;type:varchar(255);comment:封面图片url"`
 	Description string              `gorm:"column:description;type:varchar(255);comment:描述"`
-	Price       decimal.Decimal     `gorm:"column:price;type:decimal(10,2);comment:价格"`
-	RiskLevel   string              `gorm:"column:risk_level;type:varchar(255);not null;comment:风险等级"`
+	Price       string              `gorm:"column:price;type:decimal(10,2);comment:价格"`
+	RiskLevel   string              `gorm:"column:risk_level;type:varchar(100);comment:风险等级"`
 	Type        MerchantProductType `gorm:"column:type;type:enum('report','video','audio','package');not null;comment:类型"`
 	IsPermanent bool                `gorm:"column:is_permanent;type:int(1);not null;default:0;comment:是否永久"`
 	ValidDays   int                 `gorm:"column:valid_days;type:int(11);comment:有效期天数"`
 	SaleStatus  SaleStatus          `gorm:"column:sale_status;type:enum('on_sale','off_sale');not null;default:'on_sale';comment:上架/下架状态"`
-	Deleted     bool                `gorm:"column:deleted;type:tinyint(1);not null;default:0;comment:是否删除"`
+	Deleted     int                 `gorm:"column:deleted;type:tinyint(1);not null;default:0;comment:是否删除"`
 	CreatedTime time.Time           `gorm:"column:created_time;type:datetime;comment:创建时间"`
 	UpdatedTime time.Time           `gorm:"column:updated_time;type:datetime;comment:更新时间;default:CURRENT_TIMESTAMP on update CURRENT_TIMESTAMP"`
 }
@@ -46,16 +52,9 @@ func (MerchantProduct) TableName() string {
 	return "merchant_products"
 }
 
-func (m *MerchantProduct) BeforeCreate(_ *gorm.DB) (err error) {
-	m.CreatedTime = time.Now()
-	m.Deleted = false
-	m.SaleStatus = OffSale
-	return
-}
-
 func GetMerchantProductById(id int) (product MerchantProduct, err error) {
 	db := models.Main()
-	err = db.Select(detailColumns).Where("id = ? and deleted =?", id, false).First(&product).Error
+	err = db.Select(detailColumns).Where("id = ? ", id).First(&product).Error
 	return
 }
 
@@ -63,30 +62,248 @@ func GetMerchantProductBySourceId(sourceId int, productType ...MerchantProductTy
 	db := models.Main()
 	if len(productType) > 0 {
 		if len(productType) == 1 {
-			err = db.Select(detailColumns).Where("source_id =? and type = ? and deleted =?", sourceId, productType[0], false).First(&product).Error
+			if productType[0] == Package {
+				err = db.Select(detailColumnsWithRisk).Joins("left join permissions  on permissions.permission_id=source_id").Where("source_id =? and type = ? and deleted =?", sourceId, productType[0], 0).First(&product).Error
+			} else {
+				err = db.Select(detailColumns).Where("source_id =? and type = ? and deleted =?", sourceId, productType[0], 0).First(&product).Error
+			}
 		} else {
-			err = db.Select(detailColumns).Where("source_id =? and type in (?) and deleted =?", sourceId, productType, false).First(&product).Error
+			err = db.Select(detailColumns).Where("source_id =? and type in (?) and deleted =?", sourceId, productType, 0).First(&product).Error
 		}
-
 	} else {
-		err = db.Select(detailColumns).Where("source_id =? and deleted =?", sourceId, false).First(&product).Error
+		err = db.Select(detailColumns).Where("source_id =? and deleted =?", sourceId, 0).First(&product).Error
 	}
 
 	return
 }
 
-func GetProductListBySourceIds(ids []int, productType ...MerchantProductType) (productList []MerchantProduct, err error) {
+func GetProductListBySourceIds(ids []int, detail bool, productType ...MerchantProductType) (productList []MerchantProduct, err error) {
 	db := models.Main()
+	var columns string
+	if productType == nil {
+		err = errors.New("productType参数不能为空")
+		return
+	}
+	if detail {
+		columns = detailColumns
+	} else {
+		columns = sourceIdColumn
+	}
 	if len(productType) > 0 {
 		if len(productType) == 1 {
-			err = db.Select(sourceIdColumn).Where("source_id in ? and type = ? and deleted =? ", ids, productType[0], false).Find(&productList).Error
+			if productType[0] == Package {
+				err = db.Select(detailColumnsWithRisk).Joins("left join permissions  on permissions.permission_id=source_id").Where("source_id in ? and type = ? and deleted =?  order by created_time desc", ids, productType[0], false).Find(&productList).Error
+			} else {
+				err = db.Select(columns).Where("source_id in ? and type = ? and deleted =? order by created_time desc", ids, productType[0], false).Find(&productList).Error
+			}
 		} else {
-			err = db.Select(sourceIdColumn).Where("source_id in ? and type in (?) and deleted =? ", ids, productType, false).Find(&productList).Error
+			err = db.Select(columns).Where("source_id in ? and type in (?) and deleted =? by created_time desc", ids, productType, false).Find(&productList).Error
 		}
 
 	} else {
-		err = db.Select(sourceIdColumn).Where("source_id in ?  and deleted =? ", ids, productType, false).Find(&productList).Error
+		err = db.Select(columns).Where("source_id in ?  and deleted =? by created_time desc", ids, productType, false).Find(&productList).Error
+	}
+
+	return
+}
+
+func GetTotalPageCountByProductType(productType MerchantProductType) (total int64, latestId int64) {
+	db := models.Main()
+	_ = db.Model(&MerchantProduct{}).Select("count(*)").Where("type=? and deleted =?", productType, false).Scan(&total).Error
+	_ = db.Model(&MerchantProduct{}).Select("max(id)").Where("type=? and deleted =?", productType, false).Scan(&latestId).Error
+	return
+}
+
+func GetProductPageByProductType(productIds []int, id int64, offset int, limit int) (list []MerchantProduct, err error) {
+	db := models.Main()
+	err = db.Select(detailColumns).Where("id <= ?  and deleted =?  and id in ? order by created_time desc limit ?,? ", id, false, productIds, offset, limit).Find(&list).Error
+	return
+}
+
+func GetProductListByProductType(productType MerchantProductType, detail bool) (list []MerchantProduct, err error) {
+	db := models.Main()
+	var columns string
+	if detail {
+		columns = detailColumns
+	} else {
+		columns = sourceIdColumn
+	}
+	if productType == "" {
+		err = errors.New("productType参数不能为空")
+		return
+	}
+	err = db.Select(columns).Where(" type = ? and deleted =? order by created_time desc", productType, false).Find(&list).Error
+	return
+}
+
+type MerchantProductIdMap struct {
+	Type     string
+	SourceId string
+}
+
+func GetProductByProductType() (productIds map[string]string, err error) {
+	db := models.Main()
+	var productIdMap []MerchantProductIdMap
+	sql := `SELECT type,GROUP_CONCAT(source_id ORDER BY source_id SEPARATOR ',') as source_id FROM merchant_products GROUP BY type`
+	err = db.Raw(sql).Find(&productIdMap).Error
+	productIds = make(map[string]string, len(productIdMap))
+	for _, v := range productIdMap {
+		productIds[v.Type] = v.SourceId
+	}
+	return
+}
+
+func generateSignalPdSql(idMap map[string][]int) (condition string) {
+	for k, v := range idMap {
+		if condition == "" {
+			condition = "(" + fmt.Sprintf("(type ='%s' and source_id in (%s))", k, strings.Join(stringUtils.IntToStringSlice(v), ","))
+		} else {
+			condition = condition + fmt.Sprintf(" or (type ='%s' and source_id in (%s))", k, strings.Join(stringUtils.IntToStringSlice(v), ","))
+		}
+	}
+	condition = condition + ")"
+	if len(idMap) == 1 {
+		condition = condition[1 : len(condition)-1]
 	}
+	return
+}
+
+func LatestId() (latestId int64) {
+	db := models.Main()
+	_ = db.Model(&MerchantProduct{}).Select("max(id)").Where("deleted=?", false).Scan(&latestId).Error
+	return
+}
+
+type ProductDTO struct {
+	SourceId   int
+	SourceType string
+	Score      float64
+}
 
+func ProductListBySort(list []ProductDTO, id int64, offset int, size int) (productList []MerchantProduct, err error) {
+	var productMap = make(map[string][]int, len(list))
+	sort.Slice(list, func(i, j int) bool {
+		return list[i].Score > list[j].Score
+	})
+	var idSort []int
+	for _, v := range list {
+		idSort = append(idSort, v.SourceId)
+	}
+	for _, v := range list {
+		ids := productMap[v.SourceType]
+		ids = append(ids, v.SourceId)
+		productMap[v.SourceType] = ids
+	}
+	sql := generateSignalPdSql(productMap)
+	db := models.Main()
+	query := fmt.Sprintf("id <= ? and deleted =? and %s order by Field(id,?) desc limit ?,?", sql)
+	err = db.Select(detailColumns).Where(query, id, false, strings.Join(stringUtils.IntToStringSlice(idSort), ","), offset, size).Find(&productList).Error
+	return
+}
+
+type packageStruct struct {
+	SourceId int
+	Weight   int32
+}
+
+func PackageListBySort(weightMap map[int]*atomic.Int32, id int64, offset int, size int) (productList []MerchantProduct, err error) {
+	var packageList []packageStruct
+	for k, v := range weightMap {
+		packageList = append(packageList, packageStruct{
+			SourceId: k,
+			Weight:   v.Load(),
+		})
+	}
+	sort.Slice(packageList, func(i, j int) bool {
+		return packageList[i].Weight > packageList[j].Weight
+	})
+	var idSort []int
+	for _, v := range packageList {
+		idSort = append(idSort, v.SourceId)
+	}
+	db := models.Main()
+	err = db.Select(detailColumns).Where("id <= ?  and type =? and deleted =? and source_id in ? order by Field(id,?) desc limit ?,? ", id, Package, false, idSort, strings.Join(stringUtils.IntToStringSlice(idSort), ","), offset, size).Find(&productList).Error
+	return
+}
+
+func GetOffSaleProducts(query []MerchantProductType) (list []MerchantProduct, err error) {
+	db := models.Main()
+	err = db.Select(detailColumns).Where(" deleted =? and  type  in ?  and sale_status = ? ", false, query, OffSale).Find(&list).Error
+	return
+
+}
+
+func GetReportOnSalePackageIds(ids []int) (sourceIds []int, err error) {
+	sql := `
+    SELECT
+      r.id
+    FROM
+      reports r
+      LEFT JOIN permission_classify_mapping pcm ON r.classify_id = pcm.classify_id
+      LEFT JOIN merchant_products mp ON mp.source_id = pcm.permission_id 
+      AND mp.type = 'package' 
+      AND mp.sale_status = 'on_sale' 
+      AND mp.deleted = 0
+    WHERE
+      mp.id IS NOT NULL and r.id in ?
+    UNION ALL
+    SELECT
+      r.id
+    FROM
+      reports r
+      LEFT JOIN permissions p ON r.plate_name = p.NAME
+      LEFT JOIN merchant_products mp ON mp.source_id = p.permission_id 
+      AND mp.type = 'package' 
+      AND mp.sale_status = 'on_sale' 
+      AND mp.deleted = 0
+    WHERE
+      mp.id IS NOT NULL  and r.id in ?`
+	db := models.Main()
+	err = db.Raw(sql, ids, ids).Scan(&sourceIds).Error
+	return
+}
+func GetMediaOnSalePackageIds(ids []int) (sourceIds []int, err error) {
+	sql := `  SELECT
+      distinct mpm.media_id
+    FROM
+      media_permission_mappings mpm
+      LEFT JOIN merchant_products mp ON mp.source_id = mpm.permission_id 
+      AND mp.type = 'package' 
+      AND mp.sale_status = 'on_sale'
+      and mp.deleted=0
+    WHERE
+        mpm.deleted=0  and mp.id is not null and mpm.media_id in ?`
+	db := models.Main()
+	err = db.Raw(sql, ids).Scan(&sourceIds).Error
+	return
+}
+func GetOnSaleReportIds(ids []int) (sourceIds []int, err error) {
+	sql := `SELECT
+      r.id
+    FROM
+      reports r
+      LEFT JOIN merchant_products mp ON mp.source_id = r.id
+      AND mp.type = 'report' 
+      AND mp.sale_status = 'on_sale' 
+      AND mp.deleted = 0
+    WHERE
+      mp.id IS NOT NULL and r.id in ?`
+	db := models.Main()
+	err = db.Raw(sql, ids).Scan(&sourceIds).Error
+	return
+}
+func GetOnSaleMediaIds(ids []int, productType MerchantProductType) (sourceIds []int, err error) {
+	sql := `SELECT
+     distinct mpm.media_id
+    FROM
+     media_permission_mappings mpm
+      LEFT JOIN merchant_products mp ON mp.source_id = mpm.media_id
+      AND mp.type = ?
+      AND mp.sale_status = 'on_sale'
+      AND mp.deleted = 0
+    WHERE
+       mpm.deleted=0  and mp.id is not null and mpm.media_id in ?`
+	db := models.Main()
+	err = db.Raw(sql, productType, ids).Scan(&sourceIds).Error
 	return
 }

+ 36 - 0
models/merchant/user_subscription_access_flow.go

@@ -0,0 +1,36 @@
+package merchant
+
+import (
+	"eta/eta_mini_ht_api/models"
+	"time"
+)
+
+type OpenType string
+
+const (
+	OpenTypeValid   OpenType = "open"
+	OpenTypeExpired OpenType = "expired"
+	OpenTypeClose   OpenType = "close"
+)
+
+// UserSubscriptionAccessFlow  用户订阅访问列表
+type UserSubscriptionAccessFlow struct {
+	ID             int       `gorm:"column:id;primaryKey"`
+	TemplateUserId int       `gorm:"column:template_user_id"`
+	ProductID      int       `gorm:"column:product_id"`
+	ProductType    string    `gorm:"column:product_type"`
+	ProductOrderId string    `gorm:"column:product_order_id"`
+	OpenType       OpenType  `gorm:"column:open_type;type:enum('open','expired','closed');default:'open'"`
+	CreatedTime    time.Time `gorm:"column:created_time"`
+	UpdatedTime    time.Time `gorm:"column:updated_time"`
+}
+
+func (UserSubscriptionAccessFlow) TableName() string {
+	return "user_subscription_access_flow"
+}
+
+func CreateAccessFlow(userSubscriptionAccessFlow UserSubscriptionAccessFlow) (err error) {
+	db := models.Main()
+	err = db.Create(&userSubscriptionAccessFlow).Error
+	return
+}

+ 142 - 0
models/merchant/user_subscription_access_list.go

@@ -0,0 +1,142 @@
+package merchant
+
+import (
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/models"
+	"gorm.io/gorm"
+	"gorm.io/gorm/clause"
+	"time"
+)
+
+type SubscribeStatus string
+
+const (
+	SubscribeValid   SubscribeStatus = "valid"
+	SubscribeExpired SubscribeStatus = "expired"
+	SubscribeClose   SubscribeStatus = "closed"
+)
+
+var (
+	subscribeStatusMap = map[SubscribeStatus]OpenType{
+		SubscribeValid:   OpenTypeValid,
+		SubscribeExpired: OpenTypeExpired,
+		SubscribeClose:   OpenTypeClose,
+	}
+)
+
+// UserSubscriptionAccessList 用户订阅访问列表
+type UserSubscriptionAccessList struct {
+	ID             int                 `gorm:"column:id;primaryKey"`
+	TemplateUserId int                 `gorm:"column:template_user_id"`
+	ProductID      int                 `gorm:"column:product_id"`
+	ProductName    string              `gorm:"column:product_name"`
+	ProductType    MerchantProductType `gorm:"column:product_type"`
+	BeginDate      time.Time           `gorm:"column:begin_date"`
+	EndDate        time.Time           `gorm:"column:end_date"`
+	Status         SubscribeStatus     `gorm:"column:status;type:enum('valid','expired');default:'valid'"`
+	CreatedTime    time.Time           `gorm:"column:created_time"`
+	UpdatedTime    time.Time           `gorm:"column:updated_time"`
+	ProductOrderNo string              `gorm:"-"`
+}
+
+func (UserSubscriptionAccessList) TableName() string {
+	return "user_subscription_access_list"
+}
+
+func GetUserSubscribe(productIds []int, userId int) (list []UserSubscriptionAccessList, err error) {
+	db := models.Main()
+	err = db.Select("id,template_user_id,product_id,product_name,begin_date,end_date,status").Where("template_user_id=? and product_id in ?", userId, productIds).Find(&list).Error
+	return
+}
+
+func (access *UserSubscriptionAccessList) BeforeCreate(_ *gorm.DB) (err error) {
+	access.CreatedTime = time.Now()
+	flow := UserSubscriptionAccessFlow{
+		ProductID:      access.ProductID,
+		ProductType:    string(access.ProductType),
+		ProductOrderId: access.ProductOrderNo,
+		OpenType:       subscribeStatusMap[access.Status],
+		CreatedTime:    time.Now(),
+	}
+	_ = CreateAccessFlow(flow)
+	return
+}
+
+func (access *UserSubscriptionAccessList) BeforeUpdate(_ *gorm.DB) (err error) {
+	access.UpdatedTime = time.Now()
+	flow := UserSubscriptionAccessFlow{
+		TemplateUserId: access.TemplateUserId,
+		ProductID:      access.ProductID,
+		ProductType:    access.ProductName,
+		ProductOrderId: access.ProductOrderNo,
+		OpenType:       subscribeStatusMap[access.Status],
+		CreatedTime:    time.Now(),
+	}
+	_ = CreateAccessFlow(flow)
+	return
+}
+func InsertOrUpdateUserSubscribe(access UserSubscriptionAccessList) (err error) {
+	db := models.Main()
+	OnConflictFunc := clause.OnConflict{
+		Columns:   []clause.Column{{Name: "template_user_id"}, {Name: "product_id"}},
+		DoUpdates: clause.AssignmentColumns([]string{"status", "begin_date", "end_date", "product_name"}),
+	}
+	// 执行批量插入或更新操作
+	err = db.Clauses(OnConflictFunc).Create(&access).Error
+	return
+}
+
+func CloseUserAccess(access UserSubscriptionAccessList) (err error) {
+	db := models.Main()
+	err = db.Model(&access).Where("template_user_id=? AND product_id=?", access.TemplateUserId, access.ProductID).Update("status", SubscribeClose).Error
+	// 执行批量插入或更新操作
+	return
+}
+func ExpireUserAccess(access UserSubscriptionAccessList) (err error) {
+	db := models.Main()
+	err = db.Model(&access).Where("template_user_id=? AND product_id=?", access.TemplateUserId, access.ProductID).Update("status", SubscribeExpired).Error
+	// 执行批量插入或更新操作
+	return
+}
+func SubscribeList(templateUserId int, productType string, latestId int64, offset int, pageSize int) (list []UserSubscriptionAccessList, err error) {
+	db := models.Main()
+	err = db.Model(&UserSubscriptionAccessList{}).Where("id<=? and template_user_id=? AND product_type=? and status=?", latestId, templateUserId, productType, SubscribeValid).Order("begin_date desc").Offset(offset).Limit(pageSize).Find(&list).Error
+	return
+}
+func GetUserSubscribeById(templateUserId int, productId int) (list UserSubscriptionAccessList, err error) {
+	db := models.Main()
+	err = db.Select("id,template_user_id,product_id,product_name,begin_date,end_date,status").Where(" template_user_id=? AND product_id=? and status=?", templateUserId, productId, SubscribeValid).First(&list).Error
+	return
+}
+func GetTotalUserPageCountByProductType(productType string, templateUserId int) (count int64, latestId int64) {
+	db := models.Main()
+	err := db.Model(&UserSubscriptionAccessList{}).Select("count(*)").Where("template_user_id=? AND product_type=? and status=?", templateUserId, productType, SubscribeValid).Scan(&count).Error
+	if err != nil {
+		logger.Error("查询用户订阅总数失败:%s", err)
+		return 0, 0
+	}
+	err = db.Model(&UserSubscriptionAccessList{}).Select("max(id)").Where("template_user_id=? AND product_type=? and status=?", templateUserId, productType, SubscribeValid).Scan(&latestId).Error
+	if err != nil {
+		logger.Error("查询用户订阅最新id失败:%s", err)
+		return 0, 0
+	}
+	return
+}
+
+func GetNeedExpiredAccessCount(deadLine string) (count int) {
+	db := models.Main()
+	err := db.Model(&UserSubscriptionAccessList{}).Select("count(*)").Where(" product_type=? and status=? and end_date <=?", Package, SubscribeValid, deadLine).Scan(&count).Error
+	if err != nil {
+		logger.Error("查询用户订阅总数失败:%s", err)
+	}
+	return
+}
+
+func GetExpiredAccessList(num int, deadLine string) (list []UserSubscriptionAccessList, err error) {
+	db := models.Main()
+	err = db.Model(&UserSubscriptionAccessList{}).Select("*").Where(" product_type=? and status=? and end_date <=? limit 0,?", Package, SubscribeValid, deadLine, num).Find(&list).Error
+	if err != nil {
+		logger.Error("查询用户订阅总数失败:%s", err)
+	}
+	return
+}

+ 15 - 2
models/user/meta_info.go → models/message/meta_info.go

@@ -1,4 +1,4 @@
-package user
+package message
 
 import (
 	logger "eta/eta_mini_ht_api/common/component/log"
@@ -13,6 +13,7 @@ type MetaType string
 
 const (
 	UserNoticeType    MetaType   = "USER_NOTICE"
+	SysUserNoticeType MetaType   = "SYS_USER_NOTICE"
 	InitStatusType    StatusType = "INIT"
 	PendingStatusType StatusType = "PENDING"
 	FinishStatusType  StatusType = "FINISH"
@@ -21,6 +22,7 @@ const (
 	ReportSourceType SourceType = "REPORT"
 	VideoSourceType  SourceType = "VIDEO"
 	AudioSourceType  SourceType = "AUDIO"
+	RefundSourceType SourceType = "REFUND"
 )
 
 // MetaInfo 表示 meta_infos 表的模型
@@ -29,12 +31,23 @@ type MetaInfo struct {
 	Meta        string     `gorm:"column:meta"`
 	From        string     `gorm:"column:from"`
 	To          string     `gorm:"column:to"`
-	SourceType  SourceType `gorm:"column:source_type;type:enum('REPORT','VIDEO','AUDIO')"`
+	SourceType  SourceType `gorm:"column:source_type;type:enum('REPORT','VIDEO','AUDIO','REFUND')"`
 	MetaType    MetaType   `gorm:"column:meta_type;type:enum('USER_NOTICE')"`
 	Status      StatusType `gorm:"column:status;type:enum('INIT','PENDING','FINISH','FAILED')"`
 	CreatedTime time.Time
 	UpdatedTime time.Time
 }
+type MetaData struct {
+	SourceId      int    `json:"reportId"`
+	AuthorId      int    `json:"AuthorId"`
+	AuthorName    string `json:"authorName"`
+	PublishedTime string `json:"publishedTime"`
+}
+type RefundMetaData struct {
+	RealName       string `json:"realName,omitempty"`
+	ProductOrderNo string `json:"productOrderNo,omitempty"`
+	Result         string `json:"result,omitempty"`
+}
 
 func (mt *MetaInfo) BeforeCreate(_ *gorm.DB) (err error) {
 	mt.CreatedTime = time.Now()

+ 94 - 38
models/order/product_order.go

@@ -4,6 +4,8 @@ import (
 	"errors"
 	logger "eta/eta_mini_ht_api/common/component/log"
 	"eta/eta_mini_ht_api/models"
+	merchantDao "eta/eta_mini_ht_api/models/merchant"
+	"fmt"
 	"github.com/go-sql-driver/mysql"
 	"gorm.io/gorm"
 	"time"
@@ -11,7 +13,6 @@ import (
 
 type OrderStatus string
 type RefundStatus string
-type PaymentStatus string
 
 const (
 	//订单状态
@@ -26,48 +27,62 @@ const (
 	RefundStatusFailed     RefundStatus = "failed"
 	RefundStatusProcessing RefundStatus = "processing"
 
-	WechatPayWay PaymentStatus = "wechat"
-	AliPayWay    PaymentStatus = "alipay"
+	detailColumn = "id,order_id,user_id,template_user_id,real_name,area_code,mobile,product_id,product_type,product_name,status,created_time,updated_time,total_amount,expired_time,valid_duration"
 )
 
 type ProductOrder struct {
-	ID            int           `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
-	OrderID       string        `gorm:"column:order_id;size:255;comment:'订单编号'" json:"order_id"`
-	UserID        int           `gorm:"column:user_id;default:null;comment:'用户id'" json:"user_id"`
-	ProductID     int           `gorm:"column:product_id;default:null;comment:'产品id'" json:"product_id"`
-	TotalAmount   string        `gorm:"column:total_amount;size:255;default:null;comment:'金额'" json:"total_amount"`
-	TransactionID int           `gorm:"column:transaction_id;default:null;comment:'支付订单'" json:"transaction_id"`
-	RefundAmount  string        `gorm:"column:refund_amount;size:255;default:null;comment:'退款金额'" json:"refund_amount"`
-	PaymentWay    PaymentStatus `gorm:"column:payment_way;enum('wechat','alipay');default:null;comment:'支付渠道'"`
-	PaymentTime   time.Time     `gorm:"column:payment_time;default:null;comment:'支付时间'" json:"payment_time"`
-	Status        OrderStatus   `gorm:"column:status;type:enum('pending','processing','paid','closed','refund');default:'pending';comment:'订单状态'" json:"status"`
-	RefundStatus  RefundStatus  `gorm:"column:refund_status;type:enum('pending','processing','failed','success');default:null;comment:'退款状态'" json:"refund_status"`
-	Remark        string        `gorm:"column:remark;size:255;default:null;comment:'备注'" json:"remark"`
-	IsDeleted     int           `gorm:"column:is_deleted;size:1;default:0;comment:'是否删除'" json:"is_deleted"`
-	CreatedTime   time.Time     `gorm:"column:created_time;default:null;comment:'创建时间'" json:"created_time"`
-	UpdateTime    time.Time     `gorm:"column:update_time;default:null;comment:'更新时间'" json:"update_time"`
+	ID               int                             `gorm:"column:id;primaryKey;autoIncrement" json:"id"`
+	OrderID          string                          `gorm:"column:order_id;size:255;comment:'订单编号'" json:"order_id"`
+	UserID           int                             `gorm:"column:user_id;default:null;comment:'用户id'" json:"user_id"`
+	TemplateUserID   int                             `gorm:"column:template_user_id;default:null;comment:'临时用户id'" json:"template_user_id"`
+	RealName         string                          `gorm:"column:real_name;default:null;comment:'姓名'" json:"real_name"`
+	AreaCode         string                          `gorm:"column:area_code;default:null;comment:'手机区号'" json:"area_code"`
+	Mobile           string                          `gorm:"column:mobile;default:null;comment:'手机号'" json:"mobile"`
+	ProductID        int                             `gorm:"column:product_id;default:null;comment:'产品id'" json:"product_id"`
+	ProductType      merchantDao.MerchantProductType `gorm:"column:product_type;default:null;comment:'产品类型'" json:"product_type"`
+	ProductName      string                          `gorm:"column:product_name;default:null;comment:'商品名称'" json:"product_name"`
+	TotalAmount      string                          `gorm:"column:total_amount;size:255;default:null;comment:'金额'" json:"total_amount"`
+	TradeId          int                             `gorm:"column:trade_id;default:null;comment:'支付订单'" json:"trade_id"`
+	TradeNO          string                          `gorm:"column:trade_no;default:null;comment:'支付订单'" json:"trade_no"`
+	PaymentAmount    string                          `gorm:"column:payment_amount;;comment:'支付金额'"`
+	PaymentWay       PaymentWay                      `gorm:"column:payment_way;enum('wechat','alipay');default:null;comment:'支付渠道'"`
+	PaymentTime      time.Time                       `gorm:"column:payment_time;default:null;comment:'支付时间'" json:"payment_time"`
+	ExpiredTime      time.Time                       `gorm:"column:expired_time;default:null;comment:'超时时间'" json:"expired_time"`
+	Status           OrderStatus                     `gorm:"column:status;type:enum('pending','processing','paid','closed','refund');default:'pending';comment:'订单状态'" json:"status"`
+	RefundTradeId    string                          `column:"refund_trade_id;null;comment:'退款金额'" json:"refund_trade_id"`
+	RefundAmount     string                          `gorm:"column:refund_amount;size:255;default:null;comment:'退款金额'" json:"refund_amount"`
+	RefundStatus     RefundStatus                    `gorm:"column:refund_status;type:enum('pending','processing','failed','success');default:null;comment:'退款状态'" json:"refund_status"`
+	RefundFinishTime time.Time                       `gorm:"column:refund_finish_time;default:null;comment:'退款完成时间'" json:"refund_finish_time"`
+	Remark           string                          `gorm:"column:remark;size:255;default:null;comment:'备注'" json:"remark"`
+	ValidDuration    string                          `gorm:"column:valid_duration;default:null;comment:'订单有效期'" json:"valid_duration"`
+	IsDeleted        int                             `gorm:"column:is_deleted;size:1;default:0;comment:'是否删除'" json:"is_deleted"`
+	CreatedTime      time.Time                       `gorm:"column:created_time;default:null;comment:'创建时间'" json:"created_time"`
+	UpdatedTime      time.Time                       `gorm:"column:updated_time;default:null;comment:'更新时间'" json:"updated_time"`
 }
 
 func (pr *ProductOrder) BeforeCreate(db *gorm.DB) (err error) {
 	pr.Status = OrderStatusPending
-	pr.CreatedTime = time.Now()
+	createTime := time.Now()
+	expiredTime := createTime.Add(time.Duration(15) * time.Minute)
+	pr.CreatedTime = createTime
+	pr.ExpiredTime = expiredTime
 	pr.IsDeleted = 0
 	return
 }
-func CreateProductOrder(order ProductOrder) (orderNo string, err error) {
+func CreateProductOrder(order ProductOrder) (orderDB ProductOrder, err error) {
 	db := models.Main()
 	err = db.Create(&order).Error
 	var mysqlErr *mysql.MySQLError
 	if errors.As(err, &mysqlErr) && mysqlErr.Number == 1062 {
 		err = gorm.ErrDuplicatedKey
 	}
-	orderNo = order.OrderID
+	orderDB = order
 	return
 }
 
 func GetTotalPageCountByUserId(userId int, status OrderStatus) (total int64, latestId int64) {
 	db := models.Main()
-	query := db.Model(&ProductOrder{}).Select("count(*)").Where("user_id = ? and is_deleted=0 ", userId)
+	query := db.Model(&ProductOrder{}).Select("count(*)").Where("template_user_id = ? and is_deleted=0 ", userId)
 	if string(status) != "" {
 		query.Where("  status=?", status)
 	}
@@ -76,7 +91,7 @@ func GetTotalPageCountByUserId(userId int, status OrderStatus) (total int64, lat
 		logger.Error("查询订单总数失败:%v", err)
 		return 0, 0
 	}
-	queryLatestId := db.Model(&ProductOrder{}).Select("MAX(id)").Where("user_id = ? and is_deleted=0", userId)
+	queryLatestId := db.Model(&ProductOrder{}).Select("MAX(id)").Where("template_user_id = ? and is_deleted=0", userId)
 	if string(status) != "" {
 		query.Where("  status=?", status)
 	}
@@ -94,7 +109,7 @@ func GetOrderPage(latestId int64, offset int, size int, userId int, status Order
 	if string(status) != "" {
 		query.Where("  status=?", status)
 	}
-	query.Where("id<=? and user_id = ? and is_deleted=0 ", latestId, userId)
+	query.Where("id<=? and template_user_id = ? and is_deleted=0 ", latestId, userId)
 	err = query.Order("created_time desc").Offset(offset).Limit(size).Find(&orderList).Error
 	if err != nil {
 		logger.Error("查询订单总数失败:%v", err)
@@ -102,32 +117,73 @@ func GetOrderPage(latestId int64, offset int, size int, userId int, status Order
 	return
 }
 
-func GetOrderDetail(orderId int, userId int) (order ProductOrder, err error) {
+func GetOrderDetail(orderId string, userId int) (order ProductOrder, err error) {
 	db := models.Main()
-	err = db.Model(&ProductOrder{}).Select("*").Where("id = ? and user_id = ? and is_deleted=0 ", orderId, userId).First(&order).Error
+	err = db.Model(&ProductOrder{}).Select("*").Where("order_id = ? and template_user_id = ? and is_deleted=0 ", orderId, userId).First(&order).Error
+	return
+}
+func GetUserOrderByProduct(productId int, userId int, status OrderStatus) (order ProductOrder, err error) {
+	db := models.Main()
+	err = db.Model(&ProductOrder{}).Select("*").Where("product_id = ? and template_user_id = ? and is_deleted=0 and status =?", productId, userId, status).First(&order).Error
 	return
 }
 
 func GetUnPendingOrderCount() (count int, minId int) {
 	db := models.Main()
-	_ = db.Model(&ProductOrder{}).Select("count(*)").Where("is_deleted=0 and status=?", OrderStatusPending).Scan(&count).Error
-	_ = db.Model(&ProductOrder{}).Select("MIN(id)").Where("is_deleted=0 and status =?", OrderStatusPending).Scan(&minId).Error
+	_ = db.Model(&ProductOrder{}).Select("count(*)").Where("is_deleted=0 and status=? and expired_time <= ?", OrderStatusPending, time.Now()).Scan(&count).Error
+	if count > 0 {
+		_ = db.Model(&ProductOrder{}).Select("MIN(id)").Where("is_deleted=0 and status =? and expired_time <= ?", OrderStatusPending, time.Now()).Scan(&minId).Error
+	}
 	return
 }
 
-func GetPendingOrderIds(minId int, limit int) (ids []int, latestId int) {
+func GetExpiredPendingOrderIds(minId int, limit int) (ids []int) {
 	db := models.Main()
-	_ = db.Model(&ProductOrder{}).Select("id").Where("id> ? and status=? and  is_deleted=0 order by id asc", minId, OrderStatusPending).Limit(limit).Scan(&ids).Error
-	latestId = ids[0]
+	_ = db.Model(&ProductOrder{}).Select("id").Where("id>= ? and status=? and  is_deleted=0 and expired_time <= ? order by id asc", minId, OrderStatusPending, time.Now()).Limit(limit).Scan(&ids).Error
 	return
 }
 
-func batchCloseOrder(ids []int) error {
-	//db := models.Main()
-	//_ = db.Model(&ProductOrder{}).Select("id").Where("id> ? and status=? and  is_deleted=0 order by id asc", minId, OrderStatusPending).Limit(limit).Scan(&ids).Error
-	//err = db.Model(&VerificationRecord{}).Where("expired_time < ?", time.Now()).Where("status =?", StatusPending).Update("status", StatusExpired).Error
-	//if err != nil {
-	//	logger.Error("批量过期验证码失败:%v", err)
-	//}
+func BatchCloseOrder(ids []int) (err error) {
+	db := models.Main()
+	err = db.Model(&ProductOrder{}).Where("expired_time < ?", time.Now()).Where("id in ?", ids).Update("status", OrderStatusClosed).Error
+	if err != nil {
+		logger.Error("批量关闭订单失败:%v", err)
+	}
 	return nil
 }
+
+func GetOrderByUser(templateUserId int, orderNo string) (order ProductOrder, err error) {
+	db := models.Main()
+	err = db.Model(&ProductOrder{}).Select("*").Where("template_user_id= ? and order_id=? and is_deleted=0 ", templateUserId, orderNo).First(&order).Error
+	return
+}
+func GetOrderByOrderNo(orderNo string) (order ProductOrder, err error) {
+	db := models.Main()
+	err = db.Model(&ProductOrder{}).Select("*").Where("order_id=? and is_deleted=0 ", orderNo).First(&order).Error
+	return
+}
+func CloseProductOrder(templateUserId int, productOrderNo string) (err error) {
+	db := models.Main()
+	var dbOrder ProductOrder
+	err = db.Model(&ProductOrder{}).Select("*").Where("template_user_id= ? and order_id=? and is_deleted=0 ", templateUserId, productOrderNo).First(&dbOrder).Error
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			logger.Warn("当前用户[template_user_id:%d]不存在订单编号为%s的订单", templateUserId, productOrderNo)
+			err = errors.New(fmt.Sprintf("当前用户[template_user_id:%d]不存在订单编号为%s的订单", templateUserId, productOrderNo))
+			return
+		}
+		logger.Error("获取订单信息失败:%v", err)
+	}
+	if dbOrder.Status == OrderStatusPaid {
+		logger.Error("订单已支付,无法关闭订单[order_id:%s]", productOrderNo)
+		err = errors.New("订单已支付,无法关闭订单")
+		return
+	}
+	if dbOrder.Status == OrderStatusRefund {
+		logger.Error("订单已售后,无法关闭订单[order_id:%s]", productOrderNo)
+		err = errors.New("订单已售后,无法关闭订单")
+		return
+	}
+	err = db.Model(&ProductOrder{}).Where("template_user_id= ? and order_id=? and is_deleted=0 ", templateUserId, productOrderNo).Update("status", OrderStatusClosed).Error
+	return
+}

+ 25 - 0
models/order/refund_deal_flow.go

@@ -0,0 +1,25 @@
+package order
+
+import (
+	"eta/eta_mini_ht_api/models"
+	"time"
+)
+
+type RefundDealFlow struct {
+	ID             int       `gorm:"column:id;primaryKey"`
+	OperatorUserID int       `gorm:"column:operator_user_id"`
+	ProductOrderNo string    `gorm:"column:product_order_no"`
+	RefundOrderNo  string    `gorm:"column:refund_order_no"`
+	CreatedTime    time.Time `gorm:"column:created_time"`
+}
+
+// TableName 返回表名
+func (*RefundDealFlow) TableName() string {
+	return "refund_deal_flow"
+}
+
+func GetRefundFlow(refundOrderNo string) (flow RefundDealFlow, err error) {
+	db := models.Main()
+	err = db.Select("*").Where("refund_order_no=?", refundOrderNo).First(&flow).Error
+	return
+}

+ 133 - 0
models/order/trade_order.go

@@ -0,0 +1,133 @@
+package order
+
+import (
+	"eta/eta_mini_ht_api/models"
+	"gorm.io/gorm"
+	"time"
+)
+
+type PaymentStatus string
+type PaymentType string
+type PaymentWay string
+
+const (
+	PaymentStatusPending    PaymentStatus = "pending"
+	PaymentStatusProcessing PaymentStatus = "processing"
+	PaymentStatusDone       PaymentStatus = "done"
+	PaymentStatusFailed     PaymentStatus = "failed"
+
+	PaymentTypePay    PaymentType = "pay"
+	PaymentTypeRefund PaymentType = "refund"
+	WechatPayWay      PaymentWay  = "wechat"
+	AliPayWay         PaymentWay  = "alipay"
+)
+
+type TradeOrder struct {
+	ID               int           `gorm:"column:id;primaryKey"`
+	TransactionID    string        `gorm:"column:transaction_id;type:varchar(255);comment:第三方平台ID"`
+	OrgTransactionID string        `gorm:"column:org_transaction_id;type:varchar(255);comment:第三方平台ID"`
+	ProductOrderID   string        `gorm:"column:product_order_id;type:varchar(255);comment:商品订单号"`
+	ProductName      string        `gorm:"column:product_name;type:varchar(255);comment:商品名称"`
+	RealName         string        `gorm:"column:real_name;default:null;comment:'姓名'" json:"real_name"`
+	AreaCode         string        `gorm:"column:area_code;default:null;comment:'手机区号'" json:"area_code"`
+	Mobile           string        `gorm:"column:mobile;default:null;comment:'手机号'" json:"mobile"`
+	PaymentAccount   string        `gorm:"column:payment_account;type:varchar(255);comment:支付账号"`
+	PaymentWay       PaymentWay    `gorm:"column:payment_way;type:enum('wechat');comment:支付渠道"`
+	Amount           string        `gorm:"column:amount;type:varchar(20);comment:支付金额"`
+	Currency         string        `gorm:"column:currency;type:varchar(255);comment:货币"`
+	MerchantID       string        `gorm:"column:merchant_id;type:varchar(255);comment:商户id"`
+	UserID           int           `gorm:"column:user_id;type:int(11);comment:用户id"`
+	TemplateUserID   int           `gorm:"column:template_user_id;type:int(11);comment:临时用户id"`
+	PaymentType      PaymentType   `gorm:"column:payment_type;type:enum('pay','refund');comment:订单类型"`
+	PaymentStatus    PaymentStatus `gorm:"column:payment_status;type:enum('pending','processing','done','failed');comment:支付状态"`
+	DealTime         time.Time     `gorm:"column:deal_time;type:datetime;comment:完成时间"`
+	CreatedTime      time.Time     `gorm:"column:created_time;type:datetime;comment:创建时间"`
+	UpdatedTime      time.Time     `gorm:"column:updated_time;type:datetime;comment:更新时间"`
+}
+
+func (to *TradeOrder) TableName() string {
+	return "trade_orders"
+}
+
+func (to *TradeOrder) BeforeCreate(_ *gorm.DB) (err error) {
+	to.CreatedTime = time.Now()
+	to.PaymentStatus = PaymentStatusPending
+	return
+}
+
+func GetUnFailedTradeFlowByProductOrder(no string) (list []TradeOrder, err error) {
+	db := models.Main()
+	err = db.Where("product_order_id=? and payment_status!=? order by updated_time,created_time desc", no, PaymentStatusFailed).Find(&list).Error
+	return
+}
+func GetTradeOrderByNo(tradeOrderNo string, paymentType PaymentType) (order TradeOrder, err error) {
+	db := models.Main()
+	err = db.Where("transaction_id=? and payment_type =?", tradeOrderNo, paymentType).First(&order).Error
+	return
+}
+
+func DealRefundOrder(order TradeOrder, isSuccess bool) (err error) {
+	var paymentStatus PaymentStatus
+	var refundStatus RefundStatus
+	if isSuccess {
+		paymentStatus = PaymentStatusDone
+		refundStatus = RefundStatusSuccess
+	} else {
+		paymentStatus = PaymentStatusFailed
+		refundStatus = RefundStatusFailed
+	}
+	db := models.Main()
+	tx := db.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+			return
+		}
+		tx.Commit()
+	}()
+	err = tx.Model(&TradeOrder{}).Where("id=?", order.ID).Updates(map[string]interface{}{
+		"payment_status": paymentStatus,
+		"deal_time":      time.Now(),
+	}).Error
+	if err != nil {
+		return
+	}
+	err = tx.Model(&ProductOrder{}).Where("order_id=?", order.ProductOrderID).Updates(map[string]interface{}{
+		"refund_status":      refundStatus,
+		"refund_finish_time": time.Now(),
+	}).Error
+	return
+}
+
+func DealPaymentOrder(order TradeOrder, isSuccess bool, validDuration string) (err error) {
+	var paymentStatus PaymentStatus
+	if isSuccess {
+		paymentStatus = PaymentStatusDone
+	} else {
+		paymentStatus = PaymentStatusFailed
+	}
+	db := models.Main()
+	tx := db.Begin()
+	defer func() {
+		if err != nil {
+			tx.Rollback()
+			return
+		}
+		tx.Commit()
+	}()
+	err = tx.Model(&TradeOrder{}).Where("id=?", order.ID).Updates(map[string]interface{}{
+		"payment_status": paymentStatus,
+		"deal_time":      time.Now(),
+	}).Error
+	if err != nil {
+		return
+	}
+	if isSuccess {
+		err = tx.Model(&ProductOrder{}).Where("order_id=?", order.ProductOrderID).Updates(map[string]interface{}{
+			"status":         OrderStatusPaid,
+			"valid_duration": validDuration,
+		}).Error
+	}
+
+	return
+}

+ 49 - 28
models/report/report.go

@@ -6,6 +6,7 @@ import (
 	"eta/eta_mini_ht_api/common/utils/date"
 	silce_utils "eta/eta_mini_ht_api/common/utils/silce"
 	"eta/eta_mini_ht_api/models"
+	permissionDao "eta/eta_mini_ht_api/models/config"
 	"fmt"
 	"gorm.io/gorm"
 	"gorm.io/gorm/clause"
@@ -25,7 +26,7 @@ const (
 	StatusUnPublish ReportStatus = "UNPUBLISH"
 
 	MaxBatchNum   = 1000
-	CommonColumns = "id,org_id,author,abstract,title,source,cover_src,published_time,status,plate_name"
+	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"
 )
 
@@ -46,16 +47,12 @@ type Report struct {
 	UpdatedTime   time.Time    `gorm:"column:updated_time;comment:'修改时间'" json:"updated_time"`
 }
 
-func GetIdsByPlateNames(plateName []string) (ids []int, err error) {
+func GetOrgIdsByPlateNames(plateName []string) (ids []int, err error) {
 	db := models.Main()
 	err = db.Model(&Report{}).Select("distinct org_id").Where(" source ='HT' and plate_name in ?  ", plateName).Scan(&ids).Error
 	return
 }
-func GetReportByPlateNames(plateName []string) (reportList []Report, err error) {
-	db := models.Main()
-	err = db.Model(&Report{}).Select("org_id,id").Where(" source ='HT' and plate_name in ?  ", plateName).Find(&reportList).Error
-	return
-}
+
 func BatchInsertReport(list *[]Report) (err error) {
 	db := models.Main()
 	//手动事务
@@ -225,25 +222,26 @@ func GetReportIdListByOrgIds(orgIds map[string][]int) (ids []int, err error) {
 	}
 	return
 }
-func GetMaxIdByPermissionIds(orgIds map[string][]int) (total int64, maxId int64, err error) {
+func GetMaxIdByPermissionIds(orgIds map[string][]int, disCardIds []int) (total int64, maxId int64, err error) {
 	db := models.Main()
 	if len(orgIds["ETA"]) == 0 && len(orgIds["HT"]) == 0 {
-		//err := db.Model(&Report{}).Select("MAX(id) id").Where("status = ?", StatusPublish).Scan(&maxId).Error
-		//if err != nil {
-		//	logger.Error("获取报告最大ID失败:%v", err)
-		//	return 0
-		//}
 		maxId = 0
 		total = 0
 		return
 	}
+	countQuery := db.Model(&Report{}).Select("count(*)").Where("status = ? ", StatusPublish)
+	maxQuery := db.Model(&Report{}).Select("MAX(id) id").Where("status = ? ", StatusPublish)
+	if len(disCardIds) > 0 {
+		countQuery.Where("id not in ?", disCardIds)
+		maxQuery.Where("id not in ?", disCardIds)
+	}
 	if len(orgIds["ETA"]) == 0 {
-		err = db.Model(&Report{}).Select("count(*)").Where("status = ?", StatusPublish).Where(" source='HT' and org_id in ?", orgIds["HT"]).Scan(&total).Error
+		err = countQuery.Where(" source='HT' and org_id in ?", orgIds["HT"]).Scan(&total).Error
 		if err != nil {
 			logger.Error("获取记录条数失败:%v", err)
 			return
 		}
-		err = db.Model(&Report{}).Select("MAX(id) id").Where("status = ?", StatusPublish).Where(" source='HT' and org_id in ?", orgIds["HT"]).Scan(&maxId).Error
+		err = maxQuery.Where(" source='HT' and org_id in ? ", orgIds["HT"]).Scan(&maxId).Error
 		if err != nil {
 			logger.Error("获取报告最大ID失败:%v", err)
 			return
@@ -251,24 +249,24 @@ func GetMaxIdByPermissionIds(orgIds map[string][]int) (total int64, maxId int64,
 		return
 	}
 	if len(orgIds["HT"]) == 0 {
-		err = db.Model(&Report{}).Select("count(*)").Where("status = ?", StatusPublish).Where(" source='ETA' and org_id in ?", orgIds["ETA"]).Scan(&total).Error
+		err = countQuery.Where(" source='ETA' and org_id in ? ", orgIds["ETA"]).Scan(&total).Error
 		if err != nil {
 			logger.Error("获取报告最大ID失败:%v", err)
 			return
 		}
-		err = db.Model(&Report{}).Select("MAX(id) id").Where("status = ?", StatusPublish).Where(" source='ETA' and org_id in ?", orgIds["ETA"]).Scan(&maxId).Error
+		err = maxQuery.Where(" source='ETA' and org_id in ? ", orgIds["ETA"]).Scan(&maxId).Error
 		if err != nil {
 			logger.Error("获取报告最大ID失败:%v", err)
 			return
 		}
 		return
 	}
-	err = db.Model(&Report{}).Select("count(*)").Where("status = ?", StatusPublish).Where(" source='ETA' and org_id in ?", orgIds["ETA"]).Or("source='HT' and org_id in ?", orgIds["HT"]).Scan(&total).Error
+	err = countQuery.Where(" source='ETA' and org_id in ? ", orgIds["ETA"]).Or("source='HT' and org_id in ?", orgIds["HT"]).Scan(&total).Error
 	if err != nil {
 		logger.Error("获取报告最大ID失败:%v", err)
 		return
 	}
-	err = db.Model(&Report{}).Select("MAX(id) id").Where("status = ?", StatusPublish).Where(" source='ETA' and org_id in ?", orgIds["ETA"]).Or("source='HT' and org_id in ?", orgIds["HT"]).Scan(&maxId).Error
+	err = maxQuery.Where(" source='ETA' and org_id in ?", orgIds["ETA"]).Or("source='HT' and org_id in ?", orgIds["HT"]).Scan(&maxId).Error
 	if err != nil {
 		logger.Error("获取报告最大ID失败:%v", err)
 		return
@@ -323,11 +321,7 @@ func GetReportPage(latestId int64, limit int, offset int) (list []Report, err er
 	return
 }
 
-func GetReportPageByOrgIds(latestId int64, limit int, offset int, orgIds map[string][]int, searchAll bool) (list []Report, err error) {
-	//有传入品种 但是没有筛选出来的已经过滤掉了,现在orgIds为空只有是
-	if searchAll {
-		return GetReportPage(latestId, limit, offset)
-	}
+func GetReportPageByOrgIds(latestId int64, limit int, offset int, orgIds map[string][]int, discardIds []int) (list []Report, err error) {
 	if latestId < 0 {
 		err = errors.New("非法的id参数")
 		logger.Error("非法的id参数:%d", latestId)
@@ -338,15 +332,19 @@ func GetReportPageByOrgIds(latestId int64, limit int, offset int, orgIds map[str
 		logger.Error("非法的limit参数:%d", limit)
 	}
 	db := models.Main()
+	listQuery := db.Model(&Report{}).Select(CommonColumns).Where("status = ? ", StatusPublish).Where("id<= ?", latestId)
+	if len(discardIds) > 0 {
+		listQuery.Where("id not in ?", discardIds)
+	}
 	if len(orgIds["ETA"]) == 0 {
-		err = db.Select(CommonColumns).Where("id<= ?", latestId).Where("status = ?", StatusPublish).Where(" source='HT' and org_id in ?", orgIds["HT"]).Order("published_time desc").Limit(limit).Offset(offset).Find(&list).Error
+		err = listQuery.Where(" source='HT' and org_id in ?", orgIds["HT"]).Order("published_time desc").Limit(limit).Offset(offset).Find(&list).Error
 		return
 	}
 	if len(orgIds["HT"]) == 0 {
-		err = db.Select(CommonColumns).Where("id<= ?", latestId).Where("status = ?", StatusPublish).Where("source='ETA' and org_id in ?", orgIds["ETA"]).Order("published_time desc").Limit(limit).Offset(offset).Find(&list).Error
+		err = listQuery.Where("source='ETA' and org_id in ?", orgIds["ETA"]).Order("published_time desc").Limit(limit).Offset(offset).Find(&list).Error
 		return
 	}
-	err = db.Select(CommonColumns).Where("id<= ?", latestId).Where("status = ?", StatusPublish).Where("(source='ETA' and org_id in ? ) or (source='HT' and org_id in ?) ", orgIds["ETA"], orgIds["HT"]).Order("published_time desc").Limit(limit).Offset(offset).Find(&list).Error
+	err = listQuery.Where("(source='ETA' and org_id in ? ) or (source='HT' and org_id in ?) ", orgIds["ETA"], orgIds["HT"]).Order("published_time desc").Limit(limit).Offset(offset).Find(&list).Error
 	return
 }
 
@@ -393,6 +391,29 @@ func GetReportClassifyById(id int) (classifyId int, err error) {
 	db := models.Main()
 	err = db.Model(&Report{}).
 		Select("classify_id").
-		Where("orgId =? and source ='ETA' ", id).Scan(&classifyId).Error
+		Where("org_id=? and source ='ETA' ", id).Scan(&classifyId).Error
+	return
+}
+
+func CountPermissionWeight(ids []int) (list []permissionDao.PermissionWeight, err error) {
+	db := models.Main()
+	sql := `select a.permission_id,count(*) from 
+(select p.permission_id from reports r LEFT JOIN(SELECT *from permissions) p on p.name=r.plate_name where r.id in (?) and r.source='HT'
+UNION ALL
+select cpm.permission_id from reports  r LEFT JOIN (SELECT classify_id,permission_id FROM permission_classify_mapping) cpm on cpm.classify_id=r.classify_id  where r.id in (?) and r.source='ETA') a GROUP BY a.permission_id`
+	err = db.Raw(sql, ids, ids).Find(&list).Error
+	return
+}
+
+func GetHiddenReportIds(classifyIds []int, plateNames []string) (reportIds []int, err error) {
+	db := models.Main()
+	exc := db.Model(&Report{}).Select("id")
+	if len(classifyIds) > 0 {
+		exc.Where("(source='ETA' and classify_id in ?)", classifyIds)
+	}
+	if len(plateNames) > 0 {
+		exc.Or("(source='HT' and plate_name in ?)", plateNames)
+	}
+	err = exc.Scan(&reportIds).Error
 	return
 }

+ 1 - 1
models/sys/sys_config.go

@@ -13,7 +13,7 @@ type SysConfig struct {
 	ConfigName  string    `gorm:"column:config_name" json:"config_name"`
 	ConfigType  string    `gorm:"column:config_type;type:enum('string','int','byte')" json:"config_type"`
 	Deleted     int       `gorm:"column:deleted" json:"deleted"`
-	StrValue    string    `gorm:"column:strValue" json:"str_value"`
+	StrValue    string    `gorm:"column:str_value" json:"str_value"`
 	IntValue    int       `gorm:"column:int_value" json:"int_value"`
 	ByteValue   string    `gorm:"column:byte_value" json:"byte_value"`
 	CreatedTime time.Time `gorm:"column:created_time" json:"created_time"`

+ 1 - 1
models/user/template_user.go

@@ -27,7 +27,7 @@ type TemplateUser struct {
 	OpenId           string        `gorm:"column:open_id;type:varchar(50);comment:open_id"`
 	GzhOpenId        string        `gorm:"column:gzh_open_id;type:varchar(255);comment:gzh_open_id"`
 	UnionId          string        `gorm:"column:union_id;type:varchar(50);comment:union_id"`
-	ReadCount        int           `gorm:"column:read_count;type:int(11);comment:阅读次数"`
+	unt              int           `gorm:"column:read_count;type:int(11);comment:阅读次数"`
 	FollowingGzh     int           `gorm:"column:following_gzh;type:int(1);comment:是否关注公众号"`
 	LastReadTime     time.Time     `gorm:"column:last_read_time;type:timestamps;comment:最后阅读时间"`
 	LastLoginTime    time.Time     `gorm:"column:last_login_time;type:timestamps;comment:最后登录时间"`

+ 0 - 35
models/user/userSubscriptionAccessList.go

@@ -1,35 +0,0 @@
-package user
-
-import (
-	"eta/eta_mini_ht_api/models"
-	"time"
-)
-
-type SubscribeStatus string
-
-const (
-	SubscribeValid   SubscribeStatus = "valid"
-	SubscribeExpired SubscribeStatus = "expired"
-)
-
-// UserSubscriptionAccessList 用户订阅访问列表
-type UserSubscriptionAccessList struct {
-	ID          int             `gorm:"column:id;primaryKey"`
-	UserId      int             `gorm:"column:user_id"`
-	ProductID   int             `gorm:"column:product_id"`
-	ProductName string          `gorm:"column:product_name"`
-	IsPermanent bool            `gorm:"column:is_permanent"`
-	BeginDate   time.Time       `gorm:"column:begin_date"`
-	EndDate     time.Time       `gorm:"column:end_date"`
-	Status      SubscribeStatus `gorm:"column:status;type:enum('valid','expired');default:'valid'"`
-}
-
-func (UserSubscriptionAccessList) TableName() string {
-	return "user_subscription_access_list"
-}
-
-func GetUserSubscribe(productId int, userId int) (userSubscriptionAccessList UserSubscriptionAccessList, err error) {
-	db := models.Main()
-	err = db.Select("id,user_id,product_id,product_name,begin_date,end_date,status").Where("user_id=? and product_id=?", userId, productId).First(&userSubscriptionAccessList).Error
-	return
-}

+ 16 - 12
models/user/user_analyst_follow_list.go

@@ -133,18 +133,7 @@ func GetFollowingAnalystList(userId int) (list []UserAnalystFollowList, err erro
 	err = db.Select(listColumns).Where("user_id = ? and followed = ?", userId, Following).Order("followed_time desc").Find(&list).Error
 	return
 }
-func GetFollowing(userId int, analystId int) string {
-	db := models.Main()
-	var dbFollow UserAnalystFollowList
-	err := db.Model(&UserAnalystFollowList{}).Where("user_id = ? and financial_analyst_id = ?", userId, analystId).First(&dbFollow).Error
-	if err != nil {
-		if !errors.Is(err, gorm.ErrRecordNotFound) {
-			logger.Error("查询用户关注状态失败:%v", err)
-		}
-		return string(Unfollowed)
-	}
-	return string(dbFollow.Followed)
-}
+
 func FollowAnalyst(follow UserAnalystFollowList) (err error) {
 	if follow.Followed == "" {
 		err = errors.New("关注状态非法")
@@ -188,3 +177,18 @@ func GetFollowed(userId int, analystId int) string {
 	}
 	return string(record.Followed)
 }
+func GetFollowing(userId int, analystId int) string {
+	if userId > 0 {
+		db := models.Main()
+		var dbFollow UserAnalystFollowList
+		err := db.Model(&UserAnalystFollowList{}).Where("user_id = ? and financial_analyst_id = ?", userId, analystId).First(&dbFollow).Error
+		if err != nil {
+			if !errors.Is(err, gorm.ErrRecordNotFound) {
+				logger.Error("查询用户关注状态失败:%v", err)
+			}
+			return string(Unfollowed)
+		}
+		return string(dbFollow.Followed)
+	}
+	return string(Unfollowed)
+}

+ 31 - 17
models/user/user_message.go

@@ -3,30 +3,44 @@ package user
 import (
 	logger "eta/eta_mini_ht_api/common/component/log"
 	"eta/eta_mini_ht_api/models"
+	"eta/eta_mini_ht_api/models/message"
 	"gorm.io/gorm"
 	"time"
 )
 
 type MessageType string
+type UserType string
 
 const (
-	UnReadStatus     StatusType = "UNREAD"
-	ReadStatus       StatusType = "READ"
-	MaxBatchNum                 = 1000
-	MyMessageColumns            = "id,source_id,type,message"
+	UnReadStatus     message.StatusType = "UNREAD"
+	ReadStatus       message.StatusType = "READ"
+	MaxBatchNum                         = 1000
+	MyMessageColumns                    = "id,source_id,type,message"
+
+	Customer UserType = "user"
+	Admin    UserType = "admin"
+)
+
+var (
+	messageTypeMap = []message.SourceType{
+		message.ReportSourceType,
+		message.VideoSourceType,
+		message.AudioSourceType,
+	}
 )
 
 // UserMessage 表示 user_message 表的模型
 type UserMessage struct {
-	Id          int        `gorm:"primaryKey;autoIncrement;column:id"`
-	AnalystId   int        `gorm:"column:analyst_id"`
-	UserId      int        `gorm:"column:user_id"`
-	SourceId    int        `gorm:"column:source_id"`
-	Message     string     `gorm:"column:message"`
-	Type        SourceType `gorm:"column:type;type:enum('REPORT','VIDEO','AUDIO')"`
-	Status      StatusType `gorm:"column:status;type:enum('UNREAD','READ')"`
-	CreatedTime time.Time  `gorm:"column:created_time;type:timestamps;comment:创建时间"`
-	UpdatedTime time.Time  `gorm:"column:updated_time"`
+	Id          int                `gorm:"primaryKey;autoIncrement;column:id"`
+	AnalystId   int                `gorm:"column:analyst_id"`
+	UserId      int                `gorm:"column:user_id"`
+	UserType    UserType           `gorm:"column:user_type;type:enum('user','admin')"`
+	SourceId    int                `gorm:"column:source_id"`
+	Message     string             `gorm:"column:message"`
+	Type        message.SourceType `gorm:"column:type;type:enum('REPORT','VIDEO','AUDIO')"`
+	Status      message.StatusType `gorm:"column:status;type:enum('UNREAD','READ')"`
+	CreatedTime time.Time          `gorm:"column:created_time;type:timestamps;comment:创建时间"`
+	UpdatedTime time.Time          `gorm:"column:updated_time"`
 }
 
 func (u *UserMessage) BeforeCreate(_ *gorm.DB) (err error) {
@@ -56,7 +70,7 @@ func BatchInsertMessage(messages []UserMessage) bool {
 func NeedNotice(userId int, analystId int) bool {
 	db := models.Main()
 	var count int
-	err := db.Model(&UserMessage{}).Select("count(*)").Where("user_id =? and analyst_id =? and status=?", userId, analystId, UnReadStatus).Scan(&count).Error
+	err := db.Model(&UserMessage{}).Select("count(*)").Where("user_id =? and user_type=? and analyst_id =? and status=?", userId, Customer, analystId, UnReadStatus).Scan(&count).Error
 	if err != nil {
 		logger.Error("统计未读消息失败:%v", err)
 		return false
@@ -66,13 +80,13 @@ func NeedNotice(userId int, analystId int) bool {
 
 func GetUnReadMessageList(userId int) (messages []UserMessage, err error) {
 	db := models.Main()
-	err = db.Select(MyMessageColumns).Where("user_id=?  and status=?", userId, UnReadStatus).Order("created_time desc").Find(&messages).Error
+	err = db.Select(MyMessageColumns).Where("user_id=? and user_type=? and status=?", userId, Customer, UnReadStatus).Order("created_time desc").Find(&messages).Error
 	return
 }
 
 func ReadMessage(userId int, messageId int) bool {
 	db := models.Main()
-	err := db.Model(&UserMessage{}).Where("id=? and user_id=?", messageId, userId).Update("status", ReadStatus).Error
+	err := db.Model(&UserMessage{}).Where("id=? and user_id=?  and user_type=?", messageId, userId, Customer).Update("status", ReadStatus).Error
 	if err != nil {
 		return false
 	}
@@ -81,7 +95,7 @@ func ReadMessage(userId int, messageId int) bool {
 
 func ReadMessages(userId int, analystId int) bool {
 	db := models.Main()
-	err := db.Model(&UserMessage{}).Where("user_id=? and analyst_id =?", userId, analystId).Update("status", ReadStatus).Error
+	err := db.Model(&UserMessage{}).Where("user_id=? and user_type=? and analyst_id =? ", userId, Customer, analystId).Update("status", ReadStatus).Error
 	if err != nil {
 		return false
 	}

+ 18 - 16
models/user/user_source_click_flow.go

@@ -2,6 +2,7 @@ package user
 
 import (
 	"eta/eta_mini_ht_api/models"
+	"eta/eta_mini_ht_api/models/message"
 	"gorm.io/gorm"
 	"time"
 )
@@ -12,19 +13,20 @@ const (
 
 // UserReportClickFlow 用户点击研报流水记录
 type UserSourceClickFlow struct {
-	ID                  int        `gorm:"column:id;primaryKey;autoIncrement:'id'"`
-	TraceId             string     `gorm:"column:trace_id"`
-	UserID              int        `gorm:"column:user_id"` // 用户ID\
-	Mobile              string     `gorm:"column:mobile"`
-	SourceId            int        `gorm:"column:source_id"` // 研报ID
-	SourceType          SourceType `gorm:"column:source_type;type:enum('REPORT','VIDEO','AUDIO')"`
-	ClickTime           time.Time  `gorm:"column:click_time"` // 点击时间
-	ReadDurationSeconds int64      `gorm:"column:read_duration_seconds"`
-	IPAddress           string     `gorm:"column:ip_address"` // IP地址
-	Location            string     `gorm:"column:location"`   // 地理位置
-	Hidden              bool       `gorm:"column:hidden;type:tinyint(1);default:0"`
-	Referer             string     `gorm:"column:referer"`         // 来源页面
-	AdditionalData      string     `gorm:"column:additional_data"` // 额外数据
+	ID                  int                `gorm:"column:id;primaryKey;autoIncrement:'id'"`
+	TraceId             string             `gorm:"column:trace_id"`
+	UserID              int                `gorm:"column:user_id"` // 用户ID\
+	Mobile              string             `gorm:"column:mobile"`
+	SourceId            int                `gorm:"column:source_id"`    // 研报ID
+	SourceTitle         string             `gorm:"column:source_title"` // 研报ID
+	SourceType          message.SourceType `gorm:"column:source_type;type:enum('REPORT','VIDEO','AUDIO')"`
+	ClickTime           time.Time          `gorm:"column:click_time"` // 点击时间
+	ReadDurationSeconds int64              `gorm:"column:read_duration_seconds"`
+	IPAddress           string             `gorm:"column:ip_address"` // IP地址
+	Location            string             `gorm:"column:location"`   // 地理位置
+	Hidden              bool               `gorm:"column:hidden;type:tinyint(1);default:0"`
+	Referer             string             `gorm:"column:referer"`         // 来源页面
+	AdditionalData      string             `gorm:"column:additional_data"` // 额外数据
 }
 
 func (v *UserSourceClickFlow) BeforeCreate(tx *gorm.DB) (err error) {
@@ -52,19 +54,19 @@ type CountClickFlowById struct {
 	Count    int `gorm:"column:count"`
 }
 
-func GetTimeDurationReportCountsById(begin string, end string, limit int, sourceType SourceType) (ids []CountClickFlowById, err error) {
+func GetTimeDurationReportCountsById(begin string, end string, limit int, sourceType message.SourceType) (ids []CountClickFlowById, err error) {
 	db := models.Main()
 	err = db.Table(userSourceClickFlows).Select("source_id,count(*) count").Where("source_id>0 and source_type=? and DATE(click_time) BETWEEN ? AND ? AND hidden =0", sourceType, begin, end).Group("source_id").Order("count desc").Limit(limit).Scan(&ids).Error
 	return
 }
 
-func HiddenFlows(id int, sourceType SourceType) (err error) {
+func HiddenFlows(id int, sourceType message.SourceType) (err error) {
 	db := models.Main()
 	err = db.Table(userSourceClickFlows).Where("source_type=? and source_id =?", sourceType, id).Update("hidden", 1).Error
 	return
 }
 
-func ShowFlows(id int, sourceType SourceType) (err error) {
+func ShowFlows(id int, sourceType message.SourceType) (err error) {
 	db := models.Main()
 	err = db.Table(userSourceClickFlows).Where("source_type=? and source_id =?", sourceType, id).Update("hidden", 0).Error
 	return

+ 148 - 42
routers/commentsRouter.go

@@ -7,6 +7,17 @@ import (
 
 func init() {
 
+    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",
+            Router: `/search`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("key"),
+			),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/media:MediaController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/media:MediaController"],
         beego.ControllerComments{
             Method: "Count",
@@ -36,6 +47,7 @@ func init() {
             MethodParams: param.Make(
 				param.New("mediaType"),
 				param.New("mediaId"),
+				param.New("productId"),
 			),
             Filters: nil,
             Params: nil})
@@ -54,13 +66,96 @@ func init() {
 
     beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:OrderController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:OrderController"],
         beego.ControllerComments{
-            Method: "CreateProductOrder",
-            Router: `/createProductOrder`,
+            Method: "PreviewProductOrder",
+            Router: `/previewProductOrder`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("productId"),
+			),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"],
+        beego.ControllerComments{
+            Method: "CloseOrder",
+            Router: `/closeOrder`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"],
+        beego.ControllerComments{
+            Method: "GetOrderDetail",
+            Router: `/orderDetail`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("orderNo"),
+			),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"],
+        beego.ControllerComments{
+            Method: "GetOrderList",
+            Router: `/orderList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("orderStatus"),
+			),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"],
+        beego.ControllerComments{
+            Method: "GetOrderNo",
+            Router: `/orderNo`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"],
+        beego.ControllerComments{
+            Method: "SubscribeProduct",
+            Router: `/subscribe`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/order:SubscribeController"],
+        beego.ControllerComments{
+            Method: "SubscribeList",
+            Router: `/subscribeList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("productType"),
+			),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/payment:PaymentController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/payment:PaymentController"],
+        beego.ControllerComments{
+            Method: "PayOrder",
+            Router: `/payOrder`,
             AllowHTTPMethods: []string{"post"},
             MethodParams: param.Make(),
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/product:ProductController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/product:ProductController"],
+        beego.ControllerComments{
+            Method: "ProductList",
+            Router: `/ProductList`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("productType"),
+				param.New("permissionIds"),
+			),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/product:ProductController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/product:ProductController"],
         beego.ControllerComments{
             Method: "GetProductInfo",
@@ -72,6 +167,29 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/product:ProductController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/product:ProductController"],
+        beego.ControllerComments{
+            Method: "ProductSearch",
+            Router: `/productSearch`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("key"),
+				param.New("isSignal"),
+			),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/product:ProductController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/product:ProductController"],
+        beego.ControllerComments{
+            Method: "RelatePackage",
+            Router: `/relatePackage`,
+            AllowHTTPMethods: []string{"get"},
+            MethodParams: param.Make(
+				param.New("productId"),
+			),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/report:ReportController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/report:ReportController"],
         beego.ControllerComments{
             Method: "Count",
@@ -133,6 +251,7 @@ func init() {
             AllowHTTPMethods: []string{"get"},
             MethodParams: param.Make(
 				param.New("reportId"),
+				param.New("productId"),
 			),
             Filters: nil,
             Params: nil})
@@ -159,6 +278,15 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:AccountController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:AccountController"],
+        beego.ControllerComments{
+            Method: "CheckUserStatus",
+            Router: `/checkUserStatus/`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:AccountController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:AccountController"],
         beego.ControllerComments{
             Method: "GetRiskInfoToken",
@@ -285,46 +413,6 @@ func init() {
             Filters: nil,
             Params: nil})
 
-    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"],
-        beego.ControllerComments{
-            Method: "SubscribeProduct",
-            Router: `/subscribe/createOrder`,
-            AllowHTTPMethods: []string{"post"},
-            MethodParams: param.Make(),
-            Filters: nil,
-            Params: nil})
-
-    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"],
-        beego.ControllerComments{
-            Method: "GetOrderDetail",
-            Router: `/subscribe/orderDetail`,
-            AllowHTTPMethods: []string{"get"},
-            MethodParams: param.Make(
-				param.New("orderId"),
-			),
-            Filters: nil,
-            Params: nil})
-
-    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"],
-        beego.ControllerComments{
-            Method: "GetOrderList",
-            Router: `/subscribe/orderList`,
-            AllowHTTPMethods: []string{"get"},
-            MethodParams: param.Make(
-				param.New("orderStatus"),
-			),
-            Filters: nil,
-            Params: nil})
-
-    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/user:SubscribeController"],
-        beego.ControllerComments{
-            Method: "GetOrderNo",
-            Router: `/subscribe/orderNo`,
-            AllowHTTPMethods: []string{"get"},
-            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",
@@ -464,6 +552,24 @@ func init() {
             Filters: nil,
             Params: nil})
 
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/web_hook:HTFuturesTradeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/web_hook:HTFuturesTradeController"],
+        beego.ControllerComments{
+            Method: "InformPaymentResult",
+            Router: `/v1/payment/informPaymentResult`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
+    beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/web_hook:HTFuturesTradeController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers/web_hook:HTFuturesTradeController"],
+        beego.ControllerComments{
+            Method: "InformRefundResult",
+            Router: `/v1/payment/informRefundResult`,
+            AllowHTTPMethods: []string{"post"},
+            MethodParams: param.Make(),
+            Filters: nil,
+            Params: nil})
+
     beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers:WebSocketController"] = append(beego.GlobalControllerRouter["eta/eta_mini_ht_api/controllers:WebSocketController"],
         beego.ControllerComments{
             Method: "Connect",

+ 16 - 2
routers/router.go

@@ -2,7 +2,10 @@ package routers
 
 import (
 	"eta/eta_mini_ht_api/controllers"
+	"eta/eta_mini_ht_api/controllers/home"
 	"eta/eta_mini_ht_api/controllers/media"
+	"eta/eta_mini_ht_api/controllers/order"
+	"eta/eta_mini_ht_api/controllers/payment"
 	"eta/eta_mini_ht_api/controllers/product"
 	"eta/eta_mini_ht_api/controllers/report"
 	"eta/eta_mini_ht_api/controllers/sys"
@@ -32,7 +35,11 @@ func init() {
 		web.NSNamespace("/user",
 			web.NSInclude(
 				&user.UserController{},
-				&user.SubscribeController{},
+			),
+		),
+		web.NSNamespace("/home",
+			web.NSInclude(
+				&home.HomeController{},
 			),
 		),
 		web.NSNamespace("/auth",
@@ -63,6 +70,7 @@ func init() {
 		web.NSNamespace("/webhook ",
 			web.NSInclude(
 				&web_hook.HTFuturesAccountController{},
+				&web_hook.HTFuturesTradeController{},
 			),
 		),
 		web.NSNamespace("/product",
@@ -72,7 +80,8 @@ func init() {
 		),
 		web.NSNamespace("/order",
 			web.NSInclude(
-				&user.OrderController{},
+				&order.OrderController{},
+				&order.SubscribeController{},
 			),
 		),
 		web.NSNamespace("/ws",
@@ -85,6 +94,11 @@ func init() {
 				&sys.SysController{},
 			),
 		),
+		web.NSNamespace("/payment",
+			web.NSInclude(
+				&payment.PaymentController{},
+			),
+		),
 	)
 	web.AddNamespace(ns)
 }

+ 16 - 10
service/analyst/analyst_service.go

@@ -10,11 +10,14 @@ import (
 )
 
 type Analyst struct {
-	Id           int    `json:"id"`
-	Name         string `json:"name"`
-	HeadImgUrl   string `json:"headImgUrl"`
-	Introduction string `json:"introduction"`
-	Following    string `json:"following"`
+	Id                      int    `json:"id"`
+	Name                    string `json:"name"`
+	HeadImgUrl              string `json:"headImgUrl"`
+	Introduction            string `json:"introduction"`
+	Following               string `json:"following"`
+	Position                string `json:"position"`
+	InvestmentCertificate   string `json:"investmentCertificate"`
+	ProfessionalCertificate string `json:"professionalCertificate"`
 }
 
 func GetAnalystCount() (total int64, latestId int64) {
@@ -44,10 +47,13 @@ func GetAnalystList(pageInfo page.PageInfo, userId int) (analysts []Analyst, err
 
 func convertToAnalyst(analyst analystService.FinancialAnalystDTO) Analyst {
 	return Analyst{
-		Id:           analyst.Id,
-		Name:         analyst.Name,
-		HeadImgUrl:   analyst.HeadImgUrl,
-		Introduction: analyst.Introduction,
-		Following:    "",
+		Id:                      analyst.Id,
+		Name:                    analyst.Name,
+		HeadImgUrl:              analyst.HeadImgUrl,
+		Introduction:            analyst.Introduction,
+		Following:               "",
+		Position:                analyst.Position,
+		ProfessionalCertificate: analyst.ProfessionalCertificate,
+		InvestmentCertificate:   analyst.InvestmentCertificate,
 	}
 }

+ 47 - 0
service/config/config.go

@@ -0,0 +1,47 @@
+package config
+
+import (
+	logger "eta/eta_mini_ht_api/common/component/log"
+	permissionService "eta/eta_mini_ht_api/domian/config"
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+func GetHighestRiskLevel(permissions []permissionService.PermissionDTO) (riskLevelNum int) {
+	for _, permission := range permissions {
+		pRiskNum, err := ParseRiskLevel(permission.RiskLevel)
+		if err != nil {
+			logger.Error("解析风险等级失败:%v", err)
+			continue
+		}
+		if riskLevelNum == 0 {
+			riskLevelNum = pRiskNum
+		} else {
+			if riskLevelNum < pRiskNum {
+				riskLevelNum = pRiskNum
+			}
+		}
+	}
+	return
+}
+
+func ParseRiskLevel(level string) (int, error) {
+	parts := strings.Split(level, "R")
+	if len(parts) < 2 {
+		return 0, fmt.Errorf("无效的风险等级: %s", level)
+	}
+	numberStr := parts[1]
+	number, err := strconv.Atoi(numberStr)
+	if err != nil {
+		return 0, fmt.Errorf("无法将风险等级转换为数字: %s", err)
+	}
+	return number, nil
+}
+func GetPermissionByName(name string) (dto permissionService.PermissionDTO, err error) {
+	return permissionService.GetPermissionByName(name)
+}
+
+func GetSecondPermissionsByClassifyId(id int) (dtoList []permissionService.PermissionDTO, err error) {
+	return permissionService.GetSecondPermissionsByClassifyId(id)
+}

+ 12 - 5
service/facade/ht_account_service.go

@@ -44,7 +44,7 @@ func CheckUserRiskLevel(templateUserId int, productId int, officialUser userServ
 		err = exception.New(exception.IllegalTemplateUserId)
 		return
 	}
-	product, err := product.GetProductInfoById(productId)
+	productInfo, err := product.GetProductInfoById(productId)
 	if err != nil {
 		logger.Error("获取产品信息失败:%v", err)
 		code = 200
@@ -81,16 +81,23 @@ func CheckUserRiskLevel(templateUserId int, productId int, officialUser userServ
 			err = exception.New(exception.QueryRiskMappingError)
 			return
 		}
-		return compareRisk(mapping.ProductRiskLevel, product.RiskLevel)
+		return compareRisk(mapping.ProductRiskLevel, productInfo.RiskLevel)
 	}
-	mapping, mappingErr := permissionService.GetRiskMappingByCustomerRiskLevel(customerInfo.RiskInfo.CorpRiskLevel)
+	var mapping permissionService.CustomerProductRiskMappingDTO
+	var mappingErr error
+	if customerInfo.RiskInfo.CorpRiskLevel == "" {
+		mapping, mappingErr = permissionService.GetRiskMappingByCustomerRiskLevel(userInfo.RiskLevel)
+	} else {
+		mapping, mappingErr = permissionService.GetRiskMappingByCustomerRiskLevel(customerInfo.RiskInfo.CorpRiskLevel)
+	}
+
 	if mappingErr != nil {
 		logger.Error("查询产品风险等级映射失败:%v", mappingErr)
 		code = 200
-		err = exception.New(exception.IllegalProductId)
+		err = exception.NewWithException(exception.QueryRiskMappingError, err.Error())
 		return
 	}
-	return compareRisk(mapping.ProductRiskLevel, product.RiskLevel)
+	return compareRisk(mapping.ProductRiskLevel, productInfo.RiskLevel)
 }
 func parseRiskLevel(level string) (int, error) {
 	parts := strings.Split(level, "R")

+ 38 - 0
service/facade/ht_trade_service.go

@@ -0,0 +1,38 @@
+package facade
+
+import (
+	"eta/eta_mini_ht_api/api"
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/domian/order"
+	"eta/eta_mini_ht_api/service/payment"
+)
+
+const (
+	channelWeChat  = "7"
+	timeoutSeconds = "900"
+)
+
+var (
+	htTradeApi = api.GetPaymentInstance()
+)
+
+func PayOrder(userId, templateUserId int, productOrder order.ProductOrderDTO) (tradeOrderNo string, err error) {
+	//先获取支付对接平台的支付订单号
+	req := api.PaymentOrderRequest{
+		BillNo:          productOrder.OrderID,
+		Body:            productOrder.ProductDescription,
+		ChannelTypeCode: channelWeChat,
+		Optional:        "",
+		Subject:         productOrder.ProductName,
+		TimeoutExpress:  timeoutSeconds,
+		TotalFee:        productOrder.TotalAmount,
+	}
+	tradeOrderNo, err = htTradeApi.CreatePaymentOrder(req)
+	tradeOrderNo = order.GenerateTradeOrderNo()
+	err = payment.CreatePaymentOrder(userId, templateUserId, productOrder.OrderID, tradeOrderNo)
+	if err != nil {
+		logger.Error("创建支付订单失败:%v", err)
+		return
+	}
+	return
+}

+ 132 - 0
service/home/home_service.go

@@ -0,0 +1,132 @@
+package home
+
+import (
+	"encoding/json"
+	"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"
+	mediaDomian "eta/eta_mini_ht_api/domian/media"
+	reportDomain "eta/eta_mini_ht_api/domian/report"
+	mediaService "eta/eta_mini_ht_api/service/media"
+	reportService "eta/eta_mini_ht_api/service/report"
+	userService "eta/eta_mini_ht_api/service/user"
+	"strings"
+	"sync"
+	"time"
+)
+
+var (
+	sortField = []string{"_score:desc"}
+	htConfig  = config.GetConfig(contants.HT).(*config.HTBizConfig)
+)
+
+const (
+	ReportColumn = "title"
+	MediaColumn  = "mediaName"
+)
+
+type ResultType string
+
+const (
+	MediaResultType  ResultType = "media"
+	ReportResultType ResultType = "report"
+)
+
+type HomeSearch struct {
+	ResultType ResultType
+	Media      *mediaDomian.MediaDTO
+	Report     *reportDomain.ReportDTO
+}
+
+func elastic() *es.ESClient {
+	return es.GetInstance()
+}
+func Search(key string, pageInfo page.PageInfo, login bool, templateUserId int) (result []HomeSearch, total int64, err error) {
+	//同步es
+	offset := page.StartIndex(pageInfo.Current, pageInfo.PageSize)
+	sorts := append(sortField, "publishedTime:desc")
+	deadLine := time.Unix(pageInfo.TimeStamp, 0).Format(time.DateTime)
+	request := homeSearch(key, offset, pageInfo.PageSize, sorts, deadLine)
+	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)
+		searchResult := HomeSearch{}
+		err = json.Unmarshal(hit.Source, &searchResult)
+		if err != nil {
+			logger.Error("解析研报数据失败:%v", err)
+			continue
+		}
+		switch hit.Index {
+		case htConfig.GetMediaIndex():
+			searchResult = HomeSearch{
+				ResultType: MediaResultType,
+				Media:      transMedia(hit, content),
+			}
+		case htConfig.GetReportIndex():
+			searchResult = HomeSearch{
+				ResultType: ReportResultType,
+				Report:     transReport(hit, content),
+			}
+		}
+		result = append(result, searchResult)
+	}
+	_, mappingRiskLevel, userRiskStatus, err := userService.GetRiskLevelPermissionList(nil, login, templateUserId)
+	if err != nil {
+		logger.Error("获取用户信息失败:%v", err)
+	}
+	var wg sync.WaitGroup
+	wg.Add(len(result))
+	for i := 0; i < len(result); i++ {
+		go func(searchResult *HomeSearch) {
+			defer wg.Done()
+			switch searchResult.ResultType {
+			case ReportResultType:
+				searchResult.Report, _ = reportService.DealReportInfo(searchResult.Report, login, templateUserId, mappingRiskLevel, userRiskStatus)
+			case MediaResultType:
+				searchResult.Media, _ = mediaService.DealMediaInfo(searchResult.Media, login, templateUserId, mappingRiskLevel, userRiskStatus)
+			}
+		}(&result[i])
+	}
+	wg.Wait()
+	return
+}
+
+func transMedia(hit es.Hit, content map[string][]string) (media *mediaDomian.MediaDTO) {
+	err := json.Unmarshal(hit.Source, &media)
+	if err != nil {
+		logger.Error("解析媒体数据失败:%v", err)
+		return
+	}
+	media.Highlight = content[MediaColumn]
+	media.PublishedTime = media.PublishedTime[:10]
+	media.MediaTitle = media.Highlight[0]
+	media.Type = media.MediaType
+	return
+}
+
+func transReport(hit es.Hit, content map[string][]string) (report *reportDomain.ReportDTO) {
+	err := json.Unmarshal(hit.Source, &report)
+	if err != nil {
+		logger.Error("解析研报数据失败:%v", err)
+		return
+	}
+	report.Highlight = content[ReportColumn]
+	report.Title = report.Highlight[0]
+	report.PublishedTime = report.PublishedTime[:10]
+	report.Type = "report"
+	return
+}
+func homeSearch(key string, from int, to int, sorts []string, publishedTime string) (request *es.ESQueryRequest) {
+	req := new(es.ESQueryRequest)
+	return req.CreateESQueryRequest(strings.Join([]string{htConfig.GetReportIndex(), htConfig.GetMediaIndex()}, ","), "", key, from, to, sorts, es.HomeSearch).ByCondition("status", "PUBLISH").Before(publishedTime, "publishedTime")
+}

+ 170 - 486
service/media/media_service.go

@@ -1,23 +1,18 @@
 package media
 
 import (
-	"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"
 	stringUtils "eta/eta_mini_ht_api/common/utils/string"
 	configService "eta/eta_mini_ht_api/domian/config"
-	permissionService "eta/eta_mini_ht_api/domian/config"
 	mediaService "eta/eta_mini_ht_api/domian/media"
 	productService "eta/eta_mini_ht_api/domian/merchant"
 	userService "eta/eta_mini_ht_api/domian/user"
 	mediaDao "eta/eta_mini_ht_api/models/media"
 	productDao "eta/eta_mini_ht_api/models/merchant"
-	userDao "eta/eta_mini_ht_api/models/user"
-	"eta/eta_mini_ht_api/service/report"
+	"eta/eta_mini_ht_api/service/config"
 	"eta/eta_mini_ht_api/service/user"
-	"fmt"
-	"gorm.io/gorm"
 	"strconv"
 	"strings"
 	"sync"
@@ -35,6 +30,7 @@ type RecordCount struct {
 	UserId     int
 	Mobile     string
 	MediaId    int
+	MediaName  string
 	TraceId    string
 	MediaType  string
 	IpAddress  string
@@ -53,571 +49,259 @@ func convertToMediaCountDTO(record RecordCount) (dto userService.RecordCountDTO)
 }
 
 func CountMedia(count RecordCount) (traceId string, err error) {
-	meida, err := mediaService.GetMediaById(count.MediaType, count.MediaId)
+	media, err := mediaService.GetMediaById(count.MediaType, count.MediaId)
 	if err != nil {
 		err = exception.New(exception.MediaFoundFailed)
 		return
 	}
 	dto := convertToMediaCountDTO(count)
-	return userService.CountMedia(dto, meida.MediaType)
-}
-func GetTotalPageCount(mediaType string) (count int64, latestId int64) {
-	if mediaType == "" {
-		return
-	}
-	return mediaService.GetTotalPageCount(mediaType)
-}
-func GetTotalPageCountByAnalystId(mediaType string, analystId int) (total int64, latestId int64) {
-	if mediaType == "" {
-		return
-	}
-	return mediaService.GetTotalPageCountByAnalystId(mediaType, analystId)
+	dto.SourceTitle = media.MediaName
+	return userService.CountMedia(dto, media.MediaType)
 }
 
-func RangeSearchByAnalyst(mediaType string, analystId int, userId int) (total int64, latestId int64, ids []int) {
-	var 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
-	}
-	//获取产品风险等级
-	if userProfile.RiskLevel == user.RiskUnTest {
-		logger.Error("客户未做风险等级测评,mobile:%v", userProfile.Mobile)
-		return
-	}
-	if userProfile.RiskLevelStatus == user.RiskExpired {
-		logger.Error("客户风险等级已过期,mobile:%v", userProfile.Mobile)
-		return
-	}
-	mapping, mappingErr := permissionService.GetRiskMappingByCustomerRiskLevel(userProfile.RiskLevel)
-	if mappingErr != nil {
-		logger.Error("查询产品风险等级映射失败:%v", mappingErr)
-		return
-	}
-	var permissionList []permissionService.PermissionDTO
-	//获取所有设置风险等级的品种
-	permissionList, err = permissionService.GetPermissionListWithRisk()
-	permissionList = filterPermissionsByRisk(permissionList, mapping.ProductRiskLevel)
-	if len(permissionList) == 0 {
+func RangeSearchByAnalyst(mediaType string, analystId int, userId int) (total int64, latestId int64, ids []int, mappingRiskLevel, userRiskStatus string) {
+	filterPermissionIds, mappingRiskLevel, userRiskStatus, err := user.GetRiskLevelPermissionList(nil, true, userId)
+	if err != nil {
+		logger.Error("校验用户风险等级失败:%v", err)
 		return
 	}
-	var filterPermissionIds []int
-	for _, permission := range permissionList {
-		filterPermissionIds = append(filterPermissionIds, permission.PermissionId)
-	}
-	return mediaService.GetAnalystMediaPermissionMappingByPermissionIds(mediaType, filterPermissionIds, analystId)
+	total, latestId, ids = mediaService.GetAnalystMediaPermissionMappingByPermissionIds(mediaType, filterPermissionIds, analystId)
+	return
 }
 
-func getCount(mediaType string, permissionIds []int, isLogin bool, userId int) (total int64, latestId int64, orgIds []int) {
-	filterPermissionIds, riskLevel, err := report.CheckUserRisk(permissionIds, isLogin, userId)
+func getCount(mediaType string, permissionIds []int, isLogin bool, userId int) (total int64, latestId int64, mediaIds []int, mappingPdRisk string, userRiskStatus string) {
+	filterPermissionIds, mappingPdRisk, userRiskStatus, err := user.GetRiskLevelPermissionList(permissionIds, isLogin, userId)
 	if err != nil {
 		logger.Error("校验用户风险等级失败:%v", err)
 		return
 	}
-	return mediaService.GetMediaPermissionMappingByPermissionIds(mediaType, filterPermissionIds, riskLevel)
+	total, latestId, mediaIds = mediaService.GetMediaPermissionMappingByPermissionIds(mediaType, filterPermissionIds)
+	return
 }
-func RangeSearch(mediaType string, isLogin bool, userId int) (total int64, latestId int64, ids []int) {
+func RangeSearch(mediaType string, isLogin bool, userId int) (total int64, latestId int64, ids []int, mappingPdRisk string, userRiskStatus string) {
 	return getCount(mediaType, nil, isLogin, userId)
-	//var err error
-	////登录了需要校验风险等级,如果风险等级没做或者过期直接返回空,做了就筛选风险等级
-	//if isLogin {
-	//	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
-	//	}
-	//	//获取产品风险等级
-	//	if userProfile.RiskLevel == user.RiskUnTest {
-	//		logger.Error("客户未做风险等级测评,mobile:%v", userProfile.Mobile)
-	//		return
-	//	}
-	//	if userProfile.RiskLevelStatus == user.RiskExpired {
-	//		logger.Error("客户风险等级已过期,mobile:%v", userProfile.Mobile)
-	//		return
-	//	}
-	//	mapping, mappingErr := permissionService.GetRiskMappingByCustomerRiskLevel(userProfile.RiskLevel)
-	//	if mappingErr != nil {
-	//		logger.Error("查询产品风险等级映射失败:%v", mappingErr)
-	//		return
-	//	}
-	//	var permissionList []permissionService.PermissionDTO
-	//	//获取所有设置风险等级的品种
-	//	permissionList, err = permissionService.GetPermissionListWithRisk()
-	//	permissionList = filterPermissionsByRisk(permissionList, mapping.ProductRiskLevel)
-	//	if len(permissionList) == 0 {
-	//		return
-	//	}
-	//	var filterPermissionIds []int
-	//	for _, permission := range permissionList {
-	//		filterPermissionIds = append(filterPermissionIds, permission.PermissionId)
-	//	}
-	//	return mediaService.GetMediaPermissionMappingByPermissionIds(mediaType, filterPermissionIds, mapping.ProductRiskLevel)
-	//} else { //没有登录的时候展示所有设置了风险等级的品种报告,筛选的时候过滤传入ID中没有设置风险等级的品种
-	//	var permissionList []permissionService.PermissionDTO
-	//	//获取所有设置风险等级的品种
-	//	permissionList, err = permissionService.GetPermissionListWithRisk()
-	//	if err != nil {
-	//		logger.Error("根据ID查询品种列表失败:%v", err)
-	//	}
-	//	var filterPermissionIds []int
-	//	for _, permission := range permissionList {
-	//		filterPermissionIds = append(filterPermissionIds, permission.PermissionId)
-	//	}
-	//	//查询品种
-	//	return mediaService.GetMediaPermissionMappingByPermissionIds(mediaType, filterPermissionIds, "")
-	//}
 }
-func GetTotalPageCountByPermissionIds(mediaType string, permissionIds []int, isLogin bool, userId int) (total int64, latestId int64, ids []int) {
+func GetTotalPageCountByPermissionIds(mediaType string, permissionIds []int, isLogin bool, userId int) (total int64, latestId int64, ids []int, mappingPdRisk string, userRiskStatus string) {
 	if mediaType == "" {
 		return
 	}
-
 	return getCount(mediaType, permissionIds, isLogin, userId)
-	//var err error
-	////登录了需要校验风险等级,如果风险等级没做或者过期直接返回空,做了就筛选风险等级
-	//if isLogin {
-	//	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
-	//	}
-	//	//获取产品风险等级
-	//	if userProfile.RiskLevel == user.RiskUnTest {
-	//		logger.Error("客户未做风险等级测评,mobile:%v", userProfile.Mobile)
-	//		return
-	//	}
-	//	if userProfile.RiskLevelStatus == user.RiskExpired {
-	//		logger.Error("客户风险等级已过期,mobile:%v", userProfile.Mobile)
-	//		return
-	//	}
-	//	mapping, mappingErr := permissionService.GetRiskMappingByCustomerRiskLevel(userProfile.RiskLevel)
-	//	if mappingErr != nil {
-	//		logger.Error("查询产品风险等级映射失败:%v", mappingErr)
-	//		return
-	//	}
-	//	var permissionList []permissionService.PermissionDTO
-	//	if len(permissionIds) == 0 {
-	//		//获取所有设置风险等级的品种
-	//		permissionList, err = permissionService.GetPermissionListWithRisk()
-	//	} else {
-	//		//更具id过滤设置了风险等级的品种
-	//		permissionList, err = permissionService.GetPermissionListByIds(permissionIds)
-	//	}
-	//	permissionList = filterPermissionsByRisk(permissionList, mapping.ProductRiskLevel)
-	//	if len(permissionList) == 0 {
-	//		return
-	//	}
-	//	var filterPermissionIds []int
-	//	for _, permission := range permissionList {
-	//		filterPermissionIds = append(filterPermissionIds, permission.PermissionId)
-	//	}
-	//	return mediaService.GetMediaPermissionMappingByPermissionIds(mediaType, filterPermissionIds, mapping.ProductRiskLevel)
-	//} else { //没有登录的时候展示所有设置了风险等级的品种报告,筛选的时候过滤传入ID中没有设置风险等级的品种
-	//	var permissionList []permissionService.PermissionDTO
-	//	if len(permissionIds) == 0 {
-	//		//获取所有设置风险等级的品种
-	//		permissionList, err = permissionService.GetPermissionListWithRisk()
-	//	} else {
-	//		//更具id过滤设置了风险等级的品种
-	//		permissionList, err = permissionService.GetPermissionListByIds(permissionIds)
-	//	}
-	//	if err != nil {
-	//		logger.Error("根据ID查询品种列表失败:%v", err)
-	//	}
-	//	var filterPermissionIds []int
-	//	for _, permission := range permissionList {
-	//		filterPermissionIds = append(filterPermissionIds, permission.PermissionId)
-	//	}
-	//	//查询品种
-	//	return mediaService.GetMediaPermissionMappingByPermissionIds(mediaType, filterPermissionIds, "")
-	//}
 }
-func parseRiskLevel(level string) (int, error) {
-	parts := strings.Split(level, "R")
-	if len(parts) < 2 {
-		return 0, fmt.Errorf("无效的风险等级: %s", level)
-	}
-	numberStr := parts[1]
-	number, err := strconv.Atoi(numberStr)
-	if err != nil {
-		return 0, fmt.Errorf("无法将风险等级转换为数字: %s", err)
-	}
-	return number, nil
-}
-func filterPermissionsByRisk(permissionList []permissionService.PermissionDTO, riskLevel string) (resultList []permissionService.PermissionDTO) {
-	riskLevelNum, err := parseRiskLevel(riskLevel)
+
+func GetMediaById(mediaType string, mediaId int, isLogin bool, userId int) (media *mediaService.MediaDTO, err error) {
+	mappingRiskLevel, userRiskStatus, err := user.CheckUserRiskMatchStatus(userId)
 	if err != nil {
-		logger.Error("风险等级解析失败:%v", err)
-		return
-	}
-	for _, permission := range permissionList {
-		pRiskNum, riskErr := parseRiskLevel(permission.RiskLevel)
-		if riskErr != nil {
-			logger.Error("解析品种风险等级失败 permission:%d,risk:%v", permission.PermissionId, permission.RiskLevel)
-			continue
-		}
-		if pRiskNum <= riskLevelNum {
-			resultList = append(resultList, permission)
-		}
+		logger.Error("校验用户风险等级失败:%v", err)
 	}
-	return
-}
-func SearchMaxMediaId(mediaType string, key string) (total int64, latestId int64) {
-	return mediaService.SearchMaxMediaId(mediaType, key)
-}
-func GetMediaById(mediaType string, mediaId int, isLogin bool, userId int) (media mediaService.MediaDTO, err error) {
-	media, err = mediaService.GetMediaById(mediaType, mediaId)
+	var mediaInfo mediaService.MediaDTO
+	mediaInfo, err = mediaService.GetMediaById(mediaType, mediaId)
 	if err != nil {
 		logger.Error("获取媒体失败:%v", err)
 		err = exception.New(exception.MediaFoundFailed)
 		return
 	}
-	var status string
-	status, err = matchRiskLevel(userId, media)
+	return DealMediaInfo(&mediaInfo, isLogin, userId, mappingRiskLevel, userRiskStatus)
+}
+func GetMediaPageByAnalystId(mediaType string, pageInfo page.PageInfo, analystId int, mediaIds []int, userId int, mappingRiskLevel string, userRiskStatus string) (list []mediaService.MediaDTO, err error) {
+	list, err = mediaService.GetMediaPageByAnalystId(mediaType, pageInfo, analystId, mediaIds)
 	if err != nil {
-		logger.Error("匹配风险等级失败:%v", err)
-		err = exception.New(exception.ReportRiskLevelUnSet)
+		err = exception.New(exception.GetAnalystMediaListFailed)
 		return
 	}
-	idStr := strings.Split(media.PermissionIDs, ",")
-	var ids []int
-	ids, err = stringUtils.StringToIntSlice(idStr)
-	if err != nil {
-		logger.Error("品种名称列表转换失败:%v", err)
+	if len(list) == 0 {
+		logger.Info("研究员媒体列表为空")
 		return
 	}
-	media.PermissionNames = getMediaPermissionNames(ids)
-	if isLogin {
-		media.Login = true
-		if status != RiskLevelMatch {
-			media.Src = ""
-		}
-		media.RiskLevelStatus = status
-	} else {
-		logger.Info("当前用户未登录,展示部分详情")
-		media.Src = ""
-		media.RiskLevelStatus = RiskLevelUnMatch
-		media.Login = false
+	//并发获取媒体的标签
+	list, err = dealMediaInfo(list, true, userId, mappingRiskLevel, userRiskStatus)
+	if err != nil {
+		err = exception.New(exception.GetAnalystMediaListFailed)
 	}
 	return
 }
-func matchRiskLevel(userId int, media mediaService.MediaDTO) (riskLevelMatch string, err error) {
-	userProfile, userErr := user.GetUserProfile(userId)
-	if userErr != nil {
-		if errors.Is(userErr, gorm.ErrRecordNotFound) {
-			logger.Error("用户信息不存在,mobile:%d", userProfile.Mobile)
-			err = exception.New(exception.TemplateUserNotFound)
-			return
-		} else {
-			logger.Error("获取用户信息失败:%v", userErr)
-			err = exception.New(exception.TemplateUserFoundFailed)
-			return
-		}
-	}
-	//比较风险等级
-	if userProfile.RiskLevelStatus == user.RiskUnTest {
-		logger.Info("客户风险等级未测试,mobile:%v", userProfile.Mobile)
-		riskLevelMatch = RiskLevelUnTest
-		return
-	}
-	if userProfile.RiskLevelStatus == user.RiskExpired {
-		logger.Info("客户风险等级已过期,mobile:%v", userProfile.Mobile)
-		riskLevelMatch = RiskLevelExpired
-		return
-	}
-	level, err := permissionService.GetRiskMappingByCustomerRiskLevel(userProfile.RiskLevel)
+func getMediaSecondPermissions(ids []int) (permissionDTOs []configService.PermissionDTO, err error) {
+	return mediaService.GetSecondPermissionsByIds(ids)
+}
+func GetMediaPageByIds(mediaType string, pageInfo page.PageInfo, mediaIds []int, isLogin bool, userId int, mappingRiskLevel string, userRiskStatus string) (list []mediaService.MediaDTO, err error) {
+	list, err = mediaService.GetMediaPageByIds(mediaType, pageInfo, mediaIds)
 	if err != nil {
-		logger.Error("获取媒体风险等级设置失败:%v", err)
+		err = exception.New(exception.GetMediaListFailed)
 		return
 	}
-	var permissionIds []int
-	permissionStrList := strings.Split(media.PermissionIDs, ",")
-	permissionIds, err = stringUtils.StringToIntSlice(permissionStrList)
+	list, err = dealMediaInfo(list, isLogin, userId, mappingRiskLevel, userRiskStatus)
 	if err != nil {
-		logger.Error("解析媒体品种ID列表失败:%v", err)
+		err = exception.New(exception.QueryReportPageFailed)
 	}
-	permissionDTOs, err := permissionService.GetPermissionListByIds(permissionIds)
+	return
+}
+func GetMediaPermissionNames(id []int) (labels []string) {
+	permissions, err := mediaService.GetPermissionsByIds(id)
 	if err != nil {
-		logger.Error("获取品种风险等级失败:%v", err)
+		logger.Error("查询品种名称列表失败:%v", err)
+		labels = []string{}
 		return
 	}
-	//能够查看最高等级
-	matchNum, err := parseRiskLevel(level.ProductRiskLevel)
+	for _, permission := range permissions {
+		labels = append(labels, permission.PermissionName)
+	}
+	return
+}
+func DealMediaInfo(media *mediaService.MediaDTO, isLogin bool, templateUserId int, mappingRiskLevel string, userRiskStatus string) (resultMedia *mediaService.MediaDTO, err error) {
+	idStr := strings.Split(media.PermissionIDs, ",")
+	var ids []int
+	ids, err = stringUtils.StringToIntSlice(idStr)
 	if err != nil {
-		logger.Error("解析风险等级失败:%v", err)
-		return
+		logger.Error("品种名称列表转换失败:%v", err)
 	}
-	if len(permissionDTOs) == 0 {
-		logger.Error("当前报告对应品种未设置风险等级")
-		err = exception.New(exception.ReportRiskLevelUnSet)
-		return
+	media.PermissionNames = GetMediaPermissionNames(ids)
+	permissions, permissionErr := getMediaSecondPermissions(ids)
+	if permissionErr != nil {
+		logger.Error("获取媒体品种信息失败:%v,无法设置媒体风险等级", err)
 	}
-	//能够查看需要的最小等级
-	num := getLowestRiskLevel(permissionDTOs)
-	if num > matchNum {
-		riskLevelMatch = RiskLevelUnMatch
-		return
-	} else {
-		riskLevelMatch = RiskLevelMatch
-		return
+	riskNum := config.GetHighestRiskLevel(permissions)
+	media.RiskLevel = strings.Join([]string{"R", strconv.Itoa(riskNum)}, "")
+	if !isLogin {
+		media.Src = ""
 	}
-}
-
-func getHighestRiskLevel(permissions []permissionService.PermissionDTO) (riskLevelNum int) {
-	for _, permission := range permissions {
-		pRiskNum, err := parseRiskLevel(permission.RiskLevel)
+	media.Login = isLogin
+	var productType productDao.MerchantProductType
+	switch media.MediaType {
+	case string(mediaDao.Audio):
+		productType = productDao.Audio
+	case string(mediaDao.Video):
+		productType = productDao.Video
+	default:
+		logger.Error("未知媒体类型:%s", media.MediaType)
+		productType = ""
+	}
+	var packageList []productService.MerchantProductDTO
+	var permissionIds []int
+	if len(permissions) > 0 {
+		for _, permission := range permissions {
+			permissionIds = append(permissionIds, permission.PermissionId)
+		}
+		packageList, err = productService.GetProductListBySourceIds(permissionIds, "package")
 		if err != nil {
-			logger.Error("解析风险等级失败:%v", err)
-			continue
+			logger.Error("获取套餐列表失败:%v", err)
 		}
-		if riskLevelNum == 0 {
-			riskLevelNum = pRiskNum
+	}
+	//判断是否有单品,没有的话查看套餐
+	product, pdErr := productService.GetProductBySourceId(media.MediaId, productType)
+	if pdErr != nil {
+		if len(packageList) == 0 {
+			logger.Error("获取套餐列表失败:%v", err)
+			media.Price = defaultProductPrice
+			media.IsFree = true
+			media.IsSubscribe = false
+			media.IsPackage = false
 		} else {
-			if riskLevelNum < pRiskNum {
-				riskLevelNum = pRiskNum
-			}
+			media.Price = packageList[0].Price
+			media.IsFree = false
+			media.IsSubscribe = false
+			media.IsPackage = true
+			media.ProductId = packageList[0].Id
 		}
+	} else {
+		media.Price = product.Price
+		media.IsFree = false
+		media.IsPackage = false
+		media.IsSubscribe = false
+		media.ProductId = product.Id
 	}
-	return
-}
-func getLowestRiskLevel(permissions []permissionService.PermissionDTO) (riskLevelNum int) {
-	for _, permission := range permissions {
-		pRiskNum, err := parseRiskLevel(permission.RiskLevel)
-		if err != nil {
-			logger.Error("解析风险等级失败:%v", err)
-			continue
+	if isLogin {
+		var productIds []int
+		if len(packageList) > 0 {
+			for _, packageItem := range packageList {
+				productIds = append(productIds, packageItem.Id)
+			}
+		}
+		if product.Id > 0 {
+			productIds = append(productIds, product.Id)
 		}
-		if riskLevelNum == 0 {
-			riskLevelNum = pRiskNum
+		subscribeList, subscribeErr := userService.GetUserSubscribe(productIds, templateUserId)
+		if subscribeErr != nil {
+			media.IsSubscribe = false
 		} else {
-			if riskLevelNum > pRiskNum {
-				riskLevelNum = pRiskNum
+			for _, subscribe := range subscribeList {
+				if subscribe.Status == productDao.SubscribeValid {
+					media.IsSubscribe = true
+					break
+				}
+			}
+		}
+		if userRiskStatus != user.RiskValid {
+			if userRiskStatus == user.RiskUnTest {
+				media.RiskLevelStatus = RiskLevelUnTest
+			}
+			if userRiskStatus == user.RiskExpired {
+				media.RiskLevelStatus = RiskLevelExpired
+			}
+		} else {
+			media.RiskLevelStatus = RiskLevelUnMatch
+			if mappingRiskLevel != "" {
+				mappingRiskNum, parseErr := config.ParseRiskLevel(mappingRiskLevel)
+				if parseErr != nil {
+					return
+				}
+				var rpRiskNum int
+				rpRiskNum, parseErr = config.ParseRiskLevel(media.RiskLevel)
+				if parseErr != nil {
+					return
+				}
+				if rpRiskNum <= mappingRiskNum {
+					media.RiskLevelStatus = RiskLevelMatch
+				}
 			}
 		}
 	}
+	resultMedia = media
 	return
 }
-func GetMediaPageByAnalystId(mediaType string, pageInfo page.PageInfo, analystId int, mediaIds []int) (list []mediaService.MediaDTO, err error) {
-	list, err = mediaService.GetMediaPageByAnalystId(mediaType, pageInfo, analystId, mediaIds)
-	if err != nil {
-		err = exception.New(exception.GetAnalystMediaListFailed)
-		return
-	}
-	if len(list) == 0 {
-		logger.Info("研究员媒体列表为空")
-		return
-	}
+
+// stringToIntSlice 将一个包含数字字符串的切片转换为整数切片
+func dealMediaInfo(list []mediaService.MediaDTO, isLogin bool, templateUserId int, mappingRiskLevel string, userRiskStatus string) (dealList []mediaService.MediaDTO, err error) {
 	//并发获取媒体的标签
 	var wg sync.WaitGroup
 	wg.Add(len(list))
 	for i := 0; i < len(list); i++ {
 		go func(media *mediaService.MediaDTO) {
 			defer wg.Done()
-			idStr := strings.Split(media.PermissionIDs, ",")
-			var ids []int
-			ids, err = stringUtils.StringToIntSlice(idStr)
+			media, err = DealMediaInfo(media, isLogin, templateUserId, mappingRiskLevel, userRiskStatus)
 			if err != nil {
-				logger.Error("品种名称列表转换失败:%v", err)
+				logger.Error("处理媒体信息失败:%v", err)
 			}
-			media.PermissionNames = getMediaPermissionNames(ids)
 		}(&list[i])
 	}
 	wg.Wait()
-	if err != nil {
-		err = exception.New(exception.GetAnalystMediaListFailed)
-	}
+	dealList = list
 	return
 }
-func getMediaSecondPermissions(ids []int) (permissionDTOs []configService.PermissionDTO, err error) {
-	return mediaService.GetSecondPermissionsByIds(ids)
-}
-func GetMediaPageByIds(mediaType string, pageInfo page.PageInfo, mediaIds []int, isLogin bool, userId int) (list []mediaService.MediaDTO, err error) {
-	list, err = mediaService.GetMediaPageByIds(mediaType, pageInfo, mediaIds)
-	if err != nil {
-		err = exception.New(exception.GetMediaListFailed)
-		return
-	}
-	//并发获取媒体的标签
-	var wg sync.WaitGroup
-	wg.Add(len(list))
-	for i := 0; i < len(list); i++ {
-		go func(media *mediaService.MediaDTO) {
-			defer wg.Done()
-			idStr := strings.Split(media.PermissionIDs, ",")
-			var ids []int
-			ids, err = stringUtils.StringToIntSlice(idStr)
-			if err != nil {
-				logger.Error("品种名称列表转换失败:%v", err)
-			}
-			media.PermissionNames = getMediaPermissionNames(ids)
-			permissions, permissionErr := getMediaSecondPermissions(ids)
-			if permissionErr != nil {
-				logger.Error("获取媒体品种信息失败:%v,无法设置媒体风险等级", err)
-			}
-			riskNum := getHighestRiskLevel(permissions)
-			media.RiskLevel = strings.Join([]string{"R", strconv.Itoa(riskNum)}, "")
-			if !isLogin {
-				media.Src = ""
-			}
-			media.Login = isLogin
-			var productType productDao.MerchantProductType
-			switch media.MediaType {
-			case string(mediaDao.Audio):
-				productType = productDao.Audio
-			case string(mediaDao.Video):
-				productType = productDao.Video
-			default:
-				logger.Error("未知媒体类型:%s", media.MediaType)
-				productType = ""
-			}
-			product, pdErr := productService.GetProductBySourceId(media.MediaId, productType)
-			if pdErr != nil {
-				if errors.Is(pdErr, gorm.ErrRecordNotFound) {
-					media.Price = defaultProductPrice
-					media.IsFree = true
-					media.IsSubscribe = false
-				} else {
-					media.Price = defaultProductPrice
-					media.IsFree = false
-					media.IsSubscribe = false
-				}
-				return
-			}
-			media.Price = product.Price.String()
-			media.IsFree = false
-			if isLogin {
-				subscribe, subscribeErr := userService.GetUserSubscribe(product.Id, userId)
-				if subscribeErr != nil {
-					media.IsSubscribe = false
-				} else {
-					media.IsSubscribe = subscribe.Status == userDao.SubscribeValid
-				}
-			}
-			pdRiskNum, parseErr := parseRiskLevel(product.RiskLevel)
-			if parseErr != nil {
-				return
-			}
-			rpRiskNum, parseErr := parseRiskLevel(media.RiskLevel)
-			if parseErr != nil {
-				return
-			}
-			if rpRiskNum <= pdRiskNum {
-				media.RiskLevel = product.RiskLevel
-			}
-		}(&list[i])
-	}
-	wg.Wait()
+func SearchMediaList(mediaType string, key string, mediaIds []int, pageInfo page.PageInfo, isLogin bool, userId int, mappingRiskLevel string, userRiskStatus string) (medias []mediaService.MediaDTO, err error) {
+	offset := page.StartIndex(pageInfo.Current, pageInfo.PageSize)
+	medias, err = mediaService.SearchMediaList(mediaType, key, mediaIds, offset, pageInfo.PageSize, pageInfo.LatestId)
+	medias, err = dealMediaInfo(medias, isLogin, userId, mappingRiskLevel, userRiskStatus)
 	if err != nil {
-		err = exception.New(exception.QueryReportPageFailed)
+		err = exception.New(exception.SearchReportPageFailed)
 	}
 	return
 }
-func getMediaPermissionNames(id []int) (labels []string) {
-	permissions, err := mediaService.GetPermissionsByIds(id)
+
+func SearchMediaProduct(key string, docIds []int) (list []mediaService.MediaDTO, err error) {
+	list, err = mediaService.SearchMediaProduct(key, 100, 0, docIds)
 	if err != nil {
-		logger.Error("查询品种名称列表失败:%v", err)
-		labels = []string{}
-		return
-	}
-	for _, permission := range permissions {
-		labels = append(labels, permission.PermissionName)
+		err = exception.New(exception.SearchReportPageFailed)
 	}
 	return
 }
 
-// stringToIntSlice 将一个包含数字字符串的切片转换为整数切片
-
-func SearchMediaList(mediaType string, key string, mediaIds []int, pageInfo page.PageInfo, isLogin bool, userId int) (medias []mediaService.MediaDTO, err error) {
-	offset := page.StartIndex(pageInfo.Current, pageInfo.PageSize)
-	medias, err = mediaService.SearchMediaList(mediaType, key, mediaIds, offset, pageInfo.PageSize, pageInfo.LatestId)
-	var wg sync.WaitGroup
-	wg.Add(len(medias))
-	for i := 0; i < len(medias); i++ {
-		go func(media *mediaService.MediaDTO) {
-			defer wg.Done()
-			idStr := strings.Split(media.PermissionIDs, ",")
-			var ids []int
-			ids, err = stringUtils.StringToIntSlice(idStr)
-			if err != nil {
-				logger.Error("获取品种列表失败:%v", err)
-			}
-			media.PermissionNames = getMediaPermissionNames(ids)
-			permissions, permissionErr := getMediaSecondPermissions(ids)
-			if permissionErr != nil {
-				logger.Error("获取媒体品种信息失败:%v,无法设置媒体风险等级", err)
-			}
-			riskNum := getHighestRiskLevel(permissions)
-			media.RiskLevel = strings.Join([]string{"R", strconv.Itoa(riskNum)}, "")
-			if !isLogin {
-				media.Src = ""
-			}
-			media.Login = isLogin
-			var productType productDao.MerchantProductType
-			switch media.MediaType {
-			case string(mediaDao.Audio):
-				productType = productDao.Audio
-			case string(mediaDao.Video):
-				productType = productDao.Video
-			default:
-				logger.Error("未知媒体类型:%s", media.MediaType)
-				productType = ""
-			}
-			product, pdErr := productService.GetProductBySourceId(media.MediaId, productType)
-			if pdErr != nil {
-				if errors.Is(pdErr, gorm.ErrRecordNotFound) {
-					media.Price = defaultProductPrice
-					media.IsFree = true
-					media.IsSubscribe = false
-				} else {
-					media.Price = defaultProductPrice
-					media.IsFree = false
-					media.IsSubscribe = false
-				}
-				return
-			}
-			media.Price = product.Price.String()
-			media.IsFree = false
-			if isLogin {
-				subscribe, subscribeErr := userService.GetUserSubscribe(product.Id, userId)
-				if subscribeErr != nil {
-					media.IsSubscribe = false
-				} else {
-					media.IsSubscribe = subscribe.Status == userDao.SubscribeValid
-				}
-			}
-			pdRiskNum, parseErr := parseRiskLevel(product.RiskLevel)
-			if parseErr != nil {
-				return
-			}
-			rpRiskNum, parseErr := parseRiskLevel(media.RiskLevel)
-			if parseErr != nil {
-				return
-			}
-			if rpRiskNum <= pdRiskNum {
-				media.RiskLevel = product.RiskLevel
-			}
-		}(&medias[i])
-	}
-	wg.Wait()
+func CountPermissionWeight(ids []int) (permissionMap map[int]int) {
+	list, err := mediaService.CountPermissionWeight(ids)
+	permissionMap = make(map[int]int)
 	if err != nil {
-		err = exception.New(exception.SearchReportPageFailed)
+		return
+	}
+	for _, permission := range list {
+		permissionMap[permission.PermissionId] = permission.Weight
 	}
 	return
 }

+ 144 - 16
service/order/order_service.go

@@ -5,9 +5,12 @@ import (
 	logger "eta/eta_mini_ht_api/common/component/log"
 	"eta/eta_mini_ht_api/common/exception"
 	"eta/eta_mini_ht_api/common/utils/page"
+	merchantService "eta/eta_mini_ht_api/domian/merchant"
 	productService "eta/eta_mini_ht_api/domian/merchant"
 	orderService "eta/eta_mini_ht_api/domian/order"
 	userService "eta/eta_mini_ht_api/domian/user"
+	merchantDao "eta/eta_mini_ht_api/models/merchant"
+	orderDao "eta/eta_mini_ht_api/models/order"
 	"eta/eta_mini_ht_api/service/user"
 	"fmt"
 	"gorm.io/gorm"
@@ -19,12 +22,28 @@ const (
 	permanentlyValid = "永久有效"
 	OnSale           = "on_sale"  //上架
 	OffSale          = "off_sale" //下架
+
+)
+
+var (
+	productOrderStatusMap = map[orderDao.OrderStatus]string{
+		orderDao.OrderStatusPending: "pending",
+		orderDao.OrderStatusPaid:    "paid",
+		orderDao.OrderStatusClosed:  "closed",
+		orderDao.OrderStatusRefund:  "refund",
+	}
+
+	accessStatusMap = map[merchantDao.SubscribeStatus]string{
+		merchantDao.SubscribeValid:   "valid",
+		merchantDao.SubscribeExpired: "expired",
+		merchantDao.SubscribeClose:   "closed",
+	}
 )
 
 type ProductOrderInfo struct {
-	Buyer       BuyerInfo   `json:"buyer"`
-	ProductInfo ProductInfo `json:"productInfo"`
-	OrderNo     string      `json:"orderNo"`
+	Buyer       BuyerInfo    `json:"buyer"`
+	ProductInfo ProductInfo  `json:"productInfo"`
+	Order       ProductOrder `json:"order"`
 }
 type ProductInfo struct {
 	Name          string
@@ -32,6 +51,11 @@ type ProductInfo struct {
 	Price         string
 	ValidDuration string
 }
+
+type ProductOrder struct {
+	OrderNo     string
+	CreatedTime string
+}
 type BuyerInfo struct {
 	Name     string
 	Mobile   string
@@ -50,6 +74,10 @@ func CreateProductOrder(templateUser user.User, productId int, orderNo string) (
 		err = exception.NewWithException(exception.ProductInfoError, err.Error())
 		return
 	}
+	if productInfo.Deleted {
+		err = exception.New(exception.ProductInfoError)
+		return
+	}
 	if productInfo.SaleStatus == OffSale {
 		err = exception.New(exception.ProductOffSale)
 		return
@@ -63,11 +91,32 @@ func CreateProductOrder(templateUser user.User, productId int, orderNo string) (
 		}
 		return
 	}
-	orderNo, err = orderService.CreateProductOrder(orderService.ProductOrderDTO{
-		OrderID:     orderNo,
-		UserID:      templateUser.Id,
-		ProductID:   productId,
-		TotalAmount: productInfo.Price.String(),
+	//校验是否有相同待支付的产品订单
+	var existOrder orderService.ProductOrderDTO
+	existOrder, err = orderService.GetUserOrderByProduct(productId, templateUser.Id, productOrderStatusMap[orderDao.OrderStatusPending])
+	if (err != nil && !errors.Is(err, gorm.ErrRecordNotFound)) || existOrder.Status == productOrderStatusMap[orderDao.OrderStatusPending] {
+		err = exception.NewWithException(exception.DuplicateSubscribe, "当前产品有正在进行中的订单,请勿重复下单")
+		return
+	}
+	//是否在有效期的产品
+	var access merchantService.UserAccessDTO
+	access, err = merchantService.GetUserSubscribe(templateUser.Id, productId)
+	if (err != nil && !errors.Is(err, gorm.ErrRecordNotFound)) || access.Status == accessStatusMap[merchantDao.SubscribeValid] {
+		err = exception.NewWithException(exception.DuplicateSubscribe, "当前产品已订阅,请勿重复下单")
+		return
+	}
+	var orderCreated orderService.ProductOrderDTO
+	orderCreated, err = orderService.CreateProductOrder(orderService.ProductOrderDTO{
+		OrderID:        orderNo,
+		UserID:         officialUser.ID,
+		TemplateUserID: templateUser.Id,
+		RealName:       templateUser.Username,
+		AreaCode:       templateUser.AreaCode,
+		Mobile:         templateUser.Mobile,
+		ProductID:      productId,
+		ProductType:    productInfo.Type,
+		ProductName:    productInfo.ProductTile,
+		TotalAmount:    productInfo.Price,
 	})
 	if err != nil {
 		logger.Error("创建订单失败:%v", err)
@@ -94,17 +143,68 @@ func CreateProductOrder(templateUser user.User, productId int, orderNo string) (
 	product := ProductInfo{
 		Name:          productInfo.Title,
 		Type:          productInfo.Type,
-		Price:         productInfo.Price.String(),
+		Price:         productInfo.Price,
 		ValidDuration: duration,
 	}
+	order := ProductOrder{
+		OrderNo:     orderCreated.OrderID,
+		CreatedTime: orderCreated.CreatedTime,
+	}
 	orderInfo = ProductOrderInfo{
 		Buyer:       buyer,
 		ProductInfo: product,
-		OrderNo:     orderNo,
+		Order:       order,
 	}
 	return
 }
 
+func PreViewProductOrder(templateUser user.User, productId int) (orderInfo ProductOrderInfo, err error) {
+	if productId <= 0 {
+		err = exception.New(exception.IllegalProductId)
+	}
+	productInfo, err := productService.GetMerchantProductById(productId)
+	if err != nil {
+		err = exception.NewWithException(exception.ProductInfoError, err.Error())
+		return
+	}
+	if productInfo.SaleStatus == OffSale {
+		err = exception.New(exception.ProductOffSale)
+		return
+	}
+	officialUser, err := userService.GetUserByTemplateUserId(templateUser.Id)
+	if err != nil {
+		if errors.Is(err, gorm.ErrRecordNotFound) {
+			err = exception.New(exception.OfficialUserNotFound)
+		} else {
+			err = exception.NewWithException(exception.OfficialUserFoundError, err.Error())
+		}
+		return
+	}
+	buyer := BuyerInfo{
+		Name:     officialUser.RealName,
+		Mobile:   templateUser.Mobile,
+		AreaCode: templateUser.AreaCode,
+	}
+	var duration string
+	if !productInfo.IsPermanent {
+		beginDate := time.Now().Format(time.DateOnly)
+		endDate := time.Now().Add(time.Duration(productInfo.ValidDays*24) * time.Hour).Format(time.DateOnly)
+		duration = fmt.Sprintf("%s~%s", beginDate, endDate)
+	} else {
+		duration = permanentlyValid
+	}
+	product := ProductInfo{
+		Name:          productInfo.Title,
+		Type:          productInfo.Type,
+		Price:         productInfo.Price,
+		ValidDuration: duration,
+	}
+	orderInfo = ProductOrderInfo{
+		Buyer:       buyer,
+		ProductInfo: product,
+	}
+	return
+}
 func GetTotalPageCountByUserId(userId int, orderStatus string) (total int64, latestId int64) {
 	return orderService.GetTotalPageCountByUserId(userId, orderStatus)
 }
@@ -131,13 +231,41 @@ func GetOrderPage(pageInfo page.PageInfo, userId int, orderStatus string) (order
 	return
 }
 
-func GetOrderDetail(productId int, userId int) (order orderService.ProductOrderDetailDTO, err error) {
-	order, err = orderService.GetOrderDetail(productId, userId)
-	productInfo, err := productService.GetMerchantProductById(order.ProductID)
+func GetOrderDetail(orderId string, userId int) (order orderService.ProductOrderDetailDTO, err error) {
+	order, err = orderService.GetOrderDetail(orderId, userId)
+	return
+}
+
+func GetProductOrderByUser(templateUserId int, orderNo string) (order orderService.ProductOrderDTO, err error) {
+	return orderService.GetOrderByUser(templateUserId, orderNo)
+}
+
+func CloseProductOrder(templateUserId int, productOrderNo string) (err error) {
+	return orderService.CloseProductOrder(templateUserId, productOrderNo)
+}
+
+func GetTradeOrderByNo(tradeOrderNo string) (dto orderService.TradeOrderDTO, err error) {
+	return orderService.GetTradeOrderByNo(tradeOrderNo)
+}
+
+func CheckProductStatus(productOrder orderService.ProductOrderDTO) (err error) {
+	product, err := productService.GetMerchantProductById(productOrder.ProductID)
 	if err != nil {
-		return
+		return exception.NewWithException(exception.ProductInfoError, err.Error())
+	}
+	if product.SaleStatus == "off_sale" {
+		err = orderService.CloseProductOrder(productOrder.TemplateUserID, productOrder.OrderID)
+		if err != nil {
+			logger.Error("关闭订单失败:%v,订单编号:%s", err, product)
+		}
+		return exception.New(exception.ProductOffSale)
+	}
+	if product.Deleted {
+		err = orderService.CloseProductOrder(productOrder.TemplateUserID, productOrder.OrderID)
+		if err != nil {
+			logger.Error("关闭订单失败:%v,订单编号:%s", err, product)
+		}
+		return exception.New(exception.ProductNotFound)
 	}
-	order.ProductName = productInfo.Title
-	order.ProductPrice = productInfo.Price.String()
 	return
 }

+ 41 - 0
service/payment/payment_service.go

@@ -0,0 +1,41 @@
+package payment
+
+import (
+	"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/redis"
+	orderService "eta/eta_mini_ht_api/domian/order"
+)
+
+var (
+	redisUtils = cache.GetInstance()
+)
+
+func CreatePaymentOrder(userId, templateUserId int, OrderNo string, tradeNo string) (err error) {
+	//先获取当前商品订单的支付记录
+	tradeOrderList, err := orderService.GetUnFailedTradeFlowByProductOrder(OrderNo)
+	if len(tradeOrderList) > 0 {
+		for _, tradeOrder := range tradeOrderList {
+			if tradeOrder.PaymentStatus == "done" {
+				logger.Error("当前商品订单已支付,无法再次发起支付")
+				return exception.New(exception.PaymentDoneError)
+			}
+			if tradeOrder.PaymentStatus == "pending" {
+				logger.Error("当前商品订单正在支付,无法再次发起支付")
+				return exception.New(exception.PaymentProcessingError)
+			}
+		}
+	}
+	merchantId := redisUtils.GetString(redis.GenerateMerchantKey())
+	if merchantId == "" {
+		logger.Error("商户配置信息不存在,无法创建支付订单")
+		err = exception.New(exception.MerchantInfoNotConfig)
+		return
+	}
+	return orderService.CreateTradeOrder(userId, templateUserId, OrderNo, tradeNo, merchantId)
+}
+
+func GenerateProductOrderNo() string {
+	return orderService.GenerateTradeOrderNo()
+}

+ 417 - 9
service/product/product_service.go

@@ -1,23 +1,126 @@
 package product
 
 import (
+	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"
+	mediaDomain "eta/eta_mini_ht_api/domian/media"
 	merchantService "eta/eta_mini_ht_api/domian/merchant"
-	"github.com/shopspring/decimal"
+	orderDomain "eta/eta_mini_ht_api/domian/order"
+	reportDomain "eta/eta_mini_ht_api/domian/report"
+	"eta/eta_mini_ht_api/domian/user"
+	"eta/eta_mini_ht_api/models/config"
+	"eta/eta_mini_ht_api/models/image"
+	"eta/eta_mini_ht_api/models/media"
+	merchantDao "eta/eta_mini_ht_api/models/merchant"
+	configService "eta/eta_mini_ht_api/service/config"
+	mediaService "eta/eta_mini_ht_api/service/media"
+	reportService "eta/eta_mini_ht_api/service/report"
+	userService "eta/eta_mini_ht_api/service/user"
+	"fmt"
+	"sort"
+	"sync"
+	"sync/atomic"
 	"time"
 )
 
 type ProductDTO struct {
-	Title       string
-	Description string
-	Price       decimal.Decimal
-	CoverSrc    string
-	RiskLevel   string
-	Type        string
-	BeginDate   string
-	EndDate     string
+	Id              int      `json:"id"`
+	Title           string   `json:"title"`
+	SourceTitle     string   `json:"sourceTitle"`
+	Abstract        string   `json:"abstract"`
+	Src             string   `json:"src"`
+	PermissionNames []string `json:"permissionNames"`
+	PermissionIds   []int    `json:"-"`
+	Description     string   `json:"description"`
+	Price           string   `json:"price"`
+	CoverSrc        int      `json:"coverSrc"`
+	CoverUrl        string   `json:"coverUrl"`
+	RiskLevel       string   `json:"riskLevel"`
+	Type            string   `json:"type"`
+	BeginDate       string   `json:"beginDate"`
+	EndDate         string   `json:"endDate"`
+	SourceId        int      `json:"sourceId"`
+	IsSubscribe     bool     `json:"isSubscribe"`
+	ReportId        int      `json:"reportId"`
+	MediaId         int      `json:"mediaId"`
 }
 
+func GetProductRiskLevel(product merchantService.MerchantProductDTO) (riskLevel, sourceTitle, sourceAbsract, coverSrc, sourceSrc string, permissionNames []string, permissionIds []int, err error) {
+	switch product.Type {
+	case "package":
+		permissionRisk, permissionErr := config.PermissionsByPermissionId(product.SourceId)
+		permissionNames = []string{permissionRisk.Name}
+		permissionIds = []int{product.SourceId}
+		if permissionErr != nil {
+			logger.Error("获取权限信息失败[permissionId:%d]", product.SourceId)
+		} else {
+			riskLevel = permissionRisk.RiskLevel
+		}
+		coverSrc = product.CoverUrl
+	case "audio", "video":
+		mediaInfo, mediaErr := media.GetMediaById(product.Type, product.SourceId)
+		sourceTitle, coverSrc, sourceSrc = mediaInfo.MediaName, mediaInfo.CoverSrc, mediaInfo.Src
+		if mediaErr != nil {
+			logger.Error("获取媒体信息失败[mediaType:%s,mediaId:%d]", product.Type, product.SourceId)
+		}
+		permissionIds, mediaErr = media.GetMediaPermissionMappingByMedia(product.Type, product.SourceId)
+		if mediaErr != nil {
+			logger.Error("获取媒体权限失败[mediaType:%s,mediaId:%d]", product.Type, product.SourceId)
+		} else {
+			permissions, permissionErr := permissionService.GetPermissionListByIds(permissionIds)
+			if permissionErr != nil {
+				logger.Error("获取权限信息失败[permissionIds:%v]", permissionIds)
+			}
+			permissionNames = mediaService.GetMediaPermissionNames(permissionIds)
+			riskNum := configService.GetHighestRiskLevel(permissions)
+			riskLevel = fmt.Sprintf("R%d", riskNum)
+		}
+	case "report":
+		report, reportErr := reportDomain.GetReportById(product.SourceId, 0)
+		var coverSrcId int
+		sourceAbsract, sourceTitle, coverSrcId = report.Abstract, report.Title, report.CoverSrc
+		if coverSrcId > 0 {
+			var imageSrc string
+			imageSrc, coverSrcErr := image.GetImageSrc(coverSrcId)
+			if coverSrcErr != nil {
+				logger.Error("获取图片资源失败:%v,资源ID:%d", err, coverSrcId)
+			} else {
+				coverSrc = imageSrc
+			}
+		}
+		if reportErr != nil {
+			logger.Error("获取研报信息失败[reportId:%d]", product.SourceId)
+		} else {
+			switch report.Source {
+			case reportDomain.SourceHT:
+				permission, permissionErr := configService.GetPermissionByName(report.PlateName)
+				if permissionErr != nil {
+					logger.Error("获取板块权限失败[plateName:%s]", report.PlateName)
+				} else {
+					riskLevel = permission.RiskLevel
+					permissionIds = []int{permission.PermissionId}
+				}
+			case reportDomain.SourceETA:
+				permissions, permissionErr := configService.GetSecondPermissionsByClassifyId(report.ClassifyId)
+				if permissionErr != nil {
+					logger.Error("获取板块权限失败[plateName:%s]", report.PlateName)
+				} else {
+					riskNum := configService.GetHighestRiskLevel(permissions)
+					for _, permission := range permissions {
+						permissionIds = append(permissionIds, permission.PermissionId)
+					}
+					riskLevel = fmt.Sprintf("R%d", riskNum)
+				}
+			}
+			_, permissionNames = reportService.GetReportPermissionNames(report.OrgId, report.Source)
+		}
+	default:
+		logger.Warn("不支持的产品类型[%s]", product.Type)
+	}
+	return
+}
 func GetProductInfoById(productId int) (product ProductDTO, err error) {
 	merchantProduct, err := merchantService.GetMerchantProductById(productId)
 	if err != nil {
@@ -25,20 +128,325 @@ func GetProductInfoById(productId int) (product ProductDTO, err error) {
 		return
 	}
 	product = convertToProductDTO(merchantProduct)
+	product.RiskLevel, product.SourceTitle, product.Abstract, product.CoverUrl, product.Src, product.PermissionNames, _, err = GetProductRiskLevel(merchantProduct)
+	if err != nil {
+		logger.Error("获取风险等级失败[productId:%d]", productId)
+	}
 	return
 }
 func convertToProductDTO(product merchantService.MerchantProductDTO) (productDTO ProductDTO) {
 	beginDate := time.Now()
 	endDate := beginDate.Add(time.Duration(product.ValidDays) * 24 * time.Hour)
 	productDTO = ProductDTO{
+		Id:          product.Id,
 		Title:       product.Title,
 		Description: product.Description,
 		Price:       product.Price,
 		RiskLevel:   product.RiskLevel,
 		CoverSrc:    product.CoverSrc,
+		CoverUrl:    product.CoverUrl,
 		Type:        product.Type,
 		BeginDate:   beginDate.Format(time.DateOnly),
 		EndDate:     endDate.Format(time.DateOnly),
+		SourceId:    product.SourceId,
+	}
+	if product.CoverUrl == "" && product.CoverSrc > 0 {
+		imageSrc, err := image.GetImageSrc(product.CoverSrc)
+		if err != nil {
+			logger.Error("获取图片地址失败:%v,资源Id:%d", err, product.CoverSrc)
+		} else {
+			productDTO.CoverUrl = imageSrc
+		}
+	}
+	return
+}
+
+// OpenProduct 开通产品
+func OpenProduct(productOrder orderDomain.ProductOrderDTO) (err error) {
+	product, err := merchantService.GetMerchantProductById(productOrder.ProductID)
+	if err != nil {
+		logger.Error("获取产品信息失败:%v", err)
+		return
+	}
+	return merchantService.OpenProduct(productOrder.TemplateUserID, productOrder.OrderID, product)
+}
+
+// CloseProduct  退款关闭套餐
+func CloseProduct(productOrder orderDomain.ProductOrderDTO) (err error) {
+	product, err := merchantService.GetMerchantProductById(productOrder.ProductID)
+	if err != nil {
+		logger.Error("获取产品信息失败:%v", err)
+		return
 	}
+	return merchantService.CloseProduct(productOrder.TemplateUserID, productOrder.OrderID, product)
+}
+
+// 过期产品
+
+func GetRelatePackage(info ProductDTO) (prodList []ProductDTO, err error) {
+	switch info.Type {
+	case "audio", "video":
+		permissionIds, permissionErr := media.GetMediaPermissionMappingByMedia(info.Type, info.SourceId)
+		if permissionErr != nil {
+			logger.Error("获取媒体品种信息失败:%v", err)
+			return
+		}
+		merchantProduct, pdErr := merchantService.GetProductListBySourceIds(permissionIds, "package")
+		if pdErr != nil {
+			return
+		}
+		for _, product := range merchantProduct {
+			if !product.Deleted && product.SaleStatus == "on_sale" {
+				prodList = append(prodList, convertToProductDTO(product))
+			}
+		}
+		return
+	case "report":
+		reportInfo, reportErr := reportDomain.GetReportById(info.SourceId, 0)
+		if reportErr != nil {
+			logger.Error("获取报告信息失败:%v", err)
+			return
+		}
+		permissions := reportDomain.GetReportSecondPermissionsById(reportInfo.OrgId, reportInfo.Source)
+		var permissionIds []int
+		if len(permissions) > 0 {
+			for _, permission := range permissions {
+				permissionIds = append(permissionIds, permission.PermissionId)
+			}
+			permissionIds = append(permissionIds, permissions[0].PermissionId)
+		}
+		merchantProduct, pdErr := merchantService.GetProductListBySourceIds(permissionIds, "package")
+		if pdErr != nil {
+			return
+		}
+		for _, product := range merchantProduct {
+			prodList = append(prodList, convertToProductDTO(product))
+		}
+		return
+	default:
+		err = exception.New(exception.ProductTypeError)
+		return
+	}
+}
+
+func GetProductListByProductType(productType string, permissionIds []int, templateUserId int) (total, latestId int64, productIds []int) {
+	filterPermissionIds, riskLevel, _, err := userService.GetRiskLevelPermissionList(permissionIds, true, templateUserId)
+	if err != nil {
+		return
+	}
+	var dtoList []*ProductDTO
+	productList, err := merchantService.GetProductListByProductType(productType)
+	var wg sync.WaitGroup
+	wg.Add(len(productList))
+	for i := 0; i < len(productList); i++ {
+		go func(merchantProduct merchantService.MerchantProductDTO) {
+			defer wg.Done()
+			product := convertToProductDTO(merchantProduct)
+			product.RiskLevel, product.SourceTitle, product.Abstract, product.CoverUrl, product.Src, product.PermissionNames, product.PermissionIds, err = GetProductRiskLevel(merchantProduct)
+			if !compare(product.RiskLevel, riskLevel) {
+				return
+			}
+			match := false
+			for _, pdPermissionId := range product.PermissionIds {
+				for _, permissionId := range filterPermissionIds {
+					if pdPermissionId == permissionId {
+						match = true
+						break
+					}
+				}
+			}
+			if !match {
+				return
+			}
+			dtoList = append(dtoList, &product)
+		}(productList[i])
+	}
+	wg.Wait()
+	total = int64(len(dtoList))
+	var maxId int
+	for i := 0; i < len(dtoList); i++ {
+		productIds = append(productIds, dtoList[i].Id)
+		if dtoList[i].Id > maxId {
+			maxId = dtoList[i].Id
+		}
+	}
+	latestId = int64(maxId)
 	return
 }
+
+func compare(riskLevel, MatchRiskLevel string) bool {
+	pRiskNum, riskErr := configService.ParseRiskLevel(riskLevel)
+	if riskErr != nil {
+		return false
+	}
+	riskLevelNum, riskErr := configService.ParseRiskLevel(MatchRiskLevel)
+	if riskErr != nil {
+		return false
+	}
+	if pRiskNum <= riskLevelNum {
+		return true
+	}
+	return false
+}
+func ProductList(productIds []int, templateUserId int, info page.PageInfo) (dtoList []ProductDTO, err error) {
+	var merchantProductList []merchantService.MerchantProductDTO
+	merchantProductList, err = merchantService.GetProductPageByProductType(productIds, info)
+	for _, product := range merchantProductList {
+		productDTO := convertToProductDTO(product)
+		productDTO.RiskLevel, productDTO.SourceTitle, productDTO.Abstract, productDTO.CoverUrl, productDTO.Src, productDTO.PermissionNames, _, err = GetProductRiskLevel(product)
+		dtoList = append(dtoList, productDTO)
+	}
+	var wg sync.WaitGroup
+	wg.Add(len(dtoList))
+	for i := 0; i < len(dtoList); i++ {
+		go func(productDTO *ProductDTO) {
+			defer wg.Done()
+			productDTO.IsSubscribe = isSubscribeSignal(productDTO.Id, templateUserId)
+		}(&dtoList[i])
+	}
+	wg.Wait()
+	return
+}
+func RangeProductList() (productIdMap map[string][]int, err error) {
+	return merchantService.GetProductByProductType()
+}
+func ProductSearch(key string, templateUserId int, pageInfo page.PageInfo) (list []ProductDTO, err error) {
+	var merchantProductList []merchantService.MerchantProductDTO
+	var sourceIds []int
+	var maxId int
+	for _, product := range merchantProductList {
+		if product.SourceId > maxId {
+			maxId = product.SourceId
+		}
+		sourceIds = append(sourceIds, product.SourceId)
+	}
+	//reportList, err := reportDomain.SearchReportList(key, sourceIds, 1, len(sourceIds), int64(maxId))
+	//mediaList, err := mediaDomain.SearchMediaList(key)
+
+	for _, product := range merchantProductList {
+		productDTO := convertToProductDTO(product)
+		list = append(list, productDTO)
+	}
+	return
+}
+
+func convertReportToSearchDTO(report reportDomain.ReportDTO) (dto reportDomain.ProductSearchDTO) {
+	return reportDomain.ProductSearchDTO{
+		HighLight:  report.Highlight[0],
+		SourceId:   report.ReportID,
+		SourceType: "report",
+		Score:      report.Score,
+	}
+}
+func convertMediaToSearchDTO(media mediaDomain.MediaDTO) (dto reportDomain.ProductSearchDTO) {
+	return reportDomain.ProductSearchDTO{
+		HighLight:  media.Highlight[0],
+		SourceId:   media.MediaId,
+		SourceType: media.MediaType,
+		Score:      media.Score,
+	}
+}
+func SearchRelateProduct(key string, productIdMap map[string][]int) (list []reportDomain.ProductSearchDTO, err error) {
+	var wg sync.WaitGroup
+	wg.Add(2)
+	go func() {
+		defer wg.Done()
+		docIds := productIdMap["report"]
+		reports, reportErr := reportService.SearchReportProduct(key, docIds)
+		if reportErr != nil {
+			logger.Error("搜索相关报告失败:%v,key:%s", reportErr, key)
+			return
+		}
+		for _, report := range reports {
+			list = append(list, convertReportToSearchDTO(report))
+		}
+	}()
+	go func() {
+		defer wg.Done()
+		docIds := append(productIdMap["audio"], productIdMap["video"]...)
+		medias, mediaErr := mediaService.SearchMediaProduct(key, docIds)
+		if mediaErr != nil {
+			logger.Error("搜索相关媒体失败:%v,key:%s", mediaErr, key)
+			return
+		}
+		for _, mediaInfo := range medias {
+			list = append(list, convertMediaToSearchDTO(mediaInfo))
+		}
+	}()
+	wg.Wait()
+	sort.Slice(list, func(i, j int) bool {
+		return list[i].Score > list[j].Score
+	})
+	return
+}
+
+func LatestId() (latestId int64) {
+	return merchantService.LatestId()
+}
+
+func CountSearchPackageList(list []reportDomain.ProductSearchDTO) (total, latestId int64, permissionTotalMap map[int]*atomic.Int32) {
+	productIdMap := make(map[string][]int, len(list))
+	permissionTotalMap = make(map[int]*atomic.Int32)
+	for _, product := range list {
+		ids := productIdMap[product.SourceType]
+		ids = append(ids, product.SourceId)
+		productIdMap[product.SourceType] = ids
+	}
+	var wg sync.WaitGroup
+	wg.Add(len(productIdMap))
+	for key, ids := range productIdMap {
+		go func(k string, ids []int) {
+			defer wg.Done()
+			var permissionMap map[int]int
+			switch k {
+			case "report":
+				permissionMap = reportService.CountPermissionWeight(ids)
+			case "audio", "video":
+				permissionMap = mediaService.CountPermissionWeight(ids)
+			}
+			for permissionId, weight := range permissionMap {
+				if permissionTotalMap[permissionId] == nil {
+					permissionTotalMap[permissionId] = new(atomic.Int32)
+				}
+				permissionTotalMap[permissionId].Add(int32(weight))
+			}
+		}(key, ids)
+	}
+	wg.Wait()
+	total = int64(len(permissionTotalMap))
+	latestId = merchantService.LatestId()
+	return
+}
+
+func ProductListBySort(isSignal bool, list []reportDomain.ProductSearchDTO, weightMap map[int]*atomic.Int32, templateUserId int, info page.PageInfo) (resultList []ProductDTO, err error) {
+	var pdDTOS []merchantService.MerchantProductDTO
+	pdDTOS, err = merchantService.ProductListBySort(isSignal, list, weightMap, info)
+	var wg sync.WaitGroup
+	wg.Add(len(pdDTOS))
+	for _, pd := range pdDTOS {
+		go func(pd merchantService.MerchantProductDTO) {
+			defer wg.Done()
+			productDTO := convertToProductDTO(pd)
+			productDTO.RiskLevel, productDTO.SourceTitle, productDTO.Abstract, productDTO.CoverUrl, productDTO.Src, productDTO.PermissionNames, _, err = GetProductRiskLevel(pd)
+			productDTO.IsSubscribe = isSubscribeSignal(pd.Id, templateUserId)
+			resultList = append(resultList, productDTO)
+		}(pd)
+	}
+	wg.Wait()
+	return
+}
+
+func isSubscribeSignal(productId int, templateUserId int) (isSubscribe bool) {
+	subscribeList, subErr := user.GetUserSubscribe([]int{productId}, templateUserId)
+	if subErr != nil {
+		logger.Error("获取用户订阅状态失败:%v", subErr)
+		return false
+	} else {
+		if len(subscribeList) == 0 {
+			logger.Error("用户未订阅单品,productId:%v", productId)
+			return false
+		} else {
+			return subscribeList[0].Status == merchantDao.SubscribeValid
+		}
+	}
+}

+ 68 - 0
service/product/subscribe_servcie.go

@@ -0,0 +1,68 @@
+package product
+
+import (
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/utils/page"
+	"eta/eta_mini_ht_api/domian/merchant"
+	"sync"
+	"time"
+)
+
+type SubscribeDTO struct {
+	Title           string   `json:"title"`
+	Abstract        string   `json:"abstract"`
+	SourceId        int      `json:"sourceId"`
+	SourceTitle     string   `json:"sourceTitle"`
+	SourceAbstract  string   `json:"sourceAbstract"`
+	SourceSrc       string   `json:"sourceSrc"`
+	Type            string   `json:"type"`
+	RiskLevel       string   `json:"riskLevel"`
+	CoverUrl        string   `json:"coverUrl"`
+	PermissionNames []string `json:"permissionNames"`
+	CreatedDate     string   `json:"createdDate"`
+}
+
+func SubscribeList(templateUserId int, productType string, pageInfo page.PageInfo) (list []SubscribeDTO, err error) {
+	var subscribeList []merchant.UserAccessDTO
+	subscribeList, err = merchant.SubscribeList(templateUserId, productType, pageInfo)
+	var wg sync.WaitGroup
+	wg.Add(len(subscribeList))
+	for _, Subscribe := range subscribeList {
+		go func(Subscribe merchant.UserAccessDTO) {
+			defer wg.Done()
+			product, _ := merchant.GetMerchantProductById(Subscribe.ProductID)
+			subscribe := convertToSubscribeDTO(product)
+			if err != nil {
+				logger.Error("获取风险等级失败[productId:%d]", product.Id)
+			}
+			switch product.Type {
+			case "audio", "video":
+				subscribe.RiskLevel, subscribe.SourceTitle, _, subscribe.CoverUrl, subscribe.SourceSrc, subscribe.PermissionNames, _, err = GetProductRiskLevel(product)
+			case "report":
+				subscribe.RiskLevel, subscribe.SourceTitle, subscribe.SourceAbstract, subscribe.CoverUrl, _, subscribe.PermissionNames, _, err = GetProductRiskLevel(product)
+			case "package":
+				subscribe.RiskLevel, _, _, subscribe.CoverUrl, _, subscribe.PermissionNames, _, err = GetProductRiskLevel(product)
+			default:
+				logger.Error("未知产品类型[productType:%s]", product.Type)
+				return
+			}
+			list = append(list, subscribe)
+		}(Subscribe)
+	}
+	wg.Wait()
+	return
+}
+
+func convertToSubscribeDTO(product merchant.MerchantProductDTO) SubscribeDTO {
+	return SubscribeDTO{
+		SourceId:    product.SourceId,
+		Type:        product.Type,
+		Title:       product.Title,
+		Abstract:    product.Description,
+		CoverUrl:    product.CoverUrl,
+		CreatedDate: product.CreatedTime.Format(time.DateOnly),
+	}
+}
+func GetTotalUserPageCountByProductType(productType string, templateUserId int) (int64, int64) {
+	return merchant.GetTotalUserPageCountByProductType(productType, templateUserId)
+}

+ 301 - 474
service/report/report_service.go

@@ -13,7 +13,7 @@ import (
 	reportService "eta/eta_mini_ht_api/domian/report"
 	userService "eta/eta_mini_ht_api/domian/user"
 	productDao "eta/eta_mini_ht_api/models/merchant"
-	userDao "eta/eta_mini_ht_api/models/user"
+	"eta/eta_mini_ht_api/service/config"
 	user "eta/eta_mini_ht_api/service/user"
 	"fmt"
 	"gorm.io/gorm"
@@ -45,9 +45,11 @@ type PublishRankedReport struct {
 	CoverUrl          string         `json:"coverUrl"`
 	RiskLevel         string         `json:"riskLevel"`
 	IsFree            bool           `json:"isFree"`
+	IsPackage         bool           `json:"isPackage"`
 	Price             string         `json:"price"`
 	IsSubscribe       bool           `json:"isSubscribe"`
 	Login             bool           `json:"login"`
+	ProductId         int            `json:"productId"`
 }
 
 type HotRankedReport struct {
@@ -64,17 +66,12 @@ type HotRankedReport struct {
 	RiskLevel         string         `json:"riskLevel"`
 	IsFree            bool           `json:"isFree"`
 	Price             string         `json:"price"`
+	IsPackage         bool           `json:"isPackage"`
+	ProductId         int            `json:"productId"`
 	IsSubscribe       bool           `json:"isSubscribe"`
 	Login             bool           `json:"login"`
 }
 
-//type PermissionNode struct {
-//	ID       int               `json:"id"`
-//	Name     string            `json:"name"`
-//	ParentID int               `json:"parentId"`
-//	Children []*PermissionNode `json:"children,omitempty"`
-//}
-
 type RecordCount struct {
 	UserId     int
 	TraceId    string
@@ -86,7 +83,7 @@ type RecordCount struct {
 	Additional string
 }
 
-func matchRiskLevel(userId int, report reportService.ReportDTO) (riskLevelMatch string, err error) {
+func matchRiskLevel(userId int, report reportService.ReportDTO) (riskLevelMatch string, riskLevel string, err error) {
 	userProfile, userErr := user.GetUserProfile(userId)
 	if userErr != nil {
 		if errors.Is(userErr, gorm.ErrRecordNotFound) {
@@ -131,7 +128,7 @@ func matchRiskLevel(userId int, report reportService.ReportDTO) (riskLevelMatch
 		return
 	}
 	//能够查看最高等级
-	matchNum, err := parseRiskLevel(level.ProductRiskLevel)
+	matchNum, err := config.ParseRiskLevel(level.ProductRiskLevel)
 	if err != nil {
 		logger.Error("解析风险等级失败:%v", err)
 		return
@@ -142,7 +139,9 @@ func matchRiskLevel(userId int, report reportService.ReportDTO) (riskLevelMatch
 		return
 	}
 	//能够查看需要的最小等级
-	num := getLowestRiskLevel(permissionDTOs)
+	//num := getLowestRiskLevel(permissionDTOs)
+	num := config.GetHighestRiskLevel(permissionDTOs)
+	riskLevel = fmt.Sprintf("R%d", num)
 	if num > matchNum {
 		riskLevelMatch = RiskLevelUnMatch
 		return
@@ -151,85 +150,124 @@ func matchRiskLevel(userId int, report reportService.ReportDTO) (riskLevelMatch
 		return
 	}
 }
-func getHighestRiskLevel(permissions []permissionService.PermissionDTO) (riskLevelNum int) {
-	for _, permission := range permissions {
-		pRiskNum, err := parseRiskLevel(permission.RiskLevel)
-		if err != nil {
-			logger.Error("解析风险等级失败:%v", err)
-			continue
-		}
-		if riskLevelNum == 0 {
-			riskLevelNum = pRiskNum
-		} else {
-			if riskLevelNum < pRiskNum {
-				riskLevelNum = pRiskNum
-			}
-		}
+
+func GetReportById(reportId int, login bool, userId int) (report *reportService.ReportDTO, err error) {
+	var reportInfo reportService.ReportDTO
+	reportInfo, err = reportService.GetReportById(reportId, userId)
+	if err != nil {
+		logger.Error("获取研报失败:%v,研报ID:%d", err, report.ReportID)
+		err = exception.NewWithException(exception.GetReportFailed, err.Error())
+		return
+	}
+	mappingRiskLevel, userRiskStatus, err := user.CheckUserRiskMatchStatus(userId)
+	if err != nil {
+		logger.Error("获取研报失败:%v,研报ID:%d", err, report.ReportID)
+		err = exception.NewWithException(exception.GetReportFailed, err.Error())
+		return
+	}
+	report, err = DealReportInfo(&reportInfo, login, userId, mappingRiskLevel, userRiskStatus)
+	if err != nil {
+		logger.Error("获取研报失败:%v,研报ID:%d", err, report.ReportID)
+		err = exception.NewWithException(exception.GetReportFailed, err.Error())
+		return
+	}
+	err = getReportContent(report, login)
+	if err != nil {
+		logger.Error("获取研报失败:%v,研报ID:%d", err, report.ReportID)
+		err = exception.NewWithException(exception.GetReportFailed, err.Error())
 	}
 	return
 }
-func getLowestRiskLevel(permissions []permissionService.PermissionDTO) (riskLevelNum int) {
-	for _, permission := range permissions {
-		pRiskNum, err := parseRiskLevel(permission.RiskLevel)
-		if err != nil {
-			logger.Error("解析风险等级失败:%v", err)
-			continue
-		}
-		if riskLevelNum == 0 {
-			riskLevelNum = pRiskNum
-		} else {
-			if riskLevelNum > pRiskNum {
-				riskLevelNum = pRiskNum
-			}
-		}
+
+func GetTotalPageCountByPermissionIds(permissionIds []int, isLogin bool, userId int) (total int64, latestId int64, ids map[string][]int, disCardIds []int, mappingRiskLevel, userRiskStatus string) {
+	return getCount(permissionIds, isLogin, userId)
+}
+
+// ParseRiskLevel 解析风险等级字符串,并返回数字部分
+
+func SearchReportList(key string, Ids []int, pageInfo page.PageInfo, isLogin bool, userId int, mappingRiskLevel, userRiskStatus string) (list []reportService.ReportDTO, err error) {
+	offset := page.StartIndex(pageInfo.Current, pageInfo.PageSize)
+	var reports []reportService.ReportDTO
+	reports, err = reportService.SearchReportList(key, Ids, offset, pageInfo.PageSize, pageInfo.LatestId)
+	list, err = dealReportInfo(reports, isLogin, userId, mappingRiskLevel, userRiskStatus)
+	if err != nil {
+		err = exception.New(exception.SearchReportPageFailed)
 	}
 	return
 }
-func GetReportById(reportId int, login bool, userId int) (report reportService.ReportDTO, err error) {
-	report, err = reportService.GetGetReportById(reportId)
+
+func SearchReportProduct(key string, docIds []int) (list []reportService.ReportDTO, err error) {
+	list, err = reportService.SearchReportProduct(key, 100, 0, docIds)
 	if err != nil {
-		logger.Error("获取研报失败:%v", err)
-		err = exception.New(exception.GetReportFailed)
-		return
+		err = exception.New(exception.SearchReportPageFailed)
 	}
-	var status string
-	status, err = matchRiskLevel(userId, report)
+	return
+}
+func RangeSearchByAnalyst(analystName string, userId int) (total int64, latestId int64, ids []int, mappingRiskLevel, userRiskStatus string) {
+	return getCountByAnalyst(nil, true, userId, analystName)
+}
+func RangeSearch(key string, isLogin bool, userId int) (total int64, latestId int64, reportIds []int, discardIds []int, mappingRiskLevel, userRiskStatus string, err error) {
+	var orgIds map[string][]int
+	_, latestId, orgIds, discardIds, mappingRiskLevel, userRiskStatus = getCount(nil, isLogin, userId)
+	reportIds, err = GetReportByIdListByOrgIds(orgIds)
 	if err != nil {
-		logger.Error("匹配风险等级失败:%v", err)
-		err = exception.New(exception.ReportRiskLevelUnSet)
+		logger.Error("获取报告ID列表失败:%v", err)
+		err = exception.NewWithException(exception.GetReportSearchRangeFailed, err.Error())
 		return
 	}
+	total = reportService.SearchMaxReportIdWithRange(key, reportIds)
+	return
+}
+func dealReportInfo(list []reportService.ReportDTO, isLogin bool, userId int, mappingRiskLevel, userRiskStatus string) (resultList []reportService.ReportDTO, err error) {
+	var wg sync.WaitGroup
+	wg.Add(len(list))
+	for i := 0; i < len(list); i++ {
+		go func(report *reportService.ReportDTO) {
+			defer wg.Done()
+			report, err = DealReportInfo(report, isLogin, userId, mappingRiskLevel, userRiskStatus)
+			if err != nil {
+				logger.Error("处理报告信息失败:%v", err)
+			}
+		}(&list[i])
+	}
+	wg.Wait()
+	resultList = list
+	return
+}
+func getETAReportDetail(report *reportService.ReportDTO) (etaReport reportService.ETAReportDTO, err error) {
+	return reportService.GetETAReport(report.OrgId)
+}
+
+func getHTReportDetail(report *reportService.ReportDTO) (url string, err error) {
+	return reportService.GetHtReport(report.OrgId)
+}
+func getReportContent(report *reportService.ReportDTO, login bool) (err error) {
 	var pdfUrl string
 	switch report.Source {
 	case SourceETA:
 		var detail reportService.ETAReportDTO
-		detail, err = getETAReportDetail(&report)
+		detail, err = getETAReportDetail(report)
 		if err != nil {
 			logger.Error("获取研报详情失败失败:%v", err)
-			err = exception.New(exception.GetReportFailed)
 			return
 		}
 		if !login {
 			detail.Content = ""
-			report.RiskLevelStatus = RiskLevelUnMatch
-			report.Login = false
 		} else {
-			if status != RiskLevelMatch {
+			if report.RiskLevelStatus != RiskLevelMatch {
 				detail.Content = ""
 			}
-			report.RiskLevelStatus = status
-			report.Login = true
 		}
 		var jsonStr []byte
 		jsonStr, err = json.Marshal(detail)
 		if err != nil {
 			logger.Error("生成研报详情失败:%v", err)
-			err = exception.New(exception.GetReportFailed)
+			return
 		}
 		report.Detail = jsonStr
 		return
 	case SourceHT:
-		pdfUrl, err = getHTReportDetail(&report)
+		pdfUrl, err = getHTReportDetail(report)
 		if err != nil {
 			logger.Error("获取研报详情失败失败:%v")
 			err = exception.New(exception.GetReportFailed)
@@ -237,14 +275,10 @@ func GetReportById(reportId int, login bool, userId int) (report reportService.R
 		}
 		if !login {
 			report.PdfUrl = ""
-			report.RiskLevelStatus = RiskLevelUnMatch
-			report.Login = false
 		} else {
-			if status == RiskLevelMatch {
+			if report.RiskLevelStatus == RiskLevelMatch {
 				report.PdfUrl = pdfUrl
 			}
-			report.RiskLevelStatus = status
-			report.Login = true
 		}
 		return
 	default:
@@ -253,180 +287,159 @@ func GetReportById(reportId int, login bool, userId int) (report reportService.R
 		return
 	}
 }
-
-func getETAReportDetail(report *reportService.ReportDTO) (etaReport reportService.ETAReportDTO, err error) {
-	return reportService.GetETAReport(report.OrgId)
-}
-
-func getHTReportDetail(report *reportService.ReportDTO) (url string, err error) {
-	return reportService.GetHtReport(report.OrgId)
-}
-func GetTotalPageCountByPermissionIds(permissionIds []int, isLogin bool, userId int) (total int64, latestId int64, ids map[string][]int) {
-	return getCount(permissionIds, isLogin, userId)
-}
-func filterPermissionsByRisk(permissionList []permissionService.PermissionDTO, riskLevel string) (resultList []permissionService.PermissionDTO) {
-	if riskLevel != "" {
-		riskLevelNum, err := parseRiskLevel(riskLevel)
-		if err != nil {
-			logger.Error("风险等级解析失败:%v", err)
-			return
-		}
-		for _, permission := range permissionList {
-			pRiskNum, riskErr := parseRiskLevel(permission.RiskLevel)
-			if riskErr != nil {
-				logger.Error("解析品种风险等级失败 permission:%d,risk:%v", permission.PermissionId, permission.RiskLevel)
-				continue
-			}
-			if pRiskNum <= riskLevelNum {
-				resultList = append(resultList, permission)
-			}
-		}
-	} else {
-		resultList = permissionList
-	}
-	return
-}
-
-// ParseRiskLevel 解析风险等级字符串,并返回数字部分
-func parseRiskLevel(level string) (int, error) {
-	parts := strings.Split(level, "R")
-	if len(parts) < 2 {
-		return 0, fmt.Errorf("无效的风险等级: %s", level)
-	}
-	numberStr := parts[1]
-	number, err := strconv.Atoi(numberStr)
-	if err != nil {
-		return 0, fmt.Errorf("无法将风险等级转换为数字: %s", err)
+func DealReportInfo(report *reportService.ReportDTO, isLogin bool, userId int, mappingRiskLevel, userRiskStatus string) (resultReport *reportService.ReportDTO, err error) {
+	report.Login = isLogin
+	report.Permissions, report.PermissionNames = GetReportPermissionNames(report.OrgId, report.Source)
+	var permissions []permissionService.PermissionDTO
+	permissions, report.SecondPermission = getReportSecondPermissions(report.OrgId, report.Source)
+	if len(permissions) == 0 {
+		resultReport = report
+		return
 	}
-	return number, nil
-}
-
-func SearchReportList(key string, Ids []int, pageInfo page.PageInfo, isLogin bool, userId int) (list []reportService.ReportDTO, err error) {
-	offset := page.StartIndex(pageInfo.Current, pageInfo.PageSize)
-	var reports []reportService.ReportDTO
-	reports, err = reportService.SearchReportList(key, Ids, offset, pageInfo.PageSize, pageInfo.LatestId)
-	list, err = dealReportInfo(reports, isLogin, userId)
+	riskNum := config.GetHighestRiskLevel(permissions)
+	report.RiskLevel = strings.Join([]string{"R", strconv.Itoa(riskNum)}, "")
+	var src string
+	src, err = mediaService.GetImageSrc(report.CoverSrc)
 	if err != nil {
-		err = exception.New(exception.SearchReportPageFailed)
+		logger.Error("获取图片地址失败:%v", err)
+		src = ""
+	} else {
+		report.CoverUrl = src
 	}
-	return
-}
-func RangeSearchByAnalyst(analystName string, userId int) (total int64, latestId int64, ids []int) {
-	return getCountByAnalyst(nil, true, userId, analystName)
-}
-func RangeSearch(key string, isLogin bool, userId int) (total int64, latestId int64, reportIds []int, err error) {
-	var orgIds map[string][]int
-	_, latestId, orgIds = getCount(nil, isLogin, userId)
-	reportIds, err = GetReportByIdListByOrgIds(orgIds)
-	if err != nil {
-		logger.Error("获取报告ID列表失败:%v", err)
-		err = exception.NewWithException(exception.GetReportSearchRangeFailed, err.Error())
-		return
+	//套餐信息
+	var packageList []productService.MerchantProductDTO
+	//查询产品信息
+	product, pdErr := productService.GetProductBySourceId(report.ReportID, productDao.Report)
+	var permissionIds []int
+	if len(permissions) > 0 {
+		for _, permission := range permissions {
+			permissionIds = append(permissionIds, permission.PermissionId)
+		}
+		//单品不存在的话查套餐
+		packageList, err = productService.GetProductListBySourceIds(permissionIds, "package")
+		if err != nil {
+			logger.Error("获取套餐列表失败:%v", err)
+		}
 	}
-	total = reportService.SearchMaxReportIdWithRange(key, reportIds)
-	return
-}
-func dealReportInfo(list []reportService.ReportDTO, isLogin bool, userId int) (resultList []reportService.ReportDTO, err error) {
-	var wg sync.WaitGroup
-	wg.Add(len(list))
-	for i := 0; i < len(list); i++ {
-		go func(report *reportService.ReportDTO) {
-			defer wg.Done()
-			report.Login = isLogin
-			report.PermissionNames = getReportPermissionNames(report.OrgId, report.Source)
-			permissions := getReportSecondPermissions(report.OrgId, report.Source)
-			if len(permissions) == 0 {
-				return
+	if pdErr != nil {
+		//单品不存在的话查套餐
+		if len(packageList) == 0 {
+			logger.Error("获取套餐列表失败:%v", err)
+			report.Price = defaultProductPrice
+			report.IsFree = true
+			report.IsSubscribe = false
+			report.IsPackage = false
+		} else {
+			report.Price = packageList[0].Price
+			report.IsFree = false
+			report.IsSubscribe = false
+			report.IsPackage = true
+			report.ProductId = packageList[0].Id
+		}
+	} else {
+		report.Price = product.Price
+		report.IsFree = false
+		report.ProductId = product.Id
+		report.IsPackage = false
+	}
+	//最热最新的时候使用
+	productList := append(packageList)
+	if pdErr == nil {
+		productList = append(productList, product)
+	}
+	if len(productList) == 0 {
+		report.Show = true
+	} else {
+		for _, productItem := range productList {
+			if productItem.SaleStatus == "on_sale" {
+				report.Show = true
+				break
 			}
-			riskNum := getHighestRiskLevel(permissions)
-			report.RiskLevel = strings.Join([]string{"R", strconv.Itoa(riskNum)}, "")
-			var src string
-			src, err = mediaService.GetImageSrc(report.CoverSrc)
-			if err != nil {
-				logger.Error("获取图片地址失败:%v", err)
-				src = ""
-			} else {
-				report.CoverUrl = src
+		}
+	}
+	if isLogin {
+		var productIds []int
+		if len(packageList) > 0 {
+			for _, packageItem := range packageList {
+				productIds = append(productIds, packageItem.Id)
 			}
-			//下查询产品信息
-			product, pdErr := productService.GetProductBySourceId(report.ReportID, productDao.Report)
-			if pdErr != nil {
-				if errors.Is(pdErr, gorm.ErrRecordNotFound) {
-					report.Price = defaultProductPrice
-					report.IsFree = true
-					report.IsSubscribe = false
-				} else {
-					report.Price = defaultProductPrice
-					report.IsFree = false
-					report.IsSubscribe = false
-				}
-			} else {
-				report.Price = product.Price.String()
-				report.IsFree = false
-				if isLogin {
-					subscribe, subscribeErr := userService.GetUserSubscribe(product.Id, userId)
-					if subscribeErr != nil {
-						report.IsSubscribe = false
-					} else {
-						report.IsSubscribe = subscribe.Status == userDao.SubscribeValid
-					}
+		}
+		if product.Id > 0 {
+			productIds = append(productIds, product.Id)
+		}
+		subscribeList, subscribeErr := userService.GetUserSubscribe(productIds, userId)
+		if subscribeErr != nil {
+			report.IsSubscribe = false
+		} else {
+			for _, subscribe := range subscribeList {
+				if subscribe.Status == productDao.SubscribeValid {
+					report.IsSubscribe = true
+					break
 				}
-				pdRiskNum, parseErr := parseRiskLevel(product.RiskLevel)
+			}
+		}
+		if userRiskStatus != user.RiskValid {
+			if userRiskStatus == user.RiskUnTest {
+				report.RiskLevelStatus = RiskLevelUnTest
+			}
+			if userRiskStatus == user.RiskExpired {
+				report.RiskLevelStatus = RiskLevelExpired
+			}
+		} else {
+			report.RiskLevelStatus = RiskLevelUnMatch
+			if mappingRiskLevel != "" {
+				mappingRiskNum, parseErr := config.ParseRiskLevel(mappingRiskLevel)
 				if parseErr != nil {
 					return
 				}
-				rpRiskNum, parseErr := parseRiskLevel(report.RiskLevel)
+				var rpRiskNum int
+				rpRiskNum, parseErr = config.ParseRiskLevel(report.RiskLevel)
 				if parseErr != nil {
 					return
 				}
-				if rpRiskNum <= pdRiskNum {
-					report.RiskLevel = product.RiskLevel
+				if rpRiskNum <= mappingRiskNum {
+					report.RiskLevelStatus = RiskLevelMatch
 				}
 			}
-		}(&list[i])
+		}
 	}
-	wg.Wait()
-	resultList = list
+	resultReport = report
+
 	return
 }
 
 // GetReportPage 分页获取报告列表
-func GetReportPage(pageInfo page.PageInfo, orgIds map[string][]int, searchAll bool, isLogin bool, userId int) (reports []reportService.ReportDTO, err error) {
+func GetReportPage(pageInfo page.PageInfo, orgIds map[string][]int, discardIds []int, isLogin bool, userId int, mappingRiskLevel, userRiskStatus string) (reports []reportService.ReportDTO, err error) {
 	var list []reportService.ReportDTO
-	list, err = reportService.GetReportPageByOrgIds(pageInfo, orgIds, searchAll)
-	reports, err = dealReportInfo(list, isLogin, userId)
-
+	list, err = reportService.GetReportPageByOrgIds(pageInfo, orgIds, discardIds)
+	reports, err = dealReportInfo(list, isLogin, userId, mappingRiskLevel, userRiskStatus)
 	if err != nil {
 		err = exception.New(exception.QueryReportPageFailed)
 	}
 	return
 }
-func GetReportPageByAnalyst(pageInfo page.PageInfo, analyst string, reportIds []int) (list []reportService.ReportDTO, err error) {
+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)
-	//并发获取研报的标签
-	var wg sync.WaitGroup
-	wg.Add(len(list))
-	for i := 0; i < len(list); i++ {
-		go func(report *reportService.ReportDTO) {
-			defer wg.Done()
-			report.PermissionNames = getReportPermissionNames(report.OrgId, report.Source)
-		}(&list[i])
-	}
-	wg.Wait()
+	list, err = dealReportInfo(list, true, templateUserId, mappingRiskLevel, userRiskStatus)
 	if err != nil {
 		err = exception.New(exception.QueryReportPageFailed)
 	}
 	return
 }
 func CountReport(count RecordCount) (traceId string, err error) {
+	report, err := reportService.GetReportById(count.ReportId, 0)
+	if err != nil {
+		err = exception.New(exception.GetReportFailed)
+		return
+	}
 	dto := convertToRecordCountDTO(count)
+	dto.SourceTitle = report.Title
 	return userService.CountReport(dto)
 }
-func GetRandedReportByWeeklyHot(limit int, isLogin bool, userId int, pdRiskLevel string) (reports []HotRankedReport, err error) {
+func GetRandedReportByWeeklyHot(limit int, isLogin bool, userId int, mappingRiskLevel string, userRiskStatus string) (reports []HotRankedReport, err error) {
 	end := time.Now()
 	begin := date.GetBeginOfTheWeek(end, time.Monday)
 	hotReports := userService.GetHotReports(begin.Format(time.DateOnly), end.Format(time.DateOnly), limit)
-
 	if len(hotReports) > 0 {
 		var dtoList []reportService.ReportDTO
 		var ids []int
@@ -439,118 +452,39 @@ func GetRandedReportByWeeklyHot(limit int, isLogin bool, userId int, pdRiskLevel
 			err = exception.New(exception.GetHotRandListFailed)
 			return
 		}
+		dtoList, err = dealReportInfo(dtoList, isLogin, userId, mappingRiskLevel, userRiskStatus)
+		if err != nil {
+			logger.Error("获取本周最热研报列表失败:%v", err)
+			err = exception.New(exception.GetHotRandListFailed)
+			return
+		}
 		var filterList []reportService.ReportDTO
-		if pdRiskLevel != "" {
+		if mappingRiskLevel != "" {
 			for _, report := range dtoList {
-				product, pdErr := productService.GetProductBySourceId(report.ReportID, productDao.Report)
-				if pdErr != nil {
-					if errors.Is(pdErr, gorm.ErrRecordNotFound) {
-						report.Price = defaultProductPrice
-						report.IsFree = true
-						report.IsSubscribe = false
-					} else {
-						logger.Error("查询产品失败:%v", pdErr)
-						report.Price = defaultProductPrice
-						report.IsFree = false
-						report.IsSubscribe = false
-					}
+				pdRiskNum, paresErr := config.ParseRiskLevel(report.RiskLevel)
+				if paresErr != nil {
+					logger.Error("解析风险等级失败:%v", err)
+					continue
+				}
+				reRiskNum, paresErr := config.ParseRiskLevel(mappingRiskLevel)
+				if paresErr != nil {
+					logger.Error("解析风险等级失败:%v", err)
+					continue
+				}
+
+				if pdRiskNum <= reRiskNum && report.Show {
 					filterList = append(filterList, report)
-				} else {
-					pdRiskNum, paresErr := parseRiskLevel(product.RiskLevel)
-					if paresErr != nil {
-						logger.Error("解析风险等级失败:%v", err)
-						continue
-					}
-					reRiskNum, paresErr := parseRiskLevel(pdRiskLevel)
-					if paresErr != nil {
-						logger.Error("解析风险等级失败:%v", err)
-						continue
-					}
-					report.RiskLevel = product.RiskLevel
-					if isLogin {
-						subscribe, subErr := userService.GetUserSubscribe(product.Id, userId)
-						if subErr != nil {
-							logger.Error("查询用户订阅信息失败:%v,productId:%v,userId:%v", err, product.Id, userId)
-							report.Price = product.Price.String()
-							report.IsFree = false
-							report.IsSubscribe = false
-						} else {
-							report.Price = product.Price.String()
-							report.IsFree = false
-							report.IsSubscribe = subscribe.Status == userDao.SubscribeValid
-						}
-					}
-					if pdRiskNum <= reRiskNum {
-						filterList = append(filterList, report)
-					}
 				}
 			}
 		} else {
-			filterList = dtoList
-			for _, report := range filterList {
-				product, pdErr := productService.GetProductBySourceId(report.ReportID, productDao.Report)
-				if pdErr != nil {
-					if errors.Is(pdErr, gorm.ErrRecordNotFound) {
-						report.Price = defaultProductPrice
-						report.IsFree = true
-						report.IsSubscribe = false
-					} else {
-						logger.Error("查询产品失败:%v", pdErr)
-						report.Price = defaultProductPrice
-						report.IsFree = false
-						report.IsSubscribe = false
-					}
-				} else {
-					report.Price = product.Price.String()
-					report.IsFree = false
-					report.IsSubscribe = false
-					report.RiskLevel = product.RiskLevel
+			for _, report := range dtoList {
+				if report.Show {
+					filterList = append(filterList, report)
 				}
 			}
 		}
-
-		var wg sync.WaitGroup
-		wg.Add(len(filterList))
-		for i := 0; i < len(filterList); i++ {
-			go func(report *reportService.ReportDTO) {
-				defer wg.Done()
-				report.Login = isLogin
-				report.Permissions = getReportPermissionsMap(report.OrgId, report.Source)
-				report.SecondPermission = getReportSecondPermissionsMap(report.OrgId, report.Source)
-				permissions := getReportSecondPermissions(report.OrgId, report.Source)
-				var riskNum int
-				if len(permissions) == 0 {
-					riskNum = 0
-				} else {
-					riskNum = getHighestRiskLevel(permissions)
-				}
-				if report.RiskLevel == "" {
-					report.RiskLevel = strings.Join([]string{"R", strconv.Itoa(riskNum)}, "")
-				} else {
-					reRiskNum, paresErr := parseRiskLevel(report.RiskLevel)
-					if paresErr != nil {
-						logger.Error("解析风险等级失败:%v", err)
-						report.RiskLevel = strings.Join([]string{"R", strconv.Itoa(riskNum)}, "")
-					} else {
-						if reRiskNum <= riskNum {
-							report.RiskLevel = strings.Join([]string{"R", strconv.Itoa(riskNum)}, "")
-						}
-					}
-				}
-				var label []string
-				for _, permission := range report.Permissions {
-					label = append(label, permission)
-				}
-				report.PermissionNames = label
-			}(&filterList[i])
-		}
-		wg.Wait()
 		reports = make([]HotRankedReport, len(ids))
 		for i := 0; i < len(filterList); i++ {
-			risk, parseErr := parseRiskLevel(filterList[i].RiskLevel)
-			if parseErr != nil || risk == 0 {
-				continue
-			}
 			report := convertToHotRankedReport(filterList[i])
 			for j := 0; j < len(hotReports); j++ {
 				if hotReports[j].ReportId == report.Id {
@@ -566,7 +500,7 @@ func GetRandedReportByWeeklyHot(limit int, isLogin bool, userId int, pdRiskLevel
 	return
 }
 
-func GetRandedReportByPublishTimeWeekly(limit int, week bool, isLogin bool, userId int, pdRiskLevel string) (reports []PublishRankedReport, err error) {
+func GetRandedReportByPublishTimeWeekly(limit int, week bool, isLogin bool, userId int, mappingRiskLevel, userRiskStatus string) (reports []PublishRankedReport, err error) {
 	dtoList, err := reportService.GetListOrderByConditionWeekly(week, "published_time", limit, reportService.DESC)
 	if err != nil {
 		logger.Error("获取最新发布的研报列表失败:%v", err)
@@ -574,119 +508,51 @@ func GetRandedReportByPublishTimeWeekly(limit int, week bool, isLogin bool, user
 		return
 	}
 	var filterList []reportService.ReportDTO
-	if pdRiskLevel != "" {
+	dtoList, err = dealReportInfo(dtoList, isLogin, userId, mappingRiskLevel, userRiskStatus)
+	if err != nil {
+		logger.Error("获取最新发布的研报列表失败:%v", err)
+		err = exception.New(exception.GetPublishedRandListFailed)
+		return
+	}
+	if mappingRiskLevel != "" {
 		for _, report := range dtoList {
-			product, pdErr := productService.GetProductBySourceId(report.ReportID, productDao.Report)
-			if pdErr != nil {
-				if errors.Is(pdErr, gorm.ErrRecordNotFound) {
-					report.Price = defaultProductPrice
-					report.IsFree = true
-					report.IsSubscribe = false
-				} else {
-					logger.Error("查询产品失败:%v", pdErr)
-					report.Price = defaultProductPrice
-					report.IsFree = false
-					report.IsSubscribe = false
-				}
+			pdRiskNum, paresErr := config.ParseRiskLevel(report.RiskLevel)
+			if paresErr != nil {
+				logger.Error("解析风险等级失败:%v", err)
+				continue
+			}
+			reRiskNum, paresErr := config.ParseRiskLevel(mappingRiskLevel)
+			if paresErr != nil {
+				logger.Error("解析风险等级失败:%v", err)
+				continue
+			}
+			if pdRiskNum <= reRiskNum && report.Show {
 				filterList = append(filterList, report)
-			} else {
-				pdRiskNum, paresErr := parseRiskLevel(product.RiskLevel)
-				if paresErr != nil {
-					logger.Error("解析风险等级失败:%v", err)
-					continue
-				}
-				reRiskNum, paresErr := parseRiskLevel(pdRiskLevel)
-				if paresErr != nil {
-					logger.Error("解析风险等级失败:%v", err)
-					continue
-				}
-				report.RiskLevel = product.RiskLevel
-				if isLogin {
-					subscribe, subErr := userService.GetUserSubscribe(product.Id, userId)
-					if subErr != nil {
-						logger.Error("查询用户订阅信息失败:%v,productId:%v,userId:%v", err, product.Id, userId)
-						report.Price = product.Price.String()
-						report.IsFree = false
-						report.IsSubscribe = false
-					} else {
-						report.Price = product.Price.String()
-						report.IsFree = false
-						report.IsSubscribe = subscribe.Status == userDao.SubscribeValid
-					}
-				}
-				if pdRiskNum <= reRiskNum {
-					filterList = append(filterList, report)
-				}
 			}
 		}
 	} else {
-		filterList = dtoList
-		for _, report := range filterList {
-			product, pdErr := productService.GetProductBySourceId(report.ReportID, productDao.Report)
-			if pdErr != nil {
-				if errors.Is(pdErr, gorm.ErrRecordNotFound) {
-					report.Price = defaultProductPrice
-					report.IsFree = true
-					report.IsSubscribe = false
-				} else {
-					logger.Error("查询产品失败:%v", pdErr)
-					report.Price = defaultProductPrice
-					report.IsFree = false
-					report.IsSubscribe = false
-				}
-			} else {
-				report.Price = product.Price.String()
-				report.IsFree = false
-				report.IsSubscribe = false
-				report.RiskLevel = product.RiskLevel
+		for _, report := range dtoList {
+			if report.Show {
+				filterList = append(filterList, report)
 			}
 		}
 	}
-	//并发获取研报的标签
-	var wg sync.WaitGroup
-	wg.Add(len(filterList))
-	for i := 0; i < len(filterList); i++ {
-		go func(report *reportService.ReportDTO) {
-			defer wg.Done()
-			report.Login = isLogin
-			report.Permissions = getReportPermissionsMap(report.OrgId, report.Source)
-			report.SecondPermission = getReportSecondPermissionsMap(report.OrgId, report.Source)
-			report.PermissionNames = getReportPermissionNames(report.OrgId, report.Source)
-			var riskNum int
-			permissions := getReportSecondPermissions(report.OrgId, report.Source)
-			if len(permissions) == 0 {
-				riskNum = 0
-			} else {
-				riskNum = getHighestRiskLevel(permissions)
-			}
-			if report.RiskLevel == "" {
-				report.RiskLevel = strings.Join([]string{"R", strconv.Itoa(riskNum)}, "")
-			} else {
-				reRiskNum, paresErr := parseRiskLevel(report.RiskLevel)
-				if paresErr != nil {
-					logger.Error("解析风险等级失败:%v", err)
-					report.RiskLevel = strings.Join([]string{"R", strconv.Itoa(riskNum)}, "")
-				} else {
-					if reRiskNum <= riskNum {
-						report.RiskLevel = strings.Join([]string{"R", strconv.Itoa(riskNum)}, "")
-					}
-				}
-			}
-		}(&filterList[i])
-	}
-	wg.Wait()
 	reports = convertToPublishRankedReportList(filterList)
 	return
 }
 
-func getReportPermissionNames(id int, source string) (labels []string) {
+func GetReportPermissionNames(id int, source string) (permissionMap map[int]string, labels []string) {
 	permissions := reportService.GetReportPermissionsById(id, source)
-	for _, permission := range permissions {
-		labels = append(labels, permission.PermissionName)
+	permissionMap = make(map[int]string, len(permissions))
+	if len(permissions) > 0 {
+		for _, permission := range permissions {
+			labels = append(labels, permission.PermissionName)
+			permissionMap[permission.PermissionId] = permission.PermissionName
+		}
 	}
 	return
 }
-func getReportSecondPermissionsMap(id int, source string) (permissionMap map[int]string) {
+func GetReportSecondPermissionsMap(id int, source string) (permissionMap map[int]string) {
 	permissionMap = make(map[int]string)
 	permissions := reportService.GetReportSecondPermissionsById(id, source)
 	for _, permission := range permissions {
@@ -695,9 +561,15 @@ func getReportSecondPermissionsMap(id int, source string) (permissionMap map[int
 	return
 }
 
-func getReportSecondPermissions(id int, source string) (permissionList []permissionService.PermissionDTO) {
-	return reportService.GetReportSecondPermissionsById(id, source)
-
+func getReportSecondPermissions(id int, source string) (permissionList []permissionService.PermissionDTO, secondPermissionMap map[int]string) {
+	permissionList = reportService.GetReportSecondPermissionsById(id, source)
+	if len(permissionList) > 0 {
+		secondPermissionMap = make(map[int]string, len(permissionList))
+		for _, permission := range permissionList {
+			secondPermissionMap[permission.PermissionId] = permission.PermissionName
+		}
+	}
+	return
 }
 func getReportPermissionsMap(id int, source string) (permissionMap map[int]string) {
 	permissionMap = make(map[int]string)
@@ -712,11 +584,6 @@ func GetPermissionList() (root *permissionService.PermissionNode, err error) {
 }
 
 func convertToHotRankedReport(dto reportService.ReportDTO) (report HotRankedReport) {
-	src, err := mediaService.GetImageSrc(dto.CoverSrc)
-	if err != nil {
-		logger.Error("获取封面图片失败:%v", err)
-		src = ""
-	}
 	report = HotRankedReport{
 		Id:                dto.ReportID,
 		OrgId:             dto.OrgId,
@@ -726,19 +593,21 @@ func convertToHotRankedReport(dto reportService.ReportDTO) (report HotRankedRepo
 		SecondPermissions: dto.SecondPermission,
 		Permissions:       dto.Permissions,
 		PermissionNames:   dto.PermissionNames,
-		CoverUrl:          src,
+		CoverUrl:          dto.CoverUrl,
 		IsSubscribe:       dto.IsSubscribe,
 		IsFree:            dto.IsFree,
 		Price:             dto.Price,
 		RiskLevel:         dto.RiskLevel,
 		Login:             dto.Login,
+		ProductId:         dto.ProductId,
+		IsPackage:         dto.IsPackage,
 	}
 	return
 }
 func convertToPublishRankedReportList(dtoList []reportService.ReportDTO) (reports []PublishRankedReport) {
 	reports = []PublishRankedReport{}
 	for _, dto := range dtoList {
-		risk, err := parseRiskLevel(dto.RiskLevel)
+		risk, err := config.ParseRiskLevel(dto.RiskLevel)
 		if err != nil || risk == 0 {
 			continue
 		}
@@ -762,6 +631,8 @@ func convertToPublishRankedReportList(dtoList []reportService.ReportDTO) (report
 			Price:             dto.Price,
 			RiskLevel:         dto.RiskLevel,
 			Login:             dto.Login,
+			ProductId:         dto.ProductId,
+			IsPackage:         dto.IsPackage,
 		}
 		reports = append(reports, report)
 	}
@@ -790,86 +661,42 @@ func GetReportByIdListByOrgIds(orgIds map[string][]int) (ids []int, err error) {
 	return
 }
 
-func RangePermissionIds(isLogin bool, userId int) (filterPermissionIds []int, riskLevel string, err error) {
-	return CheckUserRisk(nil, isLogin, userId)
+func RangePermissionIds(isLogin bool, userId int) (filterPermissionIds []int, riskLevel string, userRiskStatus string, err error) {
+	return user.GetRiskLevelPermissionList(nil, isLogin, userId)
 }
-func CheckUserRisk(permissionIds []int, isLogin bool, userId int) (filterPermissionIds []int, riskLevel string, err error) {
-	if isLogin {
-		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
-		}
-		//获取产品风险等级
-		if userProfile.RiskLevelStatus == user.RiskUnTest {
-			logger.Warn("客户未做风险等级测评,mobile:%v", userProfile.Mobile)
-		}
-		if userProfile.RiskLevelStatus == user.RiskExpired {
-			logger.Warn("客户风险等级已过期,mobile:%v", userProfile.Mobile)
-		}
-		var mapping permissionService.CustomerProductRiskMappingDTO
-		if userProfile.RiskLevel != "" {
-			mapping, err = permissionService.GetRiskMappingByCustomerRiskLevel(userProfile.RiskLevel)
-			if err != nil {
-				logger.Error("查询产品风险等级映射失败:%v", err)
-				return
-			}
-		}
-		var permissionList []permissionService.PermissionDTO
-		if len(permissionIds) == 0 {
-			//获取所有设置风险等级的品种
-			permissionList, err = permissionService.GetPermissionListWithRisk()
-		} else {
-			//更具id过滤设置了风险等级的品种
-			permissionList, err = permissionService.GetPermissionListByIds(permissionIds)
-		}
-		permissionList = filterPermissionsByRisk(permissionList, mapping.ProductRiskLevel)
-		riskLevel = mapping.ProductRiskLevel
-		if len(permissionList) == 0 {
-			return
-		}
-		for _, permission := range permissionList {
-			filterPermissionIds = append(filterPermissionIds, permission.PermissionId)
-		}
+
+func getCount(permissionIds []int, isLogin bool, userId int) (total int64, latestId int64, ids map[string][]int, discardIds []int, mappingRiskLevel, userRiskStatus string) {
+	filterPermissionIds, mappingRiskLevel, userRiskStatus, err := user.GetRiskLevelPermissionList(permissionIds, isLogin, userId)
+	if err != nil {
+		logger.Error("获取过滤品种信息失败:%v", err)
 		return
-	} else { //没有登录的时候展示所有设置了风险等级的品种报告,筛选的时候过滤传入ID中没有设置风险等级的品种
-		var permissionList []permissionService.PermissionDTO
-		if len(permissionIds) == 0 {
-			//获取所有设置风险等级的品种
-			permissionList, err = permissionService.GetPermissionListWithRisk()
-		} else {
-			//更具id过滤设置了风险等级的品种
-			permissionList, err = permissionService.GetPermissionListByIds(permissionIds)
-		}
-		if err != nil {
-			logger.Error("根据ID查询品种列表失败:%v", err)
-		}
-		for _, permission := range permissionList {
-			filterPermissionIds = append(filterPermissionIds, permission.PermissionId)
-		}
-		//查询品种
+	}
+	if len(filterPermissionIds) == 0 {
+		logger.Warn("不存在设置风险等级的品种信息")
 		return
 	}
+	total, latestId, ids, discardIds = reportService.GetTotalPageCountByPermissionIds(filterPermissionIds)
+	return
 }
-func getCount(permissionIds []int, isLogin bool, userId int) (total int64, latestId int64, ids map[string][]int) {
-	filterPermissionIds, riskLevel, err := CheckUserRisk(permissionIds, isLogin, userId)
+
+func getCountByAnalyst(permissionIds []int, isLogin bool, userId int, analystName string) (total int64, latestId int64, ids []int, mappingRiskLevel, userRiskStatus string) {
+	filterPermissionIds, mappingRiskLevel, userRiskStatus, err := user.GetRiskLevelPermissionList(permissionIds, isLogin, userId)
 	if err != nil {
 		logger.Error("校验用户风险等级失败:%v", err)
 		return
 	}
-	return reportService.GetTotalPageCountByPermissionIds(filterPermissionIds, riskLevel)
+	total, latestId, ids = reportService.GetTotalPageCountByAnalyst(analystName, filterPermissionIds)
+	return
 }
 
-func getCountByAnalyst(permissionIds []int, isLogin bool, userId int, analystName string) (total int64, latestId int64, ids []int) {
-	filterPermissionIds, riskLevel, err := CheckUserRisk(permissionIds, isLogin, userId)
+func CountPermissionWeight(ids []int) (permissionMap map[int]int) {
+	list, err := reportService.CountPermissionWeight(ids)
+	permissionMap = make(map[int]int)
 	if err != nil {
-		logger.Error("校验用户风险等级失败:%v", err)
 		return
 	}
-	return reportService.GetTotalPageCountByAnalyst(analystName, filterPermissionIds, riskLevel)
+	for _, item := range list {
+		permissionMap[item.PermissionId] = item.Weight
+	}
+	return
 }

+ 174 - 7
service/user/user_service.go

@@ -4,8 +4,11 @@ import (
 	"errors"
 	logger "eta/eta_mini_ht_api/common/component/log"
 	"eta/eta_mini_ht_api/common/exception"
+	permissionService "eta/eta_mini_ht_api/domian/config"
 	analystService "eta/eta_mini_ht_api/domian/financial_analyst"
 	userService "eta/eta_mini_ht_api/domian/user"
+	merchantDao "eta/eta_mini_ht_api/models/merchant"
+	"eta/eta_mini_ht_api/service/config"
 	"gorm.io/gorm"
 	"sort"
 	"sync"
@@ -16,6 +19,10 @@ const (
 	RiskValid   = "valid"
 	RiskExpired = "expired"
 	RiskUnTest  = "unTest"
+
+	SubscribeExpired = "expired"
+	UnSubscribe      = "unSubscribe"
+	Subscribing      = "Subscribing"
 )
 
 type User struct {
@@ -27,10 +34,13 @@ type User struct {
 }
 
 type AnalystDetail struct {
-	AnalystName  string `json:"AnalystName"`
-	HeadImgUrl   string `json:"HeadImgUrl"`
-	Introduction string `json:"Introduction"`
-	Followed     string `json:"Followed"`
+	AnalystName             string `json:"AnalystName"`
+	HeadImgUrl              string `json:"HeadImgUrl"`
+	Introduction            string `json:"Introduction"`
+	Followed                string `json:"Followed"`
+	Position                string `json:"Position"`
+	InvestmentCertificate   string `json:"InvestmentCertificate"`
+	ProfessionalCertificate string `json:"ProfessionalCertificate"`
 }
 
 type UserProfile struct {
@@ -40,6 +50,133 @@ type UserProfile struct {
 	UserName        string `json:"userName"`
 }
 
+func CheckUserRiskMatchStatus(userId int) (riskLevel string, userRiskLevelStatus string, err error) {
+	if userId <= 0 {
+		err = exception.New(exception.IllegalTemplateUserId)
+		return
+	}
+	userProfile, userErr := 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
+	}
+	userRiskLevelStatus = userProfile.RiskLevelStatus
+	//获取产品风险等级
+	if userProfile.RiskLevelStatus == RiskUnTest {
+		logger.Warn("客户未做风险等级测评,mobile:%v", userProfile.Mobile)
+	}
+	if userProfile.RiskLevelStatus == RiskExpired {
+		logger.Warn("客户风险等级已过期,mobile:%v", userProfile.Mobile)
+	}
+	if userProfile.RiskLevel != "" && userProfile.RiskLevelStatus == RiskValid {
+		var mapping permissionService.CustomerProductRiskMappingDTO
+		mapping, err = permissionService.GetRiskMappingByCustomerRiskLevel(userProfile.RiskLevel)
+		if err != nil {
+			logger.Error("查询产品风险等级映射失败:%v", err)
+			return
+		}
+		riskLevel = mapping.ProductRiskLevel
+	}
+	return
+
+}
+
+// GetRiskLevelPermissionList 删选掉没有配置风险等级的品种,并校验客户的风险等级,riskLevel只有在客户
+func GetRiskLevelPermissionList(permissionIds []int, isLogin bool, userId int) (filterPermissionIds []int, riskLevel string, userRiskStatus string, err error) {
+	if isLogin {
+		userProfile, userErr := 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 permissionList []permissionService.PermissionDTO
+		if len(permissionIds) == 0 {
+			//获取所有设置风险等级的品种
+			permissionList, err = permissionService.GetPermissionListWithRisk()
+		} else {
+			//更具id过滤设置了风险等级的品种
+			permissionList, err = permissionService.GetPermissionListByIds(permissionIds)
+		}
+		if err != nil {
+			logger.Error("查询有风险等级的品种列表失败:%v", err)
+			return
+		}
+		userRiskStatus = userProfile.RiskLevelStatus
+		//获取产品风险等级
+		if userProfile.RiskLevelStatus == RiskUnTest {
+			logger.Warn("客户未做风险等级测评,mobile:%v", userProfile.Mobile)
+			//err = exception.New(exception.RiskUnTestError)
+		}
+		if userProfile.RiskLevelStatus == RiskExpired {
+			logger.Warn("客户风险等级已过期,mobile:%v", userProfile.Mobile)
+			//err = exception.New(exception.RiskExpiredError)
+		}
+		if userProfile.RiskLevel != "" && userProfile.RiskLevelStatus == RiskValid {
+			var mapping permissionService.CustomerProductRiskMappingDTO
+			mapping, err = permissionService.GetRiskMappingByCustomerRiskLevel(userProfile.RiskLevel)
+			if err != nil {
+				logger.Error("查询产品风险等级映射失败:%v", err)
+				return
+			}
+			permissionList = filterPermissionsByRisk(permissionList, mapping.ProductRiskLevel)
+			riskLevel = mapping.ProductRiskLevel
+		}
+		for _, permission := range permissionList {
+			filterPermissionIds = append(filterPermissionIds, permission.PermissionId)
+		}
+		return
+	} else { //没有登录的时候展示所有设置了风险等级的品种报告,筛选的时候过滤传入ID中没有设置风险等级的品种
+		var permissionList []permissionService.PermissionDTO
+		if len(permissionIds) == 0 {
+			//获取所有设置风险等级的品种
+			permissionList, err = permissionService.GetPermissionListWithRisk()
+		} else {
+			//更具id过滤设置了风险等级的品种
+			permissionList, err = permissionService.GetPermissionListByIds(permissionIds)
+		}
+		if err != nil {
+			logger.Error("查询有风险等级的品种列表失败:%v", err)
+			return
+		}
+		for _, permission := range permissionList {
+			filterPermissionIds = append(filterPermissionIds, permission.PermissionId)
+		}
+		return
+	}
+}
+
+func filterPermissionsByRisk(permissionList []permissionService.PermissionDTO, riskLevel string) (resultList []permissionService.PermissionDTO) {
+	if riskLevel != "" {
+		riskLevelNum, err := config.ParseRiskLevel(riskLevel)
+		if err != nil {
+			logger.Error("风险等级解析失败:%v", err)
+			return
+		}
+		for _, permission := range permissionList {
+			pRiskNum, riskErr := config.ParseRiskLevel(permission.RiskLevel)
+			if riskErr != nil {
+				logger.Error("解析品种风险等级失败 permission:%d,risk:%v", permission.PermissionId, permission.RiskLevel)
+				continue
+			}
+			if pRiskNum <= riskLevelNum {
+				resultList = append(resultList, permission)
+			}
+		}
+	} else {
+		resultList = permissionList
+	}
+	return
+}
 func convertUserDTOToProfile(dto userService.UserDTO) (profile UserProfile) {
 	profile = UserProfile{
 		Mobile:    dto.Mobile,
@@ -98,9 +235,12 @@ func GetAnalystDetail(userId int, analystId int) (analystDetail AnalystDetail, e
 
 func convertToAnalystDetail(dto analystService.FinancialAnalystDTO) AnalystDetail {
 	return AnalystDetail{
-		AnalystName:  dto.Name,
-		HeadImgUrl:   dto.HeadImgUrl,
-		Introduction: dto.Introduction,
+		AnalystName:             dto.Name,
+		HeadImgUrl:              dto.HeadImgUrl,
+		Introduction:            dto.Introduction,
+		Position:                dto.Position,
+		InvestmentCertificate:   dto.InvestmentCertificate,
+		ProfessionalCertificate: dto.ProfessionalCertificate,
 	}
 
 }
@@ -318,3 +458,30 @@ func GetUserByTemplateUserId(templateUserId int) (officialUser userService.Offic
 	}
 	return
 }
+
+func GetUserScribeStatus(productId int, templateUserId int) (subscribe string) {
+	userSubscribe, err := userService.GetUserSubscribe([]int{productId}, templateUserId)
+	if err != nil {
+		logger.Error("获取用户订阅状态失败:%v", err)
+		return UnSubscribe
+	}
+
+	if len(userSubscribe) == 0 {
+		return UnSubscribe
+	}
+	if userSubscribe[0].ProductType != merchantDao.Package && userSubscribe[0].Status == merchantDao.SubscribeExpired {
+		logger.Error("用户订阅状态异常:%v,单品状态为过期,productId:%d", productId)
+		return UnSubscribe
+	}
+	switch userSubscribe[0].Status {
+	case
+		merchantDao.SubscribeClose:
+		return UnSubscribe
+	case merchantDao.SubscribeExpired:
+		return SubscribeExpired
+	case merchantDao.SubscribeValid:
+		return Subscribing
+	default:
+		return UnSubscribe
+	}
+}

+ 1 - 1
task/eta/author/eta_author_task.go

@@ -11,7 +11,7 @@ import (
 
 var (
 	taskName base.TaskType = "ETAAuthorSyncTask"
-	cron                   = "0/60 * * * * *"
+	cron                   = "0 0 * * * *"
 )
 
 // Execute Task ETA取研报的数据

+ 1 - 1
task/eta/permission/eta_permission_task.go

@@ -11,7 +11,7 @@ import (
 
 var (
 	taskName base.TaskType = "ETAPermissionSyncTask"
-	cron                   = "0/10 * * * * *"
+	cron                   = "0 0 * * * *"
 )
 
 // Execute Task ETA取研报的数据

+ 4 - 4
task/message/notice_task.go

@@ -2,6 +2,7 @@ package message
 
 import (
 	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/domian/message"
 	userService "eta/eta_mini_ht_api/domian/user"
 	"eta/eta_mini_ht_api/task/base"
 	"sync"
@@ -14,14 +15,13 @@ var (
 
 // Execute Task ETA取研报的数据
 func (au *NoticeTask) Execute(taskDetail *base.TaskDetail) (err error) {
-	//logger.Info(contants.TaskFormat, "监听更新通知开始")
-	metaInfoList := userService.GetInitMetaInfos()
+	metaInfoList := message.GetInitMetaInfos()
 	var wg sync.WaitGroup
 	wg.Add(len(metaInfoList))
 	for _, metaInfo := range metaInfoList {
-		go func(metaInfo userService.MetaInfoDTO) {
+		go func(metaInfo message.MetaInfoDTO) {
 			defer wg.Done()
-			if !userService.PendingMetaInfo(metaInfo.Id) {
+			if !message.PendingMetaInfo(metaInfo.Id) {
 				return
 			}
 			msgErr := userService.CreateMessage(metaInfo)

+ 5 - 5
task/order/product_order_close_task.go

@@ -8,12 +8,12 @@ import (
 )
 
 var (
-	taskName base.TaskType = "ExpiredCodeTask"
+	taskName base.TaskType = "CloseProductOrderTask"
 	cron                   = "0/60 * * * * *"
 )
 
 // Execute Task ETA取研报的数据
-func (st *SMSTask) Execute(taskDetail *base.TaskDetail) error {
+func (st *OrderTask) Execute(taskDetail *base.TaskDetail) error {
 	logger.Info(contants.TaskFormat, "处理超时未支付的产品订单开始")
 	err := order.BatchCloseOrder()
 	if err != nil {
@@ -23,10 +23,10 @@ func (st *SMSTask) Execute(taskDetail *base.TaskDetail) error {
 	return err
 }
 
-type SMSTask struct {
+type OrderTask struct {
 }
 
 func init() {
-	smsTask := base.NewTask(taskName, cron, new(SMSTask), base.PROD)
-	base.RegisterTask(&smsTask)
+	orderTask := base.NewTask(taskName, cron, new(OrderTask), base.PROD)
+	base.RegisterTask(&orderTask)
 }

+ 40 - 0
task/product/product_expire_task.go

@@ -0,0 +1,40 @@
+package sms
+
+import (
+	logger "eta/eta_mini_ht_api/common/component/log"
+	"eta/eta_mini_ht_api/common/contants"
+	productService "eta/eta_mini_ht_api/domian/merchant"
+	"eta/eta_mini_ht_api/task/base"
+	"time"
+)
+
+var (
+	taskName base.TaskType = "ExpireProductTask"
+	cron                   = "59 10 09 * * *"
+)
+
+// Execute Task ETA取研报的数据
+func (st *OrderTask) Execute(taskDetail *base.TaskDetail) error {
+	logger.Info(contants.TaskFormat, "处理到期产品开始")
+	dateTime := time.Now()
+	var deadLine string
+	if dateTime.Hour() <= 23 {
+		deadLine = dateTime.Add(time.Hour * 24).Format(time.DateOnly)
+	} else {
+		deadLine = dateTime.Format(time.DateOnly)
+	}
+	err := productService.ExpireProduct(deadLine)
+	if err != nil {
+		logger.Error("处理到期产品失败:%v", err)
+	}
+	logger.Info(contants.TaskFormat, "处理到期产品结束")
+	return err
+}
+
+type OrderTask struct {
+}
+
+func init() {
+	orderTask := base.NewTask(taskName, cron, new(OrderTask), base.PROD)
+	base.RegisterTask(&orderTask)
+}

+ 1 - 1
task/report/report_update_task.go

@@ -16,7 +16,7 @@ import (
 
 var (
 	updateTaskName base.TaskType = "ReportUpdateSyncTask"
-	updateCron                   = "0/10 * * * * *"
+	updateCron                   = "0 10 * * * *"
 	redisCache     *cache.RedisCache
 )
 

+ 2 - 0
task/task_starter.go

@@ -7,6 +7,8 @@ import (
 	_ "eta/eta_mini_ht_api/task/eta/author"
 	_ "eta/eta_mini_ht_api/task/eta/permission"
 	_ "eta/eta_mini_ht_api/task/message"
+	_ "eta/eta_mini_ht_api/task/order"
+	_ "eta/eta_mini_ht_api/task/product"
 	_ "eta/eta_mini_ht_api/task/report"
 	_ "eta/eta_mini_ht_api/task/sms"
 	"github.com/beego/beego/v2/server/web"