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

257 lines
9.5 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 一键诊断(可长期保留)
*
* 默认仅允许命令行,避免被公网访问:
* php wechat_qrcode_diag.php
* php wechat_qrcode_diag.php 188
*
* 结果文件:
* wechat_qrcode_diag_result.txt
*
* 如确需浏览器访问,把下面 $allowWeb 改成 true并自行改 $webSecret。
* 用完建议改回 false。
*/
$cli = PHP_SAPI === 'cli';
$allowWeb = false; // 长期保留时请保持 false
$webSecret = 'CHANGE_ME_TO_A_LONG_RANDOM_SECRET';
$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 (!$allowWeb) {
http_response_code(403);
echo "web access disabled, use: php wechat_qrcode_diag.php 188\n";
exit(1);
}
if ($webSecret === '' || $webSecret === 'CHANGE_ME_TO_A_LONG_RANDOM_SECRET' || ($_GET['key'] ?? '') !== $webSecret) {
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(function_exists('miniProgramConfig') ? miniProgramConfig([
'app_id' => $appId,
'secret' => $secretCfg,
]) : [
'app_id' => $appId,
'secret' => $secretCfg,
'log' => [
'default' => 'prod',
'channels' => [
'prod' => [
'driver' => 'single',
'path' => storage_path('logs/easywechat.log'),
'level' => 'error',
],
],
],
]);
$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('--- deploy check ---');
try {
$ref = new ReflectionMethod(App\Models\Course::class, 'getCourseQrcode');
$put('getCourseQrcode_params=' . $ref->getNumberOfParameters());
$put('has_resolveMiniProgramQrcode=' . (method_exists(App\Models\Course::class, 'resolveMiniProgramQrcode') ? 'yes' : 'no'));
$appends = (new ReflectionClass(App\Models\Course::class))->getDefaultProperties()['appends'] ?? [];
$put('qrcode_in_appends=' . (in_array('qrcode', $appends, true) ? 'yes' : 'no'));
$ctrl = file_get_contents(__DIR__ . '/app/Http/Controllers/Admin/CourseController.php');
$put('controller_has_generate_flag=' . (strpos($ctrl, "generate") !== false && strpos($ctrl, 'getCourseQrcode') !== false ? 'yes' : 'no'));
$put('controller_has_try_catch_qrcode=' . (strpos($ctrl, 'courses/qrcode failed') !== false ? 'yes' : 'no'));
} catch (Throwable $e) {
$put('deploy_check_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);
}