feat(shopro_huifu): 添加联行号导入范围筛选功能 - 新增 preset 参数支持 main(默认) 和 full 两种模式 - main 模式仅导入主要省市 + 四大行/农商行等约5万条数据 - full 模式导入全国全量约15万条数据 - 添加 HuifuOnboardBankBranchImportFilter 类处理筛选逻辑 - 优化 CSV 流式处理,提升大文件导入性能 - 更新 README 文档说明新的导入命令选项 ```
201 行
8.7 KiB
PHP
201 行
8.7 KiB
PHP
<?php
|
||
|
||
namespace addons\shopro_huifu\command;
|
||
|
||
use addons\shopro_huifu\library\Installer;
|
||
use addons\shopro_huifu\library\onboard\HuifuOnboardBankBranchImporter;
|
||
use think\console\Command;
|
||
use think\console\Input;
|
||
use think\console\input\Option;
|
||
use think\console\Output;
|
||
use think\Db;
|
||
|
||
class ImportBankBranch extends Command
|
||
{
|
||
protected function configure()
|
||
{
|
||
$this->setName('shopro_huifu:import-bank-branch')
|
||
->setDescription('Import nationwide CNAPS bank branch codes into shopro_huifu_bank_branch')
|
||
->addOption('file', 'f', Option::VALUE_OPTIONAL, 'CSV/JSON source file path')
|
||
->addOption('download', 'd', Option::VALUE_NONE, 'Download gweesin/CNAPS cnaps.csv then import')
|
||
->addOption('seed', null, Option::VALUE_NONE, 'Import bundled data/bank_branches.json seed only')
|
||
->addOption('truncate', 't', Option::VALUE_NONE, 'Clear table before import (optional, default skip existing)')
|
||
->addOption('update-existing', 'u', Option::VALUE_NONE, 'Update rows when branch_code already exists')
|
||
->addOption('check', 'c', Option::VALUE_NONE, 'Check duplicate branch_code / branch_name in DB only')
|
||
->addOption('preset', 'p', Option::VALUE_OPTIONAL, 'Import scope: main(~50k, default) / full(all)', 'main')
|
||
->addOption('limit', 'l', Option::VALUE_OPTIONAL, 'Max rows to import (0=all)', 0)
|
||
->addOption('batch', 'b', Option::VALUE_OPTIONAL, 'Batch size', 500)
|
||
->addOption('format', null, Option::VALUE_OPTIONAL, 'Force format: csv|json|auto', 'auto')
|
||
->addOption('dry-run', null, Option::VALUE_NONE, 'Parse only, do not write DB')
|
||
->addOption('export-json', null, Option::VALUE_OPTIONAL, 'Export DB rows to JSON file path')
|
||
->addOption('export-limit', null, Option::VALUE_OPTIONAL, 'Max rows when exporting JSON', 0);
|
||
}
|
||
|
||
protected function execute(Input $input, Output $output)
|
||
{
|
||
@ini_set('memory_limit', '256M');
|
||
|
||
$exportJson = trim((string) $input->getOption('export-json'));
|
||
if ($exportJson !== '') {
|
||
return $this->exportJson($output, $exportJson, (int) $input->getOption('export-limit'));
|
||
}
|
||
|
||
if ((bool) $input->getOption('check')) {
|
||
return $this->checkDuplicates($output);
|
||
}
|
||
|
||
try {
|
||
Db::execute('SELECT 1 FROM `' . config('database.prefix') . 'shopro_huifu_bank_branch` LIMIT 1');
|
||
} catch (\Throwable $e) {
|
||
$output->writeln('表 shopro_huifu_bank_branch 不存在,正在自动创建 ...');
|
||
try {
|
||
Installer::ensureBankBranchTable($output);
|
||
Db::execute('SELECT 1 FROM `' . config('database.prefix') . 'shopro_huifu_bank_branch` LIMIT 1');
|
||
} catch (\Throwable $inner) {
|
||
$output->error('创建 shopro_huifu_bank_branch 失败: ' . $inner->getMessage());
|
||
$output->writeln('也可手动执行: php think shopro_huifu:repair-menu');
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
$file = trim((string) $input->getOption('file'));
|
||
$download = (bool) $input->getOption('download');
|
||
$seed = (bool) $input->getOption('seed');
|
||
|
||
$preset = trim((string) $input->getOption('preset'));
|
||
if ($preset === '') {
|
||
$preset = 'main';
|
||
}
|
||
|
||
if ($seed) {
|
||
$file = ADDON_PATH . 'shopro_huifu/data/bank_branches.json';
|
||
$preset = 'full';
|
||
} elseif ($download) {
|
||
$output->writeln('正在下载全国联行号 CSV(约 20MB,来源 gweesin/CNAPS)...');
|
||
try {
|
||
$file = HuifuOnboardBankBranchImporter::downloadCnapsCsv();
|
||
$output->writeln('已保存: ' . $file);
|
||
} catch (\Throwable $e) {
|
||
$output->error($e->getMessage());
|
||
$output->writeln('手动下载: https://hf-mirror.com/datasets/gweesin/CNAPS/resolve/main/cnaps.csv');
|
||
$output->writeln('然后执行: php think shopro_huifu:import-bank-branch --file=addons/shopro_huifu/data/cnaps.csv');
|
||
return 1;
|
||
}
|
||
} elseif ($file === '') {
|
||
$defaultCsv = ADDON_PATH . 'shopro_huifu/data/cnaps.csv';
|
||
if (is_file($defaultCsv)) {
|
||
$file = $defaultCsv;
|
||
} else {
|
||
$output->error('请指定 --file、--download 或 --seed');
|
||
$output->writeln('示例:');
|
||
$output->writeln(' php think shopro_huifu:import-bank-branch --seed');
|
||
$output->writeln(' php think shopro_huifu:import-bank-branch --download');
|
||
$output->writeln(' php think shopro_huifu:import-bank-branch --file=addons/shopro_huifu/data/cnaps.csv');
|
||
return 1;
|
||
}
|
||
}
|
||
|
||
if ($seed || strtolower(pathinfo($file, PATHINFO_EXTENSION)) === 'json') {
|
||
$preset = 'full';
|
||
}
|
||
|
||
$options = [
|
||
'truncate' => (bool) $input->getOption('truncate'),
|
||
'update_existing' => (bool) $input->getOption('update-existing'),
|
||
'preset' => $preset,
|
||
'limit' => (int) $input->getOption('limit'),
|
||
'batch' => (int) $input->getOption('batch'),
|
||
'format' => (string) $input->getOption('format'),
|
||
'dry_run' => (bool) $input->getOption('dry-run'),
|
||
];
|
||
|
||
$output->writeln('导入文件: ' . $file);
|
||
$output->writeln('导入范围: ' . ($preset === 'full' ? '全国全量(约15万)' : '主要省市 + 四大行/农商行等(约5万)'));
|
||
if ($options['dry_run']) {
|
||
$output->writeln('[dry-run] 仅解析,不写入数据库');
|
||
}
|
||
if ($options['truncate']) {
|
||
$output->writeln('[truncate] 导入前清空旧数据');
|
||
} else {
|
||
$output->writeln('已存在联行号将自动跳过(可用 --update-existing 覆盖更新)');
|
||
}
|
||
|
||
try {
|
||
$stats = HuifuOnboardBankBranchImporter::importFile($file, $options);
|
||
} catch (\Throwable $e) {
|
||
$output->error($e->getMessage());
|
||
return 1;
|
||
}
|
||
|
||
$output->writeln(sprintf(
|
||
'完成: 解析 %d 行,命中 %d 行,新增 %d,更新 %d,跳过已存在 %d,范围外 %d,文件内重复 %d,无效 %d',
|
||
$stats['parsed'] ?? 0,
|
||
$stats['matched'] ?? 0,
|
||
$stats['inserted'],
|
||
$stats['updated'],
|
||
$stats['skipped_existing'] ?? 0,
|
||
$stats['skipped_filtered'] ?? 0,
|
||
$stats['duplicate_in_file'] ?? 0,
|
||
$stats['skipped_invalid'] ?? 0
|
||
));
|
||
if (($stats['parsed'] ?? 0) > 1000 && ($stats['inserted'] ?? 0) < 1000 && ($stats['skipped_existing'] ?? 0) < 100) {
|
||
$output->writeln('警告: 解析行数很多但新增很少,请确认 CSV 联行号列为 BankCode(非 BankId)');
|
||
}
|
||
|
||
$count = Db::name('shopro_huifu_bank_branch')->where('status', 1)->count();
|
||
$output->writeln('当前库内有效支行: ' . $count);
|
||
|
||
return 0;
|
||
}
|
||
|
||
protected function checkDuplicates(Output $output)
|
||
{
|
||
try {
|
||
$result = HuifuOnboardBankBranchImporter::checkDatabaseDuplicates();
|
||
} catch (\Throwable $e) {
|
||
$output->error($e->getMessage());
|
||
return 1;
|
||
}
|
||
|
||
$output->writeln('当前库内支行总数: ' . $result['total']);
|
||
$codeDupes = $result['duplicate_codes'];
|
||
if ($codeDupes) {
|
||
$output->writeln('发现重复联行号 ' . count($codeDupes) . ' 组(不应出现,表有唯一索引):');
|
||
foreach ($codeDupes as $item) {
|
||
$output->writeln(' ' . $item['branch_code'] . ' x' . $item['cnt']);
|
||
}
|
||
} else {
|
||
$output->writeln('联行号 branch_code 无重复');
|
||
}
|
||
|
||
$nameDupes = $result['duplicate_names'];
|
||
if ($nameDupes) {
|
||
$output->writeln('同名不同号(展示前 ' . count($nameDupes) . ' 组,通常可忽略):');
|
||
foreach ($nameDupes as $item) {
|
||
$output->writeln(sprintf(
|
||
' %s(%d 条,%d 个联行号)',
|
||
$item['branch_name'],
|
||
$item['cnt'],
|
||
$item['code_cnt']
|
||
));
|
||
}
|
||
} else {
|
||
$output->writeln('支行名称无重复');
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
protected function exportJson(Output $output, $dest, $limit)
|
||
{
|
||
try {
|
||
$count = HuifuOnboardBankBranchImporter::exportJson($dest, $limit);
|
||
} catch (\Throwable $e) {
|
||
$output->error($e->getMessage());
|
||
return 1;
|
||
}
|
||
$output->writeln('已导出 ' . $count . ' 条到 ' . $dest);
|
||
return 0;
|
||
}
|
||
}
|