feat(shopro_huifu): 新增股份制银行和城商行联行号导入功能 - 添加 joint 预设选项,支持导入招商、浦发、民生等股份制银行及常用城商行 - 更新命令行选项,增加 --preset=joint 参数说明 - 重构联行号导入过滤器,添加股份制银行关键词配置 - 优化 README 文档,更新导入命令使用说明 ```
208 行
9.0 KiB
PHP
208 行
9.0 KiB
PHP
<?php
|
||
|
||
namespace addons\shopro_huifu\command;
|
||
|
||
use addons\shopro_huifu\library\Installer;
|
||
use addons\shopro_huifu\library\onboard\HuifuOnboardBankBranchImportFilter;
|
||
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) / joint(~30k) / 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');
|
||
$output->writeln(' php think shopro_huifu:import-bank-branch --file=addons/shopro_huifu/data/cnaps.csv --preset=joint');
|
||
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);
|
||
try {
|
||
$output->writeln('导入范围: ' . HuifuOnboardBankBranchImportFilter::presetLabel($preset));
|
||
} catch (\Throwable $e) {
|
||
$output->error($e->getMessage());
|
||
return 1;
|
||
}
|
||
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;
|
||
}
|
||
}
|