yunzerwebsiteallinone/go/controllers/api_getcard.go
2026-06-17 23:49:19 +08:00

313 lines
9.3 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package controllers
import (
"fmt"
"strconv"
"strings"
"time"
"server/models"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
// ApiGetCardController 对外提卡接口(无需登录)
// GET /api/getcard?type=xianyu&module=cursor
type ApiGetCardController struct {
beego.Controller
}
// validPlatformTypes 支持的来源平台
var validPlatformTypes = map[string]bool{
"xianyu": true,
"pinduoduo": true,
"jingdong": true,
"douyin": true,
"local": true,
"xubei": true,
}
// validModules 支持的号池模块
var validModules = map[string]bool{
"cursor": true,
"windsurf": true,
"krio": true,
"codex": true,
}
func (c *ApiGetCardController) cardErr(_ int, _ int, msg string) {
c.Ctx.Output.SetStatus(200)
c.Ctx.Output.Header("Content-Type", "text/plain; charset=utf-8")
_ = c.Ctx.Output.Body([]byte("error:" + msg))
}
func (c *ApiGetCardController) cardOK(text string) {
c.Ctx.Output.SetStatus(200)
c.Ctx.Output.Header("Content-Type", "text/plain; charset=utf-8")
_ = c.Ctx.Output.Body([]byte(text))
}
// GetCard 提取一张卡(不可重复提取)
// GET /api/getcard?type=xianyu&module=cursor&data_type=tk
//
// 参数:
// - type (必填) 来源平台xianyu / taobao / pinduoduo / jingdong / local / xubei
// - module (必填) 号池模块cursor / windsurf / krio / codex
// - data_type (可选) 账号类型account / tk / account_tk不传则取任意未提取的
// - id/start_id/current_id (可选) 起始 ID从该 ID 开始向后提取,避免传 896 却取到 892
func (c *ApiGetCardController) GetCard() {
platform := c.GetString("type")
module := c.GetString("module")
dataType := c.GetString("data_type")
startID, err := c.readOptionalStartID()
if err != nil {
c.cardErr(400, 400, err.Error())
return
}
// 读取机器码/MAC
machineCode := strings.TrimSpace(c.GetString("machine_code"))
if machineCode == "" {
machineCode = strings.TrimSpace(c.GetString("machineCode"))
}
if machineCode == "" {
machineCode = strings.TrimSpace(c.GetString("mac"))
}
// 参数校验
if platform == "" {
c.cardErr(400, 400, "缺少参数 type来源平台")
return
}
if !validPlatformTypes[platform] {
c.cardErr(400, 400, fmt.Sprintf("不支持的平台类型: %s支持: xianyu/taobao/pinduoduo/jingdong/local/xubei", platform))
return
}
if module == "" {
c.cardErr(400, 400, "缺少参数 module号池模块")
return
}
if !validModules[module] {
c.cardErr(400, 400, fmt.Sprintf("不支持的模块: %s支持: cursor/windsurf/krio/codex", module))
return
}
if dataType != "" && !isValidPoolType(dataType) {
c.cardErr(400, 400, "data_type 不合法,支持: account/tk/account_tk")
return
}
now := time.Now()
switch module {
case "cursor":
c.extractCursor(platform, dataType, startID, now, machineCode)
case "windsurf":
c.extractWindsurf(platform, dataType, startID, now)
case "krio":
c.extractKrio(platform, dataType, startID, now)
case "codex":
c.extractCodex(platform, dataType, startID, now)
}
}
func (c *ApiGetCardController) readOptionalStartID() (uint64, error) {
raw := strings.TrimSpace(c.GetString("id"))
if raw == "" {
raw = strings.TrimSpace(c.GetString("start_id"))
}
if raw == "" {
raw = strings.TrimSpace(c.GetString("current_id"))
}
if raw == "" {
return 0, nil
}
id, err := strconv.ParseUint(raw, 10, 64)
if err != nil || id == 0 {
return 0, fmt.Errorf("id/start_id/current_id 必须是大于 0 的整数")
}
return id, nil
}
func (c *ApiGetCardController) extractCursor(platform, dataType string, startID uint64, now time.Time, machineCode string) {
// 优先查询该机器码是否已经绑定过未删除的卡密
if machineCode != "" {
var existing models.PlatformAccountPoolCursor
err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
Filter("machine_code", machineCode).
Filter("delete_time__isnull", true).
Exclude("is_used", 0).
OrderBy("-id").
Limit(1).
One(&existing)
if err == nil {
// 直接返回已绑定的卡密信息
c.cardOK(buildCardResult(&existing.Account, &existing.Password, existing.Token, existing.DataType))
return
}
}
c.extractWithProbe("cursor", platform, dataType, now, machineCode, func() (uint64, *string, *string, string, string, *int8, error) {
var row models.PlatformAccountPoolCursor
qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
Filter("is_extracted", 0).
Filter("delete_time__isnull", true)
if startID > 0 {
qs = qs.Filter("id__gte", startID)
}
if dataType != "" {
qs = qs.Filter("data_type", dataType)
}
if err := qs.OrderBy("id").One(&row); err != nil {
return 0, nil, nil, "", "", nil, err
}
return row.ID, &row.Account, &row.Password, row.Token, row.DataType, row.IsUsed, nil
})
}
func (c *ApiGetCardController) extractWindsurf(platform, dataType string, startID uint64, now time.Time) {
c.extractWithProbe("windsurf", platform, dataType, now, "", func() (uint64, *string, *string, string, string, *int8, error) {
var row models.PlatformAccountPoolWindsurf
qs := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).
Filter("is_extracted", 0).
Filter("delete_time__isnull", true)
if startID > 0 {
qs = qs.Filter("id__gte", startID)
}
if dataType != "" {
qs = qs.Filter("data_type", dataType)
}
if err := qs.OrderBy("id").One(&row); err != nil {
return 0, nil, nil, "", "", nil, err
}
return row.ID, &row.Account, &row.Password, row.Token, row.DataType, nil, nil
})
}
func (c *ApiGetCardController) extractKrio(platform, dataType string, startID uint64, now time.Time) {
c.extractWithProbe("krio", platform, dataType, now, "", func() (uint64, *string, *string, string, string, *int8, error) {
var row models.PlatformAccountPoolKiro
qs := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).
Filter("is_extracted", 0).
Filter("delete_time__isnull", true)
if startID > 0 {
qs = qs.Filter("id__gte", startID)
}
if dataType != "" {
qs = qs.Filter("data_type", dataType)
}
if err := qs.OrderBy("id").One(&row); err != nil {
return 0, nil, nil, "", "", nil, err
}
return row.ID, &row.Account, &row.Password, row.Token, row.DataType, nil, nil
})
}
func (c *ApiGetCardController) extractCodex(platform, dataType string, startID uint64, now time.Time) {
c.extractWithProbe("codex", platform, dataType, now, "", func() (uint64, *string, *string, string, string, *int8, error) {
var row models.PlatformAccountPoolCodex
qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCodex)).
Filter("is_extracted", 0).
Filter("delete_time__isnull", true)
if startID > 0 {
qs = qs.Filter("id__gte", startID)
}
if dataType != "" {
qs = qs.Filter("data_type", dataType)
}
if err := qs.OrderBy("id").One(&row); err != nil {
return 0, nil, nil, "", "", nil, err
}
return row.ID, &row.Account, &row.Password, row.Token, row.DataType, nil, nil
})
}
type poolRowFetcher func() (id uint64, account, password *string, token, rowDataType string, isUsed *int8, err error)
// extractWithProbe 按 id 顺序提取并探测 Token 可用性;不可用则标记已提取并继续下一条。
func (c *ApiGetCardController) extractWithProbe(
module, platform, dataType string,
now time.Time,
machineCode string,
fetch poolRowFetcher,
) {
for {
id, account, password, token, rowDataType, isUsed, err := fetch()
if err != nil {
if err == orm.ErrNoRows {
c.cardErr(404, 404, "暂无可用卡密")
} else {
c.cardErr(500, 500, "查询失败")
}
return
}
tableName := poolTableName(module)
if tableName == "" {
c.cardErr(500, 500, "无效模块")
return
}
params := map[string]interface{}{
"is_extracted": 1,
"extracted_time": now,
"extracted_platform": platform,
"update_time": now,
}
if module == "cursor" && machineCode != "" {
params["machine_code"] = machineCode
}
_, err = models.Orm.QueryTable(tableName).
Filter("id", id).
Update(params)
if err != nil {
c.cardErr(500, 500, "提取失败")
return
}
// 已有探测结论:可用则直接返回,不可用则继续下一条。
if known, available := poolIsUsedAvailable(isUsed); known {
if available {
c.cardOK(buildCardResult(account, password, token, rowDataType))
return
}
if module == "cursor" && machineCode != "" {
_, _ = models.Orm.QueryTable(tableName).Filter("id", id).Update(map[string]interface{}{"machine_code": "", "update_time": time.Now()})
}
continue
}
if !poolProbeToken(module, rowDataType, token, id) {
if module == "cursor" && machineCode != "" {
_, _ = models.Orm.QueryTable(tableName).Filter("id", id).Update(map[string]interface{}{"machine_code": "", "update_time": time.Now()})
}
continue
}
c.cardOK(buildCardResult(account, password, token, rowDataType))
return
}
}
// buildCardResult 根据账号类型返回格式化字符串
func buildCardResult(account, password *string, token string, dataType string) string {
acc := ""
pwd := ""
if account != nil {
acc = *account
}
if password != nil {
pwd = *password
}
switch dataType {
case "account":
return fmt.Sprintf("账号:%s / 密码:%s", acc, pwd)
case "account_tk":
return fmt.Sprintf("账号:%s / 密码:%s / Token%s", acc, pwd, token)
default: // tk
return token
}
}