Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
438 lines
15 KiB
Go
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
|
|
}
|