diff --git a/app/Http/Controllers/Admin/StatisticsMetadataController.php b/app/Http/Controllers/Admin/StatisticsMetadataController.php new file mode 100644 index 0000000..fb10b9e --- /dev/null +++ b/app/Http/Controllers/Admin/StatisticsMetadataController.php @@ -0,0 +1,151 @@ +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(); + } + +} diff --git a/app/Models/StatisticsMetadata.php b/app/Models/StatisticsMetadata.php index ba138f3..8f7a26f 100644 --- a/app/Models/StatisticsMetadata.php +++ b/app/Models/StatisticsMetadata.php @@ -6,6 +6,14 @@ class StatisticsMetadata extends SoftDeletesModel { protected $table = 'statistics_metadata'; + protected $fillable = [ + 'key', + 'name', + 'from', + 'verify', + 'remark', + ]; + /** * 根据 key 获取统计元数据 * @param string $key diff --git a/routes/api.php b/routes/api.php index 7be9ba6..446bae2 100755 --- a/routes/api.php +++ b/routes/api.php @@ -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"]); diff --git a/数据库索引优化建议.sql b/数据库索引优化建议.sql deleted file mode 100644 index 4c144b3..0000000 --- a/数据库索引优化建议.sql +++ /dev/null @@ -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) - --- 执行前请备份数据库! --- 建议在业务低峰期执行索引创建操作 -