feat: 添加数据库迁移脚本并更新Dockerfile配置

- 在Dockerfile中添加alembic配置文件和目录的复制指令
- 更新alembic/env.py注册新的模块模型:events、workflows、writebacks
- 生成完整的初始数据库schema迁移脚本,包含以下表:
  - approval_requests, attendance_records, audit_logs, domain_events
  - expenses, feishu_event_receipts, fund_accounts, legacy_sync_runs
  - official_writeback_runs, performance_metrics, policies, procurements
  - projects, report_push_runs, risk_event_actions, risk_events
  - standards, suppliers, work_reports, work_tasks, workflow_actions
  - workflow_instances等21个数据表结构定义
- 在API路由器中添加新模块的路由:events、workflows、writebacks、observability
```
This commit is contained in:
2026-07-08 14:08:03 +08:00
parent 92f490b97e
commit 19e59e83cc
59 changed files with 3271 additions and 247 deletions

View File

@@ -0,0 +1 @@

View File

@@ -0,0 +1,27 @@
from enum import StrEnum
class ObservabilityKey(StrEnum):
STATUS = "status"
CHECKS = "checks"
METRICS = "metrics"
DATABASE = "database"
REDIS = "redis"
EVENTS = "events"
WORKFLOWS = "workflows"
WRITEBACKS = "writebacks"
class ObservabilityStatus(StrEnum):
OK = "ok"
DEGRADED = "degraded"
SKIPPED = "skipped"
ERROR = "error"
class ObservabilityMetricKey(StrEnum):
ERROR = "error"
PENDING = "pending"
FAILED = "failed"
RUNNING = "running"
DISABLED = "disabled"

View File

@@ -0,0 +1,30 @@
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from app.core.database import get_db
from app.core.security import require_api_key
from app.modules.observability.service import ObservabilityService
router = APIRouter()
@router.get("/health/live")
def live(
db: Session = Depends(get_db),
) -> dict:
_ = db
return ObservabilityService(db).live()
@router.get("/health/ready")
def ready(
db: Session = Depends(get_db),
) -> dict:
return ObservabilityService(db).ready()
@router.get("/metrics", dependencies=[Depends(require_api_key)])
def metrics(
db: Session = Depends(get_db),
) -> dict:
return ObservabilityService(db).metrics()

View File

@@ -0,0 +1,114 @@
from typing import Any
from sqlalchemy import text
from sqlalchemy.orm import Session
from app.core.config import get_settings
from app.modules.events.constants import EventStatus
from app.modules.events.service import EventService
from app.modules.observability.constants import (
ObservabilityKey,
ObservabilityMetricKey,
ObservabilityStatus,
)
from app.modules.workflows.constants import WorkflowStatus
from app.modules.workflows.service import WorkflowService
from app.modules.writebacks.constants import WritebackStatus
from app.modules.writebacks.service import WritebackService
class ObservabilityService:
"""Build health, readiness, and JSON metrics for V3 operations."""
def __init__(self, db: Session):
self.db = db
def live(self) -> dict[str, str]:
return {ObservabilityKey.STATUS: ObservabilityStatus.OK}
def ready(self) -> dict[str, Any]:
checks = {
ObservabilityKey.DATABASE: self._database_check(),
ObservabilityKey.REDIS: self._redis_check(),
ObservabilityKey.EVENTS: self._events_check(),
ObservabilityKey.WORKFLOWS: self._workflows_check(),
ObservabilityKey.WRITEBACKS: self._writebacks_check(),
}
degraded = any(
item[ObservabilityKey.STATUS]
in {ObservabilityStatus.DEGRADED, ObservabilityStatus.ERROR}
for item in checks.values()
)
return {
ObservabilityKey.STATUS: (
ObservabilityStatus.DEGRADED if degraded else ObservabilityStatus.OK
),
ObservabilityKey.CHECKS: checks,
}
def metrics(self) -> dict[str, Any]:
return {
ObservabilityKey.METRICS: {
ObservabilityKey.EVENTS: EventService(self.db).count_by_status(),
ObservabilityKey.WORKFLOWS: WorkflowService(self.db).count_by_status(),
ObservabilityKey.WRITEBACKS: WritebackService(self.db).count_by_status(),
}
}
def _database_check(self) -> dict[str, Any]:
try:
self.db.execute(text("select 1")).scalar()
except Exception as exc:
return {
ObservabilityKey.STATUS: ObservabilityStatus.ERROR,
ObservabilityMetricKey.ERROR: str(exc),
}
return {ObservabilityKey.STATUS: ObservabilityStatus.OK}
def _redis_check(self) -> dict[str, Any]:
settings = get_settings()
if not settings.task_queue_enabled:
return {ObservabilityKey.STATUS: ObservabilityStatus.SKIPPED}
try:
from redis import Redis
Redis.from_url(settings.redis_url, socket_connect_timeout=1).ping()
except Exception as exc:
return {
ObservabilityKey.STATUS: ObservabilityStatus.DEGRADED,
ObservabilityMetricKey.ERROR: str(exc),
}
return {ObservabilityKey.STATUS: ObservabilityStatus.OK}
def _events_check(self) -> dict[str, Any]:
counts = EventService(self.db).count_by_status()
failed = counts.get(EventStatus.FAILED, 0)
return {
ObservabilityKey.STATUS: (
ObservabilityStatus.DEGRADED if failed else ObservabilityStatus.OK
),
ObservabilityMetricKey.PENDING: counts.get(EventStatus.PENDING, 0),
ObservabilityMetricKey.FAILED: failed,
}
def _workflows_check(self) -> dict[str, Any]:
counts = WorkflowService(self.db).count_by_status()
failed = counts.get(WorkflowStatus.FAILED, 0)
return {
ObservabilityKey.STATUS: (
ObservabilityStatus.DEGRADED if failed else ObservabilityStatus.OK
),
ObservabilityMetricKey.RUNNING: counts.get(WorkflowStatus.RUNNING, 0),
ObservabilityMetricKey.FAILED: failed,
}
def _writebacks_check(self) -> dict[str, Any]:
counts = WritebackService(self.db).count_by_status()
failed = counts.get(WritebackStatus.FAILED, 0)
return {
ObservabilityKey.STATUS: (
ObservabilityStatus.DEGRADED if failed else ObservabilityStatus.OK
),
ObservabilityMetricKey.DISABLED: counts.get(WritebackStatus.DISABLED, 0),
ObservabilityMetricKey.FAILED: failed,
}