Просмотр исходного кода

feat: 批量修复题目里的选项多次json encode的问题

过卫栋 4 дней назад
Родитель
Сommit
8650816269
1 измененных файлов с 164 добавлено и 0 удалено
  1. 164 0
      app/Console/Commands/FixDoubleEncodedOptions.php

+ 164 - 0
app/Console/Commands/FixDoubleEncodedOptions.php

@@ -0,0 +1,164 @@
+<?php
+
+namespace App\Console\Commands;
+
+use Illuminate\Console\Command;
+use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Log;
+
+class FixDoubleEncodedOptions extends Command
+{
+    protected $signature = 'fix:options
+                            {--ids= : 指定题目ID,多个用逗号隔开,如: --ids=21757,21892}
+                            {--limit=10 : 限制处理数量,默认10条}
+                            {--execute : 真正执行修复,不加此参数则为 dry-run 模式}';
+
+    protected $description = '修复 questions 表中双重编码的 options 字段';
+
+    public function handle()
+    {
+        $ids = $this->option('ids');
+        $limit = (int) $this->option('limit');
+        $execute = $this->option('execute');
+
+        $this->info('');
+        $this->info($execute ? '=== 执行模式 ===' : '=== DRY-RUN 模式(不会修改数据)===');
+        $this->info('');
+
+        // 构建查询
+        $query = DB::connection('remote_mysql')
+            ->table('questions')
+            ->where('options', 'LIKE', '"{%')  // 双重编码特征:以 "{ 开头
+            ->whereNotNull('options');
+
+        // 如果指定了 IDs
+        if ($ids) {
+            $idArray = array_map('trim', explode(',', $ids));
+            $query->whereIn('id', $idArray);
+            $this->info("指定 ID: " . implode(', ', $idArray));
+        } else {
+            $query->limit($limit);
+            $this->info("限制数量: {$limit} 条");
+        }
+
+        $questions = $query->get(['id', 'options']);
+
+        if ($questions->isEmpty()) {
+            $this->warn('没有找到需要修复的数据');
+            return 0;
+        }
+
+        $this->info("找到 {$questions->count()} 条待处理记录");
+        $this->info('');
+        $this->table(['ID', '状态', '原始值 (前50字符)', '修复后 (前50字符)'], []);
+
+        $successCount = 0;
+        $failCount = 0;
+        $results = [];
+
+        foreach ($questions as $q) {
+            $original = $q->options;
+            $result = $this->tryFix($original);
+
+            if ($result['success']) {
+                $status = '<fg=green>可修复</>';
+                $successCount++;
+
+                if ($execute) {
+                    DB::connection('remote_mysql')
+                        ->table('questions')
+                        ->where('id', $q->id)
+                        ->update(['options' => $result['fixed']]);
+                    $status = '<fg=green>已修复</>';
+                }
+            } else {
+                $status = '<fg=red>失败: ' . $result['error'] . '</>';
+                $failCount++;
+            }
+
+            $results[] = [
+                $q->id,
+                $status,
+                mb_substr($original, 0, 50) . '...',
+                $result['success'] ? mb_substr($result['fixed'], 0, 50) . '...' : '-',
+            ];
+
+            // 详细日志
+            $this->line("ID: {$q->id}");
+            $this->line("  原始: " . mb_substr($original, 0, 80) . '...');
+            if ($result['success']) {
+                $this->line("  修复: " . mb_substr($result['fixed'], 0, 80) . '...');
+            } else {
+                $this->error("  错误: " . $result['error']);
+            }
+            $this->line('');
+        }
+
+        // 汇总
+        $this->info('=== 汇总 ===');
+        $this->info("可修复/已修复: {$successCount} 条");
+        if ($failCount > 0) {
+            $this->warn("失败: {$failCount} 条");
+        }
+
+        if (!$execute && $successCount > 0) {
+            $this->info('');
+            $this->warn('这是 DRY-RUN 模式,数据未被修改。');
+            $this->info('确认无误后,添加 --execute 参数真正执行:');
+            if ($ids) {
+                $this->info("  php artisan fix:options --ids={$ids} --execute");
+            } else {
+                $this->info("  php artisan fix:options --limit={$limit} --execute");
+            }
+        }
+
+        return 0;
+    }
+
+    /**
+     * 尝试修复双重编码的 JSON
+     */
+    private function tryFix(string $original): array
+    {
+        // 第一次解码
+        $decoded = json_decode($original, true);
+
+        if (json_last_error() !== JSON_ERROR_NONE) {
+            return [
+                'success' => false,
+                'error' => 'JSON解码失败: ' . json_last_error_msg(),
+                'fixed' => null,
+            ];
+        }
+
+        // 如果解码后是字符串,说明是双重编码
+        if (is_string($decoded)) {
+            $decoded = json_decode($decoded, true);
+            if (json_last_error() !== JSON_ERROR_NONE) {
+                return [
+                    'success' => false,
+                    'error' => '二次JSON解码失败: ' . json_last_error_msg(),
+                    'fixed' => null,
+                ];
+            }
+        }
+
+        // 验证结果是数组
+        if (!is_array($decoded)) {
+            return [
+                'success' => false,
+                'error' => '解码结果不是数组',
+                'fixed' => null,
+            ];
+        }
+
+        // 重新编码为标准 JSON
+        $fixed = json_encode($decoded, JSON_UNESCAPED_UNICODE);
+
+        return [
+            'success' => true,
+            'error' => null,
+            'fixed' => $fixed,
+        ];
+    }
+}