# 全局异常处理开发文档 ## 概述 本文档详细介绍了基于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 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 canUpdate($user)) { Handler::throwError('无权修改此用户', 403001); } // 更新逻辑 $user->update($data); return $user; } private function canUpdate(User $user): bool { // 权限检查逻辑 return true; } } ``` ### 3. 中间件中使用异常 ```php 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 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 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 errors = $errors; parent::__construct([422, $message]); } public function getErrors(): array { return $this->errors; } } ``` ### 2. 异常处理中间件 ```php json([ 'success' => false, 'message' => $e->getMessage(), 'code' => $e->getCode(), 'timestamp' => now()->toISOString() ]); } } } ``` ### 3. 异常通知系统 ```php 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月 **作者**: 开发团队 **审核**: 技术负责人