fix: 配置项更新完成

master
Lxy 3 months ago
parent fc402d80d9
commit 05604c9d71

@ -0,0 +1,101 @@
{
"database": {
"mongoDB": {
"host": "127.0.0.1",
"port": 10000,
"database": "aaa",
"username": "aaa",
"password": "aaaa",
"authSource": "aaa",
"ssl": false,
"enabled": true
},
"postgreSQL": {
"host": "localhost",
"port": 5432,
"database": "alpha-futures",
"username": "postgres",
"password": "password",
"ssl": false,
"enabled": true
},
"redis": {
"host": "localhost",
"port": 6379,
"password": "",
"db": 0,
"enabled": true
},
"influxDB": {
"host": "localhost",
"port": 8086,
"database": "alpha-futures",
"username": "",
"password": "",
"ssl": false,
"enabled": true
}
},
"server": {
"port": 3006,
"host": "0.0.0.0",
"environment": "development",
"debug": true,
"timeout": 30000,
"maxBodySize": "10mb"
},
"security": {
"jwtSecret": "your-secret-key",
"jwtExpiresIn": "7d",
"rateLimit": {
"windowMs": 60000,
"max": 120
},
"cors": {
"origin": "*",
"methods": [
"GET",
"POST",
"PUT",
"DELETE",
"OPTIONS"
],
"allowedHeaders": [
"Content-Type",
"Authorization"
]
}
},
"dataSource": {
"test": {
"enabled": true,
"timeout": 10000,
"retries": 3,
"refreshInterval": 60000
},
"tqsdk": {
"enabled": true,
"username": "windsdreamer",
"password": "1qazse42W3",
"timeout": 10000,
"retries": 10,
"maxConnections": 20
},
"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"
}
}

@ -0,0 +1,26 @@
// 数据源抽象接口
export interface DataSource {
// 获取所有合约列表
getContractList(): Promise<any[]>;
// 获取单个合约详情
getContractDetail(symbol: string): Promise<any>;
// 获取K线数据
getKlineData(symbol: string, period: string, count: number): Promise<any[]>;
// 获取实时行情数据
getTickData(symbol: string): Promise<any>;
// 获取市场概览
getMarketOverview(): Promise<any[]>;
// 获取历史成交数据
getHistoricalTrades(symbol: string, start: number, end: number): Promise<any[]>;
// 初始化数据源
initialize(): Promise<boolean>;
// 关闭数据源连接
close(): Promise<void>;
}

@ -0,0 +1,68 @@
// 数据源工厂类
import { DataSource } from './DataSource';
import { TQDataSource } from './TQDataSource';
// 数据源类型
export enum DataSourceType {
TQSDK = 'tqsdk',
MOCK = 'mock',
WIND = 'wind',
SINA = 'sina',
TEST = 'test'
}
export class DataSourceFactory {
private static dataSources: Map<DataSourceType, DataSource> = new Map();
// 获取数据源实例
static async getDataSource(type: DataSourceType = DataSourceType.TQSDK, config: any = {}): Promise<DataSource> {
if (!this.dataSources.has(type)) {
let dataSource: DataSource;
switch (type) {
case DataSourceType.TQSDK:
dataSource = new TQDataSource(config.tqsdk || {});
break;
case DataSourceType.MOCK:
case DataSourceType.TEST:
// 导入模拟数据源
const { MockDataSource } = await import('./MockDataSource');
dataSource = new MockDataSource();
break;
default:
throw new Error(`不支持的数据源类型: ${type}`);
}
// 初始化数据源
const initialized = await dataSource.initialize();
if (!initialized) {
throw new Error(`数据源${type}初始化失败`);
}
this.dataSources.set(type, dataSource);
}
return this.dataSources.get(type)!;
}
// 关闭所有数据源
static async closeAllDataSources(): Promise<void> {
for (const [type, dataSource] of this.dataSources) {
try {
await dataSource.close();
console.log(`数据源${type}已关闭`);
} catch (error) {
console.error(`关闭数据源${type}失败:`, error);
}
}
this.dataSources.clear();
}
// 切换数据源
static async switchDataSource(type: DataSourceType): Promise<DataSource> {
// 关闭当前数据源
await this.closeAllDataSources();
// 获取新数据源
return this.getDataSource(type);
}
}

