setName('clean:placeholderUsername')
->setDescription('清洗 users 表占位用户名(默认 dry-run,--apply=1 才写库)')
->addOption('companyId', null, Option::VALUE_OPTIONAL, '公司 ID,0=全部', 0)
->addOption('apply', null, Option::VALUE_OPTIONAL, '1=实际写库,默认 0 仅统计', 0)
->addOption('limit', null, Option::VALUE_OPTIONAL, '最多处理条数,0=全量', 0);
}
protected function execute(Input $input, Output $output)
{
$companyId = max(0, (int)$input->getOption('companyId'));
$apply = (string)$input->getOption('apply') === '1';
$limit = max(0, (int)$input->getOption('limit'));
$query = Db::name('users')->where(function ($q) {
$q->whereIn('username', self::PLACEHOLDERS)
->whereOr('username', '')
->whereOr('username', null);
});
if ($companyId > 0) {
$query->where('companyId', $companyId);
}
$total = (int)(clone $query)->count();
$rows = (clone $query)
->field('id,companyId,username,phone,account')
->limit($limit > 0 ? $limit : 1000)
->select();
$output->writeln(sprintf(
'[clean:placeholderUsername] 占位用户名命中 %d 条(companyId=%s, mode=%s)',
$total,
$companyId ?: 'ALL',
$apply ? 'APPLY' : 'DRY-RUN'
));
$updated = 0;
$skipped = 0;
foreach ($rows as $r) {
$fallback = $this->resolveFallback($r);
if ($fallback === '') {
$skipped++;
continue;
}
if (!$apply) {
$output->writeln(sprintf(
' - id=%d company=%s "%s" → "%s"',
$r['id'],
$r['companyId'] ?? '',
(string)($r['username'] ?? ''),
$fallback
));
continue;
}
Db::name('users')->where('id', $r['id'])->update(['username' => $fallback]);
$updated++;
}
$output->writeln(sprintf(
'[clean:placeholderUsername] done total=%d updated=%d skipped(无可推断回退)=%d',
$total,
$apply ? $updated : 0,
$skipped
));
if (!$apply) {
$output->writeln('dry-run 模式未写库;确认无误后加 --apply=1 执行。');
}
return 0;
}
/**
* 优先账号别名(非纯数字)→ 手机脱敏「客服****」;都无则空(跳过,不强改)
*/
private function resolveFallback(array $r): string
{
$account = trim((string)($r['account'] ?? ''));
if ($account !== '' && !is_numeric($account)) {
return $account;
}
$phone = preg_replace('/\D/', '', (string)($r['phone'] ?? ''));
if (strlen($phone) >= 4) {
return '客服' . substr($phone, -4);
}
return '';
}
}