10 KiB
Design Document: 商品系统迁移
Overview
本设计文档描述将PHP商品系统迁移到.NET 8的技术方案。商品系统是抽赏平台的核心模块,需要实现商品列表、详情、箱号管理、收藏、奖品统计、中奖记录等功能,同时保持与原PHP API的兼容性。
Architecture
系统架构图
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
数据流图
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
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
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
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
public interface IGoodsCacheService
{
// 获取商品参与次数
Task<int> GetJoinCountAsync(int goodsId);
// 更新商品参与次数
Task IncrementJoinCountAsync(int goodsId);
// 清除商品缓存
Task InvalidateGoodsCacheAsync(int goodsId);
}
Data Models
Request Models
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
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 | 未收藏 | 取消未收藏的商品 |
异常处理策略
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格式 - 验证所有响应符合标准格式
集成测试
- 测试完整的商品查询流程
- 测试缓存命中和失效
- 测试与数据库的交互
测试配置
// 属性测试配置
[Property(MaxTest = 100)]
public Property GoodsListFilteringProperty()
{
// 生成随机商品和查询条件
// 验证过滤结果正确性
}