新增广告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>
This commit is contained in:
2026-09-22 16:53:23 +08:00
co-authored by Claude Opus 5
parent bc605f303d
commit 01d9f7bc86
40 changed files with 2561 additions and 30 deletions
+19 -11
View File
@@ -280,19 +280,27 @@ func GetAdvertise(ctx context.Context, param proto.AdvertiseReq) (code stdLog.Co
}
// 增加广告点击次数
// 兼容策略:先按 param.Id 精确查;如果广告已过期/删除或不属于该 AppId,回退到 (AppId, LocId) 上的最新一条广告
// LocId 未传时,回退查询只按 AppId 定位;仍找不到才返回 AdvertiseInvalid
func IncAdsClickCount(ctx context.Context, param proto.AdsClickReq) (code stdLog.CodeType, data interface{}, err error) {
// 获取广告信息
adsInfo, err := advertiseMod.FindAdsById(ctx, *param.Id)
if err != nil || adsInfo.Id == 0 {
code = stdLog.AdvertiseInvalid
return
// 先按 Id 查
adsInfo, findErr := advertiseMod.FindAdsById(ctx, *param.Id)
needFallback := findErr != nil || adsInfo.Id == 0 || adsInfo.AppId != *param.AppId
if needFallback {
var locId int32
if param.LocId != nil {
locId = *param.LocId
}
latest, latestErr := advertiseMod.FindLatestAdsByAppAndLoc(ctx, *param.AppId, locId)
if latestErr != nil || latest.Id == 0 {
code = stdLog.AdvertiseInvalid
err = latestErr
return
}
adsInfo = latest
}
if adsInfo.AppId != *param.AppId {
code = stdLog.AppInfoError
return
}
// save to redis
// save to redis(用真正命中的广告 Id 统计)
now := time.Now().Local()
set := redis.GetAdvertiseClickDayHash(now)
name := redis.GetAdvertiseClickHourName(adsInfo.AppId, adsInfo.Id, now.Hour())
@@ -301,7 +309,7 @@ func IncAdsClickCount(ctx context.Context, param proto.AdsClickReq) (code stdLog
return
}
// update click+1 to db
if err = advertiseMod.IncAdsClickCount(ctx, *param.Id); err != nil {
if err = advertiseMod.IncAdsClickCount(ctx, adsInfo.Id); err != nil {
code = stdLog.Failure
return
}
+92
View File
@@ -0,0 +1,92 @@
package advitiseV2Ser
import (
"context"
"stat/api/proto"
"stat/common/genId"
"stat/models/advitiseBindMod"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
func BindList(ctx context.Context, req *proto.AdvitiseBindListReq) (*proto.AdvitiseBindListResp, error) {
resp := &proto.AdvitiseBindListResp{
Total: 0,
List: make(advitiseBindMod.AdvitiseBindList, 0),
}
filter := bson.M{}
if req.AppId != nil {
filter[advitiseBindMod.FN.AdvitiseBind.AppId] = *req.AppId
}
if req.AdvitiseId != nil {
filter[advitiseBindMod.FN.AdvitiseBind.AdvitiseId] = *req.AdvitiseId
}
if req.LocationId != nil {
filter[advitiseBindMod.FN.AdvitiseBind.LocationId] = *req.LocationId
}
total, err := advitiseBindMod.CountAdvitiseBinds(ctx, filter)
if err != nil {
return nil, err
}
opt := &options.FindOptions{}
opt.SetSort(bson.D{
{Key: advitiseBindMod.FN.AdvitiseBind.Sort, Value: -1},
{Key: advitiseBindMod.FN.AdvitiseBind.CreatedAt, Value: -1},
})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
list, err := advitiseBindMod.FindManyAdvitiseBinds(ctx, filter, opt)
if err != nil {
return nil, err
}
resp.Total = total
resp.List = list
return resp, nil
}
func BindAdd(ctx context.Context, req *proto.AddAdvitiseBindReq) error {
now := time.Now()
locs := req.LocationId
if locs == nil {
locs = []int32{}
}
doc := &advitiseBindMod.AdvitiseBind{
Id: genId.GetAdvitiseBindId(),
AppId: req.AppId,
AdvitiseId: req.AdvitiseId,
LocationId: locs,
Sort: req.Sort,
CreatedAt: now,
UpdatedAt: now,
}
return advitiseBindMod.InsertOneAdvitiseBind(ctx, doc)
}
func BindUpdate(ctx context.Context, req *proto.UpdateAdvitiseBindReq) error {
set := bson.M{
advitiseBindMod.FN.AdvitiseBind.UpdatedAt: time.Now(),
}
if req.AppId != nil {
set[advitiseBindMod.FN.AdvitiseBind.AppId] = *req.AppId
}
if req.AdvitiseId != nil {
set[advitiseBindMod.FN.AdvitiseBind.AdvitiseId] = *req.AdvitiseId
}
if req.LocationId != nil {
set[advitiseBindMod.FN.AdvitiseBind.LocationId] = *req.LocationId
}
if req.Sort != nil {
set[advitiseBindMod.FN.AdvitiseBind.Sort] = *req.Sort
}
return advitiseBindMod.UpdateOneAdvitiseBind(ctx,
bson.M{advitiseBindMod.FN.AdvitiseBind.Id: req.Id},
bson.M{"$set": set},
)
}
func BindDel(ctx context.Context, id int64) error {
_, err := advitiseBindMod.DelOneAdvitiseBind(ctx, bson.M{advitiseBindMod.FN.AdvitiseBind.Id: id})
return err
}
+214
View File
@@ -0,0 +1,214 @@
package advitiseV2Ser
import (
"context"
"errors"
"stat/api/proto"
"stat/common/log"
"stat/models/advitiseBindMod"
"stat/models/advitiseClickStatMod"
"stat/models/advitiseDayClickStatMod"
"stat/models/advitiseInfoMod"
"stat/models/advitiseLocationMod"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
// idScale 把 (bindId, locId) 编码进单个 int64 id 的乘数
// 约束:locId < idScale。int64 上限允许 bindId 最大约 9.2e13,远超实际 LID 生成量
const idScale = int64(100000)
// GetAdvitises 外部拉广告:响应结构与老 GetAdvertise (proto.AdvertiseRes) 一致
// - 每条广告绑定一个 (bindId, locId) 组合
// - CoverImg 依据 location.AdvitiseSize 选 info 里对应尺寸组的第一张
// - 返回的 id = bindId*idScale + locId,client 点击时原样回传,后端解码定位 (bind, locId)
// - BindId 也单独返回,给愿意用二维接口的 client 用
func GetAdvitises(ctx context.Context, req *proto.GetAdvitiseV2Req) (*proto.AdvertiseRes, error) {
resp := &proto.AdvertiseRes{AdvertiseList: make([]*proto.AdvertiseInfo, 0)}
// 1) 拉 bind
filter := bson.M{advitiseBindMod.FN.AdvitiseBind.AppId: req.AppId}
if req.LocationId != nil {
filter[advitiseBindMod.FN.AdvitiseBind.LocationId] = *req.LocationId
}
opt := &options.FindOptions{}
opt.SetSort(bson.D{
{Key: advitiseBindMod.FN.AdvitiseBind.Sort, Value: -1},
{Key: advitiseBindMod.FN.AdvitiseBind.CreatedAt, Value: -1},
})
binds, err := advitiseBindMod.FindManyAdvitiseBinds(ctx, filter, opt)
if err != nil {
return nil, err
}
if len(binds) == 0 {
return resp, nil
}
// 2) 批量拉当前生效 info
advitiseIds := uniqInt64(func() []int64 {
out := make([]int64, 0, len(binds))
for _, b := range binds {
out = append(out, b.AdvitiseId)
}
return out
}())
now := time.Now()
infos, err := advitiseInfoMod.FindManyAdvitiseInfos(ctx, bson.M{
advitiseInfoMod.FN.AdvitiseInfo.Id: bson.M{"$in": advitiseIds},
advitiseInfoMod.FN.AdvitiseInfo.Status: true,
advitiseInfoMod.FN.AdvitiseInfo.StartTime: bson.M{"$lte": now},
advitiseInfoMod.FN.AdvitiseInfo.EndTime: bson.M{"$gte": now},
})
if err != nil {
return nil, err
}
infoMap := make(map[int64]*advitiseInfoMod.AdvitiseInfo, len(infos))
for _, info := range infos {
infoMap[info.Id] = info
}
// 3) 批量拉 location(用于 CoverImg 尺寸匹配)
locIdsAll := uniqInt32(func() []int32 {
out := make([]int32, 0)
for _, b := range binds {
out = append(out, b.LocationId...)
}
return out
}())
locMap := make(map[int32]*advitiseLocationMod.AdvitiseLocation)
if len(locIdsAll) > 0 {
locs, lerr := advitiseLocationMod.FindManyAdvitiseLocations(ctx, bson.M{
advitiseLocationMod.FN.AdvitiseLocation.AppId: req.AppId,
advitiseLocationMod.FN.AdvitiseLocation.LocId: bson.M{"$in": locIdsAll},
})
if lerr == nil {
for _, l := range locs {
locMap[l.LocId] = l
}
}
}
// 4) 展开 bind × location 为 item
for _, b := range binds {
info, ok := infoMap[b.AdvitiseId]
if !ok {
continue
}
for _, locId := range b.LocationId {
if req.LocationId != nil && locId != *req.LocationId {
continue
}
var size advitiseLocationMod.AdvitiseSizeType
if loc, ok := locMap[locId]; ok && loc != nil {
size = loc.AdvitiseSize
}
resp.AdvertiseList = append(resp.AdvertiseList, &proto.AdvertiseInfo{
Id: b.Id*idScale + int64(locId),
Title: info.Title,
CoverImg: pickCoverImg(info, size),
LocId: locId,
JumpType: 0, // V2 素材未维护跳转类型,默认外部浏览器
Link: info.Link,
Sort: b.Sort,
Type: 0,
Tags: "",
Remark: info.Remark,
AmbientColor: "",
BindId: b.Id,
})
}
}
return resp, nil
}
// pickCoverImg 按位置尺寸选一张封面;取不到就用任一非空数组的第一张兜底
func pickCoverImg(info *advitiseInfoMod.AdvitiseInfo, size advitiseLocationMod.AdvitiseSizeType) string {
var arr []string
switch size {
case advitiseLocationMod.AdvitiseSizeFullScreen:
arr = info.FullScreenImgs
case advitiseLocationMod.AdvitiseSizeBanner720300:
arr = info.Banner720300Imgs
case advitiseLocationMod.AdvitiseSizeBanner720150:
arr = info.Banner720150Imgs
case advitiseLocationMod.AdvitiseSizeBanner750700:
arr = info.Banner750700Imgs
case advitiseLocationMod.AdvitiseSizeBanner800450:
arr = info.Banner800450Imgs
case advitiseLocationMod.AdvitiseSizePopUps600800:
arr = info.PopUps600800Imgs
case advitiseLocationMod.AdvitiseSizePopUps512512:
arr = info.PopUps512512Imgs
case advitiseLocationMod.AdvitiseSizeSmallCube:
arr = info.SmallCubeImgs
case advitiseLocationMod.AdvitiseSizeVerticalCube:
arr = info.VerticalCubeImgs
}
if len(arr) > 0 {
return arr[0]
}
// 兜底:任一非空数组的第一张
for _, cand := range [][]string{
info.FullScreenImgs, info.Banner720300Imgs, info.Banner720150Imgs,
info.Banner750700Imgs, info.Banner800450Imgs,
info.PopUps600800Imgs, info.PopUps512512Imgs,
info.SmallCubeImgs, info.VerticalCubeImgs,
} {
if len(cand) > 0 {
return cand[0]
}
}
return ""
}
func uniqInt64(in []int64) []int64 {
seen := make(map[int64]struct{}, len(in))
out := make([]int64, 0, len(in))
for _, v := range in {
if _, ok := seen[v]; ok {
continue
}
seen[v] = struct{}{}
out = append(out, v)
}
return out
}
func uniqInt32(in []int32) []int32 {
seen := make(map[int32]struct{}, len(in))
out := make([]int32, 0, len(in))
for _, v := range in {
if _, ok := seen[v]; ok {
continue
}
seen[v] = struct{}{}
out = append(out, v)
}
return out
}
// ClickAdvitise 点击回传:media 逻辑
// 1. advitise_click_stat upsert +1 (appId, advitiseId, locationId, day)
// 2. advitise_day_click_stat upsert +1 (advitiseId, day)
func ClickAdvitise(ctx context.Context, req *proto.ClickAdvitiseV2Req) error {
bindId := req.Id / idScale
location := int32(req.Id % idScale)
bind, err := advitiseBindMod.FindOneAdvitiseBind(ctx, bson.M{advitiseBindMod.FN.AdvitiseBind.Id: bindId})
if err != nil || bind == nil {
return errors.New("bind not found")
}
day := time.Now().Truncate(24 * time.Hour)
if err := advitiseClickStatMod.InsertOrUpdate(ctx, bind.AppId, bind.AdvitiseId, location, day); err != nil {
log.Warn("v2 click: fine stat upsert failed", log.E(err))
return err
}
if err := advitiseDayClickStatMod.InsertOrUpdate(ctx, bind.AdvitiseId, day); err != nil {
// 汇总表失败不阻断细维度已成功的记录
log.Warn("v2 click: day rollup upsert failed", log.E(err))
}
return nil
}
+144
View File
@@ -0,0 +1,144 @@
package advitiseV2Ser
import (
"context"
"stat/api/proto"
"stat/common/genId"
"stat/models/advitiseInfoMod"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
func InfoList(ctx context.Context, req *proto.AdvitiseInfoListReq) (*proto.AdvitiseInfoListResp, error) {
resp := &proto.AdvitiseInfoListResp{
Total: 0,
List: make(advitiseInfoMod.AdvitiseInfoList, 0),
}
filter := bson.M{}
if req.Title != "" {
filter[advitiseInfoMod.FN.AdvitiseInfo.Title] = bson.M{"$regex": req.Title, "$options": "i"}
}
if req.Remark != "" {
filter[advitiseInfoMod.FN.AdvitiseInfo.Remark] = bson.M{"$regex": req.Remark, "$options": "i"}
}
if req.Status != nil {
filter[advitiseInfoMod.FN.AdvitiseInfo.Status] = *req.Status
}
total, err := advitiseInfoMod.CountAdvitiseInfos(ctx, filter)
if err != nil {
return nil, err
}
opt := &options.FindOptions{}
opt.SetSort(bson.D{{Key: advitiseInfoMod.FN.AdvitiseInfo.CreatedAt, Value: -1}})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
list, err := advitiseInfoMod.FindManyAdvitiseInfos(ctx, filter, opt)
if err != nil {
return nil, err
}
resp.Total = total
resp.List = list
return resp, nil
}
func InfoAdd(ctx context.Context, req *proto.AddAdvitiseInfoReq) error {
now := time.Now()
doc := &advitiseInfoMod.AdvitiseInfo{
Id: genId.GetAdvitiseInfoId(),
Title: req.Title,
FullScreenImgs: emptyIfNil(req.FullScreenImgs),
Banner720300Imgs: emptyIfNil(req.Banner720300Imgs),
Banner720150Imgs: emptyIfNil(req.Banner720150Imgs),
Banner750700Imgs: emptyIfNil(req.Banner750700Imgs),
Banner800450Imgs: emptyIfNil(req.Banner800450Imgs),
PopUps600800Imgs: emptyIfNil(req.PopUps600800Imgs),
PopUps512512Imgs: emptyIfNil(req.PopUps512512Imgs),
SmallCubeImgs: emptyIfNil(req.SmallCubeImgs),
VerticalCubeImgs: emptyIfNil(req.VerticalCubeImgs),
RandomNames: emptyIfNil(req.RandomNames),
Link: req.Link,
Status: req.Status,
StartTime: req.StartTime,
EndTime: req.EndTime,
Remark: req.Remark,
Desc: req.Desc,
CreatedAt: now,
UpdatedAt: now,
}
return advitiseInfoMod.InsertOneAdvitiseInfo(ctx, doc)
}
func InfoUpdate(ctx context.Context, req *proto.UpdateAdvitiseInfoReq) error {
set := bson.M{
advitiseInfoMod.FN.AdvitiseInfo.UpdatedAt: time.Now(),
}
if req.Title != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.Title] = *req.Title
}
if req.FullScreenImgs != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.FullScreenImgs] = *req.FullScreenImgs
}
if req.Banner720300Imgs != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.Banner720300Imgs] = *req.Banner720300Imgs
}
if req.Banner720150Imgs != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.Banner720150Imgs] = *req.Banner720150Imgs
}
if req.Banner750700Imgs != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.Banner750700Imgs] = *req.Banner750700Imgs
}
if req.Banner800450Imgs != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.Banner800450Imgs] = *req.Banner800450Imgs
}
if req.PopUps600800Imgs != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.PopUps600800Imgs] = *req.PopUps600800Imgs
}
if req.PopUps512512Imgs != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.PopUps512512Imgs] = *req.PopUps512512Imgs
}
if req.SmallCubeImgs != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.SmallCubeImgs] = *req.SmallCubeImgs
}
if req.VerticalCubeImgs != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.VerticalCubeImgs] = *req.VerticalCubeImgs
}
if req.RandomNames != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.RandomNames] = *req.RandomNames
}
if req.Link != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.Link] = *req.Link
}
if req.Status != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.Status] = *req.Status
}
if req.StartTime != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.StartTime] = *req.StartTime
}
if req.EndTime != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.EndTime] = *req.EndTime
}
if req.Remark != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.Remark] = *req.Remark
}
if req.Desc != nil {
set[advitiseInfoMod.FN.AdvitiseInfo.Desc] = *req.Desc
}
return advitiseInfoMod.UpdateOneAdvitiseInfo(ctx,
bson.M{advitiseInfoMod.FN.AdvitiseInfo.Id: req.Id},
bson.M{"$set": set},
)
}
func InfoDel(ctx context.Context, id int64) error {
_, err := advitiseInfoMod.DelOneAdvitiseInfo(ctx, bson.M{advitiseInfoMod.FN.AdvitiseInfo.Id: id})
return err
}
func emptyIfNil(s []string) []string {
if s == nil {
return []string{}
}
return s
}
+93
View File
@@ -0,0 +1,93 @@
package advitiseV2Ser
import (
"context"
"stat/api/proto"
"stat/common/genId"
"stat/models/advitiseLocationMod"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo/options"
)
func LocationList(ctx context.Context, req *proto.AdvitiseLocationListReq) (*proto.AdvitiseLocationListResp, error) {
resp := &proto.AdvitiseLocationListResp{
Total: 0,
List: make(advitiseLocationMod.AdvitiseLocationList, 0),
}
filter := bson.M{}
if req.AppId != nil {
filter[advitiseLocationMod.FN.AdvitiseLocation.AppId] = *req.AppId
}
if req.LocId != nil {
filter[advitiseLocationMod.FN.AdvitiseLocation.LocId] = *req.LocId
}
if req.Name != "" {
filter[advitiseLocationMod.FN.AdvitiseLocation.Name] = bson.M{"$regex": req.Name, "$options": "i"}
}
total, err := advitiseLocationMod.CountAdvitiseLocations(ctx, filter)
if err != nil {
return nil, err
}
opt := &options.FindOptions{}
opt.SetSort(bson.D{{Key: advitiseLocationMod.FN.AdvitiseLocation.CreatedAt, Value: -1}})
opt.SetSkip((req.PageNum - 1) * req.PageSize)
opt.SetLimit(req.PageSize)
list, err := advitiseLocationMod.FindManyAdvitiseLocations(ctx, filter, opt)
if err != nil {
return nil, err
}
resp.Total = total
resp.List = list
return resp, nil
}
func LocationAdd(ctx context.Context, req *proto.AddAdvitiseLocationReq) error {
now := time.Now()
doc := &advitiseLocationMod.AdvitiseLocation{
Id: genId.GetAdvitiseLocationId(),
AppId: req.AppId,
LocId: req.LocId,
Name: req.Name,
AdvitiseSize: req.AdvitiseSize,
ImgSizeDesc: req.ImgSizeDesc,
Remark: req.Remark,
CreatedAt: now,
UpdatedAt: now,
}
return advitiseLocationMod.InsertOneAdvitiseLocation(ctx, doc)
}
func LocationUpdate(ctx context.Context, req *proto.UpdateAdvitiseLocationReq) error {
set := bson.M{
advitiseLocationMod.FN.AdvitiseLocation.UpdatedAt: time.Now(),
}
if req.AppId != nil {
set[advitiseLocationMod.FN.AdvitiseLocation.AppId] = *req.AppId
}
if req.LocId != nil {
set[advitiseLocationMod.FN.AdvitiseLocation.LocId] = *req.LocId
}
if req.Name != nil {
set[advitiseLocationMod.FN.AdvitiseLocation.Name] = *req.Name
}
if req.AdvitiseSize != nil {
set[advitiseLocationMod.FN.AdvitiseLocation.AdvitiseSize] = *req.AdvitiseSize
}
if req.ImgSizeDesc != nil {
set[advitiseLocationMod.FN.AdvitiseLocation.ImgSizeDesc] = *req.ImgSizeDesc
}
if req.Remark != nil {
set[advitiseLocationMod.FN.AdvitiseLocation.Remark] = *req.Remark
}
return advitiseLocationMod.UpdateOneAdvitiseLocation(ctx,
bson.M{advitiseLocationMod.FN.AdvitiseLocation.Id: req.Id},
bson.M{"$set": set},
)
}
func LocationDel(ctx context.Context, id int64) error {
_, err := advitiseLocationMod.DelOneAdvitiseLocation(ctx, bson.M{advitiseLocationMod.FN.AdvitiseLocation.Id: id})
return err
}
+285
View File
@@ -0,0 +1,285 @@
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
}