SpringBoot+Vue笔记分享系统:可直接运行的全栈开源实战项目
2026/9/24 21:03:36 网站建设 项目流程

大家在找 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 后端领域里的位置

SSH(Struts + Spring + Hibernate)时代和 SSM(Spring + 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 后端工程的目录结构与包职责

后端工程(通常叫serverbackend目录)的 Java 包结构一般是这样组织的:

com.example.note ├── config # 配置类:跨域、拦截器等 ├── controller # 接口层:接收前端请求,返回数据 ├── service # 业务层:处理业务逻辑 │ └── impl # 业务实现类 ├── mapper # 数据访问层:MyBatis 接口 ├── entity # 实体类:和数据库表一一对应 ├── dto # 数据传输对象:接收前端参数、返回封装数据 ├── vo # 视图对象:给前端返回的定制化数据 ├── utils # 工具类:JWT 生成与校验、密码加密等 ├── common # 公共类:统一返回结果、异常处理等 └── interceptor # 拦截器:登录态校验、权限校验

这个分层结构非常标准,controller只负责参数接收和结果返回,不写业务逻辑;service负责核心业务处理,是所有逻辑的归属地;mapper只做数据存取动作,不写任何业务判断。三层各司其职,这也是为什么很多课程设计老师会推荐学生参照这个结构写项目——它规范、好懂、方便维护。

3.2 从一次“创建笔记”请求看完整调用链

我看代码有一个习惯,就是跟着一条业务请求从入口走到数据库,把整条链路摸清楚。这里以“创建笔记”为例,给你完整走一遍:

  1. 前端Notes.vue页面里的“保存笔记”按钮被用户点击,触发submitForm方法,收集表单里的标题、正文、分类 ID、是否公开等字段;
  2. Axios 发送POST /api/note/add请求,请求头自动带上本地存储的 Token;
  3. 后端NoteController.addNote(@RequestBody NoteDTO noteDTO)接收请求,通过@RequestBody注解把 JSON 参数反序列化成 NoteDTO 对象;
  4. 控制器调用noteService.addNote(dto)方法,Service 层先判断当前登录用户是否有效,再判断笔记标题不能为空、分类 ID 必须存在,这些校验逻辑在业务层完成;
  5. Service 层把 DTO 转成 Note 实体类,调用noteMapper.insert(note)
  6. MyBatis 通过 SQL 映射执行 INSERT 语句,把数据写入 note 表;
  7. 返回结果逐层向外返回,最终在 Controller 层包装成统一格式的 JSON:{ "code": 200, "message": "success", "data": { "noteId": 18 } }返回给前端;
  8. 前端收到成功响应,提示“保存成功”,刷新笔记列表。

这条链路里,每一层都有自己明确的职责,这就是分层思想的意义。如果你打算把这套代码作为课程设计提交,论文的系统设计部分直接按照这条链路来描述各层之间的关系,评审老师基本挑不出毛病。

3.3 JWT 登录认证与拦截器:权限检查的完整实现

权限校验是这套系统里安全性的核心。项目的实现方案是标准的 JWT(JSON 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,封装三个字段:codemessagedata。所有接口都返回这个类型,前端拿到响应后只要判断code === 200就能确认业务成功,逻辑非常一致。

配合统一返回类的,是全局异常处理器。使用@RestControllerAdvice注解定义一个全局异常拦截类,针对参数校验异常、业务异常、未登录访问、服务器内部错误分别定义处理方法,返回对应的错误码和信息。这样后端代码里就不需要到处写 try-catch,正常的业务逻辑里出错了,异常会被全局处理器统一捕获并转成标准的 JSON 格式返回给前端。

这个设计带来的实际价值是:前端可以只写一套错误提示逻辑,后端无论哪个接口出问题,返回结构都一样,弹个提示条就行。

3.5 笔记查询接口:多条件组合查询的实现技巧

笔记列表页面需要支持分页、分类筛选、关键字搜索以及按时间排序,这种多条件组合查询是后端开发里的高频需求。项目里的实现方式是使用 MyBatis 的动态 SQL,通过<where>标签配合<if>标签拼装查询条件:

<select id="selectNoteList" resultType="com.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 test="keyword != null and keyword != ''"> AND (n.title LIKE CONCAT('%', #{keyword}, '%') OR n.content LIKE CONCAT('%', #{keyword}, '%')) </if> <if test="categoryId != null"> AND n.category_id = #{categoryId} </if> <if test="onlyPublic"> 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 只接收pageNumpageSize两个参数。

4. Vue 前端源码的核心实现:页面组件、路由与请求封装

后端再完善,前端一塌糊涂也白搭。这套项目的 Vue 端虽然页面风格不一定多华丽,但工程结构很规范,每个功能模块的落点都很清楚。我来把前端的关键实现拆一遍。

4.1 前端目录结构与组件划分

Vue 前端工程(通常叫webfrontend)的核心目录大致长这样:

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 还是 POST,URL 是什么,完全不用关心。一旦后端接口地址变化,你只需要修改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分类 ID
titlevarchar(200)笔记标题
contenttext正文内容
is_publictinyint是否公开:1 公开,0 私密
view_countint浏览计数
like_countint点赞计数
create_timedatetime创建时间
update_timedatetime更新时间

view_countlike_count这两个字段,是典型的冗余字段设计。严格来说,浏览量可以通过日志统计得出,点赞数可以通过关系表 count 统计得出,但是每次都去 count 一遍,数据量一大查询性能就扛不住了。所以项目选择直接在笔记表上维护一个计数字段,每次浏览 +1、每次点赞 +1,这种空间换时间的思路,在实际项目里应用非常广。

分类表(category)

字段名类型说明
idbigint主键
namevarchar(50)分类名称
descriptionvarchar(255)分类描述
sort_orderint排序权重

评论表(comment)

字段名类型说明
idbigint主键
note_idbigint所属笔记 ID
user_idbigint评论者 ID
contentvarchar(500)评论内容
create_timedatetime评论时间

收藏表(favorite)

字段名类型说明
idbigint主键
user_idbigint收藏用户 ID
note_idbigint被收藏的笔记 ID
create_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的数据库脚本。打开看一眼,脚本里一般包含了建库语句、建表语句和初始化测试数据。

初始化数据库的操作步骤如下:

  1. 打开 MySQL 命令行工具,用 root 账号登录;
  2. 执行CREATE DATABASE note_share DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;创建数据库(注意使用 utf8mb4,而不是 utf8,否则 emoji 表情存储会报错);
  3. 使用USE note_share;切换到目标数据库;
  4. 执行source /你的路径/init.sql;导入建表语句和初始数据;
  5. 执行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?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/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 版本之间的一个明显差异。serverTimezone=Asia/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_site=https://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 Security,CORS 配置必须放在安全过滤链上,而不是简单地写一个WebMvcConfigurer,两者在过滤器链中的位置不同,会导致 CORS 配置“写了没用”。这套项目因为没引入 Security 才能直接用这个方案,你二次开发如果需要加 Security,这个坑要注意。

7.2 MySQL 8 时区与驱动相关问题

MySQL 8 相比 5.7 做了很多默认值上的调整,其中两个在 SpringBoot 连接时表现得尤其突出。

第一个是时区问题。MySQL 8 的安装器在初始化时会默认使用系统时区,但如果你用 Docker 安装 MySQL 8,容器默认时区往往是 UTC,导致后端插入数据时时间比北京时间晚 8 小时。解决办法是在 JDBC URL 后面追加serverTimezone=Asia/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 和 Vuex:Vue 2 对应的是 Vue Router 3 和 Vuex 3,Vue 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 入门走到能够独立加班加点做业务的进阶基石。无论你是为了交课程设计,还是想通过一个完整项目把这些知识点串成线,这套代码都值得你花上一个周末,把每一层都吃透。遇到跑不起来的情况,别急着怀疑源码有问题,先从环境、配置、端口这三个维度自查——这三板斧解决掉,百分之九十的问题都会消失。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询