diff --git a/backend/src/api/config.ts b/backend/src/api/config.ts index dc620b5..a85e4d9 100644 --- a/backend/src/api/config.ts +++ b/backend/src/api/config.ts @@ -127,4 +127,45 @@ router.post('/test-datasource', async (req, res) => { } }); +// 保存配置 +router.post('/save', async (req, res) => { + try { + const config = req.body; + console.log('保存配置:', config); + + // 保存配置到文件 + const fs = require('fs'); + const path = require('path'); + const configPath = path.join(__dirname, '../../config.json'); + + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + + res.status(200).json({ success: true, message: '配置保存成功' }); + } catch (error) { + console.error('保存配置失败:', error); + res.status(500).json({ success: false, message: '保存配置失败' }); + } +}); + +// 获取配置 +router.get('/get', async (req, res) => { + try { + // 从文件读取配置 + const fs = require('fs'); + const path = require('path'); + const configPath = path.join(__dirname, '../../config.json'); + + let config = {}; + if (fs.existsSync(configPath)) { + const configData = fs.readFileSync(configPath, 'utf8'); + config = JSON.parse(configData); + } + + res.status(200).json({ success: true, data: config }); + } catch (error) { + console.error('获取配置失败:', error); + res.status(500).json({ success: false, message: '获取配置失败' }); + } +}); + export default router; \ No newline at end of file diff --git a/backend/src/app.ts b/backend/src/app.ts index 7f22f5d..6d5e02d 100644 --- a/backend/src/app.ts +++ b/backend/src/app.ts @@ -55,8 +55,8 @@ app.use((err: any, req: express.Request, res: express.Response, next: express.Ne }); // 启动服务器 -app.listen(config.port, () => { - console.log(`服务器运行在 http://localhost:${config.port}`); +app.listen(3007, () => { + console.log('服务器运行在 http://localhost:3007'); }); export default app; \ No newline at end of file diff --git a/backend/src/config/index.ts b/backend/src/config/index.ts index 1f8b02f..8f8d593 100644 --- a/backend/src/config/index.ts +++ b/backend/src/config/index.ts @@ -1,32 +1,80 @@ import dotenv from 'dotenv'; +import fs from 'fs'; +import path from 'path'; dotenv.config(); +// 从文件读取配置 +let fileConfig = {}; +try { + const configPath = path.join(__dirname, '../../../config.json'); + if (fs.existsSync(configPath)) { + const configData = fs.readFileSync(configPath, 'utf8'); + fileConfig = JSON.parse(configData); + } +} catch (error) { + console.error('读取配置文件失败:', error); +} + export const config = { - port: process.env.PORT || 3005, - jwtSecret: process.env.JWT_SECRET || 'your-secret-key', + port: process.env.PORT || (fileConfig.server?.port || 3005), + jwtSecret: process.env.JWT_SECRET || (fileConfig.security?.jwtSecret || 'your-secret-key'), database: { mongo: { url: process.env.MONGO_URL || 'mongodb://localhost:27017/alpha-futures' }, postgres: { - host: process.env.PG_HOST || 'localhost', - port: parseInt(process.env.PG_PORT || '5432'), - user: process.env.PG_USER || 'postgres', - password: process.env.PG_PASSWORD || 'password', - database: process.env.PG_DATABASE || 'alpha-futures' + host: process.env.PG_HOST || (fileConfig.database?.postgreSQL?.host || 'localhost'), + port: parseInt(process.env.PG_PORT || (fileConfig.database?.postgreSQL?.port || '5432')), + user: process.env.PG_USER || (fileConfig.database?.postgreSQL?.username || 'postgres'), + password: process.env.PG_PASSWORD || (fileConfig.database?.postgreSQL?.password || 'password'), + database: process.env.PG_DATABASE || (fileConfig.database?.postgreSQL?.database || 'alpha-futures') } }, redis: { url: process.env.REDIS_URL || 'redis://localhost:6379' }, rateLimit: { - windowMs: 60 * 1000, // 1分钟 - max: 120 // 每分钟120次请求 + windowMs: fileConfig.security?.rateLimit?.windowMs || 60 * 1000, // 1分钟 + max: fileConfig.security?.rateLimit?.max || 120 // 每分钟120次请求 }, cors: { - origin: '*', // 在生产环境中应该设置具体的域名 - methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Content-Type', 'Authorization'] + origin: fileConfig.security?.cors?.origin || '*', // 在生产环境中应该设置具体的域名 + methods: fileConfig.security?.cors?.methods || ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], + allowedHeaders: fileConfig.security?.cors?.allowedHeaders || ['Content-Type', 'Authorization'] + }, + dataSource: fileConfig.dataSource || { + test: { + enabled: true, + timeout: 10000, + retries: 3, + refreshInterval: 60000 + }, + tqsdk: { + enabled: true, + apiKey: '', + apiSecret: '', + username: '', + password: '', + timeout: 30000, + retries: 3, + maxConnections: 5 + }, + 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' } }; \ No newline at end of file diff --git a/backend/src/services/marketService.ts b/backend/src/services/marketService.ts index f909d21..64ef3d2 100644 --- a/backend/src/services/marketService.ts +++ b/backend/src/services/marketService.ts @@ -5,20 +5,8 @@ import { config } from '../config'; // 获取数据源配置 const getDataSourceConfig = () => { - // 这里应该从配置文件或数据库中读取数据源配置 - // 暂时返回默认配置 - return { - tqsdk: { - enabled: true, - apiKey: '', - apiSecret: '', - username: '', - password: '', - timeout: 30000, - retries: 3, - maxConnections: 5 - } - }; + // 从配置文件中读取数据源配置 + return config.dataSource; }; // 获取市场概览 @@ -78,9 +66,17 @@ export const fetchMarketOverview = async () => { })); } catch (error) { console.error('获取市场概览失败:', error); - // 失败时使用模拟数据 - await new Promise(resolve => setTimeout(resolve, 300)); - return generateFuturesOverview(); + // 获取数据源配置 + const dataSourceConfig = getDataSourceConfig(); + // 检查是否启用了测试数据源 + if (dataSourceConfig.test && dataSourceConfig.test.enabled) { + // 启用了测试数据源,使用测试数据 + await new Promise(resolve => setTimeout(resolve, 300)); + return generateFuturesOverview(); + } else { + // 未启用测试数据源,返回友好的错误提示 + throw new Error('获取市场概览失败,请检查数据源配置'); + } } }; @@ -151,13 +147,21 @@ export const fetchMarketDetail = async (symbol: string) => { }; } 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('品种不存在'); + // 获取数据源配置 + const dataSourceConfig = getDataSourceConfig(); + // 检查是否启用了测试数据源 + if (dataSourceConfig.test && dataSourceConfig.test.enabled) { + // 启用了测试数据源,使用测试数据 + 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); + } else { + // 未启用测试数据源,返回友好的错误提示 + throw new Error('获取品种详情失败,请检查数据源配置'); } - return generateFutureData(symbol, future.name); } }; @@ -181,17 +185,27 @@ export const fetchKlineData = async (symbol: string, period: string) => { })); } catch (error) { console.error(`获取合约${symbol}K线数据失败:`, error); - // 失败时使用模拟数据 - await new Promise(resolve => setTimeout(resolve, 200)); - return generateKlineData(30); + // 获取数据源配置 + const dataSourceConfig = getDataSourceConfig(); + // 检查是否启用了测试数据源 + if (dataSourceConfig.test && dataSourceConfig.test.enabled) { + // 启用了测试数据源,使用测试数据 + await new Promise(resolve => setTimeout(resolve, 200)); + return generateKlineData(30); + } else { + // 未启用测试数据源,返回友好的错误提示 + throw new Error('获取K线数据失败,请检查数据源配置'); + } } }; // 获取市场热点 export const fetchMarketHotspots = async () => { try { + // 获取数据源配置 + const dataSourceConfig = getDataSourceConfig(); // 使用TQSDK数据源 - const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK); + const dataSource = await DataSourceFactory.getDataSource(DataSourceType.TQSDK, dataSourceConfig); const overview = await dataSource.getMarketOverview(); // 按涨跌幅排序,返回前10个 @@ -206,19 +220,27 @@ export const fetchMarketHotspots = async () => { })); } catch (error) { console.error('获取市场热点失败:', error); - // 失败时使用模拟数据 - await new Promise(resolve => setTimeout(resolve, 200)); - const overview = generateFuturesOverview(); - // 按涨跌幅排序,返回前10个 - return overview - .sort((a, b) => Math.abs(b.changePercent) - Math.abs(a.changePercent)) - .slice(0, 10) - .map(item => ({ - symbol: item.code, - name: item.name, - change: item.changePercent, - volume: Math.floor(Math.random() * 1000000) + 100000 - })); + // 获取数据源配置 + const dataSourceConfig = getDataSourceConfig(); + // 检查是否启用了测试数据源 + if (dataSourceConfig.test && dataSourceConfig.test.enabled) { + // 启用了测试数据源,使用测试数据 + await new Promise(resolve => setTimeout(resolve, 200)); + const overview = generateFuturesOverview(); + // 按涨跌幅排序,返回前10个 + return overview + .sort((a, b) => Math.abs(b.changePercent) - Math.abs(a.changePercent)) + .slice(0, 10) + .map(item => ({ + symbol: item.code, + name: item.name, + change: item.changePercent, + volume: Math.floor(Math.random() * 1000000) + 100000 + })); + } else { + // 未启用测试数据源,返回友好的错误提示 + throw new Error('获取市场热点失败,请检查数据源配置'); + } } }; diff --git a/src/pages/admin/AdminConfig.jsx b/src/pages/admin/AdminConfig.jsx index 46b8a57..13eed80 100644 --- a/src/pages/admin/AdminConfig.jsx +++ b/src/pages/admin/AdminConfig.jsx @@ -80,6 +80,13 @@ const AdminConfig = () => { // 数据源配置 const dataSourceConfig = { + // 测试数据源配置 + test: { + enabled: true, + timeout: 10000, + retries: 3, + refreshInterval: 60000 + }, // TQSDK配置 tqsdk: { enabled: true, @@ -157,10 +164,26 @@ const AdminConfig = () => { }; // 处理表单提交 - const handleSubmit = (values) => { - console.log('配置保存:', values); - // 模拟保存操作 - message.success('配置已保存'); + const handleSubmit = async (values) => { + try { + const response = await fetch('http://localhost:3005/api/config/save', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(config) + }); + + const result = await response.json(); + if (result.success) { + message.success(result.message); + } else { + message.error(result.message); + } + } catch (error) { + console.error('保存配置失败:', error); + message.error('保存配置失败,请检查网络连接'); + } }; // 测试数据库连接 @@ -289,6 +312,12 @@ const AdminConfig = () => { }, dataSource: { defaultDataSource: config.dataSource.defaultDataSource, + test: { + enabled: config.dataSource.test.enabled, + timeout: config.dataSource.test.timeout, + retries: config.dataSource.test.retries, + refreshInterval: config.dataSource.test.refreshInterval + }, tqsdk: { enabled: config.dataSource.tqsdk.enabled, username: config.dataSource.tqsdk.username, @@ -839,6 +868,65 @@ const AdminConfig = () => { + {/* 测试数据源配置 */} + + + + + handleDataSourceConfigChange('test', 'enabled', checked)} + /> + + + + + handleDataSourceConfigChange('test', 'timeout', value)} + /> + + + + + handleDataSourceConfigChange('test', 'retries', value)} + /> + + + + + handleDataSourceConfigChange('test', 'refreshInterval', value)} + /> + + + +
+ +
+
+ {/* TQSDK配置 */} diff --git a/src/store/futuresSlice.js b/src/store/futuresSlice.js index abe633b..0bfc440 100644 --- a/src/store/futuresSlice.js +++ b/src/store/futuresSlice.js @@ -1,7 +1,7 @@ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; // 后端API基础URL -const API_BASE_URL = 'http://localhost:3005/api'; +const API_BASE_URL = 'http://localhost:3007/api'; // 异步获取期货概览数据 export const fetchFuturesOverview = createAsyncThunk(