这是大三上学期的软件综合课程设计。项目面向餐饮企业,完成管理后台和移动端点餐流程,并在基础业务之上实践 Redis 缓存、MySQL 读写分离和 Nginx 部署。

返回项目实践 · 课程资料(提取码:3ezu

1、项目概览

项目 内容
学习阶段 大三上学期
课程名称 软件综合课程设计
项目名称 基于 Spring Boot 和 H5 的瑞吉外卖系统
项目形式 小组课程设计
现有材料 课程报告、答辩课件、可执行 JAR、演示视频

系统包含两类使用端:

  • 管理后台:员工、分类、菜品、套餐和订单管理。
  • 移动端:手机号登录、菜品浏览、规格选择、购物车、地址管理、下单和历史订单。

报告明确记录:课程版本没有接入真实支付模块,因此下单流程不能等同于完整商业外卖系统。

2、技术栈

层次 技术
后端 Spring Boot 2.4.5、Spring MVC、MyBatis-Plus
数据 MySQL、Druid、Redis、Spring Cache
前端 HTML、CSS、JavaScript、Vue、Element UI、Axios、H5
工程 Maven、Lombok、Fastjson、Knife4j
部署与优化 Linux、Nginx、Sharding-JDBC

课程资料里保留了报告、课件、演示视频和可执行 JAR。报告中有较完整的关键接口与缓存改造代码,JAR 可以用于确认控制器、实体、服务和配置等模块结构。

3、核心功能

3.1 管理后台

  • 员工登录、退出、状态管理和分页查询。
  • 菜品分类、套餐分类、菜品及口味管理。
  • 套餐组合管理和图片上传。
  • 订单查询与基础状态管理。
  • 登录过滤、统一返回结构和全局异常处理。

3.2 移动端

  • 手机验证码登录。
  • 菜品和套餐浏览。
  • 购物车增删与数量调整。
  • 收货地址维护。
  • 提交订单和查看历史订单。

4、数据设计

课程报告整理了 11 张主要数据表:

usershopping_cartsetmeal_dishsetmealorder_detailordersdish_flavordishcategoryaddress_bookemployee

项目没有使用复杂外键关系,主要由业务层维护数据之间的关联。这让课程开发更直接,但也意味着一致性校验和删除更新逻辑需要在代码中承担。

5、性能与部署实践

5.1 Redis 与 Spring Cache

  • 将短信验证码从 Session 改为 Redis 存储,并设置有效期。
  • 缓存菜品和套餐等高频读取数据。
  • 数据更新时主动清理相关缓存,避免数据库和缓存不一致。
  • 使用 Spring Cache 注解简化套餐缓存逻辑。

5.2 MySQL 读写分离

课程材料配置了 MySQL 主从复制,并通过 Sharding-JDBC 将写操作路由到主库、读操作路由到从库。这部分主要用于理解读写分离链路,不代表系统已经完成生产级高可用设计。

5.3 Nginx

使用 Nginx 部署静态资源、反向代理后端服务,并学习负载均衡的基本配置思路。

6、关键代码记录

6.1 手机验证码缓存

移动端登录从 Session 改为 Redis 缓存验证码,核心点是:发送验证码时设置 5 分钟有效期,登录成功后删除缓存,避免验证码重复使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
@PostMapping("/sendMsg")
public R<String> sendMsg(@RequestBody User user, HttpSession session) {
String phone = user.getPhone();

if (StringUtils.isNotEmpty(phone)) {
String code = ValidateCodeUtils.generateValidateCode(4).toString();
redisTemplate.opsForValue().set(phone, code, 5, TimeUnit.MINUTES);
return R.success("手机验证码短信发送成功");
}

return R.error("短信发送失败");
}

@PostMapping("/login")
public R<User> login(@RequestBody Map map, HttpSession session) {
String phone = map.get("phone").toString();
String code = map.get("code").toString();
Object codeInRedis = redisTemplate.opsForValue().get(phone);

if (codeInRedis != null && codeInRedis.equals(code)) {
LambdaQueryWrapper<User> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(User::getPhone, phone);
User user = userService.getOne(queryWrapper);

if (user == null) {
user = new User();
user.setPhone(phone);
user.setStatus(1);
userService.save(user);
}

session.setAttribute("user", user.getId());
redisTemplate.delete(phone);
return R.success(user);
}

return R.error("登录失败");
}

6.2 菜品列表缓存

菜品列表属于高频读取接口。课程代码根据分类和状态拼接 Redis key,命中缓存则直接返回;未命中时查询数据库、组装口味数据,再写回 Redis。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@GetMapping("/list")
public R<List<DishDto>> list(Dish dish) {
String key = "dish_" + dish.getCategoryId() + "_" + dish.getStatus();
List<DishDto> dishDtoList =
(List<DishDto>) redisTemplate.opsForValue().get(key);

if (dishDtoList != null) {
return R.success(dishDtoList);
}

LambdaQueryWrapper<Dish> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(dish.getCategoryId() != null,
Dish::getCategoryId,
dish.getCategoryId());
queryWrapper.eq(Dish::getStatus, 1);
queryWrapper.orderByAsc(Dish::getSort)
.orderByDesc(Dish::getUpdateTime);

List<Dish> list = dishService.list(queryWrapper);
dishDtoList = list.stream().map(item -> {
DishDto dishDto = new DishDto();
BeanUtils.copyProperties(item, dishDto);

LambdaQueryWrapper<DishFlavor> flavorWrapper =
new LambdaQueryWrapper<>();
flavorWrapper.eq(DishFlavor::getDishId, item.getId());
dishDto.setFlavors(dishFlavorService.list(flavorWrapper));
return dishDto;
}).collect(Collectors.toList());

redisTemplate.opsForValue().set(key, dishDtoList, 60, TimeUnit.MINUTES);
return R.success(dishDtoList);
}

7、项目边界

  1. 项目为课程学习与小组实践,系统整体成果不等同于个人独立开发。
  2. 当前资料以报告、课件、可执行 JAR 和演示材料为主,完整源码仓库没有单独归档。
  3. 没有真实支付、完整安全审计、生产监控和压力测试闭环。
  4. Redis、主从复制和 Nginx 更接近技术实践,不应直接描述成生产环境能力。

8、复盘

这个项目让我第一次把登录、权限、业务接口、数据库、缓存和部署串成一条完整链路。现在回看,我会更关注版本化源码、接口测试、权限模型、事务边界、支付模拟和容器化部署,并把“能运行”与“能稳定维护”分成两个阶段验收。