24 KiB
电子班牌接口 API 文档
| 项目 | 内容 |
|---|---|
| 控制器 | com.ruoyi.web.controller.api.TrainBoardApiController |
| 源文件 | ruoyi-admin/src/main/java/com/ruoyi/web/controller/api/TrainBoardApiController.java |
| BasePath | /api/board/v1 |
| 接口数量 | 7(4 个查询类 + 1 个签到上报 + 2 个新增能力) |
| 鉴权 | RuoYi Bearer Token(Authorization: Bearer {token}) |
| 文档更新 | 2026-11-03 |
| 编写依据 | 控制器源码逐行核对,与当前实现一致 |
目录
1. 通用约定
1.1 鉴权
- 所有接口均需携带
Authorization: Bearer {token},Token 由平台登录接口签发。 - 未携带或 Token 失效时,返回 HTTP 200 + 响应体
code=401:
{ "code": 401, "msg": "请求访问:/api/board/v1/signageService/selectClassroom,认证失败,无法访问系统资源" }
本控制器未使用
@PreAuthorize数据权限注解,因此不存在403业务场景。
1.2 统一响应结构
所有接口返回 AjaxResult:
{ "code": 200, "msg": "操作成功", "data": {} }
| 字段 | 类型 | 说明 |
|---|---|---|
code |
number | 业务状态码,见 1.3 |
msg |
string | 提示信息;成功固定为 操作成功 |
data |
object / array | 业务数据,无数据时字段不出现 |
1.3 状态码
| code | 含义 | HTTP 状态 | 触发来源 |
|---|---|---|---|
| 200 | 成功 | 200 | 正常返回 |
| 401 | 未认证 / Token 无效 | 200 | AuthenticationEntryPointImpl |
| 404 | 接口路径不存在 | 404 | NoResourceFoundException |
| 500 | 参数缺失、参数格式错误、数据校验失败、控制器内业务拦截、系统异常 | 200 | GlobalExceptionHandler / 控制器返回值 |
| 40080 / 40081 / 40085 / 40086 / 40087 | 考勤业务码(计划不一致 / 课次不存在 / 未报到 / 已放弃 / 已结训) | 200 | ServiceException(AttendanceErrorCode) |
调用方必须判断响应体
code:除 404 外,失败场景的 HTTP 状态码仍是 200。
1.4 列表数据包装
除 3.1 查询教室信息(data 直接是数组)外,其余查询接口的 data 为:
{ "total": 2, "rows": [ ... ] }
| 字段 | 类型 | 说明 |
|---|---|---|
total |
number | 本次返回的记录数(非分页总数) |
rows |
array | 数据列表,无数据时为 [] |
1.5 字段类型说明
类注释约定 id / classId / classroomId / courseId / teacherId / stuId / studentId 为 Long,
但本系统相关表以业务编码为主键(字符串),因此这些字段返回字符串;
仅 lessonId(id)、enrollmentId、zoneId、seatId、capacity、classHour 等为数值。
2. 接口总览
| # | 接口名称 | 方法 | 路径 | 用途 |
|---|---|---|---|---|
| 3.1 | 查询教室信息 | GET | /api/board/v1/signageService/selectClassroom |
返回全部教学教室与实训场地 |
| 3.2 | 查询指定教室当天课程 | GET | /api/board/v1/signageService/coursesInClassroom |
按教室与日期范围返回排课 |
| 3.3 | 查询指定班级在指定教室的座位 | GET | /api/board/v1/signageService/studentInClassroom |
返回座位网格(含学员姓名) |
| 3.4 | 查询班级学员卡号 | GET | /api/board/v1/signageService/studentCard |
刷卡校验用卡号与班级信息 |
| 3.5 | 上报签到数据 | POST | /api/board/v1/signageService/checkin/upload |
JSON 上报签到并落库 |
| 3.6 | 查询本节课签到记录 | GET | /api/board/v1/signageService/checkin/list |
按课次返回签到流水与学员姓名 |
| 3.7 | 查询座位布局 | GET | /api/board/v1/signageService/seatLayout |
仅读 train_class_seat 的座位布局 |
3. 接口详情
3.1 查询教室信息
用途:返回全部教学教室 + 实训场地(train_classroom_info UNION train_training_site),供班牌后台定位终端绑定教室。
| 项 | 内容 |
|---|---|
| HTTP 方法 | GET |
| 请求路径 | /api/board/v1/signageService/selectClassroom |
| 鉴权 | 需要 |
请求参数:无。
请求体:无。
响应结构:data 为数组(不是 {total, rows} 包装)。
| 字段 | 类型 | 说明 |
|---|---|---|
id |
string | 教室ID(class_room_id)或场地ID(site_id) |
name |
string | 教室/场地名称 |
campus |
string | 校区/位置 |
capacity |
number | 容量 |
outside |
number | 0=教室,1=实训场地 |
category |
string | 教室 / 实训场地 |
成功示例
{
"code": 200,
"msg": "操作成功",
"data": [
{ "id": "CR01", "name": "多媒体教室01", "campus": "一号教学楼3层", "capacity": 60, "outside": 0, "category": "教室" },
{ "id": "SITE01", "name": "信号实训场", "campus": "实训楼1层", "capacity": 40, "outside": 1, "category": "实训场地" }
]
}
异常说明:未认证 → code=401;无数据 → data: []。
3.2 查询指定教室当天课程
用途:按教室/场地与日期范围(含首尾日期)返回排课列表。文档约定 beginDate=endDate=当天,接口本身不限制两者相等。
| 项 | 内容 |
|---|---|
| HTTP 方法 | GET |
| 请求路径 | /api/board/v1/signageService/coursesInClassroom |
| 鉴权 | 需要 |
请求参数(Query String)
| 名称 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
classroomId |
string | 是 | 无 | 教室ID(class_room_id)或场地ID(site_id),SQL 同时匹配两列 |
beginDate |
string | 是 | 无 | 开始日期 yyyy-MM-dd,条件 start_time >= beginDate |
endDate |
string | 是 | 无 | 结束日期 yyyy-MM-dd,条件 start_time < endDate + 1 天(含当天) |
请求体:无。
响应结构:data = { total, rows[] }
| 字段 | 类型 | 说明 |
|---|---|---|
id |
number | 排课ID(train_lesson.lesson_id),即 3.5 的 lessonId |
courseId |
string | 课程主数据ID(train_course.c_id) |
classId |
string | 班级ID(train_class_plan.plan_id) |
classroomId |
string | 教室ID或场地ID(COALESCE(class_room_id, site_id)) |
courseName |
string | 课程名称 |
couresName |
string | 兼容旧拼写的课程名称(与 courseName 同值) |
courseTime |
string | 上课时间 yyyy-MM-dd HH:mm:ss |
classHour |
number | 时长(分钟) |
className |
string | 班级名称 |
teacherId |
string | 教师ID(train_lesson.t_id) |
teacherName |
string | 教师姓名,未排教师为 null |
courseIntro |
string | 课程简介(train_course.teaching_objective) |
isPublic |
number | 固定 0 |
成功示例
{
"code": 200,
"msg": "操作成功",
"data": {
"total": 1,
"rows": [
{
"id": 1001, "courseId": "CR01", "classId": "PLAN_A", "classroomId": "CR01",
"courseName": "电子班牌数字化实务", "couresName": "电子班牌数字化实务",
"courseTime": "2026-11-02 09:00:00", "classHour": 90,
"className": "2026年第一期培训班", "teacherId": "T101", "teacherName": "张三",
"courseIntro": "班牌设备安装与配置", "isPublic": 0
}
]
}
}
异常说明
| 场景 | 响应 |
|---|---|
缺少 classroomId |
{"code":500,"msg":"Required request parameter 'classroomId' for method parameter type String is not present"} |
| 无排课 | {"code":200,"msg":"操作成功","data":{"total":0,"rows":[]}} |
3.3 查询班级学员卡号
用途:按班级返回学员卡号与班级、住宿信息,用于班牌刷卡校验。仅返回参训状态非“已放弃(3)”的学员。
| 项 | 内容 |
|---|---|
| HTTP 方法 | GET |
| 请求路径 | /api/board/v1/signageService/studentCard |
| 鉴权 | 需要 |
请求参数(Query String)
| 名称 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
classId |
string | 是 | 无 | 班级ID(train_enrollment.plan_id) |
请求体:无。
响应结构:data = { total, rows[] }
| 字段 | 类型 | 说明 |
|---|---|---|
cardNo |
string | 卡号(train_student.card_no,保留前导零) |
studentId |
string | 学员ID(s_id) |
classId |
string | 班级ID |
studentName |
string | 学员姓名 |
className |
string | 班级名称 |
id |
number | 参训档案ID(enrollment_id,不是学员ID) |
roomId |
string | 宿舍ID,未分配为 null |
bedType |
string | 床位类型(预留,固定 null) |
seatNumber |
string | 床位号,未分配为 null |
成功示例
{
"code": 200,
"msg": "操作成功",
"data": {
"total": 1,
"rows": [
{ "cardNo": "00012345", "studentId": "STU_1", "classId": "PLAN_A", "studentName": "王五", "className": "2026年第一期培训班", "id": 201, "roomId": "DORM-101", "bedType": null, "seatNumber": "1" }
]
}
}
异常说明:缺少 classId → code=500;无数据 → {total:0, rows:[]}。
3.4 上报签到数据
用途:班牌上报学员签到。服务端按 studentId 在课次所属培训班内查询 train_enrollment 得到 enrollmentId,写入 train_student_checkin(student_id、board_id、lesson_id、sign_in_time 一并落库)。
| 项 | 内容 |
|---|---|
| HTTP 方法 | POST |
| 请求路径 | /api/board/v1/signageService/checkin/upload |
| 鉴权 | 需要 |
| Content-Type | 必须 application/json |
| 文件上传 | 无 |
请求参数:无 Query 参数,全部字段在请求体中。
请求体结构(BoardCheckinUploadRequest)
| 名称 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
studentId |
string | 是 | 无 | 学员ID(train_student.s_id),首尾空格会被去除 |
boardId |
string | 否 | null |
门牌(班牌)ID,写入 train_student_checkin.board_id;空串按 null 处理 |
lessonId |
number(long) | 是 | 无 | 课程(课次)ID,对应 train_student_checkin.lesson_id,取值来自 3.2 的 id |
signInTime |
string | 否 | 服务器当前时间 | 签到时间,格式 yyyy-MM-dd HH:mm:ss;格式非法直接报错,不静默改写 |
{
"studentId": "STU_1",
"boardId": "BP001",
"lessonId": 1001,
"signInTime": "2026-11-02 08:30:00"
}
响应结构(data 为单对象)
| 字段 | 类型 | 说明 |
|---|---|---|
checkinId |
number | 考勤记录ID |
studentId |
string | 学员ID(库中 student_id,为空时回填请求值) |
enrollmentId |
number | 参训档案ID(由 studentId + 班次推导) |
lessonId |
number | 课次ID |
boardId |
string | 班牌ID,未传为 null |
signInTime |
string | 实际落库的签到时间 yyyy-MM-dd HH:mm:ss |
成功示例
{
"code": 200,
"msg": "操作成功",
"data": {
"checkinId": 9001, "studentId": "STU_1", "enrollmentId": 201,
"lessonId": 1001, "boardId": "BP001", "signInTime": "2026-11-02 08:30:00"
}
}
异常与错误说明
| 场景 | code | 响应 msg |
|---|---|---|
studentId 为空 |
500 | studentId 不能为空 |
lessonId 为空 |
500 | lessonId 不能为空 |
signInTime 格式错误 |
500 | signInTime 格式不正确,应为 yyyy-MM-dd HH:mm:ss |
| 课次不存在 | 500 | 课程(课次)不存在: 1001 |
| 课次未关联有效培训计划 | 500 | 课程(课次)[1001]未关联有效的培训计划 |
| 学员无该班参训记录 | 500 | 未找到学员[STU_1]在班级[PLAN_A]的参训记录 |
| 非 JSON / 空请求体 | 500 | Spring 反序列化异常信息(如 Content-Type 'text/plain' is not supported) |
| 未认证 | 401 | 请求访问:/api/board/v1/signageService/checkin/upload,认证失败,无法访问系统资源 |
| 学员未报到 / 已放弃 / 已结训 | 40085 / 40086 / 40087 | 对应中文提示(见 AttendanceErrorCode) |
| 课次所属计划与参训计划不一致 | 40080 | 业务不变量校验失败:考勤学员与课次所属培训班不一致 |
业务规则
- 幂等:同一
enrollmentId + lessonId已有签到记录(sign_in_time非空)时返回已有记录,不覆盖首次签到时间。 - 生命周期:仅“已报到(1)”“请假(2)”状态允许签到。
- 计划一致性:学员参训档案所属培训班必须与课次所属培训班一致。
- 打卡方式:固定按设备打卡
checkinWay=1落库。
3.5 查询本节课签到记录
用途:按 lessonId 返回 train_student_checkin 签到流水,并按 studentId 关联 train_student 返回学员姓名(s_name)。
| 项 | 内容 |
|---|---|
| HTTP 方法 | GET |
| 请求路径 | /api/board/v1/signageService/checkin/list |
| 鉴权 | 需要 |
请求参数(Query String)
| 名称 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
lessonId |
number(long) | 是 | 无 | 课程(课次)ID;非数字时返回参数类型不匹配错误 |
请求体:无。
响应结构:data = { total, rows[] },rows[] 为 TrainStudentCheckin:
| 字段 | 类型 | 说明 |
|---|---|---|
checkinId |
number | 考勤记录ID |
enrollmentId |
number | 参训档案ID |
lessonId |
number | 课次ID |
studentId |
string | 学员ID(优先取 train_student_checkin.student_id,为空取参训档案的 student_id) |
studentName |
string | 学员姓名(train_student.s_name) |
planId |
string | 培训班ID |
planName |
string | 培训班名称 |
className |
string | 班级名称 |
courseName |
string | 课程名称 |
boardId |
string | 打卡班牌ID |
boardSn |
string | 班牌序列号 |
boardName |
string | 班牌名称 |
checkinWay |
number | 打卡方式:1 人脸/设备,2 刷卡,3 扫码,4 教师代签 |
signInTime |
string | 签到时间 yyyy-MM-dd HH:mm:ss,未签到为 null |
signOutTime |
string | 签退时间 yyyy-MM-dd HH:mm:ss,未签退为 null |
attendanceStatus |
number | 考勤结论:0 缺勤,1 正常,2 迟到,3 早退,4 请假 |
manualFlag |
number | 是否人工修正:0 否,1 是 |
createBy / createTime / updateBy / updateTime / remark |
string | 审计字段(时间格式 yyyy-MM-dd HH:mm:ss) |
params为空时不输出;searchValue始终不输出。
成功示例
{
"code": 200,
"msg": "操作成功",
"data": {
"total": 1,
"rows": [
{
"checkinId": 9001, "enrollmentId": 201, "lessonId": 1001,
"studentId": "STU_1", "studentName": "张三",
"planId": "PLAN_A", "planName": "2026年第一期培训班", "className": "一班",
"courseName": "电子班牌数字化实务",
"boardId": "BP001", "boardSn": "BP001", "boardName": "1号班牌",
"checkinWay": 1, "signInTime": "2026-11-02 08:30:00", "signOutTime": null,
"attendanceStatus": 1, "manualFlag": 0,
"createBy": "board_device", "createTime": "2026-11-02 08:30:01",
"updateBy": null, "updateTime": null, "remark": null
}
]
}
}
异常与错误说明
| 场景 | 响应 |
|---|---|
缺少 lessonId |
{"code":500,"msg":"Required request parameter 'lessonId' for method parameter type Long is not present"} |
lessonId 非数字 |
{"code":500,"msg":"请求参数类型不匹配,参数[lessonId]要求类型为:'java.lang.Long',但输入值为:'abc'"} |
| 课次无签到记录 | {"code":200,"msg":"操作成功","data":{"total":0,"rows":[]}} |
3.6 查询座位布局
用途:按班级与教室查询座位布局,仅读取 train_class_seat 单表,不关联学员、教室、班级表;不校验班级是否删除、学员是否存在。
| 项 | 内容 |
|---|---|
| HTTP 方法 | GET |
| 请求路径 | /api/board/v1/signageService/seatLayout |
| 鉴权 | 需要 |
请求参数(Query String)
| 名称 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|
planId |
string | 是 | 无 | 班级ID,对应 train_class_seat.plan_id |
classroomId |
string | 是 | 无 | 教室ID,对应 train_class_seat.classroom_id |
请求体:无。
响应结构:data = { total, rows[] }
| 字段 | 类型 | 说明 |
|---|---|---|
seatId |
number | 座位主键(seat_id) |
planId |
string | 班级ID(plan_id) |
classroomId |
string | 教室ID(classroom_id) |
rowIndex |
number | 行号,从 1 开始 |
colIndex |
number | 列号,从 1 开始 |
seatCode |
string | 物理座位标识(seat_code) |
canceled |
number | 0=正常,1=取消位置(不做过滤,原样返回) |
studentId |
string | 占用座位学员ID,空座为 null |
zoneId |
number | 区域ID |
deptDetail |
string | 部门 |
job |
string | 职务 |
排序:row_index, col_index 升序。
成功示例
http://localhost:8096/api/board/v1/signageService/seatLayout?classroomId=afcf21a8bd99488cb19638d285f157a8&planId=f70ec13a651942e8a704ee4cc010c95e
{
"msg": "操作成功",
"code": 200,
"data": {
"total": 2,
"rows": [
{
"canceled": 0,
"classroomId": "afcf21a8bd99488cb19638d285f157a8",
"colIndex": 5,
"deptDetail": null,
"job": null,
"planId": "f70ec13a651942e8a704ee4cc010c95e",
"rowIndex": 2,
"seatCode": "2-5",
"seatId": 29,
"studentId": "c78c425e66384104bf37d150519fc5d3",
"zoneId": null
},
{
"canceled": 0,
"classroomId": "afcf21a8bd99488cb19638d285f157a8",
"colIndex": 9,
"deptDetail": null,
"job": null,
"planId": "f70ec13a651942e8a704ee4cc010c95e",
"rowIndex": 3,
"seatCode": "3-9",
"seatId": 30,
"studentId": "63d889e8be2c4d61900d09b51b2108a5",
"zoneId": null
}
]
}
}
异常与错误说明
| 场景 | 响应 |
|---|---|
缺少 planId 或 classroomId |
code=500,msg 为 Spring 缺参提示 |
| 该班该教室无座位数据 | {"code":200,"msg":"操作成功","data":{"total":0,"rows":[]}} |
表 train_class_seat 不存在 |
code=500,msg 含 Table 'xxx.train_class_seat' doesn't exist(需先执行 sql/train/v2/019_signage.sql) |
4. 异常与错误汇总
| 类型 | 返回形式 | 说明 |
|---|---|---|
| 未认证 | HTTP 200,code=401 |
Token 缺失、过期、伪造 |
| 路径不存在 | HTTP 404,code=404,msg=接口不存在或未启用 |
路由未匹配 |
| 缺少必填 Query 参数 | HTTP 200,code=500 |
Required request parameter 'xxx' ... is not present |
| 参数类型不匹配 | HTTP 200,code=500 |
请求参数类型不匹配,参数[lessonId]要求类型为:'java.lang.Long',但输入值为:'abc' |
| 请求方法不支持 | HTTP 200,code=500 |
Request method 'GET' is not supported |
| 请求体非 JSON | HTTP 200,code=500 |
Content-Type 'xxx' is not supported |
| 控制器内业务拦截 | HTTP 200,code=500 |
见 3.5 异常表 |
| 考勤业务异常 | HTTP 200,code=40080/40081/40085/40086/40087 |
AttendanceErrorCode |
AttendanceErrorCode 常用码:
| code | 含义 |
|---|---|
| 40080 | 考勤学员与课次所属培训班不一致 |
| 40081 | 课次不存在 |
| 40085 | 学员尚未完成开班报到,禁止录入课次考勤 |
| 40086 | 学员已放弃培训 |
| 40087 | 学员已结训 |
5. 调用示例
# 登录获取 token(按平台实际登录接口调整)
TOKEN=$(curl -s -X POST 'http://192.168.106.24:8096/login' \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin123"}' | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
# 3.1 教室信息
curl -s 'http://192.168.106.24:8096/api/board/v1/signageService/selectClassroom' \
-H "Authorization: Bearer $TOKEN"
# 3.2 指定教室当天课程
curl -s 'http://192.168.106.24:8096/api/board/v1/signageService/coursesInClassroom?classroomId=CR01&beginDate=2026-11-02&endDate=2026-11-02' \
-H "Authorization: Bearer $TOKEN"
# 3.3 指定班级在指定教室的座位
curl -s 'http://192.168.106.24:8096/api/board/v1/signageService/studentInClassroom?classroomId=CR01&classId=PLAN_A' \
-H "Authorization: Bearer $TOKEN"
# 3.4 班级学员卡号
curl -s 'http://192.168.106.24:8096/api/board/v1/signageService/studentCard?classId=PLAN_A' \
-H "Authorization: Bearer $TOKEN"
# 3.5 上报签到(JSON 请求体)
curl -s -X POST 'http://192.168.106.24:8096/api/board/v1/signageService/checkin/upload' \
-H 'Content-Type: application/json' -H "Authorization: Bearer $TOKEN" \
-d '{"studentId":"STU_1","boardId":"BP001","lessonId":1001,"signInTime":"2026-11-02 08:30:00"}'
# 3.6 本节课签到记录
curl -s 'http://192.168.106.24:8096/api/board/v1/signageService/checkin/list?lessonId=1001' \
-H "Authorization: Bearer $TOKEN"
# 3.7 座位布局
curl -s 'http://192.168.106.24:8096/api/board/v1/signageService/seatLayout?planId=P2026001&classroomId=CR-001' \
-H "Authorization: Bearer $TOKEN"
6. 附录
6.1 数据来源
| 接口 | 主要数据表 |
|---|---|
| 3.1 | train_classroom_info、train_training_site |
| 3.2 | train_lesson、train_plan_course、train_course、train_class_plan、train_teacher |
| 3.3 | train_class_seat、train_class_plan、train_student |
| 3.4 | train_enrollment、train_student、train_class_plan、train_accommodation、train_dormitory_info |
| 3.5 | train_lesson、train_plan_course、train_enrollment、train_student_checkin |
| 3.6 | train_student_checkin、train_enrollment、train_student、train_class_plan、train_plan_course、train_course、train_electronic_board |
| 3.7 | train_class_seat(单表) |
6.2 Swagger
Swagger UI:/swagger-ui/index.html,分组 电子班牌接口。
若 3.5 的 Request body 显示为 query 参数,说明部署的是旧包(当前实现为 @RequestBody)。
6.3 已知约束
lessonId是课次ID(train_lesson.lesson_id,数值),不是课程编码(c_id,如CR01);班牌须先调用 3.2 获取id。- 3.5 只接受 JSON 请求体,query/form 写法无效。
- 3.5 依赖
train_student_checkin.student_id列;报Unknown column 'a.student_id'时需执行sql/train/v2/020b_add_checkin_student_id_mysql.sql(MySQL)或sql/train/v2/020_add_checkin_student_id.sql(KingbaseES/PostgreSQL)。 - 3.3 与 3.7 的区别:3.3 关联
train_student返回姓名并过滤已删除班级;3.7 严格单表返回,含canceled=1的座位,不做班级有效性校验。 - 所有查询接口的
total为本次返回条数,不是分页总数。