commit 1ff2e6af0183a9900364a6cd70232c6b2a0169a4 Author: 纪田野 Date: Mon May 4 03:08:55 2026 +0800 初始化项目文档 diff --git a/00_项目总文档.md b/00_项目总文档.md new file mode 100644 index 0000000..4337444 --- /dev/null +++ b/00_项目总文档.md @@ -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/.jpg`。 +5. K210 将照片 ID 放入上传队列。 +6. 网络任务空闲时,K210 通过 ESP8266 上传照片到服务端: + +```text +POST /api/v1/faces/photos/ +``` + +7. Qt 管理端进入“人脸管理”,刷新后可以看到照片。 +8. 管理员为该 face_id 填写真实学号并保存映射。 +9. 服务端保存映射并触发同步。 +10. K210 拉取同步状态后更新本地 `face_mapping.txt`,识别发送时把 `face_id` 转成真实学号,`recordftr.txt` 中的原始 `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 适配 | diff --git a/01_STM32固件端文档.md b/01_STM32固件端文档.md new file mode 100644 index 0000000..da8dced --- /dev/null +++ b/01_STM32固件端文档.md @@ -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: +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` | diff --git a/02_K210识别端文档.md b/02_K210识别端文档.md new file mode 100644 index 0000000..384a6f0 --- /dev/null +++ b/02_K210识别端文档.md @@ -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:` 不变。 +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/` | 上传 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 管理端设置映射。 diff --git a/03_服务端文档.md b/03_服务端文档.md new file mode 100644 index 0000000..1decbc0 --- /dev/null +++ b/03_服务端文档.md @@ -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/.jpg`,不会按学号重命名。 + +| 字段 | 含义 | +| --- | --- | +| `face_id` | K210 原始人脸编号 | +| `file_path` | 服务端照片路径 | +| `content_type` | 图片类型 | +| `file_size` | 文件大小 | +| `uploaded_at` | 上传时间 | + +### 4.8 `pending_delete` + +保存等待 K210 删除的任务。 + +| 字段 | 含义 | +| --- | --- | +| `task_id` | 删除任务 ID | +| `face_id` | K210 需要删除的人脸 ID | +| `student_id` | 对应学生 ID | +| `created_at` | 创建时间 | +| `status` | 状态,默认 `pending` | +| `ack_at` | 确认时间 | + +### 4.9 `device_sync_state` + +保存各客户端同步状态。 + +| 字段 | 含义 | +| --- | --- | +| `client` | 客户端名,例如 `qt`、`stm32`、`k210` | +| `last_seen_version` | 客户端最近看到的服务端版本 | +| `last_ack_version` | 客户端最近确认的版本 | +| `last_seen_at` | 最近访问时间 | + +## 5. 默认配置 + +| 配置 | 当前值 | 说明 | +| --- | --- | --- | +| `HTTP_PORT` | `8082` | HTTP 监听端口 | +| `ATTENDANCE_BIND_HOST` | `0.0.0.0` | 默认监听地址,可用环境变量覆盖 | +| `DEFAULT_API_TOKEN` | `atk_nKfVWLhMjL0RGSfRll3R_sdJS6IrIjuV` | 默认 API Token | +| `DEFAULT_CORS_ORIGIN` | `http://localhost` | 默认 CORS 来源 | +| `MAX_JSON_BODY_SIZE` | `64 KB` | 普通 JSON 请求体上限 | +| `MAX_PHOTO_BODY_SIZE` | `512 KB` | 照片上传请求体上限 | +| `DEFAULT_TIMESLOTS_JSON` | `{"time_slots":[]}` | 默认时间段为空 | + +可以通过环境变量覆盖: + +```powershell +$env:ATTENDANCE_BIND_HOST="0.0.0.0" +$env:ATTENDANCE_API_TOKEN="你的Token" +$env:ATTENDANCE_CORS_ORIGIN="http://localhost" +``` + +## 6. 鉴权规则 + +服务端对大多数接口要求请求头: + +```text +X-Api-Token: +``` + +允许不带 Token 的情况: + +1. `OPTIONS` 预检请求。 +2. `GET /api/v1/system/health`。 + +除健康检查外,所有 `/api/v1/...` 接口都使用同样的鉴权逻辑。 + +## 7. 路由规范 + +服务端接口统一使用 `/api/v1/...` 路径。STM32、K210 和 Qt 管理端都通过这一套接口通信。 + +## 8. 核心 API + +以下示例默认服务端地址是: + +```text +http://127.0.0.1:8082 +``` + +### 8.1 健康检查 + +```text +GET /api/v1/system/health +``` + +返回示例: + +```json +{ + "status": "healthy", + "time": "2026-05-01 10:00:00", + "storage": "sqlite", + "database": "attendance.db", + "database_ok": true, + "quick_check": "ok", + "sync_version": 1 +} +``` + +### 8.2 存储状态 + +```text +GET /api/v1/system/storage +``` + +用于检查 SQLite、记录数量、照片数量、待删除任务数量等。 + +### 8.3 获取服务器时间 + +```text +GET /api/v1/system/time +``` + +返回: + +```json +{ + "epoch": 1777600800, + "server_time": "2026-05-01 10:00:00" +} +``` + +STM32 使用 `epoch` 转成 UTC+8 时间。 + +### 8.4 加密签到上传 + +```text +POST /api/v1/attendance/checkins +X-Encrypted: AES128-CBC +X-Api-Token: +``` + +请求体是 Base64 字符串。解密后的 JSON: + +```json +{ + "student_id": "20240001", + "name": "张三", + "confidence": "0.95", + "status": "success", + "class_id": "1" +} +``` + +字段校验: + +| 字段 | 要求 | +| --- | --- | +| `student_id` | 必填,安全 ID,长度不超过 32 | +| `name` | 可选,长度不超过 64 | +| `confidence` | 必须是 0.0 到 1.0 | +| `status` | 只能是 `success`、`failed`、`late` | +| `class_id` | 可选,安全 ID,长度不超过 32,默认 `0` | + +成功返回: + +```json +{ + "success": true, + "message": "签到成功", + "timestamp": "2026-05-01 10:00:00" +} +``` + +### 8.5 获取考勤记录 + +```text +GET /api/v1/attendance/records?limit=1000&offset=0 +``` + +`limit` 范围是 1 到 5000,`offset` 范围是 0 到 1000000。 + +返回数组: + +```json +[ + { + "timestamp": "2026-05-01 08:10:00", + "student_id": "20240001", + "name": "张三", + "confidence": 0.95, + "status": "success", + "class_id": "1" + } +] +``` + +如果请求头带: + +```text +X-Encrypted-Response: true +``` + +服务端会把响应 JSON 加密后以 Base64 返回。Qt 当前普通记录请求没有启用这个请求头。 + +### 8.6 获取统计 + +```text +GET /api/v1/attendance/statistics +``` + +返回: + +```json +{ + "total": 120, + "today": 8, + "server_time": "2026-05-01 10:00:00" +} +``` + +### 8.7 清空记录 + +```text +POST /api/v1/attendance/records/clear +``` + +成功后会删除 `records` 表中的所有记录,并提升同步版本。 + +### 8.8 删除单条记录 + +```text +DELETE /api/v1/attendance/records?timestamp=2026-05-01%2008%3A10%3A00&student_id=20240001 +``` + +成功后提升同步版本。 + +### 8.9 时间段配置 + +获取: + +```text +GET /api/v1/config/time-slots +``` + +保存: + +```text +POST /api/v1/config/time-slots +Content-Type: application/json +``` + +请求体: + +```json +{ + "time_slots": [ + { + "name": "上午签到", + "start_time": "08:00", + "end_time": "09:30", + "enabled": true + } + ] +} +``` + +当前默认值是: + +```json +{ + "time_slots": [] +} +``` + +### 8.10 重复签到检查 + +```text +GET /api/v1/attendance/duplicates?student_id=20240001&slot_start=08:00&slot_end=09:30 +``` + +用于 STM32 在线时补充查重。 + +### 8.11 今日某时段已签到记录 + +```text +GET /api/v1/attendance/today?slot_start=08:00&slot_end=09:30 +``` + +返回: + +```json +{ + "date": "2026-05-01", + "records": ["20240001", "20240002"] +} +``` + +STM32 开机后用它构建本地今日缓存。 + +### 8.12 学生档案和班级名称映射 + +```text +GET /api/v1/config/mappings +POST /api/v1/config/mappings +``` + +格式: + +```json +{ + "student_name_map": { + "20240001": "张三" + }, + "student_class_map": { + "20240001": "1" + }, + "class_name_map": { + "1": "软件工程一班" + } +} +``` + +其中 `student_name_map` 维护学号到姓名,`student_class_map` 维护学号到班级编号,`class_name_map` 维护班级编号到班级名称。保存后会同步写入 `students` 和 `classes`,Qt 的记录列表、筛选、导出优先使用这里维护的学生班级归属。 + +### 8.13 人脸照片 + +照片列表: + +```text +GET /api/v1/faces/photos +``` + +返回: + +```json +{ + "photos": [ + { + "id": "1", + "filename": "1.jpg" + } + ] +} +``` + +上传照片: + +```text +POST /api/v1/faces/photos/1 +Content-Type: image/jpeg +``` + +下载照片: + +```text +GET /api/v1/faces/photos/1 +``` + +成功时返回 `image/jpeg`。 + +### 8.14 人脸 ID 映射 + +```text +GET /api/v1/faces/mappings +POST /api/v1/faces/mappings +``` + +格式: + +```json +{ + "mappings": { + "1": "20240001" + } +} +``` + +保存映射后服务端会: + +1. 写入兼容表 `face_mappings`。 +2. 同步更新结构化表 `face_profiles` 和 `students`。 +3. 对已取消绑定的人脸仅清空 `student_id`,保留原始照片和 `face_id`。 +4. 提升同步版本。 +5. 设置 K210 同步请求标志。 + +### 8.15 获取全部学生 ID + +```text +GET /api/v1/faces/student-ids +``` + +从 `students`、`records` 和人脸映射中汇总学生 ID。 + +### 8.16 删除人脸 + +```text +DELETE /api/v1/faces/20240001 +DELETE /api/v1/faces/20240001?delete_records=true +``` + +服务端会: + +1. 删除相关照片。 +2. 删除相关映射。 +3. 如果 `delete_records=true`,删除该学生相关考勤记录。 +4. 创建 K210 待删除任务。 +5. 提升同步版本。 +6. 设置同步请求标志。 + +返回示例: + +```json +{ + "success": true, + "photo_deleted": true, + "mapping_deleted": true, + "records_deleted": 3, + "k210_pending": true, + "sync_requested": true +} +``` + +## 9. 统一同步接口 + +### 9.1 获取版本 + +```text +GET /api/v1/sync/version +``` + +返回: + +```json +{ + "version": 6, + "server_time": "2026-05-01 10:00:00" +} +``` + +### 9.2 获取同步状态 + +```text +GET /api/v1/sync/state?client=&last_version= +``` + +`client` 可取: + +| client | 服务端返回内容 | +| --- | --- | +| `stm32` | `time_slots` | +| `k210` | `face_mappings`、`pending_delete`、`delete_tasks` | +| `qt` | 记录、统计、时间段、映射、照片列表、学生 ID、人脸映射 | +| `all` 或空 | 尽可能返回全部 | + +如果没有变化: + +```json +{ + "version": 6, + "changed": false, + "client": "stm32", + "server_time": "2026-05-01 10:00:00" +} +``` + +如果有变化: + +```json +{ + "version": 7, + "changed": true, + "client": "k210", + "server_time": "2026-05-01 10:00:00", + "state": { + "face_mappings": {}, + "pending_delete": [], + "delete_tasks": [] + } +} +``` + +### 9.3 同步确认 + +```text +POST /api/v1/sync/ack +``` + +请求体: + +```json +{ + "client": "k210", + "version": 7, + "deleted": ["1"], + "task_ids": ["task-1"] +} +``` + +用途: + +1. 记录客户端已经确认的版本。 +2. 移除已经完成的删除任务。 +3. 必要时再次提升同步版本。 + +## 10. 编译与运行 + +### 10.1 Windows MinGW 示例 + +如果已经装好 MinGW、OpenSSL、SQLite: + +```powershell +g++ -o server_encrypted.exe server_encrypted.cpp -lws2_32 -lcrypto -lssl -lsqlite3 -std=c++11 +.\server_encrypted.exe +``` + +### 10.2 Linux 示例 + +```bash +g++ -o server_encrypted server_encrypted.cpp -lcrypto -lssl -lsqlite3 -std=c++11 -pthread +./server_encrypted +``` + +### 10.3 运行目录建议 + +建议在 `server/` 目录下运行,这样数据库、照片目录和备份目录都会生成在 `server/` 下面: + +```powershell +cd D:\University\graduation\thesis\code\server +.\server_encrypted.exe +``` + +## 11. 安全注意事项 + +1. 默认 Token 已写在代码里,不适合直接暴露公网。 +2. 当前是 HTTP 明文传输,公网部署建议放到 HTTPS 反向代理后面。 +3. AES Key/IV 是固定值,演示可以,生产环境不建议固定写死。 +4. SQLite 数据库和照片目录需要定期备份。 +5. `ATTENDANCE_CORS_ORIGIN` 不建议设置成任意来源。 +6. 服务器运行目录要限制写权限,避免误删数据。 + +## 12. 常见问题 + +| 现象 | 可能原因 | 处理 | +| --- | --- | --- | +| 401 Unauthorized | 没带 `X-Api-Token` 或 Token 不一致 | 检查四端 Token | +| Qt 连接失败 | 服务端没启动、地址错、防火墙拦截 | 访问 `/api/v1/system/health` | +| STM32 解密失败 | AES Key/IV 不一致或请求体不是 Base64 密文 | 对照三端 AES 参数 | +| 时间段配置不符合预期 | SQLite 中保存了当前配置 | 在 Qt 管理端重新保存时间段,或清理 `attendance.db` 后重新初始化 | +| K210 删除不同步 | K210 未拉取同步状态或 ACK 失败 | 看 `/api/v1/sync/state?client=k210&last_version=0` | +| 照片上传失败 | 图片超过 512KB、face_id 非法、目录不可写 | 检查服务端日志和 `faces/` 权限 | diff --git a/04_Qt管理端文档.md b/04_Qt管理端文档.md new file mode 100644 index 0000000..ae64d70 --- /dev/null +++ b/04_Qt管理端文档.md @@ -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: +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/`。 +3. 下载成功后保存到本地缓存目录: + +```text +/face_photos/.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 日志 |