1
0
mirror of https://github.com/ZeroCatDev/ClassworksKV.git synced 2025-07-01 20:09:23 +00:00

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.

This commit is contained in:
SunWuyuan 2025-05-24 20:42:15 +08:00
parent 93b9b1c6f5
commit 09875b35c1
No known key found for this signature in database
GPG Key ID: A6A54CF66F56BB64
15 changed files with 400 additions and 5 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
node_modules
# Keep environment variables out of version control
.env
prisma/database/data

192
batchMigrate.js Normal file
View File

@ -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);
});

View File

@ -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);

69
docker/docker-compose.yml Normal file
View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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;

BIN
prisma/data/db.db Normal file

Binary file not shown.

BIN
prisma/data/db.db-journal Normal file

Binary file not shown.

View File

@ -0,0 +1,24 @@
-- CreateTable
CREATE TABLE `KVStore` (
`namespace` VARCHAR(191) NOT NULL,
`key` VARCHAR(191) NOT NULL,
`value` JSON NOT NULL,
`creatorIp` VARCHAR(191) NULL DEFAULT '',
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
PRIMARY KEY (`namespace`, `key`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
-- CreateTable
CREATE TABLE `Device` (
`uuid` VARCHAR(191) NOT NULL,
`password` VARCHAR(191) NULL,
`passwordHint` VARCHAR(191) NULL,
`name` VARCHAR(191) NULL,
`accessType` ENUM('PUBLIC', 'PROTECTED', 'PRIVATE') NOT NULL DEFAULT 'PUBLIC',
`createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
`updatedAt` DATETIME(3) NOT NULL,
PRIMARY KEY (`uuid`)
) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "mysql"

View File

@ -0,0 +1,27 @@
-- CreateEnum
CREATE TYPE "AccessType" AS ENUM ('PUBLIC', 'PROTECTED', 'PRIVATE');
-- CreateTable
CREATE TABLE "KVStore" (
"namespace" TEXT NOT NULL,
"key" TEXT NOT NULL,
"value" JSONB NOT NULL,
"creatorIp" TEXT DEFAULT '',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "KVStore_pkey" PRIMARY KEY ("namespace","key")
);
-- CreateTable
CREATE TABLE "Device" (
"uuid" TEXT NOT NULL,
"password" TEXT,
"passwordHint" TEXT,
"name" TEXT,
"accessType" "AccessType" NOT NULL DEFAULT 'PUBLIC',
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Device_pkey" PRIMARY KEY ("uuid")
);

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"

View File

@ -0,0 +1,22 @@
-- CreateTable
CREATE TABLE "KVStore" (
"namespace" TEXT NOT NULL,
"key" TEXT NOT NULL,
"value" JSONB NOT NULL,
"creatorIp" TEXT DEFAULT '',
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL,
PRIMARY KEY ("namespace", "key")
);
-- CreateTable
CREATE TABLE "Device" (
"uuid" TEXT NOT NULL PRIMARY KEY,
"password" TEXT,
"passwordHint" TEXT,
"name" TEXT,
"accessType" TEXT NOT NULL DEFAULT 'PUBLIC',
"createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" DATETIME NOT NULL
);

View File

@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "sqlite"