| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109 |
- <?php
- namespace App\Services\Simulation;
- use App\Constants\HttpStatus;
- use App\Support\MemberAudience;
- use Exception;
- use Illuminate\Support\Facades\DB;
- /** 模拟管理的设置(config 表 simulation_* 键)以及每个写操作都要过的两道检查。 */
- final class SimulationSettings
- {
- /** 键 => [默认值, 最小, 最大];最小为 null 的是字符串。 */
- public const KEYS = [
- 'simulation_enabled' => ['0', 0, 1],
- 'simulation_max_concurrency' => ['2', 1, 10],
- 'simulation_rate_per_minute' => ['30', 1, 120],
- 'simulation_max_members_per_task' => ['20', 1, 200],
- 'simulation_max_members_total' => ['500', 1, 5000],
- 'simulation_max_amount' => ['10000', 1, 1000000],
- 'simulation_initial_balance' => ['0', 0, 1000000],
- 'simulation_wager_bypass' => ['1', 0, 1],
- 'simulation_default_domain' => ['sim.local', null, null],
- // 一键启动的默认任务:注册 → 登录 → 充值 → 下注 → 结算 → 提现 → 退出
- 'simulation_task_members' => ['5', 1, 200],
- 'simulation_task_recharge' => ['1000', 1, 1000000],
- 'simulation_task_bet_category' => ['sport', null, null],
- 'simulation_task_bet_amount' => ['100', 1, 1000000],
- 'simulation_task_bet_count' => ['3', 1, 50],
- 'simulation_task_withdraw' => ['200', 0, 1000000],
- 'simulation_task_interval' => ['2', 0, 3600],
- // 持续运行:在日期区间内一轮接一轮地自动启动上面的默认任务
- 'simulation_schedule_enabled' => ['0', 0, 1],
- 'simulation_schedule_start' => ['', null, null],
- 'simulation_schedule_end' => ['', null, null],
- 'simulation_schedule_gap_minutes' => ['10', 0, 1440],
- ];
- public static function all(): array
- {
- $stored = DB::table('config')->whereIn('field', array_keys(self::KEYS))->pluck('val', 'field');
- $out = [];
- foreach (self::KEYS as $key => [$default]) $out[$key] = (string) ($stored[$key] ?? $default);
- return $out;
- }
- public static function get(string $key): string
- {
- return self::all()[$key];
- }
- public static function save(array $input): array
- {
- return DB::transaction(function () use ($input) {
- $before = self::all();
- foreach (self::KEYS as $key => [, $min, $max]) {
- if (!array_key_exists($key, $input)) continue;
- $value = trim((string) $input[$key]);
- if ($key === 'simulation_task_bet_category') {
- if (!in_array($value, ['sport', 'lottery'], true)) throw new Exception('默认任务的下注分类只能是体育或彩票', HttpStatus::CUSTOM_ERROR);
- } elseif (in_array($key, ['simulation_schedule_start', 'simulation_schedule_end'], true)) {
- if ($value !== '' && (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) || date('Y-m-d', (int) strtotime($value)) !== $value)) {
- throw new Exception('持续运行的日期格式应为 YYYY-MM-DD', HttpStatus::CUSTOM_ERROR);
- }
- } elseif ($min === null) {
- if ($value === '' || strlen($value) > 100) throw new Exception('域名格式不正确', HttpStatus::CUSTOM_ERROR);
- } elseif (!is_numeric($value) || $value < $min || $value > $max) {
- throw new Exception("{$key} 应在 {$min} 到 {$max} 之间", HttpStatus::CUSTOM_ERROR);
- }
- $exists = DB::table('config')->where('field', $key)->exists();
- if ($exists) DB::table('config')->where('field', $key)->update(['val' => $value]);
- else DB::table('config')->insert(['field' => $key, 'val' => $value]);
- }
- $after = self::all();
- if ($after['simulation_schedule_enabled'] === '1') {
- [$start, $end] = [$after['simulation_schedule_start'], $after['simulation_schedule_end']];
- if ($start === '' || $end === '' || $start > $end) throw new Exception('开启持续运行前,请填写正确的开始和结束日期', HttpStatus::CUSTOM_ERROR);
- if ((strtotime($end) - strtotime($start)) / 86400 > 92) throw new Exception('持续运行的日期区间不能超过 92 天', HttpStatus::CUSTOM_ERROR);
- }
- return ['before' => $before, 'after' => $after];
- });
- }
- public static function enabled(): bool
- {
- return self::get('simulation_enabled') === '1';
- }
- public static function assertEnabled(): void
- {
- if (!MemberAudience::enabled()) throw new Exception('用户类型字段尚未迁移,无法使用模拟管理', HttpStatus::CUSTOM_ERROR);
- if (!self::enabled()) throw new Exception('模拟功能未开启,请先在“模拟设置”中开启', HttpStatus::CUSTOM_ERROR);
- }
- /** 写操作的目标必须是后台模拟会员,返回该会员。 */
- public static function member($memberId): object
- {
- $user = DB::table('users')->where('member_id', (string) $memberId)->where('user_type', MemberAudience::TYPE_SIMULATED)->first();
- if (!$user) throw new Exception('只能操作模拟会员', HttpStatus::CUSTOM_ERROR);
- return $user;
- }
- public static function assertAmount($amount): string
- {
- if (!is_numeric($amount) || $amount <= 0) throw new Exception('金额格式不正确', HttpStatus::CUSTOM_ERROR);
- if ($amount > (float) self::get('simulation_max_amount')) throw new Exception('金额超过模拟设置的单次上限', HttpStatus::CUSTOM_ERROR);
- return number_format((float) $amount, 2, '.', '');
- }
- }
|