From 09875b35c1d39fb74cdc13bb0244186dc407fe9c Mon Sep 17 00:00:00 2001 From: SunWuyuan Date: Sat, 24 May 2025 20:42:15 +0800 Subject: [PATCH] Update `.gitignore` to exclude Prisma database data directory and modify database setup in `classworks.js` to use `prisma migrate deploy` for migrations, enhancing deployment process. --- .gitignore | 1 + batchMigrate.js | 192 ++++++++++++++++++ classworks.js | 6 +- docker/docker-compose.yml | 69 +++++++ docker/mysql/conf.d/my.cnf | 33 +++ docker/mysql/initdb.d/init.sql | 12 ++ docker/postgres/initdb.d/init.sql | 10 + prisma/data/db.db | Bin 0 -> 28672 bytes prisma/data/db.db-journal | Bin 0 -> 12824 bytes .../20250524123413_2025_05_24/migration.sql | 24 +++ .../mysql/migrations/migration_lock.toml | 3 + .../20250524123225_init/migration.sql | 27 +++ .../postgres/migrations/migration_lock.toml | 3 + .../20250524124141_2025_05_24/migration.sql | 22 ++ .../sqlite/migrations/migration_lock.toml | 3 + 15 files changed, 400 insertions(+), 5 deletions(-) create mode 100644 batchMigrate.js create mode 100644 docker/docker-compose.yml create mode 100644 docker/mysql/conf.d/my.cnf create mode 100644 docker/mysql/initdb.d/init.sql create mode 100644 docker/postgres/initdb.d/init.sql create mode 100644 prisma/data/db.db create mode 100644 prisma/data/db.db-journal create mode 100644 prisma/database/mysql/migrations/20250524123413_2025_05_24/migration.sql create mode 100644 prisma/database/mysql/migrations/migration_lock.toml create mode 100644 prisma/database/postgres/migrations/20250524123225_init/migration.sql create mode 100644 prisma/database/postgres/migrations/migration_lock.toml create mode 100644 prisma/database/sqlite/migrations/20250524124141_2025_05_24/migration.sql create mode 100644 prisma/database/sqlite/migrations/migration_lock.toml diff --git a/.gitignore b/.gitignore index 11ddd8d..402f0b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules # Keep environment variables out of version control .env +prisma/database/data diff --git a/batchMigrate.js b/batchMigrate.js new file mode 100644 index 0000000..3668b84 --- /dev/null +++ b/batchMigrate.js @@ -0,0 +1,192 @@ +#!/usr/bin/env node +import fs from 'fs'; +import path from 'path'; +import { execSync } from 'child_process'; +import dotenv from 'dotenv'; + +// 加载环境变量 +dotenv.config(); + +const PRISMA_DIR = path.join(process.cwd(), 'prisma'); +const DATABASE_DIR = path.join(PRISMA_DIR, 'database'); +const MIGRATIONS_DIR = path.join(PRISMA_DIR, 'migrations'); + +// 数据库 URL 环境变量映射 +const DB_URL_VARS = { + mysql: 'MYSQL_DATABASE_URL', + postgres: 'PG_DATABASE_URL' +}; + +function copyDirectory(source, destination) { + // 如果目标目录不存在,创建它 + if (!fs.existsSync(destination)) { + fs.mkdirSync(destination, { recursive: true }); + } + + // 读取源目录中的所有内容 + const items = fs.readdirSync(source); + + for (const item of items) { + const sourcePath = path.join(source, item); + const destPath = path.join(destination, item); + + const stats = fs.statSync(sourcePath); + if (stats.isDirectory()) { + // 如果是目录,递归复制 + copyDirectory(sourcePath, destPath); + } else { + // 如果是文件,直接复制 + fs.copyFileSync(sourcePath, destPath); + } + } +} + +function deleteMigrationsDir() { + if (fs.existsSync(MIGRATIONS_DIR)) { + console.log('🗑️ 删除现有的 migrations 目录...'); + fs.rmSync(MIGRATIONS_DIR, { recursive: true, force: true }); + } +} + +// 修改 schema 文件中的数据库配置 +function updateSchemaConfig(schemaPath, dbType) { + console.log(`📝 更新 schema 文件配置...`); + + // 读取原始内容 + let content = fs.readFileSync(schemaPath, 'utf8'); + const originalContent = content; + + if (dbType === 'sqlite') { + // 修改 SQLite 数据库路径为 ../../data/db.db(用于迁移) + content = content.replace( + /url\s*=\s*"file:..\/data\/db.db"/, + 'url = "file:../../data/db.db"' + ); + } else { + // 获取对应的环境变量名 + const urlEnvVar = DB_URL_VARS[dbType]; + if (!urlEnvVar) { + throw new Error(`未找到 ${dbType} 的数据库 URL 环境变量映射`); + } + + // 替换 env("DATABASE_URL") 为对应的环境变量 + content = content.replace( + /env\s*\(\s*"DATABASE_URL"\s*\)/, + `env("${urlEnvVar}")` + ); + } + + // 写入修改后的内容 + fs.writeFileSync(schemaPath, content, 'utf8'); + + return originalContent; +} + +// 恢复 schema 文件的原始内容,对于 SQLite 恢复为 ../data/db.db +function restoreSchema(schemaPath, dbType, originalContent) { + if (originalContent) { + console.log(`📝 恢复 schema 文件的原始内容...`); + if (dbType === 'sqlite') { + // 确保恢复为 ../data/db.db + let content = originalContent; + if (content.includes('../../data/db.db')) { + content = content.replace( + /url\s*=\s*"file:..\/..\/data\/db.db"/, + 'url = "file:../data/db.db"' + ); + } + fs.writeFileSync(schemaPath, content, 'utf8'); + } else { + fs.writeFileSync(schemaPath, originalContent, 'utf8'); + } + } +} + +async function processDatabaseType(dbType) { + const schemaPath = path.join(DATABASE_DIR, dbType, 'schema.prisma'); + const dbMigrationsDir = path.join(DATABASE_DIR, dbType, 'migrations'); + + if (!fs.existsSync(schemaPath)) { + console.log(`⚠️ 跳过 ${dbType}: schema.prisma 文件不存在`); + return; + } + + let originalContent; + try { + console.log(`\n🔄 处理 ${dbType} 数据库迁移...`); + + // 删除旧的迁移目录 + deleteMigrationsDir(); + + // 修改 schema 文件配置 + originalContent = updateSchemaConfig(schemaPath, dbType); + + // 先尝试部署现有迁移 + console.log(`📦 部署现有迁移...`); + try { + execSync(`npx prisma migrate deploy --schema=${schemaPath}`, { + stdio: 'inherit' + }); + } catch (error) { + console.log(`⚠️ 部署现有迁移失败,将创建新迁移`); + } + + // 执行新迁移 + console.log(`📦 创建新迁移...`); + execSync(`npx prisma migrate dev --name ${new Date().toISOString().split('T')[0]} --schema=${schemaPath}`, { + stdio: 'inherit' + }); + + // 复制迁移文件到数据库特定目录 + if (fs.existsSync(MIGRATIONS_DIR)) { + console.log(`📋 复制迁移文件到 ${dbType} 目录...`); + copyDirectory(MIGRATIONS_DIR, dbMigrationsDir); + } + + console.log(`✅ ${dbType} 迁移完成`); + } catch (error) { + console.error(`❌ ${dbType} 迁移失败:`, error.message); + } finally { + // 确保无论成功还是失败都恢复原始内容,对于 SQLite 恢复为 ../data/db.db + restoreSchema(schemaPath, dbType, originalContent); + } +} + +async function main() { + try { + // 确保数据库目录存在 + if (!fs.existsSync(DATABASE_DIR)) { + console.error('❌ database 目录不存在'); + process.exit(1); + } + + // 获取所有数据库类型目录 + const dbTypes = fs.readdirSync(DATABASE_DIR).filter(item => { + const itemPath = path.join(DATABASE_DIR, item); + return fs.statSync(itemPath).isDirectory(); + }); + + console.log('📊 发现的数据库类型:', dbTypes.join(', ')); + console.log('🔑 数据库配置:'); + for (const [dbType, envVar] of Object.entries(DB_URL_VARS)) { + console.log(` - ${dbType}: 使用环境变量 ${envVar}`); + } + console.log(' - sqlite: 迁移时使用 ../../data/db.db,完成后恢复为 ../data/db.db'); + + // 依次处理每个数据库类型 + for (const dbType of dbTypes) { + await processDatabaseType(dbType); + } + + console.log('\n🎉 所有数据库迁移处理完成!'); + } catch (error) { + console.error('❌ 批量迁移失败:', error); + process.exit(1); + } +} + +// 执行主函数 +main().catch(error => { + console.error('❌ 程序执行失败:', error); + process.exit(1); +}); \ No newline at end of file diff --git a/classworks.js b/classworks.js index d4371de..56746d6 100644 --- a/classworks.js +++ b/classworks.js @@ -46,11 +46,7 @@ function setupDatabase() { process.env.DATABASE_URL = DATABASE_URL; // 检查数据库表并执行必要的迁移 - execSync( - "npx prisma migrate dev --name update-" + - new Date().toISOString().split("T")[0], - { stdio: "inherit" } - ); + execSync("npx prisma migrate deploy", { stdio: "inherit" }); } catch (error) { console.error("❌ 数据库初始化失败:", error.message); process.exit(1); diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 0000000..383f836 --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,69 @@ +version: '3.8' + +services: + mysql: + image: mysql:8.0 + container_name: classworks_mysql + restart: unless-stopped + environment: + MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-classworks} + MYSQL_DATABASE: ${MYSQL_DATABASE:-classworks} + MYSQL_USER: ${MYSQL_USER:-classworks} + MYSQL_PASSWORD: ${MYSQL_PASSWORD:-classworks} + TZ: Asia/Shanghai + ports: + - "3306:3306" + volumes: + - mysql_data:/var/lib/mysql + - ./mysql/conf.d:/etc/mysql/conf.d:ro + - ./mysql/initdb.d:/docker-entrypoint-initdb.d:ro + command: + - --character-set-server=utf8mb4 + - --collation-server=utf8mb4_unicode_ci + - --default-authentication-plugin=mysql_native_password + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u$$MYSQL_USER", "-p$$MYSQL_PASSWORD"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - classworks_net + + postgres: + image: postgres:15-alpine + container_name: classworks_postgres + restart: unless-stopped + environment: + POSTGRES_DB: ${POSTGRES_DB:-classworks} + POSTGRES_USER: ${POSTGRES_USER:-classworks} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-classworks} + TZ: Asia/Shanghai + ports: + - "5432:5432" + volumes: + - postgres_data:/var/lib/postgresql/data + - ./postgres/initdb.d:/docker-entrypoint-initdb.d:ro + command: + - "postgres" + - "-c" + - "max_connections=100" + - "-c" + - "shared_buffers=128MB" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - classworks_net + +volumes: + mysql_data: + name: classworks_mysql_data + postgres_data: + name: classworks_postgres_data + +networks: + classworks_net: + name: classworks_network + driver: bridge \ No newline at end of file diff --git a/docker/mysql/conf.d/my.cnf b/docker/mysql/conf.d/my.cnf new file mode 100644 index 0000000..0384d42 --- /dev/null +++ b/docker/mysql/conf.d/my.cnf @@ -0,0 +1,33 @@ +[mysqld] +# 字符集设置 +character-set-server=utf8mb4 +collation-server=utf8mb4_unicode_ci + +# 连接设置 +max_connections=100 +max_allowed_packet=64M + +# InnoDB设置 +innodb_buffer_pool_size=256M +innodb_log_file_size=64M +innodb_flush_log_at_trx_commit=2 +innodb_flush_method=O_DIRECT + +# 优化设置 +query_cache_type=1 +query_cache_size=32M +sort_buffer_size=4M +read_buffer_size=2M +read_rnd_buffer_size=4M +join_buffer_size=2M + +# 日志设置 +slow_query_log=1 +slow_query_log_file=/var/log/mysql/slow.log +long_query_time=2 + +[client] +default-character-set=utf8mb4 + +[mysql] +default-character-set=utf8mb4 \ No newline at end of file diff --git a/docker/mysql/initdb.d/init.sql b/docker/mysql/initdb.d/init.sql new file mode 100644 index 0000000..f96ed98 --- /dev/null +++ b/docker/mysql/initdb.d/init.sql @@ -0,0 +1,12 @@ +-- 设置时区 +SET GLOBAL time_zone = '+8:00'; +SET time_zone = '+8:00'; + +-- 创建数据库(如果不存在) +CREATE DATABASE IF NOT EXISTS classworks + CHARACTER SET utf8mb4 + COLLATE utf8mb4_unicode_ci; + +-- 设置权限 +GRANT ALL PRIVILEGES ON classworks.* TO 'classworks'@'%'; +FLUSH PRIVILEGES; \ No newline at end of file diff --git a/docker/postgres/initdb.d/init.sql b/docker/postgres/initdb.d/init.sql new file mode 100644 index 0000000..2507d7a --- /dev/null +++ b/docker/postgres/initdb.d/init.sql @@ -0,0 +1,10 @@ +-- 创建扩展 +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pg_trgm"; + +-- 设置时区 +SET timezone = 'Asia/Shanghai'; + +-- 设置默认权限 +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO classworks; +ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT USAGE, SELECT ON SEQUENCES TO classworks; \ No newline at end of file diff --git a/prisma/data/db.db b/prisma/data/db.db new file mode 100644 index 0000000000000000000000000000000000000000..b7a91ceeb9f7cd0ab080c655df1e92dba4aefb6c GIT binary patch literal 28672 zcmeI&-)`Dg90zbaf1sm9nbZpuMUjrUNzp=OgB^o*k&(pBXbELtx@9-k!9KKJ0DHFS z)?Kw-wO809>;d*J(;lUlJB9-x&{UPGYHIX7vSV}p{0`^ipCdU==evd%Gt&3NA&p5{ z`cjf*=^H{MN!s9hn(wo+&QFrMS&?Ta?>TNr_kRnrTW_V*?OiGR&(_~};_P3ycki6O zXAGJk009U<00Izz00bcL{|LO^+f3)Sx8*m5nDz$jggy5h@iTSYHfxqitlCk-B>8Dm zp4`h2Uh?Cy=jMrJ{$P>jW4?DA4bp1Y&uZ;+^2j{jpY{r96#e3dqUTcOx85k8h7b)S zI%HQWsN=9GvR(wD`f{oh^FgiKu*hDkd(@~O?}-Rb$Y{*m8jpLzzq?gGGgrqPciU~V zY1v6@$EuyR#Nct@UOPBb_$rmk9d65#H*(pp(a!_^OkvY;>`y-1Qyp8K9@DRt^>l7$ zM}A{WSRXy<#D2)cVdE;SsGZ9u3Q<6vJ9bTH;)9(Eo#kLHVBbn(M_ zAcoHQJ~`ZeW|9dDsX^}LFPFGa^2vekJrTBp z(2IuD9(qqh8hidITCG{T>b}}xehHG)en**P`{GJwnLks0#+-|2JX{)l`~`pan_|NqnL*L4qEJy zgqro5Cl;BdkTVWLHj3@o<7peyVIZcXK``)mpeSZRWIO&ix!DP+H!btjY?E%YQ$KB* zC#wOKOyPJfmHS@3E*DlWoUN|bCC~D=l3%J>zRO!bNqj?r00bZa0SG_<0uX=z1Rwwb z2teSY1zz2iGu>C&%|l7r{KC)<42P-ZqNZ!TqNXvW*wb0LSfTu&(Q36^Egw|$l3G1* zX}?ldol==;Mz6};Ua9X?D!QijDvGW+jOwn@XBDGt=tjTqQb*OOQq_8f{O4Nh>z5^^ zq$_$!Q~9Q;cJk3ybi1TYnp+vZE_v%;iEk(nfB*y_009U<00Izz00bZa0SJ6%0(ggfw43QKmY;|fB*y_ z009U<00Izz02jdff208fAOHafKmY;|fB*y_009U<;N}b9{r}Cs##kW)AOHafKmY;| NfB*y_009U<;6Lv@nm_;m literal 0 HcmV?d00001 diff --git a/prisma/data/db.db-journal b/prisma/data/db.db-journal new file mode 100644 index 0000000000000000000000000000000000000000..862e53b498aef7ba05e344e01eb88b68f753064d GIT binary patch literal 12824 zcmeI$O^ee&7y#f&{VtSxTktgWq+lfm z%(65~>zTT$SUsNwrlxqR#);ut+;`Q$GfkZ+t|{xX$C>UM0XGfJ(2XGQnWqpYTf{X~ zS=D7-C5kwRLaQc~b*d8SQ)BXQa_9TUt72)3eE@X(k`@VNKmY_l00ck)1V8`;KmY_l z00ck)1pY~Zlhd!C#l)n&7o)J`I7suDl~@y70+NItV_ZEG=M{0TqH0%r2dk*s7lni# zyvthcA7aDd47JYMUoPqZ&p`kLKmY_l00ck)1V8`;KmY{(M&SKx>Bg3#NFQj)+=$aG z4~v-5I6TN%8Ky~5zth;B+I`2yPXEcs#+`aX2j6XC5uMQQRM*XL>{kw+OozMu={|mD z@8j`v2aji?(fwcDyhH9C74vwb^P<~EY7i!2amanj%JbO+F+XRxYcC6~S3?sP^L65% z3P$Ncu}=6s@hWv$EKj3I4CpfN==`V~Llz~=%i6r~Dr>!DJg}ekXCnuD^E~HCNy{+i zqH~-rhhte5g`!YV@~ogfsa`WDH