SpringBoot+Vue论坛系统毕设实战:从表设计到答辩全流程解析
2026/9/10 7:14:16 网站建设 项目流程

1. 项目概述与定位

这个论坛网站平台,是我搞了一整个学期才最终完成的毕设项目。用到的技术栈很清晰:后端SpringBoot,前端Vue,数据库MySQL,跑通之后把源码、SQL脚本、接口文档打了个全家桶,直接当毕设交上去,导师给的评价是"系统完整、流程规范、有工程意识"。

说实在的,论坛类项目在Java Web毕设里属于典型的"入门偏上"选题。它不像电商系统那样涉及支付、秒杀、库存,也不像管理系统那样纯CRUD,论坛天然包含用户体系、内容发布、互动评论、权限控制这几大块,难度刚好够用,又能把SpringBoot和Vue的核心能力都覆盖到。

我翻了这几年网上的主流毕设题目,论坛类项目几乎每年都有,但很多代码质量堪忧,要么是老的JSP+Servlet写的,要么是前后端不分离的"伪Vue"项目,要么SQL脚本缺表结构注释,要么接口文档就是word里随便截几张图。我做的时候就把目标定得很明确:细节必须到位,别人拿到手能跑、能改、能答辩。

这篇博文不打算走"小白零基础从环境搭建开始"的老路,而是站在"你已经会SpringBoot和Vue基本用法、想做出一个完整可答辩的项目"这个角度来拆解,把整个项目的设计思路、表结构、核心接口、前端页面组织方式、以及我在开发过程中踩过的坑全部摊开讲。你拿到的是源码和文档,但我会告诉你每一块代码背后的设计逻辑,这才是对毕设真正有价值的东西。

2. 技术选型与版本选择的实战考量

2.1 为什么是SpringBoot而不是SSM或JSP

我做项目之前问过自己一个问题:用SSM手写配置,还是用SpringBoot自动装配?最后毫不犹豫选了SpringBoot。

核心原因是SpringBoot的自动配置帮我把大量样板代码干掉了。SSM模式下你要写web.xml、spring-mvc.xml、spring-mybatis.xml,还要操心jar包版本冲突;SpringBoot只要引入spring-boot-starter-web,内嵌Tomcat直接启动,配置文件用application.yml集中管理,全程不需要一个XML。对于毕设这种时间紧、任务重的场景,效率就是生命线。

但这里有个关键点要提醒:SpringBoot虽然简化了配置,不代表你不需要理解底层机制。比如内部Tomcat端口配置,我在application.yml里写的:

server: port: 8080 servlet: context-path: /

这配置看起来就两行,但如果你不理解"SpringBoot内嵌Servlet容器"这个机制,遇到端口被占用或者部署到外置Tomcat的场景就会抓瞎。我在开发过程中就遇到过8080端口被其他程序占用的情况,排查了半天。后面学乖了,每次启动前先用netstat -ano | findstr 8080检查端口,有占用就直接在命令行杀掉进程,或者改端口。

2.2 SpringBoot版本到底选2.x还是3.x

很多拿到项目的同学会纠结这个问题,我直接给结论:如果老师没硬性要求,选择Spring Boot 2.7.x系列,JDK用8。原因很简单:

  • SpringBoot 3.x强制要求JDK 17,有些老电脑上的开发环境还是JDK 8,To C的兼容性差很多
  • 大部分网上资料、CSDN教程、黑马课程都以2.x为基础,你遇到问题搜解决方案的时候,搜出来的基本都能直接套用
  • 我的项目用的是2.7.14版本,这个版本稳定,又没有2.7之前版本的一些安全漏洞,整体性价高

Maven依赖里Spring Boot的parent声明:

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.7.14</version> <relativePath/> </parent>

2.3 ORM框架选MyBatis还是MyBatis-Plus

论坛系统的数据操作有很多单表CRUD,比如查询用户信息、插入评论记录、分页查询帖子列表,这些操作用MyBatis-Plus能省大量时间。BaseMapper接口里已经内置了insert、selectById、selectPage这些方法,你都不用写SQL,直接用就行。

