Files
rootandClaude Opus 5 01d9f7bc86 新增广告V2模块,双端下载落地页调整
- 新增 advitise v2: info/location/bind/click-stat/day-click-stat 模型与
  对应的 service、api、router、proto 定义
- models/index.go 注册 5 个新模型的索引初始化
- 鉴权白名单: /rsdownload/ 改为 /download/,放行 advitise v2 的
  get/click 接口
- statg/config: proxyUrl 增加 fileRes1
- common/genId: lid 生成逻辑补充
- 新增 91fans / huangguo 的 mobileconfig 描述文件
- 新增 deploy/nginx/cpcdownload.mfcg.me.conf 网关配置

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-22 16:53:23 +08:00

286 lines
8.2 KiB
Go

package advitiseV2Ser
import (
"context"
"stat/api/proto"
"stat/models/advitiseClickStatMod"
"stat/models/advitiseDayClickStatMod"
"stat/models/advitiseInfoMod"
"stat/models/advitiseLocationMod"
"stat/models/appInfoMod"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
const statDateLayout = "2006-01-02"
func parseDateRange(startS, endS string) (start, end time.Time, ok bool) {
// 允许两种格式:yyyy-MM-dd 和 yyyy-MM-dd HH:mm:ss
tryLayouts := []string{"2006-01-02 15:04:05", "2006-01-02"}
if startS != "" {
for _, l := range tryLayouts {
if t, err := time.ParseInLocation(l, startS, time.Local); err == nil {
start = t.Truncate(24 * time.Hour)
ok = true
break
}
}
}
if endS != "" {
for _, l := range tryLayouts {
if t, err := time.ParseInLocation(l, endS, time.Local); err == nil {
end = t.Truncate(24 * time.Hour)
ok = true
break
}
}
}
return
}
// DayClickList 广告每日点击汇总列表(advitise_day_click_stat)
func DayClickList(ctx context.Context, req *proto.AdvitiseDayClickListReq) (*proto.AdvitiseDayClickListResp, error) {
resp := &proto.AdvitiseDayClickListResp{List: make([]*proto.AdvitiseDayClickItem, 0)}
filter := bson.M{}
if req.Id != nil {
filter[advitiseDayClickStatMod.FN.AdvitiseDayClickStat.AdvitiseId] = *req.Id
}
start, end, _ := parseDateRange(req.StatStartDate, req.StatEndDate)
dateFilter := bson.M{}
if !start.IsZero() {
dateFilter["$gte"] = start
}
if !end.IsZero() {
dateFilter["$lte"] = end
}
if len(dateFilter) > 0 {
filter[advitiseDayClickStatMod.FN.AdvitiseDayClickStat.StatDate] = dateFilter
}
total, err := advitiseDayClickStatMod.Count(ctx, filter)
if err != nil {
return nil, err
}
opt := &options.FindOptions{}
opt.SetSort(bson.D{{Key: advitiseDayClickStatMod.FN.AdvitiseDayClickStat.StatDate, Value: -1}})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
list, err := advitiseDayClickStatMod.FindMany(ctx, filter, opt)
if err != nil {
return nil, err
}
// 批量拉广告标题
advIds := make([]int64, 0, len(list))
for _, r := range list {
advIds = append(advIds, r.AdvitiseId)
}
nameMap := loadInfoNames(ctx, advIds)
for _, r := range list {
resp.List = append(resp.List, &proto.AdvitiseDayClickItem{
AdsId: r.AdvitiseId,
AdsName: nameMap[r.AdvitiseId],
Clicks: r.Clicks,
StatDate: r.StatDate.Format(statDateLayout),
})
}
resp.Total = total
return resp, nil
}
// ClickList 广告细维度点击列表(advitise_click_stat)。既用于「每日点击详情」也用于「广告位查看统计」入口
func ClickList(ctx context.Context, req *proto.AdvitiseClickListReq) (*proto.AdvitiseClickListResp, error) {
resp := &proto.AdvitiseClickListResp{List: make([]*proto.AdvitiseClickItem, 0)}
filter := bson.M{}
if req.AdsId != nil {
filter[advitiseClickStatMod.FN.AdvitiseClickStat.AdvitiseId] = *req.AdsId
}
if req.AppId != nil {
filter[advitiseClickStatMod.FN.AdvitiseClickStat.AppId] = *req.AppId
}
if req.LocationId != nil {
filter[advitiseClickStatMod.FN.AdvitiseClickStat.LocationId] = *req.LocationId
}
start, end, _ := parseDateRange(req.StatStartDate, req.StatEndDate)
dateFilter := bson.M{}
if !start.IsZero() {
dateFilter["$gte"] = start
}
if !end.IsZero() {
dateFilter["$lte"] = end
}
if len(dateFilter) > 0 {
filter[advitiseClickStatMod.FN.AdvitiseClickStat.StatDate] = dateFilter
}
total, err := advitiseClickStatMod.Count(ctx, filter)
if err != nil {
return nil, err
}
opt := &options.FindOptions{}
opt.SetSort(bson.D{{Key: advitiseClickStatMod.FN.AdvitiseClickStat.StatDate, Value: -1}})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
list, err := advitiseClickStatMod.FindMany(ctx, filter, opt)
if err != nil {
return nil, err
}
// 批量映射名字
advIds := make([]int64, 0)
appIds := make(map[int32]bool)
locIds := make(map[int32]bool)
for _, r := range list {
advIds = append(advIds, r.AdvitiseId)
appIds[r.AppId] = true
locIds[r.LocationId] = true
}
advNameMap := loadInfoNames(ctx, advIds)
appNameMap, _ := appInfoMod.GetAllAppIdNameMap(ctx)
// 位置名字 — 用 (appId, locId) 组合查
type locKey struct {
appId int32
locId int32
}
locNameMap := make(map[locKey]string)
if len(locIds) > 0 {
locIdsSlice := make([]int32, 0, len(locIds))
for id := range locIds {
locIdsSlice = append(locIdsSlice, id)
}
appIdsSlice := make([]int32, 0, len(appIds))
for id := range appIds {
appIdsSlice = append(appIdsSlice, id)
}
locs, lerr := advitiseLocationMod.FindManyAdvitiseLocations(ctx, bson.M{
advitiseLocationMod.FN.AdvitiseLocation.AppId: bson.M{"$in": appIdsSlice},
advitiseLocationMod.FN.AdvitiseLocation.LocId: bson.M{"$in": locIdsSlice},
})
if lerr == nil {
for _, l := range locs {
locNameMap[locKey{appId: l.AppId, locId: l.LocId}] = l.Name
}
}
}
for _, r := range list {
resp.List = append(resp.List, &proto.AdvitiseClickItem{
AppId: r.AppId,
AppName: appNameMap[r.AppId],
AdsId: r.AdvitiseId,
AdsName: advNameMap[r.AdvitiseId],
LocationId: r.LocationId,
LocationName: locNameMap[locKey{appId: r.AppId, locId: r.LocationId}],
Clicks: r.Clicks,
StatDate: r.StatDate.Format(statDateLayout),
})
}
resp.Total = total
return resp, nil
}
// ClickSummary 按时间范围汇总广告点击量,分页按 clicks desc
// aggregate pipeline: $match → $group by advitiseId → $sort clicks desc → $facet(list, total, grandTotal)
func ClickSummary(ctx context.Context, req *proto.AdvitiseClickSummaryReq) (*proto.AdvitiseClickSummaryResp, error) {
resp := &proto.AdvitiseClickSummaryResp{List: make([]*proto.AdvitiseClickSummaryItem, 0)}
match := bson.M{}
if req.Id != nil {
match[advitiseClickStatMod.FN.AdvitiseClickStat.AdvitiseId] = *req.Id
}
if req.AppId != nil {
match[advitiseClickStatMod.FN.AdvitiseClickStat.AppId] = *req.AppId
}
if req.LocationId != nil {
match[advitiseClickStatMod.FN.AdvitiseClickStat.LocationId] = *req.LocationId
}
start, end, _ := parseDateRange(req.StatStartDate, req.StatEndDate)
dateFilter := bson.M{}
if !start.IsZero() {
dateFilter["$gte"] = start
}
if !end.IsZero() {
dateFilter["$lte"] = end
}
if len(dateFilter) > 0 {
match[advitiseClickStatMod.FN.AdvitiseClickStat.StatDate] = dateFilter
}
pipeline := []bson.M{
{"$match": match},
{"$group": bson.M{
"_id": "$" + advitiseClickStatMod.FN.AdvitiseClickStat.AdvitiseId,
"totalClicks": bson.M{"$sum": "$" + advitiseClickStatMod.FN.AdvitiseClickStat.Clicks},
}},
{"$facet": bson.M{
"list": []bson.M{
{"$sort": bson.M{"totalClicks": -1}},
{"$skip": (req.PageNum - 1) * req.PageSize},
{"$limit": req.PageSize},
},
"total": []bson.M{{"$count": "count"}},
"grandTotal": []bson.M{{"$group": bson.M{"_id": nil, "sum": bson.M{"$sum": "$totalClicks"}}}},
}},
}
type facetRow struct {
List []struct {
Id int64 `bson:"_id"`
TotalClicks int64 `bson:"totalClicks"`
} `bson:"list"`
Total []struct {
Count int64 `bson:"count"`
} `bson:"total"`
GrandTotal []struct {
Sum int64 `bson:"sum"`
} `bson:"grandTotal"`
}
rows := make([]facetRow, 0, 1)
if err := advitiseClickStatMod.Aggregate(ctx, &rows, pipeline); err != nil {
return nil, err
}
var facet facetRow
if len(rows) > 0 {
facet = rows[0]
}
if len(facet.Total) > 0 {
resp.Total = facet.Total[0].Count
}
if len(facet.GrandTotal) > 0 {
resp.GrandTotal = facet.GrandTotal[0].Sum
}
advIds := make([]int64, 0, len(facet.List))
for _, r := range facet.List {
advIds = append(advIds, r.Id)
}
nameMap := loadInfoNames(ctx, advIds)
for _, r := range facet.List {
resp.List = append(resp.List, &proto.AdvitiseClickSummaryItem{
AdsId: r.Id,
AdsName: nameMap[r.Id],
TotalClicks: r.TotalClicks,
})
}
return resp, nil
}
func loadInfoNames(ctx context.Context, advIds []int64) map[int64]string {
m := make(map[int64]string)
if len(advIds) == 0 {
return m
}
infos, err := advitiseInfoMod.FindManyAdvitiseInfos(ctx, bson.M{
advitiseInfoMod.FN.AdvitiseInfo.Id: bson.M{"$in": advIds},
})
if err != nil {
return m
}
for _, info := range infos {
m[info.Id] = info.Title
}
return m
}