magic_current_orders.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. package services
  2. import (
  3. "app/commons/constant"
  4. "app/commons/core"
  5. "app/commons/core/exchange"
  6. "app/commons/core/redisclient"
  7. "app/commons/model/entity"
  8. "app/commons/utils"
  9. "fmt"
  10. "github.com/shopspring/decimal"
  11. "gorm.io/gorm"
  12. )
  13. func (s *CommonService) BatchStakeUserCurrentOrder(db *gorm.DB) ([]*entity.StakeUserCurrentOrder, error) {
  14. return FindAllWithBatch[entity.StakeUserCurrentOrder](db)
  15. }
  16. func (s *CommonService) FirstStakeUserCurrentOrder(db *gorm.DB) (*entity.StakeUserCurrentOrder, error) {
  17. return GetOne[entity.StakeUserCurrentOrder](db)
  18. }
  19. func (s *CommonService) FirstStakeProduct(db *gorm.DB) (*entity.StakeProduct, error) {
  20. return GetOne[entity.StakeProduct](db)
  21. }
  22. // 创建/获取用户活期质押记录
  23. func (s *CommonService) CheckUserCurrentStakeUserOrder(txDb *gorm.DB, userId int64, userUid string) (*entity.StakeUserCurrentOrder, error) {
  24. order, err := s.FirstStakeUserCurrentOrder(txDb.Where("uid", userUid).Where("user_id", userId))
  25. if err == nil && order.Id != 0 {
  26. return order, nil
  27. }
  28. currentProduct, err := s.FirstStakeProduct(txDb.Where("pledge_mode", entity.StakeProductTypeCurrent))
  29. if err != nil {
  30. return nil, err
  31. }
  32. order = &entity.StakeUserCurrentOrder{
  33. UserId: userId,
  34. Uid: userUid,
  35. ProductId: currentProduct.Id,
  36. ProductName: currentProduct.Name,
  37. Symbol: currentProduct.Symbol,
  38. FirstPledgeDate: utils.TimeDate(),
  39. LastClaimPeriod: utils.NowPeriodNo(),
  40. }
  41. err = txDb.Create(&order).Error
  42. return order, err
  43. }
  44. // 获取用户产品纬度订单 -- 聚合订单
  45. // 资产修改 -- 必须加资产锁定
  46. // 使用场景 1 api质押 2 管理后台直接增加订单
  47. // 活期 -- 随进随出
  48. func (s *CommonService) CurrentStake(user *entity.User, product *entity.StakeProduct, quantity decimal.Decimal, adminId int64) error {
  49. // 获取分布式锁 存在用户购买商品 发放奖励等情况 可能会影响资产变化
  50. lockKey := fmt.Sprintf("%s:%d", constant.UserAssetLocker, user.Id)
  51. lock, err := redisclient.Lock(lockKey)
  52. if err != nil {
  53. return fmt.Errorf("failed to acquire lock: %v", err)
  54. }
  55. core.Log.Infof("用户ID:%s 资产锁获取", lockKey)
  56. defer func() {
  57. err = redisclient.UnlockSafe(lock)
  58. if err != nil {
  59. core.Log.Error(err)
  60. }
  61. core.Log.Infof("用户ID:%s 资产锁释放", lockKey)
  62. }()
  63. // 为用户质押
  64. symbolPrice, err := exchange.GetCurrentSymbolUsdPrice(constant.CoinSystemSymbol)
  65. if err != nil {
  66. core.Log.Error(err)
  67. return err
  68. }
  69. txDb := s.DB().Begin()
  70. bs := constant.BsById(constant.BsCurrentStake)
  71. // todo: 1 创建质押记录 magic_stake_user_ops_record -- 排队的逻辑
  72. ops := s.buildCurrentStakeOps(user, quantity, symbolPrice, bs, adminId)
  73. if err = txDb.Create(&ops).Error; err != nil {
  74. txDb.Rollback()
  75. return err
  76. }
  77. order, err := s.CheckUserCurrentStakeUserOrder(txDb, user.Id, user.Uid)
  78. if err != nil {
  79. txDb.Rollback()
  80. return err
  81. }
  82. // 未开启排队
  83. if !product.IsQueue {
  84. err = txDb.Model(&entity.StakeUserCurrentOpsRecord{}).
  85. Where("id", ops.Id).
  86. Updates(map[string]interface{}{
  87. "before_quantity": order.Quantity,
  88. "before_usd_amount": order.UsdAmount,
  89. "after_quantity": order.Quantity.Add(ops.Quantity),
  90. "after_usd_amount": order.UsdAmount.Add(ops.UsdValue),
  91. "queue_state": entity.StateQueueStateSuccess,
  92. }).Error
  93. if err != nil {
  94. txDb.Rollback()
  95. return err
  96. }
  97. err = txDb.Model(&entity.StakeUserCurrentOrder{}).
  98. Where("id", order.Id).
  99. Updates(map[string]interface{}{
  100. "cum_quantity": gorm.Expr("cum_quantity+?", ops.Quantity),
  101. "quantity": gorm.Expr("quantity+?", ops.Quantity),
  102. "cum_usd_amount": gorm.Expr("cum_usd_amount+?", ops.UsdValue),
  103. "usd_amount": gorm.Expr("usd_amount+?", ops.UsdValue),
  104. "version": gorm.Expr("version + 1"),
  105. }).Error
  106. if err != nil {
  107. txDb.Rollback()
  108. return err
  109. }
  110. }
  111. // 全局操作资产方法 -- 当为管理员后台操作 则表示不需要扣除资产
  112. if adminId == 0 {
  113. if err = s.GenBillAndActionAsset(txDb, user.Id, constant.CoinSystemSymbol, decimal.Zero.Sub(quantity), decimal.Zero, bs); err != nil {
  114. txDb.Rollback()
  115. return err
  116. }
  117. }
  118. txDb.Commit()
  119. // todo: 2 生效后写入聚合订单 -- magic_stake_user_order -- 排队逻辑
  120. return nil
  121. }
  122. // 赎回
  123. func (s *CommonService) CurrentRedeem(user *entity.User, amount decimal.Decimal, adminId int64) error {
  124. // 获取分布式锁 存在用户购买商品 发放奖励等情况 可能会影响资产变化
  125. lockKey := fmt.Sprintf("%s:%d", constant.UserAssetLocker, user.Id)
  126. lock, err := redisclient.Lock(lockKey)
  127. if err != nil {
  128. return fmt.Errorf("failed to acquire lock: %v", err)
  129. }
  130. core.Log.Infof("用户ID:%s 资产锁获取", lockKey)
  131. defer func() {
  132. err = redisclient.UnlockSafe(lock)
  133. if err != nil {
  134. core.Log.Error(err)
  135. }
  136. core.Log.Infof("用户ID:%s 资产锁释放", lockKey)
  137. }()
  138. // 为用户质押
  139. return nil
  140. }
  141. // 领取
  142. func (s *CommonService) CurrentProfitClaim(user *entity.User) error {
  143. txDb := s.DB().Begin()
  144. currentOrder, err := s.FirstStakeUserCurrentOrder(txDb.Where("user_id", user.Id))
  145. if err != nil {
  146. txDb.Rollback()
  147. return err
  148. }
  149. if currentOrder.AvailableQuantity.LessThanOrEqual(decimal.Zero) {
  150. txDb.Rollback()
  151. return fmt.Errorf("可领取为:%s", currentOrder.AvailableQuantity)
  152. }
  153. bs := constant.BsById(constant.BsClaimCurrentProfit)
  154. bs.ContextName = currentOrder.TableName()
  155. bs.ContextValue = fmt.Sprintf("%d", currentOrder.Id)
  156. // 修改资产与构建流水
  157. err = s.GenBillAndActionAsset(txDb, user.Id, constant.CoinSymbolTD, decimal.Zero.Add(currentOrder.AvailableQuantity), decimal.Zero, bs)
  158. if err != nil {
  159. txDb.Rollback()
  160. return err
  161. }
  162. // AvailableQuantity decimal.Decimal `json:"availableQuantity" gorm:"type:decimal(25,8);default:0;comment:待领取数量"`
  163. // AvailableUsdAmount decimal.Decimal `json:"availableUsdAmount" gorm:"type:decimal(25,8);default:0;comment:待领取价值"`
  164. // CumClaimQuantity decimal.Decimal `json:"cumClaimQuantity" gorm:"type:decimal(25,8);default:0;comment:累计领取数量"`
  165. // CumClaimUsdAmount decimal.Decimal `json:"cumClaimUsdAmount" gorm:"type:decimal(25,8);default:0;comment:累计领取价值"`
  166. // LastClaimPeriod string `json:"lastClaimPeriod" gorm:"type:varchar(32);comment:最近领取期号"`
  167. updateStm := txDb.Model(&entity.StakeUserCurrentOrder{}).
  168. Where("id", currentOrder.Id).
  169. Where("version", currentOrder.Version).
  170. Updates(map[string]interface{}{
  171. "available_quantity": gorm.Expr("available_quantity - ?", currentOrder.AvailableQuantity),
  172. "available_usd_amount": gorm.Expr("available_usd_amount - ?", currentOrder.AvailableUsdAmount),
  173. "cum_claim_quantity": gorm.Expr("cum_claim_quantity + ?", currentOrder.AvailableQuantity),
  174. "cum_claim_usd_amount": gorm.Expr("cum_claim_usd_amount + ?", currentOrder.AvailableUsdAmount),
  175. "last_claim_period": utils.NowPeriodNo(),
  176. "version": gorm.Expr("version + 1"),
  177. })
  178. if updateStm.RowsAffected != 1 || updateStm.Error != nil {
  179. txDb.Rollback()
  180. return err
  181. }
  182. // todo:修改收益指标
  183. txDb.Commit()
  184. return nil
  185. }
  186. // todo: 构建活期操作记录
  187. func (s *CommonService) buildCurrentStakeOps(user *entity.User, quantity, price decimal.Decimal, bs *constant.BusinessType, adminId int64) *entity.StakeUserCurrentOpsRecord {
  188. return &entity.StakeUserCurrentOpsRecord{
  189. UserId: user.Id,
  190. Uid: user.Uid,
  191. Price: price,
  192. BusinessNumber: bs.BusinessNumber,
  193. BusinessName: bs.BusinessName,
  194. ContextName: bs.ContextName,
  195. ContextValue: bs.ContextValue,
  196. BeforeQuantity: decimal.Zero,
  197. BeforeUsdAmount: decimal.Zero,
  198. Quantity: quantity,
  199. UsdValue: quantity.Mul(price),
  200. AfterUsdAmount: decimal.Zero,
  201. AfterQuantity: decimal.Zero,
  202. QueueState: entity.StakeQueueStateWaiting,
  203. AdminId: adminId,
  204. EffTime: 0,
  205. Remark: "",
  206. }
  207. }