news 2026/9/9 15:39:02

SpringBoot+Vue学生选课系统:从数据库设计到部署答辩全攻略

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SpringBoot+Vue学生选课系统:从数据库设计到部署答辩全攻略

又是一个被“学生选课系统”折磨的毕业季。说实话,每年这时候找我聊这个题目的学弟学妹特别多,原因是它看起来简单,但真要做到能答辩、能演示、能写进论文,牵扯出来的细节比你想象的多得多。这次我把基于SpringBoot + Vue的学生选课系统整个从零到一的思路、建表、后端接口、前端页面、联调部署全部捋一遍,源码、数据库脚本和文档的坑也一并说清楚,这篇基本就是可以直接照做的完整参考。

如果手里已经有一套源码,但不知道怎么讲清楚、不知道怎么改成自己的,这篇同样适用——很多时候答辩没高分,不是因为代码不行,是因为你自己没吃透项目。我下面会从“设计思路”开始,一路说到“项目启动时最容易炸的地方”,你耐心看,至少能少走三天的弯路。

1. 项目整体设计:先想清楚要做什么,再动手写代码

1.1 这道毕设题到底在考什么

很多同学看到“学生选课系统”第一反应是:不就是登录、选课、退课嘛,有什么难的?结果一上手就发现,要做的功能细碎且联动极多,而且越做到后面越发现自己在重复造轮子。

这道题放在毕业设计里,核心考核点并不是“你会不会写增删改查”,而是:

  • 你懂不懂用户角色权限设计,学生、教师、管理员各自看到的界面和操作完全不一样;
  • 你懂不懂数据库表之间的关联关系,选课记录、课程表、学生表、教师表如何通过外键或逻辑键串联;
  • 你懂不懂前后端分离架构下,接口怎么定义、数据怎么交互、跨域怎么解决;
  • 你懂不懂选课业务里的冲突场景,比如课程容量满了怎么办、同一时间冲突怎么处理、并发选课怎么防止超卖。

所以拿到题目,第一件要做的事不是打开IDEA新建项目,而是把功能架构图画出来。哪怕你准备用现成源码改,也得先搞清楚每个模块的边界,不然答辩时老师随便抽一个功能问你是怎么设计的,你支支吾吾,评分直接就下去了。

1.2 为什么选SpringBoot + Vue这套组合

这套技术栈在近几年的毕业设计里几乎是“统治级”的存在,原因很实际:

  • SpringBoot把SpringMVC那套繁琐的配置全部自动化了,你不用再写一堆XML配置文件,内置Tomcat,打包成jar直接跑,对毕设来说省掉部署的很多麻烦事;
  • Vue使用组件化开发,写前端页面比传统JSP加jQuery那种方式清晰太多,数据和页面是双向绑定的,表单、列表、弹窗这些交互写起来效率极高;
  • 前后端分离以后,前端可以单独启动一个开发服务器,后端单独跑一个端口,联调时出了问题很容易定位是前端还是后端的事;
  • 网上资料、现成开源项目、教程视频极其丰富,哪怕你基础一般,遇到问题也能很快找到解决方案。

如果你单纯为了“好过”,那确实可以继续用JSP + Servlet那套老技术,但说实话,等你去实习或者找工作,会发现面试官对SpringBoot和Vue的熟悉程度几乎是默认要求了。借毕设这个机会把这套流程跑通,对你自己的收益远大于学分本身。

1.3 系统角色与功能模块拆解

一个标准的学生选课系统,通常包含三类用户,每种用户的权限和功能边界必须一开始就界定清楚,否则后面接口设计时会乱套。

角色核心功能说明
管理员教师管理、学生管理、课程管理、选课日志查看、系统统计一切基础数据的录入和维护入口
教师查看个人课程、查看选课学生名单、录入成绩只操作与自己相关的数据
学生浏览课程、在线选课、退课、查看课表、查询成绩系统使用频率最高的人群

除此之外,还有一个【未登录用户只能看到登录页】的约束,这个也是Spring Security或拦截器要处理的核心逻辑。我在写的时候习惯单独抽一个“公共模块”,用于放常量、工具类、统一返回结果封装类,这样三个角色共用一套底层代码,不会到处重复。

