Skip to content

Latest commit

 

History

History
609 lines (447 loc) · 15.3 KB

File metadata and controls

609 lines (447 loc) · 15.3 KB

AiMonitor HTTP API

本文档对应 AiMonitor 当前实现,描述设备内置 HTTP 服务提供的全部接口。

1. 服务概览

项目 说明
地址 http://<设备局域网 IP>:<自动选择的端口>
传输协议 HTTP/1.1 明文传输
HTTP 端口 10241 起使用首个可用端口
JSON 编码 UTF-8
最大请求头 16 KiB
最大请求体 8 MiB
连接读取超时 5 秒
同时处理的连接 最多 4 个,另排队 16 个
图片上传并发 1(其余上传排队)
支持的图片 JPEG、PNG、GIF
鉴权

设备 IP、端口和健康检查地址可在应用的“设置”页面查看。服务没有身份验证,建议仅在可信局域网中使用,不要直接暴露到公网。 下文示例假设 10241 未被占用;发生冲突时,请将示例端口替换为设置页或发现结果返回的实际端口。

请求队列已满时,服务返回 503 Service Unavailable,客户端应采用带抖动的退避策略稍后重试。图片上传会串行处理,以限制大请求正文带来的瞬时内存占用;普通查询和宫格更新仍可并发执行。

服务启动后会通过 mDNS/DNS-SD 发布 _aimonitor._tcp.。客户端可以通过 Android NSD、Apple Bonjour 或其他 DNS-SD 客户端自动发现设备地址和端口。 服务的 TXT 记录包含:

属性 说明
id 首次启动生成并持久化的设备 UUID
name 用户在设置页配置的设备名称
apiVersion HTTP API 版本
path 设备信息接口,当前为 /api/device

局域网或路由器禁用组播时,mDNS 可能不可用,此时仍可使用设置页显示的 IP 和端口连接。

客户端还可以使用 UDP 广播发现。向局域网广播地址的 UDP 8080 端口发送 UTF-8 文本 AIMONITOR_DISCOVER_V1。设备只响应完全匹配的探测消息,并向 客户端数据报的来源 IP 和来源端口单播回复:

{"id":"17e13bf7-98e7-4c3c-b66e-76d21eb44554","name":"客厅监控屏","port":10241,"apiVersion":"3"}

UDP 回复固定只包含 idnameport 和字符串类型的 apiVersion。 客户端应使用收到回复的数据报来源地址作为设备 IP,再通过回复中的 port 连接 HTTP API。

所有响应都包含以下 CORS 头:

Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type

2. 接口一览

方法 路径 用途
OPTIONS 任意路径 CORS 预检
GET /health 检查服务状态
GET /api/device 获取设备身份和能力信息
GET /api/config 获取宫格和端口配置
POST /api/images 上传图片
GET /api/images 扫描并返回全部缓存图片
GET /api/images/{filename} 获取一张原始图片
DELETE /api/images/{filename} 删除一张图片
POST /api/slots/{slot} 更新宫格
DELETE /api/slots/{slot} 清空宫格
POST /api/clients/{clientId}/heartbeat 为控制端占用的宫格续租

推荐的业务调用顺序:

  1. 调用 POST /api/images 上传图片。
  2. 从响应中取得 UUID 文件名。
  3. 将稳定的控制端 clientId 和图片文件名调用 POST /api/slots/{slot}
  4. 每 30 秒调用 POST /api/clients/{clientId}/heartbeat
  5. 不再使用图片时,调用 DELETE /api/images/{filename}

3. 通用约定

3.1 JSON 响应

除“获取原始图片”接口外,响应正文均为 JSON:

Content-Type: application/json; charset=utf-8

错误响应统一使用 error 字段:

{
  "error": "错误说明"
}

3.2 请求正文

带正文的请求必须发送正确的 Content-Length。当前服务不支持 HTTP chunked request body。

3.3 图片文件名

