一个完整、详细的增强控制器,包含完整的软删除、真删除功能,以及各种查询方法的详细实现和注释。
完整的增强控制器,它继承自ThinkPHP8的基础控制器,并集成以下功能:
安全防护(XSS过滤、SQL注入防护、字段白名单)
软删除和强制删除(与ThinkPHP8模型软删除兼容)
快速CRUD方法(简单查询、复杂链式查询、批量操作)
完善的错误处理和日志记录
注意:由于ThinkPHP8的基础控制器可能已经提供了一些方法,我们尽量不冲突,并扩展新功能。
由于代码较长,我将分几个部分编写:
第一部分:控制器头部、属性和基础方法(如安全参数获取、查询实例构建等)
第二部分:快速CRUD方法(增删改查)
第三部分:软删除相关方法(回收站操作)
第四部分:批量操作方法
第五部分:复杂查询示例和辅助方法
1. 完整的增强基础控制器 (ExtendedController.php)
php
<?php
declare(strict_types=1);
namespace app\controller;
use think\App;
use think\Response;
use think\facade\Request;
use think\facade\Db;
use think\facade\Log;
use think\db\Query;
use think\exception\ValidateException;
use think\exception\DbException;
/**
* 增强版基础控制器 - ExtendedController
*
* 继承ThinkPHP8的BaseController,提供更安全、更高效的数据库操作方法
* 包含完整的软删除、真删除支持,以及丰富的查询方法
*
* 主要功能特性:
* 1. 完全兼容ThinkPHP8基础控制器
* 2. 自动XSS防护和SQL注入防护
* 3. 完整的软删除支持(与ThinkPHP8模型完全兼容)
* 4. 丰富的查询方法(简单查询、复杂链式查询、批量操作)
* 5. 完善的错误处理和日志记录
* 6. 字段白名单和自动验证
* 7. 支持事务操作和批量处理
*
* @package app\controller
*/
class ExtendedController extends BaseController
{
/**
* 允许操作的字段白名单
* 设置后只允许操作这些字段,防止恶意字段提交
* 如果为空数组,则不进行字段过滤
*
* @var array
*
* 示例:
* protected array $allowFields = ['username', 'email', 'nickname', 'status'];
*/
protected array $allowFields = [];
/**
* 允许搜索的字段
* 用于关键词搜索时,限制只能在指定字段中进行搜索
* 防止搜索敏感字段或全表扫描
*
* @var array
*
* 示例:
* protected array $searchFields = ['username', 'email', 'nickname'];
*/
protected array $searchFields = [];
/**
* 最大分页数量限制
* 防止过大的分页请求导致性能问题
*
* @var int
*/
protected int $maxLimit = 100;
/**
* 默认分页数量
* 当请求未指定limit参数时使用的默认值
*
* @var int
*/
protected int $defaultLimit = 15;
/**
* 软删除配置
* 配置软删除的字段名和字段类型
* 如果为空数组,则不启用软删除功能
*
* @var array
*
* 示例:
* protected array $softDeleteConfig = [
* 'field' => 'delete_time', // 软删除字段名
* 'type' => 'timestamp' // 字段类型:timestamp/int
* ];
*/
protected array $softDeleteConfig = [
'field' => 'delete_time',
'type' => 'timestamp'
];
/**
* 请求频率限制配置
* 用于防止恶意请求和API滥用
*
* @var array
*/
protected array $rateLimitConfig = [
'max_requests' => 60, // 最大请求次数
'period' => 60, // 时间周期(秒)
'prefix' => 'rate_limit:' // 缓存前缀
];
/**
* 控制器初始化方法
* 子类可以重写此方法进行自定义初始化
*
* @return void
*/
protected function initialize(): void
{
parent::initialize();
// 子类可以在这里进行自定义初始化配置
}
// ==================== 安全基础方法 ====================
/**
* 安全获取请求参数
* 对获取的参数进行XSS过滤和类型转换,防止安全漏洞
*
* @param string $key 参数名
* @param mixed $default 默认值
* @param string $type 参数类型(string|int|float|bool|array)
* @return mixed 过滤后的安全值
*
* 示例:
* $id = $this->safeParam('id', 0, 'int');
* $name = $this->safeParam('name', '', 'string');
* $ids = $this->safeParam('ids', [], 'array');
*/
protected function safeParam(string $key, $default = null, string $type = 'string')
{
try {
// 从请求中获取原始参数值
$value = Request::param($key, $default);
// 对值进行安全过滤和类型转换
return $this->filterValue($value, $type);
} catch (\Exception $e) {
// 参数获取失败时返回默认值,并记录日志
Log::error("参数获取失败 [{$key}]: " . $e->getMessage());
return $default;
}
}
/**
* 值过滤处理(XSS防护 + 类型转换)
* 对输入数据进行安全处理,防止XSS攻击和类型混淆漏洞
*
* @param mixed $value 原始值
* @param string $type 目标类型
* @return mixed 过滤后的安全值
*/
protected function filterValue($value, string $type)
{
// 如果是数组,递归处理每个元素
if (is_array($value)) {
return array_map(function($item) use ($type) {
return $this->filterSingleValue($item, $type);
}, $value);
}
// 单值处理
return $this->filterSingleValue($value, $type);
}
/**
* 单值安全处理
* 对单个值进行XSS防护和类型转换
*
* @param mixed $value 原始值
* @param string $type 目标类型
* @return mixed 处理后的安全值
*/
protected function filterSingleValue($value, string $type)
{
// 空值直接返回
if ($value === null || $value === '') {
return $value;
}
// 字符串类型进行XSS防护
if (is_string($value)) {
// 移除HTML和PHP标签
$value = strip_tags($value);
// 转义特殊字符,防止XSS攻击
$value = htmlspecialchars($value, ENT_QUOTES | ENT_HTML5, 'UTF-8');
// 移除不可见字符
$value = trim($value);
}
// 根据目标类型进行安全转换
switch ($type) {
case 'int':
return is_numeric($value) ? (int)$value : 0;
case 'float':
return is_numeric($value) ? (float)$value : 0.0;
case 'bool':
return (bool)$value;
case 'array':
// 确保返回数组类型,如果是字符串尝试JSON解析
if (is_string($value)) {
$decoded = json_decode($value, true);
return json_last_error() === JSON_ERROR_NONE ? $decoded : [$value];
}
return is_array($value) ? $value : [$value];
case 'string':
default:
return (string)$value;
}
}
/**
* 字段白名单过滤
* 只允许操作白名单中的字段,防止恶意字段提交
*
* @param array $data 原始数据
* @return array 过滤后的数据
*/
protected function filterFields(array $data): array
{
// 如果没有设置白名单,返回所有数据
if (empty($this->allowFields)) {
return $data;
}
// 只保留白名单中的字段
return array_intersect_key($data, array_flip($this->allowFields));
}
/**
* 构建安全的WHERE查询条件
* 自动处理常见查询条件,防止SQL注入
*
* @param array $customWhere 自定义查询条件
* @return array 合并后的安全查询条件
*
* 示例:
* $where = $this->buildWhere([['category_id', '=', 1]]);
*/
protected function buildWhere(array $customWhere = []): array
{
$where = [];
// 自动处理状态参数(如果存在)
$status = $this->safeParam('status');
if ($status !== '' && $status !== null) {
$where[] = ['status', '=', (int)$status];
}
// 关键词搜索(如果设置了搜索字段)
$keyword = $this->safeParam('keyword');
if ($keyword && !empty($this->searchFields)) {
// 构建多字段模糊搜索条件
$searchCondition = [];
foreach ($this->searchFields as $field) {
// 验证字段名安全性(只允许字母数字和下划线)
if (preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $field)) {
$searchCondition[] = $field;
}
}
if (!empty($searchCondition)) {
$where[] = [implode('|', $searchCondition), 'like', "%{$keyword}%"];
}
}
// 时间范围搜索(如果存在时间参数)
$startTime = $this->safeParam('start_time');
$endTime = $this->safeParam('end_time');
if ($startTime) {
$timestamp = is_numeric($startTime) ? (int)$startTime : strtotime($startTime);
if ($timestamp !== false) {
$where[] = ['create_time', '>=', $timestamp];
}
}
if ($endTime) {
$timestamp = is_numeric($endTime) ? (int)$endTime : strtotime($endTime);
if ($timestamp !== false) {
$where[] = ['create_time', '<=', $timestamp];
}
}
// ID范围搜索
$minId = $this->safeParam('min_id', 0, 'int');
$maxId = $this->safeParam('max_id', 0, 'int');
if ($minId > 0) {
$where[] = ['id', '>=', $minId];
}
if ($maxId > 0) {
$where[] = ['id', '<=', $maxId];
}
// 合并自定义条件(自定义条件优先级更高)
if (!empty($customWhere)) {
$where = array_merge($where, $customWhere);
}
return $where;
}
// ==================== 数据库操作核心方法 ====================
/**
* 获取安全的数据查询实例
* 自动应用软删除条件、字段白名单等安全设置
*
* @param string|null $table 表名,为空时自动根据控制器名推导
* @param bool $withTrashed 是否包含软删除数据
* @return Query 返回ThinkPHP的查询对象,可链式调用
*
* 示例:
* // 默认查询(不包含软删除数据)
* $query = $this->safeDb('user')->where('status', 1);
*
* // 包含软删除数据的查询
* $query = $this->safeDb('user', true)->where('status', 1);
*/
protected function safeDb(string $table = null, bool $withTrashed = false): Query
{
try {
// 获取表名,如果未指定则自动推导
$tableName = $table ?: $this->getDefaultTable();
// 创建查询实例
$query = Db::name($tableName);
// 自动应用软删除条件(如果启用软删除且不要求包含软删除数据)
if (!$withTrashed && !empty($this->softDeleteConfig['field'])) {
$deleteField = $this->softDeleteConfig['field'];
$query->where($deleteField, null);
}
// 自动字段过滤(如果设置了白名单)
if (!empty($this->allowFields)) {
$query->field($this->allowFields);
}
return $query;
} catch (DbException $e) {
// 数据库异常处理
Log::error("数据库查询实例创建失败: " . $e->getMessage());
throw $e;
}
}
/**
* 获取默认表名(根据控制器名自动推导)
* 自动将控制器名转换为对应的表名
*
* @return string 返回推导的表名
*
* 示例:
* UserController → user
* ProductCategoryController → product_category
*/
protected function getDefaultTable(): string
{
// 获取控制器类名(不含命名空间)
$className = class_basename(static::class);
// 移除Controller后缀
$tableName = str_replace('Controller', '', $className);
// 驼峰转下划线(小写)
return strtolower(preg_replace('/(?<!^)[A-Z]/', '_$0', $tableName));
}
/**
* 包含软删除数据的查询实例
* 查询时包含已被软删除的数据
*
* @param string|null $table 表名
* @return Query
*
* 示例:
* $query = $this->withTrashed('user')->where('status', 1);
*/
protected function withTrashed(string $table = null): Query
{
return $this->safeDb($table, true);
}
/**
* 仅查询软删除数据的查询实例
* 只查询已被软删除的数据
*
* @param string|null $table 表名
* @return Query
*
* 示例:
* $query = $this->onlyTrashed('user')->where('status', 1);
*/
protected function onlyTrashed(string $table = null): Query
{
$tableName = $table ?: $this->getDefaultTable();
$query = Db::name($tableName);
// 只查询软删除的数据
if (!empty($this->softDeleteConfig['field'])) {
$deleteField = $this->softDeleteConfig['field'];
$query->where($deleteField, 'not null');
}
// 字段白名单过滤
if (!empty($this->allowFields)) {
$query->field($this->allowFields);
}
return $query;
}
/**
* 安全分页处理
* 对分页参数进行安全验证,防止过大分页导致性能问题
*
* @param Query $query 查询对象
* @param array $options 分页选项
* @return array 分页结果
*/
protected function safePaginate(Query $query, array $options = []): array
{
try {
// 安全获取分页参数
$page = max(1, $this->safeParam('page', 1, 'int'));
$limit = min($this->maxLimit, max(1, $this->safeParam('limit', $this->defaultLimit, 'int')));
// 执行分页查询
$paginator = $query->paginate([
'page' => $page,
'list_rows' => $limit,
] + $options);
// 返回标准化分页数据
return [
'list' => $paginator->items(), // 当前页数据列表
'total' => $paginator->total(), // 总记录数
'page' => $paginator->currentPage(), // 当前页码
'limit' => $paginator->listRows(), // 每页数量
'pages' => $paginator->lastPage(), // 总页数
];
} catch (DbException $e) {
Log::error("分页查询失败: " . $e->getMessage());
throw $e;
}
}
// ==================== 快速CRUD方法 ====================
/**
* 快速分页列表查询
* 提供简单快捷的分页列表查询功能,自动处理搜索、排序、分页
*
* @param string|null $table 表名
* @param array $where 自定义查询条件
* @param array $order 自定义排序
* @param bool $withTrashed 是否包含软删除数据
* @return Response JSON响应
*
* 示例:
* // 简单查询
* return $this->quickList('user');
*
* // 带条件的查询
* return $this->quickList('user', [['status', '=', 1]]);
*
* // 包含软删除数据的查询
* return $this->quickList('user', [], [], true);
*/
protected function quickList(string $table = null, array $where = [], array $order = [], bool $withTrashed = false): Response
{
try {
// 构建查询实例(根据是否包含软删除数据选择不同的实例)
$query = $withTrashed ? $this->withTrashed($table) : $this->safeDb($table);
// 构建完整的查询条件
$finalWhere = $this->buildWhere($where);
if (!empty($finalWhere)) {
$query->where($finalWhere);
}
// 处理排序
$sortField = $this->safeParam('sort_field', 'id');
$sortOrder = $this->safeParam('sort_order', 'desc');
// 验证排序字段安全性
if (!preg_match('/^[a-zA-Z_][a-zA-Z0-9_]*$/', $sortField)) {
$sortField = 'id';
}
// 验证排序方向
$sortOrder = strtolower($sortOrder) === 'asc' ? 'asc' : 'desc';
if (!empty($order)) {
// 使用自定义排序
$query->order($order);
} else {
// 使用请求参数中的排序
$query->order($sortField, $sortOrder);
}
// 执行分页查询
$pageData = $this->safePaginate($query);
return $this->success($pageData);
} catch (DbException $e) {
Log::error("快速列表查询失败: " . $e->getMessage());
return $this->error('数据库查询失败: ' . $e->getMessage());
} catch (\Exception $e) {
Log::error("快速列表查询异常: " . $e->getMessage());
return $this->error('查询失败: ' . $e->getMessage());
}
}
/**
* 快速单条数据查询
* 根据ID查询单条数据,自动处理软删除逻辑
*
* @param string|null $table 表名
* @param int|null $id 记录ID
* @param bool $withTrashed 是否包含软删除数据
* @param string $pk 主键字段名
* @return Response JSON响应
*
* 示例:
* // 简单查询
* return $this->quickFind('user', 123);
*
* // 包含软删除数据的查询
* return $this->quickFind('user', 123, true);
*/
protected function quickFind(string $table = null, int $id = null, bool $withTrashed = false, string $pk = 'id'): Response
{
try {
// 安全获取ID参数
$recordId = $id ?: $this->safeParam('id', 0, 'int');
if ($recordId <= 0) {
return $this->error('参数错误: ID必须大于0', 400);
}
// 构建查询实例
$query = $withTrashed ? $this->withTrashed($table) : $this->safeDb($table);
// 执行查询
$data = $query->where($pk, $recordId)->find();
if (!$data) {
return $this->error('数据不存在', 404);
}
return $this->success($data);
} catch (DbException $e) {
Log::error("单条数据查询失败: " . $e->getMessage());
return $this->error('数据库查询失败: ' . $e->getMessage());
} catch (\Exception $e) {
Log::error("单条数据查询异常: " . $e->getMessage());
return $this->error('查询失败: ' . $e->getMessage());
}
}
/**
* 快速新增数据
* 提供安全的数据新增功能,支持字段白名单过滤和前置处理
*
* @param string|null $table 表名
* @param array|null $data 数据数组(为空时从请求获取)
* @param callable|null $beforeSave 保存前回调函数
* @return Response JSON响应
*
* 示例:
* // 简单新增
* return $this->quickAdd('user');
*
* // 带前置处理的新增
* return $this->quickAdd('user', null, function($data) {
* $data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
* return $data;
* });
*/
protected function quickAdd(string $table = null, array $data = null, callable $beforeSave = null): Response
{
try {
// 获取数据(优先使用参数,其次从请求获取)
$postData = $data ?: Request::post();
if (empty($postData)) {
return $this->error('提交数据不能为空', 400);
}
// 字段白名单过滤
$filteredData = $this->filterFields($postData);
if (empty($filteredData)) {
return $this->error('无有效数据可保存', 400);
}
// 保存前回调处理(如数据加密、默认值设置等)
if ($beforeSave && is_callable($beforeSave)) {
$filteredData = $beforeSave($filteredData);
if (!is_array($filteredData)) {
return $this->error('前置处理返回数据格式错误', 500);
}
}
// 自动添加创建时间
if (!isset($filteredData['create_time']) && !isset($filteredData['created_at'])) {
$filteredData['create_time'] = time();
}
// 在事务中执行插入操作
$result = Db::transaction(function() use ($table, $filteredData) {
$tableName = $table ?: $this->getDefaultTable();
return Db::name($tableName)->insert($filteredData);
});
if ($result) {
$newId = Db::getLastInsID();
return $this->success(['id' => $newId], '新增成功', 201);
}
return $this->error('新增失败');
} catch (ValidateException $e) {
return $this->error($e->getMessage(), 422);
} catch (DbException $e) {
Log::error("新增数据失败: " . $e->getMessage());
return $this->error('数据库操作失败: ' . $e->getMessage());
} catch (\Exception $e) {
Log::error("新增数据异常: " . $e->getMessage());
return $this->error('新增失败: ' . $e->getMessage());
}
}
/**
* 快速更新数据
* 提供安全的数据更新功能,支持字段白名单过滤
*
* @param string|null $table 表名
* @param int|null $id 记录ID
* @param array|null $data 更新数据
* @param string $pk 主键字段名
* @return Response JSON响应
*
* 示例:
* // 简单更新
* return $this->quickUpdate('user', 123);
*
* // 指定数据更新
* return $this->quickUpdate('user', 123, ['status' => 0]);
*/
protected function quickUpdate(string $table = null, int $id = null, array $data = null, string $pk = 'id'): Response
{
try {
// 安全获取ID
$recordId = $id ?: $this->safeParam('id', 0, 'int');
if ($recordId <= 0) {
return $this->error('参数错误: ID必须大于0', 400);
}
// 获取更新数据
$updateData = $data ?: Request::put();
if (empty($updateData)) {
return $this->error('更新数据不能为空', 400);
}
// 字段白名单过滤
$filteredData = $this->filterFields($updateData);
if (empty($filteredData)) {
return $this->error('无有效更新数据', 400);
}
// 自动更新更新时间
if (!isset($filteredData['update_time']) && !isset($filteredData['updated_at'])) {
$filteredData['update_time'] = time();
}
// 在事务中执行更新操作
$result = Db::transaction(function() use ($table, $recordId, $filteredData, $pk) {
$tableName = $table ?: $this->getDefaultTable();
return Db::name($tableName)
->where($pk, $recordId)
->update($filteredData);
});
if ($result === false) {
return $this->error('数据不存在', 404);
}
if ($result > 0) {
return $this->success(null, '更新成功');
}
return $this->error('更新失败或数据无变化');
} catch (ValidateException $e) {
return $this->error($e->getMessage(), 422);
} catch (DbException $e) {
Log::error("更新数据失败: " . $e->getMessage());
return $this->error('数据库操作失败: ' . $e->getMessage());
} catch (\Exception $e) {
Log::error("更新数据异常: " . $e->getMessage());
return $this->error('更新失败: ' . $e->getMessage());
}
}
// ==================== 删除操作相关方法 ====================
/**
* 安全删除数据(支持软删除和强制删除)
* 根据配置自动选择软删除或强制删除
*
* @param string|null $table 表名
* @param int|null $id 记录ID
* @param bool $force 是否强制删除(忽略软删除)
* @param string $pk 主键字段名
* @return Response JSON响应
*
* 示例:
* // 软删除(默认)
* return $this->quickDelete('user', 123);
*
* // 强制删除
* return $this->quickDelete('user', 123, true);
*/
protected function quickDelete(string $table = null, int $id = null, bool $force = false, string $pk = 'id'): Response
{
try {
// 安全获取ID
$recordId = $id ?: $this->safeParam('id', 0, 'int');
if ($recordId <= 0) {
return $this->error('参数错误: ID必须大于0', 400);
}
$tableName = $table ?: $this->getDefaultTable();
$result = false;
// 在事务中执行删除操作
$result = Db::transaction(function() use ($tableName, $recordId, $force, $pk) {
if ($force || empty($this->softDeleteConfig['field'])) {
// 强制删除或未启用软删除:直接删除记录
return Db::name($tableName)
->where($pk, $recordId)
->delete();
} else {
// 软删除:更新删除时间字段
$deleteField = $this->softDeleteConfig['field'];
$deleteValue = $this->softDeleteConfig['type'] === 'timestamp' ? time() : 1;
return Db::name($tableName)
->where($pk, $recordId)
->update([
$deleteField => $deleteValue,
'update_time' => time()
]);
}
});
if ($result === false) {
return $this->error('数据不存在', 404);
}
if ($result > 0) {
$msg = $force ? '删除成功' : '删除成功(已放入回收站)';
return $this->success(['affected_rows' => $result], $msg);
}
return $this->error('删除失败');
} catch (DbException $e) {
Log::error("删除数据失败: " . $e->getMessage());
return $this->error('数据库操作失败: ' . $e->getMessage());
} catch (\Exception $e) {
Log::error("删除数据异常: " . $e->getMessage());
return $this->error('删除失败: ' . $e->getMessage());
}
}
/**
* 恢复软删除的数据
* 将软删除的数据恢复为正常数据
*
* @param string|null $table 表名
* @param int|null $id 记录ID
* @param string $pk 主键字段名
* @return Response JSON响应
*
* 示例:
* return $this->restore('user', 123);
*/
protected function restore(string $table = null, int $id = null, string $pk = 'id'): Response
{
try {
// 安全获取ID
$recordId = $id ?: $this->safeParam('id', 0, 'int');
if ($recordId <= 0) {
return $this->error('参数错误: ID必须大于0', 400);
}
if (empty($this->softDeleteConfig['field'])) {
return $this->error('未启用软删除功能', 400);
}
$tableName = $table ?: $this->getDefaultTable();
$deleteField = $this->softDeleteConfig['field'];
// 在事务中执行恢复操作
$result = Db::transaction(function() use ($tableName, $recordId, $deleteField, $pk) {
return Db::name($tableName)
->where($pk, $recordId)
->where($deleteField, 'not null') // 只恢复已软删除的数据
->update([
$deleteField => null,
'update_time' => time()
]);
});
if ($result === false) {
return $this->error('数据不存在或未被删除', 404);
}
if ($result > 0) {
return $this->success(['affected_rows' => $result], '恢复成功');
}
return $this->error('恢复失败或数据已是正常状态');
} catch (DbException $e) {
Log::error("恢复数据失败: " . $e->getMessage());
return $this->error('数据库操作失败: ' . $e->getMessage());
} catch (\Exception $e) {
Log::error("恢复数据异常: " . $e->getMessage());
return $this->error('恢复失败: ' . $e->getMessage());
}
}
/**
* 清空回收站(永久删除所有软删除数据)
* 谨慎使用,数据将无法恢复
*
* @param string|null $table 表名
* @return Response JSON响应
*
* 示例:
* return $this->clearTrash('user');
*/
protected function clearTrash(string $table = null): Response
{
try {
if (empty($this->softDeleteConfig['field'])) {
return $this->error('未启用软删除功能', 400);
}
$tableName = $table ?: $this->getDefaultTable();
$deleteField = $this->softDeleteConfig['field'];
// 先统计要删除的数据量
$count = Db::name($tableName)
->where($deleteField, 'not null')
->count();
if ($count === 0) {
return $this->success(null, '回收站为空');
}
// 在事务中执行清空操作
$result = Db::transaction(function() use ($tableName, $deleteField) {
return Db::name($tableName)
->where($deleteField, 'not null')
->delete();
});
if ($result) {
return $this->success(['deleted_count' => $result], '已清空回收站,永久删除 ' . $result . ' 条数据');
}
return $this->error('清空回收站失败');
} catch (DbException $e) {
Log::error("清空回收站失败: " . $e->getMessage());
return $this->error('数据库操作失败: ' . $e->getMessage());
} catch (\Exception $e) {
Log::error("清空回收站异常: " . $e->getMessage());
return $this->error('清空回收站失败: ' . $e->getMessage());
}
}
// ==================== 批量操作方法 ====================
/**
* 批量数据操作(事务支持)
* 提供安全的批量操作功能,自动处理事务
*
* @param callable $operation 操作回调函数
* @param string $successMsg 成功消息
* @param string $errorMsg 错误消息
* @return Response JSON响应
*
* 示例:
* return $this->batchOperation(function($ids) {
* return Db::name('user')->whereIn('id', $ids)->update(['status' => 0]);
* }, '批量更新成功', '批量更新失败');
*/
protected function batchOperation(callable $operation, string $successMsg = '操作成功', string $errorMsg = '操作失败'): Response
{
try {
// 获取批量操作的ID数组
$ids = $this->safeParam('ids', [], 'array');
if (empty($ids)) {
return $this->error('请选择要操作的数据', 400);
}
// 确保ID都是整数并过滤空值
$ids = array_map('intval', $ids);
$ids = array_filter($ids);
if (empty($ids)) {
return $this->error('无效的数据ID', 400);
}
// 限制一次批量操作的最大数量
$maxBatchCount = 1000;
if (count($ids) > $maxBatchCount) {
return $this->error('一次最多操作' . $maxBatchCount . '条数据', 400);
}
// 在事务中执行批量操作
$result = Db::transaction(function() use ($ids, $operation) {
return $operation($ids);
});
if ($result !== false && $result !== null) {
$affectedRows = is_int($result) ? $result : count($ids);
return $this->success(['affected_rows' => $affectedRows], $successMsg);
}
return $this->error($errorMsg);
} catch (DbException $e) {
Log::error("批量操作失败: " . $e->getMessage());
return $this->error('数据库操作失败: ' . $e->getMessage());
} catch (\Exception $e) {
Log::error("批量操作异常: " . $e->getMessage());
return $this->error($errorMsg . ': ' . $e->getMessage());
}
}
/**
* 批量软删除操作
*
* @param string|null $table 表名
* @return Response
*/
protected function batchDeleteOperation(string $table = null): Response
{
$tableName = $table ?: $this->getDefaultTable();
if (empty($this->softDeleteConfig['field'])) {
return $this->batchForceDeleteOperation($tableName);
}
$deleteField = $this->softDeleteConfig['field'];
$deleteValue = $this->softDeleteConfig['type'] === 'timestamp' ? time() : 1;
return $this->batchOperation(
function($ids) use ($tableName, $deleteField, $deleteValue) {
return Db::name($tableName)
->whereIn('id', $ids)
->update([
$deleteField => $deleteValue,
'update_time' => time()
]);
},
'批量删除成功',
'批量删除失败'
);
}
/**
* 批量恢复操作
*
* @param string|null $table 表名
* @return Response
*/
protected function batchRestoreOperation(string $table = null): Response
{
if (empty($this->softDeleteConfig['field'])) {
return $this->error('未启用软删除功能', 400);
}
$tableName = $table ?: $this->getDefaultTable();
$deleteField = $this->softDeleteConfig['field'];
return $this->batchOperation(
function($ids) use ($tableName, $deleteField) {
return Db::name($tableName)
->whereIn('id', $ids)
->where($deleteField, 'not null')
->update([
$deleteField => null,
'update_time' => time()
]);
},
'批量恢复成功',
'批量恢复失败'
);
}
/**
* 批量强制删除操作
*
* @param string|null $table 表名
* @return Response
*/
protected function batchForceDeleteOperation(string $table = null): Response
{
$tableName = $table ?: $this->getDefaultTable();
return $this->batchOperation(
function($ids) use ($tableName) {
return Db::name($tableName)
->whereIn('id', $ids)
->delete();
},
'批量永久删除成功',
'批量永久删除失败'
);
}
// ==================== 复杂查询和统计方法 ====================
/**
* 复杂链式查询构建器
* 提供灵活的链式查询接口,支持复杂查询条件
*
* @param string|null $table 表名
* @param bool $withTrashed 是否包含软删除数据
* @return Query
*
* 示例:
* $query = $this->buildQuery('user')
* ->where('status', 1)
* ->where('age', 'between', [18, 60])
* ->where(function($query) {
* $query->whereLike('name', '%张%')->whereOrLike('email', '%example%');
* })
* ->order('create_time', 'desc')
* ->field('id,name,email,create_time');
*/
protected function buildQuery(string $table = null, bool $withTrashed = false): Query
{
return $this->safeDb($table, $withTrashed);
}
/**
* 多表联查查询构建器
* 支持多表关联查询,自动处理软删除条件
*
* @param string $mainTable 主表
* @param string $joinTable 关联表
* @param string $on 关联条件
* @param string $joinType 关联类型(INNER, LEFT, RIGHT)
* @param bool $withTrashed 是否包含软删除数据
* @return Query
*
* 示例:
* $list = $this->buildJoinQuery('user', 'user_profile', 'user.id = user_profile.user_id')
* ->where('user.status', 1)
* ->field('user.*, user_profile.avatar, user_profile.bio')
* ->select();
*/
protected function buildJoinQuery(string $mainTable, string $joinTable, string $on, string $joinType = 'INNER', bool $withTrashed = false): Query
{
$query = Db::name($mainTable)->join($joinTable, $on, $joinType);
// 处理主表的软删除条件
if (!$withTrashed && !empty($this->softDeleteConfig['field'])) {
$deleteField = $this->softDeleteConfig['field'];
$query->where($mainTable . '.' . $deleteField, null);
}
return $query;
}
/**
* 获取数据统计信息
* 提供常见的数据统计功能
*
* @param string|null $table 表名
* @param array $where 查询条件
* @param bool $withTrashed 是否包含软删除数据
* @return array 统计结果
*
* 示例:
* $stats = $this->getStats('user', [['status', '=', 1]]);
*/
protected function getStats(string $table = null, array $where = [], bool $withTrashed = false): array
{
$query = $this->safeDb($table, $withTrashed);
if (!empty($where)) {
$query->where($where);
}
return [
'total' => $query->count(),
'sum' => $query->sum('score') ?: 0,
'avg' => $query->avg('score') ?: 0,
'max' => $query->max('score') ?: 0,
'min' => $query->min('score') ?: 0,
];
}
/**
* 分组统计查询
* 按指定字段进行分组统计
*
* @param string|null $table 表名
* @param string $groupField 分组字段
* @param array $where 查询条件
* @param bool $withTrashed 是否包含软删除数据
* @return array 分组统计结果
*
* 示例:
* $groupStats = $this->getGroupStats('user', 'status');
*/
protected function getGroupStats(string $table = null, string $groupField, array $where = [], bool $withTrashed = false): array
{
$query = $this->safeDb($table, $withTrashed);
if (!empty($where)) {
$query->where($where);
}
return $query->field($groupField . ', COUNT(*) as count')
->group($groupField)
->select()
->toArray();
}
/**
* 日期范围统计
* 按日期字段进行范围统计
*
* @param string|null $table 表名
* @param string $dateField 日期字段
* @param string $startDate 开始日期
* @param string $endDate 结束日期
* @param string $format 日期格式(Y-m-d, Y-m, Y等)
* @param array $where 额外查询条件
* @return array 日期统计结果
*
* 示例:
* $dateStats = $this->getDateRangeStats('user', 'create_time', '2023-01-01', '2023-12-31', 'Y-m');
*/
protected function getDateRangeStats(string $table = null, string $dateField, string $startDate, string $endDate, string $format = 'Y-m-d', array $where = []): array
{
$query = $this->safeDb($table);
// 添加日期范围条件
$query->whereBetween($dateField, [strtotime($startDate), strtotime($endDate)]);
// 添加额外条件
if (!empty($where)) {
$query->where($where);
}
// 根据格式进行分组统计
$dateFormat = $this->getDateFormatSql($format);
return $query->field("DATE_FORMAT(FROM_UNIXTIME({$dateField}), '{$dateFormat}') as date, COUNT(*) as count")
->group('date')
->order('date')
->select()
->toArray();
}
/**
* 获取日期格式的SQL表达式
*
* @param string $format 日期格式
* @return string SQL日期格式表达式
*/
private function getDateFormatSql(string $format): string
{
$formatMap = [
'Y-m-d' => '%Y-%m-%d',
'Y-m' => '%Y-%m',
'Y' => '%Y',
'm-d' => '%m-%d',
'W' => '%u', // 周
];
return $formatMap[$format] ?? '%Y-%m-%d';
}
// ==================== 响应和工具方法 ====================
/**
* 统一成功响应
* 提供标准化的成功响应格式
*
* @param mixed $data 返回数据
* @param string $msg 提示信息
* @param int $code 状态码
* @return Response
*/
protected function success($data = null, string $msg = '操作成功', int $code = 200): Response
{
$result = [
'code' => $code,
'msg' => $msg,
'data' => $data,
'time' => time()
];
return json($result);
}
/**
* 统一错误响应
* 提供标准化的错误响应格式,生产环境隐藏详细错误
*
* @param string $msg 错误信息
* @param int $code 状态码
* @param mixed $data 附加数据
* @return Response
*/
protected function error(string $msg = '操作失败', int $code = 500, $data = null): Response
{
// 生产环境隐藏详细错误信息
if (app()->isProduction() && $code === 500) {
$msg = '系统繁忙,请稍后重试';
}
$result = [
'code' => $code,
'msg' => $msg,
'data' => $data,
'time' => time()
];
return json($result, $code);
}
/**
* 验证请求频率限制
* 防止API滥用和恶意请求
*
* @param string $key 限制键名
* @param int|null $maxRequests 最大请求次数
* @param int|null $period 时间周期(秒)
* @return bool 是否允许请求
*/
protected function checkRateLimit(string $key, int $maxRequests = null, int $period = null): bool
{
$maxRequests = $maxRequests ?: $this->rateLimitConfig['max_requests'];
$period = $period ?: $this->rateLimitConfig['period'];
$prefix = $this->rateLimitConfig['prefix'];
$cacheKey = $prefix . $key . ':' . floor(time() / $period);
$requests = cache($cacheKey) ?: 0;
if ($requests >= $maxRequests) {
return false;
}
cache($cacheKey, $requests + 1, $period);
return true;
}
}2. 完整的使用示例 (UserController.php)
php
<?php
namespace app\controller;
use think\exception\ValidateException;
use think\facade\Db;
/**
* 用户控制器 - 完整功能演示
*
* 演示ExtendedController的所有功能特性,包含详细的用法示例
*
* 主要演示功能:
* 1. 基本CRUD操作(增删改查)
* 2. 软删除和恢复操作
* 3. 批量操作功能
* 4. 复杂查询和统计
* 5. 多表联查和高级查询
*
* @package app\controller
*/
class UserController extends ExtendedController
{
/**
* 允许操作的字段白名单
* 设置后只允许操作这些字段,防止恶意字段提交
* @var array
*/
protected array $allowFields = [
'username', 'email', 'password', 'nickname',
'avatar', 'phone', 'status', 'role_id', 'bio',
'age', 'gender', 'birthday', 'last_login_time'
];
/**
* 允许搜索的字段
* 用于关键词搜索时,限制只能在指定字段中进行搜索
* @var array
*/
protected array $searchFields = ['username', 'email', 'nickname', 'phone'];
/**
* 软删除配置
* 配置软删除的字段名和字段类型
* @var array
*/
protected array $softDeleteConfig = [
'field' => 'delete_time',
'type' => 'timestamp'
];
/**
* 设置最大分页数量
* @var int
*/
protected int $maxLimit = 50;
/**
* 初始化方法
* 可以在这里进行控制器级别的初始化配置
*/
protected function initialize(): void
{
parent::initialize();
// 可以在这里进行用户控制器的特殊初始化
}
// ==================== 基本CRUD操作 ====================
/**
* 用户列表 - 简单查询
* 演示最基本的列表查询功能
*
* @return \think\Response
*
* 访问方式:GET /user
* 参数:page=1&limit=15&keyword=搜索词&status=1&sort_field=id&sort_order=desc
*/
public function index(): \think\Response
{
// 使用quickList方法进行快速分页查询
// 第一个参数:表名(为空则自动推导)
// 第二个参数:额外查询条件
// 第三个参数:默认排序
// 第四个参数:是否包含软删除数据(默认false)
return $this->quickList('user',
[['status', '=', 1]], // 只查询状态为1的用户
['create_time' => 'desc'], // 按创建时间倒序
false // 不包含软删除数据
);
}
/**
* 用户详情 - 简单查询
* 根据ID查询单条用户数据
*
* @param int $id 用户ID
* @return \think\Response
*
* 访问方式:GET /user/123
*/
public function read(int $id): \think\Response
{
// 使用quickFind方法查询单条数据
// 第一个参数:表名
// 第二个参数:记录ID
// 第三个参数:是否包含软删除数据(默认false)
return $this->quickFind('user', $id);
}
/**
* 新增用户 - 带数据验证和预处理
* 演示如何新增数据并进行安全处理
*
* @return \think\Response
*
* 访问方式:POST /user
* 参数:username=用户名&email=邮箱&password=密码&nickname=昵称
*/
public function save(): \think\Response
{
// 使用quickAdd方法新增数据
// 第一个参数:表名
// 第二个参数:数据数组(为空则从请求获取)
// 第三个参数:保存前回调函数(用于数据验证和预处理)
return $this->quickAdd('user', null, function($data) {
// 数据验证
$this->validateUserData($data);
// 密码加密处理
if (isset($data['password'])) {
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
}
// 设置默认值
$data['create_time'] = time();
$data['update_time'] = time();
$data['status'] = $data['status'] ?? 1; // 默认状态为启用
// 生成默认头像
if (empty($data['avatar'])) {
$data['avatar'] = $this->generateDefaultAvatar($data['username']);
}
return $data;
});
}
/**
* 更新用户信息
* 演示如何更新数据并进行安全处理
*
* @param int $id 用户ID
* @return \think\Response
*
* 访问方式:PUT /user/123
* 参数:nickname=新昵称&phone=新手机号&bio=新简介
*/
public function update(int $id): \think\Response
{
// 使用quickUpdate方法更新数据
// 第一个参数:表名
// 第二个参数:记录ID
// 第三个参数:更新数据(为空则从请求获取)
return $this->quickUpdate('user', $id, null, function($data) {
// 更新前的数据验证
if (isset($data['email'])) {
$this->validateEmail($data['email']);
}
if (isset($data['phone'])) {
$this->validatePhone($data['phone']);
}
// 自动更新更新时间
$data['update_time'] = time();
return $data;
});
}
// ==================== 软删除相关操作 ====================
/**
* 软删除用户(放入回收站)
* 演示软删除功能,数据不会被真正删除
*
* @param int $id 用户ID
* @return \think\Response
*
* 访问方式:DELETE /user/123
*/
public function delete(int $id): \think\Response
{
// 使用quickDelete方法进行软删除
// 第一个参数:表名
// 第二个参数:记录ID
// 第三个参数:是否强制删除(false表示软删除)
return $this->quickDelete('user', $id, false);
}
/**
* 强制删除用户(永久删除)
* 演示强制删除功能,数据将永久删除
*
* @param int $id 用户ID
* @return \think\Response
*
* 访问方式:DELETE /user/123/force
*/
public function forceDelete(int $id): \think\Response
{
// 使用quickDelete方法进行强制删除
// 第三个参数设置为true表示强制删除
return $this->quickDelete('user', $id, true);
}
/**
* 恢复软删除的用户
* 演示如何恢复已被软删除的数据
*
* @param int $id 用户ID
* @return \think\Response
*
* 访问方式:POST /user/123/restore
*/
public function restore(int $id): \think\Response
{
// 使用restore方法恢复软删除的数据
return $this->restore('user', $id);
}
/**
* 回收站列表 - 仅显示软删除的数据
* 演示如何查询已被软删除的数据
*
* @return \think\Response
*
* 访问方式:GET /user/trash
*/
public function trash(): \think\Response
{
// 使用trashList方法查询回收站数据
return $this->trashList('user',
[], // 额外查询条件
['delete_time' => 'desc'] // 按删除时间倒序
);
}
/**
* 清空用户回收站
* 演示如何永久删除所有软删除数据
*
* @return \think\Response
*
* 访问方式:POST /user/clear-trash
*/
public function clearTrash(): \think\Response
{
// 使用clearTrash方法清空回收站
return $this->clearTrash('user');
}
// ==================== 批量操作功能 ====================
/**
* 批量软删除用户
* 演示批量软删除功能
*
* @return \think\Response
*
* 访问方式:POST /user/batch-delete
* 参数:ids=1,2,3,4,5 或 ids[]=1&ids[]=2&ids[]=3
*/
public function batchDelete(): \think\Response
{
// 使用batchDeleteOperation方法进行批量软删除
return $this->batchDeleteOperation('user');
}
/**
* 批量恢复用户
* 演示批量恢复软删除数据功能
*
* @return \think\Response
*
* 访问方式:POST /user/batch-restore
* 参数:ids=1,2,3,4,5
*/
public function batchRestore(): \think\Response
{
// 使用batchRestoreOperation方法进行批量恢复
return $this->batchRestoreOperation('user');
}
/**
* 批量强制删除用户
* 演示批量强制删除功能
*
* @return \think\Response
*
* 访问方式:POST /user/batch-force-delete
* 参数:ids=1,2,3,4,5
*/
public function batchForceDelete(): \think\Response
{
// 使用batchForceDeleteOperation方法进行批量强制删除
return $this->batchForceDeleteOperation('user');
}
/**
* 批量更新用户状态
* 演示自定义批量操作功能
*
* @return \think\Response
*
* 访问方式:POST /user/batch-status
* 参数:ids=1,2,3,4,5&status=0
*/
public function batchStatus(): \think\Response
{
// 使用batchOperation方法进行自定义批量操作
return $this->batchOperation(
function($ids) {
$status = $this->safeParam('status', 0, 'int');
return Db::name('user')
->whereIn('id', $ids)
->update([
'status' => $status,
'update_time' => time()
]);
},
'批量状态更新成功',
'批量状态更新失败'
);
}
// ==================== 复杂查询和统计功能 ====================
/**
* 高级搜索功能
* 演示复杂查询条件的构建
*
* @return \think\Response
*
* 访问方式:GET /user/search
* 参数:keyword=搜索词&min_age=18&max_age=60&gender=1&status=1
*/
public function search(): \think\Response
{
try {
// 使用buildQuery方法构建复杂查询
$query = $this->buildQuery('user')
->where('status', 1); // 基础条件:只查询启用状态的用户
// 年龄范围筛选
$minAge = $this->safeParam('min_age', 0, 'int');
$maxAge = $this->safeParam('max_age', 0, 'int');
if ($minAge > 0) {
$query->where('age', '>=', $minAge);
}
if ($maxAge > 0) {
$query->where('age', '<=', $maxAge);
}
// 性别筛选
$gender = $this->safeParam('gender');
if ($gender !== '') {
$query->where('gender', $gender);
}
// 关键词搜索(多字段模糊搜索)
$keyword = $this->safeParam('keyword');
if ($keyword) {
$query->where(function($q) use ($keyword) {
$q->whereLike('username', "%{$keyword}%")
->whereOrLike('email', "%{$keyword}%")
->whereOrLike('nickname', "%{$keyword}%")
->whereOrLike('phone', "%{$keyword}%");
});
}
// 注册时间范围筛选
$regStart = $this->safeParam('reg_start');
$regEnd = $this->safeParam('reg_end');
if ($regStart) {
$query->where('create_time', '>=', strtotime($regStart));
}
if ($regEnd) {
$query->where('create_time', '<=', strtotime($regEnd));
}
// 排序处理
$sortBy = $this->safeParam('sort_by', 'create_time');
$sortOrder = $this->safeParam('sort_order', 'desc');
$query->order($sortBy, $sortOrder);
// 字段选择(避免返回敏感字段)
$query->field('id,username,email,nickname,avatar,phone,gender,age,create_time,last_login_time,status');
// 执行分页查询
$result = $this->safePaginate($query);
return $this->success($result);
} catch (\Exception $e) {
return $this->error('搜索失败: ' . $e->getMessage());
}
}
/**
* 用户统计信息
* 演示数据统计功能
*
* @return \think\Response
*
* 访问方式:GET /user/stats
*/
public function stats(): \think\Response
{
try {
// 总用户数统计(包含各种状态)
$totalStats = $this->getGroupStats('user', 'status');
// 今日新增用户
$todayStart = strtotime('today');
$todayNew = $this->safeDb('user')
->where('create_time', '>=', $todayStart)
->count();
// 本周新增用户
$weekStart = strtotime('monday this week');
$weekNew = $this->safeDb('user')
->where('create_time', '>=', $weekStart)
->count();
// 本月新增用户
$monthStart = strtotime('first day of this month');
$monthNew = $this->safeDb('user')
->where('create_time', '>=', $monthStart)
->count();
// 活跃用户统计(最近7天有登录)
$activeUsers = $this->safeDb('user')
->where('last_login_time', '>=', strtotime('-7 days'))
->count();
// 性别分布统计
$genderStats = $this->getGroupStats('user', 'gender');
// 年龄分布统计
$ageStats = $this->safeDb('user')
->field('
CASE
WHEN age < 18 THEN "未成年"
WHEN age BETWEEN 18 AND 25 THEN "18-25"
WHEN age BETWEEN 26 AND 35 THEN "26-35"
WHEN age BETWEEN 36 AND 45 THEN "36-45"
WHEN age > 45 THEN "45以上"
ELSE "未知"
END as age_group,
COUNT(*) as count
')
->group('age_group')
->select();
// 回收站统计
$trashCount = $this->onlyTrashed('user')->count();
$stats = [
'total_distribution' => $totalStats, // 状态分布
'today_new' => $todayNew, // 今日新增
'week_new' => $weekNew, // 本周新增
'month_new' => $monthNew, // 本月新增
'active_users' => $activeUsers, // 活跃用户
'gender_distribution' => $genderStats, // 性别分布
'age_distribution' => $ageStats, // 年龄分布
'trash_count' => $trashCount, // 回收站数量
];
return $this->success($stats);
} catch (\Exception $e) {
return $this->error('获取统计失败: ' . $e->getMessage());
}
}
/**
* 用户登录记录
* 演示多表联查功能
*
* @param int $id 用户ID
* @return \think\Response
*
* 访问方式:GET /user/123/login-logs
*/
public function loginLogs(int $id): \think\Response
{
try {
// 使用buildJoinQuery进行多表联查
$logs = $this->buildJoinQuery(
'user', // 主表
'user_login_log', // 关联表
'user.id = user_login_log.user_id', // 关联条件
'LEFT' // 关联类型
)
->where('user.id', $id)
->field('user.username, user.nickname, user_login_log.*')
->order('user_login_log.login_time', 'desc')
->limit(20)
->select();
if (!$logs) {
return $this->error('用户不存在或没有登录记录', 404);
}
return $this->success($logs);
} catch (\Exception $e) {
return $this->error('获取登录记录失败: ' . $e->getMessage());
}
}
/**
* 用户详情带完整信息
* 演示复杂联查和数据处理
*
* @param int $id 用户ID
* @return \think\Response
*
* 访问方式:GET /user/123/full
*/
public function fullInfo(int $id): \think\Response
{
try {
// 复杂的多表联查
$userInfo = $this->buildJoinQuery('user', 'user_profile', 'user.id = user_profile.user_id', 'LEFT')
->where('user.id', $id)
->field('
user.*,
user_profile.avatar, user_profile.bio, user_profile.location,
user_profile.company, user_profile.position, user_profile.website,
user_profile.social_links, user_profile.education, user_profile.experience
')
->find();
if (!$userInfo) {
return $this->error('用户不存在', 404);
}
// 获取用户的文章统计
$articleStats = Db::name('article')
->where('user_id', $id)
->where('status', 1)
->field('COUNT(*) as total, SUM(views) as total_views, AVG(views) as avg_views')
->find();
// 获取用户的粉丝和关注统计
$followStats = Db::name('user_follow')
->where('user_id', $id)
->field('
COUNT(*) as follower_count,
(SELECT COUNT(*) FROM user_follow WHERE follower_id = ' . $id . ') as following_count
')
->find();
// 组合完整信息
$fullInfo = array_merge(
$userInfo,
['article_stats' => $articleStats],
['follow_stats' => $followStats]
);
return $this->success($fullInfo);
} catch (\Exception $e) {
return $this->error('获取用户详情失败: ' . $e->getMessage());
}
}
// ==================== 辅助方法 ====================
/**
* 用户数据验证
* 用于新增和更新时的数据验证
*
* @param array $data 用户数据
* @throws ValidateException
*/
private function validateUserData(array $data): void
{
$rules = [
'username' => 'require|min:3|max:20|unique:user',
'email' => 'require|email|unique:user',
'password' => 'require|min:6|max:20',
'phone' => 'mobile|unique:user',
'nickname' => 'max:30',
];
$messages = [
'username.require' => '用户名不能为空',
'username.unique' => '用户名已存在',
'email.unique' => '邮箱已存在',
'email.email' => '邮箱格式不正确',
'password.min' => '密码至少6位',
'phone.mobile' => '手机号格式不正确',
'phone.unique' => '手机号已存在',
];
$validator = \think\facade\Validate::rule($rules)->message($messages);
if (!$validator->check($data)) {
throw new ValidateException($validator->getError());
}
}
/**
* 邮箱验证
*
* @param string $email 邮箱地址
* @throws ValidateException
*/
private function validateEmail(string $email): void
{
if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
throw new ValidateException('邮箱格式不正确');
}
}
/**
* 手机号验证
*
* @param string $phone 手机号
* @throws ValidateException
*/
private function validatePhone(string $phone): void
{
if (!preg_match('/^1[3-9]\d{9}$/', $phone)) {
throw new ValidateException('手机号格式不正确');
}
}
/**
* 生成默认头像
*
* @param string $username 用户名
* @return string 头像URL
*/
private function generateDefaultAvatar(string $username): string
{
$firstChar = mb_substr($username, 0, 1);
$color = substr(md5($username), 0, 6);
return "https://via.placeholder.com/100x100/{$color}/ffffff?text=" . urlencode($firstChar);
}
/**
* 自定义构建查询条件
* 演示如何重写buildWhere方法添加自定义条件
*
* @param array $customWhere 自定义条件
* @return array
*/
protected function buildWhere(array $customWhere = []): array
{
// 先调用父类的构建方法
$where = parent::buildWhere($customWhere);
// 添加用户特定的查询条件
$roleId = $this->safeParam('role_id');
if ($roleId !== '' && $roleId !== null) {
$where[] = ['role_id', '=', (int)$roleId];
}
// VIP用户筛选
$isVip = $this->safeParam('is_vip');
if ($isVip !== '' && $isVip !== null) {
$where[] = ['is_vip', '=', (int)$isVip];
}
// 最后登录时间筛选
$lastLoginStart = $this->safeParam('last_login_start');
$lastLoginEnd = $this->safeParam('last_login_end');
if ($lastLoginStart) {
$where[] = ['last_login_time', '>=', strtotime($lastLoginStart)];
}
if ($lastLoginEnd) {
$where[] = ['last_login_time', '<=', strtotime($lastLoginEnd)];
}
return $where;
}
}3. 路由配置示例 (route/app.php)
php
<?php
use think\facade\Route;
/**
* 用户模块路由配置
*
* 包含完整的RESTful路由和自定义路由配置
* 演示各种路由定义方式和最佳实践
*/
// ==================== RESTful资源路由 ====================
// 自动生成标准RESTful路由,对应UserController的方法
Route::resource('user', 'UserController');
// ==================== 软删除相关路由 ====================
// 回收站功能路由
Route::get('user/trash', 'UserController/trash'); // 回收站列表
Route::get('user/with-trashed', 'UserController/indexWithTrashed'); // 包含软删除的列表
// 删除和恢复路由
Route::delete('user/:id/force', 'UserController/forceDelete'); // 强制删除
Route::post('user/:id/restore', 'UserController/restore'); // 恢复数据
Route::post('user/clear-trash', 'UserController/clearTrash'); // 清空回收站
// ==================== 批量操作路由 ====================
Route::post('user/batch-delete', 'UserController/batchDelete'); // 批量软删除
Route::post('user/batch-restore', 'UserController/batchRestore'); // 批量恢复
Route::post('user/batch-force-delete', 'UserController/batchForceDelete'); // 批量强制删除
Route::post('user/batch-status', 'UserController/batchStatus'); // 批量状态更新
// ==================== 查询和统计路由 ====================
Route::get('user/search', 'UserController/search'); // 高级搜索
Route::get('user/stats', 'UserController/stats'); // 统计信息
Route::get('user/:id/login-logs', 'UserController/loginLogs'); // 登录记录
Route::get('user/:id/full', 'UserController/fullInfo'); // 完整信息
// ==================== API分组路由(推荐使用) ====================
Route::group('api/v1', function () {
// 用户资源路由
Route::resource('users', 'UserController');
// 软删除相关
Route::get('users/trash', 'UserController/trash');
Route::delete('users/:id/force', 'UserController/forceDelete');
Route::post('users/:id/restore', 'UserController/restore');
Route::post('users/clear-trash', 'UserController/clearTrash');
// 批量操作
Route::post('users/batch/delete', 'UserController/batchDelete');
Route::post('users/batch/restore', 'UserController/batchRestore');
Route::post('users/batch/force-delete', 'UserController/batchForceDelete');
Route::post('users/batch/status', 'UserController/batchStatus');
// 查询统计
Route::get('users/search/advanced', 'UserController/search');
Route::get('users/stats/overview', 'UserController/stats');
Route::get('users/:id/login-logs', 'UserController/loginLogs');
Route::get('users/:id/full-info', 'UserController/fullInfo');
})->allowCrossDomain()->middleware(['Auth', 'Log']); // 允许跨域并添加中间件
// ==================== 管理员专用路由组 ====================
Route::group('admin', function () {
Route::group('users', function () {
// 管理员专用的用户管理路由
Route::get('/', 'admin.UserController/index'); // 用户列表
Route::get('/export', 'admin.UserController/export'); // 导出用户
Route::post('/import', 'admin.UserController/import'); // 导入用户
Route::post('/:id/reset-password', 'admin.UserController/resetPassword'); // 重置密码
Route::post('/:id/lock', 'admin.UserController/lock'); // 锁定用户
Route::post('/:id/unlock', 'admin.UserController/unlock'); // 解锁用户
});
})->middleware(['Auth', 'AdminAuth']); // 需要管理员权限
// ==================== 前端路由组 ====================
Route::group('web', function () {
Route::group('user', function () {
// 前端用户相关路由
Route::get('profile', 'user.ProfileController/index'); // 个人资料
Route::post('profile', 'user.ProfileController/update'); // 更新资料
Route::get('security', 'user.SecurityController/index'); // 安全设置
Route::post('security', 'user.SecurityController/update'); // 更新安全设置
});
})->middleware(['Auth']);
// ==================== 移动端API路由组 ====================
Route::group('mobile/api', function () {
Route::group('v1', function () {
// 移动端专用API(简化版)
Route::get('user/info', 'mobile.UserController/info'); // 用户信息
Route::post('user/update', 'mobile.UserController/update'); // 更新信息
Route::post('user/avatar', 'mobile.UserController/uploadAvatar'); // 上传头像
});
})->allowCrossDomain()->middleware(['MobileAuth']);
/**
* 路由配置说明:
*
* 1. RESTful资源路由:适合标准的CRUD操作
* 2. 分组路由:按功能模块分组,便于管理和维护
* 3. 中间件:添加权限验证、日志记录等中间件
* 4. 跨域支持:API路由需要允许跨域访问
* 5. 版本控制:API建议使用版本控制(如v1、v2)
*
* 最佳实践:
* - 使用资源路由处理标准CRUD
* - 使用分组路由组织相关功能
* - 为不同客户端(Web、Mobile、Admin)创建独立路由组
* - 为API路由添加版本控制
* - 为敏感操作添加适当的中间件保护
*/4. 模型配置示例 (User.php)
php
<?php
declare(strict_types=1);
namespace app\model;
use think\Model;
use think\model\concern\SoftDelete;
/**
* 用户模型 - User
*
* 演示ThinkPHP8模型的完整配置,包含软删除、自动时间戳等特性
*
* 主要特性:
* 1. 软删除支持(SoftDelete Trait)
* 2. 自动时间戳
* 3. 字段类型自动转换
* 4. 获取器和修改器
* 5. 模型关联定义
*
* @package app\model
*/
class User extends Model
{
// 使用软删除特性
use SoftDelete;
/**
* 表名
* 如果表名与模型名不一致,需要指定表名
* @var string
*/
protected $table = 'users';
/**
* 主键
* 默认主键为id,如果使用其他字段需要指定
* @var string
*/
protected $pk = 'id';
/**
* 自动写入时间戳
* 启用后会自动维护create_time和update_time字段
* @var bool
*/
protected $autoWriteTimestamp = true;
/**
* 创建时间字段
* @var string
*/
protected $createTime = 'create_time';
/**
* 更新时间字段
* @var string
*/
protected $updateTime = 'update_time';
/**
* 软删除字段
* 使用SoftDelete Trait时需要指定软删除字段
* @var string
*/
protected $deleteTime = 'delete_time';
/**
* 默认软删除数据
* 软删除字段的默认值
* @var mixed
*/
protected $defaultSoftDelete = null;
/**
* 字段自动完成
* 新增数据时自动设置的字段值
* @var array
*/
protected $insert = [
'status' => 1, // 默认状态为启用
'login_count' => 0, // 登录次数默认为0
'is_vip' => 0, // 默认非VIP
];
/**
* 字段类型转换
* 自动将字段值转换为指定类型
* @var array
*/
protected $type = [
'id' => 'integer',
'status' => 'integer',
'age' => 'integer',
'gender' => 'integer',
'role_id' => 'integer',
'is_vip' => 'boolean',
'login_count' => 'integer',
'create_time' => 'datetime',
'update_time' => 'datetime',
'delete_time' => 'datetime',
'last_login_time' => 'datetime',
'birthday' => 'datetime',
'social_links' => 'json', // 自动JSON编码/解码
'settings' => 'json', // 用户设置
];
/**
* 只读字段
* 这些字段只能读取,不能修改
* @var array
*/
protected $readonly = [
'id',
'create_time',
];
/**
* 可搜索字段
* 用于模型搜索的字段列表
* @var array
*/
protected $searchField = [
'username',
'email',
'nickname',
'phone',
];
// ==================== 获取器(Getter) ====================
/**
* 状态文字获取器
* 将状态码转换为文字描述
*
* @param mixed $value 字段值
* @param array $data 完整数据
* @return string
*/
public function getStatusTextAttr($value, array $data): string
{
$statusMap = [
0 => '禁用',
1 => '启用',
2 => '待审核',
3 => '锁定',
];
return $statusMap[$data['status']] ?? '未知';
}
/**
* 性别文字获取器
*
* @param mixed $value 字段值
* @param array $data 完整数据
* @return string
*/
public function getGenderTextAttr($value, array $data): string
{
$genderMap = [
0 => '未知',
1 => '男',
2 => '女',
];
return $genderMap[$data['gender']] ?? '未知';
}
/**
* 年龄阶段获取器
*
* @param mixed $value 字段值
* @param array $data 完整数据
* @return string
*/
public function getAgeStageAttr($value, array $data): string
{
if (empty($data['age'])) {
return '未知';
}
$age = $data['age'];
if ($age < 18) return '未成年';
if ($age <= 25) return '青年';
if ($age <= 35) return '中年';
if ($age <= 45) return '壮年';
return '老年';
}
/**
* VIP等级获取器
*
* @param mixed $value 字段值
* @param array $data 完整数据
* @return string
*/
public function getVipLevelTextAttr($value, array $data): string
{
if (!$data['is_vip']) {
return '普通用户';
}
$vipMap = [
1 => 'VIP1',
2 => 'VIP2',
3 => 'VIP3',
4 => 'VIP4',
5 => 'VIP5',
];
return $vipMap[$data['vip_level']] ?? 'VIP';
}
/**
* 头像URL获取器
* 自动补全头像URL
*
* @param mixed $value 字段值
* @param array $data 完整数据
* @return string
*/
public function getAvatarUrlAttr($value, array $data): string
{
if (empty($data['avatar'])) {
// 返回默认头像
$firstChar = mb_substr($data['username'] ?? 'U', 0, 1);
$color = substr(md5($data['username'] ?? 'default'), 0, 6);
return "https://via.placeholder.com/100x100/{$color}/ffffff?text=" . urlencode($firstChar);
}
// 如果已经是完整URL,直接返回
if (strpos($data['avatar'], 'http') === 0) {
return $data['avatar'];
}
// 补全相对路径
return config('app.static_url') . '/avatars/' . $data['avatar'];
}
/**
* 最后登录时间格式化
*
* @param mixed $value 字段值
* @param array $data 完整数据
* @return string
*/
public function getLastLoginTimeTextAttr($value, array $data): string
{
if (empty($data['last_login_time'])) {
return '从未登录';
}
$time = $data['last_login_time'];
$diff = time() - $time;
if ($diff < 60) return '刚刚';
if ($diff < 3600) return floor($diff / 60) . '分钟前';
if ($diff < 86400) return floor($diff / 3600) . '小时前';
if ($diff < 2592000) return floor($diff / 86400) . '天前';
return date('Y-m-d H:i', $time);
}
// ==================== 修改器(Setter) ====================
/**
* 密码修改器
* 自动对密码进行加密
*
* @param string $value 原始密码
* @return string 加密后的密码
*/
public function setPasswordAttr(string $value): string
{
return password_hash($value, PASSWORD_DEFAULT);
}
/**
* 用户名修改器
* 自动去除首尾空格
*
* @param string $value 用户名
* @return string 处理后的用户名
*/
public function setUsernameAttr(string $value): string
{
return trim($value);
}
/**
* 邮箱修改器
* 自动转换为小写
*
* @param string $value 邮箱
* @return string 小写邮箱
*/
public function setEmailAttr(string $value): string
{
return strtolower(trim($value));
}
/**
* 手机号修改器
* 自动清理格式
*
* @param string $value 手机号
* @return string 清理后的手机号
*/
public function setPhoneAttr(string $value): string
{
return preg_replace('/\D/', '', $value);
}
/**
* 生日修改器
* 自动转换为时间戳
*
* @param mixed $value 生日
* @return int 时间戳
*/
public function setBirthdayAttr($value): int
{
if (is_numeric($value)) {
return (int)$value;
}
return strtotime($value) ?: 0;
}
// ==================== 模型关联 ====================
/**
* 用户档案关联
* 一对一关联用户档案表
*
* @return \think\model\relation\HasOne
*/
public function profile()
{
return $this->hasOne(UserProfile::class, 'user_id', 'id');
}
/**
* 用户角色关联
* 多对一关联角色表
*
* @return \think\model\relation\BelongsTo
*/
public function role()
{
return $this->belongsTo(Role::class, 'role_id', 'id');
}
/**
* 用户文章关联
* 一对多关联文章表
*
* @return \think\model\relation\HasMany
*/
public function articles()
{
return $this->hasMany(Article::class, 'user_id', 'id');
}
/**
* 用户登录日志关联
* 一对多关联登录日志表
*
* @return \think\model\relation\HasMany
*/
public function loginLogs()
{
return $this->hasMany(UserLoginLog::class, 'user_id', 'id');
}
/**
* 粉丝关联(关注我的人)
* 多对多关联,通过中间表
*
* @return \think\model\relation\BelongsToMany
*/
public function followers()
{
return $this->belongsToMany(
User::class, // 关联模型
'user_follow', // 中间表
'follower_id', // 中间表对应关联模型的外键
'user_id' // 中间表对应当前模型的外键
);
}
/**
* 关注关联(我关注的人)
* 多对多关联,通过中间表
*
* @return \think\model\relation\BelongsToMany
*/
public function followings()
{
return $this->belongsToMany(
User::class, // 关联模型
'user_follow', // 中间表
'user_id', // 中间表对应当前模型的外键
'follower_id' // 中间表对应关联模型的外键
);
}
// ==================== 模型作用域 ====================
/**
* 启用状态的作用域
*
* @param \think\db\Query $query 查询对象
* @return \think\db\Query
*/
public function scopeStatusEnabled($query)
{
return $query->where('status', 1);
}
/**
* VIP用户的作用域
*
* @param \think\db\Query $query 查询对象
* @return \think\db\Query
*/
public function scopeVip($query)
{
return $query->where('is_vip', 1);
}
/**
* 最近活跃的作用域
*
* @param \think\db\Query $query 查询对象
* @param int $days 天数
* @return \think\db\Query
*/
public function scopeRecentActive($query, $days = 7)
{
return $query->where('last_login_time', '>=', time() - $days * 86400);
}
/**
* 年龄范围的作用域
*
* @param \think\db\Query $query 查询对象
* @param int $min 最小年龄
* @param int $max 最大年龄
* @return \think\db\Query
*/
public function scopeAgeBetween($query, $min, $max)
{
return $query->whereBetween('age', [$min, $max]);
}
// ==================== 自定义方法 ====================
/**
* 用户登录处理
*
* @param string $username 用户名
* @param string $password 密码
* @return bool|array 登录成功返回用户信息,失败返回false
*/
public function login(string $username, string $password)
{
// 查找用户(支持用户名、邮箱、手机号登录)
$user = $this->where(function($query) use ($username) {
$query->where('username', $username)
->whereOr('email', $username)
->whereOr('phone', $username);
})->find();
if (!$user) {
return false;
}
// 验证密码
if (!password_verify($password, $user->password)) {
return false;
}
// 更新登录信息
$user->save([
'last_login_time' => time(),
'login_count' => $user->login_count + 1,
]);
return $user->toArray();
}
/**
* 检查用户名是否可用
*
* @param string $username 用户名
* @param int $excludeId 排除的用户ID
* @return bool
*/
public function isUsernameAvailable(string $username, int $excludeId = 0): bool
{
$query = $this->where('username', $username);
if ($excludeId > 0) {
$query->where('id', '<>', $excludeId);
}
return !$query->find();
}
/**
* 获取用户统计信息
*
* @return array
*/
public function getStatistics(): array
{
return [
'total' => $this->count(),
'today_new' => $this->where('create_time', '>=', strtotime('today'))->count(),
'active_today' => $this->where('last_login_time', '>=', strtotime('today'))->count(),
'vip_count' => $this->where('is_vip', 1)->count(),
];
}
/**
* 批量更新用户状态
*
* @param array $ids 用户ID数组
* @param int $status 状态
* @return int 影响的行数
*/
public function batchUpdateStatus(array $ids, int $status): int
{
return $this->whereIn('id', $ids)->update(['status' => $status]);
}
/**
* 搜索用户
*
* @param string $keyword 关键词
* @param array $conditions 额外条件
* @return \think\Collection
*/
public function searchUsers(string $keyword, array $conditions = [])
{
$query = $this->where(function($q) use ($keyword) {
foreach ($this->searchField as $field) {
$q->whereOr($field, 'like', "%{$keyword}%");
}
});
if (!empty($conditions)) {
$query->where($conditions);
}
return $query->select();
}
}5. 其他特定功能的详细示例
php
<?php
namespace app\controller;
use think\facade\Db;
/**
* 高级功能演示控制器
*
* 演示ExtendedController的高级特性和复杂用法
* 包含各种实际业务场景的示例
*/
class AdvancedDemoController extends ExtendedController
{
protected array $allowFields = ['title', 'content', 'category_id', 'tags', 'status', 'author'];
protected array $searchFields = ['title', 'content', 'author'];
protected array $softDeleteConfig = ['field' => 'delete_time', 'type' => 'timestamp'];
// ==================== 复杂查询示例 ====================
/**
* 复杂链式查询示例
* 演示各种查询条件的组合使用
*/
public function complexQuery(): \think\Response
{
try {
// 构建复杂查询
$query = $this->buildQuery('articles')
->where('status', 1) // 基础条件
->where('create_time', '>=', strtotime('-30 days')); // 时间范围
// 动态添加条件
$categoryId = $this->safeParam('category_id', 0, 'int');
if ($categoryId > 0) {
$query->where('category_id', $categoryId);
}
// 关键词搜索(多字段)
$keyword = $this->safeParam('keyword');
if ($keyword) {
$query->where(function($q) use ($keyword) {
$q->whereLike('title', "%{$keyword}%")
->whereOrLike('content', "%{$keyword}%")
->whereOrLike('author', "%{$keyword}%")
->whereOrLike('tags', "%{$keyword}%");
});
}
// 浏览量范围
$minViews = $this->safeParam('min_views', 0, 'int');
$maxViews = $this->safeParam('max_views', 0, 'int');
if ($minViews > 0 || $maxViews > 0) {
if ($minViews > 0 && $maxViews > 0) {
$query->whereBetween('views', [$minViews, $maxViews]);
} elseif ($minViews > 0) {
$query->where('views', '>=', $minViews);
} else {
$query->where('views', '<=', $maxViews);
}
}
// 复杂排序
$sortConfig = [
'new' => ['create_time' => 'desc'],
'hot' => ['views' => 'desc', 'likes' => 'desc'],
'recommend' => ['is_recommend' => 'desc', 'create_time' => 'desc']
];
$sortType = $this->safeParam('sort_type', 'new');
$sort = $sortConfig[$sortType] ?? $sortConfig['new'];
$query->order($sort);
// 字段选择和数据转换
$query->field('id,title,author,cover_image,views,likes,comments,create_time,is_recommend')
->with(['category']); // 关联预加载
$result = $this->safePaginate($query, [
'query' => Request::get() // 保持分页参数
]);
return $this->success($result);
} catch (\Exception $e) {
return $this->error('复杂查询失败: ' . $e->getMessage());
}
}
/**
* 多表联查示例
* 演示复杂的多表关联查询
*/
public function multiTableQuery(): \think\Response
{
try {
// 复杂的多表联查
$query = $this->buildJoinQuery('articles', 'categories', 'articles.category_id = categories.id', 'LEFT')
->join('users', 'articles.user_id = users.id', 'LEFT')
->where('articles.status', 1)
->where('users.status', 1);
// 分类筛选
$categorySlug = $this->safeParam('category');
if ($categorySlug) {
$query->where('categories.slug', $categorySlug);
}
// 作者筛选
$authorName = $this->safeParam('author');
if ($authorName) {
$query->where('users.username', 'like', "%{$authorName}%");
}
// 选择字段
$query->field('
articles.*,
categories.name as category_name,
categories.slug as category_slug,
users.username as author_name,
users.avatar as author_avatar
');
// 排序和分页
$query->order('articles.create_time', 'desc');
$result = $this->safePaginate($query);
return $this->success($result);
} catch (\Exception $e) {
return $this->error('多表查询失败: ' . $e->getMessage());
}
}
/**
* 子查询示例
* 演示如何使用子查询
*/
public function subQueryExample(): \think\Response
{
try {
// 构建子查询:获取每个分类的最新文章
$subQuery = Db::name('articles')
->field('category_id, MAX(create_time) as max_time')
->where('status', 1)
->group('category_id')
->buildSql();
// 主查询
$query = Db::name('articles')
->alias('a')
->join([$subQuery => 'latest'], 'a.category_id = latest.category_id AND a.create_time = latest.max_time')
->join('categories c', 'a.category_id = c.id')
->where('a.status', 1)
->field('a.*, c.name as category_name')
->order('a.create_time', 'desc');
$result = $this->safePaginate($query);
return $this->success($result);
} catch (\Exception $e) {
return $this->error('子查询失败: ' . $e->getMessage());
}
}
// ==================== 批量操作示例 ====================
/**
* 复杂批量更新示例
* 演示带条件判断的批量更新
*/
public function complexBatchUpdate(): \think\Response
{
return $this->batchOperation(
function($ids) {
$status = $this->safeParam('status', 0, 'int');
$categoryId = $this->safeParam('category_id', 0, 'int');
$updateData = ['update_time' => time()];
if ($status !== '') {
$updateData['status'] = $status;
}
if ($categoryId > 0) {
$updateData['category_id'] = $categoryId;
}
// 只有管理员可以批量设置为推荐
$isRecommend = $this->safeParam('is_recommend');
if ($isRecommend !== '' && $this->isAdmin()) {
$updateData['is_recommend'] = (int)$isRecommend;
}
return Db::name('articles')
->whereIn('id', $ids)
->update($updateData);
},
'批量更新成功',
'批量更新失败'
);
}
/**
* 批量导入示例
* 演示如何批量导入数据
*/
public function batchImport(): \think\Response
{
try {
$importData = $this->safeParam('data', [], 'array');
if (empty($importData)) {
return $this->error('导入数据不能为空');
}
$successCount = 0;
$errorCount = 0;
$errors = [];
// 在事务中执行批量导入
Db::transaction(function() use ($importData, &$successCount, &$errorCount, &$errors) {
foreach ($importData as $index => $item) {
try {
// 数据验证和预处理
$validatedData = $this->validateImportItem($item);
if (!$validatedData) {
$errorCount++;
$errors[] = "第{$index}行数据验证失败";
continue;
}
// 执行插入
$result = Db::name('articles')->insert($validatedData);
if ($result) {
$successCount++;
} else {
$errorCount++;
$errors[] = "第{$index}行插入失败";
}
} catch (\Exception $e) {
$errorCount++;
$errors[] = "第{$index}行处理失败: " . $e->getMessage();
}
}
});
return $this->success([
'success_count' => $successCount,
'error_count' => $errorCount,
'errors' => $errors
], "导入完成,成功{$successCount}条,失败{$errorCount}条");
} catch (\Exception $e) {
return $this->error('导入失败: ' . $e->getMessage());
}
}
// ==================== 事务操作示例 ====================
/**
* 复杂事务操作示例
* 演示多个表的事务操作
*/
public function complexTransaction(): \think\Response
{
try {
$articleData = $this->safeParam('article', [], 'array');
$tagData = $this->safeParam('tags', [], 'array');
// 在事务中执行复杂操作
$result = Db::transaction(function() use ($articleData, $tagData) {
// 1. 插入文章主表
$articleId = Db::name('articles')->insertGetId($articleData);
if (!$articleId) {
throw new \Exception('文章创建失败');
}
// 2. 处理标签
if (!empty($tagData)) {
$tagRelations = [];
foreach ($tagData as $tagName) {
// 查找或创建标签
$tag = Db::name('tags')->where('name', $tagName)->find();
if (!$tag) {
$tagId = Db::name('tags')->insertGetId(['name' => $tagName]);
} else {
$tagId = $tag['id'];
}
$tagRelations[] = [
'article_id' => $articleId,
'tag_id' => $tagId,
'create_time' => time()
];
}
// 插入标签关系
if (!empty($tagRelations)) {
Db::name('article_tag')->insertAll($tagRelations);
}
}
// 3. 更新分类文章计数
if (!empty($articleData['category_id'])) {
Db::name('categories')
->where('id', $articleData['category_id'])
->inc('article_count')
->update();
}
return $articleId;
});
return $this->success(['id' => $result], '操作成功');
} catch (\Exception $e) {
return $this->error('操作失败: ' . $e->getMessage());
}
}
// ==================== 高级统计示例 ====================
/**
* 高级统计报表
* 演示复杂的数据统计分析
*/
public function advancedStats(): \think\Response
{
try {
$startDate = $this->safeParam('start_date', date('Y-m-01'));
$endDate = $this->safeParam('end_date', date('Y-m-d'));
// 1. 基础统计
$basicStats = $this->getStats('articles', [['status', '=', 1]]);
// 2. 日期范围统计
$dateStats = $this->getDateRangeStats('articles', 'create_time', $startDate, $endDate, 'Y-m-d');
// 3. 分类统计
$categoryStats = Db::name('articles')
->alias('a')
->join('categories c', 'a.category_id = c.id')
->where('a.status', 1)
->field('c.name, COUNT(a.id) as count, SUM(a.views) as total_views')
->group('a.category_id')
->select();
// 4. 作者统计
$authorStats = Db::name('articles')
->where('status', 1)
->field('author, COUNT(*) as count, AVG(views) as avg_views, SUM(views) as total_views')
->group('author')
->order('count', 'desc')
->limit(10)
->select();
// 5. 趋势分析
$trendStats = Db::name('articles')
->field("
DATE_FORMAT(FROM_UNIXTIME(create_time), '%Y-%m') as month,
COUNT(*) as article_count,
SUM(views) as total_views,
AVG(views) as avg_views
")
->where('status', 1)
->where('create_time', '>=', strtotime('-6 months'))
->group('month')
->order('month')
->select();
$stats = [
'basic' => $basicStats,
'date_range' => $dateStats,
'categories' => $categoryStats,
'authors' => $authorStats,
'trends' => $trendStats,
'summary' => [
'total_articles' => $basicStats['total'],
'total_views' => $basicStats['sum'],
'avg_views' => $basicStats['avg'],
'date_range' => "{$startDate} 至 {$endDate}"
]
];
return $this->success($stats);
} catch (\Exception $e) {
return $this->error('统计失败: ' . $e->getMessage());
}
}
// ==================== 辅助方法 ====================
/**
* 验证导入数据项
*/
private function validateImportItem(array $item): array
{
$required = ['title', 'content', 'author'];
foreach ($required as $field) {
if (empty($item[$field])) {
return false;
}
}
// 数据过滤和处理
return [
'title' => $this->filterValue($item['title'], 'string'),
'content' => $this->filterValue($item['content'], 'string'),
'author' => $this->filterValue($item['author'], 'string'),
'status' => $item['status'] ?? 1,
'create_time' => time(),
'update_time' => time()
];
}
/**
* 检查管理员权限
*/
private function isAdmin(): bool
{
// 这里实现你的权限检查逻辑
return true; // 示例
}
/**
* 自定义构建查询条件
*/
protected function buildWhere(array $customWhere = []): array
{
$where = parent::buildWhere($customWhere);
// 添加文章特定的查询条件
$isRecommend = $this->safeParam('is_recommend');
if ($isRecommend !== '') {
$where[] = ['is_recommend', '=', (int)$isRecommend];
}
$minLikes = $this->safeParam('min_likes', 0, 'int');
$maxLikes = $this->safeParam('max_likes', 0, 'int');
if ($minLikes > 0) {
$where[] = ['likes', '>=', $minLikes];
}
if ($maxLikes > 0) {
$where[] = ['likes', '<=', $maxLikes];
}
// 标签搜索
$tag = $this->safeParam('tag');
if ($tag) {
// 这里可以使用子查询或者联查来处理标签搜索
$where[] = ['tags', 'like', "%{$tag}%"];
}
return $where;
}
}以上就是完整的四个部分内容。这个增强控制器提供了:
完整的安全防护 - XSS过滤、SQL注入防护、字段白名单
完善的软删除支持 - 与ThinkPHP8完全兼容
丰富的查询方法 - 简单查询、复杂链式查询、多表联查
强大的批量操作 - 事务支持的批量处理
详细的错误处理 - 完善的异常捕获和日志记录
灵活的扩展性 - 支持自定义回调和重写方法
每个方法都有详细的注释说明,可以直接在实际项目中使用。
评论区