| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110 |
- import fs from 'fs/promises';
- import path from 'path';
- import { logger } from '../utils/logger.js';
- export interface ChinaRegionOption {
- label: string;
- value: string;
- children?: ChinaRegionOption[];
- }
- interface CachedRegions {
- mtimeMs: number;
- regions: ChinaRegionOption[];
- }
- function getServerRootDir(): string {
- return path.basename(process.cwd()).toLowerCase() === 'server'
- ? process.cwd()
- : path.resolve(process.cwd(), 'server');
- }
- function normalizeCsvCell(value: string): string {
- return String(value || '')
- .replace(/^\uFEFF/, '')
- .trim();
- }
- export class RegionService {
- private cache: CachedRegions | null = null;
- async getChinaRegionsFromCsv(): Promise<ChinaRegionOption[]> {
- const serverRoot = getServerRootDir();
- const csvPath = path.resolve(serverRoot, '中国地区编码表.csv');
- let stat: { mtimeMs: number } | null = null;
- try {
- stat = await fs.stat(csvPath);
- } catch {
- logger.warn(`[RegionService] csv not found: ${csvPath}`);
- return [];
- }
- if (this.cache && this.cache.mtimeMs === stat.mtimeMs) return this.cache.regions;
- const content = await fs.readFile(csvPath, 'utf-8');
- const lines = content.split(/\r?\n/).filter(Boolean);
- if (lines.length <= 1) return [];
- const provinceMap = new Map<string, ChinaRegionOption>();
- const cityMapByProvince = new Map<string, Map<string, ChinaRegionOption>>();
- const districtMapByCity = new Map<string, Set<string>>();
- for (let i = 1; i < lines.length; i++) {
- const rawLine = lines[i];
- if (!rawLine) continue;
- const parts = rawLine.split(',');
- if (parts.length < 6) continue;
- const provinceName = normalizeCsvCell(parts[0]);
- const provinceCode = normalizeCsvCell(parts[1]);
- const cityName = normalizeCsvCell(parts[2]);
- const cityCode = normalizeCsvCell(parts[3]);
- const districtName = normalizeCsvCell(parts[4]);
- const districtCode = normalizeCsvCell(parts[5]);
- if (!provinceCode || !provinceName) continue;
- if (!cityCode || !cityName) continue;
- if (!districtCode || !districtName) continue;
- let province = provinceMap.get(provinceCode);
- if (!province) {
- province = { label: provinceName, value: provinceCode, children: [] };
- provinceMap.set(provinceCode, province);
- cityMapByProvince.set(provinceCode, new Map());
- }
- const cityMap = cityMapByProvince.get(provinceCode)!;
- let city = cityMap.get(cityCode);
- if (!city) {
- city = { label: cityName, value: cityCode, children: [] };
- cityMap.set(cityCode, city);
- province.children!.push(city);
- }
- const districtKey = `${cityCode}:${districtCode}`;
- if (!districtMapByCity.has(cityCode)) districtMapByCity.set(cityCode, new Set());
- const districtSet = districtMapByCity.get(cityCode)!;
- if (districtSet.has(districtKey)) continue;
- districtSet.add(districtKey);
- city.children!.push({ label: districtName, value: districtCode });
- }
- const regions = Array.from(provinceMap.values())
- .sort((a, b) => Number(a.value) - Number(b.value))
- .map(p => ({
- ...p,
- children: (p.children || [])
- .slice()
- .sort((a, b) => Number(a.value) - Number(b.value))
- .map(c => ({
- ...c,
- children: (c.children || []).slice().sort((a, b) => Number(a.value) - Number(b.value)),
- })),
- }));
- this.cache = { mtimeMs: stat.mtimeMs, regions };
- return regions;
- }
- }
|