687 lines
28 KiB
Markdown
687 lines
28 KiB
Markdown
# 报到机接口 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` |
|