news 2026/9/30 3:11:37

基于SpringBoot+Vue的小说平台毕设实战:从数据库设计到前后端部署

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于SpringBoot+Vue的小说平台毕设实战:从数据库设计到前后端部署

1. 选题逻辑与系统定位:为什么小说平台适合做Java毕设

每年带毕设都会遇到一类经典问题:题目既要有技术含量,又要能在几个月内做完,还得让答辩老师一眼看出工作量。从SpringBoot到Vue,从数据库设计到接口开发,这套技术栈基本覆盖了JavaWeb开发的核心链路,而小说平台恰恰能把这条链路完整串起来。

我的个人看法是:小说系统并不是什么新鲜项目,但它非常适合作为毕设,原因有三。第一,业务模型清晰——小说、章节、用户、书架、评论,这些实体关系明确,建表设计不会绕晕。第二,技术覆盖面广——阅读时长统计涉及Redis缓存,全文搜索涉及Elasticsearch或SQL模糊查询,互动功能涉及消息推送,每个模块都能展示独立的技术点。第三,演示效果好——一个功能完整的小说平台演示起来比一个抽象的管理系统直观得多,答辩时你可以直接打开页面展示阅读、评论、书架同步的真实操作,说服力远强于PPT讲解。

先说清楚这个系统到底要做什么。在线小说阅读与管理系统,拆开来看包含三层:面向读者的前台阅读端、面向作者的作品发布端、面向管理员的后台管理端。阅读端负责小说展示、搜索、分类浏览、阅读器、书架、评论互动;发布端负责作者创建作品、维护章节、查看数据;管理端负责审核内容、管理用户、处理举报。基于SpringBoot+Vue的前后端分离架构,前端走Vue Router控制页面跳转,后端用SpringBoot提供RESTful接口,通过Axios完成数据交互,权限部分采用JWT令牌做身份验证。

技术选型上,我用的是SpringBoot 2.7.x + Vue 3 + Element Plus + MySQL 8.0,中间件搭配Redis和MinIO。SpringBoot版本不要追新,2.7.x最稳定,兼容性也最好,很多第三方库对SpringBoot 3的Jakarta命名空间支持还不完善,做毕设没必要冒险。MySQL用8.0是因为它原生支持窗口函数,做小说分类排行榜时窗口函数比传统分组聚合简洁得多。MinIO用来存储小说封面图片和作者上传的附件——小说平台和普通CRUD系统的最大区别就在于有大量非结构化数据(封面图、头像),如果全部塞进MySQL,数据库体积会迅速膨胀,性能也会急剧下降。

有的同学会问:为什么不直接用SpringBoot开发单体应用、用JSP写页面?这就要谈到JavaWeb到前后端分离演进的背景了。传统的JavaWeb项目(Servlet + JSP)与SpringBoot+Vue的根本差异在于关注点分离程度:JSP的方案中Java代码和HTML耦合在同一个文件里,前端逻辑改版必须改Java文件;而前后端分离后,前端项目可以独立运行、独立部署,后端只需要按照既定接口契约输出JSON。如果你在简历上写"熟练掌握JavaWeb",面试官默认你会Servlet、Filter、JSP这些基础;但你毕设如果还在用JSP,又显得技术栈偏旧。实际上规范的JavaWeb基础(Servlet生命周期、Filter拦截机制、Session原理)和SpringBoot并不冲突——SpringBoot本身就内置了DispatcherServlet,理解底层原理对调试和答辩都有价值。

关于系统命名,我在项目里把整个工程拆成了三个子模块:novel-frontend(Vue工程)、novel-backend(SpringBoot工程)、novel-admin-web(后台管理前端)。这样拆分的好处是职责边界清晰,答辩时也可以明确地说"我采用了前后端分离架构,并且把用户端和管理端独立为两个前端工程",这本身就是一个可讲的工作量加分项。

2. 数据库模型设计:小说系统的地基怎么打

小说平台的数据模型是整个开发中最不能含糊的部分。表设计如果出问题,后期写接口会不断被迫修改表结构、重构代码。我花了差不多一周时间反复调整了这些表,核心原则是:实体关系明确、查询路径短、扩展留有冗余。下面直接给出我最终敲定的表结构和设计理由。

