Files
rxthinkcmf-wiki/RXThinkCMF_Laravel13_NaiveVue/zh/guide/controller.md
T
2026-10-08 17:50:29 +08:00

10 KiB
Raw Blame History

3.6 控制器开发

:::tip 概述 控制器是 HTTP 请求的入口,负责参数获取、调用 Service、返回统一 JSON 响应。通过 #[Permission] 和 #[OperationLog] 注解实现权限校验和操作日志,配合基类快捷方法,单个接口通常只需几行代码。 :::

控制器文件位置

app/Http/Controllers/Api/ExampleController.php

完整代码(来自项目实际代码)

<?php

namespace App\Http\Controllers\Api;

use App\Attributes\OperationLog;
use App\Attributes\Permission;
use App\Http\Controllers\Controller;
use App\Http\Requests\ExampleRequest;
use App\Services\ExampleService;
use Illuminate\Http\JsonResponse;

/**
 * 示例管理控制器
 *
 * 负责示例相关的接口入口,包含分页、列表、详情、新增、修改、删除、批量删除,
 * 以及数据导入、导出。
 * 具体业务逻辑委托给 ExampleService,控制器仅做参数校验、
 * 权限与日志标注及响应封装。
 *
 * 说明:
 * - 所有接口通过 #[Permission] 注解声明权限标识与描述,由框架统一鉴权。
 * - 所有接口通过 #[OperationLog] 注解声明操作日志(类型、模板),由框架统一记录。
 * - 导入 / 导出接口的日志类型分别为 TYPE_IMPORT、TYPE_EXPORT,与常规 CRUD 区分。
 * - 常规 CRUD 复用基类的
 *   _page / _list / _detail / _add / _update / _delete / _batchDelete 方法。
 * - 导入 / 导出走 ExampleService 的 import / export 专用方法。
 */
class ExampleController extends Controller
{
    /**
     * 构造函数
     *
     * 注入示例业务服务,供各接口方法复用。
     *
     * @param  ExampleService  $exampleService  示例业务服务
     */
    public function __construct(protected ExampleService $exampleService) {}

    /**
     * 查询分页
     *
     * 对应权限:sys:example:page,操作日志类型:查询。
     * 返回分页结构,适合列表页展示。
     *
     * @return JsonResponse 分页数据
     */
    #[OperationLog('示例管理-查询分页', OperationLog::TYPE_QUERY)]
    #[Permission('sys:example:page', '示例分页')]
    public function page(): JsonResponse
    {
        return $this->_page($this->exampleService);
    }

    /**
     * 查询列表
     *
     * 对应权限:sys:example:list,操作日志类型:查询。
     * 返回不分页的完整列表,适合下拉选择等场景。
     *
     * @return JsonResponse 列表数据
     */
    #[OperationLog('示例管理-查询列表', OperationLog::TYPE_QUERY)]
    #[Permission('sys:example:list', '示例列表')]
    public function list(): JsonResponse
    {
        return $this->_list($this->exampleService);
    }

    /**
     * 查询详情
     *
     * 对应权限:sys:example:detail,操作日志类型:查询,
     * 日志模板中包含示例ID:{id}。
     *
     * @param  int  $id  示例ID
     * @return JsonResponse 示例详情
     */
    #[OperationLog('示例管理-查询详情', OperationLog::TYPE_QUERY, '查询示例ID:{id}')]
    #[Permission('sys:example:detail', '示例详情')]
    public function detail(int $id): JsonResponse
    {
        return $this->_detail($this->exampleService, $id);
    }

    /**
     * 新增
     *
     * 对应权限:sys:example:add,操作日志类型:新增,
     * 日志模板中包含示例名称:{name}。
     *
     * @param  ExampleRequest  $request  已通过校验的示例请求
     * @return JsonResponse 新增结果
     */
    #[OperationLog('示例管理-新增', OperationLog::TYPE_ADD, '新增示例:{name}')]
    #[Permission('sys:example:add', '添加示例')]
    public function add(ExampleRequest $request): JsonResponse
    {
        return $this->_add($this->exampleService, $request->validated());
    }

    /**
     * 修改
     *
     * 对应权限:sys:example:update,操作日志类型:修改。
     * 若请求数据中缺少 id,则直接返回失败响应。
     *
     * @param  ExampleRequest  $request  已通过校验的示例请求
     * @return JsonResponse 修改结果
     */
    #[OperationLog('示例管理-修改', OperationLog::TYPE_UPDATE, '修改示例ID:{id}')]
    #[Permission('sys:example:update', '修改示例')]
    public function update(ExampleRequest $request): JsonResponse
    {
        $data = $request->validated();
        $id = $data['id'] ?? 0;
        if (empty($id)) {
            return $this->fail('缺少id参数');
        }

        return $this->_update($this->exampleService, $id, $data);
    }

    /**
     * 删除
     *
     * 对应权限:sys:example:delete,操作日志类型:删除,
     * 日志模板中包含示例ID:{id}。
     *
     * @param  int  $id  示例ID
     * @return JsonResponse 删除结果
     */
    #[OperationLog('示例管理-删除', OperationLog::TYPE_DELETE, '删除示例ID:{id}')]
    #[Permission('sys:example:delete', '删除示例')]
    public function delete(int $id): JsonResponse
    {
        return $this->_delete($this->exampleService, $id);
    }

