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 方法必须同时补齐这两类属性,不能只写业务代码。