请求响应

XinAdmin 定义了统一的请求响应格式,前端 axios 与后端 Laravel 配合实现自动化的请求处理、错误提示和异常处理。

响应数据结构

标准响应格式

interface ResponseStructure<T = any> {
  success: boolean      // 请求是否成功
  msg: string          // 响应消息
  data?: T             // 响应数据
  errorCode?: number   // 错误码
  showType?: number    // 显示类型:0-成功消息,1-警告消息,2-错误消息,3-成功通知,4-警告通知,5-错误通知,99-静默
  status?: number      // HTTP 状态码
  description?: string // 详细描述
  placement?: string   // 通知位置:top/topLeft/topRight/bottom/bottomLeft/bottomRight
}

成功响应示例

{
  "success": true,
  "data": {
    "id": 1,
    "name": "管理员"
  },
  "msg": "ok",
  "showType": 0
}

失败响应示例

{
  "success": false,
  "data": [],
  "msg": "用户名或密码错误",
  "showType": 1
}

ShowType 显示类型

ShowType 用于控制前端提示框的显示方式,定义在 App\Enum\ShowType 中。

枚举值常量名说明前端组件
0SUCCESS_MESSAGE成功消息Message.success
1WARN_MESSAGE警告消息Message.warning
2ERROR_MESSAGE错误消息Message.error
3SUCCESS_NOTIFICATION成功通知Notification.success
4WARN_NOTIFICATION警告通知Notification.warning
5ERROR_NOTIFICATION错误通知Notification.error
99SILENT静默处理无提示

后端响应 Trait

控制器继承 BaseController 后即可使用 RequestJson Trait 提供的响应方法。

成功响应

// 返回带数据的成功响应
return $this->success(['id' => 1, 'name' => 'test']);

// 返回带消息的成功响应
return $this->success('操作成功');

// 返回空数据的成功响应
return $this->success();

失败响应

// 返回带消息的错误响应
return $this->error('操作失败');

// 返回带数据的错误响应
return $this->error(['field' => '用户名已存在'], '创建用户失败');

警告响应

// 返回警告响应
return $this->warn('数据已过期,请刷新页面');

通知响应

// 通知响应,支持自定义位置和类型
return $this->notification(
    '操作提醒',                    // 通知标题
    '您的会话即将过期',            // 通知描述
    ShowType::WARN_NOTIFICATION,  // 通知类型
    'topRight'                    // 通知位置
);

抛出响应

除了返回响应,还可以通过 throw 抛出响应来中断程序:

// 抛出成功响应并中断程序
$this->throwSuccess(['id' => 1], '创建成功');

// 抛出错误响应并中断程序
$this->throwError('权限不足');

// 抛出警告响应并中断程序
$this->throwWarn('数据未保存');

使用场景:在业务逻辑中间中断执行,无需手动 return

public function create(Request $request)
{
    // 验证失败直接抛出
    if (!$request->name) {
        $this->throwError('名称不能为空');
    }

    // 验证通过继续执行
    $this->service->create($request->all());
    return $this->success();
}

前端请求封装

前端通过 axios 封装统一处理请求响应。

请求配置

// 创建 axios 实例
const instance = axios.create({
  baseURL: import.meta.env.VITE_BASE_URL || '',
  timeout: 10000,
  responseType: 'json',
  withCredentials: false,
});

自动处理

前端自动处理以下内容:

  • 自动附加 Token:除登录接口外,自动在请求头添加 Authorization: Bearer {token}
  • 自动附加语言:自动附加 User-Language 到请求头
  • 请求去重:相同参数的可重复请求会自动取消
  • 错误提示:根据 showType 自动展示 Message 或 Notification
  • 401 处理:自动跳转登录页

HTTP 状态码处理

状态码说明
401未授权,清除 Token 并跳转登录页
400参数不正确
403无权限操作
404资源未找到
408请求超时
409数据冲突
500服务器内部错误
502网关错误
503服务不可用
504服务暂时无法访问

业务错误处理

success: false 时,根据 showType 处理:

switch (showType) {
  case 0:  // Message 成功提示
    if (msg) message.success(msg);
    break;
  case 1:  // Message 警告提示
    if (msg) message.warning(msg);
    break;
  case 2:  // Message 错误提示
    if (msg) message.error(msg);
    break;
  case 3:  // Notification 成功通知
    if (msg) notification.success({ message: msg });
    break;
  case 4:  // Notification 警告通知
    if (msg) notification.warning({ message: msg });
    break;
  case 5:  // Notification 错误通知
    if (msg) notification.error({ message: msg });
    break;
  case 99: // 静默,不显示任何提示
    break;
}