我第一次跑通MyBatis-Plus的代码生成器,那种感受是:再也不想回去手写BaseMapper的XML映射文件了。你只需要在实体类上加个@TableName注解指定表名,在Mapper接口里继承BaseMapper,再配合Service层的IService,整个单表CRUD就齐了。

不过也要守住边界。多表关联查询这种复杂场景,MyBatis-Plus也能处理,但我觉得不如手写XML里的自定义SQL直观。比如论坛首页要做"帖子列表+作者信息+分类名称+评论数量"的多表查询,直接在XML里写JOIN要清晰得多,而且SQL的执行计划更可控。

所以我的项目是两者混用的:简单的单表操作用MyBatis-Plus自带方法,涉及多表关联的统计类查询则是手写SQL在Mapper XML里。这也是真实企业项目里最常见的操作方式。

2.4 前端Vue版本和UI库的选择

Vue 3肯定是主流了,搭配Element Plus做后台和前台界面,UI效果在答辩演示的时候非常加分。Vue 2虽然稳定,但已经处于维护末期,新项目我强烈建议直接用Vue 3。

Element Plus有现成的组件,比如菜单、表格、表单、分页、消息提示,这些是论坛平台最常见的界面元素。不用自己手写CSS去画按钮、弹窗,把精力放在业务逻辑上。我用的脚手架是Vite,不是vue-cli,因为Vite的冷启动速度比Webpack快得多,热更新体验也更好。

npm create vite@latest forum-frontend -- --template vue cd forum-frontend npm install element-plus @element-plus/icons-vue axios vue-router pinia

这是完整的依赖清单。axios做HTTP请求,vue-router做路由跳转,pinia做状态管理。你可能想问为什么不用Vuex,因为Pinia是Vue 3官方推荐的状态管理库,API更简洁,没有Mutation这类冗余概念。在论坛项目里,Pinia主要用来存用户登录状态和用户信息,比如token、用户名、头像,这些信息在多个页面都要用。

3. 数据库设计与SQL脚本的实现细节

3.1 论坛系统的表结构设计思路

论坛网站的核心数据模型,我梳理下来一共六张表。第一张是用户表,第二张是帖子表,第三张是评论表,第四张是分类板块表,第五张是点赞记录表,第六张是收藏记录表。有些项目还会加管理员表、系统通知表、关注关系表,但毕设没必要无限扩大复杂度,六张表刚好够表达完整业务闭环。

设计表结构的时候有个经验:每个表必须有自增主键id、create_time创建时间、update_time更新时间。这三个字段是标配。尤其create_time,就是你做时间排序、做数据统计、做用户行为分析的基础,没有它很多查询逻辑会非常别扭。我见过很多人设计表时省略update_time,等到后面要做"编辑帖子显示最后修改日期"时又回头加字段,很折腾。

用户表的核心字段包括:id、username用户名、password密码(加密存储)、nickname昵称、avatar头像地址、email邮箱、status状态、type类�型、create_time。其中密码加密我用的是MD5+盐值的方式,没有直接用明文。这里要说明一下,虽然现在更推荐BCrypt这类强哈希算法,但MD5+盐值实现的代码量更少,答辩时你只需要说清楚"密码是加密存储的"就可以了,导师关注的是你有安全意识这个意识本身。

帖子表是最核心的表,字段有:id、user_id发帖人、category_id所属分类、title标题、content内容(用TEXT类型)、view_count浏览次数、like_count点赞数、comment_count评论数、is_top是否置顶、status状态(正常/锁定)、create_time、update_time。其中user_id和category_id都要建外键索引,但不一定真的建物理外键约束。企业开发中通常用逻辑外键,也就是不加FOREIGN KEY,只建INDEX,这样是为了避免外键约束对性能的影响和操作顺序的严格限制。毕设里我会建议你建物理外键,因为答辩老师有时候检查表关系图时,看的是外键关系线。

