Dish_Recognition/docs/API服务需求文档.md

13 KiB
Raw Permalink Blame History

菜品识别 API 服务需求文档

项目 说明
文档版本 v1.1
日期 2026-09-15
关联客户端 Dish_Recognition(海康 / USB 结算机客户端)
客户端实现参考 service/result_api.py
总服务器 central_server/(python start.py 或 python -m central_server)
状态 客户端已实现「先传图再上报」;总服务器可单独部署

1. 背景与目标

食堂客户端完成「YOLO 检盘 → 特征匹配认菜」后,需将结构化识别结果上报至业务侧 API,供:

  • 收银 / 结算机展示与记账对接
  • 总部汇总多基地识别数据
  • 未知菜品后续运营处理(可选人工审核)

本期 API 服务范围:接收图片、落对象存储、返回 URL;再接收识别结果 JSON 并落库。
对接拓扑:工位机只连总库/中心 API;业务系统只连总库,不直连工位。
明确不做:真实支付扣款、会员积分、打印票据(可由下游系统消费本 API 数据实现)。


2. 角色与系统边界

┌─────────────────┐     1) 上传图片        ┌──────────────────┐
│ 工位机客户端     │ ─────────────────────► │ 中心 API / 总库   │
│ Dish_Recognition│     2) 上报 JSON(URL)   │ 本文档            │
└────────┬────────┘                         └────────┬─────────┘
         │ 模板同步                                  │
         ▼                                           ▼
┌─────────────────┐                         ┌──────────────────┐
│ MySQL 总库       │ ◄──── 流水 / URL ───── │ 对象存储 / 静态   │
│ 模板·基地·记录   │                         │ media_store 或 OSS│
└─────────────────┘                         └──────────────────┘
                     ▲
                     │ 只对接总库
              ┌──────┴──────┐
              │ 你方业务系统 │
              └─────────────┘
系统 职责
客户端 识别、本地 SQLite 缓存;先上传图片再上报 URL
中心 API 鉴权、收图、静态访问、校验、结果落库、查询
MySQL 总库 模板权威数据;识别记录可同步写入
对象存储 / 静态服务 原图 / 标注图 / 裁剪图,对外提供 HTTP URL

3. 功能需求

3.1 识别结果上报(P0 · 必须)

项 要求
接口 POST /api/dish/recognize/result
触发时机 客户端每次选图/拍照识别完成后(异步发送)
幂等 以 request_id 为幂等键;重复上报应返回成功且不重复计费/重复插有效流水
响应时延 P99 < 500ms(不含大图上传);超时客户端按 5s 配置
失败策略 客户端失败时本地落 mock 文件;服务端应返回明确错误码便于重试

3.2 识别记录查询(P1 · 建议)

接口 说明
GET /api/dish/recognize/records 按基地、时间范围分页查询
GET /api/dish/recognize/records/{request_id} 按请求号查单条详情

3.3 图片资源(P0 · 必须:先上传再上报)

工位机本机路径跨机不可用。约定 方案 A:

  1. POST /api/dish/media/upload 上传 source / labeled / crop,返回可访问 url
  2. POST /api/dish/recognize/result 的 source_image / labeled_image / dishes[].crop_image 填 URL,不再填本地路径

本地对象存储:central_server/data/media/,由 python start.py 提供 GET /media/...。
生产可把同一目录挂到 Nginx / OSS,或把上传接口改写为直传对象存储,客户端字段不变。

3.4 未知菜审核(P2 · 可选)

客户端本地有「待学习库」;总部侧可扩展:

接口 说明
GET /api/dish/unknown/pending 拉取未知菜列表
POST /api/dish/unknown/{id}/approve 确认菜名并回写模板策略
POST /api/dish/unknown/{id}/reject 忽略

客户端 ORM 已有 PendingApproval 模型雏形,服务端可与之对齐。

3.5 健康检查(P0)

接口 说明
GET /api/health 存活探测;客户端/网关探活

4. 接口详细设计

4.1 上报识别结果

请求

  • Method:POST
  • Path:/api/dish/recognize/result
  • Content-Type:application/json
  • Header(建议):
Header 必填 说明
Authorization 建议 Bearer <token> 或基地密钥
X-Base-Code 可选 与 body.base_id 一致,便于网关路由
Content-Type 是 application/json

Body 字段(与客户端 build_result_payload 对齐)

