news 2026/8/22 16:50:40

基于SpringBoot的毕业设计选题系统:从需求到部署的全栈实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于SpringBoot的毕业设计选题系统:从需求到部署的全栈实战

每到毕业季,计算机相关专业的学生们都会面临一个共同的难题:如何高效、公平地完成毕业设计选题。传统的线下选题方式,如纸质表格、邮件沟通或简单的Excel共享,常常伴随着信息不同步、选题冲突、导师协调困难、进度难以追踪等一系列痛点。本文将分享一个基于Java和SpringBoot的完整毕业设计选题系统的设计与实现,从需求分析、技术选型、数据库设计到前后端代码实现,提供一套可直接部署、二次开发的实战项目方案。无论你是正在寻找毕业设计课题的学生,还是希望学习SpringBoot全栈开发的开发者,都能从本文中获得从零到一的完整构建思路和可运行的源码。

1. 系统需求分析与核心功能设计

在动手编码之前,明确系统的用户角色和核心功能是项目成功的关键。一个典型的毕业设计选题系统通常涉及三类用户:学生、教师(导师)和管理员。

1.1 用户角色与核心诉求

  • 学生
    • 核心诉求:浏览所有可选的课题、查看课题详情(包括导师信息、要求、已选人数)、选择自己心仪的课题、查看自己的选题状态、与导师进行简单的在线沟通(如留言)。
    • 痛点解决:避免信息不对称,实时查看课题名额,减少因沟通不畅导致的选题冲突。
  • 教师(导师)
    • 核心诉求:发布和管理自己的课题(增删改查)、审核选择自己课题的学生(通过/拒绝)、查看最终确定的学生名单、发布任务或通知。
    • 痛点解决:简化课题发布流程,集中管理学生申请,高效完成双向选择。
  • 管理员
    • 核心诉求:管理所有用户(学生、教师)账户、审核教师发布的课题(确保合规性)、监控整个选题流程的进度(如各阶段时间设置)、处理系统异常(如重置密码)。
    • 痛点解决:确保系统运行的秩序和公平性,拥有最高权限进行全局调控。

1.2 系统核心功能模块

基于以上角色分析,我们可以将系统拆解为以下几个核心功能模块:

  1. 用户认证与权限管理模块:实现用户登录、注册(通常由管理员初始化或导入)、基于角色(ROLE_STUDENT, ROLE_TEACHER, ROLE_ADMIN)的页面访问和操作权限控制。
  2. 课题信息管理模块
    • 教师端:课题的创建、编辑、删除、发布与下架。
    • 学生端:课题列表分页展示、多条件(如导师、课题名称、状态)检索、课题详情查看。
    • 管理员端:课题审核(审核通过后方可对学生可见)。
  3. 选题流程管理模块
    • 学生端:提交选题申请、查看申请状态(待审核、已通过、被拒绝)、在允许的时间内取消申请或改选。
    • 教师端:查看申请自己课题的学生列表、审核申请(通过或拒绝,需有理由可选)。
    • 系统逻辑:确保一个学生同一时间只能有一个“待审核”或“已通过”的选题;确保课题人数不超过上限。
  4. 通知与简单沟通模块:系统站内消息通知,如选题结果通知、审核通知等。可扩展为简单的留言板功能。
  5. 后台管理模块:管理员专属功能,包括用户管理、系统公告发布、选题阶段时间节点配置(如开始时间、截止时间)、数据统计报表等。

2. 技术选型与环境准备

一个稳定、高效、易于开发和维护的技术栈是项目的基石。我们选择目前Java领域最主流的SpringBoot全家桶进行后端开发,配合成熟的前后端分离方案。

2.1 后端技术栈

  • 核心框架:Spring Boot 2.7.x。它提供了极简的配置和快速启动能力,是构建现代Java Web应用的标准选择。
  • 持久层框架:MyBatis-Plus 3.5.x。它在MyBatis的基础上进行了增强,提供了强大的CRUD操作和条件构造器,能极大减少SQL编写工作量。
  • 安全框架:Spring Security 5.7.x。用于处理用户认证(登录)和授权(权限检查),是保护系统安全的必备组件。
  • 数据库:MySQL 8.0。关系型数据库,用于存储用户、课题、选题记录等结构化数据。
  • API文档:Knife4j 3.0.x。基于Swagger的增强UI,用于自动生成和调试后端RESTful API接口文档。
  • 项目构建:Maven 3.6+ 或 Gradle。本文示例使用Maven。
  • 其他工具:Lombok(简化Java Bean编写)、Hutool(Java工具类库)、Fastjson(JSON处理)。

