整合数据

This commit is contained in:
2026-06-16 01:30:39 +08:00
parent 761a5cb69c
commit c0f70823a9
31 changed files with 4385 additions and 893 deletions
+503
View File
@@ -0,0 +1,503 @@
package controllers
import (
"encoding/json"
"strings"
"time"
"server/models"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
// ApiCursorEquipmentController 开放接口:登录器上报 Cursor 设备信息(无需登录)
type ApiCursorEquipmentController struct {
beego.Controller
}
type cursorEquipmentReportPayload struct {
DeviceInfo string `json:"deviceInfo"`
DeviceInfoSnake string `json:"device_info"`
MachineCode string `json:"machineCode"`
MachineCodeSnake string `json:"machine_code"`
Status *int8 `json:"status"`
System string `json:"system"`
Version string `json:"version"`
BindAccount string `json:"bindAccount"`
BindAccountSnake string `json:"bind_account"`
OwnerUserID *uint64 `json:"ownerUserId"`
OwnerUserIDSnake *uint64 `json:"owner_user_id"`
OwnerUserName string `json:"ownerUserName"`
OwnerUserNameSnake string `json:"owner_user_name"`
ActivationTime string `json:"activationTime"`
ActivationTimeSnake string `json:"activation_time"`
ExpireTime string `json:"expireTime"`
ExpireTimeSnake string `json:"expire_time"`
Remark string `json:"remark"`
}
type cursorEquipmentActivatePayload struct {
Code string `json:"code"`
ActivationCode string `json:"activationCode"`
ActivationCodeSnake string `json:"activation_code"`
DeviceInfo string `json:"deviceInfo"`
DeviceInfoSnake string `json:"device_info"`
MachineCode string `json:"machineCode"`
MachineCodeSnake string `json:"machine_code"`
System string `json:"system"`
Version string `json:"version"`
BindAccount string `json:"bindAccount"`
BindAccountSnake string `json:"bind_account"`
OwnerUserID *uint64 `json:"ownerUserId"`
OwnerUserIDSnake *uint64 `json:"owner_user_id"`
OwnerUserName string `json:"ownerUserName"`
OwnerUserNameSnake string `json:"owner_user_name"`
Remark string `json:"remark"`
}
func cursorFirstNonEmpty(values ...string) string {
for _, v := range values {
if s := strings.TrimSpace(v); s != "" {
return s
}
}
return ""
}
func cursorStringPtr(value string) *string {
value = strings.TrimSpace(value)
if value == "" {
return nil
}
return &value
}
func cursorParseTimePtr(value string) *time.Time {
value = strings.TrimSpace(value)
if value == "" {
return nil
}
layouts := []string{
time.RFC3339,
"2006-01-02 15:04:05",
"2006-01-02 15:04",
"2006-01-02",
}
for _, layout := range layouts {
if t, err := time.ParseInLocation(layout, value, time.Local); err == nil {
return &t
}
}
return nil
}
func cursorValidStatus(status int8) bool {
return status == 0 || status == 1 || status == 2 || status == 3
}
func (c *ApiCursorEquipmentController) jsonResult(code int, msg string, data interface{}) {
resp := map[string]interface{}{"code": code, "msg": msg}
if data != nil {
resp["data"] = data
}
c.Data["json"] = resp
_ = c.ServeJSON()
}
// Report POST /api/cursor/equipment/report
//
// JSON 示例:
//
// {
// "machineCode": "ABC-123",
// "deviceInfo": "CPU/RAM/磁盘等设备信息",
// "system": "Windows",
// "version": "1.0.0",
// "bindAccount": "user@example.com",
// "ownerUserId": 1,
// "ownerUserName": "张三",
// "activationTime": "2026-06-15 22:00:00",
// "expireTime": "2026-07-15 22:00:00",
// "remark": "登录器上报"
// }
//
// 兼容 snake_case 字段,例如 machine_code、device_info、bind_account。
func (c *ApiCursorEquipmentController) Report() {
var p cursorEquipmentReportPayload
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &p); err != nil {
c.jsonResult(400, "参数错误", nil)
return
}
machineCode := cursorFirstNonEmpty(p.MachineCode, p.MachineCodeSnake)
if machineCode == "" {
c.jsonResult(400, "缺少参数 machineCode/machine_code(机器码)", nil)
return
}
if len(machineCode) > 128 {
c.jsonResult(400, "机器码长度不能超过 128 个字符", nil)
return
}
status := int8(0)
statusProvided := p.Status != nil
if statusProvided {
status = *p.Status
if !cursorValidStatus(status) {
c.jsonResult(400, "状态不合法,支持:0 未激活、1 激活中、2 已过期、3 已禁用", nil)
return
}
}
deviceInfo := cursorFirstNonEmpty(p.DeviceInfo, p.DeviceInfoSnake)
system := cursorFirstNonEmpty(p.System)
version := cursorFirstNonEmpty(p.Version)
bindAccount := cursorFirstNonEmpty(p.BindAccount, p.BindAccountSnake)
ownerUserID := p.OwnerUserID
if ownerUserID == nil {
ownerUserID = p.OwnerUserIDSnake
}
ownerUserName := cursorFirstNonEmpty(p.OwnerUserName, p.OwnerUserNameSnake)
activationTime := cursorParseTimePtr(cursorFirstNonEmpty(p.ActivationTime, p.ActivationTimeSnake))
expireTime := cursorParseTimePtr(cursorFirstNonEmpty(p.ExpireTime, p.ExpireTimeSnake))
remark := cursorFirstNonEmpty(p.Remark)
now := time.Now()
var row models.PlatformCursorEquipment
err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)).
Filter("machine_code", machineCode).
Filter("delete_time__isnull", true).
One(&row)
created := false
if err == orm.ErrNoRows {
row = models.PlatformCursorEquipment{
MachineCode: machineCode,
Status: status,
DeviceInfo: cursorStringPtr(deviceInfo),
System: cursorStringPtr(system),
Version: cursorStringPtr(version),
BindAccount: cursorStringPtr(bindAccount),
OwnerUserID: ownerUserID,
OwnerUserName: cursorStringPtr(ownerUserName),
ActivationTime: activationTime,
ExpireTime: expireTime,
Remark: cursorStringPtr(remark),
CreateTime: now,
}
id, insertErr := models.Orm.Insert(&row)
if insertErr != nil {
c.jsonResult(500, "设备信息保存失败", nil)
return
}
row.ID = uint64(id)
created = true
} else if err != nil {
c.jsonResult(500, "设备信息查询失败", nil)
return
} else {
update := map[string]interface{}{
"device_info": cursorStringPtr(deviceInfo),
"system": cursorStringPtr(system),
"version": cursorStringPtr(version),
"bind_account": cursorStringPtr(bindAccount),
"owner_user_id": ownerUserID,
"owner_user_name": cursorStringPtr(ownerUserName),
"activation_time": activationTime,
"expire_time": expireTime,
"remark": cursorStringPtr(remark),
"update_time": now,
}
if statusProvided {
update["status"] = status
row.Status = status
}
if _, updateErr := models.Orm.QueryTable(new(models.PlatformCursorEquipment)).
Filter("id", row.ID).
Update(update); updateErr != nil {
c.jsonResult(500, "设备信息更新失败", nil)
return
}
row.DeviceInfo = cursorStringPtr(deviceInfo)
row.System = cursorStringPtr(system)
row.Version = cursorStringPtr(version)
row.BindAccount = cursorStringPtr(bindAccount)
row.OwnerUserID = ownerUserID
row.OwnerUserName = cursorStringPtr(ownerUserName)
row.ActivationTime = activationTime
row.ExpireTime = expireTime
row.Remark = cursorStringPtr(remark)
row.UpdateTime = &now
}
c.jsonResult(200, "success", map[string]interface{}{
"id": row.ID,
"machineCode": row.MachineCode,
"status": row.Status,
"created": created,
})
}
// ActivateByCode POST /api/cursor/equipment/activateByCode
//
// 设备端使用激活码激活/续期 Cursor 设备(无需登录)。
//
// JSON 示例:
//
// {
// "activationCode": "CUR-XXXXXXXX",
// "machineCode": "ABC-123",
// "deviceInfo": "CPU/RAM/磁盘等设备信息",
// "system": "Windows",
// "version": "1.0.0",
// "bindAccount": "user@example.com",
// "ownerUserId": 1,
// "ownerUserName": "张三",
// "remark": "登录器激活"
// }
//
// 兼容字段:
// - 激活码:activationCode / activation_code / code
// - 机器码:machineCode / machine_code
// - 设备信息:deviceInfo / device_info
// - 绑定账号:bindAccount / bind_account
func (c *ApiCursorEquipmentController) ActivateByCode() {
var p cursorEquipmentActivatePayload
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &p); err != nil {
c.jsonResult(400, "参数错误", nil)
return
}
code := cursorFirstNonEmpty(p.ActivationCode, p.ActivationCodeSnake, p.Code)
if code == "" {
c.jsonResult(400, "缺少参数 activationCode/activation_code/code(激活码)", nil)
return
}
if len(code) > 128 {
c.jsonResult(400, "激活码长度不能超过 128 个字符", nil)
return
}
machineCode := cursorFirstNonEmpty(p.MachineCode, p.MachineCodeSnake)
if machineCode == "" {
c.jsonResult(400, "缺少参数 machineCode/machine_code(机器码)", nil)
return
}
if len(machineCode) > 128 {
c.jsonResult(400, "机器码长度不能超过 128 个字符", nil)
return
}
deviceInfo := cursorFirstNonEmpty(p.DeviceInfo, p.DeviceInfoSnake)
system := cursorFirstNonEmpty(p.System)
version := cursorFirstNonEmpty(p.Version)
bindAccount := cursorFirstNonEmpty(p.BindAccount, p.BindAccountSnake)
ownerUserID := p.OwnerUserID
if ownerUserID == nil {
ownerUserID = p.OwnerUserIDSnake
}
ownerUserName := cursorFirstNonEmpty(p.OwnerUserName, p.OwnerUserNameSnake)
remark := cursorFirstNonEmpty(p.Remark)
now := time.Now()
var activationCode models.PlatformCursorActivationCode
err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).
Filter("code", code).
Filter("delete_time__isnull", true).
One(&activationCode)
if err == orm.ErrNoRows {
c.jsonResult(404, "激活码不存在", nil)
return
}
if err != nil {
c.jsonResult(500, "激活码查询失败", nil)
return
}
if activationCode.Status == 3 {
c.jsonResult(403, "激活码已禁用", nil)
return
}
if activationCode.Status == 2 || (activationCode.ExpiredAt != nil && activationCode.ExpiredAt.Before(now)) {
_, _ = models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).
Filter("id", activationCode.ID).
Update(map[string]interface{}{"status": int8(2), "update_time": now})
c.jsonResult(410, "激活码已过期", nil)
return
}
if activationCode.Status == 1 {
if activationCode.MachineCode == nil || strings.TrimSpace(*activationCode.MachineCode) != machineCode {
c.jsonResult(409, "激活码已被其他设备使用", nil)
return
}
if activationCode.ExpiredAt != nil && activationCode.ExpiredAt.Before(now) {
_, _ = models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).
Filter("id", activationCode.ID).
Update(map[string]interface{}{"status": int8(2), "update_time": now})
c.jsonResult(410, "激活码已过期", nil)
return
}
c.jsonResult(200, "success", map[string]interface{}{
"activated": true,
"reused": true,
"activationId": activationCode.ID,
"deviceId": activationCode.BindDeviceID,
"machineCode": machineCode,
"status": 1,
"durationDays": activationCode.DurationDays,
"activatedAt": activationCode.ActivatedAt,
"expireTime": activationCode.ExpiredAt,
"expiredAt": activationCode.ExpiredAt,
})
return
}
var device models.PlatformCursorEquipment
deviceErr := models.Orm.QueryTable(new(models.PlatformCursorEquipment)).
Filter("machine_code", machineCode).
Filter("delete_time__isnull", true).
One(&device)
created := false
if deviceErr != nil && deviceErr != orm.ErrNoRows {
c.jsonResult(500, "设备信息查询失败", nil)
return
}
if deviceErr == nil && device.Status == 3 {
c.jsonResult(403, "设备已禁用,无法激活", nil)
return
}
baseTime := now
if deviceErr == nil && device.ExpireTime != nil && device.ExpireTime.After(now) {
baseTime = *device.ExpireTime
}
var expireTime *time.Time
if activationCode.DurationDays > 0 {
t := baseTime.AddDate(0, 0, activationCode.DurationDays)
expireTime = &t
}
txOrm, err := models.Orm.Begin()
if err != nil {
c.jsonResult(500, "开启事务失败", nil)
return
}
rollback := true
defer func() {
if rollback {
_ = txOrm.Rollback()
}
}()
if deviceErr == orm.ErrNoRows {
device = models.PlatformCursorEquipment{
MachineCode: machineCode,
Status: 1,
DeviceInfo: cursorStringPtr(deviceInfo),
System: cursorStringPtr(system),
Version: cursorStringPtr(version),
BindAccount: cursorStringPtr(bindAccount),
OwnerUserID: ownerUserID,
OwnerUserName: cursorStringPtr(ownerUserName),
ActivationTime: &now,
ExpireTime: expireTime,
Remark: cursorStringPtr(remark),
CreateTime: now,
}
id, insertErr := txOrm.Insert(&device)
if insertErr != nil {
c.jsonResult(500, "设备信息保存失败", nil)
return
}
device.ID = uint64(id)
created = true
} else {
if bindAccount == "" && device.BindAccount != nil {
bindAccount = *device.BindAccount
}
if ownerUserID == nil {
ownerUserID = device.OwnerUserID
}
if ownerUserName == "" && device.OwnerUserName != nil {
ownerUserName = *device.OwnerUserName
}
_, updateErr := txOrm.QueryTable(new(models.PlatformCursorEquipment)).
Filter("id", device.ID).
Update(map[string]interface{}{
"device_info": cursorStringPtr(deviceInfo),
"system": cursorStringPtr(system),
"version": cursorStringPtr(version),
"bind_account": cursorStringPtr(bindAccount),
"owner_user_id": ownerUserID,
"owner_user_name": cursorStringPtr(ownerUserName),
"activation_time": now,
"expire_time": expireTime,
"status": int8(1),
"remark": cursorStringPtr(remark),
"update_time": now,
})
if updateErr != nil {
c.jsonResult(500, "设备信息更新失败", nil)
return
}
}
codeUpdateCount, updateCodeErr := txOrm.QueryTable(new(models.PlatformCursorActivationCode)).
Filter("id", activationCode.ID).
Filter("status", int8(0)).
Filter("delete_time__isnull", true).
Update(map[string]interface{}{
"status": int8(1),
"bind_account": cursorStringPtr(bindAccount),
"bind_device_id": device.ID,
"machine_code": machineCode,
"device_info": cursorStringPtr(deviceInfo),
"owner_user_id": ownerUserID,
"owner_user_name": cursorStringPtr(ownerUserName),
"activated_at": now,
"expired_at": expireTime,
"remark": cursorStringPtr(remark),
"update_time": now,
})
if updateCodeErr != nil {
c.jsonResult(500, "激活码绑定失败", nil)
return
}
if codeUpdateCount == 0 {
c.jsonResult(409, "激活码状态已变化,请重新查询后再试", nil)
return
}
if err := txOrm.Commit(); err != nil {
c.jsonResult(500, "提交事务失败", nil)
return
}
rollback = false
c.jsonResult(200, "success", map[string]interface{}{
"activated": true,
"reused": false,
"created": created,
"activationId": activationCode.ID,
"deviceId": device.ID,
"machineCode": machineCode,
"status": 1,
"durationDays": activationCode.DurationDays,
"activationAt": now,
"activatedAt": now,
"expireTime": expireTime,
"expiredAt": expireTime,
})
}
+12 -12
View File
@@ -287,16 +287,16 @@ func (c *BackendArticleController) Detail() {
}
type cmsArticlePayload struct {
Title string `json:"title"`
Author string `json:"author"`
Cate interface{} `json:"cate"`
Content string `json:"content"`
Desc string `json:"desc"`
Image string `json:"image"`
IsTrans int8 `json:"is_trans"`
TransURL *string `json:"transurl"`
Status int8 `json:"status"`
IgnoreSimilarity int `json:"ignore_similarity"`
Title string `json:"title"`
Author string `json:"author"`
Cate interface{} `json:"cate"`
Content string `json:"content"`
Desc string `json:"desc"`
Image string `json:"image"`
IsTrans int8 `json:"is_trans"`
TransURL *string `json:"transurl"`
Status int8 `json:"status"`
IgnoreSimilarity int `json:"ignore_similarity"`
}
// Create POST /backend/createarticle
@@ -593,8 +593,8 @@ func (c *BackendArticleController) Unpublish() {
func (c *BackendArticleController) Recommend() { c.setArticleFlag("recommend", 1) }
func (c *BackendArticleController) Unrecommend() { c.setArticleFlag("recommend", 0) }
func (c *BackendArticleController) Top() { c.setArticleFlag("top", 1) }
func (c *BackendArticleController) Untop() { c.setArticleFlag("top", 0) }
func (c *BackendArticleController) Top() { c.setArticleFlag("top", 1) }
func (c *BackendArticleController) Untop() { c.setArticleFlag("top", 0) }
// List GET /backend/categories
func (c *BackendArticleCategoryController) List() {
+185 -155
View File
@@ -619,115 +619,150 @@ func replenishPoolRow(c *beego.Controller, module string) {
platform := payload.Platform
remark := strings.TrimSpace(payload.Remark)
replenishWithProbe(c, module, payload.Type, platform, remark, now)
}
type poolReplenishCandidate struct {
id uint64
dataType string
token string
isUsed *int8
row interface{}
}
type poolReplenishFetcher func() (*poolReplenishCandidate, error)
// replenishWithProbe 按 id 顺序补号并探测;不可用则标记 is_extracted=2 后继续下一条。
func replenishWithProbe(c *beego.Controller, module, dataType, platform, remark string, now time.Time) {
var fetch poolReplenishFetcher
switch module {
case "cursor":
checkedCount := 0
unavailableCount := 0
for {
fetch = func() (*poolReplenishCandidate, error) {
var row models.PlatformAccountPoolCursor
if err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
Filter("is_extracted", 0).Filter("data_type", payload.Type).
OrderBy("id").One(&row); err != nil {
msg := "暂无可用账号"
if checkedCount > 0 {
msg = fmt.Sprintf("已检测%d个账号,其中%d个不可用,暂无可用账号", checkedCount, unavailableCount)
}
poolJSONErr(c, 404, 404, msg)
return
err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
Filter("is_extracted", 0).
Filter("data_type", dataType).
Filter("delete_time__isnull", true).
OrderBy("id").
One(&row)
if err != nil {
return nil, err
}
checkedCount++
isAvailable := poolProbeToken("cursor", row.DataType, row.Token, row.ID)
if !isAvailable {
unavailableCount++
if _, err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", row.ID).Update(map[string]interface{}{
// 补号流程检测出来不可用/已用完的号,仍然归类为“补号”记录。
// 不要写成已提取/已用完状态;只有接口提取后再标记不可用的号才归到已提取侧。
"is_extracted": int8(2),
"is_used": int8(0),
"extracted_time": now,
"extracted_platform": platform,
"remark": remark,
"update_time": now,
}); err != nil {
poolJSONErr(c, 500, 500, "补号检测失败: "+err.Error())
return
}
continue
}
if _, err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", row.ID).Update(map[string]interface{}{
"is_extracted": int8(2),
"is_used": int8(1),
"extracted_time": now,
"extracted_platform": platform,
"remark": remark,
"update_time": now,
}); err != nil {
poolJSONErr(c, 500, 500, "补号失败: "+err.Error())
return
}
row.IsExtracted = 2
isUsed := int8(1)
row.IsUsed = &isUsed
row.ExtractedTime = &now
row.ExtractedPlatform = &platform
row.Remark = remark
c.Data["json"] = map[string]interface{}{
"code": 200,
"msg": "补号成功",
"data": row,
"probe": map[string]interface{}{
"checkedCount": checkedCount,
"unavailableCount": unavailableCount,
},
}
break
return &poolReplenishCandidate{
id: row.ID, dataType: row.DataType, token: row.Token, isUsed: row.IsUsed, row: row,
}, nil
}
case "windsurf":
var row models.PlatformAccountPoolWindsurf
if err := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).
Filter("is_extracted", 0).Filter("data_type", payload.Type).
OrderBy("id").One(&row); err != nil {
poolJSONErr(c, 404, 404, "暂无可用账号")
return
fetch = func() (*poolReplenishCandidate, error) {
var row models.PlatformAccountPoolWindsurf
err := models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).
Filter("is_extracted", 0).
Filter("data_type", dataType).
Filter("delete_time__isnull", true).
OrderBy("id").
One(&row)
if err != nil {
return nil, err
}
return &poolReplenishCandidate{
id: row.ID, dataType: row.DataType, token: row.Token, row: row,
}, nil
}
if _, err = models.Orm.QueryTable(new(models.PlatformAccountPoolWindsurf)).Filter("id", row.ID).Update(map[string]interface{}{
"is_extracted": int8(2), "extracted_time": now, "extracted_platform": platform, "remark": remark,
}); err != nil {
poolJSONErr(c, 500, 500, "补号失败: "+err.Error())
return
}
row.IsExtracted = 2
row.ExtractedTime = &now
row.ExtractedPlatform = &platform
row.Remark = remark
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "补号成功", "data": row}
case "krio":
var row models.PlatformAccountPoolKiro
if err := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).
Filter("is_extracted", 0).Filter("data_type", payload.Type).
OrderBy("id").One(&row); err != nil {
poolJSONErr(c, 404, 404, "暂无可用账号")
return
fetch = func() (*poolReplenishCandidate, error) {
var row models.PlatformAccountPoolKiro
err := models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).
Filter("is_extracted", 0).
Filter("data_type", dataType).
Filter("delete_time__isnull", true).
OrderBy("id").
One(&row)
if err != nil {
return nil, err
}
return &poolReplenishCandidate{
id: row.ID, dataType: row.DataType, token: row.Token, row: row,
}, nil
}
if _, err = models.Orm.QueryTable(new(models.PlatformAccountPoolKiro)).Filter("id", row.ID).Update(map[string]interface{}{
"is_extracted": int8(2), "extracted_time": now, "extracted_platform": platform, "remark": remark,
}); err != nil {
poolJSONErr(c, 500, 500, "补号失败: "+err.Error())
return
}
row.IsExtracted = 2
row.ExtractedTime = &now
row.ExtractedPlatform = &platform
row.Remark = remark
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "补号成功", "data": row}
default:
poolJSONErr(c, 400, 400, "无效模块")
return
}
_ = c.ServeJSON()
tableName := poolTableName(module)
if tableName == "" {
poolJSONErr(c, 400, 400, "无效模块")
return
}
for {
candidate, err := fetch()
if err != nil {
if err == orm.ErrNoRows {
poolJSONErr(c, 404, 404, "暂无可用账号")
} else {
poolJSONErr(c, 500, 500, "查询失败")
}
return
}
updateFields := map[string]interface{}{
"is_extracted": int8(2),
"extracted_time": now,
"extracted_platform": platform,
"remark": remark,
"update_time": now,
}
if _, err = models.Orm.QueryTable(tableName).
Filter("id", candidate.id).
Update(updateFields); err != nil {
poolJSONErr(c, 500, 500, "补号失败: "+err.Error())
return
}
if known, available := poolIsUsedAvailable(candidate.isUsed); known {
if !available {
continue
}
} else if !poolProbeToken(module, candidate.dataType, candidate.token, candidate.id) {
continue
}
data := replenishApplyResponse(candidate.row, platform, remark, now)
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "补号成功", "data": data}
_ = c.ServeJSON()
return
}
}
func replenishApplyResponse(row interface{}, platform, remark string, now time.Time) interface{} {
pf := platform
switch r := row.(type) {
case models.PlatformAccountPoolCursor:
r.IsExtracted = 2
r.ExtractedTime = &now
r.ExtractedPlatform = &pf
r.Remark = remark
if r.IsUsed == nil || *r.IsUsed != 1 {
used := int8(1)
r.IsUsed = &used
}
return r
case models.PlatformAccountPoolWindsurf:
r.IsExtracted = 2
r.ExtractedTime = &now
r.ExtractedPlatform = &pf
r.Remark = remark
return r
case models.PlatformAccountPoolKiro:
r.IsExtracted = 2
r.ExtractedTime = &now
r.ExtractedPlatform = &pf
r.Remark = remark
return r
default:
return row
}
}
func updatePoolRemark(c *beego.Controller, module string) {
@@ -843,6 +878,54 @@ func setPoolUnavailable(c *beego.Controller, module string) {
_ = c.ServeJSON()
}
func updatePoolUsable(c *beego.Controller, module string) {
if _, err := requirePlatformAuth(c); err != nil {
poolJSONErr(c, 401, 401, err.Error())
return
}
if module != "cursor" {
poolJSONErr(c, 400, 400, "该模块不支持可用状态修改")
return
}
raw, err := io.ReadAll(c.Ctx.Request.Body)
if err != nil {
poolJSONErr(c, 400, 400, "参数错误")
return
}
var payload struct {
ID uint64 `json:"id"`
Usable int `json:"usable"`
}
if err := json.Unmarshal(raw, &payload); err != nil || payload.ID == 0 {
poolJSONErr(c, 400, 400, "参数错误")
return
}
if payload.Usable != 0 && payload.Usable != 1 {
poolJSONErr(c, 400, 400, "可用状态参数错误")
return
}
now := time.Now()
updated, err := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", payload.ID).Update(orm.Params{
"is_used": int8(payload.Usable),
"update_time": now,
})
if err != nil {
poolJSONErr(c, 500, 500, "可用状态更新失败: "+err.Error())
return
}
if updated == 0 {
poolJSONErr(c, 404, 404, "记录不存在")
return
}
msg := "已标记不可用"
if payload.Usable == 1 {
msg = "已标记可用"
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": msg}
_ = c.ServeJSON()
}
func updatePoolPlatform(c *beego.Controller, module string) {
if _, err := requirePlatformAuth(c); err != nil {
poolJSONErr(c, 401, 401, err.Error())
@@ -1019,12 +1102,12 @@ func probePoolToken(c *beego.Controller, module string) {
if r.StreamNote != "" {
data["streamNote"] = r.StreamNote
}
// Cursor 探测状态只按底层探针结论 r.OK 保存。
// 注意:客户端版本过旧只是 warningToken 仍可用时 r.OK=true,不能因此写成已用完。
if module == "cursor" && payload.ID > 0 && r.HTTPStatus == http.StatusOK {
isUsed := int8(0)
var isUsed int8
if r.OK {
isUsed = 1
} else {
isUsed = 0
}
if _, uerr := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).Filter("id", payload.ID).Update(orm.Params{
"is_used": isUsed,
@@ -1041,62 +1124,6 @@ func probePoolToken(c *beego.Controller, module string) {
_ = c.ServeJSON()
}
func poolTableName(module string) string {
switch module {
case "cursor":
return (&models.PlatformAccountPoolCursor{}).TableName()
case "windsurf":
return (&models.PlatformAccountPoolWindsurf{}).TableName()
case "krio":
return (&models.PlatformAccountPoolKiro{}).TableName()
default:
return ""
}
}
func poolIsUsedAvailable(isUsed *int8) (known bool, available bool) {
if isUsed == nil {
return false, false
}
switch *isUsed {
case 1:
return true, true
case 0:
return true, false
default:
return false, false
}
}
func poolProbeToken(module, rowDataType, token string, id uint64) bool {
token = strings.TrimSpace(token)
if rowDataType == "account" || token == "" {
return true
}
r := tokenprobe.ProbeOfficial(module, token)
// Cursor 自动探测只按底层探针结论 r.OK 判定。
// 客户端版本过旧是 warning,不代表 Token 已用完;只有 tokenprobe 明确判定额度用尽/不可用时 r.OK 才为 false。
available := r.OK
// 更新数据库中的 is_used 字段
if module == "cursor" && id > 0 {
isUsed := int8(0)
if available {
isUsed = 1
}
_, _ = models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
Filter("id", id).
Update(orm.Params{
"is_used": isUsed,
"update_time": time.Now(),
})
}
return available
}
func (c *PlatformAccountPoolCursorController) List() { listPoolRows(&c.Controller, "cursor") }
func (c *PlatformAccountPoolCursorController) Add() { addPoolRow(&c.Controller, "cursor") }
func (c *PlatformAccountPoolCursorController) BatchAdd() { batchAddPoolRows(&c.Controller, "cursor") }
@@ -1109,6 +1136,9 @@ func (c *PlatformAccountPoolCursorController) UpdateRemark() {
func (c *PlatformAccountPoolCursorController) SetUnavailable() {
setPoolUnavailable(&c.Controller, "cursor")
}
func (c *PlatformAccountPoolCursorController) UpdateUsable() {
updatePoolUsable(&c.Controller, "cursor")
}
func (c *PlatformAccountPoolCursorController) UpdatePlatform() {
updatePoolPlatform(&c.Controller, "cursor")
}
@@ -0,0 +1,750 @@
package controllers
import (
"crypto/rand"
"encoding/csv"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"time"
"server/models"
"server/pkg/jwtutil"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
// PlatformCursorActivationCodeController 平台端 Cursor 激活码管理
type PlatformCursorActivationCodeController struct {
beego.Controller
}
func (c *PlatformCursorActivationCodeController) platformClaims() (*jwtutil.Claims, error) {
auth := c.Ctx.Request.Header.Get("Authorization")
if auth == "" {
return nil, fmt.Errorf("未登录")
}
parts := strings.SplitN(auth, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
return nil, fmt.Errorf("认证信息格式错误")
}
claims, err := jwtutil.ParseToken(parts[1])
if err != nil {
return nil, fmt.Errorf("无效的token")
}
if claims.UserType != "platform" {
return nil, fmt.Errorf("无权访问")
}
return claims, nil
}
func (c *PlatformCursorActivationCodeController) jsonErr(httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
func (c *PlatformCursorActivationCodeController) ok(data interface{}) {
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data}
_ = c.ServeJSON()
}
func cursorActivationCodeTrimPtr(value *string) *string {
if value == nil {
return nil
}
v := strings.TrimSpace(*value)
if v == "" {
return nil
}
return &v
}
func cursorActivationCodeTimePtr(value *string) *time.Time {
if value == nil {
return nil
}
v := strings.TrimSpace(*value)
if v == "" {
return nil
}
layouts := []string{
time.RFC3339,
"2006-01-02 15:04:05",
"2006-01-02 15:04",
"2006-01-02",
}
for _, layout := range layouts {
if t, err := time.ParseInLocation(layout, v, time.Local); err == nil {
return &t
}
}
return nil
}
func cursorActivationCodeStatusValid(status int8) bool {
return status == 0 || status == 1 || status == 2 || status == 3
}
func cursorActivationCodeTypeName(cardType int) string {
switch cardType {
case 1:
return "天卡"
case 7:
return "周卡"
case 30:
return "月卡"
case 90:
return "季卡"
case 365:
return "年卡"
case 0:
return "自定义"
default:
return fmt.Sprintf("%d天", cardType)
}
}
func (c *PlatformCursorActivationCodeController) rowToMap(row *models.PlatformCursorActivationCode) map[string]interface{} {
bindStatus := 0
if row.BindAccount != nil || row.BindDeviceID != nil || row.MachineCode != nil {
bindStatus = 1
}
return map[string]interface{}{
"id": row.ID,
"code": row.Code,
"type": row.Type,
"typeName": cursorActivationCodeTypeName(row.Type),
"status": row.Status,
"durationDays": row.DurationDays,
"bindAccount": row.BindAccount,
"bindDeviceId": row.BindDeviceID,
"bindStatus": bindStatus,
"machineCode": row.MachineCode,
"deviceInfo": row.DeviceInfo,
"ownerUserId": row.OwnerUserID,
"ownerUserName": row.OwnerUserName,
"activatedAt": row.ActivatedAt,
"expiredAt": row.ExpiredAt,
"createdAt": row.CreateTime,
"updatedAt": row.UpdateTime,
"createTime": row.CreateTime,
"updateTime": row.UpdateTime,
"remark": row.Remark,
}
}
func (c *PlatformCursorActivationCodeController) filteredQuery() orm.QuerySeter {
keyword := strings.TrimSpace(c.GetString("keyword"))
statusText := strings.TrimSpace(c.GetString("status"))
typeText := strings.TrimSpace(c.GetString("type"))
bindStatusText := strings.TrimSpace(c.GetString("bindStatus"))
qs := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).Filter("delete_time__isnull", true)
if keyword != "" {
cond := orm.NewCondition().
Or("code__icontains", keyword).
Or("bind_account__icontains", keyword).
Or("machine_code__icontains", keyword).
Or("device_info__icontains", keyword).
Or("owner_user_name__icontains", keyword).
Or("remark__icontains", keyword)
qs = qs.SetCond(cond)
qs = qs.Filter("delete_time__isnull", true)
}
if statusText != "" {
status, err := strconv.ParseInt(statusText, 10, 8)
if err == nil && cursorActivationCodeStatusValid(int8(status)) {
qs = qs.Filter("status", int8(status))
}
}
if typeText != "" {
cardType, err := strconv.Atoi(typeText)
if err == nil {
qs = qs.Filter("type", cardType)
}
}
if bindStatusText != "" {
bindStatus, err := strconv.Atoi(bindStatusText)
if err == nil {
if bindStatus == 0 {
qs = qs.Filter("bind_account__isnull", true).Filter("bind_device_id__isnull", true).Filter("machine_code__isnull", true)
} else if bindStatus == 1 {
cond := orm.NewCondition().
Or("bind_account__isnull", false).
Or("bind_device_id__isnull", false).
Or("machine_code__isnull", false)
qs = qs.SetCond(cond)
qs = qs.Filter("delete_time__isnull", true)
if statusText != "" {
status, err := strconv.ParseInt(statusText, 10, 8)
if err == nil && cursorActivationCodeStatusValid(int8(status)) {
qs = qs.Filter("status", int8(status))
}
}
if typeText != "" {
cardType, err := strconv.Atoi(typeText)
if err == nil {
qs = qs.Filter("type", cardType)
}
}
}
}
}
return qs
}
// List GET /platform/cursor/activationcode/list
func (c *PlatformCursorActivationCodeController) List() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 20)
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
if pageSize > 200 {
pageSize = 200
}
qs := c.filteredQuery()
total, _ := qs.Count()
var rows []models.PlatformCursorActivationCode
_, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows)
if err != nil {
c.jsonErr(500, 500, "获取激活码列表失败: "+err.Error())
return
}
list := make([]map[string]interface{}, 0, len(rows))
for i := range rows {
list = append(list, c.rowToMap(&rows[i]))
}
c.ok(map[string]interface{}{
"list": list,
"total": total,
"page": page,
"pageSize": pageSize,
})
}
// Detail GET /platform/cursor/activationcode/detail/:id
func (c *PlatformCursorActivationCodeController) Detail() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
var row models.PlatformCursorActivationCode
err = models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).
Filter("id", id).
Filter("delete_time__isnull", true).
One(&row)
if err != nil {
c.jsonErr(404, 404, "激活码不存在")
return
}
c.ok(c.rowToMap(&row))
}
type platformCursorActivationCodePayload struct {
ID *uint64 `json:"id"`
Code *string `json:"code"`
Type *int `json:"type"`
Status *int8 `json:"status"`
DurationDays *int `json:"durationDays"`
BindAccount *string `json:"bindAccount"`
BindDeviceID *uint64 `json:"bindDeviceId"`
OwnerUserID *uint64 `json:"ownerUserId"`
OwnerUserName *string `json:"ownerUserName"`
ActivatedAt *string `json:"activatedAt"`
ExpiredAt *string `json:"expiredAt"`
Remark *string `json:"remark"`
}
func (c *PlatformCursorActivationCodeController) readPayload() (*platformCursorActivationCodePayload, error) {
body, _ := io.ReadAll(c.Ctx.Request.Body)
var p platformCursorActivationCodePayload
if err := json.Unmarshal(body, &p); err != nil {
return nil, err
}
return &p, nil
}
func (c *PlatformCursorActivationCodeController) fillDeviceSnapshot(up map[string]interface{}, bindDeviceID *uint64) {
if bindDeviceID == nil || *bindDeviceID == 0 {
up["bind_device_id"] = nil
up["machine_code"] = nil
up["device_info"] = nil
return
}
var device models.PlatformCursorEquipment
err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)).
Filter("id", *bindDeviceID).
Filter("delete_time__isnull", true).
One(&device)
if err == nil {
up["bind_device_id"] = *bindDeviceID
up["machine_code"] = device.MachineCode
up["device_info"] = device.DeviceInfo
return
}
up["bind_device_id"] = *bindDeviceID
}
func (c *PlatformCursorActivationCodeController) payloadToUpdateMap(p *platformCursorActivationCodePayload, includeCode bool) (map[string]interface{}, error) {
up := map[string]interface{}{}
if includeCode {
if p.Code == nil || strings.TrimSpace(*p.Code) == "" {
return nil, fmt.Errorf("激活码不能为空")
}
up["code"] = strings.TrimSpace(*p.Code)
} else if p.Code != nil {
if strings.TrimSpace(*p.Code) == "" {
return nil, fmt.Errorf("激活码不能为空")
}
up["code"] = strings.TrimSpace(*p.Code)
}
if p.Type != nil {
if *p.Type < 0 {
return nil, fmt.Errorf("卡密类型不合法")
}
up["type"] = *p.Type
}
if p.Status != nil {
if !cursorActivationCodeStatusValid(*p.Status) {
return nil, fmt.Errorf("状态不合法,支持:0 未使用、1 已使用、2 已过期、3 已禁用")
}
up["status"] = *p.Status
}
if p.DurationDays != nil {
if *p.DurationDays < 0 || *p.DurationDays > 9999 {
return nil, fmt.Errorf("有效天数范围为 0-9999")
}
up["duration_days"] = *p.DurationDays
}
if p.BindAccount != nil {
up["bind_account"] = cursorActivationCodeTrimPtr(p.BindAccount)
}
if p.BindDeviceID != nil {
c.fillDeviceSnapshot(up, p.BindDeviceID)
}
if p.OwnerUserID != nil {
if *p.OwnerUserID == 0 {
up["owner_user_id"] = nil
} else {
up["owner_user_id"] = *p.OwnerUserID
}
}
if p.OwnerUserName != nil {
up["owner_user_name"] = cursorActivationCodeTrimPtr(p.OwnerUserName)
}
if p.ActivatedAt != nil {
up["activated_at"] = cursorActivationCodeTimePtr(p.ActivatedAt)
}
if p.ExpiredAt != nil {
up["expired_at"] = cursorActivationCodeTimePtr(p.ExpiredAt)
}
if p.Remark != nil {
up["remark"] = cursorActivationCodeTrimPtr(p.Remark)
}
return up, nil
}
// Add POST /platform/cursor/activationcode/add
func (c *PlatformCursorActivationCodeController) Add() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
p, err := c.readPayload()
if err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
up, err := c.payloadToUpdateMap(p, true)
if err != nil {
c.jsonErr(400, 400, err.Error())
return
}
row := models.PlatformCursorActivationCode{
Code: up["code"].(string),
Type: 30,
Status: 0,
DurationDays: 30,
BindAccount: cursorActivationCodeTrimPtr(p.BindAccount),
BindDeviceID: p.BindDeviceID,
OwnerUserID: p.OwnerUserID,
OwnerUserName: cursorActivationCodeTrimPtr(p.OwnerUserName),
ActivatedAt: cursorActivationCodeTimePtr(p.ActivatedAt),
ExpiredAt: cursorActivationCodeTimePtr(p.ExpiredAt),
Remark: cursorActivationCodeTrimPtr(p.Remark),
CreateTime: time.Now(),
}
if p.Type != nil {
row.Type = *p.Type
}
if p.Status != nil {
row.Status = *p.Status
}
if p.DurationDays != nil {
row.DurationDays = *p.DurationDays
}
if row.BindDeviceID != nil && *row.BindDeviceID == 0 {
row.BindDeviceID = nil
}
if row.OwnerUserID != nil && *row.OwnerUserID == 0 {
row.OwnerUserID = nil
}
if row.BindDeviceID != nil {
var device models.PlatformCursorEquipment
if err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)).
Filter("id", *row.BindDeviceID).
Filter("delete_time__isnull", true).
One(&device); err == nil {
row.MachineCode = &device.MachineCode
row.DeviceInfo = device.DeviceInfo
}
}
id, err := models.Orm.Insert(&row)
if err != nil {
if strings.Contains(strings.ToLower(err.Error()), "duplicate") {
c.jsonErr(400, 400, "激活码已存在")
return
}
c.jsonErr(500, 500, "新增激活码失败: "+err.Error())
return
}
c.ok(map[string]interface{}{"id": id})
}
// Update POST /platform/cursor/activationcode/update
func (c *PlatformCursorActivationCodeController) Update() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
p, err := c.readPayload()
if err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
if p.ID == nil || *p.ID == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
up, err := c.payloadToUpdateMap(p, false)
if err != nil {
c.jsonErr(400, 400, err.Error())
return
}
if len(up) == 0 {
c.jsonErr(400, 400, "无更新字段")
return
}
now := time.Now()
up["update_time"] = now
n, err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).
Filter("id", *p.ID).
Filter("delete_time__isnull", true).
Update(up)
if err != nil {
if strings.Contains(strings.ToLower(err.Error()), "duplicate") {
c.jsonErr(400, 400, "激活码已存在")
return
}
c.jsonErr(500, 500, "更新激活码失败: "+err.Error())
return
}
if n == 0 {
c.jsonErr(404, 404, "激活码不存在")
return
}
c.ok(nil)
}
// Delete POST /platform/cursor/activationcode/delete/:id
func (c *PlatformCursorActivationCodeController) Delete() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
now := time.Now()
n, err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).
Filter("id", id).
Filter("delete_time__isnull", true).
Update(map[string]interface{}{"delete_time": now, "update_time": now})
if err != nil {
c.jsonErr(500, 500, "删除激活码失败: "+err.Error())
return
}
if n == 0 {
c.jsonErr(404, 404, "激活码不存在")
return
}
c.ok(nil)
}
type platformCursorActivationCodeGeneratePayload struct {
Count int `json:"count"`
Type int `json:"type"`
DurationDays int `json:"durationDays"`
OwnerUserID *uint64 `json:"ownerUserId"`
OwnerUserName *string `json:"ownerUserName"`
Remark *string `json:"remark"`
}
func randomCursorActivationCode() (string, error) {
b := make([]byte, 12)
if _, err := rand.Read(b); err != nil {
return "", err
}
return "CUR-" + strings.ToUpper(hex.EncodeToString(b)), nil
}
// Generate POST /platform/cursor/activationcode/generate
func (c *PlatformCursorActivationCodeController) Generate() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
body, _ := io.ReadAll(c.Ctx.Request.Body)
var p platformCursorActivationCodeGeneratePayload
if err := json.Unmarshal(body, &p); err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
if p.Count < 1 {
p.Count = 1
}
if p.Count > 10000 {
c.jsonErr(400, 400, "单次最多生成 10000 个激活码")
return
}
if p.Type < 0 {
c.jsonErr(400, 400, "卡密类型不合法")
return
}
if p.DurationDays < 0 || p.DurationDays > 9999 {
c.jsonErr(400, 400, "有效天数范围为 0-9999")
return
}
if p.Type == 0 && p.DurationDays == 0 {
p.DurationDays = 30
}
if p.Type > 0 && p.DurationDays == 0 {
p.DurationDays = p.Type
}
createdIDs := make([]int64, 0, p.Count)
codes := make([]string, 0, p.Count)
now := time.Now()
for len(createdIDs) < p.Count {
code, err := randomCursorActivationCode()
if err != nil {
c.jsonErr(500, 500, "生成激活码失败: "+err.Error())
return
}
row := models.PlatformCursorActivationCode{
Code: code,
Type: p.Type,
Status: 0,
DurationDays: p.DurationDays,
OwnerUserID: p.OwnerUserID,
OwnerUserName: cursorActivationCodeTrimPtr(p.OwnerUserName),
Remark: cursorActivationCodeTrimPtr(p.Remark),
CreateTime: now,
}
if row.OwnerUserID != nil && *row.OwnerUserID == 0 {
row.OwnerUserID = nil
}
id, err := models.Orm.Insert(&row)
if err != nil {
if strings.Contains(strings.ToLower(err.Error()), "duplicate") {
continue
}
c.jsonErr(500, 500, "生成激活码失败: "+err.Error())
return
}
createdIDs = append(createdIDs, id)
codes = append(codes, code)
}
c.ok(map[string]interface{}{
"count": len(createdIDs),
"ids": createdIDs,
"codes": codes,
})
}
// Enable POST /platform/cursor/activationcode/enable/:id
func (c *PlatformCursorActivationCodeController) Enable() {
c.changeStatus(0, "启用激活码失败")
}
// Disable POST /platform/cursor/activationcode/disable/:id
func (c *PlatformCursorActivationCodeController) Disable() {
c.changeStatus(3, "禁用激活码失败")
}
func (c *PlatformCursorActivationCodeController) changeStatus(status int8, failMsg string) {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
now := time.Now()
n, err := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).
Filter("id", id).
Filter("delete_time__isnull", true).
Update(map[string]interface{}{
"status": status,
"update_time": now,
})
if err != nil {
c.jsonErr(500, 500, failMsg+": "+err.Error())
return
}
if n == 0 {
c.jsonErr(404, 404, "激活码不存在")
return
}
c.ok(nil)
}
// Export GET /platform/cursor/activationcode/export
func (c *PlatformCursorActivationCodeController) Export() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
var rows []models.PlatformCursorActivationCode
_, err := c.filteredQuery().OrderBy("-id").Limit(50000).All(&rows)
if err != nil {
c.jsonErr(500, 500, "导出激活码失败: "+err.Error())
return
}
filename := fmt.Sprintf("cursor-activation-code-%s.csv", time.Now().Format("20060102150405"))
c.Ctx.Output.Header("Content-Type", "text/csv; charset=utf-8")
c.Ctx.Output.Header("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filename))
_, _ = c.Ctx.ResponseWriter.Write([]byte{0xEF, 0xBB, 0xBF})
writer := csv.NewWriter(c.Ctx.ResponseWriter)
_ = writer.Write([]string{
"ID", "激活码", "类型", "有效天数", "状态", "绑定账号", "绑定设备ID", "机器码", "归属用户ID", "归属用户", "激活时间", "过期时间", "创建时间", "备注",
})
statusText := map[int8]string{
0: "未使用",
1: "已使用",
2: "已过期",
3: "已禁用",
}
for i := range rows {
row := rows[i]
_ = writer.Write([]string{
strconv.FormatUint(row.ID, 10),
row.Code,
cursorActivationCodeTypeName(row.Type),
strconv.Itoa(row.DurationDays),
statusText[row.Status],
stringPtrValue(row.BindAccount),
uint64PtrValue(row.BindDeviceID),
stringPtrValue(row.MachineCode),
uint64PtrValue(row.OwnerUserID),
stringPtrValue(row.OwnerUserName),
timePtrValue(row.ActivatedAt),
timePtrValue(row.ExpiredAt),
row.CreateTime.Format("2006-01-02 15:04:05"),
stringPtrValue(row.Remark),
})
}
writer.Flush()
}
func stringPtrValue(value *string) string {
if value == nil {
return ""
}
return *value
}
func uint64PtrValue(value *uint64) string {
if value == nil {
return ""
}
return strconv.FormatUint(*value, 10)
}
func timePtrValue(value *time.Time) string {
if value == nil {
return ""
}
return value.Format("2006-01-02 15:04:05")
}
+681
View File
@@ -0,0 +1,681 @@
package controllers
import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"time"
"server/models"
"server/pkg/jwtutil"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
)
// PlatformCursorEquipmentController 平台端 Cursor 设备管理
type PlatformCursorEquipmentController struct {
beego.Controller
}
func (c *PlatformCursorEquipmentController) platformClaims() (*jwtutil.Claims, error) {
auth := c.Ctx.Request.Header.Get("Authorization")
if auth == "" {
return nil, fmt.Errorf("未登录")
}
parts := strings.SplitN(auth, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
return nil, fmt.Errorf("认证信息格式错误")
}
claims, err := jwtutil.ParseToken(parts[1])
if err != nil {
return nil, fmt.Errorf("无效的token")
}
if claims.UserType != "platform" {
return nil, fmt.Errorf("无权访问")
}
return claims, nil
}
func (c *PlatformCursorEquipmentController) jsonErr(httpStatus, bizCode int, msg string) {
c.Ctx.Output.SetStatus(httpStatus)
c.Data["json"] = map[string]interface{}{"code": bizCode, "msg": msg}
_ = c.ServeJSON()
}
func (c *PlatformCursorEquipmentController) ok(data interface{}) {
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "success", "data": data}
_ = c.ServeJSON()
}
func cursorEquipmentTrimPtr(value *string) *string {
if value == nil {
return nil
}
v := strings.TrimSpace(*value)
if v == "" {
return nil
}
return &v
}
func cursorEquipmentTimePtr(value *string) *time.Time {
if value == nil {
return nil
}
v := strings.TrimSpace(*value)
if v == "" {
return nil
}
layouts := []string{
time.RFC3339,
"2006-01-02 15:04:05",
"2006-01-02 15:04",
"2006-01-02",
}
for _, layout := range layouts {
if t, err := time.ParseInLocation(layout, v, time.Local); err == nil {
return &t
}
}
return nil
}
func cursorEquipmentStatusValid(status int8) bool {
return status == 0 || status == 1 || status == 2 || status == 3
}
func (c *PlatformCursorEquipmentController) cursorActivationSummary(row *models.PlatformCursorEquipment) (int64, *models.PlatformCursorActivationCode) {
cond := orm.NewCondition().
And("delete_time__isnull", true).
AndCond(orm.NewCondition().
Or("bind_device_id", row.ID).
Or("machine_code", row.MachineCode))
qs := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).SetCond(cond)
count, _ := qs.Count()
var latest models.PlatformCursorActivationCode
if err := qs.OrderBy("-activated_at", "-id").One(&latest); err != nil {
return count, nil
}
return count, &latest
}
func (c *PlatformCursorEquipmentController) cursorExtractSummary() (int64, *models.PlatformAccountPoolCursor) {
qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
Filter("delete_time__isnull", true).
Filter("is_extracted__gt", 0)
count, _ := qs.Count()
var latest models.PlatformAccountPoolCursor
if err := qs.OrderBy("-extracted_time", "-id").One(&latest); err != nil {
return count, nil
}
return count, &latest
}
func (c *PlatformCursorEquipmentController) rowToMap(row *models.PlatformCursorEquipment) map[string]interface{} {
activationCount, latestActivation := c.cursorActivationSummary(row)
extractCount, latestExtract := c.cursorExtractSummary()
var bindActivationCode interface{}
var activationCodeId interface{}
var lastActivatedAt interface{} = row.ActivationTime
var expireTime interface{} = row.ExpireTime
var lastExtractedAt interface{}
if latestActivation != nil {
bindActivationCode = latestActivation.Code
activationCodeId = latestActivation.ID
if latestActivation.ActivatedAt != nil {
lastActivatedAt = latestActivation.ActivatedAt
}
if latestActivation.ExpiredAt != nil {
expireTime = latestActivation.ExpiredAt
}
}
if latestExtract != nil {
lastExtractedAt = latestExtract.ExtractedTime
}
return map[string]interface{}{
"id": row.ID,
"deviceInfo": row.DeviceInfo,
"machineCode": row.MachineCode,
"status": row.Status,
"system": row.System,
"os": row.System,
"version": row.Version,
"bindAccount": row.BindAccount,
"bindActivationCode": bindActivationCode,
"activationCode": bindActivationCode,
"activationCodeId": activationCodeId,
"ownerUserId": row.OwnerUserID,
"ownerUserName": row.OwnerUserName,
"activationTime": lastActivatedAt,
"lastActivatedAt": lastActivatedAt,
"expireTime": expireTime,
"expiredAt": expireTime,
"activationCount": activationCount,
"extractCount": extractCount,
"lastExtractedAt": lastExtractedAt,
"remark": row.Remark,
"createTime": row.CreateTime,
"updateTime": row.UpdateTime,
}
}
// List GET /platform/cursor/equipment/list
func (c *PlatformCursorEquipmentController) List() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 20)
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
if pageSize > 200 {
pageSize = 200
}
keyword := strings.TrimSpace(c.GetString("keyword"))
statusText := strings.TrimSpace(c.GetString("status"))
system := strings.TrimSpace(c.GetString("system"))
if system == "" {
system = strings.TrimSpace(c.GetString("os"))
}
qs := models.Orm.QueryTable(new(models.PlatformCursorEquipment)).Filter("delete_time__isnull", true)
if keyword != "" {
cond := orm.NewCondition().
Or("machine_code__icontains", keyword).
Or("device_info__icontains", keyword).
Or("bind_account__icontains", keyword).
Or("owner_user_name__icontains", keyword).
Or("remark__icontains", keyword)
qs = qs.SetCond(cond)
}
if statusText != "" {
status, err := strconv.ParseInt(statusText, 10, 8)
if err == nil && cursorEquipmentStatusValid(int8(status)) {
qs = qs.Filter("status", int8(status))
}
}
if system != "" {
qs = qs.Filter("system__icontains", system)
}
total, _ := qs.Count()
var rows []models.PlatformCursorEquipment
_, err := qs.OrderBy("-id").Limit(pageSize, (page-1)*pageSize).All(&rows)
if err != nil {
c.jsonErr(500, 500, "获取设备列表失败: "+err.Error())
return
}
list := make([]map[string]interface{}, 0, len(rows))
for i := range rows {
list = append(list, c.rowToMap(&rows[i]))
}
c.ok(map[string]interface{}{
"list": list,
"total": total,
"page": page,
"pageSize": pageSize,
})
}
// Detail GET /platform/cursor/equipment/detail/:id
func (c *PlatformCursorEquipmentController) Detail() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
var row models.PlatformCursorEquipment
err = models.Orm.QueryTable(new(models.PlatformCursorEquipment)).
Filter("id", id).
Filter("delete_time__isnull", true).
One(&row)
if err != nil {
c.jsonErr(404, 404, "设备不存在")
return
}
c.ok(c.rowToMap(&row))
}
type platformCursorEquipmentPayload struct {
ID *uint64 `json:"id"`
DeviceInfo *string `json:"deviceInfo"`
MachineCode *string `json:"machineCode"`
Status *int8 `json:"status"`
System *string `json:"system"`
Version *string `json:"version"`
BindAccount *string `json:"bindAccount"`
OwnerUserID *uint64 `json:"ownerUserId"`
OwnerUserName *string `json:"ownerUserName"`
ActivationTime *string `json:"activationTime"`
ExpireTime *string `json:"expireTime"`
Remark *string `json:"remark"`
}
func (c *PlatformCursorEquipmentController) readPayload() (*platformCursorEquipmentPayload, error) {
body, _ := io.ReadAll(c.Ctx.Request.Body)
var p platformCursorEquipmentPayload
if err := json.Unmarshal(body, &p); err != nil {
return nil, err
}
return &p, nil
}
func (c *PlatformCursorEquipmentController) payloadToUpdateMap(p *platformCursorEquipmentPayload, includeMachineCode bool) (map[string]interface{}, error) {
up := map[string]interface{}{}
if includeMachineCode {
if p.MachineCode == nil || strings.TrimSpace(*p.MachineCode) == "" {
return nil, fmt.Errorf("机器码不能为空")
}
up["machine_code"] = strings.TrimSpace(*p.MachineCode)
} else if p.MachineCode != nil {
if strings.TrimSpace(*p.MachineCode) == "" {
return nil, fmt.Errorf("机器码不能为空")
}
up["machine_code"] = strings.TrimSpace(*p.MachineCode)
}
if p.DeviceInfo != nil {
up["device_info"] = cursorEquipmentTrimPtr(p.DeviceInfo)
}
if p.Status != nil {
if !cursorEquipmentStatusValid(*p.Status) {
return nil, fmt.Errorf("状态不合法,支持:0 未激活、1 激活中、2 已过期、3 已禁用")
}
up["status"] = *p.Status
}
if p.System != nil {
up["system"] = cursorEquipmentTrimPtr(p.System)
}
if p.Version != nil {
up["version"] = cursorEquipmentTrimPtr(p.Version)
}
if p.BindAccount != nil {
up["bind_account"] = cursorEquipmentTrimPtr(p.BindAccount)
}
if p.OwnerUserID != nil {
if *p.OwnerUserID == 0 {
up["owner_user_id"] = nil
} else {
up["owner_user_id"] = *p.OwnerUserID
}
}
if p.OwnerUserName != nil {
up["owner_user_name"] = cursorEquipmentTrimPtr(p.OwnerUserName)
}
if p.ActivationTime != nil {
up["activation_time"] = cursorEquipmentTimePtr(p.ActivationTime)
}
if p.ExpireTime != nil {
up["expire_time"] = cursorEquipmentTimePtr(p.ExpireTime)
}
if p.Remark != nil {
up["remark"] = cursorEquipmentTrimPtr(p.Remark)
}
return up, nil
}
// Add POST /platform/cursor/equipment/add
func (c *PlatformCursorEquipmentController) Add() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
p, err := c.readPayload()
if err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
up, err := c.payloadToUpdateMap(p, true)
if err != nil {
c.jsonErr(400, 400, err.Error())
return
}
status := int8(0)
if value, ok := up["status"]; ok {
status = value.(int8)
}
row := models.PlatformCursorEquipment{
MachineCode: up["machine_code"].(string),
Status: status,
DeviceInfo: cursorEquipmentTrimPtr(p.DeviceInfo),
System: cursorEquipmentTrimPtr(p.System),
Version: cursorEquipmentTrimPtr(p.Version),
BindAccount: cursorEquipmentTrimPtr(p.BindAccount),
OwnerUserID: p.OwnerUserID,
OwnerUserName: cursorEquipmentTrimPtr(p.OwnerUserName),
ActivationTime: cursorEquipmentTimePtr(p.ActivationTime),
ExpireTime: cursorEquipmentTimePtr(p.ExpireTime),
Remark: cursorEquipmentTrimPtr(p.Remark),
CreateTime: time.Now(),
}
if row.OwnerUserID != nil && *row.OwnerUserID == 0 {
row.OwnerUserID = nil
}
id, err := models.Orm.Insert(&row)
if err != nil {
if strings.Contains(strings.ToLower(err.Error()), "duplicate") {
c.jsonErr(400, 400, "机器码已存在")
return
}
c.jsonErr(500, 500, "新增设备失败: "+err.Error())
return
}
c.ok(map[string]interface{}{"id": id})
}
// Update POST /platform/cursor/equipment/update
func (c *PlatformCursorEquipmentController) Update() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
p, err := c.readPayload()
if err != nil {
c.jsonErr(400, 400, "参数错误")
return
}
if p.ID == nil || *p.ID == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
up, err := c.payloadToUpdateMap(p, false)
if err != nil {
c.jsonErr(400, 400, err.Error())
return
}
if len(up) == 0 {
c.jsonErr(400, 400, "无更新字段")
return
}
now := time.Now()
up["update_time"] = now
n, err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)).
Filter("id", *p.ID).
Filter("delete_time__isnull", true).
Update(up)
if err != nil {
if strings.Contains(strings.ToLower(err.Error()), "duplicate") {
c.jsonErr(400, 400, "机器码已存在")
return
}
c.jsonErr(500, 500, "更新设备失败: "+err.Error())
return
}
if n == 0 {
c.jsonErr(404, 404, "设备不存在")
return
}
c.ok(nil)
}
// Delete POST /platform/cursor/equipment/delete/:id
func (c *PlatformCursorEquipmentController) Delete() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
id, err := strconv.ParseUint(c.Ctx.Input.Param(":id"), 10, 64)
if err != nil || id == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
now := time.Now()
n, err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)).
Filter("id", id).
Filter("delete_time__isnull", true).
Update(map[string]interface{}{"delete_time": now})
if err != nil {
c.jsonErr(500, 500, "删除设备失败: "+err.Error())
return
}
if n == 0 {
c.jsonErr(404, 404, "设备不存在")
return
}
c.ok(nil)
}
type platformCursorEquipmentActivatePayload struct {
ID uint64 `json:"id"`
}
// Activate POST /platform/cursor/equipment/activate
func (c *PlatformCursorEquipmentController) Activate() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
body, _ := io.ReadAll(c.Ctx.Request.Body)
var p platformCursorEquipmentActivatePayload
if err := json.Unmarshal(body, &p); err != nil || p.ID == 0 {
c.jsonErr(400, 400, "无效ID")
return
}
now := time.Now()
n, err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)).
Filter("id", p.ID).
Filter("delete_time__isnull", true).
Update(map[string]interface{}{
"status": int8(1),
"activation_time": now,
"update_time": now,
})
if err != nil {
c.jsonErr(500, 500, "激活设备失败: "+err.Error())
return
}
if n == 0 {
c.jsonErr(404, 404, "设备不存在")
return
}
c.ok(nil)
}
// ActivationRecords GET /platform/cursor/equipment/activationRecords
func (c *PlatformCursorEquipmentController) ActivationRecords() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
equipmentID, _ := c.GetUint64("equipmentId")
if equipmentID == 0 {
equipmentID, _ = c.GetUint64("id")
}
if equipmentID == 0 {
c.jsonErr(400, 400, "缺少设备ID")
return
}
var equipment models.PlatformCursorEquipment
if err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)).
Filter("id", equipmentID).
Filter("delete_time__isnull", true).
One(&equipment); err != nil {
c.jsonErr(404, 404, "设备不存在")
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 20)
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
if pageSize > 200 {
pageSize = 200
}
cond := orm.NewCondition().
And("delete_time__isnull", true).
AndCond(orm.NewCondition().
Or("bind_device_id", equipment.ID).
Or("machine_code", equipment.MachineCode))
qs := models.Orm.QueryTable(new(models.PlatformCursorActivationCode)).SetCond(cond)
total, _ := qs.Count()
var rows []models.PlatformCursorActivationCode
if _, err := qs.OrderBy("-activated_at", "-id").Limit(pageSize, (page-1)*pageSize).All(&rows); err != nil {
c.jsonErr(500, 500, "获取激活记录失败: "+err.Error())
return
}
list := make([]map[string]interface{}, 0, len(rows))
for i := range rows {
row := rows[i]
list = append(list, map[string]interface{}{
"id": row.ID,
"code": row.Code,
"activationCode": row.Code,
"status": row.Status,
"durationDays": row.DurationDays,
"machineCode": row.MachineCode,
"deviceInfo": row.DeviceInfo,
"ownerUserId": row.OwnerUserID,
"ownerUserName": row.OwnerUserName,
"activatedAt": row.ActivatedAt,
"expiredAt": row.ExpiredAt,
"createdAt": row.CreateTime,
"remark": row.Remark,
})
}
c.ok(map[string]interface{}{
"list": list,
"total": total,
"page": page,
"pageSize": pageSize,
})
}
// ExtractRecords GET /platform/cursor/equipment/extractRecords
func (c *PlatformCursorEquipmentController) ExtractRecords() {
if _, err := c.platformClaims(); err != nil {
c.jsonErr(401, 401, err.Error())
return
}
equipmentID, _ := c.GetUint64("equipmentId")
if equipmentID == 0 {
equipmentID, _ = c.GetUint64("id")
}
if equipmentID == 0 {
c.jsonErr(400, 400, "缺少设备ID")
return
}
var equipment models.PlatformCursorEquipment
if err := models.Orm.QueryTable(new(models.PlatformCursorEquipment)).
Filter("id", equipmentID).
Filter("delete_time__isnull", true).
One(&equipment); err != nil {
c.jsonErr(404, 404, "设备不存在")
return
}
page, _ := c.GetInt("page", 1)
pageSize, _ := c.GetInt("pageSize", 20)
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 20
}
if pageSize > 200 {
pageSize = 200
}
qs := models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
Filter("delete_time__isnull", true).
Filter("is_extracted__gt", 0)
total, _ := qs.Count()
var rows []models.PlatformAccountPoolCursor
if _, err := qs.OrderBy("-extracted_time", "-id").Limit(pageSize, (page-1)*pageSize).All(&rows); err != nil {
c.jsonErr(500, 500, "获取提取记录失败: "+err.Error())
return
}
list := make([]map[string]interface{}, 0, len(rows))
for i := range rows {
row := rows[i]
content := buildCardResult(&row.Account, &row.Password, row.Token, row.DataType)
list = append(list, map[string]interface{}{
"id": row.ID,
"status": row.IsExtracted,
"isExtracted": row.IsExtracted,
"platform": row.ExtractedPlatform,
"extractedPlatform": row.ExtractedPlatform,
"dataType": row.DataType,
"type": row.DataType,
"account": row.Account,
"password": row.Password,
"token": row.Token,
"content": content,
"extractedAt": row.ExtractedTime,
"createdAt": row.ExtractedTime,
"remark": row.Remark,
})
}
c.ok(map[string]interface{}{
"list": list,
"total": total,
"page": page,
"pageSize": pageSize,
})
}
+63
View File
@@ -0,0 +1,63 @@
package controllers
import (
"strings"
"time"
"server/models"
"server/pkg/tokenprobe"
)
func poolTableName(module string) string {
switch module {
case "cursor":
return new(models.PlatformAccountPoolCursor).TableName()
case "windsurf":
return new(models.PlatformAccountPoolWindsurf).TableName()
case "krio":
return new(models.PlatformAccountPoolKiro).TableName()
default:
return ""
}
}
// poolNeedsTokenProbe account 类型无 Token,无需探测;tk / account_tk 需探测。
func poolNeedsTokenProbe(dataType, token string) bool {
if strings.TrimSpace(token) == "" {
return false
}
return dataType != "account"
}
func poolSaveCursorIsUsed(id uint64, isUsed int8) {
_, _ = models.Orm.QueryTable(new(models.PlatformAccountPoolCursor)).
Filter("id", id).
Update(map[string]interface{}{
"is_used": isUsed,
"update_time": time.Now(),
})
}
// poolProbeToken 探测 Tokencursor 模块会回写 is_used。
func poolProbeToken(module, dataType, token string, rowID uint64) bool {
if !poolNeedsTokenProbe(dataType, token) {
return true
}
r := tokenprobe.ProbeOfficial(module, token)
if module == "cursor" && rowID > 0 {
var isUsed int8
if r.OK {
isUsed = 1
}
poolSaveCursorIsUsed(rowID, isUsed)
}
return r.OK
}
// poolIsUsedAvailable 已有探测结论时:1=可用,0=不可用,nil=未探测。
func poolIsUsedAvailable(isUsed *int8) (known bool, available bool) {
if isUsed == nil {
return false, false
}
return true, *isUsed == 1
}
@@ -0,0 +1,31 @@
-- Cursor 激活码管理
-- status: 0 未使用 1 已使用 2 已过期 3 已禁用
-- type: 0 自定义 1 天卡 7 周卡 30 月卡 90 季卡 365 年卡
CREATE TABLE IF NOT EXISTS `yz_platform_cursor_activation_code` (
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`code` varchar(128) NOT NULL COMMENT '激活码',
`type` int NOT NULL DEFAULT 30 COMMENT '卡密类型:0自定义 1天卡 7周卡 30月卡 90季卡 365年卡',
`status` tinyint NOT NULL DEFAULT 0 COMMENT '状态:0未使用 1已使用 2已过期 3已禁用',
`duration_days` int NOT NULL DEFAULT 30 COMMENT '有效天数',
`bind_account` varchar(128) DEFAULT NULL COMMENT '绑定账号',
`bind_device_id` bigint unsigned DEFAULT NULL COMMENT '绑定设备ID,关联 yz_platform_cursor_equipment.id',
`machine_code` varchar(128) DEFAULT NULL COMMENT '绑定设备机器码',
`device_info` varchar(1000) DEFAULT NULL COMMENT '绑定设备信息',
`owner_user_id` bigint unsigned DEFAULT NULL COMMENT '归属用户ID',
`owner_user_name` varchar(128) DEFAULT NULL COMMENT '归属用户名称',
`activated_at` datetime DEFAULT NULL COMMENT '激活时间',
`expired_at` datetime DEFAULT NULL COMMENT '过期时间',
`remark` varchar(1000) DEFAULT NULL COMMENT '备注',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_time` datetime DEFAULT NULL COMMENT '删除时间',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_code` (`code`),
KEY `idx_status_delete` (`status`,`delete_time`),
KEY `idx_type_status` (`type`,`status`),
KEY `idx_bind_account` (`bind_account`),
KEY `idx_bind_device_id` (`bind_device_id`),
KEY `idx_owner_user_id` (`owner_user_id`),
KEY `idx_expired_at` (`expired_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Cursor续杯激活码';
+115 -148
View File
@@ -1,53 +1,24 @@
package models
import (
"fmt"
"strings"
"sync"
"time"
"github.com/beego/beego/v2/client/orm"
)
// CmsArticle CMS文章表: yz_cms_article
type CmsArticle struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Tid uint64 `orm:"column(tid)" json:"tid"`
Title string `orm:"column(title);size(255)" json:"title"`
Author string `orm:"column(author);size(100);default()" json:"author"`
CateID uint64 `orm:"column(cate_id);default(0)" json:"cate_id"`
Content string `orm:"column(content);type(text);null" json:"content"`
Desc string `orm:"column(desc);size(500);default()" json:"desc"`
Image string `orm:"column(image);size(500);default()" json:"image"`
IsTrans int8 `orm:"column(is_trans);default(0)" json:"is_trans"`
TransURL *string `orm:"column(transurl);size(500);null" json:"transurl"`
Status int8 `orm:"column(status);default(1)" json:"status"` // 1草稿 2已发布 3已下架
Views int64 `orm:"column(views);default(0)" json:"views"`
Likes int64 `orm:"column(likes);default(0)" json:"likes"`
Top int8 `orm:"column(top);default(0)" json:"top"`
Recommend int8 `orm:"column(recommend);default(0)" json:"recommend"`
PublishTime *time.Time `orm:"column(publish_time);type(datetime);null" json:"publish_time"`
PublisherID uint64 `orm:"column(publisher_id);default(0)" json:"publisher_id"`
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
func (m *CmsArticle) TableName() string {
return "yz_cms_article"
}
// CmsArticleCategory CMS文章分类表: yz_cms_article_category
// CmsArticleCategory CMS 文章分类 yz_cms_article_category
type CmsArticleCategory struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Tid uint64 `orm:"column(tid)" json:"tid"`
Cid uint64 `orm:"column(cid);default(0)" json:"cid"` // 父级分类ID
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
Cid uint64 `orm:"column(cid);default(0)" json:"cid"`
Name string `orm:"column(name);size(100)" json:"name"`
Image string `orm:"column(image);size(500);default()" json:"image"`
Desc string `orm:"column(desc);size(500);default()" json:"desc"`
Sort int `orm:"column(sort);default(0)" json:"sort"`
Status int8 `orm:"column(status);default(1)" json:"status"`
CreateTime time.Time `orm:"column(create_time);type(datetime);auto_now_add" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);auto_now;null" json:"update_time"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
@@ -55,137 +26,133 @@ func (m *CmsArticleCategory) TableName() string {
return "yz_cms_article_category"
}
// EnsureCmsArticleTables 确保 CMS 文章相关表存在。
func EnsureCmsArticleTables() error {
sqls := []string{
`CREATE TABLE IF NOT EXISTS ` + "`yz_cms_article_category`" + ` (
` + "`id`" + ` bigint unsigned NOT NULL AUTO_INCREMENT,
` + "`tid`" + ` bigint unsigned NOT NULL DEFAULT 0,
` + "`cid`" + ` bigint unsigned NOT NULL DEFAULT 0,
` + "`name`" + ` varchar(100) NOT NULL DEFAULT '',
` + "`image`" + ` varchar(500) NOT NULL DEFAULT '',
` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '',
` + "`sort`" + ` int NOT NULL DEFAULT 0,
` + "`status`" + ` tinyint NOT NULL DEFAULT 1,
` + "`create_time`" + ` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
` + "`update_time`" + ` datetime NULL DEFAULT NULL,
` + "`delete_time`" + ` datetime NULL DEFAULT NULL,
PRIMARY KEY (` + "`id`" + `),
KEY ` + "`idx_tid`" + ` (` + "`tid`" + `),
KEY ` + "`idx_tid_cid`" + ` (` + "`tid`" + `,` + "`cid`" + `),
KEY ` + "`idx_delete_time`" + ` (` + "`delete_time`" + `)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
`CREATE TABLE IF NOT EXISTS ` + "`yz_cms_article`" + ` (
` + "`id`" + ` bigint unsigned NOT NULL AUTO_INCREMENT,
` + "`tid`" + ` bigint unsigned NOT NULL DEFAULT 0,
` + "`title`" + ` varchar(255) NOT NULL DEFAULT '',
` + "`author`" + ` varchar(100) NOT NULL DEFAULT '',
` + "`cate_id`" + ` bigint unsigned NOT NULL DEFAULT 0,
` + "`content`" + ` longtext NULL,
` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '',
` + "`image`" + ` varchar(500) NOT NULL DEFAULT '',
` + "`is_trans`" + ` tinyint NOT NULL DEFAULT 0,
` + "`transurl`" + ` varchar(500) NULL DEFAULT NULL,
` + "`status`" + ` tinyint NOT NULL DEFAULT 1,
` + "`views`" + ` bigint NOT NULL DEFAULT 0,
` + "`likes`" + ` bigint NOT NULL DEFAULT 0,
` + "`top`" + ` tinyint NOT NULL DEFAULT 0,
` + "`recommend`" + ` tinyint NOT NULL DEFAULT 0,
` + "`publish_time`" + ` datetime NULL DEFAULT NULL,
` + "`publisher_id`" + ` bigint unsigned NOT NULL DEFAULT 0,
` + "`create_time`" + ` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
` + "`update_time`" + ` datetime NULL DEFAULT NULL,
` + "`delete_time`" + ` datetime NULL DEFAULT NULL,
PRIMARY KEY (` + "`id`" + `),
KEY ` + "`idx_tid`" + ` (` + "`tid`" + `),
KEY ` + "`idx_tid_cate`" + ` (` + "`tid`" + `,` + "`cate_id`" + `),
KEY ` + "`idx_tid_status`" + ` (` + "`tid`" + `,` + "`status`" + `),
KEY ` + "`idx_delete_time`" + ` (` + "`delete_time`" + `)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
}
for _, sqlStr := range sqls {
if _, err := Orm.Raw(sqlStr).Exec(); err != nil {
return err
}
}
return nil
// CmsArticle CMS 文章 yz_cms_article
type CmsArticle struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Tid uint64 `orm:"column(tid);default(0)" json:"tid"`
Title string `orm:"column(title);size(255)" json:"title"`
Author string `orm:"column(author);size(100);default()" json:"author"`
CateID uint64 `orm:"column(cate_id);default(0)" json:"cate_id"`
Content string `orm:"column(content);type(mediumtext);null" json:"content"`
Desc string `orm:"column(desc);size(500);default()" json:"desc"`
Image string `orm:"column(image);size(500);default()" json:"image"`
IsTrans int8 `orm:"column(is_trans);default(0)" json:"is_trans"`
TransURL *string `orm:"column(transurl);size(500);null" json:"transurl"`
Status int8 `orm:"column(status);default(0)" json:"status"`
Top int8 `orm:"column(top);default(0)" json:"top"`
Recommend int8 `orm:"column(recommend);default(0)" json:"recommend"`
Views int `orm:"column(views);default(0)" json:"views"`
Likes int `orm:"column(likes);default(0)" json:"likes"`
PublisherID *uint64 `orm:"column(publisher_id);null" json:"publisher_id"`
PublishTime *time.Time `orm:"column(publish_time);type(datetime);null" json:"publish_time"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"create_time"`
UpdateTime *time.Time `orm:"column(update_time);type(datetime);null" json:"update_time"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"delete_time"`
}
func (m *CmsArticle) TableName() string {
return "yz_cms_article"
}
var cmsArticleTablesOnce sync.Once
// EnsureCmsArticleTables 首次使用时自动建表(若不存在)。
func EnsureCmsArticleTables() error {
var err error
cmsArticleTablesOnce.Do(func() {
_, err = Orm.Raw(`
CREATE TABLE IF NOT EXISTS yz_cms_article_category (
id bigint unsigned NOT NULL AUTO_INCREMENT,
tid bigint unsigned NOT NULL DEFAULT 0,
cid bigint unsigned NOT NULL DEFAULT 0,
name varchar(100) NOT NULL DEFAULT '',
image varchar(500) NOT NULL DEFAULT '',
` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '',
sort int NOT NULL DEFAULT 0,
status tinyint NOT NULL DEFAULT 1,
create_time datetime NOT NULL,
update_time datetime DEFAULT NULL,
delete_time datetime DEFAULT NULL,
PRIMARY KEY (id),
KEY idx_tid_cid (tid, cid)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec()
if err != nil {
return
}
_, err = Orm.Raw(`
CREATE TABLE IF NOT EXISTS yz_cms_article (
id bigint unsigned NOT NULL AUTO_INCREMENT,
tid bigint unsigned NOT NULL DEFAULT 0,
title varchar(255) NOT NULL DEFAULT '',
author varchar(100) NOT NULL DEFAULT '',
cate_id bigint unsigned NOT NULL DEFAULT 0,
content mediumtext,
` + "`desc`" + ` varchar(500) NOT NULL DEFAULT '',
image varchar(500) NOT NULL DEFAULT '',
is_trans tinyint NOT NULL DEFAULT 0,
transurl varchar(500) DEFAULT NULL,
status tinyint NOT NULL DEFAULT 0,
top tinyint NOT NULL DEFAULT 0,
recommend tinyint NOT NULL DEFAULT 0,
views int NOT NULL DEFAULT 0,
likes int NOT NULL DEFAULT 0,
publisher_id bigint unsigned DEFAULT NULL,
publish_time datetime DEFAULT NULL,
create_time datetime NOT NULL,
update_time datetime DEFAULT NULL,
delete_time datetime DEFAULT NULL,
PRIMARY KEY (id),
KEY idx_tid_status (tid, status),
KEY idx_cate_id (cate_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`).Exec()
})
return err
}
func CmsCategoryNameMap(tid uint64, ids []uint64) map[uint64]string {
out := make(map[uint64]string)
if len(ids) == 0 {
return out
}
var rows []CmsArticleCategory
_, _ = Orm.QueryTable(new(CmsArticleCategory)).
Filter("tid", tid).
Filter("id__in", ids).
Filter("delete_time__isnull", true).
All(&rows, "ID", "Name")
for _, r := range rows {
out[r.ID] = r.Name
}
return out
}
// CmsFormatTime 格式化 CMS 可空时间。
func CmsFormatTime(t *time.Time) string {
if t == nil || t.IsZero() {
if t == nil {
return ""
}
return t.Format("2006-01-02 15:04:05")
}
// CmsCategoryNameMap 批量获取分类ID到分类名称的映射。
func CmsCategoryNameMap(tid uint64, cateIDs []uint64) map[uint64]string {
result := make(map[uint64]string)
if tid == 0 || len(cateIDs) == 0 {
return result
}
seen := make(map[uint64]bool)
ids := make([]uint64, 0, len(cateIDs))
for _, id := range cateIDs {
if id == 0 || seen[id] {
continue
}
seen[id] = true
ids = append(ids, id)
}
if len(ids) == 0 {
return result
}
var rows []CmsArticleCategory
_, err := Orm.QueryTable(new(CmsArticleCategory)).
Filter("tid", tid).
Filter("id__in", ids).
Filter("delete_time__isnull", true).
All(&rows, "id", "name")
if err != nil && err != orm.ErrNoRows {
return result
}
for _, row := range rows {
result[row.ID] = row.Name
}
return result
}
// CmsSimilarArticles 根据标题查找相似文章,用于创建文章时提示重复内容。
func CmsSimilarArticles(tid uint64, title string, limit int) ([]map[string]interface{}, error) {
title = strings.TrimSpace(title)
if tid == 0 || title == "" {
return []map[string]interface{}{}, nil
}
func CmsSimilarArticles(tid uint64, title string, limit int) ([]orm.Params, error) {
if limit <= 0 {
limit = 5
}
var rows []CmsArticle
_, err := Orm.QueryTable(new(CmsArticle)).
Filter("tid", tid).
Filter("title__icontains", title).
Filter("delete_time__isnull", true).
OrderBy("-id").
Filter("title__icontains", title).
Limit(limit).
All(&rows, "id", "title", "cate_id", "status", "create_time")
if err != nil && err != orm.ErrNoRows {
All(&rows, "ID", "Title")
if err != nil {
return nil, err
}
out := make([]map[string]interface{}, 0, len(rows))
for _, row := range rows {
out = append(out, map[string]interface{}{
"id": row.ID,
"title": row.Title,
"cate_id": row.CateID,
"status": row.Status,
"create_time": row.CreateTime.Format("2006-01-02 15:04:05"),
"similarity": fmt.Sprintf("标题包含“%s”", title),
out := make([]orm.Params, 0, len(rows))
for _, r := range rows {
out = append(out, orm.Params{
"id": r.ID,
"title": r.Title,
"similarity": 80,
})
}
return out, nil
+4 -2
View File
@@ -56,11 +56,13 @@ func Init(_ string) {
new(ComplaintCategory),
new(PlatformComplaint),
new(SystemSoftwareUpgrade),
new(CmsArticle),
new(CmsArticleCategory),
new(PlatformCursorEquipment),
new(PlatformCursorActivationCode),
new(PlatformAccountPoolKiro),
new(PlatformAccountPoolWindsurf),
new(PlatformAccountPoolCursor),
new(CmsArticleCategory),
new(CmsArticle),
)
// 创建全局 Ormer
@@ -0,0 +1,28 @@
package models
import "time"
// PlatformCursorActivationCode Cursor 续杯激活码 yz_platform_cursor_activation_code
type PlatformCursorActivationCode struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
Code string `orm:"column(code);size(128);unique" json:"code"`
Type int `orm:"column(type);default(30)" json:"type"`
Status int8 `orm:"column(status);default(0)" json:"status"`
DurationDays int `orm:"column(duration_days);default(30)" json:"durationDays"`
BindAccount *string `orm:"column(bind_account);size(128);null" json:"bindAccount"`
BindDeviceID *uint64 `orm:"column(bind_device_id);null" json:"bindDeviceId"`
MachineCode *string `orm:"column(machine_code);size(128);null" json:"machineCode"`
DeviceInfo *string `orm:"column(device_info);size(1000);null" json:"deviceInfo"`
OwnerUserID *uint64 `orm:"column(owner_user_id);null" json:"ownerUserId"`
OwnerUserName *string `orm:"column(owner_user_name);size(128);null" json:"ownerUserName"`
ActivatedAt *time.Time `orm:"column(activated_at);type(datetime);null" json:"activatedAt"`
ExpiredAt *time.Time `orm:"column(expired_at);type(datetime);null" json:"expiredAt"`
Remark *string `orm:"column(remark);size(1000);null" json:"remark"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"`
UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"`
}
func (m *PlatformCursorActivationCode) TableName() string {
return "yz_platform_cursor_activation_code"
}
+26
View File
@@ -0,0 +1,26 @@
package models
import "time"
// PlatformCursorEquipment Cursor 设备管理 yz_platform_cursor_equipment
type PlatformCursorEquipment struct {
ID uint64 `orm:"column(id);pk;auto" json:"id"`
DeviceInfo *string `orm:"column(device_info);size(1000);null" json:"deviceInfo"`
MachineCode string `orm:"column(machine_code);size(128);unique" json:"machineCode"`
Status int8 `orm:"column(status);default(0)" json:"status"`
System *string `orm:"column(system);size(64);null" json:"system"`
Version *string `orm:"column(version);size(64);null" json:"version"`
BindAccount *string `orm:"column(bind_account);size(128);null" json:"bindAccount"`
OwnerUserID *uint64 `orm:"column(owner_user_id);null" json:"ownerUserId"`
OwnerUserName *string `orm:"column(owner_user_name);size(128);null" json:"ownerUserName"`
ActivationTime *time.Time `orm:"column(activation_time);type(datetime);null" json:"activationTime"`
ExpireTime *time.Time `orm:"column(expire_time);type(datetime);null" json:"expireTime"`
Remark *string `orm:"column(remark);size(1000);null" json:"remark"`
CreateTime time.Time `orm:"column(create_time);auto_now_add;type(datetime)" json:"createTime"`
UpdateTime *time.Time `orm:"column(update_time);auto_now;type(datetime);null" json:"updateTime"`
DeleteTime *time.Time `orm:"column(delete_time);type(datetime);null" json:"deleteTime"`
}
func (m *PlatformCursorEquipment) TableName() string {
return "yz_platform_cursor_equipment"
}
+1 -1
View File
@@ -16,5 +16,5 @@ type SystemTenantDomain struct {
}
func (m *SystemTenantDomain) TableName() string {
return "yz_tenant_domain"
return "yz_system_tenant_domain"
}
+73 -240
View File
@@ -10,7 +10,6 @@ import (
"io"
"net/http"
"os"
"regexp"
"runtime"
"strings"
"time"
@@ -23,7 +22,7 @@ import (
const (
cursorBackendURL = "https://api2.cursor.sh"
cursorAgentPath = "/aiserver.v1.ChatService/StreamUnifiedChatWithTools"
cursorClientVersion = "3.6.31"
cursorClientVersion = "2.6.22"
cursorHiMaxRead = 512 * 1024
// probeHiText 发往官方 Agent 的探测内容(与前端展示 probeMessage 一致)
probeHiText = "hi"
@@ -277,43 +276,55 @@ var cursorQuotaTipSig = []byte("Get Cursor Pro for more Agent usage, unlimited T
const cursorLimitTipPrefix = "Get Cursor Pro for more Agent usage, unlimited Tab"
// classifyCursorRawStream 在官方流式二进制/文本中匹配用量与升级提示
// 返回 (isQuotaExhausted, message)
// isQuotaExhausted: true 表示额度用完/Token不可用,false 表示 Token 可用(可能有警告信息)
func classifyCursorRawStream(raw []byte) (isQuotaExhausted bool, message string) {
// classifyCursorRawStream 在官方流式二进制/文本中匹配用量与升级提示ASCII 区不区分大小写 + UTF-8 短语)
func classifyCursorRawStream(raw []byte) (blocked bool, reason string) {
if len(raw) == 0 {
return false, ""
}
// 额度用尽只按明确完整提示判定,避免“可用但带推广/提示文案”的 Token 被误标为已用完。
// 用户自定义的二进制特征仍保留给部署方精确配置。
for _, sig := range cursorQuotaExhaustedSigsFromEnv() {
if bytes.Contains(raw, sig) {
return true, "该TOKEN已用完(额度已耗尽)"
return true, fmt.Sprintf("流中匹配:CURSOR_QUOTA_EXHAUSTED_SIG_HEX 配置的二进制特征(%d 字节)", len(sig))
}
}
if bytes.Contains(raw, cursorQuotaTipSig) {
return true, "该TOKEN已用完(Get Cursor Pro for more Agent usage, unlimited Tab, and more."
return true, "流中匹配:" + string(cursorQuotaTipSig)
}
// 社区脚本:仅到「…Agent usage」的 ASCII 前缀(流里可能只有前半段)
if bytes.Contains(raw, cursorQuotaExhaustedSigCommunity) {
return true, "流中匹配:Get Cursor Pro for more Agent usage…(社区 QuotaExhaustedSignature 前缀)"
}
if bytes.Contains(raw, []byte(cursorLimitTipPrefix)) {
return true, "流中匹配:" + cursorLimitTipPrefix + "…"
}
low := append([]byte(nil), raw...)
asciiLowerInPlace(low)
if bytes.Contains(low, []byte("you've hit your usage limit")) ||
bytes.Contains(low, []byte("youve hit your usage limit")) ||
bytes.Contains(low, []byte("hit your usage limit")) {
return true, "流中匹配:hit your usage limit / you've hit your usage limit"
}
if bytes.Contains(low, []byte("get cursor pro for more agent usage")) {
return true, "流中匹配:get cursor pro for more agent usage"
}
if bytes.Contains(low, []byte("upgrade to pro")) {
return true, "流中匹配:upgrade to pro"
}
if bytes.Contains(low, []byte("get cursor pro")) && bytes.Contains(low, []byte("agent")) {
return true, "流中匹配:get cursor pro + agent"
}
if bytes.Contains(low, []byte("usage limit")) {
return true, "流中匹配:usage limit"
}
if bytes.Contains(low, []byte("unlimited tab")) && bytes.Contains(low, []byte("cursor pro")) {
return true, "流中匹配:unlimited tab + cursor pro"
}
flat := strings.ToLower(strings.ToValidUTF8(string(raw), "\uFFFD"))
flat = strings.ReplaceAll(flat, "\u2019", "'")
flat = strings.ReplaceAll(flat, "\u2019", "'") // 右单引号
flat = strings.ReplaceAll(flat, "`", "'")
if strings.Contains(flat, "suspicious activity") ||
strings.Contains(flat, "unauthenticated") ||
strings.Contains(flat, "unauthorized request") ||
strings.Contains(flat, "unauthorizedrequest") ||
strings.Contains(flat, "error_unauthorized") {
return true, "该TOKEN不可用(账号触发可疑活动风控/未认证,需要重新登录)"
if strings.Contains(flat, "you've hit your usage limit") {
return true, "流中匹配:you've hit your usage limitUTF-8"
}
// 版本过旧警告 - 这不是额度问题,Token 仍然可用
// 返回 false,表示 Token 可用
if strings.Contains(flat, "very old version") || strings.Contains(flat, "update to the latest version") {
return false, "Token可用,但客户端版本过旧,建议更新到最新版本"
}
return false, ""
}
@@ -389,29 +400,41 @@ func decodeConnectFramedBody(raw []byte) ([]byte, string, bool) {
return nil, "", false
}
return out.Bytes(), "", true
note := fmt.Sprintf("响应体已按 Connect 分帧解析(%d 帧", frameCount)
if compressedFrames > 0 {
note += fmt.Sprintf(",其中 %d 帧已做 gzip 解压", compressedFrames)
}
note += ")后分析"
return out.Bytes(), note, true
}
func decodeCursorResponseBody(raw []byte, contentEncoding string) ([]byte, string) {
if decoded, _, ok := decodeConnectFramedBody(raw); ok {
return decoded, ""
if decoded, note, ok := decodeConnectFramedBody(raw); ok {
return decoded, note
}
enc := strings.ToLower(strings.TrimSpace(contentEncoding))
if strings.Contains(enc, "gzip") || looksLikeGzip(raw) {
decoded, err := gunzipBytes(raw)
if err != nil {
return raw, ""
if strings.Contains(enc, "gzip") {
return raw, "响应头声明 gzip,但解压失败,已回退为原始字节预览"
}
return raw, "检测到 gzip 魔数,但解压失败,已回退为原始字节预览"
}
return decoded, ""
if strings.Contains(enc, "gzip") {
return decoded, "响应体已按 gzip 解压后分析"
}
return decoded, "响应体虽未显式声明 Content-Encoding,但按 gzip 魔数解压后分析"
}
return raw, ""
if enc != "" {
return raw, "响应头 Content-Encoding=" + enc + ",当前未额外解码,按原始字节分析"
}
return raw, "响应体未压缩或未声明压缩,且未识别为 Connect 分帧,按原始字节分析"
}
// cursorStreamProtocol 与官方客户端一致:Connect-RPC + protobuf 体,HTTP/2 流式
// 当前探测接口使用新版 Agent/aiserver.v1.ChatService/StreamUnifiedChatWithTools。
// 若 Cursor 后续强制更高客户端版本,可通过环境变量 CURSOR_CLIENT_VERSION 覆盖默认 X-Cursor-Client-Version。
const cursorStreamProtocol = "Connect-Protocol-Version:1 + application/connect+protoHTTP/2 二进制流(gRPC/ConnectRPC 兼容形态,非 JSON REST"
// cursorStreamProtocol 与官方客户端一致:Connect-RPC + protobuf 体,HTTP/2 流式
const cursorStreamProtocol = "Connect-Protocol-Version:1 + application/connect+protoHTTP/2 二进制流(gRPC 兼容形态,非 JSON REST"
// cursorStreamNote 说明 rawPreview / ok 的含义边界(与「仅通 200」结论一致)
const cursorStreamNote = `【协议】本 URL 为 Cursor 官方 Agent 流式接口,请求体为 protobufrequestBodyPrefixHex 可见非表单/JSON)。` +
@@ -461,189 +484,6 @@ func cursorProbeResult(ok bool, detail string, httpStatus int, reqBody, raw, pre
}
}
// cursorReadableServerOutput 从 Cursor 的 protobuf 二进制流里提取适合展示的可读文本。
// 注意:这里不完整解析 proto,只做展示层清洗,避免把字段号、长度前缀、UUID、think 过程等内容直接展示给用户。
func cursorReadableServerOutput(decoded []byte, maxBytes int) string {
if len(decoded) == 0 {
return ""
}
s := strings.ToValidUTF8(string(decoded), "")
s = strings.ReplaceAll(s, "\uFFFD", "")
finalMarkerRe := regexp.MustCompile(`(?is)<\s*[|]\s*final\s*[|]\s*>`)
s = finalMarkerRe.ReplaceAllString(s, "<final>")
var b strings.Builder
lastSpace := false
for _, r := range s {
switch {
case r == '\r' || r == '\n' || r == '\t' || r == ' ':
if !lastSpace {
b.WriteByte('\n')
}
lastSpace = true
case r >= 32:
b.WriteRune(r)
lastSpace = false
default:
// protobuf 字段号、长度前缀等控制字符经常刚好位于单词/JSON 字段之间。
// 这里用分隔符替代直接丢弃,避免 Your + request 被粘成 Yourrequest。
if !lastSpace {
b.WriteByte('\n')
}
lastSpace = true
}
}
cleaned := strings.TrimSpace(b.String())
if cleaned == "" {
return ""
}
uuidRe := regexp.MustCompile(`(?i)\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b`)
cleaned = uuidRe.ReplaceAllString(cleaned, "")
var parts []string
const oldVersionText = "This is a very old version of Cursor. Please update to the latest version at [cursor.com/downloads](https://cursor.com/downloads)"
if strings.Contains(cleaned, oldVersionText) {
parts = append(parts, oldVersionText)
}
// 优先展示最终回复:先按 <final> 切分;没有 final 标记时,按被二进制流切碎的 </think> 标记切分。
finalText := ""
if idx := strings.LastIndex(cleaned, "<final>"); idx >= 0 {
finalText = cleaned[idx+len("<final>"):]
} else {
thinkCloseRe := regexp.MustCompile(`(?is)</\s*t\s*h\s*i\s*n\s*k\s*>`)
matches := thinkCloseRe.FindAllStringIndex(cleaned, -1)
if len(matches) > 0 {
finalText = cleaned[matches[len(matches)-1][1]:]
}
}
if strings.TrimSpace(finalText) == "" {
finalText = cleaned
}
finalText = cursorJoinFragmentedText(finalText)
if errorMessage := cursorExtractReadableCursorError(finalText); errorMessage != "" {
finalText = errorMessage
}
finalText = strings.TrimSuffix(finalText, "{}")
finalText = strings.TrimSpace(finalText)
finalText = strings.Trim(finalText, `'"#%{} `)
// 清理流尾残留的二进制标记,例如:a%߯B{}
tailJunkRe := regexp.MustCompile(`(?is)\s+[a-z]?%[^\s]{0,12}B\{\}\s*$`)
finalText = tailJunkRe.ReplaceAllString(finalText, "")
finalText = strings.TrimSpace(finalText)
if finalText != "" && !strings.Contains(strings.Join(parts, "\n"), finalText) {
parts = append(parts, finalText)
}
if len(parts) > 0 {
cleaned = strings.Join(parts, "\n\n")
} else {
cleaned = finalText
}
if maxBytes > 0 && len(cleaned) > maxBytes {
cleaned = cleaned[:maxBytes]
for len(cleaned) > 0 && !utf8.ValidString(cleaned) {
cleaned = cleaned[:len(cleaned)-1]
}
cleaned += "…(已截断)"
}
return strings.TrimSpace(cleaned)
}
func cursorExtractReadableCursorError(text string) string {
if text == "" {
return ""
}
unescaped := strings.ReplaceAll(text, `\n`, "\n")
unescaped = strings.ReplaceAll(unescaped, `\"`, `"`)
unescaped = strings.ReplaceAll(unescaped, `\/`, `/`)
if !(strings.Contains(strings.ToLower(unescaped), "error") ||
strings.Contains(strings.ToLower(unescaped), "unauthenticated") ||
strings.Contains(strings.ToLower(unescaped), "unauthorized") ||
strings.Contains(strings.ToLower(unescaped), "suspicious activity")) {
return ""
}
messageRe := regexp.MustCompile(`(?is)"(?:message|detail)"\s*:\s*"([^"]+)"`)
matches := messageRe.FindAllStringSubmatch(unescaped, -1)
for _, match := range matches {
if len(match) < 2 {
continue
}
msg := strings.TrimSpace(match[1])
if msg == "" {
continue
}
msg = strings.ReplaceAll(msg, `\n`, "\n")
msg = strings.ReplaceAll(msg, `\"`, `"`)
msg = cursorJoinFragmentedText(msg)
lowerMsg := strings.ToLower(msg)
if strings.Contains(lowerMsg, "suspicious activity") ||
strings.Contains(lowerMsg, "blocked") ||
strings.Contains(lowerMsg, "unauthorized") ||
strings.Contains(lowerMsg, "unauthenticated") {
return msg
}
}
if strings.Contains(strings.ToLower(unescaped), "suspicious activity") {
return "Your request has been blocked as our system has detected suspicious activity from your account. For troubleshooting, please visit the Cursor Docs at https://cursor.com/docs/troubleshooting/common-issues#suspicious-activity-message."
}
return ""
}
func cursorJoinFragmentedText(text string) string {
lines := strings.Split(text, "\n")
parts := make([]string, 0, len(lines))
for _, line := range lines {
part := strings.TrimSpace(line)
if part == "" {
continue
}
parts = append(parts, part)
}
out := strings.Join(parts, " ")
// 标点前不保留空格。
punctRe := regexp.MustCompile(`\s+([.,!?;:)\]}"',。!?;:)】》])`)
out = punctRe.ReplaceAllString(out, "$1")
// 只修复很明确的“单词内部被切开”场景,避免把 How can / I help / with your 误拼成 Howcan / Ihelp / withyour。
singlePrefixRe := regexp.MustCompile(`\b([b-hj-zB-HJ-Z])\s+([a-z]{2,})\b`)
out = singlePrefixRe.ReplaceAllString(out, "$1$2")
commonSuffixRe := regexp.MustCompile(`\b([A-Za-z]{3,})\s+(ing|ed|er|ers|ly|s)\b`)
out = commonSuffixRe.ReplaceAllString(out, "$1$2")
spaceRe := regexp.MustCompile(`\s+`)
out = spaceRe.ReplaceAllString(out, " ")
return strings.TrimSpace(out)
}
// cursorServerOutputDetail 将服务器响应内容放入 detail,便于前端只展示 detail 时也能看到服务端输出。
func cursorServerOutputDetail(prefix string, decoded []byte) string {
serverOutput := cursorReadableServerOutput(decoded, 8000)
if serverOutput == "" {
return prefix
}
return prefix + ",服务器可读输出:\n" + serverOutput
}
// probeCursorHiAgent 探测 Cursor Token 可用性
func probeCursorHiAgent(authToken string) Result {
if strings.Contains(authToken, "::") {
if i := strings.LastIndex(authToken, "::"); i >= 0 {
@@ -664,7 +504,7 @@ func probeCursorHiAgent(authToken string) Result {
fullURL := cursorBackendURL + cursorAgentPath
req, err := http.NewRequest(http.MethodPost, fullURL, bytes.NewReader(body))
if err != nil {
r := cursorProbeResult(false, "请求失败: "+err.Error(), 0, body, nil, nil)
r := cursorProbeResult(false, err.Error(), 0, body, nil, nil)
return r
}
req.Header.Set("Authorization", "Bearer "+authToken)
@@ -692,36 +532,29 @@ func probeCursorHiAgent(authToken string) Result {
resp, err := cursorProbeHTTPClient.Do(req)
if err != nil {
return cursorProbeResult(false, "请求失败: "+err.Error(), 0, body, nil, nil)
return cursorProbeResult(false, "请求 Cursor Agent 失败: "+err.Error(), 0, body, nil, nil)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
raw, _ := io.ReadAll(io.LimitReader(resp.Body, cursorHiMaxRead))
decoded, _ := decodeCursorResponseBody(raw, resp.Header.Get("Content-Encoding"))
isQuotaExhausted, msg := classifyCursorRawStream(decoded)
if isQuotaExhausted {
return cursorProbeResult(false, msg, resp.StatusCode, body, raw, decoded)
decoded, decodeNote := decodeCursorResponseBody(raw, resp.Header.Get("Content-Encoding"))
blocked, reason := classifyCursorRawStream(decoded)
if blocked {
return cursorProbeResult(false, reason+""+decodeNote, resp.StatusCode, body, raw, decoded)
}
// 非 200 状态码且不是额度问题
return cursorProbeResult(false, fmt.Sprintf("HTTP %d - Token不可用", resp.StatusCode), resp.StatusCode, body, raw, decoded)
detail := fmt.Sprintf("HTTP %d(非 200);%s;说明与协议边界见 streamNote", resp.StatusCode, decodeNote)
return cursorProbeResult(false, detail, resp.StatusCode, body, raw, decoded)
}
var buf bytes.Buffer
_, _ = io.Copy(&buf, io.LimitReader(resp.Body, cursorHiMaxRead))
raw := buf.Bytes()
decoded, _ := decodeCursorResponseBody(raw, resp.Header.Get("Content-Encoding"))
isQuotaExhausted, msg := classifyCursorRawStream(decoded)
if isQuotaExhausted {
// Token 不可用(额度用完等)
return cursorProbeResult(false, msg, resp.StatusCode, body, raw, decoded)
decoded, decodeNote := decodeCursorResponseBody(raw, resp.Header.Get("Content-Encoding"))
blocked, reason := classifyCursorRawStream(decoded)
if blocked {
return cursorProbeResult(false, reason+""+decodeNote, resp.StatusCode, body, raw, decoded)
}
// Token 可用时也把服务器实际输出放到 Detail,避免前端只展示 Detail 时看不到 RawPreview。
if msg != "" {
return cursorProbeResult(true, cursorServerOutputDetail(msg, decoded), resp.StatusCode, body, raw, decoded)
}
return cursorProbeResult(true, cursorServerOutputDetail("Token可用", decoded), resp.StatusCode, body, raw, decoded)
detail := "HTTP 200;未命中内置英文关键词;" + decodeNote + ";二进制流含义与 ok 边界见 streamNote"
return cursorProbeResult(true, detail, resp.StatusCode, body, raw, decoded)
}
+9 -14
View File
@@ -1,9 +1,8 @@
// Package tokenprobe 使用号池内 Token 调用各厂商接口做可用性探测。
// Cursor 走 api2.cursor.sh 的 Connect + protobuf 二进制流(非 JSON 文本接口)。
package tokenprobe
import (
"bytes"
"crypto/tls"
"encoding/base64"
"encoding/json"
"fmt"
@@ -14,9 +13,13 @@ import (
"time"
)
var httpClient = &http.Client{Timeout: 25 * time.Second}
var httpClient = &http.Client{
Timeout: 12 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
},
}
// Result 探测结果(Cursor 会填充 ProbeMessage / Endpoint / BytesRead / RawPreview 等)
type Result struct {
OK bool `json:"ok"`
Detail string `json:"detail"`
@@ -30,7 +33,6 @@ type Result struct {
StreamNote string `json:"streamNote,omitempty"`
}
// ProbeOfficial 按号池模块探测 Tokencursor / windsurf / krio
func ProbeOfficial(module, rawToken string) Result {
tok := normalizeBearerToken(strings.TrimSpace(rawToken))
if tok == "" {
@@ -38,8 +40,7 @@ func ProbeOfficial(module, rawToken string) Result {
}
switch module {
case "cursor":
// 直接使用 cursor_hi.go 中已有的完整探测函数
return probeCursorHiAgent(tok)
return probeCursor(tok)
case "windsurf":
return probeWindsurf(tok)
case "krio":
@@ -57,12 +58,10 @@ func normalizeBearerToken(s string) string {
return s
}
// probeCursor Cursor Token 探测(直接使用 cursor_hi.go 的实现)
func probeCursor(token string) Result {
return probeCursorHiAgent(token)
}
// probeWindsurf WindSurf 探测
func probeWindsurf(apiKey string) Result {
payload := map[string]interface{}{
"metadata": map[string]string{
@@ -119,13 +118,12 @@ func probeWindsurf(apiKey string) Result {
}
}
// probeKiro Kiro 探测
func probeKiro(accessToken string) Result {
arn := findProfileArnInJWT(accessToken)
if arn == "" {
return Result{
OK: false,
Detail: "无法从 Token 中解析 profileArnKiro 暂无法自动探测(需完整登录 JWT",
Detail: "无法从 Token 中解析 profileArnKiro 暂无法自动探测",
}
}
@@ -163,7 +161,6 @@ func probeKiro(accessToken string) Result {
}
}
// decodeJWTPayloadMap 解析 JWT payload
func decodeJWTPayloadMap(raw string) (map[string]interface{}, error) {
tok := normalizeBearerToken(strings.TrimSpace(raw))
parts := strings.Split(tok, ".")
@@ -181,7 +178,6 @@ func decodeJWTPayloadMap(raw string) (map[string]interface{}, error) {
return m, nil
}
// findProfileArnInJWT 从 JWT 中查找 profileArn
func findProfileArnInJWT(raw string) string {
m, err := decodeJWTPayloadMap(raw)
if err != nil {
@@ -190,7 +186,6 @@ func findProfileArnInJWT(raw string) string {
return findProfileArnValue(m)
}
// findProfileArnValue 递归查找 profileArn
func findProfileArnValue(v interface{}) string {
switch x := v.(type) {
case map[string]interface{}:
+6
View File
@@ -11,6 +11,12 @@ func Register() {
// 客户端检查更新(无需登录)
beego.Router("/api/softwareupgrade/check", &controllers.ApiSoftwareUpgradeController{}, "get:Check")
// 登录器上报 Cursor 设备信息(无需登录)
beego.Router("/api/cursor/equipment/report", &controllers.ApiCursorEquipmentController{}, "post:Report")
// 登录器使用激活码激活/续期 Cursor 设备(无需登录)
beego.Router("/api/cursor/equipment/activateByCode", &controllers.ApiCursorEquipmentController{}, "post:ActivateByCode")
// 对外提卡接口(无需登录)
// GET /api/getcard?type=xianyu&module=cursor&data_type=tk
beego.Router("/api/getcard", &controllers.ApiGetCardController{}, "get:GetCard")
+52 -17
View File
@@ -14,31 +14,20 @@ func Register() {
// RegisterAuthRoutes 注册 backend 认证相关路由。
func RegisterAuthRoutes() {
// backend 登录相关(统一走 /backend/*
// 登录、注册与找回密码相关
beego.Router("/backend/login", &controllers.BackendAuthController{}, "post:LoginBackend")
beego.Router("/backend/sendLoginCode", &controllers.BackendAuthController{}, "post:SendLoginCode")
beego.Router("/backend/loginBySms", &controllers.BackendAuthController{}, "post:LoginBySms")
beego.Router("/backend/logout", &controllers.BackendAuthController{}, "post:Logout")
// 极验与登录验证配置
beego.Router("/backend/login/getGeetest3Infos", &controllers.BackendAuthController{}, "get:GetGeetest3Infos")
beego.Router("/backend/login/getGeetest4Infos", &controllers.BackendAuthController{}, "get:GetGeetest4Infos")
beego.Router("/backend/login/getOpenVerify", &controllers.BackendAuthController{}, "get:GetOpenVerify")
// 登录相关接口
beego.Router("/backend/login/getGeetest3Infos", &controllers.BackendAuthController{}, "get:GetGeetest3Infos")
beego.Router("/backend/login/getGeetest4Infos", &controllers.BackendAuthController{}, "get:GetGeetest4Infos")
beego.Router("/backend/login/getOpenVerify", &controllers.BackendAuthController{}, "get:GetOpenVerify")
// 注册与找回密码
beego.Router("/backend/register", &controllers.BackendAuthController{}, "post:Register")
beego.Router("/backend/sendRegisterCode", &controllers.BackendAuthController{}, "post:SendRegisterCode")
beego.Router("/backend/resetPassword", &controllers.BackendAuthController{}, "post:ResetPassword")
beego.Router("/backend/sendResetCode", &controllers.BackendAuthController{}, "post:SendResetCode")
// 租户站点设
beego.Router("/backend/normalInfos", &controllers.BackendSiteSettingsController{}, "get:GetNormalInfos")
beego.Router("/backend/saveNormalInfos", &controllers.BackendSiteSettingsController{}, "post:SaveNormalInfos")
// 极验与登录验证配
beego.Router("/backend/login/getGeetest3Infos", &controllers.BackendAuthController{}, "get:GetGeetest3Infos")
beego.Router("/backend/login/getGeetest4Infos", &controllers.BackendAuthController{}, "get:GetGeetest4Infos")
beego.Router("/backend/login/getOpenVerify", &controllers.BackendAuthController{}, "get:GetOpenVerify")
// 菜单接口
beego.Router("/backend/menu/:id", &controllers.BackendMenuController{}, "get:GetBackendMenu")
@@ -50,6 +39,18 @@ func RegisterAuthRoutes() {
beego.Router("/backend/operationLogs/:id", &controllers.BackendOperationLogController{}, "get:Detail;delete:Delete")
beego.Router("/backend/operationLogs/batchDelete", &controllers.BackendOperationLogController{}, "post:BatchDelete")
// 租户站点设置
beego.Router("/backend/normalInfos", &controllers.BackendSiteSettingsController{}, "get:GetNormalInfos")
beego.Router("/backend/saveNormalInfos", &controllers.BackendSiteSettingsController{}, "post:SaveNormalInfos")
beego.Router("/backend/legalInfos", &controllers.BackendSiteSettingsController{}, "get:GetLegalInfos")
beego.Router("/backend/saveLegalInfos", &controllers.BackendSiteSettingsController{}, "post:SaveLegalInfos")
beego.Router("/backend/companyInfos", &controllers.BackendSiteSettingsController{}, "get:GetCompanyInfos")
beego.Router("/backend/saveCompanyInfos", &controllers.BackendSiteSettingsController{}, "post:SaveCompanyInfos")
beego.Router("/backend/companySeo", &controllers.BackendSiteSettingsController{}, "get:GetCompanySeo")
beego.Router("/backend/saveCompanySeo", &controllers.BackendSiteSettingsController{}, "post:SaveCompanySeo")
beego.Router("/backend/loginVerifyInfos", &controllers.BackendLoginVerifyController{}, "get:GetLoginVerifyInfos")
beego.Router("/backend/saveloginVerifyInfos", &controllers.BackendLoginVerifyController{}, "post:SaveLoginVerifyInfos")
// 文件管理(yz_system_files / yz_system_files_category
beego.Router("/backend/usercate", &controllers.BackendFileController{}, "get:GetUserCate")
beego.Router("/backend/allfiles", &controllers.BackendFileController{}, "get:GetAllFiles")
@@ -101,6 +102,40 @@ func RegisterAuthRoutes() {
beego.Router("/backend/erp/editPosition/:id", &controllers.BackendErpController{}, "post:EditPosition")
beego.Router("/backend/erp/deletePosition/:id", &controllers.BackendErpController{}, "delete:DeletePosition")
// 文章管理相关接口
// 文章管理
beego.Router("/backend/articlesList", &controllers.BackendArticleController{}, "get:List")
beego.Router("/backend/allarticles", &controllers.BackendArticleController{}, "get:ListAll")
beego.Router("/backend/articles/:id", &controllers.BackendArticleController{}, "get:Detail")
beego.Router("/backend/createarticle", &controllers.BackendArticleController{}, "post:Create")
beego.Router("/backend/editarticle/:id", &controllers.BackendArticleController{}, "post:Update")
beego.Router("/backend/deletearticle/:id", &controllers.BackendArticleController{}, "delete:Delete")
beego.Router("/backend/publisharticle/:id", &controllers.BackendArticleController{}, "post:Publish")
beego.Router("/backend/unPublisharticle/:id", &controllers.BackendArticleController{}, "post:Unpublish")
beego.Router("/backend/articleRecommend/:id", &controllers.BackendArticleController{}, "post:Recommend")
beego.Router("/backend/unArticleRecommend/:id", &controllers.BackendArticleController{}, "post:Unrecommend")
beego.Router("/backend/articleTop/:id", &controllers.BackendArticleController{}, "post:Top")
beego.Router("/backend/unArticleTop/:id", &controllers.BackendArticleController{}, "post:Untop")
beego.Router("/backend/categories", &controllers.BackendArticleCategoryController{}, "get:List")
beego.Router("/backend/allcategories", &controllers.BackendArticleCategoryController{}, "get:ListAll")
beego.Router("/backend/categories/:id", &controllers.BackendArticleCategoryController{}, "get:Detail;delete:Delete")
beego.Router("/backend/createCategory", &controllers.BackendArticleCategoryController{}, "post:Create")
beego.Router("/backend/editCategory/:id", &controllers.BackendArticleCategoryController{}, "post:Update")
beego.Router("/backend/categories/:id/status", &controllers.BackendArticleCategoryController{}, "patch:UpdateStatus")
// 域名管理(主域名池 / 租户域名)
beego.Router("/backend/domain/pool/index", &controllers.BackendDomainPoolController{}, "get:Index")
beego.Router("/backend/domain/pool/getEnabledDomains", &controllers.BackendDomainPoolController{}, "get:GetEnabledDomains")
beego.Router("/backend/domain/pool/create", &controllers.BackendDomainPoolController{}, "post:Create")
beego.Router("/backend/domain/pool/update", &controllers.BackendDomainPoolController{}, "post:Update")
beego.Router("/backend/domain/pool/delete/:id", &controllers.BackendDomainPoolController{}, "delete:Delete")
beego.Router("/backend/domain/pool/toggleStatus", &controllers.BackendDomainPoolController{}, "post:ToggleStatus")
beego.Router("/backend/domain/tenant/index", &controllers.BackendTenantDomainController{}, "get:Index")
beego.Router("/backend/domain/tenant/myDomains", &controllers.BackendTenantDomainController{}, "get:MyDomains")
beego.Router("/backend/domain/tenant/apply", &controllers.BackendTenantDomainController{}, "post:Apply")
beego.Router("/backend/domain/tenant/audit", &controllers.BackendTenantDomainController{}, "post:Audit")
beego.Router("/backend/domain/tenant/toggleStatus", &controllers.BackendTenantDomainController{}, "post:ToggleStatus")
beego.Router("/backend/domain/tenant/delete/:id", &controllers.BackendTenantDomainController{}, "delete:Delete")
}
+22
View File
@@ -162,6 +162,27 @@ func Register() {
beego.Router("/platform/home/accountPoolDailyExtract", &controllers.PlatformHomeController{}, "get:AccountPoolDailyExtract")
beego.Router("/platform/home/accountPoolInventoryTotals", &controllers.PlatformHomeController{}, "get:AccountPoolInventoryTotals")
// Cursor 设备管理(yz_platform_cursor_equipment
beego.Router("/platform/cursor/equipment/list", &controllers.PlatformCursorEquipmentController{}, "get:List")
beego.Router("/platform/cursor/equipment/detail/:id", &controllers.PlatformCursorEquipmentController{}, "get:Detail")
beego.Router("/platform/cursor/equipment/add", &controllers.PlatformCursorEquipmentController{}, "post:Add")
beego.Router("/platform/cursor/equipment/update", &controllers.PlatformCursorEquipmentController{}, "post:Update")
beego.Router("/platform/cursor/equipment/delete/:id", &controllers.PlatformCursorEquipmentController{}, "post:Delete")
beego.Router("/platform/cursor/equipment/activate", &controllers.PlatformCursorEquipmentController{}, "post:Activate")
beego.Router("/platform/cursor/equipment/activationRecords", &controllers.PlatformCursorEquipmentController{}, "get:ActivationRecords")
beego.Router("/platform/cursor/equipment/extractRecords", &controllers.PlatformCursorEquipmentController{}, "get:ExtractRecords")
// Cursor 激活码管理(yz_platform_cursor_activation_code
beego.Router("/platform/cursor/activationcode/list", &controllers.PlatformCursorActivationCodeController{}, "get:List")
beego.Router("/platform/cursor/activationcode/detail/:id", &controllers.PlatformCursorActivationCodeController{}, "get:Detail")
beego.Router("/platform/cursor/activationcode/add", &controllers.PlatformCursorActivationCodeController{}, "post:Add")
beego.Router("/platform/cursor/activationcode/update", &controllers.PlatformCursorActivationCodeController{}, "post:Update")
beego.Router("/platform/cursor/activationcode/delete/:id", &controllers.PlatformCursorActivationCodeController{}, "post:Delete")
beego.Router("/platform/cursor/activationcode/generate", &controllers.PlatformCursorActivationCodeController{}, "post:Generate")
beego.Router("/platform/cursor/activationcode/enable/:id", &controllers.PlatformCursorActivationCodeController{}, "post:Enable")
beego.Router("/platform/cursor/activationcode/disable/:id", &controllers.PlatformCursorActivationCodeController{}, "post:Disable")
beego.Router("/platform/cursor/activationcode/export", &controllers.PlatformCursorActivationCodeController{}, "get:Export")
// 账号池管理(cursor/windsurf/krio
beego.Router("/platform/accountPool/cursor/list", &controllers.PlatformAccountPoolCursorController{}, "get:List")
beego.Router("/platform/accountPool/cursor/add", &controllers.PlatformAccountPoolCursorController{}, "post:Add")
@@ -170,6 +191,7 @@ func Register() {
beego.Router("/platform/accountPool/cursor/extract", &controllers.PlatformAccountPoolCursorController{}, "post:Extract")
beego.Router("/platform/accountPool/cursor/updateRemark", &controllers.PlatformAccountPoolCursorController{}, "post:UpdateRemark")
beego.Router("/platform/accountPool/cursor/setUnavailable", &controllers.PlatformAccountPoolCursorController{}, "post:SetUnavailable")
beego.Router("/platform/accountPool/cursor/updateUsable", &controllers.PlatformAccountPoolCursorController{}, "post:UpdateUsable")
beego.Router("/platform/accountPool/cursor/updatePlatform", &controllers.PlatformAccountPoolCursorController{}, "post:UpdatePlatform")
beego.Router("/platform/accountPool/cursor/unextract", &controllers.PlatformAccountPoolCursorController{}, "post:Unextract")
beego.Router("/platform/accountPool/cursor/replenish", &controllers.PlatformAccountPoolCursorController{}, "post:Replenish")