字段 类型 必填 说明
request_id string 是 唯一请求号,格式建议 REQ_{毫秒时间戳}
base_id string 是 基地编码,如 BASE_LOCAL_01
capture_time string 是 YYYY-MM-DD HH:mm:ss
source_image string 否 原图路径或 URL
labeled_image string 否 标注预览图路径或 URL
dish_count int 是 菜品区域数,应等于 dishes.length
dishes array 是 菜品明细,见下表

dishes[] 元素

字段 类型 必填 说明
index int 是 从 1 起的序号,与预览图编号/框对应
name string | null 是 识别菜名;未知时为 null
score number | null 否 相似度,建议 0~1
is_unknown bool 是 是否未达阈值 / 未知
suggested_name string | null 否 未知时的近似菜名建议
box number[4] | null 否 [x1,y1,x2,y2] 像素坐标
crop_image string | null 否 裁剪图路径或 URL
text string | null 否 可读摘要文本(调试用)

请求示例

{
  "request_id": "REQ_1726296161000",
  "base_id": "BASE_LOCAL_01",
  "capture_time": "2026-09-14 14:22:41",
  "source_image": "https://cdn.example.com/cap/xxx.jpg",
  "labeled_image": "https://cdn.example.com/detect/xxx.jpg",
  "dish_count": 2,
  "dishes": [
    {
      "index": 1,
      "name": "土豆丝",
      "score": 0.86,
      "is_unknown": false,
      "suggested_name": null,
      "box": [120, 80, 460, 400],
      "crop_image": "https://cdn.example.com/cutting/1.jpeg",
      "text": "土豆丝 0.860"
    },
    {
      "index": 2,
      "name": null,
      "score": 0.42,
      "is_unknown": true,
      "suggested_name": "炒青菜",
      "box": [500, 90, 820, 410],
      "crop_image": "https://cdn.example.com/cutting/2.jpeg",
      "text": "未知 · 近似 炒青菜"
    }
  ]
}

成功响应(建议)

{
  "code": 0,
  "message": "ok",
  "data": {
    "request_id": "REQ_1726296161000",
    "accepted": true,
    "duplicate": false
  }
}

错误响应(建议)

{
  "code": 40001,
  "message": "dish_count 与 dishes 长度不一致",
  "data": null
}
HTTP code(示例) 含义
200 0 成功(含幂等重复)
400 40001 参数校验失败
401 40100 未授权
409 40900 业务冲突(可选)
500 50000 服务内部错误

4.2 查询识别记录(P1)

GET /api/dish/recognize/records?base_id=BASE_01&from=2026-09-01&to=2026-09-14&page=1&page_size=20

返回字段建议对齐表 recognition_records:

request_id、base_code、capture_time、dish_count、unknown_count、summary、source_image、labeled_image、result_json


4.3 媒体上传(P0)

POST /api/dish/media/upload
Content-Type: multipart/form-data

字段 必填 说明
file 是 图片文件(jpg/png/bmp/webp,默认上限 12MB)
type 是 source / labeled / crop
base_id 建议 基地编码
request_id 建议 关联某次识别,便于归档

成功响应:

{
  "code": 0,
  "message": "ok",
  "data": {
    "url": "http://127.0.0.1:9000/media/BASE_LOCAL_01/REQ_1726296161000/source_cap.jpg",
    "path": "BASE_LOCAL_01/REQ_1726296161000/source_cap.jpg",
    "type": "source"
  }
}

静态访问:GET /media/{base_id}/{request_id}/{filename}

归档规则:media_store/{base_id}/{request_id}/{type}_{原文件名}

客户端行为(ResultApiClient):

  1. 生成 request_id
  2. 默认:启动本机 media_server(:9100),把 YOLO 裁剪图等复制进 central_server/data/media/,字段写成
    http://<局域网IP>:9100/media/{base}/{request_id}/crop_xxx.jpg
  3. 若显式设置了 DISH_MEDIA_UPLOAD_URL,则优先远程上传,失败再回落本机 media server
  4. 再 POST 管理系统 recognition-orders(或本地 mock)

模拟模式不发结果 HTTP,但仍生成本机 media URL。若云端管理系统要拉取图片,本机需公网可达,或设置 DISH_MEDIA_PUBLIC_BASE 为可访问前缀(反代/穿透/OSS)。


5. 非功能需求