评论表:id、post_id、user_id、content、parent_id回复哪条评论、reply_user_id回复谁、create_time。parent_id是用来做楼中楼嵌套回复的,如果不做楼中楼,可以只保留post_id和user_id,就是简单的扁平评论。

点赞记录表:id、user_id、post_id、create_time,联合唯一索引uniq_user_post,确保一个用户对同一帖子只能点一次赞。收藏记录表结构与它基本一致。

3.2 SQL脚本要写好注释和初始化数据

我提供给同学们的这个SQL脚本,不是只建个库和表就完了,它还包含了两类初始化数据。第一类是分类板块数据,比如"技术交流""校园生活""求职就业""休闲灌水"这些默认分类,必须由脚本插入,否则项目启动后前端页面的分类菜单全是空的。第二类是演示账号,包括一个管理员账号和一个普通用户账号,方便拿到项目后直接登录测试。

SQL脚本的注释我也是认真写的,每个字段都标注了业务含义。很多人的SQL脚本打开是一堆没有任何注释的建表语句,这给后面用这个项目的人造成了很大的困扰。我已经把注释写在每个字段后面,比如:

CREATE TABLE `t_user` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID', `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(100) NOT NULL COMMENT '密码(MD5加密存储)', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `avatar` varchar(255) DEFAULT NULL COMMENT '头像URL', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `status` tinyint(1) DEFAULT '1' COMMENT '状态:1正常 0禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

注意字符集用utf8mb4,不是utf8。utf8在MySQL里最大只支持3字节,而像emoji表情这类需要4字节,如果你用utf8,用户在前端发帖时输入一个表情符号就会报错。utf8mb4作为utf8的超集,是现在建表的绝对首选。

4. 后端SpringBoot核心功能实现与源码结构

4.1 项目目录结构规划

SpringBoot项目的包结构我遵循的是常见分层架构:controller、service、mapper、entity、common、config。这样的结构对毕设来说已经足够清晰,你写代码的时候能快速定位每个类的位置,同时导师检查项目结构时也一目了然。

我习惯的包结构分法:

com.forum ├── controller # 控制层,接收前端请求 ├── service # 业务逻辑层,处理核心业务 │ └── impl # 接口实现类 ├── mapper # 数据访问层 ├── entity # 实体类,对应数据库表 ├── vo # 视图对象,返回给前端的数据模型 ├── dto # 数据传输对象,接收前端的请求参数 ├── common # 公共类:统一返回值、异常处理、常量 ├── config # 配置类:跨域、拦截器、MyBatis-Plus分页 ├── util # 工具类:JWT、MD5加密等 └── exception # 自定义异常类

包结构里的entity和vo的区别,很多新手容易搞混。entity对应数据库表的字段,是一一对应的;vo是根据页面需要自定义的数据模型。举个例子:帖子的列表页面需要显示"作者昵称"和"分类名称",但post表里只有user_id和category_id,没有冗余这两个名称字段,这时你就创建一个PostVO,里面有postId、title、content、authorName、categoryName、commentCount等字段,把查询结果直接封装进去返回给前端。这样前端拿到的数据就是展示所需的,省去了多次请求。

4.2 JWT认证的实现逻辑

论坛的注册、登录、发帖、评论这几个核心操作都需要用户认证。传统的Session方案在前后端分离架构下不太好用,因为前端和后端可能不在同一个域名下,SessionId的传递和管理都比较麻烦。我用的是JWT方案,它把用户信息加密成一串token,后端不需要存储Session,前端保存token并在每次请求时放在Header里,后端通过拦截器校验token的合法性就能识别用户。

JWT依赖:

<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>

生成token的核心代码:

