master
cody 5 months ago
parent 68e30d4856
commit b9508c85ff

@ -17,6 +17,7 @@ use App\Models\CustomFormField;
use App\Models\Department;
use App\Models\ParameterDetail;
use App\Models\SupplyDemand;
use App\Models\TimeEvent;
use App\Models\User;
use App\Repositories\DoorRepository;
use App\Repositories\EntranceRepository;
@ -118,11 +119,6 @@ class OtherController extends CommonController
*/
public function homeV2()
{
// 校友总数
$schoolmate['schoolmate_total'] = User::where('is_schoolmate', 1)->count();
// 2025年校友数
$schoolmate['schoolmate_year'] = User::where('is_schoolmate', 1)->where('created_at', 'like', '%' . date('Y') . '%')->count();
// 课程统计
$courseTypes = CourseType::where('is_chart', 1)->get();
$start_date = '2020-01-01';
@ -150,8 +146,7 @@ class OtherController extends CommonController
}
// 全国数据
$countryArea = Company::groupBy('company_city')->whereNotNull('company_city')
->get(['company_city']);
$countryArea = Company::groupBy('company_city')->whereNotNull('company_city')->get(['company_city']);
$country = [];
foreach ($countryArea as $item) {
$country[] = [
@ -163,34 +158,13 @@ class OtherController extends CommonController
}
// 本月课程
$monthCourses = Course::with('teacher')->where('start_date', 'like', '%' . date('Y-m') . '%')->get();
// 投后企业
$yuanhe['yh_invested_total'] = Company::where('is_yh_invested', 1)->count();
// 元和员工参与企业
$companyNameKeyword = ['元禾控股', '元禾原点', '元禾厚望', '元禾重元', '元禾璞华', '元禾谷风', '元禾绿柳', '元禾辰坤', '元禾沙湖', '禾裕集团', '苏州科服', '信诚管理咨询', '集成电路公司', '常州团队', '国企元禾'];
// 获取公司名字包含$companyNameKeyword任意数据的公司需要模糊匹配
$yuanhe['yh_join_company_total'] = Company::where(function ($query) use ($companyNameKeyword) {
foreach ($companyNameKeyword as $item) {
$query->orWhere('company_name', 'like', '%' . $item . '%');
}
})->count();
// 全市干部参与企业
$yuanhe['yh_ganbu_total'] = Company::whereHas('users', function ($query) {
$query->where('from', '跟班学员');
})->count();
// 三个全覆盖
// 苏州头部企业
$cover['head_total'] = 0;
// 高层次人才
$cover['high_total'] = 0;
// 重点上市公司
$cover['stock_total'] = 0;
// 时间轴
$time_axis = [];
$time_axis = TimeEvent::orderBy('sort', 'asc')->orderBy('id', 'desc')->get();
// 动态信息
$article['xiaoyou'] = Article::where('type', 1)->limit(7)->orderBy('created_at', 'desc')->get();
$article['yejie'] = Article::where('type', 2)->limit(7)->orderBy('created_at', 'desc')->get();
$article['supply_demands'] = SupplyDemand::limit(7)->orderBy('created_at', 'desc')->get();
return $this->success(compact('courseTypes', 'schoolmate', 'suzhou', 'country', 'monthCourses', 'yuanhe', 'time_axis'));
return $this->success(compact('courseTypes', 'suzhou', 'country', 'monthCourses', 'time_axis', 'article'));
}
/**

@ -0,0 +1,819 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Exports\BaseExport;
use App\Helpers\ResponseCode;
use App\Models\AppointmentType;
use App\Models\Article;
use App\Models\Book;
use App\Models\Calendar;
use App\Models\Company;
use App\Models\CourseContentEvaluationAsk;
use App\Models\CourseContentEvaluationForm;
use App\Models\CustomForm;
use App\Models\CustomFormField;
use App\Models\EmailTemplate;
use App\Models\StatisticsConfig;
use App\Models\SupplyDemand;
use App\Models\TimeEvent;
use App\Models\User;
use App\Models\CourseSign;
use App\Models\Course;
use App\Models\CourseType;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Validator;
use Maatwebsite\Excel\Facades\Excel;
use Rap2hpoutre\FastExcel\FastExcel;
class StatisticsConfigController extends BaseController
{
/**
* 构造函数
*/
public function __construct()
{
parent::__construct(new StatisticsConfig());
}
/**
* @OA\Get(
* path="/api/admin/statistics-config/index",
* tags={"动态统计"},
* summary="列表",
* description="",
* @OA\Parameter(name="is_export", in="query", @OA\Schema(type="string"), required=false, description="是否导出0否1是"),
* @OA\Parameter(name="export_fields", in="query", @OA\Schema(type="string"), required=false, description="需要导出的字段数组"),
* @OA\Parameter(name="filter", in="query", @OA\Schema(type="string"), required=false, description="查询条件。数组"),
* @OA\Parameter(name="show_relation", in="query", @OA\Schema(type="string"), required=false, description="需要输出的关联关系数组包括teachercourseSettingscoursePeriods"),
* @OA\Parameter(name="page_size", in="query", @OA\Schema(type="string"), required=false, description="每页显示的条数"),
* @OA\Parameter(name="page", in="query", @OA\Schema(type="string"), required=false, description="页码"),
* @OA\Parameter(name="sort_name", in="query", @OA\Schema(type="string"), required=false, description="排序字段名字"),
* @OA\Parameter(name="sort_type", in="query", @OA\Schema(type="string"), required=false, description="排序类型"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"),
* @OA\Response(
* response="200",
* description="暂无"
* )
* )
*/
public function index()
{
$all = request()->all();
$list = $this->model->where(function ($query) use ($all) {
if (isset($all['filter']) && !empty($all['filter'])) {
foreach ($all['filter'] as $condition) {
$key = $condition['key'] ?? null;
$op = $condition['op'] ?? null;
$value = $condition['value'] ?? null;
if (!isset($key) || !isset($op) || !isset($value)) {
continue;
}
// 等于
if ($op == 'eq') {
$query->where($key, $value);
}
// 不等于
if ($op == 'neq') {
$query->where($key, '!=', $value);
}
// 大于
if ($op == 'gt') {
$query->where($key, '>', $value);
}
// 大于等于
if ($op == 'egt') {
$query->where($key, '>=', $value);
}
// 小于
if ($op == 'lt') {
$query->where($key, '<', $value);
}
// 小于等于
if ($op == 'elt') {
$query->where($key, '<=', $value);
}
// 模糊搜索
if ($op == 'like') {
$query->where($key, 'like', '%' . $value . '%');
}
// 否定模糊搜索
if ($op == 'notlike') {
$query->where($key, 'not like', '%' . $value . '%');
}
// 范围搜索
if ($op == 'range') {
list($from, $to) = explode(',', $value);
if (empty($from) || empty($to)) {
continue;
}
$query->whereBetween($key, [$from, $to]);
}
}
}
})->orderBy($all['sort_name'] ?? 'id', $all['sort_type'] ?? 'desc');
if (isset($all['is_export']) && !empty($all['is_export'])) {
$list = $list->get()->toArray();
$export_fields = $all['export_fields'] ?? [];
// 导出文件名字
$tableName = $this->model->getTable();
$filename = (new CustomForm())->getTableComment($tableName);
return Excel::download(new BaseExport($export_fields, $list, $tableName), $filename . date('YmdHis') . '.xlsx');
} else {
// 输出
$list = $list->paginate($all['page_size'] ?? 20);
}
return $this->success($list);
}
/**
* @OA\Get(
* path="/api/admin/statistics-config/show",
* tags={"动态统计"},
* summary="详情",
* description="",
* @OA\Parameter(name="id", in="query", @OA\Schema(type="string"), required=true, description="id"),
* @OA\Parameter(name="show_relation", in="query", @OA\Schema(type="string"), required=false, description="需要输出的关联关系数组,填写输出指定数据"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"),
* @OA\Response(
* response="200",
* description="暂无"
* )
* )
*/
public function show()
{
$all = \request()->all();
$messages = [
'id.required' => 'Id必填',
];
$validator = Validator::make($all, [
'id' => 'required'
], $messages);
if ($validator->fails()) {
return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
}
$detail = $this->model->find($all['id']);
return $this->success($detail);
}
/**
* @OA\Post(
* path="/api/admin/statistics-config/save",
* tags={"动态统计"},
* summary="保存统计数据配置",
* description="根据传入的id决定是更新现有配置还是新增新的配置。",
* @OA\Parameter(name="id", in="query", @OA\Schema(type="integer"), required=false, description="配置ID存在则更新不存在则新增"),
* @OA\Parameter(name="name", in="query", @OA\Schema(type="string"), required=true, description="名字"),
* @OA\Parameter(name="key", in="query", @OA\Schema(type="string"), required=false, description="标识key"),
* @OA\Parameter(name="decimal_places", in="query", @OA\Schema(type="integer"), required=false, description="小数点位数默认0"),
* @OA\Parameter(name="description", in="query", @OA\Schema(type="string"), required=false, description="描述"),
* @OA\Parameter(name="config_json", in="query", @OA\Schema(type="string"), required=false, description="配置json包含数据来源、条件设置、统计方式等配置详见配置文档"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"),
* @OA\Response(
* response="200",
* description="操作成功"
* )
* )
*/
public function save()
{
$all = \request()->all();
DB::beginTransaction();
try {
if (isset($all['id'])) {
$model = $this->model->find($all['id']);
if (empty($model)) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '数据不存在']);
}
} else {
$model = $this->model;
$all['admin_id'] = $this->getUserId();
$all['department_id'] = $this->getUser()->department_id;
}
$original = $model->getOriginal();
$model->fill($all);
$model->save();
DB::commit();
return $this->success($model);
} catch (\Exception $exception) {
DB::rollBack();
return $this->fail([$exception->getCode(), $exception->getMessage()]);
}
}
/**
* @OA\Get(
* path="/api/admin/statistics-config/destroy",
* tags={"动态统计"},
* summary="删除",
* description="",
* @OA\Parameter(name="id", in="query", @OA\Schema(type="string"), required=true, description="id"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="token"),
* @OA\Response(
* response="200",
* description="暂无"
* )
* )
*/
public function destroy()
{
return parent::destroy();
}
/**
* @OA\Get(
* path="/api/admin/statistics-config/calculate",
* tags={"动态统计"},
* summary="根据配置key执行统计",
* description="根据配置的key获取对应的统计配置然后根据config_json的配置执行统计查询返回数据列表和统计结果",
* @OA\Parameter(name="key", in="query", @OA\Schema(type="string"), required=true, description="配置的key标识"),
* @OA\Parameter(name="page", in="query", @OA\Schema(type="integer"), required=false, description="页码默认1"),
* @OA\Parameter(name="page_size", in="query", @OA\Schema(type="integer"), required=false, description="每页显示的条数默认10"),
* @OA\Parameter(name="token", in="query", @OA\Schema(type="string"), required=true, description="认证token"),
* @OA\Response(
* response="200",
* description="返回统计数据"
* )
* )
*/
public function calculate()
{
$all = \request()->all();
$messages = [
'key.required' => 'key必填',
];
$validator = Validator::make($all, [
'key' => 'required'
], $messages);
if ($validator->fails()) {
return $this->fail([ResponseCode::ERROR_PARAMETER, implode(',', $validator->errors()->all())]);
}
// 获取分页参数
$page = isset($all['page']) ? (int) $all['page'] : 1;
$pageSize = isset($all['page_size']) ? (int) $all['page_size'] : 10;
// 根据key查找配置
$config = $this->model->where('key', $all['key'])->first();
if (empty($config)) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '配置不存在']);
}
$configJson = $config->config_json;
if (empty($configJson)) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '配置数据为空']);
}
// 如果 config_json 是字符串,尝试解析
if (is_string($configJson)) {
$configJson = json_decode($configJson, true);
if (json_last_error() !== JSON_ERROR_NONE) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '配置 JSON 格式错误:' . json_last_error_msg()]);
}
}
try {
// 检查数据结构
if (!isset($configJson['data_source'])) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '配置数据缺少 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'])) : '无';
return $this->fail([ResponseCode::ERROR_BUSINESS, '主模型名称未配置data_source 中的键:' . $dataSourceKeys]);
}
$mainModel = $this->getModel($mainModelName);
if (empty($mainModel)) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '主模型不存在:' . $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, $config->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, $config->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, $config->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, $config->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), $config->decimal_places);
} else {
$statisticsResult = round($listQuery->sum($statisticsField), $config->decimal_places);
}
} else {
$statisticsResult = round($listQuery->sum($statisticsField), $config->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), $config->decimal_places);
} else {
$statisticsResult = round($listQuery->max($statisticsField), $config->decimal_places);
}
} else {
$statisticsResult = round($listQuery->max($statisticsField), $config->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), $config->decimal_places);
} else {
$statisticsResult = round($listQuery->min($statisticsField), $config->decimal_places);
}
} else {
$statisticsResult = round($listQuery->min($statisticsField), $config->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)
];
}
return $this->success([
'sql' => $statisticsSqlFull,
'list' => $data,
'total' => $statisticsResult,
'pagination' => $pagination,
'config' => $config
]);
} catch (\Exception $exception) {
return $this->fail([$exception->getCode(), $exception->getMessage()]);
}
}
/**
* 获取模型实例
*/
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;
}
}
}

