# 6.7 文章管理 :::tip 概述 文章管理模块支持封面图上传、图集多图、分类关联。`ArticleService` 继承 `BaseTenantService` 实现租户隔离,`use HasFileProcessing` 启用文件字段自动处理,并配置了数据权限(按创建人过滤)。 ::: ## 模块特点 | 特性 | 说明 | |------|------| | 基类 | `BaseTenantService`(租户隔离 + 数据权限由基类提供) | | Trait 组合 | `HasCrudOperations, HasFileProcessing, HasQueryScopes, HasSerializeMaps` | | 文件字段 | `cover`(封面图,保存到 article 目录) | | 多文件字段 | `images`(图集,逗号分隔多 URL 逐个处理) | | 枚举映射 | `status` → article_status 字典 | | 数据权限 | `dataScopeUserField='create_user'`,按创建人过滤 | | 模糊搜索 | `title`、`author` | | 精确匹配 | `category_id`、`status` | | 关联钩子 | `afterDetail` 补充分类名称等关联数据 | ## API 接口 | 方法 | 路径 | 权限码 | 说明 | |------|------|--------|------| | GET | `/api/article/page` | sys:article:page | 分页列表 | | GET | `/api/article/list` | — | 全量列表 | | GET | `/api/article/detail/{id}` | sys:article:detail | 详情 | | POST | `/api/article/add` | sys:article:add | 新增 | | PUT | `/api/article/update` | sys:article:update | 修改 | | DELETE | `/api/article/delete/{id}` | sys:article:delete | 删除 | | DELETE | `/api/article/batchDelete` | sys:article:batchDelete | 批量删除 | ## Service 配置(来自项目实际代码) ```php class ArticleService extends BaseTenantService { // 文件字段处理由 HasFileProcessing 提供;HasDataScope 由基类提供 use HasCrudOperations, HasFileProcessing, HasQueryScopes, HasSerializeMaps; protected string $modelClass = Article::class; // 需要自动处理的文件上传字段 protected array $fileFields = ['cover']; // 多文件字段(逗号分隔的多个 URL,逐个迁移/补全) protected array $multiFileFields = ['images']; // 富文本字段:保存时迁移媒体并替换为 [IMG_URL] 占位,读取时替换回域名 protected array $contentFields = ['content']; // 文件保存的子目录 protected string $fileSaveDir = 'article'; // LIKE 模糊匹配字段 protected array $pageLikeFields = ['title', 'author']; // 精确匹配字段 protected array $pageEqFields = ['category_id', 'status']; // 默认排序(租户内按排序值) protected array $pageOrderBy = [ ['field' => 'tenant_id', 'type' => 'asc'], ['field' => 'sort', 'type' => 'asc'], ]; // 字典翻译 protected array $serializeMaps = [ 'status' => 'article_status', ]; // 数据权限:归属字段(按创建人过滤) protected string $dataScopeUserField = 'create_user'; // 详情后处理:补充分类名称等关联数据 protected function afterDetail(int $id, array &$data): void { // 补充关联显示字段... } } ``` ## Controller 代码 ```php // app/Http/Controllers/Api/ArticleController.php(形态示例) #[OperationLog('文章管理-查询分页', OperationLog::TYPE_QUERY)] #[Permission('sys:article:page', '文章分页')] public function page(): JsonResponse { return $this->_page($this->articleService); } #[OperationLog('文章管理-新增', OperationLog::TYPE_ADD, '新增文章:{title}')] #[Permission('sys:article:add', '添加文章')] public function add(ArticleRequest $request): JsonResponse { return $this->_add($this->articleService, $request->validated()); } #[OperationLog('文章管理-删除', OperationLog::TYPE_DELETE, '删除文章ID:{id}')] #[Permission('sys:article:delete', '删除文章')] public function delete(int $id): JsonResponse { return $this->_delete($this->articleService, $id); } ``` :::tip 数据权限 文章模块配置了 `dataScopeUserField = 'create_user'`(服务由 `BaseTenantService` 提供 `HasDataScope`)——数据范围解析为 3(仅本人)时,普通用户只能看到自己创建的文章;当前角色一律解析为 1(全部数据),见 [5.9 数据权限](/zh/core/data-scope.html)。 ::: :::warning 文件字段处理依赖 Trait `fileFields` / `multiFileFields` 声明后必须 `use HasFileProcessing`(本模块已启用),否则 `BaseService` 中的占位实现会直接跳过处理——封面图将停留在临时路径。详见 [4.7 横切机制](/zh/architecture/cross-cutting.html)。 ::: ## 文件处理流程 ```text 封面 cover(单文件字段): 传入临时 URL → processFileFieldsOnSave 迁移到 article/ 存相对路径 读取 → processFileFieldsOnRead 补全 FILE_DOMAIN_URL 域名 图集 images(多文件字段): 传入 "url1,url2" → 逐个迁移、去域名 → 逗号拼接入库 读取 → 逐个补全域名 → 逗号拼接返回 ```