初始提交:全国记者站管理系统
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* 数据库迁移运行器
|
||||
* 按顺序执行 migrations/ 目录下的所有 .sql 文件
|
||||
* 记录已执行的迁移到 schema_migrations 表(幂等性保障)
|
||||
*
|
||||
* 用法:
|
||||
* node migrations/_runner.js # 执行所有待执行迁移
|
||||
* node migrations/_runner.js --status # 查看迁移状态
|
||||
* node migrations/_runner.js --reset # 重置数据库(慎用)
|
||||
*/
|
||||
|
||||
import { DatabaseSync } from 'node:sqlite'
|
||||
import { readFileSync, readdirSync, mkdirSync, unlinkSync, existsSync } from 'node:fs'
|
||||
import { join, dirname } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const root = join(__dirname, '..')
|
||||
const dataDir = join(root, 'data')
|
||||
|
||||
mkdirSync(dataDir, { recursive: true })
|
||||
const dbPath = join(dataDir, 'reporter-station.db')
|
||||
|
||||
/** 初始化数据库连接(确保 schema_migrations 表存在) */
|
||||
function initDb() {
|
||||
const db = new DatabaseSync(dbPath)
|
||||
db.exec(`
|
||||
PRAGMA journal_mode = WAL;
|
||||
PRAGMA foreign_keys = ON;
|
||||
CREATE TABLE IF NOT EXISTS schema_migrations (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
applied_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
|
||||
);
|
||||
`)
|
||||
return db
|
||||
}
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
|
||||
if (args.includes('--status')) {
|
||||
const db = initDb()
|
||||
const applied = db.prepare('SELECT name, applied_at FROM schema_migrations ORDER BY id').all()
|
||||
const allFiles = readdirSync(__dirname)
|
||||
.filter(f => f.endsWith('.sql') && !f.startsWith('_'))
|
||||
.sort()
|
||||
|
||||
console.log('\n迁移状态:')
|
||||
for (const f of allFiles) {
|
||||
const name = f.replace('.sql', '')
|
||||
const row = applied.find(a => a.name === name)
|
||||
if (row) {
|
||||
console.log(` [x] ${name} (applied at ${row.applied_at})`)
|
||||
} else {
|
||||
console.log(` [ ] ${name}`)
|
||||
}
|
||||
}
|
||||
db.close()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (args.includes('--seed')) {
|
||||
const db = initDb()
|
||||
const seedFile = '002_seed_data.sql'
|
||||
const sql = readFileSync(join(__dirname, seedFile), 'utf-8')
|
||||
try {
|
||||
db.exec(sql)
|
||||
console.log(`已重新执行 seed 数据(${seedFile})`)
|
||||
} catch (err) {
|
||||
console.error(`seed 失败: ${err.message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
db.close()
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
if (args.includes('--reset')) {
|
||||
for (const ext of ['', '-wal', '-shm']) {
|
||||
const f = dbPath + ext
|
||||
if (existsSync(f)) { unlinkSync(f); console.log(`已删除: ${f}`) }
|
||||
}
|
||||
console.log('数据库已重置,重新启动服务将自动执行迁移。')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// 默认:执行待执行的迁移
|
||||
const db = initDb()
|
||||
const applied = new Set(db.prepare('SELECT name FROM schema_migrations').all().map(r => r.name))
|
||||
const allFiles = readdirSync(__dirname)
|
||||
.filter(f => f.endsWith('.sql') && !f.startsWith('_'))
|
||||
.sort()
|
||||
|
||||
let appliedCount = 0
|
||||
for (const file of allFiles) {
|
||||
const name = file.replace('.sql', '')
|
||||
if (applied.has(name)) {
|
||||
console.log(` 跳过 (已执行): ${name}`)
|
||||
continue
|
||||
}
|
||||
const sql = readFileSync(join(__dirname, file), 'utf-8')
|
||||
try {
|
||||
db.exec('BEGIN')
|
||||
db.exec(sql)
|
||||
db.prepare('INSERT INTO schema_migrations (name) VALUES (?)').run(name)
|
||||
db.exec('COMMIT')
|
||||
console.log(` [x] ${name}`)
|
||||
appliedCount++
|
||||
} catch (err) {
|
||||
db.exec('ROLLBACK')
|
||||
console.error(` [!] ${name} 执行失败: ${err.message}`)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
db.close()
|
||||
|
||||
if (appliedCount === 0) {
|
||||
console.log('\n没有待执行的迁移,数据库已是最新。')
|
||||
} else {
|
||||
console.log(`\n成功执行 ${appliedCount} 个迁移。`)
|
||||
}
|
||||
Reference in New Issue
Block a user