阶段7任务

This commit is contained in:
zpc 2026-01-03 00:20:54 +08:00
parent 2a86e9c627
commit 3783d28ca7
3 changed files with 907 additions and 0 deletions

View File

@ -0,0 +1,473 @@
# Design Document: 抽奖系统迁移
## Overview
本设计文档描述了将PHP抽奖系统迁移到.NET 8的技术方案。抽奖系统是盲盒平台的核心功能需要确保算法准确性、数据一致性和API兼容性。
迁移范围包括:
- 抽奖结果查询接口(一番赏、无限赏)
- 中奖记录查询接口
- 道具卡抽奖功能
- 抽奖算法引擎
- 库存管理机制
## Architecture
```
┌─────────────────────────────────────────────────────────────┐
│ API Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │LotteryCtrl │ │InfiniteCtrl │ │ GoodsController │ │
│ └──────┬──────┘ └──────┬──────┘ └──────────┬──────────┘ │
└─────────┼────────────────┼────────────────────┼─────────────┘
│ │ │
┌─────────┼────────────────┼────────────────────┼─────────────┐
│ ▼ ▼ ▼ │
│ ┌─────────────────────────────────────────────────────┐ │
│ │ Service Layer │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌───────────┐ │ │
│ │ │LotteryService│ │ PrizeService │ │OrderService│ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └─────┬─────┘ │ │
│ │ │ │ │ │ │
│ │ ┌──────▼─────────────────▼────────────────▼──────┐ │ │
│ │ │ LotteryEngine │ │ │
│ │ │ ┌────────────────┐ ┌─────────────────────┐ │ │ │
│ │ │ │WeightedRandom │ │InventoryManager │ │ │ │
│ │ │ └────────────────┘ └─────────────────────┘ │ │ │
│ │ └────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
┌─────────▼───────────────────────────────────────────────────┐
│ Data Layer │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ OrderItem │ │ GoodsItem │ │ ItemCard │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
```
## Components and Interfaces
### 1. ILotteryService 接口
```csharp
/// <summary>
/// 抽奖服务接口
/// </summary>
public interface ILotteryService
{
/// <summary>
/// 获取一番赏抽奖结果
/// </summary>
Task<PrizeOrderLogResponseDto> GetPrizeOrderLogAsync(int userId, string orderNum);
/// <summary>
/// 获取无限赏抽奖结果
/// </summary>
Task<PrizeOrderLogResponseDto> GetInfinitePrizeOrderLogAsync(int userId, string orderNum);
/// <summary>
/// 获取无限赏中奖记录
/// </summary>
Task<InfiniteShangLogResponseDto> GetInfiniteShangLogAsync(int goodsId, int page, int pageSize);
/// <summary>
/// 获取每日抽奖记录
/// </summary>
Task<DailyPrizeRecordsResponseDto> GetDailyPrizeRecordsAsync(int goodsId);
/// <summary>
/// 道具卡抽奖
/// </summary>
Task<ItemCardDrawResponseDto> DrawWithItemCardAsync(int userId, int goodsId, int itemCardId);
}
```
### 2. ILotteryEngine 接口
```csharp
/// <summary>
/// 抽奖引擎接口
/// </summary>
public interface ILotteryEngine
{
/// <summary>
/// 执行单次抽奖
/// </summary>
Task<LotteryDrawResult> DrawAsync(LotteryDrawRequest request);
/// <summary>
/// 执行多次抽奖
/// </summary>
Task<List<LotteryDrawResult>> DrawMultipleAsync(LotteryDrawRequest request, int count);
/// <summary>
/// 计算奖品概率
/// </summary>
Task<List<PrizeProbability>> CalculateProbabilitiesAsync(int goodsId, int num);
/// <summary>
/// 验证抽奖结果
/// </summary>
Task<bool> ValidateDrawResultAsync(LotteryDrawResult result);
}
```
### 3. IInventoryManager 接口
```csharp
/// <summary>
/// 库存管理接口
/// </summary>
public interface IInventoryManager
{
/// <summary>
/// 原子扣减库存
/// </summary>
Task<bool> DeductStockAsync(int goodsItemId, int quantity = 1);
/// <summary>
/// 检查库存可用性
/// </summary>
Task<bool> CheckStockAvailabilityAsync(int goodsItemId);
/// <summary>
/// 获取可抽奖品池
/// </summary>
Task<List<GoodsItem>> GetAvailablePrizePoolAsync(int goodsId, int num);
}
```
## Data Models
### Request/Response DTOs
```csharp
// 抽奖结果响应
public class PrizeOrderLogResponseDto
{
[JsonPropertyName("status")]
public int Status { get; set; }
[JsonPropertyName("msg")]
public string Msg { get; set; } = string.Empty;
[JsonPropertyName("data")]
public List<PrizeOrderLogItemDto> Data { get; set; } = new();
}
public class PrizeOrderLogItemDto
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("order_id")]
public int OrderId { 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("goodslist_type")]
public int GoodslistType { get; set; }
[JsonPropertyName("status")]
public int Status { get; set; }
[JsonPropertyName("addtime")]
public int Addtime { get; set; }
[JsonPropertyName("prize_code")]
public string PrizeCode { get; set; } = string.Empty;
[JsonPropertyName("luck_no")]
public int LuckNo { get; set; }
}
// 无限赏中奖记录响应
public class InfiniteShangLogResponseDto
{
[JsonPropertyName("status")]
public int Status { get; set; }
[JsonPropertyName("msg")]
public string Msg { get; set; } = string.Empty;
[JsonPropertyName("data")]
public InfiniteShangLogDataDto Data { get; set; } = new();
}
public class InfiniteShangLogDataDto
{
[JsonPropertyName("data")]
public List<InfiniteShangLogItemDto> Data { get; set; } = new();
[JsonPropertyName("last_page")]
public int LastPage { get; set; }
}
// 每日抽奖记录响应
public class DailyPrizeRecordsResponseDto
{
[JsonPropertyName("status")]
public int Status { get; set; }
[JsonPropertyName("msg")]
public string Msg { get; set; } = string.Empty;
[JsonPropertyName("data")]
public List<DailyPrizeRecordItemDto> Data { get; set; } = new();
}
// 道具卡抽奖响应
public class ItemCardDrawResponseDto
{
[JsonPropertyName("status")]
public int Status { get; set; }
[JsonPropertyName("msg")]
public string Msg { get; set; } = string.Empty;
[JsonPropertyName("data")]
public ItemCardDrawDataDto? Data { get; set; }
}
// 抽奖引擎内部模型
public class LotteryDrawRequest
{
public int UserId { get; set; }
public int GoodsId { get; set; }
public int Num { get; set; }
public int OrderId { get; set; }
public string OrderNum { get; set; } = string.Empty;
public int OrderType { get; set; }
}
public class LotteryDrawResult
{
public int GoodsItemId { get; set; }
public string Title { get; set; } = string.Empty;
public string ImgUrl { get; set; } = string.Empty;
public int ShangId { get; set; }
public string ShangTitle { get; set; } = string.Empty;
public string ShangColor { get; set; } = string.Empty;
public decimal Price { get; set; }
public decimal ScMoney { get; set; }
public string PrizeCode { get; set; } = string.Empty;
public int LuckNo { get; set; }
public DateTime DrawTime { get; set; }
}
public class PrizeProbability
{
public int GoodsItemId { get; set; }
public string Title { get; set; } = string.Empty;
public int SurplusStock { get; set; }
public decimal Probability { get; set; }
public double Weight { 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* valid order with lottery prizes, when querying lottery results, the returned prize list SHALL contain exactly the prizes associated with that order, with all required fields (title, image, price, recovery value, prize_code, luck_no) populated correctly.
**Validates: Requirements 1.1, 1.3, 2.1, 2.2, 2.3**
### Property 2: 中奖记录分页正确性
*For any* product with winning records, when querying with page number P and page size S, the returned records SHALL be a subset of all records, with count <= S, and records SHALL be ordered by time descending.
**Validates: Requirements 3.1, 3.6, 4.1, 5.3**
### Property 3: 中奖记录过滤正确性
*For any* shang_id filter value, all returned winning records SHALL have shang_id matching the filter value (when filter > 0), or include all shang_ids (when filter = 0).
**Validates: Requirements 3.2, 4.2**
### Property 4: 概率计算正确性
*For any* prize pool with total surplus stock T > 0, the probability of each prize with surplus stock S SHALL equal S/T * 100, and the sum of all probabilities SHALL equal 100%.
**Validates: Requirements 7.1**
### Property 5: 权重随机分布正确性
*For any* prize pool, over a large number of draws (N >= 1000), the actual distribution of selected prizes SHALL approximate the theoretical probability distribution within acceptable variance (chi-square test p-value > 0.05).
**Validates: Requirements 7.2**
### Property 6: 库存扣减原子性
*For any* successful lottery draw, the surplus_stock of the selected prize SHALL decrease by exactly 1, and no prize with surplus_stock = 0 SHALL be selected.
**Validates: Requirements 7.3, 7.4, 8.1, 8.4**
### Property 7: 并发安全性
*For any* concurrent lottery draws on the same prize pool, the total number of prizes distributed SHALL NOT exceed the initial total surplus stock, preventing overselling.
**Validates: Requirements 8.3**
### Property 8: 抽奖记录持久化完整性
*For any* completed lottery draw, an order_item record SHALL be created with correct user_id, goods_id, goodslist_id, shang_id, prize_code (unique), source=1, and appropriate order_type.
**Validates: Requirements 9.1, 9.2, 9.3, 9.4**
### Property 9: API响应格式一致性
*For any* API response, the structure SHALL follow { "status": int, "msg": string, "data": object }, with all field names in snake_case format.
**Validates: Requirements 10.1, 10.2, 10.3, 10.4**
### Property 10: 道具卡使用正确性
*For any* item card draw request, if the card is valid and owned by the user, the draw SHALL execute and the card SHALL be marked as consumed; if invalid or already used, an error SHALL be returned without executing the draw.
**Validates: Requirements 6.1, 6.2, 6.3**
## Error Handling
### 错误类型和处理策略
| 错误类型 | 错误码 | 处理策略 |
|---------|--------|---------|
| 订单不存在 | 0 | 返回空列表或错误消息 |
| 商品不存在 | 0 | 返回错误消息 |
| 商品已下架 | 0 | 返回错误消息 |
| 库存不足 | 0 | 返回错误消息,不执行抽奖 |
| 道具卡无效 | 0 | 返回错误消息 |
| 道具卡已使用 | 0 | 返回错误消息 |
| 并发冲突 | 0 | 重试或返回错误 |
| 系统异常 | 0 | 记录日志,返回通用错误 |
### 异常处理代码示例
```csharp
public async Task<LotteryDrawResult> DrawAsync(LotteryDrawRequest request)
{
try
{
// 1. 获取可用奖品池
var prizePool = await _inventoryManager.GetAvailablePrizePoolAsync(
request.GoodsId, request.Num);
if (!prizePool.Any())
{
throw new BusinessException("暂无可抽奖品");
}
// 2. 计算概率并选择奖品
var probabilities = CalculateProbabilities(prizePool);
var selectedPrize = SelectPrizeByWeight(probabilities);
// 3. 原子扣减库存
var deducted = await _inventoryManager.DeductStockAsync(selectedPrize.Id);
if (!deducted)
{
// 库存扣减失败,重试
return await DrawAsync(request);
}
// 4. 创建抽奖记录
var result = await CreateDrawRecordAsync(request, selectedPrize);
return result;
}
catch (DbUpdateConcurrencyException)
{
// 并发冲突,重试
_logger.LogWarning("Concurrency conflict during draw, retrying...");
return await DrawAsync(request);
}
catch (BusinessException)
{
throw;
}
catch (Exception ex)
{
_logger.LogError(ex, "Lottery draw failed");
throw new BusinessException("抽奖失败,请稍后重试");
}
}
```
## Testing Strategy
### 单元测试
- 测试概率计算逻辑的正确性
- 测试权重随机选择算法
- 测试库存扣减逻辑
- 测试响应格式转换
### 属性测试
使用 FsCheck 或类似库进行属性测试:
```csharp
// Property 4: 概率计算正确性
[Property]
public Property ProbabilityCalculation_SumsTo100()
{
return Prop.ForAll(
Arb.From<List<int>>().Filter(stocks => stocks.Any(s => s > 0)),
stocks =>
{
var prizePool = stocks.Select((s, i) => new GoodsItem
{
Id = i,
SurplusStock = Math.Max(0, s)
}).ToList();
var probabilities = _engine.CalculateProbabilities(prizePool);
var sum = probabilities.Sum(p => p.Probability);
return Math.Abs(sum - 100) < 0.01m;
});
}
// Property 6: 库存扣减原子性
[Property]
public Property StockDeduction_DecrementsByOne()
{
return Prop.ForAll(
Arb.From<int>().Filter(stock => stock > 0),
initialStock =>
{
var item = new GoodsItem { Id = 1, SurplusStock = initialStock };
_inventoryManager.DeductStockAsync(item.Id).Wait();
var updatedItem = _dbContext.GoodsItems.Find(item.Id);
return updatedItem.SurplusStock == initialStock - 1;
});
}
```
### 集成测试
- 测试完整的抽奖流程
- 测试API端点响应格式
- 测试与PHP API的兼容性
- 测试并发场景下的数据一致性
### 测试配置
- 属性测试最少运行100次迭代
- 使用内存数据库进行单元测试
- 使用真实数据库进行集成测试
- 并发测试使用多线程模拟

View File

@ -0,0 +1,129 @@
# Requirements Document
## Introduction
本文档定义了抽奖系统从PHP迁移到.NET 8的需求。抽奖系统是盲盒平台的核心功能包括一番赏抽奖、无限赏抽奖、抽奖结果查询、中奖记录展示等功能。迁移需要确保与PHP API的完全兼容性同时保持抽奖算法的准确性和公平性。
## Glossary
- **Lottery_Engine**: 抽奖引擎,负责执行抽奖算法和概率计算
- **Prize_Service**: 奖品服务,负责奖品查询和库存管理
- **Order_Item**: 订单奖品记录,存储用户抽中的奖品信息
- **Goods_Item**: 商品奖品项,存储商品的奖品配置和库存
- **Shang_Level**: 赏品等级如A赏、B赏等奖品分类
- **Weighted_Random**: 权重随机算法,根据库存计算概率进行抽奖
- **Prize_Log**: 中奖记录,展示用户的抽奖历史
## Requirements
### Requirement 1: 一番赏抽奖结果查询
**User Story:** As a user, I want to view my lottery results after payment, so that I can see what prizes I won.
#### Acceptance Criteria
1. WHEN a user requests lottery results with a valid order number, THE Prize_Service SHALL return the list of won prizes for that order
2. WHEN a user requests lottery results with an invalid order number, THE Prize_Service SHALL return an empty list
3. THE Prize_Service SHALL return prize details including title, image, price, recovery value, prize code, and luck number
4. THE Prize_Service SHALL format the response to match PHP API structure with snake_case field names
### Requirement 2: 无限赏抽奖结果查询
**User Story:** As a user, I want to view my infinite lottery results, so that I can see what prizes I won from infinite pool.
#### Acceptance Criteria
1. WHEN a user requests infinite lottery results with a valid order number, THE Prize_Service SHALL return the list of won prizes
2. WHEN the order type is infinite lottery (type=2), THE Prize_Service SHALL query from infinite order items
3. THE Prize_Service SHALL return prize details consistent with the one-shot lottery format
### Requirement 3: 一番赏中奖记录查询
**User Story:** As a user, I want to view winning records for a specific product, so that I can see who won what prizes.
#### Acceptance Criteria
1. WHEN a user requests winning records for a product, THE Prize_Service SHALL return paginated winning records
2. WHEN a shang_id filter is provided, THE Prize_Service SHALL filter records by prize level
3. THE Prize_Service SHALL return category list for filtering options
4. THE Prize_Service SHALL group records by order_id, goodslist_id, and user_id with prize count
5. THE Prize_Service SHALL mask user nicknames for privacy (show partial name with ***)
6. THE Prize_Service SHALL order records by time descending (newest first)
### Requirement 4: 无限赏中奖记录查询
**User Story:** As a user, I want to view winning records for infinite lottery products, so that I can see recent winners.
#### Acceptance Criteria
1. WHEN a user requests infinite winning records, THE Prize_Service SHALL return paginated records for infinite lottery type
2. THE Prize_Service SHALL filter records by order_type=2 (infinite lottery)
3. THE Prize_Service SHALL return records with user info, prize info, and timestamp
### Requirement 5: 每日抽奖记录查询
**User Story:** As a user, I want to view daily lottery records for infinite products, so that I can track daily activity.
#### Acceptance Criteria
1. WHEN a user requests daily records for a product, THE Prize_Service SHALL return today's winning records
2. THE Prize_Service SHALL filter records by current date
3. THE Prize_Service SHALL return records ordered by time descending
### Requirement 6: 道具卡抽奖
**User Story:** As a user, I want to use item cards to draw prizes, so that I can get prizes without paying.
#### Acceptance Criteria
1. WHEN a user uses an item card for lottery, THE Lottery_Engine SHALL validate the item card ownership
2. WHEN the item card is valid, THE Lottery_Engine SHALL execute the lottery draw
3. WHEN the item card is used, THE Lottery_Engine SHALL mark the item card as consumed
4. IF the item card is invalid or already used, THEN THE Lottery_Engine SHALL return an error message
### Requirement 7: 抽奖算法引擎
**User Story:** As a system, I want to execute fair lottery draws, so that prizes are distributed according to configured probabilities.
#### Acceptance Criteria
1. THE Lottery_Engine SHALL calculate prize probability based on remaining stock (surplus_stock / total_surplus_stock * 100)
2. THE Lottery_Engine SHALL use weighted random selection algorithm for prize selection
3. THE Lottery_Engine SHALL exclude prizes with zero remaining stock from the draw pool
4. THE Lottery_Engine SHALL deduct inventory atomically after prize selection
5. THE Lottery_Engine SHALL record draw results in order_item table
6. THE Lottery_Engine SHALL generate unique prize_code for each won prize
7. IF all prizes are depleted, THEN THE Lottery_Engine SHALL return an error indicating no available prizes
### Requirement 8: 库存管理
**User Story:** As a system, I want to manage prize inventory accurately, so that overselling is prevented.
#### Acceptance Criteria
1. WHEN a prize is won, THE Prize_Service SHALL decrement surplus_stock by 1 atomically
2. THE Prize_Service SHALL use database transactions to ensure inventory consistency
3. IF concurrent draws occur, THE Prize_Service SHALL use optimistic locking to prevent race conditions
4. THE Prize_Service SHALL validate stock availability before confirming the draw
### Requirement 9: 抽奖记录持久化
**User Story:** As a system, I want to persist all lottery records, so that results can be audited and queried.
#### Acceptance Criteria
1. WHEN a lottery draw completes, THE Prize_Service SHALL create order_item records for each prize
2. THE Prize_Service SHALL record user_id, goods_id, goodslist_id, shang_id, prize_code, and timestamp
3. THE Prize_Service SHALL set source=1 for lottery-won prizes (vs source=0 for purchased)
4. THE Prize_Service SHALL set appropriate order_type based on lottery type (1=一番赏, 2=无限赏, etc.)
### Requirement 10: API响应格式兼容
**User Story:** As a frontend developer, I want consistent API responses, so that existing frontend code works without changes.
#### Acceptance Criteria
1. THE API SHALL return responses in format: { "status": 1, "msg": "success", "data": {...} }
2. THE API SHALL use snake_case for all field names in response
3. THE API SHALL return status=0 with error message for failures
4. THE API SHALL maintain backward compatibility with PHP API response structure

View File

@ -0,0 +1,305 @@
# Implementation Plan: 抽奖系统迁移
## Overview
本任务列表将PHP抽奖系统迁移到.NET 8按照接口优先级逐个迁移。每迁移一个接口前需要先查看PHP代码了解详细业务逻辑迁移完成后需要在API接口文档.md中标记迁移状态。
## Tasks
- [ ] 1. 基础设施准备
- [ ] 1.1 创建抽奖相关的DTO和Request/Response模型
- 在HoneyBox.Model/Models/Lottery目录下创建相关模型
- 包括PrizeOrderLogResponseDto、InfiniteShangLogResponseDto等
- 包括LotteryDrawRequest、LotteryDrawResult等内部模型
- _Requirements: 1.1-10.4_
- [ ] 1.2 创建服务接口定义
- 在HoneyBox.Core/Interfaces目录下创建ILotteryService接口
- 扩展现有IPrizeService接口如需要
- _Requirements: 1.1-10.4_
- [ ] 1.3 注册服务到DI容器
- 在ServiceModule.cs中注册LotteryService
- _Requirements: 1.1-10.4_
- [ ] 2. 一番赏抽奖结果查询接口
- [ ] 2.1 查看PHP代码了解一番赏抽奖结果逻辑
- 阅读server/php/app/api/controller/Order.php中的prizeorderlog方法
- 理解订单奖品查询、字段映射逻辑
- _Requirements: 1.1-1.4_
- [ ] 2.2 实现LotteryService - GetPrizeOrderLogAsync
- 根据order_num查询订单
- 查询order_item表获取抽奖结果
- 返回奖品列表包含title, imgurl, price, money, prize_code, luck_no
- _Requirements: 1.1-1.4_
- [ ] 2.3 实现控制器接口 POST /api/prizeorderlog
- 调用LotteryService.GetPrizeOrderLogAsync
- 更新API接口文档标记迁移状态
- _Requirements: 1.1-1.4_
- [ ]* 2.4 编写抽奖结果查询属性测试
- **Property 1: 抽奖结果查询正确性**
- **Validates: Requirements 1.1, 1.3**
- [ ] 3. 无限赏抽奖结果查询接口
- [ ] 3.1 查看PHP代码了解无限赏抽奖结果逻辑
- 阅读server/php/app/api/controller/Infinite.php中的infinite_prizeorderlog方法
- 理解无限赏订单类型和查询逻辑
- _Requirements: 2.1-2.3_
- [ ] 3.2 实现LotteryService - GetInfinitePrizeOrderLogAsync
- 根据order_num查询无限赏订单
- 查询order_item表order_type=2
- 返回奖品列表
- _Requirements: 2.1-2.3_
- [ ] 3.3 实现控制器接口 POST /api/infinite_prizeorderlog
- 调用LotteryService.GetInfinitePrizeOrderLogAsync
- 更新API接口文档标记迁移状态
- _Requirements: 2.1-2.3_
- [ ] 4. Checkpoint - 抽奖结果查询测试验证
- 确保一番赏和无限赏抽奖结果查询接口测试通过
- 如有问题请询问用户
- [ ] 5. 无限赏中奖记录查询接口
- [ ] 5.1 查看PHP代码了解无限赏中奖记录逻辑
- 阅读server/php/app/api/controller/Infinite.php中的infinite_shang_log方法
- 理解分页、用户信息脱敏逻辑
- _Requirements: 4.1-4.3_
- [ ] 5.2 实现LotteryService - GetInfiniteShangLogAsync
- 查询order_item表order_type=2, source=1
- 分页返回中奖记录
- 用户昵称脱敏处理
- _Requirements: 4.1-4.3_
- [ ] 5.3 实现控制器接口 POST /api/infinite_shang_log
- 调用LotteryService.GetInfiniteShangLogAsync
- 更新API接口文档标记迁移状态
- _Requirements: 4.1-4.3_
- [ ]* 5.4 编写中奖记录分页属性测试
- **Property 2: 中奖记录分页正确性**
- **Validates: Requirements 4.1**
- [ ] 6. 每日抽奖记录查询接口
- [ ] 6.1 查看PHP代码了解每日抽奖记录逻辑
- 阅读server/php/app/api/controller/Infinite.php中的infinite_prizerecords方法
- 理解日期过滤逻辑
- _Requirements: 5.1-5.3_
- [ ] 6.2 实现LotteryService - GetDailyPrizeRecordsAsync
- 查询当天的中奖记录
- 按时间倒序排列
- _Requirements: 5.1-5.3_
- [ ] 6.3 实现控制器接口 POST /api/infinite_prizerecords
- 调用LotteryService.GetDailyPrizeRecordsAsync
- 更新API接口文档标记迁移状态
- _Requirements: 5.1-5.3_
- [ ] 7. Checkpoint - 中奖记录查询测试验证
- 确保无限赏中奖记录和每日记录查询接口测试通过
- 如有问题请询问用户
- [ ] 8. 道具卡抽奖接口
- [ ] 8.1 查看PHP代码了解道具卡抽奖逻辑
- 阅读server/php/app/api/controller/Infinite.php中的item_card_chou方法
- 理解道具卡验证、抽奖执行、状态更新逻辑
- _Requirements: 6.1-6.4_
- [ ] 8.2 实现LotteryService - DrawWithItemCardAsync
- 验证道具卡所有权和状态
- 执行抽奖逻辑
- 标记道具卡为已使用
- 创建抽奖记录
- _Requirements: 6.1-6.4_
- [ ] 8.3 实现控制器接口 POST /api/item_card_chou
- 调用LotteryService.DrawWithItemCardAsync
- 更新API接口文档标记迁移状态
- _Requirements: 6.1-6.4_
- [ ]* 8.4 编写道具卡使用属性测试
- **Property 10: 道具卡使用正确性**
- **Validates: Requirements 6.1, 6.2, 6.3**
- [ ] 9. 抽奖算法引擎实现
- [ ] 9.1 查看PHP代码了解抽奖算法
- 阅读server/php/app/api/controller/Notify.php中的drawprize_notice方法
- 阅读相关抽奖逻辑代码
- 理解概率计算、权重随机选择算法
- _Requirements: 7.1-7.7_
- [ ] 9.2 实现LotteryEngine - 概率计算
- 实现CalculateProbabilitiesAsync方法
- 概率 = surplus_stock / total_surplus_stock * 100
- 排除库存为0的奖品
- _Requirements: 7.1, 7.3_
- [ ] 9.3 实现LotteryEngine - 权重随机选择
- 实现SelectPrizeByWeight方法
- 使用权重随机算法选择奖品
- _Requirements: 7.2_
- [ ]* 9.4 编写概率计算属性测试
- **Property 4: 概率计算正确性**
- **Validates: Requirements 7.1**
- [ ]* 9.5 编写权重随机分布属性测试
- **Property 5: 权重随机分布正确性**
- **Validates: Requirements 7.2**
- [ ] 10. 库存管理实现
- [ ] 10.1 查看PHP代码了解库存扣减逻辑
- 阅读server/php/app/common/model/GoodsList.php中的库存操作方法
- 理解原子扣减、并发控制逻辑
- _Requirements: 8.1-8.4_
- [ ] 10.2 实现InventoryManager - 原子库存扣减
- 实现DeductStockAsync方法
- 使用乐观锁或行级锁确保原子性
- 验证库存可用性
- _Requirements: 8.1, 8.4_
- [ ] 10.3 实现InventoryManager - 获取可用奖品池
- 实现GetAvailablePrizePoolAsync方法
- 过滤surplus_stock > 0的奖品
- _Requirements: 7.3_
- [ ]* 10.4 编写库存扣减属性测试
- **Property 6: 库存扣减原子性**
- **Validates: Requirements 7.4, 8.1**
- [ ]* 10.5 编写并发安全属性测试
- **Property 7: 并发安全性**
- **Validates: Requirements 8.3**
- [ ] 11. Checkpoint - 抽奖引擎测试验证
- 确保抽奖算法和库存管理测试通过
- 如有问题请询问用户
- [ ] 12. 抽奖记录持久化实现
- [ ] 12.1 查看PHP代码了解抽奖记录创建逻辑
- 阅读server/php/app/api/controller/Notify.php中的记录创建代码
- 理解order_item表字段映射
- _Requirements: 9.1-9.4_
- [ ] 12.2 实现LotteryEngine - 创建抽奖记录
- 实现CreateDrawRecordAsync方法
- 设置正确的user_id, goods_id, goodslist_id, shang_id
- 生成唯一prize_code
- 设置source=1, 正确的order_type
- _Requirements: 9.1-9.4_
- [ ]* 12.3 编写抽奖记录持久化属性测试
- **Property 8: 抽奖记录持久化完整性**
- **Validates: Requirements 9.1, 9.2, 9.3, 9.4**
- [ ] 13. 完整抽奖流程集成
- [ ] 13.1 实现LotteryEngine - DrawAsync完整流程
- 整合概率计算、权重选择、库存扣减、记录创建
- 实现错误处理和重试逻辑
- _Requirements: 7.1-7.7, 8.1-8.4, 9.1-9.4_
- [ ] 13.2 实现LotteryEngine - DrawMultipleAsync
- 支持多次抽奖
- 循环调用DrawAsync
- _Requirements: 7.1-7.7_
- [ ] 14. Checkpoint - 完整抽奖流程测试验证
- 确保完整抽奖流程测试通过
- 如有问题请询问用户
- [ ] 15. API响应格式验证
- [ ] 15.1 验证所有接口响应格式
- 确保status、msg、data结构一致
- 确保字段命名与PHP API一致snake_case
- _Requirements: 10.1-10.4_
- [ ]* 15.2 编写API响应格式属性测试
- **Property 9: API响应格式一致性**
- **Validates: Requirements 10.1, 10.2, 10.3, 10.4**
- [ ] 16. 集成测试
- [ ] 16.1 编写抽奖结果查询集成测试
- 测试一番赏抽奖结果查询
- 测试无限赏抽奖结果查询
- _Requirements: 1.1-2.3_
- [ ] 16.2 编写中奖记录查询集成测试
- 测试无限赏中奖记录查询
- 测试每日抽奖记录查询
- 测试分页和过滤功能
- _Requirements: 3.1-5.3_
- [ ] 16.3 编写道具卡抽奖集成测试
- 测试有效道具卡抽奖
- 测试无效道具卡错误处理
- _Requirements: 6.1-6.4_
- [ ] 16.4 编写抽奖引擎集成测试
- 测试完整抽奖流程
- 测试库存扣减
- 测试记录创建
- _Requirements: 7.1-9.4_
- [ ] 17. 文档更新和最终验证
- [ ] 17.1 更新API接口文档
- 确认所有迁移接口都已标记
- 记录新接口地址
- _Requirements: 10.1-10.4_
- [ ] 17.2 创建HTTP测试文件
- 在HoneyBox.Api目录下创建lottery-system.http测试文件
- 包含所有抽奖系统相关接口的测试请求
- [ ] 18. Final Checkpoint - 完整功能验证
- 确保所有测试通过
- 确保API文档已更新
- 确保与前端兼容性
- 如有问题请询问用户
## Notes
- Tasks marked with `*` are optional and can be skipped for faster MVP
- Each task references specific requirements for traceability
- Checkpoints ensure incremental validation
- Property tests validate universal correctness properties
- Unit tests validate specific examples and edge cases
- 每迁移完成一个接口都需要在docs/API接口文档.md中标记迁移状态和新接口地址
- 迁移前必须先查看PHP代码了解详细业务逻辑确保功能一致性
- 抽奖系统涉及概率公平性,需要特别注意算法准确性
- 库存管理需要确保并发安全,防止超发
## 接口迁移清单
| 序号 | PHP接口 | 新接口地址 | 状态 |
|------|---------|-----------|------|
| 1 | POST /prizeorderlog | POST /api/prizeorderlog | ⏳ |
| 2 | POST /infinite_prizeorderlog | POST /api/infinite_prizeorderlog | ⏳ |
| 3 | POST /infinite_shang_log | POST /api/infinite_shang_log | ⏳ |
| 4 | POST /infinite_prizerecords | POST /api/infinite_prizerecords | ⏳ |
| 5 | POST /item_card_chou | POST /api/item_card_chou | ⏳ |
## 关键业务逻辑说明
### 抽奖类型
| order_type | 类型名称 | 说明 |
|------------|---------|------|
| 1 | 一番赏 | 标准一番赏抽奖 |
| 2 | 无限赏 | 无限池抽奖 |
| 3 | 擂台赏 | 擂台赏抽奖 |
| 4 | 转转赏 | 转转赏抽奖 |
| 5 | 福利屋 | 福利屋抽奖 |
| 6 | 商城赏 | 商城赏抽奖 |
| 7 | 翻倍赏 | 翻倍赏抽奖 |
| 8 | 抽卡机 | 抽卡机抽奖 |
### 奖品来源
| source | 说明 |
|--------|------|
| 0 | 购买获得 |
| 1 | 抽奖获得 |
### 概率计算公式
```
单个奖品概率 = (该奖品剩余库存 / 所有可抽奖品总剩余库存) * 100%
```
### 权重随机算法
```csharp
// 1. 计算总权重
var totalWeight = prizes.Sum(p => p.SurplusStock);
// 2. 生成随机数 [0, totalWeight)
var random = new Random();
var randomValue = random.NextDouble() * totalWeight;
// 3. 累加权重,找到命中的奖品
double currentWeight = 0;
foreach (var prize in prizes)
{
currentWeight += prize.SurplusStock;
if (randomValue < currentWeight)
{
return prize;
}
}
```