141 lines
5.5 KiB
JavaScript
141 lines
5.5 KiB
JavaScript
/**
|
||
* iBoard 基础数据初始化脚本
|
||
* 用法: npm run db:seed
|
||
* 幂等: 主键/唯一键冲突跳过, Strategy/Indicator 按 name 存在则更新
|
||
*/
|
||
|
||
require('dotenv').config();
|
||
const crypto = require('crypto');
|
||
const mysql = require('mysql2/promise');
|
||
|
||
function hashPassword(plain) {
|
||
const salt = crypto.randomBytes(16).toString('hex');
|
||
const hash = crypto.scryptSync(plain, salt, 64).toString('hex');
|
||
return 'scrypt$' + salt + '$' + hash;
|
||
}
|
||
|
||
function parseUrl(url) {
|
||
const u = new URL(url);
|
||
return {
|
||
host: u.hostname,
|
||
port: Number(u.port) || 3306,
|
||
user: decodeURIComponent(u.username),
|
||
password: decodeURIComponent(u.password),
|
||
database: u.pathname.replace(/^\//, ''),
|
||
};
|
||
}
|
||
|
||
async function insertIgnore(conn, sql, params) {
|
||
try {
|
||
const [r] = await conn.execute(sql, params);
|
||
return r.affectedRows;
|
||
} catch (e) {
|
||
if (e.code === 'ER_DUP_ENTRY') return 0;
|
||
throw e;
|
||
}
|
||
}
|
||
|
||
async function upsertByName(conn, table, payload, keyField) {
|
||
const fields = Object.keys(payload);
|
||
const placeholders = fields.map(() => '?').join(', ');
|
||
const updateClause = fields
|
||
.filter((f) => f !== keyField)
|
||
.map((f) => '`' + f + '` = VALUES(`' + f + '`)')
|
||
.join(', ');
|
||
const sql = 'INSERT INTO `' + table + '` (' + fields.map((f) => '`' + f + '`').join(', ') + ') VALUES (' + placeholders + ') ON DUPLICATE KEY UPDATE ' + updateClause;
|
||
const params = fields.map((f) => payload[f]);
|
||
const [r] = await conn.execute(sql, params);
|
||
return r.affectedRows;
|
||
}
|
||
|
||
async function main() {
|
||
const cfg = parseUrl(process.env.DATABASE_URL);
|
||
if (!cfg.host) throw new Error('DATABASE_URL 未配置或解析失败');
|
||
const conn = await mysql.createConnection(cfg);
|
||
console.log('已连接:', cfg.host + ':' + cfg.port + '/' + cfg.database);
|
||
|
||
const adminPwd = hashPassword('admin123');
|
||
const n1 = await insertIgnore(
|
||
conn,
|
||
'INSERT INTO `User` (name, email, password, createdAt, updatedAt) VALUES (?, ?, ?, NOW(3), NOW(3))',
|
||
['Admin', 'admin@iboard.local', adminPwd],
|
||
);
|
||
console.log('[User] 新增管理员: ' + n1 + ' 行 (admin@iboard.local / admin123)');
|
||
|
||
const strategies = [
|
||
{ name: 'us-debt-gdp', description: '美国政府债务/GDP分析',
|
||
data: { source: 'strategy.js', unit: '万亿美元', rows: [
|
||
{ year: '2019', debt: 22.7, gdp: 21.4, debtToGdp: 106.1, interestToGdp: 1.8 },
|
||
{ year: '2020', debt: 26.9, gdp: 20.9, debtToGdp: 128.7, interestToGdp: 1.6 },
|
||
{ year: '2021', debt: 28.4, gdp: 23.0, debtToGdp: 123.5, interestToGdp: 1.5 },
|
||
{ year: '2022', debt: 30.9, gdp: 25.4, debtToGdp: 121.6, interestToGdp: 1.6 },
|
||
{ year: '2023', debt: 33.1, gdp: 26.9, debtToGdp: 123.0, interestToGdp: 1.9 },
|
||
{ year: '2024', debt: 35.3, gdp: 28.5, debtToGdp: 123.9, interestToGdp: 2.1 },
|
||
]},
|
||
},
|
||
{ name: 'china-debt-gdp', description: '中国政府债务/GDP分析',
|
||
data: { source: 'strategy.js', unit: '万亿美元', rows: [
|
||
{ year: '2019', debt: 8.8, gdp: 14.3, debtToGdp: 61.5, interestToGdp: 1.9 },
|
||
{ year: '2020', debt: 10.3, gdp: 14.7, debtToGdp: 70.1, interestToGdp: 1.8 },
|
||
{ year: '2021', debt: 11.7, gdp: 16.1, debtToGdp: 72.7, interestToGdp: 1.7 },
|
||
{ year: '2022', debt: 13.1, gdp: 17.9, debtToGdp: 73.2, interestToGdp: 1.6 },
|
||
{ year: '2023', debt: 14.5, gdp: 19.4, debtToGdp: 74.7, interestToGdp: 1.6 },
|
||
{ year: '2024', debt: 15.9, gdp: 21.0, debtToGdp: 75.7, interestToGdp: 1.5 },
|
||
]},
|
||
},
|
||
];
|
||
let n2 = 0;
|
||
for (const s of strategies) {
|
||
n2 += await upsertByName(conn, 'Strategy',
|
||
{ name: s.name, description: s.description, data: JSON.stringify(s.data), updatedAt: new Date() },
|
||
'name');
|
||
}
|
||
console.log('[Strategy] upsert 策略: ' + n2 + ' 行 (1=新增, 2=更新)');
|
||
|
||
// Indicator 表: 改由 scripts/refresh-indicators.cjs 从 World Bank API 拉取最新数据
|
||
// 旧 4 个金融指标 (国债/LPR) 无稳定 JSON 数据源, 暂不维护
|
||
const indicators = [];
|
||
let n3 = 0;
|
||
for (const i of indicators) {
|
||
n3 += await upsertByName(conn, 'Indicator',
|
||
{ name: i.name, groupName: i.groupName, data: JSON.stringify(i.data), updatedAt: new Date() },
|
||
'name');
|
||
}
|
||
console.log('[Indicator] upsert 指标: ' + n3 + ' 行 (1=新增, 2=更新)');
|
||
|
||
const article = {
|
||
title: '欢迎使用 iBoard',
|
||
slug: 'welcome-to-iboard',
|
||
content: 'iBoard 用于收集和整理经济数据,以策略为主体重新组织。\n\n本文为示例条目,正式文章仍由 content/articles/*.md 文件承载。',
|
||
date: '2026-08-03',
|
||
};
|
||
const n4 = await insertIgnore(
|
||
conn,
|
||
'INSERT INTO `Article` (title, slug, content, date, createdAt, updatedAt) VALUES (?, ?, ?, ?, NOW(3), NOW(3))',
|
||
[article.title, article.slug, article.content, article.date],
|
||
);
|
||
console.log('[Article] 新增示例文章: ' + n4 + ' 行');
|
||
|
||
const sowRows = [
|
||
{ month: '2024-01', inventory: 4067 },
|
||
{ month: '2024-02', inventory: 4042 },
|
||
{ month: '2024-03', inventory: 3992 },
|
||
{ month: '2024-04', inventory: 3986 },
|
||
{ month: '2024-05', inventory: 3996 },
|
||
{ month: '2024-06', inventory: 4038 },
|
||
];
|
||
let n5 = 0;
|
||
for (const r of sowRows) {
|
||
n5 += await insertIgnore(
|
||
conn,
|
||
'INSERT INTO `Econ_SowInventory` (month, inventory, createdAt, updatedAt) VALUES (?, ?, NOW(3), NOW(3))',
|
||
[r.month, r.inventory],
|
||
);
|
||
}
|
||
console.log('[Econ_SowInventory] 新增: ' + n5 + ' 行');
|
||
|
||
await conn.end();
|
||
console.log('\nSeed 完成.');
|
||
}
|
||
|
||
main().catch((e) => { console.error('Seed 失败:', e); process.exit(1); }); |