chore: initialize insar management system v2
This commit is contained in:
@@ -0,0 +1,967 @@
|
||||
# InSAR 自动化管理与智能评估系统 - 部署与使用说明
|
||||
|
||||
本文件为本项目主中文文档,整合了产品说明、技术架构、业务流程、部署与启动、数据库与授权等内容。
|
||||
补充专题文档见:`docs/TODO.md`、`docs/RADAR_PREVIEW_SCHEME_B.md`。
|
||||
|
||||
---
|
||||
|
||||
## 1. 产品概述
|
||||
本系统是一套深度集成 **IDL/ENVI SARscape** 的专业级 InSAR 数据管理与智能分析平台,聚焦地质灾害监测领域,解决大规模雷达影像处理流程繁琐、数据关联困难、结果解译依赖人工等问题。
|
||||
|
||||
通过“Web 化管理 + 自动化引擎 + AI 智能诊断”的三位一体架构,实现从原始 SAR 影像入库到生成形变诊断报告的全生命周期自动化管理。
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心优势
|
||||
- **核心流程自动化**:覆盖数据监控、精轨关联、干涉配对、自动化处理引导与结果发布(当前扫描调度默认手动触发)。
|
||||
- **高精度空间引擎**:凸包算法 + 仿射变换,支持亚像素级 Footprint 提取与空间关联。
|
||||
- **AI 辅助决策**:集成随机森林质量评估模型与 VLM 视觉大模型,给出专家级解译建议。
|
||||
- **工业级稳定性**:针对 Windows Server 环境优化,支持 GB 级超大影像处理。
|
||||
|
||||
---
|
||||
|
||||
## 3. 技术架构
|
||||
- **管理层 (Web Interface)**:React + Vite + Leaflet(支持离线多级细节加载)
|
||||
- **逻辑层 (Service Layer)**:FastAPI 异步框架,集成 PostgreSQL + PostGIS
|
||||
- **空间引擎 (Spatial Engine)**:PostGIS 原生计算 + GeoAlchemy2 + Rasterio
|
||||
- **AI 引擎 (Intelligence)**:Scikit-learn + 本地 Ollama VLM
|
||||
- **计算引擎 (Compute)**:IDL 8.8 / ENVI 5.6(SARscape 5.6+)
|
||||
|
||||
---
|
||||
|
||||
## 4. 业务流程指南
|
||||
|
||||
### 4.1 数据资产监控
|
||||
当前系统默认采用 **Manual-only(手动触发)** 扫描模式,不启用后台定时守护任务。
|
||||
可通过前端“立即扫描”或接口 `POST /api/monitor/run-now` 触发扫描;扫描内部仍采用增量策略以降低系统负载。
|
||||
|
||||
**D-InSAR 结果扫描说明(重要):**
|
||||
当前 D-InSAR 结果扫描为**全量遍历 + 仅新增入库**。
|
||||
即:每次扫描都会遍历所有 `.hdr` 文件,但数据库只写入“新发现”的结果记录,以保证准确性和稳定性。
|
||||
|
||||
扫描任务的进度条基于**实际遍历数量与处理数量**计算,前端显示为真实进度。
|
||||
|
||||
数据列表已支持“影像信息”按钮,可查看影像基础元数据(卫星/日期/模式/极化/精轨等)。
|
||||
影像元数据解析已扩展:优先从 XML 获取成像模式/极化/轨道方向,并补充卫星模式、接收站代号、圈号、景中心经纬度、采集时间、产品类型/级别、唯一标识等字段用于展示。
|
||||
源数据预览缓存接口:`GET /api/radar-data/{data_id}/thumb`(首次请求若无缓存会自动生成)。
|
||||
|
||||
**源影像预览(Scheme B:后端预纠正缓存)**
|
||||
系统已接入双层缓存:
|
||||
- `backend/image_cache/radar_geo/`:地理纠正后的主缓存(前端优先使用)
|
||||
- `backend/image_cache/radar_raw/`:原图 WebP 回退缓存
|
||||
|
||||
**D-InSAR 结果缓存(标准目录)**
|
||||
- `backend/image_cache/dinsar/`:D-InSAR 结果可视化缓存目录
|
||||
- 文件命名:`ID_{id}_{name}.webp`
|
||||
- 清单文件:`backend/image_cache/dinsar/cache_manifest.json`
|
||||
|
||||
**一致性检测(统计面板)**
|
||||
- `GET /api/statistics` 已新增一致性字段,用于校验“数据库记录 vs 实际缓存文件 vs XML 读取结果”。
|
||||
- 重点包含:
|
||||
- D-InSAR:`db_cached_but_file_missing_count`、`db_uncached_but_file_exists_count`、`manifest_missing_file_count`
|
||||
- 源影像预览:`db_ready_but_cache_missing_count`、`preview_missing_count`
|
||||
- XML:`xml_detected_but_unparsed_count`、`xml_missing_count`
|
||||
- 前端「数据统计仪表盘」会直接显示异常数量并高亮提示,便于运维快速定位问题。
|
||||
|
||||
新增接口:
|
||||
- `GET /api/radar-data/{id}/preview-status`:查询单条源影像预览缓存状态
|
||||
- `POST /api/radar-data/{id}/rebuild-preview-cache`:管理员强制重建该影像预览缓存
|
||||
- `GET /api/radar-data?limit=500&offset=0`:分页获取源数据列表(单次最多 2000 条)
|
||||
- `POST /api/radar-data/search`:源数据分页检索(支持详情字段筛选 + 行政区/上传SHP空间筛选)
|
||||
- `GET /api/radar-data/search/options`:获取源数据检索下拉选项(来自数据库去重值)
|
||||
- `GET /api/dinsar-results?limit=500&offset=0`:分页获取 D-InSAR 结果列表(单次最多 2000 条)
|
||||
- `GET /api/radar-data/imaging-dates`:获取所有可用成像日期(用于配对起始日期下拉框)
|
||||
- `GET /api/statistics`:默认返回缓存快照;管理员可用 `GET /api/statistics?fresh=true` 强制重算
|
||||
|
||||
`/api/radar-data/search` 说明:
|
||||
- 权限:管理员与只读账号均可调用(只读安全查询接口)。
|
||||
- 筛选字段:可按卫星、成像时间范围、模式/极化/产品元数据等详情字段检索。
|
||||
- 空间筛选:支持 `region_tree_id`(复用行政区)或上传 AOI 文件(`.shp`/GeoJSON)。
|
||||
- 分页性能:首次 AOI 查询会返回 `aoi_token`,后续翻页可仅携带 token,无需重复上传文件。
|
||||
- 前端交互:检索字段采用“全下拉可选”,避免手工输入导致的筛选误差。
|
||||
|
||||
分页接口响应结构(`/api/radar-data` 与 `/api/dinsar-results` 一致):
|
||||
```json
|
||||
{
|
||||
"items": [],
|
||||
"total": 12345,
|
||||
"limit": 200,
|
||||
"offset": 0,
|
||||
"has_more": true
|
||||
}
|
||||
```
|
||||
|
||||
前端分页说明:
|
||||
- 「数据列表」与「D-InSAR 结果」均支持上一页/下一页、每页条数切换(50/100/200/500)与页码跳转。
|
||||
- 前端默认每页 200 条,避免一次性加载全量数据导致页面和地图卡顿。
|
||||
- 「数据列表」改为“先检索后展示”:系统启动后不自动加载源数据,需点击“搜索”或“搜索全部源数据”后才显示列表与分页结果。
|
||||
- 当检索条件变更并再次执行搜索时,前端会先清空旧结果再渲染新结果,避免“旧页数据残留”造成误判。
|
||||
- 异常/堆栈文本默认不参与界面翻译(e.g. `NoneType object is not callable`、`Traceback`),保持原始语言以避免日志或异常信息被误替换。
|
||||
|
||||
新增环境变量(可选):
|
||||
```env
|
||||
RADAR_GEO_CACHE_WORKERS=2
|
||||
RADAR_GEO_CACHE_VERSION=b1
|
||||
RADAR_GEO_CACHE_QUALITY=84
|
||||
RADAR_PREVIEW_BUILD_ON_DEMAND=true
|
||||
```
|
||||
|
||||
说明:
|
||||
- `thumb` 接口优先返回 `radar_geo`,失败时自动回退 `radar_raw`。
|
||||
- 扫描任务会增量构建 `radar_geo`,并维护 `radar_raw` 作为兜底。
|
||||
- 如纠正逻辑升级,提升 `RADAR_GEO_CACHE_VERSION` 可触发重建。
|
||||
- 纠正方向判定优先使用 XML 的 `sceneCornerCoord/refRow/refColumn` 建立“像素角 ↔ 地理角”映射,可显著减少左右/上下方向错误。
|
||||
|
||||
### 4.2 任务分发与处理
|
||||
系统生成处理清单对接 IDL 自动化脚本,用户可在 Web 端一键启动本地 IDL 工作站,实现“云端规划,本地计算”。
|
||||
|
||||
### 4.3 智能诊断报告
|
||||
AI 模块结合影像范围内的已知灾害点生成 Markdown 报告,涵盖:活动性评估、隐患搜寻、风险评级与处置建议。
|
||||
|
||||
---
|
||||
|
||||
## 5. 核心算法与机制概览
|
||||
为保持文档完整性,以下为算法专题的简要说明(不再拆分子文档):
|
||||
- **智能任务规划与贪心优化**:多维约束模型、覆盖多样性惩罚与任务筛选策略。
|
||||
- **空间几何与极速 Footprint**:降采样探测 + 凸包生成,结合拓扑简化提升效率。
|
||||
- **AI 诊断与图像处理**:百分比截断拉伸、质量评估特征与自动裁剪策略。
|
||||
|
||||
---
|
||||
|
||||
## 6. 环境与依赖
|
||||
- **OS**:Windows Server 2019+(推荐)
|
||||
- **数据库**:PostgreSQL 14+ + PostGIS
|
||||
- **Python**:推荐 Conda 环境(本项目 environment.yml 已固定为 Python 3.10)
|
||||
- **Nginx**:用于前端静态资源与反向代理(推荐)
|
||||
- **Node.js**:仅构建前端时需要
|
||||
- **IDL/ENVI**:仅启用 IDL 自动化时需要
|
||||
|
||||
---
|
||||
|
||||
## 7. 离线 Python 环境(推荐:conda-pack)
|
||||
|
||||
### 7.1 开发机(有外网)
|
||||
```powershell
|
||||
conda env create -f environment.yml
|
||||
conda activate InSAR
|
||||
```
|
||||
|
||||
如需授权功能,请确保安装 cryptography:
|
||||
```powershell
|
||||
pip install cryptography
|
||||
```
|
||||
|
||||
如需打包授权 GUI:
|
||||
```powershell
|
||||
pip install pyinstaller
|
||||
```
|
||||
|
||||
打包环境:
|
||||
```powershell
|
||||
conda install -n base conda-pack
|
||||
conda pack -n InSAR -o insar_env.tar.gz
|
||||
```
|
||||
|
||||
将 `insar_env.tar.gz` 拷贝到服务器。
|
||||
|
||||
### 7.2 服务器(离线)
|
||||
解压到目标目录(示例:`C:\envs\InSAR`):
|
||||
```powershell
|
||||
mkdir C:\envs\InSAR
|
||||
tar -xzf insar_env.tar.gz -C C:\envs\InSAR
|
||||
C:\envs\InSAR\Scripts\conda-unpack.exe
|
||||
```
|
||||
|
||||
如果没有 `tar`,可使用 7-Zip 解压。
|
||||
|
||||
在 `.env` 中配置:
|
||||
```
|
||||
PYTHON_PATH=C:\envs\InSAR\python.exe
|
||||
```
|
||||
`PYTHON_PATH` 也支持命令名(如 `python`),`scripts/start_app.ps1` 会通过 `Get-Command` 解析真实可执行文件路径。
|
||||
若使用 Conda,建议先激活 `InSAR` 环境后再启动,或直接填入该环境的 `python.exe` 绝对路径。
|
||||
|
||||
也可使用 Conda 原生模式(推荐):
|
||||
```
|
||||
CONDA_EXE=D:\anaconda3\Scripts\conda.exe
|
||||
CONDA_ENV_NAME=InSAR
|
||||
```
|
||||
当 `CONDA_ENV_NAME` 非空时,`scripts/start_app.ps1` 会优先使用
|
||||
`CONDA_EXE` 定位目标环境并解析其 `python.exe`,随后使用该解释器执行数据库检查、初始化、后端与 Worker 启动流程(避免并发 `conda run` 稳定性问题)。
|
||||
|
||||
**不建议直接复制 venv**,二进制依赖容易失效。
|
||||
|
||||
---
|
||||
|
||||
## 8. 数据库准备
|
||||
|
||||
### 8.1 创建数据库
|
||||
```powershell
|
||||
psql -U postgres -h localhost -p 5432
|
||||
CREATE DATABASE insar_management;
|
||||
\q
|
||||
```
|
||||
|
||||
### 8.2 启用 PostGIS
|
||||
```powershell
|
||||
psql -U postgres -h localhost -p 5432 -d insar_management
|
||||
CREATE EXTENSION IF NOT EXISTS postgis;
|
||||
\q
|
||||
```
|
||||
|
||||
### 8.3 配置连接并检测
|
||||
在 `.env` 中设置:
|
||||
```
|
||||
DATABASE_URL=postgresql+asyncpg://user:pass@host:5432/insar_management
|
||||
```
|
||||
|
||||
手动检查连接:
|
||||
```powershell
|
||||
D:\anaconda3\Scripts\conda.exe run -n InSAR python scripts/check_db_connection.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 授权(离线)
|
||||
|
||||
启动授权管理工具:
|
||||
```powershell
|
||||
D:\anaconda3\Scripts\conda.exe run -n InSAR python scripts/license_manager_gui.py
|
||||
```
|
||||
|
||||
使用流程:
|
||||
1) 选择 `.env` 路径
|
||||
2) 输入到期时间(UTC,格式:`YYYY-MM-DD HH:MM:SS`,留空默认 1 年)
|
||||
3) 点击“生成/续期授权”
|
||||
4) 自动写入 `.env` 中的 `LICENSE_SECRET` / `LICENSE_PUBLIC_KEY`
|
||||
5) 生成 `backend\license\license.lic` 与 `license_private_key.txt`
|
||||
|
||||
说明:
|
||||
- `license_private_key.txt` 请妥善保存(用于续期)。
|
||||
- 服务器只需要公钥与授权文件,不要部署私钥。
|
||||
- 建议仅在离线管理机保存私钥文件,避免在业务服务器落盘或备份私钥。
|
||||
- 离线授权属于本地合规校验机制:可显著提高误用门槛,但不等同于“对拥有服务器管理员权限场景”的绝对防篡改。
|
||||
|
||||
授权相关接口:
|
||||
```
|
||||
/api/license/status
|
||||
/api/license/upload (仅管理员可调用)
|
||||
/api/license/refresh (仅管理员可调用)
|
||||
```
|
||||
|
||||
返回约束(安全脱敏):
|
||||
- `GET /api/license/status` 默认仅返回 `ok`、`reason`、`expires_at`。
|
||||
- 当请求携带管理员会话时,`GET /api/license/status` 会额外返回调试字段(如 `fingerprint`、`license_path`)。
|
||||
|
||||
---
|
||||
|
||||
## 10. 关键配置(.env)
|
||||
常用参数示例:
|
||||
```
|
||||
# 服务显示地址(用于启动日志与前端展示)
|
||||
SERVER_HOST=192.168.1.100
|
||||
|
||||
# 后端端口(示例使用 18000,避免与常见本机服务冲突)
|
||||
PORT=18000
|
||||
|
||||
# 前端开发模式代理目标(仅 npm run dev 使用)
|
||||
VITE_BACKEND_TARGET=http://localhost:18000
|
||||
|
||||
# Python / Nginx 路径
|
||||
PYTHON_PATH=C:\envs\InSAR\python.exe
|
||||
CONDA_EXE=D:\anaconda3\Scripts\conda.exe
|
||||
CONDA_ENV_NAME=InSAR
|
||||
NGINX_PATH=C:\nginx-1.29.4\nginx.exe
|
||||
|
||||
# IDL / ENVI 路径与 worker 管理目录
|
||||
IDL_EXECUTABLE=C:\Program Files\Harris\ENVI56\IDL88\bin\bin.x86_64\idl.exe
|
||||
IDL_WORKBENCH_PATH=C:\Program Files\Harris\ENVI56\IDL88\bin\bin.x86_64\idlde.exe
|
||||
IDL_WORKER_SCRIPT_SOURCE_DIR=Z:\Code\Insar_management_system_v2\IDL\origin
|
||||
IDL_WORKER_SCRIPT_DIR=Z:\Code\Insar_management_system_v2\IDL\worker_managed
|
||||
IDL_WORKER_RUNTIME_DIR=Z:\Code\Insar_management_system_v2\backend\runtime\idl_worker
|
||||
IDL_WORKER_DEFAULT_TIMEOUT_SECONDS=14400
|
||||
IDL_WORKER_MAX_TIMEOUT_SECONDS=43200
|
||||
IDL_WORKER_TEMP_DIRECTORY=D:\Sarscape_IDL_Area
|
||||
|
||||
# IDL worker 默认任务参数(可被 API 请求体覆盖)
|
||||
IDL_IMPORT_ROOT_DIR=
|
||||
IDL_IMPORT_NUM_TO_PROCESS=0
|
||||
IDL_DINSAR_ROOT_DIR=
|
||||
IDL_DINSAR_DEM_BASE_FILE=
|
||||
IDL_DINSAR_NUM_TO_PROCESS=0
|
||||
IDL_DINSAR_TARGET_GROUND_RESOLUTION_M=10.0
|
||||
IDL_DINSAR_FILTER_METHOD=GOLDSTEIN
|
||||
IDL_DINSAR_UNWRAP_COH_THRESHOLD=0.05
|
||||
IDL_DINSAR_GCP_COH_THRESHOLD=0.7
|
||||
IDL_DINSAR_GCP_NUMBER=100
|
||||
IDL_DINSAR_GEOCODING_COH_THRESHOLD=0.0
|
||||
IDL_DINSAR_GEOCODING_PIXEL_SIZE_M=10.0
|
||||
|
||||
# 授权
|
||||
LICENSE_SECRET=...
|
||||
LICENSE_PUBLIC_KEY=...
|
||||
LICENSE_PATH=...\backend\license\license.lic
|
||||
LICENSE_STATE_PATH=...\backend\license\license_state.json
|
||||
MAX_LICENSE_UPLOAD_BYTES=1048576
|
||||
|
||||
# 鉴权(Session + Cookie)
|
||||
INIT_ADMIN_USERNAME=admin
|
||||
INIT_ADMIN_PASSWORD=<REQUIRED_STRONG_PASSWORD>
|
||||
INIT_ADMIN_RESET_PASSWORD=false
|
||||
SESSION_TTL_HOURS=12
|
||||
AUTH_SESSION_COOKIE_NAME=ims_session
|
||||
AUTH_COOKIE_SAMESITE=Lax
|
||||
AUTH_COOKIE_SECURE=false
|
||||
AUTH_LOGIN_MAX_FAILURES=5
|
||||
AUTH_LOGIN_WINDOW_SECONDS=900
|
||||
AUTH_LOGIN_LOCK_SECONDS=900
|
||||
AUTH_LOGIN_CLEANUP_INTERVAL_SECONDS=300
|
||||
|
||||
# AOI 上传限流
|
||||
AOI_UPLOAD_MAX_FILES=10
|
||||
AOI_UPLOAD_MAX_SINGLE_FILE_BYTES=20971520
|
||||
AOI_UPLOAD_MAX_TOTAL_BYTES=104857600
|
||||
|
||||
# 扫描请求边界
|
||||
MAX_SCAN_DIRECTORY_COUNT=64
|
||||
MAX_SCAN_PATH_LENGTH=2048
|
||||
|
||||
# 高频列表查询边界与超时
|
||||
LIST_QUERY_MAX_LIMIT=2000
|
||||
LIST_QUERY_MAX_OFFSET=200000
|
||||
LIST_QUERY_MAX_WINDOW=202000
|
||||
LIST_QUERY_TIMEOUT_MS=20000
|
||||
RADAR_SEARCH_OPTIONS_MAX_VALUES=5000
|
||||
RADAR_IMAGING_DATES_MAX_VALUES=5000
|
||||
|
||||
# 队列入队边界(防止异常大 payload 冲击数据库)
|
||||
JOB_QUEUE_MAX_PAYLOAD_BYTES=524288
|
||||
JOB_QUEUE_MAX_ATTEMPTS=10
|
||||
JOB_QUEUE_MAX_PRIORITY_ABS=1000
|
||||
JOB_QUEUE_MAX_ID_LENGTH=128
|
||||
JOB_QUEUE_MAX_TYPE_LENGTH=64
|
||||
|
||||
# 任务/日志查询边界(防止单次查询放大)
|
||||
TASK_ACTIVE_DEFAULT_LIMIT=100
|
||||
TASK_ACTIVE_MAX_LIMIT=500
|
||||
TASK_LOG_DEFAULT_LIMIT=100
|
||||
TASK_LOG_MAX_LIMIT=1000
|
||||
TASK_QUERY_MAX_OFFSET=500000
|
||||
MONITOR_LOG_DEFAULT_LIMIT=50
|
||||
MONITOR_LOG_MAX_LIMIT=200
|
||||
MONITOR_LOG_MAX_OFFSET=500000
|
||||
|
||||
# 批次与复制请求边界
|
||||
TASK_BATCH_MAX_ITEMS=5000
|
||||
TASK_BATCH_TEXT_MAX_LENGTH=256
|
||||
TASK_BATCH_REMARK_MAX_LENGTH=2000
|
||||
TASK_BATCH_LIST_DEFAULT_LIMIT=200
|
||||
TASK_BATCH_LIST_MAX_LIMIT=1000
|
||||
TASK_BATCH_LIST_MAX_OFFSET=500000
|
||||
COPY_BATCH_TEXT_MAX_LENGTH=2048
|
||||
COPY_BATCH_MAX_STATUS_COUNT=8
|
||||
|
||||
# 工作流请求边界
|
||||
WORKFLOW_MAX_STEPS=200
|
||||
WORKFLOW_MAX_DEPENDS=32
|
||||
WORKFLOW_TEXT_MAX_LENGTH=128
|
||||
|
||||
# AI 地图分析请求边界
|
||||
AI_ANALYZE_MAP_MAX_IMAGES=4
|
||||
AI_ANALYZE_MAP_MAX_IMAGE_BASE64_CHARS=12582912
|
||||
AI_ANALYZE_MAP_PROMPT_MAX_CHARS=8000
|
||||
|
||||
# 生产环境请使用白名单,不建议 *
|
||||
CORS_ORIGINS=http://192.168.1.100
|
||||
CORS_ALLOW_CREDENTIALS=true
|
||||
CORS_STRICT_MODE=false
|
||||
|
||||
# 统计缓存(秒)
|
||||
STATS_CACHE_TTL_SECONDS=120
|
||||
|
||||
# 预览缓存(可选)
|
||||
RADAR_THUMBNAIL_MAX_SIZE=1600
|
||||
RADAR_CACHE_WORKERS=2
|
||||
|
||||
# 灾害点数据源
|
||||
HAZARD_POINTS_DIR=Z:\Code\Insar_management_system_v2\backend\Point
|
||||
HAZARD_POINTS_FILENAME=Point.shp
|
||||
```
|
||||
|
||||
鉴权说明:
|
||||
- 系统不提供公开注册入口。
|
||||
- 首次启动或重置管理员密码时,`INIT_ADMIN_PASSWORD` 为必填项(不再提供默认密码回退)。
|
||||
- `scripts/init_db.py` 会检查管理员账号;若不存在则按 `INIT_ADMIN_*` 自动创建。
|
||||
- 如需强制重置管理员密码:将 `INIT_ADMIN_RESET_PASSWORD=true`(仅重置当次生效,完成后建议改回 `false`)。
|
||||
- 内网 HTTP 部署请保持 `AUTH_COOKIE_SECURE=false`;若启用 HTTPS,请改为 `true`。
|
||||
- 登录防爆破参数:`AUTH_LOGIN_MAX_FAILURES`(窗口内失败阈值)、`AUTH_LOGIN_WINDOW_SECONDS`(失败统计窗口)、`AUTH_LOGIN_LOCK_SECONDS`(触发后锁定时长)、`AUTH_LOGIN_CLEANUP_INTERVAL_SECONDS`(限流状态表后台清理间隔,秒)。
|
||||
- AOI 上传限流参数:`AOI_UPLOAD_MAX_FILES`(上传文件数量上限)、`AOI_UPLOAD_MAX_SINGLE_FILE_BYTES`(单文件字节上限)、`AOI_UPLOAD_MAX_TOTAL_BYTES`(同次请求总字节上限)。超限时接口返回 `HTTP 400`。
|
||||
- 扫描请求边界参数:`MAX_SCAN_DIRECTORY_COUNT`(单次扫描请求目录数量上限)、`MAX_SCAN_PATH_LENGTH`(单路径字符串长度上限)。
|
||||
- 高频列表查询边界参数:`LIST_QUERY_MAX_LIMIT`(单页最大返回条数)、`LIST_QUERY_MAX_OFFSET`(最大偏移)、`LIST_QUERY_MAX_WINDOW`(`limit+offset` 窗口上限),`LIST_QUERY_TIMEOUT_MS`(PostgreSQL 列表查询超时,毫秒);`RADAR_SEARCH_OPTIONS_MAX_VALUES` / `RADAR_IMAGING_DATES_MAX_VALUES` 用于限制雷达检索选项和成像日期去重结果规模。
|
||||
- 队列入队边界参数:`JOB_QUEUE_MAX_PAYLOAD_BYTES`(单任务 payload 字节上限)、`JOB_QUEUE_MAX_ATTEMPTS`(重试次数上限)、`JOB_QUEUE_MAX_PRIORITY_ABS`(优先级绝对值上限)、`JOB_QUEUE_MAX_ID_LENGTH`(关联 ID 字符上限)、`JOB_QUEUE_MAX_TYPE_LENGTH`(任务类型字符串上限)。
|
||||
- 任务/日志查询边界参数:`TASK_ACTIVE_DEFAULT_LIMIT`、`TASK_ACTIVE_MAX_LIMIT`(`GET /api/tasks/active` 分页上限),`TASK_LOG_DEFAULT_LIMIT`、`TASK_LOG_MAX_LIMIT`、`TASK_QUERY_MAX_OFFSET`(任务日志分页上限),`MONITOR_LOG_DEFAULT_LIMIT`、`MONITOR_LOG_MAX_LIMIT`、`MONITOR_LOG_MAX_OFFSET`(`GET /api/monitor/logs` 分页与偏移上限)。
|
||||
- 批次与复制请求边界参数:`TASK_BATCH_MAX_ITEMS`(单批次条目数上限)、`TASK_BATCH_TEXT_MAX_LENGTH`(批次名称/方向长度上限)、`TASK_BATCH_REMARK_MAX_LENGTH`(条目备注长度上限)、`TASK_BATCH_LIST_DEFAULT_LIMIT` / `TASK_BATCH_LIST_MAX_LIMIT` / `TASK_BATCH_LIST_MAX_OFFSET`(批次列表与明细分页上限)、`COPY_BATCH_TEXT_MAX_LENGTH`(复制接口 `batch_id`/`dest_dir` 长度上限)、`COPY_BATCH_MAX_STATUS_COUNT`(复制状态列表数量上限)。
|
||||
- 工作流请求边界参数:`WORKFLOW_MAX_STEPS`(单工作流步骤上限)、`WORKFLOW_MAX_DEPENDS`(单步骤依赖数上限)、`WORKFLOW_TEXT_MAX_LENGTH`(工作流文本字段长度上限)。
|
||||
- AI 地图分析请求边界参数:`AI_ANALYZE_MAP_MAX_IMAGES`(单次请求图片数上限)、`AI_ANALYZE_MAP_MAX_IMAGE_BASE64_CHARS`(单图 Base64 长度上限)、`AI_ANALYZE_MAP_PROMPT_MAX_CHARS`(prompt 长度上限)。
|
||||
- 授权上传默认采用“先校验后替换”的原子更新流程;无效授权不会覆盖当前在用授权文件。
|
||||
- `MAX_LICENSE_UPLOAD_BYTES` 用于限制授权上传文件大小(默认 1MB)。
|
||||
- `CORS_ORIGINS` 建议配置为明确白名单;生产环境不建议使用 `*`。
|
||||
- 当 `CORS_STRICT_MODE=true` 时,若 `CORS_ORIGINS` 包含 `*` 且 `CORS_ALLOW_CREDENTIALS=true`,服务将拒绝启动。
|
||||
- 严禁将包含真实 `DATABASE_URL`、`LICENSE_SECRET` 的 `.env` 提交到代码仓库或共享目录。
|
||||
- 管理员可通过 `GET /api/auth/audit-logs?limit=200` 查看最近审计日志(仅 admin 可访问)。
|
||||
- 许可文件上传与刷新接口(`POST /api/license/upload`、`POST /api/license/refresh`)仅允许管理员会话访问。
|
||||
- 前端入口:左侧「运行维护」分组下新增「用户管理」「审计日志」两个管理员面板。
|
||||
|
||||
源影像缓存说明:
|
||||
- 系统在扫描雷达源数据时,会尝试在每个场景目录中查找预览图(jpg/jpeg/png/webp/bmp/tif/tiff)并生成 WebP 缓存。
|
||||
- 雷达缓存目录:`backend/image_cache/radar_geo/`(主)与 `backend/image_cache/radar_raw/`(回退)。
|
||||
- D-InSAR 缓存目录:`backend/image_cache/dinsar/`(统一使用 `ID_{id}_{name}.webp`)。
|
||||
- 前端「数据列表」支持源影像单条显示/隐藏与“一键显示/一键隐藏”。
|
||||
- 长期稳定方案(后端预纠正缓存,B 方案)设计见:`docs/RADAR_PREVIEW_SCHEME_B.md`。
|
||||
|
||||
---
|
||||
|
||||
## 11. 启动方式
|
||||
|
||||
### 11.1 一键启动(推荐)
|
||||
双击:
|
||||
```
|
||||
start_system.bat
|
||||
```
|
||||
|
||||
或命令行:
|
||||
```powershell
|
||||
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\start_app.ps1
|
||||
```
|
||||
|
||||
说明:
|
||||
- 启动链路保持不变:`start_system.bat` -> `scripts/start_app.ps1` -> `check_db_connection.py` -> `init_db.py`。
|
||||
- `scripts/start_app.ps1` 中 `PYTHON_PATH` 支持“绝对路径”或“命令名”(例如 `python`);脚本会先解析为可执行文件再调用。
|
||||
- `init_db.py` 会在“建库/校验修复”后执行鉴权初始化(自动补齐管理员账号)。
|
||||
- 扫描调度默认 Manual-only,不会后台定时自动触发;需通过前端或 `/api/monitor/run-now` 手动触发扫描。
|
||||
- 默认不执行破坏性重建;若确需重建,请显式设置 `DB_SCHEMA_RESET_ON_MISMATCH=true`。
|
||||
|
||||
### 11.2 仅启动后端(无 Nginx)
|
||||
```powershell
|
||||
D:\anaconda3\Scripts\conda.exe run -n InSAR python run_backend.py
|
||||
```
|
||||
|
||||
说明:后端默认绑定 `127.0.0.1`,仅本机访问。
|
||||
|
||||
### 11.3 后台队列 Worker(必需)
|
||||
系统后台任务已改为“入队执行”,必须启动 Worker 才会真正执行扫描、AI、解包、复制等任务。
|
||||
|
||||
启动方式:
|
||||
```powershell
|
||||
D:\anaconda3\Scripts\conda.exe run -n InSAR python run_worker.py
|
||||
```
|
||||
|
||||
说明:
|
||||
- `start_system.bat` 已包含 Worker 启动。
|
||||
- 若只运行 `run_backend.py`,请手动启动 `run_worker.py`。
|
||||
- 数据分发/复制任务仅从“批次”读取,不再支持 Excel。
|
||||
- `POST /api/monitor/run-now` 的 `target` 仅支持:`radar`、`orbit`、`dinsar`(留空表示全部)。
|
||||
|
||||
Worker 可选环境变量:
|
||||
```
|
||||
JOB_WORKER_POLL_INTERVAL=1.0 # 轮询间隔(秒)
|
||||
JOB_WORKER_CONCURRENCY=1 # 并发执行数
|
||||
JOB_WORKER_JOB_HEARTBEAT_INTERVAL=5 # 运行中作业心跳上报间隔(秒)
|
||||
JOB_WORKER_STALE_RECOVER_INTERVAL=15 # 僵尸 RUNNING 作业回收检查间隔(秒)
|
||||
JOB_WORKER_STALE_RUNNING_SECONDS=300 # 判定作业僵尸的超时阈值(秒)
|
||||
IDL_JOB_MAX_ATTEMPTS=6 # IDL 队列任务最大重试次数
|
||||
```
|
||||
|
||||
ENVI/SARscape 集成(envipyengine):
|
||||
- 执行引擎:`envipyengine`(pip 包)→ `taskengine.exe` 子进程
|
||||
- 工作流在独立 Python 子进程中执行,隔离 FastAPI 主进程
|
||||
- D-InSAR 工作流自动检测未导入数据,先执行 Import 再处理 D-InSAR(智能串联)
|
||||
- DEM 路径为系统级配置,存储在 `.env` 的 `IDL_DINSAR_DEM_BASE_FILE`
|
||||
- 运行日志目录:`backend/runtime/idl_worker/`
|
||||
|
||||
ENVI 关键环境变量:
|
||||
```
|
||||
IDL_EXECUTABLE=... # IDL 可执行路径
|
||||
IDL_WORKBENCH_PATH=... # IDL Workbench 路径
|
||||
IDL_WORKER_RUNTIME_DIR=... # 运行日志目录
|
||||
IDL_WORKER_DEFAULT_TIMEOUT_SECONDS=14400 # 单任务执行超时(秒)
|
||||
IDL_WORKER_MAX_TIMEOUT_SECONDS=43200 # 单任务允许最大超时(秒)
|
||||
IDL_DINSAR_DEM_BASE_FILE=... # DEM 路径(D-InSAR 必需)
|
||||
```
|
||||
|
||||
API 端点(管理员):
|
||||
```
|
||||
GET /api/idl/status — 系统状态(含 DEM 路径和可用性)
|
||||
POST /api/idl/launch-workbench — 启动 IDL Workbench
|
||||
POST /api/idl/inspect/import — Import 预检查
|
||||
POST /api/idl/inspect/dinsar — D-InSAR 预检查(含 Import 状态检测)
|
||||
POST /api/idl/jobs/import — 提交 Import 任务
|
||||
POST /api/idl/jobs/dinsar — 提交 D-InSAR 任务
|
||||
GET /api/idl/jobs/recent — 最近运行记录
|
||||
```
|
||||
|
||||
提交任务请求体示例:
|
||||
```json
|
||||
{
|
||||
"root_dir": "Z:/Test_data/Test_IDL_1",
|
||||
"num_to_process": 0,
|
||||
"timeout_seconds": 14400
|
||||
}
|
||||
```
|
||||
|
||||
说明:
|
||||
- 提交后会创建系统任务并入队,由 `run_worker.py` 消费执行。
|
||||
- D-InSAR 工作流自动检测未导入数据,先执行 Import 再处理(智能串联)。
|
||||
- DEM 路径从 `.env` 读取,不需要在请求中传递。
|
||||
- Worker 在执行时会周期性更新作业心跳;若进程崩溃导致作业长期停留 `RUNNING`,会自动回收。
|
||||
- 运行日志保存在 `IDL_WORKER_RUNTIME_DIR/` 下。
|
||||
|
||||
### 11.4 前端构建(仅构建时需要)
|
||||
```powershell
|
||||
cd frontend
|
||||
npm install
|
||||
npm run build
|
||||
```
|
||||
|
||||
说明:
|
||||
- 前端日期控件已统一为 `flatpickr`(原生实例接入),无需浏览器原生 `input[type=date]`。
|
||||
- 若采用“本地构建后上传 `frontend/dist`”部署方式,服务器端无需额外安装前端依赖包。
|
||||
|
||||
### 11.5 离线切片底图切换
|
||||
前端从 `frontend/public/tiles/` 读取离线切片,当前支持三套(统一 `webp`):
|
||||
- `tiles/google_image/{z}/{x}/{y}.webp`
|
||||
- `tiles/gaode_image/{z}/{x}/{y}.webp`
|
||||
- `tiles/gaode_shp/{z}/{x}/{y}.webp`
|
||||
|
||||
地图右上角「底图」按钮可在三套底图间切换(默认 `gaode_shp`,便于仅下载高德矢量时直接可用)。
|
||||
|
||||
缩放级别已放开到 0-16;如需调整默认底图或级别,请修改 `frontend/src/App.jsx` 中的 `TILE_LAYER_DEFAULT_KEY` 与 `TILE_LAYER_OPTIONS`。
|
||||
|
||||
行政区边界叠加图层(地图参考边界)默认读取:
|
||||
- `frontend/public/geojson/全国行政区.geojson`
|
||||
|
||||
建议使用 UTF-8 无 BOM 编码,且文件体积较大时优先通过 `public/` 静态加载(不要直接放入 `frontend/src` 参与打包)。
|
||||
|
||||
地图左上角新增「区域定位」按钮(可折叠),复用行政区二级级联(省/市):
|
||||
- 可只选择到省级或市级进行定位。
|
||||
- 定位时会调用后端 `/api/aoi/regions/{tree_id}/geometry` 获取边界并自动缩放到该区域。
|
||||
- 定位成功后,面板会显示“当前定位”行政区名称,方便与当前视角对照。
|
||||
- 面板支持「清除定位高亮」,可一键移除地图上的定位边界高亮。
|
||||
- 依赖 `backend/geojson/层级映射.json` 与 `backend/geojson/全国行政区.geojson` 的行政区数据。
|
||||
|
||||
---
|
||||
|
||||
## 12. 访问与验证
|
||||
- **前端**:`http://SERVER_HOST`
|
||||
- **后端(内部)**:`http://127.0.0.1:8000`
|
||||
- **接口文档(内部)**:`http://127.0.0.1:8000/docs`
|
||||
|
||||
前端语言切换:
|
||||
- 顶部状态栏新增 `中文 / EN` 切换按钮。
|
||||
- 切换结果会保存在浏览器本地(`localStorage`),刷新页面后保持上次语言选择。
|
||||
- 站内文案翻译映射位于 `frontend/src/i18n/translations.js`,新增界面文案时需同步补充映射,避免中英文混杂。
|
||||
- 第二轮已补齐高频业务模块(批次/分发、运维自检、用户管理、审计日志、IDL 自动化、统计面板)常用文案映射。
|
||||
- 第三轮已支持运行日志双语展示:右侧日志栏、数据监控日志、数据分发任务日志会随语言切换进行前端翻译。
|
||||
- 当前词典已覆盖常见后端运行日志模板(扫描、拷贝、解包、任务生命周期);若新增日志模板,建议同步补充词典条目。
|
||||
- 收口说明:当前前端可见业务文案已基本完成双语覆盖,剩余英文主要为样式类名/技术标识(不面向客户显示)。
|
||||
|
||||
---
|
||||
|
||||
## 13. 常用脚本说明
|
||||
- `start_system.bat`:一键启动入口(调用 PowerShell 启动脚本)
|
||||
- `scripts/start_app.ps1`:启动后端 + Nginx,并执行数据库检查
|
||||
- `run_backend.py`:仅启动后端服务
|
||||
- `run_worker.py`:后台任务 Worker(队列执行)
|
||||
- `scripts/check_db_connection.py`:数据库连接检查
|
||||
- `scripts/init_db.py`:数据库结构检查与初始化
|
||||
- `scripts/license_manager_gui.py`:授权文件生成与续期
|
||||
- `scripts/get_fingerprint.py`:机器指纹获取
|
||||
- `scripts/unpack_archives.py`:解包与数据整理(支持 tar.gz)
|
||||
- `scripts/pack_environment.bat`:打包 Conda 环境(如使用)
|
||||
- `scripts/auth_smoke_check.py`:鉴权冒烟检查(401/403/200 核心链路)
|
||||
|
||||
---
|
||||
|
||||
## 14. 常见问题
|
||||
|
||||
1) **Python executable not found**
|
||||
检查 `.env` 中 `PYTHON_PATH` 是否正确;可使用绝对路径,或在已激活 Conda 环境后填写 `python`。
|
||||
如启用 Conda 原生模式,还需确认 `CONDA_EXE` 与 `CONDA_ENV_NAME` 配置正确。
|
||||
|
||||
2) **数据库连接失败**
|
||||
确认 PostgreSQL 服务已启动、`DATABASE_URL` 正确,并运行 `scripts/check_db_connection.py` 测试。
|
||||
|
||||
3) **授权无效/过期**
|
||||
使用授权工具续期,检查系统时间是否回退过多。
|
||||
|
||||
4) **前端打不开但后端正常**
|
||||
确认 Nginx 已启动、`NGINX_PATH` 正确、前端已构建。
|
||||
若启动时报 `no "events" section in configuration`,请检查 `nginx/nginx.conf` 是否为空或损坏,需恢复包含 `events {}` 与 `http {}` 的完整配置。
|
||||
|
||||
5) **后端端口被占用(Address already in use / Access denied)**
|
||||
`scripts/start_app.ps1` 启动前会检查 `PORT` 对应监听占用;若被占用会直接报错并终止。
|
||||
请停止占用该端口的进程,或在 `.env` 中修改 `PORT` 后重试。
|
||||
脚本会在启动时自动把 `nginx/nginx.conf` 中的 `proxy_pass` 同步到该 `PORT`,无需手动改 Nginx 反向代理端口。
|
||||
|
||||
6) **后端或 Worker 启动后立即退出**
|
||||
`scripts/start_app.ps1` 会在拉起后端与 Worker 后做快速存活检查;若进程秒退,脚本会立即报错并退出。
|
||||
请优先检查终端输出与后端日志,常见原因包括端口冲突、依赖缺失、环境变量配置错误。
|
||||
|
||||
最小可用 `nginx.conf` 模板(按需替换路径,`proxy_pass` 端口需与 `.env` 的 `PORT` 一致):
|
||||
```nginx
|
||||
worker_processes 1;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name localhost;
|
||||
|
||||
root "G:/Code/Insar_management_system_v2/frontend/dist";
|
||||
index index.html;
|
||||
|
||||
location / {
|
||||
try_files $uri $uri/ /index.html;
|
||||
}
|
||||
|
||||
location /api/ {
|
||||
proxy_pass http://127.0.0.1:18000;
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
|
||||
location /image_cache/ {
|
||||
alias "G:/Code/Insar_management_system_v2/backend/image_cache/";
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 14.1 鉴权与登录排查
|
||||
|
||||
- **`GET /api/auth/me` 返回 `401`**:未登录状态下这是正常现象,前端应显示登录页。
|
||||
- **`POST /api/auth/login` 返回 `401`**:用户名或密码错误,先核对 `.env` 中 `INIT_ADMIN_USERNAME/INIT_ADMIN_PASSWORD`;若为首次启动且 `INIT_ADMIN_PASSWORD` 为空,初始化会直接报错。
|
||||
- **`POST /api/auth/login` 返回 `429`**:触发登录限流/锁定,等待 `Retry-After` 秒后重试,或调整 `AUTH_LOGIN_MAX_FAILURES / AUTH_LOGIN_WINDOW_SECONDS / AUTH_LOGIN_LOCK_SECONDS`。
|
||||
- **登录后接口仍持续 `401`**:检查浏览器是否携带 Cookie(请求需 `withCredentials=true`),并确认反向代理没有丢弃 `Set-Cookie` 头。
|
||||
- **需要重置管理员密码**:设置 `INIT_ADMIN_RESET_PASSWORD=true`,重启一次系统完成重置后再改回 `false`。
|
||||
- **Cookie 在内网 HTTP 不生效**:确认 `AUTH_COOKIE_SECURE=false`;只有 HTTPS 场景才应设置为 `true`。
|
||||
- **需要核查谁触发了高风险写操作**:使用 `GET /api/auth/audit-logs?limit=200`(管理员会话)。
|
||||
- **需要快速回归鉴权链路**:后端启动后运行 `D:\anaconda3\Scripts\conda.exe run -n InSAR python scripts/auth_smoke_check.py --base-url http://127.0.0.1:8000`。
|
||||
|
||||
5) **任务一直处于“执行中”**
|
||||
确认 `run_worker.py` 已启动(或使用 `start_system.bat` 一键启动)。
|
||||
- 若期望系统自动周期扫描:当前默认是手动模式(Manual-only),请使用前端“立即扫描”或调用 `POST /api/monitor/run-now`。
|
||||
|
||||
6) **配对结果异常或为空**
|
||||
- 影像日期需为 `YYYYMMDD` 格式(系统从文件夹名解析)。
|
||||
- AOI 支持 **SHP 上传** 与 **GeoJSON 传参**(`aoi_geojson`)两种模式。
|
||||
- AOI Shapefile 建议使用 EPSG:4326(WGS84),否则需先进行投影转换。
|
||||
- 若使用“行政区选择”,请准备标准 GeoJSON(见 17.7),并确保 `features[*].properties.treeID` 与 `backend/geojson/层级映射.json` 对应。
|
||||
|
||||
7) **灾害点导入为 0 条**
|
||||
请确保 Shapefile 中存在唯一标识字段(优先 `TYBH`),并具备经纬度或点几何:
|
||||
- 必填:`TYBH`(唯一编号)
|
||||
- 推荐:`hazard_type` / `hazard_name` / `city` / `county` / `township`
|
||||
- 坐标:点几何优先;若无几何,则读取经度/纬度字段
|
||||
数据源路径:`.env` 中 `HAZARD_POINTS_DIR` + `HAZARD_POINTS_FILENAME`(默认 `backend/Point/Point.shp`)。
|
||||
|
||||
8) **Excel 功能找不到了**
|
||||
Excel 导入/导出已移除,请使用“任务批次 + 数据分发”流程完成复制与人工状态管理。
|
||||
|
||||
9) **日志出现 “got Future attached to a different loop”**
|
||||
该错误通常由**子线程内创建事件循环并复用 asyncpg 连接**引起,多发生在自建 `asyncio.run(...)` 的耗时任务里。
|
||||
处理方式:
|
||||
- 确保所有耗时任务由 Worker 执行(统一事件循环),避免在线程内自行 `asyncio.run`。
|
||||
- 重启后端与 Worker 以释放异常连接。
|
||||
- 如仍复现,请检查是否有自定义脚本在子线程内直接调用数据库。
|
||||
|
||||
---
|
||||
|
||||
## 15. 数据库结构自动对齐(重要)
|
||||
系统每次启动会进行数据库结构检查:
|
||||
- 首次启动:自动创建表结构。
|
||||
- 后续启动:校验表/列/类型/可空性。
|
||||
- 不一致(默认):不做破坏性重建,仅补齐缺失结构并输出告警。
|
||||
- 不一致(显式开启):仅当 `DB_SCHEMA_RESET_ON_MISMATCH=true` 时执行 drop/recreate 重建。
|
||||
|
||||
推荐在生产/内网环境保持如下默认值(保留历史数据):
|
||||
```
|
||||
DB_SCHEMA_RESET_ON_MISMATCH=false
|
||||
```
|
||||
|
||||
### 15.1 SQL 函数更新说明
|
||||
若修改了 `backend/migrations/002_spatial_functions.sql`(例如配对函数 `find_dinsar_pairs`),需要执行一次:
|
||||
- 重新启动系统(`start_system.bat` 会自动调用 `scripts/init_db.py`)
|
||||
- 或手动运行 `D:\anaconda3\Scripts\conda.exe run -n InSAR python scripts/init_db.py`
|
||||
|
||||
### 15.2 空间函数迁移(必需)
|
||||
启动时会自动执行以下迁移文件(用于空间函数、视图与日志表):
|
||||
- `backend/migrations/001_st_intersection_agg.sql`
|
||||
- `backend/migrations/002_spatial_functions.sql`
|
||||
|
||||
如需手工修改,请保证 SQL 语法正确且使用 UTF-8 编码。
|
||||
|
||||
---
|
||||
|
||||
## 16. 工作流与任务队列(新增)
|
||||
后台任务已改为队列执行(DB Job Queue):
|
||||
- 复制、解包、扫描、AI 训练/预测/诊断等任务都会入队。
|
||||
- Worker 执行任务并更新状态,前端可查看任务进度。
|
||||
- 所有耗时任务在 Worker 的事件循环中执行,避免跨事件循环导致的 asyncpg 连接异常。
|
||||
|
||||
工作流 API:
|
||||
- `POST /api/workflow/runs` 创建工作流实例
|
||||
- `GET /api/workflow/runs/{run_id}` 查询运行状态与步骤
|
||||
|
||||
任务查询 API(新增分页参数):
|
||||
- `GET /api/tasks/active?limit=100&offset=0`
|
||||
- `GET /api/tasks/{task_id}`
|
||||
- `GET /api/tasks/{task_id}/logs?limit=100&offset=0`
|
||||
- `GET /api/monitor/logs?limit=50&offset=0`
|
||||
|
||||
---
|
||||
|
||||
## 17. 任务批次管理(替代 Excel)
|
||||
为适配 IDL 线下生产流程,D-InSAR 与 PS-InSAR 任务改为“批次”形式存储在数据库:
|
||||
- 前端创建批次(不再使用 Excel 导入/导出)。
|
||||
- 可按批次查看明细、手动标记完成、添加备注。
|
||||
- 数据分发从批次中读取任务条目并复制到目标目录。
|
||||
- Excel 导入/导出接口已移除。
|
||||
|
||||
相关 API:
|
||||
- `POST /api/task-batches/dinsar`
|
||||
- `GET /api/task-batches/dinsar?limit=200&offset=0`
|
||||
- `GET /api/task-batches/dinsar/{batch_id}/items?limit=200&offset=0`
|
||||
- `PATCH /api/task-batches/dinsar/{batch_id}/complete-all`
|
||||
- `PATCH /api/task-batches/dinsar/items/{item_id}`
|
||||
- `POST /api/task-batches/ps`
|
||||
- `GET /api/task-batches/ps?limit=200&offset=0`
|
||||
- `GET /api/task-batches/ps/{batch_id}/items?limit=200&offset=0`
|
||||
- `PATCH /api/task-batches/ps/{batch_id}/complete-all`
|
||||
- `PATCH /api/task-batches/ps/items/{item_id}`
|
||||
|
||||
数据分发 API:
|
||||
- `POST /api/tools/copy-ps-stack` (body: `batch_id`, `dest_dir`, `copy_statuses?`)
|
||||
- `POST /api/tools/copy-dinsar-pairs` (body: `batch_id`, `dest_dir`, `copy_statuses?`)
|
||||
- `GET /api/tools/copy-status/{task_id}?limit=100&offset=0`
|
||||
|
||||
### 17.1 前端操作流程(推荐)
|
||||
1) **生成候选列表**
|
||||
- D-InSAR:左侧“干涉对配对”生成 pairs。
|
||||
- PS-InSAR:左侧“PS 时序栈”生成 stack。
|
||||
|
||||
2) **保存为批次**
|
||||
- 点击“保存批次”,系统将当前列表写入数据库。
|
||||
- 批次会自动生成 `batch_id`,支持自定义名称。
|
||||
- 保存成功后前端会自动切换到“任务批次”并定位到新建批次。
|
||||
|
||||
3) **批次管理与人工标注**
|
||||
- 在右侧“任务批次”面板选择 D-InSAR 或 PS。
|
||||
- 进入某批次后:
|
||||
- 修改每条任务状态(PENDING / IN_PROGRESS / COMPLETED / FAILED)
|
||||
- 接口仅接受上述四种状态值(大小写不敏感,非法值会返回 `400`)
|
||||
- 为每条任务添加备注(如:文件损坏、未完成原因)
|
||||
- 支持“一键全部完成”
|
||||
|
||||
4) **数据分发(复制)**
|
||||
- 进入“数据分发”面板选择批次与目标目录后启动复制。
|
||||
- 可按任务状态筛选复制(默认仅复制 `COMPLETED`)。
|
||||
- 复制任务由 Worker 执行,进度可在前端日志中查看。
|
||||
- 若出现部分文件复制失败,任务最终会标记为 `FAILED`(不再误报成功)。
|
||||
|
||||
### 17.2 批次数据结构(数据库)
|
||||
系统会创建以下表:
|
||||
- `dinsar_task_batches` / `dinsar_task_items`
|
||||
- `ps_task_batches` / `ps_task_items`
|
||||
|
||||
每条 item 包含:
|
||||
- `status`(人工状态)
|
||||
- `remark`(备注)
|
||||
- 任务路径与元数据(用于复制与后续追踪)
|
||||
|
||||
### 17.3 数据分发复制规则
|
||||
**PS-InSAR:**
|
||||
- 复制源优先使用 `{original_path}_envi_import`(若存在)
|
||||
- 否则使用原始路径
|
||||
|
||||
**D-InSAR:**
|
||||
- 为每个任务创建子目录:
|
||||
- `<dest>/<task_name>/master`
|
||||
- `<dest>/<task_name>/slave`
|
||||
- 若源目录内存在 `envi_import` 且非空,优先复制 `envi_import`
|
||||
|
||||
### 17.4 Excel 迁移说明
|
||||
历史流程若依赖 Excel:
|
||||
- 现在请在系统中生成批次并使用“数据分发”复制。
|
||||
- 复制任务仅接收 `batch_id`,不再接收 Excel 文件。
|
||||
|
||||
### 17.5 API 示例(简化)
|
||||
创建 D-InSAR 批次:
|
||||
```json
|
||||
POST /api/task-batches/dinsar
|
||||
{
|
||||
"name": "DINSAR_20260208",
|
||||
"pairs": [
|
||||
{
|
||||
"task_name": "T001",
|
||||
"master": {"file_path": "D:/data/master1", "...": "..."},
|
||||
"slave": {"file_path": "D:/data/slave1", "...": "..."},
|
||||
"time_baseline_days": 12,
|
||||
"spatial_baseline_meters": 1234.5
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
复制 D-InSAR 批次:
|
||||
```json
|
||||
POST /api/tools/copy-dinsar-pairs
|
||||
{
|
||||
"batch_id": "xxxx-xxxx-xxxx",
|
||||
"dest_dir": "D:/IDL/tasks/dinsar",
|
||||
"copy_statuses": ["COMPLETED"]
|
||||
}
|
||||
```
|
||||
|
||||
### 17.6 配对参数说明(新增)
|
||||
`/api/find-pairs` 支持以下可选参数(表单提交):
|
||||
- `require_same_imaging_mode`:是否要求主/辅影像成像模式一致(默认 `true`,空值视为不匹配)
|
||||
- `require_same_polarization`:是否要求主/辅影像极化一致(默认 `true`,空值视为不匹配)
|
||||
- `aoi_overlap_threshold`:AOI 覆盖比例阈值,计算方式为 `Area(image ∩ AOI) / Area(AOI)`,需主/辅影像同时满足
|
||||
- `aoi_geojson`:可选,标准 GeoJSON 字符串(与 `files` 二选一或都不传)
|
||||
|
||||
系统会在候选配对数超过 3000 时返回提示信息。若数据库配对函数不可用,仅在影像数量 ≤1500 时允许回退计算,并返回强提醒。
|
||||
|
||||
前端已提供“成像模式一致 / 极化一致 / AOI 覆盖率阈值”的开关与输入框,默认关闭/0(不额外限制)。
|
||||
|
||||
### 17.7 行政区选择(SHP/行政区二选一)
|
||||
生产规划弹窗中,AOI 来源支持:
|
||||
- 上传 `SHP`(兼容旧流程)
|
||||
- 行政区选择(省/市二级联动)
|
||||
|
||||
后端新增接口:
|
||||
- `GET /api/aoi/regions/children?parent_tree_id=1`
|
||||
- `GET /api/aoi/regions/{tree_id}/geometry`
|
||||
|
||||
相关环境变量(后端 `.env`):
|
||||
```
|
||||
AOI_REGION_INDEX_FILE=backend/geojson/层级映射.json
|
||||
AOI_REGION_GEOJSON_FILE=backend/geojson/全国行政区.geojson
|
||||
```
|
||||
|
||||
### 17.8 行政区 GeoJSON 数据规范(需提前准备)
|
||||
建议提供一个标准 `FeatureCollection` 文件(UTF-8),示例:
|
||||
```json
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [
|
||||
{
|
||||
"type": "Feature",
|
||||
"properties": {
|
||||
"treeID": "1-23-4",
|
||||
"name": "示例地市",
|
||||
"level": "city"
|
||||
},
|
||||
"geometry": {
|
||||
"type": "MultiPolygon",
|
||||
"coordinates": [[[ [116.0, 39.0], [116.2, 39.0], [116.2, 39.2], [116.0, 39.2], [116.0, 39.0] ]]]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
字段要求:
|
||||
- 必填:`properties.treeID`(与 `层级映射.json` 一致)
|
||||
- 可选:`properties.name`、`properties.level`
|
||||
- `geometry` 建议使用 EPSG:4326(经纬度)
|
||||
|
||||
说明:
|
||||
- 当前前端行政区选择为省/市二级,不提供区县单独选择入口。
|
||||
- 若仅提供区县级数据,系统会在省/市选择时自动合并其下级边界。
|
||||
- 若仅提供到市级数据,前端可直接按市级选择,后端会在必要时回退到最近可用上级边界(市→省)。
|
||||
- 若 `treeID` 不匹配或缺失,行政区选择将无法返回 AOI 边界。
|
||||
|
||||
---
|
||||
|
||||
## 18. 运维自检(新增)
|
||||
系统提供运维自检接口与前端面板,用于快速判断关键服务状态。
|
||||
|
||||
后端接口:
|
||||
```
|
||||
GET /api/health
|
||||
```
|
||||
|
||||
返回约束(安全脱敏):
|
||||
- 未登录或只读会话:返回最小健康摘要(`ok/timestamp` + 各子系统布尔状态与基础计数)。
|
||||
- 管理员会话:返回完整诊断信息(包含错误详情、Worker 列表、外部服务状态细节)。
|
||||
|
||||
检查内容:
|
||||
- 数据库连接 / PostGIS 扩展 / 关键表结构
|
||||
- 关键表覆盖包含:任务与队列(`system_tasks/task_logs/system_jobs/scan_states`)、工作流(`workflow_defs/workflow_runs/workflow_steps/workflow_artifacts`)、鉴权(`auth_users/auth_sessions/auth_audit_logs/auth_rate_limits`)等核心表
|
||||
- Worker 心跳
|
||||
- IDL/ENVI 状态
|
||||
- Ollama 连通性
|
||||
- Nginx 连通性
|
||||
- 一致性异常汇总(来自 `GET /api/statistics`):
|
||||
- D-InSAR 缓存:数据库标记与实际缓存文件是否一致
|
||||
- 源影像预览缓存:`READY` 状态与 `radar_geo/radar_raw` 文件是否一致
|
||||
- XML 读取:是否检测到 XML 以及关键字段是否成功入库
|
||||
|
||||
相关环境变量:
|
||||
```
|
||||
JOB_WORKER_HEARTBEAT_INTERVAL=5 # Worker 心跳上报间隔(秒)
|
||||
JOB_WORKER_HEALTH_TIMEOUT=60 # 运维自检判定 Worker 离线的阈值(秒)
|
||||
```
|
||||
|
||||
前端入口:
|
||||
- 左侧「系统工具」->「运维自检」
|
||||
- 顶部状态栏可一键刷新自检状态
|
||||
- 运维自检面板新增「一致性检测」卡片,显示异常总数、严重/一般异常,并列出具体异常项
|
||||
|
||||
---
|
||||
|
||||
## 19. 一致性统计接口(新增)
|
||||
用于检查“数据库记录、缓存文件、XML读取结果”是否一致。
|
||||
|
||||
后端接口:
|
||||
```
|
||||
GET /api/statistics
|
||||
```
|
||||
|
||||
新增返回字段(关键):
|
||||
- `dinsar_cache_consistency`
|
||||
- `db_cached_but_file_missing_count`:数据库标记已缓存,但缓存文件不存在
|
||||
- `db_uncached_but_file_exists_count`:数据库标记未缓存,但缓存文件已存在
|
||||
- `manifest_missing_file_count`:`cache_manifest.json` 记录的缓存文件缺失
|
||||
- `source_preview_consistency`
|
||||
- `preview_missing_count`:源影像预览缓存缺失(`radar_geo` 和 `radar_raw` 均不存在)
|
||||
- `db_ready_but_cache_missing_count`:数据库 `READY`,但预览缓存实际缺失
|
||||
- `source_xml_consistency`
|
||||
- `xml_missing_count`:未检测到 XML
|
||||
- `xml_detected_but_unparsed_count`:检测到 XML 但关键字段未成功入库
|
||||
|
||||
前端展示位置:
|
||||
- 「数据统计仪表盘」:新增一致性图表与异常高亮
|
||||
- 「运维自检」:新增一致性异常汇总卡片(总数 / 严重 / 一般)
|
||||
|
||||
建议运维动作:
|
||||
- 出现 `db_cached_but_file_missing_count` 或 `db_ready_but_cache_missing_count`:执行一次重扫重建缓存
|
||||
- 出现 `xml_detected_but_unparsed_count`:检查对应场景 XML 格式与解析字段
|
||||
- 出现 `xml_missing_count`:检查场景目录是否包含 XML 元数据文件
|
||||
|
||||
---
|
||||
|
||||
*© 2026 InSAR Management System - 致力于更高效、更智能的雷达遥感监测*
|
||||
@@ -0,0 +1,426 @@
|
||||
# D-InSAR 增强 Task 文档
|
||||
|
||||
更新日期:2026-03-13
|
||||
|
||||
## 1. 文档目的
|
||||
|
||||
这份文档用于统一管理本轮 D-InSAR 系统重构任务,覆盖以下两大方向:
|
||||
|
||||
- D-InSAR 多引擎生产系统增强
|
||||
- D-InSAR 结果管理系统增强
|
||||
|
||||
文档定位是“任务总表 + 实施边界 + 验收口径”,用于后续分阶段实施、联调和运维验收。
|
||||
|
||||
## 2. 背景
|
||||
|
||||
当前系统的 D-InSAR 生产能力主要绑定在 ENVI + SARscape 链路上,前端、后端任务模型、运行监控和结果入库都默认只有一个处理引擎。这会带来两个直接问题:
|
||||
|
||||
- 客户如果没有 ENVI/SARscape license,D-InSAR 核心生产能力会直接失效。
|
||||
- 后续接入 ISCE2、LANDSAR 时,只能继续堆在现有 IDL/ENVI 语义上,导致架构持续恶化。
|
||||
|
||||
同时,当前 D-InSAR 结果管理能力偏轻,更多是“结果浏览 + 缓存管理”,还不足以支撑多引擎、多版本、多产物、多轮生产的正式管理需求。
|
||||
|
||||
## 3. 本轮目标
|
||||
|
||||
### 3.1 生产系统目标
|
||||
|
||||
- 将 D-InSAR 生产系统升级为“多引擎生产中心”
|
||||
- 正式支持两类可执行引擎:
|
||||
- `sarscape`:ENVI + SARscape
|
||||
- `isce2`:WSL 内部署的 ISCE2
|
||||
- 预留第三类引擎:
|
||||
- `landsar`:仅保留接口和前端占位,不实现算法
|
||||
- 将运维自检升级为“系统级 + D-InSAR 专项级”双层检查
|
||||
|
||||
### 3.2 结果管理目标
|
||||
|
||||
- 将 D-InSAR 结果从“单文件记录”升级为“可追踪、可治理、可比对”的结果资产
|
||||
- 支持来源追踪、版本管理、多产物管理、审核状态和运维治理
|
||||
- 让结果管理适配多引擎场景,而不是继续依赖单一文件命名和单一路径假设
|
||||
|
||||
## 4. 范围与非范围
|
||||
|
||||
### 4.1 本轮范围
|
||||
|
||||
- 多引擎 D-InSAR 后端抽象层
|
||||
- 新 D-InSAR 生产中心前端架构
|
||||
- ENVI/SARscape 的兼容接入
|
||||
- ISCE2 的 WSL 环境校验与运行接口
|
||||
- 运维自检与 D-InSAR 专项健康检查
|
||||
- D-InSAR 结果管理增强
|
||||
|
||||
### 4.2 明确不在本轮范围
|
||||
|
||||
- LANDSAR 算法实现
|
||||
- 对现有配对算法本身做大幅重写
|
||||
- 对 AI 诊断能力做独立重构
|
||||
- 一次性替换全部旧接口
|
||||
|
||||
## 5. 现状要点
|
||||
|
||||
### 5.1 ENVI 现有处理模式
|
||||
|
||||
当前 ENVI 侧应明确建模为 `sarscape` 引擎下的两种 profile:
|
||||
|
||||
- `metatask`
|
||||
- `custom6`
|
||||
|
||||
其中 `custom6` 的 6 步链路为:
|
||||
|
||||
1. Interferogram Generation
|
||||
2. Filtering and Coherence
|
||||
3. Orbital Trend Removal
|
||||
4. Phase Unwrapping
|
||||
5. GCP Generation + Refinement
|
||||
6. Phase to Displacement + Geocoding
|
||||
|
||||
### 5.2 ISCE2 的特殊约束
|
||||
|
||||
ISCE2 不走本机 Windows 直接执行链路,默认按 WSL 方式部署与运行,因此必须把 WSL 环境校验设计成正式能力,而不是上线前人工检查事项。
|
||||
|
||||
### 5.3 当前结果管理主要短板
|
||||
|
||||
- 结果记录字段偏少,缺少引擎来源、运行来源、版本、产物类型等核心字段
|
||||
- 结果入库逻辑仍偏向单文件扫描,不适合多引擎多版本
|
||||
- 前端结果面板偏重展示,缺少治理、比对、版本和来源追踪能力
|
||||
- 运维自检尚未覆盖结果资产层面的异常
|
||||
|
||||
## 6. Task 总览
|
||||
|
||||
| Task ID | 主题 | 目标 |
|
||||
|---|---|---|
|
||||
| T1 | 多引擎生产基座 | 建立统一的 D-InSAR 引擎抽象、调度和兼容层 |
|
||||
| T2 | 前端生产中心 | 将现有 IDL 自动化面板升级为多引擎生产中心 |
|
||||
| T3 | SARSCAPE 兼容接入 | 将现有 ENVI 链路适配到新抽象层 |
|
||||
| T4 | ISCE2 + WSL 接入 | 建立 ISCE2 的 WSL 检查、运行和日志链路 |
|
||||
| T5 | 运维自检增强 | 增加多引擎专项健康检查和自愈入口 |
|
||||
| T6 | 结果管理增强 | 将 D-InSAR 结果升级为正式结果资产管理体系 |
|
||||
|
||||
## 7. 任务拆分
|
||||
|
||||
### T1. 多引擎生产基座
|
||||
|
||||
#### 目标
|
||||
|
||||
- 将 D-InSAR 生产从 `IDL/ENVI 单引擎假设` 中抽离
|
||||
- 为 `sarscape`、`isce2`、`landsar` 建立统一抽象
|
||||
|
||||
#### 关键任务
|
||||
|
||||
- 定义统一引擎接口
|
||||
- 建立引擎注册表
|
||||
- 建立统一运行记录模型
|
||||
- 建立统一产物收集与入库接口
|
||||
- 保留旧 `/idl/...` 路由兼容转发能力
|
||||
|
||||
#### 目标结构
|
||||
|
||||
- `dinsar_engines/base.py`
|
||||
- `dinsar_engines/registry.py`
|
||||
- `dinsar_engines/sarscape_engine.py`
|
||||
- `dinsar_engines/isce2_engine.py`
|
||||
- `dinsar_engines/landsar_engine.py`
|
||||
- `dinsar_orchestrator.py`
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- 新生产调度层可按 `engine_code` 分发
|
||||
- `sarscape`、`isce2`、`landsar` 均可在注册表中查询到
|
||||
- 旧 ENVI 生产功能不回归
|
||||
|
||||
### T2. 前端生产中心
|
||||
|
||||
#### 目标
|
||||
|
||||
- 用统一生产中心替代当前单引擎 `IDLAutomationPanel`
|
||||
- 支持多引擎选择、运行监控、日志查看和结果入库操作
|
||||
|
||||
#### 前端信息架构
|
||||
|
||||
- 引擎状态区
|
||||
- SARSCAPE
|
||||
- ISCE2
|
||||
- LANDSAR
|
||||
- 生产提交区
|
||||
- 批次选择
|
||||
- 引擎选择
|
||||
- profile 选择
|
||||
- 输入/输出目录
|
||||
- 参数模板
|
||||
- 运行监控区
|
||||
- 当前任务
|
||||
- 步骤进度
|
||||
- 运行日志
|
||||
- 历史记录
|
||||
- 产物处理区
|
||||
- 提取
|
||||
- 入库
|
||||
- 重扫
|
||||
- 缓存重建
|
||||
|
||||
#### 关键任务
|
||||
|
||||
- 新建 `DinsarProductionPanel`
|
||||
- 废弃前端对 `/idl/...` 语义的直接依赖
|
||||
- 增加引擎状态卡和 WSL 状态卡
|
||||
- 运行记录中展示 `engine`、`profile`、`run_id`
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- 前端可以清晰区分三类引擎状态
|
||||
- `landsar` 显示为预留,不可执行
|
||||
- 当 ENVI 不可用但 ISCE2 可用时,页面仍可提交 ISCE2 任务
|
||||
|
||||
### T3. SARSCAPE 兼容接入
|
||||
|
||||
#### 目标
|
||||
|
||||
- 将现有 ENVI 链路正式纳入新生产体系
|
||||
- 明确 `metatask` 与 `custom6` 是同一引擎下的两个 profile
|
||||
|
||||
#### 关键任务
|
||||
|
||||
- 封装现有 ENVI 运行逻辑为 `sarscape_engine`
|
||||
- 将 `metatask` 和 `custom6` 暴露为 profile
|
||||
- 保留进度文件、日志、超时监控和历史运行能力
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- 旧 ENVI 作业仍可执行
|
||||
- 新系统中可选择 `sarscape/metatask` 与 `sarscape/custom6`
|
||||
- 原有任务状态、日志和结果提取行为保持兼容
|
||||
|
||||
### T4. ISCE2 + WSL 接入
|
||||
|
||||
#### 目标
|
||||
|
||||
- 建立 ISCE2 运行的正式环境检查、任务执行和输出接入能力
|
||||
|
||||
#### WSL 环境校验任务
|
||||
|
||||
- 检查 WSL 是否安装
|
||||
- 检查是否支持 WSL2
|
||||
- 检查目标 distro 是否存在
|
||||
- 检查目标 distro 是否可启动
|
||||
- 检查 `bash -lc` 是否可执行
|
||||
- 检查 Python 是否可执行
|
||||
- 检查 ISCE2 是否可 import
|
||||
- 检查目标入口命令是否可执行
|
||||
- 检查 DEM 路径在 WSL 内是否可读
|
||||
- 检查轨道目录在 WSL 内是否可读
|
||||
- 检查输出目录在 WSL 内是否可写
|
||||
- 检查 Windows 路径到 WSL 路径转换是否正确
|
||||
- 增加可选 smoke test
|
||||
|
||||
#### 建议配置项
|
||||
|
||||
- `ISCE2_ENABLED`
|
||||
- `ISCE2_WSL_DISTRO`
|
||||
- `ISCE2_PYTHON`
|
||||
- `ISCE2_PROFILE`
|
||||
- `ISCE2_DEM_PATH`
|
||||
- `ISCE2_ORBIT_DIR`
|
||||
- `ISCE2_WORK_ROOT`
|
||||
- `ISCE2_OUTPUT_ROOT`
|
||||
- `ISCE2_SMOKE_TEST_ENABLED`
|
||||
|
||||
#### 关键任务
|
||||
|
||||
- 新建 `wsl_service.py`
|
||||
- 新建 `isce2_engine.py`
|
||||
- 建立 WSL 执行和日志封装
|
||||
- 将 ISCE2 输出纳入统一结果收集和入库流程
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- 前端可见 ISCE2 环境状态
|
||||
- WSL 校验结果可解释、可定位故障点
|
||||
- ISCE2 至少有一个正式 profile 可提交并跑通
|
||||
|
||||
### T5. 运维自检增强
|
||||
|
||||
#### 目标
|
||||
|
||||
- 将当前“系统健康检查”升级为“系统级 + D-InSAR 专项级”双层运维自检
|
||||
|
||||
#### 关键任务
|
||||
|
||||
- 保留现有 `/health` 简版接口
|
||||
- 增加管理员详细健康接口
|
||||
- 增加多引擎专项检查
|
||||
- 增加 WSL 专项检查
|
||||
- 增加 D-InSAR 队列、存储、结果一致性检查
|
||||
- 增加自愈动作入口
|
||||
|
||||
#### 专项健康检查项
|
||||
|
||||
- 核心服务状态
|
||||
- 数据库
|
||||
- schema
|
||||
- worker
|
||||
- queue
|
||||
- 引擎状态
|
||||
- `sarscape`
|
||||
- `isce2`
|
||||
- `landsar`
|
||||
- WSL 状态
|
||||
- DEM/轨道/输出目录可用性
|
||||
- D-InSAR 运行任务积压
|
||||
- stale run 检测
|
||||
- 输出未入库检测
|
||||
- 入库但源文件缺失检测
|
||||
- 缓存一致性检测
|
||||
|
||||
#### 自愈入口建议
|
||||
|
||||
- 重扫结果目录
|
||||
- 重建缓存
|
||||
- 重试入库
|
||||
- 重置 stale run
|
||||
- 标记失效结果
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- 运维面板能区分系统级故障与引擎级故障
|
||||
- 当 ENVI 不可用但 ISCE2 可用时,总体 D-InSAR 服务状态应为 `degraded` 或 `ok`,不能直接判全系统失败
|
||||
- WSL 故障能定位到具体检查项
|
||||
|
||||
### T6. 结果管理增强
|
||||
|
||||
#### 目标
|
||||
|
||||
- 将 D-InSAR 结果从“扫描到的一条文件记录”升级为“正式结果资产”
|
||||
|
||||
#### 核心设计
|
||||
|
||||
- `Run`
|
||||
- 一次真实生产执行
|
||||
- `Artifact`
|
||||
- 一次运行产出的具体文件
|
||||
- `Managed Result`
|
||||
- 面向前端与业务的主结果对象
|
||||
|
||||
#### 关键任务
|
||||
|
||||
- 扩充结果模型字段
|
||||
- 增加结果来源追踪
|
||||
- 增加版本管理
|
||||
- 增加多产物管理
|
||||
- 增加结果生命周期状态
|
||||
- 增加批量治理能力
|
||||
- 增加多引擎结果对比能力
|
||||
|
||||
#### 结果管理能力清单
|
||||
|
||||
- 来源追踪
|
||||
- engine
|
||||
- profile
|
||||
- batch
|
||||
- pair
|
||||
- run
|
||||
- 版本管理
|
||||
- 当前版本
|
||||
- 历史版本
|
||||
- 最新成功版本
|
||||
- 多产物管理
|
||||
- disp
|
||||
- coherence
|
||||
- unwrapped phase
|
||||
- geotiff
|
||||
- browse
|
||||
- log
|
||||
- 生命周期管理
|
||||
- `NEW`
|
||||
- `INGESTED`
|
||||
- `QC_PENDING`
|
||||
- `QC_PASSED`
|
||||
- `QC_REJECTED`
|
||||
- `PUBLISHED`
|
||||
- `ARCHIVED`
|
||||
- 批量操作
|
||||
- 发布
|
||||
- 归档
|
||||
- 重扫
|
||||
- 重建缓存
|
||||
- 重绑定来源
|
||||
- 导出
|
||||
- 检索增强
|
||||
- 按引擎
|
||||
- 按批次
|
||||
- 按任务名
|
||||
- 按状态
|
||||
- 按 AOI
|
||||
- 按版本
|
||||
|
||||
#### 前端结果管理架构建议
|
||||
|
||||
- 结果列表视图
|
||||
- 结果详情视图
|
||||
- 来源与版本视图
|
||||
- 治理与运维视图
|
||||
|
||||
#### 运维治理项
|
||||
|
||||
- 入库记录存在但源文件缺失
|
||||
- run 存在但 artifact 缺失
|
||||
- 当前版本指针异常
|
||||
- 同名结果冲突
|
||||
- 缓存与主文件不一致
|
||||
- 已发布结果未完成审核
|
||||
|
||||
#### 验收标准
|
||||
|
||||
- 同一对影像允许保留多引擎结果
|
||||
- 同一任务允许保留多版本
|
||||
- 结果详情页能追踪到来源 run 和主产物列表
|
||||
- 运维面板可识别结果资产层异常
|
||||
|
||||
## 8. 实施顺序
|
||||
|
||||
### Phase 1
|
||||
|
||||
- 完成多引擎与结果管理设计冻结
|
||||
- 完成接口契约
|
||||
- 完成数据模型草案
|
||||
|
||||
### Phase 2
|
||||
|
||||
- 实现后端抽象层
|
||||
- 完成 SARSCAPE 兼容接入
|
||||
- 保持旧接口兼容
|
||||
|
||||
### Phase 3
|
||||
|
||||
- 实现新前端生产中心
|
||||
- 实现引擎状态、运行监控和运维自检增强
|
||||
|
||||
### Phase 4
|
||||
|
||||
- 接入 ISCE2 + WSL
|
||||
- 打通首条正式生产链
|
||||
|
||||
### Phase 5
|
||||
|
||||
- 实现结果管理增强
|
||||
- 补齐版本、来源、治理与自愈
|
||||
|
||||
## 9. 交付顺序建议
|
||||
|
||||
建议按照以下顺序落地:
|
||||
|
||||
1. 先重构生产抽象层,不先碰最终结果治理细节
|
||||
2. 先让 `sarscape` 在新体系下跑通
|
||||
3. 再接入 `isce2`
|
||||
4. 最后再把结果管理全面升级
|
||||
|
||||
这样可以避免在“生产链尚未稳定”时提前锁死结果模型细节。
|
||||
|
||||
## 10. 当前结论
|
||||
|
||||
本轮不应被理解为“给当前 ENVI 面板再加一个 ISCE2 按钮”,而应被理解为:
|
||||
|
||||
- D-InSAR 生产系统平台化
|
||||
- D-InSAR 结果资产化
|
||||
- 运维自检从系统健康升级为多引擎专项健康
|
||||
|
||||
后续所有开发任务、联调任务和运维验收,均以本 Task 文档为总入口。
|
||||
@@ -0,0 +1,167 @@
|
||||
# Frontend Navigation Architecture
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
This document is the source of truth for the left-side navigation structure in the frontend.
|
||||
|
||||
It explains:
|
||||
|
||||
- the first-level menu groups
|
||||
- the second-level working domains
|
||||
- the leaf tabs bound to actual pages
|
||||
- the reserved entries for future modules
|
||||
- the files that must be updated when navigation changes
|
||||
|
||||
The goal is to keep module boundaries stable as the system expands beyond D-InSAR into PS-InSAR and broader AI analysis workflows.
|
||||
|
||||
## 2. Current First-Level Groups
|
||||
|
||||
The current first-level menu groups are:
|
||||
|
||||
- `data`: 数据管理
|
||||
- `production`: 生产规划
|
||||
- `insar_analysis`: InSAR形变分析
|
||||
- `ai_analysis`: AI分析
|
||||
- `water`: 水体监测
|
||||
- `ops`: 运行维护
|
||||
|
||||
Definition file:
|
||||
|
||||
- `frontend/src/config/appConstants.js`
|
||||
|
||||
## 3. Three-Level Structure
|
||||
|
||||
Only some first-level groups use second-level working domains.
|
||||
|
||||
### 3.1 生产规划
|
||||
|
||||
```text
|
||||
生产规划
|
||||
├─ 规划编组
|
||||
│ ├─ 配对规划
|
||||
│ ├─ 任务规划
|
||||
│ ├─ PS时序栈
|
||||
│ └─ 任务批次
|
||||
├─ 数据分发
|
||||
│ └─ 数据分发
|
||||
├─ D-InSAR
|
||||
│ ├─ D-InSAR生产
|
||||
│ └─ D-InSAR产物
|
||||
└─ PS-InSAR
|
||||
├─ PS-InSAR生产
|
||||
└─ PS-InSAR产物
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `PS时序栈` belongs to production planning, not analysis.
|
||||
- `D-InSAR产物` is distinct from `D-InSAR结果`.
|
||||
- `PS-InSAR生产` and `PS-InSAR产物` are reserved entries for future implementation.
|
||||
|
||||
### 3.2 InSAR形变分析
|
||||
|
||||
```text
|
||||
InSAR形变分析
|
||||
├─ D-InSAR
|
||||
│ ├─ D-InSAR结果
|
||||
│ └─ D-InSAR分析
|
||||
└─ PS-InSAR
|
||||
├─ PS-InSAR结果
|
||||
└─ PS-InSAR分析
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- This group is for business-facing result browsing and deformation analysis.
|
||||
- AI diagnosis no longer belongs here.
|
||||
- `D-InSAR分析`, `PS-InSAR结果`, and `PS-InSAR分析` are currently reserved placeholders.
|
||||
|
||||
### 3.3 AI分析
|
||||
|
||||
```text
|
||||
AI分析
|
||||
├─ 形变智能分析
|
||||
│ ├─ AI质量评估
|
||||
│ └─ D-InSAR诊断
|
||||
└─ 遥感视觉分析
|
||||
├─ 滑坡语义分割
|
||||
└─ 无人机影像分析
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- This group owns model-centric and intelligent-analysis capabilities.
|
||||
- `D-InSAR诊断` is the renamed placement of the old AI diagnosis page.
|
||||
- `滑坡语义分割` and `无人机影像分析` are reserved placeholders for future AI modules.
|
||||
|
||||
## 4. Navigation Design Rules
|
||||
|
||||
The navigation follows these rules:
|
||||
|
||||
- First-level groups represent stable business domains.
|
||||
- Second-level domains represent workflow clusters inside a domain.
|
||||
- Leaf tabs represent actual pages bound to `leftPanelTab`.
|
||||
- The state source of truth remains `leftPanelTab`; group and section are derived from the tab key.
|
||||
- New features should be added as leaf tabs under an existing domain whenever possible.
|
||||
- A new first-level group should be introduced only when the feature becomes a long-term standalone capability cluster.
|
||||
|
||||
## 5. Naming Rules
|
||||
|
||||
To avoid future ambiguity, follow these naming constraints:
|
||||
|
||||
- Use `结果` for result browsing, querying, and visualization pages.
|
||||
- Use `产物` for extraction, packaging, publishing, and catalog management pages.
|
||||
- Use `分析` for interpretation, statistics,专题分析, and human-facing analytical workflows.
|
||||
- Use `诊断` for model-assisted fault analysis or AI-driven case reasoning.
|
||||
- Do not reuse `PS结果` as a generic label.
|
||||
Use `PS时序栈` in production planning.
|
||||
Use `PS-InSAR结果` in analysis.
|
||||
|
||||
## 6. Files to Update When Navigation Changes
|
||||
|
||||
When adding or moving a tab, update these files together:
|
||||
|
||||
- `frontend/src/config/appConstants.js`
|
||||
Defines first-level groups, second-level sections, tab ownership, and visibility.
|
||||
- `frontend/src/utils/appUiHelpers.js`
|
||||
Defines display labels for leaf tabs.
|
||||
- `frontend/src/App.jsx`
|
||||
Renders the group tabs, section tabs, and page content.
|
||||
- `frontend/src/App.css`
|
||||
Styles the first-level, second-level, and leaf-tab navigation.
|
||||
|
||||
If the new tab is a real page instead of a placeholder, also add or update the corresponding panel component.
|
||||
|
||||
## 7. Reserved Leaf Tabs
|
||||
|
||||
The following leaf tabs are intentionally reserved for future work:
|
||||
|
||||
- `ps_production`
|
||||
- `ps_products`
|
||||
- `dinsar_analysis`
|
||||
- `psinsar_results`
|
||||
- `psinsar_analysis`
|
||||
- `landslide_segmentation`
|
||||
- `uav_image_analysis`
|
||||
|
||||
Reserved tabs should remain visible in the information architecture if they help stabilize the long-term module layout.
|
||||
|
||||
## 8. Implementation Notes
|
||||
|
||||
- Admin-only visibility is controlled by `ADMIN_ONLY_TABS` in `appConstants.js`.
|
||||
- Section ownership is derived by `LEFT_TAB_SECTION`.
|
||||
- Group ownership is derived by `LEFT_TAB_GROUP`.
|
||||
- Groups without second-level sections still render as a two-level navigation.
|
||||
- Groups with configured sections render as three-level navigation.
|
||||
|
||||
## 9. Future Extension Guidance
|
||||
|
||||
Recommended future additions:
|
||||
|
||||
- Put PS-InSAR processing forms, task submission, and runtime monitoring under `ps_production`.
|
||||
- Put PS-InSAR extraction, packaging, registration, and catalog maintenance under `ps_products`.
|
||||
- Put thematic deformation analysis and reporting under `dinsar_analysis` and `psinsar_analysis`.
|
||||
- Put optical, UAV, or segmentation-based AI modules under `ai_analysis`, not under `insar_analysis`.
|
||||
|
||||
If a new feature belongs to intelligent interpretation or computer vision, prefer adding it under `AI分析`.
|
||||
If a new feature belongs to result browsing or deformation business analysis, prefer adding it under `InSAR形变分析`.
|
||||
@@ -0,0 +1,163 @@
|
||||
# GAMMA + WSL2 双引擎集成方案
|
||||
|
||||
## Context
|
||||
|
||||
当前系统使用 ENVI/SARscape 做 D-InSAR 处理,速度慢(单对处理需数十分钟到数小时)。用户有 GAMMA License,希望在 Windows 服务器的 WSL2 中部署 GAMMA 作为第二处理引擎。ENVI 保留不动,用户可在前端选择用哪个引擎。
|
||||
|
||||
GAMMA 通过 WSL2 调用完全可行:`subprocess.run(['wsl', '-d', 'Ubuntu', 'bash', '-c', 'gamma_cmd ...'])`,与现有 ENVI 的 subprocess 模式一致。
|
||||
|
||||
---
|
||||
|
||||
## 架构对比
|
||||
|
||||
```
|
||||
ENVI 链路(保留不动):
|
||||
POST /idl/jobs/dinsar (engine=envi)
|
||||
→ SystemJob(IDL_RUN_DINSAR) → envi_runner_cli 子进程
|
||||
→ envi_dinsar.run_dinsar_custom_workflow() → 6步 SARscape
|
||||
→ envipyengine → taskengine.exe
|
||||
|
||||
GAMMA 链路(新增):
|
||||
POST /idl/jobs/dinsar (engine=gamma)
|
||||
→ SystemJob(GAMMA_RUN_DINSAR) → gamma_runner_cli 子进程
|
||||
→ gamma_dinsar.run_dinsar_workflow() → 9步 GAMMA CLI
|
||||
→ subprocess.run(['wsl', ...]) → GAMMA 原生命令
|
||||
```
|
||||
|
||||
两条链路共享:SystemTask/SystemJob 任务队列、progress.json 进度机制、job_worker 监控逻辑。
|
||||
|
||||
---
|
||||
|
||||
## 新增文件
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `backend/app/services/gamma_service.py` | WSL2 执行器、路径转换(win↔wsl)、配置读取、环境检查、进度写入 |
|
||||
| `backend/app/services/gamma_dinsar.py` | GAMMA D-InSAR 9步工作流 |
|
||||
| `backend/app/services/gamma_runner_cli.py` | 子进程 CLI 入口(对标 envi_runner_cli.py) |
|
||||
| `backend/app/services/gamma_import.py` | LT-1 原始数据 → GAMMA SLC 格式导入 |
|
||||
| `scripts/check_gamma_wsl.py` | GAMMA + WSL2 环境诊断脚本 |
|
||||
|
||||
## 修改文件
|
||||
|
||||
| 文件 | 修改内容 |
|
||||
|------|----------|
|
||||
| `.env` | 新增 `GAMMA_ENABLED`、`GAMMA_WSL_DISTRO`、`GAMMA_HOME`、`GAMMA_ENV_SCRIPT`、`GAMMA_DEM_FILE` 等配置 |
|
||||
| `backend/app/config.py` | Settings 类新增 GAMMA 配置字段 |
|
||||
| `backend/app/services/job_handlers.py` | 新增 `GAMMA_RUN_DINSAR` handler,注册到 `_HANDLERS` |
|
||||
| `backend/app/routers/idl.py` | `DinsarJobRequest` 新增 `engine` 字段(envi/gamma),路由分发 |
|
||||
| `frontend/src/IDLAutomationPanel.jsx` | Step 2 新增引擎选择器(radio:ENVI / GAMMA),GAMMA 不可用时灰显 |
|
||||
| `frontend/src/api/idl.js` | `queueDinsarJob` payload 新增 `engine` |
|
||||
|
||||
---
|
||||
|
||||
## GAMMA D-InSAR 9步工作流
|
||||
|
||||
每步通过 `gamma_service.gamma_exec(cmd, work_dir)` → `wsl -d {distro} bash -c 'source gamma_env.sh && {cmd}'` 执行。
|
||||
|
||||
| 步骤 | GAMMA 命令 | 功能 | 对应 ENVI 步骤 |
|
||||
|------|-----------|------|---------------|
|
||||
| 1 | LT-1 导入(自定义) | 原始数据 → .slc + .slc.par | SARsImportLuTan1 |
|
||||
| 2 | `gc_map` | DEM 准备 + lookup table | (ENVI 内置在 Step1) |
|
||||
| 3 | `create_offset` + `offset_pwr` + `offset_fit` + `SLC_interp` | 配准 | InterferogramGeneration 内部 |
|
||||
| 4 | `SLC_intf` + `multi_look` | 干涉图生成 | InterferogramGeneration |
|
||||
| 5 | `adf` | 自适应滤波(Goldstein) | FilterAndCoherence |
|
||||
| 6 | `cc_wave` | 相干性估计 | FilterAndCoherence |
|
||||
| 7 | `mcf` | 相位解缠 | PhaseUnwrapping |
|
||||
| 8 | `dispmap` | 相位→位移 | PhaseToDisplacement |
|
||||
| 9 | `geocode_back` + `data2geotiff` | 地理编码 + 转 GeoTIFF | PhaseToDisplacement |
|
||||
|
||||
---
|
||||
|
||||
## 关键设计
|
||||
|
||||
### 路径转换
|
||||
```python
|
||||
def win_to_wsl(win_path: str) -> str:
|
||||
# Z:\Test_data\Task_001 → /mnt/z/Test_data/Task_001
|
||||
# D:\SRTM\dem.tif → /mnt/d/SRTM/dem.tif
|
||||
```
|
||||
|
||||
### 进度跟踪
|
||||
写入与 ENVI 完全相同格式的 `backend/runtime/idl_worker/job_{id}_progress.json`,job_handler 的 keepalive 无需修改。GAMMA 的 `total_steps=9`(ENVI 为 6),进度公式 `(pair_index-1+step/total)/total_pairs` 天然适配。
|
||||
|
||||
### 输出格式统一
|
||||
GAMMA 最终输出转为 GeoTIFF(`data2geotiff`),同时生成 ENVI `.hdr`,使 `extract_disp_results()` 和 D-InSAR 结果扫描能统一识别。
|
||||
|
||||
### 前端引擎选择
|
||||
页面加载时调 `GET /gamma/status` 检查 GAMMA 是否可用。可用时显示 radio 选择器,不可用时 GAMMA 选项灰显标注"(未配置)"。
|
||||
|
||||
---
|
||||
|
||||
## .env 配置项
|
||||
|
||||
```ini
|
||||
# ============ GAMMA + WSL2 配置 ============
|
||||
# 总开关(false 则前端不显示 GAMMA 选项)
|
||||
GAMMA_ENABLED=false
|
||||
|
||||
# WSL2 发行版名称(wsl -l -v 查看)
|
||||
GAMMA_WSL_DISTRO=Ubuntu-22.04
|
||||
|
||||
# GAMMA 安装路径(Linux 路径,WSL2 内部)
|
||||
GAMMA_HOME=/opt/GAMMA_SOFTWARE-20230101
|
||||
|
||||
# GAMMA 环境初始化脚本(source 此文件后 GAMMA 命令可用)
|
||||
GAMMA_ENV_SCRIPT=/opt/GAMMA_SOFTWARE-20230101/gamma_env.sh
|
||||
|
||||
# DEM 文件路径(Windows 路径,自动转为 WSL 路径)
|
||||
GAMMA_DEM_FILE=D:\SRTM30m\srtm_dem.tif
|
||||
|
||||
# 处理参数
|
||||
GAMMA_GEOCODE_PIXEL_SIZE_M=10.0
|
||||
GAMMA_ADF_WINDOW_SIZE=32
|
||||
GAMMA_MCF_COH_THRESHOLD=0.3
|
||||
GAMMA_RANGE_LOOKS=4
|
||||
GAMMA_AZIMUTH_LOOKS=1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 分阶段实施
|
||||
|
||||
### Phase 1:基础设施 + 环境验证
|
||||
- `.env` / `config.py` 配置层
|
||||
- `gamma_service.py`:`win_to_wsl()`、`wsl_exec()`、`gamma_exec()`、`check_gamma_environment()`
|
||||
- `scripts/check_gamma_wsl.py` 诊断脚本
|
||||
- `GET /gamma/status` 端点
|
||||
- 手动验证:`wsl -d Ubuntu bash -c 'source gamma_env.sh && which par_S1_SLC'`
|
||||
|
||||
### Phase 2:LT-1 数据导入(技术风险最高)
|
||||
- `gamma_import.py`:解析 LT-1 `.meta.xml` → 构建 GAMMA `.slc.par` 参数文件
|
||||
- **关键验证**:对一个 Task_*/master/ 执行导入,检查 .slc.par 参数是否正确
|
||||
- 如果 GAMMA 内置 LT-1 支持,直接调用;否则需手工构建参数文件
|
||||
|
||||
### Phase 3:D-InSAR 核心工作流
|
||||
- `gamma_dinsar.py`:9步工作流
|
||||
- `gamma_runner_cli.py`:CLI 入口
|
||||
- 逐步调试,每步验证输入输出
|
||||
|
||||
### Phase 4:任务调度 + 前端集成
|
||||
- `job_handlers.py`:新增 GAMMA handler
|
||||
- `idl.py`:engine 字段分发
|
||||
- `IDLAutomationPanel.jsx`:引擎选择器
|
||||
- 端到端测试
|
||||
|
||||
---
|
||||
|
||||
## 风险点
|
||||
|
||||
1. **LT-1 导入**(最大风险):GAMMA 是否内置 LT-1 读取器需要安装后确认。Plan B:用 GDAL 读 TIFF + 手工构建 .slc.par
|
||||
2. **WSL2 跨文件系统 I/O**:`/mnt/z/` 比 WSL 原生文件系统慢 ~5x。初期可接受,后续可优化为先拷贝到 WSL 内部处理
|
||||
3. **GAMMA License**:WSL2 中需配置 FlexLM `LM_LICENSE_FILE`,在 `gamma_env.sh` 中统一设置
|
||||
|
||||
---
|
||||
|
||||
## 验证
|
||||
|
||||
1. `py_compile` 所有新增/修改的 .py 文件
|
||||
2. `npm run build` 前端构建通过
|
||||
3. `python scripts/check_gamma_wsl.py` 环境诊断通过
|
||||
4. 前端选 GAMMA 引擎 → 提交任务 → 进度正常更新 → 输出 GeoTIFF 位移图
|
||||
5. 前端选 ENVI 引擎 → 行为与改造前完全一致(回归测试)
|
||||
6. GAMMA 未配置时 → 前端 GAMMA 选项灰显,无法选择
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
# 当前文档总览
|
||||
|
||||
这是 `docs/` 目录的统一入口文档。
|
||||
|
||||
从现在开始,`docs/` 根目录只保留“当前仍在使用”的文档;历史过程材料、已完成实施记录、被新文档覆盖的旧方案,统一放入 `docs/archive/`。
|
||||
|
||||
## 1. 当前有效文档
|
||||
|
||||
### 核心使用
|
||||
|
||||
- **[README.md](../README.md)**
|
||||
系统主文档。优先阅读,包含整体功能、架构、快速开始和主要配置说明。
|
||||
- **[DEPLOYMENT.md](DEPLOYMENT.md)**
|
||||
部署与运维细节文档。适合正式部署、数据库初始化、服务启动和环境核对时使用。
|
||||
- **[PROJ_CONFIGURATION.md](PROJ_CONFIGURATION.md)**
|
||||
PROJ/GDAL 相关专项配置说明。只有在坐标转换或 PROJ 冲突时才需要单独查看。
|
||||
|
||||
### 当前安全状态
|
||||
|
||||
- **[SECURITY_AUDIT_2026-03-12.md](SECURITY_AUDIT_2026-03-12.md)**
|
||||
当前有效的安全审计结论。已经把“开发机阶段可接受暴露”和“上线前必须修复的问题”分开说明,后续安全判断以这份为准。
|
||||
|
||||
### 仍在推进的设计与任务
|
||||
|
||||
- **[DINSAR_ENHANCEMENT_TASKS.md](DINSAR_ENHANCEMENT_TASKS.md)**
|
||||
D-InSAR 多引擎生产增强与结果管理增强的总 Task 文档。覆盖前端架构、ENVI 六步链路、ISCE2 的 WSL 校验、运维自检与结果治理。
|
||||
- **[PAIRING_ENHANCEMENT_DESIGN.md](PAIRING_ENHANCEMENT_DESIGN.md)**
|
||||
配对能力增强设计。属于未完全落地的专项设计文档。
|
||||
- **[GAMMA_WSL2_INTEGRATION_PLAN.md](GAMMA_WSL2_INTEGRATION_PLAN.md)**
|
||||
GAMMA + WSL2 双引擎方案。属于后续扩展规划,不是当前默认运行链路。
|
||||
- **[WSL2_ISCE2_MINTPY_SBAS_INTEGRATION_PLAN_20260412.md](WSL2_ISCE2_MINTPY_SBAS_INTEGRATION_PLAN_20260412.md)**
|
||||
基于本机 `Ubuntu-24.04` WSL2、`isce2` / `mintpy` / `isce2_mintpy_v1` 实际环境核对后的 SBAS 集成落地方案,明确推荐运行时、workflow 补全顺序和正式产品边界。
|
||||
- **[ISCE2_SBAS_TIMESERIES_DESIGN.md](ISCE2_SBAS_TIMESERIES_DESIGN.md)**
|
||||
ISCE2 框架下 SBAS / 时序生产设计稿,说明如何从现有 `PS stack batch` 扩展到 `workflow + psinsar catalog` 的正式生产链路。
|
||||
- **[SBAS_PROGRESS_STATUS_20260406.md](SBAS_PROGRESS_STATUS_20260406.md)**
|
||||
SBAS 系统改造当前落地状态。优先查看这份文档以了解“已经做到哪一步、验证做到哪一步、下一步该补什么”。
|
||||
- **[SBAS_SYSTEM_INTEGRATION_AUDIT_20260406.md](SBAS_SYSTEM_INTEGRATION_AUDIT_20260406.md)**
|
||||
对现有系统接入 SBAS 的审计结果,聚焦现状缺口与最小可行接入点。
|
||||
- **[SBAS_SYSTEM_EMBEDDING_DESIGN_20260406.md](SBAS_SYSTEM_EMBEDDING_DESIGN_20260406.md)**
|
||||
SBAS 嵌入现有系统的详细设计稿,覆盖运行记录、作业编排、结果注册与前端页面边界。
|
||||
- **[SBAS_TASK_PATH_MODE_EVALUATION_20260406.md](SBAS_TASK_PATH_MODE_EVALUATION_20260406.md)**
|
||||
评估是否沿用现有 D-InSAR `task 路径 -> 生产` 模式,以及 SBAS 为什么必须增加 `prepare` 阶段。
|
||||
- **[SBAS_SYSTEM_CHANGE_RISK_ASSESSMENT_20260406.md](SBAS_SYSTEM_CHANGE_RISK_ASSESSMENT_20260406.md)**
|
||||
SBAS 系统改造风险评估,重点约束“新增不伤旧链”。
|
||||
- **[SBAS_RESULT_MANAGEMENT_AND_DISPLAY_SPEC_20260406.md](SBAS_RESULT_MANAGEMENT_AND_DISPLAY_SPEC_20260406.md)**
|
||||
SBAS 结果管理、编目和展示约定,明确发布级 bundle 与系统结果目录的关系。
|
||||
- **[SBAS_FRONTEND_UX_SPEC_20260406.md](SBAS_FRONTEND_UX_SPEC_20260406.md)**
|
||||
SBAS 生产页、结果页与分析页的前端交互设计。
|
||||
- **[SBAS_IMPLEMENTATION_TODO_20260406.md](SBAS_IMPLEMENTATION_TODO_20260406.md)**
|
||||
SBAS 系统改造任务清单,适合作为后续 Phase 2 / Phase 3 推进的执行列表。
|
||||
|
||||
## 2. 已归档文档
|
||||
|
||||
以下文档已移入 `docs/archive/`,原因通常是“历史记录”“已完成实施”或“被后续结论覆盖”。
|
||||
|
||||
### 本轮归档
|
||||
|
||||
- `SECURITY_AUDIT_2026-03-04.md`
|
||||
- `SECURITY_AUDIT_ANALYSIS_2026-03-04.md`
|
||||
- `SECURITY_FIX_PLAN.md`
|
||||
- `SECURITY_FIX_PROGRESS.md`
|
||||
- `SECURITY_FIX_CHECKLIST.md`
|
||||
- `SECURITY_FIX_STAGE1_SUMMARY.md`
|
||||
- `SECURITY_OVERVIEW.md`
|
||||
- `HARDCODE_AUDIT_2026-03-05.md`
|
||||
- `LOG_MANAGEMENT_IMPLEMENTATION_2026-03-05.md`
|
||||
|
||||
### 既有归档
|
||||
|
||||
`docs/archive/` 中原有的旧设计、旧 TODO、阶段性分析和历史修复记录继续保留,仅作历史参考,不再视为当前执行依据。
|
||||
|
||||
## 3. 建议阅读顺序
|
||||
|
||||
### 新接手项目
|
||||
|
||||
1. 先读 [README.md](../README.md)
|
||||
2. 再读 [DEPLOYMENT.md](DEPLOYMENT.md)
|
||||
3. 若涉及上线前风险,再读 [SECURITY_AUDIT_2026-03-12.md](SECURITY_AUDIT_2026-03-12.md)
|
||||
|
||||
### 正式部署
|
||||
|
||||
1. [README.md](../README.md)
|
||||
2. [DEPLOYMENT.md](DEPLOYMENT.md)
|
||||
3. [PROJ_CONFIGURATION.md](PROJ_CONFIGURATION.md)(仅在需要时)
|
||||
|
||||
### 后续功能规划
|
||||
|
||||
1. [DINSAR_ENHANCEMENT_TASKS.md](DINSAR_ENHANCEMENT_TASKS.md)
|
||||
2. [PAIRING_ENHANCEMENT_DESIGN.md](PAIRING_ENHANCEMENT_DESIGN.md)
|
||||
3. [PAIRING_SYSTEM_REFACTOR_PLAN_20260413.md](PAIRING_SYSTEM_REFACTOR_PLAN_20260413.md)
|
||||
4. [ISCE2_SBAS_TIMESERIES_DESIGN.md](ISCE2_SBAS_TIMESERIES_DESIGN.md)
|
||||
5. [WSL2_ISCE2_MINTPY_SBAS_INTEGRATION_PLAN_20260412.md](WSL2_ISCE2_MINTPY_SBAS_INTEGRATION_PLAN_20260412.md)
|
||||
6. [GAMMA_WSL2_INTEGRATION_PLAN.md](GAMMA_WSL2_INTEGRATION_PLAN.md)
|
||||
|
||||
## 4. 后续维护规则
|
||||
|
||||
- `docs/` 根目录只放当前有效文档。
|
||||
- 已完成实施总结、阶段性修复过程、旧审计分析、被覆盖方案,统一放入 `docs/archive/`。
|
||||
- 新增文档前,优先判断是否可以并入现有文档,避免再次堆出多份并行说明。
|
||||
- 如果某份文档只记录一次任务过程,而不是长期参考资料,不应继续留在根目录。
|
||||
|
||||
**最后整理**:2026-03-13
|
||||
@@ -0,0 +1,275 @@
|
||||
# ISCE2 SBAS Product Specification
|
||||
|
||||
Updated: 2026-04-06
|
||||
|
||||
## 1. Goal
|
||||
|
||||
Define the stable product contract for the current phase-1 SBAS route:
|
||||
|
||||
- `ISCE2 stripmapStack -> MintPy SBAS -> geocode/export -> psinsar publish bundle`
|
||||
|
||||
This document answers three practical questions:
|
||||
|
||||
- what the final SBAS deliverables are
|
||||
- which files are runtime-only vs publish-grade
|
||||
- which file the future system should treat as the publish entrypoint
|
||||
|
||||
## 2. Product Layers
|
||||
|
||||
The current experiment has three artifact layers.
|
||||
|
||||
### 2.1 Processing-layer runtime artifacts
|
||||
|
||||
Generated under a MintPy work directory such as:
|
||||
|
||||
- `stack_work/mintpy_sbas_v5/`
|
||||
|
||||
These are processing outputs, not yet the final publish contract.
|
||||
|
||||
Key files:
|
||||
|
||||
- `timeseries.h5`
|
||||
- radar-coordinate time-series cube
|
||||
- core scientific output
|
||||
- `velocity.h5`
|
||||
- radar-coordinate velocity solution
|
||||
- useful for quick inspection and later geocode
|
||||
- `temporalCoherence.h5`
|
||||
- quality indicator after inversion
|
||||
- `maskTempCoh.h5`
|
||||
- thresholded reliability mask
|
||||
- `maskAllValid.h5`
|
||||
- strict runtime mask used before inversion
|
||||
- `numTriNonzeroIntAmbiguity.h5`
|
||||
- unwrap/network diagnostic
|
||||
- `numTriNonzeroIntAmbiguity.png`
|
||||
- browseable diagnostic preview
|
||||
|
||||
Current runtime boundary:
|
||||
|
||||
- the minimum validated SBAS runtime boundary is `timeseries.h5` plus `velocity.h5`
|
||||
|
||||
### 2.2 Publish-layer standardized artifacts
|
||||
|
||||
Generated under a publish directory such as:
|
||||
|
||||
- `publish/mintpy_sbas_v5/`
|
||||
|
||||
This is the current publish-grade boundary for system embedding.
|
||||
|
||||
Directory contract:
|
||||
|
||||
- `manifest.json`
|
||||
- `assets/`
|
||||
- `preview/`
|
||||
- `metadata/`
|
||||
|
||||
### 2.3 Catalog-layer registration artifacts
|
||||
|
||||
The future backend should publish the standardized bundle into the `psinsar` catalog.
|
||||
|
||||
Current rule:
|
||||
|
||||
- the backend should not register raw MintPy runtime paths directly
|
||||
- the backend should register the publish bundle rooted by `manifest.json`
|
||||
- the backend should populate `manifest.json.group_key` from task/run metadata during publish registration
|
||||
|
||||
## 3. Canonical Publish Bundle
|
||||
|
||||
### 3.1 Entrypoint
|
||||
|
||||
The publish entrypoint is:
|
||||
|
||||
- `manifest.json`
|
||||
|
||||
Reason:
|
||||
|
||||
- it binds product identity, stack dates, reference info, artifact paths, and quality summary in one place
|
||||
- it allows the backend to register one bundle without hard-coding per-file conventions
|
||||
- it decouples future UI/backend integration from the temporary MintPy work directory layout
|
||||
|
||||
### 3.2 Primary data products
|
||||
|
||||
#### `assets/geo_timeseries.h5`
|
||||
|
||||
Role:
|
||||
|
||||
- primary time-series product
|
||||
|
||||
Why it matters:
|
||||
|
||||
- this is the file that proves the system has true time-series capability
|
||||
- every valid pixel contains multi-date displacement values rather than one summary statistic
|
||||
|
||||
Recommended system use:
|
||||
|
||||
- archive as the canonical analysis cube
|
||||
- expose as the main download for advanced analysis
|
||||
- use as the source for future point-query / profile / date-slice APIs
|
||||
|
||||
#### `assets/geo_velocity.h5`
|
||||
|
||||
Role:
|
||||
|
||||
- geocoded structured velocity product
|
||||
|
||||
Recommended system use:
|
||||
|
||||
- archive together with the time-series cube
|
||||
- keep for programmatic reading when HDF5-native access is preferred
|
||||
|
||||
#### `assets/velocity.tif`
|
||||
|
||||
Role:
|
||||
|
||||
- publish-grade browse/export raster
|
||||
|
||||
Why it matters:
|
||||
|
||||
- easiest file for GIS browsing, thumbnail generation, map services, and first-screen display
|
||||
|
||||
Recommended system use:
|
||||
|
||||
- default map layer for result browsing
|
||||
- not the only final product
|
||||
- should be treated as a summary view derived from the time-series solution
|
||||
|
||||
### 3.3 Quality products
|
||||
|
||||
#### `assets/geo_temporalCoherence.h5`
|
||||
|
||||
Role:
|
||||
|
||||
- structured quality field
|
||||
|
||||
#### `assets/temporalCoherence.tif`
|
||||
|
||||
Role:
|
||||
|
||||
- GIS-friendly quality raster
|
||||
|
||||
Recommended use:
|
||||
|
||||
- review where the inversion is reliable
|
||||
|
||||
#### `assets/geo_maskTempCoh.h5`
|
||||
|
||||
Role:
|
||||
|
||||
- structured reliable-pixel mask
|
||||
|
||||
#### `assets/maskTempCoh.tif`
|
||||
|
||||
Role:
|
||||
|
||||
- GIS-friendly publish mask
|
||||
|
||||
Recommended use:
|
||||
|
||||
- mask browsing products
|
||||
- support frontend display filtering
|
||||
|
||||
### 3.4 Preview and diagnostics
|
||||
|
||||
#### `preview/velocity_preview.png`
|
||||
|
||||
Role:
|
||||
|
||||
- lightweight preview image for cards, list pages, or quick QA
|
||||
|
||||
#### `preview/numTriNonzeroIntAmbiguity.png`
|
||||
|
||||
Role:
|
||||
|
||||
- diagnostic browse image
|
||||
|
||||
#### `metadata/source_quality_summary.json`
|
||||
|
||||
Role:
|
||||
|
||||
- compact machine-readable quality summary
|
||||
|
||||
#### `metadata/smallbaselineApp.cfg`
|
||||
|
||||
Role:
|
||||
|
||||
- reproducibility record of the MintPy configuration
|
||||
|
||||
## 4. File Priorities By Use Case
|
||||
|
||||
If the goal is system publish or catalog registration:
|
||||
|
||||
- focus on `manifest.json`
|
||||
|
||||
If the goal is proving time-series capability:
|
||||
|
||||
- focus on `assets/geo_timeseries.h5`
|
||||
|
||||
If the goal is first-screen map display:
|
||||
|
||||
- focus on `assets/velocity.tif`
|
||||
|
||||
If the goal is quality control:
|
||||
|
||||
- focus on `assets/temporalCoherence.tif`
|
||||
- focus on `assets/maskTempCoh.tif`
|
||||
|
||||
If the goal is full reproducibility:
|
||||
|
||||
- keep `manifest.json`
|
||||
- keep `metadata/smallbaselineApp.cfg`
|
||||
- keep runtime `maskAllValid.h5` as a workflow artifact even if it is not published as a primary product
|
||||
|
||||
## 5. Current Product Typing
|
||||
|
||||
Current artifact typing implemented by the experiment bundle builder:
|
||||
|
||||
- `timeseries_cube`
|
||||
- `assets/geo_timeseries.h5`
|
||||
- `velocity_map`
|
||||
- `assets/geo_velocity.h5`
|
||||
- `velocity_geotiff`
|
||||
- `assets/velocity.tif`
|
||||
- `temporal_coherence`
|
||||
- `assets/geo_temporalCoherence.h5`
|
||||
- `temporal_coherence_geotiff`
|
||||
- `assets/temporalCoherence.tif`
|
||||
- `quality_mask`
|
||||
- `assets/geo_maskTempCoh.h5`
|
||||
- `quality_mask_geotiff`
|
||||
- `assets/maskTempCoh.tif`
|
||||
- `preview_png`
|
||||
- `preview/velocity_preview.png`
|
||||
- `diagnostic_png`
|
||||
- `preview/numTriNonzeroIntAmbiguity.png`
|
||||
|
||||
## 6. Publish Rules
|
||||
|
||||
Current recommended rules:
|
||||
|
||||
- publish the geocoded bundle, not the radar-coordinate MintPy runtime directory
|
||||
- treat `manifest.json` as the registration root
|
||||
- treat `geo_timeseries.h5` as the canonical time-series data product
|
||||
- treat `velocity.tif` as the default visualization product
|
||||
- always publish at least one quality layer beside the velocity layer
|
||||
- keep diagnostics and config snapshots for reproducibility even if the frontend does not display all of them
|
||||
|
||||
## 7. Current Open Items
|
||||
|
||||
Still to be formalized before production:
|
||||
|
||||
- final CRS contract beyond the current `EPSG:4326` experiment assumption
|
||||
- whether the backend stores one product record per artifact or one bundle-root record plus child artifacts
|
||||
- how the frontend will query time-series pixels from `geo_timeseries.h5`
|
||||
- whether extra export formats such as COG, CSV point extraction, or JSON summaries should be added
|
||||
|
||||
## 8. Current Judgment
|
||||
|
||||
For the current phase-1 SBAS route, the final deliverable should be understood as:
|
||||
|
||||
- one manifest-driven publish bundle
|
||||
- centered on `assets/geo_timeseries.h5`
|
||||
- summarized by `assets/velocity.tif`
|
||||
- constrained by `assets/temporalCoherence.tif` and `assets/maskTempCoh.tif`
|
||||
|
||||
That is the product contract the future system should embed first.
|
||||
@@ -0,0 +1,609 @@
|
||||
# ISCE2 SBAS Time-Series Production Design
|
||||
|
||||
Updated: 2026-04-06
|
||||
|
||||
## 1. Goal
|
||||
|
||||
Add stack-based time-series production under the current ISCE2-oriented architecture.
|
||||
|
||||
The current repository already has:
|
||||
|
||||
- image discovery and stack selection
|
||||
- PS/time-series stack batch persistence
|
||||
- single-pair D-InSAR production
|
||||
|
||||
The current repository does not yet have:
|
||||
|
||||
- a stack-level production entry
|
||||
- time-series workflow orchestration
|
||||
- SBAS inversion output registration
|
||||
- PS-InSAR result/product pages beyond placeholders
|
||||
|
||||
This design focuses on the first deliverable:
|
||||
|
||||
- implement SBAS time-series production first
|
||||
- keep true PS-InSAR or StaMPS as a later phase
|
||||
|
||||
Related product contract:
|
||||
|
||||
- `docs/ISCE2_SBAS_PRODUCT_SPEC.md`
|
||||
|
||||
## 2. Current State
|
||||
|
||||
### 2.1 What already exists
|
||||
|
||||
- `backend/app/routers/pairing.py`
|
||||
- `/find-ps-timeseries` can search image stacks for time-series use.
|
||||
- `backend/app/routers/task_batches.py`
|
||||
- `/task-batches/ps` persists a selected stack into `PsTaskBatchORM` and `PsTaskItemORM`.
|
||||
- `backend/app/models/orm.py`
|
||||
- `ps_task_batches` and `ps_task_items` already represent the planning-layer stack snapshot.
|
||||
- `backend/app/models/orm.py`
|
||||
- `workflow_runs`, `workflow_steps`, and `workflow_artifacts` already exist and are suitable for multi-step orchestration.
|
||||
- `backend/app/models/orm.py`
|
||||
- `result_products` already supports multiple catalogs through `catalog_name`.
|
||||
- `frontend/src/App.jsx`
|
||||
- `ps_production`, `ps_products`, `psinsar_results`, and `psinsar_analysis` are reserved placeholders.
|
||||
|
||||
### 2.2 What is missing
|
||||
|
||||
- `backend/app/dinsar_engines/isce2_engine.py` is pair-oriented.
|
||||
- It runs a custom LT-1 `stripmapApp.py` flow for one pair or a pair-root directory.
|
||||
- It does not model stack/network/time-series execution.
|
||||
- Current `SBAS` in pairing-related code and docs means a pairing strategy.
|
||||
- It does not mean completed SBAS inversion, velocity estimation, or time-series products.
|
||||
- The current PS batch is not a production run.
|
||||
- It is only a stored stack selection.
|
||||
|
||||
### 2.3 Architectural implication
|
||||
|
||||
Time-series production is not a small extension of the current pair-based D-InSAR engine.
|
||||
|
||||
The processing object changes from:
|
||||
|
||||
- `pair -> one run -> one main displacement output`
|
||||
|
||||
to:
|
||||
|
||||
- `stack/network -> multi-step run -> multiple intermediate and final products`
|
||||
|
||||
Because of that, time-series production should not be forced into `DinsarEngine` as-is.
|
||||
|
||||
Still, the LT-1 input-preparation layer should be shared.
|
||||
|
||||
Recommended reuse point:
|
||||
|
||||
- keep DEM path resolution, orbit-pool resolution, and LT-1 precise-orbit XML generation in a shared helper
|
||||
- current implementation anchor:
|
||||
- `backend/app/isce2_pipeline/lt1_input_resolver.py`
|
||||
|
||||
### 2.4 Experiment Status
|
||||
|
||||
Current LT-1 stack experiment status already de-risks the processing side of phase 1:
|
||||
|
||||
- one offline LT-1 sample stack has completed the generated `run_01` to `run_07` chain under `Ubuntu-24.04`
|
||||
- the same LT-1 sample stack has also completed `run_08_igram`, producing filtered and unwrapped pair products under `Igrams/`
|
||||
- the same LT-1 sample stack has now also completed the first MintPy SBAS smoke test through radar-coordinate `timeseries.h5` and `velocity.h5`
|
||||
- current successful MintPy work directory:
|
||||
- `/mnt/z/Code/Insar_management_system_v2/experiments/isce2_sbas_timeseries/scratch/lt1a_strip1_hh_descending_e123p3_n46p1/stack_work/mintpy_sbas_v5`
|
||||
- the same LT-1 sample stack has now also completed an experiment-layer geocode plus publish-bundle export
|
||||
- current successful publish-style directory:
|
||||
- `/mnt/z/Code/Insar_management_system_v2/experiments/isce2_sbas_timeseries/scratch/lt1a_strip1_hh_descending_e123p3_n46p1/publish/mintpy_sbas_v5`
|
||||
- local SAR scenes, local orbit data, and a local DEM were sufficient for this stage
|
||||
- Earthdata `SWBD` was not treated as a hard dependency during experiments because `run_01_reference` can recover with a synthetic all-land `waterMask`
|
||||
- current runtime split decision:
|
||||
- keep ISCE2 stack processing in the existing WSL `isce2` env
|
||||
- validate MintPy in a separate WSL `mintpy` env to avoid mutating the working processing env on the development machine
|
||||
- phase-4 unified-env update:
|
||||
- a recreated WSL env `isce2_mintpy_v1` has now also completed the same LT-1 SBAS smoke test and publish export without the `isce` bridge
|
||||
- current successful unified SBAS work directory:
|
||||
- `/mnt/z/Code/Insar_management_system_v2/experiments/isce2_sbas_timeseries/scratch/lt1a_strip1_hh_descending_e123p3_n46p1/stack_work/mintpy_sbas_unified_v1`
|
||||
- current successful unified publish directory:
|
||||
- `/mnt/z/Code/Insar_management_system_v2/experiments/isce2_sbas_timeseries/scratch/lt1a_strip1_hh_descending_e123p3_n46p1/publish/mintpy_sbas_unified_v1`
|
||||
- current judgment:
|
||||
- unified env is viable at experiment layer and is now the preferred SBAS experiment runtime
|
||||
- existing WSL `isce2` should remain untouched for current D-InSAR production safety
|
||||
- bridge env remains the fallback baseline until a fuller comparison is written
|
||||
- current MintPy bridge decision:
|
||||
- keep `mintpy` isolated
|
||||
- bridge only the top-level `isce` package from the `isce2` env into the `mintpy` env, because MintPy's ISCE stripmap metadata path imports `isce`
|
||||
- do not bridge the full `isce2` `site-packages`, because that polluted the MintPy runtime with conflicting `h5py`
|
||||
- current MintPy runtime workaround decision:
|
||||
- use a strict `maskAllValid.h5` before inversion to suppress unstable partial-network pixels
|
||||
- use a repo-local patched launcher for the current MintPy `1.6.2` single-pixel partial-network inversion bug
|
||||
- current implementation anchors:
|
||||
- `experiments/isce2_sbas_timeseries/scripts/create_mintpy_all_ifgram_mask.py`
|
||||
- `experiments/isce2_sbas_timeseries/scripts/run_smallbaselineApp_patched.py`
|
||||
- `experiments/isce2_sbas_timeseries/scripts/run_mintpy_sbas_smoketest_ubuntu2404.sh`
|
||||
- current experiment-layer publish export decision:
|
||||
- keep geocode/export as a separate post-MintPy stage
|
||||
- current implementation anchors:
|
||||
- `experiments/isce2_sbas_timeseries/scripts/export_mintpy_publish_products_ubuntu2404.sh`
|
||||
- `experiments/isce2_sbas_timeseries/scripts/build_mintpy_publish_bundle.py`
|
||||
- the shared helper refactor is already in place:
|
||||
- `backend/app/isce2_pipeline/lt1_input_resolver.py`
|
||||
- compatibility rule:
|
||||
- the original pair-oriented D-InSAR public entry was not removed
|
||||
- `backend/app/isce2_pipeline/run_lt1_dinsar_pipeline.py` still owns the existing workflow entry and now delegates shared input preparation to the helper
|
||||
|
||||
## 3. Recommendation
|
||||
|
||||
### 3.1 Deliver SBAS first
|
||||
|
||||
Recommended first path:
|
||||
|
||||
- `stack planning -> stack prep -> ISCE2 stack processing -> MintPy smallbaselineApp -> psinsar product publish -> result browsing`
|
||||
|
||||
Reasons:
|
||||
|
||||
- the repository already has stack-selection concepts
|
||||
- SBAS outputs are easier to standardize into cataloged products
|
||||
- MintPy is a practical downstream tool for small-baseline inversion over ISCE outputs
|
||||
- the UI and backend risk are both lower than starting from full PS-InSAR
|
||||
|
||||
### 3.2 Do not start with StaMPS / true PS route
|
||||
|
||||
True PS-InSAR should be treated as phase 2 or later because it adds extra uncertainty in:
|
||||
|
||||
- candidate selection and amplitude stability logic
|
||||
- stack export conventions
|
||||
- result structure standardization
|
||||
- point-level visualization and analysis UX
|
||||
|
||||
## 4. Recommended Technical Chain
|
||||
|
||||
Phase-1 recommended chain:
|
||||
|
||||
1. User creates or reuses a `PS stack batch`.
|
||||
2. System creates a stack-level production run bound to that batch.
|
||||
3. Workflow prepares a working directory and stack metadata.
|
||||
4. Workflow runs ISCE2 stack processing in WSL.
|
||||
5. Workflow runs MintPy SBAS inversion in a dedicated WSL MintPy runtime.
|
||||
6. Workflow exports standardized products and manifest files.
|
||||
7. Workflow publishes the outputs into a `psinsar` result catalog.
|
||||
8. Frontend reads the catalog and workflow state for products and results.
|
||||
|
||||
Product interpretation rule:
|
||||
|
||||
- `manifest.json` is the publish entrypoint
|
||||
- `assets/geo_timeseries.h5` is the canonical time-series data product
|
||||
- `assets/velocity.tif` is the default browse layer, not the only final deliverable
|
||||
|
||||
## 5. Scope
|
||||
|
||||
### 5.1 In scope for phase 1
|
||||
|
||||
- SBAS time-series production entry
|
||||
- stack-level workflow orchestration
|
||||
- WSL runtime checks for stack and MintPy steps
|
||||
- product extraction and catalog registration
|
||||
- basic `ps_production` and `ps_products` pages
|
||||
- basic `psinsar_results` browsing capability
|
||||
|
||||
### 5.2 Out of scope for phase 1
|
||||
|
||||
- full PS-InSAR / StaMPS route
|
||||
- advanced point-based analysis UI
|
||||
- cluster scheduling
|
||||
- multi-sensor stack fusion
|
||||
- automatic reference-point intelligence
|
||||
|
||||
## 6. Target Architecture
|
||||
|
||||
### 6.1 Domain split
|
||||
|
||||
Keep the system split into three layers:
|
||||
|
||||
- Planning layer
|
||||
- existing `find-ps-timeseries`
|
||||
- existing `ps_task_batches`
|
||||
- Production layer
|
||||
- new stack-level time-series run submission and monitoring
|
||||
- Result layer
|
||||
- `psinsar` products and business-facing browsing
|
||||
|
||||
This keeps planning data and production runs decoupled.
|
||||
|
||||
### 6.2 Orchestration model
|
||||
|
||||
Reuse the existing generic workflow subsystem:
|
||||
|
||||
- `workflow_runs`
|
||||
- `workflow_steps`
|
||||
- `workflow_artifacts`
|
||||
|
||||
Recommended rule:
|
||||
|
||||
- do not extend `DinsarEngine` for time-series phase 1
|
||||
- use `workflow` as the orchestration primitive
|
||||
- add a thin time-series metadata table that binds:
|
||||
- one `ps_task_batch`
|
||||
- one workflow run
|
||||
- one processing mode
|
||||
- one output root
|
||||
|
||||
### 6.3 Suggested new metadata table
|
||||
|
||||
Add a new business table such as `ps_timeseries_runs`.
|
||||
|
||||
Suggested fields:
|
||||
|
||||
- `run_id`
|
||||
- `batch_id`
|
||||
- `workflow_run_id`
|
||||
- `mode`
|
||||
- `sbas`
|
||||
- `engine_code`
|
||||
- `isce2`
|
||||
- `processor_code`
|
||||
- `isce2_stack_mintpy`
|
||||
- `direction`
|
||||
- `status`
|
||||
- `work_dir`
|
||||
- `output_dir`
|
||||
- `publish_dir`
|
||||
- `reference_date`
|
||||
- `reference_strategy`
|
||||
- `params_json`
|
||||
- `summary_json`
|
||||
- `error_message`
|
||||
- `created_by`
|
||||
- `created_at`
|
||||
- `updated_at`
|
||||
- `started_at`
|
||||
- `ended_at`
|
||||
|
||||
Notes:
|
||||
|
||||
- `PsTaskBatchORM` remains the planning snapshot.
|
||||
- `WorkflowRunORM` remains the step-level orchestration record.
|
||||
- `ps_timeseries_runs` becomes the business-facing production record.
|
||||
|
||||
### 6.4 Suggested workflow
|
||||
|
||||
Recommended workflow name:
|
||||
|
||||
- `isce2_sbas_mintpy_v1`
|
||||
|
||||
Recommended steps:
|
||||
|
||||
1. `prepare_stack_input`
|
||||
- validate stack size and metadata
|
||||
- build stack working directory
|
||||
- resolve DEM/orbit/output paths
|
||||
2. `run_isce2_stack`
|
||||
- run stack pre-processing in WSL
|
||||
- collect stack intermediate artifacts
|
||||
3. `run_mintpy_sbas`
|
||||
- run repo-controlled MintPy `smallbaselineApp`
|
||||
- generate strict inversion mask if required by the pinned MintPy runtime
|
||||
- generate time-series and velocity products
|
||||
4. `export_standard_products`
|
||||
- copy or convert outputs into stable publish structure
|
||||
- generate manifest and preview assets
|
||||
5. `publish_psinsar_products`
|
||||
- register catalog entries
|
||||
- update product status and coverage
|
||||
|
||||
### 6.5 Suggested job types
|
||||
|
||||
Recommended new job types:
|
||||
|
||||
- `TIMESERIES_PREP_STACK`
|
||||
- `ISCE2_STACK_RUN`
|
||||
- `MINTPY_SBAS_RUN`
|
||||
- `EXPORT_PSINSAR_PRODUCTS`
|
||||
- `PUBLISH_PSINSAR_PRODUCTS`
|
||||
|
||||
These fit naturally under the existing job queue and worker model.
|
||||
|
||||
## 7. Backend Design
|
||||
|
||||
### 7.1 New modules
|
||||
|
||||
Recommended backend additions:
|
||||
|
||||
- `backend/app/routers/timeseries_production.py`
|
||||
- `backend/app/routers/ps_products.py`
|
||||
- `backend/app/services/timeseries_service.py`
|
||||
- `backend/app/services/timeseries_workflow_factory.py`
|
||||
- `backend/app/services/isce2_stack_service.py`
|
||||
- `backend/app/services/mintpy_service.py`
|
||||
- `backend/app/services/psinsar_catalog_service.py`
|
||||
- `backend/app/services/timeseries_paths.py`
|
||||
|
||||
### 7.2 API sketch
|
||||
|
||||
Recommended new APIs:
|
||||
|
||||
- `POST /timeseries-production/runs`
|
||||
- submit one SBAS run for one stored PS stack batch
|
||||
- `GET /timeseries-production/runs`
|
||||
- list recent time-series runs
|
||||
- `GET /timeseries-production/runs/{run_id}`
|
||||
- get business metadata + workflow state + artifacts
|
||||
- `POST /timeseries-production/runs/{run_id}/retry-step`
|
||||
- retry failed step when allowed
|
||||
- `POST /timeseries-production/wsl-check`
|
||||
- check stack + MintPy runtime environment
|
||||
- `GET /ps-products`
|
||||
- list registered `psinsar` products
|
||||
- `POST /ps-products/rebuild-catalog`
|
||||
- rebuild `psinsar` product catalog
|
||||
|
||||
### 7.3 Reuse of current tables and services
|
||||
|
||||
Recommended reuse:
|
||||
|
||||
- `ps_task_batches` / `ps_task_items`
|
||||
- keep as the source stack definition
|
||||
- `workflow_runs` / `workflow_steps` / `workflow_artifacts`
|
||||
- keep as the run and step state model
|
||||
- `result_products`
|
||||
- extend with `catalog_name = psinsar`
|
||||
- current job queue / worker / task log chain
|
||||
- reuse for execution and monitoring
|
||||
|
||||
### 7.4 Product catalog strategy
|
||||
|
||||
Do not build a separate product table just for time-series phase 1.
|
||||
|
||||
Use `result_products` with:
|
||||
|
||||
- `catalog_name = psinsar`
|
||||
- `engine_code = isce2`
|
||||
- `product_type` values specific to time-series outputs
|
||||
|
||||
Recommended `product_type` values:
|
||||
|
||||
- `velocity_map`
|
||||
- `timeseries_cube`
|
||||
- `temporal_coherence`
|
||||
- `ifgram_network`
|
||||
- `point_series_csv`
|
||||
- `preview_png`
|
||||
- `summary_report`
|
||||
|
||||
Use the existing generic asset and issue models where possible.
|
||||
|
||||
### 7.5 Current Phase-1 Artifact Contract
|
||||
|
||||
The current validated LT-1 experiment supports the following publish contract:
|
||||
|
||||
- `timeseries_cube`
|
||||
- source file:
|
||||
- `assets/geo_timeseries.h5`
|
||||
- `velocity_map`
|
||||
- source file:
|
||||
- `assets/geo_velocity.h5`
|
||||
- `velocity_geotiff`
|
||||
- source file:
|
||||
- `assets/velocity.tif`
|
||||
- `temporal_coherence`
|
||||
- source file:
|
||||
- `assets/geo_temporalCoherence.h5`
|
||||
- `quality_mask`
|
||||
- source file:
|
||||
- `assets/geo_maskTempCoh.h5`
|
||||
- `temporal_coherence_geotiff`
|
||||
- source file:
|
||||
- `assets/temporalCoherence.tif`
|
||||
- `quality_mask_geotiff`
|
||||
- source file:
|
||||
- `assets/maskTempCoh.tif`
|
||||
- `ifgram_network`
|
||||
- source file:
|
||||
- runtime diagnostic source:
|
||||
- `numTriNonzeroIntAmbiguity.h5`
|
||||
- `preview_png`
|
||||
- source file:
|
||||
- `preview/velocity_preview.png`
|
||||
- `diagnostic_png`
|
||||
- source file:
|
||||
- `preview/numTriNonzeroIntAmbiguity.png`
|
||||
- retained runtime-only artifacts:
|
||||
- `maskAllValid.h5`
|
||||
- `avgSpatialCoh.h5`
|
||||
- `smallbaselineApp.cfg`
|
||||
|
||||
Current sample manifest draft:
|
||||
|
||||
- `experiments/isce2_sbas_timeseries/configs/sample_psinsar_manifest_lt1_e123p3_n46p1.json`
|
||||
|
||||
Current successful experiment publish bundle:
|
||||
|
||||
- `experiments/isce2_sbas_timeseries/scratch/lt1a_strip1_hh_descending_e123p3_n46p1/publish/mintpy_sbas_v5/manifest.json`
|
||||
|
||||
## 8. Directory Conventions
|
||||
|
||||
Recommended Windows roots:
|
||||
|
||||
- `PS_TIMESERIES_WORK_ROOT`
|
||||
- `PS_TIMESERIES_OUTPUT_ROOT`
|
||||
- `PSINSAR_PRODUCT_DIR`
|
||||
|
||||
Recommended run layout:
|
||||
|
||||
```text
|
||||
<PS_TIMESERIES_WORK_ROOT>/<batch_id>/<run_id>/
|
||||
stack_input/
|
||||
isce2_stack/
|
||||
mintpy/
|
||||
export/
|
||||
logs/
|
||||
```
|
||||
|
||||
Recommended publish layout:
|
||||
|
||||
```text
|
||||
<PSINSAR_PRODUCT_DIR>/<product_id>/
|
||||
manifest.json
|
||||
preview/
|
||||
assets/
|
||||
metadata/
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
- production should always have a stable publish directory
|
||||
- intermediate work directory and final publish directory should stay separate
|
||||
- raw imagery must not be copied into publish output unnecessarily
|
||||
|
||||
## 9. Frontend Design
|
||||
|
||||
### 9.1 `ps_production`
|
||||
|
||||
Phase-1 page content:
|
||||
|
||||
- select an existing PS stack batch
|
||||
- choose processing mode
|
||||
- phase 1 only exposes `SBAS`
|
||||
- show runtime environment status
|
||||
- WSL
|
||||
- ISCE2 stack runtime
|
||||
- MintPy runtime
|
||||
- submit workflow run
|
||||
- show step status, logs, and artifacts
|
||||
|
||||
### 9.2 `ps_products`
|
||||
|
||||
Phase-1 page content:
|
||||
|
||||
- list `psinsar` products
|
||||
- filter by status, run, batch, direction, date
|
||||
- publish and rebuild operations
|
||||
- basic manifest view
|
||||
|
||||
### 9.3 `psinsar_results`
|
||||
|
||||
Phase-1 page content:
|
||||
|
||||
- basic product query
|
||||
- map preview of velocity or deformation raster
|
||||
- metadata drawer
|
||||
- quick link to workflow run and source batch
|
||||
|
||||
### 9.4 `psinsar_analysis`
|
||||
|
||||
Keep this reserved in phase 1.
|
||||
|
||||
Later it can take:
|
||||
|
||||
- point time-series browsing
|
||||
- rate classification
|
||||
- hotspot statistics
|
||||
- thematic reporting
|
||||
|
||||
## 10. Configuration
|
||||
|
||||
Recommended new or clarified settings:
|
||||
|
||||
- `PS_TIMESERIES_ENABLED`
|
||||
- `PS_TIMESERIES_WORK_ROOT`
|
||||
- `PS_TIMESERIES_OUTPUT_ROOT`
|
||||
- `PSINSAR_PRODUCT_DIR`
|
||||
- `MINTPY_PYTHON`
|
||||
- `MINTPY_SMALLBASELINE_APP`
|
||||
- `MINTPY_RUNNER`
|
||||
- `MINTPY_PATCHED_SMALLBASELINE_APP`
|
||||
- `MINTPY_TEMPLATE_DIR`
|
||||
- `PS_TIMESERIES_AUTO_PUBLISH`
|
||||
|
||||
Where practical:
|
||||
|
||||
- reuse `ISCE2_WSL_DISTRO`
|
||||
- reuse `ISCE2_PYTHON` if MintPy is installed in the same environment
|
||||
- reuse current DEM and orbit path conventions
|
||||
|
||||
## 11. Key Risk: LT-1 Stack Compatibility
|
||||
|
||||
This is the main technical uncertainty.
|
||||
|
||||
The current repository already uses a custom LT-1 single-pair `stripmapApp.py` flow.
|
||||
That does not prove official ISCE2 stack tooling will work directly for LT-1/LUTAN1.
|
||||
|
||||
Therefore phase 0 must verify:
|
||||
|
||||
- whether official ISCE2 stack tooling can ingest LT-1 metadata directly
|
||||
- whether orbit formatting is already sufficient for stack mode
|
||||
- whether extra stack-input conversion is required
|
||||
|
||||
If official stack tooling is not directly compatible:
|
||||
|
||||
- keep the overall architecture unchanged
|
||||
- implement a custom stack builder or adapter layer before MintPy
|
||||
|
||||
Do not hard-code the design around official stack scripts until this experiment is confirmed.
|
||||
|
||||
## 12. Option Comparison
|
||||
|
||||
| Option | Fit for current repo | Technical risk | UI/ops complexity | Phase-1 recommendation |
|
||||
|---|---|---|---|---|
|
||||
| ISCE2 stack + MintPy SBAS | High | Medium | Medium | Yes |
|
||||
| ISCE2 StackToStaMPS / PS route | Medium-Low | High | High | No |
|
||||
|
||||
Why SBAS wins first:
|
||||
|
||||
- closer to the current stack-planning model
|
||||
- easier to standardize outputs
|
||||
- easier to explain and operate
|
||||
- lower uncertainty in first delivery
|
||||
|
||||
## 13. TODO List
|
||||
|
||||
### Phase 0. Validation
|
||||
|
||||
- [x] Verify whether LT-1/LUTAN1 can run through official ISCE2 stack tooling.
|
||||
- [x] Decide whether MintPy shares the current ISCE2 environment or uses a separate env.
|
||||
- [x] Finalize work/output/publish directory rules.
|
||||
- [x] Decide the minimal product set for first release.
|
||||
|
||||
### Phase 1. Backend scaffold
|
||||
|
||||
- [ ] Add `ps_timeseries_runs` ORM and schema.
|
||||
- [ ] Add `timeseries_production` router.
|
||||
- [ ] Add `timeseries_workflow_factory` for `isce2_sbas_mintpy_v1`.
|
||||
- [ ] Add new job handlers for stack prep, stack run, MintPy run, export, and publish.
|
||||
- [ ] Bind workflow runs to `ps_task_batches`.
|
||||
- [ ] Persist workflow artifacts for major intermediate and final outputs.
|
||||
|
||||
### Phase 2. Catalog and products
|
||||
|
||||
- [ ] Extend catalog publishing to support `catalog_name = psinsar`.
|
||||
- [ ] Define manifest schema for SBAS products.
|
||||
- [ ] Add coverage extraction and preview generation.
|
||||
- [ ] Add rebuild and health-check operations for `psinsar` products.
|
||||
|
||||
### Phase 3. Frontend
|
||||
|
||||
- [ ] Replace the `ps_production` placeholder with a real production panel.
|
||||
- [ ] Replace the `ps_products` placeholder with a product management panel.
|
||||
- [ ] Add a basic `psinsar_results` result page.
|
||||
- [ ] Keep `psinsar_analysis` reserved until point-series UX is clear.
|
||||
|
||||
### Phase 4. Validation and rollout
|
||||
|
||||
- [x] Run one small AOI stack end to end in the experimental workflow.
|
||||
- [ ] Verify rerun, resume, and failure-recovery behavior.
|
||||
- [ ] Verify published products can be queried and rendered.
|
||||
- [ ] Add operator documentation and troubleshooting notes.
|
||||
|
||||
## 14. Experimental Environment Recommendation
|
||||
|
||||
Yes. A dedicated experiment folder is recommended.
|
||||
|
||||
Purpose:
|
||||
|
||||
- validate LT-1 stack compatibility without polluting production code
|
||||
- collect command templates, notes, and sample manifests
|
||||
- separate exploratory scripts from backend services
|
||||
|
||||
Recommended location:
|
||||
|
||||
- `experiments/isce2_sbas_timeseries/`
|
||||
|
||||
Rules:
|
||||
|
||||
- store only scripts, notes, configs, and tiny mock artifacts in git
|
||||
- do not commit raw SAR scenes, DEM rasters, or large intermediate outputs
|
||||
- once an experiment stabilizes, move the conclusion back into `docs/` and production code
|
||||
@@ -0,0 +1,536 @@
|
||||
# D-InSAR 配对增强设计文档
|
||||
|
||||
**版本**: v2.0
|
||||
**日期**: 2026-03-08
|
||||
**状态**: 设计阶段
|
||||
|
||||
---
|
||||
|
||||
## 一、背景与目标
|
||||
|
||||
### 1.1 现有系统局限
|
||||
|
||||
当前配对系统采用**单池全组合**模式:
|
||||
- 所有影像放入同一个池子,两两组合,用时间基线范围过滤
|
||||
- 无法控制主影像(master)和辅影像(slave)的时间段
|
||||
- 无法指定配对策略(星型、链式、SBAS 网络等)
|
||||
- 不支持手动选定参考影像
|
||||
|
||||
这种设计适合探索性分析,但不满足生产级 InSAR 处理需求。
|
||||
|
||||
### 1.2 行业标准实践
|
||||
|
||||
参考成熟 InSAR 软件(ISCE、LiCSBAS、MintPy、GAMMA):
|
||||
|
||||
| 特性 | 行业标准 | 现有系统 |
|
||||
|---|---|---|
|
||||
| 主/辅影像分池 | 明确的 reference/secondary 选择 | ❌ 无 |
|
||||
| 配对策略 | SBAS / Sequential / Star | ❌ 仅全组合 |
|
||||
| 时间范围控制 | 主辅池独立时间段 | ❌ 单一 start_date |
|
||||
| 参考影像指定 | 支持手动或自动选择 | ❌ 无 |
|
||||
| 多卫星支持 | 预留扩展机制 | ⚠️ 硬编码 LT-1 |
|
||||
|
||||
### 1.3 设计目标
|
||||
|
||||
1. **双池配对机制**:主影像池和辅影像池独立时间范围
|
||||
2. **多策略支持**:SBAS / Sequential / Star 三种标准策略
|
||||
3. **参考影像控制**:自动选择或用户指定
|
||||
4. **多卫星扩展性**:预留 Sentinel-1、ALOS-2、TerraSAR-X 等卫星接入
|
||||
5. **向后兼容**:现有配对逻辑作为 SBAS 策略保留
|
||||
|
||||
---
|
||||
|
||||
## 二、配对策略详解
|
||||
|
||||
### 2.1 SBAS(短基线子集)
|
||||
|
||||
**定义**:在时空基线阈值内全组合配对,形成冗余网络。
|
||||
|
||||
**适用场景**:
|
||||
- 大范围形变监测
|
||||
- 需要高时间分辨率和空间覆盖
|
||||
- 对大气误差敏感的区域
|
||||
|
||||
**参数**:
|
||||
- `time_baseline_min/max`:时间基线范围(天)
|
||||
- `spatial_baseline_max_meters`:空间基线上限(米)
|
||||
- `overlap_threshold`:重叠率阈值
|
||||
- `coverage_diversity_penalty`:覆盖多样性惩罚因子
|
||||
|
||||
**配对逻辑**:
|
||||
```
|
||||
主池影像 × 辅池影像 → 满足时空基线约束 → 覆盖优化 → 最终配对
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```
|
||||
主池: 2024-01-01 ~ 2024-06-30 (10 景)
|
||||
辅池: 2024-07-01 ~ 2024-12-31 (12 景)
|
||||
时间基线: 30~90 天
|
||||
→ 生成 45 对(经覆盖优化后约 20 对)
|
||||
```
|
||||
|
||||
### 2.2 Sequential(顺序配对)
|
||||
|
||||
**定义**:按时间排序,每景影像与后续 N 景配对。
|
||||
|
||||
**适用场景**:
|
||||
- 连续监测(如滑坡、地面沉降)
|
||||
- 需要时间连续性的形变序列
|
||||
- 计算资源有限时
|
||||
|
||||
**参数**:
|
||||
- `num_connections`:每景与后续几景配对(默认 1)
|
||||
- 其他时空约束同 SBAS
|
||||
|
||||
**配对逻辑**:
|
||||
```
|
||||
1. 合并主辅池,按日期排序
|
||||
2. 每景与后续 num_connections 景配对
|
||||
3. 应用时空基线过滤
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```
|
||||
影像序列: [A, B, C, D, E, F]
|
||||
num_connections = 2
|
||||
→ 配对: A-B, A-C, B-C, B-D, C-D, C-E, D-E, D-F, E-F
|
||||
```
|
||||
|
||||
### 2.3 Star(星型配对)
|
||||
|
||||
**定义**:选择一个参考影像,所有其他影像与它配对。
|
||||
|
||||
**适用场景**:
|
||||
- 时序形变分析(PS-InSAR 前处理)
|
||||
- 需要统一参考基准
|
||||
- 参考影像质量已知且优秀
|
||||
|
||||
**参数**:
|
||||
- `reference_image_id`:指定参考影像 ID(可选)
|
||||
- 自动选择策略:选择与其他影像平均重叠率最高的影像
|
||||
|
||||
**配对逻辑**:
|
||||
```
|
||||
1. 确定参考影像(用户指定 或 自动选择)
|
||||
2. 参考影像 × 所有其他影像
|
||||
3. 应用时空基线过滤
|
||||
```
|
||||
|
||||
**示例**:
|
||||
```
|
||||
参考影像: 2024-06-15 (质量最优)
|
||||
其他影像: 20 景
|
||||
→ 配对: 20 对(全部以 2024-06-15 为主影像)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 三、双池配对机制
|
||||
|
||||
### 3.1 时间范围控制
|
||||
|
||||
| 参数 | 说明 | 默认值 |
|
||||
|---|---|---|
|
||||
| `master_date_from` | 主影像起始日期 (YYYYMMDD) | None(不限) |
|
||||
| `master_date_to` | 主影像截止日期 (YYYYMMDD) | None(不限) |
|
||||
| `slave_date_from` | 辅影像起始日期 (YYYYMMDD) | None(不限) |
|
||||
| `slave_date_to` | 辅影像截止日期 (YYYYMMDD) | None(不限) |
|
||||
|
||||
### 3.2 池子定义规则
|
||||
|
||||
**主影像池**:
|
||||
```sql
|
||||
WHERE imaging_date >= master_date_from
|
||||
AND imaging_date <= master_date_to
|
||||
AND (其他约束: 卫星、轨道、模式、极化、精轨...)
|
||||
```
|
||||
|
||||
**辅影像池**:
|
||||
```sql
|
||||
WHERE imaging_date >= slave_date_from
|
||||
AND imaging_date <= slave_date_to
|
||||
AND (其他约束: 卫星、轨道、模式、极化、精轨...)
|
||||
```
|
||||
|
||||
### 3.3 向后兼容
|
||||
|
||||
当四个日期参数全为 `None` 时:
|
||||
- 主辅池合并为同一个池子
|
||||
- 行为等效于现有系统(`m.imaging_date <= s.imaging_date` 避免重复)
|
||||
|
||||
---
|
||||
|
||||
## 四、多卫星扩展设计
|
||||
|
||||
### 4.1 当前硬编码问题
|
||||
|
||||
现有代码中卫星相关硬编码:
|
||||
```python
|
||||
# spatial_service.py
|
||||
if master.satellite != slave.satellite:
|
||||
continue
|
||||
|
||||
# 前端显示
|
||||
satellite: "LT-1"
|
||||
```
|
||||
|
||||
### 4.2 扩展方案
|
||||
|
||||
**数据库层**:
|
||||
- `radar_data.satellite` 字段已支持任意字符串,无需修改
|
||||
- 配对函数增加 `p_allowed_satellites` 参数(数组)
|
||||
|
||||
**后端层**:
|
||||
```python
|
||||
class PairingRequest(BaseModel):
|
||||
# 新增
|
||||
allowed_satellites: Optional[List[str]] = None # ["LT-1", "S1A", "S1B"]
|
||||
cross_satellite_pairing: bool = False # 是否允许跨卫星配对
|
||||
```
|
||||
|
||||
**前端层**:
|
||||
- 配对 Modal 增加卫星多选框
|
||||
- 从数据库动态获取可用卫星列表:`SELECT DISTINCT satellite FROM radar_data`
|
||||
|
||||
**配对逻辑**:
|
||||
```python
|
||||
# 同卫星配对(默认)
|
||||
if not cross_satellite_pairing:
|
||||
if master.satellite != slave.satellite:
|
||||
continue
|
||||
|
||||
# 跨卫星配对(高级功能,需谨慎)
|
||||
if allowed_satellites:
|
||||
if master.satellite not in allowed_satellites:
|
||||
continue
|
||||
if slave.satellite not in allowed_satellites:
|
||||
continue
|
||||
```
|
||||
|
||||
### 4.3 卫星特性差异处理
|
||||
|
||||
不同卫星需要不同的配对参数:
|
||||
|
||||
| 卫星 | 典型时间基线 | 典型空间基线 | 波长 | 备注 |
|
||||
|---|---|---|---|---|
|
||||
| LT-1 | 30~90 天 | < 3000 m | L 波段 | 当前系统 |
|
||||
| Sentinel-1 | 6~12 天 | < 150 m | C 波段 | 高重访频率 |
|
||||
| ALOS-2 | 14 天 | < 500 m | L 波段 | 与 LT-1 类似 |
|
||||
| TerraSAR-X | 11 天 | < 200 m | X 波段 | 高分辨率 |
|
||||
|
||||
**建议**:
|
||||
- 前端提供"卫星预设"按钮,自动填充推荐参数
|
||||
- 数据库增加 `satellite_config` 表存储默认配对参数
|
||||
|
||||
---
|
||||
|
||||
## 五、数据库函数设计
|
||||
|
||||
### 5.1 新函数签名
|
||||
|
||||
```sql
|
||||
CREATE OR REPLACE FUNCTION find_dinsar_pairs_v2(
|
||||
-- 时空约束
|
||||
p_time_baseline_min INTEGER,
|
||||
p_time_baseline_max INTEGER,
|
||||
p_spatial_baseline_max_meters NUMERIC,
|
||||
p_overlap_threshold NUMERIC,
|
||||
p_aoi_geom GEOMETRY DEFAULT NULL,
|
||||
p_require_orbit_data BOOLEAN DEFAULT TRUE,
|
||||
p_require_same_imaging_mode BOOLEAN DEFAULT TRUE,
|
||||
p_require_same_polarization BOOLEAN DEFAULT TRUE,
|
||||
p_aoi_overlap_threshold NUMERIC DEFAULT NULL,
|
||||
|
||||
-- 双池日期(新增)
|
||||
p_master_date_from TEXT DEFAULT NULL,
|
||||
p_master_date_to TEXT DEFAULT NULL,
|
||||
p_slave_date_from TEXT DEFAULT NULL,
|
||||
p_slave_date_to TEXT DEFAULT NULL,
|
||||
|
||||
-- 多卫星支持(新增)
|
||||
p_allowed_satellites TEXT[] DEFAULT NULL,
|
||||
p_cross_satellite_pairing BOOLEAN DEFAULT FALSE
|
||||
)
|
||||
RETURNS TABLE (
|
||||
master_id INTEGER,
|
||||
slave_id INTEGER,
|
||||
master_imaging_date TEXT,
|
||||
slave_imaging_date TEXT,
|
||||
time_baseline_days INTEGER,
|
||||
spatial_baseline_meters NUMERIC,
|
||||
overlap_ratio NUMERIC
|
||||
)
|
||||
```
|
||||
|
||||
### 5.2 核心 SQL 逻辑变化
|
||||
|
||||
```sql
|
||||
-- 现有逻辑
|
||||
WHERE m.id < s.id -- 避免重复
|
||||
AND m.satellite = s.satellite
|
||||
|
||||
-- 新逻辑
|
||||
WHERE m.id <> s.id
|
||||
AND m.imaging_date <= s.imaging_date -- 主影像早于辅影像
|
||||
-- 主池日期约束
|
||||
AND (p_master_date_from IS NULL OR m.imaging_date >= p_master_date_from)
|
||||
AND (p_master_date_to IS NULL OR m.imaging_date <= p_master_date_to)
|
||||
-- 辅池日期约束
|
||||
AND (p_slave_date_from IS NULL OR s.imaging_date >= p_slave_date_from)
|
||||
AND (p_slave_date_to IS NULL OR s.imaging_date <= p_slave_date_to)
|
||||
-- 卫星约束
|
||||
AND (p_allowed_satellites IS NULL OR m.satellite = ANY(p_allowed_satellites))
|
||||
AND (p_allowed_satellites IS NULL OR s.satellite = ANY(p_allowed_satellites))
|
||||
AND (p_cross_satellite_pairing OR m.satellite = s.satellite)
|
||||
```
|
||||
|
||||
### 5.3 部署策略
|
||||
|
||||
1. 创建 `003_pairing_enhancement.sql` 迁移文件
|
||||
2. `init_db.py` 自动应用(已有机制,line 354-356)
|
||||
3. 保留 `find_dinsar_pairs` 旧函数(向后兼容)
|
||||
4. 后端优先调用 `find_dinsar_pairs_v2`,失败时回退
|
||||
|
||||
---
|
||||
|
||||
## 六、后端 API 设计
|
||||
|
||||
### 6.1 Schema 变更
|
||||
|
||||
```python
|
||||
# models/schemas.py
|
||||
|
||||
class PairingRequest(BaseModel):
|
||||
"""D-InSAR 配对请求参数(增强版)"""
|
||||
|
||||
# === 时空约束(保留) ===
|
||||
time_baseline_min: int = Field(default=1, ge=0, le=3650)
|
||||
time_baseline_max: int = Field(default=90, ge=1, le=3650)
|
||||
overlap_threshold: float = Field(default=0.5, ge=0.0, le=1.0)
|
||||
spatial_baseline_max_meters: int = Field(default=3000, ge=0, le=100000)
|
||||
coverage_diversity_penalty: float = Field(default=0.3, ge=0.0, le=1.0)
|
||||
require_same_imaging_mode: bool = True
|
||||
require_same_polarization: bool = True
|
||||
aoi_overlap_threshold: Optional[float] = Field(default=None, ge=0.0, le=1.0)
|
||||
|
||||
# === 双池日期(新增) ===
|
||||
master_date_from: Optional[str] = Field(default=None, pattern=r'^\d{8}$')
|
||||
master_date_to: Optional[str] = Field(default=None, pattern=r'^\d{8}$')
|
||||
slave_date_from: Optional[str] = Field(default=None, pattern=r'^\d{8}$')
|
||||
slave_date_to: Optional[str] = Field(default=None, pattern=r'^\d{8}$')
|
||||
|
||||
# === 配对策略(新增) ===
|
||||
strategy: str = Field(default="sbas", pattern=r'^(sbas|sequential|star)$')
|
||||
num_connections: int = Field(default=1, ge=1, le=10)
|
||||
reference_image_id: Optional[int] = None
|
||||
|
||||
# === 多卫星支持(新增) ===
|
||||
allowed_satellites: Optional[List[str]] = None
|
||||
cross_satellite_pairing: bool = False
|
||||
|
||||
# === 向后兼容(保留) ===
|
||||
start_date: Optional[str] = Field(default=None, pattern=r'^\d{8}$')
|
||||
|
||||
@field_validator('master_date_to')
|
||||
def validate_master_date_range(cls, v, info):
|
||||
if v and info.data.get('master_date_from'):
|
||||
if v < info.data['master_date_from']:
|
||||
raise ValueError('master_date_to must >= master_date_from')
|
||||
return v
|
||||
|
||||
@field_validator('slave_date_to')
|
||||
def validate_slave_date_range(cls, v, info):
|
||||
if v and info.data.get('slave_date_from'):
|
||||
if v < info.data['slave_date_from']:
|
||||
raise ValueError('slave_date_to must >= slave_date_from')
|
||||
return v
|
||||
```
|
||||
|
||||
### 6.2 服务层变更
|
||||
|
||||
```python
|
||||
# services/spatial_service.py
|
||||
|
||||
async def find_dinsar_pairs(
|
||||
self,
|
||||
db: AsyncSession,
|
||||
params: PairingRequest,
|
||||
aoi_wkt: Optional[str] = None,
|
||||
require_orbit_data: bool = True
|
||||
) -> Tuple[List[RadarPair], List[str], bool]:
|
||||
"""
|
||||
增强版配对逻辑:
|
||||
1. 调用 find_dinsar_pairs_v2 SQL 函数
|
||||
2. 根据 strategy 后处理结果
|
||||
3. 回退路径同步更新
|
||||
"""
|
||||
|
||||
# 1. 调用数据库函数
|
||||
pairs = await self._call_db_pairing_function(db, params, aoi_wkt, require_orbit_data)
|
||||
|
||||
# 2. 策略后处理
|
||||
if params.strategy == "sbas":
|
||||
# 覆盖优化(现有逻辑)
|
||||
pairs = self._optimize_coverage_diversity(pairs, params.coverage_diversity_penalty)
|
||||
|
||||
elif params.strategy == "sequential":
|
||||
# 顺序配对
|
||||
pairs = self._apply_sequential_strategy(pairs, params.num_connections)
|
||||
|
||||
elif params.strategy == "star":
|
||||
# 星型配对
|
||||
pairs = self._apply_star_strategy(pairs, params.reference_image_id)
|
||||
|
||||
# 3. 生成 task_name 并去重
|
||||
pairs = self._generate_task_names(pairs)
|
||||
|
||||
return pairs, warnings, fallback_used
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 七、前端 UI 设计
|
||||
|
||||
### 7.1 配对 Modal 布局
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ D-InSAR 配对参数 │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ 配对策略: ○ SBAS ○ Sequential ○ Star │
|
||||
│ │
|
||||
│ [策略说明] │
|
||||
│ SBAS: 短基线子集,形成冗余网络,适合大范围监测 │
|
||||
│ │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ 主影像时间范围 │
|
||||
│ 从: [日期选择器 YYYYMMDD] 至: [日期选择器] │
|
||||
│ │
|
||||
│ 辅影像时间范围 │
|
||||
│ 从: [日期选择器 YYYYMMDD] 至: [日期选择器] │
|
||||
│ │
|
||||
│ ☑ 使用双池模式(不勾选则主辅池合并) │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ 时间基线: [1] ~ [90] 天 │
|
||||
│ 空间基线上限: [3000] 米 │
|
||||
│ 重叠率阈值: [0.5] │
|
||||
│ 覆盖多样性惩罚: [0.3] │
|
||||
│ │
|
||||
│ ☑ 成像模式一致 ☑ 极化一致 ☑ 仅精轨影像 │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ AOI 来源: ○ 上传 SHP ○ 行政区选择 │
|
||||
│ [AOI 选择区域...] │
|
||||
│ │
|
||||
│ AOI 覆盖率阈值: [0] (0 表示不限制) │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ 高级选项 [展开 ▼] │
|
||||
│ 卫星选择: ☑ LT-1 ☐ Sentinel-1 ☐ ALOS-2 │
|
||||
│ ☐ 允许跨卫星配对(谨慎使用) │
|
||||
│ │
|
||||
│ Sequential 参数: │
|
||||
│ 每景配对数: [1] │
|
||||
│ │
|
||||
│ Star 参数: │
|
||||
│ 参考影像: [自动选择 ▼] 或 手动指定 ID: [___] │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ [取消] [开始配对] │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 7.2 策略切换交互
|
||||
|
||||
- 选择 SBAS:显示"覆盖多样性惩罚"参数
|
||||
- 选择 Sequential:显示"每景配对数"参数
|
||||
- 选择 Star:显示"参考影像选择"下拉框(从已有影像列表动态加载)
|
||||
|
||||
### 7.3 卫星选择
|
||||
|
||||
- 从 `GET /api/radar-data/available-satellites` 动态获取
|
||||
- 默认只勾选 LT-1
|
||||
- 跨卫星配对显示警告提示
|
||||
|
||||
---
|
||||
|
||||
## 八、实施计划
|
||||
|
||||
### Phase 1: 数据库层(1 天)
|
||||
|
||||
- [ ] 创建 `003_pairing_enhancement.sql`
|
||||
- [ ] 实现 `find_dinsar_pairs_v2` 函数
|
||||
- [ ] 更新 `init_db.py` 应用迁移
|
||||
- [ ] 测试 SQL 函数(手动执行验证)
|
||||
|
||||
### Phase 2: 后端层(2 天)
|
||||
|
||||
- [ ] 更新 `PairingRequest` Schema
|
||||
- [ ] 实现策略后处理逻辑(Sequential / Star)
|
||||
- [ ] 更新 `spatial_service.py` 调用新函数
|
||||
- [ ] 更新回退路径逻辑
|
||||
- [ ] 新增 `GET /api/radar-data/available-satellites` 端点
|
||||
- [ ] 单元测试(pytest)
|
||||
|
||||
### Phase 3: 前端层(2 天)
|
||||
|
||||
- [ ] 更新 `pairingStore.js` 增加新字段
|
||||
- [ ] 重构 `PairingModal.jsx` UI
|
||||
- [ ] 实现策略切换交互
|
||||
- [ ] 实现卫星多选
|
||||
- [ ] 更新 `usePairingLogic.js` 传参
|
||||
- [ ] 前端构建测试
|
||||
|
||||
### Phase 4: 集成测试(1 天)
|
||||
|
||||
- [ ] 端到端测试(SBAS / Sequential / Star)
|
||||
- [ ] 双池配对测试
|
||||
- [ ] 多卫星配对测试
|
||||
- [ ] 向后兼容测试(旧参数仍可用)
|
||||
- [ ] 性能测试(大数据量)
|
||||
|
||||
### Phase 5: 文档与部署(0.5 天)
|
||||
|
||||
- [ ] 更新 `CLAUDE.md` 记录改动
|
||||
- [ ] 更新用户手册(如有)
|
||||
- [ ] 部署到测试环境
|
||||
- [ ] 用户验收测试
|
||||
|
||||
**总计**: 约 6.5 天
|
||||
|
||||
---
|
||||
|
||||
## 九、风险与缓解
|
||||
|
||||
| 风险 | 影响 | 缓解措施 |
|
||||
|---|---|---|
|
||||
| SQL 函数性能下降 | 配对速度变慢 | 保留旧函数,性能对比测试 |
|
||||
| 前端 UI 复杂度增加 | 用户学习成本 | 提供"快速模式"和"高级模式"切换 |
|
||||
| 多卫星配对参数不当 | 生成无效配对 | 前端参数校验 + 后端警告提示 |
|
||||
| 向后兼容性破坏 | 旧配对失效 | 保留 `start_date` 参数,自动转换 |
|
||||
|
||||
---
|
||||
|
||||
## 十、未来扩展
|
||||
|
||||
1. **基线网络可视化**:时间-空间基线散点图(D3.js / ECharts)
|
||||
2. **配对质量评分**:根据相干性、大气条件预估配对质量
|
||||
3. **自动参数推荐**:基于历史配对结果的机器学习推荐
|
||||
4. **批量配对模板**:保存常用配对参数为模板
|
||||
5. **Sentinel-1 轨道框架**:支持 Sentinel-1 的 Track/Frame 概念
|
||||
|
||||
---
|
||||
|
||||
## 附录 A:术语对照表
|
||||
|
||||
| 中文 | 英文 | 说明 |
|
||||
|---|---|---|
|
||||
| 主影像 | Master / Reference | 配对中的参考影像 |
|
||||
| 辅影像 | Slave / Secondary | 配对中的从属影像 |
|
||||
| 时间基线 | Temporal Baseline | 两景影像的时间间隔 |
|
||||
| 空间基线 | Spatial Baseline | 两景影像的空间距离 |
|
||||
| 短基线子集 | SBAS (Small Baseline Subset) | 配对策略之一 |
|
||||
| 星型配对 | Star Graph | 单主影像配对策略 |
|
||||
| 顺序配对 | Sequential Pairing | 时间顺序配对策略 |
|
||||
|
||||
---
|
||||
|
||||
**文档结束**
|
||||
@@ -0,0 +1,867 @@
|
||||
# InSAR 配对系统重构方案
|
||||
|
||||
更新日期:2026-04-13
|
||||
状态:待实施
|
||||
适用范围:`Z:\Code\Insar_management_system_v2` 当前代码基线
|
||||
|
||||
## 1. 文档目标
|
||||
|
||||
这份文档不是抽象算法说明,而是基于当前系统现状给出的可落地重构方案,目标是:
|
||||
|
||||
- 重构 D-InSAR 配对内核,解决当前正确性、稳定性和可维护性问题
|
||||
- 尽量复用现有数据库自动维护、root/cursor 自维护、manifest inventory、自检面板
|
||||
- 明确开发机条件下的迁移策略
|
||||
- 为后续实现提供分阶段执行顺序、表设计、接口边界和验收口径
|
||||
|
||||
## 2. 当前系统基础与约束
|
||||
|
||||
### 2.1 当前可直接复用的系统能力
|
||||
|
||||
当前系统已经具备以下基础设施,重构应直接接入,而不是另起一套平行机制:
|
||||
|
||||
- 数据库启动自维护
|
||||
- `backend/app/db_maintenance.py`
|
||||
- `ensure_database_ready()` 会在启动时创建表、补缺列、应用 SQL migration,并可在开发机上按环境变量执行整库 schema reset
|
||||
- root 与 cursor 自维护
|
||||
- `backend/app/services/root_registry_service.py`
|
||||
- 启动时会根据 `.env` 自动同步 `managed_roots` 与 `scan_cursors`
|
||||
- manifest inventory 自维护
|
||||
- `backend/app/services/manifest_inventory_service.py`
|
||||
- 会维护 `path_inventory`、`scan_cursors.last_seen_*`、fingerprint 与 root 缺失状态
|
||||
- 结果目录自维护
|
||||
- `backend/app/services/dinsar_scan_service.py`
|
||||
- D-InSAR 统一扫描链路已经是“发布 -> manifest inventory -> catalog rebuild -> compat sync”
|
||||
- 健康检查和一致性检查
|
||||
- `backend/app/services/health_service.py`
|
||||
- `frontend/src/HealthCheckPanel.jsx`
|
||||
- 当前自检已包含:数据库 schema、worker、catalog、source roots、D-InSAR bridge、一致性统计、系统维护入口
|
||||
|
||||
### 2.2 当前配对系统的核心问题
|
||||
|
||||
现有配对系统的主要问题不是功能不够多,而是核心语义不稳定:
|
||||
|
||||
- SQL 路径和 Python fallback 路径不是同一算法
|
||||
- 同日影像会出现反向重复配对
|
||||
- `start_date` 在 SQL 主路径上已失效,但接口仍保留
|
||||
- `star` 策略没有固定参考像角色
|
||||
- `sequential` 对同日多景不稳定
|
||||
- `all` 策略排序与前端描述不一致
|
||||
- `aoi_overlap_threshold=0` 的语义前后端不一致
|
||||
- 当前任务命名与配对唯一性混在一起,业务主键不稳定
|
||||
|
||||
### 2.3 当前系统约束
|
||||
|
||||
- 当前扫描模式是 manual-only,调度器默认关闭
|
||||
- `backend/app/scheduler.py`
|
||||
- `radar_data` 是当前源影像事实表,配对系统必须以它为唯一源
|
||||
- `dinsar_task_batches` / `dinsar_task_items` 已经是生产任务落地表
|
||||
- `result_products` / `dinsar_product_profiles` 已经承担结果资产追踪
|
||||
- 健康检查和统计接口已上线,新增配对系统状态应并入现有面板
|
||||
|
||||
## 3. 重构总目标
|
||||
|
||||
本轮重构的目标不是继续修补 `find_dinsar_pairs_v2`,而是将配对系统改造成四层结构:
|
||||
|
||||
1. 影像事实层
|
||||
2. 配对指标缓存层
|
||||
3. 网络选择层
|
||||
4. 任务物化层
|
||||
|
||||
核心原则:
|
||||
|
||||
- 候选对生成与网络选择分层
|
||||
- 所有策略共享同一候选池
|
||||
- 所有配对方向统一规范化
|
||||
- 所有结果可追溯到策略版本、参数快照和候选边来源
|
||||
- 不再保留“算法不等价的 fallback”
|
||||
|
||||
## 4. 目标架构
|
||||
|
||||
### 4.1 Layer A:影像事实层
|
||||
|
||||
沿用现有 `radar_data` 表作为唯一源影像事实表。
|
||||
|
||||
重构要求:
|
||||
|
||||
- `radar_data.id` 继续作为数据库主键
|
||||
- 引入明确的场景业务键 `scene_uid`
|
||||
- 优先复用 `radar_data.unique_id`
|
||||
- 若历史数据存在 `unique_id` 缺失或不稳定,启动时执行一次回填
|
||||
- 回填规则:`hash(normalized file_path)`,并写入新列或标准化字段
|
||||
- 所有配对相关对象都基于 `scene_uid` 或 `radar_data.id` 建立引用
|
||||
|
||||
建议:
|
||||
|
||||
- 本轮实现阶段直接在 `radar_data` 上新增:
|
||||
- `scene_uid`
|
||||
- `scene_signature`
|
||||
- `pairing_eligible`
|
||||
- `pairing_updated_at`
|
||||
- 如果希望减少变更面,也可以只新增 `scene_uid` 与 `pairing_updated_at`
|
||||
|
||||
### 4.2 Layer B:配对指标缓存层
|
||||
|
||||
这是本次重构的核心。
|
||||
|
||||
目标:
|
||||
|
||||
- 将“任意两个影像是否可配、其基础指标是什么”从请求时临时计算,改为可维护的持久化缓存
|
||||
- 缓存只保存与 AOI 无关的基础指标
|
||||
- AOI 相关指标在请求时动态叠加计算
|
||||
|
||||
建议新增表:
|
||||
|
||||
#### `pairing_metric_cache`
|
||||
|
||||
一行代表一个规范化后的影像对。
|
||||
|
||||
建议字段:
|
||||
|
||||
- `id`
|
||||
- `master_scene_ref_id`
|
||||
- `slave_scene_ref_id`
|
||||
- `master_scene_uid`
|
||||
- `slave_scene_uid`
|
||||
- `pair_uid`
|
||||
- `metric_version`
|
||||
- `orientation_rule_version`
|
||||
- `time_baseline_days`
|
||||
- `spatial_baseline_meters`
|
||||
- `scene_overlap_ratio`
|
||||
- `orbit_direction`
|
||||
- `same_satellite`
|
||||
- `same_imaging_mode`
|
||||
- `same_polarization`
|
||||
- `master_imaging_date`
|
||||
- `slave_imaging_date`
|
||||
- `master_satellite`
|
||||
- `slave_satellite`
|
||||
- `master_imaging_mode`
|
||||
- `slave_imaging_mode`
|
||||
- `master_polarization`
|
||||
- `slave_polarization`
|
||||
- `master_file_path`
|
||||
- `slave_file_path`
|
||||
- `status`
|
||||
- `computed_at`
|
||||
|
||||
唯一约束:
|
||||
|
||||
- `unique(master_scene_ref_id, slave_scene_ref_id, metric_version)`
|
||||
|
||||
关键约定:
|
||||
|
||||
- `master/slave` 不是请求期可变概念,而是规范化方向
|
||||
- 规范化规则:
|
||||
- 早时间 = master
|
||||
- 同时间时,较小 `scene_uid` 或较小 `id` = master
|
||||
|
||||
### 4.3 Layer C:缓存状态与脏队列
|
||||
|
||||
为了接入现有“自动维护 + 健康检查”体系,建议新增两张状态表。
|
||||
|
||||
#### `pairing_cache_state`
|
||||
|
||||
建议设计为单例表或按 `cache_scope='global'` 管理。
|
||||
|
||||
字段建议:
|
||||
|
||||
- `id`
|
||||
- `cache_scope`
|
||||
- `metric_version`
|
||||
- `status`
|
||||
- `scene_count`
|
||||
- `pair_count`
|
||||
- `dirty_scene_count`
|
||||
- `last_full_rebuild_at`
|
||||
- `last_incremental_reconcile_at`
|
||||
- `last_error`
|
||||
- `updated_at`
|
||||
|
||||
状态建议:
|
||||
|
||||
- `READY`
|
||||
- `DIRTY`
|
||||
- `REBUILDING`
|
||||
- `DEGRADED`
|
||||
- `FAILED`
|
||||
|
||||
#### `pairing_dirty_scenes`
|
||||
|
||||
用于增量维护。
|
||||
|
||||
字段建议:
|
||||
|
||||
- `id`
|
||||
- `scene_ref_id`
|
||||
- `scene_uid`
|
||||
- `reason`
|
||||
- `marked_at`
|
||||
- `resolved_at`
|
||||
- `status`
|
||||
|
||||
唯一约束建议:
|
||||
|
||||
- `unique(scene_ref_id, status='PENDING')` 或代码层去重
|
||||
|
||||
### 4.4 Layer D:网络选择层
|
||||
|
||||
网络选择层不直接查 `radar_data`,而是查 `pairing_metric_cache`。
|
||||
|
||||
建议新增:
|
||||
|
||||
#### `pairing_network_runs`
|
||||
|
||||
代表一次配对请求或一次已保存的网络。
|
||||
|
||||
字段建议:
|
||||
|
||||
- `id`
|
||||
- `network_run_id`
|
||||
- `strategy`
|
||||
- `policy_version`
|
||||
- `request_hash`
|
||||
- `request_params_json`
|
||||
- `aoi_source`
|
||||
- `aoi_hash`
|
||||
- `aoi_summary_json`
|
||||
- `candidate_count`
|
||||
- `selected_edge_count`
|
||||
- `warning_count`
|
||||
- `status`
|
||||
- `fallback_used`
|
||||
- `created_by`
|
||||
- `created_at`
|
||||
|
||||
#### `pairing_network_edges`
|
||||
|
||||
代表某个网络 run 选中的边。
|
||||
|
||||
字段建议:
|
||||
|
||||
- `id`
|
||||
- `network_run_ref_id`
|
||||
- `metric_cache_ref_id`
|
||||
- `edge_rank`
|
||||
- `selection_reason`
|
||||
- `selection_score`
|
||||
- `selection_meta_json`
|
||||
- `is_reference_edge`
|
||||
- `created_at`
|
||||
|
||||
唯一约束:
|
||||
|
||||
- `unique(network_run_ref_id, metric_cache_ref_id)`
|
||||
|
||||
### 4.5 Layer E:任务物化层
|
||||
|
||||
当前 `dinsar_task_batches` / `dinsar_task_items` 可继续保留。
|
||||
|
||||
但建议增加可追溯字段:
|
||||
|
||||
在 `dinsar_task_items` 上新增:
|
||||
|
||||
- `network_run_id`
|
||||
- `network_edge_id`
|
||||
- `policy_version`
|
||||
- `selection_strategy`
|
||||
- `scene_pair_uid`
|
||||
|
||||
在结果侧建议同步补充:
|
||||
|
||||
- `result_products.pair_key` 继续保留
|
||||
- 在 `dinsar_product_profiles` 或 `params_json` 中补充:
|
||||
- `network_run_id`
|
||||
- `network_edge_id`
|
||||
- `policy_version`
|
||||
- `master_scene_uid`
|
||||
- `slave_scene_uid`
|
||||
|
||||
这样最终可以把:
|
||||
|
||||
- 配对网络
|
||||
- 任务项
|
||||
- 生产结果
|
||||
|
||||
串成一条可追踪链路。
|
||||
|
||||
## 5. 配对算法设计
|
||||
|
||||
### 5.1 候选对生成
|
||||
|
||||
候选对生成只负责回答:
|
||||
|
||||
- 这两个场景在基础事实上是否可配
|
||||
- 基础指标是什么
|
||||
|
||||
不负责回答:
|
||||
|
||||
- 用什么策略选
|
||||
- 最后是否进入任务
|
||||
|
||||
基础硬约束:
|
||||
|
||||
- `master != slave`
|
||||
- 方向规范化后只保留一个方向
|
||||
- 同轨向
|
||||
- 日期格式合法
|
||||
- 几何相交
|
||||
|
||||
基础指标:
|
||||
|
||||
- `time_baseline_days`
|
||||
- `spatial_baseline_meters`
|
||||
- `scene_overlap_ratio`
|
||||
|
||||
注意:
|
||||
|
||||
- `same_satellite`
|
||||
- `same_imaging_mode`
|
||||
- `same_polarization`
|
||||
|
||||
只作为缓存事实,不在缓存层提前删边,以便不同请求复用同一缓存。
|
||||
|
||||
### 5.2 请求期过滤
|
||||
|
||||
请求期过滤发生在 `pairing_metric_cache` 之上:
|
||||
|
||||
- `time_baseline_min/max`
|
||||
- `spatial_baseline_max_meters`
|
||||
- `overlap_threshold`
|
||||
- `allowed_satellites`
|
||||
- `cross_satellite_pairing`
|
||||
- `require_same_imaging_mode`
|
||||
- `require_same_polarization`
|
||||
- `master_date_from/to`
|
||||
- `slave_date_from/to`
|
||||
- `aoi_overlap_threshold`
|
||||
|
||||
AOI 处理原则:
|
||||
|
||||
- AOI 不进入持久化 pair cache 主键
|
||||
- AOI 相关 overlap 在请求时动态计算
|
||||
- `0` 不再表示数值阈值,而统一转成 `null`
|
||||
- 前后端统一语义:
|
||||
- `null` = 不启用 AOI 覆盖阈值
|
||||
- `0 < x <= 1` = 启用阈值
|
||||
|
||||
### 5.3 四种策略的正式定义
|
||||
|
||||
#### `all`
|
||||
|
||||
- 返回全部合法候选边
|
||||
- 排序统一为稳定顺序:
|
||||
- `master_imaging_date`
|
||||
- `slave_imaging_date`
|
||||
- `selection_score desc`
|
||||
- `pair_uid`
|
||||
|
||||
不再使用“数据库返回顺序”作为结果语义。
|
||||
|
||||
#### `sequential`
|
||||
|
||||
- 基于稳定时间序列排序构图
|
||||
- 排序键:
|
||||
- `acquisition_time_utc`
|
||||
- 无则 `imaging_date`
|
||||
- 再无则 `scene_uid`
|
||||
- 每景连接后续 `N` 景
|
||||
- 同日多景必须稳定,不允许受候选池原始顺序影响
|
||||
|
||||
#### `star`
|
||||
|
||||
- 参考像必须固定角色
|
||||
- 本系统建议统一采用:
|
||||
- 参考像始终为 `master`
|
||||
- 若物理上参考像日期晚于目标像,则在选择层进行拒绝或显式反转并记录相位方向规则
|
||||
|
||||
本轮建议:
|
||||
|
||||
- 为降低复杂度,先采用“参考像必须是 master”的严格规则
|
||||
- 如果用户选择的参考像晚于部分场景,则这些边不入网,并给出 warning
|
||||
|
||||
#### `sbas`
|
||||
|
||||
本轮不采用“简单按 overlap 贪心去重”的旧实现。
|
||||
|
||||
建议目标:
|
||||
|
||||
- 图连通
|
||||
- 每景至少达到最小连接数
|
||||
- 时间覆盖尽量连续
|
||||
- 每景度数受控,避免过密
|
||||
- AOI 覆盖尽量多样
|
||||
|
||||
可落地实现:
|
||||
|
||||
1. 从合法候选边中先构造基础时间邻接骨架
|
||||
2. 保证每个场景至少连接前后最近邻
|
||||
3. 再按综合评分补边
|
||||
4. 每补一条边,对已覆盖区域施加惩罚
|
||||
5. 达到目标连通性和最大边数后停止
|
||||
|
||||
综合评分建议:
|
||||
|
||||
`selection_score = w_time * time_score + w_spatial * spatial_score + w_overlap * overlap_score + w_aoi * aoi_gain - w_redundancy * redundancy_penalty`
|
||||
|
||||
### 5.4 删除旧 fallback 的原则
|
||||
|
||||
本轮不再保留“Python 语义独立 fallback”。
|
||||
|
||||
替代方案:
|
||||
|
||||
- 候选边缓存是主路径
|
||||
- 如果缓存不存在或脏数据过多:
|
||||
- 返回明确的 `DEGRADED` / `DIRTY` 状态
|
||||
- 允许管理员触发重建
|
||||
- 或在安全阈值内自动执行增量重建
|
||||
|
||||
也就是说:
|
||||
|
||||
- 可以有“降级状态”
|
||||
- 不能再有“悄悄换算法”
|
||||
|
||||
## 6. 与现有扫描和自动维护机制的集成
|
||||
|
||||
### 6.1 源影像扫描后的集成点
|
||||
|
||||
当前源影像扫描入口:
|
||||
|
||||
- `backend/app/services/data_service.py::scan_radar_data`
|
||||
- `backend/app/scheduler.py::scan_data_job`
|
||||
|
||||
建议改造:
|
||||
|
||||
在 `scan_radar_data()` 中,对新增、更新、删除的 `radar_data` 记录执行:
|
||||
|
||||
1. 标记 `pairing_dirty_scenes`
|
||||
2. 更新 `pairing_cache_state.status = DIRTY`
|
||||
3. 根据变更规模决定是否立即增量重建
|
||||
|
||||
建议阈值:
|
||||
|
||||
- 变更场景数 `<= SMALL_RECONCILE_LIMIT`
|
||||
- 扫描结束后直接执行增量 reconcile
|
||||
- 变更场景数 `> SMALL_RECONCILE_LIMIT`
|
||||
- 仅标脏,不在扫描链路内同步重建
|
||||
- 由管理员在自检面板手动触发
|
||||
|
||||
这与当前 manual-only 模式兼容,不依赖后台常驻调度器。
|
||||
|
||||
### 6.2 启动期自维护
|
||||
|
||||
当前启动期会执行:
|
||||
|
||||
- `ensure_database_ready()`
|
||||
- `root_registry_service.sync_from_settings()`
|
||||
- `manifest_inventory_service.sync_manifest_roots()`
|
||||
- catalog bootstrap
|
||||
- health check
|
||||
|
||||
建议新增启动动作:
|
||||
|
||||
- `pairing_service.bootstrap_pairing_cache_state()`
|
||||
|
||||
只做轻量检查:
|
||||
|
||||
- 新表是否存在
|
||||
- 是否有缓存状态行
|
||||
- `metric_version` 是否匹配当前版本
|
||||
- dirty scene 是否堆积
|
||||
|
||||
不建议启动即全量重建,避免启动时间失控。
|
||||
|
||||
### 6.3 数据库自动维护策略
|
||||
|
||||
当前数据库自维护具备两个模式:
|
||||
|
||||
- 常规模式:建表、补列、应用 migration
|
||||
- 开发机强制 reset 模式:整库 schema reset
|
||||
|
||||
由于本轮是配对内核重构,且当前是开发机,推荐采用:
|
||||
|
||||
#### 推荐落地策略
|
||||
|
||||
第一轮实施时直接执行一次开发机 schema reset。
|
||||
|
||||
原因:
|
||||
|
||||
- 旧配对 SQL 函数与新缓存设计语义完全不同
|
||||
- 旧任务表中的配对数据没有保留价值
|
||||
- 旧结果可以通过 manifest inventory 和结果 catalog 重建
|
||||
- 源影像可以重新扫描
|
||||
|
||||
具体方式:
|
||||
|
||||
- 合并新 ORM 与 migration 后
|
||||
- 启动前临时设置:
|
||||
- `DB_SCHEMA_RESET_ON_MISMATCH=true`
|
||||
- `DB_SCHEMA_RESET_CONFIRM=true`
|
||||
- 启动一次后让 `ensure_database_ready()` 重建 schema
|
||||
- 启动完成后恢复这两个环境变量为 `false`
|
||||
|
||||
### 6.4 migration 文件策略
|
||||
|
||||
当前 `db_maintenance.py` 中 migration 顺序为:
|
||||
|
||||
- `001_st_intersection_agg.sql`
|
||||
- `002_spatial_functions.sql`
|
||||
- `003_pairing_enhancement.sql`
|
||||
|
||||
建议新增:
|
||||
|
||||
- `004_pairing_refactor.sql`
|
||||
|
||||
该 migration 负责:
|
||||
|
||||
- 创建新 pairing 表
|
||||
- 创建必要索引
|
||||
- 创建候选边计算函数或 SQL helper view
|
||||
- 如有需要,保留旧函数为 deprecated wrapper
|
||||
|
||||
建议:
|
||||
|
||||
- 不要在 `004` 里继续强化 `find_dinsar_pairs_v2`
|
||||
- 将旧函数标记 deprecated,仅作为过渡壳
|
||||
- 新逻辑直接走新 service + 新表
|
||||
|
||||
## 7. 自检与一致性检测升级
|
||||
|
||||
### 7.1 Health 接口扩展
|
||||
|
||||
当前健康检查聚合点:
|
||||
|
||||
- `backend/app/services/health_service.py::get_health_status`
|
||||
|
||||
建议新增检查项:
|
||||
|
||||
#### `pairing_system`
|
||||
|
||||
字段建议:
|
||||
|
||||
- `ok`
|
||||
- `status`
|
||||
- `metric_version`
|
||||
- `scene_count`
|
||||
- `pair_count`
|
||||
- `dirty_scene_count`
|
||||
- `network_run_count_last_7d`
|
||||
- `last_full_rebuild_at`
|
||||
- `last_incremental_reconcile_at`
|
||||
- `last_error`
|
||||
- `duplicate_reverse_pair_count`
|
||||
- `orphan_metric_scene_ref_count`
|
||||
- `task_without_network_ref_count`
|
||||
- `result_without_pair_trace_count`
|
||||
|
||||
判定规则建议:
|
||||
|
||||
- `READY` 且无关键异常 -> `ok=true`
|
||||
- `DIRTY` 但 dirty 数量较少 -> `ok=false`,级别 `warn`
|
||||
- 存在重复反向边、孤儿引用、版本漂移 -> `ok=false`,级别 `error`
|
||||
|
||||
### 7.2 Health 面板扩展
|
||||
|
||||
当前 `HealthCheckPanel.jsx` 已能展示结构化卡片。
|
||||
|
||||
建议新增卡片:
|
||||
|
||||
- `配对系统`
|
||||
|
||||
展示内容:
|
||||
|
||||
- 缓存状态
|
||||
- 指标版本
|
||||
- 场景数 / 缓存对数
|
||||
- dirty scene 数
|
||||
- 最近全量 / 增量时间
|
||||
- 关键错误数
|
||||
|
||||
操作按钮建议:
|
||||
|
||||
- `增量修复配对缓存`
|
||||
- `全量重建配对缓存`
|
||||
- `清理旧网络记录`
|
||||
|
||||
### 7.3 Statistics 接口扩展
|
||||
|
||||
当前 `/statistics` 已承担一致性统计角色。
|
||||
|
||||
建议追加:
|
||||
|
||||
- `pairing_consistency`
|
||||
|
||||
字段建议:
|
||||
|
||||
- `metric_cache_count`
|
||||
- `duplicate_reverse_pair_count`
|
||||
- `invalid_orientation_count`
|
||||
- `network_edge_orphan_count`
|
||||
- `task_orphan_count`
|
||||
- `result_trace_missing_count`
|
||||
|
||||
## 8. API 重构计划
|
||||
|
||||
### 8.1 后端接口
|
||||
|
||||
当前接口:
|
||||
|
||||
- `POST /api/find-pairs`
|
||||
|
||||
建议分两阶段改造。
|
||||
|
||||
#### Phase 1:保持前端可用
|
||||
|
||||
保留:
|
||||
|
||||
- `POST /api/find-pairs`
|
||||
|
||||
但内部改为:
|
||||
|
||||
- 从 `pairing_metric_cache` 查询
|
||||
- 选择网络
|
||||
- 返回 `network_run_id + pairs`
|
||||
|
||||
#### Phase 2:正式 API
|
||||
|
||||
新增:
|
||||
|
||||
- `POST /api/pairing/preview`
|
||||
- `POST /api/pairing/networks/{network_run_id}/materialize-batch`
|
||||
- `POST /api/pairing/reconcile-dirty`
|
||||
- `POST /api/pairing/rebuild-cache`
|
||||
- `GET /api/pairing/health`
|
||||
- `GET /api/pairing/networks/{network_run_id}`
|
||||
|
||||
### 8.2 请求模型
|
||||
|
||||
`PairingRequest` 应清理为正式语义:
|
||||
|
||||
- 删除 `start_date`
|
||||
- `aoi_overlap_threshold` 前端空值统一传 `null`
|
||||
- `strategy` 继续保留:
|
||||
- `all`
|
||||
- `sequential`
|
||||
- `star`
|
||||
- `sbas`
|
||||
- `reference_image_id` 仅在 `star` 时有效
|
||||
|
||||
### 8.3 返回模型
|
||||
|
||||
建议新增返回字段:
|
||||
|
||||
- `network_run_id`
|
||||
- `policy_version`
|
||||
- `candidate_count`
|
||||
- `selected_edge_count`
|
||||
- `degraded`
|
||||
- `warnings`
|
||||
|
||||
## 9. 前端改造计划
|
||||
|
||||
涉及文件:
|
||||
|
||||
- `frontend/src/components/PairingModal.jsx`
|
||||
- `frontend/src/hooks/usePairingLogic.js`
|
||||
- `frontend/src/store/pairingStore.js`
|
||||
|
||||
改造原则:
|
||||
|
||||
- 前端不再承载配对语义补丁
|
||||
- 所有“0 表示不限”“默认排序说明”等语义统一交回后端协议
|
||||
- 结果列表显示:
|
||||
- `network_run_id`
|
||||
- `strategy`
|
||||
- `policy_version`
|
||||
- warning
|
||||
|
||||
界面调整建议:
|
||||
|
||||
- `all` 策略文案改为“全部合法候选边,按稳定顺序展示”
|
||||
- `star` 明确说明“参考像固定为主像”
|
||||
- `sequential` 明确说明“同日多景按稳定键排序”
|
||||
- AOI 覆盖阈值为空时传 `null`
|
||||
|
||||
## 10. 旧代码处理策略
|
||||
|
||||
### 10.1 可以删除的内容
|
||||
|
||||
在新缓存路径稳定后,可以删除:
|
||||
|
||||
- `spatial_service.find_dinsar_pairs()` 中当前 Python fallback
|
||||
- 旧 `_apply_star_strategy()` 非固定角色实现
|
||||
- 旧 `start_date` 兼容逻辑
|
||||
|
||||
### 10.2 需要保留过渡一段时间的内容
|
||||
|
||||
- 旧前端 `find-pairs` 调用入口
|
||||
- 旧 `task_alias / pair_key` 命名辅助
|
||||
|
||||
但注意:
|
||||
|
||||
- 命名只能作为展示与兼容字段
|
||||
- 不能继续作为算法主键
|
||||
|
||||
## 11. 实施阶段划分
|
||||
|
||||
### Phase 0:开发机切换准备
|
||||
|
||||
- 确认当前开发机允许 DB reset
|
||||
- 记录 `.env` 中路径配置
|
||||
- 保留现有 `managed_roots` / `path_inventory` / manifest publish 目录,不做文件层清理
|
||||
- 准备新 migration 与 ORM
|
||||
|
||||
验收:
|
||||
|
||||
- 可以安全启动一次 schema reset
|
||||
|
||||
### Phase 1:数据模型落地
|
||||
|
||||
- 新增 pairing 相关 ORM
|
||||
- 新增 `004_pairing_refactor.sql`
|
||||
- 接入 `ensure_database_ready()`
|
||||
- 建立 `pairing_cache_state` 初始化逻辑
|
||||
|
||||
验收:
|
||||
|
||||
- 后端启动后新表自动创建
|
||||
- 健康检查不报 schema 错
|
||||
|
||||
### Phase 2:候选缓存引擎
|
||||
|
||||
- 实现 `pairing_metric_cache` 全量构建
|
||||
- 实现 `pairing_dirty_scenes`
|
||||
- 在 `scan_radar_data()` 中接入 dirty 标记
|
||||
|
||||
验收:
|
||||
|
||||
- 能从 `radar_data` 构建稳定 pair cache
|
||||
- 同日反向重复为 0
|
||||
|
||||
### Phase 3:网络选择器
|
||||
|
||||
- 重写 `all / sequential / star / sbas`
|
||||
- 删除算法不等价 fallback
|
||||
- 为 `find-pairs` 接口切换到新内核
|
||||
|
||||
验收:
|
||||
|
||||
- 同一输入重复请求结果稳定
|
||||
- `star` 固定参考像角色
|
||||
- `sequential` 同日多景稳定
|
||||
|
||||
### Phase 4:任务物化与结果追踪
|
||||
|
||||
- `dinsar_task_items` 增加 network trace 字段
|
||||
- 结果 manifest / product profile 补充配对追踪字段
|
||||
|
||||
验收:
|
||||
|
||||
- 任一结果可追溯到 network edge
|
||||
|
||||
### Phase 5:自维护与自检
|
||||
|
||||
- 健康检查新增 `pairing_system`
|
||||
- Health 面板新增配对卡片与修复按钮
|
||||
- Statistics 新增 pairing consistency
|
||||
|
||||
验收:
|
||||
|
||||
- 能显示 dirty scene 数、缓存状态、重复边异常
|
||||
- 能从 UI 触发增量修复与全量重建
|
||||
|
||||
### Phase 6:清理旧逻辑
|
||||
|
||||
- 删除旧 fallback
|
||||
- 删除 `start_date`
|
||||
- 下线旧语义说明
|
||||
|
||||
验收:
|
||||
|
||||
- 配对链路只剩单一语义实现
|
||||
|
||||
## 12. 测试矩阵
|
||||
|
||||
本轮至少补以下自动化测试:
|
||||
|
||||
### 12.1 候选缓存层
|
||||
|
||||
- 同日双景只生成单方向边
|
||||
- 同日多景生成顺序稳定
|
||||
- 不同轨向不生成边
|
||||
- 时间基线越界不入缓存
|
||||
|
||||
### 12.2 请求过滤层
|
||||
|
||||
- `allowed_satellites` 生效
|
||||
- `cross_satellite_pairing=false` 生效
|
||||
- `require_same_imaging_mode/polarization` 生效
|
||||
- `aoi_overlap_threshold=null` 与 `0.2` 行为不同
|
||||
|
||||
### 12.3 策略层
|
||||
|
||||
- `all` 顺序稳定
|
||||
- `sequential` 连接数正确
|
||||
- `star` 参考像固定为 master
|
||||
- `sbas` 至少满足基础连通性
|
||||
|
||||
### 12.4 集成层
|
||||
|
||||
- 扫描新增源影像后 dirty scene 正确增加
|
||||
- 执行增量 reconcile 后 dirty scene 清零
|
||||
- `find-pairs` 返回 `network_run_id`
|
||||
- `materialize-batch` 后任务项带 network trace
|
||||
|
||||
### 12.5 自检层
|
||||
|
||||
- 重复边异常能被 health 检出
|
||||
- 孤儿网络边能被 health 检出
|
||||
- 任务无 network trace 能被 statistics 检出
|
||||
|
||||
## 13. 风险与取舍
|
||||
|
||||
### 13.1 本轮明确接受的取舍
|
||||
|
||||
- 开发机允许整库 reset
|
||||
- 不迁移旧配对记录
|
||||
- 不保留旧 fallback
|
||||
|
||||
### 13.2 需要重点控制的风险
|
||||
|
||||
- 首轮全量构建 pair cache 可能较慢
|
||||
- `radar_data.unique_id` 可能不稳定,需要一次性规范
|
||||
- 旧结果如果缺少 trace 字段,需要通过 manifest/profile 尽可能补齐
|
||||
- 若前端仍传 `0`,后端必须做强制归一化,不能再依赖 UI 自觉
|
||||
|
||||
## 14. 推荐实施顺序
|
||||
|
||||
建议严格按下面顺序推进:
|
||||
|
||||
1. 先落表和缓存状态,不先改前端
|
||||
2. 再实现候选缓存和增量脏标记
|
||||
3. 再重写四种策略
|
||||
4. 再接任务物化追踪
|
||||
5. 最后接健康检查与自修复入口
|
||||
|
||||
不要反过来做:
|
||||
|
||||
- 不要先改 UI 文案
|
||||
- 不要先修补 `find_dinsar_pairs_v2`
|
||||
- 不要保留旧 fallback 到最后
|
||||
|
||||
## 15. 本轮结论
|
||||
|
||||
在当前代码基线上,最合理的重构路线不是继续维护“实时 SQL + Python fallback”模式,而是:
|
||||
|
||||
- 用 `radar_data` 作为唯一事实层
|
||||
- 用 `pairing_metric_cache` 承载稳定候选对
|
||||
- 用 `pairing_network_runs / edges` 承载策略结果
|
||||
- 用 `pairing_cache_state / dirty_scenes` 承接自维护
|
||||
- 用现有 `HealthCheckPanel` 和 `/statistics` 承接自检
|
||||
|
||||
这样做的直接收益是:
|
||||
|
||||
- 正确性可锁定
|
||||
- 结果可追溯
|
||||
- 与现有 DB 自维护、扫描链路、自检体系自然对接
|
||||
- 后续再接 SBAS、跨卫星和多引擎生产时不会继续堆积配对债务
|
||||
@@ -0,0 +1,149 @@
|
||||
# PROJ 数据库配置指南
|
||||
|
||||
## 问题背景
|
||||
|
||||
InSAR 管理系统使用地理空间库(GDAL、pyproj)进行坐标转换和空间查询。这些库依赖 PROJ 数据库来执行坐标系转换。
|
||||
|
||||
**版本冲突问题**:
|
||||
- PostgreSQL PostGIS 扩展自带 PROJ 数据库 **v2**(旧版本)
|
||||
- GDAL/pyproj 期望 PROJ 数据库 **v3+**(新版本)
|
||||
- 版本不匹配会导致警告,甚至**坐标转换错误**
|
||||
|
||||
## 配置方式
|
||||
|
||||
### 方式 1:自动配置(推荐)
|
||||
|
||||
**不需要任何配置**,系统会自动使用 pyproj 的 PROJ 数据库。
|
||||
|
||||
启动后端时,会看到:
|
||||
```
|
||||
[*] Configuring PROJ database...
|
||||
[*] Auto-detected PROJ_LIB: C:\Users\Administrator\.conda\envs\InSAR\lib\site-packages\pyproj\proj_dir\share\proj
|
||||
[*] PROJ version: 9.2.0
|
||||
```
|
||||
|
||||
### 方式 2:手动配置(高级)
|
||||
|
||||
如果需要使用自定义的 PROJ 数据库,可以在 `.env` 文件中配置:
|
||||
|
||||
```bash
|
||||
# PROJ 数据库路径
|
||||
PROJ_LIB=C:\path\to\your\proj\data
|
||||
```
|
||||
|
||||
**配置优先级**:
|
||||
1. `.env` 中的 `PROJ_LIB`(如果设置)
|
||||
2. pyproj 自动检测(默认)
|
||||
|
||||
## 验证配置
|
||||
|
||||
### 方法 1:查看启动日志
|
||||
|
||||
启动后端时,检查日志输出:
|
||||
|
||||
```bash
|
||||
# 正确配置(使用 pyproj)
|
||||
[*] Auto-detected PROJ_LIB: ...pyproj\proj_dir\share\proj
|
||||
[*] PROJ version: 9.2.0
|
||||
|
||||
# 错误配置(使用 PostgreSQL)
|
||||
[*] Using PROJ_LIB from .env: C:\Program Files\PostgreSQL\17\share\contrib\postgis-3.6\proj
|
||||
```
|
||||
|
||||
### 方法 2:运行诊断脚本
|
||||
|
||||
```bash
|
||||
# 激活 InSAR 环境
|
||||
conda activate InSAR
|
||||
|
||||
# 运行诊断
|
||||
python scripts/check_proj.py
|
||||
```
|
||||
|
||||
诊断脚本会检查:
|
||||
- 环境变量 `PROJ_LIB` 的值
|
||||
- PROJ 数据库文件是否存在
|
||||
- PROJ 版本信息
|
||||
- 坐标转换功能是否正常
|
||||
|
||||
### 方法 3:测试坐标转换
|
||||
|
||||
```python
|
||||
import pyproj
|
||||
|
||||
# WGS84 to Web Mercator
|
||||
transformer = pyproj.Transformer.from_crs("EPSG:4326", "EPSG:3857", always_xy=True)
|
||||
x, y = transformer.transform(116.4, 39.9) # 北京坐标
|
||||
print(f"WGS84 (116.4, 39.9) -> Web Mercator ({x:.2f}, {y:.2f})")
|
||||
# 预期输出: WGS84 (116.4, 39.9) -> Web Mercator (12957588.73, 4851421.18)
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Q1: 为什么不能使用 PostgreSQL 的 PROJ 数据库?
|
||||
|
||||
**A**: PostgreSQL PostGIS 自带的 PROJ 数据库是 v2 版本,而 GDAL/pyproj 期望 v3+ 版本。使用旧版本可能导致:
|
||||
- 坐标转换精度下降
|
||||
- 某些坐标系不支持
|
||||
- 空间查询结果错误
|
||||
|
||||
### Q2: 如何知道当前使用的是哪个 PROJ 数据库?
|
||||
|
||||
**A**: 查看后端启动日志,或运行 `python scripts/check_proj.py`。
|
||||
|
||||
### Q3: 删除数据库重建后,PROJ 警告又出现了怎么办?
|
||||
|
||||
**A**: 这是正常的。删除数据库后,PostGIS 扩展会重新安装,系统环境变量可能被重置。重启后端即可自动修复。
|
||||
|
||||
### Q4: 我想使用自定义的 PROJ 数据库,如何配置?
|
||||
|
||||
**A**: 在 `.env` 文件中设置 `PROJ_LIB` 环境变量:
|
||||
|
||||
```bash
|
||||
PROJ_LIB=D:\custom\proj\data
|
||||
```
|
||||
|
||||
确保该目录包含 `proj.db` 文件。
|
||||
|
||||
### Q5: 如何确认 PROJ 配置生效?
|
||||
|
||||
**A**: 执行雷达数据扫描操作,检查日志中是否还有 PROJ 警告:
|
||||
|
||||
```
|
||||
GDAL signalled an error: err_no=1, msg='PROJ: proj_identify: ... DATABASE.LAYOUT.VERSION.MINOR = 2 ...'
|
||||
```
|
||||
|
||||
如果没有此警告,说明配置生效。
|
||||
|
||||
## 影响范围
|
||||
|
||||
正确配置 PROJ 数据库对以下功能至关重要:
|
||||
|
||||
- ✅ **雷达数据扫描**:解析影像坐标系
|
||||
- ✅ **空间查询**:隐患点与影像范围匹配
|
||||
- ✅ **D-InSAR 处理**:地理配准和坐标转换
|
||||
- ✅ **水体监测**:地理编码
|
||||
- ✅ **地图显示**:坐标系转换(WGS84 ↔ Web Mercator)
|
||||
|
||||
## 技术细节
|
||||
|
||||
### PROJ 数据库版本对比
|
||||
|
||||
| 来源 | 版本 | 路径 | 推荐 |
|
||||
|------|------|------|------|
|
||||
| PostgreSQL PostGIS | v2 | `C:\Program Files\PostgreSQL\17\share\contrib\postgis-3.6\proj` | ❌ |
|
||||
| pyproj (conda) | v9.2 | `C:\Users\...\envs\InSAR\lib\site-packages\pyproj\proj_dir\share\proj` | ✅ |
|
||||
|
||||
### 环境变量优先级
|
||||
|
||||
1. 程序内设置的 `os.environ["PROJ_LIB"]`(最高优先级)
|
||||
2. 系统环境变量 `PROJ_LIB`
|
||||
3. pyproj 默认路径
|
||||
|
||||
我们的配置策略是在程序启动时**强制覆盖**环境变量,确保使用正确的 PROJ 数据库。
|
||||
|
||||
## 参考资料
|
||||
|
||||
- [PROJ 官方文档](https://proj.org/)
|
||||
- [pyproj 文档](https://pyproj4.github.io/pyproj/)
|
||||
- [PostGIS 文档](https://postgis.net/)
|
||||
@@ -0,0 +1,232 @@
|
||||
# SBAS 前端设计说明
|
||||
|
||||
更新日期:2026-04-06
|
||||
|
||||
## 1. 设计目标
|
||||
|
||||
SBAS 前端必须解决四个问题:
|
||||
|
||||
- 让用户知道“匹配结果”和“正式生产运行”不是一回事
|
||||
- 让用户能从已有 PS 批次发起正式运行
|
||||
- 让用户能查看 `psinsar` 成果,而不是只看实验目录
|
||||
- 让用户能逐步过渡到更复杂的时序成果浏览,而不是一次性堆满功能
|
||||
|
||||
## 2. 导航结构
|
||||
|
||||
当前建议保留现有导航层级,只把占位页替换成真实页面。
|
||||
|
||||
### 2.1 生产规划组
|
||||
|
||||
- `ps_results`
|
||||
- 匹配结果浏览与批次创建
|
||||
- `ps_production`
|
||||
- 正式生产入口
|
||||
- `ps_products`
|
||||
- 成果编目与产品管理
|
||||
|
||||
### 2.2 InSAR 变形分析组
|
||||
|
||||
- `psinsar_results`
|
||||
- 以成果浏览为主的只读结果页
|
||||
- `psinsar_analysis`
|
||||
- 后续时序分析页,当前可继续保留占位
|
||||
|
||||
## 3. 页面一:`ps_production`
|
||||
|
||||
## 3.1 页面目标
|
||||
|
||||
从已有 `ps_task_batches` 中选择一个 stack,提交一次正式 SBAS 运行,并查看最近运行状态。
|
||||
|
||||
## 3.2 页面布局
|
||||
|
||||
建议分成三块:
|
||||
|
||||
- 运行说明卡
|
||||
- 新建运行表单
|
||||
- 最近运行列表
|
||||
|
||||
## 3.3 新建运行表单字段
|
||||
|
||||
必填字段:
|
||||
|
||||
- `batch_id`
|
||||
|
||||
可选字段:
|
||||
|
||||
- `run_name`
|
||||
- `reference_date`
|
||||
- `water_mask_mode`
|
||||
- `notes`
|
||||
|
||||
只读展示字段:
|
||||
|
||||
- `direction`
|
||||
- `stack_size`
|
||||
- `with_orbit_count`
|
||||
- `missing_orbit_count`
|
||||
|
||||
## 3.4 提交后反馈
|
||||
|
||||
提交后需要立刻给出:
|
||||
|
||||
- `task_id`
|
||||
- `run_id`
|
||||
- 当前状态
|
||||
|
||||
当前 Phase 1 仅执行 prepare,因此状态建议为:
|
||||
|
||||
- `PENDING`
|
||||
- `RUNNING`
|
||||
- `PREPARED`
|
||||
- `FAILED`
|
||||
|
||||
## 3.5 最近运行列表
|
||||
|
||||
每个运行卡片至少展示:
|
||||
|
||||
- `run_name`
|
||||
- `run_id`
|
||||
- `batch_id`
|
||||
- `reference_date`
|
||||
- `stack_size`
|
||||
- `status`
|
||||
- `created_at`
|
||||
|
||||
详情展开建议展示:
|
||||
|
||||
- DEM 路径
|
||||
- 轨道池路径
|
||||
- 生成的 stack manifest 路径
|
||||
- 输入日期列表
|
||||
- 质量/预检查摘要
|
||||
|
||||
## 4. 页面二:`ps_products`
|
||||
|
||||
## 4.1 页面目标
|
||||
|
||||
承接管理员视角的产物目录维护。
|
||||
|
||||
能力包括:
|
||||
|
||||
- 查看 `psinsar` catalog 状态
|
||||
- 手动重建 catalog
|
||||
- 浏览产品列表
|
||||
- 查看产品详情
|
||||
|
||||
## 4.2 页面布局
|
||||
|
||||
建议分成三块:
|
||||
|
||||
- catalog 状态卡
|
||||
- 管理操作区
|
||||
- 产品列表与详情区
|
||||
|
||||
## 4.3 catalog 状态卡
|
||||
|
||||
展示字段:
|
||||
|
||||
- `catalog_name`
|
||||
- `storage_root`
|
||||
- `status`
|
||||
- `needs_rebuild`
|
||||
- `manifest_count`
|
||||
- `db_count`
|
||||
- `issue_count`
|
||||
- `last_full_rebuild_at`
|
||||
|
||||
## 4.4 产品列表
|
||||
|
||||
每个产品卡片建议展示:
|
||||
|
||||
- `display_name`
|
||||
- `product_id`
|
||||
- `run_key`
|
||||
- `reference_date`
|
||||
- `stack_size`
|
||||
- `status`
|
||||
- `published_at`
|
||||
|
||||
优先展示缩略图:
|
||||
|
||||
- `preview/velocity_preview.png`
|
||||
|
||||
## 4.5 产品详情
|
||||
|
||||
详情建议包含:
|
||||
|
||||
- 基本信息
|
||||
- 时序信息
|
||||
- 质量摘要
|
||||
- 资产列表
|
||||
- 问题列表
|
||||
|
||||
资产列表首屏优先顺序:
|
||||
|
||||
- `velocity_geotiff`
|
||||
- `timeseries_cube`
|
||||
- `temporal_coherence_geotiff`
|
||||
- `quality_mask_geotiff`
|
||||
- `preview_png`
|
||||
- 其他辅助资产
|
||||
|
||||
## 5. 页面三:`psinsar_results`
|
||||
|
||||
## 5.1 页面目标
|
||||
|
||||
承接业务用户视角的成果浏览,不承担目录维护操作。
|
||||
|
||||
## 5.2 与 `ps_products` 的区别
|
||||
|
||||
- `ps_products` 偏运维和管理
|
||||
- `psinsar_results` 偏展示和检索
|
||||
|
||||
## 5.3 当前 Phase 1 能力
|
||||
|
||||
- 浏览产品列表
|
||||
- 查看缩略图
|
||||
- 查看资产清单
|
||||
- 查看质量摘要
|
||||
|
||||
## 5.4 后续 Phase 2/3 扩展
|
||||
|
||||
- 地图叠加 `velocity.tif`
|
||||
- 按 AOI 或时间筛选
|
||||
- 像元点位时序查询
|
||||
- 专题统计与热点识别
|
||||
|
||||
## 6. 交互原则
|
||||
|
||||
- 生产和结果页面必须分开
|
||||
- 管理操作只出现在管理页
|
||||
- 结果页优先展示 `velocity.tif` 对应预览
|
||||
- 但详情页必须强调主科学产物是 `geo_timeseries.h5`
|
||||
|
||||
## 7. 前端阶段性实施建议
|
||||
|
||||
### Phase 1
|
||||
|
||||
- `ps_production`:真实可提交 prepare 任务
|
||||
- `ps_products`:真实 catalog 管理页
|
||||
- `psinsar_results`:复用 catalog 浏览能力做只读页
|
||||
|
||||
### Phase 2
|
||||
|
||||
- 地图叠加 `velocity.tif`
|
||||
- 结果筛选
|
||||
- 与 run 详情联动
|
||||
|
||||
### Phase 3
|
||||
|
||||
- `geo_timeseries.h5` 点查询
|
||||
- 时间曲线图
|
||||
- 多产品对比
|
||||
|
||||
## 8. 当前结论
|
||||
|
||||
前端第一阶段不应追求“一口气把时序分析全做完”,而应优先做三件事:
|
||||
|
||||
- 让 SBAS 生产有正式入口
|
||||
- 让 SBAS 结果有正式目录
|
||||
- 让用户在系统里能看到真正的 `psinsar` 成果
|
||||
|
||||
这三件事完成后,系统才算真正开始承接 SBAS 能力。
|
||||
@@ -0,0 +1,102 @@
|
||||
# SBAS 系统实施 TODO
|
||||
|
||||
更新日期:2026-04-06
|
||||
|
||||
## Phase 1:低侵入落地
|
||||
|
||||
目标:
|
||||
|
||||
- 不影响 D-InSAR
|
||||
- 让系统开始承接 SBAS 生产记录与结果目录
|
||||
|
||||
### 后端模型
|
||||
|
||||
- [x] 新增 `PsTimeseriesRunORM`
|
||||
- [x] 新增 `PsTimeseriesRun` schema
|
||||
- [x] 为 `settings` 增加 `TIMESERIES_*` / `PSINSAR_PRODUCT_DIR` 配置
|
||||
|
||||
### 后端服务
|
||||
|
||||
- [x] 新增 `timeseries_service.py`
|
||||
- [x] 新增 `psinsar_catalog_service.py`
|
||||
- [x] 增加 SBAS prepare 任务处理逻辑
|
||||
|
||||
### 后端路由
|
||||
|
||||
- [x] 新增 `timeseries_production.py`
|
||||
- [x] 新增 `ps_products.py`
|
||||
- [x] 注册新路由
|
||||
|
||||
### 前端
|
||||
|
||||
- [x] 新增 `TimeseriesProductionPanel.jsx`
|
||||
- [x] 新增 `PsinsarCatalogPanel.jsx`
|
||||
- [x] 新增 `timeseriesProduction.js`
|
||||
- [x] 新增 `psinsarProducts.js`
|
||||
- [x] 替换 `ps_production` 占位页
|
||||
- [x] 替换 `ps_products` 占位页
|
||||
- [x] 替换 `psinsar_results` 占位页
|
||||
|
||||
### 校验
|
||||
|
||||
- [x] 后端语法检查
|
||||
- [x] 前端 build 检查
|
||||
- [ ] 验证 D-InSAR 页面未受影响
|
||||
|
||||
## Phase 2:串接实验处理链
|
||||
|
||||
目标:
|
||||
|
||||
- 在系统内真正触发 SBAS 处理
|
||||
|
||||
### 处理链
|
||||
|
||||
- [x] `prepare` 生成标准 stack manifest
|
||||
- [x] `materialize` 生成 stack 工作目录
|
||||
- [ ] 接入 ISCE2 stack 执行
|
||||
- [ ] 接入 MintPy SBAS 执行
|
||||
- [ ] 接入 geocode/export/publish
|
||||
|
||||
### 任务编排
|
||||
|
||||
- [x] 新增 workflow steps
|
||||
- [ ] 新增失败重试策略
|
||||
- [x] 增加运行日志归集
|
||||
|
||||
## Phase 3:结果展示增强
|
||||
|
||||
目标:
|
||||
|
||||
- 从“结果存在”升级到“结果可分析”
|
||||
|
||||
### 展示
|
||||
|
||||
- [ ] 地图叠加 `velocity.tif`
|
||||
- [ ] 按 AOI/时间筛选产品
|
||||
- [ ] 结果与 run 联动查看
|
||||
|
||||
### 分析
|
||||
|
||||
- [ ] `geo_timeseries.h5` 点位查询
|
||||
- [ ] 时间曲线图
|
||||
- [ ] 质量掩膜联动
|
||||
- [ ] 热点区域分析
|
||||
|
||||
## 当前阶段实施顺序
|
||||
|
||||
推荐顺序:
|
||||
|
||||
1. 文档补齐
|
||||
2. 运行记录模型
|
||||
3. prepare 任务
|
||||
4. `psinsar` catalog
|
||||
5. 前端生产页
|
||||
6. 前端结果页
|
||||
7. 静态校验
|
||||
|
||||
## 当前不做
|
||||
|
||||
- [ ] 不重构现有 D-InSAR 结果服务为完全通用框架
|
||||
- [ ] 不改现有配对算法
|
||||
- [ ] 不继续保留 SBAS 使用 `*_envi_import` 的旧逻辑
|
||||
- [ ] 不在当前 D-InSAR 生产环境中直接做依赖升级
|
||||
@@ -0,0 +1,124 @@
|
||||
# SBAS 系统改造当前状态
|
||||
|
||||
更新日期:2026-04-06
|
||||
|
||||
## 1. 当前结论
|
||||
|
||||
截至本轮,SBAS / PS-InSAR 已经完成第一阶段的“低侵入接入”改造:
|
||||
|
||||
- 已能在系统内创建正式 `ps_timeseries_runs`
|
||||
- 已能把现有 PS 匹配结果转换为 `stack_input_manifest.json`
|
||||
- 已能把实验产物 `psinsar.publish.v1` bundle 注册到系统结果目录
|
||||
- 已能在前端查看 SBAS 运行记录与 `psinsar` 产品目录
|
||||
- 已补齐 `psinsar` catalog 的启动期 bootstrap 与健康检查接入
|
||||
|
||||
这意味着系统已经不再只是“影像管理口”,而是具备了 SBAS 正式生产入口的骨架。当前已经串起实验级四步链:
|
||||
|
||||
- `prepare`
|
||||
- `stack_prep_initial`
|
||||
- `materialize`
|
||||
- `stack_prep_refresh`
|
||||
|
||||
但仍未接入真正的 ISCE2 stack / MintPy / geocode / publish 自动执行链。
|
||||
|
||||
## 2. 已完成范围
|
||||
|
||||
### 2.1 后端
|
||||
|
||||
- 新增 `ps_timeseries_runs` 运行记录模型
|
||||
- 新增 `timeseries_service.py`
|
||||
- 新增 `psinsar_catalog_service.py`
|
||||
- 新增 `timeseries_production.py`
|
||||
- 新增 `ps_products.py`
|
||||
- 作业系统已支持:
|
||||
- `TIMESERIES_PREPARE`
|
||||
- `TIMESERIES_STACK_PREP`
|
||||
- `TIMESERIES_MATERIALIZE`
|
||||
- `timeseries_service.py` 已补齐 workflow 驱动的:
|
||||
- `create_run`
|
||||
- `prepare_run`
|
||||
- `build_stack_prep`
|
||||
- `materialize_run`
|
||||
- `list_runs`
|
||||
- `get_run_detail`
|
||||
- 运行详情已可返回 `workflow.steps`
|
||||
- 启动阶段已同时检查 `dinsar` 与 `psinsar` 两套 catalog
|
||||
- `/health` 已保留原有 `result_catalog` 兼容字段,并新增:
|
||||
- `dinsar_result_catalog`
|
||||
- `psinsar_result_catalog`
|
||||
- `catalogs`
|
||||
|
||||
### 2.2 前端
|
||||
|
||||
- 已接入 `TimeseriesProductionPanel`
|
||||
- 已接入 `PsinsarCatalogPanel`
|
||||
- 已替换 `ps_production`、`ps_products`、`psinsar_results` 占位页
|
||||
- `TimeseriesProductionPanel` 已补充 Phase 2 状态色和 workflow 步骤展示
|
||||
|
||||
### 2.3 文档
|
||||
|
||||
已形成以下系统设计与风险文档:
|
||||
|
||||
- `SBAS_SYSTEM_INTEGRATION_AUDIT_20260406.md`
|
||||
- `SBAS_SYSTEM_EMBEDDING_DESIGN_20260406.md`
|
||||
- `SBAS_TASK_PATH_MODE_EVALUATION_20260406.md`
|
||||
- `SBAS_SYSTEM_CHANGE_RISK_ASSESSMENT_20260406.md`
|
||||
- `SBAS_RESULT_MANAGEMENT_AND_DISPLAY_SPEC_20260406.md`
|
||||
- `SBAS_FRONTEND_UX_SPEC_20260406.md`
|
||||
- `SBAS_IMPLEMENTATION_TODO_20260406.md`
|
||||
|
||||
## 3. 本轮验证结果
|
||||
|
||||
### 3.1 已通过
|
||||
|
||||
- 前端 `npm run build` 已通过
|
||||
- WSL `Ubuntu-24.04` 下,`isce2` 环境对本轮修改后的后端文件执行 `py_compile` 通过
|
||||
- 启动日志已补充 `PSInSAR-Catalog` 状态输出
|
||||
|
||||
### 3.2 当前验证边界
|
||||
|
||||
本轮在 WSL `isce2` 环境做后端“导入级冒烟测试”时失败,失败原因不是本轮代码语法问题,而是该环境缺少系统后端依赖:
|
||||
|
||||
- `ModuleNotFoundError: No module named 'fastapi'`
|
||||
|
||||
这说明当前 `isce2` 环境可以作为 SBAS 处理链环境使用,也可以做后端源码级语法校验,但还不能直接作为系统后端完整运行环境。
|
||||
|
||||
## 4. 当前缺口
|
||||
|
||||
### 4.1 处理链缺口
|
||||
|
||||
系统尚未自动执行以下步骤:
|
||||
|
||||
- ISCE2 stack 执行
|
||||
- MintPy SBAS 执行
|
||||
- geocode / export / publish
|
||||
- 产物自动注册收口
|
||||
|
||||
### 4.2 环境缺口
|
||||
|
||||
若目标是“统一环境”,则还需要复制一份稳定的 ISCE2 环境并补齐系统依赖,形成一个既能跑:
|
||||
|
||||
- ISCE2 / MintPy
|
||||
- 又能支撑 FastAPI 后端依赖
|
||||
|
||||
的独立统一环境。当前不建议直接污染现有 D-InSAR 正在使用的生产环境。
|
||||
|
||||
## 5. 下一步建议
|
||||
|
||||
建议按以下顺序继续:
|
||||
|
||||
1. 保持当前 Phase 2 bridge 收口,不继续扩散系统侧改动,先把 `prepare -> stack_prep -> materialize -> refresh` 稳定住。
|
||||
2. 在实验层继续推进 `isce2_stack -> mintpy -> export_publish` 分步脚本与统一环境验证。
|
||||
3. 单独复制一份 WSL `isce2` 环境,做“统一环境”安装实验,不动现有 D-InSAR 生产环境。
|
||||
4. 统一环境验证稳定后,再把真正的 stack / MintPy / publish 接回系统作业编排。
|
||||
|
||||
## 6. 当前判断
|
||||
|
||||
当前路线是成立的。
|
||||
|
||||
成立点不在于“已经跑通全系统自动 SBAS”,而在于:
|
||||
|
||||
- 系统边界已经切对
|
||||
- D-InSAR 老链路基本保持隔离
|
||||
- SBAS 已有正式运行记录、结果目录和前端入口
|
||||
- 后续只需要继续往中间补处理链,而不是推倒重来
|
||||
@@ -0,0 +1,236 @@
|
||||
# SBAS 结果管理与展示规范
|
||||
|
||||
更新日期:2026-04-06
|
||||
|
||||
## 1. 管理目标
|
||||
|
||||
SBAS 结果进入系统后,必须满足三个层面的要求:
|
||||
|
||||
- 可登记
|
||||
- 可追溯
|
||||
- 可展示
|
||||
|
||||
当前推荐的管理边界不是 MintPy 运行目录,而是发布级 bundle。
|
||||
|
||||
唯一注册入口:
|
||||
|
||||
- `publish/.../manifest.json`
|
||||
|
||||
## 2. 结果层级
|
||||
|
||||
### 2.1 运行层
|
||||
|
||||
运行层记录在:
|
||||
|
||||
- `ps_timeseries_runs`
|
||||
|
||||
职责:
|
||||
|
||||
- 记录一次正式处理运行的业务身份
|
||||
- 记录输入、参数、DEM、轨道、掩膜、目录与状态
|
||||
|
||||
### 2.2 产品层
|
||||
|
||||
产品层继续复用:
|
||||
|
||||
- `result_products`
|
||||
- `result_assets`
|
||||
- `result_issues`
|
||||
|
||||
约定:
|
||||
|
||||
- `catalog_name = psinsar`
|
||||
- `product_type = psinsar_bundle`
|
||||
- `run_key = ps_timeseries_runs.run_id`
|
||||
|
||||
### 2.3 资产层
|
||||
|
||||
资产层由 `result_assets` 管理。
|
||||
|
||||
建议角色:
|
||||
|
||||
- `timeseries_cube`
|
||||
- `velocity_map`
|
||||
- `velocity_geotiff`
|
||||
- `temporal_coherence`
|
||||
- `temporal_coherence_geotiff`
|
||||
- `quality_mask`
|
||||
- `quality_mask_geotiff`
|
||||
- `preview_png`
|
||||
- `diagnostic_png`
|
||||
- `config`
|
||||
- `quality_summary`
|
||||
|
||||
## 3. 发布级 bundle 约束
|
||||
|
||||
标准目录:
|
||||
|
||||
```text
|
||||
<PSINSAR_PRODUCT_DIR>/<year>/<run_id>/
|
||||
manifest.json
|
||||
assets/
|
||||
preview/
|
||||
metadata/
|
||||
```
|
||||
|
||||
最低要求:
|
||||
|
||||
- `manifest.json`
|
||||
- `assets/geo_timeseries.h5`
|
||||
- `assets/velocity.tif`
|
||||
- `preview/velocity_preview.png`
|
||||
|
||||
建议同时保留:
|
||||
|
||||
- `assets/geo_velocity.h5`
|
||||
- `assets/temporalCoherence.tif`
|
||||
- `assets/maskTempCoh.tif`
|
||||
- `preview/numTriNonzeroIntAmbiguity.png`
|
||||
- `metadata/source_quality_summary.json`
|
||||
|
||||
## 4. 产品主文件定义
|
||||
|
||||
### 4.1 科学主产物
|
||||
|
||||
- `assets/geo_timeseries.h5`
|
||||
|
||||
这是系统证明“具备时序处理能力”的核心文件。
|
||||
|
||||
### 4.2 展示主产物
|
||||
|
||||
- `assets/velocity.tif`
|
||||
|
||||
这是结果首屏展示、地图浏览、预览生成的主文件。
|
||||
|
||||
### 4.3 质量主产物
|
||||
|
||||
- `assets/temporalCoherence.tif`
|
||||
- `assets/maskTempCoh.tif`
|
||||
|
||||
## 5. manifest 解析要求
|
||||
|
||||
系统应支持:
|
||||
|
||||
- `schema_version = psinsar.publish.v1`
|
||||
|
||||
系统应从 manifest 中提取:
|
||||
|
||||
- 基础身份
|
||||
- `catalog_name`
|
||||
- `mode`
|
||||
- `engine_code`
|
||||
- `processor_code`
|
||||
- 时序信息
|
||||
- `reference_date`
|
||||
- `stack_dates`
|
||||
- 资产信息
|
||||
- `artifacts[]`
|
||||
- 质量信息
|
||||
- `quality`
|
||||
- 辅助摘要
|
||||
- `summaries`
|
||||
|
||||
如 manifest 缺少显式 bbox,可从 `summaries.*.attrs` 中推导:
|
||||
|
||||
- `X_FIRST`
|
||||
- `Y_FIRST`
|
||||
- `X_STEP`
|
||||
- `Y_STEP`
|
||||
- `WIDTH`
|
||||
- `LENGTH`
|
||||
|
||||
## 6. 展示规范
|
||||
|
||||
## 6.1 列表页
|
||||
|
||||
优先展示:
|
||||
|
||||
- 产品名称
|
||||
- 运行标识
|
||||
- 参考日期
|
||||
- 影像数量
|
||||
- 发布时间
|
||||
- 缩略图
|
||||
|
||||
## 6.2 详情页
|
||||
|
||||
详情页应至少展示:
|
||||
|
||||
- 运行信息
|
||||
- 时间序列日期列表
|
||||
- 质量摘要
|
||||
- 资产清单
|
||||
- 问题清单
|
||||
|
||||
## 6.3 地图展示优先级
|
||||
|
||||
当前优先级:
|
||||
|
||||
1. `velocity.tif`
|
||||
2. `temporalCoherence.tif`
|
||||
3. `maskTempCoh.tif`
|
||||
|
||||
不建议当前阶段直接做:
|
||||
|
||||
- `geo_timeseries.h5` 全量地图服务化
|
||||
|
||||
## 7. 结果与运行的关联
|
||||
|
||||
必须能从结果反查运行。
|
||||
|
||||
推荐方式:
|
||||
|
||||
- `result_products.run_key = ps_timeseries_runs.run_id`
|
||||
|
||||
这样可以回答:
|
||||
|
||||
- 这个结果来自哪个 batch
|
||||
- 用了哪些日期
|
||||
- DEM 和轨道来自哪里
|
||||
- 使用了什么水体掩膜策略
|
||||
|
||||
## 8. 目录与数据库职责边界
|
||||
|
||||
目录是事实源:
|
||||
|
||||
- `manifest.json`
|
||||
- `assets/*`
|
||||
- `preview/*`
|
||||
|
||||
数据库是索引层:
|
||||
|
||||
- 产品列表
|
||||
- 检索字段
|
||||
- 资产索引
|
||||
- 问题索引
|
||||
|
||||
因此不应让数据库替代目录事实源,也不应让前端直接依赖实验运行目录。
|
||||
|
||||
## 9. Phase 1 / Phase 2 边界
|
||||
|
||||
### Phase 1
|
||||
|
||||
- 支持 bundle 扫描与登记
|
||||
- 支持产品列表和详情
|
||||
- 支持缩略图展示
|
||||
|
||||
### Phase 2
|
||||
|
||||
- 支持地图叠加 velocity
|
||||
- 支持 run 与 product 联动
|
||||
- 支持更多质量视图
|
||||
|
||||
### Phase 3
|
||||
|
||||
- 支持 `geo_timeseries.h5` 点位时序查询
|
||||
- 支持图表和专题分析
|
||||
|
||||
## 10. 当前结论
|
||||
|
||||
SBAS 结果管理的核心不是“把很多文件存起来”,而是把下面三件事稳定下来:
|
||||
|
||||
- 用 `manifest.json` 定义结果边界
|
||||
- 用 `result_products/result_assets` 登记 bundle
|
||||
- 用 `run_key` 把结果和运行记录连起来
|
||||
|
||||
这三件事一旦成立,系统就具备了承接 SBAS 成果的基础能力。
|
||||
@@ -0,0 +1,219 @@
|
||||
# SBAS 系统改造风险评估
|
||||
|
||||
更新日期:2026-04-06
|
||||
|
||||
## 1. 目标
|
||||
|
||||
本次改造的目标不是把实验脚本直接搬进系统,而是在尽量不影响现有 D-InSAR 生产链的前提下,为系统增加一条独立的 SBAS/PS-InSAR 生产与结果管理能力。
|
||||
|
||||
约束条件:
|
||||
|
||||
- 现有 D-InSAR 生产逻辑必须保持可用
|
||||
- 现有匹配逻辑先保持不动
|
||||
- SBAS 生产只基于原始影像,不再沿用 `*_envi_import`
|
||||
- 结果必须最终落到系统内可管理、可展示、可追踪的发布级 bundle
|
||||
- 统一环境实验优先,但不能污染当前 D-InSAR 使用的 `isce2` 生产环境
|
||||
|
||||
## 2. 风险分级总览
|
||||
|
||||
### P0 高风险
|
||||
|
||||
- 误改现有 D-InSAR 生产入口、目录契约或作业类型,导致原有生产失败
|
||||
- 把 SBAS 直接套进现有 `master/slave` 双景输入模型,导致设计方向错误
|
||||
- 结果编目直接改写现有 `result_catalog_service.py` 的 D-InSAR 语义,造成历史产品异常
|
||||
- 在当前生产 `isce2` 环境内直接安装新依赖,破坏现有运行稳定性
|
||||
|
||||
### P1 中风险
|
||||
|
||||
- SBAS 运行记录、任务记录、结果记录之间关系不清,后期无法追溯
|
||||
- Windows/WSL 双路径继续散落在多个服务中,后续维护成本继续升高
|
||||
- 前端直接绑定运行时目录结构,导致实验目录变化后页面失效
|
||||
- 结果展示只展示 `velocity.tif`,忽略 `geo_timeseries.h5`,造成“有图无时序能力”的误判
|
||||
|
||||
### P2 低风险
|
||||
|
||||
- 字段命名不统一,后续扩展时需要补充适配
|
||||
- 初期前端只做结果浏览,不做像元级时序查询
|
||||
- 先不接入健康检查总览,短期不会阻断主流程
|
||||
|
||||
## 3. 风险清单
|
||||
|
||||
### 3.1 生产链串扰风险
|
||||
|
||||
风险:
|
||||
|
||||
- 新增 SBAS 能力时误复用或改写 D-InSAR 路由、作业类型、拷贝逻辑、结果目录逻辑
|
||||
|
||||
触发点:
|
||||
|
||||
- 修改 `backend/app/routers/dinsar_production.py`
|
||||
- 修改 `backend/app/copier.py` 中现有 D-InSAR 行为
|
||||
- 在 `result_catalog_service.py` 中直接硬改通用逻辑
|
||||
|
||||
影响:
|
||||
|
||||
- D-InSAR 生产失败
|
||||
- 历史产物目录重建异常
|
||||
|
||||
控制策略:
|
||||
|
||||
- Phase 1 新增独立入口:
|
||||
- `timeseries_production.py`
|
||||
- `ps_products.py`
|
||||
- `psinsar_catalog_service.py`
|
||||
- 不修改 D-InSAR 的路由路径和任务类型
|
||||
- 不复用 `copy-ps-stack` 作为 SBAS 生产输入准备
|
||||
|
||||
### 3.2 输入契约错误风险
|
||||
|
||||
风险:
|
||||
|
||||
- 继续沿用 D-InSAR 的 `Task_*/master + slave` 输入目录契约
|
||||
|
||||
影响:
|
||||
|
||||
- SBAS 处理逻辑被错误建模为“双景处理”
|
||||
- 后续 prepare/materialize/stack workflow 无法自然落地
|
||||
|
||||
控制策略:
|
||||
|
||||
- 明确采用 `Stack_<group_key>` / `stack_input_manifest.json` 契约
|
||||
- 生产入口接收 `batch_id`,由系统生成 stack 级 manifest
|
||||
- 匹配层继续保留,生产层单独增加 prepare
|
||||
|
||||
### 3.3 环境污染风险
|
||||
|
||||
风险:
|
||||
|
||||
- 在当前 D-InSAR 使用的 `isce2` 运行环境内直接安装 MintPy 或其他依赖
|
||||
|
||||
影响:
|
||||
|
||||
- 现有 D-InSAR 生产不可复现
|
||||
- 环境漂移,难以排查
|
||||
|
||||
控制策略:
|
||||
|
||||
- 当前系统 Phase 1 仅记录并使用 `TIMESERIES_*` 独立配置
|
||||
- 当前实验优先对接已经验证过的统一环境或桥接环境
|
||||
- 保留现有 D-InSAR `isce2` 环境不动
|
||||
|
||||
### 3.4 数据身份与结果归档风险
|
||||
|
||||
风险:
|
||||
|
||||
- 只有批次,没有正式“运行记录”
|
||||
- 只有实验目录,没有系统级产品登记
|
||||
|
||||
影响:
|
||||
|
||||
- 无法回答“某次 SBAS 运行用的 DEM、轨道、参考日期、掩膜策略是什么”
|
||||
- 无法稳定展示和管理结果
|
||||
|
||||
控制策略:
|
||||
|
||||
- 新增 `ps_timeseries_runs`
|
||||
- 结果仍复用 `result_products` / `result_assets` / `result_issues`
|
||||
- `run_key` 绑定 `ps_timeseries_runs.run_id`
|
||||
- 发布边界固定为 `manifest.json`
|
||||
|
||||
### 3.5 前端误导风险
|
||||
|
||||
风险:
|
||||
|
||||
- 仅提供一个“PS 生产占位页”,但用户无法区分:
|
||||
- 规划批次
|
||||
- 正式生产运行
|
||||
- 结果目录
|
||||
- 成果展示
|
||||
|
||||
影响:
|
||||
|
||||
- 操作路径混乱
|
||||
- 误以为已有生产能力
|
||||
|
||||
控制策略:
|
||||
|
||||
- 生产页和结果页分离
|
||||
- 生产页展示:
|
||||
- 可选批次
|
||||
- 运行参数
|
||||
- 最近运行
|
||||
- 当前准备状态
|
||||
- 结果页展示:
|
||||
- 编目状态
|
||||
- 产品列表
|
||||
- 产品详情
|
||||
- 资产列表
|
||||
- 质量摘要
|
||||
|
||||
## 4. 本次推荐改造范围
|
||||
|
||||
## 4.1 必做
|
||||
|
||||
- 新增 `ps_timeseries_runs` 业务表
|
||||
- 新增 `timeseries_production` 路由
|
||||
- 新增 SBAS prepare 任务
|
||||
- 新增 `psinsar_catalog_service`
|
||||
- 新增 `ps_products` 路由
|
||||
- 新增前端 `TimeseriesProductionPanel`
|
||||
- 新增前端 `PsinsarCatalogPanel`
|
||||
|
||||
## 4.2 暂缓
|
||||
|
||||
- 不改现有 `find-ps-timeseries` 匹配算法
|
||||
- 不改现有 D-InSAR 生产入口
|
||||
- 不把 SBAS 直接接进 `copy-ps-stack`
|
||||
- 不做像元级 `geo_timeseries.h5` 在线查询
|
||||
- 不做地图服务化切片发布
|
||||
|
||||
## 5. 回滚策略
|
||||
|
||||
回滚必须满足“删新不伤旧”。
|
||||
|
||||
可回滚对象:
|
||||
|
||||
- 新增路由
|
||||
- 新增 ORM 表
|
||||
- 新增前端面板
|
||||
- 新增 `psinsar` 编目服务
|
||||
|
||||
不应触碰回滚的对象:
|
||||
|
||||
- 现有 D-InSAR 路由
|
||||
- 现有 `system_jobs` / `system_tasks` 表结构语义
|
||||
- 现有 D-InSAR 结果目录
|
||||
|
||||
推荐回滚方式:
|
||||
|
||||
1. 先停用前端入口
|
||||
2. 再停用新路由注册
|
||||
3. 保留数据库表,不做破坏性删除
|
||||
4. 保留已发布 `psinsar` bundle,必要时仅停止 catalog rebuild
|
||||
|
||||
## 6. 验收标准
|
||||
|
||||
### 6.1 低风险验收
|
||||
|
||||
- D-InSAR 现有页面正常打开
|
||||
- D-InSAR 现有生产入口不报错
|
||||
- D-InSAR 现有结果目录查询不受影响
|
||||
|
||||
### 6.2 SBAS Phase 1 验收
|
||||
|
||||
- 可以从已有 `ps_task_batch` 创建一个正式 `ps_timeseries_run`
|
||||
- 系统能为该 run 生成 stack 级 manifest
|
||||
- 系统能记录 work/publish 目录、参考日期、轨道统计和输入快照
|
||||
- 系统可以扫描 `psinsar.publish.v1` bundle 并登记到结果目录
|
||||
- 前端能查看运行记录与产品详情
|
||||
|
||||
## 7. 当前结论
|
||||
|
||||
本次改造最重要的不是“尽快把 SBAS 跑起来”,而是先把系统边界做对:
|
||||
|
||||
- 规划层不动
|
||||
- 生产层新增
|
||||
- 结果层隔离接入
|
||||
- D-InSAR 旧链路不碰
|
||||
|
||||
按这个边界推进,风险可控,且后续可以逐步把实验能力提升为正式生产能力。
|
||||
@@ -0,0 +1,680 @@
|
||||
# SBAS InSAR 嵌入系统设计稿
|
||||
|
||||
更新日期:2026-04-06
|
||||
|
||||
## 1. 设计目标
|
||||
|
||||
目标不是把当前实验脚本“塞进系统”,而是把已经验证成功的 SBAS 路线变成系统内可提交、可跟踪、可复跑、可发布的正式能力。
|
||||
|
||||
设计必须同时满足四个约束:
|
||||
|
||||
- 不破坏现有 D-InSAR 生产
|
||||
- 尽量复用现有 `task` / `job` / `workflow` / `result catalog` 骨架
|
||||
- 以发布级 bundle 作为系统注册边界,而不是 MintPy 运行目录
|
||||
- 保留 WSL + conda 的现实部署方式,但把路径和环境耦合收敛到专门层
|
||||
|
||||
## 2. 设计原则
|
||||
|
||||
### 2.1 规划层和生产层分离
|
||||
|
||||
现有:
|
||||
|
||||
- `find-ps-timeseries`
|
||||
- `ps_task_batches`
|
||||
- `ps_task_items`
|
||||
|
||||
这些应该继续保留,但它们只负责:
|
||||
|
||||
- 选片
|
||||
- 栈定义
|
||||
- 输入快照
|
||||
|
||||
它们不应该继续承担:
|
||||
|
||||
- 生产运行主记录
|
||||
- 产物发布状态
|
||||
- 重试与失败恢复
|
||||
|
||||
### 2.2 一个 SBAS 运行对应一个发布 bundle
|
||||
|
||||
不要把 SBAS 多个文件当成多个独立产品行。
|
||||
|
||||
推荐模型:
|
||||
|
||||
- 一个 `ps_timeseries_run`
|
||||
- 对应一个 publish bundle
|
||||
- 对应 `result_products` 中一条 `psinsar` 产品记录
|
||||
- bundle 内部多个文件通过 `result_assets` 管理
|
||||
|
||||
这样最贴合当前系统的 `result_products -> result_assets` 结构。
|
||||
|
||||
### 2.3 发布边界固定为 `manifest.json`
|
||||
|
||||
系统不直接注册:
|
||||
|
||||
- `stack_work/`
|
||||
- `mintpy_work/`
|
||||
- `timeseries.h5`
|
||||
- 任意临时中间目录
|
||||
|
||||
系统只注册:
|
||||
|
||||
- `publish/.../manifest.json`
|
||||
|
||||
原因:
|
||||
|
||||
- 这是当前实验里最稳定的对外契约
|
||||
- 它天然适合作为 catalog rebuild 和产品详情入口
|
||||
- 它把运行时目录与发布目录分开了
|
||||
|
||||
### 2.4 Phase 1 优先“隔离集成”,不优先“统一重构”
|
||||
|
||||
当前 `result_catalog_service` 明显偏 D-InSAR。
|
||||
|
||||
因此 Phase 1 不建议直接大改现有 D-InSAR catalog 服务为完全泛化版本。
|
||||
|
||||
更稳妥的做法是:
|
||||
|
||||
- 新增 `psinsar_catalog_service.py`
|
||||
- 接口风格与 `result_catalog_service.py` 保持一致
|
||||
- 底层继续复用 `result_products` / `result_assets` / `result_issues`
|
||||
|
||||
等 SBAS 生产链跑稳后,再提炼公共基类。
|
||||
|
||||
## 3. 目标架构
|
||||
|
||||
推荐分成五层。
|
||||
|
||||
### 3.1 规划层
|
||||
|
||||
复用现有:
|
||||
|
||||
- `POST /find-ps-timeseries`
|
||||
- `POST /task-batches/ps`
|
||||
|
||||
职责:
|
||||
|
||||
- 根据 AOI 和筛选条件找到时间序列候选栈
|
||||
- 生成一个可复用的 `ps_task_batch`
|
||||
|
||||
### 3.2 生产控制层
|
||||
|
||||
新增:
|
||||
|
||||
- `ps_timeseries_runs`
|
||||
- `timeseries_production.py`
|
||||
- `timeseries_service.py`
|
||||
|
||||
职责:
|
||||
|
||||
- 从一个 `ps_task_batch` 派生一次正式运行
|
||||
- 固化参数、环境、输入路径、输出路径、DEM、轨道、水体掩膜策略
|
||||
- 对接 `system_tasks` / `system_jobs` / `workflow_runs`
|
||||
|
||||
### 3.3 执行编排层
|
||||
|
||||
复用现有:
|
||||
|
||||
- `system_tasks`
|
||||
- `system_jobs`
|
||||
- `workflow_runs`
|
||||
- `workflow_steps`
|
||||
- `workflow_artifacts`
|
||||
|
||||
新增:
|
||||
|
||||
- `timeseries_workflow_factory.py`
|
||||
- `mintpy_service.py`
|
||||
- `isce2_stack_service.py`
|
||||
|
||||
职责:
|
||||
|
||||
- 把一次 SBAS 运行拆成多个受控步骤
|
||||
- 每一步进入统一 worker
|
||||
- 失败可定位到 step,而不是只看到“整条链失败”
|
||||
|
||||
### 3.4 发布与编目层
|
||||
|
||||
新增:
|
||||
|
||||
- `psinsar_catalog_service.py`
|
||||
- `ps_products.py`
|
||||
|
||||
复用:
|
||||
|
||||
- `result_products`
|
||||
- `result_assets`
|
||||
- `result_issues`
|
||||
- `result_catalog_states`
|
||||
|
||||
职责:
|
||||
|
||||
- 扫描和注册 `psinsar.publish.v1`
|
||||
- 管理产品详情、预览、健康检查、重建
|
||||
|
||||
### 3.5 展示层
|
||||
|
||||
新增页面:
|
||||
|
||||
- `ps_production`
|
||||
- `ps_products`
|
||||
- `psinsar_results`
|
||||
|
||||
职责:
|
||||
|
||||
- 运行提交与监控
|
||||
- 产品浏览与筛选
|
||||
- 结果详情、预览和下载
|
||||
|
||||
## 4. 核心数据模型
|
||||
|
||||
## 4.1 新增业务表:`ps_timeseries_runs`
|
||||
|
||||
建议字段:
|
||||
|
||||
- `run_id`
|
||||
- `batch_id`
|
||||
- `run_name`
|
||||
- `catalog_name`
|
||||
- `mode`
|
||||
- `sbas`
|
||||
- `engine_code`
|
||||
- `isce2`
|
||||
- `processor_code`
|
||||
- `isce2_stack_mintpy`
|
||||
- `env_name`
|
||||
- 例如 `isce2_mintpy_v1`
|
||||
- `wsl_distro`
|
||||
- 例如 `Ubuntu-24.04`
|
||||
- `status`
|
||||
- `PENDING` / `RUNNING` / `FAILED` / `COMPLETED` / `PUBLISHED`
|
||||
- `workflow_run_id`
|
||||
- `task_id`
|
||||
- `direction`
|
||||
- `stack_size`
|
||||
- `reference_date`
|
||||
- `water_mask_mode`
|
||||
- `local` / `synthetic_fallback`
|
||||
- `dem_path_windows`
|
||||
- `dem_path_wsl`
|
||||
- `orbit_pool_windows`
|
||||
- `orbit_pool_wsl`
|
||||
- `work_root_windows`
|
||||
- `work_root_wsl`
|
||||
- `publish_dir_windows`
|
||||
- `publish_dir_wsl`
|
||||
- `manifest_path_windows`
|
||||
- `manifest_path_wsl`
|
||||
- `params_json`
|
||||
- `summary_json`
|
||||
- `input_snapshot_json`
|
||||
- `orbit_summary_json`
|
||||
- `quality_summary_json`
|
||||
- `error_message`
|
||||
- `created_by`
|
||||
- `created_at`
|
||||
- `updated_at`
|
||||
- `started_at`
|
||||
- `ended_at`
|
||||
|
||||
### 4.2 不新增产品主表
|
||||
|
||||
继续使用:
|
||||
|
||||
- `result_products`
|
||||
- `result_assets`
|
||||
- `result_issues`
|
||||
|
||||
但对 `psinsar` 约定如下:
|
||||
|
||||
- `catalog_name = psinsar`
|
||||
- `product_type = psinsar_bundle`
|
||||
- `engine_code = isce2`
|
||||
- `task_alias` 存运行展示名
|
||||
- `run_key` 直接绑定 `ps_timeseries_runs.run_id`
|
||||
|
||||
原因:
|
||||
|
||||
- 当前系统的产品层本来就是“一个产品行 + 多资产”
|
||||
- SBAS publish bundle 天然适合这种结构
|
||||
|
||||
### 4.3 `result_assets.asset_role` 约定
|
||||
|
||||
建议统一定义以下资产角色:
|
||||
|
||||
- `timeseries_cube`
|
||||
- `velocity_h5`
|
||||
- `velocity_geotiff`
|
||||
- `temporal_coherence_h5`
|
||||
- `temporal_coherence_geotiff`
|
||||
- `quality_mask_h5`
|
||||
- `quality_mask_geotiff`
|
||||
- `preview_png`
|
||||
- `diagnostic_png`
|
||||
- `config`
|
||||
- `quality_summary`
|
||||
- `input_manifest`
|
||||
|
||||
### 4.4 精轨 provenance 建议
|
||||
|
||||
最小建议不是新表,而是先把运行级 summary 做扎实。
|
||||
|
||||
在 `ps_timeseries_runs.orbit_summary_json` 中记录:
|
||||
|
||||
- 每景影像日期
|
||||
- 轨道 stem
|
||||
- 轨道 XML 路径
|
||||
- 来源
|
||||
- `existing_xml` / `generated_from_txt`
|
||||
- checksum
|
||||
- 是否来自 ISCE2 pool
|
||||
|
||||
Phase 2 再考虑单独抽 `orbit_assets`。
|
||||
|
||||
## 5. 路径与目录设计
|
||||
|
||||
## 5.1 总原则
|
||||
|
||||
- 原始影像目录不复制
|
||||
- 工作目录和发布目录必须分离
|
||||
- 发布目录一旦注册,尽量不再原地改写
|
||||
|
||||
### 5.2 推荐目录结构
|
||||
|
||||
Windows 侧:
|
||||
|
||||
```text
|
||||
<TIMESERIES_WORK_ROOT>\{run_id}\
|
||||
input\
|
||||
stack\
|
||||
mintpy\
|
||||
logs\
|
||||
publish\
|
||||
```
|
||||
|
||||
WSL 侧:
|
||||
|
||||
```text
|
||||
/mnt/.../timeseries_work/{run_id}/
|
||||
input/
|
||||
stack/
|
||||
mintpy/
|
||||
logs/
|
||||
publish/
|
||||
```
|
||||
|
||||
正式发布根目录:
|
||||
|
||||
```text
|
||||
<PSINSAR_PRODUCT_DIR>\{year}\{run_id}\
|
||||
manifest.json
|
||||
assets/
|
||||
preview/
|
||||
metadata/
|
||||
```
|
||||
|
||||
### 5.3 路径服务
|
||||
|
||||
不要在 job handler、router、script wrapper 里反复手拼 `/mnt/z`。
|
||||
|
||||
新增:
|
||||
|
||||
- `timeseries_paths.py`
|
||||
|
||||
统一负责:
|
||||
|
||||
- Windows -> WSL 路径转换
|
||||
- 运行目录分配
|
||||
- 发布目录分配
|
||||
- 产物入口路径解析
|
||||
|
||||
## 6. 工作流设计
|
||||
|
||||
推荐 workflow name:
|
||||
|
||||
- `isce2_sbas_mintpy_v1`
|
||||
|
||||
推荐步骤如下。
|
||||
|
||||
### Step 1 `prepare_stack_input`
|
||||
|
||||
输入:
|
||||
|
||||
- `batch_id`
|
||||
- 运行参数
|
||||
|
||||
动作:
|
||||
|
||||
- 读取 `ps_task_items`
|
||||
- 校验栈大小、方向一致性、日期连续性
|
||||
- 固化 `input_snapshot_json`
|
||||
- 解析 DEM、轨道池、输出路径
|
||||
- 生成 stack manifest
|
||||
|
||||
产物:
|
||||
|
||||
- `stack_input_manifest.json`
|
||||
- `source stack snapshot`
|
||||
|
||||
### Step 2 `materialize_stack_scenes`
|
||||
|
||||
动作:
|
||||
|
||||
- 复用现有实验脚本逻辑
|
||||
- 为每景影像生成 `SLC/<date>/`
|
||||
- 显式写入 `sensor.orbitFile`
|
||||
|
||||
产物:
|
||||
|
||||
- 物化后的 SLC
|
||||
- `materialization_summary.json`
|
||||
|
||||
### Step 3 `run_isce2_stack`
|
||||
|
||||
动作:
|
||||
|
||||
- 运行 stripmapStack 预处理链
|
||||
- 生成几何、配准、干涉网络基础产物
|
||||
|
||||
产物:
|
||||
|
||||
- stack work 目录
|
||||
- run files
|
||||
- log files
|
||||
|
||||
### Step 4 `run_mintpy_sbas`
|
||||
|
||||
动作:
|
||||
|
||||
- 运行 `smallbaselineApp`
|
||||
- 生成 `timeseries.h5`、`velocity.h5`、`temporalCoherence.h5`
|
||||
- 记录 MintPy 版本、参数模板、参考点信息
|
||||
|
||||
产物:
|
||||
|
||||
- MintPy work 目录
|
||||
- inversion quality summary
|
||||
|
||||
### Step 5 `export_publish_bundle`
|
||||
|
||||
动作:
|
||||
|
||||
- 地理编码
|
||||
- 导出 GeoTIFF
|
||||
- 生成 preview
|
||||
- 写出 `manifest.json`
|
||||
|
||||
产物:
|
||||
|
||||
- `publish/.../manifest.json`
|
||||
- `assets/`
|
||||
- `preview/`
|
||||
- `metadata/`
|
||||
|
||||
### Step 6 `register_psinsar_product`
|
||||
|
||||
动作:
|
||||
|
||||
- 调用 `psinsar_catalog_service`
|
||||
- 把 bundle 注册进 `result_products`
|
||||
- 回填 `ps_timeseries_runs.manifest_path_*`
|
||||
|
||||
产物:
|
||||
|
||||
- `result_products` 一条记录
|
||||
- `result_assets` 多条记录
|
||||
|
||||
### Step 7 `post_check`
|
||||
|
||||
动作:
|
||||
|
||||
- 校验关键资产是否存在
|
||||
- 校验 `velocity.tif` / `geo_timeseries.h5` / `temporalCoherence.tif`
|
||||
- 生成最终健康状态
|
||||
|
||||
## 7. 调度与任务设计
|
||||
|
||||
## 7.1 一个用户可见主任务
|
||||
|
||||
建议每次 SBAS 运行只创建一个主 `system_task`:
|
||||
|
||||
- `task_type = RUN_PSINSAR_PRODUCTION`
|
||||
|
||||
用户在 UI 上主要看这条任务。
|
||||
|
||||
### 7.2 一个工作流运行
|
||||
|
||||
创建:
|
||||
|
||||
- `workflow_run`
|
||||
|
||||
每个步骤单独入 `workflow_steps`。
|
||||
|
||||
worker 现有能力已经支持:
|
||||
|
||||
- job 完成后自动 `mark_step_completed`
|
||||
- job 失败后自动 `mark_step_failed`
|
||||
|
||||
因此不需要再发明新的 step 状态机制。
|
||||
|
||||
### 7.3 建议 job types
|
||||
|
||||
- `TIMESERIES_PREP_STACK`
|
||||
- `TIMESERIES_MATERIALIZE_STACK`
|
||||
- `TIMESERIES_RUN_ISCE2_STACK`
|
||||
- `TIMESERIES_RUN_MINTPY_SBAS`
|
||||
- `TIMESERIES_EXPORT_PRODUCTS`
|
||||
- `TIMESERIES_REGISTER_PRODUCTS`
|
||||
- `TIMESERIES_POST_CHECK`
|
||||
|
||||
## 8. 环境设计
|
||||
|
||||
## 8.1 运行环境原则
|
||||
|
||||
不修改现有 D-InSAR 使用的 `isce2` 环境。
|
||||
|
||||
SBAS 生产使用独立统一环境:
|
||||
|
||||
- WSL distro:`Ubuntu-24.04`
|
||||
- conda env:建议 `isce2_mintpy_v1`
|
||||
|
||||
这是当前实验已验证成功的路线。
|
||||
|
||||
### 8.2 配置隔离
|
||||
|
||||
不要直接复用所有 `ISCE2_*` 配置项来承载 SBAS。
|
||||
|
||||
建议新增:
|
||||
|
||||
- `TIMESERIES_ENABLED`
|
||||
- `TIMESERIES_WSL_DISTRO`
|
||||
- `TIMESERIES_PYTHON`
|
||||
- `TIMESERIES_ENV_NAME`
|
||||
- `TIMESERIES_WORK_ROOT`
|
||||
- `TIMESERIES_PUBLISH_ROOT`
|
||||
- `TIMESERIES_DEM_PATH`
|
||||
- `TIMESERIES_ORBIT_POOL_ISCE2`
|
||||
- `TIMESERIES_WATER_MASK_ROOT`
|
||||
- `TIMESERIES_ALLOW_SYNTHETIC_WATER_MASK`
|
||||
- `TIMESERIES_SMALLBASELINE_TEMPLATE`
|
||||
|
||||
其中可允许默认回退:
|
||||
|
||||
- `TIMESERIES_WSL_DISTRO -> ISCE2_WSL_DISTRO`
|
||||
- `TIMESERIES_DEM_PATH -> ISCE2_DEM_PATH`
|
||||
- `TIMESERIES_ORBIT_POOL_ISCE2 -> ORBIT_POOL_ISCE2`
|
||||
|
||||
## 9. 精轨、DEM、水体掩膜策略
|
||||
|
||||
## 9.1 精轨
|
||||
|
||||
直接复用现有:
|
||||
|
||||
- source orbit scan
|
||||
- ENVI / ISCE2 orbit pool sync
|
||||
- LT-1 orbit XML 解析/生成 helper
|
||||
|
||||
生产要求:
|
||||
|
||||
- 每景影像在运行前就解析到明确 orbit XML
|
||||
- 运行记录中保存 orbit provenance
|
||||
|
||||
## 9.2 DEM
|
||||
|
||||
生产态不依赖在线下载。
|
||||
|
||||
要求:
|
||||
|
||||
- 本地 DEM 路径必须可读
|
||||
- 运行记录中固定写入 DEM 路径与版本描述
|
||||
|
||||
## 9.3 水体掩膜
|
||||
|
||||
这里要区分实验和生产。
|
||||
|
||||
实验态:
|
||||
|
||||
- 允许 `synthetic waterMask fallback`
|
||||
|
||||
生产态建议:
|
||||
|
||||
- 优先本地水体数据源
|
||||
- 若只能 synthetic fallback,则产品可生成,但 `health_status = WARN`
|
||||
- UI 上必须显式标注“使用了 synthetic water mask”
|
||||
|
||||
## 10. `psinsar` 产品注册策略
|
||||
|
||||
## 10.1 产品行设计
|
||||
|
||||
`result_products` 一条记录代表一个 SBAS bundle,而不是单个文件。
|
||||
|
||||
推荐约定:
|
||||
|
||||
- `product_id = psinsar_<run_id>`
|
||||
- `catalog_name = psinsar`
|
||||
- `product_type = psinsar_bundle`
|
||||
- `display_name = SBAS_<direction>_<reference_date>_<run_id short>`
|
||||
- `source_primary_path = assets/geo_timeseries.h5`
|
||||
- `primary_asset_path = assets/velocity.tif`
|
||||
- `preview_path = preview/velocity_preview.png`
|
||||
|
||||
### 10.2 产品详情展示
|
||||
|
||||
产品详情页重点展示:
|
||||
|
||||
- 栈日期列表
|
||||
- 参考日期
|
||||
- 方向
|
||||
- DEM
|
||||
- 轨道来源
|
||||
- 速度图预览
|
||||
- 时间序列 cube 下载
|
||||
- 质量图层下载
|
||||
- 运行日志入口
|
||||
|
||||
## 11. API 设计
|
||||
|
||||
### 11.1 生产接口
|
||||
|
||||
- `POST /timeseries-production/runs`
|
||||
- 基于 `batch_id` 提交一次 SBAS 运行
|
||||
- `GET /timeseries-production/runs`
|
||||
- 列表
|
||||
- `GET /timeseries-production/runs/{run_id}`
|
||||
- 详情
|
||||
- `POST /timeseries-production/runs/{run_id}/retry-step`
|
||||
- 重试单步
|
||||
- `POST /timeseries-production/wsl-check`
|
||||
- 环境检查
|
||||
|
||||
### 11.2 产品接口
|
||||
|
||||
- `GET /ps-products/catalog-status`
|
||||
- `POST /ps-products/rebuild`
|
||||
- `GET /ps-products`
|
||||
- `GET /ps-products/{product_id}`
|
||||
- `GET /ps-products/{product_id}/preview`
|
||||
|
||||
## 12. 前端页面设计
|
||||
|
||||
### 12.1 `ps_production`
|
||||
|
||||
页面模块:
|
||||
|
||||
- 选择已有 `ps_task_batch`
|
||||
- 设定运行参数
|
||||
- 提交运行
|
||||
- 查看步骤进度
|
||||
- 查看失败原因和重试按钮
|
||||
|
||||
### 12.2 `ps_products`
|
||||
|
||||
页面模块:
|
||||
|
||||
- 产品列表
|
||||
- AOI / 时间 / 方向 / 状态筛选
|
||||
- preview 缩略图
|
||||
- 一键跳转到产品详情
|
||||
|
||||
### 12.3 `psinsar_results`
|
||||
|
||||
页面模块:
|
||||
|
||||
- 地图展示 `velocity.tif`
|
||||
- 质量图层切换
|
||||
- 时间序列主资产下载
|
||||
- 后续再扩展点时序查询
|
||||
|
||||
## 13. 推荐实施顺序
|
||||
|
||||
### Phase A:最小生产主链
|
||||
|
||||
- 新增 `ps_timeseries_runs`
|
||||
- 新增 `timeseries_production.py`
|
||||
- 新增 workflow factory
|
||||
- 先直接复用实验脚本作为受控 worker wrapper
|
||||
|
||||
目标:
|
||||
|
||||
- 系统能发起一次 SBAS 运行
|
||||
- 能看到步骤状态
|
||||
- 能落到 publish bundle
|
||||
|
||||
### Phase B:产品注册
|
||||
|
||||
- 新增 `psinsar_catalog_service.py`
|
||||
- 新增 `ps_products.py`
|
||||
- 注册 `psinsar.publish.v1`
|
||||
|
||||
目标:
|
||||
|
||||
- 发布 bundle 能在系统里被浏览和重建
|
||||
|
||||
### Phase C:稳定性与治理
|
||||
|
||||
- 增加 orbit provenance
|
||||
- 增加 DEM / water mask 质量标记
|
||||
- 增加 post-check 和 health-check
|
||||
- 完善路径服务
|
||||
|
||||
### Phase D:UI 完善
|
||||
|
||||
- 补生产页
|
||||
- 补产品页
|
||||
- 补结果页
|
||||
|
||||
## 14. 最终建议
|
||||
|
||||
结合当前审计结果,最稳妥的路线不是“直接把 SBAS 纳入现有 D-InSAR 产品通道”,而是:
|
||||
|
||||
1. 继续复用 `ps_task_batches` 作为输入栈定义
|
||||
2. 新增 `ps_timeseries_runs` 作为生产主记录
|
||||
3. 复用现有任务、作业、工作流骨架
|
||||
4. 以 publish bundle 为产品注册边界
|
||||
5. 先独立实现 `psinsar_catalog_service`,避免影响现有 D-InSAR 生产
|
||||
|
||||
这样可以做到:
|
||||
|
||||
- 技术上最贴合现有系统
|
||||
- 风险上最小
|
||||
- 后续也最容易把实验链稳稳升级成正式生产链
|
||||
@@ -0,0 +1,359 @@
|
||||
# SBAS 系统集成与数据管理审计报告
|
||||
|
||||
更新日期:2026-04-06
|
||||
|
||||
## 1. 审计范围
|
||||
|
||||
本次审计聚焦三件事:
|
||||
|
||||
- 当前 ISCE2 SBAS 实验是否已经真实读取并使用精密轨道
|
||||
- 现有系统的数据管理方案是否适合承接 SBAS/PS-InSAR 生产口
|
||||
- 如果后续要把实验方案嵌入系统,推荐怎样分阶段落地
|
||||
|
||||
本报告基于当前仓库实现与已成功的实验产物,不涉及对现有 D-InSAR 生产链的破坏性修改。
|
||||
|
||||
## 2. 结论摘要
|
||||
|
||||
- 结论 1:当前 SBAS 实验已经真实读取并使用精密轨道,不只是“数据库里有轨道路径”。
|
||||
- 结论 2:当前系统可以完成 PS/SBAS 选片和实验产物生成,但还不能把 `psinsar` 产物作为正式生产结果注册、编目、健康检查和发布。
|
||||
- 结论 3:当前数据管理整体偏“路径驱动”,原始数据身份、存储位置、运行产物三层边界不够清晰,因此用户会感到“数据管理比较松散”。
|
||||
- 结论 4:轨道管理反而是当前较完整的一块,已经具备源目录扫描、池同步、一致性检查和数据库状态统计能力。
|
||||
|
||||
## 3. 审计发现
|
||||
|
||||
### P0 / 高优先级
|
||||
|
||||
#### 3.1 结果编目和发布入口仍然是 D-InSAR 专用,`psinsar` 实验产物无法直接接入系统
|
||||
|
||||
证据:
|
||||
|
||||
- `backend/app/services/result_catalog_service.py:707-713`
|
||||
- `_load_manifest()` 只接受 `product_type == dinsar`
|
||||
- `backend/app/services/result_catalog_service.py:750-753`
|
||||
- 即使读入 manifest,也把 `product_type` 固定写成 `dinsar`
|
||||
- `backend/app/routers/dinsar_products.py:107-140`
|
||||
- 现有编目 API 和后台任务只暴露 `/dinsar-products/*`
|
||||
- `backend/app/services/health_service.py:124-170`
|
||||
- 结果目录健康检查只统计 `catalog_name == dinsar`
|
||||
- `experiments/isce2_sbas_timeseries/scratch/lt1a_strip1_hh_descending_e123p3_n46p1/publish/mintpy_sbas_unified_v1/manifest.json:2-9`
|
||||
- 当前实验成功产物的 manifest 是 `schema_version = psinsar.publish.v1`,`catalog_name = psinsar`
|
||||
|
||||
影响:
|
||||
|
||||
- 实验已经能产出发布级 bundle,但系统当前不会把它识别为正式产品
|
||||
- 不能复用现有结果页、目录重建、健康检查、产品详情接口
|
||||
- 后续如果直接“硬塞”进 D-InSAR 目录,会污染现有 D-InSAR 语义
|
||||
|
||||
建议:
|
||||
|
||||
- Phase 1 不要新建一套完全独立的产品表,继续复用 `result_products` / `result_assets`
|
||||
- 但必须让 catalog 服务显式支持 `catalog_name = psinsar`
|
||||
- 最小可行改造是新增 `psinsar` manifest 解析路径,或把当前 catalog service 抽象成多 catalog 分发器
|
||||
|
||||
#### 3.2 `ps_task_batches` / `ps_task_items` 目前只是选片快照,不是生产记录
|
||||
|
||||
证据:
|
||||
|
||||
- `backend/app/models/orm.py:516-550`
|
||||
- `PsTaskBatchORM` / `PsTaskItemORM` 只保存批次、`file_path`、日期、极化、是否有轨道和状态
|
||||
- `backend/app/routers/task_batches.py:281-320`
|
||||
- 创建 `/task-batches/ps` 时只是把选中的影像元数据写入 `ps_task_items`
|
||||
- `backend/app/routers/pairing.py:124-147`
|
||||
- 现有 `find-ps-timeseries` 只负责找 stack
|
||||
- `backend/app/services/spatial_service.py:444-481`
|
||||
- `find_ps_timeseries_data()` 只返回按轨道方向分组的影像栈
|
||||
- `backend/app/services/job_handlers.py:306-321`
|
||||
- 对 `PS_STACK` 的后台处理目前只是复制选中的影像路径
|
||||
|
||||
影响:
|
||||
|
||||
- 系统里没有“某次 SBAS 生产运行”的业务主记录
|
||||
- 无法稳定记录 `work_dir`、`publish_dir`、运行环境、参数版本、DEM、轨道来源、水体掩膜来源
|
||||
- 无法形成真正可追溯、可重跑、可审计的生产口
|
||||
|
||||
建议:
|
||||
|
||||
- 新增业务表 `ps_timeseries_runs`
|
||||
- 保留 `ps_task_batches` 作为“输入栈定义”
|
||||
- 把真正的处理运行、产物发布、失败重试、步骤状态全部挂到 `ps_timeseries_runs + workflow_runs`
|
||||
|
||||
#### 3.3 数据身份与存储路径耦合过深,迁移和重构成本偏高
|
||||
|
||||
证据:
|
||||
|
||||
- `backend/app/models/orm.py:22-40`
|
||||
- `RadarDataORM` 直接保存 `file_path`、`orbit_file_path`、`preview_cache_path`
|
||||
- `backend/app/models/orm.py:82-101`
|
||||
- `ResultProductORM` 直接保存 `publish_dir`、`manifest_path`、`source_primary_path`、`primary_asset_path`
|
||||
- `backend/app/models/orm.py:182-195`
|
||||
- `ResultAssetORM` 保存 `absolute_path`
|
||||
- `backend/app/services/data_service.py:621-668`
|
||||
- `unique_id` 通过 `os.path.relpath()` 生成,并按 `unique_id` 做 upsert
|
||||
|
||||
影响:
|
||||
|
||||
- 数据身份会受存储根目录变化影响
|
||||
- Windows 根目录变更、WSL 路径调整、NAS 迁移时,数据库主身份和存储位置语义混在一起
|
||||
- 产物迁移到正式发布目录后,历史绝对路径会变成系统耦合点
|
||||
|
||||
建议:
|
||||
|
||||
- 把“数据身份”和“存储位置”拆开
|
||||
- 原始影像层至少补充稳定的 `scene_uid` 或 `product_uid`
|
||||
- 结果层至少补充稳定的 `run_id` / `product_id` 业务主键,把绝对路径退化为可替换的存储定位信息
|
||||
|
||||
### P1 / 中优先级
|
||||
|
||||
#### 3.4 原始影像与轨道的关联粒度偏粗,缺少轨道版本与来源追踪
|
||||
|
||||
证据:
|
||||
|
||||
- `backend/app/services/data_service.py:611-612`
|
||||
- 影像是否有轨道仅按 `(satellite, imaging_date)` 关联
|
||||
- `backend/app/services/data_service.py:688-692`
|
||||
- 后续补关联时也是按同一键更新
|
||||
|
||||
影响:
|
||||
|
||||
- 当同一天存在多个候选轨道版本时,数据库层无法区分来源和版本
|
||||
- 无法在结果层明确回答“本次 SBAS 用的是哪份轨道文件、其 checksum 是多少、来自 source 目录还是 ISCE2 pool”
|
||||
|
||||
建议:
|
||||
|
||||
- 最小可行做法:在 `RadarDataORM` 或新表中增加
|
||||
- `orbit_stem`
|
||||
- `orbit_checksum`
|
||||
- `orbit_source_type`
|
||||
- `orbit_pool_path`
|
||||
- 更完整做法:单独建 `orbit_assets` / `orbit_bindings`
|
||||
|
||||
#### 3.5 数据扫描服务职责过重,扫描、轨道同步、预览缓存、数据库写入耦合在一起
|
||||
|
||||
证据:
|
||||
|
||||
- `backend/app/services/data_service.py:426-463`
|
||||
- 扫描原始轨道并同步 ENVI / ISCE2 池
|
||||
- `backend/app/services/data_service.py:568-699`
|
||||
- 同一流程里完成影像元数据读取、轨道关联、数据库 upsert
|
||||
- `backend/app/services/data_service.py:709-739`
|
||||
- 后面又继续做缓存重建候选收集
|
||||
|
||||
影响:
|
||||
|
||||
- 这个服务变成“总装配间”,测试和回归都更困难
|
||||
- 后续要引入 SBAS 生产口时,很容易继续把更多责任塞进同一个服务
|
||||
|
||||
建议:
|
||||
|
||||
- 拆分为至少四块:
|
||||
- `radar_inventory_service`
|
||||
- `orbit_inventory_service`
|
||||
- `radar_catalog_service`
|
||||
- `preview_cache_service`
|
||||
|
||||
#### 3.6 Windows/WSL 双路径模型是当前现实,但需要收口到专门的路径层
|
||||
|
||||
证据:
|
||||
|
||||
- `backend/app/config.py:150-186`
|
||||
- 同时维护 Windows 轨道池、DEM、IDL、WSL distro、WSL Python 等配置
|
||||
- `experiments/isce2_sbas_timeseries/scripts/build_lt1_stack_prep.py:140-149`
|
||||
- 实验准备阶段会解析 orbit XML
|
||||
- `experiments/isce2_sbas_timeseries/scripts/build_lt1_stack_prep.py:171-193`
|
||||
- 同时生成 Windows 路径和 WSL 路径
|
||||
- `experiments/isce2_sbas_timeseries/scripts/materialize_lt1_stack_scenes.py:98-103`
|
||||
- 物化阶段直接把 orbit XML 传给 ISCE2 sensor
|
||||
|
||||
影响:
|
||||
|
||||
- 当前方案能跑通,但如果没有统一的路径转换层,后续每个服务都可能自己拼接 `/mnt/*`
|
||||
- 这会继续加重“数据管理松散”的体感
|
||||
|
||||
建议:
|
||||
|
||||
- 保留当前双路径现实,不要硬回避
|
||||
- 但把路径转换统一收敛到 `timeseries_paths.py` 或类似组件
|
||||
- 生产口只暴露业务字段,不让上层 UI 和业务逻辑直接感知 `/mnt/z` 与 `F:\`
|
||||
|
||||
### P2 / 正向发现
|
||||
|
||||
#### 3.7 轨道管理是当前系统里相对成熟的一块
|
||||
|
||||
证据:
|
||||
|
||||
- `backend/app/services/data_service.py:426-463`
|
||||
- 已有源目录扫描与 ENVI / ISCE2 轨道池同步
|
||||
- `backend/app/routers/orbit.py:27-108`
|
||||
- 已有数据库统计、池一致性检查、缺口汇总
|
||||
|
||||
结论:
|
||||
|
||||
- 不需要为 SBAS 重新发明一套轨道管理
|
||||
- 更合理的方向是复用现有轨道池和一致性检查能力,再补生产级 provenance
|
||||
|
||||
## 4. 关于“现在实验读取精密轨道了吗”
|
||||
|
||||
答案:是,已经读取,而且已进入实际处理链。
|
||||
|
||||
证据链如下:
|
||||
|
||||
1. 轨道解析入口:
|
||||
- `backend/app/isce2_pipeline/lt1_input_resolver.py:165-201`
|
||||
- `ensure_lt1_orbit_xml()` 会优先取已有 XML,找不到才从 LT-1 轨道 txt 生成 XML
|
||||
2. Stack 准备阶段:
|
||||
- `experiments/isce2_sbas_timeseries/scripts/build_lt1_stack_prep.py:140-149`
|
||||
- 每景影像都调用 `ensure_lt1_orbit_xml()`
|
||||
3. Stack 物化阶段:
|
||||
- `experiments/isce2_sbas_timeseries/scripts/materialize_lt1_stack_scenes.py:98-103`
|
||||
- `sensor.orbitFile = orbit_xml`
|
||||
4. 实验 manifest 证据:
|
||||
- `experiments/isce2_sbas_timeseries/scratch/lt1a_strip1_hh_descending_e123p3_n46p1/stack_input_manifest.json:92-95`
|
||||
- `experiments/isce2_sbas_timeseries/scratch/lt1a_strip1_hh_descending_e123p3_n46p1/stack_input_manifest.json:123-126`
|
||||
- `experiments/isce2_sbas_timeseries/scratch/lt1a_strip1_hh_descending_e123p3_n46p1/stack_input_manifest.json:154-157`
|
||||
- manifest 已明确记录 `D:\\orbit_pools\\isce2\\LT1A_GpsData_GAS_C_*.xml`
|
||||
5. 运行日志证据:
|
||||
- `experiments/isce2_sbas_timeseries/scratch/lt1a_strip1_hh_descending_e123p3_n46p1/stack_work/logs/run_01_reference.log:18`
|
||||
- `experiments/isce2_sbas_timeseries/scratch/lt1a_strip1_hh_descending_e123p3_n46p1/stack_work/logs/run_03_geo2rdr_coarseResamp.log:6`
|
||||
- 日志中已出现 `Orbit interpolation method: hermite`
|
||||
|
||||
结论补充:
|
||||
|
||||
- 当前 SBAS 实验使用的是 ISCE2 轨道池中的 XML 精轨
|
||||
- 因此“精轨是否已读入”这件事在实验层已经成立
|
||||
- 真正缺的不是“有没有轨道”,而是“系统如何把这次运行及其轨道 provenance 规范地登记为生产记录”
|
||||
|
||||
## 5. 集成设计建议
|
||||
|
||||
### 5.1 集成边界
|
||||
|
||||
当前最合理的系统集成边界不是 MintPy 运行目录,而是发布级 bundle:
|
||||
|
||||
- 入口文件:`manifest.json`
|
||||
- 目录结构:`assets/`、`preview/`、`metadata/`
|
||||
|
||||
这与现有产品规范一致:
|
||||
|
||||
- `docs/ISCE2_SBAS_PRODUCT_SPEC.md:69-88`
|
||||
|
||||
因此:
|
||||
|
||||
- 不要直接注册 `stack_work/mintpy_sbas_*`
|
||||
- 要注册 `publish/.../manifest.json` 所代表的一整个 bundle
|
||||
|
||||
### 5.2 推荐最小落地架构
|
||||
|
||||
#### 层 1:规划层,继续复用现有能力
|
||||
|
||||
- 继续使用 `find-ps-timeseries`
|
||||
- 继续使用 `ps_task_batches` / `ps_task_items`
|
||||
- 其职责只保留为“选片与栈定义”
|
||||
|
||||
#### 层 2:生产层,新增业务主记录
|
||||
|
||||
新增 `ps_timeseries_runs`,至少包含:
|
||||
|
||||
- `run_id`
|
||||
- `batch_id`
|
||||
- `catalog_name`
|
||||
- `engine_code`
|
||||
- `processor_code`
|
||||
- `env_name`
|
||||
- `wsl_distro`
|
||||
- `work_dir`
|
||||
- `publish_dir`
|
||||
- `manifest_path`
|
||||
- `reference_date`
|
||||
- `direction`
|
||||
- `params_json`
|
||||
- `summary_json`
|
||||
- `orbit_summary_json`
|
||||
- `dem_path`
|
||||
- `water_mask_source`
|
||||
- `status`
|
||||
- `error_message`
|
||||
- `started_at`
|
||||
- `ended_at`
|
||||
|
||||
#### 层 3:编目层,扩展现有结果目录
|
||||
|
||||
不要单独再造一套产品表,继续复用:
|
||||
|
||||
- `result_products`
|
||||
- `result_assets`
|
||||
- `result_issues`
|
||||
|
||||
但要新增 `psinsar` catalog 入口,支持:
|
||||
|
||||
- 解析 `psinsar.publish.v1`
|
||||
- 注册 `catalog_name = psinsar`
|
||||
- 单独的 catalog status / rebuild / health-check
|
||||
|
||||
#### 层 4:接口层
|
||||
|
||||
建议新增:
|
||||
|
||||
- `POST /timeseries-production/runs`
|
||||
- `GET /timeseries-production/runs`
|
||||
- `GET /timeseries-production/runs/{run_id}`
|
||||
- `POST /timeseries-production/runs/{run_id}/retry-step`
|
||||
- `GET /ps-products`
|
||||
- `GET /ps-products/{product_id}`
|
||||
- `POST /ps-products/rebuild-catalog`
|
||||
|
||||
### 5.3 与当前设计文档的一致性
|
||||
|
||||
当前仓库里的 SBAS 设计文档已经提前写出了这条路线:
|
||||
|
||||
- `docs/ISCE2_SBAS_TIMESERIES_DESIGN.md:227`
|
||||
- 建议新增 `ps_timeseries_runs`
|
||||
- `docs/ISCE2_SBAS_TIMESERIES_DESIGN.md:292-296`
|
||||
- 建议新增 SBAS 相关 job types
|
||||
- `docs/ISCE2_SBAS_TIMESERIES_DESIGN.md:306-313`
|
||||
- 建议新增 `timeseries_production` / `ps_products` / `psinsar_catalog_service`
|
||||
- `docs/ISCE2_SBAS_TIMESERIES_DESIGN.md:563-575`
|
||||
- TODO 中已明确列出 ORM、路由、job handler、catalog 扩展和健康检查
|
||||
|
||||
所以本次审计的判断不是与现有设计冲突,而是说明:
|
||||
|
||||
- 实验层已经坐实
|
||||
- 代码现实也证明下一步确实该进入“生产层与 catalog 层补齐”
|
||||
|
||||
## 6. 建议 TODO
|
||||
|
||||
### 6.1 立即做
|
||||
|
||||
- [ ] 新增 `ps_timeseries_runs` ORM / schema / migration
|
||||
- [ ] 新增 `timeseries_production` router
|
||||
- [ ] 新增 `PUBLISH_PSINSAR_PRODUCTS` 及相关 job handlers
|
||||
- [ ] 让 catalog 服务支持 `psinsar.publish.v1`
|
||||
- [ ] 把 `publish/.../manifest.json` 作为唯一注册入口
|
||||
|
||||
### 6.2 紧接着做
|
||||
|
||||
- [ ] 给 `ps_timeseries_runs` 记录 DEM、轨道、水体掩膜、运行环境版本
|
||||
- [ ] 给轨道绑定补 provenance 字段或单独轨道资产表
|
||||
- [ ] 把 Windows/WSL 路径转换收敛到独立路径服务
|
||||
- [ ] 为 `psinsar` 增加 catalog health-check 与 rebuild
|
||||
|
||||
### 6.3 暂缓做
|
||||
|
||||
- [ ] 不要现在就改动现有 D-InSAR 生产链
|
||||
- [ ] 不要把 SBAS 运行目录直接暴露为产品目录
|
||||
- [ ] 不要为了 SBAS 先拆掉现有 `result_products`
|
||||
|
||||
## 7. 审计结论
|
||||
|
||||
如果现在问“SBAS 能不能开始往系统里接”,答案是:
|
||||
|
||||
- 能,但应该先接“发布 bundle + 生产记录 + psinsar catalog”
|
||||
- 不能直接把实验目录粗暴塞进现有 D-InSAR 产品体系
|
||||
|
||||
如果现在问“系统的数据管理为什么显得松散”,核心原因是:
|
||||
|
||||
- 原始数据身份、物理存储路径、生产产物目录三层目前还没有彻底分开
|
||||
|
||||
如果现在问“下一步最值得做什么”,答案是:
|
||||
|
||||
- 先把 `ps_timeseries_runs + psinsar catalog` 这两个缺口补上
|
||||
|
||||
这样既不会动坏现有 D-InSAR 生产,又能把已经验证成功的 SBAS 实验稳稳接成系统能力。
|
||||
@@ -0,0 +1,319 @@
|
||||
# SBAS 是否可复用现有 D-InSAR Task 路径生产模式
|
||||
|
||||
更新日期:2026-04-06
|
||||
|
||||
## 1. 结论
|
||||
|
||||
结论分两层:
|
||||
|
||||
- 可以复用“以标准输入目录作为生产入口”的模式
|
||||
- 不能直接复用现有 D-InSAR 的 `Task_x/master + slave` 目录协议和 `copy-ps-stack` 实现
|
||||
|
||||
也就是说:
|
||||
|
||||
- 可以借用 D-InSAR 的生产思想
|
||||
- 不能照搬 D-InSAR 的输入结构
|
||||
|
||||
## 2. 现有 D-InSAR 生产模式是什么
|
||||
|
||||
当前 D-InSAR 的 ISCE2 生产模式,本质上是:
|
||||
|
||||
1. 先把一对影像整理成标准任务目录
|
||||
2. 每个任务目录固定包含:
|
||||
- `master/`
|
||||
- `slave/`
|
||||
- `.dinsar_pair.json`
|
||||
3. 生产入口只接收一个 `root_dir`
|
||||
4. 引擎自行扫描这个 `root_dir`
|
||||
- 要么它本身就是一个 task 目录
|
||||
- 要么它下面有多个 `Task_*` 子目录
|
||||
5. 每个 task 目录独立产出一个 pair result
|
||||
|
||||
关键实现:
|
||||
|
||||
- `backend/app/dinsar_engines/isce2_engine.py:314-361`
|
||||
- `validate_root_dir()` 要求:
|
||||
- 单个 task 目录必须有 `master` 和 `slave`
|
||||
- 或父目录下有多个合法 `Task_*`
|
||||
- `backend/app/copier.py:178-218`
|
||||
- D-InSAR 复制时会构建:
|
||||
- `<dest>/<task_alias>/master`
|
||||
- `<dest>/<task_alias>/slave`
|
||||
- `.dinsar_pair.json`
|
||||
- `backend/app/isce2_pipeline/run_lt1_dinsar_pipeline.py:262-298`
|
||||
- pipeline 运行时明确按 `master` / `slave` 两景读取
|
||||
|
||||
所以这是一套“面向影像对”的输入协议。
|
||||
|
||||
## 3. 现有 PS/SBAS 选片算法能不能找出时序队列
|
||||
|
||||
可以,但当前更准确地说,它是:
|
||||
|
||||
- 一个“候选栈发现器”
|
||||
- 还不是一个“生产级栈定义器”
|
||||
|
||||
现有实现:
|
||||
|
||||
- `backend/app/services/spatial_service.py:414-481`
|
||||
|
||||
当前逻辑会:
|
||||
|
||||
- 先选出与 AOI 初始重叠比例满足阈值的影像
|
||||
- 按轨道方向分组
|
||||
- 求每个方向的公共重叠区
|
||||
- 再筛出对公共区覆盖比例满足阈值的影像
|
||||
- 最终按日期排序返回
|
||||
|
||||
这对 SBAS 是有价值的,因为它已经能给出:
|
||||
|
||||
- 同轨方向
|
||||
- 共享公共覆盖区
|
||||
- 时间排序后的影像列表
|
||||
|
||||
但它当前还缺少若干生产级约束:
|
||||
|
||||
- 没有显式要求 `has_orbit_data = True`
|
||||
- 没有显式要求相同 `imaging_mode`
|
||||
- 没有显式要求相同 `polarization`
|
||||
- 没有最小栈规模规则
|
||||
- 没有最大时间间隔或参考影像策略
|
||||
- 没有把结果固化为“一个稳定的 stack identity”
|
||||
|
||||
所以答案是:
|
||||
|
||||
- 用来找 SBAS 候选时序队列,可以
|
||||
- 直接当生产入口,不够
|
||||
|
||||
## 4. 为什么 D-InSAR 的 task 路径模式不能原样套给 SBAS
|
||||
|
||||
### 4.1 D-InSAR 是二元输入,SBAS 是栈输入
|
||||
|
||||
D-InSAR 当前 pipeline 只解析两个角色:
|
||||
|
||||
- `master`
|
||||
- `slave`
|
||||
|
||||
见:
|
||||
|
||||
- `backend/app/isce2_pipeline/run_lt1_dinsar_pipeline.py:262-298`
|
||||
|
||||
而 SBAS 至少需要:
|
||||
|
||||
- 一个有序场景集合
|
||||
- 栈级别的公共参数
|
||||
- 栈级别的参考日期
|
||||
- 栈级别的输出目录约定
|
||||
|
||||
因此 SBAS 的输入目录协议必须是“stack contract”,不是“pair contract”。
|
||||
|
||||
### 4.2 当前 `copy-ps-stack` 是平铺复制,不会生成栈元数据
|
||||
|
||||
现有 PS 复制逻辑:
|
||||
|
||||
- `backend/app/copier.py:45-115`
|
||||
|
||||
它的行为是:
|
||||
|
||||
- 从 `PsTaskItemORM.file_path` 取路径
|
||||
- 优先复制 `original_path + "_envi_import"`
|
||||
- 否则复制原路径
|
||||
- 直接平铺复制到 `dest_dir/<basename>`
|
||||
|
||||
它不会生成:
|
||||
|
||||
- 栈 manifest
|
||||
- 日期排序
|
||||
- 参考日期
|
||||
- 轨道清单
|
||||
- DEM / water mask / publish 约定
|
||||
|
||||
因此这套复制结果目前更像“把选中的影像搬过去”,不是“形成一个可投产的 SBAS 输入包”。
|
||||
|
||||
### 4.3 当前 PS 批次模型保存的信息不够
|
||||
|
||||
现有:
|
||||
|
||||
- `backend/app/models/orm.py:532-550`
|
||||
- `backend/app/routers/task_batches.py:303-312`
|
||||
|
||||
`PsTaskItemORM` 只保存:
|
||||
|
||||
- `file_path`
|
||||
- `satellite`
|
||||
- `imaging_date`
|
||||
- `polarization`
|
||||
- `has_orbit_data`
|
||||
|
||||
没有保存:
|
||||
|
||||
- `orbit_direction`
|
||||
- `imaging_mode`
|
||||
- 稳定 `group_key`
|
||||
- 公共覆盖区
|
||||
- 参考影像策略
|
||||
|
||||
这说明 PS 批次目前还是“输入快照”,不是“可直接投产的 stack contract”。
|
||||
|
||||
## 5. 哪部分可以直接复用
|
||||
|
||||
可以直接复用的,不少。
|
||||
|
||||
### 5.1 生产入口模式
|
||||
|
||||
复用思路:
|
||||
|
||||
- 一个 `root_dir`
|
||||
- 引擎自己扫描这个目录
|
||||
- 支持“单个 stack 目录”或“父目录下多个 stack 目录”
|
||||
|
||||
这点可以直接仿照:
|
||||
|
||||
- `backend/app/dinsar_engines/isce2_engine.py:314-361`
|
||||
|
||||
只是把目录判定从:
|
||||
|
||||
- `master + slave`
|
||||
|
||||
换成:
|
||||
|
||||
- `stack_input_manifest.json` 或 `.ps_stack.json`
|
||||
|
||||
### 5.2 作业提交模式
|
||||
|
||||
可以直接复用 D-InSAR 当前的:
|
||||
|
||||
- router 提交任务
|
||||
- `task_service.create_task()`
|
||||
- `job_queue_service.create_job()`
|
||||
- worker 执行
|
||||
- 返回单次运行状态
|
||||
|
||||
### 5.3 目录驱动执行
|
||||
|
||||
这部分思想也可以复用:
|
||||
|
||||
- 输入目录是唯一入口
|
||||
- engine 不依赖前端临时状态
|
||||
- 只要目录契约稳定,就可以重复运行
|
||||
|
||||
这对 SBAS 是对的。
|
||||
|
||||
## 6. SBAS 推荐的“task 路径模式”
|
||||
|
||||
建议做一个 SBAS 版输入目录协议,而不是复用 D-InSAR 的 `Task_*` 结构。
|
||||
|
||||
推荐:
|
||||
|
||||
```text
|
||||
Stack_<group_key>/
|
||||
stack_input_manifest.json
|
||||
scenes/
|
||||
20250118/
|
||||
source_scene.json
|
||||
<raw scene or link>
|
||||
20250315/
|
||||
source_scene.json
|
||||
<raw scene or link>
|
||||
20250510/
|
||||
source_scene.json
|
||||
<raw scene or link>
|
||||
inputs/
|
||||
dem/
|
||||
logs/
|
||||
```
|
||||
|
||||
后续运行阶段再生成:
|
||||
|
||||
```text
|
||||
Stack_<group_key>/
|
||||
stack_input_manifest.json
|
||||
scenes/
|
||||
SLC/
|
||||
stack_work/
|
||||
publish/
|
||||
logs/
|
||||
```
|
||||
|
||||
## 7. 推荐的 `stack_input_manifest.json` 最小字段
|
||||
|
||||
至少应包含:
|
||||
|
||||
- `group_key`
|
||||
- `batch_id`
|
||||
- `mode`
|
||||
- `sbas`
|
||||
- `direction`
|
||||
- `reference_date`
|
||||
- `stack_dates`
|
||||
- `scene_count`
|
||||
- `dem_path_windows`
|
||||
- `dem_path_wsl`
|
||||
- `orbit_pool_windows`
|
||||
- `orbit_pool_wsl`
|
||||
- `publish_dir_windows`
|
||||
- `publish_dir_wsl`
|
||||
- `water_mask_mode`
|
||||
- `scenes[]`
|
||||
- `date`
|
||||
- `file_path`
|
||||
- `satellite`
|
||||
- `imaging_mode`
|
||||
- `polarization`
|
||||
- `orbit_direction`
|
||||
- `orbit_xml_path`
|
||||
- `has_orbit_data`
|
||||
|
||||
这个文件对 SBAS 的作用,等价于 D-InSAR 里的 `.dinsar_pair.json`,但它描述的是一个栈,不是一对。
|
||||
|
||||
## 8. 物理复制要不要照搬 D-InSAR
|
||||
|
||||
不建议原样照搬。
|
||||
|
||||
原因:
|
||||
|
||||
- SBAS 常常是 5 景、10 景甚至更多
|
||||
- 全量复制原始影像会放大 IO 和存储成本
|
||||
- 你当前原始数据已经在 WSL 可见路径下,不一定需要再复制一遍
|
||||
|
||||
更合理的 Phase 1 做法:
|
||||
|
||||
- 保留“输入目录”概念
|
||||
- 但目录里优先存:
|
||||
- manifest
|
||||
- sidecar metadata
|
||||
- 规范化链接或原路径引用
|
||||
- 仅在必要时物化最小运行所需文件
|
||||
|
||||
也就是说:
|
||||
|
||||
- 推荐“目录驱动”
|
||||
- 不推荐“全量复制驱动”
|
||||
|
||||
## 9. 最终判断
|
||||
|
||||
如果你的问题是:
|
||||
|
||||
- “现有匹配算法能不能找出 SBAS 需要的时序影像队列?”
|
||||
|
||||
答案是:
|
||||
|
||||
- 可以作为候选栈发现器
|
||||
- 但还需要补生产级校验和栈固化
|
||||
|
||||
如果你的问题是:
|
||||
|
||||
- “能不能沿用 D-InSAR 生产时的输入 task 路径模式?”
|
||||
|
||||
答案是:
|
||||
|
||||
- 可以沿用这个模式的思想
|
||||
- 不能沿用现有 D-InSAR 的具体目录协议和复制实现
|
||||
|
||||
最推荐的落地方式是:
|
||||
|
||||
1. 保留“目录作为生产入口”
|
||||
2. 为 SBAS 定义独立的 `Stack_*` 输入契约
|
||||
3. 用 `stack_input_manifest.json` 替代 `.dinsar_pair.json`
|
||||
4. 用 stack-aware 的 prepare/materialize 步骤替代当前 `copy-ps-stack` 平铺复制
|
||||
|
||||
这样既能继承现有系统的生产模式,又不会把 SBAS 错套成二景 D-InSAR。
|
||||
@@ -0,0 +1,138 @@
|
||||
# 安全审计补充记录(2026-03-12)
|
||||
|
||||
审计范围:后端权限边界、文件与归档处理链路、部署敏感资产暴露情况。
|
||||
审计方式:静态只读审计,本文档仅记录结论与建议,不包含代码修复。
|
||||
审计前提:当前环境为开发机,目标部署环境为私有内网;授权签发材料与初始化密码当前因封装方式不足,仍位于开发目录中。
|
||||
|
||||
## 1. 本次审计的判断口径
|
||||
|
||||
本次记录将问题分为两类:
|
||||
|
||||
1. 开发阶段可解释、但上线打包时必须隔离的运维/交付风险。
|
||||
2. 与部署环境无关、进入内网后仍然成立的代码级安全缺陷。
|
||||
|
||||
这一区分的目的,是避免把“开发机上的临时暴露”与“真正需要修代码的问题”混为一谈。
|
||||
|
||||
## 2. 开发机阶段的可解释暴露
|
||||
|
||||
以下内容在当前“开发机 + 私有内网 + 尚未完成封装”的前提下,可以暂时理解为运维接受项;但它们不应进入最终部署包、客户现场机器、共享目录或备份集。
|
||||
|
||||
### 2.1 授权签发私钥与签发工具位于项目目录
|
||||
|
||||
- 证据:
|
||||
- `license-issuer/issue_license.py`
|
||||
- `license-issuer/private_key.b64`
|
||||
- `backend/app/license_service.py`
|
||||
- `license-issuer/README.md`
|
||||
- 说明:
|
||||
- 当前目录同时包含授权验证逻辑和签发私钥材料。
|
||||
- 在开发阶段这可以理解为便于调试和现场签发。
|
||||
- 但从交付角度看,签发端与验证端必须物理隔离。
|
||||
- 当前结论:
|
||||
- 在开发机上记录为“运维接受项”。
|
||||
- 在上线交付前必须从部署物中彻底移除。
|
||||
|
||||
### 2.2 `.env` 中包含初始化管理员密码
|
||||
|
||||
- 证据:
|
||||
- `.env`
|
||||
- `scripts/init_db.py`
|
||||
- `README.md`
|
||||
- 说明:
|
||||
- 初始化脚本会读取 `INIT_ADMIN_PASSWORD` 用于创建或重置管理员。
|
||||
- 这在开发期、首次部署准备期可以理解为简化初始化流程。
|
||||
- 但如果 `.env` 被打进部署包、同步到共享目录或留在客户机,管理员账户控制面会暴露。
|
||||
- 当前结论:
|
||||
- 在开发机上记录为“运维接受项”。
|
||||
- 在上线交付前必须改为受控注入,不应随项目目录长期保留。
|
||||
|
||||
## 3. 上线前应修复的代码问题
|
||||
|
||||
以下问题即使部署在私有内网,仍然成立,建议在正式部署前完成修复。
|
||||
|
||||
### 3.1 归档解压边界仍不安全
|
||||
|
||||
- 风险等级:高
|
||||
- 证据:
|
||||
- `scripts/unpack_archives.py:123`
|
||||
- `scripts/unpack_archives.py:206`
|
||||
- `backend/app/services/gf3_service.py:27`
|
||||
- `backend/app/services/gf3_service.py:36`
|
||||
- `backend/app/services/gf3_service.py:42`
|
||||
- 问题描述:
|
||||
- 现有检查主要基于归档条目名中的 `..` 或绝对路径。
|
||||
- 但后续仍直接使用 `extractall`。
|
||||
- 这种模式对 tar 的符号链接、硬链接等条目并不稳妥。
|
||||
- 影响:
|
||||
- 如果输入归档来自外部来源、共享目录或低信任操作员,可能出现目录逃逸或覆盖非目标文件的风险。
|
||||
- 建议:
|
||||
- 禁用不安全条目类型。
|
||||
- 使用逐条提取代替 `extractall`。
|
||||
- 在提取前对最终落盘路径做真实路径校验。
|
||||
|
||||
### 3.2 导出目录白名单使用前缀匹配,存在绕过空间
|
||||
|
||||
- 风险等级:中
|
||||
- 证据:
|
||||
- `backend/app/routers/dependencies.py:179`
|
||||
- `backend/app/routers/dependencies.py:219`
|
||||
- `backend/app/routers/tools.py:88`
|
||||
- `backend/app/routers/dinsar.py:299`
|
||||
- `backend/app/routers/idl.py:246`
|
||||
- 问题描述:
|
||||
- `ALLOWED_EXPORT_DIRS` 白名单校验使用 `startswith`。
|
||||
- 当允许目录为 `C:\exports` 时,类似 `C:\exports_tmp` 也可能被误判为合法。
|
||||
- 影响:
|
||||
- 导出、复制、结果提取类接口的目标目录边界不够严格。
|
||||
- 建议:
|
||||
- 改为基于规范化后的真实路径做“目录包含关系”判断,而不是字符串前缀判断。
|
||||
|
||||
### 3.3 登录限流按用户名生效,容易被用于锁死指定账号
|
||||
|
||||
- 风险等级:中
|
||||
- 证据:
|
||||
- `backend/app/routers/dependencies.py:459`
|
||||
- `backend/app/routers/auth.py:72`
|
||||
- `backend/app/routers/dependencies.py:92`
|
||||
- `backend/app/routers/dependencies.py:94`
|
||||
- 问题描述:
|
||||
- 当前限流键仅基于用户名,不区分来源。
|
||||
- 任意请求方只需反复提交错误密码,即可让目标账号进入锁定窗口。
|
||||
- 影响:
|
||||
- 会形成低成本拒绝服务,尤其对 `admin` 账号明显。
|
||||
- 建议:
|
||||
- 改为“用户名 + 来源”或多维限流。
|
||||
- 对不存在账号和存在账号保持相近的响应节奏。
|
||||
- 视部署场景决定是否叠加网关层限流。
|
||||
|
||||
## 4. 本轮未发现的高风险点
|
||||
|
||||
本轮复核中,暂未发现以下明显高危问题:
|
||||
|
||||
- 前端未见将会话令牌持久化到 `localStorage` 或 `sessionStorage`。
|
||||
- 后端主路由默认挂载了统一的授权与登录依赖。
|
||||
- 管理类写操作多数仍要求管理员身份。
|
||||
|
||||
这不代表系统不存在其他缺陷,只表示在本次覆盖范围内未发现更高优先级的新问题。
|
||||
|
||||
## 5. 建议的上线前处置顺序
|
||||
|
||||
### P0:正式部署前必须完成
|
||||
|
||||
1. 修复所有归档解压的安全边界。
|
||||
2. 修复导出路径白名单判断逻辑。
|
||||
3. 调整登录限流策略,避免用户名级别锁死。
|
||||
4. 确保 `license-issuer/`、`.env`、任何实际授权文件不进入部署包。
|
||||
|
||||
### P1:交付封装优化
|
||||
|
||||
1. 将签发工具与后端运行目录彻底拆分。
|
||||
2. 将初始化口令改为部署时注入,而不是长期存放在项目根目录。
|
||||
3. 为部署物建立显式排除清单。
|
||||
|
||||
## 6. 说明
|
||||
|
||||
- 本文档用于记录 2026-03-12 的补充审计结论。
|
||||
- 本次审计未进行动态利用验证。
|
||||
- 本次审计未修改业务代码。
|
||||
|
||||
@@ -0,0 +1,393 @@
|
||||
# WSL2 + ISCE2 + MintPy SBAS 集成方案
|
||||
|
||||
Updated: 2026-04-12
|
||||
|
||||
## 1. 背景
|
||||
|
||||
当前仓库已经具备 SBAS/PS-InSAR 的部分系统骨架:
|
||||
|
||||
- 已有 `ps_timeseries_runs` 业务表与基础 API
|
||||
- 已有 `prepare -> stack_prep_initial -> materialize -> stack_prep_refresh` 四步 workflow
|
||||
- 已有 `psinsar.publish.v1` manifest 解析与 `psinsar` catalog
|
||||
- 已有 `ps_production`、`ps_products`、`psinsar_results` 前端入口
|
||||
|
||||
同时,实验层已经验证:
|
||||
|
||||
- LT-1 stack 可以在 WSL2 `Ubuntu-24.04` 下跑通到 `run_08_igram`
|
||||
- MintPy SBAS 可以产出 `timeseries.h5`、`velocity.h5`
|
||||
- geocode/export 可以产出 `psinsar.publish.v1` publish bundle
|
||||
- 统一环境 `isce2_mintpy_v1` 可以同时导入 `isce` 与 `mintpy`
|
||||
|
||||
因此当前最合理的工作不是重写处理算法,而是把已经验证过的 WSL2 运行链稳定接入现有系统。
|
||||
|
||||
## 2. 本机环境核对
|
||||
|
||||
本方案基于 2026-04-12 的本机实际核对结果。
|
||||
|
||||
### 2.1 WSL2 发行版
|
||||
|
||||
- 可见发行版:
|
||||
- `Ubuntu`
|
||||
- `Ubuntu-24.04`
|
||||
- SBAS 集成目标发行版:
|
||||
- `Ubuntu-24.04`
|
||||
|
||||
### 2.2 已确认 conda 环境
|
||||
|
||||
- `isce2`
|
||||
- Python:
|
||||
- `/home/administrator/miniconda3/envs/isce2/bin/python`
|
||||
- 可导入:
|
||||
- `isce`
|
||||
- `mintpy`
|
||||
- Python:
|
||||
- `/home/administrator/miniconda3/envs/mintpy/bin/python`
|
||||
- 可导入:
|
||||
- `mintpy`
|
||||
- `isce2_mintpy_v1`
|
||||
- Python:
|
||||
- `/home/administrator/miniconda3/envs/isce2_mintpy_v1/bin/python`
|
||||
- 可导入:
|
||||
- `isce`
|
||||
- `mintpy`
|
||||
|
||||
### 2.3 当前判断
|
||||
|
||||
- `isce2_mintpy_v1` 已满足“单环境跑 stack + MintPy”的基础条件
|
||||
- 现有 `isce2` 环境仍应保留给当前 D-InSAR 生产链
|
||||
- 运行时不应依赖交互式 `sudo`
|
||||
- 所有系统依赖、conda 环境、脚本权限应在部署阶段一次性准备完成
|
||||
|
||||
## 3. 设计结论
|
||||
|
||||
### 3.1 主方案
|
||||
|
||||
SBAS 系统集成默认采用:
|
||||
|
||||
- WSL distro:
|
||||
- `Ubuntu-24.04`
|
||||
- runtime env:
|
||||
- `isce2_mintpy_v1`
|
||||
|
||||
### 3.2 备用方案
|
||||
|
||||
保留实验期的双环境桥接链路作为 fallback:
|
||||
|
||||
- `isce2`
|
||||
- 负责 ISCE2 stack
|
||||
- `mintpy`
|
||||
- 负责 MintPy SBAS
|
||||
|
||||
但 fallback 不应作为系统默认路径,只在统一环境故障时使用。
|
||||
|
||||
### 3.3 产品边界
|
||||
|
||||
系统正式接入边界固定为 publish bundle:
|
||||
|
||||
- `manifest.json`
|
||||
- `assets/`
|
||||
- `preview/`
|
||||
- `metadata/`
|
||||
|
||||
系统不直接把以下目录视为正式产品:
|
||||
|
||||
- `stack_work/`
|
||||
- `mintpy_sbas_*`
|
||||
- `Igrams/`
|
||||
- 其他中间目录
|
||||
|
||||
## 4. 现状与缺口
|
||||
|
||||
### 4.1 已完成
|
||||
|
||||
- 运行记录模型:
|
||||
- `ps_timeseries_runs`
|
||||
- 基础生产 API:
|
||||
- `timeseries-production`
|
||||
- 基础产品 API:
|
||||
- `ps-products`
|
||||
- `psinsar` catalog 解析与重建
|
||||
- 前端生产页、产品页、结果页入口
|
||||
|
||||
### 4.2 未完成
|
||||
|
||||
当前系统还没有把下面四步接入正式 workflow:
|
||||
|
||||
1. `run_isce2_stack`
|
||||
2. `run_mintpy_sbas`
|
||||
3. `export_publish_bundle`
|
||||
4. `register_psinsar_product`
|
||||
|
||||
这意味着当前系统是“桥接层已接入”,但还不是“完整自动生产闭环”。
|
||||
|
||||
## 5. 目标架构
|
||||
|
||||
```text
|
||||
Windows FastAPI / Worker
|
||||
-> submit run / record metadata / monitor progress
|
||||
-> dispatch WSL jobs
|
||||
-> register publish bundle into psinsar catalog
|
||||
-> serve API to frontend
|
||||
|
||||
WSL2 Ubuntu-24.04
|
||||
-> ISCE2 stripmapStack execution
|
||||
-> MintPy SBAS inversion
|
||||
-> geocode / export / preview generation
|
||||
-> build manifest.json
|
||||
|
||||
Formal Product Boundary
|
||||
-> PSINSAR_PRODUCT_DIR/<product_id>/
|
||||
manifest.json
|
||||
assets/
|
||||
preview/
|
||||
metadata/
|
||||
```
|
||||
|
||||
设计原则:
|
||||
|
||||
- 系统负责编排,不负责重计算
|
||||
- WSL2 负责执行,不负责业务登记
|
||||
- 正式产品只认 publish bundle
|
||||
- D-InSAR 与 SBAS 运行时保持隔离
|
||||
|
||||
## 6. 推荐 workflow
|
||||
|
||||
### 6.1 Phase 1 已有步骤
|
||||
|
||||
1. `prepare_stack_input`
|
||||
2. `build_stack_prep_initial`
|
||||
3. `materialize_stack_scenes`
|
||||
4. `refresh_stack_ready`
|
||||
|
||||
### 6.2 需要新增的正式步骤
|
||||
|
||||
5. `run_isce2_stack`
|
||||
- 在 `Ubuntu-24.04` 中执行生成的 `run_01` 至 `run_08_igram`
|
||||
- 产出 `Igrams/`、`geom_reference/`、`baselines/`
|
||||
|
||||
6. `run_mintpy_sbas`
|
||||
- 在 `isce2_mintpy_v1` 中执行 MintPy SBAS
|
||||
- 产出:
|
||||
- `timeseries.h5`
|
||||
- `velocity.h5`
|
||||
- `temporalCoherence.h5`
|
||||
- `maskTempCoh.h5`
|
||||
|
||||
7. `export_publish_bundle`
|
||||
- geocode
|
||||
- GeoTIFF 导出
|
||||
- 生成预览图
|
||||
- 生成 `manifest.json`
|
||||
|
||||
8. `register_psinsar_product`
|
||||
- 将 publish bundle 复制或落到正式 `PSINSAR_PRODUCT_DIR`
|
||||
- 增量登记到 `psinsar` catalog
|
||||
|
||||
## 7. 后端设计
|
||||
|
||||
### 7.1 配置项
|
||||
|
||||
建议显式配置以下参数,不再依赖默认继承:
|
||||
|
||||
```dotenv
|
||||
TIMESERIES_ENABLED=true
|
||||
TIMESERIES_WSL_DISTRO=Ubuntu-24.04
|
||||
TIMESERIES_ENV_NAME=isce2_mintpy_v1
|
||||
TIMESERIES_PYTHON=/home/administrator/miniconda3/envs/isce2_mintpy_v1/bin/python
|
||||
TIMESERIES_WORK_ROOT=<windows path>
|
||||
TIMESERIES_DEM_PATH=<windows path>
|
||||
TIMESERIES_ORBIT_POOL_ISCE2=<windows path>
|
||||
TIMESERIES_EXPERIMENT_ROOT=<repo>/experiments/isce2_sbas_timeseries
|
||||
TIMESERIES_STACK_PREP_SCRIPT=<repo>/experiments/isce2_sbas_timeseries/scripts/build_lt1_stack_prep.py
|
||||
TIMESERIES_MATERIALIZE_SCRIPT=<repo>/experiments/isce2_sbas_timeseries/scripts/materialize_lt1_stack_scenes.py
|
||||
PSINSAR_PRODUCT_DIR=<formal publish root>
|
||||
```
|
||||
|
||||
关键要求:
|
||||
|
||||
- `TIMESERIES_ENV_NAME` 与 `TIMESERIES_PYTHON` 必须指向同一环境
|
||||
- 不再让 `TIMESERIES_PYTHON` 默认继承 `ISCE2_PYTHON`
|
||||
|
||||
### 7.2 模块拆分
|
||||
|
||||
建议新增或补强以下模块:
|
||||
|
||||
- `timeseries_paths.py`
|
||||
- 统一维护 Windows/WSL 路径转换
|
||||
- 统一生成 work/publish/logs 路径
|
||||
|
||||
- `timeseries_runtime_service.py`
|
||||
- 统一执行 WSL 命令
|
||||
- 统一处理 timeout、stdout/stderr、返回码
|
||||
|
||||
- `isce2_stack_runtime_service.py`
|
||||
- 封装 `run_01` 至 `run_08_igram` 执行
|
||||
|
||||
- `mintpy_runtime_service.py`
|
||||
- 封装 MintPy SBAS 运行
|
||||
- 封装统一环境 runner
|
||||
|
||||
- `psinsar_publish_service.py`
|
||||
- 封装 export/publish bundle 生成
|
||||
- 封装产品落库前的目录检查
|
||||
|
||||
### 7.3 建议新增 job types
|
||||
|
||||
- `TIMESERIES_ISCE2_STACK_RUN`
|
||||
- `TIMESERIES_MINTPY_SBAS_RUN`
|
||||
- `TIMESERIES_EXPORT_PUBLISH`
|
||||
- `TIMESERIES_REGISTER_PRODUCT`
|
||||
|
||||
当前已有:
|
||||
|
||||
- `TIMESERIES_PREPARE`
|
||||
- `TIMESERIES_STACK_PREP`
|
||||
- `TIMESERIES_MATERIALIZE`
|
||||
|
||||
### 7.4 运行记录要求
|
||||
|
||||
`ps_timeseries_runs` 至少应稳定记录:
|
||||
|
||||
- `wsl_distro`
|
||||
- `env_name`
|
||||
- `python_path`
|
||||
- `work_root`
|
||||
- `publish_dir`
|
||||
- `manifest_path`
|
||||
- `reference_date`
|
||||
- `stack_dates`
|
||||
- `dem_path`
|
||||
- `orbit_pool`
|
||||
- `water_mask_mode`
|
||||
- `step logs`
|
||||
- `quality summary`
|
||||
- `command snapshots`
|
||||
|
||||
目标不是“只知道跑过”,而是“知道当时用什么环境、什么 DEM、什么 orbit、怎么跑出来的”。
|
||||
|
||||
## 8. 结果目录与 catalog 策略
|
||||
|
||||
### 8.1 正式产品目录
|
||||
|
||||
正式产品目录统一使用:
|
||||
|
||||
- `PSINSAR_PRODUCT_DIR`
|
||||
|
||||
实验目录:
|
||||
|
||||
- `experiments/.../publish/...`
|
||||
|
||||
只作为验证来源,不应继续充当正式产品根目录。
|
||||
|
||||
### 8.2 推荐策略
|
||||
|
||||
第一阶段先支持两种接入方式:
|
||||
|
||||
1. `import_experiment_bundle`
|
||||
- 将已有实验成功 bundle 导入正式产品目录
|
||||
- 用于快速验证 catalog 和前端显示
|
||||
|
||||
2. `publish_from_workflow`
|
||||
- 由 workflow 自动生成 bundle 并注册
|
||||
- 作为正式闭环目标
|
||||
|
||||
### 8.3 catalog 设计要求
|
||||
|
||||
`psinsar_catalog_service` 建议补充:
|
||||
|
||||
- 单 manifest 增量注册
|
||||
- 单产品刷新
|
||||
- publish 目录健康检查
|
||||
- run_id 到 product_id 的稳定关联
|
||||
|
||||
## 9. 前端设计
|
||||
|
||||
### 9.1 `ps_production`
|
||||
|
||||
第一阶段重点不是分析,而是运维可用:
|
||||
|
||||
- 显示 WSL/环境状态
|
||||
- 显示 workflow 步骤状态
|
||||
- 显示 stdout/stderr 日志位置
|
||||
- 失败后支持步骤级重试
|
||||
- 成功后跳转到产品详情
|
||||
|
||||
### 9.2 `ps_products`
|
||||
|
||||
第一阶段重点:
|
||||
|
||||
- list/filter
|
||||
- 查看 manifest
|
||||
- 查看 preview
|
||||
- 手动 rebuild catalog
|
||||
- 从 run 跳到 product
|
||||
|
||||
### 9.3 `psinsar_results`
|
||||
|
||||
第一阶段继续复用 catalog 只读视图即可。
|
||||
|
||||
### 9.4 `psinsar_analysis`
|
||||
|
||||
继续预留,不在当前阶段展开。
|
||||
|
||||
## 10. 稳定性约束
|
||||
|
||||
必须遵守以下边界:
|
||||
|
||||
- 不修改当前 D-InSAR 的默认 `ISCE2_PYTHON`
|
||||
- 不把当前 D-InSAR 脚本改指向 `isce2_mintpy_v1`
|
||||
- 不让业务层代码直接拼 `/mnt/z`
|
||||
- 不把 `MintPy` 工作目录直接暴露为正式产品
|
||||
- 不依赖交互式 `sudo`
|
||||
|
||||
## 11. 推荐实施顺序
|
||||
|
||||
### Phase A
|
||||
|
||||
先做“实验成果纳入系统”:
|
||||
|
||||
- 支持导入已有 experiment publish bundle
|
||||
- 落到正式 `PSINSAR_PRODUCT_DIR`
|
||||
- 让 catalog 和前端先可见
|
||||
|
||||
### Phase B
|
||||
|
||||
再做“完整 WSL2 自动执行闭环”:
|
||||
|
||||
- 接入 `run_isce2_stack`
|
||||
- 接入 `run_mintpy_sbas`
|
||||
- 接入 `export_publish_bundle`
|
||||
- 接入 `register_psinsar_product`
|
||||
|
||||
### Phase C
|
||||
|
||||
最后做“生产级稳定化”:
|
||||
|
||||
- 失败重试
|
||||
- 恢复执行
|
||||
- 健康检查
|
||||
- 环境快照
|
||||
- provenance 补齐
|
||||
|
||||
## 12. 验收标准
|
||||
|
||||
达到以下条件后,才算 SBAS 正式接入完成:
|
||||
|
||||
1. 系统可从一个已有 `PS stack batch` 发起 SBAS 运行。
|
||||
2. Worker 可在 `Ubuntu-24.04` 中自动驱动 ISCE2 stack。
|
||||
3. Worker 可在 `isce2_mintpy_v1` 中自动驱动 MintPy。
|
||||
4. 系统可自动生成 publish bundle。
|
||||
5. bundle 可自动注册到 `psinsar` catalog。
|
||||
6. 前端可查看运行状态、产品列表、preview 和 manifest。
|
||||
7. 失败步骤可重试,且不会污染现有 D-InSAR 生产链。
|
||||
|
||||
## 13. 当前推荐
|
||||
|
||||
当前最稳的方向是:
|
||||
|
||||
- 继续以 `isce2_mintpy_v1` 作为 SBAS 主运行时
|
||||
- 保留 `isce2` 与 `mintpy` 双环境桥接链路作为 fallback
|
||||
- 先把 experiment publish bundle 纳入正式系统
|
||||
- 再补自动执行后四步
|
||||
|
||||
这条路线最符合当前仓库状态,也最符合“稳定优先、不破坏现有 D-InSAR”的约束。
|
||||
@@ -0,0 +1,49 @@
|
||||
# Repository Guidelines
|
||||
|
||||
## Agent Behavior
|
||||
- Before modifying any code or files (including config/scripts), explicitly ask the user for permission.
|
||||
- Do not change the database initialization/check/generation flow used by `start_system.bat` and `scripts/start_app.ps1`; any database-related changes must preserve that startup logic.
|
||||
- Keep documentation (especially `docs/DEPLOYMENT.md`) updated when introducing new changes or features.
|
||||
- Any newly written or updated Chinese text in code/config/docs must be saved with UTF-8 encoding (no mojibake/garbled characters).
|
||||
- For map overlay architecture decisions, prioritize long-term runtime stability and smoothness over short-term implementation speed.
|
||||
- For source radar preview overlay, prefer backend pre-corrected/georeferenced cache generation (Scheme B) and keep frontend rendering lightweight; avoid heavy per-frame frontend geometric transforms as the primary approach.
|
||||
|
||||
## Project Structure & Module Organization
|
||||
- `backend/`: FastAPI app and services. Core entrypoint is `backend/app/main.py`, router in `backend/app/api.py`, and domain logic in `backend/app/services/`.
|
||||
- `backend/migrations/`: SQL migrations (including spatial functions used at startup).
|
||||
- `backend/Point/` and `backend/colormaps/`: spatial inputs and colormap assets used by the backend.
|
||||
- `frontend/`: React + Vite app. Source lives in `frontend/src/`, public assets in `frontend/public/`, and production build output in `frontend/dist/`.
|
||||
- `image_cache/`: generated thumbnails. Filenames use `ID_{id}_{name}.webp`.
|
||||
- `scripts/`, `start_system.bat`, `run_backend.py`: local helper scripts.
|
||||
|
||||
## Build, Test, and Development Commands
|
||||
- Python runtime: use Conda env `InSAR` only. Prefer `D:\anaconda3\Scripts\conda.exe run -n InSAR <command>`; do not call system `python`.
|
||||
- Backend dev server: `cd backend && D:\anaconda3\Scripts\conda.exe run -n InSAR python -m uvicorn app.main:app --reload` (runs on port 8000).
|
||||
- Alternate backend start: `D:\anaconda3\Scripts\conda.exe run -n InSAR python run_backend.py` (wrapper used by docs/scripts).
|
||||
- Frontend dev server: `cd frontend && npm install && npm run dev` (runs on port 5173).
|
||||
- Frontend build: `cd frontend && npm run build`.
|
||||
- Frontend lint: `cd frontend && npm run lint`.
|
||||
|
||||
## Coding Style & Naming Conventions
|
||||
- Python: follow PEP 8 (4-space indentation). Keep service logic in `backend/app/services/` and API routes in `backend/app/api.py`.
|
||||
- Frontend: use 2-space indentation in `.jsx` and `.css` to match existing files; React components use `PascalCase` filenames (e.g., `StatisticsDashboard.jsx`).
|
||||
- Image cache naming: `ID_{id}_{name}.webp` is required for cache lookups.
|
||||
- Linting: ESLint is configured in `frontend/eslint.config.js` (run via `npm run lint`).
|
||||
|
||||
## Testing Guidelines
|
||||
- No dedicated test framework or test directory is present in this workspace. If you add tests, document the runner and add a `test` script in `frontend/package.json` or a backend test command.
|
||||
- Prefer naming tests `test_*.py` (pytest-style) or `*.test.jsx` if a JS test runner is introduced.
|
||||
|
||||
## Commit & Pull Request Guidelines
|
||||
- Git metadata is not available in this workspace, so commit message conventions cannot be inferred. Use your team standard (e.g., Conventional Commits) and keep messages concise.
|
||||
- PRs should include: a short description, linked issue/ticket if applicable, screenshots for UI changes, and clear testing notes.
|
||||
|
||||
## Configuration & Environment Notes
|
||||
- Database is PostgreSQL with PostGIS. Set `DATABASE_URL` (format: `postgresql+asyncpg://user:pass@host/dbname`).
|
||||
- IDL automation requires a local IDL/ENVI install (Windows) and is managed in `backend/app/idl_service.py`.
|
||||
- Frontend expects backend at `http://localhost:8000`; update CORS in the backend if ports change.
|
||||
- Conda environment for this workspace: `InSAR` (Python 3.10). On this machine, Conda executable is `D:\anaconda3\Scripts\conda.exe`.
|
||||
- For `scripts/start_app.ps1`, prefer Conda-native startup config in `.env`:
|
||||
- `CONDA_EXE=D:\anaconda3\Scripts\conda.exe`
|
||||
- `CONDA_ENV_NAME=InSAR`
|
||||
- If `CONDA_ENV_NAME` is set, startup resolves the target env `python.exe` via Conda, then uses that interpreter for DB check/init/backend/worker.
|
||||
@@ -0,0 +1,769 @@
|
||||
# AI 分析模块重构设计文档
|
||||
|
||||
**版本**: v2.0
|
||||
**日期**: 2026-03-02
|
||||
**作者**: System Architect
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景与目标
|
||||
|
||||
### 1.1 当前问题
|
||||
|
||||
- **架构混乱**: 同步/异步接口混用,用户体验不一致
|
||||
- **Prompt 硬编码**: 无法灵活调整诊断策略
|
||||
- **模型选择僵化**: 用户无法指定模型
|
||||
- **结果存储不规范**: 诊断报告存在 `task.message`,无法检索和分析
|
||||
- **缺少历史管理**: 无法查看、对比、导出历史诊断
|
||||
|
||||
### 1.2 设计目标
|
||||
|
||||
1. **统一异步架构**: 所有 AI 任务进队列,前端轮询状态
|
||||
2. **配置化 Prompt**: 模板化管理,支持自定义
|
||||
3. **灵活模型选择**: 用户可选模型,记住偏好
|
||||
4. **规范化存储**: 独立表管理诊断记录
|
||||
5. **完整历史管理**: 查询、筛选、导出、对比
|
||||
|
||||
---
|
||||
|
||||
## 2. 数据库设计
|
||||
|
||||
### 2.1 核心表:`ai_diagnosis`
|
||||
|
||||
存储所有 AI 诊断记录。
|
||||
|
||||
```sql
|
||||
CREATE TABLE ai_diagnosis (
|
||||
id SERIAL PRIMARY KEY,
|
||||
|
||||
-- 关联信息
|
||||
result_id INTEGER NOT NULL REFERENCES dinsar_results(id) ON DELETE CASCADE,
|
||||
task_id VARCHAR(50), -- 关联 system_tasks,用于追踪任务状态
|
||||
|
||||
-- 模型与配置
|
||||
model_name VARCHAR(100) NOT NULL, -- 如 'qwen3-vl:8b'
|
||||
prompt_template VARCHAR(50) NOT NULL, -- 'quick'/'standard'/'detailed'/'custom'
|
||||
prompt_text TEXT, -- 实际使用的完整 prompt(用于审计)
|
||||
|
||||
-- 诊断结果
|
||||
diagnosis_markdown TEXT NOT NULL, -- Markdown 格式的诊断报告
|
||||
risk_level VARCHAR(20), -- 'LOW'/'MEDIUM'/'HIGH'/NULL
|
||||
confidence_score FLOAT, -- 0.0-1.0,模型自评置信度(可选)
|
||||
|
||||
-- 上下文信息(快照,避免关联查询)
|
||||
result_name VARCHAR(255),
|
||||
date_range VARCHAR(100), -- 如 '20240101-20240115'
|
||||
quality_score FLOAT, -- 当时的 ai_score
|
||||
hazards_found INTEGER DEFAULT 0, -- 覆盖范围内的隐患点数量
|
||||
hazards_snapshot JSONB, -- 隐患点详情快照 [{name, type, location}]
|
||||
|
||||
-- 元数据
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
duration_seconds FLOAT, -- 诊断耗时
|
||||
error_message TEXT, -- 如果失败,记录错误
|
||||
|
||||
-- 索引
|
||||
INDEX idx_result_id (result_id),
|
||||
INDEX idx_created_at (created_at DESC),
|
||||
INDEX idx_risk_level (risk_level)
|
||||
);
|
||||
```
|
||||
|
||||
**设计要点**:
|
||||
- `prompt_text` 存储实际 prompt,便于审计和复现
|
||||
- `hazards_snapshot` 用 JSONB 存快照,避免隐患点被删除后无法回溯
|
||||
- `duration_seconds` 用于性能分析
|
||||
- `error_message` 支持失败记录(不删除,便于调试)
|
||||
|
||||
### 2.2 配置表:`ai_prompt_templates`(可选)
|
||||
|
||||
如果需要 UI 管理 Prompt 模板,可建此表。否则用配置文件即可。
|
||||
|
||||
```sql
|
||||
CREATE TABLE ai_prompt_templates (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(50) UNIQUE NOT NULL, -- 'quick'/'standard'/'detailed'
|
||||
display_name_zh VARCHAR(100),
|
||||
display_name_en VARCHAR(100),
|
||||
template_text TEXT NOT NULL,
|
||||
is_system BOOLEAN DEFAULT FALSE, -- 系统内置模板不可删除
|
||||
created_at TIMESTAMP DEFAULT NOW(),
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
**初始数据**:
|
||||
```sql
|
||||
INSERT INTO ai_prompt_templates (name, display_name_zh, display_name_en, template_text, is_system) VALUES
|
||||
('quick', '快速诊断', 'Quick Diagnosis', '你是 InSAR 专家,用 200 字简述...', TRUE),
|
||||
('standard', '标准诊断', 'Standard Diagnosis', '你是资深专家,按 4 步分析...', TRUE),
|
||||
('detailed', '详细诊断', 'Detailed Diagnosis', '深度分析,包含地形、历史对比...', TRUE);
|
||||
```
|
||||
|
||||
### 2.3 用户偏好表:`user_ai_preferences`(可选)
|
||||
|
||||
存储用户的 AI 偏好设置。
|
||||
|
||||
```sql
|
||||
CREATE TABLE user_ai_preferences (
|
||||
user_id INTEGER PRIMARY KEY REFERENCES users(id),
|
||||
preferred_model VARCHAR(100), -- 如 'qwen3-vl:8b'
|
||||
default_template VARCHAR(50), -- 'standard'
|
||||
auto_diagnose BOOLEAN DEFAULT FALSE, -- 新结果自动诊断
|
||||
updated_at TIMESTAMP DEFAULT NOW()
|
||||
);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 后端 API 设计
|
||||
|
||||
### 3.1 路由结构
|
||||
|
||||
新建 `backend/app/routers/ai_diagnosis.py`,遵循 RESTful 规范。
|
||||
|
||||
#### 3.1.1 诊断记录管理
|
||||
|
||||
**列表查询**
|
||||
```http
|
||||
GET /api/ai-diagnosis?page=1&limit=20&result_id=123&risk_level=HIGH&sort=-created_at
|
||||
```
|
||||
|
||||
**Query 参数**:
|
||||
- `page`, `limit`: 分页
|
||||
- `result_id`: 筛选特定结果的诊断
|
||||
- `risk_level`: 筛选风险等级(`LOW`/`MEDIUM`/`HIGH`)
|
||||
- `model_name`: 筛选模型
|
||||
- `date_from`, `date_to`: 时间范围
|
||||
- `sort`: 排序字段(`-created_at` 表示倒序)
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"total": 156,
|
||||
"page": 1,
|
||||
"limit": 20,
|
||||
"items": [
|
||||
{
|
||||
"id": 42,
|
||||
"result_id": 123,
|
||||
"result_name": "Task_001_20240101_20240115",
|
||||
"model_name": "qwen3-vl:8b",
|
||||
"prompt_template": "standard",
|
||||
"risk_level": "HIGH",
|
||||
"hazards_found": 3,
|
||||
"created_at": "2026-03-02T10:30:00Z",
|
||||
"duration_seconds": 45.2
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**详情查询**
|
||||
```http
|
||||
GET /api/ai-diagnosis/{id}
|
||||
```
|
||||
|
||||
**响��**:
|
||||
```json
|
||||
{
|
||||
"id": 42,
|
||||
"result_id": 123,
|
||||
"result_name": "Task_001_20240101_20240115",
|
||||
"model_name": "qwen3-vl:8b",
|
||||
"prompt_template": "standard",
|
||||
"prompt_text": "你是一位拥有 20 年经验...",
|
||||
"diagnosis_markdown": "## 形态学分析\n\n观察到...",
|
||||
"risk_level": "HIGH",
|
||||
"confidence_score": 0.85,
|
||||
"hazards_found": 3,
|
||||
"hazards_snapshot": [
|
||||
{"name": "XX滑坡", "type": "滑坡", "location": "XX县"}
|
||||
],
|
||||
"quality_score": 0.72,
|
||||
"created_at": "2026-03-02T10:30:00Z",
|
||||
"duration_seconds": 45.2
|
||||
}
|
||||
```
|
||||
|
||||
**创建诊断任务**
|
||||
```http
|
||||
POST /api/ai-diagnosis
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"result_id": 123,
|
||||
"model_name": "qwen3-vl:8b", // 可选,不传则自动选择
|
||||
"prompt_template": "standard" // 'quick'/'standard'/'detailed'/'custom'
|
||||
"custom_prompt": "..." // 仅当 template='custom' 时需要
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"diagnosis_id": 42, // 预创建的记录 ID(状态为 PENDING)
|
||||
"task_id": "task_abc123",
|
||||
"message": "诊断任务已进入队列"
|
||||
}
|
||||
```
|
||||
|
||||
**删除记录**
|
||||
```http
|
||||
DELETE /api/ai-diagnosis/{id}
|
||||
```
|
||||
|
||||
**批量导出**
|
||||
```http
|
||||
POST /api/ai-diagnosis/export
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"ids": [42, 43, 44],
|
||||
"format": "markdown" // 'markdown'/'pdf'/'json'
|
||||
}
|
||||
```
|
||||
|
||||
**响应**:返回文件流或下载链接。
|
||||
|
||||
#### 3.1.2 模型管理
|
||||
|
||||
**获取可用模型**
|
||||
```http
|
||||
GET /api/ai/models
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"ollama_online": true,
|
||||
"models": [
|
||||
{
|
||||
"name": "qwen3-vl:8b",
|
||||
"size": "8.5 GB",
|
||||
"modified_at": "2026-03-01T12:00:00Z",
|
||||
"capabilities": ["vision", "text"],
|
||||
"recommended": true
|
||||
},
|
||||
{
|
||||
"name": "qwen2-vl:72b",
|
||||
"size": "72 GB",
|
||||
"capabilities": ["vision", "text"]
|
||||
}
|
||||
],
|
||||
"default_model": "qwen3-vl:8b"
|
||||
}
|
||||
```
|
||||
|
||||
**预热模型**
|
||||
```http
|
||||
POST /api/ai/models/{model_name}/warmup
|
||||
```
|
||||
|
||||
#### 3.1.3 Prompt 模板管理
|
||||
|
||||
**列表**
|
||||
```http
|
||||
GET /api/ai/prompt-templates
|
||||
```
|
||||
|
||||
**响应**:
|
||||
```json
|
||||
{
|
||||
"templates": [
|
||||
{
|
||||
"name": "quick",
|
||||
"display_name": "快速诊断",
|
||||
"is_system": true
|
||||
},
|
||||
{
|
||||
"name": "custom_001",
|
||||
"display_name": "我的自定义模板",
|
||||
"is_system": false
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**详情**
|
||||
```http
|
||||
GET /api/ai/prompt-templates/{name}
|
||||
```
|
||||
|
||||
**创建/更新**(仅非系统模板)
|
||||
```http
|
||||
POST /api/ai/prompt-templates
|
||||
PUT /api/ai/prompt-templates/{name}
|
||||
|
||||
{
|
||||
"name": "custom_001",
|
||||
"display_name": "我的模板",
|
||||
"template_text": "..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 任务处理流程
|
||||
|
||||
### 4.1 异步任务架构
|
||||
|
||||
**流程图**:
|
||||
```
|
||||
用户点击"诊断"
|
||||
↓
|
||||
前端 POST /api/ai-diagnosis
|
||||
↓
|
||||
后端创建 diagnosis 记录(状态 PENDING)+ system_task + system_job
|
||||
↓
|
||||
返回 diagnosis_id + task_id
|
||||
↓
|
||||
前端轮询 GET /api/tasks/{task_id}
|
||||
↓
|
||||
job_worker 执行 _handle_ai_analyze
|
||||
↓
|
||||
调用 Ollama VLM
|
||||
↓
|
||||
更新 diagnosis 记录(状态 COMPLETED,写入 markdown)
|
||||
↓
|
||||
更新 system_task(状态 COMPLETED)
|
||||
↓
|
||||
前端检测到完成,跳转到诊断详情页
|
||||
```
|
||||
|
||||
### 4.2 Handler 改造
|
||||
|
||||
`backend/app/services/job_handlers.py` 中的 `_handle_ai_analyze` 改为:
|
||||
|
||||
```python
|
||||
async def _handle_ai_analyze(job: SystemJobORM) -> None:
|
||||
payload = job.payload or {}
|
||||
diagnosis_id = payload.get("diagnosis_id")
|
||||
|
||||
if not diagnosis_id:
|
||||
raise ValueError("AI_ANALYZE requires diagnosis_id")
|
||||
|
||||
async with AsyncSessionLocal() as db:
|
||||
# 1. 查询 diagnosis 记录
|
||||
diag = await db.get(AiDiagnosisORM, diagnosis_id)
|
||||
if not diag:
|
||||
raise ValueError(f"Diagnosis {diagnosis_id} not found")
|
||||
|
||||
# 2. 查询关联的 result
|
||||
result = await db.get(DinsarResultORM, diag.result_id)
|
||||
|
||||
# 3. 查询隐患点(PostGIS)
|
||||
hazards = await db.execute(
|
||||
select(HazardPointORM).where(ST_Covers(result.geom, HazardPointORM.geom))
|
||||
)
|
||||
hazards = hazards.scalars().all()
|
||||
|
||||
# 4. 准备图片
|
||||
img_path = data_service.get_dinsar_cache_path(result.id, result.name)
|
||||
img_base64 = _load_image_as_base64(img_path)
|
||||
|
||||
# 5. 构建 prompt
|
||||
prompt = _build_prompt(diag.prompt_template, diag.prompt_text, result, hazards)
|
||||
|
||||
# 6. 调用 VLM
|
||||
start_time = time.time()
|
||||
try:
|
||||
analysis = await analyze_map_with_vlm(
|
||||
images_base64=[img_base64],
|
||||
prompt=prompt,
|
||||
model_name=diag.model_name
|
||||
)
|
||||
duration = time.time() - start_time
|
||||
|
||||
# 7. 解析风险等级(从 Markdown 中提取)
|
||||
risk_level = _extract_risk_level(analysis)
|
||||
|
||||
# 8. 更新 diagnosis 记录
|
||||
diag.diagnosis_markdown = analysis
|
||||
diag.risk_level = risk_level
|
||||
diag.hazards_found = len(hazards)
|
||||
diag.hazards_snapshot = [
|
||||
{"name": h.hazard_name, "type": h.hazard_type, "location": f"{h.city}{h.county}"}
|
||||
for h in hazards
|
||||
]
|
||||
diag.duration_seconds = duration
|
||||
await db.commit()
|
||||
|
||||
# 9. 更新 task 状态
|
||||
await task_service.update_task(
|
||||
job.task_id,
|
||||
status="COMPLETED",
|
||||
message=f"诊断完成,风险等级: {risk_level}",
|
||||
progress=100
|
||||
)
|
||||
except Exception as e:
|
||||
diag.error_message = str(e)
|
||||
await db.commit()
|
||||
await task_service.update_task(
|
||||
job.task_id,
|
||||
status="FAILED",
|
||||
message=f"诊断失败: {str(e)}"
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 前端设计
|
||||
|
||||
### 5.1 组件结构
|
||||
|
||||
```
|
||||
src/
|
||||
├── panels/
|
||||
│ └── AiAnalysisPanel.jsx # 新增:AI 分析主面板
|
||||
├── components/
|
||||
│ ├── AiDiagnosisTable.jsx # 诊断历史表格
|
||||
│ ├── AiDiagnosisDetailModal.jsx # 诊断详情 Modal
|
||||
│ ├── AiModelSelector.jsx # 模型选择器
|
||||
│ ├── AiPromptTemplateEditor.jsx # Prompt 模板编辑器
|
||||
│ └── AiQuickDiagnoseCard.jsx # 快速诊断卡片
|
||||
├── api/
|
||||
│ └── aiDiagnosis.js # API 封装
|
||||
└── hooks/
|
||||
└── useAiDiagnosis.js # 自定义 Hook
|
||||
```
|
||||
|
||||
### 5.2 AiAnalysisPanel 布局
|
||||
|
||||
```jsx
|
||||
<div className="ai-analysis-panel">
|
||||
{/* 顶部状态卡片 */}
|
||||
<div className="ai-status-cards">
|
||||
<OllamaStatusCard />
|
||||
<ModelSelectorCard />
|
||||
</div>
|
||||
|
||||
{/* 快速诊断 */}
|
||||
<AiQuickDiagnoseCard />
|
||||
|
||||
{/* 诊断历史 */}
|
||||
<div className="ai-diagnosis-history">
|
||||
<div className="history-toolbar">
|
||||
<input placeholder="搜索结果名称..." />
|
||||
<select>
|
||||
<option>全部风险等级</option>
|
||||
<option>高风险</option>
|
||||
<option>中风险</option>
|
||||
<option>低风险</option>
|
||||
</select>
|
||||
<button>导出选中</button>
|
||||
</div>
|
||||
<AiDiagnosisTable />
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
### 5.3 核心交互流程
|
||||
|
||||
**快速诊断**:
|
||||
1. 用户在下拉框选择 D-InSAR 结果
|
||||
2. 选择诊断模式(快速/标准/详细)
|
||||
3. 点击"开始诊断"
|
||||
4. 前端 POST `/api/ai-diagnosis`,获得 `task_id`
|
||||
5. 显示进度条,轮询 `/api/tasks/{task_id}`
|
||||
6. 完成后自动打开详情 Modal
|
||||
|
||||
**查看历史**:
|
||||
1. 表格显示所有诊断记录(分页)
|
||||
2. 点击行打开详情 Modal
|
||||
3. Modal 显示完整 Markdown 报告(支持复制、导出)
|
||||
|
||||
**模型管理**:
|
||||
1. 顶部卡片显示当前选中模型
|
||||
2. 点击"切换模型"打开下拉列表
|
||||
3. 选择后保存到 localStorage(或用户配置)
|
||||
|
||||
---
|
||||
|
||||
## 6. 配置管理
|
||||
|
||||
### 6.1 Prompt 模板文件
|
||||
|
||||
`backend/app/ai_prompts.py`:
|
||||
|
||||
```python
|
||||
PROMPT_TEMPLATES = {
|
||||
'quick': {
|
||||
'name_zh': '快速诊断',
|
||||
'name_en': 'Quick Diagnosis',
|
||||
'template': """
|
||||
你是 InSAR 专家。请用 200 字简述以下形变图的关键发现:
|
||||
- 是否存在明显形变区?
|
||||
- 与已知隐患点是否吻合?
|
||||
- 风险等级(低/中/高)?
|
||||
|
||||
背景:{context}
|
||||
""",
|
||||
},
|
||||
'standard': {
|
||||
'name_zh': '标准诊断',
|
||||
'name_en': 'Standard Diagnosis',
|
||||
'template': """
|
||||
你是拥有 20 年经验的 InSAR 专家。请按以下步骤分析:
|
||||
1. 形态学分析
|
||||
2. 时空一致性
|
||||
3. 风险演化预测
|
||||
4. 综合风险评级
|
||||
|
||||
背景:{context}
|
||||
隐患点:{hazards}
|
||||
""",
|
||||
},
|
||||
'detailed': {
|
||||
'name_zh': '详细诊断',
|
||||
'name_en': 'Detailed Diagnosis',
|
||||
'template': """
|
||||
深度分析,包含:
|
||||
- 地形地貌分析
|
||||
- 历史形变趋势
|
||||
- 潜在触发因素
|
||||
- 监测建议
|
||||
|
||||
背景:{context}
|
||||
""",
|
||||
},
|
||||
}
|
||||
|
||||
def build_prompt(template_name: str, context: dict) -> str:
|
||||
template = PROMPT_TEMPLATES.get(template_name, PROMPT_TEMPLATES['standard'])
|
||||
return template['template'].format(**context)
|
||||
```
|
||||
|
||||
### 6.2 环境变量
|
||||
|
||||
`.env` 新增:
|
||||
```bash
|
||||
# Ollama 配置
|
||||
OLLAMA_API_URL=http://127.0.0.1:11434/api/generate
|
||||
OLLAMA_TIMEOUT=600
|
||||
OLLAMA_DEFAULT_MODEL=qwen3-vl:8b
|
||||
|
||||
# AI 诊断配置
|
||||
AI_DIAGNOSIS_AUTO_RETRY=true
|
||||
AI_DIAGNOSIS_MAX_RETRIES=2
|
||||
AI_DIAGNOSIS_STREAM_OUTPUT=false # 未来支持流式
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 成熟方案参考
|
||||
|
||||
### 7.1 Prompt 管理
|
||||
|
||||
参考 **LangChain PromptTemplate**:
|
||||
- 支持变量插值 `{variable}`
|
||||
- 支持条件渲染 `{% if condition %}`
|
||||
- 支持模板继承
|
||||
|
||||
可引入 `Jinja2` 作为模板引擎:
|
||||
```python
|
||||
from jinja2 import Template
|
||||
|
||||
template = Template(PROMPT_TEMPLATES['standard']['template'])
|
||||
prompt = template.render(context=context, hazards=hazards)
|
||||
```
|
||||
|
||||
### 7.2 任务队列
|
||||
|
||||
当前使用自研的 `job_queue_service`,可考虑迁移到:
|
||||
- **Celery** + Redis:成熟的分布式任务队列
|
||||
- **Dramatiq**:轻量级替代方案
|
||||
- **ARQ**:基于 asyncio 的现代方案
|
||||
|
||||
### 7.3 诊断报告导出
|
||||
|
||||
- **Markdown → PDF**:使用 `weasyprint` 或 `pdfkit`
|
||||
- **Markdown → Word**:使用 `python-docx` + `markdown` 解析
|
||||
- **模板化报告**:使用 `Jinja2` + HTML 模板
|
||||
|
||||
### 7.4 前端状态管理
|
||||
|
||||
如果 AI 功能复杂度增加,可考虑:
|
||||
- **Zustand**(已使用):继续用 `useAiStore`
|
||||
- **React Query**:管理服务端状态(诊断列表、模型列表)
|
||||
|
||||
---
|
||||
|
||||
## 8. 迁移计划
|
||||
|
||||
### 8.1 数据迁移
|
||||
|
||||
**Step 1**:创建新表
|
||||
```bash
|
||||
alembic revision -m "add_ai_diagnosis_table"
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
**Step 2**:迁移历史数据(可选)
|
||||
从 `system_tasks` 的 `message` 字段提取历史诊断,写入新表:
|
||||
```python
|
||||
# 迁移脚本
|
||||
async def migrate_old_diagnoses():
|
||||
tasks = await db.execute(
|
||||
select(SystemTaskORM).where(
|
||||
SystemTaskORM.task_type == 'AI_ANALYZE',
|
||||
SystemTaskORM.status == 'COMPLETED'
|
||||
)
|
||||
)
|
||||
for task in tasks.scalars():
|
||||
msg = json.loads(task.message)
|
||||
diagnosis = AiDiagnosisORM(
|
||||
result_id=msg['result_id'],
|
||||
diagnosis_markdown=msg['analysis'],
|
||||
created_at=task.completed_at,
|
||||
...
|
||||
)
|
||||
db.add(diagnosis)
|
||||
await db.commit()
|
||||
```
|
||||
|
||||
### 8.2 API 兼容性
|
||||
|
||||
**向后兼容**:
|
||||
- 保留旧接口 `/ai/analyze-result/{id}` 3 个月
|
||||
- 内部重定向到新接口
|
||||
- 响应格式保持一致
|
||||
|
||||
**废弃通知**:
|
||||
```json
|
||||
{
|
||||
"message": "诊断任务已进入队列",
|
||||
"task_id": "...",
|
||||
"diagnosis_id": 42,
|
||||
"_deprecated": "此接口将在 2026-06-01 废弃,请使用 POST /api/ai-diagnosis"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 性能优化
|
||||
|
||||
### 9.1 缓存策略
|
||||
|
||||
- **模型列表缓存**:`GET /api/ai/models` 结果缓存 5 分钟(Redis)
|
||||
- **Prompt 模板缓存**:启动时加载到内存
|
||||
- **诊断列表分页**:使用游标分页(`cursor-based`)而非 offset
|
||||
|
||||
### 9.2 并发控制
|
||||
|
||||
- **Ollama 并发限制**:同时最多 2 个 VLM 任务(避免显存溢出)
|
||||
- **任务优先级**:用户手动触发 > 自动诊断
|
||||
|
||||
### 9.3 超时与重试
|
||||
|
||||
```python
|
||||
@retry(
|
||||
stop=stop_after_attempt(3),
|
||||
wait=wait_exponential(multiplier=1, min=4, max=60),
|
||||
retry=retry_if_exception_type(httpx.TimeoutException)
|
||||
)
|
||||
async def call_ollama_with_retry(...):
|
||||
...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 安全考虑
|
||||
|
||||
### 10.1 输入验证
|
||||
|
||||
- **Prompt 注入防护**:限制自定义 prompt 长度(8000 字符)
|
||||
- **模型名称白名单**:只允许已知模型
|
||||
- **结果 ID 权限检查**:确保用户有权访问该结果
|
||||
|
||||
### 10.2 输出过滤
|
||||
|
||||
- **敏感信息脱敏**:诊断报告中的坐标、地址模糊化
|
||||
- **XSS 防护**:Markdown 渲染时使用 `DOMPurify`
|
||||
|
||||
---
|
||||
|
||||
## 11. 监控与日志
|
||||
|
||||
### 11.1 关键指标
|
||||
|
||||
- **诊断成功率**:`completed / (completed + failed)`
|
||||
- **平均耗时**:按模型、模板统计
|
||||
- **模型使用分布**:哪个模型最受欢迎
|
||||
- **风险等级分布**:高/中/低风险占比
|
||||
|
||||
### 11.2 日志记录
|
||||
|
||||
```python
|
||||
logger.info(
|
||||
"AI diagnosis completed",
|
||||
extra={
|
||||
"diagnosis_id": diag.id,
|
||||
"result_id": diag.result_id,
|
||||
"model": diag.model_name,
|
||||
"duration": diag.duration_seconds,
|
||||
"risk_level": diag.risk_level,
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 12. 未来扩展
|
||||
|
||||
### 12.1 多模态输入
|
||||
|
||||
- 支持上传多张图片(时序对比)
|
||||
- 支持附加文本描述(用户观察)
|
||||
|
||||
### 12.2 对比分析
|
||||
|
||||
- 同一结果的多次诊断对比
|
||||
- 不同模型的诊断结果对比
|
||||
|
||||
### 12.3 自动化工作流
|
||||
|
||||
- 新结果自动触发诊断
|
||||
- 高风险自动发送邮件/钉钉通知
|
||||
|
||||
### 12.4 Fine-tuning
|
||||
|
||||
- 收集用户反馈(诊断是否准确)
|
||||
- 定期 fine-tune 模型
|
||||
|
||||
---
|
||||
|
||||
## 13. 总结
|
||||
|
||||
### 13.1 核心改进
|
||||
|
||||
| 维度 | 当前 | 重构后 |
|
||||
|------|------|--------|
|
||||
| 架构 | 同步/异步混用 | 统一异步任务 |
|
||||
| Prompt | 硬编码 | 模板化 + 可自定义 |
|
||||
| 模型 | 自动选择 | 用户可选 + 偏好记忆 |
|
||||
| 存储 | task.message | 独立表 + 完整字段 |
|
||||
| 历史 | 无法查询 | 完整 CRUD + 导出 |
|
||||
|
||||
### 13.2 实施优先级
|
||||
|
||||
**P0(必须)**:
|
||||
- 创建 `ai_diagnosis` 表
|
||||
- 改造 `_handle_ai_analyze`
|
||||
- 实现基础 CRUD API
|
||||
- 创建 `AiAnalysisPanel` 前端面板
|
||||
|
||||
**P1(重要)**:
|
||||
- Prompt 模板管理
|
||||
- 模型选择器
|
||||
- 诊断详情 Modal
|
||||
- 导出功能
|
||||
|
||||
**P2(可选)**:
|
||||
- 用户偏好保存
|
||||
- 对比分析
|
||||
- 自动化工作流
|
||||
|
||||
---
|
||||
|
||||
**文档结束**
|
||||
@@ -0,0 +1,389 @@
|
||||
# 前端锁定问题分析(2026-03-05)
|
||||
|
||||
## 问题描述
|
||||
|
||||
用户报告在执行以下操作时前端没有锁定:
|
||||
1. D-InSAR 结果扫描
|
||||
2. 数据分发(复制任务)
|
||||
|
||||
## 根本原因分析
|
||||
|
||||
### 问题 1:扫描任务前端未锁定
|
||||
|
||||
**原因**:任务执行太快(< 3 秒),前端轮询间隔(3 秒)来不及捕获。
|
||||
|
||||
**证据**:
|
||||
```
|
||||
[性能] Footprint 提取耗时: 1622.1ms (文件: Task_20250120_20250217_2_geo_disp)
|
||||
```
|
||||
单个文件处理只需 1.6 秒,如果只有少量文件,整个扫描任务可能在 2-3 秒内完成。
|
||||
|
||||
**前端轮询逻辑**:
|
||||
```javascript
|
||||
// useGlobalTaskControl.js
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
syncActiveTasks(); // 每 3 秒轮询一次
|
||||
}, 3000);
|
||||
return () => clearInterval(interval);
|
||||
}, []);
|
||||
```
|
||||
|
||||
**时间线**:
|
||||
```
|
||||
T=0s: 用户点击"扫描"按钮
|
||||
T=0.1s: 后端创建 SystemTask (status=PENDING)
|
||||
T=0.2s: job_worker 开始执行,更新为 RUNNING
|
||||
T=2.0s: 扫描完成,更新为 COMPLETED
|
||||
T=3.0s: 前端第一次轮询,任务已经完成
|
||||
```
|
||||
|
||||
**结论**:前端来不及捕获 RUNNING 状态,直接看到 COMPLETED。
|
||||
|
||||
---
|
||||
|
||||
### 问题 2:数据分发任务前端未锁定
|
||||
|
||||
**根本原因**:`DataCopierPanel` 没有调用全局锁定机制。
|
||||
|
||||
**证据**:
|
||||
|
||||
1. **App.jsx 中的调用对比**:
|
||||
|
||||
```javascript
|
||||
// IDLAutomationPanel - 有 onJobQueued 回调 ✅
|
||||
<IDLAutomationPanel
|
||||
apiEndpoint={apiClient.defaults.baseURL}
|
||||
readOnly={isReadOnlyUser}
|
||||
onJobQueued={(taskId) => handleTaskStart(taskId, '任务已入队,等待处理...')}
|
||||
/>
|
||||
|
||||
// DataMonitorPanel - 有 onTaskStart 回调 ✅
|
||||
<DataMonitorPanel
|
||||
apiEndpoint={apiClient.defaults.baseURL}
|
||||
onTaskStart={handleTaskStart}
|
||||
readOnly={isReadOnlyUser}
|
||||
/>
|
||||
|
||||
// DataCopierPanel - 没有任何回调 ❌
|
||||
<DataCopierPanel
|
||||
apiEndpoint={apiClient.defaults.baseURL}
|
||||
readOnly={isReadOnlyUser}
|
||||
/>
|
||||
```
|
||||
|
||||
2. **DataCopierPanel 内部逻辑**:
|
||||
|
||||
```javascript
|
||||
// DataCopierPanel.jsx:97-133
|
||||
const handleStartCopy = async () => {
|
||||
// ... 验证逻辑 ...
|
||||
|
||||
setIsUploading(true);
|
||||
setLogs([]);
|
||||
setStatus('RUNNING'); // 只更新组件内部状态
|
||||
|
||||
try {
|
||||
const response = await axios.post(endpoint, {
|
||||
batch_id: selectedBatchId,
|
||||
dest_dir: destDir,
|
||||
copy_statuses: copyStatuses,
|
||||
}, { withCredentials: true });
|
||||
setTaskId(response.data.task_id); // 只保存 task_id
|
||||
// ❌ 没有调用 onJobQueued 或 onTaskStart
|
||||
} catch (error) {
|
||||
// ... 错误处理 ...
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
3. **组件自己管理状态**:
|
||||
|
||||
`DataCopierPanel` 使用自己的状态管理(`status`、`taskId`、`logs`),通过轮询 `/tools/copy-status/${taskId}` 获取进度,**完全独立于全局任务控制系统**。
|
||||
|
||||
```javascript
|
||||
// DataCopierPanel.jsx:82-95
|
||||
const fetchLogs = async () => {
|
||||
if (!taskId) return;
|
||||
try {
|
||||
const response = await axios.get(`${apiEndpoint}/tools/copy-status/${taskId}`, { withCredentials: true });
|
||||
setLogs(response.data.logs);
|
||||
const nextStatus = normalizeStatus(response.data.status);
|
||||
if (nextStatus && nextStatus !== 'UNKNOWN') {
|
||||
setStatus(nextStatus); // 只更新组件内部状态
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to load logs:', error);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
**结论**:`DataCopierPanel` 是一个独立的任务管理系统,不与全局锁定机制集成。
|
||||
|
||||
---
|
||||
|
||||
## 架构对比
|
||||
|
||||
### 全局锁定机制(IDLAutomationPanel、DataMonitorPanel)
|
||||
|
||||
```
|
||||
用户操作 → 组件调用 API → 后端创建 SystemTask
|
||||
↓
|
||||
组件调用 onJobQueued(taskId)
|
||||
↓
|
||||
App.jsx 调用 handleTaskStart(taskId)
|
||||
↓
|
||||
setPendingTaskIds([...prev, taskId])
|
||||
↓
|
||||
useGlobalTaskControl 轮询 /tasks/active
|
||||
↓
|
||||
检测到 RUNNING 任务 → setIsGlobalLocked(true)
|
||||
↓
|
||||
任务完成 → setIsGlobalLocked(false)
|
||||
```
|
||||
|
||||
### 独立任务管理(DataCopierPanel)
|
||||
|
||||
```
|
||||
用户操作 → 组件调用 API → 后端创建 SystemTask
|
||||
↓
|
||||
组件保存 taskId(内部状态)
|
||||
↓
|
||||
组件轮询 /tools/copy-status/${taskId}
|
||||
↓
|
||||
更新组件内部状态(status、logs)
|
||||
↓
|
||||
❌ 不触发全局锁定
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 影响评估
|
||||
|
||||
### 扫描任务未锁定
|
||||
|
||||
**影响等级**:🟢 低
|
||||
|
||||
**原因**:
|
||||
- 扫描任务通常很快(< 3 秒)
|
||||
- 扫描是只读操作,不会修改数据
|
||||
- 即使用户在扫描时操作,也不太可能造成冲突
|
||||
|
||||
**是否需要修复**:可选
|
||||
- 如果希望用户明确感知到扫描正在进行,可以修复
|
||||
- 如果接受"快速任务不锁定"的行为,可以不修复
|
||||
|
||||
---
|
||||
|
||||
### 数据分发任务未锁定
|
||||
|
||||
**影响等级**:🟡 中等
|
||||
|
||||
**原因**:
|
||||
- 数据分发任务可能耗时较长(几分钟到几十分钟)
|
||||
- 用户可能在分发过程中启动其他任务
|
||||
- 可能导致资源竞争(磁盘 I/O、网络带宽)
|
||||
|
||||
**潜在问题**:
|
||||
1. 用户可能同时启动多个分发任务,导致磁盘 I/O 饱和
|
||||
2. 用户可能在分发过程中启动 ENVI 工作流,导致系统卡顿
|
||||
3. 用户可能不知道分发任务正在后台运行
|
||||
|
||||
**是否需要修复**:建议修复
|
||||
- 数据分发是长时间运行的任务
|
||||
- 应该与全局锁定机制集成
|
||||
- 提升用户体验和系统稳定性
|
||||
|
||||
---
|
||||
|
||||
## 修复方案
|
||||
|
||||
### 方案 A:集成到全局锁定机制(推荐)
|
||||
|
||||
**修改文件**:
|
||||
1. `App.jsx`
|
||||
2. `DataCopierPanel.jsx`
|
||||
|
||||
**步骤**:
|
||||
|
||||
1. **在 App.jsx 中传递回调**:
|
||||
|
||||
```javascript
|
||||
{leftPanelTab === 'copier' && (
|
||||
<div className="panel-content" style={{ flex: '1 1 auto', padding: 0, overflow: 'hidden' }}>
|
||||
<DataCopierPanel
|
||||
apiEndpoint={apiClient.defaults.baseURL}
|
||||
readOnly={isReadOnlyUser}
|
||||
onJobQueued={(taskId) => handleTaskStart(taskId, '数据分发任务已入队')} // 添加此行
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
2. **在 DataCopierPanel.jsx 中接收并调用回调**:
|
||||
|
||||
```javascript
|
||||
// 修改组件签名
|
||||
const DataCopierPanel = ({ apiEndpoint, readOnly = false, onJobQueued }) => {
|
||||
// ... 现有代码 ...
|
||||
|
||||
const handleStartCopy = async () => {
|
||||
// ... 验证逻辑 ...
|
||||
|
||||
setIsUploading(true);
|
||||
setLogs([]);
|
||||
setStatus('RUNNING');
|
||||
|
||||
try {
|
||||
const response = await axios.post(endpoint, {
|
||||
batch_id: selectedBatchId,
|
||||
dest_dir: destDir,
|
||||
copy_statuses: copyStatuses,
|
||||
}, { withCredentials: true });
|
||||
|
||||
const taskId = response.data.task_id;
|
||||
setTaskId(taskId);
|
||||
|
||||
// 调用全局锁定回调
|
||||
if (onJobQueued) {
|
||||
onJobQueued(taskId);
|
||||
}
|
||||
} catch (error) {
|
||||
// ... 错误处理 ...
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
};
|
||||
};
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- 与现有架构一致
|
||||
- 最小改动
|
||||
- 复用全局锁定机制
|
||||
|
||||
**缺点**:
|
||||
- 组件仍然保留自己的状态管理(有一定冗余)
|
||||
|
||||
---
|
||||
|
||||
### 方案 B:完全重构为全局任务管理
|
||||
|
||||
**修改范围**:大
|
||||
|
||||
**步骤**:
|
||||
1. 移除 `DataCopierPanel` 的内部状态管理
|
||||
2. 使用全局 `activeTasks` 和 `ActiveTasksOverlay` 显示进度
|
||||
3. 移除组件内的轮询逻辑
|
||||
|
||||
**优点**:
|
||||
- 架构统一
|
||||
- 减少代码冗余
|
||||
|
||||
**缺点**:
|
||||
- 改动较大
|
||||
- 可能影响现有功能
|
||||
- 需要充分测试
|
||||
|
||||
---
|
||||
|
||||
### 方案 C:保持现状,添加警告提示
|
||||
|
||||
**修改文件**:`DataCopierPanel.jsx`
|
||||
|
||||
**步骤**:
|
||||
在任务运行时显示警告提示:
|
||||
|
||||
```javascript
|
||||
{status === 'RUNNING' && (
|
||||
<div className="warning-banner">
|
||||
⚠️ 数据分发任务正在后台运行,请勿关闭浏览器或启动其他耗时任务
|
||||
</div>
|
||||
)}
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- 改动最小
|
||||
- 不影响现有架构
|
||||
|
||||
**缺点**:
|
||||
- 不解决根本问题
|
||||
- 用户仍可能启动冲突任务
|
||||
|
||||
---
|
||||
|
||||
## 扫描任务修复方案(可选)
|
||||
|
||||
### 方案 A:前端立即锁定
|
||||
|
||||
在提交扫描任务后立即锁定,不等待轮询:
|
||||
|
||||
```javascript
|
||||
// DataManagementPanel.jsx
|
||||
const handleScanDinsarResults = async () => {
|
||||
try {
|
||||
// 立即锁定前端
|
||||
if (onTaskStart) {
|
||||
onTaskStart(null, 'SCAN_DINSAR', '正在扫描 D-InSAR 结果...');
|
||||
}
|
||||
|
||||
const response = await scanDinsarResults(selectedDirs);
|
||||
const taskId = response.data.task_id;
|
||||
|
||||
// 更新任务 ID
|
||||
if (onTaskStart) {
|
||||
onTaskStart(taskId, 'SCAN_DINSAR', '正在扫描 D-InSAR 结果...');
|
||||
}
|
||||
|
||||
addLog('info', `扫描任务已提交: ${taskId}`);
|
||||
} catch (error) {
|
||||
// 解锁前端
|
||||
setIsGlobalLocked(false);
|
||||
addLog('error', `扫描失败: ${error.message}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 方案 B:后端添加最小执行时间
|
||||
|
||||
```python
|
||||
# job_handlers.py
|
||||
async def _handle_scan_dinsar(job: SystemJobORM) -> None:
|
||||
start_time = time.time()
|
||||
|
||||
# ... 执行扫描 ...
|
||||
|
||||
# 确保任务至少运行 2 秒
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed < 2.0:
|
||||
await asyncio.sleep(2.0 - elapsed)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 推荐修复顺序
|
||||
|
||||
1. **优先修复数据分发任务**(方案 A)
|
||||
- 影响较大
|
||||
- 修改简单
|
||||
- 风险低
|
||||
|
||||
2. **可选修复扫描任务**(方案 A)
|
||||
- 影响较小
|
||||
- 提升用户体验
|
||||
- 风险低
|
||||
|
||||
3. **长期考虑重构**(方案 B)
|
||||
- 统一架构
|
||||
- 减少冗余
|
||||
- 需要充分测试
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
- **扫描任务未锁定**:任务太快,轮询来不及捕获(影响小,可选修复)
|
||||
- **数据分发任务未锁定**:组件独立管理状态,未集成全局锁定(影响中,建议修复)
|
||||
- **推荐方案**:为 `DataCopierPanel` 添加 `onJobQueued` 回调,集成到全局锁定机制
|
||||
@@ -0,0 +1,150 @@
|
||||
# IDL/ENVI + 系统集成技术备忘录
|
||||
|
||||
> 更新日期: 2026-02-23
|
||||
|
||||
## 一、架构概览
|
||||
|
||||
```
|
||||
前端 (React) → FastAPI → Job Queue (PostgreSQL) → Job Worker → 子进程 (envi_runner_cli) → envipyengine → ENVI/SARscape
|
||||
```
|
||||
|
||||
- 前端提交任务 → 创建 Task + Job 记录 → Worker 领取 Job → 启动独立 Python 子进程执行 ENVI 工作流
|
||||
- 子进程通过 progress JSON 文件向父进程报告进度
|
||||
- 父进程监控子进程存活状态,同时通过 `_task_keepalive` 协程每 30 秒刷新 Task 的 `updated_at` 防止僵尸检测误杀
|
||||
- 前端有两层锁: IDLAutomationPanel 内的黄色横幅 + App.jsx 的全局蓝色遮罩
|
||||
|
||||
## 二、已踩的坑与解决方案
|
||||
|
||||
### 1. subprocess.PIPE 缓冲区死锁
|
||||
|
||||
**现象**: 子进程挂起,不再输出
|
||||
**原因**: Windows 上 `subprocess.Popen(stdout=PIPE)` 的管道缓冲区约 4KB,envipyengine 输出超过后阻塞
|
||||
**方案**: 使用 `tempfile.mkstemp()` 创建临时文件接收 stdout/stderr,子进程退出后读取
|
||||
|
||||
### 2. Windows TemporaryFile 句柄不可继承
|
||||
|
||||
**现象**: `Popen` 无法写入 `tempfile.TemporaryFile` 创建的文件
|
||||
**原因**: Windows 上 `TemporaryFile` 创建的句柄默认不可被子进程继承
|
||||
**方案**: 改用 `tempfile.mkstemp()` + `os.close(fd)` + 手动 `os.unlink()` 清理
|
||||
|
||||
### 3. envipyengine 提前返回(文件未写完)
|
||||
|
||||
**现象**: `task.execute()` 返回后,ENVI 仍在写输出文件(特别是 step 6 的 `_rsp_disp` 文件)
|
||||
**原因**: envipyengine 在 taskengine 返回后立即返回 Python,但 ENVI 后台仍在写大文件
|
||||
**方案**:
|
||||
- 在 step 6 后(无论成功/异常)调用 `_wait_for_disp_stable()` 等待 `*_rsp_disp` 文件出现并稳定
|
||||
- 最后调用 `_wait_files_stable()` 确认所有文件写完
|
||||
- 稳定判定: 连续 3 轮(每轮 15 秒)文件大小不变
|
||||
|
||||
### 4. envipyengine 报错但文件已生成
|
||||
|
||||
**现象**: `task.execute()` 抛出 "outputs not generated" 异常,但实际文件已正确生成
|
||||
**原因**: envipyengine 的输出验证逻辑与 SARscape 实际行为不一致(特别是 step 3 Orbital Trend Removal)
|
||||
**方案**: except 块中扫描输出目录,按文件名模式(如 `*ISARRRPF*.sml`)查找已生成的文件,构建 SARSCAPEDATA 字典继续后续步骤
|
||||
|
||||
### 5. envipyengine 永久挂起
|
||||
|
||||
**现象**: `task.execute()` 永远不返回,即使 ENVI 已完成处理
|
||||
**原因**: envipyengine 内部的 taskengine 进程未正确退出
|
||||
**方案**: 使用 `ThreadPoolExecutor` 包装 `task.execute()`,设置超时 `ENVI_TASK_TIMEOUT_SECONDS=14400`(4小时),超时后抛出异常,由文件扫描逻辑兜底
|
||||
|
||||
### 6. 子进程文件活动监控误杀
|
||||
|
||||
**现象**: 前端提前解锁,ENVI 进程仍在运行
|
||||
**原因**: `job_handlers.py` 的监控循环检测到进度文件和输出目录超过 `ENVI_FILE_STALE_SECONDS` 无变化,kill 了子进程。某些步骤(如 Phase Unwrapping)长时间在内存中计算不写文件
|
||||
**方案**: 将 `ENVI_FILE_STALE_SECONDS` 从 600 增大到 14400,与任务超时一致
|
||||
|
||||
### 7. Task 僵尸检测误杀(根本原因)
|
||||
|
||||
**现象**: 前端在 ENVI 处理过程中解锁
|
||||
**原因**: `task_service.get_active_tasks()` 内置僵尸检测——每次前端轮询时检查 RUNNING 任务的 `updated_at`,超过 `TASK_TIMEOUT_MINUTES=60` 分钟未更新则标记为 FAILED。子进程运行期间 handler 没有更新 task 的 `updated_at`,而单个 step 可能超过 60 分钟
|
||||
**方案**: 在 `_run_envi_workflow_job` 中添加 `_task_keepalive` 协程,每 30 秒读取进度文件并调用 `task_service.update_task()` 刷新 `updated_at`,同时更新进度百分比(step 映射到 10%-90%)和步骤信息
|
||||
|
||||
### 8. 强制解锁按钮被全局遮罩遮挡
|
||||
|
||||
**现象**: 强制解锁按钮写在 IDLAutomationPanel 的黄色横幅里,但全局蓝色遮罩(`App.jsx` 的 `global-task-overlay`)盖住了整个页面,用户无法点击
|
||||
**原因**: 系统有两层锁——IDLAutomationPanel 内部的 `isLocked` 控制按钮禁用,App.jsx 的 `isGlobalLocked` 控制全屏蓝色遮罩。强制解锁只加在了内层
|
||||
**方案**: 在 `App.jsx` 的全局遮罩上添加「管理员强制解锁」按钮(仅管理员可见),点击后展开密码输入框,确认后取消所有活跃任务并解锁。同时保留 IDLAutomationPanel 内的强制解锁作为备用
|
||||
|
||||
## 三、当前超时/保护参数
|
||||
|
||||
| 参数 | 值 | 位置 | 作用 |
|
||||
|------|-----|------|------|
|
||||
| `ENVI_TASK_TIMEOUT_SECONDS` | 14400 (4h) | `.env` | 单步 `task.execute()` 最大等待 |
|
||||
| `ENVI_FILE_STALE_SECONDS` | 14400 (4h) | `.env` | 子进程无文件活动的 kill 阈值 |
|
||||
| `JOB_WORKER_STALE_RUNNING_SECONDS` | 7200 (2h) | `.env` | Job 心跳超时(每 5s 更新) |
|
||||
| `TASK_TIMEOUT_MINUTES` | 60 | `task_service.py` | 僵尸任务检测阈值(keepalive 每 30s 刷新) |
|
||||
| `ENVI_STABILITY_CHECK_INTERVAL` | 15s | `.env` | 文件稳定检查间隔 |
|
||||
| `ENVI_STABILITY_ROUNDS` | 3 | `.env` | 连续稳定轮数 |
|
||||
| `ENVI_STABILITY_MAX_WAIT` | 3600 (1h) | `.env` | 文件稳定等待上限 |
|
||||
| `IDL_JOB_MAX_ATTEMPTS` | 1 | `.env` | 不重试,失败即终止 |
|
||||
|
||||
## 四、自定义 D-InSAR 6 步流程
|
||||
|
||||
| 步骤 | Task 名称 | 典型耗时 | 已知问题 |
|
||||
|------|-----------|----------|----------|
|
||||
| 1. 干涉图生成 | `SARsInSARInterferogramGeneration` | ~900s | 无 |
|
||||
| 2. 滤波+相干性 | `SARsInSARFilterAndCoherence` | ~400s | 无 |
|
||||
| 3. 轨道趋势去除 | `SARsInSARRemoveResidualPhaseFrequency` | ~65s | 报 "outputs not generated" 但文件已生成 |
|
||||
| 4. 相位解缠 | `SARsInSARPhaseUnwrapping` | ~1000s | 无 |
|
||||
| 5a. GCP 生成 | Python (rasterio+geopandas) | <1s | 无 |
|
||||
| 5b. 精化再平化 | `SARsInSARRefinementAndReflattening` | ~30s | 无 |
|
||||
| 6. 位移+地理编码 | `SARsInSARPhaseToDisplacement` | ~7600s | envipyengine 可能提前返回或挂起;第二个 Task 对曾出现 "SARscape process unexpectedly terminated" |
|
||||
|
||||
单个 Task 对总耗时约 2.8 小时(测试数据)。两个 Task 对串行处理约 3.8 小时。
|
||||
|
||||
## 五、前端锁机制
|
||||
|
||||
### 两层锁结构
|
||||
|
||||
1. **全局蓝色遮罩** (`App.jsx` → `global-task-overlay`)
|
||||
- `App.jsx` 每 3 秒轮询 `GET /tasks/active`
|
||||
- 有任何活跃任务 → `isGlobalLocked=true` → 全屏蓝色遮罩覆盖整个 UI
|
||||
- 显示所有活跃任务的名称、进度条、状态消息
|
||||
- 管理员可见「管理员强制解锁」按钮 → 输入密码 → 取消所有任务 → 遮罩消失
|
||||
- 任务完成后自动刷新页面数据
|
||||
|
||||
2. **IDLAutomationPanel 内部锁** (`IDLAutomationPanel.jsx`)
|
||||
- 每 10 秒轮询 `GET /tasks/active`,查找 IDL 类型任务
|
||||
- 有活跃 IDL 任务 → `isLocked=true` → 按钮禁用 + 黄色横幅
|
||||
- 横幅内也有强制解锁按钮(作为备用入口)
|
||||
|
||||
### 强制解锁 API
|
||||
|
||||
- `POST /tasks/{task_id}/force-cancel`
|
||||
- 需要管理员 JWT + 请求体中的密码双重验证
|
||||
- 将任务标记为 CANCELLED,前端下次轮询时解锁
|
||||
|
||||
## 六、关键文件清单
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `backend/app/services/envi_service.py` | ENVI 工作流核心(6 步流程、GCP 生成、文件稳定等待) |
|
||||
| `backend/app/services/envi_runner_cli.py` | 子进程入口(加载 .env、调用 run_workflow) |
|
||||
| `backend/app/services/job_handlers.py` | Job handler(子进程监控、keepalive、文件稳定检查��� |
|
||||
| `backend/app/services/job_worker.py` | Worker 主循环(心跳、stale 检测) |
|
||||
| `backend/app/services/task_service.py` | Task 管理(僵尸检测在 get_active_tasks 中) |
|
||||
| `backend/app/routers/tasks.py` | Task API(含 force-cancel 端点) |
|
||||
| `backend/app/routers/idl.py` | IDL 路由(Import/D-InSAR 任务提交) |
|
||||
| `frontend/src/App.jsx` | 全局遮罩锁 + 强制解锁 UI |
|
||||
| `frontend/src/IDLAutomationPanel.jsx` | SARscape 面板(内部锁 + 备用强制解锁) |
|
||||
| `frontend/src/api/idl.js` | 前端 API 函数 |
|
||||
| `.env` | 所有超时和处理参数配置 |
|
||||
|
||||
## 七、待测试内容
|
||||
|
||||
1. **keepalive 机制验证**: 确认前端在整个处理过程中保持锁定(蓝色遮罩不消失),进度和步骤信息正确显示
|
||||
2. **双 Task 对完整流程**: 两个 Task 对串行处理,全部完成后前端才解锁
|
||||
3. **全局遮罩强制解锁**: 管理员在蓝色遮罩上输入密码后能正确取消任务并解锁
|
||||
4. **进度显示**: 遮罩上的进度条和步骤信息随 ENVI 处理实时更新(Step 1/6 → Step 6/6, 10% → 90%)
|
||||
5. **异常恢复**: envipyengine 报错时文件扫描兜底逻辑是否可靠
|
||||
6. **metatask 模式**: 确认默认模式不受自定义模式改动影响
|
||||
7. **多用户场景**: 管理员操作锁定时,只读用户仍可正常浏览(只读用户看不到强制解锁按钮)
|
||||
8. **第二个 Task 对 step 6 失败**: 上次测试中第二个 Task 对的 step 6 报 "SARscape process unexpectedly terminated",需确认是数据问题还是系统问题
|
||||
|
||||
## 八、最近一次测试结果 (2026-02-23)
|
||||
|
||||
- Task_20250309_20250112: 全部 6 步成功,总耗时 10104.9s
|
||||
- Task_20250310_20250113_9: 步骤 1-5b 成功,step 6 失败 ("SARscape process unexpectedly terminated",耗时 1853s)
|
||||
- 第一个 Task 的 `_wait_for_disp_stable` 修复生效(成功路径也等待 disp 文件)
|
||||
- 前端锁在第一个 Task 处理期间因僵尸检测被误杀(已通过 keepalive 修复,待验证)
|
||||
@@ -0,0 +1,265 @@
|
||||
# 前端锁定修复总结(2026-03-05)
|
||||
|
||||
## 修复概述
|
||||
|
||||
按照系统设计理念(管理员操作时全局锁定),修复了两个前端锁定问题:
|
||||
|
||||
1. ✅ **数据分发任务锁定**(P0 高优先级)
|
||||
2. ✅ **扫描任务锁定**(P1 中优先级)
|
||||
|
||||
---
|
||||
|
||||
## 修复 1:数据分发任务锁定
|
||||
|
||||
### 问题
|
||||
`DataCopierPanel` 是独立的任务管理系统,不与全局锁定机制集成。
|
||||
|
||||
### 修改文件
|
||||
1. `frontend/src/App.jsx`
|
||||
2. `frontend/src/DataCopierPanel.jsx`
|
||||
|
||||
### 修改内容
|
||||
|
||||
**App.jsx (1334 行)**:
|
||||
```javascript
|
||||
// 修改前
|
||||
<DataCopierPanel
|
||||
apiEndpoint={apiClient.defaults.baseURL}
|
||||
readOnly={isReadOnlyUser}
|
||||
/>
|
||||
|
||||
// 修改后
|
||||
<DataCopierPanel
|
||||
apiEndpoint={apiClient.defaults.baseURL}
|
||||
readOnly={isReadOnlyUser}
|
||||
onJobQueued={(taskId) => handleTaskStart(taskId, '数据分发任务已入队,正在处理...')}
|
||||
/>
|
||||
```
|
||||
|
||||
**DataCopierPanel.jsx (14 行)**:
|
||||
```javascript
|
||||
// 修改前
|
||||
const DataCopierPanel = ({ apiEndpoint, readOnly = false }) => {
|
||||
|
||||
// 修改后
|
||||
const DataCopierPanel = ({ apiEndpoint, readOnly = false, onJobQueued }) => {
|
||||
```
|
||||
|
||||
**DataCopierPanel.jsx (119-131 行)**:
|
||||
```javascript
|
||||
// 修改前
|
||||
try {
|
||||
const response = await axios.post(endpoint, {
|
||||
batch_id: selectedBatchId,
|
||||
dest_dir: destDir,
|
||||
copy_statuses: copyStatuses,
|
||||
}, { withCredentials: true });
|
||||
setTaskId(response.data.task_id);
|
||||
} catch (error) {
|
||||
|
||||
// 修改后
|
||||
try {
|
||||
const response = await axios.post(endpoint, {
|
||||
batch_id: selectedBatchId,
|
||||
dest_dir: destDir,
|
||||
copy_statuses: copyStatuses,
|
||||
}, { withCredentials: true });
|
||||
const taskId = response.data.task_id;
|
||||
setTaskId(taskId);
|
||||
|
||||
// 触发全局锁定
|
||||
if (onJobQueued) {
|
||||
onJobQueued(taskId);
|
||||
}
|
||||
} catch (error) {
|
||||
```
|
||||
|
||||
### 效果
|
||||
- 数据分发任务启动时,前端立即锁定
|
||||
- 任务完成后,前端自动解锁
|
||||
- 与其他任务(IDL 自动化、数据监控)行为一致
|
||||
|
||||
---
|
||||
|
||||
## 修复 2:扫描任务锁定
|
||||
|
||||
### 问题
|
||||
扫描任务执行太快(< 3 秒),前端轮询间隔(3 秒)来不及捕获 RUNNING 状态。
|
||||
|
||||
### 修改文件
|
||||
`frontend/src/DataMonitorPanel.jsx`
|
||||
|
||||
### 修改内容
|
||||
|
||||
**DataMonitorPanel.jsx (168-198 行)**:
|
||||
```javascript
|
||||
// 修改前
|
||||
const handleRunNow = async (target) => {
|
||||
// ... 验证逻辑 ...
|
||||
setLoading(true);
|
||||
setMessage(`正在触发${targetMap[target] || '全部'}手动扫描...`);
|
||||
try {
|
||||
const url = target ? `${apiEndpoint}/monitor/run-now?target=${target}` : `${apiEndpoint}/monitor/run-now`;
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
setMessage(data.message);
|
||||
if (onTaskStart) onTaskStart(data.task_id, `已触发${targetMap[target] || '全部'}手动扫描...`);
|
||||
}
|
||||
// ...
|
||||
}
|
||||
};
|
||||
|
||||
// 修改后
|
||||
const handleRunNow = async (target) => {
|
||||
// ... 验证逻辑 ...
|
||||
setLoading(true);
|
||||
setMessage(`正在触发${targetMap[target] || '全部'}手动扫描...`);
|
||||
|
||||
// 立即触发全局锁定(在 API 调用之前)
|
||||
if (onTaskStart) {
|
||||
onTaskStart(null, `正在触发${targetMap[target] || '全部'}手动扫描...`);
|
||||
}
|
||||
|
||||
try {
|
||||
const url = target ? `${apiEndpoint}/monitor/run-now?target=${target}` : `${apiEndpoint}/monitor/run-now`;
|
||||
const res = await fetch(url, {
|
||||
method: 'POST',
|
||||
credentials: 'include'
|
||||
});
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
setMessage(data.message);
|
||||
// 更新任务 ID
|
||||
if (onTaskStart) onTaskStart(data.task_id, `已触发${targetMap[target] || '全部'}手动扫描...`);
|
||||
}
|
||||
// ...
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
### 关键改进
|
||||
1. **在 API 调用之前立即锁定**:`onTaskStart(null, message)`
|
||||
2. **API 成功后更新任务 ID**:`onTaskStart(taskId, message)`
|
||||
3. **失败时自动解锁**:全局轮询检测到没有活跃任务时自动解锁
|
||||
|
||||
### 效果
|
||||
- 用户点击"扫描"按钮后,前端立即锁定
|
||||
- 即使任务执行很快(< 3 秒),用户也能看到锁定状态
|
||||
- 任务完成后,前端自动解锁
|
||||
|
||||
---
|
||||
|
||||
## 测试验证
|
||||
|
||||
### 测试 1:数据分发任务锁定
|
||||
1. 登录管理员账号
|
||||
2. 进入"数据分发" Tab
|
||||
3. 选择批次和目标目录
|
||||
4. 点击"开始复制"
|
||||
5. **预期**:前端立即锁定,显示"数据分发任务已入队,正在处理..."
|
||||
6. 等待任务完成
|
||||
7. **预期**:前端自动解锁
|
||||
|
||||
### 测试 2:扫描任务锁定
|
||||
1. 登录管理员账号
|
||||
2. 进入"数据管理" Tab
|
||||
3. 点击"扫描雷达数据"或"扫描 D-InSAR 结果"
|
||||
4. **预期**:前端立即锁定,显示"正在触发...手动扫描..."
|
||||
5. 等待任务完成(可能很快)
|
||||
6. **预期**:前端自动解锁
|
||||
|
||||
### 测试 3:多任务冲突
|
||||
1. 启动数据分发任务
|
||||
2. **预期**:前端锁定,无法启动其他任务
|
||||
3. 尝试点击其他操作按钮
|
||||
4. **预期**:按钮被禁用或操作被阻止
|
||||
|
||||
---
|
||||
|
||||
## 技术细节
|
||||
|
||||
### handleTaskStart 函数行为
|
||||
|
||||
```javascript
|
||||
// hooks/useDinsarOperations.js:123-129
|
||||
const handleTaskStart = (taskId, message) => {
|
||||
if (taskId) {
|
||||
setPendingTaskIds(prev => [...prev, taskId]);
|
||||
}
|
||||
setIsGlobalLocked(true); // 总是锁定
|
||||
if (message) addLog('info', message);
|
||||
};
|
||||
```
|
||||
|
||||
**关键点**:
|
||||
- `taskId` 可以为 `null`(立即锁定,稍后更新 ID)
|
||||
- `setIsGlobalLocked(true)` 总是执行(无论 taskId 是否为 null)
|
||||
- 这允许我们在 API 调用之前就锁定前端
|
||||
|
||||
### 解锁机制
|
||||
|
||||
前端通过 `useGlobalTaskControl` hook 自动解锁:
|
||||
|
||||
```javascript
|
||||
// hooks/useGlobalTaskControl.js:56-64
|
||||
if (hasRunningTasks !== isGlobalLockedRef.current) {
|
||||
setIsGlobalLocked(hasRunningTasks);
|
||||
if (!hasRunningTasks) {
|
||||
addLog('success', '后台任务已完成,正在同步最新数据...');
|
||||
setTimeout(() => {
|
||||
initializeAppDataRef.current?.({ refreshRadarSearch: true });
|
||||
}, 500);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**工作原理**:
|
||||
1. 每 3 秒轮询 `/tasks/active`
|
||||
2. 检测到没有活跃任务时,自动解锁
|
||||
3. 不需要手动调用解锁
|
||||
|
||||
---
|
||||
|
||||
## 修改总结
|
||||
|
||||
### 修改文件
|
||||
1. `frontend/src/App.jsx` - 1 处修改(添加 onJobQueued 回调)
|
||||
2. `frontend/src/DataCopierPanel.jsx` - 2 处修改(接收回调 + 调用回调)
|
||||
3. `frontend/src/DataMonitorPanel.jsx` - 1 处修改(提前锁定)
|
||||
|
||||
### 代码行数
|
||||
- 新增:约 10 行
|
||||
- 修改:约 5 行
|
||||
- 删除:0 行
|
||||
|
||||
### 风险评估
|
||||
- 🟢 **低风险**:改动小,逻辑清晰
|
||||
- 🟢 **向后兼容**:不影响现有功能
|
||||
- 🟢 **易于回滚**:修改集中,容易撤销
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `ANALYSIS_FRONTEND_LOCKING_2026-03-05.md` - 问题分析
|
||||
- `SECURITY_FIX_PROGRESS.md` - 修复进度跟踪
|
||||
- `HOTFIX_PROJ_CONFLICT_2026-03-05.md` - PROJ 冲突修复
|
||||
|
||||
---
|
||||
|
||||
## 下一步
|
||||
|
||||
1. ✅ 代码修改完成
|
||||
2. ⏳ 前端构建测试
|
||||
3. ⏳ 功能测试验证
|
||||
4. ⏳ 用户验收测试
|
||||
|
||||
---
|
||||
|
||||
**修复完成时间**:2026-03-05
|
||||
**修复人**:Claude Opus 4.6
|
||||
**状态**:✅ 代码修改完成,等待测试验证
|
||||
@@ -0,0 +1,278 @@
|
||||
# 硬编码审计报告(2026-03-05)
|
||||
|
||||
## 审计目的
|
||||
|
||||
确保系统可以在不同环境(开发机、生产环境、客户现场)部署,所有配置必须通过 `.env` 文件管理,不允许硬编码。
|
||||
|
||||
## 审计范围
|
||||
|
||||
- 后端代码(`backend/`)
|
||||
- 启动脚本(`run_backend.py`, `scripts/`)
|
||||
- 配置文件(`.env`, `config.py`)
|
||||
|
||||
## 发现的硬编码问题
|
||||
|
||||
### 🔴 高优先级(必须修复)
|
||||
|
||||
#### 1. Ollama API 地址硬编码
|
||||
|
||||
**位置**:
|
||||
- `backend/app/ai_service.py:231`
|
||||
- `backend/app/services/health_service.py:115`
|
||||
|
||||
**问题**:
|
||||
```python
|
||||
# 硬编码 Ollama API 地址
|
||||
resp = await client.get("http://127.0.0.1:11434/api/tags")
|
||||
```
|
||||
|
||||
**影响**:
|
||||
- 如果 Ollama 部署在其他机器或端口,无法连接
|
||||
- 客户环境可能使用不同的 Ollama 地址
|
||||
|
||||
**修复方案**:
|
||||
```python
|
||||
# 从环境变量读取
|
||||
OLLAMA_BASE_URL = os.getenv("OLLAMA_BASE_URL", "http://127.0.0.1:11434")
|
||||
resp = await client.get(f"{OLLAMA_BASE_URL}/api/tags")
|
||||
```
|
||||
|
||||
**`.env` 配置**:
|
||||
```bash
|
||||
# Ollama 服务地址(基础 URL)
|
||||
OLLAMA_BASE_URL=http://127.0.0.1:11434
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 2. Nginx 健康检查地址硬编码
|
||||
|
||||
**位置**:
|
||||
- `backend/app/services/health_service.py:130`
|
||||
|
||||
**问题**:
|
||||
```python
|
||||
# 硬编码 Nginx 地址
|
||||
resp = await client.get("http://127.0.0.1/")
|
||||
```
|
||||
|
||||
**影响**:
|
||||
- 如果 Nginx 监听其他端口或地址,健康检查失败
|
||||
- 客户环境可能使用不同的 Nginx 配置
|
||||
|
||||
**修复方案**:
|
||||
```python
|
||||
# 从环境变量读取
|
||||
NGINX_HEALTH_URL = os.getenv("NGINX_HEALTH_URL", "http://127.0.0.1/")
|
||||
resp = await client.get(NGINX_HEALTH_URL)
|
||||
```
|
||||
|
||||
**`.env` 配置**:
|
||||
```bash
|
||||
# Nginx 健康检查地址
|
||||
NGINX_HEALTH_URL=http://127.0.0.1/
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
#### 3. 后端绑定地址硬编码
|
||||
|
||||
**位置**:
|
||||
- `run_backend.py:29`
|
||||
|
||||
**问题**:
|
||||
```python
|
||||
# 硬编码绑定地址
|
||||
bind_host = "127.0.0.1"
|
||||
```
|
||||
|
||||
**影响**:
|
||||
- 无法从外部访问(Docker 容器、远程部署)
|
||||
- 某些部署场景需要绑定 `0.0.0.0`
|
||||
|
||||
**修复方案**:
|
||||
```python
|
||||
# 从环境变量读取
|
||||
bind_host = os.getenv("BACKEND_BIND_HOST", "127.0.0.1")
|
||||
```
|
||||
|
||||
**`.env` 配置**:
|
||||
```bash
|
||||
# 后端绑定地址(127.0.0.1 仅本地,0.0.0.0 允许外部访问)
|
||||
BACKEND_BIND_HOST=127.0.0.1
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🟡 中优先级(建议修复)
|
||||
|
||||
#### 4. PROJ 数据库路径检查脚本硬编码
|
||||
|
||||
**位置**:
|
||||
- `scripts/check_proj.py:62`
|
||||
|
||||
**问题**:
|
||||
```python
|
||||
# 硬编码 PostgreSQL PROJ 路径
|
||||
pg_proj = r"C:\Program Files\PostgreSQL\17\share\contrib\postgis-3.6\proj\proj.db"
|
||||
```
|
||||
|
||||
**影响**:
|
||||
- 不同 PostgreSQL 版本路径不同
|
||||
- 客户环境可能安装在其他位置
|
||||
|
||||
**修复方案**:
|
||||
```python
|
||||
# 从环境变量读取,或自动检测
|
||||
pg_proj = os.getenv("POSTGRESQL_PROJ_PATH", r"C:\Program Files\PostgreSQL\17\share\contrib\postgis-3.6\proj\proj.db")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 🟢 低优先级(可选优化)
|
||||
|
||||
#### 5. 默认值中的硬编码
|
||||
|
||||
**位置**:
|
||||
- `backend/app/config.py:33-34`
|
||||
|
||||
**问题**:
|
||||
```python
|
||||
OLLAMA_API_URL: str = "http://127.0.0.1:11434/api/generate"
|
||||
DEFAULT_VLM_MODEL: str = "qwen3-vl:8b"
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- 这些是默认值,已经通过 `os.getenv()` 读取
|
||||
- 但默认值本身是硬编码的
|
||||
|
||||
**修复方案**:
|
||||
- 保持现状(默认值是合理的)
|
||||
- 或者在文档中明确说明这些默认值
|
||||
|
||||
---
|
||||
|
||||
## 修复优先级
|
||||
|
||||
| 优先级 | 问题 | 影响 | 修复难度 |
|
||||
|--------|------|------|----------|
|
||||
| 🔴 P0 | Ollama API 地址 | 高 | 低 |
|
||||
| 🔴 P0 | Nginx 健康检查地址 | 中 | 低 |
|
||||
| 🔴 P0 | 后端绑定地址 | 高 | 低 |
|
||||
| 🟡 P1 | PROJ 路径检查 | 低 | 低 |
|
||||
| 🟢 P2 | 默认值硬编码 | 低 | 无需修复 |
|
||||
|
||||
---
|
||||
|
||||
## 修复计划
|
||||
|
||||
### 阶段 1:立即修复(P0)
|
||||
|
||||
1. **修改 `backend/app/ai_service.py`**
|
||||
- 添加 `OLLAMA_BASE_URL` 环境变量
|
||||
- 修改 API 调用使用动态 URL
|
||||
|
||||
2. **修改 `backend/app/services/health_service.py`**
|
||||
- 添加 `OLLAMA_BASE_URL` 和 `NGINX_HEALTH_URL` 环境变量
|
||||
- 修改健康检查使用动态 URL
|
||||
|
||||
3. **修改 `run_backend.py`**
|
||||
- 添加 `BACKEND_BIND_HOST` 环境变量
|
||||
- 支持配置绑定地址
|
||||
|
||||
4. **更新 `.env` 文件**
|
||||
- 添加新的配置项和注释
|
||||
|
||||
5. **更新文档**
|
||||
- 在部署文档中说明这些配置项
|
||||
|
||||
### 阶段 2:建议修复(P1)
|
||||
|
||||
1. **修改 `scripts/check_proj.py`**
|
||||
- 支持从环境变量读取 PostgreSQL 路径
|
||||
- 或自动检测 PostgreSQL 安装路径
|
||||
|
||||
---
|
||||
|
||||
## 验证方法
|
||||
|
||||
### 1. 配置文件验证
|
||||
|
||||
检查 `.env` 文件是否包含所有必需的配置项:
|
||||
|
||||
```bash
|
||||
# 必需配置
|
||||
DATABASE_URL=...
|
||||
IDL_EXECUTABLE=...
|
||||
IDL_DINSAR_DEM_BASE_FILE=...
|
||||
|
||||
# 新增配置
|
||||
OLLAMA_BASE_URL=http://127.0.0.1:11434
|
||||
NGINX_HEALTH_URL=http://127.0.0.1/
|
||||
BACKEND_BIND_HOST=127.0.0.1
|
||||
```
|
||||
|
||||
### 2. 代码审计
|
||||
|
||||
运行以下命令检查是否还有硬编码:
|
||||
|
||||
```bash
|
||||
# 检查绝对路径
|
||||
grep -r "C:\\\\" backend/ --include="*.py"
|
||||
grep -r "D:\\\\" backend/ --include="*.py"
|
||||
|
||||
# 检查 IP 地址
|
||||
grep -r "127\.0\.0\.1" backend/ --include="*.py"
|
||||
grep -r "localhost" backend/ --include="*.py"
|
||||
|
||||
# 检查端口号
|
||||
grep -r ":11434" backend/ --include="*.py"
|
||||
grep -r ":8000" backend/ --include="*.py"
|
||||
```
|
||||
|
||||
### 3. 部署测试
|
||||
|
||||
在不同环境测试:
|
||||
|
||||
1. **本地开发环境**:使用默认配置
|
||||
2. **Docker 容器**:修改 `BACKEND_BIND_HOST=0.0.0.0`
|
||||
3. **远程 Ollama**:修改 `OLLAMA_BASE_URL=http://192.168.1.100:11434`
|
||||
4. **自定义端口**:修改 `PORT=18000`
|
||||
|
||||
---
|
||||
|
||||
## 部署检查清单
|
||||
|
||||
在部署到客户环境前,确认:
|
||||
|
||||
- [ ] 所有路径配置在 `.env` 中
|
||||
- [ ] 所有 IP 地址/端口配置在 `.env` 中
|
||||
- [ ] 没有硬编码的绝对路径
|
||||
- [ ] 没有硬编码的 IP 地址
|
||||
- [ ] 没有硬编码的端口号
|
||||
- [ ] 所有配置项都有默认值(合理的)
|
||||
- [ ] 所有配置项都有注释说明
|
||||
- [ ] 部署文档已更新
|
||||
|
||||
---
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `.env` 配置文件
|
||||
- `docs/PROJ_CONFIGURATION.md` - PROJ 数据库配置
|
||||
- `docs/DEPLOYMENT.md` - 部署文档(待创建)
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
**当前状态**:
|
||||
- ✅ 大部分配置已通过 `.env` 管理
|
||||
- ❌ 发现 3 个高优先级硬编码问题
|
||||
- ⚠️ 需要立即修复以支持生产部署
|
||||
|
||||
**修复后**:
|
||||
- ✅ 所有配置通过 `.env` 管理
|
||||
- ✅ 支持多种部署场景
|
||||
- ✅ 客户环境可自定义配置
|
||||
- ✅ 无需修改代码即可部署
|
||||
@@ -0,0 +1,113 @@
|
||||
# 紧急修复:Nginx UTF-8 BOM 问题(2026-03-04)
|
||||
|
||||
## 问题描述
|
||||
|
||||
在测试第一阶段修复时,发现 Nginx 启动失败:
|
||||
|
||||
```
|
||||
nginx: [emerg] unknown directive "worker_processes" in Z:/Code/Insar_management_system_v2/nginx/nginx.conf:1
|
||||
```
|
||||
|
||||
注意 `worker_processes` 前面有一个不可见字符 ``(UTF-8 BOM)。
|
||||
|
||||
## 根本原因
|
||||
|
||||
1. `nginx.conf` 文件包含 UTF-8 BOM(字节序标记:`ef bb bf`)
|
||||
2. Nginx 无法识别 BOM,将其视为非法字符
|
||||
3. PowerShell 的 `Set-Content -Encoding UTF8` 默认会添加 BOM
|
||||
|
||||
## 影响
|
||||
|
||||
- 🔴 **严重**:Nginx 无法启动,整个系统无法访问
|
||||
- 这是第一阶段修复中 1.3 PowerShell 编码问题的延伸
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 步骤 1:删除现有文件的 BOM
|
||||
|
||||
```bash
|
||||
cd "Z:\Code\Insar_management_system_v2\nginx"
|
||||
tail -c +4 nginx.conf > nginx.conf.tmp
|
||||
mv nginx.conf.tmp nginx.conf
|
||||
```
|
||||
|
||||
**验证**:
|
||||
```bash
|
||||
xxd -l 16 nginx.conf
|
||||
# 应该显示:
|
||||
# 00000000: 776f 726b 6572 5f70 726f 6365 7373 6573 worker_processes
|
||||
# 而不是:
|
||||
# 00000000: efbb bf77 6f72 6b65 725f 7072 6f63 6573 ...worker_proces
|
||||
```
|
||||
|
||||
### 步骤 2:修改 PowerShell 脚本
|
||||
|
||||
**文件**:`scripts/start_app.ps1:388`
|
||||
|
||||
**修改前**:
|
||||
```powershell
|
||||
$NewConfContent | Set-Content -LiteralPath "$NginxConfPath" -NoNewline -Encoding UTF8
|
||||
```
|
||||
|
||||
**修改后**:
|
||||
```powershell
|
||||
# 使用 UTF8 无 BOM 编码写入
|
||||
$Utf8NoBom = New-Object System.Text.UTF8Encoding $false
|
||||
[System.IO.File]::WriteAllText("$NginxConfPath", $NewConfContent, $Utf8NoBom)
|
||||
```
|
||||
|
||||
**说明**:
|
||||
- `System.Text.UTF8Encoding $false` 创建无 BOM 的 UTF-8 编码器
|
||||
- `[System.IO.File]::WriteAllText()` 直接写入文件,不添加 BOM
|
||||
- 这在 PowerShell 5 和 PowerShell 7+ 中都有效
|
||||
|
||||
## 验证
|
||||
|
||||
### 1. 检查文件编码
|
||||
```bash
|
||||
xxd -l 3 nginx/nginx.conf
|
||||
# 应该不包含 ef bb bf
|
||||
```
|
||||
|
||||
### 2. 启动系统
|
||||
```powershell
|
||||
.\scripts\start_app.ps1
|
||||
```
|
||||
|
||||
### 3. 验证 Nginx 启动
|
||||
```bash
|
||||
curl http://localhost:8080
|
||||
# 应该返回前端页面
|
||||
```
|
||||
|
||||
## 经验教训
|
||||
|
||||
1. **PowerShell 编码陷阱**:
|
||||
- `Set-Content -Encoding UTF8` 会添加 BOM
|
||||
- 需要使用 `[System.IO.File]::WriteAllText()` 或 PowerShell 6+ 的 `utf8NoBOM`
|
||||
|
||||
2. **Nginx 对 BOM 敏感**:
|
||||
- Nginx 配置文件必须是纯 ASCII 或无 BOM 的 UTF-8
|
||||
- BOM 会导致解析失败
|
||||
|
||||
3. **测试的重要性**:
|
||||
- 如果没有实际测试,这个问题不会被发现
|
||||
- 代码审查和静态分析无法发现编码问题
|
||||
|
||||
## 更新的文档
|
||||
|
||||
- ✅ `SECURITY_FIX_PROGRESS.md` - 记录问题和解决方案
|
||||
- ✅ `scripts/start_app.ps1` - 修复编码问题
|
||||
- ✅ `nginx/nginx.conf` - 删除 BOM
|
||||
|
||||
## 状态
|
||||
|
||||
- ✅ 问题已解决
|
||||
- ✅ 代码已修改
|
||||
- ⏳ 等待重新测试
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 重新运行启动脚本
|
||||
2. 验证 Nginx 正常启动
|
||||
3. 继续第一阶段的其他测试
|
||||
@@ -0,0 +1,204 @@
|
||||
# PROJ 数据库版本冲突问题修复(2026-03-05)
|
||||
|
||||
## 问题描述
|
||||
|
||||
在扫描 D-InSAR 结果时出现 PROJ 错误:
|
||||
|
||||
```
|
||||
PROJ: proj_identify: C:\Program Files\PostgreSQL\17\share\contrib\postgis-3.6\proj\proj.db
|
||||
contains DATABASE.LAYOUT.VERSION.MINOR = 2 whereas a number >= 3 is expected.
|
||||
It comes from another PROJ installation.
|
||||
```
|
||||
|
||||
## 根本原因
|
||||
|
||||
1. **PROJ 版本冲突**:
|
||||
- PostgreSQL 17 自带的 PostGIS 3.6 包含旧版 PROJ 数据库(版本 2)
|
||||
- GDAL/rasterio 期望 PROJ 数据库版本 >= 3
|
||||
- 系统在坐标转换时(`Transformer.from_crs`)触发此警告
|
||||
|
||||
2. **环境变量优先级**:
|
||||
- Windows 系统可能将 PostgreSQL 的 PROJ 路径添加到 PATH
|
||||
- GDAL 默认搜索 PATH 中的 PROJ 数据库
|
||||
- 找到了 PostgreSQL 的旧版本而非 GDAL 自带的新版本
|
||||
|
||||
## 影响
|
||||
|
||||
- 🟡 **中等**:不会导致程序崩溃,但会产生大量警告日志
|
||||
- 坐标转换仍然可以工作(PROJ 会回退到兼容模式)
|
||||
- 日志污染,影响问题排查
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 方案 1:设置 PROJ_LIB 环境变量(已实施)
|
||||
|
||||
**文件**:`run_backend.py`
|
||||
|
||||
**修改内容**:
|
||||
```python
|
||||
def main() -> None:
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Fix PROJ database version conflict
|
||||
# PostgreSQL's PROJ database is outdated, use GDAL's bundled PROJ data
|
||||
# This prevents "DATABASE.LAYOUT.VERSION.MINOR = 2 whereas >= 3 is expected" error
|
||||
if "PROJ_LIB" not in os.environ:
|
||||
# Try to find GDAL's PROJ data directory
|
||||
try:
|
||||
from osgeo import gdal
|
||||
gdal_data = gdal.GetConfigOption("GDAL_DATA")
|
||||
if gdal_data:
|
||||
proj_lib = os.path.join(os.path.dirname(gdal_data), "proj")
|
||||
if os.path.exists(proj_lib):
|
||||
os.environ["PROJ_LIB"] = proj_lib
|
||||
print(f"[*] Set PROJ_LIB to: {proj_lib}")
|
||||
except Exception as e:
|
||||
print(f"[WARN] Could not auto-configure PROJ_LIB: {e}")
|
||||
|
||||
port = int(os.getenv("PORT", 8000))
|
||||
# ... rest of the code
|
||||
```
|
||||
|
||||
**工作原理**:
|
||||
1. 在启动时检查 `PROJ_LIB` 环境变量是否已设置
|
||||
2. 如果未设置,尝试从 GDAL 配置中获取 PROJ 数据目录
|
||||
3. 设置 `PROJ_LIB` 指向 GDAL 自带的 PROJ 数据库
|
||||
|
||||
### 方案 2:手动设置环境变量(备选)
|
||||
|
||||
如果方案 1 不生效,可以在 `.env` 文件中手动设置:
|
||||
|
||||
```bash
|
||||
# 找到 GDAL 的 PROJ 数据目录
|
||||
python -c "from osgeo import gdal; import os; print(os.path.join(os.path.dirname(gdal.GetConfigOption('GDAL_DATA')), 'proj'))"
|
||||
|
||||
# 将输出路径添加到 .env
|
||||
PROJ_LIB=C:\path\to\gdal\proj
|
||||
```
|
||||
|
||||
或者在 PowerShell 启动脚本中设置:
|
||||
|
||||
```powershell
|
||||
# scripts/start_app.ps1
|
||||
$env:PROJ_LIB = "C:\path\to\gdal\proj"
|
||||
```
|
||||
|
||||
### 方案 3:抑制 GDAL 警告(不推荐)
|
||||
|
||||
如果只是想隐藏警告而不解决根本问题:
|
||||
|
||||
```python
|
||||
# 在 run_backend.py 中添加
|
||||
from osgeo import gdal
|
||||
gdal.SetConfigOption('CPL_LOG', 'OFF') # 关闭所有 GDAL 日志
|
||||
```
|
||||
|
||||
**不推荐原因**:
|
||||
- 会隐藏所有 GDAL 错误和警告
|
||||
- 不解决根本问题
|
||||
- 可能掩盖其他重要错误
|
||||
|
||||
## 验证
|
||||
|
||||
### 1. 检查 PROJ_LIB 是否生效
|
||||
|
||||
启动后端后,查看日志:
|
||||
```
|
||||
[*] Set PROJ_LIB to: C:\Users\...\site-packages\osgeo\data\proj
|
||||
```
|
||||
|
||||
### 2. 运行扫描任务
|
||||
|
||||
在前端触发 D-InSAR 结果扫描,观察日志:
|
||||
- ✅ 应该不再出现 PROJ 警告
|
||||
- ✅ Footprint 提取正常完成
|
||||
|
||||
### 3. 验证坐标转换
|
||||
|
||||
```python
|
||||
from pyproj import Transformer
|
||||
transformer = Transformer.from_crs("EPSG:32650", "EPSG:4326", always_xy=True)
|
||||
x, y = transformer.transform(500000, 3000000)
|
||||
print(f"Transformed: {x}, {y}")
|
||||
# 应该不产生警告
|
||||
```
|
||||
|
||||
## 前端锁定问题
|
||||
|
||||
**问题**:扫描任务运行时前端没有锁定
|
||||
|
||||
**可能原因**:
|
||||
1. 扫描任务执行太快(< 3 秒),前端轮询间隔(3 秒)来不及捕获
|
||||
2. 任务状态更新有延迟
|
||||
|
||||
**解决方案**:
|
||||
|
||||
### 方案 A:增加最小锁定时间(推荐)
|
||||
|
||||
在扫描任务开始时立即锁定,结束时延迟解锁:
|
||||
|
||||
```python
|
||||
# job_handlers.py
|
||||
async def _handle_scan_dinsar(job: SystemJobORM) -> None:
|
||||
if not job.task_id:
|
||||
raise ValueError("SCAN_DINSAR requires task_id for progress tracking.")
|
||||
|
||||
await task_service.start_task(job.task_id, message="正在扫描 D-InSAR 结果...")
|
||||
|
||||
# 确保任务至少运行 2 秒,让前端有时间捕获
|
||||
start_time = time.time()
|
||||
|
||||
# ... 执行扫描 ...
|
||||
|
||||
# 确保最小执行时间
|
||||
elapsed = time.time() - start_time
|
||||
if elapsed < 2.0:
|
||||
await asyncio.sleep(2.0 - elapsed)
|
||||
|
||||
await task_service.complete_task(job.task_id, message="扫描完成")
|
||||
```
|
||||
|
||||
### 方案 B:前端立即锁定(更好)
|
||||
|
||||
在提交扫描任务后立即锁定前端,不等待轮询:
|
||||
|
||||
```javascript
|
||||
// DataManagementPanel.jsx
|
||||
const handleScanDinsarResults = async () => {
|
||||
try {
|
||||
// 立即锁定前端
|
||||
onJobQueued?.(null, 'SCAN_DINSAR');
|
||||
|
||||
const response = await scanDinsarResults(selectedDirs);
|
||||
const taskId = response.data.task_id;
|
||||
|
||||
// 更新任务 ID
|
||||
onJobQueued?.(taskId, 'SCAN_DINSAR');
|
||||
|
||||
addLog('info', `D-InSAR 结果扫描任务已提交: ${taskId}`);
|
||||
} catch (error) {
|
||||
// 解锁前端
|
||||
setIsGlobalLocked(false);
|
||||
addLog('error', `扫描失败: ${error.message}`);
|
||||
}
|
||||
};
|
||||
```
|
||||
|
||||
## 状态
|
||||
|
||||
- ✅ PROJ 环境变量修复已实施
|
||||
- ⏳ 等待测试验证
|
||||
- ⏳ 前端锁定问题待确认是否需要修复
|
||||
|
||||
## 下一步
|
||||
|
||||
1. 重启后端服务
|
||||
2. 运行 D-InSAR 结果扫描
|
||||
3. 观察日志,确认 PROJ 警告消失
|
||||
4. 如果前端仍未锁定,实施方案 B
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `SECURITY_FIX_PROGRESS.md` - 安全修复进度
|
||||
- `HOTFIX_NGINX_BOM_2026-03-04.md` - Nginx BOM 修复
|
||||
@@ -0,0 +1,201 @@
|
||||
# IDL/ENVI 集成状态文档
|
||||
|
||||
> 最后更新: 2026-02-21
|
||||
|
||||
## 1. 架构概述
|
||||
|
||||
系统的 D-InSAR 生产完全依赖 ENVI 5.6 + IDL 8.8 + SARscape 5.6+。
|
||||
当前采用 **envipyengine** 作为唯一执行引擎,通过 Python subprocess 调用 `taskengine.exe`。
|
||||
|
||||
### 执行链路
|
||||
|
||||
```
|
||||
前端 (IDLAutomationPanel.jsx)
|
||||
→ POST /api/idl/jobs/import 或 /api/idl/jobs/dinsar
|
||||
→ job_handlers.py: subprocess 启动 envi_runner_cli.py
|
||||
→ envi_service.py: 调用 envipyengine → taskengine.exe
|
||||
→ ENVI Task 执行 (SARsImportLuTan1 / SARsMetataskInSARDisplacementGeneration)
|
||||
```
|
||||
|
||||
### 核心文件
|
||||
|
||||
| 文件 | 职责 |
|
||||
|------|------|
|
||||
| `backend/app/services/envi_service.py` | 核心服务:工作流、预检查、状态、历史 |
|
||||
| `backend/app/services/envi_runner_cli.py` | CLI 入口,subprocess 中执行工作流 |
|
||||
| `backend/app/services/job_handlers.py` | 任务分发,启动 envi_runner_cli 子进程 |
|
||||
| `backend/app/routers/idl.py` | API 端点 |
|
||||
| `frontend/src/IDLAutomationPanel.jsx` | 前端面板 (Step 1/2 布局) |
|
||||
| `frontend/src/api/idl.js` | 前端 API 客户端 |
|
||||
|
||||
## 2. 环境配置
|
||||
|
||||
### ENVI 安装路径
|
||||
|
||||
```
|
||||
C:\Program Files\Harris\ENVI56\
|
||||
├── IDL88\bin\bin.x86_64\idl.exe
|
||||
├── IDL88\bin\bin.x86_64\idlde.exe
|
||||
└── IDL88\bin\bin.x86_64\taskengine.exe
|
||||
```
|
||||
|
||||
### .env 关键配置
|
||||
|
||||
```ini
|
||||
IDL_EXECUTABLE=C:\Program Files\Harris\ENVI56\IDL88\bin\bin.x86_64\idl.exe
|
||||
IDL_WORKBENCH_PATH=C:\Program Files\Harris\ENVI56\IDL88\bin\bin.x86_64\idlde.exe
|
||||
IDL_WORKER_RUNTIME_DIR=...\backend\runtime\idl_worker
|
||||
IDL_WORKER_DEFAULT_TIMEOUT_SECONDS=14400
|
||||
IDL_WORKER_MAX_TIMEOUT_SECONDS=43200
|
||||
IDL_DINSAR_DEM_BASE_FILE=D:\SRTM30m\SRTMDEM_RSP_SARscape
|
||||
```
|
||||
|
||||
### envipyengine 配置
|
||||
|
||||
```python
|
||||
# 已通过 envipyengine.config.set() 配置:
|
||||
engine = r'C:\Program Files\Harris\ENVI56\IDL88\bin\bin.x86_64\taskengine.exe'
|
||||
```
|
||||
|
||||
Python 环境: `C:\Users\16216\.conda\envs\InSAR\python.exe` (envipyengine v1.0.9)
|
||||
|
||||
## 3. 根因分析:历史集成失败
|
||||
|
||||
### 3.1 症状回顾
|
||||
|
||||
之前尝试了三种引擎均不稳定:
|
||||
- `idl.exe -e` 直连:进程挂起、管道错误、idl_opserver 崩溃 (0x0000000000000001)
|
||||
- `taskengine.exe` 直调:内存访问违规 (0xC0000005 / exit code 3221225477)
|
||||
- `envipyengine`:单独测试通过,集成到项目后崩溃
|
||||
|
||||
### 3.2 根因定位
|
||||
|
||||
**`.env` 中 `IDL_PATH` 变量名与 IDL 内置环境变量冲突。**
|
||||
|
||||
- `IDL_PATH` 是 IDL 的保留环境变量,用于指定 `.pro` 文件搜索路径
|
||||
- 项目 `.env` 曾使用 `IDL_PATH` 存储 `idl.exe` 的可执行文件路径
|
||||
- `load_dotenv()` 将其注入 `os.environ`,子进程继承后 IDL 读到无效的搜索路径
|
||||
- `taskengine.exe` 因此崩溃,退出码 `0xC0000005`
|
||||
|
||||
### 3.3 验证过程
|
||||
|
||||
三态测试确认因果关系:
|
||||
|
||||
| 状态 | IDL_PATH 值 | 结果 |
|
||||
|------|-------------|------|
|
||||
| 干净环境 | 未设置 | ✅ 成功 |
|
||||
| 设置错误值 | `C:\...\idl.exe` | ❌ 崩溃 (0xC0000005) |
|
||||
| 移除后恢复 | 未设置 | ✅ 成功 |
|
||||
|
||||
### 3.4 修复
|
||||
|
||||
将 `.env` 中 `IDL_PATH` 重命名为 `IDL_EXECUTABLE`,同步更新所有引用。
|
||||
|
||||
## 4. 当前架构设计
|
||||
|
||||
### 4.1 设计决策
|
||||
|
||||
1. **envipyengine 为唯一执行引擎** — 删除了 idl 直连和 taskengine 直调的全部代码
|
||||
2. **Import → D-InSAR 固定流水线** — D-InSAR 自动检测未导入数据,先 Import 再处理(智能串联)
|
||||
3. **D-InSAR 使用 metatask** — 精细参数 (filter_method 等) metatask 不支持,已移除
|
||||
4. **DEM 路径为系统级配置** — 存储在 .env,不暴露到前端
|
||||
5. **subprocess 执行模式** — envipyengine 在独立子进程中运行,隔离 FastAPI 主进程
|
||||
|
||||
### 4.2 ENVI Tasks
|
||||
|
||||
| Task | 用途 | 关键参数 |
|
||||
|------|------|----------|
|
||||
| `SARsImportLuTan1` | 导入 LuTan-1 原始数据 | `INPUT_FILE_LIST`, `ROOT_URI_FOR_OUTPUT` |
|
||||
| `SARsMetataskInSARDisplacementGeneration` | D-InSAR 位移生成 | `REFERENCE_SARSCAPEDATA`, `SECONDARY_SARSCAPEDATA`, `DEM_SARSCAPEDATA`, `OUTPUT_FOLDER` |
|
||||
|
||||
### 4.3 智能串联逻辑 (D-InSAR 工作流)
|
||||
|
||||
```
|
||||
对每个 Task_* 文件夹:
|
||||
1. 检查 master/slave 是否有 .sml
|
||||
2. 没有 → 查找 .meta.xml → 自动执行 Import
|
||||
3. Import 完成后验证 .sml 生成
|
||||
4. 执行 D-InSAR metatask
|
||||
```
|
||||
|
||||
### 4.4 API 端点
|
||||
|
||||
```
|
||||
GET /api/idl/status — 系统状态 (含 DEM 路径和可用性)
|
||||
POST /api/idl/launch-workbench — 启动 IDL Workbench
|
||||
POST /api/idl/inspect/import — Import 预检查
|
||||
POST /api/idl/inspect/dinsar — D-InSAR 预检查 (含 Import 状态检测)
|
||||
POST /api/idl/jobs/import — 提交 Import 任务
|
||||
POST /api/idl/jobs/dinsar — 提交 D-InSAR 任务
|
||||
GET /api/idl/jobs/recent — 最近运行记录
|
||||
```
|
||||
|
||||
### 4.5 目录结构要求
|
||||
|
||||
Import 支持两种布局:
|
||||
```
|
||||
# Task_* 结构 (推荐)
|
||||
root_dir/
|
||||
├── Task_001/
|
||||
│ ├── master/ → *.meta.xml
|
||||
│ └── slave/ → *.meta.xml
|
||||
└── Task_002/
|
||||
├── master/
|
||||
└── slave/
|
||||
|
||||
# 平铺结构
|
||||
root_dir/
|
||||
├── scene_001/ → *.meta.xml
|
||||
└── scene_002/ → *.meta.xml
|
||||
```
|
||||
|
||||
D-InSAR 仅支持 Task_* 结构。
|
||||
|
||||
## 5. 测试记录
|
||||
|
||||
### 5.1 envipyengine 验证 (2026-02-20)
|
||||
|
||||
修复 IDL_PATH 冲突后,envipyengine Import 连续测试:
|
||||
|
||||
| 次数 | 耗时 | 结果 |
|
||||
|------|------|------|
|
||||
| 1 | 100.5s | ✅ 成功 |
|
||||
| 2 | 75.8s | ✅ 成功 |
|
||||
| 3 | 71.8s | ✅ 成功 |
|
||||
| 4 | 73.6s | ✅ 成功 |
|
||||
| 5 | 81.7s | ✅ 成功 |
|
||||
|
||||
测试数据: `Z:\Test_data\Test_IDL_1`
|
||||
|
||||
### 5.2 集成测试 — Step 1: Import (2026-02-21)
|
||||
|
||||
重构后通过前端 → 后端 → envipyengine 完整链路测试。
|
||||
|
||||
- 状态: ✅ 通过
|
||||
- 链路: 前端提交 → job_handlers subprocess → envi_runner_cli → envi_service → envipyengine
|
||||
|
||||
### 5.3 集成测试 — Step 2: D-InSAR (待测试)
|
||||
|
||||
- 状态: ⏳ 待测试
|
||||
- 前置条件: DEM 文件 `D:\SRTM30m\SRTMDEM_RSP_SARscape` 需存在于测试机
|
||||
- 智能串联 (自动 Import) 待验证
|
||||
|
||||
## 6. 已删除的遗留代码
|
||||
|
||||
重构中删除的文件 (2026-02-21):
|
||||
|
||||
| 文件 | 行数 | 说明 |
|
||||
|------|------|------|
|
||||
| `backend/app/services/idl_worker_service.py` | 2249 | 三引擎架构的巨型服务 |
|
||||
| `backend/app/services/idl_runner_cli.py` | 58 | 旧 CLI runner |
|
||||
| `backend/app/services/envipyengine_runner.py` | 47 | 调试用独立 runner |
|
||||
|
||||
清理的 .env 配置:所有 `IDL_TASKENGINE_*`、`IDL_WORKER_ENGINE`、`IDL_JOB_RUNNER_MODE`、
|
||||
`IDL_IDL_*`、`IDL_WORKER_PREFLIGHT_*`、D-InSAR 精细参数 (filter_method 等)。
|
||||
|
||||
## 7. 已知限制
|
||||
|
||||
1. **D-InSAR 精细参数不可调** — metatask 不支持 filter_method、unwrapping_coh_threshold 等参数
|
||||
2. **DEM 路径固定** — 从 .env 读取,不支持前端动态指定
|
||||
3. **单进程串行** — envipyengine 调用 taskengine.exe 是同步阻塞的,同一时间只能执行一个 ENVI Task
|
||||
4. **仅支持 LuTan-1** — Import task 为 `SARsImportLuTan1`,其他卫星数据需要不同的 Task
|
||||
@@ -0,0 +1,510 @@
|
||||
# 系统日志管理分析与优化方案(2026-03-05)
|
||||
|
||||
## 当前日志分布情况
|
||||
|
||||
### 1. 根目录日志(❌ 不规范)
|
||||
|
||||
```
|
||||
Z:\Code\Insar_management_system_v2\
|
||||
├── unpacker_activity.log (34KB) - 解包活动日志
|
||||
└── unpacker_log.json (8.6KB) - 解包结构化日志
|
||||
```
|
||||
|
||||
**来源**:`scripts/unpack_archives.py`
|
||||
**问题**:
|
||||
- 直接保存在项目根目录,不专业
|
||||
- 与代码文件混在一起
|
||||
- 不便于管理和清理
|
||||
- 可能被误提交到 Git
|
||||
|
||||
---
|
||||
|
||||
### 2. IDL/ENVI 工作日志
|
||||
|
||||
```
|
||||
backend/runtime/idl_worker/
|
||||
├── 20260225_022409_dinsar_custom.log
|
||||
├── 20260225_051527_dinsar_custom.log
|
||||
├── 20260304_091723_import.log
|
||||
├── 20260304_091854_import.log
|
||||
└── dinsar_custom_progress.log
|
||||
```
|
||||
|
||||
**来源**:ENVI 工作流执行日志
|
||||
**状态**:✅ 已规范化(在 `backend/runtime/` 下)
|
||||
|
||||
---
|
||||
|
||||
### 3. SARscape 工作日志
|
||||
|
||||
```
|
||||
.idl/sarmap/sarscape-3-6_1_0-idl_8_8/
|
||||
├── hwConfig.log
|
||||
└── sarscape_work/
|
||||
├── Process.log
|
||||
├── Process_20260226165004.log
|
||||
├── Process_20260226210207.log
|
||||
└── Process_20260227091802.log
|
||||
```
|
||||
|
||||
**来源**:SARscape 软件自动生成
|
||||
**状态**:✅ 可接受(第三方软件日志)
|
||||
|
||||
---
|
||||
|
||||
### 4. 应用日志(缺失)
|
||||
|
||||
**问题**:
|
||||
- ❌ 没有统一的应用日志目录
|
||||
- ❌ 没有日志轮转机制
|
||||
- ❌ 没有日志级别配置
|
||||
- ❌ 没有结构化日志
|
||||
|
||||
---
|
||||
|
||||
## 日志用途分析
|
||||
|
||||
### unpacker_activity.log
|
||||
|
||||
**用途**:记录解包脚本的活动日志(文本格式)
|
||||
|
||||
**内容示例**:
|
||||
```
|
||||
2026-03-04 20:53:15 - INFO - 开始扫描归档文件...
|
||||
2026-03-04 20:53:15 - INFO - 找到 5 个归档文件
|
||||
2026-03-04 20:53:16 - INFO - 解包完成: archive1.zip
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 保存在根目录
|
||||
- 无日志轮转(会无限增长)
|
||||
- 无日志级别控制
|
||||
|
||||
---
|
||||
|
||||
### unpacker_log.json
|
||||
|
||||
**用途**:记录解包脚本的结构化日志(JSON 格式)
|
||||
|
||||
**内容示例**:
|
||||
```json
|
||||
{
|
||||
"timestamp": "2026-03-04T20:53:15",
|
||||
"level": "INFO",
|
||||
"message": "解包完成",
|
||||
"archive": "archive1.zip",
|
||||
"files_extracted": 123,
|
||||
"duration_seconds": 5.2
|
||||
}
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- 保存在根目录
|
||||
- 与 activity.log 重复
|
||||
- 无清理机制
|
||||
|
||||
---
|
||||
|
||||
## 系统中的其他日志
|
||||
|
||||
### 1. 数据库日志
|
||||
- **位置**:PostgreSQL 数据目录
|
||||
- **管理**:由 PostgreSQL 管理
|
||||
|
||||
### 2. Nginx 日志
|
||||
- **位置**:`nginx/logs/` (如果配置了)
|
||||
- **管理**:由 Nginx 管理
|
||||
|
||||
### 3. 任务日志
|
||||
- **位置**:数据库 `system_task` 表的 `logs` 字段
|
||||
- **管理**:通过 API 查询
|
||||
|
||||
### 4. 进度文件
|
||||
- **位置**:`backend/runtime/idl_worker/job_{job_id}_progress.json`
|
||||
- **管理**:任务完成后自动清理
|
||||
|
||||
---
|
||||
|
||||
## 日志管理问题
|
||||
|
||||
### 问题 1:日志分散
|
||||
|
||||
**现状**:
|
||||
- 根目录:unpacker 日志
|
||||
- backend/runtime/:ENVI 日志
|
||||
- .idl/:SARscape 日志
|
||||
- 数据库:任务日志
|
||||
|
||||
**影响**:
|
||||
- 难以统一查看
|
||||
- 难以统一清理
|
||||
- 难以统一备份
|
||||
|
||||
---
|
||||
|
||||
### 问题 2:无日志轮转
|
||||
|
||||
**现状**:
|
||||
- unpacker_activity.log 会无限增长
|
||||
- ENVI 日志会累积(每次执行生成新文件)
|
||||
|
||||
**影响**:
|
||||
- 磁盘空间浪费
|
||||
- 日志文件过大影响性能
|
||||
|
||||
---
|
||||
|
||||
### 问题 3:无统一日志框架
|
||||
|
||||
**现状**:
|
||||
- unpacker 使用自定义日志
|
||||
- 后端使用 print() 输出
|
||||
- 没有统一的日志级别
|
||||
|
||||
**影响**:
|
||||
- 日志格式不一致
|
||||
- 难以过滤和搜索
|
||||
- 难以集成日志分析工具
|
||||
|
||||
---
|
||||
|
||||
## 优化方案
|
||||
|
||||
### 方案 A:统一日志目录结构(推荐)
|
||||
|
||||
```
|
||||
Z:\Code\Insar_management_system_v2\
|
||||
├── logs/ # 统一日志目录
|
||||
│ ├── app/ # 应用日志
|
||||
│ │ ├── backend.log # 后端主日志
|
||||
│ │ ├── backend.log.1 # 轮转日志
|
||||
│ │ ├── backend.log.2
|
||||
│ │ └── ...
|
||||
│ ├── tasks/ # 任务日志
|
||||
│ │ ├── envi/ # ENVI 工作流日志
|
||||
│ │ │ ├── 20260304_091723_import.log
|
||||
│ │ │ └── ...
|
||||
│ │ └── unpacker/ # 解包任务日志
|
||||
│ │ ├── unpacker_20260304.log
|
||||
│ │ └── ...
|
||||
│ ├── access/ # 访问日志
|
||||
│ │ ├── nginx_access.log
|
||||
│ │ └── api_access.log
|
||||
│ └── error/ # 错误日志
|
||||
│ ├── nginx_error.log
|
||||
│ └── api_error.log
|
||||
├── backend/
|
||||
└── frontend/
|
||||
```
|
||||
|
||||
**优点**:
|
||||
- 所有日志集中管理
|
||||
- 便于备份和清理
|
||||
- 便于配置 .gitignore
|
||||
|
||||
---
|
||||
|
||||
### 方案 B:使用 Python logging 模块
|
||||
|
||||
**配置文件**:`backend/app/logging_config.py`
|
||||
|
||||
```python
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
# 日志根目录
|
||||
LOG_ROOT = Path(__file__).parent.parent.parent / "logs"
|
||||
LOG_ROOT.mkdir(exist_ok=True)
|
||||
|
||||
# 应用日志目录
|
||||
APP_LOG_DIR = LOG_ROOT / "app"
|
||||
APP_LOG_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# 任务日志目录
|
||||
TASK_LOG_DIR = LOG_ROOT / "tasks"
|
||||
TASK_LOG_DIR.mkdir(exist_ok=True)
|
||||
|
||||
# 日志配置
|
||||
LOGGING_CONFIG = {
|
||||
"version": 1,
|
||||
"disable_existing_loggers": False,
|
||||
"formatters": {
|
||||
"default": {
|
||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||
},
|
||||
"detailed": {
|
||||
"format": "%(asctime)s - %(name)s - %(levelname)s - %(filename)s:%(lineno)d - %(message)s",
|
||||
"datefmt": "%Y-%m-%d %H:%M:%S",
|
||||
},
|
||||
"json": {
|
||||
"()": "pythonjsonlogger.jsonlogger.JsonFormatter",
|
||||
"format": "%(asctime)s %(name)s %(levelname)s %(message)s",
|
||||
},
|
||||
},
|
||||
"handlers": {
|
||||
"console": {
|
||||
"class": "logging.StreamHandler",
|
||||
"level": "INFO",
|
||||
"formatter": "default",
|
||||
"stream": "ext://sys.stdout",
|
||||
},
|
||||
"file": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"level": "DEBUG",
|
||||
"formatter": "detailed",
|
||||
"filename": str(APP_LOG_DIR / "backend.log"),
|
||||
"maxBytes": 10 * 1024 * 1024, # 10MB
|
||||
"backupCount": 5,
|
||||
"encoding": "utf-8",
|
||||
},
|
||||
"error_file": {
|
||||
"class": "logging.handlers.RotatingFileHandler",
|
||||
"level": "ERROR",
|
||||
"formatter": "detailed",
|
||||
"filename": str(APP_LOG_DIR / "error.log"),
|
||||
"maxBytes": 10 * 1024 * 1024, # 10MB
|
||||
"backupCount": 5,
|
||||
"encoding": "utf-8",
|
||||
},
|
||||
},
|
||||
"loggers": {
|
||||
"": { # root logger
|
||||
"level": "INFO",
|
||||
"handlers": ["console", "file", "error_file"],
|
||||
},
|
||||
"uvicorn": {
|
||||
"level": "INFO",
|
||||
"handlers": ["console", "file"],
|
||||
"propagate": False,
|
||||
},
|
||||
"sqlalchemy": {
|
||||
"level": "WARNING",
|
||||
"handlers": ["file"],
|
||||
"propagate": False,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def setup_logging():
|
||||
"""初始化日志配置"""
|
||||
import logging.config
|
||||
logging.config.dictConfig(LOGGING_CONFIG)
|
||||
```
|
||||
|
||||
**使用方式**:
|
||||
|
||||
```python
|
||||
# backend/app/main.py
|
||||
from .logging_config import setup_logging
|
||||
|
||||
setup_logging()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
logger.info("应用启动")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方案 C:修复 unpacker 日志位置
|
||||
|
||||
**修改文件**:`scripts/unpack_archives.py`
|
||||
|
||||
```python
|
||||
# 修改前
|
||||
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
||||
LOG_FILE = os.path.join(PROJECT_ROOT, "unpacker_log.json")
|
||||
ACTIVITY_LOG = os.path.join(PROJECT_ROOT, "unpacker_activity.log")
|
||||
|
||||
# 修改后
|
||||
PROJECT_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
|
||||
LOG_DIR = os.path.join(PROJECT_ROOT, "logs", "tasks", "unpacker")
|
||||
os.makedirs(LOG_DIR, exist_ok=True)
|
||||
|
||||
# 使用日期命名,便于清理
|
||||
from datetime import datetime
|
||||
log_date = datetime.now().strftime("%Y%m%d")
|
||||
LOG_FILE = os.path.join(LOG_DIR, f"unpacker_{log_date}.json")
|
||||
ACTIVITY_LOG = os.path.join(LOG_DIR, f"unpacker_{log_date}.log")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 方案 D:添加日志清理机制
|
||||
|
||||
**定期清理脚本**:`scripts/cleanup_logs.py`
|
||||
|
||||
```python
|
||||
import os
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parent.parent
|
||||
LOG_ROOT = PROJECT_ROOT / "logs"
|
||||
|
||||
# 清理策略
|
||||
CLEANUP_RULES = {
|
||||
"logs/app/*.log.*": 30, # 应用日志保留 30 天
|
||||
"logs/tasks/envi/*.log": 7, # ENVI 日志保留 7 天
|
||||
"logs/tasks/unpacker/*.log": 7, # 解包日志保留 7 天
|
||||
}
|
||||
|
||||
def cleanup_old_logs():
|
||||
"""清理过期日志"""
|
||||
now = time.time()
|
||||
for pattern, days in CLEANUP_RULES.items():
|
||||
max_age = days * 86400 # 转换为秒
|
||||
for log_file in LOG_ROOT.glob(pattern):
|
||||
if log_file.is_file():
|
||||
age = now - log_file.stat().st_mtime
|
||||
if age > max_age:
|
||||
print(f"删除过期日志: {log_file} (已存在 {age/86400:.1f} 天)")
|
||||
log_file.unlink()
|
||||
|
||||
if __name__ == "__main__":
|
||||
cleanup_old_logs()
|
||||
```
|
||||
|
||||
**添加到定时任务**:
|
||||
```python
|
||||
# backend/app/main.py
|
||||
from apscheduler.schedulers.asyncio import AsyncIOScheduler
|
||||
|
||||
scheduler = AsyncIOScheduler()
|
||||
|
||||
@app.on_event("startup")
|
||||
async def startup_event():
|
||||
# 每天凌晨 3 点清理日志
|
||||
scheduler.add_job(cleanup_old_logs, "cron", hour=3, minute=0)
|
||||
scheduler.start()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 实施步骤
|
||||
|
||||
### 第一步:创建日志目录结构
|
||||
|
||||
```bash
|
||||
cd Z:\Code\Insar_management_system_v2
|
||||
mkdir -p logs/app
|
||||
mkdir -p logs/tasks/envi
|
||||
mkdir -p logs/tasks/unpacker
|
||||
mkdir -p logs/access
|
||||
mkdir -p logs/error
|
||||
```
|
||||
|
||||
### 第二步:移动现有日志
|
||||
|
||||
```bash
|
||||
# 移动 unpacker 日志
|
||||
mv unpacker_activity.log logs/tasks/unpacker/unpacker_20260304.log
|
||||
mv unpacker_log.json logs/tasks/unpacker/unpacker_20260304.json
|
||||
|
||||
# 移动 ENVI 日志(已经在正确位置)
|
||||
# backend/runtime/idl_worker/ 保持不变
|
||||
```
|
||||
|
||||
### 第三步:修改 unpacker 脚本
|
||||
|
||||
修改 `scripts/unpack_archives.py`,使用新的日志路径。
|
||||
|
||||
### 第四步:配置 Python logging
|
||||
|
||||
创建 `backend/app/logging_config.py`,配置统一日志。
|
||||
|
||||
### 第五步:更新 .gitignore
|
||||
|
||||
```gitignore
|
||||
# 日志文件
|
||||
logs/
|
||||
*.log
|
||||
*.log.*
|
||||
|
||||
# 但保留日志目录结构
|
||||
!logs/.gitkeep
|
||||
!logs/app/.gitkeep
|
||||
!logs/tasks/.gitkeep
|
||||
```
|
||||
|
||||
### 第六步:添加日志清理
|
||||
|
||||
创建 `scripts/cleanup_logs.py`,配置定时清理。
|
||||
|
||||
---
|
||||
|
||||
## 推荐配置
|
||||
|
||||
### 日志级别
|
||||
|
||||
```python
|
||||
# 开发环境
|
||||
LOG_LEVEL = "DEBUG"
|
||||
|
||||
# 生产环境
|
||||
LOG_LEVEL = "INFO"
|
||||
|
||||
# 错误追踪
|
||||
ERROR_LOG_LEVEL = "ERROR"
|
||||
```
|
||||
|
||||
### 日志轮转
|
||||
|
||||
```python
|
||||
# 按大小轮转
|
||||
maxBytes = 10 * 1024 * 1024 # 10MB
|
||||
backupCount = 5 # 保留 5 个备份
|
||||
|
||||
# 按时间轮转
|
||||
when = "midnight" # 每天午夜轮转
|
||||
interval = 1 # 每 1 天
|
||||
backupCount = 30 # 保留 30 天
|
||||
```
|
||||
|
||||
### 日志保留
|
||||
|
||||
```python
|
||||
# 应用日志:30 天
|
||||
# 任务日志:7 天
|
||||
# 错误日志:90 天
|
||||
# 访问日志:30 天
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
### 当前问题
|
||||
1. ❌ unpacker 日志保存在根目录(不专业)
|
||||
2. ❌ 日志分散在多个位置
|
||||
3. ❌ 无统一日志框架
|
||||
4. ❌ 无日志轮转和清理机制
|
||||
|
||||
### 推荐方案
|
||||
1. ✅ 创建统一的 `logs/` 目录
|
||||
2. ✅ 使用 Python logging 模块
|
||||
3. ✅ 配置日志轮转(按大小或时间)
|
||||
4. ✅ 添加定时清理机制
|
||||
5. ✅ 更新 .gitignore
|
||||
|
||||
### 优先级
|
||||
- **P0(立即)**:移动 unpacker 日志到 logs/ 目录
|
||||
- **P1(本周)**:配置 Python logging 模块
|
||||
- **P2(两周)**:添加日志轮转和清理
|
||||
- **P3(一个月)**:集成日志分析工具(如 ELK)
|
||||
|
||||
---
|
||||
|
||||
## 下一步
|
||||
|
||||
需要我开始实施日志管理优化吗?我可以:
|
||||
1. 创建日志目录结构
|
||||
2. 修改 unpacker 脚本
|
||||
3. 配置 Python logging
|
||||
4. 添加日志清理脚本
|
||||
|
||||
还是你想先看看这个方案,再决定是否实施?
|
||||
@@ -0,0 +1,332 @@
|
||||
# 日志管理功能实施总结(2026-03-05)
|
||||
|
||||
## 实施概述
|
||||
|
||||
按照你的需求,实现了统一的日志管理功能:
|
||||
- ✅ 统一日志目录结构
|
||||
- ✅ 前端管理模块(放在运维自检)
|
||||
- ✅ Modal 查看日志
|
||||
- ✅ 手动删除(不自动删除)
|
||||
- ✅ 仅管理员可删除
|
||||
|
||||
---
|
||||
|
||||
## 已完成的工作
|
||||
|
||||
### 1. 创建统一日志目录结构
|
||||
|
||||
```
|
||||
logs/
|
||||
├── app/ # 应用日志
|
||||
├── tasks/ # 任务日志
|
||||
│ ├── envi/ # ENVI 工作流日志
|
||||
│ └── unpacker/ # 解包任务日志
|
||||
└── error/ # 错误日志
|
||||
```
|
||||
|
||||
### 2. 移动现有日志文件
|
||||
|
||||
- ✅ `unpacker_activity.log` → `logs/tasks/unpacker/unpacker_20260304.log`
|
||||
- ✅ `unpacker_log.json` → `logs/tasks/unpacker/unpacker_20260304.json`
|
||||
|
||||
### 3. 修改 unpacker 脚本
|
||||
|
||||
**文件**:`scripts/unpack_archives.py`
|
||||
|
||||
**修改内容**:
|
||||
- 使用统一的日志目录 `logs/tasks/unpacker/`
|
||||
- 使用日期命名日志文件(`unpacker_YYYYMMDD.log`)
|
||||
- 自动创建日志目录
|
||||
|
||||
### 4. 更新 .gitignore
|
||||
|
||||
**新增内容**:
|
||||
```gitignore
|
||||
# 日志文件
|
||||
logs/
|
||||
*.log
|
||||
*.log.*
|
||||
|
||||
# 但保留日志目录结构
|
||||
!logs/.gitkeep
|
||||
!logs/app/.gitkeep
|
||||
!logs/tasks/.gitkeep
|
||||
!logs/error/.gitkeep
|
||||
```
|
||||
|
||||
### 5. 实现后端 API
|
||||
|
||||
**文件**:`backend/app/routers/logs.py`
|
||||
|
||||
**API 端点**:
|
||||
1. `GET /logs/list` - 列出所有日志文件
|
||||
- 支持按类型过滤(app, task, error)
|
||||
- 返回文件名、大小、修改时间
|
||||
|
||||
2. `GET /logs/content/{log_path}` - 读取日志内容
|
||||
- 支持分页读取(offset, limit)
|
||||
- 最多一次读取 10000 行
|
||||
- 防止路径遍历攻击
|
||||
|
||||
3. `DELETE /logs/{log_path}` - 删除日志文件
|
||||
- 仅管理员可删除
|
||||
- 防止路径遍历攻击
|
||||
|
||||
**安全特性**:
|
||||
- 路径安全检查(防止 `../` 攻击)
|
||||
- 权限控制(删除仅管理员)
|
||||
- 文件类型验证
|
||||
|
||||
### 6. 实现前端 API
|
||||
|
||||
**文件**:`frontend/src/api/logs.js`
|
||||
|
||||
**函数**:
|
||||
- `listLogs(logType)` - 获取日志列表
|
||||
- `getLogContent(logPath, offset, limit)` - 获取日志内容
|
||||
- `deleteLog(logPath)` - 删除日志
|
||||
|
||||
### 7. 实现前端日志管理组件
|
||||
|
||||
**文件**:`frontend/src/LogManagementPanel.jsx`
|
||||
|
||||
**功能**:
|
||||
- 日志列表展示(表格形式)
|
||||
- 类型过滤(应用/任务/错误)
|
||||
- 查看日志(Modal)
|
||||
- 删除日志(仅管理员,需确认)
|
||||
- 日志搜索(实时过滤)
|
||||
- 分页加载(大文件支持)
|
||||
|
||||
**UI 特性**:
|
||||
- 类型标签(彩色徽章)
|
||||
- 文件大小格式化(B/KB/MB)
|
||||
- 修改时间显示
|
||||
- 深色代码编辑器风格
|
||||
- 响应式布局
|
||||
|
||||
### 8. 集成到运维自检
|
||||
|
||||
**修改文件**:
|
||||
- `frontend/src/HealthCheckPanel.jsx` - 添加日志管理区域
|
||||
- `frontend/src/App.jsx` - 传递 currentUser 参数
|
||||
|
||||
---
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 日志列表
|
||||
|
||||
| 列名 | 说明 |
|
||||
|------|------|
|
||||
| 文件名 | 日志文件名(等宽字体) |
|
||||
| 类型 | 应用/任务/错误(彩色标签) |
|
||||
| 大小 | 文件大小(自动格式化) |
|
||||
| 修改时间 | 最后修改时间 |
|
||||
| 操作 | 查看/删除按钮 |
|
||||
|
||||
**过滤功能**:
|
||||
- 全部
|
||||
- 应用日志
|
||||
- 任务日志
|
||||
- 错误日志
|
||||
|
||||
### 日志查看 Modal
|
||||
|
||||
**顶部信息栏**:
|
||||
- 文件名(等宽字体)
|
||||
- 文件大小
|
||||
- 修改时间
|
||||
- 总行数
|
||||
|
||||
**搜索栏**:
|
||||
- 实时搜索(过滤日志内容)
|
||||
- 显示当前行范围
|
||||
- 上一页/下一页按钮
|
||||
|
||||
**日志内容区**:
|
||||
- 深色背景(#1e1e1e)
|
||||
- 等宽字体(Consolas, Monaco)
|
||||
- 自动换行
|
||||
- 滚动查看
|
||||
|
||||
### 删除功能
|
||||
|
||||
**权限控制**:
|
||||
- 只有管理员可以看到删除按钮
|
||||
- 非管理员点击会提示"只有管理员可以删除日志"
|
||||
|
||||
**确认对话框**:
|
||||
```
|
||||
确定要删除日志文件 "xxx.log" 吗?
|
||||
|
||||
此操作不可恢复!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 技术实现
|
||||
|
||||
### 后端
|
||||
|
||||
**路径安全检查**:
|
||||
```python
|
||||
def _is_safe_path(file_path: str) -> bool:
|
||||
"""检查路径是否安全(防止路径遍历攻击)"""
|
||||
try:
|
||||
requested_path = (LOG_ROOT / file_path).resolve()
|
||||
return requested_path.is_relative_to(LOG_ROOT)
|
||||
except (ValueError, RuntimeError):
|
||||
return False
|
||||
```
|
||||
|
||||
**分页读取**:
|
||||
```python
|
||||
with open(log_file, "r", encoding="utf-8", errors="replace") as f:
|
||||
lines = f.readlines()
|
||||
|
||||
total_lines = len(lines)
|
||||
start = offset
|
||||
end = min(offset + limit, total_lines)
|
||||
content_lines = lines[start:end]
|
||||
```
|
||||
|
||||
### 前端
|
||||
|
||||
**分页加载**:
|
||||
```javascript
|
||||
const loadLogContent = async (logPath, offset = 0) => {
|
||||
const data = await getLogContent(logPath, offset, 1000);
|
||||
setLogContent(data.content);
|
||||
setTotalLines(data.total_lines);
|
||||
setCurrentOffset(offset);
|
||||
};
|
||||
```
|
||||
|
||||
**实时搜索**:
|
||||
```javascript
|
||||
const filteredContent = searchTerm
|
||||
? logContent.split('\n')
|
||||
.filter(line => line.toLowerCase().includes(searchTerm.toLowerCase()))
|
||||
.join('\n')
|
||||
: logContent;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 查看日志
|
||||
|
||||
1. 登录系统
|
||||
2. 进入"运维自检" Tab
|
||||
3. 滚动到"日志管理"区域
|
||||
4. 点击日志文件的"查看"按钮
|
||||
5. 在 Modal 中查看日志内容
|
||||
6. 使用搜索框过滤内容
|
||||
7. 使用上一页/下一页浏览大文件
|
||||
|
||||
### 删除日志
|
||||
|
||||
1. 以管理员身份登录
|
||||
2. 进入"运维自检" Tab
|
||||
3. 找到要删除的日志文件
|
||||
4. 点击"删除"按钮
|
||||
5. 确认删除操作
|
||||
6. 日志文件被永久删除
|
||||
|
||||
### 过滤日志
|
||||
|
||||
1. 使用"类型过滤"下拉菜单
|
||||
2. 选择"应用日志"、"任务日志"或"错误日志"
|
||||
3. 列表自动更新
|
||||
|
||||
---
|
||||
|
||||
## 文件清单
|
||||
|
||||
### 后端文件
|
||||
- `backend/app/routers/logs.py` - 日志管理 API(新建)
|
||||
- `backend/app/routers/__init__.py` - 注册日志路由(修改)
|
||||
- `scripts/unpack_archives.py` - 使用新日志路径(修改)
|
||||
|
||||
### 前端文件
|
||||
- `frontend/src/api/logs.js` - 日志 API 封装(新建)
|
||||
- `frontend/src/LogManagementPanel.jsx` - 日志管理组件(新建)
|
||||
- `frontend/src/HealthCheckPanel.jsx` - 集成日志管理(修改)
|
||||
- `frontend/src/App.jsx` - 传递 currentUser(修改)
|
||||
|
||||
### 配置文件
|
||||
- `.gitignore` - 忽略日志文件(新建)
|
||||
|
||||
### 目录结构
|
||||
- `logs/` - 统一日志目录(新建)
|
||||
- `logs/app/` - 应用日志目录(新建)
|
||||
- `logs/tasks/envi/` - ENVI 日志目录(新建)
|
||||
- `logs/tasks/unpacker/` - 解包日志目录(新建)
|
||||
- `logs/error/` - 错误日志目录(新建)
|
||||
|
||||
---
|
||||
|
||||
## 测试清单
|
||||
|
||||
### 后端测试
|
||||
- [ ] 启动后端服务
|
||||
- [ ] 访问 `/api/logs/list` 验证 API
|
||||
- [ ] 访问 `/api/logs/content/{log_path}` 验证内容读取
|
||||
- [ ] 测试路径遍历攻击(应该被拒绝)
|
||||
- [ ] 测试删除权限(非管理员应该被拒绝)
|
||||
|
||||
### 前端测试
|
||||
- [ ] 打包前端(`npm run build`)
|
||||
- [ ] 登录系统
|
||||
- [ ] 进入"运维自检" Tab
|
||||
- [ ] 验证日志列表显示
|
||||
- [ ] 点击"查看"按钮验证 Modal
|
||||
- [ ] 测试搜索功能
|
||||
- [ ] 测试分页功能
|
||||
- [ ] 测试删除功能(管理员)
|
||||
- [ ] 测试删除权限(非管理员应该看不到删除按钮)
|
||||
|
||||
### 集成测试
|
||||
- [ ] 运行 unpacker 脚本,验证日志保存到新位置
|
||||
- [ ] 在前端查看新生成的日志
|
||||
- [ ] 删除日志后验证文件确实被删除
|
||||
|
||||
---
|
||||
|
||||
## 下一步优化(可选)
|
||||
|
||||
### 短期优化
|
||||
1. 添加日志下载功能(如果需要)
|
||||
2. 添加日志实时刷新(WebSocket)
|
||||
3. 添加日志高亮(错误/警告)
|
||||
|
||||
### 长期优化
|
||||
1. 使用 Monaco Editor 替代 Textarea
|
||||
2. 添加日志统计(错误数、警告数)
|
||||
3. 添加日志归档功能
|
||||
4. 集成日志分析工具(ELK)
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
✅ 已完成所有需求:
|
||||
1. 统一日志管理 ✅
|
||||
2. 前端管理模块(放在运维自检)✅
|
||||
3. Modal 查看日志 ✅
|
||||
4. 手动删除(不自动删除)✅
|
||||
5. 仅管理员可删除 ✅
|
||||
|
||||
**代码修改**:
|
||||
- 新建文件:5 个
|
||||
- 修改文件:4 个
|
||||
- 总代码行数:约 400 行
|
||||
|
||||
**风险评估**:🟢 低风险
|
||||
- 不影响现有功能
|
||||
- 只读操作无风险
|
||||
- 删除操作有权限控制和确认
|
||||
|
||||
**状态**:✅ 代码完成,等待测试验证
|
||||
@@ -0,0 +1,86 @@
|
||||
# 源影像叠加稳定化方案(Scheme B)
|
||||
|
||||
目标:解决“覆盖面是斜四边形,源影像是矩形包围框”的错位问题,优先保障长期稳定与前端流畅。
|
||||
|
||||
---
|
||||
|
||||
## 1. 问题本质
|
||||
|
||||
当前地图展示中:
|
||||
- 覆盖范围来自 `coverage_polygon`(真实斜四边形)
|
||||
- 源图叠加常用 `imageOverlay(url, bounds)`(矩形边界)
|
||||
|
||||
当源图本身有旋转/倾斜时,直接矩形贴图会产生明显错位。
|
||||
|
||||
---
|
||||
|
||||
## 2. 方案选择
|
||||
|
||||
### A(前端实时变换)
|
||||
- 在浏览器端做逐帧几何变换
|
||||
- 优点:改动快
|
||||
- 缺点:前端负载高,图层增多后容易卡顿
|
||||
|
||||
### B(后端预纠正缓存)✅
|
||||
- 扫描/构建阶段在后端完成几何纠正,前端只显示结果图
|
||||
- 优点:渲染轻、稳定性高、易观测
|
||||
- 缺点:后端实现复杂度更高
|
||||
|
||||
本项目已确定采用 **B 方案**。
|
||||
|
||||
---
|
||||
|
||||
## 3. 当前实现(2026-02-10)
|
||||
|
||||
## 3.1 缓存分层
|
||||
- `backend/image_cache/radar_geo/`:地理纠正后的主缓存
|
||||
- `backend/image_cache/radar_raw/`:原图回退缓存
|
||||
|
||||
## 3.2 后端能力
|
||||
- 默认由手动扫描任务触发增量构建(前端“立即扫描”或 `POST /api/monitor/run-now`)
|
||||
- 扫描时增量构建纠正缓存(`radar_geo`)
|
||||
- 同步维护原图缓存(`radar_raw`)作为兜底
|
||||
- 方向判定优先使用 XML 的 `sceneCornerCoord/refRow/refColumn`,避免仅靠角点名称导致镜像/翻转误判
|
||||
- `GET /api/radar-data/{id}/thumb`:
|
||||
- 优先返回 `radar_geo`
|
||||
- 失败自动回退 `radar_raw`
|
||||
- 新增状态接口:
|
||||
- `GET /api/radar-data/{id}/preview-status`
|
||||
- `POST /api/radar-data/{id}/rebuild-preview-cache`(管理员)
|
||||
|
||||
## 3.3 前端能力
|
||||
- 每条源影像显示预览状态(纠正/回退/失败/未建)
|
||||
- 管理员支持“重建”按钮
|
||||
- 预览请求增加缓存键,避免浏览器长期缓存旧图
|
||||
|
||||
---
|
||||
|
||||
## 4. 关键配置项
|
||||
|
||||
```env
|
||||
RADAR_GEO_CACHE_WORKERS=2
|
||||
RADAR_GEO_CACHE_VERSION=b1
|
||||
RADAR_GEO_CACHE_QUALITY=84
|
||||
RADAR_PREVIEW_BUILD_ON_DEMAND=true
|
||||
```
|
||||
|
||||
建议:
|
||||
- 小规模先用 `RADAR_GEO_CACHE_WORKERS=1~2`
|
||||
- 算法升级时提高 `RADAR_GEO_CACHE_VERSION` 触发重建
|
||||
|
||||
---
|
||||
|
||||
## 5. 验收标准
|
||||
|
||||
- 同一场景下,源图与覆盖面边界明显趋于一致
|
||||
- 缩放/平移时交互平滑,无明显掉帧
|
||||
- 构建失败可在状态接口与日志中定位原因
|
||||
- 失败场景可回退原图缓存,不阻塞业务操作
|
||||
|
||||
---
|
||||
|
||||
## 6. 后续建议
|
||||
|
||||
1. 采集 3 类典型数据(升轨/降轨/大倾斜)做对齐验收
|
||||
2. 统计 `radar_geo` 构建失败原因并分级治理
|
||||
3. 发布前执行一次全量重建,提升线上命中率
|
||||
@@ -0,0 +1,89 @@
|
||||
# 项目代码审计报告(2026-03-04)
|
||||
|
||||
审计范围:后端核心链路、前端鉴权调用链、启动与脚本安全边界。
|
||||
审计方式:静态只读审计(未修改业务代码)。
|
||||
|
||||
## 1. 高危问题
|
||||
|
||||
### 1.1 ENVI 主流程成功后不返回结果,导致下游按 `dict` 使用时崩溃
|
||||
- 证据:
|
||||
- `backend/app/services/envi_service.py:1568`
|
||||
- `backend/app/services/envi_service.py:1630`
|
||||
- `backend/app/services/envi_runner_cli.py:44`
|
||||
- `backend/app/services/envi_runner_cli.py:51`
|
||||
- `backend/app/services/job_handlers.py:1020`
|
||||
- `backend/app/services/job_handlers.py:1031`
|
||||
- 影响:任务可能在实际执行后仍被标记失败或抛异常。
|
||||
|
||||
### 1.2 只读用户可通过 `GET` 触发写操作和高开销构建,绕过读写分离意图
|
||||
- 证据:
|
||||
- `backend/app/routers/radar.py:619`
|
||||
- `backend/app/routers/radar.py:365`
|
||||
- `backend/app/routers/radar.py:346`
|
||||
- `backend/app/routers/dinsar.py:214`
|
||||
- `backend/app/routers/dinsar.py:146`
|
||||
- `backend/app/routers/dependencies.py:196`
|
||||
- `backend/app/routers/dependencies.py:315`
|
||||
- 影响:读账号可诱发缓存重建/数据库写入,存在资源滥用风险。
|
||||
|
||||
### 1.3 AOI token 内存存储无容量上限,且 token 可续期
|
||||
- 证据:
|
||||
- `backend/app/routers/dependencies.py:131`
|
||||
- `backend/app/routers/dependencies.py:825`
|
||||
- `backend/app/routers/dependencies.py:850`
|
||||
- `backend/app/routers/dependencies.py:59`
|
||||
- `backend/app/routers/radar.py:439`
|
||||
- `backend/app/routers/radar.py:509`
|
||||
- 影响:认证后低权限账号可通过频繁检索制造进程内存膨胀(DoS 面)。
|
||||
|
||||
## 2. 中危问题
|
||||
|
||||
### 2.1 `extract_disp_results` 失败计数重复累加
|
||||
- 证据:
|
||||
- `backend/app/services/envi_service.py:1717`
|
||||
- `backend/app/services/envi_service.py:1726`
|
||||
- `backend/app/services/envi_service.py:1729`
|
||||
- 影响:统计报表和告警判断失真。
|
||||
|
||||
### 2.2 AOI 文件解析异常未统一转为 4xx,用户输入可触发 500
|
||||
- 证据:
|
||||
- `backend/app/routers/dependencies.py:792`
|
||||
- `backend/app/routers/dependencies.py:800`
|
||||
- 影响:可用性下降,错误语义不一致。
|
||||
|
||||
### 2.3 路径归属判断使用 `startswith`,存在前缀误匹配
|
||||
- 证据:
|
||||
- `backend/app/services/data_service.py:530`
|
||||
- 影响:可能将不属于监控根目录的路径误判为合法。
|
||||
|
||||
### 2.4 解包安全校验仅检查 `member.name`,仍使用 `extractall`
|
||||
- 证据:
|
||||
- `scripts/unpack_archives.py:98`
|
||||
- `scripts/unpack_archives.py:107`
|
||||
- `scripts/unpack_archives.py:181`
|
||||
- 影响:若归档文件不可信,仍可能存在目录逃逸/覆盖风险(尤其符号链接/硬链接场景)。
|
||||
|
||||
## 3. 低危问题
|
||||
|
||||
### 3.1 启动脚本写 `nginx.conf` 未显式编码
|
||||
- 证据:
|
||||
- `scripts/start_app.ps1:388`
|
||||
- 影响:在 Windows PowerShell 5 环境可能写出 UTF-16,导致 Nginx 配置解析异常。
|
||||
|
||||
## 4. 已复核并排除
|
||||
|
||||
### 4.1 `task_service.create_task` 的 `task_id` 未定义问题为误报
|
||||
- 证据:
|
||||
- `backend/app/services/task_service.py:153`
|
||||
- 结论:`task_id` 已正常赋值。
|
||||
|
||||
## 5. 假设与待确认
|
||||
|
||||
1. 解包问题是否上调为“高危”,取决于归档输入是否可被外部/低信任来源控制。
|
||||
2. “只读账号触发缓存构建”是否视为权限缺陷,取决于产品策略是否允许此行为。
|
||||
|
||||
## 6. 说明
|
||||
|
||||
- 本报告对应一次只读审计,不包含代码修复提交。
|
||||
- 可在确认后按优先级先修复:1.1 / 1.2 / 1.3。
|
||||
|
||||
@@ -0,0 +1,577 @@
|
||||
# 安全审计分析与修复方案(2026-03-04)
|
||||
|
||||
## 审计概述
|
||||
|
||||
本文档针对 `SECURITY_AUDIT_2026-03-04.md` 中提出的安全问题进行详细分析,并提供具体的修复方案。
|
||||
|
||||
审计发现:
|
||||
- 🔴 高危问题:3 个
|
||||
- 🟡 中危问题:4 个
|
||||
- 🟢 低危问题:1 个
|
||||
|
||||
---
|
||||
|
||||
## 1. 高危问题分析与修复方案
|
||||
|
||||
### 1.1 ENVI 主流程成功后不返回结果
|
||||
|
||||
**问题描述**:
|
||||
- `envi_service.py:run_workflow()` 在成功执行后不返回 `record` 字典
|
||||
- `envi_runner_cli.py` 期望打印 JSON 结果,但 `run_workflow()` 在成功时返回 `None`
|
||||
- `job_handlers.py:_run_envi_workflow_job()` 尝试将返回值作为字典使用,导致崩溃
|
||||
|
||||
**根本原因**:
|
||||
```python
|
||||
# envi_service.py:1630
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
# 缺少: return record
|
||||
```
|
||||
|
||||
**影响等级**:🔴 高危
|
||||
- 所有 ENVI 工作流任务在成功执行后会被误判为失败
|
||||
- 下游代码尝试访问 `run_meta.get('workflow')` 时会抛出 `AttributeError`
|
||||
|
||||
**修复方案**:
|
||||
```python
|
||||
# envi_service.py:1630 之后添加
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
return record # 添加此行
|
||||
```
|
||||
|
||||
**验证方法**:
|
||||
1. 运行一个 D-InSAR 工作流任务
|
||||
2. 检查任务状态是否正确标记为 SUCCESS
|
||||
3. 检查日志中是否包含 `workflow=dinsar_custom duration=XXXs`
|
||||
|
||||
---
|
||||
|
||||
### 1.2 只读用户可通过 GET 触发写操作
|
||||
|
||||
**问题描述**:
|
||||
多个 GET 端点在只读操作中触发了数据库写入和高开销计算:
|
||||
|
||||
1. **`GET /radar-data/{data_id}/thumb`** (radar.py:622)
|
||||
- 调用 `_get_cached_radar_preview()` → `_build_radar_preview_cache()`
|
||||
- 触发预览图生成(GDAL 处理)并写入数据库
|
||||
|
||||
2. **`GET /radar-data/imaging-dates`** (radar.py:609)
|
||||
- 虽然是只读查询,但在 `_build_radar_preview_cache()` 中会更新数据库
|
||||
|
||||
3. **`GET /dinsar-results/{result_id}/preview`** (dinsar.py:214)
|
||||
- 类似问题,按需构建预览缓存
|
||||
|
||||
**根本原因**:
|
||||
```python
|
||||
# radar.py:365
|
||||
if settings.RADAR_PREVIEW_BUILD_ON_DEMAND:
|
||||
build_status = await _build_radar_preview_cache(record, db, force=False)
|
||||
# 内部会执行 db.add(record) 和 db.commit()
|
||||
```
|
||||
|
||||
**影响等级**:🔴 高危
|
||||
- 只读账号可以触发大量 GDAL 处理任务,导致 CPU/内存耗尽(DoS)
|
||||
- 违反最小权限原则,读账号不应有写权限
|
||||
- 可能导致数据库锁竞争
|
||||
|
||||
**修复方案**:
|
||||
|
||||
**方案 A:权限分离(推荐)**
|
||||
```python
|
||||
# 在 dependencies.py 中添加权限检查
|
||||
def require_write_permission(current_user: User = Depends(get_current_user)):
|
||||
if current_user.role == "readonly":
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail="此操作需要写权限"
|
||||
)
|
||||
return current_user
|
||||
|
||||
# 在 radar.py 中修改端点
|
||||
@router.get("/radar-data/{data_id}/thumb")
|
||||
async def get_radar_data_thumb_endpoint(
|
||||
data_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
_: User = Depends(require_write_permission) # 添加权限检查
|
||||
):
|
||||
# 仅当用户有写权限时才允许按需构建
|
||||
...
|
||||
```
|
||||
|
||||
**方案 B:配置控制**
|
||||
```python
|
||||
# 在 .env 中添加
|
||||
RADAR_PREVIEW_BUILD_ON_DEMAND_READONLY=false
|
||||
|
||||
# 在 radar.py 中修改
|
||||
if settings.RADAR_PREVIEW_BUILD_ON_DEMAND:
|
||||
if current_user.role == "readonly" and not settings.RADAR_PREVIEW_BUILD_ON_DEMAND_READONLY:
|
||||
# 只返回已有缓存,不触发构建
|
||||
raise HTTPException(status_code=404, detail="预览图未生成")
|
||||
build_status = await _build_radar_preview_cache(record, db, force=False)
|
||||
```
|
||||
|
||||
**方案 C:改为 POST 端点(最符合 RESTful 原则)**
|
||||
```python
|
||||
# 将按需构建改为显式 POST 操作
|
||||
@router.post("/radar-data/{data_id}/build-preview")
|
||||
async def build_radar_preview_endpoint(
|
||||
data_id: int,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
current_user: User = Depends(require_write_permission)
|
||||
):
|
||||
# 显式构建预览图
|
||||
...
|
||||
|
||||
# GET 端点只返回已有缓存
|
||||
@router.get("/radar-data/{data_id}/thumb")
|
||||
async def get_radar_data_thumb_endpoint(data_id: int, db: AsyncSession = Depends(get_db)):
|
||||
# 不触发构建,只返回已有文件
|
||||
if not os.path.exists(geo_cache_path):
|
||||
raise HTTPException(status_code=404, detail="预览图未生成,请先调用构建接口")
|
||||
return FileResponse(geo_cache_path, ...)
|
||||
```
|
||||
|
||||
**推荐方案**:方案 C(最符合 RESTful 原则)
|
||||
|
||||
---
|
||||
|
||||
### 1.3 AOI token 内存存储无容量上限
|
||||
|
||||
**问题描述**:
|
||||
- `_AOI_TOKEN_STORE` 是全局字典,无大小限制
|
||||
- 每次查询 AOI 时会续期 token(`expires_at = now + AOI_TOKEN_TTL_SECONDS`)
|
||||
- 恶意用户可以通过频繁查询不同 AOI 区域来填满内存
|
||||
|
||||
**根本原因**:
|
||||
```python
|
||||
# dependencies.py:131
|
||||
_AOI_TOKEN_STORE: Dict[str, Dict[str, Any]] = {} # 无容量限制
|
||||
|
||||
# dependencies.py:850
|
||||
payload["expires_at"] = now + AOI_TOKEN_TTL_SECONDS # 每次访问都续期
|
||||
```
|
||||
|
||||
**影响等级**:🔴 高危
|
||||
- 认证后的低权限账号可以制造内存泄漏
|
||||
- 30 分钟 TTL + 续期机制 = 长期占用内存
|
||||
- 可能导致进程 OOM 崩溃
|
||||
|
||||
**修复方案**:
|
||||
|
||||
**方案 A:添加容量上限(推荐)**
|
||||
```python
|
||||
# dependencies.py
|
||||
AOI_TOKEN_MAX_STORE_SIZE = read_int_env(
|
||||
"AOI_TOKEN_MAX_STORE_SIZE",
|
||||
1000, # 最多存储 1000 个 token
|
||||
minimum=100,
|
||||
maximum=10000,
|
||||
)
|
||||
|
||||
async def _store_aoi_token(aoi_wkt: str, feature_collection: Dict[str, Any]) -> str:
|
||||
token = uuid.uuid4().hex
|
||||
now = time.monotonic()
|
||||
async with _AOI_TOKEN_LOCK:
|
||||
_cleanup_expired_aoi_tokens(now)
|
||||
|
||||
# 检查容量上限
|
||||
if len(_AOI_TOKEN_STORE) >= AOI_TOKEN_MAX_STORE_SIZE:
|
||||
# 删除最旧的 token(按 expires_at 排序)
|
||||
oldest_tokens = sorted(
|
||||
_AOI_TOKEN_STORE.items(),
|
||||
key=lambda x: x[1]["expires_at"]
|
||||
)[:100] # 删除最旧的 100 个
|
||||
for old_token, _ in oldest_tokens:
|
||||
_AOI_TOKEN_STORE.pop(old_token, None)
|
||||
|
||||
_AOI_TOKEN_STORE[token] = {
|
||||
"aoi_wkt": aoi_wkt,
|
||||
"aoi_geojson": feature_collection,
|
||||
"expires_at": now + AOI_TOKEN_TTL_SECONDS,
|
||||
}
|
||||
return token
|
||||
```
|
||||
|
||||
**方案 B:限制续期次数**
|
||||
```python
|
||||
# 在 token payload 中添加续期计数
|
||||
_AOI_TOKEN_STORE[token] = {
|
||||
"aoi_wkt": aoi_wkt,
|
||||
"aoi_geojson": feature_collection,
|
||||
"expires_at": now + AOI_TOKEN_TTL_SECONDS,
|
||||
"renew_count": 0, # 添加续期计数
|
||||
"max_renews": 5, # 最多续期 5 次
|
||||
}
|
||||
|
||||
# 在 _get_aoi_from_token 中检查
|
||||
async def _get_aoi_from_token(aoi_token: Optional[str]) -> Optional[Tuple[str, Dict[str, Any]]]:
|
||||
...
|
||||
payload = _AOI_TOKEN_STORE.get(normalized_token)
|
||||
if not payload:
|
||||
return None
|
||||
|
||||
# 检查续期次数
|
||||
if payload.get("renew_count", 0) >= payload.get("max_renews", 5):
|
||||
# 达到续期上限,不再续期
|
||||
return payload.get("aoi_wkt"), payload.get("aoi_geojson")
|
||||
|
||||
payload["expires_at"] = now + AOI_TOKEN_TTL_SECONDS
|
||||
payload["renew_count"] = payload.get("renew_count", 0) + 1
|
||||
return payload.get("aoi_wkt"), payload.get("aoi_geojson")
|
||||
```
|
||||
|
||||
**方案 C:使用 Redis(生产环境推荐)**
|
||||
```python
|
||||
# 使用 Redis 替代内存字典,自动过期
|
||||
import redis.asyncio as redis
|
||||
|
||||
_redis_client = redis.Redis(
|
||||
host=settings.REDIS_HOST,
|
||||
port=settings.REDIS_PORT,
|
||||
decode_responses=False,
|
||||
)
|
||||
|
||||
async def _store_aoi_token(aoi_wkt: str, feature_collection: Dict[str, Any]) -> str:
|
||||
token = uuid.uuid4().hex
|
||||
payload = {
|
||||
"aoi_wkt": aoi_wkt,
|
||||
"aoi_geojson": feature_collection,
|
||||
}
|
||||
await _redis_client.setex(
|
||||
f"aoi_token:{token}",
|
||||
AOI_TOKEN_TTL_SECONDS,
|
||||
json.dumps(payload)
|
||||
)
|
||||
return token
|
||||
```
|
||||
|
||||
**推荐方案**:方案 A(短期)+ 方案 C(长期)
|
||||
|
||||
---
|
||||
|
||||
## 2. 中危问题分析与修复方案
|
||||
|
||||
### 2.1 extract_disp_results 失败计数重复累加
|
||||
|
||||
**问题描述**:
|
||||
```python
|
||||
# envi_service.py:1717, 1726, 1729
|
||||
except OSError as e:
|
||||
task_failed += 1
|
||||
failed += 1 # 第一次累加
|
||||
task_status = f"error: {e}"
|
||||
...
|
||||
failed += task_failed # 第二次累加(1729 行)
|
||||
```
|
||||
|
||||
**影响等级**:🟡 中危
|
||||
- 统计数据不准确,影响监控和告警
|
||||
- 可能导致误报(失败数翻倍)
|
||||
|
||||
**修复方案**:
|
||||
```python
|
||||
# envi_service.py:1717 和 1726 行,删除 failed += 1
|
||||
except OSError as e:
|
||||
task_failed += 1
|
||||
# 删除: failed += 1
|
||||
task_status = f"error: {e}"
|
||||
|
||||
# 保留 1729 行的汇总
|
||||
failed += task_failed # 只在这里累加一次
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 AOI 文件解析异常未统一转为 4xx
|
||||
|
||||
**问题描述**:
|
||||
```python
|
||||
# dependencies.py:792, 800
|
||||
# 解析 GeoJSON/Shapefile 时的异常直接抛出,返回 500
|
||||
aoi_wkt = _parse_geojson_to_wkt(geojson_data) # 可能抛出 ValueError
|
||||
```
|
||||
|
||||
**影响等级**:🟡 中危
|
||||
- 用户输入错误导致 500 错误,应该返回 400
|
||||
- 错误语义不一致,影响 API 可用性
|
||||
|
||||
**修复方案**:
|
||||
```python
|
||||
# dependencies.py 中添加统一异常处理
|
||||
try:
|
||||
aoi_wkt = _parse_geojson_to_wkt(geojson_data)
|
||||
except (ValueError, KeyError, TypeError) as e:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"GeoJSON 格式错误: {str(e)}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"AOI 解析失败: {e}")
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="AOI 解析失败,请联系管理员"
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.3 路径归属判断使用 startswith
|
||||
|
||||
**问题描述**:
|
||||
```python
|
||||
# data_service.py:530
|
||||
if not any(file_path_abs.startswith(root) for root in radar_roots):
|
||||
continue
|
||||
```
|
||||
|
||||
**影响等级**:🟡 中危
|
||||
- 前缀误匹配:`/data/radar` 会匹配 `/data/radar_backup`
|
||||
- 可能将不属于监控目录的文件误判为合法
|
||||
|
||||
**修复方案**:
|
||||
```python
|
||||
# 使用 os.path.commonpath 或规范化路径比较
|
||||
def is_subpath(child: str, parent: str) -> bool:
|
||||
"""检查 child 是否是 parent 的子路径"""
|
||||
try:
|
||||
child_abs = os.path.abspath(child)
|
||||
parent_abs = os.path.abspath(parent)
|
||||
common = os.path.commonpath([child_abs, parent_abs])
|
||||
return common == parent_abs
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
# data_service.py:530
|
||||
if not any(is_subpath(file_path_abs, root) for root in radar_roots):
|
||||
continue
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.4 解包安全校验仅检查 member.name
|
||||
|
||||
**问题描述**:
|
||||
```python
|
||||
# unpack_archives.py:98-104
|
||||
def _is_safe_tar_member(member_name):
|
||||
norm_name = os.path.normpath(member_name)
|
||||
if os.path.isabs(norm_name):
|
||||
return False
|
||||
if norm_name.startswith(".."):
|
||||
return False
|
||||
return True
|
||||
|
||||
# 但仍使用 extractall,未检查符号链接/硬链接
|
||||
tar.extractall(path=tmp_dir) # 181 行
|
||||
```
|
||||
|
||||
**影响等级**:🟡 中危
|
||||
- 符号链接可以指向任意路径(如 `/etc/passwd`)
|
||||
- 硬链接可以覆盖系统文件
|
||||
- 如果归档文件来自不可信来源,存在目录逃逸风险
|
||||
|
||||
**修复方案**:
|
||||
```python
|
||||
# unpack_archives.py
|
||||
def _is_safe_tar_member(member):
|
||||
"""检查 tar 成员是否安全(包括符号链接检查)"""
|
||||
# 检查路径
|
||||
norm_name = os.path.normpath(member.name)
|
||||
if os.path.isabs(norm_name):
|
||||
return False
|
||||
if norm_name.startswith("..") or norm_name.startswith("../") or norm_name.startswith("..\\"):
|
||||
return False
|
||||
|
||||
# 检查符号链接
|
||||
if member.issym() or member.islnk():
|
||||
# 检查链接目标是否安全
|
||||
link_target = member.linkname
|
||||
if os.path.isabs(link_target) or link_target.startswith(".."):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _validate_tar_members(tar_obj, archive_path):
|
||||
for member in tar_obj.getmembers():
|
||||
if not _is_safe_tar_member(member): # 传入 member 对象而非 name
|
||||
raise IOError(f"unsafe tar entry detected: {member.name} in {archive_path}")
|
||||
|
||||
# 使用安全的逐个提取方式
|
||||
def _safe_extract_tar(tar_obj, output_dir):
|
||||
"""安全地提取 tar 文件"""
|
||||
for member in tar_obj.getmembers():
|
||||
if not _is_safe_tar_member(member):
|
||||
raise IOError(f"unsafe tar entry: {member.name}")
|
||||
|
||||
# 逐个提取,避免 extractall
|
||||
tar_obj.extract(member, path=output_dir, filter='data') # Python 3.12+
|
||||
# 或者对于旧版本:
|
||||
# tar_obj.extract(member, path=output_dir)
|
||||
|
||||
# 在 181 行替换
|
||||
with tarfile.open(archive_path, "r:*") as tar:
|
||||
_validate_tar_members(tar, archive_path)
|
||||
_safe_extract_tar(tar, tmp_dir) # 替换 tar.extractall
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 低危问题分析与修复方案
|
||||
|
||||
### 3.1 启动脚本写 nginx.conf 未显式编码
|
||||
|
||||
**问题描述**:
|
||||
```powershell
|
||||
# start_app.ps1:388
|
||||
$NewConfContent | Set-Content -LiteralPath "$NginxConfPath" -NoNewline
|
||||
# 未指定 -Encoding UTF8
|
||||
```
|
||||
|
||||
**影响等级**:🟢 低危
|
||||
- PowerShell 5 默认使用 UTF-16 LE 编码
|
||||
- Nginx 期望 UTF-8 编码,可能导致配置解析失败
|
||||
|
||||
**修复方案**:
|
||||
```powershell
|
||||
# start_app.ps1:388
|
||||
$NewConfContent | Set-Content -LiteralPath "$NginxConfPath" -NoNewline -Encoding UTF8
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 修复优先级建议
|
||||
|
||||
### P0(立即修复)
|
||||
1. ✅ **1.1 ENVI 主流程返回值缺失** - 影响所有工作流任务
|
||||
2. ✅ **1.2 只读用户触发写操作** - 权限绕过 + DoS 风险
|
||||
|
||||
### P1(本周内修复)
|
||||
3. ✅ **1.3 AOI token 内存泄漏** - DoS 风险
|
||||
4. ✅ **2.1 失败计数重复累加** - 影响监控准确性
|
||||
|
||||
### P2(两周内修复)
|
||||
5. ✅ **2.2 AOI 解析异常处理** - API 可用性问题
|
||||
6. ✅ **2.3 路径归属判断** - 潜在安全风险
|
||||
|
||||
### P3(一个月内修复)
|
||||
7. ✅ **2.4 解包安全校验** - 取决于归档文件来源可信度
|
||||
8. ✅ **3.1 PowerShell 编码** - 低影响,但易修复
|
||||
|
||||
---
|
||||
|
||||
## 5. 修复验证清单
|
||||
|
||||
### 1.1 ENVI 主流程返回值
|
||||
- [ ] 运行 D-InSAR 工作流任务
|
||||
- [ ] 检查任务状态是否为 SUCCESS
|
||||
- [ ] 检查日志是否包含 `workflow=dinsar_custom`
|
||||
|
||||
### 1.2 只读用户权限
|
||||
- [ ] 使用只读账号访问 `/radar-data/{id}/thumb`
|
||||
- [ ] 验证是否返回 403 或 404(不触发构建)
|
||||
- [ ] 使用写权限账号验证按需构建仍可用
|
||||
|
||||
### 1.3 AOI token 容量
|
||||
- [ ] 创建 1000+ 个不同 AOI token
|
||||
- [ ] 验证内存占用是否稳定
|
||||
- [ ] 检查日志是否有 token 清理记录
|
||||
|
||||
### 2.1 失败计数
|
||||
- [ ] 运行 `extract_disp_results` 并故意触发失败
|
||||
- [ ] 验证返回的 `failed` 计数是否准确
|
||||
|
||||
### 2.2 AOI 解析异常
|
||||
- [ ] 上传无效 GeoJSON 文件
|
||||
- [ ] 验证是否返回 400 而非 500
|
||||
|
||||
### 2.3 路径归属判断
|
||||
- [ ] 创建 `/data/radar_backup/test.tif` 文件
|
||||
- [ ] 验证是否被误判为 `/data/radar` 的子路径
|
||||
|
||||
### 2.4 解包安全
|
||||
- [ ] 创建包含符号链接的 tar 文件
|
||||
- [ ] 验证是否被拒绝解包
|
||||
|
||||
### 3.1 PowerShell 编码
|
||||
- [ ] 在 PowerShell 5 环境运行启动脚本
|
||||
- [ ] 验证 nginx.conf 是否为 UTF-8 编码
|
||||
|
||||
---
|
||||
|
||||
## 6. 长期改进建议
|
||||
|
||||
### 6.1 引入速率限制
|
||||
```python
|
||||
# 使用 slowapi 限制 API 调用频率
|
||||
from slowapi import Limiter
|
||||
from slowapi.util import get_remote_address
|
||||
|
||||
limiter = Limiter(key_func=get_remote_address)
|
||||
|
||||
@router.get("/radar-data/{data_id}/thumb")
|
||||
@limiter.limit("10/minute") # 每分钟最多 10 次
|
||||
async def get_radar_data_thumb_endpoint(...):
|
||||
...
|
||||
```
|
||||
|
||||
### 6.2 添加审计日志
|
||||
```python
|
||||
# 记录所有写操作和高开销操作
|
||||
async def audit_log(
|
||||
user_id: int,
|
||||
action: str,
|
||||
resource: str,
|
||||
details: Dict[str, Any]
|
||||
):
|
||||
await db.execute(
|
||||
insert(AuditLogORM).values(
|
||||
user_id=user_id,
|
||||
action=action,
|
||||
resource=resource,
|
||||
details=details,
|
||||
timestamp=datetime.utcnow(),
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
### 6.3 使用 Redis 替代内存缓存
|
||||
- AOI token 存储
|
||||
- 预览图构建队列
|
||||
- 速率限制计数器
|
||||
|
||||
### 6.4 添加资源配额
|
||||
```python
|
||||
# 限制每个用户的资源使用
|
||||
USER_QUOTA = {
|
||||
"readonly": {
|
||||
"max_concurrent_requests": 5,
|
||||
"max_aoi_tokens": 10,
|
||||
"max_preview_builds_per_hour": 0,
|
||||
},
|
||||
"user": {
|
||||
"max_concurrent_requests": 20,
|
||||
"max_aoi_tokens": 50,
|
||||
"max_preview_builds_per_hour": 100,
|
||||
},
|
||||
"admin": {
|
||||
"max_concurrent_requests": 100,
|
||||
"max_aoi_tokens": 500,
|
||||
"max_preview_builds_per_hour": 1000,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 总结
|
||||
|
||||
本次审计发现了 **3 个高危问题**、**4 个中危问题**、**1 个低危问题**,主要集中在:
|
||||
1. 函数返回值缺失导致任务失败
|
||||
2. 权限控制不严格(读账号可触发写操作)
|
||||
3. 资源无限制(内存泄漏风险)
|
||||
4. 统计数据不准确
|
||||
5. 异常处理不规范
|
||||
|
||||
建议按照优先级逐步修复,并在修复后进行充分的回归测试。长期来看,应该引入更完善的权限管理、资源配额、审计日志和速率限制机制。
|
||||
@@ -0,0 +1,330 @@
|
||||
# 安全修复快速检查清单
|
||||
|
||||
## 使用说明
|
||||
- 每次修复前检查此清单
|
||||
- 每次修复后更新状态
|
||||
- 测试失败立即停止,分析原因
|
||||
|
||||
---
|
||||
|
||||
## 第一阶段检查清单
|
||||
|
||||
### ✅ 1.1 ENVI 主流程返回值
|
||||
- [x] 代码已修改
|
||||
- [ ] 本地测试通过
|
||||
- [ ] 集成测试通过
|
||||
- [ ] 无副作用
|
||||
- [ ] 可以继续下一项
|
||||
|
||||
**快速测试命令**:
|
||||
```bash
|
||||
# 运行一个 D-InSAR 工作流
|
||||
# 在前端 IDL 自动化面板中提交任务
|
||||
# 观察任务状态和日志
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ✅ 1.2 失败计数重复累加
|
||||
- [x] 代码已修改
|
||||
- [ ] 本地测试通过
|
||||
- [ ] 集成测试通过
|
||||
- [ ] 无副作用
|
||||
- [ ] 可以继续下一项
|
||||
|
||||
**快速测试命令**:
|
||||
```bash
|
||||
# 在前端 IDL 自动化面板中
|
||||
# 使用 "Step 3: 提取 Disp 结果" 功能
|
||||
# 故意触发一些失败(如修改文件权限)
|
||||
# 检查返回的统计数据
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ✅ 1.3 PowerShell 编码
|
||||
- [x] 代码已修改
|
||||
- [ ] 本地测试通过
|
||||
- [ ] 集成测试通过
|
||||
- [ ] 无副作用
|
||||
- [ ] 可以继续下一项
|
||||
|
||||
**快速测试命令**:
|
||||
```powershell
|
||||
# 运行启动脚本
|
||||
.\scripts\start_app.ps1
|
||||
|
||||
# 检查 nginx.conf 编码
|
||||
Get-Content .\nginx\conf\nginx.conf -Encoding UTF8
|
||||
|
||||
# 验证 Nginx 启动
|
||||
curl http://localhost:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 第二阶段检查清单
|
||||
|
||||
### ⏳ 2.1 AOI token 容量上限
|
||||
- [ ] 代码已修改
|
||||
- [ ] 环境变量已配置
|
||||
- [ ] 本地测试通过
|
||||
- [ ] 压力测试通过
|
||||
- [ ] 内存监控正常
|
||||
- [ ] 无副作用
|
||||
- [ ] 可以继续下一项
|
||||
|
||||
**快速测试脚本**:
|
||||
```python
|
||||
# 创建测试脚本 test_aoi_token_limit.py
|
||||
import requests
|
||||
import json
|
||||
|
||||
base_url = "http://localhost:8000"
|
||||
token = "your_auth_token"
|
||||
|
||||
# 创建 1500 个 AOI token
|
||||
for i in range(1500):
|
||||
geojson = {
|
||||
"type": "FeatureCollection",
|
||||
"features": [{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [100 + i * 0.001, 30 + i * 0.001]
|
||||
}
|
||||
}]
|
||||
}
|
||||
response = requests.post(
|
||||
f"{base_url}/api/aoi/upload",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
json=geojson
|
||||
)
|
||||
if i % 100 == 0:
|
||||
print(f"Created {i} tokens")
|
||||
|
||||
print("Test completed")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ⏳ 2.2 AOI 解析异常处理
|
||||
- [ ] 代码已修改
|
||||
- [ ] 本地测试通过
|
||||
- [ ] 前端兼容性测试通过
|
||||
- [ ] 错误消息清晰
|
||||
- [ ] 无副作用
|
||||
- [ ] 可以继续下一项
|
||||
|
||||
**快速测试数据**:
|
||||
```json
|
||||
// 无效 GeoJSON 1:缺少 type
|
||||
{
|
||||
"features": []
|
||||
}
|
||||
|
||||
// 无效 GeoJSON 2:错误的坐标
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": [{
|
||||
"type": "Feature",
|
||||
"geometry": {
|
||||
"type": "Point",
|
||||
"coordinates": [999, 999]
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
// 无效 GeoJSON 3:格式错误
|
||||
{
|
||||
"type": "FeatureCollection",
|
||||
"features": "not an array"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### ⏳ 2.3 路径归属判断
|
||||
- [ ] 代码已修改
|
||||
- [ ] 本地测试通过
|
||||
- [ ] 边界情况测试通过
|
||||
- [ ] 扫描结果准确
|
||||
- [ ] 无副作用
|
||||
- [ ] 可以继续下一项
|
||||
|
||||
**快速测试步骤**:
|
||||
```bash
|
||||
# 1. 创建测试目录结构
|
||||
mkdir -p /data/radar_backup
|
||||
mkdir -p /data/radar/subdir
|
||||
|
||||
# 2. 放入测试文件
|
||||
touch /data/radar_backup/test.tif
|
||||
touch /data/radar/subdir/test.tif
|
||||
|
||||
# 3. 运行数据扫描
|
||||
# 在前端数据管理面板中点击"刷新"
|
||||
|
||||
# 4. 验证结果
|
||||
# radar_backup 的文件不应该出现在列表中
|
||||
# radar/subdir 的文件应该出现在列表中
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 第三阶段检查清单
|
||||
|
||||
### ⏳ 3.1 解包安全校验
|
||||
- [ ] 代码已修改
|
||||
- [ ] 本地测试通过
|
||||
- [ ] 安全测试通过
|
||||
- [ ] 性能测试通过
|
||||
- [ ] 无副作用
|
||||
- [ ] 可以继续下一项
|
||||
|
||||
**快速测试脚本**:
|
||||
```python
|
||||
# 创建测试脚本 test_tar_security.py
|
||||
import tarfile
|
||||
import os
|
||||
|
||||
# 测试 1:创建包含符号链接的 tar
|
||||
with tarfile.open("test_symlink.tar", "w") as tar:
|
||||
# 创建一个符号链接指向 /etc/passwd
|
||||
info = tarfile.TarInfo(name="link_to_passwd")
|
||||
info.type = tarfile.SYMTYPE
|
||||
info.linkname = "/etc/passwd"
|
||||
tar.addfile(info)
|
||||
|
||||
# 测试 2:创建包含 .. 路径的 tar
|
||||
with tarfile.open("test_escape.tar", "w") as tar:
|
||||
info = tarfile.TarInfo(name="../../../etc/passwd")
|
||||
info.size = 0
|
||||
tar.addfile(info)
|
||||
|
||||
# 测试 3:创建正常的 tar
|
||||
with tarfile.open("test_normal.tar", "w") as tar:
|
||||
# 创建一个正常文件
|
||||
info = tarfile.TarInfo(name="normal_file.txt")
|
||||
info.size = 5
|
||||
tar.addfile(info, fileobj=io.BytesIO(b"hello"))
|
||||
|
||||
print("Test archives created")
|
||||
print("test_symlink.tar - should be rejected")
|
||||
print("test_escape.tar - should be rejected")
|
||||
print("test_normal.tar - should be accepted")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 紧急回滚程序
|
||||
|
||||
如果任何测试失败,立即执行:
|
||||
|
||||
```bash
|
||||
# 1. 查看最近的提交
|
||||
git log --oneline -5
|
||||
|
||||
# 2. 回滚到上一个提交
|
||||
git revert HEAD
|
||||
|
||||
# 3. 或者硬回滚(谨慎使用)
|
||||
git reset --hard HEAD~1
|
||||
|
||||
# 4. 重启服务
|
||||
# 停止所有服务
|
||||
# 重新运行 start_app.ps1
|
||||
|
||||
# 5. 验证系统恢复正常
|
||||
curl http://localhost:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 测试环境要求
|
||||
|
||||
### 最小测试环境
|
||||
- [ ] Python 3.9+
|
||||
- [ ] PostgreSQL 运行中
|
||||
- [ ] Ollama 运行中(如果测试 AI 功能)
|
||||
- [ ] 至少 1 个 D-InSAR 结果数据
|
||||
- [ ] 至少 1 个雷达数据
|
||||
|
||||
### 推荐测试环境
|
||||
- [ ] 完整的开发环境
|
||||
- [ ] 测试数据库(非生产)
|
||||
- [ ] 监控工具(内存、CPU)
|
||||
- [ ] 日志查看工具
|
||||
|
||||
---
|
||||
|
||||
## 测试数据准备
|
||||
|
||||
### D-InSAR 测试数据
|
||||
```
|
||||
需要准备:
|
||||
- 至少 1 个完整的 Task_* 目录
|
||||
- 包含 dinsar_results/out_ISARPTD_*_rsp_disp
|
||||
- 用于测试 extract_disp_results 功能
|
||||
```
|
||||
|
||||
### 雷达数据测试
|
||||
```
|
||||
需要准备:
|
||||
- 至少 2 个 .tif 文件
|
||||
- 放在 MONITOR_RADAR_DIRS 配置的目录中
|
||||
- 用于测试预览图生成和路径判断
|
||||
```
|
||||
|
||||
### AOI 测试数据
|
||||
```
|
||||
需要准备:
|
||||
- 有效的 GeoJSON 文件
|
||||
- 无效的 GeoJSON 文件(多种错误类型)
|
||||
- 用于测试异常处理
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 性能基准
|
||||
|
||||
记录修复前后的性能指标:
|
||||
|
||||
### ENVI 工作流
|
||||
- 修复前成功率:_%
|
||||
- 修复后成功率:_%
|
||||
- 平均耗时:_秒
|
||||
|
||||
### AOI token
|
||||
- 修复前内存占用:_MB
|
||||
- 修复后内存占用:_MB
|
||||
- Token 数量上限:_个
|
||||
|
||||
### 解包性能
|
||||
- 修复前平均耗时:_秒
|
||||
- 修复后平均耗时:_秒
|
||||
- 性能差异:_%
|
||||
|
||||
---
|
||||
|
||||
## 签名确认
|
||||
|
||||
每完成一个阶段,填写以下信息:
|
||||
|
||||
### 第一阶段
|
||||
- 修复人:_______
|
||||
- 测试人:_______
|
||||
- 完成日期:_______
|
||||
- 签名:_______
|
||||
|
||||
### 第二阶段
|
||||
- 修复人:_______
|
||||
- 测试人:_______
|
||||
- 完成日期:_______
|
||||
- 签名:_______
|
||||
|
||||
### 第三阶段
|
||||
- 修复人:_______
|
||||
- 测试人:_______
|
||||
- 完成日期:_______
|
||||
- 签名:_______
|
||||
@@ -0,0 +1,478 @@
|
||||
# 安全问题分阶段修复计划
|
||||
|
||||
## 修复原则
|
||||
|
||||
1. **安全第一**:每个阶段修复后必须充分测试,确保不影响现有功能
|
||||
2. **最小改动**:优先选择改动最小、风险最低的方案
|
||||
3. **可回滚**:每个阶段独立提交,出问题可以快速回滚
|
||||
4. **渐进式**:从简单到复杂,从低风险到高风险
|
||||
|
||||
---
|
||||
|
||||
## 第一阶段:明确 Bug 修复(低风险)
|
||||
|
||||
**目标**:修复明确的代码错误,不涉及架构变更
|
||||
|
||||
### 1.1 ENVI 主流程返回值缺失 ✅ 已完成
|
||||
|
||||
**文件**:`backend/app/services/envi_service.py:1630`
|
||||
|
||||
**修改**:
|
||||
```python
|
||||
# 修改前
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
|
||||
# 修改后
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
return record # 添加此行
|
||||
```
|
||||
|
||||
**风险评估**:🟢 极低
|
||||
- 只添加一行返回语句
|
||||
- 不改变任何逻辑
|
||||
- 修复了明确的 bug(成功时应该返回 record)
|
||||
|
||||
**测试计划**:
|
||||
- [ ] 运行一个完整的 D-InSAR 工作流(dinsar_custom)
|
||||
- [ ] 检查任务状态是否正确标记为 SUCCESS
|
||||
- [ ] 检查日志是否包含 `workflow=dinsar_custom duration=XXXs`
|
||||
- [ ] 验证前端是否正确显示任务完成
|
||||
|
||||
---
|
||||
|
||||
### 1.2 失败计数重复累加
|
||||
|
||||
**文件**:`backend/app/services/envi_service.py:1717, 1727`
|
||||
|
||||
**修改**:
|
||||
```python
|
||||
# 修改前(1717 行)
|
||||
except OSError as e:
|
||||
task_failed += 1
|
||||
failed += 1 # 删除此行
|
||||
task_status = f"error: {e}"
|
||||
|
||||
# 修改后
|
||||
except OSError as e:
|
||||
task_failed += 1
|
||||
task_status = f"error: {e}"
|
||||
|
||||
# 修改前(1727 行)
|
||||
except OSError as e:
|
||||
task_failed += 1
|
||||
failed += 1 # 删除此行
|
||||
task_status = f"error: {e}"
|
||||
|
||||
# 修改后
|
||||
except OSError as e:
|
||||
task_failed += 1
|
||||
task_status = f"error: {e}"
|
||||
|
||||
# 保留 1730 行的汇总
|
||||
failed += task_failed # 只在这里累加一次
|
||||
```
|
||||
|
||||
**风险评估**:🟢 极低
|
||||
- 只删除重复的累加语句
|
||||
- 不改变业务逻辑
|
||||
- 修复统计数据不准确的问题
|
||||
|
||||
**测试计划**:
|
||||
- [ ] 运行 `extract_disp_results` 功能
|
||||
- [ ] 故意触发一些失败(如权限问题)
|
||||
- [ ] 验证返回的 `failed` 计数是否准确(不翻倍)
|
||||
|
||||
---
|
||||
|
||||
### 1.3 PowerShell 编码问题
|
||||
|
||||
**文件**:`scripts/start_app.ps1:388`
|
||||
|
||||
**修改**:
|
||||
```powershell
|
||||
# 修改前
|
||||
$NewConfContent | Set-Content -LiteralPath "$NginxConfPath" -NoNewline
|
||||
|
||||
# 修改后
|
||||
$NewConfContent | Set-Content -LiteralPath "$NginxConfPath" -NoNewline -Encoding UTF8
|
||||
```
|
||||
|
||||
**风险评估**:🟢 极低
|
||||
- 只添加编码参数
|
||||
- 确保 nginx.conf 使用 UTF-8 编码
|
||||
- 避免 PowerShell 5 默认 UTF-16 导致的问题
|
||||
|
||||
**测试计划**:
|
||||
- [ ] 在 PowerShell 5 环境运行启动脚本
|
||||
- [ ] 检查生成的 nginx.conf 文件编码(使用 `file` 命令或文本编辑器)
|
||||
- [ ] 验证 Nginx 是否能正常启动和解析配置
|
||||
|
||||
---
|
||||
|
||||
**第一阶段提交**:
|
||||
```
|
||||
fix: 修复 ENVI 工作流返回值缺失和统计计数错误
|
||||
|
||||
- 修复 run_workflow 成功时不返回 record 的问题
|
||||
- 修复 extract_disp_results 失败计数重复累加
|
||||
- 修复 PowerShell 脚本 nginx.conf 编码问题
|
||||
|
||||
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 第二阶段:防御性增强(中风险)
|
||||
|
||||
**目标**:添加保护措施,不改变现有业务逻辑
|
||||
|
||||
### 2.1 AOI token 容量上限
|
||||
|
||||
**文件**:`backend/app/routers/dependencies.py`
|
||||
|
||||
**修改策略**:
|
||||
1. 添加环境变量配置 `AOI_TOKEN_MAX_STORE_SIZE`(默认 1000)
|
||||
2. 在 `_store_aoi_token` 中添加容量检查
|
||||
3. 达到上限时删除最旧的 100 个 token
|
||||
|
||||
**实现**:
|
||||
```python
|
||||
# 在 dependencies.py 顶部添加
|
||||
AOI_TOKEN_MAX_STORE_SIZE = read_int_env(
|
||||
"AOI_TOKEN_MAX_STORE_SIZE",
|
||||
1000,
|
||||
minimum=100,
|
||||
maximum=10000,
|
||||
)
|
||||
|
||||
# 修改 _store_aoi_token 函数
|
||||
async def _store_aoi_token(aoi_wkt: str, feature_collection: Dict[str, Any]) -> str:
|
||||
token = uuid.uuid4().hex
|
||||
now = time.monotonic()
|
||||
async with _AOI_TOKEN_LOCK:
|
||||
_cleanup_expired_aoi_tokens(now)
|
||||
|
||||
# 检查容量上限
|
||||
if len(_AOI_TOKEN_STORE) >= AOI_TOKEN_MAX_STORE_SIZE:
|
||||
# 删除最旧的 token(按 expires_at 排序)
|
||||
oldest_tokens = sorted(
|
||||
_AOI_TOKEN_STORE.items(),
|
||||
key=lambda x: x[1]["expires_at"]
|
||||
)[:100]
|
||||
for old_token, _ in oldest_tokens:
|
||||
_AOI_TOKEN_STORE.pop(old_token, None)
|
||||
# 记录日志
|
||||
print(f"[INFO] AOI token store reached limit, cleaned {len(oldest_tokens)} oldest tokens")
|
||||
|
||||
_AOI_TOKEN_STORE[token] = {
|
||||
"aoi_wkt": aoi_wkt,
|
||||
"aoi_geojson": feature_collection,
|
||||
"expires_at": now + AOI_TOKEN_TTL_SECONDS,
|
||||
}
|
||||
return token
|
||||
```
|
||||
|
||||
**风险评估**:🟡 中等
|
||||
- 添加了新的容量限制逻辑
|
||||
- 可能影响高并发场景下的 AOI 查询
|
||||
- 但不改变现有 API 接口
|
||||
|
||||
**测试计划**:
|
||||
- [ ] 创建 1000+ 个不同的 AOI token
|
||||
- [ ] 验证内存占用是否稳定(不超过预期)
|
||||
- [ ] 检查日志是否有 token 清理记录
|
||||
- [ ] 验证被清理的 token 是否无法再使用
|
||||
- [ ] 验证新创建的 token 仍然可用
|
||||
|
||||
---
|
||||
|
||||
### 2.2 AOI 解析异常处理
|
||||
|
||||
**文件**:`backend/app/routers/dependencies.py`
|
||||
|
||||
**修改策略**:
|
||||
在所有 AOI 解析函数中添加统一的异常处理
|
||||
|
||||
**实现位置**:
|
||||
- `_parse_geojson_to_wkt` 调用处
|
||||
- `_parse_shapefile_to_wkt` 调用处
|
||||
- 文件上传端点
|
||||
|
||||
**实现**:
|
||||
```python
|
||||
# 在相关端点中添加 try-except
|
||||
try:
|
||||
aoi_wkt = _parse_geojson_to_wkt(geojson_data)
|
||||
except (ValueError, KeyError, TypeError) as e:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"GeoJSON 格式错误: {str(e)}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"AOI 解析失败: {e}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail="AOI 解析失败,请联系管理员"
|
||||
)
|
||||
```
|
||||
|
||||
**风险评估**:🟡 中等
|
||||
- 改变了错误响应码(500 → 400)
|
||||
- 可能影响前端错误处理逻辑
|
||||
- 但提升了 API 语义正确性
|
||||
|
||||
**测试计划**:
|
||||
- [ ] 上传无效的 GeoJSON 文件
|
||||
- [ ] 验证是否返回 400 而非 500
|
||||
- [ ] 验证错误消息是否清晰
|
||||
- [ ] 验证前端是否能正确处理 400 错误
|
||||
|
||||
---
|
||||
|
||||
### 2.3 路径归属判断优化
|
||||
|
||||
**文件**:`backend/app/services/data_service.py:530`
|
||||
|
||||
**修改策略**:
|
||||
添加辅助函数 `is_subpath`,使用 `os.path.commonpath` 进行准确判断
|
||||
|
||||
**实现**:
|
||||
```python
|
||||
# 在 data_service.py 顶部添加辅助函数
|
||||
def is_subpath(child: str, parent: str) -> bool:
|
||||
"""检查 child 是否是 parent 的子路径"""
|
||||
try:
|
||||
child_abs = os.path.abspath(child)
|
||||
parent_abs = os.path.abspath(parent)
|
||||
# 确保父路径以分隔符结尾,避免前缀误匹配
|
||||
if not parent_abs.endswith(os.sep):
|
||||
parent_abs += os.sep
|
||||
if not child_abs.endswith(os.sep) and os.path.isdir(child_abs):
|
||||
child_abs += os.sep
|
||||
# 使用 commonpath 判断
|
||||
common = os.path.commonpath([child_abs, parent_abs])
|
||||
return common == parent_abs.rstrip(os.sep)
|
||||
except (ValueError, TypeError):
|
||||
return False
|
||||
|
||||
# 修改 530 行
|
||||
# 修改前
|
||||
if not any(file_path_abs.startswith(root) for root in radar_roots):
|
||||
continue
|
||||
|
||||
# 修改后
|
||||
if not any(is_subpath(file_path_abs, root) for root in radar_roots):
|
||||
continue
|
||||
```
|
||||
|
||||
**风险评估**:🟡 中等
|
||||
- 改变了路径判断逻辑
|
||||
- 可能影响文件扫描结果
|
||||
- 但修复了潜在的安全问题
|
||||
|
||||
**测试计划**:
|
||||
- [ ] 在 `MONITOR_RADAR_DIRS` 旁边创建 `MONITOR_RADAR_DIRS_backup` 目录
|
||||
- [ ] 放入测试文件
|
||||
- [ ] 运行数据扫描
|
||||
- [ ] 验证 backup 目录的文件不会被误判为合法
|
||||
|
||||
---
|
||||
|
||||
**第二阶段提交**:
|
||||
```
|
||||
feat: 添加 AOI token 容量限制和异常处理增强
|
||||
|
||||
- 添加 AOI_TOKEN_MAX_STORE_SIZE 配置(默认 1000)
|
||||
- 达到上限时自动清理最旧的 token
|
||||
- 统一 AOI 解析异常处理(400 vs 500)
|
||||
- 优化路径归属判断,避免前缀误匹配
|
||||
|
||||
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 第三阶段:安全加固(高风险)
|
||||
|
||||
**目标**:解决权限和安全问题,可能涉及架构调整
|
||||
|
||||
### 3.1 解包安全校验增强
|
||||
|
||||
**文件**:`scripts/unpack_archives.py`
|
||||
|
||||
**修改策略**:
|
||||
1. 修改 `_is_safe_tar_member` 接收 member 对象而非字符串
|
||||
2. 添加符号链接和硬链接检查
|
||||
3. 使用逐个提取替代 `extractall`
|
||||
|
||||
**实现**:
|
||||
```python
|
||||
def _is_safe_tar_member(member):
|
||||
"""检查 tar 成员是否安全(包括符号链接检查)"""
|
||||
# 检查路径
|
||||
norm_name = os.path.normpath(member.name)
|
||||
if os.path.isabs(norm_name):
|
||||
return False
|
||||
if norm_name.startswith("..") or norm_name.startswith("../") or norm_name.startswith("..\\"):
|
||||
return False
|
||||
|
||||
# 检查符号链接和硬链接
|
||||
if member.issym() or member.islnk():
|
||||
link_target = member.linkname
|
||||
if os.path.isabs(link_target) or link_target.startswith(".."):
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _validate_tar_members(tar_obj, archive_path):
|
||||
for member in tar_obj.getmembers():
|
||||
if not _is_safe_tar_member(member):
|
||||
raise IOError(f"unsafe tar entry detected: {member.name} in {archive_path}")
|
||||
|
||||
def _safe_extract_tar(tar_obj, output_dir):
|
||||
"""安全地提取 tar 文件"""
|
||||
for member in tar_obj.getmembers():
|
||||
if not _is_safe_tar_member(member):
|
||||
raise IOError(f"unsafe tar entry: {member.name}")
|
||||
# 逐个提取
|
||||
tar_obj.extract(member, path=output_dir)
|
||||
|
||||
# 在 181 行替换
|
||||
with tarfile.open(archive_path, "r:*") as tar:
|
||||
_validate_tar_members(tar, archive_path)
|
||||
_safe_extract_tar(tar, output_dir) # 替换 tar.extractall
|
||||
```
|
||||
|
||||
**风险评估**:🟡 中等
|
||||
- 改变了解包逻辑
|
||||
- 可能影响解包性能(逐个提取 vs 批量提取)
|
||||
- 但提升了安全性
|
||||
|
||||
**测试计划**:
|
||||
- [ ] 创建包含符号链接的 tar 文件
|
||||
- [ ] 验证是否被拒绝解包
|
||||
- [ ] 创建正常的 tar 文件
|
||||
- [ ] 验证是否能正常解包
|
||||
- [ ] 对比解包性能(前后差异)
|
||||
|
||||
---
|
||||
|
||||
### 3.2 只读用户权限控制(暂缓)
|
||||
|
||||
**状态**:⏸️ 暂缓到第四阶段
|
||||
|
||||
**原因**:
|
||||
- 涉及权限系统重构
|
||||
- 需要修改多个端点
|
||||
- 可能影响前端调用逻辑
|
||||
- 需要更充分的测试
|
||||
|
||||
**备选方案**:
|
||||
- 方案 A:添加权限检查依赖
|
||||
- 方案 B:配置控制(允许/禁止只读用户触发构建)
|
||||
- 方案 C:改为 POST 端点(需要前端配合)
|
||||
|
||||
**决策点**:
|
||||
- 是否有只读用户在使用系统?
|
||||
- 只读用户是否需要查看预览图?
|
||||
- 是否可以接受只读用户触发构建?
|
||||
|
||||
---
|
||||
|
||||
**第三阶段提交**:
|
||||
```
|
||||
security: 增强 tar 解包安全校验
|
||||
|
||||
- 添加符号链接和硬链接检查
|
||||
- 使用逐个提取替代 extractall
|
||||
- 防止目录逃逸和文件覆盖攻击
|
||||
|
||||
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 第四阶段:权限重构(待定)
|
||||
|
||||
**目标**:解决只读用户权限问题
|
||||
|
||||
**前置条件**:
|
||||
- 第一、二、三阶段全部完成并稳定运行
|
||||
- 明确只读用户的使用场景和需求
|
||||
- 前端团队配合(如果需要修改 API 调用)
|
||||
|
||||
**待讨论问题**:
|
||||
1. 系统中是否有只读用户?
|
||||
2. 只读用户的典型使用场景是什么?
|
||||
3. 是否可以接受只读用户无法查看预览图?
|
||||
4. 是否需要前端配合修改?
|
||||
|
||||
---
|
||||
|
||||
## 修复时间表
|
||||
|
||||
| 阶段 | 预计时间 | 风险等级 | 依赖 |
|
||||
|------|---------|---------|------|
|
||||
| 第一阶段 | 1 天 | 🟢 低 | 无 |
|
||||
| 第二阶段 | 2-3 天 | 🟡 中 | 第一阶段完成 |
|
||||
| 第三阶段 | 2-3 天 | 🟡 中 | 第二阶段完成 |
|
||||
| 第四阶段 | 待定 | 🔴 高 | 需求确认 |
|
||||
|
||||
---
|
||||
|
||||
## 回滚计划
|
||||
|
||||
每个阶段独立提交,出现问题时可以:
|
||||
|
||||
1. **快速回滚**:
|
||||
```bash
|
||||
git revert <commit-hash>
|
||||
```
|
||||
|
||||
2. **部分回滚**:
|
||||
如果只有某个修复有问题,可以单独回滚该文件:
|
||||
```bash
|
||||
git checkout <previous-commit> -- <file-path>
|
||||
git commit -m "revert: 回滚 <file-path> 的修改"
|
||||
```
|
||||
|
||||
3. **紧急回滚**:
|
||||
如果影响生产环境,立即回滚到上一个稳定版本:
|
||||
```bash
|
||||
git reset --hard <last-stable-commit>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 监控指标
|
||||
|
||||
修复后需要监控的关键指标:
|
||||
|
||||
### 第一阶段
|
||||
- ENVI 工作流成功率(应该提升)
|
||||
- 任务状态准确性
|
||||
- 统计数据准确性
|
||||
|
||||
### 第二阶段
|
||||
- AOI token 内存占用(应该稳定)
|
||||
- 400 vs 500 错误比例(400 应该增加)
|
||||
- 路径扫描结果准确性
|
||||
|
||||
### 第三阶段
|
||||
- 解包成功率(应该不变)
|
||||
- 解包性能(可能略微下降)
|
||||
- 安全事件(应该为 0)
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
这个分阶段修复计划:
|
||||
- ✅ 从低风险到高风险渐进式修复
|
||||
- ✅ 每个阶段独立可测试、可回滚
|
||||
- ✅ 优先修复明确的 bug,暂缓架构性改动
|
||||
- ✅ 保留了灵活性,可以根据实际情况调整
|
||||
|
||||
**建议**:先完成第一阶段,充分测试后再决定是否继续第二阶段。
|
||||
@@ -0,0 +1,278 @@
|
||||
# 安全问题修复进度跟踪
|
||||
|
||||
## 修复状态图例
|
||||
- ✅ 已完成
|
||||
- 🚧 进行中
|
||||
- ⏸️ 暂缓
|
||||
- ⏳ 待开始
|
||||
- ❌ 已回滚
|
||||
|
||||
---
|
||||
|
||||
## 第一阶段:明确 Bug 修复
|
||||
|
||||
### 1.1 ENVI 主流程返回值缺失
|
||||
- **状态**:✅ 已完成
|
||||
- **修改文件**:`backend/app/services/envi_service.py:1630`
|
||||
- **修改内容**:添加 `return record`
|
||||
- **提交时间**:2026-03-04
|
||||
- **测试状态**:⏳ 待测试
|
||||
|
||||
**测试清单**:
|
||||
- [ ] 运行完整的 D-InSAR 工作流(dinsar_custom)
|
||||
- [ ] 检查任务状态是否为 SUCCESS
|
||||
- [ ] 检查日志是否包含 `workflow=dinsar_custom duration=XXXs`
|
||||
- [ ] 验证前端显示是否正常
|
||||
|
||||
**测试结果**:
|
||||
```
|
||||
待填写...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.2 失败计数重复累加
|
||||
- **状态**:✅ 已完成
|
||||
- **修改文件**:`backend/app/services/envi_service.py:1717, 1726`
|
||||
- **修改内容**:删除重复的 `failed += 1`(两处)
|
||||
- **提交时间**:2026-03-04
|
||||
- **测试状态**:⏳ 待测试
|
||||
|
||||
**测试清单**:
|
||||
- [ ] 运行 `extract_disp_results` 功能
|
||||
- [ ] 故意触发失败(如权限问题)
|
||||
- [ ] 验证 `failed` 计数是否准确
|
||||
|
||||
**测试结果**:
|
||||
```
|
||||
待填写...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.3 PowerShell 编码问题
|
||||
- **状态**:✅ 已完成(已增强)
|
||||
- **修改文件**:`scripts/start_app.ps1:388`
|
||||
- **修改内容**:使用 UTF-8 无 BOM 编码写入
|
||||
- **提交时间**:2026-03-04
|
||||
- **测试状态**:✅ 已验证
|
||||
|
||||
**额外修复**:
|
||||
- 发现 `nginx.conf` 文件包含 UTF-8 BOM,导致 Nginx 启动失败
|
||||
- 已删除 BOM(使用 `tail -c +4`)
|
||||
- 修改脚本使用 `[System.IO.File]::WriteAllText()` 确保无 BOM
|
||||
|
||||
**测试清单**:
|
||||
- [ ] 在 PowerShell 5 环境运行启动脚本
|
||||
- [ ] 检查 nginx.conf 文件编码
|
||||
- [ ] 验证 Nginx 是否正常启动
|
||||
|
||||
**测试结果**:
|
||||
```
|
||||
待填写...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**第一阶段总结**:
|
||||
- 完成度:3/3 (100%) ✅
|
||||
- 完成时间:2026-03-04
|
||||
- 遇到的问题:无
|
||||
- 回滚记录:无
|
||||
- **状态**:代码修改已完成,等待测试验证
|
||||
|
||||
---
|
||||
|
||||
## 第二阶段:防御性增强
|
||||
|
||||
### 2.1 AOI token 容量上限
|
||||
- **状态**:⏳ 待开始
|
||||
- **修改文件**:`backend/app/routers/dependencies.py`
|
||||
- **修改内容**:添加 `AOI_TOKEN_MAX_STORE_SIZE` 配置和容量检查
|
||||
- **提交时间**:-
|
||||
- **测试状态**:-
|
||||
|
||||
**测试清单**:
|
||||
- [ ] 创建 1000+ 个 AOI token
|
||||
- [ ] 验证内存占用是否稳定
|
||||
- [ ] 检查日志是否有清理记录
|
||||
- [ ] 验证被清理的 token 无法使用
|
||||
- [ ] 验证新 token 仍可用
|
||||
|
||||
**测试结果**:
|
||||
```
|
||||
待填写...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.2 AOI 解析异常处理
|
||||
- **状态**:⏳ 待开始
|
||||
- **修改文件**:`backend/app/routers/dependencies.py`
|
||||
- **修改内容**:添加统一异常处理(400 vs 500)
|
||||
- **提交时间**:-
|
||||
- **测试状态**:-
|
||||
|
||||
**测试清单**:
|
||||
- [ ] 上传无效 GeoJSON 文件
|
||||
- [ ] 验证返回 400 而非 500
|
||||
- [ ] 验证错误消息清晰
|
||||
- [ ] 验证前端能正确处理
|
||||
|
||||
**测试结果**:
|
||||
```
|
||||
待填写...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2.3 路径归属判断优化
|
||||
- **状态**:⏳ 待开始
|
||||
- **修改文件**:`backend/app/services/data_service.py:530`
|
||||
- **修改内容**:使用 `is_subpath` 替代 `startswith`
|
||||
- **提交时间**:-
|
||||
- **测试状态**:-
|
||||
|
||||
**测试清单**:
|
||||
- [ ] 创建 `MONITOR_RADAR_DIRS_backup` 目录
|
||||
- [ ] 放入测试文件
|
||||
- [ ] 运行数据扫描
|
||||
- [ ] 验证 backup 目录文件不被误判
|
||||
|
||||
**测试结果**:
|
||||
```
|
||||
待填写...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**第二阶段总结**:
|
||||
- 完成度:0/3 (0%)
|
||||
- 预计完成时间:-
|
||||
- 遇到的问题:-
|
||||
- 回滚记录:无
|
||||
|
||||
---
|
||||
|
||||
## 第三阶段:安全加固
|
||||
|
||||
### 3.1 解包安全校验增强
|
||||
- **状态**:⏳ 待开始
|
||||
- **修改文件**:`scripts/unpack_archives.py`
|
||||
- **修改内容**:添加符号链接检查,使用逐个提取
|
||||
- **提交时间**:-
|
||||
- **测试状态**:-
|
||||
|
||||
**测试清单**:
|
||||
- [ ] 创建包含符号链接的 tar 文件
|
||||
- [ ] 验证被拒绝解包
|
||||
- [ ] 创建正常 tar 文件
|
||||
- [ ] 验证正常解包
|
||||
- [ ] 对比解包性能
|
||||
|
||||
**测试结果**:
|
||||
```
|
||||
待填写...
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**第三阶段总结**:
|
||||
- 完成度:0/1 (0%)
|
||||
- 预计完成时间:-
|
||||
- 遇到的问题:-
|
||||
- 回滚记录:无
|
||||
|
||||
---
|
||||
|
||||
## 第四阶段:权限重构
|
||||
|
||||
### 4.1 只读用户权限控制
|
||||
- **状态**:⏸️ 暂缓
|
||||
- **原因**:需要明确需求和前端配合
|
||||
- **待讨论问题**:
|
||||
- [ ] 系统中是否有只读用户?
|
||||
- [ ] 只读用户的使用场景?
|
||||
- [ ] 是否可以接受只读用户无法查看预览图?
|
||||
- [ ] 是否需要前端配合?
|
||||
|
||||
---
|
||||
|
||||
## 总体进度
|
||||
|
||||
| 阶段 | 状态 | 完成度 | 预计时间 | 实际时间 |
|
||||
|------|------|--------|---------|---------|
|
||||
| 第一阶段 | ✅ 已完成 | 3/3 (100%) | 1 天 | 2026-03-04 |
|
||||
| 第二阶段 | ⏳ 待开始 | 0/3 (0%) | 2-3 天 | - |
|
||||
| 第三阶段 | ⏳ 待开始 | 0/1 (0%) | 2-3 天 | - |
|
||||
| 第四阶段 | ⏸️ 暂缓 | - | 待定 | - |
|
||||
|
||||
**总完成度**:3/7 (43%)
|
||||
|
||||
---
|
||||
|
||||
## 问题记录
|
||||
|
||||
### 问题 #1:Nginx 启动失败 - UTF-8 BOM 问题
|
||||
- **发现时间**:2026-03-04
|
||||
- **问题描述**:Nginx 启动失败,错误信息 `unknown directive "worker_processes"`,发现 `nginx.conf` 文件包含 UTF-8 BOM(`ef bb bf`)
|
||||
- **影响范围**:Nginx 无法启动,整个系统无法访问
|
||||
- **解决方案**:
|
||||
1. 使用 `tail -c +4` 删除现有文件的 BOM
|
||||
2. 修改 PowerShell 脚本使用 `[System.IO.File]::WriteAllText()` 确保写入时无 BOM
|
||||
- **状态**:✅ 已解决
|
||||
|
||||
### 问题 #2:PROJ 数据库版本冲突
|
||||
- **发现时间**:2026-03-05
|
||||
- **问题描述**:扫描 D-InSAR 结果时出现 PROJ 警告 `DATABASE.LAYOUT.VERSION.MINOR = 2 whereas >= 3 is expected`,PostgreSQL 自带的 PROJ 数据库版本过旧
|
||||
- **影响范围**:不影响功能,但产生大量警告日志
|
||||
- **解决方案**:
|
||||
1. 在 `run_backend.py` 中自动检测并设置 `PROJ_LIB` 环境变量
|
||||
2. 指向 GDAL 自带的新版 PROJ 数据库
|
||||
- **状态**:✅ 已修复,待测试验证
|
||||
|
||||
### 问题 #3:扫描任务时前端未锁定
|
||||
- **发现时间**:2026-03-05
|
||||
- **问题描述**:D-InSAR 结果扫描任务运行时前端没有锁定
|
||||
- **影响范围**:用户可能在扫描过程中进行其他操作,导致数据不一致
|
||||
- **可能原因**:
|
||||
1. 扫描任务执行太快(< 3 秒),前端轮询间隔(3 秒)来不及捕获
|
||||
2. 任务状态更新有延迟
|
||||
- **解决方案**:待确认是否需要修复(可能是正常行为)
|
||||
- **状态**:⏳ 待分析
|
||||
|
||||
---
|
||||
|
||||
## 回滚记录
|
||||
|
||||
### 回滚 #1
|
||||
- **回滚时间**:-
|
||||
- **回滚内容**:-
|
||||
- **回滚原因**:-
|
||||
- **影响范围**:-
|
||||
|
||||
---
|
||||
|
||||
## 下一步行动
|
||||
|
||||
1. **立即行动**:
|
||||
- [ ] 测试 1.1 ENVI 主流程返回值修复
|
||||
- [ ] 如果测试通过,继续修复 1.2 和 1.3
|
||||
|
||||
2. **本周计划**:
|
||||
- [ ] 完成第一阶段所有修复
|
||||
- [ ] 充分测试第一阶段
|
||||
- [ ] 提交第一阶段代码
|
||||
|
||||
3. **下周计划**:
|
||||
- [ ] 根据第一阶段测试结果决定是否继续
|
||||
- [ ] 如果继续,开始第二阶段
|
||||
|
||||
---
|
||||
|
||||
## 备注
|
||||
|
||||
- 每次修复后立即更新此文档
|
||||
- 测试结果详细记录,包括成功和失败的情况
|
||||
- 遇到问题及时记录到"问题记录"区域
|
||||
- 回滚操作必须记录原因和影响范围
|
||||
@@ -0,0 +1,271 @@
|
||||
# 第一阶段修复总结(2026-03-04)
|
||||
|
||||
## 修复概览
|
||||
|
||||
**阶段**:第一阶段 - 明确 Bug 修复
|
||||
**状态**:✅ 代码修改完成,等待测试验证
|
||||
**完成时间**:2026-03-04
|
||||
**风险等级**:🟢 低风险
|
||||
|
||||
---
|
||||
|
||||
## 修复内容
|
||||
|
||||
### 1.1 ENVI 主流程返回值缺失 ✅
|
||||
|
||||
**问题**:`run_workflow()` 函数在成功执行后不返回 `record` 字典,导致下游代码崩溃。
|
||||
|
||||
**修改文件**:`backend/app/services/envi_service.py:1630`
|
||||
|
||||
**修改内容**:
|
||||
```python
|
||||
# 修改前
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
# 函数结束,返回 None
|
||||
|
||||
# 修改后
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
return record # 添加此行
|
||||
```
|
||||
|
||||
**影响**:
|
||||
- 修复了所有 ENVI 工作流任务在成功后被误判为失败的问题
|
||||
- 下游代码可以正确访问 `run_meta.get('workflow')` 等字段
|
||||
|
||||
**风险评估**:🟢 极低
|
||||
- 只添加一行返回语句
|
||||
- 不改变任何业务逻辑
|
||||
- 修复明确的 bug
|
||||
|
||||
---
|
||||
|
||||
### 1.2 失败计数重复累加 ✅
|
||||
|
||||
**问题**:`extract_disp_results()` 函数中失败计数被重复累加,导致统计数据翻倍。
|
||||
|
||||
**修改文件**:`backend/app/services/envi_service.py:1717, 1726`
|
||||
|
||||
**修改内容**:
|
||||
```python
|
||||
# 修改前(1717 行)
|
||||
except OSError as e:
|
||||
task_failed += 1
|
||||
failed += 1 # 删除此行
|
||||
task_status = f"error: {e}"
|
||||
|
||||
# 修改后
|
||||
except OSError as e:
|
||||
task_failed += 1
|
||||
task_status = f"error: {e}"
|
||||
|
||||
# 修改前(1726 行)
|
||||
except OSError as e:
|
||||
task_failed += 1
|
||||
failed += 1 # 删除此行
|
||||
task_status = f"error: {e}"
|
||||
|
||||
# 修改后
|
||||
except OSError as e:
|
||||
task_failed += 1
|
||||
task_status = f"error: {e}"
|
||||
|
||||
# 保留 1730 行的汇总(只在这里累加一次)
|
||||
failed += task_failed
|
||||
```
|
||||
|
||||
**影响**:
|
||||
- 修复了失败计数翻倍的问题
|
||||
- 统计数据现在准确反映实际失败数量
|
||||
|
||||
**风险评估**:🟢 极低
|
||||
- 只删除重复的累加语句
|
||||
- 不改变业务逻辑
|
||||
- 修复统计错误
|
||||
|
||||
---
|
||||
|
||||
### 1.3 PowerShell 编码问题 ✅
|
||||
|
||||
**问题**:启动脚本写入 `nginx.conf` 时未指定编码,PowerShell 5 默认使用 UTF-16,导致 Nginx 无法解析。
|
||||
|
||||
**修改文件**:`scripts/start_app.ps1:388`
|
||||
|
||||
**修改内容**:
|
||||
```powershell
|
||||
# 修改前
|
||||
$NewConfContent | Set-Content -LiteralPath "$NginxConfPath" -NoNewline
|
||||
|
||||
# 修改后
|
||||
$NewConfContent | Set-Content -LiteralPath "$NginxConfPath" -NoNewline -Encoding UTF8
|
||||
```
|
||||
|
||||
**影响**:
|
||||
- 确保 `nginx.conf` 使用 UTF-8 编码
|
||||
- 避免 Nginx 配置解析失败
|
||||
|
||||
**风险评估**:🟢 极低
|
||||
- 只添加编码参数
|
||||
- 不改变文件内容
|
||||
- 提升兼容性
|
||||
|
||||
---
|
||||
|
||||
## 代码验证
|
||||
|
||||
### Python 语法检查
|
||||
```bash
|
||||
cd "Z:\Code\Insar_management_system_v2\backend\app\services"
|
||||
python -m py_compile envi_service.py
|
||||
```
|
||||
**结果**:✅ 通过
|
||||
|
||||
### 修改文件列表
|
||||
1. `backend/app/services/envi_service.py` - 3 处修改
|
||||
2. `scripts/start_app.ps1` - 1 处修改
|
||||
|
||||
---
|
||||
|
||||
## 测试计划
|
||||
|
||||
### 1.1 ENVI 主流程返回值测试
|
||||
|
||||
**测试步骤**:
|
||||
1. 启动后端服务
|
||||
2. 在前端 IDL 自动化面板中提交一个 D-InSAR 工作流任务
|
||||
3. 等待任务完成
|
||||
4. 检查任务状态
|
||||
|
||||
**预期结果**:
|
||||
- 任务状态显示为 SUCCESS
|
||||
- 日志包含 `workflow=dinsar_custom duration=XXXs`
|
||||
- 前端正确显示任务完成信息
|
||||
|
||||
**测试命令**:
|
||||
```bash
|
||||
# 查看任务日志
|
||||
tail -f backend/logs/app.log
|
||||
|
||||
# 查看 ENVI 运行日志
|
||||
ls backend/runtime/*.log
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.2 失败计数测试
|
||||
|
||||
**测试步骤**:
|
||||
1. 准备一个包含 `dinsar_results` 的 Task 目录
|
||||
2. 故意修改某些文件权限,使复制失败
|
||||
3. 在前端使用 "Step 3: 提取 Disp 结果" 功能
|
||||
4. 检查返回的统计数据
|
||||
|
||||
**预期结果**:
|
||||
- `failed` 计数等于实际失败的文件数(不翻倍)
|
||||
- 统计数据准确
|
||||
|
||||
**测试命令**:
|
||||
```bash
|
||||
# 修改文件权限(Windows)
|
||||
icacls "Z:\Test_data\Task_xxx\dinsar_results\out_ISARPTD_xxx_rsp_disp.dat" /deny Everyone:F
|
||||
|
||||
# 查看返回的统计数据
|
||||
# 在前端点击"提取 Disp 结果"后查看响应
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 1.3 PowerShell 编码测试
|
||||
|
||||
**测试步骤**:
|
||||
1. 在 PowerShell 5 环境运行启动脚本
|
||||
2. 检查生成的 `nginx.conf` 文件编码
|
||||
3. 验证 Nginx 是否正常启动
|
||||
|
||||
**预期结果**:
|
||||
- `nginx.conf` 文件编码为 UTF-8
|
||||
- Nginx 正常启动,无配置解析错误
|
||||
|
||||
**测试命令**:
|
||||
```powershell
|
||||
# 运行启动脚本
|
||||
.\scripts\start_app.ps1
|
||||
|
||||
# 检查文件编码(使用 file 命令或文本编辑器)
|
||||
# 在 VS Code 中打开 nginx/conf/nginx.conf,右下角显示编码
|
||||
|
||||
# 验证 Nginx 启动
|
||||
curl http://localhost:8080
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 回滚计划
|
||||
|
||||
如果测试失败,可以快速回滚:
|
||||
|
||||
### 回滚 1.1 ENVI 返回值
|
||||
```python
|
||||
# 删除 envi_service.py:1631 行的 return record
|
||||
# 恢复为:
|
||||
if error:
|
||||
raise RuntimeError(error)
|
||||
# 函数结束
|
||||
```
|
||||
|
||||
### 回滚 1.2 失败计数
|
||||
```python
|
||||
# 在 envi_service.py:1718 和 1727 行恢复
|
||||
except OSError as e:
|
||||
task_failed += 1
|
||||
failed += 1 # 恢复此行
|
||||
task_status = f"error: {e}"
|
||||
```
|
||||
|
||||
### 回滚 1.3 PowerShell 编码
|
||||
```powershell
|
||||
# 删除 start_app.ps1:388 行的 -Encoding UTF8
|
||||
$NewConfContent | Set-Content -LiteralPath "$NginxConfPath" -NoNewline
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 下一步行动
|
||||
|
||||
### 立即行动
|
||||
1. ⏳ 执行第一阶段测试计划
|
||||
2. ⏳ 记录测试结果到 `SECURITY_FIX_PROGRESS.md`
|
||||
|
||||
### 测试通过后
|
||||
1. 决定是否继续第二阶段修复
|
||||
2. 如果继续,开始第二阶段:防御性增强
|
||||
|
||||
### 测试失败时
|
||||
1. 分析失败原因
|
||||
2. 记录到 `SECURITY_FIX_PROGRESS.md` 的"问题记录"区域
|
||||
3. 决定是否回滚
|
||||
4. 如果回滚,记录回滚原因
|
||||
|
||||
---
|
||||
|
||||
## 文档更新
|
||||
|
||||
已更新的文档:
|
||||
- ✅ `SECURITY_FIX_PROGRESS.md` - 更新修复状态和总体进度
|
||||
- ✅ `SECURITY_FIX_CHECKLIST.md` - 标记代码修改完成
|
||||
- ✅ `CLAUDE.md` - 更新当前任务状态
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
第一阶段修复已完成所有代码修改,共修复 3 个明确的 bug:
|
||||
1. ✅ ENVI 主流程返回值缺失
|
||||
2. ✅ 失败计数重复累加
|
||||
3. ✅ PowerShell 编码问题
|
||||
|
||||
所有修改都是低风险的,不涉及架构变更,只修复明确的代码错误。Python 语法检查已通过,现在等待功能测试验证。
|
||||
|
||||
**风险评估**:🟢 低风险
|
||||
**建议**:可以安全地进行测试,不太可能影响现有功能。
|
||||
@@ -0,0 +1,273 @@
|
||||
# 安全审计与修复总览
|
||||
|
||||
## 📋 文档导航
|
||||
|
||||
本次安全审计产生了以下文档,请按顺序阅读:
|
||||
|
||||
1. **[SECURITY_AUDIT_2026-03-04.md](./SECURITY_AUDIT_2026-03-04.md)** - 原始审计报告
|
||||
- 审计人员提供的问题清单
|
||||
- 包含代码位置和影响分析
|
||||
|
||||
2. **[SECURITY_AUDIT_ANALYSIS_2026-03-04.md](./SECURITY_AUDIT_ANALYSIS_2026-03-04.md)** - 详细分析文档
|
||||
- 每个问题的根本原因分析
|
||||
- 多种修复方案对比
|
||||
- 长期改进建议
|
||||
|
||||
3. **[SECURITY_FIX_PLAN.md](./SECURITY_FIX_PLAN.md)** - 分阶段修复计划 ⭐
|
||||
- 四个阶段的详细修复计划
|
||||
- 风险评估和测试方案
|
||||
- 回滚计划和时间表
|
||||
|
||||
4. **[SECURITY_FIX_PROGRESS.md](./SECURITY_FIX_PROGRESS.md)** - 进度跟踪
|
||||
- 实时更新的修复进度
|
||||
- 测试结果记录
|
||||
- 问题和回滚记录
|
||||
|
||||
5. **[SECURITY_FIX_CHECKLIST.md](./SECURITY_FIX_CHECKLIST.md)** - 快速检查清单
|
||||
- 每个修复项的检查清单
|
||||
- 快速测试命令和脚本
|
||||
- 紧急回滚程序
|
||||
|
||||
---
|
||||
|
||||
## 🎯 问题概览
|
||||
|
||||
### 高危问题(3 个)
|
||||
1. **ENVI 主流程返回值缺失** - 导致所有工作流任务失败
|
||||
2. **只读用户可触发写操作** - 权限绕过 + DoS 风险
|
||||
3. **AOI token 内存泄漏** - 可导致进程 OOM
|
||||
|
||||
### 中危问题(4 个)
|
||||
1. **失败计数重复累加** - 统计数据不准确
|
||||
2. **AOI 解析异常处理** - 用户输入错误返回 500
|
||||
3. **路径归属判断错误** - 前缀误匹配风险
|
||||
4. **解包安全校验不足** - 符号链接逃逸风险
|
||||
|
||||
### 低危问题(1 个)
|
||||
1. **PowerShell 编码问题** - 可能导致 Nginx 配置解析失败
|
||||
|
||||
---
|
||||
|
||||
## 🚀 修复策略
|
||||
|
||||
### 分阶段修复原则
|
||||
1. **安全第一** - 每个阶段充分测试后再继续
|
||||
2. **最小改动** - 优先选择风险最低的方案
|
||||
3. **可回滚** - 每个阶段独立提交
|
||||
4. **渐进式** - 从简单到复杂
|
||||
|
||||
### 修复阶段
|
||||
|
||||
#### 第一阶段:明确 Bug 修复(1 天)
|
||||
- ✅ ENVI 主流程返回值缺失
|
||||
- ⏳ 失败计数重复累加
|
||||
- ⏳ PowerShell 编码问题
|
||||
|
||||
**风险等级**:🟢 低
|
||||
**状态**:🚧 进行中(1/3 完成)
|
||||
|
||||
#### 第二阶段:防御性增强(2-3 天)
|
||||
- ⏳ AOI token 容量上限
|
||||
- ⏳ AOI 解析异常处理
|
||||
- ⏳ 路径归属判断优化
|
||||
|
||||
**风险等级**:🟡 中
|
||||
**状态**:⏳ 待开始
|
||||
**前置条件**:第一阶段完成并测试通过
|
||||
|
||||
#### 第三阶段:安全加固(2-3 天)
|
||||
- ⏳ 解包安全校验增强
|
||||
|
||||
**风险等级**:🟡 中
|
||||
**状态**:⏳ 待开始
|
||||
**前置条件**:第二阶段完成并测试通过
|
||||
|
||||
#### 第四阶段:权限重构(待定)
|
||||
- ⏸️ 只读用户权限控制
|
||||
|
||||
**风险等级**:🔴 高
|
||||
**状态**:⏸️ 暂缓
|
||||
**原因**:需要明确需求和前端配合
|
||||
|
||||
---
|
||||
|
||||
## 📊 当前状态
|
||||
|
||||
### 总体进度
|
||||
- **已完成**:1/8 (12.5%)
|
||||
- **进行中**:1/8 (12.5%)
|
||||
- **待开始**:6/8 (75%)
|
||||
- **已暂缓**:1/8 (12.5%)
|
||||
|
||||
### 已完成的修复
|
||||
1. ✅ ENVI 主流程返回值缺失
|
||||
- 文件:`backend/app/services/envi_service.py:1630`
|
||||
- 修改:添加 `return record`
|
||||
- 状态:代码已修改,待测试
|
||||
|
||||
### 下一步行动
|
||||
1. 测试 ENVI 主流程返回值修复
|
||||
2. 如果测试通过,继续修复 1.2 和 1.3
|
||||
3. 完成第一阶段后提交代码
|
||||
|
||||
---
|
||||
|
||||
## 🧪 测试要求
|
||||
|
||||
### 第一阶段测试
|
||||
- [ ] ENVI 工作流端到端测试
|
||||
- [ ] 失败计数准确性测试
|
||||
- [ ] PowerShell 编码测试
|
||||
|
||||
### 第二阶段测试
|
||||
- [ ] AOI token 压力测试(1000+ tokens)
|
||||
- [ ] AOI 解析异常处理测试
|
||||
- [ ] 路径判断边界测试
|
||||
|
||||
### 第三阶段测试
|
||||
- [ ] 解包安全测试(符号链接、路径逃逸)
|
||||
- [ ] 解包性能测试
|
||||
|
||||
---
|
||||
|
||||
## 🔄 回滚计划
|
||||
|
||||
每个阶段独立提交,出现问题时可以:
|
||||
|
||||
### 快速回滚
|
||||
```bash
|
||||
git revert <commit-hash>
|
||||
```
|
||||
|
||||
### 部分回滚
|
||||
```bash
|
||||
git checkout <previous-commit> -- <file-path>
|
||||
git commit -m "revert: 回滚 <file-path> 的修改"
|
||||
```
|
||||
|
||||
### 紧急回滚
|
||||
```bash
|
||||
git reset --hard <last-stable-commit>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📈 监控指标
|
||||
|
||||
### 关键指标
|
||||
- ENVI 工作流成功率
|
||||
- AOI token 内存占用
|
||||
- API 错误率(400 vs 500)
|
||||
- 解包性能
|
||||
|
||||
### 监控工具
|
||||
- 应用日志
|
||||
- 系统资源监控
|
||||
- 数据库性能监控
|
||||
|
||||
---
|
||||
|
||||
## 🤝 协作流程
|
||||
|
||||
### 修复流程
|
||||
1. 阅读修复计划(SECURITY_FIX_PLAN.md)
|
||||
2. 执行代码修改
|
||||
3. 本地测试
|
||||
4. 更新进度文档(SECURITY_FIX_PROGRESS.md)
|
||||
5. 提交代码
|
||||
6. 集成测试
|
||||
7. 部署到测试环境
|
||||
|
||||
### 测试流程
|
||||
1. 阅读检查清单(SECURITY_FIX_CHECKLIST.md)
|
||||
2. 准备测试数据
|
||||
3. 执行测试用例
|
||||
4. 记录测试结果
|
||||
5. 更新进度文档
|
||||
|
||||
### 问题处理流程
|
||||
1. 发现问题立即记录到进度文档
|
||||
2. 分析问题原因
|
||||
3. 决定是否回滚
|
||||
4. 如果回滚,记录回滚原因
|
||||
5. 修复问题后重新测试
|
||||
|
||||
---
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
### 遇到问题时
|
||||
1. 查看进度文档的"问题记录"区域
|
||||
2. 查看分析文档的相关章节
|
||||
3. 如果需要回滚,参考回滚计划
|
||||
|
||||
### 需要讨论时
|
||||
- 第四阶段(权限重构)需要讨论需求
|
||||
- 遇到无法解决的技术问题
|
||||
- 需要调整修复计划
|
||||
|
||||
---
|
||||
|
||||
## 📝 更新日志
|
||||
|
||||
### 2026-03-04
|
||||
- ✅ 创建审计分析文档
|
||||
- ✅ 创建分阶段修复计划
|
||||
- ✅ 创建进度跟踪文档
|
||||
- ✅ 创建快速检查清单
|
||||
- ✅ 完成 1.1 ENVI 主流程返回值修复(代码已修改,待测试)
|
||||
|
||||
---
|
||||
|
||||
## 🎓 经验总结
|
||||
|
||||
### 审计发现的根本问题
|
||||
1. **错误处理不完善** - 成功路径缺少返回值
|
||||
2. **权限控制不严格** - GET 请求触发写操作
|
||||
3. **资源管理不当** - 内存无限增长
|
||||
4. **统计逻辑错误** - 重复累加
|
||||
5. **安全校验不足** - 路径判断、解包校验
|
||||
|
||||
### 改进建议
|
||||
1. 引入代码审查流程
|
||||
2. 添加单元测试覆盖
|
||||
3. 使用静态分析工具
|
||||
4. 定期安全审计
|
||||
5. 完善错误处理和日志
|
||||
|
||||
---
|
||||
|
||||
## 📚 参考资料
|
||||
|
||||
### 相关文档
|
||||
- [CLAUDE.md](../CLAUDE.md) - 项目工作备忘录
|
||||
- [AI_ANALYSIS_REDESIGN.md](./AI_ANALYSIS_REDESIGN.md) - AI 分析模块设计
|
||||
- [RECOMMENDED_VLM_MODELS.md](./RECOMMENDED_VLM_MODELS.md) - 推荐多模态模型
|
||||
|
||||
### 外部资源
|
||||
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
|
||||
- [Python Security Best Practices](https://python.readthedocs.io/en/stable/library/security_warnings.html)
|
||||
- [FastAPI Security](https://fastapi.tiangolo.com/tutorial/security/)
|
||||
|
||||
---
|
||||
|
||||
## ✅ 下一步
|
||||
|
||||
1. **立即行动**:
|
||||
- 测试 1.1 ENVI 主流程返回值修复
|
||||
- 准备测试环境和测试数据
|
||||
|
||||
2. **本周计划**:
|
||||
- 完成第一阶段所有修复
|
||||
- 充分测试第一阶段
|
||||
- 提交第一阶段代码
|
||||
|
||||
3. **下周计划**:
|
||||
- 根据第一阶段测试结果决定是否继续
|
||||
- 如果继续,开始第二阶段
|
||||
|
||||
---
|
||||
|
||||
**最后更新**:2026-03-04
|
||||
**文档版本**:v1.0
|
||||
**维护人**:Claude Opus 4.6
|
||||
@@ -0,0 +1,115 @@
|
||||
# InSAR Management System TODO(持续维护)
|
||||
|
||||
最后更新:2026-02-10
|
||||
维护目标:帮助开发者快速定位风险、统一改造节奏、避免“修一处坏一处”。
|
||||
|
||||
---
|
||||
|
||||
## 一、当前总体状态
|
||||
|
||||
- 认证授权:已切换为 `Session + Cookie`,管理员/只读权限可用。
|
||||
- 用户管理:管理员可创建与管理账号,前端面板已接入。
|
||||
- 审计日志:高风险写接口审计已接入,前端可查询。
|
||||
- 雷达源图预览:已进入 **Scheme B(后端预纠正缓存)** 实装阶段。
|
||||
- 调度模式:当前默认 `Manual-only`,通过手动触发扫描,不启用后台定时任务。
|
||||
|
||||
---
|
||||
|
||||
## 二、任务看板(按优先级)
|
||||
|
||||
## P0(本周优先)
|
||||
|
||||
### 1) SEC-01 鉴权与权限边界完善
|
||||
- **状态**:DONE(首版完成,持续加固)
|
||||
- **目标**:确保所有写接口只允许管理员,所有新增接口必须复用权限依赖。
|
||||
- **验收**:
|
||||
- 未登录访问受限接口返回 `401`
|
||||
- 只读用户写操作返回 `403`
|
||||
- 管理员写操作返回 `2xx`
|
||||
|
||||
### 2) SEC-01A 审计链路闭环
|
||||
- **状态**:DONE(首版完成,持续加固)
|
||||
- **目标**:高风险操作均可追踪“谁、何时、做了什么”。
|
||||
- **验收**:
|
||||
- 后端 `GET /api/auth/audit-logs` 可查关键操作
|
||||
- 前端审计面板支持过滤与检索
|
||||
|
||||
### SEC-02(新增)部署安全基线收敛
|
||||
- **状态**:TODO
|
||||
- **目标**:降低默认配置带来的部署风险,形成可复制的最小安全基线。
|
||||
- **验收**:
|
||||
- 生产环境 `CORS_ORIGINS` 使用明确白名单(不使用 `*`)
|
||||
- 部署文档明确 `.env` 密钥管理规范(不入库、不共享)
|
||||
- HTTP/HTTPS 场景下 `AUTH_COOKIE_SECURE` 配置策略明确
|
||||
|
||||
### 3) GEO-01 Scheme B 纠正缓存主链路
|
||||
- **状态**:DONE(首版落地)
|
||||
- **范围**:
|
||||
- 双缓存目录:`radar_geo` + `radar_raw`
|
||||
- 后端透视纠正生成地理预览缓存
|
||||
- `thumb` 接口优先 `radar_geo`,失败回退 `radar_raw`
|
||||
- 新增状态与重建接口
|
||||
|
||||
### 4) GEO-02 纠正缓存状态可观测
|
||||
- **状态**:DONE(首版落地)
|
||||
- **范围**:
|
||||
- 数据库记录 `preview_cache_status/version/path/error/updated_at`
|
||||
- 前端列表显示状态徽标(纠正/回退/失败/未建)
|
||||
- 管理员可“单条重建缓存”
|
||||
|
||||
---
|
||||
|
||||
## P1(下一阶段)
|
||||
|
||||
### 5) GEO-03 真实数据稳定性验证
|
||||
- **状态**:TODO
|
||||
- **目标**:验证升轨/降轨/大倾斜场景下叠加稳定性。
|
||||
- **验收**:
|
||||
- 覆盖面与源影像视觉对齐明显改善
|
||||
- 地图缩放/平移无明显卡顿
|
||||
- 构建失败样本可在日志与状态中定位
|
||||
|
||||
### 6) PERF-01 大批量扫描性能压测
|
||||
- **状态**:TODO
|
||||
- **目标**:评估 1000+ 场景增量扫描耗时与缓存命中率。
|
||||
- **验收**:
|
||||
- 二次扫描命中率 > 95%
|
||||
- 失败重试不影响主扫描完成
|
||||
|
||||
### 7) MAINT-01 前端 `App.jsx` 模块化拆分
|
||||
- **状态**:TODO
|
||||
- **目标**:降低单文件复杂度,便于持续维护。
|
||||
|
||||
---
|
||||
|
||||
## 三、里程碑记录
|
||||
|
||||
### 2026-02-10(Session 鉴权阶段)
|
||||
- 完成 Session + Cookie 鉴权首版。
|
||||
- 完成管理员初始化与用户管理基础链路。
|
||||
- 修复登录阶段 `MissingGreenlet` 异常。
|
||||
|
||||
### 2026-02-10(审计与运维阶段)
|
||||
- 接入高风险写接口审计日志。
|
||||
- 前端新增审计日志面板。
|
||||
|
||||
### 2026-02-10(Scheme B 首版实装)
|
||||
- 新增 `radar_geo` 与 `radar_raw` 双缓存策略。
|
||||
- 扫描流程支持增量构建纠正缓存与回退缓存。
|
||||
- 新增接口:
|
||||
- `GET /api/radar-data/{id}/preview-status`
|
||||
- `POST /api/radar-data/{id}/rebuild-preview-cache`
|
||||
- 前端源影像列表支持状态显示与管理员重建。
|
||||
- 方向判定升级:优先读取 XML 的 `refRow/refColumn` 映射像素角与地理角,减少方向错位。
|
||||
|
||||
### 2026-02-10(文档与实现对齐)
|
||||
- 明确当前扫描调度模式为 `Manual-only`(非定时守护)。
|
||||
- 补充部署安全基线项(CORS 白名单、`.env` 密钥管理、Cookie 安全策略)。
|
||||
|
||||
---
|
||||
|
||||
## 四、开发注意事项(必须遵守)
|
||||
|
||||
- 不得破坏 `start_system.bat` 与 `scripts/start_app.ps1` 的建库/校验/修复启动链路。
|
||||
- 任何数据库结构变更后,必须同步更新 `scripts/init_db.py` 的非破坏性补齐能力。
|
||||
- 新增功能必须同步更新 `docs/DEPLOYMENT.md` 与本文件。
|
||||
@@ -0,0 +1,94 @@
|
||||
# 水体提取与洪涝监测 — 开发 TODO
|
||||
|
||||
## 架构概览
|
||||
|
||||
```
|
||||
Phase 1: 水体提取 → WaterMaskORM(每景影像的水体掩膜)
|
||||
Phase 2: 水体配对 → WaterPairORM(参考期 + 监测期)
|
||||
Phase 3: 变化检测 → FloodEventORM(洪涝事件 + 告警)
|
||||
```
|
||||
|
||||
依赖库(InSAR conda 环境已有):rasterio, shapely, numpy, scipy, Pillow
|
||||
|
||||
---
|
||||
|
||||
## 任务列表
|
||||
|
||||
### Step 1:ORM 建表
|
||||
- [x] W01 `orm.py` 新增 WaterMaskORM、WaterPairORM、FloodEventORM
|
||||
- [x] W02 `models/__init__.py` 导出新 ORM
|
||||
- [x] W03 Alembic migration 0002_water_tables.py
|
||||
|
||||
### Step 2:核心服务
|
||||
- [x] W04 `water_service.py` Phase 1 — 单景水体提取(OTSU + 形态学 + 矢量化)
|
||||
- [x] W05 `water_service.py` Phase 2 — 水体配对逻辑(空间重叠 + 时间筛选)
|
||||
- [x] W06 `water_service.py` Phase 3 — 变化检测(掩膜差值 + 面积统计 + 告警)
|
||||
|
||||
### Step 3:Job 集成
|
||||
- [x] W07 `job_handlers.py` 新增 WATER_EXTRACT / WATER_DETECT job 类型
|
||||
|
||||
### Step 4:路由
|
||||
- [x] W08 `routers/water.py` 全部端点
|
||||
- [x] W09 `routers/__init__.py` 注册 water router
|
||||
|
||||
### Step 5:前端
|
||||
- [x] W10 `api/water.js` API 层
|
||||
- [x] W11 `WaterMonitorPanel.jsx` 三 Tab 面板(提取/配对/事件)
|
||||
- [x] W12 `App.jsx` 注册新面板入口
|
||||
|
||||
---
|
||||
|
||||
## 端点设计
|
||||
|
||||
```
|
||||
POST /water/extract # 批量提取(传 radar_data_ids 列表)
|
||||
GET /water/masks # 查询水体掩膜列表(分页)
|
||||
GET /water/masks/{id}/preview # 水体掩膜预览图
|
||||
|
||||
POST /water/pairs # 创建配对(reference_id + monitor_id)
|
||||
GET /water/pairs # 查询配对列表
|
||||
POST /water/pairs/{id}/detect # 对指定配对执行变化检测
|
||||
|
||||
GET /water/events # 查询洪涝事件列表
|
||||
GET /water/events/{id}/preview # 变化图预览
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 关键算法备忘
|
||||
|
||||
### OTSU(纯 numpy 实现,不依赖 scikit-image)
|
||||
```python
|
||||
def _otsu_threshold(arr_db):
|
||||
hist, bin_edges = np.histogram(arr_db[np.isfinite(arr_db)], bins=256)
|
||||
bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
|
||||
total = hist.sum()
|
||||
w0 = np.cumsum(hist) / total
|
||||
w1 = 1 - w0
|
||||
mu0 = np.cumsum(hist * bin_centers) / (np.cumsum(hist) + 1e-10)
|
||||
mu1 = (np.sum(hist * bin_centers) - np.cumsum(hist * bin_centers)) / (np.cumsum(hist[::-1])[::-1] + 1e-10)
|
||||
sigma_b = w0 * w1 * (mu0 - mu1) ** 2
|
||||
return bin_centers[np.argmax(sigma_b)]
|
||||
```
|
||||
|
||||
### 水体掩膜预览图
|
||||
- 原始影像灰度拉伸为背景(半透明)
|
||||
- 水体区域叠加蓝色(RGBA: 0, 100, 255, 180)
|
||||
- 新增水体叠加红色(洪涝变化图)
|
||||
|
||||
---
|
||||
|
||||
## 状态
|
||||
|
||||
**全部完成** ✓ — 等待测试
|
||||
|
||||
### 测试步骤
|
||||
1. 重启后端(新表由 create_all 自动创建)
|
||||
2. 前端强制刷新,进入"结果分析"→"水体监测"
|
||||
3. Phase 1:输入已入库的雷达数据 ID,提交提取任务,等待完成后刷新列表
|
||||
4. Phase 2:选参考期和监测期掩膜 ID,创建配对
|
||||
5. Phase 3:对配对点击"执行变化检测",在"洪涝事件"Tab 查看结果
|
||||
|
||||
### 已知限制
|
||||
- `_find_geocoded_path` 依赖 `preview_cache_path` 或 `file_path`,若雷达数据没有地理编码文件则提取失败
|
||||
- 告警阈值默认 30%,可通过 `.env` 中 `FLOOD_ALERT_THRESHOLD=0.3` 调整
|
||||
@@ -0,0 +1,187 @@
|
||||
# 水体监测模块 v2 — 任务清单
|
||||
|
||||
## 背景
|
||||
|
||||
原 water_service.py(手动 OTSU + 形态学)方案废弃。
|
||||
改用 SARscape 原生任务链,精度更高,与 D-InSAR 流程一致。
|
||||
|
||||
## SARscape 任务链
|
||||
|
||||
```
|
||||
单景预处理(每景独立):
|
||||
SARsBasicMultilooking → 多视处理(SLC → 强度图,降噪)
|
||||
SARsBasicGeocoding → 地理编码 + 辐射定标(输出 dB 图)
|
||||
|
||||
洪涝检测(两景配对):
|
||||
SARsBasicFeFloodingClassification → 洪涝分类图
|
||||
SARsBasicFeFloodingClassificationRefinement → MRF 精化(可选)
|
||||
```
|
||||
|
||||
## 关键 Task 参数速查
|
||||
|
||||
### SARsBasicMultilooking
|
||||
- INPUT_SARSCAPEDATA (required)
|
||||
- OUTPUT_SARSCAPEDATA (output)
|
||||
- RANGE_MULTILOOK, AZIMUTH_MULTILOOK (可选,默认自动)
|
||||
- GRID_SIZE_FOR_SUGGESTED_LOOKS (目标分辨率 m)
|
||||
- ROOT_URI_FOR_OUTPUT
|
||||
|
||||
### SARsBasicGeocoding
|
||||
- INPUT_SARSCAPEDATA (required)
|
||||
- OUTPUT_SARSCAPEDATA, OUTPUT_DB_SARSCAPEDATA (output)
|
||||
- DEM_SARSCAPEDATA (可选,有 DEM 精度更高)
|
||||
- GEOCODE_GRID_SIZE_X, GEOCODE_GRID_SIZE_Y (像素大小 m)
|
||||
- CALIBRATION: true(辐射定标)
|
||||
- OUTPUT_TYPE: "output_type_db"(输出 dB)
|
||||
- ROOT_URI_FOR_OUTPUT
|
||||
|
||||
### SARsBasicFeFloodingClassification
|
||||
- INPUT_SARSCAPEDATA (required, 灾前)
|
||||
- POST_EVENT_FILE (required, 灾后)
|
||||
- OUTPUT_SARSCAPEDATA (output, 分类图)
|
||||
- DEM_FILE, SLOPE_FILE (可选,提升精度)
|
||||
- SWL_TH: 水体阈值 dB(默认约 -14)
|
||||
- RATIO_TH: 比值阈值 dB
|
||||
- HIGH_SCATT_POINT_TH: 高散射点阈值 dB
|
||||
- RATIO_SARSCAPEDATA, PRE_EVENT_SARSCAPEDATA, POST_EVENT_SARSCAPEDATA (output)
|
||||
- ROOT_URI_FOR_OUTPUT
|
||||
|
||||
### SARsBasicFeFloodingClassificationRefinement
|
||||
- PRE_EVENT_FILE, POST_EVENT_FILE, CLASSIFIED_FILE, RATIO_FILE (required)
|
||||
- OUTPUT_SARSCAPEDATA (output)
|
||||
- DEM_FILE, SLOPE_FILE (可选)
|
||||
- MRF 参数:M_STABLE_WATER, M_FLOOD, ALPHA_STABLE_WATER, ALPHA_FLOOD 等
|
||||
- ROOT_URI_FOR_OUTPUT
|
||||
|
||||
---
|
||||
|
||||
## 任务列表
|
||||
|
||||
### W2-01 [TODO] 清理旧 water 代码
|
||||
- 删除 backend/app/services/water_service.py
|
||||
- 删除 backend/app/routers/water.py
|
||||
- 清理 backend/app/models/orm.py 中的 WaterMaskORM / WaterPairORM / FloodEventORM
|
||||
- 清理 backend/app/models/__init__.py 中的 water 导入
|
||||
- 清理 backend/app/services/job_handlers.py 中的 WATER_EXTRACT / WATER_DETECT
|
||||
- 删除 backend/alembic/versions/0002_water_tables.py
|
||||
- 删除 frontend/src/api/water.js
|
||||
- 删除 frontend/src/WaterMonitorPanel.jsx
|
||||
- 清理 frontend/src/config/appConstants.js(移除 'water' tab)
|
||||
- 清理 frontend/src/utils/appUiHelpers.js(移除 water case)
|
||||
- 清理 frontend/src/App.jsx(移除 WaterMonitorPanel 引用)
|
||||
|
||||
### W2-02 [TODO] 设计新 ORM 模型
|
||||
新增两张表:
|
||||
|
||||
**SARSceneGeoORM** (sar_scene_geo)
|
||||
- id, radar_data_id (FK → radar_data.id)
|
||||
- geo_path: 地理编码 dB 文件路径(无扩展名,ENVI 格式)
|
||||
- pixel_size_m: 像素大小
|
||||
- status: PENDING / RUNNING / DONE / FAILED
|
||||
- error_msg
|
||||
- created_at, updated_at
|
||||
|
||||
**FloodDetectionORM** (flood_detections)
|
||||
- id
|
||||
- pre_scene_id (FK → sar_scene_geo.id)
|
||||
- post_scene_id (FK → sar_scene_geo.id)
|
||||
- output_dir: 输出目录
|
||||
- classified_path: 分类图路径
|
||||
- flood_area_km2: 洪涝面积
|
||||
- stable_water_area_km2: 稳定水体面积
|
||||
- status: PENDING / RUNNING / DONE / FAILED
|
||||
- error_msg
|
||||
- created_at, updated_at
|
||||
|
||||
### W2-03 [TODO] 编写 Alembic 迁移
|
||||
- 新建 backend/alembic/versions/0002_water_v2.py
|
||||
- down_revision = "0001"
|
||||
- 创建 sar_scene_geo 和 flood_detections 表
|
||||
- 注意:旧 water_masks / water_pairs / flood_events 表如存在需 drop
|
||||
|
||||
### W2-04 [TODO] 实现单景预处理服务
|
||||
新建 backend/app/services/water_service.py(全新)
|
||||
|
||||
函数:`run_geocoding_workflow_sync(radar_data_id, db_session, job_id)`
|
||||
1. 查 RadarDataORM 获取 file_path
|
||||
2. 找 SLC 文件(_slc 后缀,ENVI 格式)
|
||||
3. 创建输出目录:`water_results/{radar_unique_id}/`
|
||||
4. 调用 execute_envi_task("SARsBasicMultilooking", ...)
|
||||
5. 调用 execute_envi_task("SARsBasicGeocoding", ..., OUTPUT_TYPE="output_type_db")
|
||||
6. 解析输出路径,更新 SARSceneGeoORM.status = DONE
|
||||
7. 写进度文件(复用 _write_progress 机制)
|
||||
|
||||
### W2-05 [TODO] 实现洪涝检测服务
|
||||
在 water_service.py 中新增:
|
||||
|
||||
函数:`run_flood_detection_sync(pre_scene_id, post_scene_id, db_session, job_id, refine=False)`
|
||||
1. 查两个 SARSceneGeoORM,验证 status=DONE
|
||||
2. 创建输出目录:`water_results/flood_{pre_id}_{post_id}/`
|
||||
3. 调用 execute_envi_task("SARsBasicFeFloodingClassification", ...)
|
||||
4. 可选:调用 SARsBasicFeFloodingClassificationRefinement
|
||||
5. 用 rasterio 读分类图,统计各类像素面积(km²)
|
||||
6. 更新 FloodDetectionORM
|
||||
|
||||
### W2-06 [TODO] 更新 job_handlers.py
|
||||
- 注册 JOB_TYPE_WATER_GEOCODE = "WATER_GEOCODE"
|
||||
- 注册 JOB_TYPE_WATER_FLOOD = "WATER_FLOOD"
|
||||
- 实现 _handle_water_geocode / _handle_water_flood
|
||||
- 加入 _HANDLERS 字典
|
||||
|
||||
### W2-07 [TODO] 更新 envi_runner_cli.py
|
||||
- choices 增加 "water_geocode" / "water_flood"
|
||||
- main() 中路由到对应 water_service 函数
|
||||
|
||||
### W2-08 [TODO] 新建 water.py router
|
||||
- POST /water/geocode (admin): 提交单景地理编码任务
|
||||
- GET /water/scenes: 列出已处理场景(关联 radar_data 的 satellite/date)
|
||||
- POST /water/flood-detect (admin): 提交洪涝检测任务
|
||||
- GET /water/flood-events: 列出洪涝检测结果
|
||||
- GET /water/flood-events/{id}/preview: 返回分类图预览
|
||||
|
||||
### W2-09 [TODO] 新建 WaterMonitorPanel.jsx(前端)
|
||||
三个 Tab:
|
||||
|
||||
**Tab 1 — 单景预处理**
|
||||
- 从雷达数据列表选择一景(或输入 radar_data_id)
|
||||
- 显示已处理场景列表(状态、像素大小、处理时间)
|
||||
- 提交按钮 → POST /water/geocode
|
||||
|
||||
**Tab 2 — 洪涝检测**
|
||||
- 选择灾前场景 + 灾后场景(从已处理列表选)
|
||||
- 可选:是否启用 MRF 精化
|
||||
- 提交按钮 → POST /water/flood-detect
|
||||
|
||||
**Tab 3 — 洪涝事件**
|
||||
- 列表:灾前日期、灾后日期、洪涝面积、稳定水体面积、状态
|
||||
- 点击查看分类图预览
|
||||
|
||||
### W2-10 [TODO] 注册路由 + 前端 Tab
|
||||
- backend/app/routers/__init__.py 引入 water router
|
||||
- frontend/src/config/appConstants.js 加 'water' tab
|
||||
- frontend/src/utils/appUiHelpers.js 加 case
|
||||
- frontend/src/App.jsx 引入 WaterMonitorPanel
|
||||
- frontend/src/api/water.js(新建 API 封装)
|
||||
|
||||
### W2-11 [TODO] 测试验证
|
||||
- 用 Image_Pool_2025 中一景 LT1 数据测试单景预处理
|
||||
- 选两景配对测试洪涝检测
|
||||
- 验证分类图输出和面积统计
|
||||
|
||||
---
|
||||
|
||||
## 进度
|
||||
|
||||
| 任务 | 状态 |
|
||||
|------|------|
|
||||
| W2-01 清理旧代码 | DONE |
|
||||
| W2-02 新 ORM 模型 | DONE |
|
||||
| W2-03 Alembic 迁移 | DONE |
|
||||
| W2-04 单景预处理服务 | DONE |
|
||||
| W2-05 洪涝检测服务 | DONE |
|
||||
| W2-06 job_handlers | DONE |
|
||||
| W2-07 envi_runner_cli | DONE |
|
||||
| W2-08 water.py router | DONE |
|
||||
| W2-09 WaterMonitorPanel | DONE |
|
||||
| W2-10 路由注册+前端Tab | DONE |
|
||||
| W2-11 测试验证 | TODO |
|
||||
+403
@@ -0,0 +1,403 @@
|
||||
# InSAR 管理系统项目汇报
|
||||
|
||||
版本日期:2026-03-13
|
||||
|
||||
## 一、汇报目的
|
||||
|
||||
本文用于项目汇报和方案评审,重点说明三件事:
|
||||
|
||||
1. 当前 InSAR 管理系统的建设内容和系统价值。
|
||||
2. 系统在 D-InSAR 生产侧已经形成的技术架构与扩展能力。
|
||||
3. 如何将 LANDSAR 系统作为第三个 D-InSAR 生产核心嵌入本系统,以及这件事对采购方案的直接影响。
|
||||
|
||||
本文是单独汇报材料,强调整体把握、系统边界、实施路径和采购判断,不展开到底层代码细节。
|
||||
|
||||
## 二、项目总体定位
|
||||
|
||||
本项目不是单一的数据展示平台,而是一个面向 InSAR 业务的综合管理与生产支撑平台,核心目标是把“数据入库、生产组织、结果管理、运行监控、质量分析”放到同一套系统中统一管理。
|
||||
|
||||
从业务定位看,系统承担了以下角色:
|
||||
|
||||
1. 数据管理平台:统一管理雷达源数据、轨道数据、D-InSAR 结果、水体监测结果等。
|
||||
2. 生产组织平台:支持配对、批次管理、任务下发、后台处理、结果回收。
|
||||
3. 运维监控平台:支持健康检查、任务状态、日志、鉴权、许可证控制。
|
||||
4. 分析辅助平台:支持 AI 质量分析、AI 诊断、统计看板、地图可视化。
|
||||
|
||||
因此,这个项目的价值不只是“能不能跑一次处理”,而是把整个业务流程固化为可持续运行的平台能力。
|
||||
|
||||
## 三、当前系统建设情况
|
||||
|
||||
### 3.1 技术架构
|
||||
|
||||
当前系统采用前后端分离架构:
|
||||
|
||||
1. 前端:React + Vite,使用 Leaflet 负责地图展示,Chart.js 负责统计图表,Zustand 负责状态管理。
|
||||
2. 后端:FastAPI 提供 REST API,SQLAlchemy 负责 ORM,Pydantic 负责数据模型校验。
|
||||
3. 数据库:PostgreSQL + PostGIS,用于业务数据存储和空间能力支撑。
|
||||
4. 后台任务:独立 Job Worker 进程,负责执行扫描、生产、AI、解包等异步任务。
|
||||
5. 部署入口:Nginx 负责前端静态资源和反向代理。
|
||||
6. 外部能力:可选接入 ENVI/SARscape、ISCE2、Ollama 等外部组件。
|
||||
|
||||
### 3.2 主要功能模块
|
||||
|
||||
系统目前已经具备较完整的业务模块:
|
||||
|
||||
1. 雷达数据管理
|
||||
支持监控目录扫描、元数据解析、预览图生成、空间范围展示、检索和分页浏览。
|
||||
2. 轨道数据管理
|
||||
支持轨道文件关联和本地轨道池同步,为生产链路提供基础输入。
|
||||
3. 配对与批次管理
|
||||
支持 D-InSAR 配对策略、批量任务组织、批次项管理和后续复制导出。
|
||||
4. D-InSAR 生产中心
|
||||
已经从单引擎思路升级为多引擎生产中心,支持统一查看引擎状态、提交任务、查看运行记录。
|
||||
5. D-InSAR 结果管理
|
||||
支持结果扫描入库、地图展示、缓存、导出、AI 评分与人工标注。
|
||||
6. AI 能力
|
||||
支持质量模型训练、质量预测、影像诊断、报告生成。
|
||||
7. 水体监测
|
||||
已形成相对独立的处理链路和界面。
|
||||
8. 运维与权限
|
||||
包含健康检查、任务管理、审计日志、用户管理、登录限流、许可证控制等。
|
||||
|
||||
### 3.3 当前系统成熟度判断
|
||||
|
||||
从工程状态看,项目已经不是原型阶段,而是进入“可持续扩展的平台阶段”:
|
||||
|
||||
1. 业务边界已明确。
|
||||
2. 前后端结构已稳定。
|
||||
3. 后台任务与数据库模型已成体系。
|
||||
4. 多引擎 D-InSAR 生产框架已经搭起来。
|
||||
5. 系统具备继续接入第三方生产核心的基础。
|
||||
|
||||
这意味着后续采购不应再按“买一个孤立的软件工具”来考虑,而应按“纳入统一生产平台的外部生产核心”来考虑。
|
||||
|
||||
## 四、系统运行主线
|
||||
|
||||
从全流程看,当前系统的主线可以概括为:
|
||||
|
||||
1. 数据进入系统
|
||||
扫描雷达目录、轨道目录、结果目录,完成入库和索引。
|
||||
2. 用户在前端组织任务
|
||||
选择 AOI、筛选源数据、执行配对、形成批次、下发生产任务。
|
||||
3. 后端进入异步处理模式
|
||||
系统创建任务记录和作业记录,由 Worker 执行具体处理。
|
||||
4. 外部引擎执行生产
|
||||
当前已支持 ENVI/SARscape 和 ISCE2 两类生产能力。
|
||||
5. 结果回流系统
|
||||
产物落地后可扫描入库、生成缓存、展示到地图和列表,并进入后续分析。
|
||||
6. 运维与质量闭环
|
||||
通过健康检查、日志、审计、AI 分析和人工核查,形成可运维闭环。
|
||||
|
||||
这一主线决定了本系统天然适合接入多个生产引擎,只要外部引擎能被标准化调用。
|
||||
|
||||
## 五、D-InSAR 生产体系现状
|
||||
|
||||
### 5.1 已形成的多引擎框架
|
||||
|
||||
当前项目已经不再把 D-InSAR 生产写死在某一套工具链上,而是抽象成统一的“生产引擎”框架。对每个引擎,系统要求至少具备三类能力:
|
||||
|
||||
1. 可用性检查:系统要知道该引擎当前能不能用。
|
||||
2. Profile 列表:系统要知道该引擎支持哪些处理链路。
|
||||
3. 统一运行入口:系统要能按同样的方式下发任务并接收结果。
|
||||
|
||||
在这个框架下,系统现在已有三类引擎标识:
|
||||
|
||||
1. `sarscape`
|
||||
已可运行,对接现有 ENVI/SARscape 流程。
|
||||
2. `isce2`
|
||||
已可运行,通过 WSL 调用 ISCE2 链路。
|
||||
3. `landsar`
|
||||
目前仅做接口预留和前端占位,尚未真正接入生产能力。
|
||||
|
||||
### 5.2 当前已落地的两个生产核心
|
||||
|
||||
#### 1. ENVI / SARscape
|
||||
|
||||
这是当前系统最成熟的生产核心,主要特点:
|
||||
|
||||
1. 与现有历史流程兼容度高。
|
||||
2. 支持 `metatask` 和 `custom6` 两种 profile。
|
||||
3. 已具备运行、日志、结果回收和健康检查能力。
|
||||
4. 适合作为当前生产主力链路。
|
||||
|
||||
#### 2. ISCE2
|
||||
|
||||
这是当前系统第二个生产核心,主要特点:
|
||||
|
||||
1. 通过 WSL 方式运行,避免强依赖本机 Windows 图形环境。
|
||||
2. 已具备环境检查、路径转换、脚本调用、任务执行能力。
|
||||
3. 适合承接 LT-1 等标准化链路。
|
||||
4. 为系统后续接入更多异构引擎提供了样板。
|
||||
|
||||
### 5.3 LANDSAR 当前状态
|
||||
|
||||
这一点在汇报和采购中必须明确说明:
|
||||
|
||||
截至 2026-03-13,`LANDSAR` 在本项目中属于“架构已预留、能力未接入”的状态,具体表现为:
|
||||
|
||||
1. 后端已存在 `landsar` 引擎占位。
|
||||
2. 前端生产中心已预留 `LANDSAR` 状态卡位置。
|
||||
3. 健康检查体系已将 `LANDSAR` 视为可纳入统一监控的引擎类型。
|
||||
4. 轨道池体系中已预留 `ORBIT_POOL_LANDSAR` 的配置位置。
|
||||
5. 但任务提交、执行、日志、结果回收尚未真正实现。
|
||||
6. 当前生产提交接口实际仍只支持已落地的引擎,`LANDSAR` 还不能像现有生产核心那样直接投入运行。
|
||||
|
||||
这意味着:系统架构已经准备好接纳 LANDSAR,但采购后的接入工作仍需要正式实施,不能误判为“买来即可无缝运行”。
|
||||
|
||||
## 六、为什么要把 LANDSAR 作为第三个生产核心
|
||||
|
||||
从平台演进角度,引入 LANDSAR 的意义主要有四点:
|
||||
|
||||
1. 降低对单一商业引擎的依赖。
|
||||
2. 提升不同场景下的处理适配能力。
|
||||
3. 形成多引擎并行的生产格局,增强方案弹性。
|
||||
4. 使采购的软件能力真正纳入平台统一调度,而不是形成新的信息孤岛。
|
||||
|
||||
如果 LANDSAR 只是作为独立软件单独运行,那么它对现有系统的价值有限,最终仍然会回到“人工切换工具、人工导入结果、人工追踪过程”的旧模式。只有把它嵌入本系统,成为第三个生产核心,采购价值才能最大化。
|
||||
|
||||
## 七、LANDSAR 接入的目标定位
|
||||
|
||||
本项目对 LANDSAR 的目标定位不是“重写 LANDSAR 算法”,而是“把 LANDSAR 系统现有 D-InSAR 生产服务纳入统一调度平台”。
|
||||
|
||||
因此,推荐的定位是:
|
||||
|
||||
1. 本系统负责任务组织、权限控制、日志审计、状态展示、结果入库和运维闭环。
|
||||
2. LANDSAR 系统负责实际 D-InSAR 生产处理。
|
||||
3. 双方通过标准接口交互,而不是把两边代码强行揉成一体。
|
||||
|
||||
这也是最符合采购落地的模式,因为它对供应商最明确,对我方平台风险最可控。
|
||||
|
||||
## 八、LANDSAR 与本系统的推荐交互模式
|
||||
|
||||
### 8.1 推荐模式:服务嵌入式接入
|
||||
|
||||
建议将 LANDSAR 以“外部生产服务”的方式接入本系统,而不是以人工操作方式接入。
|
||||
|
||||
推荐架构如下:
|
||||
|
||||
1. 用户仍然只在本系统前端操作。
|
||||
2. 用户在 D-InSAR 生产中心中选择 `LANDSAR` 作为引擎。
|
||||
3. 本系统后端创建标准任务和作业记录。
|
||||
4. `landsar` 适配引擎把任务参数发送给 LANDSAR 生产服务。
|
||||
5. LANDSAR 返回运行编号并执行处理。
|
||||
6. 本系统轮询任务状态,或者接收 LANDSAR 回调通知。
|
||||
7. 处理完成后,产物输出到约定目录,或由本系统拉取到指定目录。
|
||||
8. 本系统完成结果扫描、入库、展示和后续分析。
|
||||
|
||||
这样做的最大好处是:对用户来说,LANDSAR 不是另一套系统,而是本系统中的第三个生产核心。
|
||||
|
||||
### 8.2 不推荐模式:纯人工导入导出
|
||||
|
||||
如果采购的 LANDSAR 只能做到“人工打开软件、手工点运行、手工拷贝结果”,则它不能真正成为第三个生产核心,只能成为一个外部工具。这样会带来以下问题:
|
||||
|
||||
1. 无法统一任务状态。
|
||||
2. 无法统一日志与审计。
|
||||
3. 无法统一批次管理。
|
||||
4. 无法纳入健康检查。
|
||||
5. 无法形成稳定的生产闭环。
|
||||
|
||||
因此,采购时必须把“可集成、可调用、可监控”作为硬条件。
|
||||
|
||||
## 九、LANDSAR 接入后的业务流程
|
||||
|
||||
建议将接入流程设计为以下八步:
|
||||
|
||||
1. 任务组织
|
||||
用户在本系统完成配对、批次选择、AOI 约束和参数选择。
|
||||
2. 任务提交
|
||||
本系统向 LANDSAR 服务提交标准化请求,包括输入目录、输出目录、profile、处理参数、任务编号等。
|
||||
3. 运行登记
|
||||
本系统保存本地任务 ID 和 LANDSAR 侧运行 ID 的映射关系。
|
||||
4. 执行监控
|
||||
本系统通过轮询接口或回调机制获取进度、状态和错误信息。
|
||||
5. 日志采集
|
||||
本系统读取 LANDSAR 返回的执行日志摘要,必要时保留完整日志文件地址。
|
||||
6. 产物回收
|
||||
LANDSAR 将产物输出到双方约定目录,或通过下载接口交给本系统。
|
||||
7. 结果入库
|
||||
本系统对产物进行扫描、索引、缓存生成和结果入库。
|
||||
8. 统一展示
|
||||
用户在现有结果管理界面中查看 LANDSAR 结果,与其他引擎结果统一管理。
|
||||
|
||||
## 十、LANDSAR 作为第三核心时的系统改造点
|
||||
|
||||
从本系统角度,真正需要做的改造不是推倒重来,而是在现有多引擎框架上补全 `landsar` 适配层。
|
||||
|
||||
### 10.1 后端改造点
|
||||
|
||||
1. 完成 `landsar_engine.py`
|
||||
目前这个文件只是占位,需要实现真正的可用性检查、profile 获取和运行逻辑。
|
||||
2. 增加 LANDSAR 任务处理器
|
||||
可以新增独立 `JOB_TYPE_LANDSAR_RUN`,也可以复用统一外部引擎作业模式。
|
||||
3. 增加运行状态映射
|
||||
需要把 LANDSAR 的状态映射到本系统统一状态,如 `PENDING`、`RUNNING`、`COMPLETED`、`FAILED`。
|
||||
4. 增加日志采集与错误归一化
|
||||
要保证前端看到的日志和错误信息可理解、可追踪。
|
||||
5. 增加结果回收逻辑
|
||||
需要把 LANDSAR 输出目录纳入结果扫描与入库体系。
|
||||
6. 补充轨道池适配
|
||||
当前系统已预留 `ORBIT_POOL_LANDSAR`,采购后需明确 LANDSAR 轨道格式和同步策略。
|
||||
|
||||
### 10.2 前端改造点
|
||||
|
||||
1. 让 `LANDSAR` 状态卡从“预留”变成“可选可提交”。
|
||||
2. 展示 LANDSAR 专属 profile 和参数项。
|
||||
3. 在运行历史中显示 `engine=landsar`。
|
||||
4. 在结果列表和详情中标识结果来源于 LANDSAR。
|
||||
|
||||
### 10.3 运维改造点
|
||||
|
||||
1. 增加 LANDSAR 服务健康检查。
|
||||
2. 增加供应商服务连通性检查。
|
||||
3. 增加回调鉴权或调用鉴权。
|
||||
4. 增加 LANDSAR 结果目录一致性检查。
|
||||
|
||||
## 十一、采购时必须明确的接口要求
|
||||
|
||||
这一部分是采购方案的核心。若这些能力得不到保障,LANDSAR 就无法真正嵌入平台。
|
||||
|
||||
### 11.1 必须具备的调用方式
|
||||
|
||||
供应商至少应提供以下三种方式之一,优先级从高到低如下:
|
||||
|
||||
1. HTTP/REST 服务接口
|
||||
最推荐,最适合平台接入。
|
||||
2. 命令行接口(CLI)
|
||||
可作为次优方案,但需要明确返回码、日志和输出目录约定。
|
||||
3. Python/SDK 接口
|
||||
也可接受,但后续部署和版本兼容成本通常更高。
|
||||
|
||||
如果三者都没有,只提供 GUI 操作,则不建议作为“第三生产核心”采购。
|
||||
|
||||
### 11.2 必须具备的最小接口能力
|
||||
|
||||
若采用服务接口,建议采购时要求至少提供以下能力:
|
||||
|
||||
1. 提交任务接口
|
||||
输入参数包括任务编号、profile、输入目录、输出目录、处理参数。
|
||||
2. 查询任务状态接口
|
||||
能返回排队、运行中、完成、失败、取消等状态。
|
||||
3. 查询进度接口
|
||||
能返回百分比或阶段性进度。
|
||||
4. 查询日志接口
|
||||
至少能返回日志摘要,最好支持完整日志文件。
|
||||
5. 查询产物接口
|
||||
能返回主产物、辅产物、日志文件、质量文件等路径或下载地址。
|
||||
6. 健康检查接口
|
||||
能返回服务可用性、许可证状态、核心依赖状态。
|
||||
7. 能力枚举接口
|
||||
能列出当前支持的 profile、版本、约束条件。
|
||||
|
||||
### 11.3 必须明确的数据约定
|
||||
|
||||
采购时要和供应商约定清楚以下数据边界:
|
||||
|
||||
1. 输入目录结构由谁负责准备。
|
||||
2. 输出目录结构是否固定。
|
||||
3. 主结果文件命名规则是否稳定。
|
||||
4. 日志文件命名规则和保存期限。
|
||||
5. 失败任务是否保留中间文件。
|
||||
6. 轨道文件格式和目录组织方式。
|
||||
7. 坐标系、DEM、投影和元数据约定。
|
||||
|
||||
没有这些约定,后续集成必然出现反复沟通和返工。
|
||||
|
||||
## 十二、建议的采购技术条款
|
||||
|
||||
为了确保 LANDSAR 真正能嵌入本系统,建议采购文件中写入如下技术条款。
|
||||
|
||||
### 12.1 必选条款
|
||||
|
||||
1. 供应商须提供可编程调用接口,不得仅提供人工图形界面。
|
||||
2. 供应商须提供任务提交、状态查询、日志查询、结果获取的完整接口说明。
|
||||
3. 供应商须支持与第三方业务系统进行集成,并配合联调。
|
||||
4. 供应商须提供稳定的版本管理和升级兼容说明。
|
||||
5. 供应商须明确许可证机制对服务化调用的限制条件。
|
||||
6. 供应商须配合完成至少一个标准 D-InSAR 生产 profile 的端到端联调验收。
|
||||
|
||||
### 12.2 强烈建议条款
|
||||
|
||||
1. 支持回调通知机制,减少轮询压力。
|
||||
2. 支持独立健康检查接口。
|
||||
3. 支持 profile 枚举和参数模板查询。
|
||||
4. 支持输出标准化目录结构。
|
||||
5. 支持服务部署在内网服务器环境。
|
||||
6. 支持批量任务执行和失败重试。
|
||||
|
||||
### 12.3 验收建议
|
||||
|
||||
建议把验收拆成两个层次:
|
||||
|
||||
1. 集成验收
|
||||
能在本系统中完成 LANDSAR 任务提交、状态查看、日志查看、结果入库。
|
||||
2. 业务验收
|
||||
能稳定完成至少一个真实生产样例,并在前端完成统一展示。
|
||||
|
||||
### 12.4 采购方案分级建议
|
||||
|
||||
为了避免采购完成后无法嵌入现有系统,建议把采购方案分成三档判断:
|
||||
|
||||
1. 推荐方案
|
||||
采购“LANDSAR 服务接口版 + 联调实施服务 + 服务化许可”。
|
||||
该方案要求供应商提供 API 或等价服务接口,并配合完成与本系统的任务、状态、日志、结果联通。这是最符合“第三生产核心”目标的方案。
|
||||
2. 可接受方案
|
||||
采购“LANDSAR CLI/SDK 版 + 本地部署支持 + 接口适配支持”。
|
||||
该方案在没有标准服务接口时仍可落地,但后续部署、升级、兼容和运维成本会明显高于推荐方案。
|
||||
3. 不建议方案
|
||||
仅采购“LANDSAR 桌面 GUI 单机版”。
|
||||
该方案无法稳定嵌入本系统,只能形成独立工具,难以支撑统一任务管理、日志审计和结果治理,不建议作为本项目采购目标。
|
||||
|
||||
## 十三、LANDSAR 接入对采购决策的影响
|
||||
|
||||
这一点需要在汇报中重点强调:
|
||||
|
||||
采购 LANDSAR,不是单纯采购一个处理软件,而是在采购本系统的第三个生产核心。
|
||||
|
||||
因此,采购判断应从以下维度展开:
|
||||
|
||||
1. 不只是看算法效果,还要看是否可集成。
|
||||
2. 不只是看桌面端能不能跑,还要看服务端能不能调。
|
||||
3. 不只是看一次能不能出结果,还要看能否纳入长期运维。
|
||||
4. 不只是看供应商交付软件,还要看是否交付接口和联调能力。
|
||||
|
||||
如果采购时忽略这些条件,后续极有可能出现“软件买到了,但无法嵌入现有平台”的情况,导致采购价值被打折。
|
||||
|
||||
## 十四、建议的实施路径
|
||||
|
||||
建议分两步推进:
|
||||
|
||||
### 第一阶段:完成服务嵌入
|
||||
|
||||
目标是让 LANDSAR 先成为“可调度、可监控、可回收”的第三生产核心。
|
||||
|
||||
重点工作:
|
||||
|
||||
1. 完成接口对接。
|
||||
2. 完成任务状态联通。
|
||||
3. 完成日志与结果回流。
|
||||
4. 完成前端选择与展示。
|
||||
|
||||
### 第二阶段:完成深度治理
|
||||
|
||||
目标是把 LANDSAR 结果纳入更完整的统一治理体系。
|
||||
|
||||
重点工作:
|
||||
|
||||
1. 结果版本管理。
|
||||
2. 多引擎结果对比。
|
||||
3. 质量评价与复核闭环。
|
||||
4. 更细粒度的运行审计和运维检查。
|
||||
|
||||
这样推进的好处是先把采购价值尽快落地,再逐步做深,而不是一开始就把目标定得过重。
|
||||
|
||||
## 十五、结论
|
||||
|
||||
综合判断如下:
|
||||
|
||||
1. 当前 InSAR 管理系统已经具备平台化能力,适合承接第三方 D-InSAR 生产核心。
|
||||
2. 系统的多引擎架构已经形成,`LANDSAR` 不是从零开始接,而是在既有框架中补齐适配层。
|
||||
3. 截至目前,`LANDSAR` 在系统中仍是预留状态,尚未真正接入生产执行。
|
||||
4. 因此,采购方案必须把“可编程调用、可集成、可监控、可回收”写成明确要求。
|
||||
5. 最推荐的采购与实施方式,是将 LANDSAR 以外部生产服务的形式嵌入本系统,作为第三个 D-InSAR 生产核心统一调度。
|
||||
|
||||
一句话概括:本项目当前已经具备“接入第三生产核心”的平台基础,下一步采购是否成功,关键不在于是否购买到一个能跑的工具,而在于是否购买到一个能够被本系统稳定接入的服务化能力。
|
||||
Reference in New Issue
Block a user