CRUD 服务

XinAdmin 提供了 BaseService 抽象基类,封装了常用的 CRUD 操作、数据验证和查询构建功能,让开发者可以快速构建业务服务层。

基本用法

创建一个服务类继承 BaseService,并定义 $model 属性指向对应的 Eloquent 模型:

<?php

namespace App\Services;

use App\Models\User;

class UserService extends BaseService
{
    protected string $model = User::class;
}

继承后,服务类自动拥有以下方法:

方法参数返回值说明
findint $idarray根据 ID 查询单条记录
queryarray $paramsarray分页查询列表
createarray $databool创建新记录
updateint $id, array $databool更新记录
deleteint $idbool删除记录

搜索字段配置

$searchField - 精确搜索字段

定义支持搜索的字段及其查询操作符:

protected array $searchField = [
    'status' => '=',           // 精确匹配
    'age' => '>',              // 大于
    'score' => '>=',           // 大于等于
    'price' => '<',            // 小于
    'stock' => '<=',           // 小于等于
    'code' => '<>',            // 不等于
    'name' => 'like',          // 模糊搜索(前后匹配)
    'email' => 'afterLike',    // 后模糊搜索(前缀匹配)
    'phone' => 'beforeLike',   // 前模糊搜索(后缀匹配)
    'created_at' => 'date',           // 日期查询
    'updated_at' => 'betweenDate',    // 日期范围查询
];

操作符说明:

操作符SQL 示例说明
=WHERE field = value精确匹配
>WHERE field > value大于
>=WHERE field >= value大于等于
<WHERE field < value小于
<=WHERE field <= value小于等于
<>WHERE field <> value不等于
likeWHERE field LIKE '%value%'全模糊匹配
afterLikeWHERE field LIKE 'value%'后模糊(前缀匹配)
beforeLikeWHERE field LIKE '%value'前模糊(后缀匹配)
dateWHERE DATE(field) = 'Y-m-d'日期精确匹配
betweenDateWHERE DATE(field) BETWEEN start AND end日期范围查询

$quickSearchField - 快速搜索字段

定义支持关键词搜索的字段,用于全局模糊搜索:

protected array $quickSearchField = ['name', 'email', 'phone'];

前端传入 keywordSearch 参数时,会在所有快速搜索字段中进行模糊匹配:

// 生成的 SQL
WHERE (name LIKE '%keyword%' OR email LIKE '%keyword%' OR phone LIKE '%keyword%')

查询参数

query() 方法支持以下请求参数:

分页参数

参数类型默认值说明
pageSizeint10每页条数

filter - 筛选条件

用于多值筛选,支持 JSON 字符串或数组格式:

{
    "filter": {
        "status": [1, 2],
        "type": ["admin", "user"]
    }
}

生成的 SQL:

WHERE status IN (1, 2) AND type IN ('admin', 'user')

keywordSearch - 关键词搜索

$quickSearchField 定义的字段中进行全局模糊搜索:

{
    "keywordSearch": "张三"
}

sorter - 排序

支持单字段排序,值为 ascend(升序)或 descend(降序):

{
    "sorter": {
        "created_at": "descend"
    }
}

搜索字段参数

根据 $searchField 定义的字段传入对应参数:

{
    "status": 1,
    "name": "张",
    "created_at": "2024-01-15",
    "updated_at": ["2024-01-01", "2024-01-31"]
}

数据验证

定义验证规则

重写 rules() 方法定义验证规则:

protected function rules(): array
{
    return [
        'name' => 'required|string|max:255',
        'email' => 'required|email|unique:users,email',
        'password' => 'required|string|min:6|confirmed',
        'status' => 'integer|in:0,1',
    ];
}

定义验证消息

重写 messages() 方法定义自定义错误消息:

protected function messages(): array
{
    return [
        'name.required' => '用户名不能为空',
        'email.required' => '邮箱不能为空',
        'email.email' => '邮箱格式不正确',
        'email.unique' => '邮箱已被使用',
        'password.min' => '密码至少6个字符',
    ];
}

区分创建和更新验证

使用 isUpdate() 方法判断是否为更新请求:

protected function rules(): array
{
    $rules = [
        'name' => 'required|string|max:255',
        'status' => 'integer|in:0,1',
    ];

    if ($this->isUpdate()) {
        $rules['email'] = 'required|email|unique:users,email,' . request()->route('id');
    } else {
        $rules['email'] = 'required|email|unique:users,email';
        $rules['password'] = 'required|string|min:6';
    }

    return $rules;
}

完整示例

<?php

namespace App\Services;

use App\Models\User;
use App\Services\BaseService;

class UserService extends BaseService
{
    protected string $model = User::class;

    protected array $searchField = [
        'status' => '=',
        'role_id' => '=',
        'name' => 'like',
        'email' => 'like',
        'created_at' => 'betweenDate',
    ];

    protected array $quickSearchField = ['name', 'email', 'phone'];

    protected function rules(): array
    {
        $rules = [
            'name' => 'required|string|max:255',
            'email' => 'required|email',
            'phone' => 'nullable|string|max:20',
            'status' => 'integer|in:0,1',
            'role_id' => 'required|exists:roles,id',
        ];

        if ($this->isUpdate()) {
            $rules['email'] .= '|unique:users,email,' . request()->route('id');
        } else {
            $rules['email'] .= '|unique:users,email';
            $rules['password'] = 'required|string|min:6';
        }

        return $rules;
    }

    protected function messages(): array
    {
        return [
            'name.required' => '用户名不能为空',
            'email.required' => '邮箱不能为空',
            'email.email' => '邮箱格式不正确',
            'email.unique' => '邮箱已被使用',
            'password.required' => '密码不能为空',
            'password.min' => '密码至少6个字符',
            'role_id.required' => '请选择角色',
            'role_id.exists' => '角色不存在',
        ];
    }
}

树形数据

getTreeData() 方法用于构建树形结构数据:

public function tree(): array
{
    $list = $this->model::query()
        ->orderBy('sort', 'asc')
        ->get()
        ->toArray();

    return $this->getTreeData($list);
}

数据要求:

  • 数据必须包含 idparent_id 字段
  • 根节点的 parent_id 默认为 0
  • 生成的子节点存放在 children 字段中

异常处理

BaseService 使用 RepositoryException 抛出异常:

场景异常消息
查询记录不存在Model not found
验证失败Validation failed: {错误信息}
创建数据为空Validation failed: empty data

异常会被全局异常处理器捕获并返回统一的 JSON 响应。

扩展方法

可以在子类中扩展或覆盖父类方法:

class UserService extends BaseService
{
    public function query(array $params): array
    {
        $query = $this->model::query()->with(['role', 'department']);
        $result = $this->buildSearch($params, $query)
            ->paginate($params['pageSize'] ?? 10)
            ->toArray();

        return $result;
    }

    public function create(array $data): bool
    {
        $data['password'] = bcrypt($data['password']);
        return parent::create($data);
    }
}