1. 项目背景与核心价值
作为一名经历过毕业设计洗礼的Java开发者,我深知选题和技术栈选择的重要性。网上订餐系统之所以成为热门选题,是因为它完美覆盖了企业级应用开发的典型场景:用户管理、商品展示、订单处理、支付对接等核心模块,同时又能根据学校要求灵活调整复杂度。
SpringBoot+Vue的组合在2023年毕业季依然是最稳妥的选择。SpringBoot 2.7.x版本提供了完善的RESTful API支持,内嵌Tomcat让部署变得极其简单;而Vue 3.2的组合式API写法,让前端开发更加符合工程化规范。这个技术栈的优势在于:
- 学习曲线平缓,社区资源丰富
- 前后端完全解耦,符合现代开发趋势
- 能展示完整的全栈开发能力
我在指导学弟学妹时发现,90%的优秀毕业设计都具备以下特征:
- 有明确的需求边界(不要试图做一个"美团级"系统)
- 包含1-2个技术亮点(如WebSocket实时通知)
- 文档规范,代码有清晰分层
- 能演示核心业务流程
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统架构设计
2.1 技术栈选型解析
后端技术矩阵:
- 核心框架:SpringBoot 2.7.12(不推荐直接用3.x,避免兼容性问题)
- 持久层:MyBatis-Plus 3.5.3(比JPA更适合国内开发习惯)
- 安全框架:Spring Security + JWT
- 缓存:Redis 6.x(购物车、秒杀等场景)
- 消息队列:RabbitMQ(订单超时处理)
- 文件存储:本地存储(毕业设计够用,生产环境建议OSS)
前端技术方案:
- 框架:Vue 3.2 + Composition API
- UI库:Element Plus(餐饮类后台首选)
- 状态管理:Pinia(比Vuex更轻量)
- 可视化:ECharts(用于销售数据展示)
- 地图:高德地图JS API(配送范围展示)
2.2 分层架构示意图
code复制┌───────────────────────────────────────┐
│ 客户端层 │
│ ┌───────────┐ ┌─────────────┐ │
│ │ 用户端 │ │ 管理后台 │ │
│ │ (H5/小程序)│ │(PC端Vue3) │ │
│ └───────────┘ └─────────────┘ │
└───────────────┬──────────────┬────────┘ ┌─────────────────┐
│ HTTP/HTTPS │ │ MySQL │
└──────┬───────┘ │ ┌────────────┐ │
┌──────────────────────▼────────────────────────┐ │ │ 订单表 │ │
│ API网关层 │ │ └────────────┘ │
│ ┌───────────────────────────────────────┐ │ │ ┌────────────┐ │
│ │ SpringBoot │ │◄───────┤ │ 菜品表 │ │
│ │ ┌───────┐ ┌───────┐ ┌──────────┐ │ │ │ └────────────┘ │
│ │ │ 控制层 │ │服务层 │ │ 数据访问 │ │ │ └─────────────────┘
│ │ └───────┘ └───────┘ └──────────┘ │ │
│ └───────────────────────────────────────┘ │
└──────────────────────┬────────────────────────┘
│
┌───────────▼─────────────┐
│ 中间件层 │
│ ┌─────┐ ┌──────────┐ │
│ │Redis│ │RabbitMQ │ │
│ └─────┘ └──────────┘ │
└──────────────────────────┘
2.3 数据库设计要点
餐饮系统的ER图核心包含6大实体:
- 用户体系:user(用户)、address(收货地址)
- 商品体系:dish(菜品)、category(分类)、setmeal(套餐)
- 订单体系:orders(主单)、order_detail(明细)
关键设计技巧:
- 采用逻辑删除(is_deleted字段)
- 金额字段用DECIMAL(10,2)
- 订单表做水平分表准备(按用户ID哈希)
- 建立created_time和updated_time的联合索引
3. 核心功能实现
3.1 菜品展示模块
前端实现要点:
vue复制// 使用组合式API实现分类联动
const { categoryList, dishList } = useDishStore()
onMounted(async () => {
await getCategoryList()
// 默认加载第一个分类的菜品
if (categoryList.value.length > 0) {
currentCategoryId.value = categoryList.value[0].id
getDishListByCategory()
}
})
后端接口设计:
java复制@GetMapping("/dish/list")
public R<List<DishVO>> list(Long categoryId) {
// 1. 查询启售状态的菜品
LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Dish::getCategoryId, categoryId)
.eq(Dish::getStatus, 1)
.orderByAsc(Dish::getSort);
// 2. 对象转换(避免直接暴露实体类)
List<DishVO> dishVOs = dishService.list(queryWrapper)
.stream().map(dish -> {
DishVO vo = new DishVO();
BeanUtils.copyProperties(dish, vo);
// 处理图片路径(Nginx反向代理后的地址)
vo.setImage(aliyunOssUtil.getAccessUrl(dish.getImage()));
return vo;
}).collect(Collectors.toList());
return R.success(dishVOs);
}
3.2 购物车设计
采用Redis Hash结构存储:
- Key:cart_:userId
- Field:dishId/setmealId
- Value:JSON字符串(包含数量、口味等)
并发控制方案:
java复制public void addCart(CartDTO cartDTO) {
String key = "cart_" + cartDTO.getUserId();
String itemKey = cartDTO.getDishId() != null ?
"dish_" + cartDTO.getDishId() :
"setmeal_" + cartDTO.getSetmealId();
// 使用Redis事务保证原子性
redisTemplate.execute(new SessionCallback<>() {
@Override
public Object execute(RedisOperations operations) {
operations.watch(key);
// 判断是否已存在
Object existing = operations.opsForHash().get(key, itemKey);
CartItem currentItem = existing != null ?
JSON.parseObject(existing.toString(), CartItem.class) :
new CartItem();
// 更新数量
currentItem.setNumber(currentItem.getNumber() + cartDTO.getNumber());
operations.multi();
operations.opsForHash().put(key, itemKey, JSON.toJSONString(currentItem));
return operations.exec();
}
});
}
3.3 订单状态机设计
使用枚举定义状态流转:
java复制public enum OrderStatus {
UNPAID(1, "待支付") {
@Override
public boolean canChangeTo(OrderStatus status) {
return status == PAID || status == CANCELLED;
}
},
PAID(2, "已支付") {
@Override
public boolean canChangeTo(OrderStatus status) {
return status == DELIVERING || status == REFUNDED;
}
},
// 其他状态...
private final int code;
private final String desc;
public abstract boolean canChangeTo(OrderStatus status);
// 状态检查工具方法
public static void checkStatusTransition(OrderStatus from, OrderStatus to) {
if (!from.canChangeTo(to)) {
throw new BusinessException("订单状态转换非法");
}
}
}
4. 毕业设计加分项实现
4.1 销售数据可视化
使用ECharts实现近30天订单趋势图:
vue复制<template>
<div ref="chartEl" style="width: 600px; height: 400px;"></div>
</template>
<script setup>
import * as echarts from 'echarts'
import { onMounted, ref } from 'vue'
const chartEl = ref(null)
const initChart = async () => {
const res = await getOrderStatistics()
const chart = echarts.init(chartEl.value)
chart.setOption({
tooltip: { trigger: 'axis' },
xAxis: {
type: 'category',
data: res.data.dateList
},
yAxis: { type: 'value' },
series: [{
data: res.data.orderCountList,
type: 'line',
smooth: true,
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(58, 77, 233, 0.8)' },
{ offset: 1, color: 'rgba(58, 77, 233, 0.1)' }
])
}
}]
})
}
onMounted(() => initChart())
</script>
4.2 定时任务设计
使用Spring Task实现自动取消超时订单:
java复制@Slf4j
@Component
public class OrderTask {
@Autowired
private OrderMapper orderMapper;
// 每天凌晨1点执行
@Scheduled(cron = "0 0 1 * * ?")
public void processTimeoutOrder() {
log.info("开始处理超时未支付订单...");
LocalDateTime time = LocalDateTime.now().minusMinutes(30);
LambdaQueryWrapper<Order> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(Order::getStatus, OrderStatus.UNPAID.getCode())
.le(Order::getOrderTime, time);
List<Order> orders = orderMapper.selectList(queryWrapper);
if (CollectionUtils.isEmpty(orders)) {
return;
}
orders.forEach(order -> {
order.setStatus(OrderStatus.CANCELLED.getCode());
order.setCancelTime(LocalDateTime.now());
order.setCancelReason("超时未支付,系统自动取消");
orderMapper.updateById(order);
// 这里可以添加库存释放逻辑
log.info("已取消订单:{}", order.getId());
});
}
}
5. 论文写作与答辩技巧
5.1 论文结构建议
-
摘要部分(300字左右):
- 明确说明系统采用了SpringBoot+Vue技术栈
- 突出1-2个创新点(如"基于Redis的购物车优化设计")
- 用数据说话(如"系统响应时间<500ms")
-
系统设计章节:
- 画出完整的架构图(建议使用Draw.io)
- 数据库ER图要标注主要关系
- 重点描述2-3个核心功能的实现逻辑
-
测试部分:
- 至少包含:功能测试用例表、性能测试结果
- 使用JMeter做并发测试(模拟50用户下单)
5.2 答辩常见问题准备
技术类问题:
- SpringBoot自动配置原理(可结合starter机制讲解)
- Vue组件通信方式(本题使用了Pinia)
- 如何解决跨域问题(CORS配置示例)
业务类问题:
- 订单超时如何处理(演示定时任务代码)
- 如何防止重复下单(前端防抖+后端幂等设计)
- 高并发场景的优化思路(Redis缓存、MQ削峰)
6. 项目部署与源码说明
6.1 前后端联调配置
前端代理配置(vite.config.js):
js复制server: {
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}
后端跨域配置(SpringBoot):
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.maxAge(3600);
}
}
6.2 生产环境部署要点
- 后端打包:
bash复制mvn clean package -DskipTests
# 生成的target/xxx.jar可直接运行
java -jar your-project.jar --spring.profiles.active=prod
- 前端部署:
bash复制npm run build
# 生成的dist目录放到Nginx配置中
location / {
root /path/to/dist;
index index.html;
try_files $uri $uri/ /index.html;
}
- 数据库配置建议:
- 使用Flyway管理数据库变更
- 生产环境配置连接池(HikariCP)
- 开启慢查询日志
7. 避坑指南与优化建议
7.1 开发阶段常见问题
- 时间格式问题:
- 前端axios需配置transformRequest
- 后端使用@JsonFormat格式化LocalDateTime
- 文件上传漏洞:
- 校验文件类型(不要仅靠后缀名)
- 限制上传文件大小(spring.servlet.multipart.max-file-size)
- 循环依赖问题:
- 避免在实体类中使用Jackson的@JsonBackReference
- 改用@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class)
7.2 性能优化方案
- 缓存策略优化:
java复制@Cacheable(value = "dish", key = "#categoryId", unless = "#result == null || #result.size() == 0")
public List<DishVO> listByCategory(Long categoryId) {
// 查询逻辑
}
- SQL优化示例:
java复制// 错误写法(N+1问题)
List<Order> orders = orderMapper.selectList(queryWrapper);
orders.forEach(order -> {
User user = userMapper.selectById(order.getUserId());
order.setUserName(user.getName());
});
// 正确写法(关联查询)
@Select("SELECT o.*, u.name as user_name FROM orders o LEFT JOIN user u ON o.user_id = u.id")
List<OrderVO> selectOrderWithUser();
- 前端性能优化:
- 使用v-lazy实现图片懒加载
- 路由级代码分割(component: () => import('./views/Menu.vue'))
- 第三方库按需引入(如Element Plus的自动导入)
