go-platform/controllers/api_cursor_equipment.go
2026-06-16 00:39:20 +08:00

504 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

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

package controllers
import (
"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,
})
}