135 lines
4.6 KiB
PHP
135 lines
4.6 KiB
PHP
<?php
|
||
/**
|
||
* 存客宝数据迁移 · 公司归属修复脚本(DRY-RUN 优先)
|
||
*
|
||
* 用途:把指定手机号 / 旧 companyId 下的计划、客资、积分、海报/渠道获客记录
|
||
* 迁移到目标 companyId(卡若指定)。默认 DRY-RUN:只输出 SQL,不执行。
|
||
*
|
||
* 用法:
|
||
* # 仅打印影响行数与映射,不执行
|
||
* docker compose exec -T server php /var/www/html/script/migration/migrate_company_attribution.php \
|
||
* sourcePhone=13779954946 fromCompany=15880802661 toCompany=914
|
||
*
|
||
* # 加 --apply 后真正执行(卡若手动确认后再加)
|
||
* docker compose exec -T server php /var/www/html/script/migration/migrate_company_attribution.php \
|
||
* sourcePhone=13779954946 fromCompany=15880802661 toCompany=914 apply=1
|
||
*
|
||
* 卡若 2026-05-27 · 数据迁移 D1/D2 公司归属修复
|
||
*/
|
||
|
||
declare(strict_types=1);
|
||
|
||
require __DIR__ . '/../../thinkphp/base.php';
|
||
|
||
use think\Db;
|
||
use think\App;
|
||
|
||
$app = new App(realpath(__DIR__ . '/../../application'));
|
||
$app->initialize();
|
||
|
||
// 参数解析
|
||
$args = [];
|
||
foreach ($argv as $arg) {
|
||
if (strpos($arg, '=') !== false) {
|
||
list($k, $v) = explode('=', $arg, 2);
|
||
$args[trim($k)] = trim($v);
|
||
}
|
||
}
|
||
|
||
$sourcePhone = $args['sourcePhone'] ?? '';
|
||
$fromCompany = (int)($args['fromCompany'] ?? 0);
|
||
$toCompany = (int)($args['toCompany'] ?? 0);
|
||
$apply = !empty($args['apply']);
|
||
|
||
if (!$sourcePhone || !$fromCompany || !$toCompany) {
|
||
fwrite(STDERR, "usage: sourcePhone=1XXX fromCompany=X toCompany=Y [apply=1]\n");
|
||
exit(1);
|
||
}
|
||
|
||
$mode = $apply ? '【APPLY 实际执行】' : '【DRY-RUN 不执行】';
|
||
echo "$mode source phone=$sourcePhone, from companyId=$fromCompany → to companyId=$toCompany\n";
|
||
|
||
// 步骤 1:定位待迁移对象
|
||
$users = Db::name('users')
|
||
->where('companyId', $fromCompany)
|
||
->where(function ($q) use ($sourcePhone) {
|
||
$q->where('phone', $sourcePhone)->whereOr('account', $sourcePhone);
|
||
})
|
||
->select();
|
||
|
||
if (!$users) {
|
||
echo "未找到符合条件的用户,退出。\n";
|
||
exit(0);
|
||
}
|
||
|
||
$userIds = array_column((array)$users, 'id');
|
||
echo "影响用户:" . count($userIds) . " 个 (ids=" . implode(',', $userIds) . ")\n";
|
||
|
||
$tables = [
|
||
// [表名, 字段, 描述]
|
||
['customer_acquisition_task', 'companyId', '获客计划(含海报/渠道/付款码等场景)'],
|
||
['tokens_company', 'companyId', '算力账户'],
|
||
['plan_distribution_channel', 'companyId', '渠道获客分销渠道'],
|
||
['plan_distribution_record', 'companyId', '渠道分销记录'],
|
||
['ckb_lead_records', 'companyId', '客资入池队列'],
|
||
['content_library', 'companyId', '内容库'],
|
||
['workbench_moments_sync', 'companyId', '工作台 · 朋友圈同步'],
|
||
['workbench_group_push', 'companyId', '工作台 · 群消息推送'],
|
||
['workbench_auto_like', 'companyId', '工作台 · 自动点赞'],
|
||
];
|
||
|
||
$plan = [];
|
||
$totalAffected = 0;
|
||
Db::startTrans();
|
||
try {
|
||
foreach ($tables as [$table, $field, $desc]) {
|
||
$exists = Db::query("SHOW TABLES LIKE '" . Db::name($table)->getTable() . "'");
|
||
if (!$exists) {
|
||
echo " · [SKIP] 表 $table 不存在\n";
|
||
continue;
|
||
}
|
||
$count = Db::name($table)->where($field, $fromCompany)->count();
|
||
echo " · $table ($desc): 待迁移 $count 行";
|
||
if ($apply && $count > 0) {
|
||
$affected = Db::name($table)
|
||
->where($field, $fromCompany)
|
||
->update([$field => $toCompany]);
|
||
$totalAffected += $affected;
|
||
echo " → 已迁移 $affected 行\n";
|
||
} else {
|
||
echo "\n";
|
||
}
|
||
$plan[$table] = $count;
|
||
}
|
||
|
||
if ($apply) {
|
||
Db::commit();
|
||
echo "\n✅ 迁移完成,共影响 $totalAffected 行\n";
|
||
} else {
|
||
Db::rollback();
|
||
echo "\n[DRY-RUN] 共可迁移 " . array_sum($plan) . " 行(未执行)。如确认无误,请追加 apply=1 重跑。\n";
|
||
}
|
||
} catch (\Throwable $e) {
|
||
Db::rollback();
|
||
fwrite(STDERR, "失败回滚:" . $e->getMessage() . "\n");
|
||
exit(2);
|
||
}
|
||
|
||
// 输出迁移报告
|
||
$reportDir = dirname(__DIR__, 2) . '/runtime/migration/' . $sourcePhone;
|
||
if (!is_dir($reportDir)) {
|
||
@mkdir($reportDir, 0755, true);
|
||
}
|
||
$reportPath = $reportDir . '/migrate-' . ($apply ? 'apply' : 'dryrun') . '-' . date('YmdHis') . '.json';
|
||
file_put_contents($reportPath, json_encode([
|
||
'sourcePhone' => $sourcePhone,
|
||
'fromCompany' => $fromCompany,
|
||
'toCompany' => $toCompany,
|
||
'mode' => $apply ? 'apply' : 'dry-run',
|
||
'userIds' => $userIds,
|
||
'plan' => $plan,
|
||
'totalAffected' => $totalAffected,
|
||
'finishedAt' => date('c'),
|
||
], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE));
|
||
echo "report: $reportPath\n";
|