221 lines
6.0 KiB
Go
221 lines
6.0 KiB
Go
|
package douyin
|
||
|
|
||
|
import (
|
||
|
"encoding/json"
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
accessToken "gitea.youtukeji.com.cn/youtu/openapi-helper/douyin/access-token"
|
||
|
util2 "gitea.youtukeji.com.cn/youtu/openapi-helper/douyin/util"
|
||
|
"github.com/silenceper/wechat/v2/cache"
|
||
|
"io"
|
||
|
"net/http"
|
||
|
"strconv"
|
||
|
)
|
||
|
|
||
|
const (
|
||
|
code2Session = "https://minigame.zijieapi.com/mgplatform/api/apps/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)
|
||
|
}
|
||
|
return &DouYinOpenApi{
|
||
|
Config: config,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// GetApiUrl 获取api地址
|
||
|
func (d *DouYinOpenApi) GetApiUrl(url string) string {
|
||
|
return fmt.Sprintf("%s%s", d.BaseApi, url)
|
||
|
}
|
||
|
|
||
|
// Get 获取数据
|
||
|
func (d *DouYinOpenApi) Get(url string, params any) (data []byte, err error) {
|
||
|
paramsStr, err := util2.StructToQueryParams(params)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
fullURL := fmt.Sprintf("%s?%s", url, paramsStr)
|
||
|
res, err := http.Get(fullURL)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
defer res.Body.Close() // 关闭连接
|
||
|
data, err = io.ReadAll(res.Body)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// PostJson 封装公共的请求方法
|
||
|
func (d *DouYinOpenApi) PostJson(api string, params any, response any) (err error) {
|
||
|
body, err := util2.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"`
|
||
|
}
|
||
|
|
||
|
type Code2SessionResponse struct {
|
||
|
Errcode int `json:"errcode"`
|
||
|
Errmsg string `json:"errmsg"`
|
||
|
Message string `json:"message,omitempty"`
|
||
|
AnonymousOpenid string `json:"anonymous_openid"`
|
||
|
Error int `json:"error"`
|
||
|
Openid string `json:"openid"`
|
||
|
SessionKey string `json:"session_key"`
|
||
|
Unionid string `json:"unionid"`
|
||
|
}
|
||
|
|
||
|
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,
|
||
|
}
|
||
|
b, err := d.Get(code2Session, params)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
err = json.Unmarshal(b, &code2SessionResponse)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
return code2SessionResponse, nil
|
||
|
}
|
||
|
|
||
|
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++
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// GetAccessToken 获取accessToken
|
||
|
func (d *DouYinOpenApi) GetAccessToken() (string, error) {
|
||
|
return d.Config.AccessToken.GetAccessToken()
|
||
|
}
|