| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 |
- <?php
- namespace App\Services\Simulation;
- use App\Constants\HttpStatus;
- use App\Support\MemberAudience;
- use Exception;
- use Illuminate\Support\Facades\Cache;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Schema;
- /**
- * 重置:删除全部后台模拟会员以及他们在业务表、报表投影表里的数据。不可恢复。
- * 每条删除都以“键列 IN (SELECT … FROM users WHERE user_type = 2)”为条件,不拼接内存里的编号,不清空整表。
- */
- final class SimulationResetService
- {
- public const CONFIRM_TEXT = '重置模拟数据';
- private const TOKEN_KEY = 'simulation_reset_token';
- /** 表 => [表里的会员列, 对应的 users 列] */
- private const TABLES = [
- 'balance_logs' => ['member_id', 'member_id'], 'payment_orders' => ['member_id', 'member_id'], 'withdraws' => ['member_id', 'member_id'],
- 'recharges' => ['member_id', 'member_id'], 'wallet_freeze_records' => ['member_id', 'member_id'], 'wager_records' => ['member_id', 'member_id'],
- 'lhc_order_item' => ['member_id', 'member_id'], 'lhc_order' => ['member_id', 'member_id'], 'sport_game_order' => ['member_id', 'member_id'],
- 'jisu_game_order' => ['member_id', 'member_id'], 'bets' => ['member_id', 'member_id'], 'backflow' => ['member_id', 'member_id'],
- 'rebates' => ['member_id', 'member_id'], 'activity_user' => ['member_id', 'member_id'],
- 'sport_order' => ['user_id', 'user_id'], 'user_login' => ['user_id', 'user_id'], 'user_session' => ['user_id', 'user_id'], 'yuebao_item' => ['user_id', 'user_id'],
- 'share_bet_facts' => ['user_id', 'id'], 'third_game_orders' => ['user_id', 'id'], 'reporting_orders' => ['user_id', 'id'],
- 'reporting_events' => ['user_id', 'id'], 'reporting_contexts' => ['user_id', 'id'], 'reporting_activity' => ['user_id', 'id'],
- 'member_daily_reports' => ['user_id', 'id'],
- 'wallets' => ['member_id', 'member_id'],
- ];
- private static function scoped(string $table)
- {
- [$column, $userColumn] = self::TABLES[$table];
- return DB::table($table)->whereIn($column, MemberAudience::simulatedMembers($userColumn));
- }
- private static function tables(): array
- {
- return array_values(array_filter(array_keys(self::TABLES), fn ($table) => Schema::hasTable($table) && Schema::hasColumn($table, self::TABLES[$table][0])));
- }
- private static function assertSafe(): void
- {
- if (!MemberAudience::enabled()) throw new Exception('用户类型字段尚未迁移', HttpStatus::CUSTOM_ERROR);
- if (DB::table('simulation_task_runs')->whereIn('status', ['queued', 'running', 'paused'])->exists()) {
- throw new Exception('还有未结束的自动任务,请先停止', HttpStatus::CUSTOM_ERROR);
- }
- // 模拟会员一定是 from = 2 且没有代理;出现别的情况说明 user_type 被误标,拒绝批量删除
- $odd = DB::table('users')->where('user_type', MemberAudience::TYPE_SIMULATED)
- ->where(fn ($q) => $q->where('from', '<>', '2')->orWhereNull('from')->orWhereNotNull('agent_id'))->count();
- if ($odd > 0) throw new Exception("有 {$odd} 个标记为模拟的会员不符合模拟会员特征(来源或代理异常),已中止,请人工核查", HttpStatus::CUSTOM_ERROR);
- }
- /** 预览将删除的行数,并发放一次性确认口令(10 分钟有效)。 */
- public static function preview(): array
- {
- self::assertSafe();
- $counts = ['users' => DB::table('users')->where('user_type', MemberAudience::TYPE_SIMULATED)->count()];
- foreach (self::tables() as $table) $counts[$table] = self::scoped($table)->count();
- $counts['simulation_task_runs'] = DB::table('simulation_task_runs')->count();
- $counts['simulation_task_events'] = DB::table('simulation_task_events')->count();
- $token = bin2hex(random_bytes(16));
- Cache::put(self::TOKEN_KEY, $token, 600);
- return ['counts' => $counts, 'confirm_token' => $token, 'confirm_text' => self::CONFIRM_TEXT];
- }
- public static function reset(string $token, string $text): array
- {
- if ($text !== self::CONFIRM_TEXT) throw new Exception('确认短语不正确', HttpStatus::CUSTOM_ERROR);
- $expected = Cache::pull(self::TOKEN_KEY);
- if (!$expected || !hash_equals((string) $expected, $token)) throw new Exception('确认口令已失效,请重新预览', HttpStatus::CUSTOM_ERROR);
- self::assertSafe();
- $deleted = [];
- DB::transaction(function () use (&$deleted) {
- foreach (self::tables() as $table) $deleted[$table] = self::scoped($table)->delete();
- $deleted['simulation_task_events'] = DB::table('simulation_task_events')->delete();
- $deleted['simulation_task_runs'] = DB::table('simulation_task_runs')->delete();
- $deleted['users'] = DB::table('users')->where('user_type', MemberAudience::TYPE_SIMULATED)->where('from', '2')->whereNull('agent_id')->delete();
- });
- return $deleted;
- }
- }
|