外卖点餐自取系统 — OOD 设计文档
一、关键场景
场景1:顾客浏览菜品并下单
| 1 | 顾客 | 输入手机号密码登录 | LoginFrame, UserService, UserRepository |
| 2 | 系统 | 验证身份,判断为 Customer | User, Customer |
| 3 | 顾客 | 浏览菜品列表 | ProductPanel, ProductService, ProductRepository |
| 4 | 顾客 | 搜索"面" | ProductService.searchProducts() |
| 5 | 顾客 | 双击"红烧牛肉面"加购 | CartService.addToCart() |
| 6 | 顾客 | 查看购物车 | CartPanel, CartService |
| 7 | 顾客 | 点击提交订单 | OrderService.placeOrder() |
| 8 | 系统 | 加锁→查库存→扣库存→生成订单→清空购物车 | synchronized(lock) |
| 9 | 顾客 | 查看订单,支付 | OrderPanel, OrderService.payOrder() |
场景2:管理员接单并完成订单
| 1 | 管理员 | 输入手机号密码登录 | LoginFrame, UserService |
| 2 | 系统 | 验证身份,判断为 Admin | User, Admin |
| 3 | 管理员 | 查看待接单订单 | AdminFrame, AdminService.getPendingOrders() |
| 4 | 管理员 | 选中订单,点击接单 | AdminService.acceptOrder() |
| 5 | 系统 | 状态变更为"备餐中" | Order.setStatus(COOKING) |
| 6 | 管理员 | 点击备餐完成 | AdminService.readForPickup() |
| 7 | 系统 | 生成取餐码,状态→待自取 | IdGenerator.generatePickupCode() |
| 8 | 管理员 | 确认顾客取餐 | AdminService.completeOrder() |
| 9 | 系统 | 状态→已完成 | Order.setStatus(COMPLETED) |
二、架构设计(MVC 分层)
| 表现层 | view | Swing 窗口和面板,处理用户交互 | View |
| 业务逻辑层 | service | 核心业务流程、校验、状态控制 | Controller |
| 数据访问层 | repository | 数据存取(HashMap 模拟数据库) | Model |
| 实体层 | model | 数据模型 + 枚举 + 状态机 | Model |
调用规则:view → service → repository → model,单向依赖,不可跨层调用。
三、核心类清单(含属性、方法、可见性、关系)
| User | model | #userId:String, #phone:String, #password:String | +getUserId():String, +getPhone():String, +getPassword():String | 父类 |
| Customer | model | (继承) | — | extends User |
| Admin | model | (继承) | — | extends User |
| Product | model | -productId:String, -name:String, -stock:int, -price:double, -category:String | +getPrice():double, +getStock():int, +setStock(int):void | — |
| Order | model | -orderId:String, -total:double, -status:OrderStatusEnum, -pickupcode:String, -date:Date, -orderItems:List<OrderItem>, -userId:String | +calculateTotal():double, +getStatus():OrderStatusEnum, +getOrderItems():List | 1…* → OrderItem |
| OrderItem | model | -productId:String, -quantity:int, -price:double, -name:String | +getPrice():double, +getQuantity():int, +getName():String | 快照关联 → Product |
| ShoppingCart | model | -cartItems:List<CartItem> | +addToCartItem(String,int):void, +removeFromCartItem(String):void | 0…* → CartItem |
| CartItem | model | -productId:String, -quantity:int | +getProductId():String, +getQuantity():int | 关联 → Product |
| OrderStatusEnum | model | PENDING_PAYMENT, PENDING_ACCEPT, COOKING, READY_FOR_PICKUP, COMPLETED, CANCELLED | +canTransitionTo(OrderStatusEnum):boolean, +getDescription():String, +nextAfterPay():OrderStatusEnum | — |
| UserService | service | -userRepository:UserRepository | +register(String,String,String):User, +login(String,String):User, +isAdmin(String):boolean | — |
| ProductService | service | -productRepository:ProductRepository | +getAllProducts():List, +searchProducts(String):List, +addProduct(…):Product, +updateProduct(…):void, +deleteProduct(String):void | — |
| CartService | service | -shoppingCartRepository:ShoppingCartRepository, -productRepository:ProductRepository | +getCart(String):ShoppingCart, +addToCart(String,String,int):void, +removeFromCart(String,String):void, +getTotal(String):double, +clearCart(String):void, +isCartEmpty(String):boolean | — |
| OrderService | service | -orderRepository:OrderRepository, -productRepository:ProductRepository, -shoppingCartRepository:ShoppingCartRepository, -lock:Object | +placeOrder(String):Order, +payOrder(String):void, +cancelOrder(String):void, +getUserOrders(String):List, +getOrderById(String):Order | — |
| AdminService | service | -orderRepository:OrderRepository, -productRepository:ProductRepository, -productService:ProductService | +getAllOrders():List, +getPendingOrders():List, +getCookingOrders():List, +acceptOrder(String):void, +rejectOrder(String):void, +readForPickup(String):void, +completeOrder(String):void | — |
| UserRepository | repository | -userMap:Map<String,User> | +save(User):void, +findById(String):User, +findByPhone(String):User, +existByPhone(String):boolean, +findAll():List | — |
| ProductRepository | repository | -productMap:Map<String,Product> | +save(Product):void, +findProductById(String):Product, +findAll():List, +findByCategory(String):List, +searchByName(String):List, +reduceStock(String,int):void, +updateStock(String,int):void | — |
| OrderRepository | repository | -orderMap:Map<String,Order> | +save(Order):void, +findOrderById(String):Order, +findAllOrders():List, +findAllOrdersByUserId(String):List, +findAllOrdersByStatus(OrderStatusEnum):List | — |
| ShoppingCartRepository | repository | -shoppingCartMap:Map<String,ShoppingCart> | +findByUserId(String):ShoppingCart, +save(String,ShoppingCart):void, +clear(String):void, +remove(String):void | — |
| LoginFrame | view | -userService, -cartService, -orderService, -productService, -adminService, -phoneField, -passwordField | 登录验证+路由分发 | — |
| RegisterFrame | view | -userService, -phoneField, -passwordField, -roleBox | 注册新用户 | — |
| CustomerFrame | view | -user, -cartService, -orderService, -productService, -userService | 组装 ProductPanel+CartPanel+OrderPanel | — |
| AdminFrame | view | -adminService, -userService, -cartService, -orderService, -productService | 订单管理+菜品管理 | — |
| ProductPanel | view | -productService, -cartService, -userId | +refreshData():void | — |
| CartPanel | view | -cartService, -productService, -userId | +refreshData():void | — |
| OrderPanel | view | -orderService, -userId | +refreshData():void | — |
| AddProductDialog | view | -productService | 添加菜品弹窗 | — |
| UpdateProductDialog | view | -productService | 更新菜品弹窗 | — |
| OrderItemDialog | view | — | 显示订单项明细 | — |
| IdGenerator | util | -userCounter:int, -productCounter:int, -orderCounter:int (static) | +generateUserId():String, +generateProductId():String, +generateOrderId():String, +generatePickupCode():String | — |
| Validator | util | — | +isVailPhone(String):boolean, +isVailPassword(String):boolean, +isVailQuantity(int):boolean | — |
| BusinessException | exception | — | — | extends RuntimeException |
| InsufficientStockException | exception | — | — | extends BusinessException |
| UnauthorizedException | exception | — | — | extends BusinessException |
四、继承关系
RuntimeException
└── BusinessException
├── InsufficientStockException
└── UnauthorizedException
User
├── Customer
└── Admin
JFrame
├── LoginFrame
├── RegisterFrame
├── CustomerFrame
└── AdminFrame
JPanel
├── ProductPanel
├── CartPanel
└── OrderPanel
JDialog
├── AddProductDialog
├── UpdateProductDialog
└── OrderItemDialog
text
五、关联关系(概念类图 + 代码实现)
下图展示了系统中核心类之间的关联关系,包括一对一、一对多,以及快照关联。
#mermaid-svg-fwVnTrBmwPT9enu6{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-fwVnTrBmwPT9enu6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-fwVnTrBmwPT9enu6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-fwVnTrBmwPT9enu6 .error-icon{fill:#552222;}#mermaid-svg-fwVnTrBmwPT9enu6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-fwVnTrBmwPT9enu6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-fwVnTrBmwPT9enu6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-fwVnTrBmwPT9enu6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-fwVnTrBmwPT9enu6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-fwVnTrBmwPT9enu6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-fwVnTrBmwPT9enu6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-fwVnTrBmwPT9enu6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-fwVnTrBmwPT9enu6 .marker.cross{stroke:#333333;}#mermaid-svg-fwVnTrBmwPT9enu6 svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-fwVnTrBmwPT9enu6 p{margin:0;}#mermaid-svg-fwVnTrBmwPT9enu6 g.classGroup text{fill:#9370DB;stroke:none;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:10px;}#mermaid-svg-fwVnTrBmwPT9enu6 g.classGroup text .title{font-weight:bolder;}#mermaid-svg-fwVnTrBmwPT9enu6 .cluster-label text{fill:#333;}#mermaid-svg-fwVnTrBmwPT9enu6 .cluster-label span{color:#333;}#mermaid-svg-fwVnTrBmwPT9enu6 .cluster-label span p{background-color:transparent;}#mermaid-svg-fwVnTrBmwPT9enu6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-fwVnTrBmwPT9enu6 .cluster text{fill:#333;}#mermaid-svg-fwVnTrBmwPT9enu6 .cluster span{color:#333;}#mermaid-svg-fwVnTrBmwPT9enu6 .nodeLabel,#mermaid-svg-fwVnTrBmwPT9enu6 .edgeLabel{color:#131300;}#mermaid-svg-fwVnTrBmwPT9enu6 .edgeLabel .label rect{fill:#ECECFF;}#mermaid-svg-fwVnTrBmwPT9enu6 .label text{fill:#131300;}#mermaid-svg-fwVnTrBmwPT9enu6 .labelBkg{background:#ECECFF;}#mermaid-svg-fwVnTrBmwPT9enu6 .edgeLabel .label span{background:#ECECFF;}#mermaid-svg-fwVnTrBmwPT9enu6 .classTitle{font-weight:bolder;}#mermaid-svg-fwVnTrBmwPT9enu6 .node rect,#mermaid-svg-fwVnTrBmwPT9enu6 .node circle,#mermaid-svg-fwVnTrBmwPT9enu6 .node ellipse,#mermaid-svg-fwVnTrBmwPT9enu6 .node polygon,#mermaid-svg-fwVnTrBmwPT9enu6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-fwVnTrBmwPT9enu6 .divider{stroke:#9370DB;stroke-width:1;}#mermaid-svg-fwVnTrBmwPT9enu6 g.clickable{cursor:pointer;}#mermaid-svg-fwVnTrBmwPT9enu6 g.classGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-fwVnTrBmwPT9enu6 g.classGroup line{stroke:#9370DB;stroke-width:1;}#mermaid-svg-fwVnTrBmwPT9enu6 .classLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-fwVnTrBmwPT9enu6 .classLabel .label{fill:#9370DB;font-size:10px;}#mermaid-svg-fwVnTrBmwPT9enu6 .relation{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-fwVnTrBmwPT9enu6 .dashed-line{stroke-dasharray:3;}#mermaid-svg-fwVnTrBmwPT9enu6 .dotted-line{stroke-dasharray:1 2;}#mermaid-svg-fwVnTrBmwPT9enu6 #compositionStart,#mermaid-svg-fwVnTrBmwPT9enu6 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-fwVnTrBmwPT9enu6 #compositionEnd,#mermaid-svg-fwVnTrBmwPT9enu6 .composition{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-fwVnTrBmwPT9enu6 #dependencyStart,#mermaid-svg-fwVnTrBmwPT9enu6 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-fwVnTrBmwPT9enu6 #dependencyStart,#mermaid-svg-fwVnTrBmwPT9enu6 .dependency{fill:#333333!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-fwVnTrBmwPT9enu6 #extensionStart,#mermaid-svg-fwVnTrBmwPT9enu6 .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-fwVnTrBmwPT9enu6 #extensionEnd,#mermaid-svg-fwVnTrBmwPT9enu6 .extension{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-fwVnTrBmwPT9enu6 #aggregationStart,#mermaid-svg-fwVnTrBmwPT9enu6 .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-fwVnTrBmwPT9enu6 #aggregationEnd,#mermaid-svg-fwVnTrBmwPT9enu6 .aggregation{fill:transparent!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-fwVnTrBmwPT9enu6 #lollipopStart,#mermaid-svg-fwVnTrBmwPT9enu6 .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-fwVnTrBmwPT9enu6 #lollipopEnd,#mermaid-svg-fwVnTrBmwPT9enu6 .lollipop{fill:#ECECFF!important;stroke:#333333!important;stroke-width:1;}#mermaid-svg-fwVnTrBmwPT9enu6 .edgeTerminals{font-size:11px;line-height:initial;}#mermaid-svg-fwVnTrBmwPT9enu6 .classTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-fwVnTrBmwPT9enu6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-fwVnTrBmwPT9enu6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-fwVnTrBmwPT9enu6 :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
拥有
拥有
包含
快照关联
包含
关联
使用
1
1
1
*
1
*
1
0..*
1..*
1
0..*
1
User
+String userId
+String phone
+String password
«extends User»
Customer
«extends User»
Admin
Product
+String productId
+String name
+int stock
+double price
+String category
Order
+String orderId
+double total
+OrderStatusEnum status
+String pickupcode
+Date date
+String userId
OrderItem
+String productId
+int quantity
+double price
+String name
ShoppingCart
+List cartItems
CartItem
+String productId
+int quantity
«enumeration»
OrderStatusEnum
PENDING_PAYMENT
PENDING_ACCEPT
COOKING
READY_FOR_PICKUP
COMPLETED
CANCELLED
代码实现方式:
| User → ShoppingCart | ShoppingCartRepository 以 userId 为 key |
| User → Order | Order 存 userId,OrderRepository.findByUserId() |
| Order → OrderItem | Order 存 List<OrderItem> |
| OrderItem → Product | OrderItem 存 productId、快照 price 和 name |
| ShoppingCart → CartItem | ShoppingCart 存 List<CartItem> |
| CartItem → Product | CartItem 存 productId |
六、状态图(订单状态流转)
订单状态流转图(状态图)
系统内订单共有 6 种状态,状态之间的流转由 OrderStatusEnum.canTransitionTo() 方法严格控制。
#mermaid-svg-UGzbcS48krsWXC8l{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-UGzbcS48krsWXC8l .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-UGzbcS48krsWXC8l .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-UGzbcS48krsWXC8l .error-icon{fill:#552222;}#mermaid-svg-UGzbcS48krsWXC8l .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-UGzbcS48krsWXC8l .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-UGzbcS48krsWXC8l .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-UGzbcS48krsWXC8l .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-UGzbcS48krsWXC8l .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-UGzbcS48krsWXC8l .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-UGzbcS48krsWXC8l .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-UGzbcS48krsWXC8l .marker{fill:#333333;stroke:#333333;}#mermaid-svg-UGzbcS48krsWXC8l .marker.cross{stroke:#333333;}#mermaid-svg-UGzbcS48krsWXC8l svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-UGzbcS48krsWXC8l p{margin:0;}#mermaid-svg-UGzbcS48krsWXC8l defs #statediagram-barbEnd{fill:#333333;stroke:#333333;}#mermaid-svg-UGzbcS48krsWXC8l g.stateGroup text{fill:#9370DB;stroke:none;font-size:10px;}#mermaid-svg-UGzbcS48krsWXC8l g.stateGroup text{fill:#333;stroke:none;font-size:10px;}#mermaid-svg-UGzbcS48krsWXC8l g.stateGroup .state-title{font-weight:bolder;fill:#131300;}#mermaid-svg-UGzbcS48krsWXC8l g.stateGroup rect{fill:#ECECFF;stroke:#9370DB;}#mermaid-svg-UGzbcS48krsWXC8l g.stateGroup line{stroke:#333333;stroke-width:1;}#mermaid-svg-UGzbcS48krsWXC8l .transition{stroke:#333333;stroke-width:1;fill:none;}#mermaid-svg-UGzbcS48krsWXC8l .stateGroup .composit{fill:white;border-bottom:1px;}#mermaid-svg-UGzbcS48krsWXC8l .stateGroup .alt-composit{fill:#e0e0e0;border-bottom:1px;}#mermaid-svg-UGzbcS48krsWXC8l .state-note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-UGzbcS48krsWXC8l .state-note text{fill:black;stroke:none;font-size:10px;}#mermaid-svg-UGzbcS48krsWXC8l .stateLabel .box{stroke:none;stroke-width:0;fill:#ECECFF;opacity:0.5;}#mermaid-svg-UGzbcS48krsWXC8l .edgeLabel .label rect{fill:#ECECFF;opacity:0.5;}#mermaid-svg-UGzbcS48krsWXC8l .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-UGzbcS48krsWXC8l .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-UGzbcS48krsWXC8l .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-UGzbcS48krsWXC8l .edgeLabel .label text{fill:#333;}#mermaid-svg-UGzbcS48krsWXC8l .label div .edgeLabel{color:#333;}#mermaid-svg-UGzbcS48krsWXC8l .stateLabel text{fill:#131300;font-size:10px;font-weight:bold;}#mermaid-svg-UGzbcS48krsWXC8l .node circle.state-start{fill:#333333;stroke:#333333;}#mermaid-svg-UGzbcS48krsWXC8l .node .fork-join{fill:#333333;stroke:#333333;}#mermaid-svg-UGzbcS48krsWXC8l .node circle.state-end{fill:#9370DB;stroke:white;stroke-width:1.5;}#mermaid-svg-UGzbcS48krsWXC8l .end-state-inner{fill:white;stroke-width:1.5;}#mermaid-svg-UGzbcS48krsWXC8l .node rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UGzbcS48krsWXC8l .node polygon{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UGzbcS48krsWXC8l #statediagram-barbEnd{fill:#333333;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram-cluster rect{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-UGzbcS48krsWXC8l .cluster-label,#mermaid-svg-UGzbcS48krsWXC8l .nodeLabel{color:#131300;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram-cluster rect.outer{rx:5px;ry:5px;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram-state .divider{stroke:#9370DB;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram-state .title-state{rx:5px;ry:5px;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram-cluster.statediagram-cluster .inner{fill:white;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram-cluster.statediagram-cluster-alt .inner{fill:#f0f0f0;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram-cluster .inner{rx:0;ry:0;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram-state rect.basic{rx:5px;ry:5px;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram-state rect.divider{stroke-dasharray:10,10;fill:#f0f0f0;}#mermaid-svg-UGzbcS48krsWXC8l .note-edge{stroke-dasharray:5;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram-note rect{fill:#fff5ad;stroke:#aaaa33;stroke-width:1px;rx:0;ry:0;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram-note text{fill:black;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram-note .nodeLabel{color:black;}#mermaid-svg-UGzbcS48krsWXC8l .statediagram .edgeLabel{color:red;}#mermaid-svg-UGzbcS48krsWXC8l #dependencyStart,#mermaid-svg-UGzbcS48krsWXC8l #dependencyEnd{fill:#333333;stroke:#333333;stroke-width:1;}#mermaid-svg-UGzbcS48krsWXC8l .statediagramTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-UGzbcS48krsWXC8l :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
创建订单
支付成功
取消订单
商家接单
商家拒单
备餐完成 / 生成取餐码
确认取餐
PENDING_PAYMENT
PENDING_ACCEPT
CANCELLED
COOKING
READY_FOR_PICKUP
COMPLETED
代码实现:OrderStatusEnum.canTransitionTo(OrderStatusEnum) 控制合法流转。
七、时序图(顾客下单)
OrderRepoOrderServiceCartRepoProductRepoCartServiceProductPanelCustomerFrameUserRepoUserServiceLoginFrameOrderRepoOrderServiceCartRepoProductRepoCartServiceProductPanelCustomerFrameUserRepoUserServiceLoginFrame#mermaid-svg-K8Rw9fulrdrBQgtP{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-K8Rw9fulrdrBQgtP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-K8Rw9fulrdrBQgtP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-K8Rw9fulrdrBQgtP .error-icon{fill:#552222;}#mermaid-svg-K8Rw9fulrdrBQgtP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-K8Rw9fulrdrBQgtP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-K8Rw9fulrdrBQgtP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-K8Rw9fulrdrBQgtP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-K8Rw9fulrdrBQgtP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-K8Rw9fulrdrBQgtP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-K8Rw9fulrdrBQgtP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-K8Rw9fulrdrBQgtP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-K8Rw9fulrdrBQgtP .marker.cross{stroke:#333333;}#mermaid-svg-K8Rw9fulrdrBQgtP svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-K8Rw9fulrdrBQgtP p{margin:0;}#mermaid-svg-K8Rw9fulrdrBQgtP .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-K8Rw9fulrdrBQgtP text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-K8Rw9fulrdrBQgtP .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-K8Rw9fulrdrBQgtP .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-K8Rw9fulrdrBQgtP .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-K8Rw9fulrdrBQgtP .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-K8Rw9fulrdrBQgtP #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-K8Rw9fulrdrBQgtP .sequenceNumber{fill:white;}#mermaid-svg-K8Rw9fulrdrBQgtP #sequencenumber{fill:#333;}#mermaid-svg-K8Rw9fulrdrBQgtP #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-K8Rw9fulrdrBQgtP .messageText{fill:#333;stroke:none;}#mermaid-svg-K8Rw9fulrdrBQgtP .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-K8Rw9fulrdrBQgtP .labelText,#mermaid-svg-K8Rw9fulrdrBQgtP .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-K8Rw9fulrdrBQgtP .loopText,#mermaid-svg-K8Rw9fulrdrBQgtP .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-K8Rw9fulrdrBQgtP .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-K8Rw9fulrdrBQgtP .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-K8Rw9fulrdrBQgtP .noteText,#mermaid-svg-K8Rw9fulrdrBQgtP .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-K8Rw9fulrdrBQgtP .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-K8Rw9fulrdrBQgtP .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-K8Rw9fulrdrBQgtP .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-K8Rw9fulrdrBQgtP .actorPopupMenu{position:absolute;}#mermaid-svg-K8Rw9fulrdrBQgtP .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-K8Rw9fulrdrBQgtP .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-K8Rw9fulrdrBQgtP .actor-man circle,#mermaid-svg-K8Rw9fulrdrBQgtP line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-K8Rw9fulrdrBQgtP :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}critical[同步锁 synchronized(lock)]顾客输入手机号、密码login(phone, password)findByPhone(phone)UserUser (Customer)new CustomerFrame(user, …)登录成功,显示顾客界面双击菜品(红烧牛肉面)弹出输入框,输入数量 2addToCart(userId, productId, 2)findProductById(productId)Product (name="红烧牛肉面", stock=50)校验库存 (50 >= 2)addToCartItem / savevoid提示"已添加到购物车"点击"提交订单"placeOrder(userId)findByUserId(userId)ShoppingCart (含红烧牛肉面 x2)findProductById (遍历)检查所有商品库存是否充足reduceStock (遍历扣减)save(order)clear(userId)Order (orderId=ORDxxx, total=¥30.00)下单成功,显示订单号顾客
八、时序图(管理员接单)
OrderOrderRepoAdminServiceAdminFrameOrderOrderRepoAdminServiceAdminFrame#mermaid-svg-9cUeeOEnvo2hLy1B{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-9cUeeOEnvo2hLy1B .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-9cUeeOEnvo2hLy1B .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-9cUeeOEnvo2hLy1B .error-icon{fill:#552222;}#mermaid-svg-9cUeeOEnvo2hLy1B .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-9cUeeOEnvo2hLy1B .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-9cUeeOEnvo2hLy1B .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-9cUeeOEnvo2hLy1B .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-9cUeeOEnvo2hLy1B .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-9cUeeOEnvo2hLy1B .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-9cUeeOEnvo2hLy1B .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-9cUeeOEnvo2hLy1B .marker{fill:#333333;stroke:#333333;}#mermaid-svg-9cUeeOEnvo2hLy1B .marker.cross{stroke:#333333;}#mermaid-svg-9cUeeOEnvo2hLy1B svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-9cUeeOEnvo2hLy1B p{margin:0;}#mermaid-svg-9cUeeOEnvo2hLy1B .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9cUeeOEnvo2hLy1B text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-9cUeeOEnvo2hLy1B .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-9cUeeOEnvo2hLy1B .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-9cUeeOEnvo2hLy1B .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-9cUeeOEnvo2hLy1B .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-9cUeeOEnvo2hLy1B #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-9cUeeOEnvo2hLy1B .sequenceNumber{fill:white;}#mermaid-svg-9cUeeOEnvo2hLy1B #sequencenumber{fill:#333;}#mermaid-svg-9cUeeOEnvo2hLy1B #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-9cUeeOEnvo2hLy1B .messageText{fill:#333;stroke:none;}#mermaid-svg-9cUeeOEnvo2hLy1B .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9cUeeOEnvo2hLy1B .labelText,#mermaid-svg-9cUeeOEnvo2hLy1B .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-9cUeeOEnvo2hLy1B .loopText,#mermaid-svg-9cUeeOEnvo2hLy1B .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-9cUeeOEnvo2hLy1B .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-9cUeeOEnvo2hLy1B .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-9cUeeOEnvo2hLy1B .noteText,#mermaid-svg-9cUeeOEnvo2hLy1B .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-9cUeeOEnvo2hLy1B .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9cUeeOEnvo2hLy1B .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9cUeeOEnvo2hLy1B .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-9cUeeOEnvo2hLy1B .actorPopupMenu{position:absolute;}#mermaid-svg-9cUeeOEnvo2hLy1B .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-9cUeeOEnvo2hLy1B .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-9cUeeOEnvo2hLy1B .actor-man circle,#mermaid-svg-9cUeeOEnvo2hLy1B line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-9cUeeOEnvo2hLy1B :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}管理员点击"只看待接单"getPendingOrders()findAllOrdersByStatus(PENDING_ACCEPT)List Order待接单订单列表表格显示待接单订单选中订单,点击"接单"acceptOrder(orderId)findOrderById(orderId)Order校验状态是否为 PENDING_ACCEPTsetStatus(COOKING)save(order)void弹窗"已接单",刷新列表点击"备餐完成"readForPickup(orderId)findOrderById(orderId)Order校验状态是否为 COOKING生成取餐码 generatePickupCode()setPickupcode("4729")setStatus(READY_FOR_PICKUP)save(order)void弹窗"备餐完成,取餐码:4729"点击"确认取餐"completeOrder(orderId)findOrderById(orderId)Order校验状态是否为 READY_FOR_PICKUPsetStatus(COMPLETED)save(order)void弹窗"订单完成"管理员
九、数据库设计(HashMap 模拟)
| User | userId (String) | phone, password | — |
| Product | productId (String) | name, stock, price, category | — |
| Order | orderId (String) | total, status, pickupcode, date, userId | userId → User |
| OrderItem | (嵌入 Order) | productId, quantity, price, name | productId → Product |
| ShoppingCart | userId (String) | cartItems: List<CartItem> | userId → User |
| CartItem | (嵌入 ShoppingCart) | productId, quantity | productId → Product |
存储方式:HashMap<String, Entity>,key 为实体唯一标识,O(1) 查询。
十、设计模式应用
| 状态模式 | OrderStatusEnum | 6种订单状态 + canTransitionTo() 控制流转 |
| 依赖注入 | 所有 Service 构造器 | Repository 通过构造器传入,不内部 new |
| MVC | 整体架构 | view=View, service=Controller, model=Model |
| 多态 | User/Customer/Admin | instanceof 判断角色,父类引用指向子类对象 |
| 单例思想 | OrderService.lock | private final Object lock 全局唯一锁对象 |