上传成功后,服务根据图片实际格式生成文件名:

<UUID>.<extension>

示例:

7604efdb-28c8-4aa2-b8b1-d95762cc396f.png

宫格接口只接收文件名,不再接收 Base64。文件名只允许字母、数字、点、下划线和连字符,且扩展名必须是 jpgjpegpnggif

4. CORS 预检

OPTIONS 任意路径

服务对任意路径的 OPTIONS 请求返回成功。

响应:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
  "status": "ok"
}

5. 健康检查

GET /health

检查 HTTP 服务是否可访问。

成功响应:

HTTP/1.1 200 OK
{
  "status": "ok"
}

示例:

curl http://192.168.1.100:10241/health

6. 获取设备信息

GET /api/device

发现服务后,获取设备的稳定标识、显示名称、型号和接口能力。

成功响应:

{
  "id": "17e13bf7-98e7-4c3c-b66e-76d21eb44554",
  "name": "客厅监控屏",
  "manufacturer": "Google",
  "model": "Pixel Tablet",
  "androidVersion": "16",
  "appVersion": "1.1.1",
  "apiVersion": 3,
  "port": 10241,
  "rows": 3,
  "columns": 5,
  "capabilities": ["images", "slots", "heartbeats"]
}

客户端应使用 id 识别和记忆设备,使用 name 向用户展示。mDNS 实例名称 可能因局域网内重名而被系统修改,不应作为稳定标识。

7. 获取配置

GET /api/config

获取当前宫格行数、列数和监听端口。

成功响应:

HTTP/1.1 200 OK
{
  "rows": 3,
  "columns": 5,
  "port": 10241
}
字段 类型 说明
rows Integer 当前宫格行数,范围 1–5
columns Integer 当前宫格列数,范围 1–5
port Integer HTTP 服务实际监听端口(从 10241 起自动选择)

8. 上传图片

POST /api/images

上传 JPEG、PNG 或 GIF。服务通过文件签名识别实际格式,不依赖原文件名或客户端声明的 MIME 类型。

8.1 Multipart 上传

推荐使用 multipart/form-data,文件字段名为 file

curl -F "file=@avatar.png" \
  http://192.168.1.100:10241/api/images

等价 HTTP 请求:

POST /api/images HTTP/1.1
Host: 192.168.1.100:10241
Content-Type: multipart/form-data; boundary=BOUNDARY
Content-Length: ...

--BOUNDARY
Content-Disposition: form-data; name="file"; filename="avatar.png"
Content-Type: image/png

<图片二进制>
--BOUNDARY--

8.2 原始二进制上传

也可以直接将图片作为请求正文:

curl --data-binary "@avatar.png" \
  -H "Content-Type: image/png" \
  http://192.168.1.100:10241/api/images

8.3 成功响应

HTTP/1.1 200 OK
{
  "filename": "7604efdb-28c8-4aa2-b8b1-d95762cc396f.png"
}
字段 类型 说明
filename String 服务生成的 UUID 图片文件名

8.4 错误响应

状态码 响应 原因
400 {"error":"an image file is required"} 请求正文为空、multipart 缺少文件或 boundary 无效
400 {"error":"image must be a valid JPG, PNG, or GIF file"} 文件签名不是受支持的图片,或图片无法写入缓存
413 {"error":"request body too large"} 完整请求体超过 8 MiB

9. 扫描图片缓存

GET /api/images

扫描图片缓存目录,并分页返回有效图片的元数据。结果按文件名升序排列,列表接口不会读取或 Base64 编码图片正文。

查询参数:

参数 默认值 说明
offset 0 从第几条记录开始,必须大于等于 0
limit 50 每页记录数,范围 1~100

成功响应:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
{
  "images": [
    {
      "filename": "7604efdb-28c8-4aa2-b8b1-d95762cc396f.png",
      "mimeType": "image/png",
      "size": 123456,
      "url": "/api/images/7604efdb-28c8-4aa2-b8b1-d95762cc396f.png"
    }
  ],
  "offset": 0,
  "limit": 50,
  "total": 1,
  "hasMore": false
}

