Dish_Recognition/docs/第三方接口文档.md

24 KiB
Raw Blame History

菜品识别管理系统第三方接口文档

版本:v1.3
更新时间:2026-10-13

1. 接口地址

生产环境基础地址:

https://cpsb.ruimeida.cc/

基地和菜品查询接口使用 GET 请求;识别结果、无法识别样本和菜品图库样本写入接口使用 POST 请求;菜品学习状态和设备状态更新使用 PATCH 请求。

2. 接口认证

接口使用独立的 API Key 认证,不使用后台用户登录账号密码。

每次请求都必须携带请求头:

X-API-Key: <THIRD_PARTY_API_KEY>

请向系统管理员获取正式 API Key,不要将 API Key 写入前端页面、公开代码仓库或日志中。

3. 写入菜品识别结果

该接口接收一次识别任务的主信息和菜品明细,供第三方识别程序回传结果。接口使用 request_id 做幂等键:同一个 request_id 再次提交时会更新主记录,并完整替换该任务的菜品明细,不会产生重复订单。

请求

POST /api/public/recognition-orders
Content-Type: application/json
X-API-Key: <THIRD_PARTY_API_KEY>

完整地址:

https://cpsb.ruimeida.cc/api/public/recognition-orders

请求体示例

{
  "request_id": "REQ_20260916_000001",
  "base_name": "上海路基地",
  "device_code": "DEV-0018",
  "capture_time": "2026-09-16 12:34:56",
  "source_image": "https://example.com/images/source.jpg",
  "labeled_image": "https://example.com/images/labeled.jpg",
  "dish_count": 2,
  "dishes": [
    {
      "index": 1,
      "name": "红烧鸡块",
      "score": 0.96,
      "is_unknown": false,
      "suggested_name": null,
      "box": [120, 80, 460, 390],
      "crop_image": "https://example.com/images/crop-1.jpg",
      "text": "识别为红烧鸡块,相似度 0.96"
    },
    {
      "index": 2,
      "name": null,
      "score": 0.42,
      "is_unknown": true,
      "suggested_name": "辣椒炒肉",
      "box": [500, 90, 820, 400],
      "crop_image": "https://example.com/images/crop-2.jpg",
      "text": "低于阈值,建议人工确认"
    }
  ]
}

Body 字段

字段 类型 必填 说明
request_id string 是 唯一请求号,建议使用 REQ_ 加毫秒时间戳或业务流水号
base_name string 是 基地名称,必须与基地信息中的 name 完全一致,例如 上海路基地
device_code string 是 设备编号,对应设备列表中的设备编号
capture_time string 是 采集时间,格式为 YYYY-MM-DD HH:mm:ss,也支持 ISO 8601 字符串
source_image string | null 否 原图路径或 URL;没有时传 null 或空字符串
labeled_image string | null 否 标注预览图路径或 URL;没有时传 null 或空字符串
dish_count int 是 菜品区域数,必须等于 dishes.length,不能小于 0
dishes array 是 菜品识别明细数组

dishes[] 字段

字段 类型 必填 说明
index int 是 从 1 开始的顺序号,与标注预览图中的编号/框对应,任务内不可重复
name string | null 是 识别菜名;未知菜品时传 null
score number | null 否 相似度,建议范围为 0~1;未知或没有分数时传 null
is_unknown boolean 是 是否未达阈值或未知,未知时为 true
suggested_name string | null 否 未知菜品的近似菜名建议
box number[4] | null 否 像素坐标 [x1, y1, x2, y2]
crop_image string | null 否 当前菜品裁剪图路径或 URL
text string | null 否 可读摘要文本,仅用于调试或展示

cURL 示例

curl -X POST "https://cpsb.ruimeida.cc/api/public/recognition-orders" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <THIRD_PARTY_API_KEY>" \
  -d @recognition-order.json

成功返回

