1. 项目概述
作为一名长期从事前后端开发的技术博主,我最近完成了一个基于SpringBoot+Vue的智能菜谱推荐系统。这个项目源于我个人的实际需求——作为一个996程序员,经常为"今天吃什么"这个问题困扰。市面上的菜谱App要么推荐不够精准,要么功能过于复杂。于是,我决定自己开发一个既能满足日常烹饪需求,又能根据个人口味智能推荐的系统。
这个系统采用了主流的前后端分离架构,后端使用SpringBoot+MyBatis+MySQL技术栈,前端采用Vue.js+ElementUI。系统最大的亮点是实现了基于用户行为的个性化推荐算法,能够根据你的历史浏览、评分和收藏记录,智能推荐符合你口味的菜谱。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计
2.1 技术选型考量
在技术选型上,我主要考虑了以下几个因素:
- 开发效率:SpringBoot的约定优于配置原则和丰富的starter可以快速搭建后端服务
- 性能需求:考虑到菜谱数据的读写比例,MySQL作为关系型数据库完全能满足需求
- 前后端协作:RESTful API接口规范确保前后端可以并行开发
- 用户体验:Vue.js的响应式特性和ElementUI丰富的组件能提供良好的交互体验
2.2 系统架构图
整个系统分为四层:
- 表现层:Vue.js前端应用
- API层:SpringBoot提供的RESTful接口
- 业务逻辑层:核心推荐算法和业务处理
- 数据持久层:MyBatis+MySQL数据存储
code复制前端(Vue.js) ↔ 后端(SpringBoot) ↔ 数据库(MySQL)
↑
(RESTful API)
3. 数据库设计
3.1 核心表结构
系统主要包含三张核心表:
用户表(user)
sql复制CREATE TABLE `user` (
`user_id` bigint NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`password` varchar(100) NOT NULL,
`email` varchar(100) NOT NULL,
`gender` varchar(10) DEFAULT NULL,
`age` int DEFAULT NULL,
`preference_tags` varchar(255) DEFAULT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`user_id`),
UNIQUE KEY `idx_username` (`username`),
UNIQUE KEY `idx_email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
菜谱表(recipe)
sql复制CREATE TABLE `recipe` (
`recipe_id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL,
`description` text,
`ingredients` text NOT NULL,
`steps` text NOT NULL,
`cook_time` int DEFAULT NULL,
`difficulty` varchar(20) DEFAULT NULL,
`category` varchar(50) DEFAULT NULL,
`creator_id` bigint NOT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`recipe_id`),
KEY `idx_category` (`category`),
KEY `idx_creator` (`creator_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
用户行为表(user_behavior)
sql复制CREATE TABLE `user_behavior` (
`behavior_id` bigint NOT NULL AUTO_INCREMENT,
`user_id` bigint NOT NULL,
`recipe_id` bigint NOT NULL,
`behavior_type` varchar(20) NOT NULL,
`rating` int DEFAULT NULL,
`is_favorite` tinyint(1) DEFAULT NULL,
`create_time` datetime NOT NULL,
PRIMARY KEY (`behavior_id`),
KEY `idx_user_recipe` (`user_id`,`recipe_id`),
KEY `idx_recipe` (`recipe_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 索引设计考量
在设计索引时,我特别注意了以下几点:
- 用户表的username和email字段都加了唯一索引,确保账户唯一性
- 菜谱表的category字段加了普通索引,方便按分类快速查询
- 用户行为表建立了(user_id, recipe_id)联合索引,优化用户行为查询效率
4. 后端实现
4.1 SpringBoot项目结构
code复制src/main/java
├── config/ # 配置类
├── controller/ # 控制器
├── dto/ # 数据传输对象
├── entity/ # 实体类
├── mapper/ # MyBatis映射接口
├── service/ # 业务服务
│ ├── impl/ # 服务实现
├── util/ # 工具类
└── Application.java # 启动类
4.2 核心接口实现
用户注册接口
java复制@RestController
@RequestMapping("/api/user")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/register")
public Result register(@Valid @RequestBody UserRegisterDTO dto) {
if(userService.existsUsername(dto.getUsername())) {
return Result.fail("用户名已存在");
}
if(userService.existsEmail(dto.getEmail())) {
return Result.fail("邮箱已注册");
}
userService.register(dto);
return Result.success();
}
}
菜谱推荐接口
java复制@RestController
@RequestMapping("/api/recipe")
public class RecipeController {
@Autowired
private RecipeService recipeService;
@GetMapping("/recommend")
public Result recommend(
@RequestParam(defaultValue = "10") int size,
@RequestParam(required = false) String category) {
Long userId = SecurityUtil.getCurrentUserId();
List<RecipeVO> recipes = recipeService.recommendRecipes(userId, size, category);
return Result.success(recipes);
}
}
4.3 推荐算法实现
系统采用了混合推荐策略:
- 基于内容的推荐:分析用户偏好标签和菜谱特征的相似度
- 协同过滤:找到相似用户喜欢的菜谱
- 热门推荐:当用户数据不足时,返回热门菜谱
java复制@Service
public class RecommendServiceImpl implements RecommendService {
@Override
public List<Long> recommendRecipes(Long userId, int size) {
// 1. 获取用户偏好标签
Set<String> tags = getUserPreferenceTags(userId);
// 2. 基于内容的推荐
List<Long> contentBased = contentBasedRecommend(tags, size/2);
// 3. 协同过滤推荐
List<Long> cfBased = cfRecommend(userId, size/2);
// 4. 合并结果并去重
List<Long> all = new ArrayList<>();
all.addAll(contentBased);
all.addAll(cfBased);
// 5. 如果结果不足,补充热门推荐
if(all.size() < size) {
all.addAll(hotRecommend(size - all.size()));
}
return all.stream().distinct().limit(size).collect(Collectors.toList());
}
}
5. 前端实现
5.1 Vue项目结构
code复制src/
├── api/ # 接口请求
├── assets/ # 静态资源
├── components/ # 公共组件
├── router/ # 路由配置
├── store/ # Vuex状态管理
├── utils/ # 工具函数
├── views/ # 页面组件
└── App.vue # 根组件
5.2 核心页面实现
菜谱推荐页
vue复制<template>
<div class="recipe-list">
<el-row :gutter="20">
<el-col
v-for="recipe in recipes"
:key="recipe.id"
:xs="24" :sm="12" :md="8" :lg="6">
<recipe-card :recipe="recipe" @click="viewDetail(recipe.id)"/>
</el-col>
</el-row>
</div>
</template>
<script>
import { getRecommendRecipes } from '@/api/recipe'
import RecipeCard from '@/components/RecipeCard'
export default {
components: { RecipeCard },
data() {
return {
recipes: []
}
},
async created() {
this.loadRecipes()
},
methods: {
async loadRecipes() {
try {
const { data } = await getRecommendRecipes(10)
this.recipes = data
} catch (error) {
this.$message.error('获取推荐菜谱失败')
}
},
viewDetail(id) {
this.$router.push(`/recipe/${id}`)
}
}
}
</script>
菜谱详情页交互
vue复制<template>
<div class="recipe-detail">
<el-button
:icon="isFavorite ? 'el-icon-star-on' : 'el-icon-star-off'"
@click="toggleFavorite">
收藏
</el-button>
<el-rate
v-model="rating"
@change="handleRateChange"/>
</div>
</template>
<script>
export default {
data() {
return {
isFavorite: false,
rating: 0
}
},
methods: {
async toggleFavorite() {
try {
if(this.isFavorite) {
await removeFavorite(this.recipeId)
} else {
await addFavorite(this.recipeId)
}
this.isFavorite = !this.isFavorite
} catch (error) {
this.$message.error('操作失败')
}
},
async handleRateChange(value) {
try {
await rateRecipe(this.recipeId, value)
this.$message.success('评分成功')
} catch (error) {
this.$message.error('评分失败')
}
}
}
}
</script>
6. 系统部署
6.1 后端部署
- 打包SpringBoot应用:
bash复制mvn clean package -DskipTests
- 运行jar包:
bash复制java -jar target/recipe-recommend-1.0.0.jar
- 建议使用Nginx反向代理:
nginx复制server {
listen 80;
server_name api.yourdomain.com;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
6.2 前端部署
- 构建生产环境代码:
bash复制npm run build
- Nginx配置:
nginx复制server {
listen 80;
server_name yourdomain.com;
root /path/to/dist;
index index.html;
location / {
try_files $uri $uri/ /index.html;
}
location /api {
proxy_pass http://api.yourdomain.com;
}
}
7. 开发经验分享
7.1 性能优化技巧
- 数据库查询优化:
- 使用MyBatis的二级缓存减少重复查询
- 复杂查询添加适当的索引
- 批量操作使用batch模式
- 接口响应优化:
- 使用SpringCache缓存热门菜谱数据
- 分页查询默认限制每页大小
- 使用DTO对象避免返回不必要的数据
- 前端性能优化:
- 图片懒加载
- 路由懒加载
- 使用keep-alive缓存页面状态
7.2 常见问题排查
- 跨域问题:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*");
}
}
- MyBatis查询结果映射问题:
- 确保实体类字段名与数据库列名一致
- 复杂查询使用@ResultMap或resultMap配置
- 使用@SelectProvider处理动态SQL
- Vue响应式数据更新问题:
- 数组更新使用Vue.set或数组变更方法
- 对象属性添加使用Vue.set
- 深层对象考虑使用深拷贝更新
8. 项目扩展方向
- 推荐算法优化:
- 引入深度学习模型提升推荐准确率
- 增加实时推荐能力
- 结合季节、天气等上下文信息
- 社交功能:
- 用户关注系统
- 菜谱评论互动
- 烹饪成果分享
- 移动端适配:
- 开发微信小程序版本
- 开发React Native跨平台App
- 增加PWA支持
这个项目从构思到实现大约花费了2个月时间,期间遇到了不少挑战,特别是推荐算法的调优部分。通过这个项目,我深刻体会到前后端分离架构的优势,也积累了不少全栈开发的经验。如果你对项目有任何疑问或建议,欢迎交流讨论。
