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

19 KiB
Raw Blame History

阶段7抽奖逻辑

阶段概述

时间: 3周
目标: 实现完整的抽奖系统,包括概率计算、随机抽奖、奖品发放、库存管理等核心功能
优先级: P2 (中高优先级)

详细任务清单

7.1 抽奖算法引擎 (5天)

任务描述

实现核心的抽奖算法引擎,支持多种抽奖模式和概率计算

具体工作

  • 设计抽奖算法接口
  • 实现权重随机算法
  • 实现概率计算引擎
  • 实现随机数生成器
  • 实现抽奖结果验证
  • 添加抽奖日志记录

核心算法实现

抽奖引擎接口
// 抽奖引擎接口
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      // 特殊抽奖
}
权重随机算法
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; }
}
抽奖引擎实现
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接口支持多种抽奖类型

具体工作

  • 实现一番赏抽奖结果接口
  • 实现无限赏抽奖结果接口
  • 实现一番赏中奖记录接口
  • 实现无限赏中奖记录接口
  • 实现每日抽奖记录接口
  • 实现道具卡抽奖接口
  • 添加抽奖限制验证

核心接口实现

单次抽奖接口
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"]
    }
  }
}
多次抽奖接口
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
    }
  }
}
一番赏抽奖结果接口
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
    }
  ]
}
无限赏抽奖结果接口
POST /api/v1/lottery/infinite/prize-log
Authorization: Bearer {token}

Request:
{
  "order_num": "202401010001"
}

Response:
{
  "status": 1,
  "msg": "请求成功",
  "data": []
}
一番赏中奖记录接口
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
      }
    ]
  }
}
无限赏中奖记录接口
POST /api/v1/lottery/infinite/shang-log
Authorization: Bearer {token}

Request:
{
  "goods_id": 1001,
  "page": 1
}

Response:
{
  "status": 1,
  "msg": "请求成功",
  "data": {
    "data": []
  }
}
每日抽奖记录接口
POST /api/v1/lottery/infinite/daily-records
Authorization: Bearer {token}

Request:
{
  "goods_id": 1001
}

Response:
{
  "status": 1,
  "msg": "请求成功",
  "data": []
}
道具卡抽奖接口
POST /api/v1/lottery/item-card
Authorization: Bearer {token}

Request:
{
  "goods_id": 1001,
  "item_card_id": 1
}

Response:
{
  "status": 1,
  "msg": "抽奖成功",
  "data": {
    "prize": {}
  }
}
抽奖历史接口
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天)

任务描述

实现特殊抽奖机制,如保底、连击、全局奖等

具体工作

  • 实现保底抽奖机制
  • 实现连击抽奖逻辑
  • 实现全局奖触发
  • 实现特殊奖品发放
  • 实现抽奖事件通知

特殊机制实现

保底抽奖机制
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;
    }
}
连击抽奖逻辑
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
        };
    }
}
全局奖触发机制
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天)

任务描述

实现抽奖动画配置和特效数据

具体工作

  • 设计抽奖动画配置
  • 实现动画参数计算
  • 实现特效触发逻辑
  • 实现音效配置
  • 添加动画预设模板

动画配置实现

抽奖动画配置接口
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天)

任务描述

实现抽奖数据统计和分析功能

具体工作

  • 实现抽奖统计接口
  • 实现概率分析功能
  • 实现热门奖品统计
  • 实现用户抽奖行为分析
  • 实现抽奖报表生成

统计分析接口

抽奖统计接口
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
      }
    ]
  }
}

抽奖数据模型

抽奖记录表

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; }
}

抽奖安全机制

防作弊机制

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完成标志: 抽奖系统功能完整,包括基础抽奖、特殊机制、动画配置、统计分析等功能正常运行,算法准确性和安全性得到保障。