__init__.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. from flask import Flask, jsonify
  2. from flask_sqlalchemy import SQLAlchemy
  3. from flask_cors import CORS
  4. import logging
  5. from app.config.config import config, current_env
  6. from app.config.cors import CORS_OPTIONS
  7. db = SQLAlchemy()
  8. def create_app():
  9. """Create and configure the Flask application"""
  10. app = Flask(__name__)
  11. # 加载配置
  12. app.config.from_object(config[current_env])
  13. # 初始化扩展
  14. # 配置CORS以解决跨域问题
  15. CORS(app, **CORS_OPTIONS)
  16. db.init_app(app)
  17. # 注册蓝图
  18. from app.api.meta_data import bp as meta_bp
  19. from app.api.data_interface import bp as data_interface_bp
  20. from app.api.graph import bp as graph_bp
  21. from app.api.system import bp as system_bp
  22. from app.api.data_source import bp as data_source_bp
  23. from app.api.data_flow import bp as data_flow_bp
  24. from app.api.business_domain import bp as business_domain_bp
  25. from app.api.data_factory import bp as data_factory_bp
  26. app.register_blueprint(meta_bp, url_prefix='/api/meta')
  27. app.register_blueprint(data_interface_bp, url_prefix='/api/interface')
  28. app.register_blueprint(graph_bp, url_prefix='/api/graph')
  29. app.register_blueprint(system_bp, url_prefix='/api/system')
  30. app.register_blueprint(data_source_bp, url_prefix='/api/datasource')
  31. app.register_blueprint(data_flow_bp, url_prefix='/api/dataflow')
  32. app.register_blueprint(business_domain_bp, url_prefix='/api/bd')
  33. app.register_blueprint(data_factory_bp, url_prefix='/api/datafactory')
  34. # Configure global response headers
  35. configure_response_headers(app)
  36. # Configure logging
  37. configure_logging(app)
  38. # 添加全局异常处理器
  39. configure_error_handlers(app)
  40. # 输出启动信息
  41. port = app.config['PORT']
  42. app.logger.info(f"Starting server in {current_env} mode on port {port}")
  43. return app
  44. def configure_response_headers(app):
  45. """Configure global response headers for JSON content"""
  46. @app.after_request
  47. def after_request(response):
  48. from flask import request
  49. # 检查是否是API路径
  50. if request.path.startswith('/api/'):
  51. # 排除文件下载和特殊响应类型
  52. excluded_types = [
  53. 'application/octet-stream',
  54. 'application/pdf',
  55. 'image/',
  56. 'text/csv',
  57. 'application/vnd.ms-excel',
  58. 'application/vnd.openxmlformats-officedocument'
  59. ]
  60. if (response.content_type and
  61. any(ct in response.content_type for ct in excluded_types)):
  62. # 保持原有的文件类型不变
  63. pass
  64. elif (response.content_type and
  65. 'application/json' in response.content_type):
  66. # 确保JSON响应设置正确的Content-Type和charset
  67. ct = 'application/json; charset=utf-8'
  68. response.headers['Content-Type'] = ct
  69. elif (not response.content_type or
  70. response.content_type == 'text/html; charset=utf-8' or
  71. response.content_type == 'text/plain'):
  72. # 对于API路由,默认设置为JSON
  73. ct = 'application/json; charset=utf-8'
  74. response.headers['Content-Type'] = ct
  75. # 确保CORS头部不被覆盖
  76. if 'Access-Control-Allow-Origin' not in response.headers:
  77. # 动态设置Origin,支持任意前端地址
  78. origin = request.headers.get('Origin')
  79. if origin:
  80. # 允许任意Origin(最灵活的配置)
  81. response.headers['Access-Control-Allow-Origin'] = origin
  82. else:
  83. # 如果没有Origin头部,设置为通配符
  84. response.headers['Access-Control-Allow-Origin'] = '*'
  85. # 专门处理预检请求(OPTIONS方法)
  86. if request.method == 'OPTIONS':
  87. origin = request.headers.get('Origin', '*')
  88. response.headers['Access-Control-Allow-Origin'] = origin
  89. methods = 'GET, POST, PUT, DELETE, OPTIONS'
  90. response.headers['Access-Control-Allow-Methods'] = methods
  91. headers = (
  92. 'Content-Type, Authorization, X-Requested-With, '
  93. 'Accept, Origin, Cache-Control, X-File-Name'
  94. )
  95. response.headers['Access-Control-Allow-Headers'] = headers
  96. response.headers['Access-Control-Max-Age'] = '86400'
  97. return response
  98. # 根据配置设置凭据支持
  99. from app.config.cors import ALLOW_ALL_ORIGINS
  100. if 'Access-Control-Allow-Credentials' not in response.headers:
  101. if ALLOW_ALL_ORIGINS:
  102. # 通配符时不支持凭据
  103. response.headers['Access-Control-Allow-Credentials'] = (
  104. 'false'
  105. )
  106. else:
  107. response.headers['Access-Control-Allow-Credentials'] = (
  108. 'true'
  109. )
  110. if 'Access-Control-Allow-Methods' not in response.headers:
  111. methods = 'GET, POST, PUT, DELETE, OPTIONS'
  112. response.headers['Access-Control-Allow-Methods'] = methods
  113. if 'Access-Control-Allow-Headers' not in response.headers:
  114. headers = (
  115. 'Content-Type, Authorization, X-Requested-With, '
  116. 'Accept, Origin'
  117. )
  118. response.headers['Access-Control-Allow-Headers'] = headers
  119. # 添加安全头部
  120. if 'X-Content-Type-Options' not in response.headers:
  121. response.headers['X-Content-Type-Options'] = 'nosniff'
  122. if 'X-Frame-Options' not in response.headers:
  123. response.headers['X-Frame-Options'] = 'DENY'
  124. if 'X-XSS-Protection' not in response.headers:
  125. response.headers['X-XSS-Protection'] = '1; mode=block'
  126. return response
  127. def configure_logging(app):
  128. """Configure logging for the application"""
  129. if not app.config.get('LOG_ENABLED', True):
  130. return None
  131. log_file = app.config.get(
  132. 'LOG_FILE', f'flask_{app.config["FLASK_ENV"]}.log'
  133. )
  134. log_level_name = app.config.get('LOG_LEVEL', 'INFO')
  135. log_level = getattr(logging, log_level_name)
  136. log_format = app.config.get(
  137. 'LOG_FORMAT',
  138. '%(asctime)s - %(levelname)s - %(filename)s - '
  139. '%(funcName)s - %(lineno)s - %(message)s'
  140. )
  141. log_encoding = app.config.get('LOG_ENCODING', 'UTF-8')
  142. log_to_console = app.config.get('LOG_TO_CONSOLE', True)
  143. # 配置根日志器
  144. root_logger = logging.getLogger()
  145. root_logger.setLevel(log_level)
  146. # 清除所有现有处理器
  147. root_logger.handlers.clear()
  148. # 文件处理器 - 只添加到根日志器
  149. file_handler = logging.FileHandler(log_file, encoding=log_encoding)
  150. file_handler.setLevel(log_level)
  151. logging_format = logging.Formatter(log_format)
  152. file_handler.setFormatter(logging_format)
  153. root_logger.addHandler(file_handler)
  154. # 控制台处理器 - 只添加到根日志器
  155. if log_to_console:
  156. console = logging.StreamHandler()
  157. console.setLevel(log_level)
  158. console.setFormatter(logging_format)
  159. root_logger.addHandler(console)
  160. # 确保Flask内部日志器使用我们的配置
  161. app.logger.handlers.clear() # 移除Flask默认处理器
  162. # 配置app日志器,但禁止传播到根日志器
  163. logger = logging.getLogger("app")
  164. logger.setLevel(log_level)
  165. logger.handlers.clear() # 清除现有处理器
  166. logger.propagate = True # 通过根日志器处理
  167. app.logger.info(f"日志配置完成: 级别={log_level_name}, 文件={log_file}")
  168. return logger
  169. def configure_error_handlers(app):
  170. """Configure global error handlers for the application"""
  171. @app.errorhandler(Exception)
  172. def handle_exception(e):
  173. """全局异常处理器,捕获所有未处理的异常"""
  174. # 记录详细的错误信息
  175. app.logger.error(f"未处理的异常: {str(e)}", exc_info=True)
  176. # 返回标准化的错误响应
  177. error_response = {
  178. 'success': False,
  179. 'message': f'服务器内部错误: {str(e)}',
  180. 'data': None
  181. }
  182. return jsonify(error_response), 500
  183. @app.errorhandler(404)
  184. def handle_not_found(e):
  185. """处理404错误"""
  186. app.logger.warning(f"404错误: {str(e)}")
  187. return jsonify({
  188. 'success': False,
  189. 'message': '请求的资源不存在',
  190. 'data': None
  191. }), 404
  192. @app.errorhandler(500)
  193. def handle_internal_error(e):
  194. """处理500错误"""
  195. app.logger.error(f"500错误: {str(e)}", exc_info=True)
  196. return jsonify({
  197. 'success': False,
  198. 'message': '服务器内部错误',
  199. 'data': None
  200. }), 500