24 KiB
菜品识别管理系统第三方接口文档
版本: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
}
}
校验与存储说明
base_name必须传启用基地的名称,并且需要与基地信息中的name完全一致;接口会在服务端匹配基地编码保存关联关系。device_code必须传设备列表中的设备编号。dish_count必须与明细条数一致;index在同一请求内必须唯一且从 1 开始。box只能是 4 个数字,顺序为[x1, y1, x2, y2],单位为原图像素。- 识别原图、标注图和裁剪图由第三方自行保存,接口只保存传入的路径或 URL。
- 数据分别保存于
recognition_orders和recognition_order_dishes两张表,删除或更新主记录时会同步处理明细。 - 新版接口使用
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
}
}
校验与存储说明
order_no、device_code、capture_time、image_url为必填,缺失或格式无效返回400。base_code/base_name传入时服务端会匹配启用状态的基地并保存编码和名称;匹配不到时按传入原值保存,不影响入库。score超出 0~1、index重复或非法时返回400。- 传
request_id时按幂等处理:重复上报覆盖主记录并完整替换crops明细;不传request_id时每次上报都会新增一条样本。 - 数据保存于
unrecognized_samples和unrecognized_sample_crops两张表。 - 后台无法识别统计列表支持按时段、日期范围、基地、餐段、学习状态(默认未学习)和关键字筛选;餐段按
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
}
}
校验与存储说明
learned_at(或capture_time)缺失或格式无效返回400。- 单菜格式必须同时传
name和非空images;多菜格式每个菜品的name和images同样必填,否则返回400。 - 幂等键为
request_id + 菜品名称:同一组合重复上报时更新样本(图片、分类、学习时间等以最新为准);上报未携带code时保留原编码。 - 数据保存于
dish_gallery_samples表。 - 后台「近期学习菜品」列表按学习时间倒序展示,支持时段、日期范围、基地和关键字筛选;点「详情」按
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. 对接建议
- 第三方系统应将 API Key 保存在服务端配置中,不要放在浏览器前端代码中。
- 建议每次调用排餐接口时传入
baseName或baseId,减少返回数据量。 - 排餐接口会返回没有菜品的餐段,第三方系统应将
dishes: []识别为当天该基地该餐段暂无排餐。 - 日期不传时按
Asia/Shanghai计算,不要使用第三方服务器的本地时区替代接口返回的date。