From b5b213f1133c7775c414f7989923f2049d031874 Mon Sep 17 00:00:00 2001 From: zhora Date: Tue, 28 Oct 2025 15:02:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8A=95=E6=94=BE=E7=BB=9F=E8=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- call/config.go | 10 + modules/backend/app/response.go | 81 +++- modules/backend/bdb/mysql.go | 2 + .../handler/advertisement/advertisement.go | 395 ++++++++++++++++++ modules/backend/routers/routers.go | 1 + .../backend/routers/routers_advertisement.go | 14 + modules/backend/values/ad.go | 21 + modules/backend/values/tables.go | 48 +++ 8 files changed, 565 insertions(+), 7 deletions(-) create mode 100644 modules/backend/handler/advertisement/advertisement.go create mode 100644 modules/backend/routers/routers_advertisement.go create mode 100644 modules/backend/values/ad.go diff --git a/call/config.go b/call/config.go index 127fcb8..361d602 100644 --- a/call/config.go +++ b/call/config.go @@ -2728,3 +2728,13 @@ func GetConfigRtpTemplateId() string { } return "" } + +func GetChannelListByShow(show int) []*common.Channel { + ret := []*common.Channel{} + for _, v := range channels { + if v.Show == show { + ret = append(ret, v) + } + } + return ret +} diff --git a/modules/backend/app/response.go b/modules/backend/app/response.go index 87d825c..639e75b 100644 --- a/modules/backend/app/response.go +++ b/modules/backend/app/response.go @@ -6,6 +6,7 @@ import ( "net/http" "reflect" "server/db" + mdb "server/db/mysql" "server/modules/backend/bdb" "server/modules/backend/values" "strings" @@ -23,9 +24,10 @@ type Gin struct { } type R struct { - Data interface{} `json:"data"` - Msg string `json:"msg"` - Code int `json:"code"` + Data interface{} `json:"data"` + Msg string `json:"msg"` + Code int `json:"code"` + DB *mdb.MysqlClient `json:"-"` } func NewApp(c *gin.Context) *Gin { @@ -146,7 +148,11 @@ func (g *Gin) MUpdateAll(updates []map[string]interface{}, element interface{}) reft := reflect.TypeOf(tmp).Elem() id := reflect.ValueOf(tmp).Elem().FieldByName("ID").Int() if id == 0 { - if err = db.Mysql().Create(tmp); err != nil { + db := db.Mysql() + if g.DB != nil { + db = g.DB + } + if err = db.Create(tmp); err != nil { g.Code = values.CodeRetry return } @@ -169,7 +175,11 @@ func (g *Gin) MUpdateAll(updates []map[string]interface{}, element interface{}) // log.Debug("element:%v", element) log.Debug("update:%v", update) // log.Debug("model:%v", model) - if err = db.Mysql().Update(model.Interface(), update); err != nil { + db := db.Mysql() + if g.DB != nil { + db = g.DB + } + if err = db.Update(model.Interface(), update); err != nil { g.Code = values.CodeRetry return } @@ -182,12 +192,16 @@ func (g *Gin) MUpdateAll(updates []map[string]interface{}, element interface{}) func (g *Gin) MDel(id int, element interface{}) (pass bool) { tmp := reflect.New(reflect.TypeOf(element).Elem()) tmp.Elem().FieldByName("ID").SetInt(int64(id)) - if !db.Mysql().Exist(tmp.Interface()) { + db := db.Mysql() + if g.DB != nil { + db = g.DB + } + if !db.Exist(tmp.Interface()) { g.Code = values.CodeParam g.Msg = "该id不存在" return } - if err := db.Mysql().Del(tmp.Interface()); err != nil { + if err := db.Del(tmp.Interface()); err != nil { g.Code = values.CodeRetry return } @@ -264,3 +278,56 @@ func (g *Gin) CheckPower(power string) bool { // } // g.Data = resp //} + +// 从数据库中读取数据 +func (g *Gin) MGetSqlAll(model, tar interface{}, condition map[string]interface{}, page, pageSize int) (count int64, pass bool) { + c := g.DB + if c == nil { + c = db.Mysql() + } + sql, _ := values.CheckQueryControl(model, nil) + mod := reflect.TypeOf(model).Elem() + for k, v := range condition { + tmp, ok := mod.FieldByName(k) + if !ok { + log.Error("invalid model:%v,condition:%v", model, condition) + continue + } + tag := tmp.Tag.Get("web") + if tag == "" || tag == "-" { + log.Error("invalid model:%v,condition:%v", model, condition) + continue + } + reft := reflect.TypeOf(v) + ref := reflect.ValueOf(v) + if reft.Kind() == reflect.Slice { + if ref.Len() != 2 { + log.Error("invalid model:%v,condition:%v", model, condition) + continue + } + if len(sql) > 0 { + sql += " and " + } + sql += fmt.Sprintf("%s >= %v and %s <= %v", tag, ref.Index(0).Interface(), tag, ref.Index(1).Interface()) + } else { + if len(sql) > 0 { + sql += " and " + } + sql += fmt.Sprintf("%s = %v", tag, ref.Interface()) + } + } + var err error + if page != 0 && pageSize != 0 { + count, err = c.QueryList(page-1, pageSize, sql, "", model, tar) + } else { + count, err = c.QueryAll(sql, "", model, tar) + } + if err != nil { + log.Error("err:%v", err) + g.Code = values.CodeRetry + g.R.Msg = err.Error() + return + } + pass = true + return +} diff --git a/modules/backend/bdb/mysql.go b/modules/backend/bdb/mysql.go index b6a31de..a04232f 100644 --- a/modules/backend/bdb/mysql.go +++ b/modules/backend/bdb/mysql.go @@ -25,6 +25,8 @@ func MigrateDB() { new(values.ReviewData), new(values.FirstPageData), new(values.PlatformData), + new(values.ADConfig), + new(values.ADStats), ) if err != nil { panic(err) diff --git a/modules/backend/handler/advertisement/advertisement.go b/modules/backend/handler/advertisement/advertisement.go new file mode 100644 index 0000000..c0ae016 --- /dev/null +++ b/modules/backend/handler/advertisement/advertisement.go @@ -0,0 +1,395 @@ +package handler + +import ( + "encoding/json" + "fmt" + "server/call" + "server/common" + "server/modules/backend/app" + "server/modules/backend/bdb" + "server/modules/backend/models" + "server/modules/backend/util" + "server/modules/backend/values" + "strings" + "time" + + utils "server/util" + + "github.com/gin-gonic/gin" + "github.com/liangdas/mqant/log" + "gorm.io/gorm" +) + +var ( + RechargerFee int64 = 10 + WithdrawFee int64 = 5 + ChangePer int64 = 95 +) + +// ADConfig ad配置 +func ADConfig(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + a.DB = bdb.BackDB + path := c.Request.URL.Path + path = strings.ReplaceAll(path, "/advertisement/config/", "") + all := strings.Split(path, "/") + if len(all) > 1 || len(all) == 0 { + a.Code = values.CodeRetry + return + } + opt := all[0] + element := &values.ADConfig{} + list := &[]values.ADConfig{} + var resp interface{} + if opt == "list" { + req := &values.GMConfigCommonListReq{ + Condition: map[string]interface{}{}, + } + a.S(req) + count, pass := a.MGetSqlAll(element, list, req.Condition, req.Page, req.PageSize) + if !pass { + return + } + resp = values.GMConfigCommonListResp{ + Config: list, + Total: count, + } + } else if opt == "edit" { + req := new(values.GMConfigCommonEditReq) + if !a.S(req) { + return + } + if !a.MUpdateAll(req.Config, element) { + return + } + } else if opt == "del" { + req := new(values.GMConfigCommonDelReq) + if !a.S(req) { + return + } + if !a.MDel(req.ID, element) { + return + } + } + a.Data = resp +} + +func AdvertisementStats(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ADReq) + if !a.S(req) { + return + } + su, eu := util.GetQueryUnix(req.Start, req.End) + now := utils.GetZeroTime(time.Now()).Unix() + if su >= now { // 不允许查今天 + a.Code = values.CodeParam + a.Msg = "只能查今天之前的数据" + return + } + if eu >= now { + eu = now + } + + resp := &values.ADResp{} + a.Data = resp + resp.Count = (eu - su) / (24 * 60 * 60) + cids := []*int{} + // isSC := len(a.User.SChannels) > 0 + if len(req.ChannelID) > 0 { + cids = req.ChannelID + } else if req.GroupID > 0 { + adConfig := &values.ADConfig{ID: req.GroupID} + bdb.BackDB.Get(adConfig) + channels := []int{} + json.Unmarshal([]byte(adConfig.Channels), &channels) + if len(channels) == 0 { + a.Code = values.CodeParam + a.Msg = "该投放组未分配包" + return + } + for _, v := range channels { + one := v + cids = append(cids, &one) + } + } + // else if isSC { + // for i := range a.User.SChannels { + // cids = append(cids, &a.User.SChannels[i]) + // } + // } + // 如果是查一天的情况,罗列所有包 + if resp.Count == 1 { + if len(cids) == 1 { + one := &values.ADStats{Time: su, ChannelID: *cids[0]} + bdb.BackDB.Get(one) + if one.ID == 0 { + one = GetOneAD(su, su+24*60*60, cids) + if one.Time != now { + bdb.BackDB.Create(one) + } + } + CalStats(one) + resp.List = append(resp.List, *one) + one.ChannelID = 0 + resp.Total = *one + return + } + if len(cids) == 0 { + channels := call.GetChannelListByShow(2) + for _, v := range channels { + cids = append(cids, &v.ChannelID) + } + } + list := []*values.ADStats{} + bdb.BackDB.QueryAll(fmt.Sprintf("time = %d", su), "", &values.ADStats{}, &list) + + for _, v := range cids { + var one *values.ADStats + for _, j := range list { + if j.ChannelID == *v { + one = j + break + } + } + if one == nil { + one = GetOneAD(su, su+24*60*60, []*int{v}) + if one.Time != now { + bdb.BackDB.Create(one) + } + } + CalStats(one) + resp.List = append(resp.List, *one) + } + total := &values.ADStats{} + for _, v := range resp.List { + CalTotal(total, &v) + } + CalStats(total) + resp.Total = *total + return + } + + listLen := req.Num + if listLen > int(resp.Count) { + listLen = int(resp.Count) + } + resp.List = make([]values.ADStats, listLen) + list := []values.ADStats{} + sql := fmt.Sprintf("time >= %d and time < %d", su, eu) + if len(cids) == 0 { + sql += " and channel_id = 0" + } else if len(cids) == 1 { + sql += fmt.Sprintf(" and channel_id = %d", *cids[0]) + } + bdb.BackDB.QueryAll(sql, "time desc", &values.ADStats{}, &list) + + index := -1 + start := eu - 24*60*60 - int64((req.Page-1)*req.Num)*24*60*60 + end := start - int64(req.Num-1)*24*60*60 + if end < su { + end = su + } + for i := start; i >= end; i -= 24 * 60 * 60 { + index++ + total := &values.ADStats{} + if len(cids) == 0 { + find := false + for _, v := range list { + if v.Time != i { + continue + } + find = true + CalTotal(total, &v) + break + } + if !find { + one := GetOneAD(i, i+24*60*60, nil) + if one.Time != now { + bdb.BackDB.Create(one) + } + CalTotal(total, one) + } + } else { + for _, cid := range cids { + find := false + for _, v := range list { + if v.ChannelID != *cid { + continue + } + if v.Time != i { + continue + } + find = true + CalTotal(total, &v) + if len(cids) == 1 { + total.ID = v.ID + } + break + } + if !find { + one := GetOneAD(i, i+24*60*60, []*int{cid}) + if one.Time != now { + bdb.BackDB.Create(one) + } + CalTotal(total, one) + if len(cids) == 1 { + total.ID = one.ID + } + } + } + } + if len(cids) == 1 { + total.ChannelID = *cids[0] + } + CalStats(total) + resp.List[index] = *total + } + if index < listLen-1 { + resp.List = resp.List[:index+1] + } +} + +func GetOneAD(start, end int64, cids []*int) *values.ADStats { + one := &values.ADStats{ + Time: start, + Date: time.Unix(start, 0).Format("20060102"), + } + if len(cids) == 0 { + one.Cost = bdb.BackDB.Sum(&values.ADStats{}, fmt.Sprintf("channel_id != 0 and time = %d", one.Time), "cost") + one.TotalCost = bdb.BackDB.Sum(&values.ADStats{}, fmt.Sprintf("channel_id != 0 and time <= %d", one.Time), "cost") + } else if len(cids) == 1 { + one.ChannelID = *cids[0] + one.TotalCost = bdb.BackDB.Sum(&values.ADStats{}, fmt.Sprintf("channel_id = %d and time < %d", one.ChannelID, one.Time), "cost") + } + // 需要查的数据 + // 新增人数 + one.NewPlayers = models.GetNewPlayerCountBySqls(start, end, cids...) + // 总用户 + oldPlayers := models.GetOldPlayerCountBySqls(start, end, cids...) + one.TotalPlayers = one.NewPlayers + oldPlayers + // 新增付费人数 + one.NewPayPlayers = models.GetNewPayCountBySqls(start, end, cids...) + // 新增付费金额 + one.NewPay = models.GetNewPayAmountBySqls(start, end, common.CurrencyINR, cids...) + // 活跃付费金额 + one.ActivePay = models.GetOldPayAmountBySqls(start, end, common.CurrencyINR, cids...) + // 活跃付费人数 + one.ActivePayPlayers = models.GetOldPayCountBySqls(start, end, cids...) + // 总付费 + one.TotalPay = one.NewPay + one.ActivePay + // 总退出 + one.TotalWithdraw = models.GetWithdrawAmountTotalBySQLs(start, end, common.CurrencyINR, cids...) + + one.NewRegister = int(models.GetNewPlayerCountBySqls(start, end, cids...)) + one.ActiveDevice = int(models.GetDownloadCounts(&start, &end, cids...)) + one.NewWithdraw = models.GetNewWithdrawAmount(start, end, cids...) + oldActiveUser := models.GetOldPlayerCountBySqls(start, end, cids...) + one.ActiveUser = one.NewRegister + int(oldActiveUser) + newPayCount := models.GetNewPayCountBySqls(start, end, cids...) + oldPayCount := models.GetOldPayCountBySqls(start, end, cids...) + one.RechargeUser = int(newPayCount + oldPayCount) + // 以下为直接计算出来的数值 + + // 当日收益=当日充值金额*0.92-当日退出*1.05 + one.Profit = one.TotalPay*(100-RechargerFee)/100 - one.TotalWithdraw*(100+WithdrawFee)/100 + // 历史总收益=总充值*0.92-总退出*1.05 + totalPay := models.GetAmountTotalBySQLs(0, end, cids...) + totalWithdraw := models.GetWithdrawAmountTotalBySQLs(0, end, common.CurrencyINR, cids...) + one.TotalProfit = totalPay*(100-RechargerFee)/100 - totalWithdraw*(100+WithdrawFee)/100 + + // one.WithdrawPer = util.GetPer(one.TotalWithdraw, one.TotalPay) + + // one.NewPayPer = util.GetPer(one.NewPayPlayers, one.NewPlayers) + // one.NewPayUnit = util.GetPoint(one.NewPay, one.NewPlayers) + // one.ARPU = util.GetPoint(one.ActivePay, one.ActivePayPlayers) + return one +} + +// 修改投入 +func AdvertisementEdit(c *gin.Context) { + a := app.NewApp(c) + defer func() { + a.Response() + }() + req := new(values.ADEditReq) + if !a.S(req) { + return + } + if req.Cost < 0 { + a.Code = values.CodeParam + a.Msg = "消耗不能小于0" + return + } + one := &values.ADStats{ID: req.ID} + err := bdb.BackDB.Get(one) + if err != nil { + log.Error("err:%v", err) + a.Code = values.CodeParam + return + } + diff := req.Cost - one.Cost + if diff == 0 { + a.Code = values.CodeParam + a.Msg = "消耗无修改" + return + } + + one.Cost = req.Cost + + // one.NewCost = util.GetPoint(one.Cost, one.NewPlayers*100) + // one.NewPayCost = util.GetPoint(one.Cost, one.NewPayPlayers*100) + // one.NewROI = util.GetPer(one.Profit*100, one.Cost*93) + // one.NewPayROI = util.GetPer(one.NewPay*100, one.Cost*93) + + // totalCost := bdb.BackDB.Sum(&values.ADStats{}, fmt.Sprintf("channel_id = %d and time < %d", one.ChannelID, one.Time), "cost") + // totalCost += one.Cost + // one.TotalROI = util.GetPer(one.TotalProfit*100, totalCost*93) + + bdb.BackDB.Update(&values.ADStats{ID: req.ID}, map[string]interface{}{"cost": one.Cost}) + bdb.BackDB.UpdateW(&values.ADStats{}, map[string]interface{}{"cost": gorm.Expr("cost + ?", diff)}, + fmt.Sprintf("channel_id = 0 and time = %d", one.Time)) + bdb.BackDB.UpdateW(&values.ADStats{}, map[string]interface{}{"total_cost": gorm.Expr("total_cost + ?", diff)}, + fmt.Sprintf("(channel_id = %d or channel_id = 0) and time >= %d", one.ChannelID, one.Time)) +} + +func CalTotal(total, one *values.ADStats) { + total.Time = one.Time + total.Date = one.Date + total.Cost += one.Cost + total.NewPlayers += one.NewPlayers + total.TotalPlayers += one.TotalPlayers + total.NewPayPlayers += one.NewPayPlayers + total.NewPay += one.NewPay + total.ActivePay += one.ActivePay + total.ActivePayPlayers += one.ActivePayPlayers + total.TotalPay += one.TotalPay + total.TotalWithdraw += one.TotalWithdraw + total.Profit += one.Profit + total.TotalProfit += one.TotalProfit + total.TotalCost += one.TotalCost + total.NewRegister += one.NewRegister + total.ActiveDevice += one.ActiveDevice + total.NewWithdraw += one.NewWithdraw + total.ActiveUser += one.ActiveUser + total.RechargeUser += one.RechargeUser +} + +// 通过自己数据计算相应收益/roi等数据 +func CalStats(one *values.ADStats) { + one.NewCost = util.GetPoint(one.Cost, one.NewPlayers) + one.NewPayCost = util.GetPoint(one.Cost, one.NewPayPlayers) + one.NewPayPer = util.GetPer(one.NewPayPlayers, one.NewPlayers) + one.NewPayUnit = util.GetPoint(one.NewPay, one.NewPlayers) + one.ARPU = util.GetPoint(one.ActivePay, one.ActivePayPlayers) + one.WithdrawPer = util.GetPer(one.TotalWithdraw, one.TotalPay) + one.NewROI = util.GetPer(one.Profit*100, one.Cost*ChangePer) + one.TotalROI = util.GetPer(one.TotalProfit*100, one.TotalCost*ChangePer) + one.NewPayROI = util.GetPer(one.NewPay*100, one.Cost*ChangePer) +} diff --git a/modules/backend/routers/routers.go b/modules/backend/routers/routers.go index 7178780..8ae3999 100644 --- a/modules/backend/routers/routers.go +++ b/modules/backend/routers/routers.go @@ -47,5 +47,6 @@ func SetUpRouter() *gin.Engine { firstPage(r) notice(r) tgrobot(r) + advertisement(r) return r } diff --git a/modules/backend/routers/routers_advertisement.go b/modules/backend/routers/routers_advertisement.go new file mode 100644 index 0000000..e180885 --- /dev/null +++ b/modules/backend/routers/routers_advertisement.go @@ -0,0 +1,14 @@ +// 账号相关的接口 +package routers + +import ( + handler "server/modules/backend/handler/advertisement" + + "github.com/gin-gonic/gin" +) + +func advertisement(e *gin.Engine) { + e.POST("/advertisement/config/*action", handler.ADConfig) + e.POST("/advertisement/list", handler.AdvertisementStats) + e.POST("/advertisement/edit", handler.AdvertisementEdit) +} diff --git a/modules/backend/values/ad.go b/modules/backend/values/ad.go new file mode 100644 index 0000000..99ddbf8 --- /dev/null +++ b/modules/backend/values/ad.go @@ -0,0 +1,21 @@ +package values + +type ADReq struct { + Start string `json:"Start"` + End string `json:"End"` + Page int `json:"Page" binding:"required"` + Num int `json:"Num" binding:"required"` + ChannelID []*int `json:"ChannelID"` + GroupID int `json:"GroupID"` // 投放组ID +} + +type ADResp struct { + Count int64 + List []ADStats + Total ADStats // 当查的是一天的数据时,改值为当天汇总数据 +} + +type ADEditReq struct { + ID int `json:"ID" binding:"required"` + Cost int64 `json:"Cost"` +} diff --git a/modules/backend/values/tables.go b/modules/backend/values/tables.go index 6699edc..feafa34 100644 --- a/modules/backend/values/tables.go +++ b/modules/backend/values/tables.go @@ -160,3 +160,51 @@ type PlatformData struct { func (u *PlatformData) PlatformData() string { return "PlatformData" } + +type ADConfig struct { + ID int `gorm:"primarykey"` + GroupName string `gorm:"column:group_name;default:'';type:varchar(64);uniqueIndex:group_name;comment:投放组名称" web:"group_name"` + Channels string `gorm:"column:channels;default:'[]';type:varchar(512);comment:分配的包" web:"channels"` +} + +func (r *ADConfig) TableName() string { + return "ad_config" +} + +type ADStats struct { + ID int `gorm:"primarykey"` + Date string `gorm:"column:date;default:'';type:varchar(64);comment:日期"` + Time int64 `gorm:"column:time;type:bigint(20);default:0;uniqueIndex:time_channel;comment:记录时间"` + ChannelID int `gorm:"column:channel_id;default:0;type:int(11);uniqueIndex:time_channel"` + Cost int64 `gorm:"column:cost;type:bigint(20);default:0;comment:消耗(美元)" web:"cost"` + TotalCost int64 `gorm:"column:total_cost;type:bigint(20);default:0;comment:累计消耗(美元)" web:"TotalCost"` + NewPlayers int64 `gorm:"column:new_players;type:bigint(20);default:0;comment:新增人数"` + NewCost string `gorm:"column:new_cost;type:varchar(64);default:'';comment:新增成本(美元)"` + NewPayCost string `gorm:"column:new_pay_cost;type:varchar(64);default:'';comment:新增付费成本(美元)"` + TotalPlayers int64 `gorm:"column:total_players;type:bigint(20);default:0;comment:总用户"` + NewPayPlayers int64 `gorm:"column:new_pay_players;type:bigint(20);default:0;comment:新增付费人数"` + NewPayPer string `gorm:"column:new_pay_per;type:varchar(64);default:'';comment:新增付费率"` + NewPay int64 `gorm:"column:new_pay;type:bigint(20);default:0;comment:新增付费"` + NewPayUnit string `gorm:"column:new_pay_unit;type:varchar(64);default:'';comment:人均新增充值(卢比)"` + ActivePay int64 `gorm:"column:activity_pay;type:bigint(20);default:0;comment:活跃付费"` + ActivePayPlayers int64 `gorm:"column:activity_pay_players;type:bigint(20);default:0;comment:活跃付费人数"` + TotalPay int64 `gorm:"column:total_pay;type:bigint(20);default:0;comment:总付费"` + ARPU string `gorm:"column:arpu;type:varchar(64);default:'';comment:活跃付费arpu"` + TotalWithdraw int64 `gorm:"column:total_withdraw;type:bigint(20);default:0;comment:总退出"` + WithdrawPer string `gorm:"column:withdraw_per;type:varchar(64);default:'';comment:退出比"` + Profit int64 `gorm:"column:profit;type:bigint(20);default:0;comment:当日收益"` + TotalProfit int64 `gorm:"column:total_profit;type:bigint(20);default:0;comment:总收益"` + NewROI string `gorm:"column:new_roi;type:varchar(64);default:'';comment:当日roi"` + TotalROI string `gorm:"column:total_roi;type:varchar(64);default:'';comment:总roi"` + NewPayROI string `gorm:"column:new_pay_roi;type:varchar(64);default:'';comment:新增roi"` + + NewRegister int `gorm:"column:new_register;default:0;type:int(11);comment:注册用户数"` + ActiveDevice int `gorm:"column:active_device;default:0;type:int(11);comment:设备数"` + NewWithdraw int64 `gorm:"column:new_withdraw;type:bigint(20);default:0;comment:新增退出"` + ActiveUser int `gorm:"column:active_user;default:0;type:int(11);comment:活跃用户数"` + RechargeUser int `gorm:"column:recharge_user;default:0;type:int(11);comment:付费人数"` +} + +func (r *ADStats) TableName() string { + return "ad_stats" +}