|
|
<?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} 行");
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|