CaiWuXiTong/app/Business/JstOrderLabelImporter.php
qiuxiang 77ac3c58fa 聚水潭订单标签明细筛选选项改为缓存并预热
233 万行上做 distinct 扫描要几秒;新增 JstOrderLabelOptions(按公司缓存 24 小时),页面读取缓存,导入命令结束时失效并预热,避免每次打开页面都等待。
2026-09-30 17:12:27 +08:00

159 lines
5.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
namespace App\Business;
use Illuminate\Database\DatabaseManager;
use InvalidArgumentException;
use RuntimeException;
/**
* 聚水潭订单标签明细导入(从共享镜像 ops_jst_orders 拆标签写入 jst_order_labels)。
*
* 一个标签一行:一单有 N 个标签就写 N 行;订单不去重、标签不汇总。
* 无标签的订单保留一行,标签记为「(无标签)」,方便与订单总数核对。
*/
class JstOrderLabelImporter
{
public const NO_LABEL = '(无标签)';
private const READ_CHUNK = 2000;
private const INSERT_CHUNK = 2000;
public function __construct(private readonly DatabaseManager $databases) {}
/**
* @return array{from:string,to:string,orders:int,rows:int,no_label_orders:int,labels:int,seconds:float}
*/
public function import(string $from, string $to, int $companyId = 1, bool $replace = false, string $source = 'jst-order-label-import'): array
{
$from = $this->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<string> */
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;
}
}