初始化
This commit is contained in:
304
docs/runbooks/backend-debug.md
Normal file
304
docs/runbooks/backend-debug.md
Normal file
@@ -0,0 +1,304 @@
|
||||
# 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"
|
||||
Reference in New Issue
Block a user