85 lines
3.1 KiB
JavaScript
85 lines
3.1 KiB
JavaScript
/**
|
|
* 一次性数据迁移: 把 Indicator.data (JSON) 拆分到 IndicatorPoint 明细表
|
|
* 用法: 在迁移 A (建表+加列) 之后、迁移 B (删 data 列) 之前运行
|
|
* 幂等: 同 (indicatorId, period) 重复插入会更新 value, 可安全重跑
|
|
*/
|
|
require('dotenv').config()
|
|
const mysql = require('mysql2/promise')
|
|
|
|
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(/^\//, ''),
|
|
}
|
|
}
|
|
|
|
// 根据 row 的时间字段推断频率: year | quarter | month | day
|
|
function inferFrequency(row) {
|
|
if (row && row.month !== undefined) return 'month'
|
|
if (row && row.year !== undefined) return 'year'
|
|
const p = String((row && row.period) || '')
|
|
if (/^\d{4}$/.test(p)) return 'year'
|
|
if (/^\d{4}-Q[1-4]$/.test(p)) return 'quarter'
|
|
if (/^\d{4}-\d{2}$/.test(p)) return 'month'
|
|
if (/^\d{4}-\d{2}-\d{2}$/.test(p)) return 'day'
|
|
return 'month'
|
|
}
|
|
|
|
// 统一 row 的时间字段为 period
|
|
function periodOf(row) {
|
|
if (!row) return ''
|
|
return String(row.period !== undefined ? row.period : (row.month !== undefined ? row.month : row.year))
|
|
}
|
|
|
|
async function main() {
|
|
const conn = await mysql.createConnection(parseUrl(process.env.DATABASE_URL))
|
|
try {
|
|
const [rows] = await conn.execute('SELECT id, name, groupName, data FROM Indicator')
|
|
console.log('[Migrate] 读取', rows.length, '个 Indicator')
|
|
|
|
let points = 0, updated = 0
|
|
for (const ind of rows) {
|
|
// mysql2 对 JSON 列已自动解析为对象, 兼容字符串兜底
|
|
let data = ind.data
|
|
if (typeof data === 'string') {
|
|
try { data = JSON.parse(data) } catch { data = null }
|
|
}
|
|
const title = (data && data.title) || ind.name
|
|
const unit = (data && data.unit) || ''
|
|
const source = (data && data.source) || ''
|
|
const rowsArr = (data && Array.isArray(data.rows)) ? data.rows : []
|
|
const freq = rowsArr.length ? inferFrequency(rowsArr[0]) : 'month'
|
|
|
|
// 更新 Indicator 元数据列
|
|
await conn.execute(
|
|
'UPDATE Indicator SET title=?, unit=?, source=?, frequency=?, updatedAt=NOW(3) WHERE id=?',
|
|
[title, unit, source, freq, ind.id],
|
|
)
|
|
updated++
|
|
|
|
// 写入明细
|
|
for (const r of rowsArr) {
|
|
const period = periodOf(r)
|
|
const value = Number(r.value)
|
|
if (!period || Number.isNaN(value)) continue
|
|
const [res] = await conn.execute(
|
|
'INSERT INTO IndicatorPoint (indicatorId, period, value, createdAt, updatedAt) VALUES (?, ?, ?, NOW(3), NOW(3)) ' +
|
|
'ON DUPLICATE KEY UPDATE value=VALUES(value), updatedAt=NOW(3)',
|
|
[ind.id, period, value],
|
|
)
|
|
points += (res.affectedRows > 0 ? 1 : 0)
|
|
}
|
|
console.log('[Migrate]', ind.name, '->', rowsArr.length, '个点, 频率', freq)
|
|
}
|
|
console.log('[Migrate] 完成: 更新', updated, '个 Indicator, 写入/更新', points, '个明细点')
|
|
} finally {
|
|
await conn.end()
|
|
}
|
|
}
|
|
|
|
main().catch((e) => { console.error('[Migrate] 失败:', e); process.exit(1) })
|