# 阶段7:抽奖逻辑 ## 阶段概述 **时间**: 3周 **目标**: 实现完整的抽奖系统,包括概率计算、随机抽奖、奖品发放、库存管理等核心功能 **优先级**: P2 (中高优先级) ## 详细任务清单 ### 7.1 抽奖算法引擎 (5天) #### 任务描述 实现核心的抽奖算法引擎,支持多种抽奖模式和概率计算 #### 具体工作 - [ ] 设计抽奖算法接口 - [ ] 实现权重随机算法 - [ ] 实现概率计算引擎 - [ ] 实现随机数生成器 - [ ] 实现抽奖结果验证 - [ ] 添加抽奖日志记录 #### 核心算法实现 ##### 抽奖引擎接口 ```csharp // 抽奖引擎接口 public interface ILotteryEngine { Task DrawAsync(LotteryRequest request); Task> DrawMultipleAsync(LotteryRequest request, int count); Task CalculateProbabilityAsync(int goodsId, int goodsNum); Task 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(List> 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 { public T Item { get; set; } public double Weight { get; set; } public double Probability { get; set; } } ``` ##### 抽奖引擎实现 ```csharp public class LotteryEngine : ILotteryEngine { private readonly ILogger _logger; private readonly WeightedRandomSelector _selector; private readonly IRedisService _redis; public async Task 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 { 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 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 CheckGuaranteeAsync(int userId, int goodsId) { // 检查用户在该商品的抽奖次数 var drawCount = await GetUserDrawCountAsync(userId, goodsId); var guaranteeConfig = await GetGuaranteeConfigAsync(goodsId); return drawCount >= guaranteeConfig.RequiredDraws; } public async Task 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 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 CheckGlobalPrizeTriggerAsync(int goodsId) { var config = await GetGlobalPrizeConfigAsync(goodsId); var currentCount = await GetGlobalDrawCountAsync(goodsId); return currentCount >= config.TriggerCount; } public async Task> 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 CheckDrawFrequencyAsync(int userId, int goodsId) { var recentDraws = await GetRecentDrawsAsync(userId, goodsId, TimeSpan.FromMinutes(1)); return recentDraws.Count <= 10; // 每分钟最多10次 } // 检查异常行为 public async Task 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完成标志**: 抽奖系统功能完整,包括基础抽奖、特殊机制、动画配置、统计分析等功能正常运行,算法准确性和安全性得到保障。