1、文档规范化
2、增加cid核心功能entity
This commit is contained in:
@@ -1,9 +1,10 @@
|
||||
# CID
|
||||
|
||||
CID 服务,承载两块业务:
|
||||
CID 服务,承载三块业务:
|
||||
|
||||
1. **内容审核(现有)**:将广告素材(图片/视频)提交易盾进行内容审核,提供 REST API、定时送检调度器和前端管理页
|
||||
2. **CID 归因平台(规划中)**:连接上游广告平台(抖音/小红书/快手等)与下游电商平台(京东/淘宝等),记录广告点击、归因转化、回传转化给广告平台
|
||||
2. **投放平台(规划中)**:外投对接广告平台营销 API(巨量/聚光/磁力等)建计划、传素材、启停调优、拉报表;自有 DSP 投自有媒体流量(PD 直投/程序化直购 + RTB 实时竞价)
|
||||
3. **CID 归因平台(规划中)**:连接投放与电商平台(京东/淘宝等),记录广告点击、归因转化、回传转化给广告平台(自有流量转化回流自身优化)
|
||||
|
||||
## 目录结构与业务模块
|
||||
|
||||
@@ -25,8 +26,13 @@ sql/ DDL 与迁移脚本
|
||||
| 模块 | 业务域 | 主要表 |
|
||||
|---|---|---|
|
||||
| check/ | 内容审核(现有):易盾检测提交/回调/轮询、素材校验、送检调度 | tencent_image、tencent_video、material_verify_log、tencent_content_check_log、tencent_account_relation |
|
||||
| account/ | 账户域(规划):广告主(客户)与广告账户(多级层级 + API 凭据 + 能力) | advertiser、ad_account |
|
||||
| delivery/ | 投放域(规划):计划树四层(计划/单元/广告/创意),本地实体 + 平台 ID 双轨同步 | campaign、ad_group、ad、creative |
|
||||
| material/ | 素材域(规划):素材文件库与多通道审核状态机 | material、material_audit |
|
||||
| dsp/ | 自有流量域(规划):自有媒体/广告位、PD 预订单、RTB 请求与展示计费 | media、ad_slot、placement、bid_request_log、auction_log |
|
||||
| attribution/ | 归因域(规划):点击记录、CID 生成、订单归因与转化回传(点击→转化→回传为同一链路) | click_log、conversion_order、report_task |
|
||||
| platform/ | 平台配置域(规划):上游广告平台/下游电商平台对接配置与商品映射 | ad_platform_account、commerce_platform_config、item_mapping |
|
||||
| platform/ | 平台配置域(规划):下游电商平台对接配置与商品映射 | commerce_platform_config、item_mapping |
|
||||
| report/ | 报表域(规划):统一消耗/效果汇总(外投平台报表 + DSP 聚合) | daily_report |
|
||||
|
||||
## 数据流
|
||||
|
||||
@@ -39,10 +45,21 @@ sql/ DDL 与迁移脚本
|
||||
→ 结果写入 material_verify_log(cid库) + 回填素材表审核状态
|
||||
```
|
||||
|
||||
**投放链路(规划)**:
|
||||
|
||||
```
|
||||
外投:CID 创建计划(campaign/单元/广告/创意,本地草稿)
|
||||
→ 同步平台营销 API(计划/单元/广告/创意/素材上传)
|
||||
→ 平台审核(素材审核状态机)→ 启停/调价 → 拉取平台报表 → daily_report
|
||||
|
||||
自有DSP:PD直投(placement 预订单)或 RTB 竞价(bid_request_log → auction_log)
|
||||
→ 展示/点击计费 → 点击进归因链路(channel=self_dsp)
|
||||
```
|
||||
|
||||
**CID 归因链路(规划)**:
|
||||
|
||||
```
|
||||
用户点击广告 → 广告平台跳转 CID(携带广告参数)
|
||||
用户点击广告 → 广告平台/自有媒体跳转 CID(携带广告参数)
|
||||
→ 生成 click_id、记录点击(click_log)→ 302 跳转电商平台(URL 带 click_id)
|
||||
→ 用户下单支付 → 电商平台回调 CID(订单 + click_id)
|
||||
→ 归因去重(conversion_order,幂等)→ 回传转化给广告平台(report_task 队列 + 失败重试)
|
||||
@@ -60,14 +77,20 @@ sql/ DDL 与迁移脚本
|
||||
| tencent_content_check_log | cid | 送检日志 |
|
||||
| tencent_account_relation | cid | 腾讯广告账户关系 |
|
||||
|
||||
### attribution / platform 模块(规划,DDL 见技术设计.md)
|
||||
### 投放/归因模块(规划,DDL 见技术设计.md)
|
||||
|
||||
| 表 | 库 | 说明 |
|
||||
|---|---|---|
|
||||
| advertiser | cid | 广告主(客户) |
|
||||
| ad_account | cid | 广告账户(多级层级 + API 凭据 + 能力,由 ad_platform_account 改造) |
|
||||
| campaign / ad_group / ad / creative | cid | 计划树四层(本地实体 + 平台 ID 双轨) |
|
||||
| material / material_audit | cid | 素材库 + 多通道审核状态机 |
|
||||
| media / ad_slot / placement | cid | 自有媒体/广告位/PD 预订单 |
|
||||
| bid_request_log / auction_log | cid | RTB 请求日志 / 展示计费(按 day 分区) |
|
||||
| click_log | cid | 点击日志(不可变事件),按 day 分区 |
|
||||
| conversion_order | cid | 转化订单(归因与回传状态) |
|
||||
| report_task | cid | 转化回传任务队列(失败退避重试) |
|
||||
| ad_platform_account | cid | 上游广告平台对接配置 |
|
||||
| daily_report | cid | 统一消耗/效果汇总(外投报表 + DSP 聚合) |
|
||||
| commerce_platform_config | cid | 下游电商平台对接配置 |
|
||||
| item_mapping | cid | 广告商品 ↔ 电商商品映射 |
|
||||
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
package account
|
||||
|
||||
import (
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// Advertiser 广告主实体(客户,多级/单层账户模式的业务主体)
|
||||
type Advertiser struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
Name string `orm:"name" json:"name" description:"广告主名称"`
|
||||
Industry string `orm:"industry" json:"industry" description:"行业"`
|
||||
ContactName string `orm:"contact_name" json:"contactName" description:"联系人"`
|
||||
ContactPhone string `orm:"contact_phone" json:"contactPhone" description:"联系电话"`
|
||||
Status int `orm:"status" json:"status" description:"状态 1启用 0停用"`
|
||||
Extra string `orm:"extra" json:"extra" description:"扩展配置(JSON)"`
|
||||
}
|
||||
|
||||
// AdvertiserCol 广告主表字段定义
|
||||
type AdvertiserCol struct {
|
||||
beans.SQLBaseCol
|
||||
Name string
|
||||
Industry string
|
||||
ContactName string
|
||||
ContactPhone string
|
||||
Status string
|
||||
Extra string
|
||||
}
|
||||
|
||||
// AdvertiserCols 广告主表字段常量
|
||||
var AdvertiserCols = AdvertiserCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
Name: "name",
|
||||
Industry: "industry",
|
||||
ContactName: "contact_name",
|
||||
ContactPhone: "contact_phone",
|
||||
Status: "status",
|
||||
Extra: "extra",
|
||||
}
|
||||
@@ -10,59 +10,74 @@ import (
|
||||
type ClickLog struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
ClickID string `orm:"click_id" json:"clickId" description:"对外CID"`
|
||||
RequestID string `orm:"request_id" json:"requestId" description:"广告平台请求号(去重键)"`
|
||||
AdPlatform string `orm:"ad_platform" json:"adPlatform" description:"广告平台 douyin/xiaohongshu/kuaishou"`
|
||||
AccountID string `orm:"account_id" json:"accountId" description:"广告账户ID"`
|
||||
CampaignID string `orm:"campaign_id" json:"campaignId" description:"广告计划ID"`
|
||||
AdGroupID string `orm:"ad_group_id" json:"adGroupId" description:"广告组ID"`
|
||||
AdID string `orm:"ad_id" json:"adId" description:"广告ID"`
|
||||
CreativeID string `orm:"creative_id" json:"creativeId" description:"创意ID"`
|
||||
DeviceID string `orm:"device_id" json:"deviceId" description:"设备ID oaid/imei/idfa"`
|
||||
IP string `orm:"ip" json:"ip" description:"点击IP"`
|
||||
UA string `orm:"ua" json:"ua" description:"用户代理"`
|
||||
LandingURL string `orm:"landing_url" json:"landingUrl" description:"原始落地页URL"`
|
||||
RedirectURL string `orm:"redirect_url" json:"redirectUrl" description:"跳转下游URL(带click_id)"`
|
||||
Extra string `orm:"extra" json:"extra" description:"平台透传参数(JSON)"`
|
||||
Day *gtime.Time `orm:"day" json:"day" description:"分区日期"`
|
||||
ClickID string `orm:"click_id" json:"clickId" description:"对外CID"`
|
||||
RequestID string `orm:"request_id" json:"requestId" description:"广告平台请求号(去重键)"`
|
||||
Channel string `orm:"channel" json:"channel" description:"投放通道 external外投/self_dsp自有"`
|
||||
AdPlatform string `orm:"ad_platform" json:"adPlatform" description:"广告平台 douyin/xiaohongshu/kuaishou/self_dsp"`
|
||||
AccountID string `orm:"account_id" json:"accountId" description:"广告账户ID"`
|
||||
CampaignID string `orm:"campaign_id" json:"campaignId" description:"平台侧计划ID(透传)"`
|
||||
LocalCampaignID int64 `orm:"local_campaign_id" json:"localCampaignId" description:"本地计划ID(回传路由)"`
|
||||
LocalAdID int64 `orm:"local_ad_id" json:"localAdId" description:"本地广告ID(DSP必填,外投按映射回填)"`
|
||||
LocalCreativeID int64 `orm:"local_creative_id" json:"localCreativeId" description:"本地创意ID(DSP必填,外投按映射回填)"`
|
||||
SlotID int64 `orm:"slot_id" json:"slotId" description:"自有DSP广告位ID"`
|
||||
AdGroupID string `orm:"ad_group_id" json:"adGroupId" description:"广告组ID"`
|
||||
AdID string `orm:"ad_id" json:"adId" description:"广告ID"`
|
||||
CreativeID string `orm:"creative_id" json:"creativeId" description:"创意ID"`
|
||||
DeviceID string `orm:"device_id" json:"deviceId" description:"设备ID oaid/imei/idfa"`
|
||||
IP string `orm:"ip" json:"ip" description:"点击IP"`
|
||||
UA string `orm:"ua" json:"ua" description:"用户代理"`
|
||||
LandingURL string `orm:"landing_url" json:"landingUrl" description:"原始落地页URL"`
|
||||
RedirectURL string `orm:"redirect_url" json:"redirectUrl" description:"跳转下游URL(带click_id)"`
|
||||
Extra string `orm:"extra" json:"extra" description:"平台透传参数(JSON)"`
|
||||
Day *gtime.Time `orm:"day" json:"day" description:"分区日期"`
|
||||
}
|
||||
|
||||
// ClickLogCol 点击日志表字段定义
|
||||
type ClickLogCol struct {
|
||||
beans.SQLBaseCol
|
||||
ClickID string
|
||||
RequestID string
|
||||
AdPlatform string
|
||||
AccountID string
|
||||
CampaignID string
|
||||
AdGroupID string
|
||||
AdID string
|
||||
CreativeID string
|
||||
DeviceID string
|
||||
IP string
|
||||
UA string
|
||||
LandingURL string
|
||||
RedirectURL string
|
||||
Extra string
|
||||
Day string
|
||||
ClickID string
|
||||
RequestID string
|
||||
Channel string
|
||||
AdPlatform string
|
||||
AccountID string
|
||||
CampaignID string
|
||||
LocalCampaignID string
|
||||
LocalAdID string
|
||||
LocalCreativeID string
|
||||
SlotID string
|
||||
AdGroupID string
|
||||
AdID string
|
||||
CreativeID string
|
||||
DeviceID string
|
||||
IP string
|
||||
UA string
|
||||
LandingURL string
|
||||
RedirectURL string
|
||||
Extra string
|
||||
Day string
|
||||
}
|
||||
|
||||
// ClickLogCols 点击日志表字段常量
|
||||
var ClickLogCols = ClickLogCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
ClickID: "click_id",
|
||||
RequestID: "request_id",
|
||||
AdPlatform: "ad_platform",
|
||||
AccountID: "account_id",
|
||||
CampaignID: "campaign_id",
|
||||
AdGroupID: "ad_group_id",
|
||||
AdID: "ad_id",
|
||||
CreativeID: "creative_id",
|
||||
DeviceID: "device_id",
|
||||
IP: "ip",
|
||||
UA: "ua",
|
||||
LandingURL: "landing_url",
|
||||
RedirectURL: "redirect_url",
|
||||
Extra: "extra",
|
||||
Day: "day",
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
ClickID: "click_id",
|
||||
RequestID: "request_id",
|
||||
Channel: "channel",
|
||||
AdPlatform: "ad_platform",
|
||||
AccountID: "account_id",
|
||||
CampaignID: "campaign_id",
|
||||
LocalCampaignID: "local_campaign_id",
|
||||
LocalAdID: "local_ad_id",
|
||||
LocalCreativeID: "local_creative_id",
|
||||
SlotID: "slot_id",
|
||||
AdGroupID: "ad_group_id",
|
||||
AdID: "ad_id",
|
||||
CreativeID: "creative_id",
|
||||
DeviceID: "device_id",
|
||||
IP: "ip",
|
||||
UA: "ua",
|
||||
LandingURL: "landing_url",
|
||||
RedirectURL: "redirect_url",
|
||||
Extra: "extra",
|
||||
Day: "day",
|
||||
}
|
||||
|
||||
@@ -12,6 +12,8 @@ type ConversionOrder struct {
|
||||
OrderID string `orm:"order_id" json:"orderId" description:"电商平台订单号"`
|
||||
CommercePlatform string `orm:"commerce_platform" json:"commercePlatform" description:"电商平台 jd/taobao"`
|
||||
ClickID string `orm:"click_id" json:"clickId" description:"关联点击CID"`
|
||||
LocalCampaignID int64 `orm:"local_campaign_id" json:"localCampaignId" description:"本地计划ID(回传路由)"`
|
||||
AdAccountID int64 `orm:"ad_account_id" json:"adAccountId" description:"广告账户ID(回传凭据)"`
|
||||
DeviceID string `orm:"device_id" json:"deviceId" description:"设备ID(无click_id时兜底归因)"`
|
||||
ItemID string `orm:"item_id" json:"itemId" description:"商品ID"`
|
||||
ItemName string `orm:"item_name" json:"itemName" description:"商品名称"`
|
||||
@@ -34,6 +36,8 @@ type ConversionOrderCol struct {
|
||||
OrderID string
|
||||
CommercePlatform string
|
||||
ClickID string
|
||||
LocalCampaignID string
|
||||
AdAccountID string
|
||||
DeviceID string
|
||||
ItemID string
|
||||
ItemName string
|
||||
@@ -56,6 +60,8 @@ var ConversionOrderCols = ConversionOrderCol{
|
||||
OrderID: "order_id",
|
||||
CommercePlatform: "commerce_platform",
|
||||
ClickID: "click_id",
|
||||
LocalCampaignID: "local_campaign_id",
|
||||
AdAccountID: "ad_account_id",
|
||||
DeviceID: "device_id",
|
||||
ItemID: "item_id",
|
||||
ItemName: "item_name",
|
||||
|
||||
@@ -11,6 +11,8 @@ type ReportTask struct {
|
||||
ConversionID string `orm:"conversion_id" json:"conversionId" description:"关联转化事件ID"`
|
||||
ClickID string `orm:"click_id" json:"clickId" description:"点击CID"`
|
||||
AdPlatform string `orm:"ad_platform" json:"adPlatform" description:"回传目标广告平台"`
|
||||
AdAccountID int64 `orm:"ad_account_id" json:"adAccountId" description:"广告账户ID(回传凭据)"`
|
||||
CampaignID int64 `orm:"campaign_id" json:"campaignId" description:"回传目标本地计划ID"`
|
||||
TaskType string `orm:"task_type" json:"taskType" description:"回传类型 conversion/refund"`
|
||||
Status int `orm:"status" json:"status" description:"状态 0待回传 1回传中 2成功 3失败"`
|
||||
RetryCount int `orm:"retry_count" json:"retryCount" description:"重试次数"`
|
||||
@@ -26,6 +28,8 @@ type ReportTaskCol struct {
|
||||
ConversionID string
|
||||
ClickID string
|
||||
AdPlatform string
|
||||
AdAccountID string
|
||||
CampaignID string
|
||||
TaskType string
|
||||
Status string
|
||||
RetryCount string
|
||||
@@ -41,6 +45,8 @@ var ReportTaskCols = ReportTaskCol{
|
||||
ConversionID: "conversion_id",
|
||||
ClickID: "click_id",
|
||||
AdPlatform: "ad_platform",
|
||||
AdAccountID: "ad_account_id",
|
||||
CampaignID: "campaign_id",
|
||||
TaskType: "task_type",
|
||||
Status: "status",
|
||||
RetryCount: "retry_count",
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package delivery
|
||||
|
||||
import (
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// Ad 广告实体(计划树第三层)
|
||||
type Ad struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
AdGroupID int64 `orm:"ad_group_id" json:"adGroupId" description:"所属单元ID"`
|
||||
Name string `orm:"name" json:"name" description:"广告名称"`
|
||||
PlatformAdID string `orm:"platform_ad_id" json:"platformAdId" description:"平台侧广告ID"`
|
||||
Status string `orm:"status" json:"status" description:"本地状态 draft/active/paused/ended/rejected"`
|
||||
PlatformStatus string `orm:"platform_status" json:"platformStatus" description:"平台侧状态快照"`
|
||||
SyncStatus string `orm:"sync_status" json:"syncStatus" description:"同步状态 none/pending/synced/failed"`
|
||||
Extra string `orm:"extra" json:"extra" description:"平台特有扩展(JSON)"`
|
||||
}
|
||||
|
||||
// AdCol 广告表字段定义
|
||||
type AdCol struct {
|
||||
beans.SQLBaseCol
|
||||
AdGroupID string
|
||||
Name string
|
||||
PlatformAdID string
|
||||
Status string
|
||||
PlatformStatus string
|
||||
SyncStatus string
|
||||
Extra string
|
||||
}
|
||||
|
||||
// AdCols 广告表字段常量
|
||||
var AdCols = AdCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
AdGroupID: "ad_group_id",
|
||||
Name: "name",
|
||||
PlatformAdID: "platform_ad_id",
|
||||
Status: "status",
|
||||
PlatformStatus: "platform_status",
|
||||
SyncStatus: "sync_status",
|
||||
Extra: "extra",
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package delivery
|
||||
|
||||
import (
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// AdGroup 广告单元实体(计划树第二层,定向+出价)
|
||||
type AdGroup struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
CampaignID int64 `orm:"campaign_id" json:"campaignId" description:"所属计划ID"`
|
||||
Name string `orm:"name" json:"name" description:"单元名称"`
|
||||
PlatformAdGroupID string `orm:"platform_ad_group_id" json:"platformAdGroupId" description:"平台侧单元ID"`
|
||||
Targeting string `orm:"targeting" json:"targeting" description:"定向(JSON) 地域/人群/时段"`
|
||||
BidType string `orm:"bid_type" json:"bidType" description:"出价方式 cpm/ocpm/cpc/ocpc"`
|
||||
BidAmount int64 `orm:"bid_amount" json:"bidAmount" description:"出价(分,cpm按千次展示)"`
|
||||
Status string `orm:"status" json:"status" description:"本地状态 draft/active/paused/ended/rejected"`
|
||||
PlatformStatus string `orm:"platform_status" json:"platformStatus" description:"平台侧状态快照"`
|
||||
SyncStatus string `orm:"sync_status" json:"syncStatus" description:"同步状态 none/pending/synced/failed"`
|
||||
Extra string `orm:"extra" json:"extra" description:"平台特有扩展(JSON)"`
|
||||
}
|
||||
|
||||
// AdGroupCol 广告单元表字段定义
|
||||
type AdGroupCol struct {
|
||||
beans.SQLBaseCol
|
||||
CampaignID string
|
||||
Name string
|
||||
PlatformAdGroupID string
|
||||
Targeting string
|
||||
BidType string
|
||||
BidAmount string
|
||||
Status string
|
||||
PlatformStatus string
|
||||
SyncStatus string
|
||||
Extra string
|
||||
}
|
||||
|
||||
// AdGroupCols 广告单元表字段常量
|
||||
var AdGroupCols = AdGroupCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
CampaignID: "campaign_id",
|
||||
Name: "name",
|
||||
PlatformAdGroupID: "platform_ad_group_id",
|
||||
Targeting: "targeting",
|
||||
BidType: "bid_type",
|
||||
BidAmount: "bid_amount",
|
||||
Status: "status",
|
||||
PlatformStatus: "platform_status",
|
||||
SyncStatus: "sync_status",
|
||||
Extra: "extra",
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package delivery
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// Campaign 广告计划实体(计划树第一层,外投/自有DSP共用,预算在计划层)
|
||||
type Campaign struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
AdvertiserID int64 `orm:"advertiser_id" json:"advertiserId" description:"广告主ID"`
|
||||
AdAccountID int64 `orm:"ad_account_id" json:"adAccountId" description:"本地广告账户ID"`
|
||||
Name string `orm:"name" json:"name" description:"计划名称"`
|
||||
Channel string `orm:"channel" json:"channel" description:"投放通道 external外投/self_dsp自有"`
|
||||
Platform string `orm:"platform" json:"platform" description:"广告平台 douyin/xiaohongshu/kuaishou"`
|
||||
PlatformCampaignID string `orm:"platform_campaign_id" json:"platformCampaignId" description:"平台侧计划ID"`
|
||||
Status string `orm:"status" json:"status" description:"本地状态 draft/active/paused/ended/rejected"`
|
||||
PlatformStatus string `orm:"platform_status" json:"platformStatus" description:"平台侧状态快照"`
|
||||
SyncStatus string `orm:"sync_status" json:"syncStatus" description:"同步状态 none/pending/synced/failed"`
|
||||
BudgetMode string `orm:"budget_mode" json:"budgetMode" description:"预算方式 free不限/daily日预算"`
|
||||
DailyBudget *int64 `orm:"daily_budget" json:"dailyBudget" description:"日预算(分),budget_mode=daily时必填,否则NULL"`
|
||||
TotalBudget *int64 `orm:"total_budget" json:"totalBudget" description:"总预算(分),NULL=不限"`
|
||||
OptimizeGoal string `orm:"optimize_goal" json:"optimizeGoal" description:"优化目标 conversion/click/exposure"`
|
||||
StartTime *gtime.Time `orm:"start_time" json:"startTime" description:"投放开始时间"`
|
||||
EndTime *gtime.Time `orm:"end_time" json:"endTime" description:"投放结束时间"`
|
||||
Extra string `orm:"extra" json:"extra" description:"平台特有扩展(JSON)"`
|
||||
}
|
||||
|
||||
// CampaignCol 广告计划表字段定义
|
||||
type CampaignCol struct {
|
||||
beans.SQLBaseCol
|
||||
AdvertiserID string
|
||||
AdAccountID string
|
||||
Name string
|
||||
Channel string
|
||||
Platform string
|
||||
PlatformCampaignID string
|
||||
Status string
|
||||
PlatformStatus string
|
||||
SyncStatus string
|
||||
BudgetMode string
|
||||
DailyBudget string
|
||||
TotalBudget string
|
||||
OptimizeGoal string
|
||||
StartTime string
|
||||
EndTime string
|
||||
Extra string
|
||||
}
|
||||
|
||||
// CampaignCols 广告计划表字段常量
|
||||
var CampaignCols = CampaignCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
AdvertiserID: "advertiser_id",
|
||||
AdAccountID: "ad_account_id",
|
||||
Name: "name",
|
||||
Channel: "channel",
|
||||
Platform: "platform",
|
||||
PlatformCampaignID: "platform_campaign_id",
|
||||
Status: "status",
|
||||
PlatformStatus: "platform_status",
|
||||
SyncStatus: "sync_status",
|
||||
BudgetMode: "budget_mode",
|
||||
DailyBudget: "daily_budget",
|
||||
TotalBudget: "total_budget",
|
||||
OptimizeGoal: "optimize_goal",
|
||||
StartTime: "start_time",
|
||||
EndTime: "end_time",
|
||||
Extra: "extra",
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
package delivery
|
||||
|
||||
import (
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// Creative 创意实体(计划树第四层,引用素材库,落地页带click_id模板)
|
||||
type Creative struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
AdID int64 `orm:"ad_id" json:"adId" description:"所属广告ID(部分平台创意先于广告,可空)"`
|
||||
CampaignID int64 `orm:"campaign_id" json:"campaignId" description:"所属计划ID(冗余,ad_id为空时链路不断)"`
|
||||
AdvertiserID int64 `orm:"advertiser_id" json:"advertiserId" description:"广告主ID"`
|
||||
Name string `orm:"name" json:"name" description:"创意名称"`
|
||||
MaterialID int64 `orm:"material_id" json:"materialId" description:"素材ID(material.id)"`
|
||||
Title string `orm:"title" json:"title" description:"标题"`
|
||||
Description string `orm:"description" json:"description" description:"描述"`
|
||||
ClickURL string `orm:"click_url" json:"clickUrl" description:"落地页URL(含click_id模板)"`
|
||||
PlatformCreativeID string `orm:"platform_creative_id" json:"platformCreativeId" description:"平台侧创意ID"`
|
||||
PlatformMaterialID string `orm:"platform_material_id" json:"platformMaterialId" description:"平台侧素材ID"`
|
||||
AuditStatus string `orm:"audit_status" json:"auditStatus" description:"审核状态 pending/pass/reject"`
|
||||
Status string `orm:"status" json:"status" description:"本地状态 draft/active/paused/ended/rejected"`
|
||||
PlatformStatus string `orm:"platform_status" json:"platformStatus" description:"平台侧状态快照"`
|
||||
SyncStatus string `orm:"sync_status" json:"syncStatus" description:"同步状态 none/pending/synced/failed"`
|
||||
Extra string `orm:"extra" json:"extra" description:"平台特有扩展(JSON)"`
|
||||
}
|
||||
|
||||
// CreativeCol 创意表字段定义
|
||||
type CreativeCol struct {
|
||||
beans.SQLBaseCol
|
||||
AdID string
|
||||
CampaignID string
|
||||
AdvertiserID string
|
||||
Name string
|
||||
MaterialID string
|
||||
Title string
|
||||
Description string
|
||||
ClickURL string
|
||||
PlatformCreativeID string
|
||||
PlatformMaterialID string
|
||||
AuditStatus string
|
||||
Status string
|
||||
PlatformStatus string
|
||||
SyncStatus string
|
||||
Extra string
|
||||
}
|
||||
|
||||
// CreativeCols 创意表字段常量
|
||||
var CreativeCols = CreativeCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
AdID: "ad_id",
|
||||
CampaignID: "campaign_id",
|
||||
AdvertiserID: "advertiser_id",
|
||||
Name: "name",
|
||||
MaterialID: "material_id",
|
||||
Title: "title",
|
||||
Description: "description",
|
||||
ClickURL: "click_url",
|
||||
PlatformCreativeID: "platform_creative_id",
|
||||
PlatformMaterialID: "platform_material_id",
|
||||
AuditStatus: "audit_status",
|
||||
Status: "status",
|
||||
PlatformStatus: "platform_status",
|
||||
SyncStatus: "sync_status",
|
||||
Extra: "extra",
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package dsp
|
||||
|
||||
import (
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// AdSlot 广告位实体(自有媒体广告位,售卖给计划)
|
||||
type AdSlot struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
MediaID int64 `orm:"media_id" json:"mediaId" description:"媒体ID"`
|
||||
Name string `orm:"name" json:"name" description:"广告位名称"`
|
||||
SlotType string `orm:"slot_type" json:"slotType" description:"广告位类型 banner/feed/video/interstitial/reward"`
|
||||
Size string `orm:"size" json:"size" description:"尺寸"`
|
||||
Unit string `orm:"unit" json:"unit" description:"计费方式 cpm/cpc/cpa"`
|
||||
BasePrice int64 `orm:"base_price" json:"basePrice" description:"底价(分,cpm按千次)"`
|
||||
Status int `orm:"status" json:"status" description:"状态 1可售 0停售"`
|
||||
Extra string `orm:"extra" json:"extra" description:"扩展(JSON) 定向能力声明等"`
|
||||
}
|
||||
|
||||
// AdSlotCol 广告位表字段定义
|
||||
type AdSlotCol struct {
|
||||
beans.SQLBaseCol
|
||||
MediaID string
|
||||
Name string
|
||||
SlotType string
|
||||
Size string
|
||||
Unit string
|
||||
BasePrice string
|
||||
Status string
|
||||
Extra string
|
||||
}
|
||||
|
||||
// AdSlotCols 广告位表字段常量
|
||||
var AdSlotCols = AdSlotCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
MediaID: "media_id",
|
||||
Name: "name",
|
||||
SlotType: "slot_type",
|
||||
Size: "size",
|
||||
Unit: "unit",
|
||||
BasePrice: "base_price",
|
||||
Status: "status",
|
||||
Extra: "extra",
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package dsp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// AuctionLog 展示计费日志实体(PD与RTB统一入口,按day分区)
|
||||
type AuctionLog struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
RequestID string `orm:"request_id" json:"requestId" description:"请求ID(RTB)/流水号(PD)"`
|
||||
CampaignID int64 `orm:"campaign_id" json:"campaignId" description:"本地计划ID"`
|
||||
AdID int64 `orm:"ad_id" json:"adId" description:"广告ID"`
|
||||
SlotID int64 `orm:"slot_id" json:"slotId" description:"广告位ID"`
|
||||
Event string `orm:"event" json:"event" description:"事件 show/click"`
|
||||
Price int64 `orm:"price" json:"price" description:"成交价(分,cpm按千次)"`
|
||||
Cost int64 `orm:"cost" json:"cost" description:"本次事件应计费用(分,写入时算好)"`
|
||||
DeviceID string `orm:"device_id" json:"deviceId" description:"设备ID"`
|
||||
IP string `orm:"ip" json:"ip" description:"请求IP"`
|
||||
Day *gtime.Time `orm:"day" json:"day" description:"分区日期"`
|
||||
}
|
||||
|
||||
// AuctionLogCol 展示计费日志表字段定义
|
||||
type AuctionLogCol struct {
|
||||
beans.SQLBaseCol
|
||||
RequestID string
|
||||
CampaignID string
|
||||
AdID string
|
||||
SlotID string
|
||||
Event string
|
||||
Price string
|
||||
Cost string
|
||||
DeviceID string
|
||||
IP string
|
||||
Day string
|
||||
}
|
||||
|
||||
// AuctionLogCols 展示计费日志表字段常量
|
||||
var AuctionLogCols = AuctionLogCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
RequestID: "request_id",
|
||||
CampaignID: "campaign_id",
|
||||
AdID: "ad_id",
|
||||
SlotID: "slot_id",
|
||||
Event: "event",
|
||||
Price: "price",
|
||||
Cost: "cost",
|
||||
DeviceID: "device_id",
|
||||
IP: "ip",
|
||||
Day: "day",
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package dsp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// BidRequestLog RTB请求日志实体(高并发,按day分区,可裁剪)
|
||||
type BidRequestLog struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
RequestID string `orm:"request_id" json:"requestId" description:"请求ID(去重键)"`
|
||||
SlotID int64 `orm:"slot_id" json:"slotId" description:"广告位ID"`
|
||||
DeviceID string `orm:"device_id" json:"deviceId" description:"设备ID"`
|
||||
IP string `orm:"ip" json:"ip" description:"请求IP"`
|
||||
UA string `orm:"ua" json:"ua" description:"用户代理"`
|
||||
Extra string `orm:"extra" json:"extra" description:"上下文/定向特征(JSON)"`
|
||||
Day *gtime.Time `orm:"day" json:"day" description:"分区日期"`
|
||||
}
|
||||
|
||||
// BidRequestLogCol RTB请求日志表字段定义
|
||||
type BidRequestLogCol struct {
|
||||
beans.SQLBaseCol
|
||||
RequestID string
|
||||
SlotID string
|
||||
DeviceID string
|
||||
IP string
|
||||
UA string
|
||||
Extra string
|
||||
Day string
|
||||
}
|
||||
|
||||
// BidRequestLogCols RTB请求日志表字段常量
|
||||
var BidRequestLogCols = BidRequestLogCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
RequestID: "request_id",
|
||||
SlotID: "slot_id",
|
||||
DeviceID: "device_id",
|
||||
IP: "ip",
|
||||
UA: "ua",
|
||||
Extra: "extra",
|
||||
Day: "day",
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package dsp
|
||||
|
||||
import (
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// Media 媒体实体(自有媒体,公司流量侧)
|
||||
type Media struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
Name string `orm:"name" json:"name" description:"媒体名称"`
|
||||
Type string `orm:"type" json:"type" description:"媒体类型 app/web"`
|
||||
Domain string `orm:"domain" json:"domain" description:"域名"`
|
||||
Status int `orm:"status" json:"status" description:"状态 1启用 0停用"`
|
||||
}
|
||||
|
||||
// MediaCol 媒体表字段定义
|
||||
type MediaCol struct {
|
||||
beans.SQLBaseCol
|
||||
Name string
|
||||
Type string
|
||||
Domain string
|
||||
Status string
|
||||
}
|
||||
|
||||
// MediaCols 媒体表字段常量
|
||||
var MediaCols = MediaCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
Name: "name",
|
||||
Type: "type",
|
||||
Domain: "domain",
|
||||
Status: "status",
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
package dsp
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// Placement 预订单实体(PD直投/程序化直购:计划×广告位预订)
|
||||
type Placement struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
AdSlotID int64 `orm:"ad_slot_id" json:"adSlotId" description:"广告位ID"`
|
||||
CampaignID int64 `orm:"campaign_id" json:"campaignId" description:"本地计划ID"`
|
||||
Quota int `orm:"quota" json:"quota" description:"预订量(展示次数)"`
|
||||
Price int64 `orm:"price" json:"price" description:"协议价(分,按unit口径)"`
|
||||
BookStart *gtime.Time `orm:"book_start" json:"bookStart" description:"预订开始时间"`
|
||||
BookEnd *gtime.Time `orm:"book_end" json:"bookEnd" description:"预订结束时间"`
|
||||
Status string `orm:"status" json:"status" description:"状态 pending/running/finished/ended"`
|
||||
DeliveredCount int `orm:"delivered_count" json:"deliveredCount" description:"已投展示数"`
|
||||
}
|
||||
|
||||
// PlacementCol 预订单表字段定义
|
||||
type PlacementCol struct {
|
||||
beans.SQLBaseCol
|
||||
AdSlotID string
|
||||
CampaignID string
|
||||
Quota string
|
||||
Price string
|
||||
BookStart string
|
||||
BookEnd string
|
||||
Status string
|
||||
DeliveredCount string
|
||||
}
|
||||
|
||||
// PlacementCols 预订单表字段常量
|
||||
var PlacementCols = PlacementCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
AdSlotID: "ad_slot_id",
|
||||
CampaignID: "campaign_id",
|
||||
Quota: "quota",
|
||||
Price: "price",
|
||||
BookStart: "book_start",
|
||||
BookEnd: "book_end",
|
||||
Status: "status",
|
||||
DeliveredCount: "delivered_count",
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package material
|
||||
|
||||
import (
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// Material 素材实体(素材文件库,md5去重)
|
||||
type Material struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
AdvertiserID int64 `orm:"advertiser_id" json:"advertiserId" description:"广告主ID"`
|
||||
Type string `orm:"type" json:"type" description:"素材类型 image/video/text"`
|
||||
Title string `orm:"title" json:"title" description:"素材标题"`
|
||||
FilePath string `orm:"file_path" json:"filePath" description:"本地存储路径"`
|
||||
FileSize int64 `orm:"file_size" json:"fileSize" description:"文件大小(字节)"`
|
||||
MD5 string `orm:"md5" json:"md5" description:"文件MD5(去重键)"`
|
||||
Width int `orm:"width" json:"width" description:"宽度(像素)"`
|
||||
Height int `orm:"height" json:"height" description:"高度(像素)"`
|
||||
Duration int `orm:"duration" json:"duration" description:"视频时长(秒)"`
|
||||
Format string `orm:"format" json:"format" description:"文件格式"`
|
||||
AuditStatus string `orm:"audit_status" json:"auditStatus" description:"审核状态 pending/auditing/pass/reject/disabled"`
|
||||
Status int `orm:"status" json:"status" description:"状态 1可用 0禁用"`
|
||||
Extra string `orm:"extra" json:"extra" description:"扩展(JSON) 如平台素材ID映射"`
|
||||
}
|
||||
|
||||
// MaterialCol 素材表字段定义
|
||||
type MaterialCol struct {
|
||||
beans.SQLBaseCol
|
||||
AdvertiserID string
|
||||
Type string
|
||||
Title string
|
||||
FilePath string
|
||||
FileSize string
|
||||
MD5 string
|
||||
Width string
|
||||
Height string
|
||||
Duration string
|
||||
Format string
|
||||
AuditStatus string
|
||||
Status string
|
||||
Extra string
|
||||
}
|
||||
|
||||
// MaterialCols 素材表字段常量
|
||||
var MaterialCols = MaterialCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
AdvertiserID: "advertiser_id",
|
||||
Type: "type",
|
||||
Title: "title",
|
||||
FilePath: "file_path",
|
||||
FileSize: "file_size",
|
||||
MD5: "md5",
|
||||
Width: "width",
|
||||
Height: "height",
|
||||
Duration: "duration",
|
||||
Format: "format",
|
||||
AuditStatus: "audit_status",
|
||||
Status: "status",
|
||||
Extra: "extra",
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package material
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// MaterialAudit 素材审核记录实体(审核状态机全量日志,多通道)
|
||||
type MaterialAudit struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
MaterialID int64 `orm:"material_id" json:"materialId" description:"素材ID"`
|
||||
AuditChannel string `orm:"audit_channel" json:"auditChannel" description:"审核通道 yidun/platform/manual"`
|
||||
AuditAction string `orm:"audit_action" json:"auditAction" description:"审核动作 submit/pass/reject/revoke"`
|
||||
AuditStatus string `orm:"audit_status" json:"auditStatus" description:"审核状态"`
|
||||
Result string `orm:"result" json:"result" description:"平台返回详情(JSON) 原因/违规项"`
|
||||
Operator string `orm:"operator" json:"operator" description:"操作人"`
|
||||
AuditTime *gtime.Time `orm:"audit_time" json:"auditTime" description:"审核时间"`
|
||||
}
|
||||
|
||||
// MaterialAuditCol 素材审核记录表字段定义
|
||||
type MaterialAuditCol struct {
|
||||
beans.SQLBaseCol
|
||||
MaterialID string
|
||||
AuditChannel string
|
||||
AuditAction string
|
||||
AuditStatus string
|
||||
Result string
|
||||
Operator string
|
||||
AuditTime string
|
||||
}
|
||||
|
||||
// MaterialAuditCols 素材审核记录表字段常量
|
||||
var MaterialAuditCols = MaterialAuditCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
MaterialID: "material_id",
|
||||
AuditChannel: "audit_channel",
|
||||
AuditAction: "audit_action",
|
||||
AuditStatus: "audit_status",
|
||||
Result: "result",
|
||||
Operator: "operator",
|
||||
AuditTime: "audit_time",
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// AdAccount 广告账户实体(平台凭据 + 多级层级 + API能力标识)
|
||||
type AdAccount struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
AdvertiserID int64 `orm:"advertiser_id" json:"advertiserId" description:"归属广告主ID"`
|
||||
Name string `orm:"name" json:"name" description:"账户显示名"`
|
||||
Platform string `orm:"platform" json:"platform" description:"广告平台 douyin/xiaohongshu/kuaishou"`
|
||||
AccountID string `orm:"account_id" json:"accountId" description:"平台侧账户ID"`
|
||||
ParentAccountID int64 `orm:"parent_account_id" json:"parentAccountId" description:"上级账户ID(多级代理层级,自引用)"`
|
||||
AccountRole string `orm:"account_role" json:"accountRole" description:"账户角色 agent/owner/sub"`
|
||||
Capabilities string `orm:"capabilities" json:"capabilities" description:"API能力(JSON) attribution/delivery/report"`
|
||||
AppID string `orm:"app_id" json:"appId" description:"应用ID"`
|
||||
AppSecret string `orm:"app_secret" json:"appSecret" description:"应用密钥"`
|
||||
AccessToken string `orm:"access_token" json:"accessToken" description:"访问令牌"`
|
||||
TokenExpireAt int64 `orm:"token_expire_at" json:"tokenExpireAt" description:"令牌过期时间戳"`
|
||||
Config string `orm:"config" json:"config" description:"平台特定配置(JSON) 含回传事件映射等"`
|
||||
Enabled bool `orm:"enabled" json:"enabled" description:"是否启用"`
|
||||
}
|
||||
|
||||
// AdAccountCol 广告账户表字段定义
|
||||
type AdAccountCol struct {
|
||||
beans.SQLBaseCol
|
||||
AdvertiserID string
|
||||
Name string
|
||||
Platform string
|
||||
AccountID string
|
||||
ParentAccountID string
|
||||
AccountRole string
|
||||
Capabilities string
|
||||
AppID string
|
||||
AppSecret string
|
||||
AccessToken string
|
||||
TokenExpireAt string
|
||||
Config string
|
||||
Enabled string
|
||||
}
|
||||
|
||||
// AdAccountCols 广告账户表字段常量
|
||||
var AdAccountCols = AdAccountCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
AdvertiserID: "advertiser_id",
|
||||
Name: "name",
|
||||
Platform: "platform",
|
||||
AccountID: "account_id",
|
||||
ParentAccountID: "parent_account_id",
|
||||
AccountRole: "account_role",
|
||||
Capabilities: "capabilities",
|
||||
AppID: "app_id",
|
||||
AppSecret: "app_secret",
|
||||
AccessToken: "access_token",
|
||||
TokenExpireAt: "token_expire_at",
|
||||
Config: "config",
|
||||
Enabled: "enabled",
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
package platform
|
||||
|
||||
import (
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// AdPlatformAccount 广告平台账户配置实体(上游广告平台对接凭据)
|
||||
type AdPlatformAccount struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
Platform string `orm:"platform" json:"platform" description:"广告平台 douyin/xiaohongshu/kuaishou"`
|
||||
AccountID string `orm:"account_id" json:"accountId" description:"平台侧账户ID"`
|
||||
AppID string `orm:"app_id" json:"appId" description:"应用ID"`
|
||||
AppSecret string `orm:"app_secret" json:"appSecret" description:"应用密钥"`
|
||||
AccessToken string `orm:"access_token" json:"accessToken" description:"访问令牌"`
|
||||
TokenExpireAt int64 `orm:"token_expire_at" json:"tokenExpireAt" description:"令牌过期时间戳"`
|
||||
Config string `orm:"config" json:"config" description:"平台特定配置(JSON)"`
|
||||
Enabled bool `orm:"enabled" json:"enabled" description:"是否启用"`
|
||||
}
|
||||
|
||||
// AdPlatformAccountCol 广告平台账户表字段定义
|
||||
type AdPlatformAccountCol struct {
|
||||
beans.SQLBaseCol
|
||||
Platform string
|
||||
AccountID string
|
||||
AppID string
|
||||
AppSecret string
|
||||
AccessToken string
|
||||
TokenExpireAt string
|
||||
Config string
|
||||
Enabled string
|
||||
}
|
||||
|
||||
// AdPlatformAccountCols 广告平台账户表字段常量
|
||||
var AdPlatformAccountCols = AdPlatformAccountCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
Platform: "platform",
|
||||
AccountID: "account_id",
|
||||
AppID: "app_id",
|
||||
AppSecret: "app_secret",
|
||||
AccessToken: "access_token",
|
||||
TokenExpireAt: "token_expire_at",
|
||||
Config: "config",
|
||||
Enabled: "enabled",
|
||||
}
|
||||
@@ -8,32 +8,38 @@ import (
|
||||
type CommercePlatformConfig struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
Platform string `orm:"platform" json:"platform" description:"电商平台 jd/taobao"`
|
||||
AppKey string `orm:"app_key" json:"appKey" description:"应用Key"`
|
||||
AppSecret string `orm:"app_secret" json:"appSecret" description:"应用密钥"`
|
||||
CallbackURL string `orm:"callback_url" json:"callbackUrl" description:"订单回调接收URL"`
|
||||
Config string `orm:"config" json:"config" description:"平台特定配置(JSON)"`
|
||||
Enabled bool `orm:"enabled" json:"enabled" description:"是否启用"`
|
||||
AdvertiserID int64 `orm:"advertiser_id" json:"advertiserId" description:"归属广告主ID"`
|
||||
Name string `orm:"name" json:"name" description:"配置显示名"`
|
||||
Platform string `orm:"platform" json:"platform" description:"电商平台 jd/taobao"`
|
||||
AppKey string `orm:"app_key" json:"appKey" description:"应用Key"`
|
||||
AppSecret string `orm:"app_secret" json:"appSecret" description:"应用密钥"`
|
||||
CallbackURL string `orm:"callback_url" json:"callbackUrl" description:"订单回调接收URL"`
|
||||
Config string `orm:"config" json:"config" description:"平台特定配置(JSON)"`
|
||||
Enabled bool `orm:"enabled" json:"enabled" description:"是否启用"`
|
||||
}
|
||||
|
||||
// CommercePlatformConfigCol 电商平台配置表字段定义
|
||||
type CommercePlatformConfigCol struct {
|
||||
beans.SQLBaseCol
|
||||
Platform string
|
||||
AppKey string
|
||||
AppSecret string
|
||||
CallbackURL string
|
||||
Config string
|
||||
Enabled string
|
||||
AdvertiserID string
|
||||
Name string
|
||||
Platform string
|
||||
AppKey string
|
||||
AppSecret string
|
||||
CallbackURL string
|
||||
Config string
|
||||
Enabled string
|
||||
}
|
||||
|
||||
// CommercePlatformConfigCols 电商平台配置表字段常量
|
||||
var CommercePlatformConfigCols = CommercePlatformConfigCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
Platform: "platform",
|
||||
AppKey: "app_key",
|
||||
AppSecret: "app_secret",
|
||||
CallbackURL: "callback_url",
|
||||
Config: "config",
|
||||
Enabled: "enabled",
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
AdvertiserID: "advertiser_id",
|
||||
Name: "name",
|
||||
Platform: "platform",
|
||||
AppKey: "app_key",
|
||||
AppSecret: "app_secret",
|
||||
CallbackURL: "callback_url",
|
||||
Config: "config",
|
||||
Enabled: "enabled",
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
type ItemMapping struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
AdvertiserID int64 `orm:"advertiser_id" json:"advertiserId" description:"归属广告主ID"`
|
||||
AdPlatform string `orm:"ad_platform" json:"adPlatform" description:"广告平台"`
|
||||
AdItemID string `orm:"ad_item_id" json:"adItemId" description:"广告平台侧商品ID"`
|
||||
CommercePlatform string `orm:"commerce_platform" json:"commercePlatform" description:"电商平台"`
|
||||
@@ -19,6 +20,7 @@ type ItemMapping struct {
|
||||
// ItemMappingCol 商品映射表字段定义
|
||||
type ItemMappingCol struct {
|
||||
beans.SQLBaseCol
|
||||
AdvertiserID string
|
||||
AdPlatform string
|
||||
AdItemID string
|
||||
CommercePlatform string
|
||||
@@ -30,6 +32,7 @@ type ItemMappingCol struct {
|
||||
// ItemMappingCols 商品映射表字段常量
|
||||
var ItemMappingCols = ItemMappingCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
AdvertiserID: "advertiser_id",
|
||||
AdPlatform: "ad_platform",
|
||||
AdItemID: "ad_item_id",
|
||||
CommercePlatform: "commerce_platform",
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package report
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
|
||||
"gitea.redpowerfuture.com/red-future/common/beans"
|
||||
)
|
||||
|
||||
// DailyReport 日报表实体(统一消耗/效果汇总:外投拉平台报表,DSP聚合auction_log)
|
||||
type DailyReport struct {
|
||||
beans.SQLBaseDO `orm:",inherit"`
|
||||
// 业务字段
|
||||
ReportDate *gtime.Time `orm:"report_date" json:"reportDate" description:"报表日期"`
|
||||
Channel string `orm:"channel" json:"channel" description:"投放通道 external外投/self_dsp自有"`
|
||||
AdAccountID int64 `orm:"ad_account_id" json:"adAccountId" description:"广告账户ID"`
|
||||
CampaignID int64 `orm:"campaign_id" json:"campaignId" description:"本地计划ID"`
|
||||
Platform string `orm:"platform" json:"platform" description:"广告平台 douyin/xiaohongshu/kuaishou"`
|
||||
Impressions int64 `orm:"impressions" json:"impressions" description:"展示数"`
|
||||
Clicks int64 `orm:"clicks" json:"clicks" description:"点击数"`
|
||||
Cost int64 `orm:"cost" json:"cost" description:"消耗(分)"`
|
||||
Conversions int64 `orm:"conversions" json:"conversions" description:"转化数"`
|
||||
Extra string `orm:"extra" json:"extra" description:"平台报表原始快照(JSON),对账用"`
|
||||
}
|
||||
|
||||
// DailyReportCol 日报表字段定义
|
||||
type DailyReportCol struct {
|
||||
beans.SQLBaseCol
|
||||
ReportDate string
|
||||
Channel string
|
||||
AdAccountID string
|
||||
CampaignID string
|
||||
Platform string
|
||||
Impressions string
|
||||
Clicks string
|
||||
Cost string
|
||||
Conversions string
|
||||
Extra string
|
||||
}
|
||||
|
||||
// DailyReportCols 日报表字段常量
|
||||
var DailyReportCols = DailyReportCol{
|
||||
SQLBaseCol: beans.DefSQLBaseCol,
|
||||
ReportDate: "report_date",
|
||||
Channel: "channel",
|
||||
AdAccountID: "ad_account_id",
|
||||
CampaignID: "campaign_id",
|
||||
Platform: "platform",
|
||||
Impressions: "impressions",
|
||||
Clicks: "clicks",
|
||||
Cost: "cost",
|
||||
Conversions: "conversions",
|
||||
Extra: "extra",
|
||||
}
|
||||
@@ -1,112 +1,304 @@
|
||||
# 技术设计
|
||||
|
||||
## CID 归因平台(规划中)
|
||||
## 投放+归因一体化平台(规划中)
|
||||
|
||||
### 业务全景
|
||||
|
||||
平台承载三块能力,统一账户与素材体系:
|
||||
|
||||
1. **投放-外投**:对接巨量引擎/聚光/磁力引擎等平台营销 API,为客户建计划、传素材、启停调优、拉取报表
|
||||
2. **投放-自有DSP**:自有媒体(APP/站点)与广告位,支持 PD 直投/程序化直购与 RTB 实时竞价两种方式
|
||||
3. **归因(CID)**:点击 → click_id → 落地页 → 转化回传,外投回传广告平台、自有流量转化回流自身优化
|
||||
|
||||
### 核心链路
|
||||
|
||||
1. 用户点击广告 → 广告平台(抖音/小红书/快手等)跳转到 CID 落地页(携带广告参数)
|
||||
2. CID 生成唯一 click_id,记录点击,302 跳转电商平台(URL 带 click_id)
|
||||
3. 用户下单支付 → 电商平台(京东/淘宝等)回调 CID(订单号 + click_id)
|
||||
4. CID 归因去重(精确匹配 click_id,兜底 device_id + item + 归因窗口)
|
||||
5. 回传转化给广告平台(report_task 队列 + 失败退避重试,用于广告系统优化投放)
|
||||
**外投链路**:
|
||||
|
||||
### 表设计(PG,Phase 1)
|
||||
```
|
||||
CID 创建计划(campaign/单元/广告/创意,本地草稿)
|
||||
→ 同步平台营销 API(计划/单元/广告/创意/素材上传)
|
||||
→ 平台审核(素材审核状态机)→ 启停/调价 → 定时拉取平台报表 → daily_report
|
||||
```
|
||||
|
||||
**click_log**(高频写入,按 day 分区):
|
||||
**自有DSP链路**:
|
||||
|
||||
```
|
||||
PD直投:placement 预订单 → 按定向直接投放 → auction_log(show/click) → 计费
|
||||
RTB竞价:用户请求 → bid_request_log → 竞价 → 胜出 → auction_log(show/click) → 计费
|
||||
→ 点击进归因链路(click_log.channel=self_dsp)→ 转化回流自身优化
|
||||
```
|
||||
|
||||
**归因链路**(外投/自有共用,channel 区分):
|
||||
|
||||
```
|
||||
用户点击广告 → 广告平台/自有媒体跳转 CID(携带广告参数)
|
||||
→ 生成 click_id、记录点击(click_log)→ 302 跳转电商平台(URL 带 click_id)
|
||||
→ 用户下单支付 → 电商平台回调 CID(订单 + click_id)
|
||||
→ 归因去重(conversion_order,幂等)→ 回传转化(report_task 队列 + 失败重试)
|
||||
```
|
||||
|
||||
### 表设计(PG)
|
||||
|
||||
公共字段(`id/tenant_id/creator/created_at/updater/updated_at/deleted_at`)以下 DDL 统一省略,对应 `beans.SQLBaseDO`;金额一律整数分;时间统一 TIMESTAMPTZ(PGTZ 东八区);平台特有差异一律进 JSONB,零 DDL 扩展。
|
||||
|
||||
#### 账户域
|
||||
|
||||
**advertiser 广告主(客户)**:多级/单层账户模式的业务主体
|
||||
|
||||
```sql
|
||||
CREATE TABLE click_log (
|
||||
CREATE TABLE advertiser (
|
||||
id BIGINT PRIMARY KEY,
|
||||
click_id VARCHAR(32) NOT NULL, -- 对外 CID(base62 短码)
|
||||
request_id VARCHAR(64), -- 广告平台请求号(去重键)
|
||||
ad_platform VARCHAR(20) NOT NULL, -- douyin/xiaohongshu/kuaishou
|
||||
account_id VARCHAR(64),
|
||||
campaign_id VARCHAR(64),
|
||||
ad_group_id VARCHAR(64),
|
||||
ad_id VARCHAR(64),
|
||||
creative_id VARCHAR(64),
|
||||
device_id VARCHAR(64), -- oaid/imei/idfa
|
||||
ip VARCHAR(64),
|
||||
ua TEXT,
|
||||
landing_url TEXT, -- 原始落地页
|
||||
redirect_url TEXT, -- 跳转下游 URL(带 click_id)
|
||||
extra JSONB, -- 平台透传参数(新平台零 DDL)
|
||||
day DATE NOT NULL, -- 分区键
|
||||
create_time TIMESTAMP NOT NULL DEFAULT now(),
|
||||
UNIQUE (click_id),
|
||||
UNIQUE (ad_platform, request_id) -- 重复回调去重硬保障
|
||||
);
|
||||
CREATE INDEX idx_click_platform_day ON click_log (ad_platform, day, ad_id);
|
||||
CREATE INDEX idx_click_device ON click_log (device_id, day);
|
||||
```
|
||||
|
||||
**conversion_order**(幂等约束为硬要求——转化回传涉及资金归因):
|
||||
|
||||
```sql
|
||||
CREATE TABLE conversion_order (
|
||||
id BIGINT PRIMARY KEY,
|
||||
conversion_id VARCHAR(64) NOT NULL, -- 转化事件 ID(幂等键,防重复消费)
|
||||
order_id VARCHAR(64) NOT NULL, -- 电商订单号
|
||||
commerce_platform VARCHAR(20) NOT NULL, -- jd/taobao
|
||||
click_id VARCHAR(32),
|
||||
device_id VARCHAR(64), -- 兜底归因
|
||||
item_id VARCHAR(64),
|
||||
item_name VARCHAR(255),
|
||||
sku_id VARCHAR(64),
|
||||
conversion_type VARCHAR(20), -- order/pay/refund/cart
|
||||
order_amount BIGINT, -- 金额一律以分存储
|
||||
pay_amount BIGINT,
|
||||
quantity INT,
|
||||
order_status VARCHAR(20), -- paid/refunded/canceled
|
||||
attributed_by VARCHAR(16), -- click_id/device/manual
|
||||
report_status SMALLINT DEFAULT 0, -- 0 待回传 1 已回传 2 失败
|
||||
report_count INT DEFAULT 0,
|
||||
first_click_time BIGINT,
|
||||
create_time TIMESTAMP NOT NULL DEFAULT now(),
|
||||
update_time TIMESTAMP,
|
||||
UNIQUE (commerce_platform, order_id),
|
||||
UNIQUE (conversion_id)
|
||||
name VARCHAR(128) NOT NULL,
|
||||
industry VARCHAR(64),
|
||||
contact_name VARCHAR(64),
|
||||
contact_phone VARCHAR(32),
|
||||
status SMALLINT DEFAULT 1, -- 1启用 0停用
|
||||
extra JSONB,
|
||||
UNIQUE (name)
|
||||
);
|
||||
```
|
||||
|
||||
**report_task**(回传任务队列,调度器消费,失败退避重试):
|
||||
**ad_account 广告账户**(由 ad_platform_account 改造,见「已有表改造」):平台凭据 + 多级层级 + API 能力标识
|
||||
|
||||
#### 投放域(计划树四层,各层可选)
|
||||
|
||||
```sql
|
||||
CREATE TABLE report_task (
|
||||
-- 计划:挂账户,区分外投/自有,预算在计划层
|
||||
CREATE TABLE campaign (
|
||||
id BIGINT PRIMARY KEY,
|
||||
advertiser_id BIGINT NOT NULL, -- 冗余:查询隔离
|
||||
ad_account_id BIGINT NOT NULL, -- 本地账户(ad_account.id)
|
||||
name VARCHAR(128) NOT NULL,
|
||||
channel VARCHAR(16) NOT NULL, -- external(外投)/self_dsp(自有)
|
||||
platform VARCHAR(20), -- external: douyin/xiaohongshu/kuaishou
|
||||
platform_campaign_id VARCHAR(64), -- 平台侧计划ID(外投同步成功后有)
|
||||
status VARCHAR(20) DEFAULT 'draft', -- 本地: draft/active/paused/ended/rejected
|
||||
platform_status VARCHAR(20), -- 平台侧状态快照(同步回填)
|
||||
sync_status VARCHAR(20) DEFAULT 'none', -- none/pending/synced/failed
|
||||
budget_mode VARCHAR(10) DEFAULT 'free', -- free不限/daily日预算
|
||||
daily_budget BIGINT, -- 日预算(分),budget_mode=daily时必填,否则NULL
|
||||
total_budget BIGINT, -- 总预算(分),NULL=不限(0与NULL语义分离)
|
||||
optimize_goal VARCHAR(32), -- conversion/click/exposure
|
||||
start_time TIMESTAMPTZ,
|
||||
end_time TIMESTAMPTZ,
|
||||
extra JSONB,
|
||||
UNIQUE (ad_account_id, platform_campaign_id)
|
||||
);
|
||||
|
||||
-- 单元:定向+出价
|
||||
CREATE TABLE ad_group (
|
||||
id BIGINT PRIMARY KEY,
|
||||
campaign_id BIGINT NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
platform_ad_group_id VARCHAR(64),
|
||||
targeting JSONB, -- 地域/人群/时段
|
||||
bid_type VARCHAR(16), -- cpm/ocpm/cpc/ocpc
|
||||
bid_amount BIGINT, -- 分(cpm按千次展示)
|
||||
status VARCHAR(20) DEFAULT 'draft',
|
||||
platform_status VARCHAR(20),
|
||||
sync_status VARCHAR(20) DEFAULT 'none',
|
||||
extra JSONB,
|
||||
UNIQUE (campaign_id, platform_ad_group_id)
|
||||
);
|
||||
|
||||
-- 广告
|
||||
CREATE TABLE ad (
|
||||
id BIGINT PRIMARY KEY,
|
||||
ad_group_id BIGINT NOT NULL,
|
||||
name VARCHAR(128),
|
||||
platform_ad_id VARCHAR(64),
|
||||
status VARCHAR(20) DEFAULT 'draft',
|
||||
platform_status VARCHAR(20),
|
||||
sync_status VARCHAR(20) DEFAULT 'none',
|
||||
extra JSONB,
|
||||
UNIQUE (ad_group_id, platform_ad_id)
|
||||
);
|
||||
|
||||
-- 创意:引用素材库,落地页带click_id模板,双平台ID(创意ID+素材ID)
|
||||
CREATE TABLE creative (
|
||||
id BIGINT PRIMARY KEY,
|
||||
ad_id BIGINT, -- 所属广告(部分平台创意先于广告,可空)
|
||||
campaign_id BIGINT NOT NULL, -- 所属计划ID(冗余:ad_id为空时链路不断)
|
||||
advertiser_id BIGINT NOT NULL, -- 冗余:素材按广告主隔离
|
||||
name VARCHAR(128),
|
||||
material_id BIGINT NOT NULL, -- 引用material.id
|
||||
title VARCHAR(255),
|
||||
description TEXT,
|
||||
click_url TEXT, -- 落地页(含click_id模板)
|
||||
platform_creative_id VARCHAR(64),
|
||||
platform_material_id VARCHAR(64), -- 平台侧素材ID(上传返回)
|
||||
audit_status VARCHAR(20) DEFAULT 'pending', -- pending/pass/reject
|
||||
status VARCHAR(20) DEFAULT 'draft',
|
||||
platform_status VARCHAR(20),
|
||||
sync_status VARCHAR(20) DEFAULT 'none',
|
||||
extra JSONB
|
||||
);
|
||||
```
|
||||
|
||||
#### 素材域
|
||||
|
||||
```sql
|
||||
CREATE TABLE material (
|
||||
id BIGINT PRIMARY KEY,
|
||||
conversion_id VARCHAR(64) NOT NULL,
|
||||
click_id VARCHAR(32),
|
||||
ad_platform VARCHAR(20) NOT NULL,
|
||||
task_type VARCHAR(20), -- conversion/refund
|
||||
status SMALLINT DEFAULT 0, -- 0 待回传 1 回传中 2 成功 3 失败
|
||||
retry_count INT DEFAULT 0,
|
||||
next_retry_at BIGINT, -- 失败退避重试时间
|
||||
request_body TEXT,
|
||||
response_body TEXT,
|
||||
trace_id VARCHAR(64),
|
||||
create_time TIMESTAMP NOT NULL DEFAULT now()
|
||||
advertiser_id BIGINT NOT NULL,
|
||||
type VARCHAR(10) NOT NULL, -- image/video/text
|
||||
title VARCHAR(128),
|
||||
file_path TEXT,
|
||||
file_size BIGINT,
|
||||
md5 VARCHAR(64), -- 去重键
|
||||
width INT, height INT,
|
||||
duration INT, -- 视频时长(秒)
|
||||
format VARCHAR(16),
|
||||
audit_status VARCHAR(20) DEFAULT 'pending', -- pending/auditing/pass/reject/disabled
|
||||
status SMALLINT DEFAULT 1, -- 1可用 0禁用
|
||||
extra JSONB, -- platform_material_id: {"douyin":"x"}
|
||||
UNIQUE (md5)
|
||||
);
|
||||
|
||||
-- 审核状态机全量日志(易盾/平台审核回调/人工,多通道)
|
||||
CREATE TABLE material_audit (
|
||||
id BIGINT PRIMARY KEY,
|
||||
material_id BIGINT NOT NULL,
|
||||
audit_channel VARCHAR(20) NOT NULL, -- yidun/platform/manual
|
||||
audit_action VARCHAR(20) NOT NULL, -- submit/pass/reject/revoke
|
||||
audit_status VARCHAR(20) NOT NULL,
|
||||
result JSONB, -- 平台返回详情(原因/违规项)
|
||||
operator VARCHAR(64),
|
||||
audit_time TIMESTAMPTZ
|
||||
);
|
||||
CREATE INDEX idx_report_status ON report_task (status, next_retry_at);
|
||||
```
|
||||
|
||||
**配置表**(低频,JSONB 兜底平台特有配置,新平台只加一行配置 + 一个适配器):
|
||||
#### DSP 自有流量域
|
||||
|
||||
- `ad_platform_account`:上游广告平台对接配置(platform、account_id、app_id、app_secret、access_token、token_expire_at、config JSONB、enabled)
|
||||
- `commerce_platform_config`:下游电商平台配置(platform、app_key、app_secret、callback_url、config JSONB、enabled)
|
||||
- `item_mapping`:广告商品 ↔ 电商商品映射(ad_platform、ad_item_id、commerce_platform、commerce_item_id、sku_id、config JSONB)
|
||||
```sql
|
||||
CREATE TABLE media (
|
||||
id BIGINT PRIMARY KEY,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
type VARCHAR(10) NOT NULL, -- app/web
|
||||
domain VARCHAR(255),
|
||||
status SMALLINT DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE TABLE ad_slot (
|
||||
id BIGINT PRIMARY KEY,
|
||||
media_id BIGINT NOT NULL,
|
||||
name VARCHAR(128) NOT NULL,
|
||||
slot_type VARCHAR(20) NOT NULL, -- banner/feed/video/interstitial/reward
|
||||
size VARCHAR(32),
|
||||
unit VARCHAR(10) NOT NULL, -- cpm/cpc/cpa
|
||||
base_price BIGINT, -- 底价(分, cpm按千次)
|
||||
status SMALLINT DEFAULT 1, -- 1可售 0停售
|
||||
extra JSONB -- 定向能力声明等
|
||||
);
|
||||
|
||||
-- PD预订单(直投/程序化直购)
|
||||
CREATE TABLE placement (
|
||||
id BIGINT PRIMARY KEY,
|
||||
ad_slot_id BIGINT NOT NULL,
|
||||
campaign_id BIGINT NOT NULL, -- 本地计划
|
||||
quota INT, -- 预订量(展示次数)
|
||||
price BIGINT, -- 协议价(分, 按unit口径)
|
||||
book_start TIMESTAMPTZ,
|
||||
book_end TIMESTAMPTZ,
|
||||
status VARCHAR(20) DEFAULT 'pending', -- pending/running/finished/ended
|
||||
delivered_count INT DEFAULT 0, -- 已投展示数
|
||||
UNIQUE (ad_slot_id, campaign_id)
|
||||
);
|
||||
|
||||
-- RTB请求日志(高并发,按day分区,可裁剪)
|
||||
CREATE TABLE bid_request_log (
|
||||
id BIGINT PRIMARY KEY,
|
||||
request_id VARCHAR(64) NOT NULL,
|
||||
slot_id BIGINT NOT NULL,
|
||||
device_id VARCHAR(64),
|
||||
ip VARCHAR(64),
|
||||
ua TEXT,
|
||||
extra JSONB, -- 上下文/定向特征
|
||||
day DATE NOT NULL,
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now(),
|
||||
UNIQUE (request_id)
|
||||
) PARTITION BY RANGE (day);
|
||||
|
||||
-- 展示/计费(PD与RTB统一入口:event=show/click)
|
||||
CREATE TABLE auction_log (
|
||||
id BIGINT PRIMARY KEY,
|
||||
request_id VARCHAR(64) NOT NULL, -- RTB请求ID / PD流水号
|
||||
campaign_id BIGINT NOT NULL,
|
||||
ad_id BIGINT,
|
||||
slot_id BIGINT NOT NULL,
|
||||
event VARCHAR(10) NOT NULL, -- show/click
|
||||
price BIGINT, -- 成交价(分, cpm按千次)
|
||||
cost BIGINT, -- 本次事件应计费用(分,写入时算好,计费直接取)
|
||||
device_id VARCHAR(64),
|
||||
ip VARCHAR(64),
|
||||
day DATE NOT NULL,
|
||||
create_time TIMESTAMPTZ NOT NULL DEFAULT now()
|
||||
) PARTITION BY RANGE (day);
|
||||
```
|
||||
|
||||
#### 归因域(click_log / conversion_order / report_task,DDL 见「已有表改造」)
|
||||
|
||||
#### 报表域
|
||||
|
||||
```sql
|
||||
-- 统一消耗/效果汇总:外投拉平台报表,DSP聚合auction_log
|
||||
CREATE TABLE daily_report (
|
||||
id BIGINT PRIMARY KEY,
|
||||
report_date DATE NOT NULL,
|
||||
channel VARCHAR(16) NOT NULL, -- external/self_dsp
|
||||
ad_account_id BIGINT,
|
||||
campaign_id BIGINT NOT NULL,
|
||||
platform VARCHAR(20),
|
||||
impressions BIGINT DEFAULT 0,
|
||||
clicks BIGINT DEFAULT 0,
|
||||
cost BIGINT DEFAULT 0, -- 分
|
||||
conversions BIGINT DEFAULT 0,
|
||||
extra JSONB, -- 平台报表原始快照(对账用)
|
||||
UNIQUE (report_date, channel, campaign_id)
|
||||
);
|
||||
```
|
||||
|
||||
#### 电商配置与商品映射(commerce_platform_config / item_mapping,微调见「已有表改造」)
|
||||
|
||||
### 已有表改造方案
|
||||
|
||||
| 表 | 改造动作 | 新增字段 |
|
||||
|---|---|---|
|
||||
| ad_platform_account | 更名 **ad_account** | `advertiser_id`、`name`、`parent_account_id`(自引用多级层级)、`account_role`(agent/owner/sub)、`capabilities` JSONB(attribution/delivery/report) |
|
||||
| commerce_platform_config | 微调 | `advertiser_id`、`name` |
|
||||
| item_mapping | 微调 | `advertiser_id`;加唯一约束 `UNIQUE(ad_platform, ad_item_id, commerce_platform, commerce_item_id)`(防重复映射) |
|
||||
| click_log | 加5列 | `channel`(external/self_dsp)、`local_campaign_id`、`local_ad_id`、`local_creative_id`(本地ID,回传路由/归因)、`slot_id`(自有DSP广告位) |
|
||||
| conversion_order | 加2列 | `local_campaign_id`、`ad_account_id`(回传凭据账户) |
|
||||
| report_task | 加2列 | `ad_account_id`、`campaign_id`(回传目标本地计划) |
|
||||
|
||||
- 改造方式:启动时 `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`(幂等)+ 存量数据回填;ad_account 存量行 advertiser_id 置 NULL 待绑定、account_role 默认 owner
|
||||
- 回传路由字段冗余落 conversion_order/report_task 而非反查 click_log:click_log 按 day 分区可裁剪,转化订单生命周期长,反查不可靠
|
||||
- click_log 平台透传ID(campaign_id/ad_id/creative_id,string)与本地ID(local_campaign_id/local_ad_id/local_creative_id,int64)分列:外投点击带平台侧ID,DSP 点击只填本地ID,透传字段留空,两体系不混用
|
||||
- 无存量风险:attribution 域表尚未建(规划中),仅 ad_platform_account 等配置表有存量,改造成本低
|
||||
|
||||
### 关键设计决策
|
||||
|
||||
| 决策 | 方案 | 理由 |
|
||||
|---|---|---|
|
||||
| 平台扩展 | 平台字段枚举 + `extra/config JSONB` + 适配器 | 平台不确定,新增平台零 DDL、不改主流程 |
|
||||
| 点击去重 | 接入层 Redis 布隆过滤器(热路径拦截) + 存储层 `UNIQUE(ad_platform, request_id)` 兜底 | Redis 挡 99% 重复;唯一约束是易失层之外的硬保障 |
|
||||
| 计划树 | 四层统一模型 campaign/ad_group/ad/creative,各层可选,差异进 extra;creative 冗余 campaign_id 保证 ad_id 可空时链路不断 | 千川/聚光/磁力层级不一,统一模型 + 零 DDL 适配 |
|
||||
| 双轨ID | 本地 PK + platform_xxx_id + sync_status 状态机 | 本地草稿先行、同步失败可重试,不与平台强耦合 |
|
||||
| ID语义分离 | click_log 平台透传ID(string)与本地ID(int64)分列 | 外投点击带平台侧ID、DSP 点击只填本地ID,两体系不混用,回传/对账无歧义 |
|
||||
| 点击去重 | 接入层 Redis 布隆过滤器(热路径拦截) + 存储层 `UNIQUE(platform, request_id)` 兜底 | Redis 挡 99% 重复;唯一约束是易失层之外的硬保障 |
|
||||
| 点击事件不可变 | click_log 不含转化状态字段,转化状态由 conversion_order.click_id 反查 | 高频写入表不做 UPDATE,避免行锁竞争与表膨胀 |
|
||||
| 转化幂等 | `UNIQUE(commerce_platform, order_id)` + `UNIQUE(conversion_id)` | 同单只记一次;重复事件直接忽略(MQ 至少一次语义) |
|
||||
| 回传可靠性 | report_task 状态机 + 定时重试(退避) | 回传失败必须重试,重试靠持久化状态而非内存 |
|
||||
| 回传路由 | conversion_order/report_task 冗余 local_campaign_id + ad_account_id | click_log 按 day 分区可裁剪,转化生命周期长,反查不可靠 |
|
||||
| 双通道点击 | click_log.channel 区分 external/self_dsp,自有流量并入同一归因链路 | 归因逻辑单一路径,自有流量也能拿转化数据优化 |
|
||||
| 兜底归因 | device_id + item_id + 归因窗口(默认 7 天) | 用户清 cookie 丢 click_id 时仍可归因 |
|
||||
| click_id 编码 | base62:平台前缀 + 分片位 + 时间 + 随机 + 校验位 | 短、唯一、不可预测;预留分片/路由能力 |
|
||||
| 金额 | 一律整数分(int64),外部平台以元回传时在接入边界转分 | 禁止浮点元(公司规范) |
|
||||
| 计费精度 | CPM 价按千次存储,auction_log.cost 写入时按量算好单次费用(RoundInt),计费直接取 cost | 单次展示价非整数分,预计算避免各聚合点口径不一与浮点累加误差 |
|
||||
| 高并发分区 | bid_request_log/auction_log 按 day 分区,与 click_log 同策略 | RTB 请求量大,沿用既有分区思路 |
|
||||
| 消耗统一 | daily_report:外投拉平台报表、DSP 聚合 auction_log | 报表/对账单一路径 |
|
||||
| 素材审核 | material 状态机 + material_audit 全量日志多通道(易盾/平台/人工) | 外投素材过平台审核,自有媒体素材只过易盾 |
|
||||
| 多级账户 | ad_account 自引用 parent_account_id + account_role | 代理层级与单层统一(PG 递归用 CTE) |
|
||||
| 素材去重 | material.md5 唯一 | 一处素材多处投放,省存储与重复审核 |
|
||||
|
||||
### 事件驱动演进(分阶段,不一步到位)
|
||||
|
||||
@@ -114,7 +306,7 @@ CREATE INDEX idx_report_status ON report_task (status, next_retry_at);
|
||||
|
||||
- **Phase 1(当前)**:PG 分区表 + JSONB 扩展字段,归因走 SQL;表结构与 Phase 3 事件模型兼容
|
||||
- **Phase 2**:引入 MQ(NATS JetStream,已确定),点击/转化异步写事件流,PG 只做状态层;转化事件走低延迟通道(涉及资金回传,不进粗粒度批量管道),点击事件可批量消费
|
||||
- **Phase 3**:引入 ClickHouse 承接事件与分析(点击日百万级后归因/报表查询拖垮 PG 时再做),迁移事件表,归因/报表查询走列存
|
||||
- **Phase 3**:引入 ClickHouse 承接事件与分析(点击日百万级后归因/报表查询拖垮 PG 时再做),迁移事件表(click_log/bid_request_log/auction_log),归因/报表查询走列存
|
||||
- **Phase 4**:流式归因,Phase 1 的 SQL 归因退役
|
||||
|
||||
**MQ 选型(已确定)**:NATS JetStream,一条服务两条流,按事件类型配置不同:
|
||||
|
||||
Reference in New Issue
Block a user