gomod引用
This commit is contained in:
@@ -13,37 +13,17 @@ const (
|
|||||||
AdvertiserCacheKeyPrefix = "cid:adv:" // 广告主缓存前缀
|
AdvertiserCacheKeyPrefix = "cid:adv:" // 广告主缓存前缀
|
||||||
)
|
)
|
||||||
|
|
||||||
// 统计数据键
|
|
||||||
const (
|
|
||||||
AdStatKeyPrefix = "cid:stat:ad:" // 广告统计键前缀
|
|
||||||
AdvStatKeyPrefix = "cid:stat:adv:" // 广告主统计键前缀
|
|
||||||
PosStatKeyPrefix = "cid:stat:pos:" // 广告位统计键前缀
|
|
||||||
DailyStatKeyPrefix = "cid:stat:daily:" // 日常统计键前缀
|
|
||||||
)
|
|
||||||
|
|
||||||
// 广告匹配键
|
// 广告匹配键
|
||||||
const (
|
const (
|
||||||
AdMatchingKeyPrefix = "cid:match:" // 广告匹配键前缀
|
AdMatchingKeyPrefix = "cid:match:" // 广告匹配键前缀
|
||||||
UserProfileKeyPrefix = "cid:user:" // 用户画像键前缀
|
UserProfileKeyPrefix = "cid:user:" // 用户画像键前缀
|
||||||
)
|
)
|
||||||
|
|
||||||
// 报表键
|
|
||||||
const (
|
|
||||||
ReportCacheKeyPrefix = "cid:report:" // 报表缓存键前缀
|
|
||||||
ReportTaskKeyPrefix = "cid:task:" // 报表任务键前缀
|
|
||||||
)
|
|
||||||
|
|
||||||
// 限流键
|
// 限流键
|
||||||
const (
|
const (
|
||||||
ApiRequestLimitKeyPrefix = "cid:limit:api:" // API请求限流键前缀
|
ApiRequestLimitKeyPrefix = "cid:limit:api:" // API请求限流键前缀
|
||||||
)
|
)
|
||||||
|
|
||||||
// 队列键
|
|
||||||
const (
|
|
||||||
AdStatisticsQueueKey = "cid:queue:stat" // 统计数据队列
|
|
||||||
ReportGenerateQueueKey = "cid:queue:report" // 报表生成队列
|
|
||||||
)
|
|
||||||
|
|
||||||
// Stream键
|
// Stream键
|
||||||
const (
|
const (
|
||||||
AdEventStreamKey = "cid:stream:ad_event" // 广告事件流
|
AdEventStreamKey = "cid:stream:ad_event" // 广告事件流
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -15,7 +15,7 @@ type cidRequestDao struct{}
|
|||||||
|
|
||||||
// Create 创建CID请求记录
|
// Create 创建CID请求记录
|
||||||
func (d *cidRequestDao) Create(ctx context.Context, request *entity.CidRequest) (id string, err error) {
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@@ -30,14 +30,14 @@ func (d *cidRequestDao) GetHistory(ctx context.Context, userId string, page, siz
|
|||||||
filter := bson.M{"userId": userId}
|
filter := bson.M{"userId": userId}
|
||||||
|
|
||||||
// 获取总数
|
// 获取总数
|
||||||
total, err = mongo.Count(ctx, filter, "cid_requests")
|
total, err = mongo.Count(ctx, filter, entity.CidRequestCollection)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分页查询
|
// 分页查询
|
||||||
offset := (page - 1) * size
|
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}).
|
options.Find().SetSort(bson.M{"createdAt": -1}).
|
||||||
SetSkip(int64(offset)).
|
SetSkip(int64(offset)).
|
||||||
SetLimit(int64(size)))
|
SetLimit(int64(size)))
|
||||||
@@ -50,14 +50,14 @@ func (d *cidRequestDao) GetStatistics(ctx context.Context, userId string) (stats
|
|||||||
stats = make(map[string]interface{})
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
stats["total_requests"] = totalRequests
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
13
main.go
13
main.go
@@ -2,9 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"cid/controller"
|
"cid/controller"
|
||||||
"cid/service"
|
|
||||||
"fmt"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"gitee.com/red-future---jilin-g/common/http"
|
"gitee.com/red-future---jilin-g/common/http"
|
||||||
"gitee.com/red-future---jilin-g/common/jaeger"
|
"gitee.com/red-future---jilin-g/common/jaeger"
|
||||||
@@ -19,22 +16,12 @@ func main() {
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
defer jaeger.ShutDown(ctx)
|
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{}{
|
http.RouteRegister([]interface{}{
|
||||||
controller.Advertisement,
|
controller.Advertisement,
|
||||||
controller.Advertiser,
|
controller.Advertiser,
|
||||||
controller.AdPosition,
|
controller.AdPosition,
|
||||||
controller.AdStatistics,
|
|
||||||
controller.RateLimit,
|
controller.RateLimit,
|
||||||
controller.Application,
|
controller.Application,
|
||||||
controller.StatReport,
|
|
||||||
})
|
})
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"` // 是否成功
|
|
||||||
}
|
|
||||||
@@ -30,7 +30,7 @@ type AddAdvertisementReq struct {
|
|||||||
BillingType string `json:"billingType" v:"required"` // 计费类型:CPC、CPM、CPA等
|
BillingType string `json:"billingType" v:"required"` // 计费类型:CPC、CPM、CPA等
|
||||||
|
|
||||||
// 投放条件
|
// 投放条件
|
||||||
Targeting *entity.Targeting `json:"targeting"` // 定向条件
|
Targeting *entity.UnifiedTargeting `json:"targeting"` // 定向条件
|
||||||
}
|
}
|
||||||
|
|
||||||
type AddAdvertisementRes struct {
|
type AddAdvertisementRes struct {
|
||||||
@@ -62,7 +62,7 @@ type UpdateAdvertisementReq struct {
|
|||||||
BillingType string `json:"billingType"` // 计费类型:CPC、CPM、CPA等
|
BillingType string `json:"billingType"` // 计费类型:CPC、CPM、CPA等
|
||||||
|
|
||||||
// 投放条件
|
// 投放条件
|
||||||
Targeting *entity.Targeting `json:"targeting"` // 定向条件
|
Targeting *entity.UnifiedTargeting `json:"targeting"` // 定向条件
|
||||||
|
|
||||||
// 状态信息
|
// 状态信息
|
||||||
Status *string `json:"status"` // 广告状态:待审核、审核中、已通过、已拒绝、投放中、已暂停、已结束
|
Status *string `json:"status"` // 广告状态:待审核、审核中、已通过、已拒绝、投放中、已暂停、已结束
|
||||||
|
|||||||
@@ -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"`
|
|
||||||
}
|
|
||||||
67
model/entity/ad_creative.go
Normal file
67
model/entity/ad_creative.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
55
model/entity/ad_platform.go
Normal file
55
model/entity/ad_platform.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -8,7 +8,8 @@ const AdPositionCollection = "ad_position"
|
|||||||
|
|
||||||
// AdPosition 广告位实体
|
// AdPosition 广告位实体
|
||||||
type AdPosition struct {
|
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"` // 广告位名称
|
Name string `bson:"name" json:"name"` // 广告位名称
|
||||||
@@ -17,8 +18,8 @@ type AdPosition struct {
|
|||||||
AdFormat string `bson:"adFormat" json:"adFormat"` // 支持的广告格式
|
AdFormat string `bson:"adFormat" json:"adFormat"` // 支持的广告格式
|
||||||
|
|
||||||
// 尺寸信息
|
// 尺寸信息
|
||||||
Width int `bson:"width" json:"width"` // 宽度(px)
|
Width int64 `bson:"width" json:"width"` // 宽度(px)
|
||||||
Height int `bson:"height" json:"height"` // 高度(px)
|
Height int64 `bson:"height" json:"height"` // 高度(px)
|
||||||
|
|
||||||
// 位置信息
|
// 位置信息
|
||||||
Page string `bson:"page" json:"page"` // 所属页面
|
Page string `bson:"page" json:"page"` // 所属页面
|
||||||
@@ -39,16 +40,11 @@ type AdPosition struct {
|
|||||||
// 展示规则
|
// 展示规则
|
||||||
DisplayRules *DisplayRules `bson:"displayRules" json:"displayRules"` // 展示规则
|
DisplayRules *DisplayRules `bson:"displayRules" json:"displayRules"` // 展示规则
|
||||||
|
|
||||||
// 状态信息
|
// 限制配置
|
||||||
Status string `bson:"status" json:"status"` // 广告位状态:启用、禁用、测试
|
RestrictionConfig `bson:",inline" json:",inline"` // 内联限制配置
|
||||||
IsExclusive bool `bson:"isExclusive" json:"isExclusive"` // 是否独占广告位
|
|
||||||
|
|
||||||
// 统计信息
|
// 其他状态
|
||||||
DailyImpressions int64 `bson:"dailyImpressions" json:"dailyImpressions"` // 日均展示量
|
IsExclusive bool `bson:"isExclusive" json:"isExclusive"` // 是否独占广告位
|
||||||
DailyClicks int64 `bson:"dailyClicks" json:"dailyClicks"` // 日均点击量
|
|
||||||
DailyRevenue int64 `bson:"dailyRevenue" json:"dailyRevenue"` // 日均收入(分)
|
|
||||||
CTR float64 `bson:"ctr" json:"ctr"` // 点击率
|
|
||||||
eCPM int64 `bson:"ecpm" json:"ecpm"` // 有效千次展示收入
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisplayRules 广告位展示规则
|
// DisplayRules 广告位展示规则
|
||||||
@@ -80,3 +76,8 @@ type ExcludeCondition struct {
|
|||||||
Type string `bson:"type" json:"type"` // 条件类型
|
Type string `bson:"type" json:"type"` // 条件类型
|
||||||
Value interface{} `bson:"value" json:"value"` // 条件值
|
Value interface{} `bson:"value" json:"value"` // 条件值
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCollectionName 获取集合名称
|
||||||
|
func (a *AdPosition) GetCollectionName() string {
|
||||||
|
return AdPositionCollection
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,156 +8,65 @@ const AdSourceCollection = "ad_source"
|
|||||||
|
|
||||||
// AdSource 广告源实体
|
// AdSource 广告源实体
|
||||||
type AdSource struct {
|
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"` // 广告源名称
|
Name string `bson:"name" json:"name"` // 广告源名称
|
||||||
Code string `json:"code"` // 广告源编码,唯一标识
|
Code string `bson:"code" json:"code"` // 广告源编码,唯一标识
|
||||||
Provider string `json:"provider"` // 提供商:google、facebook、baidu、tencent、self等
|
Provider string `bson:"provider" json:"provider"` // 提供商:self(自营)、chuanshanjia(穿山甲)、gdt(腾讯广点通)、baidu(百度)、byteance(字节跳动)等
|
||||||
Type string `json:"type"` // 类型:self(自营)、third_party(第三方)、exchange(广告交易平台)
|
Type string `bson:"type" json:"type"` // 类型:self(自营)、third_party(第三方)、exchange(广告交易平台)、platform_ad_source(平台广告源)
|
||||||
Description string `json:"description"` // 描述
|
Category string `bson:"category" json:"category"` // 分类:network、ssp、dsp、rtb等
|
||||||
|
|
||||||
// 连接配置
|
// 连接配置
|
||||||
Config string `json:"config"` // 广告源配置(JSON字符串)
|
Config string `bson:"config" json:"config"` // 广告源配置(JSON字符串)
|
||||||
|
|
||||||
// API配置
|
// API配置
|
||||||
APIEndpoint string `json:"apiEndpoint"` // API端点
|
APIConfig `bson:",inline" json:",inline"` // 内联API配置
|
||||||
APIVersion string `json:"apiVersion"` // API版本
|
|
||||||
AuthType string `json:"authType"` // 认证类型:api_key、oauth、basic
|
// 创意配置
|
||||||
AuthConfig string `json:"authConfig"` // 认证配置(JSON字符串)
|
CreativeConfig `bson:",inline" json:",inline"` // 内联创意配置
|
||||||
Headers string `json:"headers"` // 请求头配置(JSON字符串)
|
|
||||||
Timeout int `json:"timeout"` // 超时时间(毫秒)
|
|
||||||
RetryCount int `json:"retryCount"` // 重试次数
|
|
||||||
|
|
||||||
// 广告源能力
|
// 广告源能力
|
||||||
Capabilities string `json:"capabilities"` // 广告源能力(JSON字符串)
|
Capabilities *AdSourceCapabilities `bson:"capabilities" json:"capabilities"` // 广告源能力
|
||||||
|
|
||||||
// 质量指标
|
// 支付配置
|
||||||
QualityMetrics string `json:"qualityMetrics"` // 质量指标(JSON字符串)
|
PaymentConfig `bson:",inline" json:",inline"` // 内联支付配置
|
||||||
|
|
||||||
// 财务设置
|
|
||||||
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定向
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdSourceCapabilities 广告源能力
|
// AdSourceCapabilities 广告源能力
|
||||||
type AdSourceCapabilities struct {
|
type AdSourceCapabilities struct {
|
||||||
// 广告格式
|
// 广告格式
|
||||||
SupportedFormats []AdFormat `json:"supportedFormats"` // 支持的广告格式
|
SupportedFormats []AdFormat `bson:"supportedFormats" json:"supportedFormats"` // 支持的广告格式
|
||||||
|
|
||||||
// 功能特性
|
// 功能特性
|
||||||
RealTimeBidding bool `json:"realTimeBidding"` // 实时竞价
|
RealTimeBidding bool `bson:"realTimeBidding" json:"realTimeBidding"` // 实时竞价
|
||||||
HeaderBidding bool `json:"headerBidding"` // 标题竞价
|
HeaderBidding bool `bson:"headerBidding" json:"headerBidding"` // 标题竞价
|
||||||
ProgrammaticDirect bool `json:"programmaticDirect"` // 程序化直购
|
ProgrammaticDirect bool `bson:"programmaticDirect" json:"programmaticDirect"` // 程序化直购
|
||||||
PrivateMarketplace bool `json:"privateMarketplace"` // 私有交易市场
|
PrivateMarketplace bool `bson:"privateMarketplace" json:"privateMarketplace"` // 私有交易市场
|
||||||
|
|
||||||
// 质量控制
|
// 质量控制
|
||||||
FraudDetection bool `json:"fraudDetection"` // 反欺诈检测
|
FraudDetection bool `bson:"fraudDetection" json:"fraudDetection"` // 反欺诈检测
|
||||||
BrandSafety bool `json:"brandSafety"` // 品牌安全
|
BrandSafety bool `bson:"brandSafety" json:"brandSafety"` // 品牌安全
|
||||||
Viewability bool `json:"viewability"` // 可见度验证
|
Viewability bool `bson:"viewability" json:"viewability"` // 可见度验证
|
||||||
CreativeApproval bool `json:"creativeApproval"` // 创意审核
|
CreativeApproval bool `bson:"creativeApproval" json:"creativeApproval"` // 创意审核
|
||||||
|
|
||||||
// 数据能力
|
// 数据能力
|
||||||
AudienceTargeting bool `json:"audienceTargeting"` // 受众定向
|
AudienceTargeting bool `bson:"audienceTargeting" json:"audienceTargeting"` // 受众定向
|
||||||
ContextualTargeting bool `json:"contextualTargeting"` // 上下文定向
|
ContextualTargeting bool `bson:"contextualTargeting" json:"contextualTargeting"` // 上下文定向
|
||||||
CrossDeviceTargeting bool `json:"crossDeviceTargeting"` // 跨设备定向
|
CrossDeviceTargeting bool `bson:"crossDeviceTargeting" json:"crossDeviceTargeting"` // 跨设备定向
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdFormat 广告格式
|
// AdFormat 广告格式
|
||||||
type AdFormat struct {
|
type AdFormat struct {
|
||||||
Type string `json:"type"` // 格式类型:banner、video、native、interstitial等
|
Type string `bson:"type" json:"type"` // 格式类型:banner、video、native、interstitial等
|
||||||
Name string `json:"name"` // 格式名称
|
Name string `bson:"name" json:"name"` // 格式名称
|
||||||
Width int `json:"width"` // 宽度
|
Width int `bson:"width" json:"width"` // 宽度
|
||||||
Height int `json:"height"` // 高度
|
Height int `bson:"height" json:"height"` // 高度
|
||||||
MimeType string `json:"mimeType"` // MIME类型
|
MimeType string `bson:"mimeType" json:"mimeType"` // MIME类型
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdSourceQualityMetrics 广告源质量指标
|
// GetCollectionName 获取集合名称
|
||||||
type AdSourceQualityMetrics struct {
|
func (a *AdSource) GetCollectionName() string {
|
||||||
// 性能指标
|
return AdSourceCollection
|
||||||
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"` // 提前付款折扣
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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"` // 数据
|
|
||||||
}
|
|
||||||
@@ -8,78 +8,46 @@ const AdvertisementCollection = "advertisement"
|
|||||||
|
|
||||||
// Advertisement 广告实体
|
// Advertisement 广告实体
|
||||||
type Advertisement struct {
|
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"` // 广告标题
|
Title string `bson:"title" json:"title"` // 广告标题
|
||||||
Description string `bson:"description" json:"description"` // 广告描述
|
Description string `bson:"description" json:"description"` // 广告描述
|
||||||
AdvertiserId string `bson:"advertiserId" json:"advertiserId"` // 广告主ID
|
|
||||||
AdPositionId string `bson:"adPositionId" json:"adPositionId"` // 广告位ID
|
AdPositionId string `bson:"adPositionId" json:"adPositionId"` // 广告位ID
|
||||||
AdType string `bson:"adType" json:"adType"` // 广告类型:图片、视频、文字等
|
AdType string `bson:"adType" json:"adType"` // 广告类型:图片、视频、文字等
|
||||||
AdFormat string `bson:"adFormat" json:"adFormat"` // 广告格式
|
AdFormat string `bson:"adFormat" json:"adFormat"` // 广告格式
|
||||||
MaterialUrl string `bson:"materialUrl" json:"materialUrl"` // 广告素材URL
|
MaterialUrl string `bson:"materialUrl" json:"materialUrl"` // 广告素材URL
|
||||||
TargetUrl string `bson:"targetUrl" json:"targetUrl"` // 目标链接(点击跳转或落地页)
|
TargetUrl string `bson:"targetUrl" json:"targetUrl"` // 目标链接(点击跳转或落地页)
|
||||||
|
|
||||||
// 投放设置
|
// 平台和广告源信息
|
||||||
StartDate int64 `bson:"startDate" json:"startDate"` // 开始投放时间
|
AdSourceId string `bson:"adSourceId" json:"adSourceId"` // 广告源ID
|
||||||
EndDate int64 `bson:"endDate" json:"endDate"` // 结束投放时间
|
AdPlatformId string `bson:"adPlatformId" json:"adPlatformId"` // 广告平台ID(当广告来自第三方平台时)
|
||||||
Budget int64 `bson:"budget" json:"budget"` // 预算(分)
|
ExternalAdId string `bson:"externalAdId" json:"externalAdId"` // 外部广告ID(第三方平台的广告ID)
|
||||||
DailyBudget int64 `bson:"dailyBudget" json:"dailyBudget"` // 日预算(分)
|
AdProvider string `bson:"adProvider" json:"adProvider"` // 广告提供者:self、chuanshanjia、xiaohongshu、douyin等
|
||||||
|
|
||||||
|
// 投放配置
|
||||||
|
BudgetConfig `bson:",inline" json:",inline"` // 内联预算配置
|
||||||
BidAmount int64 `bson:"bidAmount" json:"bidAmount"` // 出价(分)
|
BidAmount int64 `bson:"bidAmount" json:"bidAmount"` // 出价(分)
|
||||||
BillingType string `bson:"billingType" json:"billingType"` // 计费类型:CPC、CPM、CPA等
|
BillingType string `bson:"billingType" json:"billingType"` // 计费类型:CPC、CPM、CPA等
|
||||||
|
|
||||||
// 投放条件
|
// 定向条件
|
||||||
Targeting *Targeting `bson:"targeting" json:"targeting"` // 定向条件
|
Targeting *UnifiedTargeting `bson:"targeting" json:"targeting"` // 统一定向条件
|
||||||
|
|
||||||
// 状态信息
|
// 审核状态
|
||||||
Status string `bson:"status" json:"status"` // 广告状态:待审核、审核中、已通过、已拒绝、投放中、已暂停、已结束
|
AuditStatus string `bson:"auditStatus" json:"auditStatus"` // 广告状态:待审核、审核中、已通过、已拒绝、投放中、已暂停、已结束
|
||||||
AuditReason string `bson:"auditReason" json:"auditReason"` // 审核不通过原因
|
AuditReason string `bson:"auditReason" json:"auditReason"` // 审核不通过原因
|
||||||
AuditTime int64 `bson:"auditTime" json:"auditTime"` // 审核时间
|
AuditTime int64 `bson:"auditTime" json:"auditTime"` // 审核时间
|
||||||
AuditBy string `bson:"auditBy" json:"auditBy"` // 审核人
|
AuditBy string `bson:"auditBy" json:"auditBy"` // 审核人
|
||||||
|
|
||||||
// 基础统计信息(比率字段通过计算得到,不持久化存储)
|
// 限制配置
|
||||||
Impressions int64 `bson:"impressions" json:"impressions"` // 展示次数
|
RestrictionConfig `bson:",inline" json:",inline"` // 内联限制配置
|
||||||
Clicks int64 `bson:"clicks" json:"clicks"` // 点击次数
|
|
||||||
Conversions int64 `bson:"conversions" json:"conversions"` // 转化次数
|
// 其他状态信息
|
||||||
Cost int64 `bson:"cost" json:"cost"` // 消耗(分)
|
Status string `bson:"status" json:"status"` // 业务状态:active、inactive、archived
|
||||||
}
|
}
|
||||||
|
|
||||||
// Targeting 广告定向条件
|
// GetCollectionName 获取集合名称
|
||||||
type Targeting struct {
|
func (a *Advertisement) GetCollectionName() string {
|
||||||
// 地域定向
|
return AdvertisementCollection
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,8 @@ const AdvertiserCollection = "advertiser"
|
|||||||
|
|
||||||
// Advertiser 广告主实体
|
// Advertiser 广告主实体
|
||||||
type Advertiser struct {
|
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"` // 广告主名称
|
Name string `bson:"name" json:"name"` // 广告主名称
|
||||||
@@ -16,7 +17,6 @@ type Advertiser struct {
|
|||||||
ContactPhone string `bson:"contactPhone" json:"contactPhone"` // 联系电话
|
ContactPhone string `bson:"contactPhone" json:"contactPhone"` // 联系电话
|
||||||
ContactEmail string `bson:"contactEmail" json:"contactEmail"` // 联系邮箱
|
ContactEmail string `bson:"contactEmail" json:"contactEmail"` // 联系邮箱
|
||||||
Company string `bson:"company" json:"company"` // 公司名称
|
Company string `bson:"company" json:"company"` // 公司名称
|
||||||
Industry string `bson:"industry" json:"industry"` // 所属行业
|
|
||||||
Scale string `bson:"scale" json:"scale"` // 公司规模
|
Scale string `bson:"scale" json:"scale"` // 公司规模
|
||||||
|
|
||||||
// 证件信息
|
// 证件信息
|
||||||
@@ -36,8 +36,8 @@ type Advertiser struct {
|
|||||||
SignDate int64 `bson:"signDate" json:"signDate"` // 签约日期
|
SignDate int64 `bson:"signDate" json:"signDate"` // 签约日期
|
||||||
ExpireDate int64 `bson:"expireDate" json:"expireDate"` // 到期日期
|
ExpireDate int64 `bson:"expireDate" json:"expireDate"` // 到期日期
|
||||||
|
|
||||||
// 状态信息
|
// 审核状态
|
||||||
Status string `bson:"status" json:"status"` // 广告主状态:待审核、审核中、已通过、已拒绝、已冻结
|
AuditStatus string `bson:"auditStatus" json:"auditStatus"` // 广告主状态:待审核、审核中、已通过、已拒绝、已冻结
|
||||||
AuditReason string `bson:"auditReason" json:"auditReason"` // 审核不通过原因
|
AuditReason string `bson:"auditReason" json:"auditReason"` // 审核不通过原因
|
||||||
AuditTime int64 `bson:"auditTime" json:"auditTime"` // 审核时间
|
AuditTime int64 `bson:"auditTime" json:"auditTime"` // 审核时间
|
||||||
AuditBy string `bson:"auditBy" json:"auditBy"` // 审核人
|
AuditBy string `bson:"auditBy" json:"auditBy"` // 审核人
|
||||||
@@ -45,5 +45,9 @@ type Advertiser struct {
|
|||||||
// 系统信息
|
// 系统信息
|
||||||
AccountBalance int64 `bson:"accountBalance" json:"accountBalance"` // 账户余额(分)
|
AccountBalance int64 `bson:"accountBalance" json:"accountBalance"` // 账户余额(分)
|
||||||
CreditLimit int64 `bson:"creditLimit" json:"creditLimit"` // 授信额度(分)
|
CreditLimit int64 `bson:"creditLimit" json:"creditLimit"` // 授信额度(分)
|
||||||
Remark string `bson:"remark" json:"remark"` // 备注
|
}
|
||||||
|
|
||||||
|
// GetCollectionName 获取集合名称
|
||||||
|
func (a *Advertiser) GetCollectionName() string {
|
||||||
|
return AdvertiserCollection
|
||||||
}
|
}
|
||||||
|
|||||||
32
model/entity/app_platform_config.go
Normal file
32
model/entity/app_platform_config.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -8,40 +8,47 @@ const ApplicationCollection = "application"
|
|||||||
|
|
||||||
// Application 应用实体
|
// Application 应用实体
|
||||||
type Application struct {
|
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"` // 应用名称
|
Name string `bson:"name" json:"name"` // 应用名称
|
||||||
Code string `json:"code"` // 应用编码
|
Code string `bson:"code" json:"code"` // 应用编码
|
||||||
Description string `json:"description"` // 应用描述
|
Description string `bson:"description" json:"description"` // 应用描述
|
||||||
Icon string `json:"icon"` // 应用图标
|
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
|
Config string `bson:"config" json:"config"` // 应用配置(JSON字符串)
|
||||||
PackageName string `json:"packageName"` // 包名(Android)/Bundle ID(iOS)
|
Permissions string `bson:"permissions" json:"permissions"` // 权限配置(JSON字符串)
|
||||||
AppStoreURL string `json:"appStoreUrl"` // 应用商店链接
|
|
||||||
|
|
||||||
// 配置信息
|
// 应用分类和标签
|
||||||
Categories []string `json:"categories"` // 应用分类
|
Categories []string `bson:"categories" json:"categories"` // 应用分类
|
||||||
Tags []string `json:"tags"` // 应用标签
|
Tags []string `bson:"tags" json:"tags"` // 标签
|
||||||
Config string `json:"config"` // 应用配置(JSON格式)
|
AdTypes []string `bson:"adTypes" json:"adTypes"` // 支持的广告类型
|
||||||
AdTypes []string `json:"adTypes"` // 支持的广告类型
|
|
||||||
|
|
||||||
// 状态信息
|
// 回调配置
|
||||||
Status string `json:"status"` // 状态:active, inactive, audit
|
CallbackURL string `bson:"callbackUrl" json:"callbackUrl"` // 回调URL
|
||||||
AuditStatus string `json:"auditStatus"` // 审核状态
|
|
||||||
AuditReason string `json:"auditReason"` // 审核原因
|
|
||||||
|
|
||||||
// 统计信息
|
// 应用特定统计
|
||||||
DailyRequests int64 `json:"dailyRequests"` // 日请求量
|
DailyActiveUsers int64 `bson:"dailyActiveUsers" json:"dailyActiveUsers"` // 日活用户数
|
||||||
MonthlyRequests int64 `json:"monthlyRequests"` // 月请求量
|
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"` // 应用密钥
|
ContactName string `bson:"contactName" json:"contactName"` // 联系人姓名
|
||||||
AppSecret string `json:"appSecret"` // 应用密钥
|
ContactEmail string `bson:"contactEmail" json:"contactEmail"` // 联系邮箱
|
||||||
|
ContactPhone string `bson:"contactPhone" json:"contactPhone"` // 联系电话
|
||||||
// 回调信息
|
}
|
||||||
CallbackURL string `json:"callbackUrl"` // 回调URL
|
|
||||||
|
// GetCollectionName 获取集合名称
|
||||||
Remark string `json:"remark"` // 备注
|
func (a *Application) GetCollectionName() string {
|
||||||
|
return ApplicationCollection
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,18 +6,21 @@ import (
|
|||||||
|
|
||||||
const CidRequestCollection = "cid_request"
|
const CidRequestCollection = "cid_request"
|
||||||
|
|
||||||
// CidRequest CID请求实体
|
// CidRequest CID请求实体(合并后的统一版本)
|
||||||
type CidRequest struct {
|
type CidRequest struct {
|
||||||
do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, IsDeleted
|
do.MongoBaseDO `bson:",inline" json:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, IsDeleted
|
||||||
UserID string `bson:"userId" json:"userId"`
|
|
||||||
// 请求信息
|
// 请求基础信息
|
||||||
RequestID string `bson:"requestId" json:"requestId"` // 请求唯一ID
|
RequestID string `bson:"requestId" json:"requestId"` // 请求唯一ID
|
||||||
SessionID string `bson:"sessionId" json:"sessionId"` // 会话ID
|
SessionID string `bson:"sessionId" json:"sessionId"` // 会话ID
|
||||||
|
UserID string `bson:"userId" json:"userId"` // 用户ID
|
||||||
|
|
||||||
|
// 网络信息
|
||||||
IPAddress string `bson:"ipAddress" json:"ipAddress"` // IP地址
|
IPAddress string `bson:"ipAddress" json:"ipAddress"` // IP地址
|
||||||
UserAgent string `bson:"userAgent" json:"userAgent"` // 用户代理
|
UserAgent string `bson:"userAgent" json:"userAgent"` // 用户代理
|
||||||
Referer string `bson:"referer" json:"referer"` // 来源页面
|
Referer string `bson:"referer" json:"referer"` // 来源页面
|
||||||
|
|
||||||
// 广告位信息
|
// 广告位信息(使用内联结构)
|
||||||
PositionCode string `bson:"positionCode" json:"positionCode"` // 广告位编码
|
PositionCode string `bson:"positionCode" json:"positionCode"` // 广告位编码
|
||||||
PositionSize string `bson:"positionSize" json:"positionSize"` // 广告位尺寸
|
PositionSize string `bson:"positionSize" json:"positionSize"` // 广告位尺寸
|
||||||
PositionFormat string `bson:"positionFormat" json:"positionFormat"` // 广告位格式
|
PositionFormat string `bson:"positionFormat" json:"positionFormat"` // 广告位格式
|
||||||
@@ -30,15 +33,17 @@ type CidRequest struct {
|
|||||||
PageKeywords []string `bson:"pageKeywords" json:"pageKeywords"` // 页面关键词
|
PageKeywords []string `bson:"pageKeywords" json:"pageKeywords"` // 页面关键词
|
||||||
PageTags map[string]string `bson:"pageTags" json:"pageTags"` // 页面标签
|
PageTags map[string]string `bson:"pageTags" json:"pageTags"` // 页面标签
|
||||||
|
|
||||||
// 用户信息
|
// 用户上下文信息(使用统一版本)
|
||||||
UserContext *UserContext `bson:"userContext" json:"userContext"` // 用户上下文
|
UserContext *UnifiedUserContext `bson:"userContext" json:"userContext"` // 用户上下文
|
||||||
DeviceInfo *DeviceInfo `bson:"deviceInfo" json:"deviceInfo"` // 设备信息
|
DeviceInfo *DeviceInfo `bson:"deviceInfo" json:"deviceInfo"` // 设备信息
|
||||||
LocationInfo *LocationInfo `bson:"locationInfo" json:"locationInfo"` // 位置信息
|
LocationInfo *UnifiedLocationInfo `bson:"locationInfo" json:"locationInfo"` // 位置信息
|
||||||
TemporalInfo *TemporalInfo `bson:"temporalInfo" json:"temporalInfo"` // 时间信息
|
TemporalInfo *UnifiedTemporalInfo `bson:"temporalInfo" json:"temporalInfo"` // 时间信息
|
||||||
|
|
||||||
// 请求参数
|
// 请求参数(使用合并版本)
|
||||||
RequestParams *RequestParams `bson:"requestParams" json:"requestParams"` // 请求参数
|
RequestParams *RequestParams `bson:"requestParams" json:"requestParams"` // 请求参数
|
||||||
TargetingRules *TargetingRules `bson:"targetingRules" json:"targetingRules"` // 定向规则
|
|
||||||
|
// 定向规则(使用统一的定向结构)
|
||||||
|
TargetingRules *UnifiedTargeting `bson:"targetingRules" json:"targetingRules"` // 定向规则
|
||||||
|
|
||||||
// 策略配置
|
// 策略配置
|
||||||
StrategyConfig *StrategyConfig `bson:"strategyConfig" json:"strategyConfig"` // 策略配置
|
StrategyConfig *StrategyConfig `bson:"strategyConfig" json:"strategyConfig"` // 策略配置
|
||||||
@@ -70,14 +75,111 @@ type CidRequest struct {
|
|||||||
Version string `bson:"version" json:"version"` // 系统版本
|
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 {
|
type CidResponse struct {
|
||||||
Ads []Ad `bson:"ads" json:"ads"` // 广告列表
|
Ads []Ad `bson:"ads" json:"ads"` // 广告列表
|
||||||
TrackingInfo *TrackingInfo `bson:"trackingInfo" json:"trackingInfo"` // 跟踪信息
|
TrackingInfo *TrackingInfo `bson:"trackingInfo" json:"trackingInfo"` // 跟踪信息
|
||||||
Metadata *ResponseMetadata `bson:"metadata" json:"metadata"` // 响应元数据
|
Metadata *ResponseMetadata `bson:"metadata" json:"metadata"` // 响应元数据
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ad 广告
|
// Ad 广告结构(合并版本)
|
||||||
type Ad struct {
|
type Ad struct {
|
||||||
ID string `bson:"id" json:"id"` // 广告ID
|
ID string `bson:"id" json:"id"` // 广告ID
|
||||||
AdSource string `bson:"adSource" json:"adSource"` // 广告源
|
AdSource string `bson:"adSource" json:"adSource"` // 广告源
|
||||||
@@ -105,7 +207,7 @@ type Ad struct {
|
|||||||
Score float64 `bson:"score" json:"score"` // 评分
|
Score float64 `bson:"score" json:"score"` // 评分
|
||||||
}
|
}
|
||||||
|
|
||||||
// TrackingInfo 跟踪信息
|
// TrackingInfo 跟踪信息(合并版本)
|
||||||
type TrackingInfo struct {
|
type TrackingInfo struct {
|
||||||
ImpressionURLs []string `bson:"impressionUrls" json:"impressionUrls"` // 展示跟踪URL
|
ImpressionURLs []string `bson:"impressionUrls" json:"impressionUrls"` // 展示跟踪URL
|
||||||
ClickURLs []string `bson:"clickUrls" json:"clickUrls"` // 点击跟踪URL
|
ClickURLs []string `bson:"clickUrls" json:"clickUrls"` // 点击跟踪URL
|
||||||
@@ -115,7 +217,7 @@ type TrackingInfo struct {
|
|||||||
BeaconURLs []string `bson:"beaconUrls" json:"beaconUrls"` // 信标URL
|
BeaconURLs []string `bson:"beaconUrls" json:"beaconUrls"` // 信标URL
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResponseMetadata 响应元数据
|
// ResponseMetadata 响应元数据(合并版本)
|
||||||
type ResponseMetadata struct {
|
type ResponseMetadata struct {
|
||||||
TotalAvailableAds int `bson:"totalAvailableAds" json:"totalAvailableAds"` // 总可用广告数
|
TotalAvailableAds int `bson:"totalAvailableAds" json:"totalAvailableAds"` // 总可用广告数
|
||||||
SelectedAds int `bson:"selectedAds" json:"selectedAds"` // 选择的广告数
|
SelectedAds int `bson:"selectedAds" json:"selectedAds"` // 选择的广告数
|
||||||
@@ -132,7 +234,7 @@ type ResponseMetadata struct {
|
|||||||
AdSourcesUsed []string `bson:"adSourcesUsed" json:"adSourcesUsed"` // 使用的广告源
|
AdSourcesUsed []string `bson:"adSourcesUsed" json:"adSourcesUsed"` // 使用的广告源
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdSourceResponse 广告源响应
|
// AdSourceResponse 广告源响应(合并版本)
|
||||||
type AdSourceResponse struct {
|
type AdSourceResponse struct {
|
||||||
AdSource string `bson:"adSource" json:"adSource"` // 广告源名称
|
AdSource string `bson:"adSource" json:"adSource"` // 广告源名称
|
||||||
Status string `bson:"status" json:"status"` // 响应状态:success、timeout、error
|
Status string `bson:"status" json:"status"` // 响应状态:success、timeout、error
|
||||||
@@ -147,164 +249,3 @@ type AdSourceResponse struct {
|
|||||||
TotalRevenue int64 `bson:"totalRevenue" json:"totalRevenue"` // 总收入(分)
|
TotalRevenue int64 `bson:"totalRevenue" json:"totalRevenue"` // 总收入(分)
|
||||||
AverageBidAmount int64 `bson:"averageBidAmount" json:"averageBidAmount"` // 平均出价(分)
|
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"` // 自定义设置
|
|
||||||
}
|
|
||||||
|
|||||||
157
model/entity/config.go
Normal file
157
model/entity/config.go
Normal file
@@ -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"` // 排除的关键词
|
||||||
|
}
|
||||||
69
model/entity/platform_delivery_rule.go
Normal file
69
model/entity/platform_delivery_rule.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -8,7 +8,8 @@ const StrategyCollection = "strategy"
|
|||||||
|
|
||||||
// Strategy 匹配策略表
|
// Strategy 匹配策略表
|
||||||
type Strategy struct {
|
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"` // 策略名称
|
Name string `bson:"name" json:"name"` // 策略名称
|
||||||
@@ -18,6 +19,10 @@ type Strategy struct {
|
|||||||
SourceWeights string `bson:"sourceWeights" json:"sourceWeights"` // 广告源权重 (JSON格式)
|
SourceWeights string `bson:"sourceWeights" json:"sourceWeights"` // 广告源权重 (JSON格式)
|
||||||
MaxAdsPerReq int `bson:"maxAdsPerReq" json:"maxAdsPerReq"` // 每次请求最大广告数
|
MaxAdsPerReq int `bson:"maxAdsPerReq" json:"maxAdsPerReq"` // 每次请求最大广告数
|
||||||
MaxReqPerHour int `bson:"maxReqPerHour" json:"maxReqPerHour"` // 每小时最大请求次数
|
MaxReqPerHour int `bson:"maxReqPerHour" json:"maxReqPerHour"` // 每小时最大请求次数
|
||||||
Priority int `bson:"priority" json:"priority"` // 优先级
|
Priority int `bson:"priority" json:"priority"` // 优先级(用于策略排序)
|
||||||
Status string `bson:"status" json:"status"` // 状态: active, inactive
|
}
|
||||||
|
|
||||||
|
// GetCollectionName 获取集合名称
|
||||||
|
func (s *Strategy) GetCollectionName() string {
|
||||||
|
return StrategyCollection
|
||||||
}
|
}
|
||||||
|
|||||||
68
model/entity/targeting.go
Normal file
68
model/entity/targeting.go
Normal file
@@ -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"` // 时区
|
||||||
|
}
|
||||||
@@ -28,13 +28,6 @@ func (s *adPosition) Add(ctx context.Context, req *dto.AddAdPositionReq) (res *d
|
|||||||
adPosition.UpdatedAt = now
|
adPosition.UpdatedAt = now
|
||||||
adPosition.IsDeleted = false
|
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 {
|
if err = dao.AdPosition.Insert(ctx, adPosition); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -121,62 +114,5 @@ func (s *adPosition) MatchAd(ctx context.Context, positionCode string, userInfo
|
|||||||
|
|
||||||
// UpdateAdPositionStatistics 更新广告位统计
|
// UpdateAdPositionStatistics 更新广告位统计
|
||||||
func (s *adPosition) UpdateAdPositionStatistics(ctx context.Context, id string, impressions, clicks, revenue int64) (err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,11 +40,14 @@ func (s *adSourceService) CreateAdSource(ctx context.Context, req *dto.CreateAdS
|
|||||||
Code: req.Code,
|
Code: req.Code,
|
||||||
Provider: req.Provider,
|
Provider: req.Provider,
|
||||||
Type: req.Type,
|
Type: req.Type,
|
||||||
APIEndpoint: req.APIEndpoint,
|
APIConfig: entity.APIConfig{
|
||||||
Status: "active", // 默认状态
|
Endpoint: req.APIEndpoint,
|
||||||
Priority: 1, // 默认优先级
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置状态
|
||||||
|
adSource.Status = "active" // 默认状态
|
||||||
|
|
||||||
return dao.AdSource.Create(ctx, adSource)
|
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
|
updateData.Name = req.Name
|
||||||
}
|
}
|
||||||
if req.APIEndpoint != "" {
|
if req.APIEndpoint != "" {
|
||||||
updateData.APIEndpoint = req.APIEndpoint
|
updateData.APIConfig.Endpoint = req.APIEndpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
return dao.AdSource.UpdateFields(ctx, id, updateData)
|
return dao.AdSource.UpdateFields(ctx, id, updateData)
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -30,12 +30,6 @@ func (s *advertisement) Add(ctx context.Context, req *dto.AddAdvertisementReq) (
|
|||||||
// 设置初始状态
|
// 设置初始状态
|
||||||
advertisement.Status = "待审核"
|
advertisement.Status = "待审核"
|
||||||
|
|
||||||
// 初始化统计字段
|
|
||||||
advertisement.Impressions = 0
|
|
||||||
advertisement.Clicks = 0
|
|
||||||
advertisement.Conversions = 0
|
|
||||||
advertisement.Cost = 0
|
|
||||||
|
|
||||||
if err = dao.Advertisement.Insert(ctx, advertisement); err != nil {
|
if err = dao.Advertisement.Insert(ctx, advertisement); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -89,75 +83,5 @@ func (s *advertisement) List(ctx context.Context, req *dto.ListAdvertisementReq)
|
|||||||
|
|
||||||
// UpdateAdStatistics 更新广告统计
|
// UpdateAdStatistics 更新广告统计
|
||||||
func (s *advertisement) UpdateAdStatistics(ctx context.Context, id string, impressions, clicks, conversions int64, cost int64) (err error) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,12 +45,14 @@ func (s *applicationService) CreateApplication(ctx context.Context, req *dto.Cre
|
|||||||
Categories: req.Categories,
|
Categories: req.Categories,
|
||||||
Tags: req.Tags,
|
Tags: req.Tags,
|
||||||
AdTypes: req.AdTypes,
|
AdTypes: req.AdTypes,
|
||||||
Status: "active",
|
|
||||||
AppKey: appKey,
|
AppKey: appKey,
|
||||||
AppSecret: appSecret,
|
AppSecret: appSecret,
|
||||||
CallbackURL: req.CallbackURL,
|
CallbackURL: req.CallbackURL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置状态
|
||||||
|
application.Status = "active"
|
||||||
|
|
||||||
return dao.Application.Create(ctx, application)
|
return dao.Application.Create(ctx, application)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -48,9 +48,11 @@ func (s *strategyService) CreateStrategy(ctx context.Context, req *dto.CreateStr
|
|||||||
SourceWeights: string(weightsJson),
|
SourceWeights: string(weightsJson),
|
||||||
MaxAdsPerReq: req.MaxAdsPerReq,
|
MaxAdsPerReq: req.MaxAdsPerReq,
|
||||||
Priority: req.Priority,
|
Priority: req.Priority,
|
||||||
Status: req.Status,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置状态
|
||||||
|
strategy.Status = req.Status
|
||||||
|
|
||||||
_, err = dao.Strategy.Create(ctx, strategy)
|
_, err = dao.Strategy.Create(ctx, strategy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
@@ -101,9 +103,11 @@ func (s *strategyService) UpdateStrategy(ctx context.Context, req *dto.UpdateStr
|
|||||||
SourceWeights: string(weightsJson),
|
SourceWeights: string(weightsJson),
|
||||||
MaxAdsPerReq: req.MaxAdsPerReq,
|
MaxAdsPerReq: req.MaxAdsPerReq,
|
||||||
Priority: req.Priority,
|
Priority: req.Priority,
|
||||||
Status: req.Status,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 设置状态
|
||||||
|
strategy.Status = req.Status
|
||||||
|
|
||||||
return dao.Strategy.Update(ctx, strategy)
|
return dao.Strategy.Update(ctx, strategy)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user