You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

195 lines
7.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Console\Commands;
use App\Models\User;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class MergeDuplicateUsersByMobile extends Command
{
protected $signature = 'merge_duplicate_users_by_mobile
{--dry-run : 只打印将执行的操作,不实际更新或删除}';
protected $description = '将手机号重复的用户合并:保留 id 最大的用户,把旧用户的关联数据转移到新用户后软删除旧用户(仅处理有报名审核通过的重复手机号)';
/**
* 需要把 user_id 从旧用户改成新用户的表
*/
protected array $tablesWithUserId = [
'course_signs',
'course_keeps',
'appointments',
'supply_demands',
'supply_demand_keeps',
'course_content_evaluation_forms',
'course_content_checks',
'course_appointment_totals',
'score_logs',
'third_appointment_logs',
];
/**
* 表里既有 user_id 又有 to_user_id都需要把旧用户 id 替换为新用户 id
*/
protected array $tablesWithToUserId = [
'dialogues',
'messages',
];
public function handle()
{
$dryRun = $this->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}");
}
}
}
}
}
}