ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SpringBoot+Vue教学资源库管理系统实战:从数据库设计到部署上线

2026/10/3 14:09:00 拓冰建站 浏览量
SpringBoot+Vue教学资源库管理系统实战:从数据库设计到部署上线 最近终于把一套基于SpringBootVue的教学资源库管理系统从零到上线完整跑了一遍源码和数据库脚本也重新整理过。这套系统解决的是高校、培训机构里教学资源分散、查找困难、上传下载权限混乱的问题前端用Vue搭建操作界面后端用SpringBoot提供接口数据操作由MyBatis配合MySQL完成整体上是一个标准的、可以实际落地运行的前后端分离项目。如果你正打算做教学资源库、课件管理系统或者准备毕业设计再或者想系统看一下SpringBootVue全栈项目从设计到部署的完整链路这套代码和这篇文章都值得花点时间过一遍。项目开发过程中踩了不少坑尤其集中在数据库初始化、MyBatis动态SQL、Vue打包后如何放进SpringBoot、以及资源预览这几个环节。很多坑光看官方文档根本意识不到必须在真实项目里碰上一次才会长记性。下面我把整个系统的设计思路、关键实现和排错过程拆开揉碎讲清楚尽量做到每一步都可以对照着自己的项目直接抄作业。1. 教学资源库管理系统到底在管什么先想清楚业务再做技术选型1.1 业务目标与功能边界很多初学者拿到这类系统第一个动作就是建SpringBoot工程急着写Controller。我的建议是先别碰代码把业务边界画清楚。教学资源库的核心使用场景有三类人管理员、教师、学生。管理员负责分类维护、资源审核、统计报表教师负责上传课件、视频、习题、教案管理自己的资源学生负责检索资源、在线预览、下载和评价。由此功能边界就比较清晰了用户登录注册模块、资源分类管理模块、资源上传和审核模块、资源检索和筛选模块、在线预览模块、下载和收藏模块、评论评分模块外加一个简单的数据看板。这套系统我最终做下来后端大概二十来个接口就覆盖了全部核心功能前端页面也就十个左右。这里想提醒一下教学资源库不等于网盘不需要做复杂的文件目录树以分类和标签为主的扁平化结构就够了过度设计是这类项目最常见的失败原因。1.2 为什么是SpringBootVueMyBatisMySQL先直接说结论这套组合在当前阶段就是做中小型管理系统最稳妥的方案没有之一。后端用SpringBoot的理由很直白自动配置极大减少了配置代码内嵌Tomcat让部署变得异常简单一个java -jar就完事。生态里跟权限、文件处理、缓存相关的组件都很齐全遇到问题搜索引擎一抓一大把。对比以前用SSHSpringStrutsHibernate时代那种需要写大量XML配置的麻烦SpringBoot基本是把开发体验提升了一个量级。前端选Vue是因为组件化开发对维护友好对后端开发出身的人也比较容易上手。Vue Router做页面跳转、Vuex或者Pinia做全局状态管理配合Element Plus这样的组件库做后台管理界面效率极高。选Vue不选React没有对错之分纯粹是团队熟悉度和招聘市场匹配度的问题。MyBatis方面我最看重的就是SQL可控。教学资源库的检索条件组合多分类过滤、关键词模糊搜索、类型筛选、时间排序各种组合在MyBatis的XML里可以用动态SQL写得很清楚。相比JPA自动生成的SQLMyBatis在执行计划层面更可控出现慢查询也容易定位。MyBatis-Plus可以做增强但我建议新手先从原生MyBatis入手把SQL逻辑吃透再考虑Plus这种便捷工具。MySQL没什么好纠结的成熟稳定、社区活跃、资料最多。对于教学资源库这种读写比例大概在8比2的应用MySQL配合好索引几千几万条资源量级完全没有任何压力。除非你有复杂的JSON检索或地理空间查询需求否则不需要惦记PostgreSQL。1.3 前后端分离的整体架构最终落地的架构是标准的前后端分离后端只提供RESTful API不掺和页面渲染前端只负责界面展示和交互通过axios调用后端接口。后端内部按Controller、Service、Mapper、Entity四层划分前端按页面组件和路由来组织。部署方式上我采用的是把Vue打包后的dist目录静态资源直接放进SpringBoot的static目录用一个jar包同时提供接口和静态资源。这种单包部署模式对中小项目来说维护成本最低不需要单独配一台Nginx。当然如果你的项目后续要挂域名、做HTTPS、做负载均衡还是建议把前端放在Nginx里独立部署。两种方案在文章后面的部署章节会详细展开。2. 数据库设计这一步偷懒后面加班的每一晚都在还债2.1 核心表结构设计我设计的第一版表结构一共七张表用户表、角色表、资源分类表、资源信息表、资源文件表、下载记录表、收藏表、评论表。这里有几点需要特别说明。资源分类表不要设计成无限层级树教学资源库的场景一般两级就够一级分类比如计算机基础高等数学二级分类比如数据结构操作系统。我用parent_id字段实现父子级前端展示时做一层聚合逻辑简单且查询效率高。如果你一上来就用嵌套集模型或者递归查询那只会给自己找麻烦。资源信息表是核心字段包括资源标题、封面图URL、资源类型课件、视频、文档、习题、所属分类ID、上传者ID、资源简介、下载量、收藏量、评分、审核状态、上传时间等。重点说几个字段CREATE TABLE resource ( id bigint NOT NULL AUTO_INCREMENT, title varchar(200) NOT NULL COMMENT 资源标题, cover_url varchar(500) DEFAULT NULL COMMENT 封面图URL, resource_type varchar(20) NOT NULL COMMENT 类型video/document/image/audio/other, category_id bigint DEFAULT NULL COMMENT 分类ID, uploader_id bigint NOT NULL COMMENT 上传用户ID, summary varchar(1000) DEFAULT NULL COMMENT 资源简介, file_path varchar(500) NOT NULL COMMENT 文件存储路径, download_count int DEFAULT 0 COMMENT 下载量, favorite_count int DEFAULT 0 COMMENT 收藏量, status tinyint DEFAULT 0 COMMENT 审核状态0待审核 1已通过 2已驳回, create_time datetime DEFAULT NULL COMMENT 上传时间, update_time datetime DEFAULT NULL, md5 varchar(64) DEFAULT NULL COMMENT 文件md5值用于秒传, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里加md5字段是我后来做秒传功能时加的同一个文件被重复上传时直接返回已有记录对教学资源库来说能省不少磁盘空间。用户表和角色表我做成简单的RBAC模型用户表存账号、密码、姓名、身份类型教师/学生/管理员、状态角色表存角色编码和权限标识。对于毕业设计或中小型系统来说一个user表加一个区分身份的字段就够了搞太复杂的权限模型反而增加理解成本。2.2 索引设计与慢查询隐患资源列表页最常见的查询是按照分类筛选、按关键词搜索标题、按资源类型过滤、按下载量或时间排序。我建的索引如下分类ID 审核状态联合索引uk_category_status(category_id, status)因为列表页默认只展示审核通过的资源。资源类型 审核状态联合索引uk_type_status(resource_type, status)。下载量和收藏量普通索引排序场景使用。上传者ID索引用户个人资源列表使用。这里必须强调一个新手特别容易忽略的问题LIKE %关键词%这种模糊查询是走不了索引的数据量上万以后全表扫描会变慢。我在系统里做的妥协是标题模糊搜索用LIKE但配合分类和类型等条件先圈定数据范围让扫描行数尽量少。如果资源量真的到了几十万条那就得上全文检索方案比如Elasticsearch或者至少用MySQL的全文索引。很多教学资源库项目做到后面卡顿就是栽在这个看似简单的搜索框上。2.3 建库建表时的字符集和初始化细节MySQL初始化这个环节看似基础我却在上面浪费了将近半天时间。这里直接把血泪教训列出来第一数据库一律用utf8mb4不是utf8。教学资源库要存中文、英文、标点还有可能有韩文日文的课程资料utf8mb4是真正的四字节全量Unicode字符集utf8在MySQL里只是utf8mb3的别名遇到部分生僻字和Emoji会直接报错或变成乱码。第二排序规则我用的是utf8mb4_general_ci。这个排序规则不区分大小写对于登录和检索来说比较友好。如果你用utf8mb4_bin那就是区分大小写的同样的关键词搜出来的结果会不一样。MySQL 8.0默认的utf8mb4_0900_ai_ci也没问题只是相对较新的排序算法老工具可能会有兼容问题。第三建表语句一定要带上ENGINEInnoDB和DEFAULT CHARSETutf8mb4。InnoDB支持事务、外键、行级锁这是MyISAM给不了的。教学资源库的资源上传和下载记录操作需要事务保证一致性所以InnoDB是硬性要求。数据库初始化脚本建议单独准备一个init.sql包含建库、建表、插入初始分类数据和初始管理员账号。源码包里我放的顺序是先执行init.sql再启动后端不然系统一启动查询分类就报空指针因为表里没有数据。3. SpringBoot后端搭建骨架、配置和MyBatis的隐藏规则3.1 项目骨架与依赖选择我用的SpringBoot版本是2.7.x没有盲目追最新的3.x。原因是SpringBoot 3.x基于Jakarta EE和Java 17很多老教程和老依赖包在3.x下会踩坑。对于教学资源库这种项目2.7.x配合JDK 8或JDK 11运行稳定且网上参考资料最多后面要升级再单独考虑。pom.xml关键的几个依赖如下dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency这里注意mybatis-spring-boot-starter的版本和SpringBoot版本需要兼容。我用2.7.x配mybatis-starter 2.3.x没问题。如果你用SpringBoot 3.x那必须用mybatis-spring-boot-starter 3.0以上版本否则启动就会报ClassNotFoundException。另外我加了Druid作为数据库连接池监控功能对排查慢SQL很有帮助。生产环境你可以用HikariCPSpringBoot默认内置性能和稳定性都很好。Druid的优势是自带监控页面教学项目里演示起来比较直观。3.2 application.yml的配置细节配置文件的坑主要集中在数据源和MyBatis两部分server: port: 8080 servlet: context-path: / spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/teach_resource?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue username: root password: 123456 druid: initial-size: 5 min-idle: 5 max-active: 20 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.teachresource.entity configuration: map-underscore-to-camel-case: true这个连接串里藏着三个我实际踩过的坑第一个是useSSLfalse。MySQL 8.0默认开启SSL但本地开发环境根本没有配置SSL证书如果你不显式关闭启动时会报Communications link failure或者SSL握手失败。这个报错在热词里反复出现说明踩的人确实多。第二个是serverTimezoneAsia/Shanghai。MySQL 8.0的默认时区跟本地有偏差时查询时间字段会报The server time zone valueйʱ is unrecognized这种乱码错。加上时区参数后问题消失。第三个是allowPublicKeyRetrievaltrue。这个坑比较隐蔽MySQL 8.0使用caching_sha2_password认证插件时客户端需要获取服务端公钥如果不加这个参数会报Public Key Retrieval is not allowed。记住这三个参数本地MySQL连接基本不会再出问题。MyBatis配置里map-underscore-to-camel-case必须设置为true这样数据库里的create_time字段才能自动映射到实体类的createTime。如果你不配置这个你会发现查出来的实体对象里时间字段全是null这种问题排查起来相当浪费时间。3.3 Mapper接口与XML文件的约定我习惯把SQL语句写在XML文件里而不是用注解原因是为了动态SQL。教学资源库的资源筛选功能条件组合太多用注解写Select字符串拼接会很难维护。XML里可以写where、if、foreach这些标签可读性强得多。XML文件放在src/main/resources/mapper/目录下跟Mapper接口同名。启动类上用MapperScan(com.teachresource.mapper)扫描接口这样不用在每个Mapper接口上单独加Mapper注解。一个典型的分页查询XML片段select idselectResourcePage resultTypecom.teachresource.entity.Resource SELECT * FROM resource where if testcategoryId ! null AND category_id #{categoryId} /if if testresourceType ! null and resourceType ! AND resource_type #{resourceType} /if if testkeyword ! null and keyword ! AND title LIKE CONCAT(%, #{keyword}, %) /if if teststatus ! null AND status #{status} /if /where ORDER BY choose when testsortField downloaddownload_count DESC/when when testsortField favoritefavorite_count DESC/when otherwisecreate_time DESC/otherwise /choose LIMIT #{offset}, #{pageSize} /select分页这里我刻意没有引入PageHelper而是手动传入offset和pageSize。原因是PageHelper的拦截器在复杂动态SQL下偶尔会有统计SQL生成错误的问题手动LIMIT逻辑一目了然排查问题也方便。配合一个简单的PageResult封装类前端拿到的数据格式很规整。3.4 文件上传与本地存储的取舍教学资源库的核心是资源文件本身。我最初考虑过用MinIO做对象存储因为热词里也出现了minio和springboot整合minio这类内容。MinIO确实是个好东西部署简单接口兼容S3协议存教学视频、大课件很合适。但考虑到大部分二开这个项目的同学可能没有独立的Linux服务器甚至没有Docker环境MinIO的引入会大幅提升项目跑起来的门槛所以最终我选了本地磁盘存储的方案。文件上传的核心逻辑是接口接收MultipartFile按日期生成目录用UUID重命名文件把文件写入磁盘把相对路径存到数据库。对外提供一个静态资源映射接口SpringBoot通过WebMvcConfigurer把本地目录映射为/files/**访问路径Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/files/**) .addResourceLocations(file:D:/teach-resource-files/); } }上传接口中超时和大小限制也踩过坑。SpringBoot默认单文件最大1MB请求最大10MB不修改的话传视频必挂。在application.yml里加上spring: servlet: multipart: max-file-size: 2GB max-request-size: 2GB4. Vue前端开发资源预览与播放是这座系统的门面4.1 选Vue3还是Vue2Vite还是Webpack2025年了新项目直接Vue3ViteElement Plus别再犹豫。Vue3的组合式API写起来确实比Vue2的选项式API清爽而且Vite开发服务器启动速度快到起飞热更新也顺滑。唯一要留意的是Element Plus和Element UI的组件API有差异网上的老教程经常混着写报错时先看清楚是哪个版本。教学资源库的前端页面我拆成了两部分访客/学生端的资源中心和后台管理端。资源中心包括首页、资源列表页、资源详情页、个人中心后台管理端包括资源管理、分类管理、用户管理、统计看板。两套布局两套路由但共用同一个登录状态。4.2 路由模式为什么我选了hash而不是history这是部署时一个非常关键的决策点。Vue Router默认有hash和history两种模式。hash模式的URL带#号history模式不带看起来更美观。但如果你的前端打包后是放进SpringBoot的static目录然后由一个jar包对外提供history模式会有严重问题用户直接访问http://localhost:8080/resource/detail/1后端没有对应的接口会返回404。要解决history模式的404需要后端把所有非接口路径都重定向到前端首页配置起来比较繁琐。所以我最终选了hash模式。URL带个#号对教学资源库管理系统来说无伤大雅但换来的是部署环境极大的自由度。如果你坚持要用history模式那建议前端独立部署在Nginx然后配一条try_files $uri $uri/ /index.html规则。4.3 资源在线预览的完整落地资源预览是我觉得这篇文章里最有价值的小节因为热词里出现了一堆vue播放m3u8免安装vue image能显示pdf吗这类问题说明大家在这个功能上都卡过。不同资源类型预览的解决方案完全不同图片类最简单直接img :srcfilePath就能显示。前端请求图片URL时会自动发GET请求不需要加token头所以图片URL要么是公开的要么在URL上带上有效期签名。PDF预览推荐用iframe :srcpdfUrl浏览器内置的PDF插件直接渲染。这里有个容易踩的坑下载接口往往加了登录校验iframe里发请求时带不上自定义Header会导致PDF加载不了。解决方案是把PDF预览单独做成一个免登录的口子或者用pdfjs-dist这个库统一处理它能以fetch方式带Header获取PDF数据流然后用Canvas渲染。项目里我采用的就是pdfjs-dist方案兼容性最好。视频这一块MP4格式用原生video标签就行浏览器基本都支持。麻烦的是m3u8格式。m3u8是HLS协议的视频切片索引文件浏览器原生不直接支持播放。网上很多教程让你给video加typeapplication/x-mpegURL属性实测在Chrome下根本没反应。正确做法是引入hls.js库代码如下import Hls from hls.js function initHls(videoUrl) { const video document.getElementById(player) if (Hls.isSupported()) { const hls new Hls() hls.loadSource(videoUrl) hls.attachMedia(video) hls.on(Hls.Events.MANIFEST_PARSED, () video.play()) } }hls.js免安装任何浏览器插件纯JS实现实测在Chrome、Edge、Firefox下都能正常播放m3u8流。如果你的教学资源走的是RTMP直播流那又是另一套方案了但教学资源库场景下HLS足够。PPT课件的预览比较头痛。最稳的方案是后端先把PPT转成PDF再转成图片或者直接转图片逐页展示。这个转换服务可以用LibreOffice headless模式批量处理Windows和Linux下都能跑。在我的系统里上传PPT时后端会自动调用转换脚本把每一页转成PNG图片前端用轮播的方式展示效果和预览PDF差不多而且兼容性最好。4.4 axios封装与Vue打包进SpringBoot的集成细节axios封装没什么花哨的核心是拦截器service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config }) service.interceptors.response.use( response { return response.data }, error { if (error.response error.response.status 401) { localStorage.removeItem(token) router.push(/login) } return Promise.reject(error) } )接口统一返回{ code, message, data }结构正确请求返回data直接解包。这样业务代码里不需要每次response.data.data嵌套取数据。打包构建环节执行npm run build之后dist目录包含index.html、css、js等文件。把这些文件整个拷贝到SpringBoot的src/main/resources/static目录下重新打包就是一体化部署。有两个容易犯的错需要提醒第一如果static目录下已经存在index.htmlSpringBoot根路径会直接返回前端的index.html不再走Controller的GetMapping(/)。我之前遇到一个奇怪现象写完后端启动后访问根路径始终进不了前端页面排查半天发现是后端方法设置了GetMapping(/)和静态资源冲突了删掉后端方法就正常。第二如果需要修改接口路径前端的axios baseURL和生产环境后端的接口前缀要保持一致。开发环境我通过Vite代理解决跨域生产环境因为前端和后端在同一个服务下用相对路径/contextPath就能访问不需要配置CORS。如果分开部署那才需要后端加CrossOrigin或全局CORS过滤器。5. 联调和部署阶段踩过的坑能编译通过只是开始能真正用起来才算完成5.1 IDEA启动配置和SpringBoot端口问题开发阶段我用IDEA直接启动SpringBoot这里热词里提到了idea 2026 怎么配置springboot服务编辑配置数据比如启动端口。IDEA的Run/Debug Configuration中Spring Boot的启动类选好之后Program arguments可以写--server.port8081临时改端口Environment variables里可以加SPRING_PROFILES_ACTIVEdev切换开发环境配置。这个功能特别适合多项目同时开发时避免端口冲突。端口被占用是出现频率最高的问题。Windows上我固定用netstat -ano | findstr 8080查到PID再开任务管理器结束进程。如果你改了端口还是提示被占用检查一下是不是有多个IDEA实例启动了同一个项目这种情况很常见但排查起来特别容易忽略。5.2 前后端联调时的跨域与代理开发模式下前端跑在5173端口后端跑在8080端口浏览器会拦截跨域请求。我的处理方式是前端配置Vite代理vite.config.js里server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端发的/api/...请求会被Vite服务器转发到后端浏览器视角完全同源。后端接口路径统一以/api开头这也是一种良好的接口规范。这里有个隐藏细节前端代理解决的是浏览器跨域但如果你在Swagger或者Postman里直接调后端接口不会有任何跨域问题。所以跨域配置出现报错时先分清是浏览器行为还是后端拦截。5.3 数据库连接四大金刚报错一个都不能少汇总一下实际工作中MySQL连接相关的报错和处理报错信息原因解决Communications link failure数据库没启动或连接串错误确认MySQL服务已启动检查端口3306SSL connection errorMySQL 8.0 SSL握手失败连接串加useSSLfalseServer time zone value is unrecognized时区配置缺失加serverTimezoneAsia/ShanghaiPublic Key Retrieval is not allowedcaching_sha2_password认证问题加allowPublicKeyRetrievaltrue这四个问题我全部在同一周内遇到过每次都是去搜索引擎翻半天。现在直接记到自己的速查表里新项目初始化数据库连接串一口气配齐就完事。5.4 MyBatis缓存机制在项目中的实际影响关于MyBatis缓存热词里出现频率很高因为面试爱问实际项目里也真会碰到。MyBatis一级缓存是SqlSession级别的默认开启在一个SqlSession内执行两次相同的查询会命中缓存。但SpringBoot整合MyBatis后每次Mapper调用默认会开启新的SqlSession并关闭所以一级缓存几乎感知不到。二级缓存是Mapper级别的跨SqlSession生效。我在教学资源库系统里没有开启二级缓存原因是资源列表数据本来就变化不频繁加上Redis做缓存才是更合理的方案。如果你在XML里不小心加了cache/标签开发时会遇到一个莫名其妙的坑修改数据库数据后查询结果还是旧的因为二级缓存没失效。排查这种缓存问题时先把mybatis.configuration.cache-enabledfalse关掉确定问题消失后再决定怎么优化。5.5 部署后刷新404和上传文件访问404两个404问题在部署阶段特别典型。第一个是前端history路由刷新404解决办法前面说了要么换hash路由要么配Nginx fallback。第二个是上传文件后图片加载404原因往往是静态资源映射路径和文件实际存储路径不一致。我处理文件存储路径的一个原则是数据库里存相对路径而不是绝对路径。比如/files/2025/02/a1b2c3.jpg前端拼服务器的域名加这个相对路径就能访问。如果数据库存的是D:/teach-resource-files/2025/02/a1b2c3.jpg这种绝对路径一旦换了服务器部署所有文件的访问地址都要跟着改非常被动。用相对路径配合SpringBoot的静态资源映射换环境只需要改配置文件里的一个基础路径。6. 源码结构复盘和后续可以扩展的方向6.1 项目源码目录结构这套系统整理好的源码目录结构大致如下前端部分frontend/ ├── src/ │ ├── api/ # 接口请求统一封装 │ ├── assets/ # 静态资源 │ ├── components/ # 通用组件 │ ├── router/ # 路由配置 │ ├── stores/ # 状态管理 │ ├── views/ # 页面 │ │ ├── admin/ # 后台管理页面 │ │ └── portal/ # 资源中心页面 │ ├── App.vue │ └── main.js ├── package.json └── vite.config.js后端部分backend/ ├── src/main/java/com/teachresource/ │ ├── controller/ # 接口层 │ ├── service/ # 业务逻辑层 │ ├── mapper/ # MyBatis接口 │ ├── entity/ # 实体类 │ ├── config/ # 配置类静态资源映射、CORS等 │ ├── common/ # 统一返回结果、异常处理 │ └── TeachResourceApplication.java ├── src/main/resources/ │ ├── mapper/ # MyBatis XML │ ├── application.yml │ └── static/ # 前端打包产物 └── pom.xml拿到源码的第一步永远是先跑起来而不是先改代码。先把init.sql导入MySQL改好数据库密码启动后端再启动前端看到页面能正常登录和展示数据再开始按自己的需求改功能。如果你以前只做过单体项目第一次接触这种前后端分离结构建议先花半小时把每个目录里文件的职责标出来再对照着请求链路走一遍收获比直接改代码大得多。6.2 可以继续扩展的功能和思路教学资源库管理系统做完核心功能后有几个扩展方向效果很好但都不影响现有结构。第一个是对象存储的平滑迁移。目前文件存在本地磁盘如果后续资源量增大可以把上传逻辑抽象成StorageService接口本地实现和MinIO实现各写一套通过配置切换。MinIO部署简单支持分片上传配合SpringBoot整合也不复杂这是我最推荐的下一步升级。第二个是全文检索和中文分词。当前标题搜索是LIKE模糊查询扩展到几千条之后用户体验还行但到几万条就需要ES了。如果不想引入ES这么重的组件可以考虑在MySQL层面加全文索引配合HanLP做中文分词后把分词结果存到一个独立的索引表里也能很大程度提升搜索准确率。热词里提到了hanlp分词在springboot这个方向确实值得深入研究。第三个是异步处理相关。资源上传后可能要做缩略图、切片视频、提取PPT内容等操作这些都不适合在请求线程里同步做。可以使用Spring的事件机制解耦或者引入消息队列。这个系统规模不需要ActiveMQ这么重的组件用Redis的队列或者Spring自带的Async就能解决。第四个是消息通知。教学资源库中教师上传资源、管理员审核通过或驳回这些节点都适合发站内信或邮件通知。用SpringBoot整合WebSocket做消息推送或者接入邮件服务都能提升系统完整度。6.3 二次开发最实用的几条建议根据我自己的开发习惯做这种项目的二次开发时按以下顺序推进比较稳第一先理清角色权限链路。从登录拿到token到前端路由守卫判断角色再到后端接口的权限拦截整条链路走通之后再动业务代码。权限链路不清晰后面加接口时很容易忘记加权限注解造成越权漏洞。第二前端的接口请求层和后端接口层要一一对应。我看到很多同学喜欢在页面里直接写axios调用这样确实快但接口多了以后后端的接口签名一旦变动各个页面的代码都要翻出来改。统一在api目录下维护接口函数是性价比最高的规范。第三做好异常拦截。后端的全局异常处理器配合统一返回结果结构可以避免很多前端拿到null时不知所措的情况。我习惯在Controller里只做参数接收和结果返回所有业务异常在Service层抛出自定义异常由全局异常处理器统一转换成{code, message, data}返回给前端前端根据code判断业务状态。第四养成看日志的习惯。SpringBoot的日志体系很完善配置好logback后开发阶段把SQL日志打开定位问题时能省一半时间。在application.yml里加上logging: level: com.teachresource.mapper: debug这个配置会把SQL语句和执行参数打印出来联调时后端SQL写错了一目了然。热词里mybatis配置打印指的就是这个强烈建议所有同学在开发阶段都开着。做这套系统最大的收获不是代码量而是理解了能用和好用之间的差距。很多细节比如文件路径存相对路径、连接串一次配全四个参数、路由选hash模式、PDF预览用pdfjs-dist处理token单看每个都是小知识点但组合在一起才让这套系统真正具备了从演示到试用的能力。我把源码和数据库脚本整理出来后又自己照着文档从头到尾跑了一遍确认换一台干净电脑也能顺利启动这才敢拿出来分享。如果你照着文章把项目跑通并且改成了自己想要的样子那这篇文章的意义就达到了。