@ -0,0 +1,199 @@
// 模拟数据源实现
import { DataSource } from './DataSource';
import { futuresList, generateFuturesOverview, generateFutureData, generateKlineData, riskAlerts } from '../../utils/mockData';
export class MockDataSource implements DataSource {
private initialized: boolean = false;
async initialize(): Promise<boolean> {
// 模拟初始化
this.initialized = true;
console.log('模拟数据源初始化成功');
return true;
}
async getContractList(): Promise<any[]> {
if (!this.initialized) {
throw new Error('模拟数据源未初始化');
}
// 返回模拟合约列表
return futuresList.map(item => ({
symbol: item.code,
name: item.name,
exchange: this.getExchangeBySymbol(item.code),
product_class: 'futures',
price_tick: 0.01,
size: 1,
margin_rate: 0.05
}));
}
async getContractDetail(symbol: string): Promise<any> {
if (!this.initialized) {
throw new Error('模拟数据源未初始化');
}
// 返回模拟合约详情
const future = futuresList.find(item => item.code === symbol);
if (!future) {
throw new Error(`合约${symbol}不存在`);
}
return {
symbol: future.code,
name: future.name,
exchange: this.getExchangeBySymbol(future.code),
product_class: 'futures',
price_tick: 0.01,
size: 1,
margin_rate: 0.05,
delivery_month: '202412',
trading_hours: '09:00-11:30, 13:30-15:00, 21:00-02:30',
pre_close: 2000,
open: 2005,
high: 2010,
low: 1995,
last_price: 2003,
volume: 10000,
open_interest: 50000
};
}
async getKlineData(symbol: string, period: string, count: number): Promise<any[]> {
if (!this.initialized) {
throw new Error('模拟数据源未初始化');
}
// 返回模拟K线数据
const klineData = generateKlineData(count);
return klineData.map(item => ({
datetime: item.time * 1000000000, // 转换为纳秒
open: item.open,
high: item.high,
low: item.low,
close: item.close,
volume: item.volume,
open_interest: Math.floor(Math.random() * 100000) + 50000
}));
}
async getTickData(symbol: string): Promise<any> {
if (!this.initialized) {
throw new Error('模拟数据源未初始化');
}
// 返回模拟实时行情数据
const future = futuresList.find(item => item.code === symbol);
if (!future) {
throw new Error(`合约${symbol}不存在`);
}
const pre_close = 2000;
const last_price = pre_close + (Math.random() * 20 - 10);
const price_change = last_price - pre_close;
return {
datetime: Date.now() * 1000000, // 转换为纳秒
last_price: +last_price.toFixed(2),
pre_close,
open: pre_close + (Math.random() * 10 - 5),
high: Math.max(last_price, pre_close + (Math.random() * 15 - 5)),
low: Math.min(last_price, pre_close + (Math.random() * 15 - 10)),
volume: Math.floor(Math.random() * 10000) + 5000,
open_interest: Math.floor(Math.random() * 100000) + 50000,
price_change: +price_change.toFixed(2),
bid_price1: last_price - 0.01,
bid_volume1: Math.floor(Math.random() * 100) + 50,
ask_price1: last_price + 0.01,
ask_volume1: Math.floor(Math.random() * 100) + 50
};
}
async getMarketOverview(): Promise<any[]> {
if (!this.initialized) {
throw new Error('模拟数据源未初始化');
}
// 返回模拟市场概览
const overview = generateFuturesOverview();
return overview.map(item => ({
symbol: item.code,
name: item.name,
price: item.currentPrice,
change: item.changePercent,
change_percent: item.changePercent,
volume: Math.floor(Math.random() * 100000) + 50000,
open_interest: Math.floor(Math.random() * 1000000) + 500000
}));
}
async getHistoricalTrades(symbol: string, start: number, end: number): Promise<any[]> {
if (!this.initialized) {
throw new Error('模拟数据源未初始化');
}
// 返回模拟历史成交数据
const trades = [];
const count = 100; // 生成100条模拟数据
const basePrice = 2000;
for (let i = 0; i < count; i++) {
const price = basePrice + (Math.random() * 10 - 5);
const volume = Math.floor(Math.random() * 100) + 10;
const timestamp = start + Math.floor((end - start) * (i / count));
trades.push({
datetime: timestamp * 1000000, // 转换为纳秒
price: +price.toFixed(2),
volume,
direction: Math.random() > 0.5 ? 'BUY' : 'SELL'
});
}
return trades;
}
async close(): Promise<void> {
// 模拟关闭
this.initialized = false;
console.log('模拟数据源已关闭');
}
// 根据合约代码获取交易所
private getExchangeBySymbol(symbol: string): string {
// 简单的交易所映射
const exchangeMap: Record<string, string> = {
'AU': 'SHFE',
'AG': 'SHFE',
'CU': 'SHFE',
'NI': 'SHFE',
'SN': 'SHFE',
'AL': 'SHFE',
'ZN': 'SHFE',
'FG': 'CZCE',
'SJS': 'CZCE',
'SCA': 'CZCE',
'JM': 'DCE',
'RB': 'SHFE',
'ALO': 'SHFE',
'MA': 'DCE',
'PVC': 'DCE',
'FU': 'SHFE',
'SC': 'INE',
'L': 'DCE',
'NR': 'SHFE',
'BU': 'SHFE',
'LU': 'INE',
'P': 'DCE',
'LC': 'SHFE',
'SI': 'SHFE',
'PGS': 'SHFE',
'IC': 'CFFEX',
'IM': 'CFFEX',
'IH': 'CFFEX'
};
return exchangeMap[symbol] || 'SHFE';
}
}

