HTTP 约定

外部 HTTP 路径由部署的 servlet context 与 Controller 映射共同组成(默认配置 context 为 /main-service)。本手册 13 个端点的 Controller 映射前缀:/main-api/device/data-acquisition-device/main-api/device/device-share(第一代 ForPersonalSpace)、/main-api/v1/retail/device/data-acquisition-device/system-api/v1/retail/user-center/user(第二代 /v1/retail)。base URL、方法与参数位置以清单层与 Swagger 为准(device · 采集设备device · 设备分享user-center · user 用户),不能按路径名称推断。

凭据携带

服务端凭据是 AES 加密 JWT,客户端以 Authorization: Bearer <AES 加密的 JWT> 携带。处理链先按配置的多把密钥(security.jwt.enc-keys)逐把尝试 AES 解密,再以签名密钥(security.jwt.signing-key)验签,两步任一失败都按未认证处理。要点:

  • 请求头名可配(security.jwt.token-header-name,默认 Authorization);请求头缺失时,服务端兼容从同名请求参数读取凭据。
  • Bearer 前缀必须存在;客户端不能自行解密、拼接或改写凭据内容,凭据由登录/注册接口签发,过期或失效后必须重新获取(响应形态见错误响应差异)。
  • 会话完全无状态(STATELESS);服务端不为 App 维护会话,每次请求都必须携带凭据。

租户自动注入

JWT claims 中的 tenantId 在认证成功后进入当前认证 details;MyBatis-Plus TenantLine 拦截器经 TenantIdSupplierExecutor(优先显式执行器上下文,否则取认证 details 的 tenantId)自动为每条 SQL 注入 tenant_id 过滤,插入自动填充。因此:

  • 零售 App 用户的当前租户即其用户个人空间租户;13 个端点的读写自动限定在该租户的设备投影与分享记录范围内。
  • 请求不携带、也不能通过 tenantId 参数切换租户;空间级切换是服务端内部能力,不对 App 开放。跨租户读只发生在服务端内部显式忽略租户的路径上。

权限预期

  • 默认规则为 anyRequest().authenticated();白名单(swagger、error、登录、验证码、general/public 等)不含任何 retail / ForPersonalSpace 路径,13 个端点全部需要有效凭据。
  • 13 个端点均无 @PreAuthorize 权限注解——没有细粒度权限码,持有有效 JWT 即可调用。同一 Controller 的管理端 CRUD 均要求 device-data-acquisition-device:* / device-share:* 权限,形成对比。
  • 因此 App 侧无需申请或刷新权限码,也不应实现「权限不足」重试分支;凭据失效统一表现为 HTTP 200 + status=1000(见错误响应差异)。

常规响应的公共底座为 timestampstatuserrorlocalizedErrorpathextraDatadatadata 的形状由操作决定。HTTP status 与 JSON status 是两层信号,调用方不能仅以 HTTP 2xx 判成功,判定口径见错误响应差异

依据

适用后端基线:源码归纳 @ 2026-09-14。以下均为源码归纳结论:凭据解析与验签、同名请求参数兜底、无状态会话;认证失败按 200/1000 兼容形态返回;tenantId 取自凭据 claims 并经租户拦截器生效;匿名白名单不含 retail 路径(retail 端点默认无方法级授权注解,与管理端形成对比);响应信封底座与 servlet context 前缀按上文约定。