Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 

218 wiersze
6.0 KiB

  1. package model
  2. import (
  3. "errors"
  4. "fmt"
  5. "strconv"
  6. "github.com/QuantumNous/new-api/common"
  7. "github.com/QuantumNous/new-api/logger"
  8. "gorm.io/gorm"
  9. )
  10. // ErrRedeemFailed is returned when redemption fails due to database error
  11. var ErrRedeemFailed = errors.New("redeem.failed")
  12. type Redemption struct {
  13. Id int `json:"id"`
  14. UserId int `json:"user_id"`
  15. Key string `json:"key" gorm:"type:char(32);uniqueIndex"`
  16. Status int `json:"status" gorm:"default:1"`
  17. Name string `json:"name" gorm:"index"`
  18. Remark string `json:"remark"`
  19. Quota int `json:"quota" gorm:"default:100"`
  20. CreatedTime int64 `json:"created_time" gorm:"bigint"`
  21. RedeemedTime int64 `json:"redeemed_time" gorm:"bigint"`
  22. Count int `json:"count" gorm:"-:all"` // only for api request
  23. UsedUserId int `json:"used_user_id"`
  24. DeletedAt gorm.DeletedAt `gorm:"index"`
  25. ExpiredTime int64 `json:"expired_time" gorm:"bigint"` // 过期时间,0 表示不过期
  26. }
  27. func GetAllRedemptions(startIdx int, num int) (redemptions []*Redemption, total int64, err error) {
  28. // 开始事务
  29. tx := DB.Begin()
  30. if tx.Error != nil {
  31. return nil, 0, tx.Error
  32. }
  33. defer func() {
  34. if r := recover(); r != nil {
  35. tx.Rollback()
  36. }
  37. }()
  38. // 获取总数
  39. err = tx.Model(&Redemption{}).Count(&total).Error
  40. if err != nil {
  41. tx.Rollback()
  42. return nil, 0, err
  43. }
  44. // 获取分页数据
  45. err = tx.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error
  46. if err != nil {
  47. tx.Rollback()
  48. return nil, 0, err
  49. }
  50. // 提交事务
  51. if err = tx.Commit().Error; err != nil {
  52. return nil, 0, err
  53. }
  54. return redemptions, total, nil
  55. }
  56. func SearchRedemptions(keyword string, startIdx int, num int) (redemptions []*Redemption, total int64, err error) {
  57. tx := DB.Begin()
  58. if tx.Error != nil {
  59. return nil, 0, tx.Error
  60. }
  61. defer func() {
  62. if r := recover(); r != nil {
  63. tx.Rollback()
  64. }
  65. }()
  66. // Build query based on keyword type
  67. query := tx.Model(&Redemption{})
  68. // Only try to convert to ID if the string represents a valid integer
  69. if id, err := strconv.Atoi(keyword); err == nil {
  70. query = query.Where("id = ? OR name LIKE ? OR remark LIKE ?", id, keyword+"%", keyword+"%")
  71. } else {
  72. query = query.Where("name LIKE ? OR remark LIKE ?", keyword+"%", keyword+"%")
  73. }
  74. // Get total count
  75. err = query.Count(&total).Error
  76. if err != nil {
  77. tx.Rollback()
  78. return nil, 0, err
  79. }
  80. // Get paginated data
  81. err = query.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error
  82. if err != nil {
  83. tx.Rollback()
  84. return nil, 0, err
  85. }
  86. if err = tx.Commit().Error; err != nil {
  87. return nil, 0, err
  88. }
  89. return redemptions, total, nil
  90. }
  91. func GetRedemptionById(id int) (*Redemption, error) {
  92. if id == 0 {
  93. return nil, errors.New("id 为空!")
  94. }
  95. redemption := Redemption{Id: id}
  96. var err error = nil
  97. err = DB.First(&redemption, "id = ?", id).Error
  98. return &redemption, err
  99. }
  100. func Redeem(key string, userId int) (quota int, err error) {
  101. if key == "" {
  102. return 0, errors.New("未提供兑换码")
  103. }
  104. if userId == 0 {
  105. return 0, errors.New("无效的 user id")
  106. }
  107. redemption := &Redemption{}
  108. keyCol := "`key`"
  109. if common.UsingPostgreSQL {
  110. keyCol = `"key"`
  111. }
  112. common.RandomSleep()
  113. err = DB.Transaction(func(tx *gorm.DB) error {
  114. err := tx.Set("gorm:query_option", "FOR UPDATE").Where(keyCol+" = ?", key).First(redemption).Error
  115. if err != nil {
  116. return errors.New("无效的兑换码")
  117. }
  118. if redemption.Status != common.RedemptionCodeStatusEnabled {
  119. return errors.New("该兑换码已被使用")
  120. }
  121. if redemption.ExpiredTime != 0 && redemption.ExpiredTime < common.GetTimestamp() {
  122. return errors.New("该兑换码已过期")
  123. }
  124. err = tx.Model(&User{}).Where("id = ?", userId).Update("quota", gorm.Expr("quota + ?", redemption.Quota)).Error
  125. if err != nil {
  126. return err
  127. }
  128. redemption.RedeemedTime = common.GetTimestamp()
  129. redemption.Status = common.RedemptionCodeStatusUsed
  130. redemption.UsedUserId = userId
  131. err = tx.Save(redemption).Error
  132. return err
  133. })
  134. if err != nil {
  135. common.SysError("redemption failed: " + err.Error())
  136. return 0, ErrRedeemFailed
  137. }
  138. // 触发余额更新回调(在事务外)
  139. if quotaUpdateCallback != nil {
  140. newQuota, _ := GetUserQuota(userId, true)
  141. // 使用 defer/recover 保护回调
  142. func() {
  143. defer func() {
  144. if r := recover(); r != nil {
  145. common.SysError(fmt.Sprintf("quota update callback panic in Redeem: %v", r))
  146. }
  147. }()
  148. quotaUpdateCallback(userId, newQuota)
  149. }()
  150. }
  151. RecordLog(userId, LogTypeTopup, fmt.Sprintf("通过兑换码充值 %s,兑换码ID %d", logger.LogQuota(redemption.Quota), redemption.Id))
  152. return redemption.Quota, nil
  153. }
  154. func (redemption *Redemption) Insert() error {
  155. var err error
  156. err = DB.Create(redemption).Error
  157. return err
  158. }
  159. func (redemption *Redemption) SelectUpdate() error {
  160. // This can update zero values
  161. return DB.Model(redemption).Select("redeemed_time", "status").Updates(redemption).Error
  162. }
  163. // Update Make sure your token's fields is completed, because this will update non-zero values
  164. func (redemption *Redemption) Update() error {
  165. var err error
  166. err = DB.Model(redemption).Select("name", "remark", "status", "quota", "redeemed_time", "expired_time").Updates(redemption).Error
  167. return err
  168. }
  169. func (redemption *Redemption) Delete() error {
  170. var err error
  171. err = DB.Delete(redemption).Error
  172. return err
  173. }
  174. func DeleteRedemptionById(id int) (err error) {
  175. if id == 0 {
  176. return errors.New("id 为空!")
  177. }
  178. redemption := Redemption{Id: id}
  179. err = DB.Where(redemption).First(&redemption).Error
  180. if err != nil {
  181. return err
  182. }
  183. return redemption.Delete()
  184. }
  185. func DeleteInvalidRedemptions() (int64, error) {
  186. now := common.GetTimestamp()
  187. result := DB.Where("status IN ? OR (status = ? AND expired_time != 0 AND expired_time < ?)", []int{common.RedemptionCodeStatusUsed, common.RedemptionCodeStatusDisabled}, common.RedemptionCodeStatusEnabled, now).Delete(&Redemption{})
  188. return result.RowsAffected, result.Error
  189. }