public String generateToken(User user) { Date now = new Date(); Date expiryDate = new Date(now.getTime() + 86400000); // 有效期24小时 return Jwts.builder() .setSubject(String.valueOf(user.getId())) .claim("username", user.getUsername()) .setIssuedAt(now) .setExpiration(expiryDate) .signWith(SignatureAlgorithm.HS256, SECRET_KEY) .compact(); }

拦截器里解析token,把用户信息放入ThreadLocal。这样在Controller里通过UserUtil.getCurrentUser()就能拿到当前登录用户,不用每个方法都显式传递userId参数,代码会干净很多。

4.3 统一返回值与全局异常处理

接口返回的数据格式如果不统一,前端在处理响应时会很痛苦。我用一个Result类统一封装所有接口的返回:

public class Result<T> { private Integer code; // 200成功,500失败,401未登录 private String message; // 提示信息 private T data; // 数据 }

所有Controller的返回类型都是Result,只不过泛型不同。比如登录接口返回Result<String>,token作为data;帖子列表接口返回Result<Page<PostVO>>。这样做的好处是前端axios拦截器可以统一判断code,等于200就走成功逻辑,等于401就跳到登录页,等于500就弹错误提示,不用在每次请求里面都写if/else。

全局异常处理我用了@RestControllerAdvice注解,定义一个全局异常处理类,分为业务异常、参数校验异常、系统异常三层处理。业务异常是主动抛出的,比如用户名重复、帖子不存在、没有权限删除;参数校验异常是@Valid注解校验失败的;系统异常是兜底,返回一个友好的提示,避免把具体报错信息泄露给用户。

这是必要的安全防护机制,你可能觉得"反正是毕设无所谓",但如果系统异常把SQL语句细节返回给前端,页面直接显示SQL错误,答辩的时候导师一眼就看到,观感很不好。

4.4 帖子发布与分页查询的关键处理

帖子发布接口的设计要注意内容长度的校验,标题和正文都需要做非空和长度限制。我在代码里用@NotBlank和@Size注解校验:

@NotBlank(message = "标题不能为空") @Size(max = 100, message = "标题长度不能超过100") private String title; @NotBlank(message = "内容不能为空") @Size(max = 20000, message = "内容长度不能超过20000") private String content;

帖子列表分页,我用MyBatis-Plus的分页插件PageHelper的替代品。配置一个MybatisPlusInterceptor的Bean,然后把分页插件加进去:

@Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }

然后Service里就能用LambdaQueryWrapper进行条件查询加排序:

Page<Post> page = new Page<>(pageNum, pageSize); LambdaQueryWrapper<Post> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(Post::getCategoryId, categoryId) // 按分类筛选 .orderByDesc(Post::getIsTop) // 置顶帖优先 .orderByDesc(Post::getCreateTime); // 新帖优先 Page<Post> result = postMapper.selectPage(page, wrapper);

分页参数pageNum和pageSize是由前端传的,默认值分别是1和10。前端一般会做上一页下一页、跳转到第几页的功能。分页插件底层自动帮我们执行了count查询和limit拼接,我在一个数据量只有几百条的环境里测试下来性能完全没问题。

5. 前端Vue页面搭建与接口联调

5.1 前端页面结构和路由设计

论坛前端我设计了以下几个页面:登录注册页、首页(帖子列表)、帖子详情页、发布帖子页、个人中心页、分类管理页(管理员)。每个页面对应router里的一个路由:

const routes = [ { path: '/', name: 'Home', component: Home, meta: { title: '首页' } }, { path: '/login', name: 'Login', component: Login }, { path: '/register', name: 'Register', component: Register }, { path: '/post/:id', name: 'PostDetail', component: PostDetail, meta: { title: '帖子详情' } }, { path: '/publish', name: 'Publish', component: Publish, meta: { requiresAuth: true } }, { path: '/profile/:id', name: 'Profile', component: Profile, meta: { requiresAuth: true } }, { path: '/admin/category', name: 'AdminCategory', component: AdminCategory, meta: { requiresAuth: true, role: 'admin' } } ]

meta里的requiresAuth用于路由守卫判断是否需要登录,role用于判断管理员权限。在router.beforeEach全局前置守卫里做校验:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.meta.requiresAuth && !token) { next({ path: '/login', query: { redirect: to.fullPath } }) } else { next() } })

5.2 axios封装与请求拦截器

