chore: initial commit — leaudit-platform project skeleton
17-table PostgreSQL schema with full Chinese column comments, FastAPI project structure (admin/common/modules), DSL rule files, and schema migration scripts.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
"""配置模块。
|
||||
|
||||
所有 Settings 实例的字段和 @property 会被自动导出为模块级变量。
|
||||
业务代码直接导入:
|
||||
|
||||
from fastapi_admin.config import APP_PORT, ASYNCPG_DATABASE_URL
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from ._loader import load_config as _load_config
|
||||
|
||||
# 优先加载 TOML → os.environ(必须在 Settings 实例化之前)
|
||||
_load_config()
|
||||
|
||||
from ._settings import app, jwt, db, redis, oss, llm, vlm, ocr, leaudit as _leaudit # noqa: E402
|
||||
|
||||
|
||||
def _export_settings(instance: object, prefix: str = "") -> dict[str, object]:
|
||||
"""将 Settings 实例的所有字段和 @property 导出为模块级变量。"""
|
||||
result: dict[str, object] = {}
|
||||
for key in dir(type(instance)):
|
||||
if key.startswith("_"):
|
||||
continue
|
||||
value = getattr(instance, key, None)
|
||||
if callable(value) and not isinstance(value, property):
|
||||
continue
|
||||
if isinstance(value, property):
|
||||
value = value.__get__(instance)
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
|
||||
_APP = _export_settings(app)
|
||||
_JWT = _export_settings(jwt)
|
||||
_DB = _export_settings(db)
|
||||
_REDIS = _export_settings(redis)
|
||||
_OSS = _export_settings(oss)
|
||||
_LLM = _export_settings(llm)
|
||||
_VLM = _export_settings(vlm)
|
||||
_OCR = _export_settings(ocr)
|
||||
_LEAUDIT = _export_settings(_leaudit)
|
||||
|
||||
# 将所有变量注入当前模块的全局命名空间
|
||||
_ALL = {}
|
||||
_ALL.update(_APP)
|
||||
_ALL.update(_JWT)
|
||||
_ALL.update(_DB)
|
||||
_ALL.update(_REDIS)
|
||||
_ALL.update(_OSS)
|
||||
_ALL.update(_LLM)
|
||||
_ALL.update(_VLM)
|
||||
_ALL.update(_OCR)
|
||||
_ALL.update(_LEAUDIT)
|
||||
|
||||
globals().update(_ALL)
|
||||
|
||||
# 常量
|
||||
ROOT_PATH = __import__("pathlib").Path(__file__).resolve().parents[2]
|
||||
@@ -0,0 +1,55 @@
|
||||
"""类型存根 —— 为 IDE 提供动态导出变量的类型信息。"""
|
||||
|
||||
# APP
|
||||
APP_NAME: str
|
||||
APP_HOST: str
|
||||
APP_PORT: int
|
||||
APP_CORS_ORIGINS: str
|
||||
|
||||
# JWT
|
||||
JWT_SECRET_KEY: str
|
||||
JWT_ACCESS_TOKEN_EXPIRE_HOURS: int
|
||||
JWT_ALGORITHM: str
|
||||
|
||||
# DB
|
||||
DB_HOST: str
|
||||
DB_PORT: int
|
||||
DB_NAME: str
|
||||
DB_USER: str
|
||||
DB_PASSWORD: str
|
||||
ASYNCPG_DATABASE_URL: str
|
||||
|
||||
# Redis
|
||||
REDIS_HOST: str
|
||||
REDIS_PORT: int
|
||||
REDIS_DB: int
|
||||
REDIS_PASSWORD: str
|
||||
|
||||
# OSS
|
||||
OSS_ENDPOINT: str
|
||||
OSS_ACCESS_KEY: str
|
||||
OSS_SECRET_KEY: str
|
||||
OSS_BUCKET: str
|
||||
OSS_REGION: str
|
||||
|
||||
# LLM
|
||||
LLM_BASE_URL: str
|
||||
LLM_MODEL: str
|
||||
LLM_API_KEY: str
|
||||
|
||||
# VLM
|
||||
VLM_BASE_URL: str
|
||||
VLM_MODEL: str
|
||||
|
||||
# OCR
|
||||
OCR_BASE_URL: str
|
||||
OCR_TIMEOUT: int
|
||||
|
||||
# LEAUDIT
|
||||
LEAUDIT_RULES_DIR: str
|
||||
LEAUDIT_RESCUE_MODE: str
|
||||
LEAUDIT_LLM_MAX_CONCURRENCY: int
|
||||
LEAUDIT_VLM_MAX_CONCURRENCY: int
|
||||
|
||||
# 常量
|
||||
ROOT_PATH: object
|
||||
@@ -0,0 +1,68 @@
|
||||
"""配置加载器 —— 读取 TOML 文件并注入 os.environ。
|
||||
|
||||
加载顺序(后覆盖前):
|
||||
1. app.toml — 基础配置
|
||||
2. app.{APP_ENV}.toml — 环境差异
|
||||
3. app.ai.toml — AI 专用(最高 TOML 优先级)
|
||||
4. 已有环境变量 — 不覆盖(最高优先级)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
_ROOT = Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def _find_project_root() -> Path:
|
||||
"""查找项目根目录(包含 app.toml 的目录)。"""
|
||||
return _ROOT
|
||||
|
||||
|
||||
def _load_toml(path: Path) -> dict:
|
||||
"""读取 TOML 文件并展平 [SECTION].KEY → SECTION_KEY。"""
|
||||
try:
|
||||
import tomllib
|
||||
except ImportError:
|
||||
import tomli as tomllib
|
||||
|
||||
with open(path, "rb") as fh:
|
||||
data = tomllib.load(fh)
|
||||
|
||||
flat: dict[str, str] = {}
|
||||
for section, values in data.items():
|
||||
if not isinstance(values, dict):
|
||||
continue
|
||||
for key, val in values.items():
|
||||
env_key = f"{section.upper()}_{key.upper()}"
|
||||
if isinstance(val, list):
|
||||
flat[env_key] = ",".join(str(v) for v in val)
|
||||
elif isinstance(val, bool):
|
||||
flat[env_key] = str(val).lower()
|
||||
elif val is not None:
|
||||
flat[env_key] = str(val)
|
||||
return flat
|
||||
|
||||
|
||||
def load_config() -> None:
|
||||
"""加载所有 TOML 配置到 os.environ(不覆盖已有环境变量)。"""
|
||||
root = _find_project_root()
|
||||
|
||||
env = os.getenv("APP_ENV", "development")
|
||||
toml_files = [
|
||||
root / "app.toml",
|
||||
root / f"app.{env}.toml",
|
||||
root / "app.ai.toml",
|
||||
]
|
||||
|
||||
for toml_path in toml_files:
|
||||
if not toml_path.exists():
|
||||
continue
|
||||
for key, value in _load_toml(toml_path).items():
|
||||
os.environ.setdefault(key, value)
|
||||
|
||||
# 确保项目根在 sys.path 中
|
||||
if str(root) not in sys.path:
|
||||
sys.path.insert(0, str(root))
|
||||
@@ -0,0 +1,102 @@
|
||||
"""Pydantic Settings 定义。
|
||||
|
||||
每个 Settings 类对应 app.toml 中的一个 [SECTION]。
|
||||
字段名 = SECTION_KEY(TOML 展平后的环境变量名)。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic_settings import BaseSettings
|
||||
|
||||
|
||||
class _Base(BaseSettings):
|
||||
"""所有 Settings 的基类。"""
|
||||
model_config = {"env_file": None, "extra": "ignore"}
|
||||
|
||||
|
||||
class AppSettings(_Base):
|
||||
"""应用基础配置 [APP]。"""
|
||||
APP_NAME: str = "LeAudit Platform"
|
||||
APP_HOST: str = "0.0.0.0"
|
||||
APP_PORT: int = 8000
|
||||
APP_CORS_ORIGINS: str = "*"
|
||||
|
||||
|
||||
class JwtSettings(_Base):
|
||||
"""JWT 配置 [JWT]。"""
|
||||
JWT_SECRET_KEY: str = ""
|
||||
JWT_ACCESS_TOKEN_EXPIRE_HOURS: int = 24
|
||||
JWT_ALGORITHM: str = "HS256"
|
||||
|
||||
|
||||
class DbSettings(_Base):
|
||||
"""数据库配置 [DB]。"""
|
||||
DB_HOST: str = "localhost"
|
||||
DB_PORT: int = 5432
|
||||
DB_NAME: str = "leaudit"
|
||||
DB_USER: str = "postgres"
|
||||
DB_PASSWORD: str = ""
|
||||
|
||||
@property
|
||||
def ASYNCPG_DATABASE_URL(self) -> str:
|
||||
"""动态构建 asyncpg 连接 URL。"""
|
||||
return (
|
||||
f"postgresql+asyncpg://{self.DB_USER}:{self.DB_PASSWORD}"
|
||||
f"@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
|
||||
)
|
||||
|
||||
|
||||
class RedisSettings(_Base):
|
||||
"""Redis 配置 [REDIS]。"""
|
||||
REDIS_HOST: str = "localhost"
|
||||
REDIS_PORT: int = 6379
|
||||
REDIS_DB: int = 0
|
||||
REDIS_PASSWORD: str = ""
|
||||
|
||||
|
||||
class OssSettings(_Base):
|
||||
"""OSS 对象存储配置 [OSS]。"""
|
||||
OSS_ENDPOINT: str = ""
|
||||
OSS_ACCESS_KEY: str = ""
|
||||
OSS_SECRET_KEY: str = ""
|
||||
OSS_BUCKET: str = "leaudit"
|
||||
OSS_REGION: str = ""
|
||||
|
||||
|
||||
class LlmSettings(_Base):
|
||||
"""LLM 配置 [LLM]。"""
|
||||
LLM_BASE_URL: str = ""
|
||||
LLM_MODEL: str = ""
|
||||
LLM_API_KEY: str = ""
|
||||
|
||||
|
||||
class VlmSettings(_Base):
|
||||
"""VLM 配置 [VLM]。"""
|
||||
VLM_BASE_URL: str = ""
|
||||
VLM_MODEL: str = ""
|
||||
|
||||
|
||||
class OcrSettings(_Base):
|
||||
"""OCR 配置 [OCR]。"""
|
||||
OCR_BASE_URL: str = ""
|
||||
OCR_TIMEOUT: int = 300
|
||||
|
||||
|
||||
class LeauditSettings(_Base):
|
||||
"""LeAudit 引擎配置 [LEAUDIT]。"""
|
||||
LEAUDIT_RULES_DIR: str = "rules"
|
||||
LEAUDIT_RESCUE_MODE: str = "auto"
|
||||
LEAUDIT_LLM_MAX_CONCURRENCY: int = 5
|
||||
LEAUDIT_VLM_MAX_CONCURRENCY: int = 3
|
||||
|
||||
|
||||
# 实例化所有 Settings
|
||||
app = AppSettings()
|
||||
jwt = JwtSettings()
|
||||
db = DbSettings()
|
||||
redis = RedisSettings()
|
||||
oss = OssSettings()
|
||||
llm = LlmSettings()
|
||||
vlm = VlmSettings()
|
||||
ocr = OcrSettings()
|
||||
leaudit = LeauditSettings()
|
||||
Reference in New Issue
Block a user