Files
vibe_coding/docs/runbooks/backend-debug.md
2026-03-05 21:27:11 +08:00

7.2 KiB
Raw Permalink Blame History

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": "账号或密码错误"}

排查步骤

  1. 检查数据库是否有用户
docker exec hyperf-mysql mysql -uroot -p123456 case_db \
  -e "SELECT id, username, phone FROM auth_users WHERE deleted_at IS NULL;"
  1. 如果用户不存在,运行 Seeder
docker exec hyperf-skeleton php bin/hyperf.php db:seed --class=DatabaseSeeder
  1. 验证密码比对逻辑
# 检查 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"