引言

在数字化时代,订餐系统已成为餐饮行业不可或缺的一部分。它不仅提升了用户体验,还极大地优化了餐厅的运营效率。本文将从需求分析入手,逐步深入到系统架构设计、数据库设计以及功能实现,提供一个完整的订餐系统项目设计全攻略。无论你是初学者还是有经验的开发者,都能从中获得实用的指导和灵感。

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 支付集成

支付集成通常涉及第三方支付平台,如支付宝或微信支付。以下是一个简化的支付流程示例:

  1. 生成支付订单:调用支付平台的API生成支付订单,获取支付链接或二维码。
  2. 用户支付:用户通过支付链接或二维码完成支付。
  3. 支付回调:支付平台通过回调接口通知支付结果。
  4. 更新订单状态:根据支付结果更新订单的支付状态。

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 支付集成

支付集成通常涉及第三方支付平台,如支付宝或微信支付。以下是一个简化的支付流程示例:

  1. 生成支付订单:调用支付平台的API生成支付订单,获取支付链接或二维码。
  2. 用户支付:用户通过支付链接或二维码完成支付。
  3. 支付回调:支付平台通过回调接口通知支付结果。
  4. 更新订单状态:根据支付结果更新订单的支付状态。

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. 总结

本文详细介绍了订餐系统从需求分析到功能实现的完整流程。我们从明确用户需求开始,设计了基于微服务的系统架构,创建了详细的数据库表结构,并通过代码示例展示了核心功能的实现,包括用户注册、菜单管理、订单处理、支付集成和配送管理。

这个项目设计不仅涵盖了基础功能,还考虑了性能、安全性和可扩展性。通过使用微服务架构、消息队列和缓存等技术,系统能够应对高并发场景,并易于未来扩展新功能。希望这篇全攻略能为你的订餐系统项目提供有力的指导和帮助。