{
  "message": "识别结果已保存",
  "item": {
    "request_id": "REQ_20260916_000001",
    "base_name": "上海路基地",
    "device_code": "DEV-0018",
    "capture_time": "2026-09-16 12:34:56",
    "dish_count": 2
  }
}

校验与存储说明

  1. base_name 必须传启用基地的名称,并且需要与基地信息中的 name 完全一致;接口会在服务端匹配基地编码保存关联关系。
  2. device_code 必须传设备列表中的设备编号。
  3. dish_count 必须与明细条数一致;index 在同一请求内必须唯一且从 1 开始。
  4. box 只能是 4 个数字,顺序为 [x1, y1, x2, y2],单位为原图像素。
  5. 识别原图、标注图和裁剪图由第三方自行保存,接口只保存传入的路径或 URL。
  6. 数据分别保存于 recognition_orders 和 recognition_order_dishes 两张表,删除或更新主记录时会同步处理明细。
  7. 新版接口使用 base_name,不再要求第三方传基地编码;服务端会根据基地名称匹配并保存内部关联关系。历史版本的 base_id 仅作兼容处理,不建议新接入继续使用。

4. 上报无法识别样本

第三方识别程序在识别到未知菜品后调用该接口,上报的数据进入后台「数据管理 → 识别数据管理 → 无法识别统计」列表。管理员可在该页面勾选裁剪图、填写菜名后提交手动学习,学习提交会转发到识别端学习接口(POST /api/client/manual-learn)。

请求

POST /api/public/unrecognized-samples
Content-Type: application/json
X-API-Key: <THIRD_PARTY_API_KEY>

完整地址:

https://cpsb.ruimeida.cc/api/public/unrecognized-samples

请求体示例

{
  "request_id": "REQ_20260916_000002",
  "order_no": "HW26091612341817",
  "base_name": "上海路基地",
  "device_code": "DEV-0018",
  "capture_time": "2026-09-16 12:34:46",
  "image_url": "https://example.com/images/source.jpg",
  "unknown_count": 2,
  "suggested_names": ["梅菜扣肉", "黑米饭"],
  "crops": [
    {
      "index": 1,
      "image_url": "https://example.com/images/crop-1.jpg",
      "suggested_name": "梅菜扣肉",
      "score": 0.62
    },
    {
      "index": 2,
      "image_url": "https://example.com/images/crop-2.jpg",
      "suggested_name": null,
      "score": 0.41
    }
  ]
}

Body 字段

字段 类型 必填 说明
request_id string 否 唯一请求号,作为幂等键;传入后同一 request_id 重复上报会覆盖主记录并重建明细
order_no string 是 业务单号,后台无法识别统计与手动学习接口使用
base_code string 否 基地编码,与 base_name 至少传一个;服务端匹配启用基地后保存关联
base_name string 否 基地名称,需与基地信息中的 name 一致;与 base_code 同时传入时优先按编码匹配
device_code string 是 设备编号
capture_time string 是 采集时间,格式为 YYYY-MM-DD HH:mm:ss,也支持 ISO 8601 字符串
image_url string 是 整盘原图路径或 URL
unknown_count number 否 未知菜品数量;不传或非法时按 crops.length 保存
suggested_names string[] 否 建议菜名列表,在列表页「建议菜名」列展示
crops array 否 未知菜品裁剪图明细,供后台学习弹框逐盘学习

crops[] 字段

字段 类型 必填 说明
index int 否 从 1 开始的顺序号,缺省按数组位置生成;同一样本内不可重复
image_url string | null 否 当前未知菜品的裁剪图路径或 URL
suggested_name string | null 否 近似菜名建议;后台学习弹框会预填到菜名输入框
score number | null 否 相似度,范围 0~1;后台学习弹框展示为置信度

cURL 示例

curl -X POST "https://cpsb.ruimeida.cc/api/public/unrecognized-samples" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <THIRD_PARTY_API_KEY>" \
  -d @unrecognized-sample.json

