Files
web_huangguo2_server/app/api/statcenterapi/sync.go
T
2026-09-29 10:47:38 +08:00

438 lines
15 KiB
Go

package statcenterapi
import (
"context"
"fmt"
"math"
"net/http"
"strconv"
"videoweb/pub/common"
"videoweb/pub/common/log"
"videoweb/pub/common/srvdef"
"videoweb/pub/common/statcenter"
"videoweb/pub/models/cointransmod"
"videoweb/pub/models/usermod"
"videoweb/pub/models/uservisitlog"
"videoweb/pub/models/vipordermod"
"videoweb/pub/pubdef"
"github.com/gin-gonic/gin"
"github.com/shopspring/decimal"
"go.mongodb.org/mongo-driver/bson"
)
// @Tags 数据同步
// @Summary 统计中心增量同步
// @Description 按 job 拉取注册/日活/充值/绑定/消费/售卡等增量数据
// @Accept json
// @Produce json
// @Param param body statcenter.StatcenterSyncReq true "参数"
// @Success 200 {object} statcenter.StatcenterSyncResp "成功"
// @Failure 400 {string} string "失败"
// @Router /api/app/statcenter/sync [POST]
func StatcenterSyncList(ctx *gin.Context) {
var request statcenter.StatcenterSyncReq
if err := ctx.ShouldBind(&request); err != nil {
common.ServerJSON(ctx, srvdef.ErrParamError, err.Error())
return
}
var resp statcenter.StatcenterSyncResp
resp.Job = request.Job
resp.Code = 200
appId := statcenter.STAT_APPID
var err error
switch request.Job {
case string(statcenter.USER_ACCE):
resp.AccessList, err = UserAccessList(appId, request, true)
case string(statcenter.USER_REG):
resp.RegisterList, err = UserRegisterList(appId, request, true)
case string(statcenter.USER_BINDING):
resp.BindingList, err = UserBindingList(appId, request)
case string(statcenter.USER_INVITE):
resp.InviteList, err = UserInviteList(appId, request)
case string(statcenter.ConsumeRecordJob):
resp.ConsumeList, err = ConsumeRecordList(appId, request)
case string(statcenter.USER_RECH):
resp.RechargeList, err = UserRechargeList(appId, request, true)
case string(statcenter.USER_RECH_ALL):
resp.AllRechargeList, err = UserAllRechargeList(appId, request, true)
case string(statcenter.CardSellJob):
resp.CardSellList, err = CardSellList(appId, request)
case string(statcenter.AI_SELL_JOB):
resp.AiSellList, err = AiSellList(appId, request)
case string(statcenter.VipJob):
resp.VipList, err = UserSyncVipList(appId, request)
case string(statcenter.RechargeAll): // 兼容旧任务名
resp.RechargeList, err = UserAllRechargeList(appId, request, true)
default:
resp.Code = 501
resp.Msg = "job is error"
ctx.JSON(http.StatusOK, resp)
return
}
if err != nil {
resp.Code = 502
resp.Msg = err.Error()
}
ctx.JSON(http.StatusOK, resp)
}
// UserAccessList 日活数据同步
func UserAccessList(appId int32, request statcenter.StatcenterSyncReq, isNewStat bool) (accessList []statcenter.UserVisitMsg, err error) {
platformId := request.PlatformId
if platformId == "" {
platformId = "000000000000000000000000"
}
visitList, err := uservisitlog.AccessSyncById(context.Background(), platformId, request.MaxSize)
if err != nil {
log.Error("UserAccessList AccessSyncById", log.E(err))
return nil, err
}
accessList = make([]statcenter.UserVisitMsg, 0, len(visitList))
userIds := make([]int64, 0, len(visitList))
for _, visitInfo := range visitList {
userIds = append(userIds, visitInfo.UserId)
}
userList, _ := usermod.FindManyUser(context.Background(), bson.M{usermod.FN.User.Id: bson.M{"$in": userIds}})
userMap := make(map[int64]*usermod.User, len(userList))
for _, u := range userList {
userMap[u.Id] = u
}
for _, visitInfo := range visitList {
sType, dType := visitInfo.SysType, visitInfo.DevType
if appId == statcenter.STAT_APPID && isNewStat {
sType, dType = pubdef.GetCenterType(pubdef.SysTypeFromString(visitInfo.SysType), visitInfo.DevType)
}
accessInfo := statcenter.UserVisitMsg{
UserId: uint64(visitInfo.UserId),
AppId: appId,
SysType: sType,
DevType: dType,
IP: visitInfo.IP,
Version: visitInfo.Version,
DevID: visitInfo.DevID,
VisitAt: visitInfo.VisitAt,
PlatformId: visitInfo.Id.Hex(),
}
if userInfo, ok := userMap[visitInfo.UserId]; ok {
accessInfo.IsDirect = userInfo.IsDirect
accessInfo.DistrictCode = userInfo.DistrictCode
accessInfo.RegisterTime = userInfo.CreatedAt
}
accessList = append(accessList, accessInfo)
}
return
}
// UserRegisterList 注册数据同步
func UserRegisterList(appId int32, request statcenter.StatcenterSyncReq, isNewStat bool) (registerList []statcenter.UserRegisterMsg, err error) {
var userList usermod.UserList
if appId == statcenter.STAT_APPID && isNewStat {
if request.UserId > math.MaxInt64 {
return nil, fmt.Errorf("invalid register cursor userId: %d", request.UserId)
}
userList, err = usermod.StatcenterSyncListByUserID(context.Background(), int64(request.UserId), request.MaxSize)
} else {
userList, err = usermod.StatcenterSyncList(context.Background(), request.SuccessTime, request.MaxSize)
}
if err != nil {
log.Error("UserRegisterList", log.E(err))
return nil, err
}
registerList = make([]statcenter.UserRegisterMsg, 0, len(userList))
for _, userInfo := range userList {
if userInfo.Device == "robot" {
continue
}
isNativeIOS := pubdef.SysTypeFromString(userInfo.SysType) == pubdef.ST_IOS
districtCode := userInfo.DistrictCode
if isNativeIOS && statcenter.IOSRegistrationDistrictCode != "" {
districtCode = statcenter.IOSRegistrationDistrictCode
}
sType, dType := userInfo.SysType, userInfo.Device
if appId == statcenter.STAT_APPID && isNewStat {
sType, dType = pubdef.GetCenterType(pubdef.SysTypeFromString(userInfo.SysType), userInfo.Device)
}
registerList = append(registerList, statcenter.UserRegisterMsg{
UserId: uint64(userInfo.Id),
AppId: appId,
SysType: sType,
DevType: dType,
Mobile: userInfo.Email,
Name: userInfo.NickName,
IP: userInfo.RegIp,
IsDirect: userInfo.IsDirect,
DistrictCode: districtCode,
PromSeqe: userInfo.AgentInviteCode,
PromCode: userInfo.InviteCode,
RegisterTime: userInfo.CreatedAt,
})
}
return
}
// UserBindingList 绑定数据同步(本项目:正式注册并绑定邮箱)
func UserBindingList(appId int32, request statcenter.StatcenterSyncReq) ([]statcenter.UserBindingMsg, error) {
userList, err := usermod.StatcenterSyncBindUserList(context.Background(), request.SuccessTime, request.MaxSize)
if err != nil {
log.Error("UserBindingList", log.E(err))
return nil, err
}
list := make([]statcenter.UserBindingMsg, 0, len(userList))
for _, userInfo := range userList {
sType, dType := pubdef.GetCenterType(pubdef.SysTypeFromString(userInfo.SysType), userInfo.Device)
bindAt := userInfo.UpdatedAt
list = append(list, statcenter.UserBindingMsg{
UserId: uint64(userInfo.Id),
AppId: appId,
PlatformId: fmt.Sprintf("%d", userInfo.Id),
SysType: sType,
DevType: dType,
Mobile: userInfo.Email,
BindingTime: &bindAt,
})
}
return list, nil
}
// UserInviteList 邀请绑定同步
func UserInviteList(appId int32, request statcenter.StatcenterSyncReq) ([]statcenter.UserInviteBindMsg, error) {
userList, err := usermod.StatcenterSyncInviteList(context.Background(), request.SuccessTime, request.MaxSize)
if err != nil {
log.Error("UserInviteList", log.E(err))
return nil, err
}
list := make([]statcenter.UserInviteBindMsg, 0, len(userList))
for _, userInfo := range userList {
list = append(list, statcenter.UserInviteBindMsg{
UserId: uint64(userInfo.Id),
AppId: appId,
ParentPromCode: userInfo.AgentInviteCode,
InviteTime: userInfo.CreatedAt,
})
}
return list, nil
}
// ConsumeRecordList 金币消费流水同步
func ConsumeRecordList(appId int32, request statcenter.StatcenterSyncReq) ([]statcenter.ConsumeRecordMsg, error) {
startId := int64(0)
if request.PlatformId != "" {
id, err := strconv.ParseInt(request.PlatformId, 10, 64)
if err != nil {
return nil, fmt.Errorf("invalid consume cursor platformId: %s", request.PlatformId)
}
startId = id
}
transList, err := cointransmod.StatSyncConsumeList(context.Background(), startId, request.MaxSize)
if err != nil {
log.Error("ConsumeRecordList", log.E(err))
return nil, err
}
list := make([]statcenter.ConsumeRecordMsg, 0, len(transList))
for _, item := range transList {
consumeType := statcenter.StatValueAddSer
if item.TranType == cointransmod.TranTypeVideoUnlock {
consumeType = statcenter.StatVideoUnlock
}
list = append(list, statcenter.ConsumeRecordMsg{
AppID: appId,
UID: uint64(item.UserId),
Type: consumeType,
CurrencyType: statcenter.CurrencyTypeGold,
Money: decimal.Zero,
Amount: decimal.NewFromInt(item.Amount),
Uniq: strconv.FormatInt(item.Id, 10),
CreatedAt: item.CreatedAt,
})
}
return list, nil
}
// UserRechargeList 成功充值订单同步(会员+金币)
func UserRechargeList(appId int32, request statcenter.StatcenterSyncReq, isNewStat bool) (registerList []statcenter.UserRechargeMsg, err error) {
orderList, err := vipordermod.StatSyncSuccessOrderList(context.Background(), request.SuccessTime, request.MaxSize)
if err != nil {
log.Error("UserRechargeList StatSyncSuccessOrderList", log.E(err))
return nil, err
}
userMap := findUserDistrictMap(orderList)
registerList = make([]statcenter.UserRechargeMsg, 0, len(orderList))
for _, order := range orderList {
sType, dType := order.SysType, ""
if appId == statcenter.STAT_APPID && isNewStat {
sType, dType = pubdef.GetCenterType(pubdef.SysTypeFromString(order.SysType), "")
}
msg := statcenter.UserRechargeMsg{
UserId: uint64(order.UserId),
AppId: appId,
SysType: sType,
DevType: dType,
PlatformId: fmt.Sprintf("%d", order.Id),
ChannelName: order.PayChannel,
CID: order.Oid,
Type: order.PayMode,
OrderId: order.TradeNo,
OID: order.Oid,
Money: order.Amount,
PayMoney: order.Amount,
Status: statcenter.MapOrderStatusToStat(order.Status),
Rate: order.PayRate,
SuccessAt: order.FinishedAt,
CreatedAt: order.CreatedAt,
}
fillRechargeDistrict(&msg, userMap)
if order.OrderType == vipordermod.OrderTypeCoin {
msg.ChanShareMod = 1
} else {
msg.ChanShareMod = 2
}
registerList = append(registerList, msg)
}
return
}
// UserAllRechargeList 全部非失败充值订单同步
func UserAllRechargeList(appId int32, request statcenter.StatcenterSyncReq, isNewStat bool) (registerList []statcenter.UserRechargeMsg, err error) {
orderList, err := vipordermod.StatSyncAllOrderList(context.Background(), request.SuccessTime, request.MaxSize)
if err != nil {
log.Error("UserAllRechargeList StatSyncAllOrderList", log.E(err))
return nil, err
}
userMap := findUserDistrictMap(orderList)
registerList = make([]statcenter.UserRechargeMsg, 0, len(orderList))
for _, order := range orderList {
sType, dType := order.SysType, ""
if appId == statcenter.STAT_APPID && isNewStat {
sType, dType = pubdef.GetCenterType(pubdef.SysTypeFromString(order.SysType), "")
}
msg := statcenter.UserRechargeMsg{
UserId: uint64(order.UserId),
AppId: appId,
SysType: sType,
DevType: dType,
PlatformId: fmt.Sprintf("%d", order.Id),
ChannelName: order.PayChannel,
CID: order.Oid,
Type: order.PayMode,
OrderId: order.TradeNo,
OID: order.Oid,
Money: order.Amount,
PayMoney: order.Amount,
Status: statcenter.MapOrderStatusToStat(order.Status),
Rate: order.PayRate,
CreatedAt: order.CreatedAt,
SuccessAt: order.CreatedAt,
}
fillRechargeDistrict(&msg, userMap)
registerList = append(registerList, msg)
}
return
}
// CardSellList 会员卡销售流水同步(成功会员订单)
func CardSellList(appId int32, request statcenter.StatcenterSyncReq) ([]statcenter.CardSellMsg, error) {
orderList, err := vipordermod.StatSyncVipOrderList(context.Background(), request.SuccessTime, request.MaxSize)
if err != nil {
log.Error("CardSellList", log.E(err))
return nil, err
}
list := make([]statcenter.CardSellMsg, 0, len(orderList))
for _, order := range orderList {
sType, _ := pubdef.GetCenterType(pubdef.SysTypeFromString(order.SysType), "")
list = append(list, statcenter.CardSellMsg{
AppID: appId,
UID: uint64(order.UserId),
UniqID: fmt.Sprintf("%d", order.Id),
Amount: order.Amount,
TranTypeInt: int64(order.OrderType),
TranType: "会员卡",
SysType: sType,
CurrencyType: statcenter.CurrencyTypeCash,
TranCreatedAt: order.FinishedAt,
Product: statcenter.Product{
ID: fmt.Sprintf("%d", order.CardId),
Name: order.CardName,
DiscountedPrice: order.Amount,
},
})
}
return list, nil
}
// AiSellList AI 销售流水(本项目无 AI 业务,返回空列表)
func AiSellList(appId int32, request statcenter.StatcenterSyncReq) ([]statcenter.AiSellMsg, error) {
return make([]statcenter.AiSellMsg, 0), nil
}
// UserSyncVipList VIP 购买成功订单同步
func UserSyncVipList(appId int32, request statcenter.StatcenterSyncReq) (registerList []statcenter.UserBuyVIPMsg, err error) {
orderList, err := vipordermod.StatSyncVipOrderList(context.Background(), request.SuccessTime, request.MaxSize)
if err != nil {
log.Error("UserSyncVipList StatSyncVipOrderList", log.E(err))
return nil, err
}
userMap := findUserDistrictMap(orderList)
registerList = make([]statcenter.UserBuyVIPMsg, 0, len(orderList))
for _, order := range orderList {
msg := statcenter.UserBuyVIPMsg{
UID: uint64(order.UserId),
AppId: appId,
PlatformId: fmt.Sprintf("%d", order.Id),
Name: order.CardName,
Money: order.Amount,
BuyVIPType: statcenter.BuyVIPTypeMoney,
OID: order.Oid,
DeviceType: order.SysType,
Status: 3,
SuccessAt: order.FinishedAt,
}
if userInfo, ok := userMap[order.UserId]; ok {
msg.IsDirect = userInfo.IsDirect
msg.DistrictCode = userInfo.DistrictCode
}
registerList = append(registerList, msg)
}
return
}
func findUserDistrictMap(orderList vipordermod.VipOrderList) map[int64]*usermod.User {
userIds := make([]int64, 0, len(orderList))
seen := make(map[int64]struct{}, len(orderList))
for _, order := range orderList {
if order == nil || order.UserId <= 0 {
continue
}
if _, ok := seen[order.UserId]; ok {
continue
}
seen[order.UserId] = struct{}{}
userIds = append(userIds, order.UserId)
}
userMap := make(map[int64]*usermod.User, len(userIds))
if len(userIds) == 0 {
return userMap
}
userList, _ := usermod.FindManyUser(context.Background(), bson.M{usermod.FN.User.Id: bson.M{"$in": userIds}})
for _, u := range userList {
userMap[u.Id] = u
}
return userMap
}
func fillRechargeDistrict(msg *statcenter.UserRechargeMsg, userMap map[int64]*usermod.User) {
if msg == nil {
return
}
userInfo, ok := userMap[int64(msg.UserId)]
if !ok || userInfo == nil {
return
}
msg.DistrictCode = userInfo.DistrictCode
msg.RegisterTime = userInfo.CreatedAt
}