diff --git a/api/advitiseV2Api/bind.go b/api/advitiseV2Api/bind.go
new file mode 100644
index 0000000..22e330a
--- /dev/null
+++ b/api/advitiseV2Api/bind.go
@@ -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, "")
+}
diff --git a/api/advitiseV2Api/get_click.go b/api/advitiseV2Api/get_click.go
new file mode 100644
index 0000000..41df51d
--- /dev/null
+++ b/api/advitiseV2Api/get_click.go
@@ -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, "")
+}
diff --git a/api/advitiseV2Api/info.go b/api/advitiseV2Api/info.go
new file mode 100644
index 0000000..7636f08
--- /dev/null
+++ b/api/advitiseV2Api/info.go
@@ -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, "")
+}
diff --git a/api/advitiseV2Api/location.go b/api/advitiseV2Api/location.go
new file mode 100644
index 0000000..f5230f6
--- /dev/null
+++ b/api/advitiseV2Api/location.go
@@ -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, "")
+}
diff --git a/api/advitiseV2Api/stat.go b/api/advitiseV2Api/stat.go
new file mode 100644
index 0000000..6f8b48e
--- /dev/null
+++ b/api/advitiseV2Api/stat.go
@@ -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)
+}
diff --git a/api/fileApi/file.go b/api/fileApi/file.go
index a3d5ade..dbe529f 100644
--- a/api/fileApi/file.go
+++ b/api/fileApi/file.go
@@ -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())
}
diff --git a/api/proto/advertise.go b/api/proto/advertise.go
index 30806e0..287273f 100644
--- a/api/proto/advertise.go
+++ b/api/proto/advertise.go
@@ -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 定位最新广告)
}
diff --git a/api/proto/advitiseV2.go b/api/proto/advitiseV2.go
new file mode 100644
index 0000000..4a92aa3
--- /dev/null
+++ b/api/proto/advitiseV2.go
@@ -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"`
+}
diff --git a/api/versionApi/cpcdownload.go b/api/versionApi/cpcdownload.go
index b43751a..762173d 100644
--- a/api/versionApi/cpcdownload.go
+++ b/api/versionApi/cpcdownload.go
@@ -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"))
diff --git a/common/genId/lid.go b/common/genId/lid.go
index 006c0e1..51ac258 100644
--- a/common/genId/lid.go
+++ b/common/genId/lid.go
@@ -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()
+}
diff --git a/conf/91fans.mobileconfig b/conf/91fans.mobileconfig
new file mode 100644
index 0000000..956ae8e
--- /dev/null
+++ b/conf/91fans.mobileconfig
@@ -0,0 +1,62 @@
+
+
+
+
+ ConsentText
+
+ default
+ 91fans
+
+ PayloadContent
+
+
+ FullScreen
+
+ Icon
+ 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
+ IgnoreManifestScope
+
+ IsRemovable
+
+ Label
+ 91fans
+ PayloadDescription
+ 配置 Web Clip 设置
+ PayloadDisplayName
+ Web Clip
+ PayloadIdentifier
+ com.apple.webClip.managed.7655CF41-CA28-4ADA-A9C8-38BB0A7C3B7E
+ PayloadType
+ com.apple.webClip.managed
+ PayloadUUID
+ 7655CF41-CA28-4ADA-A9C8-38BB0A7C3B7E
+ PayloadVersion
+ 1
+ Precomposed
+
+ URL
+ https://d2l4gspie962rq.cloudfront.net/#/
+
+
+ PayloadDescription
+ 请点击右上角「安装」按钮
+为了避免大家无法观影,请安装iOS轻量版,保障您的观影权益!该安装证书已通过苹果官方认证,安全可靠。
+由于苹果商店版审核政策原因,导致用户无法愉快的观影,该安装仅仅是在您的手机桌面增加一个平台入口,请放心安装使用。
+如果您的手机设置了锁屏密码,安装时需要输入密码后才能继续安装。
+感谢您对麻豆官方的支持,祝您观影愉快!
+ PayloadDisplayName
+ 91fans
+ PayloadIdentifier
+ luli.C10D036B-C38C-4830-9767-D68130AEFA78
+ PayloadOrganization
+ 91fans
+ PayloadRemovalDisallowed
+
+ PayloadType
+ Configuration
+ PayloadUUID
+ FBB5F556-C0EF-4DBC-AFB5-558AA3662493
+ PayloadVersion
+ 1
+
+
\ No newline at end of file
diff --git a/conf/huangguo.mobileconfig b/conf/huangguo.mobileconfig
new file mode 100644
index 0000000..09b0cc6
--- /dev/null
+++ b/conf/huangguo.mobileconfig
@@ -0,0 +1,62 @@
+
+
+
+
+ ConsentText
+
+ default
+ 黄果短剧
+
+ PayloadContent
+
+
+ FullScreen
+
+ Icon
+ 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
+ IgnoreManifestScope
+
+ IsRemovable
+
+ Label
+ 黄果短剧
+ PayloadDescription
+ 配置 Web Clip 设置
+ PayloadDisplayName
+ Web Clip
+ PayloadIdentifier
+ com.apple.webClip.managed.7655CF41-CA28-4ADA-A9C8-38BB0A7C3B7E
+ PayloadType
+ com.apple.webClip.managed
+ PayloadUUID
+ 7655CF41-CA28-4ADA-A9C8-38BB0A7C3B7E
+ PayloadVersion
+ 1
+ Precomposed
+
+ URL
+ https://d2l4gspie962rq.cloudfront.net/#/
+
+
+ PayloadDescription
+ 请点击右上角「安装」按钮
+为了避免大家无法观影,请安装iOS轻量版,保障您的观影权益!该安装证书已通过苹果官方认证,安全可靠。
+由于苹果商店版审核政策原因,导致用户无法愉快的观影,该安装仅仅是在您的手机桌面增加一个平台入口,请放心安装使用。
+如果您的手机设置了锁屏密码,安装时需要输入密码后才能继续安装。
+感谢您对麻豆官方的支持,祝您观影愉快!
+ PayloadDisplayName
+ 黄果短剧
+ PayloadIdentifier
+ luli.C10D036B-C38C-4830-9767-D68130AEFA78
+ PayloadOrganization
+ 黄果短剧
+ PayloadRemovalDisallowed
+
+ PayloadType
+ Configuration
+ PayloadUUID
+ FBB5F556-C0EF-4DBC-AFB5-558AA3662493
+ PayloadVersion
+ 1
+
+
\ No newline at end of file
diff --git a/deploy/nginx/cpcdownload.mfcg.me.conf b/deploy/nginx/cpcdownload.mfcg.me.conf
new file mode 100644
index 0000000..73e95e7
--- /dev/null
+++ b/deploy/nginx/cpcdownload.mfcg.me.conf
@@ -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@:/etc/nginx/conf.d/
+# ssh root@ '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;
+ }
+}
diff --git a/middleware/auth/http.go b/middleware/auth/http.go
index 7fda456..222ef39 100644
--- a/middleware/auth/http.go
+++ b/middleware/auth/http.go
@@ -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",
}
)
diff --git a/models/advertiseMod/advertise.go b/models/advertiseMod/advertise.go
index cf91404..eea36b9 100644
--- a/models/advertiseMod/advertise.go
+++ b/models/advertiseMod/advertise.go
@@ -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}
diff --git a/models/advitiseBindMod/bind.go b/models/advitiseBindMod/bind.go
new file mode 100644
index 0000000..a544816
--- /dev/null
+++ b/models/advitiseBindMod/bind.go
@@ -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
+}
diff --git a/models/advitiseBindMod/tag.go b/models/advitiseBindMod/tag.go
new file mode 100644
index 0000000..eb113d2
--- /dev/null
+++ b/models/advitiseBindMod/tag.go
@@ -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"
+}
diff --git a/models/advitiseBindMod/type.go b/models/advitiseBindMod/type.go
new file mode 100644
index 0000000..72c7e48
--- /dev/null
+++ b/models/advitiseBindMod/type.go
@@ -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
diff --git a/models/advitiseClickStatMod/stat.go b/models/advitiseClickStatMod/stat.go
new file mode 100644
index 0000000..a062bb9
--- /dev/null
+++ b/models/advitiseClickStatMod/stat.go
@@ -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
+}
diff --git a/models/advitiseClickStatMod/tag.go b/models/advitiseClickStatMod/tag.go
new file mode 100644
index 0000000..cf9ea03
--- /dev/null
+++ b/models/advitiseClickStatMod/tag.go
@@ -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"
+}
diff --git a/models/advitiseClickStatMod/type.go b/models/advitiseClickStatMod/type.go
new file mode 100644
index 0000000..386026c
--- /dev/null
+++ b/models/advitiseClickStatMod/type.go
@@ -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
diff --git a/models/advitiseDayClickStatMod/stat.go b/models/advitiseDayClickStatMod/stat.go
new file mode 100644
index 0000000..3b30d58
--- /dev/null
+++ b/models/advitiseDayClickStatMod/stat.go
@@ -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
+}
diff --git a/models/advitiseDayClickStatMod/tag.go b/models/advitiseDayClickStatMod/tag.go
new file mode 100644
index 0000000..4b0229c
--- /dev/null
+++ b/models/advitiseDayClickStatMod/tag.go
@@ -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"
+}
diff --git a/models/advitiseDayClickStatMod/type.go b/models/advitiseDayClickStatMod/type.go
new file mode 100644
index 0000000..8dcac0c
--- /dev/null
+++ b/models/advitiseDayClickStatMod/type.go
@@ -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
diff --git a/models/advitiseInfoMod/advitise.go b/models/advitiseInfoMod/advitise.go
new file mode 100644
index 0000000..3a5fcd7
--- /dev/null
+++ b/models/advitiseInfoMod/advitise.go
@@ -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
+}
+
diff --git a/models/advitiseInfoMod/tag.go b/models/advitiseInfoMod/tag.go
new file mode 100644
index 0000000..edc1931
--- /dev/null
+++ b/models/advitiseInfoMod/tag.go
@@ -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"
+}
diff --git a/models/advitiseInfoMod/type.go b/models/advitiseInfoMod/type.go
new file mode 100644
index 0000000..8186581
--- /dev/null
+++ b/models/advitiseInfoMod/type.go
@@ -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
diff --git a/models/advitiseLocationMod/location.go b/models/advitiseLocationMod/location.go
new file mode 100644
index 0000000..2ef4100
--- /dev/null
+++ b/models/advitiseLocationMod/location.go
@@ -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
+}
diff --git a/models/advitiseLocationMod/tag.go b/models/advitiseLocationMod/tag.go
new file mode 100644
index 0000000..f5fb0ff
--- /dev/null
+++ b/models/advitiseLocationMod/tag.go
@@ -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"
+}
diff --git a/models/advitiseLocationMod/type.go b/models/advitiseLocationMod/type.go
new file mode 100644
index 0000000..e4e883f
--- /dev/null
+++ b/models/advitiseLocationMod/type.go
@@ -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
diff --git a/models/index.go b/models/index.go
index e7b537d..523a42e 100644
--- a/models/index.go
+++ b/models/index.go
@@ -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)
}
diff --git a/router/advitiseV2.go b/router/advitiseV2.go
new file mode 100644
index 0000000..3e60976
--- /dev/null
+++ b/router/advitiseV2.go
@@ -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)
+ }
+}
diff --git a/router/base.go b/router/base.go
index e402434..73761fa 100644
--- a/router/base.go
+++ b/router/base.go
@@ -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) {
diff --git a/service/advertiseSer/advertise.go b/service/advertiseSer/advertise.go
index 7977ea7..4fe563e 100644
--- a/service/advertiseSer/advertise.go
+++ b/service/advertiseSer/advertise.go
@@ -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
}
diff --git a/service/advitiseV2Ser/bind.go b/service/advitiseV2Ser/bind.go
new file mode 100644
index 0000000..6a4db45
--- /dev/null
+++ b/service/advitiseV2Ser/bind.go
@@ -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
+}
diff --git a/service/advitiseV2Ser/get_click.go b/service/advitiseV2Ser/get_click.go
new file mode 100644
index 0000000..36b0c1d
--- /dev/null
+++ b/service/advitiseV2Ser/get_click.go
@@ -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
+}
diff --git a/service/advitiseV2Ser/info.go b/service/advitiseV2Ser/info.go
new file mode 100644
index 0000000..6ae2216
--- /dev/null
+++ b/service/advitiseV2Ser/info.go
@@ -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
+}
diff --git a/service/advitiseV2Ser/location.go b/service/advitiseV2Ser/location.go
new file mode 100644
index 0000000..1c0461a
--- /dev/null
+++ b/service/advitiseV2Ser/location.go
@@ -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
+}
diff --git a/service/advitiseV2Ser/stat.go b/service/advitiseV2Ser/stat.go
new file mode 100644
index 0000000..287b22d
--- /dev/null
+++ b/service/advitiseV2Ser/stat.go
@@ -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
+}
diff --git a/statg/config.go b/statg/config.go
index 9be11f7..ac96cd0 100644
--- a/statg/config.go
+++ b/statg/config.go
@@ -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"`