Skip to content

Controller 标准示例 — Article

源文件:server/app/adminapi/controller/v1/article/ArticleController.php(生成代码必须模仿本示例的结构与风格)

本示例为手写风格范本,用于学习结构与分层;经 make:crud 生成的骨架方法名以骨架为准,做业务增量时不要为对齐本示例而改名。

完整代码

php
<?php
/* ============================================================
 * 项目:元点Admin
 * 官网:https://www.dev007.cn
 * Slogan:提供高质量行业系统源码,帮助中小企业快速搭建专属应用
 * Author:mashanglai Team
 * ============================================================ */
declare(strict_types=1);

namespace app\adminapi\controller\v1\article;

use core\base\Controller;
use core\attribute\Permission;
use app\service\article\ArticleService;
use app\adminapi\validate\v1\article\ArticleValidate;
use think\Response;
use OpenApi\Attributes as OA;

#[OA\Tag(name: '文章管理', description: '文章的增删改查、状态管理')]
class ArticleController extends Controller
{
    protected ArticleService $articleService;

    /**
     * 文章列表
     */
    #[Permission('article.list')]
    #[OA\Get(
        path: '/article/list',
        summary: '获取文章列表',
        security: [['bearerAuth' => []]],
        tags: ['文章管理'],
        parameters: [
            new OA\Parameter(name: 'page_no', in: 'query', description: '页码', schema: new OA\Schema(type: 'integer', default: 1)),
            new OA\Parameter(name: 'page_size', in: 'query', description: '每页数量', schema: new OA\Schema(type: 'integer', default: 20)),
            new OA\Parameter(name: 'keyword', in: 'query', description: '关键词搜索', schema: new OA\Schema(type: 'string')),
            new OA\Parameter(name: 'category_id', in: 'query', description: '分类ID', schema: new OA\Schema(type: 'integer')),
            new OA\Parameter(name: 'status', in: 'query', description: '状态(0禁用 1启用)', schema: new OA\Schema(type: 'integer', enum: [0, 1])),
        ],
        responses: [
            new OA\Response(response: 200, description: '获取成功', content: new OA\JsonContent(ref: '#/components/schemas/PaginatedResponse'))
        ]
    )]
    public function list(): Response
    {
        $params = $this->getRequestData([
            'page_no'     => 1,
            'page_size'   => 20,
            'keyword'     => '',
            'category_id' => '',
            'status'      => '',
        ]);
        $result = $this->articleService->getArticleList($params);
        return $this->paginate($result);
    }

    /**
     * 文章详情
     */
    #[Permission('article.list')]
    #[OA\Get(
        path: '/article/detail/{id}',
        summary: '获取文章详情',
        security: [['bearerAuth' => []]],
        tags: ['文章管理'],
        parameters: [
            new OA\Parameter(name: 'id', in: 'path', required: true, description: '文章ID', schema: new OA\Schema(type: 'integer'))
        ],
        responses: [
            new OA\Response(response: 200, description: '获取成功', content: new OA\JsonContent(ref: '#/components/schemas/SuccessResponse'))
        ]
    )]
    public function detail(): Response
    {
        $id = (int) $this->request->param('id');
        $result = $this->articleService->getArticleDetail($id);
        return $this->success(lang('messages.get_success'), $result);
    }

    /**
     * 创建文章
     */
    #[Permission('article.create')]
    #[OA\Post(
        path: '/article',
        summary: '创建文章',
        security: [['bearerAuth' => []]],
        tags: ['文章管理'],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(
                required: ['title', 'category_id', 'content'],
                properties: [
                    new OA\Property(property: 'title', type: 'string', description: '文章标题'),
                    new OA\Property(property: 'category_id', type: 'integer', description: '分类ID'),
                    new OA\Property(property: 'cover', type: 'string', description: '封面图片'),
                    new OA\Property(property: 'summary', type: 'string', description: '摘要'),
                    new OA\Property(property: 'content', type: 'string', description: '文章内容'),
                    new OA\Property(property: 'tags', type: 'string', description: '标签'),
                    new OA\Property(property: 'author', type: 'string', description: '作者'),
                    new OA\Property(property: 'status', type: 'integer', description: '状态(0禁用 1启用)', enum: [0, 1]),
                    new OA\Property(property: 'publish_at', type: 'string', description: '发布时间'),
                ]
            )
        ),
        responses: [
            new OA\Response(response: 200, description: '创建成功', content: new OA\JsonContent(ref: '#/components/schemas/SuccessResponse')),
            new OA\Response(response: 400, description: '验证失败', content: new OA\JsonContent(ref: '#/components/schemas/ErrorResponse'))
        ]
    )]
    public function create(): Response
    {
        $data = $this->request->only([
            'title', 'category_id', 'cover', 'summary', 'content',
            'tags', 'author', 'status', 'publish_at',
        ]);
        $this->validate($data, ArticleValidate::class, [], false, 'create');
        $data['admin_id'] = $this->getUserId();
        $result = $this->articleService->createArticle($data);
        return $this->success(lang('messages.create_success'), $result);
    }

    /**
     * 更新文章
     */
    #[Permission('article.update')]
    #[OA\Put(
        path: '/article/{id}',
        summary: '更新文章',
        security: [['bearerAuth' => []]],
        tags: ['文章管理'],
        parameters: [
            new OA\Parameter(name: 'id', in: 'path', required: true, description: '文章ID', schema: new OA\Schema(type: 'integer'))
        ],
        requestBody: new OA\RequestBody(
            content: new OA\JsonContent(
                properties: [
                    new OA\Property(property: 'title', type: 'string', description: '文章标题'),
                    new OA\Property(property: 'category_id', type: 'integer', description: '分类ID'),
                    new OA\Property(property: 'cover', type: 'string', description: '封面图片'),
                    new OA\Property(property: 'summary', type: 'string', description: '摘要'),
                    new OA\Property(property: 'content', type: 'string', description: '文章内容'),
                    new OA\Property(property: 'tags', type: 'string', description: '标签'),
                    new OA\Property(property: 'author', type: 'string', description: '作者'),
                    new OA\Property(property: 'status', type: 'integer', description: '状态(0禁用 1启用)', enum: [0, 1]),
                    new OA\Property(property: 'publish_at', type: 'string', description: '发布时间'),
                ]
            )
        ),
        responses: [
            new OA\Response(response: 200, description: '更新成功', content: new OA\JsonContent(ref: '#/components/schemas/SuccessResponse'))
        ]
    )]
    public function update(): Response
    {
        $id = (int) $this->request->param('id');
        $data = $this->request->only([
            'title', 'category_id', 'cover', 'summary', 'content',
            'tags', 'author', 'status', 'publish_at',
        ]);
        $this->validate($data, ArticleValidate::class, [], false, 'update');
        $this->articleService->updateArticle($id, $data);
        return $this->success(lang('messages.update_success'));
    }

    /**
     * 删除文章
     */
    #[Permission('article.delete')]
    #[OA\Delete(
        path: '/article/{id}',
        summary: '删除文章',
        security: [['bearerAuth' => []]],
        tags: ['文章管理'],
        parameters: [
            new OA\Parameter(name: 'id', in: 'path', required: true, description: '文章ID', schema: new OA\Schema(type: 'integer'))
        ],
        responses: [
            new OA\Response(response: 200, description: '删除成功', content: new OA\JsonContent(ref: '#/components/schemas/SuccessResponse'))
        ]
    )]
    public function delete(): Response
    {
        $id = (int) $this->request->param('id');
        $this->articleService->deleteArticle($id);
        return $this->success(lang('messages.delete_success'));
    }

    /**
     * 更新文章状态
     */
    #[Permission('article.status')]
    #[OA\Put(
        path: '/article/{id}/status',
        summary: '更新文章状态',
        security: [['bearerAuth' => []]],
        tags: ['文章管理'],
        parameters: [
            new OA\Parameter(name: 'id', in: 'path', required: true, description: '文章ID', schema: new OA\Schema(type: 'integer'))
        ],
        requestBody: new OA\RequestBody(
            required: true,
            content: new OA\JsonContent(properties: [
                new OA\Property(property: 'status', type: 'integer', description: '状态(0禁用 1启用)', enum: [0, 1]),
            ])
        ),
        responses: [
            new OA\Response(response: 200, description: '状态更新成功', content: new OA\JsonContent(ref: '#/components/schemas/SuccessResponse'))
        ]
    )]
    public function updateStatus(): Response
    {
        $id = (int) $this->request->param('id');
        $status = (int) $this->request->post('status');
        $this->articleService->updateStatus($id, $status);
        return $this->success(lang('messages.operation_success'));
    }
}

