Skip to content

Commit 9e2f238

Browse files
committed
feat: now user can top up via redemption code (close #9)
1 parent e7a809b commit 9e2f238

File tree

13 files changed

+886
-14
lines changed

13 files changed

+886
-14
lines changed

common/constants.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ const (
9393
TokenStatusExhausted = 4
9494
)
9595

96+
const (
97+
RedemptionCodeStatusEnabled = 1 // don't use 0, 0 is the default value!
98+
RedemptionCodeStatusDisabled = 2 // also don't use 0
99+
RedemptionCodeStatusUsed = 3 // also don't use 0
100+
)
101+
96102
const (
97103
ChannelStatusUnknown = 0
98104
ChannelStatusEnabled = 1 // don't use 0, 0 is the default value!

controller/redemption.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package controller
2+
3+
import (
4+
"github.com/gin-gonic/gin"
5+
"net/http"
6+
"one-api/common"
7+
"one-api/model"
8+
"strconv"
9+
)
10+
11+
func GetAllRedemptions(c *gin.Context) {
12+
p, _ := strconv.Atoi(c.Query("p"))
13+
if p < 0 {
14+
p = 0
15+
}
16+
redemptions, err := model.GetAllRedemptions(p*common.ItemsPerPage, common.ItemsPerPage)
17+
if err != nil {
18+
c.JSON(http.StatusOK, gin.H{
19+
"success": false,
20+
"message": err.Error(),
21+
})
22+
return
23+
}
24+
c.JSON(http.StatusOK, gin.H{
25+
"success": true,
26+
"message": "",
27+
"data": redemptions,
28+
})
29+
return
30+
}
31+
32+
func SearchRedemptions(c *gin.Context) {
33+
keyword := c.Query("keyword")
34+
redemptions, err := model.SearchRedemptions(keyword)
35+
if err != nil {
36+
c.JSON(http.StatusOK, gin.H{
37+
"success": false,
38+
"message": err.Error(),
39+
})
40+
return
41+
}
42+
c.JSON(http.StatusOK, gin.H{
43+
"success": true,
44+
"message": "",
45+
"data": redemptions,
46+
})
47+
return
48+
}
49+
50+
func GetRedemption(c *gin.Context) {
51+
id, err := strconv.Atoi(c.Param("id"))
52+
if err != nil {
53+
c.JSON(http.StatusOK, gin.H{
54+
"success": false,
55+
"message": err.Error(),
56+
})
57+
return
58+
}
59+
redemption, err := model.GetRedemptionById(id)
60+
if err != nil {
61+
c.JSON(http.StatusOK, gin.H{
62+
"success": false,
63+
"message": err.Error(),
64+
})
65+
return
66+
}
67+
c.JSON(http.StatusOK, gin.H{
68+
"success": true,
69+
"message": "",
70+
"data": redemption,
71+
})
72+
return
73+
}
74+
75+
func AddRedemption(c *gin.Context) {
76+
redemption := model.Redemption{}
77+
err := c.ShouldBindJSON(&redemption)
78+
if err != nil {
79+
c.JSON(http.StatusOK, gin.H{
80+
"success": false,
81+
"message": err.Error(),
82+
})
83+
return
84+
}
85+
if len(redemption.Name) == 0 || len(redemption.Name) > 20 {
86+
c.JSON(http.StatusOK, gin.H{
87+
"success": false,
88+
"message": "兑换码名称长度必须在1-20之间",
89+
})
90+
return
91+
}
92+
if redemption.Count <= 0 {
93+
c.JSON(http.StatusOK, gin.H{
94+
"success": false,
95+
"message": "兑换码个数必须大于0",
96+
})
97+
return
98+
}
99+
if redemption.Count > 100 {
100+
c.JSON(http.StatusOK, gin.H{
101+
"success": false,
102+
"message": "一次兑换码批量生成的个数不能大于 100",
103+
})
104+
return
105+
}
106+
var keys []string
107+
for i := 0; i < redemption.Count; i++ {
108+
key := common.GetUUID()
109+
cleanRedemption := model.Redemption{
110+
UserId: c.GetInt("id"),
111+
Name: redemption.Name,
112+
Key: key,
113+
CreatedTime: common.GetTimestamp(),
114+
Quota: redemption.Quota,
115+
}
116+
err = cleanRedemption.Insert()
117+
if err != nil {
118+
c.JSON(http.StatusOK, gin.H{
119+
"success": false,
120+
"message": err.Error(),
121+
"data": keys,
122+
})
123+
return
124+
}
125+
keys = append(keys, key)
126+
}
127+
c.JSON(http.StatusOK, gin.H{
128+
"success": true,
129+
"message": "",
130+
"data": keys,
131+
})
132+
return
133+
}
134+
135+
func DeleteRedemption(c *gin.Context) {
136+
id, _ := strconv.Atoi(c.Param("id"))
137+
err := model.DeleteRedemptionById(id)
138+
if err != nil {
139+
c.JSON(http.StatusOK, gin.H{
140+
"success": false,
141+
"message": err.Error(),
142+
})
143+
return
144+
}
145+
c.JSON(http.StatusOK, gin.H{
146+
"success": true,
147+
"message": "",
148+
})
149+
return
150+
}
151+
152+
func UpdateRedemption(c *gin.Context) {
153+
statusOnly := c.Query("status_only")
154+
redemption := model.Redemption{}
155+
err := c.ShouldBindJSON(&redemption)
156+
if err != nil {
157+
c.JSON(http.StatusOK, gin.H{
158+
"success": false,
159+
"message": err.Error(),
160+
})
161+
return
162+
}
163+
cleanRedemption, err := model.GetRedemptionById(redemption.Id)
164+
if err != nil {
165+
c.JSON(http.StatusOK, gin.H{
166+
"success": false,
167+
"message": err.Error(),
168+
})
169+
return
170+
}
171+
if statusOnly != "" {
172+
cleanRedemption.Status = redemption.Status
173+
} else {
174+
// If you add more fields, please also update redemption.Update()
175+
cleanRedemption.Name = redemption.Name
176+
cleanRedemption.Quota = redemption.Quota
177+
}
178+
err = cleanRedemption.Update()
179+
if err != nil {
180+
c.JSON(http.StatusOK, gin.H{
181+
"success": false,
182+
"message": err.Error(),
183+
})
184+
return
185+
}
186+
c.JSON(http.StatusOK, gin.H{
187+
"success": true,
188+
"message": "",
189+
"data": cleanRedemption,
190+
})
191+
return
192+
}

controller/token.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,34 @@ func UpdateToken(c *gin.Context) {
201201
})
202202
return
203203
}
204+
205+
type topUpRequest struct {
206+
Id int `json:"id"`
207+
Key string `json:"key"`
208+
}
209+
210+
func TopUp(c *gin.Context) {
211+
req := topUpRequest{}
212+
err := c.ShouldBindJSON(&req)
213+
if err != nil {
214+
c.JSON(http.StatusOK, gin.H{
215+
"success": false,
216+
"message": err.Error(),
217+
})
218+
return
219+
}
220+
quota, err := model.Redeem(req.Key, req.Id)
221+
if err != nil {
222+
c.JSON(http.StatusOK, gin.H{
223+
"success": false,
224+
"message": err.Error(),
225+
})
226+
return
227+
}
228+
c.JSON(http.StatusOK, gin.H{
229+
"success": true,
230+
"message": "",
231+
"data": quota,
232+
})
233+
return
234+
}

