*/ public static function create(int $count, array $options = []): array { SimulationSettings::assertEnabled(); $settings = SimulationSettings::all(); if ($count < 1 || $count > (int) $settings['simulation_max_members_per_task']) { throw new Exception('一次最多创建 ' . $settings['simulation_max_members_per_task'] . ' 个模拟会员', HttpStatus::CUSTOM_ERROR); } $existing = DB::table('users')->where('user_type', MemberAudience::TYPE_SIMULATED)->count(); if ($existing + $count > (int) $settings['simulation_max_members_total']) { throw new Exception('模拟会员总数将超过上限 ' . $settings['simulation_max_members_total'], HttpStatus::CUSTOM_ERROR); } $domain = trim((string) ($options['domain'] ?? '')) ?: $settings['simulation_default_domain']; $initial = $options['initial_balance'] ?? $settings['simulation_initial_balance']; $created = []; for ($i = 0; $i < $count; $i++) { $user = DB::transaction(function () use ($domain) { $sn = self::serial(); $user = (new User())->forceFill([ 'member_id' => $sn, 'user_id' => $sn, 'account' => 'sim_' . $sn, 'first_name' => '模拟_' . substr($sn, -4), 'username' => '', 'from' => '2', 'user_type' => MemberAudience::TYPE_SIMULATED, 'register_ip' => '127.0.0.1', 'register_domain' => $domain, 'last_active_time' => time(), ]); $user->save(); (new Wallet())->forceFill(['user_id' => $user->id, 'member_id' => $sn, 'coin' => 'USDT', 'net' => 'TRC20', 'available_balance' => 0])->save(); return $user; }); MemberAudience::remember($user->member_id); if (is_numeric($initial) && $initial > 0) SimulationFundService::adjust($user->member_id, $initial, 'credit', '模拟会员初始余额'); $created[] = ['id' => (int) $user->id, 'member_id' => (string) $user->member_id, 'account' => (string) $user->account]; } return $created; } /** 8 位 1-9 数字,与前台会员编号同形;纯数字是因为 wallets.member_id 是 bigint。 */ private static function serial(): string { for ($try = 0; $try < 50; $try++) { $sn = ''; for ($i = 0; $i < 8; $i++) $sn .= (string) random_int(1, 9); if (!DB::table('users')->where('member_id', $sn)->orWhere('user_id', $sn)->exists()) return $sn; } throw new Exception('会员编号生成失败,请重试', HttpStatus::CUSTOM_ERROR); } public static function setBanned($memberId, int $banned): void { $user = SimulationSettings::member($memberId); DB::table('users')->where('id', $user->id)->update(['is_banned' => $banned ? 1 : 0]); } /** 只写登录日志,不建会话、不签发 token:模拟会员没有可登录前台的凭证。 */ public static function login($memberId, string $platform = 'H5', ?string $domain = null): array { SimulationSettings::assertEnabled(); $user = SimulationSettings::member($memberId); self::logout($memberId); $row = ['user_id' => (string) $user->user_id, 'login_account' => (string) $user->account, 'login_ip' => '127.0.0.1', 'login_domain' => $domain ?: SimulationSettings::get('simulation_default_domain'), 'platform' => $platform, 'status' => UserLogin::STATUS_SUCCESS, 'created_at' => now(), 'updated_at' => now()]; $row = array_filter($row, fn ($column) => Schema::hasColumn('user_login', $column), ARRAY_FILTER_USE_KEY); $id = DB::table('user_login')->insertGetId($row); $update = ['last_active_time' => time()]; if (Schema::hasColumn('users', 'last_login_time')) $update += ['last_login_time' => time(), 'login_count' => DB::raw('COALESCE(login_count, 0) + 1')]; DB::table('users')->where('id', $user->id)->update($update); return ['id' => $id, 'member_id' => (string) $user->member_id]; } public static function logout($memberId): int { $user = SimulationSettings::member($memberId); if (!Schema::hasColumn('user_login', 'logout_time')) return 0; $open = DB::table('user_login')->where('user_id', (string) $user->user_id)->where('status', UserLogin::STATUS_SUCCESS)->whereNull('logout_time')->get(['id', 'created_at']); foreach ($open as $row) { DB::table('user_login')->where('id', $row->id)->update(['logout_time' => now(), 'online_duration' => max(0, now()->getTimestamp() - strtotime((string) $row->created_at)), 'updated_at' => now()]); } return $open->count(); } /** 当前“在线”的模拟会员:有未登出的成功登录记录。 */ public static function onlineUserIds() { $query = DB::table('user_login')->where('status', UserLogin::STATUS_SUCCESS)->whereIn('user_id', MemberAudience::simulatedMembers('user_id')); if (Schema::hasColumn('user_login', 'logout_time')) $query->whereNull('logout_time'); return $query->distinct()->pluck('user_id'); } public static function detail($memberId): array { $user = SimulationSettings::member($memberId); $wallet = DB::table('wallets')->where('member_id', $user->member_id)->first(); return [ 'user' => ['id' => $user->id, 'member_id' => $user->member_id, 'account' => $user->account, 'first_name' => $user->first_name, 'is_banned' => (int) ($user->is_banned ?? 0), 'register_domain' => $user->register_domain ?? '', 'created_at' => $user->created_at], 'wallet' => $wallet ? ['available_balance' => (string) $wallet->available_balance, 'frozen_balance' => (string) $wallet->frozen_balance, 'wager_required' => (string) ($wallet->wager_required ?? 0), 'wager_completed' => (string) ($wallet->wager_completed ?? 0)] : null, 'logs' => DB::table('balance_logs')->where('member_id', $user->member_id)->orderByDesc('id')->limit(20)->get(), ]; } }