前端请求不能每次都写axios.get然后重复处理错误。我把axios封装成一个request工具类,统一配置baseURL和请求拦截、响应拦截:

import axios from 'axios' import { ElMessage } from 'element-plus' import router from '../router' const request = axios.create({ baseURL: '/api', timeout: 10000 }) // 请求拦截器:自动带上token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }, error => { return Promise.reject(error) }) // 响应拦截器:统一处理code request.interceptors.response.use(response => { const res = response.data if (res.code !== 200) { ElMessage.error(res.message || '请求失败') if (res.code === 401) { localStorage.removeItem('token') router.push('/login') } return Promise.reject(new Error(res.message)) } return res }, error => { ElMessage.error('网络异常,请稍后重试') return Promise.reject(error) }) export default request

这样封装之后,业务代码里的请求变得非常简洁,比如获取帖子列表:

const res = await request.get('/post/list', { params: { pageNum, pageSize, categoryId } })

5.3 富文本编辑器的集成

发帖功能需要支持基本的文本格式,比如加粗、插入链接、插入图片等。我在发布帖子页面集成了wangeditor这个轻量级富文本编辑器,按需引入就行了。

编辑器渲染出来的内容是HTML格式,存到数据库的时候要注意XSS攻击防护。我采用的是在服务端做HTML过滤,用jsoup这个库:

<dependency> <groupId>org.jsoup</groupId> <artifactId>jsoup</artifactId> <version>1.16.1</version> </dependency>

过滤脚本标签和事件属性:

public String cleanHtml(String content) { Whitelist whitelist = Whitelist.relaxed(); // 允许的常用标签,例如p、br、strong、img等 return Jsoup.clean(content, whitelist); }

这个细节可以在答辩时主动讲出来,导师会觉得你考虑到了网络安全层面的问题。

5.4 前后端联调时的代理配置

开发阶段前端跑在5173端口(Vite默认),后端跑在8080端口,跨域是必须处理的问题。后端我配置了CORS跨域:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

前端Vite开发环境也需要配置代理,在vite.config.js里:

server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }

生产部署时则用Nginx反向代理,把/api开头的请求转发到Java后端。这块其实不是毕设必须的,但我会推荐你了解,因为答辩时"部署方案"往往是加分项。

6. 接口文档设计与核心接口说明

6.1 接口文档怎么组织才专业

接口文档我用的是Markdown格式整理的,每一个接口包含:请求URL、请求方式、请求参数、返回数据格式、权限要求、示例。这套文档完全是照着企业级接口文档的规范写的,而不是应付了事。一个规范的接口文档,能让使用者不用看任何代码就能把接口对接好。

文档的目录结构是这样的:

docs/ ├── README.md # 文档说明,列出所有接口的汇总 ├── user.md # 用户模块接口 ├── post.md # 帖子模块接口 ├── comment.md # 评论模块接口 └── category.md # 分类模块接口

每个接口的说明,我举个例子。这是帖子分页列表接口:

接口路径:GET /api/post/list 接口描述:分页查询帖子列表,支持按分类筛选、按关键词搜索 请求参数: - pageNum:页码,默认1 - pageSize:每页条数,默认10 - categoryId:分类ID,可选 - keyword:搜索关键词,可选 返回示例: { "code": 200, "message": "success", "data": { "records": [ { "id": 1, "title": "SpringBoot整合MyBatis-Plus踩坑记录", "authorName": "admin", "categoryName": "技术交流", "commentCount": 12, "createTime": "2024-11-20 10:30:00" } ], "total": 1, "size": 10, "current": 1 } }

6.2 核心接口列表

用户模块的接口主要有注册、登录、获取当前用户信息、更新用户信息、修改密码。注册和登录是不需要认证的,其他都要带token。

帖子模块的接口最多:分页查询帖子、查询帖子详情、发布帖子、编辑帖子、删除帖子、点赞/取消点赞、收藏/取消收藏。其中删除帖子要考虑权限,只有帖子的作者本人或者管理员才能删除,后端Service里要判断。