类别 要求
可用性 核心上报接口可用性 ≥ 99.5%
并发 单集群支持 ≥ 50 QPS 上报(可按基地数扩展)
安全 HTTPS;基地级鉴权;禁止匿名写
审计 保留原始 JSON ≥ 90 天;可按 request_id 追溯
日志 记录 request_id、base_id、耗时、HTTP 状态
兼容 字段向后兼容;新增字段客户端可忽略

6. 与客户端配置对齐

环境变量 含义 默认
DISH_RESULT_API_MOCK 1 写本地 mock,不发 HTTP 0(真实上报)
DISH_RESULT_API_URL 真实上报地址 管理系统 …/recognition-orders
DISH_MEDIA_UPLOAD_URL 远程传图地址;空则不走远程,用本机 media server 空
DISH_MEDIA_SERVER_ENABLED 是否启本机静态图服务 1
DISH_MEDIA_SERVER_PORT 本机 media 端口 9100
DISH_MEDIA_PUBLIC_BASE 对外可访问的 /media 前缀(云端拉图时必配公网) 自动 http://<LAN>:9100/media
DISH_MEDIA_HOST 强制写入 URL 的主机名/IP 空则自动探测局域网 IP
DISH_UPLOAD_BEFORE_REPORT 1 先落 URL 再上报 1
DISH_API_TOKEN / DISH_RESULT_API_KEY Bearer / X-API-Key 管理系统 Key
DISH_RESULT_API_TIMEOUT 结果上报超时秒数 10
DISH_MEDIA_UPLOAD_TIMEOUT 远程单张图上传超时 20
DISH_RESULT_BASE_ID / DISH_BASE_CODE 上报基地 BASE_LOCAL_01

联调步骤:

  1. 启动客户端(会自动起 :9100 media server)
  2. 识别一次,日志应出现 crop -> http://<LAN>:9100/media/.../crop_….jpg
  3. 浏览器打开该 URL 应能看到裁剪图;上报 JSON 中 dishes[].crop_image 为该 HTTP 路径
  4. 若管理系统在公网:配置 DISH_MEDIA_PUBLIC_BASE 为可外网访问的前缀,或设置 DISH_MEDIA_UPLOAD_URL 接到 OSS

7. 数据落库建议

可复用或扩展现有 recognition_records:

列 说明
request_id 幂等键,唯一索引
base_code / source_base_id 基地
capture_time 抓拍时间
source_image / labeled_image 建议存 URL
dish_count / unknown_count 统计
summary 菜名摘要
result_json 完整 dishes JSON

8. 验收标准

  • 合法 payload 上报返回 code=0,且库中可查到对应 request_id(本地 api_server)
  • 相同 request_id 重复上报不产生重复有效业务单据
  • is_unknown=true 且 name=null 可正常入库
  • dish_count 与数组长度不一致时返回 4xx
  • 配置 DISH_API_TOKEN 后未鉴权请求被拒绝
  • 客户端关闭 mock 后真实 HTTP 联调通过(抽样 ≥ 20 次)
  • 图片上传后结果中的 URL 可通过 GET /media/... 访问

9. 分期建议

阶段 内容
MVP 健康检查 + 图片上传 + 静态访问 + 结果上报 + 幂等落库
二期 记录查询分页 / 时间筛选;生产 OSS 直传
三期 未知菜总部审核流 + 统计看板 API

10. 附录:客户端当前行为说明

  • 默认 mock:不发 HTTP;图片复制到 media_store/,JSON 写入 api_mock_results/REQ_*.json(字段已是 URL)
  • 真实模式:先 multipart 上传图片,再 POST JSON;失败时仍写一份本地文件便于排错
  • 默认 异步上报(不阻塞 UI);本地 SQLite 仍保存本机路径便于预览
  • 识别同时写入本地 SQLite recognition_records;MySQL 可用时客户端也会尝试同步写总库(与 HTTP API 并行,服务端实现时注意职责划分,避免双写冲突)

11. 待确认事项(给产品/后端)

  1. 鉴权方式:已支持可选 DISH_API_TOKEN(Bearer / X-Api-Token);生产是否改 JWT / 签名?
  2. 生产对象存储:继续用本机 media_store + Nginx,还是 OSS(接口形态已对齐,只换上传实现)?
  3. 识别结果是否需要回推收银金额(单价是否由服务端维护)?

图片必须先上传再上报已冻结为协议。确认其余项后可发 v1.2。