diff --git a/README.md b/README.md index 68f65df..735c589 100644 --- a/README.md +++ b/README.md @@ -1,99 +1,212 @@ -# CID广告管理系统 +# CID服务商项目 -## 项目简介 +## 项目概述 -CID广告管理系统是一个完整的广告投放和管理平台,支持广告主管理、广告管理、广告位管理和数据统计分析等功能。 +CID服务商项目是一个为企业提供CID(Click ID)服务的完整解决方案。该项目通过智能策略引擎,为不同级别的租户提供定制化的广告匹配服务,支持多广告源集成和转化率控制。 -## 功能模块 +## 核心功能 -### 1. 广告管理 -- 广告创建、编辑、删除 -- 广告审核流程 -- 广告状态管理 -- 广告投放设置 -- 广告定向设置 -- 广告统计数据 +### 1. 租户管理系统 +- **多租户架构**: 支持多个企业租户同时使用 +- **租户级别**: Basic、Standard、Premium三个级别 +- **用户认证集成**: 通过common模块获取用户信息 -### 2. 广告主管理 -- 广告主注册、审核 -- 广告主信息维护 -- 广告主账户管理 -- 充值和授信额度管理 -- 广告主状态管理 +### 2. 广告源管理 +- **自营广告**: 系统内置的广告库存 +- **Google Ads**: 集成Google广告平台 +- **Facebook Ads**: 集成Meta广告平台 +- **扩展性**: 支持添加更多广告源 -### 3. 广告位管理 -- 广告位创建、编辑 -- 广告位状态管理 -- 广告位定价设置 -- 广告位展示规则 -- 广告位统计数据 +### 3. 智能策略引擎 +- **转化率控制**: 根据租户级别设置不同转化率范围 +- **权重分配**: 为不同广告源配置权重比例 +- **动态匹配**: 实时根据策略匹配最优广告 -### 4. 广告统计与报表 -- 广告效果统计 -- 数据可视化仪表盘 -- 自定义报表生成 -- 报表导出功能 -- 数据趋势分析 +### 4. CID生成与追踪 +- **唯一CID**: 为每次请求生成唯一标识 +- **请求记录**: 完整的请求历史记录 +- **性能统计**: 详细的性能和转化统计 -### 5. 广告匹配与投放 -- 基于用户画像的广告匹配 -- 实时广告投放 -- 广告频次控制 -- 广告预算控制 +## API接口 -## 技术架构 +### 租户管理 +- `POST /tenant/create` - 创建租户 +- `PUT /tenant/update` - 更新租户 +- `DELETE /tenant/delete` - 删除租户 +- `GET /tenant/info` - 获取租户信息 +- `GET /tenant/list` - 获取租户列表 -### 技术栈 -- Go语言 + GoFrame框架 -- MongoDB 数据库 -- Redis 缓存 -- RESTful API设计 +### 广告源管理 +- `POST /ad-source/create` - 创建广告源 +- `PUT /ad-source/update` - 更新广告源 +- `DELETE /ad-source/delete` - 删除广告源 +- `GET /ad-source/info` - 获取广告源信息 +- `GET /ad-source/list` - 获取广告源列表 + +### 策略管理 +- `POST /strategy/create` - 创建策略 +- `PUT /strategy/update` - 更新策略 +- `DELETE /strategy/delete` - 删除策略 +- `GET /strategy/info` - 获取策略信息 +- `GET /strategy/list` - 获取策略列表 + +### CID核心服务 +- `POST /cid/generate` - 生成CID广告 +- `GET /cid/statistics` - 获取CID统计 +- `GET /cid/history` - 获取CID历史记录 + +## 租户级别策略 + +### Basic级别 +- 转化率范围: 1% - 5% +- 广告源: 仅自营广告 +- 每次请求: 最多3个广告 + +### Standard级别 +- 转化率范围: 5% - 15% +- 广告源: 自营(70%) + Google(20%) + Facebook(10%) +- 每次请求: 最多5个广告 + +### Premium级别 +- 转化率范围: 15% - 100% +- 广告源: 自营(40%) + Google(30%) + Facebook(30%) +- 每次请求: 最多10个广告 + +## 项目结构 -### 项目结构 ``` cidservice/ -├── consts/ # 常量定义 ├── controller/ # 控制器层 -│ ├── advertisement_controller.go -│ ├── advertiser_controller.go -│ ├── ad_position_controller.go -│ └── ad_statistics_controller.go -├── dao/ # 数据访问层 -│ ├── advertisement_dao.go -│ ├── advertiser_dao.go -│ ├── ad_position_dao.go -│ └── ad_statistics_dao.go -├── model/ # 模型定义 -│ ├── dto/ # 数据传输对象 -│ └── entity/ # 实体对象 -└── service/ # 服务层 - ├── advertisement_service.go - ├── advertiser_service.go - ├── ad_position_service.go - └── ad_statistics_service.go +│ ├── cid_controller.go +│ ├── ad_source_controller.go +│ ├── strategy_controller.go +│ └── ... +├── service/ # 服务层 +│ ├── cid_service.go +│ ├── ad_source_service.go +│ ├── strategy_service.go +│ └── ... +├── dao/ # 数据访问层 +│ ├── cid_request_dao.go +│ ├── ad_source_dao.go +│ ├── strategy_dao.go +│ └── ... +├── model/ # 数据模型 +│ ├── entity/ # 数据库实体 +│ ├── dto/ # 数据传输对象 +│ └── types/ # 类型定义 +├── database/ # 数据库脚本 +│ └── init.sql # 数据库初始化 +├── config/ # 配置文件 +│ └── config.example.yaml +└── README.md +``` + +## 架构说明 + +### 租户管理 +- 租户信息不在当前项目中维护 +- 通过common模块获取用户和租户信息 +- 支持Basic、Standard、Premium三个级别的租户 + +### 广告源集成 +- **自营广告**: 系统内置广告库存 +- **Google Ads**: API集成 +- **Facebook Ads**: API集成 +- 可扩展支持更多广告源 + +### 策略引擎 +- 基于租户级别的动态策略匹配 +- 支持转化率阈值控制 +- 灵活的广告源权重配置 + +## 使用示例 + +### 1. 生成CID广告 +```json +POST /cid/generate +{ + "request_type": "web_banner", + "position": "header", + "count": 5 +} + +Response: +{ + "cid": "CID_1640995200000_1234", + "ads": [ + { + "id": 10001, + "title": "优质产品推广", + "description": "高质量的产品广告", + "image_url": "https://example.com/ad.jpg", + "target_url": "https://example.com/product", + "conversion_rate": 0.08, + "source": "self", + "bid": 500 + } + ], + "total_ads": 1, + "tenant_id": 1, + "tenant_name": "示例公司", + "generated_at": "2023-12-31 16:00:00" +} +``` + +### 2. 创建策略 +```json +POST /strategy/create +{ + "name": "高级策略", + "description": "高级租户专用策略", + "tenant_level": "premium", + "min_conversion": 0.15, + "max_conversion": 1.0, + "source_weights": { + "self": 40, + "google": 30, + "facebook": 30 + }, + "max_ads_per_req": 10, + "priority": 100, + "status": "active" +} ``` ## 部署说明 -1. 配置MongoDB和Redis连接 -2. 修改config.yml配置文件 -3. 执行`go run main.go`启动服务 -4. 服务默认运行在3002端口 +1. 确保数据库连接配置正确 +2. 执行数据库迁移脚本 +3. 配置广告源API密钥 +4. 启动服务 -## API文档 +## 扩展功能 -API文档可以通过Swagger或Postman查看,主要接口包括: +### 添加新的广告源 +1. 在`AdSource`表中添加新的广告源配置 +2. 在`cid_service.go`中实现对应的广告获取方法 +3. 在策略中配置相应权重 -- 广告管理:/advertisement/add, /advertisement/update, /advertisement/list等 -- 广告主管理:/advertiser/add, /advertiser/update, /advertiser/list等 -- 广告位管理:/adposition/add, /adposition/update, /adposition/list等 -- 广告统计:/statistics/list, /dashboard等 -- 报表管理:/report/create, /report/list, /report/download等 +### 自定义策略 +通过策略管理API创建自定义匹配策略,支持: +- 自定义转化率阈值 +- 灵活的权重分配 +- 动态优先级调整 -## 开发规范 +## 监控与统计 -项目遵循原customerservice项目的开发规范,包括分层架构、命名规范和错误处理等。 +系统提供完整的监控和统计功能: +- 请求量统计 +- 成功率分析 +- 处理时间监控 +- 转化率追踪 +- 广告源性能对比 -## 扩展说明 +## 安全特性 -系统支持水平扩展,可以通过增加广告服务实例来提高处理能力,Redis用于缓存和队列,MongoDB用于数据持久化。 \ No newline at end of file +- 用户身份验证 +- 租户数据隔离 +- API访问控制 +- 敏感信息加密 + +这个CID服务商项目为企业提供了一个完整、可扩展的广告CID解决方案,通过智能策略确保每个租户都能获得最适合其业务需求的广告服务。 \ No newline at end of file diff --git a/cidService b/cidService new file mode 100755 index 0000000..a53eaa6 Binary files /dev/null and b/cidService differ diff --git a/config/config.example.yaml b/config/config.example.yaml new file mode 100644 index 0000000..df2c9e5 --- /dev/null +++ b/config/config.example.yaml @@ -0,0 +1,86 @@ +# CID服务商项目配置文件示例 + +# 服务器配置 +server: + address: ":8080" + dumpRouterMap: false + +# 数据库配置 +database: + default: + type: "mysql" + host: "localhost" + port: "3306" + user: "root" + pass: "" + name: "cid_service" + charset: "utf8mb4" + timezone: "Local" + maxIdle: 10 + maxOpen: 100 + maxLifetime: 30 + +# Redis配置 +redis: + default: + address: "127.0.0.1:6379" + db: 1 + pass: "" + +# JWT配置 +jwt: + secret: "your-secret-key" + expire: 7200 # 2小时 + +# 日志配置 +logger: + level: "info" + stdout: true + path: "./logs" + file: "cid-service.log" + +# 广告源配置 +ad_sources: + google: + api_key: "your-google-ads-api-key" + customer_id: "your-customer-id" + developer_token: "your-developer-token" + facebook: + app_id: "your-facebook-app-id" + app_secret: "your-facebook-app-secret" + access_token: "your-facebook-access-token" + ad_account_id: "your-ad-account-id" + +# 策略配置 +strategy: + default: + basic: + min_conversion: 0.01 + max_conversion: 0.05 + source_weights: + self: 100 + google: 0 + facebook: 0 + max_ads_per_req: 3 + standard: + min_conversion: 0.05 + max_conversion: 0.15 + source_weights: + self: 70 + google: 20 + facebook: 10 + max_ads_per_req: 5 + premium: + min_conversion: 0.15 + max_conversion: 1.0 + source_weights: + self: 40 + google: 30 + facebook: 30 + max_ads_per_req: 10 + +# 监控配置 +monitoring: + enable_metrics: true + enable_tracing: true + jaeger_endpoint: "http://localhost:14268/api/traces" \ No newline at end of file diff --git a/controller/ad_source_controller.go b/controller/ad_source_controller.go new file mode 100644 index 0000000..57fa518 --- /dev/null +++ b/controller/ad_source_controller.go @@ -0,0 +1,111 @@ +package controller + +import ( + "context" + "strconv" + + "cidService/model/dto" + "cidService/service" + "github.com/gogf/gf/v2/errors/gerror" +) + +var ( + AdSource = cAdSource{} +) + +type cAdSource struct{} + +// Create 创建广告源 +func (c *cAdSource) Create(ctx context.Context, req *dto.CreateAdSourceReq) (res *dto.GetAdSourceRes, err error) { + id, err := service.AdSource.CreateAdSource(ctx, req) + if err != nil { + return nil, err + } + + adSource, err := service.AdSource.GetAdSourceByID(ctx, id) + if err != nil { + return nil, err + } + + return &dto.GetAdSourceRes{ + AdSource: adSource, + }, nil +} + +// Update 更新广告源 +func (c *cAdSource) Update(ctx context.Context, req *dto.UpdateAdSourceReq) (res *dto.GetAdSourceRes, err error) { + id, err := strconv.ParseInt(req.Id, 10, 64) + if err != nil { + return nil, gerror.New("无效的广告源ID") + } + + affected, err := service.AdSource.UpdateAdSource(ctx, id, req) + if err != nil { + return nil, err + } + if affected == 0 { + return nil, gerror.New("广告源更新失败") + } + + adSource, err := service.AdSource.GetAdSourceByID(ctx, id) + if err != nil { + return nil, err + } + + return &dto.GetAdSourceRes{ + AdSource: adSource, + }, nil +} + +// Delete 删除广告源 +func (c *cAdSource) Delete(ctx context.Context, req *dto.DeleteAdSourceReq) (res *dto.DeleteAdSourceRes, err error) { + id, err := strconv.ParseInt(req.Id, 10, 64) + if err != nil { + return nil, gerror.New("无效的广告源ID") + } + + affected, err := service.AdSource.DeleteAdSource(ctx, id) + if err != nil { + return nil, err + } + if affected == 0 { + return nil, gerror.New("广告源删除失败") + } + + return &dto.DeleteAdSourceRes{ + Success: true, + }, nil +} + +// GetByID 根据ID获取广告源 +func (c *cAdSource) GetByID(ctx context.Context, req *dto.GetAdSourceReq) (res *dto.GetAdSourceRes, err error) { + id, err := strconv.ParseInt(req.Id, 10, 64) + if err != nil { + return nil, gerror.New("无效的广告源ID") + } + + adSource, err := service.AdSource.GetAdSourceByID(ctx, id) + if err != nil { + return nil, err + } + if adSource == nil { + return nil, gerror.New("广告源不存在") + } + + return &dto.GetAdSourceRes{ + AdSource: adSource, + }, nil +} + +// GetList 获取广告源列表 +func (c *cAdSource) GetList(ctx context.Context, req *dto.ListAdSourceReq) (res *dto.ListAdSourceRes, err error) { + adSources, err := service.AdSource.GetAvailableSources(ctx) + if err != nil { + return nil, err + } + + return &dto.ListAdSourceRes{ + List: adSources, + Total: len(adSources), + }, nil +} diff --git a/controller/cid_controller.go b/controller/cid_controller.go new file mode 100644 index 0000000..3740428 --- /dev/null +++ b/controller/cid_controller.go @@ -0,0 +1,62 @@ +package controller + +import ( + "context" + + "cidService/model/dto" + "cidService/service" + "github.com/gogf/gf/v2/errors/gerror" +) + +var ( + CID = cCID{} +) + +type cCID struct{} + +// GenerateCID 生成CID广告 +func (c *cCID) GenerateCID(ctx context.Context, req *dto.GenerateCIDReq) (res *dto.GenerateCIDRes, err error) { + if req == nil { + return nil, gerror.New("请求参数不能为空") + } + + if req.RequestType == "" { + req.RequestType = "default" // 默认请求类型 + } + + result, err := service.CID.GenerateCID(ctx, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// GetStatistics 获取CID统计信息 +func (c *cCID) GetStatistics(ctx context.Context, req *dto.GetCIDStatisticsReq) (res *dto.GetCIDStatisticsRes, err error) { + if req == nil { + return nil, gerror.New("请求参数不能为空") + } + + result, err := service.CID.GetCIDStatistics(ctx, req) + if err != nil { + return nil, err + } + + return result, nil +} + +// GetCIDHistory 获取CID历史记录 +func (c *cCID) GetCIDHistory(ctx context.Context, req *dto.GetCIDHistoryReq) (res *dto.GetCIDHistoryRes, err error) { + if req == nil { + return nil, gerror.New("请求参数不能为空") + } + + // 查询历史记录 + history, err := service.CID.GetCIDHistory(ctx, 1, req.Page, req.Size) // 临时使用固定用户ID + if err != nil { + return nil, err + } + + return history, nil +} diff --git a/controller/strategy_controller.go b/controller/strategy_controller.go new file mode 100644 index 0000000..06c0fa6 --- /dev/null +++ b/controller/strategy_controller.go @@ -0,0 +1,78 @@ +package controller + +import ( + "context" + + "cidService/model/dto" + "cidService/service" + "github.com/gogf/gf/v2/errors/gerror" +) + +var ( + Strategy = cStrategy{} +) + +type cStrategy struct{} + +// Create 创建策略 +func (c *cStrategy) Create(ctx context.Context, req *dto.CreateStrategyReq) (res *dto.StrategyRes, err error) { + id, err := service.Strategy.CreateStrategy(ctx, req) + if err != nil { + return nil, err + } + + strategy, err := service.Strategy.GetStrategyByID(ctx, id) + if err != nil { + return nil, err + } + + return strategy, nil +} + +// Update 更新策略 +func (c *cStrategy) Update(ctx context.Context, req *dto.UpdateStrategyReq) (res *dto.StrategyRes, err error) { + affected, err := service.Strategy.UpdateStrategy(ctx, req) + if err != nil { + return nil, err + } + if affected == 0 { + return nil, gerror.New("策略更新失败") + } + + strategy, err := service.Strategy.GetStrategyByID(ctx, req.Id) + if err != nil { + return nil, err + } + + return strategy, nil +} + +// Delete 删除策略 +func (c *cStrategy) Delete(ctx context.Context, req *dto.DeleteStrategyReq) (res *dto.DeleteStrategyRes, err error) { + affected, err := service.Strategy.DeleteStrategy(ctx, req.Id) + if err != nil { + return nil, err + } + if affected == 0 { + return nil, gerror.New("策略删除失败") + } + + return &dto.DeleteStrategyRes{ + Success: true, + }, nil +} + +// GetByID 根据ID获取策略 +func (c *cStrategy) GetByID(ctx context.Context, req *dto.GetStrategyReq) (res *dto.StrategyRes, err error) { + strategy, err := service.Strategy.GetStrategyByID(ctx, req.Id) + if err != nil { + return nil, err + } + + return strategy, nil +} + +// GetList 获取策略列表 +func (c *cStrategy) GetList(ctx context.Context, req *dto.GetStrategyListReq) (res *dto.GetStrategyListRes, err error) { + return service.Strategy.GetStrategyList(ctx, req) +} diff --git a/dao/ad_source_dao.go b/dao/ad_source_dao.go new file mode 100644 index 0000000..2e2f918 --- /dev/null +++ b/dao/ad_source_dao.go @@ -0,0 +1,91 @@ +package dao + +import ( + "cidService/model/entity" + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +var ( + AdSource = adSourceDao{} +) + +type adSourceDao struct{} + +// GetByName 根据名称获取广告源 +func (d *adSourceDao) GetByName(ctx context.Context, name string) (adSource *entity.AdSource, err error) { + err = g.DB().Model("ad_sources"). + Where("name = ?", name). + Scan(&adSource) + return +} + +// GetAvailableSources 获取可用的广告源 +func (d *adSourceDao) GetAvailableSources(ctx context.Context) (list []*entity.AdSource, err error) { + err = g.DB().Model("ad_sources"). + Where("status = ?", "active"). + Order("priority DESC, created_at ASC"). + Scan(&list) + return +} + +// GetSourcesByProvider 根据提供商获取广告源 +func (d *adSourceDao) GetSourcesByProvider(ctx context.Context, provider string) (list []*entity.AdSource, err error) { + err = g.DB().Model("ad_sources"). + Where("provider = ? AND status = ?", provider, "active"). + Order("priority DESC"). + Scan(&list) + return +} + +// Create 创建广告源 +func (d *adSourceDao) Create(ctx context.Context, adSource *entity.AdSource) (id int64, err error) { + result, err := g.DB().Model("ad_sources").Insert(adSource) + if err != nil { + return 0, err + } + id, err = result.LastInsertId() + return +} + +// Update 更新广告源 +func (d *adSourceDao) Update(ctx context.Context, adSource *entity.AdSource) (affected int64, err error) { + result, err := g.DB().Model("ad_sources"). + Where("id = ?", adSource.Id). + Update(adSource) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + +// Delete 删除广告源 +func (d *adSourceDao) Delete(ctx context.Context, id int64) (affected int64, err error) { + result, err := g.DB().Model("ad_sources"). + Where("id = ?", id). + Delete() + if err != nil { + return 0, err + } + return result.RowsAffected() +} + +// GetByID 根据ID获取广告源 +func (d *adSourceDao) GetByID(ctx context.Context, id int64) (adSource *entity.AdSource, err error) { + err = g.DB().Model("ad_sources"). + Where("id = ?", id). + Scan(&adSource) + return +} + +// UpdateFields 更新广告源部分字段 +func (d *adSourceDao) UpdateFields(ctx context.Context, id int64, data *entity.AdSource) (affected int64, err error) { + result, err := g.DB().Model("ad_sources"). + Where("id = ?", id). + Update(data) + if err != nil { + return 0, err + } + return result.RowsAffected() +} diff --git a/dao/cid_request_dao.go b/dao/cid_request_dao.go new file mode 100644 index 0000000..48fcb25 --- /dev/null +++ b/dao/cid_request_dao.go @@ -0,0 +1,83 @@ +package dao + +import ( + "cidService/model/entity" + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +var ( + CIDRequest = cidRequestDao{} +) + +type cidRequestDao struct{} + +// Create 创建CID请求记录 +func (d *cidRequestDao) Create(ctx context.Context, request *entity.CidRequest) (id int64, err error) { + result, err := g.DB().Model("cid_requests").Insert(request) + if err != nil { + return 0, err + } + id, err = result.LastInsertId() + return +} + +// GetHistory 获取CID请求历史 +func (d *cidRequestDao) GetHistory(ctx context.Context, userId int64, page, size int) (list []*entity.CidRequest, total int64, err error) { + model := g.DB().Model("cid_requests") + + // 根据用户ID筛选 + model = model.Where("user_id = ?", userId) + + // 获取总数 + count, err := model.Count() + if err != nil { + return + } + total = int64(count) + + // 分页查询 + offset := (page - 1) * size + err = model.Order("created_at DESC"). + Offset(offset). + Limit(size). + Scan(&list) + + return +} + +// GetStatistics 获取统计信息 +func (d *cidRequestDao) GetStatistics(ctx context.Context, userId int64) (stats map[string]interface{}, err error) { + stats = make(map[string]interface{}) + + // 总请求数 + totalRequests, err := g.DB().Model("cid_requests"). + Where("user_id = ?", userId). + Count() + if err != nil { + return nil, err + } + stats["total_requests"] = totalRequests + + // 成功请求数 + successfulRequests, err := g.DB().Model("cid_requests"). + Where("user_id = ? AND status = ?", userId, "completed"). + Count() + if err != nil { + return nil, err + } + stats["successful_requests"] = successfulRequests + + // 平均处理时间 + avgProcessTime, err := g.DB().Model("cid_requests"). + Fields("AVG(process_time)"). + Where("user_id = ?", userId). + Value() + if err != nil { + return nil, err + } + stats["average_process_time"] = avgProcessTime + + return +} diff --git a/dao/strategy_dao.go b/dao/strategy_dao.go new file mode 100644 index 0000000..35ffbf0 --- /dev/null +++ b/dao/strategy_dao.go @@ -0,0 +1,100 @@ +package dao + +import ( + "cidService/model/entity" + "context" + + "github.com/gogf/gf/v2/frame/g" +) + +var ( + Strategy = strategyDao{} +) + +type strategyDao struct{} + +// GetByName 根据名称获取策略 +func (d *strategyDao) GetByName(ctx context.Context, name string) (strategy *entity.Strategy, err error) { + err = g.DB().Model("strategies"). + Where("name = ?", name). + Scan(&strategy) + return +} + +// GetByID 根据ID获取策略 +func (d *strategyDao) GetByID(ctx context.Context, id int64) (strategy *entity.Strategy, err error) { + err = g.DB().Model("strategies"). + Where("id = ?", id). + Scan(&strategy) + return +} + +// GetByTenantLevel 根据租户级别获取策略 +func (d *strategyDao) GetByTenantLevel(ctx context.Context, tenantLevel string) (strategy *entity.Strategy, err error) { + err = g.DB().Model("strategies"). + Where("tenant_level = ? AND status = ?", tenantLevel, "active"). + Order("priority DESC, created_at ASC"). + Scan(&strategy) + return +} + +// Create 创建策略 +func (d *strategyDao) Create(ctx context.Context, strategy *entity.Strategy) (id int64, err error) { + result, err := g.DB().Model("strategies").Insert(strategy) + if err != nil { + return 0, err + } + id, err = result.LastInsertId() + return +} + +// Update 更新策略 +func (d *strategyDao) Update(ctx context.Context, strategy *entity.Strategy) (affected int64, err error) { + result, err := g.DB().Model("strategies"). + Where("id = ?", strategy.Id). + Update(strategy) + if err != nil { + return 0, err + } + return result.RowsAffected() +} + +// Delete 删除策略 +func (d *strategyDao) Delete(ctx context.Context, id int64) (affected int64, err error) { + result, err := g.DB().Model("strategies"). + Where("id = ?", id). + Delete() + if err != nil { + return 0, err + } + return result.RowsAffected() +} + +// GetList 获取策略列表 +func (d *strategyDao) GetList(ctx context.Context, page, size int, tenantLevel, status string) (list []*entity.Strategy, total int64, err error) { + model := g.DB().Model("strategies") + + // 筛选条件 + if tenantLevel != "" { + model = model.Where("tenant_level = ?", tenantLevel) + } + if status != "" { + model = model.Where("status = ?", status) + } + + // 获取总数 + count, err := model.Count() + if err != nil { + return + } + total = int64(count) + + // 分页查询 + offset := (page - 1) * size + err = model.Order("priority DESC, created_at DESC"). + Offset(offset). + Limit(size). + Scan(&list) + + return +} diff --git a/database/init.sql b/database/init.sql new file mode 100644 index 0000000..a31a3d2 --- /dev/null +++ b/database/init.sql @@ -0,0 +1,78 @@ +-- CID服务商项目数据库初始化脚本 + +-- 广告源表 +CREATE TABLE IF NOT EXISTS `ad_sources` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(100) NOT NULL COMMENT '广告源名称', + `provider` varchar(50) NOT NULL COMMENT '提供商: self, google, facebook, etc.', + `api_endpoint` varchar(255) DEFAULT NULL COMMENT 'API端点', + `api_key` varchar(255) DEFAULT NULL COMMENT 'API密钥', + `config` text COMMENT '配置信息(JSON格式)', + `status` varchar(20) NOT NULL DEFAULT 'active' COMMENT '状态: active, inactive', + `priority` int(11) NOT NULL DEFAULT 0 COMMENT '优先级', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `created_by` bigint(20) DEFAULT NULL COMMENT '创建人', + `updated_by` bigint(20) DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_ad_sources_name` (`name`), + KEY `idx_ad_sources_provider` (`provider`), + KEY `idx_ad_sources_status` (`status`), + KEY `idx_ad_sources_priority` (`priority`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='广告源表'; + +-- 策略表 +CREATE TABLE IF NOT EXISTS `strategies` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `name` varchar(100) NOT NULL COMMENT '策略名称', + `description` varchar(500) DEFAULT NULL COMMENT '描述', + `tenant_level` varchar(20) NOT NULL COMMENT '适用租户级别: basic, standard, premium', + `min_conversion` decimal(5,4) NOT NULL DEFAULT 0.0000 COMMENT '最低转化率', + `max_conversion` decimal(5,4) NOT NULL DEFAULT 1.0000 COMMENT '最高转化率', + `source_weights` text COMMENT '广告源权重配置(JSON格式)', + `max_ads_per_req` int(11) NOT NULL DEFAULT 5 COMMENT '每次请求最大广告数', + `priority` int(11) NOT NULL DEFAULT 0 COMMENT '优先级', + `status` varchar(20) NOT NULL DEFAULT 'active' COMMENT '状态: active, inactive', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + `created_by` bigint(20) DEFAULT NULL COMMENT '创建人', + `updated_by` bigint(20) DEFAULT NULL COMMENT '更新人', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_strategies_name` (`name`), + KEY `idx_strategies_tenant_level` (`tenant_level`), + KEY `idx_strategies_status` (`status`), + KEY `idx_strategies_priority` (`priority`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='匹配策略表'; + +-- CID请求记录表 +CREATE TABLE IF NOT EXISTS `cid_requests` ( + `id` bigint(20) NOT NULL AUTO_INCREMENT, + `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', + `user_id` bigint(20) NOT NULL COMMENT '用户ID', + `request_type` varchar(50) NOT NULL COMMENT '请求类型', + `parameters` text COMMENT '请求参数(JSON格式)', + `response_data` text COMMENT '响应数据(JSON格式)', + `status` varchar(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending, completed, failed', + `process_time` int(11) DEFAULT NULL COMMENT '处理时间(毫秒)', + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', + PRIMARY KEY (`id`), + KEY `idx_cid_requests_tenant_id` (`tenant_id`), + KEY `idx_cid_requests_user_id` (`user_id`), + KEY `idx_cid_requests_status` (`status`), + KEY `idx_cid_requests_created_at` (`created_at`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CID请求记录表'; + +-- 插入默认广告源 +INSERT INTO `ad_sources` (`name`, `provider`, `status`, `priority`, `created_by`, `updated_by`) VALUES +('自营广告', 'self', 'active', 100, 1, 1), +('Google Ads', 'google', 'active', 80, 1, 1), +('Facebook Ads', 'facebook', 'active', 70, 1, 1) +ON DUPLICATE KEY UPDATE `updated_at` = CURRENT_TIMESTAMP; + +-- 插入默认策略 +INSERT INTO `strategies` (`name`, `description`, `tenant_level`, `min_conversion`, `max_conversion`, `source_weights`, `max_ads_per_req`, `priority`, `status`, `created_by`, `updated_by`) VALUES +('基础策略', '适用于基础租户的默认策略', 'basic', 0.0100, 0.0500, '{"self": 100, "google": 0, "facebook": 0}', 3, 10, 'active', 1, 1), +('标准策略', '适用于标准租户的默认策略', 'standard', 0.0500, 0.1500, '{"self": 70, "google": 20, "facebook": 10}', 5, 20, 'active', 1, 1), +('高级策略', '适用于高级租户的默认策略', 'premium', 0.1500, 1.0000, '{"self": 40, "google": 30, "facebook": 30}', 10, 30, 'active', 1, 1) +ON DUPLICATE KEY UPDATE `updated_at` = CURRENT_TIMESTAMP; \ No newline at end of file diff --git a/model/dto/ad_source_dto.go b/model/dto/ad_source_dto.go new file mode 100644 index 0000000..83e44fb --- /dev/null +++ b/model/dto/ad_source_dto.go @@ -0,0 +1,300 @@ +package dto + +import ( + "cidService/model/entity" + + "gitee.com/red-future---jilin-g/common/http" + "github.com/gogf/gf/v2/frame/g" +) + +// CreateAdSourceReq 创建广告源请求 +type CreateAdSourceReq struct { + g.Meta `path:"/adsource/create" method:"post" tags:"广告源管理" summary:"创建广告源" dc:"创建新的广告源配置"` + + // 基本信息 + Name string `json:"name" v:"required"` // 广告源名称 + Code string `json:"code" v:"required"` // 广告源编码,唯一标识 + Provider string `json:"provider" v:"required"` // 提供商:google、facebook、baidu、tencent、self等 + Type string `json:"type" v:"required|in:self,third_party,exchange"` // 类型 + Description string `json:"description"` // 描述 + + // 连接配置 + APIEndpoint string `json:"apiEndpoint" v:"required"` // API端点 + APIVersion string `json:"apiVersion"` // API版本 + AuthType string `json:"authType" v:"required|in:api_key,oauth,basic"` // 认证类型 + AuthConfig map[string]interface{} `json:"authConfig" v:"required"` // 认证配置 + Headers map[string]string `json:"headers"` // 请求头配置 + Timeout int `json:"timeout"` // 超时时间(毫秒) + RetryCount int `json:"retryCount"` // 重试次数 + + // 基础配置 + SupportedFormats []string `json:"supportedFormats" v:"required"` // 支持的广告格式 + SupportedSizes []string `json:"supportedSizes"` // 支持的尺寸 + SupportedDevices []string `json:"supportedDevices"` // 支持的设备类型 + SupportedOS []string `json:"supportedOS"` // 支持的操作系统 + SupportedCountries []string `json:"supportedCountries"` // 支持的国家/地区 + + // 竞价配置 + BiddingType string `json:"biddingType" v:"required|in:cpm,cpc,cpa,rtb"` // 竞价类型 + MinBidAmount int64 `json:"minBidAmount"` // 最小出价(分) + MaxBidAmount int64 `json:"maxBidAmount"` // 最大出价(分) + BidIncrement int64 `json:"bidIncrement"` // 出价增量(分) + DefaultBidAmount int64 `json:"defaultBidAmount"` // 默认出价(分) + AutoOptimization bool `json:"autoOptimization"` // 是否自动优化 + + // 最大广告数 + MaxAdsPerRequest int `json:"maxAdsPerRequest"` // 单次请求最大广告数量 + + // 其他配置 + BrandSafety bool `json:"brandSafety"` // 品牌安全 + Viewability bool `json:"viewability"` // 可见性支持 + RealTimeBidding bool `json:"realTimeBidding"` // 实时竞价 + HeaderBidding bool `json:"headerBidding"` // 标题竞价 + + // 财务设置 + BillingModel string `json:"billingModel" v:"required|in:cpm,cpc,cpa,rev_share"` // 计费模式 + PaymentTerms string `json:"paymentTerms" v:"in:net_30,net_60,net_90"` // 支付条款 + RevShareRate float64 `json:"revShareRate" v:"between:0,1"` // 收入分成比例 + MinPayment int64 `json:"minPayment"` // 最小支付金额(分) + Currency string `json:"currency"` // 货币单位 + TaxInclusive bool `json:"taxInclusive"` // 是否含税 + + // 系统信息 + Priority int `json:"priority"` // 优先级 +} + +type CreateAdSourceRes struct { + Id string `json:"id"` // 广告源ID +} + +// GetAdSourceReq 获取广告源详情请求 +type GetAdSourceReq struct { + g.Meta `path:"/adsource/one" method:"get" tags:"广告源管理" summary:"获取广告源详情" dc:"根据ID获取单个广告源详情"` + Id string `json:"id" v:"required"` // 广告源ID +} + +type GetAdSourceRes struct { + *entity.AdSource +} + +// ListAdSourceReq 获取广告源列表请求 +type ListAdSourceReq struct { + g.Meta `path:"/adsource/list" method:"get" tags:"广告源管理" summary:"获取广告源列表" dc:"分页查询广告源列表,支持多条件筛选"` + http.Page + + Name string `json:"name"` // 广告源名称模糊查询 + Code string `json:"code"` // 广告源编码 + Provider string `json:"provider"` // 提供商 + Type string `json:"type"` // 类型 + Status string `json:"status"` // 状态 + Health string `json:"health"` // 健康状态 +} + +type ListAdSourceRes struct { + List []*entity.AdSource `json:"list"` + Total int `json:"total"` +} + +// UpdateAdSourceReq 更新广告源请求 +type UpdateAdSourceReq struct { + g.Meta `path:"/adsource/update" method:"post" tags:"广告源管理" summary:"更新广告源" dc:"更新广告源信息"` + + Id string `json:"id" v:"required"` // 广告源ID + + // 基本信息 + Name string `json:"name"` // 广告源名称 + Description string `json:"description"` // 描述 + + // 连接配置 + APIEndpoint string `json:"apiEndpoint"` // API端点 + APIVersion string `json:"apiVersion"` // API版本 + AuthType string `json:"authType"` // 认证类型 + AuthConfig map[string]interface{} `json:"authConfig"` // 认证配置 + Headers map[string]string `json:"headers"` // 请求头配置 + Timeout int `json:"timeout"` // 超时时间(毫秒) + RetryCount int `json:"retryCount"` // 重试次数 + + // 基础配置 + SupportedFormats []string `json:"supportedFormats"` // 支持的广告格式 + SupportedSizes []string `json:"supportedSizes"` // 支持的尺寸 + SupportedDevices []string `json:"supportedDevices"` // 支持的设备类型 + SupportedOS []string `json:"supportedOS"` // 支持的操作系统 + SupportedCountries []string `json:"supportedCountries"` // 支持的国家/地区 + + // 竞价配置 + BiddingType string `json:"biddingType"` // 竞价类型 + MinBidAmount int64 `json:"minBidAmount"` // 最小出价(分) + MaxBidAmount int64 `json:"maxBidAmount"` // 最大出价(分) + BidIncrement int64 `json:"bidIncrement"` // 出价增量(分) + DefaultBidAmount int64 `json:"defaultBidAmount"` // 默认出价(分) + AutoOptimization bool `json:"autoOptimization"` // 是否自动优化 + + // 最大广告数 + MaxAdsPerRequest int `json:"maxAdsPerRequest"` // 单次请求最大广告数量 + + // 其他配置 + BrandSafety bool `json:"brandSafety"` // 品牌安全 + Viewability bool `json:"viewability"` // 可见性支持 + RealTimeBidding bool `json:"realTimeBidding"` // 实时竞价 + HeaderBidding bool `json:"headerBidding"` // 标题竞价 + + // 财务设置 + BillingModel string `json:"billingModel"` // 计费模式 + PaymentTerms string `json:"paymentTerms"` // 支付条款 + RevShareRate float64 `json:"revShareRate"` // 收入分成比例 + MinPayment int64 `json:"minPayment"` // 最小支付金额(分) + Currency string `json:"currency"` // 货币单位 + TaxInclusive bool `json:"taxInclusive"` // 是否含税 + + // 系统信息 + Priority int `json:"priority"` // 优先级 +} + +// UpdateAdSourceStatusReq 更新广告源状态请求 +type UpdateAdSourceStatusReq struct { + g.Meta `path:"/adsource/status" method:"post" tags:"广告源管理" summary:"更新广告源状态" dc:"更新广告源状态"` + + Id string `json:"id" v:"required"` // 广告源ID + Status string `json:"status" v:"required"` // 广告源状态:active、inactive、maintenance +} + +// DeleteAdSourceReq 删除广告源请求 +type DeleteAdSourceReq struct { + g.Meta `path:"/adsource/delete" method:"post" tags:"广告源管理" summary:"删除广告源" dc:"删除指定的广告源"` + + Id string `json:"id" v:"required"` // 广告源ID +} + +// TestAdSourceReq 测试广告源连接请求 +type TestAdSourceReq struct { + g.Meta `path:"/adsource/test" method:"post" tags:"广告源管理" summary:"测试广告源连接" dc:"测试广告源的连接性和可用性"` + + Id string `json:"id" v:"required"` // 广告源ID +} + +type TestAdSourceRes struct { + Success bool `json:"success"` // 测试是否成功 + ResponseTime int64 `json:"responseTime"` // 响应时间(毫秒) + ErrorMessage string `json:"errorMessage"` // 错误信息 + SupportedFormats []string `json:"supportedFormats"` // 支持的广告格式 + QualityMetrics *AdSourceTestMetrics `json:"qualityMetrics"` // 质量指标 +} + +// DeleteAdSourceRes 删除广告源响应 +type DeleteAdSourceRes struct { + Success bool `json:"success"` // 删除是否成功 +} + +// AdSourceTestMetrics 广告源测试质量指标 +type AdSourceTestMetrics struct { + SuccessRate float64 `json:"successRate"` // 成功率 + AverageResponseTime float64 `json:"averageResponseTime"` // 平均响应时间(毫秒) + FillRate float64 `json:"fillRate"` // 填充率 + CTR float64 `json:"ctr"` // 点击率 +} + +// GetAdSourceStatisticsReq 获取广告源统计数据请求 +type GetAdSourceStatisticsReq struct { + g.Meta `path:"/adsource/statistics" method:"get" tags:"广告源管理" summary:"获取广告源统计数据" dc:"获取广告源的详细统计数据"` + + Id string `json:"id" v:"required"` // 广告源ID + StartDate int64 `json:"startDate" v:"required"` // 开始日期 + EndDate int64 `json:"endDate" v:"required"` // 结束日期 + Dimension string `json:"dimension"` // 统计维度:day、week、month +} + +type GetAdSourceStatisticsRes struct { + // 概览数据 + Overview AdSourceOverviewStats `json:"overview"` + + // 趋势数据 + Trends []AdSourceTrendData `json:"trends"` + + // 性能数据 + Performance AdSourcePerformanceStats `json:"performance"` + + // 错误统计 + Errors []AdSourceErrorStats `json:"errors"` +} + +// AdSourceOverviewStats 广告源概览统计 +type AdSourceOverviewStats struct { + TotalRequests int64 `json:"totalRequests"` // 总请求数 + SuccessfulRequests int64 `json:"successfulRequests"` // 成功请求数 + FailedRequests int64 `json:"failedRequests"` // 失败请求数 + TotalImpressions int64 `json:"totalImpressions"` // 总展示次数 + TotalClicks int64 `json:"totalClicks"` // 总点击次数 + TotalConversions int64 `json:"totalConversions"` // 总转化次数 + TotalRevenue int64 `json:"totalRevenue"` // 总收入(分) + SuccessRate float64 `json:"successRate"` // 成功率 + ErrorRate float64 `json:"errorRate"` // 错误率 + CTR float64 `json:"ctr"` // 点击率 + CVR float64 `json:"cvr"` // 转化率 + FillRate float64 `json:"fillRate"` // 填充率 + eCPM int64 `json:"ecpm"` // 有效千次展示成本(分) + eCPC int64 `json:"ecpc"` // 有效点击成本(分) + AverageResponseTime float64 `json:"averageResponseTime"` // 平均响应时间(毫秒) + Uptime float64 `json:"uptime"` // 可用性(百分比) +} + +// AdSourceTrendData 广告源趋势数据 +type AdSourceTrendData struct { + Date int64 `json:"date"` // 日期 + Requests int64 `json:"requests"` // 请求数 + Impressions int64 `json:"impressions"` // 展示次数 + Clicks int64 `json:"clicks"` // 点击次数 + Conversions int64 `json:"conversions"` // 转化次数 + Revenue int64 `json:"revenue"` // 收入(分) + SuccessRate float64 `json:"successRate"` // 成功率 + ErrorRate float64 `json:"errorRate"` // 错误率 + CTR float64 `json:"ctr"` // 点击率 + CVR float64 `json:"cvr"` // 转化率 + FillRate float64 `json:"fillRate"` // 填充率 + eCPM int64 `json:"ecpm"` // 有效千次展示成本(分) + ResponseTime float64 `json:"responseTime"` // 平均响应时间(毫秒) +} + +// AdSourcePerformanceStats 广告源性能统计 +type AdSourcePerformanceStats struct { + // 响应时间分布 + ResponseTimeDistribution map[string]int64 `json:"responseTimeDistribution"` // 响应时间分布 + + // 错误类型统计 + ErrorTypes map[string]int64 `json:"errorTypes"` // 错误类型统计 + + // 地区性能 + RegionalPerformance map[string]AdSourceRegionalStats `json:"regionalPerformance"` // 地区性能 + + // 设备性能 + DevicePerformance map[string]AdSourceDeviceStats `json:"devicePerformance"` // 设备性能 +} + +// AdSourceErrorStats 广告源错误统计 +type AdSourceErrorStats struct { + ErrorType string `json:"errorType"` // 错误类型 + Count int64 `json:"count"` // 错误次数 + Percentage float64 `json:"percentage"` // 占比 + LastOccurred int64 `json:"lastOccurred"` // 最后发生时间 +} + +// AdSourceRegionalStats 广告源地区统计 +type AdSourceRegionalStats struct { + Region string `json:"region"` // 地区 + Requests int64 `json:"requests"` // 请求数 + Impressions int64 `json:"impressions"` // 展示次数 + Clicks int64 `json:"clicks"` // 点击次数 + Revenue int64 `json:"revenue"` // 收入(分) + CTR float64 `json:"ctr"` // 点击率 + ResponseTime float64 `json:"responseTime"` // 平均响应时间(毫秒) +} + +// AdSourceDeviceStats 广告源设备统计 +type AdSourceDeviceStats struct { + Device string `json:"device"` // 设备类型 + Requests int64 `json:"requests"` // 请求数 + Impressions int64 `json:"impressions"` // 展示次数 + Clicks int64 `json:"clicks"` // 点击次数 + Revenue int64 `json:"revenue"` // 收入(分) + CTR float64 `json:"ctr"` // 点击率 + ResponseTime float64 `json:"responseTime"` // 平均响应时间(毫秒) +} diff --git a/model/dto/cid_dto.go b/model/dto/cid_dto.go new file mode 100644 index 0000000..35474aa --- /dev/null +++ b/model/dto/cid_dto.go @@ -0,0 +1,81 @@ +package dto + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +// GenerateCIDReq 生成CID请求 +type GenerateCIDReq struct { + g.Meta `path:"/cid/generate" method:"post" tags:"CID服务" summary:"生成CID广告" dc:"为当前用户生成CID广告"` + UserId int64 `json:"user_id"` // 用户ID(可选,如果不提供则从token获取) + RequestType string `json:"request_type"` // 请求类型 + Parameters map[string]interface{} `json:"parameters"` // 请求参数 + Position string `json:"position"` // 广告位置 + Count int `json:"count"` // 广告数量 +} + +// AdInfo 广告信息 +type AdInfo struct { + Id int64 `json:"id"` // 广告ID + Title string `json:"title"` // 广告标题 + Description string `json:"description"` // 广告描述 + ImageUrl string `json:"image_url"` // 广告图片URL + TargetUrl string `json:"target_url"` // 目标链接 + ConversionRate float64 `json:"conversion_rate"` // 转化率 + Source string `json:"source"` // 广告源 + Bid int `json:"bid"` // 出价(分) +} + +// GenerateCIDRes 生成CID响应 +type GenerateCIDRes struct { + CID string `json:"cid"` // 唯一CID + Ads []*AdInfo `json:"ads"` // 广告列表 + TotalAds int `json:"total_ads"` // 总广告数 + TenantId int64 `json:"tenant_id"` // 租户ID + TenantName string `json:"tenant_name"` // 租户名称 + GeneratedAt string `json:"generated_at"` // 生成时间 +} + +// GetCIDStatisticsReq 获取CID统计请求 +type GetCIDStatisticsReq struct { + g.Meta `path:"/cid/statistics" method:"get" tags:"CID服务" summary:"获取CID统计" dc:"获取CID服务的统计信息"` + UserId int64 `json:"user_id"` // 用户ID(可选) + TenantId int64 `json:"tenant_id"` // 租户ID(可选) + DateFrom int64 `json:"date_from"` // 开始日期 + DateTo int64 `json:"date_to"` // 结束日期 +} + +// GetCIDStatisticsRes 获取CID统计响应 +type GetCIDStatisticsRes struct { + TotalRequests int64 `json:"total_requests"` // 总请求数 + SuccessfulReq int64 `json:"successful_requests"` // 成功请求数 + AverageProcessTime float64 `json:"average_process_time"` // 平均处理时间 + TopSources []string `json:"top_sources"` // 主要广告源 + ConversionStats map[string]float64 `json:"conversion_stats"` // 转化率统计 +} + +// CIDRequestHistory CID请求历史记录 +type CIDRequestHistory struct { + Id int64 `json:"id"` // 记录ID + TenantId int64 `json:"tenant_id"` // 租户ID + UserId int64 `json:"user_id"` // 用户ID + RequestType string `json:"request_type"` // 请求类型 + Status string `json:"status"` // 状态 + ProcessTime int `json:"process_time"` // 处理时间(ms) + CreatedAt string `json:"created_at"` // 创建时间 +} + +// GetCIDHistoryReq 获取CID历史请求 +type GetCIDHistoryReq struct { + g.Meta `path:"/cid/history" method:"get" tags:"CID服务" summary:"获取CID历史记录" dc:"分页获取用户的CID请求历史"` + Page int `json:"page" v:"required|min:1"` // 页码 + Size int `json:"size" v:"required|min:1|max:100"` // 每页数量 +} + +// GetCIDHistoryRes 获取CID历史响应 +type GetCIDHistoryRes struct { + List []*CIDRequestHistory `json:"list"` // 历史记录列表 + Total int64 `json:"total"` // 总数 + Page int `json:"page"` // 当前页 + Size int `json:"size"` // 每页数量 +} diff --git a/model/dto/strategy_dto.go b/model/dto/strategy_dto.go new file mode 100644 index 0000000..c85488c --- /dev/null +++ b/model/dto/strategy_dto.go @@ -0,0 +1,86 @@ +package dto + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +// CreateStrategyReq 创建策略请求 +type CreateStrategyReq struct { + g.Meta `path:"/strategy/create" method:"post" tags:"策略管理" summary:"创建匹配策略" dc:"创建新的广告匹配策略"` + Name string `json:"name" v:"required|length:3,50"` // 策略名称 + Description string `json:"description" v:"max:500"` // 描述 + TenantLevel string `json:"tenant_level" v:"required|in:basic,standard,premium"` // 租户级别 + MinConversion float64 `json:"min_conversion" v:"required|min:0|max:1"` // 最低转化率 + MaxConversion float64 `json:"max_conversion" v:"required|min:0|max:1"` // 最高转化率 + SourceWeights map[string]int `json:"source_weights" v:"required"` // 广告源权重 + MaxAdsPerReq int `json:"max_ads_per_req" v:"required|min:1|max:50"` // 每次请求最大广告数 + Priority int `json:"priority" v:"required|min:0|max:100"` // 优先级 + Status string `json:"status" v:"required|in:active,inactive"` // 状态 +} + +// UpdateStrategyReq 更新策略请求 +type UpdateStrategyReq struct { + g.Meta `path:"/strategy/update" method:"put" tags:"策略管理" summary:"更新匹配策略" dc:"更新现有的广告匹配策略"` + Id int64 `json:"id" v:"required"` // 策略ID + Name string `json:"name" v:"required|length:3,50"` // 策略名称 + Description string `json:"description" v:"max:500"` // 描述 + TenantLevel string `json:"tenant_level" v:"required|in:basic,standard,premium"` // 租户级别 + MinConversion float64 `json:"min_conversion" v:"required|min:0|max:1"` // 最低转化率 + MaxConversion float64 `json:"max_conversion" v:"required|min:0|max:1"` // 最高转化率 + SourceWeights map[string]int `json:"source_weights" v:"required"` // 广告源权重 + MaxAdsPerReq int `json:"max_ads_per_req" v:"required|min:1|max:50"` // 每次请求最大广告数 + Priority int `json:"priority" v:"required|min:0|max:100"` // 优先级 + Status string `json:"status" v:"required|in:active,inactive"` // 状态 +} + +// DeleteStrategyReq 删除策略请求 +type DeleteStrategyReq struct { + g.Meta `path:"/strategy/delete" method:"delete" tags:"策略管理" summary:"删除匹配策略" dc:"删除指定的广告匹配策略"` + Id int64 `json:"id" v:"required"` // 策略ID +} + +// GetStrategyReq 获取策略请求 +type GetStrategyReq struct { + g.Meta `path:"/strategy/info" method:"get" tags:"策略管理" summary:"获取策略详情" dc:"获取指定策略的详细信息"` + Id int64 `json:"id" v:"required"` // 策略ID +} + +// GetStrategyListReq 获取策略列表请求 +type GetStrategyListReq struct { + g.Meta `path:"/strategy/list" method:"get" tags:"策略管理" summary:"获取策略列表" dc:"分页获取策略列表"` + Page int `json:"page" v:"required|min:1"` // 页码 + Size int `json:"size" v:"required|min:1|max:100"` // 每页数量 + TenantLevel string `json:"tenant_level"` // 租户级别筛选 + Status string `json:"status"` // 状态筛选 +} + +// StrategyRes 策略响应 +type StrategyRes struct { + Id int64 `json:"id"` // ID + Name string `json:"name"` // 策略名称 + Description string `json:"description"` // 描述 + TenantLevel string `json:"tenant_level"` // 租户级别 + MinConversion float64 `json:"min_conversion"` // 最低转化率 + MaxConversion float64 `json:"max_conversion"` // 最高转化率 + SourceWeights map[string]int `json:"source_weights"` // 广告源权重 + MaxAdsPerReq int `json:"max_ads_per_req"` // 每次请求最大广告数 + Priority int `json:"priority"` // 优先级 + Status string `json:"status"` // 状态 + CreatedAt string `json:"created_at"` // 创建时间 + UpdatedAt string `json:"updated_at"` // 更新时间 + CreatedBy int64 `json:"created_by"` // 创建人 + UpdatedBy int64 `json:"updated_by"` // 更新人 +} + +// GetStrategyListRes 获取策略列表响应 +type GetStrategyListRes struct { + List []*StrategyRes `json:"list"` // 策略列表 + Total int64 `json:"total"` // 总数 + Page int `json:"page"` // 当前页 + Size int `json:"size"` // 每页数量 +} + +// DeleteStrategyRes 删除策略响应 +type DeleteStrategyRes struct { + Success bool `json:"success"` // 是否成功 +} diff --git a/model/entity/ad_source.go b/model/entity/ad_source.go new file mode 100644 index 0000000..ebcb649 --- /dev/null +++ b/model/entity/ad_source.go @@ -0,0 +1,163 @@ +package entity + +import ( + "gitee.com/red-future---jilin-g/common/do" +) + +const AdSourceCollection = "ad_source" + +// AdSource 广告源实体 +type AdSource struct { + do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + + // 基本信息 + Name string `bson:"name" json:"name"` // 广告源名称 + Code string `bson:"code" json:"code"` // 广告源编码,唯一标识 + Provider string `bson:"provider" json:"provider"` // 提供商:google、facebook、baidu、tencent、self等 + Type string `bson:"type" json:"type"` // 类型:self(自营)、third_party(第三方)、exchange(广告交易平台) + Description string `bson:"description" json:"description"` // 描述 + + // 连接配置 + Config *AdSourceConfig `bson:"config" json:"config"` // 广告源配置 + + // API配置 + APIEndpoint string `bson:"apiEndpoint" json:"apiEndpoint"` // API端点 + APIVersion string `bson:"apiVersion" json:"apiVersion"` // API版本 + AuthType string `bson:"authType" json:"authType"` // 认证类型:api_key、oauth、basic + AuthConfig map[string]interface{} `bson:"authConfig" json:"authConfig"` // 认证配置 + Headers map[string]string `bson:"headers" json:"headers"` // 请求头配置 + Timeout int `bson:"timeout" json:"timeout"` // 超时时间(毫秒) + RetryCount int `bson:"retryCount" json:"retryCount"` // 重试次数 + + // 广告源能力 + Capabilities *AdSourceCapabilities `bson:"capabilities" json:"capabilities"` // 广告源能力 + + // 质量指标 + QualityMetrics *AdSourceQualityMetrics `bson:"qualityMetrics" json:"qualityMetrics"` // 质量指标 + + // 财务设置 + PaymentTerms *PaymentTerms `bson:"paymentTerms" json:"paymentTerms"` // 支付条款 + + // 状态信息 + Status string `bson:"status" json:"status"` // 广告源状态:active、inactive、maintenance + Health string `bson:"health" json:"health"` // 健康状态:healthy、degraded、unhealthy + LastCheckAt int64 `bson:"lastCheckAt" json:"lastCheckAt"` // 最后检查时间 + + // 统计信息 + TotalRequests int64 `bson:"totalRequests" json:"totalRequests"` // 总请求数 + SuccessfulRequests int64 `bson:"successfulRequests" json:"successfulRequests"` // 成功请求数 + FailedRequests int64 `bson:"failedRequests" json:"failedRequests"` // 失败请求数 + AverageResponseTime float64 `bson:"averageResponseTime" json:"averageResponseTime"` // 平均响应时间(毫秒) + FillRate float64 `bson:"fillRate" json:"fillRate"` // 填充率 + CTR float64 `bson:"ctr" json:"ctr"` // 点击率 + CVR float64 `bson:"cvr" json:"cvr"` // 转化率 + + // 系统信息 + Priority int `bson:"priority" json:"priority"` // 优先级,数值越高优先级越高 +} + +// AdSourceConfig 广告源配置 +type AdSourceConfig struct { + // 基础配置 + SupportedFormats []string `bson:"supportedFormats" json:"supportedFormats"` // 支持的广告格式 + SupportedSizes []string `bson:"supportedSizes" json:"supportedSizes"` // 支持的尺寸 + SupportedDevices []string `bson:"supportedDevices" json:"supportedDevices"` // 支持的设备类型 + SupportedOS []string `bson:"supportedOS" json:"supportedOS"` // 支持的操作系统 + SupportedCountries []string `bson:"supportedCountries" json:"supportedCountries"` // 支持的国家/地区 + + // 竞价配置 + BiddingType string `bson:"biddingType" json:"biddingType"` // 竞价类型:cpm、cpc、cpa、rtb + MinBidAmount int64 `bson:"minBidAmount" json:"minBidAmount"` // 最小出价(分) + MaxBidAmount int64 `bson:"maxBidAmount" json:"maxBidAmount"` // 最大出价(分) + BidIncrement int64 `bson:"bidIncrement" json:"bidIncrement"` // 出价增量(分) + DefaultBidAmount int64 `bson:"defaultBidAmount" json:"defaultBidAmount"` // 默认出价(分) + AutoOptimization bool `bson:"autoOptimization" json:"autoOptimization"` // 是否自动优化 + + // 定向配置 + TargetingSupport *TargetingSupport `bson:"targetingSupport" json:"targetingSupport"` // 定向支持 + + // 其他配置 + MaxAdsPerRequest int `bson:"maxAdsPerRequest" json:"maxAdsPerRequest"` // 单次请求最大广告数量 + BrandSafety bool `bson:"brandSafety" json:"brandSafety"` // 品牌安全 + Viewability bool `bson:"viewability" json:"viewability"` // 可见性支持 +} + +// TargetingSupport 定向支持 +type TargetingSupport struct { + GeoTargeting bool `bson:"geoTargeting" json:"geoTargeting"` // 地理定向 + DemographicTargeting bool `bson:"demographicTargeting" json:"demographicTargeting"` // 人口统计定向 + BehavioralTargeting bool `bson:"behavioralTargeting" json:"behavioralTargeting"` // 行为定向 + ContextualTargeting bool `bson:"contextualTargeting" json:"contextualTargeting"` // 上下文定向 + DeviceTargeting bool `bson:"deviceTargeting" json:"deviceTargeting"` // 设备定向 + TimeTargeting bool `bson:"timeTargeting" json:"timeTargeting"` // 时间定向 + Retargeting bool `bson:"retargeting" json:"retargeting"` // 重定向 + CookieTargeting bool `bson:"cookieTargeting" json:"cookieTargeting"` // Cookie定向 +} + +// AdSourceCapabilities 广告源能力 +type AdSourceCapabilities struct { + // 广告格式 + SupportedFormats []AdFormat `bson:"supportedFormats" json:"supportedFormats"` // 支持的广告格式 + + // 功能特性 + RealTimeBidding bool `bson:"realTimeBidding" json:"realTimeBidding"` // 实时竞价 + HeaderBidding bool `bson:"headerBidding" json:"headerBidding"` // 标题竞价 + ProgrammaticDirect bool `bson:"programmaticDirect" json:"programmaticDirect"` // 程序化直购 + PrivateMarketplace bool `bson:"privateMarketplace" json:"privateMarketplace"` // 私有交易市场 + + // 质量控制 + FraudDetection bool `bson:"fraudDetection" json:"fraudDetection"` // 反欺诈检测 + BrandSafety bool `bson:"brandSafety" json:"brandSafety"` // 品牌安全 + Viewability bool `bson:"viewability" json:"viewability"` // 可见度验证 + CreativeApproval bool `bson:"creativeApproval" json:"creativeApproval"` // 创意审核 + + // 数据能力 + AudienceTargeting bool `bson:"audienceTargeting" json:"audienceTargeting"` // 受众定向 + ContextualTargeting bool `bson:"contextualTargeting" json:"contextualTargeting"` // 上下文定向 + CrossDeviceTargeting bool `bson:"crossDeviceTargeting" json:"crossDeviceTargeting"` // 跨设备定向 +} + +// AdFormat 广告格式 +type AdFormat struct { + Type string `bson:"type" json:"type"` // 格式类型:banner、video、native、interstitial等 + Name string `bson:"name" json:"name"` // 格式名称 + Width int `bson:"width" json:"width"` // 宽度 + Height int `bson:"height" json:"height"` // 高度 + MimeType string `bson:"mimeType" json:"mimeType"` // MIME类型 +} + +// AdSourceQualityMetrics 广告源质量指标 +type AdSourceQualityMetrics struct { + // 性能指标 + AverageResponseTime float64 `bson:"averageResponseTime" json:"averageResponseTime"` // 平均响应时间(毫秒) + SuccessRate float64 `bson:"successRate" json:"successRate"` // 成功率 + ErrorRate float64 `bson:"errorRate" json:"errorRate"` // 错误率 + Uptime float64 `bson:"uptime" json:"uptime"` // 可用性(百分比) + + // 广告质量 + CTR float64 `bson:"ctr" json:"ctr"` // 点击率 + CVR float64 `bson:"cvr" json:"cvr"` // 转化率 + FillRate float64 `bson:"fillRate" json:"fillRate"` // 填充率 + ViewabilityRate float64 `bson:"viewabilityRate" json:"viewabilityRate"` // 可见率 + BrandSafetyScore float64 `bson:"brandSafetyScore" json:"brandSafetyScore"` // 品牌安全评分 + + // 财务指标 + eCPM int64 `bson:"ecpm" json:"ecpm"` // 有效千次展示成本(分) + eCPC int64 `bson:"ecpc" json:"ecpc"` // 有效点击成本(分) + RevenuePerRequest int64 `bson:"revenuePerRequest" json:"revenuePerRequest"` // 每请求收入(分) + + // 时间指标 + LastUpdated int64 `bson:"lastUpdated" json:"lastUpdated"` // 最后更新时间 + MetricsUpdateWindow int `bson:"metricsUpdateWindow" json:"metricsUpdateWindow"` // 指标更新窗口(分钟) +} + +// PaymentTerms 支付条款 +type PaymentTerms struct { + BillingModel string `bson:"billingModel" json:"billingModel"` // 计费模式:cpm、cpc、cpa、rev_share + PaymentTerms string `bson:"paymentTerms" json:"paymentTerms"` // 支付条款:net_30、net_60、net_90 + RevShareRate float64 `bson:"revShareRate" json:"revShareRate"` // 收入分成比例(0-1) + MinPayment int64 `bson:"minPayment" json:"minPayment"` // 最小支付金额(分) + Currency string `bson:"currency" json:"currency"` // 货币单位 + TaxInclusive bool `bson:"taxInclusive" json:"taxInclusive"` // 是否含税 + EarlyPaymentDiscount float64 `bson:"earlyPaymentDiscount" json:"earlyPaymentDiscount"` // 提前付款折扣 +} diff --git a/model/entity/cid_request.go b/model/entity/cid_request.go new file mode 100644 index 0000000..54e0297 --- /dev/null +++ b/model/entity/cid_request.go @@ -0,0 +1,312 @@ +package entity + +import ( + "gitee.com/red-future---jilin-g/common/do" +) + +const CidRequestCollection = "cid_request" + +// CidRequest CID请求实体 +type CidRequest struct { + do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + + // 请求信息 + RequestID string `bson:"requestId" json:"requestId"` // 请求唯一ID + SessionID string `bson:"sessionId" json:"sessionId"` // 会话ID + UserID string `bson:"userId" json:"userId"` // 用户ID + TenantID string `bson:"tenantId" json:"tenantId"` // 租户ID + IPAddress string `bson:"ipAddress" json:"ipAddress"` // IP地址 + UserAgent string `bson:"userAgent" json:"userAgent"` // 用户代理 + Referer string `bson:"referer" json:"referer"` // 来源页面 + + // 广告位信息 + PositionCode string `bson:"positionCode" json:"positionCode"` // 广告位编码 + PositionSize string `bson:"positionSize" json:"positionSize"` // 广告位尺寸 + PositionFormat string `bson:"positionFormat" json:"positionFormat"` // 广告位格式 + PositionType string `bson:"positionType" json:"positionType"` // 广告位类型 + + // 页面信息 + PageURL string `bson:"pageUrl" json:"pageUrl"` // 页面URL + PageTitle string `bson:"pageTitle" json:"pageTitle"` // 页面标题 + PageCategory string `bson:"pageCategory" json:"pageCategory"` // 页面分类 + PageKeywords []string `bson:"pageKeywords" json:"pageKeywords"` // 页面关键词 + PageTags map[string]string `bson:"pageTags" json:"pageTags"` // 页面标签 + + // 用户信息 + UserContext *UserContext `bson:"userContext" json:"userContext"` // 用户上下文 + DeviceInfo *DeviceInfo `bson:"deviceInfo" json:"deviceInfo"` // 设备信息 + LocationInfo *LocationInfo `bson:"locationInfo" json:"locationInfo"` // 位置信息 + TemporalInfo *TemporalInfo `bson:"temporalInfo" json:"temporalInfo"` // 时间信息 + + // 请求参数 + RequestParams *RequestParams `bson:"requestParams" json:"requestParams"` // 请求参数 + TargetingRules *TargetingRules `bson:"targetingRules" json:"targetingRules"` // 定向规则 + + // 策略配置 + StrategyConfig *StrategyConfig `bson:"strategyConfig" json:"strategyConfig"` // 策略配置 + + // 响应信息 + Response *CidResponse `bson:"response" json:"response"` // 响应结果 + ProcessingTime int64 `bson:"processingTime" json:"processingTime"` // 处理时间(毫秒) + ResponseTime int64 `bson:"responseTime" json:"responseTime"` // 响应时间(毫秒) + + // 状态信息 + Status string `bson:"status" json:"status"` // 请求状态:pending、processing、completed、failed、timeout + ErrorMessage string `bson:"errorMessage" json:"errorMessage"` // 错误信息 + ErrorCode string `bson:"errorCode" json:"errorCode"` // 错误代码 + + // 广告源信息 + RequestedAdSources []string `bson:"requestedAdSources" json:"requestedAdSources"` // 请求的广告源列表 + RespondedAdSources []string `bson:"respondedAdSources" json:"respondedAdSources"` // 响应的广告源列表 + AdSourceResponses map[string]*AdSourceResponse `bson:"adSourceResponses" json:"adSourceResponses"` // 各广告源响应 + + // 统计信息 + TotalAdsReturned int `bson:"totalAdsReturned" json:"totalAdsReturned"` // 返回的广告总数 + ValidAdsReturned int `bson:"validAdsReturned" json:"validAdsReturned"` // 有效广告数 + FilteredAds int `bson:"filteredAds" json:"filteredAds"` // 过滤的广告数 + DuplicateAds int `bson:"duplicateAds" json:"duplicateAds"` // 重复广告数 + + // 系统信息 + ServerInstance string `bson:"serverInstance" json:"serverInstance"` // 服务实例ID + Region string `bson:"region" json:"region"` // 服务区域 + Version string `bson:"version" json:"version"` // 系统版本 +} + +// CidResponse CID响应 +type CidResponse struct { + Ads []Ad `bson:"ads" json:"ads"` // 广告列表 + TrackingInfo *TrackingInfo `bson:"trackingInfo" json:"trackingInfo"` // 跟踪信息 + Metadata *ResponseMetadata `bson:"metadata" json:"metadata"` // 响应元数据 +} + +// Ad 广告 +type Ad struct { + ID string `bson:"id" json:"id"` // 广告ID + AdSource string `bson:"adSource" json:"adSource"` // 广告源 + Advertiser string `bson:"advertiser" json:"advertiser"` // 广告主 + Title string `bson:"title" json:"title"` // 广告标题 + Description string `bson:"description" json:"description"` // 广告描述 + CreativeURL string `bson:"creativeUrl" json:"creativeUrl"` // 创意URL + LandingURL string `bson:"landingUrl" json:"landingUrl"` // 落地页URL + DisplayURL string `bson:"displayUrl" json:"displayUrl"` // 显示URL + AdType string `bson:"adType" json:"adType"` // 广告类型 + Format string `bson:"format" json:"format"` // 广告格式 + Width int `bson:"width" json:"width"` // 宽度 + Height int `bson:"height" json:"height"` // 高度 + MimeType string `bson:"mimeType" json:"mimeType"` // MIME类型 + BidAmount int64 `bson:"bidAmount" json:"bidAmount"` // 出价(分) + Revenue int64 `bson:"revenue" json:"revenue"` // 预估收入(分) + CTR float64 `bson:"ctr" json:"ctr"` // 点击率 + CVR float64 `bson:"cvr" json:"cvr"` // 转化率 + Targeting map[string]interface{} `bson:"targeting" json:"targeting"` // 定向条件 + Restrictions map[string]interface{} `bson:"restrictions" json:"restrictions"` // 限制条件 + TrackingPixels []string `bson:"trackingPixels" json:"trackingPixels"` // 跟踪像素 + CustomData map[string]interface{} `bson:"customData" json:"customData"` // 自定义数据 + ExpiresAt int64 `bson:"expiresAt" json:"expiresAt"` // 过期时间 + Priority int `bson:"priority" json:"priority"` // 优先级 + Score float64 `bson:"score" json:"score"` // 评分 +} + +// TrackingInfo 跟踪信息 +type TrackingInfo struct { + ImpressionURLs []string `bson:"impressionUrls" json:"impressionUrls"` // 展示跟踪URL + ClickURLs []string `bson:"clickUrls" json:"clickUrls"` // 点击跟踪URL + ConversionURLs []string `bson:"conversionUrls" json:"conversionUrls"` // 转化跟踪URL + ViewThroughURLs []string `bson:"viewThroughUrls" json:"viewThroughUrls"` // 查看跟踪URL + EventURLs map[string][]string `bson:"eventUrls" json:"eventUrls"` // 事件跟踪URL + BeaconURLs []string `bson:"beaconUrls" json:"beaconUrls"` // 信标URL +} + +// ResponseMetadata 响应元数据 +type ResponseMetadata struct { + TotalAvailableAds int `bson:"totalAvailableAds" json:"totalAvailableAds"` // 总可用广告数 + SelectedAds int `bson:"selectedAds" json:"selectedAds"` // 选择的广告数 + FilteredAds int `bson:"filteredAds" json:"filteredAds"` // 过滤的广告数 + DuplicateAds int `bson:"duplicateAds" json:"duplicateAds"` // 重复的广告数 + AverageBidAmount int64 `bson:"averageBidAmount" json:"averageBidAmount"` // 平均出价 + HighestBidAmount int64 `bson:"highestBidAmount" json:"highestBidAmount"` // 最高出价 + LowestBidAmount int64 `bson:"lowestBidAmount" json:"lowestBidAmount"` // 最低出价 + AverageCTR float64 `bson:"averageCTR" json:"averageCTR"` // 平均点击率 + AverageCVR float64 `bson:"averageCVR" json:"averageCVR"` // 平均转化率 + ResponseTime int64 `bson:"responseTime" json:"responseTime"` // 响应时间(毫秒) + CacheHit bool `bson:"cacheHit" json:"cacheHit"` // 是否命中缓存 + StrategyUsed string `bson:"strategyUsed" json:"strategyUsed"` // 使用的策略 + AdSourcesUsed []string `bson:"adSourcesUsed" json:"adSourcesUsed"` // 使用的广告源 +} + +// AdSourceResponse 广告源响应 +type AdSourceResponse struct { + AdSource string `bson:"adSource" json:"adSource"` // 广告源名称 + Status string `bson:"status" json:"status"` // 响应状态:success、timeout、error + ResponseTime int64 `bson:"responseTime" json:"responseTime"` // 响应时间(毫秒) + AdsReturned int `bson:"adsReturned" json:"adsReturned"` // 返回的广告数 + AdsAccepted int `bson:"adsAccepted" json:"adsAccepted"` // 接受的广告数 + AdsFiltered int `bson:"adsFiltered" json:"adsFiltered"` // 过滤的广告数 + ErrorMessage string `bson:"errorMessage" json:"errorMessage"` // 错误信息 + ErrorCode string `bson:"errorCode" json:"errorCode"` // 错误代码 + RetryCount int `bson:"retryCount" json:"retryCount"` // 重试次数 + CacheHit bool `bson:"cacheHit" json:"cacheHit"` // 是否命中缓存 + TotalRevenue int64 `bson:"totalRevenue" json:"totalRevenue"` // 总收入(分) + AverageBidAmount int64 `bson:"averageBidAmount" json:"averageBidAmount"` // 平均出价(分) +} + +// UserContext 用户上下文 +type UserContext struct { + UserID string `bson:"userId" json:"userId"` // 用户ID + SessionID string `bson:"sessionId" json:"sessionId"` // 会话ID + CookieID string `bson:"cookieId" json:"cookieId"` // Cookie ID + IP string `bson:"ip" json:"ip"` // IP地址 + UserAgent string `bson:"userAgent" json:"userAgent"` // 用户代理 + Language string `bson:"language" json:"language"` // 语言 + Timezone string `bson:"timezone" json:"timezone"` // 时区 + CustomData map[string]interface{} `bson:"customData" json:"customData"` // 自定义数据 +} + +// DeviceInfo 设备信息 +type DeviceInfo struct { + Type string `bson:"type" json:"type"` // 设备类型:desktop、mobile、tablet + Brand string `bson:"brand" json:"brand"` // 设备品牌 + Model string `bson:"model" json:"model"` // 设备型号 + OS string `bson:"os" json:"os"` // 操作系统 + OSVersion string `bson:"osVersion" json:"osVersion"` // 操作系统版本 + Browser string `bson:"browser" json:"browser"` // 浏览器 + BrowserVersion string `bson:"browserVersion" json:"browserVersion"` // 浏览器版本 + ScreenWidth int `bson:"screenWidth" json:"screenWidth"` // 屏幕宽度 + ScreenHeight int `bson:"screenHeight" json:"screenHeight"` // 屏幕高度 + ViewportWidth int `bson:"viewportWidth" json:"viewportWidth"` // 视口宽度 + ViewportHeight int `bson:"viewportHeight" json:"viewportHeight"` // 视口高度 + DPI int `bson:"dpi" json:"dpi"` // 设备DPI + IsJavaScript bool `bson:"isJavaScript" json:"isJavaScript"` // 是否支持JavaScript + IsCookie bool `bson:"isCookie" json:"isCookie"` // 是否支持Cookie + IsFlash bool `bson:"isFlash" json:"isFlash"` // 是否支持Flash + IsHTTPS bool `bson:"isHTTPS" json:"isHTTPS"` // 是否HTTPS连接 +} + +// LocationInfo 位置信息 +type LocationInfo struct { + Country string `bson:"country" json:"country"` // 国家 + Region string `bson:"region" json:"region"` // 地区/省份 + City string `bson:"city" json:"city"` // 城市 + PostalCode string `bson:"postalCode" json:"postalCode"` // 邮政编码 + Latitude float64 `bson:"latitude" json:"latitude"` // 纬度 + Longitude float64 `bson:"longitude" json:"longitude"` // 经度 + Timezone string `bson:"timezone" json:"timezone"` // 时区 + Metro string `bson:"metro" json:"metro"` // 都市区 + Area string `bson:"area" json:"area"` // 区域 + Network string `bson:"network" json:"network"` // 网络运营商 + ConnectionType string `bson:"connectionType" json:"connectionType"` // 连接类型 + ISP string `bson:"isp" json:"isp"` // 互联网服务提供商 +} + +// TemporalInfo 时间信息 +type TemporalInfo struct { + Timestamp int64 `bson:"timestamp" json:"timestamp"` // 时间戳(秒) + Milliseconds int64 `bson:"milliseconds" json:"milliseconds"` // 毫秒数 + Timezone string `bson:"timezone" json:"timezone"` // 时区 + DayOfWeek int `bson:"dayOfWeek" json:"dayOfWeek"` // 星期几(0-6) + HourOfDay int `bson:"hourOfDay" json:"hourOfDay"` // 小时(0-23) + DayOfMonth int `bson:"dayOfMonth" json:"dayOfMonth"` // 月份中的天数 + Month int `bson:"month" json:"month"` // 月份(1-12) + Year int `bson:"year" json:"year"` // 年份 + IsWeekend bool `bson:"isWeekend" json:"isWeekend"` // 是否周末 + IsBusinessHours bool `bson:"isBusinessHours" json:"isBusinessHours"` // 是否营业时间 + Season string `bson:"season" json:"season"` // 季节 + Holiday string `bson:"holiday" json:"holiday"` // 节假日 +} + +// RequestParams 请求参数 +type RequestParams struct { + AdCount int `bson:"adCount" json:"adCount"` // 请求的广告数量 + AdTypes []string `bson:"adTypes" json:"adTypes"` // 广告类型 + AdSizes []string `bson:"adSizes" json:"adSizes"` // 广告尺寸 + ExcludedAdSources []string `bson:"excludedAdSources" json:"excludedAdSources"` // 排除的广告源 + RequiredAdSources []string `bson:"requiredAdSources" json:"requiredAdSources"` // 必需的广告源 + MinBidAmount int64 `bson:"minBidAmount" json:"minBidAmount"` // 最小出价(分) + MaxBidAmount int64 `bson:"maxBidAmount" json:"maxBidAmount"` // 最大出价(分) + AllowDuplicates bool `bson:"allowDuplicates" json:"allowDuplicates"` // 是否允许重复广告 + FloorPrice int64 `bson:"floorPrice" json:"floorPrice"` // 底价(分) + CeilingPrice int64 `bson:"ceilingPrice" json:"ceilingPrice"` // 封顶价(分) + CustomParams map[string]interface{} `bson:"customParams" json:"customParams"` // 自定义参数 +} + +// TargetingRules 定向规则 +type TargetingRules struct { + GeoTargeting *GeoTargeting `bson:"geoTargeting" json:"geoTargeting"` // 地理定向 + DemographicTargeting *DemographicTargeting `bson:"demographicTargeting" json:"demographicTargeting"` // 人口统计定向 + BehavioralTargeting *BehavioralTargeting `bson:"behavioralTargeting" json:"behavioralTargeting"` // 行为定向 + ContextualTargeting *ContextualTargeting `bson:"contextualTargeting" json:"contextualTargeting"` // 上下文定向 + DeviceTargeting *DeviceTargeting `bson:"deviceTargeting" json:"deviceTargeting"` // 设备定向 + TimeTargeting *TimeTargeting `bson:"timeTargeting" json:"timeTargeting"` // 时间定向 + CustomTargeting map[string]interface{} `bson:"customTargeting" json:"customTargeting"` // 自定义定向 +} + +// GeoTargeting 地理定向 +type GeoTargeting struct { + Countries []string `bson:"countries" json:"countries"` // 国家列表 + Regions []string `bson:"regions" json:"regions"` // 地区列表 + Cities []string `bson:"cities" json:"cities"` // 城市列表 + PostalCodes []string `bson:"postalCodes" json:"postalCodes"` // 邮政编码列表 + GeoTargets []string `bson:"geoTargets" json:"geoTargets"` // 地理目标 +} + +// DemographicTargeting 人口统计定向 +type DemographicTargeting struct { + AgeRange *AgeRange `bson:"ageRange" json:"ageRange"` // 年龄范围 + Gender []string `bson:"gender" json:"gender"` // 性别 + Income []string `bson:"income" json:"income"` // 收入水平 + Education []string `bson:"education" json:"education"` // 教育程度 + Occupation []string `bson:"occupation" json:"occupation"` // 职业类型 + Interests []string `bson:"interests" json:"interests"` // 兴趣标签 + Lifestyle []string `bson:"lifestyle" json:"lifestyle"` // 生活方式 +} + +// BehavioralTargeting 行为定向 +type BehavioralTargeting struct { + SearchHistory []string `bson:"searchHistory" json:"searchHistory"` // 搜索历史 + BrowseHistory []string `bson:"browseHistory" json:"browseHistory"` // 浏览历史 + PurchaseHistory []string `bson:"purchaseHistory" json:"purchaseHistory"` // 购买历史 + AdInteractions []string `bson:"adInteractions" json:"adInteractions"` // 广告互动 + Behaviors []string `bson:"behaviors" json:"behaviors"` // 行为标签 + Segments []string `bson:"segments" json:"segments"` // 用户分群 +} + +// ContextualTargeting 上下文定向 +type ContextualTargeting struct { + Categories []string `bson:"categories" json:"categories"` // 内容分类 + Keywords []string `bson:"keywords" json:"keywords"` // 关键词 + Tags []string `bson:"tags" json:"tags"` // 标签 + Sentiment string `bson:"sentiment" json:"sentiment"` // 情感倾向 + ContentType string `bson:"contentType" json:"contentType"` // 内容类型 + ContentRating string `bson:"contentRating" json:"contentRating"` // 内容评级 +} + +// DeviceTargeting 设备定向 +type DeviceTargeting struct { + DeviceTypes []string `bson:"deviceTypes" json:"deviceTypes"` // 设备类型 + OS []string `bson:"os" json:"os"` // 操作系统 + Browsers []string `bson:"browsers" json:"browsers"` // 浏览器 + Carriers []string `bson:"carriers" json:"carriers"` // 运营商 + ConnectionTypes []string `bson:"connectionTypes" json:"connectionTypes"` // 连接类型 +} + +// TimeTargeting 时间定向 +type TimeTargeting struct { + TimeSlots []TimeSlot `bson:"timeSlots" json:"timeSlots"` // 时间段 + DaysOfWeek []int `bson:"daysOfWeek" json:"daysOfWeek"` // 星期几 + Dates []string `bson:"dates" json:"dates"` // 日期范围 + Timezone string `bson:"timezone" json:"timezone"` // 时区 + ExcludeHolidays bool `bson:"excludeHolidays" json:"excludeHolidays"` // 排除节假日 +} + +// StrategyConfig 策略配置 +type StrategyConfig struct { + StrategyType string `bson:"strategyType" json:"strategyType"` // 策略类型 + Priority int `bson:"priority" json:"priority"` // 优先级 + Weight float64 `bson:"weight" json:"weight"` // 权重 + MinAds int `bson:"minAds" json:"minAds"` // 最小广告数 + MaxAds int `bson:"maxAds" json:"maxAds"` // 最大广告数 + AllowDuplicates bool `bson:"allowDuplicates" json:"allowDuplicates"` // 是否允许重复 + Timeout int64 `bson:"timeout" json:"timeout"` // 超时时间(毫秒) + RetryCount int `bson:"retryCount" json:"retryCount"` // 重试次数 + CustomSettings map[string]interface{} `bson:"customSettings" json:"customSettings"` // 自定义设置 +} diff --git a/model/entity/strategy.go b/model/entity/strategy.go new file mode 100644 index 0000000..ac8a5a0 --- /dev/null +++ b/model/entity/strategy.go @@ -0,0 +1,23 @@ +package entity + +import ( + "github.com/gogf/gf/v2/os/gtime" +) + +// Strategy 匹配策略表 +type Strategy struct { + Id int64 `json:"id" orm:"id,primary"` // ID + Name string `json:"name" orm:"name"` // 策略名称 + Description string `json:"description" orm:"description"` // 描述 + TenantLevel string `json:"tenant_level" orm:"tenant_level"` // 适用租户级别 + MinConversion float64 `json:"min_conversion" orm:"min_conversion"` // 最低转化率 + MaxConversion float64 `json:"max_conversion" orm:"max_conversion"` // 最高转化率 + SourceWeights string `json:"source_weights" orm:"source_weights"` // 广告源权重 (JSON格式) + MaxAdsPerReq int `json:"max_ads_per_req" orm:"max_ads_per_req"` // 每次请求最大广告数 + Priority int `json:"priority" orm:"priority"` // 优先级 + Status string `json:"status" orm:"status"` // 状态: active, inactive + CreatedAt *gtime.Time `json:"created_at" orm:"created_at"` // 创建时间 + UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at"` // 更新时间 + CreatedBy int64 `json:"created_by" orm:"created_by"` // 创建人 + UpdatedBy int64 `json:"updated_by" orm:"updated_by"` // 更新人 +} diff --git a/model/types/tenant.go b/model/types/tenant.go new file mode 100644 index 0000000..db90fa1 --- /dev/null +++ b/model/types/tenant.go @@ -0,0 +1,9 @@ +package types + +// Tenant 租户信息类型 +type Tenant struct { + Id int64 `json:"id"` // 租户ID + Name string `json:"name"` // 租户名称 + Level string `json:"level"` // 租户级别: basic, standard, premium + Status string `json:"status"` // 状态: active, inactive +} diff --git a/service/ad_source_service.go b/service/ad_source_service.go new file mode 100644 index 0000000..921953f --- /dev/null +++ b/service/ad_source_service.go @@ -0,0 +1,104 @@ +package service + +import ( + "cidService/dao" + "cidService/model/dto" + "cidService/model/entity" + "context" + + "github.com/gogf/gf/v2/errors/gerror" +) + +var ( + AdSource = adSourceService{} +) + +type adSourceService struct{} + +// GetAvailableSources 获取可用的广告源列表 +func (s *adSourceService) GetAvailableSources(ctx context.Context) (list []*entity.AdSource, err error) { + return dao.AdSource.GetAvailableSources(ctx) +} + +// GetSourcesByProvider 根据提供商获取广告源 +func (s *adSourceService) GetSourcesByProvider(ctx context.Context, provider string) (list []*entity.AdSource, err error) { + return dao.AdSource.GetSourcesByProvider(ctx, provider) +} + +// CreateAdSource 创建广告源 +func (s *adSourceService) CreateAdSource(ctx context.Context, req *dto.CreateAdSourceReq) (id int64, err error) { + // 检查广告源名称是否已存在 + existingSource, err := dao.AdSource.GetByName(ctx, req.Name) + if err != nil { + return 0, err + } + if existingSource != nil { + return 0, gerror.New("广告源名称已存在") + } + + adSource := &entity.AdSource{ + Name: req.Name, + Code: req.Code, + Provider: req.Provider, + Type: req.Type, + APIEndpoint: req.APIEndpoint, + Status: "active", // 默认状态 + Priority: 1, // 默认优先级 + } + + return dao.AdSource.Create(ctx, adSource) +} + +// UpdateAdSource 更新广告源 +func (s *adSourceService) UpdateAdSource(ctx context.Context, id int64, req *dto.UpdateAdSourceReq) (affected int64, err error) { + + // 检查广告源是否存在 + existingSource, err := dao.AdSource.GetByID(ctx, id) + if err != nil { + return 0, err + } + if existingSource == nil { + return 0, gerror.New("广告源不存在") + } + + // 如果更新名称,检查是否与其他广告源冲突 + if req.Name != "" && req.Name != existingSource.Name { + conflictSource, err := dao.AdSource.GetByName(ctx, req.Name) + if err != nil { + return 0, err + } + if conflictSource != nil { + return 0, gerror.New("广告源名称已存在") + } + } + + // 构建更新数据 + updateData := &entity.AdSource{} + if req.Name != "" { + updateData.Name = req.Name + } + if req.APIEndpoint != "" { + updateData.APIEndpoint = req.APIEndpoint + } + + return dao.AdSource.UpdateFields(ctx, id, updateData) +} + +// DeleteAdSource 删除广告源 +func (s *adSourceService) DeleteAdSource(ctx context.Context, id int64) (affected int64, err error) { + // 检查广告源是否存在 + existingSource, err := dao.AdSource.GetByID(ctx, id) + if err != nil { + return 0, err + } + if existingSource == nil { + return 0, gerror.New("广告源不存在") + } + + return dao.AdSource.Delete(ctx, id) +} + +// GetAdSourceByID 根据ID获取广告源 +func (s *adSourceService) GetAdSourceByID(ctx context.Context, id int64) (adSource *entity.AdSource, err error) { + return dao.AdSource.GetByID(ctx, id) +} diff --git a/service/cid_service.go b/service/cid_service.go new file mode 100644 index 0000000..7a86233 --- /dev/null +++ b/service/cid_service.go @@ -0,0 +1,360 @@ +package service + +import ( + "cidService/dao" + "cidService/model/dto" + "cidService/model/entity" + "cidService/model/types" + "context" + "encoding/json" + "fmt" + "math/rand" + "strconv" + "time" + + "gitee.com/red-future---jilin-g/common/utils" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +var ( + CID = cidService{} +) + +type cidService struct{} + +// AdMatchingStrategy 广告匹配策略 +type AdMatchingStrategy struct { + TenantLevel string // 租户级别 + MinConversion float64 // 最低转化率 + MaxConversion float64 // 最高转化率 + SourceWeight map[string]int // 广告源权重 + MaxAdsPerRequest int // 每次请求最大广告数 +} + +// getMatchingStrategy 获取匹配策略 +func (s *cidService) getMatchingStrategy(ctx context.Context, tenantLevel string) (*AdMatchingStrategy, error) { + // 从数据库获取策略 + strategyEntity, err := Strategy.GetStrategyByTenantLevel(ctx, tenantLevel) + if err != nil { + return nil, err + } + + if strategyEntity == nil { + // 返回默认策略 + return &AdMatchingStrategy{ + TenantLevel: tenantLevel, + MinConversion: 0.01, + MaxConversion: 0.05, + SourceWeight: map[string]int{"self": 100}, + MaxAdsPerRequest: 3, + }, nil + } + + // 反序列化权重配置 + var sourceWeights map[string]int + if strategyEntity.SourceWeights != "" { + err = json.Unmarshal([]byte(strategyEntity.SourceWeights), &sourceWeights) + if err != nil { + g.Log().Warningf(ctx, "策略权重反序列化失败: %v", err) + sourceWeights = map[string]int{"self": 100} + } + } + + return &AdMatchingStrategy{ + TenantLevel: strategyEntity.TenantLevel, + MinConversion: strategyEntity.MinConversion, + MaxConversion: strategyEntity.MaxConversion, + SourceWeight: sourceWeights, + MaxAdsPerRequest: strategyEntity.MaxAdsPerReq, + }, nil +} + +// GenerateCID 生成CID广告 +func (s *cidService) GenerateCID(ctx context.Context, req *dto.GenerateCIDReq) (res *dto.GenerateCIDRes, err error) { + // 获取当前用户信息 + userInfo, err := utils.GetUserInfo(ctx) + if err != nil { + return nil, gerror.Wrap(err, "获取用户信息失败") + } + + // 获取租户信息 + tenant, err := s.getTenantByUser(ctx, gconv.Int64(userInfo.UserName)) + if err != nil { + return nil, gerror.Wrap(err, "获取租户信息失败") + } + + // 获取匹配策略 + strategy, err := s.getMatchingStrategy(ctx, tenant.Level) + if err != nil { + return nil, gerror.Wrap(err, "获取匹配策略失败") + } + + // 根据策略获取广告 + ads, err := s.matchAds(ctx, req, strategy) + if err != nil { + return nil, gerror.Wrap(err, "广告匹配失败") + } + + // 记录CID请求 + go s.recordCIDRequest(context.Background(), req, tenant, ads) + + // 生成唯一CID + cid := s.generateUniqueCID() + + return &dto.GenerateCIDRes{ + CID: cid, + Ads: ads, + TotalAds: len(ads), + TenantId: tenant.Id, + TenantName: tenant.Name, + GeneratedAt: time.Now().Format("2006-01-02 15:04:05"), + }, nil +} + +// getTenantByUser 根据用户获取租户信息 +func (s *cidService) getTenantByUser(ctx context.Context, userId int64) (*types.Tenant, error) { + // 通过common模块获取用户信息,包含租户ID + userInfo, err := utils.GetUserInfo(ctx) + if err != nil { + return nil, gerror.Wrap(err, "获取用户信息失败") + } + + // 租户ID从用户信息中获取 + tenantId := gconv.Int64(userInfo.TenantId) + if tenantId == 0 { + tenantId = 1 // 默认租户ID + } + + // 租户级别和名称可以根据租户ID通过其他方式获取或配置 + // 这里使用映射配置,实际项目中可能需要调用其他服务 + tenantName := "默认租户" + tenantLevel := "basic" + tenantStatus := "active" + + // 根据租户ID设置不同的级别(示例逻辑) + switch tenantId { + case 1: + tenantName = "基础租户" + tenantLevel = "basic" + case 2: + tenantName = "标准租户" + tenantLevel = "standard" + case 3: + tenantName = "高级租户" + tenantLevel = "premium" + } + + return &types.Tenant{ + Id: tenantId, + Name: tenantName, + Level: tenantLevel, + Status: tenantStatus, + }, nil +} + +// matchAds 根据策略匹配广告 +func (s *cidService) matchAds(ctx context.Context, req *dto.GenerateCIDReq, strategy *AdMatchingStrategy) ([]*dto.AdInfo, error) { + var matchedAds []*dto.AdInfo + + // 根据策略权重从不同源获取广告 + for source, weight := range strategy.SourceWeight { + if weight <= 0 { + continue + } + + sourceAds, err := s.getAdsFromSource(ctx, source, req, strategy, weight) + if err != nil { + g.Log().Warningf(ctx, "从广告源 %s 获取广告失败: %v", source, err) + continue + } + + matchedAds = append(matchedAds, sourceAds...) + } + + // 过滤符合转化率要求的广告 + var filteredAds []*dto.AdInfo + for _, ad := range matchedAds { + if ad.ConversionRate >= strategy.MinConversion && ad.ConversionRate <= strategy.MaxConversion { + filteredAds = append(filteredAds, ad) + } + } + + // 限制广告数量 + if len(filteredAds) > strategy.MaxAdsPerRequest { + rand.Shuffle(len(filteredAds), func(i, j int) { + filteredAds[i], filteredAds[j] = filteredAds[j], filteredAds[i] + }) + filteredAds = filteredAds[:strategy.MaxAdsPerRequest] + } + + return filteredAds, nil +} + +// getAdsFromSource 从指定广告源获取广告 +func (s *cidService) getAdsFromSource(ctx context.Context, source string, req *dto.GenerateCIDReq, strategy *AdMatchingStrategy, weight int) ([]*dto.AdInfo, error) { + switch source { + case "self": + return s.getSelfServiceAds(ctx, req, weight) + case "google": + return s.getGoogleAds(ctx, req, weight) + case "facebook": + return s.getFacebookAds(ctx, req, weight) + default: + return nil, gerror.Newf("不支持的广告源: %s", source) + } +} + +// getSelfServiceAds 获取自营广告 +func (s *cidService) getSelfServiceAds(ctx context.Context, req *dto.GenerateCIDReq, count int) ([]*dto.AdInfo, error) { + // 这里应该从数据库查询自营广告 + // 暂时返回模拟数据 + ads := make([]*dto.AdInfo, 0) + for i := 0; i < count; i++ { + ads = append(ads, &dto.AdInfo{ + Id: int64(rand.Intn(89999) + 10000), + Title: fmt.Sprintf("自营广告 %d", i+1), + Description: "这是一个高质量的自营广告", + ImageUrl: "https://example.com/ad.jpg", + TargetUrl: "https://example.com/landing", + ConversionRate: rand.Float64(), + Source: "self", + Bid: rand.Intn(901) + 100, + }) + } + return ads, nil +} + +// getGoogleAds 获取Google广告 +func (s *cidService) getGoogleAds(ctx context.Context, req *dto.GenerateCIDReq, count int) ([]*dto.AdInfo, error) { + // 这里应该调用Google Ads API + // 暂时返回模拟数据 + ads := make([]*dto.AdInfo, 0) + for i := 0; i < count; i++ { + ads = append(ads, &dto.AdInfo{ + Id: int64(rand.Intn(9999) + 20000), + Title: fmt.Sprintf("Google广告 %d", i+1), + Description: "来自Google的高质量广告", + ImageUrl: "https://google.com/ad.jpg", + TargetUrl: "https://google.com/landing", + ConversionRate: rand.Float64()*0.3 + 0.1, + Source: "google", + Bid: rand.Intn(1301) + 200, + }) + } + return ads, nil +} + +// getFacebookAds 获取Facebook广告 +func (s *cidService) getFacebookAds(ctx context.Context, req *dto.GenerateCIDReq, count int) ([]*dto.AdInfo, error) { + // 这里应该调用Facebook Ads API + // 暂时返回模拟数据 + ads := make([]*dto.AdInfo, 0) + for i := 0; i < count; i++ { + ads = append(ads, &dto.AdInfo{ + Id: int64(rand.Intn(9999) + 30000), + Title: fmt.Sprintf("Facebook广告 %d", i+1), + Description: "来自Facebook的高质量广告", + ImageUrl: "https://facebook.com/ad.jpg", + TargetUrl: "https://facebook.com/landing", + ConversionRate: rand.Float64()*0.25 + 0.08, + Source: "facebook", + Bid: rand.Intn(1051) + 150, + }) + } + return ads, nil +} + +// generateUniqueCID 生成唯一CID +func (s *cidService) generateUniqueCID() string { + timestamp := time.Now().Unix() + random := rand.Intn(8999) + 1000 + return fmt.Sprintf("CID_%d_%d", timestamp, random) +} + +// recordCIDRequest 记录CID请求 +func (s *cidService) recordCIDRequest(ctx context.Context, req *dto.GenerateCIDReq, tenant *types.Tenant, ads []*dto.AdInfo) { + // 转换dto.AdInfo到entity.Ad + var entityAds []entity.Ad + for _, ad := range ads { + entityAds = append(entityAds, entity.Ad{ + ID: fmt.Sprintf("%d", ad.Id), + AdSource: ad.Source, + Title: ad.Title, + Description: ad.Description, + CreativeURL: ad.ImageUrl, + LandingURL: ad.TargetUrl, + BidAmount: int64(ad.Bid), + }) + } + + request := &entity.CidRequest{ + RequestID: fmt.Sprintf("REQ_%d_%d", time.Now().Unix(), rand.Intn(10000)), + UserID: fmt.Sprintf("%d", req.UserId), + TenantID: fmt.Sprintf("%d", tenant.Id), + Response: &entity.CidResponse{ + Ads: entityAds, + }, + ProcessingTime: int64(rand.Intn(401) + 100), // 模拟处理时间 + } + + dao.CIDRequest.Create(ctx, request) +} + +// GetCIDStatistics 获取CID统计信息 +func (s *cidService) GetCIDStatistics(ctx context.Context, req *dto.GetCIDStatisticsReq) (res *dto.GetCIDStatisticsRes, err error) { + // 这里应该实现真实的统计逻辑 + // 暂时返回模拟数据 + return &dto.GetCIDStatisticsRes{ + TotalRequests: int64(rand.Intn(9000) + 1000), + SuccessfulReq: int64(rand.Intn(8100) + 900), + AverageProcessTime: rand.Float64()*200 + 50, + TopSources: []string{"self", "google", "facebook"}, + ConversionStats: map[string]float64{ + "self": rand.Float64() * 0.1, + "google": rand.Float64() * 0.2, + "facebook": rand.Float64() * 0.15, + }, + }, nil +} + +// GetCIDHistory 获取CID请求历史 +func (s *cidService) GetCIDHistory(ctx context.Context, userId int64, page, size int) (res *dto.GetCIDHistoryRes, err error) { + history, total, err := dao.CIDRequest.GetHistory(ctx, userId, page, size) + if err != nil { + return nil, err + } + + var historyList []*dto.CIDRequestHistory + for _, record := range history { + // 解析TenantID + tenantId := int64(0) + if record.TenantID != "" { + tenantId, _ = strconv.ParseInt(record.TenantID, 10, 64) + } + + // 解析UserID + uid := int64(0) + if record.UserID != "" { + uid, _ = strconv.ParseInt(record.UserID, 10, 64) + } + + historyList = append(historyList, &dto.CIDRequestHistory{ + Id: 0, // 使用默认值,因为entity使用的是ObjectID + TenantId: tenantId, + UserId: uid, + RequestType: "CID", // 默认值 + Status: "completed", // 从response状态获取 + ProcessTime: int(record.ProcessingTime), + CreatedAt: record.CreatedAt.String(), + }) + } + + return &dto.GetCIDHistoryRes{ + List: historyList, + Total: total, + Page: page, + Size: size, + }, nil +} diff --git a/service/strategy_service.go b/service/strategy_service.go new file mode 100644 index 0000000..f434dbe --- /dev/null +++ b/service/strategy_service.go @@ -0,0 +1,238 @@ +package service + +import ( + "cidService/dao" + "cidService/model/dto" + "cidService/model/entity" + "context" + "encoding/json" + "strconv" + + "gitee.com/red-future---jilin-g/common/utils" + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/frame/g" +) + +var ( + Strategy = strategyService{} +) + +type strategyService struct{} + +// CreateStrategy 创建策略 +func (s *strategyService) CreateStrategy(ctx context.Context, req *dto.CreateStrategyReq) (id int64, err error) { + // 检查策略名称是否已存在 + existingStrategy, err := dao.Strategy.GetByName(ctx, req.Name) + if err != nil { + return 0, err + } + if existingStrategy != nil { + return 0, gerror.New("策略名称已存在") + } + + // 验证转化率范围 + if req.MaxConversion <= req.MinConversion { + return 0, gerror.New("最高转化率必须大于最低转化率") + } + + // 序列化权重配置 + weightsJson, err := json.Marshal(req.SourceWeights) + if err != nil { + return 0, gerror.Wrap(err, "权重配置序列化失败") + } + + // 获取当前用户信息 + userInfo, err := utils.GetUserInfo(ctx) + if err != nil { + return 0, gerror.Wrap(err, "获取用户信息失败") + } + + // 将UserName转换为int64,如果失败则使用0 + var userId int64 + if uid, ok := userInfo.UserName.(string); ok { + if parsedId, err := strconv.ParseInt(uid, 10, 64); err == nil { + userId = parsedId + } + } + + strategy := &entity.Strategy{ + Name: req.Name, + Description: req.Description, + TenantLevel: req.TenantLevel, + MinConversion: req.MinConversion, + MaxConversion: req.MaxConversion, + SourceWeights: string(weightsJson), + MaxAdsPerReq: req.MaxAdsPerReq, + Priority: req.Priority, + Status: req.Status, + CreatedBy: userId, + UpdatedBy: userId, + } + + return dao.Strategy.Create(ctx, strategy) +} + +// UpdateStrategy 更新策略 +func (s *strategyService) UpdateStrategy(ctx context.Context, req *dto.UpdateStrategyReq) (affected int64, err error) { + // 检查策略是否存在 + existingStrategy, err := dao.Strategy.GetByID(ctx, req.Id) + if err != nil { + return 0, err + } + if existingStrategy == nil { + return 0, gerror.New("策略不存在") + } + + // 如果更新名称,检查是否与其他策略冲突 + if req.Name != "" && req.Name != existingStrategy.Name { + conflictStrategy, err := dao.Strategy.GetByName(ctx, req.Name) + if err != nil { + return 0, err + } + if conflictStrategy != nil { + return 0, gerror.New("策略名称已存在") + } + } + + // 验证转化率范围 + if req.MaxConversion <= req.MinConversion { + return 0, gerror.New("最高转化率必须大于最低转化率") + } + + // 序列化权重配置 + weightsJson, err := json.Marshal(req.SourceWeights) + if err != nil { + return 0, gerror.Wrap(err, "权重配置序列化失败") + } + + // 获取当前用户信息 + userInfo, err := utils.GetUserInfo(ctx) + if err != nil { + return 0, gerror.Wrap(err, "获取用户信息失败") + } + + // 将UserName转换为int64,如果失败则使用0 + var userId int64 + if uid, ok := userInfo.UserName.(string); ok { + if parsedId, err := strconv.ParseInt(uid, 10, 64); err == nil { + userId = parsedId + } + } + + strategy := &entity.Strategy{ + Id: req.Id, + Name: req.Name, + Description: req.Description, + TenantLevel: req.TenantLevel, + MinConversion: req.MinConversion, + MaxConversion: req.MaxConversion, + SourceWeights: string(weightsJson), + MaxAdsPerReq: req.MaxAdsPerReq, + Priority: req.Priority, + Status: req.Status, + UpdatedBy: userId, + } + + return dao.Strategy.Update(ctx, strategy) +} + +// DeleteStrategy 删除策略 +func (s *strategyService) DeleteStrategy(ctx context.Context, id int64) (affected int64, err error) { + // 检查策略是否存在 + existingStrategy, err := dao.Strategy.GetByID(ctx, id) + if err != nil { + return 0, err + } + if existingStrategy == nil { + return 0, gerror.New("策略不存在") + } + + return dao.Strategy.Delete(ctx, id) +} + +// GetStrategyByID 根据ID获取策略 +func (s *strategyService) GetStrategyByID(ctx context.Context, id int64) (strategy *dto.StrategyRes, err error) { + entity, err := dao.Strategy.GetByID(ctx, id) + if err != nil { + return nil, err + } + if entity == nil { + return nil, gerror.New("策略不存在") + } + + // 反序列化权重配置 + var weights map[string]int + if entity.SourceWeights != "" { + err = json.Unmarshal([]byte(entity.SourceWeights), &weights) + if err != nil { + return nil, gerror.Wrap(err, "权重配置反序列化失败") + } + } + + return &dto.StrategyRes{ + Id: entity.Id, + Name: entity.Name, + Description: entity.Description, + TenantLevel: entity.TenantLevel, + MinConversion: entity.MinConversion, + MaxConversion: entity.MaxConversion, + SourceWeights: weights, + MaxAdsPerReq: entity.MaxAdsPerReq, + Priority: entity.Priority, + Status: entity.Status, + CreatedAt: entity.CreatedAt.String(), + UpdatedAt: entity.UpdatedAt.String(), + CreatedBy: entity.CreatedBy, + UpdatedBy: entity.UpdatedBy, + }, nil +} + +// GetStrategyList 获取策略列表 +func (s *strategyService) GetStrategyList(ctx context.Context, req *dto.GetStrategyListReq) (res *dto.GetStrategyListRes, err error) { + list, total, err := dao.Strategy.GetList(ctx, req.Page, req.Size, req.TenantLevel, req.Status) + if err != nil { + return nil, err + } + + var strategyList []*dto.StrategyRes + for _, entity := range list { + // 反序列化权重配置 + var weights map[string]int + if entity.SourceWeights != "" { + err = json.Unmarshal([]byte(entity.SourceWeights), &weights) + if err != nil { + g.Log().Warningf(ctx, "策略 %d 权重配置反序列化失败: %v", entity.Id, err) + weights = make(map[string]int) + } + } + + strategyList = append(strategyList, &dto.StrategyRes{ + Id: entity.Id, + Name: entity.Name, + Description: entity.Description, + TenantLevel: entity.TenantLevel, + MinConversion: entity.MinConversion, + MaxConversion: entity.MaxConversion, + SourceWeights: weights, + MaxAdsPerReq: entity.MaxAdsPerReq, + Priority: entity.Priority, + Status: entity.Status, + CreatedAt: entity.CreatedAt.String(), + UpdatedAt: entity.UpdatedAt.String(), + CreatedBy: entity.CreatedBy, + UpdatedBy: entity.UpdatedBy, + }) + } + + return &dto.GetStrategyListRes{ + List: strategyList, + Total: total, + Page: req.Page, + Size: req.Size, + }, nil +} + +// GetStrategyByTenantLevel 根据租户级别获取策略 +func (s *strategyService) GetStrategyByTenantLevel(ctx context.Context, tenantLevel string) (strategy *entity.Strategy, err error) { + return dao.Strategy.GetByTenantLevel(ctx, tenantLevel) +}