study-api-v2/docs/全局异常处理开发文档.md

1013 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 全局异常处理开发文档
## 概述
本文档详细介绍了基于Laravel 12实现的全局异常处理系统确保所有API请求的错误响应都采用统一的JSON格式提供完善的错误信息和调试支持。
## 系统架构
### 1. 整体结构
```
全局异常处理系统
├── 异常处理器 (Handler)
│ ├── JSON异常渲染
│ ├── 异常分类处理
│ └── 统一响应格式
├── 业务异常 (BusinessException)
│ ├── 自定义错误码
│ ├── 错误消息
│ └── 业务逻辑异常
├── 日志异常 (LogException)
│ ├── 安全日志记录
│ ├── 多通道尝试
│ └── 权限检查修复
├── 响应枚举 (ResponseEnum)
│ ├── 标准错误码
│ ├── HTTP状态码映射
│ └── 错误消息定义
└── 全局配置 (bootstrap/app.php)
├── 异常处理注册
├── 报告策略
└── 渲染策略
```
### 2. 文件结构
```
app/
├── Exceptions/
│ ├── Handler.php # 主异常处理器
│ ├── BusinessException.php # 业务异常类
│ └── LogException.php # 日志异常处理类
├── Helpers/
│ └── ResponseEnum.php # 响应码枚举
└── Http/Controllers/
└── BaseController.php # 控制器基类
bootstrap/
└── app.php # 应用启动配置
docs/
└── 全局异常处理开发文档.md # 本文档
```
## 核心组件详细说明
### 1. Handler 异常处理器
**位置**: `app/Exceptions/Handler.php`
**职责**:
- 统一处理所有应用异常
- 区分API和Web请求
- 提供统一的JSON响应格式
- 支持开发和生产环境的不同处理策略
**主要方法**:
#### render() - 异常渲染入口
- **功能**: 判断请求类型,选择处理方式
- **逻辑**: admin/* 路径和期望JSON的请求返回JSON格式
- **输出**: JsonResponse 或 标准Response
#### renderJsonException() - JSON异常处理
- **功能**: 处理API请求的异常
- **支持的异常类型**:
- `AuthenticationException` - 认证失败
- `AccessDeniedHttpException` - 权限不足
- `ValidationException` - 参数验证失败
- `MethodNotAllowedHttpException` - 请求方法不允许
- `ModelNotFoundException` - 模型未找到
- `NotFoundHttpException` - 路由未找到
- `TooManyRequestsHttpException` - 请求频率超限
- `BusinessException` - 业务逻辑异常
#### renderSystemException() - 系统异常处理
- **功能**: 处理未预期的系统异常
- **生产环境**: 隐藏敏感信息,返回通用错误
- **开发环境**: 显示详细调试信息
#### jsonResponse() - 统一JSON响应
- **功能**: 生成标准格式的JSON响应
- **格式**:
```json
{
"success": false,
"message": "错误描述",
"code": 错误代码,
"data": null,
"errors": {} // 仅验证错误时存在
}
```
**辅助方法**:
- `throwError()` - 快速抛出自定义异常
- `throwParamError()` - 抛出参数错误
- `throwNotFound()` - 抛出数据不存在异常
- `throwDataExist()` - 抛出数据已存在异常
### 2. BusinessException 业务异常
**位置**: `app/Exceptions/BusinessException.php`
**职责**:
- 处理业务逻辑相关的异常
- 支持自定义错误码和消息
- 继承标准Exception类
**构造函数**:
```php
public function __construct(array $codeResponse, $info = '')
```
**使用示例**:
```php
// 使用预定义错误码
throw new BusinessException(ResponseEnum::DATA_NOT_FOUND_ERROR);
// 自定义错误信息
throw new BusinessException(ResponseEnum::CLIENT_PARAMETER_ERROR, '用户名不能为空');
// 完全自定义
throw new BusinessException([400001, '自定义错误消息']);
```
### 3. LogException 日志异常处理
**位置**: `app/Exceptions/LogException.php`
**职责**:
- 安全记录异常到日志系统
- 处理日志系统本身的异常
- 提供多种日志记录策略
**主要方法**:
#### safeLog() - 安全日志记录
- **功能**: 防止日志记录失败影响主业务
- **策略**: 多通道尝试记录
- **兜底**: 系统错误日志
#### tryLogToChannels() - 多通道尝试
- **支持通道**: single, daily, errorlog
- **逻辑**: 依次尝试,成功即返回
#### checkAndFixLogPermissions() - 权限检查修复
- **功能**: 检查并修复日志文件权限问题
- **自动修复**: 创建目录、设置权限
### 4. ResponseEnum 响应码枚举
**位置**: `app/Helpers/ResponseEnum.php`
**职责**:
- 定义标准错误码和消息
- 提供HTTP状态码映射
- 统一错误信息管理
**错误码分类**:
- `100-199`: 信息提示
- `200xxx`: 操作成功/失败
- `400xxx`: 客户端错误
- `500xxx`: 服务器错误
**常用错误码**:
```php
const HTTP_OK = [200001, '操作成功'];
const CLIENT_PARAMETER_ERROR = [400200, '参数错误'];
const DATA_NOT_FOUND_ERROR = [400202, '数据不存在'];
const CLIENT_HTTP_UNAUTHORIZED = [401, '授权失败,请先登录'];
const SYSTEM_ERROR = [500001, '服务器错误'];
```
## 异常处理流程
### 1. 请求异常处理流程
```mermaid
graph TD
A[应用异常发生] --> B{请求类型判断}
B -->|admin/* 或 expectsJson| C[JSON异常处理]
B -->|普通Web请求| D[默认处理]
C --> E{异常类型判断}
E -->|认证异常| F[返回401 JSON]
E -->|验证异常| G[返回422 JSON + errors]
E -->|业务异常| H[返回200 JSON + code]
E -->|系统异常| I{环境判断}
E -->|其他HTTP异常| J[返回对应状态码JSON]
I -->|生产环境| K[隐藏错误详情]
I -->|开发环境| L[显示调试信息]
F --> M[统一JSON格式]
G --> M
H --> M
J --> M
K --> M
L --> M
```
### 2. 业务异常抛出流程
```mermaid
graph TD
A[业务逻辑检查] --> B{条件判断}
B -->|正常| C[继续执行]
B -->|异常| D[选择异常类型]
D --> E[Handler::throwParamError()]
D --> F[Handler::throwNotFound()]
D --> G[Handler::throwDataExist()]
D --> H[new BusinessException()]
E --> I[抛出BusinessException]
F --> I
G --> I
H --> I
I --> J[Handler捕获处理]
J --> K[返回JSON响应]
```
### 3. 日志记录流程
```mermaid
graph TD
A[异常发生] --> B[Handler.register()]
B --> C[LogException::safeLog()]
C --> D[尝试single通道]
D --> E{记录成功?}
E -->|是| F[记录完成]
E -->|否| G[尝试daily通道]
G --> H{记录成功?}
H -->|是| F
H -->|否| I[尝试errorlog通道]
I --> J{记录成功?}
J -->|是| F
J -->|否| K[系统error_log兜底]
```
## 使用方法
### 1. 控制器中抛出业务异常
```php
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\BaseController;
use App\Exceptions\Handler;
use App\Helpers\ResponseEnum;
use Illuminate\Http\Request;
class UserController extends BaseController
{
public function show(Request $request, $id)
{
// 方法1: 使用辅助方法
if (empty($id)) {
Handler::throwParamError('用户ID不能为空');
}
$user = User::find($id);
if (!$user) {
Handler::throwNotFound('用户不存在');
}
// 方法2: 直接抛出BusinessException
if ($user->status === 0) {
throw new \App\Exceptions\BusinessException(
ResponseEnum::CLIENT_HTTP_UNAUTHORIZED,
'用户已被禁用'
);
}
return $this->SuccessObject($user);
}
public function store(Request $request)
{
$username = $request->input('username');
// 检查用户名是否已存在
if (User::where('username', $username)->exists()) {
Handler::throwDataExist('用户名已存在');
}
// 创建用户逻辑...
$user = User::create($request->all());
return $this->SuccessObject($user);
}
}
```
### 2. 服务类中使用异常
```php
<?php
namespace App\Services;
use App\Exceptions\Handler;
use App\Models\User;
class UserService
{
public function updateUser($id, array $data)
{
$user = User::find($id);
if (!$user) {
Handler::throwNotFound('用户不存在');
}
// 检查权限
if (!$this->canUpdate($user)) {
Handler::throwError('无权修改此用户', 403001);
}
// 更新逻辑
$user->update($data);
return $user;
}
private function canUpdate(User $user): bool
{
// 权限检查逻辑
return true;
}
}
```
### 3. 中间件中使用异常
```php
<?php
namespace App\Http\Middleware;
use App\Exceptions\Handler;
use Closure;
use Illuminate\Http\Request;
class CheckApiQuota
{
public function handle(Request $request, Closure $next)
{
$user = $request->user();
if (!$user) {
Handler::throwError('用户未认证', 401);
}
// 检查API调用额度
if ($this->exceedsQuota($user)) {
Handler::throwError('API调用次数已达上限', 429001);
}
return $next($request);
}
private function exceedsQuota($user): bool
{
// 额度检查逻辑
return false;
}
}
```
### 4. 模型中使用异常
```php
<?php
namespace App\Models;
use App\Exceptions\Handler;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
public function changeStatus($status)
{
// 状态验证
if (!in_array($status, [0, 1])) {
Handler::throwParamError('无效的用户状态');
}
// 检查是否可以修改
if ($this->is_admin && $status === 0) {
Handler::throwError('不能禁用管理员账户', 403002);
}
$this->status = $status;
$this->save();
}
}
```
## 响应格式规范
### 1. 成功响应格式
```json
{
"success": true,
"message": "success",
"code": 200,
"data": {
// 具体数据
}
}
```
### 2. 错误响应格式
#### 基本错误响应
```json
{
"success": false,
"message": "错误描述",
"code": 400200,
"data": null
}
```
#### 参数验证错误
```json
{
"success": false,
"message": "参数错误",
"code": 400200,
"data": null,
"errors": {
"username": ["用户名不能为空"],
"email": ["邮箱格式不正确"]
}
}
```
#### 开发环境系统错误
```json
{
"success": false,
"message": "具体错误信息",
"code": 500001,
"data": {
"exception": "Illuminate\\Database\\QueryException",
"file": "/path/to/file.php",
"line": 123,
"trace": "异常堆栈信息..."
}
}
```
#### 生产环境系统错误
```json
{
"success": false,
"message": "服务器错误",
"code": 500001,
"data": null
}
```
## 错误码规范
### 1. 错误码结构
```
错误码格式XYYZZZ
- X: 错误类别 (4=客户端错误, 5=服务器错误)
- YY: 业务模块 (00=通用, 01=用户, 02=订单等)
- ZZZ: 具体错误 (001, 002, 003...)
```
### 2. 常用错误码
| 错误码 | HTTP状态码 | 说明 | 使用场景 |
|--------|------------|------|----------|
| 200001 | 200 | 操作成功 | 正常业务响应 |
| 400200 | 422 | 参数错误 | 请求参数验证失败 |
| 400201 | 409 | 数据已存在 | 创建重复数据 |
| 400202 | 404 | 数据不存在 | 查询不到数据 |
| 401 | 401 | 授权失败 | Token无效或过期 |
| 403 | 403 | 权限不足 | 没有操作权限 |
| 404 | 404 | 页面不存在 | 路由不存在 |
| 405 | 405 | 请求方法错误 | HTTP方法不允许 |
| 429 | 429 | 请求频率超限 | 触发限流 |
| 500001 | 500 | 服务器错误 | 系统异常 |
### 3. 自定义错误码示例
```php
// 在ResponseEnum中添加新的错误码
const USER_NOT_VERIFIED = [400101, '用户未验证'];
const USER_LOCKED = [400102, '用户账户已锁定'];
const ORDER_EXPIRED = [400201, '订单已过期'];
const PAYMENT_FAILED = [400301, '支付失败'];
const API_QUOTA_EXCEEDED = [429001, 'API调用次数超限'];
```
## 配置说明
### 1. 异常处理配置
**文件**: `bootstrap/app.php`
```php
->withExceptions(function (Exceptions $exceptions): void {
// 配置不需要报告的异常类型
$exceptions->dontReport([
\App\Exceptions\BusinessException::class,
]);
// 全局API异常处理
$exceptions->render(function (Throwable $e, $request) {
if ($request->expectsJson() || $request->is('admin/*')) {
$handler = app(\App\Exceptions\Handler::class);
return $handler->render($request, $e);
}
});
})
```
### 2. 日志配置
**文件**: `config/logging.php`
```php
'channels' => [
'single' => [
'driver' => 'single',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
],
'daily' => [
'driver' => 'daily',
'path' => storage_path('logs/laravel.log'),
'level' => env('LOG_LEVEL', 'debug'),
'days' => 14,
],
'errorlog' => [
'driver' => 'errorlog',
'level' => env('LOG_LEVEL', 'debug'),
],
],
```
### 3. 环境变量
**.env 文件**:
```env
# 应用调试模式
APP_DEBUG=true # 开发环境
APP_DEBUG=false # 生产环境
# 日志级别
LOG_LEVEL=debug # 开发环境
LOG_LEVEL=error # 生产环境
# 错误报告
LOG_CHANNEL=daily
```
## 测试方法
### 1. 单元测试
```php
<?php
namespace Tests\Unit;
use App\Exceptions\BusinessException;
use App\Exceptions\Handler;
use App\Helpers\ResponseEnum;
use Tests\TestCase;
class ExceptionHandlerTest extends TestCase
{
public function test_business_exception_response()
{
$this->expectException(BusinessException::class);
Handler::throwParamError('测试参数错误');
}
public function test_api_error_response()
{
$response = $this->postJson('/admin/auth/login', []);
$response->assertStatus(422)
->assertJson([
'success' => false,
'code' => 400200,
])
->assertJsonStructure([
'success',
'message',
'code',
'data',
'errors'
]);
}
}
```
### 2. 集成测试
```php
public function test_authentication_error()
{
$response = $this->getJson('/admin/auth/me');
$response->assertStatus(401)
->assertJson([
'success' => false,
'code' => 401,
'message' => '授权失败,请先登录'
]);
}
public function test_not_found_error()
{
$response = $this->getJson('/admin/nonexistent');
$response->assertStatus(404)
->assertJson([
'success' => false,
'code' => 404
]);
}
```
### 3. 手动测试
#### 测试参数验证错误
```bash
curl -X POST http://localhost:8000/admin/auth/login \
-H "Content-Type: application/json" \
-d '{}'
```
预期响应:
```json
{
"success": false,
"message": "参数错误",
"code": 400200,
"data": null,
"errors": {
"username": ["The username field is required."],
"password": ["The password field is required."]
}
}
```
#### 测试认证错误
```bash
curl -X GET http://localhost:8000/admin/auth/me \
-H "Authorization: Bearer invalid_token"
```
预期响应:
```json
{
"success": false,
"message": "授权失败,请先登录",
"code": 401,
"data": null
}
```
## 性能优化
### 1. 异常处理性能
**避免过度异常处理**:
```php
// ❌ 不好的做法 - 用异常控制正常流程
try {
$user = User::findOrFail($id);
} catch (ModelNotFoundException $e) {
return $this->Field('用户不存在');
}
// ✅ 好的做法 - 正常判断
$user = User::find($id);
if (!$user) {
Handler::throwNotFound('用户不存在');
}
```
**缓存异常信息**:
```php
// 对于频繁抛出的业务异常,可以考虑缓存错误信息
class CachedBusinessException extends BusinessException
{
private static array $messageCache = [];
public function __construct(array $codeResponse, $info = '')
{
$key = md5(serialize($codeResponse) . $info);
if (!isset(self::$messageCache[$key])) {
self::$messageCache[$key] = $info ?: $codeResponse[1];
}
parent::__construct($codeResponse, self::$messageCache[$key]);
}
}
```
### 2. 日志性能优化
**异步日志记录**:
```php
// 在config/logging.php中配置
'async' => [
'driver' => 'custom',
'via' => App\Logging\AsyncLoggerFactory::class,
'channel' => 'daily',
],
```
**日志分级**:
```php
// 只在必要时记录详细信息
if (config('app.debug')) {
LogException::safeLog('详细调试信息', $e, $context);
} else {
LogException::safeLog('简化错误信息', $e);
}
```
## 监控和报警
### 1. 错误监控
**统计异常频率**:
```php
// 可以添加到Handler中
protected function reportException(Throwable $e): void
{
// 统计异常类型和频率
$type = get_class($e);
Cache::increment("exception_count:{$type}");
// 记录异常趋势
$hour = now()->format('Y-m-d-H');
Cache::increment("exception_hourly:{$hour}");
parent::report($e);
}
```
**关键异常报警**:
```php
protected function shouldAlert(Throwable $e): bool
{
// 系统异常立即报警
if (!($e instanceof BusinessException)) {
return true;
}
// 高频业务异常报警
$count = Cache::get("exception_count:" . get_class($e), 0);
return $count > 100; // 超过100次/小时报警
}
```
### 2. 性能监控
**响应时间监控**:
```php
class ExceptionMiddleware
{
public function handle($request, Closure $next)
{
$start = microtime(true);
try {
return $next($request);
} catch (Throwable $e) {
$duration = microtime(true) - $start;
// 记录异常处理时间
Log::info('Exception handling time', [
'duration' => $duration,
'exception' => get_class($e)
]);
throw $e;
}
}
}
```
## 安全考虑
### 1. 信息泄露防护
**生产环境信息过滤**:
```php
protected function filterSensitiveInfo(Throwable $e): array
{
if (!config('app.debug')) {
return [
'message' => '服务器内部错误',
'code' => 500001
];
}
// 过滤敏感路径
$file = str_replace(base_path(), '', $e->getFile());
return [
'message' => $e->getMessage(),
'file' => $file,
'line' => $e->getLine()
];
}
```
### 2. 日志安全
**敏感信息脱敏**:
```php
protected function sanitizeLogData(array $context): array
{
$sensitive = ['password', 'token', 'secret', 'key'];
foreach ($context as $key => $value) {
if (in_array(strtolower($key), $sensitive)) {
$context[$key] = '***';
}
}
return $context;
}
```
## 注意事项
### 1. 开发注意事项
**异常分类原则**:
- **业务异常**: 可预期的业务逻辑错误使用BusinessException
- **系统异常**: 不可预期的技术错误,让系统自动处理
- **验证异常**: 参数格式错误使用Laravel验证器
**性能考虑**:
- 避免在循环中频繁抛出异常
- 合理使用异常,不要用异常控制正常业务流程
- 异常信息要清晰明确,便于调试
### 2. 部署注意事项
**环境配置**:
- 生产环境必须关闭DEBUG模式
- 配置适当的日志轮转策略
- 设置日志文件权限
**监控设置**:
- 配置异常报警阈值
- 监控错误率和响应时间
- 定期检查日志文件大小
### 3. 维护注意事项
**错误码管理**:
- 统一错误码命名规范
- 定期清理无用的错误码
- 保持错误信息的一致性
**日志管理**:
- 定期清理旧日志文件
- 监控磁盘空间使用
- 备份重要错误日志
## 扩展开发
### 1. 自定义异常类型
```php
<?php
namespace App\Exceptions;
class ValidationException extends BusinessException
{
public function __construct(array $errors, string $message = '参数验证失败')
{
$this->errors = $errors;
parent::__construct([422, $message]);
}
public function getErrors(): array
{
return $this->errors;
}
}
```
### 2. 异常处理中间件
```php
<?php
namespace App\Http\Middleware;
class ExceptionHandlingMiddleware
{
public function handle($request, Closure $next)
{
try {
return $next($request);
} catch (BusinessException $e) {
// 业务异常特殊处理
return response()->json([
'success' => false,
'message' => $e->getMessage(),
'code' => $e->getCode(),
'timestamp' => now()->toISOString()
]);
}
}
}
```
### 3. 异常通知系统
```php
<?php
namespace App\Services;
class ExceptionNotificationService
{
public function notify(Throwable $e): void
{
// 邮件通知
if ($this->shouldEmailNotify($e)) {
Mail::to(config('app.admin_email'))
->send(new ExceptionNotification($e));
}
// 钉钉/企微通知
if ($this->shouldInstantNotify($e)) {
$this->sendInstantMessage($e);
}
}
private function shouldEmailNotify(Throwable $e): bool
{
return !($e instanceof BusinessException) && config('app.env') === 'production';
}
}
```
## 技术支持
### 1. 常见问题
**Q: 如何添加新的异常类型?**
A: 在Handler的`renderJsonException`方法中添加新的异常处理逻辑或者继承BusinessException创建自定义异常类。
**Q: 如何修改异常响应格式?**
A: 修改Handler的`jsonResponse`方法调整返回的JSON结构。
**Q: 生产环境异常信息太少,如何调试?**
A: 检查日志文件或者临时开启DEBUG模式进行调试。
### 2. 调试技巧
**查看详细异常信息**:
```bash
# 查看最新的异常日志
tail -f storage/logs/laravel.log
# 搜索特定异常
grep "Exception" storage/logs/laravel-*.log
```
**临时调试模式**:
```php
// 在特定方法中临时启用详细错误
if ($request->input('debug') === 'true' && auth()->user()->is_admin) {
config(['app.debug' => true]);
}
```
---
**文档版本**: v1.0
**最后更新**: 2024年12月
**作者**: 开发团队
**审核**: 技术负责人