| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- <?php
- namespace App\Console\Commands;
- use Illuminate\Console\Command;
- use Illuminate\Support\Facades\DB;
- 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)) {
- // 先尝试直接解码
- $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,
- ];
- }
- }
|