成功返回

{
  "message": "无法识别样本已保存",
  "item": {
    "id": 1,
    "order_no": "HW26091612341817",
    "unknown_count": 2
  }
}

校验与存储说明

  1. order_no、device_code、capture_time、image_url 为必填,缺失或格式无效返回 400。
  2. base_code / base_name 传入时服务端会匹配启用状态的基地并保存编码和名称;匹配不到时按传入原值保存,不影响入库。
  3. score 超出 0~1、index 重复或非法时返回 400。
  4. 传 request_id 时按幂等处理:重复上报覆盖主记录并完整替换 crops 明细;不传 request_id 时每次上报都会新增一条样本。
  5. 数据保存于 unrecognized_samples 和 unrecognized_sample_crops 两张表。
  6. 后台无法识别统计列表支持按时段、日期范围、基地、餐段、学习状态(默认未学习)和关键字筛选;餐段按 capture_time 落入系统餐段配置的时间范围判定。

5. 上报菜品图库样本(近期学习菜品)

识别端在学菜完成后调用该接口,把「菜名 ↔ 样本图片」映射上报给管理系统,数据展示在后台「AI 自主学习管理 → 近期学习菜品」页面,详情弹框中展示每个菜品的名称和多张样本图片。

请求

POST /api/public/dish-gallery/samples
Content-Type: application/json
X-API-Key: <THIRD_PARTY_API_KEY>

完整地址:

https://cpsb.ruimeida.cc/api/public/dish-gallery/samples

请求体示例(单菜)

{
  "request_id": "REQ_20260916_120001",
  "base_name": "上海路基地",
  "device_code": "DEV-0018",
  "learned_at": "2026-09-16 12:00:01",
  "name": "梅菜扣肉",
  "code": "DISH-001",
  "category": "成品",
  "images": [
    "https://example.com/template/mc/20260916_120001_01.jpg",
    "https://example.com/template/mc/20260916_120001_02.jpg"
  ]
}

请求体示例(一次报多个菜)

{
  "request_id": "REQ_20260916_120001",
  "base_name": "上海路基地",
  "learned_at": "2026-09-16 12:00:01",
  "dishes": [
    {
      "name": "梅菜扣肉",
      "code": "DISH-001",
      "category": "成品",
      "images": ["https://example.com/template/mc/20260916_120001_01.jpg"]
    },
    {
      "name": "黑米饭",
      "category": "成品",
      "images": ["https://example.com/template/hmf/20260916_120001_02.jpg"]
    }
  ]
}

Body 字段

字段 类型 必填 说明
request_id string 否 请求号,与菜品名称一起作为幂等键;不传时服务端自动生成
base_code string 否 基地编码,与 base_name 至少传一个;服务端匹配启用基地后保存关联
base_name string 否 基地名称,需与基地信息中的 name 一致
device_code string 否 设备编号
learned_at string 是 学习完成时间,格式为 YYYY-MM-DD HH:mm:ss,也支持 ISO 8601 字符串;兼容 capture_time 字段名
name string 单菜必填 菜品名称(单菜格式)
code string 否 菜品编码(单菜格式)
category string 否 分类,默认 成品,建议传 成品 或 配菜
images string[] 单菜必填 样本图片路径或 URL 数组,至少一张(单菜格式)
dishes array 多菜必填 多菜品格式;与 name 同时传入时优先使用 dishes

dishes[] 字段

字段 类型 必填 说明
name string 是 菜品名称
code string 否 菜品编码
category string 否 分类,默认 成品
images string[] 是 样本图片路径或 URL 数组,至少一张

cURL 示例

curl -X POST "https://cpsb.ruimeida.cc/api/public/dish-gallery/samples" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <THIRD_PARTY_API_KEY>" \
  -d @dish-gallery-sample.json

成功返回

