Files
access-manage/src/db/migrate.ts
T
2026-05-26 11:06:13 +08:00

63 lines
2.2 KiB
TypeScript

import { promises as fs } from "node:fs";
import path from "node:path";
import { createConnection } from "mysql2/promise";
import { env } from "../config/env";
// 迁移脚本使用单独连接而不是连接池,因为它是一次性命令,不是长时间运行的 HTTP 服务。
async function migrate(): Promise<void> {
const connection = await createConnection({
host: env.DB_HOST,
port: env.DB_PORT,
user: env.DB_USER,
password: env.DB_PASSWORD,
database: env.DB_NAME,
// 迁移文件里可能包含多个 CREATE TABLE / INSERT 语句,所以需要允许多语句执行。
multipleStatements: true,
timezone: "+08:00"
});
try {
// schema_migrations 记录每个已经执行过的 SQL 文件名。
// 之后重复运行 pnpm db:migrate 时,已执行过的迁移会被跳过。
await connection.query(`
CREATE TABLE IF NOT EXISTS schema_migrations (
version VARCHAR(255) NOT NULL,
applied_at DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
PRIMARY KEY (version)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='数据库迁移记录表';
`);
const migrationsDir = path.resolve(process.cwd(), "migrations");
const files = (await fs.readdir(migrationsDir))
.filter((file) => file.endsWith(".sql"))
.sort();
for (const file of files) {
// 以文件名作为版本号,简单直观,适合这个学习项目。
const [rows] = await connection.query(
"SELECT version FROM schema_migrations WHERE version = ? LIMIT 1",
[file]
);
if (Array.isArray(rows) && rows.length > 0) {
console.log(`跳过已执行迁移:${file}`);
continue;
}
const sql = await fs.readFile(path.join(migrationsDir, file), "utf8");
// 迁移文件按文件名顺序执行,便于团队协作时追踪每一次表结构变化。
await connection.query(sql);
await connection.query("INSERT INTO schema_migrations (version) VALUES (?)", [file]);
console.log(`已执行迁移:${file}`);
}
} finally {
await connection.end();
}
}
migrate().catch((error: unknown) => {
console.error("数据库迁移失败:", error);
process.exit(1);
});