From 13cd3875d3a8174923061555cf24c725cd650a10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Tue, 25 Aug 2026 15:26:13 +0800 Subject: [PATCH] =?UTF-8?q?saas=E9=9A=94=E7=A6=BB=E4=BD=BF=E7=94=A8admin-g?= =?UTF-8?q?o=E4=B8=AD=E7=9A=84=E7=A7=9F=E6=88=B7=E4=BD=93=E7=B3=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- model/entity/account/advertiser.go | 39 ---------- model/entity/delivery/campaign.go | 3 - model/entity/delivery/creative.go | 3 - model/entity/material/material.go | 77 +++++++++---------- model/entity/platform/ad_account.go | 3 - .../platform/commerce_platform_config.go | 47 ++++++----- model/entity/platform/item_mapping.go | 3 - 技术设计.md | 27 ++----- 9 files changed, 68 insertions(+), 140 deletions(-) delete mode 100644 model/entity/account/advertiser.go diff --git a/README.md b/README.md index 6ffa081..1d8be05 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,15 @@ 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/ | 平台配置域(规划):下游电商平台对接配置与商品映射 | commerce_platform_config、item_mapping | +| platform/ | 平台配置域(规划):广告账户(多级层级 + API 凭据 + 能力)、下游电商平台对接配置与商品映射 | ad_account、commerce_platform_config、item_mapping | | report/ | 报表域(规划):统一消耗/效果汇总(外投平台报表 + DSP 聚合) | daily_report | +> 租户/广告主信息由 admin-go 承载,CID 各表以 `tenant_id` 隔离,不建账户/租户表。 + ## 数据流 **内容审核链路**: @@ -81,7 +82,6 @@ sql/ DDL 与迁移脚本 | 表 | 库 | 说明 | |---|---|---| -| advertiser | cid | 广告主(客户) | | ad_account | cid | 广告账户(多级层级 + API 凭据 + 能力,由 ad_platform_account 改造) | | campaign / ad_group / ad / creative | cid | 计划树四层(本地实体 + 平台 ID 双轨) | | material / material_audit | cid | 素材库 + 多通道审核状态机 | diff --git a/model/entity/account/advertiser.go b/model/entity/account/advertiser.go deleted file mode 100644 index 2f08874..0000000 --- a/model/entity/account/advertiser.go +++ /dev/null @@ -1,39 +0,0 @@ -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", -} diff --git a/model/entity/delivery/campaign.go b/model/entity/delivery/campaign.go index d76fba0..f06c223 100644 --- a/model/entity/delivery/campaign.go +++ b/model/entity/delivery/campaign.go @@ -10,7 +10,6 @@ import ( 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自有"` @@ -31,7 +30,6 @@ type Campaign struct { // CampaignCol 广告计划表字段定义 type CampaignCol struct { beans.SQLBaseCol - AdvertiserID string AdAccountID string Name string Channel string @@ -52,7 +50,6 @@ type CampaignCol struct { // CampaignCols 广告计划表字段常量 var CampaignCols = CampaignCol{ SQLBaseCol: beans.DefSQLBaseCol, - AdvertiserID: "advertiser_id", AdAccountID: "ad_account_id", Name: "name", Channel: "channel", diff --git a/model/entity/delivery/creative.go b/model/entity/delivery/creative.go index ff238b4..1c86be0 100644 --- a/model/entity/delivery/creative.go +++ b/model/entity/delivery/creative.go @@ -10,7 +10,6 @@ type Creative struct { // 业务字段 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:"标题"` @@ -30,7 +29,6 @@ type CreativeCol struct { beans.SQLBaseCol AdID string CampaignID string - AdvertiserID string Name string MaterialID string Title string @@ -50,7 +48,6 @@ var CreativeCols = CreativeCol{ SQLBaseCol: beans.DefSQLBaseCol, AdID: "ad_id", CampaignID: "campaign_id", - AdvertiserID: "advertiser_id", Name: "name", MaterialID: "material_id", Title: "title", diff --git a/model/entity/material/material.go b/model/entity/material/material.go index 5c4a4fa..1daaffb 100644 --- a/model/entity/material/material.go +++ b/model/entity/material/material.go @@ -8,53 +8,50 @@ import ( 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映射"` + 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 + 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", + SQLBaseCol: beans.DefSQLBaseCol, + 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", } diff --git a/model/entity/platform/ad_account.go b/model/entity/platform/ad_account.go index 3b704a5..5600e6d 100644 --- a/model/entity/platform/ad_account.go +++ b/model/entity/platform/ad_account.go @@ -8,7 +8,6 @@ import ( 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"` @@ -26,7 +25,6 @@ type AdAccount struct { // AdAccountCol 广告账户表字段定义 type AdAccountCol struct { beans.SQLBaseCol - AdvertiserID string Name string Platform string AccountID string @@ -44,7 +42,6 @@ type AdAccountCol struct { // AdAccountCols 广告账户表字段常量 var AdAccountCols = AdAccountCol{ SQLBaseCol: beans.DefSQLBaseCol, - AdvertiserID: "advertiser_id", Name: "name", Platform: "platform", AccountID: "account_id", diff --git a/model/entity/platform/commerce_platform_config.go b/model/entity/platform/commerce_platform_config.go index d892326..4bd782b 100644 --- a/model/entity/platform/commerce_platform_config.go +++ b/model/entity/platform/commerce_platform_config.go @@ -8,38 +8,35 @@ import ( type CommercePlatformConfig 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:"电商平台 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:"是否启用"` + 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 - AdvertiserID string - Name string - Platform string - AppKey string - AppSecret string - CallbackURL string - Config string - Enabled string + Name string + Platform string + AppKey string + AppSecret string + CallbackURL string + Config string + Enabled string } // CommercePlatformConfigCols 电商平台配置表字段常量 var CommercePlatformConfigCols = CommercePlatformConfigCol{ - SQLBaseCol: beans.DefSQLBaseCol, - AdvertiserID: "advertiser_id", - Name: "name", - Platform: "platform", - AppKey: "app_key", - AppSecret: "app_secret", - CallbackURL: "callback_url", - Config: "config", - Enabled: "enabled", + SQLBaseCol: beans.DefSQLBaseCol, + Name: "name", + Platform: "platform", + AppKey: "app_key", + AppSecret: "app_secret", + CallbackURL: "callback_url", + Config: "config", + Enabled: "enabled", } diff --git a/model/entity/platform/item_mapping.go b/model/entity/platform/item_mapping.go index ab90ecd..cb8cf89 100644 --- a/model/entity/platform/item_mapping.go +++ b/model/entity/platform/item_mapping.go @@ -8,7 +8,6 @@ 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:"电商平台"` @@ -20,7 +19,6 @@ type ItemMapping struct { // ItemMappingCol 商品映射表字段定义 type ItemMappingCol struct { beans.SQLBaseCol - AdvertiserID string AdPlatform string AdItemID string CommercePlatform string @@ -32,7 +30,6 @@ type ItemMappingCol struct { // ItemMappingCols 商品映射表字段常量 var ItemMappingCols = ItemMappingCol{ SQLBaseCol: beans.DefSQLBaseCol, - AdvertiserID: "advertiser_id", AdPlatform: "ad_platform", AdItemID: "ad_item_id", CommercePlatform: "commerce_platform", diff --git a/技术设计.md b/技术设计.md index 2468bbe..dd2c0af 100644 --- a/技术设计.md +++ b/技术设计.md @@ -43,20 +43,7 @@ RTB竞价:用户请求 → bid_request_log → 竞价 → 胜出 → auction_l #### 账户域 -**advertiser 广告主(客户)**:多级/单层账户模式的业务主体 - -```sql -CREATE TABLE advertiser ( - id BIGINT PRIMARY KEY, - 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) -); -``` +广告主(客户)= SaaS 租户,租户信息由 **admin-go 承载**,CID 不建账户/租户实体;所有表以 SQLBaseDO 自带 `tenant_id` 做业务隔离,广告主展示信息(名称/行业/联系人)由 admin-go 提供。 **ad_account 广告账户**(由 ad_platform_account 改造,见「已有表改造」):平台凭据 + 多级层级 + API 能力标识 @@ -66,7 +53,6 @@ CREATE TABLE advertiser ( -- 计划:挂账户,区分外投/自有,预算在计划层 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(自有) @@ -119,7 +105,6 @@ 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), @@ -140,7 +125,6 @@ CREATE TABLE creative ( ```sql CREATE TABLE material ( id BIGINT PRIMARY KEY, - advertiser_id BIGINT NOT NULL, type VARCHAR(10) NOT NULL, -- image/video/text title VARCHAR(128), file_path TEXT, @@ -264,14 +248,14 @@ CREATE TABLE daily_report ( | 表 | 改造动作 | 新增字段 | |---|---|---| -| 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)`(防重复映射) | +| ad_platform_account | 更名 **ad_account** | `name`、`parent_account_id`(自引用多级层级)、`account_role`(agent/owner/sub)、`capabilities` JSONB(attribution/delivery/report) | +| commerce_platform_config | 微调 | `name` | +| item_mapping | 微调 | 加唯一约束 `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 +- 改造方式:启动时 `ALTER TABLE ... ADD COLUMN IF NOT EXISTS`(幂等)+ 存量数据回填;ad_account 存量行 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 等配置表有存量,改造成本低 @@ -298,6 +282,7 @@ CREATE TABLE daily_report ( | 消耗统一 | daily_report:外投拉平台报表、DSP 聚合 auction_log | 报表/对账单一路径 | | 素材审核 | material 状态机 + material_audit 全量日志多通道(易盾/平台/人工) | 外投素材过平台审核,自有媒体素材只过易盾 | | 多级账户 | ad_account 自引用 parent_account_id + account_role | 代理层级与单层统一(PG 递归用 CTE) | +| 租户隔离 | 广告主=租户,admin-go 承载;CID 以 tenant_id 隔离,不建账户/租户表 | SaaS 微服务边界:用户/权限/租户归 admin-go,CID 只存引用 | | 素材去重 | material.md5 唯一 | 一处素材多处投放,省存储与重复审核 | ### 事件驱动演进(分阶段,不一步到位)