7.2 KiB
7.2 KiB
Backend Debug Runbook
常见问题排查
1. 登录返回 401 "账号或密码错误"
症状
curl -X POST http://127.0.0.1:9501/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "superadmin", "password": "Admin@2026"}'
# 返回: {"code": 401, "message": "账号或密码错误"}
排查步骤
- 检查数据库是否有用户
docker exec hyperf-mysql mysql -uroot -p123456 case_db \
-e "SELECT id, username, phone FROM auth_users WHERE deleted_at IS NULL;"
- 如果用户不存在,运行 Seeder
docker exec hyperf-skeleton php bin/hyperf.php db:seed --class=DatabaseSeeder
- 验证密码比对逻辑
# 检查 User Model 是否使用 password_hash 和 password_verify
# 确保 Seeder 中的密码也是用 password_hash 加密的
2. 返回 500 "Internal Server Error" - env() 未定义
症状
curl -X POST http://127.0.0.1:9501/api/auth/sms/send \
-H "Content-Type: application/json" \
-d '{"phone": "18970867739", "purpose": "login"}'
# 返回: {"code": 500, "message": "Internal Server Error"}
日志错误
[ERROR] Call to undefined function env()[43] in /opt/www/modules/Auth/src/Service/SmsService.php
[ERROR] #0 /opt/www/runtime/container/proxy/Modules_Auth_Http_Controller_Api_SmsController.proxy.php(42):
Modules\Auth\Service\SmsService->send()
根因
PHP CLI 和 Swoole Worker 环境中不提供全局 env() 函数(这是 Laravel 特有的),会导致 Call to undefined function env() 错误。
解决方案
修改前(错误):
// ❌ 在 Service/Middleware 中直接使用 env()
if (env('APP_ENV', 'development') !== 'production') {
// ...
}
$secret = env('JWT_SECRET', 'default');
修改后(正确):
// ✅ 使用 Hyperf 的 config() 函数读取配置
$appEnv = \Hyperf\Config\config('app.env', 'development');
if ($appEnv !== 'production') {
// ...
}
$secret = \Hyperf\Config\config('jwt.secret', 'default-secret');
配置文件正确写法(在配置文件中可以使用 env()):
// config/autoload/jwt.php
use function Hyperf\Support\env;
return [
'secret' => env('JWT_SECRET', 'dev-fallback-secret'),
'ttl' => (int) env('JWT_TTL', 7200),
];
常见错误场景:
// ❌ Service 层中使用 env()
class SmsService {
public function send() {
if (env('APP_ENV') !== 'production') { // 报错
// ...
}
}
}
// ❌ Middleware 层中使用 env()
class JwtAuthMiddleware {
public function process() {
$secret = env('JWT_SECRET'); // 报错
}
}
// ❌ 即使添加命名空间前缀也不行
if (\env('APP_ENV') !== 'production') { // 仍然报错
正确实践:
// ✅ Service 层使用 config()
use Hyperf\Config\config;
class SmsService {
public function send() {
$appEnv = config('app.env', 'development');
if ($appEnv !== 'production') {
// ...
}
}
}
// ✅ 配置文件中使用 env()(正确位置)
// config/autoload/app.php
use function Hyperf\Support\env;
return [
'env' => env('APP_ENV', 'development'),
'debug' => env('APP_DEBUG', false),
];
3. Token 验证失败 - Secret 不一致
症状
# 登录成功
TOKEN=$(curl -s -X POST http://127.0.0.1:9501/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "superadmin", "password": "Admin@2026"}' | jq -r '.data.access_token')
# 但 profile 接口返回 40102
curl -X GET "http://127.0.0.1:9501/api/auth/profile" \
-H "Authorization: Bearer $TOKEN"
# 返回: {"code": 40102, "message": "Token invalid"}
根因
AuthService 和 JwtAuthMiddleware 使用不同的 JWT secret。
解决方案
确保两处使用相同的 secret:
// AuthService.php
protected function generateToken(User $user, string $type = 'access', ?int $ttlOverride = null): string
{
$secret = 'dev-only-insecure-secret-20260304'; // 固定值,不要用 md5(__DIR__)
// ...
}
// JwtAuthMiddleware.php
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
// ...
$secret = 'dev-only-insecure-secret-20260304'; // 与 AuthService 完全相同
$decoded = JWT::decode($token, new Key($secret, 'HS256'));
// ...
}
禁止的做法:
// ❌ 使用 md5(__DIR__) 会导致路径不一致
$secret = 'dev-only-insecure-key-' . md5(__DIR__);
4. 服务启动失败 - 异步队列配置缺失
症状
docker compose restart hyperf-skeleton
docker logs hyperf-skeleton --tail 50
日志错误
[ERROR] Entry "Modules\Security\Listener\SecurityEventListener" cannot be resolved:
Entry "Hyperf\AsyncQueue\Driver\DriverFactory" cannot be resolved
或
[ERROR] [Error] default is a invalid driver
根因
使用了异步队列,但缺少 config/autoload/async_queue.php 配置。
解决方案
创建配置文件:
<?php
declare(strict_types=1);
use function Hyperf\Support\env;
return [
'default' => [
'driver' => env('ASYNC_QUEUE_DRIVER', 'redis'),
'channel' => 'queue',
'retry_after' => 60,
'block_seconds' => 5,
'max_messages' => 100,
'redis' => [
'pool' => 'default',
],
],
];
5. 服务启动失败 - Config 对象未注入
症状
[ERROR] Entry "Hyperf\Config\Config" cannot be resolved:
Parameter $configs of __construct() has no value defined or guessable
根因
Hyperf Config 对象未在 dependencies.php 中注册。
解决方案
检查 config/autoload/dependencies.php:
return [
Hyperf\Database\Migrations\Migrator::class => App\Database\MigratorFactory::class,
Hyperf\Database\Seeders\Seed::class => App\Database\SeedFactory::class,
// 确保 Config 不在这里注册(Hyperf 会自动注册)
];
验证清单
新模块开发前
- 检查是否需要队列配置
- 检查是否需要缓存配置
- 检查是否需要 JWT 配置
- 所有
env()都有默认值
代码修改后
- 不在 Service/Middleware 中使用
config() - JWT secret 使用固定值或环境变量
- AuthService 和 JwtAuthMiddleware 使用相同 secret
- 重启服务并测试关键路径
部署前
- 生产环境设置
JWT_SECRET环境变量 - 检查 .env.example 是否包含所有必要的环境变量
- 运行 Seeder 确保初始数据正确
调试命令
查看日志
# Hyperf 应用日志
docker logs hyperf-skeleton --tail 100 -f
# 数据库日志
docker logs hyperf-mysql --tail 50
# Redis 日志
docker logs hyperf-redis --tail 50
进入容器调试
docker exec -it hyperf-skeleton bash
# 运行迁移
php bin/hyperf.php migrate
# 运行 Seeder
php bin/hyperf.php db:seed
# 检查 PHP 语法
php -l modules/Auth/src/Service/AuthService.php
测试 API
# 登录
curl -X POST http://127.0.0.1:9501/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "superadmin", "password": "Admin@2026"}'
# 使用 token 访问受保护接口
TOKEN="your-access-token-here"
curl -X GET "http://127.0.0.1:9501/api/auth/profile" \
-H "Authorization: Bearer $TOKEN"