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

14 KiB
Raw Permalink Blame History

服务端文档

1. 服务端的作用

服务端是整个系统的数据中心。STM32、K210 和 Qt 管理端都通过 HTTP 与它通信。

它负责:

  1. 接收 STM32 加密上传的签到记录。
  2. 解密、校验并保存记录。
  3. 提供考勤记录和统计数据给 Qt。
  4. 保存签到时间段配置。
  5. 保存学生档案和班级信息。
  6. 保存 K210 上传的人脸照片。
  7. 保存人脸档案中的 face_id 与真实学号绑定关系。
  8. 维护待删除人脸任务。
  9. 维护全局同步版本号。
  10. 提供健康检查、存储状态和时间接口。

当前推荐服务端文件:

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 班级编号

索引:

idx_records_student_time(student_id,timestamp)
idx_records_timestamp(timestamp)

4.2 kv_store

保存配置型数据。

key value 内容
time_slots 时间段 JSON
sync_request K210 同步请求标志
sync_version 全局同步版本号

4.3 classes

保存班级基础信息。

字段 含义
class_id 班级编号
class_name 班级名称
updated_at 更新时间

4.4 students

保存学生基础信息。

字段 含义
student_id 学号
student_name 姓名
class_id 所属班级编号
status 学生状态
created_at 创建时间
updated_at 更新时间

4.5 face_profiles

保存 K210 人脸实体与学生绑定关系。face_id 保持为 K210 原始编号,绑定学生时只更新 student_id,不改本地特征名。

字段 含义
face_id K210 原始人脸编号
student_id 绑定的真实学号
device_id 来源设备
status 人脸状态
created_at 创建时间
updated_at 更新时间

4.6 face_photos

保存人脸照片元数据。照片文件仍保存在 faces/<face_id>.jpg,不会按学号重命名。

字段 含义
face_id K210 原始人脸编号
file_path 服务端照片路径
content_type 图片类型
file_size 文件大小
uploaded_at 上传时间

4.7 pending_delete

保存等待 K210 删除的任务。

字段 含义
task_id 删除任务 ID
face_id K210 需要删除的人脸 ID
student_id 对应学生 ID
created_at 创建时间
status 状态,默认 pending
ack_at 确认时间

4.8 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":[]} 默认时间段为空

可以通过环境变量覆盖:

$env:ATTENDANCE_BIND_HOST="0.0.0.0"
$env:ATTENDANCE_API_TOKEN="你的Token"
$env:ATTENDANCE_CORS_ORIGIN="http://localhost"

6. 鉴权规则

服务端对业务接口要求请求头:

X-Api-Token: <Token>

允许不带 Token 的情况只有 OPTIONS 预检请求。

所有 /api/v1/... 业务接口都使用同样的鉴权逻辑,包括健康检查。

7. 路由规范

服务端接口统一使用 /api/v1/... 路径。STM32、K210 和 Qt 管理端都通过这一套接口通信。

7.1 加密传输规则

业务 JSON 接口统一使用 AES-128-CBC + PKCS#7 + Base64。JSON POST 请求体必须携带:

X-Encrypted: AES128-CBC
Content-Type: application/octet-stream

服务端会先解密请求体,再进入具体业务路由。JSON 响应统一加密返回,并携带:

X-Encrypted: AES128-CBC
Content-Type: application/octet-stream

人脸照片 JPEG 作为大体积二进制数据保留 image/jpeg 原始上传和下载;照片列表、上传结果、人脸档案、删除结果和同步状态等 JSON 数据仍然走加密传输。后续 API 小节中的 JSON 均表示解密后的业务结构,实际传输时按本节规则加密。

8. 核心 API

以下示例默认服务端地址是:

http://127.0.0.1:8082

8.1 健康检查

GET /api/v1/system/health
X-Api-Token: <Token>

返回示例:

