package logic import ( "context" "encoding/json" "errors" "fmt" "gitea.youtukeji.com.cn/youtu/youtu_grpc/app/auth_service/auth" "gitea.youtukeji.com.cn/youtu/youtu_grpc/app/ecpm_service/ecpm" "gitea.youtukeji.com.cn/youtu/youtu_grpc/app/ecpm_service/internal/svc" "io" "net/http" "strconv" "time" "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), } } func (l *GetEcpmLogic) GetEcpm(in *ecpm.GetEcpmRequest) (response *ecpm.GetEcpmResponse, err error) { response = &ecpm.GetEcpmResponse{} //获取抖音accessToken res, err := l.svcCtx.AuthServiceClient.GetAccessToken(l.ctx, &auth.GetAccessTokenRequest{ 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具体值 ecpmVal := CalcEcpm(list) l.Logger.Infof("user:%s,ecpm:%f", in.OpenId, ecpmVal) //获取后端配置的ECPM值 config, ok := l.svcCtx.EcpmConfig.Get(in.AppId) if !ok { return nil, errors.New("未找到小程序配置") } //判断是否满足条件 if ecpmVal > config.ECPM && len(list) > config.IPU { response.Ok = true } return } // CalcEcpm 计算ECPM值 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"` } 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"` }