功能模块我建议分成六个:登录认证模块、学生管理模块、课程管理模块、选课管理模块、成绩管理模块、个人中心模块。前面两个是基础,中间三个是核心,最后一个用来做一些个人信息维护和密码修改。模块划清楚了,数据库设计就有依据了。

2. 数据库设计:选课系统真正花心思的地方

2.1 数据表到底需要几张

我见过不少同学的设计方案,一上来就建了二十几张表,看着很唬人,实际上大部分都是空的或者冗余的。选课系统的核心其实就是五张表:

  • 学生表(student):存储学生学号、姓名、密码、专业、班级;
  • 教师表(teacher):存储教师工号、姓名、密码、职称、所属院系;
  • 课程表(course):存储课程编号、课程名称、学分、上课时间、上课地点、容量、已选人数、授课教师ID;
  • 选课表(student_course):记录学生和课程的对应关系,也就是中间表;
  • 管理员表(admin):管理员账号和密码,数量一般只有一两条。

如果你想做得更完整一点,可以再加一张“学生成绩表”,记录某学生某门课的成绩,这样学生在选课系统里就能查看成绩,教师也能录入成绩。但要注意,有的同学把成绩字段直接放在选课表里,也可以——看答辩时你怎么讲道理。选课表本身就包含了“某学生选了某门课”这个事实,成绩本质上是这个事实的扩展属性,所以放在选课表里一点问题都没有,反而少一张表少一次联表查询。

2.2 关键字段设计要点

这里我说几个我踩过的坑,希望你不要再踩。

课程表的“已选人数”这个字段,很多同学容易忽略。没有这个字段,你就必须在查询课程列表时统计选课表的记录数,数据量小无所谓,但列表页每次请求都做一次group by,体验会明显下降。加上这个字段后,选课成功时在事务里做update course set selected_count = selected_count + 1,退课时减一,配合容量字段去做校验,稍微注意下并发就行。

学生表和教师表的密码字段,用的是varchar(100)而不是varchar(20),因为密码加密后长度会变长。很多人在数据库阶段没考虑这事,到后端联调时发现密码总是对不上,后悔都来不及。直接用BCrypt加密,存储长度预留64位以上就稳了。

再一个就是主键的选择。学生表和教师表不建议用自增ID做主键,因为他们的业务编号——学号和工号——本身就具有唯一性。你直接用学号做主键,后面关联查询时会省掉一次关联。当然,如果你用MyBatis-Plus,它对字符串主键的支持也很友好,@TableId(type = IdType.INPUT)就能搞定。

2.3 选课冲突检测的三层保护

选课系统里最容易出问题的一个点就是:学生选了一门课,又选了另一门课,结果两门课上课时间相同。这个问题在数据库层面很难直接通过字段约束解决,因为“上课时间”是一个文本字段,比如“周一第3-4节”,数据库不知道A课和B课是否冲突。常规做法是分三层去控制:

第一层是前端校验:拿到学生的已有课程列表,和待选课程对比上课时间,有重叠就直接弹窗提示; 第二层是后端接口校验:这是最关键的,因为前端可以绕过,后端必须在事务里查出学生的已有课程,逐一比对上课时间; 第三层是数据库兜底:对选课表建立student_id和course_id的联合唯一索引,保证同一个学生选同一门课只能有一条记录。

时间冲突检查注意一个细节:课程表里最好把上课时间拆成两个字段,比如week_day(星期几)和class_section(第几节到第几节),而不是直接存“周一第3-4节”这种字符串。拆开之后,冲突判断就可以通过星期几相等且节次区间重叠来判断,用代码写条件判断也比解析字符串靠谱得多。

这个设计如果你能在数据库设计说明书里写清楚,答辩时讲出来,绝对是一个加分点——因为这体现的是你对业务冲突场景的理解,而不是只会照搬教程。

3. 后端SpringBoot实现:接口设计和核心业务的落地

3.1 项目初始化和依赖选型

