14 KiB
服务端文档
1. 服务端的作用
服务端是整个系统的数据中心。STM32、K210 和 Qt 管理端都通过 HTTP 与它通信。
它负责:
- 接收 STM32 加密上传的签到记录。
- 解密、校验并保存记录。
- 提供考勤记录和统计数据给 Qt。
- 保存签到时间段配置。
- 保存学生姓名映射和班级名称映射。
- 保存 K210 上传的人脸照片。
- 保存人脸 ID 到真实学号的映射。
- 维护待删除人脸任务。
- 维护全局同步版本号。
- 提供健康检查、存储状态和时间接口。
当前推荐服务端文件:
server/server_encrypted.cpp
2. 技术栈
| 项 | 内容 |
|---|---|
| 语言 | C++ |
| HTTP 实现 | 原生 socket + 每连接一个线程 |
| 数据库 | SQLite |
| 加密库 | OpenSSL EVP AES-128-CBC |
| 图片存储 | 本地 faces/*.jpg |
| 默认端口 | 8082 |
| 默认数据库 | attendance.db |
3. 运行时文件
服务端启动后主要依赖当前工作目录下的文件。在哪个目录运行,就在哪个目录读写这些文件。
| 文件或目录 | 当前用途 |
|---|---|
attendance.db |
主 SQLite 数据库 |
faces/ |
人脸照片目录 |
backups/ |
数据库备份目录 |
attendance_database.csv |
旧版 CSV 记录,首次初始化时可导入 |
timeslots_config.json |
旧版时间段配置,首次初始化时可导入 |
mapping_config.json |
旧版姓名/班级映射,首次初始化时可导入 |
face_id_mapping.json |
旧版人脸映射,首次初始化时可导入 |
pending_delete.json |
旧版待删除列表,首次初始化时可导入 |
sync_request.json |
旧版同步请求标志,首次初始化时可导入 |
sync_version.txt |
旧版同步版本号,首次初始化时可导入 |
首次启动时,如果 SQLite 表中没有数据,服务端会尝试从这些旧文件导入;导入后主要读写 SQLite。
4. SQLite 表结构
服务端启动时会创建这些表:
4.1 records
保存考勤记录。
| 字段 | 类型 | 含义 |
|---|---|---|
id |
INTEGER PRIMARY KEY AUTOINCREMENT | 自增主键 |
timestamp |
TEXT | 签到时间 |
student_id |
TEXT | 学号 |
name |
TEXT | 名称 |
confidence |
TEXT | 置信度 |
status |
TEXT | 状态,例如 success |
class_id |
TEXT | 班级编号 |
索引:
idx_records_student_time(student_id,timestamp)
idx_records_timestamp(timestamp)
4.2 kv_store
保存配置型数据。
| key | value 内容 |
|---|---|
time_slots |
时间段 JSON |
mapping_config |
学生姓名和班级名称映射 |
sync_request |
K210 同步请求标志 |
sync_version |
全局同步版本号 |
4.3 face_mappings
保存 K210 原始 face_id 到真实学号的映射。
| 字段 | 含义 |
|---|---|
face_id |
K210 本地人脸编号 |
student_id |
真实学号 |
4.4 pending_delete
保存等待 K210 删除的任务。
| 字段 | 含义 |
|---|---|
task_id |
删除任务 ID |
face_id |
K210 需要删除的人脸 ID |
student_id |
对应学生 ID |
created_at |
创建时间 |
status |
状态,默认 pending |
ack_at |
确认时间 |
4.5 device_sync_state
保存各客户端同步状态。
| 字段 | 含义 |
|---|---|
client |
客户端名,例如 qt、stm32、k210 |
last_seen_version |
客户端最近看到的服务端版本 |
last_ack_version |
客户端最近确认的版本 |
last_seen_at |
最近访问时间 |
5. 默认配置
| 配置 | 当前值 | 说明 |
|---|---|---|
HTTP_PORT |
8082 |
HTTP 监听端口 |
ATTENDANCE_BIND_HOST |
0.0.0.0 |
默认监听地址,可用环境变量覆盖 |
DEFAULT_API_TOKEN |
atk_nKfVWLhMjL0RGSfRll3R_sdJS6IrIjuV |
默认 API Token |
DEFAULT_CORS_ORIGIN |
http://localhost |
默认 CORS 来源 |
MAX_JSON_BODY_SIZE |
64 KB |
普通 JSON 请求体上限 |
MAX_PHOTO_BODY_SIZE |
512 KB |
照片上传请求体上限 |
DEFAULT_TIMESLOTS_JSON |
{"time_slots":[]} |
默认时间段为空 |
可以通过环境变量覆盖:
$env:ATTENDANCE_BIND_HOST="0.0.0.0"
$env:ATTENDANCE_API_TOKEN="你的Token"
$env:ATTENDANCE_CORS_ORIGIN="http://localhost"
6. 鉴权规则
服务端对大多数接口要求请求头:
X-Api-Token: <Token>
允许不带 Token 的情况:
OPTIONS预检请求。GET /api/health。
新接口和旧接口都使用同样的鉴权逻辑。
7. 路由兼容关系
服务端内部仍有一些旧路径,例如 /api/records、/api/timeslots。为了让接口更清晰,代码提供了 /api/v1/... 新路径,并在服务端内部转换到旧路径。
| 新路径 | 内部兼容路径 | 说明 |
|---|---|---|
/api/v1/system/health |
/api/health |
健康检查 |
/api/v1/system/time |
/api/time |
获取服务器时间 |
/api/v1/attendance/checkins |
/api/checkin |
加密签到上传 |
/api/v1/attendance/records |
/api/records |
获取/删除考勤记录 |
/api/v1/attendance/records/clear |
/api/clear |
清空记录 |
/api/v1/attendance/statistics |
/api/statistics |
获取统计 |
/api/v1/attendance/duplicates |
/api/check_duplicate |
查重 |
/api/v1/attendance/today |
/api/today_records |
今日某时间段记录 |
/api/v1/config/time-slots |
/api/timeslots |
时间段 |
/api/v1/config/mappings |
/api/mapping_config |
学生/班级映射 |
/api/v1/faces/mappings |
/api/face_id_mapping |
人脸映射 |
/api/v1/faces/photos |
/api/face_photos |
照片列表 |
/api/v1/faces/photos/<id> |
/api/face_photo/<id> 或 /api/face_photo_simple/<id> |
下载/上传照片 |
/api/v1/faces/student-ids |
/api/all_student_ids |
全部学生 ID |
/api/v1/faces/pending-delete |
/api/pending_delete |
待删除任务 |
/api/v1/faces/delete-ack |
/api/confirm_delete |
旧式删除确认 |
/api/v1/faces/<id> |
/api/face/<id> |
删除人脸 |
/api/v1/sync/request |
/api/sync_request |
同步请求标志 |
/api/v1/sync/request/ack |
/api/sync_ack |
同步请求确认 |
/api/v1/sync/trigger |
/api/trigger_sync |
触发同步 |
文档推荐使用 /api/v1/...,旧路径主要为了兼容旧代码。
8. 核心 API
以下示例默认服务端地址是:
http://127.0.0.1:8082
8.1 健康检查
GET /api/v1/system/health
返回示例:
{
"status": "healthy",
"time": "2026-05-01 10:00:00",
"storage": "sqlite",
"database": "attendance.db",
"database_ok": true,
"quick_check": "ok",
"sync_version": 1
}
8.2 存储状态
GET /api/v1/system/storage
用于检查 SQLite、记录数量、照片数量、待删除任务数量等。
8.3 获取服务器时间
GET /api/v1/system/time
返回:
{
"epoch": 1777600800,
"server_time": "2026-05-01 10:00:00"
}
STM32 使用 epoch 转成 UTC+8 时间。
8.4 加密签到上传
POST /api/v1/attendance/checkins
X-Encrypted: AES128-CBC
X-Api-Token: <Token>
请求体是 Base64 字符串。解密后的 JSON:
{
"student_id": "20240001",
"name": "张三",
"confidence": "0.95",
"status": "success",
"class_id": "1"
}
字段校验:
| 字段 | 要求 |
|---|---|
student_id |
必填,安全 ID,长度不超过 32 |
name |
可选,长度不超过 64 |
confidence |
必须是 0.0 到 1.0 |
status |
只能是 success、failed、late |
class_id |
可选,安全 ID,长度不超过 32,默认 0 |
成功返回:
{
"success": true,
"message": "签到成功",
"timestamp": "2026-05-01 10:00:00"
}
8.5 获取考勤记录
GET /api/v1/attendance/records?limit=1000&offset=0
limit 范围是 1 到 5000,offset 范围是 0 到 1000000。
返回数组:
[
{
"timestamp": "2026-05-01 08:10:00",
"student_id": "20240001",
"name": "张三",
"confidence": 0.95,
"status": "success",
"class_id": "1"
}
]
如果请求头带:
X-Encrypted-Response: true
服务端会把响应 JSON 加密后以 Base64 返回。Qt 当前普通记录请求没有启用这个请求头。
8.6 获取统计
GET /api/v1/attendance/statistics
返回:
{
"total": 120,
"today": 8,
"server_time": "2026-05-01 10:00:00"
}
8.7 清空记录
POST /api/v1/attendance/records/clear
成功后会删除 records 表中的所有记录,并提升同步版本。
8.8 删除单条记录
DELETE /api/v1/attendance/records?timestamp=2026-05-01%2008%3A10%3A00&student_id=20240001
成功后提升同步版本。
8.9 时间段配置
获取:
GET /api/v1/config/time-slots
保存:
POST /api/v1/config/time-slots
Content-Type: application/json
请求体:
{
"time_slots": [
{
"name": "上午签到",
"start_time": "08:00",
"end_time": "09:30",
"enabled": true
}
]
}
当前默认值是:
{
"time_slots": []
}
服务端还包含旧默认四时段的迁移判断:如果数据库里保存的是旧的四个默认时段,会自动改为空数组。
8.10 重复签到检查
GET /api/v1/attendance/duplicates?student_id=20240001&slot_start=08:00&slot_end=09:30
用于 STM32 在线时补充查重。
8.11 今日某时段已签到记录
GET /api/v1/attendance/today?slot_start=08:00&slot_end=09:30
返回:
{
"date": "2026-05-01",
"records": ["20240001", "20240002"]
}
STM32 开机后用它构建本地今日缓存。
8.12 学生姓名和班级名称映射
GET /api/v1/config/mappings
POST /api/v1/config/mappings
格式:
{
"student_name_map": {
"20240001": "张三"
},
"class_name_map": {
"1": "软件工程一班"
}
}
这个映射主要给 Qt 显示使用,不改变 STM32 签到上传的原始学号。
8.13 人脸照片
照片列表:
GET /api/v1/faces/photos
返回:
{
"photos": [
{
"id": "1",
"filename": "1.jpg"
}
]
}
上传照片:
POST /api/v1/faces/photos/1
Content-Type: image/jpeg
下载照片:
GET /api/v1/faces/photos/1
成功时返回 image/jpeg。
8.14 人脸 ID 映射
GET /api/v1/faces/mappings
POST /api/v1/faces/mappings
格式:
{
"mappings": {
"1": "20240001"
}
}
保存映射后服务端会:
- 写入
face_mappings。 - 尝试按映射重命名照片。
- 提升同步版本。
- 设置 K210 同步请求标志。
8.15 获取全部学生 ID
GET /api/v1/faces/student-ids
从 records 表中提取所有出现过的学生 ID。
8.16 删除人脸
DELETE /api/v1/faces/20240001
DELETE /api/v1/faces/20240001?delete_records=true
服务端会:
- 删除相关照片。
- 删除相关映射。
- 如果
delete_records=true,删除该学生相关考勤记录。 - 创建 K210 待删除任务。
- 提升同步版本。
- 设置同步请求标志。
返回示例:
{
"success": true,
"photo_deleted": true,
"mapping_deleted": true,
"records_deleted": 3,
"k210_pending": true,
"sync_requested": true
}
9. 统一同步接口
9.1 获取版本
GET /api/v1/sync/version
返回:
{
"version": 6,
"server_time": "2026-05-01 10:00:00"
}
9.2 获取同步状态
GET /api/v1/sync/state?client=<client>&last_version=<version>
client 可取:
| client | 服务端返回内容 |
|---|---|
stm32 |
time_slots |
k210 |
face_mappings、pending_delete、delete_tasks |
qt |
记录、统计、时间段、映射、照片列表、学生 ID、人脸映射 |
all 或空 |
尽可能返回全部 |
如果没有变化:
{
"version": 6,
"changed": false,
"client": "stm32",
"server_time": "2026-05-01 10:00:00"
}
如果有变化:
{
"version": 7,
"changed": true,
"client": "k210",
"server_time": "2026-05-01 10:00:00",
"state": {
"face_mappings": {},
"pending_delete": [],
"delete_tasks": []
}
}
9.3 同步确认
POST /api/v1/sync/ack
请求体:
{
"client": "k210",
"version": 7,
"deleted": ["1"],
"task_ids": ["task-1"]
}
用途:
- 记录客户端已经确认的版本。
- 移除已经完成的删除任务。
- 必要时再次提升同步版本。
10. 编译与运行
10.1 Windows MinGW 示例
如果已经装好 MinGW、OpenSSL、SQLite:
g++ -o server_encrypted.exe server_encrypted.cpp -lws2_32 -lcrypto -lssl -lsqlite3 -std=c++11
.\server_encrypted.exe
10.2 Linux 示例
g++ -o server_encrypted server_encrypted.cpp -lcrypto -lssl -lsqlite3 -std=c++11 -pthread
./server_encrypted
10.3 运行目录建议
建议在 server/ 目录下运行,这样数据库、照片目录和备份目录都会生成在 server/ 下面:
cd D:\University\graduation\code\Reconstruct_Qt\server
.\server_encrypted.exe
11. 安全注意事项
- 默认 Token 已写在代码里,不适合直接暴露公网。
- 当前是 HTTP 明文传输,公网部署建议放到 HTTPS 反向代理后面。
- AES Key/IV 是固定值,演示可以,生产环境不建议固定写死。
- SQLite 数据库和照片目录需要定期备份。
ATTENDANCE_CORS_ORIGIN不建议设置成任意来源。- 服务器运行目录要限制写权限,避免误删数据。
12. 常见问题
| 现象 | 可能原因 | 处理 |
|---|---|---|
| 401 Unauthorized | 没带 X-Api-Token 或 Token 不一致 |
检查四端 Token |
| Qt 连接失败 | 服务端没启动、地址错、防火墙拦截 | 访问 /api/v1/system/health |
| STM32 解密失败 | AES Key/IV 不一致或请求体不是 Base64 密文 | 对照三端 AES 参数 |
| 时间段总是旧数据 | SQLite 中已有旧配置 | 当前代码会清理旧默认四时段;自定义旧配置需手动保存为空 |
| K210 删除不同步 | K210 未拉取同步状态或 ACK 失败 | 看 /api/v1/sync/state?client=k210&last_version=0 |
| 照片上传失败 | 图片超过 512KB、face_id 非法、目录不可写 | 检查服务端日志和 faces/ 权限 |