2.2 前端技术栈(可选方案)

为了快速成型和降低前端复杂度,可以选择以下两种方案之一:

  • 方案A(前后端不分离):使用Thymeleaf模板引擎。SpringBoot原生支持,适合全栈Java开发者,开发速度快,但前后端耦合。
  • 方案B(前后端分离):使用Vue.js + Element UI。这是目前主流选择,前后端职责清晰,用户体验好。后端仅提供JSON API接口。 考虑到毕业设计项目通常需要展示完整流程,且为了聚焦后端核心逻辑,本文后续代码讲解将侧重于后端API的实现,并假定前端采用Vue+Element UI方案进行交互。前端代码会提供关键页面逻辑说明。

2.3 开发环境准备

  1. JDK:安装JDK 8或JDK 11,并配置好JAVA_HOME环境变量。
  2. IDE:IntelliJ IDEA(推荐)或Eclipse。
  3. MySQL:安装MySQL 8.0,并创建一个名为graduation_topic的数据库,字符集建议为utf8mb4
    CREATE DATABASE IF NOT EXISTS `graduation_topic` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
  4. Maven:安装Maven并配置好仓库镜像(可使用阿里云镜像加速)。
  5. 浏览器:用于测试前端(如Chrome)和API(可使用Postman或Knife4j界面)。

3. 数据库设计与核心表结构

良好的数据库设计是系统稳定运行的基础。以下是核心表结构设计,使用MyBatis-Plus时,对应的实体类将基于此设计。

3.1 用户表 (sys_user)

存储所有系统用户(学生、教师、管理员)的基础信息。

CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `username` varchar(50) NOT NULL COMMENT '用户名(学号/工号)', `password` varchar(100) NOT NULL COMMENT '加密后的密码', `real_name` varchar(20) DEFAULT NULL COMMENT '真实姓名', `role` varchar(20) NOT NULL COMMENT '角色:STUDENT, TEACHER, ADMIN', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `phone` varchar(20) DEFAULT NULL COMMENT '电话', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `is_deleted` tinyint DEFAULT '0' COMMENT '逻辑删除标志(0未删,1已删)', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统用户表';

3.2 课题表 (topic)

存储教师发布的课题信息。

CREATE TABLE `topic` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '课题ID', `teacher_id` bigint NOT NULL COMMENT '发布教师ID(关联sys_user.id)', `title` varchar(200) NOT NULL COMMENT '课题标题', `description` text COMMENT '课题详细描述和要求', `max_selected` int DEFAULT '1' COMMENT '最大可选人数', `current_selected` int DEFAULT '0' COMMENT '当前已选人数', `status` varchar(20) DEFAULT 'PENDING_REVIEW' COMMENT '状态:PENDING_REVIEW(待审核), APPROVED(已审核通过), REJECTED(被驳回), CLOSED(已关闭)', `review_comment` varchar(500) DEFAULT NULL COMMENT '管理员审核意见', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_teacher_id` (`teacher_id`), KEY `idx_status` (`status`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='课题表';

3.3 选题记录表 (selection_record)

记录学生的选题申请和教师的审核结果。

CREATE TABLE `selection_record` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '记录ID', `student_id` bigint NOT NULL COMMENT '学生ID(关联sys_user.id)', `topic_id` bigint NOT NULL COMMENT '课题ID(关联topic.id)', `status` varchar(20) DEFAULT 'PENDING' COMMENT '状态:PENDING(待审核), APPROVED(已通过), REJECTED(被拒绝)', `apply_comment` varchar(500) DEFAULT NULL COMMENT '学生申请理由', `review_comment` varchar(500) DEFAULT NULL COMMENT '教师审核意见', `apply_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '申请时间', `review_time` datetime DEFAULT NULL COMMENT '审核时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_student_current` (`student_id`, `status`) COMMENT '一个学生只能有一个进行中的选题', KEY `idx_topic_id` (`topic_id`), KEY `idx_student_id` (`student_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='选题记录表';

注意uk_student_current唯一索引配合业务逻辑,用于保证学生不能同时有多个“PENDING”或“APPROVED”状态的记录。更复杂的约束(如检查status值)可能需要在应用层代码中实现。

