stock/frontend/server.py

437 lines
12 KiB
Python
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.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
股票数据展示系统后端服务器
提供RESTful API接口支持前端数据展示
"""
import os
import sys
import json
from datetime import datetime, timedelta
from flask import Flask, jsonify, request
from flask_cors import CORS
# 添加项目根目录到Python路径
project_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
sys.path.insert(0, project_root)
from src.storage.stock_repository import StockRepository
from src.storage.database import db_manager
# 移除Config导入直接使用默认配置
class StockDataServer:
"""股票数据服务器类"""
def __init__(self):
"""初始化服务器"""
self.app = Flask(__name__)
self.repository = None
self.setup_cors()
self.setup_routes()
self.connect_database()
def setup_cors(self):
"""设置CORS支持"""
CORS(self.app)
def setup_routes(self):
"""设置API路由"""
@self.app.route('/')
def index():
"""首页重定向到前端页面"""
return self.app.send_static_file('index.html')
@self.app.route('/api/system/overview')
def system_overview():
"""获取系统概览数据"""
try:
if not self.repository:
return jsonify({
'success': True,
'stock_count': 12595,
'kline_count': 440,
'financial_count': 50,
'log_count': 4
})
# 获取真实数据统计
stock_count = self.repository.get_stock_count()
kline_count = self.repository.get_kline_count()
financial_count = self.repository.get_financial_count()
log_count = self.repository.get_log_count()
return jsonify({
'success': True,
'stock_count': stock_count,
'kline_count': kline_count,
'financial_count': financial_count,
'log_count': log_count
})
except Exception as e:
return jsonify({
'success': False,
'message': f'获取系统概览失败: {str(e)}'
}), 500
@self.app.route('/api/stocks')
def get_stocks():
"""获取股票列表"""
try:
page = int(request.args.get('page', 1))
limit = int(request.args.get('limit', 20))
offset = (page - 1) * limit
if not self.repository:
# 返回模拟数据
mock_stocks = self.get_mock_stocks()
return jsonify({
'success': True,
'data': mock_stocks[offset:offset + limit],
'total': len(mock_stocks)
})
# 获取真实股票数据
stocks = self.repository.get_stocks(limit=limit, offset=offset)
total = self.repository.get_stock_count()
# 格式化数据
formatted_stocks = []
for stock in stocks:
formatted_stocks.append({
'code': stock.code,
'name': stock.name,
'exchange': stock.exchange,
'listing_date': stock.listing_date.isoformat() if stock.listing_date else None,
'industry': stock.industry
})
return jsonify({
'success': True,
'data': formatted_stocks,
'total': total
})
except Exception as e:
return jsonify({
'success': False,
'message': f'获取股票列表失败: {str(e)}'
}), 500
@self.app.route('/api/stocks/search')
def search_stocks():
"""搜索股票"""
try:
query = request.args.get('q', '').strip()
if not query:
return jsonify({
'success': False,
'message': '搜索关键词不能为空'
}), 400
if not self.repository:
# 返回模拟数据
mock_stocks = self.get_mock_stocks()
filtered_stocks = [
stock for stock in mock_stocks
if query.lower() in stock['code'].lower() or query.lower() in stock['name'].lower()
]
return jsonify({
'success': True,
'data': filtered_stocks
})
# 搜索真实数据
stocks = self.repository.search_stocks(query)
formatted_stocks = []
for stock in stocks:
formatted_stocks.append({
'code': stock.code,
'name': stock.name,
'exchange': stock.exchange,
'listing_date': stock.listing_date.isoformat() if stock.listing_date else None,
'industry': stock.industry
})
return jsonify({
'success': True,
'data': formatted_stocks
})
except Exception as e:
return jsonify({
'success': False,
'message': f'搜索股票失败: {str(e)}'
}), 500
@self.app.route('/api/kline/<stock_code>')
def get_kline_data(stock_code):
"""获取K线数据"""
try:
period = request.args.get('period', 'daily')
days = 30 # 默认显示30天数据
if not self.repository:
# 返回模拟K线数据
mock_kline = self.get_mock_kline_data(stock_code, days)
return jsonify({
'success': True,
'data': mock_kline
})
# 获取真实K线数据
end_date = datetime.now()
start_date = end_date - timedelta(days=days)
kline_data = self.repository.get_kline_data(
stock_code=stock_code,
start_date=start_date,
end_date=end_date,
period=period
)
formatted_data = []
for kline in kline_data:
formatted_data.append({
'date': kline.trade_date.isoformat(),
'open': float(kline.open_price),
'high': float(kline.high_price),
'low': float(kline.low_price),
'close': float(kline.close_price),
'volume': int(kline.volume)
})
return jsonify({
'success': True,
'data': formatted_data
})
except Exception as e:
return jsonify({
'success': False,
'message': f'获取K线数据失败: {str(e)}'
}), 500
@self.app.route('/api/financial/<stock_code>')
def get_financial_data(stock_code):
"""获取财务数据"""
try:
year = request.args.get('year', '2023')
period = request.args.get('period', 'Q4')
if not self.repository:
# 返回模拟财务数据
mock_financial = self.get_mock_financial_data()
return jsonify({
'success': True,
'data': mock_financial
})
# 获取真实财务数据
financial_data = self.repository.get_financial_data(
stock_code=stock_code,
year=year,
period=period
)
if not financial_data:
return jsonify({
'success': True,
'data': {}
})
formatted_data = {
'revenue': float(financial_data.revenue) if financial_data.revenue else 0,
'net_profit': float(financial_data.net_profit) if financial_data.net_profit else 0,
'total_assets': float(financial_data.total_assets) if financial_data.total_assets else 0,
'total_liabilities': float(financial_data.total_liabilities) if financial_data.total_liabilities else 0,
'eps': float(financial_data.eps) if financial_data.eps else 0,
'roe': float(financial_data.roe) if financial_data.roe else 0
}
return jsonify({
'success': True,
'data': formatted_data
})
except Exception as e:
return jsonify({
'success': False,
'message': f'获取财务数据失败: {str(e)}'
}), 500
@self.app.route('/api/system/logs')
def get_system_logs():
"""获取系统日志"""
try:
level = request.args.get('level', '')
date_str = request.args.get('date', '')
if not self.repository:
# 返回模拟日志数据
mock_logs = self.get_mock_system_logs()
return jsonify({
'success': True,
'data': mock_logs
})
# 获取真实系统日志
logs = self.repository.get_system_logs(level=level, date_str=date_str)
formatted_logs = []
for log in logs:
formatted_logs.append({
'id': log.id,
'timestamp': log.timestamp.isoformat(),
'level': log.level,
'module_name': log.module_name,
'event_type': log.event_type,
'message': log.message,
'exception_type': log.exception_type
})
return jsonify({
'success': True,
'data': formatted_logs
})
except Exception as e:
return jsonify({
'success': False,
'message': f'获取系统日志失败: {str(e)}'
}), 500
# 静态文件服务
@self.app.route('/<path:path>')
def serve_static(path):
"""服务静态文件"""
try:
return self.app.send_static_file(path)
except:
return jsonify({
'success': False,
'message': '文件未找到'
}), 404
def connect_database(self):
"""连接数据库"""
try:
session = db_manager.get_session()
self.repository = StockRepository(session)
print("数据库连接成功")
except Exception as e:
print(f"数据库连接失败: {e}")
self.repository = None
def get_mock_stocks(self):
"""获取模拟股票数据"""
return [
{'code': '000001', 'name': '平安银行', 'exchange': 'SZ', 'listing_date': '1991-04-03', 'industry': '银行'},
{'code': '000002', 'name': '万科A', 'exchange': 'SZ', 'listing_date': '1991-01-29', 'industry': '房地产'},
{'code': '600000', 'name': '浦发银行', 'exchange': 'SH', 'listing_date': '1999-11-10', 'industry': '银行'},
{'code': '600036', 'name': '招商银行', 'exchange': 'SH', 'listing_date': '2002-04-09', 'industry': '银行'},
{'code': '601318', 'name': '中国平安', 'exchange': 'SH', 'listing_date': '2007-03-01', 'industry': '保险'}
]
def get_mock_kline_data(self, stock_code, days):
"""获取模拟K线数据"""
kline_data = []
base_price = 10 + hash(stock_code) % 20 # 基于股票代码生成基础价格
for i in range(days, 0, -1):
date = datetime.now() - timedelta(days=i)
price_variation = (hash(f"{stock_code}{i}") % 100 - 50) / 100 # 价格波动
open_price = base_price + price_variation
close_price = open_price + (hash(f"close{stock_code}{i}") % 100 - 50) / 200
high_price = max(open_price, close_price) + abs(hash(f"high{stock_code}{i}") % 100) / 200
low_price = min(open_price, close_price) - abs(hash(f"low{stock_code}{i}") % 100) / 200
volume = abs(hash(f"volume{stock_code}{i}") % 1000000)
kline_data.append({
'date': date.strftime('%Y-%m-%d'),
'open': round(open_price, 2),
'high': round(high_price, 2),
'low': round(low_price, 2),
'close': round(close_price, 2),
'volume': volume
})
return kline_data
def get_mock_financial_data(self):
"""获取模拟财务数据"""
return {
'revenue': 500000,
'net_profit': 80000,
'total_assets': 2000000,
'total_liabilities': 1200000,
'eps': 1.5,
'roe': 15.2
}
def get_mock_system_logs(self):
"""获取模拟系统日志"""
return [
{
'id': 1,
'timestamp': datetime.now().isoformat(),
'level': 'INFO',
'module_name': 'System',
'event_type': 'STARTUP',
'message': '系统启动成功',
'exception_type': None
},
{
'id': 2,
'timestamp': (datetime.now() - timedelta(hours=1)).isoformat(),
'level': 'INFO',
'module_name': 'DataCollector',
'event_type': 'DATA_COLLECTION',
'message': '开始采集股票数据',
'exception_type': None
},
{
'id': 3,
'timestamp': (datetime.now() - timedelta(minutes=30)).isoformat(),
'level': 'ERROR',
'module_name': 'Database',
'event_type': 'CONNECTION_ERROR',
'message': '数据库连接失败',
'exception_type': 'ConnectionError'
},
{
'id': 4,
'timestamp': (datetime.now() - timedelta(minutes=15)).isoformat(),
'level': 'WARNING',
'module_name': 'DataProcessor',
'event_type': 'DATA_FORMAT',
'message': '数据格式异常,已自动修复',
'exception_type': 'FormatError'
}
]
def run(self, host='127.0.0.1', port=5000, debug=True):
"""运行服务器"""
print(f"启动股票数据服务器: http://{host}:{port}")
print("API端点:")
print(" GET /api/system/overview - 系统概览")
print(" GET /api/stocks - 股票列表")
print(" GET /api/stocks/search - 搜索股票")
print(" GET /api/kline/<code> - K线数据")
print(" GET /api/financial/<code> - 财务数据")
print(" GET /api/system/logs - 系统日志")
self.app.static_folder = os.path.dirname(os.path.abspath(__file__))
self.app.run(host=host, port=port, debug=debug)
def main():
"""主函数"""
server = StockDataServer()
server.run()
if __name__ == '__main__':
main()