我用的是SpringBoot 2.7.x版本,配合MyBatis-Plus 3.5.x、MySQL 8.x、JDK 1.8。为什么不用SpringBoot 3?因为3.x最低要求JDK 17,很多学校机房电脑上的JDK版本还是1.8,你写的时候也许没问题,但答辩演示时环境不兼容就糟糕了。版本选择遵循“能稳定跑起来的版本,就是好版本”,不要在毕设里去追新。

依赖方面,核心需要引入这些:spring-boot-starter-web、mybatis-plus-boot-starter、mysql-connector-java、lombok、spring-boot-starter-validation,以及一个JWT库(我用的是jjwt 0.9.1)。有两个点容易踩坑:lombok在JDK 8版本下必须用1.18.24或更低版本,高版本会报不兼容;jjwt较老版本对JDK模块化不太友好,如果你用JDK 11以上,可能需要换用io.jsonwebtoken:jjwt-api:0.11.5那套新API。

项目包结构上,我习惯按业务模块分包而不是按技术层分包,大概长这样:

com.example.selectcourse ├── config // 配置类,跨域、拦截器删除线等 ├── controller // 接口层 ├── service // 业务层 ├── mapper // 数据访问层 ├── entity // 实体类 ├── common // 公共返回结果、异常处理、常量 └── utils // JWT工具类等

按业务模块分包(比如把student、teacher、course、selection分别作为子包)也可以,看个人习惯。对毕设来说,按技术层分包更容易向老师解释“三层架构”的概念,而且代码量不算大,不会乱。

3.2 统一返回结果和全局异常处理

前后端分离项目里,后端接口返回的数据格式最好统一,方便前端axios封装后统一处理。我这里给一个非常常见的约定:

{ "code": 200, "message": "操作成功", "data": { } }

code为200表示成功,其他值表示各类业务异常。写一个Result类,提供静态方法Result.success(data)、Result.error(msg),所有controller都返回Result,前端拿到数据后先判断code是否为200,再决定是否把data渲染到页面上。

然后是全局异常处理,这个往往被很多初学者忽略,导致接口报错时返回一堆英文堆栈信息给前端。用@RestControllerAdvice加@ExceptionHandler可以统一捕获业务异常和系统异常,业务异常比如“课程容量已满”“选课时间冲突”,在service层直接throw new BusinessException("具体原因"),前端拿到这个message直接弹提示框,体验会好很多。我现在还记得第一次做完这步时,前端同学说“这次报错信息终于不是500了”,那种感觉真的很爽。

3.3 JWT登录认证和拦截器配置

登录认证我推荐JWT方案,原因很简单:毕设项目不需要引入Spring Security全家桶,那套东西功能强大但配置复杂,对一个小型选课系统来说属于杀鸡用牛刀,还容易把自己绕晕。JWT的逻辑很直白:

  • 用户提交账号密码,后端校验通过后生成一个token返回给前端;
  • 前端把token存在localStorage里,之后每次请求都在请求头加Authorization: token;
  • 后端拦截器里解析token,能读到用户信息就放行,读不到或无token就返回401状态码,前端收到后跳转登录页。

JWT里我一般只放三个信息:userId、role(角色)、expire时间。不放密码、不放手机号这些敏感信息,因为JWT只是签名不是加密,别人拿到token可以解码看到payload内容。

拦截器这里容易出的问题是:放行路径配置不对,导致登录接口本身也被拦截,或者静态资源被拦截。我自己的做法是,配置一个白名单集合,包含/login、/captcha、/error这些路径,其他所有接口默认拦截。为了调试方便,可以先用一个简单的注释开关控制拦截器是否生效,联调时如果发现“莫名其妙的所有接口都401”,先检查一下排除路径写对没有。

3.4 选课接口的核心业务逻辑

选课是系统的核心接口,这段代码也最能体现你的业务思考。我先给出完整流程:

