ARTICLE DETAIL

建站实战干货

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

SpringBoot+Vue学生读书笔记共享平台开发实战:前后端分离与JWT鉴权全解析

2026/9/16 3:44:14 拓冰建站 浏览量
SpringBoot+Vue学生读书笔记共享平台开发实战:前后端分离与JWT鉴权全解析 1. 整体定位这个平台到底在解决什么问题不夸张地说SpringBootVue这个组合现在已经是Java后端开发最主流的搭配之一。如果你在找毕设或课设题目学生读书笔记共享平台这类项目是个非常好的选择——技术覆盖全面、业务逻辑清晰、数据模型也不复杂特别适合用来展示前后端分离开发能力。很多学校用这个题目一用就是好几年就是因为它在够用和出彩之间找到了一种很好的平衡。先把这个项目讲清楚。它本质上是一个围绕书籍笔记双核心的UGC用户生成内容平台普通用户浏览书籍、撰写读书笔记、收藏点赞、在笔记下交流管理员负责书籍和分类的维护、用户管理、数据概览。听上去和简书、豆瓣读书很像但规模小得多技术实现上也没那么复杂。正因为复杂度适中它才能成为毕设和课设的经典选择——后端有CRUD、有鉴权、有关联查询前端有列表页、详情页、表单页数据库有多表关联把这些串起来一份像样的毕业设计就齐了。我第一次带学生做这个项目时最深的感受是这题目不像电商系统那样堆一堆订单、库存、支付逻辑却能把SpringBoot和Vue的核心用法全部覆盖到。对学习者来说做完这个项目你对SpringBootMyBatis-PlusJWTVueAxios这一整条链路会有一个完整的肌肉记忆而不是停留在看过视频的程度。而且要注意这类项目市面上成品源码很多但很多是十几年前SpringMVCJSP的老思路改造的表面写着SpringBoot实际还是服务端渲染那一套。我下面讲的这套方案是真正的前后端分离结构前端Vue工程独立运行后端只提供RESTful接口这套思路直接对标企业开发方式哪怕以后你去做真正的商业项目底层逻辑也是一样的。2. 技术选型与整体设计思路1.1 核心需求拆解读书笔记平台必须有哪些功能做项目最忌讳的是一上来就敲代码先把需求理清楚。学生读书笔记共享平台核心参与者就两种普通用户和管理员。围绕读书这个动作普通用户关心的路径是找书→看书介绍→写笔记→浏览别人的笔记→互动管理员关心的路径是内容能不能控制→用户能不能管→网站状态怎么样。把这两条路径落到功能菜单上用户端注册登录、书籍列表按分类筛选、关键词搜索、书籍详情、笔记列表按发布时间、热度排序、笔记详情、发布笔记、编辑删除自己的笔记、点赞收藏、个人书单/我的笔记、修改资料和密码。管理端登录独立账号体系或复用用户表、书籍管理增删改查、上传封面、笔记管理审核/下架违规内容、分类管理、用户管理禁用/启用账号、统计数据用户数、笔记数、书籍数。这套功能清单不贪多但每个功能都不是摆设。比如审核/下架笔记看起来简单但它涉及用户表和笔记表的状态字段设计也涉及后端接口的权限判断加了它项目的完整度会高一个档次。1.2 技术选型为什么是这套组合拳SpringBootMyBatis-PlusMySQLVue这套组合在2025年的Java生态里依然是学生项目的主流配置原因有三第一SpringBoot极大降低了配置成本。以前SSM时代的XML配置能写几百行SpringBoot自动配置把大部分事情办了非常适合学习周期有限的毕设人群。用IDEA初始化项目时选几个依赖工程就已经能跑了。第二MyBatis-Plus解决单表CRUD不想写SQL的痛点。毕设项目里80%的操作是单表增删改查用MyBatis-Plus的BaseMapper直接搞定既保留了手写SQL的灵活性又不用像JPA那样绕弯子。书籍笔记这类表之间有关联复杂查询就自己写XML里的SQL怎么都不亏。第三VueElement UI/Element Plus让前端有成品感。写后台管理界面时Element的表格、表单、弹窗组件拿过来就能用页面颜值在线答辩时不会因为界面丑被扣分。至于数据库MySQL 5.7或8.0都可以。新机器建议直接上8.0性能好、默认字符集搞定utf8mb4但要注意和驱动版本匹配后面我会专门讲这个坑。1.3 一个核心设计决策前后端分离的原因我曾经见过同一个项目有低代码版本和前后端分离版本两种实现最后大家公认分离版本更容易讲清楚。因为前后端分离让职责边界非常干净前端管页面渲染和用户交互后端管业务规则和数据存取。开发时前端用Vue devServer跑在8080端口后端SpringBoot跑在9090端口通过代理转发搞定联调部署时前端npm run build生成静态文件扔给Nginx后端打包成jar独立运行。整套流程是标准的企业级流水线答辩时一说前后端分离架构老师基本不会再追问架构层面的问题。3. 数据库设计与核心表结构2.1 表全景七张表把业务闭环串起来数据库设计是毕设答辩时容易被追问的部分所以每一张表都要想清楚为什么存在。我的建议是七张表起步用户表、图书表、分类表、笔记表、点赞表、收藏表、笔记评论表。如果你想让数据统计更灵活还可以加一张浏览记录表但学生项目一般用不到。这几张表的关系我用大白话拆解一下用户和笔记一对多一个用户可以写多篇笔记用户被删了笔记怎么处理这里建议用逻辑删除而不是物理删除。图书和笔记一对多一篇笔记必须归属某本书。图书和分类多对一一本书属于一个分类。用户和图书多对多通过收藏表/点赞表实现虽然这两个表存的内容不一样但表结构上都是用户ID图书ID/笔记ID的关联模式。实际建表时我踩过的一个坑是笔记表里既要有图书ID又要冗余一个书名和作者。表面上看这违反了数据库范式但实际操作中笔记列表页要展示某本书下的笔记如果每次都要JOIN图书表列表一长查询就会变慢代码也会啰嗦。对毕设项目来说适当的字段冗余换来的是接口书写流畅值得。2.2 建表SQL与关键字段设计的细节下面这套SQL是我实际用过的直接复制后根据自己需要改库名即可-- 用户表 CREATE TABLE user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录名, password varchar(100) NOT NULL COMMENT 密码(加盐哈希), nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, role tinyint(4) DEFAULT 0 COMMENT 0普通用户 1管理员, status tinyint(4) DEFAULT 1 COMMENT 1正常 0禁用, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 分类表 CREATE TABLE category ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(50) NOT NULL, sort int(11) DEFAULT 0, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 图书表 CREATE TABLE book ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(100) NOT NULL COMMENT 书名, author varchar(50) DEFAULT NULL, cover varchar(255) DEFAULT NULL COMMENT 封面图URL, category_id bigint(20) DEFAULT NULL, description text COMMENT 图书简介, status tinyint(4) DEFAULT 1, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 笔记表 CREATE TABLE note ( id bigint(20) NOT NULL AUTO_INCREMENT, book_id bigint(20) NOT NULL, user_id bigint(20) NOT NULL, title varchar(100) NOT NULL, content longtext COMMENT 笔记正文,支持Markdown, view_count int(11) DEFAULT 0, like_count int(11) DEFAULT 0, status tinyint(4) DEFAULT 1 COMMENT 1正常 0下架, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_book (book_id), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; -- 点赞表、收藏表、评论表略核心是(user_id, target_id)联合唯一索引两个容易被忽视的关键点点赞表和收藏表要建联合唯一索引防止同一用户对同一篇文章点两次赞这比在Java代码里先查再插要可靠得多所有时间字段建议直接用datetime类型不要用int存时间戳否则前端展示还要自己转格式平白多出很多代码量。2.3 逻辑删除还是物理删除这个点几乎每次答辩都会被问。我的建议是用户表和笔记表用逻辑删除点赞收藏评论用物理删除。逻辑删除就是加一个deleted字段或者上面写的status字段删除时UPDATE状态查询时默认只查状态正常的。它最大的好处是数据不丢失管理员可以从后台恢复误删的笔记。而点赞记录这类无价值的关联数据物理DELETE干净利落不需要操心状态过滤。MyBatis-Plus对逻辑删除支持得很到位在实体类的status字段上加TableLogic注解配置全局逻辑删除值然后你调deleteById的时候它自动变成UPDATE查的时候自动加上WHERE status 1。这个细节说出来老师会认为你是懂行的。4. 后端核心实现SpringBootJWT接口开发全记录3.1 工程初始化与依赖管理用IDEA新建SpringBoot项目时Group填com.exampleArtifact填reading-note-platformJava版本按8或11都行如果用的新版IDEA和SpringBoot 3.x就选17后面会讲兼容性。依赖方面我建议勾选这几个Spring Web提供RESTful接口能力MySQL Driver数据库驱动Lombok省掉getter/setter模板代码Spring Boot DevTools热部署改代码自动重启提升开发效率pom.xml里手动补上MyBatis-Plus和JWT相关依赖dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.4/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency dependency groupIdcn.hutool/groupId artifactIdhutool-all/artifactId version5.8.22/version /dependencyHutool是个Java工具库里面有现成的密码加密、随机验证码、日期格式化方法能帮你省掉一大批重复代码。当然你也可以不用它但用了之后代码会精简很多我项目里的MD5加盐加密和Token生成都用它完成。3.2 application.yml配置与统一返回体设计项目配置文件是重灾区很多同学起步就挂在MySQL时区问题上。我这里给出一份可以直接跑的配置server: port: 9090 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/reading_note?useUnicodetruecharacterEncodingutf8mb4serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: 你的密码 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: status logic-delete-value: 0 logic-not-delete-value: 1注意serverTimezoneAsia/Shanghai不加这个你连数据库时会报时间错误。jackson.date-format是为了让后端返回的时间字段直接变成2025-01-15 21:30:00格式前端就不需要再写格式化函数了。接口返回结构建议统一封装以后所有的Controller都返回这个类型Data public class ResultT { private Integer code; // 200成功4xx业务错误401未登录 private String msg; private T data; public static T ResultT success(T data) { ResultT r new Result(); r.setCode(200); r.setMsg(操作成功); r.setData(data); return r; } public static T ResultT error(String msg) { ResultT r new Result(); r.setCode(500); r.setMsg(msg); return r; } public static T ResultT unauthorized() { ResultT r new Result(); r.setCode(401); r.setMsg(请先登录); return r; } }这样做最直接的好处是前端Axios拦截器只需要判断code 200就进入业务逻辑其余情况统一弹错误提示代码里不用到处写try-catch。3.3 JWT登录鉴权流程与代码登录鉴权这块是后端最容易被追问的部分。完整流程是用户提交用户名密码→校验密码→生成JWT字符串→返回给前端→前端后续请求在Header里带Authorization: Bearer token→后端拦截器解析token并获取用户ID→写入ThreadLocal方便业务层取用。JWT生成工具类核心代码如下public class JwtUtils { private static final String SECRET your-secret-key-change-it; public static String generateToken(Long userId, String username, String role) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .claim(role, role) .setExpiration(new Date(System.currentTimeMillis() 1000 * 60 * 60 * 24)) // 1小时可以视情况调整 .signWith(SignatureAlgorithm.HS256, SECRET) .compact(); } public static Claims parseToken(String token) { return Jwts.parser().setSigningKey(SECRET).parseClaimsJws(token).getBody(); } }拦截器的核心逻辑在preHandle里取出Header去掉Bearer 前缀调用parseToken解析成功就把userId放到ThreadLocal里失败直接返回401。放行列表要包含登录注册接口、书籍列表查询、笔记列表查询这些公开接口。这里强调一个很多新手容易搞混的点拦截器只管是否登录至于能不能操作这篇笔记比如删除别人的笔记那是业务接口内部要做的归属判断。我见过很多代码只在拦截器里判断一遍导致用户可以随意改别人的内容这个问题在设计时就要想好。3.4 核心业务接口实现思路以笔记模块为例最核心的接口是发布笔记和笔记列表分页查询。发布笔记时前端会传来bookId、title、content这三个字段。后端Controller里从ThreadLocal拿到当前登录用户IDnew一个Note实体通过MyBatis-Plus的save方法插入然后让图书表的笔记数加1。这个图书表笔记数1操作初学者容易忽略但它是对关联数据的维护不做的话图书详情页的统计数量永远是0。笔记列表分页查询用MyBatis-Plus的Page对象即可GetMapping(/notes) public ResultPageNote pageNotes(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) Long bookId, RequestParam(required false) String keyword) { PageNote page new Page(pageNum, pageSize); LambdaQueryWrapperNote wrapper new LambdaQueryWrapper(); if (bookId ! null) { wrapper.eq(Note::getBookId, bookId); } if (StrUtil.isNotBlank(keyword)) { wrapper.like(Note::getTitle, keyword); } wrapper.orderByDesc(Note::getCreateTime); noteMapper.selectPage(page, wrapper); return Result.success(page); }如果列表页需要连带显示用户名和书名而不是查回前端再去挨个表查就要用自定义SQL联表查询。在NoteMapper.xml里写一个selectPageWithDetail用LEFT JOIN关联user表取nickname关联book表取title。这套多表查询代码也是答辩时的加分点。3.5 文件上传封面图处理方案书籍封面上传看似简单里面也有坑。我推荐把图片存服务器本地目录而不是塞进数据库BLOB字段。后端接收MultipartFile用UUID生成文件名保存到resources/upload/目录同时把文件的访问URL返回给前端。实现方案如下PostMapping(/upload) public ResultString upload(RequestParam(file) MultipartFile file) throws IOException { String originalFilename file.getOriginalFilename(); String suffix originalFilename.substring(originalFilename.lastIndexOf(.)); String newName UUID.randomUUID().toString().replace(-, ) suffix; File dir new File(uploadPath); if (!dir.exists()) { dir.mkdirs(); } file.transferTo(new File(dir, newName)); String url /upload/ newName; return Result.success(url); }但你一定要给SpringBoot配置一个虚拟路径映射否则图片URL访问不到Configuration public class WebConfig implements WebMvcConfigurer { Value(${file.upload-path}) private String uploadPath; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadPath /); } }这里有个细节把绝对路径写在application.yml里以后部署到服务器不用改代码只改配置。如果你把uploadPath写死在代码里换环境就麻烦。5. 前端实现Vue工程搭建与页面逻辑4.1 工程初始化与路由设计前端我建议用Vue 2 Element UI还是Vue 3 Element Plus如果是全新学习直接上Vue 3现在不管是中文社区还是插件的生态都已经很成熟了。创建工程用Vite比webpack快得多一条命令搞定npm create vitelatest reading-note-web -- --template vue cd reading-note-web npm install npm install element-plus axios vue-router4 pinia路由规划我建议分两层前台页面和后台管理页面。前台包括图书列表、图书详情、笔记详情、登录注册、个人中心后台包括概览、书籍管理、笔记管理、分类管理、用户管理。用路由懒加载避免首屏加载太慢const routes [ { path: /, component: () import(../views/Home.vue), }, { path: /book/:id, component: () import(../views/BookDetail.vue), }, { path: /admin, component: () import(../layout/AdminLayout.vue), redirect: /admin/dashboard, children: [ { path: books, component: () import(../views/admin/BookManage.vue), }, // 其他管理页面 ], }, ];路由守卫是必须写的。需求是进入个人中心和后台管理页必须登录管理员页面还需要判断角色。在beforeEach里读取本地存储的token和用户信息没有就直接跳登录页。4.2 Axios封装与API模块管理Axios拦截器封装是前端工程的关键环节。你肯定不想每个页面都手动用axios.get然后写错误处理所以统一封装成工具库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 Bearer ${token}; } return config; }); request.interceptors.response.use( response { const res response.data; if (res.code ! 200) { ElMessage.error(res.msg || 请求失败); return Promise.reject(new Error(res.msg)); } return res.data; }, error { if (error.response?.status 401) { localStorage.removeItem(token); router.push(/login); } ElMessage.error(网络请求失败); return Promise.reject(error); } ); export default request;这样每个页面的API调用就变成了export const getNoteList (params) request.get(/notes, { params }); export const publishNote (data) request.post(/notes, data);页面里只管拿数据和渲染出错弹提示和跳登录都是拦截器统一处理。这种写法在答辩时讲前端工程化说服力很强。4.3 核心页面实现笔记发布与Markdown编辑笔记发布页是前端最有技术含量的页面。因为笔记内容是Markdown格式所以需要引入一个编辑器组件。我这里用的是mavon-editorVue 3兼容版几行代码就能在页面里放一个完整的Markdown编辑器预览区域template div el-form label-width80px el-form-item label选择书籍 el-select v-modelform.bookId placeholder请选择书籍 el-option v-forbook in bookList :keybook.id :labelbook.title :valuebook.id / /el-select /el-form-item el-form-item label标题 el-input v-modelform.title placeholder请输入笔记标题 / /el-form-item el-form-item label正文 mavon-editor v-modelform.content styleheight: 400px / /el-form-item el-form-item el-button typeprimary clicksubmit发 布/el-button /el-form-item /el-form /div /template script setup import { ref, reactive, onMounted } from vue; import request from ../../api/request; import { ElMessage } from element-plus; const form reactive({ bookId: null, title: , content: }); const bookList ref([]); onMounted(async () { bookList.value await request.get(/books/all); }); const submit async () { if (!form.bookId || !form.title || !form.content) { ElMessage.warning(请填写完整信息); return; } await request.post(/notes, form); ElMessage.success(发布成功); // 跳转到新笔记详情 }; /script这里有两个小细节编辑器组件取决于项目版本的兼容性如果使用Vue 3 Vite需要确认编辑器插件是否有对应的Vue 3版本否则会出现插件不渲染的诡异问题。提交前必须有非空校验后端也要校验绝不能只做前端校验。6. 前后端联调与权限细节处理5.1 跨域问题开发环境和生产环境的两种解法前端跑在5173端口Vite默认后端跑在9090端口跨域是绕不开的问题。我推荐开发环境用Vite代理生产环境用Nginx代理。Vite配置vite.config.jsexport default defineConfig({ server: { port: 5173, proxy: { /api: { target: http://localhost:9090, changeOrigin: true, rewrite: (path) path.replace(/^\/api/, ), }, }, }, });这样前端请求/api/notes时代理会转发到http://localhost:9090/notes前端代码里没有任何跨域痕迹。生产环境则让Nginx把/api/前缀转发到jar包端口配置更简单。用这种代理方式后端的WebMvcConfigurer里就不需要额外写CorsRegistry但如果你想让其他域名也能直接访问接口后端仍然需要配置跨域二选一别两种都写否则会出现两次Access-Control-Allow-Origin的坑。5.2 权限操作的前后端配合我讲一个学生项目里最常见的安全漏洞前端把删除按钮隐藏了但后端接口完全可以被直接调用导致用户绕开前端操作接口越权。正确做法是后端接口里必须做归属校验比如删除笔记时先根据noteId查笔记判断note.getUserId().equals(currentUserId)才放行。用AOP做权限控制对学生项目来说有点重拦截器方法内判断就够了。核心是养成习惯凡是修改型接口都要问一句当前登录用户有没有权利操作这个资源。5.3 点赞收藏的状态同步问题点赞/收藏在页面上的交互是点亮/取消点亮和数量同步变化。做法是进入详情页时前端带着笔记ID请求后端GET /notes/{id}/like-status后端根据当前登录用户的ID去like表查返回true/false。点击点赞按钮时调POST /notes/{id}/like返回操作后的最新likeCount。前端用这个返回值更新显示而不是前端自己加减避免数值不一致。7. 环境搭建与部署上线6.1 本地开发环境版本匹配建议版本问题占了这个项目70%的启动报错。给出的建议JDKSpringBoot 2.x用JDK 8或11SpringBoot 3.x必须JDK 17及以上。如果你用IDEA 2023以上版本创建项目默认拉取的是SpringBoot 3.x所以不小心选了JDK 8就会直接编译失败。MySQL5.7或8.0均可但驱动版本注意。SpringBoot 2.7.8默认的MySQL驱动是8.0.33连接MySQL 5.7没问题如果用的更老驱动去连MySQL 8.0会报认证协议错误。Node.jsVite 5需要Node 18以上先执行node -v检查。前端依赖安装慢换国内镜像源npm config set registry https://registry.npmmirror.com这是最快的解决方案。6.2 前后端打包与服务器部署后端打包直接用Maven插件mvn clean package -DskipTests然后得到一个reading-note-platform-0.0.1-SNAPSHOT.jar。上传到服务器后执行nohup java -jar reading-note-platform-0.0.1-SNAPSHOT.jar --spring.profiles.activeprod log.txt 21 前端部署前要注意接口地址不能写死为localhost。在vite.config.js里设置envDir或用环境变量区分开发/生产然后执行npm run buildVite会生成dist目录把里面所有文件上传到服务器Nginx的html目录。Nginx配置核心部分server { listen 80; server_name your-domain.com; root /var/www/reading-note-web/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:9090/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } # 前端路由使用history模式时的关键配置 location / { try_files $uri $uri/ /index.html; } }try_files那一行是很多同学部署后白屏的原因——Vue Router的history模式下直接刷新/book/3时Nginx找不到这个文件必须让它回退到index.html由前端路由接管。6.3 进程守护不挂项目的后台运行方案很多人用nohup java -jar启动后端但服务器一重启进程就没了。学生项目虽然不要求高可用但答辩演示时服务挂了很尴尬。推荐用systemd管理Java进程新建/etc/systemd/system/note.service[Unit] DescriptionReading Note Platform Afternetwork.target [Service] ExecStart/usr/bin/java -jar /opt/reading-note-platform.jar Restarton-failure RestartSec10 [Install] WantedBymulti-user.target然后systemctl enable note设置开机自启后面用systemctl start note、systemctl status note管理服务比nohup靠谱得多。8. 常见问题与排查技巧实录7.1 SpringBoot启动直接报Failed to configure a DataSource这类报错通常校验的是配置项最典型的原因是application.yml里数据库密码配置了中文或者driver-class-name写成了老版的com.mysql.jdbc.Driver。新驱动必须是com.mysql.cj.jdbc.Driver。还有一个隐藏坑是SpringBoot启动时会先去校验spring.datasource.url如果你的数据库服务没启动也会报这个错。排查顺序先确认MySQL服务启动再用Navicat那边测试连接。7.2 Vue前端一直报401但接口是对的如果你确定登录成功、token存在localStorage里但还是401十有八九是Axios请求拦截器没有正确读取token。排查方法打开浏览器DevTools的Network面板看请求头里有没有Authorization字段——没有就是前端拦截器没生效有但是报401那就是后端解析token失败。另外注意拦截器里config.headers.Authorization ...在Vue 3 Axios新版里一般没问题但老版本Axios有大小写问题统一用Authorization。7.3 前端白屏没有报错但页面只剩背景这是后端路由配置的问题90%是后端返回了一个路由不存在的错误但前端没有做统一捕获导致页面崩溃。先用Postman直接调接口确认返回数据正常再看浏览器Console里有没有具体报错。如果发现是404排查Controller的RequestMapping路径是否和前端请求路径一致。很多同学喜欢在类上写RequestMapping(/api/note)又在方法上写GetMapping(/list)前端请求却是/api/notes/list字母对不上非常容易疏忽。7.4 Markdown编辑器显示原始代码而不是渲染结果这个坑几乎每个人都踩过。原因很简单后端返回的content是Markdown源码丢到Vue的{{ }}插值里自然纯文本显示。正确做法是使用mavon-editor的v-html渲染或者用marked库转成HTML再展示。分享一个细节预览时用编辑器组件的只读模式可以让笔记详情页和编辑页的渲染效果保持一致不用另写一套样式。7.5 打包后Element Plus字体文件404Vite打包时Element Plus的字体文件路径容易出问题表现为图标不显示、控制台报woff2文件404。解决方法是修改vite.config.js里的base配置或者使用CDN方式引入Element Plus相关CSS。我在项目里用了组件按需导入配合unplugin-element-plus插件这类问题基本不再出现。9. 毕设答辩要点与后续扩展建议最后聊几个答辩时很加分的小技巧。项目演示不要登录后直奔管理后台而是按照游客→注册→找书→写笔记→管理端审核这条故事线走把平台的完整业务闭环展示出来比零散地演示功能效果好得多。被问到这个项目你遇到最大的困难是什么不要回答没有困难挑一个具体的坑讲比如跨域问题说清楚你是什么时候发现的、排查思路是什么、最后怎么解决的老师对这类回答印象非常好。项目做完想继续提升的话有几个方向可以考虑搜索引擎用Elasticsearch替代MySQL的LIKE模糊查询提升搜索性能笔记内容增加Word/PDF导出功能这只需要Java后端加一个docx模板渲染用Redis存储点赞数和浏览量降低数据库压力把前台从多页面跳转升级为单页滚动加载加无限下拉。这些点单独拿出来每一个都可以作为你在答辩时说对未来展望的话题。我自己做了几个版本之后整体的体会是这类平台型项目最重要的不是功能堆砌而是把用户能正常写、正常读、正常互动这条链路打磨得足够顺。你哪怕只做一套书籍和笔记的完整交互做得精致也比做了一堆半吊子功能强。起步阶段代码可以粗糙但每个模块一定要亲手敲一遍尤其是JWT鉴权和联表查询这两块它们不是背下来就能过而是写一遍才有感觉。最后再分享一个小建议如果你打算把这个项目当作毕设从现在开始就把每个表每个字段的作用写进文档答辩时老师随便指一个字段你都能说清楚它是干嘛的这份从容感比任何花哨的代码都值钱。