没有图片时:

{
  "images": [],
  "offset": 0,
  "limit": 50,
  "total": 0,
  "hasMore": false
}
字段 类型 说明
images Array 图片列表
images[].filename String UUID 图片文件名
images[].mimeType String image/jpegimage/pngimage/gif
images[].size Number 图片文件字节数
images[].url String 按需获取原图的相对 URL
offset Number 当前页起始位置
limit Number 当前页大小
total Number 有效图片总数
hasMore Boolean 是否还有下一页

需要显示图片时,客户端应使用 images[].url 按需加载原图。

10. 获取原始图片

GET /api/images/{filename}

按文件名获取原始图片字节。

示例:

curl -o downloaded.png \
  http://192.168.1.100:10241/api/images/7604efdb-28c8-4aa2-b8b1-d95762cc396f.png

成功响应:

HTTP/1.1 200 OK
Content-Type: image/png
Content-Disposition: inline; filename="7604efdb-28c8-4aa2-b8b1-d95762cc396f.png"
Content-Length: ...

<图片二进制>

错误响应:

HTTP/1.1 404 Not Found
{
  "error": "image not found"
}

当文件不存在、文件名不合法、扩展名不受支持或缓存文件不是有效图片时,均返回 404

11. 删除图片

DELETE /api/images/{filename}

从缓存中删除图片。

示例:

curl -X DELETE \
  http://192.168.1.100:10241/api/images/7604efdb-28c8-4aa2-b8b1-d95762cc396f.png

成功响应:

HTTP/1.1 200 OK
{
  "status": "deleted",
  "filename": "7604efdb-28c8-4aa2-b8b1-d95762cc396f.png"
}

如果某个宫格正在引用该图片,图片删除后宫格的用户名、AI 名称和内容仍会保留,图片区域立即切换为默认占位图。

文件不存在或文件名不合法时:

HTTP/1.1 404 Not Found
{
  "error": "image not found"
}

12. 更新宫格

POST /api/slots/{slot}

更新一个宫格的完整内容。slot 使用从 1 开始的编号。

当前内部最多有 25 个宫格,因此合法范围为 1–25。页面实际显示数量由 rows × columns 决定;更新暂时不可见的合法宫格仍会返回成功。

请求:

POST /api/slots/1 HTTP/1.1
Host: 192.168.1.100:10241
Content-Type: application/json
Content-Length: ...

{
  "clientId": "550e8400-e29b-41d4-a716-446655440000",
  "username": "小明",
  "aiName": "CodeX",
  "content": "运行正常",
  "image": "7604efdb-28c8-4aa2-b8b1-d95762cc396f.png"
}
字段 类型 必填 说明
clientId String 控制端稳定唯一 ID;1-128 位字母、数字、-_
username String 用户名称;首尾空白会被移除,处理后不能为空
aiName String AI 名称;首尾空白会被移除,处理后不能为空
content String 展示内容;缺省或 null 时使用空字符串
image String 上传接口返回的图片文件名,不是 Base64

成功响应:

HTTP/1.1 200 OK
{
  "status": "updated",
  "slot": 1
}

12.1 图片不存在时的行为

只要 image 是格式合法的图片文件名,即使缓存中找不到对应文件,宫格更新仍返回 200。应用会保留该文件名,并在图片区域显示默认占位图。

这也适用于图片被系统清理或随后通过删除接口移除的情况。

12.2 错误响应

状态码 响应 原因
400 {"error":"invalid JSON"} 请求正文不是有效 JSON
400 {"error":"clientId is required"} clientId 缺失或格式无效
400 {"error":"username is required"} username 缺失、不是字符串或处理后为空
400 {"error":"aiName is required"} aiName 缺失、不是字符串或处理后为空
400 {"error":"image is required"} image 缺失、不是字符串或处理后为空
400 {"error":"image must be a valid uploaded filename"} 图片文件名包含非法字符或扩展名不受支持
404 {"error":"slot must be between 1 and 25"} slot 超出合法范围

