douyin-openapi/douyin_openapi.go
2025-01-14 18:35:29 +08:00

197 lines
5.6 KiB
Go

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" // 小程序登录地址
getEcpm = "https://minigame.zijieapi.com/mgplatform/api/apps/data/get_ecpm" // 获取ECPM
)
// DouYinOpenApiConfig 实例化配置
type DouYinOpenApiConfig struct {
AppId string
AppSecret string
AccessToken accessToken.AccessToken
Cache cache.Cache
IsSandbox bool
Token string
Salt string
}
// DouYinOpenApi 基类
type DouYinOpenApi struct {
Config DouYinOpenApiConfig
BaseApi string
}
// NewDouYinOpenApi 实例化一个抖音openapi实例
func NewDouYinOpenApi(config DouYinOpenApiConfig) *DouYinOpenApi {
if config.Cache == nil {
config.Cache = cache.NewMemory()
}
if config.AccessToken == nil {
config.AccessToken = accessToken.NewDefaultAccessToken(config.AppId, config.AppSecret, config.Cache, config.IsSandbox)
}
BaseApi := "https://developer.toutiao.com"
if config.IsSandbox {
BaseApi = "https://open-sandbox.douyin.com"
}
return &DouYinOpenApi{
Config: config,
BaseApi: BaseApi,
}
}
// GetApiUrl 获取api地址
func (d *DouYinOpenApi) GetApiUrl(url string) string {
return fmt.Sprintf("%s%s", d.BaseApi, url)
}
// PostJson 封装公共的请求方法
func (d *DouYinOpenApi) PostJson(api string, params any, response any) (err error) {
body, err := util.PostJSON(api, params)
if err != nil {
return
}
err = json.Unmarshal(body, &response)
if err != nil {
return
}
return
}
// Code2SessionParams 小程序登录 所需参数
type Code2SessionParams struct {
Appid string `json:"appid,omitempty"`
Secret string `json:"secret,omitempty"`
AnonymousCode string `json:"anonymous_code,omitempty"`
Code string `json:"code,omitempty"`
}
// Code2SessionResponse 小程序登录返回值
type Code2SessionResponse struct {
ErrNo int `json:"err_no,omitempty"`
ErrTips string `json:"err_tips,omitempty"`
Data Code2SessionResponseData `json:"data,omitempty"`
}
type Code2SessionResponseData struct {
SessionKey string `json:"session_key,omitempty"`
Openid string `json:"openid,omitempty"`
AnonymousOpenid string `json:"anonymous_openid,omitempty"`
UnionId string `json:"unionid,omitempty"`
}
// Code2Session 小程序登录
func (d *DouYinOpenApi) Code2Session(code, anonymousCode string) (code2SessionResponse Code2SessionResponse, err error) {
params := Code2SessionParams{
Appid: d.Config.AppId,
Secret: d.Config.AppSecret,
AnonymousCode: anonymousCode,
Code: code,
}
err = d.PostJson(d.GetApiUrl(code2Session), params, &code2SessionResponse)
if err != nil {
return
}
if code2SessionResponse.ErrNo != 0 {
return code2SessionResponse, fmt.Errorf("小程序登录错误: %s %d", code2SessionResponse.ErrTips, code2SessionResponse.ErrNo)
}
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++
}
}