初始化项目文档
This commit is contained in:
+346
@@ -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 适配 |
|
||||||
@@ -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
@@ -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
@@ -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
@@ -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 日志 |
|
||||||
Reference in New Issue
Block a user