487 lines
17 KiB
Markdown
487 lines
17 KiB
Markdown
# Design Document: 订单系统迁移
|
||
|
||
## Overview
|
||
|
||
本设计文档描述将PHP订单系统迁移到.NET 8的技术方案。订单系统是抽奖盲盒平台的核心业务模块,涉及订单金额计算、订单创建支付、订单查询管理、仓库/盒柜管理等功能。
|
||
|
||
## Architecture
|
||
|
||
### 系统架构
|
||
|
||
```
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ API Layer │
|
||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||
│ │ OrderController │ │WarehouseController│ │
|
||
│ └────────┬────────┘ └────────┬────────┘ │
|
||
└───────────┼─────────────────────┼───────────────────────────┘
|
||
│ │
|
||
┌───────────┼─────────────────────┼───────────────────────────┐
|
||
│ ▼ ▼ Service Layer │
|
||
│ ┌─────────────────┐ ┌─────────────────┐ │
|
||
│ │ OrderService │ │WarehouseService │ │
|
||
│ └────────┬────────┘ └────────┬────────┘ │
|
||
│ │ │ │
|
||
│ ┌────────┴────────┐ ┌────────┴────────┐ │
|
||
│ │OrderCalculation │ │ PrizeService │ │
|
||
│ │ Service │ │ │ │
|
||
│ └─────────────────┘ └─────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
│ │
|
||
┌───────────┼─────────────────────┼───────────────────────────┐
|
||
│ ▼ ▼ Data Layer │
|
||
│ ┌─────────────────────────────────────────────────────┐ │
|
||
│ │ HoneyBoxDbContext │ │
|
||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌───────────┐ │ │
|
||
│ │ │ Orders │ │OrderList│ │Delivery │ │ Recovery │ │ │
|
||
│ │ └─────────┘ └─────────┘ └─────────┘ └───────────┘ │ │
|
||
│ └─────────────────────────────────────────────────────┘ │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
```
|
||
|
||
## Components and Interfaces
|
||
|
||
### 1. OrderController
|
||
|
||
```csharp
|
||
[ApiController]
|
||
[Route("api")]
|
||
public class OrderController : ControllerBase
|
||
{
|
||
// 一番赏订单金额计算
|
||
[HttpPost("ordermoney")]
|
||
Task<ApiResponse<OrderCalculationDto>> CalculateOrderMoney(OrderMoneyRequest request);
|
||
|
||
// 一番赏订单创建
|
||
[HttpPost("orderbuy")]
|
||
Task<ApiResponse<OrderBuyResponseDto>> CreateOrder(OrderBuyRequest request);
|
||
|
||
// 无限赏订单金额计算
|
||
[HttpPost("infinite_ordermoney")]
|
||
Task<ApiResponse<OrderCalculationDto>> CalculateInfiniteOrderMoney(InfiniteOrderMoneyRequest request);
|
||
|
||
// 无限赏订单创建
|
||
[HttpPost("infinite_orderbuy")]
|
||
Task<ApiResponse<OrderBuyResponseDto>> CreateInfiniteOrder(InfiniteOrderBuyRequest request);
|
||
|
||
// 商城订单金额计算
|
||
[HttpPost("mall_ordermoney")]
|
||
Task<ApiResponse<OrderCalculationDto>> CalculateMallOrderMoney(MallOrderMoneyRequest request);
|
||
|
||
// 订单列表
|
||
[HttpPost("order_list")]
|
||
Task<ApiResponse<PageResponse<OrderListDto>>> GetOrderList(OrderListRequest request);
|
||
|
||
// 订单详情
|
||
[HttpPost("order_detail")]
|
||
Task<ApiResponse<OrderDetailDto>> GetOrderDetail(OrderDetailRequest request);
|
||
|
||
// 一番赏抽奖结果
|
||
[HttpPost("prizeorderlog")]
|
||
Task<ApiResponse<List<PrizeOrderLogDto>>> GetPrizeOrderLog(PrizeOrderLogRequest request);
|
||
|
||
// 无限赏抽奖结果
|
||
[HttpPost("infinite_prizeorderlog")]
|
||
Task<ApiResponse<List<PrizeOrderLogDto>>> GetInfinitePrizeOrderLog(PrizeOrderLogRequest request);
|
||
}
|
||
```
|
||
|
||
### 2. WarehouseController
|
||
|
||
```csharp
|
||
[ApiController]
|
||
[Route("api")]
|
||
public class WarehouseController : ControllerBase
|
||
{
|
||
// 仓库首页
|
||
[HttpPost("warehouse_index")]
|
||
Task<ApiResponse<PageResponse<WarehouseItemDto>>> GetWarehouseIndex(WarehouseIndexRequest request);
|
||
|
||
// 奖品回收
|
||
[HttpPost("warehouse_recovery")]
|
||
Task<ApiResponse<RecoveryResultDto>> RecoveryPrizes(RecoveryRequest request);
|
||
|
||
// 奖品发货
|
||
[HttpPost("warehouse_send")]
|
||
Task<ApiResponse<SendResultDto>> SendPrizes(SendRequest request);
|
||
|
||
// 确认发货
|
||
[HttpPost("warehouse_send_confirm")]
|
||
Task<ApiResponse> ConfirmSend(ConfirmSendRequest request);
|
||
|
||
// 发货记录
|
||
[HttpPost("warehouse_send_record")]
|
||
Task<ApiResponse<PageResponse<SendRecordDto>>> GetSendRecords(SendRecordRequest request);
|
||
|
||
// 发货记录详情
|
||
[HttpPost("warehouse_send_record_detail")]
|
||
Task<ApiResponse<SendRecordDetailDto>> GetSendRecordDetail(SendRecordDetailRequest request);
|
||
|
||
// 回收记录
|
||
[HttpPost("warehouse_recovery_record")]
|
||
Task<ApiResponse<PageResponse<RecoveryRecordDto>>> GetRecoveryRecords(RecoveryRecordRequest request);
|
||
|
||
// 物流信息
|
||
[HttpPost("warehouse_order_logistics")]
|
||
Task<ApiResponse<LogisticsDto>> GetLogistics(LogisticsRequest request);
|
||
}
|
||
```
|
||
|
||
### 3. IOrderService
|
||
|
||
```csharp
|
||
public interface IOrderService
|
||
{
|
||
// 订单金额计算
|
||
Task<OrderCalculationDto> CalculateOrderMoneyAsync(int userId, OrderMoneyRequest request);
|
||
Task<OrderCalculationDto> CalculateInfiniteOrderMoneyAsync(int userId, InfiniteOrderMoneyRequest request);
|
||
Task<OrderCalculationDto> CalculateMallOrderMoneyAsync(int userId, MallOrderMoneyRequest request);
|
||
|
||
// 订单创建
|
||
Task<OrderBuyResponseDto> CreateOrderAsync(int userId, OrderBuyRequest request);
|
||
Task<OrderBuyResponseDto> CreateInfiniteOrderAsync(int userId, InfiniteOrderBuyRequest request);
|
||
|
||
// 订单查询
|
||
Task<PageResponse<OrderListDto>> GetOrderListAsync(int userId, OrderListRequest request);
|
||
Task<OrderDetailDto> GetOrderDetailAsync(int userId, string orderNum);
|
||
|
||
// 抽奖结果
|
||
Task<List<PrizeOrderLogDto>> GetPrizeOrderLogAsync(int userId, string orderNum);
|
||
Task<List<PrizeOrderLogDto>> GetInfinitePrizeOrderLogAsync(int userId, string orderNum);
|
||
}
|
||
```
|
||
|
||
### 4. IWarehouseService
|
||
|
||
```csharp
|
||
public interface IWarehouseService
|
||
{
|
||
// 仓库查询
|
||
Task<PageResponse<WarehouseItemDto>> GetWarehouseIndexAsync(int userId, int page, int status);
|
||
|
||
// 奖品回收
|
||
Task<RecoveryResultDto> RecoveryPrizesAsync(int userId, string orderListIds);
|
||
|
||
// 奖品发货
|
||
Task<SendResultDto> SendPrizesAsync(int userId, SendRequest request);
|
||
Task<bool> ConfirmSendAsync(int userId, int id);
|
||
|
||
// 记录查询
|
||
Task<PageResponse<SendRecordDto>> GetSendRecordsAsync(int userId, int page);
|
||
Task<SendRecordDetailDto> GetSendRecordDetailAsync(int userId, int id);
|
||
Task<PageResponse<RecoveryRecordDto>> GetRecoveryRecordsAsync(int userId, int page);
|
||
|
||
// 物流查询
|
||
Task<LogisticsDto> GetLogisticsAsync(int userId, int id);
|
||
}
|
||
```
|
||
|
||
## Data Models
|
||
|
||
### Request Models
|
||
|
||
```csharp
|
||
// 一番赏订单金额计算请求
|
||
public class OrderMoneyRequest
|
||
{
|
||
[JsonPropertyName("goods_id")]
|
||
public int GoodsId { get; set; }
|
||
|
||
[JsonPropertyName("num")]
|
||
public int Num { get; set; }
|
||
|
||
[JsonPropertyName("prize_num")]
|
||
public int PrizeNum { get; set; }
|
||
|
||
[JsonPropertyName("coupon_id")]
|
||
public string? CouponId { get; set; }
|
||
|
||
[JsonPropertyName("use_money_is")]
|
||
public int UseMoneyIs { get; set; } = 2;
|
||
|
||
[JsonPropertyName("use_integral_is")]
|
||
public int UseIntegralIs { get; set; } = 2;
|
||
|
||
[JsonPropertyName("use_money2_is")]
|
||
public int UseMoney2Is { get; set; } = 2;
|
||
}
|
||
|
||
// 无限赏订单金额计算请求
|
||
public class InfiniteOrderMoneyRequest
|
||
{
|
||
[JsonPropertyName("goods_id")]
|
||
public int GoodsId { get; set; }
|
||
|
||
[JsonPropertyName("prize_num")]
|
||
public int PrizeNum { get; set; }
|
||
|
||
[JsonPropertyName("use_money_is")]
|
||
public int UseMoneyIs { get; set; } = 2;
|
||
|
||
[JsonPropertyName("use_integral_is")]
|
||
public int UseIntegralIs { get; set; } = 2;
|
||
|
||
[JsonPropertyName("use_money2_is")]
|
||
public int UseMoney2Is { get; set; } = 2;
|
||
|
||
[JsonPropertyName("coupon_id")]
|
||
public string? CouponId { get; set; }
|
||
}
|
||
|
||
// 商城订单金额计算请求
|
||
public class MallOrderMoneyRequest
|
||
{
|
||
[JsonPropertyName("goods_id")]
|
||
public int GoodsId { get; set; }
|
||
|
||
[JsonPropertyName("prize_num")]
|
||
public int PrizeNum { get; set; }
|
||
|
||
[JsonPropertyName("goods_num")]
|
||
public int GoodsNum { get; set; }
|
||
|
||
[JsonPropertyName("use_money_is")]
|
||
public int UseMoneyIs { get; set; } = 2;
|
||
|
||
[JsonPropertyName("use_integral_is")]
|
||
public int UseIntegralIs { get; set; } = 2;
|
||
|
||
[JsonPropertyName("use_money2_is")]
|
||
public int UseMoney2Is { get; set; } = 2;
|
||
}
|
||
|
||
// 仓库首页请求
|
||
public class WarehouseIndexRequest
|
||
{
|
||
[JsonPropertyName("page")]
|
||
public int Page { get; set; } = 1;
|
||
|
||
[JsonPropertyName("status")]
|
||
public int Status { get; set; } = 0;
|
||
}
|
||
|
||
// 发货请求
|
||
public class SendRequest
|
||
{
|
||
[JsonPropertyName("order_list_ids")]
|
||
public string OrderListIds { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("name")]
|
||
public string Name { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("mobile")]
|
||
public string Mobile { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("address")]
|
||
public string Address { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("message")]
|
||
public string? Message { get; set; }
|
||
}
|
||
```
|
||
|
||
### Response Models
|
||
|
||
```csharp
|
||
// 订单金额计算响应
|
||
public class OrderCalculationDto
|
||
{
|
||
[JsonPropertyName("order_total")]
|
||
public string OrderTotal { get; set; } = "0.00";
|
||
|
||
[JsonPropertyName("price")]
|
||
public string Price { get; set; } = "0.00";
|
||
|
||
[JsonPropertyName("goods_info")]
|
||
public GoodsInfoDto? GoodsInfo { get; set; }
|
||
|
||
[JsonPropertyName("goodsExtend")]
|
||
public GoodsExtendDto? GoodsExtend { get; set; }
|
||
|
||
[JsonPropertyName("user_money")]
|
||
public string UserMoney { get; set; } = "0.00";
|
||
|
||
[JsonPropertyName("user_integral")]
|
||
public string UserIntegral { get; set; } = "0.00";
|
||
|
||
[JsonPropertyName("user_money2")]
|
||
public string UserMoney2 { get; set; } = "0.00";
|
||
}
|
||
|
||
// 订单创建响应
|
||
public class OrderBuyResponseDto
|
||
{
|
||
[JsonPropertyName("status")]
|
||
public int Status { get; set; }
|
||
|
||
[JsonPropertyName("order_num")]
|
||
public string OrderNum { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("res")]
|
||
public WechatPayParamsDto? Res { get; set; }
|
||
}
|
||
|
||
// 微信支付参数
|
||
public class WechatPayParamsDto
|
||
{
|
||
[JsonPropertyName("appId")]
|
||
public string AppId { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("timeStamp")]
|
||
public string TimeStamp { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("nonceStr")]
|
||
public string NonceStr { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("package")]
|
||
public string Package { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("signType")]
|
||
public string SignType { get; set; } = "RSA";
|
||
|
||
[JsonPropertyName("paySign")]
|
||
public string PaySign { get; set; } = string.Empty;
|
||
}
|
||
|
||
// 订单列表项
|
||
public class OrderListDto
|
||
{
|
||
[JsonPropertyName("id")]
|
||
public int Id { get; set; }
|
||
|
||
[JsonPropertyName("order_num")]
|
||
public string OrderNum { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("goods_title")]
|
||
public string GoodsTitle { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("goods_imgurl")]
|
||
public string GoodsImgUrl { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("order_total")]
|
||
public string OrderTotal { get; set; } = "0.00";
|
||
|
||
[JsonPropertyName("price")]
|
||
public string Price { get; set; } = "0.00";
|
||
|
||
[JsonPropertyName("prize_num")]
|
||
public int PrizeNum { get; set; }
|
||
|
||
[JsonPropertyName("status")]
|
||
public int Status { get; set; }
|
||
|
||
[JsonPropertyName("addtime")]
|
||
public long AddTime { get; set; }
|
||
|
||
[JsonPropertyName("pay_time")]
|
||
public long? PayTime { get; set; }
|
||
}
|
||
|
||
// 仓库物品
|
||
public class WarehouseItemDto
|
||
{
|
||
[JsonPropertyName("id")]
|
||
public int Id { get; set; }
|
||
|
||
[JsonPropertyName("goodslist_title")]
|
||
public string GoodsListTitle { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("goodslist_imgurl")]
|
||
public string GoodsListImgUrl { get; set; } = string.Empty;
|
||
|
||
[JsonPropertyName("goodslist_price")]
|
||
public string GoodsListPrice { get; set; } = "0.00";
|
||
|
||
[JsonPropertyName("goodslist_money")]
|
||
public string GoodsListMoney { get; set; } = "0.00";
|
||
|
||
[JsonPropertyName("status")]
|
||
public int Status { get; set; }
|
||
|
||
[JsonPropertyName("addtime")]
|
||
public long AddTime { get; set; }
|
||
}
|
||
```
|
||
|
||
## Correctness Properties
|
||
|
||
*A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do. Properties serve as the bridge between human-readable specifications and machine-verifiable correctness guarantees.*
|
||
|
||
### Property 1: 订单金额计算正确性
|
||
|
||
*For any* order calculation request with valid goods_id and prize_num, the calculated order_total should equal price * prize_num minus any applicable discounts (coupon, balance, integral, money2).
|
||
|
||
**Validates: Requirements 1.1, 1.2, 1.3, 1.4, 1.5**
|
||
|
||
### Property 2: 订单创建数据完整性
|
||
|
||
*For any* successfully created order, the order record should contain all required fields (order_num, goods_id, user_id, order_total, status) and the order_num should be unique.
|
||
|
||
**Validates: Requirements 2.1, 2.2**
|
||
|
||
### Property 3: 仓库物品状态一致性
|
||
|
||
*For any* warehouse item, after recovery operation, the item status should be updated to recovered (status=1) and the user's balance should increase by the recovery amount.
|
||
|
||
**Validates: Requirements 11.2, 11.3**
|
||
|
||
### Property 4: 发货记录完整性
|
||
|
||
*For any* delivery request with valid order_list_ids and address info, a delivery record should be created with all items linked and status set to pending.
|
||
|
||
**Validates: Requirements 12.1, 12.2, 12.3, 12.4**
|
||
|
||
### Property 5: 订单列表分页正确性
|
||
|
||
*For any* order list request, the returned data should be correctly paginated and the last_page value should accurately reflect the total number of pages.
|
||
|
||
**Validates: Requirements 6.1, 6.3, 6.4**
|
||
|
||
## Error Handling
|
||
|
||
### 订单相关错误
|
||
|
||
| 错误场景 | 错误码 | 错误消息 |
|
||
|---------|--------|---------|
|
||
| 商品不存在 | 0 | 商品不存在 |
|
||
| 库存不足 | 0 | 库存不足 |
|
||
| 超出购买限制 | 0 | 超出购买限制 |
|
||
| 订单不存在 | 0 | 订单不存在 |
|
||
| 无权访问订单 | 0 | 无权访问该订单 |
|
||
| 优惠券无效 | 0 | 优惠券无效或已过期 |
|
||
|
||
### 仓库相关错误
|
||
|
||
| 错误场景 | 错误码 | 错误消息 |
|
||
|---------|--------|---------|
|
||
| 奖品不存在 | 0 | 奖品不存在 |
|
||
| 奖品不可回收 | 0 | 该奖品不可回收 |
|
||
| 奖品不可发货 | 0 | 该奖品不可发货 |
|
||
| 地址信息不完整 | 0 | 请填写完整的收货信息 |
|
||
| 发货记录不存在 | 0 | 发货记录不存在 |
|
||
|
||
## Testing Strategy
|
||
|
||
### 单元测试
|
||
|
||
- 测试订单金额计算逻辑的各种场景
|
||
- 测试优惠券、余额、积分抵扣逻辑
|
||
- 测试仓库物品状态转换逻辑
|
||
- 测试分页逻辑
|
||
|
||
### 属性测试
|
||
|
||
使用xUnit和FsCheck进行属性测试:
|
||
|
||
- Property 1: 订单金额计算正确性
|
||
- Property 2: 订单创建数据完整性
|
||
- Property 3: 仓库物品状态一致性
|
||
- Property 4: 发货记录完整性
|
||
- Property 5: 订单列表分页正确性
|
||
|
||
### 集成测试
|
||
|
||
- 测试完整的订单创建流程
|
||
- 测试仓库回收和发货流程
|
||
- 测试API响应格式兼容性
|