321
This commit is contained in:
parent
f2ee678a2f
commit
cd5e451f5a
354
.kiro/specs/designated-prize-winner/design.md
Normal file
354
.kiro/specs/designated-prize-winner/design.md
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
# Design Document: 指定用户中奖功能
|
||||
|
||||
## Overview
|
||||
|
||||
本设计文档描述了"指定用户中奖"功能的技术实现方案。该功能允许后台管理员为盒子中的特定奖品指定中奖用户,使得该奖品只能被指定用户抽中(无限赏)或优先被指定用户抽中(有限赏带兜底)。
|
||||
|
||||
核心设计原则:
|
||||
- 不修改奖品本身的概率配置
|
||||
- 有限赏保证"每抽必中"机制(兜底)
|
||||
- 无限赏严格保护指定奖品
|
||||
- 最小化对现有抽奖流程的侵入
|
||||
|
||||
## Architecture
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "后台管理层"
|
||||
AdminAPI[后台管理 API]
|
||||
DesignatedPrizeService[DesignatedPrizeService]
|
||||
end
|
||||
|
||||
subgraph "抽奖核心层"
|
||||
LotteryEngine[LotteryEngine]
|
||||
InventoryManager[InventoryManager]
|
||||
PrizePoolFilter[奖品池过滤器]
|
||||
end
|
||||
|
||||
subgraph "数据层"
|
||||
DbContext[HoneyBoxDbContext]
|
||||
DesignatedPrizeEntity[GoodsDesignatedPrize]
|
||||
GoodsItemEntity[GoodsItem]
|
||||
end
|
||||
|
||||
AdminAPI --> DesignatedPrizeService
|
||||
DesignatedPrizeService --> DbContext
|
||||
|
||||
LotteryEngine --> PrizePoolFilter
|
||||
LotteryEngine --> InventoryManager
|
||||
PrizePoolFilter --> DbContext
|
||||
|
||||
DbContext --> DesignatedPrizeEntity
|
||||
DbContext --> GoodsItemEntity
|
||||
```
|
||||
|
||||
## Components and Interfaces
|
||||
|
||||
### 1. 数据实体:GoodsDesignatedPrize
|
||||
|
||||
```csharp
|
||||
namespace HoneyBox.Model.Entities;
|
||||
|
||||
/// <summary>
|
||||
/// 指定中奖配置实体
|
||||
/// </summary>
|
||||
public class GoodsDesignatedPrize
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int GoodsId { get; set; }
|
||||
public int GoodsItemId { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public bool IsActive { get; set; } = true;
|
||||
public string? Remark { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
// 导航属性
|
||||
public virtual Good? Goods { get; set; }
|
||||
public virtual GoodsItem? GoodsItem { get; set; }
|
||||
public virtual User? User { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 抽奖引擎接口扩展:ILotteryEngine
|
||||
|
||||
```csharp
|
||||
public interface ILotteryEngine
|
||||
{
|
||||
// 现有方法保持不变
|
||||
Task<LotteryDrawResult> DrawAsync(LotteryDrawRequest request);
|
||||
Task<LotteryDrawResult> DrawInfiniteAsync(LotteryDrawRequest request);
|
||||
|
||||
// 内部使用,不暴露新接口
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 指定中奖服务接口:IDesignatedPrizeService
|
||||
|
||||
```csharp
|
||||
namespace HoneyBox.Admin.Business.Services.Interfaces;
|
||||
|
||||
public interface IDesignatedPrizeService
|
||||
{
|
||||
Task<List<DesignatedPrizeDto>> GetByGoodsIdAsync(int goodsId);
|
||||
Task<DesignatedPrizeDto> CreateAsync(CreateDesignatedPrizeRequest request);
|
||||
Task<DesignatedPrizeDto> UpdateAsync(int id, UpdateDesignatedPrizeRequest request);
|
||||
Task DeleteAsync(int id);
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 后台管理 API 控制器
|
||||
|
||||
```csharp
|
||||
[ApiController]
|
||||
[Route("api/admin/goods/{goodsId}/designated-prizes")]
|
||||
public class DesignatedPrizeController : BusinessControllerBase
|
||||
{
|
||||
[HttpGet]
|
||||
public Task<ApiResponse<List<DesignatedPrizeDto>>> GetList(int goodsId);
|
||||
|
||||
[HttpPost]
|
||||
public Task<ApiResponse<DesignatedPrizeDto>> Create(int goodsId, CreateDesignatedPrizeRequest request);
|
||||
|
||||
[HttpPut("{id}")]
|
||||
public Task<ApiResponse<DesignatedPrizeDto>> Update(int goodsId, int id, UpdateDesignatedPrizeRequest request);
|
||||
|
||||
[HttpDelete("{id}")]
|
||||
public Task<ApiResponse> Delete(int goodsId, int id);
|
||||
}
|
||||
```
|
||||
|
||||
## Data Models
|
||||
|
||||
### 数据库表设计
|
||||
|
||||
```sql
|
||||
CREATE TABLE goods_designated_prizes (
|
||||
id INT PRIMARY KEY IDENTITY(1,1),
|
||||
goods_id INT NOT NULL,
|
||||
goods_item_id INT NOT NULL,
|
||||
user_id INT NOT NULL,
|
||||
is_active BIT NOT NULL DEFAULT 1,
|
||||
remark NVARCHAR(200),
|
||||
created_at DATETIME2 NOT NULL DEFAULT GETDATE(),
|
||||
updated_at DATETIME2,
|
||||
|
||||
INDEX IX_designated_goods_id (goods_id),
|
||||
INDEX IX_designated_user_id (user_id),
|
||||
INDEX IX_designated_goods_item (goods_item_id),
|
||||
CONSTRAINT UQ_goods_item_user UNIQUE (goods_id, goods_item_id)
|
||||
);
|
||||
```
|
||||
|
||||
### DTO 模型
|
||||
|
||||
```csharp
|
||||
// 查询响应
|
||||
public class DesignatedPrizeDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int GoodsId { get; set; }
|
||||
public int GoodsItemId { get; set; }
|
||||
public string? GoodsItemTitle { get; set; }
|
||||
public string? GoodsItemImgUrl { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public string? UserNickname { get; set; }
|
||||
public string? UserPhone { get; set; }
|
||||
public bool IsActive { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
public DateTime CreatedAt { get; set; }
|
||||
}
|
||||
|
||||
// 创建请求
|
||||
public class CreateDesignatedPrizeRequest
|
||||
{
|
||||
public int GoodsItemId { get; set; }
|
||||
public int UserId { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
|
||||
// 更新请求
|
||||
public class UpdateDesignatedPrizeRequest
|
||||
{
|
||||
public int? UserId { get; set; }
|
||||
public bool? IsActive { get; set; }
|
||||
public string? Remark { get; set; }
|
||||
}
|
||||
```
|
||||
|
||||
### 抽奖引擎内部数据结构
|
||||
|
||||
```csharp
|
||||
// 指定中奖配置缓存结构
|
||||
// Key: GoodsItemId, Value: UserId
|
||||
private Dictionary<int, int> _designatedPrizes;
|
||||
```
|
||||
|
||||
## 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: Unique Constraint Enforcement
|
||||
*For any* goods and goods_item combination, attempting to create a second designated prize configuration SHALL result in an error, ensuring only one user can be designated per prize.
|
||||
**Validates: Requirements 1.2, 4.5**
|
||||
|
||||
### Property 2: Active Configuration Filtering
|
||||
*For any* query to get designated prize configurations, the result SHALL only contain configurations where is_active is true, regardless of how many inactive configurations exist.
|
||||
**Validates: Requirements 1.3**
|
||||
|
||||
### Property 3: Designated User Prize Pool Inclusion
|
||||
*For any* designated user drawing from either finite or infinite lottery, their prize pool SHALL include all their designated prizes (where they are the designated user and is_active is true).
|
||||
**Validates: Requirements 2.3, 3.4**
|
||||
|
||||
### Property 4: Non-Designated User Prize Pool Exclusion (Finite Lottery)
|
||||
*For any* non-designated user drawing from a finite lottery with normal prizes available, their prize pool SHALL NOT contain any designated prizes.
|
||||
**Validates: Requirements 2.4**
|
||||
|
||||
### Property 5: Non-Designated User Prize Pool Exclusion (Infinite Lottery - Strict)
|
||||
*For any* non-designated user drawing from an infinite lottery, their prize pool SHALL NEVER contain designated prizes, even when no normal prizes exist.
|
||||
**Validates: Requirements 3.3, 3.5**
|
||||
|
||||
### Property 6: Finite Lottery Fallback Mechanism
|
||||
*For any* finite lottery where only designated prizes remain (normal prize pool is empty), a non-designated user SHALL be able to draw from all remaining prizes (fallback mechanism).
|
||||
**Validates: Requirements 2.5**
|
||||
|
||||
### Property 7: Probability Calculation Invariance
|
||||
*For any* prize pool (filtered or unfiltered), the probability calculation method SHALL remain unchanged - probabilities are calculated based on stock (finite) or real_pro (infinite) of the filtered pool, and the sum of probabilities SHALL equal 100%.
|
||||
**Validates: Requirements 5.1, 5.3**
|
||||
|
||||
### Property 8: Prize Data Immutability
|
||||
*For any* designated prize configuration operation (create, update, delete), the underlying prize's stock, real_pro, and other probability-related fields SHALL remain unchanged.
|
||||
**Validates: Requirements 5.2**
|
||||
|
||||
### Property 9: Admin CRUD Validation
|
||||
*For any* admin create operation, the system SHALL validate that goods_id, goods_item_id, and user_id all reference existing records; invalid references SHALL result in an error.
|
||||
**Validates: Requirements 4.1**
|
||||
|
||||
## Error Handling
|
||||
|
||||
### 抽奖引擎错误处理
|
||||
|
||||
| 场景 | 处理方式 | 日志级别 |
|
||||
|------|----------|----------|
|
||||
| 指定中奖配置查询失败 | 降级为无配置模式,继续抽奖 | Warning |
|
||||
| 有限赏兜底机制触发 | 记录日志,允许抽奖继续 | Warning |
|
||||
| 无限赏奖品池为空(全是指定奖品) | 返回"暂无可抽奖品"错误 | Warning |
|
||||
| 数据库连接异常 | 返回"系统繁忙"错误 | Error |
|
||||
|
||||
### 后台管理 API 错误处理
|
||||
|
||||
| 场景 | HTTP 状态码 | 错误消息 |
|
||||
|------|-------------|----------|
|
||||
| 盒子不存在 | 404 | "盒子不存在" |
|
||||
| 奖品不存在 | 404 | "奖品不存在" |
|
||||
| 用户不存在 | 404 | "用户不存在" |
|
||||
| 重复配置 | 400 | "该奖品已配置指定用户" |
|
||||
| 配置不存在 | 404 | "配置不存在" |
|
||||
| 权限不足 | 403 | "无权限操作" |
|
||||
|
||||
### 错误处理代码示例
|
||||
|
||||
```csharp
|
||||
// 抽奖引擎中的降级处理
|
||||
private async Task<Dictionary<int, int>> GetDesignatedPrizesAsync(int goodsId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _dbContext.GoodsDesignatedPrizes
|
||||
.Where(dp => dp.GoodsId == goodsId && dp.IsActive)
|
||||
.ToDictionaryAsync(dp => dp.GoodsItemId, dp => dp.UserId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to query designated prizes for goods {GoodsId}, falling back to no configuration", goodsId);
|
||||
return new Dictionary<int, int>();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### 测试框架选择
|
||||
|
||||
- **单元测试框架**: xUnit
|
||||
- **属性测试框架**: FsCheck (C# 属性测试库)
|
||||
- **Mock 框架**: Moq
|
||||
- **集成测试**: Microsoft.AspNetCore.Mvc.Testing
|
||||
|
||||
### 单元测试覆盖
|
||||
|
||||
1. **GoodsDesignatedPrize 实体测试**
|
||||
- 字段默认值验证
|
||||
- 导航属性关联
|
||||
|
||||
2. **DesignatedPrizeService 测试**
|
||||
- CRUD 操作正确性
|
||||
- 验证逻辑(存在性检查)
|
||||
- 唯一约束处理
|
||||
|
||||
3. **LotteryEngine 奖品池过滤测试**
|
||||
- 有限赏过滤逻辑(带兜底)
|
||||
- 无限赏过滤逻辑(严格)
|
||||
- 边界情况(空池、全指定等)
|
||||
|
||||
### 属性测试配置
|
||||
|
||||
每个属性测试运行 **100 次迭代**,使用 FsCheck 生成随机测试数据。
|
||||
|
||||
```csharp
|
||||
// 属性测试示例配置
|
||||
[Property(MaxTest = 100)]
|
||||
public Property DesignatedUserSeesTheirPrizes()
|
||||
{
|
||||
// Feature: designated-prize-winner, Property 3: Designated User Prize Pool Inclusion
|
||||
return Prop.ForAll(
|
||||
Arb.From<int>(), // userId
|
||||
Arb.From<List<GoodsItem>>(), // prizePool
|
||||
Arb.From<Dictionary<int, int>>(), // designatedPrizes
|
||||
(userId, prizePool, designatedPrizes) =>
|
||||
{
|
||||
// Test implementation
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
### 测试数据生成策略
|
||||
|
||||
```csharp
|
||||
// 奖品池生成器
|
||||
public static Arbitrary<List<GoodsItem>> GoodsItemListArbitrary()
|
||||
{
|
||||
return Gen.ListOf(Gen.Fresh(() => new GoodsItem
|
||||
{
|
||||
Id = Gen.Choose(1, 1000).Sample(0, 1).First(),
|
||||
SurplusStock = Gen.Choose(0, 100).Sample(0, 1).First(),
|
||||
RealPro = Gen.Choose(0, 100).Sample(0, 1).First()
|
||||
})).ToArbitrary();
|
||||
}
|
||||
|
||||
// 指定中奖配置生成器
|
||||
public static Arbitrary<Dictionary<int, int>> DesignatedPrizesArbitrary()
|
||||
{
|
||||
return Gen.DictionaryOf(
|
||||
Gen.Choose(1, 1000), // GoodsItemId
|
||||
Gen.Choose(1, 10000) // UserId
|
||||
).ToArbitrary();
|
||||
}
|
||||
```
|
||||
|
||||
### 集成测试场景
|
||||
|
||||
1. **有限赏完整流程测试**
|
||||
- 指定用户抽中指定奖品
|
||||
- 普通用户抽不到指定奖品
|
||||
- 兜底机制触发场景
|
||||
|
||||
2. **无限赏完整流程测试**
|
||||
- 指定用户抽中指定奖品
|
||||
- 普通用户永远抽不到指定奖品
|
||||
|
||||
3. **后台管理 API 测试**
|
||||
- 完整 CRUD 流程
|
||||
- 并发创建重复配置
|
||||
- 权限验证
|
||||
|
||||
76
.kiro/specs/designated-prize-winner/requirements.md
Normal file
76
.kiro/specs/designated-prize-winner/requirements.md
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
# Requirements Document
|
||||
|
||||
## Introduction
|
||||
|
||||
指定用户中奖功能允许后台管理员为每个盒子配置"指定用户中奖"规则,使某个奖品只能由指定用户抽中。该功能不修改奖品本身的概率,指定用户仍需按概率抽奖,但非指定用户无法抽中该奖品。
|
||||
|
||||
## Glossary
|
||||
|
||||
- **Goods**: 盒子/商品,包含多个奖品的抽奖容器
|
||||
- **GoodsItem**: 奖品,盒子中的单个可抽取物品
|
||||
- **DesignatedPrize**: 指定中奖配置,将特定奖品指定给特定用户
|
||||
- **LotteryEngine**: 抽奖引擎,负责执行抽奖逻辑的核心服务
|
||||
- **FiniteLottery**: 有限赏抽奖,基于库存概率的抽奖模式(一番赏、福袋等),抽完即止
|
||||
- **InfiniteLottery**: 无限赏抽奖,基于固定概率的抽奖模式(无限赏、领主赏等),无限池
|
||||
- **PrizePool**: 奖品池,当前可抽取的奖品集合
|
||||
- **FallbackMechanism**: 兜底机制,当普通奖品池为空时允许普通用户抽取指定奖品
|
||||
|
||||
## Requirements
|
||||
|
||||
### Requirement 1: 指定中奖配置数据管理
|
||||
|
||||
**User Story:** As a 后台管理员, I want to 为盒子中的奖品配置指定中奖用户, so that 特定奖品只能被指定用户抽中。
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. THE GoodsDesignatedPrize Entity SHALL store goods_id, goods_item_id, user_id, is_active, remark, created_at, and updated_at fields
|
||||
2. WHEN a designated prize configuration is created, THE System SHALL enforce that one prize can only be designated to one user per goods (unique constraint on goods_id + goods_item_id)
|
||||
3. WHEN a designated prize configuration is queried, THE System SHALL only return active configurations (is_active = true)
|
||||
4. THE System SHALL support enabling and disabling designated prize configurations via the is_active field
|
||||
|
||||
### Requirement 2: 有限赏抽奖流程改造(带兜底机制)
|
||||
|
||||
**User Story:** As a 用户, I want to 在有限赏抽奖时遵循指定中奖规则, so that 指定奖品优先给指定用户,但保证每抽必中。
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN a user draws from a finite lottery, THE LotteryEngine SHALL query designated prize configurations for the goods
|
||||
2. WHEN designated prize configurations exist, THE LotteryEngine SHALL separate the prize pool into normal prizes and designated prizes
|
||||
3. WHEN the current user is a designated user for any prize, THE LotteryEngine SHALL include that user's designated prizes in their prize pool
|
||||
4. WHEN the current user is not a designated user, THE LotteryEngine SHALL exclude designated prizes from their prize pool
|
||||
5. IF the normal prize pool is empty (only designated prizes remain), THEN THE LotteryEngine SHALL allow the user to draw from all prizes (fallback mechanism)
|
||||
6. WHEN the fallback mechanism is triggered, THE LotteryEngine SHALL log a warning for operational tracking
|
||||
|
||||
### Requirement 3: 无限赏抽奖流程改造(严格过滤)
|
||||
|
||||
**User Story:** As a 用户, I want to 在无限赏抽奖时遵循指定中奖规则, so that 指定奖品只能被指定用户抽中。
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN a user draws from an infinite lottery, THE LotteryEngine SHALL query designated prize configurations for the goods
|
||||
2. WHEN designated prize configurations exist, THE LotteryEngine SHALL strictly filter the prize pool
|
||||
3. WHEN a prize has a designated user configuration and the current user is not the designated user, THE LotteryEngine SHALL remove that prize from the pool
|
||||
4. WHEN a prize has a designated user configuration and the current user is the designated user, THE LotteryEngine SHALL keep that prize in the pool
|
||||
5. THE InfiniteLottery SHALL NOT have a fallback mechanism (designated prizes are strictly protected)
|
||||
|
||||
### Requirement 4: 后台管理 API
|
||||
|
||||
**User Story:** As a 后台管理员, I want to 通过 API 管理指定中奖配置, so that 我可以灵活配置和调整指定中奖规则。
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN an admin creates a designated prize configuration, THE System SHALL validate that the goods_id, goods_item_id, and user_id exist
|
||||
2. WHEN an admin queries designated prize configurations for a goods, THE System SHALL return all configurations with user information
|
||||
3. WHEN an admin updates a designated prize configuration, THE System SHALL update the is_active, user_id, or remark fields
|
||||
4. WHEN an admin deletes a designated prize configuration, THE System SHALL remove the configuration from the database
|
||||
5. IF a duplicate configuration is attempted (same goods_id and goods_item_id), THEN THE System SHALL return an error message
|
||||
|
||||
### Requirement 5: 概率保持不变
|
||||
|
||||
**User Story:** As a 产品经理, I want to 确保指定中奖不改变奖品概率, so that 抽奖公平性得到保证。
|
||||
|
||||
#### Acceptance Criteria
|
||||
|
||||
1. WHEN calculating prize probabilities, THE LotteryEngine SHALL use the original probability calculation method
|
||||
2. THE DesignatedPrize configuration SHALL NOT modify the prize's stock, real_pro, or any probability-related fields
|
||||
3. WHEN a designated user draws, THE LotteryEngine SHALL calculate probabilities based on the filtered prize pool
|
||||
151
.kiro/specs/designated-prize-winner/tasks.md
Normal file
151
.kiro/specs/designated-prize-winner/tasks.md
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
# Implementation Plan: 指定用户中奖功能
|
||||
|
||||
## Overview
|
||||
|
||||
本实现计划将"指定用户中奖"功能分解为可执行的编码任务。实现顺序为:数据层 → 抽奖核心层 → 后台管理层,确保每个步骤都能独立验证。
|
||||
|
||||
## Tasks
|
||||
|
||||
- [-] 1. 数据层实现
|
||||
- [ ] 1.1 创建 GoodsDesignatedPrize 实体类
|
||||
- 在 `HoneyBox.Model/Entities/` 目录创建 `GoodsDesignatedPrize.cs`
|
||||
- 包含 Id, GoodsId, GoodsItemId, UserId, IsActive, Remark, CreatedAt, UpdatedAt 字段
|
||||
- 添加导航属性 Goods, GoodsItem, User
|
||||
- _Requirements: 1.1_
|
||||
|
||||
- [ ] 1.2 更新 HoneyBoxDbContext
|
||||
- 添加 `DbSet<GoodsDesignatedPrize> GoodsDesignatedPrizes` 属性
|
||||
- 在 `OnModelCreating` 中配置实体映射
|
||||
- 配置唯一约束 (goods_id, goods_item_id)
|
||||
- 配置索引 (goods_id, user_id, goods_item_id)
|
||||
- _Requirements: 1.1, 1.2_
|
||||
|
||||
- [ ] 1.3 创建数据库迁移 SQL 脚本
|
||||
- 在 `server/HoneyBox/scripts/` 目录创建 `create_goods_designated_prizes.sql`
|
||||
- 包含建表语句、索引、唯一约束
|
||||
- _Requirements: 1.1, 1.2_
|
||||
|
||||
- [ ] 2. 抽奖引擎核心改造
|
||||
- [ ] 2.1 添加指定中奖配置查询方法
|
||||
- 在 `LotteryEngine.cs` 中添加 `GetDesignatedPrizesAsync(int goodsId)` 私有方法
|
||||
- 返回 `Dictionary<int, int>` (GoodsItemId → UserId)
|
||||
- 只查询 is_active = true 的配置
|
||||
- 添加异常降级处理(查询失败返回空字典)
|
||||
- _Requirements: 2.1, 3.1, 1.3_
|
||||
|
||||
- [ ] 2.2 实现有限赏奖品池过滤方法(带兜底)
|
||||
- 添加 `FilterPrizePoolWithFallback(prizePool, userId, designatedPrizes)` 私有方法
|
||||
- 分离普通奖品和指定奖品
|
||||
- 指定用户:返回普通奖品 + 自己的指定奖品
|
||||
- 普通用户:优先返回普通奖品池
|
||||
- 兜底:普通奖品池为空时返回全部奖品池
|
||||
- 兜底触发时记录 Warning 日志
|
||||
- _Requirements: 2.2, 2.3, 2.4, 2.5, 2.6_
|
||||
|
||||
- [ ] 2.3 编写有限赏过滤方法的属性测试
|
||||
- **Property 3: Designated User Prize Pool Inclusion**
|
||||
- **Property 4: Non-Designated User Prize Pool Exclusion (Finite)**
|
||||
- **Property 6: Finite Lottery Fallback Mechanism**
|
||||
- **Validates: Requirements 2.3, 2.4, 2.5**
|
||||
|
||||
- [ ] 2.4 实现无限赏奖品池过滤方法(严格)
|
||||
- 添加 `FilterPrizePoolStrict(prizePool, userId, designatedPrizes)` 私有方法
|
||||
- 移除所有非当前用户的指定奖品
|
||||
- 保留当前用户的指定奖品
|
||||
- 无兜底机制
|
||||
- _Requirements: 3.2, 3.3, 3.4, 3.5_
|
||||
|
||||
- [ ] 2.5 编写无限赏过滤方法的属性测试
|
||||
- **Property 5: Non-Designated User Prize Pool Exclusion (Infinite - Strict)**
|
||||
- **Validates: Requirements 3.3, 3.5**
|
||||
|
||||
- [ ] 2.6 改造 DrawAsync 方法(有限赏)
|
||||
- 在获取奖品池后调用 `GetDesignatedPrizesAsync`
|
||||
- 如果有配置,调用 `FilterPrizePoolWithFallback` 过滤奖品池
|
||||
- 保持后续概率计算和抽奖逻辑不变
|
||||
- _Requirements: 2.1, 2.2, 5.1, 5.3_
|
||||
|
||||
- [ ] 2.7 改造 DrawInfiniteAsync 方法(无限赏)
|
||||
- 在获取奖品池后调用 `GetDesignatedPrizesAsync`
|
||||
- 如果有配置,调用 `FilterPrizePoolStrict` 过滤奖品池
|
||||
- 保持后续概率计算和抽奖逻辑不变
|
||||
- _Requirements: 3.1, 3.2, 5.1, 5.3_
|
||||
|
||||
- [ ] 2.8 编写概率计算不变性属性测试
|
||||
- **Property 7: Probability Calculation Invariance**
|
||||
- **Validates: Requirements 5.1, 5.3**
|
||||
|
||||
- [ ] 3. Checkpoint - 核心功能验证
|
||||
- 确保所有测试通过,如有问题请询问用户
|
||||
|
||||
- [ ] 4. 后台管理服务层实现
|
||||
- [ ] 4.1 创建 DTO 模型
|
||||
- 在 `HoneyBox.Admin.Business/Models/DesignatedPrize/` 目录创建:
|
||||
- `DesignatedPrizeDto.cs` - 查询响应
|
||||
- `CreateDesignatedPrizeRequest.cs` - 创建请求
|
||||
- `UpdateDesignatedPrizeRequest.cs` - 更新请求
|
||||
- _Requirements: 4.2_
|
||||
|
||||
- [ ] 4.2 创建 IDesignatedPrizeService 接口
|
||||
- 在 `HoneyBox.Admin.Business/Services/Interfaces/` 目录创建接口
|
||||
- 定义 GetByGoodsIdAsync, CreateAsync, UpdateAsync, DeleteAsync 方法
|
||||
- _Requirements: 4.1, 4.2, 4.3, 4.4_
|
||||
|
||||
- [ ] 4.3 实现 DesignatedPrizeService 服务
|
||||
- 在 `HoneyBox.Admin.Business/Services/` 目录创建实现类
|
||||
- 实现 GetByGoodsIdAsync:查询配置列表,关联用户和奖品信息
|
||||
- 实现 CreateAsync:验证存在性,检查唯一约束,创建配置
|
||||
- 实现 UpdateAsync:更新 is_active, user_id, remark 字段
|
||||
- 实现 DeleteAsync:删除配置
|
||||
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
|
||||
|
||||
- [ ] 4.4 编写服务层单元测试
|
||||
- 测试 CRUD 操作正确性
|
||||
- 测试验证逻辑(存在性检查)
|
||||
- 测试唯一约束处理
|
||||
- _Requirements: 4.1, 4.5_
|
||||
|
||||
- [ ] 4.5 编写唯一约束属性测试
|
||||
- **Property 1: Unique Constraint Enforcement**
|
||||
- **Validates: Requirements 1.2, 4.5**
|
||||
|
||||
- [ ] 5. 后台管理 API 层实现
|
||||
- [ ] 5.1 创建 DesignatedPrizeController 控制器
|
||||
- 在 `HoneyBox.Admin.Business/Controllers/` 目录创建控制器
|
||||
- 路由:`api/admin/goods/{goodsId}/designated-prizes`
|
||||
- 实现 GET(列表)、POST(创建)、PUT(更新)、DELETE(删除)端点
|
||||
- 添加权限验证
|
||||
- _Requirements: 4.1, 4.2, 4.3, 4.4_
|
||||
|
||||
- [ ] 5.2 注册服务到依赖注入容器
|
||||
- 在 `ServiceCollectionExtensions.cs` 中注册 IDesignatedPrizeService
|
||||
- _Requirements: 4.1_
|
||||
|
||||
- [ ] 5.3 编写 API 集成测试
|
||||
- 测试完整 CRUD 流程
|
||||
- 测试错误响应(404, 400)
|
||||
- _Requirements: 4.1, 4.2, 4.3, 4.4, 4.5_
|
||||
|
||||
- [ ] 6. Checkpoint - 后台管理功能验证
|
||||
- 确保所有测试通过,如有问题请询问用户
|
||||
|
||||
- [ ] 7. 集成测试和文档
|
||||
- [ ] 7.1 编写端到端集成测试
|
||||
- 有限赏完整流程:配置指定中奖 → 指定用户抽奖 → 验证结果
|
||||
- 无限赏完整流程:配置指定中奖 → 普通用户抽奖 → 验证无法抽中
|
||||
- 兜底机制测试:只剩指定奖品时普通用户抽奖
|
||||
- _Requirements: 2.3, 2.4, 2.5, 3.3, 3.4, 3.5_
|
||||
|
||||
- [ ] 7.2 编写奖品数据不可变性属性测试
|
||||
- **Property 8: Prize Data Immutability**
|
||||
- **Validates: Requirements 5.2**
|
||||
|
||||
- [ ] 8. Final Checkpoint - 完整功能验证
|
||||
- 确保所有测试通过,如有问题请询问用户
|
||||
|
||||
## Notes
|
||||
|
||||
- 每个任务都引用了具体的需求条款以确保可追溯性
|
||||
- 检查点用于增量验证,确保每个阶段的正确性
|
||||
- 属性测试验证通用正确性属性,单元测试验证具体示例和边界情况
|
||||
- 所有测试任务均为必需,确保全面的测试覆盖
|
||||
Loading…
Reference in New Issue
Block a user