485 lines
11 KiB
Markdown
485 lines
11 KiB
Markdown
# 异常处理使用指南
|
||
|
||
## 概述
|
||
|
||
本项目采用统一的异常处理机制,所有API接口的错误都将返回统一的JSON格式。开发者只需要使用简单的方法就能抛出业务异常。
|
||
|
||
## 快速使用
|
||
|
||
### 1. 引入异常处理器
|
||
|
||
```php
|
||
use App\Exceptions\Handler;
|
||
```
|
||
|
||
### 2. 基本用法
|
||
|
||
```php
|
||
// 最基本的用法 - 抛出默认错误
|
||
Handler::throw('用户不存在');
|
||
|
||
// 指定错误码
|
||
Handler::throw('用户不存在', 404);
|
||
|
||
// 使用别名方法
|
||
Handler::error('参数错误', 400);
|
||
|
||
// 操作失败
|
||
Handler::fail('删除失败');
|
||
```
|
||
|
||
## 可用方法
|
||
|
||
### Handler::throw($message, $code = 400)
|
||
**功能**: 抛出业务异常(主要方法)
|
||
```php
|
||
Handler::throw('数据不存在', 404);
|
||
Handler::throw('权限不足', 403);
|
||
Handler::throw('操作失败'); // 默认错误码400
|
||
```
|
||
|
||
### Handler::error($message, $code = 400)
|
||
**功能**: throw方法的别名,使用习惯更友好
|
||
```php
|
||
Handler::error('用户名已存在', 409);
|
||
Handler::error('参数错误');
|
||
```
|
||
|
||
### Handler::fail($message = '操作失败')
|
||
**功能**: 快速抛出操作失败异常
|
||
```php
|
||
Handler::fail(); // 默认消息:操作失败
|
||
Handler::fail('用户创建失败');
|
||
Handler::fail('文件上传失败');
|
||
```
|
||
|
||
## 使用场景
|
||
|
||
### 1. 控制器中使用
|
||
|
||
```php
|
||
<?php
|
||
|
||
namespace App\Http\Controllers\Admin;
|
||
|
||
use App\Http\Controllers\BaseController;
|
||
use App\Exceptions\Handler;
|
||
use App\Models\User;
|
||
use Illuminate\Http\Request;
|
||
|
||
class UserController extends BaseController
|
||
{
|
||
public function show($id)
|
||
{
|
||
// 参数验证
|
||
if (empty($id)) {
|
||
Handler::error('用户ID不能为空');
|
||
}
|
||
|
||
// 查找用户
|
||
$user = User::find($id);
|
||
if (!$user) {
|
||
Handler::throw('用户不存在', 404);
|
||
}
|
||
|
||
// 权限检查
|
||
if ($user->status === 0) {
|
||
Handler::throw('用户已被禁用', 403);
|
||
}
|
||
|
||
return $this->SuccessObject($user);
|
||
}
|
||
|
||
public function store(Request $request)
|
||
{
|
||
$username = $request->input('username');
|
||
|
||
// 检查用户名是否存在
|
||
if (User::where('username', $username)->exists()) {
|
||
Handler::error('用户名已存在', 409);
|
||
}
|
||
|
||
try {
|
||
$user = User::create($request->all());
|
||
return $this->SuccessObject($user);
|
||
} catch (\Exception $e) {
|
||
Handler::fail('用户创建失败');
|
||
}
|
||
}
|
||
|
||
public function destroy($id)
|
||
{
|
||
$user = User::find($id);
|
||
if (!$user) {
|
||
Handler::throw('用户不存在', 404);
|
||
}
|
||
|
||
// 不能删除管理员
|
||
if ($user->is_admin) {
|
||
Handler::error('不能删除管理员账户', 403);
|
||
}
|
||
|
||
if (!$user->delete()) {
|
||
Handler::fail('用户删除失败');
|
||
}
|
||
|
||
return $this->Success(['message' => '删除成功']);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 2. 服务类中使用
|
||
|
||
```php
|
||
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Exceptions\Handler;
|
||
use App\Models\User;
|
||
|
||
class UserService
|
||
{
|
||
public function createUser(array $data)
|
||
{
|
||
// 验证用户名
|
||
if (empty($data['username'])) {
|
||
Handler::error('用户名不能为空');
|
||
}
|
||
|
||
// 检查重复
|
||
if ($this->usernameExists($data['username'])) {
|
||
Handler::error('用户名已存在', 409);
|
||
}
|
||
|
||
// 创建用户
|
||
try {
|
||
return User::create($data);
|
||
} catch (\Exception $e) {
|
||
Handler::fail('用户创建失败');
|
||
}
|
||
}
|
||
|
||
public function updateUserStatus($userId, $status)
|
||
{
|
||
$user = User::find($userId);
|
||
if (!$user) {
|
||
Handler::throw('用户不存在', 404);
|
||
}
|
||
|
||
if ($user->is_admin && $status === 0) {
|
||
Handler::error('不能禁用管理员账户', 403);
|
||
}
|
||
|
||
$user->status = $status;
|
||
if (!$user->save()) {
|
||
Handler::fail('状态更新失败');
|
||
}
|
||
|
||
return $user;
|
||
}
|
||
|
||
private function usernameExists($username)
|
||
{
|
||
return User::where('username', $username)->exists();
|
||
}
|
||
}
|
||
```
|
||
|
||
### 3. 中间件中使用
|
||
|
||
```php
|
||
<?php
|
||
|
||
namespace App\Http\Middleware;
|
||
|
||
use App\Exceptions\Handler;
|
||
use Closure;
|
||
use Illuminate\Http\Request;
|
||
|
||
class CheckUserStatus
|
||
{
|
||
public function handle(Request $request, Closure $next)
|
||
{
|
||
$user = $request->user();
|
||
|
||
if (!$user) {
|
||
Handler::error('用户未登录', 401);
|
||
}
|
||
|
||
if ($user->status === 0) {
|
||
Handler::throw('账户已被禁用', 403);
|
||
}
|
||
|
||
if ($user->deleted) {
|
||
Handler::throw('账户不存在', 404);
|
||
}
|
||
|
||
return $next($request);
|
||
}
|
||
}
|
||
```
|
||
|
||
### 4. 模型中使用
|
||
|
||
```php
|
||
<?php
|
||
|
||
namespace App\Models;
|
||
|
||
use App\Exceptions\Handler;
|
||
use Illuminate\Database\Eloquent\Model;
|
||
|
||
class User extends Model
|
||
{
|
||
public function changePassword($newPassword)
|
||
{
|
||
if (strlen($newPassword) < 6) {
|
||
Handler::error('密码长度不能少于6位');
|
||
}
|
||
|
||
$this->password = bcrypt($newPassword);
|
||
|
||
if (!$this->save()) {
|
||
Handler::fail('密码修改失败');
|
||
}
|
||
}
|
||
|
||
public function assignRole($roleId)
|
||
{
|
||
if ($this->is_admin) {
|
||
Handler::error('管理员不能修改角色', 403);
|
||
}
|
||
|
||
$role = Role::find($roleId);
|
||
if (!$role) {
|
||
Handler::throw('角色不存在', 404);
|
||
}
|
||
|
||
$this->role_id = $roleId;
|
||
if (!$this->save()) {
|
||
Handler::fail('角色分配失败');
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
## 响应格式
|
||
|
||
所有异常都会返回统一的JSON格式:
|
||
|
||
### 成功响应
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "success",
|
||
"code": 200,
|
||
"data": {
|
||
// 具体数据
|
||
}
|
||
}
|
||
```
|
||
|
||
### 异常响应
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "具体错误信息",
|
||
"code": 400,
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
### 参数验证错误
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "参数错误",
|
||
"code": 422,
|
||
"data": null,
|
||
"errors": {
|
||
"username": ["用户名不能为空"],
|
||
"email": ["邮箱格式不正确"]
|
||
}
|
||
}
|
||
```
|
||
|
||
## 常用错误码
|
||
|
||
| 错误码 | 说明 | 使用场景 |
|
||
|--------|------|----------|
|
||
| 400 | 通用错误 | 默认业务错误 |
|
||
| 401 | 未授权 | 用户未登录 |
|
||
| 403 | 权限不足 | 没有操作权限 |
|
||
| 404 | 资源不存在 | 数据不存在 |
|
||
| 409 | 冲突 | 数据已存在 |
|
||
| 422 | 参数错误 | 验证失败 |
|
||
| 500 | 服务器错误 | 系统异常 |
|
||
|
||
## 测试异常处理
|
||
|
||
项目提供了测试接口来验证异常处理(仅开发环境可用):
|
||
|
||
```bash
|
||
# 测试参数验证异常
|
||
curl -X POST http://localhost:8000/admin/test/validation \
|
||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{}'
|
||
|
||
# 测试业务异常
|
||
curl -X GET http://localhost:8000/admin/test/business-exception \
|
||
-H "Authorization: Bearer YOUR_TOKEN"
|
||
|
||
# 测试参数错误
|
||
curl -X GET http://localhost:8000/admin/test/param-error \
|
||
-H "Authorization: Bearer YOUR_TOKEN"
|
||
|
||
# 测试操作失败
|
||
curl -X GET http://localhost:8000/admin/test/fail \
|
||
-H "Authorization: Bearer YOUR_TOKEN"
|
||
|
||
# 测试系统异常
|
||
curl -X GET http://localhost:8000/admin/test/system-exception \
|
||
-H "Authorization: Bearer YOUR_TOKEN"
|
||
```
|
||
|
||
## 最佳实践
|
||
|
||
### 1. 异常使用原则
|
||
- **业务逻辑错误**: 使用 `Handler::throw()` 或 `Handler::error()`
|
||
- **操作失败**: 使用 `Handler::fail()`
|
||
- **参数验证**: 使用Laravel的Validation,会自动处理
|
||
- **系统错误**: 直接throw Exception,会自动处理
|
||
|
||
### 2. 错误信息编写
|
||
- 错误信息要简洁明确
|
||
- 面向用户,避免技术术语
|
||
- 提供解决建议(如果可能)
|
||
|
||
```php
|
||
// ✅ 好的错误信息
|
||
Handler::error('用户名已存在,请尝试其他用户名');
|
||
Handler::throw('文件大小不能超过2MB', 413);
|
||
|
||
// ❌ 不好的错误信息
|
||
Handler::error('数据库约束冲突');
|
||
Handler::throw('系统错误');
|
||
```
|
||
|
||
### 3. 错误码规范
|
||
```php
|
||
// 常用错误码
|
||
Handler::throw('数据不存在', 404);
|
||
Handler::throw('权限不足', 403);
|
||
Handler::throw('数据已存在', 409);
|
||
Handler::error('参数错误', 400);
|
||
```
|
||
|
||
### 4. 性能考虑
|
||
- 不要在循环中频繁抛出异常
|
||
- 异常只用于错误处理,不要用于控制程序流程
|
||
- 在抛出异常前先做基本检查
|
||
|
||
```php
|
||
// ✅ 好的做法
|
||
if (empty($users)) {
|
||
return $this->Success([]); // 返回空数据
|
||
}
|
||
|
||
foreach ($users as $user) {
|
||
// 正常处理
|
||
}
|
||
|
||
// ❌ 不好的做法
|
||
foreach ($users as $user) {
|
||
if ($user->invalid) {
|
||
Handler::error('用户无效'); // 在循环中抛异常
|
||
}
|
||
}
|
||
```
|
||
|
||
## 注意事项
|
||
|
||
1. **生产环境**: 系统异常会隐藏详细信息,只返回"服务器错误"
|
||
2. **开发环境**: 系统异常会显示详细的调试信息
|
||
3. **日志记录**: 所有异常都会自动记录到日志文件
|
||
4. **测试路由**: 仅在开发环境可用,生产环境会自动禁用
|
||
|
||
## 迁移指南
|
||
|
||
如果您之前使用的是复杂的异常处理方式,可以按以下方式迁移:
|
||
|
||
```php
|
||
// 之前的方式
|
||
throw new BusinessException(ResponseEnum::DATA_NOT_FOUND_ERROR, '用户不存在');
|
||
|
||
// 现在的方式
|
||
Handler::throw('用户不存在', 404);
|
||
|
||
// 之前的方式
|
||
$this->throwBusinessException(ResponseEnum::CLIENT_PARAMETER_ERROR, '参数错误');
|
||
|
||
// 现在的方式
|
||
Handler::error('参数错误');
|
||
```
|
||
|
||
这样大大简化了异常处理的使用,让开发更加便捷!
|
||
|
||
## API认证中间件
|
||
|
||
为了解决API项目中认证失败时返回重定向错误的问题,我们创建了专用的API认证中间件:
|
||
|
||
### 中间件文件
|
||
- `app/Http/Middleware/AdminApiAuthenticate.php` - 专用API认证中间件
|
||
|
||
### 功能特点
|
||
1. **统一的401错误响应**:认证失败时返回JSON格式的401错误,而不是重定向
|
||
2. **支持Sanctum认证**:默认使用`sanctum`守护器进行认证
|
||
3. **扩展性强**:可轻松扩展支持其他认证方式
|
||
|
||
### 响应格式
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "未授权访问,请先登录",
|
||
"code": 401,
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
### 使用方式
|
||
在路由中使用`admin.auth`中间件:
|
||
```php
|
||
Route::middleware('admin.auth')->group(function () {
|
||
// 需要认证的路由
|
||
});
|
||
```
|
||
|
||
### 扩展示例
|
||
如果需要支持更多认证方式,可以修改中间件:
|
||
```php
|
||
public function handle(Request $request, Closure $next, ...$guards)
|
||
{
|
||
$guards = empty($guards) ? ['sanctum'] : $guards;
|
||
|
||
// 可以在这里添加其他认证逻辑
|
||
// 例如:JWT、API Key等
|
||
|
||
foreach ($guards as $guard) {
|
||
if (auth()->guard($guard)->check()) {
|
||
auth()->shouldUse($guard);
|
||
return $next($request);
|
||
}
|
||
}
|
||
|
||
// 自定义认证失败响应
|
||
return response()->json([
|
||
'success' => false,
|
||
'message' => '未授权访问,请先登录',
|
||
'code' => 401,
|
||
'data' => null
|
||
], 401);
|
||
}
|
||
```
|