305 lines
7.2 KiB
Markdown
305 lines
7.2 KiB
Markdown
# Backend Debug Runbook
|
||
|
||
## 常见问题排查
|
||
|
||
### 1. 登录返回 401 "账号或密码错误"
|
||
|
||
#### 症状
|
||
```bash
|
||
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": "账号或密码错误"}
|
||
```
|
||
|
||
#### 排查步骤
|
||
|
||
1. **检查数据库是否有用户**
|
||
```bash
|
||
docker exec hyperf-mysql mysql -uroot -p123456 case_db \
|
||
-e "SELECT id, username, phone FROM auth_users WHERE deleted_at IS NULL;"
|
||
```
|
||
|
||
2. **如果用户不存在,运行 Seeder**
|
||
```bash
|
||
docker exec hyperf-skeleton php bin/hyperf.php db:seed --class=DatabaseSeeder
|
||
```
|
||
|
||
3. **验证密码比对逻辑**
|
||
```bash
|
||
# 检查 User Model 是否使用 password_hash 和 password_verify
|
||
# 确保 Seeder 中的密码也是用 password_hash 加密的
|
||
```
|
||
|
||
### 2. 返回 500 "Internal Server Error" - env() 未定义
|
||
|
||
#### 症状
|
||
```bash
|
||
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()` 错误。
|
||
|
||
#### 解决方案
|
||
|
||
**修改前(错误)**:
|
||
```php
|
||
// ❌ 在 Service/Middleware 中直接使用 env()
|
||
if (env('APP_ENV', 'development') !== 'production') {
|
||
// ...
|
||
}
|
||
$secret = env('JWT_SECRET', 'default');
|
||
```
|
||
|
||
**修改后(正确)**:
|
||
```php
|
||
// ✅ 使用 Hyperf 的 config() 函数读取配置
|
||
$appEnv = \Hyperf\Config\config('app.env', 'development');
|
||
if ($appEnv !== 'production') {
|
||
// ...
|
||
}
|
||
$secret = \Hyperf\Config\config('jwt.secret', 'default-secret');
|
||
```
|
||
|
||
**配置文件正确写法**(在配置文件中可以使用 env()):
|
||
```php
|
||
// config/autoload/jwt.php
|
||
use function Hyperf\Support\env;
|
||
|
||
return [
|
||
'secret' => env('JWT_SECRET', 'dev-fallback-secret'),
|
||
'ttl' => (int) env('JWT_TTL', 7200),
|
||
];
|
||
```
|
||
|
||
**常见错误场景**:
|
||
```php
|
||
// ❌ 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') { // 仍然报错
|
||
```
|
||
|
||
**正确实践**:
|
||
```php
|
||
// ✅ 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 不一致
|
||
|
||
#### 症状
|
||
```bash
|
||
# 登录成功
|
||
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**:
|
||
|
||
```php
|
||
// 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'));
|
||
// ...
|
||
}
|
||
```
|
||
|
||
**禁止的做法**:
|
||
```php
|
||
// ❌ 使用 md5(__DIR__) 会导致路径不一致
|
||
$secret = 'dev-only-insecure-key-' . md5(__DIR__);
|
||
```
|
||
|
||
### 4. 服务启动失败 - 异步队列配置缺失
|
||
|
||
#### 症状
|
||
```bash
|
||
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
|
||
<?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`**:
|
||
```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 确保初始数据正确
|
||
|
||
## 调试命令
|
||
|
||
### 查看日志
|
||
```bash
|
||
# Hyperf 应用日志
|
||
docker logs hyperf-skeleton --tail 100 -f
|
||
|
||
# 数据库日志
|
||
docker logs hyperf-mysql --tail 50
|
||
|
||
# Redis 日志
|
||
docker logs hyperf-redis --tail 50
|
||
```
|
||
|
||
### 进入容器调试
|
||
```bash
|
||
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
|
||
```bash
|
||
# 登录
|
||
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"
|