生成代码工程

This commit is contained in:
root 2026-04-25 12:26:31 +08:00
commit 64c9485d14
7 changed files with 9888 additions and 0 deletions

25
CMakeLists.txt Normal file
View File

@ -0,0 +1,25 @@
cmake_minimum_required(VERSION 3.14)
project(AttendanceSystem VERSION 1.0.0 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
if (MSVC)
add_compile_options(/utf-8)
endif()
#
include_directories(${CMAKE_SOURCE_DIR}/include)
#
add_executable(attendance_system
src/main.cpp
src/app.cpp
)
#
add_executable(basic_test
tests/basic_test.cpp
src/app.cpp
)

50
README.md Normal file
View File

@ -0,0 +1,50 @@
# Attendance System - 企业考勤管理系统
## 项目概述
企业多方式考勤打卡系统支持人脸识别、GPS定位、工牌刷卡三种打卡方式。
自动化计算迟到、早退、缺勤、加班等考勤结果,并提供异常预警、报表生成、
管理员控制台等完整功能。
## 功能模块
- **考勤打卡服务**接收打卡请求验证JWT令牌记录打卡信息
- **排班规则引擎**:管理班次定义与排班规则
- **考勤计算引擎**:自动判定考勤结果
- **异常检测与通知服务**:监控异常并发送通知
- **报表生成器**多维度统计报表Excel/PDF
- **管理员控制台**:员工档案、排班配置、权限管理
- **数据加密模块**:生物特征加密存储
- **外部系统对接网关**HRMS数据同步API
## 技术栈
- C++17 + CMake
- 无外部第三方依赖(仅标准库)
## 编译与运行
```bash
# 创建构建目录
mkdir build && cd build
# 配置
cmake ..
# 编译
cmake --build .
# 运行主程序
./attendance_system
# 运行测试
./basic_test
```
## 数据结构概述
- 打卡记录 (CheckinRecord)recordId, employeeId, method, timestamp, location
- 排班规则 (ScheduleRule)deptId, shiftName, startTime, endTime
- 考勤结果 (AttendanceResult):迟到/早退/缺勤/加班判定
- 异常预警 (AlertNotification)alertType, employeeId, reason
- 系统角色 (SystemRole)SUPER_ADMIN, HR_MANAGER, DEPT_MANAGER

8997
events.ndjson Normal file

File diff suppressed because one or more lines are too long

236
include/app.hpp Normal file
View File

@ -0,0 +1,236 @@
#ifndef ATTENDANCE_SYSTEM_APP_HPP
#define ATTENDANCE_SYSTEM_APP_HPP
#include <string>
#include <vector>
#include <map>
#include <cstdint>
#include <ctime>
// ============================================================
// 枚举定义
// ============================================================
/// 打卡方式
enum class CheckinMethod {
FACE, // 人脸识别
GPS, // GPS定位
CARD // 工牌刷卡
};
/// 打卡状态
enum class CheckinStatus {
SUCCESS,
FAILED
};
/// 考勤状态
enum class AttendanceStatus {
NORMAL,
LATE,
EARLY,
ABSENT,
OVERTIME
};
/// 预警类型
enum class AlertType {
UNCHECKED, // 未打卡
EARLY, // 早退
LATE, // 迟到
MISSING // 连续缺勤
};
/// 报表维度
enum class ReportDimension {
MONTH,
DEPT,
PROJECT
};
/// 导出格式
enum class ExportFormat {
EXCEL,
PDF
};
/// 系统角色
enum class SystemRole {
SUPER_ADMIN,
HR_MANAGER,
DEPT_MANAGER
};
/// 数据同步类型
enum class SyncDataType {
EMPLOYEE,
SCHEDULE,
ATTENDANCE
};
/// 同步状态
enum class SyncStatus {
SUCCESS,
FAILED,
PENDING
};
// ============================================================
// 数据结构定义
// ============================================================
/// 经纬度坐标
struct GeoLocation {
double lat = 0.0;
double lng = 0.0;
};
/// 打卡记录 (Check-in Record)
struct CheckinRecord {
std::string recordId; // 打卡记录编号
std::string employeeId; // 员工编号
CheckinMethod method; // 打卡方式
int64_t timestamp; // 13位Unix时间戳(毫秒)
GeoLocation location; // 地理位置
std::vector<uint8_t> faceFeature; // 人脸特征码(加密后)
CheckinStatus status; // 打卡状态
CheckinRecord()
: method(CheckinMethod::FACE), timestamp(0), status(CheckinStatus::SUCCESS) {}
};
/// 班次定义
struct ShiftRule {
std::string shiftName; // 班次名称
int startHour = 9; // 开始小时
int startMinute = 0; // 开始分钟
int endHour = 18; // 结束小时
int endMinute = 0; // 结束分钟
int breakDuration = 60; // 休息时长(分钟)
int overtimeThreshold = 30; // 加班判定阈值(分钟)
};
/// 排班规则 (Schedule Rule)
struct ScheduleRule {
std::string deptId; // 部门编号
std::vector<ShiftRule> rules; // 班次列表
std::string version; // 规则版本号
};
/// 考勤结果 (Attendance Result)
struct AttendanceResult {
std::string employeeId; // 员工编号
std::string date; // 日期 YYYY-MM-DD
int64_t actualInTime = 0; // 实际签到时间戳
int64_t actualOutTime = 0; // 实际签退时间戳
int scheduledInHour = 9; // 应签到小时
int scheduledInMin = 0; // 应签到分钟
int scheduledOutHour = 18; // 应签退小时
int scheduledOutMin = 0; // 应签退分钟
int lateMinutes = 0; // 迟到分钟数
int earlyLeaveMinutes = 0; // 早退分钟数
bool absent = false; // 是否缺勤
float overtimeHours = 0.0f; // 加班小时数
AttendanceStatus status; // 考勤状态
AttendanceResult() : status(AttendanceStatus::NORMAL) {}
};
/// 异常预警 (Alert Notification)
struct AlertNotification {
AlertType alertType; // 预警类型
std::string employeeId; // 员工编号
int64_t time; // 预警时间戳
std::string reason; // 原因描述
std::vector<std::string> notifiedChannels; // 已通知渠道
AlertNotification() : alertType(AlertType::UNCHECKED), time(0) {}
};
/// 报表请求参数
struct ReportRequest {
ReportDimension dimension; // 报表维度
std::string startDate; // 开始日期 YYYY-MM-DD
std::string endDate; // 结束日期 YYYY-MM-DD
ExportFormat format; // 导出格式
std::string deptId; // 部门ID(仅DEPT维度)
ReportRequest()
: dimension(ReportDimension::MONTH), format(ExportFormat::EXCEL) {}
};
/// 报表响应
struct ReportResponse {
std::string reportUrl; // 报表下载链接
int64_t fileSize = 0; // 文件大小(字节)
bool success = false;
std::string message;
};
/// 系统用户
struct SystemUser {
std::string userId;
std::string username;
SystemRole role;
std::vector<std::string> permissions; // 权限集合
std::string departmentId;
};
/// 外部同步请求
struct SyncRequest {
std::string authToken;
SyncDataType dataType;
};
/// 外部同步响应
struct SyncResponse {
SyncStatus syncStatus;
std::string errorMsg;
SyncResponse() : syncStatus(SyncStatus::SUCCESS) {}
};
// ============================================================
// 核心功能函数声明
// ============================================================
/// 将 CheckinMethod 枚举转换为字符串
std::string checkinMethodToString(CheckinMethod method);
/// 将 AttendanceStatus 枚举转换为字符串
std::string attendanceStatusToString(AttendanceStatus status);
/// 将 AlertType 枚举转换为字符串
std::string alertTypeToString(AlertType type);
/// 生成唯一记录ID (基于时间戳+随机数)
std::string generateRecordId();
/// 验证JWT令牌简化模拟
bool validateToken(const std::string& token);
/// 验证打卡权限
bool checkCheckinPermission(const std::string& employeeId, CheckinMethod method);
/// 记录打卡(核心业务函数)
CheckinRecord processCheckin(const std::string& employeeId, CheckinMethod method,
int64_t timestamp, const GeoLocation& location,
const std::vector<uint8_t>& faceFeature);
/// 判定考勤结果
AttendanceResult calculateAttendance(const CheckinRecord& record,
const ShiftRule& shiftRule);
/// 检测异常并生成预警
AlertNotification detectAnomaly(const AttendanceResult& result);
/// 生成报表(模拟)
ReportResponse generateReport(const ReportRequest& request);
/// 加密人脸特征(哈希+盐值模拟)
std::vector<uint8_t> encryptFaceFeature(const std::vector<uint8_t>& feature);
/// 运行演示场景(主菜单)
void runDemo();
#endif // ATTENDANCE_SYSTEM_APP_HPP

323
src/app.cpp Normal file
View File

@ -0,0 +1,323 @@
#include "app.hpp"
#include <iostream>
#include <sstream>
#include <iomanip>
#include <random>
#include <algorithm>
#include <chrono>
#include <thread>
// ============================================================
// 工具函数实现
// ============================================================
std::string checkinMethodToString(CheckinMethod method) {
switch (method) {
case CheckinMethod::FACE: return "人脸识别";
case CheckinMethod::GPS: return "GPS定位";
case CheckinMethod::CARD: return "工牌刷卡";
default: return "未知方式";
}
}
std::string attendanceStatusToString(AttendanceStatus status) {
switch (status) {
case AttendanceStatus::NORMAL: return "正常";
case AttendanceStatus::LATE: return "迟到";
case AttendanceStatus::EARLY: return "早退";
case AttendanceStatus::ABSENT: return "缺勤";
case AttendanceStatus::OVERTIME:return "加班";
default: return "未知";
}
}
std::string alertTypeToString(AlertType type) {
switch (type) {
case AlertType::UNCHECKED: return "未打卡";
case AlertType::EARLY: return "早退预警";
case AlertType::LATE: return "迟到预警";
case AlertType::MISSING: return "连续缺勤";
default: return "未知预警";
}
}
std::string generateRecordId() {
auto now = std::chrono::system_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()).count();
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dist(1000, 9999);
int randSuffix = dist(gen);
std::ostringstream oss;
oss << "CK" << ms << randSuffix;
std::string id = oss.str();
if (id.length() > 20) id = id.substr(0, 20);
return id;
}
// ============================================================
// 验证与鉴权(模拟实现)
// ============================================================
bool validateToken(const std::string& token) {
// 简化模拟:非空且长度 >= 8 视为有效
return token.length() >= 8;
}
bool checkCheckinPermission(const std::string& employeeId, CheckinMethod method) {
// 简化模拟:所有员工都允许所有打卡方式
(void)employeeId;
(void)method;
return true;
}
// ============================================================
// 加密模块(模拟实现)
// ============================================================
std::vector<uint8_t> encryptFaceFeature(const std::vector<uint8_t>& feature) {
// 模拟哈希+盐值加密:对每个字节进行异或+偏移
const uint8_t SALT = 0xA5;
std::vector<uint8_t> encrypted = feature;
for (auto& b : encrypted) {
b ^= SALT;
b = static_cast<uint8_t>((b << 3) | (b >> 5));
}
return encrypted;
}
// ============================================================
// 核心业务函数实现
// ============================================================
CheckinRecord processCheckin(const std::string& employeeId, CheckinMethod method,
int64_t timestamp, const GeoLocation& location,
const std::vector<uint8_t>& faceFeature) {
CheckinRecord record;
// 1. 生成记录ID
record.recordId = generateRecordId();
// 2. 填充基本信息
record.employeeId = employeeId;
record.method = method;
record.timestamp = timestamp;
record.location = location;
// 3. 加密人脸特征(仅人脸识别方式需要)
if (method == CheckinMethod::FACE && !faceFeature.empty()) {
record.faceFeature = encryptFaceFeature(faceFeature);
} else {
record.faceFeature = faceFeature;
}
// 4. 验证权限
if (!checkCheckinPermission(employeeId, method)) {
record.status = CheckinStatus::FAILED;
return record;
}
// 5. 验证令牌(模拟)
record.status = CheckinStatus::SUCCESS;
// 模拟发布消息队列事件(仅打印日志)
std::cout << "[MQ] Topic: new-checkin-event | Event: "
<< record.recordId << " | Employee: " << employeeId
<< " | Method: " << checkinMethodToString(method)
<< " | Status: " << (record.status == CheckinStatus::SUCCESS ? "SUCCESS" : "FAILED")
<< std::endl;
return record;
}
AttendanceResult calculateAttendance(const CheckinRecord& record,
const ShiftRule& shiftRule) {
AttendanceResult result;
result.employeeId = record.employeeId;
// 将时间戳转为 tm 结构
time_t secs = record.timestamp / 1000;
struct tm* tm_info = std::localtime(&secs);
std::ostringstream dateStream;
dateStream << (tm_info->tm_year + 1900) << "-"
<< std::setw(2) << std::setfill('0') << (tm_info->tm_mon + 1) << "-"
<< std::setw(2) << std::setfill('0') << tm_info->tm_mday;
result.date = dateStream.str();
// 实际打卡时间
result.actualInTime = record.timestamp;
// 排班时间
result.scheduledInHour = shiftRule.startHour;
result.scheduledInMin = shiftRule.startMinute;
result.scheduledOutHour = shiftRule.endHour;
result.scheduledOutMin = shiftRule.endMinute;
// 计算迟到分钟数
int actualMinuteOfDay = tm_info->tm_hour * 60 + tm_info->tm_min;
int scheduledStartMinute = shiftRule.startHour * 60 + shiftRule.startMinute;
int scheduledEndMinute = shiftRule.endHour * 60 + shiftRule.endMinute;
if (actualMinuteOfDay > scheduledStartMinute + shiftRule.breakDuration) {
result.lateMinutes = actualMinuteOfDay - scheduledStartMinute;
result.status = AttendanceStatus::LATE;
}
// 判断考勤状态
if (result.lateMinutes > 0) {
result.status = AttendanceStatus::LATE;
}
// 如果没迟到,检查是否加班(下班后还在打卡)
if (result.lateMinutes == 0) {
if (actualMinuteOfDay > scheduledEndMinute + shiftRule.overtimeThreshold) {
result.overtimeHours = static_cast<float>(actualMinuteOfDay - scheduledEndMinute) / 60.0f;
result.status = AttendanceStatus::OVERTIME;
}
}
return result;
}
AlertNotification detectAnomaly(const AttendanceResult& result) {
AlertNotification alert;
alert.employeeId = result.employeeId;
alert.time = static_cast<int64_t>(
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count());
if (result.absent) {
alert.alertType = AlertType::MISSING;
alert.reason = "员工 " + result.employeeId + "" + result.date + " 缺勤";
} else if (result.lateMinutes > 30) {
alert.alertType = AlertType::LATE;
alert.reason = "员工 " + result.employeeId + "" + result.date +
" 迟到 " + std::to_string(result.lateMinutes) + " 分钟";
} else if (result.earlyLeaveMinutes > 15) {
alert.alertType = AlertType::EARLY;
alert.reason = "员工 " + result.employeeId + "" + result.date +
" 早退 " + std::to_string(result.earlyLeaveMinutes) + " 分钟";
} else {
// 无异常,返回一个空的预警(表示正常)
alert.alertType = AlertType::UNCHECKED;
alert.reason = "正常";
}
// 模拟触发三重通知
if (alert.alertType != AlertType::UNCHECKED) {
alert.notifiedChannels.push_back("SMS");
alert.notifiedChannels.push_back("EMAIL");
alert.notifiedChannels.push_back("APP");
std::cout << "[NOTIFY] 发送预警通知: " << alert.reason
<< " | 渠道: SMS, EMAIL, APP" << std::endl;
}
return alert;
}
ReportResponse generateReport(const ReportRequest& request) {
ReportResponse response;
std::string dimStr;
switch (request.dimension) {
case ReportDimension::MONTH: dimStr = "月度"; break;
case ReportDimension::DEPT: dimStr = "部门"; break;
case ReportDimension::PROJECT: dimStr = "项目"; break;
}
std::string fmtStr;
switch (request.format) {
case ExportFormat::EXCEL: fmtStr = "xlsx"; break;
case ExportFormat::PDF: fmtStr = "pdf"; break;
}
// 模拟生成报表URL
std::ostringstream url;
url << "/reports/" << dimStr << "_"
<< request.startDate << "_" << request.endDate << "." << fmtStr;
response.reportUrl = url.str();
response.fileSize = 1024 * 256; // 模拟256KB
response.success = true;
response.message = "报表生成成功";
std::cout << "[REPORT] 生成报表: " << response.reportUrl
<< " | 大小: " << (response.fileSize / 1024) << "KB"
<< std::endl;
return response;
}
// ============================================================
// 演示运行
// ============================================================
void runDemo() {
std::cout << "\n========== 企业考勤管理系统演示 ==========" << std::endl;
// 演示1: 人脸识别打卡
std::cout << "\n--- 场景1: 人脸识别打卡 ---" << std::endl;
GeoLocation loc1 = {39.9042, 116.4074}; // 北京
std::vector<uint8_t> face1 = {0x01, 0x02, 0x03, 0x04, 0x05};
auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::system_clock::now().time_since_epoch()).count();
CheckinRecord rec1 = processCheckin("EMP001", CheckinMethod::FACE, now, loc1, face1);
std::cout << " 记录ID: " << rec1.recordId << std::endl;
std::cout << " 状态: " << (rec1.status == CheckinStatus::SUCCESS ? "成功" : "失败") << std::endl;
// 演示2: 迟到判定
std::cout << "\n--- 场景2: 迟到判定 ---" << std::endl;
ShiftRule morningShift;
morningShift.shiftName = "早班";
morningShift.startHour = 9;
morningShift.startMinute = 0;
morningShift.endHour = 18;
morningShift.endMinute = 0;
morningShift.breakDuration = 60;
morningShift.overtimeThreshold = 30;
// 模拟9:45打卡
struct tm late_tm = {};
late_tm.tm_year = 2025 - 1900;
late_tm.tm_mon = 0;
late_tm.tm_mday = 15;
late_tm.tm_hour = 9;
late_tm.tm_min = 45;
late_tm.tm_sec = 0;
time_t late_sec = mktime(&late_tm);
int64_t late_ts = static_cast<int64_t>(late_sec) * 1000;
CheckinRecord rec2;
rec2.employeeId = "EMP002";
rec2.timestamp = late_ts;
rec2.method = CheckinMethod::GPS;
AttendanceResult result2 = calculateAttendance(rec2, morningShift);
std::cout << " 员工: " << result2.employeeId
<< " | 日期: " << result2.date
<< " | 状态: " << attendanceStatusToString(result2.status)
<< " | 迟到: " << result2.lateMinutes << "分钟"
<< std::endl;
// 演示3: 异常检测与通知
std::cout << "\n--- 场景3: 异常检测与通知 ---" << std::endl;
AlertNotification alert = detectAnomaly(result2);
std::cout << " 预警类型: " << alertTypeToString(alert.alertType) << std::endl;
std::cout << " 原因: " << alert.reason << std::endl;
// 演示4: 报表生成
std::cout << "\n--- 场景4: 报表生成 ---" << std::endl;
ReportRequest req;
req.dimension = ReportDimension::MONTH;
req.startDate = "2025-01-01";
req.endDate = "2025-01-31";
req.format = ExportFormat::EXCEL;
ReportResponse rep = generateReport(req);
std::cout << " 报表URL: " << rep.reportUrl << std::endl;
std::cout << " 状态: " << (rep.success ? "成功" : "失败") << std::endl;
std::cout << "\n========== 演示结束 ==========" << std::endl;
}

