startOfMonth()->timestamp; // 2. 业务状态校验 (提出来的校验逻辑) $calendar = DB::table('calendar') ->where('del_time', 0) ->where('time', $monthStart) ->first(); if (!$calendar) return [false, '当月日历未配置']; $hasWorkDays = DB::table('calendar_details') ->where('del_time', 0) ->where('calendar_id', $calendar->id) ->where('is_work', 1) ->exists(); if (!$hasWorkDays) return [false, '当月无工作日,无法生成']; // try { // DB::transaction(function () use($data) { // $this->doGenerate($data['month']); // }); // } catch (\Exception $e) { // // 只有这里 throw 了,任务才会进入 failed_jobs 表 // return [false, $e->getMessage()]; // // } $key = self::job_name . $data['month']; $data['lock_key'] = $key; list($status,$msg) = $this->limitingSendRequest($key); if(! $status) return [false, '该月设备数据已在后台处理,请勿重复操作']; ProcessDataJob::dispatch($data)->onQueue(self::job_name); return [true, '任看']; } public function delTableKey($key){ $this->dellimitingSendRequest($key); } // 内存缓冲区 protected $pendingDetails = []; public function doGenerate(string $month) { $monthStart = Carbon::createFromFormat('Y-m', $month)->startOfMonth()->timestamp; $monthEnd = Carbon::createFromFormat('Y-m', $month)->endOfMonth()->timestamp; // 1. 基础数据准备 $calendar = DB::table('calendar')->where('del_time', 0)->where('time', $monthStart)->first(); if (!$calendar) throw new \Exception('当月日历未配置'); $workDays = DB::table('calendar_details') ->where('del_time', 0)->where('calendar_id', $calendar->id)->where('is_work', 1) ->orderBy('time')->pluck('time')->toArray(); $items = DB::table('item')->where('del_time', 0)->where('is_use', 1) ->where('start_time', '<=', $monthEnd)->where('end_time', '>=', $monthStart) ->orderBy('id','desc')->get(); if ($items->isEmpty() || empty($workDays)) return; // 2. 清理旧数据 (保持原样) $rds = DB::table('rd')->where('del_time', 0)->where('type', RD::type_two) ->whereBetween('order_time', [$monthStart, $monthEnd])->pluck('id')->toArray(); if (!empty($rds)) { DB::table('rd_details')->whereIn('rd_id', $rds)->delete(); DB::table('rd')->whereIn('id', $rds)->delete(); } $deviceConfigs = DB::table('device_details')->where('del_time', 0) ->where('time', $calendar->time)->where('total_hours_2', '>', 0)->get()->keyBy('device_id'); // 3. 内存计算数据 $pendingRds = []; $rdToDeviceMap = []; // 用于记录 order_number 对应哪个 device_id foreach ($deviceConfigs as $deviceId => $config) { $this->calculateData($deviceId, $items, $workDays, $config, $pendingRds, $rdToDeviceMap); } // 4. 批量插入主表 (分批写入,防止 SQL 过长) if (empty($pendingRds)) return; foreach (array_chunk($pendingRds, 1000) as $chunk) { DB::table('rd')->insert($chunk); } // 5. 核心:通过 order_number 批量找回生成的 ID $allOrderNumbers = array_keys($rdToDeviceMap); $insertedRds = DB::table('rd') ->whereIn('order_number', $allOrderNumbers) ->select('id', 'order_number', 'crt_time') ->get(); // 6. 构造并批量插入详情表 $pendingDetails = []; foreach ($insertedRds as $rd) { $pendingDetails[] = [ 'rd_id' => $rd->id, 'data_id' => $rdToDeviceMap[$rd->order_number], // 从映射表找回设备ID 'type' => RD::type_two, 'crt_time' => $rd->crt_time, 'upd_time' => $rd->crt_time, 'del_time' => 0 ]; } foreach (array_chunk($pendingDetails, 1000) as $chunk) { DB::table('rd_details')->insert($chunk); } } protected function calculateData($device_id, $items, $workDays, $config, &$pendingRds, &$rdToDeviceMap) { $totalMinutes = intval(round($config->total_hours_2 * 60) / 60) * 60; // 筛选有效天数 $validDays = []; foreach ($workDays as $day) { $dayItems = []; foreach ($items as $item) { if ($day >= $item->start_time && $day <= $item->end_time) $dayItems[] = $item; } if (!empty($dayItems)) $validDays[$day] = $dayItems; } if (empty($validDays)) return; // 计算每日工时分配 (略,保持您原本的分配逻辑...) $dailyMinutesMap = $this->distributeMinutes(count($validDays), $totalMinutes, array_keys($validDays)); foreach ($validDays as $day => $dayItems) { $dailyMinutes = $dailyMinutesMap[$day]; if ($dailyMinutes <= 0) continue; // 简单的随机项目分配 $totalHoursToday = $dailyMinutes / 60; for ($h = 0; $h < $totalHoursToday; $h++) { $item = $dayItems[array_rand($dayItems)]; // 生成时间点逻辑 $start = Carbon::createFromTimestamp($day)->setTime(8, 0)->addMinutes(mt_rand(0, 7) * 60); $end = $start->copy()->addMinutes(60); // 生成绝对唯一的单号:时间戳 + 毫秒 + 设备ID + 随机 // 这样既解决了速度问题,又保证了 order_number 的唯一索引不会崩 $orderNumber = 'RD' . $day . 'D' . $device_id . 'S' . uniqid(); $crtTime = $day + 59400 + mt_rand(0, 1800); $pendingRds[] = [ 'crt_time' => $crtTime, 'order_time' => $day, 'item_id' => $item->id, 'start_time_hour' => $start->hour, 'start_time_min' => $start->minute, 'end_time_hour' => $end->hour, 'end_time_min' => $end->minute, 'total_hours' => 60, 'order_number' => $orderNumber, 'type' => RD::type_two, 'del_time' => 0 ]; // 记录这个单号属于哪个设备 $rdToDeviceMap[$orderNumber] = $device_id; } } } private function distributeMinutes($daysCount, $totalMinutes, $dayKeys) { $avg = (intval(floor($totalMinutes / $daysCount) / 60)) * 60; $res = array_fill_keys($dayKeys, $avg); $rem = ($totalMinutes - ($avg * $daysCount)) / 60; for($i=0; $i<$rem; $i++) { $res[$dayKeys[$i % $daysCount]] += 60; } return $res; } }