diff --git a/common/rchgutil/bill.go b/common/rchgutil/bill.go index 1139f3e..c2bb808 100644 --- a/common/rchgutil/bill.go +++ b/common/rchgutil/bill.go @@ -23,7 +23,7 @@ type PayTypeParam struct { } // BillPlatformID 账单中心平台标识 -const BillPlatformID = "逼哩逼哩" +const BillPlatformID = "黄果短剧" // billDevType 账单中心设备类型,非android按ios处理 func billDevType(devType string) string { diff --git a/web/api/statcenterctl/bili_sync.go b/web/api/statcenterctl/bili_sync.go new file mode 100644 index 0000000..d4f377f --- /dev/null +++ b/web/api/statcenterctl/bili_sync.go @@ -0,0 +1,379 @@ +package statcenterctl + +import ( + "net/http" + "strings" + "time" + + "91porn-server/common" + "91porn-server/common/log" + "91porn-server/common/stderr" + "91porn-server/models/l/visitlogmod" + "91porn-server/models/v/rchgordmod" + "91porn-server/models/v/txnmod" + "91porn-server/models/v/usermod" + + "github.com/gin-gonic/gin" + "go.mongodb.org/mongo-driver/bson" + "go.mongodb.org/mongo-driver/mongo/options" +) + +// 数据中心同步协议(与哔哩 statcenter 第三版协议保持一致) + +// statAppId 数据中心同步使用的应用ID +const statAppId int32 = 2 + +type StatJob string + +const ( + StatJobRegister StatJob = "register" // 用户注册 + StatJobRecharge StatJob = "recharge" // 用户充值 + StatJobVisit StatJob = "visit" // 用户访问 + StatJobVip StatJob = "vip" // VIP + StatJobRechargeAll StatJob = "rechargeAll" // 充值订单 + StatJobRegisterLight StatJob = "registerLight" // 注册轻量(H5注册) +) + +type StatcenterSyncReq struct { + Job string `json:"job" bson:"job"` // 同步 + UserId uint64 `json:"userId" bson:"userId"` // 用户(注册) + PlatformId string `json:"platformId" bson:"platformId"` // 原始平台Id(访问) + SuccessTime time.Time `json:"successTime"` // 成功时间(购买会员、提现、充值) + MaxSize int64 `json:"maxSize" bson:"maxSize"` // 最大条数 +} + +type StatcenterSyncResp struct { + Code int `json:"code"` // 200正常 其他异常 + Msg string `json:"msg"` // 错误消息 + Job string `json:"job" bson:"job"` // 类型 + VisitList []UserVisitMsg `json:"visitList"` // 日活记录 + RegisterList []UserRegisterMsg `json:"registerList"` // 注册记录 + WithdrawList []UserWithdrawMsg `json:"withdrawList"` // 提现记录 + RechargeList []UserRechargeMsg `json:"rechargeList"` // 充值记录 + VipList []UserBuyVIPMsg `json:"buyVipList"` // VIP购买记录 + GameRecharge []UserRechargeMsg `json:"gameRecharge"` // 游戏充值订单 + GameWithdraw []UserWithdrawMsg `json:"gameWithdraw"` // 游戏提现订单 +} + +type UserVisitMsg struct { + UserId uint64 `json:"userId" bson:"userId"` // 用户ID + PlatformId string `json:"platformId" bson:"platformId"` // 原始平台流水Id + AppId int32 `json:"appId" bson:"appId"` // appID + SysType string `json:"sysType" bson:"sysType"` // 操作系统类型 安卓 IOS + DevType string `json:"devType,omitempty" bson:"devType"` // 设备型号 + IP string `json:"ip,omitempty" bson:"ip"` // IP + Version string `json:"version,omitempty" bson:"version"` // APP版本号 + DevID string `json:"devID,omitempty" bson:"devID"` // 设备ID + VisitAt time.Time `json:"visitAt,omitempty" bson:"visitAt"` // 访问时间 +} + +type UserRegisterMsg struct { + UserId uint64 `json:"userId" bson:"userId"` // 用户ID + AppId int32 `json:"appId" bson:"appId"` // appID + SysType string `json:"sysType" bson:"sysType"` // 操作系统类型 安卓 IOS + PlatformId string `json:"platformId" bson:"platformId"` // 原始平台流水Id + DevType string `json:"devType" bson:"devType"` // 设备类型 + Mobile string `json:"mobile" bson:"mobile"` // 手机号 + IP string `json:"ip,omitempty" bson:"ip"` // IP + Name string `json:"name" bson:"name"` // 名称 + IsDirect bool `json:"isDirect" bson:"isDirect"` // true:是直推用户 + DistrictCode string `json:"districtCode" bson:"districtCode" ` // 渠道码 + PromSeqe string `json:"promSeqe" bson:"promSeqe"` // 全名代理 + PromCode string `json:"promCode" bson:"promCode"` // 推广序列 + RegisterTime time.Time `json:"registerTime" bson:"registerTime"` // 注册时间时间 +} + +type UserRechargeMsg struct { + UserId uint64 `json:"userId" bson:"userId"` // 用户ID + AppId int32 `json:"appId" bson:"appId"` // appID + PlatformId string `json:"platformId" bson:"platformId"` // 原始平台流水Id + SysType string `json:"sysType" bson:"sysType"` // 操作系统类型 安卓 IOS + DevType string `json:"devType" bson:"devType"` // 设备类型 + ChannelName string `json:"channelName" bson:"channelName"` // 支付渠道名字 + CID string `json:"cid" bson:"cid"` // 渠道id + Type string `bson:"type" json:"type"` // 充值方式 + OrderId string `json:"orderId" bson:"orderId"` // 流水id + Money int64 `json:"money" bson:"money"` // 充值金额 订单金额 + PayMoney int64 `json:"payMoney" bson:"payMoney"` // 实际到账金额 用户实际支付金额 + Status int `json:"status" bson:"status"` // 2付款失败 3付款成功(目前只有成功才发送) + Rate string `json:"rate"` // 渠道费率 + SuccessAt time.Time `json:"successAt" bson:"successAt"` // 成功时间 + CreatedAt time.Time `json:"createdAt" bson:"createdAt"` // 创建时间 +} + +type UserWithdrawMsg struct { + AppId int32 `json:"appId" bson:"appId"` //appID + UID uint64 `json:"uid" bson:"uid"` //用户id + PlatformId string `json:"platformId" bson:"platformId"` //原始平台流水Id + Name string `json:"name" bson:"name"` //用户名 + OID string `json:"oid" bson:"oid"` //第三方支付平台id + Money int64 `json:"money" bson:"money"` //提现金额 + PayMoney int64 `json:"payMoney" bson:"payMoney"` //实际到账 + PayType string `json:"payType" bson:"payType"` //提现方式,alipay,bankcard + ActName string `json:"actName" bson:"actName"` //交易账户持有人 + Act string `json:"act" bson:"act"` //交易账户 + DeviceType string `json:"deviceType" bson:"deviceType"` //设备类型 + DevID string `json:"devID" bson:"devID"` //设备id + Status int `json:"status" bson:"status"` //5提现成功 + SuccessAt time.Time `json:"successAt" bson:"successAt"` //回调成功时间 + UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"` //刷新时间 + CreatedAt time.Time `json:"createdAt" bson:"createdAt"` //提现申请时间 + IsDirect bool `json:"isDirect" bson:"isDirect"` //true:是直推用户 + DistrictCode string `json:"districtCode" bson:"districtCode"` //渠道码 + PromCode string `json:"promCode" bson:"promCode"` //用户推广序列 +} + +type BuyVIPType int64 + +const ( + BuyVIPTypeMoney BuyVIPType = 0 + iota // 访问 + BuyVIPTypeGold // 金币 +) + +type UserBuyVIPMsg struct { + AppId int32 `json:"appId" bson:"appId"` //appID + UID uint64 `json:"uid" bson:"uid"` //用户id + PlatformId string `json:"platformId" bson:"platformId"` //原始平台流水Id + Name string `json:"name" bson:"name"` //用户名 + Money int64 `json:"money" bson:"money"` //提现金额 + Amount int64 `json:"amount" bson:"amount"` //金币 + BuyVIPType BuyVIPType `json:"buyVIPType" bson:"buyVIPType"` //支付方式 + OID string `json:"oid" bson:"oid"` //第三方支付平台id + DeviceType string `json:"deviceType" bson:"deviceType"` //设备类型 + Status int `json:"status" bson:"status"` //1未审核 5提现成功 + SuccessAt time.Time `json:"successTime"` // 成功时间(购买会员、提现、充值) + IsDirect bool `json:"isDirect" bson:"isDirect"` //true:是直推用户 + DistrictCode string `json:"districtCode" bson:"districtCode"` //渠道码 + PromCode string `json:"promCode" bson:"promCode"` //用户推广序列 +} + +// StatcenterSyncList doc +// @Tags 数据通过 +// @Summary 拉去数据同步信息 +// @Description 返回用户收益详情 +// @Accept json +// @Produce json +// @Param param body StatcenterSyncReq true "参数" +// @Success 200 {object} StatcenterSyncResp "成功" +// @Success 400 {string} string "失败" +// @Router /api/web/statcenter/sync [POST] +func StatcenterSyncList(ctx *gin.Context) { + var request StatcenterSyncReq + if err := ctx.ShouldBind(&request); err != nil { + common.ServeJSON(ctx, stderr.ErrParamError, err.Error()) + return + } + + var ( + resp StatcenterSyncResp + err error + ) + resp.Job = request.Job + resp.Code = 200 + log.Info("StatcenterSyncList", log.Any("job", request.Job)) + + switch StatJob(request.Job) { + case StatJobVisit: + resp.VisitList, err = syncUserVisitList(request) + case StatJobRegister: + resp.RegisterList, err = syncUserRegisterList(request) + case StatJobRegisterLight: + resp.RegisterList, err = syncH5UserRegisterList(request) + case StatJobRecharge: + resp.RechargeList, err = syncUserRechargeList(request) + case StatJobRechargeAll: + resp.RechargeList, err = syncUserAllRechargeList(request) + case StatJobVip: + resp.VipList, err = syncUserVipList(request) + default: + resp.Code = 501 + resp.Msg = "job is error" + } + if err != nil { + resp.Code = 502 + resp.Msg = err.Error() + } + + ctx.JSON(http.StatusOK, resp) +} + +// syncUserVisitList 日活数据同步 +func syncUserVisitList(request StatcenterSyncReq) ([]UserVisitMsg, error) { + visitList, err := visitlogmod.AccessSyncById(request.PlatformId, request.MaxSize) + if err != nil { + log.Error("syncUserVisitList visitlogmod.AccessSyncById", log.Any("request", request), log.E(err)) + return nil, err + } + list := make([]UserVisitMsg, 0, len(visitList)) + for _, v := range visitList { + list = append(list, UserVisitMsg{ + UserId: v.UID, + AppId: statAppId, + SysType: v.SysType, + DevType: v.SysType, + IP: v.IP, + Version: v.Ver, + DevID: v.DevID, + VisitAt: v.CreatedAt, + PlatformId: v.ID.Hex(), + }) + } + return list, nil +} + +// syncUserRegisterList 注册数据同步 +func syncUserRegisterList(request StatcenterSyncReq) ([]UserRegisterMsg, error) { + filter := bson.M{ + "createdAt": bson.M{"$gt": request.SuccessTime}, + "userType": bson.M{"$in": []usermod.UserType{usermod.UserUnkown, usermod.UserLoufengAgent}}, + } + opts := options.Find().SetLimit(request.MaxSize).SetSort(bson.D{{Key: "uid", Value: 1}}) + userList, _, err := usermod.FetchList(filter, opts) + if err != nil { + log.Error("syncUserRegisterList usermod.FetchList", log.Any("request", request), log.E(err)) + return nil, err + } + list := make([]UserRegisterMsg, 0, len(userList)) + for _, u := range userList { + list = append(list, toUserRegisterMsg(u, u.CreatedAt)) + } + return list, nil +} + +// syncH5UserRegisterList H5注册数据同步(按绑定手机时间) +func syncH5UserRegisterList(request StatcenterSyncReq) ([]UserRegisterMsg, error) { + userList, err := usermod.StatcenterSyncBindUserList(request.SuccessTime, request.MaxSize) + if err != nil { + log.Error("syncH5UserRegisterList usermod.StatcenterSyncBindUserList", log.Any("request", request), log.E(err)) + return nil, err + } + list := make([]UserRegisterMsg, 0, len(userList)) + for _, u := range userList { + if strings.Contains(u.DevType, "aosp:") { + continue + } + if !isH5SysType(u.SysType) { + continue + } + var registerTime time.Time + if u.MobileBindAt != nil { + registerTime = *u.MobileBindAt + } + list = append(list, toUserRegisterMsg(&u, registerTime)) + } + return list, nil +} + +func toUserRegisterMsg(u *usermod.User, registerTime time.Time) UserRegisterMsg { + return UserRegisterMsg{ + UserId: u.UID, + AppId: statAppId, + SysType: u.SysType, + DevType: u.DevType, + Mobile: u.Mobile, + Name: u.Name, + IP: u.RegisterIP, + IsDirect: u.IsDirect, + DistrictCode: u.DistrictCode, + PromSeqe: u.PromSeqe, + PromCode: u.PromCode, + RegisterTime: registerTime, + } +} + +// isH5SysType 是否H5端 +func isH5SysType(sysType string) bool { + s := strings.ToLower(sysType) + switch s { + case "light", "tg", "web": + return true + } + return strings.Contains(s, "h5") +} + +// syncUserRechargeList 成功充值订单同步 +func syncUserRechargeList(request StatcenterSyncReq) ([]UserRechargeMsg, error) { + orders, err := rchgordmod.StatCenterSyncRecharge(request.SuccessTime, request.MaxSize) + if err != nil { + log.Error("syncUserRechargeList rchgordmod.StatCenterSyncRecharge", log.Any("request", request), log.E(err)) + return nil, err + } + list := make([]UserRechargeMsg, 0, len(orders)) + for _, o := range orders { + msg := toUserRechargeMsg(o) + msg.SuccessAt = o.SuccessAt + list = append(list, msg) + } + return list, nil +} + +// syncUserAllRechargeList 全部充值订单同步(不含失败订单) +func syncUserAllRechargeList(request StatcenterSyncReq) ([]UserRechargeMsg, error) { + orders, err := rchgordmod.StatCenterSyncOrder(request.SuccessTime, request.MaxSize) + if err != nil { + log.Error("syncUserAllRechargeList rchgordmod.StatCenterSyncOrder", log.Any("request", request), log.E(err)) + return nil, err + } + list := make([]UserRechargeMsg, 0, len(orders)) + for _, o := range orders { + if o.Status == rchgordmod.FAILURE { + continue + } + msg := toUserRechargeMsg(o) + msg.CreatedAt = o.CreatedAt + list = append(list, msg) + } + return list, nil +} + +func toUserRechargeMsg(o rchgordmod.RechargeOrder) UserRechargeMsg { + return UserRechargeMsg{ + UserId: o.UID, + AppId: statAppId, + SysType: o.DevType, + DevType: o.DevType, + PlatformId: o.ID.Hex(), + ChannelName: o.Channel, + CID: o.OID, + Type: o.RechargeType, + OrderId: o.ID.Hex(), + Money: o.Money, + PayMoney: o.PayMoney, + Status: o.Status, + Rate: o.Rate, + } +} + +// syncUserVipList 充值购买会员卡同步 +func syncUserVipList(request StatcenterSyncReq) ([]UserBuyVIPMsg, error) { + endTime := time.Now().Add(-time.Minute * 2) + filter := bson.M{ + "tranTypeInt": int64(txnmod.BuyVIP), + "createdAt": bson.M{"$gt": request.SuccessTime, "$lt": endTime}, + } + opts := options.Find().SetLimit(request.MaxSize).SetSort(bson.D{{Key: "createdAt", Value: 1}}) + _, logs, err := txnmod.FindTransactionLogs(filter, opts) + if err != nil { + log.Error("syncUserVipList txnmod.FindTransactionLogs", log.Any("request", request), log.E(err)) + return nil, err + } + list := make([]UserBuyVIPMsg, 0, len(logs)) + for _, l := range logs { + money := l.Amount + if money < 0 { + money = -money + } + list = append(list, UserBuyVIPMsg{ + UID: l.UID, + AppId: statAppId, + PlatformId: l.ID.Hex(), + Money: money * 10, // 流水金额为会员卡价格(角),转换为分 + BuyVIPType: BuyVIPTypeMoney, + OID: l.TransNo.Hex(), + DeviceType: l.SysType, + Status: 3, + SuccessAt: l.CreatedAt, + }) + } + return list, nil +} diff --git a/web/router/base.go b/web/router/base.go index acb407c..f82f6b4 100644 --- a/web/router/base.go +++ b/web/router/base.go @@ -189,4 +189,9 @@ func registerRouter(router *gin.Engine) { { syncRouter(syncDataRouter) } + //数据中心同步接口,不走后台登录鉴权 + statcenterGroup := router.Group("/api/web/statcenter") + { + statcenterRouter(statcenterGroup) + } } diff --git a/web/router/sync.go b/web/router/sync.go index 4a87a21..7799e69 100644 --- a/web/router/sync.go +++ b/web/router/sync.go @@ -6,6 +6,11 @@ import ( "github.com/gin-gonic/gin" ) +// statcenterRouter 数据中心拉取同步数据(协议与哔哩 /api/web/statcenter/sync 一致) +func statcenterRouter(router *gin.RouterGroup) { + router.POST("/sync", statcenterctl.StatcenterSyncList) +} + // syncRouter 数据同步 func syncRouter(router *gin.RouterGroup) { routerGroup := router.Group("/router")