fix:修复配置错误等

master
Lxy 3 months ago
parent 8ed5e97ae7
commit c1384d1142

@ -18,7 +18,8 @@
"morgan": "^1.10.0",
"pg": "^8.11.3",
"redis": "^4.6.12",
"socket.io": "^4.7.4"
"socket.io": "^4.7.4",
"tqsdk": "^1.3.1"
},
"devDependencies": {
"@types/cors": "^2.8.17",
@ -1112,6 +1113,20 @@
"dev": true,
"license": "MIT"
},
"node_modules/bufferutil": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz",
"integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
@ -1374,6 +1389,17 @@
"dev": true,
"license": "MIT"
},
"node_modules/core-js": {
"version": "3.48.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.48.0.tgz",
"integrity": "sha512-zpEHTy1fjTMZCKLHUZoVeylt9XrzaIN2rbPXEt0k+q7JE5CkCZdo6bNq55bn24a69CH7ErAVLKijxJja4fw+UQ==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cors": {
"version": "2.8.6",
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz",
@ -1894,6 +1920,12 @@
"node": ">= 0.6"
}
},
"node_modules/eventemitter3": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-3.1.2.tgz",
"integrity": "sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==",
"license": "MIT"
},
"node_modules/express": {
"version": "4.22.1",
"resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz",
@ -2508,6 +2540,12 @@
"node": ">= 4"
}
},
"node_modules/immediate": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz",
"integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==",
"license": "MIT"
},
"node_modules/import-fresh": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@ -2784,6 +2822,24 @@
"node": ">= 0.8.0"
}
},
"node_modules/lie": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
"integrity": "sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==",
"license": "MIT",
"dependencies": {
"immediate": "~3.0.5"
}
},
"node_modules/localforage": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/localforage/-/localforage-1.10.0.tgz",
"integrity": "sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==",
"license": "Apache-2.0",
"dependencies": {
"lie": "3.1.1"
}
},
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@ -3250,6 +3306,18 @@
"node": ">= 0.6"
}
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"license": "MIT",
"optional": true,
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
@ -4324,6 +4392,21 @@
"node": ">=0.6"
}
},
"node_modules/tqsdk": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/tqsdk/-/tqsdk-1.3.1.tgz",
"integrity": "sha512-3SzqnMg4lES/GWw2ktKJ17619UrFSSmElmix32Ptula/Us5gKyBpukra9XkJbbNBdUOgi4RGnyvW1edseEtFtw==",
"license": "ISC",
"dependencies": {
"core-js": "^3.4.4",
"eventemitter3": "^3.1.0",
"localforage": "^1.7.3"
},
"optionalDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
}
},
"node_modules/tr46": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
@ -4619,6 +4702,20 @@
"punycode": "^2.1.0"
}
},
"node_modules/utf-8-validate": {
"version": "5.0.10",
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",

@ -11,32 +11,33 @@
"test": "mocha --require ts-node/register test/**/*.ts"
},
"dependencies": {
"express": "^4.18.2",
"cors": "^2.8.5",
"helmet": "^7.1.0",
"morgan": "^1.10.0",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"express-rate-limit": "^7.1.5",
"helmet": "^7.1.0",
"jsonwebtoken": "^9.0.2",
"dotenv": "^16.3.1",
"mongoose": "^8.0.3",
"morgan": "^1.10.0",
"pg": "^8.11.3",
"redis": "^4.6.12",
"socket.io": "^4.7.4"
"socket.io": "^4.7.4",
"tqsdk": "^1.3.1"
},
"devDependencies": {
"@types/express": "^4.17.21",
"@types/cors": "^2.8.17",
"@types/morgan": "^1.9.9",
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.5",
"@types/pg": "^8.10.9",
"@types/morgan": "^1.9.9",
"@types/node": "^20.10.4",
"@types/pg": "^8.10.9",
"@typescript-eslint/eslint-plugin": "^6.15.0",
"@typescript-eslint/parser": "^6.15.0",
"chai": "^4.3.10",
"eslint": "^8.56.0",
"ts-node-dev": "^2.0.0",
"typescript": "^5.3.3",
"mocha": "^10.2.0",
"chai": "^4.3.10",
"supertest": "^6.3.3"
"supertest": "^6.3.3",
"ts-node-dev": "^2.0.0",
"typescript": "^5.3.3"
}
}

