refactor(core): 重构核心模块结构并更新导入路径

- 将配置相关的设置从 app.core.config 移除
- 将常量定义从 app.core.constants 移除
- 将数据库相关功能从 app.core.database 移除
- 将基础数据库模型从 app.core.db_base 移除
- 将敏感信息掩码功能从 app.core.masking 移除
- 将中间件定义从 app.core.middleware 移除
- 将操作保护功能从 app.core.operation_guard 移除
- 将分页工具从 app.core.pagination 移除
- 将请求上下文管理从 app.core.request_context 移除
- 将调度器功能从 app.core.scheduler 移除
- 将安全认证逻辑从 app.core.security 移除
- 将任务队列相关功能从 app.core.task_queue 移除
- 将时间工具从 app.core.time 移除
- 更新 alembic 配置中的 Base 模型导入路径
- 更新各模块中对重构后组件的引用路径
```
This commit is contained in:
2026-07-09 17:41:16 +08:00
parent 0cda45238a
commit dc8605ce3f
47 changed files with 129 additions and 54 deletions

View File

@@ -4,7 +4,7 @@ from alembic import context
from sqlalchemy import engine_from_config, pool from sqlalchemy import engine_from_config, pool
from app.core.config import get_settings from app.core.config import get_settings
from app.core.db_base import Base from app.core.database import Base
from app.modules.ai_memory import models as ai_memory_models from app.modules.ai_memory import models as ai_memory_models
from app.modules.audit import models as audit_models from app.modules.audit import models as audit_models
from app.modules.business import models as business_models from app.modules.business import models as business_models

View File

@@ -0,0 +1 @@
"""Background scheduler and queue helpers."""

View File

@@ -33,7 +33,7 @@ def create_scheduler(app: FastAPI | None = None) -> Any:
from apscheduler.schedulers.background import BackgroundScheduler from apscheduler.schedulers.background import BackgroundScheduler
from app.core.database import SessionLocal from app.core.database import SessionLocal
from app.core.task_queue import ( from app.core.background.task_queue import (
enqueue_daily_brief_push, enqueue_daily_brief_push,
enqueue_event_dispatch, enqueue_event_dispatch,
enqueue_legacy_project_sync, enqueue_legacy_project_sync,

View File

@@ -0,0 +1,3 @@
from app.core.config.settings import Settings, get_settings
__all__ = ["Settings", "get_settings"]

View File

@@ -0,0 +1,23 @@
from app.core.constants.values import (
BEARER_TOKEN_TEMPLATE,
DEFAULT_MODEL_PROVIDER,
DEFAULT_OPENCLAW_ACTION_JSON,
ActorValue,
ApiResponseKey,
ApiStatus,
ConfigErrorDetail,
HttpHeader,
SecurityErrorDetail,
)
__all__ = [
"ActorValue",
"ApiResponseKey",
"ApiStatus",
"BEARER_TOKEN_TEMPLATE",
"ConfigErrorDetail",
"DEFAULT_MODEL_PROVIDER",
"DEFAULT_OPENCLAW_ACTION_JSON",
"HttpHeader",
"SecurityErrorDetail",
]

View File

@@ -0,0 +1,30 @@
from typing import Any
from app.core.database.base import Base
__all__ = [
"Base",
"LegacySessionLocal",
"SessionLocal",
"engine",
"get_db",
"get_legacy_db",
"legacy_engine",
]
_SESSION_EXPORTS = {
"LegacySessionLocal",
"SessionLocal",
"engine",
"get_db",
"get_legacy_db",
"legacy_engine",
}
def __getattr__(name: str) -> Any:
if name in _SESSION_EXPORTS:
from app.core.database import session
return getattr(session, name)
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")

View File

@@ -4,7 +4,8 @@ from sqlalchemy import create_engine
from sqlalchemy.orm import Session, sessionmaker from sqlalchemy.orm import Session, sessionmaker
from app.core.config import get_settings from app.core.config import get_settings
from app.core.db_base import Base
from .base import Base
settings = get_settings() settings = get_settings()

View File

@@ -0,0 +1 @@
"""HTTP middleware, masking, pagination, and request context helpers."""

View File

@@ -4,7 +4,7 @@ from collections.abc import Callable
from fastapi import Request, Response from fastapi import Request, Response
from app.core.constants import HttpHeader from app.core.constants import HttpHeader
from app.core.request_context import reset_request_id, set_request_id from app.core.http.request_context import reset_request_id, set_request_id
async def request_id_middleware(request: Request, call_next: Callable) -> Response: async def request_id_middleware(request: Request, call_next: Callable) -> Response:

View File

@@ -0,0 +1,13 @@
from app.core.security.api_keys import ApiPrincipal, require_api_key, require_audit_api_key
from app.core.security.operation_guard import (
READ_ONLY_OPERATION_DISABLED,
require_operations_enabled,
)
__all__ = [
"ApiPrincipal",
"READ_ONLY_OPERATION_DISABLED",
"require_api_key",
"require_audit_api_key",
"require_operations_enabled",
]

View File

@@ -0,0 +1 @@
"""Core utility helpers."""

View File

@@ -3,8 +3,8 @@ from fastapi.middleware.cors import CORSMiddleware
from app.api.router import api_router from app.api.router import api_router
from app.core.config import get_settings from app.core.config import get_settings
from app.core.middleware import request_id_middleware from app.core.http.middleware import request_id_middleware
from app.core.scheduler import attach_scheduler from app.core.background.scheduler import attach_scheduler
def _allow_cors_credentials(cors_origins: list[str]) -> bool: def _allow_cors_credentials(cors_origins: list[str]) -> bool:

View File

@@ -2,8 +2,7 @@ from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.database import get_db from app.core.database import get_db
from app.core.operation_guard import require_operations_enabled from app.core.security import ApiPrincipal, require_api_key, require_operations_enabled
from app.core.security import ApiPrincipal, require_api_key
from app.modules.audit.constants import AuditSource from app.modules.audit.constants import AuditSource
from app.modules.ai_agent.schemas import ( from app.modules.ai_agent.schemas import (
AIAskRequest, AIAskRequest,

View File

@@ -4,8 +4,8 @@ from sqlalchemy import JSON, DateTime, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from app.core.constants import ActorValue from app.core.constants import ActorValue
from app.core.db_base import Base from app.core.database import Base
from app.core.time import utc_now from app.core.utils.time import utc_now
from app.modules.ai_memory.constants import AIMemoryScope, AIMemorySource, AIMemoryStatus from app.modules.ai_memory.constants import AIMemoryScope, AIMemorySource, AIMemoryStatus

View File

@@ -5,8 +5,8 @@ from sqlalchemy.orm import Session
from app.core.config import get_settings from app.core.config import get_settings
from app.core.constants import ActorValue from app.core.constants import ActorValue
from app.core.pagination import bounded_limit from app.core.http.pagination import bounded_limit
from app.core.time import utc_now from app.core.utils.time import utc_now
from app.modules.ai_memory.constants import ( from app.modules.ai_memory.constants import (
AI_MEMORY_CODE_PREFIX, AI_MEMORY_CODE_PREFIX,
AI_MEMORY_MAX_CONTENT_LENGTH, AI_MEMORY_MAX_CONTENT_LENGTH,

View File

@@ -4,8 +4,8 @@ from sqlalchemy import DateTime, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from app.core.constants import ActorValue from app.core.constants import ActorValue
from app.core.db_base import Base from app.core.database import Base
from app.core.time import utc_now from app.core.utils.time import utc_now
from app.modules.audit.constants import AuditRiskLevel, AuditSource, AuditStatus from app.modules.audit.constants import AuditRiskLevel, AuditSource, AuditStatus

View File

@@ -4,8 +4,8 @@ from typing import Any
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.pagination import bounded_limit from app.core.http.pagination import bounded_limit
from app.core.request_context import get_request_id from app.core.http.request_context import get_request_id
from app.modules.audit.constants import AUDIT_REDACTED_VALUE, AUDIT_SENSITIVE_KEYS from app.modules.audit.constants import AUDIT_REDACTED_VALUE, AUDIT_SENSITIVE_KEYS
from app.modules.audit.models import AuditLog from app.modules.audit.models import AuditLog
from app.modules.audit.schemas import AuditLogCreate from app.modules.audit.schemas import AuditLogCreate

View File

@@ -5,8 +5,8 @@ from sqlalchemy import JSON, Date, DateTime, Integer, Numeric, String, Text
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from app.core.constants import ActorValue from app.core.constants import ActorValue
from app.core.db_base import Base from app.core.database import Base
from app.core.time import utc_now from app.core.utils.time import utc_now
from app.modules.business.constants import ( from app.modules.business.constants import (
AccountType, AccountType,
PriorityValue, PriorityValue,

View File

@@ -3,7 +3,7 @@ from fastapi import status as http_status
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.database import get_db from app.core.database import get_db
from app.core.masking import mask_configured from app.core.http.masking import mask_configured
from app.core.security import require_api_key from app.core.security import require_api_key
from app.modules.business.constants import BusinessField, BusinessResponseKey from app.modules.business.constants import BusinessField, BusinessResponseKey
from app.modules.business.registry import supported_domain_values from app.modules.business.registry import supported_domain_values

View File

@@ -10,7 +10,7 @@ from sqlalchemy import Select, func, select
from sqlalchemy.sql.schema import Column from sqlalchemy.sql.schema import Column
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.pagination import bounded_limit, bounded_offset from app.core.http.pagination import bounded_limit, bounded_offset
from app.modules.business.registry import get_domain_model, get_writable_fields from app.modules.business.registry import get_domain_model, get_writable_fields
from app.modules.business.constants import ( from app.modules.business.constants import (
INVALID_FIELD_VALUE_TEMPLATE, INVALID_FIELD_VALUE_TEMPLATE,

View File

@@ -2,7 +2,7 @@ from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.database import get_db from app.core.database import get_db
from app.core.masking import mask_configured from app.core.http.masking import mask_configured
from app.core.security import ApiPrincipal, require_api_key from app.core.security import ApiPrincipal, require_api_key
from app.modules.dashboard.service import DashboardService from app.modules.dashboard.service import DashboardService

View File

@@ -4,8 +4,8 @@ from sqlalchemy import JSON, DateTime, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from app.core.constants import ActorValue from app.core.constants import ActorValue
from app.core.db_base import Base from app.core.database import Base
from app.core.time import utc_now from app.core.utils.time import utc_now
from app.modules.events.constants import EventSource, EventStatus from app.modules.events.constants import EventSource, EventStatus

View File

@@ -8,8 +8,8 @@ from sqlalchemy.orm import Session
from app.core.config import get_settings from app.core.config import get_settings
from app.core.constants import ActorValue from app.core.constants import ActorValue
from app.core.pagination import bounded_limit from app.core.http.pagination import bounded_limit
from app.core.time import utc_now from app.core.utils.time import utc_now
from app.modules.audit.constants import ( from app.modules.audit.constants import (
AuditAction, AuditAction,
AuditRiskLevel, AuditRiskLevel,
@@ -189,13 +189,15 @@ class EventService:
def retry_event(self, event_id: str, actor: str = ActorValue.API) -> DomainEvent: def retry_event(self, event_id: str, actor: str = ActorValue.API) -> DomainEvent:
record = self.get_event(event_id) record = self.get_event(event_id)
if record.status == EventStatus.PROCESSED or not self._can_attempt(record): if record.status == EventStatus.PROCESSED:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_409_CONFLICT, status_code=status.HTTP_409_CONFLICT,
detail=EventErrorDetail.EVENT_NOT_RETRYABLE, detail=EventErrorDetail.EVENT_NOT_RETRYABLE,
) )
record.status = EventStatus.PENDING record.status = EventStatus.PENDING
record.actor = actor record.actor = actor
record.attempts = 0
record.max_attempts = record.max_attempts or get_settings().event_dispatch_max_attempts
record.last_error = None record.last_error = None
record.locked_by = None record.locked_by = None
record.locked_until = None record.locked_until = None

View File

@@ -3,8 +3,8 @@ from datetime import datetime
from sqlalchemy import DateTime, Integer, String from sqlalchemy import DateTime, Integer, String
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from app.core.db_base import Base from app.core.database import Base
from app.core.time import utc_now from app.core.utils.time import utc_now
class FeishuEventReceipt(Base): class FeishuEventReceipt(Base):

View File

@@ -1,11 +1,10 @@
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.background.task_queue import enqueue_legacy_project_sync, enqueue_legacy_task_sync
from app.core.database import get_db from app.core.database import get_db
from app.core.masking import mask_configured from app.core.http.masking import mask_configured
from app.core.operation_guard import require_operations_enabled from app.core.security import ApiPrincipal, require_api_key, require_operations_enabled
from app.core.security import ApiPrincipal, require_api_key
from app.core.task_queue import enqueue_legacy_project_sync, enqueue_legacy_task_sync
from app.modules.legacy_mysql.schemas import ( from app.modules.legacy_mysql.schemas import (
LegacyProjectSyncRequest, LegacyProjectSyncRequest,
LegacyProjectSyncResult, LegacyProjectSyncResult,

View File

@@ -11,8 +11,8 @@ from sqlalchemy.orm import Session
from app.core.constants import ActorValue, ApiStatus from app.core.constants import ActorValue, ApiStatus
from app.core.config import get_settings from app.core.config import get_settings
from app.core.database import legacy_engine from app.core.database import legacy_engine
from app.core.pagination import bounded_limit from app.core.http.pagination import bounded_limit
from app.core.time import utc_now from app.core.utils.time import utc_now
from app.modules.audit.constants import AuditAction, AuditRiskLevel, AuditSource, AuditStatus from app.modules.audit.constants import AuditAction, AuditRiskLevel, AuditSource, AuditStatus
from app.modules.audit.schemas import AuditLogCreate from app.modules.audit.schemas import AuditLogCreate
from app.modules.audit.service import AuditService from app.modules.audit.service import AuditService

View File

@@ -3,8 +3,8 @@ from datetime import datetime
from sqlalchemy import DateTime, Integer, String from sqlalchemy import DateTime, Integer, String
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from app.core.db_base import Base from app.core.database import Base
from app.core.time import utc_now from app.core.utils.time import utc_now
class SystemHeartbeat(Base): class SystemHeartbeat(Base):

View File

@@ -6,7 +6,7 @@ from sqlalchemy.orm import Session
from app.core.config import get_settings from app.core.config import get_settings
from app.core.constants import ActorValue from app.core.constants import ActorValue
from app.core.time import utc_now from app.core.utils.time import utc_now
from app.modules.ai_memory.service import AIMemoryService from app.modules.ai_memory.service import AIMemoryService
from app.modules.audit.constants import ( from app.modules.audit.constants import (
AuditAction, AuditAction,

View File

@@ -3,16 +3,15 @@ from datetime import date
from fastapi import APIRouter, Depends from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.background.task_queue import enqueue_daily_brief_push, enqueue_project_weekly_push
from app.core.database import get_db from app.core.database import get_db
from app.core.operation_guard import require_operations_enabled from app.core.security import ApiPrincipal, require_api_key, require_operations_enabled
from app.core.security import ApiPrincipal, require_api_key from app.modules.reports.constants import ReportPushKey
from app.core.task_queue import enqueue_daily_brief_push, enqueue_project_weekly_push
from app.modules.reports.schemas import ( from app.modules.reports.schemas import (
PushReportRequest, PushReportRequest,
ReportResponse, ReportResponse,
WorkReportGenerateRequest, WorkReportGenerateRequest,
) )
from app.modules.reports.constants import ReportPushKey
from app.modules.reports.service import ReportService from app.modules.reports.service import ReportService
router = APIRouter(dependencies=[Depends(require_api_key)]) router = APIRouter(dependencies=[Depends(require_api_key)])

View File

@@ -6,8 +6,8 @@ from sqlalchemy import func, or_, select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.constants import ActorValue from app.core.constants import ActorValue
from app.core.pagination import bounded_limit from app.core.http.pagination import bounded_limit
from app.core.time import utc_now from app.core.utils.time import utc_now
from app.modules.audit.constants import AuditAction, AuditSource, AuditTargetType from app.modules.audit.constants import AuditAction, AuditSource, AuditTargetType
from app.modules.audit.schemas import AuditLogCreate from app.modules.audit.schemas import AuditLogCreate
from app.modules.audit.service import AuditService from app.modules.audit.service import AuditService

View File

@@ -1,10 +1,9 @@
from fastapi import APIRouter, Depends, Query from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.background.task_queue import enqueue_risk_event_generation
from app.core.database import get_db from app.core.database import get_db
from app.core.operation_guard import require_operations_enabled from app.core.security import ApiPrincipal, require_api_key, require_operations_enabled
from app.core.security import ApiPrincipal, require_api_key
from app.core.task_queue import enqueue_risk_event_generation
from app.modules.risk.constants import RiskEventActionKey, RiskGenerationResultKey from app.modules.risk.constants import RiskEventActionKey, RiskGenerationResultKey
from app.modules.risk.schemas import ( from app.modules.risk.schemas import (
RiskAssignRequest, RiskAssignRequest,

View File

@@ -6,8 +6,8 @@ from sqlalchemy import select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.constants import ActorValue from app.core.constants import ActorValue
from app.core.pagination import bounded_limit from app.core.http.pagination import bounded_limit
from app.core.time import utc_now from app.core.utils.time import utc_now
from app.modules.audit.constants import ( from app.modules.audit.constants import (
AuditAction, AuditAction,
AuditRiskLevel, AuditRiskLevel,

View File

@@ -4,8 +4,8 @@ from sqlalchemy import JSON, DateTime, Integer, String
from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.orm import Mapped, mapped_column
from app.core.constants import ActorValue from app.core.constants import ActorValue
from app.core.db_base import Base from app.core.database import Base
from app.core.time import utc_now from app.core.utils.time import utc_now
from app.modules.workflows.constants import WorkflowStatus from app.modules.workflows.constants import WorkflowStatus

View File

@@ -5,8 +5,8 @@ from sqlalchemy import func, select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from app.core.constants import ActorValue from app.core.constants import ActorValue
from app.core.pagination import bounded_limit from app.core.http.pagination import bounded_limit
from app.core.time import utc_now from app.core.utils.time import utc_now
from app.modules.business.service import serialize_model from app.modules.business.service import serialize_model
from app.modules.workflows.constants import ( from app.modules.workflows.constants import (
WORKFLOW_ACTION_CODE_PREFIX, WORKFLOW_ACTION_CODE_PREFIX,

View File

@@ -1,6 +1,6 @@
from time import sleep from time import sleep
from app.core.scheduler import create_scheduler from app.core.background.scheduler import create_scheduler
def main() -> None: def main() -> None:

View File

@@ -43,6 +43,7 @@ services:
environment: environment:
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-company_ai}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@db:5432/${POSTGRES_DB:-company_ai} DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-company_ai}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@db:5432/${POSTGRES_DB:-company_ai}
REDIS_URL: redis://redis:6379/0 REDIS_URL: redis://redis:6379/0
SCHEDULER_ENABLED: "false"
ports: ports:
- "8010:8010" - "8010:8010"
depends_on: depends_on:
@@ -72,6 +73,7 @@ services:
DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-company_ai}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@db:5432/${POSTGRES_DB:-company_ai} DATABASE_URL: postgresql+psycopg://${POSTGRES_USER:-company_ai}:${POSTGRES_PASSWORD:?POSTGRES_PASSWORD is required}@db:5432/${POSTGRES_DB:-company_ai}
REDIS_URL: redis://redis:6379/0 REDIS_URL: redis://redis:6379/0
TASK_QUEUE_ENABLED: "true" TASK_QUEUE_ENABLED: "true"
SCHEDULER_ENABLED: "false"
command: ["celery", "-A", "app.tasks.celery_app", "worker", "--loglevel=info"] command: ["celery", "-A", "app.tasks.celery_app", "worker", "--loglevel=info"]
depends_on: depends_on:
db: db:

View File

@@ -31,7 +31,7 @@ from fastapi.testclient import TestClient
from app.core.config import Settings, get_settings from app.core.config import Settings, get_settings
from app.core.database import Base, SessionLocal, engine from app.core.database import Base, SessionLocal, engine
from app.core.pagination import bounded_limit, bounded_offset from app.core.http.pagination import bounded_limit, bounded_offset
from app.core.security import require_api_key, require_audit_api_key from app.core.security import require_api_key, require_audit_api_key
from app.main import _allow_cors_credentials, app from app.main import _allow_cors_credentials, app
from app.modules.audit.constants import AUDIT_REDACTED_VALUE from app.modules.audit.constants import AUDIT_REDACTED_VALUE
@@ -268,6 +268,7 @@ def test_v3_event_retry_and_dispatch_pending_route() -> None:
) )
event.status = EventStatus.FAILED event.status = EventStatus.FAILED
event.last_error = "transient" event.last_error = "transient"
event.attempts = event.max_attempts
db.commit() db.commit()
event_id = event.event_id event_id = event.event_id
finally: finally:
@@ -276,6 +277,7 @@ def test_v3_event_retry_and_dispatch_pending_route() -> None:
retry_response = client.post(f"/api/v1/events/{event_id}/retry", headers=headers) retry_response = client.post(f"/api/v1/events/{event_id}/retry", headers=headers)
assert retry_response.status_code == 200 assert retry_response.status_code == 200
assert retry_response.json()["event"]["status"] == EventStatus.PENDING assert retry_response.json()["event"]["status"] == EventStatus.PENDING
assert retry_response.json()["event"]["attempts"] == 0
dispatch_response = client.post("/api/v1/events/dispatch-pending", headers=headers) dispatch_response = client.post("/api/v1/events/dispatch-pending", headers=headers)
assert dispatch_response.status_code == 200 assert dispatch_response.status_code == 200