要点注解

  • 第 22 行 protected ArticleService $articleService; 是唯一的依赖属性声明,由 core\base\Controller 基类自动 DI 注入,禁止在 Controller 中 new ArticleService() 或手动 app() 解析。
  • Controller 全程只做三件事:取参数($this->request->only() / $this->getRequestData() / $this->request->param())、调用 validate()、调用 Service 方法并包装响应;禁止出现 Db::、Model 静态查询(Article::where() 等)或任何业务逻辑判断(如状态流转、时间计算)。
  • 第 117、161 行 $this->validate($data, ArticleValidate::class, [], false, 'create'/'update'):第一个参数是已经取好的数据数组 $data,不是验证类本身;第五个参数是场景名(scene),与 Validate 类中定义的 scene 对应。
  • 第 46-52 行 list()$this->getRequestData([...]) 一次性取出所有查询参数并附带默认值,避免逐个 $this->request->get('xxx') 手写默认值判断。
  • 第 53-54 行分页列表统一调用 $this->success() 之外的 $this->paginate($result) 封装,返回 PaginatedResponse 结构,而非普通 success()
  • 第 118 行 $data['admin_id'] = $this->getUserId();:需要写入当前登录管理员 ID 时,在 Controller 层从上下文取值并合并进 $data,再整体传给 Service,不在 Service 内部反查登录态。
  • 每个方法均返回 think\Response,成功统一用 $this->success(消息, 数据),消息文案统一走 lang('messages.xxx_success') 多语言键,禁止硬编码中文提示字符串。
  • 每个对外方法头部都有 #[Permission('article.xxx')] 权限属性 + #[OA\...] OpenAPI 文档属性,新增 Controller 方法必须同时补齐这两类属性,不能只写业务代码。

基于 MIT 许可发布