{
  "message": "图库样本已保存",
  "item": {
    "request_id": "REQ_20260916_120001",
    "count": 2
  }
}

校验与存储说明

  1. learned_at(或 capture_time)缺失或格式无效返回 400。
  2. 单菜格式必须同时传 name 和非空 images;多菜格式每个菜品的 name 和 images 同样必填,否则返回 400。
  3. 幂等键为 request_id + 菜品名称:同一组合重复上报时更新样本(图片、分类、学习时间等以最新为准);上报未携带 code 时保留原编码。
  4. 数据保存于 dish_gallery_samples 表。
  5. 后台「近期学习菜品」列表按学习时间倒序展示,支持时段、日期范围、基地和关键字筛选;点「详情」按 request_id 展示该次学习的全部菜品及多张样本图片。

6. 后台查询识别结果

以下接口用于系统后台的统计列表和详情查询,使用登录用户的 JWT,不使用第三方 API Key。第三方系统如需查询自身回传的数据,建议由业务服务端代理调用,不要把管理员 JWT 暴露给浏览器。

识别统计列表

GET /api/recognition-stats
Authorization: Bearer <ACCESS_TOKEN>

可选参数:period(today、yesterday、week、month)、baseCode、keyword、startDate、endDate、page、pageSize。pageSize 支持 10、20、50。

识别结果详情

GET /api/recognition-orders/{request_id}
Authorization: Bearer <ACCESS_TOKEN>

详情返回主记录和 dishes 明细,主记录返回 base_name、device_code,不返回或展示基地编码 base_id;明细包含 index、name、score、is_unknown、suggested_name、box、crop_image、text。

7. 更新设备状态信息

第三方设备程序使用该接口按设备编号更新设备状态、心跳更新时间和菜谱当前日期。接口使用 API Key 认证。

请求

PATCH /api/public/devices/{device_code}/status
Content-Type: application/json
X-API-Key: <THIRD_PARTY_API_KEY>

完整地址示例:

https://cpsb.ruimeida.cc/api/public/devices/DEV-0018/status

请求体

{
  "status": "在线",
  "updated_at": "2026-09-16 14:30:00",
  "recipe_date": "2026-09-16"
}

参数说明

参数 类型 必填 说明
路径 device_code string 是 设备编号,对应设备列表中的设备编号
status string 是 设备状态,支持 在线、离线,也支持 online、offline
updated_at string | null 否 设备最后更新时间,格式为 YYYY-MM-DD HH:mm:ss;不传时使用服务器当前时间
recipe_date string | null 否 菜谱当前日期,格式为 YYYY-MM-DD;不传时保留原值,传 null 或空字符串可清空

cURL 示例

curl -X PATCH "https://cpsb.ruimeida.cc/api/public/devices/DEV-0018/status" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <THIRD_PARTY_API_KEY>" \
  -d '{"status":"在线","updated_at":"2026-09-16 14:30:00","recipe_date":"2026-09-16"}'

成功返回

{
  "message": "设备状态信息已更新",
  "item": {
    "id": 1,
    "code": "DEV-0018",
    "status": 1,
    "status_label": "在线",
    "updated_at": "2026-09-16 14:30:00",
    "recipe_date": "2026-09-16"
  }
}

设备列表查询接口 GET /api/devices 返回的字段包括 status、status_label、updated_at、recipe_date,其中 status=1 表示在线,status=0 表示离线。设备状态和时间信息保存于 devices 表。

8. 菜品信息接口

菜品接口使用 API Key 认证,返回菜品名称、菜品分类、学习状态、价格等信息。

8.1 获取菜品信息

GET /api/public/dishes
X-API-Key: <THIRD_PARTY_API_KEY>

完整地址:

https://cpsb.ruimeida.cc/api/public/dishes

查询参数均为可选,不传参数时返回全部菜品:

参数 类型 必填 说明
name string 否 按菜品名称模糊查询,例如 鸡
category string 否 按菜品分类精确查询,例如 素菜;传 全部 等同于不筛选

