十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot全栈实战:课程设计选题系统架构设计与核心实现

SpringBoot全栈实战:课程设计选题系统架构设计与核心实现 简介本资源是一套完整的基于SpringBoot开发的课程设计选题管理系统毕业设计解决方案面向计算机类专业本科生及指导教师解决高校课程设计环节中课题发布、学生选题、教师审核与管理员统筹等全流程数字化管理需求。压缩包共6个文件13.99MB包含可直接运行的SpringBoot源码zip、MySQL数据库脚本sql、万字详细设计文档docx、系统功能说明与技术实现报告doc、答辩用PPTpptx及简要说明文本txt覆盖开发、部署、测试与汇报全阶段。已有115人学习下载内容结构清晰源码含完整三层架构与角色权限控制数据库脚本支持一键导入文档详述需求分析、ER图、接口设计与核心代码逻辑PPT涵盖系统演示、架构图与答辩要点便于快速理解与二次开发。1. 项目概述与核心价值最近在整理过往的课程设计项目时翻到了一个我几年前带学生团队完成的“课程设计选题管理系统”。这个项目虽然听起来很“教学化”但麻雀虽小五脏俱全它完整地走了一遍从需求分析、技术选型、前后端开发到部署上线的全流程是一个绝佳的SpringBoot全栈实战案例。今天我就把这个项目的核心设计思路、技术实现细节以及那些“教科书上不会写”的踩坑经验掰开揉碎了分享给大家。无论你是正在做课程设计的学生还是想找一个完整项目来巩固SpringBoot技能的开发者这篇文章都能给你提供一份可以直接“抄作业”的蓝图。这个系统的核心目标是解决高校课程设计中“选题”这个环节的混乱局面。传统模式下老师发布题目靠Excel或Word学生选题靠抢、靠邮件、靠跑腿结果汇总和过程管理更是头疼。我们构建的这个系统就是要将选题流程线上化、规范化、透明化。老师可以方便地发布、审核题目学生可以浏览、选择心仪的题目管理员则能统筹全局整个过程数据实时同步报表一键生成。它本质上是一个轻量级的、垂直领域的流程审批与资源管理系统其技术架构和业务逻辑对很多中小型后台管理系统都有很强的参考价值。2. 技术选型与架构设计思路2.1 为什么是SpringBoot在项目启动时我们面临几个框架选项传统的SSMSpringSpringMVCMyBatis组合或者新兴的SpringBoot。最终选择SpringBoot是基于以下几点核心考量快速启动与约定大于配置课程设计项目周期短我们需要把精力集中在业务逻辑上而不是繁琐的XML配置和依赖管理上。SpringBoot的自动配置和起步依赖Starter让我们在几分钟内就能搭起一个可运行的后端服务。比如引入spring-boot-starter-webWeb MVC相关的组件就齐活了引入spring-boot-starter-data-jpa或mybatis-spring-boot-starter数据库访问层也基本配置完成。这种“开箱即用”的特性对于追求效率的教学和实战项目是首选。微服务友好与生态成熟虽然我们这个系统是单体应用但SpringBoot的设计哲学是为微服务做准备。其内嵌的Tomcat服务器、健康检查、外部化配置等特性使得未来如果业务增长拆分成微服务会相对平滑。同时SpringBoot拥有极其丰富的社区生态我们需要的任何功能如安全控制Spring Security、API文档Swagger/OpenAPI、缓存Redis、任务调度Quartz等几乎都有对应的、集成良好的Starter大大降低了集成复杂度。简化部署与运维SpringBoot可以将整个应用打包成一个可执行的JAR文件内嵌了Web服务器。这意味着部署时只需要一个Java运行环境JRE和这个JAR包运行java -jar your-app.jar即可启动彻底告别了以往需要单独配置Web服务器如Tomcat并部署WAR包的繁琐过程。这对于课程设计演示、以及后续可能的简易部署场景非常友好。注意SpringBoot版本选择需谨慎。我们当时选择了2.x系列的一个稳定版本如2.3.12.RELEASE。不建议盲目追求最新版因为新版本可能引入不兼容的变更导致依赖库出现冲突。选择长期支持LTS版本或已被广泛验证的稳定版本是项目平稳进行的保障。2.2 前后端分离的架构决策我们采用了经典的前后端分离架构。后端提供纯粹的RESTful API前端通过Ajax调用这些API进行数据交互和页面渲染。后端API层技术栈SpringBoot Spring Data JPA / MyBatis-Plus MySQL。选择JPA还是MyBatis-Plus这是一个经典问题。我们最终选择了MyBatis-Plus。原因在于课程设计管理系统涉及较多复杂的关联查询如查询某个老师发布了哪些题目这些题目被哪些学生选了学生的信息是什么。JPA的“对象-关系映射”在简单CRUD上非常优雅但面对复杂动态SQL其代码会显得有些笨重。而MyBatis-Plus在保留MyBatis强大SQL灵活性的基础上提供了类似JPA的Lambda查询Wrapper、通用的Service/Mapper层封装做到了灵活与高效的平衡。它的QueryWrapper可以非常直观地构建动态查询条件这对于后端业务逻辑的实现至关重要。API设计规范我们定义了统一的响应体格式例如{“code”: 200, “msg”: “success”, “data”: {…}}便于前端统一处理。同时使用Spring的RestControllerAdvice进行全局异常处理将各种异常如业务异常、参数校验异常、数据库异常捕获并转换为友好的、格式统一的错误信息返回给前端。前端展现层技术栈我们当时选择了Vue.js 2.x Element UI。选择Vue是因为其学习曲线相对平缓组件化思想清晰非常适合学生团队快速上手。Element UI提供了丰富的、符合后台管理系统审美的UI组件表格、表单、对话框、导航菜单等能极大提升开发效率。前后端协作使用Swagger通过springfox-boot-starter或springdoc-openapi自动生成API在线文档。前端开发人员可以不依赖后端启动直接查看接口文档和模拟数据并行开发大大提升了协作效率。数据库选择MySQL作为最流行的开源关系型数据库之一MySQL的稳定性、社区支持和学习资源都非常丰富。对于这个量级的系统其性能完全足够。设计要点我们设计了几个核心表user用户表通过role字段区分学生、教师、管理员。teacher教师信息表与user表一对一关联。student学生信息表与user表一对一关联包含班级、学号等字段。course_design课程设计题目表包含标题、描述、要求、最大可选人数、当前已选人数、状态待审核/已发布/已截止、关联的教师ID等。selection_record选题记录表这是核心的关联表记录学生ID、题目ID、选择时间、状态待确认/已确认/已拒绝等。它建立了学生与题目之间的多对多关系一个学生只能选一个题目一个题目可被多个学生选直到满额。这种架构确保了职责清晰后端专注于数据和业务规则前端专注于交互和展示为项目的可维护性和可扩展性打下了基础。3. 核心功能模块设计与实现细节3.1 用户角色与权限控制系统包含三类角色学生、教师、系统管理员。他们的权限泾渭分明学生浏览已发布的题目、选择题目一人一题、查看自己的选题状态、取消选择在教师确认前。教师创建并提交课程设计题目需管理员审核、查看自己发布的题目列表及选题学生、确认或拒绝学生的选题申请、发布/截止题目。管理员管理用户增删改查、审核教师提交的题目通过/驳回、查看全局选题情况、导出各类统计报表。权限实现方案 我们采用了基于角色的访问控制RBAC模型但实现上做了简化。没有引入复杂的权限-角色-资源三级模型而是直接在代码层面进行控制。接口级权限使用Spring Security或自定义拦截器。在每个需要权限控制的Controller方法上使用注解如PreAuthorize(“hasRole(‘TEACHER’)”)或PreAuthorize(“hasAnyRole(‘ADMIN’, ‘TEACHER’)”)。这样非教师用户访问创建题目的接口会被直接拦截。数据级权限这是更精细的控制。例如教师只能操作查看、修改、确认自己发布的题目。我们在Service层进行校验。当教师尝试确认一个选题时业务逻辑代码会先检查这个选题对应的题目ID是否属于当前登录的教师。如果不是则抛出“无权操作”的业务异常。// 伪代码示例 public void confirmSelection(Long selectionRecordId, Long currentTeacherId) { SelectionRecord record selectionRecordRepository.findById(selectionRecordId).orElseThrow(...); CourseDesign courseDesign record.getCourseDesign(); // 数据级权限校验当前登录教师是否是题目的所有者 if (!courseDesign.getTeacher().getId().equals(currentTeacherId)) { throw new BusinessException(“您无权操作此题目下的选题”); } // ... 执行确认逻辑 record.setStatus(SelectionStatus.CONFIRMED); selectionRecordRepository.save(record); }3.2 选题流程的状态机设计选题是系统的核心业务流程涉及多个状态流转。我们将其抽象为一个状态机这能让业务逻辑变得非常清晰避免出现非法状态转换。核心状态定义题目状态 (CourseDesign.status)PENDING_REVIEW待审核教师提交后。PUBLISHED已发布管理员审核通过后。CLOSED已截止教师手动关闭或到达截止时间。选题记录状态 (SelectionRecord.status)PENDING待确认学生选择后。CONFIRMED已确认教师操作后。REJECTED已拒绝教师操作后。状态流转规则学生只能选择状态为PUBLISHED且未满额的题目。选择后创建一条状态为PENDING的选题记录。教师可以对状态为PENDING的选题记录进行CONFIRMED或REJECTED操作。一旦某个题目的一个选题被CONFIRMED该题目的已选人数1。如果人数达到上限题目自动变为CLOSED。学生可以在选题记录被CONFIRMED前主动取消删除记录。一旦被确认则无法单方面取消。实操心得在数据库表中对于状态字段我们使用String类型存储枚举值如“PUBLISHED”并在Java中定义对应的枚举类。这样做的好处是数据库中的数据可读性强便于直接查询和调试。同时在业务代码中所有状态判断都必须使用枚举避免魔法字符串。状态转换的逻辑最好集中在一个服务类如SelectionStateMachineService中提供canTransit(fromStatus, toStatus)和transit(recordId, targetStatus)等方法确保状态转换的合法性和一致性。3.3 数据库表关键字段与关联设计这里详细展开几个核心表的设计这是系统稳定的基石。course_design课程设计题目表CREATE TABLE course_design ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, title varchar(200) NOT NULL COMMENT 题目名称, description text COMMENT 题目详细描述, requirements text COMMENT 任务要求, max_selection int(11) NOT NULL DEFAULT 1 COMMENT 最大可选人数, current_selected int(11) NOT NULL DEFAULT 0 COMMENT 当前已选人数, status varchar(50) NOT NULL COMMENT 状态: PENDING_REVIEW/PUBLISHED/CLOSED, teacher_id bigint(20) NOT NULL COMMENT 发布教师ID, created_at datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, updated_at datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), KEY idx_teacher_id (teacher_id), KEY idx_status (status), CONSTRAINT fk_course_design_teacher FOREIGN KEY (teacher_id) REFERENCES teacher (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT课程设计题目表;关键点1max_selection和current_selected。current_selected的更新必须放在事务中并且考虑并发问题。当学生选题时需要先判断current_selected max_selection然后在同一事务中执行current_selected current_selected 1和创建选题记录。这可以通过数据库的乐观锁如版本号或悲观锁SELECT ... FOR UPDATE来实现防止超选。关键点2外键teacher_id建立了与教师表的关联。ON DELETE CASCADE表示如果教师账号被删除其发布的题目也一并删除根据业务需求也可能设置为SET NULL或禁止删除。selection_record选题记录表CREATE TABLE selection_record ( id bigint(20) NOT NULL AUTO_INCREMENT COMMENT 主键ID, student_id bigint(20) NOT NULL COMMENT 学生ID, course_design_id bigint(20) NOT NULL COMMENT 题目ID, status varchar(50) NOT NULL COMMENT 状态: PENDING/CONFIRMED/REJECTED, selected_at datetime DEFAULT CURRENT_TIMESTAMP COMMENT 选择时间, confirmed_at datetime COMMENT 教师确认时间, teacher_feedback varchar(500) COMMENT 教师反馈拒绝或确认时填写, PRIMARY KEY (id), UNIQUE KEY uk_student_course (student_id, course_design_id), -- 唯一约束防止重复选择 KEY idx_student_id (student_id), KEY idx_course_design_id (course_design_id), KEY idx_status (status), CONSTRAINT fk_selection_student FOREIGN KEY (student_id) REFERENCES student (id) ON DELETE CASCADE, CONSTRAINT fk_selection_course FOREIGN KEY (course_design_id) REFERENCES course_design (id) ON DELETE CASCADE ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT选题记录表;关键点1UNIQUE KEY uk_student_course (student_id, course_design_id)。这是一个组合唯一索引它的作用是保证一个学生对同一个题目只能有一条选题记录。这是业务规则的强约束必须在数据库层面守住。即使前端或后端逻辑有漏洞导致重复提交数据库也会抛出唯一键冲突异常保证数据一致性。关键点2student_id和course_design_id都建立了外键约束。删除学生或题目时关联的选题记录也会被级联删除保证了数据的参照完整性。4. 后端核心业务逻辑与API实现4.1 教师发布题目与管理员审核流程教师发布题目不是一个简单的插入操作而是一个状态驱动的流程。教师提交教师前端填写表单调用后端/api/course-design/draft接口。此时题目对象被创建但状态为PENDING_REVIEWcurrent_selected为0。这个题目对学生不可见。管理员审核管理员在后台看到一个待审核题目列表。他可以查看详情然后调用/api/course-design/{id}/review接口传递actionPASS或REJECT和reviewComment审核意见。PostMapping(/{id}/review) PreAuthorize(hasRole(ADMIN)) public ApiResponse reviewCourseDesign(PathVariable Long id, RequestBody ReviewRequest request) { CourseDesign courseDesign courseDesignService.getById(id); if (courseDesign.getStatus() ! CourseDesignStatus.PENDING_REVIEW) { throw new BusinessException(该题目当前状态不可审核); } if (request.getAction() ReviewAction.PASS) { courseDesign.setStatus(CourseDesignStatus.PUBLISHED); courseDesign.setReviewComment(request.getComment()); // 可以在这里触发通知告知教师题目已通过 // notificationService.notifyTeacher(courseDesign.getTeacherId(), 您的题目已通过审核); } else { courseDesign.setStatus(CourseDesignStatus.REJECTED); // 可以定义一个REJECTED状态或直接删除 courseDesign.setReviewComment(request.getComment()); } courseDesignService.updateById(courseDesign); return ApiResponse.success(); }状态同步一旦题目状态变为PUBLISHED学生端查询题目列表的接口就会将其包含在内。这里查询接口需要根据当前用户角色动态构造查询条件。对于学生只查询status ‘PUBLISHED’的题目。4.2 学生选题的并发控制与事务处理这是系统最需要处理高并发的场景之一多个学生同时抢一个热门题目。我们必须确保current_selected不会超过max_selection。方案一数据库乐观锁在course_design表中增加一个version字段版本号。Transactional(rollbackFor Exception.class) public ApiResponse selectCourseDesign(Long studentId, Long courseDesignId) { // 1. 查询题目并带上版本号 CourseDesign courseDesign courseDesignMapper.selectByIdForUpdate(courseDesignId); // 或者用乐观锁先select // 2. 检查是否可选 if (courseDesign.getCurrentSelected() courseDesign.getMaxSelection()) { throw new BusinessException(该题目已选满); } // 3. 检查学生是否已选过其他题目业务规则一人一题 if (selectionRecordMapper.existsByStudentId(studentId)) { throw new BusinessException(您已选择过题目无法重复选择); } // 4. 更新题目已选人数 (乐观锁) int updateCount courseDesignMapper.updateSelectedCount(courseDesignId, courseDesign.getVersion()); if (updateCount 0) { // 更新失败说明版本号已变有并发冲突 throw new BusinessException(选题人数已满请重试); } // 5. 创建选题记录 SelectionRecord record new SelectionRecord(); record.setStudentId(studentId); record.setCourseDesignId(courseDesignId); record.setStatus(SelectionStatus.PENDING); selectionRecordMapper.insert(record); return ApiResponse.success(); }对应的Mapper更新SQLUPDATE course_design SET current_selected current_selected 1, version version 1 WHERE id #{id} AND version #{version} AND current_selected max_selection;updateCount返回受影响的行数。如果为0表示更新失败可能是版本号不对也可能是人数已满整个事务回滚。方案二数据库悲观锁在查询题目时直接使用SELECT ... FOR UPDATE锁定该行记录直到当前事务提交。Transactional(rollbackFor Exception.class) public ApiResponse selectCourseDesign(Long studentId, Long courseDesignId) { // 1. 悲观锁查询 CourseDesign courseDesign courseDesignMapper.selectByIdWithLock(courseDesignId); // ... 后续检查与插入逻辑同上 }对应的SQLSELECT * FROM course_design WHERE id #{id} FOR UPDATE。实操心得对于课程设计这种并发压力不是极端巨大的场景乐观锁通常是更优选择。因为它不会长时间阻塞其他线程在高并发下性能更好。悲观锁更简单直接但可能引发死锁或降低吞吐量。我们最终采用了乐观锁方案。此外整个“检查-更新-插入”操作必须放在一个Transactional事务中确保原子性避免出现“检查通过但更新前被其他线程修改”的中间状态。4.3 复杂查询与分页实现系统中有很多列表查询需求如学生查看题目列表、教师查看自己的题目、管理员查看所有选题记录等。这些查询往往带有复杂的过滤条件按状态、按教师、按标题关键词和分页要求。我们使用MyBatis-Plus的QueryWrapper来动态构建查询条件并结合其强大的分页插件。GetMapping(/api/course-design/list) public ApiResponsePageCourseDesignVO listCourseDesign( RequestParam(required false) String title, RequestParam(required false) String status, RequestParam(required false) Long teacherId, RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize) { PageCourseDesign page new Page(pageNum, pageSize); QueryWrapperCourseDesign queryWrapper new QueryWrapper(); // 动态条件 if (StringUtils.hasText(title)) { queryWrapper.like(“title”, title); } if (StringUtils.hasText(status)) { queryWrapper.eq(“status”, status); } if (teacherId ! null) { queryWrapper.eq(“teacher_id”, teacherId); } // 排序 queryWrapper.orderByDesc(“created_at”); // 执行分页查询 PageCourseDesign courseDesignPage courseDesignService.page(page, queryWrapper); // 将PageCourseDesign 转换为 PageCourseDesignVO (视图对象包含教师姓名等额外信息) PageCourseDesignVO voPage convertToVOPage(courseDesignPage); return ApiResponse.success(voPage); }关键点VOView Object的使用我们不会直接把数据库实体CourseDesign返回给前端。因为前端可能需要显示教师姓名而实体中只有teacher_id。我们会定义一个CourseDesignVO在Service层或Controller层通过关联查询如MyBatis的Select注解配合ResultMap或使用MyBatis-Plus的TableField(exist false)配合自定义SQL将所需的数据组装好。这避免了N1查询问题。分页插件配置需要在SpringBoot配置类中配置MyBatis-Plus的分页插件PaginationInterceptor它会在执行分页查询时自动在SQL后加上LIMIT语句。5. 前端关键页面与交互实现5.1 学生选题页面的设计与防重复提交学生选题页面通常是一个表格列出所有PUBLISHED状态的题目包含标题、要求、教师、已选/限额等信息并有一个“选择”按钮。前端实现要点数据加载使用Vue的created或mounted生命周期钩子调用后端分页接口加载题目列表。“选择”按钮的状态需要根据业务逻辑动态禁用。例如如果题目已满额(currentSelected maxSelection)按钮应禁用并显示“已满”如果当前学生已经选择过其他题目需要从全局状态如Vuex中获取所有按钮都应禁用并提示“您已选题”。防重复提交这是前端必须做的。点击“选择”按钮后立即将按钮设置为loading状态并禁用防止用户快速双击。然后调用后端选题API。// Vue组件方法示例 async handleSelect(courseId) { // 1. 防止重复点击 if (this.selectLoading) return; this.selectLoading true; try { // 2. 调用后端API const resp await this.$axios.post(/api/selection/select, { courseDesignId: courseId }); if (resp.data.code 200) { this.$message.success(选题成功等待教师确认); // 3. 更新本地状态例如重新加载列表或更新该行数据状态 this.fetchCourseList(); } else { this.$message.error(resp.data.msg); } } catch (error) { this.$message.error(网络错误或系统异常); console.error(error); } finally { // 4. 无论成功失败最终都要解除loading状态 this.selectLoading false; } }结果反馈与状态更新选题成功后前端需要给出明确提示并立即更新页面状态。例如将对应题目的“选择”按钮变为“已申请”并禁用之。这可以通过重新调用列表接口或者更优化地只更新本地对应数据项的状态来实现。5.2 教师管理页面的实时数据更新教师后台主要有两个核心页面“我的题目管理”和“待确认选题列表”。“我的题目管理”页面功能以表格形式展示教师自己发布的所有题目支持对题目进行“发布/截止”、“编辑”、“删除”等操作。对于已发布的题目可以查看选题学生列表。实时性当教师执行“截止”操作后该题目应立即从学生的可选列表中消失。这可以通过两种方式实现方式一简单操作成功后前端只是给出成功提示。学生端的更新依赖于他们下一次主动刷新列表或页面。方式二体验更好引入WebSocket或Server-Sent Events (SSE)。当教师截止一个题目时后端向所有在线的学生客户端广播一条消息通知某个题目状态已更新。学生前端收到消息后可以主动更新本地列表将已截止的题目移除或标记。对于课程设计系统方式一通常已足够。“待确认选题列表”页面功能以表格形式列出所有选择了该教师名下题目且状态为PENDING的学生申请。每条记录包含学生信息、选择时间并提供“确认”和“拒绝”按钮。交互细节点击“确认”或“拒绝”时最好弹出一个对话框让教师可以填写简短的反馈意见teacher_feedback。确认操作是关键事务前端在调用API后应根据返回结果立即更新该行记录的状态变为“已确认”或“已拒绝”并从“待确认”列表中移除。这里同样需要注意按钮的防重复提交。6. 系统部署、测试与性能考量6.1 从开发到部署的完整链路环境配置使用SpringBoot的application.yml多环境配置文件application-dev.yml,application-prod.yml通过spring.profiles.active激活不同配置。开发环境连接本地MySQL生产环境连接云服务器数据库。打包使用Maven或Gradle的打包命令生成可执行的JAR文件。mvn clean package会生成一个形如course-design-system-0.0.1-SNAPSHOT.jar的文件。前端构建在Vue项目根目录下运行npm run build生成静态资源dist文件夹。部署后端将JAR包上传到服务器如使用scp命令。服务器上需要安装Java运行环境JRE 8或11。使用nohup命令在后台运行nohup java -jar course-design-system-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod app.log 21 。这会将应用日志输出到app.log文件。更推荐的方式是使用系统服务管理如创建systemd服务单元文件实现开机自启和便捷的启停管理。部署前端将dist文件夹内的所有文件放到Nginx或Apache的Web根目录下。并配置Nginx将所有非静态资源的API请求反向代理到后端SpringBoot应用默认端口8080。server { listen 80; server_name your-domain.com; # 或服务器IP location / { root /path/to/your/dist; index index.html; try_files $uri $uri/ /index.html; # 支持Vue Router的history模式 } location /api/ { proxy_pass http://localhost:8080; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }6.2 关键测试场景与性能优化建议测试场景并发选题测试使用JMeter或Postman Runner模拟数十个学生同时请求同一个热门题目。验证是否会出现超选已选人数最大人数的情况。这是检验乐观锁或悲观锁是否生效的关键测试。边界条件测试学生尝试选择已满的题目。学生尝试选择状态不是PUBLISHED的题目。教师尝试确认一个已经被其他教师确认过的题目通过不同账号模拟。删除一个有学生选择的题目检查外键约束是否正确触发选题记录应被级联删除。分页压力测试当题目数量达到上万条时测试列表分页查询的响应速度特别是带有模糊搜索(like)的情况。性能优化建议数据库索引确保查询条件常用的字段都建立了索引如course_design表的teacher_id,statusselection_record表的student_id,course_design_id,status。这是提升查询性能最有效的手段。API响应缓存对于一些不常变动的数据如“系统公告”、“教师列表”如果不变可以使用Spring Cache集成Redis或Caffeine进行缓存减少数据库查询。静态资源分离将前端构建出的CSS、JS、图片等静态资源部署到CDN或Nginx直接服务减轻后端服务器的压力。SQL优化使用MyBatis-Plus的QueryWrapper时注意避免在WHERE条件中对字段进行函数操作如DATE(created_at) ‘2023-10-01’这会导致索引失效。对于日期范围查询应使用created_at ‘2023-10-01 00:00:00’ AND created_at ‘2023-10-02 00:00:00’。7. 常见问题排查与实战踩坑记录在实际开发和部署过程中我们遇到了不少典型问题这里记录下来供大家参考。问题一前端跨域CORS错误现象前端Vue应用运行在localhost:8081后端SpringBoot运行在localhost:8080。前端调用API时浏览器控制台报CORS错误。解决方案在后端SpringBoot应用中全局配置CORS。可以创建一个配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(“/api/**”) // 针对所有/api开头的接口 .allowedOrigins(“http://localhost:8081”) // 允许的前端地址 .allowedMethods(“GET”, “POST”, “PUT”, “DELETE”, “OPTIONS”) .allowedHeaders(“*”) .allowCredentials(true); // 如果前端请求带cookie需要设为true } }注意在生产环境allowedOrigins应替换为实际的前端域名或IP不要使用“*”以免带来安全风险。问题二MyBatis-Plus分页查询总数不准或性能慢现象当表数据量很大且查询条件复杂时分页插件自动执行的COUNT(*)语句可能很慢或者在某些带有GROUP BY的复杂查询中返回错误的总数。解决方案自定义分页查询。可以创建一个自定义的Page对象或者直接编写包含分页逻辑的XML映射文件SQL手动计算总数。MyBatis-Plus也支持关闭自动优化count查询page.setOptimizeCountSql(false)但更根本的是优化查询语句和索引。问题三事务失效问题现象在Service方法中即使抛出了异常数据库操作也没有回滚。排查检查方法是否是public。Spring AOP代理默认只对public方法生效。检查是否在同一个类内部调用带有Transactional注解的方法。由于Spring AOP是基于代理的自调用会绕过代理导致事务注解失效。解决方法是将事务方法放到另一个Service中调用。检查异常类型。默认情况下Transactional只对运行时异常RuntimeException和错误Error进行回滚。如果抛出的受检异常如Exception需要显式指定Transactional(rollbackFor Exception.class)。检查数据库引擎是否支持事务如InnoDB支持MyISAM不支持。问题四前端打包后访问后端API 404现象本地开发时一切正常前端打包部署到Nginx后访问页面正常但所有API请求都返回404。原因前端代码中配置的API基础地址baseURL可能还是localhost:8080。打包后这个地址不会变但浏览器是从Nginx的域名或IP访问页面向localhost:8080发请求自然会跨域或找不到。解决方案前端不要写死API地址。可以通过环境变量来区分开发和生产环境。例如在Vue项目中创建.env.production文件内容如VUE_APP_API_BASE_URL/api。在代码中通过process.env.VUE_APP_API_BASE_URL获取。这样开发时可以是http://localhost:8080/api生产时就是/api由Nginx进行代理转发。这个基于SpringBoot的课程设计选题管理系统从技术上看它串联了SpringBoot、MyBatis-Plus、Vue、MySQL等主流技术栈从业务上看它涵盖用户权限、状态机、事务控制、并发处理等经典场景。实现它的过程就是一个完整的全栈项目实战。希望这份超详细的拆解能帮你不仅“复制”出一个系统更能理解其背后的设计逻辑和实现细节在未来的项目中举一反三。本文还有配套的精品资源点击获取
返回列表