generate-area-sql.mjs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202
  1. #!/usr/bin/env node
  2. /**
  3. * 区域数据CSV转SQL导入脚本
  4. * 将 docs/省市区.csv 转换为 area_data 表的 INSERT 语句
  5. * 使用方法: node docs/generate-area-sql.mjs
  6. */
  7. import fs from 'fs';
  8. import path from 'path';
  9. import { fileURLToPath } from 'url';
  10. // 获取当前目录
  11. const __filename = fileURLToPath(import.meta.url);
  12. const __dirname = path.dirname(__filename);
  13. // 文件路径
  14. const csvFilePath = path.join(__dirname, '省市区.csv');
  15. const outputSqlPath = path.join(__dirname, 'area_data_init.sql');
  16. // 读取CSV文件
  17. function readCSV(filePath) {
  18. try {
  19. const content = fs.readFileSync(filePath, 'utf-8');
  20. return content.trim().split('\n');
  21. } catch (error) {
  22. console.error('读取CSV文件失败:', error.message);
  23. process.exit(1);
  24. }
  25. }
  26. // 解析CSV行
  27. function parseCSVLine(line) {
  28. // 处理包含逗号的字段
  29. const parts = line.split(',');
  30. if (parts.length !== 3) {
  31. console.warn('跳过格式不正确的行:', line);
  32. return null;
  33. }
  34. const [id, parentId, name] = parts;
  35. const parsedId = parseInt(id.trim());
  36. const parsedParentId = parseInt(parentId.trim()) || null;
  37. const parsedName = name.trim();
  38. // 推断层级和行政区划代码
  39. let level, code;
  40. if (parsedParentId === 0 || parsedParentId === null) {
  41. level = 1; // 省/直辖市
  42. code = parsedId.toString().padStart(6, '0'); // 省级代码
  43. } else if (parsedId < 1000) {
  44. level = 2; // 市
  45. code = parsedId.toString().padStart(6, '0'); // 市级代码
  46. } else {
  47. level = 3; // 区/县
  48. code = parsedId.toString().padStart(6, '0'); // 区县级代码
  49. }
  50. return {
  51. id: parsedId,
  52. parentId: parsedParentId,
  53. name: parsedName,
  54. level,
  55. code
  56. };
  57. }
  58. // 生成SQL语句
  59. function generateSQLStatements(data) {
  60. const statements = [
  61. '-- 区域数据初始化脚本',
  62. '-- 来源: scripts/省市区.csv',
  63. '-- 生成时间: ' + new Date().toISOString(),
  64. '-- 数据库: PostgreSQL',
  65. '',
  66. '-- 清空现有数据(可选)',
  67. '-- TRUNCATE TABLE areas;',
  68. '',
  69. '-- 插入区域数据',
  70. 'INSERT INTO areas (id, parent_id, name, level, code, is_disabled, is_deleted, created_at, updated_at) VALUES'
  71. ];
  72. const values = data.map(item =>
  73. ` (${item.id}, ${item.parentId === 0 ? 'NULL' : item.parentId}, '${escapeString(item.name)}', ${item.level}, '${item.code}', 0, 0, NOW(), NOW())`
  74. );
  75. statements.push(values.join(',\n'));
  76. statements.push('ON CONFLICT (id) DO UPDATE SET');
  77. statements.push(' parent_id = EXCLUDED.parent_id,');
  78. statements.push(' name = EXCLUDED.name,');
  79. statements.push(' level = EXCLUDED.level,');
  80. statements.push(' code = EXCLUDED.code,');
  81. statements.push(' updated_at = NOW();');
  82. return statements.join('\n');
  83. }
  84. // 转义SQL字符串
  85. function escapeString(str) {
  86. return str.replace(/'/g, "''");
  87. }
  88. // 验证数据完整性
  89. function validateData(data) {
  90. const ids = new Set();
  91. const parentIds = new Set();
  92. const levels = { 1: 0, 2: 0, 3: 0 };
  93. for (const item of data) {
  94. if (ids.has(item.id)) {
  95. console.warn(`重复的ID: ${item.id}`);
  96. }
  97. ids.add(item.id);
  98. if (item.parentId !== null && item.parentId !== 0) {
  99. parentIds.add(item.parentId);
  100. }
  101. // 统计各层级数量
  102. if (item.level >= 1 && item.level <= 3) {
  103. levels[item.level]++;
  104. }
  105. }
  106. // 检查所有parentId是否都存在对应的记录
  107. for (const parentId of parentIds) {
  108. if (!ids.has(parentId)) {
  109. console.warn(`父ID不存在: ${parentId}`);
  110. }
  111. }
  112. console.log(`总计 ${data.length} 条区域数据`);
  113. console.log(`顶级区域数量: ${data.filter(item => item.parentId === 0 || item.parentId === null).length}`);
  114. console.log(`省级区域数量: ${levels[1]}`);
  115. console.log(`市级区域数量: ${levels[2]}`);
  116. console.log(`区县级区域数量: ${levels[3]}`);
  117. }
  118. // 主函数
  119. function main() {
  120. console.log('开始生成区域数据SQL文件...');
  121. // 检查CSV文件是否存在
  122. if (!fs.existsSync(csvFilePath)) {
  123. console.error('CSV文件不存在:', csvFilePath);
  124. process.exit(1);
  125. }
  126. // 读取并解析CSV
  127. const lines = readCSV(csvFilePath);
  128. // 跳过表头
  129. const header = lines[0];
  130. if (header !== 'aId,aFId,aName') {
  131. console.warn('CSV文件格式可能不正确,表头不匹配');
  132. }
  133. const data = [];
  134. for (let i = 1; i < lines.length; i++) {
  135. const line = lines[i].trim();
  136. if (line) {
  137. const item = parseCSVLine(line);
  138. if (item) {
  139. data.push(item);
  140. }
  141. }
  142. }
  143. // 验证数据
  144. validateData(data);
  145. // 生成SQL
  146. const sql = generateSQLStatements(data);
  147. // 写入SQL文件
  148. try {
  149. fs.writeFileSync(outputSqlPath, sql, 'utf-8');
  150. console.log('SQL文件已生成:', outputSqlPath);
  151. console.log('文件大小:', (fs.statSync(outputSqlPath).size / 1024).toFixed(2), 'KB');
  152. } catch (error) {
  153. console.error('写入SQL文件失败:', error.message);
  154. process.exit(1);
  155. }
  156. // 生成使用说明
  157. console.log('\n使用说明:');
  158. console.log('1. 运行: node scripts/generate-area-sql.mjs');
  159. console.log('2. 执行生成的SQL文件: psql -h 127.0.0.1 -U postgres -d postgres -f scripts/area_data_init.sql');
  160. console.log('3. 或者直接在数据库管理工具中执行SQL内容');
  161. }
  162. // 执行主函数
  163. if (import.meta.url === `file://${process.argv[1]}`) {
  164. main();
  165. }
  166. export {
  167. readCSV,
  168. parseCSVLine,
  169. generateSQLStatements,
  170. validateData
  171. };