d1数据迁移

This commit is contained in:
mtvpls
2026-01-30 15:14:07 +08:00
parent c86f9cdca7
commit 465821a6c9
5 changed files with 172 additions and 63 deletions
+7
View File
@@ -7,6 +7,13 @@
* 注意:此模块仅在服务端使用,通过 webpack 配置排除客户端打包
*/
// Cloudflare D1 Database 接口
export interface D1Database {
prepare(query: string): D1PreparedStatement;
batch(statements: any[]): Promise<D1Result[]>;
exec(query: string): Promise<D1Result>;
}
// D1 PreparedStatement 接口
export interface D1PreparedStatement {
bind(...values: any[]): D1PreparedStatement;
+67 -1
View File
@@ -14,8 +14,8 @@ import {
DanmakuFilterConfig,
Notification,
MovieRequest,
AdminConfig,
} from './types';
import { AdminConfig } from './admin.types';
import { DatabaseAdapter } from './d1-adapter';
/**
@@ -735,6 +735,72 @@ export class D1Storage implements IStorage {
}
}
// 获取用户密码哈希(用于数据导出)
async getUserPasswordHash(userName: string): Promise<string | null> {
try {
const user = await this.db
.prepare('SELECT password_hash FROM users WHERE username = ?')
.bind(userName)
.first();
return user ? (user.password_hash as string) : null;
} catch (err) {
console.error('D1Storage.getUserPasswordHash error:', err);
return null;
}
}
// 直接设置用户密码哈希(用于数据导入,不进行二次哈希)
async setUserPasswordHash(userName: string, passwordHash: string): Promise<void> {
try {
await this.db
.prepare('UPDATE users SET password_hash = ? WHERE username = ?')
.bind(passwordHash, userName)
.run();
} catch (err) {
console.error('D1Storage.setUserPasswordHash error:', err);
throw err;
}
}
// 直接创建用户(用于数据导入,密码已经是哈希值)
async createUserWithHashedPassword(
userName: string,
passwordHash: string,
role: 'owner' | 'admin' | 'user',
createdAt: number,
tags?: string[],
oidcSub?: string,
enabledApis?: string[],
banned?: boolean
): Promise<void> {
try {
await this.db
.prepare(`
INSERT INTO users (
username, password_hash, role, banned, tags, oidc_sub,
enabled_apis, created_at, playrecord_migrated,
favorite_migrated, skip_migrated
)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, 1, 1)
`)
.bind(
userName,
passwordHash,
role,
banned ? 1 : 0,
tags ? JSON.stringify(tags) : null,
oidcSub || null,
enabledApis ? JSON.stringify(enabledApis) : null,
createdAt
)
.run();
} catch (err) {
console.error('D1Storage.createUserWithHashedPassword error:', err);
throw err;
}
}
async getUserEmail?(userName: string): Promise<string | null> {
try {
const result = await this.db
+24 -25
View File
@@ -49,41 +49,40 @@ function getD1Adapter(): any {
// 动态导入适配器以避免客户端打包
const { CloudflareD1Adapter, SQLiteAdapter } = require('./d1-adapter');
// 检查是否为 Cloudflare 构建
const isCloudflare = process.env.CF_PAGES === '1' || process.env.BUILD_TARGET === 'cloudflare';
// 生产环境:Cloudflare Workers/Pages
if (typeof process !== 'undefined' && (process as any).env?.DB) {
if (isCloudflare && typeof process !== 'undefined' && (process as any).env?.DB) {
console.log('Using Cloudflare D1 database');
return new CloudflareD1Adapter((process as any).env.DB);
}
// 开发环境:better-sqlite3
if (process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'test') {
try {
const Database = require('better-sqlite3');
const path = require('path');
const fs = require('fs');
try {
const Database = require('better-sqlite3');
const path = require('path');
const fs = require('fs');
const dbPath = path.join(process.cwd(), '.data', 'moontv.db');
const dbPath = path.join(process.cwd(), '.data', 'moontv.db');
// 检查数据库文件是否存在
if (!fs.existsSync(dbPath)) {
console.error('❌ SQLite database not found. Please run: npm run init:sqlite');
throw new Error('SQLite database not initialized');
}
const db = new Database(dbPath);
db.pragma('journal_mode = WAL'); // 启用 WAL 模式提升性能
console.log('Using SQLite database (development mode)');
console.log('Database location:', dbPath);
return new SQLiteAdapter(db);
} catch (err) {
console.error('Failed to initialize SQLite:', err);
throw err;
// 检查数据库文件是否存在
if (!fs.existsSync(dbPath)) {
console.error('❌ SQLite database not found. Please run: npm run init:sqlite');
throw new Error('SQLite database not initialized');
}
}
throw new Error('D1 database not available. Set NEXT_PUBLIC_STORAGE_TYPE to another option or configure D1.');
const db = new Database(dbPath);
db.pragma('journal_mode = WAL'); // 启用 WAL 模式提升性能
console.log('Using SQLite database (development mode)');
console.log('Database location:', dbPath);
return new SQLiteAdapter(db);
} catch (err) {
console.error('Failed to initialize SQLite:', err);
throw err;
}
}
// 单例存储实例