# 服务端文档 ## 1. 服务端的作用 服务端是整个系统的数据中心。STM32、K210 和 Qt 管理端都通过 HTTP 与它通信。 它负责: 1. 接收 STM32 加密上传的签到记录。 2. 解密、校验并保存记录。 3. 提供考勤记录和统计数据给 Qt。 4. 保存签到时间段配置。 5. 保存学生姓名映射和班级名称映射。 6. 保存 K210 上传的人脸照片。 7. 保存人脸 ID 到真实学号的映射。 8. 维护待删除人脸任务。 9. 维护全局同步版本号。 10. 提供健康检查、存储状态和时间接口。 当前推荐服务端文件: ```text 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 | 班级编号 | 索引: ```sql 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":[]}` | 默认时间段为空 | 可以通过环境变量覆盖: ```powershell $env:ATTENDANCE_BIND_HOST="0.0.0.0" $env:ATTENDANCE_API_TOKEN="你的Token" $env:ATTENDANCE_CORS_ORIGIN="http://localhost" ``` ## 6. 鉴权规则 服务端对大多数接口要求请求头: ```text X-Api-Token: ``` 允许不带 Token 的情况: 1. `OPTIONS` 预检请求。 2. `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/` | `/api/face_photo/` 或 `/api/face_photo_simple/` | 下载/上传照片 | | `/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/` | `/api/face/` | 删除人脸 | | `/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 以下示例默认服务端地址是: ```text http://127.0.0.1:8082 ``` ### 8.1 健康检查 ```text GET /api/v1/system/health ``` 返回示例: ```json { "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 存储状态 ```text GET /api/v1/system/storage ``` 用于检查 SQLite、记录数量、照片数量、待删除任务数量等。 ### 8.3 获取服务器时间 ```text GET /api/v1/system/time ``` 返回: ```json { "epoch": 1777600800, "server_time": "2026-05-01 10:00:00" } ``` STM32 使用 `epoch` 转成 UTC+8 时间。 ### 8.4 加密签到上传 ```text POST /api/v1/attendance/checkins X-Encrypted: AES128-CBC X-Api-Token: ``` 请求体是 Base64 字符串。解密后的 JSON: ```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` | 成功返回: ```json { "success": true, "message": "签到成功", "timestamp": "2026-05-01 10:00:00" } ``` ### 8.5 获取考勤记录 ```text GET /api/v1/attendance/records?limit=1000&offset=0 ``` `limit` 范围是 1 到 5000,`offset` 范围是 0 到 1000000。 返回数组: ```json [ { "timestamp": "2026-05-01 08:10:00", "student_id": "20240001", "name": "张三", "confidence": 0.95, "status": "success", "class_id": "1" } ] ``` 如果请求头带: ```text X-Encrypted-Response: true ``` 服务端会把响应 JSON 加密后以 Base64 返回。Qt 当前普通记录请求没有启用这个请求头。 ### 8.6 获取统计 ```text GET /api/v1/attendance/statistics ``` 返回: ```json { "total": 120, "today": 8, "server_time": "2026-05-01 10:00:00" } ``` ### 8.7 清空记录 ```text POST /api/v1/attendance/records/clear ``` 成功后会删除 `records` 表中的所有记录,并提升同步版本。 ### 8.8 删除单条记录 ```text DELETE /api/v1/attendance/records?timestamp=2026-05-01%2008%3A10%3A00&student_id=20240001 ``` 成功后提升同步版本。 ### 8.9 时间段配置 获取: ```text GET /api/v1/config/time-slots ``` 保存: ```text POST /api/v1/config/time-slots Content-Type: application/json ``` 请求体: ```json { "time_slots": [ { "name": "上午签到", "start_time": "08:00", "end_time": "09:30", "enabled": true } ] } ``` 当前默认值是: ```json { "time_slots": [] } ``` 服务端还包含旧默认四时段的迁移判断:如果数据库里保存的是旧的四个默认时段,会自动改为空数组。 ### 8.10 重复签到检查 ```text GET /api/v1/attendance/duplicates?student_id=20240001&slot_start=08:00&slot_end=09:30 ``` 用于 STM32 在线时补充查重。 ### 8.11 今日某时段已签到记录 ```text GET /api/v1/attendance/today?slot_start=08:00&slot_end=09:30 ``` 返回: ```json { "date": "2026-05-01", "records": ["20240001", "20240002"] } ``` STM32 开机后用它构建本地今日缓存。 ### 8.12 学生姓名和班级名称映射 ```text GET /api/v1/config/mappings POST /api/v1/config/mappings ``` 格式: ```json { "student_name_map": { "20240001": "张三" }, "class_name_map": { "1": "软件工程一班" } } ``` 这个映射主要给 Qt 显示使用,不改变 STM32 签到上传的原始学号。 ### 8.13 人脸照片 照片列表: ```text GET /api/v1/faces/photos ``` 返回: ```json { "photos": [ { "id": "1", "filename": "1.jpg" } ] } ``` 上传照片: ```text POST /api/v1/faces/photos/1 Content-Type: image/jpeg ``` 下载照片: ```text GET /api/v1/faces/photos/1 ``` 成功时返回 `image/jpeg`。 ### 8.14 人脸 ID 映射 ```text GET /api/v1/faces/mappings POST /api/v1/faces/mappings ``` 格式: ```json { "mappings": { "1": "20240001" } } ``` 保存映射后服务端会: 1. 写入 `face_mappings`。 2. 尝试按映射重命名照片。 3. 提升同步版本。 4. 设置 K210 同步请求标志。 ### 8.15 获取全部学生 ID ```text GET /api/v1/faces/student-ids ``` 从 `records` 表中提取所有出现过的学生 ID。 ### 8.16 删除人脸 ```text DELETE /api/v1/faces/20240001 DELETE /api/v1/faces/20240001?delete_records=true ``` 服务端会: 1. 删除相关照片。 2. 删除相关映射。 3. 如果 `delete_records=true`,删除该学生相关考勤记录。 4. 创建 K210 待删除任务。 5. 提升同步版本。 6. 设置同步请求标志。 返回示例: ```json { "success": true, "photo_deleted": true, "mapping_deleted": true, "records_deleted": 3, "k210_pending": true, "sync_requested": true } ``` ## 9. 统一同步接口 ### 9.1 获取版本 ```text GET /api/v1/sync/version ``` 返回: ```json { "version": 6, "server_time": "2026-05-01 10:00:00" } ``` ### 9.2 获取同步状态 ```text GET /api/v1/sync/state?client=&last_version= ``` `client` 可取: | client | 服务端返回内容 | | --- | --- | | `stm32` | `time_slots` | | `k210` | `face_mappings`、`pending_delete`、`delete_tasks` | | `qt` | 记录、统计、时间段、映射、照片列表、学生 ID、人脸映射 | | `all` 或空 | 尽可能返回全部 | 如果没有变化: ```json { "version": 6, "changed": false, "client": "stm32", "server_time": "2026-05-01 10:00:00" } ``` 如果有变化: ```json { "version": 7, "changed": true, "client": "k210", "server_time": "2026-05-01 10:00:00", "state": { "face_mappings": {}, "pending_delete": [], "delete_tasks": [] } } ``` ### 9.3 同步确认 ```text POST /api/v1/sync/ack ``` 请求体: ```json { "client": "k210", "version": 7, "deleted": ["1"], "task_ids": ["task-1"] } ``` 用途: 1. 记录客户端已经确认的版本。 2. 移除已经完成的删除任务。 3. 必要时再次提升同步版本。 ## 10. 编译与运行 ### 10.1 Windows MinGW 示例 如果已经装好 MinGW、OpenSSL、SQLite: ```powershell g++ -o server_encrypted.exe server_encrypted.cpp -lws2_32 -lcrypto -lssl -lsqlite3 -std=c++11 .\server_encrypted.exe ``` ### 10.2 Linux 示例 ```bash g++ -o server_encrypted server_encrypted.cpp -lcrypto -lssl -lsqlite3 -std=c++11 -pthread ./server_encrypted ``` ### 10.3 运行目录建议 建议在 `server/` 目录下运行,这样数据库、照片目录和备份目录都会生成在 `server/` 下面: ```powershell cd D:\University\graduation\code\Reconstruct_Qt\server .\server_encrypted.exe ``` ## 11. 安全注意事项 1. 默认 Token 已写在代码里,不适合直接暴露公网。 2. 当前是 HTTP 明文传输,公网部署建议放到 HTTPS 反向代理后面。 3. AES Key/IV 是固定值,演示可以,生产环境不建议固定写死。 4. SQLite 数据库和照片目录需要定期备份。 5. `ATTENDANCE_CORS_ORIGIN` 不建议设置成任意来源。 6. 服务器运行目录要限制写权限,避免误删数据。 ## 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/` 权限 |