feat: 新增命令和迁移以优化抽奖数据管理

- 新增 `LotteryDevPruneDrawBacklogCommand` 命令,用于按营业日区间删除积压的抽奖期号,并支持干运行和级联删除相关数据。
- 添加多个迁移文件以同步数据库结构,包括重命名重复的迁移文件、添加用户名字段、迁移抽奖状态到领域字典、合并显示名称字段、扩展审计日志目标类型字段,以及细化后台权限管理。
- 更新 `AdminRbacAndUserSeeder` 以包含角色代码字段,确保一致性与可维护性。
This commit is contained in:
2026-05-25 15:33:33 +08:00
parent e27a00f260
commit 6a8cdbe3b8
9 changed files with 258 additions and 4 deletions

View File

@@ -0,0 +1,119 @@
<?php
namespace App\Console\Commands;
use App\Models\Draw;
use Illuminate\Console\Command;
use App\Services\Draw\DrawTickService;
use Illuminate\Support\Facades\DB;
/**
* 开发/测试库:按营业日区间删除积压期号(级联清理关联数据)。
*
* 仅允许 local / testing 环境执行。
*/
final class LotteryDevPruneDrawBacklogCommand extends Command
{
protected $signature = 'lottery:dev-prune-draw-backlog
{--from=2026-05-11 : 营业日起Y-m-d}
{--to=2026-05-22 : 营业日止Y-m-d}
{--dry-run : 只统计,不删除}
{--force : 跳过确认}
{--tick : 删除后执行 lottery:draw-tick积压多时可能占内存}';
protected $description = '【仅 dev/test】删除指定营业日区间的期号积压cascade';
public function handle(DrawTickService $tickService): int
{
if (! app()->environment(['local', 'testing'])) {
$this->error('Refused: only allowed in local or testing environment.');
return self::FAILURE;
}
$from = (string) $this->option('from');
$to = (string) $this->option('to');
$dryRun = (bool) $this->option('dry-run');
$query = Draw::query()->whereBetween('business_date', [$from, $to]);
$total = (int) $query->count();
if ($total === 0) {
$this->info("No draws found between {$from} and {$to}.");
return self::SUCCESS;
}
$byStatus = (clone $query)
->selectRaw('status, count(*) as c')
->groupBy('status')
->orderByDesc('c')
->pluck('c', 'status');
$drawIds = (clone $query)->pluck('id');
$ticketOrders = (int) DB::table('ticket_orders')->whereIn('draw_id', $drawIds)->count();
$this->table(
['Metric', 'Value'],
[
['Date range', "{$from} .. {$to}"],
['Draws to delete', (string) $total],
['Ticket orders (cascade)', (string) $ticketOrders],
],
);
$this->line('By status:');
foreach ($byStatus as $status => $count) {
$this->line(" {$status}: {$count}");
}
if ($dryRun) {
$this->info('Dry run — no rows deleted.');
return self::SUCCESS;
}
if (! $this->option('force') && ! $this->confirm("Delete {$total} draws and related rows?", false)) {
$this->warn('Aborted.');
return self::SUCCESS;
}
$deleted = 0;
(clone $query)->orderBy('id')->chunkById(200, function ($draws) use (&$deleted): void {
foreach ($draws as $draw) {
$draw->delete();
$deleted++;
}
$this->output->write('.');
});
$this->newLine();
$this->info("Deleted {$deleted} draws.");
if ($this->option('tick')) {
$report = $tickService->tick();
$this->info(sprintf(
'Post-tick: status_updates=%d rng=%d planned_created=%d',
array_sum($report['status_updates'] ?? []),
$report['rng_rung'],
$report['planned']['created'] ?? 0,
));
} else {
$this->comment('Skipped tick (pass --tick to run draw-tick after prune).');
}
$head = Draw::query()
->whereNotIn('status', ['settled', 'cancelled'])
->orderBy('draw_time')
->first(['draw_no', 'status', 'draw_time']);
if ($head !== null) {
$this->info("Hall pipeline head is now: {$head->draw_no} ({$head->status}) @ {$head->draw_time}");
} else {
$this->info('Hall pipeline head: none (all settled/cancelled).');
}
return self::SUCCESS;
}
}