@Transactional(rollbackFor = Exception.class) public Result selectCourse(Long studentId, Long courseId) { // 1. 查课程信息 Course course = courseMapper.selectById(courseId); if (course == null) { throw new BusinessException("课程不存在"); } // 2. 校验容量 if (course.getSelectedCount() >= course.getCapacity()) { throw new BusinessException("课程容量已满"); } // 3. 查学生已有的选课记录 List<StudentCourse> selectedList = studentCourseMapper .selectList(new LambdaQueryWrapper<StudentCourse>() .eq(StudentCourse::getStudentId, studentId)); // 4. 检查是否重复选课 boolean duplicate = selectedList.stream() .anyMatch(sc -> sc.getCourseId().equals(courseId)); if (duplicate) { throw new BusinessException("不能重复选课"); } // 5. 检查上课时间冲突 for (StudentCourse sc : selectedList) { Course selected = courseMapper.selectById(sc.getCourseId()); if (timeConflict(selected.getWeekDay(), selected.getSection(), course.getWeekDay(), course.getSection())) { throw new BusinessException("上课时间冲突:" + selected.getCourseName()); } } // 6. 插入选课记录并更新已选人数 StudentCourse sc = new StudentCourse(); sc.setStudentId(studentId); sc.setCourseId(courseId); sc.setSelectTime(new Date()); studentCourseMapper.insert(sc); course.setSelectedCount(course.getSelectedCount() + 1); courseMapper.updateById(course); return Result.success(); }

这段逻辑的关键在于事务:插入选课记录和更新已选人数这两个操作必须放在同一个事务里,要么同时成功,要么同时回滚。我用了@Transactional(rollbackFor = Exception.class),注意rollbackFor必须写上,否则默认只能拦截RuntimeException,而如果你在事务里抛的是自定义的受检异常,事务会完全不生效。

关于并发,如果你的毕设需要在文档里体现“高并发处理意识”,可以再加一个乐观锁字段version,更新已选人数时用update course set selected_count = selected_count + 1, version = version + 1 where id = ? and version = ?,如果更新行数为0就说明数据被别人改了,回滚并提示“选课人数发生变化,请重试”。这个点讲出来,老师会觉得你考虑到了实际生产中的超卖问题,比单纯的增删改查有深度得多。

4. 前端Vue实现:路由、状态管理和选课页面交互

4.1 前端项目结构和依赖

前端我推荐用Vue 3 + Vite + Element Plus,不要用Vue 2了,毕竟Vue 2已经停止维护,Vite也比webpack那套开发体验快得多。如果你手里拿到的是Vue 2 + Element UI的旧源码,可以继续用,但自己新建项目时建议直接用Vue 3。

项目基本结构:

src ├── api // 接口请求封装 ├── assets // 静态资源 ├── components // 公共组件 ├── router // 路由配置 ├── store // Pinia状态管理 ├── utils // request封装,token处理 ├── views // 页面视图 │ ├── login.vue │ ├── admin │ ├── teacher │ └── student └── App.vue

Element Plus是按需引入还是全量引入?毕设项目我建议全量引入。虽然按需引入能减小打包体积,但多一步配置,而且毕设项目对性能要求没那么高,全量引入省心,老师在演示时也不会因为一个组件没注册导致页面空白。

4.2 Axios封装和路由守卫

axios封装的核心就是把baseURL配好、token自动加上、响应统一拦截处理。我这里给出核心配置:

// utils/request.js import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: '/api', // 开发环境通过Vite代理到后端 timeout: 10000 }) request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers.Authorization = token } return config }) request.interceptors.response.use( response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message || '请求失败') return Promise.reject(new Error(res.message)) } return res.data }, error => { if (error.response && error.response.status === 401) { ElMessage.error('登录已过期,请重新登录') localStorage.removeItem('token') router.push('/login') } else { ElMessage.error('服务器异常,请稍后重试') } return Promise.reject(error) } ) export default request

这里有个细节,baseURL配成/api而不是直接配http://localhost:8080,是为了配合Vite的代理配置。开发时前端跑在5173端口,后端跑在8080端口,如果直接跨域请求,浏览器会报CORS错误。解决方式有两种:后端加@CrossOrigin配置CORS,或者前端配置Vite代理。我推荐Vite代理,因为这样做后端不需要额外处理跨域,生产部署时也比较统一。

Vite代理配置,在vite.config.js里加一段server.proxy即可:

server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } }

这样前端请求/api/course/list会代理到http://localhost:8080/course/list,跨域问题直接被开发服务器解决了。

