基于 Java SpringBoot 的校园餐厅在线点餐管理系统
系统概述
本系统采用前后端不分离架构,后端使用 SpringBoot + MyBatis,前端结合 Thymeleaf 模板引擎与 Vue.js 处理动态交互。系统分为用户和管理员两个角色。
用户模块:
- 登录注册、餐品搜索、加入购物车、订单支付、餐品评价、公告查看、留言交流。
管理员模块:
- 用户管理、评价管理、餐品管理、公告管理、留言管理、订单管理、销售报表统计。
业务流程: 注册 -> 登录 -> 点餐 -> 购物车 -> 结算 -> 配送 -> 收货 -> 评价 -> 完成
数据库设计 (MySQL)
核心表结构如下:
CREATE DATABASE IF NOT EXISTS campus_canteen DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE campus_canteen;
-- 1. 用户表
CREATE TABLE user (
id bigint(20) NOT NULL AUTO_INCREMENT,
username varchar(50) NOT NULL COMMENT '账号',
password varchar(100) NOT NULL COMMENT '加密密码',
role tinyint(4) NOT NULL DEFAULT 1 COMMENT '1:普通用户 2:管理员',
nickname varchar(50) DEFAULT NULL,
phone varchar(20) DEFAULT NULL,
avatar varchar(255) DEFAULT NULL,
create_time datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id),
UNIQUE KEY uk_username (username)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- 2. 餐品分类表
CREATE TABLE category (
id bigint(20) NOT NULL AUTO_INCREMENT,
name varchar(50) NOT NULL,
sort int(11) DEFAULT 0,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 3. 餐品表
CREATE TABLE dish (
id bigint(20) NOT NULL AUTO_INCREMENT,
category_id bigint(20) DEFAULT NULL,
name varchar(100) NOT NULL,
price decimal(10,2) NOT NULL,
image varchar(255) DEFAULT NULL,
description text,
status tinyint(4) DEFAULT 1 COMMENT '1:在售 0:下架',
sales int(11) DEFAULT 0 COMMENT '销量',
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 4. 订单表
CREATE TABLE orders (
id bigint(20) NOT NULL AUTO_INCREMENT,
order_no varchar(64) NOT NULL COMMENT '订单号',
user_id bigint(20) NOT NULL,
total_amount decimal(10,2) NOT NULL,
status tinyint(4) NOT NULL DEFAULT 0 COMMENT '0:待支付 1:待配送 2:配送中 3:已完成 4:已取消',
address varchar(255) DEFAULT NULL,
remark varchar(255) DEFAULT NULL,
create_time datetime DEFAULT CURRENT_TIMESTAMP,
pay_time datetime DEFAULT NULL,
finish_time datetime DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE KEY uk_order_no (order_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 5. 订单详情表
CREATE TABLE order_item (
id bigint(20) NOT NULL AUTO_INCREMENT,
order_id bigint(20) NOT NULL,
dish_id bigint(20) NOT NULL,
dish_name varchar(100) NOT NULL,
price decimal(10,2) NOT NULL,
quantity int(11) NOT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 6. 评价表
CREATE TABLE comment (
id bigint(20) NOT NULL AUTO_INCREMENT,
order_id bigint(20) NOT NULL,
user_id bigint(20) NOT NULL,
dish_id bigint(20) NOT NULL,
content text,
rating tinyint(4) NOT NULL COMMENT '1-5 星',
create_time datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 7. 公告表
CREATE TABLE notice (
id bigint(20) NOT NULL AUTO_INCREMENT,
title varchar(100) NOT NULL,
content text,
publish_time datetime DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
后端核心代码 (SpringBoot + MyBatis)
依赖配置
确保引入 mybatis-spring-boot-starter, lombok, thymeleaf, mysql-connector-java。
实体类 (Entity)
以 Order 为例:
@Data
@TableName("orders")
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
private String orderNo;
private Long userId;
private BigDecimal totalAmount;
private Integer status;
private String address;
private Date createTime;
@TableField(exist = false)
private List<OrderItem> items;
@TableField(exist = false)
private User user;
}
Mapper 接口
@Mapper
public interface OrderMapper extends BaseMapper {
// 自定义复杂查询,例如查看销售报表
@Select("SELECT DATE(create_time) as date, SUM(total_amount) as total FROM orders WHERE status >= 1 GROUP BY DATE(create_time)")
List<Map<String, Object>> selectSalesReport();
// 关联查询订单详情
@Select("SELECT * FROM orders WHERE id = #{id}")
@Results(id = "orderMap", value = {
@Result(property = "items", column = "id", javaType = List.class, many = @Many(select = "com.canteen.mapper.OrderItemMapper.selectByOrderId"))
})
Order selectDetailById(Long id);
}
Service 层 - 下单逻辑
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private OrderItemMapper orderItemMapper;
@Autowired
private DishMapper dishMapper;
@Transactional(rollbackFor = Exception.class)
public String createOrder(Long userId, List<CartItem> cartItems, String address) {
// 1. 生成订单号
String orderNo = "ORD" + System.currentTimeMillis() + new Random().nextInt(1000);
// 2. 计算总价并构建订单对象
BigDecimal total = BigDecimal.ZERO;
Order order = new Order();
order.setOrderNo(orderNo);
order.setUserId(userId);
order.setAddress(address);
order.setStatus(0); // 待支付
for (CartItem item : cartItems) {
Dish dish = dishMapper.selectById(item.getDishId());
if (dish == null || dish.getStatus() == 0) {
throw new RuntimeException("餐品 " + item.getDishName() + " 已售罄或下架");
}
total = total.add(dish.getPrice().multiply(new (item.getQuantity())));
}
order.setTotalAmount(total);
orderMapper.insert(order);
(CartItem item : cartItems) {
();
orderItem.setOrderId(order.getId());
orderItem.setDishId(item.getDishId());
orderItem.setDishName(item.getDishName());
orderItem.setPrice(item.getPrice());
orderItem.setQuantity(item.getQuantity());
orderItemMapper.insert(orderItem);
dishMapper.selectById(item.getDishId());
dish.setSales(dish.getSales() + item.getQuantity());
dishMapper.updateById(dish);
}
orderNo;
}
{
orderMapper.selectOne( ().eq(, orderNo));
(order != && order.getStatus() == ) {
order.setStatus();
order.setPayTime( ());
orderMapper.updateById(order);
}
}
}
Controller 层
@Controller
@RequestMapping("/order")
public class OrderController {
@Autowired
private OrderService orderService;
// 跳转到确认订单页
@GetMapping("/confirm")
public String confirmPage(Model model, HttpSession session) {
User user = (User) session.getAttribute("currentUser");
if (user == null) return "redirect:/login";
List<CartItem> cart = (List<CartItem>) session.getAttribute("cart");
model.addAttribute("cart", cart);
return "order_confirm";
}
// 提交订单
@PostMapping("/submit")
@ResponseBody
public Result submit(@RequestBody OrderSubmitDTO dto, HttpSession session) {
try {
User user = (User) session.getAttribute("currentUser");
String orderNo = orderService.createOrder(user.getId(), dto.getItems(), dto.getAddress());
return Result.success(orderNo);
} catch (Exception e) {
return Result.error(e.getMessage());
}
}
// 管理员查看报表数据
@GetMapping("/admin/sales-data")
@ResponseBody
List<Map<String, Object>> {
orderService.getSalesReport();
}
}
前端核心实现 (HTML + Vue.js + Thymeleaf)
由于是前后端不分离,使用 Thymeleaf 作为模板引擎,在页面内部引入 Vue.js 处理动态交互。
菜单点餐页 (menu.html)
<div id="app">
<div class="dish-card" v-for="dish in filteredDishes" :key="dish.id">
<h3>{{ dish.name }}</h3>
<p>价格:¥{{ dish.price }}</p>
<p>销量:{{ dish.sales }}</p>
<button @click="addToCart(dish)">加入购物车</button>
</div>
<div class="cart-fixed">
<p>共 {{ totalCount }} 件,总计:¥{{ totalPrice }}</p>
<button @click="goToSettle">去结算</button>
</div>
</div>
<script>
var initialDishes = /*[[${dishes}]]*/ [];
new Vue({
el: '#app',
data: {
dishes: initialDishes,
: ,
: []
},
: {
() {
(!.) .;
..( d..(.));
},
() {
..( sum + item., );
},
() {
..( sum + (item. * item.), ).();
}
},
: {
() {
item = ..( c. === dish.);
(item) {
item.++;
} {
..({...dish, : });
}
},
() {
.. = ;
}
}
});
管理员报表页 (admin_report.html) - 集成 Echarts
<div id="mainChart"></div>
<script>
var salesData = /*[[${salesData}]]*/ [];
var myChart = echarts.init(document.getElementById('mainChart'));
var dates = salesData.map(item => item.date);
var totals = salesData.map(item => item.total);
var option = {
title: { text: '每日销售额趋势' },
tooltip: {},
xAxis: { data: dates },
yAxis: {},
series: [{
name: '销售额',
type: 'bar',
data: totals,
itemStyle: { color: '#5470C6' }
}]
};
myChart.setOption(option);
</script>
菜品详情与评价实现
后端代码
// Dish.java
@Data
@TableName("dish")
public class Dish {
@TableId(type = IdType.AUTO)
private Long id;
private Long categoryId;
private String name;
private BigDecimal price;
private String image;
private String description;
private Integer status;
private Integer sales;
@TableField(exist = false)
private String categoryName;
@TableField(exist = false)
private List<Comment> comments;
}
// Comment.java
@Data
@TableName("comment")
public class Comment {
@TableId(type = IdType.AUTO)
private Long id;
private Long orderId;
private Long userId;
private Long dishId;
private String content;
private Integer rating;
private Date createTime;
@TableField(exist = false)
private String username;
}
Controller 层
@Controller
@RequestMapping("/dish")
public class DishController {
@Autowired
private DishService dishService;
@GetMapping("/detail")
public String detailPage(@RequestParam Long id, Model model) {
Dish dish = dishService.getDishDetail(id);
if (dish == null) return "redirect:/";
model.addAttribute("dish", dish);
return "dish_detail";
}
}
前端页面 (dish_detail.html)
<div id="app">
<div class="dish-info">
<div class="dish-image"><img :src="dish.image" /></div>
<div class="dish-details">
<h2 class="dish-title">{{ dish.name }}</h2>
<p class="dish-price">¥ {{ dish.price }}</p>
<p class="dish-desc">{{ dish.description }}</p>
<div class="buy-section">
<div class="quantity-control">
<button @click="decreaseQuantity">-</button>
<input type="text" v-model.number="quantity" readonly />
< @=>+
加入餐车
菜品评价 ({{ dish.comments.length }})
{{ comment.username || '匿名用户' }}
{{ comment.content }}
{{ formatDate(comment.createTime) }}
购物车功能实现
后端服务 (CartService)
@Service
public class CartService {
public List<CartItem> getCart(HttpSession session) {
List<CartItem> cart = (List<CartItem>) session.getAttribute("cart");
if (cart == null) {
cart = new ArrayList<>();
session.setAttribute("cart", cart);
}
return cart;
}
public void addToCart(HttpSession session, CartItem newItem) {
List<CartItem> cart = getCart(session);
for (CartItem item : cart) {
if (item.getDishId().equals(newItem.getDishId())) {
item.setQuantity(item.getQuantity() + newItem.getQuantity());
return;
}
}
cart.add(newItem);
}
public void removeFromCart(HttpSession session, Long dishId) {
List<CartItem> cart = getCart(session);
cart.removeIf(item -> item.getDishId().equals(dishId));
}
public BigDecimal calculateTotal(List<CartItem> cart) {
return cart.stream()
.map(CartItem::getSubtotal)
.reduce(BigDecimal.ZERO, BigDecimal::add);
}
}
前端页面 (cart.html)
<div id="app">
<table>
<thead>
<tr><th>菜品</th><th>单价</th><th>数量</th><th>小计</th><th>操作</th></tr>
</thead>
<tbody>
<tr v-for="item in cart" :key="item.dishId">
<td>{{ item.dishName }}</td>
<td>¥ {{ item.price }}</td>
<td>
<button @click="decreaseQuantity(item)">-</button>
{{ item.quantity }}
<button @click="increaseQuantity(item)">+</button>
¥ {{ item.subtotal.toFixed(2) }}
删除
应付金额:¥ {{ totalAmount.toFixed(2) }}
提交订单
运行指南
- 初始化项目:使用 Spring Initializr 创建包含 Web, MyBatis, MySQL, Lombok, Thymeleaf 的项目。
- 建库建表:执行第一部分提供的 SQL 语句。
- 配置连接:在 application.yml 中配置数据库账号密码。
- 复制代码:将 Entity, Mapper, Service, Controller 代码填入对应包结构。
- 编写页面:在 src/main/resources/templates 下创建 HTML 文件。
- 启动项目:运行 SpringBoot 应用即可访问。


