ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于WEB的操作系统课程教学网站:Java毕业设计源码部署与答辩全解析

基于WEB的操作系统课程教学网站:Java毕业设计源码部署与答辩全解析

简介:基于WEB操作系统课程教学网站的设计与实现是一份完整的Java毕业设计源码包,面向计算机相关专业毕业生、Java Web学习者及需要完成课程设计的在校学生。系统覆盖课程讲义、视频教程、在线测试、答疑解惑、作业提交、成绩查询等核心模块,可帮助读者掌握JSP+Servlet+数据库的典型开发流程,并作为毕设项目直接参考或二次开发。压缩包共502个文件,约8.57MB,主要包含125个JSP页面、7个Java源文件、27个class文件、6个CSS样式、5个db数据库文件及11个doc说明文档,另有大量gif图片和bak备份文件,目录结构清晰,便于按模块查看和学习。已有38人浏览学习。从中可获取完整站点源代码、数据库初始脚本、页面样式及开发文档,既能对照学习前端JSP与后端Java的交互实现,也能直接部署运行查看在线测试、成绩管理等具体效果,适合用于毕业设计讲解、项目复现和功能扩展。

1. 这份java毕业设计源码:基于WEB的操作系统课程教学网站能直接跑吗

如果你手上正好压着一个“基于WEB操作系统课程教学网站”的java毕业设计源码包,而且马上要交课程设计或者准备答辩,那我的建议只有一句:先别急着改代码,把压缩包里的结构、SQL和配置看明白,再启动一次看能不能跑通。很多人拆包第一反应是找“登录按钮在哪”,结果在依赖、数据库和端口上浪费一个周末。这个源码包解决的是校内操作系统课程从线下搬到线上的问题:学生能看课程章节、下载课件、提交作业,教师能上传资料、批改作业,后台还能管理用户和权限。适合三类人:一是拿它当毕业设计底子的学生,二是课程设计想少走弯路的人,三是想快速搭一个教学类内容管理站点看看Java Web项目实际怎么组织的开发者。但它是源码包,不是黑匣子,你得知道它怎么被组织,才能动手改。

2. 拆包先看结构:技术栈、数据库和五个核心模块

拿到zip先解压,不要直接双击打开。我一般会按顺序做三件事:看pom.xml确认技术栈,看application.yml确认连的是什么库,看SQL脚本确认表设计。这三样都清楚了,项目在你脑子里就不是一团线,而是能改能修的东西。

2.1 技术选型:为什么这套Spring Boot + MyBatis组合更对路

这个毕业设计源码包用的是Spring Boot + MyBatis + MySQL,前端渲染走Thymeleaf模板,没有把前后端拆成两个项目。这个选型在课程设计、毕业设计里非常主流,原因有三个。

第一,Spring Boot内置了Tomcat,不需要单独装外部容器,双击启动类就能跑,对答辩演示很友好。第二,MyBatis把SQL写在Mapper XML里,你可以直接在日志里看到每一条查询,导师问起来也容易解释“为什么这里要连表”。第三,MySQL是学校机房最常见的数据库,环境不折腾。

相比之下,老式的JSP + Servlet项目虽然也能跑,但写页面时要把Java和HTML混在一起,改个样式得重启,代码看起来累赘。而这个项目里Controller只负责接口跳转,MyBatis只管数据,Thymeleaf只渲染页面,职责分得比较开。代码量看起来多,但每个文件干的事很单一。

有一点要提醒:如果你打开后看到的是Spring Boot,但引了很多类似SSH遗留的XML,那就要注意。正常毕业设计源码里,配置应该集中在application.yml和config包下,而不是散落在一堆XML。这个包的结构是标准的Spring Boot布局,src/main/java下面按controller、service、mapper、entity分包,src/main/resources下面有static、templates和mapper目录。第一次看的时候按照这个路径走,不容易迷路。

2.2 数据库设计:五张核心表搞定用户、课程、资源、作业