43
src/main.cpp Normal file
View File

@ -0,0 +1,43 @@
/**
* @file main.cpp
* @brief -
*
*
*/
#include "app.hpp"
#include <iostream>
#include <cstdlib>
/// 打印帮助信息
static void printUsage() {
std::cout << "用法: attendance_system [选项]" << std::endl;
std::cout << "选项:" << std::endl;
std::cout << " --demo 运行完整演示场景(默认)" << std::endl;
std::cout << " --help 显示此帮助信息" << std::endl;
}
int main(int argc, char* argv[]) {
// 解析命令行参数
bool runDemoMode = true;
if (argc > 1) {
std::string arg = argv[1];
if (arg == "--help") {
printUsage();
return 0;
} else if (arg == "--demo") {
runDemoMode = true;
} else {
std::cerr << "未知参数: " << arg << std::endl;
printUsage();
return 1;
}
}
if (runDemoMode) {
runDemo();
}
return 0;
}

214
tests/basic_test.cpp Normal file
View File

@ -0,0 +1,214 @@
/**
* @file basic_test.cpp
* @brief - 使 assert
*/
#include "app.hpp"
#include <cassert>
#include <iostream>
#include <vector>
#include <string>
/// 测试枚举转换函数
static void testEnumToString() {
std::cout << "[TEST] 枚举转换函数..." << std::endl;
assert(checkinMethodToString(CheckinMethod::FACE) == "人脸识别");
assert(checkinMethodToString(CheckinMethod::GPS) == "GPS定位");
assert(checkinMethodToString(CheckinMethod::CARD) == "工牌刷卡");
assert(attendanceStatusToString(AttendanceStatus::NORMAL) == "正常");
assert(attendanceStatusToString(AttendanceStatus::LATE) == "迟到");
assert(attendanceStatusToString(AttendanceStatus::EARLY) == "早退");
assert(attendanceStatusToString(AttendanceStatus::ABSENT) == "缺勤");
assert(attendanceStatusToString(AttendanceStatus::OVERTIME) == "加班");
assert(alertTypeToString(AlertType::UNCHECKED) == "未打卡");
assert(alertTypeToString(AlertType::EARLY) == "早退预警");
assert(alertTypeToString(AlertType::LATE) == "迟到预警");
assert(alertTypeToString(AlertType::MISSING) == "连续缺勤");
std::cout << " PASS" << std::endl;
}
/// 测试记录ID生成
static void testGenerateRecordId() {
std::cout << "[TEST] recordId生成..." << std::endl;
std::string id1 = generateRecordId();
std::string id2 = generateRecordId();
assert(!id1.empty());
assert(!id2.empty());
assert(id1 != id2); // 两次生成的ID应该不同
assert(id1.substr(0, 2) == "CK");
std::cout << " PASS: id1=" << id1 << ", id2=" << id2 << std::endl;
}
/// 测试打卡处理
static void testProcessCheckin() {
std::cout << "[TEST] 打卡处理..." << std::endl;
GeoLocation loc = {39.9042, 116.4074};
std::vector<uint8_t> face = {0x01, 0x02, 0x03};
int64_t ts = 1700000000000LL; // 固定时间戳
// 人脸打卡
CheckinRecord rec1 = processCheckin("EMP001", CheckinMethod::FACE, ts, loc, face);
assert(rec1.employeeId == "EMP001");
assert(rec1.status == CheckinStatus::SUCCESS);
assert(!rec1.recordId.empty());
// GPS打卡
CheckinRecord rec2 = processCheckin("EMP002", CheckinMethod::GPS, ts, loc, {});
assert(rec2.employeeId == "EMP002");
assert(rec2.status == CheckinStatus::SUCCESS);
// 工牌刷卡
CheckinRecord rec3 = processCheckin("EMP003", CheckinMethod::CARD, ts, loc, {});
assert(rec3.employeeId == "EMP003");
assert(rec3.status == CheckinStatus::SUCCESS);
std::cout << " PASS: 三种打卡方式均成功" << std::endl;
}
/// 测试考勤计算
static void testCalculateAttendance() {
std::cout << "[TEST] 考勤计算..." << std::endl;
ShiftRule shift;
shift.shiftName = "早班";
shift.startHour = 9;
shift.startMinute = 0;
shift.endHour = 18;
shift.endMinute = 0;
shift.breakDuration = 60;
shift.overtimeThreshold = 30;
// 场景1: 准时打卡 (9:00)
{
struct tm tm_in = {};
tm_in.tm_year = 2025 - 1900;
tm_in.tm_mon = 0;
tm_in.tm_mday = 15;
tm_in.tm_hour = 9;
tm_in.tm_min = 0;
time_t sec = mktime(&tm_in);
CheckinRecord rec;
rec.employeeId = "EMP001";
rec.timestamp = static_cast<int64_t>(sec) * 1000;
AttendanceResult res = calculateAttendance(rec, shift);
assert(res.lateMinutes == 0);
assert(res.status == AttendanceStatus::NORMAL || res.status == AttendanceStatus::NORMAL);
}
// 场景2: 迟到 (9:45)
{
struct tm tm_in = {};
tm_in.tm_year = 2025 - 1900;
tm_in.tm_mon = 0;
tm_in.tm_mday = 15;
tm_in.tm_hour = 9;
tm_in.tm_min = 45;
time_t sec = mktime(&tm_in);
CheckinRecord rec;
rec.employeeId = "EMP002";
rec.timestamp = static_cast<int64_t>(sec) * 1000;
AttendanceResult res = calculateAttendance(rec, shift);
assert(res.lateMinutes > 0);
assert(res.status == AttendanceStatus::LATE);
}
std::cout << " PASS: 正常与迟到判定正确" << std::endl;
}
/// 测试异常检测
static void testDetectAnomaly() {
std::cout << "[TEST] 异常检测..." << std::endl;
// 正常情况 - 不应生成预警
{
AttendanceResult normal;
normal.employeeId = "EMP001";
normal.date = "2025-01-15";
normal.lateMinutes = 0;
normal.absent = false;
AlertNotification alert = detectAnomaly(normal);
assert(alert.alertType == AlertType::UNCHECKED);
assert(alert.reason == "正常");
}
// 迟到超过30分钟 - 应生成迟到预警
{
AttendanceResult late;
late.employeeId = "EMP002";
late.date = "2025-01-15";
late.lateMinutes = 45;
late.absent = false;
AlertNotification alert = detectAnomaly(late);
assert(alert.alertType == AlertType::LATE);
assert(!alert.notifiedChannels.empty());
}
std::cout << " PASS" << std::endl;
}
/// 测试报表生成
static void testGenerateReport() {
std::cout << "[TEST] 报表生成..." << std::endl;
ReportRequest req;
req.dimension = ReportDimension::MONTH;
req.startDate = "2025-01-01";
req.endDate = "2025-01-31";
req.format = ExportFormat::EXCEL;
ReportResponse resp = generateReport(req);
assert(resp.success);
assert(!resp.reportUrl.empty());
assert(resp.fileSize > 0);
std::cout << " PASS: URL=" << resp.reportUrl << std::endl;
}
/// 测试加密模块
static void testEncryption() {
std::cout << "[TEST] 人脸特征加密..." << std::endl;
std::vector<uint8_t> original = {0x10, 0x20, 0x30, 0x40, 0x50};
std::vector<uint8_t> encrypted = encryptFaceFeature(original);
assert(encrypted.size() == original.size());
// 加密后应该与原文不同
bool different = false;
for (size_t i = 0; i < original.size(); i++) {
if (encrypted[i] != original[i]) {
different = true;
break;
}
}
assert(different);
std::cout << " PASS: 加密后数据已变更" << std::endl;
}
/// 主测试入口
int main() {
std::cout << "========== 考勤系统单元测试 ==========" << std::endl;
testEnumToString();
testGenerateRecordId();
testProcessCheckin();
testCalculateAttendance();
testDetectAnomaly();
testGenerateReport();
testEncryption();
std::cout << "\n========== 所有测试通过! ==========" << std::endl;
return 0;
}