作者介绍:专注于计算机课设、毕设辅导,本人开发原创代码一题一稿绝不撞题坚持原创个人创作非工作室源码全网唯一

原创唯一:个人原创开发,独立设计数据库与业务逻辑,拒绝工作室代码改造

技术主流:SpringBoot + Vue 前后端分离,MySQL,Echarts数据统计,可本地运行

配套资料:源码 + 数据库 + 实验报告/论文 + 答辩 PPT+部署演示+远程调试+问题解答

🍅文末获取联系🍅

摘要

随着高校信息化建设的推进,传统纸质点名方式效率低、统计难、数据难以追溯。本文设计并实现了一套基于 B/S 架构的校园课堂考勤系统,采用 Vue 3 + Element Plus 构建前端界面,Spring Boot 3 + MyBatis-Plus 构建后端服务,MySQL 存储业务数据。系统面向管理员、教师、学生三类角色:管理员负责班级、教师、学生等基础数据维护及考勤监管;教师可发布课堂签到、管理考勤明细、导出缺勤统计;学生可在签到时段内主动签到并查看个人考勤记录。系统引入「待签到—出勤—缺勤—补签」状态流转机制,超时未签到自动记为缺勤,兼顾公平性与自动化。经功能测试,系统运行稳定,满足课堂考勤管理需求。

技术栈: Spring Boot 3 + MyBatis-Plus + MySQL + Vue 3 + Element Plus + ECharts

数据库表:6张

技术范围:SpringBoot、Vue、数据可视化、小程序、HLMT、Nodejs、uni-app、MySQL数据库、ElementUi等设计与开发。

适用范围:软件工程、软件技术、数据库课程设计、计算机科学与技术、数据库系统原理、JavaWeb开发、JavaEE、Java、Web应用开发、动态网页设计的课程设计、课设、大作业、课程实验、期末作业

实验报告参考内容

实验报告可供大家参考使用

用户端功能

教师端功能

管理员端功能

数据库设计

系统数据库设计为:

admins(管理员表)

表说明:存储系统后台管理员账号,用于 Web 管理端登录与全局数据维护。

字段名

数据类型

约束

默认值

说明

id

BIGINT(20)

PRIMARY KEY, AUTO_INCREMENT

主键,自增

username

VARCHAR(50)

NOT NULL, UNIQUE

登录用户名,全局唯一

password

VARCHAR(100)

NOT NULL

BCrypt 加密后的密码哈希

real_name

VARCHAR(50)

NULL

NULL

管理员真实姓名

phone

VARCHAR(20)

NULL

NULL

联系电话

enabled

TINYINT(4)

NOT NULL

1

账号启用状态:1=启用,0=停用

created_at

DATETIME

NULL

NULL

账号创建时间

索引:PRIMARY KEY(id),UNIQUE INDEX username(username)。

设计说明:管理员不开放自助注册,通常由初始化脚本预置。

teachers(教师表)

表说明:存储授课教师账号,用于教师端登录、创建签到场次及考勤管理。

字段名

数据类型

约束

默认值

说明

id

BIGINT(20)

PRIMARY KEY, AUTO_INCREMENT

主键,自增

username

VARCHAR(50)

NOT NULL, UNIQUE

登录用户名,全局唯一

password

VARCHAR(100)

NOT NULL

BCrypt 加密后的密码哈希

real_name

VARCHAR(50)

NULL

NULL

教师真实姓名

phone

VARCHAR(20)

NULL

NULL

联系电话

employee_no

VARCHAR(30)

NULL

NULL

教师工号

enabled

TINYINT(4)

NOT NULL

1

账号启用状态:1=启用,0=停用

created_at

DATETIME

NULL

NULL

账号创建时间

索引:PRIMARY KEY(id),UNIQUE INDEX username(username)。

classes(班级表)

表说明:存储教学班级基础信息,作为学生归属与签到场次的目标单位。

字段名

数据类型

约束

默认值

说明

id

BIGINT(20)

PRIMARY KEY, AUTO_INCREMENT

主键,自增

class_code

VARCHAR(30)

NOT NULL, UNIQUE

班级编码,如 CS2024-1

name

VARCHAR(100)

NOT NULL

班级名称,如 计算机2024级1班

grade

VARCHAR(30)

NULL

NULL

年级,如 2024

major

VARCHAR(100)

NULL

NULL

专业名称

teacher_id

BIGINT(20)

NULL, FK→teachers.id

NULL

负责教师 ID

created_at

DATETIME

NULL

NULL

创建时间

索引:PRIMARY KEY(id),UNIQUE INDEX class_code(class_code),INDEX teacher_id(teacher_id)。

外键:teacher_id 引用 teachers(id),ON DELETE RESTRICT,ON UPDATE RESTRICT。

删除班级前须确保无关联学生及签到场次。

students(学生表)

表说明:存储学生账号,小程序端登录主体,关联所属班级。

字段名

数据类型

约束

默认值

说明

id

BIGINT(20)

PRIMARY KEY, AUTO_INCREMENT

主键,自增

