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 }