@ -95,4 +95,36 @@ router.post('/user', async (req, res) => {
}
});
// 测试数据库连接
router.post('/test-database', async (req, res) => {
try {
const { dbType, config } = req.body;
console.log(`测试${dbType}连接`, config);
// 模拟测试操作
await new Promise(resolve => setTimeout(resolve, 1000));
res.status(200).json({ success: true, message: `${dbType}连接测试成功` });
} catch (error) {
console.error('测试数据库连接失败:', error);
res.status(500).json({ success: false, message: '测试数据库连接失败' });
}
});
// 测试数据源连接
router.post('/test-datasource', async (req, res) => {
try {
const { dsType, config } = req.body;
console.log(`测试${dsType}数据源连接`, config);
// 模拟测试操作
await new Promise(resolve => setTimeout(resolve, 1000));
res.status(200).json({ success: true, message: `${dsType}数据源连接测试成功` });
} catch (error) {
console.error('测试数据源连接失败:', error);
res.status(500).json({ success: false, message: '测试数据源连接失败' });
}
});
export default router;

@ -1,34 +1,212 @@
// 市场数据服务
import { DataSourceFactory, DataSourceType } from './datasource/DataSourceFactory';
import { futuresList, generateFuturesOverview, generateFutureData, generateKlineData, riskAlerts } from '../utils/mockData';
import { config } from '../config';
// 获取数据源配置
const getDataSourceConfig = () => {
// 这里应该从配置文件或数据库中读取数据源配置
// 暂时返回默认配置
return {
tqsdk: {
enabled: true,
apiKey: '',
apiSecret: '',
username: '',
password: '',
timeout: 30000,
retries: 3,
maxConnections: 5
}
};
};
// 获取市场概览
export const fetchMarketOverview = async () => {
// 模拟API请求延迟
try {
// 获取数据源配置
const dataSourceConfig = getDataSourceConfig();
// 使用TQSDK数据源
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
const overview = await dataSource.getMarketOverview();
// 转换为前端需要的格式
return overview.map(item => ({
code: item.symbol,
name: item.name,
currentPrice: item.price,
changePercent: item.change_percent,
winRate: Math.floor(Math.random() * 50) + 30, // 模拟胜率
atr: +(Math.random() * 5 + 0.5).toFixed(2), // 模拟ATR
adx: Math.floor(Math.random() * 60) + 10, // 模拟ADX
adxStatus: adx => {
if (adx < 20) return '无趋势/震荡';
if (adx < 40) return '弱趋势';
return '强趋势';
},
trends: {
'5MIN': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'30MIN': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'1HOUR': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'1DAY': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
}
},
tradingAdvice: {
entry: item.price,
stopLoss: item.price * (1 - 0.02 * (Math.random() + 0.5)),
target: item.price * (1 + 0.03 * (Math.random() + 0.5)),
resistance: item.price * (1 + 0.05 * (Math.random() + 0.5)),
support: item.price * (1 - 0.05 * (Math.random() + 0.5))
},
overallView: ['观望', '中线', '多头排列', '空头排列', '震荡'][Math.floor(Math.random() * 5)],
aiAnalysis: `MACD:金叉向上 | RSI:${Math.floor(Math.random() * 80) + 10}(中性) | 布林带:中轨附近`
}));
} catch (error) {
console.error('获取市场概览失败:', error);
// 失败时使用模拟数据
await new Promise(resolve => setTimeout(resolve, 300));
return generateFuturesOverview();
}
};
// 获取品种详情
export const fetchMarketDetail = async (symbol: string) => {
// 模拟API请求延迟
try {
// 获取数据源配置
const dataSourceConfig = getDataSourceConfig();
// 使用TQSDK数据源
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
const contract = await dataSource.getContractDetail(symbol);
const tick = await dataSource.getTickData(symbol);
// 转换为前端需要的格式
return {
code: contract.symbol,
name: contract.name,
fullName: `${contract.name}-${contract.symbol}605`,
currentPrice: tick.last_price,
changePercent: tick.price_change / tick.pre_close * 100,
winRate: Math.floor(Math.random() * 50) + 30,
atr: +(Math.random() * 5 + 0.5).toFixed(2),
adx: Math.floor(Math.random() * 60) + 10,
adxStatus: adx => {
if (adx < 20) return '无趋势/震荡';
if (adx < 40) return '弱趋势';
return '强趋势';
},
trends: {
'5MIN': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'30MIN': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'1HOUR': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
},
'1DAY': {
direction: ['看多', '看空', '观望'][Math.floor(Math.random() * 3)],
status: ['多头趋势', '空头趋势', '震荡'][Math.floor(Math.random() * 3)],
rsi: Math.floor(Math.random() * 80) + 10
}
},
indicators: {
macd: ['金叉向上', '死叉向下', '走平'][Math.floor(Math.random() * 3)],
rsi: `${Math.floor(Math.random() * 80) + 10}(中性)`,
bollinger: ['触及上轨', '触及下轨', '中轨附近'][Math.floor(Math.random() * 3)],
kdj: ['金叉向上', '死叉向下', '走平'][Math.floor(Math.random() * 3)]
},
tradingAdvice: {
entry: tick.last_price,
stopLoss: tick.last_price * (1 - 0.02 * (Math.random() + 0.5)),
target: tick.last_price * (1 + 0.03 * (Math.random() + 0.5)),
resistance: tick.last_price * (1 + 0.05 * (Math.random() + 0.5)),
support: tick.last_price * (1 - 0.05 * (Math.random() + 0.5))
},
riskLevel: ['低', '中等', '高'][Math.floor(Math.random() * 3)],
volatility: ['低', '中等', '高'][Math.floor(Math.random() * 3)],
overallView: ['观望', '中线', '多头排列', '空头排列', '震荡'][Math.floor(Math.random() * 5)],
aiAnalysis: `MACD:金叉向上 | RSI:${Math.floor(Math.random() * 80) + 10}(中性) | 布林带:中轨附近`
};
} catch (error) {
console.error(`获取品种${symbol}详情失败:`, error);
// 失败时使用模拟数据
await new Promise(resolve => setTimeout(resolve, 200));
const future = futuresList.find(item => item.code === symbol);
if (!future) {
throw new Error('品种不存在');
}
return generateFutureData(symbol, future.name);
}
};
// 获取K线数据
export const fetchKlineData = async (symbol: string, period: string) => {
// 模拟API请求延迟
try {
// 获取数据源配置
const dataSourceConfig = getDataSourceConfig();
// 使用TQSDK数据源
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig);
const klineData = await dataSource.getKlineData(symbol, period, 30);
// 转换为前端需要的格式
return klineData.map(item => ({
timestamp: item.datetime / 1000000000, // 转换为秒
open: item.open,
high: item.high,
low: item.low,
close: item.close,
volume: item.volume
}));
} catch (error) {
console.error(`获取合约${symbol}K线数据失败:`, error);
// 失败时使用模拟数据
await new Promise(resolve => setTimeout(resolve, 200));
return generateKlineData(30);
}
};
// 获取市场热点
export const fetchMarketHotspots = async () => {
// 模拟API请求延迟
try {
// 使用TQSDK数据源
const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK);
const overview = await dataSource.getMarketOverview();
// 按涨跌幅排序返回前10个
return overview
.sort((a, b) => Math.abs(b.change_percent) - Math.abs(a.change_percent))
.slice(0, 10)
.map(item => ({
symbol: item.symbol,
name: item.name,
change: item.change_percent,
volume: item.volume
}));
} catch (error) {
console.error('获取市场热点失败:', error);
// 失败时使用模拟数据
await new Promise(resolve => setTimeout(resolve, 200));
const overview = generateFuturesOverview();
// 按涨跌幅排序返回前10个
@ -41,11 +219,12 @@ export const fetchMarketHotspots = async () => {
change: item.changePercent,
volume: Math.floor(Math.random() * 1000000) + 100000
}));
}
};
// 获取风险预警
export const fetchRiskAlerts = async () => {
// 模拟API请求延迟
// 直接返回模拟数据
await new Promise(resolve => setTimeout(resolve, 100));
return riskAlerts;
};

