文件
xionggeshop/addons/shopro_huifu/library/Installer.php
可乐加冰 8b35cfd568 ```
feat(shopro_huifu): 增加联行号表自动创建和结算费率配置功能

- 新增 install_bank_branch_table.sql 脚本用于独立创建联行号表
- 修改 ImportBankBranch 命令,当联行号表不存在时自动创建,无需手动执行 repair-menu
- 添加 upgradeSchema 方法支持独立运行数据库升级脚本
- 在配置界面新增 onboard_settle_fee_rate 字段,支持设置D1结算费率
- 更新 HuifuOnboardBusiOpen 类使用配置的结算费率参数
```
2026-09-30 14:01:38 +08:00

302 行
10 KiB
PHP

此文件含有模棱两可的 Unicode 字符

此文件含有可能会与其他字符混淆的 Unicode 字符。 如果您是想特意这样的,可以安全地忽略该警告。 使用 Escape 按钮显示他们。

<?php
namespace addons\shopro_huifu\library;
use app\admin\model\AuthRule;
use think\addons\Service;
use think\Cache;
use think\Db;
class Installer
{
public static function run($output = null)
{
$addonDir = ADDON_PATH . 'shopro_huifu' . DS;
$name = 'shopro_huifu';
self::log($output, '复制后台文件 application/public ...');
foreach (['application', 'public'] as $dir) {
$source = $addonDir . $dir;
if (is_dir($source)) {
copydirs($source, ROOT_PATH . $dir);
}
}
self::log($output, '执行 install.sql ...');
self::importSqlFile($addonDir . 'install.sql', $output);
self::importSqlFile($addonDir . 'upgrade_huifu_split.sql', $output, true);
self::importSqlFile($addonDir . 'upgrade_huifu_onboard.sql', $output, true);
self::importSqlFile($addonDir . 'upgrade_huifu_onboard_account.sql', $output, true);
self::upgradePayTypeColumn($output);
self::log($output, '注册汇付/分账菜单 ...');
$parent = self::installMenu($output);
self::installSplitMenu($output);
\app\common\library\Menu::enable('shopro_huifu/config');
\app\common\library\Menu::enable('shopro/split');
self::grantRulesToShoproGroups($output);
self::clearMenuCache();
self::setAddonState($name, 1, $output);
Service::refresh();
self::log($output, '安装完成');
if ($parent) {
self::log($output, '汇付配置: 系统管理 -> 商城配置 -> 汇付支付配置');
self::log($output, '分账设置: 系统管理 -> 商城配置 -> 支付分账 -> 分账设置');
self::log($output, '直达: /admin/shopro_huifu/config | /admin/shopro/split/config');
}
}
/**
* 直接写入 info.ini 的 state,不依赖 get_addon_instance(CLI 下类可能尚未加载)
*/
public static function setAddonState($name, $state, $output = null)
{
$file = ADDON_PATH . $name . DS . 'info.ini';
if (!is_file($file)) {
self::log($output, ' [warn] info.ini 不存在,跳过 state 写入');
return false;
}
$content = file_get_contents($file);
$state = intval($state);
if (preg_match('/^state\s*=\s*\d+/m', $content)) {
$content = preg_replace('/^state\s*=\s*\d+/m', 'state = ' . $state, $content);
} else {
$content = rtrim($content) . "\nstate = " . $state . "\n";
}
if (file_put_contents($file, $content, LOCK_EX) === false) {
throw new \RuntimeException('无法写入 ' . $file . ',请检查文件权限');
}
\think\Config::set($name, null, 'addoninfo');
\think\Config::set('addon-info-' . $name, null);
self::log($output, '已设置 info.ini state=' . $state);
if ($state === 1) {
try {
Service::refresh();
self::log($output, '已刷新 application/extra/addons.php 插件缓存');
} catch (\Exception $e) {
self::log($output, ' [warn] Service::refresh 失败: ' . $e->getMessage());
self::log($output, ' [warn] 请在后台插件中心重新启用一次 Shopro汇付支付');
}
}
return true;
}
public static function installMenu($output = null)
{
$menuFile = ADDON_PATH . 'shopro_huifu' . DS . 'config' . DS . 'menu.php';
if (!is_file($menuFile)) {
return false;
}
$menu = include $menuFile;
if (!$menu) {
return false;
}
$parent = self::resolveMenuParent();
self::log($output, '菜单父节点: ' . $parent);
self::recreateMenu($menu, $parent, 'shopro_huifu/config', ['shopro_huifu']);
$configMenu = AuthRule::getByName('shopro_huifu/config');
if ($configMenu) {
AuthRule::update([
'ismenu' => 1,
'status' => 'normal',
'weigh' => 5,
], ['id' => $configMenu['id']]);
}
\app\common\library\Menu::refresh('shopro_huifu', $menu);
return $parent;
}
public static function installSplitMenu($output = null)
{
$menuFile = ADDON_PATH . 'shopro_huifu' . DS . 'config' . DS . 'split_menu.php';
if (!is_file($menuFile)) {
return false;
}
$menu = include $menuFile;
if (!$menu) {
return false;
}
$parent = self::resolveConfigParent();
self::log($output, '分账菜单父节点: ' . $parent);
self::recreateMenu($menu, $parent, 'shopro/split');
$splitMenu = AuthRule::getByName('shopro/split');
if ($splitMenu) {
AuthRule::update([
'ismenu' => 1,
'status' => 'normal',
'weigh' => 4,
], ['id' => $splitMenu['id']]);
}
return $parent;
}
/**
* FastAdmin Menu::create 不会检测库中已存在同名节点,repair 时需先删后建
*/
protected static function recreateMenu(array $menu, $parent, $rootName, array $legacyNames = [])
{
$toDelete = array_unique(array_filter(array_merge(
$legacyNames,
[$rootName],
array_column($menu, 'name')
)));
foreach ($toDelete as $name) {
\app\common\library\Menu::delete($name);
}
\app\common\library\Menu::create($menu, $parent);
}
/**
* 补跑升级 SQL(repair-menu / 导入联行号前可单独调用,忽略已存在字段/表)
*/
public static function upgradeSchema($output = null)
{
$addonDir = ADDON_PATH . 'shopro_huifu' . DS;
self::log($output, '检查/升级汇付数据表 ...');
self::importSqlFile($addonDir . 'upgrade_huifu_split.sql', $output, true);
self::importSqlFile($addonDir . 'upgrade_huifu_onboard.sql', $output, true);
self::importSqlFile($addonDir . 'upgrade_huifu_onboard_account.sql', $output, true);
self::upgradePayTypeColumn($output);
}
public static function ensureBankBranchTable($output = null)
{
self::upgradeSchema($output);
}
/**
* 汇付/分账菜单统一挂到「商城配置」
*/
public static function resolveConfigParent()
{
foreach (['shopro/config', 'shopro'] as $name) {
$rule = AuthRule::getByName($name);
if ($rule && $rule['status'] === 'normal') {
return $name;
}
}
return 0;
}
/**
* 优先挂到侧栏可见的「商城配置」,其次 Shopro 根节点
*/
public static function resolveMenuParent()
{
return self::resolveConfigParent();
}
/**
* 给已拥有 Shopro 权限的管理组追加汇付/分账菜单节点
*/
public static function grantRulesToShoproGroups($output = null)
{
$ruleIds = array_unique(array_merge(
\app\common\library\Menu::getAuthRuleIdsByName('shopro_huifu/config'),
\app\common\library\Menu::getAuthRuleIdsByName('shopro/split')
));
if (!$ruleIds) {
self::log($output, ' [warn] 未找到汇付/分账菜单规则,跳过权限分配');
return false;
}
$groups = Db::name('auth_group')->where('status', 'normal')->select();
$granted = 0;
foreach ($groups as $group) {
if ($group['rules'] === '*') {
continue;
}
$owned = array_filter(array_map('intval', explode(',', (string) $group['rules'])));
if (!$owned) {
continue;
}
$hasShopro = Db::name('auth_rule')
->where('id', 'in', $owned)
->where('name', 'like', 'shopro%')
->count();
if (!$hasShopro) {
continue;
}
$merged = array_unique(array_merge($owned, $ruleIds));
Db::name('auth_group')->where('id', $group['id'])->update([
'rules' => implode(',', $merged),
]);
$granted++;
}
self::log($output, '已为 ' . $granted . ' 个 Shopro 权限组追加汇付/分账菜单');
return true;
}
public static function clearMenuCache()
{
Cache::rm('__menu__');
}
/**
* 扩展 shopro_order.pay_type,支持 huifu_wechat / group
*/
public static function upgradePayTypeColumn($output = null)
{
$file = ADDON_PATH . 'shopro_huifu' . DS . 'upgrade_huifu_pay_type.sql';
self::importSqlFile($file, $output, true);
self::log($output, '已检查/升级 shopro_order.pay_type 字段');
}
protected static function log($output, $message)
{
if ($output) {
$output->writeln($message);
}
}
/**
* 使用 Db::execute 导入 SQL(CLI 下 Service::importsql 的 Db::getPdo() 可能未连接)
*/
protected static function importSqlFile($file, $output = null, $ignoreErrors = false)
{
if (!is_file($file)) {
return;
}
$prefix = config('database.prefix');
$lines = file($file);
$templine = '';
foreach ($lines as $line) {
if (substr($line, 0, 2) === '--' || $line === '' || substr($line, 0, 2) === '/*') {
continue;
}
$templine .= $line;
if (substr(trim($line), -1, 1) === ';') {
$templine = str_ireplace('__PREFIX__', $prefix, $templine);
$templine = preg_replace('/^INSERT INTO /i', 'INSERT IGNORE INTO ', $templine);
try {
Db::execute($templine);
} catch (\Exception $e) {
if (!$ignoreErrors) {
throw $e;
}
self::log($output, ' [skip] ' . basename($file) . ': ' . $e->getMessage());
}
$templine = '';
}
}
}
}