评论模块的接口:获取帖子的评论列表、发表评论、删除评论。因为当前论坛系统没有做楼中楼回复的完整功能,所以parent_id这字段保留着,但接口层面暂时只支持一级评论。

分类模块的接口:获取所有分类(用于发帖时选择分类)、新增分类(管理员)。这个模块比较简单,但要注意在删除分类前判断是否有帖子属于该分类。

6.3 接口安全设计的几个要点

接口安全这块,我在做的时候总结了几个要点。登录和注册接口要做频率限制,防止暴力破解,这个用的是拦截器加计数器的方案,在拦截器里对同一个IP的请求次数做统计,超过阈值就拒绝。帖子内容的读取要注意处理HTML转义,防止恶意脚本注入。删除接口在Service层一定先查后删,确保对象存在且有权限,避免空指针异常。

还有一个很重要的点:返回给前端的数据库自增主键id不能直接用有符号long类型,因为前端JavaScript的Number类型安全整数上限是2^53-1,如果你用雪花算法生成的long类型id返回给前端,精度会丢失。但我的项目因为用的是数据库自增id,数值范围远没到那么大,所以没有这个问题。不过这个问题在面试中常被提到,理解它是有好处的。

数据库层面防止SQL注入,我用的是MyBatis的#{}占位符而不是${},前者是预编译参数化查询,后者是直接将字符串拼接进SQL。MyBatis-Plus的LambdaQueryWrapper本身也是参数化的,这个可以从源头上避免SQL注入。

7. 常见问题与排查技巧实录

7.1 数据库连接失败和中文乱码

拿到项目源码后,最容易出问题的就是数据库连接。第一个问题是数据库版本不同导致驱动类不对,MySQL 8.x要写com.mysql.cj.jdbc.Driver,MySQL 5.x写com.mysql.jdbc.Driver。很多同学的MySQL版本是5.7或者8.0,我的项目是基于8.0开发的,如果你是5.7,驱动类路径要改一下。

第二个问题是中文乱码。启动项目后页面显示中文全是问号,一般原因是数据库连接URL没有加characterEncoding=utf8参数:

spring: datasource: url: jdbc:mysql://localhost:3306/forum?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai

serverTimezone参数也很重要,不配置的话在高版本的MySQL驱动下会报时区错误,说serverTimezone需要被明确指定。

还有一点要注意:MySQL的sql_mode配置对新版本的MySQL 8.0有影响。如果你导入SQL脚本时报错,比如"ONLY_FULL_GROUP_BY"相关的错误,需要检查一下当前的sql_mode,在MySQL命令行执行SELECT @@sql_mode;,如果包含ONLY_FULL_GROUP_BY,可以临时移除。

7.2 npm安装依赖慢或安装失败

前端依赖安装经常出问题。npm的镜像源是国外的,速度很慢,我直接把镜像切到淘宝的方式:

npm config set registry https://registry.npmmirror.com

还有最经典的node-sass报错,但我项目用的是sass(dart-sass),没有node-sass的编译问题,用sass或scss做样式编写时很省心。如果你在npm install过程中遇到node-gyp相关的报错,常见的解决方式是删除node_modules文件夹,清理npm缓存,然后重新安装。

7.3 Token过期和刷新机制

JWT有个特点:服务端无法主动让某个token失效,所以用户修改密码或者被管理员禁号后,原来的token在过期之前仍然有效。这会带来一定的安全隐患。我在项目里采用的方案是:拦截器里每次请求都到数据库检查用户状态,status是否为1,为0就返回"账号已被禁用"。这样虽然多了一次数据库查询,但确保了被禁用的用户不能继续操作。

token的有效期我设置的是24小时,如果用户24小时无操作就需要重新登录,这个时长对毕设来说足够了。关于"记住我"功能,可以在前端登录页加一个勾选框,勾选时把token存localStorage,否则存sessionStorage,后者关闭浏览器就失效。这个细节虽然小,但做出来体验很好。

7.4 跨域问题排查

