推送代码工程

This commit is contained in:
root 2026-04-25 16:10:44 +08:00
parent ff76e7467f
commit 083c7fb454
5 changed files with 497 additions and 1014 deletions

View File

@ -1,29 +1,38 @@
cmake_minimum_required(VERSION 3.14)
project(AttendanceSystem VERSION 1.0.0 LANGUAGES CXX)
project(AttendanceSystem LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDebugDLL")
# MSVC UTF-8
if (MSVC)
add_compile_options(/utf-8)
# MSVC utf-8 BOM
if(MSVC)
add_compile_options("/utf-8")
endif()
#
add_executable(attendance_system
src/main.cpp
# Google Test
find_package(GTest REQUIRED)
include(GoogleTest)
#
add_library(attendance_lib STATIC
src/app.cpp
include/app.hpp
)
target_include_directories(attendance_lib PUBLIC include)
target_include_directories(attendance_system PRIVATE include)
#
add_executable(basic_test
tests/basic_test.cpp
src/app.cpp
include/app.hpp
)
#
target_link_libraries(attendance_lib PUBLIC)
add_executable(attendance_system src/main.cpp)
target_link_libraries(attendance_system PRIVATE attendance_lib)
#
get_filename_component(TEST_SRC tests/basic_test.cpp NAME)
add_executable(basic_test tests/basic_test.cpp)
target_link_libraries(basic_test PRIVATE attendance_lib GTest::GTest GTest::Main)
target_include_directories(basic_test PRIVATE include)
gtest_discover_tests(basic_test)
#
# add_executable(basic_test_old tests/basic_test_old.cpp)
# target_link_libraries(basic_test_old PRIVATE attendance_lib)

View File

@ -1,240 +1,146 @@
#ifndef ATTENDANCE_SYSTEM_APP_HPP
#define ATTENDANCE_SYSTEM_APP_HPP
/**
* @file app.hpp
* @brief
*
*
* - SRS-ATT_F-001:
* - SRS-ATT_F-002:
* - SRS-ATT_F-003:
* - SRS-ATT_F-004:
* - SRS-ATT_F-005:
*/
#ifndef APP_HPP
#define APP_HPP
#include <string>
#include <vector>
#include <map>
#include <ctime>
#include <memory>
#include <functional>
#include <algorithm>
#include <sstream>
#include <iomanip>
#include <stdexcept>
#include <cassert>
// ============================================================
// 常量与枚举
// ============================================================
// ---------- 数据结构 ----------
/// 打卡类型
enum class CheckType {
Unknown,
ClockIn, // 上班打卡
ClockOut // 下班打卡
// 打卡请求SRS-ATT_F-001 输入)
struct PunchRequest {
std::string employeeId; // 员工唯一标识
std::string terminalType; // 终端类型MOBILE / WEB / MACHINE
std::string deviceId; // 设备标识
std::time_t timestamp; // 打卡时间戳(降级模式下仅缓存此字段)
double latitude; // 纬度(可选)
double longitude; // 经度(可选)
};
/// 异常标记
enum class AbnormalFlag {
None,
Late, // 迟到
EarlyLeave, // 早退
Absenteeism // 旷工
// 打卡结果响应SRS-ATT_F-001 输出)
struct PunchResponse {
bool success;
std::string message;
std::time_t serverTime;
};
/// 报表输出格式
enum class ReportFormat {
Excel,
PDF
};
/// 预警发送状态
enum class AlertStatus {
Pending,
Sent,
Failed,
Read
};
/// 角色类型RBAC
enum class RoleType {
Employee,
DeptManager,
HRAdmin,
SystemAdmin
};
// ============================================================
// 数据结构定义
// ============================================================
/// 员工信息
struct Employee {
std::string employeeId;
std::string name;
// 考勤规则配置SRS-ATT_F-002 输入)
struct AttendanceRule {
std::string department;
std::string position;
std::string shiftId;
std::string phone;
RoleType role{RoleType::Employee};
// 脱敏手机号显示后4位明文
std::string maskedPhone() const {
if (phone.length() != 11) return "***";
return "*******" + phone.substr(7);
}
std::string shift; // 班次
int startHour; // 上班时间(小时)
int startMinute;
int endHour; // 下班时间(小时)
int endMinute;
int lateThreshold; // 迟到容忍分钟数
int earlyLeaveThreshold; // 早退容忍分钟数
std::vector<std::string> holidays; // 节假日列表
};
/// 排班规则
struct ShiftRule {
std::string shiftId;
// 原始打卡记录SRS-ATT_F-002 输入)
struct RawRecord {
std::string employeeId;
std::time_t punchTime;
std::string terminalType;
};
// 核算结果SRS-ATT_F-002 输出SRS-ATT_F-003 输入)
struct CalculationResult {
std::string employeeId;
std::time_t date;
enum Status { NORMAL, LATE, EARLY_LEAVE, ABSENT, MISSING_CARD };
Status status;
std::string shiftName;
int startHour{9}; // 上班小时
int startMin{0}; // 上班分钟
int endHour{18}; // 下班小时
int endMin{0}; // 下班分钟
int flexWindow{30}; // 弹性时间窗口(分钟)
int graceLate{15}; // 迟到宽限分钟
/// 获取上班时间(分钟偏移量)
int workStartMinutes() const { return startHour * 60 + startMin; }
/// 获取下班时间(分钟偏移量)
int workEndMinutes() const { return endHour * 60 + endMin; }
std::time_t actualArrival;
std::time_t actualDeparture;
};
/// 打卡流水记录
struct CheckInRecord {
std::string recordId;
// 预警通知SRS-ATT_F-003 输出)
struct WarningNotification {
std::string employeeId;
std::time_t timestamp{0};
CheckType type{CheckType::Unknown};
std::string deviceId;
std::string latitude;
std::string longitude;
bool verified{false};
std::string managerId; // 管理员ID严重异常时填充
CalculationResult::Status anomalyType;
std::string message;
long long sendTimestamp;
};
/// 考勤核算结果(按天)
struct DailyAttendance {
std::string dateStr;
std::string employeeId;
int expectedMinutes{0}; // 应出勤分钟
int actualMinutes{0}; // 实际出勤分钟
int overtimeMinutes{0}; // 加班分钟
int leaveDeduction{0}; // 调休抵扣分钟
AbnormalFlag abnormal{AbnormalFlag::None};
/// 有效出勤率(%
double attendanceRate() const {
if (expectedMinutes == 0) return 100.0;
return 100.0 * actualMinutes / expectedMinutes;
}
/// 是否异常
bool hasAbnormal() const { return abnormal != AbnormalFlag::None; }
std::string abnormalStr() const {
switch (abnormal) {
case AbnormalFlag::Late: return "迟到";
case AbnormalFlag::EarlyLeave: return "早退";
case AbnormalFlag::Absenteeism: return "旷工";
default: return "正常";
}
}
// 报表查询条件SRS-ATT_F-004 输入)
struct ReportQuery {
std::string department;
std::string employeeId; // 可选,若为空则查部门
std::time_t startDate;
std::time_t endDate;
enum Format { EXCEL, PDF, ONLINE };
Format format;
};
/// 异常预警日志
struct AlertLog {
std::string alertId;
std::string employeeId;
AbnormalFlag type{AbnormalFlag::None};
std::time_t occurTime{0};
std::string channel; // 通知渠道:站内信/短信/企业微信
AlertStatus status{AlertStatus::Pending};
int retryCount{0};
};
/// 报表任务
struct ReportTask {
std::string taskId;
std::string requester;
std::string deptId;
std::string dateRangeBegin;
std::string dateRangeEnd;
ReportFormat format{ReportFormat::Excel};
std::string status{"Pending"}; // Pending / Running / Done / Failed
// 统计报表文件SRS-ATT_F-004 输出)
struct ReportFile {
std::string fileName;
std::string downloadUrl;
std::time_t createTime{0};
std::vector<char> rawData; // 二进制数据
};
/// 权限配置RBAC
struct Permission {
std::string resource; // 资源路径,如 "/api/v1/reports"
std::string action; // 操作READ / WRITE / DELETE
// 第三方同步请求SRS-ATT_F-005 输入)
struct ThirdPartySyncRequest {
std::string apiKey; // 鉴权密钥
std::string systemType; // HR / OA / DoorAccess
std::vector<RawRecord> records; // 需要同步的打卡记录
};
// ============================================================
// 业务逻辑类
// ============================================================
// 考勤同步数据SRS-ATT_F-005 输出)
struct SyncResponse {
bool success;
int syncedCount;
int failedCount;
std::string message;
};
class AttendanceService {
// ---------- 业务接口 ----------
class AttendanceSystem {
public:
AttendanceService();
AttendanceSystem();
~AttendanceSystem();
// ---- 员工管理 ----
void addEmployee(const Employee& emp);
const Employee* findEmployee(const std::string& id) const;
std::vector<Employee> getEmployeesByDept(const std::string& dept) const;
// SRS-ATT_F-001: 处理打卡请求移动端、Web端、考勤机
PunchResponse processPunch(const PunchRequest& req);
// ---- 排班管理 ----
void addShiftRule(const ShiftRule& rule);
const ShiftRule* findShift(const std::string& shiftId) const;
// SRS-ATT_F-002: 自动核算考勤数据(定时/事件触发)
void autoCalculateAttendance();
// ---- 打卡处理 ----
CheckInRecord processCheckIn(const std::string& employeeId,
CheckType type,
const std::string& deviceId,
std::time_t timestamp);
// SRS-ATT_F-003: 异常预警通知
void sendWarningForAnomaly(const CalculationResult& result);
std::vector<CheckInRecord> getRecords(const std::string& employeeId) const;
// SRS-ATT_F-004: 生成并导出报表
ReportFile generateReport(const ReportQuery& query);
// ---- 考勤核算 ----
DailyAttendance calculateDaily(const std::string& employeeId,
const std::string& dateStr);
std::vector<DailyAttendance> generateReport(const std::string& deptId,
const std::string& dateBegin,
const std::string& dateEnd);
// ---- 异常预警 ----
AlertLog createAlert(const std::string& employeeId, AbnormalFlag type);
std::vector<AlertLog> getAlerts(const std::string& employeeId) const;
// ---- 报表任务 ----
ReportTask createReportTask(const std::string& requester,
const std::string& deptId,
const std::string& dateBegin,
const std::string& dateEnd,
ReportFormat fmt);
void completeTask(const std::string& taskId, const std::string& url);
// ---- 工具 ----
static std::string timeToString(std::time_t t);
static std::string currentDateStr();
static std::time_t parseDate(const std::string& dateStr);
// SRS-ATT_F-005: 与第三方系统同步数据
SyncResponse syncWithThirdParty(const ThirdPartySyncRequest& request);
private:
std::map<std::string, Employee> employees_;
std::map<std::string, ShiftRule> shifts_;
std::vector<CheckInRecord> records_;
std::vector<AlertLog> alerts_;
std::vector<ReportTask> tasks_;
int idCounter_{0};
std::string nextId();
int minutesFromMidnight(std::time_t t) const;
bool isSameDate(std::time_t t, const std::string& dateStr) const;
// 辅助函数
bool validateEmployee(const std::string& employeeId);
AttendanceRule getRuleForEmployee(const std::string& employeeId);
void storeRawRecord(const RawRecord& rec);
void storeCalculationResult(const CalculationResult& res);
std::vector<RawRecord> fetchUncalculatedRecords();
CalculationResult calculateSingleRecord(const RawRecord& rec, const AttendanceRule& rule);
void logWarningSend(const WarningNotification& notif);
std::vector<std::string> getManagersForEmployee(const std::string& employeeId);
bool authenticateThirdParty(const std::string& apiKey);
};
// ============================================================
// 工具函数声明
// ============================================================
/// 脱敏工具:将敏感字符串中间部分替换为 '*'
std::string maskSensitive(const std::string& input, size_t visibleHead = 2, size_t visibleTail = 2);
/// 格式化输出一行分隔线
void printSeparator(char ch = '=', size_t count = 60);
#endif // ATTENDANCE_SYSTEM_APP_HPP
#endif // APP_HPP

View File

@ -1,345 +1,277 @@
/**
* @file app.cpp
* @brief
*
*
*/
#include "app.hpp"
#include <iostream>
#include <cstring>
#include <ctime>
#include <algorithm>
#include <chrono>
#include <random>
#include <sstream>
#include <iomanip>
// ============================================================
// 工具函数实现
// ============================================================
// 构造函数与析构函数
AttendanceSystem::AttendanceSystem() {}
AttendanceSystem::~AttendanceSystem() {}
std::string AttendanceService::timeToString(std::time_t t) {
std::tm* tm = std::localtime(&t);
if (!tm) return "InvalidTime";
std::ostringstream oss;
oss << std::put_time(tm, "%Y-%m-%d %H:%M:%S");
return oss.str();
}
// SRS-ATT_F-001: 多终端打卡支持
// 处理流程步骤1接收打卡请求 -> 步骤2终端识别与身份校验 -> 步骤4获取位置与时间戳 -> 步骤5规则匹配 -> 步骤6/7生成记录 -> 步骤8返回结果
PunchResponse AttendanceSystem::processPunch(const PunchRequest& req) {
PunchResponse resp;
// 步骤2: 终端类型识别与员工身份合法性校验
if (!validateEmployee(req.employeeId)) {
// 步骤3: 拒绝打卡并返回失败提示
resp.success = false;
resp.message = "员工身份校验失败";
resp.serverTime = std::time(nullptr);
return resp;
}
std::string AttendanceService::currentDateStr() {
// 步骤4: 获取员工位置信息与当前时间戳(降级模式下仅缓存时间戳)
std::time_t now = std::time(nullptr);
std::tm* tm = std::localtime(&now);
if (!tm) return "InvalidDate";
std::ostringstream oss;
oss << std::put_time(tm, "%Y-%m-%d");
return oss.str();
}
double lat = req.latitude;
double lon = req.longitude;
std::time_t AttendanceService::parseDate(const std::string& dateStr) {
std::tm tm = {};
std::istringstream iss(dateStr);
iss >> std::get_time(&tm, "%Y-%m-%d");
if (iss.fail()) return 0;
return std::mktime(&tm);
}
std::string maskSensitive(const std::string& input, size_t visibleHead, size_t visibleTail) {
if (input.length() <= visibleHead + visibleTail) return input;
std::string result = input.substr(0, visibleHead);
result.append(input.length() - visibleHead - visibleTail, '*');
result.append(input.substr(input.length() - visibleTail));
return result;
}
void printSeparator(char ch, size_t count) {
std::cout << std::string(count, ch) << std::endl;
}
// ============================================================
// AttendanceService 实现
// ============================================================
AttendanceService::AttendanceService() {
// 初始化默认排班
ShiftRule defaultShift;
defaultShift.shiftId = "S001";
defaultShift.shiftName = "标准白班";
defaultShift.startHour = 9;
defaultShift.startMin = 0;
defaultShift.endHour = 18;
defaultShift.endMin = 0;
defaultShift.flexWindow = 30;
defaultShift.graceLate = 15;
shifts_[defaultShift.shiftId] = defaultShift;
// 初始化默认管理员
Employee admin;
admin.employeeId = "ADMIN001";
admin.name = "系统管理员";
admin.department = "技术部";
admin.position = "运维工程师";
admin.shiftId = "S001";
admin.phone = "13800138000";
admin.role = RoleType::SystemAdmin;
employees_[admin.employeeId] = admin;
}
std::string AttendanceService::nextId() {
return "ID" + std::to_string(++idCounter_);
}
// ---- 员工管理 ----
void AttendanceService::addEmployee(const Employee& emp) {
if (employees_.count(emp.employeeId) > 0) {
throw std::runtime_error("员工已存在: " + emp.employeeId);
}
employees_[emp.employeeId] = emp;
}
const Employee* AttendanceService::findEmployee(const std::string& id) const {
auto it = employees_.find(id);
return (it != employees_.end()) ? &(it->second) : nullptr;
}
std::vector<Employee> AttendanceService::getEmployeesByDept(const std::string& dept) const {
std::vector<Employee> result;
for (const auto& [id, emp] : employees_) {
if (emp.department == dept) {
result.push_back(emp);
}
}
return result;
}
// ---- 排班管理 ----
void AttendanceService::addShiftRule(const ShiftRule& rule) {
shifts_[rule.shiftId] = rule;
}
const ShiftRule* AttendanceService::findShift(const std::string& shiftId) const {
auto it = shifts_.find(shiftId);
return (it != shifts_.end()) ? &(it->second) : nullptr;
}
// ---- 打卡处理 ----
CheckInRecord AttendanceService::processCheckIn(const std::string& employeeId,
CheckType type,
const std::string& deviceId,
std::time_t timestamp) {
if (employees_.count(employeeId) == 0) {
throw std::runtime_error("未知员工: " + employeeId);
// 步骤5: 将时间与位置数据与预设考勤规则进行匹配判断
AttendanceRule rule = getRuleForEmployee(req.employeeId);
// 降级模式:仅缓存打卡时间戳,不进行规则匹配
if (/* 降级模式判断条件 */ false) {
RawRecord rec;
rec.employeeId = req.employeeId;
rec.punchTime = now;
rec.terminalType = req.terminalType;
storeRawRecord(rec);
// 步骤8: 组装响应
resp.success = true;
resp.message = "打卡成功(降级模式)";
resp.serverTime = now;
return resp;
}
CheckInRecord rec;
rec.recordId = nextId();
rec.employeeId = employeeId;
rec.timestamp = (timestamp == 0) ? std::time(nullptr) : timestamp;
rec.type = type;
rec.deviceId = deviceId;
rec.verified = true;
records_.push_back(rec);
return rec;
}
std::vector<CheckInRecord> AttendanceService::getRecords(const std::string& employeeId) const {
std::vector<CheckInRecord> result;
for (const auto& r : records_) {
if (r.employeeId == employeeId) {
result.push_back(r);
}
}
return result;
}
// ---- 辅助方法 ----
int AttendanceService::minutesFromMidnight(std::time_t t) const {
std::tm* tm = std::localtime(&t);
if (!tm) return 0;
return tm->tm_hour * 60 + tm->tm_min;
}
bool AttendanceService::isSameDate(std::time_t t, const std::string& dateStr) const {
std::tm* tm = std::localtime(&t);
if (!tm) return false;
std::ostringstream oss;
oss << std::put_time(tm, "%Y-%m-%d");
return oss.str() == dateStr;
}
// ---- 考勤核算 ----
DailyAttendance AttendanceService::calculateDaily(const std::string& employeeId,
const std::string& dateStr) {
const Employee* emp = findEmployee(employeeId);
if (!emp) {
throw std::runtime_error("员工不存在: " + employeeId);
}
const ShiftRule* shift = findShift(emp->shiftId);
if (!shift) {
throw std::runtime_error("未找到排班: " + emp->shiftId);
}
DailyAttendance da;
da.dateStr = dateStr;
da.employeeId = employeeId;
// 应出勤时长标准工时不含午休设为8小时
da.expectedMinutes = (shift->workEndMinutes() - shift->workStartMinutes()) - 60; // 减1小时午休
if (da.expectedMinutes < 0) da.expectedMinutes = 480; // 默认8小时
// 查找该日所有打卡记录
std::vector<const CheckInRecord*> dailyRecords;
for (const auto& r : records_) {
if (r.employeeId == employeeId && isSameDate(r.timestamp, dateStr)) {
dailyRecords.push_back(&r);
}
}
// 找出最早的上班打卡和最晚的下班打卡
std::time_t firstClockIn = 0;
std::time_t lastClockOut = 0;
for (const auto* r : dailyRecords) {
if (r->type == CheckType::ClockIn) {
if (firstClockIn == 0 || r->timestamp < firstClockIn)
firstClockIn = r->timestamp;
} else if (r->type == CheckType::ClockOut) {
if (r->timestamp > lastClockOut)
lastClockOut = r->timestamp;
}
}
// 如果存在有效打卡,计算实际工时
if (firstClockIn != 0 && lastClockOut != 0 && lastClockOut > firstClockIn) {
int diffMinutes = static_cast<int>((lastClockOut - firstClockIn) / 60);
// 扣除午休1小时
da.actualMinutes = (diffMinutes > 60) ? (diffMinutes - 60) : diffMinutes;
// 加班判定(超过应出勤部分)
if (da.actualMinutes > da.expectedMinutes) {
da.overtimeMinutes = da.actualMinutes - da.expectedMinutes;
} else {
da.overtimeMinutes = 0;
}
// 异常判定:是否有上班打卡
bool hasClockIn = false;
bool hasClockOut = false;
for (const auto* r : dailyRecords) {
if (r->type == CheckType::ClockIn) hasClockIn = true;
if (r->type == CheckType::ClockOut) hasClockOut = true;
}
if (!hasClockIn || !hasClockOut) {
da.abnormal = AbnormalFlag::Absenteeism;
} else {
// 迟到判定:上班打卡时间超过规定上班时间+弹性窗口
int actualStartMin = minutesFromMidnight(firstClockIn);
int expectedStartWithFlex = shift->workStartMinutes() + shift->flexWindow;
if (actualStartMin > expectedStartWithFlex) {
da.abnormal = AbnormalFlag::Late;
}
// 早退判定:下班打卡时间早于规定下班时间
int actualEndMin = minutesFromMidnight(lastClockOut);
if (actualEndMin < shift->workEndMinutes() && da.abnormal == AbnormalFlag::None) {
da.abnormal = AbnormalFlag::EarlyLeave;
}
}
// 正常模式:进行匹配
// 步骤6/7: 根据匹配结果生成记录(此处简化,实际需对比时间)
bool match = (/* 实际时间与规则时间对比 */);
if (match) {
// 步骤6: 生成正常打卡记录并存储
RawRecord rec;
rec.employeeId = req.employeeId;
rec.punchTime = now;
rec.terminalType = req.terminalType;
storeRawRecord(rec);
resp.success = true;
resp.message = "打卡成功";
} else {
da.actualMinutes = 0;
da.overtimeMinutes = 0;
// 没有打卡记录视为旷工
if (dailyRecords.empty()) {
da.abnormal = AbnormalFlag::Absenteeism;
// 步骤7: 标记异常状态并缓存数据等待核算
RawRecord rec;
rec.employeeId = req.employeeId;
rec.punchTime = now;
rec.terminalType = req.terminalType;
storeRawRecord(rec);
// 异常标记由后续核算模块处理
resp.success = true; // 仍返回成功,异常在核算中处理
resp.message = "打卡已记录,等待核算";
}
// 步骤8: 返回响应
resp.serverTime = now;
return resp;
}
// SRS-ATT_F-002: 考勤数据自动核算
// 处理流程步骤1触发核算任务 -> 步骤2提取未核算记录 -> 步骤3加载规则 -> 步骤4匹配计算 -> 步骤5/6生成结果 -> 步骤7更新档案 -> 步骤8输出
void AttendanceSystem::autoCalculateAttendance() {
// 步骤1: 定时任务或事件触发(由外部调用)
// 步骤2: 从数据库提取未核算的原始打卡记录
std::vector<RawRecord> records = fetchUncalculatedRecords();
for (const auto& rec : records) {
// 步骤3: 根据员工部门与班次加载对应的考勤规则配置
AttendanceRule rule = getRuleForEmployee(rec.employeeId);
// 步骤4: 对打卡时间与规则时间阈值进行匹配计算
CalculationResult result = calculateSingleRecord(rec, rule);
// 步骤5/6: 生成正常或异常考勤记录
storeCalculationResult(result);
// 步骤7: 更新至员工考勤综合档案(简化,直接存储)
// 步骤8: 输出核算结果至异常预警模块与报表生成模块
if (result.status != CalculationResult::NORMAL) {
sendWarningForAnomaly(result); // 触发预警SRS-ATT_F-003
}
// 同时将结果传递给报表模块(由报表生成时查询)
}
return da;
}
std::vector<DailyAttendance> AttendanceService::generateReport(const std::string& deptId,
const std::string& dateBegin,
const std::string& dateEnd) {
std::vector<DailyAttendance> report;
std::vector<Employee> deptEmployees = getEmployeesByDept(deptId);
// SRS-ATT_F-003: 异常考勤预警
// 处理流程步骤1接收异常结果 -> 步骤2解析异常类型与员工信息 -> 步骤3判断严重级别 -> 步骤4/5发送通知 -> 步骤6记录日志
void AttendanceSystem::sendWarningForAnomaly(const CalculationResult& result) {
WarningNotification notif;
notif.employeeId = result.employeeId;
notif.anomalyType = result.status;
// 解析日期区间
std::time_t beginT = parseDate(dateBegin);
std::time_t endT = parseDate(dateEnd);
if (beginT == 0 || endT == 0 || endT < beginT) {
throw std::runtime_error("无效日期范围");
// 步骤2: 解析异常类型与员工信息
switch (result.status) {
case CalculationResult::LATE:
notif.message = "您今日迟到,请留意考勤规则。";
break;
case CalculationResult::EARLY_LEAVE:
notif.message = "您今日早退,请留意考勤规则。";
break;
case CalculationResult::MISSING_CARD:
notif.message = "您今日存在缺卡记录,请及时补卡。";
break;
case CalculationResult::ABSENT:
notif.message = "您今日缺勤,请说明原因。";
break;
default:
break;
}
// 遍历每日
for (std::time_t day = beginT; day <= endT; day += 86400) {
std::tm* tm = std::localtime(&day);
if (!tm) continue;
std::ostringstream oss;
oss << std::put_time(tm, "%Y-%m-%d");
std::string dateStr = oss.str();
// 跳过周末(简化处理,实际应根据节假日配置)
if (tm->tm_wday == 0 || tm->tm_wday == 6) continue;
for (const auto& emp : deptEmployees) {
DailyAttendance da = calculateDaily(emp.employeeId, dateStr);
report.push_back(da);
// 步骤3: 判断异常严重级别(此示例中连续缺卡为严重)
bool isSevere = (result.status == CalculationResult::ABSENT);
// 查询历史记录判断连续缺卡(简化:用随机或逻辑)
if (isSevere) {
// 步骤5: 向员工与人事管理员同时发送预警通知
std::vector<std::string> managers = getManagersForEmployee(result.employeeId);
for (const auto& mgr : managers) {
// 发送给管理员(此处模拟打印)
std::cout << "[警告-管理员] 员工 " << result.employeeId << " 缺勤,需关注。" << std::endl;
}
// 仍给员工发送
std::cout << "[警告-员工] " << notif.message << std::endl;
} else {
// 步骤4: 仅向员工发送提醒通知
std::cout << "[提醒-员工] " << notif.message << std::endl;
}
return report;
// 步骤6: 将预警通知发送状态与时间记录至系统日志
notif.sendTimestamp = std::time(nullptr);
logWarningSend(notif);
}
// ---- 异常预警 ----
// SRS-ATT_F-004: 考勤报表生成导出
// 处理流程步骤1接收请求或定时触发 -> 步骤2解析维度条件 -> 步骤3聚合数据 -> 步骤4渲染 -> 步骤5/6导出或展示
ReportFile AttendanceSystem::generateReport(const ReportQuery& query) {
// 步骤2: 解析报表维度
std::string dept = query.department;
std::string emp = query.employeeId;
std::time_t start = query.startDate;
std::time_t end = query.endDate;
AlertLog AttendanceService::createAlert(const std::string& employeeId, AbnormalFlag type) {
AlertLog alert;
alert.alertId = nextId();
alert.employeeId = employeeId;
alert.type = type;
alert.occurTime = std::time(nullptr);
alert.channel = "站内信";
alert.status = AlertStatus::Pending;
alert.retryCount = 0;
alerts_.push_back(alert);
return alert;
}
// 步骤3: 从档案中聚合考勤数据统计指标(模拟)
// 此处略去实际数据库查询,直接构造示例数据
std::stringstream content;
content << "考勤报表 - 部门:" << dept << " 时间:" << start << "-" << end << std::endl;
content << "出勤天数: 20, 迟到: 1, 早退: 0, 缺勤: 0" << std::endl;
std::vector<AlertLog> AttendanceService::getAlerts(const std::string& employeeId) const {
std::vector<AlertLog> result;
for (const auto& a : alerts_) {
if (a.employeeId == employeeId) {
result.push_back(a);
}
// 步骤4: 利用渲染引擎生成报表图表与表格视图(此处仅文本模拟)
ReportFile file;
file.fileName = "attendance_report_" + dept + ".csv";
std::string dataStr = content.str();
file.rawData.assign(dataStr.begin(), dataStr.end());
file.downloadUrl = "http://localhost/reports/" + file.fileName;
// 步骤5: 判断导出格式选择
if (query.format == ReportQuery::EXCEL || query.format == ReportQuery::PDF) {
// 步骤6: 生成文件并提供下载链接(此处模拟返回)
// 实际需要调用格式转换库
return file;
} else {
// 步骤7: 直接返回页面展示结果(此处返回模拟 URL
file.downloadUrl = "";
return file;
}
return result;
}
// ---- 报表任务 ----
ReportTask AttendanceService::createReportTask(const std::string& requester,
const std::string& deptId,
const std::string& dateBegin,
const std::string& dateEnd,
ReportFormat fmt) {
ReportTask task;
task.taskId = nextId();
task.requester = requester;
task.deptId = deptId;
task.dateRangeBegin = dateBegin;
task.dateRangeEnd = dateEnd;
task.format = fmt;
task.status = "Pending";
task.createTime = std::time(nullptr);
tasks_.push_back(task);
return task;
}
void AttendanceService::completeTask(const std::string& taskId, const std::string& url) {
for (auto& t : tasks_) {
if (t.taskId == taskId) {
t.status = "Done";
t.downloadUrl = url;
return;
}
// SRS-ATT_F-005: 第三方系统集成
// 处理流程步骤1接收同步请求 -> 步骤2接口鉴权与协议校验 -> 步骤4数据格式转换 -> 步骤5双向同步 -> 步骤6日志 -> 步骤7响应
SyncResponse AttendanceSystem::syncWithThirdParty(const ThirdPartySyncRequest& request) {
SyncResponse resp;
// 步骤2: 接口鉴权与通信协议解析校验
if (!authenticateThirdParty(request.apiKey)) {
// 步骤3: 拒绝同步并记录攻击日志
resp.success = false;
resp.message = "鉴权失败";
resp.syncedCount = 0;
resp.failedCount = 0;
// 记录攻击日志(此处简化)
std::cout << "[安全日志] 第三方鉴权失败API Key: " << request.apiKey << std::endl;
// 步骤8: 返回失败响应
return resp;
}
throw std::runtime_error("任务不存在: " + taskId);
// 步骤4: 数据格式转换与字段映射匹配(此处假设直接使用原始记录)
int successCount = 0;
int failCount = 0;
for (const auto& rec : request.records) {
// 步骤5: 执行考勤核算结果与第三方系统的数据双向同步写入
// 模拟写入实际应调用第三方SDK
// 假设全部成功
successCount++;
}
// 步骤6: 生成同步成功与失败条目的详细日志
std::cout << "[同步日志] 成功: " << successCount << " 条, 失败: " << failCount << "" << std::endl;
// 步骤7: 向第三方系统返回同步成功响应与结果统计
resp.success = true;
resp.syncedCount = successCount;
resp.failedCount = failCount;
resp.message = "同步完成";
return resp;
}
// ---------- 辅助函数实现(占位) ----------
bool AttendanceSystem::validateEmployee(const std::string& employeeId) {
// 实际应查询数据库或缓存,此处模拟有效
return !employeeId.empty();
}
AttendanceRule AttendanceSystem::getRuleForEmployee(const std::string& employeeId) {
// 模拟返回默认规则
AttendanceRule rule;
rule.department = "DEFAULT";
rule.shift = "DAY";
rule.startHour = 9;
rule.startMinute = 0;
rule.endHour = 18;
rule.endMinute = 0;
rule.lateThreshold = 30;
rule.earlyLeaveThreshold = 30;
return rule;
}
void AttendanceSystem::storeRawRecord(const RawRecord& rec) {
// 模拟存储到数据库
std::cout << "[存储] 原始打卡记录: " << rec.employeeId << " @ " << rec.punchTime << std::endl;
}
void AttendanceSystem::storeCalculationResult(const CalculationResult& res) {
// 模拟存储核算结果
std::cout << "[存储] 核算结果: " << res.employeeId << " 状态: " << res.status << std::endl;
}
std::vector<RawRecord> AttendanceSystem::fetchUncalculatedRecords() {
// 模拟返回未核算记录
return std::vector<RawRecord>();
}
CalculationResult AttendanceSystem::calculateSingleRecord(const RawRecord& rec, const AttendanceRule& rule) {
// 模拟核算逻辑,返回正常
CalculationResult res;
res.employeeId = rec.employeeId;
res.date = rec.punchTime;
res.status = CalculationResult::NORMAL;
return res;
}
void AttendanceSystem::logWarningSend(const WarningNotification& notif) {
// 模拟日志记录
std::cout << "[日志] 预警通知已发送至 " << notif.employeeId << " 于时间戳 " << notif.sendTimestamp << std::endl;
}
std::vector<std::string> AttendanceSystem::getManagersForEmployee(const std::string& employeeId) {
// 模拟返回管理员列表
return {"manager001"};
}
bool AttendanceSystem::authenticateThirdParty(const std::string& apiKey) {
// 模拟鉴权,简单比较
return apiKey == "VALID_API_KEY";
}

View File

@ -1,225 +1,62 @@
/**
* @file main.cpp
* @brief
*
*
* - SRS-ATT_F-001:
* - SRS-ATT_F-002:
* - SRS-ATT_F-003:
* - SRS-ATT_F-004:
* - SRS-ATT_F-005:
*/
#include "app.hpp"
#include <iostream>
#include <iomanip>
#include <thread>
#include <chrono>
// ============================================================
// 功能演示(非 unittest仅为展示业务能力
// ============================================================
static void demoEmployeeManagement(AttendanceService& svc) {
printSeparator();
std::cout << "【1. 员工管理】" << std::endl;
Employee e1;
e1.employeeId = "EMP001";
e1.name = "张三";
e1.department = "研发部";
e1.position = "高级工程师";
e1.shiftId = "S001";
e1.phone = "13912345678";
e1.role = RoleType::Employee;
svc.addEmployee(e1);
Employee e2;
e2.employeeId = "EMP002";
e2.name = "李四";
e2.department = "研发部";
e2.position = "测试工程师";
e2.shiftId = "S001";
e2.phone = "13987654321";
e2.role = RoleType::Employee;
svc.addEmployee(e2);
Employee e3;
e3.employeeId = "EMP003";
e3.name = "王五";
e3.department = "市场部";
e3.position = "市场经理";
e3.shiftId = "S001";
e3.phone = "13811112222";
e3.role = RoleType::DeptManager;
svc.addEmployee(e3);
std::cout << "已添加 3 名员工:" << std::endl;
for (const auto& id : {"EMP001", "EMP002", "EMP003"}) {
const auto* emp = svc.findEmployee(id);
if (emp) {
std::cout << " " << emp->employeeId << " | "
<< emp->name << " | "
<< emp->department << " | "
<< emp->position << " | "
<< "手机(脱敏): " << emp->maskedPhone() << std::endl;
}
}
}
static void demoCheckIn(AttendanceService& svc) {
printSeparator();
std::cout << "【2. 打卡处理】" << std::endl;
// 模拟当天时间:早上 8:50正常上班下午 18:10正常下班
std::time_t now = std::time(nullptr);
std::tm* tm_now = std::localtime(&now);
// 构造今日 8:50
std::tm tm_in = *tm_now;
tm_in.tm_hour = 8;
tm_in.tm_min = 50;
tm_in.tm_sec = 0;
std::time_t clockInTime = std::mktime(&tm_in);
// 构造今日 18:10
std::tm tm_out = *tm_now;
tm_out.tm_hour = 18;
tm_out.tm_min = 10;
tm_out.tm_sec = 0;
std::time_t clockOutTime = std::mktime(&tm_out);
// 员工 EMP001 正常打卡
auto rec1 = svc.processCheckIn("EMP001", CheckType::ClockIn, "DEVICE_APP_01", clockInTime);
std::cout << "EMP001 上班打卡: " << AttendanceService::timeToString(rec1.timestamp)
<< " 设备: " << rec1.deviceId << " 校验: " << (rec1.verified ? "通过" : "失败") << std::endl;
auto rec2 = svc.processCheckIn("EMP001", CheckType::ClockOut, "DEVICE_APP_01", clockOutTime);
std::cout << "EMP001 下班打卡: " << AttendanceService::timeToString(rec2.timestamp)
<< " 设备: " << rec2.deviceId << " 校验: " << (rec2.verified ? "通过" : "失败") << std::endl;
// 员工 EMP002 只打上班卡(缺下班卡)
auto rec3 = svc.processCheckIn("EMP002", CheckType::ClockIn, "DEVICE_WEB_01", clockInTime + 1200); // 9:10 到
std::cout << "EMP002 上班打卡(迟到): " << AttendanceService::timeToString(rec3.timestamp)
<< " 设备: " << rec3.deviceId << std::endl;
// EMP003 没有打卡
std::cout << "EMP003 今日未打卡" << std::endl;
}
static void demoAttendanceCalculation(AttendanceService& svc) {
printSeparator();
std::cout << "【3. 考勤核算】" << std::endl;
std::string today = AttendanceService::currentDateStr();
// 核算 EMP001
auto da1 = svc.calculateDaily("EMP001", today);
std::cout << "EMP001 张三: 应出勤 " << da1.expectedMinutes
<< " 分钟, 实际 " << da1.actualMinutes
<< " 分钟, 加班 " << da1.overtimeMinutes
<< " 分钟, 状态: " << da1.abnormalStr()
<< " 出勤率: " << std::fixed << std::setprecision(1) << da1.attendanceRate() << "%" << std::endl;
// 核算 EMP002仅有上班打卡
auto da2 = svc.calculateDaily("EMP002", today);
std::cout << "EMP002 李四: 应出勤 " << da2.expectedMinutes
<< " 分钟, 实际 " << da2.actualMinutes
<< " 分钟, 状态: " << da2.abnormalStr()
<< " 出勤率: " << std::fixed << std::setprecision(1) << da2.attendanceRate() << "%" << std::endl;
// 核算 EMP003无打卡
auto da3 = svc.calculateDaily("EMP003", today);
std::cout << "EMP003 王五: 应出勤 " << da3.expectedMinutes
<< " 分钟, 实际 " << da3.actualMinutes
<< " 分钟, 状态: " << da3.abnormalStr()
<< " 出勤率: " << std::fixed << std::setprecision(1) << da3.attendanceRate() << "%" << std::endl;
}
static void demoAlerts(AttendanceService& svc) {
printSeparator();
std::cout << "【4. 异常预警】" << std::endl;
// 为旷工员工创建预警
auto alert1 = svc.createAlert("EMP002", AbnormalFlag::Absenteeism);
std::cout << "预警已创建: ID=" << alert1.alertId
<< " 员工=" << alert1.employeeId
<< " 类型=旷工"
<< " 渠道=" << alert1.channel << std::endl;
auto alert2 = svc.createAlert("EMP003", AbnormalFlag::Absenteeism);
std::cout << "预警已创建: ID=" << alert2.alertId
<< " 员工=" << alert2.employeeId
<< " 类型=旷工"
<< " 渠道=" << alert2.channel << std::endl;
// 查询 EMP002 的预警
auto empAlerts = svc.getAlerts("EMP002");
std::cout << "EMP002 当前预警数: " << empAlerts.size() << std::endl;
}
static void demoReport(AttendanceService& svc) {
printSeparator();
std::cout << "【5. 报表任务】" << std::endl;
std::string today = AttendanceService::currentDateStr();
// 取今天往前 7 天
std::time_t now = std::time(nullptr);
std::time_t weekAgo = now - 7 * 86400;
std::tm* tm = std::localtime(&weekAgo);
std::ostringstream oss;
oss << std::put_time(tm, "%Y-%m-%d");
std::string weekAgoStr = oss.str();
auto task = svc.createReportTask("ADMIN001", "研发部", weekAgoStr, today, ReportFormat::Excel);
std::cout << "报表任务已创建: ID=" << task.taskId
<< " 范围=" << task.dateRangeBegin << "~" << task.dateRangeEnd
<< " 格式=Excel 状态=" << task.status << std::endl;
// 模拟报表生成完成
svc.completeTask(task.taskId, "/download/report_" + task.taskId + ".xlsx");
std::cout << "报表任务已完成: 下载链接=" << "/download/report_" + task.taskId + ".xlsx" << std::endl;
}
static void demoDataSecurity() {
printSeparator();
std::cout << "【6. 数据安全演示】" << std::endl;
// 脱敏演示
std::string phone = "13912345678";
std::string idCard = "110101199001011234";
std::string email = "zhangsan@company.com";
std::cout << "原始手机号: " << phone
<< " -> 脱敏: " << maskSensitive(phone, 3, 4) << std::endl;
std::cout << "原始身份证: " << idCard
<< " -> 脱敏: " << maskSensitive(idCard, 6, 4) << std::endl;
std::cout << "原始邮箱: " << email
<< " -> 脱敏: " << maskSensitive(email, 2, email.find('@') - 1) << std::endl;
// RBAC 权限演示
std::cout << "\nRBAC 权限模型:" << std::endl;
std::cout << " SystemAdmin - 全部资源访问权限" << std::endl;
std::cout << " HRAdmin - 考勤数据读写权限" << std::endl;
std::cout << " DeptManager - 本部门数据只读权限" << std::endl;
std::cout << " Employee - 个人数据只读权限" << std::endl;
}
// ============================================================
// 主入口
// ============================================================
int main() {
std::cout << "\n";
std::cout << " ____ _ _ _____ _____ _ _ ____ _ _" << std::endl;
std::cout << " / ___|| \\ | |_ _| ____| \\ | |/ ___| / \\ | |" << std::endl;
std::cout << " \\___ \\| \\| | | | | _| | \\| | | _ / _ \\| |" << std::endl;
std::cout << " ___) | |\\ | | | | |___| |\\ | |_| |/ ___ \\ |___" << std::endl;
std::cout << " |____/|_| \\_| |_| |_____|_| \\_|\\____/_/ \\_\\_____|" << std::endl;
std::cout << " 智能考勤管理系统 v1.0" << std::endl;
std::cout << " C++ 示范工程" << std::endl;
std::cout << "\n";
AttendanceSystem system;
AttendanceService svc;
// SRS-ATT_F-001: 多终端打卡支持 - 模拟移动端打卡请求
std::cout << "=== SRS-ATT_F-001: 多终端打卡支持 ===" << std::endl;
PunchRequest mobileReq;
mobileReq.employeeId = "EMP001";
mobileReq.terminalType = "MOBILE";
mobileReq.deviceId = "iPhone14Pro";
mobileReq.timestamp = std::time(nullptr);
mobileReq.latitude = 31.2304;
mobileReq.longitude = 121.4737;
PunchResponse resp = system.processPunch(mobileReq);
std::cout << "打卡结果: " << (resp.success ? "成功" : "失败") << " - " << resp.message << std::endl;
demoEmployeeManagement(svc);
demoCheckIn(svc);
demoAttendanceCalculation(svc);
demoAlerts(svc);
demoReport(svc);
demoDataSecurity();
// SRS-ATT_F-002: 考勤数据自动核算
std::cout << "\n=== SRS-ATT_F-002: 考勤数据自动核算 ===" << std::endl;
system.autoCalculateAttendance();
printSeparator();
std::cout << "全部演示完成!" << std::endl;
printSeparator();
// SRS-ATT_F-003: 异常考勤预警(由核算内部触发,此处仅示意)
std::cout << "\n=== SRS-ATT_F-003: 异常考勤预警 ===" << std::endl;
// 预警已在 autoCalculate 中自动调用,此处可额外模拟
CalculationResult anomaly;
anomaly.employeeId = "EMP002";
anomaly.status = CalculationResult::ABSENT;
system.sendWarningForAnomaly(anomaly);
// SRS-ATT_F-004: 考勤报表生成导出
std::cout << "\n=== SRS-ATT_F-004: 考勤报表生成导出 ===" << std::endl;
ReportQuery query;
query.department = "DEPT_A";
query.startDate = std::time(nullptr) - 7*24*3600;
query.endDate = std::time(nullptr);
query.format = ReportQuery::EXCEL;
ReportFile report = system.generateReport(query);
std::cout << "报表文件名: " << report.fileName << std::endl;
// SRS-ATT_F-005: 第三方系统集成
std::cout << "\n=== SRS-ATT_F-005: 第三方系统集成 ===" << std::endl;
ThirdPartySyncRequest syncReq;
syncReq.apiKey = "VALID_API_KEY";
syncReq.systemType = "HR";
SyncResponse syncResp = system.syncWithThirdParty(syncReq);
std::cout << "同步结果: " << (syncResp.success ? "成功" : "失败") << " - " << syncResp.message << std::endl;
return 0;
}

View File

@ -1,285 +1,84 @@
/**
* @file basic_test.cpp
* @brief
*
*
* - TestPunchFunction: SRS-ATT_F-001
* - TestAutoCalculate: SRS-ATT_F-002
* - TestWarning: SRS-ATT_F-003
* - TestReport: SRS-ATT_F-004
* - TestThirdPartySync: SRS-ATT_F-005
*/
#include "app.hpp"
#include <cassert>
#include <iostream>
#include <cstring>
// ============================================================
// 单元测试(使用标准库 assert无外部依赖
// ============================================================
static void testEmployeeManagement() {
AttendanceService svc;
// 添加员工
Employee e;
e.employeeId = "T001";
e.name = "测试员工";
e.department = "测试部";
e.position = "测试工程师";
e.shiftId = "S001";
e.phone = "13900001111";
e.role = RoleType::Employee;
svc.addEmployee(e);
// 查找
const Employee* found = svc.findEmployee("T001");
assert(found != nullptr);
assert(found->name == "测试员工");
assert(found->department == "测试部");
// 脱敏手机号
std::string masked = found->maskedPhone();
assert(masked == "*******1111");
std::cout << "[PASS] testEmployeeManagement: 员工管理正常" << std::endl;
// 测试 SRS-ATT_F-001: 多终端打卡支持
void testPunchFunction() {
AttendanceSystem sys;
PunchRequest req;
req.employeeId = "EMP_TEST";
req.terminalType = "MOBILE";
req.timestamp = std::time(nullptr);
PunchResponse resp = sys.processPunch(req);
assert(resp.success);
std::cout << "TestPunchFunction passed.\n";
}
static void testDuplicateEmployee() {
AttendanceService svc;
Employee e;
e.employeeId = "T002";
e.name = "重复测试";
svc.addEmployee(e);
bool thrown = false;
// 测试 SRS-ATT_F-002: 考勤数据自动核算(简单验证不崩溃)
void testAutoCalculate() {
AttendanceSystem sys;
// 正常情况下不应抛出异常
try {
svc.addEmployee(e);
} catch (const std::runtime_error&) {
thrown = true;
sys.autoCalculateAttendance();
std::cout << "TestAutoCalculate passed.\n";
} catch (...) {
assert(false && "autoCalculate threw exception");
}
assert(thrown);
std::cout << "[PASS] testDuplicateEmployee: 重复添加异常正常" << std::endl;
}
static void testCheckInAndRecords() {
AttendanceService svc;
Employee e;
e.employeeId = "T003";
e.name = "打卡测试";
e.department = "测试部";
e.shiftId = "S001";
svc.addEmployee(e);
// 上班打卡
std::time_t now = std::time(nullptr);
auto rec1 = svc.processCheckIn("T003", CheckType::ClockIn, "DEVICE_TEST", now);
assert(rec1.employeeId == "T003");
assert(rec1.type == CheckType::ClockIn);
assert(rec1.verified == true);
assert(!rec1.recordId.empty());
// 下班打卡
auto rec2 = svc.processCheckIn("T003", CheckType::ClockOut, "DEVICE_TEST", now + 28800); // 8h后
assert(rec2.type == CheckType::ClockOut);
// 查询记录
auto records = svc.getRecords("T003");
assert(records.size() == 2);
assert(records[0].type == CheckType::ClockIn);
assert(records[1].type == CheckType::ClockOut);
// 未知员工打卡应抛异常
bool thrown = false;
try {
svc.processCheckIn("UNKNOWN", CheckType::ClockIn, "DEVICE", now);
} catch (const std::runtime_error&) {
thrown = true;
}
assert(thrown);
std::cout << "[PASS] testCheckInAndRecords: 打卡流程正常" << std::endl;
// 测试 SRS-ATT_F-003: 异常考勤预警(验证报警触发)
void testWarning() {
AttendanceSystem sys;
CalculationResult res;
res.employeeId = "EMP_WARN";
res.status = CalculationResult::LATE;
// 只需确保函数执行不崩溃
sys.sendWarningForAnomaly(res);
std::cout << "TestWarning passed.\n";
}
static void testDailyCalculation() {
AttendanceService svc;
Employee e;
e.employeeId = "T004";
e.name = "核算测试";
e.department = "测试部";
e.shiftId = "S001";
svc.addEmployee(e);
// 模拟当日打卡8:30 上班18:00 下班
std::time_t now = std::time(nullptr);
std::tm* tm = std::localtime(&now);
std::tm tm_in = *tm;
tm_in.tm_hour = 8;
tm_in.tm_min = 30;
tm_in.tm_sec = 0;
std::time_t tIn = std::mktime(&tm_in);
std::tm tm_out = *tm;
tm_out.tm_hour = 18;
tm_out.tm_min = 0;
tm_out.tm_sec = 0;
std::time_t tOut = std::mktime(&tm_out);
svc.processCheckIn("T004", CheckType::ClockIn, "DEVICE", tIn);
svc.processCheckIn("T004", CheckType::ClockOut, "DEVICE", tOut);
// 获取核算结果
std::ostringstream oss;
oss << std::put_time(tm, "%Y-%m-%d");
std::string today = oss.str();
auto da = svc.calculateDaily("T004", today);
assert(!da.dateStr.empty());
assert(da.employeeId == "T004");
assert(da.expectedMinutes > 0);
assert(da.actualMinutes > 0);
// 实际工时应约为 8.5h - 1h(午休) = 7.5h = 450min有弹性
// 但至少大于0
assert(da.actualMinutes > 100);
std::cout << "[PASS] testDailyCalculation: 核算正常, 出勤 " << da.actualMinutes << " 分钟" << std::endl;
// 测试 SRS-ATT_F-004: 考勤报表生成导出
void testReport() {
AttendanceSystem sys;
ReportQuery query;
query.department = "TEST_DEPT";
query.startDate = 0;
query.endDate = std::time(nullptr);
query.format = ReportQuery::EXCEL;
ReportFile file = sys.generateReport(query);
assert(!file.fileName.empty());
std::cout << "TestReport passed.\n";
}
static void testAbnormalDetection() {
AttendanceService svc;
Employee e;
e.employeeId = "T005";
e.name = "异常测试";
e.department = "测试部";
e.shiftId = "S001";
svc.addEmployee(e);
// 没有打卡记录 -> 旷工
std::time_t now = std::time(nullptr);
std::tm* tm = std::localtime(&now);
std::ostringstream oss;
oss << std::put_time(tm, "%Y-%m-%d");
std::string today = oss.str();
auto da = svc.calculateDaily("T005", today);
assert(da.abnormal == AbnormalFlag::Absenteeism);
std::cout << "[PASS] testAbnormalDetection: 无打卡=旷工判定正常" << std::endl;
// 测试 SRS-ATT_F-005: 第三方系统集成
void testThirdPartySync() {
AttendanceSystem sys;
ThirdPartySyncRequest req;
req.apiKey = "VALID_API_KEY";
req.systemType = "HR";
SyncResponse resp = sys.syncWithThirdParty(req);
assert(resp.success);
std::cout << "TestThirdPartySync passed.\n";
}
static void testReportGeneration() {
AttendanceService svc;
// 添加员工
Employee e1;
e1.employeeId = "T006";
e1.name = "报表A";
e1.department = "报表部";
svc.addEmployee(e1);
Employee e2;
e2.employeeId = "T007";
e2.name = "报表B";
e2.department = "报表部";
svc.addEmployee(e2);
// 添加打卡
std::time_t now = std::time(nullptr);
std::tm* tm = std::localtime(&now);
std::tm tm_in = *tm;
tm_in.tm_hour = 9;
tm_in.tm_min = 0;
std::time_t tIn = std::mktime(&tm_in);
std::tm tm_out = *tm;
tm_out.tm_hour = 18;
tm_out.tm_min = 0;
std::time_t tOut = std::mktime(&tm_out);
svc.processCheckIn("T006", CheckType::ClockIn, "DEVICE", tIn);
svc.processCheckIn("T006", CheckType::ClockOut, "DEVICE", tOut);
svc.processCheckIn("T007", CheckType::ClockIn, "DEVICE", tIn);
svc.processCheckIn("T007", CheckType::ClockOut, "DEVICE", tOut);
// 生成报表
std::ostringstream oss;
oss << std::put_time(tm, "%Y-%m-%d");
std::string today = oss.str();
auto report = svc.generateReport("报表部", today, today);
assert(!report.empty());
assert(report.size() == 2);
std::cout << "[PASS] testReportGeneration: 报表生成正常, 共 " << report.size() << " 条记录" << std::endl;
}
static void testAlertCreation() {
AttendanceService svc;
// 创建预警
auto alert = svc.createAlert("T999", AbnormalFlag::Late);
assert(!alert.alertId.empty());
assert(alert.employeeId == "T999");
assert(alert.type == AbnormalFlag::Late);
assert(alert.status == AlertStatus::Pending);
// 查询
auto alerts = svc.getAlerts("T999");
assert(alerts.size() == 1);
assert(alerts[0].type == AbnormalFlag::Late);
std::cout << "[PASS] testAlertCreation: 预警创建与查询正常" << std::endl;
}
static void testReportTask() {
AttendanceService svc;
auto task = svc.createReportTask("ADMIN", "部门X", "2025-01-01", "2025-01-31", ReportFormat::PDF);
assert(task.taskId.length() > 0);
assert(task.status == "Pending");
assert(task.format == ReportFormat::PDF);
svc.completeTask(task.taskId, "/download/test.pdf");
assert(task.status == "Done");
assert(task.downloadUrl == "/download/test.pdf");
std::cout << "[PASS] testReportTask: 报表任务流程正常" << std::endl;
}
static void testMaskSensitive() {
assert(maskSensitive("12345678901", 3, 4) == "123****8901");
assert(maskSensitive("abc", 1, 1) == "abc"); // 太短时不脱敏
assert(maskSensitive("", 0, 0).empty());
std::cout << "[PASS] testMaskSensitive: 脱敏函数正常" << std::endl;
}
static void testShiftRule() {
ShiftRule rule;
rule.shiftId = "S999";
rule.startHour = 8;
rule.startMin = 30;
rule.endHour = 17;
rule.endMin = 30;
assert(rule.workStartMinutes() == 8 * 60 + 30); // 510
assert(rule.workEndMinutes() == 17 * 60 + 30); // 1050
std::cout << "[PASS] testShiftRule: 排班规则计算正常" << std::endl;
}
// ============================================================
// 主测试入口
// ============================================================
int main() {
std::cout << "============================================" << std::endl;
std::cout << " 智能考勤管理系统 - 单元测试" << std::endl;
std::cout << "============================================" << std::endl;
testEmployeeManagement();
testDuplicateEmployee();
testCheckInAndRecords();
testDailyCalculation();
testAbnormalDetection();
testReportGeneration();
testAlertCreation();
testReportTask();
testMaskSensitive();
testShiftRule();
std::cout << "============================================" << std::endl;
std::cout << "所有测试通过!" << std::endl;
std::cout << "============================================" << std::endl;
testPunchFunction();
testAutoCalculate();
testWarning();
testReport();
testThirdPartySync();
std::cout << "All tests passed!\n";
return 0;
}