19 KiB
19 KiB
阶段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%
安全验收
- 防作弊机制有效
- 随机数生成安全
- 抽奖结果不可预测
- 异常行为检测正常
业务验收
- 抽奖概率符合配置
- 库存扣减准确
- 特殊奖品发放正确
- 用户体验流畅
风险点和注意事项
技术风险
- 随机性: 随机数生成的真随机性
- 并发安全: 高并发抽奖时的数据一致性
- 性能优化: 大量抽奖请求的性能处理
- 算法准确性: 概率计算的精确性
业务风险
- 概率偏差: 实际概率与理论概率的偏差
- 库存超发: 库存管理不当导致的超发
- 作弊风险: 恶意用户的作弊行为
- 用户体验: 抽奖结果的公平性感知
解决方案
- 算法测试: 大量测试验证算法准确性
- 分布式锁: 关键操作使用分布式锁
- 监控告警: 实时监控抽奖数据异常
- 审计日志: 完整的抽奖操作日志
下一阶段准备
为阶段8准备的内容
- 高级功能模块规划
- 系统优化和性能调优
- 监控和告警系统
- 部署和运维准备
交接文档
- 抽奖算法设计文档
- 概率计算说明
- 特殊机制配置指南
- 安全机制说明
阶段7完成标志: 抽奖系统功能完整,包括基础抽奖、特殊机制、动画配置、统计分析等功能正常运行,算法准确性和安全性得到保障。