Files
51dj_server/web/api/statcenterctl/bili_sync.go
T
2026-10-14 17:14:38 +08:00

380 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package statcenterctl
import (
"net/http"
"strings"
"time"
"91jd-server/common"
"91jd-server/common/log"
"91jd-server/common/stderr"
"91jd-server/models/l/visitlogmod"
"91jd-server/models/v/rchgordmod"
"91jd-server/models/v/txnmod"
"91jd-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 = 6
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
}