文件
xionggeshop/addons/shopro_huifu/command/VerifyInstall.php

397 行
16 KiB
PHP

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

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

<?php
namespace addons\shopro_huifu\command;
use think\console\Command;
use think\console\Input;
use think\console\Output;
use think\Db;
class VerifyInstall extends Command
{
protected function configure()
{
$this->setName('shopro_huifu:verify')
->setDescription('Check shopro_huifu plugin installation (tables, menus, files)');
}
protected function execute(Input $input, Output $output)
{
$prefix = config('database.prefix');
$root = ROOT_PATH;
$pass = 0;
$fail = 0;
$checks = [
'入口文件 Shopro_huifu.php' => is_file($root . 'addons/shopro_huifu/Shopro_huifu.php'),
'info.ini' => is_file($root . 'addons/shopro_huifu/info.ini'),
'config/menu.php' => is_file($root . 'addons/shopro_huifu/config/menu.php'),
'后台控制器(已部署)' => is_file($root . 'application/admin/controller/shopro_huifu/Config.php'),
'后台控制器(插件包内)' => is_file($root . 'addons/shopro_huifu/application/admin/controller/shopro_huifu/Config.php'),
'后台视图(已部署)' => is_file($root . 'application/admin/view/shopro_huifu/config/index.html'),
'后台 JS(已部署)' => is_file($root . 'public/assets/js/backend/shopro_huifu/config.js'),
'汇付 SDK init.php' => $this->hasSdk($root),
];
$output->writeln('=== 文件检查 ===');
foreach ($checks as $label => $ok) {
$this->line($output, $label, $ok, $pass, $fail);
}
$output->writeln('');
$output->writeln('=== 数据库检查 ===');
$tables = ['shopro_huifu_config', 'shopro_huifu_pay_ext', 'shopro_huifu_notify_log'];
foreach ($tables as $table) {
$ok = $this->tableExists($prefix . $table);
$this->line($output, '表 ' . $prefix . $table, $ok, $pass, $fail);
}
try {
$cfgCount = Db::name('shopro_huifu_config')->count();
$this->line($output, '汇付配置项数量 >= 10', $cfgCount >= 10, $pass, $fail, (string) $cfgCount);
} catch (\Exception $e) {
$this->line($output, '汇付配置项', false, $pass, $fail, $e->getMessage());
}
try {
$orderTable = $prefix . 'shopro_order';
$rows = Db::query("SHOW COLUMNS FROM `{$orderTable}` LIKE 'pay_type'");
$type = $rows[0]['Type'] ?? '';
$supportsHuifu = stripos($type, 'huifu_wechat') !== false;
$this->line(
$output,
'shopro_order.pay_type 支持 huifu_wechat',
$supportsHuifu,
$pass,
$fail,
$type ?: '未找到 pay_type 字段'
);
} catch (\Exception $e) {
$this->line($output, 'shopro_order.pay_type 支持 huifu_wechat', false, $pass, $fail, $e->getMessage());
}
$output->writeln('');
$output->writeln('=== 菜单与权限检查 ===');
$menuNames = [
'shopro_huifu/config',
'shopro_huifu/config/index',
'shopro_huifu/config/save',
'shopro/split',
'shopro/split/config',
'shopro/split/config/index',
'shopro/split/receiver',
'shopro/split/rule',
'shopro/split/order',
];
foreach ($menuNames as $name) {
$row = Db::name('auth_rule')->where('name', $name)->find();
$ok = (bool) $row;
$extra = $row ? ('pid=' . $row['pid'] . ' status=' . $row['status'] . ' ismenu=' . $row['ismenu']) : '未找到';
$this->line($output, '菜单 ' . $name, $ok, $pass, $fail, $extra);
}
$legacyMenu = Db::name('auth_rule')->where('name', 'shopro_huifu')->find();
$this->line($output, '旧版菜单 shopro_huifu 已移除', !$legacyMenu, $pass, $fail, $legacyMenu ? '仍存在,需 repair' : '无');
foreach ([
'shopro_huifu/config' => '汇付菜单父节点',
'shopro/split' => '分账菜单父节点',
] as $menuName => $label) {
$row = Db::name('auth_rule')->where('name', $menuName)->find();
if ($row) {
$parent = Db::name('auth_rule')->where('id', $row['pid'])->find();
$parentName = $parent['name'] ?? 'unknown';
$ok = in_array($parentName, ['shopro/config', 'shopro'], true);
$this->line($output, $label . '正确', $ok, $pass, $fail, 'parent=' . $parentName);
} else {
$this->line($output, $label . '正确', false, $pass, $fail, '缺少 ' . $menuName);
}
}
$splitConfig = Db::name('auth_rule')->where('name', 'shopro/split/config')->find();
if ($splitConfig && intval($splitConfig['ismenu']) === 1 && $splitConfig['status'] === 'normal') {
$path = $this->getMenuPathTitles(intval($splitConfig['id']));
$this->line($output, '分账设置侧栏路径', !empty($path), $pass, $fail, implode(' -> ', $path));
} else {
$this->line($output, '分账设置侧栏路径', false, $pass, $fail, '缺少 shopro/split/config');
}
$huifuConfig = Db::name('auth_rule')->where('name', 'shopro_huifu/config')->find();
if ($huifuConfig && intval($huifuConfig['ismenu']) === 1 && $huifuConfig['status'] === 'normal') {
$path = $this->getMenuPathTitles(intval($huifuConfig['id']));
$this->line($output, '汇付配置侧栏路径', !empty($path), $pass, $fail, implode(' -> ', $path));
}
$indexRule = Db::name('auth_rule')->where('name', 'shopro_huifu/config/index')->find();
$superGroup = Db::name('auth_group')->where('rules', '*')->find();
if ($superGroup) {
$this->line($output, '存在超级管理员权限组(*)', true, $pass, $fail, 'id=' . $superGroup['id']);
} else {
$this->line($output, '存在超级管理员权限组(*)', false, $pass, $fail);
}
if ($indexRule) {
$groupsWithHuifu = 0;
foreach (Db::name('auth_group')->where('status', 'normal')->select() as $group) {
if ($group['rules'] === '*') {
$groupsWithHuifu++;
continue;
}
$owned = array_filter(array_map('trim', explode(',', (string) $group['rules'])));
if (in_array((string) $indexRule['id'], $owned, true)) {
$groupsWithHuifu++;
}
}
$this->line($output, '至少1个管理组拥有汇付权限', $groupsWithHuifu > 0, $pass, $fail, (string) $groupsWithHuifu);
}
$iniFile = ADDON_PATH . 'shopro_huifu/info.ini';
$iniState = 0;
if (is_file($iniFile)) {
$ini = parse_ini_file($iniFile);
$iniState = intval($ini['state'] ?? 0);
}
$cachedInfo = get_addon_info('shopro_huifu');
$cachedState = intval($cachedInfo['state'] ?? 0);
$this->line(
$output,
'插件 info.ini state=1',
$iniState === 1,
$pass,
$fail,
'info.ini state=' . $iniState
);
if ($iniState === 1 && $cachedState !== 1) {
$this->line(
$output,
'get_addon_info 缓存与 info.ini 一致',
false,
$pass,
$fail,
'缓存 state=' . $cachedState . ',需刷新插件缓存'
);
} else {
$this->line(
$output,
'get_addon_info 缓存与 info.ini 一致',
true,
$pass,
$fail,
'缓存 state=' . $cachedState
);
}
$addonsFile = ROOT_PATH . 'application/extra/addons.php';
$appInitHooks = [];
if (is_file($addonsFile)) {
$addonsConfig = include $addonsFile;
$appInitHooks = (array) ($addonsConfig['hooks']['app_init'] ?? []);
}
$inAppInit = in_array('shopro_huifu', $appInitHooks, true);
$this->line(
$output,
'addons.php app_init 含 shopro_huifu',
$iniState !== 1 || $inAppInit,
$pass,
$fail,
$inAppInit ? '已注册' : '未注册(线上 Hook 可能不加载,需刷新插件缓存)'
);
$output->writeln('');
$output->writeln('=== 支付通道路由检查(小程序 prepay 是否走汇付) ===');
$this->line(
$output,
'说明:收银台仍显示「微信支付」属正常,由后端路由决定实际通道',
true,
$pass,
$fail
);
try {
$cfg = Db::name('shopro_huifu_config')->column('value', 'name');
$channel = $cfg['miniprogram_channel'] ?? 'yansongda';
$hfStatus = $cfg['status'] ?? '0';
$this->line(
$output,
'miniprogram_channel=huifu',
$channel === 'huifu',
$pass,
$fail,
'当前=' . $channel
);
$this->line(
$output,
'汇付通道开关 status=1',
$hfStatus === '1',
$pass,
$fail,
'当前=' . $hfStatus
);
if ($channel === 'huifu') {
$credOk = !empty($cfg['sys_id']) && !empty($cfg['product_id']) && !empty($cfg['huifu_id']);
$this->line(
$output,
'汇付凭证 sys_id/product_id/huifu_id 已填',
$credOk,
$pass,
$fail,
$credOk ? '已填' : '缺商户号/系统号/产品号'
);
$keyOk = !empty($cfg['merchant_private_key']) && !empty($cfg['huifu_public_key']);
$this->line(
$output,
'RSA 密钥对已填',
$keyOk,
$pass,
$fail,
$keyOk ? '已填' : '缺私钥或汇付公钥'
);
if ($keyOk && class_exists('\\addons\\shopro_huifu\\library\\HuifuConfig')) {
try {
\addons\shopro_huifu\library\HuifuConfig::reload()->validateKeys();
$this->line($output, 'RSA 密钥格式可签名', true, $pass, $fail);
} catch (\Exception $e) {
$this->line($output, 'RSA 密钥格式可签名', false, $pass, $fail, $e->getMessage());
}
}
$payScene = trim((string) ($cfg['pay_scene'] ?? ''));
$channelNo = trim((string) ($cfg['channel_no'] ?? ''));
$scenePairOk = ($channelNo === '') || ($payScene !== '' && $channelNo !== '');
$this->line(
$output,
'pay_scene 与 channel_no 成对配置',
$scenePairOk,
$pass,
$fail,
$scenePairOk ? 'OK' : '填了 channel_no 时必须同时填 pay_scene'
);
}
$mini = Db::name('shopro_config')->where('name', 'wxMiniProgram')->value('value');
$mini = json_decode($mini, true);
$appId = $mini['app_id'] ?? '';
$this->line(
$output,
'商城小程序 AppID 已配置',
$appId !== '',
$pass,
$fail,
$appId !== '' ? $appId : 'shopro_config.wxMiniProgram 为空'
);
} catch (\Exception $e) {
$this->line($output, '支付通道配置可读', false, $pass, $fail, $e->getMessage());
}
if (class_exists('\\addons\\shopro_huifu\\library\\Bridge')) {
$bridgeEnabled = \addons\shopro_huifu\library\Bridge::isEnabled();
$this->line(
$output,
'Bridge::isEnabled()',
$bridgeEnabled,
$pass,
$fail,
$bridgeEnabled ? 'true' : 'false(插件未启用或 status=0)'
);
$huifuChannel = \addons\shopro_huifu\library\Bridge::isHuifuMiniProgramChannel('wxMiniProgram');
$this->line(
$output,
'Bridge::isHuifuMiniProgramChannel(wxMiniProgram)',
$huifuChannel,
$pass,
$fail,
$huifuChannel ? 'true,prepay 应走汇付' : 'false,prepay 仍走 Yansongda'
);
$shouldPay = \addons\shopro_huifu\library\Bridge::shouldHandlePay([
'platform' => 'wxMiniProgram',
'payment' => 'wechat',
'prepay_type' => 'order',
]);
$this->line(
$output,
'Bridge::shouldHandlePay(小程序订单)',
$shouldPay,
$pass,
$fail,
$shouldPay ? 'true' : 'false'
);
}
$hookBehaviors = \think\Hook::get('shopro_pay_create');
$hookOk = !empty($hookBehaviors);
$this->line(
$output,
'Hook shopro_pay_create 已注册',
$hookOk,
$pass,
$fail,
$hookOk ? json_encode($hookBehaviors, JSON_UNESCAPED_UNICODE) : '未注册(插件 state=0 或未加载 appInit)'
);
$output->writeln('');
$output->writeln("结果: 通过 {$pass} / 失败 {$fail}");
$output->writeln('汇付配置直达: /admin/shopro_huifu/config');
$output->writeln('分账设置直达: /admin/shopro/split/config');
if ($fail > 0) {
$output->writeln('修复菜单/权限/缓存: php think shopro_huifu:repair-menu');
$output->writeln('修复后请退出后台重新登录');
}
}
protected function getMenuPathTitles($menuId)
{
$path = [];
$currentId = $menuId;
$guard = 0;
while ($currentId && $guard < 20) {
$row = Db::name('auth_rule')->where('id', $currentId)->find();
if (!$row) {
break;
}
if (intval($row['ismenu']) === 1 && $row['status'] === 'normal') {
array_unshift($path, $row['title']);
}
$currentId = intval($row['pid']);
$guard++;
}
return $path;
}
protected function line(Output $output, $label, $ok, &$pass, &$fail, $extra = '')
{
if ($ok) {
$pass++;
$output->writeln('[OK] ' . $label . ($extra ? ' (' . $extra . ')' : ''));
} else {
$fail++;
$output->writeln('[FAIL] ' . $label . ($extra ? ' (' . $extra . ')' : ''));
}
}
protected function tableExists($table)
{
try {
$rows = Db::query("SHOW TABLES LIKE '{$table}'");
return !empty($rows);
} catch (\Exception $e) {
return false;
}
}
protected function hasSdk($root)
{
$paths = [
$root . 'addons/shopro_huifu/library/sdk/extracted/huifurepo-bspay-php-sdk-45b895b/BsPaySdk/init.php',
$root . 'vendor/huifurepo/dg-php-sdk/BsPaySdk/init.php',
];
foreach ($paths as $p) {
if (is_file($p)) {
return true;
}
}
return false;
}
}