列表响应格式

分页数据

type ListResponse<T> = {
  data: T[]      // 数据列表
  page: number   // 当前页码
  total: number  // 总记录数
  per_page: number  // 每页条数
  current_page: number  // 当前页码
}

响应示例

{
  "success": true,
  "data": {
    "data": [
      { "id": 1, "name": "用户1" },
      { "id": 2, "name": "用户2" }
    ],
    "page": 1,
    "total": 100,
    "per_page": 10,
    "current_page": 1
  },
  "msg": "ok",
  "showType": 0
}

使用示例

后端控制器

<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\BaseController;
use App\Services\Admin\UserService;
use Illuminate\Http\JsonResponse;
use Xin\AnnoRoute\RequestAttribute;
use Xin\AnnoRoute\Route\GetRoute;
use Xin\AnnoRoute\Route\PostRoute;

#[RequestAttribute('/admin/user', 'admin.user')]
class UserController extends BaseController
{
    public function __construct(
        protected UserService $service
    ) {}

    #[GetRoute('/{id}')]
    public function show(int $id): JsonResponse
    {
        $user = $this->service->find($id);
        if (!$user) {
            return $this->error('用户不存在');
        }
        return $this->success($user);
    }

    #[PostRoute]
    public function store(Request $request): JsonResponse
    {
        $data = $request->validate([
            'name' => 'required|string|max:50',
            'email' => 'required|email|unique:users',
        ]);

        try {
            $user = $this->service->create($data);
            return $this->success($user, '创建用户成功');
        } catch (\Exception $e) {
            return $this->error($e->getMessage());
        }
    }

    #[PostRoute('/batch-import')]
    public function batchImport(Request $request): JsonResponse
    {
        $file = $request->file('file');
        if (!$file) {
            return $this->throwError('请上传文件');
        }

        // 大量数据导入,可能耗时较长
        $result = $this->service->import($file);

        if ($result['failed'] > 0) {
            // 有失败数据,提示警告
            return $this->warn($result, '导入完成,但有' . $result['failed'] . '条数据失败');
        }

        return $this->success($result, '导入成功');
    }
}

前端调用

import createAxios from '@/utils/request';

// 获取用户详情
async function getUser(id: number) {
  const response = await createAxios({
    url: `/admin/user/${id}`,
    method: 'get',
  });
  return response.data.data;
}

// 创建用户
async function createUser(data: UserData) {
  const response = await createAxios({
    url: '/admin/user',
    method: 'post',
    data,
  });
  return response.data.data;
}

// 批量导入
async function importUsers(file: File) {
  const formData = new FormData();
  formData.append('file', file);

  const response = await createAxios({
    url: '/admin/user/batch-import',
    method: 'post',
    data: formData,
    headers: { 'Content-Type': 'multipart/form-data' },
  });
  return response.data.data;
}

异常处理

全局异常处理

ExceptionsHandler 统一处理未捕获的异常:

// 验证异常
if ($exception instanceof ValidationException) {
    return $this->error($exception->errors(), '参数验证失败');
}

// 业务异常
if ($exception instanceof RepositoryException) {
    return $this->error($exception->getMessage());
}

// 认证异常
if ($exception instanceof AuthenticationException) {
    return $this->error([], '请先登录')->setStatusCode(401);
}

HttpResponseException

业务代码可通过抛出 HttpResponseException 直接返回响应:

use App\Exceptions\HttpResponseException;

public function update(Request $request, int $id)
{
    $user = User::find($id);
    if (!$user) {
        throw new HttpResponseException([
            'success' => false,
            'msg' => '用户不存在',
            'showType' => ShowType::ERROR_MESSAGE,
        ]);
    }

    // ...
}

最佳实践

1. 统一响应格式

在控制器中始终使用 $this->success()$this->error() 返回响应,保持格式一致。

2. 合理选择 showType

  • 普通操作反馈:使用默认的 Message
  • 重要操作需要用户确认:使用 Notification
  • 后台静默处理:使用 SILENT

3. 错误消息本地化

// 使用语言包
return $this->error(__('user.not_found'));

4. 前端统一错误处理

避免在业务代码中手动处理响应错误,依赖前端的统一拦截器处理。