13. 清空宫格

DELETE /api/slots/{slot}

清空指定宫格中的用户名、AI 名称、内容、图片引用和更新时间。该操作不会删除图片缓存文件。

示例:

curl -X DELETE http://192.168.1.100:10241/api/slots/1

成功响应:

HTTP/1.1 200 OK
{
  "status": "cleared",
  "slot": 1
}

宫格编号超出范围:

HTTP/1.1 404 Not Found
{
  "error": "slot must be between 1 and 25"
}

14. 控制端心跳

POST /api/clients/{clientId}/heartbeat

刷新控制端租约,不需要请求正文。clientId 必须与槽位更新中的字段一致,控制端 应每 30 秒调用一次。设备每 30 秒检查租约;连续 2 分钟未收到心跳时,会把该 clientId 拥有的全部槽位重置为空,效果等同于逐个调用 DELETE,不影响其他客户端。

curl -X POST \
  http://192.168.1.100:10241/api/clients/550e8400-e29b-41d4-a716-446655440000/heartbeat

成功响应:

{"status":"alive","clientId":"550e8400-e29b-41d4-a716-446655440000"}

15. 通用错误

状态码 响应 说明
400 {"error":"invalid HTTP headers"} 请求头不完整、超过 16 KiB 或连接提前结束
400 {"error":"invalid content length"} Content-Length 为负数
404 {"error":"not found"} 路径或 HTTP 方法没有对应接口
413 {"error":"request body too large"} 请求体超过 8 MiB

16. 完整调用示例

以下示例使用 jq 从上传响应中读取文件名:

BASE_URL="http://192.168.1.100:10241"
CLIENT_ID="550e8400-e29b-41d4-a716-446655440000"

FILENAME="$(
  curl -sS -F "file=@avatar.png" "$BASE_URL/api/images" |
    jq -r '.filename'
)"

curl -sS \
  -H "Content-Type: application/json" \
  --data "{
    \"clientId\":\"$CLIENT_ID\",
    \"username\":\"小明\",
    \"aiName\":\"CodeX\",
    \"content\":\"运行正常\",
    \"image\":\"$FILENAME\"
  }" \
  "$BASE_URL/api/slots/1"

curl -sS -X POST \
  "$BASE_URL/api/clients/$CLIENT_ID/heartbeat"

curl -sS "$BASE_URL/api/images" | jq

curl -sS -o downloaded-image \
  "$BASE_URL/api/images/$FILENAME"

curl -sS -X DELETE \
  "$BASE_URL/api/images/$FILENAME"

curl -sS -X DELETE \
  "$BASE_URL/api/slots/1"

17. 数据生命周期

  • 图片保存在 Android 应用的 cacheDir/monitor_images 目录。
  • 上传图片在应用进程重启后仍然存在。
  • Android 系统可能在存储空间不足时清理缓存。
  • 卸载应用或清除应用数据会删除缓存图片。
  • 宫格内容当前保存在进程内存中,应用进程重启后会恢复为空宫格。
  • 控制端心跳租约保存在进程内存中;2 分钟未续租会自动清空该控制端的宫格。
  • 行数和列数保存在应用偏好设置中,应用重启后仍然保留。
  • 删除宫格不会删除图片;删除图片也不会清空宫格文字。

18. 当前限制

  • 没有鉴权、TLS、速率限制或访问日志接口。
  • 不支持分块请求体。
  • 不支持局部更新宫格;每次更新都必须提供 usernameaiNameimage
  • 不提供读取单个宫格或列出全部宫格的接口。
  • 不支持 WebP、SVG、BMP、AVIF 等其他图片格式。