2.1 核心表结构:用户、作品、章节、书架五张必备表

第一张是sys_user用户表,这是任何系统的基础。字段我设计了:user_id(自增主键)、username(登录名)、password(加密存储,采用BCrypt算法)、nickname(昵称)、avatar_url(头像地址,存的是MinIO的路径)、role(角色标识,1为普通用户,2为作者,3为管理员)、status(账号状态,0禁用,1正常)、create_time、update_time。角色的设计这里要特别说一下:一个用户既可以是读者也可以是作者,所以我在设计上允许作者角色继续阅读和评论,而不是把作者限制为纯创作者。如果你想要更灵活的方案,可以加一张用户角色中间表,但做毕设用单字段枚举角色就够了,简洁且不容易出错。

第二张是novel_info作品信息表。这张表承载了小说书架页面的核心信息,字段比较多:novel_id、novel_name(书名)、author_id(关联sys_user)、category_id(关联分类表)、introduction(简介)、cover_url(封面图地址)、serial_status(连载状态,1连载中,2已完结)、click_count(点击数)、collect_count(收藏数)、word_count(字数)、latest_chapter_id(最新章节ID)、publish_time、update_time。注意latest_chapter_id这个字段——它属于典型的冗余设计,每次发布新章节时需要同步更新这张表的对应字段。这样做虽然增加了代码逻辑的复杂度,但换来了极高的查询效率:书架页面不需要连表查章节表就能直接展示最新章节的信息,用户浏览小说列表时也不必对每本小说执行一次子查询。

第三张是novel_chapter章节表。这里有一个很多新手都会踩的坑:把整本小说的所有章节都塞在一张表里,并且把章节内容直接放在业务表里用text字段存储。我最终设计的字段是:chapter_id、novel_id(索引)、chapter_no(章节序号)、title、content(长文本)、word_count(该章字数)、is_free(是否免费,可扩展VIP章节,但前期可全部置为免费)、audit_status(审核状态)、create_time、update_time。关键点在于:content字段的数据量可能非常大,我建议把章节内容单独拆到novel_chapter_content表,与章节元信息一一对应。原因不必说是性能——更核心的是富文本编辑器的内容中可能包含样式标签(比如段首缩进、加粗、图片标签),它的体积远超普通字段的预期,单独存放可以避免章节列表查询时被大字段拖慢响应。

第四张是user_bookshelf书架表。字段为:bookshelf_id、user_id、novel_id、last_read_chapter_id(最近阅读章节)、last_read_time、create_time。这张表的价值在于实现"同步阅读进度"——用户在手机上看了一半,换电脑打开时可以直接定位到上次阅读位置。联合唯一索引要加在(user_id, novel_id)上,防止同一用户重复收藏同一本小说。last_read_time专门用来做"最近阅读"列表的排序,而书架里常见的时间排序依赖于这个字段。

第五张是book_category分类表和sys_comment评论表。分类表就三个字段:category_id、category_name、sort_order。评论表要设计成既支持对章节评论,也支持对整本小说评论:comment_id、novel_id、chapter_id(允许为0,表示整书评论)、user_id、content、parent_id(支持楼中楼回复)、like_count、status(0正常,1被删除,2隐藏)、create_time。这里我多设计了一个parent_id是为了做二级评论,这个功能虽然增加一些递归查询的复杂度,但演示效果非常好,也是答辩时可以重点讲的一个业务亮点。

2.2 表关系与索引设计的取舍思路

后台管理还需要额外的审核表、举报表,但核心业务表就上面这六七张。这部分我想多聊一下索引的使用,很多同学建表时完全不加索引,导致后期测试数据一多接口立刻变慢。我的原则是:所有外键字段必须建索引,所有查询排序字段必须建索引。具体来说,novel_chapter表的(novel_id, chapter_no)要建联合索引,保证"查询某本小说的章节列表"能利用索引有序性直接按章节号排序,而不需要额外的filesort;user_bookshelf表的(user_id, novel_id)联合唯一索引就不重复说了;sys_comment表的(novel_id, status)索引则服务于评论列表的分页查询。

