option('dry-run'); if ($dryRun) { $this->warn('【预检模式】不会修改数据库'); } $duplicateGroups = $this->getDuplicateMobileUsers(); if ($duplicateGroups->isEmpty()) { $this->info('没有需要合并的重复手机号。'); return 0; } $this->info('共 ' . $duplicateGroups->count() . ' 组重复手机号待处理。'); $merged = 0; $failed = 0; foreach ($duplicateGroups as $mobile => $users) { // 按 id 升序,id 最大为“新”,保留;其余为“旧”,合并后软删 $sorted = $users->sortBy('id')->values(); $newUser = $sorted->last(); $oldUsers = $sorted->slice(0, -1); $newId = (int) $newUser->id; $oldIds = $oldUsers->pluck('id')->map(fn($v) => (int) $v)->toArray(); $this->line(''); $this->line("手机号: {$mobile} | 保留用户 id={$newId} ({$newUser->name}), 合并并删除: " . implode(', ', $oldIds)); if ($dryRun) { $this->listTransfers($newId, $oldIds); $merged++; continue; } try { DB::transaction(function () use ($newId, $oldIds) { foreach ($oldIds as $oldId) { $this->transferRelations($oldId, $newId); } User::whereIn('id', $oldIds)->delete(); // 软删除 }); $this->info(" 已合并并软删除旧用户: " . implode(', ', $oldIds)); $merged++; } catch (\Throwable $e) { $this->error(" 失败: " . $e->getMessage()); $failed++; } } $this->line(''); $this->info("完成: 成功 {$merged} 组" . ($failed > 0 ? ", 失败 {$failed} 组" : '') . ($dryRun ? '(未写入)' : '')); return $failed > 0 ? 1 : 0; } /** * 查询:有报名审核通过且手机号重复的用户,按手机号分组 */ protected function getDuplicateMobileUsers() { $sql = " SELECT u.id, u.name, u.mobile FROM users u WHERE u.deleted_at IS NULL AND u.mobile IS NOT NULL AND TRIM(u.mobile) != '' AND EXISTS ( SELECT 1 FROM course_signs cs WHERE cs.user_id = u.id AND cs.status = 1 AND cs.deleted_at IS NULL ) AND u.mobile IN ( SELECT u2.mobile FROM users u2 INNER JOIN course_signs cs2 ON cs2.user_id = u2.id AND cs2.status = 1 AND cs2.deleted_at IS NULL WHERE u2.deleted_at IS NULL AND u2.mobile IS NOT NULL AND TRIM(u2.mobile) != '' GROUP BY u2.mobile HAVING COUNT(DISTINCT u2.id) > 1 ) ORDER BY u.mobile, u.id "; $rows = DB::select($sql); return collect($rows)->groupBy('mobile'); } /** * 把 oldUserId 的关联全部改为 newUserId */ protected function transferRelations(int $oldUserId, int $newUserId): void { foreach ($this->tablesWithUserId as $table) { if (!Schema::hasTable($table) || !Schema::hasColumn($table, 'user_id')) { continue; } $n = DB::table($table)->where('user_id', $oldUserId)->update(['user_id' => $newUserId]); if ($n > 0) { $this->line(" {$table}.user_id: {$oldUserId} -> {$newUserId}, 更新 {$n} 行"); } } foreach ($this->tablesWithToUserId as $table) { if (!Schema::hasTable($table)) { continue; } if (Schema::hasColumn($table, 'user_id')) { $n = DB::table($table)->where('user_id', $oldUserId)->update(['user_id' => $newUserId]); if ($n > 0) { $this->line(" {$table}.user_id: {$oldUserId} -> {$newUserId}, 更新 {$n} 行"); } } if (Schema::hasColumn($table, 'to_user_id')) { $n = DB::table($table)->where('to_user_id', $oldUserId)->update(['to_user_id' => $newUserId]); if ($n > 0) { $this->line(" {$table}.to_user_id: {$oldUserId} -> {$newUserId}, 更新 {$n} 行"); } } } } /** * dry-run:只统计并打印每个表将更新的行数 */ protected function listTransfers(int $newUserId, array $oldIds): void { foreach ($oldIds as $oldId) { foreach ($this->tablesWithUserId as $table) { if (!Schema::hasTable($table) || !Schema::hasColumn($table, 'user_id')) { continue; } $n = DB::table($table)->where('user_id', $oldId)->count(); if ($n > 0) { $this->line(" [拟] {$table}.user_id: {$oldId} -> {$newUserId}, 约 {$n} 行"); } } foreach ($this->tablesWithToUserId as $table) { if (!Schema::hasTable($table)) { continue; } if (Schema::hasColumn($table, 'user_id')) { $n = DB::table($table)->where('user_id', $oldId)->count(); if ($n > 0) { $this->line(" [拟] {$table}.user_id: {$oldId} -> {$newUserId}, 约 {$n} 行"); } } if (Schema::hasColumn($table, 'to_user_id')) { $n = DB::table($table)->where('to_user_id', $oldId)->count(); if ($n > 0) { $this->line(" [拟] {$table}.to_user_id: {$oldId} -> {$newUserId}, 约 {$n} 行"); } } } } } }