diff --git a/controller/redemption.go b/controller/redemption.go
index a7e09a8a..50620597 100644
--- a/controller/redemption.go
+++ b/controller/redemption.go
@@ -5,6 +5,7 @@ import (
"one-api/common"
"one-api/model"
"strconv"
+ "errors"
"github.com/gin-gonic/gin"
)
@@ -126,6 +127,10 @@ func AddRedemption(c *gin.Context) {
})
return
}
+ if err := validateExpiredTime(redemption.ExpiredTime); err != nil {
+ c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
+ return
+ }
var keys []string
for i := 0; i < redemption.Count; i++ {
key := common.GetUUID()
@@ -135,6 +140,7 @@ func AddRedemption(c *gin.Context) {
Key: key,
CreatedTime: common.GetTimestamp(),
Quota: redemption.Quota,
+ ExpiredTime: redemption.ExpiredTime,
}
err = cleanRedemption.Insert()
if err != nil {
@@ -191,12 +197,18 @@ func UpdateRedemption(c *gin.Context) {
})
return
}
- if statusOnly != "" {
- cleanRedemption.Status = redemption.Status
- } else {
+ if statusOnly == "" {
+ if err := validateExpiredTime(redemption.ExpiredTime); err != nil {
+ c.JSON(http.StatusOK, gin.H{"success": false, "message": err.Error()})
+ return
+ }
// If you add more fields, please also update redemption.Update()
cleanRedemption.Name = redemption.Name
cleanRedemption.Quota = redemption.Quota
+ cleanRedemption.ExpiredTime = redemption.ExpiredTime
+ }
+ if statusOnly != "" {
+ cleanRedemption.Status = redemption.Status
}
err = cleanRedemption.Update()
if err != nil {
@@ -213,3 +225,27 @@ func UpdateRedemption(c *gin.Context) {
})
return
}
+
+func DeleteInvalidRedemption(c *gin.Context) {
+ rows, err := model.DeleteInvalidRedemptions()
+ if err != nil {
+ c.JSON(http.StatusOK, gin.H{
+ "success": false,
+ "message": err.Error(),
+ })
+ return
+ }
+ c.JSON(http.StatusOK, gin.H{
+ "success": true,
+ "message": "",
+ "data": rows,
+ })
+ return
+}
+
+func validateExpiredTime(expired int64) error {
+ if expired != 0 && expired < common.GetTimestamp() {
+ return errors.New("过期时间不能早于当前时间")
+ }
+ return nil
+}
diff --git a/model/redemption.go b/model/redemption.go
index 89c4ac8c..bf237668 100644
--- a/model/redemption.go
+++ b/model/redemption.go
@@ -21,6 +21,7 @@ type Redemption struct {
Count int `json:"count" gorm:"-:all"` // only for api request
UsedUserId int `json:"used_user_id"`
DeletedAt gorm.DeletedAt `gorm:"index"`
+ ExpiredTime int64 `json:"expired_time" gorm:"bigint"` // 过期时间,0 表示不过期
}
func GetAllRedemptions(startIdx int, num int) (redemptions []*Redemption, total int64, err error) {
@@ -131,6 +132,9 @@ func Redeem(key string, userId int) (quota int, err error) {
if redemption.Status != common.RedemptionCodeStatusEnabled {
return errors.New("该兑换码已被使用")
}
+ if redemption.ExpiredTime != 0 && redemption.ExpiredTime < common.GetTimestamp() {
+ return errors.New("该兑换码已过期")
+ }
err = tx.Model(&User{}).Where("id = ?", userId).Update("quota", gorm.Expr("quota + ?", redemption.Quota)).Error
if err != nil {
return err
@@ -162,7 +166,7 @@ func (redemption *Redemption) SelectUpdate() 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
- err = DB.Model(redemption).Select("name", "status", "quota", "redeemed_time").Updates(redemption).Error
+ err = DB.Model(redemption).Select("name", "status", "quota", "redeemed_time", "expired_time").Updates(redemption).Error
return err
}
@@ -183,3 +187,9 @@ func DeleteRedemptionById(id int) (err error) {
}
return redemption.Delete()
}
+
+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
+}
diff --git a/router/api-router.go b/router/api-router.go
index 0ab8be7f..851e9193 100644
--- a/router/api-router.go
+++ b/router/api-router.go
@@ -126,6 +126,7 @@ func SetApiRouter(router *gin.Engine) {
redemptionRoute.GET("/:id", controller.GetRedemption)
redemptionRoute.POST("/", controller.AddRedemption)
redemptionRoute.PUT("/", controller.UpdateRedemption)
+ redemptionRoute.DELETE("/invalid", controller.DeleteInvalidRedemption)
redemptionRoute.DELETE("/:id", controller.DeleteRedemption)
}
logRoute := apiRouter.Group("/log")
diff --git a/web/src/components/table/RedemptionsTable.js b/web/src/components/table/RedemptionsTable.js
index 7d70f179..e11a4657 100644
--- a/web/src/components/table/RedemptionsTable.js
+++ b/web/src/components/table/RedemptionsTable.js
@@ -59,7 +59,16 @@ function renderTimestamp(timestamp) {
const RedemptionsTable = () => {
const { t } = useTranslation();
- const renderStatus = (status) => {
+ const isExpired = (rec) => {
+ return rec.status === 1 && rec.expired_time !== 0 && rec.expired_time < Math.floor(Date.now() / 1000);
+ };
+
+ const renderStatus = (status, record) => {
+ if (isExpired(record)) {
+ return (
+