跳转至

错误码与状态码

HTTP 状态码

标准状态码

状态码 说明 使用场景
200 OK 请求成功
201 Created 资源创建成功
400 Bad Request 请求参数错误
401 Unauthorized 未认证或token无效
403 Forbidden 无权限访问
404 Not Found 资源不存在
409 Conflict 资源冲突(如重复绑定)
422 Unprocessable Entity 请求格式正确但语义错误
429 Too Many Requests 请求频率超限
500 Internal Server Error 服务器内部错误

统一响应格式

{
  "code": 200,
  "message": "成功",
  "data": {}
}

错误响应格式

{
  "code": 400,
  "message": "请求参数错误",
  "data": {
    "field": "username",
    "detail": "用户名长度不能少于3个字符"
  }
}

业务错误码

认证相关 (1xxx)

错误码 说明 详细信息
1001 用户名或密码错误 请检查用户名和密码
1002 账号已被禁用 联系管理员
1003 账号未审核 等待管理员审核
1004 账号已被拒绝 查看拒绝原因
1005 Token已过期 请重新登录
1006 Token无效 请重新登录
1007 Refresh Token无效 请重新登录
1008 验证码错误 请重新输入
1009 验证码已过期 请重新获取
1010 邮箱已被注册 使用其他邮箱
1011 用户名已被占用 使用其他用户名
1012 呼号已被注册 联系管理员
1013 SSO认证失败 重试或联系管理员
1014 SSO账号已绑定 解绑后重试

设备相关 (2xxx)

错误码 说明 详细信息
2001 设备不存在 检查设备ID
2002 设备已绑定 不能重复绑定
2003 动态码无效 请重新获取
2004 动态码已过期 请重新获取
2005 SSID不可用 选择其他SSID
2006 SSID已被占用 选择其他SSID
2007 设备型号不支持 检查设备型号
2008 MAC地址无效 检查MAC格式
2009 设备密码错误 检查设备密码
2010 设备已被禁用 联系管理员
2011 配置同步失败 设备可能离线
2012 固件版本无效 使用semver格式

群组相关 (3xxx)

错误码 说明 详细信息
3001 群组不存在 检查群组ID
3002 群组密码错误 输入正确密码
3003 已在群组中 不能重复加入
3004 不在群组中 先加入群组
3005 无权操作群组 需要群主或管理员权限
3006 群组已满 联系管理员
3007 不能加入虚拟互联组 仅管理员可操作

用户相关 (4xxx)

错误码 说明 详细信息
4001 用户不存在 检查用户ID
4002 无权修改用户 需要管理员或本人权限
4003 密码强度不足 至少8位,包含字母和数字
4004 旧密码错误 输入正确旧密码
4005 操作证已提交 等待审核
4006 文件上传失败 检查文件大小和格式

通信相关 (5xxx)

错误码 说明 详细信息
5001 通信记录不存在 检查记录ID
5002 音频文件不存在 可能已删除
5003 通联日志不存在 检查日志ID
5004 录制未启用 联系管理员开启

WebSocket 相关 (6xxx)

错误码 说明 详细信息
6001 认证失败 请重新登录
6002 Token过期 刷新token
6003 同账号冲突 关闭其他页面
6004 群组不存在 切换群组
6005 服务器错误 重连

UDP 协议相关 (7xxx)

错误码 说明 详细信息
7001 认证失败 检查用户名和密码
7002 用户不存在 检查用户名
7003 用户已禁用 联系管理员
7004 用户未审核 等待审核
7005 无效设备型号 检查DevModel
7006 同平台在线冲突 等待旧连接断开
7007 IP被封禁 等待解封

JWT 认证响应状态码

Type=1 (JWTAuth) DATA[0] 状态码

说明
0 认证成功
1 Token无效或过期
2 用户不存在
3 用户已禁用
4 用户未审核
5 无效设备型号(非101-104)
6 同平台已有在线幽灵设备

响应格式

DATA[0] = 状态码 (0=成功, 非0=失败)
DATA[1:] = 错误消息 (UTF-8, 失败时)

心跳响应状态码

Type=2 (Heartbeat) 响应

普通设备心跳认证成功后,服务器会在响应中填充 CallSign。

成功响应:

Header:
  CallSign: "BG1AAA" (服务器填充)

DATA: 原样返回设备发送的DATA

失败响应: 服务器直接丢弃心跳包,不返回响应。

设备认证失败处理

阶梯封禁机制

失败次数    封禁时长    封禁Key
1-3        无          -
4          10秒        IP + Username
5          30秒        IP + Username
6          60秒        IP + Username
7+         300秒       IP + Username

封禁响应

被封禁的设备发送心跳包时,服务器直接丢弃,不返回响应。

设备可通过以下方式判断是否被封禁: 1. 发送心跳后长时间无响应 2. 尝试其他用户名(如果封禁是基于IP+Username)

HTTP 限流响应

429 Too Many Requests

{
  "code": 429,
  "message": "请求过于频繁,请稍后重试",
  "data": {
    "retry_after": 9
  }
}

限流规则

接口 限流策略
/api/auth/login IP每秒5次
/api/auth/send-code IP每分钟2次
/api/auth/register IP每小时5次
/api/device/pre-check IP每秒1次,MAC每分钟5次
/api/device/request-code IP每10秒1次,MAC每分钟1次
/api/device/confirm-bind MAC每5秒1次
/api/device/bind 用户每分钟5次
/api/device/submit-config 用户每分钟10次
/api/public/relays IP每分钟10次

业务状态码

用户审核状态

approval_status 说明
0 待审核
1 已通过
2 已拒绝

设备在线状态

is_online 说明
0 离线
1 在线

通信记录状态

status 说明
0 录制中
1 待上传
2 已完成
3 上传失败

操作证审核状态

status 说明
0 待审核
1 已通过
2 已拒绝

错误处理最佳实践

客户端错误处理

async function apiRequest(url, options) {
  try {
    const response = await fetch(url, options);
    const data = await response.json();

    if (response.ok) {
      return data;
    }

    switch (response.status) {
      case 401:
        // Token过期,尝试刷新
        const refreshed = await refreshToken();
        if (refreshed) {
          // 重试原请求
          return apiRequest(url, options);
        }
        // 刷新失败,跳转登录
        window.location.href = '/login';
        break;

      case 403:
        showToast('无权限访问', 'error');
        break;

      case 404:
        showToast('资源不存在', 'error');
        break;

      case 429:
        const retryAfter = data.data?.retry_after || 5;
        showToast(`请求过于频繁,${retryAfter}秒后重试`, 'warning');
        await sleep(retryAfter * 1000);
        return apiRequest(url, options);

      default:
        showToast(data.message || '请求失败', 'error');
    }

    throw new Error(data.message);
  } catch (error) {
    if (error.name === 'TypeError') {
      showToast('网络错误,请检查连接', 'error');
    }
    throw error;
  }
}

WebSocket 错误处理

ws.onerror = (error) => {
  console.error('WebSocket error:', error);
  showToast('连接错误', 'error');
};

ws.onclose = (event) => {
  switch (event.code) {
    case 4001:
      showToast('认证失败', 'error');
      break;
    case 4003:
      showToast('同账号冲突,请关闭其他页面', 'warning');
      break;
    case 1006:
      showToast('连接异常断开,正在重连...', 'info');
      reconnect();
      break;
  }
};