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

🍅文末获取联系🍅

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

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

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

学生选课管理系统摘要

随着高校信息化建设的推进,传统人工或分散的选课方式已难以满足师生对效率与准确性的要求。本文设计并实现了一套基于 B/S 架构的高校选课管理系统,采用 Spring Boot 3 + MyBatis-Plus 构建后端服务,Vue 3 + Element Plus 构建前端界面,MySQL 作为数据存储。系统面向管理员、教师、在校生三类角色,实现了课程维护、开课审核、选课退课、个人课表、公告发布及数据统计等功能。管理员、教师、学生分表存储,保证角色数据独立;后端按 JWT 鉴权与角色权限控制接口访问;选课模块支持人数上限校验与时间冲突检测。经功能测试,系统运行稳定,满足课程设计预期目标。

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

数据库表:6张

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

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

实验报告参考内容

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

功能需求

(1)认证模块:支持管理员、教师、学生分身份登录;学生可注册;登录后 JWT 令牌维持会话;支持个人资料修改与密码变更。

(2)课程模块:管理员可直接创建开放课程;教师提交开课申请后由管理员审核;课程包含编号、名称、学分、教室、上课时间(星期与节次)、选课上限等字段。

(3)选课模块:学生仅可选状态为 OPEN 的课程;校验是否重复选课、是否超过人数上限、是否与已选课程时间冲突;支持退课并回退已选人数。

(4)用户管理:管理员对教师表、学生表分别进行增删改查及启用/禁用操作。

(5)公告模块:管理员发布/编辑/下架公告;教师与学生可浏览已发布公告。

(6)统计模块:工作台展示课程、用户、选课等汇总数据及漏斗图、环形图、折线图、雷达图。

角色

主要职责

管理员

维护课程信息、限制选课人数、审核开课申请、管理教师与学生、发布公告

教师

提交授课课程申请、查看我的课程、查看选课名单、浏览系统公告

在校生

浏览可选课程、选课/退课、查看个人课表、浏览系统公告、注册账号

学生端功能

教师端功能

管理员端功能

数据库设计

系统数据库设计为:

数据库名称为 0vuez_xuanke。三类用户采用独立表 admins、teachers、students。主要数据表如下:

表名

说明

主要字段

admins

管理员

username, password, real_name, enabled

teachers

教师

username, password, employee_no, enabled

students

学生

username, password, student_no, enabled

announcements

公告

title, content, admin_id, status

courses

课程

course_code, name, teacher_id, max_students, status

enrollments

选课记录

student_id, course_id, status

1  admins(管理员表)

admins 字段结构

序号

字段名

数据类型

允许空

默认值

约束/索引

字段说明

1

id

bigint(20)

自增

PRIMARY KEY

主键,管理员唯一标识

2

username

varchar(50)

UNIQUE

登录用户名,全局唯一

3

password

varchar(100)

BCrypt 加密后的登录密码

4

real_name

varchar(50)

NULL

管理员真实姓名

5

phone

varchar(20)

NULL

联系电话

6

enabled

tinyint(4)

1

账号启用状态:1 启用,0 禁用

7

created_at

datetime

NULL

账号创建时间

表 4.1  索引设计

索引名

索引类型

字段

说明

PRIMARY

主键

id

聚簇索引

username

唯一索引

username

登录名不重复

4.2  teachers(教师表)

teachers 字段结构

序号

字段名

数据类型

允许空

默认值

约束/索引

字段说明

1

id

bigint(20)

自增

PRIMARY KEY

主键,教师唯一标识

2

username

varchar(50)

UNIQUE

登录用户名,全局唯一

3

password

varchar(100)

BCrypt 加密后的登录密码

4

real_name

varchar(50)

NULL

教师姓名,用于课程展示

5

phone

varchar(20)

NULL

联系电话

6

employee_no

varchar(30)

NULL

工号

7

enabled

tinyint(4)

1

账号启用状态:1 启用,0 禁用

8

created_at

datetime

NULL

账号创建时间

表 4.2  索引设计

索引名

索引类型

字段

说明

PRIMARY

主键

id

聚簇索引

