通用查询系统使用手册(ThinkPHP / PHP 7.4)
适用于:
BaseModel::getList()+ 控制器层searchPage()
目标:单表 / 多表联查 / 自动分页 / 分组统计 / 统一输出 一套搞定
版本特性:兼容 PHP 7.4(无命名参数),自动从request()读取分页参数
目录
一、核心理念与总体结构
框架提供两层通用查询接口:
| 层级 | 方法 | 作用 |
|---|---|---|
| 模型层 | getList() | 通用数据查询(字段、联表、分组、关联、分页) |
| 控制器层 | searchPage() | 自动构造查询条件并分页输出 |
理念:
让所有查询接口实现“一行搞定”,
参数驱动、结果统一、0 重复 SQL。
二、BaseModel::getList
方法签名
public function getList(array $where = [], array $expand = []): arrayexpand 参数表
| 字段 | 说明 |
|---|---|
page / limit | 分页参数(自动从 request 获取) |
field | 查询字段,默认 * |
order | 排序,如 id desc |
group | 分组字段 |
with | 模型关联加载 |
hidden | 隐藏字段 |
join | 联表配置 |
alias | 主表别名(默认 t) |
where 条件写法
[
['status', '=', 1],
['nickname', 'like', '%王%'],
['create_time', 'between', [1700000000,1710000000]],
]支持操作符:
=, like, between, in, notnull, null, noInjoin 写法
✅ 推荐写法(结构化)
'join' => [
['tp_user u', 'u.id = t.uid', 'LEFT'],
['tp_order o', 'o.user_id = u.id', 'INNER'],
]⚠️ 不推荐写法(字符串)
'join' => ['tp_user u on u.id = t.uid']使用示例
1️⃣ 单表查询
$this->getList([['status', '=', 1]], ['order' => 'id desc']);2️⃣ 多表联查
$this->getList(
[['u.nickname', 'like', '%张%']],
[
'alias' => 'm',
'field' => 'm.*,u.nickname,u.phone',
'join' => [['tp_user u', 'u.id = m.uid', 'LEFT']],
'order' => 'm.id desc',
'page' => 1,
'limit' => 10
]
);3️⃣ 聚合统计
$this->getList(
[],
[
'alias' => 'm',
'field' => 'u.nickname, SUM(m.amount) AS total_amount',
'join' => [['tp_user u', 'u.id = m.uid', 'LEFT']],
'group' => 'm.uid',
'order' => 'total_amount desc',
'limit' => 20
]
);三、searchPage(控制器层)
方法签名
protected function searchPage(string $modelClass, array $directive = []): arraydirective 参数表
| 字段 | 说明 |
|---|---|
spec | 快捷搜索映射(自动取 request 参数) |
where | 手动附加条件 |
join | 联表配置 |
alias | 主表别名 |
field | 查询字段 |
with | 模型关联 |
hider | 隐藏字段 |
order | 排序 |
group | 分组字段 |
spec 快捷搜索说明
例:
'spec' => ['nickname' => 'like', 'phone' => '=', 'status' => '=']当请求:
?page=1&limit=10&nickname=王&status=1生成:
[
['nickname','like','%王%'],
['status','=','1']
]使用示例
1️⃣ 单表搜索
return $this->searchPage(UserModel::class, [
'spec' => ['nickname' => 'like', 'phone' => '='],
'where' => [['status', '=', 1]],
'order' => 'id desc'
]);2️⃣ 联表搜索
return $this->searchPage(UserMoneyRecordModel::class, [
'alias' => 'm',
'join' => [['tp_user u', 'u.id = m.uid', 'LEFT']],
'field' => 'm.*,u.nickname,u.phone',
'spec' => ['order_sn' => 'like', 'u.nickname' => 'like'],
'order' => 'm.id desc'
]);3️⃣ 分组统计
return $this->searchPage(UserMoneyRecordModel::class, [
'alias' => 'm',
'join' => [['tp_user u', 'u.id = m.uid', 'LEFT']],
'field' => 'u.nickname, SUM(m.amount) as total_amount',
'group' => 'm.uid',
'order' => 'total_amount desc'
]);四、统一返回格式
{
"total": 120,
"list": [ ... ]
}五、最佳实践与常见坑
✅ 0 值条件
if ($value !== '' && $value !== null)保留 0,排除空字符串/null。
✅ -1 表示“全部”
传 -1 时忽略该字段条件。
✅ 分页逻辑
page>0 && limit>0 → 分页
否则 → 全量。
✅ group + paginate
group 查询自动切换 simple 模式,避免 count 报错。
✅ join 别名
确保 ON 条件别名与 alias 一致(例:u.id=m.uid)。
六、联表模板(复制即用)
'join' => [
['tp_user u', 'u.id = t.uid', 'LEFT'],
['tp_order o', 'o.user_id = u.id', 'LEFT'],
]七、控制器模板(复制即用)
public function index()
{
$data = $this->searchPage(\app\model\UserMoneyRecordModel::class, [
'alias' => 'm',
'join' => [['tp_user u', 'u.id = m.uid', 'LEFT']],
'field' => 'm.*,u.nickname,u.phone',
'spec' => ['order_sn' => 'like', 'u.nickname' => 'like', 'status' => '='],
'order' => 'm.id desc'
]);
return $this->success($data);
}八、推荐 buildWhereConditions 逻辑
foreach ($conditions as $condition) {
if ($value === '' || $value === null) continue;
switch ($operator) {
case 'like':
$query->where($field, 'like', $value);
break;
case 'between':
$range = is_array($value) ? $value : explode(',', $value);
if (count($range) === 2)
$query->whereBetween($field, $range);
break;
case 'in':
is_array($value) && count($value)
? $query->whereIn($field, $value)
: $query->whereRaw('1=0');
break;
default:
$query->where($field, $operator, $value);
}
}九、术语速查
| 名称 | 说明 |
|---|---|
| spec | 快捷搜索配置 |
| where | 自定义条件 |
| join | 联表配置 [表名 别名, on 条件, 类型] |
| alias | 主表别名 |
| with | 关联模型 |
| hidden | 隐藏字段 |
| group | 分组字段 |
| 自动分页 | page、limit 同时存在即分页 |
✅ 一句话总结:
用
searchPage()说“查什么”,
用getList()控制“怎么查”。统一结果,低重复,结构优雅,就是这套框架的灵魂。
BaseModel的封装
/**
* 查询一条数据如果不存在则抛出异常
* @param $where //条件数组或主键id
* @param $msg //抛出的异常信息
* @param $lock //是否行锁
* @return array|mixed|Model
* @throws ApiException
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function FindTips($where, $msg, $lock = false)
{
$Model = self::getModel();
if (is_array($where)) {
$Model = $Model->where($where);
} else {
$Model = $Model->where($Model->getPk(), $where);
}
$Info = $Model->lock($lock)->find();
if (!$Info) throw new ApiException($msg);
return $Info;
}
/**
* 通用查询列表(Clean版)
* 支持:单表 / 联表 / 自动分页 / 分组 / 统一输出
*
* @param array $where 查询条件
* @param array $expand 附加配置:
* [
* 'page' => 1, // 页码(可选,自动从 request 获取)
* 'limit' => 20, // 每页条数(可选,自动从 request 获取)
* 'field' => 't.*,u.nickname', // 查询字段
* 'with' => [], // 关联模型
* 'hidden' => [], // 隐藏字段
* 'order' => 't.id desc', // 排序
* 'group' => '', // 分组字段
* 'join' => [['tp_user u','u.id=t.uid','LEFT']], // 链表
* 'alias' => 't', // 主表别名
* ]
* @return array ['total'=>x,'list'=>[]]
*/
public function getList(array $where = [], array $expand = []): array
{
// ====== 自动获取分页参数 ======
$page = isset($expand['page']) ? (int)$expand['page'] : (int)request()->get('page', 0);
$limit = isset($expand['limit']) ? (int)$expand['limit'] : (int)request()->get('limit', 0);
// ====== 提取其他参数 ======
$field = $expand['field'] ?? '*';
$with = $expand['with'] ?? [];
$hidden = $expand['hidden'] ?? [];
$order = $expand['order'] ?? '';
$group = $expand['group'] ?? '';
$join = $expand['join'] ?? [];
$alias = $expand['alias'] ?? null;
// ====== 初始化模型 ======
$model = $this->getModel();
$pk = $model->getPk();
if (empty($order)) $order = "{$pk} desc";
$query = $model;
// ====== 设置别名 ======
if ($alias || $join) {
$query = $query->alias($alias ?: 't');
}
// ====== 处理联表 ======
foreach ($join as $j) {
if (is_array($j) && count($j) >= 2) {
[$table, $on, $type] = array_pad(array_values($j), 3, 'LEFT');
$query->join($table, $on, strtoupper($type));
} elseif (is_string($j)) {
$query->join($j);
}
}
// ====== where 条件 ======
$query = $this->buildWhereConditions($where);
// ====== 字段 / 排序 / 分组 / 关联 ======
$query->field($field)
->with($with)
->hidden($hidden)
->orderRaw($order);
if (!empty($group)) {
$query->group($group);
}
// ====== 是否分页(自然逻辑) ======
$isGroup = !empty($group);
$paginate = ($page > 0 && $limit > 0);
// ====== 执行查询 ======
if ($paginate) {
$res = $query->paginate([
'list_rows' => max(1, $limit),
'page' => max(1, $page),
'simple' => $isGroup, // group 模式禁用 count
])->toArray();
return [
'total' => $res['total'] ?? 0,
'list' => $res['data'] ?? [],
];
} else {
$list = $query->select()->toArray();
return [
'total' => count($list),
'list' => $list,
];
}
}
/**
* 查询条件重构
*/
public function buildWhereConditions($conditions)
{
return $this->getModel()->where(function ($query) use ($conditions) {
if (count($conditions) <= 0) return $query;
foreach ($conditions as $condition) {
if (count($condition) == 3) {
[$field, $operator, $value] = $condition;
} else {
$operator = '=';
[$field, $value] = $condition;
}
if ($value !== '' && $value !== null) {
switch ($operator) {
case 'like':
$query->where($field, 'like', $value);
break;
case 'between':
$query->whereBetween($field, $value);
break;
case 'in':
$query->whereIn($field, $value);
break;
case '=':
$query->where($field, '=', $value);
break;
case 'notnull':
$query->whereNotNull($field);
break;
case 'null':
$query->whereNull($field);
break;
case 'noIn':
$query->wherenotIn($field, $value);
break;
// 不存在上面特殊声明的查询类型
// 默认使用传递的参数
default:
$query->where($field, $operator, $value);
break;
}
}
}
});
}ApiController的封装适用于后端BaseAdminConteroller
/**
* 通用分页搜索(加强版)
* 支持 spec 快捷搜索、自定义 where、关联模型、join、多表、分组、排序等
*
* @param string $modelClass 模型类名
* @param array $directive 查询指令参数
* [
* 'spec' => ['nickname' => 'like', 'phone' => '='], // 快捷搜索字段(自动从 request 取值)
* 'where' => [['status', '=', 1]], // 自定义条件
* 'join' => [['tp_user u', 'u.id = m.uid', 'LEFT']], // 链表查询
* 'alias' => 'm', // 主表别名
* 'field' => 'm.*,u.nickname,u.phone', // 字段筛选
* 'with' => ['relation'], // 模型关联
* 'hider' => ['password'], // 隐藏字段
* 'order' => 'm.id desc', // 排序
* 'group' => 'm.uid', // 分组
* ]
* @return array ['total'=>x,'list'=>[]]
*/
protected function searchPage(string $modelClass, array $directive = []): array
{
// ==== 初始化模型 ====
$model = new $modelClass;
// ==== 拆解指令参数 ====
$spec = $directive['spec'] ?? [];
$where = $directive['where'] ?? [];
$join = $directive['join'] ?? [];
$alias = $directive['alias'] ?? null;
$field = $directive['field'] ?? '*';
$with = $directive['with'] ?? [];
$hidden = $directive['hider'] ?? [];
$order = $directive['order'] ?? '';
$group = $directive['group'] ?? '';
// ==== 自动构造 where 条件 ====
$params = request()->param();
foreach ($spec as $key => $op) {
if (isset($params[$key]) && $params[$key] !== '') {
switch (strtolower($op)) {
case 'like':
$where[] = [$key, 'like', "%{$params[$key]}%"];
break;
case 'between':
$range = is_array($params[$key])
? $params[$key]
: explode(',', $params[$key]);
if (count($range) === 2) {
$where[] = [$key, 'between', $range];
}
break;
default:
$where[] = [$key, $op, $params[$key]];
break;
}
}
}
// ==== 统一构造 expand 参数 ====
$expand = [
'field' => $field,
'with' => $with,
'hidden' => $hidden,
'order' => $order,
'group' => $group,
'join' => $join,
'alias' => $alias,
];
// ==== 调用核心 getList ====
return $model->getList($where, $expand);
}