Some checks failed
Build docker and publish / linux (1.24.5) (push) Failing after 41s
新增随机种子生成与验证逻辑,包括: 1. 添加随机承诺生成接口 2. 实现抽奖执行与验证流程 3. 新增批量用户创建与删除功能 4. 添加抽奖收据记录表 5. 完善配置管理与错误码 新增测试用例验证随机算法正确性
218 lines
6.7 KiB
JavaScript
218 lines
6.7 KiB
JavaScript
// 抽奖盈亏计算测试示例
|
||
// 基于实际业务逻辑的模拟数据
|
||
|
||
// 模拟一个抽奖活动的配置
|
||
const mockActivity = {
|
||
id: 1,
|
||
name: "春节抽奖活动",
|
||
price_draw: 1000, // 门票价格:10元 = 1000分
|
||
status: 1
|
||
};
|
||
|
||
// 模拟奖项配置
|
||
const mockRewards = [
|
||
{
|
||
id: 1,
|
||
name: "iPhone 15 Pro",
|
||
weight: 1,
|
||
quantity: 10,
|
||
product_id: 101,
|
||
cost: 800000 // 成本:8000元 = 800000分
|
||
},
|
||
{
|
||
id: 2,
|
||
name: "AirPods Pro",
|
||
weight: 10,
|
||
quantity: 50,
|
||
product_id: 102,
|
||
cost: 200000 // 成本:2000元 = 200000分
|
||
},
|
||
{
|
||
id: 3,
|
||
name: "小米手环",
|
||
weight: 100,
|
||
quantity: 200,
|
||
product_id: 103,
|
||
cost: 20000 // 成本:200元 = 20000分
|
||
},
|
||
{
|
||
id: 4,
|
||
name: "优惠券10元",
|
||
weight: 1000,
|
||
quantity: -1, // -1 表示不限量
|
||
product_id: null,
|
||
cost: 1000 // 成本:10元 = 1000分(平台补贴)
|
||
},
|
||
{
|
||
id: 5,
|
||
name: "谢谢参与",
|
||
weight: 5000,
|
||
quantity: -1,
|
||
product_id: null,
|
||
cost: 0 // 无成本
|
||
}
|
||
];
|
||
|
||
// 计算理论概率和期望支出
|
||
function calculateTheoreticalData(rewards) {
|
||
// 筛选有效奖项(weight > 0 且 quantity != 0)
|
||
const validRewards = rewards.filter(r => r.weight > 0 && r.quantity !== 0);
|
||
|
||
// 计算总权重
|
||
const totalWeight = validRewards.reduce((sum, r) => sum + r.weight, 0);
|
||
|
||
// 计算每个奖项的理论概率和期望支出
|
||
const results = validRewards.map(reward => {
|
||
const probability = reward.weight / totalWeight;
|
||
const expectedCost = probability * reward.cost; // 单次抽奖的期望支出
|
||
|
||
return {
|
||
...reward,
|
||
probability,
|
||
expectedCost
|
||
};
|
||
});
|
||
|
||
// 总期望支出(单次抽奖)
|
||
const totalExpectedCost = results.reduce((sum, r) => sum + r.expectedCost, 0);
|
||
|
||
return {
|
||
rewards: results,
|
||
totalWeight,
|
||
totalExpectedCost
|
||
};
|
||
}
|
||
|
||
// 模拟抽奖结果
|
||
function simulateDraws(rewards, sampleSize) {
|
||
const theoretical = calculateTheoreticalData(rewards);
|
||
const results = {};
|
||
|
||
// 初始化结果统计
|
||
theoretical.rewards.forEach(reward => {
|
||
results[reward.id] = {
|
||
...reward,
|
||
count: 0,
|
||
simulatedCost: 0
|
||
};
|
||
});
|
||
|
||
// 模拟抽奖
|
||
for (let i = 0; i < sampleSize; i++) {
|
||
const random = Math.random();
|
||
let cumulativeProbability = 0;
|
||
|
||
// 根据概率选择奖项
|
||
for (const reward of theoretical.rewards) {
|
||
cumulativeProbability += reward.probability;
|
||
if (random <= cumulativeProbability) {
|
||
results[reward.id].count++;
|
||
results[reward.id].simulatedCost += reward.cost;
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
return {
|
||
theoretical,
|
||
simulated: Object.values(results),
|
||
sampleSize
|
||
};
|
||
}
|
||
|
||
// 计算盈亏指标
|
||
function calculateProfitMetrics(activity, simulation) {
|
||
const { theoretical, simulated, sampleSize } = simulation;
|
||
const ticketPrice = activity.price_draw;
|
||
|
||
// 理论计算
|
||
const theoreticalRevenue = ticketPrice * sampleSize;
|
||
const theoreticalPayout = theoretical.totalExpectedCost * sampleSize;
|
||
const theoreticalProfit = theoreticalRevenue - theoreticalPayout;
|
||
const theoreticalMargin = theoreticalProfit / theoreticalRevenue;
|
||
|
||
// 模拟计算
|
||
const simulatedRevenue = ticketPrice * sampleSize;
|
||
const simulatedPayout = simulated.reduce((sum, r) => sum + r.simulatedCost, 0);
|
||
const simulatedProfit = simulatedRevenue - simulatedPayout;
|
||
const simulatedMargin = simulatedProfit / simulatedRevenue;
|
||
|
||
return {
|
||
theoretical: {
|
||
revenue: theoreticalRevenue,
|
||
payout: theoreticalPayout,
|
||
profit: theoreticalProfit,
|
||
margin: theoreticalMargin
|
||
},
|
||
simulated: {
|
||
revenue: simulatedRevenue,
|
||
payout: simulatedPayout,
|
||
profit: simulatedProfit,
|
||
margin: simulatedMargin
|
||
}
|
||
};
|
||
}
|
||
|
||
// 运行测试
|
||
function runLotteryProfitTest() {
|
||
console.log("=== 抽奖盈亏分析测试 ===");
|
||
console.log(`活动: ${mockActivity.name}`);
|
||
console.log(`门票价格: ¥${(mockActivity.price_draw / 100).toFixed(2)}`);
|
||
console.log("");
|
||
|
||
// 理论计算
|
||
const theoreticalData = calculateTheoreticalData(mockRewards);
|
||
console.log("--- 理论概率分析 ---");
|
||
console.log(`总权重: ${theoreticalData.totalWeight}`);
|
||
console.log(`单次期望支出: ¥${(theoreticalData.totalExpectedCost / 100).toFixed(4)}`);
|
||
console.log(`单次期望利润: ¥${((mockActivity.price_draw - theoreticalData.totalExpectedCost) / 100).toFixed(4)}`);
|
||
console.log("");
|
||
|
||
theoreticalData.rewards.forEach(reward => {
|
||
console.log(`${reward.name}:`);
|
||
console.log(` 权重: ${reward.weight}, 概率: ${(reward.probability * 100).toFixed(4)}%`);
|
||
console.log(` 成本: ¥${(reward.cost / 100).toFixed(2)}, 期望支出: ¥${(reward.expectedCost / 100).toFixed(4)}`);
|
||
});
|
||
|
||
console.log("\n--- 模拟结果 (样本数: 10000) ---");
|
||
const simulation = simulateDraws(mockRewards, 10000);
|
||
const metrics = calculateProfitMetrics(mockActivity, simulation);
|
||
|
||
console.log("理论值:");
|
||
console.log(` 总收入: ¥${(metrics.theoretical.revenue / 100).toFixed(2)}`);
|
||
console.log(` 总支出: ¥${(metrics.theoretical.payout / 100).toFixed(2)}`);
|
||
console.log(` 总利润: ¥${(metrics.theoretical.profit / 100).toFixed(2)}`);
|
||
console.log(` 利润率: ${(metrics.theoretical.margin * 100).toFixed(2)}%`);
|
||
|
||
console.log("\n模拟值:");
|
||
console.log(` 总收入: ¥${(metrics.simulated.revenue / 100).toFixed(2)}`);
|
||
console.log(` 总支出: ¥${(metrics.simulated.payout / 100).toFixed(2)}`);
|
||
console.log(` 总利润: ¥${(metrics.simulated.profit / 100).toFixed(2)}`);
|
||
console.log(` 利润率: ${(metrics.simulated.margin * 100).toFixed(2)}%`);
|
||
|
||
console.log("\n--- 各奖项模拟结果 ---");
|
||
simulation.simulated.forEach(result => {
|
||
const simulatedRate = result.count / simulation.sampleSize;
|
||
const theoreticalRate = result.probability;
|
||
const diff = Math.abs(simulatedRate - theoreticalRate) * 100;
|
||
|
||
console.log(`${result.name}:`);
|
||
console.log(` 模拟次数: ${result.count}, 模拟概率: ${(simulatedRate * 100).toFixed(3)}%`);
|
||
console.log(` 理论概率: ${(theoreticalRate * 100).toFixed(3)}%, 差异: ${diff.toFixed(3)}%`);
|
||
console.log(` 模拟支出: ¥${(result.simulatedCost / 100).toFixed(2)}`);
|
||
});
|
||
|
||
// 验证大数定律
|
||
console.log("\n--- 大数定律验证 ---");
|
||
const largeSample = simulateDraws(mockRewards, 100000);
|
||
largeSample.simulated.forEach(result => {
|
||
const simulatedRate = result.count / largeSample.sampleSize;
|
||
const theoreticalRate = result.probability;
|
||
const diff = Math.abs(simulatedRate - theoreticalRate) * 100;
|
||
|
||
console.log(`${result.name}: 模拟概率 ${(simulatedRate * 100).toFixed(4)}% vs 理论概率 ${(theoreticalRate * 100).toFixed(4)}%, 差异 ${diff.toFixed(4)}%`);
|
||
});
|
||
}
|
||
|
||
// 运行测试
|
||
runLotteryProfitTest(); |