diff --git a/go/controllers/api_cursor_detect.go b/go/controllers/api_cursor_detect.go new file mode 100644 index 0000000..ce09d4b --- /dev/null +++ b/go/controllers/api_cursor_detect.go @@ -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 +} diff --git a/go/controllers/api_getcard.go b/go/controllers/api_getcard.go index 0c8b725..cc56394 100644 --- a/go/controllers/api_getcard.go +++ b/go/controllers/api_getcard.go @@ -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) } diff --git a/go/routers/api/api.go b/go/routers/api/api.go index a6788bb..0e258bd 100644 --- a/go/routers/api/api.go +++ b/go/routers/api/api.go @@ -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") diff --git a/platform/src/views/accountpool/cursor/index.vue b/platform/src/views/accountpool/cursor/index.vue index e79edbe..f37fe3e 100644 --- a/platform/src/views/accountpool/cursor/index.vue +++ b/platform/src/views/accountpool/cursor/index.vue @@ -1098,13 +1098,13 @@ function closeBatchProbeDialog() { - +