
整理这套“海滨学院班级回忆录设计与实现信息管理系统”的源码我把它完整盘了一遍从数据库表设计、后端接口到前端页面和本地部署整个过程走下来比想象中顺畅也踩了几个不大不小的坑。先说结论如果你需要一个能直接跑起来、前后端分离、数据落库的班级回忆录Web应用这套SpringBootVueMySQL的方案确实是个很省心的起点。它面向的场景很简单——班级毕业之后散落在群相册、朋友圈、聊天记录里的照片和文字太零散了一个班级需要一块能把大家共同回忆集中存放、按时间线翻阅、还能让同学留言互动的阵地。适合拿来做毕业设计、课程设计也适合想自学前后端分离开发的同学拿来当参照工程。下面我把这套系统从设计到落地的完整过程整理出来。内容会覆盖模块拆解、表结构设计、后端关键实现、前端页面组织以及本地一键运行的部署细节和避坑记录基本照着操作就能把你手里的源码跑明白再往上加功能也有清晰的思路。1. 项目整体设计与核心模块拆解1.1 需求拆解一台回忆录要装下哪些东西班级回忆录这个切入点核心不是“做一个好看的首页”而是把回忆内容组织起来。回忆录要装下的东西其实就四类人、内容、事件、互动。人是参与主体。一个班级系统必须有用户体系区分管理员、班委和普通同学。管理员负责整体维护班委可以审核内容或者管理分类普通同学则是主要内容贡献者。内容是核心资产。这里包括回忆文章、照片、留言。文章可以写某次班会、某位老师的金句也可以写个人和班级的故事照片是班级回忆里最直观的部分按相册或时间归类留言墙则提供一个相对轻量的表达空间。事件是时间轴。班级四年或三年发生了什么大事录取、迎新、军训、运动会、毕业照这些事件串起来才是一本真正的“回忆录”而不是一堆零散碎片的堆砌。互动是温度。没有点赞和评论回忆录就只是一个静态存档缺少让同学再次连接起来的气氛。所以评论和点赞功能也应该是系统的一等公民。1.2 技术选型为什么这套组合最适合拿来就跑这套系统用的是SpringBoot后端、Vue前端、MySQL数据库我觉得选型是相当务实的理由有三点。第一SpringBoot天生适合做这种单体业务系统。自动配置大幅减少了中间件环境的折腾内置Tomcat意味着后端环境装好JDK就能直接启动。对于一个班级回忆录场景完全不需要拆微服务把业务写在清晰的分层里就够了。第二Vue作为前端框架足够灵活也足够主流。Vue的组件化开发让页面结构非常清楚路由管理、状态管理、HTTP请求这些都有成熟配套对刚接触前后端分离的开发者来说这个技术栈能找到的参考案例是最多的。第三MySQL存这种强结构化数据最合适。用户信息、文章、评论、点赞都是明确的表结构用SQL查起来效率高事务也可靠。相比NoSQLMySQL更适合这个场景下的数据一致性维护。这套组合还守住了“可直接运行”的底线——不需要额外引入Redis、不需要搭建消息队列一个MySQL加一个Node环境前后端就能全部拉起。1.3 功能清单先把边界画清楚具体落地的功能模块我整理成了下面这张表模块核心功能使用角色用户管理注册、登录、个人信息维护、头像上传全部用户回忆文章发布、编辑、删除、分类与标签、列表分页全部用户删除限本人或管理员评论与点赞文章评论、点赞、取消点赞登录用户照片墙相册分类、照片上传、图片预览登录用户上传所有人可看留言墙发布留言、留言展示登录用户班级时光轴按年份/学期记录班级大事、时间线展示管理员/班委维护所有人可看统计面板文章数、照片数、留言数、用户数概览所有人可见这套边界画得很克制没有加一些“看起来高大上但很难跑完”的功能比如全文搜索引擎、分布式文件存储。边界清晰的好处是开发量可控出问题也好定位。2. 数据库设计与表结构关系2.1 整体表设计先把收纳盒分好数据库设计是这套系统最关键的环节。我的习惯是先想清楚数据从哪里来、会被谁以什么方式查再动手建表。这张回忆录系统一共规划了这么几张表sys_user用户表账号、密码、昵称、头像、角色、注册时间。article回忆文章表标题、正文、封面图、分类、标签、作者、发布时间。photo照片表所属相册、图片URL、上传者、上传时间、图片描述。album相册表相册名称、封面、创建者。comment评论表所属文章、评论内容、评论人、评论时间。like_record点赞表所属文章、点赞人、点赞时间。message留言表留言内容、留言人、留言时间、是否展示。timeline_event时光轴事件表事件标题、事件描述、发生时间、创建人。把这些表看成家里收纳杂物的盒子每个盒子只放一类东西。用户表就是通讯录文章表是日记本照片表是相册留言表是便签墙。盒子之间通过外键或逻辑外键联系成一张网。2.2 核心建表SQL与字段设计先看用户表它要做的事很明确——登录认证和展示身份信息。密码字段必须存加密后的密文这里使用BCrypt加密。角色字段我是直接用字符串存储的简单直接查询高效不像角色表加权限表那么重适合这个体量的系统。CREATE TABLE sys_user ( id bigint(20) NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录账号, password varchar(100) NOT NULL COMMENT BCrypt加密后的密码, nickname varchar(50) DEFAULT NULL COMMENT 昵称, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, role varchar(20) NOT NULL DEFAULT STUDENT COMMENT 角色ADMIN/MONITOR/STUDENT, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_username (username) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表;然后是回忆文章表这是整个系统的内容核心。一个关键设计是author_id字段用于关联用户后续查询“我发布的回忆”“某个人的回忆”全靠它。状态字段status我留了0和1两个值0表示草稿1表示已发布以后要加审核功能只要再加一个值就行。CREATE TABLE article ( id bigint(20) NOT NULL AUTO_INCREMENT, title varchar(100) NOT NULL, content text NOT NULL, cover_url varchar(255) DEFAULT NULL, author_id bigint(20) NOT NULL, category_id bigint(20) DEFAULT NULL, tags varchar(200) DEFAULT NULL COMMENT 逗号分隔标签, status tinyint(4) NOT NULL DEFAULT 1, view_count int(11) NOT NULL DEFAULT 0, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_author_time (author_id, create_time), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT回忆文章表;像comment、photo、message、like_record这几张表结构都比较直接核心就是谁、在什么时候、对什么内容、做了什么事。比如点赞表我特意给article_id和user_id加了一个唯一索引目的就是让同一人对同一篇文章只能点赞一次这一条约束能从数据库层面防住重复点赞。2.3 关系与查询路径从页面反推SQL设计完表要习惯从页面反推查询SQL。看一眼前端要展示的场景数据库设计合不合理就很清楚了。首页的“最新回忆列表”要展示文章标题、封面、作者昵称、创建时间。对应SQL要关联article和sys_user两张表取昵称SELECT a.id, a.title, a.cover_url, a.create_time, u.nickname FROM article a LEFT JOIN sys_user u ON a.author_id u.id WHERE a.status 1 ORDER BY a.create_time DESC LIMIT 10;文章详情页除了文章本身还要展示评论区。评论列表的SQL会关联用户表取评论者的头像和昵称然后按时间升序排列模拟一个自然对话的顺序。文章点击量则是每次进入详情页时对view_count字段做增量。时光轴页面比较特殊它不按照文章的发布时间排序而是按照timeline_event表中的event_date字段排序。所以设计时光轴表时event_date一定要单独建索引否则班级回忆录累计到几百条事件之后页面会明显变慢。3. 后端核心实现细节3.1 后端工程分层与包结构拿到源码以后肉眼第一件事就是看包结构。这套系统的后端分层很常规但规范得很舒服controller接收HTTP请求、参数校验、调用service、返回统一结果。service业务逻辑层处理具体的业务规则。mapper数据访问层用MyBatis或MyBatis-Plus操作数据库。entity实体类和数据库表字段一一对应。common通用返回结果、异常处理、常量。config配置类比如跨域配置、拦截器注册。这种分层逻辑其实就是厨房分工。Controller是前台服务员只负责端菜收菜单Service是厨师负责炒菜不管菜怎么端上桌Mapper是后厨采购员只管找食材。这样一来改前端接口结构不会动业务代码改业务逻辑也不会碰SQL排错的时候特别省时间。3.2 登录鉴权与权限控制登录功能是后端最容易出问题的点也是系统安全的门户。这套系统采用的是JWTJSON Web Token方案。登录流程是这样的用户提交用户名和密码后端用BCrypt验证密码验证通过后生成一个包含用户ID和角色的JWT返回给前端。前端把JWT存储在本地之后每一次HTTP请求都在请求头里带上Authorization: Bearer token后端通过拦截器解析token就能知道当前用户是谁、是什么角色。拦截器的核心逻辑我简化成下面的样子public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (request.getMethod().equals(OPTIONS)) { return true; } String token request.getHeader(Authorization); if (StringUtils.hasText(token) token.startsWith(Bearer )) { token token.substring(7); try { Claims claims JwtUtil.parseToken(token); request.setAttribute(userId, claims.get(userId)); request.setAttribute(role, claims.get(role)); return true; } catch (Exception e) { throw new BusinessException(登录已过期请重新登录); } } throw new BusinessException(未登录); }这里踩过的一个坑是预检请求。前后端分离之后前端浏览器在发起跨域POST请求前会先发一个OPTIONS请求试探。如果拦截器把OPTIONS请求也拦了前端跨域请求统统失败。所以拦截器里第一步就要判断OPTIONS请求直接放行配合后端的跨域配置一起使用才能保证联调顺滑。3.3 核心业务接口的实现逻辑几张核心表的接口设计基本遵循RESTful风格同时结合页面需要做适当简化。文章发布的Controller长这样PostMapping(/article) public Result? createArticle(RequestBody ArticleDTO dto, HttpServletRequest request) { Long userId (Long) request.getAttribute(userId); return Result.success(articleService.createArticle(dto, userId)); }在Service层真正干活的逻辑核心就几步将DTO转成实体补上作者ID和默认状态然后插入数据库。如果上传了封面图封面URL是提前生成好的直接存进去。文章发布成功后系统还会更新该用户的发布统计。点赞接口有个细节值得单独说。点击“点赞”按钮后前端会调/article/{id}/like这个接口。后端先去like_record表查记录如果存在记录就删除这条点赞并给文章点赞数减一相当于“取消点赞”如果不存在记录就新增一条并加一。因为这个表有article_id和user_id的唯一索引即使前端疯狂点击导致并发请求同时到达数据库层面也只会有一条记录插入成功天然防住了重复点赞。3.4 图片上传与静态资源映射图片上传是照片墙和头像功能的核心。后端的实现思路是接收MultipartFile文件校验文件类型和大小限制生成一个UUID文件名防止重名保存到本地配置的上传目录然后返回一个可以访问的URL地址。这里最容易犯的错是保存成功后前端图片打不开。原因在于SpringBoot默认只处理静态资源目录里的文件你上传到的是自定义目录框架并不知道怎么把它暴露给外部访问。所以必须在配置类里加一个映射Configuration public class WebMvcConfig implements WebMvcConfigurer { Value(${file.upload-dir}) private String uploadDir; Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceHandler(file: uploadDir /); } }这样配置后上传图片的访问地址就是http://localhost:8080/upload/xxxx.jpg。这个路径要小心写file:前缀不能丢路径末尾的斜杠也不能少本地Windows和Linux目录格式还不一样都是容易踩的细节。4. 前端页面设计与Vue集成4.1 前端工程结构与路由设计前端是Vue工程入口后主要页面包括登录注册页、首页、回忆列表页、文章详情页、照片墙页、留言墙页、时光轴页和个人中心页。路由设计如下/login登录注册。/home首页展示最新回忆、统计概览。/article回忆文章列表支持分类筛选。/article/:id文章详情包括评论和点赞。/album照片墙。/time-line班级时光轴。/message留言墙。/profile个人中心。前端工程目录里views目录放页面级组件components目录放可复用的子组件比如文章卡片、图片懒加载组件、分页条。api目录下按业务模块拆分接口请求utils目录放axios封装和登录状态工具。这种结构看一遍就能理解整个项目脉络。4.2 核心页面组件拆解时光轴页面是整个回忆录最有趣味的页面。我实现的思路是后端一次性返回按年份分组的事件数据前端用时间轴样式展示左侧是年份右侧是该年份下的事件卡片卡片上显示日期、标题和短描述。Vue组件里其实就是一个v-for嵌套遍历外层遍历年份内层遍历事件配合CSS实现左右交替的效果。照片墙页面同样不难核心在一个grid栅格布局。照片卡片用懒加载指令处理只有滚动到可视区域才真正请求图片地址否则一个班级传几百张照片首屏加载会卡到怀疑人生。前端图片预览用的lightbox模式点击小图弹出大图支持左右切换。我挑了一个比较能说明Vue核心用法的片段文章列表组件里的渲染逻辑template div classarticle-list article-card v-foritem in articles :keyitem.id :articleitem clickgoDetail(item.id) / /div /template script setup import { ref, onMounted } from vue import { getArticleList } from /api/article import ArticleCard from /components/ArticleCard.vue const articles ref([]) const loadArticles async () { const res await getArticleList({ page: 1, pageSize: 10 }) articles.value res.data.records } onMounted(loadArticles) /script组件化开发的核心就是像搭积木一样把文章卡片抽成独立组件列表页用、个人中心用、搜索结果页也用改样式只改一处全局生效。4.3 前后端联调细节axios封装、Token携带与跨域前端联调部分最值得写清楚的是axios的统一封装。我习惯建一个request.js统一配置baseURL、超时时间、请求拦截器和响应拦截器。baseURL不要写死成localhost:8080而应该用环境变量控制在.env.development里写一份在.env.production里写另一份。这样以后部署到服务器只需要改环境变量不需要动业务代码。请求拦截器里统一把token塞进请求头service.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers[Authorization] Bearer token } return config })响应拦截器里统一处理错误尤其是后端的401状态码。遇到401说明token失效或者未登录直接跳转回登录页。这一步能避免每一处接口调用都重复写“没登录怎么办”的判断逻辑。跨域问题则是在后端解决的。配置类里加上CORS映射允许指定来源的请求访问后端接口同时允许携带凭证registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowedHeaders(*) .allowCredentials(true);开发环境其实还有另一种更稳妥的方案前端的Vite配置devServer代理把/api前缀的请求代理到后端8080端口浏览器全程只和前端域名打交道就没有跨域问题了。源码里这两种思路都有体现二选一即可。5. 本地一键运行的部署流程与避坑清单5.1 环境版本推荐这套系统要本地跑起来环境准备工作其实很简单但版本搭配不要乱来。我实测下来比较稳的组合是组件推荐版本备注JDK1.8 或 11我用的JDK 8兼容性最好Maven3.6 以上用来打包后端MySQL5.7 或 8.08.0需要注意驱动名变化Node.js16 或 1816最稳20可能遇到编译兼容问题npm随Node自带安装前端依赖用后端是一个标准Maven工程前端是一个Vue工程目录结构一般在压缩包里都分好了一个叫backend一个叫frontend打开就能看到。5.2 数据库初始化与后端启动第一步是创建数据库并导入SQL脚本。我习惯在MySQL命令行里执行mysql -u root -p create database class_memory default character set utf8mb4; use class_memory; source /path/to/sql/class_memory.sql;导入SQL后改成后端的数据源配置。在application.yml里把URL、用户名、密码改成自己本地的实际值。这里强烈建议URL后面加上characterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai特别是MySQL 8.0不加时区参数会直接报时间错误。spring: datasource: url: jdbc:mysql://localhost:3306/class_memory?characterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 你的密码 driver-class-name: com.mysql.cj.jdbc.Driver然后在项目根目录执行mvn clean package -DskipTests java -jar target/class-memory-server.jar看到Started Application日志输出后端就就跑起来了默认端口是8080。5.3 前端启动与联调前端启动前先把依赖装上npm install装依赖的时候容易遇到速度慢的问题可以先把镜像源切换成国内源过程不展开。装完之后npm run dev启动成功会给你一个本地访问地址默认是http://localhost:5173浏览器打开就是系统首页。如果前端页面打开后接口数据加载不出来优先看两个地方第一个是浏览器的DevTools Network面板看请求是404还是500第二个是检查前端的代理配置看看/api请求是否符合已经配置的转发规则。大部分联调失败问题都出现在这两个环节。5.4 高频报错与解决清单部署过程中我整理了几个比较有代表性的报错直接做成速查列表后端启动报Access denied for user rootlocalhost说明数据库用户名或密码写错了检查application.yml。启动报Unknown database class_memory说明数据库没有创建成功回SQL那一步检查脚本执行情况。前端npm install报opensslErrorStack多半是Node版本太高和某些依赖不兼容切换到Node 16即可。前端页面能打开但是登录接口404大概率是前端代理路径和后端context-path没对上需要确认代理转发路径。上传图片成功但照片墙加载不出来检查后端的静态资源映射是否配置正确以及上传目录是否真实存在。这些坑看起来琐碎但每一个都是实际项目中高频出现的。把版本环境先用对后面能省掉一大堆排查时间。6. 常见问题速查与未来扩展思路6.1 常见问题速查表我把在这个项目上被反复问到的问题整理成了一张速查表方便源码拿到手后照着排查现象可能原因处理方式登录接口返回500数据库密码加密方式不匹配确认用户表里存的密码是BCrypt生成的密文所有接口返回401前端请求未携带token检查axios请求拦截器是否正常写入Authorization评论功能报“文章不存在”文章状态为草稿或已删除查询时增加status过滤数据中文乱码数据库连接串缺少编码参数URL加上characterEncodingutf8运行一段时间后图片加载慢原图未做压缩处理扩展方向接入图片压缩或对象存储时光轴事件顺序混乱排序字段用错确认按照event_date排序而不是create_time6.2 从“能跑”到“好用”的扩展方向这套系统跑起来之后它的意义不是停留在“能运行”这个层面而是给后续的功能扩展留足了空间。我结合实操经验列几个值得尝试的方向。第一个方向是备份与导出。班级回忆录本质是情感资料沉淀数据不能被单个数据库绑架。最常见的扩展是做一个“回忆册导出”功能把文章和照片打包成PDF或HTML文件下载。这个功能用后端生成PDF或者HTML模板就能实现前端加一个导出按钮即可。第二个方向是评论审核和内容安全。当系统真的给班级所有人使用后评论和留言需要有人管理。给管理员加一个“内容审核”页面comment表和message表加一个status字段0待审核1已通过2已拒绝这比直接删数据温柔得多也更符合班级管理的使用习惯。第三个方向是视觉体验升级。照片墙目前处理正常图片没问题但如果同学们上传的都是手机原图动辄几MB加载速度会受影响。把图片上传环节加上压缩或者迁移到对象存储是提升体验的一个很直接的路径。第四个方向是数据统计和纪念日提醒。毕业多年后系统里最温馨的功能其实是“今天是入学十周年”这类提醒。做一个定时任务读取timeline_event里的事件日期匹配到当前日期就推送一条提醒。这个功能技术难度不高但情感价值极高。这次把整套流程完整跑下来我最深的体会是这类系统的成败往往不取决于用了多前沿的技术而在于表结构是否清爽、接口约定是否统一、环境版本是否对齐。班级回忆录这种项目真正的“技术含量”恰恰藏在照片上传路径、Token拦截顺序、分页查询效率这些看起来不起眼的细节里。如果这个项目后续要继续迭代我优先想加的还是回忆册导出和评论审核因为这两件事能让它从一个“演示项目”变成一个真正可以被班级长久使用的工具。如果你正在拿这套源码做改造欢迎在实际操作中多关注我上面列的那几个坑省下来的时间够你再写一个功能模块了。