'FLG', 'PREMIUM' => 'PRM', 'STANDARD' => 'STD', 'DELUXE' => 'DLX', // edition 'HONOR' => 'HNR', 'CHAMPION' => 'CHP', 'ANNIV' => 'ANV', // trans '1SPEED' => '1SP', // drive/fuel/body 等一般已短,不必压缩 ]; /** 入口:生成 sku_code */ public static function build(array $s, array $opts = []): string { $maxLen = $opts['max_len'] ?? 40; $ensureUnique = $opts['ensure_unique'] ?? true; $tokens = [ 'brand' => self::tok($s['brand_code'] ?? ''), 'series' => self::tok($s['series_code'] ?? ''), 'year' => isset($s['model_year']) ? (string)intval($s['model_year']) : '', 'fuel' => self::tok($s['fuel_type_code'] ?? ''), 'drive' => self::tok($s['drive_type_code'] ?? ''), 'spec' => self::buildSpec($s, self::tok($s['fuel_type_code'] ?? '')), 'level' => self::abbr(self::tok($s['trim_level_code'] ?? '')), 'edition' => self::abbr(self::tok($s['edition_code'] ?? '')), ]; // 过滤空段并按既定顺序拼接 $order = ['brand','series','year','fuel','drive','spec','level','edition']; $parts = []; foreach ($order as $k) { if (!empty($tokens[$k])) $parts[$k] = $tokens[$k]; } $sku = self::normalizeSku(implode('-', $parts)); if (strlen($sku) > $maxLen) { $sku = self::shrink($parts, $maxLen); // 传入“带键名的 parts” } if ($ensureUnique) { $sku = self::ensureUnique($sku, $maxLen); } return $sku; } /** 构造规格位(SPEC),按能源类型差异化 */ private static function buildSpec(array $s, string $fuel): string { $fuel = strtoupper($fuel); if ($fuel === 'EV') { if (!empty($s['range_km'])) { $r = (int)$s['range_km']; $r10 = (int)round($r / 10) * 10; // 到 10 位 return (string)$r10; } if (!empty($s['battery_kwh'])) { return self::numToken($s['battery_kwh']) . 'KWH'; } return ''; } if ($fuel === 'GAS') { $bits = []; if (!empty($s['engine_disp_l'])) { $bits[] = self::dispToken($s['engine_disp_l']); // 1.5L -> 15L } if (!empty($s['engine_turbo']) && intval($s['engine_turbo']) === 1) { $bits[] = 'T'; } if (!empty($s['transmission_code'])) { $bits[] = self::abbr(self::tok($s['transmission_code'])); } return implode('', $bits); // 如 15LTCVT } // HEV/PHEV/EREV 等 $bits = []; if (!empty($s['engine_disp_l'])) { $bits[] = self::dispToken($s['engine_disp_l']); // 可空 } if (!empty($s['motor_layout_code'])) { $bits[] = self::tok($s['motor_layout_code']); // 如 AWD / DM-I / DM-P } return implode('', $bits); } /** 冲突检测并追加短校验(全局唯一) */ private static function ensureUnique(string $sku, int $maxLen): string { if (!VehVehicleSku::where('sku_code', $sku)->exists()) { return $sku; } // 用 crc32 生成 5~6 位 base36 校验(稳定且短) $crc = self::base36Crc($sku); $suffix = '-X' . $crc; if (strlen($sku . $suffix) > $maxLen) { $sku = substr($sku, 0, max(0, $maxLen - strlen($suffix))); $sku = rtrim($sku, '-'); // 避免尾部 '-' } $sku .= $suffix; // 若极端情况下仍冲突,追加递增计数 $i = 2; $candidate = $sku; while (VehVehicleSku::where('sku_code', $candidate)->exists()) { $extra = '-'.$i; $cut = max(0, $maxLen - strlen($extra)); $candidate = substr($sku, 0, $cut) . $extra; $i++; } return $candidate; } /** 把 token 数组尽量压到 maxLen(逐步压缩策略) */ private static function shrink(array $partsByKey, int $maxLen): string { // 1) 先对 series/level/edition 做激进压缩 $p = $partsByKey; foreach (['series','level','edition'] as $k) { if (!empty($p[$k])) $p[$k] = self::heavyAbbr($p[$k]); } $sku = self::normalizeSku(implode('-', $p)); if (strlen($sku) <= $maxLen) return $sku; // 2) 依次移除不太关键的信息:edition → level(若存在) if (!empty($p['edition'])) { unset($p['edition']); } $sku = self::normalizeSku(implode('-', $p)); if (strlen($sku) <= $maxLen) return $sku; if (!empty($p['level'])) { unset($p['level']); } $sku = self::normalizeSku(implode('-', $p)); if (strlen($sku) <= $maxLen) return $sku; // 3) 压缩 spec(如去 KM/KWH,仅保留核心) if (!empty($p['spec'])) { $p['spec'] = self::compactSpec($p['spec']); } $sku = self::normalizeSku(implode('-', $p)); if (strlen($sku) <= $maxLen) return $sku; // 4) 仍超长:从右向左截断次要段(spec/drive/year),保留前三段(brand/series/fuel)优先 $dropOrder = ['spec','drive','year']; $q = $p; foreach ($dropOrder as $k) { if (!empty($q[$k])) { unset($q[$k]); } $sku = self::normalizeSku(implode('-', $q)); if (strlen($sku) <= $maxLen) return $sku; } // 5) 最后兜底:硬截到 maxLen,后续 ensureUnique 会追加 -X 校验并避免冲突 return substr(self::normalizeSku(implode('-', $p)), 0, $maxLen); } /* ===== Helpers ===== */ private static function tok($v): string { $v = strtoupper((string)$v); return preg_replace('/[^A-Z0-9_]/', '', $v ?? ''); } private static function abbr(string $t): string { return self::ABBR[$t] ?? $t; } private static function heavyAbbr(string $t): string { // 更激进:去除元音,连字符化 $t = str_replace(['STANDARD','FLAGSHIP','PREMIUM','DELUXE'], ['STD','FLG','PRM','DLX'], $t); $t = preg_replace('/[AEIOU]/', '', $t); return $t; } private static function compactSpec(string $s): string { // 规则:去 KM/KWH 等单位,只保留数字与核心字母 $s = strtoupper($s); $s = str_replace(['KM','KWH'], '', $s); return $s; } private static function numToken($v): string { return preg_replace('/[^0-9]/', '', (string)$v); } /** 1.5L -> 15L;2.0L -> 20L */ private static function dispToken($l): string { $x = (float)$l; return (string)round($x * 10) . 'L'; } private static function base36Crc(string $s): string { $crc = sprintf('%u', crc32($s)); // 无符号 $n = (int)$crc; $chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'; $res = ''; do { $res = $chars[$n % 36] . $res; $n = intdiv($n, 36); } while ($n > 0); return substr($res, -6); // 取后6位足够 } private static function normalizeSku(string $s): string { // 连续的 '-' 合并,首尾修剪 $s = preg_replace('/-+/', '-', $s); $s = trim($s, '-'); return $s; } }