This commit is contained in:
2026-07-20 20:09:26 +08:00 Unverified
parent a6e07eebbb
commit 5a33a6bcc4
5 changed files with 91 additions and 39 deletions
+38 -11
View File
@@ -4,6 +4,7 @@ import { rm } from 'node:fs/promises';
import { isAbsolute, join, relative, resolve } from 'node:path';
import { loadEnvFile } from 'node:process';
import { buildSeedOperations, createDatabase, relationalTables } from '../database.mjs';
import { createBaseDatabase } from '../src/data/base.mjs';
import { createSeedDatabase } from '../src/data/seed.mjs';
const root = resolve(process.cwd());
@@ -17,13 +18,24 @@ const client = String(configuredClient || (process.env.NODE_ENV === 'production'
if (!['sqlite', 'mysql'].includes(client)) throw new Error(`不支持的 DATABASE_CLIENT${client}`);
process.env.DATABASE_CLIENT = client;
const force = options.has('--force');
const initializeEmpty = options.has('--empty');
function hashPassword(password, salt = randomBytes(16).toString('hex')) {
const hash = pbkdf2Sync(password, salt, 120000, 32, 'sha256').toString('hex');
return `${salt}:${hash}`;
}
const seed = () => createSeedDatabase({ nowIso: () => new Date().toISOString(), hashPassword });
const createTargetState = () => initializeEmpty
? createBaseDatabase({
nowIso: () => new Date().toISOString(),
hashPassword,
initialAdmin: {
username: process.env.INITIAL_ADMIN_USERNAME,
password: process.env.INITIAL_ADMIN_PASSWORD,
displayName: process.env.INITIAL_ADMIN_DISPLAY_NAME
}
})
: createSeedDatabase({ nowIso: () => new Date().toISOString(), hashPassword });
async function prepareSqlite() {
const databasePath = resolve(process.env.SQLITE_PATH || join(root, 'data', 'exam.sqlite'));
@@ -100,19 +112,28 @@ async function prepareMysql() {
}
const [[userRow]] = await connection.query('SELECT COUNT(*) AS count FROM users');
if (Number(userRow.count) > 1) nonEmpty.push(`users=${userRow.count}`);
if (nonEmpty.length && !force) {
let recognizedTestData = false;
if (initializeEmpty && nonEmpty.length) {
const [[bulkUsers]] = await connection.query("SELECT COUNT(*) AS count FROM users WHERE id LIKE 'usr_bulk_%'");
const [[testSchools]] = await connection.query("SELECT COUNT(*) AS count FROM schools WHERE id IN ('school_hz1', 'school_hz3', 'school_hz5', 'school_hz7')");
recognizedTestData = Number(bulkUsers.count) >= 300 && Number(testSchools.count) === 4;
}
if (nonEmpty.length && !force && !recognizedTestData) {
const forceCommand = initializeEmpty
? 'npm run initialize-system:mysql -- --force'
: 'npm run seed-test-data:mysql -- --force';
throw new Error(
`MySQL 数据库 ${databaseName} 已有业务数据(${nonEmpty.slice(0, 8).join(', ')}${nonEmpty.length > 8 ? ', ...' : ''})。` +
'如确认这是可覆盖的测试库,请运行 npm run seed-test-data:mysql -- --force'
`如确认这是可覆盖的测试库,请运行 ${forceCommand}`
);
}
if (nonEmpty.length) {
console.warn(`[force] Replacing existing business data in MySQL database ${databaseName}`);
console.warn(`[${recognizedTestData ? 'test-data cleanup' : 'force'}] Replacing existing business data in MySQL database ${databaseName}`);
} else {
console.log(`Preparing empty MySQL database ${databaseName} for test data`);
console.log(`Preparing empty MySQL database ${databaseName} for ${initializeEmpty ? 'system initialization' : 'test data'}`);
}
const state = seed();
const state = createTargetState();
await connection.query('SET FOREIGN_KEY_CHECKS = 0');
await connection.beginTransaction();
try {
@@ -145,7 +166,7 @@ if (client === 'sqlite') {
}
const database = await createDatabase({
root,
seed
seed: createTargetState
});
const state = await database.read();
await database.close();
@@ -154,7 +175,13 @@ const pending = state.registrations.filter(item => item.status === 'pending').le
const rejected = state.registrations.filter(item => item.status === 'rejected').length;
const unpaid = state.registrations.filter(item => item.status === 'approved' && item.paymentStatus === 'unpaid').length;
const paid = state.registrations.filter(item => item.status === 'approved' && item.paymentStatus === 'paid').length;
console.log(`Imported test data into ${location}${mysqlAlreadyImported ? ' (transactional replace)' : ''}`);
console.log(`${state.schools.length} schools, ${state.candidateProfiles.length} candidates, ${state.registrations.length} registrations`);
console.log(`pending ${pending}, rejected ${rejected}, approved/unpaid ${unpaid}, approved/paid ${paid}`);
console.log(`arrangement plans ${state.arrangementPlans.length}, admit cards ${state.registrations.filter(item => item.admitCard).length}`);
if (initializeEmpty) {
console.log(`Initialized empty system in ${location}${mysqlAlreadyImported ? ' (transactional replace)' : ''}`);
console.log(`${state.users.length} initial administrator, ${state.schools.length} schools, ${state.candidateProfiles.length} candidates, ${state.registrations.length} registrations`);
} else {
console.log(`Imported test data into ${location}${mysqlAlreadyImported ? ' (transactional replace)' : ''}`);
console.log(`${state.schools.length} schools, ${state.candidateProfiles.length} candidates, ${state.registrations.length} registrations`);
console.log(`pending ${pending}, rejected ${rejected}, approved/unpaid ${unpaid}, approved/paid ${paid}`);
console.log(`arrangement plans ${state.arrangementPlans.length}, admit cards ${state.registrations.filter(item => item.admitCard).length}`);
console.log('all predefined test account passwords: 12345678');
}