@ -0,0 +1,12 @@
<?php
namespace App\Models;
class StatisticsConfig extends SoftDeletesModel
{
protected $casts = [
'config_json' => 'json',
];
}

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration {
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('statistics_configs', function (Blueprint $table) {
$table->id();
$table->string('name')->comment('名字');
$table->string('key')->nullable()->comment('标识key');
$table->tinyInteger('decimal_places')->default(0)->comment('小数点位数');
$table->text('description')->nullable()->comment('描述');
$table->json('config_json')->nullable()->comment('配置json');
$table->timestamps();
$table->softDeletes();
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('statistics_configs');
}
};

@ -0,0 +1,616 @@
<?php
namespace Database\Seeders;
use App\Models\StatisticsConfig;
use Illuminate\Database\Seeder;
class StatisticsConfigSeeder extends Seeder
{
/**
* Run the database seeds.
*
* @return void
*/
public function run()
{
$configs = [
// 1. 校友总数 - 对应 homeV2 中的 schoolmate_total
[
'name' => '校友总数',
'key' => 'schoolmate_total',
'decimal_places' => 0,
'description' => '统计所有校友的总数',
'config_json' => [
'data_source' => [
'main_model' => 'user',
'relations' => []
],
'conditions' => [
'logic' => 'and',
'items' => [
[
'key' => 'is_schoolmate',
'operator' => 'eq',
'value' => '1'
]
]
],
'statistics' => [
'type' => 'count',
'order_by' => [
'field' => 'id',
'direction' => 'desc'
]
]
]
],
// 2. 2025年校友数 - 对应 homeV2 中的 schoolmate_year
[
'name' => '2025年校友数',
'key' => 'schoolmate_year',
'decimal_places' => 0,
'description' => '统计2025年创建的校友数量',
'config_json' => [
'data_source' => [
'main_model' => 'user',
'relations' => []
],
'conditions' => [
'logic' => 'and',
'items' => [
[
'key' => 'is_schoolmate',
'operator' => 'eq',
'value' => '1'
],
[
'key' => 'created_at',
'operator' => 'like',
'value' => date('Y')
]
]
],
'statistics' => [
'type' => 'count',
'order_by' => [
'field' => 'created_at',
'direction' => 'desc'
]
]
]
],
// 3. 已开设期数 - 对应 homeV2 中的 course_periods_total按课程类型
[
'name' => '各课程类型已开设期数',
'key' => 'course_periods_total_by_type',
'decimal_places' => 0,
'description' => '统计各课程类型已开设的期数',
'config_json' => [
'data_source' => [
'main_model' => 'course',
'relations' => []
],
'conditions' => [
'logic' => 'and',
'items' => []
],
'statistics' => [
'type' => 'count',
'group_by' => 'type',
'order_by' => [
'field' => 'total',
'direction' => 'desc'
]
]
]
],
// 4. 培养人数去重 - 对应 homeV2 中的 course_signs_total按课程类型
// 注意:去重逻辑需要在应用层处理,这里先统计总数
[
'name' => '各课程类型培养人数',
'key' => 'course_signs_total_by_type',
'decimal_places' => 0,
'description' => '统计各课程类型的审核通过报名人数2020-01-01至今',
'config_json' => [
'data_source' => [
'main_model' => 'course_sign',
'relations' => ['course']
],
'conditions' => [
'logic' => 'and',
'items' => [
[
'key' => 'status',
'operator' => 'eq',
'value' => '1'
],
[
'key' => 'created_at',
'operator' => 'between',
'value' => '2020-01-01,' . date('Y-m-d')
],
[
'key' => 'status',
'operator' => 'neq',
'value' => '4'
],
[
'key' => 'status',
'operator' => 'neq',
'value' => '5'
]
]
],
'statistics' => [
'type' => 'count',
'group_by' => 'course.type',
'order_by' => [
'field' => 'total',
'direction' => 'desc'
]
]
]
],
// 5. 苏州区域数据 - 对应 homeV2 中的 suzhou
[
'name' => '苏州各区域校友人数',
'key' => 'suzhou_schoolmate_by_area',
'decimal_places' => 0,
'description' => '统计苏州各区域的校友人数',
'config_json' => [
'data_source' => [
'main_model' => 'user',
'relations' => ['company']
],
'conditions' => [
'logic' => 'and',
'items' => [
[
'key' => 'is_schoolmate',
'operator' => 'eq',
'value' => '1'
],
[
'key' => 'company.company_city',
'operator' => 'eq',
'value' => '苏州市'
],
[
'key' => 'company.company_area',
'operator' => 'isnotnull'
]
]
],
'statistics' => [
'type' => 'count',
'group_by' => 'company.company_area',
'order_by' => [
'field' => 'total',
'direction' => 'desc'
]
]
]
],
// 6. 全国数据 - 对应 homeV2 中的 country
[
'name' => '全国各城市校友人数',
'key' => 'country_schoolmate_by_city',
'decimal_places' => 0,
'description' => '统计全国各城市的校友人数',
'config_json' => [
'data_source' => [
'main_model' => 'user',
'relations' => ['company']
],
'conditions' => [
'logic' => 'and',
'items' => [
[
'key' => 'is_schoolmate',
'operator' => 'eq',
'value' => '1'
],
[
'key' => 'company.company_city',
'operator' => 'isnotnull'
]
]
],
'statistics' => [
'type' => 'count',
'group_by' => 'company.company_city',
'order_by' => [
'field' => 'total',
'direction' => 'desc'
]
]
]
],
// 7. 本月课程 - 对应 homeV2 中的 monthCourses
[
'name' => '本月课程列表',
'key' => 'month_courses',
'decimal_places' => 0,
'description' => '获取本月开课的课程列表',
'config_json' => [
'data_source' => [
'main_model' => 'course',
'relations' => ['teacher']
],
'conditions' => [
'logic' => 'and',
'items' => [
[
'key' => 'start_date',
'operator' => 'like',
'value' => date('Y-m')
]
]
],
'statistics' => [
'type' => 'count',
'order_by' => [
'field' => 'start_date',
'direction' => 'asc'
]
]
]
],
// 8. 投后企业 - 对应 homeV2 中的 yh_invested_total
[
'name' => '投后企业总数',
'key' => 'yh_invested_total',
'decimal_places' => 0,
'description' => '统计元禾已投企业的总数',
'config_json' => [
'data_source' => [
'main_model' => 'company',
'relations' => []
],
'conditions' => [
'logic' => 'and',
'items' => [
[
'key' => 'is_yh_invested',
'operator' => 'eq',
'value' => '1'
]
]
],
'statistics' => [
'type' => 'count',
'order_by' => [
'field' => 'id',
'direction' => 'desc'
]
]
]
],
// 9. 元和员工参与企业 - 对应 homeV2 中的 yh_join_company_total
[
'name' => '元和员工参与企业总数',
'key' => 'yh_join_company_total',
'decimal_places' => 0,
'description' => '统计公司名称包含元禾相关关键词的企业总数',
'config_json' => [
'data_source' => [
'main_model' => 'company',
'relations' => []
],
'conditions' => [
'logic' => 'or',
'items' => [
[
'key' => 'company_name',
'operator' => 'like',
'value' => '元禾控股'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '元禾原点'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '元禾厚望'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '元禾重元'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '元禾璞华'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '元禾谷风'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '元禾绿柳'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '元禾辰坤'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '元禾沙湖'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '禾裕集团'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '苏州科服'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '信诚管理咨询'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '集成电路公司'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '常州团队'
],
[
'key' => 'company_name',
'operator' => 'like',
'value' => '国企元禾'
]
]
],
'statistics' => [
'type' => 'count',
'order_by' => [
'field' => 'id',
'direction' => 'desc'
]
]
]
],
// 10. 全市干部参与企业 - 对应 homeV2 中的 yh_ganbu_total
[
'name' => '全市干部参与企业总数',
'key' => 'yh_ganbu_total',
'decimal_places' => 0,
'description' => '统计有"跟班学员"用户的企业总数',
'config_json' => [
'data_source' => [
'main_model' => 'company',
'relations' => ['users']
],
'conditions' => [
'logic' => 'and',
'items' => [
[
'key' => 'users.from',
'operator' => 'eq',
'value' => '跟班学员'
]
]
],
'statistics' => [
'type' => 'count',
'order_by' => [
'field' => 'id',
'direction' => 'desc'
]
]
]
],
// 11. 课程统计列表 - 对应 homeV2 中的 courseTypes已开设期数
[
'name' => '课程统计列表(已开设期数)',
'key' => 'course_types_list_periods',
'decimal_places' => 0,
'description' => '统计各课程类型的已开设期数(仅统计 is_chart=1 的课程类型)',
'config_json' => [
'data_source' => [
'main_model' => 'course',
'relations' => ['typeDetail']
],
'conditions' => [
'logic' => 'and',
'items' => [
[
'key' => 'typeDetail.is_chart',
'operator' => 'eq',
'value' => '1'
]
]
],
'statistics' => [
'type' => 'count',
'group_by' => 'typeDetail.id',
'order_by' => [
'field' => 'total',
'direction' => 'desc'
]
]
]
],
// 12. 课程统计列表 - 对应 homeV2 中的 courseTypes培养人数
[
'name' => '课程统计列表(培养人数)',
'key' => 'course_types_list_signs',
'decimal_places' => 0,
'description' => '统计各课程类型的培养人数2020-01-01至今审核通过仅统计 is_chart=1 的课程类型)',
'config_json' => [
'data_source' => [
'main_model' => 'course_sign',
'relations' => ['course']
],
'conditions' => [
'logic' => 'and',
'items' => [
[
'key' => 'status',
'operator' => 'eq',
'value' => '1'
],
[
'key' => 'created_at',
'operator' => 'between',
'value' => '2020-01-01,' . date('Y-m-d')
],
[
'key' => 'status',
'operator' => 'neq',
'value' => '4'
],
[
'key' => 'status',
'operator' => 'neq',
'value' => '5'
],
[
'key' => 'course.typeDetail.is_chart',
'operator' => 'eq',
'value' => '1'
]
]
],
'statistics' => [
'type' => 'count',
'group_by' => 'course.typeDetail.id',
'order_by' => [
'field' => 'total',
'direction' => 'desc'
]
]
]
],
// 13. 课程分类下的课程数量统计
[
'name' => '课程分类下的课程数量',
'key' => 'course_count_by_type',
'decimal_places' => 0,
'description' => '统计各课程分类下的所有课程数量',
'config_json' => [
'data_source' => [
'main_model' => 'course',
'relations' => ['typeDetail']
],
'conditions' => [
'logic' => 'and',
'items' => []
],
'statistics' => [
'type' => 'count',
'group_by' => 'typeDetail.id',
'order_by' => [
'field' => 'total',
'direction' => 'desc'
]
]
]
],
// 14. 课程分类列表及审核通过用户数量统计(按手机号去重)
[
'name' => '课程分类列表及审核通过用户数量统计',
'key' => 'course_type_list_with_student_count',
'decimal_places' => 0,
'description' => '获取课程分类列表,并统计每个分类下审核通过的报名用户数量(按照手机号去重)',
'config_json' => [
'data_source' => [
'main_model' => 'course_sign',
'relations' => ['course', 'user']
],
'conditions' => [
'logic' => 'and',
'items' => [
[
'key' => 'status',
'operator' => 'eq',
'value' => '1'
]
]
],
'statistics' => [
'type' => 'count_distinct',
'distinct_field' => 'user.mobile',
'group_by' => 'course.type',
'order_by' => [
'field' => 'group_value',
'direction' => 'asc'
]
]
]
],
// 15. 课程分类下按手机号去重的审核通过用户数量统计
[
'name' => '课程分类下按手机号去重的审核通过用户数量',
'key' => 'course_type_student_count_distinct',
'decimal_places' => 0,
'description' => '统计各课程分类下审核通过的报名用户数量,按照手机号去重',
'config_json' => [
'data_source' => [
'main_model' => 'course_sign',
'relations' => ['course', 'user']
],
'conditions' => [
'logic' => 'and',
'items' => [
[
'key' => 'status',
'operator' => 'eq',
'value' => '1'
]
]
],
'statistics' => [
'type' => 'count_distinct',
'distinct_field' => 'user.mobile',
'group_by' => 'course.type',
'order_by' => [
'field' => 'total',
'direction' => 'desc'
]
]
]
]
];
foreach ($configs as $config) {
StatisticsConfig::updateOrCreate(
['key' => $config['key']],
$config
);
}
$this->command->info('已生成 ' . count($configs) . ' 条统计数据配置测试数据(基于 homeV2 方法)');
}
}

@ -252,6 +252,13 @@ Route::group(["namespace" => "Admin", "prefix" => "admin"], function () {
Route::get('time-event/show', [\App\Http\Controllers\Admin\TimeEventController::class, "show"]);
Route::post('time-event/save', [\App\Http\Controllers\Admin\TimeEventController::class, "save"]);
Route::get('time-event/destroy', [\App\Http\Controllers\Admin\TimeEventController::class, "destroy"]);
// 统计数据配置管理
Route::get('statistics-configs/index', [\App\Http\Controllers\Admin\StatisticsConfigController::class, "index"]);
Route::get('statistics-config/show', [\App\Http\Controllers\Admin\StatisticsConfigController::class, "show"]);
Route::post('statistics-config/save', [\App\Http\Controllers\Admin\StatisticsConfigController::class, "save"]);
Route::get('statistics-config/destroy', [\App\Http\Controllers\Admin\StatisticsConfigController::class, "destroy"]);
Route::get('statistics-config/calculate', [\App\Http\Controllers\Admin\StatisticsConfigController::class, "calculate"]);
});
});