model/main.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ func InitDB() (err error) {
6969
if err != nil {
7070
return err
7171
}
72+
err = db.AutoMigrate(&Redemption{})
73+
if err != nil {
74+
return err
75+
}
7276
err = createRootAccountIfNeed()
7377
return err
7478
} else {

model/redemption.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package model
2+
3+
import (
4+
"errors"
5+
_ "gorm.io/driver/sqlite"
6+
"one-api/common"
7+
)
8+
9+
type Redemption struct {
10+
Id int `json:"id"`
11+
UserId int `json:"user_id"`
12+
Key string `json:"key" gorm:"uniqueIndex"`
13+
Status int `json:"status" gorm:"default:1"`
14+
Name string `json:"name" gorm:"index"`
15+
Quota int `json:"quota" gorm:"default:100"`
16+
CreatedTime int64 `json:"created_time" gorm:"bigint"`
17+
RedeemedTime int64 `json:"redeemed_time" gorm:"bigint"`
18+
Count int `json:"count" gorm:"-:all"` // only for api request
19+
}
20+
21+
func GetAllRedemptions(startIdx int, num int) ([]*Redemption, error) {
22+
var redemptions []*Redemption
23+
var err error
24+
err = DB.Order("id desc").Limit(num).Offset(startIdx).Find(&redemptions).Error
25+
return redemptions, err
26+
}
27+
28+
func SearchRedemptions(keyword string) (redemptions []*Redemption, err error) {
29+
err = DB.Where("id = ? or name LIKE ?", keyword, keyword+"%").Find(&redemptions).Error
30+
return redemptions, err
31+
}
32+
33+
func GetRedemptionById(id int) (*Redemption, error) {
34+
if id == 0 {
35+
return nil, errors.New("id 为空!")
36+
}
37+
redemption := Redemption{Id: id}
38+
var err error = nil
39+
err = DB.First(&redemption, "id = ?", id).Error
40+
return &redemption, err
41+
}
42+
43+
func Redeem(key string, tokenId int) (quota int, err error) {
44+
if key == "" {
45+
return 0, errors.New("未提供兑换码")
46+
}
47+
if tokenId == 0 {
48+
return 0, errors.New("未提供 token id")
49+
}
50+
redemption := &Redemption{}
51+
err = DB.Where("key = ?", key).First(redemption).Error
52+
if err != nil {
53+
return 0, errors.New("无效的兑换码")
54+
}
55+
if redemption.Status != common.RedemptionCodeStatusEnabled {
56+
return 0, errors.New("该兑换码已被使用")
57+
}
58+
err = TopUpToken(tokenId, redemption.Quota)
59+
if err != nil {
60+
return 0, err
61+
}
62+
go func() {
63+
redemption.RedeemedTime = common.GetTimestamp()
64+
redemption.Status = common.RedemptionCodeStatusUsed
65+
err := redemption.SelectUpdate()
66+
if err != nil {
67+
common.SysError("更新兑换码状态失败:" + err.Error())
68+
}
69+
}()
70+
return redemption.Quota, nil
71+
}
72+
73+
func (redemption *Redemption) Insert() error {
74+
var err error
75+
err = DB.Create(redemption).Error
76+
return err
77+
}
78+
79+
func (redemption *Redemption) SelectUpdate() error {
80+
// This can update zero values
81+
return DB.Model(redemption).Select("redeemed_time", "status").Updates(redemption).Error
82+
}
83+
84+
// Update Make sure your token's fields is completed, because this will update non-zero values
85+
func (redemption *Redemption) Update() error {
86+
var err error
87+
err = DB.Model(redemption).Select("name", "status", "redeemed_time").Updates(redemption).Error
88+
return err
89+
}
90+
91+
func (redemption *Redemption) Delete() error {
92+
var err error
93+
err = DB.Delete(redemption).Error
94+
return err
95+
}
96+
97+
func DeleteRedemptionById(id int) (err error) {
98+
if id == 0 {
99+
return errors.New("id 为空!")
100+
}
101+
redemption := Redemption{Id: id}
102+
err = DB.Where(redemption).First(&redemption).Error
103+
if err != nil {
104+
return err
105+
}
106+
return redemption.Delete()
107+
}

model/token.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,3 +123,8 @@ func DecreaseTokenRemainTimesById(id int) (err error) {
123123
err = DB.Model(&Token{}).Where("id = ?", id).Update("remain_times", gorm.Expr("remain_times - ?", 1)).Error
124124
return err
125125
}
126+
127+
func TopUpToken(id int, times int) (err error) {
128+
err = DB.Model(&Token{}).Where("id = ?", id).Update("remain_times", gorm.Expr("remain_times + ?", times)).Error
129+
return err
130+
}

router/api-router.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,21 @@ func SetApiRouter(router *gin.Engine) {
7070
{
7171
tokenRoute.GET("/", controller.GetAllTokens)
7272
tokenRoute.GET("/search", controller.SearchTokens)
73+
tokenRoute.POST("/topup", controller.TopUp)
7374
tokenRoute.GET("/:id", controller.GetToken)
7475
tokenRoute.POST("/", controller.AddToken)
7576
tokenRoute.PUT("/", controller.UpdateToken)
7677
tokenRoute.DELETE("/:id", controller.DeleteToken)
7778
}
79+
redemptionRoute := apiRouter.Group("/redemption")
80+
redemptionRoute.Use(middleware.AdminAuth())
81+
{
82+
redemptionRoute.GET("/", controller.GetAllRedemptions)
83+
redemptionRoute.GET("/search", controller.SearchRedemptions)
84+
redemptionRoute.GET("/:id", controller.GetRedemption)
85+
redemptionRoute.POST("/", controller.AddRedemption)
86+
redemptionRoute.PUT("/", controller.UpdateRedemption)
87+
redemptionRoute.DELETE("/:id", controller.DeleteRedemption)
88+
}
7889
}
7990
}

0 commit comments

Comments
 (0)