/** * AreaCity 全国行政区划数据导入脚本 * ================================== * 数据源: https://github.com/xiangyuecn/AreaCity-JsSpider-StatsGov * 下载: https://github.com/xiangyuecn/AreaCity-JsSpider-StatsGov/releases/download/2025.251231.260403/ok_data_level3-4.csv.7z * (Gitee 备用: https://gitee.com/xiangyuecn/AreaCity-JsSpider-StatsGov/releases) * 说明: 解压后得到 ok_data_level4.csv(省市区镇四级,UTF-8 带 BOM) * 列: id, pid, deep, name, pinyin_prefix, pinyin, ext_id, ext_name * deep: 0=省 1=市(地级/直辖市虚拟) 2=县(区/县级市) 3=乡镇(镇/乡/街道) * * 用法: * 1) 下载并解压 ok_data_level4.csv 到本目录 tmp/ok_data_level4.csv * 2) npm i mysql2 * 3) node import-regions.mjs * 脚本会 DROP 并重建 Regions 表后导入(幂等,可重复执行)。 */ import fs from 'fs' import path from 'path' import mysql from 'mysql2/promise' import { fileURLToPath } from 'url' const __dirname = path.dirname(fileURLToPath(import.meta.url)) const root = path.resolve(__dirname, '../..') const csvPath = path.join(__dirname, 'tmp/ok_data_level4.csv') if (!fs.existsSync(csvPath)) { console.error('未找到 ' + csvPath + ',请先下载并解压数据文件') process.exit(1) } // ---------- 读取数据库连接串 ---------- const appCfg = JSON.parse(fs.readFileSync(path.join(root, 'backend/AgriculturalPlatform.Api/appsettings.json'), 'utf8')) const parseCs = (s) => Object.fromEntries( s.split(';').filter(Boolean).map(kv => { const i = kv.indexOf('=') return [kv.slice(0, i).trim().toLowerCase(), kv.slice(i + 1).trim()] }) ) const dbc = parseCs(appCfg.ConnectionStrings.Default) // ---------- 解析 CSV ---------- const raw = fs.readFileSync(csvPath, 'utf8').replace(/^\uFEFF/, '') const lines = raw.split(/\r?\n/).filter(l => l.trim().length > 0) lines.shift() // 去掉表头 const parseRow = (line) => { const fields = [] let cur = '', inQ = false for (const ch of line) { if (ch === '"') inQ = !inQ else if (ch === ',' && !inQ) { fields.push(cur); cur = '' } else cur += ch } fields.push(cur) return fields } // 常用省份(热度高的排前面,值越大越靠前,供前端优先展示) const HOT_PROVINCES = [ '四川省', '广西壮族自治区', '陕西省', '重庆市', '安徽省', '云南省', '贵州省', '河南省', '山东省', '湖北省', '湖南省', '甘肃省', '新疆维吾尔自治区', '江西省', '福建省', '广东省', '河北省', '山西省', '江苏省', '浙江省' ] const hotOf = (deep, extName) => { if (deep !== 0) return 0 const i = HOT_PROVINCES.indexOf(extName) return i < 0 ? 0 : HOT_PROVINCES.length - i } const rows = [] for (const line of lines) { const [id, pid, deep, name, pfx, pinyin, , extName] = parseRow(line) if (!id) continue rows.push([Number(id), Number(pid), Number(deep), name, extName, pinyin, pfx, hotOf(Number(deep), extName)]) } console.log('解析完成,共 ' + rows.length + ' 条') const stat = {} for (const r of rows) stat[r[2]] = (stat[r[2]] || 0) + 1 console.log('层级分布(deep):', JSON.stringify(stat)) // ---------- 建表并导入 ---------- const conn = await mysql.createConnection({ host: dbc.server, port: Number(dbc.port || 3306), user: dbc.user, password: dbc.password, database: dbc.database, charset: 'utf8mb4' }) await conn.query('DROP TABLE IF EXISTS Regions') await conn.query(`CREATE TABLE Regions ( Id INT NOT NULL PRIMARY KEY, Pid INT NOT NULL, Deep INT NOT NULL, Name VARCHAR(100) NOT NULL, ExtName VARCHAR(100) NOT NULL, Pinyin VARCHAR(200) NOT NULL DEFAULT '', PinyinPrefix VARCHAR(10) NOT NULL DEFAULT '', Hot INT NOT NULL DEFAULT 0, KEY idx_regions_pid (Pid), KEY idx_regions_deep (Deep) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci`) const BATCH = 1000 for (let i = 0; i < rows.length; i += BATCH) { const chunk = rows.slice(i, i + BATCH) await conn.query('INSERT INTO Regions (Id, Pid, Deep, Name, ExtName, Pinyin, PinyinPrefix, Hot) VALUES ?', [chunk]) } const [cnt] = await conn.query('SELECT COUNT(*) AS c FROM Regions') console.log('导入完成,Regions 表共 ' + cnt[0].c + ' 条记录') await conn.end()