username

VARCHAR(50)

NOT NULL, UNIQUE

登录用户名,全局唯一

password

VARCHAR(100)

NOT NULL

BCrypt 加密后的密码哈希

real_name

VARCHAR(50)

NULL

NULL

学生真实姓名

phone

VARCHAR(20)

NULL

NULL

手机号码

student_no

VARCHAR(30)

NULL

NULL

学号

class_id

BIGINT(20)

NULL, FK→classes.id

NULL

所属班级 ID

enabled

TINYINT(4)

NOT NULL

1

账号启用状态:1=启用,0=停用

created_at

DATETIME

NULL

NULL

注册/创建时间

索引:PRIMARY KEY(id),UNIQUE INDEX username(username),INDEX class_id(class_id)。

外键:class_id 引用 classes(id),ON DELETE RESTRICT,ON UPDATE RESTRICT。

学生可通过小程序注册接口自助注册,class_id 可由管理员后续分配。

attendance_sessions(签到场次表)

表说明:存储教师发起的课堂签到任务,定义签到时间窗口与目标班级。

字段名

数据类型

约束

默认值

说明

id

BIGINT(20)

PRIMARY KEY, AUTO_INCREMENT

主键,自增

title

VARCHAR(200)

NOT NULL

签到标题,如 Java程序设计 第1次签到

class_id

BIGINT(20)

NOT NULL, FK→classes.id

目标班级 ID

teacher_id

BIGINT(20)

NOT NULL, FK→teachers.id

发起教师 ID

session_date

DATE

NOT NULL

签到日期

start_time

TIME

NULL

NULL

签到开始时间

end_time

TIME

NULL

NULL

签到截止时间

status

VARCHAR(20)

NOT NULL

OPEN

场次状态:OPEN=进行中,CLOSED=已结束

created_at

DATETIME

NULL

NULL

场次创建时间

索引:PRIMARY KEY(id),INDEX class_id(class_id),INDEX teacher_id(teacher_id)。

外键:class_id 引用 classes(id),teacher_id 引用 teachers(id),均为 RESTRICT。

教师创建场次时 status 设为 OPEN,并为班级全体学生批量生成 PENDING 考勤记录。

超过 end_time 或教师手动结束签到后,status 更新为 CLOSED,待签到学生自动记缺勤。

attendance_records(考勤明细表)

表说明:存储学生在各签到场次中的考勤状态,是签到业务的核心明细表。

字段名

数据类型

约束

默认值

说明

id

BIGINT(20)

PRIMARY KEY, AUTO_INCREMENT

主键,自增

session_id

BIGINT(20)

NOT NULL, FK→attendance_sessions.id

所属签到场次 ID

student_id

BIGINT(20)

NOT NULL, FK→students.id

学生 ID

status

VARCHAR(20)

NOT NULL

ABSENT

考勤状态,见第四节枚举说明

sign_time

DATETIME

NULL

NULL

签到登记时间,未签到时为 NULL

remark

VARCHAR(200)

NULL

NULL

备注,补签或手动调整时填写

created_at

DATETIME

NULL

NULL

记录创建时间

索引:PRIMARY KEY(id),UNIQUE INDEX uk_session_student(session_id, student_id),INDEX student_id(student_id)。

外键:session_id 引用 attendance_sessions(id),student_id 引用 students(id),均为 RESTRICT。

唯一约束 uk_session_student 保证一名学生在同一场次中只有一条考勤记录。

业务创建时 status 初始值为 PENDING(待签到),而非数据库默认值 ABSENT。

系统架构

Controller及Service层核心代码写法:

@RestController
@RequiredArgsConstructor
public class AttendanceController {

    private final ClazzService clazzService;
    private final AttendanceService attendanceService;

    // 班级分页列表接口
    @GetMapping("/api/classes")
    @RequireRole(UserRole.ADMIN)
    public ApiResponse<PageResult<Clazz>> listClasses(
            @RequestParam(required = false) String keyword,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        return ApiResponse.ok(clazzService.list(keyword, page, size));
    }

    // 班级下拉选项接口
    @GetMapping("/api/classes/options")
    @RequireRole({UserRole.ADMIN, UserRole.TEACHER})
    public ApiResponse<List<Clazz>> classOptions() {
        return ApiResponse.ok(clazzService.options());
    }

    // 新增班级接口
    @PostMapping("/api/classes")
    @RequireRole(UserRole.ADMIN)
    public ApiResponse<Clazz> createClass(@Valid @RequestBody ClazzDTO dto) {
        return ApiResponse.ok("创建成功", clazzService.create(dto));
    }

    // 编辑班级接口
    @PutMapping("/api/classes/{id}")
    @RequireRole(UserRole.ADMIN)
    public ApiResponse<Clazz> updateClass(@PathVariable Long id, @Valid @RequestBody ClazzDTO dto) {
        return ApiResponse.ok("更新成功", clazzService.update(id, dto));
    }