查询全部菜品:

curl -X GET "https://cpsb.ruimeida.cc/api/public/dishes" \
  -H "X-API-Key: <THIRD_PARTY_API_KEY>"

按菜品名称和分类查询:

curl -G "https://cpsb.ruimeida.cc/api/public/dishes" \
  -H "X-API-Key: <THIRD_PARTY_API_KEY>" \
  --data-urlencode "name=鸡" \
  --data-urlencode "category=大荤"

返回示例:

{
  "total": 1,
  "items": [
    {
      "id": 1,
      "name": "红烧鸡块",
      "code": "DISH-001",
      "category": "大荤",
      "price": 4,
      "status": "已学习",
      "confidence": 96.5,
      "picks": 3809,
      "created_at": "2026-09-16 10:00:00",
      "updated_at": "2026-09-28 09:30:00"
    }
  ]
}

8.2 按菜品名称更新学习状态

PATCH /api/public/dishes/learning-status
Content-Type: application/json
X-API-Key: <THIRD_PARTY_API_KEY>

完整地址:

https://cpsb.ruimeida.cc/api/public/dishes/learning-status

请求体:

{
  "name": "红烧鸡块",
  "status": "已学习"
}
字段 类型 必填 说明
name string 是 菜品名称,按名称精确匹配
status string 是 学习状态,只能是 已学习 或 未学习;也兼容 learned、unlearned

cURL 示例:

curl -X PATCH "https://cpsb.ruimeida.cc/api/public/dishes/learning-status" \
  -H "Content-Type: application/json" \
  -H "X-API-Key: <THIRD_PARTY_API_KEY>" \
  -d '{"name":"红烧鸡块","status":"已学习"}'

成功返回:

{
  "message": "菜品学习状态已更新",
  "item": {
    "id": 1,
    "name": "红烧鸡块",
    "code": "DISH-001",
    "category": "大荤",
    "price": 4,
    "status": "已学习",
    "confidence": 96.5,
    "picks": 3809,
    "created_at": "2026-09-16 10:00:00",
    "updated_at": "2026-09-28 09:30:00"
  }
}

菜品名称不存在时返回 404;状态不合法或菜品名称为空时返回 400。

9. 获取全部基地信息

请求

GET /api/public/bases

完整地址:

https://cpsb.ruimeida.cc/api/public/bases

cURL 示例

curl -X GET "https://cpsb.ruimeida.cc/api/public/bases" \
  -H "X-API-Key: <THIRD_PARTY_API_KEY>"

返回示例

{
  "items": [
    {
      "id": 1364,
      "branchName": "安仁街分部",
      "name": "上海路基地",
      "code": "上海路基地-1788059884828-e55vu",
      "province": "",
      "city": "",
      "district": "",
      "address": "",
      "contact": "",
      "phone": "",
      "longitude": null,
      "latitude": null,
      "description": "",
      "status": 1,
      "sortOrder": 0
    }
  ]
}

字段说明

字段 类型 说明
id number 基地 ID,可用于排餐查询的 baseId 参数
branchName string 分部名称
name string 基地名称,可用于排餐查询的 baseName 参数
code string 基地编码,可用于排餐查询的 baseCode 参数
province / city / district string 所属地区
address string 详细地址
contact string 联系人
phone string 联系电话
longitude / latitude number | null 经纬度
description string 基地备注
status number 1 启用,0 禁用
sortOrder number 排序值

10. 获取当天排餐信息

请求

GET /api/public/meal-schedules/today

兼容地址:

GET /api/meal-schedules/today

接口默认返回中国时区(Asia/Shanghai)当天的数据,并包含所有基地、早餐、中餐和晚餐。

查询参数

所有查询参数均为可选:

