36 KiB
报到机接口文档(TrainRegisterApiController)
服务模块:
ruoyi-admin控制器:com.ruoyi.web.controller.api.TrainRegisterApiController接口前缀:/api/register/v1文档依据:控制器源码(截至 2026-11-09),字段口径以对应 VO / 数据库字段为准 适用对象:报到机(自助终端)厂商、联调与测试人员
目录
1. 通用说明
1.1 请求地址
| 项 | 值 |
|---|---|
| 协议 | HTTP(生产建议由网关承载 HTTPS) |
| Host | 部署服务器地址与端口,例如 http://192.168.1.202:8080 |
| Base Path | /api/register/v1 |
| 完整示例 | http://192.168.1.202:8080/api/register/v1/student/list |
1.2 鉴权与请求头
SecurityConfig中已将/api/register/v1/**配置为permitAll,当前所有接口均无需登录令牌(控制器类注释中提到的 Bearer 鉴权已不再生效)。- 若生产环境在网关/边缘设备层增加了设备签名(如 HMAC)校验,以网关约定为准,不影响本文档的接口定义。
- 请求头:
| 场景 | Header | 说明 |
|---|---|---|
| 所有请求 | Accept: application/json |
可选 |
| POST 请求 | Content-Type: application/json |
必填,请求体为 JSON |
| 文件/人脸 | 无 | 人脸图片以 base64 字符串放在 JSON 请求体内,不使用 multipart |
1.3 统一响应结构
除个别特殊说明外,全部接口返回 AjaxResult:
{
"code": 200,
"msg": "操作成功",
"data": {}
}
| 字段 | 类型 | 说明 |
|---|---|---|
| code | int | 业务状态码,见 1.4 |
| msg | string | 提示信息,成功时为“操作成功”,失败时为错误描述 |
| data | any | 业务数据;失败时可能不存在 |
注意:
data为null时部分序列化配置下该字段可能被省略,客户端应做空值兜底。
1.4 状态码与错误约定
| code | 含义 | 典型场景 |
|---|---|---|
| 200 | 成功 | 正常返回 |
| 500 | 通用业务/参数错误 | 参数缺失、图片解析失败、文件写入失败、业务校验不通过 |
| 601 | 警告 | 预留,本文档接口未使用 |
| 40402 | 学员不存在 | GET /student/{sId} 及依赖学员主档的接口(StudentErrorCode.STUDENT_NOT_FOUND) |
| 401 | 未认证 | 当前 /api/register/v1/** 已放行,正常不会出现 |
重要约定:业务异常由全局异常处理器统一捕获,HTTP 状态码固定为 200,调用方必须依据响应体中的 code 判断成败,不能只看 HTTP 状态。
1.5 时间与命名约定
- 服务端 Jackson 时区为
GMT+8,日期时间统一格式yyyy-MM-dd HH:mm:ss,纯日期格式yyyy-MM-dd。 - JSON 字段以接口定义为准,部分沿用数据库下划线风格(如
dorm_room),属于既有契约,请勿自行转换。 - 学员标识为
sId,教师标识为tId;人脸库中教师人员 ID 统一为tId + "_teacher"。
2. 接口总览
| 序号 | 模块 | 方法 | 路径 | 用途 |
|---|---|---|---|---|
| 1 | 学员信息 | GET | /student/list |
学员主档列表(含报到状态) |
| 2 | 学员信息 | GET | /student/{sId} |
单个学员详情(含账号绑定信息) |
| 3 | 学员信息 | GET | /student/{sId}/base |
学员基础信息(宿舍/教室/开班/场地) |
| 4 | 学员信息 | GET | /student/{sId}/resources |
学员关联资源(在基础信息上增加课程表与课次) |
| 5 | 教师信息 | GET | /teacher/list |
教师主档列表(字段结构同学员列表) |
| 6 | 教师信息 | GET | /teacher/{tId}/base |
教师基础信息(信息/宿舍/授课课表) |
| 7 | 班级 | GET | /class/student |
进行中班级及其未报到学员名单 |
| 8 | 宿舍 | GET | /dormitory/layout |
宿舍布局(楼栋→楼层→房间) |
| 9 | 课表 | GET | /lesson/schedule |
按日期(可选课程类型)查询课程安排 |
| 10 | 人脸 | POST | /student/face |
上传学员人脸并更新头像、补办报到 |
| 11 | 人脸 | POST | /teacher/face |
上传教师人脸并更新头像 |
| 12 | 人脸 | POST | /face |
按 employeeId 后缀自动分发到学员/教师 |
| 13 | 报到管理 | POST | /report/reset |
报到重置(解绑人脸 + 清门锁授权 + 复位参训状态) |
3. 学员信息模块
3.1 获取学员信息列表
用途:报到机启动或刷新时拉取全部学员主档,用于本地人员检索、人脸比对名单准备;每条记录额外补入该学员最新一条报到记录的参训状态与报到时间。
- 方法 / 路径:
GET /api/register/v1/student/list - 请求参数:无
- 请求体:不适用
响应示例
{
"code": 200,
"msg": "操作成功",
"data": [
{
"sId": "S20260001",
"sName": "张三",
"personNo": "P0001",
"sGender": "男",
"sBirth": "1998-05-12",
"deptName": "供电段",
"cardNo": "C0001",
"sPhone": "13800000001",
"sEmail": "zhangsan@example.com",
"idCardMasked": "210***********1234",
"idCardLast4": "1234",
"sourceType": 1,
"status": 1,
"avatar": "/profile/upload/2026/11/09/xxx.png",
"dutyLevel": null,
"id": 1,
"deptId": 103,
"enrollmentStatus": 0,
"joinTime": null
}
]
}
字段说明(StudentItemVO)
| 字段 | 类型 | 说明 |
|---|---|---|
| sId | string | 学员唯一标识(主键引用口径) |
| sName | string | 姓名 |
| personNo | string | 人员编号 |
| sGender | string | 性别 |
| sBirth | string | 出生日期 yyyy-MM-dd |
| deptName | string | 所属部门/车间名称 |
| cardNo | string | 卡号 |
| sPhone / sEmail | string | 手机号 / 邮箱 |
| idCardMasked / idCardLast4 | string | 脱敏身份证 / 后四位(明文身份证不下发) |
| sourceType | int | 学员来源类型 |
| status | int | 档案状态 |
| avatar | string | 头像可访问路径(相对资源前缀) |
| id / deptId | long | 自增编号 / 部门ID |
| enrollmentStatus | int | 参训状态:0待报到、1已报到、2请假、3放弃、4已完成(取最新一条报名记录) |
| joinTime | datetime | 报到时间(取最新一条报名记录,未报到为 null) |
| extractStatus / extractTime / extractMsg / photoMd5 / featureData / featureMd5 / featureAlgoVersion / featureSize | — | 人脸特征提取的内部扩展字段,报到机通常无需使用,多为 null |
错误场景
| 场景 | code | msg |
|---|---|---|
| 数据库异常 | 500 | 系统异常提示 |
| 查询结果为空 | 200 | data: [](空数组,非错误) |
3.2 获取单个学员详情
用途:按 sId 查询单个学员的完整档案,并附带其系统账号绑定状态,供报到机核对身份或人工处理。
- 方法 / 路径:
GET /api/register/v1/student/{sId} - 请求参数(Path)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sId | string | 是 | 学员唯一标识 |
- 请求体:不适用
响应示例
{
"code": 200,
"msg": "操作成功",
"data": {
"sId": "S20260001",
"sName": "张三",
"personNo": "P0001",
"sGender": "男",
"sBirth": "1998-05-12",
"deptName": "供电段",
"cardNo": "C0001",
"sPhone": "13800000001",
"sEmail": "zhangsan@example.com",
"avatar": "/profile/upload/2026/11/09/xxx.png",
"faceRef": null,
"remark": null,
"createBy": "admin",
"createTime": "2026-09-01 09:00:00",
"userId": 105,
"userName": "student01",
"nickName": "张三",
"accountBindingStatus": 1
}
}
字段说明:StudentAdminVO = StudentItemVO + StudentDetailVO + 账号绑定字段
| 字段 | 类型 | 说明 |
|---|---|---|
| (基础字段) | — | 同 3.1 学员列表字段 |
| faceRef | string | 人脸参考标识 |
| remark | string | 备注 |
| createBy / createTime | string / datetime | 建档人 / 建档时间 |
| userId | long | 绑定的系统用户ID(未绑定为 null) |
| userName | string | 绑定的系统登录账号 |
| nickName | string | 绑定的用户昵称 |
| accountBindingStatus | int | 账号关联状态:0未关联、1已关联 |
错误场景
| 场景 | code | msg |
|---|---|---|
| 学员不存在 | 40402 | 学员不存在 |
sId 为空 |
40402 / 500 | 学员不存在(按未命中处理) |
说明:源码中
detail == null分支返回data: null,但底层selectStudentDetail在未命中时会抛业务异常,因此实际表现为code=40402。
3.3 获取学员基础信息
用途:报到机刷脸/刷卡识别学员后,展示该学员的个人基础信息、所属开班、当前宿舍床位、上课教室、实训场地,用于报到确认页。
- 方法 / 路径:
GET /api/register/v1/student/{sId}/base - 请求参数(Path)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sId | string | 是 | 学员唯一标识 |
- 请求体:不适用
- 数据来源:班级优先取该学员最新报到记录(
train_class_report)中的plan_id,缺失时回落到报名关系(train_enrollment)中最新的班级;教室取该班排课课次(train_lesson)中首个带教室的课次,实训场地取首个带场地的课次(两者可能来自不同课次)。
响应示例
{
"code": 200,
"msg": "操作成功",
"data": {
"studInfo": {
"sId": "S20260001",
"sName": "张三",
"sPhone": "13800000001",
"sGender": "男",
"deptName": "供电段",
"personNo": "P0001"
},
"classPlan": {
"planId": "PLAN-2026-01",
"planName": "2026年秋季高铁房建设施高级研修班",
"startTime": "2026-09-15 08:30:00",
"endTime": "2026-09-30 17:30:00"
},
"dormitory": {
"dormId": "DORM-0101",
"bedNo": "2",
"dormBuilding": "1号楼",
"dormRoom": "101"
},
"classroom": {
"classRoomId": "CR-001",
"roomLocation": "实训基地A栋201",
"roomName": "第一理论教室"
},
"trainingSite": {
"siteId": "TS-001",
"siteLocation": "实训基地B栋",
"siteName": "接触网实训场",
"major": "接触网工"
}
}
}
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| studInfo | object | 学员精简信息:sId、sName、sPhone、sGender、deptName、personNo |
| classPlan | object | null | 所属开班:planId、planName、startTime、endTime;未分配班级时为 null |
| dormitory | object | 宿舍:dormId、bedNo(有住宿记录时)、dormBuilding、dormRoom;未分配宿舍时字段可能为 null |
| classroom | object | null | 上课教室:classRoomId、roomLocation、roomName;该班无教室课次时为 null |
| trainingSite | object | null | 实训场地:siteId、siteLocation、siteName、major;该班无场地课次时为 null |
本接口不返回
courses与lesson明细(源码中已注释关闭),需要课表请调用 3.4 或 7.1。
错误场景
| 场景 | code | msg |
|---|---|---|
| 学员不存在 | 40402 | 学员不存在 |
| 学员未分配班级/宿舍 | 200 | 对应字段为 null(不是错误) |
3.4 获取学员关联资源
用途:在 3.3 的基础上额外返回该学员所在班级的课程清单与课次明细,供报到机展示“我的课程 / 我的课表”。
- 方法 / 路径:
GET /api/register/v1/student/{sId}/resources - 请求参数(Path)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sId | string | 是 | 学员唯一标识 |
- 请求体:不适用
响应示例(在 3.3 的 data 基础上增加两个字段)
{
"code": 200,
"msg": "操作成功",
"data": {
"studInfo": { "sId": "S20260001", "sName": "张三", "sPhone": "13800000001", "sGender": "男", "deptName": "供电段", "personNo": "P0001" },
"classPlan": { "planId": "PLAN-2026-01", "planName": "2026年秋季高铁房建设施高级研修班", "startTime": "2026-09-15 08:30:00", "endTime": "2026-09-30 17:30:00" },
"courses": [
{ "cId": "C0001", "cName": "高铁站房巡检" }
],
"lesson": [
{ "startTime": "2026-09-16 09:00:00", "endTime": "2026-09-16 11:30:00", "planCourseId": 1, "cName": "高铁站房巡检" }
],
"dormitory": { "dormId": "DORM-0101", "bedNo": "2", "dormBuilding": "1号楼", "dormRoom": "101" },
"classroom": { "classRoomId": "CR-001", "roomLocation": "实训基地A栋201", "roomName": "第一理论教室" },
"trainingSite": { "siteId": "TS-001", "siteLocation": "实训基地B栋", "siteName": "接触网实训场", "major": "接触网工" }
}
}
新增字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| courses | array | 该班已配置课程:cId(课程ID)、cName(课程名称);无班级时为 null |
| lesson | array | 该班全部课次:startTime、endTime、planCourseId、cName;无课次时为 null 或空数组 |
lesson元素不含教师与场地字段;如需教师/场地/班牌信息,请使用 7.1GET /lesson/schedule。
错误场景
| 场景 | code | msg |
|---|---|---|
| 学员不存在 | 40402 | 学员不存在 |
| 无班级 | 200 | classPlan、courses、lesson 为 null |
4. 教师信息模块
4.1 获取教师信息列表
用途:拉取全部教师主档,字段结构与学员列表完全一致,便于报到机用同一套解析逻辑处理两类人员。
- 方法 / 路径:
GET /api/register/v1/teacher/list - 请求参数:无
- 请求体:不适用
字段映射规则
| 输出字段 | 取值来源 |
|---|---|
| sId | tId + "_teacher"(与人脸库教师 employee_id 口径一致) |
| sName | train_teacher.t_name |
| personNo / sGender / sBirth / deptName / cardNo / sPhone / sEmail / status / avatar / id | train_teacher 对应字段 |
| enrollmentStatus | train_teacher.enrollment_status(0待报到/1已报到/2请假/3放弃/4已完成) |
| joinTime | train_teacher.join_time(未报到为 null) |
响应示例
{
"code": 200,
"msg": "操作成功",
"data": [
{
"sId": "T101_teacher",
"sName": "李老师",
"personNo": "T0001",
"sGender": "男",
"deptName": "供电教研室",
"sPhone": "13900000001",
"status": 1,
"avatar": "/profile/upload/2026/11/09/teacher.png",
"id": 3,
"enrollmentStatus": 1,
"joinTime": "2026-09-14 09:10:00"
}
]
}
错误场景
| 场景 | code | msg |
|---|---|---|
| 无教师数据 | 200 | data: [] |
4.2 获取教师基础信息
用途:教师刷脸后展示教师基本信息、当前宿舍、授课课表。
- 方法 / 路径:
GET /api/register/v1/teacher/{tId}/base - 请求参数(Path)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| tId | string | 是 | 教师工号;支持传入 tId + "_teacher",服务端会自动截去后缀 |
- 请求体:不适用
响应示例
{
"code": 200,
"msg": "操作成功",
"data": {
"teacherInfo": {
"tId": "T101",
"tName": "李老师",
"tPhone": "13900000001",
"tGender": "男",
"deptName": "供电教研室",
"personNo": "T0001",
"avatar": "/profile/upload/2026/11/09/teacher.png"
},
"dormitory": {
"dormId": "DORM-0203",
"bedNo": "1",
"dormBuilding": "2号楼",
"dormRoom": "203"
},
"lesson": [
{
"lessonId": 101,
"planCourseId": 1,
"courseName": "高铁站房巡检",
"tId": "T101",
"teacherName": "李老师",
"startTime": "2026-09-16 09:00:00",
"endTime": "2026-09-16 11:30:00",
"venueType": 1,
"classRoomId": "CR-001",
"classRoomName": "第一理论教室",
"siteId": null,
"siteName": null,
"venueName": "第一理论教室",
"boardSn": "BOARD-SN-001",
"remark": null
}
]
}
}
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| teacherInfo | object | null | 教师不存在时为 null;字段:tId、tName、tPhone、tGender、deptName、personNo、avatar |
| dormitory | object | dormId、bedNo(有住宿时)、dormBuilding、dormRoom |
| lesson | array | 该教师全部授课课次(按开始时间升序),元素为 TimetableLessonItemVO,字段见下表 |
lesson 元素字段(TimetableLessonItemVO)
| 字段 | 类型 | 说明 |
|---|---|---|
| lessonId | long | 课次ID |
| planCourseId | long | 计划课程实例ID |
| courseName | string | 课程名称 |
| tId | string | 教师工号 |
| teacherName | string | 教师姓名 |
| startTime / endTime | datetime | 课次起止时间 |
| venueType | int | 场地类型:1普通教室、2实训场地 |
| classRoomId / classRoomName | string | 教室ID / 名称(venueType=1 时有效) |
| siteId / siteName | string | 实训场地ID / 名称(venueType=2 时有效) |
| venueName | string | 场地聚合展示名(教室名或场地名) |
| boardSn | string | 电子班牌 SN;未唯一绑定该教室时为 null |
| remark | string | 课次备注 |
该接口不按日期过滤,返回教师全部课次。
错误场景
| 场景 | code | msg |
|---|---|---|
| tId 不存在 | 200 | teacherInfo: null、lesson: [] |
| 教师无住宿/无课次 | 200 | 对应字段为空对象/空数组 |
5. 班级模块
5.1 获取进行中班级的未报到学员名单
用途:报到机按班级展示待报到学员,用于现场点名/呼叫报到。
- 方法 / 路径:
GET /api/register/v1/class/student - 请求参数:无
- 请求体:不适用
- 筛选口径:班级
plan_status = 1(进行中);学员enrollment_status = 0(待报到)
响应示例
{
"code": 200,
"msg": "操作成功",
"data": [
{
"planId": "PLAN-2026-01",
"planName": "2026年秋季高铁房建设施高级研修班",
"classId": "CLASS-01",
"className": "房建一班",
"students": [
{ "sId": "S20260001", "sName": "张三", "deptId": 103, "deptName": "供电段" }
]
}
]
}
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| planId / planName | string | 培训班ID / 名称 |
| classId / className | string | 班级ID / 名称 |
| students | array | 未报到学员:sId、sName、deptId、deptName |
容错说明:单个学员主档缺失时不会中断整体返回,改用报名记录(train_enrollment)中的姓名/部门冗余字段兜底,此时 deptId 为 null。
错误场景
| 场景 | code | msg |
|---|---|---|
| 无进行中班级 | 200 | data: [] |
6. 宿舍模块
6.1 获取宿舍布局信息
用途:报到机展示宿舍楼栋—楼层—房间的床位占用情况(如“已分配数/总床位数”)。
- 方法 / 路径:
GET /api/register/v1/dormitory/layout - 请求参数:无
- 请求体:不适用
- 统计口径:已分配数 =
train_accommodation中住宿状态为 0(预分配)、1(已入住)的记录数;总床位 = 房间核定容量capacity
响应示例
{
"code": 200,
"msg": "操作成功",
"data": {
"1号楼": {
"1层": {
"101": { "id": 10, "dorm_room": "101", "accommodation": "2/4" }
},
"未标注楼层": {
"102": { "id": 11, "dorm_room": "102", "accommodation": "0/2" }
}
}
}
}
字段说明
| 层级 | 键 | 说明 |
|---|---|---|
| 一级 | 楼栋名称 | dorm_building,为空的数据会被跳过 |
| 二级 | 楼层 | dorm_floor,为空时固定为 未标注楼层 |
| 三级 | 房间号 | dorm_room |
| 叶子对象 | id | 房间自增编号 |
| dorm_room | 房间号(下划线命名,保持既有契约) | |
| accommodation | 已分配床位数/总床位数 字符串,如 2/4 |
返回使用
LinkedHashMap,键顺序与 SQL 排序(楼栋→楼层→房间号)一致;无宿舍数据时data: {}。
错误场景
| 场景 | code | msg |
|---|---|---|
| 无宿舍数据 | 200 | data: {} |
7. 课表模块
7.1 获取课程安排(按日期 + 课程类型)
用途:查询指定自然日的课程安排,按日期为键分组返回课程名称、课程类型、上课地点、上课教师等信息,供报到机/班牌展示当日课表。
- 方法 / 路径:
GET /api/register/v1/lesson/schedule - 请求参数(Query)
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
| startTime | string | 否 | 当天(北京时间) | 课程开始时间,对应 train_lesson.start_time。支持 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss、yyyy/MM/dd、ISO-8601(T 分隔、Z 结尾、+08:00 偏移)、10 位秒级 / 13 位毫秒级时间戳;服务端取该时间所在自然日 |
| courseType | string | 否 | 不筛选 | 课程类型,对应 train_course.c_type:1 = 理论、2 = 实训。缺省或空串返回当日全部类型;传其它值返回参数错误 |
- 请求体:不适用
- 查询范围:
train_lesson为主表,关联train_plan_course、train_course、train_teacher、train_classroom_info、train_training_site、train_class_plan;日期区间为[当日 00:00:00, 次日 00:00:00),由 SQLSTR_TO_DATE在库内生成(不受应用 JVM 时区影响);培训班为逻辑删除时不参与过滤(课表属于已发生的教学事实)。
请求示例
GET /api/register/v1/lesson/schedule
GET /api/register/v1/lesson/schedule?startTime=2026-11-09
GET /api/register/v1/lesson/schedule?startTime=2026-11-09&courseType=1
GET /api/register/v1/lesson/schedule?startTime=2026-11-09T09:00:00&courseType=2
响应示例
{
"code": 200,
"msg": "操作成功",
"data": {
"2026-11-09": [
{
"id": 101,
"name": "高铁站房巡检",
"courseType": "1",
"venueType": 1,
"startTime": "2026-11-09 09:00:00",
"endTime": "2026-11-09 11:30:00",
"teacher": "李老师",
"classroom": "第一理论教室",
"planName": "2026年秋季高铁房建设施高级研修班"
},
{
"id": 102,
"name": "接触网实操",
"courseType": "2",
"venueType": 2,
"startTime": "2026-11-09 14:00:00",
"endTime": "2026-11-09 16:30:00",
"teacher": "王老师",
"classroom": "接触网实训场",
"planName": "2026年秋季高铁房建设施高级研修班"
}
]
}
}
data 结构说明
| 层级 | 说明 |
|---|---|
| 键 | 日期字符串 yyyy-MM-dd(按课次开始时间的北京时间自然日分组,升序) |
| 值 | 该日课程数组,按 startTime、id 升序 |
课程元素字段(CourseScheduleItemVO)
| 字段 | 类型 | 说明 |
|---|---|---|
| id | long | 课次ID(train_lesson.lesson_id) |
| name | string | 课程名称(train_course.c_name) |
| courseType | string | 课程类型,与 train_course.c_type 一致:1 理论、2 实训;课程缺失或为空时为 null |
| venueType | int | 课次场地类型:1 普通教室、2 实训场地 |
| startTime / endTime | datetime | 课次起止时间 yyyy-MM-dd HH:mm:ss |
| teacher | string | 上课教师姓名(train_teacher.t_name) |
| classroom | string | 上课地点:venueType=1 取教室名称,venueType=2 取实训场地名称 |
| planName | string | 所属培训班名称(train_class_plan.plan_name) |
错误场景
| 场景 | code | msg | 说明 |
|---|---|---|---|
| startTime 格式无法识别 | 500 | startTime 格式不正确,支持 yyyy-MM-dd / yyyy-MM-dd HH:mm:ss / ISO-8601,示例:2026-11-09 |
例如传入 2026年11月9日 |
| courseType 非 1/2 | 500 | courseType 仅支持 1(理论)或 2(实训) |
例如传入 理论、3 |
| 当日无课程 | 200 | data: {} |
空对象,非错误 |
| 该类型当日无课程 | 200 | data: {} |
加了 courseType 后无匹配 |
服务端会输出日志
lessonSchedule 课程安排查询 day=..., courseType=..., rows=...,排障时可直接用rows区分“SQL 未命中”与“命中但未返回”。
8. 人脸模块
8.1 上传学员人脸照片
用途:报到机采集学员人脸 → 转 PNG 落盘 → 更新 train_student.avatar → 同步到人脸识别设备 → 回写报到状态并办理门锁入住(若已分配宿舍)。
- 方法 / 路径:
POST /api/register/v1/student/face - 请求头:
Content-Type: application/json
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| sId | string | 是 | 学员唯一标识,同时作为人脸库 employee_id |
| faceImage | string | 是 | base64 人脸图片,可带 data:image/png;base64, 前缀;服务端统一转码为 PNG |
{
"sId": "S20260001",
"faceImage": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..."
}
业务副作用(按顺序)
- 图片解码并保存到
profile/upload/yyyy/MM/dd/{uuid}.png,路径写入train_student.avatar - 调用人脸设备新增/更新人员(
employee_id = sId) - 回查该学员报名记录,将第一条记录的参训状态置为 1(已报到)并记录报到时间
- 若存在有效住宿记录,调用门禁入住接口(
/openapi/iot/stay/checkin)下发入住授权
响应示例
{
"code": 200,
"msg": "操作成功",
"data": {
"sId": "S20260001",
"avatar": "/profile/upload/2026/11/09/9f1c....png",
"faceSync": "success",
"lockCheckin": { "code": 200, "msg": "入住成功" },
"lockAuthId": "",
"joinTime": "2026-11-09 09:12:33"
}
}
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| sId | string | 学员标识 |
| avatar | string | 头像可访问路径 |
| faceSync | string | 人脸设备同步结果:success、failed、skipped |
| faceMsg | string | 仅 faceSync=failed 时出现,失败原因(设备无响应/异常信息) |
| lockCheckin | object | 门锁入住接口返回结果;无有效住宿时不返回 |
| lockAuthId | string | 预留字段,固定空串 |
| joinTime | datetime | 实际报到时间;无住宿时可能不返回 |
| (设备扩展字段) | any | 设备成功时其返回的 data 会被并入本层 |
错误场景
| 场景 | code | msg |
|---|---|---|
| 缺少 sId 或 faceImage | 500 | 参数缺失:sId 与 faceImage 均为必填 |
| sId 为空串 | 500 | sId 不能为空 |
| 图片非法/无法解码 | 500 | 图片解析失败:{具体原因} |
| 目录创建失败 | 500 | 创建存储目录失败 |
| 文件写入失败 | 500 | 图片写入失败:{具体原因} |
| 学员不存在或更新 0 行 | 500 | 学员不存在或更新失败 |
| 设备同步失败 | 200 | 仍返回成功,faceSync=failed + faceMsg(头像已保存) |
8.2 上传教师人脸照片
用途:教师人脸采集与入库,更新 train_teacher.avatar,并同步人脸设备(employee_id = tId + "_teacher")。
- 方法 / 路径:
POST /api/register/v1/teacher/face - 请求头:
Content-Type: application/json
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| teacherId | string | 是 | 教师工号(train_teacher.t_id),不要带 _teacher 后缀 |
| faceImage | string | 是 | base64 人脸图片,可带 data URI 前缀 |
{
"teacherId": "T101",
"faceImage": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..."
}
业务差异:教师无开班报名记录,不做报到状态更新;仅当教师已有有效住宿且已生成门锁流水号时,补发一次入住授权(有效期 15 天)。
响应示例
{
"code": 200,
"msg": "操作成功",
"data": {
"tId": "T101",
"avatar": "/profile/upload/2026/11/09/ab34....png",
"faceSync": "success",
"lockCheckin": { "code": 200, "msg": "入住成功" },
"lockAuthId": ""
}
}
字段说明:tId、avatar、faceSync、faceMsg(失败时)、lockCheckin(有住宿且门锁可用时)、lockAuthId(固定空串)、设备扩展字段合并规则同上。
错误场景
| 场景 | code | msg |
|---|---|---|
| 缺少 teacherId 或 faceImage | 500 | 参数缺失:teacherId 与 faceImage 均为必填 |
| teacherId 为空串 | 500 | teacherId 不能为空 |
| 图片非法/无法解码 | 500 | 图片解析失败:{具体原因} |
| 目录/文件写入失败 | 500 | 创建存储目录失败 / 图片写入失败:{具体原因} |
| 教师不存在或更新 0 行 | 500 | 教师不存在或更新失败 |
| 设备同步失败 | 200 | faceSync=failed + faceMsg(头像已保存) |
8.3 上传人脸照片(按员工标识自动分发)
用途:统一入口。按 employeeId 后缀自动路由,设备侧无需区分学员/教师接口。
- 方法 / 路径:
POST /api/register/v1/face - 请求头:
Content-Type: application/json
请求体
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| employeeId | string | 是 | 人脸库人员ID。以 _teacher 结尾 → 视为教师(去掉后缀即 tId);其它 → 视为学员 sId |
| faceImage | string | 是 | base64 人脸图片 |
{
"employeeId": "T101_teacher",
"faceImage": "data:image/jpeg;base64,/9j/4AAQSkZJRgABAQ..."
}
分发规则
| 判定 | 路由 | 说明 |
|---|---|---|
employeeId 以 _teacher 结尾 |
→ 8.2 教师人脸上传 | tId = employeeId 去掉后缀;后缀匹配区分大小写、不做 trim |
| 其它 | → 8.1 学员人脸上传 | sId = employeeId |
响应:直接透传被调用接口(8.1 / 8.2)的响应体,结构与之一致。
错误场景
| 场景 | code | msg |
|---|---|---|
| 缺少 employeeId 或 faceImage | 500 | 参数缺失:employeeId 与 faceImage 均为必填 |
| employeeId 为空串 | 500 | employeeId 不能为空 |
employeeId 仅为 _teacher(工号为空) |
500 | employeeId 非法:教师工号(tId)不能为空 |
| 后续流程失败 | 500 / 200 | 同 8.1 / 8.2 |
9. 报到管理模块
9.1 报到重置
用途:误点他人信息导致人脸错绑时的补救接口。按顺序执行:解绑人脸 → 清除门锁入住授权 → 参训状态复位为 0(待报到) → 清空学员头像。宿舍分配记录保留,不做退宿。
- 方法 / 路径:
POST /api/register/v1/report/reset - 请求头:
Content-Type: application/x-www-form-urlencoded(参数通过 Query 传递,无请求体)
请求参数(Query)
| 参数名 | 类型 | 必填 | 说明 |
|---|---|---|---|
| planId | string | 是 | 培训班ID(train_enrollment.plan_id) |
| studentId | string | 是 | 学员ID(train_student.s_id) |
请求示例
POST /api/register/v1/report/reset?planId=PLAN-2026-01&studentId=S20260001
响应示例(成功)
{
"code": 200,
"msg": "报到重置成功",
"data": {
"planId": "PLAN-2026-01",
"studentId": "S20260001",
"enrollmentId": 900001,
"previousStatus": 1,
"faceUnbind": "success",
"lockCheckout": "success",
"lockApiBizNo": "LOCK20261109001",
"currentStatus": 0,
"alreadyReset": false,
"accommodationKept": true,
"avatarCleared": true,
"avatarSId": "S20260001"
}
}
字段说明
| 字段 | 类型 | 说明 |
|---|---|---|
| planId / studentId | string | 入参回显 |
| enrollmentId | long | 参训记录ID |
| previousStatus | int | 重置前参训状态 |
| faceUnbind | string | success(人脸解绑成功) |
| lockCheckout | string | success,或 skipped: 无有效住宿记录 / skipped: 缺少门锁业务流水号与宿舍信息 |
| lockApiBizNo | string | 门锁业务流水号(仅实际操作门锁时返回) |
| currentStatus | int | 重置后状态,固定 0 |
| alreadyReset | boolean | 重置前是否已是 0(重复调用标识) |
| accommodationKept | boolean | 是否保留了住宿分配 |
| avatarCleared | boolean | 学员头像是否已清空 |
| avatarSId | string | 被清空头像的学员标识 |
分步失败场景(返回 code=500,msg 指明失败步骤)
| 场景 | msg |
|---|---|
| 缺少 planId / studentId 参数 | Spring 参数缺失提示(Required request parameter 'planId' ...) |
| planId 或 studentId 为空串 | planId 和 studentId 不能为空 |
| 报名记录不存在 | 未找到学员[{studentId}]在班级[{planId}]的参训记录 |
| 学员已放弃培训(状态3) | 学员已放弃培训,不允许重置报到状态 |
| 学员已结训(状态4) | 学员已结训,不允许重置报到状态 |
| 步骤1 人脸解绑失败 | 步骤1 人脸解绑失败:{原因};参训状态未变更 |
| 步骤2 门锁响应解析失败 | 步骤2 门锁授权清除响应解析失败:{原因};人脸已解绑,参训状态未变更 |
| 步骤2 门锁清除失败 | 步骤2 门锁授权清除失败:{原因};人脸已解绑,参训状态未变更 |
| 步骤3 状态复位失败 | 步骤3 参训状态重置失败;人脸与门锁授权已解除,请人工核对后重试 |
| 学员档案缺失(前 3 步已完成) | 报到重置已完成,但未找到有效学员档案[{sId}],avatar 未清空(失败响应中仍带 data) |
| 清空头像异常(前 3 步已完成) | 报到重置已完成,但清空学员头像失败:{原因}(失败响应中仍带 data) |
该接口为非幂等敏感操作,重复调用会对已复位的数据再次执行解绑/清库动作,建议设备端加二次确认。
10. 附录
10.1 枚举值速查
| 字段 | 取值 |
|---|---|
参训/报到状态 enrollmentStatus |
0 待报到、1 已报到、2 请假、3 放弃、4 已完成 |
场地类型 venueType |
1 普通教室、2 实训场地 |
课程类型 courseType(train_course.c_type) |
1 理论、2 实训 |
账号关联状态 accountBindingStatus |
0 未关联、1 已关联 |
人脸同步状态 faceSync |
success / failed / skipped |
房间占用 accommodation |
字符串 已分配数/总床位数,如 2/4 |
10.2 关联数据表
| 模块 | 主要数据表 |
|---|---|
| 学员 | train_student、train_enrollment |
| 教师 | train_teacher |
| 开班 / 课程 | train_class_plan、train_plan_course、train_course |
| 排课 | train_lesson、train_classroom_info、train_training_site、train_electronic_board |
| 宿舍 | train_dormitory_info、train_accommodation |
| 报到 | train_class_report |
| 人脸/门锁 | 人脸识别服务、/openapi/iot/stay/checkin 门禁开放接口 |
文档结束。接口变更请同步更新本文件,并在提交说明中标注版本。