config.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. """DMS环境配置。"""
  2. from __future__ import annotations
  3. import json
  4. import os
  5. from pathlib import Path
  6. from typing import Any
  7. from zoneinfo import ZoneInfo, ZoneInfoNotFoundError
  8. import shutil
  9. def _json_serializer(value: Any) -> str:
  10. """SQLAlchemy JSON 列序列化:原文中文(ensure_ascii=False),对齐达梦存量与 Java 版。"""
  11. return json.dumps(value, ensure_ascii=False)
  12. BACKEND_ROOT = Path(__file__).resolve().parents[1]
  13. DEFAULT_STORAGE_ROOT = BACKEND_ROOT / "dms-storage"
  14. DEFAULT_UI_DICTIONARY_CONFIG_PATH = (
  15. BACKEND_ROOT / "dms" / "resources" / "ui-dictionaries.zh-CN.json"
  16. )
  17. DEFAULT_DOCUMENT_RELATION_PRESET_CONFIG_PATH = (
  18. BACKEND_ROOT / "dms" / "resources" / "document-relation-presets.zh-CN.json"
  19. )
  20. # 当前数据库:达梦 DM(dm+dmPython 方言,默认端口 5236,schema 由用户 DMS 决定)。
  21. # 原 MySQL 默认链接(保留备查,回切时恢复):
  22. # "mysql+pymysql://dms_app:change-me@127.0.0.1:3306/dms?charset=utf8mb4"
  23. DEFAULT_DATABASE_URL = "dm+dmPython://DMS:change-me@127.0.0.1:5236"
  24. def _positive_int(name: str, default: int) -> int:
  25. raw_value = os.environ.get(name)
  26. if raw_value is None or not raw_value.strip():
  27. return default
  28. try:
  29. value = int(raw_value)
  30. except ValueError as exc:
  31. raise ValueError(f"{name}必须是正整数") from exc
  32. if value <= 0:
  33. raise ValueError(f"{name}必须是正整数")
  34. return value
  35. def load_dms_config() -> dict[str, Any]:
  36. """从环境变量加载DMS配置,不读取或覆盖现有AI配置。"""
  37. storage_value = os.environ.get("DMS_STORAGE_ROOT", "").strip()
  38. storage_root = Path(storage_value or DEFAULT_STORAGE_ROOT).expanduser()
  39. max_upload_size = _positive_int(
  40. "DMS_MAX_UPLOAD_SIZE", 100 * 1024 * 1024
  41. )
  42. max_file_size = _positive_int(
  43. "DMS_MAX_FILE_SIZE", min(50 * 1024 * 1024, max_upload_size)
  44. )
  45. if max_file_size > max_upload_size:
  46. raise ValueError("DMS_MAX_FILE_SIZE不得超过DMS_MAX_UPLOAD_SIZE")
  47. database_url = os.environ.get("DMS_DATABASE_URL", "").strip()
  48. ui_dictionary_config_value = os.environ.get(
  49. "DMS_UI_DICTIONARY_CONFIG_PATH", ""
  50. ).strip()
  51. ui_dictionary_config_path = Path(
  52. ui_dictionary_config_value or DEFAULT_UI_DICTIONARY_CONFIG_PATH
  53. ).expanduser()
  54. relation_preset_config_value = os.environ.get(
  55. "DMS_DOCUMENT_RELATION_PRESET_CONFIG_PATH", ""
  56. ).strip()
  57. relation_preset_config_path = Path(
  58. relation_preset_config_value
  59. or DEFAULT_DOCUMENT_RELATION_PRESET_CONFIG_PATH
  60. ).expanduser()
  61. business_timezone = (
  62. os.environ.get("DMS_BUSINESS_TIMEZONE", "").strip()
  63. or "Asia/Shanghai"
  64. )
  65. try:
  66. ZoneInfo(business_timezone)
  67. except (ZoneInfoNotFoundError, ValueError) as exc:
  68. raise ValueError("DMS_BUSINESS_TIMEZONE必须是有效的IANA时区") from exc
  69. office_preview_enabled = (
  70. os.environ.get("DMS_OFFICE_PREVIEW_ENABLED", "true").strip().lower()
  71. in {"1", "true", "yes", "on"}
  72. )
  73. libreoffice_executable = (
  74. os.environ.get("DMS_LIBREOFFICE_EXECUTABLE", "").strip()
  75. or shutil.which("soffice")
  76. or shutil.which("libreoffice")
  77. or ""
  78. )
  79. libreoffice_timeout_seconds = _positive_int(
  80. "DMS_LIBREOFFICE_TIMEOUT_SECONDS", 60
  81. )
  82. libreoffice_max_concurrency = _positive_int(
  83. "DMS_LIBREOFFICE_MAX_CONCURRENCY", 2
  84. )
  85. pdf_viewer_engine_raw = (
  86. os.environ.get("DMS_PDF_VIEWER_ENGINE", "iframe").strip().lower()
  87. )
  88. pdf_viewer_engine = (
  89. pdf_viewer_engine_raw if pdf_viewer_engine_raw in {"iframe", "pdfjs"} else "iframe"
  90. )
  91. resolved_database_url = database_url or DEFAULT_DATABASE_URL
  92. # init_command(SET time_zone)为 pymysql 专属参数,达梦 dmPython 不接受;
  93. # 仅在 MySQL 链接时下发。
  94. connect_args = (
  95. {"init_command": "SET time_zone = '+00:00'"}
  96. if resolved_database_url.startswith(("mysql", "jdbc:mysql"))
  97. else {}
  98. )
  99. return {
  100. "SQLALCHEMY_DATABASE_URI": resolved_database_url,
  101. "SQLALCHEMY_TRACK_MODIFICATIONS": False,
  102. "SQLALCHEMY_ENGINE_OPTIONS": {
  103. "isolation_level": "READ COMMITTED",
  104. "pool_pre_ping": True,
  105. "pool_recycle": 1800,
  106. "connect_args": connect_args,
  107. # JSON 列统一原文中文序列化(ensure_ascii=False),与达梦存量数据及
  108. # Java 版 Jackson 写入格式一致;否则 \uXXXX 转义会导致 LIKE 检索漏配。
  109. "json_serializer": _json_serializer,
  110. },
  111. "DMS_STORAGE_ROOT": str(storage_root.resolve()),
  112. "DMS_MAX_UPLOAD_SIZE": max_upload_size,
  113. "DMS_MAX_FILE_SIZE": max_file_size,
  114. "MAX_CONTENT_LENGTH": max_upload_size,
  115. "DMS_BATCH_MAX_FILES": _positive_int("DMS_BATCH_MAX_FILES", 50),
  116. "DMS_BUSINESS_TIMEZONE": business_timezone,
  117. "DMS_JWT_SECRET": os.environ.get("DMS_JWT_SECRET", ""),
  118. "DMS_JWT_ISSUER": "dms",
  119. "DMS_JWT_EXPIRES_SECONDS": 7200,
  120. "DMS_JWT_KEEP_SIGNED_IN_EXPIRES_SECONDS": 604800,
  121. "DMS_UI_DICTIONARY_CONFIG_PATH": str(
  122. ui_dictionary_config_path.resolve()
  123. ),
  124. "DMS_DOCUMENT_RELATION_PRESET_CONFIG_PATH": str(
  125. relation_preset_config_path.resolve()
  126. ),
  127. "DMS_OFFICE_PREVIEW_ENABLED": office_preview_enabled,
  128. "DMS_LIBREOFFICE_EXECUTABLE": libreoffice_executable,
  129. "DMS_LIBREOFFICE_TIMEOUT_SECONDS": libreoffice_timeout_seconds,
  130. "DMS_LIBREOFFICE_MAX_CONCURRENCY": libreoffice_max_concurrency,
  131. "DMS_PDF_VIEWER_ENGINE": pdf_viewer_engine,
  132. }