项目介绍
智能考勤管理系统是一个基于 Java 全栈技术开发的现代化企业考勤解决方案。该系统采用前后端分离架构,实现了员工考勤、请假管理、统计分析等核心功能,旨在帮助企业提高人力资源管理效率。
技术栈
后端技术
- Spring Boot 2.6.x
- Spring Security
- MyBatis-Plus
- MySQL 8.0
- Redis
- JWT
前端技术
- Vue 3
- Element Plus
- Axios
- Echarts
- Vite
核心功能
1. 考勤管理
- 上下班打卡
- 加班申请
- 外出登记
- 考勤规则设置
- 异常考勤处理
2. 请假管理
- 请假申请
- 请假审批
- 请假类型配置
- 假期余额查询
3. 统计分析
- 考勤统计报表
- 部门出勤率分析
- 加班统计
- 请假趋势分析
4. 系统管理
- 用户管理
- 角色权限
- 部门管理
- 系统配置
系统架构
├── smart-attendance-system
│ ├── attendance-admin # 后台管理服务
│ ├── attendance-api # 接口服务
│ ├── attendance-common # 公共模块
│ ├── attendance-model # 数据模型
│ └── attendance-web # 前端项目
数据库设计
核心表结构
-- 员工表
CREATE TABLE sys_employee (
id BIGINT PRIMARY KEY,
emp_no VARCHAR(32),
name VARCHAR(50),
department_id BIGINT,
position VARCHAR(50),
status TINYINT,
create_time DATETIME
);
-- 考勤记录表
CREATE TABLE attendance_record (
id BIGINT PRIMARY KEY,
emp_id BIGINT,
check_in_time DATETIME,
check_out_time DATETIME,
status TINYINT,
type TINYINT,
remark VARCHAR(255)
);
-- 请假记录表
CREATE TABLE leave_record (
id BIGINT PRIMARY KEY,
emp_id BIGINT,
leave_type TINYINT,
start_time DATETIME,
end_time DATETIME,
reason VARCHAR(255),
status TINYINT
);
项目亮点
1. 人脸识别打卡
- 集成人脸识别SDK
- 活体检测
- 高精度识别算法
2. 智能定位打卡
- 基于地理围栏技术
- 支持移动端GPS定位
- 异地打卡预警
3. 灵活的考勤规则
- 多班次管理
- 弹性工时
- 节假日智能排班
- 加班规则配置
4. 数据可视化
- 直观的统计图表
- 多维度数据分析
- 自定义报表导出
性能优化
1. 缓存优化
@Cacheable(value = "attendance", key = "#empId")
public AttendanceDTO getAttendanceInfo(Long empId) {
// 获取考勤信息逻辑
}
2. SQL优化
@Select("SELECT DATE_FORMAT(check_in_time,'%Y-%m-%d') as date, " +
"COUNT(*) as count FROM attendance_record " +
"WHERE emp_id = #{empId} " +
"GROUP BY DATE_FORMAT(check_in_time,'%Y-%m-%d')")
List<StatisticsDTO> getAttendanceStatistics(Long empId);
3. 接口性能
- 接口响应时间控制在200ms以内
- 使用线程池处理异步任务
- 批量操作优化
安全性设计
1. 身份认证
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilter(new JwtAuthenticationFilter(authenticationManager()));
}
}
2. 数据安全
- 敏感数据加密
- SQL注入防护
- XSS防御
部署方案
1. 容器化部署
version: '3'
services:
attendance-api:
image: attendance-api:latest
ports:
- "8080:8080"
environment:
- SPRING_PROFILES_ACTIVE=prod
2. 高可用方案
- 服务器集群
- 负载均衡
- 数据库主从复制
项目总结
智能考勤管理系统采用现代化的技术栈和架构设计,实现了企业考勤管理的智能化和自动化。系统具有良好的可扩展性和维护性,为企业提供了一个高效、可靠的考勤解决方案。
未来展望
- 引入机器学习算法,实现考勤预测
- 集成企业微信等第三方平台
- 开发移动端应用
- 支持多租户架构
智能考勤系统 - 考勤与请假管理模块详解
一、考勤管理模块
1. 上下班打卡
1.1 打卡记录表设计
CREATE TABLE attendance_record (
id BIGINT PRIMARY KEY,
emp_id BIGINT COMMENT '员工ID',
check_type TINYINT COMMENT '打卡类型:1-上班,2-下班',
check_time DATETIME COMMENT '打卡时间',
check_location VARCHAR(255) COMMENT '打卡地点',
device_info VARCHAR(100) COMMENT '设备信息',
face_image VARCHAR(255) COMMENT '人脸照片URL',
status TINYINT COMMENT '状态:1-正常,2-迟到,3-早退',
remark VARCHAR(255) COMMENT '备注',
create_time DATETIME,
FOREIGN KEY (emp_id) REFERENCES sys_employee(id)
);
1.2 打卡服务实现
@Service
@Slf4j
public class CheckInService {
@Autowired
private AttendanceRuleService ruleService;
@Autowired
private FaceRecognitionService faceService;
@Autowired
private LocationService locationService;
@Transactional
public CheckInResult doCheckIn(CheckInDTO checkInDTO) {
// 1. 人脸识别验证
boolean faceValid = faceService.verify(
checkInDTO.getEmpId(),
checkInDTO.getFaceImage()
);
if (!faceValid) {
throw new BusinessException("人脸识别失败");
}
// 2. 位置验证
boolean locationValid = locationService.verifyLocation(
checkInDTO.getLocation(),
checkInDTO.getEmpId()
);
if (!locationValid) {
throw new BusinessException("不在打卡范围内");
}
// 3. 判断打卡类型和状态
AttendanceRule rule = ruleService.getEmployeeRule(checkInDTO.getEmpId());
CheckInStatus status = calculateStatus(
checkInDTO.getCheckTime(),
rule
);
// 4. 保存打卡记录
AttendanceRecord record = new AttendanceRecord();
record.setEmpId(checkInDTO.getEmpId());
record.setCheckType(checkInDTO.getCheckType());
record.setCheckTime(checkInDTO.getCheckTime());
record.setStatus(status.getCode());
attendanceMapper.insert(record);
// 5. 返回打卡结果
return new CheckInResult(status, record.getId());
}
private CheckInStatus calculateStatus(LocalDateTime checkTime,
AttendanceRule rule) {
LocalTime time = checkTime.toLocalTime();
if (isCheckIn) {
if (time.isAfter(rule.getLateTime())) {
return CheckInStatus.LATE;
}
} else {
if (time.isBefore(rule.getEarlyLeaveTime())) {
return CheckInStatus.EARLY_LEAVE;
}
}
return CheckInStatus.NORMAL;
}
}
1.3 打卡界面实现
<template>
<div class="check-in-container">
<div class="camera-container">
<video ref="video" class="camera-preview"></video>
<canvas ref="canvas" style="display: none;"></canvas>
</div>
<div class="check-in-info">
<div class="time-display">
{{ currentTime }}
</div>
<el-button
type="primary"
size="large"
@click="handleCheckIn"
:loading="checking"
>
{{ checkType === 1 ? '上班打卡' : '下班打卡' }}
</el-button>
<div class="location-info">
当前位置:{{ location }}
</div>
</div>
<!-- 打卡结果弹窗 -->
<el-dialog
title="打卡结果"
:visible.sync="showResult"
width="300px"
center
>
<div class="check-result">
<i :class="resultIcon"></i>
<span>{{ resultMessage }}</span>
<div class="check-time">{{ checkTime }}</div>
</div>
</el-dialog>
</div>
</template>
<script>
export default {
data() {
return {
checking: false,
currentTime: '',
location: '',
checkType: this.getCheckType(),
showResult: false,
resultMessage: '',
stream: null
}
},
methods: {
async initCamera() {
try {
this.stream = await navigator.mediaDevices.getUserMedia({
video: true
});
this.$refs.video.srcObject = this.stream;
} catch (error) {
this.$message.error('摄像头启动失败');
}
},
getCheckType() {
const now = new Date();
const hour = now.getHours();
return hour < 12 ? 1 : 2; // 12点前为上班打卡
},
async handleCheckIn() {
try {
this.checking = true;
// 1. 获取人脸图片
const faceImage = this.captureFace();
// 2. 获取位置信息
const location = await this.getCurrentLocation();
// 3. 提交打卡
const result = await this.$api.attendance.checkIn({
checkType: this.checkType,
faceImage,
location,
checkTime: new Date()
});
// 4. 显示结果
this.showCheckResult(result);
} catch (error) {
this.$message.error(error.message);
} finally {
this.checking = false;
}
},
captureFace() {
const canvas = this.$refs.canvas;
const video = this.$refs.video;
const context = canvas.getContext('2d');
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
context.drawImage(video, 0, 0);
return canvas.toDataURL('image/jpeg');
}
},
mounted() {
this.initCamera();
// 更新当前时间
setInterval(() => {
this.currentTime = new Date().toLocaleTimeString();
}, 1000);
},
beforeDestroy() {
// 关闭摄像头
if (this.stream) {
this.stream.getTracks().forEach(track => track.stop());
}
}
}
</script>
2. 加班申请
2.1 加班申请表
CREATE TABLE overtime_application (
id BIGINT PRIMARY KEY,
emp_id BIGINT,
start_time DATETIME,
end_time DATETIME,
overtime_type TINYINT COMMENT '加班类型:1-工作日,2-周末,3-节假日',
reason VARCHAR(500),
status TINYINT COMMENT '状态:0-待审批,1-已通过,2-已拒绝',
approver_id BIGINT,
approve_time DATETIME,
approve_remark VARCHAR(255),
create_time DATETIME
);
2.2 加班服务实现
@Service
public class OvertimeService {
@Autowired
private WorkflowService workflowService;
public void applyOvertime(OvertimeDTO overtimeDTO) {
// 1. 验证加班时长
validateOvertimeHours(overtimeDTO);
// 2. 创建加班申请
OvertimeApplication application = new OvertimeApplication();
BeanUtils.copyProperties(overtimeDTO, application);
application.setStatus(ApprovalStatus.PENDING.getCode());
overtimeMapper.insert(application);
// 3. 发起工作流
workflowService.startProcess(
"overtime_process",
application.getId(),
overtimeDTO.getEmpId()
);
}
private void validateOvertimeHours(OvertimeDTO overtime) {
// 计算加班时长
long hours = ChronoUnit.HOURS.between(
overtime.getStartTime(),
overtime.getEndTime()
);
// 获取员工当月已加班时长
int monthlyHours = overtimeMapper.getMonthlyHours(
overtime.getEmpId(),
overtime.getStartTime()
);
// 验证是否超过月度限制
if (monthlyHours + hours > MAX_MONTHLY_HOURS) {
throw new BusinessException("超过月度加班时长限制");
}
}
}
3. 外出登记
3.1 外出登记表
CREATE TABLE business_trip (
id BIGINT PRIMARY KEY,
emp_id BIGINT,
start_time DATETIME,
end_time DATETIME,
destination VARCHAR(255),
purpose VARCHAR(500),
status TINYINT,
contact_info VARCHAR(100),
create_time DATETIME
);
3.2 外出服务实现
@Service
public class BusinessTripService {
public void register(BusinessTripDTO tripDTO) {
// 1. 检查是否有重叠的外出记录
boolean hasOverlap = checkTimeOverlap(
tripDTO.getEmpId(),
tripDTO.getStartTime(),
tripDTO.getEndTime()
);
if (hasOverlap) {
throw new BusinessException("当前时间段已有外出记录");
}
// 2. 保存外出记录
BusinessTrip trip = new BusinessTrip();
BeanUtils.copyProperties(tripDTO, trip);
tripMapper.insert(trip);
// 3. 更新考勤规则
attendanceRuleService.addException(
tripDTO.getEmpId(),
tripDTO.getStartTime(),
tripDTO.getEndTime(),
AttendanceExceptionType.BUSINESS_TRIP
);
}
}
4. 考勤规则设置
4.1 考勤规则表
CREATE TABLE attendance_rule (
id BIGINT PRIMARY KEY,
rule_name VARCHAR(50),
work_start_time TIME,
work_end_time TIME,
late_minutes INT COMMENT '迟到判定分钟数',
early_leave_minutes INT COMMENT '早退判定分钟数',
work_hours DECIMAL(4,1) COMMENT '每日工时',
flexible_time INT COMMENT '弹性工作时间(分钟)',
dept_id BIGINT COMMENT '适用部门',
status TINYINT,
create_time DATETIME
);
4.2 规则配置服务
@Service
public class AttendanceRuleService {
@Cacheable(value = "attendance_rule", key = "#deptId")
public AttendanceRule getDeptRule(Long deptId) {
return ruleMapper.selectByDeptId(deptId);
}
@CacheEvict(value = "attendance_rule", key = "#rule.deptId")
public void updateRule(AttendanceRule rule) {
validateRule(rule);
ruleMapper.updateById(rule);
}
private void validateRule(AttendanceRule rule) {
// 验证工作时间设置
if (rule.getWorkEndTime()
.isBefore(rule.getWorkStartTime())) {
throw new BusinessException("下班时间不能早于上班时间");
}
// 验证弹性工作时间
if (rule.getFlexibleTime() != null
&& rule.getFlexibleTime() > MAX_FLEXIBLE_TIME) {
throw new BusinessException("弹性工作时间超出限制");
}
}
}
5. 异常考勤处理
5.1 异常考勤表
CREATE TABLE attendance_exception (
id BIGINT PRIMARY KEY,
emp_id BIGINT,
exception_date DATE,
exception_type TINYINT COMMENT '异常类型:1-漏打卡,2-迟到,3-早退',
handle_status TINYINT COMMENT '处理状态',
handle_result VARCHAR(255),
handle_time DATETIME,
handler_id BIGINT,
create_time DATETIME
);
5.2 异常处理服务
@Service
public class ExceptionHandleService {
@Async
public void handleException(AttendanceException exception) {
// 1. 判断异常类型
switch (exception.getExceptionType()) {
case MISSING_CHECK:
handleMissingCheck(exception);
break;
case LATE:
handleLate(exception);
break;
case EARLY_LEAVE:
handleEarlyLeave(exception);
break;
}
// 2. 发送通知
notificationService.sendExceptionNotice(exception);
// 3. 更新处理状态
exceptionMapper.updateStatus(
exception.getId(),
ExceptionStatus.HANDLED
);
}
private void handleMissingCheck(AttendanceException exception) {
// 检查是否有相关的请假或外出记录
List<LeaveRecord> leaveRecords = leaveMapper
.findByEmpAndDate(
exception.getEmpId(),
exception.getExceptionDate()
);
if (!leaveRecords.isEmpty()) {
// 有请假记录,标记为已确认
exception.setHandleResult("已确认请假");
return;
}
// 发起补卡申请
createSupplementaryApplication(exception);
}
}
二、请假管理模块
1. 请假申请
1.1 请假记录表
CREATE TABLE leave_record (
id BIGINT PRIMARY KEY,
emp_id BIGINT,
leave_type TINYINT COMMENT '请假类型',
start_time DATETIME,
end_time DATETIME,
duration DECIMAL(5,1) COMMENT '请假时长(天)',
reason VARCHAR(500),
status TINYINT COMMENT '状态:0-待审批,1-已通过,2-已拒绝',
approver_id BIGINT,
approve_time DATETIME,
approve_remark VARCHAR(255),
attachment_url VARCHAR(255) COMMENT '附件URL',
create_time DATETIME
);
1.2 请假服务实现
@Service
public class LeaveService {
@Autowired
private LeaveBalanceService balanceService;
@Autowired
private WorkflowService workflowService;
@Transactional
public void applyLeave(LeaveApplicationDTO leaveDTO) {
// 1. 校验请假时长
validateLeaveDuration(leaveDTO);
// 2. 检查假期余额
checkLeaveBalance(
leaveDTO.getEmpId(),
leaveDTO.getLeaveType(),
leaveDTO.getDuration()
);
// 3. 创建请假记录
LeaveRecord record = new LeaveRecord();
BeanUtils.copyProperties(leaveDTO, record);
record.setStatus(ApprovalStatus.PENDING.getCode());
leaveMapper.insert(record);
// 4. 发起工作流
workflowService.startProcess(
"leave_process",
record.getId(),
leaveDTO.getEmpId()
);
}
private void checkLeaveBalance(Long empId,
LeaveType leaveType,
BigDecimal duration) {
LeaveBalance balance = balanceService
.getBalance(empId, leaveType);
if (balance.getRemaining().compareTo(duration) < 0) {
throw new BusinessException(
leaveType.getName() + "假期余额不足"
);
}
}
}
2. 请假审批
2.1 审批流程配置
@Configuration
public class LeaveWorkflowConfig {
@Autowired
private RuntimeService runtimeService;
@Bean
public ProcessEngineConfiguration leaveProcess() {
return ProcessEngineConfiguration.createStandaloneProcessEngineConfiguration()
.setDatabaseSchemaUpdate(ProcessEngineConfiguration.DB_SCHEMA_UPDATE_TRUE)
.setJdbcUrl("jdbc:mysql://localhost:3306/workflow")
.setJdbcDriver("com.mysql.cj.jdbc.Driver")
.setJdbcUsername("root")
.setJdbcPassword("password")
.setActivityFontName("宋体")
.setLabelFontName("宋体")
.setAnnotationFontName("宋体");
}
}
2.2 审批服务实现
@Service
public class LeaveApprovalService {
@Autowired
private TaskService taskService;
@Autowired
private LeaveBalanceService balanceService;
@Transactional
public void approve(ApprovalDTO approvalDTO) {
// 1. 获取请假记录
LeaveRecord record = leaveMapper
.selectById(approvalDTO.getRecordId());
if (record == null) {
throw new BusinessException("请假记录不存在");
}
// 2. 更新审批状态
record.setStatus(approvalDTO.getApproved()
? ApprovalStatus.APPROVED.getCode()
: ApprovalStatus.REJECTED.getCode());
record.setApproveRemark(approvalDTO.getRemark());
record.setApproveTime(LocalDateTime.now());
record.setApproverId(approvalDTO.getApproverId());
leaveMapper.updateById(record);
// 3. 如果审批通过,扣减假期余额
if (approvalDTO.getApproved()) {
balanceService.deductBalance(
record.getEmpId(),
record.getLeaveType(),
record.getDuration()
);
}
// 4. 完成工作流任务
taskService.complete(approvalDTO.getTaskId());
// 5. 发送通知
notificationService.sendApprovalResult(record);
}
}
3. 请假类型配置
3.1 请假类型表
CREATE TABLE leave_type (
id BIGINT PRIMARY KEY,
type_name VARCHAR(50),
type_code VARCHAR(50),
paid TINYINT COMMENT '是否带薪',
max_duration INT COMMENT '最大请假天数',
min_unit DECIMAL(2,1) COMMENT '最小请假单位(天)',
need_attachment TINYINT COMMENT '是否需要附件',
status TINYINT,
create_time DATETIME
);
3.2 类型配置服务
@Service
public class LeaveTypeService {
@Cacheable(value = "leave_type", key = "#typeId")
public LeaveType getLeaveType(Long typeId) {
return typeMapper.selectById(typeId);
}
@CacheEvict(value = "leave_type", allEntries = true)
public void updateLeaveType(LeaveType leaveType) {
validateLeaveType(leaveType);
typeMapper.updateById(leaveType);
}
private void validateLeaveType(LeaveType type) {
// 验证最大请假天数
if (type.getMaxDuration() <= 0) {
throw new BusinessException("最大请假天数必须大于0");
}
// 验证最小请假单位
if (type.getMinUnit().compareTo(BigDecimal.ZERO) <= 0) {
throw new BusinessException("最小请假单位必须大于0");
}
}
}
4. 假期余额查询
4.1 假期余额表
CREATE TABLE leave_balance (
id BIGINT PRIMARY KEY,
emp_id BIGINT,
leave_type TINYINT,
year INT,
total_days DECIMAL(5,1),
used_days DECIMAL(5,1),
remaining_days DECIMAL(5,1),
update_time DATETIME
);
4.2 余额查询服务
@Service
public class LeaveBalanceService {
@Cacheable(value = "leave_balance",
key = "#empId + ':' + #leaveType")
public LeaveBalance getBalance(Long empId, LeaveType leaveType) {
return balanceMapper.selectByEmpAndType(empId, leaveType);
}
@Transactional
@CacheEvict(value = "leave_balance",
key = "#empId + ':' + #leaveType")
public void deductBalance(Long empId,
LeaveType leaveType,
BigDecimal days) {
LeaveBalance balance = getBalance(empId, leaveType);
if (balance == null) {
throw new BusinessException("假期余额记录不存在");
}
// 检查余额是否足够
if (balance.getRemainingDays().compareTo(days) < 0) {
throw new BusinessException("假期余额不足");
}
// 更新已使用和剩余天数
balance.setUsedDays(
balance.getUsedDays().add(days)
);
balance.setRemainingDays(
balance.getRemainingDays().subtract(days)
);
balanceMapper.updateById(balance);
}
// 年度假期初始化
@Scheduled(cron = "0 0 0 1 1 ?") // 每年1月1日执行
public void initializeYearlyBalance() {
int year = LocalDate.now().getYear();
// 获取所有在职员工
List<Employee> employees = employeeMapper
.selectAllActive();
for (Employee emp : employees) {
// 初始化各类假期余额
initializeBalance(emp.getId(), year);
}
}
}
4.3 余额查询界面
<template>
<div class="leave-balance">
<el-card class="balance-card">
<div slot="header">
<span>假期余额</span>
<el-button
type="text"
@click="refreshBalance"
style="float: right;"
>
刷新
</el-button>
</div>
<el-table :data="balanceList" border>
<el-table-column
prop="typeName"
label="假期类型"
/>
<el-table-column
prop="totalDays"
label="总天数"
/>
<el-table-column
prop="usedDays"
label="已用天数"
/>
<el-table-column
prop="remainingDays"
label="剩余天数"
>
<template slot-scope="scope">
<span :class="{
'warning': scope.row.remainingDays < 5,
'danger': scope.row.remainingDays <= 0
}">
{{ scope.row.remainingDays }}
</span>
</template>
</el-table-column>
</el-table>
</el-card>
<!-- 假期使用记录 -->
<el-card class="usage-card">
<div slot="header">
<span>假期使用记录</span>
</div>
<el-table :data="usageRecords" border>
<el-table-column
prop="leaveType"
label="假期类型"
/>
<el-table-column
prop="startTime"
label="开始时间"
/>
<el-table-column
prop="endTime"
label="结束时间"
/>
<el-table-column
prop="duration"
label="请假天数"
/>
<el-table-column
prop="status"
label="状态"
>
<template slot-scope="scope">
<el-tag :type="getStatusType(scope.row.status)">
{{ getStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
</el-table>
</el-card>
</div>
</template>
<script>
export default {
data() {
return {
balanceList: [],
usageRecords: []
}
},
methods: {
async refreshBalance() {
try {
const response = await this.$api.leave
.getBalance(this.userId);
this.balanceList = response.data;
} catch (error) {
this.$message.error('获取假期余额失败');
}
},
getStatusType(status) {
const typeMap = {
0: 'info',
1: 'success',
2: 'danger'
};
return typeMap[status] || 'info';
},
getStatusText(status) {
const textMap = {
0: '待审批',
1: '已通过',
2: '已拒绝'
};
return textMap[status] || '未知';
}
},
created() {
this.refreshBalance();
}
}
</script>
<style scoped>
.leave-balance {
padding: 20px;
}
.balance-card {
margin-bottom: 20px;
}
.warning {
color: #E6A23C;
}
.danger {
color: #F56C6C;
}
</style>
这些模块的实现涵盖了:
- 数据库表设计
- 业务逻辑实现
- 工作流集成
- 缓存管理
- 定时任务
- 前端界面开发
- 权限控制
- 数据验证
通过这些功能的实现,可以为企业提供完整的考勤和请假管理解决方案。
智能考勤系统 - 统计分析与系统管理模块详解
一、统计分析模块
1. 考勤统计报表
1.1 数据维度
-- 考勤统计视图
CREATE VIEW v_attendance_statistics AS
SELECT
e.department_id,
e.emp_no,
e.name,
DATE_FORMAT(ar.check_in_time, '%Y-%m') as month,
COUNT(DISTINCT DATE(ar.check_in_time)) as work_days,
COUNT(CASE WHEN ar.status = 1 THEN 1 END) as normal_days,
COUNT(CASE WHEN ar.status = 2 THEN 1 END) as late_days,
COUNT(CASE WHEN ar.status = 3 THEN 1 END) as early_leave_days,
COUNT(CASE WHEN ar.status = 4 THEN 1 END) as absent_days
FROM sys_employee e
LEFT JOIN attendance_record ar ON e.id = ar.emp_id
GROUP BY e.id, DATE_FORMAT(ar.check_in_time, '%Y-%m');
1.2 报表类型
- 月度考勤汇总表
- 个人考勤明细表
- 部门考勤对比表
- 异常考勤分析表
1.3 数据展示
@Service
public class AttendanceReportService {
@Autowired
private AttendanceMapper attendanceMapper;
public List<AttendanceReportDTO> generateMonthlyReport(String month) {
return attendanceMapper.getMonthlyStatistics(month);
}
// 支持多种导出格式
public void exportReport(String format, String month) {
List<AttendanceReportDTO> data = generateMonthlyReport(month);
switch(format.toLowerCase()) {
case "excel":
exportToExcel(data);
break;
case "pdf":
exportToPDF(data);
break;
case "csv":
exportToCSV(data);
break;
}
}
}
2. 部门出勤率分析
2.1 核心指标
- 部门整体出勤率
- 迟到率
- 早退率
- 缺勤率
- 加班时长
2.2 可视化展示
<template>
<div class="department-analysis">
<!-- 部门出勤率图表 -->
<div class="chart-container">
<v-chart :option="chartOption" />
</div>
<!-- 部门排名列表 -->
<el-table :data="departmentRanking">
<el-table-column prop="deptName" label="部门" />
<el-table-column prop="attendanceRate" label="出勤率">
<template #default="scope">
<el-progress
:percentage="scope.row.attendanceRate"
:color="getColorByRate(scope.row.attendanceRate)"
/>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
data() {
return {
chartOption: {
title: { text: '部门出勤率分析' },
series: [{
type: 'pie',
data: [
{ value: 95, name: '研发部' },
{ value: 92, name: '市场部' },
{ value: 88, name: '运营部' }
]
}]
}
}
}
}
</script>
3. 加班统计
3.1 加班类型统计
public enum OvertimeType {
WORKDAY(1, "工作日加班"),
WEEKEND(2, "周末加班"),
HOLIDAY(3, "节假日加班");
private int code;
private String desc;
}
@Service
public class OvertimeStatisticsService {
public Map<String, Object> calculateOvertimeHours(Long empId, String month) {
Map<String, Object> statistics = new HashMap<>();
// 计算不同类型加班时长
statistics.put("workdayHours", calculateByType(empId, month, OvertimeType.WORKDAY));
statistics.put("weekendHours", calculateByType(empId, month, OvertimeType.WEEKEND));
statistics.put("holidayHours", calculateByType(empId, month, OvertimeType.HOLIDAY));
// 计算加班费
statistics.put("overtimePay", calculateOvertimePay(statistics));
return statistics;
}
}
4. 请假趋势分析
4.1 请假类型分布
SELECT
leave_type,
COUNT(*) as count,
SUM(TIMESTAMPDIFF(HOUR, start_time, end_time)) as total_hours
FROM leave_record
WHERE DATE_FORMAT(start_time, '%Y-%m') = #{month}
GROUP BY leave_type;
4.2 趋势图表配置
const leaveAnalysisChart = {
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月']
},
yAxis: {
type: 'value'
},
series: [
{
name: '病假',
type: 'line',
data: [10, 8, 12, 6, 9, 7]
},
{
name: '事假',
type: 'line',
data: [5, 7, 4, 8, 6, 9]
},
{
name: '年假',
type: 'line',
data: [2, 4, 6, 8, 3, 5]
}
]
}
二、系统管理模块
1. 用户管理
1.1 用户表设计
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
real_name VARCHAR(50),
email VARCHAR(100),
mobile VARCHAR(20),
status TINYINT DEFAULT 1,
create_time DATETIME,
update_time DATETIME,
last_login_time DATETIME,
remark VARCHAR(255)
);
1.2 用户服务实现
@Service
public class UserService {
@Autowired
private PasswordEncoder passwordEncoder;
public void createUser(UserDTO userDTO) {
// 密码加密
String encodedPassword = passwordEncoder.encode(userDTO.getPassword());
SysUser user = new SysUser();
user.setUsername(userDTO.getUsername());
user.setPassword(encodedPassword);
// ... 设置其他属性
userMapper.insert(user);
}
public void updateUserStatus(Long userId, Integer status) {
userMapper.updateStatus(userId, status);
}
}
2. 角色权限
2.1 RBAC权限模型
-- 角色表
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY,
role_name VARCHAR(50),
role_code VARCHAR(50),
status TINYINT,
create_time DATETIME
);
-- 权限表
CREATE TABLE sys_permission (
id BIGINT PRIMARY KEY,
parent_id BIGINT,
name VARCHAR(50),
type TINYINT,
permission_code VARCHAR(50),
path VARCHAR(200),
component VARCHAR(100),
icon VARCHAR(50),
sort INT
);
-- 角色权限关联表
CREATE TABLE sys_role_permission (
role_id BIGINT,
permission_id BIGINT,
PRIMARY KEY(role_id, permission_id)
);
2.2 权限控制实现
@PreAuthorize("hasRole('ADMIN') or hasPermission(#id, 'attendance:view')")
@GetMapping("/attendance/{id}")
public AttendanceDTO getAttendanceDetail(@PathVariable Long id) {
return attendanceService.getById(id);
}
@Service
public class CustomPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication authentication,
Object targetDomainObject,
Object permission) {
// 权限判断逻辑
return checkPermission(authentication, permission.toString());
}
}
3. 部门管理
3.1 部门树形结构
@Data
public class DepartmentTree {
private Long id;
private String name;
private Long parentId;
private List<DepartmentTree> children;
private String leader;
private Integer employeeCount;
}
@Service
public class DepartmentService {
public List<DepartmentTree> buildDepartmentTree() {
List<Department> allDepts = departmentMapper.selectAll();
return buildTree(allDepts, 0L);
}
private List<DepartmentTree> buildTree(List<Department> depts, Long parentId) {
return depts.stream()
.filter(dept -> dept.getParentId().equals(parentId))
.map(dept -> {
DepartmentTree node = new DepartmentTree();
node.setId(dept.getId());
node.setName(dept.getName());
node.setChildren(buildTree(depts, dept.getId()));
return node;
})
.collect(Collectors.toList());
}
}
4. 系统配置
4.1 配置管理
@Configuration
@ConfigurationProperties(prefix = "system")
@Data
public class SystemConfig {
private AttendanceConfig attendance;
private SecurityConfig security;
private NotificationConfig notification;
@Data
public static class AttendanceConfig {
private String workStartTime;
private String workEndTime;
private Integer lateMinutes;
private Integer earlyLeaveMinutes;
}
}
4.2 动态配置实现
@Service
public class ConfigService {
@Autowired
private StringRedisTemplate redisTemplate;
public void updateConfig(String key, String value) {
// 更新数据库
configMapper.updateValue(key, value);
// 更新缓存
redisTemplate.opsForValue().set(
"system:config:" + key,
value,
1,
TimeUnit.DAYS
);
}
public String getConfig(String key) {
// 先从缓存获取
String value = redisTemplate.opsForValue().get("system:config:" + key);
if (value == null) {
// 缓存未命中,从数据库获取
value = configMapper.selectByKey(key);
if (value != null) {
redisTemplate.opsForValue().set(
"system:config:" + key,
value,
1,
TimeUnit.DAYS
);
}
}
return value;
}
}
4.3 配置页面
<template>
<div class="system-config">
<el-form :model="configForm" label-width="120px">
<el-tabs v-model="activeTab">
<!-- 考勤配置 -->
<el-tab-pane label="考勤配置" name="attendance">
<el-form-item label="上班时间">
<el-time-picker v-model="configForm.workStartTime" />
</el-form-item>
<el-form-item label="下班时间">
<el-time-picker v-model="configForm.workEndTime" />
</el-form-item>
</el-tab-pane>
<!-- 系统配置 -->
<el-tab-pane label="系统配置" name="system">
<el-form-item label="系统名称">
<el-input v-model="configForm.systemName" />
</el-form-item>
<el-form-item label="Logo">
<el-upload
action="/api/system/upload"
:show-file-list="false"
:on-success="handleLogoSuccess">
<img v-if="configForm.logo" :src="configForm.logo" class="logo">
<el-button v-else>上传Logo</el-button>
</el-upload>
</el-form-item>
</el-tab-pane>
</el-tabs>
<el-form-item>
<el-button type="primary" @click="saveConfig">保存配置</el-button>
</el-form-item>
</el-form>
</div>
</template>
<script>
export default {
data() {
return {
activeTab: 'attendance',
configForm: {
workStartTime: '',
workEndTime: '',
systemName: '',
logo: ''
}
}
},
methods: {
async saveConfig() {
try {
await this.$api.system.updateConfig(this.configForm)
this.$message.success('配置保存成功')
} catch (error) {
this.$message.error('配置保存失败')
}
}
}
}
</script>
这些模块的实现涉及到了前后端的完整开发流程,包括:
- 数据库表设计
- 后端服务实现
- 权限控制
- 缓存优化
- 前端界面开发
- 数据可视化
通过这些功能的实现,可以为企业提供完整的考勤管理解决方案。