1. 为渠道账户ID查询添加最小长度校验,统一分析模块常量引用 2. 新增扫码登录向导端点,完善文档说明 3. 优化配对统计接口,移除无效参数 4. 为出站箱接口添加批量上限与202状态码 5. 新增测试用例、访问规则、配额等模块的查询与校验参数 6. 新增适配器健康批量查询、健康检查触发接口 7. 统一告警、审计日志的错误处理方式 8. 新增插件配置账户ID支持,优化批量操作响应 9. 新增环境健康批量查询、Webhook限流与参数校验 10. 完善会话管理、审计日志的参数与文档说明 11. 修复导入模块的校验错误处理逻辑
553 lines
21 KiB
Python
553 lines
21 KiB
Python
"""Environment 子域 Router。
|
||
|
||
外部系统限界上下文的环境管理 API,覆盖环境 CRUD / 默认 / 启停 / 克隆 /
|
||
连通性测试 / 差异 / 导出 / 健康状态 / 统计 / 跨系统查询 /
|
||
批量克隆 / 批量启停。所有端点通过 ``create_use_cases_from_db`` 装配 use_cases,
|
||
经 ``environment_service`` 端口调用用例。
|
||
|
||
路径顺序约束:静态路径(``/diff`` / ``/active`` / ``/by-key`` / ``/stats`` /
|
||
``/cross-system`` / ``/batch-test-connectivity`` / ``/batch-clone`` /
|
||
``/batch-enabled`` / ``/batch-health``)必须在 ``/{env_id}`` 前声明,避免被路径参数捕获。
|
||
|
||
认证策略:查询类端点使用 ``get_required_user``,写操作端点使用 ``get_admin_user``。
|
||
``export_environment`` 因 ``include_secrets=true`` 会泄露明文密钥,统一要求 ``get_admin_user``。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import json
|
||
from datetime import UTC, datetime
|
||
from typing import Any
|
||
|
||
from fastapi import APIRouter, Depends, Query
|
||
from fastapi.responses import StreamingResponse
|
||
from pydantic import BaseModel, ConfigDict, Field
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
from yuxi.external_systems.infrastructure.container import create_use_cases_from_db
|
||
from yuxi.external_systems.use_cases.dto.environment import (
|
||
BatchCloneEnvironmentsInput,
|
||
BatchEnableEnvironmentsInput,
|
||
BatchEnvironmentHealthInput,
|
||
BulkTestEnvironmentsInput,
|
||
CloneEnvironmentInput,
|
||
CreateEnvironmentInput,
|
||
CrossSystemEnvironmentsInput,
|
||
DeleteEnvironmentInput,
|
||
DiffEnvironmentsInput,
|
||
DisableEnvironmentInput,
|
||
EnableEnvironmentInput,
|
||
ExportEnvironmentInput,
|
||
GetActiveEnvironmentInput,
|
||
GetEnvironmentByKeyInput,
|
||
GetEnvironmentHealthInput,
|
||
GetEnvironmentInput,
|
||
GetEnvironmentStatsInput,
|
||
ListEnvironmentsInput,
|
||
SetDefaultEnvironmentInput,
|
||
TestEnvironmentConnectionInput,
|
||
UpdateEnvironmentInput,
|
||
)
|
||
from yuxi.storage.postgres.models_business import User
|
||
|
||
from server.utils.auth_middleware import get_admin_user, get_db, get_required_user
|
||
|
||
environment_router = APIRouter(
|
||
prefix="/environments",
|
||
tags=["external-systems-environment"],
|
||
)
|
||
|
||
|
||
# ---------------- Request Schemas ----------------
|
||
|
||
|
||
class CreateEnvironmentRequest(BaseModel):
|
||
"""创建环境请求体。字段对齐 ``CreateEnvironmentInput``。
|
||
|
||
字段长度约束对齐 ``ExternalSystemEnvironment`` ORM 列定义,在边界层拦截非法输入:
|
||
``env_key`` 对齐 String(32) 并沿用 slug 标识符格式,``name`` 对齐 String(128)。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
system_id: int
|
||
env_key: str = Field(..., min_length=1, max_length=32, pattern=r"^[a-zA-Z_][a-zA-Z0-9_-]{0,31}$")
|
||
name: str = Field(..., min_length=1, max_length=128)
|
||
connection_config: dict[str, Any] = Field(default_factory=dict)
|
||
auth_config: dict[str, Any] | None = None
|
||
is_default: bool = False
|
||
enabled: bool = True
|
||
|
||
|
||
class UpdateEnvironmentRequest(BaseModel):
|
||
"""更新环境请求体。``id`` 与 ``system_id`` 由路径/Query 提供,不在 body 中。
|
||
|
||
字段长度约束对齐 ``ExternalSystemEnvironment`` ORM 列定义,仅透传客户端显式设置的字段。
|
||
不含 ``is_default``:默认环境切换必须通过 ``PATCH /{env_id}/default`` 端点,
|
||
该端点会清除同系统其他环境的默认标记,避免违反部分唯一约束。
|
||
不含 ``enabled``:启停必须通过 ``PATCH /{env_id}/enabled`` 端点,该端点会执行
|
||
禁用环境的安全检查(清除默认标记、释放连接池、失效 Token、发布状态变更事件)。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
name: str | None = Field(default=None, min_length=1, max_length=128)
|
||
connection_config: dict[str, Any] | None = None
|
||
auth_config: dict[str, Any] | None = None
|
||
|
||
|
||
class SetEnvironmentEnabledRequest(BaseModel):
|
||
"""启停环境请求体。按 ``enabled`` 值分派 enable/disable。"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
enabled: bool
|
||
|
||
|
||
class CloneEnvironmentRequest(BaseModel):
|
||
"""克隆环境请求体。``id`` 与 ``system_id`` 由路径/Query 提供。
|
||
|
||
字段长度约束对齐 ``ExternalSystemEnvironment`` ORM 列定义,``new_env_key`` 与
|
||
``CreateEnvironmentRequest.env_key`` 保持一致格式。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
new_env_key: str = Field(..., min_length=1, max_length=32, pattern=r"^[a-zA-Z_][a-zA-Z0-9_-]{0,31}$")
|
||
new_name: str | None = Field(default=None, min_length=1, max_length=128)
|
||
|
||
|
||
class BatchTestConnectivityRequest(BaseModel):
|
||
"""批量测试连通性请求体。字段对齐 ``BulkTestEnvironmentsInput``。
|
||
|
||
``env_keys`` 限制最多 100 条,对齐 ``BatchEnvironmentHealthRequest.env_ids``
|
||
及其他批量请求体的列表上限,避免请求体过大。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
system_id: int
|
||
env_keys: list[str] | None = Field(default=None, max_length=100)
|
||
|
||
|
||
class BatchCloneEnvironmentsRequest(BaseModel):
|
||
"""批量克隆环境请求体。字段对齐 ``BatchCloneEnvironmentsInput``(不含 ``created_by``)。
|
||
|
||
``created_by`` 由 ``current_user.uid`` 填充,不在请求体中暴露。
|
||
``source_env_ids`` 限制 1-100 条,对齐 ``system_router.BatchCloneRequest``。
|
||
``new_env_key_suffix`` 限制为安全字符(字母/数字/下划线/连字符),避免拼接出非法 env_key。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
system_id: int
|
||
source_env_ids: list[int] = Field(..., min_length=1, max_length=100)
|
||
new_env_key_suffix: str = Field(..., min_length=1, max_length=32, pattern=r"^[a-zA-Z0-9_-]+$")
|
||
new_name_suffix: str | None = Field(default=None, min_length=1, max_length=128)
|
||
|
||
|
||
class BatchEnableEnvironmentsRequest(BaseModel):
|
||
"""批量启停环境请求体。字段对齐 ``BatchEnableEnvironmentsInput``(不含 ``updated_by``)。
|
||
|
||
``updated_by`` 由 ``current_user.uid`` 填充,不在请求体中暴露。
|
||
``env_ids`` 限制 1-100 条,对齐 ``system_router.BatchEnabledRequest``。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
system_id: int
|
||
env_ids: list[int] = Field(..., min_length=1, max_length=100)
|
||
enabled: bool
|
||
|
||
|
||
class BatchEnvironmentHealthRequest(BaseModel):
|
||
"""批量查询环境健康状态请求体。字段对齐 ``BatchEnvironmentHealthInput``。
|
||
|
||
``env_ids`` 为 None 时查询系统下所有环境的健康状态。
|
||
"""
|
||
|
||
model_config = ConfigDict(frozen=True)
|
||
|
||
system_id: int
|
||
env_ids: list[int] | None = Field(default=None, max_length=100)
|
||
|
||
|
||
# ---------------- Endpoints ----------------
|
||
|
||
|
||
@environment_router.get("/diff", response_model=dict)
|
||
async def diff_environments(
|
||
system_id: int = Query(...),
|
||
source_env_key: str = Query(..., max_length=32),
|
||
target_env_key: str = Query(..., max_length=32),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""对比两个环境的配置差异。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = DiffEnvironmentsInput(
|
||
system_id=system_id,
|
||
source_env_key=source_env_key,
|
||
target_env_key=target_env_key,
|
||
)
|
||
output = await use_cases.environment_service.diff_environments(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.get("", response_model=dict)
|
||
async def list_environments(
|
||
system_id: int = Query(...),
|
||
env_key: str | None = Query(None, max_length=32),
|
||
enabled: bool | None = Query(None),
|
||
keyword: str | None = Query(None, max_length=128),
|
||
limit: int = Query(100, ge=1, le=500),
|
||
offset: int = Query(0, ge=0),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""分页列出指定外部系统下的环境。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = ListEnvironmentsInput(
|
||
system_id=system_id,
|
||
env_key=env_key,
|
||
enabled=enabled,
|
||
keyword=keyword,
|
||
limit=limit,
|
||
offset=offset,
|
||
)
|
||
output = await use_cases.environment_service.list_environments(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.post("", response_model=dict)
|
||
async def create_environment(
|
||
body: CreateEnvironmentRequest,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""创建环境。``created_by`` 由 ``current_user.uid`` 填充。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = CreateEnvironmentInput(**body.model_dump(), created_by=current_user.uid)
|
||
output = await use_cases.environment_service.create_environment(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
# ========== 静态路径端点(必须在 /{env_id} 之前声明) ==========
|
||
|
||
|
||
@environment_router.get("/active", response_model=dict)
|
||
async def get_active_environment(
|
||
system_id: int = Query(...),
|
||
env_key: str | None = Query(None, max_length=32),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""获取当前生效的环境配置。
|
||
|
||
指定 ``env_key`` 返回对应环境,否则返回默认环境,都没有返回 ``data=null``。
|
||
"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = GetActiveEnvironmentInput(system_id=system_id, env_key=env_key)
|
||
output = await use_cases.environment_service.get_active_environment(input_dto)
|
||
return {"success": True, "data": output.model_dump() if output else None}
|
||
|
||
|
||
@environment_router.get("/by-key", response_model=dict)
|
||
async def get_environment_by_key(
|
||
system_id: int = Query(...),
|
||
env_key: str = Query(..., max_length=32),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""按 ``system_id`` + ``env_key`` 获取环境详情,不存在时抛 404。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = GetEnvironmentByKeyInput(system_id=system_id, env_key=env_key)
|
||
output = await use_cases.environment_service.get_environment_by_key(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.get("/stats", response_model=dict)
|
||
async def get_environment_stats(
|
||
system_id: int = Query(...),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""统计指定系统下的环境数量(按启用状态、默认标记分组)。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = GetEnvironmentStatsInput(system_id=system_id)
|
||
output = await use_cases.environment_service.get_environment_stats(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.get("/cross-system", response_model=dict)
|
||
async def list_cross_system_environments(
|
||
env_key: str = Query(..., max_length=32),
|
||
limit: int = Query(100, ge=1, le=500),
|
||
offset: int = Query(0, ge=0),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""跨系统查询同 ``env_key`` 的环境列表。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = CrossSystemEnvironmentsInput(
|
||
env_key=env_key,
|
||
limit=limit,
|
||
offset=offset,
|
||
)
|
||
output = await use_cases.environment_service.list_cross_system_environments(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.post("/batch-test-connectivity", response_model=dict)
|
||
async def batch_test_environment_connectivity(
|
||
body: BatchTestConnectivityRequest,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""批量测试环境连通性(并发上限 5)。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = BulkTestEnvironmentsInput(
|
||
system_id=body.system_id,
|
||
env_keys=body.env_keys,
|
||
)
|
||
output = await use_cases.environment_service.bulk_test_environments(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.post("/batch-clone", response_model=dict)
|
||
async def batch_clone_environments(
|
||
body: BatchCloneEnvironmentsRequest,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""批量克隆环境配置。``created_by`` 由 ``current_user.uid`` 填充。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = BatchCloneEnvironmentsInput(
|
||
system_id=body.system_id,
|
||
source_env_ids=body.source_env_ids,
|
||
new_env_key_suffix=body.new_env_key_suffix,
|
||
new_name_suffix=body.new_name_suffix,
|
||
created_by=current_user.uid,
|
||
)
|
||
output = await use_cases.environment_service.batch_clone_environments(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.post("/batch-enabled", response_model=dict)
|
||
async def batch_set_environments_enabled(
|
||
body: BatchEnableEnvironmentsRequest,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""批量启用或禁用多个环境。``updated_by`` 由 ``current_user.uid`` 填充。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = BatchEnableEnvironmentsInput(
|
||
system_id=body.system_id,
|
||
env_ids=body.env_ids,
|
||
enabled=body.enabled,
|
||
updated_by=current_user.uid,
|
||
)
|
||
output = await use_cases.environment_service.batch_set_environments_enabled(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.post("/batch-health", response_model=dict)
|
||
async def batch_get_environment_health(
|
||
body: BatchEnvironmentHealthRequest,
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""批量查询环境健康状态(避免前端逐条请求健康接口造成请求风暴)。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = BatchEnvironmentHealthInput(
|
||
system_id=body.system_id,
|
||
env_ids=body.env_ids,
|
||
)
|
||
output = await use_cases.environment_service.batch_get_environment_health(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.get("/{env_id}", response_model=dict)
|
||
async def get_environment(
|
||
env_id: int,
|
||
system_id: int = Query(...),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""获取环境详情。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = GetEnvironmentInput(id=env_id, system_id=system_id)
|
||
output = await use_cases.environment_service.get_environment(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.put("/{env_id}", response_model=dict)
|
||
async def update_environment(
|
||
env_id: int,
|
||
body: UpdateEnvironmentRequest,
|
||
system_id: int = Query(...),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""更新环境。仅透传客户端显式设置的字段。``updated_by`` 由 ``current_user.uid`` 填充。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = UpdateEnvironmentInput(
|
||
id=env_id,
|
||
system_id=system_id,
|
||
updated_by=current_user.uid,
|
||
**body.model_dump(exclude_unset=True),
|
||
)
|
||
output = await use_cases.environment_service.update_environment(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.delete("/{env_id}", response_model=dict)
|
||
async def delete_environment(
|
||
env_id: int,
|
||
system_id: int = Query(...),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""删除环境。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = DeleteEnvironmentInput(id=env_id, system_id=system_id, deleted_by=current_user.uid)
|
||
output = await use_cases.environment_service.delete_environment(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.patch("/{env_id}/default", response_model=dict)
|
||
async def set_default_environment(
|
||
env_id: int,
|
||
system_id: int = Query(...),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""设置默认环境。``updated_by`` 由 ``current_user.uid`` 填充。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = SetDefaultEnvironmentInput(
|
||
id=env_id,
|
||
system_id=system_id,
|
||
updated_by=current_user.uid,
|
||
)
|
||
output = await use_cases.environment_service.set_default_environment(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.patch("/{env_id}/enabled", response_model=dict)
|
||
async def set_environment_enabled(
|
||
env_id: int,
|
||
body: SetEnvironmentEnabledRequest,
|
||
system_id: int = Query(...),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""启用/禁用环境。按 ``enabled`` 值分派 ``enable_environment``/``disable_environment``。
|
||
|
||
``updated_by`` 由 ``current_user.uid`` 填充。
|
||
"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
if body.enabled:
|
||
input_dto = EnableEnvironmentInput(
|
||
id=env_id,
|
||
system_id=system_id,
|
||
updated_by=current_user.uid,
|
||
)
|
||
output = await use_cases.environment_service.enable_environment(input_dto)
|
||
else:
|
||
input_dto = DisableEnvironmentInput(
|
||
id=env_id,
|
||
system_id=system_id,
|
||
updated_by=current_user.uid,
|
||
)
|
||
output = await use_cases.environment_service.disable_environment(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.post("/{env_id}/clone", response_model=dict)
|
||
async def clone_environment(
|
||
env_id: int,
|
||
body: CloneEnvironmentRequest,
|
||
system_id: int = Query(...),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""克隆环境。``created_by`` 由 ``current_user.uid`` 填充。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = CloneEnvironmentInput(
|
||
id=env_id,
|
||
system_id=system_id,
|
||
new_env_key=body.new_env_key,
|
||
new_name=body.new_name,
|
||
created_by=current_user.uid,
|
||
)
|
||
output = await use_cases.environment_service.clone_environment(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.post("/{env_id}/test-connectivity", response_model=dict)
|
||
async def test_environment_connection(
|
||
env_id: int,
|
||
system_id: int = Query(...),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> dict[str, Any]:
|
||
"""测试环境连通性。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = TestEnvironmentConnectionInput(id=env_id, system_id=system_id)
|
||
output = await use_cases.environment_service.test_environment_connection(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|
||
|
||
|
||
@environment_router.get("/{env_id}/export")
|
||
async def export_environment(
|
||
env_id: int,
|
||
system_id: int = Query(...),
|
||
include_secrets: bool = Query(False),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_admin_user),
|
||
) -> StreamingResponse:
|
||
"""导出环境配置为 JSON 文件下载流。
|
||
|
||
``include_secrets=false``(默认)返回脱敏配置;``include_secrets=true``
|
||
返回已解密的明文配置。由于 ``include_secrets=true`` 会泄露明文密钥,
|
||
本端点统一要求管理员权限,对齐写操作的权限梯度。
|
||
返回 ``StreamingResponse``(``application/octet-stream``),
|
||
对齐 ``system_router.export_systems`` 模式。
|
||
"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = ExportEnvironmentInput(
|
||
id=env_id,
|
||
system_id=system_id,
|
||
include_secrets=include_secrets,
|
||
)
|
||
output = await use_cases.environment_service.export_environment(input_dto)
|
||
payload = json.dumps(
|
||
output.model_dump(),
|
||
ensure_ascii=False,
|
||
default=str,
|
||
).encode("utf-8")
|
||
timestamp = datetime.now(UTC).strftime("%Y%m%d%H%M%S")
|
||
filename = f"environment_{env_id}_{output.env_key}_{timestamp}.json"
|
||
|
||
async def _stream():
|
||
yield payload
|
||
|
||
return StreamingResponse(
|
||
_stream(),
|
||
media_type="application/octet-stream",
|
||
headers={"Content-Disposition": f"attachment; filename={filename}"},
|
||
)
|
||
|
||
|
||
@environment_router.get("/{env_id}/health", response_model=dict)
|
||
async def get_environment_health(
|
||
env_id: int,
|
||
system_id: int = Query(...),
|
||
db: AsyncSession = Depends(get_db),
|
||
current_user: User = Depends(get_required_user),
|
||
) -> dict[str, Any]:
|
||
"""查询环境最近健康状态(基于审计日志推导,不执行实时探测)。"""
|
||
use_cases = create_use_cases_from_db(db)
|
||
input_dto = GetEnvironmentHealthInput(id=env_id, system_id=system_id)
|
||
output = await use_cases.environment_service.get_environment_health(input_dto)
|
||
return {"success": True, "data": output.model_dump()}
|