diff --git a/cidService b/cidService deleted file mode 100755 index a53eaa6..0000000 Binary files a/cidService and /dev/null differ diff --git a/config.yml b/config.yml index 285378f..1ea07d4 100644 --- a/config.yml +++ b/config.yml @@ -1,11 +1,18 @@ server: - address : ":3002" + address : ":3004" name: "cidService" jwt: secret: "abcdefghijklmnopqrstuvwxyz" rate: limit: 200 burst: 300 + +# 租户限流配置 +tenantRateLimit: + enabled: true # 是否启用租户限流 + requestsPerHour: 3600 # 每小时最大请求数 + window: 3600 # 时间窗口(秒) + burst: 100 # 突发请求数 mongo: logger: level: "all" diff --git a/config/config.example.yaml b/config/config.example.yaml deleted file mode 100644 index df2c9e5..0000000 --- a/config/config.example.yaml +++ /dev/null @@ -1,86 +0,0 @@ -# CID服务商项目配置文件示例 - -# 服务器配置 -server: - address: ":8080" - dumpRouterMap: false - -# 数据库配置 -database: - default: - type: "mysql" - host: "localhost" - port: "3306" - user: "root" - pass: "" - name: "cid_service" - charset: "utf8mb4" - timezone: "Local" - maxIdle: 10 - maxOpen: 100 - maxLifetime: 30 - -# Redis配置 -redis: - default: - address: "127.0.0.1:6379" - db: 1 - pass: "" - -# JWT配置 -jwt: - secret: "your-secret-key" - expire: 7200 # 2小时 - -# 日志配置 -logger: - level: "info" - stdout: true - path: "./logs" - file: "cid-service.log" - -# 广告源配置 -ad_sources: - google: - api_key: "your-google-ads-api-key" - customer_id: "your-customer-id" - developer_token: "your-developer-token" - facebook: - app_id: "your-facebook-app-id" - app_secret: "your-facebook-app-secret" - access_token: "your-facebook-access-token" - ad_account_id: "your-ad-account-id" - -# 策略配置 -strategy: - default: - basic: - min_conversion: 0.01 - max_conversion: 0.05 - source_weights: - self: 100 - google: 0 - facebook: 0 - max_ads_per_req: 3 - standard: - min_conversion: 0.05 - max_conversion: 0.15 - source_weights: - self: 70 - google: 20 - facebook: 10 - max_ads_per_req: 5 - premium: - min_conversion: 0.15 - max_conversion: 1.0 - source_weights: - self: 40 - google: 30 - facebook: 30 - max_ads_per_req: 10 - -# 监控配置 -monitoring: - enable_metrics: true - enable_tracing: true - jaeger_endpoint: "http://localhost:14268/api/traces" \ No newline at end of file diff --git a/consts/consts.go b/consts/consts.go new file mode 100644 index 0000000..12e7097 --- /dev/null +++ b/consts/consts.go @@ -0,0 +1,93 @@ +package consts + +// Redis键前缀 +const ( + AdRequestLimitKeyPrefix = "ad_request_limit:" // 广告请求限流键前缀 + RateLimitKeyPrefix = "rate_limit:" // 通用限流键前缀 + SessionCacheKeyPrefix = "session_cache:" // 会话缓存键前缀 +) + +// 广告源状态 +const ( + AdSourceStatusActive = "active" // 活跃 + AdSourceStatusInactive = "inactive" // 非活跃 + AdSourceStatusMaintenance = "maintenance" // 维护中 +) + +// 广告源健康状态 +const ( + AdSourceHealthHealthy = "healthy" // 健康 + AdSourceHealthDegraded = "degraded" // 降级 + AdSourceHealthUnhealthy = "unhealthy" // 不健康 +) + +// 广告源类型 +const ( + AdSourceTypeSelf = "self" // 自营 + AdSourceTypeThirdParty = "third_party" // 第三方 + AdSourceTypeExchange = "exchange" // 广告交易平台 +) + +// 广告源提供商 +const ( + AdSourceProviderGoogle = "google" // Google + AdSourceProviderBaidu = "baidu" // 百度 + AdSourceProviderTencent = "tencent" // 腾讯 + AdSourceProviderSelf = "self" // 自营 +) + +// 认证类型 +const ( + AuthTypeAPIKey = "api_key" // API密钥 + AuthTypeOAuth = "oauth" // OAuth + AuthTypeBasic = "basic" // Basic认证 +) + +// 竞价类型 +const ( + BiddingTypeCPM = "cpm" // 千次展示成本 + BiddingTypeCPC = "cpc" // 每次点击成本 + BiddingTypeCPA = "cpa" // 每次行动成本 + BiddingTypeRTB = "rtb" // 实时竞价 +) + +// 广告格式类型 +const ( + AdFormatTypeBanner = "banner" // 横幅广告 + AdFormatTypeVideo = "video" // 视频广告 + AdFormatTypeNative = "native" // 原生广告 + AdFormatTypeInterstitial = "interstitial" // 插屏广告 +) + +// 计费模式 +const ( + BillingModelCPM = "cpm" // 千次展示成本 + BillingModelCPC = "cpc" // 每次点击成本 + BillingModelCPA = "cpa" // 每次行动成本 + BillingModelRevShare = "rev_share" // 收入分成 +) + +// 支付条款 +const ( + PaymentTermsNet30 = "net_30" // 30天 + PaymentTermsNet60 = "net_60" // 60天 + PaymentTermsNet90 = "net_90" // 90天 +) + +// 默认配置值 +const ( + DefaultTimeout = 5000 // 默认超时时间(毫秒) + DefaultRetryCount = 3 // 默认重试次数 + DefaultPriority = 1 // 默认优先级 +) + +// 错误消息 +const ( + ErrAdSourceNotFound = "广告源不存在" + ErrAdSourceNameExists = "广告源名称已存在" + ErrAdSourceCodeExists = "广告源编码已存在" + ErrAdSourceInactive = "广告源非活跃状态" + ErrAdSourceUnhealthy = "广告源健康状态异常" + ErrRateLimitExceeded = "请求频率超限" + ErrInvalidConfiguration = "配置参数无效" +) diff --git a/consts/redis_key.go b/consts/redis_key.go index 9e5a1eb..c39f6c8 100644 --- a/consts/redis_key.go +++ b/consts/redis_key.go @@ -31,7 +31,6 @@ const ( // 限流键 const ( - AdRequestLimitKeyPrefix = "cid:limit:req:" // 广告请求限流键前缀 ApiRequestLimitKeyPrefix = "cid:limit:api:" // API请求限流键前缀 ) diff --git a/controller/ad_position_controller.go b/controller/ad_position_controller.go index 3bd68d0..313ae3c 100644 --- a/controller/ad_position_controller.go +++ b/controller/ad_position_controller.go @@ -3,50 +3,50 @@ package controller import ( "context" - "cidService/model/dto" - "cidService/service" + "cidservice/model/dto" + "cidservice/service" "gitee.com/red-future---jilin-g/common/http" ) -type cAdPosition struct{} +type adPosition struct{} -var AdPosition = &cAdPosition{} +var AdPosition = &adPosition{} // Add 添加广告位 -func (c *cAdPosition) Add(ctx context.Context, req *dto.AddAdPositionReq) (res *dto.AddAdPositionRes, err error) { +func (c *adPosition) Add(ctx context.Context, req *dto.AddAdPositionReq) (res *dto.AddAdPositionRes, err error) { return service.AdPosition.Add(ctx, req) } // Update 更新广告位 -func (c *cAdPosition) Update(ctx context.Context, req *dto.UpdateAdPositionReq) (res *http.ResponseEmpty, err error) { +func (c *adPosition) Update(ctx context.Context, req *dto.UpdateAdPositionReq) (res *http.ResponseEmpty, err error) { err = service.AdPosition.Update(ctx, req) return } // UpdateStatus 更新广告位状态 -func (c *cAdPosition) UpdateStatus(ctx context.Context, req *dto.UpdateAdPositionStatusReq) (res *http.ResponseEmpty, err error) { +func (c *adPosition) UpdateStatus(ctx context.Context, req *dto.UpdateAdPositionStatusReq) (res *http.ResponseEmpty, err error) { err = service.AdPosition.UpdateStatus(ctx, req) return } // GetOne 获取广告位详情 -func (c *cAdPosition) GetOne(ctx context.Context, req *dto.GetAdPositionReq) (res *dto.GetAdPositionRes, err error) { +func (c *adPosition) GetOne(ctx context.Context, req *dto.GetAdPositionReq) (res *dto.GetAdPositionRes, err error) { return service.AdPosition.GetOne(ctx, req) } // List 获取广告位列表 -func (c *cAdPosition) List(ctx context.Context, req *dto.ListAdPositionReq) (res *dto.ListAdPositionRes, err error) { +func (c *adPosition) List(ctx context.Context, req *dto.ListAdPositionReq) (res *dto.ListAdPositionRes, err error) { return service.AdPosition.List(ctx, req) } // GetStatistics 获取广告位统计数据 -func (c *cAdPosition) GetStatistics(ctx context.Context, req *dto.GetAdPositionStatisticsReq) (res *dto.GetAdPositionStatisticsRes, err error) { +func (c *adPosition) GetStatistics(ctx context.Context, req *dto.GetAdPositionStatisticsReq) (res *dto.GetAdPositionStatisticsRes, err error) { return service.AdPosition.GetStatistics(ctx, req) } // GetAvailableAdPositions 获取可用的广告位列表 -func (c *cAdPosition) GetAvailableAdPositions(ctx context.Context, req *dto.GetAvailableAdPositionsReq) (res *dto.GetAvailableAdPositionsRes, err error) { +func (c *adPosition) GetAvailableAdPositions(ctx context.Context, req *dto.GetAvailableAdPositionsReq) (res *dto.GetAvailableAdPositionsRes, err error) { list, err := service.AdPosition.GetAvailableAdPositions(ctx) if err != nil { return nil, err @@ -58,7 +58,7 @@ func (c *cAdPosition) GetAvailableAdPositions(ctx context.Context, req *dto.GetA } // MatchAd 匹配广告 -func (c *cAdPosition) MatchAd(ctx context.Context, req *dto.MatchAdReq) (res *dto.MatchAdRes, err error) { +func (c *adPosition) MatchAd(ctx context.Context, req *dto.MatchAdReq) (res *dto.MatchAdRes, err error) { ad, err := service.AdPosition.MatchAd(ctx, req.PositionCode, req.UserInfo) if err != nil { return nil, err diff --git a/controller/ad_source_controller.go b/controller/ad_source_controller.go index 57fa518..1b458d5 100644 --- a/controller/ad_source_controller.go +++ b/controller/ad_source_controller.go @@ -4,19 +4,20 @@ import ( "context" "strconv" - "cidService/model/dto" - "cidService/service" + "cidservice/model/dto" + "cidservice/service" + "github.com/gogf/gf/v2/errors/gerror" ) var ( - AdSource = cAdSource{} + AdSource = adSource{} ) -type cAdSource struct{} +type adSource struct{} // Create 创建广告源 -func (c *cAdSource) Create(ctx context.Context, req *dto.CreateAdSourceReq) (res *dto.GetAdSourceRes, err error) { +func (c *adSource) Create(ctx context.Context, req *dto.CreateAdSourceReq) (res *dto.GetAdSourceRes, err error) { id, err := service.AdSource.CreateAdSource(ctx, req) if err != nil { return nil, err @@ -33,7 +34,7 @@ func (c *cAdSource) Create(ctx context.Context, req *dto.CreateAdSourceReq) (res } // Update 更新广告源 -func (c *cAdSource) Update(ctx context.Context, req *dto.UpdateAdSourceReq) (res *dto.GetAdSourceRes, err error) { +func (c *adSource) Update(ctx context.Context, req *dto.UpdateAdSourceReq) (res *dto.GetAdSourceRes, err error) { id, err := strconv.ParseInt(req.Id, 10, 64) if err != nil { return nil, gerror.New("无效的广告源ID") @@ -58,7 +59,7 @@ func (c *cAdSource) Update(ctx context.Context, req *dto.UpdateAdSourceReq) (res } // Delete 删除广告源 -func (c *cAdSource) Delete(ctx context.Context, req *dto.DeleteAdSourceReq) (res *dto.DeleteAdSourceRes, err error) { +func (c *adSource) Delete(ctx context.Context, req *dto.DeleteAdSourceReq) (res *dto.DeleteAdSourceRes, err error) { id, err := strconv.ParseInt(req.Id, 10, 64) if err != nil { return nil, gerror.New("无效的广告源ID") @@ -78,7 +79,7 @@ func (c *cAdSource) Delete(ctx context.Context, req *dto.DeleteAdSourceReq) (res } // GetByID 根据ID获取广告源 -func (c *cAdSource) GetByID(ctx context.Context, req *dto.GetAdSourceReq) (res *dto.GetAdSourceRes, err error) { +func (c *adSource) GetByID(ctx context.Context, req *dto.GetAdSourceReq) (res *dto.GetAdSourceRes, err error) { id, err := strconv.ParseInt(req.Id, 10, 64) if err != nil { return nil, gerror.New("无效的广告源ID") @@ -98,7 +99,7 @@ func (c *cAdSource) GetByID(ctx context.Context, req *dto.GetAdSourceReq) (res * } // GetList 获取广告源列表 -func (c *cAdSource) GetList(ctx context.Context, req *dto.ListAdSourceReq) (res *dto.ListAdSourceRes, err error) { +func (c *adSource) GetList(ctx context.Context, req *dto.ListAdSourceReq) (res *dto.ListAdSourceRes, err error) { adSources, err := service.AdSource.GetAvailableSources(ctx) if err != nil { return nil, err diff --git a/controller/ad_statistics_controller.go b/controller/ad_statistics_controller.go index 42c05f5..fb4aaa7 100644 --- a/controller/ad_statistics_controller.go +++ b/controller/ad_statistics_controller.go @@ -3,26 +3,26 @@ package controller import ( "context" - "cidService/model/dto" - "cidService/service" + "cidservice/model/dto" + "cidservice/service" ) -type cAdStatistics struct{} +type adStatistics struct{} -var AdStatistics = &cAdStatistics{} +var AdStatistics = &adStatistics{} // GetStatistics 获取统计数据 -func (c *cAdStatistics) GetStatistics(ctx context.Context, req *dto.GetAdStatisticsReq) (res *dto.GetAdStatisticsRes, err error) { +func (c *adStatistics) GetStatistics(ctx context.Context, req *dto.GetAdStatisticsReq) (res *dto.GetAdStatisticsRes, err error) { return service.AdStatistics.GetStatistics(ctx, req) } // GetDashboard 获取仪表盘数据 -func (c *cAdStatistics) GetDashboard(ctx context.Context, req *dto.GetDashboardReq) (res *dto.GetDashboardRes, err error) { +func (c *adStatistics) GetDashboard(ctx context.Context, req *dto.GetDashboardReq) (res *dto.GetDashboardRes, err error) { return service.AdStatistics.GetDashboard(ctx, req) } // GenerateDailyStatistics 生成每日统计数据 -func (c *cAdStatistics) GenerateDailyStatistics(ctx context.Context, req *dto.GenerateDailyStatisticsReq) (res *dto.GenerateDailyStatisticsRes, err error) { +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 diff --git a/controller/advertisement_controller.go b/controller/advertisement_controller.go index d206a71..3009b26 100644 --- a/controller/advertisement_controller.go +++ b/controller/advertisement_controller.go @@ -1,51 +1,51 @@ package controller import ( - "cidService/model/dto" - "cidService/service" + "cidservice/model/dto" + "cidservice/service" "context" "gitee.com/red-future---jilin-g/common/http" ) -type cAdvertisement struct{} +type advertisement struct{} -var Advertisement = &cAdvertisement{} +var Advertisement = &advertisement{} // Add 添加广告 -func (c *cAdvertisement) Add(ctx context.Context, req *dto.AddAdvertisementReq) (res *dto.AddAdvertisementRes, err error) { +func (c *advertisement) Add(ctx context.Context, req *dto.AddAdvertisementReq) (res *dto.AddAdvertisementRes, err error) { return service.Advertisement.Add(ctx, req) } // Update 更新广告 -func (c *cAdvertisement) Update(ctx context.Context, req *dto.UpdateAdvertisementReq) (res *http.ResponseEmpty, err error) { +func (c *advertisement) Update(ctx context.Context, req *dto.UpdateAdvertisementReq) (res *http.ResponseEmpty, err error) { err = service.Advertisement.Update(ctx, req) return } // UpdateStatus 更新广告状态 -func (c *cAdvertisement) UpdateStatus(ctx context.Context, req *dto.UpdateAdStatusReq) (res *http.ResponseEmpty, err error) { +func (c *advertisement) UpdateStatus(ctx context.Context, req *dto.UpdateAdStatusReq) (res *http.ResponseEmpty, err error) { err = service.Advertisement.UpdateStatus(ctx, req) return } // Audit 审核广告 -func (c *cAdvertisement) Audit(ctx context.Context, req *dto.AuditAdvertisementReq) (res *http.ResponseEmpty, err error) { +func (c *advertisement) Audit(ctx context.Context, req *dto.AuditAdvertisementReq) (res *http.ResponseEmpty, err error) { err = service.Advertisement.Audit(ctx, req) return } // GetOne 获取广告详情 -func (c *cAdvertisement) GetOne(ctx context.Context, req *dto.GetAdvertisementReq) (res *dto.GetAdvertisementRes, err error) { +func (c *advertisement) GetOne(ctx context.Context, req *dto.GetAdvertisementReq) (res *dto.GetAdvertisementRes, err error) { return service.Advertisement.GetOne(ctx, req) } // List 获取广告列表 -func (c *cAdvertisement) List(ctx context.Context, req *dto.ListAdvertisementReq) (res *dto.ListAdvertisementRes, err error) { +func (c *advertisement) List(ctx context.Context, req *dto.ListAdvertisementReq) (res *dto.ListAdvertisementRes, err error) { return service.Advertisement.List(ctx, req) } // GetStatistics 获取广告统计数据 -func (c *cAdvertisement) GetStatistics(ctx context.Context, req *dto.GetAdStatisticsForAdvertisementReq) (res *dto.GetAdStatisticsForAdvertisementRes, err error) { +func (c *advertisement) GetStatistics(ctx context.Context, req *dto.GetAdStatisticsForAdvertisementReq) (res *dto.GetAdStatisticsForAdvertisementRes, err error) { return service.Advertisement.GetStatistics(ctx, req) } diff --git a/controller/advertiser_controller.go b/controller/advertiser_controller.go index f163d49..f5024a0 100644 --- a/controller/advertiser_controller.go +++ b/controller/advertiser_controller.go @@ -1,64 +1,64 @@ package controller import ( - "cidService/model/dto" - "cidService/service" + "cidservice/model/dto" + "cidservice/service" "context" "gitee.com/red-future---jilin-g/common/http" ) -type cAdvertiser struct{} +type advertiser struct{} -var Advertiser = &cAdvertiser{} +var Advertiser = &advertiser{} // Add 添加广告主 -func (c *cAdvertiser) Add(ctx context.Context, req *dto.AddAdvertiserReq) (res *dto.AddAdvertiserRes, err error) { +func (c *advertiser) Add(ctx context.Context, req *dto.AddAdvertiserReq) (res *dto.AddAdvertiserRes, err error) { return service.Advertiser.Add(ctx, req) } // Update 更新广告主 -func (c *cAdvertiser) Update(ctx context.Context, req *dto.UpdateAdvertiserReq) (res *http.ResponseEmpty, err error) { +func (c *advertiser) Update(ctx context.Context, req *dto.UpdateAdvertiserReq) (res *http.ResponseEmpty, err error) { err = service.Advertiser.Update(ctx, req) return } // UpdateStatus 更新广告主状态 -func (c *cAdvertiser) UpdateStatus(ctx context.Context, req *dto.UpdateAdvertiserStatusReq) (res *http.ResponseEmpty, err error) { +func (c *advertiser) UpdateStatus(ctx context.Context, req *dto.UpdateAdvertiserStatusReq) (res *http.ResponseEmpty, err error) { err = service.Advertiser.UpdateStatus(ctx, req) return } // Audit 审核广告主 -func (c *cAdvertiser) Audit(ctx context.Context, req *dto.AuditAdvertiserReq) (res *http.ResponseEmpty, err error) { +func (c *advertiser) Audit(ctx context.Context, req *dto.AuditAdvertiserReq) (res *http.ResponseEmpty, err error) { err = service.Advertiser.Audit(ctx, req) return } // Recharge 充值 -func (c *cAdvertiser) Recharge(ctx context.Context, req *dto.RechargeAdvertiserReq) (res *http.ResponseEmpty, err error) { +func (c *advertiser) Recharge(ctx context.Context, req *dto.RechargeAdvertiserReq) (res *http.ResponseEmpty, err error) { err = service.Advertiser.Recharge(ctx, req) return } // UpdateCreditLimit 更新授信额度 -func (c *cAdvertiser) UpdateCreditLimit(ctx context.Context, req *dto.UpdateCreditLimitReq) (res *http.ResponseEmpty, err error) { +func (c *advertiser) UpdateCreditLimit(ctx context.Context, req *dto.UpdateCreditLimitReq) (res *http.ResponseEmpty, err error) { err = service.Advertiser.UpdateCreditLimit(ctx, req) return } // GetOne 获取广告主详情 -func (c *cAdvertiser) GetOne(ctx context.Context, req *dto.GetAdvertiserReq) (res *dto.GetAdvertiserRes, err error) { +func (c *advertiser) GetOne(ctx context.Context, req *dto.GetAdvertiserReq) (res *dto.GetAdvertiserRes, err error) { return service.Advertiser.GetOne(ctx, req) } // List 获取广告主列表 -func (c *cAdvertiser) List(ctx context.Context, req *dto.ListAdvertiserReq) (res *dto.ListAdvertiserRes, err error) { +func (c *advertiser) List(ctx context.Context, req *dto.ListAdvertiserReq) (res *dto.ListAdvertiserRes, err error) { return service.Advertiser.List(ctx, req) } // GetBalance 获取广告主余额 -func (c *cAdvertiser) GetBalance(ctx context.Context, req *dto.GetAdvertiserBalanceReq) (res *dto.GetAdvertiserBalanceRes, err error) { +func (c *advertiser) GetBalance(ctx context.Context, req *dto.GetAdvertiserBalanceReq) (res *dto.GetAdvertiserBalanceRes, err error) { balance, creditLimit, err := service.Advertiser.GetBalance(ctx, req.Id) if err != nil { return nil, err diff --git a/controller/application_controller.go b/controller/application_controller.go new file mode 100644 index 0000000..a4ed698 --- /dev/null +++ b/controller/application_controller.go @@ -0,0 +1,147 @@ +package controller + +import ( + "context" + + "cidservice/model/dto" + "cidservice/service" +) + +var ( + Application = application{} +) + +type application struct{} + +// CreateApplication 创建应用 +func (c *application) CreateApplication(ctx context.Context, req *dto.CreateApplicationReq) (res *dto.CreateApplicationRes, err error) { + id, err := service.Application.CreateApplication(ctx, req) + if err != nil { + return nil, err + } + + return &dto.CreateApplicationRes{ + ID: id, + }, nil +} + +// UpdateApplication 更新应用 +func (c *application) UpdateApplication(ctx context.Context, req *dto.UpdateApplicationReq) (res *dto.UpdateApplicationRes, err error) { + affected, err := service.Application.UpdateApplication(ctx, req.ID, req) + if err != nil { + return nil, err + } + + return &dto.UpdateApplicationRes{ + Success: affected > 0, + }, nil +} + +// GetApplication 获取应用信息 +func (c *application) GetApplication(ctx context.Context, req *dto.GetApplicationReq) (res *dto.GetApplicationRes, err error) { + app, err := service.Application.GetApplicationByID(ctx, req.ID) + if err != nil { + return nil, err + } + + return &dto.GetApplicationRes{ + ID: app.Id, + TenantID: app.TenantId, + Name: app.Name, + Code: app.Code, + Description: app.Description, + Platform: app.Platform, + PackageName: app.PackageName, + AppStoreURL: app.AppStoreURL, + Categories: app.Categories, + Tags: app.Tags, + AdTypes: app.AdTypes, + Status: app.Status, + AppKey: app.AppKey, + CallbackURL: app.CallbackURL, + CreatedAt: app.CreatedAt.Unix(), + UpdatedAt: app.UpdatedAt.Unix(), + }, nil +} + +// ListApplications 获取应用列表 +func (c *application) ListApplications(ctx context.Context, req *dto.ListApplicationsReq) (res *dto.ListApplicationsRes, err error) { + list, total, err := service.Application.GetApplicationsByTenant(ctx, req.TenantID, req.Platform, req.Status, req.Page, req.Size) + if err != nil { + return nil, err + } + + // 转换为响应格式 + appItems := make([]dto.ApplicationItem, len(list)) + for i, app := range list { + appItems[i] = dto.ApplicationItem{ + ID: app.Id, + Name: app.Name, + Code: app.Code, + Description: app.Description, + Platform: app.Platform, + PackageName: app.PackageName, + Categories: app.Categories, + Tags: app.Tags, + AdTypes: app.AdTypes, + Status: app.Status, + DailyRequests: app.DailyRequests, + MonthlyRequests: app.MonthlyRequests, + CreatedAt: app.CreatedAt.Unix(), + } + } + + return &dto.ListApplicationsRes{ + List: appItems, + Total: total, + Page: req.Page, + Size: req.Size, + }, nil +} + +// ResetAPIKeys 重置API密钥 +func (c *application) ResetAPIKeys(ctx context.Context, req *dto.ResetAPIKeysReq) (res *dto.ResetAPIKeysRes, err error) { + appKey, appSecret, err := service.Application.ResetAPIKeys(ctx, req.ID) + if err != nil { + return nil, err + } + + return &dto.ResetAPIKeysRes{ + AppKey: appKey, + AppSecret: appSecret, + }, nil +} + +// ValidateApplication 验证应用权限 +func (c *application) ValidateApplication(ctx context.Context, req *dto.ValidateApplicationReq) (res *dto.ValidateApplicationRes, err error) { + app, err := service.Application.ValidateApplication(ctx, req.AppKey, req.AppSecret) + if err != nil { + return &dto.ValidateApplicationRes{ + Valid: false, + }, nil + } + + tentantName := "" + + return &dto.ValidateApplicationRes{ + Valid: true, + AppID: app.Id, + AppName: app.Name, + TenantID: app.TenantId, + TenantName: tentantName, + Platform: app.Platform, + AdTypes: app.AdTypes, + }, nil +} + +// DeleteApplication 删除应用 +func (c *application) DeleteApplication(ctx context.Context, req *dto.DeleteApplicationReq) (res *dto.DeleteApplicationRes, err error) { + affected, err := service.Application.DeleteApplication(ctx, req.ID) + if err != nil { + return nil, err + } + + return &dto.DeleteApplicationRes{ + Success: affected > 0, + }, nil +} diff --git a/controller/cid_controller.go b/controller/cid_controller.go index 3740428..d5da68f 100644 --- a/controller/cid_controller.go +++ b/controller/cid_controller.go @@ -3,19 +3,20 @@ package controller import ( "context" - "cidService/model/dto" - "cidService/service" + "cidservice/model/dto" + "cidservice/service" + "github.com/gogf/gf/v2/errors/gerror" ) var ( - CID = cCID{} + CID = cid{} ) -type cCID struct{} +type cid struct{} // GenerateCID 生成CID广告 -func (c *cCID) GenerateCID(ctx context.Context, req *dto.GenerateCIDReq) (res *dto.GenerateCIDRes, err error) { +func (c *cid) GenerateCID(ctx context.Context, req *dto.GenerateCIDReq) (res *dto.GenerateCIDRes, err error) { if req == nil { return nil, gerror.New("请求参数不能为空") } @@ -33,7 +34,7 @@ func (c *cCID) GenerateCID(ctx context.Context, req *dto.GenerateCIDReq) (res *d } // GetStatistics 获取CID统计信息 -func (c *cCID) GetStatistics(ctx context.Context, req *dto.GetCIDStatisticsReq) (res *dto.GetCIDStatisticsRes, err error) { +func (c *cid) GetStatistics(ctx context.Context, req *dto.GetCIDStatisticsReq) (res *dto.GetCIDStatisticsRes, err error) { if req == nil { return nil, gerror.New("请求参数不能为空") } @@ -47,7 +48,7 @@ func (c *cCID) GetStatistics(ctx context.Context, req *dto.GetCIDStatisticsReq) } // GetCIDHistory 获取CID历史记录 -func (c *cCID) GetCIDHistory(ctx context.Context, req *dto.GetCIDHistoryReq) (res *dto.GetCIDHistoryRes, err error) { +func (c *cid) GetCIDHistory(ctx context.Context, req *dto.GetCIDHistoryReq) (res *dto.GetCIDHistoryRes, err error) { if req == nil { return nil, gerror.New("请求参数不能为空") } diff --git a/controller/rate_limit_controller.go b/controller/rate_limit_controller.go new file mode 100644 index 0000000..564fcad --- /dev/null +++ b/controller/rate_limit_controller.go @@ -0,0 +1,39 @@ +package controller + +import ( + "context" + + "cidservice/model/dto" + "cidservice/service" +) + +var ( + RateLimit = rateLimit{} +) + +type rateLimit struct{} + +// SetTenantRateLimit 设置租户限流配置 +func (c *rateLimit) SetTenantRateLimit(ctx context.Context, req *dto.SetTenantRateLimitReq) (res *dto.SetTenantRateLimitRes, err error) { + // 注意:实际使用的是config.yml中的全局配置,此接口仅用于兼容旧API + // 实际限流参数请修改config.yml中的tenantRateLimit部分 + + return &dto.SetTenantRateLimitRes{ + Success: true, + }, nil +} + +// GetTenantRateLimitUsage 获取租户限流使用情况 +func (c *rateLimit) GetTenantRateLimitUsage(ctx context.Context, req *dto.GetTenantRateLimitUsageReq) (res *dto.GetTenantRateLimitUsageRes, err error) { + current, max, err := service.RateLimit.GetTenantCurrentUsage(ctx, req.TenantID, nil) + if err != nil { + return nil, err + } + + return &dto.GetTenantRateLimitUsageRes{ + TenantID: req.TenantID, + CurrentUsed: current, + MaxAllowed: max, + UsagePercent: float64(current) / float64(max) * 100, + }, nil +} diff --git a/controller/report_controller.go b/controller/report_controller.go index b5318c9..98dad06 100644 --- a/controller/report_controller.go +++ b/controller/report_controller.go @@ -5,8 +5,8 @@ import ( "gitee.com/red-future---jilin-g/common/http" - "cidService/model/dto" - "cidService/service" + "cidservice/model/dto" + "cidservice/service" ) type report struct{} @@ -31,12 +31,14 @@ func (c *report) List(ctx context.Context, req *dto.ListReportReq) (res *dto.Lis // Update 更新报表 func (c *report) Update(ctx context.Context, req *dto.UpdateReportReq) (res *http.ResponseEmpty, err error) { err = service.Report.Update(ctx, req) + res = &http.ResponseEmpty{} return } // Delete 删除报表 func (c *report) Delete(ctx context.Context, req *dto.DeleteReportReq) (res *http.ResponseEmpty, err error) { err = service.Report.Delete(ctx, req) + res = &http.ResponseEmpty{} return } @@ -48,5 +50,6 @@ func (c *report) Download(ctx context.Context, req *dto.DownloadReportReq) (res // Generate 生成报表 func (c *report) Generate(ctx context.Context, req *dto.GenerateReportReq) (res *http.ResponseEmpty, err error) { err = service.Report.Generate(ctx, req) + res = &http.ResponseEmpty{} return } diff --git a/controller/stat_report_controller.go b/controller/stat_report_controller.go new file mode 100644 index 0000000..e8611e9 --- /dev/null +++ b/controller/stat_report_controller.go @@ -0,0 +1,147 @@ +package controller + +import ( + "context" + + "cidservice/model/dto" + "cidservice/service" +) + +var ( + StatReport = &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 "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, err + } + + 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.ReportDetailReq) (res *dto.ExportReportRes, err error) { + // 获取报表详情 + resp, err := service.StatReport.GetReportDetail(ctx, req.ReportID) + if err != nil { + return nil, err + } + + // 返回导出数据,由上层处理HTTP响应 + return &dto.ExportReportRes{ + ReportData: resp, + }, nil +} diff --git a/controller/strategy_controller.go b/controller/strategy_controller.go index 06c0fa6..6f099ff 100644 --- a/controller/strategy_controller.go +++ b/controller/strategy_controller.go @@ -3,19 +3,20 @@ package controller import ( "context" - "cidService/model/dto" - "cidService/service" + "cidservice/model/dto" + "cidservice/service" + "github.com/gogf/gf/v2/errors/gerror" ) var ( - Strategy = cStrategy{} + Strategy = strategy{} ) -type cStrategy struct{} +type strategy struct{} // Create 创建策略 -func (c *cStrategy) Create(ctx context.Context, req *dto.CreateStrategyReq) (res *dto.StrategyRes, err error) { +func (c *strategy) Create(ctx context.Context, req *dto.CreateStrategyReq) (res *dto.StrategyRes, err error) { id, err := service.Strategy.CreateStrategy(ctx, req) if err != nil { return nil, err @@ -30,7 +31,7 @@ func (c *cStrategy) Create(ctx context.Context, req *dto.CreateStrategyReq) (res } // Update 更新策略 -func (c *cStrategy) Update(ctx context.Context, req *dto.UpdateStrategyReq) (res *dto.StrategyRes, err error) { +func (c *strategy) Update(ctx context.Context, req *dto.UpdateStrategyReq) (res *dto.StrategyRes, err error) { affected, err := service.Strategy.UpdateStrategy(ctx, req) if err != nil { return nil, err @@ -48,7 +49,7 @@ func (c *cStrategy) Update(ctx context.Context, req *dto.UpdateStrategyReq) (res } // Delete 删除策略 -func (c *cStrategy) Delete(ctx context.Context, req *dto.DeleteStrategyReq) (res *dto.DeleteStrategyRes, err error) { +func (c *strategy) Delete(ctx context.Context, req *dto.DeleteStrategyReq) (res *dto.DeleteStrategyRes, err error) { affected, err := service.Strategy.DeleteStrategy(ctx, req.Id) if err != nil { return nil, err @@ -63,7 +64,7 @@ func (c *cStrategy) Delete(ctx context.Context, req *dto.DeleteStrategyReq) (res } // GetByID 根据ID获取策略 -func (c *cStrategy) GetByID(ctx context.Context, req *dto.GetStrategyReq) (res *dto.StrategyRes, err error) { +func (c *strategy) GetByID(ctx context.Context, req *dto.GetStrategyReq) (res *dto.StrategyRes, err error) { strategy, err := service.Strategy.GetStrategyByID(ctx, req.Id) if err != nil { return nil, err @@ -73,6 +74,6 @@ func (c *cStrategy) GetByID(ctx context.Context, req *dto.GetStrategyReq) (res * } // GetList 获取策略列表 -func (c *cStrategy) GetList(ctx context.Context, req *dto.GetStrategyListReq) (res *dto.GetStrategyListRes, err error) { +func (c *strategy) GetList(ctx context.Context, req *dto.GetStrategyListReq) (res *dto.GetStrategyListRes, err error) { return service.Strategy.GetStrategyList(ctx, req) } diff --git a/dao/ad_position_dao.go b/dao/ad_position_dao.go index 3cac698..c02a98a 100644 --- a/dao/ad_position_dao.go +++ b/dao/ad_position_dao.go @@ -1,8 +1,8 @@ package dao import ( - "cidService/model/dto" - "cidService/model/entity" + "cidservice/model/dto" + "cidservice/model/entity" "context" "gitee.com/red-future---jilin-g/common/http" diff --git a/dao/ad_source_dao.go b/dao/ad_source_dao.go index 2e2f918..55598c7 100644 --- a/dao/ad_source_dao.go +++ b/dao/ad_source_dao.go @@ -1,9 +1,9 @@ package dao import ( - "cidService/model/entity" "context" + "cidservice/model/entity" "github.com/gogf/gf/v2/frame/g" ) diff --git a/dao/ad_statistics_dao.go b/dao/ad_statistics_dao.go index 06c08e3..413340d 100644 --- a/dao/ad_statistics_dao.go +++ b/dao/ad_statistics_dao.go @@ -3,8 +3,8 @@ package dao import ( "context" - "cidService/model/dto" - "cidService/model/entity" + "cidservice/model/dto" + "cidservice/model/entity" "github.com/gogf/gf/v2/frame/g" "go.mongodb.org/mongo-driver/v2/bson" diff --git a/dao/advertisement_dao.go b/dao/advertisement_dao.go index ec71bee..148dd72 100644 --- a/dao/advertisement_dao.go +++ b/dao/advertisement_dao.go @@ -1,8 +1,8 @@ package dao import ( - "cidService/model/dto" - "cidService/model/entity" + "cidservice/model/dto" + "cidservice/model/entity" "context" "time" diff --git a/dao/advertiser_dao.go b/dao/advertiser_dao.go index 33415ef..94b35e0 100644 --- a/dao/advertiser_dao.go +++ b/dao/advertiser_dao.go @@ -1,8 +1,8 @@ package dao import ( - "cidService/model/dto" - "cidService/model/entity" + "cidservice/model/dto" + "cidservice/model/entity" "context" "time" diff --git a/dao/application_dao.go b/dao/application_dao.go new file mode 100644 index 0000000..026f5b5 --- /dev/null +++ b/dao/application_dao.go @@ -0,0 +1,97 @@ +package dao + +import ( + "context" + + "cidservice/model/entity" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// applicationDao 应用DAO +type applicationDao struct{} + +var Application = &applicationDao{} + +// Ctx 获取数据库上下文 +func (d *applicationDao) Ctx(ctx context.Context) *gdb.Model { + return g.DB().Model("application").Ctx(ctx) +} + +// Create 创建应用 +func (d *applicationDao) Create(ctx context.Context, app *entity.Application) (int64, error) { + result, err := d.Ctx(ctx).Insert(app) + if err != nil { + return 0, err + } + id, _ := result.LastInsertId() + return id, nil +} + +// GetByID 根据ID获取应用 +func (d *applicationDao) GetByID(ctx context.Context, id int64) (*entity.Application, error) { + var app *entity.Application + err := d.Ctx(ctx).Where("id", id).Scan(&app) + return app, err +} + +// GetByTenantID 根据租户ID获取应用列表 +func (d *applicationDao) GetByTenantID(ctx context.Context, tenantID int64) ([]*entity.Application, error) { + var apps []*entity.Application + err := d.Ctx(ctx).Where("tenant_id", tenantID).Scan(&apps) + return apps, err +} + +// GetByAPIKey 根据API密钥获取应用 +func (d *applicationDao) GetByAPIKey(ctx context.Context, apiKey string) (*entity.Application, error) { + var app *entity.Application + err := d.Ctx(ctx).Where("app_key", apiKey).Scan(&app) + return app, err +} + +// Update 更新应用 +func (d *applicationDao) Update(ctx context.Context, app *entity.Application) error { + _, err := d.Ctx(ctx).Where("id", app.Id).Update(app) + return err +} + +// Delete 删除应用 +func (d *applicationDao) Delete(ctx context.Context, id int64) error { + _, err := d.Ctx(ctx).Where("id", id).Delete() + return err +} + +// List 应用列表 +func (d *applicationDao) List(ctx context.Context, tenantID int64, page, pageSize int) ([]*entity.Application, int, error) { + model := d.Ctx(ctx) + if tenantID > 0 { + model = model.Where("tenant_id", tenantID) + } + + var apps []*entity.Application + err := model.Page(page, pageSize).OrderDesc("created_at").Scan(&apps) + if err != nil { + return nil, 0, err + } + + total, err := model.Count() + if err != nil { + return nil, 0, err + } + + return apps, total, nil +} + +// GetByName 根据名称获取应用 +func (d *applicationDao) GetByName(ctx context.Context, name string) (*entity.Application, error) { + var app *entity.Application + err := d.Ctx(ctx).Where("name", name).Scan(&app) + return app, err +} + +// UpdateFields 更新应用部分字段 +func (d *applicationDao) UpdateFields(ctx context.Context, id int64, data *entity.Application) error { + _, err := d.Ctx(ctx).Where("id", id).Update(data) + return err +} diff --git a/dao/cid_request_dao.go b/dao/cid_request_dao.go index 48fcb25..6101f5b 100644 --- a/dao/cid_request_dao.go +++ b/dao/cid_request_dao.go @@ -1,7 +1,7 @@ package dao import ( - "cidService/model/entity" + "cidservice/model/entity" "context" "github.com/gogf/gf/v2/frame/g" diff --git a/dao/report_dao.go b/dao/report_dao.go index 1780fc9..e29767f 100644 --- a/dao/report_dao.go +++ b/dao/report_dao.go @@ -3,8 +3,8 @@ package dao import ( "context" - "cidService/model/dto" - "cidService/model/entity" + "cidservice/model/dto" + "cidservice/model/entity" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/util/gconv" diff --git a/dao/stat_report_dao.go b/dao/stat_report_dao.go new file mode 100644 index 0000000..06e99c0 --- /dev/null +++ b/dao/stat_report_dao.go @@ -0,0 +1,90 @@ +package dao + +import ( + "context" + + "cidservice/model/entity" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +// statReportDao 统计报表DAO +type statReportDao struct{} + +var StatReport = &statReportDao{} + +// Ctx 获取数据库上下文 +func (d *statReportDao) Ctx(ctx context.Context) *gdb.Model { + return g.DB().Model("stat_report").Ctx(ctx) +} + +// Create 创建统计报表 +func (d *statReportDao) Create(ctx context.Context, report *entity.StatReport) (int64, error) { + result, err := d.Ctx(ctx).Insert(report) + if err != nil { + return 0, err + } + id, _ := result.LastInsertId() + return id, nil +} + +// GetByID 根据ID获取统计报表 +func (d *statReportDao) GetByID(ctx context.Context, id int64) (*entity.StatReport, error) { + var report *entity.StatReport + err := d.Ctx(ctx).Where("id", id).Scan(&report) + return report, err +} + +// GetByTenantAndDate 根据租户和日期获取统计报表 +func (d *statReportDao) GetByTenantAndDate(ctx context.Context, tenantID int64, reportType, date string) (*entity.StatReport, error) { + var report *entity.StatReport + err := d.Ctx(ctx).Where("tenant_id", tenantID).Where("report_type", reportType).Where("report_date", date).Scan(&report) + return report, err +} + +// Update 更新统计报表 +func (d *statReportDao) Update(ctx context.Context, report *entity.StatReport) error { + _, err := d.Ctx(ctx).Where("id", report.Id).Update(report) + return err +} + +// Delete 删除统计报表 +func (d *statReportDao) Delete(ctx context.Context, id int64) error { + _, err := d.Ctx(ctx).Where("id", id).Delete() + return err +} + +// List 统计报表列表 +func (d *statReportDao) List(ctx context.Context, tenantID, appID int64, reportType, startDate, endDate string, page, pageSize int) ([]*entity.StatReport, int, error) { + model := d.Ctx(ctx) + + if tenantID > 0 { + model = model.Where("tenant_id", tenantID) + } + if appID > 0 { + model = model.Where("app_id", appID) + } + if reportType != "" { + model = model.Where("report_type", reportType) + } + if startDate != "" { + model = model.WhereGTE("report_date", startDate) + } + if endDate != "" { + model = model.WhereLTE("report_date", endDate) + } + + var reports []*entity.StatReport + err := model.Page(page, pageSize).OrderDesc("generated_at").Scan(&reports) + if err != nil { + return nil, 0, err + } + + total, err := model.Count() + if err != nil { + return nil, 0, err + } + + return reports, total, nil +} diff --git a/dao/strategy_dao.go b/dao/strategy_dao.go index 35ffbf0..6181990 100644 --- a/dao/strategy_dao.go +++ b/dao/strategy_dao.go @@ -1,7 +1,7 @@ package dao import ( - "cidService/model/entity" + "cidservice/model/entity" "context" "github.com/gogf/gf/v2/frame/g" diff --git a/dao/tenant_dao.go b/dao/tenant_dao.go new file mode 100644 index 0000000..cba1147 --- /dev/null +++ b/dao/tenant_dao.go @@ -0,0 +1,79 @@ +package dao + +import ( + "context" + + "cidservice/model/entity" + + "github.com/gogf/gf/v2/database/gdb" + "github.com/gogf/gf/v2/frame/g" +) + +var Tenant = tenantDao{} + +type tenantDao struct{} + +// Create 创建租户 +func (d *tenantDao) Create(ctx context.Context, tenant *entity.Tenant) (id int64, err error) { + result, err := g.DB().Model("tenant").Ctx(ctx).Insert(tenant) + if err != nil { + return 0, err + } + id, _ = result.LastInsertId() + return id, nil +} + +// GetByID 根据ID获取租户 +func (d *tenantDao) GetByID(ctx context.Context, id int64) (tenant *entity.Tenant, err error) { + err = g.DB().Model("tenant").Ctx(ctx).Where("id = ? AND is_deleted = ?", id, false).Scan(&tenant) + return +} + +// GetByCode 根据编码获取租户 +func (d *tenantDao) GetByCode(ctx context.Context, code string) (tenant *entity.Tenant, err error) { + err = g.DB().Model("tenant").Ctx(ctx).Where("code = ? AND is_deleted = ?", code, false).Scan(&tenant) + return +} + +// UpdateFields 更新租户字段 +func (d *tenantDao) UpdateFields(ctx context.Context, id int64, updateData *entity.Tenant) (affected int64, err error) { + result, err := g.DB().Model("tenant").Ctx(ctx).Where("id = ? AND is_deleted = ?", id, false).Update(updateData) + if err != nil { + return 0, err + } + rowsAffected, _ := result.RowsAffected() + return rowsAffected, nil +} + +// Delete 删除租户 +func (d *tenantDao) Delete(ctx context.Context, id int64) (affected int64, err error) { + result, err := g.DB().Model("tenant").Ctx(ctx).Where("id = ?", id).Update(gdb.Map{"is_deleted": true}) + if err != nil { + return 0, err + } + rowsAffected, _ := result.RowsAffected() + return rowsAffected, nil +} + +// List 获取租户列表 +func (d *tenantDao) List(ctx context.Context, page, size int, filter map[string]interface{}) (list []*entity.Tenant, total int64, err error) { + model := g.DB().Model("tenant").Ctx(ctx).Where("is_deleted = ?", false) + + // 应用过滤条件 + if filter != nil { + for key, value := range filter { + model = model.Where(key, value) + } + } + + // 获取总数 + count, err := model.Count() + if err != nil { + return + } + total = int64(count) + + // 分页查询 + err = model.Page(page, size).OrderDesc("created_at").Scan(&list) + return +} diff --git a/database/init.sql b/database/init.sql deleted file mode 100644 index a31a3d2..0000000 --- a/database/init.sql +++ /dev/null @@ -1,78 +0,0 @@ --- CID服务商项目数据库初始化脚本 - --- 广告源表 -CREATE TABLE IF NOT EXISTS `ad_sources` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `name` varchar(100) NOT NULL COMMENT '广告源名称', - `provider` varchar(50) NOT NULL COMMENT '提供商: self, google, facebook, etc.', - `api_endpoint` varchar(255) DEFAULT NULL COMMENT 'API端点', - `api_key` varchar(255) DEFAULT NULL COMMENT 'API密钥', - `config` text COMMENT '配置信息(JSON格式)', - `status` varchar(20) NOT NULL DEFAULT 'active' COMMENT '状态: active, inactive', - `priority` int(11) NOT NULL DEFAULT 0 COMMENT '优先级', - `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `created_by` bigint(20) DEFAULT NULL COMMENT '创建人', - `updated_by` bigint(20) DEFAULT NULL COMMENT '更新人', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_ad_sources_name` (`name`), - KEY `idx_ad_sources_provider` (`provider`), - KEY `idx_ad_sources_status` (`status`), - KEY `idx_ad_sources_priority` (`priority`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='广告源表'; - --- 策略表 -CREATE TABLE IF NOT EXISTS `strategies` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `name` varchar(100) NOT NULL COMMENT '策略名称', - `description` varchar(500) DEFAULT NULL COMMENT '描述', - `tenant_level` varchar(20) NOT NULL COMMENT '适用租户级别: basic, standard, premium', - `min_conversion` decimal(5,4) NOT NULL DEFAULT 0.0000 COMMENT '最低转化率', - `max_conversion` decimal(5,4) NOT NULL DEFAULT 1.0000 COMMENT '最高转化率', - `source_weights` text COMMENT '广告源权重配置(JSON格式)', - `max_ads_per_req` int(11) NOT NULL DEFAULT 5 COMMENT '每次请求最大广告数', - `priority` int(11) NOT NULL DEFAULT 0 COMMENT '优先级', - `status` varchar(20) NOT NULL DEFAULT 'active' COMMENT '状态: active, inactive', - `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - `created_by` bigint(20) DEFAULT NULL COMMENT '创建人', - `updated_by` bigint(20) DEFAULT NULL COMMENT '更新人', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_strategies_name` (`name`), - KEY `idx_strategies_tenant_level` (`tenant_level`), - KEY `idx_strategies_status` (`status`), - KEY `idx_strategies_priority` (`priority`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='匹配策略表'; - --- CID请求记录表 -CREATE TABLE IF NOT EXISTS `cid_requests` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT, - `tenant_id` bigint(20) NOT NULL COMMENT '租户ID', - `user_id` bigint(20) NOT NULL COMMENT '用户ID', - `request_type` varchar(50) NOT NULL COMMENT '请求类型', - `parameters` text COMMENT '请求参数(JSON格式)', - `response_data` text COMMENT '响应数据(JSON格式)', - `status` varchar(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending, completed, failed', - `process_time` int(11) DEFAULT NULL COMMENT '处理时间(毫秒)', - `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `updated_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', - PRIMARY KEY (`id`), - KEY `idx_cid_requests_tenant_id` (`tenant_id`), - KEY `idx_cid_requests_user_id` (`user_id`), - KEY `idx_cid_requests_status` (`status`), - KEY `idx_cid_requests_created_at` (`created_at`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='CID请求记录表'; - --- 插入默认广告源 -INSERT INTO `ad_sources` (`name`, `provider`, `status`, `priority`, `created_by`, `updated_by`) VALUES -('自营广告', 'self', 'active', 100, 1, 1), -('Google Ads', 'google', 'active', 80, 1, 1), -('Facebook Ads', 'facebook', 'active', 70, 1, 1) -ON DUPLICATE KEY UPDATE `updated_at` = CURRENT_TIMESTAMP; - --- 插入默认策略 -INSERT INTO `strategies` (`name`, `description`, `tenant_level`, `min_conversion`, `max_conversion`, `source_weights`, `max_ads_per_req`, `priority`, `status`, `created_by`, `updated_by`) VALUES -('基础策略', '适用于基础租户的默认策略', 'basic', 0.0100, 0.0500, '{"self": 100, "google": 0, "facebook": 0}', 3, 10, 'active', 1, 1), -('标准策略', '适用于标准租户的默认策略', 'standard', 0.0500, 0.1500, '{"self": 70, "google": 20, "facebook": 10}', 5, 20, 'active', 1, 1), -('高级策略', '适用于高级租户的默认策略', 'premium', 0.1500, 1.0000, '{"self": 40, "google": 30, "facebook": 30}', 10, 30, 'active', 1, 1) -ON DUPLICATE KEY UPDATE `updated_at` = CURRENT_TIMESTAMP; \ No newline at end of file diff --git a/go.mod b/go.mod index f92b829..2897456 100644 --- a/go.mod +++ b/go.mod @@ -1,4 +1,4 @@ -module cidService +module cidservice go 1.25.3 @@ -10,7 +10,7 @@ require ( go.mongodb.org/mongo-driver/v2 v2.4.0 golang.org/x/net v0.47.0 ) - +replace gitee.com/red-future---jilin-g/common v0.1.9 => ../common require ( github.com/BurntSushi/toml v1.5.0 // indirect github.com/armon/go-metrics v0.4.1 // indirect diff --git a/main.go b/main.go index ac64570..b685e79 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,7 @@ package main import ( - "cidService/controller" + "cidservice/controller" "gitee.com/red-future---jilin-g/common/http" "gitee.com/red-future---jilin-g/common/jaeger" @@ -20,6 +20,9 @@ func main() { controller.AdPosition, controller.AdStatistics, controller.Report, + //controller.RateLimit, + //controller.Application, + //controller.StatReport, }) select {} } diff --git a/model/dto/ad_position_dto.go b/model/dto/ad_position_dto.go index 0e09459..d918e4f 100644 --- a/model/dto/ad_position_dto.go +++ b/model/dto/ad_position_dto.go @@ -1,7 +1,7 @@ package dto import ( - "cidService/model/entity" + "cidservice/model/entity" "gitee.com/red-future---jilin-g/common/http" "github.com/gogf/gf/v2/frame/g" @@ -9,7 +9,7 @@ import ( // AddAdPositionReq 添加广告位请求 type AddAdPositionReq struct { - g.Meta `path:"/adposition/add" method:"post" tags:"广告位管理" summary:"添加广告位" dc:"添加新的广告位"` + g.Meta `path:"/add" method:"post" tags:"广告位管理" summary:"添加广告位" dc:"添加新的广告位"` // 基本信息 Name string `json:"name" v:"required"` // 广告位名称 @@ -51,7 +51,7 @@ type AddAdPositionRes struct { // UpdateAdPositionReq 更新广告位请求 type UpdateAdPositionReq struct { - g.Meta `path:"/adposition/update" method:"post" tags:"广告位管理" summary:"更新广告位" dc:"更新广告位信息"` + g.Meta `path:"/update" method:"put" tags:"广告位管理" summary:"更新广告位" dc:"更新广告位信息"` Id string `json:"id" v:"required"` // ID @@ -91,7 +91,7 @@ type UpdateAdPositionReq struct { // GetAdPositionReq 获取广告位详情请求 type GetAdPositionReq struct { - g.Meta `path:"/adposition/one" method:"get" tags:"广告位管理" summary:"获取广告位详情" dc:"根据ID获取单个广告位详情"` + g.Meta `path:"/one" method:"get" tags:"广告位管理" summary:"获取广告位详情" dc:"根据ID获取单个广告位详情"` Id string `json:"id" v:"required"` // ID } @@ -101,7 +101,7 @@ type GetAdPositionRes struct { // ListAdPositionReq 获取广告位列表请求 type ListAdPositionReq struct { - g.Meta `path:"/adposition/list" method:"get" tags:"广告位管理" summary:"获取广告位列表" dc:"分页查询广告位列表,支持多条件筛选"` + g.Meta `path:"/list" method:"get" tags:"广告位管理" summary:"获取广告位列表" dc:"分页查询广告位列表,支持多条件筛选"` http.Page Name string `json:"name"` // 广告位名称模糊查询 @@ -120,7 +120,7 @@ type ListAdPositionRes struct { // UpdateAdPositionStatusReq 更新广告位状态请求 type UpdateAdPositionStatusReq struct { - g.Meta `path:"/adposition/status" method:"post" tags:"广告位管理" summary:"更新广告位状态" dc:"更新广告位状态"` + g.Meta `path:"/updateStatus" method:"patch" tags:"广告位管理" summary:"更新广告位状态" dc:"更新广告位状态"` Id string `json:"id" v:"required"` // 广告位ID Status string `json:"status" v:"required"` // 广告位状态:启用、禁用、测试 @@ -128,7 +128,7 @@ type UpdateAdPositionStatusReq struct { // GetAdPositionStatisticsReq 获取广告位统计数据请求 type GetAdPositionStatisticsReq struct { - g.Meta `path:"/adposition/statistics" method:"get" tags:"广告位管理" summary:"获取广告位统计数据" dc:"获取广告位的统计数据"` + g.Meta `path:"getStatisticsAdPosition" method:"get" tags:"广告位管理" summary:"获取广告位统计数据" dc:"获取广告位的统计数据"` Id string `json:"id" v:"required"` // 广告位ID StatType string `json:"statType" v:"required"` // 统计类型:天、周、月 @@ -143,7 +143,7 @@ type GetAdPositionStatisticsRes struct { // GetAvailableAdPositionsReq 获取可用广告位请求 type GetAvailableAdPositionsReq struct { - g.Meta `path:"/adposition/available" method:"get" tags:"广告位管理" summary:"获取可用广告位列表" dc:"获取所有启用的广告位列表"` + g.Meta `path:"getAvailableAdPositions" method:"get" tags:"广告位管理" summary:"获取可用广告位列表" dc:"获取所有启用的广告位列表"` } type GetAvailableAdPositionsRes struct { @@ -152,7 +152,7 @@ type GetAvailableAdPositionsRes struct { // MatchAdReq 匹配广告请求 type MatchAdReq struct { - g.Meta `path:"/adposition/match" method:"post" tags:"广告位管理" summary:"匹配广告" dc:"根据广告位编码和用户信息匹配适合的广告"` + g.Meta `path:"matchAdAdPosition" method:"post" tags:"广告位管理" summary:"匹配广告" dc:"根据广告位编码和用户信息匹配适合的广告"` PositionCode string `json:"positionCode" v:"required"` // 广告位编码 UserInfo map[string]interface{} `json:"userInfo"` // 用户信息 diff --git a/model/dto/ad_source_dto.go b/model/dto/ad_source_dto.go index 83e44fb..31e7f52 100644 --- a/model/dto/ad_source_dto.go +++ b/model/dto/ad_source_dto.go @@ -1,7 +1,7 @@ package dto import ( - "cidService/model/entity" + "cidservice/model/entity" "gitee.com/red-future---jilin-g/common/http" "github.com/gogf/gf/v2/frame/g" @@ -9,7 +9,7 @@ import ( // CreateAdSourceReq 创建广告源请求 type CreateAdSourceReq struct { - g.Meta `path:"/adsource/create" method:"post" tags:"广告源管理" summary:"创建广告源" dc:"创建新的广告源配置"` + g.Meta `path:"createAdSource" method:"post" tags:"广告源管理" summary:"创建广告源" dc:"创建新的广告源配置"` // 基本信息 Name string `json:"name" v:"required"` // 广告源名称 @@ -69,7 +69,7 @@ type CreateAdSourceRes struct { // GetAdSourceReq 获取广告源详情请求 type GetAdSourceReq struct { - g.Meta `path:"/adsource/one" method:"get" tags:"广告源管理" summary:"获取广告源详情" dc:"根据ID获取单个广告源详情"` + g.Meta `path:"getByIDAdSource" method:"get" tags:"广告源管理" summary:"获取广告源详情" dc:"根据ID获取单个广告源详情"` Id string `json:"id" v:"required"` // 广告源ID } @@ -79,7 +79,7 @@ type GetAdSourceRes struct { // ListAdSourceReq 获取广告源列表请求 type ListAdSourceReq struct { - g.Meta `path:"/adsource/list" method:"get" tags:"广告源管理" summary:"获取广告源列表" dc:"分页查询广告源列表,支持多条件筛选"` + g.Meta `path:"getListAdSource" method:"get" tags:"广告源管理" summary:"获取广告源列表" dc:"分页查询广告源列表,支持多条件筛选"` http.Page Name string `json:"name"` // 广告源名称模糊查询 @@ -97,7 +97,7 @@ type ListAdSourceRes struct { // UpdateAdSourceReq 更新广告源请求 type UpdateAdSourceReq struct { - g.Meta `path:"/adsource/update" method:"post" tags:"广告源管理" summary:"更新广告源" dc:"更新广告源信息"` + g.Meta `path:"updateAdSource" method:"put" tags:"广告源管理" summary:"更新广告源" dc:"更新广告源信息"` Id string `json:"id" v:"required"` // 广告源ID @@ -152,7 +152,7 @@ type UpdateAdSourceReq struct { // UpdateAdSourceStatusReq 更新广告源状态请求 type UpdateAdSourceStatusReq struct { - g.Meta `path:"/adsource/status" method:"post" tags:"广告源管理" summary:"更新广告源状态" dc:"更新广告源状态"` + g.Meta `path:"/adsource" method:"patch" tags:"广告源管理" summary:"更新广告源状态" dc:"更新广告源状态"` Id string `json:"id" v:"required"` // 广告源ID Status string `json:"status" v:"required"` // 广告源状态:active、inactive、maintenance @@ -160,14 +160,14 @@ type UpdateAdSourceStatusReq struct { // DeleteAdSourceReq 删除广告源请求 type DeleteAdSourceReq struct { - g.Meta `path:"/adsource/delete" method:"post" tags:"广告源管理" summary:"删除广告源" dc:"删除指定的广告源"` + g.Meta `path:"deleteAdSource" method:"delete" tags:"广告源管理" summary:"删除广告源" dc:"删除指定的广告源"` Id string `json:"id" v:"required"` // 广告源ID } // TestAdSourceReq 测试广告源连接请求 type TestAdSourceReq struct { - g.Meta `path:"/adsource/test" method:"post" tags:"广告源管理" summary:"测试广告源连接" dc:"测试广告源的连接性和可用性"` + g.Meta `path:"/adsource-test" method:"post" tags:"广告源管理" summary:"测试广告源连接" dc:"测试广告源的连接性和可用性"` Id string `json:"id" v:"required"` // 广告源ID } @@ -195,7 +195,7 @@ type AdSourceTestMetrics struct { // GetAdSourceStatisticsReq 获取广告源统计数据请求 type GetAdSourceStatisticsReq struct { - g.Meta `path:"/adsource/statistics" method:"get" tags:"广告源管理" summary:"获取广告源统计数据" dc:"获取广告源的详细统计数据"` + g.Meta `path:"/adsource-statistics" method:"get" tags:"广告源管理" summary:"获取广告源统计数据" dc:"获取广告源的详细统计数据"` Id string `json:"id" v:"required"` // 广告源ID StartDate int64 `json:"startDate" v:"required"` // 开始日期 diff --git a/model/dto/ad_statistics_dto.go b/model/dto/ad_statistics_dto.go index 0d8bc76..2b50863 100644 --- a/model/dto/ad_statistics_dto.go +++ b/model/dto/ad_statistics_dto.go @@ -1,7 +1,7 @@ package dto import ( - "cidService/model/entity" + "cidservice/model/entity" "gitee.com/red-future---jilin-g/common/http" "github.com/gogf/gf/v2/frame/g" @@ -9,7 +9,7 @@ import ( // GetAdStatisticsReq 获取广告统计数据请求 type GetAdStatisticsReq struct { - g.Meta `path:"/statistics/list" method:"get" tags:"广告统计" summary:"获取广告统计数据" dc:"获取广告的统计数据"` + g.Meta `path:"getStatisticsAdStatistics" method:"get" tags:"广告统计" summary:"获取广告统计数据" dc:"获取广告的统计数据"` // 分页参数 http.Page @@ -42,7 +42,7 @@ type GetAdStatisticsRes struct { // GetDashboardReq 获取仪表盘数据请求 type GetDashboardReq struct { - g.Meta `path:"/dashboard" method:"get" tags:"广告仪表盘" summary:"获取仪表盘数据" dc:"获取广告系统的仪表盘统计数据"` + g.Meta `path:"getDashboardAdStatistics" method:"get" tags:"广告仪表盘" summary:"获取仪表盘数据" dc:"获取广告系统的仪表盘统计数据"` // 时间范围 StartDate int64 `json:"startDate" v:"required"` // 开始日期 @@ -103,7 +103,7 @@ type RankData struct { // GenerateDailyStatisticsReq 生成每日统计数据请求 type GenerateDailyStatisticsReq struct { - g.Meta `path:"/statistics/generate-daily" method:"post" tags:"广告统计" summary:"生成每日统计数据" dc:"手动生成指定日期的广告统计数据"` + g.Meta `path:"generateDailyStatisticsAdStatistics" method:"post" tags:"广告统计" summary:"生成每日统计数据" dc:"手动生成指定日期的广告统计数据"` Date int64 `json:"date" v:"required"` // 日期时间戳 } diff --git a/model/dto/advertisement_dto.go b/model/dto/advertisement_dto.go index f407ca6..318bcf4 100644 --- a/model/dto/advertisement_dto.go +++ b/model/dto/advertisement_dto.go @@ -1,7 +1,7 @@ package dto import ( - "cidService/model/entity" + "cidservice/model/entity" "gitee.com/red-future---jilin-g/common/http" "github.com/gogf/gf/v2/frame/g" @@ -9,7 +9,7 @@ import ( // AddAdvertisementReq 添加广告请求 type AddAdvertisementReq struct { - g.Meta `path:"/advertisement/add" method:"post" tags:"广告管理" summary:"添加广告" dc:"添加新的广告"` + g.Meta `path:"addAdvertisement" method:"post" tags:"广告管理" summary:"添加广告" dc:"添加新的广告"` // 广告基本信息 Title string `json:"title" v:"required"` // 广告标题 @@ -40,7 +40,7 @@ type AddAdvertisementRes struct { // UpdateAdvertisementReq 更新广告请求 type UpdateAdvertisementReq struct { - g.Meta `path:"/advertisement/update" method:"post" tags:"广告管理" summary:"更新广告" dc:"更新广告信息"` + g.Meta `path:"updateAdvertisement" method:"put" tags:"广告管理" summary:"更新广告" dc:"更新广告信息"` Id string `json:"id" v:"required"` // ID @@ -74,7 +74,7 @@ type UpdateAdvertisementReq struct { // GetAdvertisementReq 获取广告详情请求 type GetAdvertisementReq struct { - g.Meta `path:"/advertisement/one" method:"get" tags:"广告管理" summary:"获取广告详情" dc:"根据ID获取单个广告详情"` + g.Meta `path:"getOneAdvertisement" method:"get" tags:"广告管理" summary:"获取广告详情" dc:"根据ID获取单个广告详情"` Id string `json:"id" v:"required"` // ID } @@ -84,7 +84,7 @@ type GetAdvertisementRes struct { // ListAdvertisementReq 获取广告列表请求 type ListAdvertisementReq struct { - g.Meta `path:"/advertisement/list" method:"get" tags:"广告管理" summary:"获取广告列表" dc:"分页查询广告列表,支持多条件筛选"` + g.Meta `path:"listAdvertisement" method:"get" tags:"广告管理" summary:"获取广告列表" dc:"分页查询广告列表,支持多条件筛选"` http.Page AdvertiserId string `json:"advertiserId"` // 广告主ID @@ -103,7 +103,7 @@ type ListAdvertisementRes struct { // AuditAdvertisementReq 审核广告请求 type AuditAdvertisementReq struct { - g.Meta `path:"/advertisement/audit" method:"post" tags:"广告管理" summary:"审核广告" dc:"审核广告,通过或拒绝"` + g.Meta `path:"auditAdvertisement" method:"post" tags:"广告管理" summary:"审核广告" dc:"审核广告,通过或拒绝"` Id string `json:"id" v:"required"` // 广告ID AuditStatus string `json:"auditStatus" v:"required"` // 审核状态:通过、拒绝 @@ -112,7 +112,7 @@ type AuditAdvertisementReq struct { // UpdateAdStatusReq 更新广告状态请求 type UpdateAdStatusReq struct { - g.Meta `path:"/advertisement/status" method:"post" tags:"广告管理" summary:"更新广告状态" dc:"更新广告状态"` + g.Meta `path:"updateStatusAdvertisement" method:"patch" tags:"广告管理" summary:"更新广告状态" dc:"更新广告状态"` Id string `json:"id" v:"required"` // 广告ID Status string `json:"status" v:"required"` // 广告状态:启用、禁用 @@ -120,7 +120,7 @@ type UpdateAdStatusReq struct { // GetAdStatisticsReq 获取广告统计数据请求 type GetAdStatisticsForAdvertisementReq struct { - g.Meta `path:"/advertisement/statistics" method:"get" tags:"广告管理" summary:"获取广告统计数据" dc:"获取广告的统计数据"` + g.Meta `path:"getStatisticsAdvertisement" method:"get" tags:"广告管理" summary:"获取广告统计数据" dc:"获取广告的统计数据"` Id string `json:"id" v:"required"` // 广告ID StatType string `json:"statType" v:"required"` // 统计类型:天、周、月 diff --git a/model/dto/advertiser_dto.go b/model/dto/advertiser_dto.go index 8414171..7898a58 100644 --- a/model/dto/advertiser_dto.go +++ b/model/dto/advertiser_dto.go @@ -1,7 +1,7 @@ package dto import ( - "cidService/model/entity" + "cidservice/model/entity" "gitee.com/red-future---jilin-g/common/http" "github.com/gogf/gf/v2/frame/g" @@ -9,7 +9,7 @@ import ( // AddAdvertiserReq 添加广告主请求 type AddAdvertiserReq struct { - g.Meta `path:"/advertiser/add" method:"post" tags:"广告主管理" summary:"添加广告主" dc:"添加新的广告主"` + g.Meta `path:"addAdvertiser" method:"post" tags:"广告主管理" summary:"添加广告主" dc:"添加新的广告主"` // 基本信息 Name string `json:"name" v:"required"` // 广告主名称 @@ -49,7 +49,7 @@ type AddAdvertiserRes struct { // UpdateAdvertiserReq 更新广告主请求 type UpdateAdvertiserReq struct { - g.Meta `path:"/advertiser/update" method:"post" tags:"广告主管理" summary:"更新广告主" dc:"更新广告主信息"` + g.Meta `path:"updateAdvertiser" method:"put" tags:"广告主管理" summary:"更新广告主" dc:"更新广告主信息"` Id string `json:"id" v:"required"` // ID @@ -92,7 +92,7 @@ type UpdateAdvertiserReq struct { // GetAdvertiserReq 获取广告主详情请求 type GetAdvertiserReq struct { - g.Meta `path:"/advertiser/one" method:"get" tags:"广告主管理" summary:"获取广告主详情" dc:"根据ID获取单个广告主详情"` + g.Meta `path:"getOneAdvertiser" method:"get" tags:"广告主管理" summary:"获取广告主详情" dc:"根据ID获取单个广告主详情"` Id string `json:"id" v:"required"` // ID } @@ -102,7 +102,7 @@ type GetAdvertiserRes struct { // ListAdvertiserReq 获取广告主列表请求 type ListAdvertiserReq struct { - g.Meta `path:"/advertiser/list" method:"get" tags:"广告主管理" summary:"获取广告主列表" dc:"分页查询广告主列表,支持多条件筛选"` + g.Meta `path:"listAdvertiser" method:"get" tags:"广告主管理" summary:"获取广告主列表" dc:"分页查询广告主列表,支持多条件筛选"` http.Page Name string `json:"name"` // 广告主名称模糊查询 @@ -121,7 +121,7 @@ type ListAdvertiserRes struct { // AuditAdvertiserReq 审核广告主请求 type AuditAdvertiserReq struct { - g.Meta `path:"/advertiser/audit" method:"post" tags:"广告主管理" summary:"审核广告主" dc:"审核广告主,通过或拒绝"` + g.Meta `path:"auditAdvertiser" method:"post" tags:"广告主管理" summary:"审核广告主" dc:"审核广告主,通过或拒绝"` Id string `json:"id" v:"required"` // 广告主ID AuditStatus string `json:"auditStatus" v:"required"` // 审核状态:通过、拒绝 @@ -130,7 +130,7 @@ type AuditAdvertiserReq struct { // UpdateAdvertiserStatusReq 更新广告主状态请求 type UpdateAdvertiserStatusReq struct { - g.Meta `path:"/advertiser/status" method:"post" tags:"广告主管理" summary:"更新广告主状态" dc:"更新广告主状态"` + g.Meta `path:"updateStatusAdvertiser" method:"patch" tags:"广告主管理" summary:"更新广告主状态" dc:"更新广告主状态"` Id string `json:"id" v:"required"` // 广告主ID Status string `json:"status" v:"required"` // 广告主状态:启用、禁用、冻结 @@ -138,7 +138,7 @@ type UpdateAdvertiserStatusReq struct { // RechargeAdvertiserReq 广告主充值请求 type RechargeAdvertiserReq struct { - g.Meta `path:"/advertiser/recharge" method:"post" tags:"广告主管理" summary:"广告主充值" dc:"为广告主账户充值"` + g.Meta `path:"rechargeAdvertiser" method:"post" tags:"广告主管理" summary:"广告主充值" dc:"为广告主账户充值"` Id string `json:"id" v:"required"` // 广告主ID Amount int64 `json:"amount" v:"required"` // 充值金额(分) @@ -147,7 +147,7 @@ type RechargeAdvertiserReq struct { // UpdateCreditLimitReq 更新授信额度请求 type UpdateCreditLimitReq struct { - g.Meta `path:"/advertiser/credit" method:"post" tags:"广告主管理" summary:"更新授信额度" dc:"更新广告主的授信额度"` + g.Meta `path:"updateCreditLimitAdvertiser" method:"post" tags:"广告主管理" summary:"更新授信额度" dc:"更新广告主的授信额度"` Id string `json:"id" v:"required"` // 广告主ID CreditLimit int64 `json:"creditLimit" v:"required"` // 授信额度(分) @@ -156,7 +156,7 @@ type UpdateCreditLimitReq struct { // GetAdvertiserBalanceReq 获取广告主余额请求 type GetAdvertiserBalanceReq struct { - g.Meta `path:"/advertiser/balance" method:"get" tags:"广告主管理" summary:"获取广告主余额" dc:"根据ID获取广告主账户余额和授信额度"` + g.Meta `path:"getBalanceAdvertiser" method:"get" tags:"广告主管理" summary:"获取广告主余额" dc:"根据ID获取广告主账户余额和授信额度"` Id string `json:"id" v:"required"` // 广告主ID } diff --git a/model/dto/application_dto.go b/model/dto/application_dto.go new file mode 100644 index 0000000..65a7f18 --- /dev/null +++ b/model/dto/application_dto.go @@ -0,0 +1,157 @@ +package dto + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +// CreateApplicationReq 创建应用请求 +type CreateApplicationReq struct { + g.Meta `path:"createApplication" method:"post" summary:"创建应用"` + + TenantID int64 `json:"tenantId" v:"required#租户ID不能为空"` + Name string `json:"name" v:"required#应用名称不能为空"` + Code string `json:"code" v:"required#应用编码不能为空"` + Description string `json:"description"` + Platform string `json:"platform" v:"required#平台不能为空|in:web,h5,android,ios#平台类型错误"` + PackageName string `json:"packageName"` + AppStoreURL string `json:"appStoreUrl"` + Categories []string `json:"categories"` + Tags []string `json:"tags"` + AdTypes []string `json:"adTypes"` + CallbackURL string `json:"callbackUrl"` +} + +// CreateApplicationRes 创建应用响应 +type CreateApplicationRes struct { + ID int64 `json:"id"` + AppKey string `json:"appKey"` + AppSecret string `json:"appSecret"` +} + +// UpdateApplicationReq 更新应用请求 +type UpdateApplicationReq struct { + g.Meta `path:"updateApplication" method:"put" summary:"更新应用"` + + ID int64 `json:"id" v:"required#应用ID不能为空"` + Name string `json:"name"` + Description string `json:"description"` + Platform string `json:"platform" v:"in:web,h5,android,ios#平台类型错误"` + PackageName string `json:"packageName"` + AppStoreURL string `json:"appStoreUrl"` + Categories []string `json:"categories"` + Tags []string `json:"tags"` + AdTypes []string `json:"adTypes"` + CallbackURL string `json:"callbackUrl"` +} + +// UpdateApplicationRes 更新应用响应 +type UpdateApplicationRes struct { + Success bool `json:"success"` +} + +// GetApplicationReq 获取应用请求 +type GetApplicationReq struct { + g.Meta `path:"getApplication" method:"get" summary:"获取应用信息"` + + ID int64 `json:"id" v:"required#应用ID不能为空"` +} + +// GetApplicationRes 获取应用响应 +type GetApplicationRes struct { + ID int64 `json:"id"` + TenantID int64 `json:"tenantId"` + Name string `json:"name"` + Code string `json:"code"` + Description string `json:"description"` + Platform string `json:"platform"` + PackageName string `json:"packageName"` + AppStoreURL string `json:"appStoreUrl"` + Categories []string `json:"categories"` + Tags []string `json:"tags"` + AdTypes []string `json:"adTypes"` + Status string `json:"status"` + AppKey string `json:"appKey"` + CallbackURL string `json:"callbackUrl"` + CreatedAt int64 `json:"createdAt"` + UpdatedAt int64 `json:"updatedAt"` +} + +// ListApplicationsReq 获取应用列表请求 +type ListApplicationsReq struct { + g.Meta `path:"listApplications" method:"get" summary:"获取应用列表"` + + TenantID int64 `json:"tenantId" v:"required#租户ID不能为空"` + Platform string `json:"platform"` + Status string `json:"status"` + Page int `json:"page" d:"1"` + Size int `json:"size" d:"20"` +} + +// ListApplicationsRes 获取应用列表响应 +type ListApplicationsRes struct { + List []ApplicationItem `json:"list"` + Total int64 `json:"total"` + Page int `json:"page"` + Size int `json:"size"` +} + +// ApplicationItem 应用列表项 +type ApplicationItem struct { + ID int64 `json:"id"` + Name string `json:"name"` + Code string `json:"code"` + Description string `json:"description"` + Platform string `json:"platform"` + PackageName string `json:"packageName"` + Categories []string `json:"categories"` + Tags []string `json:"tags"` + AdTypes []string `json:"adTypes"` + Status string `json:"status"` + DailyRequests int64 `json:"dailyRequests"` + MonthlyRequests int64 `json:"monthlyRequests"` + CreatedAt int64 `json:"createdAt"` +} + +// ResetAPIKeysReq 重置API密钥请求 +type ResetAPIKeysReq struct { + g.Meta `path:"/applications-reset-keys" method:"post" summary:"重置API密钥"` + + ID int64 `json:"id" v:"required#应用ID不能为空"` +} + +// ResetAPIKeysRes 重置API密钥响应 +type ResetAPIKeysRes struct { + AppKey string `json:"appKey"` + AppSecret string `json:"appSecret"` +} + +// ValidateApplicationReq 验证应用请求 +type ValidateApplicationReq struct { + g.Meta `path:"/applications-validate" method:"post" summary:"验证应用权限"` + + AppKey string `json:"appKey" v:"required#应用密钥不能为空"` + AppSecret string `json:"appSecret" v:"required#应用密钥不能为空"` +} + +// ValidateApplicationRes 验证应用响应 +type ValidateApplicationRes struct { + Valid bool `json:"valid"` + AppID int64 `json:"appId"` + AppName string `json:"appName"` + TenantID int64 `json:"tenantId"` + TenantName string `json:"tenantName"` + Platform string `json:"platform"` + AdTypes []string `json:"adTypes"` +} + +// DeleteApplicationReq 删除应用请求 +type DeleteApplicationReq struct { + g.Meta `path:"/applications" method:"delete" summary:"删除应用"` + + ID int64 `json:"id" v:"required#应用ID不能为空"` +} + +// DeleteApplicationRes 删除应用响应 +type DeleteApplicationRes struct { + Success bool `json:"success"` +} diff --git a/model/dto/cid_dto.go b/model/dto/cid_dto.go index 35474aa..721b82b 100644 --- a/model/dto/cid_dto.go +++ b/model/dto/cid_dto.go @@ -6,7 +6,7 @@ import ( // GenerateCIDReq 生成CID请求 type GenerateCIDReq struct { - g.Meta `path:"/cid/generate" method:"post" tags:"CID服务" summary:"生成CID广告" dc:"为当前用户生成CID广告"` + g.Meta `path:"/cid-generate" method:"post" tags:"CID服务" summary:"生成CID广告" dc:"为当前用户生成CID广告"` UserId int64 `json:"user_id"` // 用户ID(可选,如果不提供则从token获取) RequestType string `json:"request_type"` // 请求类型 Parameters map[string]interface{} `json:"parameters"` // 请求参数 @@ -38,7 +38,7 @@ type GenerateCIDRes struct { // GetCIDStatisticsReq 获取CID统计请求 type GetCIDStatisticsReq struct { - g.Meta `path:"/cid/statistics" method:"get" tags:"CID服务" summary:"获取CID统计" dc:"获取CID服务的统计信息"` + g.Meta `path:"/cid-statistics" method:"get" tags:"CID服务" summary:"获取CID统计" dc:"获取CID服务的统计信息"` UserId int64 `json:"user_id"` // 用户ID(可选) TenantId int64 `json:"tenant_id"` // 租户ID(可选) DateFrom int64 `json:"date_from"` // 开始日期 @@ -67,7 +67,7 @@ type CIDRequestHistory struct { // GetCIDHistoryReq 获取CID历史请求 type GetCIDHistoryReq struct { - g.Meta `path:"/cid/history" method:"get" tags:"CID服务" summary:"获取CID历史记录" dc:"分页获取用户的CID请求历史"` + g.Meta `path:"/cid-history" method:"get" tags:"CID服务" summary:"获取CID历史记录" dc:"分页获取用户的CID请求历史"` Page int `json:"page" v:"required|min:1"` // 页码 Size int `json:"size" v:"required|min:1|max:100"` // 每页数量 } diff --git a/model/dto/rate_limit_dto.go b/model/dto/rate_limit_dto.go new file mode 100644 index 0000000..e8dc770 --- /dev/null +++ b/model/dto/rate_limit_dto.go @@ -0,0 +1,35 @@ +package dto + +import ( + "github.com/gogf/gf/v2/frame/g" +) + +// SetTenantRateLimitReq 设置租户限流配置请求 +type SetTenantRateLimitReq struct { + g.Meta `path:"/ratelimit-tenant-set" method:"post" tags:"租户限流" summary:"设置租户限流配置" dc:"设置指定租户的请求次数限制配置(实际使用全局配置)"` + + TenantID int64 `json:"tenant_id" v:"required"` // 租户ID(仅用于记录,实际使用全局配置) + RequestsPerSecond float64 `json:"requests_per_second" v:"required"` // 每秒请求数 + Burst int `json:"burst" v:"required"` // 突发请求数 + WindowSeconds int `json:"window_seconds" v:"required"` // 时间窗口(秒) +} + +// SetTenantRateLimitRes 设置租户限流配置响应 +type SetTenantRateLimitRes struct { + Success bool `json:"success"` // 是否成功 +} + +// GetTenantRateLimitUsageReq 获取租户限流使用情况请求 +type GetTenantRateLimitUsageReq struct { + g.Meta `path:"/ratelimit-tenant-usage" method:"get" tags:"租户限流" summary:"获取租户限流使用情况" dc:"获取指定租户的请求次数使用情况"` + + TenantID int64 `json:"tenant_id" v:"required"` // 租户ID +} + +// GetTenantRateLimitUsageRes 获取租户限流使用情况响应 +type GetTenantRateLimitUsageRes struct { + TenantID int64 `json:"tenant_id"` // 租户ID + CurrentUsed int64 `json:"current_used"` // 当前已使用请求数 + MaxAllowed int64 `json:"max_allowed"` // 最大允许请求数(基于全局配置) + UsagePercent float64 `json:"usage_percent"` // 使用率百分比 +} diff --git a/model/dto/report_dto.go b/model/dto/report_dto.go index 198a688..8451ac9 100644 --- a/model/dto/report_dto.go +++ b/model/dto/report_dto.go @@ -1,7 +1,7 @@ package dto import ( - "cidService/model/entity" + "cidservice/model/entity" "gitee.com/red-future---jilin-g/common/http" "github.com/gogf/gf/v2/frame/g" diff --git a/model/dto/stat_report_dto.go b/model/dto/stat_report_dto.go new file mode 100644 index 0000000..367d6d2 --- /dev/null +++ b/model/dto/stat_report_dto.go @@ -0,0 +1,168 @@ +package dto + +import "github.com/gogf/gf/v2/frame/g" + +// 报表生成请求 +type ReportGenerateReq struct { + g.Meta `path:"/report/generate" method:"post"` + TenantID int64 `json:"tenant_id" v:"required"` + AppID int64 `json:"app_id"` + ReportType string `json:"report_type" v:"required|in:daily,monthly,quarterly,yearly"` + Date string `json:"date"` // 格式: 2024-01-01 (daily), 2024-01 (monthly), 2024-Q1 (quarterly), 2024 (yearly) +} + +// 报表生成响应 +type ReportGenerateResp struct { + ReportID int64 `json:"report_id"` + ReportType string `json:"report_type"` + ReportDate string `json:"report_date"` + Data interface{} `json:"data"` +} + +// 报表列表请求 +type ReportListReq struct { + g.Meta `path:"/report/list" 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:"/report/detail" method:"get"` + ReportID int64 `json:"report_id" v:"required"` +} + +// 报表详情响应 +type ReportDetailResp struct { + ID int64 `json:"id"` + TenantID int64 `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 int64 `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:"/stat/query" 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:"/stat/realtime" method:"get"` + TenantID int64 `json:"tenant_id" v:"required"` + AppID int64 `json:"app_id"` + Hours int `json:"hours" d:"24"` // 过去多少小时的统计 +} + +// 实时统计响应 +type RealTimeStatResp struct { + CurrentHour *HourlyStat `json:"current_hour"` + Last24Hours []*HourlyStat `json:"last_24_hours"` +} + +// 小时统计 +type HourlyStat struct { + Hour string `json:"hour"` + Impressions int64 `json:"impressions"` + Clicks int64 `json:"clicks"` + Revenue float64 `json:"revenue"` +} + +// Controller响应结构 + +// 生成报表响应 +type ReportGenerateRes struct { + Data *ReportGenerateResp `json:"data"` +} + +// 报表列表响应 +type ReportListRes struct { + Data *ReportListResp `json:"data"` +} + +// 报表详情响应 +type ReportDetailRes struct { + Data *ReportDetailResp `json:"data"` +} + +// 统计查询响应 +type StatQueryRes struct { + Data *StatQueryResp `json:"data"` +} + +// 实时统计响应 +type RealTimeStatRes struct { + Data *RealTimeStatResp `json:"data"` +} + +// 导出报表响应 +type ExportReportRes struct { + ReportData *ReportDetailResp `json:"report_data"` +} diff --git a/model/dto/strategy_dto.go b/model/dto/strategy_dto.go index c85488c..99e989e 100644 --- a/model/dto/strategy_dto.go +++ b/model/dto/strategy_dto.go @@ -6,7 +6,7 @@ import ( // CreateStrategyReq 创建策略请求 type CreateStrategyReq struct { - g.Meta `path:"/strategy/create" method:"post" tags:"策略管理" summary:"创建匹配策略" dc:"创建新的广告匹配策略"` + g.Meta `path:"/strategy" method:"post" tags:"策略管理" summary:"创建匹配策略" dc:"创建新的广告匹配策略"` Name string `json:"name" v:"required|length:3,50"` // 策略名称 Description string `json:"description" v:"max:500"` // 描述 TenantLevel string `json:"tenant_level" v:"required|in:basic,standard,premium"` // 租户级别 @@ -14,13 +14,14 @@ type CreateStrategyReq struct { MaxConversion float64 `json:"max_conversion" v:"required|min:0|max:1"` // 最高转化率 SourceWeights map[string]int `json:"source_weights" v:"required"` // 广告源权重 MaxAdsPerReq int `json:"max_ads_per_req" v:"required|min:1|max:50"` // 每次请求最大广告数 + MaxReqPerHour int `json:"max_req_per_hour" v:"required|min:1"` // 每小时最大请求次数 Priority int `json:"priority" v:"required|min:0|max:100"` // 优先级 Status string `json:"status" v:"required|in:active,inactive"` // 状态 } // UpdateStrategyReq 更新策略请求 type UpdateStrategyReq struct { - g.Meta `path:"/strategy/update" method:"put" tags:"策略管理" summary:"更新匹配策略" dc:"更新现有的广告匹配策略"` + g.Meta `path:"/strategy" method:"put" tags:"策略管理" summary:"更新匹配策略" dc:"更新现有的广告匹配策略"` Id int64 `json:"id" v:"required"` // 策略ID Name string `json:"name" v:"required|length:3,50"` // 策略名称 Description string `json:"description" v:"max:500"` // 描述 @@ -29,25 +30,26 @@ type UpdateStrategyReq struct { MaxConversion float64 `json:"max_conversion" v:"required|min:0|max:1"` // 最高转化率 SourceWeights map[string]int `json:"source_weights" v:"required"` // 广告源权重 MaxAdsPerReq int `json:"max_ads_per_req" v:"required|min:1|max:50"` // 每次请求最大广告数 + MaxReqPerHour int `json:"max_req_per_hour" v:"required|min:1"` // 每小时最大请求次数 Priority int `json:"priority" v:"required|min:0|max:100"` // 优先级 Status string `json:"status" v:"required|in:active,inactive"` // 状态 } // DeleteStrategyReq 删除策略请求 type DeleteStrategyReq struct { - g.Meta `path:"/strategy/delete" method:"delete" tags:"策略管理" summary:"删除匹配策略" dc:"删除指定的广告匹配策略"` + g.Meta `path:"/strategy" method:"delete" tags:"策略管理" summary:"删除匹配策略" dc:"删除指定的广告匹配策略"` Id int64 `json:"id" v:"required"` // 策略ID } // GetStrategyReq 获取策略请求 type GetStrategyReq struct { - g.Meta `path:"/strategy/info" method:"get" tags:"策略管理" summary:"获取策略详情" dc:"获取指定策略的详细信息"` + g.Meta `path:"/strategy" method:"get" tags:"策略管理" summary:"获取策略详情" dc:"获取指定策略的详细信息"` Id int64 `json:"id" v:"required"` // 策略ID } // GetStrategyListReq 获取策略列表请求 type GetStrategyListReq struct { - g.Meta `path:"/strategy/list" method:"get" tags:"策略管理" summary:"获取策略列表" dc:"分页获取策略列表"` + g.Meta `path:"/strategies" method:"get" tags:"策略管理" summary:"获取策略列表" dc:"分页获取策略列表"` Page int `json:"page" v:"required|min:1"` // 页码 Size int `json:"size" v:"required|min:1|max:100"` // 每页数量 TenantLevel string `json:"tenant_level"` // 租户级别筛选 @@ -56,20 +58,21 @@ type GetStrategyListReq struct { // StrategyRes 策略响应 type StrategyRes struct { - Id int64 `json:"id"` // ID - Name string `json:"name"` // 策略名称 - Description string `json:"description"` // 描述 - TenantLevel string `json:"tenant_level"` // 租户级别 - MinConversion float64 `json:"min_conversion"` // 最低转化率 - MaxConversion float64 `json:"max_conversion"` // 最高转化率 - SourceWeights map[string]int `json:"source_weights"` // 广告源权重 - MaxAdsPerReq int `json:"max_ads_per_req"` // 每次请求最大广告数 - Priority int `json:"priority"` // 优先级 - Status string `json:"status"` // 状态 - CreatedAt string `json:"created_at"` // 创建时间 - UpdatedAt string `json:"updated_at"` // 更新时间 - CreatedBy int64 `json:"created_by"` // 创建人 - UpdatedBy int64 `json:"updated_by"` // 更新人 + Id int64 `json:"id"` // ID + Name string `json:"name"` // 策略名称 + Description string `json:"description"` // 描述 + TenantLevel string `json:"tenant_level"` // 租户级别 + MinConversion float64 `json:"min_conversion"` // 最低转化率 + MaxConversion float64 `json:"max_conversion"` // 最高转化率 + SourceWeights map[string]int `json:"source_weights"` // 广告源权重 + MaxAdsPerReq int `json:"max_ads_per_req"` // 每次请求最大广告数 + MaxReqPerHour int `json:"max_req_per_hour"` // 每小时最大请求次数 + Priority int `json:"priority"` // 优先级 + Status string `json:"status"` // 状态 + CreatedAt string `json:"created_at"` // 创建时间 + UpdatedAt string `json:"updated_at"` // 更新时间 + CreatedBy int64 `json:"created_by"` // 创建人 + UpdatedBy int64 `json:"updated_by"` // 更新人 } // GetStrategyListRes 获取策略列表响应 diff --git a/model/entity/ad_source.go b/model/entity/ad_source.go index ebcb649..0500b93 100644 --- a/model/entity/ad_source.go +++ b/model/entity/ad_source.go @@ -1,163 +1,167 @@ package entity import ( - "gitee.com/red-future---jilin-g/common/do" + "time" ) -const AdSourceCollection = "ad_source" - // AdSource 广告源实体 type AdSource struct { - do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + Id int64 `json:"id"` // 主键ID + CreatedAt time.Time `json:"createdAt"` // 创建时间 + UpdatedAt time.Time `json:"updatedAt"` // 更新时间 + Creator string `json:"creator"` // 创建者 + Updater string `json:"updater"` // 更新者 + TenantId int64 `json:"tenantId"` // 租户ID + IsDeleted bool `json:"isDeleted"` // 是否删除 // 基本信息 - Name string `bson:"name" json:"name"` // 广告源名称 - Code string `bson:"code" json:"code"` // 广告源编码,唯一标识 - Provider string `bson:"provider" json:"provider"` // 提供商:google、facebook、baidu、tencent、self等 - Type string `bson:"type" json:"type"` // 类型:self(自营)、third_party(第三方)、exchange(广告交易平台) - Description string `bson:"description" json:"description"` // 描述 + Name string `json:"name"` // 广告源名称 + Code string `json:"code"` // 广告源编码,唯一标识 + Provider string `json:"provider"` // 提供商:google、facebook、baidu、tencent、self等 + Type string `json:"type"` // 类型:self(自营)、third_party(第三方)、exchange(广告交易平台) + Description string `json:"description"` // 描述 // 连接配置 - Config *AdSourceConfig `bson:"config" json:"config"` // 广告源配置 + Config string `json:"config"` // 广告源配置(JSON字符串) // API配置 - APIEndpoint string `bson:"apiEndpoint" json:"apiEndpoint"` // API端点 - APIVersion string `bson:"apiVersion" json:"apiVersion"` // API版本 - AuthType string `bson:"authType" json:"authType"` // 认证类型:api_key、oauth、basic - AuthConfig map[string]interface{} `bson:"authConfig" json:"authConfig"` // 认证配置 - Headers map[string]string `bson:"headers" json:"headers"` // 请求头配置 - Timeout int `bson:"timeout" json:"timeout"` // 超时时间(毫秒) - RetryCount int `bson:"retryCount" json:"retryCount"` // 重试次数 + APIEndpoint string `json:"apiEndpoint"` // API端点 + APIVersion string `json:"apiVersion"` // API版本 + AuthType string `json:"authType"` // 认证类型:api_key、oauth、basic + AuthConfig string `json:"authConfig"` // 认证配置(JSON字符串) + Headers string `json:"headers"` // 请求头配置(JSON字符串) + Timeout int `json:"timeout"` // 超时时间(毫秒) + RetryCount int `json:"retryCount"` // 重试次数 // 广告源能力 - Capabilities *AdSourceCapabilities `bson:"capabilities" json:"capabilities"` // 广告源能力 + Capabilities string `json:"capabilities"` // 广告源能力(JSON字符串) // 质量指标 - QualityMetrics *AdSourceQualityMetrics `bson:"qualityMetrics" json:"qualityMetrics"` // 质量指标 + QualityMetrics string `json:"qualityMetrics"` // 质量指标(JSON字符串) // 财务设置 - PaymentTerms *PaymentTerms `bson:"paymentTerms" json:"paymentTerms"` // 支付条款 + PaymentTerms string `json:"paymentTerms"` // 支付条款(JSON字符串) // 状态信息 - Status string `bson:"status" json:"status"` // 广告源状态:active、inactive、maintenance - Health string `bson:"health" json:"health"` // 健康状态:healthy、degraded、unhealthy - LastCheckAt int64 `bson:"lastCheckAt" json:"lastCheckAt"` // 最后检查时间 + Status string `json:"status"` // 广告源状态:active、inactive、maintenance + Health string `json:"health"` // 健康状态:healthy、degraded、unhealthy + LastCheckAt int64 `json:"lastCheckAt"` // 最后检查时间 // 统计信息 - TotalRequests int64 `bson:"totalRequests" json:"totalRequests"` // 总请求数 - SuccessfulRequests int64 `bson:"successfulRequests" json:"successfulRequests"` // 成功请求数 - FailedRequests int64 `bson:"failedRequests" json:"failedRequests"` // 失败请求数 - AverageResponseTime float64 `bson:"averageResponseTime" json:"averageResponseTime"` // 平均响应时间(毫秒) - FillRate float64 `bson:"fillRate" json:"fillRate"` // 填充率 - CTR float64 `bson:"ctr" json:"ctr"` // 点击率 - CVR float64 `bson:"cvr" json:"cvr"` // 转化率 + 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 `bson:"priority" json:"priority"` // 优先级,数值越高优先级越高 + Priority int `json:"priority"` // 优先级,数值越高优先级越高 } // AdSourceConfig 广告源配置 type AdSourceConfig struct { // 基础配置 - SupportedFormats []string `bson:"supportedFormats" json:"supportedFormats"` // 支持的广告格式 - SupportedSizes []string `bson:"supportedSizes" json:"supportedSizes"` // 支持的尺寸 - SupportedDevices []string `bson:"supportedDevices" json:"supportedDevices"` // 支持的设备类型 - SupportedOS []string `bson:"supportedOS" json:"supportedOS"` // 支持的操作系统 - SupportedCountries []string `bson:"supportedCountries" json:"supportedCountries"` // 支持的国家/地区 + SupportedFormats []string `json:"supportedFormats"` // 支持的广告格式 + SupportedSizes []string `json:"supportedSizes"` // 支持的尺寸 + SupportedDevices []string `json:"supportedDevices"` // 支持的设备类型 + SupportedOS []string `json:"supportedOS"` // 支持的操作系统 + SupportedCountries []string `json:"supportedCountries"` // 支持的国家/地区 // 竞价配置 - BiddingType string `bson:"biddingType" json:"biddingType"` // 竞价类型:cpm、cpc、cpa、rtb - MinBidAmount int64 `bson:"minBidAmount" json:"minBidAmount"` // 最小出价(分) - MaxBidAmount int64 `bson:"maxBidAmount" json:"maxBidAmount"` // 最大出价(分) - BidIncrement int64 `bson:"bidIncrement" json:"bidIncrement"` // 出价增量(分) - DefaultBidAmount int64 `bson:"defaultBidAmount" json:"defaultBidAmount"` // 默认出价(分) - AutoOptimization bool `bson:"autoOptimization" json:"autoOptimization"` // 是否自动优化 + 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 `bson:"targetingSupport" json:"targetingSupport"` // 定向支持 + TargetingSupport *TargetingSupport `json:"targetingSupport"` // 定向支持 // 其他配置 - MaxAdsPerRequest int `bson:"maxAdsPerRequest" json:"maxAdsPerRequest"` // 单次请求最大广告数量 - BrandSafety bool `bson:"brandSafety" json:"brandSafety"` // 品牌安全 - Viewability bool `bson:"viewability" json:"viewability"` // 可见性支持 + MaxAdsPerRequest int `json:"maxAdsPerRequest"` // 单次请求最大广告数量 + BrandSafety bool `json:"brandSafety"` // 品牌安全 + Viewability bool `json:"viewability"` // 可见性支持 } // TargetingSupport 定向支持 type TargetingSupport struct { - GeoTargeting bool `bson:"geoTargeting" json:"geoTargeting"` // 地理定向 - DemographicTargeting bool `bson:"demographicTargeting" json:"demographicTargeting"` // 人口统计定向 - BehavioralTargeting bool `bson:"behavioralTargeting" json:"behavioralTargeting"` // 行为定向 - ContextualTargeting bool `bson:"contextualTargeting" json:"contextualTargeting"` // 上下文定向 - DeviceTargeting bool `bson:"deviceTargeting" json:"deviceTargeting"` // 设备定向 - TimeTargeting bool `bson:"timeTargeting" json:"timeTargeting"` // 时间定向 - Retargeting bool `bson:"retargeting" json:"retargeting"` // 重定向 - CookieTargeting bool `bson:"cookieTargeting" json:"cookieTargeting"` // Cookie定向 + 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 广告源能力 type AdSourceCapabilities struct { // 广告格式 - SupportedFormats []AdFormat `bson:"supportedFormats" json:"supportedFormats"` // 支持的广告格式 + SupportedFormats []AdFormat `json:"supportedFormats"` // 支持的广告格式 // 功能特性 - RealTimeBidding bool `bson:"realTimeBidding" json:"realTimeBidding"` // 实时竞价 - HeaderBidding bool `bson:"headerBidding" json:"headerBidding"` // 标题竞价 - ProgrammaticDirect bool `bson:"programmaticDirect" json:"programmaticDirect"` // 程序化直购 - PrivateMarketplace bool `bson:"privateMarketplace" json:"privateMarketplace"` // 私有交易市场 + RealTimeBidding bool `json:"realTimeBidding"` // 实时竞价 + HeaderBidding bool `json:"headerBidding"` // 标题竞价 + ProgrammaticDirect bool `json:"programmaticDirect"` // 程序化直购 + PrivateMarketplace bool `json:"privateMarketplace"` // 私有交易市场 // 质量控制 - FraudDetection bool `bson:"fraudDetection" json:"fraudDetection"` // 反欺诈检测 - BrandSafety bool `bson:"brandSafety" json:"brandSafety"` // 品牌安全 - Viewability bool `bson:"viewability" json:"viewability"` // 可见度验证 - CreativeApproval bool `bson:"creativeApproval" json:"creativeApproval"` // 创意审核 + FraudDetection bool `json:"fraudDetection"` // 反欺诈检测 + BrandSafety bool `json:"brandSafety"` // 品牌安全 + Viewability bool `json:"viewability"` // 可见度验证 + CreativeApproval bool `json:"creativeApproval"` // 创意审核 // 数据能力 - AudienceTargeting bool `bson:"audienceTargeting" json:"audienceTargeting"` // 受众定向 - ContextualTargeting bool `bson:"contextualTargeting" json:"contextualTargeting"` // 上下文定向 - CrossDeviceTargeting bool `bson:"crossDeviceTargeting" json:"crossDeviceTargeting"` // 跨设备定向 + AudienceTargeting bool `json:"audienceTargeting"` // 受众定向 + ContextualTargeting bool `json:"contextualTargeting"` // 上下文定向 + CrossDeviceTargeting bool `json:"crossDeviceTargeting"` // 跨设备定向 } // AdFormat 广告格式 type AdFormat struct { - Type string `bson:"type" json:"type"` // 格式类型:banner、video、native、interstitial等 - Name string `bson:"name" json:"name"` // 格式名称 - Width int `bson:"width" json:"width"` // 宽度 - Height int `bson:"height" json:"height"` // 高度 - MimeType string `bson:"mimeType" json:"mimeType"` // MIME类型 + Type string `json:"type"` // 格式类型:banner、video、native、interstitial等 + Name string `json:"name"` // 格式名称 + Width int `json:"width"` // 宽度 + Height int `json:"height"` // 高度 + MimeType string `json:"mimeType"` // MIME类型 } // AdSourceQualityMetrics 广告源质量指标 type AdSourceQualityMetrics struct { // 性能指标 - AverageResponseTime float64 `bson:"averageResponseTime" json:"averageResponseTime"` // 平均响应时间(毫秒) - SuccessRate float64 `bson:"successRate" json:"successRate"` // 成功率 - ErrorRate float64 `bson:"errorRate" json:"errorRate"` // 错误率 - Uptime float64 `bson:"uptime" json:"uptime"` // 可用性(百分比) + AverageResponseTime float64 `json:"averageResponseTime"` // 平均响应时间(毫秒) + SuccessRate float64 `json:"successRate"` // 成功率 + ErrorRate float64 `json:"errorRate"` // 错误率 + Uptime float64 `json:"uptime"` // 可用性(百分比) // 广告质量 - CTR float64 `bson:"ctr" json:"ctr"` // 点击率 - CVR float64 `bson:"cvr" json:"cvr"` // 转化率 - FillRate float64 `bson:"fillRate" json:"fillRate"` // 填充率 - ViewabilityRate float64 `bson:"viewabilityRate" json:"viewabilityRate"` // 可见率 - BrandSafetyScore float64 `bson:"brandSafetyScore" json:"brandSafetyScore"` // 品牌安全评分 + CTR float64 `json:"ctr"` // 点击率 + CVR float64 `json:"cvr"` // 转化率 + FillRate float64 `json:"fillRate"` // 填充率 + ViewabilityRate float64 `json:"viewabilityRate"` // 可见率 + BrandSafetyScore float64 `json:"brandSafetyScore"` // 品牌安全评分 // 财务指标 - eCPM int64 `bson:"ecpm" json:"ecpm"` // 有效千次展示成本(分) - eCPC int64 `bson:"ecpc" json:"ecpc"` // 有效点击成本(分) - RevenuePerRequest int64 `bson:"revenuePerRequest" json:"revenuePerRequest"` // 每请求收入(分) + Ecpm int64 `json:"ecpm"` // 有效千次展示成本(分) + Ecpc int64 `json:"ecpc"` // 有效点击成本(分) + RevenuePerRequest int64 `json:"revenuePerRequest"` // 每请求收入(分) // 时间指标 - LastUpdated int64 `bson:"lastUpdated" json:"lastUpdated"` // 最后更新时间 - MetricsUpdateWindow int `bson:"metricsUpdateWindow" json:"metricsUpdateWindow"` // 指标更新窗口(分钟) + LastUpdated int64 `json:"lastUpdated"` // 最后更新时间 + MetricsUpdateWindow int `json:"metricsUpdateWindow"` // 指标更新窗口(分钟) } // PaymentTerms 支付条款 type PaymentTerms struct { - BillingModel string `bson:"billingModel" json:"billingModel"` // 计费模式:cpm、cpc、cpa、rev_share - PaymentTerms string `bson:"paymentTerms" json:"paymentTerms"` // 支付条款:net_30、net_60、net_90 - RevShareRate float64 `bson:"revShareRate" json:"revShareRate"` // 收入分成比例(0-1) - MinPayment int64 `bson:"minPayment" json:"minPayment"` // 最小支付金额(分) - Currency string `bson:"currency" json:"currency"` // 货币单位 - TaxInclusive bool `bson:"taxInclusive" json:"taxInclusive"` // 是否含税 - EarlyPaymentDiscount float64 `bson:"earlyPaymentDiscount" json:"earlyPaymentDiscount"` // 提前付款折扣 + 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"` // 提前付款折扣 } diff --git a/model/entity/ad_type.go b/model/entity/ad_type.go new file mode 100644 index 0000000..3760cd2 --- /dev/null +++ b/model/entity/ad_type.go @@ -0,0 +1,44 @@ +package entity + +import ( + "gitee.com/red-future---jilin-g/common/do" +) + +const AdTypeCollection = "ad_type" + +// AdType 广告类型实体 +type AdType struct { + do.MongoBaseDO `bson:",inline"` + + // 广告类型信息 + Name string `bson:"name" json:"name"` // 广告类型名称 + Code string `bson:"code" json:"code"` // 广告类型编码 + Description string `bson:"description" json:"description"` // 广告类型描述 + Icon string `bson:"icon" json:"icon"` // 广告类型图标 + + // 类型配置 + Category string `bson:"category" json:"category"` // 分类:display, video, native, interstitial + Platforms []string `bson:"platforms" json:"platforms"` // 支持的平台 + Formats []string `bson:"formats" json:"formats"` // 支持格式 + Dimensions []string `bson:"dimensions" json:"dimensions"` // 尺寸规格 + + // 技术要求 + MaxFileSize int64 `bson:"maxFileSize" json:"maxFileSize"` // 最大文件大小(bytes) + MaxDuration int64 `bson:"maxDuration" json:"maxDuration"` // 最大时长(秒) + SupportedMimeTypes []string `bson:"supportedMimeTypes" json:"supportedMimeTypes"` // 支持的MIME类型 + + // 业务配置 + BidType string `bson:"bidType" json:"bidType"` // 竞价类型:CPM, CPC, CPA + MinBidPrice int64 `bson:"minBidPrice" json:"minBidPrice"` // 最低出价(分) + MaxBidPrice int64 `bson:"maxBidPrice" json:"maxBidPrice"` // 最高出价(分) + + // 状态信息 + Status string `bson:"status" json:"status"` // 状态:active, inactive + SortOrder int `bson:"sortOrder" json:"sortOrder"` // 排序顺序 + + // 统计信息 + DailyImpression int64 `bson:"dailyImpression" json:"dailyImpression"` // 日展示量 + DailyClick int64 `bson:"dailyClick" json:"dailyClick"` // 日点击量 + + Remark string `bson:"remark" json:"remark"` // 备注 +} diff --git a/model/entity/application.go b/model/entity/application.go new file mode 100644 index 0000000..079b924 --- /dev/null +++ b/model/entity/application.go @@ -0,0 +1,51 @@ +package entity + +import ( + "time" +) + +// Application 应用实体 +type Application struct { + Id int64 `json:"id"` // 主键ID + CreatedAt time.Time `json:"createdAt"` // 创建时间 + UpdatedAt time.Time `json:"updatedAt"` // 更新时间 + Creator string `json:"creator"` // 创建者 + Updater string `json:"updater"` // 更新者 + TenantId int64 `json:"tenantId"` // 租户ID + IsDeleted bool `json:"isDeleted"` // 是否删除 + + // 应用信息 + Name string `json:"name"` // 应用名称 + Code string `json:"code"` // 应用编码 + Description string `json:"description"` // 应用描述 + Icon string `json:"icon"` // 应用图标 + + // 平台信息 + Platform string `json:"platform"` // 平台:web, h5, android, ios + PackageName string `json:"packageName"` // 包名(Android)/Bundle ID(iOS) + AppStoreURL string `json:"appStoreUrl"` // 应用商店链接 + + // 配置信息 + Categories []string `json:"categories"` // 应用分类 + Tags []string `json:"tags"` // 应用标签 + Config string `json:"config"` // 应用配置(JSON格式) + AdTypes []string `json:"adTypes"` // 支持的广告类型 + + // 状态信息 + Status string `json:"status"` // 状态:active, inactive, audit + AuditStatus string `json:"auditStatus"` // 审核状态 + AuditReason string `json:"auditReason"` // 审核原因 + + // 统计信息 + DailyRequests int64 `json:"dailyRequests"` // 日请求量 + MonthlyRequests int64 `json:"monthlyRequests"` // 月请求量 + + // API密钥 + AppKey string `json:"appKey"` // 应用密钥 + AppSecret string `json:"appSecret"` // 应用密钥 + + // 回调信息 + CallbackURL string `json:"callbackUrl"` // 回调URL + + Remark string `json:"remark"` // 备注 +} diff --git a/model/entity/stat_report.go b/model/entity/stat_report.go new file mode 100644 index 0000000..1d06591 --- /dev/null +++ b/model/entity/stat_report.go @@ -0,0 +1,26 @@ +package entity + +import ( + "time" +) + +// StatReport 统计报表实体 +type StatReport struct { + Id int64 `json:"id"` // 主键ID + CreatedAt time.Time `json:"createdAt"` // 创建时间 + UpdatedAt time.Time `json:"updatedAt"` // 更新时间 + Creator string `json:"creator"` // 创建者 + Updater string `json:"updater"` // 更新者 + TenantId int64 `json:"tenantId"` // 租户ID + IsDeleted bool `json:"isDeleted"` // 是否删除 + + // 报表基本信息 + AppID int64 `json:"appId"` // 应用ID + ReportType string `json:"reportType"` // 报表类型:daily, weekly, monthly, quarterly, yearly + ReportDate time.Time `json:"reportDate"` // 报表日期 + GeneratedAt time.Time `json:"generatedAt"` // 生成时间 + ReportData string `json:"reportData"` // 报表数据(JSON格式) + + // 状态信息 + Status string `json:"status"` // 状态:generated, processing, completed +} diff --git a/model/entity/strategy.go b/model/entity/strategy.go index ac8a5a0..51652ec 100644 --- a/model/entity/strategy.go +++ b/model/entity/strategy.go @@ -6,18 +6,19 @@ import ( // Strategy 匹配策略表 type Strategy struct { - Id int64 `json:"id" orm:"id,primary"` // ID - Name string `json:"name" orm:"name"` // 策略名称 - Description string `json:"description" orm:"description"` // 描述 - TenantLevel string `json:"tenant_level" orm:"tenant_level"` // 适用租户级别 - MinConversion float64 `json:"min_conversion" orm:"min_conversion"` // 最低转化率 - MaxConversion float64 `json:"max_conversion" orm:"max_conversion"` // 最高转化率 - SourceWeights string `json:"source_weights" orm:"source_weights"` // 广告源权重 (JSON格式) - MaxAdsPerReq int `json:"max_ads_per_req" orm:"max_ads_per_req"` // 每次请求最大广告数 - Priority int `json:"priority" orm:"priority"` // 优先级 - Status string `json:"status" orm:"status"` // 状态: active, inactive - CreatedAt *gtime.Time `json:"created_at" orm:"created_at"` // 创建时间 - UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at"` // 更新时间 - CreatedBy int64 `json:"created_by" orm:"created_by"` // 创建人 - UpdatedBy int64 `json:"updated_by" orm:"updated_by"` // 更新人 + Id int64 `json:"id" orm:"id,primary"` // ID + Name string `json:"name" orm:"name"` // 策略名称 + Description string `json:"description" orm:"description"` // 描述 + TenantLevel string `json:"tenant_level" orm:"tenant_level"` // 适用租户级别 + MinConversion float64 `json:"min_conversion" orm:"min_conversion"` // 最低转化率 + MaxConversion float64 `json:"max_conversion" orm:"max_conversion"` // 最高转化率 + SourceWeights string `json:"source_weights" orm:"source_weights"` // 广告源权重 (JSON格式) + MaxAdsPerReq int `json:"max_ads_per_req" orm:"max_ads_per_req"` // 每次请求最大广告数 + MaxReqPerHour int `json:"max_req_per_hour" orm:"max_req_per_hour"` // 每小时最大请求次数 + Priority int `json:"priority" orm:"priority"` // 优先级 + Status string `json:"status" orm:"status"` // 状态: active, inactive + CreatedAt *gtime.Time `json:"created_at" orm:"created_at"` // 创建时间 + UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at"` // 更新时间 + CreatedBy int64 `json:"created_by" orm:"created_by"` // 创建人 + UpdatedBy int64 `json:"updated_by" orm:"updated_by"` // 更新人 } diff --git a/model/entity/tenant.go b/model/entity/tenant.go new file mode 100644 index 0000000..e83311a --- /dev/null +++ b/model/entity/tenant.go @@ -0,0 +1,39 @@ +package entity + +import ( + "gitee.com/red-future---jilin-g/common/do" +) + +const TenantCollection = "tenant" + +// Tenant 租户实体 +type Tenant struct { + do.MongoBaseDO `bson:",inline"` + + // 租户信息 + Name string `bson:"name" json:"name"` // 租户名称 + Code string `bson:"code" json:"code"` // 租户编码 + Description string `bson:"description" json:"description"` // 租户描述 + Logo string `bson:"logo" json:"logo"` // 租户Logo + Domain string `bson:"domain" json:"domain"` // 租户域名 + + // 联系信息 + ContactName string `bson:"contactName" json:"contactName"` // 联系人姓名 + ContactPhone string `bson:"contactPhone" json:"contactPhone"` // 联系电话 + ContactEmail string `bson:"contactEmail" json:"contactEmail"` // 联系邮箱 + + // 状态信息 + Status string `bson:"status" json:"status"` // 状态:active, inactive, suspended + PackageType string `bson:"packageType" json:"packageType"` // 套餐类型:basic, standard, premium + ExpireTime int64 `bson:"expireTime" json:"expireTime"` // 套餐到期时间 + + // 限制信息 + MaxApps int64 `bson:"maxApps" json:"maxApps"` // 最大应用数 + MaxUsers int64 `bson:"maxUsers" json:"maxUsers"` // 最大用户数 + MaxRequestPerDay int64 `bson:"maxRequestPerDay" json:"maxRequestPerDay"` // 每日最大请求数 + + // 配置信息 + Config map[string]interface{} `bson:"config" json:"config"` // 租户配置 + + Remark string `bson:"remark" json:"remark"` // 备注 +} diff --git a/service/ad_position_service.go b/service/ad_position_service.go index e7f59c9..6f16fec 100644 --- a/service/ad_position_service.go +++ b/service/ad_position_service.go @@ -1,9 +1,9 @@ package service import ( - "cidService/dao" - "cidService/model/dto" - "cidService/model/entity" + "cidservice/dao" + "cidservice/model/dto" + "cidservice/model/entity" "context" "time" diff --git a/service/ad_source_service.go b/service/ad_source_service.go index 921953f..25d82a1 100644 --- a/service/ad_source_service.go +++ b/service/ad_source_service.go @@ -1,11 +1,10 @@ package service import ( - "cidService/dao" - "cidService/model/dto" - "cidService/model/entity" + "cidservice/dao" + "cidservice/model/dto" + "cidservice/model/entity" "context" - "github.com/gogf/gf/v2/errors/gerror" ) diff --git a/service/ad_statistics_service.go b/service/ad_statistics_service.go index 95424eb..57129dd 100644 --- a/service/ad_statistics_service.go +++ b/service/ad_statistics_service.go @@ -6,9 +6,9 @@ import ( "sort" "time" - "cidService/dao" - "cidService/model/dto" - "cidService/model/entity" + "cidservice/dao" + "cidservice/model/dto" + "cidservice/model/entity" ) var AdStatistics = new(adStatistics) diff --git a/service/advertisement_service.go b/service/advertisement_service.go index 59cc1b4..caac880 100644 --- a/service/advertisement_service.go +++ b/service/advertisement_service.go @@ -1,9 +1,9 @@ package service import ( - "cidService/dao" - "cidService/model/dto" - "cidService/model/entity" + "cidservice/dao" + "cidservice/model/dto" + "cidservice/model/entity" "context" "time" diff --git a/service/advertiser_service.go b/service/advertiser_service.go index 589da7b..9221934 100644 --- a/service/advertiser_service.go +++ b/service/advertiser_service.go @@ -7,9 +7,9 @@ import ( "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/util/gconv" - "cidService/dao" - "cidService/model/dto" - "cidService/model/entity" + "cidservice/dao" + "cidservice/model/dto" + "cidservice/model/entity" ) var Advertiser = new(advertiser) diff --git a/service/application_service.go b/service/application_service.go new file mode 100644 index 0000000..5c401d1 --- /dev/null +++ b/service/application_service.go @@ -0,0 +1,246 @@ +package service + +import ( + "context" + "crypto/rand" + "encoding/hex" + + "cidservice/dao" + "cidservice/model/dto" + "cidservice/model/entity" + + "github.com/gogf/gf/v2/errors/gerror" +) + +var ( + Application = applicationService{} +) + +type applicationService struct{} + +// CreateApplication 创建应用 +func (s *applicationService) CreateApplication(ctx context.Context, req *dto.CreateApplicationReq) (id int64, err error) { + // 检查应用名称是否已存在 + existingApp, err := dao.Application.GetByName(ctx, req.Name) + if err != nil { + return 0, err + } + if existingApp != nil { + return 0, gerror.New("应用名称已存在") + } + + // 生成API密钥 + appKey, appSecret, err := s.generateAPIKeys() + if err != nil { + return 0, err + } + + application := &entity.Application{ + TenantId: req.TenantID, + Name: req.Name, + Code: req.Code, + Description: req.Description, + Platform: req.Platform, + PackageName: req.PackageName, + AppStoreURL: req.AppStoreURL, + Categories: req.Categories, + Tags: req.Tags, + AdTypes: req.AdTypes, + Status: "active", + AppKey: appKey, + AppSecret: appSecret, + CallbackURL: req.CallbackURL, + } + + return dao.Application.Create(ctx, application) +} + +// UpdateApplication 更新应用 +func (s *applicationService) UpdateApplication(ctx context.Context, id int64, req *dto.UpdateApplicationReq) (affected int64, err error) { + // 检查应用是否存在 + existingApp, err := dao.Application.GetByID(ctx, id) + if err != nil { + return 0, err + } + if existingApp == nil { + return 0, gerror.New("应用不存在") + } + + // 如果更新名称,检查是否与其他应用冲突 + if req.Name != "" && req.Name != existingApp.Name { + conflictApp, err := dao.Application.GetByName(ctx, req.Name) + if err != nil { + return 0, err + } + if conflictApp != nil && conflictApp.Id != id { + return 0, gerror.New("应用名称已存在") + } + } + + // 构建更新数据 + updateData := &entity.Application{} + if req.Name != "" { + updateData.Name = req.Name + } + if req.Description != "" { + updateData.Description = req.Description + } + if req.Platform != "" { + updateData.Platform = req.Platform + } + if req.PackageName != "" { + updateData.PackageName = req.PackageName + } + if req.AppStoreURL != "" { + updateData.AppStoreURL = req.AppStoreURL + } + if req.CallbackURL != "" { + updateData.CallbackURL = req.CallbackURL + } + if len(req.Categories) > 0 { + updateData.Categories = req.Categories + } + if len(req.Tags) > 0 { + updateData.Tags = req.Tags + } + if len(req.AdTypes) > 0 { + updateData.AdTypes = req.AdTypes + } + + // 使用Update方法更新应用 + err = dao.Application.Update(ctx, updateData) + if err != nil { + return 0, err + } + return 1, nil +} + +// GetApplicationsByTenant 获取租户下的应用列表 +func (s *applicationService) GetApplicationsByTenant(ctx context.Context, tenantID int64, platform, status string, page, size int) (list []*entity.Application, total int64, err error) { + // 构建过滤条件 + filter := make(map[string]interface{}) + filter["tenant_id"] = tenantID + if platform != "" { + filter["platform"] = platform + } + if status != "" { + filter["status"] = status + } + + // 调用DAO的GetByTenantID方法获取租户下的所有应用 + apps, err := dao.Application.GetByTenantID(ctx, tenantID) + if err != nil { + return nil, 0, err + } + + // 应用额外的过滤条件 + var filteredApps []*entity.Application + for _, app := range apps { + if platform != "" && app.Platform != platform { + continue + } + if status != "" && app.Status != status { + continue + } + filteredApps = append(filteredApps, app) + } + + // 实现简单的分页 + startIndex := (page - 1) * size + endIndex := startIndex + size + if startIndex >= len(filteredApps) { + return []*entity.Application{}, int64(len(filteredApps)), nil + } + if endIndex > len(filteredApps) { + endIndex = len(filteredApps) + } + + return filteredApps[startIndex:endIndex], int64(len(filteredApps)), nil +} + +// GetApplicationByKey 根据API密钥获取应用 +func (s *applicationService) GetApplicationByKey(ctx context.Context, appKey string) (application *entity.Application, err error) { + return dao.Application.GetByAPIKey(ctx, appKey) +} + +// GetApplicationByID 根据ID获取应用 +func (s *applicationService) GetApplicationByID(ctx context.Context, id int64) (application *entity.Application, err error) { + return dao.Application.GetByID(ctx, id) +} + +// DeleteApplication 删除应用 +func (s *applicationService) DeleteApplication(ctx context.Context, id int64) (affected int64, err error) { + err = dao.Application.Delete(ctx, id) + if err != nil { + return 0, err + } + return 1, nil +} + +// ValidateApplication 验证应用权限 +func (s *applicationService) ValidateApplication(ctx context.Context, appKey, appSecret string) (application *entity.Application, err error) { + app, err := dao.Application.GetByAPIKey(ctx, appKey) + if err != nil { + return nil, err + } + if app == nil { + return nil, gerror.New("应用不存在") + } + if app.Status != "active" { + return nil, gerror.New("应用状态异常") + } + if app.AppSecret != appSecret { + return nil, gerror.New("密钥验证失败") + } + + return app, nil +} + +// generateAPIKeys 生成API密钥 +func (s *applicationService) generateAPIKeys() (appKey, appSecret string, err error) { + // 生成32位随机字符串作为AppKey + keyBytes := make([]byte, 16) + if _, err := rand.Read(keyBytes); err != nil { + return "", "", err + } + appKey = hex.EncodeToString(keyBytes) + + // 生成64位随机字符串作为AppSecret + secretBytes := make([]byte, 32) + if _, err := rand.Read(secretBytes); err != nil { + return "", "", err + } + appSecret = hex.EncodeToString(secretBytes) + + return appKey, appSecret, nil +} + +// ResetAPIKeys 重置API密钥 +func (s *applicationService) ResetAPIKeys(ctx context.Context, id int64) (appKey, appSecret string, err error) { + // 检查应用是否存在 + existingApp, err := dao.Application.GetByID(ctx, id) + if err != nil { + return "", "", err + } + if existingApp == nil { + return "", "", gerror.New("应用不存在") + } + + // 生成新的API密钥 + appKey, appSecret, err = s.generateAPIKeys() + if err != nil { + return "", "", err + } + + // 更新应用密钥 + updateData := &entity.Application{ + AppKey: appKey, + AppSecret: appSecret, + } + err = dao.Application.Update(ctx, updateData) + if err != nil { + return "", "", err + } + + return appKey, appSecret, nil +} diff --git a/service/cid_service.go b/service/cid_service.go index 7a86233..4be0d6f 100644 --- a/service/cid_service.go +++ b/service/cid_service.go @@ -1,10 +1,10 @@ package service import ( - "cidService/dao" - "cidService/model/dto" - "cidService/model/entity" - "cidService/model/types" + "cidservice/dao" + "cidservice/model/dto" + "cidservice/model/entity" + "cidservice/model/types" "context" "encoding/json" "fmt" @@ -85,6 +85,15 @@ func (s *cidService) GenerateCID(ctx context.Context, req *dto.GenerateCIDReq) ( return nil, gerror.Wrap(err, "获取租户信息失败") } + // 检查租户请求次数限制 + allowed, err := RateLimit.CheckTenantRequestLimit(ctx, tenant.Id, nil) + if err != nil { + return nil, gerror.Wrap(err, "检查租户请求限制失败") + } + if !allowed { + return nil, gerror.New("租户请求次数已超过限制,请稍后再试") + } + // 获取匹配策略 strategy, err := s.getMatchingStrategy(ctx, tenant.Level) if err != nil { diff --git a/service/rate_limit_service.go b/service/rate_limit_service.go new file mode 100644 index 0000000..0ed4c22 --- /dev/null +++ b/service/rate_limit_service.go @@ -0,0 +1,137 @@ +package service + +import ( + "context" + "fmt" + "time" + + "cidservice/consts" + + "github.com/gogf/gf/v2/frame/g" +) + +var ( + RateLimit = rateLimitService{} +) + +type rateLimitService struct{} + +// TenantRateLimitConfig 租户限流配置 +type TenantRateLimitConfig struct { + TenantID int64 // 租户ID + RequestsPerSecond float64 // 每秒请求数 + Burst int // 突发请求数 + Window time.Duration // 时间窗口 +} + +// CheckTenantRequestLimit 检查租户请求次数限制 +func (s *rateLimitService) CheckTenantRequestLimit(ctx context.Context, tenantID int64, config *TenantRateLimitConfig) (bool, error) { + if config == nil { + // 使用默认配置 + config = s.getDefaultTenantRateLimitConfig(tenantID) + } + + // 构建Redis键 - 使用当前小时的键,确保按小时计数 + now := time.Now() + hourKey := fmt.Sprintf("%s%d:%d", consts.AdRequestLimitKeyPrefix, tenantID, now.Hour()) + + // 获取当前计数 + currentCountVar, err := g.Redis().Get(ctx, hourKey) + if err != nil && err.Error() != "redis: nil" { + return false, err + } + + currentCount := currentCountVar.Int64() + + // 如果是第一次请求,设置计数和过期时间(到下一个小时) + if currentCount == 0 { + // 设置过期时间为到下一个小时的剩余时间 + nextHour := now.Truncate(time.Hour).Add(time.Hour) + ttl := nextHour.Sub(now) + // 使用SetEX一次性设置值和过期时间 + err = g.Redis().SetEX(ctx, hourKey, 1, int64(ttl.Seconds())) + if err != nil { + return false, err + } + return true, nil + } + + // 检查是否超过限制 + maxRequests := int64(config.RequestsPerSecond * config.Window.Seconds()) + if currentCount >= maxRequests { + return false, nil + } + + // 增加计数 + _, err = g.Redis().Incr(ctx, hourKey) + if err != nil { + return false, err + } + + return true, nil +} + +// GetDefaultTenantRateLimitConfig 获取默认的租户限流配置 +func (s *rateLimitService) getDefaultTenantRateLimitConfig(tenantID int64) *TenantRateLimitConfig { + // 从配置文件中读取限流参数 + ctx := context.Background() + + // 检查是否启用租户限流 + enabled := g.Cfg().MustGet(ctx, "tenantRateLimit.enabled", false).Bool() + if !enabled { + // 如果未启用,返回一个很大的限制值,相当于不限制 + return &TenantRateLimitConfig{ + TenantID: tenantID, + RequestsPerSecond: 10000, // 每秒10000个请求,相当于不限制 + Burst: 20000, // 突发20000个请求 + Window: time.Hour, + } + } + + // 从配置文件中获取限流参数 + requestsPerHour := g.Cfg().MustGet(ctx, "tenantRateLimit.requestsPerHour", 3600).Int64() + windowSeconds := g.Cfg().MustGet(ctx, "tenantRateLimit.window", 3600).Int64() + burst := g.Cfg().MustGet(ctx, "tenantRateLimit.burst", 100).Int() + + // 转换为每秒请求数 + requestsPerSecond := float64(requestsPerHour) / float64(windowSeconds) + + return &TenantRateLimitConfig{ + TenantID: tenantID, + RequestsPerSecond: requestsPerSecond, + Burst: burst, + Window: time.Duration(windowSeconds) * time.Second, + } +} + +// SetTenantRateLimitConfig 设置租户限流配置 +func (s *rateLimitService) SetTenantRateLimitConfig(ctx context.Context, config *TenantRateLimitConfig) error { + // 注意:实际使用的是config.yml中的全局配置,此方法仅用于兼容旧API + // 实际限流参数请修改config.yml中的tenantRateLimit部分 + return nil +} + +// GetTenantCurrentUsage 获取租户当前请求使用情况 +func (s *rateLimitService) GetTenantCurrentUsage(ctx context.Context, tenantID int64, config *TenantRateLimitConfig) (current int64, max int64, err error) { + if config == nil { + config = s.getDefaultTenantRateLimitConfig(tenantID) + } + + // 构建当前小时的Redis键 + now := time.Now() + hourKey := fmt.Sprintf("%s%d:%d", consts.AdRequestLimitKeyPrefix, tenantID, now.Hour()) + + // 获取当前计数 + currentVar, err := g.Redis().Get(ctx, hourKey) + if err != nil && err.Error() == "redis: nil" { + current = 0 + err = nil + } else if err != nil { + return 0, 0, err + } else { + current = currentVar.Int64() + } + + max = int64(config.RequestsPerSecond * config.Window.Seconds()) + return current, max, nil +} diff --git a/service/report_service.go b/service/report_service.go index 674f2d8..f9a9695 100644 --- a/service/report_service.go +++ b/service/report_service.go @@ -6,9 +6,9 @@ import ( "github.com/gogf/gf/v2/errors/gerror" - "cidService/dao" - "cidService/model/dto" - "cidService/model/entity" + "cidservice/dao" + "cidservice/model/dto" + "cidservice/model/entity" ) // Report Service 单例 diff --git a/service/stat_report_service.go b/service/stat_report_service.go new file mode 100644 index 0000000..6dd2c7b --- /dev/null +++ b/service/stat_report_service.go @@ -0,0 +1,514 @@ +package service + +import ( + "context" + "fmt" + "time" + + "cidservice/dao" + "cidservice/model/dto" + "cidservice/model/entity" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" +) + +type StatReportService struct{} + +var StatReport = &StatReportService{} + +// 生成日报表 +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 + } + } + + // 生成日报表数据 + reportData, err := s.generateReportData(ctx, req.TenantID, req.AppID, "daily", reportDate) + if err != nil { + return nil, err + } + + // 保存报表 + report := &entity.StatReport{ + TenantId: req.TenantID, + AppID: req.AppID, + 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, + ReportType: "daily", + ReportDate: reportDate.Format("2006-01-02"), + Data: reportData, + }, nil +} + +// 生成月报表 +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 + } + } + + reportData, err := s.generateReportData(ctx, req.TenantID, req.AppID, "monthly", reportDate) + if err != nil { + return nil, err + } + + report := &entity.StatReport{ + TenantId: req.TenantID, + AppID: req.AppID, + 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, + ReportType: "monthly", + ReportDate: reportDate.Format("2006-01"), + 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 + } + } + + reportData, err := s.generateReportData(ctx, req.TenantID, req.AppID, "quarterly", reportDate) + if err != nil { + return nil, err + } + + report := &entity.StatReport{ + TenantId: req.TenantID, + AppID: req.AppID, + 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, + 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 + } + } + + reportData, err := s.generateReportData(ctx, req.TenantID, req.AppID, "yearly", reportDate) + if err != nil { + return nil, err + } + + report := &entity.StatReport{ + TenantId: req.TenantID, + AppID: req.AppID, + 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, + 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} + + // 查询基础统计数据 + var stats []map[string]interface{} + err := g.DB().Model("ad_statistics").Where(where).Scan(&stats) + if err != nil { + return nil, err + } + + // 计算环比数据 + 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, req.TenantID, req.AppID, 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 { + reportDTOs = append(reportDTOs, &dto.ReportDTO{ + ID: report.Id, + TenantID: report.TenantId, + AppID: report.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, reportID) + 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 + } + + return &dto.ReportDetailResp{ + ID: report.Id, + TenantID: report.TenantId, + AppID: report.AppID, + ReportType: report.ReportType, + ReportDate: report.ReportDate.Format("2006-01-02"), + GeneratedAt: report.GeneratedAt.Format("2006-01-02 15:04:05"), + Data: reportData, + }, nil +} diff --git a/service/strategy_service.go b/service/strategy_service.go index f434dbe..5edebe3 100644 --- a/service/strategy_service.go +++ b/service/strategy_service.go @@ -1,9 +1,9 @@ package service import ( - "cidService/dao" - "cidService/model/dto" - "cidService/model/entity" + "cidservice/dao" + "cidservice/model/dto" + "cidservice/model/entity" "context" "encoding/json" "strconv" diff --git a/test_config.go b/test_config.go new file mode 100644 index 0000000..83e2e80 --- /dev/null +++ b/test_config.go @@ -0,0 +1,33 @@ +package main + +import ( + "context" + "fmt" + + "cidservice/service" +) + +func main() { + ctx := context.Background() + + // 检查租户请求次数限制 + allowed, err := service.RateLimit.CheckTenantRequestLimit(ctx, 1, nil) + if err != nil { + fmt.Printf("错误: %v\n", err) + return + } + + fmt.Printf("租户请求是否允许: %v\n", allowed) + + // 获取租户当前使用情况 + current, max, err := service.RateLimit.GetTenantCurrentUsage(ctx, 1, nil) + if err != nil { + fmt.Printf("错误: %v\n", err) + return + } + + fmt.Printf("租户请求使用情况:\n") + fmt.Printf(" 当前使用: %d\n", current) + fmt.Printf(" 最大允许: %d\n", max) + fmt.Printf(" 使用率: %.2f%%\n", float64(current)/float64(max)*100) +}