master
cody 3 months ago
parent c485a785c4
commit c2f0ffdd9a

@ -0,0 +1,94 @@
<?php
namespace App\Exports;
use Illuminate\Support\Collection;
use Maatwebsite\Excel\Concerns\FromCollection;
use Maatwebsite\Excel\Concerns\WithMultipleSheets;
use Maatwebsite\Excel\Concerns\WithStyles;
use Maatwebsite\Excel\Concerns\WithColumnWidths;
use Maatwebsite\Excel\Concerns\WithEvents;
use Maatwebsite\Excel\Events\AfterSheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
class MultiSheetExport implements WithMultipleSheets
{
protected $sheets = [];
public function __construct(array $sheets)
{
$this->sheets = $sheets;
}
public function sheets(): array
{
return $this->sheets;
}
}
class SheetExport implements FromCollection, WithStyles, WithColumnWidths, WithEvents
{
protected $data;
protected $fields;
protected $sheetName;
public function __construct($data, $fields, $sheetName = 'Sheet1')
{
$this->data = $data;
$this->fields = $fields;
$this->sheetName = $sheetName;
}
public function collection()
{
$newList = [];
// 添加表头
$header = array_values($this->fields);
$newList[] = $header;
// 添加数据行
foreach ($this->data as $row) {
$temp = [];
foreach (array_keys($this->fields) as $field) {
$temp[] = $row[$field] ?? '';
}
$newList[] = $temp;
}
return new Collection($newList);
}
public function styles(Worksheet $sheet)
{
return [
1 => [
'font' => ['bold' => true],
'alignment' => ['horizontal' => Alignment::HORIZONTAL_CENTER],
],
];
}
public function columnWidths(): array
{
$widths = [];
$column = 'A';
foreach ($this->fields as $field) {
$widths[$column] = 15;
$column++;
}
return $widths;
}
public function registerEvents(): array
{
return [
AfterSheet::class => function (AfterSheet $event) {
$sheet = $event->sheet->getDelegate();
$sheet->setTitle($this->sheetName);
},
];
}
}

@ -32,6 +32,8 @@ use EasyWeChat\Factory;
use Illuminate\Filesystem\Filesystem;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\CommonExport;
use App\Exports\MultiSheetExport;
use App\Exports\SheetExport;
class OtherController extends CommonController
{
@ -585,10 +587,12 @@ class OtherController extends CommonController
// 审核通过人数明细 - 使用courseSignsTotal方法获取列表与coursesHome算法一致
$courseSigns = CourseSign::courseSignsTotal($start_date, $end_date, 1, $course_ids, true);
// 加载关联关系
$courseSigns->load(['user', 'course']);
$courseSigns->load(['user.company', 'course.typeDetail']);
// 当前课程数据
$currentData = [];
foreach ($courseSigns as $sign) {
$data[] = [
$currentData[] = [
'user_name' => $sign->user->name ?? '',
'mobile' => $sign->user->mobile ?? '',
'company_name' => $sign->user->company->company_name ?? '',
@ -596,19 +600,70 @@ class OtherController extends CommonController
'company_industry' => $sign->user->company->company_industry ?? '',
'course_name' => $sign->course->name ?? '',
'course_type' => $sign->course->typeDetail->name ?? '',
// 'created_at' => $sign->created_at ? $sign->created_at->format('Y-m-d H:i:s') : '',
];
}
$fields = [
$currentFields = [
'user_name' => '学员姓名',
'mobile' => '手机号',
'company_name' => '企业名称',
'company_area' => '所在区域',
'company_industry' => '所在行业',
'course_name' => '课程名称',
'course_type' => '课程类型',
// 'created_at' => '报名时间',
];
// 历史课程数据
$historyData = [];
$course_type_id_array = $course_type_id ? (is_array($course_type_id) ? $course_type_id : explode(',', $course_type_id)) : [];
$historyCourses = HistoryCourse::whereHas('calendar', function ($query) {
$query->where('is_count_people', 1);
})->whereHas('typeDetail', function ($query) {
$query->where('is_history', 1);
})->where(function ($query) use ($start_date, $end_date) {
if ($start_date && $end_date) {
$query->whereBetween('start_time', [$start_date, $end_date])
->orWhereBetween('end_time', [$start_date, $end_date]);
}
})->where(function ($query) use ($course_type_id_array) {
if (!empty($course_type_id_array)) {
$query->whereIn('type', $course_type_id_array);
}
})->with('typeDetail')->get();
foreach ($historyCourses as $historyCourse) {
$historyData[] = [
'course_type' => $historyCourse->typeDetail->name ?? '',
'course_name' => $historyCourse->course_name ?? '',
'start_time' => $historyCourse->start_time ?? '',
'end_time' => $historyCourse->end_time ?? '',
'course_type_signs_pass' => $historyCourse->course_type_signs_pass ?? 0,
'course_type_signs_pass_unique' => $historyCourse->course_type_signs_pass_unique ?? 0,
'course_signs_pass' => $historyCourse->course_signs_pass ?? 0,
];
}
$historyFields = [
'course_type' => '课程体系',
'course_name' => '课程名称',
'start_time' => '开始时间',
'end_time' => '结束时间',
'course_type_signs_pass' => '培养人数未去重',
'course_type_signs_pass_unique' => '培养人数去重',
'course_signs_pass' => '课程培养人数',
];
// 创建多 sheet 导出
$sheets = [
new SheetExport($currentData, $currentFields, '当前课程数据'),
new SheetExport($historyData, $historyFields, '历史课程数据'),
];
$filename = '审核通过人数明细';
// 直接返回多 sheet 导出
return Excel::download(
new MultiSheetExport($sheets),
$filename . '_' . date('YmdHis') . '.xlsx'
);
break;
case 'course_signs_pass_unique':

@ -0,0 +1,295 @@
-- ============================================
-- 数据库索引优化建议
-- 基于 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