366 lines
10 KiB
Markdown
366 lines
10 KiB
Markdown
# Design Document: 商品系统迁移
|
||
|
||
## Overview
|
||
|
||
本设计文档描述将PHP商品系统迁移到.NET 8的技术方案。商品系统是抽赏平台的核心模块,需要实现商品列表、详情、箱号管理、收藏、奖品统计、中奖记录等功能,同时保持与原PHP API的兼容性。
|
||
|
||
## Architecture
|
||
|
||
### 系统架构图
|
||
|
||
```mermaid
|
||
graph TB
|
||
subgraph "API Layer"
|
||
GC[GoodsController]
|
||
CC[CollectionController]
|
||
end
|
||
|
||
subgraph "Service Layer"
|
||
GS[GoodsService]
|
||
CS[CollectionService]
|
||
PS[PrizeService]
|
||
CacheS[GoodsCacheService]
|
||
end
|
||
|
||
subgraph "Data Layer"
|
||
DB[(MySQL Database)]
|
||
Redis[(Redis Cache)]
|
||
end
|
||
|
||
GC --> GS
|
||
GC --> PS
|
||
CC --> CS
|
||
GS --> CacheS
|
||
GS --> DB
|
||
CS --> DB
|
||
PS --> DB
|
||
CacheS --> Redis
|
||
```
|
||
|
||
### 数据流图
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Client
|
||
participant Controller
|
||
participant Service
|
||
participant Cache
|
||
participant Database
|
||
|
||
Client->>Controller: GET /goods
|
||
Controller->>Service: GetGoodsListAsync()
|
||
Service->>Cache: TryGetGoodsList()
|
||
alt Cache Hit
|
||
Cache-->>Service: Cached Data
|
||
else Cache Miss
|
||
Service->>Database: Query Goods
|
||
Database-->>Service: Goods Data
|
||
Service->>Cache: SetGoodsList()
|
||
end
|
||
Service-->>Controller: GoodsListDto
|
||
Controller-->>Client: JSON Response
|
||
```
|
||
|
||
## Components and Interfaces
|
||
|
||
### 1. GoodsService
|
||
|
||
```csharp
|
||
public interface IGoodsService
|
||
{
|
||
// 商品列表查询
|
||
Task<PagedResult<GoodsListDto>> GetGoodsListAsync(GoodsQueryRequest request, int userId);
|
||
|
||
// 商品详情查询
|
||
Task<GoodsDetailResponse> GetGoodsDetailAsync(int goodsId, int goodsNum, int userId);
|
||
|
||
// 商品子奖品查询
|
||
Task<List<GoodsChildrenDto>> GetGoodsChildrenAsync(int goodsId, int goodsNum, int goodsListId);
|
||
|
||
// 商品扩展配置查询
|
||
Task<GoodsExtendDto> GetGoodsExtendAsync(int goodsId, int goodsType);
|
||
|
||
// 箱号列表查询
|
||
Task<List<BoxGroupDto>> GetBoxListAsync(int goodsId);
|
||
|
||
// 箱号详情查询
|
||
Task<List<BoxDetailDto>> GetBoxDetailAsync(int goodsId, int pageNo, int sort);
|
||
}
|
||
```
|
||
|
||
### 2. CollectionService
|
||
|
||
```csharp
|
||
public interface ICollectionService
|
||
{
|
||
// 收藏/取消收藏
|
||
Task<bool> ToggleCollectionAsync(int userId, int goodsId, int goodsNum, string action);
|
||
|
||
// 收藏列表查询
|
||
Task<PagedResult<CollectionDto>> GetCollectionListAsync(int userId, int page, int limit);
|
||
|
||
// 检查收藏状态
|
||
Task<bool> IsCollectedAsync(int userId, int goodsId, int goodsNum);
|
||
}
|
||
```
|
||
|
||
### 3. PrizeService
|
||
|
||
```csharp
|
||
public interface IPrizeService
|
||
{
|
||
// 奖品数量统计
|
||
Task<PrizeCountResponse> GetPrizeCountAsync(int goodsId);
|
||
|
||
// 奖品内容查询
|
||
Task<PrizeContentResponse> GetPrizeContentAsync(int goodsId, int num);
|
||
|
||
// 中奖记录查询
|
||
Task<PrizeLogsResponse> GetPrizeLogsAsync(int goodsId, int goodsNum, int shangId, int page);
|
||
}
|
||
```
|
||
|
||
### 4. GoodsCacheService
|
||
|
||
```csharp
|
||
public interface IGoodsCacheService
|
||
{
|
||
// 获取商品参与次数
|
||
Task<int> GetJoinCountAsync(int goodsId);
|
||
|
||
// 更新商品参与次数
|
||
Task IncrementJoinCountAsync(int goodsId);
|
||
|
||
// 清除商品缓存
|
||
Task InvalidateGoodsCacheAsync(int goodsId);
|
||
}
|
||
```
|
||
|
||
## Data Models
|
||
|
||
### Request Models
|
||
|
||
```csharp
|
||
public class GoodsQueryRequest
|
||
{
|
||
public int Type { get; set; } = -1;
|
||
public int Page { get; set; } = 1;
|
||
public int PageSize { get; set; } = 15;
|
||
}
|
||
|
||
public class GoodsDetailRequest
|
||
{
|
||
public int GoodsId { get; set; }
|
||
public int GoodsNum { get; set; } = 0;
|
||
}
|
||
|
||
public class BoxDetailRequest
|
||
{
|
||
public int GoodsId { get; set; }
|
||
public int PageNo { get; set; } = 0;
|
||
public int Sort { get; set; } = 0; // 0=箱号升序, 1=箱号降序, 2=余量降序
|
||
}
|
||
|
||
public class CollectionRequest
|
||
{
|
||
public int GoodsId { get; set; }
|
||
public int GoodsNum { get; set; }
|
||
public string Action { get; set; } // "add" or "remove"
|
||
}
|
||
|
||
public class PrizeLogsRequest
|
||
{
|
||
public int GoodsId { get; set; }
|
||
public int GoodsNum { get; set; }
|
||
public int ShangId { get; set; } = 0;
|
||
public int Page { get; set; } = 1;
|
||
}
|
||
```
|
||
|
||
### Response Models
|
||
|
||
```csharp
|
||
public class GoodsListDto
|
||
{
|
||
public int Id { get; set; }
|
||
public string Title { get; set; }
|
||
public string ImgUrl { get; set; }
|
||
public string Price { get; set; }
|
||
public int Type { get; set; }
|
||
public string TypeText { get; set; }
|
||
public int Stock { get; set; }
|
||
public int SaleStock { get; set; }
|
||
public int Status { get; set; }
|
||
public int LockIs { get; set; }
|
||
public int IsShouZhe { get; set; }
|
||
public int NewIs { get; set; }
|
||
public int JoinCount { get; set; }
|
||
public int NeedDrawNum { get; set; }
|
||
}
|
||
|
||
public class GoodsDetailResponse
|
||
{
|
||
public GoodsInfoDto Goods { get; set; }
|
||
public LockInfoDto LockInfo { get; set; }
|
||
public List<string> JoinUser { get; set; }
|
||
public int JoinCount { get; set; }
|
||
public List<GoodsListItemDto> GoodsList { get; set; }
|
||
public LimitInfoDto LimitInfo { get; set; }
|
||
}
|
||
|
||
public class GoodsListItemDto
|
||
{
|
||
public int Id { get; set; }
|
||
public int ShangId { get; set; }
|
||
public ShangInfoDto ShangInfo { get; set; }
|
||
public string Title { get; set; }
|
||
public int Stock { get; set; }
|
||
public int SurplusStock { get; set; }
|
||
public string ImgUrl { get; set; }
|
||
public int GoodsType { get; set; }
|
||
public string Price { get; set; }
|
||
public string ScMoney { get; set; }
|
||
public string SaleTime { get; set; }
|
||
public string Pro { get; set; }
|
||
public bool Children { get; set; }
|
||
}
|
||
|
||
public class BoxDetailDto
|
||
{
|
||
public int Num { get; set; }
|
||
public int SurplusAllStock { get; set; }
|
||
public List<BoxGoodsListDto> GoodsList { get; set; }
|
||
}
|
||
|
||
public class PrizeLogsResponse
|
||
{
|
||
public List<CategoryDto> Category { get; set; }
|
||
public List<PrizeLogDto> Data { get; set; }
|
||
public int LastPage { 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* goods query with type filter, the returned list SHALL only contain goods matching the specified type (or all goods if type=-1), with status=1, unlock_amount <= user consumption, sorted by sort DESC then id DESC.
|
||
|
||
**Validates: Requirements 1.1, 1.4, 1.5, 1.6**
|
||
|
||
### Property 2: 商品详情数据完整性
|
||
|
||
*For any* goods detail request, the response SHALL contain complete goods info, lock_info structure, join_user list, collection_is flag, and limitInfo structure with all required fields present.
|
||
|
||
**Validates: Requirements 2.1, 2.4, 2.5, 2.6, 2.7**
|
||
|
||
### Property 3: 奖品概率计算正确性
|
||
|
||
*For any* prize item in goods detail, the probability (pro) SHALL be calculated as (surplus_stock / total_surplus_stock * 100) and formatted correctly.
|
||
|
||
**Validates: Requirements 2.3, 3.2**
|
||
|
||
### Property 4: 子奖品数据完整性
|
||
|
||
*For any* child prize request, the response SHALL contain all child items with real_pro calculated and shang_info included for each item.
|
||
|
||
**Validates: Requirements 3.1, 3.2, 3.3**
|
||
|
||
### Property 5: 箱号管理数据正确性
|
||
|
||
*For any* box detail request, the response SHALL contain boxes with correct surplus_stock, support specified sort order, and include goodslist summary for each box.
|
||
|
||
**Validates: Requirements 5.1, 5.2, 5.3, 5.4**
|
||
|
||
### Property 6: 收藏操作往返一致性
|
||
|
||
*For any* collection add operation followed by collection list query, the added goods SHALL appear in the list; for any remove operation, the goods SHALL not appear in the list.
|
||
|
||
**Validates: Requirements 6.1, 6.2, 6.4**
|
||
|
||
### Property 7: 中奖记录数据完整性
|
||
|
||
*For any* prize logs request, the response SHALL contain paginated records with user_info, prize_info, shang_info, and addtime for each record, supporting shang_id filtering.
|
||
|
||
**Validates: Requirements 8.1, 8.2, 8.3, 8.4**
|
||
|
||
### Property 8: API响应格式一致性
|
||
|
||
*For any* API response, the structure SHALL contain status (int), msg (string), and data (object) fields consistent with PHP API format.
|
||
|
||
**Validates: Requirements 10.1, 10.3**
|
||
|
||
## Error Handling
|
||
|
||
### 错误码定义
|
||
|
||
| 错误码 | 描述 | 场景 |
|
||
|--------|------|------|
|
||
| 0 | 请求失败 | 通用错误 |
|
||
| 1 | 请求成功 | 成功响应 |
|
||
| -1 | 未登录 | Token无效或过期 |
|
||
| -2 | 商品不存在 | 商品ID无效 |
|
||
| -3 | 商品已下架 | 商品状态非1 |
|
||
| -4 | 已收藏 | 重复收藏 |
|
||
| -5 | 未收藏 | 取消未收藏的商品 |
|
||
|
||
### 异常处理策略
|
||
|
||
```csharp
|
||
public class GoodsNotFoundException : BusinessException
|
||
{
|
||
public GoodsNotFoundException(int goodsId)
|
||
: base(-2, $"商品不存在: {goodsId}") { }
|
||
}
|
||
|
||
public class GoodsOfflineException : BusinessException
|
||
{
|
||
public GoodsOfflineException(int goodsId)
|
||
: base(-3, $"商品已下架: {goodsId}") { }
|
||
}
|
||
|
||
public class AlreadyCollectedException : BusinessException
|
||
{
|
||
public AlreadyCollectedException()
|
||
: base(-4, "已收藏") { }
|
||
}
|
||
```
|
||
|
||
## Testing Strategy
|
||
|
||
### 单元测试
|
||
|
||
- 测试商品列表过滤逻辑
|
||
- 测试概率计算算法
|
||
- 测试箱号排序逻辑
|
||
- 测试收藏状态切换
|
||
|
||
### 属性测试
|
||
|
||
使用FsCheck或类似库进行属性测试:
|
||
|
||
- **Property 1**: 商品列表过滤和排序 - 生成随机商品数据,验证过滤和排序正确性
|
||
- **Property 3**: 概率计算 - 生成随机库存数据,验证概率计算公式
|
||
- **Property 6**: 收藏往返 - 生成随机收藏操作,验证状态一致性
|
||
- **Property 8**: API格式 - 验证所有响应符合标准格式
|
||
|
||
### 集成测试
|
||
|
||
- 测试完整的商品查询流程
|
||
- 测试缓存命中和失效
|
||
- 测试与数据库的交互
|
||
|
||
### 测试配置
|
||
|
||
```csharp
|
||
// 属性测试配置
|
||
[Property(MaxTest = 100)]
|
||
public Property GoodsListFilteringProperty()
|
||
{
|
||
// 生成随机商品和查询条件
|
||
// 验证过滤结果正确性
|
||
}
|
||
```
|