```
feat(shopro_huifu): 添加分账扩展材料上传功能并优化图片处理 - 新增 UploadSplitExtendedMaterials 命令用于上传 F469/F330 等分账补充材料 - 优化 ProbeSplitConfigDeps 命令中扩展材料配置的检查逻辑 - 支持 PDF 文件上传,增加文件大小限制(PDF 5MB,图片 2MB) - 修复 MCC 编码可作为 online_busi_type 备选值的问题 ```
这个提交包含在:
@@ -67,8 +67,21 @@ class ProbeSplitConfigDeps extends Command
|
||||
$output->writeln('');
|
||||
|
||||
$extMat = trim((string) $cfg->get('split_config_extended_material_json', ''));
|
||||
$output->writeln('--- extended_material_list (按编码表可选/必填) ---');
|
||||
$output->writeln('split_config_extended_material_json: ' . ($extMat !== '' ? '已配置' : '(空,若下一报错要求补充材料再填)'));
|
||||
$output->writeln('--- extended_material_list (H5921L 等常需 F469/F330) ---');
|
||||
if ($extMat === '') {
|
||||
$output->writeln('split_config_extended_material_json: (空)');
|
||||
$output->writeln('[FAIL] 酒类分账常见缺 F469 协议 + F330 食品证 → upload-split-ext-materials');
|
||||
} else {
|
||||
$output->writeln('split_config_extended_material_json: 已配置');
|
||||
$decoded = json_decode($extMat, true);
|
||||
if (is_array($decoded)) {
|
||||
$types = array_column($decoded, 'file_type');
|
||||
foreach (['F469', 'F330'] as $need) {
|
||||
$ok = in_array($need, $types, true);
|
||||
$output->writeln(' ' . ($ok ? '[OK]' : '[FAIL]') . ' ' . $need);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$client = new HuifuClient();
|
||||
$basic = $client->queryMerchantBasicdata($main, true);
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace addons\shopro_huifu\command;
|
||||
|
||||
use addons\shopro_huifu\library\Bridge;
|
||||
use addons\shopro_huifu\library\HuifuConfig;
|
||||
use addons\shopro_huifu\library\onboard\HuifuOnboardPicture;
|
||||
use think\console\Command;
|
||||
use think\console\Input;
|
||||
use think\console\input\Option;
|
||||
use think\console\Output;
|
||||
use think\Db;
|
||||
|
||||
/**
|
||||
* 上传 split/config 补充材料 extended_material_list(如 H5921L 要求的 F469/F330)
|
||||
*/
|
||||
class UploadSplitExtendedMaterials extends Command
|
||||
{
|
||||
protected function configure()
|
||||
{
|
||||
$this->setName('shopro_huifu:upload-split-ext-materials')
|
||||
->addOption('f469-url', null, Option::VALUE_OPTIONAL, 'F469 分账配置-服务佣金相关协议(pdf/jpg/png URL)')
|
||||
->addOption('f330-url', null, Option::VALUE_OPTIONAL, 'F330 食品经营许可证(pdf/jpg/png URL)')
|
||||
->addOption('file-type', null, Option::VALUE_OPTIONAL, '自定义:格式 F469=https://... 可多次传')
|
||||
->addOption('online-busi-type', null, Option::VALUE_OPTIONAL, '一并写入 split_config_online_busi_type,如 H5921L')
|
||||
->addOption('save-config', null, Option::VALUE_NONE, '写入 split_config_extended_material_json')
|
||||
->setDescription('Upload F469/F330 etc. for split/config extended_material_list');
|
||||
}
|
||||
|
||||
protected function execute(Input $input, Output $output)
|
||||
{
|
||||
if (!Bridge::isEnabled()) {
|
||||
$output->writeln('[FAIL] 汇付插件未启用');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$tasks = [];
|
||||
$f469 = trim((string) $input->getOption('f469-url'));
|
||||
$f330 = trim((string) $input->getOption('f330-url'));
|
||||
if ($f469 !== '') {
|
||||
$tasks['F469'] = $f469;
|
||||
}
|
||||
if ($f330 !== '') {
|
||||
$tasks['F330'] = $f330;
|
||||
}
|
||||
$custom = trim((string) $input->getOption('file-type'));
|
||||
if ($custom !== '' && strpos($custom, '=') !== false) {
|
||||
list($ft, $url) = explode('=', $custom, 2);
|
||||
$ft = trim($ft);
|
||||
$url = trim($url);
|
||||
if ($ft !== '' && $url !== '') {
|
||||
$tasks[$ft] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
if ($tasks === []) {
|
||||
$output->writeln('[FAIL] 至少提供 --f469-url 与 --f330-url(酒类 H5921L 分账常见必填)');
|
||||
$output->writeln('示例:');
|
||||
$output->writeln(' php think shopro_huifu:upload-split-ext-materials \\');
|
||||
$output->writeln(' --f469-url=https://域名/多方分账协议.pdf \\');
|
||||
$output->writeln(' --f330-url=https://域名/食品经营许可证.pdf \\');
|
||||
$output->writeln(' --online-busi-type=H5921L --save-config');
|
||||
return 1;
|
||||
}
|
||||
|
||||
$picture = new HuifuOnboardPicture();
|
||||
$list = [];
|
||||
foreach ($tasks as $fileType => $url) {
|
||||
$output->writeln('上传 ' . $fileType . ' ...');
|
||||
$fileId = $picture->uploadByUrl($fileType, $url);
|
||||
$output->writeln(' file_id: ' . $fileId);
|
||||
$list[] = [
|
||||
'file_id' => $fileId,
|
||||
'file_type' => $fileType,
|
||||
];
|
||||
}
|
||||
|
||||
$json = json_encode($list, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
$output->writeln('');
|
||||
$output->writeln('split_config_extended_material_json:');
|
||||
$output->writeln($json);
|
||||
|
||||
if ($input->getOption('save-config')) {
|
||||
$this->upsertConfig('split_config_extended_material_json', $json, 'split/config extended_material_list JSON');
|
||||
HuifuConfig::reload();
|
||||
$output->writeln('[OK] 已写入 split_config_extended_material_json');
|
||||
}
|
||||
|
||||
$busiType = trim((string) $input->getOption('online-busi-type'));
|
||||
if ($busiType !== '') {
|
||||
if ($input->getOption('save-config')) {
|
||||
$this->upsertConfig('split_config_online_busi_type', $busiType, '线上业务类型编码');
|
||||
HuifuConfig::reload();
|
||||
$output->writeln('[OK] 已写入 split_config_online_busi_type=' . $busiType);
|
||||
} else {
|
||||
$output->writeln('[提示] 未 --save-config,online_busi_type 未写入库');
|
||||
}
|
||||
}
|
||||
|
||||
$output->writeln('');
|
||||
$output->writeln('下一步: php think shopro_huifu:sync-split-receivers');
|
||||
return 0;
|
||||
}
|
||||
|
||||
protected function upsertConfig($name, $value, $description)
|
||||
{
|
||||
$row = Db::name('shopro_huifu_config')->where('name', $name)->find();
|
||||
if ($row) {
|
||||
Db::name('shopro_huifu_config')->where('name', $name)->update(['value' => $value]);
|
||||
} else {
|
||||
Db::name('shopro_huifu_config')->insert([
|
||||
'name' => $name,
|
||||
'value' => $value,
|
||||
'group' => 'split',
|
||||
'description' => $description,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,14 @@ class HuifuOnboardPicture
|
||||
$request->setFileType($fileType);
|
||||
|
||||
list($sourcePath, $sourceTemp) = $this->resolveUploadFile($imageUrl);
|
||||
list($uploadPath, $uploadTemp) = $this->normalizeImageForHuifu($sourcePath, $sourceTemp);
|
||||
if ($this->isPdfFile($sourcePath)) {
|
||||
list($uploadPath, $uploadTemp) = [$sourcePath, $sourceTemp];
|
||||
} else {
|
||||
list($uploadPath, $uploadTemp) = $this->normalizeImageForHuifu($sourcePath, $sourceTemp);
|
||||
}
|
||||
|
||||
$filename = $this->buildUploadFilename($fileType, $uploadPath);
|
||||
$mime = $this->detectMime($uploadPath);
|
||||
$mime = $this->isPdfFile($uploadPath) ? 'application/pdf' : $this->detectMime($uploadPath);
|
||||
$merchantHuifuId = $this->sdk->getPictureMerchantHuifuId();
|
||||
|
||||
$extend = $this->sdk->mergeSkillHuifuId([
|
||||
@@ -166,9 +170,11 @@ class HuifuOnboardPicture
|
||||
'F12' => 'bank_card',
|
||||
'F13' => 'bank_card',
|
||||
];
|
||||
$base = $map[$fileType] ?? 'upload';
|
||||
$base = $map[$fileType] ?? strtolower($fileType);
|
||||
$ext = strtolower(pathinfo($localPath, PATHINFO_EXTENSION));
|
||||
if (!in_array($ext, ['jpg', 'jpeg', 'png'], true)) {
|
||||
if ($this->isPdfFile($localPath)) {
|
||||
$ext = 'pdf';
|
||||
} elseif (!in_array($ext, ['jpg', 'jpeg', 'png'], true)) {
|
||||
$ext = 'jpg';
|
||||
}
|
||||
if ($ext === 'jpeg') {
|
||||
@@ -177,6 +183,15 @@ class HuifuOnboardPicture
|
||||
return substr($base, 0, 20) . '.' . $ext;
|
||||
}
|
||||
|
||||
protected function isPdfFile($localPath)
|
||||
{
|
||||
if (!is_file($localPath)) {
|
||||
return false;
|
||||
}
|
||||
$head = @file_get_contents($localPath, false, null, 0, 5);
|
||||
return is_string($head) && strncmp($head, '%PDF-', 5) === 0;
|
||||
}
|
||||
|
||||
protected function detectMime($localPath)
|
||||
{
|
||||
if (function_exists('mime_content_type')) {
|
||||
@@ -293,6 +308,12 @@ class HuifuOnboardPicture
|
||||
{
|
||||
$localPath = $this->resolveLocalPath($imageUrl);
|
||||
if ($localPath && is_file($localPath)) {
|
||||
$isPdf = $this->isPdfFile($localPath);
|
||||
$maxBytes = $isPdf ? (5 * 1024 * 1024) : (2 * 1024 * 1024);
|
||||
$size = filesize($localPath);
|
||||
if ($size !== false && $size > $maxBytes) {
|
||||
throw new \RuntimeException($isPdf ? 'PDF 超过 5MB,请压缩后重新上传' : '图片超过 2MB,请压缩后重新上传');
|
||||
}
|
||||
return [$localPath, false];
|
||||
}
|
||||
$absoluteUrl = $this->resolveAbsoluteUrl($imageUrl);
|
||||
@@ -300,10 +321,12 @@ class HuifuOnboardPicture
|
||||
if ($content === '') {
|
||||
throw new \RuntimeException('无法读取图片:' . $absoluteUrl);
|
||||
}
|
||||
if (strlen($content) > 2 * 1024 * 1024) {
|
||||
throw new \RuntimeException('图片超过 2MB,请压缩后重新上传');
|
||||
$isPdf = strncmp($content, '%PDF-', 5) === 0;
|
||||
$maxBytes = $isPdf ? (5 * 1024 * 1024) : (2 * 1024 * 1024);
|
||||
if (strlen($content) > $maxBytes) {
|
||||
throw new \RuntimeException($isPdf ? 'PDF 超过 5MB,请压缩后重新上传' : '图片超过 2MB,请压缩后重新上传');
|
||||
}
|
||||
$ext = $this->guessImageExt($content, $absoluteUrl);
|
||||
$ext = $isPdf ? 'pdf' : $this->guessImageExt($content, $absoluteUrl);
|
||||
$tmp = tempnam(sys_get_temp_dir(), 'hf_pic_');
|
||||
if ($tmp === false) {
|
||||
throw new \RuntimeException('创建临时图片文件失败');
|
||||
@@ -345,7 +368,10 @@ class HuifuOnboardPicture
|
||||
}
|
||||
$path = parse_url($url, PHP_URL_PATH);
|
||||
$ext = strtolower(pathinfo((string) $path, PATHINFO_EXTENSION));
|
||||
if (in_array($ext, ['jpg', 'jpeg', 'png'], true)) {
|
||||
if (strncmp($binary, '%PDF-', 5) === 0) {
|
||||
return 'pdf';
|
||||
}
|
||||
if (in_array($ext, ['jpg', 'jpeg', 'png', 'pdf'], true)) {
|
||||
return $ext === 'jpeg' ? 'jpg' : $ext;
|
||||
}
|
||||
return 'jpg';
|
||||
|
||||
@@ -448,8 +448,14 @@ class HuifuMerchantSplitService
|
||||
try {
|
||||
$basic = $this->client->queryMerchantBasicdata($mainHuifuId);
|
||||
if (!empty($basic['ok']) && is_array($basic['data'] ?? null)) {
|
||||
$found = HuifuClient::collectNestedKeys($basic['data'], ['online_busi_type']);
|
||||
$found = HuifuClient::collectNestedKeys($basic['data'], ['online_busi_type', 'mcc']);
|
||||
$v = trim((string) ($found['online_busi_type'] ?? ''));
|
||||
if ($v === '') {
|
||||
$mcc = trim((string) ($found['mcc'] ?? ''));
|
||||
if ($mcc !== '' && preg_match('/^H[A-Z0-9]{4,}$/i', $mcc)) {
|
||||
$v = strtoupper($mcc);
|
||||
}
|
||||
}
|
||||
if ($v !== '') {
|
||||
return $v;
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户