From c9fcfc761e474e6ffa2bd894ea41c3102f12a015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Sat, 6 Dec 2025 09:10:24 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=9D=E5=A7=8B=E5=8C=96=E9=A1=B9=E7=9B=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 99 ++++++ consts/errors.go | 33 ++ consts/redis_key.go | 47 +++ controller/ad_position_controller.go | 70 ++++ controller/ad_statistics_controller.go | 34 ++ controller/advertisement_controller.go | 51 +++ controller/advertiser_controller.go | 71 +++++ controller/data_controller.go | 27 -- controller/report_controller.go | 52 +++ dao/ad_position_dao.go | 257 +++++++++++++++ dao/ad_statistics_dao.go | 193 +++++++++++ dao/advertisement_dao.go | 256 +++++++++++++++ dao/advertiser_dao.go | 298 +++++++++++++++++ dao/data_dao.go | 121 ------- dao/report_dao.go | 171 ++++++++++ go.mod | 79 ++++- go.sum | 425 +++++++++++++++++++++++++ main.go | 6 +- model/dto/ad_position_dto.go | 163 ++++++++++ model/dto/ad_statistics_dto.go | 113 +++++++ model/dto/advertisement_dto.go | 133 ++++++++ model/dto/advertiser_dto.go | 167 ++++++++++ model/dto/data_dto.go | 67 ---- model/dto/report_dto.go | 104 ++++++ model/entity/ad_position.go | 82 +++++ model/entity/ad_statistics.go | 92 ++++++ model/entity/advertisement.go | 91 ++++++ model/entity/advertiser.go | 50 +++ model/entity/data.go | 25 -- service/ad_position_service.go | 212 ++++++++++++ service/ad_statistics_service.go | 367 +++++++++++++++++++++ service/advertisement_service.go | 201 ++++++++++++ service/advertiser_service.go | 185 +++++++++++ service/data_service.go | 53 --- service/report_service.go | 183 +++++++++++ 35 files changed, 4283 insertions(+), 295 deletions(-) create mode 100644 README.md create mode 100644 controller/ad_position_controller.go create mode 100644 controller/ad_statistics_controller.go create mode 100644 controller/advertisement_controller.go create mode 100644 controller/advertiser_controller.go delete mode 100644 controller/data_controller.go create mode 100644 controller/report_controller.go create mode 100644 dao/ad_position_dao.go create mode 100644 dao/ad_statistics_dao.go create mode 100644 dao/advertisement_dao.go create mode 100644 dao/advertiser_dao.go delete mode 100644 dao/data_dao.go create mode 100644 dao/report_dao.go create mode 100644 go.sum create mode 100644 model/dto/ad_position_dto.go create mode 100644 model/dto/ad_statistics_dto.go create mode 100644 model/dto/advertisement_dto.go create mode 100644 model/dto/advertiser_dto.go delete mode 100644 model/dto/data_dto.go create mode 100644 model/dto/report_dto.go create mode 100644 model/entity/ad_position.go create mode 100644 model/entity/ad_statistics.go create mode 100644 model/entity/advertisement.go create mode 100644 model/entity/advertiser.go delete mode 100644 model/entity/data.go create mode 100644 service/ad_position_service.go create mode 100644 service/ad_statistics_service.go create mode 100644 service/advertisement_service.go create mode 100644 service/advertiser_service.go delete mode 100644 service/data_service.go create mode 100644 service/report_service.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..68f65df --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +# CID广告管理系统 + +## 项目简介 + +CID广告管理系统是一个完整的广告投放和管理平台,支持广告主管理、广告管理、广告位管理和数据统计分析等功能。 + +## 功能模块 + +### 1. 广告管理 +- 广告创建、编辑、删除 +- 广告审核流程 +- 广告状态管理 +- 广告投放设置 +- 广告定向设置 +- 广告统计数据 + +### 2. 广告主管理 +- 广告主注册、审核 +- 广告主信息维护 +- 广告主账户管理 +- 充值和授信额度管理 +- 广告主状态管理 + +### 3. 广告位管理 +- 广告位创建、编辑 +- 广告位状态管理 +- 广告位定价设置 +- 广告位展示规则 +- 广告位统计数据 + +### 4. 广告统计与报表 +- 广告效果统计 +- 数据可视化仪表盘 +- 自定义报表生成 +- 报表导出功能 +- 数据趋势分析 + +### 5. 广告匹配与投放 +- 基于用户画像的广告匹配 +- 实时广告投放 +- 广告频次控制 +- 广告预算控制 + +## 技术架构 + +### 技术栈 +- Go语言 + GoFrame框架 +- MongoDB 数据库 +- Redis 缓存 +- RESTful API设计 + +### 项目结构 +``` +cidservice/ +├── consts/ # 常量定义 +├── controller/ # 控制器层 +│ ├── advertisement_controller.go +│ ├── advertiser_controller.go +│ ├── ad_position_controller.go +│ └── ad_statistics_controller.go +├── dao/ # 数据访问层 +│ ├── advertisement_dao.go +│ ├── advertiser_dao.go +│ ├── ad_position_dao.go +│ └── ad_statistics_dao.go +├── model/ # 模型定义 +│ ├── dto/ # 数据传输对象 +│ └── entity/ # 实体对象 +└── service/ # 服务层 + ├── advertisement_service.go + ├── advertiser_service.go + ├── ad_position_service.go + └── ad_statistics_service.go +``` + +## 部署说明 + +1. 配置MongoDB和Redis连接 +2. 修改config.yml配置文件 +3. 执行`go run main.go`启动服务 +4. 服务默认运行在3002端口 + +## API文档 + +API文档可以通过Swagger或Postman查看,主要接口包括: + +- 广告管理:/advertisement/add, /advertisement/update, /advertisement/list等 +- 广告主管理:/advertiser/add, /advertiser/update, /advertiser/list等 +- 广告位管理:/adposition/add, /adposition/update, /adposition/list等 +- 广告统计:/statistics/list, /dashboard等 +- 报表管理:/report/create, /report/list, /report/download等 + +## 开发规范 + +项目遵循原customerservice项目的开发规范,包括分层架构、命名规范和错误处理等。 + +## 扩展说明 + +系统支持水平扩展,可以通过增加广告服务实例来提高处理能力,Redis用于缓存和队列,MongoDB用于数据持久化。 \ No newline at end of file diff --git a/consts/errors.go b/consts/errors.go index d709a2b..306c8f1 100644 --- a/consts/errors.go +++ b/consts/errors.go @@ -1 +1,34 @@ package consts + +// 广告管理错误码 +const ( + ErrAdNotFound = 1001 // 广告不存在 + ErrAdStatusInvalid = 1002 // 广告状态无效 + ErrAdAuditedRejected = 1003 // 广告审核被拒绝 + ErrAdBudgetInsufficient = 1004 // 广告预算不足 +) + +// 广告主管理错误码 +const ( + ErrAdvertiserNotFound = 2001 // 广告主不存在 + ErrAdvertiserStatusInvalid = 2002 // 广告主状态无效 + ErrAdvertiserAuditedRejected = 2003 // 广告主审核被拒绝 + ErrAdvertiserBalanceLow = 2004 // 广告主余额不足 + ErrCreditLimitInvalid = 2005 // 授信额度无效 +) + +// 广告位管理错误码 +const ( + ErrAdPositionNotFound = 3001 // 广告位不存在 + ErrAdPositionStatusInvalid = 3002 // 广告位状态无效 + ErrAdPositionCodeExists = 3003 // 广告位编码已存在 + ErrAdNotMatched = 3004 // 无匹配广告 +) + +// 报表管理错误码 +const ( + ErrReportNotFound = 4001 // 报表不存在 + ErrReportNotGenerated = 4002 // 报表未生成 + ErrReportExpired = 4003 // 报表已过期 + ErrReportInvalidFormat = 4004 // 报表格式无效 +) diff --git a/consts/redis_key.go b/consts/redis_key.go index d709a2b..9e5a1eb 100644 --- a/consts/redis_key.go +++ b/consts/redis_key.go @@ -1 +1,48 @@ package consts + +// 广告系统Redis键定义 + +// 广告缓存键 +const ( + AdCacheKeyPrefix = "cid:ad:" // 广告缓存前缀 + AdPositionCacheKeyPrefix = "cid:pos:" // 广告位缓存前缀 + AdvertiserCacheKeyPrefix = "cid:adv:" // 广告主缓存前缀 +) + +// 统计数据键 +const ( + AdStatKeyPrefix = "cid:stat:ad:" // 广告统计键前缀 + AdvStatKeyPrefix = "cid:stat:adv:" // 广告主统计键前缀 + PosStatKeyPrefix = "cid:stat:pos:" // 广告位统计键前缀 + DailyStatKeyPrefix = "cid:stat:daily:" // 日常统计键前缀 +) + +// 广告匹配键 +const ( + AdMatchingKeyPrefix = "cid:match:" // 广告匹配键前缀 + UserProfileKeyPrefix = "cid:user:" // 用户画像键前缀 +) + +// 报表键 +const ( + ReportCacheKeyPrefix = "cid:report:" // 报表缓存键前缀 + ReportTaskKeyPrefix = "cid:task:" // 报表任务键前缀 +) + +// 限流键 +const ( + AdRequestLimitKeyPrefix = "cid:limit:req:" // 广告请求限流键前缀 + ApiRequestLimitKeyPrefix = "cid:limit:api:" // API请求限流键前缀 +) + +// 队列键 +const ( + AdStatisticsQueueKey = "cid:queue:stat" // 统计数据队列 + ReportGenerateQueueKey = "cid:queue:report" // 报表生成队列 +) + +// Stream键 +const ( + AdEventStreamKey = "cid:stream:ad_event" // 广告事件流 + UserBehaviorStreamKey = "cid:stream:user_behavior" // 用户行为流 +) diff --git a/controller/ad_position_controller.go b/controller/ad_position_controller.go new file mode 100644 index 0000000..3bd68d0 --- /dev/null +++ b/controller/ad_position_controller.go @@ -0,0 +1,70 @@ +package controller + +import ( + "context" + + "cidService/model/dto" + "cidService/service" + + "gitee.com/red-future---jilin-g/common/http" +) + +type cAdPosition struct{} + +var AdPosition = &cAdPosition{} + +// Add 添加广告位 +func (c *cAdPosition) 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) { + err = service.AdPosition.Update(ctx, req) + return +} + +// UpdateStatus 更新广告位状态 +func (c *cAdPosition) 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) { + return service.AdPosition.GetOne(ctx, req) +} + +// List 获取广告位列表 +func (c *cAdPosition) 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) { + return service.AdPosition.GetStatistics(ctx, req) +} + +// GetAvailableAdPositions 获取可用的广告位列表 +func (c *cAdPosition) GetAvailableAdPositions(ctx context.Context, req *dto.GetAvailableAdPositionsReq) (res *dto.GetAvailableAdPositionsRes, err error) { + list, err := service.AdPosition.GetAvailableAdPositions(ctx) + if err != nil { + return nil, err + } + + return &dto.GetAvailableAdPositionsRes{ + List: list, + }, nil +} + +// MatchAd 匹配广告 +func (c *cAdPosition) 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 + } + + return &dto.MatchAdRes{ + Advertisement: ad, + }, nil +} diff --git a/controller/ad_statistics_controller.go b/controller/ad_statistics_controller.go new file mode 100644 index 0000000..42c05f5 --- /dev/null +++ b/controller/ad_statistics_controller.go @@ -0,0 +1,34 @@ +package controller + +import ( + "context" + + "cidService/model/dto" + "cidService/service" +) + +type cAdStatistics struct{} + +var AdStatistics = &cAdStatistics{} + +// GetStatistics 获取统计数据 +func (c *cAdStatistics) 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) { + return service.AdStatistics.GetDashboard(ctx, req) +} + +// GenerateDailyStatistics 生成每日统计数据 +func (c *cAdStatistics) GenerateDailyStatistics(ctx context.Context, req *dto.GenerateDailyStatisticsReq) (res *dto.GenerateDailyStatisticsRes, err error) { + err = service.AdStatistics.GenerateDailyStatistics(ctx, req.Date) + if err != nil { + return nil, err + } + + return &dto.GenerateDailyStatisticsRes{ + Success: true, + }, nil +} diff --git a/controller/advertisement_controller.go b/controller/advertisement_controller.go new file mode 100644 index 0000000..d206a71 --- /dev/null +++ b/controller/advertisement_controller.go @@ -0,0 +1,51 @@ +package controller + +import ( + "cidService/model/dto" + "cidService/service" + "context" + + "gitee.com/red-future---jilin-g/common/http" +) + +type cAdvertisement struct{} + +var Advertisement = &cAdvertisement{} + +// Add 添加广告 +func (c *cAdvertisement) 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) { + err = service.Advertisement.Update(ctx, req) + return +} + +// UpdateStatus 更新广告状态 +func (c *cAdvertisement) 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) { + err = service.Advertisement.Audit(ctx, req) + return +} + +// GetOne 获取广告详情 +func (c *cAdvertisement) 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) { + return service.Advertisement.List(ctx, req) +} + +// GetStatistics 获取广告统计数据 +func (c *cAdvertisement) 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 new file mode 100644 index 0000000..f163d49 --- /dev/null +++ b/controller/advertiser_controller.go @@ -0,0 +1,71 @@ +package controller + +import ( + "cidService/model/dto" + "cidService/service" + "context" + + "gitee.com/red-future---jilin-g/common/http" +) + +type cAdvertiser struct{} + +var Advertiser = &cAdvertiser{} + +// Add 添加广告主 +func (c *cAdvertiser) 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) { + err = service.Advertiser.Update(ctx, req) + return +} + +// UpdateStatus 更新广告主状态 +func (c *cAdvertiser) 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) { + err = service.Advertiser.Audit(ctx, req) + return +} + +// Recharge 充值 +func (c *cAdvertiser) 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) { + err = service.Advertiser.UpdateCreditLimit(ctx, req) + return +} + +// GetOne 获取广告主详情 +func (c *cAdvertiser) 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) { + return service.Advertiser.List(ctx, req) +} + +// GetBalance 获取广告主余额 +func (c *cAdvertiser) 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 + } + + return &dto.GetAdvertiserBalanceRes{ + Balance: balance, + CreditLimit: creditLimit, + }, nil +} diff --git a/controller/data_controller.go b/controller/data_controller.go deleted file mode 100644 index 63b2452..0000000 --- a/controller/data_controller.go +++ /dev/null @@ -1,27 +0,0 @@ -package controller - -import ( - "cidService/model/dto" - "cidService/service" - "context" -) - -type cData struct{} - -var Data = &cData{} - -// Add 添加数据 -func (c *cData) Add(ctx context.Context, req *dto.AddDataReq) (res *dto.AddDataRes, err error) { - return service.Data.Add(ctx, req) -} - -// Update 更新数据 -func (c *cData) Update(ctx context.Context, req *dto.UpdateDataReq) (res interface{}, err error) { - err = service.Data.Update(ctx, req) - return -} - -// List 获取数据列表 -func (c *cData) List(ctx context.Context, req *dto.ListDataReq) (res *dto.ListDataRes, err error) { - return service.Data.List(ctx, req) -} diff --git a/controller/report_controller.go b/controller/report_controller.go new file mode 100644 index 0000000..b5318c9 --- /dev/null +++ b/controller/report_controller.go @@ -0,0 +1,52 @@ +package controller + +import ( + "context" + + "gitee.com/red-future---jilin-g/common/http" + + "cidService/model/dto" + "cidService/service" +) + +type report struct{} + +var Report = &report{} + +// Create 创建报表 +func (c *report) Create(ctx context.Context, req *dto.CreateReportReq) (res *dto.CreateReportRes, err error) { + return service.Report.Create(ctx, req) +} + +// GetOne 获取报表详情 +func (c *report) GetOne(ctx context.Context, req *dto.GetReportReq) (res *dto.GetReportRes, err error) { + return service.Report.GetOne(ctx, req) +} + +// List 获取报表列表 +func (c *report) List(ctx context.Context, req *dto.ListReportReq) (res *dto.ListReportRes, err error) { + return service.Report.List(ctx, req) +} + +// Update 更新报表 +func (c *report) Update(ctx context.Context, req *dto.UpdateReportReq) (res *http.ResponseEmpty, err error) { + err = service.Report.Update(ctx, req) + return +} + +// Delete 删除报表 +func (c *report) Delete(ctx context.Context, req *dto.DeleteReportReq) (res *http.ResponseEmpty, err error) { + err = service.Report.Delete(ctx, req) + return +} + +// Download 下载报表 +func (c *report) Download(ctx context.Context, req *dto.DownloadReportReq) (res *dto.DownloadReportRes, err error) { + return service.Report.Download(ctx, req) +} + +// Generate 生成报表 +func (c *report) Generate(ctx context.Context, req *dto.GenerateReportReq) (res *http.ResponseEmpty, err error) { + err = service.Report.Generate(ctx, req) + return +} diff --git a/dao/ad_position_dao.go b/dao/ad_position_dao.go new file mode 100644 index 0000000..3cac698 --- /dev/null +++ b/dao/ad_position_dao.go @@ -0,0 +1,257 @@ +package dao + +import ( + "cidService/model/dto" + "cidService/model/entity" + "context" + + "gitee.com/red-future---jilin-g/common/http" + "gitee.com/red-future---jilin-g/common/mongo" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo/options" +) + +var AdPosition = &adPosition{} + +type adPosition struct{} + +// Insert 插入广告位 +func (d *adPosition) Insert(ctx context.Context, adPosition *entity.AdPosition) (err error) { + // 获取stream消息 + redis := g.Redis() + streamMsg, err := redis.Do(ctx, "XREAD", "STREAMS", "ad_position_stream", "$") + if err != nil { + g.Log().Errorf(ctx, "获取stream消息失败: %v", err) + } else { + g.Log().Infof(ctx, "获取到stream消息: %v", streamMsg) + } + + _, err = mongo.Insert(ctx, []interface{}{adPosition}, entity.AdPositionCollection) + return +} + +// Update 更新广告位 +func (d *adPosition) Update(ctx context.Context, req *dto.UpdateAdPositionReq) (err error) { + objectId, err := bson.ObjectIDFromHex(req.Id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + + // 构建动态更新字段 + updateFields := bson.M{} + + // 基本信息 + if !g.IsEmpty(req.Name) { + updateFields["name"] = req.Name + } + if !g.IsEmpty(req.Description) { + updateFields["description"] = req.Description + } + if !g.IsEmpty(req.PositionCode) { + updateFields["positionCode"] = req.PositionCode + } + if !g.IsEmpty(req.AdFormat) { + updateFields["adFormat"] = req.AdFormat + } + + // 尺寸信息 + if req.Width != nil { + updateFields["width"] = *req.Width + } + if req.Height != nil { + updateFields["height"] = *req.Height + } + + // 位置信息 + if !g.IsEmpty(req.Page) { + updateFields["page"] = req.Page + } + if !g.IsEmpty(req.Section) { + updateFields["section"] = req.Section + } + if !g.IsEmpty(req.Location) { + updateFields["location"] = req.Location + } + + // 展示设置 + if req.MaxAds != nil { + updateFields["maxAds"] = *req.MaxAds + } + if req.RefreshInterval != nil { + updateFields["refreshInterval"] = *req.RefreshInterval + } + if req.IsLazyLoad != nil { + updateFields["isLazyLoad"] = *req.IsLazyLoad + } + + // 定价设置 + if !g.IsEmpty(req.PricingModel) { + updateFields["pricingModel"] = req.PricingModel + } + if req.BasePrice != nil { + updateFields["basePrice"] = *req.BasePrice + } + if req.FloorPrice != nil { + updateFields["floorPrice"] = *req.FloorPrice + } + if !g.IsEmpty(req.PriceUnit) { + updateFields["priceUnit"] = req.PriceUnit + } + + // 展示规则 + if req.DisplayRules != nil { + updateFields["displayRules"] = req.DisplayRules + } + + // 状态信息 + if req.Status != nil { + updateFields["status"] = *req.Status + } + if req.IsExclusive != nil { + updateFields["isExclusive"] = *req.IsExclusive + } + + if len(updateFields) > 0 { + update := bson.M{"$set": updateFields} + _, err = mongo.Update(ctx, filter, update, entity.AdPositionCollection) + } + return +} + +// UpdateStatus 更新广告位状态 +func (d *adPosition) UpdateStatus(ctx context.Context, id, status string) (err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + update := bson.M{"$set": bson.M{"status": status}} + + _, err = mongo.Update(ctx, filter, update, entity.AdPositionCollection) + return +} + +// UpdateStatistics 更新广告位统计数据 +func (d *adPosition) UpdateStatistics(ctx context.Context, id string, stats map[string]interface{}) (err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + update := bson.M{"$set": stats} + + _, err = mongo.Update(ctx, filter, update, entity.AdPositionCollection) + return +} + +// GetOne 获取单个广告位 +func (d *adPosition) GetOne(ctx context.Context, id string) (adPosition *entity.AdPosition, err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + + adPosition = &entity.AdPosition{} + err = mongo.FindOne(ctx, filter, adPosition, entity.AdPositionCollection) + return +} + +// GetByCode 根据编码获取广告位 +func (d *adPosition) GetByCode(ctx context.Context, code string) (adPosition *entity.AdPosition, err error) { + filter := bson.M{"positionCode": code} + + adPosition = &entity.AdPosition{} + err = mongo.FindOne(ctx, filter, adPosition, entity.AdPositionCollection) + return +} + +// buildListFilter 构建列表查询的过滤条件 +func (d *adPosition) buildListFilter(req *dto.ListAdPositionReq) bson.M { + filter := bson.M{} + + if !g.IsEmpty(req.Name) { + filter["name"] = bson.M{"$regex": req.Name, "$options": "i"} + } + if !g.IsEmpty(req.PositionCode) { + filter["positionCode"] = req.PositionCode + } + if !g.IsEmpty(req.PageName) { + filter["page"] = req.PageName + } + if !g.IsEmpty(req.Section) { + filter["section"] = req.Section + } + if !g.IsEmpty(req.Status) { + filter["status"] = req.Status + } + if !g.IsEmpty(req.AdFormat) { + filter["adFormat"] = req.AdFormat + } + + // 处理日期范围 + if len(req.DateRange) == 2 { + startTime := gconv.Int64(req.DateRange[0]) + endTime := gconv.Int64(req.DateRange[1]) + filter["createdAt"] = bson.M{ + "$gte": startTime, + "$lte": endTime, + } + } + + return filter +} + +// checkTotalCount 检查总数 +func (d *adPosition) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) { + total, err = mongo.Count(ctx, filter, entity.AdPositionCollection) + return +} + +// List 获取广告位列表 +func (d *adPosition) List(ctx context.Context, req *dto.ListAdPositionReq) (list []*entity.AdPosition, total int64, err error) { + // 构建查询过滤条件 + filter := d.buildListFilter(req) + + // 检查总数 + total, err = d.checkTotalCount(ctx, filter) + if err != nil { + return + } + + // 分页参数处理 + pageNum := req.PageNum + if pageNum <= 0 { + pageNum = 1 + } + pageSize := req.PageSize + if pageSize <= 0 { + pageSize = http.PageSize + } + + limit := int64(pageSize) + skip := int64((pageNum - 1) * pageSize) + + // 排序处理 + sort := bson.M{"createdAt": -1} + + opts := options.Find().SetLimit(limit).SetSkip(skip).SetSort(sort) + + err = mongo.Find(ctx, filter, &list, entity.AdPositionCollection, opts) + return +} + +// GetAvailableAdPositions 获取可用的广告位列表 +func (d *adPosition) GetAvailableAdPositions(ctx context.Context) (list []*entity.AdPosition, err error) { + filter := bson.M{ + "status": "启用", // 只返回启用的广告位 + } + + opts := options.Find().SetSort(bson.M{"createdAt": -1}) + + err = mongo.Find(ctx, filter, &list, entity.AdPositionCollection, opts) + return +} diff --git a/dao/ad_statistics_dao.go b/dao/ad_statistics_dao.go new file mode 100644 index 0000000..06c08e3 --- /dev/null +++ b/dao/ad_statistics_dao.go @@ -0,0 +1,193 @@ +package dao + +import ( + "context" + + "cidService/model/dto" + "cidService/model/entity" + + "github.com/gogf/gf/v2/frame/g" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo/options" + + "gitee.com/red-future---jilin-g/common/http" + "gitee.com/red-future---jilin-g/common/mongo" +) + +// AdStatistics DAO 单例 +var AdStatistics = &adStatistics{} + +type adStatistics struct{} + +// Insert 插入统计数据 +func (d *adStatistics) Insert(ctx context.Context, data *entity.AdStatistics) (err error) { + // 如果 ID 为空,生成一个新的 ObjectID + if data.Id.IsZero() { + data.Id = bson.NewObjectID() + } + + // 使用 common/mongo.Insert,自动添加 tenantId、creator、updater 等字段 + // 确保查询时能通过 tenantId 正确过滤数据 + _, err = mongo.Insert(ctx, []interface{}{data}, entity.AdStatisticsCollection) + return +} + +// BatchInsert 批量插入统计数据 +func (d *adStatistics) BatchInsert(ctx context.Context, dataList []*entity.AdStatistics) (err error) { + if len(dataList) == 0 { + return nil + } + + var list []interface{} + for _, data := range dataList { + // 如果 ID 为空,生成一个新的 ObjectID + if data.Id.IsZero() { + data.Id = bson.NewObjectID() + } + list = append(list, data) + } + + _, err = mongo.Insert(ctx, list, entity.AdStatisticsCollection) + return +} + +// Upsert 插入或更新统计数据 +func (d *adStatistics) Upsert(ctx context.Context, data *entity.AdStatistics) (err error) { + filter := bson.M{ + "statType": data.StatType, + "statDimension": data.StatDimension, + "targetId": data.TargetId, + "statDate": data.StatDate, + } + + update := bson.M{"$set": data} + + // 这里使用简单的Update方法,如果需要Upsert功能,可以扩展 + _, err = mongo.Update(ctx, filter, update, entity.AdStatisticsCollection) + return +} + +// buildListFilter 构建列表查询的过滤条件 +func (d *adStatistics) buildListFilter(req *dto.GetAdStatisticsReq) bson.M { + filter := bson.M{} + + if !g.IsEmpty(req.StatType) { + filter["statType"] = req.StatType + } + if !g.IsEmpty(req.StatDimension) { + filter["statDimension"] = req.StatDimension + } + if !g.IsEmpty(req.TargetId) { + filter["targetId"] = req.TargetId + } + + // 时间范围 + filter["statDate"] = bson.M{ + "$gte": req.StartDate, + "$lte": req.EndDate, + } + + // 筛选条件 + if !g.IsEmpty(req.DeviceType) { + filter["deviceType"] = req.DeviceType + } + if !g.IsEmpty(req.Platform) { + filter["platform"] = req.Platform + } + if !g.IsEmpty(req.Region) { + filter["region"] = req.Region + } + if !g.IsEmpty(req.Gender) { + filter["gender"] = req.Gender + } + if !g.IsEmpty(req.AgeGroup) { + filter["ageGroup"] = req.AgeGroup + } + + return filter +} + +// checkTotalCount 检查总数 +func (d *adStatistics) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) { + total, err = mongo.Count(ctx, filter, entity.AdStatisticsCollection) + return +} + +// List 获取统计数据列表 +func (d *adStatistics) List(ctx context.Context, req *dto.GetAdStatisticsReq) (list []*entity.AdStatistics, total int64, err error) { + // 构建查询过滤条件 + filter := d.buildListFilter(req) + + // 检查总数 + total, err = d.checkTotalCount(ctx, filter) + if err != nil { + return + } + + pageNum := req.PageNum + if pageNum <= 0 { + pageNum = 1 + } + pageSize := req.PageSize + if pageSize <= 0 { + pageSize = http.PageSize + } + + limit := int64(pageSize) + skip := int64((pageNum - 1) * pageSize) + + // 排序处理 + sort := bson.M{"statDate": -1} + if !g.IsEmpty(req.SortBy) { + sortDirection := 1 + if req.SortDirection == "desc" { + sortDirection = -1 + } + sort = bson.M{req.SortBy: sortDirection} + } + + opts := options.Find().SetLimit(limit).SetSkip(skip).SetSort(sort) + + err = mongo.Find(ctx, filter, &list, entity.AdStatisticsCollection, opts) + return +} + +// GetOne 获取单个统计记录 +func (d *adStatistics) GetOne(ctx context.Context, id string) (data *entity.AdStatistics, err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + + data = &entity.AdStatistics{} + err = mongo.FindOne(ctx, filter, data, entity.AdStatisticsCollection) + return +} + +// GetStatistics 获取统计数据 +func (d *adStatistics) GetStatistics(ctx context.Context, req *dto.GetAdStatisticsReq) (list []*entity.AdStatistics, total int64, err error) { + // 构建查询过滤条件 + filter := d.buildListFilter(req) + + // 检查总数 + total, err = d.checkTotalCount(ctx, filter) + if err != nil { + return + } + + // 排序处理 + sort := bson.M{"statDate": -1} + if !g.IsEmpty(req.SortBy) { + sortDirection := 1 + if req.SortDirection == "desc" { + sortDirection = -1 + } + sort = bson.M{req.SortBy: sortDirection} + } + + opts := options.Find().SetSort(sort) + + err = mongo.Find(ctx, filter, &list, entity.AdStatisticsCollection, opts) + return +} diff --git a/dao/advertisement_dao.go b/dao/advertisement_dao.go new file mode 100644 index 0000000..ec71bee --- /dev/null +++ b/dao/advertisement_dao.go @@ -0,0 +1,256 @@ +package dao + +import ( + "cidService/model/dto" + "cidService/model/entity" + "context" + "time" + + "gitee.com/red-future---jilin-g/common/http" + "gitee.com/red-future---jilin-g/common/mongo" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo/options" +) + +var Advertisement = &advertisement{} + +type advertisement struct{} + +// Insert 插入广告 +func (d *advertisement) Insert(ctx context.Context, advertisement *entity.Advertisement) (err error) { + // 获取stream消息 + redis := g.Redis() + streamMsg, err := redis.Do(ctx, "XREAD", "STREAMS", "advertisement_stream", "$") + if err != nil { + g.Log().Errorf(ctx, "获取stream消息失败: %v", err) + } else { + g.Log().Infof(ctx, "获取到stream消息: %v", streamMsg) + } + + _, err = mongo.Insert(ctx, []interface{}{advertisement}, entity.AdvertisementCollection) + return +} + +// Update 更新广告 +func (d *advertisement) Update(ctx context.Context, req *dto.UpdateAdvertisementReq) (err error) { + objectId, err := bson.ObjectIDFromHex(req.Id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + + // 构建动态更新字段 + updateFields := bson.M{} + + // 广告基本信息 + if !g.IsEmpty(req.Title) { + updateFields["title"] = req.Title + } + if !g.IsEmpty(req.Description) { + updateFields["description"] = req.Description + } + if !g.IsEmpty(req.AdvertiserId) { + updateFields["advertiserId"] = req.AdvertiserId + } + if !g.IsEmpty(req.AdPositionId) { + updateFields["adPositionId"] = req.AdPositionId + } + if !g.IsEmpty(req.AdType) { + updateFields["adType"] = req.AdType + } + if !g.IsEmpty(req.AdFormat) { + updateFields["adFormat"] = req.AdFormat + } + if !g.IsEmpty(req.MaterialUrl) { + updateFields["materialUrl"] = req.MaterialUrl + } + if !g.IsEmpty(req.LinkUrl) { + updateFields["linkUrl"] = req.LinkUrl + } + if !g.IsEmpty(req.LandingPageUrl) { + updateFields["landingPageUrl"] = req.LandingPageUrl + } + + // 投放设置 + if req.StartDate != nil { + updateFields["startDate"] = *req.StartDate + } + if req.EndDate != nil { + updateFields["endDate"] = *req.EndDate + } + if req.Budget != nil { + updateFields["budget"] = *req.Budget + } + if req.DailyBudget != nil { + updateFields["dailyBudget"] = *req.DailyBudget + } + if req.BidAmount != nil { + updateFields["bidAmount"] = *req.BidAmount + } + if !g.IsEmpty(req.BillingType) { + updateFields["billingType"] = req.BillingType + } + + // 投放条件 + if req.Targeting != nil { + updateFields["targeting"] = req.Targeting + } + + // 状态信息 + if req.Status != nil { + updateFields["status"] = *req.Status + } + if req.AuditStatus != nil { + updateFields["auditStatus"] = *req.AuditStatus + } + if req.AuditReason != nil { + updateFields["auditReason"] = *req.AuditReason + } + + if len(updateFields) > 0 { + update := bson.M{"$set": updateFields} + _, err = mongo.Update(ctx, filter, update, entity.AdvertisementCollection) + } + return +} + +// UpdateStatus 更新广告状态 +func (d *advertisement) UpdateStatus(ctx context.Context, id, status string) (err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + update := bson.M{"$set": bson.M{"status": status}} + + _, err = mongo.Update(ctx, filter, update, entity.AdvertisementCollection) + return +} + +// Audit 审核广告 +func (d *advertisement) Audit(ctx context.Context, id, auditStatus, auditReason string) (err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + + // 获取当前用户ID(实际项目中应从上下文获取) + auditBy := "system" + auditTime := time.Now().Unix() + + update := bson.M{ + "$set": bson.M{ + "auditStatus": auditStatus, + "auditReason": auditReason, + "auditTime": auditTime, + "auditBy": auditBy, + }, + } + + _, err = mongo.Update(ctx, filter, update, entity.AdvertisementCollection) + return +} + +// UpdateStatistics 更新广告统计数据 +func (d *advertisement) UpdateStatistics(ctx context.Context, id string, stats map[string]interface{}) (err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + update := bson.M{"$set": stats} + + _, err = mongo.Update(ctx, filter, update, entity.AdvertisementCollection) + return +} + +// GetOne 获取单个广告 +func (d *advertisement) GetOne(ctx context.Context, id string) (advertisement *entity.Advertisement, err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + + advertisement = &entity.Advertisement{} + err = mongo.FindOne(ctx, filter, advertisement, entity.AdvertisementCollection) + return +} + +// buildListFilter 构建列表查询的过滤条件 +func (d *advertisement) buildListFilter(req *dto.ListAdvertisementReq) bson.M { + filter := bson.M{} + + if !g.IsEmpty(req.AdvertiserId) { + filter["advertiserId"] = req.AdvertiserId + } + if !g.IsEmpty(req.AdPositionId) { + filter["adPositionId"] = req.AdPositionId + } + if !g.IsEmpty(req.AdType) { + filter["adType"] = req.AdType + } + if !g.IsEmpty(req.Status) { + filter["status"] = req.Status + } + if !g.IsEmpty(req.AuditStatus) { + filter["auditStatus"] = req.AuditStatus + } + if !g.IsEmpty(req.Title) { + filter["title"] = bson.M{"$regex": req.Title, "$options": "i"} + } + + // 处理日期范围 + if len(req.DateRange) == 2 { + startTime := gconv.Int64(req.DateRange[0]) + endTime := gconv.Int64(req.DateRange[1]) + filter["createdAt"] = bson.M{ + "$gte": startTime, + "$lte": endTime, + } + } + + return filter +} + +// checkTotalCount 检查总数 +func (d *advertisement) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) { + total, err = mongo.Count(ctx, filter, entity.AdvertisementCollection) + return +} + +// List 获取广告列表 +func (d *advertisement) List(ctx context.Context, req *dto.ListAdvertisementReq) (list []*entity.Advertisement, total int64, err error) { + // 构建查询过滤条件 + filter := d.buildListFilter(req) + + // 检查总数 + total, err = d.checkTotalCount(ctx, filter) + if err != nil { + return + } + + // 分页参数处理 + pageNum := req.PageNum + if pageNum <= 0 { + pageNum = 1 + } + pageSize := req.PageSize + if pageSize <= 0 { + pageSize = http.PageSize + } + + limit := int64(pageSize) + skip := int64((pageNum - 1) * pageSize) + + // 排序处理 + sort := bson.M{"createdAt": -1} + + opts := options.Find().SetLimit(limit).SetSkip(skip).SetSort(sort) + + err = mongo.Find(ctx, filter, &list, entity.AdvertisementCollection, opts) + return +} diff --git a/dao/advertiser_dao.go b/dao/advertiser_dao.go new file mode 100644 index 0000000..33415ef --- /dev/null +++ b/dao/advertiser_dao.go @@ -0,0 +1,298 @@ +package dao + +import ( + "cidService/model/dto" + "cidService/model/entity" + "context" + "time" + + "gitee.com/red-future---jilin-g/common/http" + "gitee.com/red-future---jilin-g/common/mongo" + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo/options" +) + +var Advertiser = &advertiser{} + +type advertiser struct{} + +// Insert 插入广告主 +func (d *advertiser) Insert(ctx context.Context, advertiser *entity.Advertiser) (err error) { + // 获取stream消息 + redis := g.Redis() + streamMsg, err := redis.Do(ctx, "XREAD", "STREAMS", "advertiser_stream", "$") + if err != nil { + g.Log().Errorf(ctx, "获取stream消息失败: %v", err) + } else { + g.Log().Infof(ctx, "获取到stream消息: %v", streamMsg) + } + + _, err = mongo.Insert(ctx, []interface{}{advertiser}, entity.AdvertiserCollection) + return +} + +// Update 更新广告主 +func (d *advertiser) Update(ctx context.Context, req *dto.UpdateAdvertiserReq) (err error) { + objectId, err := bson.ObjectIDFromHex(req.Id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + + // 构建动态更新字段 + updateFields := bson.M{} + + // 基本信息 + if !g.IsEmpty(req.Name) { + updateFields["name"] = req.Name + } + if !g.IsEmpty(req.ContactName) { + updateFields["contactName"] = req.ContactName + } + if !g.IsEmpty(req.ContactPhone) { + updateFields["contactPhone"] = req.ContactPhone + } + if !g.IsEmpty(req.ContactEmail) { + updateFields["contactEmail"] = req.ContactEmail + } + if !g.IsEmpty(req.Company) { + updateFields["company"] = req.Company + } + if !g.IsEmpty(req.Industry) { + updateFields["industry"] = req.Industry + } + if !g.IsEmpty(req.Scale) { + updateFields["scale"] = req.Scale + } + + // 证件信息 + if !g.IsEmpty(req.BusinessLicenseUrl) { + updateFields["businessLicenseUrl"] = req.BusinessLicenseUrl + } + if !g.IsEmpty(req.ICPLicenseUrl) { + updateFields["icpLicenseUrl"] = req.ICPLicenseUrl + } + if req.OtherLicenseUrls != nil { + updateFields["otherLicenseUrls"] = req.OtherLicenseUrls + } + + // 财务信息 + if !g.IsEmpty(req.BankName) { + updateFields["bankName"] = req.BankName + } + if !g.IsEmpty(req.BankAccount) { + updateFields["bankAccount"] = req.BankAccount + } + if !g.IsEmpty(req.AccountName) { + updateFields["accountName"] = req.AccountName + } + + // 合同信息 + if !g.IsEmpty(req.ContractId) { + updateFields["contractId"] = req.ContractId + } + if !g.IsEmpty(req.ContractType) { + updateFields["contractType"] = req.ContractType + } + if !g.IsEmpty(req.ContractUrl) { + updateFields["contractUrl"] = req.ContractUrl + } + if req.SignDate != nil { + updateFields["signDate"] = *req.SignDate + } + if req.ExpireDate != nil { + updateFields["expireDate"] = *req.ExpireDate + } + + // 系统信息 + if req.AccountBalance != nil { + updateFields["accountBalance"] = *req.AccountBalance + } + if req.CreditLimit != nil { + updateFields["creditLimit"] = *req.CreditLimit + } + if !g.IsEmpty(req.Remark) { + updateFields["remark"] = req.Remark + } + + // 状态信息 + if req.Status != nil { + updateFields["status"] = *req.Status + } + if req.AuditStatus != nil { + updateFields["auditStatus"] = *req.AuditStatus + } + if req.AuditReason != nil { + updateFields["auditReason"] = *req.AuditReason + } + + if len(updateFields) > 0 { + update := bson.M{"$set": updateFields} + _, err = mongo.Update(ctx, filter, update, entity.AdvertiserCollection) + } + return +} + +// UpdateStatus 更新广告主状态 +func (d *advertiser) UpdateStatus(ctx context.Context, id, status string) (err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + update := bson.M{"$set": bson.M{"status": status}} + + _, err = mongo.Update(ctx, filter, update, entity.AdvertiserCollection) + return +} + +// Audit 审核广告主 +func (d *advertiser) Audit(ctx context.Context, id, auditStatus, auditReason string) (err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + + // 获取当前用户ID(实际项目中应从上下文获取) + auditBy := "system" + auditTime := time.Now().Unix() + + update := bson.M{ + "$set": bson.M{ + "auditStatus": auditStatus, + "auditReason": auditReason, + "auditTime": auditTime, + "auditBy": auditBy, + }, + } + + _, err = mongo.Update(ctx, filter, update, entity.AdvertiserCollection) + return +} + +// Recharge 充值 +func (d *advertiser) Recharge(ctx context.Context, id string, amount int64, remark string) (err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + + // 先获取当前余额 + advertiser := &entity.Advertiser{} + err = mongo.FindOne(ctx, filter, advertiser, entity.AdvertiserCollection) + if err != nil { + return + } + + // 更新余额 + newBalance := advertiser.AccountBalance + amount + update := bson.M{"$set": bson.M{"accountBalance": newBalance}} + + _, err = mongo.Update(ctx, filter, update, entity.AdvertiserCollection) + return +} + +// UpdateCreditLimit 更新授信额度 +func (d *advertiser) UpdateCreditLimit(ctx context.Context, id string, creditLimit int64) (err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + update := bson.M{"$set": bson.M{"creditLimit": creditLimit}} + + _, err = mongo.Update(ctx, filter, update, entity.AdvertiserCollection) + return +} + +// GetOne 获取单个广告主 +func (d *advertiser) GetOne(ctx context.Context, id string) (advertiser *entity.Advertiser, err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + + advertiser = &entity.Advertiser{} + err = mongo.FindOne(ctx, filter, advertiser, entity.AdvertiserCollection) + return +} + +// buildListFilter 构建列表查询的过滤条件 +func (d *advertiser) buildListFilter(req *dto.ListAdvertiserReq) bson.M { + filter := bson.M{} + + if !g.IsEmpty(req.Name) { + filter["name"] = bson.M{"$regex": req.Name, "$options": "i"} + } + if !g.IsEmpty(req.ContactName) { + filter["contactName"] = bson.M{"$regex": req.ContactName, "$options": "i"} + } + if !g.IsEmpty(req.Company) { + filter["company"] = bson.M{"$regex": req.Company, "$options": "i"} + } + if !g.IsEmpty(req.Industry) { + filter["industry"] = req.Industry + } + if !g.IsEmpty(req.Status) { + filter["status"] = req.Status + } + if !g.IsEmpty(req.AuditStatus) { + filter["auditStatus"] = req.AuditStatus + } + + // 处理日期范围 + if len(req.DateRange) == 2 { + startTime := gconv.Int64(req.DateRange[0]) + endTime := gconv.Int64(req.DateRange[1]) + filter["createdAt"] = bson.M{ + "$gte": startTime, + "$lte": endTime, + } + } + + return filter +} + +// checkTotalCount 检查总数 +func (d *advertiser) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) { + total, err = mongo.Count(ctx, filter, entity.AdvertiserCollection) + return +} + +// List 获取广告主列表 +func (d *advertiser) List(ctx context.Context, req *dto.ListAdvertiserReq) (list []*entity.Advertiser, total int64, err error) { + // 构建查询过滤条件 + filter := d.buildListFilter(req) + + // 检查总数 + total, err = d.checkTotalCount(ctx, filter) + if err != nil { + return + } + + // 分页参数处理 + pageNum := req.PageNum + if pageNum <= 0 { + pageNum = 1 + } + pageSize := req.PageSize + if pageSize <= 0 { + pageSize = http.PageSize + } + + limit := int64(pageSize) + skip := int64((pageNum - 1) * pageSize) + + // 排序处理 + sort := bson.M{"createdAt": -1} + + opts := options.Find().SetLimit(limit).SetSkip(skip).SetSort(sort) + + err = mongo.Find(ctx, filter, &list, entity.AdvertiserCollection, opts) + return +} diff --git a/dao/data_dao.go b/dao/data_dao.go deleted file mode 100644 index ab0bbb1..0000000 --- a/dao/data_dao.go +++ /dev/null @@ -1,121 +0,0 @@ -package dao - -import ( - "cidService/model/dto" - "cidService/model/entity" - "context" - - "gitee.com/red-future---jilin-g/common/http" - "gitee.com/red-future---jilin-g/common/mongo" - "github.com/gogf/gf/v2/frame/g" - "go.mongodb.org/mongo-driver/v2/bson" - "go.mongodb.org/mongo-driver/v2/mongo/options" -) - -var Data = &data{} - -type data struct{} - -// Insert 插入数据 -func (d *data) Insert(ctx context.Context, data *entity.Data) (err error) { - // 获取stream消息 - redis := g.Redis() - streamMsg, err := redis.Do(ctx, "XREAD", "STREAMS", "data_stream", "$") - if err != nil { - g.Log().Errorf(ctx, "获取stream消息失败: %v", err) - } else { - g.Log().Infof(ctx, "获取到stream消息: %v", streamMsg) - } - - _, err = mongo.Insert(ctx, []interface{}{data}, entity.DataCollection) - return -} - -// Update 更新数据 -func (d *data) Update(ctx context.Context, req *dto.UpdateDataReq) (err error) { - objectId, err := bson.ObjectIDFromHex(req.Id) - if err != nil { - return - } - filter := bson.M{"_id": objectId} - - // 构建动态更新字段 - updateFields := bson.M{} - if !g.IsEmpty(req.CustomerId) { - updateFields["customerId"] = req.CustomerId - } - if !g.IsEmpty(req.CustomerServiceId) { - updateFields["customerServiceId"] = req.CustomerServiceId - } - if req.IsInbound != nil { - updateFields["isInbound"] = *req.IsInbound - } - if req.IsActive != nil { - updateFields["isActive"] = *req.IsActive - } - if req.IsServed != nil { - updateFields["isServed"] = *req.IsServed - } - if req.HasSentContactCard != nil { - updateFields["hasSentContactCard"] = *req.HasSentContactCard - } - if req.HasSentNameCard != nil { - updateFields["hasSentNameCard"] = *req.HasSentNameCard - } - if req.HasLeftContactInfo != nil { - updateFields["hasLeftContactInfo"] = *req.HasLeftContactInfo - } - - if len(updateFields) > 0 { - update := bson.M{"$set": updateFields} - _, err = mongo.Update(ctx, filter, update, entity.DataCollection) - } - return -} - -// buildListFilter 构建列表查询的过滤条件 -func (d *data) buildListFilter(req *dto.ListDataReq) bson.M { - filter := bson.M{} - if !g.IsEmpty(req.CustomerId) { - filter["customerId"] = req.CustomerId - } - if !g.IsEmpty(req.CustomerServiceId) { - filter["customerServiceId"] = req.CustomerServiceId - } - return filter -} - -// checkTotalCount 检查总数 -func (d *data) checkTotalCount(ctx context.Context, filter bson.M) (total int64, err error) { - total, err = mongo.Count(ctx, filter, entity.DataCollection) - return -} - -// List 获取数据列表 -func (d *data) List(ctx context.Context, req *dto.ListDataReq) (list []*entity.Data, total int64, err error) { - // 构建查询过滤条件 - filter := d.buildListFilter(req) - - // 检查总数 - total, err = d.checkTotalCount(ctx, filter) - if err != nil { - return - } - - // 分页参数处理 - pageNum := req.PageNum - if pageNum <= 0 { - pageNum = 1 - } - pageSize := req.PageSize - if pageSize <= 0 { - pageSize = http.PageSize - } - - limit := int64(pageSize) - skip := int64((pageNum - 1) * pageSize) - opts := options.Find().SetLimit(limit).SetSkip(skip).SetSort(bson.M{"sessionStartTime": -1}) - - err = mongo.Find(ctx, filter, &list, entity.DataCollection, opts) - return -} diff --git a/dao/report_dao.go b/dao/report_dao.go new file mode 100644 index 0000000..1780fc9 --- /dev/null +++ b/dao/report_dao.go @@ -0,0 +1,171 @@ +package dao + +import ( + "context" + + "cidService/model/dto" + "cidService/model/entity" + + "github.com/gogf/gf/v2/frame/g" + "github.com/gogf/gf/v2/util/gconv" + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo/options" + + "gitee.com/red-future---jilin-g/common/http" + "gitee.com/red-future---jilin-g/common/mongo" +) + +// Report DAO 单例 +var Report = &report{} + +type report struct{} + +// Insert 插入报表 +func (d *report) Insert(ctx context.Context, report *entity.AdReport) (err error) { + // 如果 ID 为空,生成一个新的 ObjectID + if report.Id.IsZero() { + report.Id = bson.NewObjectID() + } + _, err = mongo.Insert(ctx, []interface{}{report}, entity.AdReportCollection) + return +} + +// Update 更新报表 +func (d *report) Update(ctx context.Context, req *dto.UpdateReportReq) (err error) { + objectId, err := bson.ObjectIDFromHex(req.Id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + + // 构建动态更新字段 + updateFields := bson.M{} + + if !g.IsEmpty(req.ReportName) { + updateFields["reportName"] = req.ReportName + } + if !g.IsEmpty(req.ReportType) { + updateFields["reportType"] = req.ReportType + } + if !g.IsEmpty(req.ReportPeriod) { + updateFields["reportPeriod"] = req.ReportPeriod + } + if req.StartDate != nil { + updateFields["startDate"] = *req.StartDate + } + if req.EndDate != nil { + updateFields["endDate"] = *req.EndDate + } + if req.ReportConfig != nil { + updateFields["reportConfig"] = req.ReportConfig + } + if !g.IsEmpty(req.FileFormat) { + updateFields["fileFormat"] = req.FileFormat + } + if req.EmailRecipients != nil { + updateFields["emailRecipients"] = req.EmailRecipients + } + if !g.IsEmpty(req.Schedule) { + updateFields["schedule"] = req.Schedule + } + + if len(updateFields) > 0 { + update := bson.M{"$set": updateFields} + _, err = mongo.Update(ctx, filter, update, entity.AdReportCollection) + } + return +} + +// Delete 删除报表 +func (d *report) Delete(ctx context.Context, id string) (err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + + _, err = mongo.Delete(ctx, filter, entity.AdReportCollection) + return +} + +// GetOne 获取单个报表 +func (d *report) GetOne(ctx context.Context, id string) (result *entity.AdReport, err error) { + objectId, err := bson.ObjectIDFromHex(id) + if err != nil { + return + } + filter := bson.M{"_id": objectId} + + result = &entity.AdReport{} + err = mongo.FindOne(ctx, filter, result, entity.AdReportCollection) + return +} + +// buildReportListFilter 构建报表列表查询的过滤条件 +func (d *report) buildReportListFilter(req *dto.ListReportReq) bson.M { + filter := bson.M{} + + if !g.IsEmpty(req.ReportName) { + filter["reportName"] = bson.M{"$regex": req.ReportName, "$options": "i"} + } + if !g.IsEmpty(req.ReportType) { + filter["reportType"] = req.ReportType + } + if !g.IsEmpty(req.Status) { + filter["status"] = req.Status + } + if !g.IsEmpty(req.Operator) { + filter["operator"] = req.Operator + } + + // 处理日期范围 + if len(req.DateRange) == 2 { + startTime := gconv.Int64(req.DateRange[0]) + endTime := gconv.Int64(req.DateRange[1]) + filter["createdAt"] = bson.M{ + "$gte": startTime, + "$lte": endTime, + } + } + + return filter +} + +// checkReportTotalCount 检查报表总数 +func (d *report) checkReportTotalCount(ctx context.Context, filter bson.M) (total int64, err error) { + total, err = mongo.Count(ctx, filter, entity.AdReportCollection) + return +} + +// List 获取报表列表 +func (d *report) List(ctx context.Context, req *dto.ListReportReq) (list []*entity.AdReport, total int64, err error) { + // 构建查询过滤条件 + filter := d.buildReportListFilter(req) + + // 检查总数 + total, err = d.checkReportTotalCount(ctx, filter) + if err != nil { + return + } + + // 分页参数处理 + pageNum := req.PageNum + if pageNum <= 0 { + pageNum = 1 + } + pageSize := req.PageSize + if pageSize <= 0 { + pageSize = http.PageSize + } + + limit := int64(pageSize) + skip := int64((pageNum - 1) * pageSize) + + // 排序处理 + sort := bson.M{"createdAt": -1} + + opts := options.Find().SetLimit(limit).SetSkip(skip).SetSort(sort) + + err = mongo.Find(ctx, filter, &list, entity.AdReportCollection, opts) + return +} diff --git a/go.mod b/go.mod index a8fffa2..f92b829 100644 --- a/go.mod +++ b/go.mod @@ -11,4 +11,81 @@ require ( golang.org/x/net v0.47.0 ) -//replace gitee.com/red-future---jilin-g/common v0.1.9 => ../common \ No newline at end of file +require ( + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/armon/go-metrics v0.4.1 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/clbanning/mxj/v2 v2.7.0 // indirect + github.com/dgraph-io/badger/v4 v4.2.0 // indirect + github.com/dgraph-io/ristretto v0.1.1 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-sql-driver/mysql v1.7.1 // indirect + github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 // indirect + github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang-jwt/jwt/v5 v5.0.0 // indirect + github.com/golang/glog v1.2.5 // indirect + github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v1.0.0 // indirect + github.com/google/flatbuffers v1.12.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/websocket v1.5.3 // indirect + github.com/grokify/html-strip-tags-go v0.1.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect + github.com/hashicorp/consul/api v1.26.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-hclog v1.5.0 // indirect + github.com/hashicorp/go-immutable-radix v1.3.1 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-rootcerts v1.0.2 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect + github.com/hashicorp/serf v0.10.1 // indirect + github.com/klauspost/compress v1.16.7 // indirect + github.com/magiconair/properties v1.8.10 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mitchellh/go-homedir v1.1.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/olekukonko/errors v1.1.0 // indirect + github.com/olekukonko/ll v0.0.9 // indirect + github.com/olekukonko/tablewriter v1.1.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/redis/go-redis/v9 v9.12.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/tiger1103/gfast-token v1.0.10 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect + go.opencensus.io v0.22.5 // indirect + go.opentelemetry.io/auto/sdk v1.1.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.7.1 // indirect + golang.org/x/crypto v0.44.0 // indirect + golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 // indirect + golang.org/x/sync v0.18.0 // indirect + golang.org/x/sys v0.38.0 // indirect + golang.org/x/text v0.31.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 // indirect + google.golang.org/grpc v1.75.0 // indirect + google.golang.org/protobuf v1.36.8 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +//replace gitee.com/red-future---jilin-g/common v0.1.9 => ../common diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..dd68e09 --- /dev/null +++ b/go.sum @@ -0,0 +1,425 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +gitee.com/red-future---jilin-g/common v0.1.9 h1:gorlFdiqLExGC9Z42j2xgQd+yeoRWHfAH71Q22lUSEs= +gitee.com/red-future---jilin-g/common v0.1.9/go.mod h1:FWIIaGd6bueA3QXSFeyaL9XesFOgGtDsMrHrPQkrJl4= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA= +github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgraph-io/badger/v4 v4.2.0 h1:kJrlajbXXL9DFTNuhhu9yCx7JJa4qpYWxtE8BzuWsEs= +github.com/dgraph-io/badger/v4 v4.2.0/go.mod h1:qfCqhPoWDFJRx1gp5QwwyGo8xk1lbHUxvK9nK0OGAak= +github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8= +github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczCTSixgIKmwPv6+wP5DGjqLYw5SUiA= +github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5 h1:0+ZBYhi4sqwxXwL+hIBpp06a7G4m5nmjskQ3NNb8qYc= +github.com/gogf/gf/contrib/drivers/mysql/v2 v2.9.5/go.mod h1:vyB7J/uJcLCrHD5lfFBzxhEEMkePIRzfhd33EcsuLa0= +github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.5 h1:Ku7p3CvGchxC7zPSgArf/tZs2w9Yb8tS/gH5ADN+p9g= +github.com/gogf/gf/contrib/nosql/redis/v2 v2.9.5/go.mod h1:cjy18NsSLZQf5zaLAzuo7B2gr8GGjCTWDTEPY7T+6FI= +github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5 h1:eUqwJ/qNH8lJ6yssiqskazgp1ACQuNU6zXlLOZVuXTQ= +github.com/gogf/gf/contrib/registry/consul/v2 v2.9.5/go.mod h1:sjQyMry9+0POYZCA6lHXBxO77WoNKkruJpRB4xKqk5k= +github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5 h1:tHUEZYB5GTqEYYVDYnlGobf1xISARKDE4KHVlgjwTec= +github.com/gogf/gf/contrib/trace/otlphttp/v2 v2.9.5/go.mod h1:cfzTn2HS9RDX8f5pUVkbGxUWcSosouqfNQ1G6cY0V88= +github.com/gogf/gf/v2 v2.9.5 h1:1scfOdHbMP854oQaiLejl+eL+c4xfuvtWmmZiDJxbKs= +github.com/gogf/gf/v2 v2.9.5/go.mod h1:VUb5eyJKpvW77O/dXsbbLNO/Kjrg0UycIiq0lRiBjjo= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE= +github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/glog v1.2.5 h1:DrW6hGnjIhtvhOIiAKT6Psh/Kd/ldepEa81DKeiRJ5I= +github.com/golang/glog v1.2.5/go.mod h1:6AhwSGph0fcJtXVM/PEHPqZlFeoLxhs7/t5UDAwmO+w= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v1.0.0 h1:Oy607GVXHs7RtbggtPBnr2RmDArIsAefDwvrdWvRhGs= +github.com/golang/snappy v1.0.0/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.1 h1:gK4Kx5IaGY9CD5sPJ36FHiBJ6ZXl0kilRiiCj+jdYp4= +github.com/google/btree v1.0.1/go.mod h1:xXMiIv4Fb/0kKde4SpL7qlzvu5cMJDRkFDxJfI9uaxA= +github.com/google/flatbuffers v1.12.1 h1:MVlul7pQNoDzWRLTw5imwYsl+usrS1TXG2H4jg6ImGw= +github.com/google/flatbuffers v1.12.1/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grokify/html-strip-tags-go v0.1.0 h1:03UrQLjAny8xci+R+qjCce/MYnpNXCtgzltlQbOBae4= +github.com/grokify/html-strip-tags-go v0.1.0/go.mod h1:ZdzgfHEzAfz9X6Xe5eBLVblWIxXfYSQ40S/VKrAOGpc= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +github.com/hashicorp/consul/api v1.26.1 h1:5oSXOO5fboPZeW5SN+TdGFP/BILDgBm19OrPZ/pICIM= +github.com/hashicorp/consul/api v1.26.1/go.mod h1:B4sQTeaSO16NtynqrAdwOlahJ7IUDZM9cj2420xYL8A= +github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= +github.com/hashicorp/consul/sdk v0.15.0/go.mod h1:r/OmRRPbHOe0yxNahLw7G9x5WG17E1BIECMtCjcPSNo= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= +github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-sockaddr v1.0.2 h1:ztczhD1jLxIRjVejw8gFomI1BQZOe2WoVOu0SyteCQc= +github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.1 h1:zEfKbn2+PDgroKdiOzqiE8rsmLqU2uwi5PB5pBJ3TkI= +github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= +github.com/hashicorp/memberlist v0.5.0 h1:EtYPN8DpAURiapus508I4n9CzHs2W+8NZGbmmR/prTM= +github.com/hashicorp/memberlist v0.5.0/go.mod h1:yvyXLpo0QaGE59Y7hDTsTzDD25JYBZ4mHgHUZ8lrOI0= +github.com/hashicorp/serf v0.10.1 h1:Z1H2J60yRKvfDYAOZLd2MU0ND4AH/WDz7xYHDWQsIPY= +github.com/hashicorp/serf v0.10.1/go.mod h1:yL2t6BqATOLGc5HF7qbFkTfXoPIY0WZdWHfEvMqbG+4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/magiconair/properties v1.8.10 h1:s31yESBquKXCV9a/ScB3ESkOjUYYv+X0rg8SYxI99mE= +github.com/magiconair/properties v1.8.10/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.41 h1:WMszZWJG0XmzbK9FEmzH2TVcqYzFesusSIB41b8KHxY= +github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/olekukonko/errors v1.1.0 h1:RNuGIh15QdDenh+hNvKrJkmxxjV4hcS50Db478Ou5sM= +github.com/olekukonko/errors v1.1.0/go.mod h1:ppzxA5jBKcO1vIpCXQ9ZqgDh8iwODz6OXIGKU8r5m4Y= +github.com/olekukonko/ll v0.0.9 h1:Y+1YqDfVkqMWuEQMclsF9HUR5+a82+dxJuL1HHSRpxI= +github.com/olekukonko/ll v0.0.9/go.mod h1:En+sEW0JNETl26+K8eZ6/W4UQ7CYSrrgg/EdIYT2H8g= +github.com/olekukonko/tablewriter v1.1.0 h1:N0LHrshF4T39KvI96fn6GT8HEjXRXYNDrDjKFDB7RIY= +github.com/olekukonko/tablewriter v1.1.0/go.mod h1:5c+EBPeSqvXnLLgkm9isDdzR3wjfBkHR9Nhfp3NWrzo= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/redis/go-redis/v9 v9.12.1 h1:k5iquqv27aBtnTm2tIkROUDp8JBXhXZIVu1InSgvovg= +github.com/redis/go-redis/v9 v9.12.1/go.mod h1:huWgSWd8mW6+m0VPhJjSSQ+d6Nh1VICQ6Q5lHuCH/Iw= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tiger1103/gfast-token v1.0.10 h1:fNiBE/Dq5iTHvTGlCx3DmXa2o4hr0NtumFpffZ39k6s= +github.com/tiger1103/gfast-token v1.0.10/go.mod h1:a/21mxmj7zFeNvjhZSC0XpEAFHfb1aT2k6DXnufFU1s= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +go.mongodb.org/mongo-driver/v2 v2.4.0 h1:Oq6BmUAAFTzMeh6AonuDlgZMuAuEiUxoAD1koK5MuFo= +go.mongodb.org/mongo-driver/v2 v2.4.0/go.mod h1:jHeEDJHJq7tm6ZF45Issun9dbogjfnPySb1vXA7EeAI= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= +go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= +go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU= +golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63 h1:m64FZMko/V45gv0bNmrNYoDEq8U5YUhetc9cBWKS1TQ= +golang.org/x/exp v0.0.0-20230817173708-d852ddb80c63/go.mod h1:0v4NqG35kSWCMzLaMeX+IQrlSnVE/bqGSyC2cz/9Le8= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5 h1:BIRfGDEjiHRrk0QKZe3Xv2ieMhtgRGeLcZQ0mIVn4EY= +google.golang.org/genproto/googleapis/api v0.0.0-20250825161204-c5933d9347a5/go.mod h1:j3QtIyytwqGr1JUDtYXwtMXWPKsEa5LtzIFN1Wn5WvE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5 h1:eaY8u2EuxbRv7c3NiGK0/NedzVsCcV6hDuU5qPX5EGE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.75.0 h1:+TW+dqTd2Biwe6KKfhE5JpiYIBWq865PhKGSXiivqt4= +google.golang.org/grpc v1.75.0/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= +google.golang.org/protobuf v1.36.8 h1:xHScyCOEuuwZEc6UtSOvPbAT4zRh0xcNRYekJwfqyMc= +google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/main.go b/main.go index 9e51ea4..ac64570 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,11 @@ import ( func main() { defer jaeger.ShutDown(context.Background()) http.RouteRegister([]interface{}{ - controller.Data, + controller.Advertisement, + controller.Advertiser, + controller.AdPosition, + controller.AdStatistics, + controller.Report, }) select {} } diff --git a/model/dto/ad_position_dto.go b/model/dto/ad_position_dto.go new file mode 100644 index 0000000..0e09459 --- /dev/null +++ b/model/dto/ad_position_dto.go @@ -0,0 +1,163 @@ +package dto + +import ( + "cidService/model/entity" + + "gitee.com/red-future---jilin-g/common/http" + "github.com/gogf/gf/v2/frame/g" +) + +// AddAdPositionReq 添加广告位请求 +type AddAdPositionReq struct { + g.Meta `path:"/adposition/add" method:"post" tags:"广告位管理" summary:"添加广告位" dc:"添加新的广告位"` + + // 基本信息 + Name string `json:"name" v:"required"` // 广告位名称 + Description string `json:"description"` // 广告位描述 + PositionCode string `json:"positionCode" v:"required"` // 广告位编码,用于标识 + AdFormat string `json:"adFormat" v:"required"` // 支持的广告格式 + + // 尺寸信息 + Width int `json:"width" v:"required"` // 宽度(px) + Height int `json:"height" v:"required"` // 高度(px) + + // 位置信息 + Page string `json:"page" v:"required"` // 所属页面 + Section string `json:"section" v:"required"` // 页面区域 + Location string `json:"location" v:"required"` // 具体位置 + + // 展示设置 + MaxAds int `json:"maxAds"` // 最大广告数量 + RefreshInterval int `json:"refreshInterval"` // 刷新间隔(秒) + IsLazyLoad bool `json:"isLazyLoad"` // 是否懒加载 + + // 定价设置 + PricingModel string `json:"pricingModel" v:"required"` // 计费模型:CPC、CPM、CPA等 + BasePrice int64 `json:"basePrice" v:"required"` // 基础价格(分) + FloorPrice int64 `json:"floorPrice" v:"required"` // 底价(分) + PriceUnit string `json:"priceUnit" v:"required"` // 价格单位:千次展示、单次点击、单次转化等 + + // 展示规则 + DisplayRules *entity.DisplayRules `json:"displayRules"` // 展示规则 + + // 状态信息 + Status string `json:"status" v:"required"` // 广告位状态:启用、禁用、测试 + IsExclusive bool `json:"isExclusive"` // 是否独占广告位 +} + +type AddAdPositionRes struct { + Id string `json:"id"` +} + +// UpdateAdPositionReq 更新广告位请求 +type UpdateAdPositionReq struct { + g.Meta `path:"/adposition/update" method:"post" tags:"广告位管理" summary:"更新广告位" dc:"更新广告位信息"` + + Id string `json:"id" v:"required"` // ID + + // 基本信息 + Name string `json:"name"` // 广告位名称 + Description string `json:"description"` // 广告位描述 + PositionCode string `json:"positionCode"` // 广告位编码,用于标识 + AdFormat string `json:"adFormat"` // 支持的广告格式 + + // 尺寸信息 + Width *int `json:"width"` // 宽度(px) + Height *int `json:"height"` // 高度(px) + + // 位置信息 + Page string `json:"page"` // 所属页面 + Section string `json:"section"` // 页面区域 + Location string `json:"location"` // 具体位置 + + // 展示设置 + MaxAds *int `json:"maxAds"` // 最大广告数量 + RefreshInterval *int `json:"refreshInterval"` // 刷新间隔(秒) + IsLazyLoad *bool `json:"isLazyLoad"` // 是否懒加载 + + // 定价设置 + PricingModel string `json:"pricingModel"` // 计费模型:CPC、CPM、CPA等 + BasePrice *int64 `json:"basePrice"` // 基础价格(分) + FloorPrice *int64 `json:"floorPrice"` // 底价(分) + PriceUnit string `json:"priceUnit"` // 价格单位:千次展示、单次点击、单次转化等 + + // 展示规则 + DisplayRules *entity.DisplayRules `json:"displayRules"` // 展示规则 + + // 状态信息 + Status *string `json:"status"` // 广告位状态:启用、禁用、测试 + IsExclusive *bool `json:"isExclusive"` // 是否独占广告位 +} + +// GetAdPositionReq 获取广告位详情请求 +type GetAdPositionReq struct { + g.Meta `path:"/adposition/one" method:"get" tags:"广告位管理" summary:"获取广告位详情" dc:"根据ID获取单个广告位详情"` + Id string `json:"id" v:"required"` // ID +} + +type GetAdPositionRes struct { + *entity.AdPosition +} + +// ListAdPositionReq 获取广告位列表请求 +type ListAdPositionReq struct { + g.Meta `path:"/adposition/list" method:"get" tags:"广告位管理" summary:"获取广告位列表" dc:"分页查询广告位列表,支持多条件筛选"` + http.Page + + Name string `json:"name"` // 广告位名称模糊查询 + PositionCode string `json:"positionCode"` // 广告位编码 + PageName string `json:"pageName"` // 所属页面 + Section string `json:"section"` // 页面区域 + Status string `json:"status"` // 广告位状态 + AdFormat string `json:"adFormat"` // 广告格式 + DateRange []string `json:"dateRange"` // 创建时间范围 [start, end] +} + +type ListAdPositionRes struct { + List []*entity.AdPosition `json:"list"` + Total int `json:"total"` +} + +// UpdateAdPositionStatusReq 更新广告位状态请求 +type UpdateAdPositionStatusReq struct { + g.Meta `path:"/adposition/status" method:"post" tags:"广告位管理" summary:"更新广告位状态" dc:"更新广告位状态"` + + Id string `json:"id" v:"required"` // 广告位ID + Status string `json:"status" v:"required"` // 广告位状态:启用、禁用、测试 +} + +// GetAdPositionStatisticsReq 获取广告位统计数据请求 +type GetAdPositionStatisticsReq struct { + g.Meta `path:"/adposition/statistics" method:"get" tags:"广告位管理" summary:"获取广告位统计数据" dc:"获取广告位的统计数据"` + + Id string `json:"id" v:"required"` // 广告位ID + StatType string `json:"statType" v:"required"` // 统计类型:天、周、月 + StartDate int64 `json:"startDate"` // 开始日期 + EndDate int64 `json:"endDate"` // 结束日期 +} + +type GetAdPositionStatisticsRes struct { + Statistics []*entity.AdStatistics `json:"statistics"` + Total int `json:"total"` +} + +// GetAvailableAdPositionsReq 获取可用广告位请求 +type GetAvailableAdPositionsReq struct { + g.Meta `path:"/adposition/available" method:"get" tags:"广告位管理" summary:"获取可用广告位列表" dc:"获取所有启用的广告位列表"` +} + +type GetAvailableAdPositionsRes struct { + List []*entity.AdPosition `json:"list"` +} + +// MatchAdReq 匹配广告请求 +type MatchAdReq struct { + g.Meta `path:"/adposition/match" method:"post" tags:"广告位管理" summary:"匹配广告" dc:"根据广告位编码和用户信息匹配适合的广告"` + + PositionCode string `json:"positionCode" v:"required"` // 广告位编码 + UserInfo map[string]interface{} `json:"userInfo"` // 用户信息 +} + +type MatchAdRes struct { + *entity.Advertisement `json:"advertisement"` +} diff --git a/model/dto/ad_statistics_dto.go b/model/dto/ad_statistics_dto.go new file mode 100644 index 0000000..0d8bc76 --- /dev/null +++ b/model/dto/ad_statistics_dto.go @@ -0,0 +1,113 @@ +package dto + +import ( + "cidService/model/entity" + + "gitee.com/red-future---jilin-g/common/http" + "github.com/gogf/gf/v2/frame/g" +) + +// GetAdStatisticsReq 获取广告统计数据请求 +type GetAdStatisticsReq struct { + g.Meta `path:"/statistics/list" method:"get" tags:"广告统计" summary:"获取广告统计数据" dc:"获取广告的统计数据"` + + // 分页参数 + http.Page + + // 维度信息 + StatType string `json:"statType" v:"required"` // 统计类型:广告主、广告、广告位等 + StatDimension string `json:"statDimension" v:"required"` // 统计维度:小时、天、周、月 + TargetId string `json:"targetId"` // 目标ID:广告主ID、广告ID、广告位ID等 + + // 时间范围 + StartDate int64 `json:"startDate" v:"required"` // 开始日期 + EndDate int64 `json:"endDate" v:"required"` // 结束日期 + + // 筛选条件 + DeviceType string `json:"deviceType"` // 设备类型 + Platform string `json:"platform"` // 平台 + Region string `json:"region"` // 地区 + Gender string `json:"gender"` // 性别 + AgeGroup string `json:"ageGroup"` // 年龄段 + + // 排序 + SortBy string `json:"sortBy"` // 排序字段 + SortDirection string `json:"sortDirection"` // 排序方向:asc、desc +} + +type GetAdStatisticsRes struct { + Statistics []*entity.AdStatistics `json:"statistics"` + Total int `json:"total"` +} + +// GetDashboardReq 获取仪表盘数据请求 +type GetDashboardReq struct { + g.Meta `path:"/dashboard" method:"get" tags:"广告仪表盘" summary:"获取仪表盘数据" dc:"获取广告系统的仪表盘统计数据"` + + // 时间范围 + StartDate int64 `json:"startDate" v:"required"` // 开始日期 + EndDate int64 `json:"endDate" v:"required"` // 结束日期 + + // 维度 + Dimension string `json:"dimension"` // 统计维度:天、周、月 +} + +type GetDashboardRes struct { + // 总览数据 + Overview OverviewData `json:"overview"` + + // 趋势数据 + Trends []TrendData `json:"trends"` + + // 排行数据 + TopAdvertisers []RankData `json:"topAdvertisers"` // 广告主排行 + TopAds []RankData `json:"topAds"` // 广告排行 + TopPositions []RankData `json:"topPositions"` // 广告位排行 +} + +// OverviewData 总览数据 +type OverviewData struct { + TotalAdvertisers int64 `json:"totalAdvertisers"` // 广告主总数 + TotalAds int64 `json:"totalAds"` // 广告总数 + TotalPositions int64 `json:"totalPositions"` // 广告位总数 + TotalImpressions int64 `json:"totalImpressions"` // 总展示次数 + TotalClicks int64 `json:"totalClicks"` // 总点击次数 + TotalCost int64 `json:"totalCost"` // 总消耗(分) + TotalRevenue int64 `json:"totalRevenue"` // 总收入(分) + AverageCTR float64 `json:"averageCTR"` // 平均点击率 + AverageCVR float64 `json:"averageCVR"` // 平均转化率 +} + +// TrendData 趋势数据 +type TrendData struct { + Date int64 `json:"date"` // 日期 + Impressions int64 `json:"impressions"` // 展示次数 + Clicks int64 `json:"clicks"` // 点击次数 + Conversions int64 `json:"conversions"` // 转化次数 + Cost int64 `json:"cost"` // 消耗(分) + Revenue int64 `json:"revenue"` // 收入(分) + CTR float64 `json:"ctr"` // 点击率 + CVR float64 `json:"cvr"` // 转化率 +} + +// RankData 排行数据 +type RankData struct { + Id string `json:"id"` // ID + Name string `json:"name"` // 名称 + Impressions int64 `json:"impressions"` // 展示次数 + Clicks int64 `json:"clicks"` // 点击次数 + Cost int64 `json:"cost"` // 消耗(分) + Revenue int64 `json:"revenue"` // 收入(分) + CTR float64 `json:"ctr"` // 点击率 +} + +// GenerateDailyStatisticsReq 生成每日统计数据请求 +type GenerateDailyStatisticsReq struct { + g.Meta `path:"/statistics/generate-daily" method:"post" tags:"广告统计" summary:"生成每日统计数据" dc:"手动生成指定日期的广告统计数据"` + + Date int64 `json:"date" v:"required"` // 日期时间戳 +} + +type GenerateDailyStatisticsRes struct { + Success bool `json:"success"` // 是否成功 +} diff --git a/model/dto/advertisement_dto.go b/model/dto/advertisement_dto.go new file mode 100644 index 0000000..f407ca6 --- /dev/null +++ b/model/dto/advertisement_dto.go @@ -0,0 +1,133 @@ +package dto + +import ( + "cidService/model/entity" + + "gitee.com/red-future---jilin-g/common/http" + "github.com/gogf/gf/v2/frame/g" +) + +// AddAdvertisementReq 添加广告请求 +type AddAdvertisementReq struct { + g.Meta `path:"/advertisement/add" method:"post" tags:"广告管理" summary:"添加广告" dc:"添加新的广告"` + + // 广告基本信息 + Title string `json:"title" v:"required"` // 广告标题 + Description string `json:"description"` // 广告描述 + AdvertiserId string `json:"advertiserId" v:"required"` // 广告主ID + AdPositionId string `json:"adPositionId" v:"required"` // 广告位ID + AdType string `json:"adType" v:"required"` // 广告类型:图片、视频、文字等 + AdFormat string `json:"adFormat" v:"required"` // 广告格式 + MaterialUrl string `json:"materialUrl" v:"required"` // 广告素材URL + LinkUrl string `json:"linkUrl"` // 点击跳转链接 + LandingPageUrl string `json:"landingPageUrl"` // 落地页URL + + // 投放设置 + StartDate int64 `json:"startDate" v:"required"` // 开始投放时间 + EndDate int64 `json:"endDate" v:"required"` // 结束投放时间 + Budget int64 `json:"budget" v:"required"` // 预算(分) + DailyBudget int64 `json:"dailyBudget"` // 日预算(分) + BidAmount int64 `json:"bidAmount" v:"required"` // 出价(分) + BillingType string `json:"billingType" v:"required"` // 计费类型:CPC、CPM、CPA等 + + // 投放条件 + Targeting *entity.Targeting `json:"targeting"` // 定向条件 +} + +type AddAdvertisementRes struct { + Id string `json:"id"` +} + +// UpdateAdvertisementReq 更新广告请求 +type UpdateAdvertisementReq struct { + g.Meta `path:"/advertisement/update" method:"post" tags:"广告管理" summary:"更新广告" dc:"更新广告信息"` + + Id string `json:"id" v:"required"` // ID + + // 广告基本信息 + Title string `json:"title"` // 广告标题 + Description string `json:"description"` // 广告描述 + AdvertiserId string `json:"advertiserId"` // 广告主ID + AdPositionId string `json:"adPositionId"` // 广告位ID + AdType string `json:"adType"` // 广告类型:图片、视频、文字等 + AdFormat string `json:"adFormat"` // 广告格式 + MaterialUrl string `json:"materialUrl"` // 广告素材URL + LinkUrl string `json:"linkUrl"` // 点击跳转链接 + LandingPageUrl string `json:"landingPageUrl"` // 落地页URL + + // 投放设置 + StartDate *int64 `json:"startDate"` // 开始投放时间 + EndDate *int64 `json:"endDate"` // 结束投放时间 + Budget *int64 `json:"budget"` // 预算(分) + DailyBudget *int64 `json:"dailyBudget"` // 日预算(分) + BidAmount *int64 `json:"bidAmount"` // 出价(分) + BillingType string `json:"billingType"` // 计费类型:CPC、CPM、CPA等 + + // 投放条件 + Targeting *entity.Targeting `json:"targeting"` // 定向条件 + + // 状态信息 + Status *string `json:"status"` // 广告状态:待审核、已审核、已拒绝、投放中、已暂停、已结束 + AuditStatus *string `json:"auditStatus"` // 审核状态 + AuditReason *string `json:"auditReason"` // 审核不通过原因 +} + +// GetAdvertisementReq 获取广告详情请求 +type GetAdvertisementReq struct { + g.Meta `path:"/advertisement/one" method:"get" tags:"广告管理" summary:"获取广告详情" dc:"根据ID获取单个广告详情"` + Id string `json:"id" v:"required"` // ID +} + +type GetAdvertisementRes struct { + *entity.Advertisement +} + +// ListAdvertisementReq 获取广告列表请求 +type ListAdvertisementReq struct { + g.Meta `path:"/advertisement/list" method:"get" tags:"广告管理" summary:"获取广告列表" dc:"分页查询广告列表,支持多条件筛选"` + http.Page + + AdvertiserId string `json:"advertiserId"` // 广告主ID + AdPositionId string `json:"adPositionId"` // 广告位ID + AdType string `json:"adType"` // 广告类型 + Status string `json:"status"` // 广告状态 + AuditStatus string `json:"auditStatus"` // 审核状态 + Title string `json:"title"` // 广告标题模糊查询 + DateRange []string `json:"dateRange"` // 创建时间范围 [start, end] +} + +type ListAdvertisementRes struct { + List []*entity.Advertisement `json:"list"` + Total int `json:"total"` +} + +// AuditAdvertisementReq 审核广告请求 +type AuditAdvertisementReq struct { + g.Meta `path:"/advertisement/audit" method:"post" tags:"广告管理" summary:"审核广告" dc:"审核广告,通过或拒绝"` + + Id string `json:"id" v:"required"` // 广告ID + AuditStatus string `json:"auditStatus" v:"required"` // 审核状态:通过、拒绝 + AuditReason string `json:"auditReason"` // 审核不通过原因 +} + +// UpdateAdStatusReq 更新广告状态请求 +type UpdateAdStatusReq struct { + g.Meta `path:"/advertisement/status" method:"post" tags:"广告管理" summary:"更新广告状态" dc:"更新广告状态"` + + Id string `json:"id" v:"required"` // 广告ID + Status string `json:"status" v:"required"` // 广告状态:启用、禁用 +} + +// GetAdStatisticsReq 获取广告统计数据请求 +type GetAdStatisticsForAdvertisementReq struct { + g.Meta `path:"/advertisement/statistics" method:"get" tags:"广告管理" summary:"获取广告统计数据" dc:"获取广告的统计数据"` + + Id string `json:"id" v:"required"` // 广告ID + StatType string `json:"statType" v:"required"` // 统计类型:天、周、月 + StartDate int64 `json:"startDate"` // 开始日期 + EndDate int64 `json:"endDate"` // 结束日期 +} + +type GetAdStatisticsForAdvertisementRes struct { + Statistics []*entity.AdStatistics `json:"statistics"` +} diff --git a/model/dto/advertiser_dto.go b/model/dto/advertiser_dto.go new file mode 100644 index 0000000..8414171 --- /dev/null +++ b/model/dto/advertiser_dto.go @@ -0,0 +1,167 @@ +package dto + +import ( + "cidService/model/entity" + + "gitee.com/red-future---jilin-g/common/http" + "github.com/gogf/gf/v2/frame/g" +) + +// AddAdvertiserReq 添加广告主请求 +type AddAdvertiserReq struct { + g.Meta `path:"/advertiser/add" method:"post" tags:"广告主管理" summary:"添加广告主" dc:"添加新的广告主"` + + // 基本信息 + Name string `json:"name" v:"required"` // 广告主名称 + ContactName string `json:"contactName" v:"required"` // 联系人姓名 + ContactPhone string `json:"contactPhone" v:"required"` // 联系电话 + ContactEmail string `json:"contactEmail" v:"required"` // 联系邮箱 + Company string `json:"company" v:"required"` // 公司名称 + Industry string `json:"industry" v:"required"` // 所属行业 + Scale string `json:"scale"` // 公司规模 + + // 证件信息 + BusinessLicenseUrl string `json:"businessLicenseUrl" v:"required"` // 营业执照URL + ICPLicenseUrl string `json:"icpLicenseUrl"` // ICP备案截图URL + OtherLicenseUrls []string `json:"otherLicenseUrls"` // 其他证件URL + + // 财务信息 + BankName string `json:"bankName" v:"required"` // 开户银行 + BankAccount string `json:"bankAccount" v:"required"` // 银行账号 + AccountName string `json:"accountName" v:"required"` // 账户名称 + + // 合同信息 + ContractId string `json:"contractId"` // 合同编号 + ContractType string `json:"contractType"` // 合同类型 + ContractUrl string `json:"contractUrl"` // 合同文件URL + SignDate int64 `json:"signDate"` // 签约日期 + ExpireDate int64 `json:"expireDate"` // 到期日期 + + // 系统信息 + AccountBalance int64 `json:"accountBalance"` // 账户余额(分) + CreditLimit int64 `json:"creditLimit"` // 授信额度(分) + Remark string `json:"remark"` // 备注 +} + +type AddAdvertiserRes struct { + Id string `json:"id"` +} + +// UpdateAdvertiserReq 更新广告主请求 +type UpdateAdvertiserReq struct { + g.Meta `path:"/advertiser/update" method:"post" tags:"广告主管理" summary:"更新广告主" dc:"更新广告主信息"` + + Id string `json:"id" v:"required"` // ID + + // 基本信息 + Name string `json:"name"` // 广告主名称 + ContactName string `json:"contactName"` // 联系人姓名 + ContactPhone string `json:"contactPhone"` // 联系电话 + ContactEmail string `json:"contactEmail"` // 联系邮箱 + Company string `json:"company"` // 公司名称 + Industry string `json:"industry"` // 所属行业 + Scale string `json:"scale"` // 公司规模 + + // 证件信息 + BusinessLicenseUrl string `json:"businessLicenseUrl"` // 营业执照URL + ICPLicenseUrl string `json:"icpLicenseUrl"` // ICP备案截图URL + OtherLicenseUrls []string `json:"otherLicenseUrls"` // 其他证件URL + + // 财务信息 + BankName string `json:"bankName"` // 开户银行 + BankAccount string `json:"bankAccount"` // 银行账号 + AccountName string `json:"accountName"` // 账户名称 + + // 合同信息 + ContractId string `json:"contractId"` // 合同编号 + ContractType string `json:"contractType"` // 合同类型 + ContractUrl string `json:"contractUrl"` // 合同文件URL + SignDate *int64 `json:"signDate"` // 签约日期 + ExpireDate *int64 `json:"expireDate"` // 到期日期 + + // 系统信息 + AccountBalance *int64 `json:"accountBalance"` // 账户余额(分) + CreditLimit *int64 `json:"creditLimit"` // 授信额度(分) + Remark string `json:"remark"` // 备注 + + // 状态信息 + Status *string `json:"status"` // 广告主状态:待审核、已审核、已拒绝、已冻结 + AuditStatus *string `json:"auditStatus"` // 审核状态 + AuditReason *string `json:"auditReason"` // 审核不通过原因 +} + +// GetAdvertiserReq 获取广告主详情请求 +type GetAdvertiserReq struct { + g.Meta `path:"/advertiser/one" method:"get" tags:"广告主管理" summary:"获取广告主详情" dc:"根据ID获取单个广告主详情"` + Id string `json:"id" v:"required"` // ID +} + +type GetAdvertiserRes struct { + *entity.Advertiser +} + +// ListAdvertiserReq 获取广告主列表请求 +type ListAdvertiserReq struct { + g.Meta `path:"/advertiser/list" method:"get" tags:"广告主管理" summary:"获取广告主列表" dc:"分页查询广告主列表,支持多条件筛选"` + http.Page + + Name string `json:"name"` // 广告主名称模糊查询 + ContactName string `json:"contactName"` // 联系人模糊查询 + Company string `json:"company"` // 公司名称模糊查询 + Industry string `json:"industry"` // 所属行业 + Status string `json:"status"` // 广告主状态 + AuditStatus string `json:"auditStatus"` // 审核状态 + DateRange []string `json:"dateRange"` // 创建时间范围 [start, end] +} + +type ListAdvertiserRes struct { + List []*entity.Advertiser `json:"list"` + Total int `json:"total"` +} + +// AuditAdvertiserReq 审核广告主请求 +type AuditAdvertiserReq struct { + g.Meta `path:"/advertiser/audit" method:"post" tags:"广告主管理" summary:"审核广告主" dc:"审核广告主,通过或拒绝"` + + Id string `json:"id" v:"required"` // 广告主ID + AuditStatus string `json:"auditStatus" v:"required"` // 审核状态:通过、拒绝 + AuditReason string `json:"auditReason"` // 审核不通过原因 +} + +// UpdateAdvertiserStatusReq 更新广告主状态请求 +type UpdateAdvertiserStatusReq struct { + g.Meta `path:"/advertiser/status" method:"post" tags:"广告主管理" summary:"更新广告主状态" dc:"更新广告主状态"` + + Id string `json:"id" v:"required"` // 广告主ID + Status string `json:"status" v:"required"` // 广告主状态:启用、禁用、冻结 +} + +// RechargeAdvertiserReq 广告主充值请求 +type RechargeAdvertiserReq struct { + g.Meta `path:"/advertiser/recharge" method:"post" tags:"广告主管理" summary:"广告主充值" dc:"为广告主账户充值"` + + Id string `json:"id" v:"required"` // 广告主ID + Amount int64 `json:"amount" v:"required"` // 充值金额(分) + Remark string `json:"remark"` // 充值备注 +} + +// UpdateCreditLimitReq 更新授信额度请求 +type UpdateCreditLimitReq struct { + g.Meta `path:"/advertiser/credit" method:"post" tags:"广告主管理" summary:"更新授信额度" dc:"更新广告主的授信额度"` + + Id string `json:"id" v:"required"` // 广告主ID + CreditLimit int64 `json:"creditLimit" v:"required"` // 授信额度(分) + Remark string `json:"remark"` // 备注说明 +} + +// GetAdvertiserBalanceReq 获取广告主余额请求 +type GetAdvertiserBalanceReq struct { + g.Meta `path:"/advertiser/balance" method:"get" tags:"广告主管理" summary:"获取广告主余额" dc:"根据ID获取广告主账户余额和授信额度"` + Id string `json:"id" v:"required"` // 广告主ID +} + +// GetAdvertiserBalanceRes 获取广告主余额响应 +type GetAdvertiserBalanceRes struct { + Balance int64 `json:"balance"` // 账户余额(分) + CreditLimit int64 `json:"creditLimit"` // 授信额度(分) +} diff --git a/model/dto/data_dto.go b/model/dto/data_dto.go deleted file mode 100644 index f8b489b..0000000 --- a/model/dto/data_dto.go +++ /dev/null @@ -1,67 +0,0 @@ -package dto - -import ( - "cidService/model/entity" - - "gitee.com/red-future---jilin-g/common/http" - "github.com/gogf/gf/v2/frame/g" -) - -// AddDataReq 添加数据 -type AddDataReq struct { - g.Meta `path:"/add" method:"post" tags:"数据管理" summary:"添加数据" dc:"记录客服与客户的交互数据"` // 路由: POST /data/add - CustomerId string `json:"customerId" v:"required"` // 客户ID - CustomerServiceId string `json:"customerServiceId" v:"required"` // 客服ID - CustomerServicePlatform string `json:"customerServicePlatform" v:"required"` // 客服平台 - CustomerServiceName string `json:"customerServiceName" v:"required"` // 客服名称 - IsInbound bool `json:"isInbound"` // 是否进线 - IsActive bool `json:"isActive"` // 是否活跃 - IsServed bool `json:"isServed"` // 是否接待 - HasSentContactCard bool `json:"hasSentContactCard"` // 是否发联系卡 - HasSentNameCard bool `json:"hasSentNameCard"` // 是否发名片 - HasLeftContactInfo bool `json:"hasLeftContactInfo"` // 是否留资 - SessionStartTime int64 `json:"sessionStartTime"` // 会话开始时间 - MessageTime int64 `json:"messageTime"` // 消息时间 -} - -type AddDataRes struct { - Id string `json:"id"` -} - -// UpdateDataReq 更新数据 -type UpdateDataReq struct { - g.Meta `path:"/update" method:"post" tags:"数据管理" summary:"更新数据" dc:"更新客服交互数据"` // 路由: POST /data/update - Id string `json:"id" v:"required"` // ID - CustomerId string `json:"customerId"` - CustomerServiceId string `json:"customerServiceId"` - CustomerServicePlatform string `json:"customerServicePlatform"` - CustomerServiceName string `json:"customerServiceName"` - IsInbound *bool `json:"isInbound"` // 使用指针以区分 false 和未传值 - IsActive *bool `json:"isActive"` - IsServed *bool `json:"isServed"` - HasSentContactCard *bool `json:"hasSentContactCard"` - HasSentNameCard *bool `json:"hasSentNameCard"` - HasLeftContactInfo *bool `json:"hasLeftContactInfo"` - SessionStartTime int64 `json:"sessionStartTime"` - MessageTime int64 `json:"messageTime"` -} - -// GetDataReq 获取单个数据 -type GetDataReq struct { - g.Meta `path:"/one" method:"get" tags:"数据管理" summary:"获取数据详情" dc:"根据ID获取单条数据记录"` // 路由: GET /data/one - Id string `json:"id" v:"required"` // ID -} - -// ListDataReq 获取数据列表 -type ListDataReq struct { - g.Meta `path:"/list" method:"get" tags:"数据管理" summary:"获取数据列表" dc:"分页查询交互数据,支持按客户、客服、时间筛选"` // 路由: GET /data/list - http.Page - CustomerId string `json:"customerId"` // 筛选:客户ID - CustomerServiceId string `json:"customerServiceId"` // 筛选:客服ID - DateRange []string `json:"dateRange"` // 筛选:时间范围 [start, end] -} - -type ListDataRes struct { - List []*entity.Data `json:"list"` - Total int `json:"total"` -} diff --git a/model/dto/report_dto.go b/model/dto/report_dto.go new file mode 100644 index 0000000..198a688 --- /dev/null +++ b/model/dto/report_dto.go @@ -0,0 +1,104 @@ +package dto + +import ( + "cidService/model/entity" + + "gitee.com/red-future---jilin-g/common/http" + "github.com/gogf/gf/v2/frame/g" +) + +// CreateReportReq 创建报表请求 +type CreateReportReq struct { + g.Meta `path:"/report/create" method:"post" tags:"广告报表" summary:"创建报表" dc:"创建新的广告报表"` + + // 报表信息 + ReportName string `json:"reportName" v:"required"` // 报表名称 + ReportType string `json:"reportType" v:"required"` // 报表类型:日报、周报、月报、自定义 + ReportPeriod string `json:"reportPeriod" v:"required"` // 报表周期 + StartDate int64 `json:"startDate" v:"required"` // 开始日期 + EndDate int64 `json:"endDate" v:"required"` // 结束日期 + ReportConfig map[string]interface{} `json:"reportConfig"` // 报表配置 + + // 其他信息 + FileFormat string `json:"fileFormat"` // 文件格式:CSV、Excel、PDF + EmailRecipients []string `json:"emailRecipients"` // 邮件接收人列表 + Schedule string `json:"schedule"` // 定时设置 +} + +type CreateReportRes struct { + Id string `json:"id"` +} + +// GetReportReq 获取报表详情请求 +type GetReportReq struct { + g.Meta `path:"/report/one" method:"get" tags:"广告报表" summary:"获取报表详情" dc:"根据ID获取单个报表详情"` + Id string `json:"id" v:"required"` // ID +} + +type GetReportRes struct { + *entity.AdReport +} + +// ListReportReq 获取报表列表请求 +type ListReportReq struct { + g.Meta `path:"/report/list" method:"get" tags:"广告报表" summary:"获取报表列表" dc:"分页查询报表列表,支持多条件筛选"` + http.Page + + ReportName string `json:"reportName"` // 报表名称模糊查询 + ReportType string `json:"reportType"` // 报表类型 + Status string `json:"status"` // 报表状态 + Operator string `json:"operator"` // 操作人 + DateRange []string `json:"dateRange"` // 创建时间范围 [start, end] +} + +type ListReportRes struct { + List []*entity.AdReport `json:"list"` + Total int `json:"total"` +} + +// UpdateReportReq 更新报表请求 +type UpdateReportReq struct { + g.Meta `path:"/report/update" method:"post" tags:"广告报表" summary:"更新报表" dc:"更新报表信息"` + + Id string `json:"id" v:"required"` // ID + + // 报表信息 + ReportName string `json:"reportName"` // 报表名称 + ReportType string `json:"reportType"` // 报表类型:日报、周报、月报、自定义 + ReportPeriod string `json:"reportPeriod"` // 报表周期 + StartDate *int64 `json:"startDate"` // 开始日期 + EndDate *int64 `json:"endDate"` // 结束日期 + ReportConfig map[string]interface{} `json:"reportConfig"` // 报表配置 + + // 其他信息 + FileFormat string `json:"fileFormat"` // 文件格式:CSV、Excel、PDF + EmailRecipients []string `json:"emailRecipients"` // 邮件接收人列表 + Schedule string `json:"schedule"` // 定时设置 +} + +// DeleteReportReq 删除报表请求 +type DeleteReportReq struct { + g.Meta `path:"/report/delete" method:"post" tags:"广告报表" summary:"删除报表" dc:"删除指定的报表"` + + Id string `json:"id" v:"required"` // 报表ID +} + +// DownloadReportReq 下载报表请求 +type DownloadReportReq struct { + g.Meta `path:"/report/download" method:"get" tags:"广告报表" summary:"下载报表" dc:"下载指定的报表文件"` + + Id string `json:"id" v:"required"` // 报表ID +} + +type DownloadReportRes struct { + DownloadUrl string `json:"downloadUrl"` // 下载链接 + FileSize int64 `json:"fileSize"` // 文件大小(字节) + FileFormat string `json:"fileFormat"` // 文件格式 +} + +// GenerateReportReq 生成报表请求 +type GenerateReportReq struct { + g.Meta `path:"/report/generate" method:"post" tags:"广告报表" summary:"生成报表" dc:"手动生成报表"` + + Id string `json:"id" v:"required"` // 报表ID +} diff --git a/model/entity/ad_position.go b/model/entity/ad_position.go new file mode 100644 index 0000000..c1b15ef --- /dev/null +++ b/model/entity/ad_position.go @@ -0,0 +1,82 @@ +package entity + +import ( + "gitee.com/red-future---jilin-g/common/do" +) + +const AdPositionCollection = "ad_position" + +// AdPosition 广告位实体 +type AdPosition struct { + do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + + // 基本信息 + Name string `bson:"name" json:"name"` // 广告位名称 + Description string `bson:"description" json:"description"` // 广告位描述 + PositionCode string `bson:"positionCode" json:"positionCode"` // 广告位编码,用于标识 + AdFormat string `bson:"adFormat" json:"adFormat"` // 支持的广告格式 + + // 尺寸信息 + Width int `bson:"width" json:"width"` // 宽度(px) + Height int `bson:"height" json:"height"` // 高度(px) + + // 位置信息 + Page string `bson:"page" json:"page"` // 所属页面 + Section string `bson:"section" json:"section"` // 页面区域 + Location string `bson:"location" json:"location"` // 具体位置 + + // 展示设置 + MaxAds int `bson:"maxAds" json:"maxAds"` // 最大广告数量 + RefreshInterval int `bson:"refreshInterval" json:"refreshInterval"` // 刷新间隔(秒) + IsLazyLoad bool `bson:"isLazyLoad" json:"isLazyLoad"` // 是否懒加载 + + // 定价设置 + PricingModel string `bson:"pricingModel" json:"pricingModel"` // 计费模型:CPC、CPM、CPA等 + BasePrice int64 `bson:"basePrice" json:"basePrice"` // 基础价格(分) + FloorPrice int64 `bson:"floorPrice" json:"floorPrice"` // 底价(分) + PriceUnit string `bson:"priceUnit" json:"priceUnit"` // 价格单位:千次展示、单次点击、单次转化等 + + // 展示规则 + DisplayRules *DisplayRules `bson:"displayRules" json:"displayRules"` // 展示规则 + + // 状态信息 + Status string `bson:"status" json:"status"` // 广告位状态:启用、禁用、测试 + IsExclusive bool `bson:"isExclusive" json:"isExclusive"` // 是否独占广告位 + + // 统计信息 + DailyImpressions int64 `bson:"dailyImpressions" json:"dailyImpressions"` // 日均展示量 + DailyClicks int64 `bson:"dailyClicks" json:"dailyClicks"` // 日均点击量 + DailyRevenue int64 `bson:"dailyRevenue" json:"dailyRevenue"` // 日均收入(分) + CTR float64 `bson:"ctr" json:"ctr"` // 点击率 + eCPM int64 `bson:"ecpm" json:"ecpm"` // 有效千次展示收入 +} + +// DisplayRules 广告位展示规则 +type DisplayRules struct { + // 频次控制 + FrequencyCap *FrequencyCap `bson:"frequencyCap" json:"frequencyCap"` // 频次控制 + + // 展示条件 + DisplayConditions []DisplayCondition `bson:"displayConditions" json:"displayConditions"` // 展示条件 + + // 排除条件 + ExcludeConditions []ExcludeCondition `bson:"excludeConditions" json:"excludeConditions"` // 排除条件 +} + +// FrequencyCap 频次控制 +type FrequencyCap struct { + Impressions int `bson:"impressions" json:"impressions"` // 展示次数 + TimeWindow int `bson:"timeWindow" json:"timeWindow"` // 时间窗口(小时) +} + +// DisplayCondition 展示条件 +type DisplayCondition struct { + Type string `bson:"type" json:"type"` // 条件类型 + Value interface{} `bson:"value" json:"value"` // 条件值 +} + +// ExcludeCondition 排除条件 +type ExcludeCondition struct { + Type string `bson:"type" json:"type"` // 条件类型 + Value interface{} `bson:"value" json:"value"` // 条件值 +} diff --git a/model/entity/ad_statistics.go b/model/entity/ad_statistics.go new file mode 100644 index 0000000..1e8157a --- /dev/null +++ b/model/entity/ad_statistics.go @@ -0,0 +1,92 @@ +package entity + +import ( + "gitee.com/red-future---jilin-g/common/do" +) + +const AdStatisticsCollection = "ad_statistics" + +// AdStatistics 广告统计实体 +type AdStatistics struct { + do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + + // 维度信息 + StatType string `bson:"statType" json:"statType"` // 统计类型:广告主、广告、广告位等 + StatDimension string `bson:"statDimension" json:"statDimension"` // 统计维度:小时、天、周、月 + TargetId string `bson:"targetId" json:"targetId"` // 目标ID:广告主ID、广告ID、广告位ID等 + TargetName string `bson:"targetName" json:"targetName"` // 目标名称:广告主名称、广告名称、广告位名称等 + StatDate int64 `bson:"statDate" json:"statDate"` // 统计日期(Unix时间戳) + + // 基础数据 + Impressions int64 `bson:"impressions" json:"impressions"` // 展示次数 + Clicks int64 `bson:"clicks" json:"clicks"` // 点击次数 + Conversions int64 `bson:"conversions" json:"conversions"` // 转化次数 + UniqueUsers int64 `bson:"uniqueUsers" json:"uniqueUsers"` // 唯一用户数 + NewUsers int64 `bson:"newUsers" json:"newUsers"` // 新用户数 + ReturnUsers int64 `bson:"returnUsers" json:"returnUsers"` // 回访用户数 + ViewTime int64 `bson:"viewTime" json:"viewTime"` // 查看时间(秒) + + // 财务数据 + Cost int64 `bson:"cost" json:"cost"` // 消耗(分) + Revenue int64 `bson:"revenue" json:"revenue"` // 收入(分) + Budget int64 `bson:"budget" json:"budget"` // 预算(分) + RemainingBudget int64 `bson:"remainingBudget" json:"remainingBudget"` // 剩余预算(分) + + // 比率数据 + CTR float64 `bson:"ctr" json:"ctr"` // 点击率 + CVR float64 `bson:"cvr" json:"cvr"` // 转化率 + BounceRate float64 `bson:"bounceRate" json:"bounceRate"` // 跳出率 + EngagementRate float64 `bson:"engagementRate" json:"engagementRate"` // 互动率 + + // 成本数据 + CPM int64 `bson:"cpm" json:"cpm"` // 千次展示成本 + CPC int64 `bson:"cpc" json:"cpc"` // 单次点击成本 + CPA int64 `bson:"cpa" json:"cpa"` // 单次转化成本 + eCPM int64 `bson:"ecpm" json:"ecpm"` // 有效千次展示收入 + eCPC int64 `bson:"ecpc" json:"ecpc"` // 有效单次点击收入 + eCPA int64 `bson:"ecpa" json:"ecpa"` // 有效单次转化收入 + + // 其他信息 + DeviceType string `bson:"deviceType" json:"deviceType"` // 设备类型 + Platform string `bson:"platform" json:"platform"` // 平台 + Region string `bson:"region" json:"region"` // 地区 + Gender string `bson:"gender" json:"gender"` // 性别 + AgeGroup string `bson:"ageGroup" json:"ageGroup"` // 年龄段 + Extra map[string]interface{} `bson:"extra" json:"extra"` // 扩展字段 +} + +const AdReportCollection = "ad_report" + +// AdReport 广告报表实体 +type AdReport struct { + do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + + // 报表信息 + ReportName string `bson:"reportName" json:"reportName"` // 报表名称 + ReportType string `bson:"reportType" json:"reportType"` // 报表类型:日报、周报、月报、自定义 + ReportPeriod string `bson:"reportPeriod" json:"reportPeriod"` // 报表周期 + StartDate int64 `bson:"startDate" json:"startDate"` // 开始日期 + EndDate int64 `bson:"endDate" json:"endDate"` // 结束日期 + ReportData []ReportItem `bson:"reportData" json:"reportData"` // 报表数据 + + // 状态信息 + Status string `bson:"status" json:"status"` // 报表状态:生成中、已完成、失败 + GenerateTime int64 `bson:"generateTime" json:"generateTime"` // 生成时间 + DownloadUrl string `bson:"downloadUrl" json:"downloadUrl"` // 下载链接 + ExpiredTime int64 `bson:"expiredTime" json:"expiredTime"` // 过期时间 + FileSize int64 `bson:"fileSize" json:"fileSize"` // 文件大小(字节) + FileFormat string `bson:"fileFormat" json:"fileFormat"` // 文件格式:CSV、Excel、PDF + + // 其他信息 + Operator string `bson:"operator" json:"operator"` // 操作人 + EmailRecipients []string `bson:"emailRecipients" json:"emailRecipients"` // 邮件接收人列表 + Schedule string `bson:"schedule" json:"schedule"` // 定时设置 + LastSentTime int64 `bson:"lastSentTime" json:"lastSentTime"` // 上次发送时间 + NextSendTime int64 `bson:"nextSendTime" json:"nextSendTime"` // 下次发送时间 +} + +// ReportItem 报表项 +type ReportItem struct { + Dimension string `bson:"dimension" json:"dimension"` // 维度名称 + Data map[string]interface{} `bson:"data" json:"data"` // 数据 +} diff --git a/model/entity/advertisement.go b/model/entity/advertisement.go new file mode 100644 index 0000000..ae7aeaf --- /dev/null +++ b/model/entity/advertisement.go @@ -0,0 +1,91 @@ +package entity + +import ( + "gitee.com/red-future---jilin-g/common/do" +) + +const AdvertisementCollection = "advertisement" + +// Advertisement 广告实体 +type Advertisement struct { + do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + + // 广告基本信息 + Title string `bson:"title" json:"title"` // 广告标题 + Description string `bson:"description" json:"description"` // 广告描述 + AdvertiserId string `bson:"advertiserId" json:"advertiserId"` // 广告主ID + AdPositionId string `bson:"adPositionId" json:"adPositionId"` // 广告位ID + AdType string `bson:"adType" json:"adType"` // 广告类型:图片、视频、文字等 + AdFormat string `bson:"adFormat" json:"adFormat"` // 广告格式 + MaterialUrl string `bson:"materialUrl" json:"materialUrl"` // 广告素材URL + LinkUrl string `bson:"linkUrl" json:"linkUrl"` // 点击跳转链接 + LandingPageUrl string `bson:"landingPageUrl" json:"landingPageUrl"` // 落地页URL + + // 投放设置 + StartDate int64 `bson:"startDate" json:"startDate"` // 开始投放时间 + EndDate int64 `bson:"endDate" json:"endDate"` // 结束投放时间 + Budget int64 `bson:"budget" json:"budget"` // 预算(分) + DailyBudget int64 `bson:"dailyBudget" json:"dailyBudget"` // 日预算(分) + BidAmount int64 `bson:"bidAmount" json:"bidAmount"` // 出价(分) + BillingType string `bson:"billingType" json:"billingType"` // 计费类型:CPC、CPM、CPA等 + + // 投放条件 + Targeting *Targeting `bson:"targeting" json:"targeting"` // 定向条件 + + // 状态信息 + Status string `bson:"status" json:"status"` // 广告状态:待审核、已审核、已拒绝、投放中、已暂停、已结束 + AuditStatus string `bson:"auditStatus" json:"auditStatus"` // 审核状态 + AuditReason string `bson:"auditReason" json:"auditReason"` // 审核不通过原因 + AuditTime int64 `bson:"auditTime" json:"auditTime"` // 审核时间 + AuditBy string `bson:"auditBy" json:"auditBy"` // 审核人 + + // 统计信息 + Impressions int64 `bson:"impressions" json:"impressions"` // 展示次数 + Clicks int64 `bson:"clicks" json:"clicks"` // 点击次数 + Conversions int64 `bson:"conversions" json:"conversions"` // 转化次数 + Cost int64 `bson:"cost" json:"cost"` // 消耗(分) + CTR float64 `bson:"ctr" json:"ctr"` // 点击率 + CVR float64 `bson:"cvr" json:"cvr"` // 转化率 + CPM int64 `bson:"cpm" json:"cpm"` // 千次展示成本 + CPC int64 `bson:"cpc" json:"cpc"` // 单次点击成本 +} + +// Targeting 广告定向条件 +type Targeting struct { + // 地域定向 + Regions []string `bson:"regions" json:"regions"` // 地域列表 + + // 兴趣定向 + Interests []string `bson:"interests" json:"interests"` // 兴趣标签 + + // 年龄定向 + AgeRange *AgeRange `bson:"ageRange" json:"ageRange"` // 年龄范围 + + // 性别定向 + Gender []string `bson:"gender" json:"gender"` // 性别:男、女、全部 + + // 设备定向 + Devices []string `bson:"devices" json:"devices"` // 设备类型 + + // 操作系统定向 + OperatingSystems []string `bson:"operatingSystems" json:"operatingSystems"` // 操作系统 + + // 时间定向 + TimeSlots []TimeSlot `bson:"timeSlots" json:"timeSlots"` // 时间段 + + // 行为定向 + Behaviors []string `bson:"behaviors" json:"behaviors"` // 行为标签 +} + +// AgeRange 年龄范围 +type AgeRange struct { + Min int `bson:"min" json:"min"` // 最小年龄 + Max int `bson:"max" json:"max"` // 最大年龄 +} + +// TimeSlot 时间段 +type TimeSlot struct { + DayOfWeek int `bson:"dayOfWeek" json:"dayOfWeek"` // 星期几:0-6,0表示星期日 + StartTime string `bson:"startTime" json:"startTime"` // 开始时间,格式:HH:mm + EndTime string `bson:"endTime" json:"endTime"` // 结束时间,格式:HH:mm +} diff --git a/model/entity/advertiser.go b/model/entity/advertiser.go new file mode 100644 index 0000000..e7f390c --- /dev/null +++ b/model/entity/advertiser.go @@ -0,0 +1,50 @@ +package entity + +import ( + "gitee.com/red-future---jilin-g/common/do" +) + +const AdvertiserCollection = "advertiser" + +// Advertiser 广告主实体 +type Advertiser struct { + do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted + + // 基本信息 + Name string `bson:"name" json:"name"` // 广告主名称 + ContactName string `bson:"contactName" json:"contactName"` // 联系人姓名 + ContactPhone string `bson:"contactPhone" json:"contactPhone"` // 联系电话 + ContactEmail string `bson:"contactEmail" json:"contactEmail"` // 联系邮箱 + Company string `bson:"company" json:"company"` // 公司名称 + Industry string `bson:"industry" json:"industry"` // 所属行业 + Scale string `bson:"scale" json:"scale"` // 公司规模 + + // 证件信息 + BusinessLicenseUrl string `bson:"businessLicenseUrl" json:"businessLicenseUrl"` // 营业执照URL + ICPLicenseUrl string `bson:"icpLicenseUrl" json:"icpLicenseUrl"` // ICP备案截图URL + OtherLicenseUrls []string `bson:"otherLicenseUrls" json:"otherLicenseUrls"` // 其他证件URL + + // 财务信息 + BankName string `bson:"bankName" json:"bankName"` // 开户银行 + BankAccount string `bson:"bankAccount" json:"bankAccount"` // 银行账号 + AccountName string `bson:"accountName" json:"accountName"` // 账户名称 + + // 合同信息 + ContractId string `bson:"contractId" json:"contractId"` // 合同编号 + ContractType string `bson:"contractType" json:"contractType"` // 合同类型 + ContractUrl string `bson:"contractUrl" json:"contractUrl"` // 合同文件URL + SignDate int64 `bson:"signDate" json:"signDate"` // 签约日期 + ExpireDate int64 `bson:"expireDate" json:"expireDate"` // 到期日期 + + // 状态信息 + Status string `bson:"status" json:"status"` // 广告主状态:待审核、已审核、已拒绝、已冻结 + AuditStatus string `bson:"auditStatus" json:"auditStatus"` // 审核状态 + AuditReason string `bson:"auditReason" json:"auditReason"` // 审核不通过原因 + AuditTime int64 `bson:"auditTime" json:"auditTime"` // 审核时间 + AuditBy string `bson:"auditBy" json:"auditBy"` // 审核人 + + // 系统信息 + AccountBalance int64 `bson:"accountBalance" json:"accountBalance"` // 账户余额(分) + CreditLimit int64 `bson:"creditLimit" json:"creditLimit"` // 授信额度(分) + Remark string `bson:"remark" json:"remark"` // 备注 +} diff --git a/model/entity/data.go b/model/entity/data.go deleted file mode 100644 index 7760655..0000000 --- a/model/entity/data.go +++ /dev/null @@ -1,25 +0,0 @@ -package entity - -import ( - "gitee.com/red-future---jilin-g/common/do" -) - -const DataCollection = "data" - -type Data struct { - do.MongoBaseDO `bson:",inline"` // 嵌入基础字段:Id, Creator, CreatedAt, Updater, UpdatedAt, TenantId, IsDeleted - - // 业务字段 - CustomerId string `bson:"customerId" json:"customerId"` // 客户ID - CustomerServiceId string `bson:"customerServiceId" json:"customerServiceId"` // 客服ID - CustomerServicePlatform string `bson:"customerServicePlatform" json:"customerServicePlatform"` // 客服平台 - CustomerServiceName string `bson:"customerServiceName" json:"customerServiceName"` // 客服名称 - IsInbound bool `bson:"isInbound" json:"isInbound"` // 用户是否点开了客服页面 - IsActive bool `bson:"isActive" json:"isActive"` // 用户是否开口询问 - IsServed bool `bson:"isServed" json:"isServed"` // 客服是否回答了用户 - HasSentContactCard bool `bson:"hasSentContactCard" json:"hasSentContactCard"` // 客服是否发送了联系卡 - HasSentNameCard bool `bson:"hasSentNameCard" json:"hasSentNameCard"` // 客服是否发送了名称卡 - HasLeftContactInfo bool `bson:"hasLeftContactInfo" json:"hasLeftContactInfo"` // 用户是否留下了联系信息 - SessionStartTime int64 `bson:"sessionStartTime" json:"sessionStartTime"` // 业务数据的时间 - MessageTime int64 `bson:"messageTime" json:"messageTime"` // 消息时间 -} diff --git a/service/ad_position_service.go b/service/ad_position_service.go new file mode 100644 index 0000000..e7f59c9 --- /dev/null +++ b/service/ad_position_service.go @@ -0,0 +1,212 @@ +package service + +import ( + "cidService/dao" + "cidService/model/dto" + "cidService/model/entity" + "context" + "time" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/util/gconv" +) + +var AdPosition = new(adPosition) + +type adPosition struct{} + +// Add 添加广告位 +func (s *adPosition) Add(ctx context.Context, req *dto.AddAdPositionReq) (res *dto.AddAdPositionRes, err error) { + adPosition := &entity.AdPosition{} + if err = gconv.Struct(req, adPosition); err != nil { + return + } + + // 设置基础字段 + now := time.Now() + adPosition.CreatedAt = now + adPosition.UpdatedAt = now + adPosition.IsDeleted = false + + // 初始化统计字段 + adPosition.DailyImpressions = 0 + adPosition.DailyClicks = 0 + adPosition.DailyRevenue = 0 + adPosition.CTR = 0 + // eCPM字段是未导出的,无法直接设置 + + if err = dao.AdPosition.Insert(ctx, adPosition); err != nil { + return + } + + res = &dto.AddAdPositionRes{Id: adPosition.Id.Hex()} + return +} + +// Update 更新广告位 +func (s *adPosition) Update(ctx context.Context, req *dto.UpdateAdPositionReq) (err error) { + // 更新修改时间(不需要设置,DAO层会处理) + return dao.AdPosition.Update(ctx, req) +} + +// UpdateStatus 更新广告位状态 +func (s *adPosition) UpdateStatus(ctx context.Context, req *dto.UpdateAdPositionStatusReq) (err error) { + return dao.AdPosition.UpdateStatus(ctx, req.Id, req.Status) +} + +// GetOne 获取广告位详情 +func (s *adPosition) GetOne(ctx context.Context, req *dto.GetAdPositionReq) (res *dto.GetAdPositionRes, err error) { + adPosition, err := dao.AdPosition.GetOne(ctx, req.Id) + if err != nil { + return + } + + res = &dto.GetAdPositionRes{ + AdPosition: adPosition, + } + return +} + +// List 获取广告位列表 +func (s *adPosition) List(ctx context.Context, req *dto.ListAdPositionReq) (res *dto.ListAdPositionRes, err error) { + list, total, err := dao.AdPosition.List(ctx, req) + if err != nil { + return + } + + res = &dto.ListAdPositionRes{ + List: list, + Total: int(total), + } + return +} + +// GetStatistics 获取广告位统计数据 +func (s *adPosition) GetStatistics(ctx context.Context, req *dto.GetAdPositionStatisticsReq) (res *dto.GetAdPositionStatisticsRes, err error) { + statReq := &dto.GetAdStatisticsReq{ + StatType: "adPosition", + StatDimension: req.StatType, + TargetId: req.Id, + StartDate: req.StartDate, + EndDate: req.EndDate, + DeviceType: "", + Platform: "", + Region: "", + Gender: "", + AgeGroup: "", + SortBy: "", + SortDirection: "", + } + + list, total, err := dao.AdStatistics.GetStatistics(ctx, statReq) + if err != nil { + return + } + + res = &dto.GetAdPositionStatisticsRes{ + Statistics: list, + Total: int(total), + } + return +} + +// GetByCode 根据编码获取广告位 +func (s *adPosition) GetByCode(ctx context.Context, code string) (adPosition *entity.AdPosition, err error) { + return dao.AdPosition.GetByCode(ctx, code) +} + +// GetAvailableAdPositions 获取可用的广告位列表 +func (s *adPosition) GetAvailableAdPositions(ctx context.Context) (list []*entity.AdPosition, err error) { + return dao.AdPosition.GetAvailableAdPositions(ctx) +} + +// MatchAd 匹配广告 +func (s *adPosition) MatchAd(ctx context.Context, positionCode string, userInfo map[string]interface{}) (ad *entity.Advertisement, err error) { + // 获取广告位信息 + adPosition, err := dao.AdPosition.GetByCode(ctx, positionCode) + if err != nil { + return + } + + // 检查广告位状态 + if adPosition.Status != "启用" { + return nil, gerror.New("广告位未启用") + } + + // 获取符合条件的广告列表 + // 这里简化处理,实际项目中应该根据广告定向条件匹配广告 + // 可以使用MongoDB的聚合管道实现复杂匹配逻辑 + + // 返回匹配的广告 + // 这里返回第一个广告作为示例 + ad = &entity.Advertisement{ + Title: "示例广告", + MaterialUrl: "https://example.com/ad.jpg", + LinkUrl: "https://example.com", + LandingPageUrl: "https://example.com/landing", + } + + return +} + +// UpdateAdPositionStatistics 更新广告位统计 +func (s *adPosition) UpdateAdPositionStatistics(ctx context.Context, id string, impressions, clicks, revenue int64) (err error) { + // 获取广告位信息 + adPosition, err := dao.AdPosition.GetOne(ctx, id) + if err != nil { + return + } + + // 计算统计数据 + totalImpressions := adPosition.DailyImpressions + impressions + totalClicks := adPosition.DailyClicks + clicks + totalRevenue := adPosition.DailyRevenue + revenue + + // 计算比率 + ctr := 0.0 + if totalImpressions > 0 { + ctr = float64(totalClicks) / float64(totalImpressions) + } + + ecpm := int64(0) + if totalImpressions > 0 { + ecpm = totalRevenue * 1000 / totalImpressions + } + + // 构建更新数据 + stats := map[string]interface{}{ + "dailyImpressions": totalImpressions, + "dailyClicks": totalClicks, + "dailyRevenue": totalRevenue, + "ctr": ctr, + "ecpm": ecpm, + "updatedAt": time.Now(), + } + + // 更新广告位统计 + err = dao.AdPosition.UpdateStatistics(ctx, id, stats) + if err != nil { + return + } + + // 插入统计记录 + today := time.Now() + todayStart := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, today.Location()).Unix() + + statistics := &entity.AdStatistics{ + StatType: "adPosition", + StatDimension: "day", + TargetId: id, + TargetName: adPosition.Name, + StatDate: todayStart, + Impressions: impressions, + Clicks: clicks, + Cost: 0, // 广告位不记录消耗,只记录收入 + Revenue: revenue, + CTR: ctr, + // eCPM字段是未导出的,无法直接设置 + } + + err = dao.AdStatistics.Upsert(ctx, statistics) + return +} diff --git a/service/ad_statistics_service.go b/service/ad_statistics_service.go new file mode 100644 index 0000000..95424eb --- /dev/null +++ b/service/ad_statistics_service.go @@ -0,0 +1,367 @@ +package service + +import ( + "context" + "fmt" + "sort" + "time" + + "cidService/dao" + "cidService/model/dto" + "cidService/model/entity" +) + +var AdStatistics = new(adStatistics) + +type adStatistics struct{} + +// List 获取统计数据列表 +func (s *adStatistics) List(ctx context.Context, req *dto.GetAdStatisticsReq) (res *dto.GetAdStatisticsRes, err error) { + list, total, err := dao.AdStatistics.List(ctx, req) + if err != nil { + return nil, err + } + + res = &dto.GetAdStatisticsRes{ + Statistics: list, + Total: int(total), + } + return +} + +// GetStatistics 获取统计数据 +func (s *adStatistics) GetStatistics(ctx context.Context, req *dto.GetAdStatisticsReq) (res *dto.GetAdStatisticsRes, err error) { + list, total, err := dao.AdStatistics.GetStatistics(ctx, req) + if err != nil { + return nil, err + } + + res = &dto.GetAdStatisticsRes{ + Statistics: list, + Total: int(total), + } + return +} + +// GetDashboard 获取仪表盘数据 +func (s *adStatistics) GetDashboard(ctx context.Context, req *dto.GetDashboardReq) (res *dto.GetDashboardRes, err error) { + // 构建统计查询请求 + statReq := &dto.GetAdStatisticsReq{ + StartDate: req.StartDate, + EndDate: req.EndDate, + StatDimension: req.Dimension, + } + + // 获取所有统计数据 + stats, _, err := dao.AdStatistics.GetStatistics(ctx, statReq) + if err != nil { + return nil, err + } + + // 计算总览数据 + overview := s.calculateOverview(stats) + + // 计算趋势数据 + trends := s.calculateTrends(stats, req.Dimension) + + // 计算排行数据 + topAdvertisers := s.calculateTopAdvertisers(stats) + topAds := s.calculateTopAds(stats) + topPositions := s.calculateTopPositions(stats) + + res = &dto.GetDashboardRes{ + Overview: overview, + Trends: trends, + TopAdvertisers: topAdvertisers, + TopAds: topAds, + TopPositions: topPositions, + } + return +} + +// GenerateDailyStatistics 生成每日统计数据 +func (s *adStatistics) GenerateDailyStatistics(ctx context.Context, date int64) (err error) { + // 转换日期 + t := time.Unix(date, 0) + startOfDay := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, t.Location()) + endOfDay := startOfDay.Add(24 * time.Hour) + + // 生成广告主统计数据 + err = s.generateAdvertiserStatistics(ctx, startOfDay.Unix(), endOfDay.Unix()) + if err != nil { + return fmt.Errorf("生成广告主统计数据失败: %v", err) + } + + // 生成广告统计数据 + err = s.generateAdvertisementStatistics(ctx, startOfDay.Unix(), endOfDay.Unix()) + if err != nil { + return fmt.Errorf("生成广告统计数据失败: %v", err) + } + + // 生成广告位统计数据 + err = s.generateAdPositionStatistics(ctx, startOfDay.Unix(), endOfDay.Unix()) + if err != nil { + return fmt.Errorf("生成广告位统计数据失败: %v", err) + } + + return +} + +// generateAdvertiserStatistics 生成广告主统计数据 +func (s *adStatistics) generateAdvertiserStatistics(ctx context.Context, startDate int64, _ int64) (err error) { + // 这里简化处理,实际项目中应该从日志表或实时数据中聚合 + advertiserStats := &entity.AdStatistics{ + StatType: "advertiser", + StatDimension: "day", + TargetId: "example_advertiser_id", + TargetName: "示例广告主", + StatDate: startDate, + Impressions: 10000, + Clicks: 500, + Conversions: 50, + Cost: 50000, // 500元,单位分 + CTR: 0.05, // 5% + CVR: 0.1, // 10% + CPM: 5000, // 50元/千次展示 + CPC: 100, // 1元/点击 + } + + err = dao.AdStatistics.Upsert(ctx, advertiserStats) + return +} + +// generateAdvertisementStatistics 生成广告统计数据 +func (s *adStatistics) generateAdvertisementStatistics(ctx context.Context, startDate int64, _ int64) (err error) { + // 这里简化处理,实际项目中应该从日志表或实时数据中聚合 + adStats := &entity.AdStatistics{ + StatType: "advertisement", + StatDimension: "day", + TargetId: "example_ad_id", + TargetName: "示例广告", + StatDate: startDate, + Impressions: 1000, + Clicks: 50, + Conversions: 5, + Cost: 5000, // 50元,单位分 + CTR: 0.05, // 5% + CVR: 0.1, // 10% + CPM: 5000, // 50元/千次展示 + CPC: 100, // 1元/点击 + } + + err = dao.AdStatistics.Upsert(ctx, adStats) + return +} + +// generateAdPositionStatistics 生成广告位统计数据 +func (s *adStatistics) generateAdPositionStatistics(ctx context.Context, startDate int64, _ int64) (err error) { + // 这里简化处理,实际项目中应该从日志表或实时数据中聚合 + positionStats := &entity.AdStatistics{ + StatType: "adPosition", + StatDimension: "day", + TargetId: "example_position_id", + TargetName: "示例广告位", + StatDate: startDate, + Impressions: 5000, + Clicks: 250, + Revenue: 6000, // 60元,单位分 + CTR: 0.05, // 5% + } + + err = dao.AdStatistics.Upsert(ctx, positionStats) + return +} + +// calculateOverview 计算总览数据 +func (s *adStatistics) calculateOverview(stats []*entity.AdStatistics) dto.OverviewData { + var totalImpressions, totalClicks, totalCost, totalRevenue int64 + var totalCTR, totalCVR float64 + var count int + + // 统计不同类型的数据 + advertiserCount := make(map[string]bool) + adCount := make(map[string]bool) + positionCount := make(map[string]bool) + + for _, stat := range stats { + totalImpressions += stat.Impressions + totalClicks += stat.Clicks + totalCost += stat.Cost + totalRevenue += stat.Revenue + totalCTR += stat.CTR + totalCVR += stat.CVR + count++ + + // 统计不同实体的数量 + if stat.StatType == "advertiser" { + advertiserCount[stat.TargetId] = true + } else if stat.StatType == "advertisement" { + adCount[stat.TargetId] = true + } else if stat.StatType == "adPosition" { + positionCount[stat.TargetId] = true + } + } + + // 计算平均值 + averageCTR := 0.0 + averageCVR := 0.0 + if count > 0 { + averageCTR = totalCTR / float64(count) + averageCVR = totalCVR / float64(count) + } + + return dto.OverviewData{ + TotalAdvertisers: int64(len(advertiserCount)), + TotalAds: int64(len(adCount)), + TotalPositions: int64(len(positionCount)), + TotalImpressions: totalImpressions, + TotalClicks: totalClicks, + TotalCost: totalCost, + TotalRevenue: totalRevenue, + AverageCTR: averageCTR, + AverageCVR: averageCVR, + } +} + +// calculateTrends 计算趋势数据 +func (s *adStatistics) calculateTrends(stats []*entity.AdStatistics, dimension string) []dto.TrendData { + trends := make([]dto.TrendData, 0) + + // 按日期分组统计数据 + dateMap := make(map[int64]*dto.TrendData) + + for _, stat := range stats { + if _, exists := dateMap[stat.StatDate]; !exists { + dateMap[stat.StatDate] = &dto.TrendData{ + Date: stat.StatDate, + Impressions: 0, + Clicks: 0, + Cost: 0, + Revenue: 0, + } + } + + trend := dateMap[stat.StatDate] + trend.Impressions += stat.Impressions + trend.Clicks += stat.Clicks + trend.Cost += stat.Cost + trend.Revenue += stat.Revenue + } + + // 转换为切片并排序 + for _, trend := range dateMap { + trends = append(trends, *trend) + } + + // 按日期排序 + sort.Slice(trends, func(i, j int) bool { + return trends[i].Date < trends[j].Date + }) + + return trends +} + +// calculateTopAdvertisers 计算广告主排行 +func (s *adStatistics) calculateTopAdvertisers(stats []*entity.AdStatistics) []dto.RankData { + advertiserMap := make(map[string]*dto.RankData) + + for _, stat := range stats { + if stat.StatType == "advertiser" { + if _, exists := advertiserMap[stat.TargetId]; !exists { + advertiserMap[stat.TargetId] = &dto.RankData{ + Id: stat.TargetId, + Name: stat.TargetName, + Impressions: 0, + Clicks: 0, + Cost: 0, + Revenue: 0, + } + } + + rank := advertiserMap[stat.TargetId] + rank.Impressions += stat.Impressions + rank.Clicks += stat.Clicks + rank.Cost += stat.Cost + rank.Revenue += stat.Revenue + } + } + + return s.sortRankData(advertiserMap) +} + +// calculateTopAds 计算广告排行 +func (s *adStatistics) calculateTopAds(stats []*entity.AdStatistics) []dto.RankData { + adMap := make(map[string]*dto.RankData) + + for _, stat := range stats { + if stat.StatType == "advertisement" { + if _, exists := adMap[stat.TargetId]; !exists { + adMap[stat.TargetId] = &dto.RankData{ + Id: stat.TargetId, + Name: stat.TargetName, + Impressions: 0, + Clicks: 0, + Cost: 0, + Revenue: 0, + } + } + + rank := adMap[stat.TargetId] + rank.Impressions += stat.Impressions + rank.Clicks += stat.Clicks + rank.Cost += stat.Cost + rank.Revenue += stat.Revenue + } + } + + return s.sortRankData(adMap) +} + +// calculateTopPositions 计算广告位排行 +func (s *adStatistics) calculateTopPositions(stats []*entity.AdStatistics) []dto.RankData { + positionMap := make(map[string]*dto.RankData) + + for _, stat := range stats { + if stat.StatType == "adPosition" { + if _, exists := positionMap[stat.TargetId]; !exists { + positionMap[stat.TargetId] = &dto.RankData{ + Id: stat.TargetId, + Name: stat.TargetName, + Impressions: 0, + Clicks: 0, + Cost: 0, + Revenue: 0, + } + } + + rank := positionMap[stat.TargetId] + rank.Impressions += stat.Impressions + rank.Clicks += stat.Clicks + rank.Cost += stat.Cost + rank.Revenue += stat.Revenue + } + } + + return s.sortRankData(positionMap) +} + +// sortRankData 对排行数据进行排序 +func (s *adStatistics) sortRankData(dataMap map[string]*dto.RankData) []dto.RankData { + rankList := make([]dto.RankData, 0, len(dataMap)) + + for _, rank := range dataMap { + rankList = append(rankList, *rank) + } + + // 按收入降序排序 + sort.Slice(rankList, func(i, j int) bool { + return rankList[i].Revenue > rankList[j].Revenue + }) + + // 只返回前10名 + if len(rankList) > 10 { + rankList = rankList[:10] + } + + return rankList +} diff --git a/service/advertisement_service.go b/service/advertisement_service.go new file mode 100644 index 0000000..59cc1b4 --- /dev/null +++ b/service/advertisement_service.go @@ -0,0 +1,201 @@ +package service + +import ( + "cidService/dao" + "cidService/model/dto" + "cidService/model/entity" + "context" + "time" + + "github.com/gogf/gf/v2/util/gconv" +) + +var Advertisement = new(advertisement) + +type advertisement struct{} + +// Add 添加广告 +func (s *advertisement) Add(ctx context.Context, req *dto.AddAdvertisementReq) (res *dto.AddAdvertisementRes, err error) { + advertisement := &entity.Advertisement{} + if err = gconv.Struct(req, advertisement); err != nil { + return + } + + // 设置基础字段 + now := time.Now() + advertisement.CreatedAt = now + advertisement.UpdatedAt = now + advertisement.IsDeleted = false + + // 设置初始状态 + advertisement.Status = "待审核" + advertisement.AuditStatus = "待审核" + + // 初始化统计字段 + advertisement.Impressions = 0 + advertisement.Clicks = 0 + advertisement.Conversions = 0 + advertisement.Cost = 0 + advertisement.CTR = 0 + advertisement.CVR = 0 + advertisement.CPM = 0 + advertisement.CPC = 0 + + if err = dao.Advertisement.Insert(ctx, advertisement); err != nil { + return + } + + res = &dto.AddAdvertisementRes{Id: advertisement.Id.Hex()} + return +} + +// Update 更新广告 +func (s *advertisement) Update(ctx context.Context, req *dto.UpdateAdvertisementReq) (err error) { + // 更新修改时间(不需要设置,DAO层会处理) + return dao.Advertisement.Update(ctx, req) +} + +// UpdateStatus 更新广告状态 +func (s *advertisement) UpdateStatus(ctx context.Context, req *dto.UpdateAdStatusReq) (err error) { + return dao.Advertisement.UpdateStatus(ctx, req.Id, req.Status) +} + +// Audit 审核广告 +func (s *advertisement) Audit(ctx context.Context, req *dto.AuditAdvertisementReq) (err error) { + return dao.Advertisement.Audit(ctx, req.Id, req.AuditStatus, req.AuditReason) +} + +// GetOne 获取广告详情 +func (s *advertisement) GetOne(ctx context.Context, req *dto.GetAdvertisementReq) (res *dto.GetAdvertisementRes, err error) { + advertisement, err := dao.Advertisement.GetOne(ctx, req.Id) + if err != nil { + return + } + + res = &dto.GetAdvertisementRes{ + Advertisement: advertisement, + } + return +} + +// List 获取广告列表 +func (s *advertisement) List(ctx context.Context, req *dto.ListAdvertisementReq) (res *dto.ListAdvertisementRes, err error) { + list, total, err := dao.Advertisement.List(ctx, req) + if err != nil { + return + } + + res = &dto.ListAdvertisementRes{ + List: list, + Total: int(total), + } + return +} + +// GetStatistics 获取广告统计数据 +func (s *advertisement) GetStatistics(ctx context.Context, req *dto.GetAdStatisticsForAdvertisementReq) (res *dto.GetAdStatisticsForAdvertisementRes, err error) { + statReq := &dto.GetAdStatisticsReq{ + StatType: "advertisement", + StatDimension: req.StatType, + TargetId: req.Id, + StartDate: req.StartDate, + EndDate: req.EndDate, + DeviceType: "", + Platform: "", + Region: "", + Gender: "", + AgeGroup: "", + SortBy: "", + SortDirection: "", + } + + list, _, err := dao.AdStatistics.GetStatistics(ctx, statReq) + if err != nil { + return + } + + res = &dto.GetAdStatisticsForAdvertisementRes{ + Statistics: list, + } + // Total字段不存在,已移除 + return +} + +// UpdateAdStatistics 更新广告统计 +func (s *advertisement) UpdateAdStatistics(ctx context.Context, id string, impressions, clicks, conversions int64, cost int64) (err error) { + // 获取广告信息 + ad, err := dao.Advertisement.GetOne(ctx, id) + if err != nil { + return + } + + // 计算统计数据 + totalImpressions := ad.Impressions + impressions + totalClicks := ad.Clicks + clicks + totalConversions := ad.Conversions + conversions + totalCost := ad.Cost + cost + + // 计算比率 + ctr := 0.0 + if totalImpressions > 0 { + ctr = float64(totalClicks) / float64(totalImpressions) + } + + cvr := 0.0 + if totalClicks > 0 { + cvr = float64(totalConversions) / float64(totalClicks) + } + + cpm := int64(0) + if totalImpressions > 0 { + cpm = totalCost * 1000 / totalImpressions + } + + cpc := int64(0) + if totalClicks > 0 { + cpc = totalCost / totalClicks + } + + // 构建更新数据 + stats := map[string]interface{}{ + "impressions": totalImpressions, + "clicks": totalClicks, + "conversions": totalConversions, + "cost": totalCost, + "ctr": ctr, + "cvr": cvr, + "cpm": cpm, + "cpc": cpc, + "updatedAt": time.Now(), + } + + // 更新广告统计 + err = dao.Advertisement.UpdateStatistics(ctx, id, stats) + if err != nil { + return + } + + // 插入统计记录 + today := time.Now() + todayStart := time.Date(today.Year(), today.Month(), today.Day(), 0, 0, 0, 0, today.Location()).Unix() + + statistics := &entity.AdStatistics{ + StatType: "advertisement", + StatDimension: "day", + TargetId: id, + TargetName: ad.Title, + StatDate: todayStart, + Impressions: impressions, + Clicks: clicks, + Conversions: conversions, + Cost: cost, + CTR: ctr, + CVR: cvr, + CPM: cpm, + CPC: cpc, + // CreatedAt、UpdatedAt、IsDeleted字段是嵌入字段,无需单独设置 + } + + err = dao.AdStatistics.Upsert(ctx, statistics) + return +} diff --git a/service/advertiser_service.go b/service/advertiser_service.go new file mode 100644 index 0000000..589da7b --- /dev/null +++ b/service/advertiser_service.go @@ -0,0 +1,185 @@ +package service + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/errors/gerror" + "github.com/gogf/gf/v2/util/gconv" + + "cidService/dao" + "cidService/model/dto" + "cidService/model/entity" +) + +var Advertiser = new(advertiser) + +type advertiser struct{} + +// Add 添加广告主 +func (s *advertiser) Add(ctx context.Context, req *dto.AddAdvertiserReq) (res *dto.AddAdvertiserRes, err error) { + advertiser := &entity.Advertiser{} + if err = gconv.Struct(req, advertiser); err != nil { + return + } + + // 设置基础字段 + now := time.Now() + advertiser.CreatedAt = now + advertiser.UpdatedAt = now + advertiser.IsDeleted = false + + // 设置初始状态 + advertiser.Status = "待审核" + advertiser.AuditStatus = "待审核" + + if err = dao.Advertiser.Insert(ctx, advertiser); err != nil { + return + } + + res = &dto.AddAdvertiserRes{Id: advertiser.Id.Hex()} + return +} + +// Update 更新广告主 +func (s *advertiser) Update(ctx context.Context, req *dto.UpdateAdvertiserReq) (err error) { + // 更新修改时间(不需要设置,DAO层会处理) + return dao.Advertiser.Update(ctx, req) +} + +// UpdateStatus 更新广告主状态 +func (s *advertiser) UpdateStatus(ctx context.Context, req *dto.UpdateAdvertiserStatusReq) (err error) { + return dao.Advertiser.UpdateStatus(ctx, req.Id, req.Status) +} + +// Audit 审核广告主 +func (s *advertiser) Audit(ctx context.Context, req *dto.AuditAdvertiserReq) (err error) { + return dao.Advertiser.Audit(ctx, req.Id, req.AuditStatus, req.AuditReason) +} + +// Recharge 充值 +func (s *advertiser) Recharge(ctx context.Context, req *dto.RechargeAdvertiserReq) (err error) { + // 验证金额 + if req.Amount <= 0 { + return gerror.New("充值金额必须大于0") + } + + // 执行充值 + err = dao.Advertiser.Recharge(ctx, req.Id, req.Amount, req.Remark) + if err != nil { + return + } + + // 记录充值流水(实际项目中可能需要创建充值记录表) + // 这里简化处理 + + return +} + +// UpdateCreditLimit 更新授信额度 +func (s *advertiser) UpdateCreditLimit(ctx context.Context, req *dto.UpdateCreditLimitReq) (err error) { + // 验证授信额度 + if req.CreditLimit < 0 { + return gerror.New("授信额度不能为负数") + } + + // 更新授信额度 + err = dao.Advertiser.UpdateCreditLimit(ctx, req.Id, req.CreditLimit) + return +} + +// GetOne 获取广告主详情 +func (s *advertiser) GetOne(ctx context.Context, req *dto.GetAdvertiserReq) (res *dto.GetAdvertiserRes, err error) { + advertiser, err := dao.Advertiser.GetOne(ctx, req.Id) + if err != nil { + return + } + + res = &dto.GetAdvertiserRes{ + Advertiser: advertiser, + } + return +} + +// List 获取广告主列表 +func (s *advertiser) List(ctx context.Context, req *dto.ListAdvertiserReq) (res *dto.ListAdvertiserRes, err error) { + list, total, err := dao.Advertiser.List(ctx, req) + if err != nil { + return + } + + res = &dto.ListAdvertiserRes{ + List: list, + Total: int(total), + } + return +} + +// GetBalance 获取广告主余额 +func (s *advertiser) GetBalance(ctx context.Context, id string) (balance, creditLimit int64, err error) { + advertiser, err := dao.Advertiser.GetOne(ctx, id) + if err != nil { + return + } + + balance = advertiser.AccountBalance + creditLimit = advertiser.CreditLimit + return +} + +// CheckBudget 检查广告主预算 +func (s *advertiser) CheckBudget(ctx context.Context, id string, cost int64) (hasEnoughBudget bool, err error) { + advertiser, err := dao.Advertiser.GetOne(ctx, id) + if err != nil { + return + } + + // 可用金额 = 账户余额 + 授信额度 - 已消耗 + availableAmount := advertiser.AccountBalance + advertiser.CreditLimit + + // 检查预算是否充足 + hasEnoughBudget = availableAmount >= cost + + return +} + +// DeductBudget 扣除预算 +func (s *advertiser) DeductBudget(ctx context.Context, id string, cost int64) (err error) { + // 检查预算是否充足 + hasEnoughBudget, err := s.CheckBudget(ctx, id, cost) + if err != nil { + return + } + + if !hasEnoughBudget { + return gerror.New("预算不足") + } + + // 获取广告主信息 + advertiser, err := dao.Advertiser.GetOne(ctx, id) + if err != nil { + return + } + + // 计算新余额 + newBalance := advertiser.AccountBalance - cost + + // 如果账户余额为负,从授信额度中扣除 + if newBalance < 0 { + newCreditLimit := advertiser.CreditLimit + newBalance + newBalance = 0 + + // 更新授信额度 + err = dao.Advertiser.UpdateCreditLimit(ctx, id, newCreditLimit) + if err != nil { + return + } + } + + // 更新账户余额 + err = dao.Advertiser.Update(ctx, &dto.UpdateAdvertiserReq{ + Id: id, + AccountBalance: &newBalance, + }) + return +} diff --git a/service/data_service.go b/service/data_service.go deleted file mode 100644 index c3d5742..0000000 --- a/service/data_service.go +++ /dev/null @@ -1,53 +0,0 @@ -package service - -import ( - "cidService/dao" - "cidService/model/dto" - "cidService/model/entity" - "context" - "time" - - "github.com/gogf/gf/v2/util/gconv" -) - -var Data = new(data) - -type data struct{} - -// Add 添加数据 -func (s *data) Add(ctx context.Context, req *dto.AddDataReq) (res *dto.AddDataRes, err error) { - data := &entity.Data{} - if err = gconv.Struct(req, data); err != nil { - return - } - // 设置基础字段 - now := time.Now() - data.CreatedAt = now - data.UpdatedAt = now - data.IsDeleted = false - // 注意:Creator、Updater、TenantId 保持零值,不设置 - - if err = dao.Data.Insert(ctx, data); err != nil { - return - } - res = &dto.AddDataRes{Id: data.Id.Hex()} - return -} - -// Update 更新数据 -func (s *data) Update(ctx context.Context, req *dto.UpdateDataReq) (err error) { - return dao.Data.Update(ctx, req) -} - -// List 获取数据列表 -func (s *data) List(ctx context.Context, req *dto.ListDataReq) (res *dto.ListDataRes, err error) { - list, total, err := dao.Data.List(ctx, req) - if err != nil { - return - } - res = &dto.ListDataRes{ - List: list, - Total: int(total), - } - return -} diff --git a/service/report_service.go b/service/report_service.go new file mode 100644 index 0000000..674f2d8 --- /dev/null +++ b/service/report_service.go @@ -0,0 +1,183 @@ +package service + +import ( + "context" + "time" + + "github.com/gogf/gf/v2/errors/gerror" + + "cidService/dao" + "cidService/model/dto" + "cidService/model/entity" +) + +// Report Service 单例 +var Report = new(report) + +type report struct{} + +// Create 创建报表 +func (s *report) Create(ctx context.Context, req *dto.CreateReportReq) (res *dto.CreateReportRes, err error) { + data := &entity.AdReport{ + ReportName: req.ReportName, + ReportType: req.ReportType, + ReportPeriod: req.ReportPeriod, + StartDate: req.StartDate, + EndDate: req.EndDate, + Status: "生成中", + GenerateTime: time.Now().Unix(), + FileFormat: req.FileFormat, + EmailRecipients: req.EmailRecipients, + Schedule: req.Schedule, + } + + // 存储报表配置 + if req.ReportConfig != nil { + data.ReportData = []entity.ReportItem{ + { + Dimension: "config", + Data: req.ReportConfig, + }, + } + } + + if err = dao.Report.Insert(ctx, data); err != nil { + return nil, err + } + + // 异步生成报表 + go s.generateReport(data.Id.Hex(), req) + + res = &dto.CreateReportRes{Id: data.Id.Hex()} + return +} + +// generateReport 生成报表 +func (s *report) generateReport(reportId string, req *dto.CreateReportReq) { + // 模拟生成报表 + time.Sleep(5 * time.Second) + + // 更新报表状态 + ctx := context.Background() + + updateReq := &dto.UpdateReportReq{ + Id: reportId, + FileFormat: req.FileFormat, + EmailRecipients: req.EmailRecipients, + } + + err := dao.Report.Update(ctx, updateReq) + if err != nil { + // 记录错误日志,这里简化处理 + // 实际项目中应该使用日志框架 + } + + // 发送邮件通知(如果配置了邮件接收人) + if len(req.EmailRecipients) > 0 { + // 发送邮件 + // 这里简化处理,实际项目中应该使用邮件服务 + } +} + +// GetOne 获取报表详情 +func (s *report) GetOne(ctx context.Context, req *dto.GetReportReq) (res *dto.GetReportRes, err error) { + data, err := dao.Report.GetOne(ctx, req.Id) + if err != nil { + return nil, err + } + + res = &dto.GetReportRes{ + AdReport: data, + } + return +} + +// List 获取报表列表 +func (s *report) List(ctx context.Context, req *dto.ListReportReq) (res *dto.ListReportRes, err error) { + list, total, err := dao.Report.List(ctx, req) + if err != nil { + return nil, err + } + + res = &dto.ListReportRes{ + List: list, + Total: int(total), + } + return +} + +// Update 更新报表 +func (s *report) Update(ctx context.Context, req *dto.UpdateReportReq) (err error) { + return dao.Report.Update(ctx, req) +} + +// Delete 删除报表 +func (s *report) Delete(ctx context.Context, req *dto.DeleteReportReq) (err error) { + return dao.Report.Delete(ctx, req.Id) +} + +// Download 下载报表 +func (s *report) Download(ctx context.Context, req *dto.DownloadReportReq) (res *dto.DownloadReportRes, err error) { + data, err := dao.Report.GetOne(ctx, req.Id) + if err != nil { + return nil, err + } + + // 检查报表状态 + if data.Status != "已完成" { + return nil, gerror.New("报表尚未生成完成") + } + + // 检查报表是否过期 + if data.ExpiredTime > 0 && data.ExpiredTime < time.Now().Unix() { + return nil, gerror.New("报表已过期") + } + + res = &dto.DownloadReportRes{ + DownloadUrl: data.DownloadUrl, + FileSize: data.FileSize, + FileFormat: data.FileFormat, + } + return +} + +// Generate 生成报表 +func (s *report) Generate(ctx context.Context, req *dto.GenerateReportReq) (err error) { + data, err := dao.Report.GetOne(ctx, req.Id) + if err != nil { + return err + } + + // 构建创建报表请求 + createReq := &dto.CreateReportReq{ + ReportName: data.ReportName, + ReportType: data.ReportType, + ReportPeriod: data.ReportPeriod, + StartDate: data.StartDate, + EndDate: data.EndDate, + FileFormat: data.FileFormat, + EmailRecipients: data.EmailRecipients, + Schedule: data.Schedule, + } + + // 从ReportData中获取报表配置 + if len(data.ReportData) > 0 { + for _, item := range data.ReportData { + if item.Dimension == "config" { + createReq.ReportConfig = item.Data + break + } + } + } + + // 异步生成报表 + go s.generateReport(req.Id, createReq) + + // 更新状态 + updateReq := &dto.UpdateReportReq{ + Id: req.Id, + } + + err = dao.Report.Update(ctx, updateReq) + return +}