优化一键测试权重

This commit is contained in:
2026-03-13 15:47:12 +08:00
parent f5eaf8da30
commit 0b26afde70
19 changed files with 991 additions and 274 deletions

View File

@@ -30,20 +30,22 @@ class DiceLotteryPoolConfigController extends BaseController
}
/**
* 获取 DiceLotteryPoolConfig 列表数据,用于 lottery_config_id 下拉(值为 id显示为 name并附带 T1-T5 档位权重
* 获取 DiceLotteryPoolConfig 列表数据,用于 lottery_config_id 下拉(值为 id显示为 name并附带 type、T1-T5 档位权重
* type0=付费抽奖券1=免费抽奖券;一键测试权重中付费默认选 type=0免费默认选 type=1
* @param Request $request
* @return Response 返回 [ ['id' => int, 'name' => string, 't1_weight' => int, ... 't5_weight' => int], ... ]
* @return Response 返回 [ ['id' => int, 'name' => string, 'type' => int, 't1_weight' => int, ... 't5_weight' => int], ... ]
*/
#[Permission('色子奖池配置列表', 'dice:lottery_pool_config:index:index')]
public function getOptions(Request $request): Response
{
$list = DiceLotteryPoolConfig::field('id,name,t1_weight,t2_weight,t3_weight,t4_weight,t5_weight')
$list = DiceLotteryPoolConfig::field('id,name,type,t1_weight,t2_weight,t3_weight,t4_weight,t5_weight')
->order('id', 'asc')
->select();
$data = $list->map(function ($item) {
return [
'id' => (int) $item['id'],
'name' => (string) ($item['name'] ?? ''),
'type' => (int) ($item['type'] ?? 0),
't1_weight' => (int) ($item['t1_weight'] ?? 0),
't2_weight' => (int) ($item['t2_weight'] ?? 0),
't3_weight' => (int) ($item['t3_weight'] ?? 0),

View File

@@ -30,7 +30,7 @@ class DicePlayRecordTestController extends BaseController
}
/**
* 数据列表,并在结果中附带当前筛选条件下所有测试数据的玩家总收益 total_win_coinDicePlayRecordTest.win_coin 求和
* 数据列表,并在结果中附带当前筛选条件下测试数据的平台总盈利 total_win_coin付费抽奖次数×100 - 玩家总收益
* @param Request $request
* @return Response
*/
@@ -44,13 +44,17 @@ class DicePlayRecordTestController extends BaseController
['win_coin_min', ''],
['win_coin_max', ''],
['reward_tier', ''],
['roll_number', ''],
]);
$query = $this->logic->search($where);
$query->with(['diceLotteryPoolConfig', 'diceRewardConfig']);
// 按当前筛选条件统计所有测试数据的总收益(游戏总亏损)
// 按当前筛选条件统计:平台总盈利 = 付费抽奖(lottery_type=0)次数×100 - 玩家总收益(win_coin 求和)
$sumQuery = clone $query;
$totalWinCoin = $sumQuery->sum('win_coin');
$playerTotalWin = (float) $sumQuery->sum('win_coin');
$paidCountQuery = clone $query;
$paidCount = (int) $paidCountQuery->where('lottery_type', 0)->count();
$totalWinCoin = $paidCount * 100 - $playerTotalWin;
$data = $this->logic->getList($query);
$data['total_win_coin'] = $totalWinCoin;

View File

@@ -79,20 +79,31 @@ class DiceRewardController extends BaseController
}
/**
* 一键测试权重:创建测试记录并启动单进程后台执行,实时写入 dice_play_record_test,更新 dice_reward_config_record 进度
* 参数lottery_config_id 奖池配置s_count 顺时针次数 100/500/1000/5000n_count 逆时针次数 100/500/1000/5000
* 一键测试权重:创建测试记录并启动单进程后台执行,按付费/免费、顺逆方向交替写入 dice_play_record_test
* 参数lottery_config_id 可选,不选则传 paid_tier_weights / free_tier_weights 自定义档位;
* paid_s_count, paid_n_count, free_s_count, free_n_count或兼容旧版 s_count, n_count
*/
#[Permission('奖励对照列表', 'dice:reward:index:index')]
public function startWeightTest(Request $request): Response
{
$lotteryConfigId = (int) $request->post('lottery_config_id', 0);
$sCount = (int) $request->post('s_count', 100);
$nCount = (int) $request->post('n_count', 100);
$post = is_array($request->post()) ? $request->post() : [];
$params = [
'lottery_config_id' => $post['lottery_config_id'] ?? null,
'paid_lottery_config_id' => $post['paid_lottery_config_id'] ?? null,
'free_lottery_config_id' => $post['free_lottery_config_id'] ?? null,
's_count' => $post['s_count'] ?? null,
'n_count' => $post['n_count'] ?? null,
'paid_s_count' => $post['paid_s_count'] ?? null,
'paid_n_count' => $post['paid_n_count'] ?? null,
'free_s_count' => $post['free_s_count'] ?? null,
'free_n_count' => $post['free_n_count'] ?? null,
'paid_tier_weights' => $post['paid_tier_weights'] ?? null,
'free_tier_weights' => $post['free_tier_weights'] ?? null,
];
$adminId = isset($this->adminInfo['id']) ? (int) $this->adminInfo['id'] : null;
try {
$logic = new DiceRewardConfigRecordLogic();
$recordId = $logic->createWeightTestRecord($lotteryConfigId, $sCount, $nCount, $adminId);
// 由独立进程 WeightTestProcess 定时轮询 status=0 并执行,不占用 HTTP 资源
$recordId = $logic->createWeightTestRecord($params, $adminId);
return $this->success(['record_id' => $recordId]);
} catch (\plugin\saiadmin\exception\ApiException $e) {
return $this->fail($e->getMessage());

View File

@@ -141,26 +141,25 @@ class DiceRewardConfigRecordController extends BaseController
}
/**
* 导入:测试记录的权重写入 DiceRewardConfig 与 DiceLotteryPoolConfig,并重新实例化缓存
* @param Request $request record_id: 测试记录ID, lottery_config_id: 可选导入档位权重到的奖池配置ID不传则用记录内的 lottery_config_id
* @return Response
* 导入:测试记录 DiceReward、DiceRewardConfig(BIGWIN)、DiceLotteryPoolConfig(付费/免费 T1-T5)
* @param Request $request record_id, paid_lottery_config_id(可选), free_lottery_config_id(可选), lottery_config_id(兼容旧版)
*/
#[Permission('奖励配置权重测试记录列表', 'dice:reward_config_record:index:index')]
public function importFromRecord(Request $request): Response
{
$recordId = (int) $request->post('record_id', 0);
$lotteryConfigId = $request->post('lottery_config_id', null);
if ($recordId <= 0) {
return $this->fail('请指定测试记录');
}
if ($lotteryConfigId !== null && $lotteryConfigId !== '') {
$lotteryConfigId = (int) $lotteryConfigId;
} else {
$lotteryConfigId = null;
}
$paidId = $request->post('paid_lottery_config_id', null);
$freeId = $request->post('free_lottery_config_id', null);
$legacyId = $request->post('lottery_config_id', null);
$paidLotteryConfigId = $paidId !== null && $paidId !== '' ? (int) $paidId : null;
$freeLotteryConfigId = $freeId !== null && $freeId !== '' ? (int) $freeId : null;
$lotteryConfigId = $legacyId !== null && $legacyId !== '' ? (int) $legacyId : null;
try {
$this->logic->importFromRecord($recordId, $lotteryConfigId);
return $this->success('导入成功,已刷新奖励配置与奖池配置');
$this->logic->importFromRecord($recordId, $paidLotteryConfigId, $freeLotteryConfigId, $lotteryConfigId);
return $this->success('导入成功,已刷新 DiceReward、DiceRewardConfig(BIGWIN)、奖池配置');
} catch (\plugin\saiadmin\exception\ApiException $e) {
return $this->fail($e->getMessage());
}

View File

@@ -71,12 +71,13 @@ class DiceRewardConfigRecordLogic extends BaseLogic
}
/**
* 将测试记录的权重导入weight_config_snapshot → dice_reward(顺时针/逆时针同值tier_weights_snapshot → DiceLotteryPoolConfig,并刷新缓存
* 将测试记录导入DiceReward权重快照、DiceRewardConfigBIGWIN weightDiceLotteryPoolConfig(付费/免费 T1-T5
* @param int $recordId 测试记录 ID
* @param int|null $lotteryConfigId 导入档位权重的奖池配置 ID不传则使用记录中的 lottery_config_id(若有)
* @throws ApiException
* @param int|null $paidLotteryConfigId 导入付费档位概率到的奖池type=0不传则用记录 paid_lottery_config_id
* @param int|null $freeLotteryConfigId 导入免费档位概率到的奖池type=1不传则用记录 free_lottery_config_id
* @param int|null $lotteryConfigId 兼容旧版:不传 paid/free 时用作统一奖池
*/
public function importFromRecord(int $recordId, ?int $lotteryConfigId = null): void
public function importFromRecord(int $recordId, ?int $paidLotteryConfigId = null, ?int $freeLotteryConfigId = null, ?int $lotteryConfigId = null): void
{
$record = $this->model->find($recordId);
if (!$record) {
@@ -96,11 +97,15 @@ class DiceRewardConfigRecordLogic extends BaseLogic
if ($id <= 0) {
continue;
}
$tier = DiceRewardConfig::where('id', $id)->value('tier');
if ($tier === null || $tier === '') {
$tier = isset($item['tier']) ? (string) $item['tier'] : '';
if ($tier === '') {
$tier = DiceRewardConfig::where('id', $id)->value('tier');
$tier = $tier !== null && $tier !== '' ? (string) $tier : '';
}
if ($tier === '') {
continue;
}
$tier = (string) $tier;
// 写入 DiceReward顺/逆时针同值)
foreach ([DiceReward::DIRECTION_CLOCKWISE, DiceReward::DIRECTION_COUNTERCLOCKWISE] as $direction) {
$affected = DiceReward::where('tier', $tier)->where('direction', $direction)->where('end_index', $id)->update(['weight' => $weight]);
if ($affected === 0) {
@@ -112,30 +117,78 @@ class DiceRewardConfigRecordLogic extends BaseLogic
$m->save();
}
}
// BIGWIN同步写入 DiceRewardConfig.weight
if (strtoupper($tier) === 'BIGWIN') {
DiceRewardConfig::where('id', $id)->update(['weight' => $weight]);
}
}
DiceReward::refreshCache();
}
$tiers = ['T1', 'T2', 'T3', 'T4', 'T5'];
$tierSnapshot = $record['tier_weights_snapshot'] ?? null;
if (is_string($tierSnapshot)) {
$tierSnapshot = json_decode($tierSnapshot, true);
}
$targetLotteryId = $lotteryConfigId !== null && $lotteryConfigId > 0
? $lotteryConfigId
: (isset($record['lottery_config_id']) && (int) $record['lottery_config_id'] > 0 ? (int) $record['lottery_config_id'] : null);
if (is_array($tierSnapshot) && !empty($tierSnapshot) && $targetLotteryId > 0) {
$pool = DiceLotteryPoolConfig::find($targetLotteryId);
$paidWeights = $record['paid_tier_weights'] ?? null;
if (is_string($paidWeights)) {
$paidWeights = json_decode($paidWeights, true);
}
$freeWeights = $record['free_tier_weights'] ?? null;
if (is_string($freeWeights)) {
$freeWeights = json_decode($freeWeights, true);
}
$fallbackLotteryId = $lotteryConfigId > 0 ? $lotteryConfigId : (isset($record['lottery_config_id']) && (int) $record['lottery_config_id'] > 0 ? (int) $record['lottery_config_id'] : null);
$paidTargetId = $paidLotteryConfigId > 0 ? $paidLotteryConfigId : ($fallbackLotteryId ?? (isset($record['paid_lottery_config_id']) && (int) $record['paid_lottery_config_id'] > 0 ? (int) $record['paid_lottery_config_id'] : null));
$freeTargetId = $freeLotteryConfigId > 0 ? $freeLotteryConfigId : (isset($record['free_lottery_config_id']) && (int) $record['free_lottery_config_id'] > 0 ? (int) $record['free_lottery_config_id'] : null);
// tier_weights_snapshot 新结构:['paid' => [...], 'free' => [...]]
$snapshotPaid = null;
$snapshotFree = null;
if (is_array($tierSnapshot) && !empty($tierSnapshot)) {
if (array_key_exists('paid', $tierSnapshot) || array_key_exists('free', $tierSnapshot)) {
if (isset($tierSnapshot['paid']) && is_array($tierSnapshot['paid'])) {
$snapshotPaid = $tierSnapshot['paid'];
}
if (isset($tierSnapshot['free']) && is_array($tierSnapshot['free'])) {
$snapshotFree = $tierSnapshot['free'];
}
} else {
// 兼容旧结构:直接就是一个 T1-T5 的数组,视为付费
$snapshotPaid = $tierSnapshot;
}
}
$paidData = is_array($paidWeights) && !empty($paidWeights) ? $paidWeights : $snapshotPaid;
$freeData = is_array($freeWeights) && !empty($freeWeights) ? $freeWeights : $snapshotFree;
if (is_array($paidData) && $paidTargetId > 0) {
$pool = DiceLotteryPoolConfig::find($paidTargetId);
if (!$pool) {
throw new ApiException('奖池配置不存在');
throw new ApiException('付费奖池配置不存在');
}
$update = [
't1_weight' => (int) ($tierSnapshot['T1'] ?? $tierSnapshot['t1'] ?? 0),
't2_weight' => (int) ($tierSnapshot['T2'] ?? $tierSnapshot['t2'] ?? 0),
't3_weight' => (int) ($tierSnapshot['T3'] ?? $tierSnapshot['t3'] ?? 0),
't4_weight' => (int) ($tierSnapshot['T4'] ?? $tierSnapshot['t4'] ?? 0),
't5_weight' => (int) ($tierSnapshot['T5'] ?? $tierSnapshot['t5'] ?? 0),
't1_weight' => (int) ($paidData['T1'] ?? $paidData['t1'] ?? 0),
't2_weight' => (int) ($paidData['T2'] ?? $paidData['t2'] ?? 0),
't3_weight' => (int) ($paidData['T3'] ?? $paidData['t3'] ?? 0),
't4_weight' => (int) ($paidData['T4'] ?? $paidData['t4'] ?? 0),
't5_weight' => (int) ($paidData['T5'] ?? $paidData['t5'] ?? 0),
];
DiceLotteryPoolConfig::where('id', $targetLotteryId)->update($update);
DiceLotteryPoolConfig::where('id', $paidTargetId)->update($update);
}
if (is_array($freeData) && $freeTargetId > 0) {
$pool = DiceLotteryPoolConfig::find($freeTargetId);
if (!$pool) {
throw new ApiException('免费奖池配置不存在');
}
$update = [
't1_weight' => (int) ($freeData['T1'] ?? $freeData['t1'] ?? 0),
't2_weight' => (int) ($freeData['T2'] ?? $freeData['t2'] ?? 0),
't3_weight' => (int) ($freeData['T3'] ?? $freeData['t3'] ?? 0),
't4_weight' => (int) ($freeData['T4'] ?? $freeData['t4'] ?? 0),
't5_weight' => (int) ($freeData['T5'] ?? $freeData['t5'] ?? 0),
];
DiceLotteryPoolConfig::where('id', $freeTargetId)->update($update);
}
DiceRewardConfig::refreshCache();
@@ -143,28 +196,63 @@ class DiceRewardConfigRecordLogic extends BaseLogic
}
/**
* 创建一键测试权重记录并返回 ID供后台执行器写入 dice_play_record_test 并更新进度
* @param int $lotteryConfigId 奖池配置 IDDiceLotteryPoolConfig
* @param int $sCount 顺时针模拟次数 100/500/1000/5000
* @param int $nCount 逆时针模拟次数 100/500/1000/5000
* @param int|null $adminId 执行人
* 创建一键测试权重记录并返回 ID供后台执行器按付费/免费、顺逆方向交替写入 dice_play_record_test
* 支持两种模式1选择奖池配置 lottery_config_id档位概率取自配置2不选配置使用自定义 paid_tier_weights / free_tier_weights
* @param array|int $params 数组lottery_config_id(可选), paid_s_count, paid_n_count, free_s_count, free_n_count或兼容旧版传 4 个 int 时视为 (paid_s_count, paid_n_count, free_s_count, free_n_count)
* @param int|null $adminId 执行人(旧版 4 参调用时第二参为 paid_n_count此处不传 adminId
* @return int 记录 ID
* @throws ApiException
*/
public function createWeightTestRecord(int $lotteryConfigId, int $sCount, int $nCount, ?int $adminId = null): int
public function createWeightTestRecord(array|int $params, mixed $adminIdOrFreeS = null, mixed $freeSOrFreeN = null, mixed $freeN = null): int
{
$adminId = null;
if (!is_array($params)) {
// 兼容旧版调用createWeightTestRecord(paid_s_count, paid_n_count, free_s_count, free_n_count)
$params = [
'paid_s_count' => (int) $params,
'paid_n_count' => (int) $adminIdOrFreeS,
'free_s_count' => (int) $freeSOrFreeN,
'free_n_count' => (int) $freeN,
];
} else {
$adminId = $adminIdOrFreeS !== null && $adminIdOrFreeS !== '' ? (int) $adminIdOrFreeS : null;
}
$allowed = [100, 500, 1000, 5000];
if (!in_array($sCount, $allowed, true) || !in_array($nCount, $allowed, true)) {
throw new ApiException('顺时针/逆时针次数仅支持 100、500、1000、5000');
$lotteryConfigId = isset($params['lottery_config_id']) ? (int) $params['lottery_config_id'] : 0;
$paidConfigId = isset($params['paid_lottery_config_id']) ? (int) $params['paid_lottery_config_id'] : 0;
$freeConfigId = isset($params['free_lottery_config_id']) ? (int) $params['free_lottery_config_id'] : 0;
if ($paidConfigId <= 0 && $lotteryConfigId > 0) {
$paidConfigId = $lotteryConfigId;
}
$config = DiceLotteryPoolConfig::find($lotteryConfigId);
if (!$config) {
throw new ApiException('奖池配置不存在');
if ($freeConfigId <= 0 && $lotteryConfigId > 0) {
$freeConfigId = $lotteryConfigId;
}
$paidS = isset($params['paid_s_count']) ? (int) $params['paid_s_count'] : (int) ($params['s_count'] ?? 0);
$paidN = isset($params['paid_n_count']) ? (int) $params['paid_n_count'] : (int) ($params['n_count'] ?? 0);
$freeS = (int) ($params['free_s_count'] ?? 0);
$freeN = (int) ($params['free_n_count'] ?? 0);
foreach ([$paidS, $paidN, $freeS, $freeN] as $c) {
if ($c !== 0 && !in_array($c, $allowed, true)) {
throw new ApiException('各抽奖次数仅支持 0、100、500、1000、5000');
}
}
$total = $paidS + $paidN + $freeS + $freeN;
if ($total <= 0) {
throw new ApiException('付费或免费至少一种方向次数之和大于 0');
}
$snapshot = [];
// 档位权重快照:区分付费/免费,结构为 ['paid' => [...], 'free' => [...]]
$tierWeightsSnapshot = [
'paid' => null,
'free' => null,
];
$paidTierWeights = null;
$freeTierWeights = null;
$instance = DiceReward::getCachedInstance();
$byTierDirection = $instance['by_tier_direction'] ?? [];
$snapshot = [];
foreach ($byTierDirection as $tier => $byDir) {
foreach ($byDir as $dir => $rows) {
foreach ($rows as $row) {
@@ -177,26 +265,98 @@ class DiceRewardConfigRecordLogic extends BaseLogic
}
}
}
$tierWeightsSnapshot = [
'T1' => (int) ($config->t1_weight ?? 0),
'T2' => (int) ($config->t2_weight ?? 0),
'T3' => (int) ($config->t3_weight ?? 0),
'T4' => (int) ($config->t4_weight ?? 0),
'T5' => (int) ($config->t5_weight ?? 0),
];
$total = $sCount + $nCount;
if ($paidConfigId > 0) {
$config = DiceLotteryPoolConfig::find($paidConfigId);
if (!$config) {
throw new ApiException('付费奖池配置不存在');
}
$tierWeightsSnapshot['paid'] = [
'T1' => (int) ($config->t1_weight ?? 0),
'T2' => (int) ($config->t2_weight ?? 0),
'T3' => (int) ($config->t3_weight ?? 0),
'T4' => (int) ($config->t4_weight ?? 0),
'T5' => (int) ($config->t5_weight ?? 0),
];
} else {
$paidTierWeights = $params['paid_tier_weights'] ?? null;
if (!is_array($paidTierWeights)) {
throw new ApiException('付费未选择奖池配置时请填写付费自定义档位概率T1T5');
}
$tiers = ['T1', 'T2', 'T3', 'T4', 'T5'];
foreach ($tiers as $t) {
$v = (int) ($paidTierWeights[$t] ?? 0);
if ($v < 0 || $v > 100) {
throw new ApiException('付费档位概率每档只能 0-100%');
}
$paidTierWeights[$t] = $v;
}
$paidSum = array_sum(array_intersect_key($paidTierWeights, array_flip($tiers)));
if ($paidSum > 100) {
throw new ApiException('付费档位概率 T1T5 之和不能超过 100%');
}
$tierWeightsSnapshot['paid'] = $paidTierWeights;
}
if ($freeConfigId > 0) {
$config = DiceLotteryPoolConfig::find($freeConfigId);
if (!$config) {
throw new ApiException('免费奖池配置不存在');
}
$tierWeightsSnapshot['free'] = [
'T1' => (int) ($config->t1_weight ?? 0),
'T2' => (int) ($config->t2_weight ?? 0),
'T3' => (int) ($config->t3_weight ?? 0),
'T4' => (int) ($config->t4_weight ?? 0),
'T5' => (int) ($config->t5_weight ?? 0),
];
} else {
$freeTierWeights = $params['free_tier_weights'] ?? null;
if (!is_array($freeTierWeights)) {
throw new ApiException('免费未选择奖池配置时请填写免费自定义档位概率T1T5');
}
$tiers = ['T1', 'T2', 'T3', 'T4', 'T5'];
foreach ($tiers as $t) {
$v = (int) ($freeTierWeights[$t] ?? 0);
if ($v < 0 || $v > 100) {
throw new ApiException('免费档位概率每档只能 0-100%');
}
$freeTierWeights[$t] = $v;
}
$freeSum = array_sum(array_intersect_key($freeTierWeights, array_flip($tiers)));
if ($freeSum > 100) {
throw new ApiException('免费档位概率 T1T5 之和不能超过 100%');
}
$tierWeightsSnapshot['free'] = $freeTierWeights;
}
// 兼容:若某一侧未配置,则保存为空数组,方便前端直接解构
if (!is_array($tierWeightsSnapshot['paid'])) {
$tierWeightsSnapshot['paid'] = [];
}
if (!is_array($tierWeightsSnapshot['free'])) {
$tierWeightsSnapshot['free'] = [];
}
$record = new DiceRewardConfigRecord();
$record->test_count = $total;
$record->weight_config_snapshot = $snapshot;
$record->tier_weights_snapshot = $tierWeightsSnapshot;
$record->lottery_config_id = $lotteryConfigId;
$record->lottery_config_id = $lotteryConfigId > 0 ? $lotteryConfigId : null;
$record->paid_lottery_config_id = $paidConfigId > 0 ? $paidConfigId : null;
$record->free_lottery_config_id = $freeConfigId > 0 ? $freeConfigId : null;
$record->total_play_count = $total;
$record->over_play_count = 0;
$record->status = DiceRewardConfigRecord::STATUS_RUNNING;
$record->remark = null;
$record->s_count = $sCount;
$record->n_count = $nCount;
$record->s_count = $paidS + $paidN;
$record->n_count = $freeS + $freeN;
$record->paid_s_count = $paidS;
$record->paid_n_count = $paidN;
$record->free_s_count = $freeS;
$record->free_n_count = $freeN;
$record->paid_tier_weights = $paidTierWeights;
$record->free_tier_weights = $freeTierWeights;
$record->result_counts = [];
$record->tier_counts = null;
$record->admin_id = $adminId;

View File

@@ -17,7 +17,8 @@ class WeightTestRunner
private const BATCH_SIZE = 10;
/**
* 执行指定测试记录:按 s_count(顺时针)+n_count(逆时针) 模拟,每 10 条写入一次测试表并更新进度
* 执行指定测试记录:按付费/免费、顺/逆方向交替模拟(付费顺→付费逆→免费顺→免费逆),每 10 条写入一次测试表并更新进度
* 支持1lottery_config_id 有值时用奖池配置档位权重2无值时用记录中的 paid_tier_weights / free_tier_weights
* @param int $recordId dice_reward_config_record.id
*/
public function run(int $recordId): void
@@ -28,39 +29,91 @@ class WeightTestRunner
return;
}
$sCount = (int) ($record->s_count ?? 0);
$nCount = (int) ($record->n_count ?? 0);
$total = $sCount + $nCount;
if ($total <= 0) {
$this->markFailed($recordId, 's_count + n_count 必须大于 0');
return;
$paidS = (int) ($record->paid_s_count ?? 0);
$paidN = (int) ($record->paid_n_count ?? 0);
$freeS = (int) ($record->free_s_count ?? 0);
$freeN = (int) ($record->free_n_count ?? 0);
if ($paidS + $paidN + $freeS + $freeN <= 0) {
$sCount = (int) ($record->s_count ?? 0);
$nCount = (int) ($record->n_count ?? 0);
$total = $sCount + $nCount;
if ($total <= 0) {
$this->markFailed($recordId, '抽奖次数必须大于 0');
return;
}
$paidS = $sCount;
$paidN = $nCount;
} else {
$total = $paidS + $paidN + $freeS + $freeN;
}
$configId = (int) ($record->lottery_config_id ?? 0);
$config = $configId > 0 ? DiceLotteryPoolConfig::find($configId) : DiceLotteryPoolConfig::where('type', 0)->find();
if (!$config) {
$this->markFailed($recordId, '奖池配置不存在');
$paidConfigId = (int) ($record->paid_lottery_config_id ?? 0);
$freeConfigId = (int) ($record->free_lottery_config_id ?? 0);
if ($paidConfigId <= 0) {
$paidConfigId = (int) ($record->lottery_config_id ?? 0);
}
if ($freeConfigId <= 0) {
$freeConfigId = (int) ($record->lottery_config_id ?? 0);
}
$paidConfig = $paidConfigId > 0 ? DiceLotteryPoolConfig::find($paidConfigId) : null;
$freeConfig = $freeConfigId > 0 ? DiceLotteryPoolConfig::find($freeConfigId) : null;
if ($paidConfigId > 0 && !$paidConfig) {
$this->markFailed($recordId, '付费奖池配置不存在');
return;
}
if ($freeConfigId > 0 && !$freeConfig) {
$this->markFailed($recordId, '免费奖池配置不存在');
return;
}
$paidTierWeights = null;
$freeTierWeights = null;
if ($paidConfig === null) {
$paidTierWeights = $record->paid_tier_weights;
if (!is_array($paidTierWeights) || $paidTierWeights === []) {
$this->markFailed($recordId, '付费未选奖池时需提供 paid_tier_weights');
return;
}
}
if ($freeConfig === null) {
$freeTierWeights = $record->free_tier_weights;
if (!is_array($freeTierWeights) || $freeTierWeights === []) {
$this->markFailed($recordId, '免费未选奖池时需提供 free_tier_weights');
return;
}
}
$playLogic = new PlayStartLogic();
$resultCounts = []; // grid_number => count
$tierCounts = []; // tier => count
$resultCounts = [];
$tierCounts = [];
$buffer = [];
$done = 0;
try {
for ($i = 0; $i < $sCount; $i++) {
$row = $playLogic->simulateOnePlay($config, 0);
for ($i = 0; $i < $paidS; $i++) {
$row = $playLogic->simulateOnePlay($paidConfig, 0, 0, $paidTierWeights);
$this->aggregate($row, $resultCounts, $tierCounts);
$buffer[] = $this->rowForInsert($row);
$buffer[] = $this->rowForInsert($row, $recordId);
$done++;
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
}
for ($i = 0; $i < $nCount; $i++) {
$row = $playLogic->simulateOnePlay($config, 1);
for ($i = 0; $i < $paidN; $i++) {
$row = $playLogic->simulateOnePlay($paidConfig, 1, 0, $paidTierWeights);
$this->aggregate($row, $resultCounts, $tierCounts);
$buffer[] = $this->rowForInsert($row);
$buffer[] = $this->rowForInsert($row, $recordId);
$done++;
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
}
for ($i = 0; $i < $freeS; $i++) {
$row = $playLogic->simulateOnePlay($freeConfig, 0, 1, $freeTierWeights);
$this->aggregate($row, $resultCounts, $tierCounts);
$buffer[] = $this->rowForInsert($row, $recordId);
$done++;
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
}
for ($i = 0; $i < $freeN; $i++) {
$row = $playLogic->simulateOnePlay($freeConfig, 1, 1, $freeTierWeights);
$this->aggregate($row, $resultCounts, $tierCounts);
$buffer[] = $this->rowForInsert($row, $recordId);
$done++;
$this->flushIfNeeded($buffer, $recordId, $done, $total, $resultCounts, $tierCounts);
}
@@ -68,6 +121,7 @@ class WeightTestRunner
$this->insertBuffer($buffer);
$this->updateProgress($recordId, $done, $resultCounts, $tierCounts);
}
// 平台赚取金额:通过关联 DicePlayRecordTestreward_config_record_id统计
$this->markSuccess($recordId, $resultCounts, $tierCounts);
} catch (\Throwable $e) {
Log::error('WeightTestRunner exception: ' . $e->getMessage(), ['record_id' => $recordId, 'trace' => $e->getTraceAsString()]);
@@ -87,9 +141,11 @@ class WeightTestRunner
}
}
private function rowForInsert(array $row): array
private function rowForInsert(array $row, int $rewardConfigRecordId): array
{
$out = [];
$out = [
'reward_config_record_id' => $rewardConfigRecordId,
];
$keys = [
'player_id', 'admin_id', 'lottery_config_id', 'lottery_type', 'is_win', 'win_coin',
'super_win_coin', 'reward_win_coin', 'use_coins', 'direction', 'reward_config_id',
@@ -134,14 +190,20 @@ class WeightTestRunner
}
}
/**
* 标记测试成功并记录平台总盈利 platform_profit
* 通过关联 DicePlayRecordTestreward_config_record_id统计付费(lottery_type=0)次数×100 - win_coin 求和
*/
private function markSuccess(int $recordId, array $resultCounts, array $tierCounts): void
{
$platformProfit = DiceRewardConfigRecord::computePlatformProfitFromRelated($recordId);
$record = DiceRewardConfigRecord::find($recordId);
if ($record) {
$record->status = DiceRewardConfigRecord::STATUS_SUCCESS;
$record->result_counts = $resultCounts;
$record->tier_counts = $tierCounts;
$record->remark = null;
$record->platform_profit = $platformProfit;
$record->save();
}
}
@@ -149,8 +211,9 @@ class WeightTestRunner
private function markFailed(int $recordId, string $message): void
{
DiceRewardConfigRecord::where('id', $recordId)->update([
'status' => DiceRewardConfigRecord::STATUS_FAIL,
'remark' => mb_substr($message, 0, 500),
'status' => DiceRewardConfigRecord::STATUS_FAIL,
'remark' => mb_substr($message, 0, 500),
'platform_profit' => null,
]);
}
}

View File

@@ -8,6 +8,7 @@ namespace app\dice\model\play_record_test;
use plugin\saiadmin\basic\think\BaseModel;
use app\dice\model\reward_config\DiceRewardConfig;
use app\dice\model\reward_config_record\DiceRewardConfigRecord;
use app\dice\model\lottery_pool_config\DiceLotteryPoolConfig;
use think\model\relation\BelongsTo;
@@ -33,6 +34,7 @@ use think\model\relation\BelongsTo;
* @property $super_win_coin 中大奖平台币
* @property $reward_win_coin 摇色子中奖平台币
* @property $admin_id 所属管理员
* @property int|null $reward_config_record_id 关联 DiceRewardConfigRecord.id权重测试记录
*/
class DicePlayRecordTest extends BaseModel
{
@@ -66,6 +68,15 @@ class DicePlayRecordTest extends BaseModel
return $this->belongsTo(DiceRewardConfig::class, 'reward_config_id', 'id');
}
/**
* 关联的权重测试记录
* reward_config_record_id -> DiceRewardConfigRecord.id
*/
public function diceRewardConfigRecord(): BelongsTo
{
return $this->belongsTo(DiceRewardConfigRecord::class, 'reward_config_record_id', 'id');
}
/** 抽奖类型 0=付费 1=赠送 */
public function searchLotteryTypeAttr($query, $value)
{
@@ -119,4 +130,12 @@ class DicePlayRecordTest extends BaseModel
$query->whereRaw('1=0');
}
}
/** 点数和 roll_number摇取点数和 5-30 */
public function searchRollNumberAttr($query, $value)
{
if ($value !== '' && $value !== null) {
$query->where('roll_number', '=', $value);
}
}
}

View File

@@ -6,7 +6,9 @@
// +----------------------------------------------------------------------
namespace app\dice\model\reward_config_record;
use app\dice\model\play_record_test\DicePlayRecordTest;
use plugin\saiadmin\basic\think\BaseModel;
use think\model\relation\HasMany;
/**
* 奖励配置权重测试记录模型
@@ -17,15 +19,24 @@ use plugin\saiadmin\basic\think\BaseModel;
* @property int $test_count 测试次数 100/500/1000/5000/10000
* @property array $weight_config_snapshot 测试时权重配比快照:按档位 id,grid_number,tier,weight
* @property array $tier_weights_snapshot 测试时 T1-T5 档位权重快照(来自奖池配置)
* @property int|null $lottery_config_id 测试时使用的奖池配置 ID
* @property int|null $lottery_config_id 测试时使用的奖池配置 ID(兼容旧:付费+免费共用)
* @property int|null $paid_lottery_config_id 付费抽奖奖池配置 ID默认 type=0
* @property int|null $free_lottery_config_id 免费抽奖奖池配置 ID默认 type=1
* @property int $total_play_count 总模拟次数s_count+n_count
* @property int $over_play_count 已完成次数
* @property int $status 状态 -1失败 0进行中 1成功
* @property string|null $remark 失败时记录原因
* @property int $s_count 顺时针模拟次数
* @property int $n_count 逆时针模拟次数
* @property int $s_count 顺时针模拟次数(兼容旧数据)
* @property int $n_count 逆时针模拟次数(兼容旧数据)
* @property int $paid_s_count 付费抽奖顺时针次数
* @property int $paid_n_count 付费抽奖逆时针次数
* @property int $free_s_count 免费抽奖顺时针次数
* @property int $free_n_count 免费抽奖逆时针次数
* @property array|null $paid_tier_weights 付费自定义档位权重 T1-T5
* @property array|null $free_tier_weights 免费自定义档位权重 T1-T5
* @property array $result_counts 落点统计 grid_number=>出现次数
* @property array|null $tier_counts 档位出现次数 T1=>count
* @property float|null $platform_profit 平台赚取金额付费抽取次数×100-玩家总收益)
* @property int|null $admin_id 执行测试的管理员ID
* @property string|null $create_time 创建时间
*/
@@ -33,8 +44,10 @@ class DiceRewardConfigRecord extends BaseModel
{
/** 状态:失败 */
public const STATUS_FAIL = -1;
/** 状态:进行中 */
/** 状态:待执行(队列中) */
public const STATUS_RUNNING = 0;
/** 状态:执行中(已被某进程领取,防止定时器重入重复执行) */
public const STATUS_EXECUTING = 2;
/** 状态:成功 */
public const STATUS_SUCCESS = 1;
@@ -42,7 +55,31 @@ class DiceRewardConfigRecord extends BaseModel
protected $table = 'dice_reward_config_record';
protected $json = ['weight_config_snapshot', 'tier_weights_snapshot', 'result_counts', 'tier_counts'];
protected $json = ['weight_config_snapshot', 'tier_weights_snapshot', 'result_counts', 'tier_counts', 'paid_tier_weights', 'free_tier_weights'];
protected $jsonAssoc = true;
/**
* 关联的测试抽奖记录(通过 reward_config_record_id
*/
public function playRecordTests(): HasMany
{
return $this->hasMany(DicePlayRecordTest::class, 'reward_config_record_id', 'id');
}
/**
* 根据关联的 DicePlayRecordTest 统计平台赚取平台币
* platform_profit = 关联的付费(lottery_type=0)抽取次数 × 100 - 关联的 win_coin 求和
* @param int $recordId dice_reward_config_record.id
* @return float
*/
public static function computePlatformProfitFromRelated(int $recordId): float
{
$paidCount = DicePlayRecordTest::where('reward_config_record_id', $recordId)
->where('lottery_type', 0)
->count();
$sumWinCoin = (float) DicePlayRecordTest::where('reward_config_record_id', $recordId)
->sum('win_coin');
return round($paidCount * 100 - $sumWinCoin, 2);
}
}