# 报到机接口 API 文档 | 项目 | 内容 | |---|---| | 控制器 | `com.ruoyi.web.controller.api.TrainRegisterApiController` | | 源文件 | `ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TrainRegisterApiController.java` | | BasePath | `/api/register/v1` | | 接口数量 | 12(8 个 GET + 4 个 POST) | | 鉴权 | Spring Security 已放行 `/api/register/v1/**`(`permitAll`);仅 `/report/reset` 内部取登录用户,需携带 RuoYi Bearer Token | | 统一响应 | `AjaxResult`(`code` / `msg` / `data`) | | 日期格式 | `yyyy-MM-dd HH:mm:ss`(`spring.jackson.date-format`,时区 GMT+8) | | 文档更新 | 2026-11-09(新增宿舍布局接口) | | 编写依据 | 控制器源码逐行核对,与当前实现一致 | --- ## 目录 - [1. 通用约定](#1-通用约定) - [1.1 鉴权说明](#11-鉴权说明) - [1.2 统一响应结构](#12-统一响应结构) - [1.3 状态码与错误提示](#13-状态码与错误提示) - [1.4 公共数据结构](#14-公共数据结构) - [2. 接口总览](#2-接口总览) - [3. 学员信息](#3-学员信息) - [3.1 获取学员信息列表](#31-获取学员信息列表) - [3.2 获取学员详情](#32-获取学员详情) - [3.3 获取学员基础信息](#33-获取学员基础信息) - [3.4 获取学员关联资源](#34-获取学员关联资源) - [4. 教师信息](#4-教师信息) - [4.1 获取教师信息列表](#41-获取教师信息列表) - [4.2 获取教师基础信息](#42-获取教师基础信息) - [5. 班级名单](#5-班级名单) - [5.1 获取进行中班级的学员名单](#51-获取进行中班级的学员名单) - [6. 人脸照片上传](#6-人脸照片上传) - [6.1 上传学员人脸照片](#61-上传学员人脸照片) - [6.2 上传教师人脸照片](#62-上传教师人脸照片) - [6.3 上传人脸照片(统一入口)](#63-上传人脸照片统一入口) - [7. 报到业务](#7-报到业务) - [7.1 报到重置](#71-报到重置) - [8. 宿舍布局](#8-宿舍布局) - [8.1 获取宿舍布局信息](#81-获取宿舍布局信息) - [9. 附录](#9-附录) - [9.1 典型调用流程](#91-典型调用流程) - [9.2 调用示例](#92-调用示例) --- ## 1. 通用约定 ### 1.1 鉴权说明 - 源代码类注释声明“其余接口复用 RuoYi Bearer 鉴权”,但当前 `SecurityConfig` 中已配置: ```110:110:ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java .requestMatchers("/api/register/v1/**").permitAll(); ``` 即 **整个 `/api/register/v1/**` 路径实际为匿名可访问**,查询类与人脸上传接口不强制 Token。 - 例外:`POST /report/reset` 内部通过 `SecurityUtils.getUsername()` 写入操作人,无登录态时该方法会抛 `ServiceException("获取用户账户异常", 401)`。因此该接口**必须携带** `Authorization: Bearer {token}`。 - 控制器注释提到的 `X-Signature` / `X-Nonce`(HMAC)验签目前**未在本控制器内实现**,`DeviceHmacFilter` 仅对 `/api/iot/v1/device/login` 生效。 ### 1.2 统一响应结构 所有接口返回 `AjaxResult`: ```json { "code": 200, "msg": "操作成功", "data": {} } ``` | 字段 | 类型 | 说明 | |---|---|---| | `code` | number | 业务状态码,见 1.3 | | `msg` | string | 提示信息;成功时通常为 `操作成功` | | `data` | object / array | 业务数据,无数据时该字段不出现 | ### 1.3 状态码与错误提示 | code | 含义 | 触发来源 | |---|---|---| | 200 | 成功 | 正常返回 | | 401 | 未认证 / 无登录上下文 | `SecurityUtils` 抛出的 `ServiceException` | | 500 | 参数缺失、图片解析失败、目标记录不存在等业务失败 | 控制器内 `AjaxResult.error(...)` | > 除框架级 401/404 外,业务失败统一为 `code=500`,调用方需读取 `msg` 区分原因。 ### 1.4 公共数据结构 #### StudentItemVO(学员列表项,`/student/list`) | 字段 | 类型 | 说明 | |---|---|---| | `sId` | string | 学员ID(业务主键) | | `sName` | string | 学员姓名 | | `personNo` | string | 人员编号 | | `sGender` | string | 性别 | | `sBirth` | string | 出生日期,`yyyy-MM-dd` | | `deptName` | string | 所属部门名称 | | `cardNo` | string | 卡号 | | `sPhone` | string | 手机号 | | `sEmail` | string | 邮箱 | | `idCardMasked` | string | 证件号掩码 | | `idCardLast4` | string | 证件号后 4 位 | | `sourceType` | number | 数据来源类型 | | `status` | number | 状态 | | `avatar` | string | 头像访问路径(如 `/profile/upload/...png`) | | `dutyLevel` | number | 职务级别 | | `extractStatus` / `extractTime` / `extractMsg` | string / string / string | 人脸特征提取状态、时间、描述 | | `photoMd5` | string | 人脸照片 MD5 | | `featureData` / `featureMd5` / `featureAlgoVersion` / `featureSize` | string / string / string / number | 人脸特征数据及元信息 | | `id` | number | 主键 | | `deptId` | number | 部门ID | | `enrollmentStatus` | number | 报到状态:0 待报到、1 已报到、2 请假、3 放弃、4 已完成 | | `joinTime` | string | 报到/入学时间 | #### 门锁入住结果 lockCheckin(`LockStayCheckinResult`) | 字段 | 类型 | 说明 | |---|---|---| | `success` | boolean | 是否成功 | | `status` | string | 处理状态 | | `bizNo` | string | 门锁业务流水号 | | `httpStatus` | number | 下游 HTTP 状态 | | `code` | string | 下游业务码 | | `message` | string | 下游提示信息 | | `rawBody` | string | 下游原始响应体 | --- ## 2. 接口总览 | 序号 | 分组 | 方法 | 路径 | 功能说明 | |---|---|---|---|---| | 1 | 学员信息 | GET | `/api/register/v1/student/list` | 获取全部学员列表(含报到状态) | | 2 | 学员信息 | GET | `/api/register/v1/student/{sId}` | 获取单个学员详情(含账号绑定信息) | | 3 | 学员信息 | GET | `/api/register/v1/student/{sId}/base` | 获取学员基础信息(个人信息/开班/宿舍/教室/实训场地) | | 4 | 学员信息 | GET | `/api/register/v1/student/{sId}/resources` | 获取学员关联资源(基础信息 + 课程 + 课表) | | 5 | 教师信息 | GET | `/api/register/v1/teacher/list` | 获取全部教师列表(结构同学员列表) | | 6 | 教师信息 | GET | `/api/register/v1/teacher/{tId}/base` | 获取教师基础信息(个人信息/宿舍/授课课表) | | 7 | 班级名单 | GET | `/api/register/v1/class/student` | 获取进行中班级及其未报到学员名单 | | 8 | 人脸照片 | POST | `/api/register/v1/student/face` | 上传学员人脸照片并同步人脸设备、办理报到 | | 9 | 人脸照片 | POST | `/api/register/v1/teacher/face` | 上传教师人脸照片并同步人脸设备 | | 10 | 人脸照片 | POST | `/api/register/v1/face` | 按 `employeeId` 后缀统一分发到学员/教师上传 | | 11 | 报到业务 | POST | `/api/register/v1/report/reset` | 报到重置(解绑人脸、清门锁授权、状态回退) | | 12 | 宿舍布局 | GET | `/api/register/v1/dormitory/layout` | 按楼栋/楼层/房间返回宿舍布局及已分配床位数 | --- ## 3. 学员信息 ### 3.1 获取学员信息列表 - **接口路径**:`/api/register/v1/student/list` - **请求方法**:`GET` - **功能说明**:读取学员主档列表(`train_student`),并按 `sId` 反查 `train_enrollment` 报名记录,用第一条记录的报到状态与报到时间补齐 `enrollmentStatus`、`joinTime`。 **请求参数**:无(无分页、无过滤条件,返回全量) **返回结果说明**: - `data`:`StudentItemVO` 数组,字段见 [1.4](#14-公共数据结构)。 - 学员无报名记录时,`enrollmentStatus` 与 `joinTime` 为 `null`。 ```json { "code": 200, "msg": "操作成功", "data": [ { "sId": "S0001", "sName": "张三", "personNo": "P1001", "sGender": "1", "sBirth": "2000-01-01", "deptName": "供电段", "sPhone": "13800000000", "status": 1, "avatar": "/profile/upload/2026/11/05/xxxx.png", "enrollmentStatus": 1, "joinTime": "2026-11-05 09:12:33" } ] } ``` --- ### 3.2 获取学员详情 - **接口路径**:`/api/register/v1/student/{sId}` - **请求方法**:`GET` - **功能说明**:按学员ID查询详情,并在基础字段上补充账号绑定信息(`userId`、`userName`、`nickName`、`accountBindingStatus`)。 **请求参数** | 名称 | 位置 | 类型 | 必填 | 说明 | |---|---|---|---|---| | `sId` | path | string | 是 | 学员ID,如 `S0001` | **返回结果说明**: - `data` 为 `StudentAdminVO`,继承 `StudentDetailVO` ← `StudentItemVO`。 - 在 [1.4 StudentItemVO](#14-公共数据结构) 字段基础上,追加: | 字段 | 类型 | 说明 | |---|---|---| | `faceRef` | string | 人脸引用 | | `remark` | string | 备注 | | `createBy` / `createTime` | string / string | 创建人 / 创建时间 | | `userId` | number | 绑定的系统用户ID | | `userName` | string | 登录账号 | | `nickName` | string | 用户昵称 | | `accountBindingStatus` | number | 账号绑定状态:1 已绑定,0 未绑定 | - 学员不存在时返回 `{"code":200,"msg":"操作成功","data":null}`。 --- ### 3.3 获取学员基础信息 - **接口路径**:`/api/register/v1/student/{sId}/base` - **请求方法**:`GET` - **功能说明**:按 `sId` 聚合该学员的个人信息、所属开班计划、宿舍、教室、实训场地,供报到机首屏展示。开班 planId 取值优先取最新报到记录,缺失时从报名关系兜底。 **请求参数** | 名称 | 位置 | 类型 | 必填 | 说明 | |---|---|---|---|---| | `sId` | path | string | 是 | 学员ID | **返回结果说明**:`data` 为对象(字段可能为 `null`): | 字段 | 类型 | 说明 | |---|---|---| | `studInfo` | object | 学员基础信息:`sId`、`sName`、`sPhone`、`sGender`、`deptName`、`personNo` | | `classPlan` | object | 开班信息:`planId`、`planName`、`startTime`、`endTime` | | `dormitory` | object | 宿舍信息:`dormId`、`bedNo`、`dormBuilding`、`dormRoom` | | `classroom` | object | 教室信息(取自本计划首个带教室的课次):`classRoomId`、`roomLocation`、`roomName` | | `trainingSite` | object | 实训场地信息(取自本计划首个带场地的课次):`siteId`、`siteLocation`、`siteName`、`major` | ```json { "code": 200, "msg": "操作成功", "data": { "studInfo": { "sId": "S0001", "sName": "张三", "sPhone": "13800000000", "sGender": "1", "deptName": "供电段", "personNo": "P1001" }, "classPlan": { "planId": "P2026001", "planName": "2026年第一期", "startTime": "2026-11-01 08:00:00", "endTime": "2026-11-30 18:00:00" }, "dormitory": { "dormId": "D01", "bedNo": "3", "dormBuilding": "1号楼", "dormRoom": "101" }, "classroom": { "classRoomId": "R01", "roomLocation": "教学楼2层", "roomName": "201" }, "trainingSite": { "siteId": "T01", "siteLocation": "实训楼1层", "siteName": "接触网实训场", "major": "接触网" } } } ``` > 注意:本接口**不返回** `courses` 与 `lesson`(源码中已注释),需要课表请使用 3.4。 --- ### 3.4 获取学员关联资源 - **接口路径**:`/api/register/v1/student/{sId}/resources` - **请求方法**:`GET` - **功能说明**:在 3.3 的基础上额外返回本计划课程列表与课次(含课程名),供报到机展示课表。 **请求参数** | 名称 | 位置 | 类型 | 必填 | 说明 | |---|---|---|---|---| | `sId` | path | string | 是 | 学员ID | **返回结果说明**:`data` 在 3.3 全部字段之外,追加: | 字段 | 类型 | 说明 | |---|---|---| | `courses` | array | 本计划课程:`cId`(计划课程主键)、`cName`(课程名称) | | `lesson` | array | 课次列表:`startTime`、`endTime`、`planCourseId`、`cName` | ```json { "code": 200, "msg": "操作成功", "data": { "studInfo": { "sId": "S0001", "sName": "张三" }, "classPlan": { "planId": "P2026001", "planName": "2026年第一期" }, "courses": [ { "cId": 1001, "cName": "接触网巡视" } ], "lesson": [ { "startTime": "2026-11-06 08:00:00", "endTime": "2026-11-06 10:00:00", "planCourseId": 1001, "cName": "接触网巡视" } ], "dormitory": { "dormId": "D01", "bedNo": "3" }, "classroom": { "classRoomId": "R01", "roomName": "201" }, "trainingSite": { "siteId": "T01", "siteName": "接触网实训场" } } } ``` --- ## 4. 教师信息 ### 4.1 获取教师信息列表 - **接口路径**:`/api/register/v1/teacher/list` - **请求方法**:`GET` - **功能说明**:读取教师主档(`train_teacher`),按**与学员列表完全一致的结构**返回,便于报到机统一处理。转换规则: - `sId` = `tId + "_teacher"`(与人脸库教师 `employee_id` 口径一致) - `sName` = `tName`,其余字段一一映射 - 教师无报名记录,`enrollmentStatus`、`joinTime` 恒为 `null` **请求参数**:无 **返回结果说明**:`data` 为 `StudentItemVO` 数组,实际填充字段:`sId`、`sName`、`personNo`、`sGender`、`sBirth`、`deptName`、`cardNo`、`sPhone`、`sEmail`、`status`、`avatar`、`id`。 ```json { "code": 200, "msg": "操作成功", "data": [ { "sId": "T0001_teacher", "sName": "李老师", "personNo": "P2001", "sGender": "1", "sPhone": "13900000000", "deptName": "供电段", "status": 1, "avatar": null, "enrollmentStatus": null, "joinTime": null } ] } ``` --- ### 4.2 获取教师基础信息 - **接口路径**:`/api/register/v1/teacher/{tId}/base` - **请求方法**:`GET` - **功能说明**:按教师工号聚合个人信息、宿舍信息与授课课表。兼容传入 `tId + "_teacher"` 口径:路径参数以 `_teacher` 结尾时会自动去除后缀再查询。 **请求参数** | 名称 | 位置 | 类型 | 必填 | 说明 | |---|---|---|---|---| | `tId` | path | string | 是 | 教师工号;可传 `T0001` 或 `T0001_teacher` | **返回结果说明**: | 字段 | 类型 | 说明 | |---|---|---| | `teacherInfo` | object | 教师信息:`tId`、`tName`、`tPhone`、`tGender`、`deptName`、`personNo`、`avatar`;教师不存在时为 `null` | | `dormitory` | object | `dormId`、`bedNo`、`dormBuilding`、`dormRoom`(无有效住宿时 `dormId` 为 `null`) | | `lesson` | array | 授课课次(按开始时间升序,不传日期范围即全部);字段:`lessonId`、`planCourseId`、`courseName`、`tId`、`teacherName`、`startTime`、`endTime`、`venueType`、`classRoomId`、`classRoomName`、`siteId`、`siteName`、`venueName`、`boardSn`、`remark` | ```json { "code": 200, "msg": "操作成功", "data": { "teacherInfo": { "tId": "T0001", "tName": "李老师", "tPhone": "13900000000", "tGender": "1", "deptName": "供电段", "personNo": "P2001", "avatar": null }, "dormitory": { "dormId": "D02", "bedNo": "1", "dormBuilding": "2号楼", "dormRoom": "205" }, "lesson": [ { "lessonId": 9001, "courseName": "接触网巡视", "tId": "T0001", "teacherName": "李老师", "startTime": "2026-11-06 08:00:00", "endTime": "2026-11-06 10:00:00", "classRoomId": "R01", "classRoomName": "201", "venueName": "教学楼201" } ] } } ``` --- ## 5. 班级名单 ### 5.1 获取进行中班级的学员名单 - **接口路径**:`/api/register/v1/class/student` - **请求方法**:`GET` - **功能说明**:查询所有**进行中**(`planStatus = 1`)的开班计划,并按计划取报名记录中**未报到**(`enrollmentStatus = 0`)的学员,学员列表作为班级属性 `students` 一并返回。单个学员主档缺失时用报名记录的冗余字段兜底,不会中断整体返回。 **请求参数**:无 **返回结果说明**:`data` 为班级数组: | 字段 | 类型 | 说明 | |---|---|---| | `planId` | string | 开班计划ID | | `planName` | string | 开班计划名称 | | `classId` | string | 班级ID | | `className` | string | 班级名称 | | `students` | array | 未报到学员:`sId`、`sName`、`deptId`、`deptName` | ```json { "code": 200, "msg": "操作成功", "data": [ { "planId": "P2026001", "planName": "2026年第一期", "classId": "C01", "className": "接触网一班", "students": [ { "sId": "S0001", "sName": "张三", "deptId": 103, "deptName": "供电段" } ] } ] } ``` --- ## 6. 人脸照片上传 > 三个接口均为 `POST` + `application/json`,`faceImage` 支持**纯 base64** 或 **data URI**(`data:image/png;base64,....`)两种形式;服务端统一解码并转码为 PNG 落盘,路径形如 `/profile/upload/yyyy/MM/dd/{uuid}.png`,随后写回对应主档的 `avatar` 字段。 ### 6.1 上传学员人脸照片 - **接口路径**:`/api/register/v1/student/face` - **请求方法**:`POST` - **功能说明**:保存学员人脸照片到 `train_student.avatar`,同步人脸识别设备(`employee_id = sId`),并按住宿记录办理报到: 1. 更新报名记录 `enrollmentStatus = 1`、`joinTime = 当前时间`(存在报名记录时); 2. 若学员有有效住宿记录(按 `create_time` 降序首条且 `dormId` 非空),生成门锁业务流水号并调用入住接口 `/openapi/iot/stay/checkin`。 **请求参数**(JSON Body) | 名称 | 类型 | 必填 | 说明 | |---|---|---|---| | `sId` | string | 是 | 学员ID | | `faceImage` | string | 是 | 人脸图片 base64,可带 `data:image/xxx;base64,` 前缀 | **返回结果说明**:`data` 字段: | 字段 | 类型 | 说明 | |---|---|---| | `sId` | string | 学员ID | | `avatar` | string | 头像访问路径 | | `faceSync` | string | 设备同步状态:`success` / `failed` / `skipped` | | `faceMsg` | string | 同步失败原因(仅失败时存在) | | `faceData` | any | 设备返回的非 Map 数据(成功时可能追加) | | `lockCheckin` | object | 门锁入住结果,结构见 [1.4 lockCheckin](#14-公共数据结构)(有住宿时返回) | | `lockAuthId` | string | 门锁授权ID,当前固定为空串(有住宿时返回) | | `joinTime` | string | 报到时间(有住宿时返回) | **失败场景**(`code=500`): | msg | 触发条件 | |---|---| | `参数缺失:sId 与 faceImage 均为必填` | 任一参数为 `null` | | `sId 不能为空` | `sId` 为空串 | | `图片解析失败:...` | base64 非法 / 非图片 / 无法编码为 PNG | | `创建存储目录失败` | 落盘目录创建失败 | | `图片写入失败:...` | 文件写入 IO 异常 | | `学员不存在或更新失败` | 更新 `train_student` 影响行数为 0 | > 人脸设备同步失败不影响接口成功返回,仅通过 `faceSync=failed` + `faceMsg` 体现。 --- ### 6.2 上传教师人脸照片 - **接口路径**:`/api/register/v1/teacher/face` - **请求方法**:`POST` - **功能说明**:保存教师人脸照片到 `train_teacher.avatar`,以 `employee_id = tId + "_teacher"` 同步人脸识别设备。教师无报名记录,**不处理报到状态**;若教师已有有效住宿且已生成门锁流水号,则补发一次入住授权(有效期 15 天)。 **请求参数**(JSON Body) | 名称 | 类型 | 必填 | 说明 | |---|---|---|---| | `teacherId` | string | 是 | 教师工号(`train_teacher.t_id`),不含 `_teacher` 后缀 | | `faceImage` | string | 是 | 人脸图片 base64,可带 data URI 前缀 | **返回结果说明**:`data` 字段: | 字段 | 类型 | 说明 | |---|---|---| | `tId` | string | 教师工号 | | `avatar` | string | 头像访问路径 | | `faceSync` | string | 设备同步状态:`success` / `failed` / `skipped` | | `faceMsg` | string | 同步失败原因(仅失败时存在) | | `lockCheckin` | object | 补发入住授权结果(教师有住宿且已生成 `lockApiBizNo` 时返回) | | `lockAuthId` | string | 当前固定为空串(补发授权时返回) | **失败场景**(`code=500`): | msg | 触发条件 | |---|---| | `参数缺失:teacherId 与 faceImage 均为必填` | 任一参数为 `null` | | `teacherId 不能为空` | `teacherId` 为空串 | | `图片解析失败:...` / `创建存储目录失败` / `图片写入失败:...` | 同 6.1 | | `教师不存在或更新失败` | 更新 `train_teacher` 影响行数为 0 | --- ### 6.3 上传人脸照片(统一入口) - **接口路径**:`/api/register/v1/face` - **请求方法**:`POST` - **功能说明**:按 `employeeId` 后缀分发到 6.1 / 6.2,返回被调用方法的原样结果: - `employeeId` 以 `_teacher` 结尾 → 去掉后缀作为 `teacherId`,转 6.2; - 其他情况 → `employeeId` 即 `sId`,转 6.1。 - 后缀匹配**区分大小写**且不做 `trim`(`_TEACHER` 会被当作学员ID)。 **请求参数**(JSON Body) | 名称 | 类型 | 必填 | 说明 | |---|---|---|---| | `employeeId` | string | 是 | 人脸库人员ID:学员 `sId` 或教师 `tId + "_teacher"` | | `faceImage` | string | 是 | 人脸图片 base64,可带 data URI 前缀 | **返回结果说明**:与分发目标接口完全一致(见 6.1 / 6.2 的 `data` 说明)。 **失败场景**(`code=500`): | msg | 触发条件 | |---|---| | `参数缺失:employeeId 与 faceImage 均为必填` | 任一参数为 `null` | | `employeeId 不能为空` | `employeeId` 为空串 | | `employeeId 非法:教师工号(tId)不能为空` | `employeeId` 仅由 `_teacher` 组成 | --- ## 7. 报到业务 ### 7.1 报到重置 - **接口路径**:`/api/register/v1/report/reset` - **请求方法**:`POST` - **功能说明**:用于“误点他人信息导致人脸错绑”的补救。按顺序执行 4 步,任一关键步骤失败即中止并返回原因: 1. 解绑人脸(复用 `TrainFaceApiController.deleteEmployee`,`employee_id = studentId`); 2. 清除门锁入住授权(复用 `TrainLockApiController.checkout`),**住宿分配记录保留**; 3. 参训状态重置为未报到 `0`(备注保持不变); 4. 清空学员头像(`train_student.avatar` 置 NULL)。 **请求参数**(Query / Form,`@RequestParam`,非 JSON Body) | 名称 | 类型 | 必填 | 说明 | |---|---|---|---| | `planId` | string | 是 | 开班计划ID(`train_enrollment.plan_id`) | | `studentId` | string | 是 | 学员ID(`train_enrollment.student_id`) | **返回结果说明**:`data` 字段: | 字段 | 类型 | 说明 | |---|---|---| | `planId` | string | 开班计划ID | | `studentId` | string | 学员ID | | `enrollmentId` | number | 参训记录主键 | | `previousStatus` | number | 重置前的报到状态 | | `faceUnbind` | string | 人脸解绑结果,成功为 `success` | | `lockCheckout` | string | 门锁授权清除结果:`success` / `skipped: 无有效住宿记录` / `skipped: 缺少门锁业务流水号与宿舍信息` | | `lockApiBizNo` | string | 门锁业务流水号(清除成功时返回) | | `currentStatus` | number | 重置后状态,固定 `0` | | `alreadyReset` | boolean | 重置前是否已是未报到 | | `accommodationKept` | boolean | 是否保留了住宿记录 | | `avatarCleared` | boolean | 头像是否已清空(成功路径) | | `avatarSId` | string | 清空头像的学员ID(成功路径) | **失败场景**(`code=500`): | msg | 触发条件 | |---|---| | `planId 和 studentId 不能为空` | 任一参数为空 | | `未找到学员[xxx]在班级[yyy]的参训记录` | 报名记录不存在 | | `学员已放弃培训,不允许重置报到状态` | `previousStatus = 3` | | `学员已结训,不允许重置报到状态` | `previousStatus = 4` | | `步骤1 人脸解绑失败:...;参训状态未变更` | 人脸解绑未返回 200 | | `步骤2 门锁授权清除响应解析失败:...;人脸已解绑,参训状态未变更` | 下游响应非法 JSON | | `步骤2 门锁授权清除失败:...;人脸已解绑,参训状态未变更` | 下游返回非 200 | | `步骤3 参训状态重置失败;人脸与门锁授权已解除,请人工核对后重试` | 状态更新影响行数为 0 | | `报到重置已完成,但未找到有效学员档案[xxx],avatar 未清空` | 主档缺失(`data` 仍返回已完成信息) | | `报到重置已完成,但清空学员头像失败:...` | 头像更新异常(`data` 仍返回已完成信息) | ```json { "code": 200, "msg": "报到重置成功", "data": { "planId": "P2026001", "studentId": "S0001", "enrollmentId": 5001, "previousStatus": 1, "faceUnbind": "success", "lockCheckout": "success", "lockApiBizNo": "STAY20261105001", "currentStatus": 0, "alreadyReset": false, "accommodationKept": true, "avatarCleared": true, "avatarSId": "S0001" } } ``` --- ## 8. 宿舍布局 ### 8.1 获取宿舍布局信息 - **接口路径**:`/api/register/v1/dormitory/layout` - **请求方法**:`GET` - **功能说明**:查询 `train_dormitory_info`(仅未删除房间),依次按 `dorm_building`(楼栋)→ `dorm_floor`(楼层)→ `dorm_room`(房间号)分组;并左关联 `train_accommodation` 统计每个房间的已分配床位数(住宿状态 `0 预分配` / `1 已入住` 计入,`2 已退宿` 不计),总床位数取房间核定容量 `capacity`。 **请求参数**:无 **返回结果说明**: - `data` 为三层嵌套对象,键依次为「楼栋名称 → 楼层 → 房间号」,每间房的值包含: | 字段 | 类型 | 说明 | |---|---|---| | `id` | number | 宿舍自增编号(`train_dormitory_info.id`) | | `dorm_room` | string | 房间号 | | `accommodation` | string | 已分配床位数/总床位数,如 `"2/4"` | - 楼栋、楼层、房间均按数据库升序排列(`dorm_building, dorm_floor, dorm_room`),嵌套对象使用有序 Map 输出,顺序稳定。 - 无房间数据时 `data` 为空对象 `{}`。 ```json { "code": 200, "msg": "操作成功", "data": { "1号楼": { "1": { "101": { "id": 1, "dorm_room": "101", "accommodation": "2/4" }, "102": { "id": 2, "dorm_room": "102", "accommodation": "0/4" } }, "2": { "201": { "id": 5, "dorm_room": "201", "accommodation": "4/6" } } }, "2号楼": { "1": { "101": { "id": 9, "dorm_room": "101", "accommodation": "1/2" } } } } } ``` > 说明:同一楼栋内房间号唯一(约束 `uq_dorm_building_room`),因此以房间号作为楼层下的键不会冲突;楼层为空时键为 `未标注楼层`。 --- ## 9. 附录 ### 9.1 典型调用流程 1. `GET /student/list` 或 `GET /class/student` —— 报到机展示待报到人员。 2. `GET /teacher/list` —— 展示教师名单(`sId` 形如 `T0001_teacher`)。 3. 人员点击后:`GET /student/{sId}/resources`(或 `/base`)—— 展示开班、宿舍、教室、课表。 4. 采集人脸后:`POST /face`(统一入口,传 `employeeId` + `faceImage`)—— 完成照片入库、设备下发、报到登记、门锁入住。 5. 宿舍看板:`GET /dormitory/layout` —— 按楼栋/楼层/房间展示床位占用情况。 6. 误绑时:`POST /report/reset?planId=...&studentId=...` —— 回退报到与人脸/门锁授权。 ### 9.2 调用示例 ```bash # 学员列表 curl "http://localhost:8096/api/register/v1/student/list" # 学员关联资源 curl "http://localhost:8096/api/register/v1/student/S0001/resources" # 教师基础信息(tId 可带 _teacher 后缀) curl "http://localhost:8096/api/register/v1/teacher/T0001_teacher/base" # 统一人脸上传 curl -X POST "http://localhost:8096/api/register/v1/face" \ -H "Content-Type: application/json" \ -d '{"employeeId":"S0001","faceImage":"data:image/jpeg;base64,/9j/4AAQSkZJRg..."}' # 报到重置(需登录态) curl -X POST "http://localhost:8096/api/register/v1/report/reset?planId=P2026001&studentId=S0001" \ -H "Authorization: Bearer {token}" # 宿舍布局 curl "http://localhost:8096/api/register/v1/dormitory/layout" ``` ### 9.3 与源码注释的差异说明 | 位置 | 注释/声明 | 实际实现 | |---|---|---| | 类注释 | 登录接口需 HMAC 签名(`X-Signature`/`X-Nonce`) | 控制器内未实现;`DeviceHmacFilter` 仅覆盖 `/api/iot/v1/device/login` | | `SecurityConfig` 注释 | `/api/register/v1/**` 走 Bearer 鉴权 | 已配置 `permitAll`,除 `/report/reset` 外不校验 Token | | `/student/{sId}/base` | 注释称返回“课表、实训场地” | 实际不返回 `courses`、`lesson`,需调用 `/student/{sId}/resources` |