引言
在数字化时代,订餐系统已成为餐饮行业不可或缺的一部分。它不仅提升了用户体验,还极大地优化了餐厅的运营效率。本文将从需求分析入手,逐步深入到系统架构设计、数据库设计以及功能实现,提供一个完整的订餐系统项目设计全攻略。无论你是初学者还是有经验的开发者,都能从中获得实用的指导和灵感。
1. 需求分析
需求分析是项目成功的基石。在这一阶段,我们需要明确系统的目标用户、核心功能以及非功能性需求(如性能、安全性等)。
1.1 目标用户
订餐系统的主要用户包括:
- 顾客:浏览菜单、下单、支付、查看订单状态。
- 餐厅管理员:管理菜单、处理订单、查看销售数据。
- 配送员:接收配送任务、更新配送状态。
1.2 核心功能
基于用户角色,我们可以确定以下核心功能:
- 用户注册与登录:支持手机号、邮箱或第三方登录。
- 菜单管理:管理员可以添加、删除、修改菜品信息。
- 购物车与下单:顾客可以将菜品加入购物车,生成订单。
- 支付集成:支持多种支付方式,如支付宝、微信支付。
- 订单管理:管理员处理订单,顾客查看订单状态。
- 配送跟踪:配送员更新配送状态,顾客实时跟踪。
1.3 非功能性需求
- 性能:系统应能处理高并发请求,响应时间在2秒以内。
- 安全性:用户数据加密存储,支付过程安全可靠。
- 可用性:系统应保证99.9%的可用性,支持7x24小时运行。
2. 系统架构设计
系统架构设计决定了系统的可扩展性、可维护性和性能。我们将采用微服务架构,以应对复杂的业务需求和未来的扩展。
2.1 架构概述
系统分为前端、后端和数据库三大部分,后端采用微服务架构,主要包括以下服务:
- 用户服务:负责用户注册、登录、信息管理。
- 菜单服务:管理菜品信息。
- 订单服务:处理订单的创建、支付、状态更新。
- 配送服务:管理配送任务和状态更新。
- 网关服务:统一入口,负责路由、认证、限流等。
2.2 技术栈选择
- 前端:Vue.js 或 React,用于构建用户界面。
- 后端:Spring Boot(Java)或 Django(Python),用于构建微服务。
- 数据库:MySQL(关系型数据库)和 Redis(缓存)。
- 消息队列:RabbitMQ 或 Kafka,用于服务间异步通信。
- 部署:Docker 容器化部署,Kubernetes 进行容器编排。
2.3 架构图
用户 -> 网关服务 -> [用户服务, 菜单服务, 订单服务, �配送服务] -> 数据库/缓存/消息队列
3. 数据库设计
数据库设计是系统设计的核心,直接影响系统的性能和数据一致性。我们将采用关系型数据库 MySQL 来存储核心数据。
3.1 主要表结构设计
3.1.1 用户表 (users)
存储用户的基本信息。
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
phone VARCHAR(20) NOT NULL UNIQUE,
role ENUM('customer', 'admin', 'rider') NOT NULL DEFAULT 'customer',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
3.1.2 菜品表 (dishes)
存储菜品信息。
CREATE TABLE dishes (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL,
image_url VARCHAR(255),
category_id BIGINT,
is_available BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
3.1.3 订单表 (orders)
存储订单信息。
`orders` (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
status ENUM('pending', 'confirmed', 'preparing', 'ready', 'delivering', 'completed', 'cancelled') NOT NULL DEFAULT 'pending',
payment_status ENUM('unpaid', 'paid') NOT NULL DEFAULT 'unpaid',
address TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
3.1.4 订单详情表 (order_items)
存储订单中每个菜品的详细信息。
CREATE TABLE order_items (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT NOT NULL,
dish_id BIGINT NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id),
键 (dish_id) REFERENCES dishes(id)
);
3.1.5 配送表 (deliveries)
存储配送信息。
CREATE TABLE deliveries (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT NOT NULL,
rider_id BIGINT,
status ENUM('pending', 'assigned', 'picked_up', 'delivered') NOT NULL DEFAULT 'pending',
estimated_time INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (rider_id) REFERENCES users(id)
);
3.2 索引优化
为了提高查询效率,我们可以在常用查询字段上创建索引:
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
4. 功能实现
在这一部分,我们将详细说明如何实现核心功能,并提供代码示例。我们假设使用 Spring Boot 作为后端框架。
4.1 用户注册与登录
4.1.1 实体类 (User.java)
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String passwordHash;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false, unique = true)
private String phone;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role = Role.CUSTOMER;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Getters and Setters
public enum Role {
CUSTOMER, ADMIN, RIDER
}
}
4.1.2 Repository (UserRepository.java)
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
Optional<User> findByPhone(String phone);
}
4.1.3 Service 层 (UserService.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public User registerUser(String username, String email, String phone, String password) {
// 检查邮箱或手机号是否已注册
if (userRepository.findByEmail(email).isPresent() || userRepository.findByPhone(phone).isPresent()) {
throw new RuntimeException("Email or Phone already registered");
}
User user = new User();
user.setUsername(username);
user.setEmail(email);
user.setPhone(phone);
user.setPasswordHash(passwordEncoder.encode(password));
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
return userRepository.save(user);
}
}
4.1.4 Controller 层 (AuthController.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private UserService userService;
@PostMapping("/register")
public ResponseEntity<String> register(@RequestBody RegistrationRequest request) {
userService.registerUser(request.getUsername(), request.getEmail(), request.getPhone(), request.getPassword());
return ResponseEntity.ok("User registered successfully");
}
}
class RegistrationRequest {
private String username;
private String email;
private String phone;
private String password;
// Getters and Setters
}
4.2 菜单管理
4.2.1 实体类 (Dish.java)
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "dishes")
public class Dish {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
private String description;
@Column(nullable = false)
private BigDecimal price;
private String imageUrl;
private Long categoryId;
private Boolean isAvailable = true;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Getters and Setters
}
4.2.2 Service 层 (DishService.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class DishService {
@Autowired
private DishRepository dishRepository;
public Dish addDish(String name, String description, BigDecimal price, String imageUrl, Long categoryId) {
Dish dish = new Dish();
dish.setName(name);
dish.setDescription(description);
dish.setPrice(price);
dish.setImageUrl(imageUrl);
dish.setCategoryId(categoryId);
dish.setCreatedAt(LocalDateTime.now());
dish.setUpdatedAt(LocalDateTime.now());
return dishRepository.save(dish);
}
public List<Dish> getAllDishes() {
return dishRepository.findAll();
}
}
4.2.3 Controller 层 (DishController.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/dishes")
public class DishController {
@Autowired
private DishService dishService;
@PostMapping
public ResponseEntity<Dish> addDish(@RequestBody DishRequest request) {
Dish dish = dishService.addDish(request.getName(), request.getDescription(), request.getPrice(), request.getImageUrl(), request.getCategoryId());
return ResponseEntity.ok(dish);
}
@GetMapping
public ResponseEntity<List<Dish>> getAllDishes() {
List<Dish> dishes = dishService.getAllDishes();
return ResponseEntity.ok(dishes);
}
}
class DishRequest {
private String name;
private String description;
private BigDecimal price;
private String imageUrl;
private Long categoryId;
// Getters and Setters
}
4.3 订单处理
4.3.1 实体类 (Order.java)
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Column(nullable = false)
private BigDecimal totalAmount;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private OrderStatus status = OrderStatus.PENDING;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PaymentStatus paymentStatus = PaymentStatus.UNPAID;
@Column(nullable = false)
private String address;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems;
// Getters and Setters
public enum OrderStatus {
PENDING, CONFIRMED, PREPARING, READY, DELIVERING, COMPLETED, CANCELLED
}
public enum PaymentStatus {
UNPAID, PAID
}
}
4.3.2 Service 层 (OrderService.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private DishRepository dishRepository;
@Autowired
所有的 OrderItemRepository orderItemRepository;
@Transactional
public Order createOrder(Long userId, List<OrderItemRequest> items, String address) {
BigDecimal totalAmount = BigDecimal.ZERO;
List<OrderItem> orderItems = new ArrayList<>();
// 计算总价并准备订单项
for (OrderItemRequest itemRequest : items) {
Dish dish = dishRepository.findById(itemRequest.getDishId())
.orElseThrow(() -> new RuntimeException("Dish not found"));
if (!dish.getIsAvailable()) {
throw new RuntimeException("Dish is not available: " + dish.getName());
}
BigDecimal itemTotal = dish.getPrice().multiply(BigDecimal.valueOf(itemRequest.getQuantity()));
totalAmount = totalAmount.add(itemTotal);
OrderItem orderItem = new OrderItem();
orderItem.setDish(dish);
orderItem.setQuantity(itemRequest.getQuantity());
orderItem.setPrice(dish.getPrice());
orderItems.add(orderItem);
}
// 创建订单
Order order = new Order();
order.setUser(userRepository.findById(userId).orElseThrow(() -> new RuntimeException("User not found")));
order.setTotalAmount(totalAmount);
order.setAddress(address);
order.setCreatedAt(LocalDateTime.now());
order.setUpdatedAt(LocalDateTime.now());
order.setOrderItems(orderItems);
// 保存订单和订单项
order = orderRepository.save(order);
for (OrderItem item : orderItems) {
item.setOrder(order);
orderItemRepository.save(item);
}
// TODO: 发送消息到消息队列,通知其他服务(如配送服务)
// messageQueueService.sendMessage("order.created", order.getId());
return order;
}
}
4.3.3 Controller 层 (OrderController.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
Order order = orderService.createOrder(request.getUserId(), request.getItems(), request.getAddress());
return ResponseEntity.ok(order);
}
}
class OrderRequest {
private Long userId;
private List<OrderItemRequest> items;
private String address;
// Getters and Setters
}
class OrderItemRequest {
private Long dishId;
private Integer quantity;
// Getters and Setters
}
4.4 支付集成
支付集成通常涉及第三方支付平台,如支付宝或微信支付。以下是一个简化的支付流程示例:
- 生成支付订单:调用支付平台的API生成支付订单,获取支付链接或二维码。
- 用户支付:用户通过支付链接或二维码完成支付。
- 支付回调:支付平台通过回调接口通知支付结果。
- 更新订单状态:根据支付结果更新订单的支付状态。
4.4.1 支付服务接口 (PaymentService.java)
public interface PaymentService {
String createPayment(Long orderId, BigDecimal amount);
void handlePaymentCallback(String paymentId, boolean success);
}
4.4.2 支付服务实现 (AlipayServiceImpl.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class AlipayServiceImpl implements PaymentService {
@Autowired
private OrderRepository orderRepository;
@Override
public String createPayment(Long orderId, BigDecimal amount) {
// 调用支付宝SDK生成支付订单
// AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
// request.setOutTradeNo(orderId.toString());
// request.setTotalAmount(amount.toString());
// ...
// return alipayClient.pageExecute(request).getBody();
return "https://example.com/payment/" + orderId; // 模拟支付链接
}
@Override
public void handlePaymentCallback(String paymentId, boolean success) {
Long orderId = Long.parseLong(paymentId);
Order order = orderRepository.findById(orderId).orElseThrow(() -> new RuntimeException("Order not found"));
if (success) {
order.setPaymentStatus(Order.PaymentStatus.PAID);
order.setStatus(Order.OrderStatus.CONFIRMED); // 支付成功后,订单状态变为已确认
orderRepository.save(order);
// TODO: 发送消息通知订单已支付,触发后续流程(如通知厨房)
} else {
// 处理支付失败逻辑
order.setPaymentStatus(Order.PaymentStatus.UNPAID);
orderRepository.save(order);
}
}
}
4.4.3 支付回调 Controller (PaymentController.java)
import org.springframework.beans.factory.annotation.Autowired;
import.org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/payment")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@PostMapping("/callback")
public ResponseEntity<String> handleCallback(@RequestParam String paymentId, @RequestParam boolean success) {
paymentService.handlePaymentCallback(paymentId, success);
return ResponseEntity.ok("Callback handled");
}
}
4.5 配送管理
配送管理涉及分配订单给配送员并跟踪配送状态。我们可以通过消息队列来异步处理配送任务。
4.5.1 消息消费者 (DeliveryMessageConsumer.java)
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DeliveryMessageConsumer {
@Autowired
private DeliveryRepository deliveryRepository;
@Autowired
private UserRepository userRepository;
@RabbitListener(queues = "order.created")
public void handleOrderCreated(Long orderId) {
// 自动分配配送员(简单策略:随机分配)
List<User> riders = userRepository.findByRole(User.Role.RIDER);
if (riders.isEmpty()) {
// 没有可用配送员,稍后重试或记录日志
return;
}
User rider = riders.get(0); // 简单策略:取第一个
Delivery delivery = new Delivery();
delivery.setOrderId(orderId);
delivery.setRiderId(rider.getId());
delivery.setStatus(Delivery.Status.PENDING);
delivery.setEstimatedTime(30); // 估计30分钟送达
deliveryRepository.save(delivery);
// TODO: 通知配送员有新任务(如通过WebSocket推送)
}
}
4.5.2 配送状态更新
配送员可以通过API更新配送状态。
@RestController
@RequestMapping("/api/deliveries")
public class DeliveryController {
@Autowired
private DeliveryService deliveryService;
@PutMapping("/{deliveryId}/status")
public ResponseEntity<String> updateStatus(@PathVariable Long deliveryId, @RequestParam Delivery.Status status) {
deliveryService.updateStatus(deliveryId, status);
return ResponseEntity.ok("Status updated");
}
}
5. 总结
本文详细介绍了订餐系统从需求分析到功能实现的完整流程。我们从明确用户需求开始,设计了基于微服务的系统架构,创建了详细的数据库表结构,并通过代码示例展示了核心功能的实现,包括用户注册、菜单管理、订单处理、支付集成和配送管理。
这个项目设计不仅涵盖了基础功能,还考虑了性能、安全性和可扩展性。通过使用微服务架构、消息队列和缓存等技术,系统能够应对高并发场景,并易于未来扩展新功能。希望这篇全攻略能为你的订餐系统项目提供有力的指导和帮助。# 订餐系统项目设计全攻略 从需求分析到系统架构再到数据库设计与功能实现
引言
在数字化时代,订餐系统已成为餐饮行业不可或缺的一部分。它不仅提升了用户体验,还极大地优化了餐厅的运营效率。本文将从需求分析入手,逐步深入到系统架构设计、数据库设计以及功能实现,提供一个完整的订餐系统项目设计全攻略。无论你是初学者还是有经验的开发者,都能从中获得实用的指导和灵感。
1. 需求分析
需求分析是项目成功的基石。在这一阶段,我们需要明确系统的目标用户、核心功能以及非功能性需求(如性能、安全性等)。
1.1 目标用户
订餐系统的主要用户包括:
- 顾客:浏览菜单、下单、支付、查看订单状态。
- 餐厅管理员:管理菜单、处理订单、查看销售数据。
- 配送员:接收配送任务、更新配送状态。
1.2 核心功能
基于用户角色,我们可以确定以下核心功能:
- 用户注册与登录:支持手机号、邮箱或第三方登录。
- 菜单管理:管理员可以添加、删除、修改菜品信息。
- 购物车与下单:顾客可以将菜品加入购物车,生成订单。
- 支付集成:支持多种支付方式,如支付宝、微信支付。
- 订单管理:管理员处理订单,顾客查看订单状态。
- 配送跟踪:配送员更新配送状态,顾客实时跟踪。
1.3 非功能性需求
- 性能:系统应能处理高并发请求,响应时间在2秒以内。
- 安全性:用户数据加密存储,支付过程安全可靠。
- 可用性:系统应保证99.9%的可用性,支持7x24小时运行。
2. 系统架构设计
系统架构设计决定了系统的可扩展性、可维护性和性能。我们将采用微服务架构,以应对复杂的业务需求和未来的扩展。
2.1 架构概述
系统分为前端、后端和数据库三大部分,后端采用微服务架构,主要包括以下服务:
- 用户服务:负责用户注册、登录、信息管理。
- 菜单服务:管理菜品信息。
- 订单服务:处理订单的创建、支付、状态更新。
- 配送服务:管理配送任务和状态更新。
- 网关服务:统一入口,负责路由、认证、限流等。
2.2 技术栈选择
- 前端:Vue.js 或 React,用于构建用户界面。
- 后端:Spring Boot(Java)或 Django(Python),用于构建微服务。
- 数据库:MySQL(关系型数据库)和 Redis(缓存)。
- 消息队列:RabbitMQ 或 Kafka,用于服务间异步通信。
- 部署:Docker 容器化部署,Kubernetes 进行容器编排。
2.3 架构图
用户 -> 网关服务 -> [用户服务, 菜单服务, 订单服务, 配送服务] -> 数据库/缓存/消息队列
3. 数据库设计
数据库设计是系统设计的核心,直接影响系统的性能和数据一致性。我们将采用关系型数据库 MySQL 来存储核心数据。
3.1 主要表结构设计
3.1.1 用户表 (users)
存储用户的基本信息。
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password_hash VARCHAR(255) NOT NULL,
email VARCHAR(100) NOT NULL UNIQUE,
phone VARCHAR(20) NOT NULL UNIQUE,
role ENUM('customer', 'admin', 'rider') NOT NULL DEFAULT 'customer',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
3.1.2 菜品表 (dishes)
存储菜品信息。
CREATE TABLE dishes (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(100) NOT NULL,
description TEXT,
price DECIMAL(10, 2) NOT NULL,
image_url VARCHAR(255),
category_id BIGINT,
is_available BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
3.1.3 订单表 (orders)
存储订单信息。
CREATE TABLE orders (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL,
total_amount DECIMAL(10, 2) NOT NULL,
status ENUM('pending', 'confirmed', 'preparing', 'ready', 'delivering', 'completed', 'cancelled') NOT NULL DEFAULT 'pending',
payment_status ENUM('unpaid', 'paid') NOT NULL DEFAULT 'unpaid',
address TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
3.1.4 订单详情表 (order_items)
存储订单中每个菜品的详细信息。
CREATE TABLE order_items (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT NOT NULL,
dish_id BIGINT NOT NULL,
quantity INT NOT NULL,
price DECIMAL(10, 2) NOT NULL,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (dish_id) REFERENCES dishes(id)
);
3.1.5 配送表 (deliveries)
存储配送信息。
CREATE TABLE deliveries (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT NOT NULL,
rider_id BIGINT,
status ENUM('pending', 'assigned', 'picked_up', 'delivered') NOT NULL DEFAULT 'pending',
estimated_time INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (order_id) REFERENCES orders(id),
FOREIGN KEY (rider_id) REFERENCES users(id)
);
3.2 索引优化
为了提高查询效率,我们可以在常用查询字段上创建索引:
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
4. 功能实现
在这一部分,我们将详细说明如何实现核心功能,并提供代码示例。我们假设使用 Spring Boot 作为后端框架。
4.1 用户注册与登录
4.1.1 实体类 (User.java)
import javax.persistence.*;
import java.time.LocalDateTime;
@Entity
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String passwordHash;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false, unique = true)
private String phone;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private Role role = Role.CUSTOMER;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Getters and Setters
public enum Role {
CUSTOMER, ADMIN, RIDER
}
}
4.1.2 Repository (UserRepository.java)
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByEmail(String email);
Optional<User> findByPhone(String phone);
}
4.1.3 Service 层 (UserService.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public User registerUser(String username, String email, String phone, String password) {
// 检查邮箱或手机号是否已注册
if (userRepository.findByEmail(email).isPresent() || userRepository.findByPhone(phone).isPresent()) {
throw new RuntimeException("Email or Phone already registered");
}
User user = new User();
user.setUsername(username);
user.setEmail(email);
user.setPhone(phone);
user.setPasswordHash(passwordEncoder.encode(password));
user.setCreatedAt(LocalDateTime.now());
user.setUpdatedAt(LocalDateTime.now());
return userRepository.save(user);
}
}
4.1.4 Controller 层 (AuthController.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private UserService userService;
@PostMapping("/register")
public ResponseEntity<String> register(@RequestBody RegistrationRequest request) {
userService.registerUser(request.getUsername(), request.getEmail(), request.getPhone(), request.getPassword());
return ResponseEntity.ok("User registered successfully");
}
}
class RegistrationRequest {
private String username;
private String email;
private String phone;
private String password;
// Getters and Setters
}
4.2 菜单管理
4.2.1 实体类 (Dish.java)
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Entity
@Table(name = "dishes")
public class Dish {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
private String name;
private String description;
@Column(nullable = false)
private BigDecimal price;
private String imageUrl;
private Long categoryId;
private Boolean isAvailable = true;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
// Getters and Setters
}
4.2.2 Service 层 (DishService.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Service
public class DishService {
@Autowired
private DishRepository dishRepository;
public Dish addDish(String name, String description, BigDecimal price, String imageUrl, Long categoryId) {
Dish dish = new Dish();
dish.setName(name);
dish.setDescription(description);
dish.setPrice(price);
dish.setImageUrl(imageUrl);
dish.setCategoryId(categoryId);
dish.setCreatedAt(LocalDateTime.now());
dish.setUpdatedAt(LocalDateTime.now());
return dishRepository.save(dish);
}
public List<Dish> getAllDishes() {
return dishRepository.findAll();
}
}
4.2.3 Controller 层 (DishController.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/dishes")
public class DishController {
@Autowired
private DishService dishService;
@PostMapping
public ResponseEntity<Dish> addDish(@RequestBody DishRequest request) {
Dish dish = dishService.addDish(request.getName(), request.getDescription(), request.getPrice(), request.getImageUrl(), request.getCategoryId());
return ResponseEntity.ok(dish);
}
@GetMapping
public ResponseEntity<List<Dish>> getAllDishes() {
List<Dish> dishes = dishService.getAllDishes();
return ResponseEntity.ok(dishes);
}
}
class DishRequest {
private String name;
private String description;
private BigDecimal price;
private String imageUrl;
private Long categoryId;
// Getters and Setters
}
4.3 订单处理
4.3.1 实体类 (Order.java)
import javax.persistence.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@JoinColumn(name = "user_id", nullable = false)
private User user;
@Column(nullable = false)
private BigDecimal totalAmount;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private OrderStatus status = OrderStatus.PENDING;
@Enumerated(EnumType.STRING)
@Column(nullable = false)
private PaymentStatus paymentStatus = PaymentStatus.UNPAID;
@Column(nullable = false)
private String address;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems;
// Getters and Setters
public enum OrderStatus {
PENDING, CONFIRMED, PREPARING, READY, DELIVERING, COMPLETED, CANCELLED
}
public enum PaymentStatus {
UNPAID, PAID
}
}
4.3.2 Service 层 (OrderService.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private DishRepository dishRepository;
@Autowired
private OrderItemRepository orderItemRepository;
@Transactional
public Order createOrder(Long userId, List<OrderItemRequest> items, String address) {
BigDecimal totalAmount = BigDecimal.ZERO;
List<OrderItem> orderItems = new ArrayList<>();
// 计算总价并准备订单项
for (OrderItemRequest itemRequest : items) {
Dish dish = dishRepository.findById(itemRequest.getDishId())
.orElseThrow(() -> new RuntimeException("Dish not found"));
if (!dish.getIsAvailable()) {
throw new RuntimeException("Dish is not available: " + dish.getName());
}
BigDecimal itemTotal = dish.getPrice().multiply(BigDecimal.valueOf(itemRequest.getQuantity()));
totalAmount = totalAmount.add(itemTotal);
OrderItem orderItem = new OrderItem();
orderItem.setDish(dish);
orderItem.setQuantity(itemRequest.getQuantity());
orderItem.setPrice(dish.getPrice());
orderItems.add(orderItem);
}
// 创建订单
Order order = new Order();
order.setUser(userRepository.findById(userId).orElseThrow(() -> new RuntimeException("User not found")));
order.setTotalAmount(totalAmount);
order.setAddress(address);
order.setCreatedAt(LocalDateTime.now());
order.setUpdatedAt(LocalDateTime.now());
order.setOrderItems(orderItems);
// 保存订单和订单项
order = orderRepository.save(order);
for (OrderItem item : orderItems) {
item.setOrder(order);
orderItemRepository.save(item);
}
// TODO: 发送消息到消息队列,通知其他服务(如配送服务)
// messageQueueService.sendMessage("order.created", order.getId());
return order;
}
}
4.3.3 Controller 层 (OrderController.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/orders")
public class OrderController {
@Autowired
private OrderService orderService;
@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
Order order = orderService.createOrder(request.getUserId(), request.getItems(), request.getAddress());
return ResponseEntity.ok(order);
}
}
class OrderRequest {
private Long userId;
private List<OrderItemRequest> items;
private String address;
// Getters and Setters
}
class OrderItemRequest {
private Long dishId;
private Integer quantity;
// Getters and Setters
}
4.4 支付集成
支付集成通常涉及第三方支付平台,如支付宝或微信支付。以下是一个简化的支付流程示例:
- 生成支付订单:调用支付平台的API生成支付订单,获取支付链接或二维码。
- 用户支付:用户通过支付链接或二维码完成支付。
- 支付回调:支付平台通过回调接口通知支付结果。
- 更新订单状态:根据支付结果更新订单的支付状态。
4.4.1 支付服务接口 (PaymentService.java)
public interface PaymentService {
String createPayment(Long orderId, BigDecimal amount);
void handlePaymentCallback(String paymentId, boolean success);
}
4.4.2 支付服务实现 (AlipayServiceImpl.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class AlipayServiceImpl implements PaymentService {
@Autowired
private OrderRepository orderRepository;
@Override
public String createPayment(Long orderId, BigDecimal amount) {
// 调用支付宝SDK生成支付订单
// AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
// request.setOutTradeNo(orderId.toString());
// request.setTotalAmount(amount.toString());
// ...
// return alipayClient.pageExecute(request).getBody();
return "https://example.com/payment/" + orderId; // 模拟支付链接
}
@Override
public void handlePaymentCallback(String paymentId, boolean success) {
Long orderId = Long.parseLong(paymentId);
Order order = orderRepository.findById(orderId).orElseThrow(() -> new RuntimeException("Order not found"));
if (success) {
order.setPaymentStatus(Order.PaymentStatus.PAID);
order.setStatus(Order.OrderStatus.CONFIRMED); // 支付成功后,订单状态变为已确认
orderRepository.save(order);
// TODO: 发送消息通知订单已支付,触发后续流程(如通知厨房)
} else {
// 处理支付失败逻辑
order.setPaymentStatus(Order.PaymentStatus.UNPAID);
orderRepository.save(order);
}
}
}
4.4.3 支付回调 Controller (PaymentController.java)
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/payment")
public class PaymentController {
@Autowired
private PaymentService paymentService;
@PostMapping("/callback")
public ResponseEntity<String> handleCallback(@RequestParam String paymentId, @RequestParam boolean success) {
paymentService.handlePaymentCallback(paymentId, success);
return ResponseEntity.ok("Callback handled");
}
}
4.5 配送管理
配送管理涉及分配订单给配送员并跟踪配送状态。我们可以通过消息队列来异步处理配送任务。
4.5.1 消息消费者 (DeliveryMessageConsumer.java)
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DeliveryMessageConsumer {
@Autowired
private DeliveryRepository deliveryRepository;
@Autowired
private UserRepository userRepository;
@RabbitListener(queues = "order.created")
public void handleOrderCreated(Long orderId) {
// 自动分配配送员(简单策略:随机分配)
List<User> riders = userRepository.findByRole(User.Role.RIDER);
if (riders.isEmpty()) {
// 没有可用配送员,稍后重试或记录日志
return;
}
User rider = riders.get(0); // 简单策略:取第一个
Delivery delivery = new Delivery();
delivery.setOrderId(orderId);
delivery.setRiderId(rider.getId());
delivery.setStatus(Delivery.Status.PENDING);
delivery.setEstimatedTime(30); // 估计30分钟送达
deliveryRepository.save(delivery);
// TODO: 通知配送员有新任务(如通过WebSocket推送)
}
}
4.5.2 配送状态更新
配送员可以通过API更新配送状态。
@RestController
@RequestMapping("/api/deliveries")
public class DeliveryController {
@Autowired
private DeliveryService deliveryService;
@PutMapping("/{deliveryId}/status")
public ResponseEntity<String> updateStatus(@PathVariable Long deliveryId, @RequestParam Delivery.Status status) {
deliveryService.updateStatus(deliveryId, status);
return ResponseEntity.ok("Status updated");
}
}
5. 总结
本文详细介绍了订餐系统从需求分析到功能实现的完整流程。我们从明确用户需求开始,设计了基于微服务的系统架构,创建了详细的数据库表结构,并通过代码示例展示了核心功能的实现,包括用户注册、菜单管理、订单处理、支付集成和配送管理。
这个项目设计不仅涵盖了基础功能,还考虑了性能、安全性和可扩展性。通过使用微服务架构、消息队列和缓存等技术,系统能够应对高并发场景,并易于未来扩展新功能。希望这篇全攻略能为你的订餐系统项目提供有力的指导和帮助。