课程教学网站的数据核心,说到底是“谁能看什么,谁交了什么,谁批了什么”。里面最基础的是用户表,它把管理员、教师、学生通过一个role字段区分开,没必要开三张表。其次是课程表,一门课对应多个章节,所以章节表里要存course_id作为外键。然后是资源表,存课件和资料的原始文件名、存储路径、大小,上传人ID,这样前台才能列表展示和下载。最后是作业表,它除了作业内容和学生ID,还会有status字段标记“未提交、待批改、已批改”。

如果你要手动建库,核心表可以这样起步:

CREATE TABLE `t_user` ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(100) NOT NULL, `real_name` varchar(50) DEFAULT NULL, `role` tinyint(4) NOT NULL COMMENT '1管理员 2教师 3学生', `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `t_course` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(100) NOT NULL, `teacher_id` int(11) DEFAULT NULL, `description` text, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

这里的逻辑说明:t_user里role字段用数字而不是字符串,是为了程序判断简单,但你在页面上要显示“管理员/教师/学生”时,必须写一个字典转换。t_course里teacher_id关联t_user,说明这门课由哪个教师负责。

继续看资源表:

CREATE TABLE `t_resource` ( `id` int(11) NOT NULL AUTO_INCREMENT, `course_id` int(11) NOT NULL, `chapter_id` int(11) DEFAULT NULL, `file_name` varchar(200) NOT NULL COMMENT '原始文件名', `store_path` varchar(255) NOT NULL COMMENT '磁盘存储路径', `file_size` bigint(20) DEFAULT NULL, `upload_user` int(11) DEFAULT NULL, `create_time` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

注意store_path只存相对路径,不要把本地磁盘的完整路径写进去,否则换一台电脑跑项目,路径全失效。这点很多新手容易忽略。上传的课件如果被删除,资源表里也要及时清理记录,这部分代码在service层里处理。

作业表的字段会稍微多一点,除了id、assignment_id、student_id、content外,还要有status、score、comment、submit_time、review_time。assignment_id指向教师发布的作业批次,student_id指向学生,content可以存富文本提交内容,file_path存附件。如果设计成每个学生一条记录,那次数就是学生数乘以作业数,数据量不大,但对于毕业设计足够展示关联查询和权限控制。

2.3 功能模块划分:前台学习与后台管理的权限边界

一个能答辩的教学网站,功能不是越多越好,而是要让评委一眼看出你能处理角色权限和业务流程。这个包划分成了五个模块,我按自己的理解整理如下。

模块角色核心操作
用户登录注册所有用户用户名密码校验、角色写入Session
课程学习学生查看课程列表、章节详情、下载资源
资源管理教师/管理员上传课件、删除资源、管理章节
作业管理学生/教师学生提交作业、教师批改并打分
系统管理管理员用户管理、课程审核、公告发布

权限边界靠的是登录拦截器。学生访问后台资源管理URL时会被拦截跳回登录页,教师不能打开用户管理页面,管理员则拥有全部路径。这个设计在代码里体现为拦截器配置,后面第四章会详细讲。如果你拿到的源码没有权限拦截,那会在答辩时被问住。所以先确认这个资源里有没有AuthInterceptor或者InterceptorConfig这一类文件。

我建议你在读功能模块时,不要只看Controller里有没有方法,而是跟着一条主线走:学生登录后进入课程列表,点开一个章节,看到课件下载链接和作业提交入口。这一条链路涉及CourseController、ResourceController、HomeworkController三个控制器和对应的Service、Mapper。把这套链路读顺,比每个页面都点一遍有用得多。

3. 从源码包到本地跑起来:导入、配置、初始化三步走

跑通一个Java毕业设计项目,最耗时间的不是改代码,而是环境和数据不一致。下面这套流程我每次拆新包都用,能省掉大量“明明代码没问题”的尴尬。

3.1 环境准备:JDK、Maven、MySQL与IDEA导入后的等待

环境版本建议:JDK 1.8,Maven 3.6以上,MySQL 5.7或8.0,IDEA 2020之后版本。Spring Boot对JDK版本比较敏感,如果你用了JDK 11但源码里没升到Spring Boot 2.4以上,可能遇到依赖不兼容。这个包默认按JDK 1.8写,所以不用刻意追新。

打开IDEA后选择File -> Open,选中解压后的目录,等待Maven识别pom.xml。IDEA会提示导入Maven工程,选“Enable Auto-Import”让依赖自动下载。第一次下载可能需要几分钟,这取决于网速和本地Maven仓库是否还有缓存。如果出现pom文件报红,先更新Maven项目,别急着改代码。

导入后我习惯先把项目跑一次空启动,看看能不能不连数据库就启动到什么程度。通常Spring Boot会在数据源初始化时报错,不用慌,只要错误信息里出现Failed to configure a DataSource,就说明代码本身编译通过,问题出在配置。还有一种常见情况是Spring Boot启动时连不上数据库,报Communications link failure,那基本就是MySQL没启动、端口不对或账号密码错误,按下一步检查配置即可。

启动类一般在src/main/java下的Application.java,右键运行。如果你用的是命令行,也可以执行mvn spring-boot:run。不过IDEA的Run面板能看到更完整的控制台输出,比如哪个Mapper加载失败、哪个Bean没注入,这些信息对排错很有用。

3.2 配置文件:application.yml里的数据源与上传路径

打开src/main/resources/application.yml,里面最常改的是数据源、端口、上传大小三个地方。下面是一个典型的配置,我把容易踩坑的参数都写出来了。

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/online_course?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver servlet: multipart: max-file-size: 20MB max-request-size: 50MB thymeleaf: cache: false mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.onlinecourse.entity custom: upload-dir: D:/upload/course

这里每一行都有意义。url里的serverTimezone=Asia/Shanghai解决时区报错,allowPublicKeyRetrieval=true是应对MySQL 8.0公钥检索问题。username和password一定改成你自己本机数据库的账号,否则连接会失败。max-file-size控制单个课件上传上限,如果老师传一个300MB的视频,这个值要调大,但也要考虑服务器磁盘和带宽。custom.upload-dir是自定义的上传根目录,我建议不要放在项目内部,因为每次重新打包会清掉。

参数说明:如果你用的是MySQL 5.7,driver-class-name可以保留com.mysql.cj.jdbc.Driver,也可以换回com.mysql.jdbc.Driver,但新驱动兼容5.7,没必要改。type-aliases-package不是必须,但写了之后MyBatis映射文件里写resultType时可以不写全限定类名。thymeleaf.cache设为false能避免你改完页面还要重启服务才能看到效果,这个配置对调试阶段特别友好,正式部署时再改成true。

如果你在部署时发现文件上传后无法下载,优先检查custom.upload-dir目录是否存在。Windows下写法是D:/upload/course,Linux下是/home/ubuntu/upload/course,路径分隔符不要用反斜杠,否则在Linux环境会失败。目录不会自动创建的话,你需要在启动前手动建好,或者让代码在第一次上传时mkdirs()。

3.3 初始化数据库:执行SQL脚本并验证默认账号

源码包里一般会带一个sql或者docs目录,里面放着online_course.sql。用命令行或数据库工具导入都行,我习惯命令行,因为不容易漏看输出日志。

mysql -uroot -p123456 create database online_course default character set utf8mb4; use online_course; source D:/yourpath/online_course.sql;

执行完成后,重点观察有没有报错信息,比如“Table already exists”说明库重复,可以先把库drop掉再重建。导入成功后在t_user表里看看默认账号,常见做法是管理员连admin/admin123,教师连teacher/123456,学生连student/123456,具体以文档为准。

然后重新启动Application类,看到“Started Application in x.xxx seconds”就说明基础环境没问题。这时候打开浏览器访问http://localhost:8080,能跳到登录页就证明项目已经跑通了。如果端口被占用,回3.2把server.port改成8081再启动。

这里有一个容易被忽略的环节:导入SQL后查看一下t_user表里是否有密码为加密后的hash值,比如60位字符串。因为有些源码用MD5或者BCrypt存密码,那么登录时也会有一个对应的加密处理器。你如果在表里看到明文密码,反而说明这个包没有做加密,安全性不是重点,但这会在答辩时被问“密码为什么明文存储”,建议你提前想好怎么回答,或者干脆换成BCrypt加密后再展示。

4. 核心功能解析:登录拦截、文件上传与作业批改

跑起来之后,建议先把几个核心功能读一遍。因为答辩重点就是这些。读代码不是从头到尾读,而是从Web层往下读,读到Service和Mapper就停,解析一个请求是怎么从浏览器走到数据库再返回的。

4.1 登录拦截器:拦截URL并放行登录页

这个项目的权限控制是通过自定义HandlerInterceptor实现的。核心代码大致长这样。

@Component public class AuthInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { User user = (User) request.getSession().getAttribute("loginUser"); String uri = request.getRequestURI(); if (uri.startsWith("/login") || uri.startsWith("/css/") || uri.startsWith("/js/")) { return true; } if (user == null) { response.sendRedirect("/login"); return false; } if (uri.startsWith("/admin/") && user.getRole() != 1) { response.sendRedirect("/index"); return false; } return true; } }

逻辑说明:第一步从Session取loginUser,这个变量在登录成功时被塞进去。第二步放行登录页和静态资源,否则登录页本身会被拦截导致死循环。第三步判断用户是否为空,是则重定向。第四步针对/admin开头路径校验角色,如果不是管理员就丢回首页。

这里有个很重要的设计:角色判断直接用user.getRole(),而不是查数据库,因为Session里已经存了用户对象。如果你要改这个逻辑,记得登录成功返回对象时要全部字段都set进去,不要只存一个id,否则后面查到NPE。还有一点需要注意,拦截器只拦截它注册的路径模式,源码里通常只拦“/**”,但会排除登录接口。你改成注册多个拦截器时,要确保顺序对,否则静态资源可能被拦。

注册拦截器一般在WebMvcConfigurer实现类里:

@Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private AuthInterceptor authInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns("/**") .excludePathPatterns("/login", "/logout", "/register"); } }

参数说明:excludePathPatterns里除了登录页,一般还要把/error排除掉,否则Spring Boot错误页会被拦截,报错信息显示不出来。如果你加了静态资源目录映射,不排除css/js也不会导致死循环,因为前面已经按uri放行。但把/images、/upload也加在排除列表里是更稳妥的做法,尤其是上传目录如果放在磁盘根目录,请求路径是/upload开头,拦截器看到未登录就跳登录页,体验很差。

4.2 课程资料上传:MultipartFile与磁盘存储注意事项

文件上传功能是教师端的核心点。Controller里接收MultipartFile后,要做的不只是存文件,还要记录原始文件名和生成随机文件名,避免同名覆盖。常见实现是这样:

@PostMapping("/resource/upload") public String upload(@RequestParam("file") MultipartFile file, @RequestParam("courseId") Integer courseId, @RequestParam("chapterId") Integer chapterId) throws IOException { String dir = uploadDir + "/" + courseId + "/" + chapterId; File realDir = new File(dir); if (!realDir.exists()) { realDir.mkdirs(); } String originalName = file.getOriginalFilename(); String suffix = ""; if (originalName != null && originalName.contains(".")) { suffix = originalName.substring(originalName.lastIndexOf(".")); } String newName = UUID.randomUUID().toString().replace("-", "") + suffix; file.transferTo(new File(realDir, newName)); resourceService.save(courseId, chapterId, originalName, newName, file.getSize(), currentUserId()); return "redirect:/course/" + courseId; }

逻辑说明:uploadDir来自配置里的custom.upload-dir,我用它拼courseId和chapterId作为子目录,防止所有文件堆在一个目录里。originalName保留用户的原始名字,展示名用这个;newName是UUID生成的名字,存储和下载时用。file.transferTo是Spring封装的方法,比直接FileOutputStream更安全,但要注意目标目录必须存在,否则会报NoSuchFileException。

参数说明:UUID的新文件名里没有中文和空格,能避免URL编码问题,也防止跨平台时路径解析错误。如果你需要保留原文件扩展名,就必须通过lastIndexOf(".")去截取,不能直接拿originalName,因为有些浏览器会把完整路径当作文件名。上传成功后,重定向回课程详情页,这样用户一刷新就能看到新资源。

这个功能容易翻车的地方在于:如果你把文件存到D:/upload/course,但浏览器访问的是/upload/course,那是绝对访问不到的。Spring Boot默认只映射classpath:/static,不映射磁盘目录,所以还需要额外加一个静态映射配置,这点在第五章会讲。另外,上传时校验文件类型也很重要,如果只放行jpg、pdf、zip、docx,那就要避免用后缀白名单,因为Windows的文件名可能大小写不一样,要用lastIndexOf拿后缀后统一转小写再判断。

如果你需要对上传做更细的权限控制,可以在Controller方法上用@PreAuthorize("hasRole('TEACHER')"),前提是项目整合了Spring Security。没有Security的话,就依赖拦截器先拦,再在Controller内部判断user.getRole() == 2 || role == 1。我一般会把上传方法单独放在以/teacher/开头的路径下,这样拦截器配置一次,后续维护也清楚。

4.3 作业提交与批改:用状态字段驱动的前后端交互

作业模块的设计核心是状态字段。学生在作业页面提交后,作业记录的status从0变成1,表示待批改。教师打开批改列表只能看到status=1的记录,批改完之后更新分数和评语,status变成2。这个流程在数据库层面看起来非常清晰。

-- 学生提交作业 UPDATE t_homework SET content = ?, file_path = ?, status = 1, submit_time = NOW() WHERE student_id = ? AND assignment_id = ?; -- 教师批改完成 UPDATE t_homework SET score = ?, comment = ?, status = 2, review_time = NOW() WHERE id = ?;

逻辑说明:第一条SQL是提交动作,status从0变成1,同时记录提交时间和文件路径,这样学生可以重复覆盖前一次提交,重新提交后状态重新变回待批改。第二条SQL是教师在评审页点击批改按钮时做的更新,score和comment不能为空,否则前端校验会拦下来。

参数说明:WHERE条件里student_id和assignment_id必须联合使用,不能只靠主键id,否则同一个学生提交多条作业时会互相覆盖。提交时间用数据库的NOW()而不是Java方面传时间,是为了避免服务器和数据库时区不一致。批改完成后,通常还需要在作业列表页面按学生分组展示平均分和提交率,这部分属于统计SQL,如果源码里没有,可以自己再加上,作用是让答辩展示更完整。

如果你想看“哪些人还没交作业”,可以使用未提交查询:

SELECT s.id, s.username, a.id AS assignment_id FROM t_student s CROSS JOIN t_assignment a WHERE a.course_id = 1 AND NOT EXISTS ( SELECT 1 FROM t_homework h WHERE h.student_id = s.id AND h.assignment_id = a.id );

逻辑说明:用CROSS JOIN生成学生和作业的笛卡尔积,再用NOT EXISTS过滤掉已经提交过的组合,剩下的就是没有提交的学生。这个查询在答辩时非常加分,因为评审喜欢的不是你能读数据,而是你能找“数据里没有的东西”。参数说明:a.course_id要改成你实际课程id,如果作业按章节分,需要再关联章节表。

5. 避坑排查:跑这个源码包最容易翻车的四个地方

说真的,我拆过不少类似的源码包,发现大多数启动不成功都不是代码问题,而是环境和配置问题。下面这几条是最高频的坑,按“现象-原因-解决”写,方便你直接对照。

5.1 MySQL 8.0认证插件导致的数据源连接失败

现象:启动项目时报Public Key Retrieval is not allowed,或者Access denied for user 'root'@'localhost',前者更常见。

原因:MySQL 8.0默认使用caching_sha2_password认证,低版本的JDBC驱动或没有设置allowPublicKeyRetrieval时,无法在首次连接时拿公钥。你拿到的源码如果是两三年以前的包,驱动版本可能还是5.1.46,那就更容易触发这个问题。

解决:在JDBC URL上显式加上两个参数,allowPublicKeyRetrieval=true和useSSL=false,同时确保驱动是com.mysql.cj.jdbc.Driver。如果还不行,就到MySQL里执行ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY '123456';,把root账号切回旧认证方式。这个方法只建议本地测试用,生产环境别乱切。修改完记得重启MySQL服务或者刷新权限FLUSH PRIVILEGES;。

另外,如果你用的是云数据库,host不是localhost,而是公网地址,那么认证方式还可能受白名单和ssl策略影响。公网连接建议加上useSSL=true,不然密码是明文传输的,虽然测试环境无所谓,但答辩时被问到安全策略,还是得能说出区别。

5.2 8080端口被占用:启动失败先确认端口

现象:控制台报Web server failed to start. Port 8080 was already in use.,项目完全起不来。

原因:本机已有服务占用8080端口,可能是你自己启动过的另一个项目,也可能是其他软件。Spring Boot默认端口就是8080,这个包也没改,所以很容易撞车。

解决:最直接的办法是修改server.port,改成8081、8082都能解决问题。但要注意,如果前端页面或接口里写死了8080的跳转地址,改了端口后这些地方全都要一起改。快速查谁占用了端口可以用netstat -ano | findstr 8080(Windows)或者lsof -i:8080(macOS/Linux)。如果确认是残留的Java进程,直接kill掉再启动更省事。我一般会先看占用者是什么程序,再决定是杀进程还是改端口,因为乱杀进程可能把Docker或其他开发工具带走。

在Windows下,PID通过netstat能看到,然后taskkill /PID 1234 /F就能杀掉。在Linux下是kill -9 1234,但不要杀到系统关键进程。如果你经常在本地启动多个项目,建议把端口规划一下:项目A用8080,项目B用8081,避免以后来回改。日志里能看到一行Tomcat started on port(s): 8080 (http),这就是端口已经绑定的证据。

5.3 Maven依赖下载卡死:换镜像源是常规操作

现象:IDEA导入项目后,pom.xml一直报红,或者依赖列表里很多包显示Could not resolve dependencies。

原因:默认中央仓库在国外,网络连接不稳定,或者本地仓库里正好缺某个版本。毕业设计包里的Maven仓库地址如果写的是http://repo1.maven.org,那就更慢了,有些机房甚至直接连不上。

解决:在Maven的conf/settings.xml里加一个镜像配置,我一般用阿里云公共仓库。配置片段如下。

<mirror> <id>aliyun</id> <name>Aliyun Maven Mirror</name> <url>https://maven.aliyun.com/repository/public</url> <mirrorOf>central</mirrorOf> </mirror>

逻辑说明:mirrorOf设为central,意思是把中央仓库的请求都转发到阿里云镜像。改完后在IDEA里执行mvn clean compile,或者直接刷新Maven工程。要注意,这个配置影响的是所有项目,如果公司内部有私服地址,就不要覆盖,否则可能从私服拉不到包。还有一点,JDK版本和Maven版本不匹配也可能导致拉包后编译失败,所以建议本地JDK和源码要求的版本保持一致。

另一个比较隐蔽的问题是IDEA里Maven配置用的User settings file路径不对。你改了settings.xml,但IDEA仍然用的默认路径,那镜像配置根本没生效。校验手段是执行mvn help:effective-settings,看输出里有没有mirror这一段。没有的话就手动在IDEA的Settings -> Maven里指定你修改的那个settings.xml。

5.4 上传的图片显示404:静态资源映射漏配

现象:教师在后台上传课件、图片后,后台列表能显示文件名,但点击预览或前台展示时访问/upload/xxx.jpg返回404。

原因:Spring Boot默认只把classpath:/static/作为静态资源目录,你上传的磁盘目录并不在这个范围里,所以URL找不到文件。

解决:在配置类里加一个自定义资源映射,把/upload/**映射到配置的磁盘路径。参考写法如下。

@Configuration public class WebMvcConfig implements WebMvcConfigurer { @Value("${custom.upload-dir}") private String uploadDir; @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/upload/**") .addResourceLocations("file:" + uploadDir + "/"); } }

逻辑说明:addResourceHandler表示URL中以/upload/开头的请求都交给这个处理器。addResourceLocations表示到本地磁盘的哪个目录去找文件。注意file:前缀不能漏,漏了就变成了classpath下的路径。目录最后的斜杠也要写,否则Spring拼路径时会少一层分隔符。

这条解决后,上传文件就能正常访问。但还有一个隐藏坑:如果你改了端口或者context-path,上传资源的完整URL也会跟着变。例如设置了server.servlet.context-path=/course,那访问地址就变成/course/upload/xxx.jpg。前端在拼接路径时最好用相对路径,或者从服务端获取,不要手写死。还有,如果文件是通过模板引擎渲染的,要用th:src="@{/upload/{name}(name=${resource.filePath})}",这样Spring会帮你处理好context-path前缀。

如果你发现改了配置还是404,先用浏览器直接访问http://localhost:8080/upload/test.jpg,如果返回404,说明映射没生效。检查一下你的配置类是不是没被Spring扫描到,比如放在了没有被主启动类覆盖的包路径下。这种情况在源码包里很常见,config包位置不对,springBoot启动时扫不到,资源映射自然没注册。

6. 进阶技巧:把默认课程内容换成自己的,并通过答辩验证

跑通只是第一步,把项目变成“自己的东西”才是毕业设计的目的。我的习惯是先做三个替换:替换站点的页面标题和Logo,替换默认的课程数据,替换上传目录为本地新目录。这三个做完,项目看起来就跟你自己做的一样了。

替换站点标题很简单,在templates/layout.html或src/main/resources/templates下的公共头文件里,找到<title>和顶部导航的文本,直接改成你的课程名和姓名。需要注意,Thymeleaf模板与普通HTML不同,修改后要重启项目,如果配置了spring.thymeleaf.cache=false,那页面就能热加载,不用每次重启。Logo替换更简单,把static/images下的logo图片换成同名文件即可,但要注意图片尺寸,不然布局会变形。

替换课程数据时,打开数据库里的t_course表,把name改成操作系统、计算机体系结构之类的课程,把description改成与你相关的说明。外键关联的章节表、资源表也要同步改,否则课程详情页点进去没有内容,答辩时会被追问。如果你时间紧张,优先改课程名和首页展示的课程列表,其他细节留到后面。

对于资源下载和视频展示,最轻量的做法是把课件PDF传上去,顺便在章节详情页加一个<a href="/upload/文件名.pdf" target="_blank">直接预览。如果源码里没有这个功能,可以先在模板里用原生的HTML写死路径,不要急着写Java接口。

最后验收时,我会按这样一个顺序走一遍:管理员登录能进后台,教师登录能上传资源,学生登录能看课程并提交作业,退出登录后访问受保护页面会被拦截。这四条是答辩演示的主路径,只要不卡住,项目就算立住了。另外,记得把数据库里的默认密码改成自己的,免得评委现场看到后台密码就是admin123。

说一个我自己的血泪教训:有一次演示前我改了application.yml的端口,但忘了顺带改上传目录配置,结果现场访问课件图片全部404。从那以后,我每次拿到新的源码包,都会强制先跑一遍“从登录到上传资源”这个闭环,确认没有遗漏后再改代码。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进