定时任务抽取数据

This commit is contained in:
2026-04-07 09:51:32 +08:00
parent 41089cca2d
commit 4f3ad39eeb
12 changed files with 1684 additions and 2 deletions

1
go.mod
View File

@@ -8,6 +8,7 @@ require (
github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.5 github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.5
github.com/gogf/gf/v2 v2.10.0 github.com/gogf/gf/v2 v2.10.0
github.com/olekukonko/errors v1.1.0 github.com/olekukonko/errors v1.1.0
github.com/sirupsen/logrus v1.9.3
golang.org/x/net v0.47.0 golang.org/x/net v0.47.0
) )

View File

@@ -0,0 +1,39 @@
package main
import (
"fmt"
"time"
"cid/sync"
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
"github.com/gogf/gf/v2/os/gctx"
"github.com/sirupsen/logrus"
)
func main() {
ctx := gctx.New()
syncService := sync.NewSyncService()
req := &sync.CampaignReportRequest{
AdvertiserID: 10001,
StartTime: time.Now().AddDate(0, 0, -30).UnixNano() / 1e6,
EndTime: time.Now().UnixNano() / 1e6,
SelectColumns: []string{"impression", "click", "cost", "t0GMV"},
GroupType: 1,
QueryVersion: 1,
}
logrus.Info("=== 开始执行定时同步任务 ===")
result, err := syncService.SyncCampaignReportWithPagination(ctx, req, true, 3)
if err != nil {
logrus.Errorf("定时同步任务失败:%v", err)
return
}
fmt.Printf("✓ 定时同步完成:\n")
fmt.Printf(" 汇总数据:成功=%v, ID=%d\n", result.SumSuccess, result.SumID)
fmt.Printf(" 明细数据:总数=%d, 成功=%d, 失败=%d\n",
result.DetailCount, result.DetailSuccessCount, result.DetailFailCount)
}

View File

