['平台', 'platform'], 'violation_category' => ['违规分类', '分类', 'violation_category'], 'violation_type' => ['违规类型', '类型', 'violation_type'], 'match_keywords' => ['匹配关键词', '关键词', 'match_keywords'], 'alert_level' => ['预警级别', '级别', '预警等级', 'alert_level'], 'needs_alert' => ['是否需要提醒', '需提醒', '需要提醒', 'needs_alert'], 'suggested_action' => ['建议处理动作', '处理建议', '建议动作', 'suggested_action'], 'handling_hours' => ['处理时效', '时效', 'handling_hours'], 'remind_channel' => ['提醒方式', 'remind_channel'], 'sort' => ['排序', 'sort'], 'enabled' => ['启用', 'enabled'], 'remark' => ['备注', 'remark'], ]; private const PLATFORM_MAP = [ '淘系' => 'taobao', '淘宝' => 'taobao', '天猫' => 'taobao', '京东' => 'jd', '京喜' => 'jingxi', '抖音' => 'douyin', '拼多多' => 'pinduoduo', '阿里巴巴' => 'alibaba', ]; public function handle(): int { $file = (string) $this->argument('file'); if (! is_file($file)) { $this->error('文件不存在:' . $file); return self::FAILURE; } try { $reader = IOFactory::createReaderForFile($file); if (method_exists($reader, 'setReadDataOnly')) { $reader->setReadDataOnly(true); } if (method_exists($reader, 'setReadFilter')) { // 字典文件通常只有几十行;部分导出文件带巨大空白维度,限制读取范围。 $reader->setReadFilter(new class implements \PhpOffice\PhpSpreadsheet\Reader\IReadFilter { public function readCell($columnAddress, $row, $worksheetName = ''): bool { return $row <= 5000; } }); } $matrix = $reader->load($file)->getActiveSheet()->toArray(null, true, true, false); } catch (Throwable $exception) { $this->error('无法读取文件:' . $exception->getMessage()); return self::FAILURE; } if ($matrix === []) { $this->error('文件为空。'); return self::FAILURE; } $map = []; foreach ($matrix[0] as $index => $cell) { $label = mb_strtolower(trim((string) $cell)); foreach (self::HEADERS as $key => $aliases) { foreach ($aliases as $alias) { if ($label === mb_strtolower($alias)) { $map[$key] = $index; break 2; } } } } if (! isset($map['platform'], $map['violation_type'])) { $this->error('缺少必需表头:平台、违规类型。'); return self::FAILURE; } $connection = DB::connection((string) config('upstream_mirrors.connection', 'business_mirror')); if (! app(StoreViolationRuleRepository::class)->available()) { $this->error('违规字典表尚未建立,请先执行数据库迁移。'); return self::FAILURE; } $created = 0; $updated = 0; $skipped = 0; $sort = 0; foreach (array_slice($matrix, 1) as $row) { $value = static fn (string $key): string => isset($map[$key]) ? trim((string) ($row[$map[$key]] ?? '')) : ''; $platform = $value('platform'); $platform = self::PLATFORM_MAP[$platform] ?? mb_strtolower($platform); $type = $value('violation_type'); if ($platform === '' || $type === '') { $skipped++; continue; } $sort += 10; $levelRaw = strtoupper($value('alert_level')); $level = in_array($levelRaw, ['P0', 'P1', 'P2'], true) ? $levelRaw : null; $needsAlertRaw = mb_strtolower($value('needs_alert')); $needsAlert = ! in_array($needsAlertRaw, ['否', 'no', '0', 'false'], true); $hoursRaw = $value('handling_hours'); $hours = is_numeric($hoursRaw) ? max((int) $hoursRaw, 0) : null; $payload = [ 'platform' => $platform, 'violation_category' => $value('violation_category') !== '' ? mb_substr($value('violation_category'), 0, 64) : null, 'violation_type' => mb_substr($type, 0, 500), 'match_keywords' => $value('match_keywords') !== '' ? mb_substr($value('match_keywords'), 0, 500) : null, 'alert_level' => $needsAlert ? $level : null, 'needs_alert' => $needsAlert ? 1 : 0, 'suggested_action' => $value('suggested_action') !== '' ? mb_substr($value('suggested_action'), 0, 500) : null, 'handling_hours' => $hours, 'remind_channel' => $value('remind_channel') !== '' ? mb_substr($value('remind_channel'), 0, 191) : null, 'sort' => is_numeric($value('sort')) ? (int) $value('sort') : $sort, 'enabled' => ! in_array(mb_strtolower($value('enabled')), ['否', 'no', '0', 'false'], true) ? 1 : 0, 'remark' => $value('remark') !== '' ? mb_substr($value('remark'), 0, 191) : null, 'updated_at' => now(), ]; $exists = $connection->table(StoreViolationRuleRepository::TABLE) ->where('platform', $platform) ->where('violation_type', $payload['violation_type']) ->exists(); if ($this->option('dry-run')) { $exists ? $updated++ : $created++; continue; } if ($exists) { $connection->table(StoreViolationRuleRepository::TABLE) ->where('platform', $platform) ->where('violation_type', $payload['violation_type']) ->update($payload); $updated++; } else { $connection->table(StoreViolationRuleRepository::TABLE)->insert($payload + [ 'created_at' => now(), ]); $created++; } } $this->info(sprintf( '字典导入%s:新增 %d 条,更新 %d 条,跳过 %d 条。', $this->option('dry-run') ? '(试运行,未写库)' : '完成', $created, $updated, $skipped, )); $this->line('提示:需要让存量记录套用新字典时,执行 store-violations:recalc-alerts。'); return self::SUCCESS; } }