說明:
廣告系列 : 廣告組關係 1 : n
廣告組 : 廣告關係 1 : n
業務邏輯
全量、增量更新從Facebook獲取的數據的定時任務
UpdateFBAds 增量更新facebook 所有廣告數據
- 1、獲取當前開通自動化投放的賬號(調用application服務獲取Facebook賬戶ID)
- 2、獲取時間(根據賬戶ID 從redis獲取)
- 根據賬戶ID 獲取廣告系列最新的更新時間 暫定全量更新,
- 根據賬戶ID 獲取廣告組最新的更新時間
- 根據賬戶ID 獲取廣告最新的更新時間
- 3、全量、增量更新(根據前端傳值 refresh == 0:刷新所有; refresh ==1: 只刷新廣告組和廣告; refresh ==2: 系列)
- 第一次時間爲0,全量更新;後續只需要過濾出比上次最新更新時間大的數據,增量更新
- 賬戶的新增廣告系列
- 請求Facebook獲取數據,若出現錯誤,請求重試機制,重試3次
- 處理Facebook返回的數據,取出自己需要用的,剩餘的全部存到json對象裏
- 入庫(mysql、redis)
- FB返回的結果集是按照時間降序排列的,獲取本次操作的最大更新時間(第0個):accCps[0].UpdatedAt
- 入庫,gorm的save方法。(save:可更新或新增)
- 將最新時間set到redis
- 如果出現錯誤,釘釘報警,發送到我們內部釘釘羣,我們便可以及時查看錯誤
- message = fmt.Sprintf("[業務異常][服務:%s][主機名:%s] %s", serverName, hostname, message)
- 賬戶的新增廣告組
- 與系列同理
- 賬戶的新增廣告
- 與系列同理
- 4、更新完成之後,kafka 通知BI本天更新完成
- gkafka.ProducerString(context.Background(), "adv_asset_notice", "ok")
代碼實現
schedule-console-調度服務
internal-cron
cron-cron.go(啓動定時任務)
package cron
import (
"context"
"gitlab.ftsview.com/aircraft/schedule-console/internal/logic"
"gitlab.ftsview.com/fotoable-go/gkafka"
"gitlab.ftsview.com/fotoable-go/glog"
"github.com/robfig/cron/v3"
)
var cronIns Cron
type Cron struct {
cron *cron.Cron
ctx context.Context
}
func InitCron() {
// nyc, _ := time.LoadLocation("Asia/Shanghai")
// c := cron.New(cron.WithLocation(nyc))
c := cron.New()
c.Start()
cronIns.cron = c
cronIns.ctx = context.Background()
cronIns.StartUpdateFBAdData()
}
func (c Cron) StartCron(timer string, f func()) {
_, err := c.cron.AddFunc(timer, f)
if err != nil {
glog.Errorf(c.ctx, "start cron error %w", err)
}
}
func (c Cron) StartUpdateFBAdData() {
// 測試時間5s一更新:@every 5s
// 01 00 * * *
c.StartCron("01 00 * * *", func() {
result, _ := logic.SingletonFbCampaginLogic().UpdateFBAds(cronIns.ctx, nil)
glog.Infof(cronIns.ctx, "本次更新廣告系列數:%d, 廣告組數: %d, 廣告數: %d", result.Campagins, result.AdSets, result.Ads)
//TODO 通知BI本天更新完成
gkafka.ProducerString(context.Background(), "adv_asset_notice", "ok")
})
//c.StartCron("30 17 * * *", func() {
// if config.GlobConfig.Syncbi {
// logic.SingletonFBAdsetLogic().CromSyncBi(cronIns.ctx)
// }
//})
}
internal-logic
logic-fb_campagic.go(廣告系列logic)
package logic
import (
"context"
"fmt"
"gitlab.ftsview.com/aircraft/schedule-console/internal/constants"
"gitlab.ftsview.com/aircraft/schedule-console/internal/handler/bean"
"gitlab.ftsview.com/aircraft/schedule-console/internal/service"
"gitlab.ftsview.com/fotoable-go/glog"
"sync"
"sync/atomic"
)
var (
_fbCampaginOnce sync.Once
_fbCampaginLogic *FbCampaginLogic
)
type FbCampaginLogic struct {
fbCampaginService *service.FbCampaginService // 廣告系列service
ramblerService *service.RamblerService // 獲取賬號service
fbAdSetService *service.FbAdSetService // 廣告組service
fbAdService *service.FbAdService // 廣告service
}
func SingletonFbCampaginLogic() *FbCampaginLogic {
_fbCampaginOnce.Do(func() {
_fbCampaginLogic = &FbCampaginLogic{
fbCampaginService: service.SingletonFbCampaginService(),
ramblerService: service.SingletonRamblerService(),
fbAdSetService: service.SingletonFbAdSetService(),
fbAdService: service.SingletonFbAdService(),
}
})
return _fbCampaginLogic
}
// UpdateFBAds 增量更新facebook 所有廣告數據
func (f *FbCampaginLogic) UpdateFBAds(ctx context.Context,
fields *bean.CampaignListRequest) (bean.UpdateResult, error) {
var (
accountIDs []string
refresh int
campaignCounts int64
adSetCounts int64
adCounts int64
)
if fields != nil {
accountIDs = fields.AccountID
refresh = fields.Refresh
}
// 獲取當前開通自動化投放的賬號
accountList := f.ramblerService.GetFBCruiserAccount(ctx, accountIDs...)
fmt.Println("accountList:==", accountList)
for _, it := range accountList {
// 獲取廣告系列最新的更新時間 暫定全量更新,
// 全量更新 ,暫不Redis 設置時間戳,因爲 facebook update_time,預算更改,不會更新update_time字段問題,所以全量拉取
campaignAt := f.fbCampaginService.GetMaxUpdateTime(ctx, "2543780585639422")
// 獲取廣告組最新的更新時間
adSetAt := f.fbAdSetService.GetMaxUpdateTime(ctx, "2543780585639422")
// 獲取廣告最新的更新時間
adAt := f.fbAdService.GetMaxUpdateTime(ctx, "2543780585639422")
// 賬戶的新增廣告系列
// 根據前端傳值 refresh == 0:刷新所有; refresh ==1: 只刷新廣告組和廣告; refresh ==2: 系列
if refresh == 0 || refresh == 2 {
campaigns := f.fbCampaginService.GetCampaignsByAccount(ctx, it.AccountId, constants.EmptyString,
constants.RetryInit, nil, campaignAt)
glog.Infof(ctx, "account: %s,Insert Campaigns success: %d條,maxTime: %d ", it.AccountId, len(campaigns), campaignAt)
atomic.AddInt64(&campaignCounts, int64(len(campaigns)))
f.fbCampaginService.InsertCampaignsByAccount(ctx, campaigns, it.AccountId, constants.RealBoolean)
}
if refresh == 0 || refresh == 1 {
// 賬戶的新增廣告組
adSets := f.fbAdSetService.GetFBAdSetList(ctx, it.AccountId, constants.EmptyString, constants.RetryInit,
nil, adSetAt) // > 1669015007 有6條數據
glog.Infof(ctx, "account: %s,Insert AdSets success: %d條,maxTime: %d ", it.AccountId, len(adSets), adSetAt)
atomic.AddInt64(&adSetCounts, int64(len(adSets)))
f.fbAdSetService.InsertAdSetByAccount(ctx, adSets, it.AccountId, constants.RealBoolean)
// 賬戶的新增廣告
ads := f.fbAdService.GetFBAdList(ctx, it.AccountId, constants.EmptyString, constants.RetryInit,
nil, adAt) // > 1669015007 有6條數據
glog.Infof(ctx, "account: %s,Insert Ads success: %d條,maxTime: %d ", it.AccountId, len(ads), adAt)
atomic.AddInt64(&adCounts, int64(len(ads)))
f.fbAdService.InsertAdByAccount(ctx, ads, it.AccountId, constants.RealBoolean)
}
}
result := bean.UpdateResult{
Campagins: campaignCounts,
AdSets: adSetCounts,
Ads: adCounts,
}
return result, nil
}
// InsertCampaigns 拉取併入庫廣告系列數據
//func (f *FbcampaginLogic) InsertCampaigns(ctx context.Context) (string, error) {
// accountList := f.ramblerService.GetFBCruiserAccount(ctx)
// for _, it := range accountList {
// timestamp := f.fbcampaginService.GetMaxUpdateTime(ctx, it.AccountId)
// }
//
// return "", nil
//}
internal-rpc
rpc - application_console.go
package rpc
import (
"context"
"gitlab.ftsview.com/aircraft/schedule-console/internal/handler/bean"
"gitlab.ftsview.com/fotoable-go/gerrors"
)
import (
. "gitlab.ftsview.com/micro/application"
)
func FilterFBAccountID(ctx context.Context, accountIDs ...string) ([]bean.AccountID, error) {
//filter := map[string]interface{}{"is_used_cruiser": true}
//if len(accountIDs) > 0 {
// filter["account_id"] = accountIDs
//}
data, err := ApplicationAdapter.FilterFBAccountID(ctx, &FilterFBAccountIDReq{
Id: accountIDs,
IsEnableCruiser: true,
})
if err != nil {
return nil, gerrors.Wrap(err, "rpc FilterFBAccountID err")
}
var Ids []bean.AccountID
for _, it := range data.Id {
id := bean.AccountID{AccountId: it}
Ids = append(Ids, id)
}
return Ids, nil
}
rpc - client.go
package rpc
import (
"fmt"
"strings"
"gitlab.ftsview.com/aircraft/schedule-console/internal/constants"
"gitlab.ftsview.com/micro/application"
)
func MustInitRpc(endpoint []string) {
addr := fmt.Sprintf("etcd://%s", strings.Join(endpoint, constants.Comma))
application.ApplicationAdapterInit(addr)
}
internal-service
service-rambler.go()
package service
import (
"context"
"gitlab.ftsview.com/aircraft/schedule-console/internal/handler/bean"
"gitlab.ftsview.com/aircraft/schedule-console/internal/rpc"
"sync"
)
var (
_ramblerOnce sync.Once
_ramblerService *RamblerService
)
type RamblerService struct {
}
func SingletonRamblerService() *RamblerService {
_ramblerOnce.Do(func() {
_ramblerService = &RamblerService{}
})
return _ramblerService
}
func (l *RamblerService) GetFBCruiserAccount(ctx context.Context, accountIDs ...string) []bean.AccountID {
//filter := map[string]interface{}{"is_used_cruiser": true}
//if len(accountIDs) > 0 {
// filter["account_id"] = accountIDs
//}
res, err := rpc.FilterFBAccountID(ctx, accountIDs...)
if err != nil {
return nil
}
return res
}
service-fb_campagin.go
package service
import (
"context"
"fmt"
"github.com/spf13/cast"
"gitlab.ftsview.com/aircraft/schedule-console/internal/config"
"gitlab.ftsview.com/aircraft/schedule-console/internal/constants"
"gitlab.ftsview.com/aircraft/schedule-console/internal/handler/bean"
"gitlab.ftsview.com/aircraft/schedule-console/internal/store/model"
"gitlab.ftsview.com/aircraft/schedule-console/internal/store/query"
"gitlab.ftsview.com/fotoable-go/glog"
"gitlab.ftsview.com/fotoable-go/gmysql"
"gitlab.ftsview.com/fotoable-go/gredis"
"gitlab.ftsview.com/fotoable-go/gutil"
"gopkg.in/resty.v1"
"strconv"
"sync"
"time"
)
var (
_fbCampaginOnce sync.Once
_fbCampaginService *FbCampaginService
)
type FbCampaginService struct {
}
func SingletonFbCampaginService() *FbCampaginService {
_fbCampaginOnce.Do(func() {
_fbCampaginService = &FbCampaginService{}
})
return _fbCampaginService
}
type FBCursor struct {
Before string `json:"before"`
After string `json:"after"`
}
type FBPaging struct {
Cursors FBCursor `json:"cursors"`
Next string `json:"next"`
}
// FBCampaignResult FaceBook獲取的原始Campagin
type FBCampaignResult struct {
Data []bean.FBCampagin `json:"data"`
Paging FBPaging `json:"paging"`
}
// FbCampaignResult 處理後返回的Campagin
type FbCampaignResult struct {
Data []bean.FbCampagin `json:"data"`
Paging FBPaging `json:"paging"`
}
func (f *FbCampaginService) GetMaxUpdateTime(ctx context.Context, accountId string) int64 {
maxTime, err := gredis.Redis(constants.RedisName).HGet(ctx, constants.CampaignMaxUpdateTime, accountId)
if err != nil {
glog.Error(ctx, "GetMaxUpdateTime error: %s ", err.Error())
return 0
}
return cast.ToInt64(maxTime)
}
//GetCampaignsByAccount 獲取廣告系列篩選項
func (f *FbCampaginService) GetCampaignsByAccount(ctx context.Context, accountId, url string, retry int,
accCps []bean.FbCampagin, timestamp int64) []bean.FbCampagin {
if url == constants.EmptyString {
filter := []bean.FBFilterParams{{
Field: "campaign.delivery_status",
Operator: "IN",
Value: []string{"active", "deleted", "archived", "inactive", "off", "pending"}}}
url = fmt.Sprintf(`%sact_%s/campaigns/?fields=%s&access_token=%s`,
config.GlobConfig.FbApi.BaseUrl, accountId, constants.FBCampaignFields, config.GlobConfig.FbApi.AccessToken)
if timestamp != constants.EmptyInt {
filter = append(filter, bean.FBFilterParams{
Field: "updated_time",
Operator: "GREATER_THAN",
Value: strconv.FormatInt(timestamp, 10)})
}
url = fmt.Sprintf(`%s&filtering=%s`, url, gutil.Object2JSON(filter))
}
resp, err := resty.New().R().Get(url)
// 請求重試機制 重試3次
if err != nil {
if retry >= constants.RetryNum {
glog.Error(ctx, "GetCampaignsByAccount error: ", err)
} else {
retry++
f.GetCampaignsByAccount(ctx, accountId, constants.EmptyString, retry, accCps, timestamp)
}
}
glog.Info(ctx, "GetCampaignsByAccount: success ", accountId)
var result FbCampaignResult
var faceBookCampagin FBCampaignResult
if err := gutil.JSON2ObjectE(resp.Body(), &faceBookCampagin); err != nil {
glog.Errorf(ctx, "GetCampaignsByAccount: json to object error.accountID: %s, error: %s",
accountId, err.Error())
}
result.Paging = faceBookCampagin.Paging
for _, datum := range faceBookCampagin.Data {
var data bean.FbCampagin
// 更新時間
if datum.UpdatedTime == constants.EmptyString {
data.UpdatedAt = 0
} else {
updatedAt, err := time.Parse(constants.DateLayout, datum.UpdatedTime)
if err != nil {
return nil
}
data.UpdatedAt = updatedAt.Unix()
}
// 創建時間
if datum.CreatedTime == constants.EmptyString {
data.CreatedAt = 0
} else {
createAt, err := time.Parse(constants.DateLayout, datum.CreatedTime)
if err != nil {
return nil
}
data.UpdatedAt = createAt.Unix()
}
// data
faceBookCampaginData, err := gutil.Object2JSONE(&datum)
if err != nil {
return nil
}
data.Data = faceBookCampaginData
// 主要字段
data.CampaignID = datum.CampaignID
data.AccountID = datum.AccountID
//data.UpdatedAt = updatedAt.Unix()
data.Name = datum.Name
//data.CreatedAt = createdAt.Unix()
data.BidStrategy = datum.BidStrategy
data.DailyBudget = datum.DailyBudget
data.EffectiveStatus = datum.EffectiveStatus
data.LifetimeBudget = datum.LifetimeBudget
data.SmartPromotionType = datum.SmartPromotionType
result.Data = append(result.Data, data)
}
accCps = append(accCps, result.Data...)
if result.Paging.Next != constants.EmptyString {
accCps = f.GetCampaignsByAccount(ctx, accountId, result.Paging.Next, 0, accCps, timestamp)
}
return accCps
}
// InsertCampaignsByAccount 廣告 系列入庫
func (l *FbCampaginService) InsertCampaignsByAccount(ctx context.Context, accCps []bean.FbCampagin, accountID string, types bool) {
db := gmysql.DB(ctx, config.GlobConfig.Mysql.DBName)
campaginTable := query.Use(db).FbCampaign
//FB返回的結果集是按照降序排列的,獲取本次操作的最大更新時間
if len(accCps) == 0 {
return
}
if types { // 增量更新 || 全部新增
for _, i2 := range accCps {
var accCampagin model.FbCampaign
accCampagin.CampaignID = i2.CampaignID
accCampagin.AccountID = i2.AccountID
accCampagin.UpdatedAt = i2.UpdatedAt
accCampagin.Name = i2.Name
accCampagin.CreatedAt = i2.CreatedAt
accCampagin.BidStrategy = i2.BidStrategy
accCampagin.DailyBudget = i2.DailyBudget
accCampagin.EffectiveStatus = i2.EffectiveStatus
accCampagin.LifetimeBudget = i2.LifetimeBudget
accCampagin.SmartPromotionType = i2.SmartPromotionType
accCampagin.Data = i2.Data
err := campaginTable.WithContext(ctx).Save(&accCampagin)
if err != nil {
glog.Error(ctx, "增量更新 || 全部新增 campaigns error: %s", err.Error())
return
}
}
}
//全量更新 ,暫不Redis 設置時間戳,因爲 facebopl update_time,預算更改,不會更新update_time字段問題,所以全量拉取
//maxTime, err := time.Parse(constants.DateTimeLayout, accCps[0].UpdatedAt)
//if err != nil {
// gutil.DingTalkAlarm(constants.ServiceName, fmt.Sprintf("MaxTime error, %s", accCps[0].UpdatedAt))
// glog.Error(ctx, "MaxTime error: %s", err.Error())
// return
//}
if err := gredis.Redis(constants.RedisName).HSet(
ctx, constants.CampaignMaxUpdateTime, accountID, cast.ToString(accCps[0].UpdatedAt)); err != nil {
glog.Error(ctx, "set max time to redis error: %s", err.Error())
return
}
}
service-fb_ad_set.go
package service
import (
"context"
"fmt"
"github.com/spf13/cast"
"gitlab.ftsview.com/aircraft/schedule-console/internal/config"
"gitlab.ftsview.com/aircraft/schedule-console/internal/constants"
"gitlab.ftsview.com/aircraft/schedule-console/internal/handler/bean"
"gitlab.ftsview.com/aircraft/schedule-console/internal/store/model"
"gitlab.ftsview.com/aircraft/schedule-console/internal/store/query"
"gitlab.ftsview.com/fotoable-go/glog"
"gitlab.ftsview.com/fotoable-go/gmysql"
"gitlab.ftsview.com/fotoable-go/gredis"
"gitlab.ftsview.com/fotoable-go/gutil"
"gopkg.in/resty.v1"
"strconv"
"sync"
"time"
)
var (
_fbAdSetOnce sync.Once
_fbAdSetService *FbAdSetService
)
type FbAdSetService struct {
}
func SingletonFbAdSetService() *FbAdSetService {
_fbAdSetOnce.Do(func() {
_fbAdSetService = &FbAdSetService{}
})
return _fbAdSetService
}
// FBAdsetResult FaceBook獲取的原始廣告組Adset
type FBAdsetResult struct {
Data []bean.FBAdSet `json:"data"`
Paging FBPaging `json:"paging"`
}
// FbAdsetResult 處理後返回的廣告組Adset
type FbAdsetResult struct {
Data []bean.FbAdset `json:"data"`
Paging FBPaging `json:"paging"`
}
//GetMaxUpdateTime 獲取廣告組最新的數據更新時間
func (f *FbAdSetService) GetMaxUpdateTime(ctx context.Context, accountId string) int64 {
maxTime, err := gredis.Redis(constants.RedisName).HGet(ctx, constants.AdSetMaxUpdateTime, accountId)
if err != nil {
glog.Error(ctx, "GetMaxUpdateTime error: %s ", err.Error())
return 0
}
return cast.ToInt64(maxTime)
}
//GetFBAdSetList 獲取fb後臺的廣告組列表
func (f *FbAdSetService) GetFBAdSetList(ctx context.Context, accountId, url string, retry int, accCps []bean.FbAdset,
timestamp int64) []bean.FbAdset {
if url == constants.EmptyString {
filter := []bean.FBFilterParams{{
Field: "adset.delivery_status",
Operator: "IN",
Value: []string{"active", "deleted", "archived", "inactive", "off", "pending"}}}
url = fmt.Sprintf(`%sact_%s/adsets/?fields=%s&access_token=%s`, config.GlobConfig.FbApi.BaseUrl,
accountId, constants.FBAdSetFields, config.GlobConfig.FbApi.AccessToken)
if timestamp != constants.EmptyInt {
filter = append(filter, bean.FBFilterParams{
Field: "updated_time",
Operator: "GREATER_THAN",
Value: strconv.FormatInt(timestamp, 10)})
}
url = fmt.Sprintf(`%s&filtering=%s`, url, gutil.Object2JSON(filter))
}
resp, err := resty.New().R().Get(url)
// 請求重試機制 重試3次
if err != nil {
if retry >= constants.RetryNum {
glog.Error(ctx, "GetAdSetsByAccount error: ", err)
} else {
retry++
f.GetFBAdSetList(ctx, accountId, constants.EmptyString, retry, accCps, timestamp)
}
}
glog.Info(ctx, "GetAdSetsByAccount: success ", accountId)
var (
result FbAdsetResult // 處理後返回的廣告組Adset
facebookAdSet FBAdsetResult // FaceBook獲取的原始廣告組Adset
)
if err := gutil.JSON2ObjectE(resp.Body(), &facebookAdSet); err != nil {
glog.Errorf(ctx, "GetCampaignsByAccount: json to object error.accountID: %s, error: %s",
accountId, err.Error())
}
result.Paging = facebookAdSet.Paging
for _, datum := range facebookAdSet.Data {
adSet := bean.FbAdset{
AdsetID: datum.AdsetID,
CampaignID: datum.CampaignID,
AccountID: datum.AccountID,
Name: datum.Name,
EffectiveStatus: datum.EffectiveStatus,
BidStrategy: datum.BidStrategy,
DailyBudget: datum.DailyBudget,
LifetimeBudget: datum.LifetimeBudget,
//AttributionSpec: datum.AttributionSpec,
OptimizationGoal: datum.OptimizationGoal,
//CreatedAt: 0,
//UpdatedAt: 0
//Data: "",
}
// 更新時間
if datum.UpdatedTime == constants.EmptyString {
adSet.UpdatedAt = 0
} else {
updatedAt, err := time.Parse(constants.DateLayout, datum.UpdatedTime)
if err != nil {
return nil
}
adSet.UpdatedAt = updatedAt.Unix()
}
// 創建時間
if datum.CreatedTime == constants.EmptyString {
adSet.CreatedAt = 0
} else {
createAt, err := time.Parse(constants.DateLayout, datum.CreatedTime)
if err != nil {
return nil
}
adSet.UpdatedAt = createAt.Unix()
}
// AttributionSpec
attributionSpec, err := gutil.Object2JSONE(&datum.AttributionSpec)
if err != nil {
return nil
}
adSet.AttributionSpec = attributionSpec
// Data
adSetData, err := gutil.Object2JSONE(&datum)
if err != nil {
return nil
}
adSet.Data = adSetData
result.Data = append(result.Data, adSet)
}
accCps = append(accCps, result.Data...)
if result.Paging.Next != constants.EmptyString {
accCps = f.GetFBAdSetList(ctx, accountId, result.Paging.Next, constants.RetryInit, accCps, timestamp)
}
return accCps
}
// InsertAdSetByAccount 廣告組信息入庫
func (f *FbAdSetService) InsertAdSetByAccount(ctx context.Context, accCps []bean.FbAdset, accountID string, types bool) {
db := gmysql.DB(ctx, config.GlobConfig.Mysql.DBName)
adSetTable := query.Use(db).FbAdSet
//FB返回的結果集是按照降序排列的,獲取本次操作的最大更新時間
if len(accCps) == 0 {
return
}
if types { // 增量更新 || 全部新增
for _, cp := range accCps {
accAdSet := model.FbAdSet{
AdsetID: cp.AdsetID,
CampaignID: cp.CampaignID,
AccountID: cp.AccountID,
Name: cp.Name,
EffectiveStatus: cp.EffectiveStatus,
BidStrategy: cp.BidStrategy,
DailyBudget: cp.DailyBudget,
LifetimeBudget: cp.LifetimeBudget,
AttributionSpec: cp.AttributionSpec,
OptimizationGoal: cp.OptimizationGoal,
CreatedAt: cp.CreatedAt,
UpdatedAt: cp.UpdatedAt,
Data: cp.Data,
}
err := adSetTable.WithContext(ctx).Save(&accAdSet)
if err != nil {
glog.Error(ctx, "增量更新 || 全部新增 adSet error: %s", err.Error())
return
}
}
}
// 全量更新 ,暫不Redis 設置時間戳,因爲 facebopl update_time,預算更改,不會更新update_time字段問題,所以全量拉取
//maxTime, err := time.Parse(constants.DateTimeLayout, accCps[0].UpdatedAt)
//if err != nil {
// gutil.DingTalkAlarm(constants.ServiceName, fmt.Sprintf("adset MaxTime error, %s adsetID: %s",
// accCps[0].UpdatedAt,
// accCps[0].AdsetID))
// glog.Error(ctx, "adset MaxTime error: %s", err.Error())
// return
//}
if err := gredis.Redis(constants.RedisName).HSet(
ctx, constants.AdSetMaxUpdateTime, accountID, cast.ToString(accCps[0].UpdatedAt)); err != nil {
glog.Error(ctx, "adset set max time to redis error: %s", err.Error())
return
}
}
service-fb_ad.go
package service
import (
"context"
"fmt"
"github.com/spf13/cast"
"gitlab.ftsview.com/aircraft/schedule-console/internal/config"
"gitlab.ftsview.com/aircraft/schedule-console/internal/constants"
"gitlab.ftsview.com/aircraft/schedule-console/internal/handler/bean"
"gitlab.ftsview.com/aircraft/schedule-console/internal/store/model"
"gitlab.ftsview.com/aircraft/schedule-console/internal/store/query"
"gitlab.ftsview.com/fotoable-go/glog"
"gitlab.ftsview.com/fotoable-go/gmysql"
"gitlab.ftsview.com/fotoable-go/gredis"
"gitlab.ftsview.com/fotoable-go/gutil"
"gopkg.in/resty.v1"
"strconv"
"sync"
"time"
)
var (
_fbAdOnce sync.Once
_fbAdService *FbAdService
)
type FbAdService struct {
}
func SingletonFbAdService() *FbAdService {
_fbAdOnce.Do(func() {
_fbAdService = &FbAdService{}
})
return _fbAdService
}
// FBAdResult FaceBook獲取的原始廣告Ad
type FBAdResult struct {
Data []bean.FBAd `json:"data"`
Paging FBPaging `json:"paging"`
}
// FbAdResult 處理後返回的廣告Ad
type FbAdResult struct {
Data []bean.FbAd `json:"data"`
Paging FBPaging `json:"paging"`
}
//GetMaxUpdateTime 獲取廣告新的數據更新時間
func (f *FbAdService) GetMaxUpdateTime(ctx context.Context, accountId string) int64 {
maxTime, err := gredis.Redis(constants.RedisName).HGet(ctx, constants.AdMaxUpdateTime, accountId)
if err != nil {
glog.Error(ctx, "GetMaxUpdateTime error: %s ", err.Error())
return 0
}
return cast.ToInt64(maxTime)
}
//GetFBAdList 獲取fb的廣告數據
func (f *FbAdService) GetFBAdList(ctx context.Context, accountId, url string, retry int, accCps []bean.FbAd,
timestamp int64) []bean.FbAd {
if url == constants.EmptyString {
filter := []bean.FBFilterParams{{
Field: "ad.delivery_status",
Operator: "IN",
Value: []string{"active", "deleted", "archived", "inactive", "off", "pending"}}}
url = fmt.Sprintf(`%sact_%s/ads/?fields=%s&access_token=%s`, config.GlobConfig.FbApi.BaseUrl,
accountId, constants.FBAdFields, config.GlobConfig.FbApi.AccessToken)
// 增量拉取數據
if timestamp != constants.EmptyInt {
filter = append(filter, bean.FBFilterParams{
Field: "updated_time",
Operator: "GREATER_THAN",
Value: strconv.FormatInt(timestamp, 10)})
}
url = fmt.Sprintf(`%s&filtering=%s`, url, gutil.Object2JSON(filter))
}
resp, err := resty.New().R().Get(url)
glog.Info(ctx, url)
// 請求重試機制 重試3次
if err != nil {
if retry >= constants.RetryNum {
glog.Error(ctx, "GetAdsByAccount error: ", err)
} else {
retry++
f.GetFBAdList(ctx, accountId, constants.EmptyString, retry, accCps, timestamp)
}
}
glog.Info(ctx, "GetAdsByAccount: success ", accountId)
var (
result FbAdResult // 處理後返回的廣告Ad
facebookAd FBAdResult // FaceBook獲取的原始廣告Ad
)
if err = gutil.JSON2ObjectE(resp.Body(), &facebookAd); err != nil {
glog.Errorf(ctx, "GetAdsByAccount: json to object error.accountID: %s, error: %s",
accountId, err.Error())
}
result.Paging = facebookAd.Paging
for _, datum := range facebookAd.Data {
ad := bean.FbAd{
AdID: datum.AdID,
AdsetID: datum.AdsetID,
CampaignID: datum.CampaignID,
CreativeID: datum.Creative["id"],
Name: datum.Name,
EffectiveStatus: datum.EffectiveStatus,
//CreatedAt: 0,
//UpdatedAt: 0,
//Data: "",
}
// 更新時間
if datum.UpdatedTime == constants.EmptyString {
ad.UpdatedAt = 0
} else {
updatedAt, err := time.Parse(constants.DateLayout, datum.UpdatedTime)
if err != nil {
return nil
}
ad.UpdatedAt = updatedAt.Unix()
}
// 創建時間
if datum.CreatedTime == constants.EmptyString {
ad.CreatedAt = 0
} else {
createAt, err := time.Parse(constants.DateLayout, datum.CreatedTime)
if err != nil {
return nil
}
ad.UpdatedAt = createAt.Unix()
}
// Data
adData, err := gutil.Object2JSONE(&datum)
if err != nil {
return nil
}
ad.Data = adData
result.Data = append(result.Data, ad)
}
accCps = append(accCps, result.Data...)
if result.Paging.Next != constants.EmptyString {
accCps = f.GetFBAdList(ctx, accountId, result.Paging.Next, constants.RetryInit, accCps, timestamp)
}
return accCps
}
//InsertAdByAccount 入庫廣告數據
func (f *FbAdService) InsertAdByAccount(ctx context.Context, accCps []bean.FbAd, accountID string, types bool) {
db := gmysql.DB(ctx, config.GlobConfig.Mysql.DBName)
adTable := query.Use(db).FbAd
//FB返回的結果集是按照降序排列的,獲取本次操作的最大更新時間
if len(accCps) == 0 {
return
}
if types { // 增量更新
for _, cp := range accCps {
accAd := model.FbAd{
AdID: cp.AdID,
AdsetID: cp.AdsetID,
CampaignID: cp.CampaignID,
CreativeID: cp.CreativeID,
Name: cp.Name,
EffectiveStatus: cp.EffectiveStatus,
CreatedAt: cp.CreatedAt,
UpdatedAt: cp.UpdatedAt,
Data: cp.Data,
}
err := adTable.WithContext(ctx).Save(&accAd)
if err != nil {
glog.Error(ctx, "增量更新 || 全部新增 ad error: %s", err.Error())
return
}
}
}
//默認情況FB返回的爲降序
//maxTime, err := time.Parse(constants.DateTimeLayout, accCps[0].UpdatedAt)
//if err != nil {
// gutil.DingTalkAlarm(constants.ServiceName, fmt.Sprintf("MaxTime error, %s", accCps[0].UpdatedAt))
// glog.Error(ctx, "MaxTime error: %s", err.Error())
// return
//}
//插入BI庫
//f.SyncMaterial(ctx, accCps, accountID)
glog.Infof(ctx, "accountID max update_time. %s", cast.ToString(accCps[0].UpdatedAt))
if err := gredis.Redis(constants.RedisName).HSet(
ctx, constants.AdMaxUpdateTime, accountID, cast.ToString(accCps[0].UpdatedAt)); err != nil {
glog.Error(ctx, "ad set max time to redis error: %s", err.Error())
return
}
}
internal - handler
bean - common.go
package bean
type BaseRequest struct {
CompanyID int `json:"company_id"`
AppID interface{} `json:"app_id"`
GameID interface{} `json:"game_id"`
}
type BasePageRequest struct {
Page int64 `json:"page"`
Size int64 `json:"size"`
}
type AccountID struct {
AccountId string `json:"id"`
}
bean - fb_ad.go
package bean
// FbAd mapped from table <fb_ad>
type FbAd struct {
AdID string `gorm:"column:ad_id;primaryKey" json:"ad_id"` // 廣告ID
AdsetID string `gorm:"column:adset_id;not null" json:"adset_id"` // 廣告組ID
CampaignID string `gorm:"column:campaign_id;not null" json:"campaign_id"` // 廣告系列ID
CreativeID string `gorm:"column:creative_id;not null" json:"creative_id"` // 廣告創意ID
Name string `gorm:"column:name;not null" json:"name"` // 廣告名稱
EffectiveStatus string `gorm:"column:effective_status;not null" json:"effective_status"` // 廣告狀態
CreatedAt int64 `gorm:"column:created_at;autoUpdateTime" json:"created_at"` // 創建時間戳
UpdatedAt int64 `gorm:"column:updated_at;autoCreateTime" json:"updated_at"` // 更新時間戳
Data string `gorm:"column:data" json:"data"`
}
// FBAd FaceBook原始廣告數據
type (
FBAd struct {
// ID primitive.ObjectID `bson:"_id" json:"-"`
CompanyID int `bson:"company_id" json:"company_id,omitempty"`
AdID string `bson:"ad_id" json:"id,omitempty"`
AccountID string `bson:"account_id" json:"account_id,omitempty"`
AdReviewFeedback map[string]interface{} `bson:"ad_review_feedback" json:"ad_review_feedback,omitempty"` // 審閱後的審閱反饋
Adlabels []Adlabels `bson:"adlabels" json:"adlabels,omitempty"` // 與此活動關聯的廣告標籤
Adset map[string]interface{} `bson:"adset" json:"adset,omitempty"` // 包含此廣告的廣告組
AdsetID string `bson:"adset_id,omitempty" json:"adset_id,omitempty"` // 包含此廣告的光廣告組ID
BidAmount int32 `bson:"bid_amount,omitempty" json:"bid_amount,omitempty"` // 廣告的出價金額
Campaign map[string]interface{} `bson:"campaign" json:"campaign,omitempty"` //廣告的廣告系列
CampaignID string `bson:"campaign_id" json:"campaign_id,omitempty"`
ConfiguredStatus string `bson:"configured_status" json:"configured_status,omitempty"` // 廣告的配置狀態
ConversionDomain string `bson:"conversion_domain" json:"conversion_domain,omitempty"` // 發生轉換的域
Creative map[string]string `bson:"creative" json:"creative,omitempty"` // 廣告創意
EffectiveStatus string `bson:"effective_status" json:"effective_status,omitempty"` // ad的有效狀態
IssuesInfo []map[string]interface{} `bson:"issues_info,omitempty" json:"issues_info,omitempty"` // 廣告的問題使其無法交付
LastUpdatedByAppId string `bson:"last_updated_by_app_id" json:"last_updated_by_app_id,omitempty"` // 指示用於廣告最新更新的應用程序。
Name string `bson:"name" json:"name,omitempty"` // 廣告名稱
PreviewShareableLink string `bson:"preview_shareable_link" json:"preview_shareable_link,omitempty"` // 允許用戶在不同位置預覽廣告的鏈接
Recommendations []map[string]interface{} `bson:"recommendations" json:"recommendations,omitempty"` // 廣告的建議
SourceAd map[string]interface{} `bson:"source_ad" json:"source_ad,omitempty"` // 複製此廣告的源廣告
SourceAdId string `bson:"source_ad_id" json:"source_ad_id,omitempty"` // 複製此廣告的源廣告id
Status string `bson:"status,omitempty" json:"status,omitempty"` // 狀態
TrackingSpecs []map[string]interface{} `bson:"tracking_specs" json:"tracking_specs,omitempty"` // 跟蹤規範
CreatedTime string `bson:"created_time" json:"created_time,omitempty"`
UpdatedTime string `bson:"updated_time" json:"updated_time,omitempty"`
}
)
bean - fb_ad_set.go
package bean
// AdSetListRequest 廣告組請求參數
type AdSetListRequest struct {
BaseRequest
BasePageRequest
AccountID []string `json:"account_ids"`
CampaignID []string `json:"campaign_ids"`
Status []string `json:"status"`
AdSetID string `json:"adset_id"`
KeyWord string `json:"keyword"`
}
// FbAdset mapped from table <fb_adset>
type FbAdset struct {
AdsetID string `gorm:"column:adset_id;primaryKey" json:"adset_id"` // 廣告組ID
CampaignID string `gorm:"column:campaign_id;not null" json:"campaign_id"` // 廣告系列ID
AccountID string `gorm:"column:account_id;not null" json:"account_id"` // 賬號ID
Name string `gorm:"column:name;not null" json:"name"` // 廣告組名稱
EffectiveStatus string `gorm:"column:effective_status;not null" json:"effective_status"` // 廣告組狀態
BidStrategy string `gorm:"column:bid_strategy;not null" json:"bid_strategy"` // 廣告組策略
DailyBudget string `gorm:"column:daily_budget;not null" json:"daily_budget"` // 日預算
LifetimeBudget string `gorm:"column:lifetime_budget;not null" json:"lifetime_budget"` // 總預算
AttributionSpec string `gorm:"column:attribution_spec" json:"attribution_spec"` // 歸因設置
OptimizationGoal string `gorm:"column:optimization_goal;not null" json:"optimization_goal"` // 優化目標
CreatedAt int64 `gorm:"column:created_at;autoUpdateTime" json:"created_at"` // 創建時間戳
UpdatedAt int64 `gorm:"column:updated_at;autoCreateTime" json:"updated_at"` // 更新時間戳
Data string `gorm:"column:data" json:"data"`
}
// FBAdSet FaceBook原始廣告組數據
type (
FBAdSet struct {
// ID primitive.ObjectID `bson:"_id" json:"-"`
CompanyID int `bson:"company_id" json:"company_id,omitempty"`
CampaignID string `bson:"campaign_id" json:"campaign_id,omitempty"`
AdsetID string `bson:"adset_id" json:"id,omitempty"`
AccountID string `bson:"account_id" json:"account_id,omitempty"`
AdsetSchedule []map[string]interface{} `bson:"adset_schedule" json:"adset_schedule,omitempty"` // 廣告集計劃,表示一天的交貨時間表
AssetFeedId string `bson:"asset_feed_id" json:"asset_feed_id,omitempty"` // 包含內容以創建廣告的資產源的ID
Adlabels []Adlabels `bson:"adlabels" json:"adlabels,omitempty"` // 與此活動關聯的廣告標籤
AttributionSpec []SpecEvents `bson:"attribution_spec" json:"attribution_spec,omitempty"` // 轉化時間窗
BidAdjustments map[string]interface{} `bson:"bid_adjustments,omitempty" json:"bid_adjustments,omitempty"` // 投標調整類型到值的映射
BidAmount int32 `bson:"bid_amount,omitempty" json:"bid_amount,omitempty"` // 廣告組投放上線
BidConstraints map[string]interface{} `bson:"bid_constraints,omitempty" json:"bid_constraints,omitempty"` //廣告組投標限制條件
BidInfo map[string]interface{} `bson:"bid_info,omitempty" json:"bid_info,omitempty"` // 投標目標與投標價值的關係圖
BidStrategy string `bson:"bid_strategy" json:"bid_strategy,omitempty"` // 競價策略
BillingEvent string `bson:"billing_event" json:"billing_event,omitempty"` // 計費事件
BudgetRemaining string `bson:"budget_remaining" json:"budget_remaining,omitempty"` // 廣告組剩餘預算
Campaign map[string]interface{} `bson:"campaign" json:"campaign,omitempty"` // 廣告系列
ConfiguredStatus string `bson:"configured_status" json:"configured_status,omitempty"` // 廣告組級別的狀態
ContextualBundlingSpec map[string]interface{} `bson:"contextual_bundling_spec,omitempty" json:"contextual_bundling_spec,omitempty"`
CreativeSequence []interface{} `bson:"creative_sequence" json:"creative_sequence,omitempty"` // 向用戶顯示adgroup序列的順序
DailyBudget string `bson:"daily_budget" json:"daily_budget,omitempty"` // 競選活動的每日預算
DailyMinSpendTarget string `bson:"daily_min_spend_target" json:"daily_min_spend_target,omitempty"` // 每日最低支出目標
DailySpendCap string `bson:"daily_spend_cap" json:"daily_spend_cap,omitempty"` // 每日支出上限
DestinationType string `bson:"destination_type" json:"destination_type,omitempty"` // 廣告組的投放目標類型(平臺)
EffectiveStatus string `bson:"effective_status" json:"effective_status,omitempty"`
EndTime string `bson:"end_time" json:"end_time,omitempty"` // 結束時間
FrequencyControlSpecs []map[string]interface{} `bson:"frequency_control_specs" json:"frequency_control_specs,omitempty"` // 頻率控制規格數組
InstagramActorId string `bson:"instagram_actor_id" json:"instagram_actor_id,omitempty"` // Instagram帳戶id
IsDynamicCreative bool `bson:"is_dynamic_creative" json:"is_dynamic_creative,omitempty"` // 是否爲動態創意廣告組
IssuesInfo []map[string]interface{} `bson:"issues_info,omitempty" json:"issues_info,omitempty"` // 錯誤支付信息
LearningStageInfo map[string]interface{} `bson:"learning_stage_info" json:"learning_stage_info,omitempty"` // 有關排名或發佈系統是否仍在學習此廣告集的信息
LifetimeBudget string `bson:"lifetime_budget" json:"lifetime_budget,omitempty"` // 終身預算
LifetimeImps int32 `bson:"lifetime_imps" json:"lifetime_imps,omitempty"` // 終生印象 僅適用於具有buying_type=FIXED_CPM
LifetimeSpendCap string `bson:"lifetime_spend_cap" json:"lifetime_spend_cap,omitempty"` // 終身開支上限
LifetimeMinSpendTarget string `bson:"lifetime_min_spend_target" json:"lifetime_min_spend_target,omitempty"` // 終身最低花費目標
MultiOptimizationGoalWeight string `bson:"multi_optimization_goal_weight" json:"multi_optimization_goal_weight,omitempty"` // 多目標優化
Name string `bson:"name" json:"name,omitempty"` // 廣告組名稱
OptimizationGoal string `bson:"optimization_goal" json:"optimization_goal,omitempty"` // 優化方式
OptimizationSubEvent string `bson:"optimization_sub_event" json:"optimization_sub_event,omitempty"` // 優化子事件
PacingType []string `bson:"pacing_type,omitempty" json:"pacing_type,omitempty"` // 定義活動的步調類
PromotedObject map[string]interface{} `bson:"promoted_object,omitempty" json:"promoted_object,omitempty"` // 宣傳的對象
Recommendations []map[string]interface{} `bson:"recommendations,omitempty" json:"recommendations,omitempty"` // 活動的建議
RecurringBudgetSemantics bool `bson:"recurring_budget_semantics" json:"recurring_budget_semantics,omitempty"` // 如果這個字段是true,您的日常開支可能會超過您的日常預算,
ReviewFeedback string `bson:"review_feedback" json:"review_feedback,omitempty"` // 動態創意廣告評論
RfPredictionId string `bson:"rf_prediction_id,omitempty" json:"rf_prediction_id,omitempty"` // 範圍和頻率預測ID
SourceAdset map[string]interface{} `bson:"source_adset" json:"source_adset,omitempty"` // 從中複製此活動的源市場活動id
SourceAdsetId string `bson:"source_adset_id,omitempty" json:"source_adset_id,omitempty"` // 特別廣告類別
StartTime string `bson:"start_time" json:"start_time,omitempty"`
Status string `bson:"status,omitempty" json:"status,omitempty"` // 狀態
Targeting map[string]interface{} `bson:"targeting" json:"targeting,omitempty"` // 受衆定向
TargetingOptimizationTypes []map[string]interface{} `bson:"targeting_optimization_types,omitempty" json:"targeting_optimization_types,omitempty"` // 將放鬆的選項作爲優化的信號
TimeBasedAdRotationIdBlocks []map[string]interface{} `bson:"time_based_ad_rotation_id_blocks,omitempty" json:"time_based_ad_rotation_id_blocks,omitempty"` // 廣告組ID的列表
TimeBasedAdRotationIntervals []map[string]interface{} `bson:"time_based_ad_rotation_intervals,omitempty" json:"time_based_ad_rotation_intervals,omitempty"` // 特定廣告創意在活動期間顯示的日期範圍
UseNewAppClick bool `bson:"use_new_app_click,omitempty" json:"use_new_app_click,omitempty"` // 頂線ID
CreatedTime string `bson:"created_time" json:"created_time,omitempty"`
UpdatedTime string `bson:"updated_time" json:"updated_time,omitempty"`
}
SpecEvents struct {
EventType string `json:"event_type" bson:"event_type"`
WindowDays int `json:"window_days" bson:"window_days"`
}
)
bean - fb_campagin.go
package bean
import gginutil "gitlab.ftsview.com/fotoable-go/ggin-util"
// CampaignListRequest 廣告系列列表請求參數
type CampaignListRequest struct {
BaseRequest
BasePageRequest
Header gginutil.HeaderToB `json:"-"`
AccountID []string `json:"account_ids"`
CampaignID string `json:"campaign_id"`
Objective string `json:"objective"`
SmartPromotionType string `json:"smart_promotion_type"`
Status []string `json:"status"`
KeyWord string `json:"keyword"`
Refresh int `json:"refresh"`
}
type UpdateResult struct {
Campagins int64 `json:"campagins"`
AdSets int64 `json:"adSets"`
Ads int64 `json:"ads"`
}
type FbCampagin struct {
CampaignID string `gorm:"column:campaign_id;primaryKey" json:"campaign_id"` // 系列ID
AccountID string `gorm:"column:account_id;not null" json:"account_id"` // 賬號ID
Name string `gorm:"column:name;not null" json:"name"` // 系列名稱
EffectiveStatus string `gorm:"column:effective_status;not null" json:"effective_status"` // 狀態
SmartPromotionType string `gorm:"column:smart_promotion_type;not null" json:"smart_promotion_type"` // 廣告系列類型
BidStrategy string `gorm:"column:bid_strategy;not null" json:"bid_strategy"` // 廣告系列策略
DailyBudget string `gorm:"column:daily_budget;not null" json:"daily_budget"` // 日預算
LifetimeBudget string `gorm:"column:lifetime_budget;not null" json:"lifetime_budget"` // 總預算
CreatedAt int64 `gorm:"column:created_at;autoUpdateTime" json:"created_at"` // 創建時間戳
UpdatedAt int64 `gorm:"column:updated_at;autoCreateTime" json:"updated_at"` // 更新時間戳
Data string `gorm:"column:data" json:"data"`
}
type FBFilterParams struct {
Field string `json:"field"`
Operator string `json:"operator"`
Value interface{} `json:"value"`
}
// FaceBook原始Campagin數據
type (
FBCampagin struct {
// ID primitive.ObjectID `bson:"_id" json:"-"`
CompanyID int `bson:"company_id" json:"company_id,omitempty"`
CampaignID string `bson:"campaign_id" json:"id,omitempty"`
AccountID string `bson:"account_id" json:"account_id,omitempty"`
AdStrategyGroupId string `bson:"ad_strategy_group_id" json:"ad_strategy_group_id,omitempty"` // 廣告策略組ID
AdStrategyId string `bson:"ad_strategy_id" json:"ad_strategy_id,omitempty"` // 廣告策略ID
Adlabels []Adlabels `bson:"adlabels" json:"adlabels,omitempty"` // 與此活動關聯的廣告標籤
BidStrategy string `bson:"bid_strategy" json:"bid_strategy,omitempty"` // 競價策略
BoostedObjectId string `bson:"boosted_object_id,omitempty" json:"boosted_object_id,omitempty"` // 此活動關聯的增強對象
BrandLiftStudies []map[string]interface{} `bson:"brand_lift_studies,omitempty" json:"brand_lift_studies,omitempty"`
BudgetRebalanceFlag bool `bson:"budget_rebalance_flag" json:"budget_rebalance_flag"`
BudgetRemaining string `bson:"budget_remaining" json:"budget_remaining,omitempty"` //剩餘預算
BuyingType string `bson:"buying_type" json:"buying_type,omitempty"` // 購買類型
CanCreateBrandLiftStudy bool `bson:"can_create_brand_lift_study" json:"can_create_brand_lift_study,omitempty"` // 是否提升研究
CanUseSpendCap bool `bson:"can_use_spend_cap" json:"can_use_spend_cap,omitempty"` // 競選活動能否設定開支上限
ConfiguredStatus string `bson:"configured_status" json:"configured_status"`
CreatedTime string `bson:"created_time" json:"created_time,omitempty"`
DailyBudget string `bson:"daily_budget" json:"daily_budget,omitempty"` // 競選活動的每日預算
EffectiveStatus string `bson:"effective_status" json:"effective_status,omitempty"`
IsSkadnetworkAttribution bool `bson:"is_skadnetwork_attribution" json:"is_skadnetwork_attribution,omitempty"` // 設置爲時true表示活動將包括SKAdNetwork,iOS 14。
IssuesInfo []map[string]interface{} `bson:"issues_info,omitempty" json:"issues_info,omitempty"` // 錯誤支付信息
LastBudgetTogglingTime string `bson:"last_budget_toggling_time" json:"last_budget_toggling_time,omitempty"` // 上次預算切換時間
LifetimeBudget string `bson:"lifetime_budget" json:"lifetime_budget,omitempty"` // 競選的終身預算
Name string `bson:"name" json:"name,omitempty"` // 廣告系列名稱
Objective string `bson:"objective" json:"objective,omitempty"` // 活動目標
PacingType []string `bson:"pacing_type,omitempty" json:"pacing_type,omitempty"` // 定義活動的步調類
PromotedObject map[string]interface{} `bson:"promoted_object,omitempty" json:"promoted_object,omitempty"` // 宣傳的對象
Recommendations []map[string]interface{} `bson:"recommendations,omitempty" json:"recommendations,omitempty"` // 活動的建議
SmartPromotionType string `bson:"smart_promotion_type,omitempty" json:"smart_promotion_type,omitempty"` // 智能促銷類型
SourceCampaign map[string]interface{} `bson:"source_campaign,omitempty" json:"source_campaign,omitempty"`
SourceCampaignId string `bson:"source_campaign_id" json:"source_campaign_id,omitempty"` // 從中複製此活動的源市場活動id
SpecialAdCategories []string `bson:"special_ad_categories,omitempty" json:"special_ad_categories,omitempty"` // 特別廣告類別
SpecialAdCategory string `bson:"special_ad_category,omitempty" json:"special_ad_category,omitempty"` // 特別廣告類別
SpecialAdCategoryCountry []string `bson:"special_ad_category_country,omitempty" json:"special_ad_category_country,omitempty"` // 特殊廣告類別的國家/地區字段。
SpendCap string `bson:"spend_cap,omitempty" json:"spend_cap,omitempty"` // 開支上限
StartTime string `bson:"start_time" json:"start_time,omitempty"`
Status string `bson:"status,omitempty" json:"status,omitempty"` // 狀態
StopTime string `bson:"stop_time" json:"stop_time,omitempty"`
ToplineId string `bson:"topline_id,omitempty" json:"topline_id,omitempty"` // 頂線ID
UpdatedTime string `bson:"updated_time" json:"updated_time,omitempty"`
}
Adlabels struct {
ID string `bson:"id" json:"id"`
Name string `bson:"name" json:"name"`
CreatedTime string `bson:"created_time" json:"created_time"`
UpdatedTime string `bson:"updated_time" json:"updated_time"`
}
)