HaniBlindBox/docs/API迁移详细文档/阶段7-抽奖逻辑.md
2026-01-02 15:46:56 +08:00

844 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 阶段7抽奖逻辑
## 阶段概述
**时间**: 3周
**目标**: 实现完整的抽奖系统,包括概率计算、随机抽奖、奖品发放、库存管理等核心功能
**优先级**: P2 (中高优先级)
## 详细任务清单
### 7.1 抽奖算法引擎 (5天)
#### 任务描述
实现核心的抽奖算法引擎,支持多种抽奖模式和概率计算
#### 具体工作
- [ ] 设计抽奖算法接口
- [ ] 实现权重随机算法
- [ ] 实现概率计算引擎
- [ ] 实现随机数生成器
- [ ] 实现抽奖结果验证
- [ ] 添加抽奖日志记录
#### 核心算法实现
##### 抽奖引擎接口
```csharp
// 抽奖引擎接口
public interface ILotteryEngine
{
Task<LotteryResult> DrawAsync(LotteryRequest request);
Task<List<LotteryResult>> DrawMultipleAsync(LotteryRequest request, int count);
Task<ProbabilityInfo> CalculateProbabilityAsync(int goodsId, int goodsNum);
Task<bool> ValidateDrawResultAsync(LotteryResult result);
}
// 抽奖请求
public class LotteryRequest
{
public int UserId { get; set; }
public int GoodsId { get; set; }
public int GoodsNum { get; set; }
public int DrawCount { get; set; }
public string OrderNo { get; set; }
public LotteryMode Mode { get; set; } = LotteryMode.Normal;
}
// 抽奖结果
public class LotteryResult
{
public int GoodsListId { get; set; }
public string PrizeTitle { get; set; }
public string PrizeImage { get; set; }
public int ShangId { get; set; }
public string ShangTitle { get; set; }
public string ShangColor { get; set; }
public decimal PrizeValue { get; set; }
public bool IsSpecialPrize { get; set; }
public DateTime DrawTime { get; set; }
public string DrawId { get; set; }
}
// 抽奖模式
public enum LotteryMode
{
Normal = 1, // 普通抽奖
Guarantee = 2, // 保底抽奖
Special = 3 // 特殊抽奖
}
```
##### 权重随机算法
```csharp
public class WeightedRandomSelector
{
private readonly Random _random;
public WeightedRandomSelector()
{
_random = new Random(Guid.NewGuid().GetHashCode());
}
public T SelectItem<T>(List<WeightedItem<T>> items) where T : class
{
if (!items.Any() || items.All(i => i.Weight <= 0))
{
return null;
}
var totalWeight = items.Sum(i => i.Weight);
var randomValue = _random.NextDouble() * totalWeight;
double currentWeight = 0;
foreach (var item in items)
{
currentWeight += item.Weight;
if (randomValue <= currentWeight)
{
return item.Item;
}
}
return items.Last().Item; // 兜底返回最后一个
}
}
public class WeightedItem<T>
{
public T Item { get; set; }
public double Weight { get; set; }
public double Probability { get; set; }
}
```
##### 抽奖引擎实现
```csharp
public class LotteryEngine : ILotteryEngine
{
private readonly ILogger<LotteryEngine> _logger;
private readonly WeightedRandomSelector _selector;
private readonly IRedisService _redis;
public async Task<LotteryResult> DrawAsync(LotteryRequest request)
{
// 1. 获取奖品池
var prizePool = await GetPrizePoolAsync(request.GoodsId, request.GoodsNum);
if (!prizePool.Any())
{
throw new BusinessException("奖品池为空");
}
// 2. 过滤可抽奖品(库存>0
var availablePrizes = prizePool.Where(p => p.SurplusStock > 0).ToList();
if (!availablePrizes.Any())
{
throw new BusinessException("暂无可抽奖品");
}
// 3. 构建权重列表
var weightedItems = availablePrizes.Select(prize => new WeightedItem<GoodsList>
{
Item = prize,
Weight = CalculateWeight(prize),
Probability = CalculateProbability(prize, availablePrizes)
}).ToList();
// 4. 执行抽奖
var selectedPrize = _selector.SelectItem(weightedItems);
if (selectedPrize == null)
{
throw new BusinessException("抽奖失败");
}
// 5. 扣减库存
await DeductInventoryAsync(selectedPrize.Id);
// 6. 记录抽奖日志
var drawId = await RecordDrawLogAsync(request, selectedPrize);
// 7. 构建返回结果
return new LotteryResult
{
GoodsListId = selectedPrize.Id,
PrizeTitle = selectedPrize.Title,
PrizeImage = selectedPrize.ImgUrl,
ShangId = selectedPrize.ShangId,
ShangTitle = selectedPrize.ShangInfo?.Title,
ShangColor = selectedPrize.ShangInfo?.Color,
PrizeValue = selectedPrize.Price,
IsSpecialPrize = IsSpecialPrize(selectedPrize.ShangId),
DrawTime = DateTime.Now,
DrawId = drawId
};
}
private double CalculateWeight(GoodsList prize)
{
// 基础权重 = 剩余库存
var baseWeight = prize.SurplusStock;
// 特殊奖品权重调整
if (IsSpecialPrize(prize.ShangId))
{
baseWeight *= 0.1; // 特殊奖品权重降低
}
return Math.Max(baseWeight, 0.001); // 确保最小权重
}
private double CalculateProbability(GoodsList prize, List<GoodsList> allPrizes)
{
var totalStock = allPrizes.Sum(p => p.SurplusStock);
if (totalStock == 0) return 0;
return (double)prize.SurplusStock / totalStock * 100;
}
}
```
### 7.2 抽奖接口实现 (4天)
#### 任务描述
实现抽奖相关的API接口支持多种抽奖类型
#### 具体工作
- [ ] 实现一番赏抽奖结果接口
- [ ] 实现无限赏抽奖结果接口
- [ ] 实现一番赏中奖记录接口
- [ ] 实现无限赏中奖记录接口
- [ ] 实现每日抽奖记录接口
- [ ] 实现道具卡抽奖接口
- [ ] 添加抽奖限制验证
#### 核心接口实现
##### 单次抽奖接口
```http
POST /api/v1/lottery/draw
Authorization: Bearer {token}
Content-Type: application/json
Request:
{
"order_no": "ORD202401010001",
"goods_id": 123,
"goods_num": 1,
"draw_count": 1
}
Response:
{
"status": 1,
"msg": "抽奖成功",
"data": {
"draw_id": "DRAW202401010001",
"results": [
{
"goodslist_id": 456,
"prize_title": "奖品标题",
"prize_image": "https://example.com/prize.jpg",
"shang_id": 10,
"shang_title": "A赏",
"shang_color": "#FF0000",
"prize_value": "100.00",
"is_special_prize": true,
"draw_time": "2024-01-01T12:00:00Z"
}
],
"animation_data": {
"duration": 3000,
"effects": ["sparkle", "confetti"]
}
}
}
```
##### 多次抽奖接口
```http
POST /api/v1/lottery/draw-multiple
Authorization: Bearer {token}
Content-Type: application/json
Request:
{
"order_no": "ORD202401010001",
"goods_id": 123,
"goods_num": 1,
"draw_count": 5
}
Response:
{
"status": 1,
"msg": "抽奖成功",
"data": {
"draw_id": "DRAW202401010001",
"results": [
{
"goodslist_id": 456,
"prize_title": "奖品A",
"prize_image": "https://example.com/prize1.jpg",
"shang_id": 10,
"shang_title": "A赏",
"shang_color": "#FF0000",
"prize_value": "100.00",
"is_special_prize": true,
"draw_time": "2024-01-01T12:00:00Z"
},
{
"goodslist_id": 457,
"prize_title": "奖品B",
"prize_image": "https://example.com/prize2.jpg",
"shang_id": 20,
"shang_title": "B赏",
"shang_color": "#00FF00",
"prize_value": "50.00",
"is_special_prize": false,
"draw_time": "2024-01-01T12:00:01Z"
}
],
"summary": {
"total_value": "150.00",
"special_count": 1,
"normal_count": 4
}
}
}
```
##### 一番赏抽奖结果接口
```http
POST /api/v1/lottery/prize-log
Authorization: Bearer {token}
Request:
{
"order_num": "202401010001"
}
Response:
{
"status": 1,
"msg": "请求成功",
"data": [
{
"id": 20001,
"order_id": 10001,
"goodslist_title": "限定手办A",
"goodslist_imgurl": "https://example.com/prize.jpg",
"goodslist_price": "299.00",
"goodslist_money": "150.00",
"goodslist_type": 1,
"status": 0,
"addtime": 1640995300,
"prize_code": "A001",
"luck_no": 1
}
]
}
```
##### 无限赏抽奖结果接口
```http
POST /api/v1/lottery/infinite/prize-log
Authorization: Bearer {token}
Request:
{
"order_num": "202401010001"
}
Response:
{
"status": 1,
"msg": "请求成功",
"data": []
}
```
##### 一番赏中奖记录接口
```http
POST /api/v1/lottery/shang-log
Authorization: Bearer {token}
Request:
{
"goods_id": 1001,
"num": 0,
"page": 1
}
Response:
{
"status": 1,
"msg": "请求成功",
"data": {
"data": [
{
"user_nickname": "用户***",
"goodslist_title": "限定手办A",
"addtime": "2024-01-01 10:30:00",
"luck_no": 1
}
]
}
}
```
##### 无限赏中奖记录接口
```http
POST /api/v1/lottery/infinite/shang-log
Authorization: Bearer {token}
Request:
{
"goods_id": 1001,
"page": 1
}
Response:
{
"status": 1,
"msg": "请求成功",
"data": {
"data": []
}
}
```
##### 每日抽奖记录接口
```http
POST /api/v1/lottery/infinite/daily-records
Authorization: Bearer {token}
Request:
{
"goods_id": 1001
}
Response:
{
"status": 1,
"msg": "请求成功",
"data": []
}
```
##### 道具卡抽奖接口
```http
POST /api/v1/lottery/item-card
Authorization: Bearer {token}
Request:
{
"goods_id": 1001,
"item_card_id": 1
}
Response:
{
"status": 1,
"msg": "抽奖成功",
"data": {
"prize": {}
}
}
```
##### 抽奖历史接口
```http
GET /api/v1/user/lottery-history
Authorization: Bearer {token}
Query Parameters:
- goods_id: 商品ID可选
- page: 页码
- limit: 每页数量
Response:
{
"status": 1,
"msg": "请求成功",
"data": {
"data": [
{
"draw_id": "DRAW202401010001",
"order_no": "ORD202401010001",
"goods_title": "商品标题",
"goods_num": 1,
"draw_count": 5,
"total_value": "150.00",
"best_prize": {
"prize_title": "最佳奖品",
"shang_title": "A赏",
"prize_value": "100.00"
},
"draw_time": "2024-01-01T12:00:00Z"
}
],
"last_page": 10,
"statistics": {
"total_draws": 100,
"total_value": "5000.00",
"special_count": 5
}
}
}
```
### 7.3 特殊抽奖机制 (4天)
#### 任务描述
实现特殊抽奖机制,如保底、连击、全局奖等
#### 具体工作
- [ ] 实现保底抽奖机制
- [ ] 实现连击抽奖逻辑
- [ ] 实现全局奖触发
- [ ] 实现特殊奖品发放
- [ ] 实现抽奖事件通知
#### 特殊机制实现
##### 保底抽奖机制
```csharp
public class GuaranteeService
{
public async Task<bool> CheckGuaranteeAsync(int userId, int goodsId)
{
// 检查用户在该商品的抽奖次数
var drawCount = await GetUserDrawCountAsync(userId, goodsId);
var guaranteeConfig = await GetGuaranteeConfigAsync(goodsId);
return drawCount >= guaranteeConfig.RequiredDraws;
}
public async Task<LotteryResult> ExecuteGuaranteeDrawAsync(LotteryRequest request)
{
// 获取保底奖品池
var guaranteePrizes = await GetGuaranteePrizesAsync(request.GoodsId);
// 执行保底抽奖
var result = await _lotteryEngine.DrawFromPoolAsync(guaranteePrizes);
// 重置保底计数
await ResetGuaranteeCountAsync(request.UserId, request.GoodsId);
return result;
}
}
```
##### 连击抽奖逻辑
```csharp
public class ComboService
{
public async Task<ComboInfo> CheckComboAsync(int userId, int goodsId)
{
var recentDraws = await GetRecentDrawsAsync(userId, goodsId, TimeSpan.FromMinutes(5));
return new ComboInfo
{
ComboCount = recentDraws.Count,
ComboMultiplier = CalculateComboMultiplier(recentDraws.Count),
NextComboThreshold = GetNextComboThreshold(recentDraws.Count)
};
}
private double CalculateComboMultiplier(int comboCount)
{
return comboCount switch
{
>= 10 => 2.0,
>= 5 => 1.5,
>= 3 => 1.2,
_ => 1.0
};
}
}
```
##### 全局奖触发机制
```csharp
public class GlobalPrizeService
{
public async Task<bool> CheckGlobalPrizeTriggerAsync(int goodsId)
{
var config = await GetGlobalPrizeConfigAsync(goodsId);
var currentCount = await GetGlobalDrawCountAsync(goodsId);
return currentCount >= config.TriggerCount;
}
public async Task<List<int>> SelectGlobalPrizeWinnersAsync(int goodsId, int prizeCount)
{
// 获取最近参与抽奖的用户
var recentUsers = await GetRecentDrawUsersAsync(goodsId, TimeSpan.FromHours(24));
// 随机选择获奖用户
return recentUsers.OrderBy(x => Guid.NewGuid()).Take(prizeCount).ToList();
}
}
```
### 7.4 抽奖动画和特效 (3天)
#### 任务描述
实现抽奖动画配置和特效数据
#### 具体工作
- [ ] 设计抽奖动画配置
- [ ] 实现动画参数计算
- [ ] 实现特效触发逻辑
- [ ] 实现音效配置
- [ ] 添加动画预设模板
#### 动画配置实现
##### 抽奖动画配置接口
```http
GET /api/v1/lottery/animation-config/{goodsId}
Authorization: Bearer {token}
Response:
{
"status": 1,
"msg": "请求成功",
"data": {
"basic_animation": {
"duration": 3000,
"easing": "ease-in-out",
"rotation_speed": 360
},
"special_effects": {
"sparkle": {
"enabled": true,
"particle_count": 50,
"colors": ["#FFD700", "#FFA500"]
},
"confetti": {
"enabled": true,
"fall_speed": 2,
"colors": ["#FF0000", "#00FF00", "#0000FF"]
}
},
"sound_effects": {
"draw_start": "https://example.com/sounds/draw_start.mp3",
"draw_end": "https://example.com/sounds/draw_end.mp3",
"special_prize": "https://example.com/sounds/special.mp3"
},
"prize_animations": {
"A赏": {
"glow_color": "#FF0000",
"shake_intensity": 5,
"zoom_scale": 1.2
},
"B赏": {
"glow_color": "#00FF00",
"shake_intensity": 3,
"zoom_scale": 1.1
}
}
}
}
```
### 7.5 抽奖统计和分析 (3天)
#### 任务描述
实现抽奖数据统计和分析功能
#### 具体工作
- [ ] 实现抽奖统计接口
- [ ] 实现概率分析功能
- [ ] 实现热门奖品统计
- [ ] 实现用户抽奖行为分析
- [ ] 实现抽奖报表生成
#### 统计分析接口
##### 抽奖统计接口
```http
GET /api/v1/lottery/statistics
Authorization: Bearer {token}
Query Parameters:
- goods_id: 商品ID
- start_date: 开始日期
- end_date: 结束日期
- type: 统计类型 (daily, weekly, monthly)
Response:
{
"status": 1,
"msg": "请求成功",
"data": {
"summary": {
"total_draws": 10000,
"total_users": 1000,
"total_prizes": 8500,
"avg_draws_per_user": 10
},
"prize_distribution": [
{
"shang_id": 10,
"shang_title": "A赏",
"count": 50,
"percentage": 0.5,
"total_value": "5000.00"
}
],
"daily_trends": [
{
"date": "2024-01-01",
"draws": 500,
"users": 100,
"prizes": 425
}
],
"hot_prizes": [
{
"prize_title": "热门奖品",
"draw_count": 200,
"win_rate": 15.5
}
]
}
}
```
## 抽奖数据模型
### 抽奖记录表
```csharp
public class LotteryRecord
{
public int Id { get; set; }
public string DrawId { get; set; }
public string OrderNo { get; set; }
public int UserId { get; set; }
public int GoodsId { get; set; }
public int GoodsNum { get; set; }
public int GoodsListId { get; set; }
public int ShangId { get; set; }
public int DrawType { get; set; }
public bool IsSpecialPrize { get; set; }
public decimal PrizeValue { get; set; }
public string DrawData { get; set; }
public DateTime DrawTime { get; set; }
}
public class LotteryConfig
{
public int Id { get; set; }
public int GoodsId { get; set; }
public string ConfigType { get; set; }
public string ConfigData { get; set; }
public bool IsEnabled { get; set; }
public DateTime CreateTime { get; set; }
public DateTime UpdateTime { get; set; }
}
```
## 抽奖安全机制
### 防作弊机制
```csharp
public class LotterySecurityService
{
// 检查抽奖频率
public async Task<bool> CheckDrawFrequencyAsync(int userId, int goodsId)
{
var recentDraws = await GetRecentDrawsAsync(userId, goodsId, TimeSpan.FromMinutes(1));
return recentDraws.Count <= 10; // 每分钟最多10次
}
// 检查异常行为
public async Task<bool> DetectAbnormalBehaviorAsync(int userId)
{
var userDraws = await GetUserDrawsAsync(userId, TimeSpan.FromHours(24));
// 检查是否有异常高的中奖率
var winRate = (double)userDraws.Count(d => d.IsWin) / userDraws.Count;
if (winRate > 0.8) // 中奖率超过80%
{
await LogSuspiciousActivityAsync(userId, "异常高中奖率");
return false;
}
return true;
}
// 随机数验证
public bool ValidateRandomSeed(string drawId, long timestamp, string seed)
{
var expectedSeed = GenerateExpectedSeed(drawId, timestamp);
return seed == expectedSeed;
}
}
```
## 验收标准
### 功能验收
- [ ] 抽奖算法准确性验证通过
- [ ] 概率计算正确性验证通过
- [ ] 特殊抽奖机制正常工作
- [ ] 抽奖接口响应正常
- [ ] 抽奖统计数据准确
### 性能验收
- [ ] 单次抽奖响应时间 < 500ms
- [ ] 多次抽奖响应时间 < 1000ms
- [ ] 支持并发抽奖 > 100 QPS
- [ ] 概率计算准确率 100%
### 安全验收
- [ ] 防作弊机制有效
- [ ] 随机数生成安全
- [ ] 抽奖结果不可预测
- [ ] 异常行为检测正常
### 业务验收
- [ ] 抽奖概率符合配置
- [ ] 库存扣减准确
- [ ] 特殊奖品发放正确
- [ ] 用户体验流畅
## 风险点和注意事项
### 技术风险
1. **随机性**: 随机数生成的真随机性
2. **并发安全**: 高并发抽奖时的数据一致性
3. **性能优化**: 大量抽奖请求的性能处理
4. **算法准确性**: 概率计算的精确性
### 业务风险
1. **概率偏差**: 实际概率与理论概率的偏差
2. **库存超发**: 库存管理不当导致的超发
3. **作弊风险**: 恶意用户的作弊行为
4. **用户体验**: 抽奖结果的公平性感知
### 解决方案
1. **算法测试**: 大量测试验证算法准确性
2. **分布式锁**: 关键操作使用分布式锁
3. **监控告警**: 实时监控抽奖数据异常
4. **审计日志**: 完整的抽奖操作日志
## 下一阶段准备
### 为阶段8准备的内容
- [ ] 高级功能模块规划
- [ ] 系统优化和性能调优
- [ ] 监控和告警系统
- [ ] 部署和运维准备
### 交接文档
- [ ] 抽奖算法设计文档
- [ ] 概率计算说明
- [ ] 特殊机制配置指南
- [ ] 安全机制说明
---
**阶段7完成标志**: 抽奖系统功能完整,包括基础抽奖、特殊机制、动画配置、统计分析等功能正常运行,算法准确性和安全性得到保障。