Files
bishe-docs/03_服务端文档.md
T
2026-05-04 03:08:55 +08:00

676 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 服务端文档
## 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/<face_id>.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>
```
允许不带 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: <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=<client>&last_version=<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/` 权限 |