package services import ( "app/commons/constant" "app/commons/core" "app/commons/core/exchange" "app/commons/core/redisclient" "app/commons/model/entity" "app/commons/utils" "fmt" "github.com/shopspring/decimal" "gorm.io/gorm" ) func (s *CommonService) BatchStakeUserCurrentOrder(db *gorm.DB) ([]*entity.StakeUserCurrentOrder, error) { return FindAllWithBatch[entity.StakeUserCurrentOrder](db) } func (s *CommonService) FirstStakeUserCurrentOrder(db *gorm.DB) (*entity.StakeUserCurrentOrder, error) { return GetOne[entity.StakeUserCurrentOrder](db) } func (s *CommonService) FirstStakeProduct(db *gorm.DB) (*entity.StakeProduct, error) { return GetOne[entity.StakeProduct](db) } // 创建/获取用户活期质押记录 func (s *CommonService) CheckUserCurrentStakeUserOrder(txDb *gorm.DB, userId int64, userUid string) (*entity.StakeUserCurrentOrder, error) { order, err := s.FirstStakeUserCurrentOrder(txDb.Where("uid", userUid).Where("user_id", userId)) if err == nil && order.Id != 0 { return order, nil } currentProduct, err := s.FirstStakeProduct(txDb.Where("pledge_mode", entity.StakeProductTypeCurrent)) if err != nil { return nil, err } order = &entity.StakeUserCurrentOrder{ UserId: userId, Uid: userUid, ProductId: currentProduct.Id, ProductName: currentProduct.Name, Symbol: currentProduct.Symbol, FirstPledgeDate: utils.TimeDate(), LastClaimPeriod: utils.NowPeriodNo(), } err = txDb.Create(&order).Error return order, err } // 获取用户产品纬度订单 -- 聚合订单 // 资产修改 -- 必须加资产锁定 // 使用场景 1 api质押 2 管理后台直接增加订单 // 活期 -- 随进随出 func (s *CommonService) CurrentStake(user *entity.User, product *entity.StakeProduct, quantity decimal.Decimal, adminId int64) error { // 获取分布式锁 存在用户购买商品 发放奖励等情况 可能会影响资产变化 lockKey := fmt.Sprintf("%s:%d", constant.UserAssetLocker, user.Id) lock, err := redisclient.Lock(lockKey) if err != nil { return fmt.Errorf("failed to acquire lock: %v", err) } core.Log.Infof("用户ID:%s 资产锁获取", lockKey) defer func() { err = redisclient.UnlockSafe(lock) if err != nil { core.Log.Error(err) } core.Log.Infof("用户ID:%s 资产锁释放", lockKey) }() // 为用户质押 symbolPrice, err := exchange.GetCurrentSymbolUsdPrice(constant.CoinSystemSymbol) if err != nil { core.Log.Error(err) return err } txDb := s.DB().Begin() bs := constant.BsById(constant.BsCurrentStake) // todo: 1 创建质押记录 magic_stake_user_ops_record -- 排队的逻辑 ops := s.buildCurrentStakeOps(user, quantity, symbolPrice, bs, adminId) if err = txDb.Create(&ops).Error; err != nil { txDb.Rollback() return err } order, err := s.CheckUserCurrentStakeUserOrder(txDb, user.Id, user.Uid) if err != nil { txDb.Rollback() return err } // 未开启排队 if !product.IsQueue { err = txDb.Model(&entity.StakeUserCurrentOpsRecord{}). Where("id", ops.Id). Updates(map[string]interface{}{ "before_quantity": order.Quantity, "before_usd_amount": order.UsdAmount, "after_quantity": order.Quantity.Add(ops.Quantity), "after_usd_amount": order.UsdAmount.Add(ops.UsdValue), "queue_state": entity.StateQueueStateSuccess, }).Error if err != nil { txDb.Rollback() return err } err = txDb.Model(&entity.StakeUserCurrentOrder{}). Where("id", order.Id). Updates(map[string]interface{}{ "cum_quantity": gorm.Expr("cum_quantity+?", ops.Quantity), "quantity": gorm.Expr("quantity+?", ops.Quantity), "cum_usd_amount": gorm.Expr("cum_usd_amount+?", ops.UsdValue), "usd_amount": gorm.Expr("usd_amount+?", ops.UsdValue), "version": gorm.Expr("version + 1"), }).Error if err != nil { txDb.Rollback() return err } } // 全局操作资产方法 -- 当为管理员后台操作 则表示不需要扣除资产 if adminId == 0 { if err = s.GenBillAndActionAsset(txDb, user.Id, constant.CoinSystemSymbol, decimal.Zero.Sub(quantity), decimal.Zero, bs); err != nil { txDb.Rollback() return err } } txDb.Commit() // todo: 2 生效后写入聚合订单 -- magic_stake_user_order -- 排队逻辑 return nil } // 赎回 func (s *CommonService) CurrentRedeem(user *entity.User, amount decimal.Decimal, adminId int64) error { // 获取分布式锁 存在用户购买商品 发放奖励等情况 可能会影响资产变化 lockKey := fmt.Sprintf("%s:%d", constant.UserAssetLocker, user.Id) lock, err := redisclient.Lock(lockKey) if err != nil { return fmt.Errorf("failed to acquire lock: %v", err) } core.Log.Infof("用户ID:%s 资产锁获取", lockKey) defer func() { err = redisclient.UnlockSafe(lock) if err != nil { core.Log.Error(err) } core.Log.Infof("用户ID:%s 资产锁释放", lockKey) }() // 为用户质押 return nil } // 领取 func (s *CommonService) CurrentProfitClaim(user *entity.User) error { txDb := s.DB().Begin() currentOrder, err := s.FirstStakeUserCurrentOrder(txDb.Where("user_id", user.Id)) if err != nil { txDb.Rollback() return err } if currentOrder.AvailableQuantity.LessThanOrEqual(decimal.Zero) { txDb.Rollback() return fmt.Errorf("可领取为:%s", currentOrder.AvailableQuantity) } bs := constant.BsById(constant.BsClaimCurrentProfit) bs.ContextName = currentOrder.TableName() bs.ContextValue = fmt.Sprintf("%d", currentOrder.Id) // 修改资产与构建流水 err = s.GenBillAndActionAsset(txDb, user.Id, constant.CoinSymbolTD, decimal.Zero.Add(currentOrder.AvailableQuantity), decimal.Zero, bs) if err != nil { txDb.Rollback() return err } // AvailableQuantity decimal.Decimal `json:"availableQuantity" gorm:"type:decimal(25,8);default:0;comment:待领取数量"` // AvailableUsdAmount decimal.Decimal `json:"availableUsdAmount" gorm:"type:decimal(25,8);default:0;comment:待领取价值"` // CumClaimQuantity decimal.Decimal `json:"cumClaimQuantity" gorm:"type:decimal(25,8);default:0;comment:累计领取数量"` // CumClaimUsdAmount decimal.Decimal `json:"cumClaimUsdAmount" gorm:"type:decimal(25,8);default:0;comment:累计领取价值"` // LastClaimPeriod string `json:"lastClaimPeriod" gorm:"type:varchar(32);comment:最近领取期号"` updateStm := txDb.Model(&entity.StakeUserCurrentOrder{}). Where("id", currentOrder.Id). Where("version", currentOrder.Version). Updates(map[string]interface{}{ "available_quantity": gorm.Expr("available_quantity - ?", currentOrder.AvailableQuantity), "available_usd_amount": gorm.Expr("available_usd_amount - ?", currentOrder.AvailableUsdAmount), "cum_claim_quantity": gorm.Expr("cum_claim_quantity + ?", currentOrder.AvailableQuantity), "cum_claim_usd_amount": gorm.Expr("cum_claim_usd_amount + ?", currentOrder.AvailableUsdAmount), "last_claim_period": utils.NowPeriodNo(), "version": gorm.Expr("version + 1"), }) if updateStm.RowsAffected != 1 || updateStm.Error != nil { txDb.Rollback() return err } // todo:修改收益指标 txDb.Commit() return nil } // todo: 构建活期操作记录 func (s *CommonService) buildCurrentStakeOps(user *entity.User, quantity, price decimal.Decimal, bs *constant.BusinessType, adminId int64) *entity.StakeUserCurrentOpsRecord { return &entity.StakeUserCurrentOpsRecord{ UserId: user.Id, Uid: user.Uid, Price: price, BusinessNumber: bs.BusinessNumber, BusinessName: bs.BusinessName, ContextName: bs.ContextName, ContextValue: bs.ContextValue, BeforeQuantity: decimal.Zero, BeforeUsdAmount: decimal.Zero, Quantity: quantity, UsdValue: quantity.Mul(price), AfterUsdAmount: decimal.Zero, AfterQuantity: decimal.Zero, QueueState: entity.StakeQueueStateWaiting, AdminId: adminId, EffTime: 0, Remark: "", } }