You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
wx.sstbc.com/wechat_qrcode_diag.php

219 lines
7.7 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
/**
* 微信小程序码 / access_token 一键诊断
*
* 用法(任选其一):
* 1) 命令行php wechat_qrcode_diag.php
* 2) 命令行指定课程php wechat_qrcode_diag.php 188
* 3) 浏览器(用完务必删除本文件):
* https://你的域名/wechat_qrcode_diag.php?key=stbc-diag-2026&course_id=188
*/
$cli = PHP_SAPI === 'cli';
$secret = 'stbc-diag-2026';
$courseId = $cli
? (int) (isset($argv[1]) ? $argv[1] : 188)
: (int) (isset($_GET['course_id']) ? $_GET['course_id'] : 188);
if (!$cli) {
header('Content-Type: text/plain; charset=utf-8');
if (($_GET['key'] ?? '') !== $secret) {
http_response_code(403);
echo "forbidden\n";
exit(1);
}
}
$lines = [];
$put = static function (string $text) use (&$lines): void {
$lines[] = $text;
};
$put('=== WeChat QRCode Diagnose ===');
$put('time: ' . date('Y-m-d H:i:s'));
$put('php: ' . PHP_VERSION);
$put('cwd: ' . getcwd());
$put('course_id: ' . $courseId);
$put('');
try {
require __DIR__ . '/vendor/autoload.php';
$app = require __DIR__ . '/bootstrap/app.php';
$kernel = $app->make(Illuminate\Contracts\Console\Kernel::class);
$kernel->bootstrap();
$put('[bootstrap] ok');
} catch (Throwable $e) {
$put('[bootstrap] FAIL: ' . $e->getMessage());
output_and_exit($lines, $cli, 1);
}
$put('');
$put('--- config ---');
$appId = (string) config('app.applet_appid');
$secretCfg = (string) config('app.applet_secret');
$put('APPLET_APPID=[' . $appId . ']');
$put('APPLET_SECRET_set=' . ($secretCfg !== '' ? 'yes(len=' . strlen($secretCfg) . ')' : 'EMPTY'));
$put('CACHE_DRIVER=[' . config('cache.default') . ']');
$put('APP_URL=[' . config('app.url') . ']');
$put('LOG_CHANNEL=[' . config('logging.default') . ']');
$put('LOG_LEVEL=[' . env('LOG_LEVEL', 'debug') . ']');
$put('public.root=[' . config('filesystems.disks.public.root') . ']');
$put('public.url=[' . config('filesystems.disks.public.url') . ']');
$put('');
$put('--- cache dir ---');
$cacheDir = storage_path('framework/cache/data');
$put('cache_dir=' . $cacheDir);
$put('exists=' . (is_dir($cacheDir) ? 'yes' : 'no'));
$put('writable=' . (is_writable($cacheDir) ? 'yes' : 'no'));
if (is_dir($cacheDir)) {
$put('perms=' . substr(sprintf('%o', fileperms($cacheDir)), -4));
}
$put('');
$put('--- laravel cache ---');
try {
Illuminate\Support\Facades\Cache::put('wechat_qrcode_diag', 'ok', 60);
$val = Illuminate\Support\Facades\Cache::get('wechat_qrcode_diag');
$has = Illuminate\Support\Facades\Cache::has('wechat_qrcode_diag');
$put('put_get=' . ($val === 'ok' ? 'ok' : 'FAIL'));
$put('has=' . ($has ? 'yes' : 'no'));
Illuminate\Support\Facades\Cache::forget('wechat_qrcode_diag');
} catch (Throwable $e) {
$put('cache_exception: ' . $e->getMessage());
}
$put('');
$put('--- storage public ---');
$publicRoot = (string) config('filesystems.disks.public.root');
$qrDir = rtrim($publicRoot, '/') . '/course_qrcode';
$qrPath = $qrDir . '/' . $courseId . '.png';
$put('qr_dir=' . $qrDir);
$put('qr_dir_exists=' . (is_dir($qrDir) ? 'yes' : 'no'));
$put('qr_dir_writable=' . ((is_dir($qrDir) ? is_writable($qrDir) : is_writable(dirname($qrDir))) ? 'yes' : 'no'));
$put('qr_file_exists=' . (is_file($qrPath) ? 'yes' : 'no'));
if (is_file($qrPath)) {
$put('qr_file_size=' . filesize($qrPath));
}
try {
if (!is_dir($qrDir)) {
mkdir($qrDir, 0755, true);
}
$testFile = $qrDir . '/_diag_write_test';
file_put_contents($testFile, 'ok');
$ok = is_file($testFile) && trim((string) file_get_contents($testFile)) === 'ok';
@unlink($testFile);
$put('write_test=' . ($ok ? 'ok' : 'FAIL'));
} catch (Throwable $e) {
$put('write_test_exception: ' . $e->getMessage());
}
$put('');
$put('--- easywechat token ---');
if ($appId === '' || $secretCfg === '') {
$put('skip: missing appid/secret');
} else {
try {
$mini = EasyWeChat\Factory::miniProgram([
'app_id' => $appId,
'secret' => $secretCfg,
]);
$token = $mini->access_token->getToken(true);
$put('token_ok=yes');
$put('expires_in=' . ($token['expires_in'] ?? 'n/a'));
$put('token_prefix=' . substr((string) ($token['access_token'] ?? ''), 0, 10) . '...');
} catch (Throwable $e) {
$put('token_FAIL: ' . $e->getMessage());
output_summary($lines);
output_and_exit($lines, $cli, 1);
}
$put('');
$put('--- easywechat app_code ---');
try {
$tmp = $mini->app_code->get('packages/course/detail?id=' . $courseId, [
'env_version' => 'release',
]);
$body = $tmp instanceof EasyWeChat\Kernel\Http\StreamResponse
? $tmp->getBodyContents()
: (string) $tmp;
$put('bytes=' . strlen($body));
$isJson = strpos(ltrim($body), '{') === 0;
$put('is_json=' . ($isJson ? 'yes' : 'no'));
$put('looks_like_image=' . (strlen($body) > 100 && !$isJson ? 'yes' : 'no'));
if ($isJson) {
$put('wechat_body=' . substr($body, 0, 500));
} else {
// 尝试落盘,验证完整链路
if (!is_dir($qrDir)) {
mkdir($qrDir, 0755, true);
}
file_put_contents($qrPath, $body);
$put('saved_to=' . $qrPath);
$put('saved_ok=' . (is_file($qrPath) ? 'yes' : 'no'));
}
} catch (Throwable $e) {
$put('app_code_FAIL: ' . $e->getMessage());
}
}
$put('');
$put('--- course model generate ---');
try {
if (class_exists(App\Models\Course::class)) {
$course = App\Models\Course::find($courseId);
$put('course_exists=' . ($course ? 'yes' : 'no'));
if ($course) {
$put('course_name=' . ($course->name ?? ''));
$url = (new App\Models\Course())->getCourseQrcode($courseId, true);
$put('getCourseQrcode=[' . $url . ']');
}
} else {
$put('Course model not found');
}
} catch (Throwable $e) {
$put('course_generate_FAIL: ' . $e->getMessage());
}
$put('');
output_summary($lines);
$put('DONE. 用完请删除本文件wechat_qrcode_diag.php');
output_and_exit($lines, $cli, 0);
function output_summary(array &$lines)
{
$text = implode("\n", $lines);
$lines[] = '--- summary ---';
if (strpos($text, 'SECRET_set=EMPTY') !== false || strpos($text, 'APPLET_APPID=[]') !== false) {
$lines[] = '结论倾向: 微信配置未生效APPLET_APPID/SECRET';
} elseif (strpos($text, 'cache_exception') !== false || preg_match('/has=no/', $text)) {
$lines[] = '结论倾向: 缓存不可用(易触发 Failed to cache access token';
} elseif (strpos($text, 'token_FAIL') !== false) {
$lines[] = '结论倾向: 获取 access_token 失败(配置/网络/IP白名单';
} elseif (strpos($text, 'is_json=yes') !== false) {
$lines[] = '结论倾向: 微信拒绝生成小程序码(看 wechat_body';
} elseif (strpos($text, 'write_test=FAIL') !== false || strpos($text, 'write_test_exception') !== false) {
$lines[] = '结论倾向: 二维码目录不可写';
} elseif (strpos($text, 'getCourseQrcode=[http') !== false || strpos($text, 'saved_ok=yes') !== false) {
$lines[] = '结论倾向: 生成链路正常,可再试管理后台生成按钮';
} else {
$lines[] = '结论倾向: 请把完整输出发开发排查';
}
}
function output_and_exit(array $lines, bool $cli, int $code): void
{
$out = implode("\n", $lines) . "\n";
echo $out;
// 同时落盘,方便宝塔直接下载结果
$resultFile = __DIR__ . '/wechat_qrcode_diag_result.txt';
@file_put_contents($resultFile, $out);
if (!$cli) {
echo "\n结果已写入: wechat_qrcode_diag_result.txt\n";
} else {
fwrite(STDERR, "结果已写入: {$resultFile}\n");
}
exit($code);
}