username

唯一索引

username

登录名不重复

4.3  students(学生表)

students 字段结构

序号

字段名

数据类型

允许空

默认值

约束/索引

字段说明

1

id

bigint(20)

自增

PRIMARY KEY

主键,学生唯一标识

2

username

varchar(50)

UNIQUE

登录用户名,全局唯一

3

password

varchar(100)

BCrypt 加密后的登录密码

4

real_name

varchar(50)

NULL

学生姓名

5

phone

varchar(20)

NULL

手机号码

6

student_no

varchar(30)

NULL

学号,注册时填写

7

enabled

tinyint(4)

1

账号启用状态:1 启用,0 禁用

8

created_at

datetime

NULL

账号创建时间

表 4.3  索引设计

索引名

索引类型

字段

说明

PRIMARY

主键

id

聚簇索引

username

唯一索引

username

登录名不重复

4.4  courses(课程表)

courses 字段结构

序号

字段名

数据类型

允许空

默认值

约束/索引

字段说明

1

id

bigint(20)

自增

PRIMARY KEY

主键,课程唯一标识

2

course_code

varchar(30)

UNIQUE

课程编号,如 CS101

3

name

varchar(200)

课程名称

4

credit

decimal(3,1)

0.0

学分,如 3.0

5

classroom

varchar(100)

NULL

上课教室

6

week_day

int(11)

星期几:1-7 表示周一至周日

7

start_section

int(11)

开始节次

8

end_section

int(11)

结束节次

9

teacher_id

bigint(20)

NULL

INDEX, FK

授课教师 ID,关联 teachers.id

10

max_students

int(11)

50

选课人数上限

11

enrolled_count

int(11)

0

当前已选人数,选课时递增、退课时递减

12

semester

varchar(30)

2024-2025-1

开课学期

13

status

varchar(20)

PENDING

课程状态:PENDING 待审、OPEN 开放、REJECTED 驳回

14

reject_reason

varchar(200)

NULL

管理员驳回开课申请时的原因

15

description

varchar(500)

NULL

课程简介

16

created_at

datetime

NULL

记录创建时间

17

updated_at

datetime

NULL

记录最后更新时间

表 4.4  索引设计

索引名

索引类型

字段

说明

PRIMARY

主键

id

聚簇索引

course_code

唯一索引

course_code

课程编号不重复

teacher_id

普通索引

teacher_id

按教师查课程

表 4.4  外键约束

外键名

本表字段

引用表

引用字段

删除规则

更新规则

courses_ibfk_1

teacher_id

teachers

id

RESTRICT

RESTRICT

4.5  enrollments(选课记录表)

enrollments 字段结构

序号

字段名

数据类型

允许空

默认值

约束/索引

字段说明

1

id

bigint(20)

自增

PRIMARY KEY

主键,选课记录唯一标识

2

student_id

bigint(20)

INDEX, FK

学生 ID,关联 students.id

3

course_id

bigint(20)

INDEX, FK

课程 ID,关联 courses.id

4

status

varchar(20)

ENROLLED

选课状态:ENROLLED 已选(有效记录)

5

created_at

datetime

NULL

选课时间

表 4.5  索引设计

索引名

索引类型

字段

说明

PRIMARY

主键

id

聚簇索引

uk_student_course

唯一索引

student_id, course_id

同一学生不能重复选同一门课

course_id

普通索引

course_id

按课程查选课名单

表 4.5  外键约束

外键名

本表字段

引用表

引用字段

删除规则

更新规则

enrollments_ibfk_1

student_id

students

id

RESTRICT

RESTRICT

enrollments_ibfk_2

course_id

courses

id

RESTRICT

RESTRICT

4.6  announcements(系统公告表)

announcements 字段结构

序号

字段名

数据类型

允许空

默认值

约束/索引

字段说明

1

id

bigint(20)

自增

PRIMARY KEY

主键,公告唯一标识

2

title

varchar(200)

公告标题

3

content

text

公告正文内容

4

admin_id

bigint(20)

NULL

INDEX, FK

发布管理员 ID,关联 admins.id

5

status

tinyint(4)

