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 = '可修复'; $successCount++; if ($execute) { DB::connection('remote_mysql') ->table('questions') ->where('id', $q->id) ->update(['options' => $result['fixed']]); $status = '已修复'; } } else { $status = '失败: ' . $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)) { // 先尝试直接解码 $secondDecode = json_decode($decoded, true); if (json_last_error() !== JSON_ERROR_NONE) { // 解码失败,可能是 LaTeX 中的反斜杠问题 // 问题:\times 中的 \t 是合法 JSON 转义(tab),\frac 中的 \f 也是(form feed) // 解决:第一次解码后,所有反斜杠都是 LaTeX 的,全部转义为双反斜杠 $escapedDecoded = str_replace('\\', '\\\\', $decoded); $secondDecode = json_decode($escapedDecoded, true); if (json_last_error() !== JSON_ERROR_NONE) { return [ 'success' => false, 'error' => '二次JSON解码失败: ' . json_last_error_msg(), 'fixed' => null, ]; } } $decoded = $secondDecode; } // 验证结果是数组 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, ]; } }