当前位置: 首页 > article >正文

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>

项目亮点

  1. 前后端分离架构

    • 采用RESTful API设计规范
    • 使用JWT实现无状态认证
  2. 权限控制

    • 基于RBAC模型的权限设计
    • 细粒度的接口权限控制
  3. 性能优化

    • Redis缓存优化
    • MyBatis Plus分页查询优化
    • 前端组件按需加载
  4. 数据安全

    • 密码加密存储
    • SQL注入防护
    • XSS防护

部署方案

  1. 环境要求

    • JDK 1.8+
    • MySQL 8.0+
    • Redis 6.0+
    • Node.js 14+
  2. 部署步骤

    # 后端部署
    mvn clean package
    java -jar student-system.jar
    
    # 前端部署
    npm install
    npm run build
    

总结与展望

本项目实现了学生档案管理的核心功能,采用主流的技术栈和最佳实践,具有良好的可扩展性。未来计划添加以下功能:

  1. 接入第三方登录
  2. 添加移动端适配
  3. 引入数据分析功能
  4. 优化系统性能
  5. 增加系统监控功能

通过本项目的开发,不仅实现了学生档案的信息化管理,也为后续的功能扩展打下了良好的基础。

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. 安全性考虑

  1. 数据验证

    • 使用JSR-303注解进行参数校验
    • 前端表单验证
    • 文件上传类型限制
  2. 权限控制

    • 基于RBAC的权限控制
    • 使用Spring Security注解控制接口访问
    • 前端按钮级别权限控制
  3. 敏感信息保护

    • 身份证号等敏感信息加密存储
    • 密码加密传输和存储
    • 文件上传路径安全处理
  4. 操作日志

    • 记录重要操作日志
    • 使用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. 系统优化

  1. 性能优化

    • 使用Redis缓存考勤统计数据
    • 批量处理考勤记录
    • 定时任务性能优化
  2. 用户体验

    • 移动端适配
    • 实时推送考勤异常提醒
    • 请假审批进度实时通知
  3. 数据分析

    • 考勤趋势分析
    • 学生成绩与考勤关联分析
    • 可视化报表展示
  4. 系统集成

    • 对接门禁系统
    • 集成短信通知
    • 支持微信小程序签到

这两个模块通过合理的数据结构设计和功能实现,为学生档案管理系统提供了完整的学习记录和考勤管理功能。系统不仅支持基本的数据录入和管理,还提供了丰富的统计分析功能,帮助教师更好地了解学生的学习情况和出勤状况。


http://www.kler.cn/a/444935.html

相关文章:

  • 坑人 C# MySql.Data SDK
  • Springboot 学习 之 logback-spring.xml 日志压缩 .tmp 临时文件问题
  • unity接入coze智能体
  • 探秘C语言:从诞生到广泛应用的编程世界
  • 使用计算机创建一个虚拟世界
  • WEB开发: 全栈工程师起步 - Python Flask +SQLite的管理系统实现
  • 网络安全等级保护—定级
  • 我在广州学 Mysql 系列——有关 Mysql 函数的练习
  • 发送webhook到飞书机器人
  • Kingbase数据库备份还原操作手册
  • 解锁 Jenkins+Ant+Jmeter 自动化框架搭建新思路
  • 【Ubuntu】设置静态Ip
  • HTML5+CSS3+JS制作精美的宠物主题网站(内附源码,含5个页面)
  • 前端之CSS光速入门
  • 在Win11系统上安装Android Studio
  • 【C#】方法参数的修饰符ref 与 out
  • 华纳云:虚拟服务器之间如何分布式运行?
  • PostgreSQL的交互式终端使用一系列命令来获取有关文本搜索配置对象的信息
  • WPF Binding 绑定
  • linux常用命令(touch、cat、less、head、tail)
  • Scala的惰性求值:深入理解与实践
  • 回归预测模型 | LSTM、CNN、Transformer、TCN、串行、并行模型集合
  • 最大子数组和 最大子数组和(有长度限制) 最大m段子数组和
  • windows openssl编译x64版libssl.lib,编译x64版本libcurl.lib,支持https,vs2015编译器
  • 【NVIDIA】启动ubuntu后显卡驱动丢失
  • esp8266_TFTST7735语音识别UI界面虚拟小助手