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, }) }