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 }