{
  "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 存储状态

GET /api/v1/system/storage

用于检查 SQLite、记录数量、照片数量、待删除任务数量等。

8.3 获取服务器时间

GET /api/v1/system/time

返回:

{
  "epoch": 1777600800,
  "server_time": "2026-05-01 10:00:00"
}

STM32 使用 epoch 转成 UTC+8 时间。

8.4 加密签到上传

POST /api/v1/attendance/checkins
X-Encrypted: AES128-CBC
X-Encrypted-Response: true
X-Api-Token: <Token>

请求体是 Base64 字符串。解密后的 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

成功返回:

{
  "success": true,
  "message": "签到成功",
  "timestamp": "2026-05-01 10:00:00"
}

8.5 获取考勤记录

GET /api/v1/attendance/records?limit=1000&offset=0

limit 范围是 1 到 5000,offset 范围是 0 到 1000000。

返回数组:

[
  {
    "timestamp": "2026-05-01 08:10:00",
    "student_id": "20240001",
    "name": "张三",
    "confidence": 0.95,
    "status": "success",
    "class_id": "1"
  }
]

服务端返回 AES 加密后的 Base64 字符串,客户端解密后得到上述 JSON 数组。

8.6 获取统计

GET /api/v1/attendance/statistics

返回:

{
  "total": 120,
  "today": 8,
  "server_time": "2026-05-01 10:00:00"
}

8.7 清空记录

POST /api/v1/attendance/records/clear

成功后会删除 records 表中的所有记录,并提升同步版本。

8.8 删除单条记录

DELETE /api/v1/attendance/records?timestamp=2026-05-01%2008%3A10%3A00&student_id=20240001

成功后提升同步版本。

8.9 时间段配置

获取:

GET /api/v1/config/time-slots

保存:

POST /api/v1/config/time-slots
X-Encrypted: AES128-CBC
Content-Type: application/octet-stream

请求体解密后的 JSON 结构:

{
  "time_slots": [
    {
      "name": "上午签到",
      "start_time": "08:00",
      "end_time": "09:30",
      "enabled": true
    }
  ]
}

当前默认值是:

{
  "time_slots": []
}

8.10 重复签到检查

GET /api/v1/attendance/duplicates?student_id=20240001&slot_start=08:00&slot_end=09:30

用于 STM32 在线时补充查重。

8.11 今日某时段已签到记录

GET /api/v1/attendance/today?slot_start=08:00&slot_end=09:30

返回:

{
  "date": "2026-05-01",
  "records": ["20240001", "20240002"]
}

STM32 开机后用它构建本地今日缓存。

8.12 学生档案

GET /api/v1/students
POST /api/v1/students

返回或提交格式:

{
  "students": [
    {
      "student_id": "20240001",
      "student_name": "张三",
      "class_id": "1",
      "status": "active"
    }
  ]
}

其中 student_id 为学号,student_name 为姓名,class_id 为所属班级编号。保存后服务端写入 students 表并提升同步版本。Qt 管理端修改学号前会弹窗提示该操作会影响人脸绑定、考勤显示、班级筛选和导出结果。

8.13 班级信息

GET /api/v1/classes
POST /api/v1/classes

返回或提交格式:

{
  "classes": [
    {
      "class_id": "1",
      "class_name": "软件工程一班"
    }
  ]
}

保存班级时服务端写入 classes 表并提升同步版本。删除班级前会检查该班级下是否还有未删除学生;如果仍有学生,接口返回失败,避免产生悬空班级归属。

8.14 人脸照片

照片列表:

GET /api/v1/faces/photos

返回:

{
  "photos": [
    {
      "id": "1",
      "filename": "1.jpg"
    }
  ]
}

上传照片:

POST /api/v1/faces/photos/1
Content-Type: image/jpeg

下载照片:

GET /api/v1/faces/photos/1

成功时返回 image/jpeg。

8.15 人脸档案

GET /api/v1/face_profiles
POST /api/v1/face_profiles

返回或提交格式:

{
  "face_profiles": [
    {
      "face_id": "1",
      "student_id": "20240001",
      "device_id": "k210-01",
      "status": "active",
      "has_photo": true
    }
  ]
}

保存人脸档案后服务端会:

  1. 写入结构化表 face_profiles。
  2. 对已取消绑定的人脸仅清空 student_id,保留原始照片和 face_id。
  3. 提升同步版本。
  4. 设置 K210 同步请求标志。

8.16 删除人脸

DELETE /api/v1/faces/20240001
DELETE /api/v1/faces/20240001?delete_records=true

服务端会:

  1. 删除相关照片。
  2. 解除 face_profiles 中的人脸绑定。
  3. 如果 delete_records=true,删除该学生相关考勤记录。
  4. 创建 K210 待删除任务。
  5. 提升同步版本。
  6. 设置同步请求标志。

返回示例:

{
  "success": true,
  "photo_deleted": true,
  "profile_unbound": true,
  "records_deleted": 3,
  "k210_pending": true,
  "sync_requested": true
}

9. 统一同步接口

9.1 获取版本

GET /api/v1/sync/version

返回:

{
  "version": 6,
  "server_time": "2026-05-01 10:00:00"
}

9.2 获取同步状态

GET /api/v1/sync/state?client=<client>&last_version=<version>

client 可取:

client 服务端返回内容
stm32 time_slots
k210 face_profiles、pending_delete、delete_tasks
qt 记录、统计、时间段、学生档案、班级信息、人脸档案、照片列表、学生 ID
all 或空 尽可能返回全部

如果没有变化:

{
  "version": 6,
  "changed": false,
  "client": "stm32",
  "server_time": "2026-05-01 10:00:00"
}

如果有变化:

{
  "version": 7,
  "changed": true,
  "client": "k210",
  "server_time": "2026-05-01 10:00:00",
  "state": {
    "face_profiles": [],
    "pending_delete": [],
    "delete_tasks": []
  }
}

9.3 同步确认

POST /api/v1/sync/ack

请求体:

{
  "client": "k210",
  "version": 7,
  "deleted": ["1"],
  "task_ids": ["task-1"]
}

用途:

  1. 记录客户端已经确认的版本。
  2. 移除已经完成的删除任务。
  3. 必要时再次提升同步版本。

10. 编译与运行

10.1 Windows MinGW 示例

如果已经装好 MinGW、OpenSSL、SQLite:

g++ -o server_encrypted.exe server_encrypted.cpp -lws2_32 -lcrypto -lssl -lsqlite3 -std=c++11
.\server_encrypted.exe

10.2 Linux 示例

g++ -o server_encrypted server_encrypted.cpp -lcrypto -lssl -lsqlite3 -std=c++11 -pthread
./server_encrypted

10.3 运行目录建议

建议在 server/ 目录下运行,这样数据库、照片目录和备份目录都会生成在 server/ 下面:

cd D:\University\graduation\thesis\code\server
.\server_encrypted.exe

11. 安全注意事项

  1. 默认 Token 已写在代码里,不适合直接暴露公网。
  2. 业务 JSON 已使用应用层 AES 加密,公网部署仍建议放到 HTTPS 反向代理后面,保护照片二进制、Token 和传输元数据。
  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/ 权限