1013 lines
23 KiB
Markdown
1013 lines
23 KiB
Markdown
# 全局异常处理开发文档
|
||
|
||
## 概述
|
||
|
||
本文档详细介绍了基于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月
|
||
**作者**: 开发团队
|
||
**审核**: 技术负责人 |