初始化项目文档

This commit is contained in:
2026-05-04 03:08:55 +08:00
commit 1ff2e6af01
5 changed files with 2107 additions and 0 deletions
+675
View File
@@ -0,0 +1,675 @@
# 服务端文档
## 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/` 权限 |