@ -1,5 +1,5 @@
import React, { useState } from 'react';
import { Card, Row, Col, Form, Input, Button, Select, Switch, InputNumber, Alert, Divider, Tabs } from 'antd';
import { Card, Row, Col, Form, Input, Button, Select, Switch, InputNumber, Alert, Divider, Tabs, message } from 'antd';
import { DatabaseOutlined, KeyOutlined, SettingOutlined, SaveOutlined, ToolOutlined } from '@ant-design/icons';
import './AdminConfig.css';
@ -78,10 +78,43 @@ const AdminConfig = () => {
}
};
//
const dataSourceConfig = {
// TQSDK
tqsdk: {
enabled: true,
username: '',
password: '',
timeout: 30000,
retries: 3,
maxConnections: 5
},
// Wind
wind: {
enabled: false,
apiKey: '',
apiSecret: '',
url: 'https://api.wind.com.cn',
timeout: 30000,
retries: 3
},
//
sina: {
enabled: false,
url: 'https://finance.sina.com.cn',
timeout: 10000,
retries: 3,
refreshInterval: 60000
},
//
defaultDataSource: 'tqsdk'
};
const [config, setConfig] = useState({
database: databaseConfig,
server: serverConfig,
security: securityConfig
security: securityConfig,
dataSource: dataSourceConfig
});
//
@ -109,42 +142,193 @@ const AdminConfig = () => {
}));
};
//
const handleDataSourceConfigChange = (dsType, key, value) => {
setConfig(prev => ({
...prev,
dataSource: {
...prev.dataSource,
[dsType]: {
...prev.dataSource[dsType],
[key]: value
}
}
}));
};
//
const handleSubmit = (values) => {
console.log('配置保存:', values);
//
Alert.success('配置已保存');
message.success('配置已保存');
};
//
const testDatabaseConnection = (dbType) => {
console.log(`测试${dbType}连接`);
//
setTimeout(() => {
Alert.success(`${dbType}连接测试成功`);
}, 1000);
const testDatabaseConnection = async (dbType) => {
try {
const response = await fetch('http://localhost:3005/api/config/test-database', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
dbType,
config: config.database[dbType.toLowerCase()]
})
});
const result = await response.json();
if (result.success) {
message.success(result.message);
} else {
message.error(result.message);
}
} catch (error) {
console.error('测试数据库连接失败:', error);
message.error('测试数据库连接失败,请检查网络连接');
}
};
//
const testDataSourceConnection = async (dsType) => {
try {
const response = await fetch('http://localhost:3005/api/config/test-datasource', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
dsType,
config: config.dataSource[dsType.toLowerCase()]
})
});
const result = await response.json();
if (result.success) {
message.success(result.message);
} else {
message.error(result.message);
}
} catch (error) {
console.error('测试数据源连接失败:', error);
message.error('测试数据源连接失败,请检查网络连接');
}
};
return (
<div className="admin-config">
<h2>管理配置</h2>
<Alert
message="警告:此页面包含敏感配置信息,请谨慎操作"
title="警告:此页面包含敏感配置信息,请谨慎操作"
type="warning"
style={{ marginBottom: 24 }}
/>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Tabs defaultActiveKey="database">
{/* 数据库配置 */}
<TabPane tab={<span><DatabaseOutlined /> 数据库配置</span>} key="database">
<Form
form={form}
layout="vertical"
onFinish={handleSubmit}
initialValues={{
database: {
mongoDB: {
host: config.database.mongoDB.host,
port: config.database.mongoDB.port,
database: config.database.mongoDB.database,
username: config.database.mongoDB.username,
password: config.database.mongoDB.password,
authSource: config.database.mongoDB.authSource,
ssl: config.database.mongoDB.ssl,
enabled: config.database.mongoDB.enabled
},
postgreSQL: {
host: config.database.postgreSQL.host,
port: config.database.postgreSQL.port,
database: config.database.postgreSQL.database,
username: config.database.postgreSQL.username,
password: config.database.postgreSQL.password,
ssl: config.database.postgreSQL.ssl,
enabled: config.database.postgreSQL.enabled
},
redis: {
host: config.database.redis.host,
port: config.database.redis.port,
password: config.database.redis.password,
db: config.database.redis.db,
enabled: config.database.redis.enabled
},
influxDB: {
host: config.database.influxDB.host,
port: config.database.influxDB.port,
database: config.database.influxDB.database,
username: config.database.influxDB.username,
password: config.database.influxDB.password,
ssl: config.database.influxDB.ssl,
enabled: config.database.influxDB.enabled
}
},
server: {
port: config.server.port,
host: config.server.host,
environment: config.server.environment,
debug: config.server.debug,
timeout: config.server.timeout,
maxBodySize: config.server.maxBodySize
},
security: {
jwtSecret: config.security.jwtSecret,
jwtExpiresIn: config.security.jwtExpiresIn,
rateLimit: {
windowMs: config.security.rateLimit.windowMs,
max: config.security.rateLimit.max
},
cors: {
origin: config.security.cors.origin,
methods: config.security.cors.methods.join(', '),
allowedHeaders: config.security.cors.allowedHeaders.join(', ')
}
},
dataSource: {
defaultDataSource: config.dataSource.defaultDataSource,
tqsdk: {
enabled: config.dataSource.tqsdk.enabled,
username: config.dataSource.tqsdk.username,
password: config.dataSource.tqsdk.password,
timeout: config.dataSource.tqsdk.timeout,
retries: config.dataSource.tqsdk.retries,
maxConnections: config.dataSource.tqsdk.maxConnections
},
wind: {
enabled: config.dataSource.wind.enabled,
apiKey: config.dataSource.wind.apiKey,
apiSecret: config.dataSource.wind.apiSecret,
url: config.dataSource.wind.url,
timeout: config.dataSource.wind.timeout,
retries: config.dataSource.wind.retries
},
sina: {
enabled: config.dataSource.sina.enabled,
url: config.dataSource.sina.url,
timeout: config.dataSource.sina.timeout,
retries: config.dataSource.sina.retries,
refreshInterval: config.dataSource.sina.refreshInterval
}
}
}}
>
<Tabs
defaultActiveKey="database"
items={[
{
label: <span><DatabaseOutlined /> 数据库配置</span>,
key: 'database',
children: (
<>
{/* MongoDB配置 */}
<Card title="MongoDB配置" className="admin-config-card" style={{ marginBottom: 24 }}>
<Row gutter={[16, 16]}>
<Col span={6}>
<Item label="主机" name="database.mongoDB.host">
<Input
defaultValue={config.database.mongoDB.host}
onChange={(e) => handleDatabaseConfigChange('mongoDB', 'host', e.target.value)}
/>
</Item>
@ -152,7 +336,6 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="端口" name="database.mongoDB.port">
<InputNumber
defaultValue={config.database.mongoDB.port}
min={1}
max={65535}
onChange={(value) => handleDatabaseConfigChange('mongoDB', 'port', value)}
@ -162,7 +345,6 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="数据库" name="database.mongoDB.database">
<Input
defaultValue={config.database.mongoDB.database}
onChange={(e) => handleDatabaseConfigChange('mongoDB', 'database', e.target.value)}
/>
</Item>
@ -170,7 +352,6 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="认证源" name="database.mongoDB.authSource">
<Input
defaultValue={config.database.mongoDB.authSource}
onChange={(e) => handleDatabaseConfigChange('mongoDB', 'authSource', e.target.value)}
/>
</Item>
@ -178,7 +359,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="用户名" name="database.mongoDB.username">
<Input
defaultValue={config.database.mongoDB.username}
onChange={(e) => handleDatabaseConfigChange('mongoDB', 'username', e.target.value)}
/>
</Item>
@ -186,7 +367,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="密码" name="database.mongoDB.password">
<Input.Password
defaultValue={config.database.mongoDB.password}
onChange={(e) => handleDatabaseConfigChange('mongoDB', 'password', e.target.value)}
/>
</Item>
@ -240,7 +421,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="主机" name="database.postgreSQL.host">
<Input
defaultValue={config.database.postgreSQL.host}
onChange={(e) => handleDatabaseConfigChange('postgreSQL', 'host', e.target.value)}
/>
</Item>
@ -248,7 +429,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="端口" name="database.postgreSQL.port">
<InputNumber
defaultValue={config.database.postgreSQL.port}
min={1}
max={65535}
onChange={(value) => handleDatabaseConfigChange('postgreSQL', 'port', value)}
@ -258,7 +439,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="数据库" name="database.postgreSQL.database">
<Input
defaultValue={config.database.postgreSQL.database}
onChange={(e) => handleDatabaseConfigChange('postgreSQL', 'database', e.target.value)}
/>
</Item>
@ -266,7 +447,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="用户名" name="database.postgreSQL.username">
<Input
defaultValue={config.database.postgreSQL.username}
onChange={(e) => handleDatabaseConfigChange('postgreSQL', 'username', e.target.value)}
/>
</Item>
@ -274,7 +455,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="密码" name="database.postgreSQL.password">
<Input.Password
defaultValue={config.database.postgreSQL.password}
onChange={(e) => handleDatabaseConfigChange('postgreSQL', 'password', e.target.value)}
/>
</Item>
@ -327,7 +508,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="主机" name="database.redis.host">
<Input
defaultValue={config.database.redis.host}
onChange={(e) => handleDatabaseConfigChange('redis', 'host', e.target.value)}
/>
</Item>
@ -335,7 +516,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="端口" name="database.redis.port">
<InputNumber
defaultValue={config.database.redis.port}
min={1}
max={65535}
onChange={(value) => handleDatabaseConfigChange('redis', 'port', value)}
@ -345,7 +526,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="密码" name="database.redis.password">
<Input.Password
defaultValue={config.database.redis.password}
onChange={(e) => handleDatabaseConfigChange('redis', 'password', e.target.value)}
/>
</Item>
@ -353,7 +534,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="数据库" name="database.redis.db">
<InputNumber
defaultValue={config.database.redis.db}
min={0}
max={15}
onChange={(value) => handleDatabaseConfigChange('redis', 'db', value)}
@ -398,7 +579,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="主机" name="database.influxDB.host">
<Input
defaultValue={config.database.influxDB.host}
onChange={(e) => handleDatabaseConfigChange('influxDB', 'host', e.target.value)}
/>
</Item>
@ -406,7 +587,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="端口" name="database.influxDB.port">
<InputNumber
defaultValue={config.database.influxDB.port}
min={1}
max={65535}
onChange={(value) => handleDatabaseConfigChange('influxDB', 'port', value)}
@ -416,7 +597,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="数据库" name="database.influxDB.database">
<Input
defaultValue={config.database.influxDB.database}
onChange={(e) => handleDatabaseConfigChange('influxDB', 'database', e.target.value)}
/>
</Item>
@ -424,7 +605,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="用户名" name="database.influxDB.username">
<Input
defaultValue={config.database.influxDB.username}
onChange={(e) => handleDatabaseConfigChange('influxDB', 'username', e.target.value)}
/>
</Item>
@ -432,7 +613,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="密码" name="database.influxDB.password">
<Input.Password
defaultValue={config.database.influxDB.password}
onChange={(e) => handleDatabaseConfigChange('influxDB', 'password', e.target.value)}
/>
</Item>
@ -478,16 +659,19 @@ const AdminConfig = () => {
</Button>
</div>
</Card>
</TabPane>
{/* 服务器配置 */}
<TabPane tab={<span><ToolOutlined /> 服务器配置</span>} key="server">
</>
)
},
{
label: <span><ToolOutlined /> 服务器配置</span>,
key: 'server',
children: (
<Card title="服务器配置" className="admin-config-card" style={{ marginBottom: 24 }}>
<Row gutter={[16, 16]}>
<Col span={6}>
<Item label="端口" name="server.port">
<InputNumber
defaultValue={config.server.port}
min={1}
max={65535}
onChange={(value) => handleConfigChange('server', 'port', value)}
@ -497,7 +681,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="主机" name="server.host">
<Input
defaultValue={config.server.host}
onChange={(e) => handleConfigChange('server', 'host', e.target.value)}
/>
</Item>
@ -505,7 +689,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="环境" name="server.environment">
<Select
defaultValue={config.server.environment}
onChange={(value) => handleConfigChange('server', 'environment', value)}
>
<Option value="development">开发环境</Option>
@ -525,7 +709,7 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="超时时间(ms)" name="server.timeout">
<InputNumber
defaultValue={config.server.timeout}
min={1000}
max={60000}
step={1000}
@ -536,24 +720,27 @@ const AdminConfig = () => {
<Col span={6}>
<Item label="最大请求体大小" name="server.maxBodySize">
<Input
defaultValue={config.server.maxBodySize}
onChange={(e) => handleConfigChange('server', 'maxBodySize', e.target.value)}
/>
</Item>
</Col>
</Row>
</Card>
</TabPane>
{/* 安全配置 */}
<TabPane tab={<span><KeyOutlined /> 安全配置</span>} key="security">
)
},
{
label: <span><KeyOutlined /> 安全配置</span>,
key: 'security',
children: (
<>
{/* JWT配置 */}
<Card title="JWT配置" className="admin-config-card" style={{ marginBottom: 24 }}>
<Row gutter={[16, 16]}>
<Col span={12}>
<Item label="JWT密钥" name="security.jwtSecret">
<Input.Password
defaultValue={config.security.jwtSecret}
onChange={(e) => handleConfigChange('security', 'jwtSecret', e.target.value)}
/>
</Item>
@ -561,7 +748,7 @@ const AdminConfig = () => {
<Col span={12}>
<Item label="过期时间" name="security.jwtExpiresIn">
<Input
defaultValue={config.security.jwtExpiresIn}
onChange={(e) => handleConfigChange('security', 'jwtExpiresIn', e.target.value)}
/>
</Item>
@ -575,7 +762,7 @@ const AdminConfig = () => {
<Col span={12}>
<Item label="时间窗口(ms)" name="security.rateLimit.windowMs">
<InputNumber
defaultValue={config.security.rateLimit.windowMs}
min={1000}
max={3600000}
step={1000}
@ -586,7 +773,7 @@ const AdminConfig = () => {
<Col span={12}>
<Item label="最大请求数" name="security.rateLimit.max">
<InputNumber
defaultValue={config.security.rateLimit.max}
min={1}
max={1000}
step={10}
@ -603,7 +790,7 @@ const AdminConfig = () => {
<Col span={24}>
<Item label="允许的源" name="security.cors.origin">
<Input
defaultValue={config.security.cors.origin}
onChange={(e) => handleConfigChange('security', 'cors', { ...config.security.cors, origin: e.target.value })}
/>
</Item>
@ -611,7 +798,7 @@ const AdminConfig = () => {
<Col span={24}>
<Item label="允许的方法" name="security.cors.methods">
<Input
defaultValue={config.security.cors.methods.join(', ')}
onChange={(e) => handleConfigChange('security', 'cors', { ...config.security.cors, methods: e.target.value.split(', ').map(m => m.trim()) })}
/>
</Item>
@ -619,15 +806,286 @@ const AdminConfig = () => {
<Col span={24}>
<Item label="允许的头部" name="security.cors.allowedHeaders">
<Input
defaultValue={config.security.cors.allowedHeaders.join(', ')}
onChange={(e) => handleConfigChange('security', 'cors', { ...config.security.cors, allowedHeaders: e.target.value.split(', ').map(h => h.trim()) })}
/>
</Item>
</Col>
</Row>
</Card>
</TabPane>
</Tabs>
</>
)
},
{
label: <span><DatabaseOutlined /> 数据源配置</span>,
key: 'dataSource',
children: (
<>
{/* 默认数据源配置 */}
<Card title="默认数据源" className="admin-config-card" style={{ marginBottom: 24 }}>
<Row gutter={[16, 16]}>
<Col span={12}>
<Item label="默认数据源">
<Select
onChange={(value) => handleConfigChange('dataSource', 'defaultDataSource', value)}
>
<Option value="tqsdk">TQSDK</Option>
<Option value="wind">Wind</Option>
<Option value="sina">新浪财经</Option>
</Select>
</Item>
</Col>
</Row>
</Card>
{/* TQSDK配置 */}
<Card title="TQSDK配置" className="admin-config-card" style={{ marginBottom: 24 }}>
<Row gutter={[16, 16]}>
<Col span={6}>
<Item label="启用">
<Switch
defaultChecked={config.dataSource.tqsdk.enabled}
onChange={(checked) => handleDataSourceConfigChange('tqsdk', 'enabled', checked)}
/>
</Item>
</Col>
<Col span={12}>
<Item label="用户名">
<Input
onChange={(e) => handleDataSourceConfigChange('tqsdk', 'username', e.target.value)}
/>
</Item>
</Col>
<Col span={12}>
<Item label="密码">
<Input.Password
onChange={(e) => handleDataSourceConfigChange('tqsdk', 'password', e.target.value)}
/>
</Item>
</Col>
<Col span={6}>
<Item label="超时时间(ms)">
<InputNumber
min={1000}
max={60000}
step={1000}
onChange={(value) => handleDataSourceConfigChange('tqsdk', 'timeout', value)}
/>
</Item>
</Col>
<Col span={6}>
<Item label="重试次数">
<InputNumber
min={0}
max={10}
step={1}
onChange={(value) => handleDataSourceConfigChange('tqsdk', 'retries', value)}
/>
</Item>
</Col>
<Col span={6}>
<Item label="最大连接数">
<InputNumber
min={1}
max={20}
step={1}
onChange={(value) => handleDataSourceConfigChange('tqsdk', 'maxConnections', value)}
/>
</Item>
</Col>
</Row>
<div style={{ marginTop: 16 }}>
<Button
type="primary"
onClick={() => testDataSourceConnection('TQSDK')}
style={{ marginRight: 8 }}
>
测试连接
</Button>
<Button
type="default"
onClick={() => {
handleDataSourceConfigChange('tqsdk', 'enabled', true);
handleDataSourceConfigChange('tqsdk', 'username', '');
handleDataSourceConfigChange('tqsdk', 'password', '');
handleDataSourceConfigChange('tqsdk', 'timeout', 30000);
handleDataSourceConfigChange('tqsdk', 'retries', 3);
handleDataSourceConfigChange('tqsdk', 'maxConnections', 5);
}}
>
恢复默认
</Button>
</div>
</Card>
{/* Wind配置 */}
<Card title="Wind配置" className="admin-config-card" style={{ marginBottom: 24 }}>
<Row gutter={[16, 16]}>
<Col span={6}>
<Item label="启用">
<Switch
defaultChecked={config.dataSource.wind.enabled}
onChange={(checked) => handleDataSourceConfigChange('wind', 'enabled', checked)}
/>
</Item>
</Col>
<Col span={12}>
<Item label="API密钥">
<Input.Password
onChange={(e) => handleDataSourceConfigChange('wind', 'apiKey', e.target.value)}
/>
</Item>
</Col>
<Col span={12}>
<Item label="API密钥密钥">
<Input.Password
onChange={(e) => handleDataSourceConfigChange('wind', 'apiSecret', e.target.value)}
/>
</Item>
</Col>
<Col span={12}>
<Item label="API URL">
<Input
onChange={(e) => handleDataSourceConfigChange('wind', 'url', e.target.value)}
/>
</Item>
</Col>
<Col span={6}>
<Item label="超时时间(ms)">
<InputNumber
min={1000}
max={60000}
step={1000}
onChange={(value) => handleDataSourceConfigChange('wind', 'timeout', value)}
/>
</Item>
</Col>
<Col span={6}>
<Item label="重试次数">
<InputNumber
min={0}
max={10}
step={1}
onChange={(value) => handleDataSourceConfigChange('wind', 'retries', value)}
/>
</Item>
</Col>
</Row>
<div style={{ marginTop: 16 }}>
<Button
type="primary"
onClick={() => testDataSourceConnection('Wind')}
style={{ marginRight: 8 }}
>
测试连接
</Button>
<Button
type="default"
onClick={() => {
handleDataSourceConfigChange('wind', 'enabled', false);
handleDataSourceConfigChange('wind', 'apiKey', '');
handleDataSourceConfigChange('wind', 'apiSecret', '');
handleDataSourceConfigChange('wind', 'url', 'https://api.wind.com.cn');
handleDataSourceConfigChange('wind', 'timeout', 30000);
handleDataSourceConfigChange('wind', 'retries', 3);
}}
>
恢复默认
</Button>
</div>
</Card>
{/* 新浪财经配置 */}
<Card title="新浪财经配置" className="admin-config-card" style={{ marginBottom: 24 }}>
<Row gutter={[16, 16]}>
<Col span={6}>
<Item label="启用">
<Switch
defaultChecked={config.dataSource.sina.enabled}
onChange={(checked) => handleDataSourceConfigChange('sina', 'enabled', checked)}
/>
</Item>
</Col>
<Col span={12}>
<Item label="API URL">
<Input
onChange={(e) => handleDataSourceConfigChange('sina', 'url', e.target.value)}
/>
</Item>
</Col>
<Col span={6}>
<Item label="超时时间(ms)">
<InputNumber
min={1000}
max={60000}
step={1000}
onChange={(value) => handleDataSourceConfigChange('sina', 'timeout', value)}
/>
</Item>
</Col>
<Col span={6}>
<Item label="重试次数">
<InputNumber
min={0}
max={10}
step={1}
onChange={(value) => handleDataSourceConfigChange('sina', 'retries', value)}
/>
</Item>
</Col>
<Col span={6}>
<Item label="刷新间隔(ms)">
<InputNumber
min={1000}
max={300000}
step={1000}
onChange={(value) => handleDataSourceConfigChange('sina', 'refreshInterval', value)}
/>
</Item>
</Col>
</Row>
<div style={{ marginTop: 16 }}>
<Button
type="primary"
onClick={() => testDataSourceConnection('新浪财经')}
style={{ marginRight: 8 }}
>
测试连接
</Button>
<Button
type="default"
onClick={() => {
handleDataSourceConfigChange('sina', 'enabled', false);
handleDataSourceConfigChange('sina', 'url', 'https://finance.sina.com.cn');
handleDataSourceConfigChange('sina', 'timeout', 10000);
handleDataSourceConfigChange('sina', 'retries', 3);
handleDataSourceConfigChange('sina', 'refreshInterval', 60000);
}}
>
恢复默认
</Button>
</div>
</Card>
</>
)
}
]}
/>
<Divider />

Loading…
Cancel
Save