Java全栈项目 - 学生档案管理系统
项目介绍
学生档案管理系统是一个基于 Spring Boot + Vue.js 的全栈项目,主要用于管理学生的基本信息、学习记录、考勤情况等数据。系统采用前后端分离架构,具有良好的可扩展性和维护性。
技术栈
后端技术
- Spring Boot 2.7.x
- Spring Security
- MyBatis Plus
- MySQL 8.0
- Redis
- JWT
前端技术
- Vue 3
- Element Plus
- Axios
- Vue Router
- Pinia
核心功能
1. 用户管理
- 用户登录/注销
- 角色权限控制
- 密码修改
- 用户信息管理
2. 学生信息管理
- 基本信息录入与修改
- 学生照片上传
- 信息批量导入/导出
- 学生信息查询与筛选
3. 学习记录管理
- 成绩录入
- 成绩统计分析
- 学习评价
- 成绩报表导出
4. 考勤管理
- 考勤记录
- 请假管理
- 考勤统计
- 异常考勤提醒
数据库设计
主要数据表
-- 学生信息表
CREATE TABLE student (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_no VARCHAR(20) NOT NULL COMMENT '学号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
gender TINYINT COMMENT '性别:0-女,1-男',
birth_date DATE COMMENT '出生日期',
phone VARCHAR(20) COMMENT '联系电话',
email VARCHAR(100) COMMENT '邮箱',
address TEXT COMMENT '家庭住址',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 成绩记录表
CREATE TABLE score (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_id BIGINT COMMENT '学生ID',
subject VARCHAR(50) COMMENT '科目',
score DECIMAL(5,2) COMMENT '分数',
exam_time DATE COMMENT '考试时间',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
系统架构
├── backend // 后端项目
│ ├── src/main/java
│ │ ├── config // 配置类
│ │ ├── controller // 控制器
│ │ ├── service // 服务层
│ │ ├── mapper // 数据访问层
│ │ ├── entity // 实体类
│ │ └── util // 工具类
│ └── resources
│ └── application.yml // 配置文件
│
├── frontend // 前端项目
│ ├── src
│ │ ├── api // 接口请求
│ │ ├── components // 组件
│ │ ├── router // 路由配置
│ │ ├── store // 状态管理
│ │ ├── utils // 工具函数
│ │ └── views // 页面
│ └── package.json
核心代码示例
后端接口示例
@RestController
@RequestMapping("/api/student")
public class StudentController {
@Autowired
private StudentService studentService;
@GetMapping("/list")
public Result<IPage<Student>> list(PageParam pageParam, StudentQuery query) {
IPage<Student> page = studentService.getStudentList(pageParam, query);
return Result.success(page);
}
@PostMapping("/add")
public Result<Boolean> add(@RequestBody Student student) {
boolean success = studentService.save(student);
return Result.success(success);
}
}
前端代码示例
<template>
<div class="student-list">
<el-table :data="studentList" border>
<el-table-column prop="studentNo" label="学号" />
<el-table-column prop="name" label="姓名" />
<el-table-column prop="gender" label="性别">
<template #default="scope">
{{ scope.row.gender === 1 ? '男' : '女' }}
</template>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="danger" @click="handleDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
项目亮点
-
前后端分离架构
- 采用RESTful API设计规范
- 使用JWT实现无状态认证
-
权限控制
- 基于RBAC模型的权限设计
- 细粒度的接口权限控制
-
性能优化
- Redis缓存优化
- MyBatis Plus分页查询优化
- 前端组件按需加载
-
数据安全
- 密码加密存储
- SQL注入防护
- XSS防护
部署方案
-
环境要求
- JDK 1.8+
- MySQL 8.0+
- Redis 6.0+
- Node.js 14+
-
部署步骤
# 后端部署 mvn clean package java -jar student-system.jar # 前端部署 npm install npm run build
总结与展望
本项目实现了学生档案管理的核心功能,采用主流的技术栈和最佳实践,具有良好的可扩展性。未来计划添加以下功能:
- 接入第三方登录
- 添加移动端适配
- 引入数据分析功能
- 优化系统性能
- 增加系统监控功能
通过本项目的开发,不仅实现了学生档案的信息化管理,也为后续的功能扩展打下了良好的基础。
Java全栈项目 - 学生档案管理系统(一):用户与学生信息管理模块详解
一、用户管理模块
1. 数据库设计
-- 用户表
CREATE TABLE sys_user (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL COMMENT '用户名',
password VARCHAR(100) NOT NULL COMMENT '密码',
real_name VARCHAR(50) COMMENT '真实姓名',
phone VARCHAR(20) COMMENT '手机号',
email VARCHAR(100) COMMENT '邮箱',
avatar VARCHAR(255) COMMENT '头像URL',
status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- 角色表
CREATE TABLE sys_role (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
role_name VARCHAR(50) NOT NULL COMMENT '角色名称',
role_code VARCHAR(50) NOT NULL COMMENT '角色编码',
description VARCHAR(200) COMMENT '角色描述',
status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用'
);
-- 用户角色关联表
CREATE TABLE sys_user_role (
user_id BIGINT NOT NULL,
role_id BIGINT NOT NULL,
PRIMARY KEY (user_id, role_id)
);
-- 权限表
CREATE TABLE sys_permission (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
parent_id BIGINT COMMENT '父权限ID',
name VARCHAR(50) NOT NULL COMMENT '权限名称',
code VARCHAR(50) NOT NULL COMMENT '权限编码',
type TINYINT COMMENT '类型:1-菜单,2-按钮',
url VARCHAR(200) COMMENT '访问路径',
status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用'
);
2. 核心功能实现
2.1 用户登录/注销
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private AuthService authService;
@PostMapping("/login")
public Result<LoginVO> login(@RequestBody LoginDTO loginDTO) {
// 验证码校验
if (!captchaService.verify(loginDTO.getCaptchaKey(), loginDTO.getCaptchaCode())) {
throw new BusinessException("验证码错误");
}
// 用户认证
String token = authService.login(loginDTO.getUsername(), loginDTO.getPassword());
// 获取用户信息
UserVO userInfo = authService.getUserInfo();
return Result.success(new LoginVO(token, userInfo));
}
@PostMapping("/logout")
public Result<Void> logout() {
authService.logout();
return Result.success();
}
}
2.2 角色权限控制
@Service
public class RoleServiceImpl implements RoleService {
@Autowired
private RoleMapper roleMapper;
@Override
public List<RoleVO> getUserRoles(Long userId) {
return roleMapper.selectRolesByUserId(userId);
}
@Override
@PreAuthorize("hasRole('ADMIN')")
public void assignRole(Long userId, List<Long> roleIds) {
// 删除原有角色
roleMapper.deleteUserRoles(userId);
// 分配新角色
if (!CollectionUtils.isEmpty(roleIds)) {
roleMapper.insertUserRoles(userId, roleIds);
}
}
}
2.3 密码修改
@Service
public class UserServiceImpl implements UserService {
@Autowired
private PasswordEncoder passwordEncoder;
@Override
public void updatePassword(Long userId, String oldPassword, String newPassword) {
SysUser user = userMapper.selectById(userId);
if (user == null) {
throw new BusinessException("用户不存在");
}
// 校验原密码
if (!passwordEncoder.matches(oldPassword, user.getPassword())) {
throw new BusinessException("原密码错误");
}
// 更新密码
user.setPassword(passwordEncoder.encode(newPassword));
userMapper.updateById(user);
}
}
二、学生信息管理模块
1. 数据库设计
-- 学生基本信息表
CREATE TABLE student_info (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_no VARCHAR(20) NOT NULL COMMENT '学号',
name VARCHAR(50) NOT NULL COMMENT '姓名',
gender TINYINT COMMENT '性别:0-女,1-男',
birth_date DATE COMMENT '出生日期',
id_card VARCHAR(18) COMMENT '身份证号',
phone VARCHAR(20) COMMENT '联系电话',
email VARCHAR(100) COMMENT '邮箱',
address TEXT COMMENT '家庭住址',
photo_url VARCHAR(255) COMMENT '照片URL',
class_id BIGINT COMMENT '班级ID',
status TINYINT DEFAULT 1 COMMENT '状态:1-在读,2-毕业,3-退学',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
2. 核心功能实现
2.1 学生信息管理接口
@RestController
@RequestMapping("/api/student")
public class StudentController {
@Autowired
private StudentService studentService;
@PostMapping("/add")
@PreAuthorize("hasPermission('student:add')")
public Result<Boolean> add(@Valid @RequestBody StudentDTO studentDTO) {
return Result.success(studentService.addStudent(studentDTO));
}
@PutMapping("/update")
@PreAuthorize("hasPermission('student:update')")
public Result<Boolean> update(@Valid @RequestBody StudentDTO studentDTO) {
return Result.success(studentService.updateStudent(studentDTO));
}
@GetMapping("/page")
@PreAuthorize("hasPermission('student:view')")
public Result<IPage<StudentVO>> page(StudentQueryDTO queryDTO) {
return Result.success(studentService.getStudentPage(queryDTO));
}
@PostMapping("/import")
@PreAuthorize("hasPermission('student:import')")
public Result<ImportResultVO> importStudents(MultipartFile file) {
return Result.success(studentService.importStudents(file));
}
@GetMapping("/export")
@PreAuthorize("hasPermission('student:export')")
public void export(StudentQueryDTO queryDTO, HttpServletResponse response) {
studentService.exportStudents(queryDTO, response);
}
}
2.2 文件上传服务
@Service
public class FileServiceImpl implements FileService {
@Value("${file.upload.path}")
private String uploadPath;
@Value("${file.access.url}")
private String accessUrl;
@Override
public String uploadPhoto(MultipartFile file) {
// 校验文件类型
String originalFilename = file.getOriginalFilename();
if (!isImageFile(originalFilename)) {
throw new BusinessException("只能上传图片文件");
}
// 生成文件名
String fileName = generateFileName(originalFilename);
// 保存文件
try {
File targetFile = new File(uploadPath + fileName);
FileUtils.copyInputStreamToFile(file.getInputStream(), targetFile);
} catch (IOException e) {
throw new BusinessException("文件上传失败");
}
return accessUrl + fileName;
}
}
2.3 Excel导入导出
@Service
public class StudentExcelService {
@Autowired
private StudentService studentService;
public ImportResultVO importStudents(MultipartFile file) {
ImportResultVO result = new ImportResultVO();
try {
EasyExcel.read(file.getInputStream(), StudentImportDTO.class, new StudentImportListener(studentService))
.sheet()
.doRead();
} catch (Exception e) {
throw new BusinessException("导入失败:" + e.getMessage());
}
return result;
}
public void exportStudents(List<StudentVO> students, HttpServletResponse response) {
try {
response.setContentType("application/vnd.ms-excel");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("学生信息", "UTF-8");
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), StudentExportVO.class)
.sheet("学生信息")
.doWrite(students);
} catch (Exception e) {
throw new BusinessException("导出失败:" + e.getMessage());
}
}
}
3. 前端实现
3.1 学生信息列表
<template>
<div class="student-list">
<!-- 搜索表单 -->
<el-form :inline="true" :model="queryForm" class="search-form">
<el-form-item label="学号">
<el-input v-model="queryForm.studentNo" placeholder="请输入学号" />
</el-form-item>
<el-form-item label="姓名">
<el-input v-model="queryForm.name" placeholder="请输入姓名" />
</el-form-item>
<el-form-item label="班级">
<el-select v-model="queryForm.classId" placeholder="请选择班级">
<el-option
v-for="item in classList"
:key="item.id"
:label="item.name"
:value="item.id"
/>
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">查询</el-button>
<el-button @click="resetForm">重置</el-button>
</el-form-item>
</el-form>
<!-- 操作按钮 -->
<div class="operation-bar">
<el-button type="primary" @click="handleAdd">新增学生</el-button>
<el-button type="success" @click="handleImport">批量导入</el-button>
<el-button type="warning" @click="handleExport">导出Excel</el-button>
</div>
<!-- 数据表格 -->
<el-table
:data="studentList"
border
v-loading="loading"
>
<el-table-column prop="studentNo" label="学号" width="120" />
<el-table-column prop="name" label="姓名" width="100" />
<el-table-column prop="gender" label="性别" width="60">
<template #default="scope">
{{ scope.row.gender === 1 ? '男' : '女' }}
</template>
</el-table-column>
<el-table-column prop="phone" label="联系电话" width="120" />
<el-table-column prop="className" label="班级" width="120" />
<el-table-column prop="status" label="状态" width="80">
<template #default="scope">
<el-tag :type="getStatusType(scope.row.status)">
{{ getStatusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="200" fixed="right">
<template #default="scope">
<el-button type="text" @click="handleEdit(scope.row)">编辑</el-button>
<el-button type="text" @click="handleViewDetail(scope.row)">查看</el-button>
<el-popconfirm
title="确定删除该学生信息吗?"
@confirm="handleDelete(scope.row)"
>
<template #reference>
<el-button type="text" class="delete-btn">删除</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<el-pagination
:current-page="page.current"
:page-size="page.size"
:total="page.total"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
<!-- 新增/编辑对话框 -->
<student-dialog
v-if="dialogVisible"
:visible.sync="dialogVisible"
:student="currentStudent"
@success="handleDialogSuccess"
/>
</div>
</template>
<script>
import { ref, reactive } from 'vue'
import { getStudentList, deleteStudent } from '@/api/student'
import StudentDialog from './components/StudentDialog.vue'
export default {
name: 'StudentList',
components: { StudentDialog },
setup() {
const loading = ref(false)
const dialogVisible = ref(false)
const currentStudent = ref(null)
const queryForm = reactive({
studentNo: '',
name: '',
classId: null
})
const page = reactive({
current: 1,
size: 10,
total: 0
})
// 获取学生列表
const loadData = async () => {
loading.value = true
try {
const res = await getStudentList({
...queryForm,
current: page.current,
size: page.size
})
studentList.value = res.data.records
page.total = res.data.total
} finally {
loading.value = false
}
}
// 其他方法实现...
return {
loading,
queryForm,
page,
dialogVisible,
currentStudent,
loadData,
// ...其他方法
}
}
}
</script>
4. 安全性考虑
-
数据验证
- 使用JSR-303注解进行参数校验
- 前端表单验证
- 文件上传类型限制
-
权限控制
- 基于RBAC的权限控制
- 使用Spring Security注解控制接口访问
- 前端按钮级别权限控制
-
敏感信息保护
- 身份证号等敏感信息加密存储
- 密码加密传输和存储
- 文件上传路径安全处理
-
操作日志
- 记录重要操作日志
- 使用AOP统一处理日志记录
这两个模块是学生档案管理系统的基础,为其他功能模块提供了必要的支持。通过合理的数据库设计、规范的接口实现和友好的用户界面,实现了用户管理和学生信息管理的核心功能。
Java全栈项目 - 学生档案管理系统(二):学习记录与考勤管理模块详解
一、学习记录管理模块
1. 数据库设计
-- 课程表
CREATE TABLE course (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
course_name VARCHAR(100) NOT NULL COMMENT '课程名称',
course_code VARCHAR(50) NOT NULL COMMENT '课程代码',
credit DECIMAL(3,1) COMMENT '学分',
teacher_id BIGINT COMMENT '任课教师ID',
semester VARCHAR(20) COMMENT '学期',
status TINYINT DEFAULT 1 COMMENT '状态:0-禁用,1-启用'
);
-- 成绩记录表
CREATE TABLE score_record (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_id BIGINT NOT NULL COMMENT '学生ID',
course_id BIGINT NOT NULL COMMENT '课程ID',
score DECIMAL(5,2) COMMENT '成绩',
exam_type TINYINT COMMENT '考试类型:1-平时成绩,2-期中考试,3-期末考试',
score_type TINYINT COMMENT '成绩类型:1-百分制,2-等级制',
grade_level VARCHAR(2) COMMENT '等级:A+、A、B+、B、C+、C、D、F',
remark TEXT COMMENT '备注',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
create_by BIGINT COMMENT '创建人ID'
);
-- 学习评价表
CREATE TABLE study_evaluation (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_id BIGINT NOT NULL COMMENT '学生ID',
course_id BIGINT NOT NULL COMMENT '课程ID',
semester VARCHAR(20) COMMENT '学期',
attendance_rate DECIMAL(5,2) COMMENT '出勤率',
homework_completion DECIMAL(5,2) COMMENT '作业完成率',
class_performance DECIMAL(5,2) COMMENT '课堂表现',
evaluation_content TEXT COMMENT '评价内容',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
create_by BIGINT COMMENT '评价人ID'
);
2. 核心功能实现
2.1 成绩录入与管理
@RestController
@RequestMapping("/api/score")
public class ScoreController {
@Autowired
private ScoreService scoreService;
@PostMapping("/batch-save")
@PreAuthorize("hasPermission('score:add')")
public Result<Boolean> batchSaveScore(@RequestBody List<ScoreDTO> scoreDTOList) {
return Result.success(scoreService.batchSaveScore(scoreDTOList));
}
@GetMapping("/student/{studentId}")
public Result<List<ScoreVO>> getStudentScores(
@PathVariable Long studentId,
@RequestParam(required = false) String semester) {
return Result.success(scoreService.getStudentScores(studentId, semester));
}
}
@Service
public class ScoreServiceImpl implements ScoreService {
@Autowired
private ScoreRecordMapper scoreRecordMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean batchSaveScore(List<ScoreDTO> scoreDTOList) {
List<ScoreRecord> records = scoreDTOList.stream()
.map(this::convertToEntity)
.collect(Collectors.toList());
return scoreRecordMapper.batchInsert(records) > 0;
}
@Override
public ScoreAnalysisVO analyzeClassScore(Long courseId, String semester) {
List<ScoreRecord> records = scoreRecordMapper.selectByCourseAndSemester(courseId, semester);
return ScoreAnalysisVO.builder()
.averageScore(calculateAverage(records))
.maxScore(calculateMax(records))
.minScore(calculateMin(records))
.passRate(calculatePassRate(records))
.distributionMap(calculateDistribution(records))
.build();
}
}
2.2 成绩统计分析
@Service
public class ScoreAnalysisService {
public ScoreStatisticsVO calculateStatistics(List<ScoreRecord> records) {
DoubleSummaryStatistics stats = records.stream()
.mapToDouble(ScoreRecord::getScore)
.summaryStatistics();
return ScoreStatisticsVO.builder()
.averageScore(stats.getAverage())
.maxScore(stats.getMax())
.minScore(stats.getMin())
.totalCount(stats.getCount())
.passCount(calculatePassCount(records))
.excellentCount(calculateExcellentCount(records))
.build();
}
public Map<String, Long> calculateDistribution(List<ScoreRecord> records) {
return records.stream()
.collect(Collectors.groupingBy(
record -> getScoreLevel(record.getScore()),
Collectors.counting()
));
}
private String getScoreLevel(double score) {
if (score >= 90) return "优秀";
if (score >= 80) return "良好";
if (score >= 70) return "中等";
if (score >= 60) return "及格";
return "不及格";
}
}
2.3 成绩报表导出
@Service
public class ScoreExportService {
@Autowired
private ScoreService scoreService;
public void exportScoreReport(ScoreQueryDTO queryDTO, HttpServletResponse response) {
List<ScoreExportVO> exportData = scoreService.getScoreExportData(queryDTO);
try {
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
response.setCharacterEncoding("utf-8");
String fileName = URLEncoder.encode("成绩报表", "UTF-8").replaceAll("\\+", "%20");
response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
EasyExcel.write(response.getOutputStream(), ScoreExportVO.class)
.sheet("成绩数据")
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.doWrite(exportData);
} catch (IOException e) {
throw new BusinessException("导出失败:" + e.getMessage());
}
}
}
二、考勤管理模块
1. 数据库设计
-- 考勤记录表
CREATE TABLE attendance_record (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_id BIGINT NOT NULL COMMENT '学生ID',
course_id BIGINT NOT NULL COMMENT '课程ID',
attendance_date DATE NOT NULL COMMENT '考勤日期',
attendance_type TINYINT COMMENT '考勤类型:1-正常,2-迟到,3-早退,4-旷课,5-请假',
check_in_time DATETIME COMMENT '签到时间',
check_out_time DATETIME COMMENT '签退时间',
remark VARCHAR(255) COMMENT '备注',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- 请假申请表
CREATE TABLE leave_application (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
student_id BIGINT NOT NULL COMMENT '学生ID',
leave_type TINYINT COMMENT '请假类型:1-事假,2-病假,3-其他',
start_time DATETIME NOT NULL COMMENT '开始时间',
end_time DATETIME NOT NULL COMMENT '结束时间',
reason TEXT COMMENT '请假原因',
attachment_url VARCHAR(255) COMMENT '附件URL',
status TINYINT COMMENT '状态:1-待审核,2-已通过,3-已驳回',
approver_id BIGINT COMMENT '审批人ID',
approve_time DATETIME COMMENT '审批时间',
approve_remark VARCHAR(255) COMMENT '审批意见',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
2. 核心功能实现
2.1 考勤记录管理
@RestController
@RequestMapping("/api/attendance")
public class AttendanceController {
@Autowired
private AttendanceService attendanceService;
@PostMapping("/check-in")
public Result<Boolean> checkIn(@RequestBody AttendanceCheckDTO checkDTO) {
return Result.success(attendanceService.checkIn(checkDTO));
}
@GetMapping("/statistics")
public Result<AttendanceStatisticsVO> getStatistics(
@RequestParam Long studentId,
@RequestParam String semester) {
return Result.success(attendanceService.calculateStatistics(studentId, semester));
}
}
@Service
public class AttendanceServiceImpl implements AttendanceService {
@Autowired
private AttendanceRecordMapper attendanceRecordMapper;
@Override
public boolean checkIn(AttendanceCheckDTO checkDTO) {
// 判断是否迟到
AttendanceType type = determineAttendanceType(checkDTO.getCourseId(), LocalDateTime.now());
AttendanceRecord record = AttendanceRecord.builder()
.studentId(checkDTO.getStudentId())
.courseId(checkDTO.getCourseId())
.attendanceDate(LocalDate.now())
.attendanceType(type.getCode())
.checkInTime(LocalDateTime.now())
.build();
return attendanceRecordMapper.insert(record) > 0;
}
private AttendanceType determineAttendanceType(Long courseId, LocalDateTime checkTime) {
// 获取课程时间安排
CourseSchedule schedule = courseScheduleMapper.selectByCourseId(courseId);
// 判断考勤类型
if (checkTime.isAfter(schedule.getStartTime().plusMinutes(15))) {
return AttendanceType.LATE;
}
return AttendanceType.NORMAL;
}
}
2.2 请假管理
@RestController
@RequestMapping("/api/leave")
public class LeaveApplicationController {
@Autowired
private LeaveApplicationService leaveService;
@PostMapping("/apply")
public Result<Boolean> applyLeave(@RequestBody LeaveApplicationDTO leaveDTO) {
return Result.success(leaveService.submitApplication(leaveDTO));
}
@PostMapping("/approve/{id}")
@PreAuthorize("hasRole('TEACHER')")
public Result<Boolean> approveLeave(
@PathVariable Long id,
@RequestBody LeaveApprovalDTO approvalDTO) {
return Result.success(leaveService.approveApplication(id, approvalDTO));
}
}
@Service
public class LeaveApplicationServiceImpl implements LeaveApplicationService {
@Autowired
private LeaveApplicationMapper leaveApplicationMapper;
@Override
@Transactional(rollbackFor = Exception.class)
public boolean submitApplication(LeaveApplicationDTO leaveDTO) {
// 验证请假时间
validateLeaveTime(leaveDTO);
LeaveApplication application = convertToEntity(leaveDTO);
application.setStatus(LeaveStatus.PENDING.getCode());
// 保存请假申请
boolean success = leaveApplicationMapper.insert(application) > 0;
if (success) {
// 发送通知给相关教师
notifyTeachers(application);
}
return success;
}
@Override
public boolean approveApplication(Long id, LeaveApprovalDTO approvalDTO) {
LeaveApplication application = leaveApplicationMapper.selectById(id);
if (application == null) {
throw new BusinessException("请假申请不存在");
}
application.setStatus(approvalDTO.getApproved() ?
LeaveStatus.APPROVED.getCode() :
LeaveStatus.REJECTED.getCode());
application.setApproveRemark(approvalDTO.getRemark());
application.setApproveTime(LocalDateTime.now());
application.setApproverId(SecurityUtils.getCurrentUserId());
boolean success = leaveApplicationMapper.updateById(application) > 0;
if (success) {
// 发送通知给学生
notifyStudent(application);
}
return success;
}
}
2.3 考勤统计与提醒
@Service
public class AttendanceAnalysisService {
@Autowired
private AttendanceRecordMapper attendanceRecordMapper;
@Autowired
private MessageService messageService;
public AttendanceStatisticsVO calculateStatistics(Long studentId, String semester) {
List<AttendanceRecord> records = attendanceRecordMapper
.selectByStudentAndSemester(studentId, semester);
return AttendanceStatisticsVO.builder()
.totalDays(records.size())
.normalDays(countByType(records, AttendanceType.NORMAL))
.lateDays(countByType(records, AttendanceType.LATE))
.absentDays(countByType(records, AttendanceType.ABSENT))
.leaveDays(countByType(records, AttendanceType.LEAVE))
.attendanceRate(calculateAttendanceRate(records))
.build();
}
@Scheduled(cron = "0 0 20 * * ?") // 每天晚上8点执行
public void checkAbsenceAndNotify() {
// 获取当天的考勤异常记录
List<AttendanceAbnormalVO> abnormalRecords = attendanceRecordMapper
.selectAbnormalRecords(LocalDate.now());
for (AttendanceAbnormalVO record : abnormalRecords) {
// 发送通知给学生
messageService.sendMessage(MessageDTO.builder()
.userId(record.getStudentId())
.title("考勤异常提醒")
.content(buildAbnormalMessage(record))
.type(MessageType.ATTENDANCE_ABNORMAL)
.build());
// 发送通知给班主任
messageService.sendMessage(MessageDTO.builder()
.userId(record.getTeacherId())
.title("学生考勤异常通知")
.content(buildTeacherMessage(record))
.type(MessageType.ATTENDANCE_ABNORMAL)
.build());
}
}
}
3. 前端实现
3.1 考勤记录页面
<template>
<div class="attendance-page">
<!-- 日历视图 -->
<el-calendar v-model="currentDate">
<template #dateCell="{ data }">
<div class="calendar-cell" @click="showDayDetail(data)">
<span>{{ data.day.split('-').slice(2).join('') }}</span>
<div class="attendance-status" v-if="getAttendanceStatus(data)">
<el-tag :type="getStatusType(getAttendanceStatus(data))">
{{ getStatusText(getAttendanceStatus(data)) }}
</el-tag>
</div>
</div>
</template>
</el-calendar>
<!-- 考勤详情弹窗 -->
<el-dialog
title="考勤详情"
:visible.sync="dialogVisible"
width="600px"
>
<div class="attendance-detail">
<div class="detail-item" v-for="item in dayAttendanceList" :key="item.id">
<span class="course-name">{{ item.courseName }}</span>
<span class="attendance-time">
{{ formatDateTime(item.checkInTime) }}
</span>
<span class="attendance-status">
<el-tag :type="getStatusType(item.attendanceType)">
{{ getStatusText(item.attendanceType) }}
</el-tag>
</span>
</div>
</div>
</el-dialog>
<!-- 请假申请按钮 -->
<el-button
type="primary"
class="leave-button"
@click="showLeaveDialog"
>
申请请假
</el-button>
<!-- 请假申请表单 -->
<leave-application-dialog
v-if="leaveDialogVisible"
:visible.sync="leaveDialogVisible"
@success="handleLeaveSuccess"
/>
</div>
</template>
<script>
import { ref, reactive, onMounted } from 'vue'
import { getAttendanceList, getAttendanceDetail } from '@/api/attendance'
import LeaveApplicationDialog from './components/LeaveApplicationDialog.vue'
export default {
name: 'AttendancePage',
components: { LeaveApplicationDialog },
setup() {
const currentDate = ref(new Date())
const dialogVisible = ref(false)
const leaveDialogVisible = ref(false)
const dayAttendanceList = ref([])
const attendanceMap = reactive({})
// 获取考勤数据
const loadAttendanceData = async () => {
try {
const res = await getAttendanceList({
year: currentDate.value.getFullYear(),
month: currentDate.value.getMonth() + 1
})
// 将考勤数据转换为日期映射
res.data.forEach(item => {
const date = item.attendanceDate.split('T')[0]
if (!attendanceMap[date]) {
attendanceMap[date] = []
}
attendanceMap[date].push(item)
})
} catch (error) {
console.error('获取考勤数据失败:', error)
}
}
// 获取某天的考勤状态
const getAttendanceStatus = (data) => {
const date = data.day
return attendanceMap[date]?.[0]?.attendanceType
}
// 显示某天的详细考勤记录
const showDayDetail = async (data) => {
const date = data.day
try {
const res = await getAttendanceDetail(date)
dayAttendanceList.value = res.data
dialogVisible.value = true
} catch (error) {
console.error('获取考勤详情失败:', error)
}
}
onMounted(() => {
loadAttendanceData()
})
return {
currentDate,
dialogVisible,
leaveDialogVisible,
dayAttendanceList,
getAttendanceStatus,
showDayDetail
}
}
}
</script>
<style scoped>
.attendance-page {
padding: 20px;
}
.calendar-cell {
height: 100%;
position: relative;
cursor: pointer;
}
.attendance-status {
position: absolute;
bottom: 5px;
right: 5px;
}
.attendance-detail {
max-height: 400px;
overflow-y: auto;
}
.detail-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px;
border-bottom: 1px solid #eee;
}
.leave-button {
position: fixed;
bottom: 30px;
right: 30px;
}
</style>
4. 系统优化
-
性能优化
- 使用Redis缓存考勤统计数据
- 批量处理考勤记录
- 定时任务性能优化
-
用户体验
- 移动端适配
- 实时推送考勤异常提醒
- 请假审批进度实时通知
-
数据分析
- 考勤趋势分析
- 学生成绩与考勤关联分析
- 可视化报表展示
-
系统集成
- 对接门禁系统
- 集成短信通知
- 支持微信小程序签到
这两个模块通过合理的数据结构设计和功能实现,为学生档案管理系统提供了完整的学习记录和考勤管理功能。系统不仅支持基本的数据录入和管理,还提供了丰富的统计分析功能,帮助教师更好地了解学生的学习情况和出勤状况。