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 ''; } }