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,11 @@
|
||||
"""LeAudit 模型导出。"""
|
||||
|
||||
from fastapi_modules.fastapi_leaudit.models.leauditDocument import LeauditDocument
|
||||
from fastapi_modules.fastapi_leaudit.models.leauditDocumentFile import LeauditDocumentFile
|
||||
from fastapi_modules.fastapi_leaudit.models.leauditAuditRun import LeauditAuditRun
|
||||
|
||||
__all__ = [
|
||||
"LeauditDocument",
|
||||
"LeauditDocumentFile",
|
||||
"LeauditAuditRun",
|
||||
]
|
||||
@@ -0,0 +1,77 @@
|
||||
"""JWT Token 模型 —— jwt_tokens 表。
|
||||
|
||||
记录每次签发的 Token 生命周期:签发、刷新、撤销。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import BigInteger, Boolean, DateTime, String, select, update
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from fastapi_common.fastapi_common_web.models import BaseModel
|
||||
|
||||
|
||||
class JwtToken(BaseModel):
|
||||
"""JWT Token 记录表。"""
|
||||
|
||||
__tablename__ = "jwt_tokens"
|
||||
|
||||
Id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||
userId: Mapped[int] = mapped_column(BigInteger, comment="用户ID")
|
||||
tokenJti: Mapped[str] = mapped_column(String(128), comment="Token JTI")
|
||||
tokenHash: Mapped[str] = mapped_column(String(128), comment="Access Token SHA256")
|
||||
refreshTokenHash: Mapped[str | None] = mapped_column(String(128), comment="Refresh Token SHA256")
|
||||
tokenType: Mapped[str] = mapped_column(String(32), default="ACCESS", comment="ACCESS/REFRESH")
|
||||
deviceId: Mapped[str | None] = mapped_column(String(128))
|
||||
deviceName: Mapped[str | None] = mapped_column(String(256))
|
||||
userAgent: Mapped[str | None] = mapped_column(String(512))
|
||||
ipAddress: Mapped[str | None] = mapped_column(String(64))
|
||||
issuedAt: Mapped[datetime] = mapped_column(DateTime(timezone=True))
|
||||
expiresAt: Mapped[datetime] = mapped_column(DateTime(timezone=True))
|
||||
refreshExpiresAt: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
lastUsedAt: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
isRevoked: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
revokedAt: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
revokeReason: Mapped[str | None] = mapped_column(String(256))
|
||||
|
||||
@classmethod
|
||||
async def get_by_jti(cls, session: AsyncSession, jti: str) -> "JwtToken | None":
|
||||
"""按 JTI 查询 Token 记录。"""
|
||||
return await session.scalar(select(cls).where(cls.tokenJti == jti))
|
||||
|
||||
@classmethod
|
||||
async def revoke_by_jti(cls, session: AsyncSession, jti: str, reason: str = "") -> None:
|
||||
"""撤销指定 JTI 的 Token。"""
|
||||
await session.execute(
|
||||
update(cls)
|
||||
.where(cls.tokenJti == jti)
|
||||
.values(isRevoked=True, revokedAt=datetime.now(), revokeReason=reason)
|
||||
)
|
||||
|
||||
@classmethod
|
||||
async def revoke_all_user_tokens(cls, session: AsyncSession, userId: int, reason: str = "") -> list[str]:
|
||||
"""撤销用户的所有活跃 Token,返回被撤销的 JTI 列表。"""
|
||||
result = await session.execute(
|
||||
select(cls.tokenJti).where(cls.userId == userId, cls.isRevoked == False)
|
||||
)
|
||||
jtis = [row[0] for row in result.fetchall()]
|
||||
await session.execute(
|
||||
update(cls)
|
||||
.where(cls.userId == userId, cls.isRevoked == False)
|
||||
.values(isRevoked=True, revokedAt=datetime.now(), revokeReason=reason)
|
||||
)
|
||||
return jtis
|
||||
|
||||
@classmethod
|
||||
async def cleanup_expired(cls, session: AsyncSession, before: datetime) -> int:
|
||||
"""清理过期的 Token 记录,返回删除数。"""
|
||||
result = await session.execute(
|
||||
select(cls).where(cls.expiresAt < before)
|
||||
)
|
||||
rows = result.scalars().all()
|
||||
for row in rows:
|
||||
await session.delete(row)
|
||||
return len(rows)
|
||||
@@ -0,0 +1,60 @@
|
||||
"""LeAudit AuditRun 模型 —— leaudit_audit_runs 表。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import BigInteger, Boolean, DateTime, Integer, Numeric, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from fastapi_common.fastapi_common_web.models import BaseModel
|
||||
|
||||
|
||||
class LeauditAuditRun(BaseModel):
|
||||
"""评查运行主表。"""
|
||||
|
||||
__tablename__ = "leaudit_audit_runs"
|
||||
|
||||
Id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||
documentId: Mapped[int] = mapped_column(BigInteger, comment="关联 leaudit_documents.id")
|
||||
documentFileId: Mapped[int | None] = mapped_column(BigInteger, comment="输入文件ID")
|
||||
runNo: Mapped[int] = mapped_column(Integer, comment="同一文档第几次执行")
|
||||
triggerSource: Mapped[str] = mapped_column(String(64), comment="upload/manual/retry/migration/batch")
|
||||
triggerUserId: Mapped[int | None] = mapped_column(BigInteger, comment="触发人")
|
||||
taskId: Mapped[str | None] = mapped_column(String(128), comment="Celery 任务 ID")
|
||||
|
||||
# 状态
|
||||
status: Mapped[str] = mapped_column(String(64), default="pending", comment="pending/processing/completed/failed/cancelled")
|
||||
phase: Mapped[str | None] = mapped_column(String(32), comment="draft/executed")
|
||||
|
||||
# 规则溯源
|
||||
ruleSetId: Mapped[int] = mapped_column(BigInteger, comment="关联 leaudit_rule_sets.id")
|
||||
ruleVersionId: Mapped[int] = mapped_column(BigInteger, comment="关联 leaudit_rule_versions.id")
|
||||
ruleTypeId: Mapped[str | None] = mapped_column(String(256), comment="LeAudit metadata.type_id")
|
||||
ruleSourceOssUrl: Mapped[str | None] = mapped_column(String(2048), comment="规则 YAML OSS 地址")
|
||||
ruleSourceSha256: Mapped[str | None] = mapped_column(String(64), comment="规则文件 SHA256")
|
||||
ruleLocalCachePath: Mapped[str | None] = mapped_column(String(1024), comment="本地缓存路径")
|
||||
|
||||
# 模型快照
|
||||
engineVersion: Mapped[str | None] = mapped_column(String(64))
|
||||
llmProvider: Mapped[str | None] = mapped_column(String(64))
|
||||
llmModel: Mapped[str | None] = mapped_column(String(128))
|
||||
vlmProvider: Mapped[str | None] = mapped_column(String(64))
|
||||
vlmModel: Mapped[str | None] = mapped_column(String(128))
|
||||
ocrProvider: Mapped[str | None] = mapped_column(String(64))
|
||||
ocrModel: Mapped[str | None] = mapped_column(String(128))
|
||||
|
||||
# Rescue
|
||||
rescueMode: Mapped[str | None] = mapped_column(String(32), comment="off/tier1/auto")
|
||||
rescueApplied: Mapped[bool] = mapped_column(Boolean, default=False, comment="是否执行 rescue")
|
||||
|
||||
# 结果汇总
|
||||
totalScore: Mapped[float | None] = mapped_column(Numeric(10, 2))
|
||||
passedCount: Mapped[int | None] = mapped_column(Integer)
|
||||
failedCount: Mapped[int | None] = mapped_column(Integer)
|
||||
skippedCount: Mapped[int | None] = mapped_column(Integer)
|
||||
resultStatus: Mapped[str | None] = mapped_column(String(32), comment="pass/fail/partial/error")
|
||||
|
||||
# 时间
|
||||
startedAt: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
finishedAt: Mapped[datetime | None] = mapped_column(DateTime(timezone=True))
|
||||
@@ -0,0 +1,44 @@
|
||||
"""LeAudit 域文档镜像模型 —— leaudit_documents 表。
|
||||
|
||||
通过 biz_document_id 关联业务 documents 表,不复制业务字段。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy import BigInteger, String, ForeignKey
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from fastapi_common.fastapi_common_web.models import BaseModel
|
||||
|
||||
|
||||
class LeauditDocument(BaseModel):
|
||||
"""LeAudit 文档镜像表。"""
|
||||
|
||||
__tablename__ = "leaudit_documents"
|
||||
|
||||
Id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||
bizDocumentId: Mapped[int] = mapped_column(BigInteger, unique=True, comment="关联业务 documents.id")
|
||||
typeId: Mapped[int | None] = mapped_column(BigInteger, comment="文档类型ID")
|
||||
processingStatus: Mapped[str | None] = mapped_column(String(64), default="waiting", comment="waiting/processing/completed/failed")
|
||||
currentRunId: Mapped[int | None] = mapped_column(BigInteger, comment="最新有效 run id")
|
||||
|
||||
@classmethod
|
||||
async def get_by_biz_id(cls, session: AsyncSession, bizDocumentId: int) -> "LeauditDocument | None":
|
||||
"""按业务文档 ID 查询。"""
|
||||
from sqlalchemy import select
|
||||
return await session.scalar(select(cls).where(cls.bizDocumentId == bizDocumentId))
|
||||
|
||||
@classmethod
|
||||
async def upsert_by_biz_id(cls, session: AsyncSession, bizDocumentId: int, **fields) -> "LeauditDocument":
|
||||
"""按业务文档 ID 创建或更新。"""
|
||||
from sqlalchemy import select
|
||||
doc = await session.scalar(select(cls).where(cls.bizDocumentId == bizDocumentId))
|
||||
if doc is None:
|
||||
doc = cls(bizDocumentId=bizDocumentId, **fields)
|
||||
session.add(doc)
|
||||
else:
|
||||
for k, v in fields.items():
|
||||
setattr(doc, k, v)
|
||||
await session.flush()
|
||||
return doc
|
||||
@@ -0,0 +1,28 @@
|
||||
"""LeAudit 文档文件模型 —— leaudit_document_files 表。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy import BigInteger, Boolean, String
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from fastapi_common.fastapi_common_web.models import BaseModel
|
||||
|
||||
|
||||
class LeauditDocumentFile(BaseModel):
|
||||
"""文档文件表。"""
|
||||
|
||||
__tablename__ = "leaudit_document_files"
|
||||
|
||||
Id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True)
|
||||
documentId: Mapped[int] = mapped_column(BigInteger, comment="关联 leaudit_documents.id")
|
||||
fileRole: Mapped[str] = mapped_column(String(64), comment="original/converted_pdf/merged_pdf/temp_input")
|
||||
fileName: Mapped[str] = mapped_column(String(512), comment="文件名")
|
||||
fileExt: Mapped[str | None] = mapped_column(String(32), comment="扩展名")
|
||||
mimeType: Mapped[str | None] = mapped_column(String(128), comment="MIME")
|
||||
fileSize: Mapped[int | None] = mapped_column(BigInteger, comment="文件大小")
|
||||
sha256: Mapped[str | None] = mapped_column(String(64), comment="SHA256")
|
||||
localPath: Mapped[str | None] = mapped_column(String(1024), comment="本地路径")
|
||||
ossUrl: Mapped[str | None] = mapped_column(String(2048), comment="OSS 地址")
|
||||
storageProvider: Mapped[str | None] = mapped_column(String(32), comment="oss/minio/local")
|
||||
isActive: Mapped[bool] = mapped_column(Boolean, default=True, comment="当前生效文件")
|
||||
createdBy: Mapped[int | None] = mapped_column(BigInteger, comment="上传人")
|
||||
Reference in New Issue
Block a user