| 1 | /* |
| 2 | * @Author: nevin |
| 3 | * @Date: 2025-01-20 16:22:03 |
| 4 | * @LastEditTime: 2025-03-18 22:53:17 |
| 5 | * @LastEditors: nevin |
| 6 | * @Description: 数据库 |
| 7 | */ |
| 8 | import { DataSource } from 'typeorm'; |
| 9 | import { AccountModel } from './models/account'; |
| 10 | import { UserModel } from './models/user'; |
| 11 | import { PubRecordModel } from './models/pubRecord'; |
| 12 | import { VideoModel } from './models/video'; |
| 13 | import * as migrations from './migrations'; |
| 14 | import path from 'path'; |
| 15 | import { app } from 'electron'; |
| 16 | import fs from 'fs/promises'; |
| 17 | import { logger } from '../global/log'; |
| 18 | import { AutoRunModel } from './models/autoRun'; |
| 19 | import { AutoRunRecordModel } from './models/autoRunRecord'; |
| 20 | import { ImgTextModel } from './models/imgText'; |
| 21 | import { ReplyCommentRecordModel } from './models/replyCommentRecord'; |
| 22 | import { InteractionRecordModel } from './models/interactionRecord'; |
| 23 | import { AccountGroupModel } from './models/accountGroup'; |
| 24 | import { defaultAccountGroupId } from '../../commont/AccountEnum'; |
| 25 | |
| 26 | const configPath = app.getPath('userData'); |
| 27 | const database = path.join(configPath, 'database.sqlite'); |
| 28 | logger.log('att database path:', database); |
| 29 | |
| 30 | export const AppDataSource = new DataSource({ |
| 31 | type: 'better-sqlite3', // 设定链接的数据库类型 |
| 32 | database, // 数据库存放地址 |
| 33 | synchronize: true, // 确保每次运行应用程序时实体都将与数据库同步 |
| 34 | logging: false, // 日志,默认在控制台中打印,数组列举错误类型枚举 |
| 35 | entities: [ |
| 36 | AccountModel, |
| 37 | UserModel, |
| 38 | PubRecordModel, |
| 39 | VideoModel, |
| 40 | AutoRunModel, |
| 41 | AutoRunRecordModel, |
| 42 | ImgTextModel, |
| 43 | ReplyCommentRecordModel, |
| 44 | InteractionRecordModel, |
| 45 | AccountGroupModel, |
| 46 | ], // 实体或模型表 |
| 47 | migrations: Object.values(migrations), // 迁移类 |
| 48 | migrationsRun: true, // 确保在连接时自动运行迁移 |
| 49 | }); |
| 50 | |
| 51 | // 数据库默认数据添加 |
| 52 | async function sqliteDefaultDataInit() { |
| 53 | // 添加用户组 【默认列表】 |
| 54 | const accountGroupRepository = AppDataSource.getRepository(AccountGroupModel); |
| 55 | const accountGroup = await accountGroupRepository.findOne({ |
| 56 | where: { id: defaultAccountGroupId }, |
| 57 | }); |
| 58 | if (!accountGroup) { |
| 59 | await accountGroupRepository.save({ |
| 60 | id: defaultAccountGroupId, |
| 61 | name: '默认列表', |
| 62 | rank: 0, |
| 63 | }); |
| 64 | } |
| 65 | } |
| 66 | |
| 67 | /** |
| 68 | * 初始化sqlite3数据库 |
| 69 | */ |
| 70 | export async function initSqlite3Db() { |
| 71 | if (!AppDataSource.isInitialized) { |
| 72 | try { |
| 73 | await AppDataSource.initialize(); |
| 74 | await sqliteDefaultDataInit(); |
| 75 | // await AppDataSource.runMigrations(); // 上面已经有自动迁移 |
| 76 | return true; |
| 77 | } catch (error) { |
| 78 | logger.error('Error during database initialization:', error); |
| 79 | return false; |
| 80 | } |
| 81 | } |
| 82 | return true; |
| 83 | } |
| 84 | |
| 85 | /** |
| 86 | * 导出数据库到SQL文件 |
| 87 | * @param filePath 导出文件路径 |
| 88 | */ |
| 89 | export async function exportDatabase(filePath: string): Promise<void> { |
| 90 | try { |
| 91 | if (!AppDataSource.isInitialized) { |
| 92 | logger.error('Database is not initialized'); |
| 93 | throw new Error('Database is not initialized'); |
| 94 | } |
| 95 | |
| 96 | const queryRunner = AppDataSource.createQueryRunner(); |
| 97 | await queryRunner.connect(); |
| 98 | |
| 99 | // 获取所有表的数据 |
| 100 | const tables = AppDataSource.entityMetadatas.map( |
| 101 | (entity) => entity.tableName, |
| 102 | ); |
| 103 | let sqlContent = ''; |
| 104 | |
| 105 | for (const table of tables) { |
| 106 | const records = await queryRunner.query(`SELECT * FROM ${table}`); |
| 107 | if (records.length > 0) { |
| 108 | sqlContent += `-- Table: ${table}\n`; |
| 109 | for (const record of records) { |
| 110 | const columns = Object.keys(record).join(', '); |
| 111 | const values = Object.values(record) |
| 112 | .map((value) => { |
| 113 | if (value === null) return 'NULL'; |
| 114 | if (typeof value === 'string') |
| 115 | return `'${value.replace(/'/g, "''")}'`; |
| 116 | return value; |
| 117 | }) |
| 118 | .join(', '); |
| 119 | sqlContent += `INSERT INTO ${table} (${columns}) VALUES (${values});\n`; |
| 120 | } |
| 121 | sqlContent += '\n'; |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | await fs.writeFile(filePath, sqlContent, 'utf8'); |
| 126 | await queryRunner.release(); |
| 127 | } catch (error) { |
| 128 | logger.error('Failed to export database:', error); |
| 129 | throw error; |
| 130 | } |
| 131 | } |
| 132 | |
| 133 | /** |
| 134 | * 从SQL文件导入数据 |
| 135 | * @param filePath SQL文件路径 |
| 136 | */ |
| 137 | export async function importDatabase(filePath: string): Promise<void> { |
| 138 | try { |
| 139 | if (!AppDataSource.isInitialized) { |
| 140 | throw new Error('Database is not initialized'); |
| 141 | } |
| 142 | |
| 143 | const sqlContent = await fs.readFile(filePath, 'utf8'); |
| 144 | const queryRunner = AppDataSource.createQueryRunner(); |
| 145 | await queryRunner.connect(); |
| 146 | await queryRunner.startTransaction(); |
| 147 | |
| 148 | try { |
| 149 | // 清空所有表 |
| 150 | const tables = AppDataSource.entityMetadatas.map( |
| 151 | (entity) => entity.tableName, |
| 152 | ); |
| 153 | for (const table of tables) { |
| 154 | await queryRunner.query(`DELETE FROM ${table}`); |
| 155 | } |
| 156 | |
| 157 | // 执行SQL语句 |
| 158 | const statements = sqlContent |
| 159 | .split('\n') |
| 160 | .filter((line) => line.trim() && !line.startsWith('--')) |
| 161 | .join('\n') |
| 162 | .split(';') |
| 163 | .filter((statement) => statement.trim()); |
| 164 | |
| 165 | for (const statement of statements) { |
| 166 | await queryRunner.query(statement); |
| 167 | } |
| 168 | |
| 169 | await queryRunner.commitTransaction(); |
| 170 | } catch (error) { |
| 171 | await queryRunner.rollbackTransaction(); |
| 172 | throw error; |
| 173 | } finally { |
| 174 | await queryRunner.release(); |
| 175 | } |
| 176 | } catch (error) { |
| 177 | logger.error('Failed to import database:', error); |
| 178 | throw error; |
| 179 | } |
| 180 | } |
| 181 |