@ -0,0 +1,550 @@
# 用户统计数据配置 JSON 结构说明
## 概述
`user_statistics_configs` 表的 `config_json` 字段用于存储动态统计配置,包含三个主要部分:数据来源、条件设置、统计方式。
## JSON 结构
```json
{
"data_source": {
"main_model": "user|company|course_sign|course|course_type",
"relations": ["user", "company", "course_sign", "course", "course_type"]
},
"conditions": {
"logic": "and|or",
"items": [
{
"key": "字段名",
"operator": "操作类型",
"value": "值"
}
]
},
"statistics": {
"type": "sum|max|min|count|count_distinct",
"field": "统计字段sum/max/min 时使用,可选)",
"distinct_field": "去重字段count_distinct 时使用,可选)",
"group_by": "分组字段(可选,不设置则不分组)",
"order_by": {
"field": "排序字段(可选)",
"direction": "asc|desc"
}
}
}
```
---
## 一、数据来源data_source
### 1.1 主模型main_model
**说明**:指定统计数据的主要来源模型。
**可选值**
- `user` - 用户模型
- `company` - 公司模型
- `course_sign` - 报名模型
- `course` - 课程模型
- `course_type` - 课程分类模型
**示例**
```json
{
"main_model": "user"
}
```
### 1.2 关联模型relations
**说明**:指定需要关联的其他模型,可以关联多个模型。
**可选值**(数组):
- `user` - 用户模型
- `company` - 公司模型
- `course_sign` - 报名模型
- `course` - 课程模型
- `course_type` - 课程分类模型
**注意**
- 关联模型不能包含主模型本身
- 可以关联多个模型
- 数组可以为空
**示例**
```json
{
"relations": ["company", "course_sign"]
}
```
---
## 二、条件设置conditions
### 2.1 逻辑关系logic
**说明**:指定多个条件之间的逻辑关系。
**可选值**
- `and` - 所有条件都必须满足AND
- `or` - 至少一个条件满足OR
**示例**
```json
{
"logic": "and"
}
```
### 2.2 条件项items
**说明**:条件数组,每个条件包含键名、操作类型和值。
**条件项结构**
```json
{
"key": "字段名",
"operator": "操作类型",
"value": "值"
}
```
#### 字段说明
- **key**(字符串):要查询的字段名
- 可以是主模型的字段
- 可以是关联模型的字段(使用点号分隔,如 `company.name`
- **operator**(字符串):操作类型
- `eq` - 等于
- `neq` - 不等于
- `gt` - 大于
- `egt` - 大于等于
- `lt` - 小于
- `elt` - 小于等于
- `like` - 模糊匹配
- `notlike` - 不匹配
- `in` - 在范围内(值为逗号分隔的字符串)
- `notin` - 不在范围内
- `between` - 在范围内(值为逗号分隔的两个值)
- `notbetween` - 不在范围内
- `isnull` - 为空value 可省略)
- `isnotnull` - 不为空value 可省略)
- **value**(字符串/数字/数组):条件值
- 根据操作类型不同,值的形式也不同
- `in` 操作:值为逗号分隔的字符串,如 `"1,2,3"`
- `between` 操作:值为逗号分隔的两个值,如 `"2024-01-01,2024-12-31"`
- `isnull``isnotnull` 操作value 可以省略
**示例**
```json
{
"logic": "and",
"items": [
{
"key": "is_schoolmate",
"operator": "eq",
"value": "1"
},
{
"key": "company.is_yh_invested",
"operator": "eq",
"value": "1"
},
{
"key": "created_at",
"operator": "between",
"value": "2024-01-01,2024-12-31"
}
]
}
```
---
## 三、统计方式statistics
### 3.1 统计类型type
**说明**:指定统计的方式。
**可选值**
- `sum` - 求和(需要指定 `field` 字段)
- `max` - 最大值(需要指定 `field` 字段)
- `min` - 最小值(需要指定 `field` 字段)
- `count` - 统计总数量(不需要指定 `field` 字段)
- `count_distinct` - 统计去重数量(需要指定 `distinct_field` 字段)
**示例**
```json
{
"type": "count"
}
```
### 3.2 统计字段field
**说明**:当统计类型为 `sum`、`max` 或 `min` 时,指定要统计的字段名。
**注意**
- `type``sum`、`max`、`min` 时必须指定 `field`
- `type``count` 时可以省略 `field`
- 可以是主模型的字段
- 可以是关联模型的字段(使用点号分隔,如 `company.company_fund`
**示例**
```json
{
"type": "sum",
"field": "company_fund"
}
```
```json
{
"type": "max",
"field": "company.company_fund"
}
```
```json
{
"type": "min",
"field": "created_at"
}
```
### 3.3 去重字段distinct_field
**说明**:当统计类型为 `count_distinct` 时,指定要去重的字段名。
**注意**
- `type``count_distinct` 时必须指定 `distinct_field`
- 可以是主模型的字段
- 可以是关联模型的字段(使用点号分隔,如 `user.mobile`
- **可以与 `group_by` 同时使用**:可以按某个字段分组,然后统计每个分组的去重数量
**示例1不分组去重统计**
```json
{
"type": "count_distinct",
"distinct_field": "mobile"
}
```
**示例2关联模型字段去重**
```json
{
"type": "count_distinct",
"distinct_field": "user.mobile"
}
```
**示例3分组 + 去重统计(组合使用)**
```json
{
"type": "count_distinct",
"distinct_field": "user.mobile",
"group_by": "course.type"
}
```
### 3.4 分组字段group_by
**说明**:指定按哪个字段进行分组统计。这是一个可选配置,可以选择不分组或选择具体的分组字段。
**配置选项**
- **不分组**:不设置 `group_by` 字段,或设置为 `null`,将返回所有符合条件的记录列表
- **按字段分组**:设置具体的分组字段,将按该字段进行分组统计
**分组字段格式**
- 可以是主模型的字段(如:`company_area`
- 可以是关联模型的字段(使用点号分隔,如 `company.company_area`
**示例1不分组统计**
```json
{
"statistics": {
"type": "count"
// 不设置 group_by表示不分组
}
}
```
**示例2按主模型字段分组**
```json
{
"statistics": {
"type": "count",
"group_by": "company_area"
}
}
```
**示例3按关联模型字段分组**
```json
{
"statistics": {
"type": "count",
"group_by": "company.company_area"
}
}
```
**示例4分组 + 去重统计(组合使用)**
```json
{
"statistics": {
"type": "count_distinct",
"distinct_field": "user.mobile",
"group_by": "course.type"
}
}
```
### 3.4 排序方式order_by
**说明**:指定结果的排序方式。
**结构**
```json
{
"field": "排序字段",
"direction": "asc|desc"
}
```
**字段说明**
- **field**(字符串):排序字段名
- 可以是主模型的字段
- 可以是关联模型的字段(使用点号分隔)
- 可以是统计结果字段(如 `total`、`count`
- **direction**(字符串):排序方向
- `asc` - 升序
- `desc` - 降序
**示例**
```json
{
"order_by": {
"field": "total",
"direction": "desc"
}
}
```
---
## 完整示例
### 示例1统计各区域的校友人数
```json
{
"data_source": {
"main_model": "user",
"relations": ["company"]
},
"conditions": {
"logic": "and",
"items": [
{
"key": "is_schoolmate",
"operator": "eq",
"value": "1"
},
{
"key": "created_at",
"operator": "between",
"value": "2024-01-01,2024-12-31"
}
]
},
"statistics": {
"type": "count",
"group_by": "company.company_area",
"order_by": {
"field": "count",
"direction": "desc"
}
}
}
```
### 示例2统计各课程类型的报名人数
```json
{
"data_source": {
"main_model": "course_sign",
"relations": ["course", "user"]
},
"conditions": {
"logic": "and",
"items": [
{
"key": "status",
"operator": "eq",
"value": "1"
},
{
"key": "created_at",
"operator": "between",
"value": "2024-01-01,2024-12-31"
}
]
},
"statistics": {
"type": "count",
"group_by": "course.type",
"order_by": {
"field": "count",
"direction": "desc"
}
}
}
```
### 示例3统计各公司的融资总额
```json
{
"data_source": {
"main_model": "company",
"relations": ["user"]
},
"conditions": {
"logic": "and",
"items": [
{
"key": "is_yh_invested",
"operator": "eq",
"value": "1"
},
{
"key": "company_fund",
"operator": "isnotnull"
}
]
},
"statistics": {
"type": "sum",
"field": "company_fund",
"group_by": "company_area",
"order_by": {
"field": "total",
"direction": "desc"
}
}
}
```
### 示例4统计审核通过或待审核的报名人数
```json
{
"data_source": {
"main_model": "course_sign",
"relations": []
},
"conditions": {
"logic": "or",
"items": [
{
"key": "status",
"operator": "eq",
"value": "0"
},
{
"key": "status",
"operator": "eq",
"value": "1"
}
]
},
"statistics": {
"type": "count",
"order_by": {
"field": "created_at",
"direction": "desc"
}
}
}
```
### 示例5统计各课程类型的去重培养人数按手机号去重
```json
{
"data_source": {
"main_model": "course_sign",
"relations": ["user", "course"]
},
"conditions": {
"logic": "and",
"items": [
{
"key": "status",
"operator": "eq",
"value": "1"
},
{
"key": "created_at",
"operator": "between",
"value": "2020-01-01," . date('Y-m-d')
}
]
},
"statistics": {
"type": "count_distinct",
"distinct_field": "user.mobile",
"group_by": "course.type",
"order_by": {
"field": "total",
"direction": "desc"
}
}
}
```
---
## 注意事项
1. **字段引用**
- 主模型字段直接使用字段名
- 关联模型字段使用 `模型名.字段名` 格式
- 例如:`company.name`、`course.type`
2. **数据类型**
- 所有值在 JSON 中都存储为字符串
- 系统会根据字段类型自动转换
3. **条件逻辑**
- `and` 表示所有条件都必须满足
- `or` 表示至少一个条件满足
- 条件数组可以为空(表示无条件)
4. **统计字段**
- `sum` 类型必须指定 `field`
- `count` 类型不需要 `field`
- 分组字段可以为空(表示不分组)
5. **排序字段**
- 可以按任意字段排序
- 可以按统计结果字段排序(如 `total`、`count`
- 排序字段可以为空(使用默认排序)
---
## 文档版本
- **创建日期**2025-11-19
- **最后更新**2025-11-19
Loading…
Cancel
Save