feat: 重构代码架构 + 新增报表/跟单/输赢统计功能

- 拆分 HomeController → TransactionController, ReportWebController, FollowPlanController
- 新增 Service 层: TransactionService, ReportService, FollowPlanService
- pk10.php JS 抽离为 4 个独立文件 (sound/race/bet/poll)
- 前台新增报表查询页面 (/report) + 跟单计划页面 (/follow-plan)
- 后台新增跟单计划管理 + 用户输赢明细统计
- 封盘状态显示倒计时 (x:xx)
- 音效仅在开奖弹窗打开时播放
- 路由按模块分组整理
- autoload 支持 App\Services 命名空间
This commit is contained in:
li
2026-03-27 18:41:06 +08:00
parent 48cfe4be04
commit 4a94fe36cf
87 changed files with 8681 additions and 646 deletions
+134 -2
View File
@@ -61,12 +61,23 @@ class PK10PeriodController extends AdminBaseController {
}
$periodNumber = PK10Algorithm::generatePeriodNumber($gameId);
$createdAt = date('Y-m-d H:i:s');
$this->db->insert('periods', [
'game_id' => $gameId,
'period_number' => $periodNumber,
'status' => 'pending',
'created_at' => date('Y-m-d H:i:s'),
'start_time' => $createdAt,
'created_at' => $createdAt,
]);
$periodId = (int)$this->db->id();
if ($periodId > 0) {
$this->queueCountdownPushes($gameId, [
'id' => $periodId,
'period_number' => $periodNumber,
'created_at' => $createdAt,
]);
}
$this->json(['status' => 'success', 'message' => 'New period started', 'period_number' => $periodNumber]);
}
@@ -83,6 +94,14 @@ class PK10PeriodController extends AdminBaseController {
}
$this->db->update('periods', ['status' => 'locked'], ['id' => $periodId]);
$this->queueBotPushByGame($period['game_id'], 'countdown', [
'event' => 'period_locked',
'period_id' => (int)$period['id'],
'period_number' => (string)($period['period_number'] ?? ''),
'message' => 'Period locked',
], [
'remind_close_countdown' => 1,
]);
$this->json(['status' => 'success', 'message' => 'Period locked']);
}
@@ -122,7 +141,8 @@ class PK10PeriodController extends AdminBaseController {
]);
if (!empty($waterConfig) && !empty($bets)) {
$result = PK10Algorithm::generateControlledResult($bets, $waterConfig);
$targetProfitRate = floatval(\App\Core\SettingsHelper::get('target_profit_rate'));
$result = PK10Algorithm::generateControlledResult($bets, $waterConfig, 100, $targetProfitRate);
} else {
$result = PK10Algorithm::generateResult();
}
@@ -150,6 +170,17 @@ class PK10PeriodController extends AdminBaseController {
'rank_10' => $result[9], 'champion_sum' => $sum,
]);
$this->queueBotPushByGame($period['game_id'], 'draw', [
'event' => 'period_drawn',
'period_id' => (int)$period['id'],
'period_number' => (string)($period['period_number'] ?? ''),
'result' => $result,
'champion_sum' => $sum,
'manual' => !empty($manual),
], [
'remind_draw_result' => 1,
], (string)($period['period_number'] ?? ''));
$this->db->medoo->pdo->commit();
} catch (\Throwable $e) {
$this->db->medoo->pdo->rollBack();
@@ -180,6 +211,15 @@ class PK10PeriodController extends AdminBaseController {
$bets = $this->db->select('bets', '*', ['period_id' => $periodId, 'status' => 'pending']);
if (empty($bets)) {
$this->db->update('periods', ['status' => 'settled'], ['id' => $periodId]);
$this->queueBotPushByGame($period['game_id'], 'system', [
'event' => 'period_settled',
'period_id' => (int)$period['id'],
'period_number' => (string)($period['period_number'] ?? ''),
'wins' => 0,
'losses' => 0,
'total_payout' => 0,
'message' => 'No bets to settle',
], [], (string)($period['period_number'] ?? ''));
$this->json(['status' => 'success', 'message' => 'No bets to settle', 'wins' => 0, 'losses' => 0]);
return;
}
@@ -230,6 +270,14 @@ class PK10PeriodController extends AdminBaseController {
$this->settleAgentCommissions($bets, $periodId);
$this->db->update('periods', ['status' => 'settled'], ['id' => $periodId]);
$this->queueBotPushByGame($period['game_id'], 'system', [
'event' => 'period_settled',
'period_id' => (int)$period['id'],
'period_number' => (string)($period['period_number'] ?? ''),
'wins' => $wins,
'losses' => $losses,
'total_payout' => (float)$totalPayout,
], [], (string)($period['period_number'] ?? ''));
$this->db->medoo->pdo->commit();
} catch (\Throwable $e) {
$this->db->medoo->pdo->rollBack();
@@ -298,6 +346,90 @@ class PK10PeriodController extends AdminBaseController {
return $game ? (int)$game : 0;
}
private function queueCountdownPushes(int $gameId, array $period): void {
if ($gameId <= 0 || empty($period['id']) || empty($period['period_number'])) {
return;
}
$groups = $this->db->select('bot_groups', ['id', 'countdown_config'], [
'game_id' => $gameId,
'status' => 1,
'remind_close_countdown' => 1,
]) ?: [];
if (empty($groups)) {
return;
}
$game = $this->db->get('games', ['period_duration', 'lock_before_end'], ['id' => $gameId]);
$periodDuration = (int)($game['period_duration'] ?? 300);
$lockBeforeEnd = max(0, (int)($game['lock_before_end'] ?? 30));
$periodCreatedAt = strtotime((string)($period['created_at'] ?? ''));
if ($periodCreatedAt <= 0) {
$periodCreatedAt = time();
}
$closeAtTs = $periodCreatedAt + max(0, $periodDuration - $lockBeforeEnd);
$now = date('Y-m-d H:i:s');
foreach ($groups as $group) {
$decoded = json_decode((string)($group['countdown_config'] ?? ''), true);
if (!is_array($decoded) || empty($decoded)) {
continue;
}
$secondsList = array_values(array_unique(array_filter(array_map('intval', $decoded), static function (int $seconds) use ($periodDuration, $lockBeforeEnd) {
return $seconds > 0 && $seconds <= max(0, $periodDuration - $lockBeforeEnd);
})));
rsort($secondsList);
foreach ($secondsList as $seconds) {
$dispatchAtTs = $closeAtTs - $seconds;
if ($dispatchAtTs <= $periodCreatedAt) {
continue;
}
$payload = [
'event' => 'countdown_tick',
'period_id' => (int)$period['id'],
'period_number' => (string)$period['period_number'],
'countdown_seconds' => $seconds,
'dispatch_at' => date('Y-m-d H:i:s', $dispatchAtTs),
'message' => sprintf('Bet closes in %d seconds', $seconds),
];
$this->db->insert('bot_push_logs', [
'group_id' => (int)$group['id'],
'period_number' => (string)$period['period_number'],
'push_type' => 'countdown',
'payload_json' => json_encode($payload, JSON_UNESCAPED_UNICODE),
'status' => 'pending',
'created_at' => $now,
'updated_at' => $now,
]);
}
}
}
private function queueBotPushByGame(int $gameId, string $pushType, array $payload, array $groupFilters = [], ?string $periodNumber = null): void {
if ($gameId <= 0) {
return;
}
$where = array_merge(['game_id' => $gameId, 'status' => 1], $groupFilters);
$groups = $this->db->select('bot_groups', ['id'], $where) ?: [];
$now = date('Y-m-d H:i:s');
foreach ($groups as $group) {
$this->db->insert('bot_push_logs', [
'group_id' => (int)$group['id'],
'period_number' => $periodNumber !== null ? $periodNumber : ((string)($payload['period_number'] ?? '') ?: null),
'push_type' => $pushType,
'payload_json' => json_encode($payload, JSON_UNESCAPED_UNICODE),
'status' => 'pending',
'created_at' => $now,
'updated_at' => $now,
]);
}
}
private function json(array $data) {
header('Content-Type: application/json');
echo json_encode($data);