Files
2026-03-15 20:48:19 +08:00

325 lines
8.0 KiB
Go

package handler
import (
"context"
"net/http"
"strconv"
"strings"
"time"
"gpt-plus/internal/db"
"gpt-plus/pkg/provider/card"
"github.com/gin-gonic/gin"
)
func RegisterCardRoutes(api *gin.RouterGroup) {
// Cards
api.GET("/cards", ListCards)
api.GET("/cards/active", GetActiveCard)
api.GET("/cards/stats", GetCardStats)
api.POST("/cards", AddCard)
api.PUT("/cards/:id/activate", ActivateCard)
api.PUT("/cards/:id/status", UpdateCardStatus)
api.DELETE("/cards/:id", DeleteCard)
// Card codes
api.GET("/card-codes", ListCardCodes)
api.GET("/card-codes/stats", GetCardCodeStats)
api.POST("/card-codes/import", ImportCardCodes)
api.POST("/card-codes/redeem", RedeemCardCode)
api.DELETE("/card-codes/:id", DeleteCardCode)
}
func ListCards(c *gin.Context) {
d := db.GetDB()
query := d.Model(&db.Card{}).Order("created_at DESC")
if status := c.Query("status"); status != "" {
query = query.Where("status = ?", status)
}
p := db.PaginationParams{
Page: intQuery(c, "page", 1),
Size: intQuery(c, "size", 20),
}
var cards []db.Card
result, err := db.Paginate(query, p, &cards)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
for i := range cards {
number, _ := db.Decrypt(cards[i].NumberEnc)
cards[i].NumberLast4 = number
cvc, _ := db.Decrypt(cards[i].CVCEnc)
cards[i].CVCPlain = cvc
}
result.Items = cards
c.JSON(http.StatusOK, result)
}
func GetActiveCard(c *gin.Context) {
d := db.GetDB()
var card db.Card
if err := d.Where("status = ?", "active").First(&card).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "无激活的卡片"})
return
}
number, _ := db.Decrypt(card.NumberEnc)
card.NumberLast4 = number
cvc, _ := db.Decrypt(card.CVCEnc)
card.CVCPlain = cvc
c.JSON(http.StatusOK, card)
}
func GetCardStats(c *gin.Context) {
c.JSON(http.StatusOK, db.GetCardStats(db.GetDB()))
}
type addCardRequest struct {
Number string `json:"number" binding:"required"`
CVC string `json:"cvc" binding:"required"`
ExpMonth string `json:"exp_month" binding:"required"`
ExpYear string `json:"exp_year" binding:"required"`
Name string `json:"name"`
Country string `json:"country"`
}
func AddCard(c *gin.Context) {
var req addCardRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
numberEnc, _ := db.Encrypt(req.Number)
cvcEnc, _ := db.Encrypt(req.CVC)
maxBinds := 0 // 0 = unlimited
var cfg db.SystemConfig
if db.GetDB().Where("key = ?", "card.max_binds").First(&cfg).Error == nil {
maxBinds, _ = strconv.Atoi(cfg.Value)
}
card := &db.Card{
NumberHash: db.HashSHA256(req.Number),
NumberEnc: numberEnc,
CVCEnc: cvcEnc,
ExpMonth: req.ExpMonth,
ExpYear: req.ExpYear,
Name: req.Name,
Country: req.Country,
Source: "manual",
Status: "available",
MaxBinds: maxBinds,
}
if err := db.GetDB().Create(card).Error; err != nil {
if strings.Contains(err.Error(), "UNIQUE") {
c.JSON(http.StatusConflict, gin.H{"error": "该卡号已存在"})
return
}
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"id": card.ID, "message": "卡片已添加"})
}
func ActivateCard(c *gin.Context) {
id := c.Param("id")
d := db.GetDB()
// Deactivate current active card
d.Model(&db.Card{}).Where("status = ?", "active").Update("status", "available")
var card db.Card
if err := d.First(&card, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "卡片不存在"})
return
}
card.Status = "active"
d.Save(&card)
c.JSON(http.StatusOK, gin.H{"message": "已激活"})
}
func UpdateCardStatus(c *gin.Context) {
id := c.Param("id")
var req struct {
Status string `json:"status" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.Status != "disabled" && req.Status != "available" {
c.JSON(http.StatusBadRequest, gin.H{"error": "状态只能为 disabled 或 available"})
return
}
if err := db.GetDB().Model(&db.Card{}).Where("id = ?", id).Update("status", req.Status).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "状态已更新"})
}
func DeleteCard(c *gin.Context) {
if err := db.GetDB().Delete(&db.Card{}, c.Param("id")).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "已删除"})
}
// Card Codes
func ListCardCodes(c *gin.Context) {
d := db.GetDB()
query := d.Model(&db.CardCode{}).Order("created_at DESC")
if status := c.Query("status"); status != "" {
query = query.Where("status = ?", status)
}
p := db.PaginationParams{Page: intQuery(c, "page", 1), Size: intQuery(c, "size", 20)}
var codes []db.CardCode
result, err := db.Paginate(query, p, &codes)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, result)
}
func GetCardCodeStats(c *gin.Context) {
c.JSON(http.StatusOK, db.GetCardCodeStats(db.GetDB()))
}
func ImportCardCodes(c *gin.Context) {
var req struct {
Codes []string `json:"codes" binding:"required"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
d := db.GetDB()
var count int
for _, code := range req.Codes {
code = strings.TrimSpace(code)
if code == "" {
continue
}
cc := &db.CardCode{Code: code, Status: "unused"}
if d.Create(cc).Error == nil {
count++
}
}
c.JSON(http.StatusOK, gin.H{"imported": count})
}
func RedeemCardCode(c *gin.Context) {
var req struct {
ID uint `json:"id"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
d := db.GetDB()
var code db.CardCode
if err := d.First(&code, req.ID).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "卡密不存在"})
return
}
if code.Status != "unused" {
c.JSON(http.StatusBadRequest, gin.H{"error": "该卡密状态不是 unused"})
return
}
code.Status = "redeeming"
d.Save(&code)
// Redeem in background
go func() {
var cfgBase db.SystemConfig
apiBase := ""
if db.GetDB().Where("key = ?", "card.api_base_url").First(&cfgBase).Error == nil {
apiBase = cfgBase.Value
}
if apiBase == "" {
code.Status = "failed"
code.Error = "开卡 API 地址未配置"
d.Save(&code)
return
}
apiProv, err := card.NewAPIProvider(card.APIProviderConfig{
BaseURL: apiBase,
Codes: []string{code.Code},
PoolCfg: card.PoolConfig{MultiBind: true, MaxBinds: 999},
})
if err != nil {
code.Status = "failed"
code.Error = err.Error()
d.Save(&code)
return
}
cardInfo, err := apiProv.GetCard(context.Background())
if err != nil {
code.Status = "failed"
code.Error = err.Error()
d.Save(&code)
return
}
numberEnc, _ := db.Encrypt(cardInfo.Number)
cvcEnc, _ := db.Encrypt(cardInfo.CVC)
newCard := &db.Card{
NumberHash: db.HashSHA256(cardInfo.Number),
NumberEnc: numberEnc,
CVCEnc: cvcEnc,
ExpMonth: cardInfo.ExpMonth,
ExpYear: cardInfo.ExpYear,
Name: cardInfo.Name,
Country: cardInfo.Country,
Source: "api",
CardCodeID: &code.ID,
Status: "available",
MaxBinds: 1,
}
d.Create(newCard)
now := time.Now()
code.Status = "redeemed"
code.CardID = &newCard.ID
code.RedeemedAt = &now
d.Save(&code)
}()
c.JSON(http.StatusOK, gin.H{"message": "兑换已开始"})
}
func DeleteCardCode(c *gin.Context) {
if err := db.GetDB().Delete(&db.CardCode{}, c.Param("id")).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"message": "已删除"})
}
func intQuery(c *gin.Context, key string, def int) int {
v := c.Query(key)
if v == "" {
return def
}
n, err := strconv.Atoi(v)
if err != nil {
return def
}
return n
}