1. 项目概述
这个美食网站系统采用前后端分离架构,前端使用Vue.js框架,后端基于SpringBoot+MyBatis技术栈,数据库选用MySQL。系统实现了完整的餐饮业务闭环,包括菜品展示、在线订餐、订单管理、用户评价等核心功能模块。
作为一名有多年全栈开发经验的工程师,我认为这种技术组合在当前Web开发领域非常具有代表性。SpringBoot提供了快速构建RESTful API的能力,Vue.js则以其响应式特性和组件化开发见长,两者结合既能保证开发效率,又能提供良好的用户体验。
2. 技术架构解析
2.1 前端架构设计
前端采用Vue 2.x版本构建,主要基于以下技术栈:
- Vue CLI:项目脚手架
- Vue Router:页面路由管理
- Vuex:状态管理
- Element UI:UI组件库
- Axios:HTTP请求库
在实际开发中,我建议采用以下目录结构:
code复制src/
├── api/ # 接口请求封装
├── assets/ # 静态资源
├── components/ # 公共组件
├── router/ # 路由配置
├── store/ # Vuex状态管理
├── utils/ # 工具函数
└── views/ # 页面组件
2.2 后端架构设计
后端采用SpringBoot 2.x版本,主要技术组件包括:
- Spring Security:认证授权
- MyBatis-Plus:ORM框架
- Redis:缓存
- Swagger:API文档
- Lombok:简化代码
推荐的项目分层结构:
code复制com.example.food/
├── config/ # 配置类
├── controller/ # 控制器层
├── service/ # 业务逻辑层
├── dao/ # 数据访问层
├── entity/ # 实体类
├── dto/ # 数据传输对象
├── vo/ # 视图对象
└── util/ # 工具类
3. 数据库设计与实现
3.1 核心表结构
用户表(user)
sql复制CREATE TABLE `user` (
`user_id` bigint NOT NULL AUTO_INCREMENT,
`user_nickname` varchar(50) NOT NULL,
`user_phone` varchar(20) NOT NULL,
`user_password` varchar(64) NOT NULL,
`user_avatar` varchar(255) DEFAULT NULL,
`user_status` tinyint DEFAULT '1',
`register_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`user_id`),
UNIQUE KEY `idx_phone` (`user_phone`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
菜品表(dish)
sql复制CREATE TABLE `dish` (
`dish_id` bigint NOT NULL AUTO_INCREMENT,
`dish_name` varchar(100) NOT NULL,
`dish_price` decimal(10,2) NOT NULL,
`dish_category` varchar(50) NOT NULL,
`dish_image` varchar(255) DEFAULT NULL,
`dish_desc` text,
`dish_status` tinyint DEFAULT '1',
`create_time` datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`dish_id`),
KEY `idx_category` (`dish_category`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
3.2 索引优化建议
- 用户表的手机号字段应添加唯一索引,避免重复注册
- 菜品表的分类字段应添加普通索引,提高分类查询效率
- 订单表的用户ID字段应添加索引,加速用户订单查询
4. 核心功能实现
4.1 用户认证模块
采用JWT(JSON Web Token)实现无状态认证,流程如下:
- 用户登录成功后,服务端生成JWT token返回给前端
- 前端将token存储在localStorage中
- 后续请求在Authorization头中携带token
- 服务端通过拦截器验证token有效性
关键代码示例:
java复制// JWT工具类
public class JwtUtil {
private static final String SECRET = "your-secret-key";
private static final long EXPIRATION = 86400L; // 24小时
public static String generateToken(UserDetails userDetails) {
return Jwts.builder()
.setSubject(userDetails.getUsername())
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + EXPIRATION * 1000))
.signWith(SignatureAlgorithm.HS512, SECRET)
.compact();
}
}
4.2 菜品展示模块
前端采用分页加载策略,关键实现要点:
- 使用Element UI的Pagination组件实现分页
- 通过axios发送分页查询请求
- 后端使用MyBatis-Plus的分页插件处理分页逻辑
后端分页实现示例:
java复制@GetMapping("/dishes")
public Result<Page<DishVO>> getDishList(
@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer size,
@RequestParam(required = false) String category) {
Page<Dish> pageInfo = new Page<>(page, size);
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
if (StringUtils.isNotBlank(category)) {
queryWrapper.eq(Dish::getDishCategory, category);
}
queryWrapper.eq(Dish::getDishStatus, 1);
Page<Dish> dishPage = dishService.page(pageInfo, queryWrapper);
return Result.success(dishPage.convert(this::convertToVO));
}
5. 部署方案
5.1 开发环境部署
- 前端开发环境:
bash复制# 安装依赖
npm install
# 启动开发服务器
npm run serve
- 后端开发环境:
- 安装JDK 1.8+
- 配置Maven环境
- 导入IDE(推荐IntelliJ IDEA)
- 启动Application主类
5.2 生产环境部署
推荐使用Docker容器化部署:
- 前端Dockerfile示例:
dockerfile复制FROM nginx:alpine
COPY dist/ /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]
- 后端Dockerfile示例:
dockerfile复制FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]
EXPOSE 8080
6. 性能优化实践
6.1 缓存策略
- 菜品数据缓存:
java复制@Cacheable(value = "dish", key = "#id")
public Dish getDishById(Long id) {
return dishMapper.selectById(id);
}
@CacheEvict(value = "dish", key = "#dish.dishId")
public void updateDish(Dish dish) {
dishMapper.updateById(dish);
}
- 热门菜品预加载:
java复制@Scheduled(cron = "0 0 10 * * ?") // 每天10点执行
public void preloadHotDishes() {
List<Dish> hotDishes = dishMapper.selectHotDishes();
hotDishes.forEach(dish ->
redisTemplate.opsForValue().set("dish:hot:"+dish.getDishId(), dish)
);
}
6.2 数据库优化
- 读写分离配置:
yaml复制spring:
datasource:
master:
url: jdbc:mysql://master:3306/food
username: root
password: 123456
slave:
url: jdbc:mysql://slave:3306/food
username: root
password: 123456
- SQL优化建议:
- 避免SELECT *,只查询需要的字段
- 复杂查询使用EXPLAIN分析执行计划
- 合理使用索引,避免索引失效
7. 常见问题解决方案
7.1 跨域问题
解决方案:
java复制@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.maxAge(3600);
}
}
7.2 文件上传问题
前端实现:
vue复制<template>
<el-upload
action="/api/upload"
:on-success="handleSuccess"
:before-upload="beforeUpload">
<el-button type="primary">点击上传</el-button>
</el-upload>
</template>
<script>
export default {
methods: {
beforeUpload(file) {
const isImage = file.type.includes('image/');
if (!isImage) {
this.$message.error('只能上传图片文件');
}
return isImage;
}
}
}
</script>
后端实现:
java复制@PostMapping("/upload")
public Result<String> uploadFile(@RequestParam("file") MultipartFile file) {
if (file.isEmpty()) {
return Result.error("上传文件不能为空");
}
String fileName = UUID.randomUUID() + "." +
FilenameUtils.getExtension(file.getOriginalFilename());
Path path = Paths.get(uploadPath, fileName);
try {
Files.copy(file.getInputStream(), path, StandardCopyOption.REPLACE_EXISTING);
return Result.success(fileName);
} catch (IOException e) {
return Result.error("文件上传失败");
}
}
8. 项目扩展方向
- 微信小程序端开发
- 推荐算法集成(基于用户行为的菜品推荐)
- 会员积分系统
- 配送跟踪功能
- 多商家入驻模式
在实际开发中,我建议采用渐进式扩展策略,先完善核心功能,再逐步添加增值功能。对于高并发场景,可以考虑引入消息队列(如RabbitMQ)进行异步处理,使用Elasticsearch实现菜品搜索功能。