跨域是前后端分离项目最经典的坑。前端报错"CORS policy: No 'Access-Control-Allow-Origin' header",处理步骤是:先看控制台报错信息,确认是跨域问题还是404问题;然后检查后端CorsConfig是否生效,注意拦截器的preflight请求;再检查Vite代理的target地址是否正确。

有次排查我见过最坑的情况:前端所有接口请求都成功,只有带token的请求报跨域错误。原因就是自定义的Authorization请求头没有在allowedHeaders允许的范围内。所以CORS配置里allowedHeaders("*")是可以简化这个问题的。

8. 答辩演示与项目扩展建议

8.1 演示时要有演示脚本

答辩演示流程不能上台再想下一步做什么,提前写一个演示脚本,按顺序展示每个功能。我自己准备的顺序是:先介绍项目结构和技术栈,然后启动项目,从注册账号开始演示,展示首页帖子列表、帖子详情页的评论互动、发布帖子的完整流程、个人中心的信息修改,最后由管理员账号演示分类管理功能。

演示过程中要有意识地切换到代码中,展示一些关键代码。比如你写到JWT登录时,可以切到JwtUtil类,说明token是如何生成和解析的;演示分页时切到MyBatis-Plus配置类,说明分页插件的用法。这样导师不仅能确认项目是真实可运行,还能确认代码确实是自己的。

8.2 项目可以怎么扩展

论坛项目扩展空间其实很大,如果你想做得比基础要求更多,可以试试加这几个方向。第一个是搜索功能,在首页提供关键词搜索,用MySQL的LIKE语句就能实现简单的模糊搜索,后续可以升级为Elasticsearch全文检索,这是在毕设中很拿得出手的点。第二个是消息通知,当你的帖子被评论、你的评论被回复时,通过站内信或邮件通知你。第三个是私信功能,用户之间点对点聊天。

这些扩展方向每个都涉及新的数据表和新接口,可以写进"项目展望"部分作为论文的结尾章节。导师看到你有能力把一个系统规划得更完整,即使只完成了基础的论坛功能,也会认为你的架构设计能力是达标的。

8.3 项目文档的组织与答辩说明

论文和文档中,建议画一张系统架构图,展示"前端Vue -> Nginx -> 后端SpringBoot -> MySQL"的完整链路,放在论文的技术架构章节。另外每个核心接口的时序图,比如"用户登录 -> 校验用户名密码 -> 生成token -> 返回前端 -> 后续请求携带token -> 拦截器校验",这些能清楚地表达你对整个业务流程的理解。

文档的README部分,我写好了环境要求、项目启动步骤、默认账号密码、项目目录结构说明。拿到项目后按README一步步操作,基本10分钟就能把项目跑起来。

9. 写在最后的赶工经验和避坑总结

做这个项目最大的收获不是技术本身,而是学会了"先设计再编码"的工程习惯。刚开始时我也想着能跑就行,直接上手写代码,结果写到一半发现表设计不合理,用户表和帖子表都没法很好地支持分页查询,又推倒重构了一遍。后面耐住性子先把表结构和接口文档写好,再动手敲代码,进度反而快了很多。

另一个建议是:做毕设时一定要给自己的项目写好每一处业务逻辑的注释。不是写满代码注释,而是关键业务逻辑处用几行文字说清楚"这里为什么这样做"。这样过两个月再打开代码,你还能快速回忆起设计思路,答辩前复习也不用重新翻需求文档。

如果你手头的是其他类似的源码项目,比如旅游管理系统、图书管理系统、商城系统,只要基于SpringBoot+Vue这套架构,这篇博文里的设计思路、排错技巧、答辩经验基本都能直接借鉴。

最后再分享一个同学们普遍容易忽略的小点:项目命名规范。包名统一小写,接口路径统一小写加斜杠,前端组件用驼峰命名,路由用短横线连接。这件事虽然不直接影响功能,但一个命名规范的项目给导师的观感会比乱起名的项目好上不止一个档次。做开发,保持整洁永远是一种专业素养。

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

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

立即咨询