From 75d43bc3b7cf93b3f43dedf766a16086cf334606 Mon Sep 17 00:00:00 2001 From: xiabin Date: Tue, 14 Jan 2025 18:35:29 +0800 Subject: [PATCH] update --- access-token/access_token.go | 11 +---- douyin_openapi.go | 92 +++++++++++++++++++++++++++++++++++- douyin_openapi_test.go | 32 ++++++++++++- 3 files changed, 123 insertions(+), 12 deletions(-) diff --git a/access-token/access_token.go b/access-token/access_token.go index 2978cae..ab91749 100644 --- a/access-token/access_token.go +++ b/access-token/access_token.go @@ -12,9 +12,6 @@ import ( // 正式地址 const accessTokenURL = "https://developer.toutiao.com/api/apps/v2/token" -// 沙盒地址 -const sandBoxTokenURL = "https://open-sandbox.douyin.com/api/apps/v2/token" - // AccessToken 管理AccessToken 的基础接口 type AccessToken interface { GetCacheKey() string // 获取缓存的key @@ -37,7 +34,7 @@ type DefaultAccessToken struct { } // NewDefaultAccessToken 实例化默认的token管理类 -func NewDefaultAccessToken(appId, appSecret string, cache cache.Cache, IsSandbox bool) AccessToken { +func NewDefaultAccessToken(appId, appSecret string, cache cache.Cache, IsSandbox bool) *DefaultAccessToken { if cache == nil { panic(any("cache is need")) } @@ -80,11 +77,7 @@ func (dd *DefaultAccessToken) GetAccessToken() (string, error) { } // 开始调用接口获取token - api := accessTokenURL - if dd.SandBox { - api = sandBoxTokenURL - } - reqAccessToken, err := GetTokenFromServer(api, dd.AppId, dd.AppSecret) + reqAccessToken, err := GetTokenFromServer(accessTokenURL, dd.AppId, dd.AppSecret) if err != nil { return "", err } diff --git a/douyin_openapi.go b/douyin_openapi.go index 8999831..40203cf 100644 --- a/douyin_openapi.go +++ b/douyin_openapi.go @@ -2,14 +2,19 @@ package douyin_openapi import ( "encoding/json" + "errors" "fmt" accessToken "gitea.youtukeji.com.cn/xiabin/douyin-openapi/access-token" "gitea.youtukeji.com.cn/xiabin/douyin-openapi/cache" "gitea.youtukeji.com.cn/xiabin/douyin-openapi/util" + "io" + "net/http" + "strconv" ) const ( - code2Session = "/api/apps/v2/jscode2session" // 小程序登录地址 + code2Session = "/api/apps/v2/jscode2session" // 小程序登录地址 + getEcpm = "https://minigame.zijieapi.com/mgplatform/api/apps/data/get_ecpm" // 获取ECPM ) // DouYinOpenApiConfig 实例化配置 @@ -104,3 +109,88 @@ func (d *DouYinOpenApi) Code2Session(code, anonymousCode string) (code2SessionRe } return } + +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"` +} + +// 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"` +} + +// GetEcpmResponseData 获取ECPM响应数据 +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"` +} + +// GetEcpm 获取ECPM +// https://bytedance.larkoffice.com/docx/Vg4yd0RDSovZINxJDyIc6THhnod +// 根据分页大小循环获取,聚合总数返回 +func (d *DouYinOpenApi) 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++ + } +} diff --git a/douyin_openapi_test.go b/douyin_openapi_test.go index 0ca3a0d..b7c318d 100644 --- a/douyin_openapi_test.go +++ b/douyin_openapi_test.go @@ -4,12 +4,13 @@ import ( accessToken "gitea.youtukeji.com.cn/xiabin/douyin-openapi/access-token" "gitea.youtukeji.com.cn/xiabin/douyin-openapi/cache" "testing" + "time" ) // 声明测试常量 const ( - AppId = "" - AppSecret = "" + AppId = "tt8b32fd8f14071db707" + AppSecret = "44018e80b1bde34395a52de67ce1e0c37c572d80" Token = "" Salt = "" ) @@ -60,3 +61,30 @@ func TestDouYinOpenApi_Code2Session(t *testing.T) { } t.Logf("got a value %+v", session) } + +func TestDouYinOpenApi_GetEcpm(t *testing.T) { + + // 获取动态的 access_token + token := accessToken.NewDefaultAccessToken(AppId, AppSecret, Cache, true) + + at, err := token.GetAccessToken() + if err != nil { + t.Errorf("got a error: %s", err.Error()) + return + } + + list, err := OpenApi.GetEcpm(GetEcpmParams{ + AppId: AppId, + OpenId: "_000aG3MQTzVW-yCUH-ndwv90JIyvdTSD0gf", + AccessToken: at, + DateHour: time.Now().Format(time.DateOnly), + PageSize: 500, + PageNo: 1, + }) + + if err != nil { + t.Errorf("got a error %s", err.Error()) + return + } + t.Logf("got a value %+v", list) +}