表之间的外键约束我建议建,但不用强制物理外键,逻辑外键就够了。用物理外键FOREIGN KEY约束在MySQL里的问题是:删除被引用行时必须先删除子表数据,步骤繁琐,而且在高并发下锁竞争更明显。做毕设时用逻辑关联(也就是普通的索引字段)配合Service层的事务控制就够了,既保证了数据一致性的核心要求,又不会在开发时被外键约束频繁"卡住"。

3. 后端SpringBoot核心模块落地:权限、发布与阅读链路

后端开发阶段是工作量最大的部分,我把它按业务链路拆成了几个模块逐个实现。下面按"先搭骨架、再做核心业务"的顺序,把我实际的编码经验记录下来。

3.1 JWT鉴权链路:从登录到请求带令牌的完整流程

传统JavaWeb用Session保存登录状态,但前后端分离架构下,Session方案存在跨域会话保持和并发扩展的短板,尤其Vue前端常常部署在不同端口或域名上,Session跨域处理很麻烦。这个项目我弃用了Session方案,选择了JWT(JSON Web Token)做无状态认证。

JWT的核心思想是:用户登录成功后,后端生成一个经过签名加密的字符串令牌返回给前端;前端在后续每次请求时把这个令牌放进HTTP请求头的Authorization字段中;后端通过拦截器或过滤器解析令牌,验证签名和有效期,解析出用户信息,完成身份识别。由于JWT本身携带了用户基本信息,服务端不需要在内存或Redis中保存Session数据。

我在项目中实现这条链路用的技术组合是:SpringBoot +jjwt库 + 自定义拦截器。具体步骤如下:

  1. 引入依赖:在pom.xml中引入io.jsonwebtoken:jjwt-api:0.11.5、jjwt-impl:0.11.5、jjwt-jackson:0.11.5三个包;
  2. 登录接口:POST /api/auth/login接收用户名和密码,用BCryptPasswordEncoder校验密码,通过后生成JWT,载荷部分存放userId、username、role三个字段,expiration设置为7天,签名密钥使用256位以上的字符串,我用的是自定义配置在application.yml的jwt.secret;
  3. 自定义拦截器:实现HandlerInterceptor接口,在preHandle方法中读取请求头Authorization,用固定前缀Bearer截取令牌,尝试解析,如果解析失败则抛出业务异常,由全局异常处理器兜底返回401状态码;
  4. 用户信息传递:将解析出的用户信息放入ThreadLocal中——我封装了一个UserContext类,里面有静态的set和get方法,这样Controller层不需要在每个方法里重复解析令牌,直接UserContext.getUserId()即可。这个方法在高频请求下性能好,但要注意请求结束后必须清理ThreadLocal,否则线程池复用的线程会残留上次请求的用户信息,造成用户数据串号。

