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:
wren
2026-04-27 16:48:22 +08:00
commit 535d97a70c
142 changed files with 25219 additions and 0 deletions
+59
View File
@@ -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]
+55
View File
@@ -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
+68
View File
@@ -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))
+102
View File
@@ -0,0 +1,102 @@
"""Pydantic Settings 定义。
每个 Settings 类对应 app.toml 中的一个 [SECTION]。
字段名 = SECTION_KEYTOML 展平后的环境变量名)。
"""
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()