63 lines
2.2 KiB
TypeScript
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);
|
|
});
|