1. 项目概述
作为一个长期混迹在美食圈和IT圈的"双料"开发者,我一直想打造一个能真正满足美食爱好者需求的技术平台。去年疫情期间,在家钻研厨艺时萌生了这个想法——为什么不把SpringBoot的技术优势和美食分享的乐趣结合起来呢?于是就有了这个美食网站项目。
这个平台的核心定位是"用技术连接美食爱好者"。不同于简单的菜谱网站,我们更注重社区互动和个性化体验。平台上线三个月就积累了2万+注册用户,日均UV达到5000+,验证了这种技术+美食模式的可行性。
2. 技术架构设计
2.1 后端技术选型
选择SpringBoot作为基础框架是经过深思熟虑的:
- 快速启动:内嵌Tomcat,省去传统Java Web项目的繁琐配置
- 约定优于配置:默认集成了Jackson、Spring Data等常用组件
- 生态丰富:Spring Security、Spring Cache等模块开箱即用
数据库方面采用混合存储方案:
- MySQL:存储用户、菜谱等结构化数据
- MongoDB:存储用户评论、攻略等非结构化内容
- Redis:缓存热门数据和会话信息
提示:Redis的缓存策略特别重要,我们采用两级缓存:本地Caffeine+分布式Redis,命中率能达到92%以上
2.2 前端技术栈
Vue.js+ElementUI的组合让前端开发效率提升明显:
- 组件化开发:将菜谱卡片、评论框等封装成可复用组件
- 响应式设计:使用flex布局适配不同设备
- 状态管理:Vuex管理用户登录状态等全局数据
地图功能集成高德API时遇到个坑:需要特别注意坐标系转换。我们最终采用GCJ-02标准,并在后端做了坐标统一处理。
3. 核心功能实现
3.1 菜谱模块
数据库设计是关键,我们的recipes表包含:
sql复制CREATE TABLE `recipes` (
`id` bigint NOT NULL AUTO_INCREMENT,
`title` varchar(100) NOT NULL,
`cover_url` varchar(255) NOT NULL,
`user_id` bigint NOT NULL,
`category_id` int NOT NULL COMMENT '菜系分类',
`difficulty` tinyint NOT NULL COMMENT '1-初级 2-中级 3-高级',
`cook_time` int NOT NULL COMMENT '分钟',
`ingredients` json NOT NULL COMMENT '食材JSON数组',
`steps` json NOT NULL COMMENT '步骤JSON数组',
`view_count` int DEFAULT '0',
`like_count` int DEFAULT '0',
`status` tinyint DEFAULT '1' COMMENT '0-下架 1-正常',
`create_time` datetime NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_category` (`category_id`),
KEY `idx_user` (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
上传图片时我们做了这些优化:
- 使用阿里云OSS存储,通过CDN加速访问
- 前端限制图片大小不超过5MB
- 后端用Thumbnailator生成缩略图
- 对用户上传的图片进行内容安全审核
3.2 推荐算法实现
基于用户行为的协同过滤算法核心代码:
java复制public List<Recipe> recommendRecipes(Long userId) {
// 1. 获取用户最近浏览记录
List<Long> viewedIds = redisTemplate.opsForList().range(
"user:view:" + userId, 0, 9);
// 2. 查找相似用户
Set<Long> similarUsers = findSimilarUsers(viewedIds);
// 3. 获取相似用户喜欢的菜谱
List<Long> candidateIds = recipeMapper.selectTopLikedRecipes(similarUsers);
// 4. 过滤已看过的
candidateIds.removeAll(viewedIds);
// 5. 按热度排序返回
return recipeMapper.selectByIds(candidateIds)
.stream()
.sorted(Comparator.comparingInt(Recipe::getLikeCount).reversed())
.limit(10)
.collect(Collectors.toList());
}
4. 性能优化实践
4.1 缓存策略
采用多级缓存架构:
- 本地缓存:使用Caffeine缓存热点数据,有效期5分钟
- Redis缓存:缓存完整页面数据,有效期1小时
- 数据库:最终数据源
缓存更新策略:
- 菜谱更新时:先更新DB,再删除缓存
- 使用@Cacheable注解简化缓存逻辑
- 对缓存穿透做了空值缓存处理
4.2 数据库优化
慢查询优化案例:
sql复制-- 优化前(执行时间1.2s)
EXPLAIN SELECT * FROM recipes
WHERE category_id = 3
ORDER BY create_time DESC
LIMIT 20;
-- 优化后(执行时间0.03s)
EXPLAIN SELECT * FROM recipes
WHERE category_id = 3
ORDER BY id DESC -- 改用自增ID排序
LIMIT 20;
还做了这些优化:
- 为常用查询字段添加组合索引
- 大文本字段拆分到单独表
- 定期执行ANALYZE TABLE更新统计信息
5. 部署与监控
5.1 容器化部署
Docker Compose编排文件关键部分:
yaml复制version: '3'
services:
app:
image: foodie-app:1.0
ports:
- "8080:8080"
depends_on:
- redis
- mysql
environment:
- SPRING_PROFILES_ACTIVE=prod
mysql:
image: mysql:5.7
volumes:
- ./mysql-data:/var/lib/mysql
environment:
- MYSQL_ROOT_PASSWORD=123456
- MYSQL_DATABASE=foodie
redis:
image: redis:6
ports:
- "6379:6379"
5.2 监控方案
采用Prometheus+Grafana监控体系:
- JVM监控:Micrometer暴露/metrics端点
- 业务指标:自定义计数器统计关键操作
- 日志收集:ELK栈分析错误日志
告警规则示例:
code复制- alert: HighErrorRate
expr: rate(http_server_requests_errors_total[1m]) > 0.1
for: 5m
labels:
severity: critical
annotations:
summary: "High error rate on {{ $labels.instance }}"
6. 踩坑经验分享
6.1 文件上传坑
遇到的坑:用户上传超大视频导致服务崩溃
解决方案:
- 配置Spring Boot的multipart.max-file-size
- 前端做文件大小校验
- 使用Nginx限制上传大小
- 实现断点续传功能
6.2 缓存雪崩问题
某天晚上8点缓存集中过期,导致DB压力激增
最终方案:
- 设置缓存过期时间添加随机值
- 使用Hystrix做熔断降级
- 提前预热热点数据
6.3 事务问题
发现用户点赞数和实际不一致,原因是:
java复制// 错误写法
@Transactional
public void likeRecipe(Long recipeId) {
// 查询当前点赞数
Integer likes = recipeMapper.selectLikes(recipeId);
// 更新点赞数
recipeMapper.updateLikes(recipeId, likes + 1);
}
正确做法应该是:
java复制@Transactional
public void likeRecipe(Long recipeId) {
// 直接原子性更新
recipeMapper.incrementLikes(recipeId);
}
7. 扩展与演进
7.1 近期规划
- 引入机器学习优化推荐算法
- 增加直播功能支持美食教学
- 开发微信小程序版本
- 实现菜谱智能识别功能
7.2 架构演进
当前架构的不足:
- 单体应用,扩展性受限
- 部分服务耦合度高
改造方向:
- 按业务拆分微服务
- 引入消息队列解耦
- 实现灰度发布能力
在实际开发过程中,最大的体会是:技术方案要服务于业务需求。比如我们最初想上Elasticsearch做全文搜索,但实际分析用户行为后发现,90%的搜索都是通过分类筛选完成的,最终简化了搜索实现方案。