'json', ]; /** * 根据配置执行统计计算 * * @param array $params 参数数组,包含:page, page_size, show_type * @return array 返回统计结果数组 * @throws \Exception */ public function calculateStatistics($params = []) { $page = isset($params['page']) ? (int) $params['page'] : 1; $pageSize = isset($params['page_size']) ? (int) $params['page_size'] : 10; $showType = isset($params['show_type']) ? $params['show_type'] : 'statistics'; if (!in_array($showType, ['statistics', 'list'])) { $showType = 'statistics'; } $configJson = $this->config_json; if (empty($configJson)) { throw new \Exception('配置数据为空'); } // 如果 config_json 是字符串,尝试解析 if (is_string($configJson)) { $configJson = json_decode($configJson, true); if (json_last_error() !== JSON_ERROR_NONE) { throw new \Exception('配置 JSON 格式错误:' . json_last_error_msg()); } } // 检查数据结构 if (!isset($configJson['data_source'])) { throw new \Exception('配置数据缺少 data_source 字段,当前配置键:' . implode(', ', array_keys($configJson))); } // 获取主模型 $mainModelName = $configJson['data_source']['main_model'] ?? ''; if (empty($mainModelName)) { $dataSourceKeys = isset($configJson['data_source']) ? implode(', ', array_keys($configJson['data_source'])) : '无'; throw new \Exception('主模型名称未配置,data_source 中的键:' . $dataSourceKeys); } $mainModel = $this->getModel($mainModelName); if (empty($mainModel)) { throw new \Exception('主模型不存在:' . $mainModelName . ',可用的模型:user, company, course_sign, course, course_type'); } $query = $mainModel::query(); // 获取主模型表名 $tableName = $mainModel::make()->getTable(); // 加载关联模型 $relations = $configJson['data_source']['relations'] ?? []; if (!empty($relations)) { foreach ($relations as $relation) { $query->with($relation); } } // 应用条件 $conditions = $configJson['conditions'] ?? []; if (!empty($conditions['items'])) { $logic = $conditions['logic'] ?? 'and'; if ($logic === 'or') { $query->where(function ($q) use ($conditions, $tableName) { foreach ($conditions['items'] as $index => $item) { if ($index === 0) { $this->applyCondition($q, $item, 'and', 0, $tableName); } else { $q->orWhere(function ($subQ) use ($item, $tableName) { $this->applyCondition($subQ, $item, 'and', 0, $tableName); }); } } }); } else { foreach ($conditions['items'] as $item) { $this->applyCondition($query, $item, 'and', 0, $tableName); } } } // 执行统计 $statistics = $configJson['statistics'] ?? []; $statisticsType = $statistics['type'] ?? 'count'; $groupBy = $statistics['group_by'] ?? null; // 确保空字符串也被视为不分组 $groupBy = !empty($groupBy) ? $groupBy : null; // 保存原始查询用于获取列表 $listQuery = clone $query; if ($groupBy) { // 分组统计 $groupParts = explode('.', $groupBy); if (count($groupParts) > 1) { // 关联模型字段分组,需要 join $relationName = $groupParts[0]; $fieldName = $groupParts[1]; $relationModel = $this->getRelationModel($mainModel, $relationName); if ($relationModel) { $relationTable = $relationModel::make()->getTable(); $relationKey = $this->getRelationKey($mainModel, $relationName); $query->join($relationTable, $tableName . '.' . $relationKey, '=', $relationTable . '.id'); $selectFields = [$relationTable . '.' . $fieldName . ' as group_value']; } else { $selectFields = [$groupBy . ' as group_value']; } } else { // 主模型字段分组 $selectFields = [$tableName . '.' . $groupBy . ' as group_value']; } // 根据统计类型构建 SQL $statisticsField = $statistics['field'] ?? null; if ($statisticsType === 'sum' && $statisticsField) { // 处理关联模型字段 $fieldParts = explode('.', $statisticsField); if (count($fieldParts) > 1) { $fieldRelationName = $fieldParts[0]; $fieldFieldName = $fieldParts[1]; $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); if ($fieldRelationModel) { $fieldRelationTable = $fieldRelationModel::make()->getTable(); $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); // 如果还没有 join,需要 join $query->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); $selectFields[] = DB::raw('SUM(' . $fieldRelationTable . '.' . $fieldFieldName . ') as total'); } else { $selectFields[] = DB::raw('SUM(' . $tableName . '.' . $statisticsField . ') as total'); } } else { $selectFields[] = DB::raw('SUM(' . $tableName . '.' . $statisticsField . ') as total'); } } elseif ($statisticsType === 'max' && $statisticsField) { // 处理关联模型字段 $fieldParts = explode('.', $statisticsField); if (count($fieldParts) > 1) { $fieldRelationName = $fieldParts[0]; $fieldFieldName = $fieldParts[1]; $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); if ($fieldRelationModel) { $fieldRelationTable = $fieldRelationModel::make()->getTable(); $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); // 如果还没有 join,需要 join $query->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); $selectFields[] = DB::raw('MAX(' . $fieldRelationTable . '.' . $fieldFieldName . ') as total'); } else { $selectFields[] = DB::raw('MAX(' . $tableName . '.' . $statisticsField . ') as total'); } } else { $selectFields[] = DB::raw('MAX(' . $tableName . '.' . $statisticsField . ') as total'); } } elseif ($statisticsType === 'min' && $statisticsField) { // 处理关联模型字段 $fieldParts = explode('.', $statisticsField); if (count($fieldParts) > 1) { $fieldRelationName = $fieldParts[0]; $fieldFieldName = $fieldParts[1]; $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); if ($fieldRelationModel) { $fieldRelationTable = $fieldRelationModel::make()->getTable(); $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); // 如果还没有 join,需要 join $query->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); $selectFields[] = DB::raw('MIN(' . $fieldRelationTable . '.' . $fieldFieldName . ') as total'); } else { $selectFields[] = DB::raw('MIN(' . $tableName . '.' . $statisticsField . ') as total'); } } else { $selectFields[] = DB::raw('MIN(' . $tableName . '.' . $statisticsField . ') as total'); } } elseif ($statisticsType === 'count_distinct' && isset($statistics['distinct_field'])) { // 去重数量统计 $distinctField = $statistics['distinct_field']; // 处理关联模型字段 $fieldParts = explode('.', $distinctField); if (count($fieldParts) > 1) { $fieldRelationName = $fieldParts[0]; $fieldFieldName = $fieldParts[1]; $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); if ($fieldRelationModel) { $fieldRelationTable = $fieldRelationModel::make()->getTable(); $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); // 如果还没有 join,需要 join $query->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); $selectFields[] = DB::raw('COUNT(DISTINCT ' . $fieldRelationTable . '.' . $fieldFieldName . ') as total'); } else { $selectFields[] = DB::raw('COUNT(DISTINCT ' . $tableName . '.' . $distinctField . ') as total'); } } else { $selectFields[] = DB::raw('COUNT(DISTINCT ' . $tableName . '.' . $distinctField . ') as total'); } } else { // count 统计总数量 $selectFields[] = DB::raw('COUNT(*) as total'); } $query->select($selectFields); if (count($groupParts) > 1) { $query->groupBy($relationTable . '.' . $fieldName); } else { $query->groupBy($tableName . '.' . $groupBy); } } else { // 不分组统计,获取所有数据列表 // 列表查询保持原样,获取所有符合条件的记录 } // 排序(分组统计时对分组结果排序,不分组时对列表排序) if (!empty($statistics['order_by'])) { $orderField = $statistics['order_by']['field'] ?? ($groupBy ? 'total' : 'id'); $orderDirection = $statistics['order_by']['direction'] ?? 'desc'; $query->orderBy($orderField, $orderDirection); if (!$groupBy) { // 不分组时,列表查询也需要排序 $listQuery->orderBy($orderField, $orderDirection); } } else { // 没有指定排序时,不分组情况使用默认排序 if (!$groupBy) { $listQuery->orderBy('id', 'desc'); } } // 获取统计查询的 SQL 语句(在执行查询前) $statisticsSql = $query->toSql(); $statisticsBindings = $query->getBindings(); $statisticsSqlFull = $this->getFullSql($statisticsSql, $statisticsBindings); // 获取分组统计结果列表(分页) // 先获取所有结果用于计算统计结果 $allResults = $query->get(); // 对结果进行分页处理 $totalCount = $allResults->count(); $pagedResults = $allResults->slice(($page - 1) * $pageSize, $pageSize); // 格式化分组统计结果 $data = []; foreach ($pagedResults as $item) { $row = [ 'group_value' => $item->group_value ?? null, 'total' => round($item->total, $this->decimal_places) ]; $data[] = $row; } // 计算统计结果 $statisticsResult = null; $pagination = null; if ($groupBy) { // 分组统计:根据统计类型计算最终结果 $statisticsField = $statistics['field'] ?? null; if ($statisticsType === 'sum' && $statisticsField) { // 分组求和时,统计结果是所有分组的总和 $allTotalValue = 0; foreach ($allResults as $item) { $allTotalValue += $item->total; } $statisticsResult = round($allTotalValue, $this->decimal_places); } elseif ($statisticsType === 'max' && $statisticsField) { // 分组最大值时,统计结果是所有分组中的最大值 $maxValue = null; foreach ($allResults as $item) { if ($maxValue === null || $item->total > $maxValue) { $maxValue = $item->total; } } $statisticsResult = $maxValue !== null ? round($maxValue, $this->decimal_places) : 0; } elseif ($statisticsType === 'min' && $statisticsField) { // 分组最小值时,统计结果是所有分组中的最小值 $minValue = null; foreach ($allResults as $item) { if ($minValue === null || $item->total < $minValue) { $minValue = $item->total; } } $statisticsResult = $minValue !== null ? round($minValue, $this->decimal_places) : 0; } else { // 分组计数时,统计结果是所有分组的计数总和 $allTotalValue = 0; foreach ($allResults as $item) { $allTotalValue += $item->total; } $statisticsResult = $allTotalValue; } // 分页信息 $pagination = [ 'current_page' => $page, 'page_size' => $pageSize, 'total' => $totalCount, 'total_pages' => ceil($totalCount / $pageSize) ]; } else { // 不分组统计:先获取 SQL,再执行查询 // 克隆查询用于获取 SQL(避免执行后无法获取) $calcQuery = clone $listQuery; $listQueryForData = clone $listQuery; // 根据统计类型计算统计值 $statisticsField = $statistics['field'] ?? null; if ($statisticsType === 'sum' && $statisticsField) { // 处理关联模型字段 $fieldParts = explode('.', $statisticsField); if (count($fieldParts) > 1) { $fieldRelationName = $fieldParts[0]; $fieldFieldName = $fieldParts[1]; $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); if ($fieldRelationModel) { $fieldRelationTable = $fieldRelationModel::make()->getTable(); $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); $calcQuery->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); $statisticsResult = round($calcQuery->sum($fieldRelationTable . '.' . $fieldFieldName), $this->decimal_places); } else { $statisticsResult = round($listQuery->sum($statisticsField), $this->decimal_places); } } else { $statisticsResult = round($listQuery->sum($statisticsField), $this->decimal_places); } } elseif ($statisticsType === 'max' && $statisticsField) { // 处理关联模型字段 $fieldParts = explode('.', $statisticsField); if (count($fieldParts) > 1) { $fieldRelationName = $fieldParts[0]; $fieldFieldName = $fieldParts[1]; $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); if ($fieldRelationModel) { $fieldRelationTable = $fieldRelationModel::make()->getTable(); $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); $calcQuery->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); $statisticsResult = round($calcQuery->max($fieldRelationTable . '.' . $fieldFieldName), $this->decimal_places); } else { $statisticsResult = round($listQuery->max($statisticsField), $this->decimal_places); } } else { $statisticsResult = round($listQuery->max($statisticsField), $this->decimal_places); } } elseif ($statisticsType === 'min' && $statisticsField) { // 处理关联模型字段 $fieldParts = explode('.', $statisticsField); if (count($fieldParts) > 1) { $fieldRelationName = $fieldParts[0]; $fieldFieldName = $fieldParts[1]; $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); if ($fieldRelationModel) { $fieldRelationTable = $fieldRelationModel::make()->getTable(); $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); $calcQuery->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); $statisticsResult = round($calcQuery->min($fieldRelationTable . '.' . $fieldFieldName), $this->decimal_places); } else { $statisticsResult = round($listQuery->min($statisticsField), $this->decimal_places); } } else { $statisticsResult = round($listQuery->min($statisticsField), $this->decimal_places); } } elseif ($statisticsType === 'count_distinct' && isset($statistics['distinct_field'])) { // 去重数量统计 $distinctField = $statistics['distinct_field']; // 处理关联模型字段 $fieldParts = explode('.', $distinctField); if (count($fieldParts) > 1) { $fieldRelationName = $fieldParts[0]; $fieldFieldName = $fieldParts[1]; $fieldRelationModel = $this->getRelationModel($mainModel, $fieldRelationName); if ($fieldRelationModel) { $fieldRelationTable = $fieldRelationModel::make()->getTable(); $fieldRelationKey = $this->getRelationKey($mainModel, $fieldRelationName); $calcQuery->leftJoin($fieldRelationTable, $tableName . '.' . $fieldRelationKey, '=', $fieldRelationTable . '.id'); $statisticsResult = $calcQuery->selectRaw('COUNT(DISTINCT ' . $fieldRelationTable . '.' . $fieldFieldName . ') as total')->value('total') ?? 0; } else { $statisticsResult = $listQuery->selectRaw('COUNT(DISTINCT ' . $distinctField . ') as total')->value('total') ?? 0; } } else { $statisticsResult = $listQuery->selectRaw('COUNT(DISTINCT ' . $tableName . '.' . $distinctField . ') as total')->value('total') ?? 0; } } else { // count 统计总数量 $statisticsResult = $listQuery->count(); } // 获取统计查询的 SQL(在计算完统计值之后,确保包含所有修改) $statisticsSqlForCalc = $calcQuery->toSql(); $statisticsBindingsForCalc = $calcQuery->getBindings(); $statisticsSqlFull = $this->getFullSql($statisticsSqlForCalc, $statisticsBindingsForCalc); // 获取列表总数(用于分页) $listTotalCount = $listQueryForData->count(); // 不分组时,获取分页数据作为列表 $listResults = $listQueryForData->skip(($page - 1) * $pageSize)->take($pageSize)->get(); $data = []; foreach ($listResults as $item) { $data[] = $item->toArray(); } // 分页信息 $pagination = [ 'current_page' => $page, 'page_size' => $pageSize, 'total' => $listTotalCount, 'total_pages' => ceil($listTotalCount / $pageSize) ]; } // 根据显示类型决定返回的数据 if ($showType === 'statistics') { // 只返回统计数据 return [ 'total' => $statisticsResult, 'sql' => $statisticsSqlFull ]; } else { // 只返回列表数据 return [ 'list' => $data, 'pagination' => $pagination, 'sql' => $statisticsSqlFull ]; } } /** * 获取模型实例 */ private function getModel($modelName) { $models = [ 'user' => User::class, 'company' => Company::class, 'course_sign' => CourseSign::class, 'course' => Course::class, 'course_type' => CourseType::class, ]; return $models[$modelName] ?? null; } /** * 应用查询条件 */ private function applyCondition($query, $condition, $logic, $index, $tableName = null) { $key = $condition['key'] ?? ''; $operator = $condition['operator'] ?? 'eq'; $value = $condition['value'] ?? ''; if (empty($key)) { return; } // 处理关联模型的字段 $keyParts = explode('.', $key); if (count($keyParts) > 1) { // 关联模型字段,使用 whereHas $relationName = $keyParts[0]; $fieldName = $keyParts[1]; $query->whereHas($relationName, function ($q) use ($fieldName, $operator, $value) { $this->applyOperator($q, $fieldName, $operator, $value); }); return; } // 主模型字段,添加表名前缀避免字段歧义 $qualifiedKey = $tableName ? $tableName . '.' . $key : $key; $this->applyOperator($query, $qualifiedKey, $operator, $value); } /** * 获取关联模型 */ private function getRelationModel($mainModel, $relationName) { $model = new $mainModel(); if (method_exists($model, $relationName)) { $relation = $model->$relationName(); return get_class($relation->getRelated()); } return null; } /** * 获取关联键名 */ private function getRelationKey($mainModel, $relationName) { $model = new $mainModel(); if (method_exists($model, $relationName)) { $relation = $model->$relationName(); return $relation->getForeignKeyName(); } return 'id'; } /** * 获取完整的 SQL 语句(包含绑定参数) */ private function getFullSql($sql, $bindings) { if (empty($bindings)) { return $sql; } $fullSql = $sql; foreach ($bindings as $binding) { $value = is_numeric($binding) ? $binding : "'" . addslashes($binding) . "'"; $fullSql = preg_replace('/\?/', $value, $fullSql, 1); } return $fullSql; } /** * 应用操作符 */ private function applyOperator($query, $key, $operator, $value) { switch ($operator) { case 'eq': $query->where($key, $value); break; case 'neq': $query->where($key, '!=', $value); break; case 'gt': $query->where($key, '>', $value); break; case 'egt': $query->where($key, '>=', $value); break; case 'lt': $query->where($key, '<', $value); break; case 'elt': $query->where($key, '<=', $value); break; case 'like': $query->where($key, 'like', '%' . $value . '%'); break; case 'notlike': $query->where($key, 'not like', '%' . $value . '%'); break; case 'in': $array = explode(',', $value); $query->whereIn($key, $array); break; case 'notin': $array = explode(',', $value); $query->whereNotIn($key, $array); break; case 'between': list($from, $to) = explode(',', $value); if (!empty($from) && !empty($to)) { $query->whereBetween($key, [$from, $to]); } break; case 'notbetween': list($from, $to) = explode(',', $value); if (!empty($from) && !empty($to)) { $query->whereNotBetween($key, [$from, $to]); } break; case 'isnull': $query->whereNull($key); break; case 'isnotnull': $query->whereNotNull($key); break; } } }