    // 删除班级接口
    @DeleteMapping("/api/classes/{id}")
    @RequireRole(UserRole.ADMIN)
    public ApiResponse<Void> deleteClass(@PathVariable Long id) {
        clazzService.delete(id);
        return ApiResponse.ok("删除成功", null);
    }

    // 签到场次分页列表接口
    @GetMapping("/api/attendance/sessions")
    @RequireRole({UserRole.ADMIN, UserRole.TEACHER})
    public ApiResponse<PageResult<AttendanceSession>> listSessions(
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) Long class_id,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        return ApiResponse.ok(attendanceService.listSessions(keyword, class_id, page, size));
    }

    // 签到场次详情接口
    @GetMapping("/api/attendance/sessions/{id}")
    @RequireRole({UserRole.ADMIN, UserRole.TEACHER})
    public ApiResponse<AttendanceSession> getSession(@PathVariable Long id) {
        return ApiResponse.ok(attendanceService.getSession(id));
    }

    // 创建课堂签到接口
    @PostMapping("/api/attendance/sessions")
    @RequireRole(UserRole.TEACHER)
    public ApiResponse<AttendanceSession> createSession(@Valid @RequestBody AttendanceSessionDTO dto) {
        return ApiResponse.ok("创建成功", attendanceService.createSession(dto));
    }

    // 结束签到接口
    @PutMapping("/api/attendance/sessions/{id}/close")
    @RequireRole({UserRole.ADMIN, UserRole.TEACHER})
    public ApiResponse<AttendanceSession> closeSession(@PathVariable Long id) {
        return ApiResponse.ok("已结束签到", attendanceService.closeSession(id));
    }

    // 场次考勤明细分页接口
    @GetMapping("/api/attendance/records")
    @RequireRole({UserRole.ADMIN, UserRole.TEACHER})
    public ApiResponse<PageResult<AttendanceRecord>> listRecords(
            @RequestParam Long session_id,
            @RequestParam(required = false) String status,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "100") int size) {
        return ApiResponse.ok(attendanceService.listRecords(session_id, status, page, size));
    }

    // 更新考勤状态接口(出勤/迟到/缺勤)
    @PutMapping("/api/attendance/records/{id}")
    @RequireRole({UserRole.ADMIN, UserRole.TEACHER})
    public ApiResponse<AttendanceRecord> updateRecord(
            @PathVariable Long id, @Valid @RequestBody AttendanceRecordDTO dto) {
        return ApiResponse.ok("更新成功", attendanceService.updateRecord(id, dto));
    }

    // 手动补签接口
    @PostMapping("/api/attendance/records/{id}/makeup")
    @RequireRole({UserRole.ADMIN, UserRole.TEACHER})
    public ApiResponse<AttendanceRecord> makeup(
            @PathVariable Long id, @RequestParam(required = false) String remark) {
        return ApiResponse.ok("补签成功", attendanceService.makeup(id, remark));
    }

    // 学生待签到场次列表接口
    @GetMapping("/api/attendance/open")
    @RequireRole(UserRole.STUDENT)
    public ApiResponse<List<AttendanceRecord>> openSessions() {
        return ApiResponse.ok(attendanceService.listOpenForStudent());
    }

    // 学生主动签到接口
    @PostMapping("/api/attendance/check-in")
    @RequireRole(UserRole.STUDENT)
    public ApiResponse<AttendanceRecord> checkIn(@RequestParam Long session_id) {
        return ApiResponse.ok("签到成功", attendanceService.checkIn(session_id));
    }

    // 学生个人考勤明细接口
    @GetMapping("/api/attendance/my")
    @RequireRole(UserRole.STUDENT)
    public ApiResponse<PageResult<AttendanceRecord>> myRecords(
            @RequestParam(required = false) String status,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        return ApiResponse.ok(attendanceService.myRecords(status, page, size));
    }

    // 导出缺勤统计表接口
    @GetMapping("/api/attendance/export")
    @RequireRole({UserRole.ADMIN, UserRole.TEACHER})
    public ResponseEntity<byte[]> export(
            @RequestParam(required = false) Long class_id,
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate start_date,
            @RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate end_date) {
        byte[] data = attendanceService.exportAbsenceStats(class_id, start_date, end_date);
        String filename = "absence_stats_" + LocalDate.now() + ".csv";
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + filename + "\"")
                .contentType(new MediaType("text", "csv", StandardCharsets.UTF_8))
                .body(data);
    }
}

博主本身从事软件开发、有丰富的编程能力和水平,积给上千名同学进行辅导,论文纯手写查重低于10%,全都顺利通过答辩!
 

擅长功能设计、开题报告、任务书、中期检查PPT、系统功能实现、代码编写、论文编写和辅导、论文降重、长期答辩答疑辅导、腾讯会议一对一专业讲解辅导答辩、模拟答辩演练、和理解代码逻辑思路等。

更多个人原创作品👇🏻

原创课程设计大全✅

原创毕业设计集合✅

获取联系

项目功能完整,可在本地运行,并可远程调试,确保运行顺利!

查看👇🏻👇🏻获取联系方式👇🏻👇🏻

更多推荐