refactor(db)!: finalize pooled WNDB v2 migration
This commit is contained in:
@@ -1,43 +1,18 @@
|
||||
import json
|
||||
from fastapi import APIRouter, Request, HTTPException, Query, Path, Depends
|
||||
from fastapi.responses import PlainTextResponse
|
||||
from typing import Any, Dict, List
|
||||
from fastapi import APIRouter, HTTPException, Query, Depends
|
||||
from app.infra.db.metadb.repositories.metadata_repository import MetadataRepository
|
||||
from app.auth.project_dependencies import get_metadata_repository
|
||||
from app.auth.permissions import (
|
||||
ENVIRONMENT_MANAGE,
|
||||
require_permission,
|
||||
from app.auth.project_dependencies import (
|
||||
get_metadata_repository,
|
||||
)
|
||||
from app.domain.schemas.metadata import ProjectMetaResponse
|
||||
import app.services.project_info as project_info
|
||||
from app.services.tjnetwork import (
|
||||
ChangeSet,
|
||||
list_project,
|
||||
have_project,
|
||||
create_project,
|
||||
delete_project,
|
||||
is_project_open,
|
||||
open_project,
|
||||
close_project,
|
||||
copy_project,
|
||||
export_inp,
|
||||
read_inp,
|
||||
dump_inp,
|
||||
get_all_vertices,
|
||||
get_all_scada_info,
|
||||
convert_inp_v3_to_v2,
|
||||
)
|
||||
|
||||
# For inp file upload/download
|
||||
import os
|
||||
from fastapi import Response, status
|
||||
from fastapi.responses import FileResponse
|
||||
inpDir = "data/" # Assuming data directory exists or is defined somewhere.
|
||||
# In main.py it was likely global. For safety, let's use a relative path or get from config.
|
||||
# But let's stick to what main.py probably used or a default.
|
||||
|
||||
router = APIRouter()
|
||||
lockedPrjs: Dict[str, str] = {}
|
||||
|
||||
@router.get("/projects/current", summary="获取项目信息", description="从数据库获取项目的详细信息,包括地图范围等。", response_model=ProjectMetaResponse)
|
||||
async def get_project_info_endpoint(
|
||||
@@ -63,105 +38,8 @@ async def get_project_info_endpoint(
|
||||
project_role="viewer", # Default role for public access
|
||||
)
|
||||
|
||||
@router.get("/project-codes", summary="获取项目列表", description="获取服务器上所有可用的供水管网项目名称列表。")
|
||||
async def list_projects_endpoint() -> list[str]:
|
||||
"""
|
||||
获取项目列表
|
||||
|
||||
返回所有已创建项目的名称列表。
|
||||
"""
|
||||
return list_project()
|
||||
|
||||
@router.get("/projects/existence", summary="检查项目是否存在", description="检查指定名称的项目是否存在。")
|
||||
async def have_project_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)")
|
||||
):
|
||||
"""
|
||||
检查项目是否存在
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
"""
|
||||
return have_project(network)
|
||||
|
||||
@router.post("/projects", summary="创建新项目", description="创建一个新的供水管网项目。如果项目已存在,可能会覆盖或报错(取决于底层实现)。")
|
||||
async def create_project_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
_=Depends(require_permission(ENVIRONMENT_MANAGE)),
|
||||
):
|
||||
"""
|
||||
创建新项目
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
"""
|
||||
create_project(network)
|
||||
return network
|
||||
|
||||
@router.delete("/projects", summary="删除项目", description="永久删除指定的供水管网项目。此操作不可恢复。")
|
||||
async def delete_project_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
_=Depends(require_permission(ENVIRONMENT_MANAGE)),
|
||||
):
|
||||
"""
|
||||
删除项目
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
"""
|
||||
delete_project(network)
|
||||
return True
|
||||
|
||||
@router.get("/projects/current/status", summary="检查项目是否已打开", description="检查指定项目是否已被加载到内存中。")
|
||||
async def is_project_open_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)")
|
||||
):
|
||||
"""
|
||||
检查项目是否已打开
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
"""
|
||||
return is_project_open(network)
|
||||
|
||||
@router.post("/projects/current", summary="打开项目", description="将指定项目加载到内存中,并初始化数据库连接池。")
|
||||
async def open_project_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)")
|
||||
):
|
||||
"""
|
||||
打开项目
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
"""
|
||||
open_project(network)
|
||||
|
||||
return network
|
||||
|
||||
@router.delete("/projects/current", summary="关闭项目", description="将指定项目从内存中卸载,释放资源。")
|
||||
async def close_project_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)")
|
||||
):
|
||||
"""
|
||||
关闭项目
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
"""
|
||||
close_project(network)
|
||||
return True
|
||||
|
||||
@router.post("/project-copies", summary="复制项目", description="将现有项目复制为新项目。")
|
||||
async def copy_project_endpoint(
|
||||
source: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
target: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
_=Depends(require_permission(ENVIRONMENT_MANAGE)),
|
||||
):
|
||||
"""
|
||||
复制项目
|
||||
|
||||
- **source**: 管网名称(或数据库名称)
|
||||
- **target**: 管网名称(或数据库名称)
|
||||
"""
|
||||
copy_project(source, target)
|
||||
return True
|
||||
|
||||
@router.get("/projects/current/exports/change-set", response_model=None, summary="导出项目为 ChangeSet", description="导出项目的变更集 (ChangeSet),包含顶点、SCADA 元素、DMA、SA、VD 等信息。")
|
||||
async def export_inp_endpoint(
|
||||
def export_inp_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
version: str = Query(..., description="版本号 (通常用于增量更新)")
|
||||
) -> ChangeSet:
|
||||
@@ -173,279 +51,7 @@ async def export_inp_endpoint(
|
||||
"""
|
||||
cs = export_inp(network, version)
|
||||
op = cs.operations[0]
|
||||
open_project(network)
|
||||
op["vertex"] = json.dumps(get_all_vertices(network))
|
||||
op["scada"] = json.dumps(get_all_scada_info(network))
|
||||
|
||||
close_project(network)
|
||||
|
||||
return cs
|
||||
@router.post("/projects/current/imports", summary="读取 INP 文件到项目", description="从服务器文件系统中读取指定的 INP 文件并加载到项目中。")
|
||||
async def read_inp_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
inp: str = Query(..., description="INP 文件名 (不包含路径)")
|
||||
) -> bool:
|
||||
"""
|
||||
读取 INP 文件到项目
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
- **inp**: INP 文件名
|
||||
"""
|
||||
read_inp(network, inp)
|
||||
return True
|
||||
|
||||
@router.post("/projects/current/exports/inp", summary="导出项目到 INP 文件", description="将项目当前状态保存为 INP 文件到服务器文件系统。")
|
||||
async def dump_inp_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
inp: str = Query(..., description="目标文件名")
|
||||
) -> bool:
|
||||
"""
|
||||
导出项目到 INP 文件
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
- **inp**: 目标文件名
|
||||
"""
|
||||
dump_inp(network, inp)
|
||||
return True
|
||||
|
||||
@router.get("/projects/current/lock", summary="检查项目是否被锁定", description="检查指定项目是否处于锁定状态。")
|
||||
async def is_project_locked_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
req: Request = None
|
||||
):
|
||||
"""
|
||||
检查项目是否被锁定
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
"""
|
||||
return network in lockedPrjs.keys()
|
||||
|
||||
@router.get("/projects/current/lock/ownership", summary="检查项目是否被当前用户锁定", description="检查指定项目是否被当前访问地址 (IP) 锁定。")
|
||||
async def is_project_locked_by_me_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
req: Request = None
|
||||
):
|
||||
"""
|
||||
检查项目是否被当前用户锁定
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
"""
|
||||
client_host = req.client.host
|
||||
return lockedPrjs.get(network) == client_host
|
||||
|
||||
# 0 successfully locked
|
||||
# 1 already locked by you
|
||||
# 2 locked by others
|
||||
@router.post("/projects/current/lock", summary="锁定项目", description="锁定指定项目以防止并发修改。")
|
||||
async def lock_project_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
req: Request = None
|
||||
):
|
||||
"""
|
||||
锁定项目
|
||||
|
||||
返回值:
|
||||
- **0**: 锁定成功
|
||||
- **1**: 已被当前用户锁定
|
||||
- **2**: 已被其他用户锁定
|
||||
"""
|
||||
client_host = req.client.host
|
||||
if not network in lockedPrjs.keys():
|
||||
lockedPrjs[network] = client_host
|
||||
return 0
|
||||
else:
|
||||
if lockedPrjs.get(network) == client_host:
|
||||
return 1
|
||||
else:
|
||||
return 2
|
||||
|
||||
@router.delete("/projects/current/lock", summary="解锁项目", description="释放对项目的锁定。")
|
||||
def unlock_project_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
req: Request = None
|
||||
):
|
||||
"""
|
||||
解锁项目
|
||||
|
||||
只有锁定者才能解锁。
|
||||
"""
|
||||
client_host = req.client.host
|
||||
if lockedPrjs.get(network) == client_host:
|
||||
print("delete key")
|
||||
del lockedPrjs[network]
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@router.get("/projects/current/files/inp", status_code=status.HTTP_200_OK, summary="下载 INP 文件", description="从服务器数据目录下载指定的 INP 文件。")
|
||||
async def fastapi_download_inp(
|
||||
name: str = Query(..., description="文件名"),
|
||||
response: Response = None
|
||||
):
|
||||
"""
|
||||
下载 INP 文件
|
||||
|
||||
- **name**: 文件名
|
||||
"""
|
||||
filePath = inpDir + name
|
||||
if os.path.exists(filePath):
|
||||
return FileResponse(
|
||||
filePath, media_type="application/octet-stream", filename="inp.inp"
|
||||
)
|
||||
else:
|
||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||
return True
|
||||
|
||||
# DingZQ, 2024-12-28, convert v3 to v2
|
||||
@router.post("/project-conversions", response_model=None, summary="转换 INP V3 为 V2", description="将 EPANET 3.0 格式的 INP 内容转换为 2.x 格式。")
|
||||
async def fastapi_convert_v3_to_v2(
|
||||
req: Request
|
||||
) -> ChangeSet:
|
||||
"""
|
||||
转换 INP V3 为 V2
|
||||
|
||||
- **req**: 请求体,需包含 `{"inp": "..."}` 结构
|
||||
"""
|
||||
network = "v3Tov2"
|
||||
jo_root = await req.json()
|
||||
inp = jo_root["inp"]
|
||||
cs = convert_inp_v3_to_v2(inp)
|
||||
op = cs.operations[0]
|
||||
open_project(network)
|
||||
op["vertex"] = json.dumps(get_all_vertices(network))
|
||||
op["scada"] = json.dumps(get_all_scada_info(network))
|
||||
|
||||
close_project(network)
|
||||
|
||||
return cs
|
||||
|
||||
async def read_inp_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
inp: str = Query(..., description="INP 文件名 (不包含路径)")
|
||||
) -> bool:
|
||||
"""
|
||||
读取 INP 文件到项目
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
- **inp**: INP 文件名
|
||||
"""
|
||||
read_inp(network, inp)
|
||||
return True
|
||||
|
||||
async def dump_inp_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
inp: str = Query(..., description="目标文件名")
|
||||
) -> bool:
|
||||
"""
|
||||
导出项目到 INP 文件
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
- **inp**: 目标文件名
|
||||
"""
|
||||
dump_inp(network, inp)
|
||||
return True
|
||||
|
||||
async def is_project_locked_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
req: Request = None
|
||||
):
|
||||
"""
|
||||
检查项目是否被锁定
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
"""
|
||||
return network in lockedPrjs.keys()
|
||||
|
||||
async def is_project_locked_by_me_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
req: Request = None
|
||||
):
|
||||
"""
|
||||
检查项目是否被当前用户锁定
|
||||
|
||||
- **network**: 管网名称(或数据库名称)
|
||||
"""
|
||||
client_host = req.client.host
|
||||
return lockedPrjs.get(network) == client_host
|
||||
|
||||
# 0 successfully locked
|
||||
# 1 already locked by you
|
||||
# 2 locked by others
|
||||
async def lock_project_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
req: Request = None
|
||||
):
|
||||
"""
|
||||
锁定项目
|
||||
|
||||
返回值:
|
||||
- **0**: 锁定成功
|
||||
- **1**: 已被当前用户锁定
|
||||
- **2**: 已被其他用户锁定
|
||||
"""
|
||||
client_host = req.client.host
|
||||
if not network in lockedPrjs.keys():
|
||||
lockedPrjs[network] = client_host
|
||||
return 0
|
||||
else:
|
||||
if lockedPrjs.get(network) == client_host:
|
||||
return 1
|
||||
else:
|
||||
return 2
|
||||
|
||||
def unlock_project_endpoint(
|
||||
network: str = Query(..., description="管网名称(或数据库名称)"),
|
||||
req: Request = None
|
||||
):
|
||||
"""
|
||||
解锁项目
|
||||
|
||||
只有锁定者才能解锁。
|
||||
"""
|
||||
client_host = req.client.host
|
||||
if lockedPrjs.get(network) == client_host:
|
||||
print("delete key")
|
||||
del lockedPrjs[network]
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
async def fastapi_download_inp(
|
||||
name: str = Query(..., description="文件名"),
|
||||
response: Response = None
|
||||
):
|
||||
"""
|
||||
下载 INP 文件
|
||||
|
||||
- **name**: 文件名
|
||||
"""
|
||||
filePath = inpDir + name
|
||||
if os.path.exists(filePath):
|
||||
return FileResponse(
|
||||
filePath, media_type="application/octet-stream", filename="inp.inp"
|
||||
)
|
||||
else:
|
||||
response.status_code = status.HTTP_400_BAD_REQUEST
|
||||
return True
|
||||
|
||||
# DingZQ, 2024-12-28, convert v3 to v2
|
||||
async def fastapi_convert_v3_to_v2(
|
||||
req: Request
|
||||
) -> ChangeSet:
|
||||
"""
|
||||
转换 INP V3 为 V2
|
||||
|
||||
- **req**: 请求体,需包含 `{"inp": "..."}` 结构
|
||||
"""
|
||||
network = "v3Tov2"
|
||||
jo_root = await req.json()
|
||||
inp = jo_root["inp"]
|
||||
cs = convert_inp_v3_to_v2(inp)
|
||||
op = cs.operations[0]
|
||||
open_project(network)
|
||||
op["vertex"] = json.dumps(get_all_vertices(network))
|
||||
op["scada"] = json.dumps(get_all_scada_info(network))
|
||||
|
||||
close_project(network)
|
||||
|
||||
return cs
|
||||
|
||||
Reference in New Issue
Block a user