新增广告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
+106
View File
@@ -0,0 +1,106 @@
package advitiseV2Api
import (
"stat/api/proto"
"stat/common/stdLog"
"stat/common/utils"
"stat/service/advitiseV2Ser"
"stat/service/opLogSer"
"github.com/gin-gonic/gin"
)
// @Tags 广告管理V2/绑定
// @Summary 绑定关系列表
// @Router /api/stat/advitise/v2/bind/list [POST]
// @Param param body proto.AdvitiseBindListReq true "参数"
// @Success 200 {object} proto.AdvitiseBindListResp "成功"
// @Security ApiKeyAuth
func BindList(ctx *gin.Context) {
req := &proto.AdvitiseBindListReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
resp, err := advitiseV2Ser.BindList(ctx, req)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
stdLog.ServerJSON(ctx, stdLog.Success, resp)
}
// @Tags 广告管理V2/绑定
// @Summary 新增绑定
// @Router /api/stat/advitise/v2/bind/add [POST]
// @Param param body proto.AddAdvitiseBindReq true "参数"
// @Success 200 {string} string "成功"
// @Security ApiKeyAuth
func BindAdd(ctx *gin.Context) {
account, err := utils.GetAdmin(ctx)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.NoToken, err.Error())
return
}
req := &proto.AddAdvitiseBindReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
if err := advitiseV2Ser.BindAdd(ctx, req); err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
opLogSer.AddOpLog(ctx, account, true, req)
stdLog.ServerJSON(ctx, stdLog.Success, "")
}
// @Tags 广告管理V2/绑定
// @Summary 更新绑定
// @Router /api/stat/advitise/v2/bind/update [POST]
// @Param param body proto.UpdateAdvitiseBindReq true "参数"
// @Success 200 {string} string "成功"
// @Security ApiKeyAuth
func BindUpdate(ctx *gin.Context) {
account, err := utils.GetAdmin(ctx)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.NoToken, err.Error())
return
}
req := &proto.UpdateAdvitiseBindReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
if err := advitiseV2Ser.BindUpdate(ctx, req); err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
opLogSer.AddOpLog(ctx, account, true, req)
stdLog.ServerJSON(ctx, stdLog.Success, "")
}
// @Tags 广告管理V2/绑定
// @Summary 删除绑定
// @Router /api/stat/advitise/v2/bind/del [POST]
// @Param param body proto.DelAdvitiseBindReq true "参数"
// @Success 200 {string} string "成功"
// @Security ApiKeyAuth
func BindDel(ctx *gin.Context) {
account, err := utils.GetAdmin(ctx)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.NoToken, err.Error())
return
}
req := &proto.DelAdvitiseBindReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
if err := advitiseV2Ser.BindDel(ctx, req.Id); err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
opLogSer.AddOpLog(ctx, account, true, req)
stdLog.ServerJSON(ctx, stdLog.Success, "")
}
+49
View File
@@ -0,0 +1,49 @@
package advitiseV2Api
import (
"stat/api/proto"
"stat/common/stdLog"
"stat/service/advitiseV2Ser"
"github.com/gin-gonic/gin"
)
// @Tags 广告管理V2/外部
// @Summary 拉广告列表(客户端)
// @Description 按 (appId, locationId?) 返回可展示广告;结构对齐老 /ads/get(advertiseList);每条 item 多带 bindId 供点击回传
// @Router /api/stat/advitise/v2/get [POST]
// @Param param body proto.GetAdvitiseV2Req true "参数"
// @Success 200 {object} proto.AdvertiseRes "成功"
func GetAdvitises(ctx *gin.Context) {
req := &proto.GetAdvitiseV2Req{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
resp, err := advitiseV2Ser.GetAdvitises(ctx, req)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
// 对齐老 GetAds 直接 ctx.JSON(200, data) 的行为
ctx.JSON(200, resp)
}
// @Tags 广告管理V2/外部
// @Summary 广告点击(客户端)
// @Description 根据 GetAds 返回的 bindId 上报点击,服务端反查 (广告, 位置) 并沿用老统计逻辑(redis 时段 hash + doc 累计)
// @Router /api/stat/advitise/v2/click [POST]
// @Param param body proto.ClickAdvitiseV2Req true "参数"
// @Success 200 {string} string "成功"
func ClickAdvitise(ctx *gin.Context) {
req := &proto.ClickAdvitiseV2Req{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
if err := advitiseV2Ser.ClickAdvitise(ctx, req); err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
stdLog.ServerJSON(ctx, stdLog.Success, "")
}
+106
View File
@@ -0,0 +1,106 @@
package advitiseV2Api
import (
"stat/api/proto"
"stat/common/stdLog"
"stat/common/utils"
"stat/service/advitiseV2Ser"
"stat/service/opLogSer"
"github.com/gin-gonic/gin"
)
// @Tags 广告管理V2/素材
// @Summary 素材列表
// @Router /api/stat/advitise/v2/info/list [POST]
// @Param param body proto.AdvitiseInfoListReq true "参数"
// @Success 200 {object} proto.AdvitiseInfoListResp "成功"
// @Security ApiKeyAuth
func InfoList(ctx *gin.Context) {
req := &proto.AdvitiseInfoListReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
resp, err := advitiseV2Ser.InfoList(ctx, req)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
stdLog.ServerJSON(ctx, stdLog.Success, resp)
}
// @Tags 广告管理V2/素材
// @Summary 新增素材
// @Router /api/stat/advitise/v2/info/add [POST]
// @Param param body proto.AddAdvitiseInfoReq true "参数"
// @Success 200 {string} string "成功"
// @Security ApiKeyAuth
func InfoAdd(ctx *gin.Context) {
account, err := utils.GetAdmin(ctx)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.NoToken, err.Error())
return
}
req := &proto.AddAdvitiseInfoReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
if err := advitiseV2Ser.InfoAdd(ctx, req); err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
opLogSer.AddOpLog(ctx, account, true, req)
stdLog.ServerJSON(ctx, stdLog.Success, "")
}
// @Tags 广告管理V2/素材
// @Summary 更新素材
// @Router /api/stat/advitise/v2/info/update [POST]
// @Param param body proto.UpdateAdvitiseInfoReq true "参数"
// @Success 200 {string} string "成功"
// @Security ApiKeyAuth
func InfoUpdate(ctx *gin.Context) {
account, err := utils.GetAdmin(ctx)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.NoToken, err.Error())
return
}
req := &proto.UpdateAdvitiseInfoReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
if err := advitiseV2Ser.InfoUpdate(ctx, req); err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
opLogSer.AddOpLog(ctx, account, true, req)
stdLog.ServerJSON(ctx, stdLog.Success, "")
}
// @Tags 广告管理V2/素材
// @Summary 删除素材
// @Router /api/stat/advitise/v2/info/del [POST]
// @Param param body proto.DelAdvitiseInfoReq true "参数"
// @Success 200 {string} string "成功"
// @Security ApiKeyAuth
func InfoDel(ctx *gin.Context) {
account, err := utils.GetAdmin(ctx)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.NoToken, err.Error())
return
}
req := &proto.DelAdvitiseInfoReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
if err := advitiseV2Ser.InfoDel(ctx, req.Id); err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
opLogSer.AddOpLog(ctx, account, true, req)
stdLog.ServerJSON(ctx, stdLog.Success, "")
}
+106
View File
@@ -0,0 +1,106 @@
package advitiseV2Api
import (
"stat/api/proto"
"stat/common/stdLog"
"stat/common/utils"
"stat/service/advitiseV2Ser"
"stat/service/opLogSer"
"github.com/gin-gonic/gin"
)
// @Tags 广告管理V2/位置
// @Summary 广告位列表
// @Router /api/stat/advitise/v2/location/list [POST]
// @Param param body proto.AdvitiseLocationListReq true "参数"
// @Success 200 {object} proto.AdvitiseLocationListResp "成功"
// @Security ApiKeyAuth
func LocationList(ctx *gin.Context) {
req := &proto.AdvitiseLocationListReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
resp, err := advitiseV2Ser.LocationList(ctx, req)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
stdLog.ServerJSON(ctx, stdLog.Success, resp)
}
// @Tags 广告管理V2/位置
// @Summary 新增广告位
// @Router /api/stat/advitise/v2/location/add [POST]
// @Param param body proto.AddAdvitiseLocationReq true "参数"
// @Success 200 {string} string "成功"
// @Security ApiKeyAuth
func LocationAdd(ctx *gin.Context) {
account, err := utils.GetAdmin(ctx)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.NoToken, err.Error())
return
}
req := &proto.AddAdvitiseLocationReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
if err := advitiseV2Ser.LocationAdd(ctx, req); err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
opLogSer.AddOpLog(ctx, account, true, req)
stdLog.ServerJSON(ctx, stdLog.Success, "")
}
// @Tags 广告管理V2/位置
// @Summary 更新广告位
// @Router /api/stat/advitise/v2/location/update [POST]
// @Param param body proto.UpdateAdvitiseLocationReq true "参数"
// @Success 200 {string} string "成功"
// @Security ApiKeyAuth
func LocationUpdate(ctx *gin.Context) {
account, err := utils.GetAdmin(ctx)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.NoToken, err.Error())
return
}
req := &proto.UpdateAdvitiseLocationReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
if err := advitiseV2Ser.LocationUpdate(ctx, req); err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
opLogSer.AddOpLog(ctx, account, true, req)
stdLog.ServerJSON(ctx, stdLog.Success, "")
}
// @Tags 广告管理V2/位置
// @Summary 删除广告位
// @Router /api/stat/advitise/v2/location/del [POST]
// @Param param body proto.DelAdvitiseLocationReq true "参数"
// @Success 200 {string} string "成功"
// @Security ApiKeyAuth
func LocationDel(ctx *gin.Context) {
account, err := utils.GetAdmin(ctx)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.NoToken, err.Error())
return
}
req := &proto.DelAdvitiseLocationReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
if err := advitiseV2Ser.LocationDel(ctx, req.Id); err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
opLogSer.AddOpLog(ctx, account, true, req)
stdLog.ServerJSON(ctx, stdLog.Success, "")
}
+80
View File
@@ -0,0 +1,80 @@
package advitiseV2Api
import (
"stat/api/proto"
"stat/common/stdLog"
"stat/service/advitiseV2Ser"
"github.com/gin-gonic/gin"
)
// @Tags 广告管理V2/统计
// @Summary 广告每日点击列表(汇总)
// @Router /api/stat/advitise/v2/day/click/list [POST]
// @Param param body proto.AdvitiseDayClickListReq true "参数"
// @Success 200 {object} proto.AdvitiseDayClickListResp "成功"
// @Security ApiKeyAuth
func DayClickList(ctx *gin.Context) {
req := &proto.AdvitiseDayClickListReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
resp, err := advitiseV2Ser.DayClickList(ctx, req)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
stdLog.ServerJSON(ctx, stdLog.Success, resp)
}
// @Tags 广告管理V2/统计
// @Summary 广告点击详情(细维度)
// @Router /api/stat/advitise/v2/day/click/info [POST]
// @Param param body proto.AdvitiseClickListReq true "参数"
// @Success 200 {object} proto.AdvitiseClickListResp "成功"
// @Security ApiKeyAuth
func DayClickInfo(ctx *gin.Context) {
req := &proto.AdvitiseClickListReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
resp, err := advitiseV2Ser.ClickList(ctx, req)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
stdLog.ServerJSON(ctx, stdLog.Success, resp)
}
// @Tags 广告管理V2/统计
// @Summary 广告细维度点击列表(广告位查看统计入口)
// @Router /api/stat/advitise/v2/click/list [POST]
// @Param param body proto.AdvitiseClickListReq true "参数"
// @Success 200 {object} proto.AdvitiseClickListResp "成功"
// @Security ApiKeyAuth
func ClickList(ctx *gin.Context) {
// 与 DayClickInfo 语义一致,复用同一入口
DayClickInfo(ctx)
}
// @Tags 广告管理V2/统计
// @Summary 按时间范围汇总广告点击量(每广告一行)
// @Router /api/stat/advitise/v2/day/click/summary [POST]
// @Param param body proto.AdvitiseClickSummaryReq true "参数"
// @Success 200 {object} proto.AdvitiseClickSummaryResp "成功"
// @Security ApiKeyAuth
func ClickSummary(ctx *gin.Context) {
req := &proto.AdvitiseClickSummaryReq{}
if err := ctx.ShouldBindJSON(req); err != nil {
stdLog.ServerJSON(ctx, stdLog.ParamError, err.Error())
return
}
resp, err := advitiseV2Ser.ClickSummary(ctx, req)
if err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
return
}
stdLog.ServerJSON(ctx, stdLog.Success, resp)
}
+2 -1
View File
@@ -25,9 +25,10 @@ func FileUpload(ctx *gin.Context) {
return
}
// 上传到桶2
data, err := file.PutObject(ctx, statg.Conf.ProxyUrl.FileRes, &file.FileObject{
data, err := file.PutObject(ctx, statg.Conf.ProxyUrl.FileRes1, &file.FileObject{
File: f,
})
if err != nil {
stdLog.ServerJSON(ctx, stdLog.Failure, err.Error())
}
+14 -12
View File
@@ -107,21 +107,23 @@ type AdvertiseRes struct {
// 外部广告信息
type AdvertiseInfo struct {
Id int64 `json:"id"` // 广告id
Title string `json:"title"` // 广告标题
CoverImg string `json:"coverImg"` // 封面
LocId int32 `json:"locId"` // 位置id
JumpType advertiseMod.JumpType `json:"jumpType"` // 跳转方式 0:外部浏览器跳转 1:内部浏览器跳转 2:app内部跳转
Link string `json:"link"` // 链接地址
Sort int64 `json:"sort"` // 排序
Tags string `json:"tags"` // 标签
Remark string `json:"remark"` // 备注描述
AmbientColor string `json:"ambientColor"` // 氛围色(逼哩漫画)
Type int // 0:系统 1:渠道
Id int64 `json:"id"` // 广告id(V2 = advitiseId)
Title string `json:"title"` // 广告标题
CoverImg string `json:"coverImg"` // 封面
LocId int32 `json:"locId"` // 位置id
JumpType advertiseMod.JumpType `json:"jumpType"` // 跳转方式 0:外部浏览器跳转 1:内部浏览器跳转 2:app内部跳转
Link string `json:"link"` // 链接地址
Sort int64 `json:"sort"` // 排序
Tags string `json:"tags"` // 标签
Remark string `json:"remark"` // 备注描述
AmbientColor string `json:"ambientColor"` // 氛围色(逼哩漫画)
Type int // 0:系统 1:渠道
BindId int64 `json:"bindId,omitempty"` // V2 点击回传凭证(老接口不填,omitempty 隐藏)
}
// 广告点击请求
type AdsClickReq struct {
Id *int64 `from:"id" json:"id" binding:"required"` // 广告id
Id *int64 `from:"id" json:"id" binding:"required"` // 广告id(客户端上报,可能已过期)
AppId *int32 `from:"appId" json:"appId" binding:"required"` // 应用id
LocId *int32 `from:"locId" json:"locId"` // 位置id(可选;传入后,Id 失效时会按 AppId+LocId 定位最新广告)
}
+232
View File
@@ -0,0 +1,232 @@
package proto
import (
"stat/models/advitiseBindMod"
"stat/models/advitiseInfoMod"
"stat/models/advitiseLocationMod"
"time"
)
// ============ 广告素材(Info) ============
type AdvitiseInfoListReq struct {
Title string `json:"title" form:"title"`
Remark string `json:"remark" form:"remark"`
Status *bool `json:"status" form:"status"`
PageNum int64 `json:"pageNum" form:"pageNum" binding:"required,gte=1"`
PageSize int64 `json:"pageSize" form:"pageSize" binding:"required,gte=1,lte=500"`
}
type AdvitiseInfoListResp struct {
Total int64 `json:"total"`
List advitiseInfoMod.AdvitiseInfoList `json:"list"`
}
type AddAdvitiseInfoReq struct {
Title string `json:"title" binding:"required"`
FullScreenImgs []string `json:"fullScreenImgs"`
Banner720300Imgs []string `json:"banner720300Imgs"`
Banner720150Imgs []string `json:"banner720150Imgs"`
Banner750700Imgs []string `json:"banner750700Imgs"`
Banner800450Imgs []string `json:"banner800450Imgs"`
PopUps600800Imgs []string `json:"popUps600800Imgs"`
PopUps512512Imgs []string `json:"popUps512512Imgs"`
SmallCubeImgs []string `json:"smallCubeImgs"`
VerticalCubeImgs []string `json:"verticalCubeImgs"`
RandomNames []string `json:"randomNames"`
Link string `json:"link"`
Status bool `json:"status"`
StartTime time.Time `json:"startTime"`
EndTime time.Time `json:"endTime"`
Remark string `json:"remark"`
Desc string `json:"desc"`
}
type UpdateAdvitiseInfoReq struct {
Id int64 `json:"id" binding:"required"`
Title *string `json:"title"`
FullScreenImgs *[]string `json:"fullScreenImgs"`
Banner720300Imgs *[]string `json:"banner720300Imgs"`
Banner720150Imgs *[]string `json:"banner720150Imgs"`
Banner750700Imgs *[]string `json:"banner750700Imgs"`
Banner800450Imgs *[]string `json:"banner800450Imgs"`
PopUps600800Imgs *[]string `json:"popUps600800Imgs"`
PopUps512512Imgs *[]string `json:"popUps512512Imgs"`
SmallCubeImgs *[]string `json:"smallCubeImgs"`
VerticalCubeImgs *[]string `json:"verticalCubeImgs"`
RandomNames *[]string `json:"randomNames"`
Link *string `json:"link"`
Status *bool `json:"status"`
StartTime *time.Time `json:"startTime"`
EndTime *time.Time `json:"endTime"`
Remark *string `json:"remark"`
Desc *string `json:"desc"`
}
type DelAdvitiseInfoReq struct {
Id int64 `json:"id" binding:"required"`
}
// ============ 广告位(Location) ============
type AdvitiseLocationListReq struct {
AppId *int32 `json:"appId" form:"appId"`
LocId *int32 `json:"locId" form:"locId"`
Name string `json:"name" form:"name"`
PageNum int64 `json:"pageNum" form:"pageNum" binding:"required,gte=1"`
PageSize int64 `json:"pageSize" form:"pageSize" binding:"required,gte=1,lte=500"`
}
type AdvitiseLocationListResp struct {
Total int64 `json:"total"`
List advitiseLocationMod.AdvitiseLocationList `json:"list"`
}
type AddAdvitiseLocationReq struct {
AppId int32 `json:"appId" binding:"required"`
LocId int32 `json:"locId" binding:"required"`
Name string `json:"name" binding:"required"`
AdvitiseSize advitiseLocationMod.AdvitiseSizeType `json:"advitiseSize"`
ImgSizeDesc string `json:"imgSizeDesc"`
Remark string `json:"remark"`
}
type UpdateAdvitiseLocationReq struct {
Id int64 `json:"id" binding:"required"`
AppId *int32 `json:"appId"`
LocId *int32 `json:"locId"`
Name *string `json:"name"`
AdvitiseSize *advitiseLocationMod.AdvitiseSizeType `json:"advitiseSize"`
ImgSizeDesc *string `json:"imgSizeDesc"`
Remark *string `json:"remark"`
}
type DelAdvitiseLocationReq struct {
Id int64 `json:"id" binding:"required"`
}
// ============ 广告绑定(Bind) ============
type AdvitiseBindListReq struct {
AppId *int32 `json:"appId" form:"appId"`
AdvitiseId *int64 `json:"advitiseId" form:"advitiseId"`
LocationId *int32 `json:"locationId" form:"locationId"`
PageNum int64 `json:"pageNum" form:"pageNum" binding:"required,gte=1"`
PageSize int64 `json:"pageSize" form:"pageSize" binding:"required,gte=1,lte=500"`
}
type AdvitiseBindListResp struct {
Total int64 `json:"total"`
List advitiseBindMod.AdvitiseBindList `json:"list"`
}
type AddAdvitiseBindReq struct {
AppId int32 `json:"appId" binding:"required"`
AdvitiseId int64 `json:"advitiseId" binding:"required"`
LocationId []int32 `json:"locationId" binding:"required"`
Sort int64 `json:"sort"`
}
type UpdateAdvitiseBindReq struct {
Id int64 `json:"id" binding:"required"`
AppId *int32 `json:"appId"`
AdvitiseId *int64 `json:"advitiseId"`
LocationId *[]int32 `json:"locationId"`
Sort *int64 `json:"sort"`
}
type DelAdvitiseBindReq struct {
Id int64 `json:"id" binding:"required"`
}
// ============ 外部获取广告 / 点击 ============
// GetAdvitiseV2Req 客户端拉广告列表
type GetAdvitiseV2Req struct {
AppId int32 `json:"appId" binding:"required"`
LocationId *int32 `json:"locationId"` // 可选,指定位置
}
// 响应结构直接复用老 proto.AdvertiseRes (advertiseList) — V2 客户端从 item.bindId 拿点击回传凭证
// ClickAdvitiseV2Req 客户端点击回传
// LocationId 必须传:细维度统计表(advitise_click_stat)按 (appId, advitiseId, locationId, day) 聚合
type ClickAdvitiseV2Req struct {
Id int64 `from:"id" json:"id" binding:"required"` // 广告id
// LocationId int32 `json:"locationId" binding:"required"` // 客户端展示时选中的位置
}
// ============ 统计管理 ============
// AdvitiseDayClickListReq 每日点击列表(汇总,来自 advitise_day_click_stat)
type AdvitiseDayClickListReq struct {
Id *int64 `json:"id" form:"id"` // 广告id(advitiseId)
StatStartDate string `json:"statStartDate" form:"statStartDate"` // yyyy-MM-dd HH:mm:ss
StatEndDate string `json:"statEndDate" form:"statEndDate"`
PageNum int64 `json:"pageNum" form:"pageNum" binding:"required,gte=1"`
PageSize int64 `json:"pageSize" form:"pageSize" binding:"required,gte=1,lte=500"`
}
type AdvitiseDayClickItem struct {
AdsId int64 `json:"adsId"` // 广告id (advitiseId)
AdsName string `json:"adsName"` // 广告标题
Clicks int64 `json:"clicks"`
StatDate string `json:"statDate"` // yyyy-MM-dd
}
type AdvitiseDayClickListResp struct {
Total int64 `json:"total"`
List []*AdvitiseDayClickItem `json:"list"`
}
// AdvitiseClickListReq 细维度列表(来自 advitise_click_stat)
type AdvitiseClickListReq struct {
AdsId *int64 `json:"adsId" form:"adsId"` // 广告id(advitiseId)
AppId *int32 `json:"appId" form:"appId"`
LocationId *int32 `json:"locationId" form:"locationId"`
StatStartDate string `json:"statStartDate" form:"statStartDate"`
StatEndDate string `json:"statEndDate" form:"statEndDate"`
PageNum int64 `json:"pageNum" form:"pageNum" binding:"required,gte=1"`
PageSize int64 `json:"pageSize" form:"pageSize" binding:"required,gte=1,lte=500"`
}
type AdvitiseClickItem struct {
AppId int32 `json:"appId"`
AppName string `json:"appName"`
AdsId int64 `json:"adsId"`
AdsName string `json:"adsName"`
LocationId int32 `json:"locationId"`
LocationName string `json:"locationName"`
Clicks int64 `json:"clicks"`
StatDate string `json:"statDate"`
}
type AdvitiseClickListResp struct {
Total int64 `json:"total"`
List []*AdvitiseClickItem `json:"list"`
}
// AdvitiseClickSummaryReq 按时间范围汇总广告点击量,每个广告一行
// StatStartDate/StatEndDate 至少填一个;可选 id/appId/locationId 缩窄范围
type AdvitiseClickSummaryReq struct {
Id *int64 `json:"id" form:"id"`
AppId *int32 `json:"appId" form:"appId"`
LocationId *int32 `json:"locationId" form:"locationId"`
StatStartDate string `json:"statStartDate" form:"statStartDate"`
StatEndDate string `json:"statEndDate" form:"statEndDate"`
PageNum int64 `json:"pageNum" form:"pageNum" binding:"required,gte=1"`
PageSize int64 `json:"pageSize" form:"pageSize" binding:"required,gte=1,lte=500"`
}
type AdvitiseClickSummaryItem struct {
AdsId int64 `json:"adsId"`
AdsName string `json:"adsName"`
TotalClicks int64 `json:"totalClicks"`
}
type AdvitiseClickSummaryResp struct {
Total int64 `json:"total"` // 匹配到的广告数
GrandTotal int64 `json:"grandTotal"` // 时间范围内所有点击总量
List []*AdvitiseClickSummaryItem `json:"list"`
}
+1 -1
View File
@@ -22,7 +22,7 @@ import (
// @Success 200 {file} file "ios返回mobileconfig文件"
// @Success 302 {string} string "android跳转下载地址"
// @Failure 400 {string} string "失败"
// @Router /rsdownload/{appId}/{channel} [get]
// @Router /download/{appId}/{channel} [get]
func CpcDownload(ctx *gin.Context) {
// 第一个参数 appId
appId, err := strconv.Atoi(ctx.Param("appId"))
+31 -1
View File
@@ -20,7 +20,10 @@ var audioBook *lid.Lid // 有声小说自增器
var landpageConfig *lid.Lid // 落地页配置id
var appCategory *lid.Lid //
var appLocLid *lid.Lid // 应用中心位置id自增器
var cpcDownloadCfgLid *lid.Lid // CPC下载配置id自增器
var cpcDownloadCfgLid *lid.Lid // CPC下载配置id自增器
var advitiseInfoLid *lid.Lid // 广告素材(V2) id自增器
var advitiseLocationLid *lid.Lid // 广告位(V2) id自增器
var advitiseBindLid *lid.Lid // 广告绑定(V2) id自增器
func Start() {
var err error
@@ -104,6 +107,21 @@ func Start() {
if err != nil {
panic(err)
}
advitiseInfoLid, err = lid.NewLid(collection, "advitise_info_id", 100)
if err != nil {
panic(err)
}
advitiseLocationLid, err = lid.NewLid(collection, "advitise_location_id", 100)
if err != nil {
panic(err)
}
advitiseBindLid, err = lid.NewLid(collection, "advitise_bind_id", 100)
if err != nil {
panic(err)
}
}
func GetNextRoleId() int64 {
@@ -169,3 +187,15 @@ func GetNextAppLocId() int64 {
func GetCpcDownloadCfgId() int64 {
return cpcDownloadCfgLid.Get()
}
func GetAdvitiseInfoId() int64 {
return advitiseInfoLid.Get()
}
func GetAdvitiseLocationId() int64 {
return advitiseLocationLid.Get()
}
func GetAdvitiseBindId() int64 {
return advitiseBindLid.Get()
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+58
View File
@@ -0,0 +1,58 @@
# /etc/nginx/conf.d/cpcdownload.mfcg.me.conf
#
# cpcdownload.mfcg.me -> stat_center Go 服务 (127.0.0.1:8089)
# 用途:
# - GET /download/{appId}/{channel} 双端下载落地页(ios 返回 mobileconfig / android 302 跳转)
# - POST /api/stat/cpc/download/{list,add,update,del} 后台 CPC 下载配置管理
#
# 部署:
# scp deploy/nginx/cpcdownload.mfcg.me.conf root@<gateway>:/etc/nginx/conf.d/
# ssh root@<gateway> 'nginx -t && nginx -s reload'
# HTTP -> HTTPS
server {
listen 80;
listen [::]:80;
server_name cpcdownload.mfcg.me;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name cpcdownload.mfcg.me;
# 证书路径按你机器上的实际位置调整(沿用同 mfcg.me 通配证书即可)
ssl_certificate /etc/nginx/ssl/mfcg.me/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/mfcg.me/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
access_log /var/log/nginx/cpcdownload.mfcg.me.access.log;
error_log /var/log/nginx/cpcdownload.mfcg.me.error.log;
# mobileconfig 大小通常很小,这里放宽到 8M 兜底
client_max_body_size 8m;
location / {
proxy_pass http://127.0.0.1:8089;
proxy_http_version 1.1;
# 透传真实 IP(后端 ctx.ClientIP() 依赖此头取 IP 入库 landingpage_cpc)
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# mobileconfig 是二进制,保持原样;不做缓冲以便 302 立即下发
proxy_buffering off;
proxy_request_buffering off;
proxy_connect_timeout 5s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
}
+3 -1
View File
@@ -86,7 +86,9 @@ var (
"/api/stat/daily-stat/hour",
"/api/stat/daily-stat/rank",
"/api/stat/daily-stat/total",
"/rsdownload/",
"/download/",
"/api/stat/advitise/v2/get",
"/api/stat/advitise/v2/click",
}
)
+24
View File
@@ -150,6 +150,30 @@ func FindAdsById(ctx context.Context, id int64) (data Advertise, err error) {
return
}
// FindLatestAdsByAppAndLoc 取该应用/位置当前生效的最新一条广告(用于点击回退到最新广告)
// locId <= 0 表示不按位置过滤,只在应用维度取最新
func FindLatestAdsByAppAndLoc(ctx context.Context, appId, locId int32) (data Advertise, err error) {
now := time.Now()
filter := bson.M{
"appId": appId,
"isActive": true,
"startTime": bson.M{"$lte": now},
"endTime": bson.M{"$gte": now},
}
if locId > 0 {
filter["locId"] = locId
}
opts := options.FindOne().SetSort(bson.D{
{Key: "sort", Value: 1},
{Key: "createAt", Value: -1},
})
if err = db.FindOne(ctx, &data, filter, opts); err != nil {
log.Error("advertise FindLatestAdsByAppAndLoc FindOne err", log.E(err))
return
}
return
}
// 增加广告点击量
func IncAdsClickCount(ctx context.Context, id int64) (err error) {
filter := bson.M{"_id": id}
+78
View File
@@ -0,0 +1,78 @@
package advitiseBindMod
import (
"context"
"fmt"
"stat/common/log"
"stat/common/mongos"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func Init(ctx context.Context) {
db = mongos.New(mongos.MgoManager, TN.AdvitiseBind)
indexes := []mongo.IndexModel{
{Keys: bson.D{
{Key: FN.AdvitiseBind.AppId, Value: 1},
{Key: FN.AdvitiseBind.LocationId, Value: 1},
}},
{Keys: bson.D{{Key: FN.AdvitiseBind.AdvitiseId, Value: 1}}},
{Keys: bson.D{{Key: FN.AdvitiseBind.CreatedAt, Value: -1}}},
}
if _, err := db.CreateIndex(ctx, indexes); err != nil {
panic(fmt.Sprintf("(advitiseBindMod:Init) CreateIndex failed: %+v", err))
}
}
func InsertOneAdvitiseBind(ctx context.Context, doc *AdvitiseBind) error {
if _, err := db.InsertOne(ctx, doc); err != nil {
log.Warn("advitiseBindMod InsertOne failed", log.E(err))
return err
}
return nil
}
func FindOneAdvitiseBind(ctx context.Context, filter bson.M) (*AdvitiseBind, error) {
var doc AdvitiseBind
if err := db.FindOne(ctx, &doc, filter); err != nil {
return nil, err
}
return &doc, nil
}
func DelOneAdvitiseBind(ctx context.Context, filter bson.M) (int64, error) {
count, err := db.DeleteOne(ctx, filter)
if err != nil {
log.Warn("advitiseBindMod DeleteOne failed", log.E(err), log.Any("filter", filter))
return 0, err
}
return count, nil
}
func UpdateOneAdvitiseBind(ctx context.Context, filter bson.M, update bson.M) error {
if _, err := db.UpdateOne(ctx, filter, update); err != nil {
log.Warn("advitiseBindMod UpdateOne failed", log.E(err), log.Any("filter", filter))
return err
}
return nil
}
func CountAdvitiseBinds(ctx context.Context, filter bson.M) (int64, error) {
count, err := db.Count(ctx, filter)
if err != nil {
log.Warn("advitiseBindMod Count failed", log.E(err), log.Any("filter", filter))
return 0, err
}
return count, nil
}
func FindManyAdvitiseBinds(ctx context.Context, filter bson.M, opts ...*options.FindOptions) (AdvitiseBindList, error) {
list := make(AdvitiseBindList, 0)
if err := db.Find(ctx, &list, filter, opts...); err != nil {
log.Warn("advitiseBindMod Find failed", log.E(err), log.Any("filter", filter))
return nil, err
}
return list, nil
}
+30
View File
@@ -0,0 +1,30 @@
package advitiseBindMod
type TagAdvitiseBind struct {
Id string
AppId string
AdvitiseId string
LocationId string
Sort string
CreatedAt string
UpdatedAt string
}
var FN struct {
AdvitiseBind TagAdvitiseBind
}
var TN struct {
AdvitiseBind string
}
func init() {
FN.AdvitiseBind.Id = "_id"
FN.AdvitiseBind.AppId = "appId"
FN.AdvitiseBind.AdvitiseId = "advitiseId"
FN.AdvitiseBind.LocationId = "locationId"
FN.AdvitiseBind.Sort = "sort"
FN.AdvitiseBind.CreatedAt = "createdAt"
FN.AdvitiseBind.UpdatedAt = "updatedAt"
TN.AdvitiseBind = "advitise_bind"
}
+22
View File
@@ -0,0 +1,22 @@
package advitiseBindMod
import (
"stat/common/mongos"
"time"
)
var db *mongos.MongoTool
// AdvitiseBind 广告投放绑定(V2):把一个 AdvitiseInfo 绑到指定 (appId, locationId[]) 上
// @table:advitise_bind
type AdvitiseBind struct {
Id int64 `json:"id" bson:"_id"`
AppId int32 `json:"appId" bson:"appId"` // 产品id
AdvitiseId int64 `json:"advitiseId" bson:"advitiseId"` // 广告id -> advitiseInfoMod
LocationId []int32 `json:"locationId" bson:"locationId"` // 位置id列表(可多)
Sort int64 `json:"sort" bson:"sort"` // 排序值,优先级
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
}
type AdvitiseBindList []*AdvitiseBind
+99
View File
@@ -0,0 +1,99 @@
package advitiseClickStatMod
import (
"context"
"fmt"
"stat/common/log"
"stat/common/mongos"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func Init(ctx context.Context) {
db = mongos.New(mongos.MgoManager, TN.AdvitiseClickStat)
indexes := []mongo.IndexModel{
{
Keys: bson.D{
{Key: FN.AdvitiseClickStat.AppId, Value: 1},
{Key: FN.AdvitiseClickStat.AdvitiseId, Value: 1},
{Key: FN.AdvitiseClickStat.LocationId, Value: 1},
{Key: FN.AdvitiseClickStat.StatDate, Value: -1},
},
},
{Keys: bson.D{
{Key: FN.AdvitiseClickStat.AdvitiseId, Value: 1},
{Key: FN.AdvitiseClickStat.StatDate, Value: -1},
}},
{Keys: bson.D{
{Key: FN.AdvitiseClickStat.StatDate, Value: -1},
}},
}
if _, err := db.CreateIndex(ctx, indexes); err != nil {
panic(fmt.Sprintf("(advitiseClickStatMod:Init) CreateIndex failed: %+v", err))
}
}
// InsertOrUpdate 按 (appId, advitiseId, locationId, statDate) upsert +1
// 对齐 media/biadsclickmod.InsertOrUpdateBIAdsClickModel
func InsertOrUpdate(ctx context.Context, appId int32, advitiseId int64, locationId int32, statDate time.Time) error {
filter := bson.M{
FN.AdvitiseClickStat.AppId: appId,
FN.AdvitiseClickStat.AdvitiseId: advitiseId,
FN.AdvitiseClickStat.LocationId: locationId,
FN.AdvitiseClickStat.StatDate: statDate,
}
update := bson.M{
"$inc": bson.M{FN.AdvitiseClickStat.Clicks: 1},
"$setOnInsert": bson.M{
FN.AdvitiseClickStat.CreatedAt: time.Now(),
},
}
if _, err := db.UpsertOneEx(ctx, filter, update); err != nil {
log.Warn("advitiseClickStatMod InsertOrUpdate upsert failed",
log.E(err), log.Any("filter", filter))
return err
}
return nil
}
func FindMany(ctx context.Context, filter bson.M, opts ...*options.FindOptions) (AdvitiseClickStatList, error) {
list := make(AdvitiseClickStatList, 0)
if err := db.Find(ctx, &list, filter, opts...); err != nil {
log.Warn("advitiseClickStatMod Find failed", log.E(err), log.Any("filter", filter))
return nil, err
}
return list, nil
}
func Count(ctx context.Context, filter bson.M) (int64, error) {
c, err := db.Count(ctx, filter)
if err != nil {
log.Warn("advitiseClickStatMod Count failed", log.E(err), log.Any("filter", filter))
return 0, err
}
return c, nil
}
// Aggregate 通用聚合(留给上层写 pipeline)
func Aggregate(ctx context.Context, bind interface{}, pipeline []bson.M) error {
return db.Aggregate(ctx, bind, pipeline)
}
// GetAppClicks 按 appId + statDate 汇总点击(对齐 media/biadsclickmod.GetAppClicks)
func GetAppClicks(ctx context.Context, appId int32, statDate time.Time) (int64, error) {
list, err := FindMany(ctx, bson.M{
FN.AdvitiseClickStat.AppId: appId,
FN.AdvitiseClickStat.StatDate: statDate,
})
if err != nil {
return 0, err
}
total := int64(0)
for _, i := range list {
total += i.Clicks
}
return total, nil
}
+30
View File
@@ -0,0 +1,30 @@
package advitiseClickStatMod
type TagAdvitiseClickStat struct {
Id string
AppId string
AdvitiseId string
LocationId string
Clicks string
StatDate string
CreatedAt string
}
var FN struct {
AdvitiseClickStat TagAdvitiseClickStat
}
var TN struct {
AdvitiseClickStat string
}
func init() {
FN.AdvitiseClickStat.Id = "_id"
FN.AdvitiseClickStat.AppId = "appId"
FN.AdvitiseClickStat.AdvitiseId = "advitiseId"
FN.AdvitiseClickStat.LocationId = "locationId"
FN.AdvitiseClickStat.Clicks = "clicks"
FN.AdvitiseClickStat.StatDate = "statDate"
FN.AdvitiseClickStat.CreatedAt = "createdAt"
TN.AdvitiseClickStat = "advitise_click_stat"
}
+25
View File
@@ -0,0 +1,25 @@
package advitiseClickStatMod
import (
"stat/common/mongos"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
var db *mongos.MongoTool
// AdvitiseClickStat 广告点击天粒度细维度统计(对齐 media/biadsclickmod)
// 键: (appId, advitiseId, locationId, statDate)
// @table:advitise_click_stat
type AdvitiseClickStat struct {
Id primitive.ObjectID `json:"id" bson:"_id,omitempty"`
AppId int32 `json:"appId" bson:"appId"`
AdvitiseId int64 `json:"advitiseId" bson:"advitiseId"`
LocationId int32 `json:"locationId" bson:"locationId"`
Clicks int64 `json:"clicks" bson:"clicks"`
StatDate time.Time `json:"statDate" bson:"statDate"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
}
type AdvitiseClickStatList []*AdvitiseClickStat
+68
View File
@@ -0,0 +1,68 @@
package advitiseDayClickStatMod
import (
"context"
"fmt"
"stat/common/log"
"stat/common/mongos"
"time"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func Init(ctx context.Context) {
db = mongos.New(mongos.MgoManager, TN.AdvitiseDayClickStat)
indexes := []mongo.IndexModel{
{Keys: bson.D{
{Key: FN.AdvitiseDayClickStat.AdvitiseId, Value: 1},
{Key: FN.AdvitiseDayClickStat.StatDate, Value: -1},
}},
{Keys: bson.D{
{Key: FN.AdvitiseDayClickStat.StatDate, Value: -1},
}},
}
if _, err := db.CreateIndex(ctx, indexes); err != nil {
panic(fmt.Sprintf("(advitiseDayClickStatMod:Init) CreateIndex failed: %+v", err))
}
}
// InsertOrUpdate 按 (advitiseId, statDate) upsert +1
// 对齐 media/adsdayclickstatmod.InsertOrUpdateAdsDayClickStatModel
func InsertOrUpdate(ctx context.Context, advitiseId int64, statDate time.Time) error {
filter := bson.M{
FN.AdvitiseDayClickStat.AdvitiseId: advitiseId,
FN.AdvitiseDayClickStat.StatDate: statDate,
}
update := bson.M{
"$inc": bson.M{FN.AdvitiseDayClickStat.Clicks: 1},
"$setOnInsert": bson.M{
FN.AdvitiseDayClickStat.CreatedAt: time.Now(),
},
}
if _, err := db.UpsertOneEx(ctx, filter, update); err != nil {
log.Warn("advitiseDayClickStatMod InsertOrUpdate upsert failed",
log.E(err), log.Any("filter", filter))
return err
}
return nil
}
func FindMany(ctx context.Context, filter bson.M, opts ...*options.FindOptions) (AdvitiseDayClickStatList, error) {
list := make(AdvitiseDayClickStatList, 0)
if err := db.Find(ctx, &list, filter, opts...); err != nil {
log.Warn("advitiseDayClickStatMod Find failed", log.E(err), log.Any("filter", filter))
return nil, err
}
return list, nil
}
func Count(ctx context.Context, filter bson.M) (int64, error) {
c, err := db.Count(ctx, filter)
if err != nil {
log.Warn("advitiseDayClickStatMod Count failed", log.E(err), log.Any("filter", filter))
return 0, err
}
return c, nil
}
+26
View File
@@ -0,0 +1,26 @@
package advitiseDayClickStatMod
type TagAdvitiseDayClickStat struct {
Id string
AdvitiseId string
Clicks string
StatDate string
CreatedAt string
}
var FN struct {
AdvitiseDayClickStat TagAdvitiseDayClickStat
}
var TN struct {
AdvitiseDayClickStat string
}
func init() {
FN.AdvitiseDayClickStat.Id = "_id"
FN.AdvitiseDayClickStat.AdvitiseId = "advitiseId"
FN.AdvitiseDayClickStat.Clicks = "clicks"
FN.AdvitiseDayClickStat.StatDate = "statDate"
FN.AdvitiseDayClickStat.CreatedAt = "createdAt"
TN.AdvitiseDayClickStat = "advitise_day_click_stat"
}
+23
View File
@@ -0,0 +1,23 @@
package advitiseDayClickStatMod
import (
"stat/common/mongos"
"time"
"go.mongodb.org/mongo-driver/bson/primitive"
)
var db *mongos.MongoTool
// AdvitiseDayClickStat 广告点击天粒度汇总(不含位置维度,对齐 media/adsdayclickstatmod)
// 键: (advitiseId, statDate)
// @table:advitise_day_click_stat
type AdvitiseDayClickStat struct {
Id primitive.ObjectID `json:"id" bson:"_id,omitempty"`
AdvitiseId int64 `json:"advitiseId" bson:"advitiseId"`
Clicks int64 `json:"clicks" bson:"clicks"`
StatDate time.Time `json:"statDate" bson:"statDate"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
}
type AdvitiseDayClickStatList []*AdvitiseDayClickStat
+79
View File
@@ -0,0 +1,79 @@
package advitiseInfoMod
import (
"context"
"fmt"
"stat/common/log"
"stat/common/mongos"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func Init(ctx context.Context) {
db = mongos.New(mongos.MgoManager, TN.AdvitiseInfo)
indexes := []mongo.IndexModel{
{Keys: bson.D{
{Key: FN.AdvitiseInfo.StartTime, Value: 1},
{Key: FN.AdvitiseInfo.EndTime, Value: 1},
{Key: FN.AdvitiseInfo.Status, Value: 1},
}},
{Keys: bson.D{{Key: FN.AdvitiseInfo.CreatedAt, Value: -1}}},
}
if _, err := db.CreateIndex(ctx, indexes); err != nil {
panic(fmt.Sprintf("(advitiseInfoMod:Init) CreateIndex failed: %+v", err))
}
}
func InsertOneAdvitiseInfo(ctx context.Context, doc *AdvitiseInfo) error {
if _, err := db.InsertOne(ctx, doc); err != nil {
log.Warn("advitiseInfoMod InsertOne failed", log.E(err))
return err
}
return nil
}
func FindOneAdvitiseInfo(ctx context.Context, filter bson.M) (*AdvitiseInfo, error) {
var doc AdvitiseInfo
if err := db.FindOne(ctx, &doc, filter); err != nil {
return nil, err
}
return &doc, nil
}
func DelOneAdvitiseInfo(ctx context.Context, filter bson.M) (int64, error) {
count, err := db.DeleteOne(ctx, filter)
if err != nil {
log.Warn("advitiseInfoMod DeleteOne failed", log.E(err), log.Any("filter", filter))
return 0, err
}
return count, nil
}
func UpdateOneAdvitiseInfo(ctx context.Context, filter bson.M, update bson.M) error {
if _, err := db.UpdateOne(ctx, filter, update); err != nil {
log.Warn("advitiseInfoMod UpdateOne failed", log.E(err), log.Any("filter", filter))
return err
}
return nil
}
func CountAdvitiseInfos(ctx context.Context, filter bson.M) (int64, error) {
count, err := db.Count(ctx, filter)
if err != nil {
log.Warn("advitiseInfoMod Count failed", log.E(err), log.Any("filter", filter))
return 0, err
}
return count, nil
}
func FindManyAdvitiseInfos(ctx context.Context, filter bson.M, opts ...*options.FindOptions) (AdvitiseInfoList, error) {
list := make(AdvitiseInfoList, 0)
if err := db.Find(ctx, &list, filter, opts...); err != nil {
log.Warn("advitiseInfoMod Find failed", log.E(err), log.Any("filter", filter))
return nil, err
}
return list, nil
}
+56
View File
@@ -0,0 +1,56 @@
package advitiseInfoMod
type TagAdvitiseInfo struct {
Id string
Title string
FullScreenImgs string
Banner720300Imgs string
Banner720150Imgs string
Banner750700Imgs string
Banner800450Imgs string
PopUps600800Imgs string
PopUps512512Imgs string
SmallCubeImgs string
VerticalCubeImgs string
RandomNames string
Link string
Status string
StartTime string
EndTime string
Remark string
Desc string
CreatedAt string
UpdatedAt string
}
var FN struct {
AdvitiseInfo TagAdvitiseInfo
}
var TN struct {
AdvitiseInfo string
}
func init() {
FN.AdvitiseInfo.Id = "_id"
FN.AdvitiseInfo.Title = "title"
FN.AdvitiseInfo.FullScreenImgs = "fullScreenImgs"
FN.AdvitiseInfo.Banner720300Imgs = "banner720300Imgs"
FN.AdvitiseInfo.Banner720150Imgs = "banner720150Imgs"
FN.AdvitiseInfo.Banner750700Imgs = "banner750700Imgs"
FN.AdvitiseInfo.Banner800450Imgs = "banner800450Imgs"
FN.AdvitiseInfo.PopUps600800Imgs = "popUps600800Imgs"
FN.AdvitiseInfo.PopUps512512Imgs = "popUps512512Imgs"
FN.AdvitiseInfo.SmallCubeImgs = "smallCubeImgs"
FN.AdvitiseInfo.VerticalCubeImgs = "verticalCubeImgs"
FN.AdvitiseInfo.RandomNames = "randomNames"
FN.AdvitiseInfo.Link = "link"
FN.AdvitiseInfo.Status = "status"
FN.AdvitiseInfo.StartTime = "startTime"
FN.AdvitiseInfo.EndTime = "endTime"
FN.AdvitiseInfo.Remark = "remark"
FN.AdvitiseInfo.Desc = "desc"
FN.AdvitiseInfo.CreatedAt = "createdAt"
FN.AdvitiseInfo.UpdatedAt = "updatedAt"
TN.AdvitiseInfo = "advitise_info"
}
+35
View File
@@ -0,0 +1,35 @@
package advitiseInfoMod
import (
"stat/common/mongos"
"time"
)
var db *mongos.MongoTool
// AdvitiseInfo 广告素材(V2):素材本身与位置/绑定解耦,可复用到多个 app 的多个位置
// @table:advitise_info
type AdvitiseInfo struct {
Id int64 `json:"id" bson:"_id"`
Title string `json:"title" bson:"title"` // 广告标题
FullScreenImgs []string `json:"fullScreenImgs" bson:"fullScreenImgs"` // 全屏封面
Banner720300Imgs []string `json:"banner720300Imgs" bson:"banner720300Imgs"` // Banner720*300 封面
Banner720150Imgs []string `json:"banner720150Imgs" bson:"banner720150Imgs"` // Banner720*150 封面
Banner750700Imgs []string `json:"banner750700Imgs" bson:"banner750700Imgs"` // Banner750*700 封面
Banner800450Imgs []string `json:"banner800450Imgs" bson:"banner800450Imgs"` // Banner800*450 封面
PopUps600800Imgs []string `json:"popUps600800Imgs" bson:"popUps600800Imgs"` // 弹窗 600*800 封面
PopUps512512Imgs []string `json:"popUps512512Imgs" bson:"popUps512512Imgs"` // 弹窗 512*512 封面
SmallCubeImgs []string `json:"smallCubeImgs" bson:"smallCubeImgs"` // 小方块封面
VerticalCubeImgs []string `json:"verticalCubeImgs" bson:"verticalCubeImgs"` // 竖方块封面
RandomNames []string `json:"randomNames" bson:"randomNames"` // 随机名称
Link string `json:"link" bson:"link"` // 跳转链接
Status bool `json:"status" bson:"status"` // 是否激活
StartTime time.Time `json:"startTime" bson:"startTime"` // 开始时间
EndTime time.Time `json:"endTime" bson:"endTime"` // 结束时间
Remark string `json:"remark" bson:"remark"` // 备注
Desc string `json:"desc" bson:"desc"` // 描述
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
}
type AdvitiseInfoList []*AdvitiseInfo
+80
View File
@@ -0,0 +1,80 @@
package advitiseLocationMod
import (
"context"
"fmt"
"stat/common/log"
"stat/common/mongos"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
func Init(ctx context.Context) {
db = mongos.New(mongos.MgoManager, TN.AdvitiseLocation)
indexes := []mongo.IndexModel{
{
Keys: bson.D{
{Key: FN.AdvitiseLocation.AppId, Value: 1},
{Key: FN.AdvitiseLocation.LocId, Value: 1},
},
Options: options.Index().SetUnique(true),
},
{Keys: bson.D{{Key: FN.AdvitiseLocation.CreatedAt, Value: -1}}},
}
if _, err := db.CreateIndex(ctx, indexes); err != nil {
panic(fmt.Sprintf("(advitiseLocationMod:Init) CreateIndex failed: %+v", err))
}
}
func InsertOneAdvitiseLocation(ctx context.Context, doc *AdvitiseLocation) error {
if _, err := db.InsertOne(ctx, doc); err != nil {
log.Warn("advitiseLocationMod InsertOne failed", log.E(err))
return err
}
return nil
}
func FindOneAdvitiseLocation(ctx context.Context, filter bson.M) (*AdvitiseLocation, error) {
var doc AdvitiseLocation
if err := db.FindOne(ctx, &doc, filter); err != nil {
return nil, err
}
return &doc, nil
}
func DelOneAdvitiseLocation(ctx context.Context, filter bson.M) (int64, error) {
count, err := db.DeleteOne(ctx, filter)
if err != nil {
log.Warn("advitiseLocationMod DeleteOne failed", log.E(err), log.Any("filter", filter))
return 0, err
}
return count, nil
}
func UpdateOneAdvitiseLocation(ctx context.Context, filter bson.M, update bson.M) error {
if _, err := db.UpdateOne(ctx, filter, update); err != nil {
log.Warn("advitiseLocationMod UpdateOne failed", log.E(err), log.Any("filter", filter))
return err
}
return nil
}
func CountAdvitiseLocations(ctx context.Context, filter bson.M) (int64, error) {
count, err := db.Count(ctx, filter)
if err != nil {
log.Warn("advitiseLocationMod Count failed", log.E(err), log.Any("filter", filter))
return 0, err
}
return count, nil
}
func FindManyAdvitiseLocations(ctx context.Context, filter bson.M, opts ...*options.FindOptions) (AdvitiseLocationList, error) {
list := make(AdvitiseLocationList, 0)
if err := db.Find(ctx, &list, filter, opts...); err != nil {
log.Warn("advitiseLocationMod Find failed", log.E(err), log.Any("filter", filter))
return nil, err
}
return list, nil
}
+34
View File
@@ -0,0 +1,34 @@
package advitiseLocationMod
type TagAdvitiseLocation struct {
Id string
AppId string
LocId string
Name string
AdvitiseSize string
ImgSizeDesc string
Remark string
CreatedAt string
UpdatedAt string
}
var FN struct {
AdvitiseLocation TagAdvitiseLocation
}
var TN struct {
AdvitiseLocation string
}
func init() {
FN.AdvitiseLocation.Id = "_id"
FN.AdvitiseLocation.AppId = "appId"
FN.AdvitiseLocation.LocId = "locId"
FN.AdvitiseLocation.Name = "name"
FN.AdvitiseLocation.AdvitiseSize = "advitiseSize"
FN.AdvitiseLocation.ImgSizeDesc = "imgSizeDesc"
FN.AdvitiseLocation.Remark = "remark"
FN.AdvitiseLocation.CreatedAt = "createdAt"
FN.AdvitiseLocation.UpdatedAt = "updatedAt"
TN.AdvitiseLocation = "advitise_location"
}
+38
View File
@@ -0,0 +1,38 @@
package advitiseLocationMod
import (
"stat/common/mongos"
"time"
)
var db *mongos.MongoTool
type AdvitiseSizeType string
const (
AdvitiseSizeFullScreen AdvitiseSizeType = "全屏" // 全屏
AdvitiseSizeBanner720300 AdvitiseSizeType = "banner720*300" // Banner720*300
AdvitiseSizeBanner720150 AdvitiseSizeType = "banner720*150" // Banner720*150
AdvitiseSizeBanner750700 AdvitiseSizeType = "banner750*700" // Banner750*700
AdvitiseSizeBanner800450 AdvitiseSizeType = "banner800*450" // Banner800*450
AdvitiseSizePopUps600800 AdvitiseSizeType = "弹窗600*800" // 弹窗 600*800
AdvitiseSizePopUps512512 AdvitiseSizeType = "弹窗512*512" // 弹窗 512*512
AdvitiseSizeSmallCube AdvitiseSizeType = "小方块" // 小方块
AdvitiseSizeVerticalCube AdvitiseSizeType = "竖方块" // 竖方块
)
// AdvitiseLocation 广告位(V2):按 (appId, locId) 唯一
// @table:advitise_location
type AdvitiseLocation struct {
Id int64 `json:"id" bson:"_id"`
AppId int32 `json:"appId" bson:"appId"` // 应用id
LocId int32 `json:"locId" bson:"locId"` // 位置id,外部使用
Name string `json:"name" bson:"name"` // 位置名称
AdvitiseSize AdvitiseSizeType `json:"advitiseSize" bson:"advitiseSize"` // 尺寸类型
ImgSizeDesc string `json:"imgSizeDesc" bson:"imgSizeDesc"` // 图片描述
Remark string `json:"remark" bson:"remark"` // 备注
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
}
type AdvitiseLocationList []*AdvitiseLocation
+10
View File
@@ -9,6 +9,11 @@ import (
"stat/models/advertiseLocMod"
"stat/models/advertiseMod"
"stat/models/advertisestat"
"stat/models/advitiseBindMod"
"stat/models/advitiseClickStatMod"
"stat/models/advitiseDayClickStatMod"
"stat/models/advitiseInfoMod"
"stat/models/advitiseLocationMod"
"stat/models/announInfoMod"
"stat/models/appInfoMod"
"stat/models/appcategory"
@@ -109,4 +114,9 @@ func InitIndex() {
applicationLocMod.Init(ctx)
channelpackage.Init(ctx)
cpcDownloadCfg.Init(ctx)
advitiseInfoMod.Init(ctx)
advitiseLocationMod.Init(ctx)
advitiseBindMod.Init(ctx)
advitiseClickStatMod.Init(ctx)
advitiseDayClickStatMod.Init(ctx)
}
+40
View File
@@ -0,0 +1,40 @@
package router
import (
"stat/api/advitiseV2Api"
"github.com/gin-gonic/gin"
)
func advitiseV2Router(router *gin.RouterGroup) {
v2 := router.Group("/advitise/v2")
{
info := v2.Group("/info")
info.POST("/list", advitiseV2Api.InfoList)
info.POST("/add", advitiseV2Api.InfoAdd)
info.POST("/update", advitiseV2Api.InfoUpdate)
info.POST("/del", advitiseV2Api.InfoDel)
loc := v2.Group("/location")
loc.POST("/list", advitiseV2Api.LocationList)
loc.POST("/add", advitiseV2Api.LocationAdd)
loc.POST("/update", advitiseV2Api.LocationUpdate)
loc.POST("/del", advitiseV2Api.LocationDel)
bind := v2.Group("/bind")
bind.POST("/list", advitiseV2Api.BindList)
bind.POST("/add", advitiseV2Api.BindAdd)
bind.POST("/update", advitiseV2Api.BindUpdate)
bind.POST("/del", advitiseV2Api.BindDel)
// 外部无鉴权接口(需与白名单一致)
v2.POST("/get", advitiseV2Api.GetAdvitises)
v2.POST("/click", advitiseV2Api.ClickAdvitise)
// 统计管理(后台)
v2.POST("/day/click/list", advitiseV2Api.DayClickList)
v2.POST("/day/click/info", advitiseV2Api.DayClickInfo)
v2.POST("/click/list", advitiseV2Api.ClickList)
v2.POST("/day/click/summary", advitiseV2Api.ClickSummary)
}
}
+2 -1
View File
@@ -103,10 +103,11 @@ func registerRouter(router *gin.Engine) {
applicationRouter(rootRouter) // 配置app应用
appcategoryRouter(rootRouter) // app分类
userInviteRouter(rootRouter) // 兼容
advitiseV2Router(rootRouter) // 广告管理V2(素材/位置/绑定)
}
// 双端下载落地页(无需登陆鉴权)
router.GET("/rsdownload/:appId/:channel", versionApi.CpcDownload)
router.GET("/download/:appId/:channel", versionApi.CpcDownload)
}
func setGinSwagger(router *gin.Engine) {
+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
}
+3 -2
View File
@@ -51,8 +51,9 @@ type GlobalConfig struct {
ManhuaPackageUrl string `json:"manhuaPackageUrl"`
} `json:"url"`
ProxyUrl struct {
File string `json:"file"`
FileRes string `json:"fileRes"`
File string `json:"file"`
FileRes string `json:"fileRes"`
FileRes1 string `json:"fileRes1"`
} `json:"proxyUrl"`
KafkaServer bool `json:"kafkaServer"`
Host string `json:"host"`