# 服务端文档 ## 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/` | 数据库备份目录 | 服务端数据统一保存在 SQLite 数据库和 `faces/` 照片目录中。 ## 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 到真实学号的映射。服务端同时维护结构化主表,避免把学生、照片和人脸绑定关系混在一个配置 JSON 里。 | 字段 | 含义 | | --- | --- | | `face_id` | K210 本地人脸编号 | | `student_id` | 真实学号 | ### 4.4 `classes` 保存班级基础信息。 | 字段 | 含义 | | --- | --- | | `class_id` | 班级编号 | | `class_name` | 班级名称 | | `updated_at` | 更新时间 | ### 4.5 `students` 保存学生基础信息。 | 字段 | 含义 | | --- | --- | | `student_id` | 学号 | | `student_name` | 姓名 | | `class_id` | 所属班级编号 | | `status` | 学生状态 | | `created_at` | 创建时间 | | `updated_at` | 更新时间 | ### 4.6 `face_profiles` 保存 K210 人脸实体与学生绑定关系。`face_id` 保持为 K210 原始编号,绑定学生时只更新 `student_id`,不改本地特征名。 | 字段 | 含义 | | --- | --- | | `face_id` | K210 原始人脸编号 | | `student_id` | 绑定的真实学号 | | `device_id` | 来源设备 | | `status` | 人脸状态 | | `created_at` | 创建时间 | | `updated_at` | 更新时间 | ### 4.7 `face_photos` 保存人脸照片元数据。照片文件仍保存在 `faces/.jpg`,不会按学号重命名。 | 字段 | 含义 | | --- | --- | | `face_id` | K210 原始人脸编号 | | `file_path` | 服务端照片路径 | | `content_type` | 图片类型 | | `file_size` | 文件大小 | | `uploaded_at` | 上传时间 | ### 4.8 `pending_delete` 保存等待 K210 删除的任务。 | 字段 | 含义 | | --- | --- | | `task_id` | 删除任务 ID | | `face_id` | K210 需要删除的人脸 ID | | `student_id` | 对应学生 ID | | `created_at` | 创建时间 | | `status` | 状态,默认 `pending` | | `ack_at` | 确认时间 | ### 4.9 `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/v1/system/health`。 除健康检查外,所有 `/api/v1/...` 接口都使用同样的鉴权逻辑。 ## 7. 路由规范 服务端接口统一使用 `/api/v1/...` 路径。STM32、K210 和 Qt 管理端都通过这一套接口通信。 ## 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": "张三" }, "student_class_map": { "20240001": "1" }, "class_name_map": { "1": "软件工程一班" } } ``` 其中 `student_name_map` 维护学号到姓名,`student_class_map` 维护学号到班级编号,`class_name_map` 维护班级编号到班级名称。保存后会同步写入 `students` 和 `classes`,Qt 的记录列表、筛选、导出优先使用这里维护的学生班级归属。 ### 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. 同步更新结构化表 `face_profiles` 和 `students`。 3. 对已取消绑定的人脸仅清空 `student_id`,保留原始照片和 `face_id`。 4. 提升同步版本。 5. 设置 K210 同步请求标志。 ### 8.15 获取全部学生 ID ```text GET /api/v1/faces/student-ids ``` 从 `students`、`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\thesis\code\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 中保存了当前配置 | 在 Qt 管理端重新保存时间段,或清理 `attendance.db` 后重新初始化 | | K210 删除不同步 | K210 未拉取同步状态或 ACK 失败 | 看 `/api/v1/sync/state?client=k210&last_version=0` | | 照片上传失败 | 图片超过 512KB、face_id 非法、目录不可写 | 检查服务端日志和 `faces/` 权限 |