config.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580
  1. import os
  2. import platform
  3. from collections.abc import Mapping
  4. def get_bool_env(name: str, default: bool = False) -> bool:
  5. """Return a boolean value from an environment variable."""
  6. value = os.environ.get(name)
  7. if value is None:
  8. return default
  9. return value.strip().lower() in {"1", "true", "yes", "on"}
  10. def _bounded_int(name: str, default: int, minimum: int, maximum: int) -> int:
  11. try:
  12. value = int(os.environ.get(name, str(default)))
  13. except (TypeError, ValueError) as exc:
  14. raise ValueError(f"{name} must be an integer") from exc
  15. if value < minimum or value > maximum:
  16. raise ValueError(f"{name} must be between {minimum} and {maximum}")
  17. return value
  18. def _apply_datasource_pool_overrides(
  19. settings: dict, overrides: Mapping[str, int]
  20. ) -> dict:
  21. bounds = {
  22. "pool_size": (1, 10),
  23. "max_overflow": (0, 10),
  24. }
  25. unsupported = sorted(set(overrides) - set(bounds))
  26. if unsupported:
  27. raise ValueError(
  28. f"Unsupported data source pool overrides: {', '.join(unsupported)}"
  29. )
  30. resolved = dict(settings)
  31. for name, value in overrides.items():
  32. minimum, maximum = bounds[name]
  33. if type(value) is not int or value < minimum or value > maximum:
  34. raise ValueError(f"{name} must be between {minimum} and {maximum}")
  35. resolved[name] = value
  36. return resolved
  37. def datasource_pool_settings(
  38. overrides: Mapping[str, int] = None,
  39. ) -> dict:
  40. """Return bounded settings for external business data-source pools."""
  41. settings = {
  42. "pool_size": _bounded_int("DATASOURCE_POOL_SIZE", 2, 1, 10),
  43. "max_overflow": _bounded_int("DATASOURCE_MAX_OVERFLOW", 3, 0, 10),
  44. "pool_timeout": _bounded_int("DATASOURCE_POOL_TIMEOUT", 10, 1, 60),
  45. "pool_recycle": _bounded_int(
  46. "DATASOURCE_POOL_RECYCLE", 1800, 60, 86400
  47. ),
  48. "idle_ttl": _bounded_int(
  49. "DATASOURCE_POOL_IDLE_TTL", 900, 60, 86400
  50. ),
  51. "max_idle_pools": _bounded_int(
  52. "DATASOURCE_MAX_IDLE_POOLS", 20, 1, 100
  53. ),
  54. "query_timeout": _bounded_int(
  55. "DATASOURCE_QUERY_TIMEOUT", 30, 1, 300
  56. ),
  57. }
  58. return _apply_datasource_pool_overrides(settings, overrides or {})
  59. def get_environment():
  60. """
  61. 获取当前运行环境
  62. 优先级:
  63. 1. 环境变量 FLASK_ENV
  64. 2. 根据操作系统自动判断(Windows -> development, Linux -> production)
  65. """
  66. # 首先检查环境变量
  67. env = os.environ.get("FLASK_ENV")
  68. if env:
  69. return env.lower()
  70. # 根据操作系统判断
  71. system = platform.system().lower()
  72. if system == "windows":
  73. return "development"
  74. elif system == "linux":
  75. return "production"
  76. else:
  77. return "development" # 其他系统默认使用开发环境
  78. def resolve_log_file(default_filename: str = "flask_production.log") -> str:
  79. """Resolve application log path to an absolute file under LOG_DIR."""
  80. raw = os.environ.get("LOG_FILE", default_filename)
  81. if os.path.isabs(raw):
  82. return raw
  83. app_dir = os.environ.get("APP_DIR", os.getcwd())
  84. log_dir = os.environ.get("LOG_DIR", os.path.join(app_dir, "logs"))
  85. return os.path.join(log_dir, os.path.basename(raw))
  86. def _clean_env(name: str, default: str = "") -> str:
  87. value = os.environ.get(name)
  88. if value is None:
  89. return default
  90. return value.strip().strip("\r\n\t")
  91. def load_production_env_file() -> None:
  92. """Load production env file into os.environ without overriding existing keys."""
  93. env_file = os.environ.get(
  94. "APP_ENV_FILE",
  95. os.environ.get("ENV_FILE", "/etc/dataops-platform/dataops.env"),
  96. )
  97. if not env_file or not os.path.isfile(env_file):
  98. return
  99. if not os.access(env_file, os.R_OK):
  100. return
  101. try:
  102. from dotenv import load_dotenv
  103. load_dotenv(env_file, override=False)
  104. except Exception:
  105. return
  106. def is_placeholder_env_value(value: str) -> bool:
  107. """Detect template placeholders that should not override production defaults."""
  108. if not value:
  109. return True
  110. lower = value.lower()
  111. if lower.startswith("replace-"):
  112. return True
  113. placeholders = (
  114. "replace-password",
  115. "replace-neo4j-password",
  116. "replace-minio",
  117. "replace-with-a-long-random-secret",
  118. "replace-n8n-api-key",
  119. "your-api-key",
  120. )
  121. if any(item in lower for item in placeholders):
  122. return True
  123. if "dataops_user@" in lower and "127.0.0.1" in lower:
  124. return True
  125. return lower in {"127.0.0.1:9000", "localhost:9000"}
  126. def _is_local_minio_host(host: str) -> bool:
  127. if not host:
  128. return False
  129. return host.split(":")[0].lower() in {"127.0.0.1", "localhost"}
  130. def _apply_minio_config(app, defaults: dict) -> None:
  131. """Apply MinIO settings as a whole to avoid host/user/password mismatch."""
  132. host = _clean_env("MINIO_HOST")
  133. user = _clean_env("MINIO_USER")
  134. password = _clean_env("MINIO_PASSWORD")
  135. bucket = _clean_env("MINIO_BUCKET")
  136. prefix = _clean_env("MINIO_PREFIX")
  137. is_production = os.environ.get("FLASK_ENV", "").lower() == "production"
  138. use_defaults = (
  139. is_placeholder_env_value(user)
  140. or is_placeholder_env_value(password)
  141. or is_placeholder_env_value(host)
  142. or (is_production and _is_local_minio_host(host))
  143. )
  144. if use_defaults:
  145. app.config["MINIO_HOST"] = defaults["MINIO_HOST"]
  146. app.config["MINIO_USER"] = defaults["MINIO_USER"]
  147. app.config["MINIO_PASSWORD"] = defaults["MINIO_PASSWORD"]
  148. app.config["MINIO_BUCKET"] = defaults["MINIO_BUCKET"]
  149. app.config["PREFIX"] = defaults["MINIO_PREFIX"]
  150. app.config["MINIO_SECURE"] = defaults["MINIO_SECURE"]
  151. return
  152. app.config["MINIO_HOST"] = host or defaults["MINIO_HOST"]
  153. app.config["MINIO_USER"] = user or defaults["MINIO_USER"]
  154. app.config["MINIO_PASSWORD"] = password or defaults["MINIO_PASSWORD"]
  155. app.config["MINIO_BUCKET"] = bucket or defaults["MINIO_BUCKET"]
  156. app.config["PREFIX"] = prefix if prefix else defaults["MINIO_PREFIX"]
  157. app.config["MINIO_SECURE"] = get_bool_env("MINIO_SECURE", defaults["MINIO_SECURE"])
  158. PRODUCTION_SERVICE_DEFAULTS = {
  159. "SQLALCHEMY_DATABASE_URI": "postgresql://postgres:postgres@127.0.0.1:5432/dataops",
  160. "NEO4J_URI": "bolt://127.0.0.1:7687",
  161. "NEO4J_HTTP_URI": "http://127.0.0.1:7474",
  162. "NEO4J_USER": "neo4j",
  163. "NEO4J_PASSWORD": "Passw0rd",
  164. "NEO4J_ENCRYPTED": False,
  165. "MINIO_HOST": "127.0.0.1:9000",
  166. "MINIO_USER": "citu-test",
  167. "MINIO_PASSWORD": "citu-test",
  168. "MINIO_SECURE": False,
  169. "MINIO_BUCKET": "dataops-bucket",
  170. "MINIO_PREFIX": "",
  171. }
  172. def _apply_config_from_env(app, config_key: str, env_name: str, default):
  173. value = _clean_env(env_name)
  174. if value and not is_placeholder_env_value(value):
  175. app.config[config_key] = value
  176. return value
  177. app.config[config_key] = default
  178. return default
  179. def apply_runtime_env_config(app) -> None:
  180. """Re-read env-backed settings when the worker starts (after dataops.env is loaded)."""
  181. load_production_env_file()
  182. defaults = PRODUCTION_SERVICE_DEFAULTS
  183. _apply_config_from_env(
  184. app, "SQLALCHEMY_DATABASE_URI", "DATABASE_URL", defaults["SQLALCHEMY_DATABASE_URI"]
  185. )
  186. _apply_config_from_env(app, "NEO4J_URI", "NEO4J_URI", defaults["NEO4J_URI"])
  187. _apply_config_from_env(
  188. app, "NEO4J_HTTP_URI", "NEO4J_HTTP_URI", defaults["NEO4J_HTTP_URI"]
  189. )
  190. _apply_config_from_env(app, "NEO4J_USER", "NEO4J_USER", defaults["NEO4J_USER"])
  191. _apply_config_from_env(
  192. app, "NEO4J_PASSWORD", "NEO4J_PASSWORD", defaults["NEO4J_PASSWORD"]
  193. )
  194. app.config["NEO4J_ENCRYPTED"] = get_bool_env(
  195. "NEO4J_ENCRYPTED", defaults["NEO4J_ENCRYPTED"]
  196. )
  197. _apply_minio_config(app, defaults)
  198. secret_key = _clean_env("SECRET_KEY")
  199. if secret_key and not is_placeholder_env_value(secret_key):
  200. app.config["SECRET_KEY"] = secret_key
  201. deepseek_key = _clean_env("DEEPSEEK_API_KEY")
  202. llm_key = deepseek_key or _clean_env("LLM_API_KEY")
  203. if deepseek_key and not is_placeholder_env_value(deepseek_key):
  204. app.config["DEEPSEEK_API_KEY"] = deepseek_key
  205. app.config["LLM_API_KEY"] = deepseek_key
  206. elif llm_key and not is_placeholder_env_value(llm_key):
  207. app.config["LLM_API_KEY"] = llm_key
  208. _apply_config_from_env(
  209. app,
  210. "LLM_BASE_URL",
  211. "LLM_BASE_URL",
  212. "https://api.deepseek.com",
  213. )
  214. llm_model = _clean_env("LLM_MODEL_NAME")
  215. if llm_model:
  216. app.config["LLM_MODEL_NAME"] = llm_model
  217. llm_reasoning = _clean_env("LLM_REASONING_EFFORT")
  218. if llm_reasoning:
  219. app.config["LLM_REASONING_EFFORT"] = llm_reasoning
  220. pool_settings = datasource_pool_settings()
  221. app.config.update(
  222. {
  223. "DATASOURCE_POOL_SIZE": pool_settings["pool_size"],
  224. "DATASOURCE_MAX_OVERFLOW": pool_settings["max_overflow"],
  225. "DATASOURCE_POOL_TIMEOUT": pool_settings["pool_timeout"],
  226. "DATASOURCE_POOL_RECYCLE": pool_settings["pool_recycle"],
  227. "DATASOURCE_POOL_IDLE_TTL": pool_settings["idle_ttl"],
  228. "DATASOURCE_MAX_IDLE_POOLS": pool_settings["max_idle_pools"],
  229. "DATASOURCE_QUERY_TIMEOUT": pool_settings["query_timeout"],
  230. "DATASOURCE_CREDENTIAL_MASTER_KEY": _clean_env(
  231. "DATASOURCE_CREDENTIAL_MASTER_KEY"
  232. ),
  233. "DATASOURCE_CREDENTIAL_KEY_VERSION": _clean_env(
  234. "DATASOURCE_CREDENTIAL_KEY_VERSION", "v1"
  235. ),
  236. "DATASOURCE_CERT_DIR": _clean_env(
  237. "DATASOURCE_CERT_DIR",
  238. "/etc/dataops-platform/datasource-certs",
  239. ),
  240. }
  241. )
  242. def log_llm_env_status(app) -> None:
  243. """Log LLM env load result after logging is configured."""
  244. deepseek_key = _clean_env("DEEPSEEK_API_KEY") or _clean_env("LLM_API_KEY")
  245. if not deepseek_key:
  246. deepseek_key = str(
  247. app.config.get("DEEPSEEK_API_KEY") or app.config.get("LLM_API_KEY") or ""
  248. ).strip().strip("\r\n\t")
  249. env_file = os.environ.get("APP_ENV_FILE", "/etc/dataops-platform/dataops.env")
  250. if deepseek_key:
  251. app.logger.info(
  252. "DeepSeek API Key 已加载 (长度=%s, base_url=%s, model=%s)",
  253. len(deepseek_key),
  254. app.config.get("LLM_BASE_URL", "https://api.deepseek.com"),
  255. app.config.get("LLM_MODEL_NAME", "deepseek-chat"),
  256. )
  257. return
  258. if os.path.isfile(env_file) and not os.access(env_file, os.R_OK):
  259. app.logger.error(
  260. f"无法读取 {env_file},Supervisor 用户需有读权限。"
  261. f"请执行: sudo chown root:{os.environ.get('APP_USER', 'ubuntu')} "
  262. f"{env_file} && sudo chmod 640 {env_file}"
  263. )
  264. return
  265. app.logger.warning(
  266. f"DeepSeek API Key 未配置,请在 {env_file} 中设置 DEEPSEEK_API_KEY"
  267. )
  268. def log_service_env_status(app) -> None:
  269. """Log database/Neo4j endpoints after logging is configured."""
  270. db_uri = str(app.config.get("SQLALCHEMY_DATABASE_URI", ""))
  271. db_host = db_uri.split("@")[-1] if "@" in db_uri else db_uri
  272. app.logger.info(
  273. "服务连接配置: PostgreSQL=%s, Neo4j=%s, MinIO=%s (user=%s)",
  274. db_host,
  275. app.config.get("NEO4J_URI"),
  276. app.config.get("MINIO_HOST"),
  277. _mask_secret(str(app.config.get("MINIO_USER", ""))),
  278. )
  279. if is_placeholder_env_value(_clean_env("DATABASE_URL")):
  280. app.logger.warning(
  281. "DATABASE_URL 仍为模板占位符,已回退到默认生产库 "
  282. f"{PRODUCTION_SERVICE_DEFAULTS['SQLALCHEMY_DATABASE_URI'].split('@')[-1]}"
  283. )
  284. if is_placeholder_env_value(_clean_env("NEO4J_PASSWORD")):
  285. app.logger.warning(
  286. "NEO4J_PASSWORD 仍为模板占位符,已回退到 config.py 中的默认生产配置"
  287. )
  288. minio_user_env = _clean_env("MINIO_USER")
  289. minio_host_env = _clean_env("MINIO_HOST")
  290. if (
  291. is_placeholder_env_value(minio_user_env)
  292. or is_placeholder_env_value(minio_host_env)
  293. or (
  294. os.environ.get("FLASK_ENV", "").lower() == "production"
  295. and _is_local_minio_host(minio_host_env)
  296. )
  297. ):
  298. app.logger.warning(
  299. "MinIO 配置仍为模板或 localhost,已回退到默认生产 MinIO "
  300. f"{PRODUCTION_SERVICE_DEFAULTS['MINIO_HOST']}"
  301. )
  302. def _mask_secret(value: str) -> str:
  303. if not value:
  304. return "(empty)"
  305. if len(value) <= 4:
  306. return "****"
  307. return f"{value[:4]}****"
  308. class BaseConfig:
  309. """基础配置类,包含所有环境共享的配置"""
  310. SECRET_KEY = os.environ.get("SECRET_KEY") or "you-will-never-guess"
  311. RULE_GENERATION_RECEIPT_SECRET = os.environ.get(
  312. "RULE_GENERATION_RECEIPT_SECRET"
  313. )
  314. JSON_AS_ASCII = False
  315. JSONIFY_PRETTYPRINT_REGULAR = True
  316. JSON_SORT_KEYS = False
  317. # 平台特定配置
  318. PLATFORM = platform.system().lower()
  319. # 文件上传配置
  320. ALLOWED_EXTENSIONS = {
  321. "txt",
  322. "pdf",
  323. "png",
  324. "jpg",
  325. "jpeg",
  326. "gif",
  327. "xlsx",
  328. "xls",
  329. "csv",
  330. "sql",
  331. "dll",
  332. "docx",
  333. "doc",
  334. }
  335. DATA_RESEARCH_FILE_MAX_BYTES = int(
  336. os.environ.get("DATA_RESEARCH_FILE_MAX_BYTES", str(25 * 1024 * 1024))
  337. )
  338. DATA_RESEARCH_FILE_MAX_PAGES = int(
  339. os.environ.get("DATA_RESEARCH_FILE_MAX_PAGES", "500")
  340. )
  341. DATA_RESEARCH_MAX_CONCURRENT_JOBS = int(
  342. os.environ.get("DATA_RESEARCH_MAX_CONCURRENT_JOBS", "10")
  343. )
  344. # 数据抽取配置
  345. DATA_EXTRACT_BATCH_SIZE = 1000 # 每批处理的记录数
  346. # PostgreSQL 基础配置
  347. SQLALCHEMY_ENGINE_OPTIONS = {
  348. "pool_pre_ping": True,
  349. "pool_recycle": 300,
  350. "pool_size": 10,
  351. "max_overflow": 20,
  352. }
  353. _DATASOURCE_POOL_SETTINGS = datasource_pool_settings()
  354. DATASOURCE_POOL_SIZE = _DATASOURCE_POOL_SETTINGS["pool_size"]
  355. DATASOURCE_MAX_OVERFLOW = _DATASOURCE_POOL_SETTINGS["max_overflow"]
  356. DATASOURCE_POOL_TIMEOUT = _DATASOURCE_POOL_SETTINGS["pool_timeout"]
  357. DATASOURCE_POOL_RECYCLE = _DATASOURCE_POOL_SETTINGS["pool_recycle"]
  358. DATASOURCE_POOL_IDLE_TTL = _DATASOURCE_POOL_SETTINGS["idle_ttl"]
  359. DATASOURCE_MAX_IDLE_POOLS = _DATASOURCE_POOL_SETTINGS["max_idle_pools"]
  360. DATASOURCE_QUERY_TIMEOUT = _DATASOURCE_POOL_SETTINGS["query_timeout"]
  361. DATASOURCE_CREDENTIAL_MASTER_KEY = os.environ.get(
  362. "DATASOURCE_CREDENTIAL_MASTER_KEY", ""
  363. )
  364. DATASOURCE_CREDENTIAL_KEY_VERSION = os.environ.get(
  365. "DATASOURCE_CREDENTIAL_KEY_VERSION", "v1"
  366. )
  367. DATASOURCE_CERT_DIR = os.environ.get(
  368. "DATASOURCE_CERT_DIR",
  369. "/etc/dataops-platform/datasource-certs",
  370. )
  371. # DeepSeek LLM(OpenAI 兼容接口)
  372. DEEPSEEK_API_KEY = os.environ.get("DEEPSEEK_API_KEY", "")
  373. LLM_BASE_URL = os.environ.get("LLM_BASE_URL", "https://api.deepseek.com")
  374. LLM_MODEL_NAME = os.environ.get("LLM_MODEL_NAME", "deepseek-chat")
  375. LLM_REASONING_EFFORT = os.environ.get("LLM_REASONING_EFFORT", "high")
  376. # 兼容旧环境变量名 LLM_API_KEY
  377. LLM_API_KEY = DEEPSEEK_API_KEY or os.environ.get("LLM_API_KEY", "")
  378. # 数据治理知识库
  379. KNOWLEDGE_ENABLED = os.environ.get("KNOWLEDGE_ENABLED", "true").lower() == "true"
  380. KNOWLEDGE_VECTOR_TOP_K = int(os.environ.get("KNOWLEDGE_VECTOR_TOP_K", "40"))
  381. KNOWLEDGE_RRF_K = int(os.environ.get("KNOWLEDGE_RRF_K", "60"))
  382. KNOWLEDGE_RERANK_TOP_K = int(os.environ.get("KNOWLEDGE_RERANK_TOP_K", "8"))
  383. KNOWLEDGE_EVIDENCE_TOKEN_BUDGET = int(
  384. os.environ.get("KNOWLEDGE_EVIDENCE_TOKEN_BUDGET", "8000")
  385. )
  386. KNOWLEDGE_IMPACT_MAX_HOPS = int(os.environ.get("KNOWLEDGE_IMPACT_MAX_HOPS", "3"))
  387. KNOWLEDGE_IMPACT_MAX_POINTS = int(
  388. os.environ.get("KNOWLEDGE_IMPACT_MAX_POINTS", "1000")
  389. )
  390. KNOWLEDGE_LIGHTRAG_ENABLED = (
  391. os.environ.get("KNOWLEDGE_LIGHTRAG_ENABLED", "false").lower() == "true"
  392. )
  393. KNOWLEDGE_LIGHTRAG_SHADOW_ONLY = (
  394. os.environ.get("KNOWLEDGE_LIGHTRAG_SHADOW_ONLY", "true").lower() == "true"
  395. )
  396. KNOWLEDGE_LIGHTRAG_BASE_URL = os.environ.get(
  397. "KNOWLEDGE_LIGHTRAG_BASE_URL", "http://lightrag:9621"
  398. )
  399. KNOWLEDGE_LIGHTRAG_API_KEY = os.environ.get("KNOWLEDGE_LIGHTRAG_API_KEY", "")
  400. QWEN_EMBEDDING_BASE_URL = os.environ.get("QWEN_EMBEDDING_BASE_URL", "")
  401. QWEN_EMBEDDING_API_KEY = os.environ.get("QWEN_EMBEDDING_API_KEY", "")
  402. QWEN_EMBEDDING_MODEL = os.environ.get("QWEN_EMBEDDING_MODEL", "text-embedding-v3")
  403. QWEN_EMBEDDING_DIMENSION = int(os.environ.get("QWEN_EMBEDDING_DIMENSION", "1024"))
  404. # 日志基础配置
  405. LOG_FORMAT = "%(asctime)s - %(levelname)s - %(filename)s - %(funcName)s - %(lineno)s - %(message)s"
  406. LOG_ENCODING = "UTF-8"
  407. LOG_ENABLED = True
  408. # DataFlow 配置
  409. DATAFLOW_SCHEMA = os.environ.get("DATAFLOW_SCHEMA", "dags")
  410. # n8n 工作流引擎配置
  411. N8N_API_URL = os.environ.get("N8N_API_URL", "")
  412. N8N_API_KEY = os.environ.get("N8N_API_KEY", "")
  413. N8N_API_TIMEOUT = int(os.environ.get("N8N_API_TIMEOUT", "30"))
  414. # Kestra-backed governed Data Factory. Activation remains fail-closed
  415. # unless the operator explicitly opens the post-acceptance gate.
  416. KESTRA_BASE_URL = os.environ.get(
  417. "KESTRA_BASE_URL", "http://127.0.0.1:18080/api/v1"
  418. )
  419. KESTRA_TENANT_ID = os.environ.get("KESTRA_TENANT_ID", "main")
  420. KESTRA_USERNAME = os.environ.get("KESTRA_USERNAME", "")
  421. KESTRA_PASSWORD = os.environ.get("KESTRA_PASSWORD", "")
  422. KESTRA_HTTP_TIMEOUT_SECONDS = int(
  423. os.environ.get("KESTRA_HTTP_TIMEOUT_SECONDS", "30")
  424. )
  425. RUNNER_TASK_TOKEN_SECRET = os.environ.get(
  426. "RUNNER_TASK_TOKEN_SECRET", ""
  427. )
  428. DATA_FACTORY_ACTIVATION_ENABLED = get_bool_env(
  429. "DATA_FACTORY_ACTIVATION_ENABLED", False
  430. )
  431. # DataOps 平台 API 基础 URL(用于 n8n 工作流回调等)
  432. API_BASE_URL = os.environ.get("API_BASE_URL", "http://127.0.0.1:5500/api")
  433. class DevelopmentConfig(BaseConfig):
  434. """Windows 开发环境配置"""
  435. FLASK_ENV = "development"
  436. DEBUG = True
  437. PORT = 5500
  438. # 开发环境 MinIO 配置
  439. MINIO_HOST = "localhost:9000"
  440. MINIO_USER = "citu-test"
  441. MINIO_PASSWORD = "citu-test"
  442. MINIO_SECURE = False
  443. MINIO_BUCKET = "dataops-bucket"
  444. PREFIX = ""
  445. # 开发环境 PostgreSQL 配置
  446. SQLALCHEMY_DATABASE_URI = "postgresql://postgres:postgres@localhost:5432/dataops"
  447. # 开发环境 Neo4j 配置
  448. NEO4J_URI = "bolt://localhost:7687"
  449. NEO4J_HTTP_URI = "http://localhost:7474"
  450. NEO4J_USER = "neo4j"
  451. NEO4J_PASSWORD = "Passw0rd"
  452. NEO4J_ENCRYPTED = False
  453. # 开发环境文件路径配置
  454. UPLOAD_BASE_PATH = "C:\\tmp\\upload"
  455. ARCHIVE_BASE_PATH = "C:\\tmp\\archive"
  456. # 开发环境日志配置
  457. LOG_LEVEL = "DEBUG"
  458. LOG_FILE = resolve_log_file("flask_development.log")
  459. LOG_TO_CONSOLE = True
  460. class ProductionConfig(BaseConfig):
  461. """Linux 生产环境配置"""
  462. FLASK_ENV = "production"
  463. DEBUG = False
  464. # 与 run_dataops.sh / dataops.env 中 LISTEN_PORT 保持一致(默认 5500,供 Nginx 反代)
  465. PORT = int(os.environ.get("LISTEN_PORT", os.environ.get("PORT", "5500")))
  466. # 生产环境 MinIO 配置
  467. MINIO_HOST = os.environ.get("MINIO_HOST", "127.0.0.1:9000")
  468. MINIO_USER = os.environ.get("MINIO_USER", "citu-test")
  469. MINIO_PASSWORD = os.environ.get("MINIO_PASSWORD", "citu-test")
  470. MINIO_SECURE = get_bool_env("MINIO_SECURE", False)
  471. MINIO_BUCKET = os.environ.get("MINIO_BUCKET", "dataops-bucket")
  472. PREFIX = os.environ.get("MINIO_PREFIX", "")
  473. # 生产环境 PostgreSQL 配置
  474. SQLALCHEMY_DATABASE_URI = os.environ.get(
  475. "DATABASE_URL", "postgresql://postgres:postgres@127.0.0.1:5432/dataops"
  476. )
  477. # 生产环境 Neo4j 配置
  478. NEO4J_URI = os.environ.get("NEO4J_URI", "bolt://127.0.0.1:7687")
  479. NEO4J_HTTP_URI = os.environ.get("NEO4J_HTTP_URI", "http://127.0.0.1:7474")
  480. NEO4J_USER = os.environ.get("NEO4J_USER", "neo4j")
  481. NEO4J_PASSWORD = os.environ.get("NEO4J_PASSWORD", "Passw0rd")
  482. NEO4J_ENCRYPTED = get_bool_env("NEO4J_ENCRYPTED", False)
  483. # 生产环境文件路径配置
  484. UPLOAD_BASE_PATH = os.environ.get("UPLOAD_BASE_PATH", "/data/upload")
  485. ARCHIVE_BASE_PATH = os.environ.get("ARCHIVE_BASE_PATH", "/data/archive")
  486. # 生产环境日志配置
  487. LOG_LEVEL = os.environ.get("LOG_LEVEL", "INFO")
  488. LOG_FILE = resolve_log_file("flask_production.log")
  489. LOG_TO_CONSOLE = get_bool_env("LOG_TO_CONSOLE", False)
  490. # 配置字典
  491. config = {
  492. "development": DevelopmentConfig,
  493. "production": ProductionConfig,
  494. "default": DevelopmentConfig,
  495. }
  496. # 获取当前环境
  497. current_env = get_environment()