这里给第一次写JavaWeb项目的同学提个醒:拦截器上的放行路径配置必须精确。登录、注册、小说列表、小说详情、章节内容这些接口是游客可以访问的,要放在excludePathPatterns里;而发布章节、评论、书架操作必须经过鉴权。我一开始把/api/**全部干掉了拦截,导致前端登录接口也返回401,排查半天才发现是拦截器把所有请求都拦了,登录接口本身还没来得及获取token。

3.2 作品发布与章节管理:事务与富文本内容存储

小说发布是平台的核心业务,完整流程是:作者在前端填写书名、简介、分类、上传封面图,调用POST /api/novel创建作品;接着在作品管理页面新建章节,编辑内容,点击发布调用POST /api/novel/{novelId}/chapter;发布成功后,后端需要同时做几件事——保存章节内容、更新novel_info的latest_chapter_id、累加word_count(总字数)。

这几个操作必须放在一个事务里执行。所谓事务,简单理解就是"一组操作要么全部成功,要么全部失败"。如果章节保存成功,但是更新作品的latest_chapter_id失败,那么前台展示的章节列表和详情页的最新章节就会不一致,用户会看到"最新章节点进去是404"这种低级错误。SpringBoot中使用@Transactional注解是最方便的方式,加在Service方法上即可。默认事务在抛出RuntimeException时回滚,所以业务代码里必须用异常而非正常流程处理错误。

富文本内容存储这块我踩过一次坑。最初的方案是前端直接把富文本编辑器渲染出的完整HTML(包含样式标签和图片链接)作为字符串传给后端,存在novel_chapter_content表里。这样在阅读器里是可以直接渲染的,但字数统计就麻烦了,因为HTML标签不算字数。我最终改成了双字段方案:content字段存原始HTML(用于阅读器渲染),content_text字段存储去掉标签后的纯文本(用于字数统计和搜索)。前端提交时,编辑器给出纯文本内容用于字数计算,而后端也做一次HTML标签剥离作为兜底校验,双保险。

章节目录的接口设计也有一个容易忽视的细节:GET /api/novel/{novelId}/chapter/list不要直接返回章节正文内容,只返回章节的id、title、chapter_no、word_count,正文单独用GET /api/chapter/{chapterId}读取。这样做的理由很直接:章节目录通常是在小说详情页一并请求的,同时返回多个章节的正文会极大增加响应体积,拖慢详情页加载;而用户点击某一章时才拉取单章正文,后续翻页阅读时也会经过阅读器逻辑。这叫按需加载,是最基本的接口设计原则。

3.3 阅读行为链路:点亮进度、记录时长与书架自动同步

阅读模块是我个人觉得最考验细节的部分。用户点进章节后,前端需要做两件事:一是把当前章节的阅读进度保存到书架表,二是上报阅读时长。很多同学会设计成"每次进入章节都执行一次完整更新",这就是典型的性能浪费。

我最终做成了两步。第一步比较轻量:进入章节时调用POST /api/bookshelf/progress,传novelId、chapterId,后端执行一条INSERT ... ON DUPLICATE KEY UPDATE的SQL,利用书架表的联合唯一索引,有则更新、无则插入,一条语句完成所有逻辑。第二步是阅读时长:前端在用户离开阅读页、切换页面或退出浏览器时,调用POST /api/reading/log把本次阅读时长以增量形式上传,后端累加到阅读统计表。这种做法比定时上报要省请求量,而且时机精准。

为了展示效果更好,我还在阅读器底部做了"同步书架"的提示按钮,如果本次阅读章节与上次不同,会弹出一个轻提示"已更新阅读进度"。这个交互让用户感知到"我的数据被记录下来了",在演示阶段特别有用。

另外,click_count点击量的统计我一开始是每次访问详情页就UPDATE novel_info SET click_count = click_count + 1,后来发现高并发下这个操作会频繁锁行。改法是:把点击量先累加到Redis(INCR命令),每5分钟或累计达到50次才批量刷回MySQL。这样既保证最终一致性,又能避免数据库行锁竞争。如果是做线上项目,这是标准做法;就算是毕设,你把"缓存 + 定时刷库"的思路讲出来,也比一条裸UPDATE高级得多。

4. 前端Vue页面体系与阅读体验实现

后端接口齐了之后就是前端。Vue 3项目我用了create-vite脚手架初始化,目录结构做了模块划分,而不是把一堆组件全堆在views下面。

4.1 前端路由与页面模块划分

路由规划直接体现了一个前端的清晰度。我的router/index.js按业务领域拆成了几个路由模块:

  • /首页(小说轮播图推荐 + 分类入口 + 近期热门)
  • /novel/:id小说详情页(简介、目录、章节列表)
  • /reader/:chapterId阅读器页(核心阅读界面)
  • /category?categoryId=xxx&page=xxx分类列表页
  • /search?keyword=xxx搜索结果页
  • /bookshelf我的书架(需要登录)
  • /author/novel/manage作者管理后台(需要作者或管理员权限)
  • /admin/*管理员后台模块(页面懒加载)

路由守卫(beforeEach)用来做权限控制非常方便。我在全局路由守卫里读取localStorage中保存的JWT令牌和用户信息,检查目标路由的meta中是否包含requiresAuth或requiresAdmin字段,如果是则判断角色,不符合就跳转到登录页。前端路由守卫只是体验上的控制——因为按钮和菜单可以隐藏,但后端接口仍要鉴权,这个我在上一章说过。前端守卫的目的是让未登录用户看不到入口,后端拦截器才是真正的安全防线,两层配合才叫完整的权限体系。

页面代码上,我用了Element Plus的组件库,但自定义的样式也不少。首页的书籍卡片、详情页的封面悬浮效果、阅读器的背景皮肤切换,这些视觉效果都是自定义CSS实现的。Vue组件的核心优势在于组件复用——我的BookCard.vue组件在首页、分类页、搜索页、书架页四个地方被复用,只需要传递不同的novelInfo对象即可,不用重复写展示逻辑。

4.2 阅读器组件的关键实现:翻页、字号与切换样式

阅读器是整个前端里最有"技术含量"的部分。一个合格的阅读器组件需要支持字号调节、背景皮肤切换(白色、浅黄、绿色、黑色夜间模式)、目录侧边栏滑出、上一章/下一章切换。实现这些的核心其实就是状态管理和样式切换:

字体大小用data里的fontSize变量控制,模板中绑定style="font-size: px",用+和-按钮增减,限制范围是14px到22px。背景皮肤用数据对象数组管理,每一项包含className和name,点击切换时动态给阅读区容器加class。目录侧边栏用的是el-drawer抽屉组件,从右侧滑出,数据来自小说详情页已经加载的章节目录接口。上一章/下一章则直接改变当前路由参数chapterId,重新请求正文接口,并把阅读器滚动位置清到顶部。

这里有一个体验细节,就是章节加载状态。章节正文接口一般有300到500ms的延迟,如果不做加载提示,用户会以为页面卡死了。我封装了一个简单的方式:切换章节时把阅读区遮罩一层半透明的loading状态,接口返回后再渲染正文。这个效果不用上Element Plus的Loading组件也能实现,但加了会让演示体验提升不少。

对于深夜看小说这个高频场景,夜间模式是阅读器的标配功能。我在阅读器页面上加了皮肤切换后实测发现,夜间模式不是简单的换个背景色就行,正文文字颜色也要跟着变。系统提供以下几个组合:白底黑字、淡黄底深棕字、浅绿底灰字、黑底浅灰字。页面切换皮肤时,同时改背景色和文字颜色,刷新后通过localStorage保存用户偏好,这个细节在答辩时也可以提——"我考虑了用户阅读场景的差异化体验并做了个性化偏好持久化"。

4.3 搜索与分类:防抖、分页和筛选条件的组合

搜索是平台的高频功能。我实现了一个搜索页面,顶部是输入框和搜索按钮,下面展示结果列表。这里有几个好习惯可以分享。

第一是防抖(debounce)处理。在小说名搜索建议功能里,每输入一个字符就请求一次后端接口,会被服务器拒绝。我用了自定义的debounce函数,用户停止输入500ms后才真正发起请求,代码不到10行,但演示时输入"剑"和"剑来"的间隔请求明显减少。

第二是查询参数封装。搜索页支持关键字、分类、连载状态、排序方式四个条件组合。我把这些参数封装成一个searchQuery对象,拼接成URL查询参数传给后端GET /api/search接口。因为参数是响应式的,用户切换筛选条件时自动触发重新查询,不需要手动维护多个页面状态变量。后端接口用Spring Data JPA或MyBatis-Plus的LambdaQueryWrapper动态构造查询条件,这就体现了ORM框架处理复杂查询的便捷性。

第三是分页加载。"加载更多"按钮触发pageNum + 1并把新数据追加到列表尾部,而不是替换列表,这个交互在移动端上很自然。分类页我还会在顶部展示当前分类下的子分类导航,比如"玄幻"下面细分为"东方玄幻""异界大陆""王朝争霸",前端通过同一接口传categoryId即可实现,不需要单独的接口。

5. 互动模块:评论、点赞和排行榜中的实用套路

小说平台不能只有读,还得有互动。这部分我不打算展开全部功能代码,而是挑几个我认为最值得写的技术点深入讲。

5.1 评论模块:楼中楼回复的姿态控制与敏感词过滤

评论区的数据模型在第二章已经聊过,这里讲两件实现上的事。第一是回复接口的设计:POST /api/comment/reply接收parentId、novelId、content三个参数。如果parentId是0,表示直接评论整本小说;如果非0,表示这条是某条评论的回复。前端渲染时判断parentId是否为0,把回复内容缩进展示在父评论下方,并显示@用户名格式。

第二是敏感词过滤的实现方式。这个功能可轻可重,最轻的做法是维护一个敏感词表,评论提交时逐条遍历用字符串的contains方法做匹配替换。这个方案在小数据量下完全可行,我的系统里就是用的这个方案:一张sys_sensitive_word表,启动时加载进内存的Set集合,提交评论时如果命中敏感词,直接返回业务异常提示"评论内容包含敏感词汇"。如果你想让这个功能炫耀一点,可以引入前缀树算法(Trie),把敏感词构建成树结构,匹配效率从O(n*m)降低到O(n),但做毕设的话用Set足够了,重点是把流程跑通。

评论列表的查询里还有一个容易踩的坑:评论表要分页,评论回复也要分页,两层分页会让前端渲染变得特别复杂。我的方案是第一层只分页顶级评论,每条顶级评论下面默认最多加载三条回复(LIMIT 3),并提供一个"展开全部回复"的按钮,点击后再请求该评论下的完整回复列表。这种折中方案对毕设展示来说完全够用,而且交互也很常见。

5.2 排行榜缓存:Redis缓存热门榜单的过期策略

小说平台一般都有"热度榜""收藏榜""新书榜"这些榜单模块。直接查数据库做排行很容易想到,但每次打开首页都执行一次ORDER BY click_count DESC LIMIT 10这样的查询,在点击量大的时候会给数据库增加不必要的压力——尤其多个榜单一起查就是好几条SQL。

我的实现思路是:把榜单数据缓存到Redis里,key设计为ranking:click:daily、ranking:collect:weekly这样,值为排行榜的小说基本信息JSON列表,过期时间设为30分钟到1小时。前端请求排行榜接口时,后端先从Redis查,命中直接返回;未命中则查MySQL并回填Redis。过期时间越短数据越新鲜,但数据库查询次数也越多;我做的是动态过期:白天高峰期过期时间设为20分钟,凌晨时段功能少、压力小,过期时间设长到2小时也没关系。

Redis在这套系统里还不止干这一件事。我顺手把"热门搜索词"也做了:每次用户搜索成功后,把关键词写入Redis的ZSet结构,分数+1,然后定时把Top N关键词同步回MySQL的表里,供首页展示热搜词条。一个中间件复用两种功能,在答辩时很有说服力——你可以在系统设计部分写"引入Redis实现热门榜单缓存和热搜词统计",比单纯说"用了Redis做缓存"有力得多。

5.3 动态通知机制:用WebSocket做有新章节发布的实时消息

小说平台有个场景很适合用WebSocket做实时推送:用户收藏了一本连载中的小说,作者发布新章节后,收藏者希望能收到提醒。如果前端用定时轮询接口的方式,既麻烦也不优雅。我在后端集成了SpringBoot自带WebSocket支持,实现了一个轻量级通知模块。

流程大概是:作者发布章节成功后,Service层调用WebSocketService.sendMessageToUser(userId, message),向目标用户的WebSocket会话推送一条JSON消息,内容格式类似{"type":"NEW_CHAPTER","novelId":1,"novelName":"xxx","chapterTitle":"第x章 yyy"}。前端在全局Vue组件里创建WebSocket连接,连接时通过URL参数携带userId(实际项目必须做鉴权握手,毕设可以简化),收到消息后弹出一个轻提示。不用集成Spring Security做复杂的WebSocket握手鉴权,但基本思路要有。

实时消息模块的注意事项主要是连接管理:用户关闭浏览器导致连接断开时,后端要能感知到并从会话池中移除。我在WebSocket的afterConnectionClosed回调里清理了的就是当前会话,避免无效推送。如果会话连接管理不好,部署起来连接数会缓慢泄漏,导致内存升高,这是个隐蔽的性能坑。

6. 打包部署与答辩准备:从开发到演示的完整闭环

开发完成只是成功了一半,部署和演示环节出问题导致答辩翻车的情况每年都见得到。这一章把我的部署配置和答辩准备经验完整写出来。

6.1 前后端分离部署:Nginx与SpringBoot的配合

环境部署我用的是一台2核4G的云服务器,系统为CentOS 7。部署分三块:后端jar包、前端静态文件、Nginx反向代理配置。

后端打包含两个注意点。第一是SpringBoot的jar包要用Maven的package生命周期,生成target/novel-backend-0.0.1-SNAPSHOT.jar;第二是必须配置生产环境的application-prod.yml,内容包含数据库连接URL、Redis地址、JWT密钥、MinIO服务的Endpoint。启动命令我用的是:

nohup java -jar novel-backend.jar --spring.profiles.active=prod > server.log 2>&1 &

这个命令的含义是:以后台进程方式运行Jar,使用prod环境配置,日志输出到server.log。注意nohup和&配合使用才能让程序在断开SSH连接后继续运行。

前端部署相对简单。在Vue项目根目录执行npm run build,生成dist目录,然后把这个目录上传到服务器的/usr/share/nginx/html/novel路径下。Nginx配置里需要做两件事:一是location /指向前端静态文件的目录,做try_files $uri $uri/ /index.html——这是SPA路由的经典配置,如果不加这个,Vue Router的history模式刷新页面时会报404;二是把/api、/ws等路径反向代理到后端服务的端口8080,同时配置proxy_set_header Host $host、proxy_set_header X-Real-IP $remote_addr,这样后端才能获取到客户端的真实IP,用于后面的登录日志和异常监控。

关于MinIO,我单独建了一个minio目录存放jar包和启动脚本,启动后用mc命令创建bucket,并把bucket的访问权限设为只读公开,这样封面图片可以通过http://服务器IP:9000/novel-cover/xxx.jpg直接访问。部署顺序要严格:先启动MySQL和Redis,再启动MinIO,最后启动后端jar包。我踩过的坑是,后端服务启动时如果连不上数据库,SpringBoot会启动失败(因为DataSource初始化不成功),但日志只会在后段打印连接超时,排查起来费时间。

6.2 性能整理与答辩演示路线

答辩演示和平时测试是完全不同的场景,需要提前编排一套清晰的演示顺序。我的建议是:进门先展示登录注册,快速进入首页,打开一本热门小说,用阅读器翻几页切换到夜间模式,加进书架,再演示发布一本测试小说和评论回复。这样大概5分钟就能把系统亮点全部串起来。

这个演示顺序对应到答辩讲解的逻辑是:注册登录展示JWT鉴权,首页展示缓存排行榜,阅读器展示前端交互体验,书架展示数据持久化和节本自动同步,作者发布展示事务处理和后端接口能力,评论和WebSocket展示实时互动。每个演示点都对应一个技术点,这样答辩老师问"这个怎么实现的",你可以马上给出明确答复。

关于性能优化,这个项目我只做了必要的措施:MySQL的慢查询日志开启并检查慢SQL,Redis缓存的热门接口数据,Nginx开启Gzip压缩减少前端文件体积。这些优化不用做太多,把一条链路做透讲清楚就足够了。

6.3 答辩前必须能答上来的8个高频问题

根据我参加和旁听过不少毕业答辩的经验,这些问题是老师最爱问的,建议提前梳理答案:

  1. 为什么用前后端分离架构?答:提高开发效率,前后端可并行开发;部署灵活;数据交互标准化,通过RESTful接口返回JSON格式。
  2. JWT和Session的区别?为什么选JWT?答:JWT无状态、不占服务端存储、天然支持跨域和分布式部署,适用于前后端分离场景;Session需要共享存储或做粘性会话处理。
  3. 如果用户量很大,你现在的系统瓶颈在哪?答:数据库连接数和磁盘I/O会成为主要瓶颈,可以引入读写分离分库分表、增加Redis缓存命中率、对静态资源做CDN加速。
  4. 排行榜数据怎么保证实时性和一致性?答:缓存更新用主动失效和过期结合——访问期间如果榜单数据源变化,最多延迟一个过期周期展示;刷库操作通过定时任务兜底,实现最终一致性。
  5. MinIO和传统文件存储的区别?答:MinIO是开源对象存储服务,提供S3兼容API,适合存储大量非结构化数据,支持扩容和权限管理,比直接存数据库更适合实际生产场景。
  6. 前端如何获取登录用户信息?答:登录成功后后端返回JWT,前端把JWT存到localStorage,axios拦截器在每次请求时自动携带这个令牌;路由守卫根据令牌是否存在来判断用户登录状态。
  7. 事务都用在哪里?答:发布章节(保存章节+更新小说元信息)、用户注册(插入用户+初始化书架记录)、评论发布(插入评论+更新评论数),这些场景都加上了@Transactional。
  8. WebSocket连接需要鉴权吗?答:需要。正式系统应该在握手阶段校验用户的JWT令牌,防止未授权用户建立连接或接收到不属于自己的消息。

每个问题都不需要长篇大论,三五句话给到关键点加一句落地方案,就足以证明你确实是自己做的这个项目。

做这个系统前后用了大约一个半月的时间,最耗时的是数据库表设计和阅读器前端的细节调整,反而是后端CRUD接口开发速度最快,因为有SpringBoot自带的自动配置和MyBatis-Plus的代码生成器兜底。如果你也在做类似的毕设项目,我的核心建议是:不要一开始就追求功能堆砌,先把一本书从发布到阅读的完整闭环做通,再逐步加互动和实时特性;每一步都理解了"为什么这样做",答辩时你根本不会怯场。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/30 3:11:22

分区表与引导链路详解:从no such partition到双系统修复

开机屏幕停在黑底白字,一行error: no such partition,后面跟着grub rescue>提示符,输入什么指令都不认,只能干瞪眼。这种场面我见过太多次了,尤其是在双系统环境里手滑删了 Linux 分区之后。很多人第一反应是"…

作者头像 李华
网站建设 2026/9/30 3:11:06

智算算力规划与集群部署:从业务需求到落地实施方案解析

简介:面向智算中心规划建设与运营管理人群,这份v2.0版PDF系统整理了智算技术架构、算力需求测算、资源池规划、调度策略与落地实施要点。文档围绕实际项目推进中的规划设计难点展开,覆盖从需求分析到部署上线的关键环节,可作为智算…

作者头像 李华
网站建设 2026/9/30 3:10:37

TypeSafe 创始人论“智能内嵌“如何开启可编程的概率时代

自动化究竟去哪了?这是 TypeSafe AI 创始人 Diogo Almeida(迭戈阿尔梅达)抛出的核心问题,也是他建立整个公司的起点。在 a16z 播客中,他与合伙人 Ben Horowitz 和 Martin Casado 的对话,让这个问题变得格外…

作者头像 李华
网站建设 2026/9/30 3:10:24

UDP协议实验全流程:netns拓扑、关闭offload与Wireshark校验和分析

简介:计算机网络实验三:UDP协议探索和分析是一份完整的实验报告资源,适合计算机网络课程学生、Linux网络运维人员及协议分析初学者。报告以UDP协议为核心,通过搭建虚拟网络拓扑、配置静态路由、关闭网卡offload、使用nc命令建立客…

作者头像 李华
网站建设 2026/9/30 3:10:23

JDK自带三件套jstat+jmap+jstack实战:JVM性能排查与内存分析指南

1. 工具速览:JDK自带监控三件套到底能干什么排查Java生产环境问题,很多人第一反应是上VisualVM、Arthas这些重武器。但很多时候,机器上根本没装这些工具,尤其是客户机房、容器环境,网络隔离加上权限管控,想…

作者头像 李华
网站建设 2026/9/30 3:10:21

Windows本地HTTPS环境搭建:OpenSSL生成SSL证书与Nginx配置指南

最近重新装了一次开发机,把 Windows 下的本地 HTTPS 环境又完整搭了一遍。起因是一个项目里要调试摄像头和麦克风权限,还有 Service Worker 离线缓存,这些功能在 Chrome 里全都要求 secure context,也就是必须走 HTTPS 或者 local…

作者头像 李华