侧边栏壁纸
博主头像
云BLOG 博主等级

行动起来,活在当下

  • 累计撰写 318 篇文章
  • 累计创建 6 个标签
  • 累计收到 0 条评论

目 录CONTENT

文章目录

一个完整、详细的增强控制器,包含完整的软删除、真删除功能,以及各种查询方法的详细实现和注释。

Administrator
2025-09-25 / 0 评论 / 0 点赞 / 7 阅读 / 0 字

一个完整、详细的增强控制器,包含完整的软删除、真删除功能,以及各种查询方法的详细实现和注释。

完整的增强控制器,它继承自ThinkPHP8的基础控制器,并集成以下功能:

  1. 安全防护(XSS过滤、SQL注入防护、字段白名单)

  2. 软删除和强制删除(与ThinkPHP8模型软删除兼容)

  3. 快速CRUD方法(简单查询、复杂链式查询、批量操作)

  4. 完善的错误处理和日志记录

注意:由于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;
    }
}

以上就是完整的四个部分内容。这个增强控制器提供了:

  1. 完整的安全防护 - XSS过滤、SQL注入防护、字段白名单

  2. 完善的软删除支持 - 与ThinkPHP8完全兼容

  3. 丰富的查询方法 - 简单查询、复杂链式查询、多表联查

  4. 强大的批量操作 - 事务支持的批量处理

  5. 详细的错误处理 - 完善的异常捕获和日志记录

  6. 灵活的扩展性 - 支持自定义回调和重写方法

每个方法都有详细的注释说明,可以直接在实际项目中使用。

0

评论区