feat(shopro_huifu): 增加联行号表自动创建和结算费率配置功能 - 新增 install_bank_branch_table.sql 脚本用于独立创建联行号表 - 修改 ImportBankBranch 命令,当联行号表不存在时自动创建,无需手动执行 repair-menu - 添加 upgradeSchema 方法支持独立运行数据库升级脚本 - 在配置界面新增 onboard_settle_fee_rate 字段,支持设置D1结算费率 - 更新 HuifuOnboardBusiOpen 类使用配置的结算费率参数 ```
302 行
10 KiB
PHP
302 行
10 KiB
PHP
<?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 = '';
|
||
}
|
||
}
|
||
}
|
||
}
|