@@ -68,7 +68,7 @@ func (s *cidAccountReportDetailService) BatchCreate(ctx context.Context, req *dt
return return
} }
// Create 创建广告数据报表汇总 // CreateSum Create 创建广告数据报表汇总
func (s *cidAccountReportDetailService) CreateSum(ctx context.Context, req *dto.CidAccountReportSumItem) (res *dto.CreateCidAccountReportSumRes, err error) { func (s *cidAccountReportDetailService) CreateSum(ctx context.Context, req *dto.CidAccountReportSumItem) (res *dto.CreateCidAccountReportSumRes, err error) {
// 验证必要字段 // 验证必要字段
if req.DataType == "" { if req.DataType == "" {
@@ -90,7 +90,7 @@ func (s *cidAccountReportDetailService) CreateSum(ctx context.Context, req *dto.
return return
} }
// BatchCreate 批量创建广告数据报表汇总 // BatchCreateSum 批量创建广告数据报表汇总
func (s *cidAccountReportDetailService) BatchCreateSum(ctx context.Context, req *dto.BatchCreateCidAccountReportSumReq) (res *dto.BatchCreateCidAccountReportSumRes, err error) { func (s *cidAccountReportDetailService) BatchCreateSum(ctx context.Context, req *dto.BatchCreateCidAccountReportSumReq) (res *dto.BatchCreateCidAccountReportSumRes, err error) {
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"}) ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
// 验证数据 // 验证数据

79
sync/base_report_sync.go Normal file
View File

@@ -0,0 +1,79 @@
package sync
import (
"context"
)
type ReportSyncable interface {
FetchReport(ctx context.Context, params interface{}) (interface{}, error)
ConvertToSum(apiData interface{}, dataType string) interface{}
ConvertToDetails(apiData interface{}, dataType string) []interface{}
SaveSum(ctx context.Context, data interface{}) (int64, error)
SaveDetails(ctx context.Context, data []interface{}) (successCount, failCount int64, err error)
}
type BaseReportSync struct {
httpClient *HttpClient
}
func NewBaseReportSync() *BaseReportSync {
return &BaseReportSync{
httpClient: NewHttpClient("", 0),
}
}
func (b *BaseReportSync) FetchReport(ctx context.Context, params interface{}) (interface{}, error) {
return nil, nil
}
func (b *BaseReportSync) ConvertToSum(apiData interface{}, dataType string) interface{} {
return nil
}
func (b *BaseReportSync) ConvertToDetails(apiData interface{}, dataType string) []interface{} {
return nil
}
func (b *BaseReportSync) SaveSum(ctx context.Context, data interface{}) (int64, error) {
return 0, nil
}
func (b *BaseReportSync) SaveDetails(ctx context.Context, data []interface{}) (int64, int64, error) {
return 0, 0, nil
}
func (b *BaseReportSync) ExecuteSync(ctx context.Context, syncer ReportSyncable, params interface{}, dataType string, useMock bool) (*SyncResult, error) {
result := &SyncResult{}
apiData, err := syncer.FetchReport(ctx, params)
if err != nil {
result.Error = err
return result, err
}
sumData := syncer.ConvertToSum(apiData, dataType)
if sumData != nil {
sumID, err := syncer.SaveSum(ctx, sumData)
if err != nil {
result.Error = err
return result, err
}
result.SumSuccess = true
result.SumID = sumID
}
detailData := syncer.ConvertToDetails(apiData, dataType)
if len(detailData) > 0 {
successCount, failCount, err := syncer.SaveDetails(ctx, detailData)
if err != nil {
result.Error = err
return result, err
}
result.DetailSuccess = true
result.DetailCount = len(detailData)
result.DetailSuccessCount = successCount
result.DetailFailCount = failCount
}
return result, nil
}

View File

@@ -0,0 +1,115 @@
package sync
import (
dto "cid/model/dto/copydata"
"cid/service/copydata"
"context"
"encoding/json"
"fmt"
"github.com/sirupsen/logrus"
)
type CampaignReportSync struct {
*BaseReportSync
converter *DataConverter
mockGen *MockDataGenerator
}
func NewCampaignReportSync() *CampaignReportSync {
return &CampaignReportSync{
BaseReportSync: NewBaseReportSync(),
converter: NewDataConverter(),
mockGen: NewMockDataGenerator(),
}
}
func (c *CampaignReportSync) FetchReport(ctx context.Context, params interface{}) (interface{}, error) {
req, ok := params.(*CampaignReportRequest)
if !ok {
return nil, fmt.Errorf("参数类型错误,期望 CampaignReportRequest 类型")
}
useMock := false
if useMock {
logrus.Info("使用 Mock 数据")
return c.mockGen.GenerateCampaignReportResponse(), nil
}
respBytes, err := NewHttpClient("https://ad.e.kuaishou.com", 0).Post(ctx, "/rest/openapi/gw/esp/report/campaignReport", req)
if err != nil {
return nil, fmt.Errorf("调用 API 失败:%w", err)
}
var response CampaignReportResponse
if err := json.Unmarshal(respBytes, &response); err != nil {
return nil, fmt.Errorf("解析响应失败:%w", err)
}
if response.Code != 0 {
return nil, fmt.Errorf("API 返回错误code=%d, message=%s", response.Code, response.Message)
}
return &response, nil
}
func (c *CampaignReportSync) ConvertToSum(apiData interface{}, dataType string) interface{} {
response, ok := apiData.(*CampaignReportResponse)
if !ok || response.Data == nil || response.Data.Sum == nil {
return nil
}
return c.converter.ConvertToSumItem(response.Data.Sum, dataType)
}
func (c *CampaignReportSync) ConvertToDetails(apiData interface{}, dataType string) []interface{} {
response, ok := apiData.(*CampaignReportResponse)
if !ok || response.Data == nil || len(response.Data.Detail) == 0 {
return nil
}
detailItems := c.converter.ConvertToDetailItems(response.Data.Detail, dataType)
result := make([]interface{}, len(detailItems))
for i, item := range detailItems {
result[i] = item
}
return result
}
func (c *CampaignReportSync) SaveSum(ctx context.Context, data interface{}) (int64, error) {
sumItem, ok := data.(*dto.CidAccountReportSumItem)
if !ok {
return 0, fmt.Errorf("数据类型错误,期望 CidAccountReportSumItem 类型")
}
res, err := copydata.CidAccountReportDetail.CreateSum(ctx, sumItem)
if err != nil {
return 0, err
}
return res.Id, nil
}
func (c *CampaignReportSync) SaveDetails(ctx context.Context, data []interface{}) (int64, int64, error) {
detailItems := make([]*dto.CidAccountReportDetailItem, len(data))
for i, item := range data {
detailItem, ok := item.(*dto.CidAccountReportDetailItem)
if !ok {
return 0, 0, fmt.Errorf("第 %d 条数据类型错误", i)
}
detailItems[i] = detailItem
}
req := &dto.BatchCreateCidAccountReportDetailReq{
Items: detailItems,
}
res, err := copydata.CidAccountReportDetail.BatchCreate(ctx, req)
if err != nil {
return 0, 0, err
}
return res.SuccessCount, res.FailCount, nil
}

View File

@@ -0,0 +1,235 @@
package sync
type CampaignReportRequest struct {
AdvertiserID int64 `json:"advertiser_id"`
StartTime int64 `json:"start_time"`
EndTime int64 `json:"end_time"`
SelectColumns []string `json:"select_columns"`
GroupType int `json:"group_type"`
QueryVersion int `json:"query_version"`
SelectParam *CampaignSelectParam `json:"select_param,omitempty"`
PageInfo *PageInfo `json:"page_info,omitempty"`
}
type CampaignSelectParam struct {
CampaignIDs []int64 `json:"campaign_ids,omitempty"`
AuthorID int64 `json:"author_id,omitempty"`
AdTypeStr string `json:"ad_type_str,omitempty"`
MarketingObjective int `json:"marketing_objective,omitempty"`
DeliveryScenario int `json:"delivery_scenario,omitempty"`
DeliveryMethod int `json:"delivery_method,omitempty"`
SupportType string `json:"support_type,omitempty"`
OcpcActionType string `json:"ocpc_action_type,omitempty"`
SpeedType string `json:"speed_type,omitempty"`
ItemType string `json:"item_type,omitempty"`
CreativeBuildType string `json:"creative_build_type,omitempty"`
AdScene string `json:"ad_scene,omitempty"`
IncrementExploreType []int `json:"increment_explore_type,omitempty"`
}
type PageInfo struct {
CurrentPage int `json:"current_page"`
PageSize int `json:"page_size"`
TotalCount int `json:"total_count"`
}
type CampaignReportResponse struct {
Code int `json:"code"`
Message string `json:"message"`
Data *CampaignReportData `json:"data"`
}
type CampaignReportData struct {
Sum *CampaignReportSum `json:"sum"`
Detail []*CampaignReportItem `json:"detail"`
TotalCount int `json:"total_count"`
}
type CampaignReportSum struct {
T0OrderPaymentAmt string `json:"t0_order_payment_amt"`
CreativeMaterialType string `json:"creative_material_type"`
LiveName string `json:"live_name"`
AuthorId string `json:"author_id"`
PicUrl string `json:"pic_url"`
PicName string `json:"pic_name"`
PicId string `json:"pic_id"`
CoverUrl string `json:"cover_url"`
CoverId int64 `json:"cover_id"`
ItemOrderConversionRatio *float64 `json:"item_order_conversion_ratio"`
ItemCardClickRatio *float64 `json:"item_card_click_ratio"`
ItemCardClkCnt *int64 `json:"item_card_clk_cnt"`
LivePlayCntCost *float64 `json:"live_play_cnt_cost"`
AdMerchantFollowCost *float64 `json:"ad_merchant_follow_cost"`
AdMerchantFollow *int64 `json:"ad_merchant_follow"`
NetT0OrderCnt *int64 `json:"net_t0_order_cnt"`
NetT0Roi *float64 `json:"net_t0_roi"`
NetT0Gmv *float64 `json:"net_t0_gmv"`
PhotoName string `json:"photo_name"`
PhotoIdStr string `json:"photo_id_str"`
PhotoId string `json:"photo_id"`
ModPriceSegment string `json:"mod_price_segment"`
AgeSegment string `json:"age_segment"`
Province string `json:"province"`
Gender string `json:"gender"`
AdPhotoPlayedFiveRatio *float64 `json:"ad_photo_played_five_ratio"`
AdPhotoPlayedThreeRatio *float64 `json:"ad_photo_played_three_ratio"`
OrderSubmitRoi *float64 `json:"order_submit_roi"`
OrderSubmitAmt *int64 `json:"order_submit_amt"`
EventOrderSubmitCost *float64 `json:"event_order_submit_cost"`
EventOrderSubmit *int64 `json:"event_order_submit"`
EventOrderPaidRoi *float64 `json:"event_order_paid_roi"`
EventAppInvoked *int64 `json:"event_app_invoked"`
EventAddShoppingCart *int64 `json:"event_add_shopping_cart"`
ConversionNumCost *float64 `json:"conversion_num_cost"`
AdEffectivePlayNum *int64 `json:"ad_effective_play_num"`
AdItemClick *int64 `json:"ad_item_click"`
MerchantProductId string `json:"merchant_product_id"`
CostTotal *float64 `json:"cost_total"`
AdShow *int64 `json:"ad_show"`
AdShow1kCost *float64 `json:"ad_show1k_cost"`
Impression *int64 `json:"impression"`
PhotoClick *int64 `json:"photo_click"`
PhotoClickRatio *float64 `json:"photo_click_ratio"`
Click *int64 `json:"click"`
ActionbarClick *int64 `json:"actionbar_click"`
ActionbarClickCost *float64 `json:"actionbar_click_cost"`
EspClickRatio *float64 `json:"esp_click_ratio"`
ActionRatio *float64 `json:"action_ratio"`
AdItemClickCount *int64 `json:"ad_item_click_count"`
EspLivePlayedSeconds *int64 `json:"esp_live_played_seconds"`
PlayedThreeSeconds *int64 `json:"played_three_seconds"`
Play3sRatio *float64 `json:"play3s_ratio"`
PlayedFiveSeconds *int64 `json:"played_five_seconds"`
Play5sRatio *float64 `json:"play5s_ratio"`
PlayedEnd *int64 `json:"played_end"`
PlayEndRatio *float64 `json:"play_end_ratio"`
Share *int64 `json:"share"`
Comment *int64 `json:"comment"`
Likes *int64 `json:"likes"`
Report *int64 `json:"report"`
Block *int64 `json:"block"`
ItemNegative *int64 `json:"item_negative"`
LiveShare *int64 `json:"live_share"`
LiveComment *int64 `json:"live_comment"`
LiveReward *int64 `json:"live_reward"`
EffectivePlayCount *int64 `json:"effective_play_count"`
EffectivePlayRatio *float64 `json:"effective_play_ratio"`
ConversionNum *int64 `json:"conversion_num"`
ConversionCostEsp *float64 `json:"conversion_cost_esp"`
Roi *float64 `json:"roi"`
Gmv *float64 `json:"gmv"`
T0Gmv *float64 `json:"t0_gmv"`
T1Gmv *float64 `json:"t1_gmv"`
T7Gmv *float64 `json:"t7_gmv"`
T15Gmv *float64 `json:"t15_gmv"`
T30Gmv *float64 `json:"t30_gmv"`
T0Roi *float64 `json:"t0_roi"`
T1Roi *float64 `json:"t1_roi"`
T7Roi *float64 `json:"t7_roi"`
T15Roi *float64 `json:"t15_roi"`
T30Roi *float64 `json:"t30_roi"`
PaiedOrder *int64 `json:"paied_order"`
OrderRatio *float64 `json:"order_ratio"`
T0OrderCnt *int64 `json:"t0_order_cnt"`
T0OrderCntCost *float64 `json:"t0_order_cnt_cost"`
T0OrderCntRatio *float64 `json:"t0_order_cnt_ratio"`
T1OrderCnt *int64 `json:"t1_order_cnt"`
T3OrderCnt *int64 `json:"t3_order_cnt"`
T7OrderCnt *int64 `json:"t7_order_cnt"`
T15OrderCnt *int64 `json:"t15_order_cnt"`
T30OrderCnt *int64 `json:"t30_order_cnt"`
MerchantRecoFans *int64 `json:"merchant_reco_fans"`
T1Retention *float64 `json:"t1_retention"`
T7Retention *float64 `json:"t7_retention"`
T15Retention *float64 `json:"t15_retention"`
T30Retention *float64 `json:"t30_retention"`
T1RetentionRatio *float64 `json:"t1_retention_ratio"`
T7RetentionRatio *float64 `json:"t7_retention_ratio"`
T15RetentionRatio *float64 `json:"t15_retention_ratio"`
T30RetentionRatio *float64 `json:"t30_retention_ratio"`
ReservationSuccess *int64 `json:"reservation_success"`
ReservationCost *float64 `json:"reservation_cost"`
StandardLivePlayedStarted *int64 `json:"standard_live_played_started"`
AdLivePlayCnt *int64 `json:"ad_live_play_cnt"`
AdLivePlayCntCost *float64 `json:"ad_live_play_cnt_cost"`
LiveAudienceCost *float64 `json:"live_audience_cost"`
LiveEventGoodsView *int64 `json:"live_event_goods_view"`
GoodsClickRatio *float64 `json:"goods_click_ratio"`
DirectAttrPlatNewBuyerCnt *int64 `json:"direct_attr_plat_new_buyer_cnt"`
T30AttrPlatTotalBuyerCnt *int64 `json:"t30_attr_plat_total_buyer_cnt"`
DirectAttrSellerNewBuyerCnt *int64 `json:"direct_attr_seller_new_buyer_cnt"`
T30AttrSellerTotalBuyerCnt *int64 `json:"t30_attr_seller_total_buyer_cnt"`
T3Gmv *float64 `json:"t3_gmv"`
T3Roi *float64 `json:"t3_roi"`
T7IndirectOrderAmt *float64 `json:"t7_indirect_order_amt"`
T7IndirectOrderCnt *int64 `json:"t7_indirect_order_cnt"`
FansT0GmvPerFans *float64 `json:"fans_t0_gmv_per_fans"`
FansT3GmvPerFans *float64 `json:"fans_t3_gmv_per_fans"`
FansT7GmvPerFans *float64 `json:"fans_t7_gmv_per_fans"`
FansT15GmvPerFans *float64 `json:"fans_t15_gmv_per_fans"`
FansT30GmvPerFans *float64 `json:"fans_t30_gmv_per_fans"`
RecoFansCost *float64 `json:"reco_fans_cost"`
QcpxWhiteboxDirectOrderPaymentAmt *float64 `json:"qcpx_whitebox_direct_order_payment_amt"`
QcpxWhiteboxDirectOrderCnt *int64 `json:"qcpx_whitebox_direct_order_cnt"`
FansT0Gmv *float64 `json:"fans_t0_gmv"`
FansT1Gmv *float64 `json:"fans_t1_gmv"`
FansT7Gmv *float64 `json:"fans_t7_gmv"`
FansT15Gmv *float64 `json:"fans_t15_gmv"`
FansT30Gmv *float64 `json:"fans_t30_gmv"`
FansT0Roi *float64 `json:"fans_t0_roi"`
FansT1Roi *float64 `json:"fans_t1_roi"`
FansT7Roi *float64 `json:"fans_t7_roi"`
FansT15Roi *float64 `json:"fans_t15_roi"`
FansT30Roi *float64 `json:"fans_t30_roi"`
T0ShopNewBuyerOrderPaymentAmt *float64 `json:"t0_shop_new_buyer_order_payment_amt"`
T1ShopNewBuyerOrderPaymentAmt *float64 `json:"t1_shop_new_buyer_order_payment_amt"`
T3ShopNewBuyerOrderPaymentAmt *float64 `json:"t3_shop_new_buyer_order_payment_amt"`
T7ShopNewBuyerOrderPaymentAmt *float64 `json:"t7_shop_new_buyer_order_payment_amt"`
T15ShopNewBuyerOrderPaymentAmt *float64 `json:"t15_shop_new_buyer_order_payment_amt"`
T30ShopNewBuyerOrderPaymentAmt *float64 `json:"t30_shop_new_buyer_order_payment_amt"`
T0ShopNewBuyerOrderCnt *int64 `json:"t0_shop_new_buyer_order_cnt"`
T1ShopNewBuyerOrderCnt *int64 `json:"t1_shop_new_buyer_order_cnt"`
T3ShopNewBuyerOrderCnt *int64 `json:"t3_shop_new_buyer_order_cnt"`
T7ShopNewBuyerOrderCnt *int64 `json:"t7_shop_new_buyer_order_cnt"`
T15ShopNewBuyerOrderCnt *int64 `json:"t15_shop_new_buyer_order_cnt"`
T30ShopNewBuyerOrderCnt *int64 `json:"t30_shop_new_buyer_order_cnt"`
T1NewBuyerRepurchaseRatio *float64 `json:"t1_new_buyer_repurchase_ratio"`
T3NewBuyerRepurchaseRatio *float64 `json:"t3_new_buyer_repurchase_ratio"`
T7NewBuyerRepurchaseRatio *float64 `json:"t7_new_buyer_repurchase_ratio"`
T15NewBuyerRepurchaseRatio *float64 `json:"t15_new_buyer_repurchase_ratio"`
T30NewBuyerRepurchaseRatio *float64 `json:"t30_new_buyer_repurchase_ratio"`
T0ShopNewBuyerRoi *float64 `json:"t0_shop_new_buyer_roi"`
T1ShopNewBuyerRoi *float64 `json:"t1_shop_new_buyer_roi"`
T3ShopNewBuyerRoi *float64 `json:"t3_shop_new_buyer_roi"`
T7ShopNewBuyerRoi *float64 `json:"t7_shop_new_buyer_roi"`
T15ShopNewBuyerRoi *float64 `json:"t15_shop_new_buyer_roi"`
T30ShopNewBuyerRoi *float64 `json:"t30_shop_new_buyer_roi"`
CreateCardOrderCnt *int64 `json:"create_card_order_cnt"`
ForwardTsCreateCardOrderCnt *int64 `json:"forward_ts_create_card_order_cnt"`
CreateCardOrderCost *float64 `json:"create_card_order_cost"`
ForwardTsCreateCardOrderCost *float64 `json:"forward_ts_create_card_order_cost"`
ActivateCardOrderCnt *int64 `json:"activate_card_order_cnt"`
ForwardTsActivateCardOrderCnt *int64 `json:"forward_ts_activate_card_order_cnt"`
ActivateCardOrderCost *float64 `json:"activate_card_order_cost"`
ForwardTsActivateCardOrderCost *float64 `json:"forward_ts_activate_card_order_cost"`
CreateCardOrderRatio *float64 `json:"create_card_order_ratio"`
ForwardTsCreateCardOrderRatio *float64 `json:"forward_ts_create_card_order_ratio"`
ActivateCardOrderCntRatio *float64 `json:"activate_card_order_cnt_ratio"`
ForwardTsActivateCardOrderRatio *float64 `json:"forward_ts_activate_card_order_ratio"`
LivePlayCnt *int64 `json:"live_play_cnt"`
ItemEntranceClkCnt *int64 `json:"item_entrance_clk_cnt"`
ShowCnt *int64 `json:"show_cnt"`
ReportDateStr string `json:"report_date_str"`
CampaignId *int64 `json:"campaign_id"`
CampaignName string `json:"campaign_name"`
UnitId *int64 `json:"unit_id"`
UnitName string `json:"unit_name"`
CreativeId *int64 `json:"creative_id"`
CreativeName string `json:"creative_name"`
CidActualRoiAfterSubsidy *float64 `json:"cid_actual_roi_after_subsidy"`
CidCouponAmount *int64 `json:"cid_coupon_amount"`
CidCouponCallbackPaidRefundAmount *int64 `json:"cid_coupon_callback_paid_refund_amount"`
CidVoucherCost *float64 `json:"cid_voucher_cost"`
}
type CampaignReportItem CampaignReportSum

415
sync/data_converter.go Normal file
View File

@@ -0,0 +1,415 @@
package sync
import (
"cid/model/dto/copydata"
)
type DataConverter struct{}
func NewDataConverter() *DataConverter {
return &DataConverter{}
}
func (c *DataConverter) ConvertToSumItem(apiData *CampaignReportSum, dataType string) *copydata.CidAccountReportSumItem {
if apiData == nil {
return nil
}
return &copydata.CidAccountReportSumItem{
DataType: dataType,
T0OrderPaymentAmt: apiData.T0OrderPaymentAmt,
CreativeMaterialType: apiData.CreativeMaterialType,
LiveName: apiData.LiveName,
AuthorId: apiData.AuthorId,
PicUrl: apiData.PicUrl,
PicName: apiData.PicName,
PicId: apiData.PicId,
CoverUrl: apiData.CoverUrl,
CoverId: apiData.CoverId,
ItemOrderConversionRatio: apiData.ItemOrderConversionRatio,
ItemCardClickRatio: apiData.ItemCardClickRatio,
ItemCardClkCnt: apiData.ItemCardClkCnt,
LivePlayCntCost: apiData.LivePlayCntCost,
AdMerchantFollowCost: apiData.AdMerchantFollowCost,
AdMerchantFollow: apiData.AdMerchantFollow,
NetT0OrderCnt: apiData.NetT0OrderCnt,
NetT0Roi: apiData.NetT0Roi,
NetT0Gmv: apiData.NetT0Gmv,
PhotoName: apiData.PhotoName,
PhotoIdStr: apiData.PhotoIdStr,
PhotoId: apiData.PhotoId,
ModPriceSegment: apiData.ModPriceSegment,
AgeSegment: apiData.AgeSegment,
Province: apiData.Province,
Gender: apiData.Gender,
AdPhotoPlayedFiveRatio: apiData.AdPhotoPlayedFiveRatio,
AdPhotoPlayedThreeRatio: apiData.AdPhotoPlayedThreeRatio,
OrderSubmitRoi: apiData.OrderSubmitRoi,
OrderSubmitAmt: apiData.OrderSubmitAmt,
EventOrderSubmitCost: apiData.EventOrderSubmitCost,
EventOrderSubmit: apiData.EventOrderSubmit,
EventOrderPaidRoi: apiData.EventOrderPaidRoi,
EventAppInvoked: apiData.EventAppInvoked,
EventAddShoppingCart: apiData.EventAddShoppingCart,
ConversionNumCost: apiData.ConversionNumCost,
AdEffectivePlayNum: apiData.AdEffectivePlayNum,
AdItemClick: apiData.AdItemClick,
MerchantProductId: apiData.MerchantProductId,
CostTotal: apiData.CostTotal,
AdShow: apiData.AdShow,
AdShow1kCost: apiData.AdShow1kCost,
Impression: apiData.Impression,
PhotoClick: apiData.PhotoClick,
PhotoClickRatio: apiData.PhotoClickRatio,
Click: apiData.Click,
ActionbarClick: apiData.ActionbarClick,
ActionbarClickCost: apiData.ActionbarClickCost,
EspClickRatio: apiData.EspClickRatio,
ActionRatio: apiData.ActionRatio,
AdItemClickCount: apiData.AdItemClickCount,
EspLivePlayedSeconds: apiData.EspLivePlayedSeconds,
PlayedThreeSeconds: apiData.PlayedThreeSeconds,
Play3sRatio: apiData.Play3sRatio,
PlayedFiveSeconds: apiData.PlayedFiveSeconds,
Play5sRatio: apiData.Play5sRatio,
PlayedEnd: apiData.PlayedEnd,
PlayEndRatio: apiData.PlayEndRatio,
Share: apiData.Share,
Comment: apiData.Comment,
Likes: apiData.Likes,
Report: apiData.Report,
Block: apiData.Block,
ItemNegative: apiData.ItemNegative,
LiveShare: apiData.LiveShare,
LiveComment: apiData.LiveComment,
LiveReward: apiData.LiveReward,
EffectivePlayCount: apiData.EffectivePlayCount,
EffectivePlayRatio: apiData.EffectivePlayRatio,
ConversionNum: apiData.ConversionNum,
ConversionCostEsp: apiData.ConversionCostEsp,
Roi: apiData.Roi,
Gmv: apiData.Gmv,
T0Gmv: apiData.T0Gmv,
T1Gmv: apiData.T1Gmv,
T3Gmv: apiData.T3Gmv,
T7Gmv: apiData.T7Gmv,
T15Gmv: apiData.T15Gmv,
T30Gmv: apiData.T30Gmv,
T0Roi: apiData.T0Roi,
T1Roi: apiData.T1Roi,
T3Roi: apiData.T3Roi,
T7Roi: apiData.T7Roi,
T15Roi: apiData.T15Roi,
T30Roi: apiData.T30Roi,
PaiedOrder: apiData.PaiedOrder,
OrderRatio: apiData.OrderRatio,
T0OrderCnt: apiData.T0OrderCnt,
T0OrderCntCost: apiData.T0OrderCntCost,
T0OrderCntRatio: apiData.T0OrderCntRatio,
T1OrderCnt: apiData.T1OrderCnt,
T3OrderCnt: apiData.T3OrderCnt,
T7OrderCnt: apiData.T7OrderCnt,
T15OrderCnt: apiData.T15OrderCnt,
T30OrderCnt: apiData.T30OrderCnt,
MerchantRecoFans: apiData.MerchantRecoFans,
T1Retention: apiData.T1Retention,
T7Retention: apiData.T7Retention,
T15Retention: apiData.T15Retention,
T30Retention: apiData.T30Retention,
T1RetentionRatio: apiData.T1RetentionRatio,
T7RetentionRatio: apiData.T7RetentionRatio,
T15RetentionRatio: apiData.T15RetentionRatio,
T30RetentionRatio: apiData.T30RetentionRatio,
ReservationSuccess: apiData.ReservationSuccess,
ReservationCost: apiData.ReservationCost,
StandardLivePlayedStarted: apiData.StandardLivePlayedStarted,
AdLivePlayCnt: apiData.AdLivePlayCnt,
AdLivePlayCntCost: apiData.AdLivePlayCntCost,
LiveAudienceCost: apiData.LiveAudienceCost,
LiveEventGoodsView: apiData.LiveEventGoodsView,
GoodsClickRatio: apiData.GoodsClickRatio,
DirectAttrPlatNewBuyerCnt: apiData.DirectAttrPlatNewBuyerCnt,
T30AttrPlatTotalBuyerCnt: apiData.T30AttrPlatTotalBuyerCnt,
DirectAttrSellerNewBuyerCnt: apiData.DirectAttrSellerNewBuyerCnt,
T30AttrSellerTotalBuyerCnt: apiData.T30AttrSellerTotalBuyerCnt,
T7IndirectOrderAmt: apiData.T7IndirectOrderAmt,
T7IndirectOrderCnt: apiData.T7IndirectOrderCnt,
FansT0GmvPerFans: apiData.FansT0GmvPerFans,
FansT3GmvPerFans: apiData.FansT3GmvPerFans,
FansT7GmvPerFans: apiData.FansT7GmvPerFans,
FansT15GmvPerFans: apiData.FansT15GmvPerFans,
FansT30GmvPerFans: apiData.FansT30GmvPerFans,
RecoFansCost: apiData.RecoFansCost,
QcpxWhiteboxDirectOrderPaymentAmt: apiData.QcpxWhiteboxDirectOrderPaymentAmt,
QcpxWhiteboxDirectOrderCnt: apiData.QcpxWhiteboxDirectOrderCnt,
FansT0Gmv: apiData.FansT0Gmv,
FansT1Gmv: apiData.FansT1Gmv,
FansT7Gmv: apiData.FansT7Gmv,
FansT15Gmv: apiData.FansT15Gmv,
FansT30Gmv: apiData.FansT30Gmv,
FansT0Roi: apiData.FansT0Roi,
FansT1Roi: apiData.FansT1Roi,
FansT7Roi: apiData.FansT7Roi,
FansT15Roi: apiData.FansT15Roi,
FansT30Roi: apiData.FansT30Roi,
T0ShopNewBuyerOrderPaymentAmt: apiData.T0ShopNewBuyerOrderPaymentAmt,
T1ShopNewBuyerOrderPaymentAmt: apiData.T1ShopNewBuyerOrderPaymentAmt,
T3ShopNewBuyerOrderPaymentAmt: apiData.T3ShopNewBuyerOrderPaymentAmt,
T7ShopNewBuyerOrderPaymentAmt: apiData.T7ShopNewBuyerOrderPaymentAmt,
T15ShopNewBuyerOrderPaymentAmt: apiData.T15ShopNewBuyerOrderPaymentAmt,
T30ShopNewBuyerOrderPaymentAmt: apiData.T30ShopNewBuyerOrderPaymentAmt,
T0ShopNewBuyerOrderCnt: apiData.T0ShopNewBuyerOrderCnt,
T1ShopNewBuyerOrderCnt: apiData.T1ShopNewBuyerOrderCnt,
T3ShopNewBuyerOrderCnt: apiData.T3ShopNewBuyerOrderCnt,
T7ShopNewBuyerOrderCnt: apiData.T7ShopNewBuyerOrderCnt,
T15ShopNewBuyerOrderCnt: apiData.T15ShopNewBuyerOrderCnt,
T30ShopNewBuyerOrderCnt: apiData.T30ShopNewBuyerOrderCnt,
T1NewBuyerRepurchaseRatio: apiData.T1NewBuyerRepurchaseRatio,
T3NewBuyerRepurchaseRatio: apiData.T3NewBuyerRepurchaseRatio,
T7NewBuyerRepurchaseRatio: apiData.T7NewBuyerRepurchaseRatio,
T15NewBuyerRepurchaseRatio: apiData.T15NewBuyerRepurchaseRatio,
T30NewBuyerRepurchaseRatio: apiData.T30NewBuyerRepurchaseRatio,
T0ShopNewBuyerRoi: apiData.T0ShopNewBuyerRoi,
T1ShopNewBuyerRoi: apiData.T1ShopNewBuyerRoi,
T3ShopNewBuyerRoi: apiData.T3ShopNewBuyerRoi,
T7ShopNewBuyerRoi: apiData.T7ShopNewBuyerRoi,
T15ShopNewBuyerRoi: apiData.T15ShopNewBuyerRoi,
T30ShopNewBuyerRoi: apiData.T30ShopNewBuyerRoi,
CreateCardOrderCnt: apiData.CreateCardOrderCnt,
ForwardTsCreateCardOrderCnt: apiData.ForwardTsCreateCardOrderCnt,
CreateCardOrderCost: apiData.CreateCardOrderCost,
ForwardTsCreateCardOrderCost: apiData.ForwardTsCreateCardOrderCost,
ActivateCardOrderCnt: apiData.ActivateCardOrderCnt,
ForwardTsActivateCardOrderCnt: apiData.ForwardTsActivateCardOrderCnt,
ActivateCardOrderCost: apiData.ActivateCardOrderCost,
ForwardTsActivateCardOrderCost: apiData.ForwardTsActivateCardOrderCost,
CreateCardOrderRatio: apiData.CreateCardOrderRatio,
ForwardTsCreateCardOrderRatio: apiData.ForwardTsCreateCardOrderRatio,
ActivateCardOrderCntRatio: apiData.ActivateCardOrderCntRatio,
ForwardTsActivateCardOrderRatio: apiData.ForwardTsActivateCardOrderRatio,
LivePlayCnt: apiData.LivePlayCnt,
ItemEntranceClkCnt: apiData.ItemEntranceClkCnt,
ShowCnt: apiData.ShowCnt,
ReportDateStr: apiData.ReportDateStr,
CampaignId: apiData.CampaignId,
CampaignName: apiData.CampaignName,
UnitId: apiData.UnitId,
UnitName: apiData.UnitName,
CreativeId: apiData.CreativeId,
CreativeName: apiData.CreativeName,
CidActualRoiAfterSubsidy: apiData.CidActualRoiAfterSubsidy,
CidCouponAmount: apiData.CidCouponAmount,
CidCouponCallbackPaidRefundAmount: apiData.CidCouponCallbackPaidRefundAmount,
CidVoucherCost: apiData.CidVoucherCost,
}
}
func (c *DataConverter) ConvertToDetailItems(apiItems []*CampaignReportItem, dataType string) []*copydata.CidAccountReportDetailItem {
if len(apiItems) == 0 {
return nil
}
result := make([]*copydata.CidAccountReportDetailItem, 0, len(apiItems))
for _, item := range apiItems {
detailItem := c.convertItemToDetail(item, dataType)
result = append(result, detailItem)
}
return result
}
func (c *DataConverter) convertItemToDetail(apiItem *CampaignReportItem, dataType string) *copydata.CidAccountReportDetailItem {
if apiItem == nil {
return nil
}
item := (*CampaignReportSum)(apiItem)
sumItem := c.ConvertToSumItem(item, dataType)
return &copydata.CidAccountReportDetailItem{
DataType: sumItem.DataType,
T0OrderPaymentAmt: sumItem.T0OrderPaymentAmt,
CreativeMaterialType: sumItem.CreativeMaterialType,
LiveName: sumItem.LiveName,
AuthorId: sumItem.AuthorId,
PicUrl: sumItem.PicUrl,
PicName: sumItem.PicName,
PicId: sumItem.PicId,
CoverUrl: sumItem.CoverUrl,
CoverId: sumItem.CoverId,
ItemOrderConversionRatio: sumItem.ItemOrderConversionRatio,
ItemCardClickRatio: sumItem.ItemCardClickRatio,
ItemCardClkCnt: sumItem.ItemCardClkCnt,
LivePlayCntCost: sumItem.LivePlayCntCost,
AdMerchantFollowCost: sumItem.AdMerchantFollowCost,
AdMerchantFollow: sumItem.AdMerchantFollow,
NetT0OrderCnt: sumItem.NetT0OrderCnt,
NetT0Roi: sumItem.NetT0Roi,
NetT0Gmv: sumItem.NetT0Gmv,
PhotoName: sumItem.PhotoName,
PhotoIdStr: sumItem.PhotoIdStr,
PhotoId: sumItem.PhotoId,
ModPriceSegment: sumItem.ModPriceSegment,
AgeSegment: sumItem.AgeSegment,
Province: sumItem.Province,
Gender: sumItem.Gender,
AdPhotoPlayedFiveRatio: sumItem.AdPhotoPlayedFiveRatio,
AdPhotoPlayedThreeRatio: sumItem.AdPhotoPlayedThreeRatio,
OrderSubmitRoi: sumItem.OrderSubmitRoi,
OrderSubmitAmt: sumItem.OrderSubmitAmt,
EventOrderSubmitCost: sumItem.EventOrderSubmitCost,
EventOrderSubmit: sumItem.EventOrderSubmit,
EventOrderPaidRoi: sumItem.EventOrderPaidRoi,
EventAppInvoked: sumItem.EventAppInvoked,
EventAddShoppingCart: sumItem.EventAddShoppingCart,
ConversionNumCost: sumItem.ConversionNumCost,
AdEffectivePlayNum: sumItem.AdEffectivePlayNum,
AdItemClick: sumItem.AdItemClick,
MerchantProductId: sumItem.MerchantProductId,
CostTotal: sumItem.CostTotal,
AdShow: sumItem.AdShow,
AdShow1kCost: sumItem.AdShow1kCost,
Impression: sumItem.Impression,
PhotoClick: sumItem.PhotoClick,
PhotoClickRatio: sumItem.PhotoClickRatio,
Click: sumItem.Click,
ActionbarClick: sumItem.ActionbarClick,
ActionbarClickCost: sumItem.ActionbarClickCost,
EspClickRatio: sumItem.EspClickRatio,
ActionRatio: sumItem.ActionRatio,
AdItemClickCount: sumItem.AdItemClickCount,
EspLivePlayedSeconds: sumItem.EspLivePlayedSeconds,
PlayedThreeSeconds: sumItem.PlayedThreeSeconds,
Play3sRatio: sumItem.Play3sRatio,
PlayedFiveSeconds: sumItem.PlayedFiveSeconds,
Play5sRatio: sumItem.Play5sRatio,
PlayedEnd: sumItem.PlayedEnd,
PlayEndRatio: sumItem.PlayEndRatio,
Share: sumItem.Share,
Comment: sumItem.Comment,
Likes: sumItem.Likes,
Report: sumItem.Report,
Block: sumItem.Block,
ItemNegative: sumItem.ItemNegative,
LiveShare: sumItem.LiveShare,
LiveComment: sumItem.LiveComment,
LiveReward: sumItem.LiveReward,
EffectivePlayCount: sumItem.EffectivePlayCount,
EffectivePlayRatio: sumItem.EffectivePlayRatio,
ConversionNum: sumItem.ConversionNum,
ConversionCostEsp: sumItem.ConversionCostEsp,
Roi: sumItem.Roi,
Gmv: sumItem.Gmv,
T0Gmv: sumItem.T0Gmv,
T1Gmv: sumItem.T1Gmv,
T3Gmv: sumItem.T3Gmv,
T7Gmv: sumItem.T7Gmv,
T15Gmv: sumItem.T15Gmv,
T30Gmv: sumItem.T30Gmv,
T0Roi: sumItem.T0Roi,
T1Roi: sumItem.T1Roi,
T3Roi: sumItem.T3Roi,
T7Roi: sumItem.T7Roi,
T15Roi: sumItem.T15Roi,
T30Roi: sumItem.T30Roi,
PaiedOrder: sumItem.PaiedOrder,
OrderRatio: sumItem.OrderRatio,
T0OrderCnt: sumItem.T0OrderCnt,
T0OrderCntCost: sumItem.T0OrderCntCost,
T0OrderCntRatio: sumItem.T0OrderCntRatio,
T1OrderCnt: sumItem.T1OrderCnt,
T3OrderCnt: sumItem.T3OrderCnt,
T7OrderCnt: sumItem.T7OrderCnt,
T15OrderCnt: sumItem.T15OrderCnt,
T30OrderCnt: sumItem.T30OrderCnt,
MerchantRecoFans: sumItem.MerchantRecoFans,
T1Retention: sumItem.T1Retention,
T7Retention: sumItem.T7Retention,
T15Retention: sumItem.T15Retention,
T30Retention: sumItem.T30Retention,
T1RetentionRatio: sumItem.T1RetentionRatio,
T7RetentionRatio: sumItem.T7RetentionRatio,
T15RetentionRatio: sumItem.T15RetentionRatio,
T30RetentionRatio: sumItem.T30RetentionRatio,
ReservationSuccess: sumItem.ReservationSuccess,
ReservationCost: sumItem.ReservationCost,
StandardLivePlayedStarted: sumItem.StandardLivePlayedStarted,
AdLivePlayCnt: sumItem.AdLivePlayCnt,
AdLivePlayCntCost: sumItem.AdLivePlayCntCost,
LiveAudienceCost: sumItem.LiveAudienceCost,
LiveEventGoodsView: sumItem.LiveEventGoodsView,
GoodsClickRatio: sumItem.GoodsClickRatio,
DirectAttrPlatNewBuyerCnt: sumItem.DirectAttrPlatNewBuyerCnt,
T30AttrPlatTotalBuyerCnt: sumItem.T30AttrPlatTotalBuyerCnt,
DirectAttrSellerNewBuyerCnt: sumItem.DirectAttrSellerNewBuyerCnt,
T30AttrSellerTotalBuyerCnt: sumItem.T30AttrSellerTotalBuyerCnt,
T7IndirectOrderAmt: sumItem.T7IndirectOrderAmt,
T7IndirectOrderCnt: sumItem.T7IndirectOrderCnt,
FansT0GmvPerFans: sumItem.FansT0GmvPerFans,
FansT3GmvPerFans: sumItem.FansT3GmvPerFans,
FansT7GmvPerFans: sumItem.FansT7GmvPerFans,
FansT15GmvPerFans: sumItem.FansT15GmvPerFans,
FansT30GmvPerFans: sumItem.FansT30GmvPerFans,
RecoFansCost: sumItem.RecoFansCost,
QcpxWhiteboxDirectOrderPaymentAmt: sumItem.QcpxWhiteboxDirectOrderPaymentAmt,
QcpxWhiteboxDirectOrderCnt: sumItem.QcpxWhiteboxDirectOrderCnt,
FansT0Gmv: sumItem.FansT0Gmv,
FansT1Gmv: sumItem.FansT1Gmv,
FansT7Gmv: sumItem.FansT7Gmv,
FansT15Gmv: sumItem.FansT15Gmv,
FansT30Gmv: sumItem.FansT30Gmv,
FansT0Roi: sumItem.FansT0Roi,
FansT1Roi: sumItem.FansT1Roi,
FansT7Roi: sumItem.FansT7Roi,
FansT15Roi: sumItem.FansT15Roi,
FansT30Roi: sumItem.FansT30Roi,
T0ShopNewBuyerOrderPaymentAmt: sumItem.T0ShopNewBuyerOrderPaymentAmt,
T1ShopNewBuyerOrderPaymentAmt: sumItem.T1ShopNewBuyerOrderPaymentAmt,
T3ShopNewBuyerOrderPaymentAmt: sumItem.T3ShopNewBuyerOrderPaymentAmt,
T7ShopNewBuyerOrderPaymentAmt: sumItem.T7ShopNewBuyerOrderPaymentAmt,
T15ShopNewBuyerOrderPaymentAmt: sumItem.T15ShopNewBuyerOrderPaymentAmt,
T30ShopNewBuyerOrderPaymentAmt: sumItem.T30ShopNewBuyerOrderPaymentAmt,
T0ShopNewBuyerOrderCnt: sumItem.T0ShopNewBuyerOrderCnt,
T1ShopNewBuyerOrderCnt: sumItem.T1ShopNewBuyerOrderCnt,
T3ShopNewBuyerOrderCnt: sumItem.T3ShopNewBuyerOrderCnt,
T7ShopNewBuyerOrderCnt: sumItem.T7ShopNewBuyerOrderCnt,
T15ShopNewBuyerOrderCnt: sumItem.T15ShopNewBuyerOrderCnt,
T30ShopNewBuyerOrderCnt: sumItem.T30ShopNewBuyerOrderCnt,
T1NewBuyerRepurchaseRatio: sumItem.T1NewBuyerRepurchaseRatio,
T3NewBuyerRepurchaseRatio: sumItem.T3NewBuyerRepurchaseRatio,
T7NewBuyerRepurchaseRatio: sumItem.T7NewBuyerRepurchaseRatio,
T15NewBuyerRepurchaseRatio: sumItem.T15NewBuyerRepurchaseRatio,
T30NewBuyerRepurchaseRatio: sumItem.T30NewBuyerRepurchaseRatio,
T0ShopNewBuyerRoi: sumItem.T0ShopNewBuyerRoi,
T1ShopNewBuyerRoi: sumItem.T1ShopNewBuyerRoi,
T3ShopNewBuyerRoi: sumItem.T3ShopNewBuyerRoi,
T7ShopNewBuyerRoi: sumItem.T7ShopNewBuyerRoi,
T15ShopNewBuyerRoi: sumItem.T15ShopNewBuyerRoi,
T30ShopNewBuyerRoi: sumItem.T30ShopNewBuyerRoi,
CreateCardOrderCnt: sumItem.CreateCardOrderCnt,
ForwardTsCreateCardOrderCnt: sumItem.ForwardTsCreateCardOrderCnt,
CreateCardOrderCost: sumItem.CreateCardOrderCost,
ForwardTsCreateCardOrderCost: sumItem.ForwardTsCreateCardOrderCost,
ActivateCardOrderCnt: sumItem.ActivateCardOrderCnt,
ForwardTsActivateCardOrderCnt: sumItem.ForwardTsActivateCardOrderCnt,
ActivateCardOrderCost: sumItem.ActivateCardOrderCost,
ForwardTsActivateCardOrderCost: sumItem.ForwardTsActivateCardOrderCost,
CreateCardOrderRatio: sumItem.CreateCardOrderRatio,
ForwardTsCreateCardOrderRatio: sumItem.ForwardTsCreateCardOrderRatio,
ActivateCardOrderCntRatio: sumItem.ActivateCardOrderCntRatio,
ForwardTsActivateCardOrderRatio: sumItem.ForwardTsActivateCardOrderRatio,
LivePlayCnt: sumItem.LivePlayCnt,
ItemEntranceClkCnt: sumItem.ItemEntranceClkCnt,
ShowCnt: sumItem.ShowCnt,
ReportDateStr: sumItem.ReportDateStr,
CampaignId: sumItem.CampaignId,
CampaignName: sumItem.CampaignName,
UnitId: sumItem.UnitId,
UnitName: sumItem.UnitName,
CreativeId: sumItem.CreativeId,
CreativeName: sumItem.CreativeName,
CidActualRoiAfterSubsidy: sumItem.CidActualRoiAfterSubsidy,
CidCouponAmount: sumItem.CidCouponAmount,
CidCouponCallbackPaidRefundAmount: sumItem.CidCouponCallbackPaidRefundAmount,
CidVoucherCost: sumItem.CidVoucherCost,
}
}

68
sync/http_client.go Normal file
View File

@@ -0,0 +1,68 @@
package sync
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
type HttpClient struct {
BaseURL string
Timeout time.Duration
HTTPClient *http.Client
}
func NewHttpClient(baseURL string, timeout time.Duration) *HttpClient {
if timeout == 0 {
timeout = 30 * time.Second
}
return &HttpClient{
BaseURL: baseURL,
Timeout: timeout,
HTTPClient: &http.Client{
Timeout: timeout,
},
}
}
func (c *HttpClient) Post(ctx context.Context, url string, body interface{}) ([]byte, error) {
jsonData, err := json.Marshal(body)
if err != nil {
return nil, fmt.Errorf("序列化请求失败:%w", err)
}
fullURL := url
if c.BaseURL != "" && len(url) > 0 && url[0] != '/' {
fullURL = c.BaseURL + url
} else if c.BaseURL != "" {
fullURL = c.BaseURL + url
}
req, err := http.NewRequestWithContext(ctx, "POST", fullURL, bytes.NewBuffer(jsonData))
if err != nil {
return nil, fmt.Errorf("创建请求失败:%w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := c.HTTPClient.Do(req)
if err != nil {
return nil, fmt.Errorf("请求失败:%w", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
return nil, fmt.Errorf("读取响应失败:%w", err)
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP 错误状态码:%d", resp.StatusCode)
}
return respBody, nil
}

272
sync/mock_generator.go Normal file
View File

@@ -0,0 +1,272 @@
package sync
import (
"math/rand"
"time"
)
type MockDataGenerator struct {
rand *rand.Rand
}
func NewMockDataGenerator() *MockDataGenerator {
return &MockDataGenerator{
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
}
}
func (m *MockDataGenerator) GenerateCampaignReportRequest() *CampaignReportRequest {
return &CampaignReportRequest{
AdvertiserID: 10001,
StartTime: time.Now().AddDate(0, 0, -30).UnixNano() / 1e6,
EndTime: time.Now().UnixNano() / 1e6,
SelectColumns: []string{"impression", "click", "cost", "t0GMV"},
GroupType: 1,
QueryVersion: 1,
SelectParam: &CampaignSelectParam{
CampaignIDs: []int64{1, 2, 3},
},
PageInfo: &PageInfo{
CurrentPage: 1,
PageSize: 20,
},
}
}
func (m *MockDataGenerator) GenerateCampaignReportResponse() *CampaignReportResponse {
sumData := m.generateSumData()
detailData := m.generateDetailData(5)
return &CampaignReportResponse{
Code: 0,
Message: "success",
Data: &CampaignReportData{
Sum: sumData,
Detail: detailData,
TotalCount: len(detailData),
},
}
}
func (m *MockDataGenerator) generateSumData() *CampaignReportSum {
cost := m.randomFloat(1000, 10000)
impression := m.randomInt64(10000, 100000)
click := m.randomInt64(100, 1000)
return &CampaignReportSum{
T0OrderPaymentAmt: "888.99",
CreativeMaterialType: "视频素材类型",
LiveName: "测试直播间",
AuthorId: "123456",
PicUrl: "http://example.com/pic.jpg",
PicName: "图片名称",
PicId: "pic_123",
CoverUrl: "http://example.com/cover.jpg",
CoverId: 4551122,
ItemOrderConversionRatio: m.randomFloatPtr(0.01, 0.5),
ItemCardClickRatio: m.randomFloatPtr(0.02, 0.3),
ItemCardClkCnt: m.randomIntPtr(10, 100),
LivePlayCntCost: m.randomFloatPtr(0.5, 5.0),
AdMerchantFollowCost: m.randomFloatPtr(1.0, 10.0),
AdMerchantFollow: m.randomIntPtr(50, 500),
NetT0OrderCnt: m.randomIntPtr(10, 100),
NetT0Roi: m.randomFloatPtr(1.5, 5.0),
NetT0Gmv: m.randomFloatPtr(5000, 50000),
PhotoName: "测试视频",
PhotoIdStr: "video_123",
PhotoId: "video_123",
ModPriceSegment: "1000-2000",
AgeSegment: "24-30",
Province: "广东",
Gender: "男",
AdPhotoPlayedFiveRatio: m.randomFloatPtr(0.3, 0.8),
AdPhotoPlayedThreeRatio: m.randomFloatPtr(0.5, 0.9),
OrderSubmitRoi: m.randomFloatPtr(1.0, 3.0),
OrderSubmitAmt: m.randomIntPtr(10, 100),
EventOrderSubmitCost: m.randomFloatPtr(5.0, 20.0),
EventOrderSubmit: m.randomIntPtr(5, 50),
EventOrderPaidRoi: m.randomFloatPtr(0.5, 2.0),
EventAppInvoked: m.randomIntPtr(100, 1000),
EventAddShoppingCart: m.randomIntPtr(50, 500),
ConversionNumCost: m.randomFloatPtr(10.0, 50.0),
AdEffectivePlayNum: m.randomIntPtr(1000, 10000),
AdItemClick: m.randomIntPtr(100, 1000),
MerchantProductId: "product_123",
CostTotal: &cost,
AdShow: m.randomIntPtr(10000, 100000),
AdShow1kCost: m.randomFloatPtr(5.0, 50.0),
Impression: &impression,
PhotoClick: m.randomIntPtr(100, 5000),
PhotoClickRatio: m.randomFloatPtr(0.01, 0.1),
Click: &click,
ActionbarClick: m.randomIntPtr(50, 500),
ActionbarClickCost: m.randomFloatPtr(1.0, 10.0),
EspClickRatio: m.randomFloatPtr(0.01, 0.1),
ActionRatio: m.randomFloatPtr(0.02, 0.2),
AdItemClickCount: m.randomIntPtr(10, 100),
EspLivePlayedSeconds: m.randomIntPtr(30, 300),
PlayedThreeSeconds: m.randomIntPtr(5000, 50000),
Play3sRatio: m.randomFloatPtr(0.3, 0.8),
PlayedFiveSeconds: m.randomIntPtr(3000, 30000),
Play5sRatio: m.randomFloatPtr(0.2, 0.6),
PlayedEnd: m.randomIntPtr(1000, 10000),
PlayEndRatio: m.randomFloatPtr(0.1, 0.4),
Share: m.randomIntPtr(10, 100),
Comment: m.randomIntPtr(20, 200),
Likes: m.randomIntPtr(100, 1000),
Report: m.randomIntPtr(1, 10),
Block: m.randomIntPtr(1, 10),
ItemNegative: m.randomIntPtr(5, 50),
LiveShare: m.randomIntPtr(5, 50),
LiveComment: m.randomIntPtr(10, 100),
LiveReward: m.randomIntPtr(20, 200),
EffectivePlayCount: m.randomIntPtr(1000, 10000),
EffectivePlayRatio: m.randomFloatPtr(0.1, 0.5),
ConversionNum: m.randomIntPtr(5, 50),
ConversionCostEsp: m.randomFloatPtr(10.0, 50.0),
Roi: m.randomFloatPtr(1.0, 3.0),
Gmv: m.randomFloatPtr(1000, 10000),
T0Gmv: m.randomFloatPtr(500, 5000),
T1Gmv: m.randomFloatPtr(800, 8000),
T3Gmv: m.randomFloatPtr(1200, 12000),
T7Gmv: m.randomFloatPtr(2000, 20000),
T15Gmv: m.randomFloatPtr(3000, 30000),
T30Gmv: m.randomFloatPtr(5000, 50000),
T0Roi: m.randomFloatPtr(0.5, 2.0),
T1Roi: m.randomFloatPtr(0.8, 2.5),
T3Roi: m.randomFloatPtr(1.0, 3.0),
T7Roi: m.randomFloatPtr(1.5, 4.0),
T15Roi: m.randomFloatPtr(2.0, 5.0),
T30Roi: m.randomFloatPtr(2.5, 6.0),
PaiedOrder: m.randomIntPtr(5, 50),
OrderRatio: m.randomFloatPtr(0.01, 0.1),
T0OrderCnt: m.randomIntPtr(5, 50),
T0OrderCntCost: m.randomFloatPtr(10.0, 100.0),
T0OrderCntRatio: m.randomFloatPtr(0.5, 0.9),
T1OrderCnt: m.randomIntPtr(10, 100),
T3OrderCnt: m.randomIntPtr(20, 200),
T7OrderCnt: m.randomIntPtr(30, 300),
T15OrderCnt: m.randomIntPtr(40, 400),
T30OrderCnt: m.randomIntPtr(50, 500),
MerchantRecoFans: m.randomIntPtr(100, 1000),
T1Retention: m.randomFloatPtr(0.3, 0.8),
T7Retention: m.randomFloatPtr(0.2, 0.6),
T15Retention: m.randomFloatPtr(0.15, 0.5),
T30Retention: m.randomFloatPtr(0.1, 0.4),
T1RetentionRatio: m.randomFloatPtr(0.3, 0.8),
T7RetentionRatio: m.randomFloatPtr(0.2, 0.6),
T15RetentionRatio: m.randomFloatPtr(0.15, 0.5),
T30RetentionRatio: m.randomFloatPtr(0.1, 0.4),
ReservationSuccess: m.randomIntPtr(10, 100),
ReservationCost: m.randomFloatPtr(5.0, 50.0),
StandardLivePlayedStarted: m.randomIntPtr(100, 1000),
AdLivePlayCnt: m.randomIntPtr(50, 500),
AdLivePlayCntCost: m.randomFloatPtr(1.0, 10.0),
LiveAudienceCost: m.randomFloatPtr(0.5, 5.0),
LiveEventGoodsView: m.randomIntPtr(100, 1000),
GoodsClickRatio: m.randomFloatPtr(0.05, 0.3),
DirectAttrPlatNewBuyerCnt: m.randomIntPtr(10, 100),
T30AttrPlatTotalBuyerCnt: m.randomIntPtr(50, 500),
DirectAttrSellerNewBuyerCnt: m.randomIntPtr(5, 50),
T30AttrSellerTotalBuyerCnt: m.randomIntPtr(20, 200),
T7IndirectOrderAmt: m.randomFloatPtr(500, 5000),
T7IndirectOrderCnt: m.randomIntPtr(5, 50),
FansT0GmvPerFans: m.randomFloatPtr(10.0, 100.0),
FansT3GmvPerFans: m.randomFloatPtr(20.0, 200.0),
FansT7GmvPerFans: m.randomFloatPtr(30.0, 300.0),
FansT15GmvPerFans: m.randomFloatPtr(40.0, 400.0),
FansT30GmvPerFans: m.randomFloatPtr(50.0, 500.0),
RecoFansCost: m.randomFloatPtr(5.0, 50.0),
QcpxWhiteboxDirectOrderPaymentAmt: m.randomFloatPtr(100, 1000),
QcpxWhiteboxDirectOrderCnt: m.randomIntPtr(1, 10),
FansT0Gmv: m.randomFloatPtr(100, 1000),
FansT1Gmv: m.randomFloatPtr(200, 2000),
FansT7Gmv: m.randomFloatPtr(300, 3000),
FansT15Gmv: m.randomFloatPtr(400, 4000),
FansT30Gmv: m.randomFloatPtr(500, 5000),
FansT0Roi: m.randomFloatPtr(0.5, 2.0),
FansT1Roi: m.randomFloatPtr(0.8, 2.5),
FansT7Roi: m.randomFloatPtr(1.0, 3.0),
FansT15Roi: m.randomFloatPtr(1.5, 4.0),
FansT30Roi: m.randomFloatPtr(2.0, 5.0),
T0ShopNewBuyerOrderPaymentAmt: m.randomFloatPtr(100, 1000),
T1ShopNewBuyerOrderPaymentAmt: m.randomFloatPtr(200, 2000),
T3ShopNewBuyerOrderPaymentAmt: m.randomFloatPtr(300, 3000),
T7ShopNewBuyerOrderPaymentAmt: m.randomFloatPtr(400, 4000),
T15ShopNewBuyerOrderPaymentAmt: m.randomFloatPtr(500, 5000),
T30ShopNewBuyerOrderPaymentAmt: m.randomFloatPtr(600, 6000),
T0ShopNewBuyerOrderCnt: m.randomIntPtr(1, 10),
T1ShopNewBuyerOrderCnt: m.randomIntPtr(2, 20),
T3ShopNewBuyerOrderCnt: m.randomIntPtr(3, 30),
T7ShopNewBuyerOrderCnt: m.randomIntPtr(4, 40),
T15ShopNewBuyerOrderCnt: m.randomIntPtr(5, 50),
T30ShopNewBuyerOrderCnt: m.randomIntPtr(6, 60),
T1NewBuyerRepurchaseRatio: m.randomFloatPtr(0.1, 0.5),
T3NewBuyerRepurchaseRatio: m.randomFloatPtr(0.15, 0.55),
T7NewBuyerRepurchaseRatio: m.randomFloatPtr(0.2, 0.6),
T15NewBuyerRepurchaseRatio: m.randomFloatPtr(0.25, 0.65),
T30NewBuyerRepurchaseRatio: m.randomFloatPtr(0.3, 0.7),
T0ShopNewBuyerRoi: m.randomFloatPtr(0.5, 2.0),
T1ShopNewBuyerRoi: m.randomFloatPtr(0.8, 2.5),
T3ShopNewBuyerRoi: m.randomFloatPtr(1.0, 3.0),
T7ShopNewBuyerRoi: m.randomFloatPtr(1.5, 4.0),
T15ShopNewBuyerRoi: m.randomFloatPtr(2.0, 5.0),
T30ShopNewBuyerRoi: m.randomFloatPtr(2.5, 6.0),
CreateCardOrderCnt: m.randomIntPtr(1, 10),
ForwardTsCreateCardOrderCnt: m.randomIntPtr(1, 10),
CreateCardOrderCost: m.randomFloatPtr(10.0, 100.0),
ForwardTsCreateCardOrderCost: m.randomFloatPtr(10.0, 100.0),
ActivateCardOrderCnt: m.randomIntPtr(1, 10),
ForwardTsActivateCardOrderCnt: m.randomIntPtr(1, 10),
ActivateCardOrderCost: m.randomFloatPtr(10.0, 100.0),
ForwardTsActivateCardOrderCost: m.randomFloatPtr(10.0, 100.0),
CreateCardOrderRatio: m.randomFloatPtr(0.01, 0.1),
ForwardTsCreateCardOrderRatio: m.randomFloatPtr(0.01, 0.1),
ActivateCardOrderCntRatio: m.randomFloatPtr(0.01, 0.1),
ForwardTsActivateCardOrderRatio: m.randomFloatPtr(0.01, 0.1),
LivePlayCnt: m.randomIntPtr(100, 1000),
ItemEntranceClkCnt: m.randomIntPtr(50, 500),
ShowCnt: m.randomIntPtr(1000, 10000),
ReportDateStr: time.Now().Format("2006-01-02"),
CampaignId: m.randomIntPtr(1, 100),
CampaignName: "测试计划",
UnitId: m.randomIntPtr(1, 50),
UnitName: "测试单元",
CreativeId: m.randomIntPtr(1, 20),
CreativeName: "测试创意",
CidActualRoiAfterSubsidy: m.randomFloatPtr(1.0, 3.0),
CidCouponAmount: m.randomIntPtr(100, 1000),
CidCouponCallbackPaidRefundAmount: m.randomIntPtr(50, 500),
CidVoucherCost: m.randomFloatPtr(5.0, 50.0),
}
}
func (m *MockDataGenerator) generateDetailData(count int) []*CampaignReportItem {
items := make([]*CampaignReportItem, count)
for i := 0; i < count; i++ {
items[i] = (*CampaignReportItem)(m.generateSumData())
}
return items
}
func (m *MockDataGenerator) randomInt(min, max int) int {
return m.rand.Intn(max-min) + min
}
func (m *MockDataGenerator) randomInt64(min, max int64) int64 {
return m.rand.Int63n(max-min) + min
}
func (m *MockDataGenerator) randomFloat(min, max float64) float64 {
return m.rand.Float64()*(max-min) + min
}
func (m *MockDataGenerator) randomIntPtr(min, max int) *int64 {
v := int64(m.randomInt(min, max))
return &v
}
func (m *MockDataGenerator) randomFloatPtr(min, max float64) *float64 {
v := m.randomFloat(min, max)
return &v
}

51
sync/quick_sync.go Normal file
View File

@@ -0,0 +1,51 @@
package sync
import (
"context"
"time"
"github.com/sirupsen/logrus"
)
func SyncCampaignReportWithMock(ctx context.Context) error {
syncService := NewSyncService()
req := &CampaignReportRequest{
AdvertiserID: 10001,
StartTime: time.Now().AddDate(0, 0, -30).UnixNano() / 1e6,
EndTime: time.Now().UnixNano() / 1e6,
SelectColumns: []string{"impression", "click", "cost", "t0GMV"},
GroupType: 1,
QueryVersion: 1,
SelectParam: &CampaignSelectParam{
CampaignIDs: []int64{1, 2, 3},
},
PageInfo: &PageInfo{
CurrentPage: 1,
PageSize: 20,
},
}
result, err := syncService.SyncCampaignReport(ctx, req, true)
if err != nil {
logrus.Errorf("同步失败:%v", err)
return err
}
logrus.Infof("同步成功 - 汇总 ID: %d, 明细数量:%d", result.SumID, result.DetailCount)
return nil
}
func SyncCampaignReportWithRealAPI(ctx context.Context, req *CampaignReportRequest) error {
syncService := NewSyncService()
result, err := syncService.SyncCampaignReport(ctx, req, false)
if err != nil {
logrus.Errorf("同步失败:%v", err)
return err
}
logrus.Infof("同步成功 - 汇总 ID: %d, 明细数量:%d, 成功:%d, 失败:%d",
result.SumID, result.DetailCount, result.DetailSuccessCount, result.DetailFailCount)
return nil
}

268
sync/sync_service.go Normal file
View File

@@ -0,0 +1,268 @@
package sync
import (
dto "cid/model/dto/copydata"
"cid/service/copydata"
"context"
"encoding/json"
"fmt"
"time"
"gitea.com/red-future/common/beans"
"github.com/sirupsen/logrus"
)
type SyncService struct {
httpClient *HttpClient
converter *DataConverter
mockGen *MockDataGenerator
}
func NewSyncService() *SyncService {
return &SyncService{
httpClient: NewHttpClient("https://ad.e.kuaishou.com", 0),
converter: NewDataConverter(),
mockGen: NewMockDataGenerator(),
}
}
type SyncResult struct {
SumSuccess bool `json:"sum_success"`
SumID int64 `json:"sum_id"`
DetailSuccess bool `json:"detail_success"`
DetailCount int `json:"detail_count"`
DetailSuccessCount int64 `json:"detail_success_count"`
DetailFailCount int64 `json:"detail_fail_count"`
Error error `json:"error"`
}
func (s *SyncService) SyncCampaignReport(ctx context.Context, req *CampaignReportRequest, useMock bool) (*SyncResult, error) {
result := &SyncResult{}
var responseData *CampaignReportResponse
if useMock {
logrus.Info("使用 Mock 数据同步快手广告计划报表")
responseData = s.mockGen.GenerateCampaignReportResponse()
} else {
logrus.Info("从真实 API 同步快手广告计划报表")
respBytes, err := s.httpClient.Post(ctx, "/rest/openapi/gw/esp/report/campaignReport", req)
if err != nil {
result.Error = fmt.Errorf("调用 API 失败:%w", err)
return result, result.Error
}
responseData = &CampaignReportResponse{}
if err := json.Unmarshal(respBytes, responseData); err != nil {
result.Error = fmt.Errorf("解析响应失败:%w", err)
return result, result.Error
}
if responseData.Code != 0 {
result.Error = fmt.Errorf("API 返回错误code=%d, message=%s", responseData.Code, responseData.Message)
return result, result.Error
}
}
if responseData.Data.Sum != nil {
sumItem := s.converter.ConvertToSumItem(responseData.Data.Sum, "campaign_report")
ctx = context.WithValue(ctx, "user", &beans.User{UserName: "admin"})
sumResult, saveErr := s.saveSumData(ctx, sumItem)
if saveErr != nil {
logrus.Errorf("保存汇总数据失败:%v", saveErr)
result.Error = fmt.Errorf("保存汇总数据失败:%w", saveErr)
} else {
result.SumSuccess = true
result.SumID = sumResult.Id
logrus.Infof("成功保存汇总数据ID=%d", sumResult.Id)
}
}
if len(responseData.Data.Detail) > 0 {
detailItems := s.converter.ConvertToDetailItems(responseData.Data.Detail, "campaign_report")
detailResult, saveErr := s.saveDetailData(ctx, detailItems)
if saveErr != nil {
logrus.Errorf("保存明细数据失败:%v", saveErr)
result.Error = fmt.Errorf("保存明细数据失败:%w", saveErr)
} else {
result.DetailSuccess = true
result.DetailCount = len(detailItems)
result.DetailSuccessCount = detailResult.SuccessCount
result.DetailFailCount = detailResult.FailCount
logrus.Infof("成功保存明细数据,成功=%d, 失败=%d", detailResult.SuccessCount, detailResult.FailCount)
}
}
return result, result.Error
}
// SyncCampaignReportWithPagination 带分页处理的同步方法(支持全量数据抽取)
func (s *SyncService) SyncCampaignReportWithPagination(ctx context.Context, req *CampaignReportRequest, useMock bool, maxRetries int) (*SyncResult, error) {
aggregatedResult := &SyncResult{
SumSuccess: false,
SumID: 0,
}
allDetailItems := make([]*dto.CidAccountReportDetailItem, 0)
totalCount := 0
currentPage := 1
pageSize := 100
if req.PageInfo == nil {
req.PageInfo = &PageInfo{}
}
for {
logrus.Infof(">>> 正在同步第 %d 页数据...", currentPage)
req.PageInfo.CurrentPage = currentPage
req.PageInfo.PageSize = pageSize
result, err := s.SyncWithRetry(ctx, req, useMock, maxRetries)
if err != nil {
logrus.Errorf("第 %d 页同步失败:%v", currentPage, err)
return aggregatedResult, err
}
if result.SumSuccess && aggregatedResult.SumID == 0 {
aggregatedResult.SumSuccess = true
aggregatedResult.SumID = result.SumID
logrus.Infof("✓ 汇总数据已保存ID=%d", result.SumID)
}
if result.DetailSuccess && result.DetailCount > 0 {
detailItems := s.extractDetailItems(req, useMock)
if len(detailItems) > 0 {
allDetailItems = append(allDetailItems, detailItems...)
totalCount += len(detailItems)
logrus.Infof("✓ 第 %d 页获取到 %d 条明细数据,累计 %d 条", currentPage, len(detailItems), totalCount)
}
}
currentData := s.fetchCurrentData(req, useMock)
if currentData != nil && currentData.TotalCount > 0 {
totalPages := (currentData.TotalCount + pageSize - 1) / pageSize
logrus.Infof("总记录数:%d, 总页数:%d, 当前页:%d/%d",
currentData.TotalCount, totalPages, currentPage, totalPages)
if currentPage >= totalPages {
logrus.Infof("✓ 已同步所有页面数据,共 %d 页,%d 条记录", totalPages, currentData.TotalCount)
break
}
}
if result.DetailCount < pageSize {
logrus.Infof("✓ 当前页数据不足 %d 条,已到达最后一页", pageSize)
break
}
currentPage++
time.Sleep(300 * time.Millisecond)
}
if len(allDetailItems) > 0 {
logrus.Infof("开始批量保存 %d 条明细数据...", len(allDetailItems))
detailResult, saveErr := s.saveDetailData(ctx, allDetailItems)
if saveErr != nil {
logrus.Errorf("批量保存明细数据失败:%v", saveErr)
aggregatedResult.Error = fmt.Errorf("批量保存明细数据失败:%w", saveErr)
} else {
aggregatedResult.DetailSuccess = true
aggregatedResult.DetailCount = len(allDetailItems)
aggregatedResult.DetailSuccessCount = detailResult.SuccessCount
aggregatedResult.DetailFailCount = detailResult.FailCount
logrus.Infof("✓ 批量保存明细数据完成,成功=%d, 失败=%d",
detailResult.SuccessCount, detailResult.FailCount)
}
} else {
logrus.Info("没有明细数据需要保存")
}
return aggregatedResult, aggregatedResult.Error
}
func (s *SyncService) extractDetailItems(req *CampaignReportRequest, useMock bool) []*dto.CidAccountReportDetailItem {
if useMock {
responseData := s.mockGen.GenerateCampaignReportResponse()
if responseData == nil || responseData.Data == nil || len(responseData.Data.Detail) == 0 {
return nil
}
return s.converter.ConvertToDetailItems(responseData.Data.Detail, "campaign_report")
}
respBytes, err := s.httpClient.Post(context.Background(), "/rest/openapi/gw/esp/report/campaignReport", req)
if err != nil {
logrus.Errorf("重新获取数据失败:%v", err)
return nil
}
responseData := &CampaignReportResponse{}
if err := json.Unmarshal(respBytes, responseData); err != nil {
logrus.Errorf("解析响应失败:%v", err)
return nil
}
if responseData.Code != 0 || responseData.Data == nil || len(responseData.Data.Detail) == 0 {
return nil
}
return s.converter.ConvertToDetailItems(responseData.Data.Detail, "campaign_report")
}
func (s *SyncService) fetchCurrentData(req *CampaignReportRequest, useMock bool) *CampaignReportData {
if useMock {
responseData := s.mockGen.GenerateCampaignReportResponse()
if responseData != nil && responseData.Data != nil {
return responseData.Data
}
return nil
}
respBytes, err := s.httpClient.Post(context.Background(), "/rest/openapi/gw/esp/report/campaignReport", req)
if err != nil {
return nil
}
responseData := &CampaignReportResponse{}
if err := json.Unmarshal(respBytes, responseData); err != nil {
return nil
}
if responseData.Code == 0 && responseData.Data != nil {
return responseData.Data
}
return nil
}
func (s *SyncService) saveSumData(ctx context.Context, item *dto.CidAccountReportSumItem) (*dto.CreateCidAccountReportSumRes, error) {
return copydata.CidAccountReportDetail.CreateSum(ctx, item)
}
func (s *SyncService) saveDetailData(ctx context.Context, items []*dto.CidAccountReportDetailItem) (*dto.BatchCreateCidAccountReportDetailRes, error) {
req := &dto.BatchCreateCidAccountReportDetailReq{
Items: items,
}
return copydata.CidAccountReportDetail.BatchCreate(ctx, req)
}
func (s *SyncService) SyncWithRetry(ctx context.Context, req *CampaignReportRequest, useMock bool, maxRetries int) (*SyncResult, error) {
var lastResult *SyncResult
var lastErr error
for attempt := 0; attempt <= maxRetries; attempt++ {
result, err := s.SyncCampaignReport(ctx, req, useMock)
lastResult = result
lastErr = err
if err == nil {
logrus.Infof("同步成功,尝试次数:%d", attempt+1)
return result, nil
}
logrus.Warnf("同步失败,第 %d 次重试,错误:%v", attempt+1, err)
}
return lastResult, lastErr
}

139
sync/sync_test.go Normal file
View File

@@ -0,0 +1,139 @@
package sync
import (
"fmt"
"testing"
"time"
_ "github.com/gogf/gf/contrib/drivers/pgsql/v2"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
)
func init() {
fmt.Println("=== 初始化测试环境 ===")
ctx := gctx.New()
db := g.DB()
if db != nil {
_, err := db.Query(ctx, "SELECT 1")
if err == nil {
fmt.Println("✓ 数据库连接成功")
} else {
fmt.Printf("⚠️ 数据库连接失败:%v\n", err)
fmt.Println("⚠️ 将跳过数据库相关测试")
}
} else {
fmt.Println("⚠️ 数据库未初始化")
}
fmt.Println("========================")
}
func TestMockDataGeneration(t *testing.T) {
mockGen := NewMockDataGenerator()
req := mockGen.GenerateCampaignReportRequest()
if req == nil {
t.Error("请求数据生成失败")
return
}
fmt.Printf("✓ Mock 请求生成成功AdvertiserID=%d\n", req.AdvertiserID)
}
func TestDataConverter(t *testing.T) {
converter := NewDataConverter()
mockGen := NewMockDataGenerator()
responseData := mockGen.GenerateCampaignReportResponse()
if responseData == nil || responseData.Data.Sum == nil {
t.Fatal("Mock 数据生成失败")
}
sumItem := converter.ConvertToSumItem(responseData.Data.Sum, "campaign_report")
if sumItem == nil {
t.Fatal("转换为汇总数据失败")
}
if sumItem.CampaignName == "" {
t.Error("计划名称为空")
}
fmt.Printf("✓ 汇总数据转换成功:计划=%s\n", sumItem.CampaignName)
detailItems := converter.ConvertToDetailItems(responseData.Data.Detail, "campaign_report")
if len(detailItems) == 0 {
t.Fatal("转换为明细数据失败")
}
fmt.Printf("✓ 明细数据转换成功:数量=%d\n", len(detailItems))
}
func TestSyncCampaignReportWithDB(t *testing.T) {
ctx := gctx.New()
syncService := NewSyncService()
req := &CampaignReportRequest{
AdvertiserID: 10001,
StartTime: time.Now().AddDate(0, 0, -30).UnixNano() / 1e6,
EndTime: time.Now().UnixNano() / 1e6,
SelectColumns: []string{"impression", "click", "cost", "t0GMV"},
GroupType: 1,
QueryVersion: 1,
}
result, err := syncService.SyncCampaignReport(ctx, req, true)
if err != nil {
t.Logf("同步失败(可能是数据库问题): %v", err)
return
}
fmt.Printf("✓ 同步结果:汇总成功=%v, 汇总 ID=%d, 明细数量=%d\n",
result.SumSuccess, result.SumID, result.DetailCount)
}
//
//// TestScheduledSyncTask 测试定时同步任务(每小时执行一次,全量分页抽取)
//func TestScheduledSyncTask(t *testing.T) {
// ctx := gctx.New()
// syncService := NewSyncService()
//
// req := &CampaignReportRequest{
// AdvertiserID: 10001,
// StartTime: time.Now().AddDate(0, 0, -30).UnixNano() / 1e6,
// EndTime: time.Now().UnixNano() / 1e6,
// SelectColumns: []string{"impression", "click", "cost", "t0GMV"},
// GroupType: 1,
// QueryVersion: 1,
// }
//
// logrus.Info("=== 开始执行定时同步任务 ===")
// result, err := syncService.SyncCampaignReportWithPagination(ctx, req, true, 3)
// if err != nil {
// t.Logf("定时同步任务失败:%v", err)
// return
// }
//
// fmt.Printf("✓ 定时同步完成:\n")
// fmt.Printf(" 汇总数据:成功=%v, ID=%d\n", result.SumSuccess, result.SumID)
// fmt.Printf(" 明细数据:总数=%d, 成功=%d, 失败=%d\n",
// result.DetailCount, result.DetailSuccessCount, result.DetailFailCount)
//}
func BenchmarkSyncCampaignReport(b *testing.B) {
ctx := gctx.New()
syncService := NewSyncService()
req := &CampaignReportRequest{
AdvertiserID: 10001,
StartTime: time.Now().AddDate(0, 0, -30).UnixNano() / 1e6,
EndTime: time.Now().UnixNano() / 1e6,
SelectColumns: []string{"impression", "click", "cost"},
GroupType: 1,
QueryVersion: 1,
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, _ = syncService.SyncCampaignReport(ctx, req, true)
}
}