@ -0,0 +1,207 @@
// TQSDK数据源实现
import { DataSource } from './DataSource';
import { TqSdk, TqAccount } from 'tqsdk';
export class TQDataSource implements DataSource {
private tq: TqSdk | null = null;
private initialized: boolean = false;
private config: {
username?: string;
password?: string;
timeout?: number;
retries?: number;
maxConnections?: number;
};
constructor(config: any = {}) {
this.config = {
username: config.username || '',
password: config.password || '',
timeout: config.timeout || 30000,
retries: config.retries || 3,
maxConnections: config.maxConnections || 5
};
}
async initialize(): Promise<boolean> {
try {
// 创建TQSDK实例使用配置的参数
const tqConfig: any = {};
// 如果有用户名和密码,使用用户名密码登录
if (this.config.username && this.config.password) {
tqConfig.account = this.config.username;
tqConfig.password = this.config.password;
}
this.tq = new TqSdk(tqConfig);
// 等待初始化完成
await new Promise((resolve) => {
this.tq?.on('connected', () => {
resolve(true);
});
});
this.initialized = true;
console.log('TQSDK数据源初始化成功');
return true;
} catch (error) {
console.error('TQSDK数据源初始化失败:', error);
this.initialized = false;
return false;
}
}
async getContractList(): Promise<any[]> {
if (!this.initialized || !this.tq) {
throw new Error('TQSDK数据源未初始化');
}
try {
// 获取所有合约
const contracts = await this.tq.get_contracts();
// 过滤期货合约,排除期权等其他类型
const futuresContracts = contracts.filter((contract: any) => {
return contract.exchange.includes('SHFE') ||
contract.exchange.includes('DCE') ||
contract.exchange.includes('CZCE') ||
contract.exchange.includes('INE');
});
return futuresContracts;
} catch (error) {
console.error('获取合约列表失败:', error);
throw error;
}
}
async getContractDetail(symbol: string): Promise<any> {
if (!this.initialized || !this.tq) {
throw new Error('TQSDK数据源未初始化');
}
try {
// 获取合约详情
const contract = await this.tq.get_contract(symbol);
return contract;
} catch (error) {
console.error(`获取合约${symbol}详情失败:`, error);
throw error;
}
}
async getKlineData(symbol: string, period: string, count: number): Promise<any[]> {
if (!this.initialized || !this.tq) {
throw new Error('TQSDK数据源未初始化');
}
try {
// 转换周期格式
const tqPeriod = this.convertPeriod(period);
// 获取K线数据
const kline = await this.tq.get_kline_serial(symbol, tqPeriod, count);
return kline;
} catch (error) {
console.error(`获取合约${symbol}K线数据失败:`, error);
throw error;
}
}
async getTickData(symbol: string): Promise<any> {
if (!this.initialized || !this.tq) {
throw new Error('TQSDK数据源未初始化');
}
try {
// 获取实时行情数据
const tick = await this.tq.get_tick_serial(symbol, 1);
return tick[0];
} catch (error) {
console.error(`获取合约${symbol}实时行情数据失败:`, error);
throw error;
}
}
async getMarketOverview(): Promise<any[]> {
if (!this.initialized || !this.tq) {
throw new Error('TQSDK数据源未初始化');
}
try {
// 获取所有合约
const contracts = await this.getContractList();
// 限制获取的合约数量,避免请求过多
const limitedContracts = contracts.slice(0, 20);
// 获取每个合约的实时行情
const overview = [];
for (const contract of limitedContracts) {
try {
const tick = await this.getTickData(contract.symbol);
overview.push({
symbol: contract.symbol,
name: contract.name,
price: tick.last_price,
change: tick.price_change,
change_percent: tick.price_change / tick.pre_close * 100,
volume: tick.volume,
open_interest: tick.open_interest
});
} catch (error) {
console.error(`获取合约${contract.symbol}行情失败:`, error);
}
}
return overview;
} catch (error) {
console.error('获取市场概览失败:', error);
throw error;
}
}
async getHistoricalTrades(symbol: string, start: number, end: number): Promise<any[]> {
if (!this.initialized || !this.tq) {
throw new Error('TQSDK数据源未初始化');
}
try {
// 获取历史成交数据
const trades = await this.tq.get_trade_serial(symbol, start, end);
return trades;
} catch (error) {
console.error(`获取合约${symbol}历史成交数据失败:`, error);
throw error;
}
}
async close(): Promise<void> {
if (this.tq) {
this.tq.close();
this.tq = null;
this.initialized = false;
console.log('TQSDK数据源已关闭');
}
}
// 转换周期格式
private convertPeriod(period: string): string {
switch (period) {
case '1M':
return '1min';
case '5M':
return '5min';
case '15M':
return '15min';
case '30M':
return '30min';
case '1H':
return '60min';
case '4H':
return '240min';
case '1D':
return '1d';
case '1W':
return '1w';
default:
return '1min';
}
}
}

