| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141 |
- <?php
- namespace App\Console\Commands;
- use App\Services\PointsService;
- use Illuminate\Console\Command;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Schema;
- class BackfillPointsScriptCommand extends Command
- {
- /**
- * The name and signature of the console command.
- *
- * @var string
- */
- protected $signature = 'points:backfill-script
- {--type= : 只回填指定类型(video/image/chat),默认三种类型全部回填}
- {--dry-run : 只统计将回填的记录数,不实际更新}
- {--force : 重算已有锚点的记录(默认只处理缺失动漫/剧本的记录)}
- {--limit= : 最多处理记录数(默认全部)}';
- /**
- * The console command description.
- *
- * @var string
- */
- protected $description = '回填积分明细的动漫ID/动漫名与剧本ID/剧本名(历史数据无动漫/剧本维度时为空)';
- /**
- * 每批处理条数
- */
- const CHUNK_SIZE = 500;
- /**
- * Execute the console command.
- *
- * @param PointsService $pointsService
- * @return int
- */
- public function handle(PointsService $pointsService)
- {
- if (!Schema::hasColumn('mp_user_points_details', 'script_id')) {
- $this->error('mp_user_points_details 缺少 script_id/script_name 字段,请先执行 php artisan migrate');
- return 1;
- }
- $dryRun = (bool)$this->option('dry-run');
- $force = (bool)$this->option('force');
- $limit = (int)$this->option('limit');
- if ($limit < 0) {
- $limit = 0;
- }
- $type = trim((string)$this->option('type'));
- $types = in_array($type, ['video', 'image', 'chat'], true) ? [$type] : ['video', 'image', 'chat'];
- $query = DB::table('mp_user_points_details')
- ->select('id', 'type', 'task_id', 'charge_info')
- ->whereIn('type', $types)
- ->orderBy('id');
- if (!$force) {
- $query->where(function ($q) {
- $q->whereNull('anime_id')
- ->orWhereNull('anime_name')
- ->orWhereNull('script_id')
- ->orWhere('script_id', 0);
- });
- }
- $total = (clone $query)->count();
- $this->info('待回填明细总数: ' . $total . ($dryRun ? '(dry-run 模式,不更新)' : ''));
- if ($total <= 0) {
- return 0;
- }
- if ($limit > 0) {
- $this->info('本次最多处理: ' . $limit . ' 条');
- }
- $scanned = 0;
- $filled = 0;
- $skipped = 0;
- $query->chunkById(self::CHUNK_SIZE, function ($rows) use ($pointsService, $dryRun, $limit, &$scanned, &$filled, &$skipped) {
- /** @var array 剧本 => 待更新明细ID */
- $pending = [];
- foreach ($rows as $row) {
- if ($limit > 0 && $scanned >= $limit) {
- return false;
- }
- $scanned++;
- $chargeInfo = $row->charge_info;
- if (is_string($chargeInfo)) {
- $chargeInfo = json_decode($chargeInfo, true);
- }
- $chargeInfo = is_array($chargeInfo) ? $chargeInfo : [];
- $context = $pointsService->resolveScriptContext(
- $chargeInfo,
- $row->task_id === null ? null : (int)$row->task_id,
- (string)$row->type
- );
- if (empty($context['anime_id']) && empty($context['script_id'])) {
- $skipped++;
- continue;
- }
- $key = (int)$context['anime_id'] . '|' . (string)$context['anime_name'] . '|' . (int)$context['script_id'] . '|' . (string)$context['script_name'];
- $pending[$key][] = (int)$row->id;
- }
- foreach ($pending as $key => $ids) {
- list($animeId, $animeName, $scriptId, $scriptName) = explode('|', $key, 4);
- $filled += count($ids);
- if ($dryRun) {
- continue;
- }
- DB::table('mp_user_points_details')->whereIn('id', $ids)->update([
- 'anime_id' => (int)$animeId ?: null,
- 'anime_name' => $animeName !== '' ? $animeName : null,
- 'script_id' => (int)$scriptId ?: null,
- 'script_name' => $scriptName !== '' ? $scriptName : null,
- ]);
- }
- return true;
- });
- $this->info('扫描明细: ' . $scanned . ' 条,可回填: ' . $filled . ' 条,与剧本无关: ' . $skipped . ' 条');
- $this->info($dryRun ? 'dry-run 结束,未更新任何数据' : '回填完成');
- return 0;
- }
- }
|