# 4.7 横切机制
:::tip 概述
横切机制是 `BaseService` / 模型 Trait 内置的自动处理能力,开发者通过属性配置启用,无需手动编写处理代码。项目共有 9 种横切机制,覆盖数据转换、文件处理、校验、权限、隔离等维度;参数格式校验则由 HTTP 入口的 FormRequest 承担。
:::
## 横切机制总览
| 机制 | 配置位置 | 配置属性 | 触发时机 |
|------|---------|---------|---------|
| 驼峰/下划线转换 | `config/api.php` | `camel_snake_convert` | 查询参数入、响应出 |
| 文件字段处理 | Service(需 `use HasFileProcessing`) | `fileFields` + `fileSaveDir` | add/update 写入,detail/page 读取 |
| 多文件字段处理 | Service(需 `use HasFileProcessing`) | `multiFileFields` + `fileSaveDir` | add/update 写入,detail/page 读取 |
| 富文本占位符 | Service(需 `use HasFileProcessing`) | `contentFields` | add/update 写入,detail/page 读取 |
| 配置值类型化处理 | Service(需 `use HasConfigValueProcessing`) | 记录的 `type` + `fileSaveDir` | add/update 写入,detail/webIndex 读取 |
| 字典显示名翻译 | Service(需 `use HasSerializeMaps`) | `serializeMaps` | detail/page 查询后 |
| 唯一性校验 | Service(需 `use HasUniqueCheck`) | `uniqueFields` | add/update 时 |
| 数据权限过滤 | Service(需 `use HasDataScope`) | `dataScopeUserField` / `dataScopeDeptField` | 查询时 |
| 租户隔离 | `BaseTenantService`(自动 use HasDataScope) | `tenantScopeField` | 查询时 + 新增时 |
| 审计字段 + 软删除 | 模型 Trait | 自动生效 | 所有 CRUD |
:::tip 参数格式校验在哪里?
不在上表——Laravel 将其前置到 HTTP 入口:控制器方法**类型注入 FormRequest** 即自动校验(见 [3.4 FormRequest 验证器](/zh/guide/validator.html))。Service 只做业务规则(唯一性、状态依赖)。
:::
## 1. 驼峰/下划线自动转换
前端使用 camelCase,数据库使用 snake_case,框架自动完成双向转换。
### 转换流程
```text
请求方向(前端 → 数据库):
前端传参:{ userName: "admin", createTime: "2026-01-01" }
│
▼ HasQueryScopes::convertQueryParams()
│ camel_to_snake() 逐键转换(排除分页/排序参数)
▼
数据库操作:WHERE user_name = 'admin' AND create_time = '2026-01-01'
表单入参(add/update 写入):
$request->validated() ← 格式校验,原样传递(camelCase 键名)
│
▼ HasCrudOperations::doAdd / doUpdate 入口
│ array_camel_to_snake() 统一转换
▼
入库数据:snake_case 键名(非表字段由 filterTableFields 过滤)
响应方向(数据库 → 前端):
数据库数据:{ user_name: "admin", create_time: "2026-01-01" }
│
▼ Result::success() / page()
│ convert_to_camel() / convert_list_to_camel()
▼
前端接收:{ userName: "admin", createTime: "2026-01-01" }
```
### 排除项(不参与请求方向转换)
以下参数保持 camelCase,不转为 snake_case(`config('api.convert_except_params')`):
- `pageNo`、`pageSize` — 分页参数
- `orderField`、`orderType` — 排序参数
### 配置开关
```php
// config/api.php
'camel_snake_convert' => env('API_CAMEL_SNAKE_CONVERT', true),
```
```ini
# .env
API_CAMEL_SNAKE_CONVERT=true # 开启(默认)
API_CAMEL_SNAKE_CONVERT=false # 关闭(请求保持驼峰,响应保持下划线)
```
:::tip 开发者无需手动处理
驼峰/下划线转换完全自动,开发者只需在前端用 camelCase,在数据库用 snake_case,框架自动处理双向转换。需要原样返回时用 `Result::success($data, $msg, true)`(`$raw=true`)。
:::
## 2. 文件字段自动处理
**配置方式**:Service 中 `use HasFileProcessing` 并设置 `fileFields` 和 `fileSaveDir`
```php
class ArticleService extends BaseTenantService
{
use HasFileProcessing, /* 其它 Trait */;
protected array $fileFields = ['cover', 'attachment'];
protected string $fileSaveDir = 'article';
}
```
### 写入时(add/update)
```text
前端传入:cover = "https://temp.example.com/tmp/abc123.jpg"
│
▼ processFileFieldsOnSave()
│ 1. 检测字段值是否为临时文件 URL
│ 2. 将临时文件迁移到正式目录(fileSaveDir)
│ 3. 去掉域名,只存储相对路径
▼
入库存储:cover = "article/20261002/abc123.jpg"
```
### 读取时(detail/pageList/allList)
```text
数据库读取:cover = "article/20261002/abc123.jpg"
│
▼ processFileFieldsOnRead()
│ 补全完整访问 URL(config/file.php 的 domain_url)
▼
前端接收:cover = "https://cdn.example.com/uploads/article/20261002/abc123.jpg"
```
### 多个单文件字段
```php
protected array $fileFields = ['cover', 'avatar', 'attachment'];
// 三个字段都会自动处理
```
:::warning 未 use Trait 时为空操作
`BaseService` 中对应方法是**占位实现**(直接 `return $data`)。未 `use HasFileProcessing` 时文件字段不会被处理——这是刻意设计(见 [4.6 核心基类体系](/zh/architecture/base-classes.html)「占位覆盖机制」)。
:::
## 3. 多文件字段自动处理
当一个字段存储多个文件 URL(逗号分隔)时,使用 `multiFileFields`:
**配置方式**:Service 中设置 `multiFileFields`(复用 `fileSaveDir`)
```php
protected array $multiFileFields = ['images', 'attachments'];
protected string $fileSaveDir = 'article';
```
**写入时(add/update)**
```text
前端传入:images = "http://example.com/temp/a.png,http://example.com/temp/b.png"
│
▼ processMultiFileFieldsOnSave()
│ 1. 按逗号拆分为数组
│ 2. 逐个迁移临时文件并去掉域名
│ 3. 重新用逗号拼接
▼
入库存储:images = "article/20261002/a.png,article/20261002/b.png"
```
**读取时(detail/pageList/allList)**
```text
数据库读取:images = "article/20261002/a.png,article/20261002/b.png"
│
▼ processMultiFileFieldsOnRead()
│ 按逗号拆分 → 逐个补全域名 → 重新拼接
▼
前端接收:images = "https://cdn.example.com/.../a.png,https://cdn.example.com/.../b.png"
```
## 4. 富文本占位符机制
**配置方式**:Service 中设置 `contentFields`
```php
protected array $contentFields = ['content'];
```
### 写入时
```text
前端传入:content = '
文章内容
'
│
▼ processContentFieldsOnSave()
│ 1. 解析 HTML 中的
/