最近在辅导学生做毕业设计时,发现很多同学对如何从零开始构建一个完整的JavaWeb项目感到迷茫,尤其是在整合前后端分离架构时,常常在环境配置、接口联调、数据库设计等环节卡住。本文将以一个“美食分享网站”为例,手把手带你完成一个基于Spring Boot 3.x和Vue 3.x的毕业设计项目。从项目初始化、数据库设计、核心功能开发到最终部署,全程提供可复制的代码和清晰的步骤,旨在帮助计算机专业的同学高效、高质量地完成毕业设计,也为希望学习全栈开发的开发者提供一个完整的实战案例。
1. 项目概述与技术选型
1.1 项目简介
美食分享网站是一个典型的Web应用,旨在为用户提供一个分享、浏览、搜索美食菜谱的平台。核心功能包括用户注册登录、菜谱发布、图片上传、菜谱分类浏览、收藏点赞、评论互动以及后台管理。这类项目业务逻辑清晰,技术栈主流,非常适合作为毕业设计或全栈入门项目。
1.2 技术栈详解
本项目采用前后端分离架构,这是当前企业级Web开发的主流模式。
- 后端 (Backend):
- Spring Boot 3.x: 作为核心后端框架,它简化了Spring应用的初始搭建和开发过程,提供了自动配置、内嵌Web服务器等特性,能让我们快速构建RESTful API。
- Spring Security + JWT: 用于实现用户认证与授权。Spring Security提供强大的安全框架,结合JWT(JSON Web Token)可以实现无状态的、可扩展的认证机制。
- MyBatis-Plus: 一个强大的MyBatis增强工具,在MyBatis的基础上只做增强不做改变,简化了CRUD操作,内置通用Mapper和分页插件,能极大提高开发效率。
- MySQL 8.0: 关系型数据库,用于存储用户、菜谱、评论等结构化数据。
- Redis: 作为缓存数据库,可用于存储用户会话、热门菜谱列表、验证码等,提升系统响应速度。
- Maven: 项目构建与依赖管理工具。
- 前端 (Frontend):
- Vue 3.x (Composition API): 渐进式JavaScript框架,用于构建用户界面。Vue 3的Composition API提供了更灵活的逻辑复用和代码组织方式。
- Vite: 下一代前端构建工具,提供极速的冷启动和快速的热更新,开发体验远超Webpack。
- Element Plus: 基于Vue 3的桌面端组件库,提供了丰富的UI组件,能快速搭建美观的界面。
- Axios: 基于Promise的HTTP客户端,用于向后端API发送请求。
- Vue Router: Vue.js官方的路由管理器,用于构建单页面应用。
- Pinia: Vue 3官方推荐的状态管理库,用于管理跨组件的共享状态。
- 开发工具:
- IntelliJ IDEA: 后端Java开发。
- Visual Studio Code: 前端Vue开发。
- Postman/Apifox: API接口调试。
- Navicat/DBeaver: 数据库管理。
2. 开发环境准备
2.1 基础软件安装
在开始编码前,请确保你的开发环境已安装以下软件:
- JDK 17+: Spring Boot 3.x要求JDK 17及以上版本。安装后配置
JAVA_HOME环境变量。 - Node.js 18+ & npm: Vue 3.x和Vite的运行时环境。建议安装LTS版本。
- MySQL 8.0+: 安装并启动MySQL服务,记住root用户的密码。
- Redis 7.x: 安装并启动Redis服务。
- Maven 3.6+: 用于管理后端项目依赖。
2.2 初始化后端Spring Boot项目
使用Spring Initializr(可通过IDEA内置工具或访问 start.spring.io )快速生成项目骨架。
- Project: Maven
- Language: Java
- Spring Boot: 3.2.x (选择稳定版本)
- Project Metadata:
- Group:
com.foodshare - Artifact:
food-share-backend - Packaging: Jar
- Group:
- Dependencies: 添加以下依赖:
- Spring Web
- Spring Security
- MyBatis Framework
- MySQL Driver
- Lombok (简化POJO代码)
- Validation (参数校验)
生成项目后,用IDEA打开,等待Maven下载完所有依赖。
2.3 初始化前端Vue项目
打开命令行工具,执行以下命令创建Vue项目:
# 使用Vite官方模板创建项目 npm create vue@latest food-share-frontend # 进入项目目录 cd food-share-frontend # 安装依赖 npm install # 安装额外需要的库 npm install element-plus axios vue-router pinia # 启动开发服务器 npm run dev创建过程中,根据提示选择需要的特性(如TypeScript, Vue Router, Pinia等)。
3. 后端核心功能实现
3.1 数据库设计与建表
根据业务需求,设计核心数据表。以下为部分关键表的SQL示例:
-- 用户表 CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `username` varchar(50) NOT NULL COMMENT '用户名', `password` varchar(255) NOT NULL COMMENT '密码', `nickname` varchar(50) DEFAULT NULL COMMENT '昵称', `avatar` varchar(500) DEFAULT NULL COMMENT '头像URL', `email` varchar(100) DEFAULT NULL COMMENT '邮箱', `status` tinyint 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 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户表'; -- 菜谱表 CREATE TABLE `recipe` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键', `title` varchar(200) NOT NULL COMMENT '菜谱标题', `description` text COMMENT '菜谱描述', `cover_image` varchar(500) DEFAULT NULL COMMENT '封面图URL', `user_id` bigint NOT NULL COMMENT '发布者ID', `category_id` bigint DEFAULT NULL COMMENT '分类ID', `difficulty` tinyint DEFAULT NULL COMMENT '难度(1简单 2中等 3困难)', `prep_time` int DEFAULT NULL COMMENT '准备时间(分钟)', `cook_time` int DEFAULT NULL COMMENT '烹饪时间(分钟)', `steps` json DEFAULT NULL COMMENT '步骤(JSON数组)', `ingredients` json DEFAULT NULL COMMENT '食材(JSON数组)', `view_count` int DEFAULT '0' COMMENT '浏览数', `like_count` int DEFAULT '0' COMMENT '点赞数', `collect_count` int DEFAULT '0' COMMENT '收藏数', `status` tinyint DEFAULT '1' COMMENT '状态(1公开 0私密 2审核中)', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_id` (`user_id`), KEY `idx_category_id` (`category_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='菜谱表';建议使用utf8mb4字符集以支持存储Emoji表情。
3.2 集成MyBatis-Plus与代码生成
- 在
pom.xml中添加MyBatis-Plus依赖:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.5.5</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-generator</artifactId> <version>3.5.5</version> </dependency>- 配置
application.yml数据库连接和MyBatis-Plus:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/food_share_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password data: redis: host: localhost port: 6379 database: 0 mybatis-plus: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印SQL,生产环境关闭 global-config: db-config: logic-delete-field: deleted # 全局逻辑删除字段名 logic-delete-value: 1 # 逻辑已删除值 logic-not-delete-value: 0 # 逻辑未删除值- 使用MyBatis-Plus代码生成器快速生成
Entity,Mapper,Service,Controller层代码,可以编写一个简单的生成类或使用官方提供的插件。
3.3 实现JWT认证与Spring Security配置
- 添加JWT依赖(如
jjwt)。 - 创建
JwtUtil工具类,用于生成和解析Token。 - 创建
LoginUser类实现Spring Security的UserDetails接口。 - 创建
JwtAuthenticationTokenFilter过滤器,用于拦截请求并验证JWT。 - 配置
SecurityConfiguration,放行登录、注册等接口,并配置密码加密器。 核心配置示例:
@Configuration @EnableWebSecurity public class SecurityConfig { @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http // 禁用CSRF,因为使用JWT无状态认证 .csrf().disable() // 设置会话管理为无状态 .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() // 配置请求授权 .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/login", "/api/auth/register", "/api/recipes/public/**").permitAll() // 公开接口 .anyRequest().authenticated() // 其他所有接口需要认证 ) // 添加JWT过滤器 .addFilterBefore(jwtAuthenticationTokenFilter(), UsernamePasswordAuthenticationFilter.class) // 配置异常处理 .exceptionHandling() .authenticationEntryPoint(authenticationEntryPoint) // 认证失败处理 .accessDeniedHandler(accessDeniedHandler); // 授权失败处理 return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }3.4 核心业务接口开发
以菜谱模块为例,开发RESTful API。
- Controller层 (
RecipeController.java): 定义API端点,处理HTTP请求和响应。
@RestController @RequestMapping("/api/recipes") @RequiredArgsConstructor public class RecipeController { private final RecipeService recipeService; @GetMapping("/{id}") public Result<RecipeVO> getRecipeById(@PathVariable Long id) { RecipeVO recipe = recipeService.getRecipeDetail(id); return Result.success(recipe); } @PostMapping public Result<Long> createRecipe(@Valid @RequestBody RecipeCreateDTO dto) { Long recipeId = recipeService.createRecipe(dto); return Result.success(recipeId); } @GetMapping public Result<PageResult<RecipeVO>> listRecipes(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) Long categoryId) { PageResult<RecipeVO> page = recipeService.listRecipes(pageNum, pageSize, categoryId); return Result.success(page); } }- Service层 (
RecipeServiceImpl.java): 实现业务逻辑,如参数校验、数据组装、事务管理等。 - DTO/VO对象: 使用
RecipeCreateDTO接收创建请求,使用RecipeVO返回给前端,与数据库实体Recipe解耦。 - 统一响应封装 (
Result.java): 定义统一的API响应格式。
@Data @NoArgsConstructor @AllArgsConstructor public class Result<T> { private Integer code; private String message; private T data; private Long timestamp; public static <T> Result<T> success(T data) { return new Result<>(200, "success", data, System.currentTimeMillis()); } // 其他静态方法如 error, fail 等 }3.5 文件上传功能
使用Spring Boot的MultipartFile处理图片上传,并集成OSS(如阿里云OSS、MinIO)或保存到本地。
- 配置文件上传大小限制。
- 创建
FileService,处理文件上传、生成访问URL、删除等逻辑。 - 在Controller中提供上传接口。
@PostMapping("/upload") public Result<String> uploadImage(@RequestParam("file") MultipartFile file) { if (file.isEmpty()) { return Result.error("文件不能为空"); } // 校验文件类型、大小 // 调用FileService上传 String url = fileService.upload(file); return Result.success(url); }4. 前端核心功能实现
4.1 项目结构搭建与配置
在food-share-frontend项目中,组织清晰的目录结构:
src/ ├── api/ # 所有API请求封装 ├── assets/ # 静态资源 ├── components/ # 公共组件 ├── composables/ # 组合式函数 ├── router/ # 路由配置 ├── stores/ # Pinia状态管理 ├── utils/ # 工具函数 ├── views/ # 页面组件 └── App.vue配置Vue Router和Pinia,并在main.js或main.ts中引入Element Plus。
4.2 封装全局请求工具
在utils/request.js中,使用Axios创建实例,并配置请求/响应拦截器,用于自动添加JWT Token、处理通用错误等。
import axios from 'axios'; import { ElMessage } from 'element-plus'; import router from '@/router'; import { useUserStore } from '@/stores/user'; const service = axios.create({ baseURL: import.meta.env.VITE_APP_BASE_API, // 从环境变量读取 timeout: 10000, }); // 请求拦截器 service.interceptors.request.use( (config) => { const userStore = useUserStore(); if (userStore.token) { config.headers['Authorization'] = `Bearer ${userStore.token}`; } return config; }, (error) => { return Promise.reject(error); } ); // 响应拦截器 service.interceptors.response.use( (response) => { const res = response.data; if (res.code !== 200) { ElMessage.error(res.message || 'Error'); // 处理特定错误码,如 401 跳转登录 if (res.code === 401) { router.push('/login'); } return Promise.reject(new Error(res.message || 'Error')); } else { return res; } }, (error) => { ElMessage.error(error.message || '请求失败'); return Promise.reject(error); } ); export default service;4.3 用户登录与状态管理
- 在
stores/user.js中创建Pinia store,管理用户登录状态、Token等信息。 - 创建登录页面
Login.vue,调用封装好的登录API。 - 登录成功后,将Token存储到Pinia和localStorage中,并跳转到首页。
<template> <div class="login-container"> <el-form :model="form" :rules="rules" ref="loginFormRef"> <el-form-item prop="username"> <el-input v-model="form.username" placeholder="请输入用户名" /> </el-form-item> <el-form-item prop="password"> <el-input v-model="form.password" type="password" placeholder="请输入密码" /> </el-form-item> <el-button type="primary" @click="handleLogin" :loading="loading">登录</el-button> </el-form> </div> </template> <script setup> import { ref, reactive } from 'vue'; import { useRouter } from 'vue-router'; import { ElMessage } from 'element-plus'; import { useUserStore } from '@/stores/user'; import { login } from '@/api/auth'; const router = useRouter(); const userStore = useUserStore(); const loginFormRef = ref(); const loading = ref(false); const form = reactive({ username: '', password: '', }); const rules = { username: [{ required: true, message: '请输入用户名', trigger: 'blur' }], password: [{ required: true, message: '请输入密码', trigger: 'blur' }], }; const handleLogin = async () => { await loginFormRef.value.validate(); loading.value = true; try { const res = await login(form); userStore.setToken(res.data.token); userStore.setUserInfo(res.data.userInfo); ElMessage.success('登录成功'); router.push('/'); } catch (error) { console.error(error); } finally { loading.value = false; } }; </script>4.4 菜谱列表与详情页开发
- 列表页 (
RecipeList.vue): 使用Element Plus的el-row和el-col进行栅格布局,结合el-card展示菜谱卡片。通过调用分页查询API获取数据,并实现分类筛选、搜索等功能。 - 详情页 (
RecipeDetail.vue): 根据路由参数中的ID,调用获取菜谱详情的API。页面布局可划分为:顶部菜谱信息(标题、封面、作者)、食材列表、步骤图文展示、评论区。 - 组件化: 将评论列表、点赞收藏按钮等抽离为独立组件,提高复用性。
4.5 菜谱发布与富文本编辑
- 使用
v-form和Element Plus表单组件构建发布页面。 - 集成富文本编辑器(如
@wangeditor/editor-for-vue),用于编辑菜谱的描述和步骤。注意处理图片上传到后端。 - 表单数据校验使用
async-validator或Vue的响应式校验。 - 提交时,将表单数据(包含富文本HTML内容或JSON格式的步骤)通过API提交到后端。
5. 前后端联调与跨域处理
5.1 后端解决跨域
在Spring Boot后端配置全局CORS,允许前端开发服务器的请求。
@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); } }5.2 前端代理配置
在Vite项目根目录的vite.config.js中配置开发服务器代理,避免开发时出现跨域问题。
import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' export default defineConfig({ plugins: [vue()], server: { proxy: { '/api': { target: 'http://localhost:8080', // 后端服务地址 changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, '') } } } })这样,前端在开发时请求/api/recipes会被代理到http://localhost:8080/api/recipes。
5.3 联调技巧
- 使用浏览器的开发者工具(F12)的Network面板查看请求和响应。
- 使用Postman或Apifox先独立测试后端API,确保接口逻辑正确。
- 前端调用API时,注意请求方法(GET/POST)、请求头(Content-Type, Authorization)、请求体格式(JSON/FormData)要与后端定义一致。
- 善用
console.log和断点调试。
6. 项目部署与上线
6.1 后端打包与运行
- 使用Maven打包:
mvn clean package -DskipTests,会在target目录生成food-share-backend-0.0.1-SNAPSHOT.jar。 - 在服务器上运行:
java -jar food-share-backend-0.0.1-SNAPSHOT.jar。可以使用nohup或配置为系统服务(如systemd)使其在后台运行。 - 生产环境配置:
- 使用
application-prod.yml覆盖开发配置,设置正确的数据库、Redis连接信息。 - 关闭开发工具(如
spring-boot-devtools)和调试日志。 - 配置正确的CORS源,而不是允许所有(
*)。 - 确保文件上传路径有读写权限,或正确配置OSS。
- 使用
6.2 前端打包与部署
- 构建生产版本:
npm run build,生成静态文件在dist目录。 - 部署方式:
- 方式一(前后端分离): 将
dist目录下的文件部署到Nginx或Apache等Web服务器。需要配置Nginx将API请求反向代理到后端服务。
# Nginx 配置示例 server { listen 80; server_name your-domain.com; location / { root /path/to/frontend/dist; index index.html; try_files $uri $uri/ /index.html; # 支持Vue Router的history模式 } location /api/ { proxy_pass http://localhost:8080/; # 代理到后端 proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } }- 方式二(静态资源托管): 将
dist目录上传至云存储(如阿里云OSS、腾讯云COS)并开启静态网站托管,配合CDN加速。
- 方式一(前后端分离): 将
7. 常见问题与排查思路
在开发过程中,你可能会遇到以下典型问题:
| 问题现象 | 可能原因 | 排查与解决思路 |
|---|---|---|
| 前端访问后端API出现404 | 1. 后端服务未启动。 2. 请求URL路径错误。 3. 后端Controller未正确映射。 | 1. 检查后端服务日志,确认是否启动成功。 2. 核对前端请求的URL与后端 @RequestMapping定义的路径是否一致。3. 使用Postman直接测试后端接口。 |
| 登录成功但后续请求返回401 | 1. 前端未正确携带Token。 2. Token已过期。 3. 后端JWT解析失败或密钥不匹配。 | 1. 检查Axios请求拦截器是否成功添加了Authorization头。2. 检查Token有效期,实现Token自动刷新逻辑。 3. 检查后端JWT工具类使用的密钥是否一致。 |
| 文件上传失败或无法访问 | 1. 文件大小超出限制。 2. 存储路径权限不足。 3. 返回的URL路径不正确。 | 1. 检查Spring Boot的spring.servlet.multipart.max-file-size配置。2. 检查服务器上文件保存目录的读写权限。 3. 确认文件访问URL是否被正确拼接,Nginx是否配置了静态资源映射。 |
| Vue页面刷新后路由丢失或404 | Vue Router使用了history模式,但服务器未配置。 | 在Nginx配置中添加try_files $uri $uri/ /index.html;(见6.2节)。 |
| 数据库连接失败 | 1. MySQL服务未启动。 2. 连接URL、用户名或密码错误。 3. 时区设置问题。 | 1. 检查MySQL服务状态。 2. 核对 application.yml中的配置。3. 在JDBC URL中添加 &serverTimezone=Asia/Shanghai。 |
| 页面样式错乱或Element Plus组件未生效 | 1. Element Plus未正确引入或注册。 2. 组件按需引入配置错误。 | 1. 检查main.js中是否全局引入了Element Plus。2. 如果使用按需导入,检查 unplugin-vue-components插件配置。 |
8. 项目优化与扩展建议
一个合格的毕业设计不应只停留在功能实现,适当的优化和扩展能体现你的技术深度。
8.1 性能优化
- 数据库层面:
- 为
recipe表的user_id,category_id,create_time等常用查询字段建立索引。 - 对复杂的多表关联查询,考虑使用MyBatis-Plus的查询优化或手动编写优化后的SQL。
- 合理使用数据库连接池(如HikariCP,Spring Boot默认集成)。
- 为
- 应用层面:
- 缓存: 使用Redis缓存热点数据,如首页菜谱列表、用户信息。注意设置合理的过期时间和缓存更新策略(如删除更新)。
- 异步处理: 对于耗时的操作(如发送邮件、生成复杂报表),可以使用Spring的
@Async注解或消息队列(如RabbitMQ)进行异步处理,提升接口响应速度。 - 图片处理: 用户上传的图片进行压缩、生成缩略图,并使用CDN加速访问。
8.2 功能扩展
- 搜索功能: 集成Elasticsearch,实现菜谱标题、描述、食材的全文本搜索,并支持高亮、分词和相关性排序。
- 社交功能: 增加关注用户、私信、动态Feed流(类似朋友圈)等功能。
- 数据可视化: 在后台管理界面,使用ECharts展示网站数据统计,如每日新增用户、热门菜谱排行等。
- 第三方登录: 集成微信、QQ、Github等OAuth2.0第三方登录,提升用户体验。
- 容器化部署: 编写
Dockerfile和docker-compose.yml,将后端、前端、MySQL、Redis等服务容器化,实现一键部署。
8.3 代码质量与安全
- API文档: 使用
SpringDoc OpenAPI(Swagger UI)自动生成并维护API文档,方便前后端协作。 - 参数校验: 在DTO类中使用
@NotNull,@Size,@Pattern等注解进行入参校验,并在Controller中使用@Valid注解触发。 - 全局异常处理: 使用
@ControllerAdvice和@ExceptionHandler定义全局异常处理器,统一处理业务异常、参数校验异常等,返回友好的错误信息。 - SQL防注入: 坚持使用MyBatis的
#{}预编译方式,避免使用字符串拼接的${}。 - 密码安全: 使用BCrypt等强哈希算法存储用户密码,绝对禁止明文存储。
- 接口限流与防刷: 对登录、注册、短信发送等接口使用Guava RateLimiter或Redis实现简单的限流,防止恶意请求。
通过以上步骤,你不仅完成了一个功能完整的美食分享网站,更系统地实践了从需求分析、技术选型、环境搭建、编码实现、联调测试到部署上线的全流程。这个项目涵盖了JavaWeb开发的核心技术栈,可以作为你求职简历中的一个有力项目经历。在开发过程中,多思考、多尝试、多总结,遇到问题善用搜索引擎和官方文档,你的工程能力一定会得到质的提升。