2025-02-05 18:45:49 +08:00
|
|
|
package logic
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2025-02-07 15:17:01 +08:00
|
|
|
"encoding/json"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2025-02-14 17:21:32 +08:00
|
|
|
"gitea.youtukeji.com.cn/youtu/youtu_grpc/app/auth_service/auth"
|
2025-02-14 17:30:53 +08:00
|
|
|
"gitea.youtukeji.com.cn/youtu/youtu_grpc/app/ecpm_service/ecpm"
|
|
|
|
"gitea.youtukeji.com.cn/youtu/youtu_grpc/app/ecpm_service/internal/svc"
|
2025-02-07 15:17:01 +08:00
|
|
|
"io"
|
|
|
|
"net/http"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
2025-02-05 18:45:49 +08:00
|
|
|
|
|
|
|
"github.com/zeromicro/go-zero/core/logx"
|
|
|
|
)
|
|
|
|
|
|
|
|
type GetEcpmLogic struct {
|
|
|
|
ctx context.Context
|
|
|
|
svcCtx *svc.ServiceContext
|
|
|
|
logx.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
func NewGetEcpmLogic(ctx context.Context, svcCtx *svc.ServiceContext) *GetEcpmLogic {
|
|
|
|
return &GetEcpmLogic{
|
|
|
|
ctx: ctx,
|
|
|
|
svcCtx: svcCtx,
|
|
|
|
Logger: logx.WithContext(ctx),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-02-14 10:30:15 +08:00
|
|
|
func (l *GetEcpmLogic) GetEcpm(in *ecpm.GetEcpmRequest) (response *ecpm.GetEcpmResponse, err error) {
|
2025-02-07 15:17:01 +08:00
|
|
|
//获取抖音accessToken
|
2025-02-14 10:30:15 +08:00
|
|
|
res, err := l.svcCtx.AuthServiceClient.GetAccessToken(l.ctx, &auth.GetAccessTokenRequest{
|
2025-02-07 15:17:01 +08:00
|
|
|
AppId: in.AppId,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//获取抖音ECPM数据
|
|
|
|
list, err := GetEcpmData(res.AccessToken, in.AppId, in.OpenId, time.Now().Format(time.DateOnly))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
//计算ECPM具体值
|
2025-02-14 10:39:52 +08:00
|
|
|
ecpmVal := CalcEcpm(list)
|
2025-02-07 15:17:01 +08:00
|
|
|
|
|
|
|
//获取后端配置的ECPM值
|
|
|
|
config, ok := l.svcCtx.EcpmConfig.Get(in.AppId)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("未找到小程序配置")
|
|
|
|
}
|
|
|
|
|
|
|
|
//判断是否满足条件
|
2025-02-17 11:29:48 +08:00
|
|
|
if ecpmVal > config.ECPM && len(list) > config.IPU {
|
2025-02-07 15:17:01 +08:00
|
|
|
response.Ok = true
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2025-02-14 10:43:28 +08:00
|
|
|
// CalcEcpm 计算ECPM值
|
2025-02-07 15:17:01 +08:00
|
|
|
func CalcEcpm(res []Record) (ecpm float64) {
|
|
|
|
// 计算 ECPM
|
|
|
|
totalRecords := len(res)
|
|
|
|
|
|
|
|
// 如果没有记录,则返回 0
|
|
|
|
if totalRecords == 0 {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
totalCost := 0
|
|
|
|
|
|
|
|
for _, record := range res {
|
|
|
|
totalCost += record.Cost
|
|
|
|
}
|
|
|
|
|
|
|
|
// 总 cost / 100000 * 1000 / 总记录数
|
|
|
|
ecpm = float64(totalCost) / 100000 * 1000 / float64(totalRecords)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetEcpmData 获取ECPM数据
|
|
|
|
// appId: 小程序id
|
|
|
|
// openId: 抖音openId
|
|
|
|
// dateHour: 日期
|
|
|
|
func GetEcpmData(accessToken, appId, openId, dateHour string) (list []Record, err error) {
|
|
|
|
list, err = GetEcpm(GetEcpmParams{
|
|
|
|
AppId: appId,
|
|
|
|
OpenId: openId,
|
|
|
|
AccessToken: accessToken,
|
|
|
|
DateHour: dateHour,
|
|
|
|
PageSize: 500,
|
|
|
|
PageNo: 1,
|
|
|
|
})
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
const getEcpm = "https://minigame.zijieapi.com/mgplatform/api/apps/data/get_ecpm" // 获取ECPM
|
|
|
|
|
|
|
|
// GetEcpm 获取ECPM
|
|
|
|
// https://bytedance.larkoffice.com/docx/Vg4yd0RDSovZINxJDyIc6THhnod
|
|
|
|
// 根据分页大小循环获取,聚合总数返回
|
|
|
|
func GetEcpm(params GetEcpmParams) (list []Record, err error) {
|
|
|
|
fullURL := fmt.Sprintf("%s?open_id=%s&mp_id=%s&access_token=%s&date_hour=%s&page_size=500&page_no=", getEcpm, params.OpenId, params.AppId, params.AccessToken, params.DateHour)
|
|
|
|
for {
|
|
|
|
fullURL += strconv.Itoa(params.PageNo)
|
|
|
|
var responseBody []byte
|
|
|
|
func() {
|
|
|
|
// 调用抖音 API
|
|
|
|
var resp *http.Response
|
|
|
|
resp, err = http.Get(fullURL)
|
|
|
|
if err != nil {
|
|
|
|
err = errors.New("调用抖音 API 失败")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
// 读取抖音 API 响应数据
|
|
|
|
responseBody, err = io.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
err = errors.New("读取抖音 API 响应失败")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
// 解析抖音 API 响应数据
|
|
|
|
var apiResponse GetEcpmResponseData
|
|
|
|
err = json.Unmarshal(responseBody, &apiResponse)
|
|
|
|
if err != nil {
|
|
|
|
err = errors.New("解析 API 响应数据失败")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// 检查 API 是否返回错误
|
|
|
|
if apiResponse.ErrNo != 0 {
|
|
|
|
err = fmt.Errorf("抖音 API 返回错误: %s (错误码: %d)", apiResponse.ErrMsg, apiResponse.ErrNo)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
list = append(list, apiResponse.Data.Records...)
|
|
|
|
|
|
|
|
// 当页数据小于总页数时,无更多数据,返回
|
|
|
|
if len(apiResponse.Data.Records) <= params.PageSize {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
params.PageNo++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// GetEcpmParams 获取ECPM参数
|
|
|
|
type GetEcpmParams struct {
|
|
|
|
AppId string `json:"app_id" form:"app_id"`
|
|
|
|
OpenId string `json:"open_id" form:"open_id"`
|
|
|
|
AccessToken string `json:"access_token" form:"access_token"`
|
|
|
|
DateHour string `json:"date_hour" form:"date_hour"`
|
|
|
|
PageSize int `json:"page_size" form:"page_size"`
|
|
|
|
PageNo int `json:"page_no" form:"page_no"`
|
|
|
|
}
|
|
|
|
|
|
|
|
type GetEcpmResponseData struct {
|
|
|
|
BaseResp struct {
|
|
|
|
StatusCode int `json:"StatusCode"`
|
|
|
|
StatusMessage string `json:"StatusMessage"`
|
|
|
|
} `json:"BaseResp"`
|
|
|
|
Data struct {
|
|
|
|
Records []Record `json:"records"`
|
|
|
|
Total int `json:"total"`
|
|
|
|
} `json:"data"`
|
|
|
|
ErrMsg string `json:"err_msg"`
|
|
|
|
ErrNo int `json:"err_no"`
|
|
|
|
LogID string `json:"log_id"`
|
|
|
|
}
|
2025-02-05 18:45:49 +08:00
|
|
|
|
2025-02-07 15:17:01 +08:00
|
|
|
type Record struct {
|
|
|
|
Aid string `json:"aid"`
|
|
|
|
Cost int `json:"cost"`
|
|
|
|
Did string `json:"did"`
|
|
|
|
EventName string `json:"event_name"`
|
|
|
|
EventTime string `json:"event_time"`
|
|
|
|
OpenID string `json:"open_id"`
|
|
|
|
ID int `json:"id"`
|
2025-02-05 18:45:49 +08:00
|
|
|
}
|