ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue笔记分享系统:可直接运行的全栈开源实战项目

SpringBoot+Vue笔记分享系统:可直接运行的全栈开源实战项目 大家在找 SpringBoot Vue 全栈开源项目的时候应该都体会过那种心情源码包下了十几个不是缺数据库脚本就是前端依赖装到一半报错再不济就是代码版本太老跑不起来。这套“笔记记录分享网站信息管理系统”之所以值得拿出来单独聊聊是因为它解决了上面所有这些痛点——SpringBoot 后端、Vue 前端、MySQL 存储三件套齐全数据库脚本、接口文档、前端工程都是配套好的下载之后按步骤配置就能直接跑起来拿来学习、做课程设计或者二次开发接私活都够用。这套项目虽然名字听着像个简单的“记笔记”工具但它实际上覆盖了一条非常完整的全栈链路用户注册登录、笔记的增删改查、分类管理、笔记分享展示、搜索筛选甚至还包括了评论互动这类社交属性功能。换句话说这不是一个只停留在“能跑通”层面的教学 Demo而是一个具备真实业务形态的信息管理系统。我从三个角度来说一下这篇文章的适用人群你对照看看自己在不在里面刚学完 Java Web 基础、想找一个完整项目练手的人这个项目的代码分层非常规范Controller、Service、Mapper 三层结构清晰你能从里面学会怎么组织一个真实项目的代码。正在做 Java 课程设计或者毕业设计的学生笔记分享系统的功能完整性、业务复杂度都刚好卡在“够丰富但不至于做不完”的区间而且源码可直接运行省去大量从头开发的时间。想快速给甲方或者自己搭一套内容管理后台的开发者前端页面是现成的后端接口是通用的你只需要改改业务字段、调整下数据库表结构就能迁移成其他类型的分享系统比如食谱分享、学习资料分享、电子书收藏管理。接下来我把这套项目的核心架构、关键代码逻辑、数据库设计、运行步骤以及我在实际部署过程中踩过的坑全部拆开来讲保证你看完不仅能把它跑起来还能摸透它背后的设计思路。1. 笔记分享系统的真实业务形态这对项目到底实现了什么在没有打开源码之前先花两分钟搞清楚这套系统“长什么样”。一个笔记记录分享网站表面上看起来就是一个普通的 CRUD 项目但往深了看它其实包含了内容型 Web 系统最典型的产品结构从用户维度看有注册、登录、权限区分从内容维度看有笔记的创建、编辑、查看、删除、分类从互动维度看有笔记的公开分享、浏览、搜索、评论和收藏。这些东西拼在一起就是一个很完整的 CMS内容管理系统雏形。1.1 从用户角度拆解出来的功能全景我按角色把系统的功能切成两条线这样你理解起来会更清晰普通登录用户能做的事注册账号并登录系统密码经过加密存储不是明文的创建自己的笔记填写标题、正文内容、选择分类、设置是否公开对自己创建的笔记进行编辑和删除操作但无法修改别人的笔记浏览公开笔记列表按分类筛选通过关键字搜索笔记对感兴趣的笔记进行收藏收藏之后可以在个人中心统一查看在笔记下方发表评论也可以删除自己发过的评论在个人中心查看自己发布的笔记数量、收藏记录、评论记录等统计信息。游客未登录用户能做的事浏览公开笔记列表和笔记详情按分类查看笔记使用搜索功能查找公开笔记看到公开笔记下的评论列表但无法发表评论或收藏笔记。你注意看这个权限设计思路它把“可浏览”和“可操作”两个层级分开了。即使你不登录也能看到别人分享的笔记内容这是“分享”属性的体现而一旦涉及评论、收藏、写笔记这些会产生数据的操作就必须先登录这是“信息管理系统”的边界意识。很多初学者在写类似系统的时候最容易犯的错就是把所有功能都塞在登录态后面看起来安全了但实际上完全破坏了分享类产品的信息开放逻辑。1.2 管理员的额外控制能力系统里还预留了管理员角色的能力。管理员账号登录之后在原有用户功能的基础上还能看到所有用户的注册信息、所有笔记的数据列表可以对违规笔记做强制下架也可以对违规评论进行删除操作。从代码层面来看这种角色区分的实现方式并不复杂——后端通过拦截器校验请求头中的 Token解析出用户角色字段然后在需要进行权限控制的接口上做一次角色判断。下一章我会专门讲这个校验机制的实现细节这里先有个概念就行权限控制是全栈项目里绕不开的硬骨头而它控制得好不好直接决定系统能不能从“课程设计”升级成“可对外交付的产品”。1.3 为什么笔记系统是练手全栈的最佳载体我说句大实话市面上能练手的全栈项目有很多购物系统、博客系统、后台管理系统但笔记分享系统是其中性价比很高的一个选题。原因有三点第一业务模型简单但完整。笔记这个实体没有太复杂的业务流转不存在订单状态机、支付回调、库存扣减这类高难度逻辑但用户、分类、内容、评论、收藏这几张表的关系正好把一对一、一对多、多对多三种最常见的数据库关系全部涵盖了。一个项目学完你对关系型数据库的设计认知就是完整的。第二技术点覆盖广。SpringBoot 端的拦截器、异常处理、参数校验、MyBatis 映射、事务管理Vue 端的组件通信、路由守卫、Axios 封装、状态管理这些全栈开发里的高频知识点在这个项目里都能找到对应的落点。你不需要去各个 Demo 里东拼西凑学知识一个项目就能串起来。第三可延展性强。笔记系统加个标签功能就是博客系统把“笔记”换成“文件”就是网盘系统换成“问题”就是问答社区。你学会这一套以后再做别的管理系统改改实体类和数据表就行底层的用户认证、权限控制、前后端交互逻辑是可以平移复用的。2. 技术栈选型的底层逻辑SpringBoot、Vue、MySQL 这套组合到底好在哪聊完了业务接下来聊一个很多人容易忽略的问题为什么这个项目用的是 SpringBoot Vue MySQL而不是 Spring Cloud 微服务也不是直接用若依这类脚手架这里面的选型逻辑直接关系到项目的上手难度和实用性。2.1 SpringBoot 在 Java 后端领域里的位置SSHStruts Spring Hibernate时代和 SSMSpring SpringMVC MyBatis时代Java 后端最大的痛点就是配置繁琐。你要引入一个依赖要写 XML 配置要配数据源要配事务管理器前后可能要折腾半天。SpringBoot 的诞生核心就是解决“配置地狱”这个问题。在笔记分享系统这个场景里SpringBoot 带来的实际收益有三点内嵌的 Tomcat 服务器不需要单独装一个 Tomcat也不用把 War 包丢到 webapps 目录下直接通过java -jar命令就能启动应用部署成本极低自动配置机制引入spring-boot-starter-web之后Spring MVC 的基础配置、默认序列化器、静态资源映射规则全都自动配好了你只需要关心自己的业务代码生态整合的便利性配合 MyBatis 的 Starter、MySQL 驱动、JWT 工具包依赖管理和版本兼容问题被大幅简化。这就是为什么这套系统会选择 SpringBoot 而不是 SSM——同样是 Java 后端SpringBoot 把你的时间从“配置环境”里解放出来让你把精力放在“写业务”上。2.2 Vue 在前端领域的优势前端三大框架里Vue 的入门曲线是最平缓的而且它的渐进式设计决定了它特别适合这种中后台管理系统。笔记分享网站的页面形态包含大量数据渲染场景——笔记列表要循环渲染卡片分类侧边栏要根据数据动态生成评论区域要实时刷新——这些场景正好是 Vue 响应式数据的强项。更关键的一点是Vue 单文件组件SFC的结构把 HTML、CSS、JavaScript 写在一个.vue文件里对后端开发者来说特别友好。你不需要在多个文件之间反复跳转找对应的逻辑一个组件从模板到脚本到样式全部收拢在一起模块边界非常清楚。项目里用的 Vue 版本大概率是 Vue 2.6 或者 Vue 2.x 系列配合 Element UI 组件库这套组合在国内的存量项目中占比非常大。为什么不用 Vue 3因为在真实的就业市场和课程设计场景里Vue 2 Element UI 仍然占据着大量企业项目的存量空间学习这套组合可以直接复用进大多数公司的实际业务中。当然如果你本地环境允许升到 Vue 3 Element Plus 也不是难事这个我会在最后一章兼容性部分讲。2.3 MySQL 为什么依然是首选数据库前后端技术栈可能有争议但数据库这一层MySQL 基本没有悬念。原因很简单笔记系统是典型的关系型数据存储场景数据结构稳定关系明确事务要求高比如用户注册时插入用户记录和初始化默认分类需要在同一个事务里而 MySQL 是关系型数据库里开源免费、资料最多、跨平台能力最稳的方案。选择 MySQL 需要注意的版本差异我在最后一章会专门讲——MySQL 5.7 和 MySQL 8.x 在驱动类名、时区配置、连接 URL 写法上都有细微区别这是部署 SpringBoot 项目时新手最容易踩的一个隐藏坑。2.4 前后端分离架构的优点与代价这套系统采用的是前后端完全分离的架构前端 Vue 工程通过 Axios 发送 HTTP 请求访问后端接口后端不返回页面只返回 JSON 数据。这种架构的好处是前后端可以并行开发、独立部署前端代码可以单独托管到 Nginx 上做负载均衡后端服务可以水平扩展。对学习者来说前后端分离的架构也更贴近企业真实开发模式。当然分离架构也带来了一些代价最典型的就是跨域问题。前端跑在 8080 端口后端跑在 8081 端口假设前端请求后端接口时浏览器会因为同源策略拦截请求。项目的解决方案是后端配置了全局跨域过滤器允许前端地址跨域访问。这个过滤器的代码在项目源码里是一个单独的配置类注释写得也很清楚你可以直接参考。3. SpringBoot 后端源码的核心模块拆解从控制器到数据库访问层这一章是整个项目源码的重头戏。我按照后端代码的分层结构从外到内逐层拆解你看完就能明白一个标准的 SpringBoot 业务模块是怎么组织起来的。3.1 后端工程的目录结构与包职责后端工程通常叫server或backend目录的 Java 包结构一般是这样组织的com.example.note ├── config # 配置类跨域、拦截器等 ├── controller # 接口层接收前端请求返回数据 ├── service # 业务层处理业务逻辑 │ └── impl # 业务实现类 ├── mapper # 数据访问层MyBatis 接口 ├── entity # 实体类和数据库表一一对应 ├── dto # 数据传输对象接收前端参数、返回封装数据 ├── vo # 视图对象给前端返回的定制化数据 ├── utils # 工具类JWT 生成与校验、密码加密等 ├── common # 公共类统一返回结果、异常处理等 └── interceptor # 拦截器登录态校验、权限校验这个分层结构非常标准controller只负责参数接收和结果返回不写业务逻辑service负责核心业务处理是所有逻辑的归属地mapper只做数据存取动作不写任何业务判断。三层各司其职这也是为什么很多课程设计老师会推荐学生参照这个结构写项目——它规范、好懂、方便维护。3.2 从一次“创建笔记”请求看完整调用链我看代码有一个习惯就是跟着一条业务请求从入口走到数据库把整条链路摸清楚。这里以“创建笔记”为例给你完整走一遍前端Notes.vue页面里的“保存笔记”按钮被用户点击触发submitForm方法收集表单里的标题、正文、分类 ID、是否公开等字段Axios 发送POST /api/note/add请求请求头自动带上本地存储的 Token后端NoteController.addNote(RequestBody NoteDTO noteDTO)接收请求通过RequestBody注解把 JSON 参数反序列化成 NoteDTO 对象控制器调用noteService.addNote(dto)方法Service 层先判断当前登录用户是否有效再判断笔记标题不能为空、分类 ID 必须存在这些校验逻辑在业务层完成Service 层把 DTO 转成 Note 实体类调用noteMapper.insert(note)MyBatis 通过 SQL 映射执行 INSERT 语句把数据写入 note 表返回结果逐层向外返回最终在 Controller 层包装成统一格式的 JSON{ code: 200, message: success, data: { noteId: 18 } }返回给前端前端收到成功响应提示“保存成功”刷新笔记列表。这条链路里每一层都有自己明确的职责这就是分层思想的意义。如果你打算把这套代码作为课程设计提交论文的系统设计部分直接按照这条链路来描述各层之间的关系评审老师基本挑不出毛病。3.3 JWT 登录认证与拦截器权限检查的完整实现权限校验是这套系统里安全性的核心。项目的实现方案是标准的 JWTJSON Web Token无状态认证模式具体流程如下用户提交账号密码后后端校验通过生成一个包含用户 ID 和角色的 Token 字符串返回给前端。前端拿到 Token 后存储在 localStorage 中之后每次请求都在 Header 里携带Authorization: Bearer {token}。后端定义一个拦截器拦截所有/api/**路径下的请求从 Header 中取出 Token解析、校验签名、判断是否过期校验通过就把用户信息放入请求上下文。拦截器里面的核心逻辑用文字描述大致是这样的public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { // 如果是预检请求直接放行 if (OPTIONS.equalsIgnoreCase(request.getMethod())) { return true; } // 从请求头获取 token String token request.getHeader(Authorization); if (token null || !token.startsWith(Bearer )) { // 未登录返回 401 return false; } // 使用 JWT 工具类解析 token Claims claims JwtUtil.parseToken(token.replace(Bearer , )); if (claims null) { // token 无效或过期返回 401 return false; } // 将用户 id 和角色存入 request 属性中供后续业务使用 request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; }这段逻辑不复杂但里面有两个非常容易踩坑的细节第一个细节是OPTIONS 预检请求必须放行。浏览器在发送跨域 POST 请求之前会先发送一个 OPTIONS 请求来试探服务器允不允许跨域。如果拦截器把 OPTIONS 请求也拦了前端就会一直报“跨域请求失败”而这个错误在浏览器控制台里显示的往往是 CORS 错误很容易误导你往跨域配置方向排查浪费大量时间。第二个细节是Token 过期时间的设计。项目里一般会把 Token 过期时间设置为 24 小时或者 7 天。时间设短了用户体验差用着用着就被迫重新登录时间设长了安全性下降Token 一旦泄露别人就能在很长一段时间内以你的身份操作。对这个项目来说建议设置为 24 小时同时在前端 Axios 响应拦截器里做 401 的全局跳转处理让用户自动回到登录页。3.4 统一返回结果与全局异常处理不知道你看别人老项目的时候有没有遇到过这种情况第一个接口返回{ code: 0, msg: success, data: ... }第二个接口返回{ status: 200, message: ok, result: ... }第三个接口直接返回一个数组……这就是典型的没有统一返回结构前后端联调时沟通成本极高。这套项目的做法是在 common 包里定义一个统一的返回类Result封装三个字段code、message、data。所有接口都返回这个类型前端拿到响应后只要判断code 200就能确认业务成功逻辑非常一致。配合统一返回类的是全局异常处理器。使用RestControllerAdvice注解定义一个全局异常拦截类针对参数校验异常、业务异常、未登录访问、服务器内部错误分别定义处理方法返回对应的错误码和信息。这样后端代码里就不需要到处写 try-catch正常的业务逻辑里出错了异常会被全局处理器统一捕获并转成标准的 JSON 格式返回给前端。这个设计带来的实际价值是前端可以只写一套错误提示逻辑后端无论哪个接口出问题返回结构都一样弹个提示条就行。3.5 笔记查询接口多条件组合查询的实现技巧笔记列表页面需要支持分页、分类筛选、关键字搜索以及按时间排序这种多条件组合查询是后端开发里的高频需求。项目里的实现方式是使用 MyBatis 的动态 SQL通过where标签配合if标签拼装查询条件select idselectNoteList resultTypecom.example.note.vo.NoteVO SELECT n.*, u.nickname, c.name as categoryName FROM note n LEFT JOIN user u ON n.user_id u.id LEFT JOIN category c ON n.category_id c.id where if testkeyword ! null and keyword ! AND (n.title LIKE CONCAT(%, #{keyword}, %) OR n.content LIKE CONCAT(%, #{keyword}, %)) /if if testcategoryId ! null AND n.category_id #{categoryId} /if if testonlyPublic AND n.is_public 1 /if /where ORDER BY n.create_time DESC LIMIT #{offset}, #{pageSize} /select这段 SQL 里有几个值得特别说明的设计LEFT JOIN 关联用户表和分类表是为了在列表页一次性拿到笔记作者的昵称和分类名称避免前端拿到 userId 和 categoryId 之后再发起两次额外的查询这是典型的联表查询优化思路关键字搜索用LIKE配合CONCAT(%, #{keyword}, %)注意这里不能用字符串拼接% #{keyword} %因为 MyBatis 的#{}是预编译占位符能有效防 SQL 注入而字符串拼接会产生注入风险。这个细节在面试时经常被问到分页用了 MySQL 的LIMIT语法offset (pageNum - 1) * pageSize的计算通常放在 Service 层完成Controller 只接收pageNum和pageSize两个参数。4. Vue 前端源码的核心实现页面组件、路由与请求封装后端再完善前端一塌糊涂也白搭。这套项目的 Vue 端虽然页面风格不一定多华丽但工程结构很规范每个功能模块的落点都很清楚。我来把前端的关键实现拆一遍。4.1 前端目录结构与组件划分Vue 前端工程通常叫web或frontend的核心目录大致长这样src ├── api # API 请求接口封装 │ ├── user.js # 用户相关接口 │ ├── note.js # 笔记相关接口 │ └── comment.js # 评论相关接口 ├── assets # 静态资源 ├── components # 公共组件 │ ├── NavBar.vue # 顶部导航栏 │ ├── CategoryMenu.vue# 分类侧边栏 │ └── NoteCard.vue # 笔记卡片组件 ├── router # 前端路由 │ └── index.js ├── store # Vuex 状态管理或简单项目用本地存储 ├── views # 页面级组件 │ ├── Login.vue # 登录页 │ ├── Register.vue # 注册页 │ ├── Home.vue # 首页/笔记广场 │ ├── NoteDetail.vue # 笔记详情页 │ ├── NoteEdit.vue # 笔记编辑页 │ ├── MyNotes.vue # 我的笔记 │ ├── Favorites.vue # 我的收藏 │ └── CategoryManage.vue # 分类管理管理员 ├── App.vue └── main.js这个结构最值得学习的点在于API 层独立封装。所有的后端接口调用全部收敛在src/api目录下页面组件里不直接写 Axios 请求而是引入对应的 API 方法。比如NoteDetail.vue页面里只需要调用getNoteDetail(noteId)至于这个请求是 GET 还是 POSTURL 是什么完全不用关心。一旦后端接口地址变化你只需要修改note.js一个文件不会牵动任何页面代码。4.2 路由与导航守卫前端的最后一道权限闸门前端路由用的是 Vue Router。路由表里有两类页面不需要登录就能访问的登录页、注册页、公开笔记列表、笔记详情以及必须登录才能访问的我的笔记、创建笔记、收藏、后台管理。实现的方式就是在路由配置里给需要登录的页面添加meta: { requiresAuth: true }标记然后在全局前置守卫中统一判断router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.requiresAuth !token) { // 需要登录但未登录跳转到登录页 next({ path: /login, query: { redirect: to.fullPath } }); } else { next(); } });登录成功后跳回 redirect 参数指向的页面这个小细节很少有人会写但体验差异很明显——用户第一次访问收藏页被踢去登录登录完应该回到收藏页而不是回到首页让他重新点进去。需要强调一点前端路由守卫只是用户体验层面的优化真正的安全校验必须以后端拦截器为准。因为前端代码是暴露在浏览器里的用户完全可以通过修改 JS 绕过前端守卫直接发请求。所以哪怕是课程设计也应该把后端拦截器的校验做完整这是系统的安全底线。4.3 Axios 请求封装与拦截器前端请求层是这套系统里最能提现“工程意识”的部分。项目在src/api/request.js或类似文件里创建了一个统一的 Axios 实例并注入了请求拦截器和响应拦截器。请求拦截器做两件事从 localStorage 里取出 Token 并加到请求头统一设置请求超时时间。响应拦截器做三件事判断 HTTP 状态码是否为 200解析返回体的 code 字段如果业务失败则弹出错误提示如果检测到 HTTP 401 状态码说明 Token 过期或未登录清除本地 Token 并跳转到登录页。这样做的好处是前端每个具体业务页面里不需要重复写错误判断逻辑。发起一次请求要么正常拿到data继续往下走要么已经通过全局提示和跳转把异常情况处理完了代码会非常清爽。4.4 笔记编辑页设计富文本还是纯文本笔记系统的核心是什么是记笔记。而记笔记的核心体验就是编辑页面好不好用。这套项目里笔记编辑页支持纯文本和简单的 HTML 格式输入标题用 Input 框正文用 Textarea 或多行文本域配合 Element UI 的表单组件可以做到字数统计、必填校验、分类下拉选择。如果你是二次开发想让笔记支持富文本编辑加粗、标题、图片插入可以集成 wangEditor 或 Quill 编辑器。这个扩展思路的操作成本并不高前端在NoteEdit.vue里把 Textarea 替换成富文本组件后端对应字段的类型从TEXT扩容到MEDIUMTEXT就行。但要注意富文本内容包含 HTML 标签后端展示时需要特殊处理 XSS 防御不能直接 v-html 渲染用户输入的内容否则会留下安全漏洞。5. 数据库表结构设计笔记系统的数据基石数据库设计是全栈项目里最容易被忽视、又最能体现功底的部分。这套系统的表结构虽然不算复杂但它的设计思路非常规范我把它拆出来单独分析因为理解了这张表的设计你就理解了这类内容分享类系统的数据底层。5.1 核心表的字段设计与关系解读系统核心的数据表大致有四张用户表、笔记表、分类表、评论表外加一张收藏关系表。各表的字段设计思路如下用户表user字段名类型说明idbigint主键自增usernamevarchar(50)用户名唯一索引passwordvarchar(255)BCrypt 加密后的密码nicknamevarchar(50)昵称avatarvarchar(255)头像地址roletinyint角色0 普通用户1 管理员create_timedatetime注册时间密码字段用varchar(255)而不是varchar(32)这件事我特别想拿出来说一下。很多刚学编程的同学习惯用 MD5 加密后塞进 32 位字符串但 MD5 已经被证明是不安全的碰撞攻击、彩虹表攻击都很成熟。项目用的是 BCrypt 加密算法生成的哈希串长度是 60 位所以字段类型必须留够空间。BCrypt 是加盐加密每次加密同一个密码得到的哈希值都不同安全性远超 MD5。笔记表note字段名类型说明idbigint主键user_idbigint作者 ID外键关联用户表category_idbigint分类 IDtitlevarchar(200)笔记标题contenttext正文内容is_publictinyint是否公开1 公开0 私密view_countint浏览计数like_countint点赞计数create_timedatetime创建时间update_timedatetime更新时间view_count和like_count这两个字段是典型的冗余字段设计。严格来说浏览量可以通过日志统计得出点赞数可以通过关系表 count 统计得出但是每次都去 count 一遍数据量一大查询性能就扛不住了。所以项目选择直接在笔记表上维护一个计数字段每次浏览 1、每次点赞 1这种空间换时间的思路在实际项目里应用非常广。分类表category字段名类型说明idbigint主键namevarchar(50)分类名称descriptionvarchar(255)分类描述sort_orderint排序权重评论表comment字段名类型说明idbigint主键note_idbigint所属笔记 IDuser_idbigint评论者 IDcontentvarchar(500)评论内容create_timedatetime评论时间收藏表favorite字段名类型说明idbigint主键user_idbigint收藏用户 IDnote_idbigint被收藏的笔记 IDcreate_timedatetime收藏时间收藏表是一张典型的关联表用户和笔记之间是多对多的关系一个用户可以收藏多篇笔记一篇笔记可以被多个用户收藏。关联表里不需要存冗余的用户信息和笔记信息只要存 ID 就行查询时 JOIN 连表拿数据。这是数据库设计里最基础但也最重要的思想一旦这个道理通透了你设计任何多对多关系的业务比如用户关注话题、角色分配权限都会觉得顺手很多。5.2 逻辑删除还是物理删除这套系统的笔记删除功能我在看源码的时候注意到它采用了逻辑删除策略——笔记表里加了一个deleted字段删除操作其实是执行 UPDATE 把deleted置为 1而不是执行 DELETE 把记录真正删掉。为什么这样做站在产品经理的视角用户删除笔记后可能会后悔如果采用物理删除数据一旦没了就彻底找不回来站在开发者的视角评论、收藏这两张表都关联着笔记 ID如果笔记被物理删除外键关联的数据会变成悬空状态处理起来非常麻烦。逻辑删除的代价是业务里的每个 SELECT 查询都需要手动加上WHERE deleted 0条件。项目里的做法是在 MyBatis XML 中的公共 SQL 片段里预置这个条件或者使用 MyBatis 的拦截器统一注入。这样即使某个地方忘了加条件拦截器也会帮你兜底。5.3 索引设计策略项目在数据库初始化脚本里已经建好了几张表的基础索引但我建议你在本地运行的时候再根据实际查询习惯补充两条索引笔记表上(is_public, create_time)的联合索引。因为首页的公开笔记列表就是按照这个条件查询的is_public 1过滤 create_time排序联合索引可以避免文件排序提升列表页的响应速度收藏表上的(user_id, note_id)唯一索引。这个索引既能保证同一个用户不会重复收藏同一篇笔记业务上通过唯一索引做幂等约束也能加速个人收藏列表的查询。索引不是越多越好每个索引都会拖慢 INSERT 和 UPDATE 的速度。像“我的笔记”查询、评论查询这类场景如果没有明显的性能瓶颈就不需要额外加索引避免过度设计。6. 从零环境准备到本地运行完整实操步骤标题里赫然写着“可直接运行”这五个字意味着什么意味着你把源码下载下来之后只要按照说明部署不需要改代码就能把整个系统跑起来。但我在帮几十个同学远程排查过运行问题之后可以负责任地告诉你绝大多数人第一次运行还是跑不起来而且问题往往不在代码本身而是卡在环境和配置上。这一章我把从零开始的完整环境准备和运行步骤写一遍你照着走一遍基本不会跑偏。6.1 本地开发环境清单先把这套系统运行所需要的基础环境列出来你可以对着检查自己的电脑缺不缺软件版本建议用途JDK1.8 或 11编译运行 SpringBoot 后端Maven3.6 及以上依赖管理、项目构建Node.js12.x 至 16.x前端依赖安装与运行npm6.x 及以上随 Node.js 自带前端包管理器MySQL5.7 或 8.0数据存储Navicat / SQLyog可选可视化操作数据库也可用命令行IDEIDEA / VS Code后端推荐 IDEA前端推荐 VS Code注意这是基于项目原本的技术版本来推荐的。如果你下载的源码是 SpringBoot 2.x 的JDK 8 就够用如果是 3.x 版本JDK 就得升级到 17 以上。具体看项目里的pom.xml文件中 spring-boot-starter-parent 的版本号就能判断出来。6.2 MySQL 初始化数据库创建与数据导入拿到源码包后通常在/sql或者/doc目录下能找到init.sql或者note_share.sql的数据库脚本。打开看一眼脚本里一般包含了建库语句、建表语句和初始化测试数据。初始化数据库的操作步骤如下打开 MySQL 命令行工具用 root 账号登录执行CREATE DATABASE note_share DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;创建数据库注意使用 utf8mb4而不是 utf8否则 emoji 表情存储会报错使用USE note_share;切换到目标数据库执行source /你的路径/init.sql;导入建表语句和初始数据执行SHOW TABLES;检查表是否导入成功。如果你用的是 Navicat操作会更简单——新建连接连接上 MySQL右键“运行 SQL 文件”选择脚本即可。这里我再强调一遍 utf8mb4 的重要性否则遇到用户昵称里带个表情符号插入数据库直接报Incorrect string value排查起来很费劲。6.3 后端配置文件修改与启动后端工程里src/main/resources/application.yml文件是连接一切配置的枢纽。你需要重点修改以下几项server: port: 8081 spring: datasource: url: jdbc:mysql://localhost:3306/note_share?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/Shanghai username: root password: 你的数据库密码 driver-class-name: com.mysql.cj.jdbc.Driver如果你的 MySQL 是 5.7 及以下版本driver-class-name要换成com.mysql.jdbc.Driver不带 cj这是 MySQL 8 驱动类名和 5.x 版本之间的一个明显差异。serverTimezoneAsia/Shanghai这个参数也是后端和数据库之间时间差问题的老熟人了不配置的话你存入数据库的时间可能会比本地时间慢 8 小时或者直接报时区错误。配置修改完成后打开 IDEA导入后端 Maven 工程等待依赖下载完毕。点开NoteApplication.java或类似主类右键 Run 启动。控制台出现Tomcat started on port(s): 8081字样就代表后端启动成功了。6.4 前端环境准备与启动前端工程目录下执行以下命令# 进入前端项目目录 cd web # 安装依赖首次运行需要可能耗时几分钟 npm install # 启动开发服务器 npm run serve依赖安装过程中最常见的报错是node-sass安装失败——这个包需要从 GitHub 下载二进制文件网络不稳定时很容易失败。解决方案是用 npm 淘宝镜像源来安装执行npm config set registry https://registry.npmmirror.com如果 node-sass 还是装不上可以在项目根目录建一个.npmrc文件写入sass_binary_sitehttps://npm.taobao.org/mirrors/node-sass/重新安装即可。前端启动成功后控制台会显示类似App running at: http://localhost:8080的地址。打开浏览器访问这个地址能看到系统首页就代表前后端都跑起来了。用管理员账号登录后台注册一个新用户测试一下写笔记、收藏、评论的完整流程整个项目就跑通了。6.5 常见端口冲突的应急处理开发环境跑多个项目时8080 和 8081 被占用是非常常见的事。后端端口冲突解决方式直接在 application.yml 里改server.port为 8082 或任意空闲端口。前端端口冲突解决方式在vue.config.js里增加配置devServer: { port: 8082 }或者启动时通过命令行参数指定npm run serve -- --port 8082。如果前端项目里配置了后端接口的 baseURL通常在src/api/request.js中你改动后端端口后记得同步修改 baseURL 里的端口号保持前后端指向一致。7. 那些官方文档不会告诉你的坑端口、跨域、依赖版本与兼容性最后这一章我把在多次部署和帮人排错过程中积累的经验集中列出来每一段都是真金白银踩出来的。这些坑单看都不复杂但叠加在一起足以让一个新手卡住一整个晚上。7.1 跨域配置CORS 的三种实现方式和踩坑点前后端分离项目跨域是绕不开的第一关。这套系统在config包里用 Spring 的WebMvcConfigurer重写了addCorsMappings方法配置允许的来源地址、请求方法、请求头以及是否允许携带凭证。配置的方法大致如下Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true) .maxAge(3600); } }一个很容易被忽略的点allowCredentials(true)与allowedOriginPatterns(*)的组合意味着前端请求可以携带 Cookie但生产环境如果允许任意来源跨域并携带凭证有 CSRF 攻击风险。部署到公网时建议把allowedOriginPatterns改成前端部署的实际域名窄化信任边界。另一个常见的问题是如果项目里引用了 Spring SecurityCORS 配置必须放在安全过滤链上而不是简单地写一个WebMvcConfigurer两者在过滤器链中的位置不同会导致 CORS 配置“写了没用”。这套项目因为没引入 Security 才能直接用这个方案你二次开发如果需要加 Security这个坑要注意。7.2 MySQL 8 时区与驱动相关问题MySQL 8 相比 5.7 做了很多默认值上的调整其中两个在 SpringBoot 连接时表现得尤其突出。第一个是时区问题。MySQL 8 的安装器在初始化时会默认使用系统时区但如果你用 Docker 安装 MySQL 8容器默认时区往往是 UTC导致后端插入数据时时间比北京时间晚 8 小时。解决办法是在 JDBC URL 后面追加serverTimezoneAsia/Shanghai这个参数我之前在配置章节已经强调过了。第二个是驱动类名变更。MySQL 8 之后官方把驱动类从com.mysql.jdbc.Driver迁移到了com.mysql.cj.jdbc.Driver如果版本与驱动不匹配启动时大概率会直接报ClassNotFoundException或者Unsupported major.minor version。所以下载源码后先检查 pom.xml 里 mysql-connector-java 的版本再对应检查驱动类名能省去大量排查时间。7.3 前端依赖版本冲突Element UI 与 Vue 2 的兼容关系这套项目前端用的是 Element UI 组件库。Element UI 从 2.x 版本开始只支持 Vue 2.x不支持 Vue 3。如果你手贱把前端项目升到 Vue 3然后直接引入 Element UI页面会白屏控制台报错说组件注册失败。解决方案很明确如果想留在 Vue 2就用 Element UI 的最新版本2.15.x如果非要用 Vue 3就必须把组件库换成 Element Plus而且很多组件 API 有变化需要整体走一遍适配。同样的道理适用于 Vue Router 和 VuexVue 2 对应的是 Vue Router 3 和 Vuex 3Vue 3 对应的是 Vue Router 4 和 Vuex 4。升级主版本后路由创建方式从new Router()变成了createRouter()状态管理从new Vuex.Store()变成了createStore()。这一整套连锁反应是前端版本升级最头疼的地方。7.4 登录后 Token 丢失与页面刷新问题有些同学跑起来之后遇到一个很怪的现象登录成功跳转到首页一切正常。但只要按一下 F5 刷新页面就被踢回登录页了。这个问题的根源几乎都在 localStorage 和 sessionStorage 的选择上。如果前端把 Token 存在 sessionStorage 里那 sessionStorage 的生命周期是“标签页存活期间”关闭标签页或刷新时行为可能因浏览器实现而异部分场景下会导致 Token 丢失。而 localStorage 的存储是持久化的关闭浏览器再打开只要没手动清除Token 就还在。配合这个问题的是路由守卫的判断时机刷新页面时前端会重新执行router.beforeEach此时它会去 localStorage 里检查 Token。如果后端返回的登录接口数据里确实包含了 Token并且前端登录成功回调里写了localStorage.setItem(token, response.data.token)刷新后就不会被踢。这个排查思路你可以拿去对照自己的项目十有八九是存储选型写错了。7.5 后端启动慢与端口未释放的排查在做课程设计答辩前最怕的就是现场演示时后端启动失败。我遇到过一次很尴尬的情况是IDEA 里反复点击 Run但每次都提示端口被占用原因是前一次运行的程序没有正常终止进程仍然占着 8081 端口。排查命令很简单Windows 下执行netstat -ano | findstr 8081查到的 PID 再执行taskkill /F /PID 进程号macOS / Linux 下用lsof -i :8081 kill -9 进程号这类环境问题其实不难解决但如果你不熟悉命令行的操作光靠 IDEA 的界面提示很容易变成无头苍蝇。提前掌握这几条命令现场演示的时候会从容很多。另外一个“启动慢”的常见原因是Maven 首次下载依赖时需要从中央仓库拉取几百个 jar 包网速稍微慢点就要等十几分钟中间还会提示下载失败。解决方案是给 Maven 配置阿里云镜像在settings.xml的mirrors节点中加入镜像源这个操作能直接把依赖下载速度提升几个量级建议一拿到新电脑就先配置好。这套笔记记录分享网站我在本机前后跑了不下三遍每次跑都会对某个细节产生新的理解。它不是一个“一眼就能看穿所有代码”的简单项目而是真正能陪你从 SpringBoot 入门走到能够独立加班加点做业务的进阶基石。无论你是为了交课程设计还是想通过一个完整项目把这些知识点串成线这套代码都值得你花上一个周末把每一层都吃透。遇到跑不起来的情况别急着怀疑源码有问题先从环境、配置、端口这三个维度自查——这三板斧解决掉百分之九十的问题都会消失。
返回列表