路由守卫方面,用beforeEach判断localStorage里有没有token,没有token直接跳转登录页。同时根据路由的meta.role字段来判断角色的访问权限,比如/admin开头的页面只允许管理员访问,这样即使学生手动输入URL也进不了管理页面。

4.3 选课页面的展示与交互

学生选课页面是系统里最常被展示的功能。我用Element Plus的el-card加el-table实现一个课程列表,每行显示课程名称、教师、学分、上课时间、容量和已选人数,最后一列是一个“选课”按钮。

这里有一个很实用的设计:已选人数达到容量时,按钮要变成disabled状态,颜色变为灰色并显示“已满”;如果该课程已经被当前学生选了,按钮变成“已选”并禁用;有空位但时间冲突的,可以不做前置判断,让用户点击后由后端返回错误信息——因为时间冲突要根据学生的全部选课记录来判断,前端判断逻辑会和后端重复,没必要。

前端点击选课后,调接口,成功就刷新当前列表,同时更新该行的已选人数,成本很低。退课则加上一个确认弹窗,用ElMessageBox.confirm提示“确定要退选这门课程吗?”,防止误操作——这个细节在演示时会被老师当作“用户体验考虑”的加分项。

教师端页面相对简单,教师登录后看到“我的课程”列表,点击某门课可以查看选课学生名单,然后给每个学生录入成绩。这里对权限的控制要注意:教师只能看到自己创建的课程,查询时必须在SQL里带上teacherId条件,不能用一个课程列表给所有教师共用,否则就是越权访问。

4.4 前端调试的常用技巧

前端出问题时,第一件事是按F12打开开发者工具,看Network面板里请求的实际请求地址、请求头和响应体。我遇到最多的情况是:接口明明通了,但页面不更新,十有八九是数据格式没对上,比如后端返回的是Result包装对象,前端却直接当数组用了;或者字段名大小写不一致,MyBatis-Plus的驼峰映射没开启,导致courseName在JSON里变成了coursename。

如果页面白屏,先看Console有没有报错。Vue项目里最常见的几个报错:找不到组件、读不到undefined的属性、路由未匹配到。这些大多数是代码笔误,根据报错信息定位到具体文件行数并不难。还有一种情况:登录进不去,一直跳回登录页,检查一下axios拦截器是不是把业务code非200当成401处理了,或者路由守卫里判断token的逻辑写反了——这种问题我调试了整整一个下午才想起来去看路由守卫的代码。

5. 联调、部署和文档撰写:从能跑到能交的最后一公里

5.1 本地联调的完整流程

前后端都写完之后,第一次联调是最痛苦的。常规流程是这样的:先在本地启动MySQL,执行项目提供的sql脚本,确认表都建好了;再启动SpringBoot后端,用Postman或Apifox测试登录接口,确认返回token;最后启动前端Vite开发服务器,浏览器打开登录页,输入测试账号走一遍完整流程。

联调这里我建议一定要准备几个测试账号:管理员、教师、学生各至少一个,并且提前造好数据,比如至少5门课程、一个满足条件的学生账号。我就是因为只在数据库里插了一条admin账号,第一次演示时临时想注册学生账号,发现注册接口一堆问题,场面一度非常尴尬。

数据库SQL脚本建议直接用Navicat或MySQL Workbench导出一份完整的sql文件,包括建表语句和初始数据。这份sql文件要随源代码一起提交,并且文档里要写清楚“导入数据库脚本的前提是已经安装了MySQL 8.x并设置了utf8mb4编码”。很多同学数据库报错就是因为建库时没有指定utf8mb4,中文字段全是乱码或直接报“Incorrect string value”。

5.2 生产环境部署方案

毕设答辩一般是在自己电脑上演示,但有些学校要求部署到服务器或者提交一个可演示的URL,那就要掌握最基本的部署流程。

后端的SpringBoot项目打包成jar包,在项目根目录执行mvn clean package -DskipTests,然后java -jar target/xxx.jar启动。注意如果你用了自定义的端口或数据库连接配置,可以在启动命令上加参数覆盖,比如java -jar xxx.jar --server.port=8081,部署文档里最好写清楚这些可调整的配置项。

