|
|
@@ -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,
|
|
|
+ ];
|
|
|
+ }
|
|
|
+}
|