@ -0,0 +1,345 @@
# 配置管理逻辑文档
本文档详细介绍了 Alpha Futures Pro 系统中配置管理的实现逻辑,包括配置的获取、保存、存储位置等内容。
## 配置管理架构
- **前端组件**`AdminConfig.jsx`(管理配置界面)
- **后端 API**`config.ts`(配置管理接口)
- **配置存储**`backend/config.json`JSON 格式文件)
## 前端配置管理逻辑
### 1. 获取配置
**文件位置**`src/pages/admin/AdminConfig.jsx`
**函数**`fetchConfig`第174-205行
**实现逻辑**
```javascript
const fetchConfig = async () => {
try {
// 调用后端 API 获取配置
const response = await fetch('http://localhost:3007/api/config/get');
const result = await response.json();
if (result.success) {
// 更新本地配置状态
const newConfig = result.data;
setConfig(newConfig);
// 更新表单字段值
form.setFieldsValue({
database: newConfig.database,
server: newConfig.server,
security: {
...newConfig.security,
cors: {
...newConfig.security.cors,
methods: newConfig.security.cors.methods.join(', '),
allowedHeaders: newConfig.security.cors.allowedHeaders.join(', ')
}
},
dataSource: newConfig.dataSource
});
messageApi.success('配置加载成功');
} else {
messageApi.error('配置加载失败');
}
} catch (error) {
console.error('获取配置失败:', error);
messageApi.error('获取配置失败,请检查网络连接');
}
};
```
**调用时机**
- 组件挂载时(通过 useEffect 钩子)
- 保存配置成功后(重新获取最新配置)
### 2. 保存配置
**文件位置**`src/pages/admin/AdminConfig.jsx`
**函数**`handleSubmit`第208-228行
**实现逻辑**
```javascript
const handleSubmit = async (values) => {
try {
// 调用后端 API 保存配置
const response = await fetch('http://localhost:3007/api/config/save', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(config)
});
const result = await response.json();
if (result.success) {
messageApi.success(result.message);
// 保存成功后重新获取配置
fetchConfig();
} else {
messageApi.error(result.message);
}
} catch (error) {
console.error('保存配置失败:', error);
messageApi.error('保存配置失败,请检查网络连接');
}
};
```
**调用时机**
- 用户点击 "保存配置" 按钮时
## 后端配置管理逻辑
### 1. 获取配置 API
**文件位置**`backend/src/api/config.ts`
**路由**`/api/config/get`第151-169行
**实现逻辑**
```typescript
// 获取配置
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: '获取配置失败' });
}
});
```
**功能**
- 从 `backend/config.json` 文件读取配置
- 如果文件不存在,返回空对象
- 将配置数据以 JSON 格式返回给前端
### 2. 保存配置 API
**文件位置**`backend/src/api/config.ts`
**路由**`/api/config/save`第131-148行
**实现逻辑**
```typescript
// 保存配置
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: '保存配置失败' });
}
});
```
**功能**
- 接收前端发送的配置数据
- 将配置数据写入 `backend/config.json` 文件
- 返回保存成功的提示信息
## 配置存储位置
**配置文件**`backend/config.json`
**文件格式**JSON 格式
**存储内容**
- 数据库配置MongoDB、PostgreSQL、Redis、InfluxDB
- 服务器配置(端口、主机、环境等)
- 安全配置JWT、速率限制、CORS 等)
- 数据源配置测试数据、TQSDK、Wind、新浪财经等
**文件示例**
```json
{
"database": {
"mongoDB": {
"host": "localhost",
"port": 27017,
"database": "alpha-futures",
"username": "",
"password": "",
"authSource": "admin",
"ssl": false,
"enabled": true
},
"postgreSQL": {
"host": "localhost",
"port": 5432,
"database": "alpha-futures",
"username": "postgres",
"password": "password",
"ssl": false,
"enabled": true
},
"redis": {
"host": "localhost",
"port": 6379,
"password": "",
"db": 0,
"enabled": true
},
"influxDB": {
"host": "localhost",
"port": 8086,
"database": "alpha-futures",
"username": "",
"password": "",
"ssl": false,
"enabled": true
}
},
"server": {
"port": 3007,
"host": "0.0.0.0",
"environment": "development",
"debug": true,
"timeout": 30000,
"maxBodySize": "10mb"
},
"security": {
"jwtSecret": "your-secret-key",
"jwtExpiresIn": "7d",
"rateLimit": {
"windowMs": 60000,
"max": 120
},
"cors": {
"origin": "*",
"methods": ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
"allowedHeaders": ["Content-Type", "Authorization"]
}
},
"dataSource": {
"test": {
"enabled": true,
"timeout": 10000,
"retries": 3,
"refreshInterval": 60000
},
"tqsdk": {
"enabled": true,
"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"
}
}
```
## 配置管理流程
### 1. 系统启动时
1. 后端服务启动,从 `backend/config.json` 文件读取配置
2. 如果配置文件不存在,使用默认配置
3. 前端应用启动,加载 `AdminConfig` 组件
4. `AdminConfig` 组件挂载时调用 `fetchConfig` 函数获取配置
### 2. 用户修改配置时
1. 用户在管理配置界面修改配置项
2. 前端实时更新本地配置状态
3. 用户点击 "保存配置" 按钮
4. 前端调用 `handleSubmit` 函数,将配置发送到后端
5. 后端接收配置数据,写入 `config.json` 文件
6. 后端返回保存成功的响应
7. 前端显示成功提示并重<E5B9B6><E9878D><EFBFBD>获取配置以更新本地状态
### 3. 配置生效
- **前端**:配置修改后立即在界面上生效
- **后端**:配置保存后,新的配置会在下次服务重启时生效
- **数据源配置**:修改后需要重启后端服务才能完全生效
## 注意事项
1. **配置文件权限**:确保后端服务有读写 `config.json` 文件的权限
2. **配置格式**:配置数据必须是有效的 JSON 格式
3. **敏感信息**配置文件中包含敏感信息如数据库密码、TQSDK 用户名密码),请妥善保管
4. **配置备份**:建议定期备份 `config.json` 文件,以防止配置丢失
5. **服务重启**:修改某些配置项(如服务器端口、数据库连接信息)后,需要重启后端服务才能生效
## 故障排查
### 1. 获取配置失败
**症状**:前端显示 "获取配置失败" 错误
**解决方案**
- 检查后端服务是否正常运行
- 检查 `config.json` 文件是否存在且格式正确
- 检查后端 API 是否可以正常访问
### 2. 保存配置失败
**症状**:前端显示 "保存配置失败" 错误
**解决方案**
- 检查后端服务是否正常运行
- 检查 `config.json` 文件是否有写入权限
- 检查配置数据是否格式正确
- 检查后端日志中的错误信息
### 3. 配置不生效
**症状**:修改配置后,系统行为没有改变
**解决方案**
- 检查配置是否成功保存到 `config.json` 文件
- 尝试重启后端服务
- 检查配置项是否正确设置
---
**文档版本**v1.0
**最后更新**2026-02-20