1

发布状态:1 已发布,0 已下架

6

created_at

datetime

NULL

发布时间

7

updated_at

datetime

NULL

最后更新时间

表 4.6  索引设计

索引名

索引类型

字段

说明

PRIMARY

主键

id

聚簇索引

admin_id

普通索引

admin_id

按管理员查公告

表 4.6  外键约束

外键名

本表字段

引用表

引用字段

删除规则

更新规则

announcements_ibfk_1

admin_id

admins

id

RESTRICT

RESTRICT

系统架构

Controller及Service层核心代码写法:

 // 选课记录分页列表接口
    @GetMapping
    @RequireRole({UserRole.ADMIN, UserRole.TEACHER, UserRole.STUDENT})
    public ApiResponse<PageResult<Enrollment>> list(
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false) Long course_id,
            @RequestParam(defaultValue = "1") int page,
            @RequestParam(defaultValue = "10") int size) {
        return ApiResponse.ok(enrollmentService.list(keyword, course_id, page, size));
    }

    // 学生个人课表接口
    @GetMapping("/timetable")
    @RequireRole(UserRole.STUDENT)
    public ApiResponse<List<Map<String, Object>>> timetable() {
        return ApiResponse.ok(enrollmentService.timetable());
    }

    // 选课接口
    @PostMapping
    @RequireRole(UserRole.STUDENT)
    public ApiResponse<Enrollment> enroll(@Valid @RequestBody EnrollmentDTO dto) {
        return ApiResponse.ok("选课成功", enrollmentService.enroll(dto));
    }

    // 退课接口
    @PutMapping("/{id}/drop")
    @RequireRole({UserRole.STUDENT, UserRole.ADMIN})
    public ApiResponse<Void> drop(@PathVariable Long id) {
        enrollmentService.drop(id);
        return ApiResponse.ok("退课成功", null);
    }
@Service
@RequiredArgsConstructor
public class EnrollmentService extends ServiceImpl<EnrollmentMapper, Enrollment> {

    private final CourseMapper courseMapper;
    private final StudentMapper studentMapper;
    private final RelationFillHelper relationFillHelper;

    public PageResult<Enrollment> list(String keyword, Long course_id, int page, int size) {
        var query = lambdaQuery().eq(Enrollment::getStatus, "ENROLLED");

        if (AuthContext.isStudent()) {
            query.eq(Enrollment::getStudent_id, AuthContext.getUserId());
        } else if (AuthContext.isTeacher()) {
            List<Long> course_ids = courseMapper.selectList(
                    Wrappers.<Course>lambdaQuery().eq(Course::getTeacher_id, AuthContext.getUserId()))
                    .stream().map(Course::getId).toList();
            if (course_ids.isEmpty()) {
                return new PageResult<>(List.of(), 0, page, size);
            }
            if (course_id != null) {
                if (!course_ids.contains(course_id)) {
                    throw new RuntimeException("无权查看该课程选课名单");
                }
                query.eq(Enrollment::getCourse_id, course_id);
            } else {
                query.in(Enrollment::getCourse_id, course_ids);
            }
        } else if (course_id != null) {
            query.eq(Enrollment::getCourse_id, course_id);
        }

        if (StringUtils.hasText(keyword)) {
            List<Long> student_ids = studentMapper.selectList(
                            Wrappers.<Student>lambdaQuery()
                                    .like(Student::getUsername, keyword)
                                    .or().like(Student::getReal_name, keyword)
                                    .or().like(Student::getStudent_no, keyword))
                    .stream().map(Student::getId).toList();
            if (student_ids.isEmpty()) {
                return new PageResult<>(List.of(), 0, page, size);
            }
            query.in(Enrollment::getStudent_id, student_ids);
        }

        Page<Enrollment> result = query.orderByDesc(Enrollment::getId).page(new Page<>(page, size));
        relationFillHelper.fillEnrollments(result.getRecords());
        return PageResult.of(result);
    }

