#!/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/') 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/') 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('/') 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/ - K线数据") print(" GET /api/financial/ - 财务数据") 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()