@ -177,23 +177,126 @@ const AdminConfig = () => {
const result = await response.json(); const result = await response.json();
if (result.success) { if (result.success) {
const newConfig = result.data; const newConfig = result.data;
setConfig(newConfig); console.log('当前配置:', newConfig);
//
const completeConfig = {
database: newConfig.database || {
mongoDB: { host: 'localhost', port: 27017, database: 'alpha-futures', username: '', password: '', authSource: 'admin', ssl: false, enabled: true },
postgreSQL: { host: 'localhost', port: 5432, database: 'alpha-futures', username: 'postgres', password: 'password', ssl: false, enabled: true },
redis: { host: 'localhost', port: 6379, password: '', db: 0, enabled: true },
influxDB: { host: 'localhost', port: 8086, database: 'alpha-futures', username: '', password: '', ssl: false, enabled: true }
},
server: newConfig.server || {
port: 3007, host: '0.0.0.0', environment: 'development', debug: true, timeout: 30000, maxBodySize: '10mb'
},
security: newConfig.security || {
jwtSecret: 'your-secret-key', jwtExpiresIn: '7d',
rateLimit: { windowMs: 60000, max: 120 },
cors: { origin: '*', methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization'] }
},
dataSource: newConfig.dataSource || {
test: { enabled: true, timeout: 10000, retries: 3, refreshInterval: 60000 },
tqsdk: { enabled: true, 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'
}
};
// security.cors
if (completeConfig.security.cors && Array.isArray(completeConfig.security.cors.methods)) {
completeConfig.security.cors.methods = completeConfig.security.cors.methods.join(', ');
}
if (completeConfig.security.cors && Array.isArray(completeConfig.security.cors.allowedHeaders)) {
completeConfig.security.cors.allowedHeaders = completeConfig.security.cors.allowedHeaders.join(', ');
}
console.log('完整配置:', completeConfig);
// //
form.setFieldsValue({ form.setFieldsValue({
database: newConfig.database, 'database.mongoDB.host': completeConfig.database.mongoDB.host,
server: newConfig.server, 'database.mongoDB.port': completeConfig.database.mongoDB.port,
security: { 'database.mongoDB.database': completeConfig.database.mongoDB.database,
...newConfig.security, 'database.mongoDB.username': completeConfig.database.mongoDB.username,
cors: { 'database.mongoDB.password': completeConfig.database.mongoDB.password,
...newConfig.security.cors, 'database.mongoDB.authSource': completeConfig.database.mongoDB.authSource,
methods: newConfig.security.cors.methods.join(', '), 'database.mongoDB.ssl': completeConfig.database.mongoDB.ssl,
allowedHeaders: newConfig.security.cors.allowedHeaders.join(', ') 'database.mongoDB.enabled': completeConfig.database.mongoDB.enabled,
}
}, 'database.postgreSQL.host': completeConfig.database.postgreSQL.host,
dataSource: newConfig.dataSource 'database.postgreSQL.port': completeConfig.database.postgreSQL.port,
'database.postgreSQL.database': completeConfig.database.postgreSQL.database,
'database.postgreSQL.username': completeConfig.database.postgreSQL.username,
'database.postgreSQL.password': completeConfig.database.postgreSQL.password,
'database.postgreSQL.ssl': completeConfig.database.postgreSQL.ssl,
'database.postgreSQL.enabled': completeConfig.database.postgreSQL.enabled,
'database.redis.host': completeConfig.database.redis.host,
'database.redis.port': completeConfig.database.redis.port,
'database.redis.password': completeConfig.database.redis.password,
'database.redis.db': completeConfig.database.redis.db,
'database.redis.enabled': completeConfig.database.redis.enabled,
'database.influxDB.host': completeConfig.database.influxDB.host,
'database.influxDB.port': completeConfig.database.influxDB.port,
'database.influxDB.database': completeConfig.database.influxDB.database,
'database.influxDB.username': completeConfig.database.influxDB.username,
'database.influxDB.password': completeConfig.database.influxDB.password,
'database.influxDB.ssl': completeConfig.database.influxDB.ssl,
'database.influxDB.enabled': completeConfig.database.influxDB.enabled,
'server.port': completeConfig.server.port,
'server.host': completeConfig.server.host,
'server.environment': completeConfig.server.environment,
'server.debug': completeConfig.server.debug,
'server.timeout': completeConfig.server.timeout,
'server.maxBodySize': completeConfig.server.maxBodySize,
'security.jwtSecret': completeConfig.security.jwtSecret,
'security.jwtExpiresIn': completeConfig.security.jwtExpiresIn,
'security.rateLimit.windowMs': completeConfig.security.rateLimit.windowMs,
'security.rateLimit.max': completeConfig.security.rateLimit.max,
'security.cors.origin': completeConfig.security.cors.origin,
'security.cors.methods': completeConfig.security.cors.methods,
'security.cors.allowedHeaders': completeConfig.security.cors.allowedHeaders,
'dataSource.defaultDataSource': completeConfig.dataSource.defaultDataSource,
'dataSource.test.enabled': completeConfig.dataSource.test.enabled,
'dataSource.test.timeout': completeConfig.dataSource.test.timeout,
'dataSource.test.retries': completeConfig.dataSource.test.retries,
'dataSource.test.refreshInterval': completeConfig.dataSource.test.refreshInterval,
'dataSource.tqsdk.enabled': completeConfig.dataSource.tqsdk.enabled,
'dataSource.tqsdk.username': completeConfig.dataSource.tqsdk.username,
'dataSource.tqsdk.password': completeConfig.dataSource.tqsdk.password,
'dataSource.tqsdk.timeout': completeConfig.dataSource.tqsdk.timeout,
'dataSource.tqsdk.retries': completeConfig.dataSource.tqsdk.retries,
'dataSource.tqsdk.maxConnections': completeConfig.dataSource.tqsdk.maxConnections,
'dataSource.wind.enabled': completeConfig.dataSource.wind.enabled,
'dataSource.wind.apiKey': completeConfig.dataSource.wind.apiKey,
'dataSource.wind.apiSecret': completeConfig.dataSource.wind.apiSecret,
'dataSource.wind.url': completeConfig.dataSource.wind.url,
'dataSource.wind.timeout': completeConfig.dataSource.wind.timeout,
'dataSource.wind.retries': completeConfig.dataSource.wind.retries,
'dataSource.sina.enabled': completeConfig.dataSource.sina.enabled,
'dataSource.sina.url': completeConfig.dataSource.sina.url,
'dataSource.sina.timeout': completeConfig.dataSource.sina.timeout,
'dataSource.sina.retries': completeConfig.dataSource.sina.retries,
'dataSource.sina.refreshInterval': completeConfig.dataSource.sina.refreshInterval
}); });
//
setTimeout(() => {
console.log('表单字段值:', form.getFieldsValue());
}, 200);
// config
setTimeout(() => {
setConfig(completeConfig);
console.log('表单字段值已更新');
}, 100);
messageApi.success('配置加载成功'); messageApi.success('配置加载成功');
} else { } else {
messageApi.error('配置加载失败'); messageApi.error('配置加载失败');
@ -296,98 +399,6 @@ const AdminConfig = () => {
form={form} form={form}
layout="vertical" layout="vertical"
onFinish={handleSubmit} 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,
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,
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 <Tabs
defaultActiveKey="database" defaultActiveKey="database"

@ -0,0 +1,141 @@
# Alpha Futures Pro 系统启动指南
本文档详细介绍了 Alpha Futures Pro 系统的启动方法、流程顺序和注意事项,帮助您快速部署和使用系统。
## 系统架构
- **前端**React 19.x + Ant Design 6.x + Vite
- **后端**Node.js 18.x + Express 4.x + TypeScript 5.x
- **数据源**TQSDK、测试数据
- **API 接口**:后端运行在 `http://localhost:3007`
- **前端应用**:前端运行在 `http://localhost:5173`(或类似端口)
## 启动顺序
**必须按照以下顺序启动系统组件:**
1. **后端服务**(优先启动)
2. **前端服务**(后端启动后再启动)
## 详细启动流程
### 1. 启动后端服务
**步骤 1进入后端目录**
```powershell
cd d:\workspace\AlphaFuturesPro\backend
```
**步骤 2安装依赖**(首次启动时需要)
```powershell
npm install
```
**步骤 3启动后端服务**
```powershell
npm run dev
```
**验证后端服务启动成功**
- 终端显示 "服务器运行在 http://localhost:3007"
- 可以通过浏览器访问 `http://localhost:3007/api/config/get` 测试 API 是否正常响应
- 正常响应会返回系统配置的 JSON 数据
### 2. 启动前端服务
**步骤 1进入前端目录**
```powershell
cd d:\workspace\AlphaFuturesPro
```
**步骤 2安装依赖**(首次启动时需要)
```powershell
npm install
```
**步骤 3启动前端服务**
```powershell
npm run dev
```
**验证前端服务启动成功**
- 终端显示 Vite 服务器启动信息,通常运行在 `http://localhost:5173` 或类似端口
- 可以通过浏览器访问显示的 URL 进入系统
## 系统访问
- **主页**`http://localhost:5173/`(市场概览)
- **管理配置**`http://localhost:5173/admin`(系统配置界面)
- **自选合约**`http://localhost:5173/watchlist`(自选合约列表)
- **详细分析**`http://localhost:5173/detail/:code`(单个合约详细分析)
- **风险控制**`http://localhost:5173/risk-control`(风险控制界面)
## 注意事项
1. **端口占用**
- 后端默认使用端口 3007如果被占用需要停止占用该端口的进程后重新启动
- 前端默认使用端口 5173如果被占用会自动尝试其他端口
2. **依赖安装**
- 首次启动或代码更新后,可能需要重新安装依赖
- 后端和前端的依赖需要分别安装
3. **配置文件**
- 系统配置会保存在后端的配置文件中,修改配置后会自动生效
- 数据源配置(如 TQSDK 的用户名和密码)需要在管理配置界面中设置
4. **数据源连接**
- TQSDK 需要正确配置用户名和密码才能获取数据
- 如果 TQSDK 连接失败,系统会自动使用测试数据(如果启用了测试数据源)
5. **网络连接**
- 系统需要网络连接来获取外部数据(如 TQSDK 数据)
- 确保防火墙没有阻止系统的网络请求
## 常见问题排查
### 1. 后端服务启动失败
**症状**:终端显示错误信息,如 "Error: listen EADDRINUSE: address already in use"
**解决方案**
- 检查端口 3007 是否被占用
- 停止占用该端口的进程
- 重新启动后端服务
### 2. 前端访问不通
**症状**:浏览器显示 "无法访问此网站" 或类似错误
**解决方案**
- 检查后端服务是否正常运行
- 检查前端服务是否启动成功
- 确认访问的 URL 是否正确
### 3. 数据获取失败
**症状**:前端显示 "获取配置失败" 或 "数据加载失败" 等错误
**解决方案**
- 检查后端服务是否正常运行
- 检查数据源配置是否正确
- 检查网络连接是否正常
- 尝试重启后端服务
### 4. 管理配置界面无法保存配置
**症状**:点击 "保存配置" 按钮后显示错误信息
**解决方案**
- 检查后端服务是否正常运行
- 检查配置数据是否格式正确
- 尝试刷新页面后重新保存
## 技术支持
如果您在启动或使用系统过程中遇到问题,请检查上述文档内容。如果问题仍然存在,请联系技术支持团队获取帮助。
---
**文档版本**v1.0
**最后更新**2026-02-20
Loading…
Cancel
Save