__init__.py 8.7 KB

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