__init__.py 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. from flask import Flask, jsonify, request, make_response
  2. import os
  3. from flask_cors import CORS
  4. from flask_sqlalchemy import SQLAlchemy
  5. from flask_migrate import Migrate
  6. import logging
  7. import pandas as pd
  8. from datetime import datetime
  9. import json
  10. from .utils import get_current_data, get_dataset_by_id, get_table_data, get_table_metal_averages
  11. # 先创建SQLAlchemy实例,确保其他模块可以导入
  12. db = SQLAlchemy()
  13. # 导入配置和工具函数(在db定义之后)
  14. from . import config
  15. def create_app():
  16. app = Flask(__name__)
  17. # 配置应用
  18. app.config['SECRET_KEY'] = 'abcdef1234567890'
  19. app.config.from_object(config.Config)
  20. app.logger.setLevel(logging.DEBUG)
  21. # 初始化CORS(允许所有源,所有方法,带凭证)
  22. CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)
  23. # 初始化数据库
  24. db.init_app(app)
  25. # 初始化迁移工具
  26. migrate = Migrate(app, db)
  27. # 注册蓝图
  28. from . import routes, frontend
  29. app.register_blueprint(routes.bp)
  30. app.register_blueprint(frontend.bp,url_prefix="/admin")
  31. # 注册自定义接口
  32. register_api_routes(app)
  33. # -------- 新增:统一处理 OPTIONS 预检请求 --------
  34. @app.before_request
  35. def handle_options():
  36. if request.method == "OPTIONS":
  37. response = make_response()
  38. response.headers.add("Access-Control-Allow-Origin", "*")
  39. response.headers.add("Access-Control-Allow-Headers", "Content-Type,Authorization")
  40. response.headers.add("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
  41. return response
  42. # --------------------------------------------
  43. return app
  44. def safe_data_conversion(df):
  45. """安全转换DataFrame为可序列化的字典列表,处理特殊数据类型"""
  46. # 处理日期时间类型
  47. for col in df.columns:
  48. if pd.api.types.is_datetime64_any_dtype(df[col]):
  49. df[col] = df[col].dt.strftime('%Y-%m-%d %H:%M:%S')
  50. # 处理空值
  51. df = df.fillna('')
  52. # 转换为字典列表
  53. data_list = df.to_dict(orient='records')
  54. # 处理可能的嵌套结构
  55. def serialize_item(item):
  56. if isinstance(item, dict):
  57. return {k: serialize_item(v) for k, v in item.items()}
  58. elif isinstance(item, list):
  59. return [serialize_item(v) for v in item]
  60. elif isinstance(item, datetime):
  61. return item.strftime('%Y-%m-%d %H:%M:%S')
  62. else:
  63. return item
  64. return [serialize_item(item) for item in data_list]
  65. def register_api_routes(app):
  66. # 新增:通用表查询接口
  67. @app.route('/api/table-data', methods=['GET'])
  68. def api_table_data():
  69. table_name = request.args.get('table_name')
  70. if not table_name:
  71. return jsonify({"error": "请传入 table_name 参数(如 ?table_name=dataset_35)"}), 400
  72. try:
  73. df = get_table_data(db.session, table_name)
  74. data = safe_data_conversion(df)
  75. return jsonify({"success": True, "data": data})
  76. except ValueError as e:
  77. return jsonify({"success": False, "error": str(e)}), 400
  78. except Exception as e:
  79. app.logger.error(f"查询表 {table_name} 失败: {str(e)}", exc_info=True)
  80. return jsonify({"success": False, "error": str(e)}), 500
  81. # 新增接口:查询金属平均值(/api/table-averages)
  82. @app.route('/api/table-averages', methods=['GET'])
  83. def api_table_averages():
  84. table_name = request.args.get('table_name')
  85. if not table_name:
  86. return jsonify({"error": "缺少参数:table_name"}), 400
  87. try:
  88. averages = get_table_metal_averages(db.session, table_name)
  89. return jsonify({"success": True, "averages": averages})
  90. except ValueError as e:
  91. return jsonify({"success": False, "error": str(e)}), 400
  92. except Exception as e:
  93. app.logger.error(f"计算 {table_name} 平均值失败: {str(e)}", exc_info=True)
  94. return jsonify({"success": False, "error": str(e)}), 500