package jobs import ( "context" "errors" "gadmin/config" "gadmin/internal/admin/consts" "gadmin/internal/gorm/model" "gadmin/internal/gorm/query" "gadmin/utility/player" "github.com/jinzhu/now" "github.com/sirupsen/logrus" "gorm.io/gorm" "gorm.io/gorm/clause" "os" "sync" "time" ) var SyncOrders = new(jSyncOrders) const DelaySecond = 7200 // 延迟拉取2小時内的订单,防止订单未支付而过后又支付完成的情况 (ios) const DelayId = 50 // 延迟拉取前N个订单,防止订单未支付而过后又支付完成的情况 (安卓) type jSyncOrders struct { sync.Mutex } // Run // 存在问题: // 1. 通过尾数U...关联时,如果存在多个相同的关联,无法准备识别应该关联哪个 // 2. 有个别是U无法准确关联 绝大部分是U+1 // 3. 过早的订单存在直接无法关联问题 func (j *jSyncOrders) Run() { logrus.Info("SyncOrders Run.....") if os.Getenv("GIN_MODE") != "release" && os.Getenv("ADMIN_IS_LOCAL") != "1" { logrus.Warnf("测试环境禁止同步") return } j.Lock() defer j.Unlock() //游戏服订单相关表 //virtualpayorder表示游戏内订单 InGameOrders //wxpay_iosorder表示游戏外订单 OutOfGameOrders //wxpay_order存储订单状态 for serverId, _ := range config.GDBGroup { j.syncInGameOrders(serverId) j.syncOutOfGameOrders(serverId) } //for serverId, _ := range config.GDBGroup { // j.syncIos(serverId) // j.syncAndroid(serverId) //} } func (j *jSyncOrders) syncInGameOrders(serverId int) { logrus.Infof("Load Sync In-Game-Orders; ServerId %d .....", serverId) //获取游戏服id DB, err := player.GetDBByServerID(serverId) if err != nil { logrus.Warningf("syncAndroid GetDBByServerID,err:%v", err) return } //PayOrderAndriod 原名virtualpayorder 表示游戏内订单 //PayOrderIos 原名wxpay_iosorder 表示游戏外订单 var ( ctx = context.TODO() o = query.Use(config.GDBGroup[DB]).PayOrderAndriod m = o.WithContext(ctx) orderList []model.PayOrderAndriod lastOrder model.Order ) //获取游戏内最后一笔订单时间/id lastOrderSyncInfo, err := j.getLastOrderIdByOrderWay(serverId, consts.InGameOrders) if err != nil { logrus.Warnf("getLastOrderTime err . %v", err) return } //获取近两个小时内的订单 if lastOrderSyncInfo.ID != 0 { m = m.Where(o.CreateTime.Gte(lastOrderSyncInfo.LastCreateTime.Add(time.Hour * -2))) } if err = m.Scan(&orderList); err != nil { logrus.Warnf("orderList Scan err . %v", err) return } if len(orderList) == 0 { logrus.Warnf("没有需要同步的订单") return } uIds := make([]int64, 0) uIdsMap := make(map[int64]struct{}) for _, order := range orderList { if _, ok := uIdsMap[order.Playerid]; ok { continue } uIdsMap[order.Playerid] = struct{}{} uIds = append(uIds, order.Playerid) } firstOrders, err := o.WithContext(ctx).Select(o.Playerid).Group(o.Playerid).Where(o.Playerid.In(uIds...)).Having(o.Playerid.Count().Eq(1)).Find() if err != nil { return } newOrder := make(map[int64]struct{}) for _, order := range firstOrders { newOrder[order.ID] = struct{}{} } for _, order := range orderList { var ( isNew int32 = 0 status int32 = consts.OrderStatusNo orderSn = "" goodsID = int32(order.GoodsID) orderDate = "" payAt time.Time createdAt time.Time ) if _, ok := newOrder[order.ID]; ok { isNew = 1 } status = consts.OrderStateSuccess payAt = order.CreateTime orderDate = payAt.Format("2006-01-02") createdAt = payAt if payAt.IsZero() { payAt = time.Now().In(time.Local) } channelID := player.GetUserChannel(order.Playerid) userCreatedAt := player.GetUserStamp(order.Playerid) paymentType := consts.OrderPaymentType if order.Platform == 1 { paymentType = consts.OrderPaymentTypeWx } else if order.Platform == 2 { paymentType = consts.OrderPaymentTypeDy } platform := consts.OrderPlatform // 0:苹果游戏外微信支付 1:安卓游戏内支付 2:安卓游戏外微信支付 3:苹果游戏内支付 if order.PayMethod == 3 { platform = consts.OrderPlatformIos } else if order.PayMethod == 1 || order.PayMethod == 2 { platform = consts.OrderPlatformAndroid } lastOrder = model.Order{ ServerID: int32(serverId), Platform: int32(platform), PaymentType: int32(paymentType), RelatID: int32(order.ID), PlayerID: order.Playerid, GoodsID: goodsID, OrderSn: orderSn, OutTradeNo: order.BillNo, Money: float64(order.Balance-order.PayedBalance) / 100, IsNew: isNew, Status: status, Date: orderDate, PayAt: payAt, CreatedAt: createdAt, ChannelID: channelID, UserCreatedAt: userCreatedAt, Flag: player.GetUserFlag(serverId, order.Playerid), } if err = j.saveOrder(lastOrder); err != nil { logrus.Warnf("Android saveOrder Scan err: %v; order:%+v; lastOrder:%+v;\n", err, order, lastOrder) return } } if err = j.setLastOrderTime(serverId, lastOrder, consts.InGameOrders, lastOrderSyncInfo.ID); err != nil { logrus.Warnf("setLastOrderTime Scan err . %v", err) return } } func (j *jSyncOrders) syncOutOfGameOrders(serverId int) { logrus.Infof("Load Sync Out-Of-Game-Orders; ServerId: %d .....", serverId) lastOrderSyncInfo, err := j.getLastOrderIdByOrderWay(serverId, consts.OutOfGameOrders) if err != nil { logrus.Warnf("getLastOrderTime err . %v", err) return } DB, err := player.GetDBByServerID(serverId) if err != nil { logrus.Warningf("syncIos GetDBByServerID,err:%v", err) return } var ( ctx = context.TODO() o = query.Use(config.GDBGroup[DB]).PayOrderIos m = o.WithContext(ctx) orderList []model.PayOrderIos lastOrder model.Order ) //获取近两个小时内的订单 if lastOrderSyncInfo.ID != 0 { m = m.Where(o.CreateTime.Gte(lastOrderSyncInfo.LastCreateTime.Add(time.Hour * -2))) } if err = m.Scan(&orderList); err != nil { logrus.Warnf("orderList Scan err . %v", err) return } if len(orderList) == 0 { logrus.Warnf("没有需要同步的订单") return } uIds := make([]int64, 0) uIdsMap := make(map[int64]struct{}) for _, order := range orderList { if _, ok := uIdsMap[order.PlayerID]; ok { continue } uIdsMap[order.PlayerID] = struct{}{} uIds = append(uIds, order.PlayerID) } //查找第一次下单的用户 newOrders := make(map[int64]struct{}) firstOrders, err := o.WithContext(ctx).Select(o.PlayerID).Group(o.PlayerID).Where(o.PlayerID.In(uIds...)).Having(o.PlayerID.Count().Eq(1)).Find() if err != nil { return } for _, order := range firstOrders { newOrders[order.PlayerID] = struct{}{} } for _, order := range orderList { var ( isNew int32 = 0 ) if _, ok := newOrders[order.PlayerID]; ok { isNew = 1 } PayAt := time.Unix(0, 0).In(time.Local) if order.SuccessTime != "" { PayAt, _ = now.ParseInLocation(time.Local, order.SuccessTime) } if order.TradeState != "SUCESS" { continue } channelID := player.GetUserChannel(order.PlayerID) userCreatedAt := player.GetUserStamp(order.PlayerID) paymentType := consts.OrderPaymentType if order.Platform == 1 { paymentType = consts.OrderPaymentTypeWx } else if order.Platform == 2 { paymentType = consts.OrderPaymentTypeDy } platform := consts.OrderPlatform //order.Phone 1苹果 2安卓 if order.Phone == 1 { platform = consts.OrderPlatformIos } else if order.Phone == 2 { platform = consts.OrderPlatformAndroid } lastOrder = model.Order{ ServerID: int32(serverId), Platform: int32(platform), RelatID: int32(order.ID), PaymentType: int32(paymentType), PlayerID: order.PlayerID, GoodsID: order.GoodsID, OrderSn: order.OutTradeNo, OutTradeNo: order.TransactionID, Money: float64(order.Total) / 100, IsNew: isNew, Status: consts.OrderStatusSuccess, Date: order.CreateTime.Format("2006-01-02"), PayAt: PayAt, CreatedAt: order.CreateTime, ChannelID: channelID, UserCreatedAt: userCreatedAt, Flag: player.GetUserFlag(serverId, order.PlayerID), } if err = j.saveOrder(lastOrder); err != nil { logrus.Warnf("IOS saveOrder Scan err: %v; order:%+v; lastOrder:%+v;\n", err, order, lastOrder) return } } if err = j.setLastOrderTime(serverId, lastOrder, consts.OutOfGameOrders, lastOrderSyncInfo.ID); err != nil { logrus.Warnf("setLastOrderTime Scan err . %v", err) return } } //func (j *jSyncOrders) syncAndroid(serverId int) { // logrus.Info("load SyncAndroid .....") // lastId, err := j.getLastOrderId(serverId, consts.OrderPlatformAndroid) // if err != nil { // logrus.Warnf("getLastOrderTime err . %v", err) // return // } // // DB, err := player.GetDBByServerID(serverId) // if err != nil { // logrus.Warningf("syncAndroid GetDBByServerID,err:%v", err) // return // } // // var ( // o = query.Use(config.GDBGroup[DB]).PayOrderAndriod // m = o.WithContext(context.TODO()) // orderLst []model.PayOrderAndriod // lastOrder model.Order // ) // // if lastId > 0 { // m = m.Where(o.ID.Gte(lastId)) // } // // if err = m.Scan(&orderLst); err != nil { // logrus.Warnf("orderLst Scan err . %v", err) // return // } // // if len(orderLst) == 0 { // logrus.Warnf("没有需要同步的订单") // return // } // // for _, order := range orderLst { // var ( // isNew int32 = 0 // status int32 = consts.OrderStatusNo // dao = query.Use(config.GDBGroup[DB]).PayOrderAndriod // orderSn = "" // goodsID = int32(order.GoodsID) // orderDate = "" // payAt time.Time // createdAt time.Time // ) // // first, err := dao.Where(dao.Playerid.Eq(order.Playerid)).First() // if err == nil && first != nil { // if first.ID == order.ID { // isNew = 1 // } // } // // status = consts.OrderStateSuccess // // payAt = order.CreateTime // orderDate = payAt.Format("2006-01-02") // createdAt = payAt // // if payAt.IsZero() { // payAt = time.Now().In(time.Local) // } // // channelID := player.GetUserChannel(order.Playerid) // userCreatedAt := player.GetUserStamp(order.Playerid) // // lastOrder = model.Order{ // ServerID: int32(serverId), // Platform: consts.OrderPlatformAndroid, // RelatID: int32(order.ID), // PaymentType: consts.OrderPaymentTypeWx, // PlayerID: order.Playerid, // GoodsID: goodsID, // OrderSn: orderSn, // OutTradeNo: order.BillNo, // Money: float64(order.Balance-order.PayedBalance) / 100, // IsNew: isNew, // Status: status, // Date: orderDate, // PayAt: payAt, // CreatedAt: createdAt, // ChannelID: channelID, // UserCreatedAt: userCreatedAt, // Flag: player.GetUserFlag(serverId, order.Playerid), // } // // if err = j.saveOrder(lastOrder); err != nil { // logrus.Warnf("Android saveOrder Scan err: %v; order:%+v; lastOrder:%+v;\n", err, order, lastOrder) // return // } // } // // if err = j.setLastOrderTime(serverId, consts.OrderPlatformAndroid, lastOrder); err != nil { // logrus.Warnf("setLastOrderTime Scan err . %v", err) // return // } //} // //func (j *jSyncOrders) syncIos(serverId int) { // logrus.Info("load SyncIos .....") // lastTime, err := j.getLastOrderTime(serverId, consts.OrderPlatformIos) // if err != nil { // logrus.Warnf("getLastOrderTime err . %v", err) // return // } // // DB, err := player.GetDBByServerID(serverId) // if err != nil { // logrus.Warningf("syncIos GetDBByServerID,err:%v", err) // return // } // // var ( // o = query.Use(config.GDBGroup[DB]).PayOrderIos // m = o.WithContext(context.TODO()) // orderLst []model.PayOrderIos // lastOrder model.Order // ) // // if lastTime.Unix() > 0 { // m = m.Where(o.CreateTime.Gte(lastTime.Add(-time.Second * DelaySecond))) // } // // if err = m.Scan(&orderLst); err != nil { // logrus.Warnf("orderLst Scan err . %v", err) // return // } // // if len(orderLst) == 0 { // logrus.Warnf("没有需要同步的订单") // return // } // // for _, order := range orderLst { // var ( // isNew int32 = 0 // status int32 = consts.OrderStatusNo // dao = query.Use(config.GDBGroup[DB]).PayOrderIos // ) // // first, err := query.Use(config.GDBGroup[DB]).PayOrderIos.Select(dao.ALL).Where(dao.TradeState.Eq("SUCESS"), dao.PlayerID.Eq(order.PlayerID)).First() // if err == nil && first != nil { // if first.ID == order.ID { // isNew = 1 // } // } // // payAt, err := now.ParseInLocation(time.Local, order.SuccessTime) // if err != nil { // payAt = time.Now().In(time.Local) // logrus.Errorf("order successtime parse err: %v; payAt: %v;\n", err, payAt) // } // // if order.TradeState == "SUCESS" { // status = consts.OrderStatusSuccess // } else if order.TradeState == "CLOSED" { // status = consts.OrderStatusCancel // } else if order.TradeState == "NOTENOUGH" { // status = consts.OrderStatusNotEnough // } else { // status = consts.OrderStatusOther // } // // channelID := player.GetUserChannel(order.PlayerID) // userCreatedAt := player.GetUserStamp(order.PlayerID) // // lastOrder = model.Order{ // ServerID: int32(serverId), // Platform: consts.OrderPlatformIos, // RelatID: int32(order.ID), // PaymentType: consts.OrderPaymentTypeWx, // PlayerID: order.PlayerID, // GoodsID: order.GoodsID, // OrderSn: order.OutTradeNo, // OutTradeNo: order.TransactionID, // Money: float64(order.Total) / 100, // IsNew: isNew, // Status: status, // Date: order.CreateTime.Format("2006-01-02"), // PayAt: payAt, // CreatedAt: order.CreateTime, // ChannelID: channelID, // UserCreatedAt: userCreatedAt, // Flag: player.GetUserFlag(serverId, order.PlayerID), // } // // if err = j.saveOrder(lastOrder); err != nil { // logrus.Warnf("IOS saveOrder Scan err: %v; order:%+v; lastOrder:%+v;\n", err, order, lastOrder) // return // } // } // // if err = j.setLastOrderTime(serverId, consts.OrderPlatformIos, lastOrder); err != nil { // logrus.Warnf("setLastOrderTime Scan err . %v", err) // return // } //} // getLastOrderTime 获取下次同步订单的截止时间,主要用于ios func (j *jSyncOrders) getLastOrderTime(serverId int, platform int32) (last time.Time, err error) { var ( q = query.Use(config.DB).OrdersSync models model.OrdersSync ) if err = q.Where(q.Platform.Eq(platform), q.ServerID.Eq(int32(serverId))).Scan(&models); err != nil { return } return models.LastCreateTime, nil } // getLastOrderTime 获取下次同步订单的截止ID,主要用于安卓 func (j *jSyncOrders) getLastOrderId(serverId int, platform int32) (last int64, err error) { var ( q = query.Use(config.DB).OrdersSync models model.OrdersSync ) if err = q.Where(q.Platform.Eq(platform), q.ServerID.Eq(int32(serverId))).Scan(&models); err != nil { return } if models.LastID == 0 { return 0, nil } return models.LastID - DelayId, nil } func (j *jSyncOrders) getLastOrderIdByOrderWay(serverId int, orderWay int) (lastOrder *model.OrdersSync, err error) { var ( q = query.Use(config.DB).OrdersSync ) res, err := q.Where(q.ServerID.Eq(int32(serverId)), q.OrderWay.Eq(int32(orderWay))).First() if err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { return &model.OrdersSync{}, nil } return } return res, nil } func (j *jSyncOrders) setLastOrderTime(serverId int, data model.Order, orderWay int, lastSyncInfoId int64) (err error) { var ( q = query.Use(config.DB).OrdersSync ) syncData := &model.OrdersSync{ LastID: int64(data.RelatID), LastCreateTime: data.CreatedAt, UpdatedAt: time.Now(), } if lastSyncInfoId != 0 { _, err := q.Where(q.ID.Eq(lastSyncInfoId)).Updates(syncData) if err != nil { return err } } else { syncData.ServerID = int32(serverId) syncData.OrderWay = int32(orderWay) err := q.Create(syncData) if err != nil { return err } } return } func (j *jSyncOrders) saveOrder(data model.Order) (err error) { //var ( // q = query.Use(config.DB).Order // models model.Order //) // //if err = q.Where(q.ServerID.Eq(data.ServerID), q.RelatID.Eq(data.RelatID), q.Platform.Eq(data.Platform)).Scan(&models); err != nil { // return err //} // //if models.ID > 0 { // if _, err = query.Use(config.DB).Order.Where(q.RelatID.Eq(data.RelatID), q.Platform.Eq(data.Platform)).Updates(&data); err != nil { // return err // } // return nil //} else { // return query.Use(config.DB).Order.Create(&data) //} return query.Use(config.DB).Order.Clauses(clause.OnConflict{UpdateAll: true}).Create(&data) }