{
  "openapi": "3.0.0",
  "info": {
    "title": "智慧营养健康餐厅设备人脸接口增量更新",
    "version": "1.0.0"
  },
  "paths": {
    "/p/api/userdata": {
      "post": {
        "summary": "人员数据同步(人脸/卡片)",
        "deprecated": false,
        "description": "## 接口概述\n\n设备同步人员、人脸照片及卡片数据。张家港设备端学习使用 `type=1` 全量：读取 `data.face_learning_mode`，模式 `2` 下有照片但尚无特征的人员仍以 `status=1` 下发，由设备下载照片并学习；学习成功后调用 `/p/api/facedataLearnResult` 回传，`staff_uuid` 取自当前 `face_data[]` 记录。`type=0` 增量沿用原有消息队列逻辑，本期不改造增量下发。\n\n## 接口信息\n\n| 项目 | 内容 |\n| --- | --- |\n| 地址 | `POST /p/api/userdata` |\n| Content-Type | `application/x-www-form-urlencoded` |\n| 调用方 | 消费机等安卓设备 |\n\n## 请求头（Header）\n\n`Content-Type: application/x-www-form-urlencoded`。历史可选 `Access-Token` 头仍保留；当前业务方法以设备编码核对设备是否存在，不校验该头。\n\n## 请求参数（Body）\n\n| 字段 | 必填 | 说明 |\n| --- | --- | --- |\n| `code` | 是 | 已登记且未删除的设备编码。 |\n| `type` | 否 | `1` 全量，`0` 增量；默认 `0`，其他值按 `0` 处理。 |\n\n## 响应格式\n\n成功示例（模式 2、待设备学习）：\n\n```json\n{\n  \"code\": 0,\n  \"message\": \"查询成功\",\n  \"data\": {\n    \"face_data\": [\n      {\n        \"id\": 123,\n        \"staff_uuid\": \"11111111-2222-3333-4444-555555555555\",\n        \"name\": \"示例人员\",\n        \"head_img_url\": \"http://example.invalid/static/upload/face/example.jpg\",\n        \"feature\": \"\",\n        \"face_token\": \"\",\n        \"hardware_uuid\": \"66666666-7777-8888-9999-000000000000\",\n        \"status\": 1,\n        \"card_id\": \"\"\n      }\n    ],\n    \"face_learning_mode\": 2\n  },\n  \"trace_id\": \"<本次请求追踪ID>\"\n}\n```\n\n失败示例：\n\n```json\n{\n  \"code\": 1,\n  \"message\": \"设备不存在\",\n  \"data\": null,\n  \"trace_id\": \"<本次请求追踪ID>\"\n}\n```\n\n增量成功时 `data.face_data` 为消息队列中的待同步记录，具体记录字段按消息类型变化。\n\n## 状态码与设备处理\n\nHTTP 通常为 200，必须以顶层 `code` 判定业务结果。`data.face_learning_mode=1` 表示服务端学习，`2` 表示设备端学习；该字段在成功响应中返回，**不在响应最外层**。模式 2 下，有照片且 `face_token` 为空时，设备下载照片并学习；有 token 时按既有设备逻辑保存。无照片也无有效卡号时 `status=3`；有有效卡号时仍可作为有效人员。全量查询更新设备同步时间并处理积累消息。\n\n## 响应字段说明\n\n`data.face_data` 是人员、人脸和卡片列表；`id` 为 `ydy_staff.id`，学习结果回传时使用同一记录的 `staff_uuid`，`id` 不作为回传参数；`head_img_url` 是完整照片地址，回传时须原样传入；`feature`、`face_token` 在未学习时可以为空；`card_id` 可为空。全量记录的 `status=1` 为有效，`status=3` 为删除/无有效人脸或卡号；增量状态沿用原消息。`data.face_learning_mode` 为 `1` 或 `2`。`trace_id` 在成功和业务失败响应中均由项目统一补充。\n\n## trace_id 排查说明\n\n排查时提供 `trace_id`、设备编码和请求时间，以便关联服务端日志；不要在工单中传播真实人脸特征值。\n",
        "parameters": [
          {
            "name": "Access-Token",
            "in": "header",
            "description": "",
            "required": false,
            "example": "{{terminal_token}}",
            "schema": {
              "type": "string",
              "default": "{{terminal_token}}"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "description": "设备编号",
                    "type": "string",
                    "example": "0013"
                  },
                  "type": {
                    "description": "同步方式：0增量1全量",
                    "type": "string",
                    "example": "1"
                  }
                },
                "required": [
                  "code"
                ]
              },
              "example": {
                "code": "0013",
                "type": "1"
              }
            }
          }
        },
        "responses": {
          "200": {
            "x-apifox-name": "成功",
            "x-apifox-ordering": 0,
            "description": "",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "code",
                    "message",
                    "data",
                    "trace_id"
                  ],
                  "properties": {
                    "code": {
                      "type": "integer",
                      "enum": [
                        0,
                        1
                      ],
                      "description": "0成功，1业务失败。"
                    },
                    "message": {
                      "type": "string"
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "face_data": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer"
                              },
                              "staff_uuid": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "head_img_url": {
                                "type": "string"
                              },
                              "feature": {
                                "type": "string"
                              },
                              "face_token": {
                                "type": "string"
                              },
                              "hardware_uuid": {
                                "type": "string"
                              },
                              "status": {
                                "type": "integer"
                              },
                              "card_id": {
                                "type": "string"
                              }
                            },
                            "x-apifox-orders": [
                              "id",
                              "staff_uuid",
                              "name",
                              "head_img_url",
                              "feature",
                              "face_token",
                              "hardware_uuid",
                              "status",
                              "card_id"
                            ],
                            "x-apifox-ignore-properties": []
                          }
                        },
                        "face_learning_mode": {
                          "type": "integer",
                          "enum": [
                            1,
                            2
                          ],
                          "description": "成功响应：1服务端学习，2设备端学习。"
                        }
                      },
                      "x-apifox-orders": [
                        "face_data",
                        "face_learning_mode"
                      ],
                      "x-apifox-ignore-properties": [],
                      "nullable": true
                    },
                    "trace_id": {
                      "type": "string",
                      "description": "固定返回的请求追踪ID。"
                    }
                  },
                  "x-apifox-orders": [
                    "code",
                    "message",
                    "data",
                    "trace_id"
                  ],
                  "x-apifox-ignore-properties": []
                },
                "examples": {
                  "deviceLearning": {
                    "summary": "模式2：待设备学习的人员",
                    "value": {
                      "code": 0,
                      "message": "查询成功",
                      "data": {
                        "face_data": [
                          {
                            "id": 123,
                            "staff_uuid": "11111111-2222-3333-4444-555555555555",
                            "name": "示例人员",
                            "head_img_url": "http://example.invalid/static/upload/face/example.jpg",
                            "feature": "",
                            "face_token": "",
                            "hardware_uuid": "66666666-7777-8888-9999-000000000000",
                            "status": 1,
                            "card_id": ""
                          }
                        ],
                        "face_learning_mode": 2
                      },
                      "trace_id": "<本次请求追踪ID>"
                    }
                  },
                  "businessError": {
                    "summary": "设备不存在",
                    "value": {
                      "code": 1,
                      "message": "设备不存在",
                      "data": null,
                      "trace_id": "<本次请求追踪ID>"
                    }
                  }
                }
              }
            },
            "headers": {}
          }
        },
        "security": [],
        "x-apifox-status": "developing",
        "x-run-in-apifox": "https://app.apifox.com/web/project/5737553/apis/api-386797026-run"
      }
    },
    "/p/api/facedataLearnResult": {
      "post": {
        "summary": "设备端人脸学习结果回传",
        "deprecated": false,
        "description": "## 接口概述\n\n设备在 `face_learning_mode=2` 时完成本地人脸学习后，把特征值和 token 回传服务端。服务端核对当前人员、照片和关联关系后，更新 `ydy_staff_face` 及对应的 `ydy_hardware_img`。\n\n## 接口信息\n\n| 项目 | 内容 |\n| --- | --- |\n| 接口地址 | `/p/api/facedataLearnResult` |\n| 请求方法 | POST |\n| Content-Type | `application/x-www-form-urlencoded` |\n| 接口版本 | 当前张家港分支；接口路径没有独立版本号 |\n\n## 请求头（Header）\n\n| 参数名 | 是否必填 | 说明 |\n| --- | --- | --- |\n| `Content-Type` | 是 | `application/x-www-form-urlencoded` |\n\n当前代码不校验设备密钥或 `Access-Token`，只使用请求体中的 `code` 确认未删除设备存在；生产环境须依靠内网访问控制。不能照搬其他接口的 `account`、`pwd`、`key` 请求头。\n\n## 请求参数（Body）\n\n| 参数名 | 类型 | 是否必填 | 描述 | 示例值 |\n| --- | --- | --- | --- | --- |\n| `code` | String | 是 | 设备编码，仅校验设备存在，不入库 | `FACE-DEVICE-001` |\n| `staff_uuid` | String | 是 | `/p/api/userdata` 的 `data.face_data[].staff_uuid`；最长 64 字节 | `11111111-2222-3333-4444-555555555555` |\n| `head_img_url` | String | 是 | 原样提交 `/p/api/userdata` 返回的完整照片 URL；最长 1024 字节 | `http://example.invalid/static/upload/face/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.jpg` |\n| `feature` | String | 是 | 设备学习得到的特征值；最多 1048576 字节 | `<特征值占位>` |\n| `baidu_face_token` | String | 是 | 设备学习得到的 token；最多 56 字节 | `<token占位>` |\n\n`feature` 和 `baidu_face_token` 为敏感数据，不要把真实值写入共享文档、聊天记录或问题工单。\n\n## 响应格式\n\n保存成功：\n\n```json\n{\n  \"code\": 0,\n  \"message\": \"人脸学习结果已保存\",\n  \"data\": null,\n  \"trace_id\": \"<本次请求追踪ID>\"\n}\n```\n\n照片在设备学习期间发生更新时：\n\n```json\n{\n  \"code\": 1,\n  \"message\": \"人脸照片已更新，请重新拉取\",\n  \"data\": null,\n  \"trace_id\": \"<本次请求追踪ID>\"\n}\n```\n\n未登记设备时：\n\n```json\n{\n  \"code\": 1,\n  \"message\": \"设备不存在\",\n  \"data\": null,\n  \"trace_id\": \"<本次请求追踪ID>\"\n}\n```\n\n## 状态码说明\n\n| HTTP 状态 | `code` | 描述 | 设备处理 |\n| --- | --- | --- | --- |\n| 通常 200 | `0` | 同一照片的学习结果已保存 | 可继续同步；同一照片、同一结果重复回传可成功。 |\n| 通常 200 | `1` | 业务失败 | 按 `message` 处理；照片已更新或人脸已学习时重新全量拉取，不覆盖本地新数据。 |\n\n可能的失败文案包括：`当前未启用设备端人脸学习`、`参数错误`、`设备不存在`、`人员不存在`、`当前人脸数据不存在或不唯一`、`人脸照片已更新，请重新拉取`、`人脸已学习，请重新拉取`、`人脸照片关联异常`。异常时也可能返回通用的 `人脸学习结果回传失败`。HTTP 200 不等于业务成功。\n\n## 响应字段说明\n\n| 字段名 | 类型 | 描述 |\n| --- | --- | --- |\n| `code` | Integer | `0` 保存成功，`1` 失败。 |\n| `message` | String | 保存结果或失败原因。 |\n| `data` | Null | 当前固定为 `null`。 |\n| `trace_id` | String | 本次请求的追踪 ID，成功与业务失败响应均返回。 |\n\n校验照片 URL 时，服务端为库内相对路径补上站点前缀，再与请求中的完整 `head_img_url` 精确比较；不一致不写入。若已保存不同 token 或 feature，也拒绝覆盖。仅在设备端学习模式下允许回传。\n\n## trace_id 说明\n\n项目自定义 `json()` 响应函数会统一补充 `trace_id`，成功与业务失败均固定返回。排查时优先提供该 ID，并附设备编码、人员 UUID 和调用时间；不要提供真实 `feature` 或 `baidu_face_token`。Docker 内对模式 1 下回传的真实 HTTP 请求已验证错误响应包含该字段。",
        "parameters": [
          {
            "name": "Access-Token",
            "in": "header",
            "description": "",
            "example": "{{terminal_token}}",
            "schema": {
              "type": "string",
              "default": "{{terminal_token}}"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "type": "object",
                "properties": {
                  "code": {
                    "type": "string",
                    "minLength": 1,
                    "description": "设备编码，仅检查设备是否存在，不入库。",
                    "example": "FACE-DEVICE-001"
                  },
                  "head_img_url": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1024,
                    "description": "原样使用 /p/api/userdata 下发的完整 URL。",
                    "example": "http://example.invalid/static/upload/face/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.jpg"
                  },
                  "feature": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 1048576,
                    "description": "设备学习得到的人脸特征值；服务端限制为 1048576 字节。",
                    "example": "<设备学习产生的特征值>"
                  },
                  "baidu_face_token": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 56,
                    "description": "设备学习得到的人脸 token；服务端限制为 56 字节。",
                    "example": "<设备学习产生的人脸token>"
                  },
                  "staff_uuid": {
                    "type": "string",
                    "minLength": 1,
                    "maxLength": 64,
                    "description": "人员 UUID，来自 /p/api/userdata 的 data.face_data[].staff_uuid。",
                    "example": "11111111-2222-3333-4444-555555555555"
                  }
                },
                "required": [
                  "code",
                  "staff_uuid",
                  "head_img_url",
                  "feature",
                  "baidu_face_token"
                ]
              },
              "example": {
                "code": "FACE-DEVICE-001",
                "head_img_url": "http://example.invalid/static/upload/face/aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee.jpg",
                "feature": "<设备学习产生的特征值>",
                "baidu_face_token": "<设备学习产生的人脸token>",
                "staff_uuid": "11111111-2222-3333-4444-555555555555"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "业务结果请以顶层 code 判断。",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "required": [
                    "code",
                    "message",
                    "data",
                    "trace_id"
                  ],
                  "properties": {
                    "code": {
                      "type": "integer",
                      "enum": [
                        0,
                        1
                      ]
                    },
                    "message": {
                      "type": "string"
                    },
                    "trace_id": {
                      "type": "string",
                      "description": "项目 json() 响应函数统一补充的请求追踪 ID。"
                    },
                    "data": {
                      "type": "object",
                      "x-apifox-orders": [],
                      "properties": {},
                      "x-apifox-ignore-properties": [],
                      "nullable": true
                    }
                  },
                  "x-apifox-orders": [
                    "code",
                    "message",
                    "trace_id",
                    "data"
                  ],
                  "x-apifox-ignore-properties": []
                },
                "examples": {
                  "success": {
                    "summary": "学习结果保存成功",
                    "value": {
                      "code": 0,
                      "message": "人脸学习结果已保存",
                      "data": null,
                      "trace_id": "<本次请求追踪ID>"
                    }
                  },
                  "stalePhoto": {
                    "summary": "照片已更新，需重新拉取",
                    "value": {
                      "code": 1,
                      "message": "人脸照片已更新，请重新拉取",
                      "data": null,
                      "trace_id": "<本次请求追踪ID>"
                    }
                  },
                  "missingDevice": {
                    "summary": "设备不存在",
                    "value": {
                      "code": 1,
                      "message": "设备不存在",
                      "data": null,
                      "trace_id": "<本次请求追踪ID>"
                    }
                  }
                }
              }
            },
            "headers": {},
            "x-apifox-name": "成功",
            "x-apifox-ordering": 0
          }
        },
        "security": [],
        "x-apifox-status": "developing",
        "x-run-in-apifox": "https://app.apifox.com/web/project/5737553/apis/api-522738590-run"
      }
    }
  }
}