fix(sync): align customer backend behavior

This commit is contained in:
2026-06-11 11:41:24 +08:00
parent 60c8fc4948
commit d6dda51008
10 changed files with 34 additions and 47 deletions
+3 -1
View File
@@ -1,6 +1,7 @@
# TJWater Server 环境变量配置模板 # TJWater Server 环境变量配置模板
# 复制此文件为 .env 并填写实际值 # 复制此文件为 .env 并填写实际值
ENVIRONMENT="local" # CI/CD: 将生产 .env 的完整内容保存为 Gitea 仓库密钥 TJWATER_SERVER_ENV。
ENVIRONMENT="production"
NETWORK_NAME="tjwater" NETWORK_NAME="tjwater"
# ============================================ # ============================================
# 安全配置 (必填) # 安全配置 (必填)
@@ -49,6 +50,7 @@ KEYCLOAK_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
KEYCLOAK_ALGORITHM=RS256 KEYCLOAK_ALGORITHM=RS256
KEYCLOAK_AUDIENCE="account" KEYCLOAK_AUDIENCE="account"
# ============================================ # ============================================
# Bocha Web Search API # Bocha Web Search API
# ============================================ # ============================================
-22
View File
@@ -1,5 +1,4 @@
from typing import Any from typing import Any
import random
from fastapi import APIRouter, Query from fastapi import APIRouter, Query
from fastapi.responses import JSONResponse from fastapi.responses import JSONResponse
from fastapi import status from fastapi import status
@@ -63,24 +62,3 @@ async def fastapi_test_dict(data: Item) -> dict[str, str]:
""" """
item = data.dict() item = data.dict()
return item return item
@router.get("/getrealtimedata/", summary="获取实时数据", description="获取实时监测数据")
async def fastapi_get_realtimedata():
"""
获取实时数据
返回随机生成的实时监测数据示例
"""
data = [random.randint(0, 100) for _ in range(100)]
return data
@router.get("/getsimulationresult/", summary="获取模拟结果", description="获取仿真计算结果")
async def fastapi_get_simulationresult():
"""
获取仿真结果
返回随机生成的仿真计算结果示例
"""
data = [random.randint(0, 100) for _ in range(100)]
return data
+6 -2
View File
@@ -6,6 +6,7 @@ from app.api.v1.endpoints import (
scada, scada,
extension, extension,
snapshots, snapshots,
# data_query,
users, users,
schemes, schemes,
misc, misc,
@@ -54,7 +55,9 @@ api_router = APIRouter()
# Core Services # Core Services
api_router.include_router(auth.router, prefix="/auth", tags=["Auth"]) api_router.include_router(auth.router, prefix="/auth", tags=["Auth"])
api_router.include_router(user_management.router, prefix="/users", tags=["User Management"]) # 新增 api_router.include_router(
user_management.router, prefix="/users", tags=["User Management"]
) # 新增
api_router.include_router(audit.router, prefix="/audit", tags=["Audit Logs"]) # 新增 api_router.include_router(audit.router, prefix="/audit", tags=["Audit Logs"]) # 新增
api_router.include_router(meta.router, tags=["Metadata"]) api_router.include_router(meta.router, tags=["Metadata"])
api_router.include_router(project.router, tags=["Project"]) api_router.include_router(project.router, tags=["Project"])
@@ -84,7 +87,8 @@ api_router.include_router(visuals.router, tags=["Visuals"])
# Simulation & Data # Simulation & Data
api_router.include_router(simulation.router, tags=["Simulation Control"]) api_router.include_router(simulation.router, tags=["Simulation Control"])
api_router.include_router(scada.router, tags=["SCADA"]) # api_router.include_router(data_query.router, tags=["Data Query & InfluxDB"])
api_router.include_router(scada.router)
api_router.include_router(snapshots.router, tags=["Snapshots"]) api_router.include_router(snapshots.router, tags=["Snapshots"])
api_router.include_router(users.router, tags=["Users"]) api_router.include_router(users.router, tags=["Users"])
api_router.include_router(schemes.router, tags=["Schemes"]) api_router.include_router(schemes.router, tags=["Schemes"])
+3 -1
View File
@@ -6,9 +6,11 @@ from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings): class Settings(BaseSettings):
PROJECT_NAME: str = "TJWater Server" PROJECT_NAME: str = "TJWater Server"
ENVIRONMENT: str = "local" ENVIRONMENT: str = "production"
API_V1_STR: str = "/api/v1" API_V1_STR: str = "/api/v1"
NETWORK_NAME: str = "default_network"
# JWT 配置 # JWT 配置
SECRET_KEY: str = ( SECRET_KEY: str = (
"your-secret-key-here-change-in-production-use-openssl-rand-hex-32" "your-secret-key-here-change-in-production-use-openssl-rand-hex-32"
+4 -5
View File
@@ -14,6 +14,7 @@ from app.services.scheme_management import (
store_scheme_info, store_scheme_info,
) )
from app.services.tjnetwork import get_all_scada_info from app.services.tjnetwork import get_all_scada_info
from app.services.time_api import extract_date, parse_utc_time, utc_now
def run_burst_detection( def run_burst_detection(
@@ -241,7 +242,7 @@ def list_burst_detection_schemes(
network: str, network: str,
query_date: datetime | str | None = None, query_date: datetime | str | None = None,
) -> list[dict[str, Any]]: ) -> list[dict[str, Any]]:
parsed_date = _to_datetime(query_date).date() if query_date is not None else None parsed_date = extract_date(query_date, field_name="query_date") if query_date is not None else None
return query_burst_detection_schemes( return query_burst_detection_schemes(
name=network, name=network,
network=network, network=network,
@@ -269,7 +270,7 @@ def _store_burst_detection_scheme(
if scheme_name_exists(network, scheme_name): if scheme_name_exists(network, scheme_name):
raise ValueError(f"方案名称已存在: {scheme_name}") raise ValueError(f"方案名称已存在: {scheme_name}")
now_iso = datetime.now().isoformat() now_iso = utc_now().isoformat()
scheme_detail = { scheme_detail = {
"network": network, "network": network,
"sensor_nodes": payload.get("sensor_nodes", []), "sensor_nodes": payload.get("sensor_nodes", []),
@@ -426,6 +427,4 @@ def _build_observed_pressure_from_scada(
def _to_datetime(value: datetime | str) -> datetime: def _to_datetime(value: datetime | str) -> datetime:
if isinstance(value, datetime): return parse_utc_time(value)
return value
return datetime.fromisoformat(value)
+4 -5
View File
@@ -15,6 +15,7 @@ from app.services.scheme_management import (
store_scheme_info, store_scheme_info,
) )
from app.services.tjnetwork import dump_inp, get_all_scada_info from app.services.tjnetwork import dump_inp, get_all_scada_info
from app.services.time_api import extract_date, parse_utc_time, utc_now
SeriesInput = pd.Series | dict[str, Any] | list[dict[str, Any]] SeriesInput = pd.Series | dict[str, Any] | list[dict[str, Any]]
FLOW_SCADA_TYPES = {"pipe_flow", "flow", "demand"} FLOW_SCADA_TYPES = {"pipe_flow", "flow", "demand"}
@@ -301,7 +302,7 @@ def run_burst_location_by_network(
def list_burst_location_schemes( def list_burst_location_schemes(
network: str, query_date: datetime | str | None = None network: str, query_date: datetime | str | None = None
) -> list[dict[str, Any]]: ) -> list[dict[str, Any]]:
parsed_date = _to_datetime(query_date).date() if query_date is not None else None parsed_date = extract_date(query_date, field_name="query_date") if query_date is not None else None
return query_burst_location_schemes( return query_burst_location_schemes(
name=network, network=network, query_date=parsed_date name=network, network=network, query_date=parsed_date
) )
@@ -327,7 +328,7 @@ def _store_burst_scheme(
if scheme_name_exists(network, scheme_name): if scheme_name_exists(network, scheme_name):
raise ValueError(f"方案名称已存在: {scheme_name}") raise ValueError(f"方案名称已存在: {scheme_name}")
now_iso = datetime.now().isoformat() now_iso = utc_now().isoformat()
scheme_detail = { scheme_detail = {
"network": network, "network": network,
"pressure_scada_ids": payload.get("pressure_scada_ids", []), "pressure_scada_ids": payload.get("pressure_scada_ids", []),
@@ -641,9 +642,7 @@ def _dedupe_ids(ids: list[str] | None) -> list[str]:
def _to_datetime(value: datetime | str) -> datetime: def _to_datetime(value: datetime | str) -> datetime:
if isinstance(value, datetime): return parse_utc_time(value)
return value
return datetime.fromisoformat(value)
def _prepare_burst_inp(network: str) -> str: def _prepare_burst_inp(network: str) -> str:
+4 -5
View File
@@ -23,6 +23,7 @@ from app.services.tjnetwork import (
get_network_link_nodes, get_network_link_nodes,
get_network_node_coords, get_network_node_coords,
) )
from app.services.time_api import extract_date, parse_utc_time, utc_now
DEFAULT_N_WORKERS = max(1, min((os.cpu_count() or 1) - 1, 4)) DEFAULT_N_WORKERS = max(1, min((os.cpu_count() or 1) - 1, 4))
@@ -119,7 +120,7 @@ def run_leakage_identification(
scheme_start_time = ( scheme_start_time = (
_to_datetime(scada_start).isoformat() _to_datetime(scada_start).isoformat()
if scada_start is not None if scada_start is not None
else datetime.now().isoformat() else utc_now().isoformat()
) )
scheme_detail = { scheme_detail = {
"network": network, "network": network,
@@ -177,7 +178,7 @@ def run_leakage_identification(
def list_leakage_identify_schemes( def list_leakage_identify_schemes(
network: str, query_date: datetime | str | None = None network: str, query_date: datetime | str | None = None
) -> list[dict[str, Any]]: ) -> list[dict[str, Any]]:
parsed_date = _to_datetime(query_date).date() if query_date is not None else None parsed_date = extract_date(query_date, field_name="query_date") if query_date is not None else None
return query_leakage_identify_schemes( return query_leakage_identify_schemes(
name=network, network=network, query_date=parsed_date name=network, network=network, query_date=parsed_date
) )
@@ -509,9 +510,7 @@ def _build_observed_pressure_from_scada(
def _to_datetime(value: datetime | str) -> datetime: def _to_datetime(value: datetime | str) -> datetime:
if isinstance(value, datetime): return parse_utc_time(value)
return value
return datetime.fromisoformat(value)
def _prepare_leakage_inp(network: str) -> str: def _prepare_leakage_inp(network: str) -> str:
+2 -3
View File
@@ -1,4 +1,3 @@
import os from app.core.config import settings
# 从环境变量 NETWORK_NAME 读取 name = settings.NETWORK_NAME
name = os.getenv("NETWORK_NAME")
+7 -3
View File
@@ -1,6 +1,6 @@
import ast import ast
import json import json
from datetime import date from datetime import date, datetime
import geopandas as gpd import geopandas as gpd
import pandas as pd import pandas as pd
@@ -8,6 +8,7 @@ import psycopg
from sqlalchemy import create_engine from sqlalchemy import create_engine
from app.core.config import get_pgconn_string from app.core.config import get_pgconn_string
from app.services.time_api import parse_utc_time
# 2025/03/23 # 2025/03/23
@@ -89,7 +90,7 @@ def store_scheme_info(
scheme_name: str, scheme_name: str,
scheme_type: str, scheme_type: str,
username: str, username: str,
scheme_start_time: str, scheme_start_time: datetime | str,
scheme_detail: dict, scheme_detail: dict,
): ):
""" """
@@ -112,13 +113,16 @@ def store_scheme_info(
""" """
# 将字典转换为 JSON 字符串 # 将字典转换为 JSON 字符串
scheme_detail_json = json.dumps(scheme_detail) scheme_detail_json = json.dumps(scheme_detail)
normalized_scheme_start_time = parse_utc_time(
scheme_start_time, field_name="scheme_start_time"
)
cur.execute( cur.execute(
sql, sql,
( (
scheme_name, scheme_name,
scheme_type, scheme_type,
username, username,
scheme_start_time, normalized_scheme_start_time,
scheme_detail_json, scheme_detail_json,
), ),
) )
+1
View File
@@ -3,6 +3,7 @@ services:
# Core API Service # Core API Service
# ========================================== # ==========================================
api: api:
image: ${TJWATER_SERVER_IMAGE:-tjwater-server:local}
build: build:
context: ../.. context: ../..
dockerfile: Dockerfile dockerfile: Dockerfile