You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

588 lines
26 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Models;
use Illuminate\Support\Facades\DB;
class StatisticsConfig extends SoftDeletesModel
{
protected $casts = [
'config_json' => '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;
}
}
}