diff --git a/consts/redis_key.go b/consts/redis_key.go index caef315..ddb7202 100644 --- a/consts/redis_key.go +++ b/consts/redis_key.go @@ -13,37 +13,17 @@ const ( AdvertiserCacheKeyPrefix = "cid:adv:" // 广告主缓存前缀 ) -// 统计数据键 -const ( - AdStatKeyPrefix = "cid:stat:ad:" // 广告统计键前缀 - AdvStatKeyPrefix = "cid:stat:adv:" // 广告主统计键前缀 - PosStatKeyPrefix = "cid:stat:pos:" // 广告位统计键前缀 - DailyStatKeyPrefix = "cid:stat:daily:" // 日常统计键前缀 -) - // 广告匹配键 const ( AdMatchingKeyPrefix = "cid:match:" // 广告匹配键前缀 UserProfileKeyPrefix = "cid:user:" // 用户画像键前缀 ) -// 报表键 -const ( - ReportCacheKeyPrefix = "cid:report:" // 报表缓存键前缀 - ReportTaskKeyPrefix = "cid:task:" // 报表任务键前缀 -) - // 限流键 const ( ApiRequestLimitKeyPrefix = "cid:limit:api:" // API请求限流键前缀 ) -// 队列键 -const ( - AdStatisticsQueueKey = "cid:queue:stat" // 统计数据队列 - ReportGenerateQueueKey = "cid:queue:report" // 报表生成队列 -) - // Stream键 const ( AdEventStreamKey = "cid:stream:ad_event" // 广告事件流 diff --git a/controller/ad_statistics_controller.go b/controller/ad_statistics_controller.go deleted file mode 100644 index 9bd2fb9..0000000 --- a/controller/ad_statistics_controller.go +++ /dev/null @@ -1,34 +0,0 @@ -package controller - -import ( - "context" - - "cid/model/dto" - "cid/service" -) - -type adStatistics struct{} - -var AdStatistics = new(adStatistics) - -// GetStatistics 获取统计数据 -func (c *adStatistics) GetStatistics(ctx context.Context, req *dto.GetAdStatisticsReq) (res *dto.GetAdStatisticsRes, err error) { - return service.AdStatistics.GetStatistics(ctx, req) -} - -// GetDashboard 获取仪表盘数据 -func (c *adStatistics) GetDashboard(ctx context.Context, req *dto.GetDashboardReq) (res *dto.GetDashboardRes, err error) { - return service.AdStatistics.GetDashboard(ctx, req) -} - -// GenerateDailyStatistics 生成每日统计数据 -func (c *adStatistics) GenerateDailyStatistics(ctx context.Context, req *dto.GenerateDailyStatisticsReq) (res *dto.GenerateDailyStatisticsRes, err error) { - err = service.AdStatistics.GenerateDailyStatistics(ctx, req.Date) - if err != nil { - return nil, err - } - - return &dto.GenerateDailyStatisticsRes{ - Success: true, - }, nil -} diff --git a/controller/stat_report_controller.go b/controller/stat_report_controller.go deleted file mode 100644 index e932606..0000000 --- a/controller/stat_report_controller.go +++ /dev/null @@ -1,148 +0,0 @@ -package controller - -import ( - "context" - "fmt" - - "cid/model/dto" - "cid/service" -) - -var StatReport = new(statReport) - -type statReport struct{} - -// GenerateReport 生成报表 -func (c *statReport) GenerateReport(ctx context.Context, req *dto.ReportGenerateReq) (res *dto.ReportGenerateRes, err error) { - var resp *dto.ReportGenerateResp - - switch req.ReportType { - case "daily": - resp, err = service.StatReport.GenerateDailyReport(ctx, req) - case "weekly": - resp, err = service.StatReport.GenerateWeeklyReport(ctx, req) - case "monthly": - resp, err = service.StatReport.GenerateMonthlyReport(ctx, req) - case "quarterly": - resp, err = service.StatReport.GenerateQuarterlyReport(ctx, req) - case "yearly": - resp, err = service.StatReport.GenerateYearlyReport(ctx, req) - default: - return nil, fmt.Errorf("不支持的报表类型: %s", req.ReportType) - } - - if err != nil { - return nil, err - } - - return &dto.ReportGenerateRes{ - Data: resp, - }, nil -} - -// GetReportList 获取报表列表 -func (c *statReport) GetReportList(ctx context.Context, req *dto.ReportListReq) (res *dto.ReportListRes, err error) { - resp, err := service.StatReport.GetReportList(ctx, req) - if err != nil { - return nil, err - } - - return &dto.ReportListRes{ - Data: resp, - }, nil -} - -// GetReportDetail 获取报表详情 -func (c *statReport) GetReportDetail(ctx context.Context, req *dto.ReportDetailReq) (res *dto.ReportDetailRes, err error) { - resp, err := service.StatReport.GetReportDetail(ctx, req.ReportID) - if err != nil { - return nil, err - } - - return &dto.ReportDetailRes{ - Data: resp, - }, nil -} - -// QueryStats 统计查询 -func (c *statReport) QueryStats(ctx context.Context, req *dto.StatQueryReq) (res *dto.StatQueryRes, err error) { - // 这里调用统计查询服务 - // resp, err := service.StatReport.QueryStats(ctx, req) - // 暂时返回示例数据 - resp := &dto.StatQueryResp{ - Data: []*dto.StatDataPoint{ - { - Date: "2024-01-01", - Impressions: 1000, - Clicks: 50, - Revenue: 500.0, - CTR: 5.0, - AvgDuration: 30.5, - }, - }, - Summary: &dto.StatSummary{ - TotalImpressions: 1000, - TotalClicks: 50, - TotalRevenue: 500.0, - AvgCTR: 5.0, - AvgDuration: 30.5, - GrowthRate: &dto.GrowthRate{ - Impressions: 10.5, - Clicks: 8.2, - Revenue: 12.3, - CTR: -1.2, - }, - }, - } - - return &dto.StatQueryRes{ - Data: resp, - }, nil -} - -// RealTimeStats 实时统计 -func (c *statReport) RealTimeStats(ctx context.Context, req *dto.RealTimeStatReq) (res *dto.RealTimeStatRes, err error) { - // 这里调用实时统计服务 - // resp, err := service.StatReport.GetRealTimeStats(ctx, req) - // 暂时返回示例数据 - resp := &dto.RealTimeStatResp{ - CurrentHour: &dto.HourlyStat{ - Hour: "14:00", - Impressions: 120, - Clicks: 6, - Revenue: 60.0, - }, - Last24Hours: []*dto.HourlyStat{ - { - Hour: "13:00", - Impressions: 110, - Clicks: 5, - Revenue: 55.0, - }, - { - Hour: "12:00", - Impressions: 100, - Clicks: 4, - Revenue: 50.0, - }, - }, - } - - return &dto.RealTimeStatRes{ - Data: resp, - }, nil -} - -// ExportReport 导出报表 -func (c *statReport) ExportReport(ctx context.Context, req *dto.ExportReportReq) (res *dto.ExportReportRes, err error) { - // 获取报表详情 - resp, err := service.StatReport.GetReportDetail(ctx, req.ReportID) - if err != nil { - return nil, err - } - - // 返回导出数据,由上层处理HTTP响应 - return &dto.ExportReportRes{ - ReportData: resp, - }, nil -} diff --git a/dao/ad_statistics_dao.go b/dao/ad_statistics_dao.go deleted file mode 100644 index 6f4f7b8..0000000 --- a/dao/ad_statistics_dao.go +++ /dev/null @@ -1,193 +0,0 @@ -package dao - -import ( - "context" - - "cid/model/dto" - "cid/model/entity" - - "github.com/gogf/gf/v2/frame/g" - "go.mongodb.org/mongo-driver/v2/bson" - "go.mongodb.org/mongo-driver/v2/mongo/options" - - "gitee.com/red-future---jilin-g/common/http" - "gitee.com/red-future---jilin-g/common/mongo" -) - -// AdStatistics DAO 单例 -var AdStatistics = &adStatistics{} - -type adStatistics struct{} - -// Insert 插入统计数据 -func (d *adStatistics) Insert(ctx context.Context, data *entity.AdStatistics) (err error) { - // 如果 ID 为空,生成一个新的 ObjectID - if data.Id.IsZero() { - data.Id = bson.NewObjectID() - } - - // 使用 common/mongo.Insert,自动添加 tenantId、creator、updater 等字段 - // 确保查询时能通过 tenantId 正确过滤数据 - _, err = mongo.Insert(ctx, []interface{}{data}, entity.AdStatisticsCollection) - return -} - -// BatchInsert 批量插入统计数据 -func (d *adStatistics) BatchInsert(ctx context.Context, dataList []*entity.AdStatistics) (err error) { - if len(dataList) == 0 { - return nil - } - - var list []interface{} - for _, data := range dataList { - // 如果 ID 为空,生成一个新的 ObjectID - if data.Id.IsZero() { - data.Id = bson.NewObjectID() - } - list = append(list, data) - } - - _, err = mongo.Insert(ctx, list, entity.AdStatisticsCollection) - return -} - -// Upsert 插入或更新统计数据 -func (d *adStatistics) Upsert(ctx context.Context, data *entity.AdStatistics) (err error) { - filter := bson.M{ - "statType": data.StatType, - "statDimension": data.StatDimension, - "targetId": data.TargetId, - "statDate": data.StatDate, - } - - update := bson.M{"$set": data} - - // 这里使用简单的Update方法,如果需要Upsert功能,可以扩展 - _, err = mongo.Update(ctx, filter, update, entity.AdStatisticsCollection) - return -} - -// buildListFilter 构建列表查询的过滤条件 -func (d *adStatistics) buildListFilter(req *dto.GetAdStatisticsReq) bson.M { - filter := bson.M{} - - if !g.IsEmpty(req.StatType) { - filter["statType"] = req.StatType - } - if !g.IsEmpty(req.StatDimension) { - filter["statDimension"] = req.StatDimension - } - if !g.IsEmpty(req.TargetId) { - filter["targetId"] = req.TargetId - } - - // 时间范围 - filter["statDate"] = bson.M{ - "$gte": req.StartDate, - "$lte": req.EndDate, - } - - // 筛选条件 - if !g.IsEmpty(req.DeviceType) { - filter["deviceType"] = req.DeviceType - } - if !g.IsEmpty(req.Platform) { - filter["platform"] = req.Platform - } - if !g.IsEmpty(req.Region) { - filter["region"] = req.Region - } - if !g.IsEmpty(req.Gender) { - filter["gender"] = req.Gender - } - if !g.IsEmpty(req.AgeGroup) { - filter["ageGroup"] = req.AgeGroup - } - - return filter -} - -// checkTotalCount 检查总数 -func (d *adStatistics) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) { - total, err = mongo.Count(ctx, filter, entity.AdStatisticsCollection) - return -} - -// List 获取统计数据列表 -func (d *adStatistics) List(ctx context.Context, req *dto.GetAdStatisticsReq) (list []*entity.AdStatistics, total int64, err error) { - // 构建查询过滤条件 - filter := d.buildListFilter(req) - - // 检查总数 - total, err = d.checkTotalCount(ctx, filter) - if err != nil { - return - } - - pageNum := req.PageNum - if pageNum <= 0 { - pageNum = 1 - } - pageSize := req.PageSize - if pageSize <= 0 { - pageSize = http.PageSize - } - - limit := int64(pageSize) - skip := int64((pageNum - 1) * pageSize) - - // 排序处理 - sort := bson.M{"statDate": -1} - if !g.IsEmpty(req.SortBy) { - sortDirection := 1 - if req.SortDirection == "desc" { - sortDirection = -1 - } - sort = bson.M{req.SortBy: sortDirection} - } - - opts := options.Find().SetLimit(limit).SetSkip(skip).SetSort(sort) - - err = mongo.Find(ctx, filter, &list, entity.AdStatisticsCollection, opts) - return -} - -// GetOne 获取单个统计记录 -func (d *adStatistics) GetOne(ctx context.Context, id string) (data *entity.AdStatistics, err error) { - objectId, err := bson.ObjectIDFromHex(id) - if err != nil { - return - } - filter := bson.M{"_id": objectId} - - data = &entity.AdStatistics{} - err = mongo.FindOne(ctx, filter, data, entity.AdStatisticsCollection) - return -} - -// GetStatistics 获取统计数据 -func (d *adStatistics) GetStatistics(ctx context.Context, req *dto.GetAdStatisticsReq) (list []*entity.AdStatistics, total int64, err error) { - // 构建查询过滤条件 - filter := d.buildListFilter(req) - - // 检查总数 - total, err = d.checkTotalCount(ctx, filter) - if err != nil { - return - } - - // 排序处理 - sort := bson.M{"statDate": -1} - if !g.IsEmpty(req.SortBy) { - sortDirection := 1 - if req.SortDirection == "desc" { - sortDirection = -1 - } - sort = bson.M{req.SortBy: sortDirection} - } - - opts := options.Find().SetSort(sort) - - err = mongo.Find(ctx, filter, &list, entity.AdStatisticsCollection, opts) - return -} diff --git a/dao/cid_request_dao.go b/dao/cid_request_dao.go index bd0730f..c9dcb72 100644 --- a/dao/cid_request_dao.go +++ b/dao/cid_request_dao.go @@ -15,7 +15,7 @@ type cidRequestDao struct{} // Create 创建CID请求记录 func (d *cidRequestDao) Create(ctx context.Context, request *entity.CidRequest) (id string, err error) { - ids, err := mongo.Insert(ctx, []interface{}{request}, "cid_requests") + ids, err := mongo.Insert(ctx, []interface{}{request}, entity.CidRequestCollection) if err != nil { return "", err } @@ -30,14 +30,14 @@ func (d *cidRequestDao) GetHistory(ctx context.Context, userId string, page, siz filter := bson.M{"userId": userId} // 获取总数 - total, err = mongo.Count(ctx, filter, "cid_requests") + total, err = mongo.Count(ctx, filter, entity.CidRequestCollection) if err != nil { return } // 分页查询 offset := (page - 1) * size - err = mongo.Find(ctx, filter, &list, "cid_requests", + err = mongo.Find(ctx, filter, &list, entity.CidRequestCollection, options.Find().SetSort(bson.M{"createdAt": -1}). SetSkip(int64(offset)). SetLimit(int64(size))) @@ -50,14 +50,14 @@ func (d *cidRequestDao) GetStatistics(ctx context.Context, userId string) (stats stats = make(map[string]interface{}) // 总请求数 - totalRequests, err := mongo.Count(ctx, bson.M{"userId": userId}, "cid_requests") + totalRequests, err := mongo.Count(ctx, bson.M{"userId": userId}, entity.CidRequestCollection) if err != nil { return nil, err } stats["total_requests"] = totalRequests // 成功请求数 - successfulRequests, err := mongo.Count(ctx, bson.M{"userId": userId, "status": "completed"}, "cid_requests") + successfulRequests, err := mongo.Count(ctx, bson.M{"userId": userId, "status": "completed"}, entity.CidRequestCollection) if err != nil { return nil, err } diff --git a/dao/stat_report_dao.go b/dao/stat_report_dao.go deleted file mode 100644 index 15c1943..0000000 --- a/dao/stat_report_dao.go +++ /dev/null @@ -1,88 +0,0 @@ -package dao - -import ( - "context" - - "cid/model/entity" - - "gitee.com/red-future---jilin-g/common/mongo" - "go.mongodb.org/mongo-driver/v2/bson" - "go.mongodb.org/mongo-driver/v2/mongo/options" -) - -// statReportDao 统计报表DAO -type statReportDao struct{} - -var StatReport = &statReportDao{} - -// Create 创建统计报表 -func (d *statReportDao) Create(ctx context.Context, report *entity.StatReport) (err error) { - _, err = mongo.Insert(ctx, []interface{}{report}, "stat_report") - return -} - -// GetByID 根据ID获取统计报表 -func (d *statReportDao) GetByID(ctx context.Context, id string) (report *entity.StatReport, err error) { - filter := bson.M{"_id": id} - report = &entity.StatReport{} - err = mongo.FindOne(ctx, filter, report, "stat_report") - return -} - -// GetByTenantAndDate 根据租户和日期获取统计报表 -func (d *statReportDao) GetByTenantAndDate(ctx context.Context, tenantID, reportType, date string) (report *entity.StatReport, err error) { - filter := bson.M{"tenantId": tenantID, "reportType": reportType, "reportDate": date} - report = &entity.StatReport{} - err = mongo.FindOne(ctx, filter, report, "stat_report") - return -} - -// Update 更新统计报表 -func (d *statReportDao) Update(ctx context.Context, report *entity.StatReport) (err error) { - filter := bson.M{"_id": report.Id} - update := bson.M{"$set": report} - _, err = mongo.Update(ctx, filter, update, "stat_report") - return -} - -// Delete 删除统计报表 -func (d *statReportDao) Delete(ctx context.Context, id string) (err error) { - filter := bson.M{"_id": id} - _, err = mongo.Delete(ctx, filter, "stat_report") - return -} - -// List 统计报表列表 -func (d *statReportDao) List(ctx context.Context, tenantID, appID, reportType, startDate, endDate string, page, pageSize int) (reports []*entity.StatReport, total int, err error) { - filter := bson.M{} - - if tenantID != "" { - filter["tenantId"] = tenantID - } - if appID != "" { - filter["appId"] = appID - } - if reportType != "" { - filter["reportType"] = reportType - } - if startDate != "" && endDate != "" { - filter["reportDate"] = bson.M{"$gte": startDate, "$lte": endDate} - } - - // 获取总数 - total64, err := mongo.Count(ctx, filter, "stat_report") - if err != nil { - return nil, 0, err - } - total = int(total64) - - // 分页参数处理 - limit := int64(pageSize) - skip := int64((page - 1) * pageSize) - - // 排序处理 - opts := options.Find().SetLimit(limit).SetSkip(skip).SetSort(bson.M{"generatedAt": -1}) - - err = mongo.Find(ctx, filter, &reports, "stat_report", opts) - return -} diff --git a/main.go b/main.go index 1cbb5a6..c2d2cc5 100644 --- a/main.go +++ b/main.go @@ -2,9 +2,6 @@ package main import ( "cid/controller" - "cid/service" - "fmt" - "time" "gitee.com/red-future---jilin-g/common/http" "gitee.com/red-future---jilin-g/common/jaeger" @@ -19,22 +16,12 @@ func main() { ctx := context.Background() defer jaeger.ShutDown(ctx) - // 启动统计报表定时任务调度器 - go func() { - time.Sleep(5 * time.Second) // 等待数据库连接初始化完成 - if err := service.StatReportSchedulerInstance.StartScheduler(ctx); err != nil { - fmt.Printf("启动统计报表定时任务失败: %v\n", err) - } - }() - http.RouteRegister([]interface{}{ controller.Advertisement, controller.Advertiser, controller.AdPosition, - controller.AdStatistics, controller.RateLimit, controller.Application, - controller.StatReport, }) select {} } diff --git a/model/dto/ad_statistics_dto.go b/model/dto/ad_statistics_dto.go deleted file mode 100644 index 79937fa..0000000 --- a/model/dto/ad_statistics_dto.go +++ /dev/null @@ -1,113 +0,0 @@ -package dto - -import ( - "cid/model/entity" - - "gitee.com/red-future---jilin-g/common/http" - "github.com/gogf/gf/v2/frame/g" -) - -// GetAdStatisticsReq 获取广告统计数据请求 -type GetAdStatisticsReq struct { - g.Meta `path:"/getStatistics" method:"get" tags:"广告统计" summary:"获取广告统计数据" dc:"获取广告的统计数据"` - - // 分页参数 - http.Page - - // 维度信息 - StatType string `json:"statType" v:"required"` // 统计类型:广告主、广告、广告位等 - StatDimension string `json:"statDimension" v:"required"` // 统计维度:小时、天、周、月 - TargetId string `json:"targetId"` // 目标ID:广告主ID、广告ID、广告位ID等 - - // 时间范围 - StartDate int64 `json:"startDate" v:"required"` // 开始日期 - EndDate int64 `json:"endDate" v:"required"` // 结束日期 - - // 筛选条件 - DeviceType string `json:"deviceType"` // 设备类型 - Platform string `json:"platform"` // 平台 - Region string `json:"region"` // 地区 - Gender string `json:"gender"` // 性别 - AgeGroup string `json:"ageGroup"` // 年龄段 - - // 排序 - SortBy string `json:"sortBy"` // 排序字段 - SortDirection string `json:"sortDirection"` // 排序方向:asc、desc -} - -type GetAdStatisticsRes struct { - Statistics []*entity.AdStatistics `json:"statistics"` - Total int `json:"total"` -} - -// GetDashboardReq 获取仪表盘数据请求 -type GetDashboardReq struct { - g.Meta `path:"/getDashboard" method:"get" tags:"广告仪表盘" summary:"获取仪表盘数据" dc:"获取广告系统的仪表盘统计数据"` - - // 时间范围 - StartDate int64 `json:"startDate" v:"required"` // 开始日期 - EndDate int64 `json:"endDate" v:"required"` // 结束日期 - - // 维度 - Dimension string `json:"dimension"` // 统计维度:天、周、月 -} - -type GetDashboardRes struct { - // 总览数据 - Overview OverviewData `json:"overview"` - - // 趋势数据 - Trends []TrendData `json:"trends"` - - // 排行数据 - TopAdvertisers []RankData `json:"topAdvertisers"` // 广告主排行 - TopAds []RankData `json:"topAds"` // 广告排行 - TopPositions []RankData `json:"topPositions"` // 广告位排行 -} - -// OverviewData 总览数据 -type OverviewData struct { - TotalAdvertisers int64 `json:"totalAdvertisers"` // 广告主总数 - TotalAds int64 `json:"totalAds"` // 广告总数 - TotalPositions int64 `json:"totalPositions"` // 广告位总数 - TotalImpressions int64 `json:"totalImpressions"` // 总展示次数 - TotalClicks int64 `json:"totalClicks"` // 总点击次数 - TotalCost int64 `json:"totalCost"` // 总消耗(分) - TotalRevenue int64 `json:"totalRevenue"` // 总收入(分) - AverageCTR float64 `json:"averageCTR"` // 平均点击率 - AverageCVR float64 `json:"averageCVR"` // 平均转化率 -} - -// TrendData 趋势数据 -type TrendData struct { - Date int64 `json:"date"` // 日期 - Impressions int64 `json:"impressions"` // 展示次数 - Clicks int64 `json:"clicks"` // 点击次数 - Conversions int64 `json:"conversions"` // 转化次数 - Cost int64 `json:"cost"` // 消耗(分) - Revenue int64 `json:"revenue"` // 收入(分) - CTR float64 `json:"ctr"` // 点击率 - CVR float64 `json:"cvr"` // 转化率 -} - -// RankData 排行数据 -type RankData struct { - Id string `json:"id"` // ID - Name string `json:"name"` // 名称 - Impressions int64 `json:"impressions"` // 展示次数 - Clicks int64 `json:"clicks"` // 点击次数 - Cost int64 `json:"cost"` // 消耗(分) - Revenue int64 `json:"revenue"` // 收入(分) - CTR float64 `json:"ctr"` // 点击率 -} - -// GenerateDailyStatisticsReq 生成每日统计数据请求 -type GenerateDailyStatisticsReq struct { - g.Meta `path:"/generateDailyStatistics" method:"post" tags:"广告统计" summary:"生成每日统计数据" dc:"手动生成指定日期的广告统计数据"` - - Date int64 `json:"date" v:"required"` // 日期时间戳 -} - -type GenerateDailyStatisticsRes struct { - Success bool `json:"success"` // 是否成功 -} diff --git a/model/dto/advertisement_dto.go b/model/dto/advertisement_dto.go index 0d1fecd..bfabbcf 100644 --- a/model/dto/advertisement_dto.go +++ b/model/dto/advertisement_dto.go @@ -30,7 +30,7 @@ type AddAdvertisementReq struct { BillingType string `json:"billingType" v:"required"` // 计费类型:CPC、CPM、CPA等 // 投放条件 - Targeting *entity.Targeting `json:"targeting"` // 定向条件 + Targeting *entity.UnifiedTargeting `json:"targeting"` // 定向条件 } type AddAdvertisementRes struct { @@ -62,7 +62,7 @@ type UpdateAdvertisementReq struct { BillingType string `json:"billingType"` // 计费类型:CPC、CPM、CPA等 // 投放条件 - Targeting *entity.Targeting `json:"targeting"` // 定向条件 + Targeting *entity.UnifiedTargeting `json:"targeting"` // 定向条件 // 状态信息 Status *string `json:"status"` // 广告状态:待审核、审核中、已通过、已拒绝、投放中、已暂停、已结束 diff --git a/model/dto/stat_report_dto.go b/model/dto/stat_report_dto.go deleted file mode 100644 index 97e00de..0000000 --- a/model/dto/stat_report_dto.go +++ /dev/null @@ -1,172 +0,0 @@ -package dto - -import "github.com/gogf/gf/v2/frame/g" - -// 报表生成请求 -type ReportGenerateReq struct { - g.Meta `path:"/generateReport" method:"post"` - TenantID int64 `json:"tenant_id" v:"required"` - AppID int64 `json:"app_id"` - ReportType string `json:"report_type" v:"required|in:daily,weekly,monthly,quarterly,yearly"` - Date string `json:"date"` // 格式: 2024-01-01 (daily), 2024-01 (monthly), 2024-Q1 (quarterly), 2024 (yearly) -} - -// 报表生成响应 -type ReportGenerateResp struct { - ReportID string `json:"report_id"` - ReportType string `json:"report_type"` - ReportDate string `json:"report_date"` - Data interface{} `json:"data"` -} - -// 报表列表请求 -type ReportListReq struct { - g.Meta `path:"/getReportList" method:"get"` - TenantID int64 `json:"tenant_id"` - AppID int64 `json:"app_id"` - ReportType string `json:"report_type"` - StartDate string `json:"start_date"` - EndDate string `json:"end_date"` - Page int `json:"page" d:"1"` - PageSize int `json:"page_size" d:"20"` -} - -// 报表列表响应 -type ReportListResp struct { - Reports []*ReportDTO `json:"reports"` - Total int `json:"total"` - Page int `json:"page"` - PageSize int `json:"page_size"` -} - -// 报表详情请求 -type ReportDetailReq struct { - g.Meta `path:"/getReportDetail" method:"get"` - ReportID int64 `json:"report_id" v:"required"` -} -type ExportReportReq struct { - g.Meta `path:"/exportReport" method:"get"` - ReportID int64 `json:"report_id" v:"required"` -} - -// 报表详情响应 -type ReportDetailResp struct { - ID int64 `json:"id"` - TenantID interface{} `json:"tenant_id"` - AppID int64 `json:"app_id"` - ReportType string `json:"report_type"` - ReportDate string `json:"report_date"` - GeneratedAt string `json:"generated_at"` - Data interface{} `json:"data"` -} - -// 报表DTO -type ReportDTO struct { - ID int64 `json:"id"` - TenantID interface{} `json:"tenant_id"` - AppID int64 `json:"app_id"` - ReportType string `json:"report_type"` - ReportDate string `json:"report_date"` - GeneratedAt string `json:"generated_at"` -} - -// 统计查询请求 -type StatQueryReq struct { - g.Meta `path:"/queryStats" method:"get"` - TenantID int64 `json:"tenant_id" v:"required"` - AppID int64 `json:"app_id"` - AdType string `json:"ad_type"` - Platform string `json:"platform"` - Region string `json:"region"` - StartDate string `json:"start_date" v:"required"` - EndDate string `json:"end_date" v:"required"` - Granularity string `json:"granularity" v:"in:daily,weekly,monthly" d:"daily"` // 统计粒度 -} - -// 统计查询响应 -type StatQueryResp struct { - Data []*StatDataPoint `json:"data"` - Summary *StatSummary `json:"summary"` -} - -// 统计数据点 -type StatDataPoint struct { - Date string `json:"date"` - Impressions int64 `json:"impressions"` - Clicks int64 `json:"clicks"` - Revenue float64 `json:"revenue"` - CTR float64 `json:"ctr"` - AvgDuration float64 `json:"avg_duration"` -} - -// 统计摘要 -type StatSummary struct { - TotalImpressions int64 `json:"total_impressions"` - TotalClicks int64 `json:"total_clicks"` - TotalRevenue float64 `json:"total_revenue"` - AvgCTR float64 `json:"avg_ctr"` - AvgDuration float64 `json:"avg_duration"` - GrowthRate *GrowthRate `json:"growth_rate"` -} - -// 增长率统计 -type GrowthRate struct { - Impressions float64 `json:"impressions"` - Clicks float64 `json:"clicks"` - Revenue float64 `json:"revenue"` - CTR float64 `json:"ctr"` -} - -// 实时统计请求 -type RealTimeStatReq struct { - g.Meta `path:"/realTimeStats" method:"get"` - TenantID int64 `json:"tenant_id" v:"required"` - AppID int64 `json:"app_id"` - Hours int `json:"hours" d:"24"` // 过去多少小时的统计 -} - -// 实时统计响应 -type RealTimeStatResp struct { - CurrentHour *HourlyStat `json:"current_hour"` - Last24Hours []*HourlyStat `json:"last_24_hours"` -} - -// 小时统计 -type HourlyStat struct { - Hour string `json:"hour"` - Impressions int64 `json:"impressions"` - Clicks int64 `json:"clicks"` - Revenue float64 `json:"revenue"` -} - -// Controller响应结构 - -// 生成报表响应 -type ReportGenerateRes struct { - Data *ReportGenerateResp `json:"data"` -} - -// 报表列表响应 -type ReportListRes struct { - Data *ReportListResp `json:"data"` -} - -// 报表详情响应 -type ReportDetailRes struct { - Data *ReportDetailResp `json:"data"` -} - -// 统计查询响应 -type StatQueryRes struct { - Data *StatQueryResp `json:"data"` -} - -// 实时统计响应 -type RealTimeStatRes struct { - Data *RealTimeStatResp `json:"data"` -} - -// 导出报表响应 -type ExportReportRes struct { - ReportData *ReportDetailResp `json:"report_data"` -} diff --git a/model/entity/ad_creative.go b/model/entity/ad_creative.go new file mode 100644 index 0000000..f9225dd --- /dev/null +++ b/model/entity/ad_creative.go @@ -0,0 +1,67 @@ +package entity + +import ( + "gitee.com/red-future---jilin-g/common/do" +) + +const AdCreativeCollection = "ad_creative" + +// AdCreative 广告创意素材实体 +type AdCreative struct { + do.MongoBaseDO `bson:",inline" json:",inline"` + AdvertiserId string `bson:"advertiserId" json:"advertiserId"` // 广告主ID + + // 基本信息 + Name string `bson:"name" json:"name"` // 创意名称 + Title string `bson:"title" json:"title"` // 广告标题 + Description string `bson:"description" json:"description"` // 广告描述 + AdType string `bson:"adType" json:"adType"` // 广告类型:image、video、native、interstitial等 + Format string `bson:"format" json:"format"` // 创意格式:jpg、png、mp4、html等 + + // 素材信息 + MaterialURL string `bson:"materialUrl" json:"materialUrl"` // 素材URL + ThumbnailURL string `bson:"thumbnailUrl" json:"thumbnailUrl"` // 缩略图URL + LandingPageURL string `bson:"landingPageUrl" json:"landingPageUrl"` // 落地页URL + DisplayURL string `bson:"displayUrl" json:"displayUrl"` // 显示URL + + // 尺寸和文件信息 + Width int64 `bson:"width" json:"width"` // 宽度(px) + Height int64 `bson:"height" json:"height"` // 高度(px) + Size int64 `bson:"size" json:"size"` // 文件大小(bytes) + Duration int64 `bson:"duration" json:"duration"` // 时长(秒) + HasAudio bool `bson:"hasAudio" json:"hasAudio"` // 是否有音频 + AspectRatio string `bson:"aspectRatio" json:"aspectRatio"` // 宽高比 + + // 技术信息 + MimeType string `bson:"mimeType" json:"mimeType"` // MIME类型 + FileHash string `bson:"fileHash" json:"fileHash"` // 文件哈希值 + Source string `bson:"source" json:"source"` // 来源:upload、sync、generate + BackupURL string `bson:"backupUrl" json:"backupUrl"` // 备份URL + CDNURL string `bson:"cdnUrl" json:"cdnUrl"` // CDN加速URL + CompressInfo string `bson:"compressInfo" json:"compressInfo"` // 压缩信息(JSON格式) + + // 平台兼容性 + SupportedPlatforms []string `bson:"supportedPlatforms" json:"supportedPlatforms"` // 支持的平台 + PlatformSpecific string `bson:"platformSpecific" json:"platformSpecific"` // 平台特定配置(JSON格式) + + // 外部平台信息 + ExternalCreativeId string `bson:"externalCreativeId" json:"externalCreativeId"` // 外部创意ID + PlatformId string `bson:"platformId" json:"platformId"` // 平台ID + SyncStatus string `bson:"syncStatus" json:"syncStatus"` // 同步状态 + LastSyncTime int64 `bson:"lastSyncTime" json:"lastSyncTime"` // 最后同步时间 + + // 基础配置 + BaseConfig `bson:",inline" json:",inline"` // 内联基础配置 + + // 限制配置 + RestrictionConfig `bson:",inline" json:",inline"` // 内联限制配置 + + // 其他信息 + Status string `bson:"status" json:"status"` // 状态:active、inactive、archived + ExpireTime int64 `bson:"expireTime" json:"expireTime"` // 过期时间 +} + +// GetCollectionName 获取集合名称 +func (a *AdCreative) GetCollectionName() string { + return AdCreativeCollection +} diff --git a/model/entity/ad_platform.go b/model/entity/ad_platform.go new file mode 100644 index 0000000..81b7ae3 --- /dev/null +++ b/model/entity/ad_platform.go @@ -0,0 +1,55 @@ +package entity + +import ( + "gitee.com/red-future---jilin-g/common/do" +) + +const AdPlatformCollection = "ad_platform" + +// AdPlatform 广告平台实体 +type AdPlatform struct { + do.MongoBaseDO `bson:",inline" json:",inline"` + Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等 + + // 平台基本信息 + Name string `bson:"name" json:"name"` // 平台名称:小红书、抖音、快手、京东、淘宝、百度等 + Code string `bson:"code" json:"code"` // 平台编码,唯一标识 + DisplayName string `bson:"displayName" json:"displayName"` // 显示名称 + Logo string `bson:"logo" json:"logo"` // 平台Logo + Description string `bson:"description" json:"description"` // 平台描述 + Category string `bson:"category" json:"category"` // 平台分类:social、ecommerce、search、short_video等 + + // 支持的广告类型 + SupportedAdTypes []string `bson:"supportedAdTypes" json:"supportedAdTypes"` // 支持的广告类型 + SupportedFormats []string `bson:"supportedFormats" json:"supportedFormats"` // 支持的广告格式 + + // 技术能力 + RealTimeBidding bool `bson:"realTimeBidding" json:"realTimeBidding"` // 是否支持实时竞价 + ProgrammaticGuaranteed bool `bson:"programmaticGuaranteed" json:"programmaticGuaranteed"` // 是否支持程序化保障 + HeaderBidding bool `bson:"headerBidding" json:"headerBidding"` // 是否支持Header Bidding + + // API配置 + APIConfig `bson:",inline" json:",inline"` // 内联API配置 + + // 竞价配置 + BiddingConfig `bson:",inline" json:",inline"` // 内联竞价配置 + + // 支付配置 + PaymentConfig `bson:",inline" json:",inline"` // 内联支付配置 + + // 限流配置 + RateLimit int64 `bson:"rateLimit" json:"rateLimit"` // 速率限制 + MaxBudgetPerDay int64 `bson:"maxBudgetPerDay" json:"maxBudgetPerDay"` // 每日最大预算 + + LastSyncTime int64 `bson:"lastSyncTime" json:"lastSyncTime"` // 最后同步时间 + + // 联系信息 + SupportContact string `bson:"supportContact" json:"supportContact"` // 技术支持联系方式 + AccountManager string `bson:"accountManager" json:"accountManager"` // 客户经理 + TechDocumentation string `bson:"techDocumentation" json:"techDocumentation"` // 技术文档链接 +} + +// GetCollectionName 获取集合名称 +func (a *AdPlatform) GetCollectionName() string { + return AdPlatformCollection +} diff --git a/model/entity/ad_position.go b/model/entity/ad_position.go index c1b15ef..799d64d 100644 --- a/model/entity/ad_position.go +++ b/model/entity/ad_position.go @@ -8,7 +8,8 @@ const AdPositionCollection = "ad_position" // AdPosition 广告位实体 type AdPosition struct { - do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + do.MongoBaseDO `bson:",inline" json:",inline"` + Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等 // 基本信息 Name string `bson:"name" json:"name"` // 广告位名称 @@ -17,8 +18,8 @@ type AdPosition struct { AdFormat string `bson:"adFormat" json:"adFormat"` // 支持的广告格式 // 尺寸信息 - Width int `bson:"width" json:"width"` // 宽度(px) - Height int `bson:"height" json:"height"` // 高度(px) + Width int64 `bson:"width" json:"width"` // 宽度(px) + Height int64 `bson:"height" json:"height"` // 高度(px) // 位置信息 Page string `bson:"page" json:"page"` // 所属页面 @@ -39,16 +40,11 @@ type AdPosition struct { // 展示规则 DisplayRules *DisplayRules `bson:"displayRules" json:"displayRules"` // 展示规则 - // 状态信息 - Status string `bson:"status" json:"status"` // 广告位状态:启用、禁用、测试 - IsExclusive bool `bson:"isExclusive" json:"isExclusive"` // 是否独占广告位 + // 限制配置 + RestrictionConfig `bson:",inline" json:",inline"` // 内联限制配置 - // 统计信息 - DailyImpressions int64 `bson:"dailyImpressions" json:"dailyImpressions"` // 日均展示量 - DailyClicks int64 `bson:"dailyClicks" json:"dailyClicks"` // 日均点击量 - DailyRevenue int64 `bson:"dailyRevenue" json:"dailyRevenue"` // 日均收入(分) - CTR float64 `bson:"ctr" json:"ctr"` // 点击率 - eCPM int64 `bson:"ecpm" json:"ecpm"` // 有效千次展示收入 + // 其他状态 + IsExclusive bool `bson:"isExclusive" json:"isExclusive"` // 是否独占广告位 } // DisplayRules 广告位展示规则 @@ -80,3 +76,8 @@ type ExcludeCondition struct { Type string `bson:"type" json:"type"` // 条件类型 Value interface{} `bson:"value" json:"value"` // 条件值 } + +// GetCollectionName 获取集合名称 +func (a *AdPosition) GetCollectionName() string { + return AdPositionCollection +} diff --git a/model/entity/ad_source.go b/model/entity/ad_source.go index 390329a..3267c3a 100644 --- a/model/entity/ad_source.go +++ b/model/entity/ad_source.go @@ -8,156 +8,65 @@ const AdSourceCollection = "ad_source" // AdSource 广告源实体 type AdSource struct { - do.MongoBaseDO `bson:",inline" json:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + do.MongoBaseDO `bson:",inline" json:",inline"` + Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等 // 基本信息 - Name string `json:"name"` // 广告源名称 - Code string `json:"code"` // 广告源编码,唯一标识 - Provider string `json:"provider"` // 提供商:google、facebook、baidu、tencent、self等 - Type string `json:"type"` // 类型:self(自营)、third_party(第三方)、exchange(广告交易平台) - Description string `json:"description"` // 描述 + Name string `bson:"name" json:"name"` // 广告源名称 + Code string `bson:"code" json:"code"` // 广告源编码,唯一标识 + Provider string `bson:"provider" json:"provider"` // 提供商:self(自营)、chuanshanjia(穿山甲)、gdt(腾讯广点通)、baidu(百度)、byteance(字节跳动)等 + Type string `bson:"type" json:"type"` // 类型:self(自营)、third_party(第三方)、exchange(广告交易平台)、platform_ad_source(平台广告源) + Category string `bson:"category" json:"category"` // 分类:network、ssp、dsp、rtb等 // 连接配置 - Config string `json:"config"` // 广告源配置(JSON字符串) + Config string `bson:"config" json:"config"` // 广告源配置(JSON字符串) // API配置 - APIEndpoint string `json:"apiEndpoint"` // API端点 - APIVersion string `json:"apiVersion"` // API版本 - AuthType string `json:"authType"` // 认证类型:api_key、oauth、basic - AuthConfig string `json:"authConfig"` // 认证配置(JSON字符串) - Headers string `json:"headers"` // 请求头配置(JSON字符串) - Timeout int `json:"timeout"` // 超时时间(毫秒) - RetryCount int `json:"retryCount"` // 重试次数 + APIConfig `bson:",inline" json:",inline"` // 内联API配置 + + // 创意配置 + CreativeConfig `bson:",inline" json:",inline"` // 内联创意配置 // 广告源能力 - Capabilities string `json:"capabilities"` // 广告源能力(JSON字符串) + Capabilities *AdSourceCapabilities `bson:"capabilities" json:"capabilities"` // 广告源能力 - // 质量指标 - QualityMetrics string `json:"qualityMetrics"` // 质量指标(JSON字符串) - - // 财务设置 - PaymentTerms string `json:"paymentTerms"` // 支付条款(JSON字符串) - - // 状态信息 - Status string `json:"status"` // 广告源状态:active、inactive、maintenance - Health string `json:"health"` // 健康状态:healthy、degraded、unhealthy - LastCheckAt int64 `json:"lastCheckAt"` // 最后检查时间 - - // 统计信息 - TotalRequests int64 `json:"totalRequests"` // 总请求数 - SuccessfulRequests int64 `json:"successfulRequests"` // 成功请求数 - FailedRequests int64 `json:"failedRequests"` // 失败请求数 - AverageResponseTime float64 `json:"averageResponseTime"` // 平均响应时间(毫秒) - FillRate float64 `json:"fillRate"` // 填充率 - CTR float64 `json:"ctr"` // 点击率 - CVR float64 `json:"cvr"` // 转化率 - - // 系统信息 - Priority int `json:"priority"` // 优先级,数值越高优先级越高 -} - -// AdSourceConfig 广告源配置 -type AdSourceConfig struct { - // 基础配置 - SupportedFormats []string `json:"supportedFormats"` // 支持的广告格式 - SupportedSizes []string `json:"supportedSizes"` // 支持的尺寸 - SupportedDevices []string `json:"supportedDevices"` // 支持的设备类型 - SupportedOS []string `json:"supportedOS"` // 支持的操作系统 - SupportedCountries []string `json:"supportedCountries"` // 支持的国家/地区 - - // 竞价配置 - BiddingType string `json:"biddingType"` // 竞价类型:cpm、cpc、cpa、rtb - MinBidAmount int64 `json:"minBidAmount"` // 最小出价(分) - MaxBidAmount int64 `json:"maxBidAmount"` // 最大出价(分) - BidIncrement int64 `json:"bidIncrement"` // 出价增量(分) - DefaultBidAmount int64 `json:"defaultBidAmount"` // 默认出价(分) - AutoOptimization bool `json:"autoOptimization"` // 是否自动优化 - - // 定向配置 - TargetingSupport *TargetingSupport `json:"targetingSupport"` // 定向支持 - - // 其他配置 - MaxAdsPerRequest int `json:"maxAdsPerRequest"` // 单次请求最大广告数量 - BrandSafety bool `json:"brandSafety"` // 品牌安全 - Viewability bool `json:"viewability"` // 可见性支持 -} - -// TargetingSupport 定向支持 -type TargetingSupport struct { - GeoTargeting bool `json:"geoTargeting"` // 地理定向 - DemographicTargeting bool `json:"demographicTargeting"` // 人口统计定向 - BehavioralTargeting bool `json:"behavioralTargeting"` // 行为定向 - ContextualTargeting bool `json:"contextualTargeting"` // 上下文定向 - DeviceTargeting bool `json:"deviceTargeting"` // 设备定向 - TimeTargeting bool `json:"timeTargeting"` // 时间定向 - Retargeting bool `json:"retargeting"` // 重定向 - CookieTargeting bool `json:"cookieTargeting"` // Cookie定向 + // 支付配置 + PaymentConfig `bson:",inline" json:",inline"` // 内联支付配置 } // AdSourceCapabilities 广告源能力 type AdSourceCapabilities struct { // 广告格式 - SupportedFormats []AdFormat `json:"supportedFormats"` // 支持的广告格式 + SupportedFormats []AdFormat `bson:"supportedFormats" json:"supportedFormats"` // 支持的广告格式 // 功能特性 - RealTimeBidding bool `json:"realTimeBidding"` // 实时竞价 - HeaderBidding bool `json:"headerBidding"` // 标题竞价 - ProgrammaticDirect bool `json:"programmaticDirect"` // 程序化直购 - PrivateMarketplace bool `json:"privateMarketplace"` // 私有交易市场 + 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 `json:"fraudDetection"` // 反欺诈检测 - BrandSafety bool `json:"brandSafety"` // 品牌安全 - Viewability bool `json:"viewability"` // 可见度验证 - CreativeApproval bool `json:"creativeApproval"` // 创意审核 + 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 `json:"audienceTargeting"` // 受众定向 - ContextualTargeting bool `json:"contextualTargeting"` // 上下文定向 - CrossDeviceTargeting bool `json:"crossDeviceTargeting"` // 跨设备定向 + AudienceTargeting bool `bson:"audienceTargeting" json:"audienceTargeting"` // 受众定向 + ContextualTargeting bool `bson:"contextualTargeting" json:"contextualTargeting"` // 上下文定向 + CrossDeviceTargeting bool `bson:"crossDeviceTargeting" json:"crossDeviceTargeting"` // 跨设备定向 } // AdFormat 广告格式 type AdFormat struct { - Type string `json:"type"` // 格式类型:banner、video、native、interstitial等 - Name string `json:"name"` // 格式名称 - Width int `json:"width"` // 宽度 - Height int `json:"height"` // 高度 - MimeType string `json:"mimeType"` // MIME类型 + 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 `json:"averageResponseTime"` // 平均响应时间(毫秒) - SuccessRate float64 `json:"successRate"` // 成功率 - ErrorRate float64 `json:"errorRate"` // 错误率 - Uptime float64 `json:"uptime"` // 可用性(百分比) - - // 广告质量 - CTR float64 `json:"ctr"` // 点击率 - CVR float64 `json:"cvr"` // 转化率 - FillRate float64 `json:"fillRate"` // 填充率 - ViewabilityRate float64 `json:"viewabilityRate"` // 可见率 - BrandSafetyScore float64 `json:"brandSafetyScore"` // 品牌安全评分 - - // 财务指标 - Ecpm int64 `json:"ecpm"` // 有效千次展示成本(分) - Ecpc int64 `json:"ecpc"` // 有效点击成本(分) - RevenuePerRequest int64 `json:"revenuePerRequest"` // 每请求收入(分) - - // 时间指标 - LastUpdated int64 `json:"lastUpdated"` // 最后更新时间 - MetricsUpdateWindow int `json:"metricsUpdateWindow"` // 指标更新窗口(分钟) -} - -// PaymentTerms 支付条款 -type PaymentTerms struct { - BillingModel string `json:"billingModel"` // 计费模式:cpm、cpc、cpa、rev_share - PaymentTerms string `json:"paymentTerms"` // 支付条款:net_30、net_60、net_90 - RevShareRate float64 `json:"revShareRate"` // 收入分成比例(0-1) - MinPayment int64 `json:"minPayment"` // 最小支付金额(分) - Currency string `json:"currency"` // 货币单位 - TaxInclusive bool `json:"taxInclusive"` // 是否含税 - EarlyPaymentDiscount float64 `json:"earlyPaymentDiscount"` // 提前付款折扣 +// GetCollectionName 获取集合名称 +func (a *AdSource) GetCollectionName() string { + return AdSourceCollection } diff --git a/model/entity/ad_statistics.go b/model/entity/ad_statistics.go deleted file mode 100644 index 1e8157a..0000000 --- a/model/entity/ad_statistics.go +++ /dev/null @@ -1,92 +0,0 @@ -package entity - -import ( - "gitee.com/red-future---jilin-g/common/do" -) - -const AdStatisticsCollection = "ad_statistics" - -// AdStatistics 广告统计实体 -type AdStatistics struct { - do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted - - // 维度信息 - StatType string `bson:"statType" json:"statType"` // 统计类型:广告主、广告、广告位等 - StatDimension string `bson:"statDimension" json:"statDimension"` // 统计维度:小时、天、周、月 - TargetId string `bson:"targetId" json:"targetId"` // 目标ID:广告主ID、广告ID、广告位ID等 - TargetName string `bson:"targetName" json:"targetName"` // 目标名称:广告主名称、广告名称、广告位名称等 - StatDate int64 `bson:"statDate" json:"statDate"` // 统计日期(Unix时间戳) - - // 基础数据 - Impressions int64 `bson:"impressions" json:"impressions"` // 展示次数 - Clicks int64 `bson:"clicks" json:"clicks"` // 点击次数 - Conversions int64 `bson:"conversions" json:"conversions"` // 转化次数 - UniqueUsers int64 `bson:"uniqueUsers" json:"uniqueUsers"` // 唯一用户数 - NewUsers int64 `bson:"newUsers" json:"newUsers"` // 新用户数 - ReturnUsers int64 `bson:"returnUsers" json:"returnUsers"` // 回访用户数 - ViewTime int64 `bson:"viewTime" json:"viewTime"` // 查看时间(秒) - - // 财务数据 - Cost int64 `bson:"cost" json:"cost"` // 消耗(分) - Revenue int64 `bson:"revenue" json:"revenue"` // 收入(分) - Budget int64 `bson:"budget" json:"budget"` // 预算(分) - RemainingBudget int64 `bson:"remainingBudget" json:"remainingBudget"` // 剩余预算(分) - - // 比率数据 - CTR float64 `bson:"ctr" json:"ctr"` // 点击率 - CVR float64 `bson:"cvr" json:"cvr"` // 转化率 - BounceRate float64 `bson:"bounceRate" json:"bounceRate"` // 跳出率 - EngagementRate float64 `bson:"engagementRate" json:"engagementRate"` // 互动率 - - // 成本数据 - CPM int64 `bson:"cpm" json:"cpm"` // 千次展示成本 - CPC int64 `bson:"cpc" json:"cpc"` // 单次点击成本 - CPA int64 `bson:"cpa" json:"cpa"` // 单次转化成本 - eCPM int64 `bson:"ecpm" json:"ecpm"` // 有效千次展示收入 - eCPC int64 `bson:"ecpc" json:"ecpc"` // 有效单次点击收入 - eCPA int64 `bson:"ecpa" json:"ecpa"` // 有效单次转化收入 - - // 其他信息 - DeviceType string `bson:"deviceType" json:"deviceType"` // 设备类型 - Platform string `bson:"platform" json:"platform"` // 平台 - Region string `bson:"region" json:"region"` // 地区 - Gender string `bson:"gender" json:"gender"` // 性别 - AgeGroup string `bson:"ageGroup" json:"ageGroup"` // 年龄段 - Extra map[string]interface{} `bson:"extra" json:"extra"` // 扩展字段 -} - -const AdReportCollection = "ad_report" - -// AdReport 广告报表实体 -type AdReport struct { - do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted - - // 报表信息 - ReportName string `bson:"reportName" json:"reportName"` // 报表名称 - ReportType string `bson:"reportType" json:"reportType"` // 报表类型:日报、周报、月报、自定义 - ReportPeriod string `bson:"reportPeriod" json:"reportPeriod"` // 报表周期 - StartDate int64 `bson:"startDate" json:"startDate"` // 开始日期 - EndDate int64 `bson:"endDate" json:"endDate"` // 结束日期 - ReportData []ReportItem `bson:"reportData" json:"reportData"` // 报表数据 - - // 状态信息 - Status string `bson:"status" json:"status"` // 报表状态:生成中、已完成、失败 - GenerateTime int64 `bson:"generateTime" json:"generateTime"` // 生成时间 - DownloadUrl string `bson:"downloadUrl" json:"downloadUrl"` // 下载链接 - ExpiredTime int64 `bson:"expiredTime" json:"expiredTime"` // 过期时间 - FileSize int64 `bson:"fileSize" json:"fileSize"` // 文件大小(字节) - FileFormat string `bson:"fileFormat" json:"fileFormat"` // 文件格式:CSV、Excel、PDF - - // 其他信息 - Operator string `bson:"operator" json:"operator"` // 操作人 - EmailRecipients []string `bson:"emailRecipients" json:"emailRecipients"` // 邮件接收人列表 - Schedule string `bson:"schedule" json:"schedule"` // 定时设置 - LastSentTime int64 `bson:"lastSentTime" json:"lastSentTime"` // 上次发送时间 - NextSendTime int64 `bson:"nextSendTime" json:"nextSendTime"` // 下次发送时间 -} - -// ReportItem 报表项 -type ReportItem struct { - Dimension string `bson:"dimension" json:"dimension"` // 维度名称 - Data map[string]interface{} `bson:"data" json:"data"` // 数据 -} diff --git a/model/entity/advertisement.go b/model/entity/advertisement.go index 963a51d..a8257ff 100644 --- a/model/entity/advertisement.go +++ b/model/entity/advertisement.go @@ -8,78 +8,46 @@ const AdvertisementCollection = "advertisement" // Advertisement 广告实体 type Advertisement struct { - do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + do.MongoBaseDO `bson:",inline" json:",inline"` + AdvertiserId string `bson:"advertiserId" json:"advertiserId"` // 广告主ID // 广告基本信息 Title string `bson:"title" json:"title"` // 广告标题 Description string `bson:"description" json:"description"` // 广告描述 - AdvertiserId string `bson:"advertiserId" json:"advertiserId"` // 广告主ID AdPositionId string `bson:"adPositionId" json:"adPositionId"` // 广告位ID AdType string `bson:"adType" json:"adType"` // 广告类型:图片、视频、文字等 AdFormat string `bson:"adFormat" json:"adFormat"` // 广告格式 MaterialUrl string `bson:"materialUrl" json:"materialUrl"` // 广告素材URL TargetUrl string `bson:"targetUrl" json:"targetUrl"` // 目标链接(点击跳转或落地页) - // 投放设置 - StartDate int64 `bson:"startDate" json:"startDate"` // 开始投放时间 - EndDate int64 `bson:"endDate" json:"endDate"` // 结束投放时间 - Budget int64 `bson:"budget" json:"budget"` // 预算(分) - DailyBudget int64 `bson:"dailyBudget" json:"dailyBudget"` // 日预算(分) - BidAmount int64 `bson:"bidAmount" json:"bidAmount"` // 出价(分) - BillingType string `bson:"billingType" json:"billingType"` // 计费类型:CPC、CPM、CPA等 + // 平台和广告源信息 + AdSourceId string `bson:"adSourceId" json:"adSourceId"` // 广告源ID + AdPlatformId string `bson:"adPlatformId" json:"adPlatformId"` // 广告平台ID(当广告来自第三方平台时) + ExternalAdId string `bson:"externalAdId" json:"externalAdId"` // 外部广告ID(第三方平台的广告ID) + AdProvider string `bson:"adProvider" json:"adProvider"` // 广告提供者:self、chuanshanjia、xiaohongshu、douyin等 - // 投放条件 - Targeting *Targeting `bson:"targeting" json:"targeting"` // 定向条件 + // 投放配置 + BudgetConfig `bson:",inline" json:",inline"` // 内联预算配置 + BidAmount int64 `bson:"bidAmount" json:"bidAmount"` // 出价(分) + BillingType string `bson:"billingType" json:"billingType"` // 计费类型:CPC、CPM、CPA等 - // 状态信息 - Status string `bson:"status" json:"status"` // 广告状态:待审核、审核中、已通过、已拒绝、投放中、已暂停、已结束 + // 定向条件 + Targeting *UnifiedTargeting `bson:"targeting" json:"targeting"` // 统一定向条件 + + // 审核状态 + AuditStatus string `bson:"auditStatus" json:"auditStatus"` // 广告状态:待审核、审核中、已通过、已拒绝、投放中、已暂停、已结束 AuditReason string `bson:"auditReason" json:"auditReason"` // 审核不通过原因 AuditTime int64 `bson:"auditTime" json:"auditTime"` // 审核时间 AuditBy string `bson:"auditBy" json:"auditBy"` // 审核人 - // 基础统计信息(比率字段通过计算得到,不持久化存储) - Impressions int64 `bson:"impressions" json:"impressions"` // 展示次数 - Clicks int64 `bson:"clicks" json:"clicks"` // 点击次数 - Conversions int64 `bson:"conversions" json:"conversions"` // 转化次数 - Cost int64 `bson:"cost" json:"cost"` // 消耗(分) + // 限制配置 + RestrictionConfig `bson:",inline" json:",inline"` // 内联限制配置 + + // 其他状态信息 + Status string `bson:"status" json:"status"` // 业务状态:active、inactive、archived } -// Targeting 广告定向条件 -type Targeting struct { - // 地域定向 - Regions []string `bson:"regions" json:"regions"` // 地域列表 - - // 兴趣定向 - Interests []string `bson:"interests" json:"interests"` // 兴趣标签 - - // 年龄定向 - AgeRange *AgeRange `bson:"ageRange" json:"ageRange"` // 年龄范围 - - // 性别定向 - Gender []string `bson:"gender" json:"gender"` // 性别:男、女、全部 - - // 设备定向 - Devices []string `bson:"devices" json:"devices"` // 设备类型 - - // 操作系统定向 - OperatingSystems []string `bson:"operatingSystems" json:"operatingSystems"` // 操作系统 - - // 时间定向 - TimeSlots []TimeSlot `bson:"timeSlots" json:"timeSlots"` // 时间段 - - // 行为定向 - Behaviors []string `bson:"behaviors" json:"behaviors"` // 行为标签 -} - -// AgeRange 年龄范围 -type AgeRange struct { - Min int `bson:"min" json:"min"` // 最小年龄 - Max int `bson:"max" json:"max"` // 最大年龄 -} - -// TimeSlot 时间段 -type TimeSlot struct { - DayOfWeek int `bson:"dayOfWeek" json:"dayOfWeek"` // 星期几:0-6,0表示星期日 - StartTime string `bson:"startTime" json:"startTime"` // 开始时间,格式:HH:mm - EndTime string `bson:"endTime" json:"endTime"` // 结束时间,格式:HH:mm +// GetCollectionName 获取集合名称 +func (a *Advertisement) GetCollectionName() string { + return AdvertisementCollection } diff --git a/model/entity/advertiser.go b/model/entity/advertiser.go index ee5e1e7..021ea7d 100644 --- a/model/entity/advertiser.go +++ b/model/entity/advertiser.go @@ -8,7 +8,8 @@ const AdvertiserCollection = "advertiser" // Advertiser 广告主实体 type Advertiser struct { - do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + do.MongoBaseDO `bson:",inline" json:",inline"` + Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等 // 基本信息 Name string `bson:"name" json:"name"` // 广告主名称 @@ -16,7 +17,6 @@ type Advertiser struct { ContactPhone string `bson:"contactPhone" json:"contactPhone"` // 联系电话 ContactEmail string `bson:"contactEmail" json:"contactEmail"` // 联系邮箱 Company string `bson:"company" json:"company"` // 公司名称 - Industry string `bson:"industry" json:"industry"` // 所属行业 Scale string `bson:"scale" json:"scale"` // 公司规模 // 证件信息 @@ -36,14 +36,18 @@ type Advertiser struct { SignDate int64 `bson:"signDate" json:"signDate"` // 签约日期 ExpireDate int64 `bson:"expireDate" json:"expireDate"` // 到期日期 - // 状态信息 - Status string `bson:"status" json:"status"` // 广告主状态:待审核、审核中、已通过、已拒绝、已冻结 + // 审核状态 + AuditStatus string `bson:"auditStatus" json:"auditStatus"` // 广告主状态:待审核、审核中、已通过、已拒绝、已冻结 AuditReason string `bson:"auditReason" json:"auditReason"` // 审核不通过原因 AuditTime int64 `bson:"auditTime" json:"auditTime"` // 审核时间 AuditBy string `bson:"auditBy" json:"auditBy"` // 审核人 // 系统信息 - AccountBalance int64 `bson:"accountBalance" json:"accountBalance"` // 账户余额(分) - CreditLimit int64 `bson:"creditLimit" json:"creditLimit"` // 授信额度(分) - Remark string `bson:"remark" json:"remark"` // 备注 + AccountBalance int64 `bson:"accountBalance" json:"accountBalance"` // 账户余额(分) + CreditLimit int64 `bson:"creditLimit" json:"creditLimit"` // 授信额度(分) +} + +// GetCollectionName 获取集合名称 +func (a *Advertiser) GetCollectionName() string { + return AdvertiserCollection } diff --git a/model/entity/app_platform_config.go b/model/entity/app_platform_config.go new file mode 100644 index 0000000..3d75598 --- /dev/null +++ b/model/entity/app_platform_config.go @@ -0,0 +1,32 @@ +package entity + +import ( + "gitee.com/red-future---jilin-g/common/do" +) + +const AppPlatformConfigCollection = "app_platform_config" + +// AppPlatformConfig 应用平台配置实体 +type AppPlatformConfig struct { + do.MongoBaseDO `bson:",inline" json:",inline"` + Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等 + + // 关联信息 + AppID string `bson:"appId" json:"appId"` // 应用ID + PlatformID string `bson:"platformId" json:"platformId"` // 平台ID + + // 配置信息 + Config string `bson:"config" json:"config"` // 配置信息(JSON字符串) + MaxAdsPerReq int `bson:"maxAdsPerReq" json:"maxAdsPerReq"` // 每次请求最大广告数 + + // 定向配置 + TargetingRules string `bson:"targetingRules" json:"targetingRules"` // 定向规则(JSON字符串) + + // 过滤配置 + FilterRules string `bson:"filterRules" json:"filterRules"` // 过滤规则(JSON字符串) +} + +// GetCollectionName 获取集合名称 +func (a *AppPlatformConfig) GetCollectionName() string { + return AppPlatformConfigCollection +} diff --git a/model/entity/application.go b/model/entity/application.go index 9f3d500..a83e24c 100644 --- a/model/entity/application.go +++ b/model/entity/application.go @@ -8,40 +8,47 @@ const ApplicationCollection = "application" // Application 应用实体 type Application struct { - do.MongoBaseDO `bson:",inline" json:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + do.MongoBaseDO `bson:",inline" json:",inline"` + Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等 - // 应用信息 - Name string `json:"name"` // 应用名称 - Code string `json:"code"` // 应用编码 - Description string `json:"description"` // 应用描述 - Icon string `json:"icon"` // 应用图标 + // 应用基本信息 + Name string `bson:"name" json:"name"` // 应用名称 + Code string `bson:"code" json:"code"` // 应用编码 + Description string `bson:"description" json:"description"` // 应用描述 + AppKey string `bson:"appKey" json:"appKey"` // 应用密钥 + AppSecret string `bson:"appSecret" json:"appSecret"` // 应用秘钥 + Platform string `bson:"platform" json:"platform"` // 平台:web、ios、android、h5 + Version string `bson:"version" json:"version"` // 版本号 + PackageName string `bson:"packageName" json:"packageName"` // 包名(移动应用) + BundleID string `bson:"bundleId" json:"bundleId"` // Bundle ID(iOS应用) + AppStoreURL string `bson:"appStoreUrl" json:"appStoreUrl"` // 应用商店URL - // 平台信息 - Platform string `json:"platform"` // 平台:web, h5, android, ios - PackageName string `json:"packageName"` // 包名(Android)/Bundle ID(iOS) - AppStoreURL string `json:"appStoreUrl"` // 应用商店链接 + // 应用配置 + Config string `bson:"config" json:"config"` // 应用配置(JSON字符串) + Permissions string `bson:"permissions" json:"permissions"` // 权限配置(JSON字符串) - // 配置信息 - Categories []string `json:"categories"` // 应用分类 - Tags []string `json:"tags"` // 应用标签 - Config string `json:"config"` // 应用配置(JSON格式) - AdTypes []string `json:"adTypes"` // 支持的广告类型 + // 应用分类和标签 + Categories []string `bson:"categories" json:"categories"` // 应用分类 + Tags []string `bson:"tags" json:"tags"` // 标签 + AdTypes []string `bson:"adTypes" json:"adTypes"` // 支持的广告类型 - // 状态信息 - Status string `json:"status"` // 状态:active, inactive, audit - AuditStatus string `json:"auditStatus"` // 审核状态 - AuditReason string `json:"auditReason"` // 审核原因 + // 回调配置 + CallbackURL string `bson:"callbackUrl" json:"callbackUrl"` // 回调URL - // 统计信息 - DailyRequests int64 `json:"dailyRequests"` // 日请求量 - MonthlyRequests int64 `json:"monthlyRequests"` // 月请求量 + // 应用特定统计 + DailyActiveUsers int64 `bson:"dailyActiveUsers" json:"dailyActiveUsers"` // 日活用户数 + MonthlyActiveUsers int64 `bson:"monthlyActiveUsers" json:"monthlyActiveUsers"` // 月活用户数 + TotalRequests int64 `bson:"totalRequests" json:"totalRequests"` // 总请求数 + DailyRequests int64 `bson:"dailyRequests" json:"dailyRequests"` // 日请求数 + MonthlyRequests int64 `bson:"monthlyRequests" json:"monthlyRequests"` // 月请求数 - // API密钥 - AppKey string `json:"appKey"` // 应用密钥 - AppSecret string `json:"appSecret"` // 应用密钥 - - // 回调信息 - CallbackURL string `json:"callbackUrl"` // 回调URL - - Remark string `json:"remark"` // 备注 + // 联系信息 + ContactName string `bson:"contactName" json:"contactName"` // 联系人姓名 + ContactEmail string `bson:"contactEmail" json:"contactEmail"` // 联系邮箱 + ContactPhone string `bson:"contactPhone" json:"contactPhone"` // 联系电话 +} + +// GetCollectionName 获取集合名称 +func (a *Application) GetCollectionName() string { + return ApplicationCollection } diff --git a/model/entity/cid_request.go b/model/entity/cid_request.go index 556eeec..59881c5 100644 --- a/model/entity/cid_request.go +++ b/model/entity/cid_request.go @@ -6,18 +6,21 @@ import ( const CidRequestCollection = "cid_request" -// CidRequest CID请求实体 +// CidRequest CID请求实体(合并后的统一版本) type CidRequest struct { - do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, IsDeleted - UserID string `bson:"userId" json:"userId"` - // 请求信息 + do.MongoBaseDO `bson:",inline" json:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, IsDeleted + + // 请求基础信息 RequestID string `bson:"requestId" json:"requestId"` // 请求唯一ID SessionID string `bson:"sessionId" json:"sessionId"` // 会话ID + UserID string `bson:"userId" json:"userId"` // 用户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"` // 广告位格式 @@ -30,15 +33,17 @@ type CidRequest struct { 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"` // 时间信息 + // 用户上下文信息(使用统一版本) + UserContext *UnifiedUserContext `bson:"userContext" json:"userContext"` // 用户上下文 + DeviceInfo *DeviceInfo `bson:"deviceInfo" json:"deviceInfo"` // 设备信息 + LocationInfo *UnifiedLocationInfo `bson:"locationInfo" json:"locationInfo"` // 位置信息 + TemporalInfo *UnifiedTemporalInfo `bson:"temporalInfo" json:"temporalInfo"` // 时间信息 - // 请求参数 - RequestParams *RequestParams `bson:"requestParams" json:"requestParams"` // 请求参数 - TargetingRules *TargetingRules `bson:"targetingRules" json:"targetingRules"` // 定向规则 + // 请求参数(使用合并版本) + RequestParams *RequestParams `bson:"requestParams" json:"requestParams"` // 请求参数 + + // 定向规则(使用统一的定向结构) + TargetingRules *UnifiedTargeting `bson:"targetingRules" json:"targetingRules"` // 定向规则 // 策略配置 StrategyConfig *StrategyConfig `bson:"strategyConfig" json:"strategyConfig"` // 策略配置 @@ -70,14 +75,111 @@ type CidRequest struct { Version string `bson:"version" json:"version"` // 系统版本 } -// CidResponse CID响应 +// GetCollectionName 获取集合名称 +func (c *CidRequest) GetCollectionName() string { + return CidRequestCollection +} + +// UnifiedUserContext 统一的用户上下文 +type UnifiedUserContext 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"` // 自定义数据 +} + +// UnifiedLocationInfo 统一的位置信息 +type UnifiedLocationInfo 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"` // 互联网服务提供商 +} + +// UnifiedTemporalInfo 统一的时间信息 +type UnifiedTemporalInfo 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"` // 节假日 +} + +// 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连接 +} + +// 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"` // 自定义参数 +} + +// 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"` // 自定义设置 +} + +// CidResponse CID响应(合并版本) type CidResponse struct { Ads []Ad `bson:"ads" json:"ads"` // 广告列表 TrackingInfo *TrackingInfo `bson:"trackingInfo" json:"trackingInfo"` // 跟踪信息 Metadata *ResponseMetadata `bson:"metadata" json:"metadata"` // 响应元数据 } -// Ad 广告 +// Ad 广告结构(合并版本) type Ad struct { ID string `bson:"id" json:"id"` // 广告ID AdSource string `bson:"adSource" json:"adSource"` // 广告源 @@ -105,7 +207,7 @@ type Ad struct { Score float64 `bson:"score" json:"score"` // 评分 } -// TrackingInfo 跟踪信息 +// TrackingInfo 跟踪信息(合并版本) type TrackingInfo struct { ImpressionURLs []string `bson:"impressionUrls" json:"impressionUrls"` // 展示跟踪URL ClickURLs []string `bson:"clickUrls" json:"clickUrls"` // 点击跟踪URL @@ -115,7 +217,7 @@ type TrackingInfo struct { BeaconURLs []string `bson:"beaconUrls" json:"beaconUrls"` // 信标URL } -// ResponseMetadata 响应元数据 +// ResponseMetadata 响应元数据(合并版本) type ResponseMetadata struct { TotalAvailableAds int `bson:"totalAvailableAds" json:"totalAvailableAds"` // 总可用广告数 SelectedAds int `bson:"selectedAds" json:"selectedAds"` // 选择的广告数 @@ -132,7 +234,7 @@ type ResponseMetadata struct { AdSourcesUsed []string `bson:"adSourcesUsed" json:"adSourcesUsed"` // 使用的广告源 } -// AdSourceResponse 广告源响应 +// AdSourceResponse 广告源响应(合并版本) type AdSourceResponse struct { AdSource string `bson:"adSource" json:"adSource"` // 广告源名称 Status string `bson:"status" json:"status"` // 响应状态:success、timeout、error @@ -147,164 +249,3 @@ type AdSourceResponse struct { 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/config.go b/model/entity/config.go new file mode 100644 index 0000000..4d94bae --- /dev/null +++ b/model/entity/config.go @@ -0,0 +1,157 @@ +package entity + +// BaseConfig 基础配置结构 +type BaseConfig struct { + // 优先级和权重 + Priority int `bson:"priority" json:"priority"` // 优先级 + Weight float64 `bson:"weight" json:"weight"` // 权重 + Order int `bson:"order" json:"order"` // 排序顺序 + + // 标签和分类 + Tags []string `bson:"tags" json:"tags"` // 标签 + Category string `bson:"category" json:"category"` // 分类 + Industry string `bson:"industry" json:"industry"` // 行业 + + // 配置信息 + Config string `bson:"config" json:"config"` // 配置信息(JSON格式) + Extra map[string]interface{} `bson:"extra" json:"extra"` // 扩展字段 + Remark string `bson:"remark" json:"remark"` // 备注 +} + +// BiddingConfig 竞价配置 +type BiddingConfig struct { + // 竞价类型 + BiddingType string `bson:"biddingType" json:"biddingType"` // 竞价类型:cpm、cpc、cpa、rtb + BiddingStrategy string `bson:"biddingStrategy" json:"biddingStrategy"` // 出价策略:manual、auto、target_cpa、target_roas等 + + // 出价范围 + MinBidAmount int64 `bson:"minBidAmount" json:"minBidAmount"` // 最小出价(分) + MaxBidAmount int64 `bson:"maxBidAmount" json:"maxBidAmount"` // 最大出价(分) + DefaultBidAmount int64 `bson:"defaultBidAmount" json:"defaultBidAmount"` // 默认出价(分) + BidIncrement int64 `bson:"bidIncrement" json:"bidIncrement"` // 出价增量(分) + + // 自动优化 + AutoOptimization bool `bson:"autoOptimization" json:"autoOptimization"` // 是否自动优化 + TargetCPA int64 `bson:"targetCPA" json:"targetCPA"` // 目标CPA(分) + TargetROAS float64 `bson:"targetROAS" json:"targetROAS"` // 目标ROAS + ConversionValue int64 `bson:"conversionValue" json:"conversionValue"` // 转化价值 + AttributionWindow string `bson:"attributionWindow" json:"attributionWindow"` // 归因窗口 + OptimizationGoal string `bson:"optimizationGoal" json:"optimizationGoal"` // 优化目标:impressions、clicks、conversions、revenue等 +} + +// BudgetConfig 预算配置 +type BudgetConfig struct { + // 预算设置 + TotalBudget int64 `bson:"totalBudget" json:"totalBudget"` // 总预算(分) + DailyBudget int64 `bson:"dailyBudget" json:"dailyBudget"` // 日预算(分) + HourlyBudget int64 `bson:"hourlyBudget" json:"hourlyBudget"` // 小时预算(分) + + // 投放节奏 + PaceType string `bson:"paceType" json:"paceType"` // 投放节奏:even、accelerated、standard + IsBudgetPacing bool `bson:"isBudgetPacing" json:"isBudgetPacing"` // 是否预算匀速投放 + + // 时间配置 + StartDate int64 `bson:"startDate" json:"startDate"` // 开始投放时间 + EndDate int64 `bson:"endDate" json:"endDate"` // 结束投放时间 + TimeSlots []string `bson:"timeSlots" json:"timeSlots"` // 投放时间段 + DaysOfWeek []int `bson:"daysOfWeek" json:"daysOfWeek"` // 投放日期(0-6,0表示周日) + Timezone string `bson:"timezone" json:"timezone"` // 时区 + IsTimeLimited bool `bson:"isTimeLimited" json:"isTimeLimited"` // 是否限时投放 +} + +// APIConfig API配置 +type APIConfig struct { + // 基础配置 + Endpoint string `bson:"endpoint" json:"endpoint"` // API端点 + Version string `bson:"version" json:"version"` // API版本 + Timeout int `bson:"timeout" json:"timeout"` // 超时时间(毫秒) + RetryCount int `bson:"retryCount" json:"retryCount"` // 重试次数 + + // 认证配置 + AuthType string `bson:"authType" json:"authType"` // 认证类型:api_key、oauth、basic + AuthConfig string `bson:"authConfig" json:"authConfig"` // 认证配置(JSON字符串) + + // 请求配置 + Headers string `bson:"headers" json:"headers"` // 请求头配置(JSON字符串) + + // 限流配置 + RateLimit int64 `bson:"rateLimit" json:"rateLimit"` // 速率限制 + MaxRequestsPerHour int64 `bson:"maxRequestsPerHour" json:"maxRequestsPerHour"` // 每小时最大请求数 +} + +// CreativeConfig 创意配置 +type CreativeConfig struct { + // 轮播设置 + CreativeRotation string `bson:"creativeRotation" json:"creativeRotation"` // 创意轮播方式:optimize、even、random + SelectedCreatives []string `bson:"selectedCreatives" json:"selectedCreatives"` // 选中的创意列表 + ExcludedCreatives []string `bson:"excludedCreatives" json:"excludedCreatives"` // 排除的创意列表 + + // 技术要求 + MaxFileSize int64 `bson:"maxFileSize" json:"maxFileSize"` // 最大文件大小(bytes) + MaxDuration int64 `bson:"maxDuration" json:"maxDuration"` // 最大时长(秒) + SupportedMimeTypes []string `bson:"supportedMimeTypes" json:"supportedMimeTypes"` // 支持的MIME类型 + + // 支持的格式 + SupportedFormats []string `bson:"supportedFormats" json:"supportedFormats"` // 支持的格式 + SupportedSizes []string `bson:"supportedSizes" json:"supportedSizes"` // 支持的尺寸 +} + +// PaymentConfig 支付配置 +type PaymentConfig struct { + // 计费模式 + BillingModel string `bson:"billingModel" json:"billingModel"` // 计费模式:CPC、CPM、CPA等 + CommissionRate float64 `bson:"commissionRate" json:"commissionRate"` // 佣金比例 + MinimumBudget int64 `bson:"minimumBudget" json:"minimumBudget"` // 最低预算(分) + + // 结算配置 + SettlementCycle string `bson:"settlementCycle" json:"settlementCycle"` // 结算周期:daily、weekly、monthly + PaymentTerms string `bson:"paymentTerms" json:"paymentTerms"` // 支付条款 + Currency string `bson:"currency" json:"currency"` // 货币单位 + + // 收入分成 + RevShareRate float64 `bson:"revShareRate" json:"revShareRate"` // 收入分成比例(0-1) + MinPayment int64 `bson:"minPayment" json:"minPayment"` // 最小支付金额(分) + TaxInclusive bool `bson:"taxInclusive" json:"taxInclusive"` // 是否含税 + EarlyPaymentDiscount float64 `bson:"earlyPaymentDiscount" json:"earlyPaymentDiscount"` // 提前付款折扣 +} + +// FrequencyCapConfig 频次控制配置 +type FrequencyCapConfig struct { + // 频次限制 + Impressions int `bson:"impressions" json:"impressions"` // 展示次数 + TimeWindow int `bson:"timeWindow" json:"timeWindow"` // 时间窗口(小时) + PerUser int `bson:"perUser" json:"perUser"` // 每用户频次 + PerHour int `bson:"perHour" json:"perHour"` // 每小时频次 + PerDay int `bson:"perDay" json:"perDay"` // 每日频次 + + // 频次控制规则 + CapType string `bson:"capType" json:"capType"` // 频次类型:lifetime、daily、hourly + CapScope string `bson:"capScope" json:"capScope"` // 频次范围:user、device、ip + ResetRule string `bson:"resetRule" json:"resetRule"` // 重置规则:daily、weekly、monthly +} + +// RestrictionConfig 限制配置 +type RestrictionConfig struct { + // 年龄限制 + AgeRestriction bool `bson:"ageRestriction" json:"ageRestriction"` // 年龄限制 + MinAge int `bson:"minAge" json:"minAge"` // 最小年龄 + MaxAge int `bson:"maxAge" json:"maxAge"` // 最大年龄 + + // 地域限制 + GeoRestrictions []string `bson:"geoRestrictions" json:"geoRestrictions"` // 地域限制 + + // 设备限制 + DeviceRestrictions []string `bson:"deviceRestrictions" json:"deviceRestrictions"` // 设备限制 + + // 分类限制 + CategoryRestrictions []string `bson:"categoryRestrictions" json:"categoryRestrictions"` // 分类限制 + + // 内容限制 + ContentRestrictions []string `bson:"contentRestrictions" json:"contentRestrictions"` // 内容限制 + + // 品牌安全 + BrandSafety bool `bson:"brandSafety" json:"brandSafety"` // 品牌安全 + BlockedCategories []string `bson:"blockedCategories" json:"blockedCategories"` // 阻止的分类 + AllowedCategories []string `bson:"allowedCategories" json:"allowedCategories"` // 允许的分类 + ExcludedKeywords []string `bson:"excludedKeywords" json:"excludedKeywords"` // 排除的关键词 +} diff --git a/model/entity/platform_delivery_rule.go b/model/entity/platform_delivery_rule.go new file mode 100644 index 0000000..d66501e --- /dev/null +++ b/model/entity/platform_delivery_rule.go @@ -0,0 +1,69 @@ +package entity + +import ( + "gitee.com/red-future---jilin-g/common/do" +) + +const PlatformDeliveryRuleCollection = "platform_delivery_rule" + +// PlatformDeliveryRule 平台投放规则实体 +type PlatformDeliveryRule struct { + do.MongoBaseDO `bson:",inline" json:",inline"` + Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等 + + // 关联信息 + AppID string `bson:"appId" json:"appId"` // 应用ID + PlatformID string `bson:"platformId" json:"platformId"` // 平台ID + + // 规则基本信息 + Name string `bson:"name" json:"name"` // 规则名称 + Description string `bson:"description" json:"description"` // 规则描述 + RuleType string `bson:"ruleType" json:"ruleType"` // 规则类型:budget、targeting、bidding、frequency等 + + // 预算配置 + BudgetConfig `bson:",inline" json:",inline"` // 内联预算配置 + + // 出价配置 + BiddingConfig `bson:",inline" json:",inline"` // 内联竞价配置 + + // 定向配置 + TargetingConfig string `bson:"targetingConfig" json:"targetingConfig"` // 定向配置(JSON格式) + IncludeAudience []string `bson:"includeAudience" json:"includeAudience"` // 包含受众 + ExcludeAudience []string `bson:"excludeAudience" json:"excludeAudience"` // 排除受众 + + // 频次控制配置 + FrequencyCapConfig `bson:",inline" json:",inline"` // 内联频次控制配置 + + // 创意配置 + CreativeRotation string `bson:"creativeRotation" json:"creativeRotation"` // 创意轮播方式:optimize、even、random + SelectedCreatives []string `bson:"selectedCreatives" json:"selectedCreatives"` // 选中的创意列表 + ExcludedCreatives []string `bson:"excludedCreatives" json:"excludedCreatives"` // 排除的创意列表 + + // 平台特定配置 + PlatformSpecific string `bson:"platformSpecific" json:"platformSpecific"` // 平台特定配置(JSON格式) + + // 监控和告警 + PerformanceThresholds string `bson:"performanceThresholds" json:"performanceThresholds"` // 性能阈值(JSON格式) + + // 自动优化配置 + IsAutoOptimize bool `bson:"isAutoOptimize" json:"isAutoOptimize"` // 是否自动优化 + LastOptimizeTime int64 `bson:"lastOptimizeTime" json:"lastOptimizeTime"` // 最后优化时间 + AutoOptimizeConfig string `bson:"autoOptimizeConfig" json:"autoOptimizeConfig"` // 自动优化配置(JSON格式) + + // 执行统计 + ExecutionCount int64 `bson:"executionCount" json:"executionCount"` // 执行次数 + SuccessCount int64 `bson:"successCount" json:"successCount"` // 成功次数 + FailureCount int64 `bson:"failureCount" json:"failureCount"` // 失败次数 + LastExecutionTime int64 `bson:"lastExecutionTime" json:"lastExecutionTime"` // 最后执行时间 + NextExecutionTime int64 `bson:"nextExecutionTime" json:"nextExecutionTime"` // 下次执行时间 + + // 执行信息 + CreatedBy string `bson:"createdBy" json:"createdBy"` // 创建人 + LastModifiedBy string `bson:"lastModifiedBy" json:"lastModifiedBy"` // 最后修改人 + ModifiedReason string `bson:"modifiedReason" json:"modifiedReason"` // 修改原因 +} + +// GetCollectionName 获取集合名称 +func (p *PlatformDeliveryRule) GetCollectionName() string { + return PlatformDeliveryRuleCollection +} diff --git a/model/entity/stat_report.go b/model/entity/stat_report.go deleted file mode 100644 index e73d6e7..0000000 --- a/model/entity/stat_report.go +++ /dev/null @@ -1,24 +0,0 @@ -package entity - -import ( - "time" - - "gitee.com/red-future---jilin-g/common/do" -) - -const StatReportCollection = "stat_report" - -// StatReport 统计报表实体 -type StatReport struct { - do.MongoBaseDO `bson:",inline" json:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted - - // 报表基本信息 - AppID string `json:"appId"` // 应用ID (空字符串表示所有应用) - ReportType string `json:"reportType"` // 报表类型:daily, weekly, monthly, quarterly, yearly - ReportDate time.Time `json:"reportDate"` // 报表日期 - GeneratedAt time.Time `json:"generatedAt"` // 生成时间 - ReportData string `json:"reportData"` // 报表数据(JSON格式) - - // 状态信息 - Status string `json:"status"` // 状态:generated, processing, completed -} diff --git a/model/entity/strategy.go b/model/entity/strategy.go index 51459e1..45532e2 100644 --- a/model/entity/strategy.go +++ b/model/entity/strategy.go @@ -8,7 +8,8 @@ const StrategyCollection = "strategy" // Strategy 匹配策略表 type Strategy struct { - do.MongoBaseDO `bson:",inline" json:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + do.MongoBaseDO `bson:",inline" json:",inline"` + Status string `bson:"status" json:"status"` // 状态:active、inactive、maintenance等 // 策略基本信息 Name string `bson:"name" json:"name"` // 策略名称 @@ -18,6 +19,10 @@ type Strategy struct { SourceWeights string `bson:"sourceWeights" json:"sourceWeights"` // 广告源权重 (JSON格式) MaxAdsPerReq int `bson:"maxAdsPerReq" json:"maxAdsPerReq"` // 每次请求最大广告数 MaxReqPerHour int `bson:"maxReqPerHour" json:"maxReqPerHour"` // 每小时最大请求次数 - Priority int `bson:"priority" json:"priority"` // 优先级 - Status string `bson:"status" json:"status"` // 状态: active, inactive + Priority int `bson:"priority" json:"priority"` // 优先级(用于策略排序) +} + +// GetCollectionName 获取集合名称 +func (s *Strategy) GetCollectionName() string { + return StrategyCollection } diff --git a/model/entity/targeting.go b/model/entity/targeting.go new file mode 100644 index 0000000..7313664 --- /dev/null +++ b/model/entity/targeting.go @@ -0,0 +1,68 @@ +package entity + +// UnifiedTargeting 统一的定向条件 +type UnifiedTargeting 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"` // 邮政编码列表 + + // 人口统计定向 + AgeRange *UnifiedAgeRange `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"` // 生活方式 + + // 行为定向 + 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"` // 用户分群 + + // 上下文定向 + 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"` // 内容评级 + + // 设备定向 + 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"` // 连接类型 + + // 时间定向 + TimeSlots []UnifiedTimeSlot `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"` // 排除节假日 + + // 扩展定向条件 + CustomTargeting map[string]interface{} `bson:"customTargeting" json:"customTargeting"` // 自定义定向 +} + +// UnifiedAgeRange 统一的年龄范围 +type UnifiedAgeRange struct { + Min int `bson:"min" json:"min"` // 最小年龄 + Max int `bson:"max" json:"max"` // 最大年龄 +} + +// UnifiedTimeSlot 统一的时间段 +type UnifiedTimeSlot struct { + DayOfWeek int `bson:"dayOfWeek" json:"dayOfWeek"` // 星期几:0-6,0表示星期日 + StartTime string `bson:"startTime" json:"startTime"` // 开始时间,格式:HH:mm + EndTime string `bson:"endTime" json:"endTime"` // 结束时间,格式:HH:mm + Timezone string `bson:"timezone" json:"timezone"` // 时区 +} diff --git a/service/ad_position_service.go b/service/ad_position_service.go index 09ee099..d64e040 100644 --- a/service/ad_position_service.go +++ b/service/ad_position_service.go @@ -28,13 +28,6 @@ func (s *adPosition) Add(ctx context.Context, req *dto.AddAdPositionReq) (res *d adPosition.UpdatedAt = now adPosition.IsDeleted = false - // 初始化统计字段 - adPosition.DailyImpressions = 0 - adPosition.DailyClicks = 0 - adPosition.DailyRevenue = 0 - adPosition.CTR = 0 - // eCPM字段是未导出的,无法直接设置 - if err = dao.AdPosition.Insert(ctx, adPosition); err != nil { return } @@ -121,62 +114,5 @@ func (s *adPosition) MatchAd(ctx context.Context, positionCode string, userInfo // UpdateAdPositionStatistics 更新广告位统计 func (s *adPosition) UpdateAdPositionStatistics(ctx context.Context, id string, impressions, clicks, revenue int64) (err error) { - // 获取广告位信息 - adPosition, err := dao.AdPosition.GetOne(ctx, id) - if err != nil { - return - } - - // 计算统计数据 - totalImpressions := adPosition.DailyImpressions + impressions - totalClicks := adPosition.DailyClicks + clicks - totalRevenue := adPosition.DailyRevenue + revenue - - // 计算比率 - ctr := 0.0 - if totalImpressions > 0 { - ctr = float64(totalClicks) / float64(totalImpressions) - } - - ecpm := int64(0) - if totalImpressions > 0 { - ecpm = totalRevenue * 1000 / totalImpressions - } - - // 构建更新数据 - stats := map[string]interface{}{ - "dailyImpressions": totalImpressions, - "dailyClicks": totalClicks, - "dailyRevenue": totalRevenue, - "ctr": ctr, - "ecpm": ecpm, - "updatedAt": time.Now(), - } - - // 更新广告位统计 - err = dao.AdPosition.UpdateStatistics(ctx, id, stats) - if err != nil { - return - } - - // 插入统计记录 - today := time.Now() - todayStart := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, today.Location()).Unix() - - statistics := &entity.AdStatistics{ - StatType: "adPosition", - StatDimension: "day", - TargetId: id, - TargetName: adPosition.Name, - StatDate: todayStart, - Impressions: impressions, - Clicks: clicks, - Cost: 0, // 广告位不记录消耗,只记录收入 - Revenue: revenue, - CTR: ctr, - // eCPM字段是未导出的,无法直接设置 - } - - err = dao.AdStatistics.Upsert(ctx, statistics) return } diff --git a/service/ad_source_service.go b/service/ad_source_service.go index 3eb36b9..4a2b986 100644 --- a/service/ad_source_service.go +++ b/service/ad_source_service.go @@ -36,15 +36,18 @@ func (s *adSourceService) CreateAdSource(ctx context.Context, req *dto.CreateAdS } adSource := &entity.AdSource{ - Name: req.Name, - Code: req.Code, - Provider: req.Provider, - Type: req.Type, - APIEndpoint: req.APIEndpoint, - Status: "active", // 默认状态 - Priority: 1, // 默认优先级 + Name: req.Name, + Code: req.Code, + Provider: req.Provider, + Type: req.Type, + APIConfig: entity.APIConfig{ + Endpoint: req.APIEndpoint, + }, } + // 设置状态 + adSource.Status = "active" // 默认状态 + return dao.AdSource.Create(ctx, adSource) } @@ -77,7 +80,7 @@ func (s *adSourceService) UpdateAdSource(ctx context.Context, id string, req *dt updateData.Name = req.Name } if req.APIEndpoint != "" { - updateData.APIEndpoint = req.APIEndpoint + updateData.APIConfig.Endpoint = req.APIEndpoint } return dao.AdSource.UpdateFields(ctx, id, updateData) diff --git a/service/ad_statistics_service.go b/service/ad_statistics_service.go deleted file mode 100644 index 24a1d1b..0000000 --- a/service/ad_statistics_service.go +++ /dev/null @@ -1,367 +0,0 @@ -package service - -import ( - "context" - "fmt" - "sort" - "time" - - "cid/dao" - "cid/model/dto" - "cid/model/entity" -) - -var AdStatistics = new(adStatistics) - -type adStatistics struct{} - -// List 获取统计数据列表 -func (s *adStatistics) List(ctx context.Context, req *dto.GetAdStatisticsReq) (res *dto.GetAdStatisticsRes, err error) { - list, total, err := dao.AdStatistics.List(ctx, req) - if err != nil { - return nil, err - } - - res = &dto.GetAdStatisticsRes{ - Statistics: list, - Total: int(total), - } - return -} - -// GetStatistics 获取统计数据 -func (s *adStatistics) GetStatistics(ctx context.Context, req *dto.GetAdStatisticsReq) (res *dto.GetAdStatisticsRes, err error) { - list, total, err := dao.AdStatistics.GetStatistics(ctx, req) - if err != nil { - return nil, err - } - - res = &dto.GetAdStatisticsRes{ - Statistics: list, - Total: int(total), - } - return -} - -// GetDashboard 获取仪表盘数据 -func (s *adStatistics) GetDashboard(ctx context.Context, req *dto.GetDashboardReq) (res *dto.GetDashboardRes, err error) { - // 构建统计查询请求 - statReq := &dto.GetAdStatisticsReq{ - StartDate: req.StartDate, - EndDate: req.EndDate, - StatDimension: req.Dimension, - } - - // 获取所有统计数据 - stats, _, err := dao.AdStatistics.GetStatistics(ctx, statReq) - if err != nil { - return nil, err - } - - // 计算总览数据 - overview := s.calculateOverview(stats) - - // 计算趋势数据 - trends := s.calculateTrends(stats, req.Dimension) - - // 计算排行数据 - topAdvertisers := s.calculateTopAdvertisers(stats) - topAds := s.calculateTopAds(stats) - topPositions := s.calculateTopPositions(stats) - - res = &dto.GetDashboardRes{ - Overview: overview, - Trends: trends, - TopAdvertisers: topAdvertisers, - TopAds: topAds, - TopPositions: topPositions, - } - return -} - -// GenerateDailyStatistics 生成每日统计数据 -func (s *adStatistics) GenerateDailyStatistics(ctx context.Context, date int64) (err error) { - // 转换日期 - t := time.Unix(date, 0) - startOfDay := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) - endOfDay := startOfDay.Add(24 * time.Hour) - - // 生成广告主统计数据 - err = s.generateAdvertiserStatistics(ctx, startOfDay.Unix(), endOfDay.Unix()) - if err != nil { - return fmt.Errorf("生成广告主统计数据失败: %v", err) - } - - // 生成广告统计数据 - err = s.generateAdvertisementStatistics(ctx, startOfDay.Unix(), endOfDay.Unix()) - if err != nil { - return fmt.Errorf("生成广告统计数据失败: %v", err) - } - - // 生成广告位统计数据 - err = s.generateAdPositionStatistics(ctx, startOfDay.Unix(), endOfDay.Unix()) - if err != nil { - return fmt.Errorf("生成广告位统计数据失败: %v", err) - } - - return -} - -// generateAdvertiserStatistics 生成广告主统计数据 -func (s *adStatistics) generateAdvertiserStatistics(ctx context.Context, startDate int64, _ int64) (err error) { - // 这里简化处理,实际项目中应该从日志表或实时数据中聚合 - advertiserStats := &entity.AdStatistics{ - StatType: "advertiser", - StatDimension: "day", - TargetId: "example_advertiser_id", - TargetName: "示例广告主", - StatDate: startDate, - Impressions: 10000, - Clicks: 500, - Conversions: 50, - Cost: 50000, // 500元,单位分 - CTR: 0.05, // 5% - CVR: 0.1, // 10% - CPM: 5000, // 50元/千次展示 - CPC: 100, // 1元/点击 - } - - err = dao.AdStatistics.Upsert(ctx, advertiserStats) - return -} - -// generateAdvertisementStatistics 生成广告统计数据 -func (s *adStatistics) generateAdvertisementStatistics(ctx context.Context, startDate int64, _ int64) (err error) { - // 这里简化处理,实际项目中应该从日志表或实时数据中聚合 - adStats := &entity.AdStatistics{ - StatType: "advertisement", - StatDimension: "day", - TargetId: "example_ad_id", - TargetName: "示例广告", - StatDate: startDate, - Impressions: 1000, - Clicks: 50, - Conversions: 5, - Cost: 5000, // 50元,单位分 - CTR: 0.05, // 5% - CVR: 0.1, // 10% - CPM: 5000, // 50元/千次展示 - CPC: 100, // 1元/点击 - } - - err = dao.AdStatistics.Upsert(ctx, adStats) - return -} - -// generateAdPositionStatistics 生成广告位统计数据 -func (s *adStatistics) generateAdPositionStatistics(ctx context.Context, startDate int64, _ int64) (err error) { - // 这里简化处理,实际项目中应该从日志表或实时数据中聚合 - positionStats := &entity.AdStatistics{ - StatType: "adPosition", - StatDimension: "day", - TargetId: "example_position_id", - TargetName: "示例广告位", - StatDate: startDate, - Impressions: 5000, - Clicks: 250, - Revenue: 6000, // 60元,单位分 - CTR: 0.05, // 5% - } - - err = dao.AdStatistics.Upsert(ctx, positionStats) - return -} - -// calculateOverview 计算总览数据 -func (s *adStatistics) calculateOverview(stats []*entity.AdStatistics) dto.OverviewData { - var totalImpressions, totalClicks, totalCost, totalRevenue int64 - var totalCTR, totalCVR float64 - var count int - - // 统计不同类型的数据 - advertiserCount := make(map[string]bool) - adCount := make(map[string]bool) - positionCount := make(map[string]bool) - - for _, stat := range stats { - totalImpressions += stat.Impressions - totalClicks += stat.Clicks - totalCost += stat.Cost - totalRevenue += stat.Revenue - totalCTR += stat.CTR - totalCVR += stat.CVR - count++ - - // 统计不同实体的数量 - if stat.StatType == "advertiser" { - advertiserCount[stat.TargetId] = true - } else if stat.StatType == "advertisement" { - adCount[stat.TargetId] = true - } else if stat.StatType == "adPosition" { - positionCount[stat.TargetId] = true - } - } - - // 计算平均值 - averageCTR := 0.0 - averageCVR := 0.0 - if count > 0 { - averageCTR = totalCTR / float64(count) - averageCVR = totalCVR / float64(count) - } - - return dto.OverviewData{ - TotalAdvertisers: int64(len(advertiserCount)), - TotalAds: int64(len(adCount)), - TotalPositions: int64(len(positionCount)), - TotalImpressions: totalImpressions, - TotalClicks: totalClicks, - TotalCost: totalCost, - TotalRevenue: totalRevenue, - AverageCTR: averageCTR, - AverageCVR: averageCVR, - } -} - -// calculateTrends 计算趋势数据 -func (s *adStatistics) calculateTrends(stats []*entity.AdStatistics, dimension string) []dto.TrendData { - trends := make([]dto.TrendData, 0) - - // 按日期分组统计数据 - dateMap := make(map[int64]*dto.TrendData) - - for _, stat := range stats { - if _, exists := dateMap[stat.StatDate]; !exists { - dateMap[stat.StatDate] = &dto.TrendData{ - Date: stat.StatDate, - Impressions: 0, - Clicks: 0, - Cost: 0, - Revenue: 0, - } - } - - trend := dateMap[stat.StatDate] - trend.Impressions += stat.Impressions - trend.Clicks += stat.Clicks - trend.Cost += stat.Cost - trend.Revenue += stat.Revenue - } - - // 转换为切片并排序 - for _, trend := range dateMap { - trends = append(trends, *trend) - } - - // 按日期排序 - sort.Slice(trends, func(i, j int) bool { - return trends[i].Date < trends[j].Date - }) - - return trends -} - -// calculateTopAdvertisers 计算广告主排行 -func (s *adStatistics) calculateTopAdvertisers(stats []*entity.AdStatistics) []dto.RankData { - advertiserMap := make(map[string]*dto.RankData) - - for _, stat := range stats { - if stat.StatType == "advertiser" { - if _, exists := advertiserMap[stat.TargetId]; !exists { - advertiserMap[stat.TargetId] = &dto.RankData{ - Id: stat.TargetId, - Name: stat.TargetName, - Impressions: 0, - Clicks: 0, - Cost: 0, - Revenue: 0, - } - } - - rank := advertiserMap[stat.TargetId] - rank.Impressions += stat.Impressions - rank.Clicks += stat.Clicks - rank.Cost += stat.Cost - rank.Revenue += stat.Revenue - } - } - - return s.sortRankData(advertiserMap) -} - -// calculateTopAds 计算广告排行 -func (s *adStatistics) calculateTopAds(stats []*entity.AdStatistics) []dto.RankData { - adMap := make(map[string]*dto.RankData) - - for _, stat := range stats { - if stat.StatType == "advertisement" { - if _, exists := adMap[stat.TargetId]; !exists { - adMap[stat.TargetId] = &dto.RankData{ - Id: stat.TargetId, - Name: stat.TargetName, - Impressions: 0, - Clicks: 0, - Cost: 0, - Revenue: 0, - } - } - - rank := adMap[stat.TargetId] - rank.Impressions += stat.Impressions - rank.Clicks += stat.Clicks - rank.Cost += stat.Cost - rank.Revenue += stat.Revenue - } - } - - return s.sortRankData(adMap) -} - -// calculateTopPositions 计算广告位排行 -func (s *adStatistics) calculateTopPositions(stats []*entity.AdStatistics) []dto.RankData { - positionMap := make(map[string]*dto.RankData) - - for _, stat := range stats { - if stat.StatType == "adPosition" { - if _, exists := positionMap[stat.TargetId]; !exists { - positionMap[stat.TargetId] = &dto.RankData{ - Id: stat.TargetId, - Name: stat.TargetName, - Impressions: 0, - Clicks: 0, - Cost: 0, - Revenue: 0, - } - } - - rank := positionMap[stat.TargetId] - rank.Impressions += stat.Impressions - rank.Clicks += stat.Clicks - rank.Cost += stat.Cost - rank.Revenue += stat.Revenue - } - } - - return s.sortRankData(positionMap) -} - -// sortRankData 对排行数据进行排序 -func (s *adStatistics) sortRankData(dataMap map[string]*dto.RankData) []dto.RankData { - rankList := make([]dto.RankData, 0, len(dataMap)) - - for _, rank := range dataMap { - rankList = append(rankList, *rank) - } - - // 按收入降序排序 - sort.Slice(rankList, func(i, j int) bool { - return rankList[i].Revenue > rankList[j].Revenue - }) - - // 只返回前10名 - if len(rankList) > 10 { - rankList = rankList[:10] - } - - return rankList -} diff --git a/service/advertisement_service.go b/service/advertisement_service.go index 3c2f8af..6708188 100644 --- a/service/advertisement_service.go +++ b/service/advertisement_service.go @@ -30,12 +30,6 @@ func (s *advertisement) Add(ctx context.Context, req *dto.AddAdvertisementReq) ( // 设置初始状态 advertisement.Status = "待审核" - // 初始化统计字段 - advertisement.Impressions = 0 - advertisement.Clicks = 0 - advertisement.Conversions = 0 - advertisement.Cost = 0 - if err = dao.Advertisement.Insert(ctx, advertisement); err != nil { return } @@ -89,75 +83,5 @@ func (s *advertisement) List(ctx context.Context, req *dto.ListAdvertisementReq) // UpdateAdStatistics 更新广告统计 func (s *advertisement) UpdateAdStatistics(ctx context.Context, id string, impressions, clicks, conversions int64, cost int64) (err error) { - // 获取广告信息 - ad, err := dao.Advertisement.GetOne(ctx, id) - if err != nil { - return - } - - // 计算统计数据 - totalImpressions := ad.Impressions + impressions - totalClicks := ad.Clicks + clicks - totalConversions := ad.Conversions + conversions - totalCost := ad.Cost + cost - - // 计算比率 - ctr := 0.0 - if totalImpressions > 0 { - ctr = float64(totalClicks) / float64(totalImpressions) - } - - cvr := 0.0 - if totalClicks > 0 { - cvr = float64(totalConversions) / float64(totalClicks) - } - - cpm := int64(0) - if totalImpressions > 0 { - cpm = totalCost * 1000 / totalImpressions - } - - cpc := int64(0) - if totalClicks > 0 { - cpc = totalCost / totalClicks - } - - // 构建更新数据 - stats := map[string]interface{}{ - "impressions": totalImpressions, - "clicks": totalClicks, - "conversions": totalConversions, - "cost": totalCost, - "updatedAt": time.Now(), - } - - // 更新广告统计 - err = dao.Advertisement.UpdateStatistics(ctx, id, stats) - if err != nil { - return - } - - // 插入统计记录 - today := time.Now() - todayStart := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, today.Location()).Unix() - - statistics := &entity.AdStatistics{ - StatType: "advertisement", - StatDimension: "day", - TargetId: id, - TargetName: ad.Title, - StatDate: todayStart, - Impressions: impressions, - Clicks: clicks, - Conversions: conversions, - Cost: cost, - CTR: ctr, - CVR: cvr, - CPM: cpm, - CPC: cpc, - // CreatedAt、UpdatedAt、IsDeleted字段是嵌入字段,无需单独设置 - } - - err = dao.AdStatistics.Upsert(ctx, statistics) return } diff --git a/service/application_service.go b/service/application_service.go index bb63f73..7058091 100644 --- a/service/application_service.go +++ b/service/application_service.go @@ -45,12 +45,14 @@ func (s *applicationService) CreateApplication(ctx context.Context, req *dto.Cre Categories: req.Categories, Tags: req.Tags, AdTypes: req.AdTypes, - Status: "active", AppKey: appKey, AppSecret: appSecret, CallbackURL: req.CallbackURL, } + // 设置状态 + application.Status = "active" + return dao.Application.Create(ctx, application) } diff --git a/service/stat_report_scheduler.go b/service/stat_report_scheduler.go deleted file mode 100644 index 012ef82..0000000 --- a/service/stat_report_scheduler.go +++ /dev/null @@ -1,631 +0,0 @@ -package service - -import ( - "context" - "fmt" - "strconv" - "sync" - "time" - - "cid/dao" - "cid/model/entity" - - "github.com/gogf/gf/v2/frame/g" - "github.com/gogf/gf/v2/util/gconv" -) - -// StatReportScheduler 统计报表定时任务调度器 -type StatReportScheduler struct{} - -var StatReportSchedulerInstance = &StatReportScheduler{} -var schedulerLock sync.Mutex -var isSchedulerRunning bool - -// StartScheduler 启动定时任务调度器(分布式安全) -func (s *StatReportScheduler) StartScheduler(ctx context.Context) error { - schedulerLock.Lock() - defer schedulerLock.Unlock() - - // 检查是否已经有调度器在运行(分布式部署时避免重复执行) - if isSchedulerRunning { - g.Log().Info(ctx, "统计报表定时任务调度器已在运行") - return nil - } - - // 尝试获取分布式锁 - if !s.acquireDistributedLock(ctx) { - g.Log().Info(ctx, "其他节点正在运行统计报表定时任务,当前节点跳过") - return nil - } - - isSchedulerRunning = true - - // 启动锁续期任务 - go s.startLockRenewal(ctx) - - // 启动日报表生成任务(每天凌晨3点执行) - go s.startDailyReportScheduler(ctx) - - // 启动月报表生成任务(每月1日凌晨4点执行) - go s.startMonthlyReportScheduler(ctx) - - // 启动季度报表生成任务(每季度第一天凌晨5点执行) - go s.startQuarterlyReportScheduler(ctx) - - // 启动年报表生成任务(每年1月1日凌晨6点执行) - go s.startYearlyReportScheduler(ctx) - - g.Log().Info(ctx, "统计报表定时任务调度器已启动") - return nil -} - -// acquireDistributedLock 获取分布式锁(基于Redis) -func (s *StatReportScheduler) acquireDistributedLock(ctx context.Context) bool { - // 使用Redis实现分布式锁 - // 锁的有效期为1小时,避免死锁 - lockKey := "stat_report_scheduler_lock" - lockValue := fmt.Sprintf("%d", time.Now().Unix()) - - // 尝试获取锁 - result, err := g.Redis().Do(ctx, "SET", lockKey, lockValue, "NX", "EX", 3600) - if err != nil { - g.Log().Errorf(ctx, "获取分布式锁失败: %v", err) - return false - } - - return result != nil -} - -// renewDistributedLock 续期分布式锁 -func (s *StatReportScheduler) renewDistributedLock(ctx context.Context) bool { - lockKey := "stat_report_scheduler_lock" - - // 检查锁是否存在 - exists, err := g.Redis().Do(ctx, "EXISTS", lockKey) - if err != nil || exists == nil { - return false - } - - // 检查锁是否存在(EXISTS返回1表示存在,0表示不存在) - existsInt := exists.Int64() - if existsInt == 0 { - return false - } - - // 续期锁,延长1小时 - _, err = g.Redis().Do(ctx, "EXPIRE", lockKey, 3600) - if err != nil { - g.Log().Errorf(ctx, "续期分布式锁失败: %v", err) - return false - } - - return true -} - -// startLockRenewal 启动锁续期任务 -func (s *StatReportScheduler) startLockRenewal(ctx context.Context) { - ticker := time.NewTicker(30 * time.Minute) // 每30分钟续期一次 - defer ticker.Stop() - - for { - select { - case <-ticker.C: - if !s.renewDistributedLock(ctx) { - g.Log().Error(ctx, "锁续期失败,调度器将停止运行") - // 锁丢失,停止调度器 - schedulerLock.Lock() - isSchedulerRunning = false - schedulerLock.Unlock() - return - } - case <-ctx.Done(): - return - } - } -} - -// acquireTaskLock 获取任务级分布式锁 -func (s *StatReportScheduler) acquireTaskLock(ctx context.Context, lockKey string) bool { - lockValue := fmt.Sprintf("%d", time.Now().Unix()) - - // 尝试获取任务锁,有效期为2小时 - result, err := g.Redis().Do(ctx, "SET", lockKey, lockValue, "NX", "EX", 7200) - if err != nil { - g.Log().Errorf(ctx, "获取任务锁失败: %v", err) - return false - } - - return result != nil -} - -// releaseTaskLock 释放任务级分布式锁 -func (s *StatReportScheduler) releaseTaskLock(ctx context.Context, lockKey string) { - _, err := g.Redis().Do(ctx, "DEL", lockKey) - if err != nil { - g.Log().Errorf(ctx, "释放任务锁失败: %v", err) - } -} - -// startDailyReportScheduler 日报表定时任务 -func (s *StatReportScheduler) startDailyReportScheduler(ctx context.Context) { - // 计算到凌晨3点的时间 - now := time.Now() - next := time.Date(now.Year(), now.Month(), now.Day()+1, 3, 0, 0, 0, time.Local) - duration := next.Sub(now) - - // 等待到凌晨3点 - time.Sleep(duration) - - ticker := time.NewTicker(24 * time.Hour) - defer ticker.Stop() - - // 立即执行一次昨天的日报表生成 - go s.generateYesterdayDailyReport(ctx) - - for { - select { - case <-ticker.C: - // 生成昨天的日报表 - s.generateYesterdayDailyReport(ctx) - case <-ctx.Done(): - return - } - } -} - -// startMonthlyReportScheduler 月报表定时任务 -func (s *StatReportScheduler) startMonthlyReportScheduler(ctx context.Context) { - // 计算到下个月1日凌晨4点的时间 - now := time.Now() - next := time.Date(now.Year(), now.Month()+1, 1, 4, 0, 0, 0, time.Local) - duration := next.Sub(now) - - // 等待到下个月1日凌晨4点 - time.Sleep(duration) - - ticker := time.NewTicker(24 * time.Hour) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - // 检查是否是每月1日,如果是则生成上个月的月报表 - if time.Now().Day() == 1 { - go s.generateLastMonthReport(ctx) - } - case <-ctx.Done(): - return - } - } -} - -// startQuarterlyReportScheduler 季度报表定时任务 -func (s *StatReportScheduler) startQuarterlyReportScheduler(ctx context.Context) { - // 计算到下个季度第一天凌晨5点的时间 - now := time.Now() - nextQuarter := s.getNextQuarterFirstDay(now) - next := time.Date(nextQuarter.Year(), nextQuarter.Month(), nextQuarter.Day(), 5, 0, 0, 0, time.Local) - duration := next.Sub(now) - - // 等待到下个季度第一天凌晨5点 - time.Sleep(duration) - - ticker := time.NewTicker(24 * time.Hour) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - // 检查是否是季度第一天,如果是则生成上个季度的季度报表 - if s.isQuarterFirstDay() { - go s.generateLastQuarterReport(ctx) - } - case <-ctx.Done(): - return - } - } -} - -// startYearlyReportScheduler 年报表定时任务 -func (s *StatReportScheduler) startYearlyReportScheduler(ctx context.Context) { - // 计算到明年1月1日凌晨6点的时间 - now := time.Now() - next := time.Date(now.Year()+1, time.January, 1, 6, 0, 0, 0, time.Local) - duration := next.Sub(now) - - // 等待到明年1月1日凌晨6点 - time.Sleep(duration) - - ticker := time.NewTicker(24 * time.Hour) - defer ticker.Stop() - - for { - select { - case <-ticker.C: - // 检查是否是1月1日,如果是则生成去年的年报表 - if time.Now().Month() == time.January && time.Now().Day() == 1 { - go s.generateLastYearReport(ctx) - } - case <-ctx.Done(): - return - } - } -} - -// generateYesterdayDailyReport 生成昨天的日报表 -func (s *StatReportScheduler) generateYesterdayDailyReport(ctx context.Context) error { - yesterday := time.Now().AddDate(0, 0, -1) - return s.generateDailyReportForDate(ctx, yesterday) -} - -// generateLastMonthReport 生成上个月的月报表 -func (s *StatReportScheduler) generateLastMonthReport(ctx context.Context) error { - lastMonth := time.Now().AddDate(0, -1, 0) - return s.generateMonthlyReportFromDaily(ctx, lastMonth) -} - -// generateLastQuarterReport 生成上个季度的季度报表 -func (s *StatReportScheduler) generateLastQuarterReport(ctx context.Context) error { - lastQuarter := time.Now().AddDate(0, -3, 0) - return s.generateQuarterlyReportFromMonthly(ctx, lastQuarter) -} - -// generateLastYearReport 生成去年的年报表 -func (s *StatReportScheduler) generateLastYearReport(ctx context.Context) error { - lastYear := time.Now().AddDate(-1, 0, 0) - return s.generateYearlyReportFromQuarterly(ctx, lastYear) -} - -// generateDailyReportForDate 为指定日期生成日报表 -func (s *StatReportScheduler) generateDailyReportForDate(ctx context.Context, date time.Time) error { - // 获取日报表任务分布式锁 - dailyLockKey := fmt.Sprintf("daily_report_lock_%s", date.Format("2006-01-02")) - if !s.acquireTaskLock(ctx, dailyLockKey) { - g.Log().Info(ctx, "其他节点正在生成日报表,日期: %s", date.Format("2006-01-02")) - return nil - } - defer s.releaseTaskLock(ctx, dailyLockKey) - - // 获取所有租户 - tenants, err := s.getAllTenants(ctx) - if err != nil { - return err - } - - for _, tenantID := range tenants { - // 检查是否已生成该日期的报表 - if s.isReportGenerated(ctx, tenantID, "daily", date.Format("2006-01-02")) { - continue - } - - // 生成日报表数据(从流水数据统计) - reportData, err := s.generateReportDataFromRawData(ctx, tenantID, 0, "daily", date) - if err != nil { - g.Log().Errorf(ctx, "生成租户%d日报表失败: %v", tenantID, err) - continue - } - - // 保存日报表 - report := &entity.StatReport{ - AppID: "0", // 0表示所有应用 - ReportType: "daily", - ReportDate: date, - ReportData: gconv.String(reportData), - GeneratedAt: time.Now(), - Status: "completed", - } - - err = dao.StatReport.Create(ctx, report) - if err != nil { - g.Log().Errorf(ctx, "保存租户%d日报表失败: %v", tenantID, err) - continue - } - - g.Log().Infof(ctx, "成功生成租户%d的日报表,日期: %s", tenantID, date.Format("2006-01-02")) - } - - return nil -} - -// generateMonthlyReportFromDaily 从日报表生成月报表 -func (s *StatReportScheduler) generateMonthlyReportFromDaily(ctx context.Context, date time.Time) error { - // 获取月报表任务分布式锁 - monthlyLockKey := fmt.Sprintf("monthly_report_lock_%s", date.Format("2006-01")) - if !s.acquireTaskLock(ctx, monthlyLockKey) { - g.Log().Info(ctx, "其他节点正在生成月报表,日期: %s", date.Format("2006-01")) - return nil - } - defer s.releaseTaskLock(ctx, monthlyLockKey) - - tenants, err := s.getAllTenants(ctx) - if err != nil { - return err - } - - for _, tenantID := range tenants { - if s.isReportGenerated(ctx, tenantID, "monthly", date.Format("2006-01")) { - continue - } - - // 获取该月的所有日报表数据 - dailyReports, err := s.getDailyReportsForMonth(ctx, tenantID, date) - if err != nil { - g.Log().Errorf(ctx, "获取租户%d月报数据失败: %v", tenantID, err) - continue - } - - // 聚合日报表数据生成月报表 - reportData := s.aggregateDailyReportsToMonthly(dailyReports) - - report := &entity.StatReport{ - AppID: "0", - ReportType: "monthly", - ReportDate: date, - ReportData: gconv.String(reportData), - GeneratedAt: time.Now(), - Status: "completed", - } - - err = dao.StatReport.Create(ctx, report) - if err != nil { - g.Log().Errorf(ctx, "保存租户%d月报表失败: %v", tenantID, err) - continue - } - - g.Log().Infof(ctx, "成功生成租户%d的月报表,日期: %s", tenantID, date.Format("2006-01")) - } - - return nil -} - -// generateQuarterlyReportFromMonthly 从月报表生成季度报表 -func (s *StatReportScheduler) generateQuarterlyReportFromMonthly(ctx context.Context, date time.Time) error { - // 获取季度报表任务分布式锁 - quarter := fmt.Sprintf("Q%d", (date.Month()-1)/3+1) - quarterlyLockKey := fmt.Sprintf("quarterly_report_lock_%d-%s", date.Year(), quarter) - if !s.acquireTaskLock(ctx, quarterlyLockKey) { - g.Log().Info(ctx, "其他节点正在生成季度报表,日期: %d-%s", date.Year(), quarter) - return nil - } - defer s.releaseTaskLock(ctx, quarterlyLockKey) - - tenants, err := s.getAllTenants(ctx) - if err != nil { - return err - } - - for _, tenantID := range tenants { - reportDate := fmt.Sprintf("%d-%s", date.Year(), quarter) - - if s.isReportGenerated(ctx, tenantID, "quarterly", reportDate) { - continue - } - - // 获取该季度的所有月报表数据 - monthlyReports, err := s.getMonthlyReportsForQuarter(ctx, tenantID, date) - if err != nil { - g.Log().Errorf(ctx, "获取租户%d季报数据失败: %v", tenantID, err) - continue - } - - // 聚合月报表数据生成季度报表 - reportData := s.aggregateMonthlyReportsToQuarterly(monthlyReports) - - report := &entity.StatReport{ - AppID: "0", - ReportType: "quarterly", - ReportDate: date, - ReportData: gconv.String(reportData), - GeneratedAt: time.Now(), - Status: "completed", - } - - err = dao.StatReport.Create(ctx, report) - if err != nil { - g.Log().Errorf(ctx, "保存租户%d季度报表失败: %v", tenantID, err) - continue - } - - g.Log().Infof(ctx, "成功生成租户%d的季度报表,日期: %s", tenantID, reportDate) - } - - return nil -} - -// generateYearlyReportFromQuarterly 从季度报表生成年报表 -func (s *StatReportScheduler) generateYearlyReportFromQuarterly(ctx context.Context, date time.Time) error { - // 获取年报表任务分布式锁 - yearlyLockKey := fmt.Sprintf("yearly_report_lock_%d", date.Year()) - if !s.acquireTaskLock(ctx, yearlyLockKey) { - g.Log().Info(ctx, "其他节点正在生成年报表,日期: %d", date.Year()) - return nil - } - defer s.releaseTaskLock(ctx, yearlyLockKey) - - tenants, err := s.getAllTenants(ctx) - if err != nil { - return err - } - - for _, tenantID := range tenants { - reportDate := fmt.Sprintf("%d", date.Year()) - - if s.isReportGenerated(ctx, tenantID, "yearly", reportDate) { - continue - } - - // 获取该年度的所有季度报表数据 - quarterlyReports, err := s.getQuarterlyReportsForYear(ctx, tenantID, date) - if err != nil { - g.Log().Errorf(ctx, "获取租户%d年报数据失败: %v", tenantID, err) - continue - } - - // 聚合季度报表数据生成年报表 - reportData := s.aggregateQuarterlyReportsToYearly(quarterlyReports) - - report := &entity.StatReport{ - AppID: "0", - ReportType: "yearly", - ReportDate: date, - ReportData: gconv.String(reportData), - GeneratedAt: time.Now(), - Status: "completed", - } - - err = dao.StatReport.Create(ctx, report) - if err != nil { - g.Log().Errorf(ctx, "保存租户%d年报表失败: %v", tenantID, err) - continue - } - - g.Log().Infof(ctx, "成功生成租户%d的年报表,日期: %s", tenantID, reportDate) - } - - return nil -} - -// generateReportDataFromRawData 从原始流水数据生成报表数据 -func (s *StatReportScheduler) generateReportDataFromRawData(ctx context.Context, tenantID, appID int64, reportType string, reportDate time.Time) (map[string]interface{}, error) { - // 使用现有的报表生成逻辑 - return StatReport.generateReportData(ctx, tenantID, appID, reportType, reportDate) -} - -// getDailyReportsForMonth 获取某个月的所有日报表 -func (s *StatReportScheduler) getDailyReportsForMonth(ctx context.Context, tenantID int64, date time.Time) ([]map[string]interface{}, error) { - startDate := time.Date(date.Year(), date.Month(), 1, 0, 0, 0, 0, time.Local) - endDate := startDate.AddDate(0, 1, -1) - - reports, _, err := dao.StatReport.List(ctx, strconv.FormatInt(tenantID, 10), "0", "daily", startDate.Format("2006-01-02"), endDate.Format("2006-01-02"), 1, 31) - if err != nil { - return nil, err - } - - var dailyData []map[string]interface{} - for _, report := range reports { - var data map[string]interface{} - if err := gconv.Struct(report.ReportData, &data); err != nil { - continue - } - dailyData = append(dailyData, data) - } - - return dailyData, nil -} - -// getMonthlyReportsForQuarter 获取某个季度的所有月报表 -func (s *StatReportScheduler) getMonthlyReportsForQuarter(ctx context.Context, tenantID int64, date time.Time) ([]map[string]interface{}, error) { - quarterStartMonth := time.Month(((date.Month()-1)/3)*3 + 1) - reports := make([]map[string]interface{}, 0) - - for i := 0; i < 3; i++ { - monthDate := time.Date(date.Year(), quarterStartMonth+time.Month(i), 1, 0, 0, 0, 0, time.Local) - reportDate := monthDate.Format("2006-01") - - report, err := dao.StatReport.GetByTenantAndDate(ctx, strconv.FormatInt(tenantID, 10), "monthly", reportDate) - if err != nil || report == nil { - continue - } - - var data map[string]interface{} - if err := gconv.Struct(report.ReportData, &data); err != nil { - continue - } - reports = append(reports, data) - } - - return reports, nil -} - -// getQuarterlyReportsForYear 获取某年的所有季度报表 -func (s *StatReportScheduler) getQuarterlyReportsForYear(ctx context.Context, tenantID int64, date time.Time) ([]map[string]interface{}, error) { - reports := make([]map[string]interface{}, 0) - - for quarter := 1; quarter <= 4; quarter++ { - reportDate := fmt.Sprintf("%d-Q%d", date.Year(), quarter) - report, err := dao.StatReport.GetByTenantAndDate(ctx, strconv.FormatInt(tenantID, 10), "quarterly", reportDate) - if err != nil || report == nil { - continue - } - - var data map[string]interface{} - if err := gconv.Struct(report.ReportData, &data); err != nil { - continue - } - reports = append(reports, data) - } - - return reports, nil -} - -// aggregateDailyReportsToMonthly 聚合日报表数据生成月报表 -func (s *StatReportScheduler) aggregateDailyReportsToMonthly(dailyReports []map[string]interface{}) map[string]interface{} { - // 实现聚合逻辑,这里简化处理 - return map[string]interface{}{ - "type": "monthly", - "data": dailyReports, - "summary": "聚合后的月报数据", - } -} - -// aggregateMonthlyReportsToQuarterly 聚合月报表数据生成季度报表 -func (s *StatReportScheduler) aggregateMonthlyReportsToQuarterly(monthlyReports []map[string]interface{}) map[string]interface{} { - // 实现聚合逻辑,这里简化处理 - return map[string]interface{}{ - "type": "quarterly", - "data": monthlyReports, - "summary": "聚合后的季报数据", - } -} - -// aggregateQuarterlyReportsToYearly 聚合季度报表数据生成年报表 -func (s *StatReportScheduler) aggregateQuarterlyReportsToYearly(quarterlyReports []map[string]interface{}) map[string]interface{} { - // 实现聚合逻辑,这里简化处理 - return map[string]interface{}{ - "type": "yearly", - "data": quarterlyReports, - "summary": "聚合后的年报数据", - } -} - -// getAllTenants 获取所有租户ID -func (s *StatReportScheduler) getAllTenants(ctx context.Context) ([]int64, error) { - // 这里应该从数据库查询所有租户ID - // 暂时返回示例数据 - return []int64{1, 2, 3}, nil -} - -// isReportGenerated 检查报表是否已生成 -func (s *StatReportScheduler) isReportGenerated(ctx context.Context, tenantID int64, reportType, date string) bool { - report, err := dao.StatReport.GetByTenantAndDate(ctx, strconv.FormatInt(tenantID, 10), reportType, date) - if err != nil { - return false - } - return report != nil -} - -// isQuarterFirstDay 检查是否是季度第一天 -func (s *StatReportScheduler) isQuarterFirstDay() bool { - now := time.Now() - month := now.Month() - day := now.Day() - - // 季度第一天:1月1日、4月1日、7月1日、10月1日 - return (month == time.January && day == 1) || - (month == time.April && day == 1) || - (month == time.July && day == 1) || - (month == time.October && day == 1) -} - -// getNextQuarterFirstDay 获取下个季度第一天 -func (s *StatReportScheduler) getNextQuarterFirstDay(now time.Time) time.Time { - currentQuarter := (now.Month()-1)/3 + 1 - nextQuarter := currentQuarter + 1 - if nextQuarter > 4 { - nextQuarter = 1 - now = now.AddDate(1, 0, 0) - } - - nextQuarterMonth := time.Month((nextQuarter-1)*3 + 1) - return time.Date(now.Year(), nextQuarterMonth, 1, 0, 0, 0, 0, time.Local) -} diff --git a/service/stat_report_service.go b/service/stat_report_service.go deleted file mode 100644 index a69caaf..0000000 --- a/service/stat_report_service.go +++ /dev/null @@ -1,657 +0,0 @@ -package service - -import ( - "context" - "fmt" - "strconv" - "time" - - "cid/dao" - "cid/model/dto" - "cid/model/entity" - - "github.com/gogf/gf/v2/frame/g" - "github.com/gogf/gf/v2/util/gconv" -) - -type StatReportService struct{} - -var StatReport = &StatReportService{} - -// GenerateDailyReport 生成日报表(现在只用于手动触发,定时任务会自动生成) -func (s *StatReportService) GenerateDailyReport(ctx context.Context, req *dto.ReportGenerateReq) (*dto.ReportGenerateResp, error) { - // 获取统计日期 - reportDate := time.Now() - if req.Date != "" { - parsedDate, err := time.Parse("2006-01-02", req.Date) - if err == nil { - reportDate = parsedDate - } - } - - // 检查是否已存在报表 - existingReport, err := dao.StatReport.GetByTenantAndDate(ctx, strconv.FormatInt(req.TenantID, 10), "daily", reportDate.Format("2006-01-02")) - if err == nil && existingReport != nil { - // 返回已存在的报表 - var reportData map[string]interface{} - if err := gconv.Struct(existingReport.ReportData, &reportData); err != nil { - return nil, err - } - - return &dto.ReportGenerateResp{ - ReportID: existingReport.Id.Hex(), - ReportType: "daily", - ReportDate: reportDate.Format("2006-01-02"), - Data: reportData, - }, nil - } - - // 生成日报表数据 - reportData, err := s.generateReportData(ctx, req.TenantID, req.AppID, "daily", reportDate) - if err != nil { - return nil, err - } - - // 保存报表 - report := &entity.StatReport{ - AppID: strconv.FormatInt(req.AppID, 10), - ReportType: "daily", - ReportDate: reportDate, - ReportData: gconv.String(reportData), - GeneratedAt: time.Now(), - Status: "completed", - } - - err = dao.StatReport.Create(ctx, report) - if err != nil { - return nil, err - } - - return &dto.ReportGenerateResp{ - ReportID: report.Id.Hex(), - ReportType: "daily", - ReportDate: reportDate.Format("2006-01-02"), - Data: reportData, - }, nil -} - -// GenerateMonthlyReport 生成月报表(现在优先使用预生成的报表) -func (s *StatReportService) GenerateMonthlyReport(ctx context.Context, req *dto.ReportGenerateReq) (*dto.ReportGenerateResp, error) { - reportDate := time.Now() - if req.Date != "" { - parsedDate, err := time.Parse("2006-01", req.Date) - if err == nil { - reportDate = parsedDate - } - } - - // 检查是否已存在报表 - existingReport, err := dao.StatReport.GetByTenantAndDate(ctx, strconv.FormatInt(req.TenantID, 10), "monthly", reportDate.Format("2006-01")) - if err == nil && existingReport != nil { - var reportData map[string]interface{} - if err := gconv.Struct(existingReport.ReportData, &reportData); err != nil { - return nil, err - } - - return &dto.ReportGenerateResp{ - ReportID: existingReport.Id.Hex(), - ReportType: "monthly", - ReportDate: reportDate.Format("2006-01"), - Data: reportData, - }, nil - } - - reportData, err := s.generateReportData(ctx, req.TenantID, req.AppID, "monthly", reportDate) - if err != nil { - return nil, err - } - - report := &entity.StatReport{ - AppID: strconv.FormatInt(req.AppID, 10), - ReportType: "monthly", - ReportDate: reportDate, - ReportData: gconv.String(reportData), - GeneratedAt: time.Now(), - Status: "completed", - } - - err = dao.StatReport.Create(ctx, report) - if err != nil { - return nil, err - } - - return &dto.ReportGenerateResp{ - ReportID: report.Id.Hex(), - ReportType: "monthly", - ReportDate: reportDate.Format("2006-01"), - Data: reportData, - }, nil -} - -// GenerateWeeklyReport 生成周报表(新增周报表支持) -func (s *StatReportService) GenerateWeeklyReport(ctx context.Context, req *dto.ReportGenerateReq) (*dto.ReportGenerateResp, error) { - reportDate := time.Now() - if req.Date != "" { - // 周报表格式:2024-W01 - parsedDate, err := time.Parse("2006-W01", req.Date) - if err == nil { - reportDate = parsedDate - } - } - - // 检查是否已存在报表 - existingReport, err := dao.StatReport.GetByTenantAndDate(ctx, strconv.FormatInt(req.TenantID, 10), "weekly", reportDate.Format("2006-W01")) - if err == nil && existingReport != nil { - var reportData map[string]interface{} - if err := gconv.Struct(existingReport.ReportData, &reportData); err != nil { - return nil, err - } - - return &dto.ReportGenerateResp{ - ReportID: existingReport.Id.Hex(), - ReportType: "weekly", - ReportDate: reportDate.Format("2006-W01"), - Data: reportData, - }, nil - } - - reportData, err := s.generateReportData(ctx, req.TenantID, req.AppID, "weekly", reportDate) - if err != nil { - return nil, err - } - - report := &entity.StatReport{ - AppID: strconv.FormatInt(req.AppID, 10), - ReportType: "weekly", - ReportDate: reportDate, - ReportData: gconv.String(reportData), - GeneratedAt: time.Now(), - Status: "completed", - } - - err = dao.StatReport.Create(ctx, report) - if err != nil { - return nil, err - } - - return &dto.ReportGenerateResp{ - ReportID: report.Id.Hex(), - ReportType: "weekly", - ReportDate: reportDate.Format("2006-W01"), - Data: reportData, - }, nil -} - -// 生成季度报表 -func (s *StatReportService) GenerateQuarterlyReport(ctx context.Context, req *dto.ReportGenerateReq) (*dto.ReportGenerateResp, error) { - reportDate := time.Now() - if req.Date != "" { - parsedDate, err := time.Parse("2006-Q1", req.Date) - if err == nil { - reportDate = parsedDate - } - } - - // 检查是否已存在报表 - existingReport, err := dao.StatReport.GetByTenantAndDate(ctx, strconv.FormatInt(req.TenantID, 10), "quarterly", reportDate.Format("2006-Q1")) - if err == nil && existingReport != nil { - var reportData map[string]interface{} - if err := gconv.Struct(existingReport.ReportData, &reportData); err != nil { - return nil, err - } - - return &dto.ReportGenerateResp{ - ReportID: existingReport.Id.Hex(), - ReportType: "quarterly", - ReportDate: reportDate.Format("2006-Q1"), - Data: reportData, - }, nil - } - - reportData, err := s.generateReportData(ctx, req.TenantID, req.AppID, "quarterly", reportDate) - if err != nil { - return nil, err - } - - report := &entity.StatReport{ - AppID: strconv.FormatInt(req.AppID, 10), - ReportType: "quarterly", - ReportDate: reportDate, - ReportData: gconv.String(reportData), - GeneratedAt: time.Now(), - Status: "completed", - } - - err = dao.StatReport.Create(ctx, report) - if err != nil { - return nil, err - } - - return &dto.ReportGenerateResp{ - ReportID: report.Id.Hex(), - ReportType: "quarterly", - ReportDate: reportDate.Format("2006-Q1"), - Data: reportData, - }, nil -} - -// 生成年报表 -func (s *StatReportService) GenerateYearlyReport(ctx context.Context, req *dto.ReportGenerateReq) (*dto.ReportGenerateResp, error) { - reportDate := time.Now() - if req.Date != "" { - parsedDate, err := time.Parse("2006", req.Date) - if err == nil { - reportDate = parsedDate - } - } - - // 检查是否已存在报表 - existingReport, err := dao.StatReport.GetByTenantAndDate(ctx, strconv.FormatInt(req.TenantID, 10), "yearly", reportDate.Format("2006")) - if err == nil && existingReport != nil { - var reportData map[string]interface{} - if err := gconv.Struct(existingReport.ReportData, &reportData); err != nil { - return nil, err - } - - return &dto.ReportGenerateResp{ - ReportID: existingReport.Id.Hex(), - ReportType: "yearly", - ReportDate: reportDate.Format("2006"), - Data: reportData, - }, nil - } - - reportData, err := s.generateReportData(ctx, req.TenantID, req.AppID, "yearly", reportDate) - if err != nil { - return nil, err - } - - report := &entity.StatReport{ - AppID: strconv.FormatInt(req.AppID, 10), - ReportType: "yearly", - ReportDate: reportDate, - ReportData: gconv.String(reportData), - GeneratedAt: time.Now(), - Status: "completed", - } - - err = dao.StatReport.Create(ctx, report) - if err != nil { - return nil, err - } - - return &dto.ReportGenerateResp{ - ReportID: report.Id.Hex(), - ReportType: "yearly", - ReportDate: reportDate.Format("2006"), - Data: reportData, - }, nil -} - -// 生成报表数据 -func (s *StatReportService) generateReportData(ctx context.Context, tenantID, appID int64, reportType string, reportDate time.Time) (map[string]interface{}, error) { - // 构建查询条件 - where := g.Map{"tenant_id": tenantID} - if appID > 0 { - where["app_id"] = appID - } - - // 根据报表类型确定时间范围 - startTime, endTime := s.getReportTimeRange(reportType, reportDate) - where["created_at between ? and ?"] = g.Slice{startTime, endTime} - - // 查询基础统计数据 - // 这里简化实现,实际应该使用mongo查询ad_statistics集合 - // 由于ad_statistics可能不存在或需要重构,这里返回模拟数据 - stats := []map[string]interface{}{ - { - "impressions": 1200, - "clicks": 60, - "revenue": 600.0, - "ad_type": "banner", - "region": "北京", - "platform": "web", - "play_duration": 30.5, - }, - } - - // 计算环比数据 - yoyData, err := s.calculateYearOverYear(ctx, tenantID, appID, reportType, reportDate) - if err != nil { - return nil, err - } - - // 计算同比数据 - momData, err := s.calculateMonthOverMonth(ctx, tenantID, appID, reportType, reportDate) - if err != nil { - return nil, err - } - - // 按广告类型分组统计 - adTypeStats, err := s.groupByAdType(stats) - if err != nil { - return nil, err - } - - // 按地区分组统计 - regionStats := s.groupByRegion(stats) - - // 按终端类型分组统计 - platformStats := s.groupByPlatform(stats) - - return map[string]interface{}{ - "basic_stats": map[string]interface{}{ - "total_impressions": s.sumField(stats, "impressions"), - "total_clicks": s.sumField(stats, "clicks"), - "total_revenue": s.sumField(stats, "revenue"), - "avg_ctr": s.calculateCTR(stats), - "avg_play_duration": s.avgField(stats, "play_duration"), - }, - "ad_type_stats": adTypeStats, - "region_stats": regionStats, - "platform_stats": platformStats, - "year_over_year": yoyData, - "month_over_month": momData, - "time_range": map[string]string{ - "start": startTime.Format("2006-01-02 15:04:05"), - "end": endTime.Format("2006-01-02 15:04:05"), - }, - }, nil -} - -// 获取报表时间范围 -func (s *StatReportService) getReportTimeRange(reportType string, reportDate time.Time) (time.Time, time.Time) { - switch reportType { - case "daily": - start := time.Date(reportDate.Year(), reportDate.Month(), reportDate.Day(), 0, 0, 0, 0, time.Local) - end := start.AddDate(0, 0, 1).Add(-time.Second) - return start, end - case "monthly": - start := time.Date(reportDate.Year(), reportDate.Month(), 1, 0, 0, 0, 0, time.Local) - end := start.AddDate(0, 1, 0).Add(-time.Second) - return start, end - case "quarterly": - quarter := (reportDate.Month()-1)/3 + 1 - startMonth := time.Month((quarter-1)*3 + 1) - start := time.Date(reportDate.Year(), startMonth, 1, 0, 0, 0, 0, time.Local) - end := start.AddDate(0, 3, 0).Add(-time.Second) - return start, end - case "yearly": - start := time.Date(reportDate.Year(), 1, 1, 0, 0, 0, 0, time.Local) - end := start.AddDate(1, 0, 0).Add(-time.Second) - return start, end - default: - return reportDate, reportDate - } -} - -// 计算同比数据 -func (s *StatReportService) calculateYearOverYear(ctx context.Context, tenantID, appID int64, reportType string, reportDate time.Time) (map[string]interface{}, error) { - lastYearDate := reportDate.AddDate(-1, 0, 0) - lastYearData, err := s.getComparisonData(ctx, tenantID, appID, reportType, lastYearDate) - if err != nil { - return nil, err - } - - return map[string]interface{}{ - "last_year": lastYearData, - "growth_rate": s.calculateGrowthRate(lastYearData, s.getCurrentPeriodData(ctx, tenantID, appID, reportType, reportDate)), - }, nil -} - -// 计算环比数据 -func (s *StatReportService) calculateMonthOverMonth(ctx context.Context, tenantID, appID int64, reportType string, reportDate time.Time) (map[string]interface{}, error) { - var lastPeriodDate time.Time - switch reportType { - case "daily": - lastPeriodDate = reportDate.AddDate(0, 0, -1) - case "monthly": - lastPeriodDate = reportDate.AddDate(0, -1, 0) - case "quarterly": - lastPeriodDate = reportDate.AddDate(0, -3, 0) - case "yearly": - lastPeriodDate = reportDate.AddDate(-1, 0, 0) - } - - lastPeriodData, err := s.getComparisonData(ctx, tenantID, appID, reportType, lastPeriodDate) - if err != nil { - return nil, err - } - - return map[string]interface{}{ - "last_period": lastPeriodData, - "growth_rate": s.calculateGrowthRate(lastPeriodData, s.getCurrentPeriodData(ctx, tenantID, appID, reportType, reportDate)), - }, nil -} - -// 获取对比数据 -func (s *StatReportService) getComparisonData(ctx context.Context, tenantID, appID int64, reportType string, date time.Time) (map[string]float64, error) { - // 这里简化实现,实际应该查询数据库 - return map[string]float64{ - "impressions": 1000, - "clicks": 50, - "revenue": 500.0, - "ctr": 0.05, - }, nil -} - -// 获取当前周期数据 -func (s *StatReportService) getCurrentPeriodData(ctx context.Context, tenantID, appID int64, reportType string, date time.Time) map[string]float64 { - // 这里简化实现,实际应该查询数据库 - return map[string]float64{ - "impressions": 1200, - "clicks": 60, - "revenue": 600.0, - "ctr": 0.05, - } -} - -// 计算增长率 -func (s *StatReportService) calculateGrowthRate(lastData, currentData map[string]float64) map[string]float64 { - growthRate := make(map[string]float64) - for key, lastValue := range lastData { - currentValue := currentData[key] - if lastValue == 0 { - growthRate[key] = 0 - } else { - growthRate[key] = (currentValue - lastValue) / lastValue * 100 - } - } - return growthRate -} - -// 按广告类型分组统计 -func (s *StatReportService) groupByAdType(stats []map[string]interface{}) (map[string]interface{}, error) { - result := make(map[string]interface{}) - for _, stat := range stats { - adType := gconv.String(stat["ad_type"]) - if adType == "" { - adType = "unknown" - } - - if _, exists := result[adType]; !exists { - result[adType] = map[string]float64{ - "impressions": 0, - "clicks": 0, - "revenue": 0, - } - } - - adTypeStat, ok := result[adType].(map[string]float64) - if !ok { - return nil, fmt.Errorf("invalid adTypeStat type") - } - adTypeStat["impressions"] += gconv.Float64(stat["impressions"]) - adTypeStat["clicks"] += gconv.Float64(stat["clicks"]) - adTypeStat["revenue"] += gconv.Float64(stat["revenue"]) - } - - // 计算每个广告类型的CTR - for adType, stat := range result { - adTypeStat, ok := stat.(map[string]float64) - if !ok { - return nil, fmt.Errorf("invalid adTypeStat type for adType: %s", adType) - } - if adTypeStat["impressions"] > 0 { - adTypeStat["ctr"] = adTypeStat["clicks"] / adTypeStat["impressions"] * 100 - } else { - adTypeStat["ctr"] = 0 - } - } - - return result, nil -} - -// 按地区分组统计 -func (s *StatReportService) groupByRegion(stats []map[string]interface{}) map[string]interface{} { - result := make(map[string]interface{}) - for _, stat := range stats { - region := gconv.String(stat["region"]) - if region == "" { - region = "unknown" - } - - if _, exists := result[region]; !exists { - result[region] = map[string]float64{ - "impressions": 0, - "clicks": 0, - "revenue": 0, - } - } - - regionStat := result[region].(map[string]float64) - regionStat["impressions"] += gconv.Float64(stat["impressions"]) - regionStat["clicks"] += gconv.Float64(stat["clicks"]) - regionStat["revenue"] += gconv.Float64(stat["revenue"]) - } - return result -} - -// 按终端类型分组统计 -func (s *StatReportService) groupByPlatform(stats []map[string]interface{}) map[string]interface{} { - result := make(map[string]interface{}) - for _, stat := range stats { - platform := gconv.String(stat["platform"]) - if platform == "" { - platform = "unknown" - } - - if _, exists := result[platform]; !exists { - result[platform] = map[string]float64{ - "impressions": 0, - "clicks": 0, - "revenue": 0, - } - } - - platformStat := result[platform].(map[string]float64) - platformStat["impressions"] += gconv.Float64(stat["impressions"]) - platformStat["clicks"] += gconv.Float64(stat["clicks"]) - platformStat["revenue"] += gconv.Float64(stat["revenue"]) - } - return result -} - -// 计算字段总和 -func (s *StatReportService) sumField(stats []map[string]interface{}, field string) float64 { - total := 0.0 - for _, stat := range stats { - total += gconv.Float64(stat[field]) - } - return total -} - -// 计算字段平均值 -func (s *StatReportService) avgField(stats []map[string]interface{}, field string) float64 { - if len(stats) == 0 { - return 0 - } - return s.sumField(stats, field) / float64(len(stats)) -} - -// 计算平均CTR -func (s *StatReportService) calculateCTR(stats []map[string]interface{}) float64 { - totalImpressions := s.sumField(stats, "impressions") - totalClicks := s.sumField(stats, "clicks") - if totalImpressions == 0 { - return 0 - } - return totalClicks / totalImpressions * 100 -} - -// 查询报表列表 -func (s *StatReportService) GetReportList(ctx context.Context, req *dto.ReportListReq) (*dto.ReportListResp, error) { - // 使用DAO的List方法 - reports, count, err := dao.StatReport.List(ctx, strconv.FormatInt(req.TenantID, 10), strconv.FormatInt(req.AppID, 10), req.ReportType, req.StartDate, req.EndDate, req.Page, req.PageSize) - if err != nil { - return nil, err - } - - // 转换为DTO - var reportDTOs []*dto.ReportDTO - for _, report := range reports { - appID, _ := strconv.ParseInt(report.AppID, 10, 64) - // 使用ObjectId的十六进制字符串作为ID,在DTO中保持为字符串 - idStr := report.Id.Hex() - - // 将ObjectId的十六进制字符串转换为int64,如果失败则使用0 - var idInt64 int64 - if id, err := strconv.ParseInt(idStr, 16, 64); err == nil { - idInt64 = id - } - - reportDTOs = append(reportDTOs, &dto.ReportDTO{ - ID: idInt64, - TenantID: report.TenantId, - AppID: appID, - ReportType: report.ReportType, - ReportDate: report.ReportDate.Format("2006-01-02"), - GeneratedAt: report.GeneratedAt.Format("2006-01-02 15:04:05"), - }) - } - - return &dto.ReportListResp{ - Reports: reportDTOs, - Total: count, - Page: req.Page, - PageSize: req.PageSize, - }, nil -} - -// 获取报表详情 -func (s *StatReportService) GetReportDetail(ctx context.Context, reportID int64) (*dto.ReportDetailResp, error) { - var report *entity.StatReport - report, err := dao.StatReport.GetByID(ctx, strconv.FormatInt(reportID, 10)) - if err != nil { - return nil, err - } - - if report == nil { - return nil, fmt.Errorf("报表不存在") - } - - // 解析报表数据 - var reportData map[string]interface{} - if err := gconv.Struct(report.ReportData, &reportData); err != nil { - return nil, err - } - - appID, _ := strconv.ParseInt(report.AppID, 10, 64) - idStr := report.Id.Hex() - - // 将ObjectId的十六进制字符串转换为int64,如果失败则使用0 - var idInt64 int64 - if id, err := strconv.ParseInt(idStr, 16, 64); err == nil { - idInt64 = id - } - - return &dto.ReportDetailResp{ - ID: idInt64, - TenantID: report.TenantId, - AppID: appID, - ReportType: report.ReportType, - ReportDate: report.ReportDate.Format("2006-01-02"), - GeneratedAt: report.GeneratedAt.Format("2006-01-02 15:04:05"), - Data: reportData, - }, nil -} diff --git a/service/strategy_service.go b/service/strategy_service.go index b50e442..cdd0d4c 100644 --- a/service/strategy_service.go +++ b/service/strategy_service.go @@ -48,9 +48,11 @@ func (s *strategyService) CreateStrategy(ctx context.Context, req *dto.CreateStr SourceWeights: string(weightsJson), MaxAdsPerReq: req.MaxAdsPerReq, Priority: req.Priority, - Status: req.Status, } + // 设置状态 + strategy.Status = req.Status + _, err = dao.Strategy.Create(ctx, strategy) if err != nil { return 0, err @@ -101,9 +103,11 @@ func (s *strategyService) UpdateStrategy(ctx context.Context, req *dto.UpdateStr SourceWeights: string(weightsJson), MaxAdsPerReq: req.MaxAdsPerReq, Priority: req.Priority, - Status: req.Status, } + // 设置状态 + strategy.Status = req.Status + return dao.Strategy.Update(ctx, strategy) }