初始化项目文档

This commit is contained in:
2026-05-04 03:08:55 +08:00
commit 1ff2e6af01
5 changed files with 2107 additions and 0 deletions
+346
View File
@@ -0,0 +1,346 @@
# 人脸识别考勤系统项目总文档
## 1. 这是什么项目
本项目是一个多端协同的人脸识别考勤系统。它不是只有一个程序,而是由四个主要部分组成:
| 部分 | 所在目录 | 主要职责 | 可以把它理解成 |
| --- | --- | --- | --- |
| STM32 固件端 | `stm32/` | 接收 K210 识别结果、判断是否允许签到、加密上传记录、控制 OLED/蜂鸣器/指示灯/补光灯 | 考勤机主控板 |
| K210 识别端 | `k210/` | 摄像头采集、人脸检测、人脸特征比对、添加/删除本地人脸、上传人脸照片、同步人脸映射 | 摄像头和 AI 识别模块 |
| C++ 服务端 | `server/` | 提供 HTTP API、保存考勤记录、保存配置、管理照片和同步状态 | 中心服务器 |
| Qt 管理端 | `QT/` | Windows/Android 图形管理界面,用于查看记录、统计、配置时间段、管理映射和照片 | 管理后台 |
整个系统的目标是:
1. K210 看到人脸并识别出一个人。
2. K210 通过串口把识别结果发给 STM32。
3. STM32 判断当前是否在允许签到的时间段内,并检查是否重复签到。
4. 如果允许签到,STM32 将签到数据用 AES-128-CBC 加密后通过 ESP8266 发给服务端。
5. 服务端保存数据,并提供给 Qt 管理端查看、统计、导出和管理。
6. Qt 管理端修改的配置会同步给服务端,再由 STM32 或 K210 拉取。
## 2. 新手先理解几个概念
### 2.1 人脸 ID 和学号不是同一个东西
K210 本地添加人脸时,会生成一个原始人脸编号,例如 `ID:1`、`ID:2`。这个编号只表示 K210 本地第几个录入的人脸。
真实考勤需要的是学号,例如 `20240001`。所以项目里有一个“人脸 ID 到学号”的映射:
```json
{
"mappings": {
"1": "20240001",
"2": "20240002"
}
}
```
含义是:K210 本地人脸 `1` 对应真实学号 `20240001`。
### 2.2 签到时间段默认是空的
系统默认不创建“上午签到”“中午签退”“下午签到”“晚上签退”等固定时间段。新部署时:
```json
{
"time_slots": []
}
```
如果没有配置时间段,STM32 会认为当前不在任何考勤时段内,识别到人脸也不会允许签到。需要在 Qt 管理端“配置中心”里新增时间段并保存同步。
### 2.3 服务端是数据中心
当前服务端以 SQLite 数据库为主,数据库文件是:
```text
attendance.db
```
服务端还会使用以下本地资源:
| 文件或目录 | 作用 |
| --- | --- |
| `attendance.db` | 主数据库,保存记录、配置、映射、同步状态 |
| `faces/` | 人脸照片目录,保存 K210 上传的 `.jpg` |
| `backups/` | 数据库备份目录 |
服务端数据以 SQLite 为准,考勤记录、配置、映射和同步状态都保存在 `attendance.db` 中。
### 2.4 同步版本号是什么
服务端维护一个全局同步版本号 `sync_version`。当考勤记录、时间段、映射、人脸照片、删除任务等数据发生变化时,服务端会把版本号加一。
设备同步时会带上自己上次同步到的版本:
```text
GET /api/v1/sync/state?client=stm32&last_version=3
```
如果服务端当前版本还是 `3`,会返回 `changed:false`。如果服务端当前版本是 `4` 或更高,会返回 `changed:true` 并带上该设备需要的数据。
这样做的好处是:设备不用不停下载所有数据,只要版本没变就不用重新处理。
## 3. 总体架构
```mermaid
flowchart LR
Camera["摄像头"] --> K210["K210 识别端"]
K210 -->|"UART1: ID, NAME, CONF"| STM32["STM32 主控"]
STM32 -->|"USART1 AT 指令"| ESP1["ESP8266"]
ESP1 -->|"HTTP + AES 签到"| Server["C++ 服务端 8082"]
K210 -->|"UART2 AT 指令"| ESP2["K210 侧 ESP8266"]
ESP2 -->|"HTTP 上传照片/同步映射"| Server
Qt["Qt 管理端 Windows/Android"] -->|"HTTP API"| Server
Server --> SQLite["attendance.db"]
Server --> Faces["faces/*.jpg"]
```
## 4. 数据流说明
### 4.1 正常签到流程
1. K210 摄像头采集画面。
2. K210 使用三个 KPU 模型执行人脸检测、关键点定位和特征提取。
3. K210 将当前人脸特征与 SD 卡中的 `recordftr.txt` 做比对。
4. 如果分数大于阈值,K210 认为识别成功。
5. K210 通过 UART1 发送给 STM32:
```text
ID:20240001,NAME:20240001,CONF:0.95
```
6. STM32 解析串口数据。
7. STM32 检查置信度是否超过 `0.75`。
8. STM32 检查当前时间是否落在已启用的时间段内。
9. STM32 检查本地缓存、今日记录缓存和服务端查重结果,避免同一个学生在同一时段重复签到。
10. STM32 生成 JSON:
```json
{
"student_id": "20240001",
"name": "20240001",
"confidence": 0.95,
"status": "success",
"class_id": "1"
}
```
11. STM32 使用 AES-128-CBC 加密并做 Base64 编码。
12. STM32 通过 ESP8266 发送:
```text
POST /api/v1/attendance/checkins
X-Api-Token: ...
X-Encrypted: AES128-CBC
```
13. 服务端解密、校验字段、写入 SQLite。
14. Qt 管理端刷新后可以看到新记录。
### 4.2 新增人脸流程
1. 在 K210 正常识别界面短按按键,进入添加人脸模式。
2. 摄像头检测到陌生人脸后,再短按确认。
3. K210 将人脸特征保存到 SD 卡 `recordftr.txt`。
4. K210 将对应照片保存到 `/sd/face_photos/<face_id>.jpg`。
5. K210 将照片 ID 放入上传队列。
6. 网络任务空闲时,K210 通过 ESP8266 上传照片到服务端:
```text
POST /api/v1/faces/photos/<face_id>
```
7. Qt 管理端进入“人脸管理”,刷新后可以看到照片。
8. 管理员为该 face_id 填写真实学号并保存映射。
9. 服务端保存映射并触发同步。
10. K210 拉取同步状态后更新本地 `face_mapping.txt`,识别发送时把 `face_id` 转成真实学号,`recordftr.txt` 中的原始 `ID:<face_id>` 保持不变。
### 4.3 删除人脸流程
1. Qt 管理端删除某个学生或人脸。
2. 服务端删除照片、映射,按需要删除考勤记录。
3. 服务端写入待删除任务 `pending_delete`,并提升同步版本。
4. K210 定期检查同步请求或拉取统一同步状态。
5. K210 在本地删除对应特征和照片。
6. K210 向服务端发送 ACK:
```text
POST /api/v1/sync/ack
```
7. 服务端移除待删除任务。
## 5. 目录结构
```text
code/
├── docs/ # 项目文档
├── server/
│ ├── server_encrypted.cpp # C++ HTTP 服务端,当前推荐版本
├── QT/
│ ├── CMakeLists.txt # Qt 客户端构建配置
│ ├── clear_qt_cache.ps1 # 清理 Qt Creator/CMake 项目缓存
│ ├── main.cpp # Qt 程序入口
│ ├── qml/Main.qml # Qt 主界面
│ ├── src/ # Qt C++ 业务逻辑、网络客户端、模型
│ ├── android/ # Android 打包配置和图标
│ └── assets/ # 桌面端图标
├── k210/
│ └── main.py # K210 主程序
└── stm32/
├── User/
│ └── main.c # STM32 主循环
├── Hardware/ # STM32 外设驱动与业务模块
├── System/ # STM32 延时、调试等基础模块
├── Library/ # STM32 标准外设库
├── Start/ # 启动文件和芯片头文件
├── Project.uvprojx # Keil 工程文件
├── Project.uvoptx # Keil 选项文件
└── 连接图.md # 硬件接线表
```
## 6. 默认配置总览
| 配置项 | 当前值 | 所在位置 |
| --- | --- | --- |
| 服务端端口 | `8082` | `server/server_encrypted.cpp` |
| 服务端默认 API Token | `atk_nKfVWLhMjL0RGSfRll3R_sdJS6IrIjuV` | 服务端、Qt、STM32、K210 |
| 服务端默认 CORS 来源 | `http://localhost` | `server/server_encrypted.cpp` |
| STM32 WiFi SSID | `yzfh` | `stm32/User/main.c` |
| STM32 WiFi 密码 | `yzfhroot` | `stm32/User/main.c` |
| STM32 服务端 IP | `27.106.113.205` | `stm32/User/main.c` |
| STM32 班级编号 | `1` | `stm32/User/main.c` |
| K210 WiFi SSID | `yzfh` | `k210/main.py` |
| K210 WiFi 密码 | `yzfhroot` | `k210/main.py` |
| K210 服务端 IP | `27.106.113.205` | `k210/main.py` |
| Qt 默认服务端地址 | `http://127.0.0.1:8082` | `QT/src/ApiClient.cpp`、`QT/src/AppController.cpp` |
| 默认签到时间段 | 空数组 | Qt、服务端、STM32 |
| AES 算法 | AES-128-CBC + PKCS#7 + Base64 | `stm32/Hardware/Crypto.c`、`server/server_encrypted.cpp`、`QT/src/CryptoUtils.cpp` |
注意:这些配置里包含真实网络信息和默认 Token。如果要部署到公网,建议改成自己的值,并用环境变量覆盖服务端和 Qt 的 Token。
## 7. 硬件连接概览
完整连接表在 `stm32/连接图.md`。常用连接如下:
| 模块 | STM32 引脚 | 功能 |
| --- | --- | --- |
| ESP8266 | PA9 / PA10 | USART1,STM32 通过 AT 指令访问 WiFi 和 HTTP |
| K210 | PB10 / PB11 | USART3,K210 把识别结果发给 STM32 |
| OLED | PB8 / PB9 | I2C 显示 |
| 调试串口 | PA2 / PA3 | USART2 调试输出 |
| 成功 LED | PB4 | 高电平点亮 |
| 蜂鸣器 | PB5 | 低电平触发 |
| 光敏传感器 | PB0 | ADC 输入 |
| 补光灯 | PB1 | 高电平点亮 |
| DS1302 CLK/DAT/RST | PA4 / PA5 / PA6 | 软件模拟时序 |
K210 侧:
| 模块 | K210 引脚 | 功能 |
| --- | --- | --- |
| STM32 UART | 24 / 25 | UART1,发送识别结果给 STM32 |
| ESP8266 UART | 34 / 35 | UART2,上传照片和同步配置 |
| 按键 | 16 | 短按添加,长按删除,超长按删除全部 |
| RGB LED | 12 / 13 / 14 | 状态提示 |
## 8. 加密与鉴权
### 8.1 API Token
除少数公开接口外,服务端要求请求头中带 Token:
```text
X-Api-Token: atk_nKfVWLhMjL0RGSfRll3R_sdJS6IrIjuV
```
服务端可用环境变量覆盖:
```powershell
$env:ATTENDANCE_API_TOKEN="你的新Token"
```
Qt 端也会读取同名环境变量;STM32 和 K210 目前是写在代码里的常量,需要改代码后重新烧录或上传脚本。
### 8.2 AES 参数
三端必须保持完全一致:
| 参数 | 值 |
| --- | --- |
| 算法 | AES-128-CBC |
| 填充 | PKCS#7 |
| 编码 | Base64 |
| Key | `2b 7e 15 16 28 ae d2 a6 ab f7 15 88 09 cf 4f 3c` |
| IV | `00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f` |
STM32 上传签到使用加密请求。Qt 当前普通记录和统计请求使用 JSON 明文响应,代码仍保留了解密能力。
## 9. 推荐启动顺序
1. 启动服务端,确认 `http://服务器IP:8082/api/v1/system/health` 返回健康状态。
2. 启动 Qt 管理端,在配置中心填写服务端地址和 API Token,点击“保存”后再点“测试连接”。
3. 在 Qt 管理端新增签到时间段并保存。
4. 烧录并启动 STM32,确认 OLED 显示系统就绪。
5. 启动 K210,确认摄像头画面、模型加载和 WiFi 连接正常。
6. 在 K210 添加人脸,等待照片上传到服务端。
7. 在 Qt 管理端为照片对应的人脸 ID 填写真实学号并保存映射。
8. K210 同步映射后,再让同一人进行识别签到。
9. 在 Qt 管理端查看考勤记录和统计结果。
## 10. 一键理解各端关系
| 你想做的事 | 应该操作哪里 | 背后发生什么 |
| --- | --- | --- |
| 改服务端地址 | Qt 配置中心、STM32/K210 代码常量 | 各端 HTTP 请求指向新服务器 |
| 设置允许签到的时间 | Qt 配置中心 | Qt 保存到服务端,STM32 定期下载 |
| 给人脸绑定学号 | Qt 人脸管理 | 服务端保存映射,K210 同步后更新本地 `face_mapping.txt`,识别发送时转换成学号 |
| 添加人脸 | K210 按键短按 | K210 保存特征和照片,照片上传服务端 |
| 删除人脸 | Qt 人脸管理或 K210 删除模式 | Qt 删除会生成服务端同步任务;K210 本地删除只影响本地 |
| 查看记录 | Qt 考勤记录页 | Qt 从服务端读取最近记录 |
| 导出报表 | Qt 记录页或统计页 | Qt 把当前加载/筛选结果写成 CSV |
## 11. 新手常见问题
### 11.1 为什么识别成功但没有签到成功
常见原因:
1. 没有配置签到时间段。
2. 当前时间不在任何启用的时间段内。
3. STM32 时间不正确,DS1302 失效且网络对时失败。
4. 同一学生在当前时间段已经签过。
5. K210 发给 STM32 的 ID 不是想要的真实学号。
6. 置信度低于 `0.75`。
7. STM32 无法连接服务端,记录暂存在本地等待同步。
### 11.2 为什么 Qt 看不到 K210 的照片
可能原因:
1. K210 侧 ESP8266 没连上 WiFi。
2. K210 服务端 IP 或 Token 不正确。
3. K210 本地照片保存在 `/sd/face_photos`,但还没轮到网络队列上传。
4. 服务端 `faces/` 目录没有写入权限。
5. Qt 配置的服务端地址不是 K210 上传照片的那个服务端。
### 11.3 为什么删除后 K210 还认识这个人
Qt 删除会先在服务端生成待删除任务,K210 需要同步后才会删除本地 SD 卡中的特征。可以等待 K210 定期同步,也可以通过服务端同步请求接口触发。
### 11.4 为什么改了 Token 后有的端还能用,有的端不能用
服务端和 Qt 可以读取环境变量 `ATTENDANCE_API_TOKEN`。STM32 与 K210 的 Token 写在代码里,必须同步修改并重新烧录或上传脚本。
## 12. 文档索引
| 文档 | 内容 |
| --- | --- |
| `docs/00_项目总文档.md` | 总体介绍、架构、运行顺序、概念说明 |
| `docs/01_STM32固件端文档.md` | STM32 固件、硬件模块、签到判断、加密上传 |
| `docs/02_K210识别端文档.md` | K210 人脸识别、按键操作、照片上传、同步 |
| `docs/03_服务端文档.md` | 服务端 API、SQLite、同步版本、部署安全 |
| `docs/04_Qt管理端文档.md` | Qt 界面功能、数据模型、本地缓存、PC/Android 适配 |
+339
View File
@@ -0,0 +1,339 @@
# STM32 固件端文档
## 1. STM32 在系统中做什么
STM32 是整套考勤机的主控。它不做人脸识别,人脸识别由 K210 完成。STM32 负责把 K210 的识别结果变成“是否允许签到”的业务判断,并控制硬件反馈。
它主要做这些事:
1. 初始化 OLED、蜂鸣器、LED、串口、RTC、光敏传感器、补光灯等硬件。
2. 接收 K210 通过 USART3 发来的识别结果。
3. 判断识别置信度是否足够。
4. 判断当前时间是否在服务端配置的签到时间段内。
5. 判断当前学生在本时间段是否已经签过。
6. 生成签到记录。
7. 使用 AES-128-CBC 加密签到 JSON。
8. 通过 ESP8266 发送 HTTP 请求到服务端。
9. 网络异常时把记录留在本地,后续自动重试。
## 2. 相关文件
本节路径以 `stm32/` 目录为根。
| 文件 | 作用 |
| --- | --- |
| `User/main.c` | 主程序入口、初始化流程、主循环、联网和同步调度 |
| `Hardware/Attendance.c` | 考勤业务逻辑:时间段、重复判断、本地记录、同步状态 |
| `Hardware/Attendance.h` | 考勤数据结构和函数声明 |
| `Hardware/K210_USART.c` | 解析 K210 串口识别结果 |
| `Hardware/esp8266.c` | ESP8266 AT 指令、HTTP GET/POST、对时、查重 |
| `Hardware/Crypto.c` | AES-128-CBC 加密和 Base64 编码 |
| `Hardware/DS1302.c` | DS1302 RTC 时钟读写 |
| `Hardware/LightSensor.c` | 光敏电阻 ADC 和补光灯控制 |
| `Hardware/Buzzer_LED.c` | 蜂鸣器、成功 LED、状态 LED |
| `Hardware/OLED.c` | OLED 显示 |
| `System/Delay.c` | 延时和毫秒计时 |
| `System/Debug.c` | 调试串口输出 |
| `Project.uvprojx` | Keil 工程文件 |
## 3. 硬件连接
| 外设 | STM32 引脚 | 说明 |
| --- | --- | --- |
| ESP8266 TX/RX | PA10 / PA9 | USART1,波特率 115200 |
| K210 TX/RX | PB11 / PB10 | USART3,波特率 115200 |
| 调试串口 | PA3 / PA2 | USART2,波特率 115200 |
| OLED SCL/SDA | PB8 / PB9 | I2C |
| 成功 LED | PB4 | 高电平点亮 |
| 蜂鸣器 | PB5 | 低电平触发 |
| 光敏传感器 | PB0 | ADC1 通道 8 |
| 补光灯 | PB1 | 高电平点亮 |
| DS1302 CLK/DAT/RST | PA4 / PA5 / PA6 | 软件 GPIO 时序 |
| 板载状态 LED | PC13 | 低电平点亮,初始化中亮,就绪后灭 |
## 4. 代码中的主要配置
位置:`User/main.c`
| 配置 | 当前值 | 说明 |
| --- | --- | --- |
| `WIFI_SSID` | `yzfh` | ESP8266 要连接的 WiFi |
| `WIFI_PASSWORD` | `yzfhroot` | WiFi 密码 |
| `HTTP_SERVER_IP` | `27.106.113.205` | 服务端 IP |
| `HTTP_SERVER_PORT` | `8082` | 服务端 HTTP 端口 |
| `HTTP_API_TOKEN` | `atk_nKfVWLhMjL0RGSfRll3R_sdJS6IrIjuV` | API Token |
| `CLASS_ID` | `1` | 当前 STM32 设备所属班级编号 |
| `TIME_SYNC_INTERVAL_MS` | `3600000` | 每小时对时一次 |
| `TIMESLOT_DOWNLOAD_INTERVAL_MS` | `30000` | 每 30 秒检查一次时间段配置 |
| `WIFI_RECONNECT_INTERVAL_MS` | `30000` | WiFi 断开后每 30 秒重连 |
| `RECONNECT_INTERVAL_MS` | `5000` | 服务端连接不可用后每 5 秒尝试重连 |
| `MIN_CONFIDENCE_THRESHOLD` | `0.75` | 最低识别置信度 |
| `MAX_ATTENDANCE_RECORDS` | `100` | STM32 本地最多缓存 100 条记录 |
| `MAX_TIME_SLOT_COUNT` | `10` | 最多支持 10 个签到时间段 |
## 5. 启动流程
STM32 上电后按下面顺序执行:
1. 初始化调试串口。
2. 初始化延时模块。
3. 初始化 OLED。
4. 初始化蜂鸣器和 LED。
5. 初始化 USART1,用于连接 ESP8266。
6. 初始化 USART3,用于接收 K210 数据。
7. 初始化考勤模块。
8. 初始化 AES 加密模块。
9. 初始化 DS1302,并尝试读取时间。
10. 初始化光敏传感器和补光灯。
11. 初始化 ESP8266。
12. 连接 WiFi。
13. 从服务端获取当前时间。
14. 连接 HTTP 服务端。
15. 下载签到时间段配置。
16. 下载今日当前时段已签到记录。
17. 进入主循环。
如果 WiFi 或服务端连接失败,程序不会停机,而是进入离线或重连状态。后续主循环会自动重试。
## 6. 主循环每次做什么
`User/main.c` 的主循环大致如下:
```c
while (1) {
ProcessBeep();
UpdateCurrentTime();
CheckServerConnection();
SyncPendingRecords();
UpdateTimeSlotConfig();
LightSensor_Update();
if (K210_GetRxFlag()) {
Attendance_ProcessFaceResult(&K210_FaceResult);
}
}
```
逐项解释:
| 函数 | 作用 |
| --- | --- |
| `ProcessBeep` | 非阻塞处理蜂鸣器和成功 LED |
| `UpdateCurrentTime` | 从 DS1302 或软件时钟维护当前时间,并定期网络对时 |
| `CheckServerConnection` | 检查当前 HTTP/TCP 连接状态,连接不可用时按需重连 |
| `SyncPendingRecords` | 把本地未同步记录加密上传 |
| `UpdateTimeSlotConfig` | 定期下载服务端签到时间段 |
| `LightSensor_Update` | 根据环境光自动开关补光灯 |
| `K210_GetRxFlag` | 看是否收到 K210 的新识别结果 |
注意:当前 STM32 HTTP 请求和服务端响应都使用 `Connection: close`,所以它不是长期保持一个 TCP 长连接,而是在每次请求前检查连接状态,并在需要时重新建立连接。
## 7. K210 发给 STM32 的数据格式
K210 通过 UART1 发送文本,STM32 通过 USART3 接收。
识别成功:
```text
ID:20240001,NAME:Zhang_San,CONF:0.95
```
字段说明:
| 字段 | 含义 |
| --- | --- |
| `ID` | 学号或 K210 同步后的身份编号 |
| `NAME` | 显示名,K210 代码中会用下划线代替空格 |
| `CONF` | 置信度,范围一般是 0.0 到 1.0 |
未识别:
```text
UNKNOWN
```
STM32 收到 `UNKNOWN` 后不会签到。
## 8. 签到判断逻辑
STM32 对一次识别结果的判断顺序如下:
1. 判断 K210 是否识别成功。
2. 判断置信度是否低于 `0.75`。
3. 判断当前是否在启用的时间段内。
4. 判断当前时段本地记录中是否已经有该学生。
5. 判断从服务端下载的今日缓存中是否已经有该学生。
6. 如果服务端在线,再调用服务端查重接口确认。
7. 通过所有检查后,生成一条本地考勤记录。
8. 如果服务端在线,后续同步任务会加密上传。
当前默认没有任何时间段。必须由 Qt 管理端新增并保存时间段,否则第 3 步会失败,OLED 会显示不在签到时间。
## 9. 时间段同步
STM32 调用:
```text
GET /api/v1/sync/state?client=stm32&last_version=<本地版本>
```
服务端返回示例:
```json
{
"version": 5,
"changed": true,
"client": "stm32",
"server_time": "2026-05-01 10:00:00",
"state": {
"time_slots": [
{
"name": "上午签到",
"start_time": "08:00",
"end_time": "09:30",
"enabled": true
}
]
}
}
```
如果 `time_slots` 是空数组,STM32 会清空本地时间段配置。
## 10. 今日记录缓存
STM32 为了减少重复签到,会在开机连接服务端后下载今日当前时间段的已签到学号:
```text
GET /api/v1/attendance/today?slot_start=08:00&slot_end=09:30
```
返回:
```json
{
"date": "2026-05-01",
"records": ["20240001", "20240002"]
}
```
下载后,STM32 会把这些学号放进缓存。之后如果同一个学生在同一个时段再次识别,会被判定为重复签到。
## 11. 加密上传
STM32 上传签到记录时使用:
```text
POST /api/v1/attendance/checkins
X-Api-Token: <Token>
X-Encrypted: AES128-CBC
Content-Type: application/octet-stream
```
请求体是 Base64 编码后的 AES 密文。明文 JSON 类似:
```json
{
"student_id": "20240001",
"name": "Zhang San",
"confidence": 0.95,
"status": "success",
"class_id": "1"
}
```
服务端解密成功后写入 `attendance.db`。
## 12. 本地记录和同步状态
STM32 的本地记录结构体:
```c
typedef struct {
char student_id[20];
char student_name[32];
float confidence;
uint32_t timestamp;
uint8_t time_slot;
SyncStatus sync_status;
uint8_t retry_count;
uint32_t last_retry_time;
} AttendanceRecord;
```
同步状态:
| 状态 | 含义 |
| --- | --- |
| `SYNC_PENDING` | 新记录,等待上传 |
| `SYNC_SYNCING` | 正在上传 |
| `SYNC_SYNCED` | 上传成功 |
| `SYNC_FAILED` | 上传失败,后续重试 |
## 13. OLED 和声光反馈
| 情况 | OLED | 蜂鸣器/LED |
| --- | --- | --- |
| 初始化中 | 显示 ESP/WiFi/HTTP 状态 | PC13 状态灯亮 |
| 服务端在线 | `System Ready / Online Mode` | 成功提示 |
| WiFi 在线但服务端断开 | `System Ready / Reconnecting..` | 后台重连 |
| WiFi 正在重连 | `System Ready / WiFi retry...` | 后台重连 |
| ESP8266 不可用 | `System Ready / Offline Mode` | 错误提示 |
| 签到成功 | `Check-in OK!` | 成功提示 |
| 重复签到 | `Already Signed!` | 错误提示 |
| 置信度低 | `Low Confidence` | 错误提示 |
| 不在时间段 | `Not Check Time` | 错误提示 |
## 14. 编译与烧录
使用 Keil 打开:
```text
Project.uvprojx
```
一般步骤:
1. 安装 Keil MDK 和 STM32F10x 支持包。
2. 打开 `Project.uvprojx`。
3. 确认芯片型号和启动文件与实际开发板一致。
4. 修改 `User/main.c` 中的 WiFi、服务端 IP、班级编号和 Token。
5. 编译工程。
6. 使用 ST-Link 或串口 Bootloader 烧录。
7. 打开调试串口查看日志。
## 15. 调试建议
### 15.1 先看调试串口
调试串口会输出:
1. 硬件初始化状态。
2. ESP8266 AT 指令结果。
3. WiFi 连接结果。
4. HTTP 服务端连接结果。
5. 时间同步结果。
6. 时间段下载结果。
7. K210 原始数据和解析结果。
8. 加密上传结果。
### 15.2 常见问题
| 现象 | 可能原因 | 处理 |
| --- | --- | --- |
| 一直显示 WiFi retry | SSID/密码错误、ESP8266 没接好、供电不足 | 检查 `WIFI_SSID`、`WIFI_PASSWORD`、供电和串口 |
| HTTP connection failed | 服务端 IP/端口错误、防火墙拦截、服务端没启动 | 用电脑访问健康检查接口 |
| Not Check Time | 没有配置时间段,或当前时间不在时间段内 | Qt 新增时间段并保存,检查 DS1302/NTP |
| Already Signed | 当前时段已经签过 | 换时间段或清空记录 |
| Low Confidence | K210 比对分数偏低 | 调整光线、重新录入人脸 |
| Qt 看不到记录 | STM32 未同步成功或服务端地址不一致 | 看服务端日志和 Qt 服务端地址 |
## 16. 修改配置时要同步修改哪里
| 你修改了什么 | 需要同步修改 |
| --- | --- |
| 服务端 IP | `User/main.c`、`k210/main.py`、Qt 配置中心 |
| API Token | 服务端环境变量或代码、`User/main.c`、`k210/main.py`、Qt 环境变量 |
| AES Key/IV | `Hardware/Crypto.c`、`server/server_encrypted.cpp`、`QT/src/CryptoUtils.cpp` |
| 班级编号 | 每块 STM32 对应修改 `CLASS_ID` |
| WiFi | `User/main.c` 和 `k210/main.py` |
+288
View File
@@ -0,0 +1,288 @@
# K210 识别端文档
## 1. K210 在系统中做什么
K210 负责“看见人脸并判断是谁”。它不直接决定能不能签到,最终签到判断由 STM32 完成。
K210 的职责包括:
1. 初始化摄像头和 LCD。
2. 加载人脸检测、关键点检测、特征提取三个 KPU 模型。
3. 从 SD 卡读取已经录入的人脸特征。
4. 实时检测画面中的人脸。
5. 将识别结果通过 UART1 发给 STM32。
6. 通过按键添加或删除本地人脸。
7. 保存新增人脸照片到 SD 卡。
8. 通过 K210 侧 ESP8266 上传照片到服务端。
9. 从服务端同步人脸 ID 到学号的映射。
10. 从服务端同步待删除任务,并删除本地特征。
## 2. 相关文件
| 文件 | 作用 |
| --- | --- |
| `k210/main.py` | K210 主程序,包含识别、添加、删除、同步、上传照片 |
K210 程序依赖 SD 卡上的模型文件:
```text
/sd/FaceDetection.smodel
/sd/FaceLandmarkDetection.smodel
/sd/FeatureExtraction.smodel
```
如果模型文件不存在或路径错误,程序会在加载 KPU 模型时报错。
## 3. 代码中的主要配置
位置:`k210/main.py`
| 配置 | 当前值 | 说明 |
| --- | --- | --- |
| `WIFI_SSID` | `yzfh` | K210 侧 ESP8266 要连接的 WiFi |
| `WIFI_PASSWORD` | `yzfhroot` | WiFi 密码 |
| `SERVER_IP` | `27.106.113.205` | 服务端 IP |
| `SERVER_PORT` | `8082` | 服务端端口 |
| `API_TOKEN` | `atk_nKfVWLhMjL0RGSfRll3R_sdJS6IrIjuV` | 服务端鉴权 Token |
| `SYNC_CHECK_INTERVAL_MS` | `50000` | 每 50 秒检查一次服务端同步请求 |
| `NETWORK_FRAME_INTERVAL` | `300` | 每 300 帧处理一次网络任务 |
| `LOCAL_FACE_PHOTO_DIR` | `/sd/face_photos` | 本地照片缓存目录 |
| `CAMERA_HMIRROR` | `1` | 水平镜像 |
| `CAMERA_VFLIP` | `1` | 垂直翻转 |
## 4. 硬件连接
| 功能 | K210 引脚 | 说明 |
| --- | --- | --- |
| 发给 STM32 | 24 | UART1 TX |
| 收 STM32 | 25 | UART1 RX |
| 发给 ESP8266 | 34 | UART2 TX |
| 收 ESP8266 | 35 | UART2 RX |
| 蓝/绿/红 LED | 12 / 13 / 14 | 状态提示 |
| 按键 | 16 | 添加、删除、删除全部 |
UART 参数:
```text
115200, 8 data bits, no parity, 1 stop bit
```
## 5. SD 卡文件
| 文件或目录 | 作用 |
| --- | --- |
| `/sd/recordftr.txt` | 人脸特征库,每行一个人脸 |
| `/sd/face_mapping.txt` | face_id 到学号的本地映射 |
| `/sd/sync_version.txt` | K210 已同步到的服务端版本号 |
| `/sd/face_photos/` | 新增人脸时保存的本地照片 |
| `/sd/FaceDetection.smodel` | 人脸检测模型 |
| `/sd/FaceLandmarkDetection.smodel` | 人脸关键点模型 |
| `/sd/FeatureExtraction.smodel` | 人脸特征提取模型 |
`recordftr.txt` 的一行大致是:
```text
ID:1#[0.0123, -0.0456, ...]
```
程序只解析安全的浮点数组文本,不执行任意表达式。
当前同步映射不会把 `recordftr.txt` 中的 `ID:1` 改成学号;真实学号保存在 `/sd/face_mapping.txt`,识别发送给 STM32 前再转换。
## 6. 启动流程
K210 启动后主要执行:
1. 设置堆内存到 1MB。
2. 初始化 UART1 和 UART2。
3. 初始化 K210 侧 ESP8266,尝试连接 WiFi。
4. 加载本地映射 `/sd/face_mapping.txt`。
5. 加载本地同步版本 `/sd/sync_version.txt`。
6. 加载三个 KPU 模型。
7. 初始化 LCD 和摄像头。
8. 初始化按键中断。
9. 加载本地人脸特征库 `/sd/recordftr.txt`。
10. 扫描本地照片目录,把未上传照片加入队列。
11. 如果 WiFi 可用,执行一次统一同步。
12. 进入主循环。
## 7. 人脸识别流程
每一帧画面大致按下面处理:
1. 摄像头采集图像。
2. `task_fd` 做人脸检测。
3. `task_ld` 做关键点定位。
4. 根据关键点做仿射变换,把人脸对齐到 128x128。
5. `task_fe` 提取人脸特征。
6. 与 `peoples` 中的本地特征逐个比对。
7. 如果最高分大于 `85`,认为识别成功。
8. 如果识别成功,LCD 画绿色框;陌生人画红色框。
9. 每隔 `SEND_INTERVAL_MS = 1000` 毫秒,向 STM32 发送一次状态变化。
发给 STM32 的格式:
```text
ID:<学号或face_id>,NAME:<显示名>,CONF:<置信度>
```
示例:
```text
ID:20240001,NAME:20240001,CONF:0.95
```
如果陌生人:
```text
UNKNOWN
```
## 8. 按键操作
| 操作 | 程序状态 | 作用 |
| --- | --- | --- |
| 短按 | 正常识别界面 | 进入添加人脸模式 |
| 长按约 2 秒 | 正常识别界面 | 进入删除人脸模式 |
| 超长按约 5 秒 | 正常识别界面 | 进入删除全部人脸模式 |
| 添加模式中短按 | 检测到陌生人脸时 | 确认添加 |
| 添加模式中长按 | 添加模式 | 退出添加模式 |
| 删除模式中短按 | 删除模式 | 切换候选项 |
| 删除模式中长按 | 删除模式 | 确认删除当前项 |
## 9. 添加人脸流程
1. 正常识别界面短按按键。
2. LCD 显示 `Add Face Mode`。
3. 摄像头检测到陌生人脸后显示 `Ready`。
4. 再短按确认添加。
5. K210 分配一个未使用的 `face_id`,例如 `1`。
6. 特征写入 `/sd/recordftr.txt`,名称为 `ID:1`。
7. 当前画面压缩为 JPEG。
8. JPEG 保存到 `/sd/face_photos/1.jpg`。
9. `1` 加入照片上传队列。
10. 后续网络任务会上传到服务端。
服务端接收接口:
```text
POST /api/v1/faces/photos/1
```
## 10. 人脸映射同步
新增人脸后,K210 只知道它是 `ID:1`,不知道真实学号。管理员需要在 Qt 管理端把 `1` 映射成学号:
```json
{
"mappings": {
"1": "20240001"
}
}
```
K210 同步后会:
1. 拉取服务端 `face_mappings`。
2. 修改本地 `face_mapping.txt`。
3. 保持 `recordftr.txt` 中的原始 `ID:<face_id>` 不变。
4. 识别成功后先取原始 `face_id`,再通过 `face_mapping.txt` 转成真实学号发送给 STM32。
5. 未绑定学号的人脸按 `UNKNOWN` 处理,不参与签到。
## 11. 照片上传队列
K210 不会在识别每一帧时都做网络请求,因为网络请求会阻塞画面。它采用“分时网络任务”:
1. 新照片先保存到 SD 卡。
2. 只把 face_id 放入 `photo_upload_queue`。
3. 每隔一定帧数,主循环处理一个网络任务。
4. 如果上传失败,重新放回队列。
这样可以减少卡顿,并且即使网络临时不可用,照片也不会丢,因为原始 JPEG 还在 SD 卡。
## 12. 服务端同步
K210 主要使用统一同步接口:
```text
GET /api/v1/sync/state?client=k210&last_version=<本地版本>
```
如果有变化,服务端返回 K210 需要的数据:
```json
{
"version": 5,
"changed": true,
"state": {
"face_mappings": {
"1": "20240001"
},
"pending_delete": ["1"],
"delete_tasks": [
{
"task_id": "delete-1",
"face_id": "1"
}
]
}
}
```
K210 会先处理删除任务,再应用映射。删除成功后发送 ACK:
```json
{
"client": "k210",
"version": 5,
"deleted": ["1"],
"task_ids": ["delete-1"]
}
```
如果 ACK 失败,K210 不会推进本地版本号,下一次还会重试。
## 13. 删除人脸
K210 本地删除有两种来源:
### 13.1 K210 按键本地删除
这只会删除 K210 本地 `recordftr.txt` 和本地照片,不会自动删除服务端记录。适合现场维护。
### 13.2 Qt 管理端远程删除
Qt 删除会在服务端创建待删除任务。K210 同步后删除本地人脸,并向服务端确认。适合后台统一管理。
## 14. K210 常用接口
| 方法 | 路径 | 用途 |
| --- | --- | --- |
| `POST` | `/api/v1/faces/photos/<face_id>` | 上传 JPEG 照片 |
| `GET` | `/api/v1/faces/photos` | 查询服务端已有照片 |
| `GET` | `/api/v1/faces/mappings` | 拉取人脸 ID 到学号映射 |
| `GET` | `/api/v1/faces/pending-delete` | 拉取待删除列表 |
| `POST` | `/api/v1/faces/delete-ack` | 删除同步确认 |
| `GET` | `/api/v1/sync/state?client=k210&last_version=N` | 推荐统一同步 |
| `POST` | `/api/v1/sync/ack` | 推荐统一同步确认 |
| `GET` | `/api/v1/sync/request` | 查询 Qt/服务端是否请求同步 |
| `POST` | `/api/v1/sync/request/ack` | 确认收到同步请求 |
## 15. 常见问题
| 现象 | 可能原因 | 处理 |
| --- | --- | --- |
| 模型加载失败 | SD 卡缺少 `.smodel` 文件 | 把三个模型放到 `/sd/` 根目录 |
| WiFi 连接失败 | SSID/密码错误、ESP8266 未启动、波特率不匹配 | 看串口日志,代码会尝试多个波特率 |
| 添加人脸失败 | 没检测到陌生人脸、SD 卡不可写、照片保存失败 | 检查光照、SD 卡和日志 |
| Qt 看不到照片 | 上传队列未完成、服务端地址不一致、Token 错误 | 看 K210 `[PHOTO]`、`[NET]`、`[ESP]` 日志 |
| 删除后仍识别 | K210 尚未同步或 ACK 失败 | 等待同步,检查 `/api/v1/sync/state` |
| 发给 STM32 的还是 `ID:1` | 映射没有保存或 K210 未同步 | Qt 保存映射后触发同步 |
## 16. 修改配置时要注意
1. K210 的服务端 IP、端口、Token 必须和服务端一致。
2. K210 的 WiFi 配置和 STM32 可以相同,也可以不同,但都要能访问服务端。
3. K210 侧照片上传使用自己的 ESP8266,不依赖 STM32 的 ESP8266。
4. K210 本地 SD 卡数据很重要,删除或格式化 SD 卡会丢失本地人脸特征。
5. 如果重新录入人脸,原 face_id 可能变化,需要重新在 Qt 管理端设置映射。
+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/` 权限 |
+459
View File
@@ -0,0 +1,459 @@
# Qt 管理端文档
## 1. Qt 管理端是什么
Qt 管理端是整个系统的图形化后台。它支持 Windows 桌面端和 Android 端,主要用于:
1. 查看考勤记录。
2. 按班级、学号、姓名、日期筛选记录。
3. 导出记录 CSV。
4. 清空服务端考勤记录。
5. 查看统计分析。
6. 导出统计 CSV。
7. 配置服务端地址和 API Token。
8. 配置签到时间段。
9. 配置学号到姓名、学号到班级编号、班级编号到班级名的学生档案映射。
10. 查看 K210 上传的人脸照片。
11. 给 K210 face_id 绑定真实学号。
12. 删除人脸信息,并可选择删除相关考勤记录。
## 2. 相关文件
| 文件 | 作用 |
| --- | --- |
| `QT/main.cpp` | Qt 程序入口,设置应用信息、图标、QML 引擎 |
| `QT/qml/Main.qml` | 主界面,包括所有页面和对话框 |
| `QT/src/AppController.*` | QML 与 C++ 业务逻辑的中枢 |
| `QT/src/ApiClient.*` | HTTP API 客户端 |
| `QT/src/AttendanceTypes.h` | 考勤记录、统计、时间段结构体 |
| `QT/src/CryptoUtils.*` | AES 解密工具 |
| `QT/src/RecordListModel.*` | 考勤记录模型 |
| `QT/src/StatisticsListModel.*` | 统计模型 |
| `QT/src/TimeSlotListModel.*` | 时间段模型 |
| `QT/src/MappingListModel.*` | 学生姓名、学生班级归属、班级名称映射模型 |
| `QT/src/FaceListModel.*` | 人脸管理模型 |
| `QT/CMakeLists.txt` | Qt/CMake 构建配置 |
| `QT/clear_qt_cache.ps1` | 清理当前 Qt 项目的 Qt Creator/CMake 缓存 |
| `QT/android/` | Android Gradle 配置和图标 |
| `QT/assets/` | 桌面端图标 |
## 3. 技术栈
| 分类 | 内容 |
| --- | --- |
| 语言 | C++17、QML |
| UI | Qt Quick、Qt Quick Controls 2 |
| Qt 模块 | Core、Gui、Qml、Quick、QuickControls2、Network |
| 构建系统 | CMake 3.16+ |
| 样式 | `QQuickStyle::setStyle("Basic")` |
| Android 文字渲染 | `NativeTextRendering` |
| 本地配置 | `QSettings`、`QStandardPaths::AppDataLocation` |
## 4. 应用基础信息
位置:`QT/main.cpp`
| 项 | 值 |
| --- | --- |
| ApplicationName | `人脸识别考勤系统` |
| ApplicationVersion | `3.0.0` |
| OrganizationName | `STM32 Face Recognition` |
| 主 QML | `qrc:/qml/Main.qml` |
| 默认窗口大小 | `1560 x 920` |
| PC 最小窗口 | `900 x 620` |
| Android 最小窗口 | `360 x 560` |
## 5. 页面结构
Qt 管理端是单窗口应用,共 5 个页面:
| 页面 | 作用 |
| --- | --- |
| 考勤记录 | 查看、筛选、导出、清空记录 |
| 统计分析 | 按时间范围统计学生签到情况 |
| 配置中心 | 服务端地址、签到时间段、名称映射 |
| 人脸管理 | 查看照片、绑定学号、删除人脸 |
| 关于 | 系统说明、版本和作者信息 |
PC 宽屏显示左侧导航栏;窄屏和 Android 显示顶部/底部更紧凑的导航布局。
## 6. 网络访问规则
Qt 端的服务端地址默认是:
```text
http://127.0.0.1:8082
```
用户可在“配置中心”修改服务端地址和 API Token。保存后写入本地 `QSettings`:
```text
server/url
server/apiToken
```
`ApiClient::buildUrl` 会在地址后自动拼接 `/api`,例如:
```text
服务端地址: http://127.0.0.1:8082
传入 endpoint: /v1/system/health
实际请求: http://127.0.0.1:8082/api/v1/system/health
```
所有请求都会带:
```text
X-Api-Token: <Token>
Accept: application/json
```
Token 默认值写在 `QT/src/ApiClient.cpp`,也可以通过环境变量覆盖:
```powershell
$env:ATTENDANCE_API_TOKEN="你的Token"
```
网络超时:
```text
5000 ms
```
## 7. 启动流程
Qt 启动后:
1. `main.cpp` 创建 `QGuiApplication`。
2. 设置应用图标、应用名、版本号、组织名。
3. Android 下启用原生文字渲染。
4. 创建 `AppController`。
5. `AppController` 读取本地服务端地址、API Token、映射配置、时间段配置。
6. `ApiClient` 设置当前服务端地址和 API Token。
7. 启动自动重连状态并执行健康检查。
8. QML 载入 `Main.qml`。
9. QML 通过上下文对象 `appController` 调用 C++。
## 8. 自动重连
如果健康检查失败,`AppController` 会进入重连状态。
相关参数:
```text
ReconnectIntervalMs = 5000
```
重连成功后,如果之前处于重连中,会调用:
1. `refreshAll()`
2. `refreshFaces()`
这样服务端恢复后,记录、配置、照片和映射会重新同步。
## 9. 考勤记录页
功能:
1. 显示服务端记录。
2. 显示总记录数、今日记录数、当前筛选结果数。
3. 按班级筛选。
4. 按学号筛选。
5. 按姓名关键字筛选。
6. 按日期范围筛选。
7. 导出当前筛选结果。
8. 清空服务端所有记录。
记录加载默认请求:
```text
GET /api/v1/attendance/records?limit=1000&offset=0
```
注意:当前 Qt 统计和筛选主要基于已加载的记录。如果服务端超过 1000 条,界面统计不一定覆盖全部历史记录。
## 10. 筛选规则
筛选在 Qt 本地完成。
支持:
| 筛选项 | 说明 |
| --- | --- |
| 班级 | `class_id` 匹配 |
| 学号 | `student_id` 匹配 |
| 姓名 | 映射姓名或原始 name 包含关键字 |
| 日期 | 全部、今天、最近 7 天、最近 30 天、指定日期 |
姓名显示优先级:
1. 如果学生映射里有该学号,显示映射姓名。
2. 否则显示服务端记录中的 `name`。
3. 如果都没有,则显示原始学号。
班级显示优先级:
1. 如果班级映射里有该 `class_id`,显示映射班级名。
2. 否则显示 `class_id`。
## 11. 记录导出
导出 CSV 文件时会写入:
```text
# 考勤记录导出
# 导出时间: yyyy-MM-dd HH:mm:ss
# 记录总数: N
签到时间,班级编号,班级名称,学号,姓名,置信度,状态
```
字段会做 CSV 转义,包含逗号、双引号、换行时会自动加引号并转义。
## 12. 统计分析页
统计页基于当前加载记录在本地计算。
支持时间范围:
1. 今天。
2. 最近 7 天。
3. 最近 30 天。
4. 本月。
5. 自定义开始和结束日期。
统计指标:
| 指标 | 含义 |
| --- | --- |
| 统计学生数 | 当前范围内出现过的学生数量 |
| 统计记录数 | 当前范围内记录数量 |
| 总签到次数 | 每个学生在范围内的记录数量 |
| 今日签到 | 每个学生今天的记录数量 |
| 最后签到时间 | 每个学生最新一条记录时间 |
| 平均置信度 | 每个学生记录的平均 confidence |
导出字段:
```text
考勤统计报表,<范围>
学号,姓名,总签到次数,今日签到,最后签到时间,平均置信度
```
## 13. 配置中心
配置中心包含三块:
1. 服务端地址和 API Token。
2. 时间段配置。
3. 名称映射配置。
### 13.1 服务端配置
字段示例:
```text
http://27.106.113.205:8082
```
按钮:
| 按钮 | 作用 |
| --- | --- |
| 保存 | 同时保存服务端地址和 API Token 到 `QSettings`,并立即切换 `ApiClient` 配置 |
| 测试连接 | 使用当前配置请求 `/api/v1/system/health` |
### 13.2 时间段配置
时间段格式:
```json
{
"name": "上午签到",
"start_time": "08:00",
"end_time": "09:30",
"enabled": true
}
```
当前默认时间段为空。第一次使用时需要手动新增。
按钮:
| 按钮 | 作用 |
| --- | --- |
| 展开/收起 | 展示或隐藏时间段列表 |
| 新增时段 | 打开新增对话框 |
| 清空时段 | 清空本地时间段并保存同步到服务端 |
| 从服务器同步/同步 | 从服务端读取时间段;服务端为空数组时也会清空本地时间段 |
| 保存时间段/保存 | 保存到本地并上传服务端 |
本地文件:
```text
timeslot_config.json
```
该文件只作为 Qt 管理端本地缓存;服务端当前以 SQLite 中的配置为准。
### 13.3 学生档案映射配置
学号到姓名:
```json
{
"20240001": "张三"
}
```
学号到班级编号:
```json
{
"20240001": "1"
}
```
班级编号到班级名称:
```json
{
"1": "软件工程一班"
}
```
记录列表、班级筛选和导出优先使用“学号到班级编号”的归属关系;没有配置时才显示设备上传记录中的原始班级编号。
本地文件:
```text
mapping_config.json
```
## 14. 人脸管理页
人脸管理页把两类人脸数据合并展示:
1. 服务端照片列表。
2. 服务端 face_id 到学生 ID 映射。
照片 ID 始终使用 K210 原始 `face_id`,例如 `faces/1.jpg`。Qt 只维护 `face_id -> 学号` 的绑定关系,不再把所有已有考勤学生 ID 当成“无照片人脸”展示。
用户可以:
1. 刷新人脸库。
2. 为每张照片填写真实学号。
3. 保存映射。
4. 删除人脸。
5. 删除人脸时选择是否同时删除该学生的考勤记录。
照片下载流程:
1. Qt 获取照片 ID 列表。
2. 对每个 ID 请求 `/api/v1/faces/photos/<id>`。
3. 下载成功后保存到本地缓存目录:
```text
<AppDataLocation>/face_photos/<id>.jpg
```
4. QML 使用本地 file URL 显示图片。
## 15. 关于页面
关于页显示:
1. 系统用途。
2. 功能范围。
3. 毕业设计说明。
4. 作者和版权信息。
5. 仓库地址。
6. 演示账号信息。
这些文本在 `QT/qml/Main.qml` 中。
## 16. PC 和 Android 适配
当前 QML 中有这些布局断点:
| 属性 | 条件 | 用途 |
| --- | --- | --- |
| `compactLayout` | `width < 1460` | PC 紧凑布局 |
| `denseLayout` | `width < 1380` | 更密集显示 |
| `narrowLayout` | `width < 1280` | 表格列裁剪 |
| `slimLayout` | `width < 1040` | 隐藏侧边栏,使用移动导航 |
| `phoneLayout` | PC 小于 560,Android 小于 640 | 手机布局 |
Android 特殊处理:
1. 最小窗口宽度是 `360`。
2. 最小窗口高度是 `560`。
3. 字体使用 `sans-serif`。
4. 粗体权重降低为 `Font.Medium`。
5. 使用原生文字渲染,减少中文粗体重影。
配置页卡片现在使用内容自适应高度,避免按钮遮挡。
## 17. 本地持久化
Qt 使用两类本地持久化。
### 17.1 QSettings
保存服务端地址和 API Token:
```text
server/url
server/apiToken
```
### 17.2 AppDataLocation 文件
| 文件 | 内容 |
| --- | --- |
| `timeslot_config.json` | 时间段配置 |
| `mapping_config.json` | 学生姓名、学生班级归属和班级名称映射 |
| `face_photos/*.jpg` | 下载后的人脸照片缓存 |
## 18. 构建方式
### 18.1 Qt Creator
1. 打开 Qt Creator。
2. 打开 `QT/CMakeLists.txt`。
3. 选择 Desktop Qt 6.x 或 Android Kit。
4. 等待 CMake 配置完成。
5. 构建 `apprewrite_qt`。
6. 桌面端直接运行;Android 端部署到设备或模拟器。
### 18.2 命令行示例
Windows:
```powershell
cd D:\University\graduation\thesis\code\QT
cmake -S . -B build\Desktop -DCMAKE_PREFIX_PATH=D:\Qt\6.10.0\mingw_64
cmake --build build\Desktop
```
实际路径要按本机 Qt 安装位置调整。
### 18.3 迁移目录后清理缓存
如果项目移动到新目录后 Qt Creator 仍使用旧构建目录,或 CMake 报旧源码路径错误,可关闭 Qt Creator 后在 `QT/` 目录运行:
```powershell
powershell -ExecutionPolicy Bypass -File .\clear_qt_cache.ps1
```
脚本只删除当前 Qt 项目目录内的 `.qtcreator`、`build`、`CMakeCache.txt`、`CMakeFiles`、`*.user` 等缓存,不清理 Qt Creator 全局配置。
## 19. 常见问题
| 现象 | 可能原因 | 处理 |
| --- | --- | --- |
| 测试连接失败 | 服务端没启动、地址错、Token 不一致 | 检查服务端健康接口和 `ATTENDANCE_API_TOKEN` |
| 迁移目录后 Qt Creator 报旧路径或构建目录不对 | 项目内 Qt Creator/CMake 缓存仍指向旧目录 | 关闭 Qt Creator,运行 `QT/clear_qt_cache.ps1` 后重新打开 `QT/CMakeLists.txt` |
| 页面按钮重叠 | 窗口宽度过小或系统缩放异常 | 当前 QML 已做自适应布局,可适当放大窗口或降低缩放 |
| Android 文字发虚/重影 | 字体渲染方式问题 | 当前 Android 使用原生渲染和中等粗体 |
| 人脸照片不显示 | 下载失败、本地缓存路径无效、服务端无照片 | 刷新人脸库,看服务端 `faces/` |
| 统计数量少于服务端总量 | Qt 默认只拉取 1000 条记录 | 增加分页或服务端统计接口 |
| 时间段为空导致无法签到 | 当前默认就是空配置 | 在配置中心新增时间段并保存 |
| 保存映射后 K210 没变化 | K210 尚未同步或 WiFi 不通 | 等待同步或检查 K210 日志 |