|
@@ -1,33 +1,118 @@
|
|
|
+from flask import Flask, jsonify, request, make_response
|
|
|
import os
|
|
|
|
|
|
-from flask import Flask
|
|
|
from flask_cors import CORS
|
|
|
-from . import config
|
|
|
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 全局实例
|
|
|
+# 先创建SQLAlchemy实例,确保其他模块可以导入
|
|
|
db = SQLAlchemy()
|
|
|
|
|
|
-# 创建并配置 Flask 应用
|
|
|
+# 导入配置和工具函数(在db定义之后)
|
|
|
+from . import config
|
|
|
+
|
|
|
+
|
|
|
def create_app():
|
|
|
app = Flask(__name__)
|
|
|
- CORS(app)
|
|
|
- # 进行初始配置,加载配置文件等
|
|
|
+
|
|
|
+ # 配置应用
|
|
|
+ app.config['SECRET_KEY'] = 'abcdef1234567890'
|
|
|
app.config.from_object(config.Config)
|
|
|
app.logger.setLevel(logging.DEBUG)
|
|
|
- # 初始化 SQLAlchemy
|
|
|
+
|
|
|
+ # 初始化CORS(允许所有源,所有方法,带凭证)
|
|
|
+ CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)
|
|
|
+
|
|
|
+ # 初始化数据库
|
|
|
db.init_app(app)
|
|
|
-
|
|
|
- # 初始化 Flask-Migrate
|
|
|
+
|
|
|
+ # 初始化迁移工具
|
|
|
migrate = Migrate(app, db)
|
|
|
-
|
|
|
- # 导入路由
|
|
|
- from . import routes
|
|
|
- from . import frontend
|
|
|
- app.register_blueprint(routes.bp)
|
|
|
- app.register_blueprint(frontend.bp)
|
|
|
- app.config['SECRET_KEY'] = os.getenv('SECRET_KEY', 'dev-secret-key-here')
|
|
|
|
|
|
+ # 注册蓝图
|
|
|
+ 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
|