2023-04-26 17:02:26 +08:00
package model
import (
"errors"
2023-06-10 16:04:04 +08:00
"fmt"
2023-04-26 17:02:26 +08:00
"one-api/common"
2025-08-14 20:05:06 +08:00
"one-api/logger"
2024-12-31 15:28:25 +08:00
"strconv"
"gorm.io/gorm"
2023-04-26 17:02:26 +08:00
)
type Redemption struct {
2024-03-11 18:28:51 +08:00
Id int ` json:"id" `
UserId int ` json:"user_id" `
Key string ` json:"key" gorm:"type:char(32);uniqueIndex" `
Status int ` json:"status" gorm:"default:1" `
Name string ` json:"name" gorm:"index" `
Quota int ` json:"quota" gorm:"default:100" `
CreatedTime int64 ` json:"created_time" gorm:"bigint" `
RedeemedTime int64 ` json:"redeemed_time" gorm:"bigint" `
Count int ` json:"count" gorm:"-:all" ` // only for api request
UsedUserId int ` json:"used_user_id" `
DeletedAt gorm . DeletedAt ` gorm:"index" `
2025-06-13 20:51:20 +08:00
ExpiredTime int64 ` json:"expired_time" gorm:"bigint" ` // 过期时间, 0 表示不过期
2023-04-26 17:02:26 +08:00
}
2024-12-31 15:28:25 +08:00
func GetAllRedemptions ( startIdx int , num int ) ( redemptions [ ] * Redemption , total int64 , err error ) {
// 开始事务
tx := DB . Begin ( )
if tx . Error != nil {
return nil , 0 , tx . Error
}
defer func ( ) {
if r := recover ( ) ; r != nil {
tx . Rollback ( )
}
} ( )
// 获取总数
err = tx . Model ( & Redemption { } ) . Count ( & total ) . Error
if err != nil {
tx . Rollback ( )
return nil , 0 , err
}
// 获取分页数据
err = tx . Order ( "id desc" ) . Limit ( num ) . Offset ( startIdx ) . Find ( & redemptions ) . Error
if err != nil {
tx . Rollback ( )
return nil , 0 , err
}
// 提交事务
if err = tx . Commit ( ) . Error ; err != nil {
return nil , 0 , err
}
return redemptions , total , nil
2023-04-26 17:02:26 +08:00
}
2024-12-31 15:28:25 +08:00
func SearchRedemptions ( keyword string , startIdx int , num int ) ( redemptions [ ] * Redemption , total int64 , err error ) {
tx := DB . Begin ( )
if tx . Error != nil {
return nil , 0 , tx . Error
}
defer func ( ) {
if r := recover ( ) ; r != nil {
tx . Rollback ( )
}
} ( )
// Build query based on keyword type
query := tx . Model ( & Redemption { } )
// Only try to convert to ID if the string represents a valid integer
if id , err := strconv . Atoi ( keyword ) ; err == nil {
query = query . Where ( "id = ? OR name LIKE ?" , id , keyword + "%" )
} else {
query = query . Where ( "name LIKE ?" , keyword + "%" )
}
// Get total count
err = query . Count ( & total ) . Error
if err != nil {
tx . Rollback ( )
return nil , 0 , err
}
// Get paginated data
err = query . Order ( "id desc" ) . Limit ( num ) . Offset ( startIdx ) . Find ( & redemptions ) . Error
if err != nil {
tx . Rollback ( )
return nil , 0 , err
}
if err = tx . Commit ( ) . Error ; err != nil {
return nil , 0 , err
}
return redemptions , total , nil
2023-04-26 17:02:26 +08:00
}
func GetRedemptionById ( id int ) ( * Redemption , error ) {
if id == 0 {
return nil , errors . New ( "id 为空!" )
}
redemption := Redemption { Id : id }
var err error = nil
err = DB . First ( & redemption , "id = ?" , id ) . Error
return & redemption , err
}
2023-05-16 11:26:09 +08:00
func Redeem ( key string , userId int ) ( quota int , err error ) {
2023-04-26 17:02:26 +08:00
if key == "" {
return 0 , errors . New ( "未提供兑换码" )
}
2023-05-16 11:26:09 +08:00
if userId == 0 {
return 0 , errors . New ( "无效的 user id" )
2023-04-26 17:02:26 +08:00
}
redemption := & Redemption { }
2023-07-07 21:26:45 +08:00
2023-10-22 18:38:29 +08:00
keyCol := "`key`"
if common . UsingPostgreSQL {
keyCol = ` "key" `
}
2024-04-04 19:18:00 +08:00
common . RandomSleep ( )
2023-07-07 21:26:45 +08:00
err = DB . Transaction ( func ( tx * gorm . DB ) error {
2023-10-22 18:38:29 +08:00
err := tx . Set ( "gorm:query_option" , "FOR UPDATE" ) . Where ( keyCol + " = ?" , key ) . First ( redemption ) . Error
2023-04-26 17:02:26 +08:00
if err != nil {
2023-07-07 21:26:45 +08:00
return errors . New ( "无效的兑换码" )
2023-04-26 17:02:26 +08:00
}
2023-07-07 21:26:45 +08:00
if redemption . Status != common . RedemptionCodeStatusEnabled {
return errors . New ( "该兑换码已被使用" )
}
2025-06-13 20:51:20 +08:00
if redemption . ExpiredTime != 0 && redemption . ExpiredTime < common . GetTimestamp ( ) {
return errors . New ( "该兑换码已过期" )
}
2023-07-23 19:34:23 +08:00
err = tx . Model ( & User { } ) . Where ( "id = ?" , userId ) . Update ( "quota" , gorm . Expr ( "quota + ?" , redemption . Quota ) ) . Error
2023-07-07 21:26:45 +08:00
if err != nil {
return err
}
redemption . RedeemedTime = common . GetTimestamp ( )
redemption . Status = common . RedemptionCodeStatusUsed
2023-11-09 17:08:32 +08:00
redemption . UsedUserId = userId
2023-07-23 19:34:23 +08:00
err = tx . Save ( redemption ) . Error
return err
2023-07-07 21:26:45 +08:00
} )
if err != nil {
return 0 , errors . New ( "兑换失败," + err . Error ( ) )
}
2025-08-14 20:05:06 +08:00
RecordLog ( userId , LogTypeTopup , fmt . Sprintf ( "通过兑换码充值 %s, 兑换码ID %d" , logger . LogQuota ( redemption . Quota ) , redemption . Id ) )
2023-04-26 17:02:26 +08:00
return redemption . Quota , nil
}
func ( redemption * Redemption ) Insert ( ) error {
var err error
err = DB . Create ( redemption ) . Error
return err
}
func ( redemption * Redemption ) SelectUpdate ( ) error {
// This can update zero values
return DB . Model ( redemption ) . Select ( "redeemed_time" , "status" ) . Updates ( redemption ) . Error
}
// Update Make sure your token's fields is completed, because this will update non-zero values
func ( redemption * Redemption ) Update ( ) error {
var err error
2025-06-13 20:51:20 +08:00
err = DB . Model ( redemption ) . Select ( "name" , "status" , "quota" , "redeemed_time" , "expired_time" ) . Updates ( redemption ) . Error
2023-04-26 17:02:26 +08:00
return err
}
func ( redemption * Redemption ) Delete ( ) error {
var err error
err = DB . Delete ( redemption ) . Error
return err
}
func DeleteRedemptionById ( id int ) ( err error ) {
if id == 0 {
return errors . New ( "id 为空!" )
}
redemption := Redemption { Id : id }
err = DB . Where ( redemption ) . First ( & redemption ) . Error
if err != nil {
return err
}
return redemption . Delete ( )
}
2025-06-13 20:51:20 +08:00
func DeleteInvalidRedemptions ( ) ( int64 , error ) {
now := common . GetTimestamp ( )
result := DB . Where ( "status IN ? OR (status = ? AND expired_time != 0 AND expired_time < ?)" , [ ] int { common . RedemptionCodeStatusUsed , common . RedemptionCodeStatusDisabled } , common . RedemptionCodeStatusEnabled , now ) . Delete ( & Redemption { } )
return result . RowsAffected , result . Error
}