HaniBlindBox/.kiro/specs/goods-system-migration/design.md
2026-01-02 19:03:15 +08:00

10 KiB
Raw Blame History

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()
{
    // 生成随机商品和查询条件
    // 验证过滤结果正确性
}