master
cody 3 months ago
parent d253af6dad
commit d8c404f960

@ -0,0 +1,194 @@
<?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} 行");
}
}
}
}
}
}
Loading…
Cancel
Save