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