cangku/backend/src/services/setupWizard.js

196 lines
5.3 KiB
JavaScript

const bcrypt = require('bcryptjs');
const database = require('../config/database');
const { getRedisConfig } = require('./redisConfig');
const DEFAULT_ADMIN_PASSWORD = 'admin123';
const SETUP_COMPLETED_KEY = 'setup_completed';
const SETUP_COMPLETED_AT_KEY = 'setup_completed_at';
function normalizeBoolean(value) {
return value === true || value === 1 || value === '1' || value === 'true';
}
async function getSystemConfigMap(keys) {
const rows = await database.local.query(
`SELECT config_key, config_value
FROM system_config
WHERE config_key IN (${keys.map(() => '?').join(', ')})`,
keys
);
return rows.reduce((acc, row) => {
acc[row.config_key] = row.config_value;
return acc;
}, {});
}
async function upsertSystemConfig(key, value, type = 'string') {
const existing = await database.local.query(
'SELECT id FROM system_config WHERE config_key = ? LIMIT 1',
[key]
);
if (existing.length > 0) {
await database.local.query(
'UPDATE system_config SET config_value = ?, config_type = ? WHERE config_key = ?',
[value, type, key]
);
return;
}
await database.local.insert('system_config', {
config_key: key,
config_value: value,
config_type: type,
});
}
async function isBusinessDatabaseConfigured() {
const rows = await database.local.query(
`SELECT host, database_name, username, status
FROM database_configs
WHERE is_default = 1
ORDER BY id ASC
LIMIT 1`
);
const row = rows[0];
return Boolean(
row &&
row.status === 'active' &&
row.host &&
row.database_name &&
row.username
);
}
async function isAuthConfigured() {
const rows = await database.local.query(
`SELECT auth_base_url, dingtalk_enabled, dingtalk_app_key, dingtalk_app_secret,
google_enabled, google_client_id, google_client_secret
FROM auth_configs
ORDER BY id ASC
LIMIT 1`
);
const row = rows[0];
if (!row) {
return false;
}
const dingtalkReady = normalizeBoolean(row.dingtalk_enabled) && Boolean(row.dingtalk_app_key && row.dingtalk_app_secret);
const googleReady = normalizeBoolean(row.google_enabled) && Boolean(row.google_client_id && row.google_client_secret);
return Boolean((row.auth_base_url || '').trim() || dingtalkReady || googleReady);
}
async function isS3Configured() {
const rows = await database.local.query(
`SELECT bucket, access_key_id, secret_access_key
FROM s3_configs
WHERE is_default = 1
ORDER BY id ASC
LIMIT 1`
);
const row = rows[0];
return Boolean(row && row.bucket && row.access_key_id && row.secret_access_key);
}
async function getCurrentAdminUser(userId) {
const rows = await database.local.query(
`SELECT id, username, name, email, role, status, password_hash
FROM users
WHERE id = ?
LIMIT 1`,
[userId]
);
return rows[0] || null;
}
async function isUsingDefaultPassword(user) {
if (!user || !user.password_hash) {
return false;
}
return bcrypt.compare(DEFAULT_ADMIN_PASSWORD, user.password_hash);
}
async function ensureSetupConfigDefaults() {
const configMap = await getSystemConfigMap([SETUP_COMPLETED_KEY, SETUP_COMPLETED_AT_KEY]);
if (configMap[SETUP_COMPLETED_KEY] === undefined) {
const inferredCompleted = await isBusinessDatabaseConfigured();
await upsertSystemConfig(SETUP_COMPLETED_KEY, inferredCompleted ? 'true' : 'false', 'boolean');
}
if (configMap[SETUP_COMPLETED_AT_KEY] === undefined) {
await upsertSystemConfig(SETUP_COMPLETED_AT_KEY, '', 'string');
}
}
async function getSetupStatus(userId) {
await ensureSetupConfigDefaults();
const [configMap, currentUser, businessDatabaseConfigured, authConfigured, s3Configured] = await Promise.all([
getSystemConfigMap([SETUP_COMPLETED_KEY, SETUP_COMPLETED_AT_KEY]),
getCurrentAdminUser(userId),
isBusinessDatabaseConfigured(),
isAuthConfigured(),
isS3Configured(),
]);
const redisConfigured = (await getRedisConfig()).enabled;
const currentUserUsesDefaultPassword = await isUsingDefaultPassword(currentUser);
const completed = normalizeBoolean(configMap[SETUP_COMPLETED_KEY]);
return {
completed,
shouldShowWizard: !completed,
completedAt: configMap[SETUP_COMPLETED_AT_KEY] || '',
currentUser: currentUser
? {
id: currentUser.id,
username: currentUser.username,
name: currentUser.name,
email: currentUser.email,
role: currentUser.role,
status: currentUser.status,
}
: null,
currentUserUsesDefaultPassword,
steps: {
changeAdminPassword: !currentUserUsesDefaultPassword,
businessDatabase: businessDatabaseConfigured,
redisCache: redisConfigured,
authConfig: authConfigured,
objectStorage: s3Configured,
},
};
}
async function markSetupCompleted(userId) {
const status = await getSetupStatus(userId);
if (!status.steps.businessDatabase) {
throw new Error('请先完成业务数据库配置');
}
if (status.currentUserUsesDefaultPassword) {
throw new Error('请先修改默认管理员密码');
}
await upsertSystemConfig(SETUP_COMPLETED_KEY, 'true', 'boolean');
await upsertSystemConfig(SETUP_COMPLETED_AT_KEY, new Date().toISOString(), 'string');
return getSetupStatus(userId);
}
module.exports = {
DEFAULT_ADMIN_PASSWORD,
ensureSetupConfigDefaults,
getSetupStatus,
markSetupCompleted,
};