Compare commits

...

1 Commits

Author SHA1 Message Date
root ff76e7467f 生成代码工程 2026-04-25 11:15:03 +08:00
7 changed files with 13058 additions and 3979 deletions

29
CMakeLists.txt Normal file
View File

@ -0,0 +1,29 @@
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)
# MSVC UTF-8
if (MSVC)
add_compile_options(/utf-8)
endif()
#
add_executable(attendance_system
src/main.cpp
src/app.cpp
include/app.hpp
)
target_include_directories(attendance_system PRIVATE include)
#
add_executable(basic_test
tests/basic_test.cpp
src/app.cpp
include/app.hpp
)
target_include_directories(basic_test PRIVATE include)

View File

@ -1,47 +1,51 @@
# 单体示例工程
# 智能考勤管理系统 (Attendance System)
一个基于 FastAPI 的简单待办事项Todo管理服务使用内存假数据存储。
## 概述
本工程实现了一个精简但结构清晰的智能考勤管理系统,涵盖:
- 多终端打卡数据接收与处理
- 自动化工时核算与异常标记
- 考勤报表生成
- RBAC 权限与数据安全模型
## 功能
- 获取所有待办事项列表
- 根据 ID 获取单个待办事项
- 创建新的待办事项
- 更新待办事项
- 删除待办事项
## 安装依赖
```bash
cd codegen-runs/codegen_a2cef5070965479b93c5b3c07d4c8216
pip install -r requirements.txt
## 项目结构
```
AttendanceSystem/
├── CMakeLists.txt # 构建配置C++17, MSVC UTF-8
├── README.md # 本文件
├── include/
│ └── app.hpp # 核心数据结构与业务接口声明
├── src/
│ ├── app.cpp # 业务逻辑实现
│ └── main.cpp # 命令行入口(功能演示)
└── tests/
└── basic_test.cpp # 单元测试(使用标准 assert
```
## 启动服务
## 编译与运行
### 使用 CMake
```bash
uvicorn app.main:app --reload --port 8000
cd AttendanceSystem
mkdir build && cd build
cmake ..
cmake --build .
```
访问 http://127.0.0.1:8000/docs 查看自动生成的 API 文档Swagger UI
## 运行测试
### 运行主程序
```bash
cd codegen-runs/codegen_a2cef5070965479b93c5b3c07d4c8216
pytest tests/ -v
./attendance_system
```
## 快速验证
启动服务后,在另一个终端执行:
### 运行测试
```bash
# 获取所有待办事项
curl http://127.0.0.1:8000/todos
# 创建新待办事项
curl -X POST http://127.0.0.1:8000/todos \
-H "Content-Type: application/json" \
-d '{"title": "学习 FastAPI", "completed": false}'
./basic_test
```
## 核心设计
- **员工管理**:支持员工信息、班次、部门维护
- **打卡处理**:统一打卡协议,校验时间戳与设备标识
- **考勤核算**:根据排班规则与节假日自动计算工时、标记异常
- **报表导出**:支持按部门与时间范围生成统计报表
- **安全模型**RBAC 权限控制,敏感数据脱敏
> 本工程为 C++ 示范实现,重点关注数据结构的清晰与业务逻辑的可读性。

File diff suppressed because one or more lines are too long

240
include/app.hpp Normal file
View File

@ -0,0 +1,240 @@
#ifndef ATTENDANCE_SYSTEM_APP_HPP
#define ATTENDANCE_SYSTEM_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 // 下班打卡
};
/// 异常标记
enum class AbnormalFlag {
None,
Late, // 迟到
EarlyLeave, // 早退
Absenteeism // 旷工
};
/// 报表输出格式
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;
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);
}
};
/// 排班规则
struct ShiftRule {
std::string shiftId;
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; }
};
/// 打卡流水记录
struct CheckInRecord {
std::string recordId;
std::string employeeId;
std::time_t timestamp{0};
CheckType type{CheckType::Unknown};
std::string deviceId;
std::string latitude;
std::string longitude;
bool verified{false};
};
/// 考勤核算结果(按天)
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 "正常";
}
}
};
/// 异常预警日志
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
std::string downloadUrl;
std::time_t createTime{0};
};
/// 权限配置RBAC
struct Permission {
std::string resource; // 资源路径,如 "/api/v1/reports"
std::string action; // 操作READ / WRITE / DELETE
};
// ============================================================
// 业务逻辑类
// ============================================================
class AttendanceService {
public:
AttendanceService();
// ---- 员工管理 ----
void addEmployee(const Employee& emp);
const Employee* findEmployee(const std::string& id) const;
std::vector<Employee> getEmployeesByDept(const std::string& dept) const;
// ---- 排班管理 ----
void addShiftRule(const ShiftRule& rule);
const ShiftRule* findShift(const std::string& shiftId) const;
// ---- 打卡处理 ----
CheckInRecord processCheckIn(const std::string& employeeId,
CheckType type,
const std::string& deviceId,
std::time_t timestamp);
std::vector<CheckInRecord> getRecords(const std::string& employeeId) const;
// ---- 考勤核算 ----
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);
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;
};
// ============================================================
// 工具函数声明
// ============================================================
/// 脱敏工具:将敏感字符串中间部分替换为 '*'
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

345
src/app.cpp Normal file
View File

@ -0,0 +1,345 @@
#include "app.hpp"
#include <iostream>
#include <cstring>
#include <ctime>
#include <chrono>
#include <random>
// ============================================================
// 工具函数实现
// ============================================================
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();
}
std::string AttendanceService::currentDateStr() {
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();
}
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);
}
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;
}
}
} else {
da.actualMinutes = 0;
da.overtimeMinutes = 0;
// 没有打卡记录视为旷工
if (dailyRecords.empty()) {
da.abnormal = AbnormalFlag::Absenteeism;
}
}
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);
// 解析日期区间
std::time_t beginT = parseDate(dateBegin);
std::time_t endT = parseDate(dateEnd);
if (beginT == 0 || endT == 0 || endT < beginT) {
throw std::runtime_error("无效日期范围");
}
// 遍历每日
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);
}
}
return report;
}
// ---- 异常预警 ----
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;
}
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);
}
}
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;
}
}
throw std::runtime_error("任务不存在: " + taskId);
}

225
src/main.cpp Normal file
View File

@ -0,0 +1,225 @@
#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";
AttendanceService svc;
demoEmployeeManagement(svc);
demoCheckIn(svc);
demoAttendanceCalculation(svc);
demoAlerts(svc);
demoReport(svc);
demoDataSecurity();
printSeparator();
std::cout << "全部演示完成!" << std::endl;
printSeparator();
return 0;
}

285
tests/basic_test.cpp Normal file
View File

@ -0,0 +1,285 @@
#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;
}
static void testDuplicateEmployee() {
AttendanceService svc;
Employee e;
e.employeeId = "T002";
e.name = "重复测试";
svc.addEmployee(e);
bool thrown = false;
try {
svc.addEmployee(e);
} catch (const std::runtime_error&) {
thrown = true;
}
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;
}
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;
}
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;
}
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;
return 0;
}