4. SpringBoot后端核心代码实现

接下来,我们搭建SpringBoot项目并实现核心业务逻辑。项目采用经典的分层架构:Controller -> Service -> Mapper。

4.1 项目初始化与依赖配置

使用Spring Initializr或IDE创建SpringBoot项目,主要依赖如下(pom.xml片段):

<dependencies> <!-- SpringBoot Web --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- MyBatis-Plus --> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.3.1</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency> <!-- Spring Security --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <!-- Lombok --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <!-- Knife4j API文档 --> <dependency> <groupId>com.github.xiaoymin</groupId> <artifactId>knife4j-spring-boot-starter</artifactId> <version>3.0.3</version> </dependency> <!-- Hutool工具包 --> <dependency> <groupId>cn.hutool</groupId> <artifactId>hutool-all</artifactId> <version>5.8.16</version> </dependency> </dependencies>

4.2 实体类与Mapper

使用MyBatis-Plus,实体类与Mapper的编写非常简洁。以Topic实体为例:

文件路径:src/main/java/com/graduation/topic/entity/Topic.java

package com.graduation.topic.entity; import com.baomidou.mybatisplus.annotation.*; import lombok.Data; import java.time.LocalDateTime; @Data @TableName("topic") public class Topic { @TableId(type = IdType.AUTO) private Long id; private Long teacherId; private String title; private String description; private Integer maxSelected; private Integer currentSelected; private String status; // 使用枚举更佳,此处简化为字符串 private String reviewComment; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }

文件路径:src/main/java/com/graduation/topic/mapper/TopicMapper.java

package com.graduation.topic.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.graduation.topic.entity.Topic; import org.apache.ibatis.annotations.Mapper; @Mapper public interface TopicMapper extends BaseMapper<Topic> { // 继承BaseMapper即拥有了基本的CRUD方法 }

4.3 Service层业务逻辑

Service层封装核心业务规则。以下是TopicService中发布课题和选择课题的关键方法。

文件路径:src/main/java/com/graduation/topic/service/TopicService.java

package com.graduation.topic.service; import com.baomidou.mybatisplus.extension.service.IService; import com.graduation.topic.entity.Topic; import com.graduation.topic.vo.TopicVO; public interface TopicService extends IService<Topic> { /** * 教师发布新课题 * @param topic 课题信息 * @param teacherId 教师ID * @return 是否成功 */ boolean publishTopic(Topic topic, Long teacherId); /** * 学生选择课题 * @param topicId 课题ID * @param studentId 学生ID * @param applyComment 申请理由 * @return 选择结果信息 */ String selectTopic(Long topicId, Long studentId, String applyComment); }

文件路径:src/main/java/com/graduation/topic/service/impl/TopicServiceImpl.java

package com.graduation.topic.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.graduation.topic.entity.SelectionRecord; import com.graduation.topic.entity.Topic; import com.graduation.topic.mapper.SelectionRecordMapper; import com.graduation.topic.mapper.TopicMapper; import com.graduation.topic.service.TopicService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; @Service public class TopicServiceImpl extends ServiceImpl<TopicMapper, Topic> implements TopicService { @Autowired private SelectionRecordMapper selectionRecordMapper; @Override public boolean publishTopic(Topic topic, Long teacherId) { // 1. 基础校验 if (topic.getMaxSelected() == null || topic.getMaxSelected() <= 0) { throw new RuntimeException("最大可选人数必须大于0"); } // 2. 设置初始状态和关联教师 topic.setTeacherId(teacherId); topic.setStatus("PENDING_REVIEW"); // 新课题需管理员审核 topic.setCurrentSelected(0); // 3. 保存到数据库 return this.save(topic); } @Override @Transactional(rollbackFor = Exception.class) // 开启事务,保证数据一致性 public String selectTopic(Long topicId, Long studentId, String applyComment) { // 1. 检查课题是否存在且状态为“已通过” Topic topic = this.getById(topicId); if (topic == null) { return "课题不存在"; } if (!"APPROVED".equals(topic.getStatus())) { return "该课题暂不可选"; } // 2. 检查课题是否已满额 if (topic.getCurrentSelected() >= topic.getMaxSelected()) { return "该课题人数已满"; } // 3. 检查该学生是否已有进行中的选题(PENDING或APPROVED) LambdaQueryWrapper<SelectionRecord> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(SelectionRecord::getStudentId, studentId) .in(SelectionRecord::getStatus, "PENDING", "APPROVED"); Long count = selectionRecordMapper.selectCount(wrapper); if (count > 0) { return "您已有一个正在进行中的选题,请先取消或等待审核结果"; } // 4. 创建选题记录 SelectionRecord record = new SelectionRecord(); record.setStudentId(studentId); record.setTopicId(topicId); record.setStatus("PENDING"); record.setApplyComment(applyComment); record.setApplyTime(LocalDateTime.now()); selectionRecordMapper.insert(record); // 注意:此时课题的 currentSelected 并未增加,需教师审核通过后再增加 return "申请提交成功,等待导师审核"; } }

4.4 Controller层API接口

Controller层接收前端请求,调用Service,并返回统一格式的JSON数据。这里使用一个简单的Result类包装返回结果。

文件路径:src/main/java/com/graduation/topic/controller/TopicController.java

package com.graduation.topic.controller; import com.graduation.topic.common.Result; import com.graduation.topic.entity.Topic; import com.graduation.topic.service.TopicService; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/api/topic") @Api(tags = "课题管理接口") public class TopicController { @Autowired private TopicService topicService; @GetMapping("/list") @ApiOperation("分页查询课题列表(学生/教师查看)") public Result<List<Topic>> listTopics(@RequestParam(required = false) String keyword, @RequestParam(defaultValue = "APPROVED") String status) { // 构建查询条件,这里简化处理,实际应使用Page对象分页 List<Topic> list = topicService.lambdaQuery() .like(Topic::getTitle, keyword) .eq(Topic::getStatus, status) .orderByDesc(Topic::getCreateTime) .list(); return Result.success(list); } @PostMapping("/publish") @ApiOperation("教师发布课题") public Result<String> publishTopic(@RequestBody Topic topic) { // 从Spring Security上下文中获取当前登录用户ID(假设已存储) // 实际项目中需要更完善的用户信息获取方式 Long teacherId = getCurrentUserId(); boolean success = topicService.publishTopic(topic, teacherId); if (success) { return Result.success("课题发布成功,等待管理员审核"); } else { return Result.error("课题发布失败"); } } @PostMapping("/select/{topicId}") @ApiOperation("学生选择课题") public Result<String> selectTopic(@PathVariable Long topicId, @RequestParam String applyComment) { Long studentId = getCurrentUserId(); String resultMsg = topicService.selectTopic(topicId, studentId, applyComment); if (resultMsg.contains("成功")) { return Result.success(resultMsg); } else { return Result.error(resultMsg); } } // 获取当前登录用户ID的辅助方法(需结合Spring Security实现) private Long getCurrentUserId() { // 示例:从SecurityContext中获取用户名,再查询数据库获取ID // 实际项目可将UserDetails扩展,直接存入ID String username = SecurityContextHolder.getContext().getAuthentication().getName(); // 模拟返回ID 1,实际应查询数据库 return 1L; } }

4.5 Spring Security 安全配置

安全配置是系统的门户,负责登录认证和接口权限控制。

文件路径:src/main/java/com/graduation/topic/config/SecurityConfig.java

package com.graduation.topic.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { @Bean public PasswordEncoder passwordEncoder() { // 使用BCrypt强哈希加密密码 return new BCryptPasswordEncoder(); } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .authorizeHttpRequests(auth -> auth // 允许静态资源和登录接口匿名访问 .antMatchers("/css/**", "/js/**", "/images/**", "/webjars/**").permitAll() .antMatchers("/api/auth/**", "/doc.html", "/swagger-resources/**", "/v2/api-docs").permitAll() // 基于角色的权限控制 .antMatchers("/api/teacher/**").hasRole("TEACHER") .antMatchers("/api/student/**").hasRole("STUDENT") .antMatchers("/api/admin/**").hasRole("ADMIN") // 其他所有请求都需要认证 .anyRequest().authenticated() ) .formLogin(form -> form .loginProcessingUrl("/api/auth/login") // 自定义登录处理URL .successHandler((request, response, authentication) -> { // 登录成功,返回JSON信息 response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":200, \"msg\":\"登录成功\"}"); }) .failureHandler((request, response, exception) -> { response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":500, \"msg\":\"用户名或密码错误\"}"); }) .permitAll() ) .logout(logout -> logout .logoutUrl("/api/auth/logout") .logoutSuccessHandler((request, response, authentication) -> { response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"code\":200, \"msg\":\"注销成功\"}"); }) ) .csrf().disable() // 禁用CSRF,便于API测试,生产环境需谨慎考虑 .sessionManagement().sessionFixation().newSession(); // 会话管理 return http.build(); } }

5. 前端Vue.js关键页面示例

前端使用Vue 3 + Element Plus。这里展示学生选题页面的关键逻辑。

文件路径:src/views/student/TopicList.vue

<template> <div class="topic-list"> <el-card> <template #header> <div class="card-header"> <span>毕业设计课题列表</span> <el-input v-model="searchKeyword" placeholder="搜索课题标题或导师" style="width: 300px;" @keyup.enter="loadTopics"> <template #append> <el-button :icon="Search" @click="loadTopics" /> </template> </el-input> </div> </template> <el-table :data="topicList" v-loading="loading"> <el-table-column prop="title" label="课题标题" width="300" /> <el-table-column prop="teacherName" label="发布导师" width="120" /> <el-table-column prop="description" label="课题描述" show-overflow-tooltip /> <el-table-column label="人数" width="100"> <template #default="scope"> <span>{{ scope.row.currentSelected }} / {{ scope.row.maxSelected }}</span> </template> </el-table-column> <el-table-column prop="status" label="状态" width="100"> <template #default="scope"> <el-tag :type="scope.row.status === 'APPROVED' ? 'success' : 'info'"> {{ scope.row.status === 'APPROVED' ? '可选' : '待审核' }} </el-tag> </template> </el-table-column> <el-table-column label="操作" width="180" fixed="right"> <template #default="scope"> <el-button size="small" @click="showDetail(scope.row)">详情</el-button> <el-button size="small" type="primary" :disabled="scope.row.status !== 'APPROVED' || scope.row.currentSelected >= scope.row.maxSelected" @click="handleSelect(scope.row)" > 选择 </el-button> </template> </el-table-column> </el-table> <el-pagination @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="currentPage" :page-sizes="[10, 20, 50]" :page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" :total="total" style="margin-top: 20px;" /> </el-card> <!-- 选题申请对话框 --> <el-dialog v-model="selectDialogVisible" title="申请课题" width="500px"> <el-form :model="selectForm" label-width="80px"> <el-form-item label="申请理由"> <el-input v-model="selectForm.applyComment" type="textarea" :rows="4" placeholder="请简要说明你的研究兴趣、相关技能或选择该课题的原因" maxlength="500" show-word-limit /> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="selectDialogVisible = false">取消</el-button> <el-button type="primary" @click="submitSelection">提交申请</el-button> </span> </template> </el-dialog> </div> </template> <script setup> import { ref, onMounted } from 'vue' import { Search } from '@element-plus/icons-vue' import { ElMessage, ElMessageBox } from 'element-plus' import axios from '@/utils/request' // 封装了axios的请求工具 const searchKeyword = ref('') const topicList = ref([]) const loading = ref(false) const currentPage = ref(1) const pageSize = ref(10) const total = ref(0) const selectDialogVisible = ref(false) const selectForm = ref({ topicId: null, applyComment: '' }) const currentTopic = ref(null) // 加载课题列表 const loadTopics = async () => { loading.value = true try { const params = { page: currentPage.value, size: pageSize.value, keyword: searchKeyword.value, status: 'APPROVED' // 只显示已审核通过的 } const res = await axios.get('/api/topic/list', { params }) // 假设后端返回 { code: 200, data: { records: [], total: 0 } } if (res.code === 200) { topicList.value = res.data.records total.value = res.data.total } } catch (error) { ElMessage.error('加载课题列表失败') } finally { loading.value = false } } // 显示课题详情 const showDetail = (topic) => { ElMessageBox.alert(topic.description, `课题详情:${topic.title}`, { confirmButtonText: '关闭', customClass: 'topic-detail-box' }) } // 处理选择课题 const handleSelect = (topic) => { currentTopic.value = topic selectForm.value.topicId = topic.id selectForm.value.applyComment = '' selectDialogVisible.value = true } // 提交选题申请 const submitSelection = async () => { if (!selectForm.value.applyComment.trim()) { ElMessage.warning('请输入申请理由') return } try { const res = await axios.post(`/api/topic/select/${selectForm.value.topicId}`, null, { params: { applyComment: selectForm.value.applyComment } }) if (res.code === 200) { ElMessage.success(res.msg || '申请提交成功!') selectDialogVisible.value = false loadTopics() // 刷新列表 } else { ElMessage.error(res.msg || '申请失败') } } catch (error) { ElMessage.error('网络错误,提交失败') } } onMounted(() => { loadTopics() }) </script>

6. 系统部署与运行

6.1 后端启动与配置

  1. 配置文件:在src/main/resources/application.yml中配置数据库连接等信息。
    server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/graduation_topic?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password # MyBatis-Plus配置 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQL,生产环境关闭 global-config: db-config: logic-delete-field: isDeleted # 全局逻辑删除字段 logic-delete-value: 1 logic-not-delete-value: 0 # Knife4j配置 knife4j: enable: true setting: language: zh_cn
  2. 启动类:标准的SpringBoot启动类。
    package com.graduation.topic; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class GraduationTopicApplication { public static void main(String[] args) { SpringApplication.run(GraduationTopicApplication.class, args); } }
  3. 运行:在IDE中直接运行GraduationTopicApplication的main方法,或使用Maven命令mvn spring-boot:run启动。访问http://localhost:8080/doc.html即可查看和调试所有API接口。

6.2 前端启动

  1. 进入前端项目目录,安装依赖:npm install
  2. 启动开发服务器:npm run serve。默认访问地址为http://localhost:8081
  3. 修改前端项目中的axios请求基地址,指向后端服务(localhost:8080)。

6.3 初始化数据

系统启动后,需要手动在数据库sys_user表中插入初始管理员账户(密码需使用BCrypt加密)。可以使用以下Java代码生成加密密码:

String rawPassword = "admin123"; BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); String encodedPassword = encoder.encode(rawPassword); System.out.println(encodedPassword); // 将输出结果复制到SQL中

然后执行SQL插入管理员用户,再通过管理员账户登录系统后台,创建教师和学生用户。

7. 常见问题与排查思路

在开发和部署过程中,你可能会遇到以下典型问题:

问题现象可能原因排查思路与解决方案
启动报错:Failed to configure a DataSource数据库连接配置错误或数据库服务未启动。1. 检查application.yml中的url,username,password
2. 确认MySQL服务已启动,且graduation_topic数据库已创建。
3. 检查MySQL驱动版本是否匹配。
访问接口返回403 Forbidden401 UnauthorizedSpring Security权限配置问题或用户未登录/角色不符。1. 检查请求的URL是否在Security配置的permitAll()路径中。
2. 确认请求头是否携带了有效的认证Token(如JWT)或Session。
3. 检查当前登录用户的角色是否拥有访问该接口的权限(hasRole)。
前端调用API跨域(CORS)错误浏览器安全策略阻止了不同源(域名、端口、协议)的请求。在后端添加全局CORS配置。在Spring配置类中添加:
@Bean
public CorsFilter corsFilter() { ... },允许前端域名和端口。
插入数据时出现重复键错误数据库唯一约束冲突,如用户名重复。1. 检查插入的数据是否违反了表设计的唯一约束(如uk_username)。
2. 在前端或后端添加数据重复性校验。
教师审核通过后,课题已选人数未增加业务逻辑有漏洞,currentSelected字段未更新。检查SelectionRecord状态更新为APPROVED时,是否同步执行了topic.setCurrentSelected(topic.getCurrentSelected() + 1)并更新数据库。这是一个关键的业务逻辑点,务必在事务中完成。
分页查询结果不正确前端传递的分页参数与后端接收不一致,或SQL分页语句有误。1. 使用MyBatis-Plus的Page对象,确保currentsize参数正确接收。
2. 检查前端请求参数名是否为pagesize(或pageNumpageSize),保持前后端一致。

8. 项目扩展与最佳实践建议

完成基础功能后,可以考虑以下方向进行扩展和优化,这也能为你的毕业设计论文增加亮点:

  1. 引入Redis缓存:将频繁访问且变化不频繁的数据(如课题列表、用户基本信息)缓存到Redis中,显著提升查询性能,减轻数据库压力。
  2. 实现JWT无状态认证:替换默认的Session机制,使用JSON Web Token (JWT)。用户登录后获取Token,后续请求在Header中携带,更适合前后端分离架构和分布式部署。
  3. 增加文件上传功能:允许教师上传任务书、参考文献等附件,学生上传开题报告、中期检查等文档。需考虑文件存储(本地或OSS)和在线预览。
  4. 集成消息推送:使用WebSocket或第三方推送服务,实现选题状态变更、新通知等信息的实时推送,提升用户体验。
  5. 完善后台管理功能
    • 数据可视化:使用ECharts等库,为管理员提供选题进度统计、各导师课题数量、学生选择热度等图表。
    • 操作日志:记录关键操作(如发布课题、审核、用户管理),便于审计和问题追溯。
    • 系统配置:允许管理员在界面上动态配置选题的起止时间、每人可选课题数量等规则。
  6. 代码层面的最佳实践
    • 统一异常处理:使用@ControllerAdvice@ExceptionHandler全局处理异常,返回友好的错误信息给前端。
    • 接口参数校验:使用@Validated注解和JSR-303规范(如@NotBlank,@Size)对Controller入参进行校验。
    • 使用枚举类:将数据库中的状态字段(如topic.status,selection_record.status)定义为Java枚举,避免魔法字符串,提高代码可读性和安全性。
    • 服务层事务管理:对于涉及多表修改的操作(如学生选课),务必在Service方法上使用@Transactional注解,保证数据一致性。
    • 定期备份数据库:编写脚本或使用MySQL的定时任务,定期对数据库进行备份,防止数据丢失。

这个基于SpringBoot的毕业设计选题系统,从需求到部署提供了一个完整的全栈开发范例。它不仅是一个可运行的毕业设计项目,更是一个学习现代Java Web开发技术栈(SpringBoot、MyBatis-Plus、Spring Security、Vue.js)的绝佳实践。在实现过程中,深入理解业务逻辑与数据一致性的处理、前后端分离的协作模式、以及安全认证的设计,远比单纯实现功能更有价值。你可以在此基础上,结合自己学校的实际流程,增加或调整功能模块,使其更贴合实际应用场景。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/22 16:50:19

CLion中Makefile项目自动化构建:编译前清理与编译后复制配置指南

1. 项目概述&#xff1a;为什么要在CLion里折腾Makefile&#xff1f;如果你是一个C/C的老手&#xff0c;或者正在维护一个历史悠久的项目&#xff0c;那你对Makefile一定不会陌生。它就像一个项目的“烹饪食谱”&#xff0c;清晰地定义了从原材料&#xff08;源代码&#xff09…

作者头像 李华
网站建设 2026/8/22 16:49:04

车载安卓Framework开发核心技术与面试指南

1. 项目背景与核心价值作为一名在安卓Framework层开发领域深耕多年的工程师&#xff0c;我注意到近期车载系统开发岗位的需求呈现爆发式增长。根据行业调研数据&#xff0c;2023年智能座舱相关岗位数量同比增加了67%&#xff0c;其中FW&#xff08;Framework&#xff09;工程师…

作者头像 李华
网站建设 2026/8/22 16:46:02

5分钟同步上云:Nextcloud桌面客户端新手配置指南

5分钟同步上云&#xff1a;Nextcloud桌面客户端新手配置指南 【免费下载链接】desktop &#x1f4bb; Desktop sync client for Nextcloud 项目地址: https://gitcode.com/gh_mirrors/deskto/desktop 很多文档散在网页端和本地硬盘之间&#xff0c;上传下载全靠手动&…

作者头像 李华
网站建设 2026/8/22 16:43:53

2026年7月辽源市新房价格深度分析报告

一、报告背景与数据说明本报告基于2026年7月辽源市新房实际成交案例&#xff0c;结合区域分布、楼盘定位、户型结构与成交价格等多维数据&#xff0c;对当前辽源市新房市场进行深度分析。报告旨在为购房者、投资者及行业研究者提供客观、真实的市场参考。数据来源说明&#xff…

作者头像 李华
网站建设 2026/8/22 16:38:39

稳态耦合智能体:从内在驱动到亲社会行为的AI架构革新

1. 从“观察”到“耦合”&#xff1a;一个被误解的智能体进化路径最近在AI智能体&#xff08;Agent&#xff09;的圈子里&#xff0c;一个老生常谈的话题又被翻了出来&#xff1a;我们到底需要什么样的“社会性”智能体&#xff1f;是那种能通过观察海量数据&#xff0c;模仿出…

作者头像 李华