参数 类型 说明
date string 指定日期,格式为 YYYY-MM-DD;不传时使用中国时区当天日期
baseName string 按基地名称精确查询,推荐使用;中文需要进行 URL 编码
baseId number 按基地 ID 查询
baseCode string 按基地编码精确查询

基地筛选参数只需要传一个。如果同时传多个参数,优先级为:baseId > baseCode > baseName。

调用示例

查询当天所有基地:

curl -X GET "https://cpsb.ruimeida.cc/api/public/meal-schedules/today" \
  -H "X-API-Key: <THIRD_PARTY_API_KEY>"

按基地名称查询:

curl -G "https://cpsb.ruimeida.cc/api/public/meal-schedules/today" \
  -H "X-API-Key: <THIRD_PARTY_API_KEY>" \
  --data-urlencode "baseName=上海路基地"

查询指定日期和基地:

curl -G "https://cpsb.ruimeida.cc/api/public/meal-schedules/today" \
  -H "X-API-Key: <THIRD_PARTY_API_KEY>" \
  --data-urlencode "date=2026-09-16" \
  --data-urlencode "baseName=上海路基地"

返回示例

{
  "date": "2026-09-16",
  "timezone": "Asia/Shanghai",
  "mealPeriods": [
    {
      "id": 1,
      "name": "早餐",
      "startTime": "07:00",
      "endTime": "09:00"
    },
    {
      "id": 2,
      "name": "中餐",
      "startTime": "11:30",
      "endTime": "13:30"
    },
    {
      "id": 3,
      "name": "晚餐",
      "startTime": "17:30",
      "endTime": "19:00"
    }
  ],
  "bases": [
    {
      "id": 1364,
      "code": "上海路基地-1788059884828-e55vu",
      "name": "上海路基地",
      "branchName": "安仁街分部",
      "status": 1,
      "meals": [
        {
          "mealPeriodId": 1,
          "mealPeriodName": "早餐",
          "startTime": "07:00",
          "endTime": "09:00",
          "dishes": []
        },
        {
          "mealPeriodId": 2,
          "mealPeriodName": "中餐",
          "startTime": "11:30",
          "endTime": "13:30",
          "dishes": [
            {
              "name": "米粉蒸肉",
              "price": 4,
              "learningStatus": "未学习",
              "sortOrder": 0
            }
          ]
        },
        {
          "mealPeriodId": 3,
          "mealPeriodName": "晚餐",
          "startTime": "17:30",
          "endTime": "19:00",
          "dishes": []
        }
      ]
    }
  ]
}

排餐字段说明

字段 类型 说明
date string 查询日期
timezone string 日期使用的时区
mealPeriods array 当天配置的餐段列表
bases array 基地列表
bases[].meals array 当前基地的各餐段;没有菜品时仍返回该餐段
meals[].mealPeriodId number 餐段 ID
meals[].mealPeriodName string 餐段名称
meals[].startTime / endTime string 餐段开始、结束时间,格式为 HH:mm
meals[].dishes array 当前基地当前餐段的菜品
dishes[].name string 菜品名称
dishes[].price number 菜品价格
dishes[].learningStatus string 已学习 或 未学习
dishes[].sortOrder number 菜品在餐段中的顺序

11. HTTP 状态码

状态码 说明
200 请求成功
400 请求参数格式错误,例如日期或 baseId 格式错误
401 API Key 缺失或错误
503 服务端尚未配置第三方 API Key
500 服务端内部错误

错误返回格式:

{
  "message": "第三方接口认证失败"
}

12. 对接建议

  1. 第三方系统应将 API Key 保存在服务端配置中,不要放在浏览器前端代码中。
  2. 建议每次调用排餐接口时传入 baseName 或 baseId,减少返回数据量。
  3. 排餐接口会返回没有菜品的餐段,第三方系统应将 dishes: [] 识别为当天该基地该餐段暂无排餐。
  4. 日期不传时按 Asia/Shanghai 计算,不要使用第三方服务器的本地时区替代接口返回的 date。