__init__.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101
  1. """方案计划文档管理系统(DMS)后端模块。"""
  2. from __future__ import annotations
  3. import logging
  4. from pathlib import Path
  5. from flask import Flask
  6. from dms.api.v1 import api_v1
  7. from dms.api.v1.blueprint import (
  8. register_dms_response_headers,
  9. register_dms_routing_error_handlers,
  10. )
  11. from dms.config import (
  12. load_dms_config,
  13. resolve_backup_config_path,
  14. resolve_env_path,
  15. )
  16. from dms.extensions import db
  17. from dms.services.backup_service import BackupEngine, BackupOutcome
  18. from dms.services.document_relation_service import load_relation_presets
  19. from dms.services.ui_dictionary_service import load_ui_dictionary_config
  20. from dms.storage.paths import ensure_storage_directories
  21. from dms.services.document_converter import create_converter
  22. logger = logging.getLogger(__name__)
  23. def init_dms(app: Flask) -> None:
  24. """初始化独立DMS基础设施并注册 ``/api/v1`` Blueprint。"""
  25. app.config.from_mapping(load_dms_config())
  26. app.extensions["dms_ui_dictionaries"] = load_ui_dictionary_config(
  27. app.config["DMS_UI_DICTIONARY_CONFIG_PATH"]
  28. )
  29. app.extensions["dms_relation_presets"] = load_relation_presets(
  30. app.config["DMS_DOCUMENT_RELATION_PRESET_CONFIG_PATH"]
  31. )
  32. app.extensions["dms_document_converter"] = create_converter(app.config)
  33. db.init_app(app)
  34. # 导入模型以确保SQLAlchemy metadata完整;不触发数据库连接。
  35. import dms.models # noqa: F401
  36. ensure_storage_directories(app.config["DMS_STORAGE_ROOT"])
  37. app.register_blueprint(api_v1)
  38. register_dms_routing_error_handlers(app)
  39. register_dms_response_headers(app)
  40. backup_engine = BackupEngine(
  41. storage_root=Path(app.config["DMS_STORAGE_ROOT"]),
  42. business_timezone=app.config["DMS_BUSINESS_TIMEZONE"],
  43. database_url=app.config["SQLALCHEMY_DATABASE_URI"],
  44. backup_config_path=resolve_backup_config_path(),
  45. env_path=resolve_env_path(),
  46. )
  47. app.extensions["dms_backup_engine"] = backup_engine
  48. backup_engine.start_scheduler(
  49. lambda outcome: _write_schedule_audit(app, outcome)
  50. )
  51. def _write_schedule_audit(app: Flask, outcome: BackupOutcome) -> None:
  52. """定时备份终态审计落库(无请求上下文,独立短事务)。"""
  53. from dms.common.enums import AuditAction, AuditResult, AuditTarget
  54. from dms.models import AuditLog
  55. try:
  56. with app.app_context():
  57. db.session.add(
  58. AuditLog(
  59. username="system",
  60. action_type=AuditAction.BACKUP_CREATE.value,
  61. target_type=AuditTarget.BACKUP.value,
  62. target_id=0,
  63. target_name=outcome.type,
  64. operation_result=(
  65. AuditResult.SUCCESS.value
  66. if outcome.success
  67. else AuditResult.FAILURE.value
  68. ),
  69. failure_reason=(
  70. None
  71. if outcome.success or outcome.error is None
  72. else outcome.error[:1000]
  73. ),
  74. operation_detail={
  75. "type": outcome.type,
  76. "trigger": outcome.trigger,
  77. "backupId": outcome.backup_id,
  78. },
  79. )
  80. )
  81. db.session.commit()
  82. except Exception: # noqa: BLE001 - 审计失败不否定已触发的备份
  83. logger.warning("定时备份审计写入失败", exc_info=True)
  84. with app.app_context():
  85. db.session.rollback()
  86. __all__ = ["init_dms"]