normalizeDate($from, '起始日期'); $to = $this->normalizeDate($to, '结束日期'); if ($from > $to) { throw new InvalidArgumentException('起始日期不能晚于结束日期。'); } $start = $from . ' 00:00:00'; $end = date('Y-m-d', strtotime($to . ' +1 day')) . ' 00:00:00'; $mirror = $this->databases->connection(config('upstream_mirrors.connection', 'business_mirror')); if (! $mirror->getSchemaBuilder()->hasTable('ops_jst_orders')) { throw new RuntimeException('聚水潭订单镜像尚未建立(ops_jst_orders)。'); } $target = $this->databases->connection(); $startedAt = microtime(true); if ($replace) { $target->table('jst_order_labels') ->where('company_id', $companyId) ->where('ordered_at', '>=', $start) ->where('ordered_at', '<', $end) ->delete(); } $now = now(); $orders = 0; $rows = 0; $noLabelOrders = 0; $buffer = []; $flush = function () use (&$buffer, &$rows, $target): void { foreach (array_chunk($buffer, self::INSERT_CHUNK) as $chunk) { $target->table('jst_order_labels')->insert($chunk); $rows += count($chunk); } $buffer = []; }; $mirror->table('ops_jst_orders') ->where('order_date', '>=', $start) ->where('order_date', '<', $end) ->orderBy('Id') ->chunkById(self::READ_CHUNK, function ($records) use (&$buffer, &$orders, &$noLabelOrders, $flush, $companyId, $source, $now): void { foreach ($records as $record) { $orders++; $orderedAt = $record->order_date === null ? null : substr((string) $record->order_date, 0, 19); if ($orderedAt === null || $orderedAt === '') { continue; } $labels = $this->splitLabels((string) ($record->labels ?? '')); if ($labels === []) { $labels = [self::NO_LABEL]; $noLabelOrders++; } foreach ($labels as $label) { $buffer[] = [ 'company_id' => $companyId, 'order_no' => mb_substr((string) $record->so_id, 0, 100), 'shop_name' => mb_substr((string) ($record->shop_name ?? ''), 0, 191), 'ordered_at' => $orderedAt, 'label' => $label, 'created_from' => $source, 'created_at' => $now, 'updated_at' => $now, ]; } if (count($buffer) >= self::INSERT_CHUNK * 2) { $flush(); } } }, 'Id'); $flush(); $labels = $target->table('jst_order_labels') ->where('company_id', $companyId) ->where('ordered_at', '>=', $start) ->where('ordered_at', '<', $end) ->distinct() ->count('label'); // 导入会改变筛选选项,重新预热缓存,避免页面下次打开时做几秒的 distinct 扫描。 app(JstOrderLabelOptions::class)->forget($companyId); app(JstOrderLabelOptions::class)->warm($companyId); return [ 'from' => $from, 'to' => $to, 'orders' => $orders, 'rows' => $rows, 'no_label_orders' => $noLabelOrders, 'labels' => (int) $labels, 'seconds' => round(microtime(true) - $startedAt, 1), ]; } /** @return list */ private function splitLabels(string $labels): array { $out = []; foreach (explode(',', $labels) as $label) { $label = trim($label); if ($label !== '') { $out[] = mb_substr($label, 0, 191); } } return array_values(array_unique($out)); } private function normalizeDate(string $value, string $label): string { $value = trim($value); if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $value) !== 1) { throw new InvalidArgumentException($label . '必须是 YYYY-MM-DD。'); } [$year, $month, $day] = array_map('intval', explode('-', $value)); if (! checkdate($month, $day, $year)) { throw new InvalidArgumentException($label . '不是有效日期。'); } return $value; } }