This commit is contained in:
扫地僧 2026-06-16 23:17:01 +08:00
commit 080db544ea
3 changed files with 164 additions and 7 deletions

View File

@ -0,0 +1,117 @@
package controllers
import (
"fmt"
"strconv"
"strings"
"server/models"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
// ApiCursorDetectController Cursor Token 顺序读取接口(不改变号池状态)
//
// 用途:
// - 前端传入 start_id/current_id/id从该 ID 开始按 id 从小到大读取 Cursor Token。
// - 只读取未提取记录,不更新 is_extracted、extracted_time、extracted_platform、is_used 等任何状态。
// - 如果传入 ID 对应记录已提取,会自动跳过,继续找下一条未提取记录。
// - 返回 next_id前端下一次点击时传 next_id即可实现 11 -> 12 -> 13 递增读取。
//
// 示例:
//
// GET /api/cursor/token/peek?id=11
// GET /api/cursor/token/peek?start_id=11
// GET /api/cursor/token/peek?current_id=11
//
// 可选参数:
// - data_type=tk/account/account_tk默认 tk
type ApiCursorDetectController struct {
beego.Controller
}
func (c *ApiCursorDetectController) cursorDetectJSONErr(httpStatus, code int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{
"code": code,
"msg": msg,
}
_ = c.ServeJSON()
}
// PeekToken 按 ID 顺序读取 Cursor Token不改变任何状态。
func (c *ApiCursorDetectController) PeekToken() {
startID, err := c.readStartID()
if err != nil {
c.cursorDetectJSONErr(400, 400, err.Error())
return
}
dataType := strings.TrimSpace(c.GetString("data_type"))
if dataType == "" {
dataType = "tk"
}
if !isValidPoolType(dataType) {
c.cursorDetectJSONErr(400, 400, "data_type 不合法,支持: account/tk/account_tk")
return
}
var row models.PlatformAccountPoolCursor
qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
Filter("id__gte", startID).
Filter("data_type", dataType).
Filter("delete_time__isnull", true).
Filter("extracted_time__isnull", true).
Filter("is_extracted", 0).
Exclude("token", "")
err = qs.OrderBy("id").One(&row)
if err != nil {
if err == orm.ErrNoRows {
c.cursorDetectJSONErr(404, 404, "从当前 ID 开始暂无未提取 Cursor Token")
return
}
c.cursorDetectJSONErr(500, 500, "查询失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "success",
"data": map[string]interface{}{
"id": row.ID,
"next_id": row.ID + 1,
"data_type": row.DataType,
"account": row.Account,
"password": row.Password,
"token": row.Token,
"remark": row.Remark,
"is_used": row.IsUsed,
"create_time": row.CreateTime,
// 明确告诉前端:本接口只是读取,没有更新号池状态。
"state_changed": false,
},
}
_ = c.ServeJSON()
}
func (c *ApiCursorDetectController) readStartID() (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, fmt.Errorf("缺少参数 id/start_id/current_id")
}
id, err := strconv.ParseUint(raw, 10, 64)
if err != nil || id == 0 {
return 0, fmt.Errorf("id 必须是大于 0 的整数")
}
return id, nil
}

View File

@ -2,6 +2,8 @@ package controllers
import (
"fmt"
"strconv"
"strings"
"time"
"server/models"
@ -23,7 +25,7 @@ var validPlatformTypes = map[string]bool{
"jingdong": true,
"douyin": true,
"local": true,
"xubei": true,
"xubei": true,
}
// validModules 支持的号池模块
@ -52,10 +54,16 @@ func (c *ApiGetCardController) cardOK(text string) {
// - type (必填) 来源平台xianyu / taobao / pinduoduo / jingdong / local / xubei
// - module (必填) 号池模块cursor / windsurf / krio
// - 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
}
// 参数校验
if platform == "" {
@ -83,20 +91,42 @@ func (c *ApiGetCardController) GetCard() {
switch module {
case "cursor":
c.extractCursor(platform, dataType, now)
c.extractCursor(platform, dataType, startID, now)
case "windsurf":
c.extractWindsurf(platform, dataType, now)
c.extractWindsurf(platform, dataType, startID, now)
case "krio":
c.extractKrio(platform, dataType, now)
c.extractKrio(platform, dataType, startID, now)
}
}
func (c *ApiGetCardController) extractCursor(platform, dataType string, now time.Time) {
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) {
c.extractWithProbe("cursor", platform, dataType, now, 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)
}
@ -107,12 +137,15 @@ func (c *ApiGetCardController) extractCursor(platform, dataType string, now time
})
}
func (c *ApiGetCardController) extractWindsurf(platform, dataType string, now time.Time) {
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)
}
@ -123,12 +156,15 @@ func (c *ApiGetCardController) extractWindsurf(platform, dataType string, now ti
})
}
func (c *ApiGetCardController) extractKrio(platform, dataType string, now time.Time) {
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)
}

View File

@ -17,6 +17,10 @@ func Register() {
// 登录器使用激活码激活/续期 Cursor 设备(无需登录)
beego.Router("/api/cursor/equipment/activateByCode", &controllers.ApiCursorEquipmentController{}, "post:ActivateByCode")
// Cursor Token 顺序读取/检测接口(无需登录,不改变号池状态)
// GET /api/cursor/token/peek?id=11&data_type=tk
beego.Router("/api/cursor/token/peek", &controllers.ApiCursorDetectController{}, "get:PeekToken")
// 对外提卡接口(无需登录)
// GET /api/getcard?type=xianyu&module=cursor&data_type=tk
beego.Router("/api/getcard", &controllers.ApiGetCardController{}, "get:GetCard")