    @Transactional
    public Enrollment enroll(EnrollmentDTO dto) {
        if (!AuthContext.isStudent()) {
            throw new RuntimeException("仅学生可以选课");
        }
        Long student_id = AuthContext.getUserId();
        Course course = courseMapper.selectById(dto.getCourse_id());
        if (course == null) {
            throw new RuntimeException("课程不存在");
        }
        if (!"OPEN".equals(course.getStatus())) {
            throw new RuntimeException("该课程未开放选课");
        }
        if (course.getEnrolled_count() >= course.getMax_students()) {
            throw new RuntimeException("选课人数已满");
        }
        Enrollment existed = lambdaQuery()
                .eq(Enrollment::getStudent_id, student_id)
                .eq(Enrollment::getCourse_id, course.getId())
                .one();
        if (existed != null) {
            if ("ENROLLED".equals(existed.getStatus())) {
                throw new RuntimeException("您已选过该课程");
            }
            removeById(existed.getId());
        }
        checkScheduleConflict(student_id, course);

        Enrollment enrollment = new Enrollment();
        enrollment.setStudent_id(student_id);
        enrollment.setCourse_id(course.getId());
        enrollment.setStatus("ENROLLED");
        save(enrollment);

        course.setEnrolled_count(course.getEnrolled_count() + 1);
        courseMapper.updateById(course);

        relationFillHelper.fillEnrollment(enrollment);
        return enrollment;
    }

    @Transactional
    public void drop(Long id) {
        Enrollment enrollment = getById(id);
        if (enrollment == null) {
            throw new RuntimeException("选课记录不存在");
        }
        if (!AuthContext.isStudent() && !AuthContext.isAdmin()) {
            throw new RuntimeException("无权退课");
        }
        if (AuthContext.isStudent() && !AuthContext.getUserId().equals(enrollment.getStudent_id())) {
            throw new RuntimeException("只能退选自己的课程");
        }
        if (!"ENROLLED".equals(enrollment.getStatus())) {
            throw new RuntimeException("该记录已退课");
        }

        Course course = courseMapper.selectById(enrollment.getCourse_id());
        if (course != null && course.getEnrolled_count() > 0) {
            course.setEnrolled_count(course.getEnrolled_count() - 1);
            courseMapper.updateById(course);
        }

        removeById(id);
    }

    public List<Map<String, Object>> timetable() {
        if (!AuthContext.isStudent()) {
            throw new RuntimeException("仅学生可查看个人课表");
        }
        List<Enrollment> list = lambdaQuery()
                .eq(Enrollment::getStudent_id, AuthContext.getUserId())
                .eq(Enrollment::getStatus, "ENROLLED")
                .list();
        relationFillHelper.fillEnrollments(list);
        List<Map<String, Object>> items = new ArrayList<>();
        for (Enrollment e : list) {
            Course c = e.getCourse();
            if (c == null) {
                continue;
            }
            Map<String, Object> item = new HashMap<>();
            item.put("course_id", c.getId());
            item.put("course_code", c.getCourse_code());
            item.put("name", c.getName());
            item.put("classroom", c.getClassroom());
            item.put("week_day", c.getWeek_day());
            item.put("start_section", c.getStart_section());
            item.put("end_section", c.getEnd_section());
            item.put("teacher_name", c.getTeacher() != null ? c.getTeacher().getReal_name() : null);
            item.put("credit", c.getCredit());
            items.add(item);
        }
        return items;
    }

    private void checkScheduleConflict(Long student_id, Course target) {
        List<Enrollment> enrolled = lambdaQuery()
                .eq(Enrollment::getStudent_id, student_id)
                .eq(Enrollment::getStatus, "ENROLLED")
                .list();
        for (Enrollment e : enrolled) {
            Course c = courseMapper.selectById(e.getCourse_id());
            if (c == null || !c.getWeek_day().equals(target.getWeek_day())) {
                continue;
            }
            if (target.getStart_section() <= c.getEnd_section() && target.getEnd_section() >= c.getStart_section()) {
                throw new RuntimeException("与已选课程「" + c.getName() + "」时间冲突");
            }
        }
    }
}

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

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

更多个人原创作品👇🏻

原创课程设计大全✅

原创毕业设计集合✅

获取联系

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

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

更多推荐