## GitHub 同步说明 | 项 | 内容 | |:---|:---| | 仓库 | https://github.com/fnvtk/cunkebao_v3 | | 分支 | develop | | 方向 | 本地 → GitHub(单向 push) | | 是否拉取远程 | 否(未 fetch / pull / merge) | | 是否改本地源文件 | 否(仅 git add/commit,未编辑业务/文档正文) | | 远程基准 | origin/develop @b5b40e8b| | 本批工作区变更 | 999 files, +47622 / -4007 lines | ## 一并推送的本地已有 commit(此前未 push) 1.3aa7ecf8deploy: 官网 ckb.quwanzhi.com 宝塔上线 DNS+SSL+验收文档 2.16a70045fix(官网): 桌面导航仅保留一个留资 CTA 并重新上线 3.dbe7b7aafix(官网): Hero 改为主 CTA 按钮并去掉重复 1200+ 数据条 4.ac82726echore(部署): 五端上线验收46项并统一留资与缓存版本 ## 本 commit 目录统计 | 目录 | 文件数 | 说明 | |:---|---:|:---| | 开发文档/ | 639 | 10、项目管理、未完成项跨仓汇总、五端需求/部署/接口 | | Server/ | 129 | 后端 API、迁移、部署相关 | | Touchkebao/ | 80 | PC 工作台、获客、算力 | | 官网/ | 61 | ckb.quwanzhi.com 静态站与 CTA 迭代 | | Cunkebao/ | 49 | 移动端场景/设置/流量池 | | SuperAdmin/ | 26 | 超管后台 | | .cursor/ | 11 | 规则与 Skill | | 其他 | 14 | docker-compose、.obsidian、.codegraph 等 | ## 重点文件(本地为准) - 开发文档/1、需求/修改/未完成项与跨仓验收汇总.md - 开发文档/10、项目管理/账号密码与登录环境一览.md - 开发文档/10、项目管理/(新建项目管理目录与截图) - 官网/ 全站 + 宝塔上线文档 - 五端上线验收 46 项(见 ac82726e) ## 刻意未上传 - node_modules/、.DS_Store、.smart-env/ - .开发文档_nested_git_backup/ - **/__pycache__/ ## 验收 - 提交页(含本说明): https://github.com/fnvtk/cunkebao_v3/commit/HEAD - 分支树: https://github.com/fnvtk/cunkebao_v3/tree/develop Co-authored-by: Cursor <cursoragent@cursor.com>
114 lines
3.8 KiB
PHP
114 lines
3.8 KiB
PHP
<?php
|
||
|
||
namespace app\command;
|
||
|
||
use think\console\Command;
|
||
use think\console\Input;
|
||
use think\console\Output;
|
||
use think\console\input\Option;
|
||
use think\Db;
|
||
|
||
/**
|
||
* 占位用户名数据清洗(REQ-CKB-65)
|
||
*
|
||
* 库内历史 username 为占位「我 / 用户 / undefined / 空」,展示层已过滤,
|
||
* 本命令做数据侧统计与(可选)规范化清洗。
|
||
*
|
||
* 默认 dry-run(只统计不改库):
|
||
* php think clean:placeholderUsername
|
||
* 实际写入(须显式 --apply=1):
|
||
* php think clean:placeholderUsername --apply=1 [--companyId=]
|
||
*/
|
||
class CleanPlaceholderUsernameCommand extends Command
|
||
{
|
||
/** 视为占位的用户名 */
|
||
private const PLACEHOLDERS = ['我', '用户', 'undefined', 'null', 'User', 'user'];
|
||
|
||
protected function configure()
|
||
{
|
||
$this->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(
|
||
'<info>[clean:placeholderUsername] 占位用户名命中 %d 条(companyId=%s, mode=%s)</info>',
|
||
$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(
|
||
'<info>[clean:placeholderUsername] done total=%d updated=%d skipped(无可推断回退)=%d</info>',
|
||
$total,
|
||
$apply ? $updated : 0,
|
||
$skipped
|
||
));
|
||
if (!$apply) {
|
||
$output->writeln('<comment>dry-run 模式未写库;确认无误后加 --apply=1 执行。</comment>');
|
||
}
|
||
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 '';
|
||
}
|
||
}
|