Files
oil/server-tp6/deploy/h5_debug.php
T
2026-09-18 20:55:21 +08:00

114 lines
4.5 KiB
PHP
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
// +----------------------------------------------------------------------
// | 7.15 免登链接 · 编码方式对照工具(仅联调用,CLI)
// +----------------------------------------------------------------------
// | 背景:同一条链接在桌面浏览器能打开,微信里打开 / 扫码却提示验签失败。
// | base64 签名里的 + / = 在不同 URL 解析实现下会被区别对待,
// | 本工具用同一个签名生成多种编码变体,逐个在微信里试,定位是哪一种。
// |
// | 用法(在站点根目录执行):
// | php deploy/h5_debug.php 13800000000
// | php deploy/h5_debug.php 13800000000 2 # 指定企业ID,不传用默认企业
// |
// | 测法:把打印出来的每条链接发到微信「文件传输助手」,逐条点开,
// | 记下哪几条能进、哪几条报验签失败,再据此定方案。
// |
// | ⚠️ 会生成真实可登录的免登链接,联调结束请删除本文件。
// +----------------------------------------------------------------------
declare (strict_types=1);
if (PHP_SAPI !== 'cli') {
http_response_code(403);
exit('仅限命令行执行');
}
$root = dirname(__DIR__);
require $root . '/vendor/autoload.php';
$app = new \think\App($root . '/');
$app->initialize();
use app\common\model\qyt\Enterprise as EnterpriseModel;
use app\common\service\QytConfig;
$mobile = $argv[1] ?? '';
$enterpriseId = (int) ($argv[2] ?? 0);
if (!preg_match('/^1[3-9]\d{9}$/', $mobile)) {
exit("用法:php deploy/h5_debug.php <司机手机号> [企业ID]\n");
}
$enterprise = EnterpriseModel::resolve($enterpriseId);
if (!$enterprise) {
exit("未找到可用企业,请先在后台「设置 → 企业管理」建企业并设为默认\n");
}
$conf = QytConfig::forEnterprise($enterprise);
$base = rtrim((string) $conf['h5_base_url'], '/');
$appId = (string) ($conf['h5_app_id'] ?: $conf['app_id']);
// ---------- 按 7.15 规则签名(ksort -> http_build_query -> urldecode -> 大写MD5 -> SHA1withRSA -> base64) ----------
$params = [
'appId' => $appId,
'mobile' => $mobile,
'timestamp' => date('YmdHis'),
];
!empty($conf['h5_with_creditcode']) && $params['creditcode'] = (string) $conf['business_license'];
$forSign = $params;
ksort($forSign);
$raw = urldecode(http_build_query($forSign));
$md5 = strtoupper(md5($raw));
$pem = trim((string) $conf['private_key']);
if (strpos($pem, '-----BEGIN') === false) {
$pem = "-----BEGIN PRIVATE KEY-----\n" . chunk_split($pem, 64, "\n") . "-----END PRIVATE KEY-----\n";
}
$pkey = openssl_pkey_get_private($pem);
if ($pkey === false) {
exit("私钥无效:" . openssl_error_string() . "\n");
}
$sig = '';
openssl_sign($md5, $sig, $pkey, OPENSSL_ALGO_SHA1);
$sign = base64_encode($sig);
// ---------- 输出 ----------
$build = function (array $p, string $sign, string $tail) {
$pairs = [];
foreach ($p as $k => $v) {
$pairs[] = $k . '=' . $v;
}
$pairs[] = 'sign=' . $sign;
return '/?' . implode('&', $pairs) . $tail;
};
echo "企业:{$enterprise['company_name']}({$enterprise['business_license']},状态 {$enterprise['status']})\n";
echo "通道:{$conf['passageway_name']} appId:{$appId}\n";
echo "待签串:{$raw}\n";
echo "大写MD5:{$md5}\n";
echo "签名含 + 的个数:" . substr_count($sign, '+') . ",含 / 的个数:" . substr_count($sign, '/') . "\n";
echo str_repeat('=', 70) . "\n\n";
$variants = [
'A. 原样(当前线上行为)' => [$sign, '#/'],
'B. 只把 + 编码为 %2B' => [str_replace('+', '%2B', $sign), '#/'],
'C. 把 + 和 / 都编码' => [str_replace(['+', '/'], ['%2B', '%2F'], $sign), '#/'],
'D. 全部编码(改之前的老行为)' => [rawurlencode($sign), '#/'],
'E. 原样,但去掉末尾的 #/' => [$sign, ''],
];
$i = 1;
foreach ($variants as $label => [$s, $tail]) {
echo "【{$i}】{$label}\n";
echo $base . $build($params, $s, $tail) . "\n\n";
$i++;
}
echo str_repeat('-', 70) . "\n";
echo "把上面 5 条分别发到微信里点开,记录结果:\n";
echo " 只有 A 失败、B 成功 -> 问题是 +,对方页面会解码,我们编码 + 即可\n";
echo " A、B 都失败、C 成功 -> + 和 / 都要编码\n";
echo " A 成功、其余失败 -> 不是编码问题,重点查 E(末尾 #/)与微信追加参数\n";
echo " 只有 E 成功 -> 末尾的 #/ 被算进了 sign\n";
echo " 全都失败 -> 不是 URL 层面的问题,查公钥是否配对、企业是否已审核通过\n";