    /**
     * 批量删除
     *
     * 对应权限:sys:example:batchDelete,操作日志类型:删除。
     * 从 JSON 请求体中读取 ids 数组,未传时按空数组处理。
     *
     * @return JsonResponse 批量删除结果
     */
    #[OperationLog('示例管理-批量删除', OperationLog::TYPE_DELETE)]
    #[Permission('sys:example:batchDelete', '批量删除')]
    public function batchDelete(): JsonResponse
    {
        $ids = $this->getJsonBody()['ids'] ?? [];

        return $this->_batchDelete($this->exampleService, $ids);
    }

    /**
     * 导出数据
     *
     * 对应权限:sys:example:export,操作日志类型:导出(TYPE_EXPORT)。
     * 由基类 _export() 统一处理文件生成与响应封装。
     *
     * @return JsonResponse 导出结果(data 为文件地址)
     */
    #[OperationLog('示例管理-导出数据', OperationLog::TYPE_EXPORT)]
    #[Permission('sys:example:export', '导出示例')]
    public function export(): JsonResponse
    {
        return $this->_export($this->exampleService);
    }

    /**
     * 导入数据
     *
     * 对应权限:sys:example:import,操作日志类型:导入(TYPE_IMPORT)。
     * 由基类 _import() 统一处理文件校验(格式/大小/MIME)与响应封装。
     *
     * @return JsonResponse 导入结果
     */
    #[OperationLog('示例管理-导入数据', OperationLog::TYPE_IMPORT, '通过Excel导入示例数据')]
    #[Permission('sys:example:import', '导入示例')]
    public function import(): JsonResponse
    {
        return $this->_import($this->exampleService);
    }
}

:::tip 控制器模式 每个控制器遵循固定模式:

  1. 构造函数注入 Service(Laravel 依赖注入,protected 简写属性即成员属性)
  2. 每个方法标注 #[OperationLog] 和 #[Permission] 注解
  3. 简单调用 $this->_page() / _detail() / _add() 等快捷方法
  4. 入参由 FormRequest 类型注入完成格式校验(见 3.4 FormRequest 验证器) :::

Controller 基类快捷方法

app/Http/Controllers/Controller.php 提供的通用方法:

方法 用途 内部调用 失败场景
_page($service) 分页查询 Service::pageList() -
_list($service) 全量列表 Service::allList() -
_treeList($service) 树形列表 Service::treeList() -
_detail($service, $id) 详情查询 Service::detail($id) 空数据返回 code=404
_add($service, $data) 新增 Service::add($data) 捕获 BizException → fail(消息, code)
_update($service, $id, $data) 修改 Service::update($id, $data) id 缺失提示;捕获 BizException
_delete($service, $id) 删除 Service::delete($id) 捕获 BizException;其他异常记日志后返回通用提示
_batchDelete($service, $ids) 批量删除 Service::batchDelete($ids) ids 为空提示;同上
_export($service) 导出 Service::export() 捕获 BizException(如"无数据可导出")
_import($service) 导入 Service::import() 文件格式/大小/MIME 校验 + 通用提示

响应封装方法:$this->success($data, $msg) / $this->fail($msg, $code) / $this->paginate($result)(委托给 Support\Result,见 5.16 统一响应与异常抛出)。

参数获取方法:getPageParams()(pageNo/pageSize,限 1~100)、getJsonBody()(JSON 请求体)、getParams()(query + body 合并)。

注解说明

#[OperationLog] 操作日志注解

#[OperationLog('示例管理-新增', OperationLog::TYPE_ADD, '新增示例:{name}')]
//                │                      │                  │
//                │                      │                  └── 描述模板(支持 {param} 占位符,替换为请求参数值)
//                │                      └── 操作类型常量(TYPE_QUERY/ADD/UPDATE/DELETE/IMPORT/EXPORT 等 30 种)
//                └── 操作标题
  • 由 log.operation 中间件采集,响应后(terminable 阶段)落库;
  • 写操作(POST/PUT/DELETE)始终记录,读操作按注解类型选择性记录。

#[Permission] 权限注解

#[Permission('sys:example:add', '添加示例')]
//               │                    │
//               │                    └── 权限名称(菜单管理中显示)
//               └── 权限编码(菜单表 permission 字段)
  • 由 auth.jwt 中间件反射校验:解析注解 → 比对当前用户权限列表 → 无权限返回 code=403;
  • 超级管理员(用户 ID=1)直接放行;
  • 未标注 #[Permission] 的认证接口只需登录即可访问。

与手动构造响应的对比

// ✅ 推荐:快捷方法(异常处理、日志、404 兜底全部内置)
public function page(): JsonResponse
{
    return $this->_page($this->exampleService);
}

// 特殊场景:手动组织响应
public function stats(): JsonResponse
{
    $data = $this->exampleService->stats();
    if (! $data) {
        return $this->fail('暂无统计数据', 404);
    }

    return $this->success($data);
}