Chapter 8 开发者指南
本章面向希望将自有水文模型接入 GHDC 平台的研究者或工程师。阅读本章前,建议先了解 GHDC 的整体使用流程(第 5 章)。
8.1 平台架构概述
GHDC 的系统分为两个独立层,通过文件系统进行解耦通信,没有 RPC 或 HTTP 调用:
┌─────────────────────────────┐
│ Web 层(Flask + 前端) │
│ 接受用户提交、发送邮件 │
│ 写任务文件 → 读状态信号 │
└──────────────┬──────────────┘
│ 文件系统(/data/GHDC/tasks-v2/)
┌──────────────▼──────────────┐
│ R 层(GO.R 调度器) │
│ 轮询任务文件 → 调用模型 │
│ 写状态信号 → 写结果 ZIP │
└─────────────────────────────┘
关键原则:Web 层只负责接收用户输入、写入任务文件、轮询状态信号;R 层只负责读取任务文件、执行建模、写回结果。两层之间没有共享内存、没有数据库交互、没有网络调用,完全通过约定好的文件格式通信。
这一设计使得替换或扩展 R 层中的建模逻辑非常简单,无需改动任何 Web 代码。
8.2 任务文件协议
用户点击邮件确认链接后,Web 层在任务队列目录 /data/GHDC/tasks-v2/ 中写入以下文件:
/data/GHDC/tasks-v2/
├── task_{task_id}.txt ← 触发文件(GO.R 扫描此文件)
└── {task_id}/
├── input.json ← 建模参数(JSON 格式)
└── input.zip ← 流域边界 Shapefile ZIP
{task_id} 的格式为 YYYYMMDD_HHMMSS_{email_hash}_{project_name},例如 20260618_211224_6d1bf451_lcg2017。
8.2.1 触发文件(task_{task_id}.txt)
触发文件共 4 行:
20260618_211224_6d1bf451_lcg2017
user@example.com
lcg2017
2026-06-18T21:12:24.000000
依次为:任务 ID、邮箱、项目名称、任务创建时间(ISO 8601)。
GO.R 通过扫描 tasks-v2/task_*.txt 文件的出现来检测新任务。
8.2.2 参数文件(input.json)
{
"task_id": "20260618_211224_6d1bf451_lcg2017",
"email": "user@example.com",
"project_name": "lcg2017",
"parameters": {
"start_year": 2017,
"end_year": 2018,
"dem_source": "aster30",
"forcing": "GLDAS",
"min_cells": 400,
"max_cell_area": 0.1,
"aquifer_depth": 10,
"aquifer_thickness": 10,
"soil_data": "HWSD",
"land_use": "MODIS",
"dataonly": "no"
},
"created_at": "2026-06-18T21:12:24.000000"
}parameters 子对象中各字段的含义:
| 字段 | 类型 | 说明 |
|---|---|---|
start_year |
int | 模拟起始年份 |
end_year |
int | 模拟结束年份 |
dem_source |
str | 高程数据:aster30(ASTER 30m)或 merit90(MERIT 90m) |
forcing |
str | 气象驱动:GLDAS、NLDAS、CMFD |
min_cells |
int | 网格最小单元数 |
max_cell_area |
float | 网格单元最大面积(km²) |
aquifer_depth |
int | 含水层厚度(m),与 aquifer_thickness 互为别名 |
aquifer_thickness |
int | 同上 |
soil_data |
str | 土壤数据:HWSD |
land_use |
str | 土地利用:MODIS |
dataonly |
str | "yes" 表示仅准备数据不建模(超大流域时由系统自动设置) |
注意:
locale(语言)字段不会出现在input.json中,仅在 Web 数据库和邮件发送中使用,R 后端无需感知。
8.3 R 后端结构
图 ?? 展示了 Web 层与 R 层之间通过文件系统交互的架构:
R 后端位于仓库 R_Backend/ 目录,核心文件如下:
| 文件 | 作用 |
|---|---|
GO.R |
主调度入口:持续轮询任务队列,逐一处理新任务 |
getReady.R |
初始化:加载依赖包、读取服务配置 |
Run_GHDC.sh |
生产环境启动脚本(通过 systemd 管理) |
functions/Configure.R |
任务初始化:读取 input.json、计算建模参数 |
functions/task_intake_v2.R |
v2 任务解析:flatten.v2.task.json() 展平 JSON |
script/service_ghdc.cfg.txt |
服务配置:数据目录路径等 |
8.3.1 GO.R 调度机制
GO.R 是一个轮询循环,逻辑如下(简化):
repeat {
tasks <- list.files(task_dir, pattern = "^task_.*\\.txt$")
for (task_file in tasks) {
task_id <- read_task_id(task_file)
if (!is_processed(task_id)) {
Configure(task_id) # 初始化任务
ModelDeploy(task_id) # 执行建模
}
}
Sys.sleep(check_interval)
}检测到 task_*.txt 后,GO.R 调用 Configure() 读取并展平 input.json,然后调用 ModelDeploy() 执行完整的建模流程。
8.3.2 input.json 展平与字段映射
functions/task_intake_v2.R 中的 flatten.v2.task.json() 将 input.json 的嵌套结构展平,并对部分字段进行重命名,以兼容现有 R 函数:
input.json 字段 |
R 内部变量名 | 说明 |
|---|---|---|
min_cells |
minimum_cell_number |
最小网格单元数 |
max_cell_area |
maxim_cell_area |
最大单元面积(km²) |
forcing |
meteorological_data |
气象驱动名称 |
soil_data |
soil(小写) |
土壤数据集名称 |
land_use |
landuse;MODIS → mglc |
土地利用数据集 |
task_id |
keyid |
任务唯一标识 |
| (默认) | model = "shud" |
默认建模模式 |
dataonly |
dataonly |
仅数据模式标志 |
8.4 状态信号文件
R 后端通过在任务子目录下写入特定文件来向 Web 层报告处理状态,Web 层每 5 秒扫描一次这些文件:
| 信号文件 | 路径 | 含义 |
|---|---|---|
task_log.txt |
{task_id}/task_log.txt |
任务已开始处理(Web 显示”处理中”) |
completed.txt |
{task_id}/completed.txt |
任务成功完成 |
error.log |
{task_id}/error.log |
任务失败,文件内含错误信息 |
判断逻辑(优先级从高到低):
completed.txt存在 → 已完成error.log存在 → 失败task_log.txt存在 → 处理中- 均不存在 → 排队等待
结果 ZIP 文件写入 /data/GHDC/downloads/{task_id}_result.zip,Web 层在检测到 completed.txt 后从此路径提供下载链接。
8.5 接入新模型的步骤
如果你希望将模型 X 集成到 GHDC 平台,以下是完整的接入流程。
8.5.1 第一步:实现 ModelDeploy() 函数
ModelDeploy() 是建模执行的唯一入口,接收由 Configure() 构建的 task_config 列表:
ModelDeploy <- function(task_config) {
task_id <- task_config$keyid
task_dir <- file.path(task_config$target_dir, task_id)
log_file <- file.path(task_dir, "task_log.txt")
done_file <- file.path(task_dir, "completed.txt")
error_file <- file.path(task_dir, "error.log")
# 1. 写入 task_log.txt,通知 Web 层任务已开始
writeLines(c(task_id, as.character(Sys.time())), log_file)
tryCatch({
# 2. 读取建模参数
start_year <- task_config$start_year
end_year <- task_config$end_year
dem_source <- task_config$dem_source # "aster30" 或 "merit90"
forcing <- task_config$meteorological_data
min_cells <- task_config$minimum_cell_number
max_cell_area <- task_config$maxim_cell_area
aquifer_depth <- task_config$aquifer_depth
boundary_zip <- file.path(task_dir, "input.zip") # 边界 Shapefile ZIP
# 3. 调用模型 X 的建模逻辑
run_model_x(boundary_zip, start_year, end_year, forcing, ...)
# 4. 打包结果为 ZIP,写入下载目录
result_zip <- file.path(task_config$download_dir,
paste0(task_id, "_result.zip"))
zip(result_zip, files = <你的结果目录>)
# 5. 写入 completed.txt,通知 Web 层任务成功
writeLines(c(task_id, as.character(Sys.time())), done_file)
}, error = function(e) {
# 失败时写入 error.log
writeLines(c(task_id, conditionMessage(e)), error_file)
})
}8.5.2 第二步:注册新的数据选项(可选)
如果你的模型需要新的气象驱动或数据集,需要在 Web 端的配置文件 dev/config/data_sources.yaml 中注册,并将 enabled 设为 true:
forcing:
- value: "MyForcing"
label: "My Forcing Dataset"
year_min: 1980
year_max: 2023
coverage: "global"
enabled: true修改后需要重新部署 Web 端(sudo ./dev/scripts/remot-deploy-v2.sh)使更改生效。
8.5.3 第三步:配置路径
R 后端的路径配置在 R_Backend/script/service_ghdc.cfg.txt 中定义:
TARGET.DIR=/data/GHDC/tasks-v2
DIR.DOWNLOAD_DIR=/data/GHDC/downloads
DIR.PROCESSING=/data/GHDC/rProcessing
DIR.RLOG=/data/GHDC/rProcessing/rlog
| R 配置键 | 说明 |
|---|---|
TARGET.DIR |
任务队列目录(Web 层写入,R 层读取) |
DIR.DOWNLOAD_DIR |
结果 ZIP 输出目录(R 层写入,Web 层读取) |
DIR.PROCESSING |
建模工作区(中间文件,处理完成后可清理) |
确保这些路径与 Web 端 dev/config/prod.yaml 中的 paths 配置保持一致:
| R 配置键 | Flask prod.yaml 键 |
|---|---|
TARGET.DIR |
paths.r_task_dir |
DIR.DOWNLOAD_DIR |
paths.download_dir |
8.6 本地开发与测试
8.6.2 构造测试任务
使用仓库内的示例边界文件(Misc/ExampleData/ 目录下的任一 .zip)手动构造一个测试任务:
TASK_ID="20260101_120000_test0001_mytest"
QUEUE_DIR="/your/local/tasks-v2"
# 创建任务子目录并复制边界文件
mkdir -p "${QUEUE_DIR}/${TASK_ID}"
cp Misc/ExampleData/example_boundary.zip "${QUEUE_DIR}/${TASK_ID}/input.zip"
# 写入 input.json
cat > "${QUEUE_DIR}/${TASK_ID}/input.json" << 'EOF'
{
"task_id": "20260101_120000_test0001_mytest",
"email": "test@example.com",
"project_name": "mytest",
"parameters": {
"start_year": 2000,
"end_year": 2024,
"dem_source": "aster30",
"forcing": "GLDAS",
"min_cells": 100,
"max_cell_area": 1.0,
"aquifer_depth": 10,
"aquifer_thickness": 10,
"soil_data": "HWSD",
"land_use": "MODIS",
"dataonly": "no"
},
"created_at": "2026-01-01T12:00:00.000000"
}
EOF
# 写入触发文件
echo -e "${TASK_ID}\ntest@example.com\nmytest\n2026-01-01T12:00:00" \
> "${QUEUE_DIR}/task_${TASK_ID}.txt"构造完成后,启动 GO.R 即可触发处理:
8.7 相关资源
| 资源 | 链接 |
|---|---|
| SHUD 模型 | https://github.com/SHUD-System/SHUD |
| rSHUD 工具包 | https://github.com/SHUD-System/rSHUD |
| AutoSHUD 自动化脚本 | https://github.com/SHUD-System/AutoSHUD |
| GHDC 仓库 | https://github.com/SHUD-System/GHDC |
如有接入需求或技术问题,请联系:
- 平台管理(接入申请):shud@nieer.ac.cn
- 技术开发(代码/架构):shulele@lzb.ac.cn