2026_11_11_130000_create_simulation_tables.php 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108
  1. <?php
  2. use Illuminate\Database\Migrations\Migration;
  3. use Illuminate\Database\Schema\Blueprint;
  4. use Illuminate\Support\Facades\DB;
  5. use Illuminate\Support\Facades\Schema;
  6. /** 模拟管理:自动任务的模板、运行、事件三张表,以及 config 表里的模拟设置默认值(默认关闭)。 */
  7. return new class extends Migration {
  8. private const SETTINGS = [
  9. 'simulation_enabled' => ['0', '模拟管理总开关:1 开启'],
  10. 'simulation_max_concurrency' => ['2', '模拟自动任务同时运行数上限'],
  11. 'simulation_rate_per_minute' => ['30', '模拟自动任务每分钟动作数上限'],
  12. 'simulation_max_members_per_task' => ['20', '单次创建/单个任务的模拟会员数上限'],
  13. 'simulation_max_members_total' => ['500', '模拟会员总数上限'],
  14. 'simulation_max_amount' => ['10000', '模拟充值/提现/下注单次金额上限'],
  15. 'simulation_initial_balance' => ['0', '新建模拟会员的初始余额'],
  16. 'simulation_wager_bypass' => ['1', '模拟提现是否跳过打码量:1 跳过'],
  17. 'simulation_default_domain' => ['sim.local', '模拟会员默认注册/登录域名'],
  18. 'simulation_task_members' => ['5', '一键任务:注册的模拟会员数'],
  19. 'simulation_task_recharge' => ['1000', '一键任务:每人充值金额'],
  20. 'simulation_task_bet_category' => ['mixed', '一键任务:下注分类 sport 体育 / lottery 彩票 / third 第三方游戏 / mixed 混合'],
  21. 'simulation_task_bet_amount' => ['100', '一键任务:单次下注金额'],
  22. 'simulation_task_bet_count' => ['3', '一键任务:每人下注次数'],
  23. 'simulation_task_withdraw' => ['200', '一键任务:每人提现金额,0 为不提现'],
  24. 'simulation_task_interval' => ['2', '一键任务:每个动作之间的间隔秒数'],
  25. 'simulation_schedule_enabled' => ['0', '持续运行开关:1 在日期区间内一轮接一轮自动启动一键任务'],
  26. 'simulation_schedule_start' => ['', '持续运行开始日期 YYYY-MM-DD'],
  27. 'simulation_schedule_end' => ['', '持续运行结束日期 YYYY-MM-DD(含当天)'],
  28. 'simulation_schedule_gap_minutes' => ['10', '持续运行:上一轮结束到下一轮开始的间隔分钟数'],
  29. ];
  30. public function up(): void
  31. {
  32. if (!Schema::hasTable('simulation_tasks')) {
  33. Schema::create('simulation_tasks', function (Blueprint $t) {
  34. $t->id();
  35. $t->string('name', 100);
  36. $t->json('steps');
  37. $t->string('target_mode', 16)->default('new')->comment('new 新建会员 / existing 使用已有模拟会员');
  38. $t->unsignedInteger('member_count')->default(1);
  39. $t->string('remark', 255)->default('');
  40. $t->unsignedBigInteger('created_by')->nullable();
  41. $t->timestamps();
  42. });
  43. }
  44. if (!Schema::hasTable('simulation_task_runs')) {
  45. Schema::create('simulation_task_runs', function (Blueprint $t) {
  46. $t->id();
  47. $t->unsignedBigInteger('task_id')->nullable();
  48. $t->string('name', 100);
  49. $t->json('steps');
  50. $t->json('member_ids');
  51. $t->unsignedInteger('member_count')->default(1);
  52. $t->string('status', 16)->default('queued')->comment('queued running paused stopped completed failed');
  53. $t->unsignedInteger('total')->default(0);
  54. $t->unsignedInteger('completed')->default(0);
  55. $t->unsignedInteger('failed')->default(0);
  56. $t->json('cursor')->nullable();
  57. $t->dateTime('next_run_at')->nullable();
  58. $t->dateTime('started_at')->nullable();
  59. $t->dateTime('finished_at')->nullable();
  60. $t->unsignedBigInteger('created_by')->nullable();
  61. $t->string('last_error', 500)->nullable();
  62. $t->timestamps();
  63. $t->index(['status', 'next_run_at']);
  64. });
  65. }
  66. if (!Schema::hasTable('simulation_task_events')) {
  67. Schema::create('simulation_task_events', function (Blueprint $t) {
  68. $t->id();
  69. $t->unsignedBigInteger('run_id');
  70. $t->string('member_id', 64)->default('');
  71. $t->unsignedInteger('step_index');
  72. $t->unsignedInteger('seq');
  73. $t->string('action', 16);
  74. $t->string('status', 16)->comment('ok failed');
  75. $t->string('ref_type', 32)->nullable();
  76. $t->string('ref_id', 64)->nullable();
  77. $t->decimal('amount', 22, 2)->nullable();
  78. $t->string('message', 500)->default('');
  79. $t->dateTime('created_at');
  80. $t->unique(['run_id', 'step_index', 'member_id', 'seq'], 'simulation_task_events_step_unique');
  81. });
  82. }
  83. if (Schema::hasTable('config')) {
  84. $hasRemark = Schema::hasColumn('config', 'remark');
  85. $hasGroup = Schema::hasColumn('config', 'group_id');
  86. $hasTimes = Schema::hasColumn('config', 'created_at');
  87. foreach (self::SETTINGS as $field => [$value, $remark]) {
  88. if (DB::table('config')->where('field', $field)->exists()) continue;
  89. $row = ['field' => $field, 'val' => $value];
  90. if ($hasRemark) $row['remark'] = $remark;
  91. if ($hasGroup) $row['group_id'] = 1;
  92. if ($hasTimes) $row += ['created_at' => now(), 'updated_at' => now()];
  93. DB::table('config')->insert($row);
  94. }
  95. }
  96. }
  97. public function down(): void
  98. {
  99. Schema::dropIfExists('simulation_task_events');
  100. Schema::dropIfExists('simulation_task_runs');
  101. Schema::dropIfExists('simulation_tasks');
  102. if (Schema::hasTable('config')) DB::table('config')->whereIn('field', array_keys(self::SETTINGS))->delete();
  103. }
  104. };