master
cody 3 months ago
parent b62165eb18
commit 081a2d5ac3

@ -0,0 +1,151 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Helpers\ResponseCode;
use App\Models\StatisticsMetadata;
use Illuminate\Support\Facades\DB;
class StatisticsMetadataController extends BaseController
{
/**
* 构造函数
*/
public function __construct()
{
parent::__construct(new StatisticsMetadata());
}
/**
* @OA\Get(
* path="/api/admin/statistics-metadata/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="需要输出的关联关系数组"),
* @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()
{
return parent::index();
}
/**
* @OA\Get(
* path="/api/admin/statistics-metadata/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()
{
return parent::show();
}
/**
* @OA\Post(
* path="/api/admin/statistics-metadata/save",
* tags={"统计指标管理"},
* summary="保存(新增/更新)",
* description="",
* @OA\Parameter(name="id", in="query", @OA\Schema(type="string"), required=false, description="id有id则为更新无id则为新增"),
* @OA\Parameter(name="key", in="query", @OA\Schema(type="string"), required=true, description="统计项标识"),
* @OA\Parameter(name="name", in="query", @OA\Schema(type="string"), required=true, description="统计项名称"),
* @OA\Parameter(name="from", in="query", @OA\Schema(type="string"), required=false, description="统计逻辑说明"),
* @OA\Parameter(name="verify", in="query", @OA\Schema(type="string"), required=false, description="验证方法说明"),
* @OA\Parameter(name="remark", 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 save()
{
$all = \request()->all();
DB::beginTransaction();
try {
// 验证必填字段
if (empty($all['key'])) {
return $this->fail([ResponseCode::ERROR_PARAMETER, '统计项标识不能为空']);
}
if (empty($all['name'])) {
return $this->fail([ResponseCode::ERROR_PARAMETER, '统计项名称不能为空']);
}
if (isset($all['id'])) {
$model = $this->model->find($all['id']);
if (empty($model)) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '数据不存在']);
}
// 更新时检查 key 是否重复(排除自己)
$exists = StatisticsMetadata::where('key', $all['key'])
->where('id', '!=', $all['id'])
->first();
if ($exists) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '统计项标识已存在']);
}
} else {
$model = $this->model;
// 新增时检查 key 是否重复
$exists = StatisticsMetadata::where('key', $all['key'])->first();
if ($exists) {
return $this->fail([ResponseCode::ERROR_BUSINESS, '统计项标识已存在']);
}
}
$original = $model->getOriginal();
$model->fill($all);
$model->save();
DB::commit();
// 记录日志
$this->saveLogs($original, $model);
return $this->success($model);
} catch (\Exception $exception) {
DB::rollBack();
return $this->fail([$exception->getCode(), $exception->getMessage()]);
}
}
/**
* @OA\Get(
* path="/api/admin/statistics-metadata/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();
}
}

@ -6,6 +6,14 @@ class StatisticsMetadata extends SoftDeletesModel
{
protected $table = 'statistics_metadata';
protected $fillable = [
'key',
'name',
'from',
'verify',
'remark',
];
/**
* 根据 key 获取统计元数据
* @param string $key

@ -242,6 +242,12 @@ Route::group(["namespace" => "Admin", "prefix" => "admin"], function () {
Route::post('company/save', [\App\Http\Controllers\Admin\CompanyController::class, "save"]);
Route::get('company/destroy', [\App\Http\Controllers\Admin\CompanyController::class, "destroy"]);
// 统计指标管理
Route::get('statistics-metadata/index', [\App\Http\Controllers\Admin\StatisticsMetadataController::class, "index"]);
Route::get('statistics-metadata/show', [\App\Http\Controllers\Admin\StatisticsMetadataController::class, "show"]);
Route::post('statistics-metadata/save', [\App\Http\Controllers\Admin\StatisticsMetadataController::class, "save"]);
Route::get('statistics-metadata/destroy', [\App\Http\Controllers\Admin\StatisticsMetadataController::class, "destroy"]);
// 上市公司管理
Route::get('stock-company/index', [\App\Http\Controllers\Admin\StockCompanyController::class, "index"]);
Route::get('stock-company/show', [\App\Http\Controllers\Admin\StockCompanyController::class, "show"]);

@ -1,295 +0,0 @@
-- ============================================
-- 数据库索引优化建议
-- 基于 coursesHome 统计逻辑分析
-- ============================================
-- ============================================
-- 1. course_signs 表索引
-- ============================================
-- 1.1 核心查询索引status + course_id最常用
-- 用于courseSignsTotal, courseSignsTotalByUnique, getStudentList
-- 查询条件status, course_id, whereHas('course')
ALTER TABLE `course_signs`
ADD INDEX `idx_status_course_id` (`status`, `course_id`);
-- 1.2 用户去重查询索引user_id + status
-- 用于courseSignsTotalByUnique按手机号去重
ALTER TABLE `course_signs`
ADD INDEX `idx_user_id_status` (`user_id`, `status`);
-- 1.3 排除状态索引status用于 whereNotIn status [4,5,6]
-- 注意:如果 status 字段选择性不高,可考虑与 course_id 组合
ALTER TABLE `course_signs`
ADD INDEX `idx_status_not_in` (`status`);
-- 1.4 复合索引status + course_id + user_id用于关联查询优化
ALTER TABLE `course_signs`
ADD INDEX `idx_status_course_user` (`status`, `course_id`, `user_id`);
-- ============================================
-- 2. courses 表索引
-- ============================================
-- 2.1 图表统计索引is_chart + 日期范围
-- 用于getStudentList 中的 whereHas('course')
-- 查询条件is_chart=1, start_date/end_date BETWEEN
ALTER TABLE `courses`
ADD INDEX `idx_is_chart_dates` (`is_chart`, `start_date`, `end_date`);
-- 2.2 课程体系索引type + is_chart
-- 用于:按课程体系筛选课程
ALTER TABLE `courses`
ADD INDEX `idx_type_is_chart` (`type`, `is_chart`);
-- 2.3 复合索引type + is_chart + start_date + end_date
-- 用于:课程分类明细统计
ALTER TABLE `courses`
ADD INDEX `idx_type_chart_dates` (`type`, `is_chart`, `start_date`, `end_date`);
-- ============================================
-- 3. calendars 表索引
-- ============================================
-- 3.1 日期范围查询索引start_time + end_time
-- 用于getCourseTotal, getCourseDayTotal
-- 查询条件start_time/end_time BETWEEN
ALTER TABLE `calendars`
ADD INDEX `idx_dates_range` (`start_time`, `end_time`);
-- 3.2 课程体系索引course_type_id + 日期
-- 用于:按课程体系筛选日历
ALTER TABLE `calendars`
ADD INDEX `idx_course_type_dates` (`course_type_id`, `start_time`, `end_time`);
-- 3.3 统计天数索引is_count_days + 日期
-- 用于getCourseDayTotal开课天数统计
ALTER TABLE `calendars`
ADD INDEX `idx_count_days_dates` (`is_count_days`, `start_time`, `end_time`);
-- 3.4 课程关联索引course_id + course_type_id
-- 用于:通过 course.type 匹配课程体系
ALTER TABLE `calendars`
ADD INDEX `idx_course_type_id` (`course_id`, `course_type_id`);
-- ============================================
-- 4. companies 表索引
-- ============================================
-- 4.1 上市公司索引company_market
-- 用于shangshi, suzhouStock上市公司统计
ALTER TABLE `companies`
ADD INDEX `idx_company_market` (`company_market`);
-- 4.2 被投企业索引is_yh_invested
-- 用于yhInvestedTotal, companyInvestedYear被投企业统计
ALTER TABLE `companies`
ADD INDEX `idx_is_yh_invested` (`is_yh_invested`);
-- 4.3 企业标签索引company_tag用于 LIKE 查询)
-- 用于toubuqiye高新技术企业筛选
-- 注意LIKE '%高新技术企业%' 无法使用索引,但可以优化前缀匹配
ALTER TABLE `companies`
ADD INDEX `idx_company_tag` (`company_tag`(100));
-- 4.4 城市区域索引company_city + company_area
-- 用于area区域统计, isSuzhou苏州筛选
ALTER TABLE `companies`
ADD INDEX `idx_city_area` (`company_city`, `company_area`);
-- 4.5 地址索引company_address用于 LIKE 查询)
-- 用于isSuzhou苏州筛选
-- 注意LIKE '%苏州%' 无法使用索引,但可以优化前缀匹配
ALTER TABLE `companies`
ADD INDEX `idx_company_address` (`company_address`(100));
-- 4.6 复合索引company_market + company_city用于苏州上市公司
ALTER TABLE `companies`
ADD INDEX `idx_market_city` (`company_market`, `company_city`);
-- ============================================
-- 5. users 表索引
-- ============================================
-- 5.1 公司关联索引company_id
-- 用于:通过用户关联公司
ALTER TABLE `users`
ADD INDEX `idx_company_id` (`company_id`);
-- 5.2 跟班学员索引from用于 LIKE 查询)
-- 用于genban, ganbu跟班学员统计
-- 注意LIKE '%跟班学员%' 无法使用索引,但可以优化前缀匹配
ALTER TABLE `users`
ADD INDEX `idx_from` (`from`(50));
-- 5.3 手机号索引mobile用于去重
-- 用于courseSignsTotalByUnique按手机号去重
ALTER TABLE `users`
ADD INDEX `idx_mobile` (`mobile`);
-- 5.4 公司名称索引company_name用于 LIKE 查询)
-- 用于companyJoin元和员工筛选
-- 注意LIKE '%元禾控股%' 等无法使用索引
ALTER TABLE `users`
ADD INDEX `idx_company_name` (`company_name`(100));
-- 5.5 用户地址索引company_address用于 LIKE 查询)
-- 用于isSuzhou苏州筛选
ALTER TABLE `users`
ADD INDEX `idx_user_company_address` (`company_address`(100));
-- 5.6 复合索引company_id + from用于跟班学员筛选
ALTER TABLE `users`
ADD INDEX `idx_company_from` (`company_id`, `from`(50));
-- ============================================
-- 6. stock_companies 表索引
-- ============================================
-- 6.1 上市日期索引stock_date
-- 用于company_market_year_total今年上市公司数量
ALTER TABLE `stock_companys`
ADD INDEX `idx_stock_date` (`stock_date`);
-- 6.2 入学后上市索引is_after_enrollment
-- 用于company_market_after_enrollment_total
ALTER TABLE `stock_companys`
ADD INDEX `idx_after_enrollment` (`is_after_enrollment`);
-- 6.3 公司关联索引company_id
-- 用于:关联 companies 表
ALTER TABLE `stock_companys`
ADD INDEX `idx_company_id` (`company_id`);
-- 6.4 复合索引is_after_enrollment + stock_date
ALTER TABLE `stock_companys`
ADD INDEX `idx_enrollment_date` (`is_after_enrollment`, `stock_date`);
-- ============================================
-- 7. history_courses 表索引
-- ============================================
-- 7.1 日期范围索引start_time + end_time
-- 用于:历史课程统计
ALTER TABLE `history_courses`
ADD INDEX `idx_history_dates` (`start_time`, `end_time`);
-- 7.2 课程类型索引type + 日期
-- 用于:按课程体系筛选历史课程
ALTER TABLE `history_courses`
ADD INDEX `idx_type_dates` (`type`, `start_time`, `end_time`);
-- 7.3 日历关联索引calendar_id
-- 用于whereHas('calendar', is_count_people=1)
ALTER TABLE `history_courses`
ADD INDEX `idx_calendar_id` (`calendar_id`);
-- 7.4 复合索引type + calendar_id + 日期
ALTER TABLE `history_courses`
ADD INDEX `idx_type_calendar_dates` (`type`, `calendar_id`, `start_time`, `end_time`);
-- ============================================
-- 8. course_types 表索引
-- ============================================
-- 8.1 历史课程索引is_history
-- 用于:筛选历史课程类型
ALTER TABLE `course_types`
ADD INDEX `idx_is_history` (`is_history`);
-- 8.2 跟班学员统计索引is_count_genban
-- 用于genban筛选需要统计跟班学员的课程
ALTER TABLE `course_types`
ADD INDEX `idx_is_count_genban` (`is_count_genban`);
-- ============================================
-- 9. 关联查询优化索引
-- ============================================
-- 9.1 course_signs 关联 users 优化
-- 已通过 user_id 索引优化
-- 9.2 users 关联 course_signs 优化
-- 需要在 course_signs 表已有 user_id 索引
-- 9.3 companies 关联 users 优化
-- 需要在 users 表已有 company_id 索引
-- 9.4 courses 关联 course_types 优化
-- 需要在 courses 表已有 type 索引
-- ============================================
-- 10. 特殊查询优化建议
-- ============================================
-- 10.1 JSON 字段查询优化
-- companies.project_users 字段JSON无法直接建立索引
-- 建议:如果 investDate 查询频繁,考虑单独建立 invest_dates 表或字段
-- 10.2 LIKE 查询优化
-- 对于 '%关键词%' 类型的 LIKE 查询,无法使用普通索引
-- 建议:
-- 1. 如果可能,改为前缀匹配 '关键词%' 可以使用索引
-- 2. 考虑使用全文索引FULLTEXT
ALTER TABLE `companies`
ADD FULLTEXT INDEX `ft_company_tag` (`company_tag`);
ALTER TABLE `users`
ADD FULLTEXT INDEX `ft_company_name` (`company_name`);
ALTER TABLE `users`
ADD FULLTEXT INDEX `ft_from` (`from`);
-- 10.3 日期范围查询优化
-- 对于 start_date/end_date BETWEEN 查询,确保日期字段有索引
-- 对于 orWhereBetween 查询MySQL 可能无法同时使用两个索引
-- 建议:如果性能问题,考虑拆分为两个查询 UNION
-- ============================================
-- 11. 索引使用说明
-- ============================================
-- 11.1 索引创建顺序
-- 建议按照表的数据量和查询频率,优先创建高频查询的索引
-- 11.2 索引维护
-- 定期使用 EXPLAIN 分析查询计划,确认索引被正确使用
-- 示例EXPLAIN SELECT * FROM course_signs WHERE status=1 AND course_id IN (1,2,3);
-- 11.3 索引监控
-- 使用以下查询监控索引使用情况:
-- SELECT * FROM sys.schema_unused_indexes;
-- SELECT * FROM performance_schema.table_io_waits_summary_by_index_usage;
-- 11.4 注意事项
-- 1. 索引会占用存储空间,增加写入成本
-- 2. 不要过度索引,每个表建议不超过 5-7 个索引
-- 3. 复合索引的顺序很重要,将选择性高的字段放在前面
-- 4. 定期分析表更新统计信息ANALYZE TABLE table_name;
-- ============================================
-- 12. 性能优化建议
-- ============================================
-- 12.1 查询优化
-- 1. 避免在 WHERE 子句中使用函数
-- 2. 使用 EXISTS 替代 IN当子查询结果集较大时
-- 3. 合理使用 JOIN避免过度嵌套
-- 12.2 分页优化
-- 对于大数据量分页,考虑使用游标分页替代 OFFSET
-- 12.3 缓存策略
-- 对于统计类查询,考虑使用 Redis 缓存结果
-- ============================================
-- 索引创建脚本执行顺序建议
-- ============================================
-- 1. 先创建核心表索引course_signs, courses, calendars
-- 2. 再创建关联表索引companies, users
-- 3. 最后创建辅助表索引stock_companies, history_courses
-- 执行前请备份数据库!
-- 建议在业务低峰期执行索引创建操作
Loading…
Cancel
Save