前端打包是npm run build,生成dist目录,里面是纯静态文件。部署方式有两种:一种是把dist塞进Nginx,配置一个server块指向dist目录,同时把/api反向代理到后端端口;另一种简单粗暴点,直接把dist文件夹放到后端项目的src/main/resources/static目录下重新打包,让SpringBoot同时提供静态页面和接口服务。

第二种方式对毕设来说反而更稳妥,因为不用单独部署Nginx,一个jar包跑起来就什么都有了。但有个坑:前端build时baseURL如果写成/api,并且用Vite代理代理掉的,打包后代理就不存在了,你需要把请求地址改成后端接口的真实地址,或者在SpringBoot里加一个/api开头的转发Controller。最省事的方案是,把前端请求的baseURL配成和后端接口路径完全一致的一个相对路径,比如后端接口是/course/list,前端baseURL就配置成空字符串,然后生产环境直接把dist放进static目录,开发环境用Vite代理配一个/api前缀。如果这块觉得绕,最简单的办法就是:本地演示永远用Vite开发模式跑前端,不打包。

5.3 文档撰写的核心逻辑

毕业设计的文档,重要性完全不亚于代码。但很多人的文档是怎么写的呢?从网上拼拼凑凑,把系统功能描述一遍,代码贴一堆,答辩时老师翻了两页就放下了——因为没看到“为什么”的论述。

我的经验是,文档里至少要在三个地方体现你的独立思考。第一处是技术选型论证:为什么用SpringBoot而不是SSH,为什么用Vue而不是JSP,从开发效率、维护成本、市场需求三个角度说。第二处是数据库设计说明:每一张表的用途、表与表之间的关系、为什么选课表要加联合唯一索引,这些都要用ER图和文字说清楚。第三处是核心功能的设计思路:选课接口的时序图、事务处理方式、并发冲突的解决方案,这部分的文字量可以少,但一定要逻辑闭环。

文档结构上按:绪论、需求分析、系统设计、数据库设计、系统实现、系统测试、总结与展望这个标准模板来写,这是学校普遍接受的格式。测试部分不要只写“功能正常”,要给出一张测试用例表,包括测试项、测试步骤、预期结果、实际结果,最后所有用例都打勾通过——这个表格一出来,文档的严谨性立刻就拉上去了。

5.4 答辩时的高频问题清单

答辩前最好把下面这些问题自己过一遍,我整理了这几年被问得最多的:

  • 为什么选课表要用联合唯一索引,它解决了什么问题?
  • 选课时如果两个学生同时选最后一门课,你怎么保证不超卖?
  • JWT登录和传统的Session登录有什么区别、为什么选JWT?
  • 前端路由守卫的原理是什么、放在前端做权限控制安全吗?
  • 项目里有没有遇到跨域问题,是怎么解决的?
  • 课程时间冲突你是怎么判断的、如果上课时间跨多节怎么处理?
  • 如果选课人数很多,这个系统有哪些可以优化的点?

前两个问题涉及并发和数据库约束,第三个涉及认证机制,第四个考的是前端安全认知,需要你诚实回答“前端控制权限有限,真正的安全控制应该在后端”,这个答案比强行圆场要加分。第七个问题是开放性提问,你可以说加Redis缓存热点课程数据、用消息队列削峰、接口限流等,不用真的做出来,但要让老师知道你有扩展意识。

实操中容易踩的坑和我的解决经验

最后集中说几个我在做这类项目时实实在在遇到过的坑,都是网上教程不会主动提醒你的细节。

第一个坑是Maven依赖下载极慢或直接卡住。解决方式是使用阿里云镜像,在settings.xml里配置mirror节点,这一步能节省大量等待时间。如果你用的是2023年之后的IDEA版本,自带的Maven可能默认走maven中央仓库,网络不好时项目创建半天没反应,换镜像后明显改善。

第二个坑是删除线MySQL时区问题。SpringBoot连接MySQL时,jdbcUrl里的serverTimezone参数不配或者配错,启动或查询时就会报"The server time zone value"错误。我一般直接写jdbc:mysql://localhost:3306/select_course?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai,一串全带上,省得后面来回折腾。

