基于SpringBoot和Vue的毕设项目实战:从零搭建全栈应用并附完整代码
摘要:许多毕业生在毕设阶段面临全栈项目经验不足、前后端联调困难、代码结构混乱等问题。本文以一个可运行的毕业设计项目为载体,详细讲解如何基于SpringBoot构建RESTful后端服务,结合Vue3实现响应式前端,并完成JWT鉴权、数据库建模与部署打包。读者将获得一套结构清晰、注释完整、可直接复用的源码,显著提升开发效率与答辩质量。
1. 背景痛点:毕设全栈开发常见盲区
- 跨域处理:浏览器同源策略导致前端
localhost:5173无法直接访问后端localhost:8080,若未正确配置CORS,调试阶段频繁出现403/OPTIONS报错,浪费大量时间。 - 接口规范:无统一响应封装,返回格式时而
{"code":200}时而{"status":"ok"},前端需写多套解析逻辑,维护成本高。 - 代码组织:Controller、Service、DAO 全部堆在一个包下,包名随意,后期新增模块时互相引用,循环依赖导致 Spring 容器启动失败。
- 鉴权缺失:直接在 Session 里存
userId,分布式部署时状态无法共享,且易被伪造。 - 部署空白:本地能跑,放到云服务器 404,不知道 Nginx 需要配
try_files做 History 回退。
2. 技术选型:为何 SpringBoot + Vue3 而非 Django + React
| 维度 | SpringBoot + Vue3 | Django + React |
|---|---|---|
| 生态成熟度 | 国内社区庞大,Star 数 68k+,插件应有尽有 | 国际流行,国内中文资料相对少 |
| 学习曲线 | Java 学生有基础,注解式开发直观 | Python 语法简单,但 ORM 与迁移命令新概念多 |
| 打包部署 | 单 jar 直接java -jar | 需额外 uWSGI + Nginx 双进程 |
| 前端配套 | Vue3 中文文档完整,Vite 秒级热重载 | React 英文文档为主,新手易卡在 Hook 规则 |
| 就业匹配 | 国内 Java 岗位占比 >50%,毕设代码可直接当面试作品 | Python 岗位集中在数据分析,Web 岗偏少 |
结论:对计算机专业学生而言,SpringBoot + Vue3 组合“会的人多、资料全、面试能聊”,是毕业设计性价比最高的全栈方案。
3. 核心实现:用户登录与数据 CRUD 示例
3.1 工程结构(后端)
graduation-backend ├─ src/main/java/com/grad │ ├─ config │ │ ├─ CorsConfig.java │ │ └─ SecurityConfig.java │ ├─ controller │ ├─ dto │ ├─ entity │ ├─ mapper │ ├─ service │ └─ GradApplication.java └─ resources ├─ application.yml └─ mapper/xml3.2 统一响应封装(Clean Code 第一步)
@Data public class R<T> { private Integer code; private String msg; private T data; public static <T> R<T> ok(T data) { R<T> r = new R<>(); r.code = 200; r.msg = "success"; r.data = data; return r; } public static <T> R<T> fail(String msg) { R<T> r = new R<>(); r.code = 500; r.msg = msg; return r; } }3.3 JWT 工具类
@Component public class JwtUtil { private static final String KEY = "graduation_secret_2024"; private static final long EXPIRE = 86400000; // 1d public String createToken(Long userId) { return Jwts.builder() .setSubject(userStr.valueOf(userId)) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + EXPIRE)) .signWith(SignatureAlgorithm.HS256, KEY) .compact(); } public Long getUserId(String token) { return Long.valueOf(Jwts.parser() .setSigningKey(KEY) .parseClaims(token) .getBody() .getSubject()); } }3.4 登录接口(含密码加密)
@RestController @RequiredArgsConstructor @RequestMapping("/api/auth") public class AuthController { private final UserService userService; private final JwtUtil jwtUtil; @PostMapping("/login") public R<Map<String, Object>> login(@Valid @RequestBody LoginDTO dto) { // 1. 校验用户 User user = userService.lambdaQuery() .eq(User::getUsername, dto.getUsername()) .one(); if (user == null || !new BCryptPasswordEncoder().matches(dto.getPassword(), user.getPassword())) { return R.fail("账号或密码错误"); } // 2. 生成 Token String token = jwtUtil.createToken(user.getId()); Map<String, Object> map = new HashMap<>(); map.put("token", token); map.put("user", user); return R.ok(map); } }3.5 数据 CRUD(以“论文题目”模块为例)
@RestController @RequestMapping("/api/topic") @RequiredArgsConstructor public class TopicController { private final TopicService topicService; @GetMapping("/page") public R<Page<Topic>> page(@RequestParam(defaultValue = "0") int current, @RequestParam(defaultValue = "10") int size) { Page<Topic> page = topicService.page(new Page<>(current, size)); return R.ok(page); } @PostMapping public R<String> save(@Valid @RequestBody Topic topic) { topicService.save(topic); return R.ok("新增成功"); } @PutMapping("/{id}") public R<String> update(@PathVariable Long id日记 , @Valid @RequestBody Topic topic) { topic.setId(id); topicService.updateById(topic); return R.ok("修改成功"); } @DeleteMapping("/{id}") public R<String> delete(@PathVariable Long id) { topicService.removeById(id); return R.ok("删除成功"); } }3.6 Vue3 前端关键片段
// src/api/request.ts import axios from 'axios' const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE, timeout: 6000 }) // 请求拦截:统一携带 Token request.interceptors.request.use(config => { const token = localStorage.getItem('token') if (token) config.headers!.Authorization = `Bearer ${token}` return config }) // 响应拦截:统一弹错 request.interceptors.response.use( res => res.data, err => { ElMessage.error(err.response?.data?.msg || '服务异常') return Promise.reject(err) } ) export default request<!-- src/views/Login.vue --> <template> <el-form :model="form" @submit.prevent="handleLogin"> <el-form-item label="账号"> <el-input v-model="form.username" /> </el-form-item> <el-form-item label="密码"> <el-input type="password" v-model="form.password" /> </el-form-item> <el-button native-type="submit" type="primary">登录</el-button> </el-form> </template> <script setup lang="ts"> import { reactive } from 'vue' import { useRouter } router from 'vue-router' import request from '@/api/request' const form = reactive({ username: '', password: '' }) async function handleLogin() { const res = await request.post('/auth/login', form) localStorage.setItem('token', res.data.token) router.push('/dashboard') } </script>4. 部署与测试
打包后端
mvn clean package -DskipTests得到
graduation-backend-1.0.0.jar构建前端
npm run build生成
dist/静态资源Nginx 反向代理配置
server { listen 80; server_name your-domain.com; location / { root /usr/share/nginx/html/dist; try_files $uri $uri/ /index.html; } location /api { proxy_pass http://127.0.0.1:8080/api; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }Postman 测试
- 登录接口
POST {{base}}/api/auth/login - 在
Tests脚本里将返回的token设为全局变量{{token}} - 后续接口
Authorization类型选Bearer Token,自动带入,验证通过即 200
- 登录接口
5. 生产环境避坑指南
CORS 配置陷阱
本地开发允许allowedOrigins("*")上线后务必改为白名单,否则可被第三方网站恶意调用。Token 刷新机制
短 Token(15 min)+ 长 Refresh(7 d)双键方案,前端在 401 时静默调用/auth/refresh,避免用户跳登录页。敏感信息脱敏
返回User实体时,使用@JsonIgnore屏蔽password,并新建UserVO做字段裁剪,防止把邮箱手机号泄露给前端。文件上传大小限制
SpringBoot 默认 1 MB,生产需在application.yml调整spring: servlet: multipart: max-file-size: 10MB max-request-size: 50MB日志与监控
引入spring-boot-starter-actuator,暴露/actuator/health接口,配合 Prometheus + Grafana 做面板,答辩演示可加分。
6. 可扩展方向
- 集成 Redis 缓存:对
/topic/page做分页缓存,使用spring-cache注解,5 分钟失效,减轻数据库压力。 - 日志审计:利用 MyBatis-Plus 插件自动注入创建人、创建时间字段,并写
operate_log表,追踪谁在什么时间改了哪张表。 - 移动端适配:基于 Uni-app 复用 Vue 语法,编译成小程序,实现“一码多端”。
- 微服务拆分:将用户、论文、消息模块分别独立成服务,注册到 Nacos,体验 SpringCloud 治理。
7. 结语
整套代码已在 GitHub 开源,tagv1.0.0可直接跑通;若想进一步提升答辩亮点,建议从“性能优化”或“安全加固”任选一个点深挖,把对比数据、JMeter 压测图、安全扫描报告放到 PPT 里,让评委看到量化收益。毕业设计不是终点,而是第一份可展示的作品,动手把 Redis 缓存或日志审计加上去,你会感受到全栈链路真正跑通那一刻的成就感。祝你答辩顺利,代码常新。