初始化
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"
|
||||
226
docs/runbooks/deployment.md
Normal file
226
docs/runbooks/deployment.md
Normal file
@@ -0,0 +1,226 @@
|
||||
# 🚢 Deployment Runbook
|
||||
|
||||
> PHP Hyperf + Swoole + Vue 3 部署流程指南
|
||||
|
||||
---
|
||||
|
||||
## 环境
|
||||
|
||||
| 环境 | URL | 触发 |
|
||||
|------|-----|------|
|
||||
| Development | `localhost:8200` (前端) / `localhost:9501` (后端) | 本地开发 |
|
||||
| Staging | `staging.example.com` | merge to `develop` |
|
||||
| Production | `www.example.com` | merge to `main` |
|
||||
|
||||
## Docker Compose 模板
|
||||
|
||||
```yaml
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# Nginx — 负载均衡 + 静态资源 + SSL
|
||||
nginx:
|
||||
image: nginx:1.25-alpine
|
||||
container_name: app_nginx
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
volumes:
|
||||
- ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf
|
||||
- ./docker/nginx/conf.d:/etc/nginx/conf.d
|
||||
- ./Case-Database-Frontend-user/dist:/usr/share/nginx/html/user
|
||||
- ./Case-Database-Frontend-admin/dist:/usr/share/nginx/html/admin
|
||||
- ./docker/nginx/ssl:/etc/nginx/ssl
|
||||
depends_on:
|
||||
- hyperf
|
||||
networks:
|
||||
- app_network
|
||||
restart: unless-stopped
|
||||
|
||||
# Hyperf — PHP 8.1 + Swoole (HTTP + WebSocket)
|
||||
hyperf:
|
||||
build:
|
||||
context: ./Case-Database-Backend
|
||||
dockerfile: Dockerfile
|
||||
container_name: app_backend
|
||||
ports:
|
||||
- "9501:9501" # HTTP API
|
||||
- "9502:9502" # WebSocket
|
||||
volumes:
|
||||
- ./Case-Database-Backend:/opt/www
|
||||
environment:
|
||||
- APP_ENV=${APP_ENV:-production}
|
||||
- DB_HOST=mysql
|
||||
- DB_PORT=3306
|
||||
- DB_DATABASE=${DB_DATABASE}
|
||||
- DB_USERNAME=${DB_USERNAME}
|
||||
- DB_PASSWORD=${DB_PASSWORD}
|
||||
- REDIS_HOST=redis
|
||||
- REDIS_PORT=6379
|
||||
- REDIS_AUTH=${REDIS_AUTH}
|
||||
depends_on:
|
||||
mysql:
|
||||
condition: service_healthy
|
||||
redis:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- app_network
|
||||
restart: unless-stopped
|
||||
|
||||
# MySQL 8.1 — 主数据库
|
||||
mysql:
|
||||
image: mysql:8.1
|
||||
container_name: app_mysql
|
||||
ports:
|
||||
- "${DB_PORT:-3307}:3306"
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
|
||||
MYSQL_DATABASE: ${DB_DATABASE}
|
||||
MYSQL_USER: ${DB_USERNAME}
|
||||
MYSQL_PASSWORD: ${DB_PASSWORD}
|
||||
volumes:
|
||||
- mysql_data:/var/lib/mysql
|
||||
- ./docker/mysql/conf.d:/etc/mysql/conf.d
|
||||
- ./docker/mysql/init:/docker-entrypoint-initdb.d
|
||||
command: >
|
||||
--default-authentication-plugin=caching_sha2_password
|
||||
--character-set-server=utf8mb4
|
||||
--collation-server=utf8mb4_unicode_ci
|
||||
--max-connections=500
|
||||
--innodb-buffer-pool-size=1G
|
||||
--slow-query-log=ON
|
||||
--long-query-time=1
|
||||
healthcheck:
|
||||
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- app_network
|
||||
restart: unless-stopped
|
||||
|
||||
# Redis 7 — 缓存 + 队列 + Session
|
||||
redis:
|
||||
image: redis:7.2-alpine
|
||||
container_name: app_redis
|
||||
ports:
|
||||
- "${REDIS_PORT:-6379}:6379"
|
||||
command: redis-server --requirepass ${REDIS_AUTH} --maxmemory 512mb --maxmemory-policy allkeys-lru
|
||||
volumes:
|
||||
- redis_data:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "-a", "${REDIS_AUTH}", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
networks:
|
||||
- app_network
|
||||
restart: unless-stopped
|
||||
|
||||
volumes:
|
||||
mysql_data:
|
||||
driver: local
|
||||
redis_data:
|
||||
driver: local
|
||||
|
||||
networks:
|
||||
app_network:
|
||||
driver: bridge
|
||||
```
|
||||
|
||||
## Backend Dockerfile
|
||||
|
||||
```dockerfile
|
||||
FROM hyperf/hyperf:8.1-alpine-v3.18-swoole
|
||||
|
||||
LABEL maintainer="Enterprise Digital Platform"
|
||||
|
||||
ENV TIMEZONE=Asia/Shanghai
|
||||
ENV APP_ENV=production
|
||||
|
||||
WORKDIR /opt/www
|
||||
|
||||
COPY composer.json composer.lock ./
|
||||
RUN composer install --no-dev --optimize-autoloader --no-scripts
|
||||
|
||||
COPY . .
|
||||
RUN composer dump-autoload -o
|
||||
|
||||
EXPOSE 9501 9502
|
||||
|
||||
ENTRYPOINT ["php", "/opt/www/bin/hyperf.php", "start"]
|
||||
```
|
||||
|
||||
## 部署检查清单
|
||||
|
||||
### 部署前
|
||||
- [ ] 所有后端测试通过 (`composer test`)
|
||||
- [ ] 所有前端测试通过 (`npm test`)
|
||||
- [ ] 代码审查已批准
|
||||
- [ ] 数据库迁移已准备 (`php bin/hyperf.php migrate:status`)
|
||||
- [ ] 环境变量已配置 (`.env`)
|
||||
- [ ] Redis 连接正常
|
||||
- [ ] 静态分析通过 (`composer analyse`)
|
||||
|
||||
### 部署步骤
|
||||
```bash
|
||||
# 1. 拉取最新代码
|
||||
git pull origin main
|
||||
|
||||
# 2. 后端依赖更新
|
||||
cd Case-Database-Backend && composer install --no-dev -o
|
||||
|
||||
# 3. 数据库迁移
|
||||
php bin/hyperf.php migrate
|
||||
|
||||
# 4. 前端构建
|
||||
cd Case-Database-Frontend-user && npm ci && npm run build
|
||||
cd ../Case-Database-Frontend-admin && npm ci && npm run build
|
||||
|
||||
# 5. 重启服务
|
||||
docker-compose restart hyperf
|
||||
docker-compose restart nginx
|
||||
|
||||
# 6. 验证
|
||||
curl -s https://your-domain.com/admin/health | jq
|
||||
```
|
||||
|
||||
### 部署后
|
||||
- [ ] 健康检查通过
|
||||
- [ ] 监控无异常 (CPU/内存/连接数)
|
||||
- [ ] 数据库迁移成功
|
||||
- [ ] WebSocket 连接正常
|
||||
- [ ] 队列消费进程运行中
|
||||
|
||||
## 回滚
|
||||
|
||||
```bash
|
||||
# 代码回滚
|
||||
git revert HEAD
|
||||
git push origin main
|
||||
|
||||
# 数据库回滚
|
||||
php bin/hyperf.php migrate:rollback --step=1
|
||||
|
||||
# Docker 容器回滚到上一个镜像
|
||||
docker-compose pull hyperf
|
||||
docker-compose up -d hyperf
|
||||
```
|
||||
|
||||
## 扩展到多实例
|
||||
|
||||
```bash
|
||||
# 水平扩展 Hyperf 实例
|
||||
docker-compose up -d --scale hyperf=3
|
||||
|
||||
# Nginx upstream 配置多后端
|
||||
upstream hyperf_cluster {
|
||||
server hyperf_1:9501;
|
||||
server hyperf_2:9501;
|
||||
server hyperf_3:9501;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*最后更新: YYYY-MM-DD*
|
||||
54
docs/runbooks/incident-response.md
Normal file
54
docs/runbooks/incident-response.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# 🚨 Incident Response
|
||||
|
||||
> 事故响应流程 (PHP Hyperf + Vue 3)
|
||||
|
||||
---
|
||||
|
||||
## 严重等级
|
||||
|
||||
| 等级 | 定义 | 响应时间 |
|
||||
|------|------|----------|
|
||||
| P0 | 服务完全不可用 | 15 分钟内 |
|
||||
| P1 | 核心功能受损 | 1 小时内 |
|
||||
| P2 | 非核心功能受损 | 4 小时内 |
|
||||
| P3 | 轻微问题 | 1 工作日 |
|
||||
|
||||
## 响应流程
|
||||
|
||||
1. **确认**: 确认问题、评估影响范围和严重等级
|
||||
2. **通知**: 通知相关人员 (企业微信 / Slack #incidents)
|
||||
3. **修复**: 定位问题、实施修复或回滚
|
||||
4. **验证**: 确认修复有效
|
||||
5. **复盘**: 编写事后分析报告
|
||||
|
||||
## 常用命令
|
||||
|
||||
```bash
|
||||
# 查看后端日志
|
||||
tail -f runtime/logs/hyperf.log
|
||||
|
||||
# Docker 查看日志
|
||||
docker compose logs -f hyperf
|
||||
|
||||
# 检查数据库连接
|
||||
php bin/hyperf.php tinker --execute="Db::select('SELECT 1')"
|
||||
|
||||
# 检查 Swoole 状态
|
||||
curl http://localhost:9501/admin/health
|
||||
|
||||
# 快速回滚代码
|
||||
git revert HEAD && git push origin main
|
||||
|
||||
# 回滚数据库迁移
|
||||
php bin/hyperf.php migrate:rollback --step=1
|
||||
|
||||
# Docker 回滚到上一版本
|
||||
docker compose pull && docker compose up -d
|
||||
|
||||
# 重启服务
|
||||
docker compose restart hyperf
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*最后更新: 2026-02-24*
|
||||
Reference in New Issue
Block a user