11 KiB
11 KiB
异常处理使用指南
概述
本项目采用统一的异常处理机制,所有API接口的错误都将返回统一的JSON格式。开发者只需要使用简单的方法就能抛出业务异常。
快速使用
1. 引入异常处理器
use App\Exceptions\Handler;
2. 基本用法
// 最基本的用法 - 抛出默认错误
Handler::throw('用户不存在');
// 指定错误码
Handler::throw('用户不存在', 404);
// 使用别名方法
Handler::error('参数错误', 400);
// 操作失败
Handler::fail('删除失败');
可用方法
Handler::throw($message, $code = 400)
功能: 抛出业务异常(主要方法)
Handler::throw('数据不存在', 404);
Handler::throw('权限不足', 403);
Handler::throw('操作失败'); // 默认错误码400
Handler::error($message, $code = 400)
功能: throw方法的别名,使用习惯更友好
Handler::error('用户名已存在', 409);
Handler::error('参数错误');
Handler::fail($message = '操作失败')
功能: 快速抛出操作失败异常
Handler::fail(); // 默认消息:操作失败
Handler::fail('用户创建失败');
Handler::fail('文件上传失败');
使用场景
1. 控制器中使用
<?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
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
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
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格式:
成功响应
{
"success": true,
"message": "success",
"code": 200,
"data": {
// 具体数据
}
}
异常响应
{
"success": false,
"message": "具体错误信息",
"code": 400,
"data": null
}
参数验证错误
{
"success": false,
"message": "参数错误",
"code": 422,
"data": null,
"errors": {
"username": ["用户名不能为空"],
"email": ["邮箱格式不正确"]
}
}
常用错误码
| 错误码 | 说明 | 使用场景 |
|---|---|---|
| 400 | 通用错误 | 默认业务错误 |
| 401 | 未授权 | 用户未登录 |
| 403 | 权限不足 | 没有操作权限 |
| 404 | 资源不存在 | 数据不存在 |
| 409 | 冲突 | 数据已存在 |
| 422 | 参数错误 | 验证失败 |
| 500 | 服务器错误 | 系统异常 |
测试异常处理
项目提供了测试接口来验证异常处理(仅开发环境可用):
# 测试参数验证异常
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. 错误信息编写
- 错误信息要简洁明确
- 面向用户,避免技术术语
- 提供解决建议(如果可能)
// ✅ 好的错误信息
Handler::error('用户名已存在,请尝试其他用户名');
Handler::throw('文件大小不能超过2MB', 413);
// ❌ 不好的错误信息
Handler::error('数据库约束冲突');
Handler::throw('系统错误');
3. 错误码规范
// 常用错误码
Handler::throw('数据不存在', 404);
Handler::throw('权限不足', 403);
Handler::throw('数据已存在', 409);
Handler::error('参数错误', 400);
4. 性能考虑
- 不要在循环中频繁抛出异常
- 异常只用于错误处理,不要用于控制程序流程
- 在抛出异常前先做基本检查
// ✅ 好的做法
if (empty($users)) {
return $this->Success([]); // 返回空数据
}
foreach ($users as $user) {
// 正常处理
}
// ❌ 不好的做法
foreach ($users as $user) {
if ($user->invalid) {
Handler::error('用户无效'); // 在循环中抛异常
}
}
注意事项
- 生产环境: 系统异常会隐藏详细信息,只返回"服务器错误"
- 开发环境: 系统异常会显示详细的调试信息
- 日志记录: 所有异常都会自动记录到日志文件
- 测试路由: 仅在开发环境可用,生产环境会自动禁用
迁移指南
如果您之前使用的是复杂的异常处理方式,可以按以下方式迁移:
// 之前的方式
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认证中间件
功能特点
- 统一的401错误响应:认证失败时返回JSON格式的401错误,而不是重定向
- 支持Sanctum认证:默认使用
sanctum守护器进行认证 - 扩展性强:可轻松扩展支持其他认证方式
响应格式
{
"success": false,
"message": "未授权访问,请先登录",
"code": 401,
"data": null
}
使用方式
在路由中使用admin.auth中间件:
Route::middleware('admin.auth')->group(function () {
// 需要认证的路由
});
扩展示例
如果需要支持更多认证方式,可以修改中间件:
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);
}