1. 系统架构设计思路
这个考勤薪酬绩效管理系统采用前后端分离架构,前端使用Vue 3+TypeScript技术栈,后端采用Python FastAPI框架。系统设计时重点考虑了企业实际业务场景中的四个核心角色需求,通过模块化设计实现功能解耦。
1.1 技术选型考量
选择Vue 3作为前端框架主要基于其组合式API带来的更好代码组织能力,特别是对于复杂业务逻辑的考勤计算模块。Element Plus组件库提供了丰富的企业级UI组件,可以快速构建管理后台界面。Pinia状态管理方案相比Vuex更适合中型应用,其模块化设计正好对应我们系统的角色权限体系。
后端选择FastAPI而非Django/Rails这类全栈框架,是因为:
- 考勤系统需要处理大量实时请求,FastAPI的异步特性更优
- 薪酬计算涉及复杂业务逻辑,需要更灵活的架构
- 与现有企业微信/钉钉系统的API对接更方便
数据库选用MySQL 8.0,主要利用其:
- 窗口函数便于生成各类排名报表
- JSON字段支持存储动态考勤规则
- 事务特性确保薪酬数据的强一致性
1.2 核心模块划分
系统主要分为四大模块:
- 身份认证模块:采用JWT+RBAC模型,支持多终端登录
- 考勤管理模块:处理打卡记录、异常考勤、请假审批等
- 薪酬计算模块:基于考勤数据和绩效结果的自动化计算
- 绩效评估模块:支持KPI考核和360度评估流程
各模块通过事件总线解耦,例如考勤异常触发通知事件,绩效提交触发薪酬重算事件等。这种设计使得后期新增考勤方式(如人脸识别)或薪酬规则调整时,只需修改对应模块即可。
2. 数据库设计关键点
2.1 核心表结构设计
**员工表(employees)**设计要点:
sql复制CREATE TABLE employees (
id BIGINT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
department_id INT NOT NULL,
position_id INT NOT NULL,
base_salary DECIMAL(10,2) NOT NULL,
hire_date DATE NOT NULL,
leave_date DATE NULL,
status TINYINT NOT NULL COMMENT '0-在职 1-离职',
face_data TEXT COMMENT '人脸特征数据',
CONSTRAINT fk_dept FOREIGN KEY (department_id) REFERENCES departments(id),
CONSTRAINT fk_position FOREIGN KEY (position_id) REFERENCES positions(id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
**考勤记录表(attendance_records)**的特殊处理:
- 使用复合索引(employee_id, record_date)加速查询
- 地理坐标使用MySQL的POINT类型存储
- 添加check_in_type字段区分正常打卡/补卡/人工录入
sql复制CREATE TABLE attendance_records (
id BIGINT PRIMARY KEY,
employee_id BIGINT NOT NULL,
record_date DATE NOT NULL,
check_in_time DATETIME NULL,
check_out_time DATETIME NULL,
location POINT SRID 4326,
check_in_type TINYINT NOT NULL COMMENT '0-正常 1-补卡 2-人工',
status TINYINT NOT NULL COMMENT '0-正常 1-迟到 2-早退 3-缺勤',
SPATIAL INDEX(location),
UNIQUE KEY uk_emp_date (employee_id, record_date),
CONSTRAINT fk_employee FOREIGN KEY (employee_id) REFERENCES employees(id)
) ENGINE=InnoDB;
2.2 薪酬计算相关表
**薪酬规则表(salary_rules)**采用JSON存储灵活规则:
sql复制CREATE TABLE salary_rules (
id INT PRIMARY KEY,
rule_name VARCHAR(100) NOT NULL,
rule_config JSON NOT NULL COMMENT '存储迟到扣款、加班费率等规则',
effective_date DATE NOT NULL,
is_active BOOLEAN NOT NULL DEFAULT true
);
示例规则配置:
json复制{
"late_penalty": {
"threshold": 10,
"unit": "minute",
"deduction_per_time": 20
},
"overtime_rate": {
"weekday": 1.5,
"weekend": 2.0,
"holiday": 3.0
}
}
3. 前端关键技术实现
3.1 权限控制系统
基于Vue 3的权限控制方案:
- 路由级别控制:通过meta.roles定义可访问角色
javascript复制const routes = [
{
path: '/salary',
component: SalaryManagement,
meta: { roles: ['admin', 'hr'] }
}
]
- 指令级控制:v-permission指令控制按钮显示
javascript复制app.directive('permission', {
mounted(el, binding) {
const { value } = binding
const roles = store.getters.roles
if (!roles.some(role => value.includes(role))) {
el.parentNode?.removeChild(el)
}
}
})
- 动态菜单生成:根据用户角色过滤导航菜单
typescript复制function filterAsyncRoutes(routes: RouteRecordRaw[], roles: string[]) {
return routes.filter(route => {
if (route.meta?.roles) {
return roles.some(role => route.meta.roles.includes(role))
}
return true
})
}
3.2 考勤打卡页面实现
使用浏览器地理定位API+Canvas实现web端打卡:
vue复制<script setup>
import { ref } from 'vue'
const position = ref(null)
const error = ref(null)
const isChecking = ref(false)
const getLocation = () => {
isChecking.value = true
if (!navigator.geolocation) {
error.value = '浏览器不支持地理定位'
return
}
navigator.geolocation.getCurrentPosition(
(pos) => {
position.value = {
lat: pos.coords.latitude,
lng: pos.coords.longitude,
accuracy: pos.coords.accuracy
}
submitCheckIn()
},
(err) => {
error.value = `定位失败: ${err.message}`
isChecking.value = false
},
{
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0
}
)
}
const submitCheckIn = async () => {
try {
await api.submitAttendance({
type: 'check_in',
coordinates: position.value
})
// 更新状态...
} catch (e) {
error.value = e.message
} finally {
isChecking.value = false
}
}
</script>
注意事项:实际部署时需要HTTPS才能使用地理定位API,对于精度要求高的场景建议配合人脸识别验证
4. 后端核心业务逻辑
4.1 考勤异常检测算法
每日定时任务检查异常考勤:
python复制async def check_abnormal_attendance():
# 获取当日应出勤员工
active_employees = await Employee.filter(status=0).values('id', 'department_id')
for emp in active_employees:
# 获取部门考勤规则
rule = await get_department_rule(emp['department_id'])
# 检查当日记录
record = await Attendance.filter(
employee_id=emp['id'],
record_date=date.today()
).first()
if not record:
await create_absence_record(emp['id'])
continue
# 迟到早退判断
if record.check_in_time > rule['start_time'] + timedelta(minutes=rule['late_threshold']):
record.status = AttendanceStatus.LATE
await record.save()
if record.check_out_time < rule['end_time'] - timedelta(minutes=rule['early_leave_threshold']):
record.status = AttendanceStatus.EARLY_LEAVE
await record.save()
4.2 薪酬计算服务
基于考勤数据的薪酬计算核心逻辑:
python复制class SalaryCalculator:
def __init__(self, employee_id: int, month: str):
self.employee = self._get_employee(employee_id)
self.month = month
self.rules = self._get_current_rules()
async def calculate(self) -> SalaryDetail:
base = self.employee.base_salary
attendance = await self._get_attendance_data()
performance = await self._get_performance_data()
# 计算考勤相关扣款
late_deduction = len(attendance.late_records) * self.rules.late_penalty
absence_deduction = attendance.absence_days * (base / 21.75)
# 计算加班费
overtime_pay = sum(
hours * self.rules.get_overtime_rate(day_type)
for hours, day_type in attendance.overtime_hours
)
# 绩效系数影响
performance_factor = performance.factor if performance else 1.0
return SalaryDetail(
base=base,
late_deduction=late_deduction,
absence_deduction=absence_deduction,
overtime_pay=overtime_pay,
performance_bonus=base * 0.2 * performance_factor,
actual_amount=base - late_deduction - absence_deduction
+ overtime_pay + (base * 0.2 * performance_factor)
)
关键点:21.75是月计薪天数(365天-104天休息日)÷12个月=21.75天,这是劳动法规定的标准计算方式
5. 系统部署方案
5.1 容器化部署配置
使用Docker Compose编排服务:
yaml复制version: '3.8'
services:
backend:
build: ./backend
ports:
- "8000:8000"
environment:
- DATABASE_URL=mysql://user:pass@mysql:3306/attendance
depends_on:
- mysql
- redis
frontend:
build: ./frontend
ports:
- "8080:80"
depends_on:
- backend
mysql:
image: mysql:8.0
environment:
- MYSQL_ROOT_PASSWORD=rootpass
- MYSQL_DATABASE=attendance
- MYSQL_USER=user
- MYSQL_PASSWORD=pass
volumes:
- mysql_data:/var/lib/mysql
redis:
image: redis:6
ports:
- "6379:6379"
volumes:
mysql_data:
5.2 性能优化措施
-
前端优化:
- 使用Vite构建工具加速开发和生产构建
- 按需加载Element Plus组件
- 路由懒加载拆分代码包
-
后端优化:
- 使用Redis缓存高频访问的考勤规则和员工基础信息
- 对薪酬计算接口添加二级缓存
- 使用Celery异步处理报表生成等耗时任务
-
数据库优化:
- 为考勤记录表按月份分表
- 为常用查询创建适当的索引
- 使用读写分离架构分担压力
6. 实际开发中的经验总结
6.1 时区问题处理
考勤系统必须统一时区处理:
- 数据库服务器时区设置为UTC
- 应用层根据用户所在时区转换显示时间
- 前端传递时间戳而非格式化字符串
python复制# 后端接收时间参数处理示例
def parse_time_param(value: str) -> datetime:
try:
# 前端传递ISO格式时间字符串
dt = datetime.fromisoformat(value.replace('Z', '+00:00'))
return dt.astimezone(timezone.utc)
except ValueError:
raise HTTPException(400, "Invalid time format")
6.2 批量操作优化
处理月末考勤统计时需要注意:
- 使用生成器分批读取大数据集
- 采用异步IO提高并发处理能力
- 添加进度状态可查询
python复制async def batch_calculate_salary(department_id: int, month: str):
async with DatabaseTransaction():
employees = await Employee.filter(
department_id=department_id,
status=0
).prefetch_related('contract')
results = []
batch_size = 50
total = await employees.count()
for i in range(0, total, batch_size):
batch = employees[i:i+batch_size]
tasks = [
SalaryCalculator(emp.id, month).calculate()
for emp in batch
]
results.extend(await asyncio.gather(*tasks))
# 更新进度
await update_progress(i/batch_size)
return results
6.3 安全防护措施
-
数据加密:
- 敏感字段如薪资、身份证号等使用AES加密存储
- 数据库连接使用SSL加密
-
接口防护:
- 关键操作接口添加速率限制
- 使用JWT签名防止篡改
- 敏感操作要求二次验证
-
日志审计:
- 记录所有薪资修改操作
- 关键数据变更保存历史版本
- 定期归档日志文件
python复制# 审计日志装饰器示例
def audit_log(action: str):
def decorator(func):
@wraps(func)
async def wrapper(*args, **kwargs):
user = get_current_user()
result = await func(*args, **kwargs)
await Audit.create(
user_id=user.id,
action=action,
detail=f"{func.__name__} called",
ip_address=request.client.host
)
return result
return wrapper
return decorator
这个系统的开发过程中,最大的挑战在于平衡灵活性和规范性。不同企业的考勤规则差异很大,我们最终采用规则引擎+配置化的方案,核心计算逻辑保持不变,通过配置适应不同规则。实际部署时建议先在小范围试用,收集反馈后再全公司推广。