第三个坑是前端表单验证。Element Plus的form组件需要给表单项设置prop属性,并且rules里的字段名必须和表单数据字段名一致,否则验证规则完全不生效。这个问题非常隐蔽,因为不报错、页面也没异常,就是点了提交没反应,很多人排查半天才发现是prop写错了。

第四个坑是课程表里如果有删除操作,删除课程前必须检查该课程是否已经有人选了,有人选的课程不能物理删除,否则选课表里会出现悬空引用。最稳妥的做法是不提供物理删除功能,而是用一个status字段标记该课程为“已下架”,学生端查询时自动过滤掉下架课程。这个设计给数据库保留完整历史,答辩时也讲得通。

其实做完这个项目你会发现,编码本身占用的时间只占三成,剩下七成都在“和环境搏斗”以及“理解业务边界”。这些经验学校不会教你,网上也很难有人系统总结,但都是真实项目里最值钱的东西。希望这篇东西能帮你在整个过程中少烧掉几次CPU,不管你是打算自己从零写,还是拿到源码后准备改造,把上面的设计思路和避坑清单过一遍,心里都会踏实很多。

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

书匠策AI深度评测:六大核心能力拆解论文写作全流程

写完近三年论文、帮几十个朋友调过论文工作流之后&#xff0c;我对“AI写作工具”这五个字的态度从“不靠谱”变成了“真香”&#xff0c;但前提是你得用对路子。如果你现在正在为开题报告、本科毕业论文、硕士论文甚至期刊小论文发愁&#xff0c;大概率听过“书匠策AI”这个名…

作者头像 李华
网站建设 2026/9/9 15:38:08

办公、研发、关基部署,2026国产操作系统该怎么选

伴随国内数字基建持续升级与信创产业纵深落地&#xff0c;操作系统作为连接芯片、整机、数据库、中间件及应用系统的底层枢纽&#xff0c;其自主可控水平、行业适配深度与长期运维能力&#xff0c;已成为政企、科研及关键基础设施用户不可回避的选型核心。2026年国产操作系统市…

作者头像 李华
网站建设 2026/9/9 15:37:57

eBay矩阵买家号运营全攻略:环境隔离、养号技巧与风控规避

1. 为什么大家都在做ebay矩阵买家号先说个现象&#xff0c;最近两年跨境电商圈子里&#xff0c;做ebay的卖家几乎都在悄悄布局买家号矩阵。原因很简单&#xff0c;平台的流量分发机制越来越偏向于有真实互动、有订单沉淀的Listing&#xff0c;而新店铺或者新品想要在搜索结果里…

作者头像 李华
网站建设 2026/9/9 15:36:56

无定义无证据,AGI何时能来?黄仁勋与Gary Marcus的争论

老黄说“AGI要来了”&#xff0c;Gary Marcus 当场开怼&#xff1a;定义呢&#xff1f;证据呢&#xff1f;先交代背景&#xff1a;最近 AI 圈又吵起来了。英伟达的 Jensen Huang 在一次公开场合放话&#xff0c;说通用人工智能&#xff08;AGI&#xff09;会在很短时间内到来&a…

作者头像 李华
网站建设 2026/9/9 15:36:43

ABAQUS中UMAT/VUMAT材料损伤断裂二次开发详解:从弹塑性到单元删除

做材料损伤断裂仿真这么多年&#xff0c;有个体会越来越深&#xff1a;ABAQUS内置材料库再强大&#xff0c;也总有不够用的时候。实测数据拿在手里&#xff0c;本构关系想表达却找不到现成模型&#xff0c;或者内置模型的软化段、损伤演化规律跟试验曲线对不上&#xff0c;这时…

作者头像 李华
网站建设 2026/9/9 15:36:34

WeClone:3步把你的微信聊天记录,变成一个微信AI助手

WeClone&#xff1a;3步把你的微信聊天记录&#xff0c;变成一个微信AI助手 【免费下载链接】WeClone &#x1f680; One-stop solution for creating your AI twin from chat history &#x1f4a1; Fine-tune LLMs with your chat logs to capture your unique style, then b…

作者头像 李华