123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- from flask import Flask, jsonify, request, make_response
- import os
- from flask_cors import CORS
- from flask_sqlalchemy import SQLAlchemy
- from flask_migrate import Migrate
- import logging
- import pandas as pd
- from datetime import datetime
- import json
- from .utils import get_current_data, get_dataset_by_id, get_table_data, get_table_metal_averages
- # 先创建SQLAlchemy实例,确保其他模块可以导入
- db = SQLAlchemy()
- # 导入配置和工具函数(在db定义之后)
- from . import config
- def create_app():
- app = Flask(__name__)
-
- # 配置应用
- app.config['SECRET_KEY'] = 'abcdef1234567890'
- app.config.from_object(config.Config)
- app.logger.setLevel(logging.DEBUG)
-
- # 初始化CORS(允许所有源,所有方法,带凭证)
- CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)
-
- # 初始化数据库
- db.init_app(app)
-
- # 初始化迁移工具
- migrate = Migrate(app, db)
-
- # 注册蓝图
- from . import routes, frontend
- app.register_blueprint(routes.bp)
- app.register_blueprint(frontend.bp,url_prefix="/admin")
- # 注册自定义接口
- register_api_routes(app)
- # -------- 新增:统一处理 OPTIONS 预检请求 --------
- @app.before_request
- def handle_options():
- if request.method == "OPTIONS":
- response = make_response()
- response.headers.add("Access-Control-Allow-Origin", "*")
- response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization")
- response.headers.add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
- return response
- # --------------------------------------------
- return app
- def safe_data_conversion(df):
- """安全转换DataFrame为可序列化的字典列表,处理特殊数据类型"""
- # 处理日期时间类型
- for col in df.columns:
- if pd.api.types.is_datetime64_any_dtype(df[col]):
- df[col] = df[col].dt.strftime('%Y-%m-%d %H:%M:%S')
-
- # 处理空值
- df = df.fillna('')
-
- # 转换为字典列表
- data_list = df.to_dict(orient='records')
-
- # 处理可能的嵌套结构
- def serialize_item(item):
- if isinstance(item, dict):
- return {k: serialize_item(v) for k, v in item.items()}
- elif isinstance(item, list):
- return [serialize_item(v) for v in item]
- elif isinstance(item, datetime):
- return item.strftime('%Y-%m-%d %H:%M:%S')
- else:
- return item
-
- return [serialize_item(item) for item in data_list]
- def register_api_routes(app):
- # 新增:通用表查询接口
- @app.route('/api/table-data', methods=['GET'])
- def api_table_data():
- table_name = request.args.get('table_name')
- if not table_name:
- return jsonify({"error": "请传入 table_name 参数(如 ?table_name=dataset_35)"}), 400
- try:
- df = get_table_data(db.session, table_name)
- data = safe_data_conversion(df)
- return jsonify({"success": True, "data": data})
- except ValueError as e:
- return jsonify({"success": False, "error": str(e)}), 400
- except Exception as e:
- app.logger.error(f"查询表 {table_name} 失败: {str(e)}", exc_info=True)
- return jsonify({"success": False, "error": str(e)}), 500
-
- # 新增接口:查询金属平均值(/api/table-averages)
- @app.route('/api/table-averages', methods=['GET'])
- def api_table_averages():
- table_name = request.args.get('table_name')
- if not table_name:
- return jsonify({"error": "缺少参数:table_name"}), 400
- try:
- averages = get_table_metal_averages(db.session, table_name)
- return jsonify({"success": True, "averages": averages})
- except ValueError as e:
- return jsonify({"success": False, "error": str(e)}), 400
- except Exception as e:
- app.logger.error(f"计算 {table_name} 平均值失败: {str(e)}", exc_info=True)
- return jsonify({"success": False, "error": str(e)}), 500
|