跳转至

工厂控制台 API

工厂控制台 API 负责设备注册、NFC 凭证生成与下载、审批工作流及审计记录。挂载在 /internal/factory/*,通过独立的 factory-api ingress 暴露,与用户 API 完全隔离:用户 JWT 无法访问此 API,工厂凭证也无法访问任何用户接口。

认证

工厂 API 使用静态 Bearer Token(由 FACTORY_PRINCIPALS 环境变量配置),而非用户 JWT。每个 Principal 持有唯一 ID、角色及批次/SKU 访问范围。

Authorization: Bearer <factory-token>

角色

角色 说明
operator 注册设备、生成凭证、首次下载 .nfc.json 文件
supervisor 拥有 operator 全部权限,另可批准重复下载申请、吊销并重发凭证、创建批次、批量导入设备、批量生成文件
auditor 只读:可查看所有设备、批次、统计及审计日志,不可写入

范围限制

每个 Principal 的 batchesskus 字段限定其可访问的批次和 SKU。["*"] 表示不限制;空数组 [] 表示无权访问任何资源(非通配符)。


批次(Batches)

批次对应一张生产订单(PO)。设备必须先关联到批次才能注册。

GET /internal/factory/batches

列出当前 Principal 可访问的所有批次。

查询参数:

参数 类型 描述
status string 按状态筛选(openclosed

响应: 200 OK

{
  "items": [
    {
      "id": "01912345-...",
      "orderNo": "PO-2026-001",
      "sku": "INK-800-BW-001",
      "hwRevision": "v1.2",
      "plannedQuantity": 500,
      "factoryId": "factory-shenzhen",
      "status": "open",
      "createdAt": "2026-07-01T00:00:00Z",
      "createdBy": "admin"
    }
  ],
  "total": 1,
  "limit": 1,
  "offset": 0
}

POST /internal/factory/batches

需要 Supervisor 角色

创建新批次。

请求体:

{
  "orderNo": "PO-2026-001",
  "sku": "INK-800-BW-001",
  "hwRevision": "v1.2",
  "plannedQuantity": 500,
  "factoryId": "factory-shenzhen"
}
字段 类型 必填 描述
orderNo string 采购订单号,全局唯一
sku string 产品 SKU
hwRevision string 硬件版本号
plannedQuantity integer 计划数量
factoryId string 工厂标识符

响应: 201 Created — 返回创建的批次对象


GET /internal/factory/batches/{batchId}

获取单个批次详情。

响应: 200 OK — 返回批次对象


GET /internal/factory/batches/{batchId}/stats

获取批次的统计数据(注册数、生成数、下载数等)。

响应: 200 OK

{
  "batch": { ... },
  "counts": {
    "registered": 50,
    "credentialGenerated": 45,
    "downloaded": 40,
    "bound": 30,
    "plannedQuantity": 500
  }
}

设备(Devices)

GET /internal/factory/devices

列出设备,支持分页和过滤。

查询参数:

参数 类型 描述
batchId UUID 按批次筛选
lifecycleStatus string 按生命周期状态筛选(registeredprovisioned
nfcStatus string 按 NFC 状态筛选(pendinggenerateddownloadedrevoked
bound boolean 按是否已绑定筛选
q string 按 serial 或 hwId 关键词搜索
limit integer 每页数量(默认 50,最大 200)
offset integer 分页偏移

响应: 200 OK — 返回 DeviceView 列表(见下方模型)


POST /internal/factory/devices

需要写权限(operator 或 supervisor)

注册单台设备。hwIdthingName 由后端分配,调用方不可指定。

请求体:

{
  "batchId": "01912345-...",
  "serial": "SN2026001",
  "sku": "INK-800-BW-001",
  "hwRevision": "v1.2"
}
字段 类型 必填 描述
batchId UUID 所属批次 ID
serial string 工厂序列号;batchId + serial 唯一
sku string 覆盖批次 SKU
hwRevision string 覆盖批次硬件版本

请求头(可选):

请求头 描述
Idempotency-Key 同一 batchId + serial 重复提交时安全去重

响应: 201 Created(新建)或 200 OK(幂等命中)

{
  "id": "01912345-...",
  "hwId": "a1b2c3d4e5f60708090a0b0c0d0e0f10",
  "thingName": "inklet-a1b2c3d4e5f60708",
  "serial": "SN2026001",
  "batchId": "01912345-...",
  "lifecycleStatus": "registered",
  "nfcStatus": "pending",
  "credentialVersion": 0,
  "bound": false,
  "createdAt": "2026-07-01T10:00:00Z"
}

POST /internal/factory/devices:batch-import

需要 Supervisor 角色

通过 CSV 批量注册设备。CSV 文件需包含 serial(必填)、sku(选填)、hwRevision(选填)列,batchId 通过查询参数传入。

查询参数:

参数 类型 描述
batchId UUID 所属批次 ID(必填)

请求体: multipart/form-data,字段名 file,CSV 文件(最大 4 MiB)

响应: 200 OK

{
  "ok": 48,
  "idempotent": 2,
  "errors": [
    { "row": 3, "serial": "SN2026003", "error": "duplicate serial" }
  ]
}

错误行不阻塞合法行;部分成功是正常结果。


GET /internal/factory/devices/{deviceId}

获取单台设备详情,包含当前凭证视图(proof 已遮蔽)。

响应: 200 OK — 返回 DeviceView 对象


POST /internal/factory/devices/{deviceId}/nfc-files

需要写权限

为设备生成 NFC 凭证。每台设备在同一 credentialVersion 下只能有一份有效凭证;重复调用返回 409,不会重新生成。如需重新获取文件,通过 download 接口下载(可能需要申请审批)。

响应: 201 Created — 返回 CredentialView 对象


POST /internal/factory/devices:nfc-files-batch-generate

需要 Supervisor 角色

批量为指定批次内所有 nfcStatus=pending 的设备生成凭证。

请求体:

{ "batchId": "01912345-..." }

响应: 200 OK

{ "generated": 48, "skipped": 2 }

POST /internal/factory/devices/{deviceId}/nfc-credentials:revoke-and-reissue

需要 Supervisor 角色

吊销当前凭证并生成新凭证(credentialVersion + 1)。旧凭证立即失效,旧 .nfc.json 文件无法再下载。已用旧凭证绑定的 App 在下次绑定时将被拒绝。

请求体:

{ "reason": "tag damaged — rewrite required" }

响应: 200 OK — 返回新 CredentialView 对象


NFC 文件(NFC Files)

.nfc.json 文件是交给产线的烧录文件,包含设备身份信息及 NFC URI 载荷。下载是有权限控制和审计的独立动作。

.nfc.json 文件结构

{
  "schemaVersion": 1,
  "type": "inklet-nfc-bind",
  "serial": "SN2026001",
  "hwId": "a1b2c3d4e5f60708090a0b0c0d0e0f10",
  "thingName": "inklet-a1b2c3d4e5f60708",
  "batchId": "01912345-...",
  "sku": "INK-800-BW-001",
  "hwRevision": "v1.2",
  "keyId": "k1",
  "credentialVersion": 1,
  "nfcPayload": "inklet://bind?v=2&kid=k1&hw=a1b2c3d4...&cv=1&p=AAAA...",
  "payloadSha256": "abcdef0123456789...",
  "issuedAt": "2026-07-01T10:00:00Z",
  "manifestKeyId": "manifest-key-1",
  "manifestSignature": "base64-ed25519-sig"
}
字段 描述
nfcPayload 写入 NFC 标签的完整 URI(产线直接使用此字段)
payloadSha256 payload 的 SHA-256,用于回读校验
manifestSignature 文件整体的 Ed25519 签名(用于产线验证文件完整性)

凭证敏感性

.nfc.json 文件包含完整的 NFC URI(含 proof),是有效的绑定凭证。请妥善保管,遵循最小分发原则。文件泄露只影响对应单台设备,通过吊销并重发可使其立即失效。


GET /internal/factory/nfc-files/{credentialId}/download

需要写权限 · 受下载限流保护

下载单台设备的 .nfc.json 文件。

  • 首次下载:直接返回文件。
  • 重复下载:operator 需先提交下载申请(POST .../download-requests),由 supervisor 审批后方可下载;supervisor 自身可直接重复下载。

响应: 200 OKContent-Type: application/json,文件名 {serial}.nfc.json

错误:

状态码 原因
403 重复下载申请未审批,或无权限
404 凭证不存在或已吊销

POST /internal/factory/nfc-files/{credentialId}/download-requests

需要写权限

提交重复下载申请,等待 supervisor 审批。每个凭证每次只能有一个待审批申请。

响应: 201 Created

{
  "id": "request-uuid",
  "credentialId": "cred-uuid",
  "requestedBy": "operator-1",
  "status": "pending",
  "createdAt": "2026-07-01T11:00:00Z"
}

POST /internal/factory/nfc-files:download-zip

需要 Supervisor 角色 · 受下载限流保护

批量下载指定批次所有设备的 .nfc.json 文件,返回 ZIP 压缩包。

请求体:

{ "batchId": "01912345-..." }

响应: 200 OKContent-Type: application/zip


下载审批(Download Requests)

GET /internal/factory/download-requests

列出当前 Principal 可见的下载审批申请。

查询参数:

参数 类型 描述
status string pendingapprovedrejected
batchId UUID 按批次筛选

响应: 200 OK — 申请列表


POST /internal/factory/download-requests/{requestId}:approve

需要 Supervisor 角色

审批通过下载申请。之后 operator 可在限定时间内下载一次。

响应: 200 OK


POST /internal/factory/download-requests/{requestId}:reject

需要 Supervisor 角色

拒绝下载申请,需提供原因。

请求体:

{ "reason": "not authorized for this batch" }

响应: 200 OK


统计与审计

GET /internal/factory/stats

获取全局或批次级别的统计数据。

查询参数:

参数 类型 描述
batchId UUID 指定批次(不传则返回全局)

响应: 200 OK

{
  "registered": 1000,
  "credentialGenerated": 980,
  "downloaded": 950,
  "bound": 800
}

GET /internal/factory/audit-logs

查询审计日志。所有写操作(注册、生成、下载、吊销、审批)均自动记录。

查询参数:

参数 类型 描述
batchId UUID 按批次筛选
deviceId UUID 按设备筛选
action string 按操作类型筛选
from RFC3339 开始时间
to RFC3339 结束时间
limit integer 每页数量(默认 50)
offset integer 分页偏移

响应: 200 OK

{
  "items": [
    {
      "id": "log-uuid",
      "action": "credential_download",
      "result": "ok",
      "principalId": "operator-1",
      "principalName": "Line A Station 3",
      "batchId": "01912345-...",
      "deviceId": "01912345-...",
      "detail": "first download",
      "createdAt": "2026-07-01T10:05:00Z"
    }
  ],
  "total": 150,
  "limit": 50,
  "offset": 0
}

GET /internal/factory/manifest-keys

列出当前有效的 manifest 签名公钥(供产线验证 .nfc.json 完整性)。

响应: 200 OK

[
  {
    "id": "manifest-key-1",
    "publicKey": "base64-ed25519-public-key",
    "algorithm": "ed25519",
    "createdAt": "2026-07-01T00:00:00Z"
  }
]

GET /internal/factory/whoami

返回当前认证 Principal 的身份信息。

响应: 200 OK

{
  "id": "operator-1",
  "name": "Line A Station 3",
  "role": "operator",
  "stationId": "station-a3",
  "factoryId": "factory-shenzhen",
  "batches": ["*"],
  "skus": ["INK-800-BW-001"]
}

DeviceView 模型

{
  "id": "01912345-...",
  "serial": "SN2026001",
  "hwId": "a1b2c3d4e5f60708090a0b0c0d0e0f10",
  "thingName": "inklet-a1b2c3d4e5f60708",
  "batchId": "01912345-...",
  "orderNo": "PO-2026-001",
  "sku": "INK-800-BW-001",
  "hwRevision": "v1.2",
  "lifecycleStatus": "registered",
  "nfcStatus": "downloaded",
  "credentialVersion": 1,
  "nfcKeyId": "k1",
  "bound": false,
  "online": false,
  "createdAt": "2026-07-01T10:00:00Z",
  "credential": {
    "id": "cred-uuid",
    "keyId": "k1",
    "credentialVersion": 1,
    "status": "downloaded",
    "payloadSha256": "abcdef...",
    "payloadPreview": "inklet://bind?v=2&kid=k1&hw=a1b2...&cv=1&p=****",
    "issuedBy": "operator-1",
    "issuedAt": "2026-07-01T10:00:00Z",
    "downloadCount": 1,
    "requiresApproval": true
  }
}

NFC 状态说明:

nfcStatus 描述
pending 未生成凭证
generated 已生成,尚未下载
downloaded 已至少下载一次
revoked 已吊销(等待重发)

注意: payloadPreview 中的 proof 永远以 **** 遮蔽;完整 proof 只通过 download 接口获取。

错误响应

所有错误响应统一格式:

{ "error": "description of what went wrong" }
状态码 描述
400 请求格式错误或参数无效
401 缺少或无效的 Bearer Token
403 角色或范围不足
404 资源不存在
409 冲突(如同一 serial 重复注册,或凭证已生成无法重新生成)
429 触发限流