Files
MYXY_Web/src/components/EnvSyncModal.tsx
2025-12-12 20:01:39 +08:00

414 lines
12 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* .env配置同步功能组件
* @author MHXY Development Team
* @version 1.0.0
*/
import React, { useState } from 'react';
import { Modal, Form, Button, Space, Alert, Typography, Divider, Checkbox, Progress } from 'antd';
import {
SyncOutlined,
FileTextOutlined,
CheckCircleOutlined,
ExclamationCircleOutlined,
DownloadOutlined
} from '@ant-design/icons';
import { SystemConfig } from '../types/systemConfig';
import systemConfigService from '../services/systemConfigService';
const { Text, Paragraph } = Typography;
const { confirm } = Modal;
interface EnvSyncModalProps {
visible: boolean;
configs: SystemConfig[];
onClose: () => void;
onSync?: () => void;
}
const EnvSyncModal: React.FC<EnvSyncModalProps> = ({
visible,
configs,
onClose,
onSync
}) => {
const [form] = Form.useForm();
const [loading, setLoading] = useState<boolean>(false);
const [progress, setProgress] = useState<number>(0);
const [syncResult, setSyncResult] = useState<{
success: boolean;
message: string;
details?: string[];
} | null>(null);
// 准备.env文件内容
const generateEnvContent = (selectedConfigs: string[]): string => {
const envVars: Record<string, string> = {};
// 根据选择的配置生成环境变量
configs.forEach(config => {
if (selectedConfigs.includes(config.config_key)) {
switch (config.config_key) {
case 'jwt_secret':
envVars.JWT_SECRET = config.config_value;
break;
case 'jwt_expires_in':
envVars.JWT_EXPIRES_IN = config.config_value;
break;
case 'game_server_api':
envVars.GAME_SERVER_API = config.config_value;
break;
case 'game_server_psk':
envVars.GAME_SERVER_PSK = config.config_value;
break;
case 'game_server_timeout':
envVars.GAME_SERVER_TIMEOUT = config.config_value;
break;
case 'game_server_retry_count':
envVars.GAME_SERVER_RETRY_COUNT = config.config_value;
break;
case 'site_name':
envVars.SITE_NAME = config.config_value;
break;
case 'maintenance_mode':
envVars.MAINTENANCE_MODE = config.config_value;
break;
}
}
});
// 生成.env文件内容
const envContent = [
'# .env文件 - 由系统配置自动生成',
`# 生成时间: ${new Date().toLocaleString('zh-CN')}`,
'# 警告: 请不要手动修改此文件,系统配置变更时此文件将被覆盖',
'',
'# === 系统基本信息 ===',
`SITE_NAME="${envVars.SITE_NAME || '梦幻西游运营管理系统'}"`,
`MAINTENANCE_MODE=${envVars.MAINTENANCE_MODE || '0'}`,
'',
'# === JWT安全配置 ===',
`JWT_SECRET="${envVars.JWT_SECRET || ''}"`,
`JWT_EXPIRES_IN=${envVars.JWT_EXPIRES_IN || '24'}`,
'',
'# === 游戏服务端配置 ===',
`GAME_SERVER_API="${envVars.GAME_SERVER_API || ''}"`,
`GAME_SERVER_PSK="${envVars.GAME_SERVER_PSK || ''}"`,
`GAME_SERVER_TIMEOUT=${envVars.GAME_SERVER_TIMEOUT || '30'}`,
`GAME_SERVER_RETRY_COUNT=${envVars.GAME_SERVER_RETRY_COUNT || '3'}`,
'',
'# === 其他配置 ===',
'# 请在此处添加其他环境变量',
''
].join('\n');
return envContent;
};
// 执行同步操作
const handleSync = async () => {
try {
const values = await form.validateFields();
const selectedConfigs = Object.keys(values).filter(key => values[key]);
if (selectedConfigs.length === 0) {
throw new Error('请至少选择一个配置项进行同步');
}
setLoading(true);
setProgress(0);
setSyncResult(null);
// 模拟同步进度
const progressSteps = [
{ percent: 20, message: '准备配置数据...' },
{ percent: 40, message: '生成.env文件内容...' },
{ percent: 60, message: '验证配置格式...' },
{ percent: 80, message: '执行同步操作...' },
{ percent: 100, message: '同步完成' }
];
for (const step of progressSteps) {
await new Promise(resolve => setTimeout(resolve, 500));
setProgress(step.percent);
}
// 准备环境配置数据
const envConfigs = selectedConfigs.reduce((acc, configKey) => {
const config = configs.find(c => c.config_key === configKey);
if (config) {
switch (configKey) {
case 'jwt_secret':
acc.JWT_SECRET = config.config_value;
break;
case 'jwt_expires_in':
acc.JWT_EXPIRES_IN = config.config_value;
break;
case 'game_server_api':
acc.GAME_SERVER_API = config.config_value;
break;
case 'game_server_psk':
acc.GAME_SERVER_PSK = config.config_value;
break;
}
}
return acc;
}, {} as any);
// 调用服务同步
const result = await systemConfigService.syncToEnvFile(envConfigs);
setSyncResult({
success: result.success,
message: result.message,
details: selectedConfigs
});
if (result.success) {
onSync?.();
}
} catch (error) {
setSyncResult({
success: false,
message: error instanceof Error ? error.message : '同步失败',
details: []
});
} finally {
setLoading(false);
}
};
// 下载.env文件
const handleDownload = () => {
const selectedConfigs = ['jwt_secret', 'jwt_expires_in', 'game_server_api', 'game_server_psk'];
const envContent = generateEnvContent(selectedConfigs);
const blob = new Blob([envContent], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = '.env';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
// 重置状态
setSyncResult(null);
setProgress(0);
};
// 关闭模态框
const handleClose = () => {
setSyncResult(null);
setProgress(0);
form.resetFields();
onClose();
};
// 可同步的配置项
const configOptions = [
{
key: 'jwt_secret',
label: 'JWT密钥',
description: 'JWT签名密钥用于令牌验证',
sensitive: true,
envVar: 'JWT_SECRET'
},
{
key: 'jwt_expires_in',
label: 'JWT过期时间',
description: 'JWT访问令牌有效期小时',
sensitive: false,
envVar: 'JWT_EXPIRES_IN'
},
{
key: 'game_server_api',
label: '游戏服务端API',
description: '游戏服务端HTTP接口地址',
sensitive: false,
envVar: 'GAME_SERVER_API'
},
{
key: 'game_server_psk',
label: '游戏服务端PSK',
description: '游戏服务端预共享密钥',
sensitive: true,
envVar: 'GAME_SERVER_PSK'
},
{
key: 'game_server_timeout',
label: '请求超时时间',
description: '与游戏服务端通信超时时间(秒)',
sensitive: false,
envVar: 'GAME_SERVER_TIMEOUT'
},
{
key: 'game_server_retry_count',
label: '重试次数',
description: 'API请求失败时的重试次数',
sensitive: false,
envVar: 'GAME_SERVER_RETRY_COUNT'
}
];
// 默认选中的配置
const defaultChecked = ['jwt_secret', 'jwt_expires_in', 'game_server_api', 'game_server_psk'];
return (
<Modal
title={
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<SyncOutlined />
.env文件
</div>
}
open={visible}
onCancel={handleClose}
width={600}
footer={[
<Button key="cancel" onClick={handleClose}>
</Button>,
<Button
key="download"
icon={<DownloadOutlined />}
onClick={handleDownload}
disabled={loading}
>
.env文件
</Button>,
<Button
key="sync"
type="primary"
icon={<SyncOutlined />}
loading={loading}
onClick={handleSync}
disabled={syncResult?.success}
>
</Button>
]}
>
<div style={{ marginBottom: '16px' }}>
<Alert
message="配置同步说明"
description="选择要同步到.env文件的系统配置项然后执行同步操作。敏感配置如密钥将被加密处理。"
type="info"
showIcon
style={{ marginBottom: '16px' }}
/>
</div>
{/* 同步进度 */}
{loading && (
<div style={{ marginBottom: '16px' }}>
<Progress
percent={progress}
status="active"
strokeColor={{
'0%': '#108ee9',
'100%': '#87d068',
}}
/>
<Text type="secondary" style={{ fontSize: '12px' }}>
...
</Text>
</div>
)}
{/* 同步结果 */}
{syncResult && (
<div style={{ marginBottom: '16px' }}>
<Alert
message={syncResult.success ? '同步成功' : '同步失败'}
description={syncResult.message}
type={syncResult.success ? 'success' : 'error'}
showIcon
action={
syncResult.success && (
<Button size="small" onClick={handleDownload}>
.env文件
</Button>
)
}
/>
</div>
)}
{/* 配置选择 */}
{!syncResult?.success && (
<Form
form={form}
layout="vertical"
initialValues={{
configOptions: defaultChecked
}}
>
<Form.Item
name="configOptions"
label="选择要同步的配置项"
rules={[
{ required: true, message: '请至少选择一个配置项' }
]}
>
<Checkbox.Group style={{ width: '100%' }}>
<Space direction="vertical" size="middle" style={{ width: '100%' }}>
{configOptions.map(option => (
<div
key={option.key}
style={{
padding: '12px',
border: '1px solid #d9d9d9',
borderRadius: '6px',
background: '#fafafa'
}}
>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Checkbox value={option.key}>
<div style={{ flex: 1 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
<Text strong>{option.label}</Text>
{option.sensitive && (
<Text type="danger" style={{ fontSize: '12px' }}>
</Text>
)}
</div>
<Text type="secondary" style={{ fontSize: '12px', display: 'block', marginTop: '4px' }}>
{option.description}
</Text>
<Text code style={{ fontSize: '11px', display: 'block', marginTop: '4px' }}>
{option.envVar}
</Text>
</div>
</Checkbox>
</div>
</div>
))}
</Space>
</Checkbox.Group>
</Form.Item>
</Form>
)}
{/* 操作说明 */}
<Divider />
<div style={{ fontSize: '12px', color: '#666' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px' }}>
<FileTextOutlined />
<strong></strong>
</div>
<ul style={{ margin: 0, paddingLeft: '20px' }}>
<li>.env文件</li>
<li></li>
<li>.env文件</li>
<li>使</li>
</ul>
</div>
</Modal>
);
};
export default EnvSyncModal;