将 TLUSTY/SYNSPEC 拆为各自独立的 enabled/policy/strategies 阶段,
以策略链自动弹栈取代单级 seed_step 布尔回退;定向修复 2026-08-02
僵尸任务涡旋事故;新增节点并发配额热调;前端详情页从 1412 行巨型
视图拆为薄控制器 + detail 子模块,并补齐工具层与单测。
引擎与调度(task_engine_decoupling_design.md)
- models.rs: 新增 StagePolicy / EngineStageConfig / TaskSpec 阶段字段、
normalize_compat() 校正旧版在途消息策略链、failed_stage 归因
- scheduler.rs: resolve_dispatchable_chain 派发门控、
trigger_strategy_fallback 按 failed_stage 精确弹栈;启动期
force_recompute/skip_converged(默认)/skip_failed 三策略
- db.rs: tasks 表 +7 列持久化阶段配置;终态守卫
(mark_grid_point_running 仅 pending/queued→running;
record_task_report 拒绝迟到失败翻黑 converged);策略弹栈快照
僵尸涡旋修复(runbook-20260802-zombie-vortex-fix.md)
- 全链路跨库活性交叉校验:派发/claim/孤儿回收/回退统一查 MQ 队列活性,
活则放行、死则清僵尸,结构性消除"每点重复派发"
- stop/重启卫生:清队列同步 delete_tasks_by_ids,杜绝遗留 pending 行
- report_task: 幂等吸收 + 409 区分迟到冗余结果,仅 state_changed 时回退
- MQ: NULL workflow_name 回填 __legacy__、requeue 后迟到上报被 403 竞态修复
动态 CPU 配额(dynamic_cpu_slots_design.md)
- admin.rs: POST /admin/nodes/:id/quota(Option<Option<i32>> 区分
缺字段/显式 null);nodes 表 +admin_max_slots
- worker.rs: effective_max_slots = min(admin, physical),心跳下发原子生效
科学产物保全(tlusty_result_artifacts.md)
- runner.rs: SYNSPEC 启动前快照 fort.12/fort.14 → .bfac/.emflux 防覆盖
- 半失败点(大气收敛+光谱失败)改判 Failed 并写入 note;仅 SYNSPEC
场景不再恒判失败;撤销归档 LRU 200 上限改为永久保留
- executor.rs: 透传 synspec_params 数值参数(此前固定 None)
前端(dashboard/)
- workflowDetail.js 1412→328 行,拆出 views/detail/{ctx,overview,
pointsTable,parSets,pointPanel}.js,AbortController 治理监听/请求生命周期
- 删除 wfActions.js,新增 wfEnginePanel.js(双阶段三维配置编辑面板)
- 新增 utils/{errors,format,icons,polling,yamlStage}.js 纯函数模块
- 路由级动态 import 代码分割;节点配额三点菜单 + Modal 管理
- 首次引入 node:test 单测(format/polling/yamlStage/psCache,644 行)
- 系统性补齐 a11y:skip-link、ARIA、Tab 键盘漫游、toast 关闭、退出动画
文档与工具
- 新增 6 篇设计/调研:引擎解耦、动态配额、涡旋 runbook、
光谱正确性分析、收敛判断、产物归档
- PIPELINE/design/api/database 等协同重写为分布式 C/S 架构口径
- scripts/fetch_results.sh 跨节点产物备份;import_results 按 cno 升序导入
- workflows/sdB_cno.yaml: 新增 tlusty/synspec_stage 配置块,修正 wstart 笔误
1308 lines
47 KiB
Markdown
1308 lines
47 KiB
Markdown
# DCTS (Distributed Computing TLUSTY/SYNSPEC) API 文档
|
||
|
||
本文档由源代码自动提取并整理,详细说明了 **DCTS 分布式恒星大气网格计算系统** 服务端 (`dcts_server`) 提供的所有 RESTful API 接口规范、数据结构定义、鉴权机制、错误码及 `curl` 调用示例。
|
||
|
||
---
|
||
|
||
## 目录 (Table of Contents)
|
||
|
||
1. [通用说明与鉴权机制](#1-通用说明与鉴权机制)
|
||
2. [数据结构与类型定义 (Rust & TypeScript Schema)](#2-数据结构与类型定义-rust--typescript-schema)
|
||
3. [计算节点管理 API (Node Management)](#3-计算节点管理-api-node-management)
|
||
4. [任务调度与结果上报 API (Task Processing)](#4-任务调度与结果上报-api-task-processing)
|
||
5. [种子文件管理 API (Seed Management)](#5-种子文件管理-api-seed-management)
|
||
6. [静态资源与数据下载 API (Data Assets)](#6-静态资源与数据下载-api-data-assets)
|
||
7. [系统状态监控 API (System Status)](#7-系统状态监控-api-system-status)
|
||
8. [工作流管理 API (Workflow CRUD & Execution)](#8-工作流管理-api-workflow-crud--execution)
|
||
9. [错误处理与状态码汇总](#9-错误处理与状态码汇总)
|
||
|
||
---
|
||
|
||
## 1. 通用说明与鉴权机制
|
||
|
||
### 1.1 服务端信息
|
||
- **默认服务地址**: `http://127.0.0.1:8090` (端口可通过 `--port` / `DCTS_PORT` 环境变量配置)
|
||
- **传输协议**: HTTP / HTTPS
|
||
- **默认请求/响应格式**: `application/json` (部分文件下载接口为 `application/octet-stream`,任务上报为 `multipart/form-data`)
|
||
|
||
### 1.2 鉴权机制与权限控制矩阵 (`auth_middleware` & RBAC)
|
||
|
||
服务端基于角色访问控制 (RBAC) 划分三种请求鉴权级别:
|
||
|
||
1. **Admin 角色**:具有系统管理权限(工作流 CRUD/起停、节点凭据审批/重发/停用/启用、系统恢复)。在 Request Header 中需携带:
|
||
```http
|
||
Authorization: Bearer <DCTS_ADMIN_TOKEN>
|
||
# 或
|
||
x-api-key: <DCTS_ADMIN_TOKEN>
|
||
```
|
||
2. **Node 角色**:仅限 Worker 节点运行态调用(心跳/抢占任务/汇报/下载数据)。携带管理员审批颁发的专属节点 Token:
|
||
```http
|
||
Authorization: Bearer <NODE_SPECIFIC_TOKEN>
|
||
```
|
||
3. **Public 角色**:无需鉴权直接访问(如 `/login` 管理登录、`/node/register` 提交申请、`/node/check_status` 轮询审批、`/healthz` 健康检查)。
|
||
|
||
> [!TIP]
|
||
> **防侧信道保护**:底层调用 Constant-Time 等时比较机制 (`Sha256` 摘要匹配),彻底规避侧信道攻击(Side-Channel Attack)。
|
||
|
||
### 1.3 限流机制与错误模型 (Rate Limiting & Unified Error)
|
||
|
||
- **API 限流 (Rate Limiting)**:服务端对敏感接口(如 `/login`、`/node/register`)应用了**滑动窗口**(sliding window)请求限流器(`rate_limit.rs`)。超出速率上限时返回 `429 Too Many Requests`。
|
||
- **统一错误格式 (AppError)**:所有 RESTful API 的错误响应均格式化为标准 JSON:
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "错误原因详细说明",
|
||
"data": null
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 2. 数据结构与类型定义 (Rust & TypeScript Schema)
|
||
|
||
### 2.1 6维网格点参数 (`GridPointParams`)
|
||
定义恒星大气模型的 6 维大气参数:温度 $T_{\text{eff}}$、重力加速度 $\log g$、以及元素丰度 $\log(N_{\text{He}}/N_{\text{H}})$, $\log(N_{\text{C}}/N_{\text{H}})$, $\log(N_{\text{N}}/N_{\text{H}})$, $\log(N_{\text{O}}/N_{\text{H}})$。
|
||
|
||
- **Rust 定义** ([models.rs](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/common/src/models.rs)):
|
||
```rust
|
||
// 每个轴值携带数值与 YAML 源书写文本,使命名严格忠于源精度。
|
||
#[derive(Debug, Clone, PartialEq)]
|
||
pub struct GridAxisValue { /* 内部:value: f64 + text: 源文本 */ }
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||
pub struct GridPointParams {
|
||
pub teff: GridAxisValue, // 有效温度 (K), e.g. 35000.0
|
||
pub logg: GridAxisValue, // 表面重力加速度对数 (cgs), e.g. 5.5
|
||
pub loghe: GridAxisValue, // 氦丰度对数, e.g. -1.0
|
||
pub logc: GridAxisValue, // 碳丰度对数, e.g. -2.0
|
||
pub logn: GridAxisValue, // 氮丰度对数, e.g. -2.0
|
||
pub logo: GridAxisValue, // 氧丰度对数, e.g. -2.0
|
||
}
|
||
```
|
||
|
||
> **serde 行为**:`GridAxisValue` 序列化为纯数值(`f64`),下游 JSON 消费者无感;
|
||
> 反序列化时优先捕获 YAML/JSON 标量原文。**命名精度**:`model_name()` 直接拼接各轴
|
||
> 源书写文本——配置里多少位小数就多少位(`logg: 5.0` → `g5.0`,`teff: 20000` → `t20000`),
|
||
> 与旧版 Python `gen_input5.model_name` 逐字符一致,保证历史数据可迁移。详见
|
||
> [`GridConfig::from_yaml_str`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/common/src/config.rs)(YAML 源文本捕获路径)。
|
||
|
||
- **TypeScript 类型声明**:
|
||
```typescript
|
||
export interface GridPointParams {
|
||
teff: number;
|
||
logg: number;
|
||
loghe: number;
|
||
logc: number;
|
||
logn: number;
|
||
logo: number;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2.2 任务规格 (`TaskSpec`)
|
||
服务端派发给 Worker 节点的单个计算任务定义。
|
||
|
||
- **Rust 定义** ([models.rs](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/common/src/models.rs#L389-L425)):
|
||
```rust
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct TaskSpec {
|
||
pub task_id: Uuid,
|
||
pub point_name: String,
|
||
pub params: GridPointParams,
|
||
pub task_type: TaskType, // ColdRun | SeedStep(兼容字段 = strategies[0])
|
||
pub seed_point_name: Option<String>, // 步进种子点名称(如适用)
|
||
pub timeout_sec: u64,
|
||
pub workflow_name: Option<String>, // 多工作流分区键
|
||
pub wave: i32, // 难度波次
|
||
pub tlusty_config: EngineStageConfig, // TLUSTY 阶段配置(enabled/policy/strategies)
|
||
pub synspec_config: EngineStageConfig, // SYNSPEC 阶段配置
|
||
pub synspec_params: Option<serde_json::Value>, // SYNSPEC 数值参数(波长范围等)
|
||
pub atmosphere_ref: Option<String>, // 显式大气来源点(仅 SYNSPEC-only 场景)
|
||
}
|
||
```
|
||
|
||
`EngineStageConfig`(阶段独立配置,见 `task_engine_decoupling_design.md §3`):
|
||
```rust
|
||
pub struct EngineStageConfig {
|
||
pub enabled: bool,
|
||
pub policy: String, // skip_converged | force_recompute | skip_failed
|
||
pub strategies: Vec<String>, // 策略链:["cold_run","seed_step"],失败回退弹首项
|
||
}
|
||
```
|
||
|
||
- **TypeScript 类型声明**:
|
||
```typescript
|
||
export type TaskType = 'cold_run' | 'seed_step';
|
||
|
||
export interface EngineStageConfig {
|
||
enabled: boolean;
|
||
policy: string;
|
||
strategies: string[];
|
||
}
|
||
|
||
export interface TaskSpec {
|
||
task_id: string;
|
||
point_name: string;
|
||
params: GridPointParams;
|
||
task_type: TaskType;
|
||
seed_point_name?: string | null;
|
||
timeout_sec: number;
|
||
workflow_name?: string | null;
|
||
wave: number;
|
||
tlusty_config: EngineStageConfig;
|
||
synspec_config: EngineStageConfig;
|
||
synspec_params?: Record<string, unknown> | null;
|
||
atmosphere_ref?: string | null;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2.3 任务上报报告 (`TaskReport`)
|
||
Worker 节点向服务端上报的任务计算结果。
|
||
|
||
- **Rust 定义** ([models.rs](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/common/src/models.rs#L496-L513)):
|
||
```rust
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct TaskReport {
|
||
pub task_id: Uuid,
|
||
pub point_name: String,
|
||
#[serde(default)]
|
||
pub params: Option<GridPointParams>,
|
||
pub node_id: String,
|
||
pub status: TaskStatus, // Pending | Running | Completed | Failed | Timeout
|
||
pub converged: bool,
|
||
pub max_relc: Option<f64>,
|
||
pub atmosphere_has_nan: bool,
|
||
pub elapsed_sec: f64,
|
||
pub error_message: Option<String>,
|
||
pub summary_json: String,
|
||
pub failed_stage: Option<String>, // "tlusty" | "synspec":失败阶段归因,决定弹哪条策略链
|
||
}
|
||
|
||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||
#[serde(rename_all = "snake_case")]
|
||
pub enum TaskStatus {
|
||
Pending,
|
||
Running,
|
||
Completed,
|
||
Failed,
|
||
Timeout,
|
||
}
|
||
```
|
||
|
||
- **TypeScript 类型声明**:
|
||
```typescript
|
||
export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed' | 'timeout';
|
||
|
||
export interface TaskReport {
|
||
task_id: string;
|
||
point_name: string;
|
||
params?: GridPointParams;
|
||
node_id: string;
|
||
status: TaskStatus;
|
||
converged: boolean;
|
||
max_relc?: number | null;
|
||
atmosphere_has_nan: boolean;
|
||
elapsed_sec: number;
|
||
error_message?: string | null;
|
||
summary_json: string;
|
||
failed_stage?: 'tlusty' | 'synspec' | null;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 2.4 节点信息与请求模型 (`NodeRegisterRequest` / `NodeHeartbeatRequest`)
|
||
|
||
- **Rust 定义** ([models.rs](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/common/src/models.rs#L142-L170)):
|
||
```rust
|
||
pub struct NodeRegisterRequest {
|
||
pub node_id: String,
|
||
pub max_slots: i32,
|
||
}
|
||
|
||
pub struct NodeHeartbeatRequest {
|
||
pub node_id: String,
|
||
pub active_slots: i32,
|
||
pub cpu_usage: f32,
|
||
pub memory_usage: f32,
|
||
}
|
||
|
||
pub struct NodeInfo {
|
||
pub node_id: String,
|
||
pub max_slots: i32,
|
||
pub active_slots: i32,
|
||
pub status: String,
|
||
pub cpu_usage: f32,
|
||
pub memory_usage: f32,
|
||
pub last_heartbeat: DateTime<Utc>,
|
||
}
|
||
```
|
||
|
||
- **TypeScript 类型声明**:
|
||
```typescript
|
||
export interface NodeRegisterRequest {
|
||
node_id: string;
|
||
max_slots: number;
|
||
}
|
||
|
||
export interface NodeHeartbeatRequest {
|
||
node_id: string;
|
||
active_slots: number;
|
||
cpu_usage: number;
|
||
memory_usage: number;
|
||
}
|
||
|
||
export interface NodeInfo {
|
||
node_id: string;
|
||
max_slots: number;
|
||
active_slots: number;
|
||
status: 'online' | 'offline' | 'pending_approval' | 'disabled' | 'rejected';
|
||
cpu_usage: number;
|
||
memory_usage: number;
|
||
last_heartbeat: string;
|
||
}
|
||
```
|
||
> **节点 `status` 取值**: `'online'`(在线分发中)、`'offline'`(心跳超时离线)、`'pending_approval'`(待管理员审批)、`'disabled'`(管理员手动停用——在线但不分发任务,详见 [`/disable`](#6-停用节点-post-apiadminnodesnode_iddisable))。
|
||
|
||
---
|
||
|
||
### 2.5 工作流响应模型与请求体 (`CreateWorkflowRequest` / `ApiResponse<T>`)
|
||
|
||
- **Rust 定义** ([workflow.rs](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L12-L24)):
|
||
```rust
|
||
pub struct CreateWorkflowRequest {
|
||
pub name: String,
|
||
pub description: Option<String>,
|
||
pub config_yaml: String,
|
||
}
|
||
|
||
pub struct ApiResponse<T> {
|
||
pub success: bool,
|
||
pub message: String,
|
||
pub data: Option<T>,
|
||
}
|
||
```
|
||
|
||
- **TypeScript 类型声明**:
|
||
```typescript
|
||
export interface CreateWorkflowRequest {
|
||
name: string;
|
||
description?: string | null;
|
||
config_yaml: string;
|
||
}
|
||
|
||
export interface ApiResponse<T = unknown> {
|
||
success: boolean;
|
||
message: string;
|
||
data?: T | null;
|
||
}
|
||
|
||
export interface WorkflowSummary {
|
||
name: string;
|
||
description?: string | null;
|
||
status: 'idle' | 'initializing' | 'running' | 'paused' | 'completed';
|
||
created_at: string;
|
||
updated_at: string;
|
||
stats?: WorkflowListStats | null; // 内联网格点聚合计数(未启动为 null,见 §8.11)
|
||
}
|
||
|
||
export interface WorkflowListStats {
|
||
total: number;
|
||
converged: number;
|
||
failed: number;
|
||
running: number;
|
||
cold_run_converged: number;
|
||
seed_step_converged: number;
|
||
}
|
||
|
||
export interface WorkflowItem {
|
||
name: string;
|
||
description?: string | null;
|
||
config_yaml: string;
|
||
status: 'idle' | 'running' | 'paused' | 'completed';
|
||
created_at: string;
|
||
updated_at: string;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 3. 计算节点管理 API (Node Management)
|
||
|
||
处理 Worker 节点的注册登录与定期心跳保活。
|
||
|
||
### 3.1 注册计算节点 (`POST /api/node/register`)
|
||
|
||
- **处理函数**: [`register_node`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/node.rs#L6-L14)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn register_node(
|
||
State(state): State<AppState>,
|
||
auth_node: Option<Extension<AuthenticatedNode>>,
|
||
Json(req): Json<NodeRegisterRequest>
|
||
) -> impl IntoResponse
|
||
```
|
||
- **鉴权**: 否(免凭据申请,提交后进入 `pending_approval` 待管理员审批)
|
||
- **请求 Header**: `Content-Type: application/json`
|
||
- **请求 Body**:
|
||
```json
|
||
{
|
||
"node_id": "node-worker-01",
|
||
"max_slots": 8
|
||
}
|
||
```
|
||
- **响应 Schema**:
|
||
- `200 OK` (新节点申请提交成功,待审批):
|
||
```json
|
||
{
|
||
"status": "pending_approval",
|
||
"message": "节点注册申请已成功提交!请在管理 Dashboard 控制台上点击【同意接入】授权该节点",
|
||
"node_token": null,
|
||
"registration_secret": "<一次性凭据>"
|
||
}
|
||
```
|
||
> `registration_secret`:节点注册时下发的一次性凭据,节点须在后续
|
||
> `/api/node/check_status` 时回传,才能取走待发 token(防止仅知 node_id 的攻击者抢先冒领)。
|
||
- `200 OK` (已授权节点带专属 token 刷新配置):
|
||
```json
|
||
{
|
||
"status": "approved",
|
||
"message": "节点配置更新成功",
|
||
"node_token": null
|
||
}
|
||
```
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X POST http://localhost:8090/api/node/register \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"node_id": "node-worker-01",
|
||
"max_slots": 8
|
||
}'
|
||
```
|
||
|
||
---
|
||
|
||
### 3.2 节点心跳保活 (`POST /api/node/heartbeat`)
|
||
|
||
- **处理函数**: [`heartbeat_node`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/node.rs#L167-L190)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn heartbeat_node(
|
||
State(state): State<AppState>,
|
||
Extension(auth_node): Extension<AuthenticatedNode>,
|
||
Json(req): Json<NodeHeartbeatRequest>,
|
||
) -> impl IntoResponse
|
||
```
|
||
- **鉴权**: 是(Node 角色必填;`req.node_id` 必须与鉴权身份一致,否则 403)
|
||
- **请求 Header**: `Content-Type: application/json`
|
||
- **请求 Body**:
|
||
```json
|
||
{
|
||
"node_id": "node-worker-01",
|
||
"active_slots": 2,
|
||
"cpu_usage": 45.2,
|
||
"memory_usage": 30.8
|
||
}
|
||
```
|
||
- **响应 Schema**:
|
||
- `200 OK` (成功):
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"admin_max_slots": 4
|
||
}
|
||
```
|
||
> `admin_max_slots`:管理员强制的并发槽位配额(`null` = 无限制,沿用物理 `max_slots`)。
|
||
> 节点据此动态调整本地 `effective_max_slots`(见 `dynamic_cpu_slots_design.md`)。
|
||
- `200 OK` (失败):
|
||
```json
|
||
{
|
||
"status": "error",
|
||
"message": "节点未找到或心跳更新失败"
|
||
}
|
||
```
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X POST http://localhost:8090/api/node/heartbeat \
|
||
-H "Content-Type: application/json" \
|
||
-H "Authorization: Bearer secret_token" \
|
||
-d '{
|
||
"node_id": "node-worker-01",
|
||
"active_slots": 2,
|
||
"cpu_usage": 45.2,
|
||
"memory_usage": 30.8
|
||
}'
|
||
```
|
||
|
||
---
|
||
|
||
### 3.3 节点凭据与审批管理 API (Admin 角色)
|
||
|
||
#### 1. 列出全部节点及凭据状态 (`GET /api/admin/nodes`)
|
||
- **权限**: Admin 角色
|
||
- **响应 (`200 OK`)**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "成功获取节点列表",
|
||
"data": [
|
||
{
|
||
"node_id": "node-worker-01",
|
||
"status": "online",
|
||
"token_status": "active",
|
||
"token_issued_at": "2026-07-28T12:00:00Z",
|
||
"admin_max_slots": 4
|
||
}
|
||
]
|
||
}
|
||
```
|
||
> `token_status` 取值:`active`(有效)/ `none`(无凭据记录)。token 失效靠重发覆盖 hash 实现,不存在「已吊销」中间态。
|
||
> `admin_max_slots`:管理员强制并发槽位配额(`null` = 无限制),Dashboard 据此渲染节点配额状态。
|
||
|
||
#### 2. 同意节点接入申请 (`POST /api/admin/nodes/:node_id/approve`)
|
||
- **权限**: Admin 角色
|
||
- **说明**: 批准处于待审批状态的 Node,并生成该节点的专属 Token。
|
||
|
||
#### 3. 拒绝节点接入申请 (`POST /api/admin/nodes/:node_id/reject`)
|
||
- **权限**: Admin 角色
|
||
- **说明**: 拒绝处于待审批状态的 Node 接入。
|
||
|
||
#### 4. 重新颁发节点专属 Token (`POST /api/admin/nodes/:node_id/reissue`)
|
||
- **权限**: Admin 角色
|
||
- **说明**: 作废旧 Token(hash 被覆盖,立即失效)并重新生成新 Token 文本返回。新明文同时暂存到服务端,节点可通过 `/node/check_status` 自动拉取(限 1 天内有效),或由管理员手动同步到节点本地 `.node_token`。
|
||
|
||
#### 5. 停用节点 (`POST /api/admin/nodes/:node_id/disable`)
|
||
- **权限**: Admin 角色
|
||
- **说明**: 手动停用一个处于 `online`/`offline` 状态的节点。停用后节点**保持在线心跳**(Dashboard 仍可见其存活),但 `claim` 不再向其分发任务,Worker 收到 `{"status":"disabled"}` 后会拉长轮询、空闲待命,可随时调用 `enable` 恢复。
|
||
- **状态语义**: 节点 `status` 切为 `disabled`。与「重发 Token」(凭据失效、Worker 强制退出重注册)不同——停用仅是运维意图,不退出 Worker 进程、不动凭据。
|
||
- **响应**:
|
||
- `200 OK`: `{"success": true, "message": "节点 '...' 已停用,不再分发任务"}`
|
||
- `409 Conflict`: 节点当前状态不支持停用(仅 `online`/`offline` 可停用,`pending_approval`/`disabled` 返回此码)。
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X POST http://localhost:8090/api/admin/nodes/node-worker-01/disable \
|
||
-H "Authorization: Bearer <ADMIN_TOKEN>"
|
||
```
|
||
|
||
#### 6. 重新启用节点 (`POST /api/admin/nodes/:node_id/enable`)
|
||
- **权限**: Admin 角色
|
||
- **说明**: 重新启用被手动停用(`disabled`)的节点。状态切为 `offline`,靠节点下一次心跳自然翻为 `online` 后即恢复分发任务——既能自愈,又不会对真实离线的节点虚报在线。
|
||
- **响应**:
|
||
- `200 OK`: `{"success": true, "message": "节点 '...' 已重新启用,将在下一次心跳后恢复分发任务"}`
|
||
- `409 Conflict`: 节点当前状态不支持启用(仅 `disabled` 可启用)。
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X POST http://localhost:8090/api/admin/nodes/node-worker-01/enable \
|
||
-H "Authorization: Bearer <ADMIN_TOKEN>"
|
||
```
|
||
|
||
#### 7. 调整节点并发配额 (`POST /api/admin/nodes/:node_id/quota`)
|
||
- **权限**: Admin 角色
|
||
- **说明**: 动态调整节点并发槽位配额上限(不重启 Worker 进程,经下一次心跳响应下发生效)。传 `null` 解除限制,恢复物理 `max_slots`。详见 `dynamic_cpu_slots_design.md`。
|
||
- **请求 Body**:
|
||
```json
|
||
{ "admin_max_slots": 4 }
|
||
```
|
||
- **响应**:
|
||
- `200 OK`: `{"success": true, "message": "节点配额已更新"}`
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X POST http://localhost:8090/api/admin/nodes/node-worker-01/quota \
|
||
-H "Authorization: Bearer <ADMIN_TOKEN>" -H "Content-Type: application/json" \
|
||
-d '{"admin_max_slots": 4}'
|
||
```
|
||
|
||
---
|
||
|
||
## 4. 任务调度与结果上报 API (Task Processing)
|
||
|
||
支持 Worker 节点抢占式领用任务与计算结果(含种子 `.7` 文件)上传。
|
||
|
||
### 4.1 领用计算任务 (`POST /api/task/claim`)
|
||
|
||
- **处理函数**: [`claim_task`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/task.rs#L15-L25)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn claim_task(
|
||
State(state): State<AppState>,
|
||
Extension(auth_node): Extension<AuthenticatedNode>,
|
||
) -> impl IntoResponse
|
||
```
|
||
- **鉴权**: 是(Node 角色必填;服务端据注入身份校验节点是否被停用)
|
||
- **请求 Body**: 无
|
||
- **响应 Schema**:
|
||
- `200 OK` (有可计算任务):
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"task": {
|
||
"task_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"point_name": "t35000_g5.5_he-1_c-2_n-2_o-2",
|
||
"params": {
|
||
"teff": 35000.0,
|
||
"logg": 5.5,
|
||
"loghe": -1.0,
|
||
"logc": -2.0,
|
||
"logn": -2.0,
|
||
"logo": -2.0
|
||
},
|
||
"task_type": "cold_run",
|
||
"seed_point_name": null,
|
||
"timeout_sec": 7200
|
||
}
|
||
}
|
||
```
|
||
- `200 OK` (当前队列为空):
|
||
```json
|
||
{
|
||
"status": "empty",
|
||
"task": null
|
||
}
|
||
```
|
||
- `200 OK` (节点被管理员手动停用,不再分发任务):
|
||
```json
|
||
{
|
||
"status": "disabled",
|
||
"task": null
|
||
}
|
||
```
|
||
> Worker 收到此响应后保持存活、拉长轮询(每 60s)空闲待命,**不会**退出进程(区别于 401/403 的 Token 失效语义)。管理员调用 `/enable` 后下一次轮询即恢复领用。
|
||
- `500 Internal Server Error`:
|
||
```json
|
||
{
|
||
"status": "error",
|
||
"message": "领用任务失败: <error_details>"
|
||
}
|
||
```
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X POST http://localhost:8090/api/task/claim \
|
||
-H "Authorization: Bearer secret_token"
|
||
```
|
||
|
||
---
|
||
|
||
### 4.2 上报任务结果与种子文件 (`POST /api/task/report`)
|
||
|
||
- **处理函数**: [`report_task`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/task.rs#L27-L102)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn report_task(
|
||
State(state): State<AppState>,
|
||
Extension(auth_node): Extension<AuthenticatedNode>,
|
||
mut multipart: Multipart,
|
||
) -> impl IntoResponse
|
||
```
|
||
- **鉴权**: 是(Node 角色必填;服务端据注入身份做任务归属校验 `verify_task_claim`,防跨节点伪造)
|
||
- **请求格式**: `multipart/form-data`
|
||
- Part `report`: JSON 字符串 (映射为 `TaskReport`)
|
||
- Part `seed_file` *(可选)*: 二进制数据 (收敛网格点的 `.7` 大气结构种子文件)
|
||
- **响应 Schema**:
|
||
- `200 OK` (成功 / 幂等重放):
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"message": "上报成功"
|
||
}
|
||
```
|
||
> **幂等重放**:已结算任务(终态守卫已吸收)的重复/迟到上报仍返回 200,并补写种子,不重复结算。
|
||
- `400 Bad Request` (缺少 `report` 字段):
|
||
```json
|
||
{
|
||
"status": "error",
|
||
"message": "请求中缺少 report 字段"
|
||
}
|
||
```
|
||
- `400 Bad Request` (参数格式错误):
|
||
```json
|
||
{
|
||
"status": "error",
|
||
"message": "无法解析 params 或 summary_json"
|
||
}
|
||
```
|
||
- `409 Conflict` (任务归属校验失败): 任务被其它节点重新领用、或队列中已无该 task_id(已被结算清理)。此时返回冲突,不写库。
|
||
- **说明**:
|
||
- 当 `converged == true` 且 `atmosphere_has_nan == false` 且包含 `seed_file` 时,服务端会将种子原子写入 `seeds_dir/<point_name>/<point_name>.7` 并记入 `seeds` 表(源精度 `point_name`)。
|
||
- 失败上报(未收敛 / 失败 / 超时)且 `state_changed == true` 时,服务端才触发**策略链回退**(`trigger_strategy_fallback`,按 `failed_stage` 弹 TLUSTY 或 SYNSPEC 策略链);被终态守卫吸收的重复失败(`state_changed=false`)不再触发,杜绝重复派发涡旋(2026-08-02 修复)。
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X POST http://localhost:8090/api/task/report \
|
||
-H "Authorization: Bearer secret_token" \
|
||
-F 'report={
|
||
"task_id": "550e8400-e29b-41d4-a716-446655440000",
|
||
"point_name": "t35000_g5.5_he-1_c-2_n-2_o-2",
|
||
"node_id": "node-worker-01",
|
||
"status": "completed",
|
||
"converged": true,
|
||
"max_relc": 0.00008,
|
||
"atmosphere_has_nan": false,
|
||
"elapsed_sec": 142.5,
|
||
"error_message": null,
|
||
"summary_json": "{\"name\":\"t35000_g5.5_he-1_c-2_n-2_o-2\",\"params\":{\"teff\":35000.0,\"logg\":5.5,\"loghe\":-1.0,\"logc\":-2.0,\"logn\":-2.0,\"logo\":-2.0},\"stages\":[],\"converged\":true,\"elapsed_sec\":142.5,\"atmosphere_has_nan\":false}"
|
||
};type=application/json' \
|
||
-F 'seed_file=@/path/to/t35000_g5.5_he-1_c-2_n-2_o-2.7'
|
||
```
|
||
|
||
### 4.3 历史种子批量导入 (`POST /api/admin/import_seed`)
|
||
|
||
把旧版单机 `run_grid.py` 产物(`conv.json` + `.7` 大气文件)批量回灌进 DCTS。与 `/task/report` 的关键区别:**跳过任务归属校验**(历史数据无领用语义),直接幂等落库。供 [`tools/import_results`](file:///home/fmq/program/tlusty/tl208-s54/dcts/tools/import_results/src/main.rs) 调用。
|
||
|
||
- **处理函数**: [`import_seed`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/task.rs)
|
||
- **鉴权**: **Admin 角色**(admin token 或登录 session)
|
||
- **请求格式**: `multipart/form-data`
|
||
- Part `report`: 旧版 `conv.json` 的**原文 JSON**(映射为 `ModelSummary`,服务端解析出 `name` / `params` / `converged` / `final_max_relc`)
|
||
- Part `seed_file`: 二进制数据(`.7` 大气种子文件;收敛点必传)
|
||
- Part `success_method` *(可选)*: 文本 `cold_run` / `seed_step`。由 `tools/import_results` 依据旧 `conv.json` 的 stages 是否含 `seed_nc` 判定后设置,决定导入点最终归因(缺省按 seed_step 统计)
|
||
- **Query 参数**: `workflow`(可选,默认 `imported`):目标工作流名,种子导入到该工作流的 `grid_points`。
|
||
- **命名保真**: `point_name` 取旧 `conv.json` 的 `name` 字段(源精度真名,如 `t20000_g5.0_...`),**逐字符**落库(磁盘目录、`grid_points.name`、`seeds.point_name`),与旧版 Python `gen_input5.model_name` 完全一致。
|
||
- **幂等**: `ON CONFLICT DO NOTHING` upsert `grid_points`、`conv.json` 与 `.7` 原子覆盖写,可重复运行。
|
||
- **响应 Schema**:
|
||
- `200 OK`:
|
||
```json
|
||
{
|
||
"status": "ok",
|
||
"point_name": "t20000_g5.0_he-2_c-4_n-4_o-4",
|
||
"converged": true,
|
||
"max_relc": 0.000321
|
||
}
|
||
```
|
||
- `400 Bad Request`: 缺少 `report` / `conv.json` 解析失败 / 非法网格点名称
|
||
- `401 Unauthorized`: 缺少或无效 admin 凭据
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X POST "http://localhost:8090/api/admin/import_seed?workflow=sdB_cno" \
|
||
-H "Authorization: Bearer <admin_token>" \
|
||
-F 'report=@/path/to/old_results/t20000_g5.0_he-2_c-4_n-4_o-4/conv.json;type=application/json' \
|
||
-F 'seed_file=@/path/to/old_results/t20000_g5.0_he-2_c-4_n-4_o-4/t20000_g5.0_he-2_c-4_n-4_o-4.7'
|
||
```
|
||
- **批量导入工具**: [`import_results`](file:///home/fmq/program/tlusty/tl208-s54/dcts/tools/import_results/src/main.rs) 自动扫描结果目录、校验无 NaN 且收敛、逐点调用本端点(同时把完整产物树迁移到 `data/result/`):
|
||
```bash
|
||
cargo run -p import_results -- --dir <旧 results 根目录> \
|
||
--config workflows/sdB_cno.yaml \
|
||
--server http://127.0.0.1:8090 --workflow sdB_cno --token <admin_token>
|
||
```
|
||
|
||
---
|
||
|
||
## 5. 种子文件管理 API (Seed Management)
|
||
|
||
提供在网格计算过程中相近网格点间传递与下载 TLUSTY `fort.7` 大气结构二进制种子文件的功能。
|
||
|
||
### 5.1 下载网格点种子文件 (`GET /api/seed/:name`)
|
||
|
||
- **处理函数**: [`download_seed`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/seed.rs#L12-L58)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn download_seed(
|
||
State(state): State<AppState>,
|
||
AxumPath(name): AxumPath<String>,
|
||
) -> Response
|
||
```
|
||
- **鉴权**: 是 (若配置 Token)
|
||
- **路径参数**:
|
||
- `name`: 网格点名称 (例如: `t35000_g5.5_he-1_c-2_n-2_o-2`)
|
||
- **安全检查**: 防止路径穿越攻击,校验参数中不可包含 `..`、`/` 或 `\`。
|
||
- **响应 Header**:
|
||
- `Content-Type: application/octet-stream`
|
||
- `Content-Disposition: attachment; filename="<name>.7"`
|
||
- **状态码与响应体**:
|
||
- `200 OK`: 返回文件二进制流
|
||
- `400 Bad Request`: `"非法的种子名称参数"`
|
||
- `404 Not Found`: `"请求的种子文件不存在"`
|
||
- `500 Internal Server Error`: `"无法打开种子文件"`
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X GET http://localhost:8090/api/seed/t35000_g5.5_he-1_c-2_n-2_o-2 \
|
||
-H "Authorization: Bearer secret_token" \
|
||
--output t35000_g5.5_he-1_c-2_n-2_o-2.7
|
||
```
|
||
|
||
---
|
||
|
||
## 6. 静态资源与数据下载 API (Data Assets)
|
||
|
||
供 Worker 节点下载执行 TLUSTY / SYNSPEC 所需的原子数据文件和线表。
|
||
|
||
### 6.1 下载任意数据资源文件 (`GET /api/data/file/*filename`)
|
||
|
||
- **处理函数**: [`download_single_data_file`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/data.rs#L11-L24)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn download_single_data_file(
|
||
AxumPath(filename): AxumPath<String>
|
||
) -> axum::response::Response
|
||
```
|
||
- **鉴权**: 是 (若配置 Token)
|
||
- **路径参数**:
|
||
- `filename`: 文件相对名称 (例如: `he2.dat`)
|
||
- **响应 Header**:
|
||
- `Content-Type: application/octet-stream`
|
||
- `Content-Disposition: attachment; filename="<filename>"`
|
||
- **状态码与响应体**:
|
||
- `200 OK`: 返回数据文件二进制流
|
||
- `400 Bad Request`: `"无效的数据文件名"`
|
||
- `404 Not Found`: `"资源数据文件不存在"`
|
||
- `500 Internal Server Error`: `"无法读取资源数据文件"`
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X GET http://localhost:8090/api/data/file/he2.dat \
|
||
-H "Authorization: Bearer secret_token" \
|
||
--output he2.dat
|
||
```
|
||
|
||
---
|
||
|
||
### 6.2 下载主光谱线表文件 (`GET /api/data/linelist`)
|
||
|
||
- **处理函数**: [`download_linelist`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/data.rs#L26-L28)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn download_linelist() -> axum::response::Response
|
||
```
|
||
- **鉴权**: 是 (若配置 Token)
|
||
- **响应**: 默认定位并流式返回 `assets/gfVIS99.dat` 文件。
|
||
- **状态码**: `200 OK` (或 `404 Not Found` / `500 Internal Server Error`)
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X GET http://localhost:8090/api/data/linelist \
|
||
-H "Authorization: Bearer secret_token" \
|
||
--output gfVIS99.dat
|
||
```
|
||
|
||
---
|
||
|
||
## 7. 系统状态监控 API (System Status)
|
||
|
||
实时监控分布式计算集群节点活跃度与计算槽位利用率。
|
||
|
||
### 7.1 获取集群整体状态 (`GET /api/status`)
|
||
|
||
- **处理函数**: [`get_status`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/status.rs#L5-L17)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn get_status(State(state): State<AppState>) -> impl IntoResponse
|
||
```
|
||
- **鉴权**: 是 (若配置 Token)
|
||
- **响应 Schema (`200 OK`)**:
|
||
```json
|
||
{
|
||
"status": "online",
|
||
"nodes_online": 2,
|
||
"total_active_slots": 4,
|
||
"total_max_slots": 16,
|
||
"nodes": [
|
||
{
|
||
"node_id": "node-worker-01",
|
||
"max_slots": 8,
|
||
"active_slots": 2,
|
||
"status": "online",
|
||
"cpu_usage": 45.2,
|
||
"memory_usage": 30.8,
|
||
"last_heartbeat": "2026-07-27T16:55:00.000Z"
|
||
}
|
||
],
|
||
"grid_stats": {
|
||
"total": 512,
|
||
"pending": 210,
|
||
"queued": 60,
|
||
"running": 32,
|
||
"converged": 260,
|
||
"failed": 10,
|
||
"cold_run_converged": 200,
|
||
"seed_step_converged": 60
|
||
}
|
||
}
|
||
```
|
||
> `grid_stats` 为**全部工作流的合计**(跨工作流全局聚合)。多工作流并发运行时,此处展示所有
|
||
> 工作流 grid_points 的汇总进度;`queued` 与 `pending` 分开计数(多工作流分区新语义),
|
||
> `cold_run_converged` / `seed_step_converged` 为收敛手段归因;如需查看单个工作流的进度,
|
||
> 可读取该工作流各自的 grid_points 统计(`Database::get_grid_summary_stats(Some(workflow_name))`)。
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X GET http://localhost:8090/api/status \
|
||
-H "Authorization: Bearer secret_token"
|
||
```
|
||
|
||
---
|
||
|
||
### 7.2 轻量级健康检查端点 (`GET /healthz`)
|
||
|
||
- **鉴权**: 否 (Public 免鉴权,专用于 Docker / K8s / Caddy 探针)
|
||
- **响应 (`200 OK`)**:
|
||
```json
|
||
{
|
||
"status": "ok"
|
||
}
|
||
```
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -i http://localhost:8090/healthz
|
||
```
|
||
|
||
---
|
||
|
||
## 8. 工作流管理 API (Workflow CRUD & Execution)
|
||
|
||
管理恒星大气网格计算工作流 YAML 配置的增删改查、启动与暂停控制。
|
||
|
||
### 8.1 获取工作流列表 (`GET /api/workflows`)
|
||
|
||
- **处理函数**: [`list_workflows`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L26-L31)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn list_workflows(State(state): State<AppState>) -> impl IntoResponse
|
||
```
|
||
- **鉴权**: 是 (若配置 Token)
|
||
- **说明**: 返回工作流轻量级元数据列表(包含 `name`、`description`、`status`、`created_at`、`updated_at`)。如需获取具体工作流的 YAML 配置详情,请调用 `GET /api/workflows/:name`。
|
||
- **响应 Schema (`200 OK`)**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "成功获取工作流列表",
|
||
"data": [
|
||
{
|
||
"name": "sdB_cno",
|
||
"description": "sdB CNO 6D Stellar Atmosphere Grid",
|
||
"status": "idle",
|
||
"created_at": "2026-07-27T08:00:00Z",
|
||
"updated_at": "2026-07-27T08:00:00Z"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X GET http://localhost:8090/api/workflows \
|
||
-H "Authorization: Bearer secret_token"
|
||
```
|
||
|
||
---
|
||
|
||
### 8.2 创建或保存工作流 (`POST /api/workflows` / `PUT /api/workflows/:name`)
|
||
|
||
- **处理函数**: [`save_workflow`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L44-L81)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn save_workflow(
|
||
State(state): State<AppState>,
|
||
Json(req): Json<CreateWorkflowRequest>,
|
||
) -> impl IntoResponse
|
||
```
|
||
- **鉴权**: 是 (若配置 Token)
|
||
- **请求 Body**:
|
||
```json
|
||
{
|
||
"name": "sdB_cno_custom",
|
||
"description": "自定义 6维 网格计算工作流",
|
||
"config_yaml": "grid:\n teff: [35000, 36000]\n logg: [5.5, 6.0]\n loghe: [-1.0]\n logc: [-2.0]\n logn: [-2.0]\n logo: [-2.0]\nchain:\n - label: LTE_START\n lte: T\n niter: 30\n"
|
||
}
|
||
```
|
||
- **响应 Schema**:
|
||
- `200 OK` (成功保存):
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "工作流 'sdB_cno_custom' 保存成功",
|
||
"data": null
|
||
}
|
||
```
|
||
- `400 Bad Request` (YAML 格式不合法):
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "无效的 YAML 配置: invalid syntax at line 2...",
|
||
"data": null
|
||
}
|
||
```
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X POST http://localhost:8090/api/workflows \
|
||
-H "Content-Type: application/json" \
|
||
-H "Authorization: Bearer secret_token" \
|
||
-d '{
|
||
"name": "sdB_cno_custom",
|
||
"description": "自定义 6维 网格计算工作流",
|
||
"config_yaml": "grid:\n teff: [35000, 36000]\n logg: [5.5, 6.0]\n loghe: [-1.0]\n logc: [-2.0]\n logn: [-2.0]\n logo: [-2.0]\nchain:\n - label: LTE_START\n lte: T\n niter: 30\n"
|
||
}'
|
||
```
|
||
|
||
---
|
||
|
||
### 8.3 获取特定工作流详情 (`GET /api/workflows/:name`)
|
||
|
||
- **处理函数**: [`get_workflow`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L33-L42)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn get_workflow(
|
||
State(state): State<AppState>,
|
||
AxumPath(name): AxumPath<String>,
|
||
) -> impl IntoResponse
|
||
```
|
||
- **鉴权**: 是 (若配置 Token)
|
||
- **路径参数**: `name` (工作流唯一名称,如 `sdB_cno`)
|
||
- **响应 Schema**:
|
||
- `200 OK` (成功):
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "成功获取工作流详情",
|
||
"data": {
|
||
"name": "sdB_cno",
|
||
"description": "sdB CNO 6D Stellar Atmosphere Grid",
|
||
"config_yaml": "...",
|
||
"status": "idle",
|
||
"created_at": "2026-07-27T08:00:00Z",
|
||
"updated_at": "2026-07-27T08:00:00Z"
|
||
}
|
||
}
|
||
```
|
||
- `404 Not Found` (不存在):
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "工作流 'unknown_wf' 未找到",
|
||
"data": null
|
||
}
|
||
```
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X GET http://localhost:8090/api/workflows/sdB_cno \
|
||
-H "Authorization: Bearer secret_token"
|
||
```
|
||
|
||
---
|
||
|
||
### 8.4 删除工作流 (`DELETE /api/workflows/:name`)
|
||
|
||
- **处理函数**: [`delete_workflow`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L83-L105)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn delete_workflow(
|
||
State(state): State<AppState>,
|
||
AxumPath(name): AxumPath<String>,
|
||
) -> impl IntoResponse
|
||
```
|
||
- **鉴权**: 是 (若配置 Token)
|
||
- **响应 Schema (`200 OK`)**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "工作流 'sdB_cno_custom' 已删除",
|
||
"data": null
|
||
}
|
||
```
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X DELETE http://localhost:8090/api/workflows/sdB_cno_custom \
|
||
-H "Authorization: Bearer secret_token"
|
||
```
|
||
|
||
---
|
||
|
||
### 8.5 启动工作流 (`POST /api/workflows/:name/start`)
|
||
|
||
- **处理函数**: [`start_workflow`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L107-L183)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn start_workflow(
|
||
State(state): State<AppState>,
|
||
AxumPath(name): AxumPath<String>,
|
||
) -> impl IntoResponse
|
||
```
|
||
- **说明**: 以原子 CAS(`transition_workflow_to_initializing`)抢占启动权,随后**异步 spawn** 网格初始化:解析 YAML 的 6 维网格点、按 policy 决定点状态(跳过收敛/打回失败)、展开并计算保序难度 Wave、把 pending 点推入 MQ 开启节点调度。接口先返回 200,后台推进为 `running`。
|
||
- **响应 Schema**:
|
||
- `200 OK` (成功启动,后台初始化中):
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "工作流 'sdB_cno' 已进入后台异步建立与挂载流程",
|
||
"data": null
|
||
}
|
||
```
|
||
- `400 Bad Request` (重复启动):
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "工作流 'sdB_cno' 已处于运行状态,无需重复启动",
|
||
"data": null
|
||
}
|
||
```
|
||
- `409 Conflict` (并发启动,初始化抢占失败):
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "工作流 'sdB_cno' 初始化抢占挂起异常,请稍后重试",
|
||
"data": null
|
||
}
|
||
```
|
||
- `404 Not Found`:
|
||
```json
|
||
{
|
||
"success": false,
|
||
"message": "工作流 'sdB_cno' 未找到",
|
||
"data": null
|
||
}
|
||
```
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X POST http://localhost:8090/api/workflows/sdB_cno/start \
|
||
-H "Authorization: Bearer secret_token"
|
||
```
|
||
|
||
---
|
||
|
||
### 8.6 暂停工作流 (`POST /api/workflows/:name/stop`)
|
||
|
||
- **处理函数**: [`stop_workflow`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L185-L207)
|
||
- **函数签名**:
|
||
```rust
|
||
pub async fn stop_workflow(
|
||
State(state): State<AppState>,
|
||
AxumPath(name): AxumPath<String>,
|
||
) -> impl IntoResponse
|
||
```
|
||
- **响应 Schema (`200 OK`)**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "工作流 'sdB_cno' 已暂停",
|
||
"data": null
|
||
}
|
||
```
|
||
- **curl 示例**:
|
||
```bash
|
||
curl -X POST http://localhost:8090/api/workflows/sdB_cno/stop \
|
||
-H "Authorization: Bearer secret_token"
|
||
```
|
||
|
||
---
|
||
|
||
### 8.7 工作流执行统计 (`GET /api/workflows/:name/stats`)
|
||
|
||
- **处理函数**: `get_workflow_stats`(`crates/server/src/api/workflow.rs`)
|
||
- **权限**: Admin(`/workflows/*` 前缀统一映射)
|
||
- **用途**: 详情页"任务控制条 + 执行概览"数据源。在 `get_grid_summary_stats(Some(wf))`
|
||
之上追加难度波次分布与近似 ETA。**`pending` 与 `queued` 分开计数**(与 `/api/status`
|
||
的全局口径不同,后者由前端合并展示)。
|
||
- **路径参数**: `:name` — 工作流名(白名单 `[A-Za-z0-9._-]{1,64}`);未知工作流 → `404`。
|
||
- **响应 Schema (`200 OK`)**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "成功获取工作流统计",
|
||
"data": {
|
||
"name": "sdB_cno",
|
||
"status": "running",
|
||
"total": 432,
|
||
"pending": 120,
|
||
"queued": 40,
|
||
"running": 8,
|
||
"converged": 261,
|
||
"failed": 3,
|
||
"cold_run_converged": 220,
|
||
"seed_step_converged": 41,
|
||
"waves": [
|
||
{ "wave": 0, "total": 108, "converged": 108, "failed": 0 }
|
||
],
|
||
"avg_point_sec": 740.5,
|
||
"eta_sec": 9620.0
|
||
}
|
||
}
|
||
```
|
||
> 收敛手段归因仅 `cold_run_converged` / `seed_step_converged` 两字段;**无独立
|
||
> `imported_converged`**——历史导入点统一按 seed_step 途径计入(`success_method` 语义见 §8.8)。
|
||
> `avg_point_sec` = `AVG(COALESCE(tasks.elapsed_sec, created_at→completed_at 时间戳差))`——
|
||
> 优先用 Worker 回报的精确墙钟(不含排队等待),历史无 `elapsed_sec` 的行回退时间戳差近似;
|
||
> `eta_sec` = `avg_point_sec × (total - converged - failed) ÷ 在线节点总槽位`(并发感知;
|
||
> 无在线节点按串行兜底);无历史数据时二者为 `null`。
|
||
|
||
---
|
||
|
||
### 8.8 工作流网格点列表 (`GET /api/workflows/:name/points`)
|
||
|
||
- **处理函数**: `get_workflow_points`(`crates/server/src/api/workflow.rs`)
|
||
- **权限**: Admin
|
||
- **用途**: 详情页"网格点明细"表与"收敛性分析"(Parallel Sets 平行集合图)、概览"最近动态"流的数据源。
|
||
每行附带**最近一次尝试**信息(`tasks` 关联子查询取最新行,从未派发过的点 `last_*` 为 null)。
|
||
- **查询参数**(全部可选,枚举值白名单校验,非法 → `400`):
|
||
|
||
| 参数 | 取值 | 默认 |
|
||
| :--- | :--- | :--- |
|
||
| `status` | `pending`/`queued`/`running`/`converged`/`failed` | 不过滤 |
|
||
| `method` | `cold_run`/`seed_step`(白名单**不含** `imported`,传入即 `400`) | 不过滤 |
|
||
| `wave` | 整数波次 | 不过滤 |
|
||
| `q` | 点名子串(LIKE 通配符已转义) | 不过滤 |
|
||
| `sort` | `wave`/`teff`/`max_relc`/`attempts`/`last_completed_at` | `wave` |
|
||
| `order` | `asc`/`desc` | `asc` |
|
||
| `limit` | 1–500(超出钳位) | 100 |
|
||
| `offset` | ≥0 | 0 |
|
||
|
||
- **响应 Schema (`200 OK`)**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "成功获取工作流网格点列表",
|
||
"data": {
|
||
"total": 432,
|
||
"points": [
|
||
{
|
||
"name": "t60000_g5.0_he-2_c-4_n-4_o-4",
|
||
"teff": 60000.0, "logg": 5.0, "loghe": -2.0,
|
||
"logc": -4.0, "logn": -4.0, "logo": -4.0,
|
||
"cno_sum": -12.0, "wave": 0,
|
||
"status": "converged",
|
||
"success_method": "seed_step",
|
||
"attempt_count": 2,
|
||
"last_max_relc": 0.00043,
|
||
"last_task_type": "seed_step",
|
||
"seed_point_name": "t60000_g5.0_he2_c-4_n-4_o-4",
|
||
"node_id": "node-a1b2",
|
||
"last_completed_at": "2026-07-30 11:12:00",
|
||
"last_error": null
|
||
}
|
||
]
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 8.9 网格点详情 (`GET /api/workflows/:name/points/:point`)
|
||
|
||
- **处理函数**: `get_workflow_point_detail`(`crates/server/src/api/workflow.rs`)
|
||
- **权限**: Admin
|
||
- **用途**: 点详情滑入面板——尝试历史(还原"冷启动失败 → 种子步进救回"剧情)
|
||
+ `conv.json` 逐阶段诊断。
|
||
- **路径参数**: `:point` — 点名白名单 `[A-Za-z0-9._-+@]`(非空、拒前导 `.`、≤128,
|
||
即路径穿越前置闸门);conv.json 读盘前再经 canonicalize 归属校验。非法点名 → `400`;
|
||
点不存在 → `404`。
|
||
- **响应 Schema (`200 OK`)**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "成功获取网格点详情",
|
||
"data": {
|
||
"point": { "...": "同 8.8 单行" },
|
||
"attempts": [
|
||
{
|
||
"task_id": "uuid",
|
||
"task_type": "cold_run",
|
||
"seed_point_name": null,
|
||
"status": "failed",
|
||
"max_relc": 954000.0,
|
||
"atmosphere_has_nan": false,
|
||
"node_id": "node-a1b2",
|
||
"error_message": "nl stage diverged",
|
||
"created_at": "2026-07-30 09:00:00",
|
||
"completed_at": "2026-07-30 09:30:00"
|
||
},
|
||
{ "task_type": "seed_step", "status": "completed", "...": "第二次尝试(救回)" }
|
||
],
|
||
"conv": {
|
||
"converged": true,
|
||
"final_max_relc": 0.00043,
|
||
"final_chmax": 0.001,
|
||
"seed": "data/seeds/<seed_name>/<seed_name>.7",
|
||
"atmosphere_has_nan": false,
|
||
"synspec_rc": 0,
|
||
"synspec_sec": 3.1,
|
||
"elapsed_sec": 126.4,
|
||
"stages": [
|
||
{ "label": "seed_nc", "chmax": 0.001, "lte": false, "converged": false, "best_max_relc": 0.02, "elapsed_sec": 62.4, "note": null },
|
||
{ "label": "nl", "chmax": 0.001, "lte": false, "converged": true, "best_max_relc": 0.00043, "elapsed_sec": 64.0, "note": null }
|
||
]
|
||
}
|
||
}
|
||
}
|
||
```
|
||
> `conv` 来自 `seeds_dir/<point>/conv.json`(`ModelSummary` 结构,见
|
||
> `crates/common/src/models.rs`)。文件缺失/解析失败时为 `null`(仍返回 200,
|
||
> 前端降级显示"诊断文件不可用")。
|
||
|
||
---
|
||
|
||
### 8.10 工作流进度时间序列 (`GET /api/workflows/:name/progress`)
|
||
|
||
- **处理函数**: `get_workflow_progress`(`crates/server/src/api/workflow.rs`)
|
||
- **权限**: Admin
|
||
- **用途**: 详情页概览的进度曲线(sparkline)、经验速率 ETA 与停滞预警数据源。
|
||
快照由服务端后台循环(~30s)对每个运行中工作流写入 `workflow_progress_snapshots`
|
||
表,**计数无变化不落库**(去重防膨胀),保留期 7 天自动清理。
|
||
- **查询参数**: `hours`(时间窗口,默认 24,钳位 1–168)。
|
||
- **响应 Schema (`200 OK`)**:
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "成功获取进度时间序列",
|
||
"data": {
|
||
"hours": 24,
|
||
"series": [
|
||
{ "ts": "2026-07-31 08:00:00", "total": 432, "pending": 120, "queued": 40,
|
||
"running": 8, "converged": 261, "failed": 3 }
|
||
],
|
||
"rate_per_hour": 12.5,
|
||
"now": "2026-07-31T09:00:00Z",
|
||
"done_rate_per_hour": 11.0,
|
||
"rate_span_hours": 23.5,
|
||
"stalled_minutes": 0.0
|
||
}
|
||
}
|
||
```
|
||
> `series` 超 300 条自动降采样(首末点保留);`rate_per_hour` = 窗口首末
|
||
> converged 增量 ÷ 时长(快照不足 2 条为 null);`now` = 服务端当前 UTC 时刻(前端锚定曲线右缘);
|
||
> `done_rate_per_hour` = 终态完成速率(用于 ETA);`rate_span_hours` = 速率统计实际时间跨度;
|
||
> `stalled_minutes` = 终态数(converged+failed)最后一次增长至窗口末端的分钟数(前端 >10 分钟触发停滞预警)。
|
||
|
||
---
|
||
|
||
### 8.11 工作流列表内联统计(`GET /api/workflows` 响应增强)
|
||
|
||
`WorkflowSummary` 新增 `stats` 字段(单条 `GROUP BY` 聚合回填,无 N+1):
|
||
工作流尚无网格点(未启动)时为 `null`,前端据此不渲染卡片进度条。
|
||
|
||
```json
|
||
{
|
||
"success": true,
|
||
"message": "成功获取工作流列表",
|
||
"data": [
|
||
{
|
||
"name": "sdB_cno",
|
||
"description": "sdB 6维恒星大气模型计算网格",
|
||
"status": "running",
|
||
"created_at": "2026-07-30 10:00:00",
|
||
"updated_at": "2026-07-30 11:00:00",
|
||
"stats": {
|
||
"total": 432,
|
||
"converged": 261,
|
||
"failed": 3,
|
||
"running": 8,
|
||
"cold_run_converged": 220,
|
||
"seed_step_converged": 41
|
||
}
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 9. 错误处理与状态码汇总
|
||
|
||
| HTTP 状态码 | 触发场景说明 | 响应格式 | 核心原因与解决建议 |
|
||
| :--- | :--- | :--- | :--- |
|
||
| **`200 OK`** | 请求正常处理 | JSON / Binary Stream | 操作成功执行。 |
|
||
| **`400 Bad Request`** | 参数校验失败、缺失关键字段、YAML 格式错误或枚举白名单拒绝 | `application/json` / Plain Text | 检查请求 JSON 结构,验证 YAML 配置语法,确认 `method`/`sort`/`order` 等枚举在合法取值内。 |
|
||
| **`401 Unauthorized`** | 鉴权失败或缺失 Authorization Header | `application/json` | 确认环境变量配置,并在 Request Header 中包含正确的 Token。 |
|
||
| **`403 Forbidden`** | 身份校验失败(心跳 `node_id` 与鉴权身份不匹配、跨点上报拒绝、角色权限不足) | `application/json` | 确认节点 token 与上报身份一致,检查 RBAC 角色。 |
|
||
| **`404 Not Found`** | 资源、种子文件或工作流不存在 | `application/json` | 校验请求 URL 中的资源文件名或工作流 `name` 是否拼写无误。 |
|
||
| **`409 Conflict`** | 状态冲突:节点停用/启用状态不支持、start 初始化抢占失败、report 任务归属校验失败 | `application/json` | 检查目标状态是否合法,或稍后重试。 |
|
||
| **`429 Too Many Requests`** | API 请求超出速率限制(滑动窗口限流生效) | `application/json` | 降低请求频率;限流为滑动窗口实现(`rate_limit.rs`),非漏桶。 |
|
||
| **`500 Internal Server Error`** | 服务端数据库错误、I/O 打开失败或队列异常 | `application/json` | 检查服务端日志以进一步厘清 SQLite 锁冲突、磁盘空间或资源路径问题。 |
|
||
|
||
---
|
||
*文档生成于 2026-07-27,2026-08-04 更新以对齐阶段独立配置 / 多工作流隔离 / 节点配额 / 失败阶段归因。*
|