refactor(storage): route project DSNs and remove legacy backends

This commit is contained in:
2026-08-18 18:29:09 +08:00
parent b21eaffe40
commit 6b09662de6
51 changed files with 542 additions and 10951 deletions
-57
View File
@@ -1,57 +0,0 @@
from fastapi import APIRouter, Query
from app.infra.cache.redis_client import redis_client
router = APIRouter()
@router.delete("/redis-keys/detail", summary="清除单个缓存键", description="根据键名清除单个Redis缓存")
async def fastapi_clear_redis_key(key: str = Query(..., description="缓存键名")):
"""
清除单个缓存键
根据指定的键名删除Redis中对应的缓存
"""
redis_client.delete(key)
return True
@router.delete("/redis-keys", summary="清除匹配的缓存键", description="根据模式清除匹配的Redis缓存键")
async def fastapi_clear_redis_keys(keys: str = Query(..., description="缓存键模式(支持通配符)")):
"""
清除匹配的缓存键
根据指定的模式删除Redis中所有匹配的缓存键
"""
# delete keys contains the key
matched_keys = redis_client.keys(f"*{keys}*")
if matched_keys:
redis_client.delete(*matched_keys)
return True
@router.delete("/all-redis", summary="清除所有缓存", description="清空整个Redis数据库的所有缓存")
async def fastapi_clear_all_redis():
"""
清除所有缓存
清空Redis数据库中的所有缓存键值对
"""
redis_client.flushdb()
return True
@router.get("/redis", summary="查询缓存键列表", description="获取Redis中所有的缓存键")
async def fastapi_query_redis():
"""
查询缓存键列表
获取Redis数据库中所有的缓存键列表
"""
# Helper to decode bytes to str for JSON response if needed,
# but original just returned keys (which might be bytes in redis-py unless decode_responses=True)
# create_redis_client usually sets decode_responses=False by default.
# We will assume user handles bytes or we should decode.
# Original just returned redis_client.keys("*")
keys = redis_client.keys("*")
# Clean output for API
return [k.decode('utf-8') if isinstance(k, bytes) else k for k in keys]
+22 -5
View File
@@ -17,8 +17,13 @@ from app.auth.metadata_dependencies import (
get_current_metadata_admin,
get_metadata_repository,
)
from app.auth.project_dependencies import (
ProjectContext,
resolve_project_business_routing,
)
from app.core.audit import AuditAction, log_audit_event
from app.infra.db.metadb.repositories.metadata_repository import MetadataRepository
from app.infra.db.project_routing import activate_project_routing
from app.services.network_import import network_update
from app.services.tjnetwork import run_inp
@@ -118,21 +123,21 @@ async def _run_uploaded_inp(content: bytes) -> str:
return run_inp(model_name)
async def _update_from_inp(content: bytes) -> None:
async def _update_from_inp(content: bytes, project_code: str) -> None:
temp_path: Path | None = None
try:
with NamedTemporaryFile(suffix=".inp", delete=False) as temp_file:
temp_file.write(content)
temp_path = Path(temp_file.name)
network_update(str(temp_path))
network_update(str(temp_path), project_code)
finally:
if temp_path is not None:
temp_path.unlink(missing_ok=True)
async def _apply_model_update(content: bytes) -> None:
async def _apply_model_update(content: bytes, project_code: str) -> None:
try:
await _update_from_inp(content)
await _update_from_inp(content, project_code)
except Exception as exc:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
@@ -177,7 +182,19 @@ async def update_project_model(
) -> dict:
project = await _get_active_project(project_id, metadata_repo)
content, filename = await _read_upload(file)
await _apply_model_update(content)
routing = await resolve_project_business_routing(
ProjectContext(
project_id=project.id,
project_code=project.code,
user_id=current_user.id,
project_role="owner",
system_role=current_user.role,
is_superuser=current_user.is_superuser,
),
metadata_repo,
)
with activate_project_routing(routing):
await _apply_model_update(content, project.code)
await _audit_model_change(
request=request,
current_user=current_user,
@@ -333,7 +333,6 @@ async def fastapi_get_all_junction_properties(
list: 包含所有节点属性的列表
"""
# 缓存查询结果提高性能
# global redis_client # Redis logic removed for clean split, can be re-added if needed or imported
results = get_all_junctions(network)
return results
-1
View File
@@ -385,7 +385,6 @@ async def fastapi_get_all_pipe_properties(
包含所有管道属性的字典列表
"""
# 缓存查询结果提高性能
# global redis_client
results = get_all_pipes(network)
return results
-1
View File
@@ -177,7 +177,6 @@ async def fastapi_get_all_pump_properties(
包含所有水泵属性的字典列表
"""
# 缓存查询结果提高性能
# global redis_client
results = get_all_pumps(network)
return results
-1
View File
@@ -540,7 +540,6 @@ async def fastapi_get_all_tank_properties(
包含所有水箱属性的字典列表
"""
# 缓存查询结果提高性能
# global redis_client
results = get_all_tanks(network)
return results
-1
View File
@@ -307,7 +307,6 @@ async def fastapi_get_all_valve_properties(
返回指定水网中所有阀门的完整属性列表。
"""
# 缓存查询结果提高性能
# global redis_client
results = get_all_valves(network)
return results
+42 -5
View File
@@ -17,7 +17,16 @@ from app.api.problem_details import ProblemDetails
from app.api.pagination import PaginatedList
from app.api.v1.router import api_router as handler_api_router
from app.auth.metadata_dependencies import get_current_metadata_user
from app.auth.project_dependencies import ProjectContext, get_project_context
from app.auth.project_dependencies import (
ProjectContext,
get_project_business_routing,
get_project_context,
get_project_simulation_routing,
)
from app.infra.db.project_routing import (
ActiveProjectRouting,
activate_project_routing,
)
T = TypeVar("T")
@@ -44,6 +53,13 @@ _PUBLIC_PARAMETER_RENAMES = {
}
_MODEL_NAME_IS_NETWORK = {"RunSimulationManuallyByDate", "PressureSensorPlacement"}
_MODEL_USERNAME_FROM_AUTH = {"PressureSensorPlacement"}
_TIMESCALE_ROUTED_ENDPOINT_MODULES = {
"app.api.v1.endpoints.burst_detection",
"app.api.v1.endpoints.burst_location",
"app.api.v1.endpoints.leakage",
"app.api.v1.endpoints.simulation",
}
_TIMESCALE_ROUTED_ENDPOINT_NAMES = {"open_project_endpoint"}
def _clean_name(name: str) -> str:
@@ -128,10 +144,14 @@ def _with_header_project_context(endpoint, route_name: str):
None,
)
injected_context_name = existing_context_parameter or "_rest_project_context"
injected_routing_name = "_rest_project_routing"
injected_user_name = "_rest_current_user"
@wraps(endpoint)
async def wrapper(*args, **kwargs):
project_routing = kwargs.pop(injected_routing_name, None)
if not isinstance(project_routing, ActiveProjectRouting):
raise RuntimeError("REST project database routing was not resolved")
project_context = kwargs.get(injected_context_name)
if not isinstance(project_context, ProjectContext):
raise RuntimeError("REST project context was not resolved")
@@ -162,10 +182,11 @@ def _with_header_project_context(endpoint, route_name: str):
kwargs[parameter_name] = original_model.model_validate(data)
if model_has_username:
kwargs.pop(injected_user_name, None)
result = endpoint(*args, **kwargs)
if inspect.isawaitable(result):
return await result
return result
with activate_project_routing(project_routing):
result = endpoint(*args, **kwargs)
if inspect.isawaitable(result):
return await result
return result
parameters = []
for name, parameter in signature.parameters.items():
@@ -181,6 +202,14 @@ def _with_header_project_context(endpoint, route_name: str):
if name in body_models:
parameter = parameter.replace(annotation=body_models[name][1])
parameters.append(parameter)
routing_dependency = (
get_project_simulation_routing
if (
endpoint.__module__ in _TIMESCALE_ROUTED_ENDPOINT_MODULES
or endpoint.__name__ in _TIMESCALE_ROUTED_ENDPOINT_NAMES
)
else get_project_business_routing
)
if not existing_context_parameter:
parameters.append(
inspect.Parameter(
@@ -190,6 +219,14 @@ def _with_header_project_context(endpoint, route_name: str):
default=Depends(get_project_context),
)
)
parameters.append(
inspect.Parameter(
injected_routing_name,
kind=inspect.Parameter.KEYWORD_ONLY,
annotation=ActiveProjectRouting,
default=Depends(routing_dependency),
)
)
if username_parameter or model_has_username:
parameters.append(
inspect.Parameter(
-8
View File
@@ -7,7 +7,6 @@ from app.api.v1.endpoints import (
audit,
burst_detection,
burst_location,
cache,
extension,
geocoding,
leakage,
@@ -53,7 +52,6 @@ from app.api.v1.endpoints.timeseries import (
)
from app.auth.permissions import (
BURST_RUN,
ENVIRONMENT_MANAGE,
OPTIMIZATION_RUN,
RISK_RUN,
SCADA_CLEAN,
@@ -89,7 +87,6 @@ simulation_access = Depends(
webgis_view_access = Depends(require_permission(WEBGIS_VIEW))
simulation_run_access = Depends(require_permission(SIMULATION_RUN))
environment_manage_access = Depends(require_permission(ENVIRONMENT_MANAGE))
burst_run_access = Depends(require_permission(BURST_RUN))
risk_run_access = Depends(require_permission(RISK_RUN))
optimization_run_access = Depends(require_permission(OPTIMIZATION_RUN))
@@ -168,11 +165,6 @@ api_router.include_router(
tags=["Risk"],
dependencies=[risk_run_access],
)
api_router.include_router(
cache.router,
tags=["Cache"],
dependencies=[environment_manage_access],
)
api_router.include_router(
web_search.router,
tags=["Web Search"],