模块调用上下文
了解模块方法当前可以读取的 Runtime、调用方、用户和请求上下文字段。
模块调用上下文是平台在解析并校验一次模块方法调用时生成的 JSON 对象。它与页面或调用方提交的业务参数 相互独立,用于向方法实现提供当前 Runtime、调用方、访问用户和请求路由信息。
模块不能假定所有字段在所有入口都存在。方法只应读取本页列出的稳定字段,并根据调用入口决定哪些字段 必须存在。没有建立对应身份的调用不会伪造默认值,也不会从请求体中的同名字段回退。
稳定字段
| 字段 | 当前类型 | 何时存在 | 含义 |
|---|---|---|---|
applicationRuntimeId | string | 所有已经解析到 Runtime 的模块后端调用 | 当前应用 Runtime ID。平台根据实际执行的 Runtime 写入,并覆盖调用入口提供的同名上下文值。 |
callerBusinessId | string | 已验证出调用方业务身份时 | 调用当前方法的源模块或服务 businessId。模块通过服务引用调用另一模块时使用该字段区分调用方;直接用户调用或没有业务身份的入口可能不存在。 |
targetBusinessId | string | 按模块或服务 businessId 路由方法时 | 当前被调用模块或服务的 businessId。按全局方法名解析时不存在。 |
targetMethodName | string | 已成功解析方法时 | 当前目标方法名。 |
userId | number | string | 当前入口完成用户身份校验并返回用户时 | 当前平台用户 ID。未登录、可选认证未建立身份或服务调用未携带用户身份时不存在。 |
workspaceId | number | string | 当前入口完成工作区身份校验时 | 当前可信工作区 ID。它不会仅根据请求参数或请求体中的同名字段生成。 |
httpMethod | string | HTTP 入口 | 进入 Runtime 的 HTTP 方法,例如 GET 或 POST。 |
requestPath | string | HTTP 入口 | 进入 Runtime 的原始请求路径。 |
number | string 表示当前不同鉴权入口可能保留数字 ID,也可能保留字符串形式。ID 用作比较、存储键或
HTTP Header 前应先规范化为非空字符串,不要进行算术运算。
userId 是目标方法可读取的已验证上下文字段。外部模块后端继续通过 ServiceReference 调用下游服务时,
不能把这个数字重新放进业务参数或自定义用户 Header;应转发 Runtime 为本次请求注入的不透明
X-Runtime-Actor-Assertion。该 assertion 不属于本表的 JSON 上下文字段,也不由 methodBody 映射,完整
规则见 Runtime 用户委托。
Runtime 与模块级隔离
需要按 Runtime 和调用模块隔离数据时,同时把 applicationRuntimeId 和 callerBusinessId 声明为
必需上下文。若数据还属于工作区,再同时要求 workspaceId。任一必需字段缺失时应拒绝调用,不能回退
到仅工作区、仅 Runtime 或公共目录。
映射到 HTTP 请求
上下文不会自动完整转发给模块后端。HTTP 方法必须在 methodBody 中显式选择字段,并映射到 Header、
Path、Query 或 Body。以下示例把 Runtime、调用模块、工作区和可选用户映射为模块自己的业务 Header:
{
"header": {
"X-Module-Runtime-Id": {
"position": "context",
"key": "applicationRuntimeId",
"required": true
},
"X-Module-Caller-Business-Id": {
"position": "context",
"key": "callerBusinessId",
"required": true
},
"X-Module-Workspace-Id": {
"position": "context",
"key": "workspaceId",
"required": true
},
"X-Module-User-Id": {
"position": "context",
"key": "userId",
"required": false
}
}
}position: "context" 按 key 精确读取上下文字段。输出字段名与上下文键不同时必须写 key;省略
key 时使用输出字段名查找。required: true 的字段缺失会终止调用,不应使用固定值或更宽作用域代替。
需要转换字段或组装请求体时可以使用表达式:
{
"body": {
"runtimeId": {
"expression": "context.applicationRuntimeId",
"required": true
},
"callerBusinessId": {
"expression": "context.callerBusinessId",
"required": true
}
}
}表达式中的 context 只包含当前调用已经建立的字段。不要在表达式中从业务参数回退身份字段。
Rust / Axum 接入
Rust 后端不会自动获得一个 Runtime SDK 对象、请求扩展或完整上下文 JSON。Runtime 只按当前模块方法的
methodBody 映射发送选中的字段;Rust 服务读取的是映射后的普通 HTTP Header、Path、Query 或 Body。
因此,Rust 代码中的字段名由模块自己的 HTTP 契约决定;除非映射时刻意使用同名输出字段,否则不要假定
applicationRuntimeId 等网关内部键会直接出现在服务请求中。
使用上面的 Header 映射时,Axum 可以在 HTTP 入口立即转换成类型化上下文:
use axum::{
http::{HeaderMap, StatusCode},
response::{IntoResponse, Response},
};
#[derive(Debug)]
struct RuntimeCallContext {
application_runtime_id: String,
caller_business_id: String,
workspace_id: String,
user_id: Option<String>,
}
#[derive(Debug)]
enum ContextError {
Missing(&'static str),
Invalid(&'static str),
}
impl IntoResponse for ContextError {
fn into_response(self) -> Response {
let message = match self {
Self::Missing(name) => format!("missing required runtime context: {name}"),
Self::Invalid(name) => format!("invalid runtime context: {name}"),
};
(StatusCode::BAD_REQUEST, message).into_response()
}
}
fn read_header(
headers: &HeaderMap,
name: &'static str,
) -> Result<Option<String>, ContextError> {
let Some(value) = headers.get(name) else {
return Ok(None);
};
let value = value.to_str().map_err(|_| ContextError::Invalid(name))?;
let value = value.trim();
if value.is_empty() {
return Err(ContextError::Invalid(name));
}
Ok(Some(value.to_owned()))
}
fn required_header(
headers: &HeaderMap,
name: &'static str,
) -> Result<String, ContextError> {
read_header(headers, name)?.ok_or(ContextError::Missing(name))
}
impl TryFrom<&HeaderMap> for RuntimeCallContext {
type Error = ContextError;
fn try_from(headers: &HeaderMap) -> Result<Self, Self::Error> {
Ok(Self {
application_runtime_id: required_header(headers, "x-module-runtime-id")?,
caller_business_id: required_header(headers, "x-module-caller-business-id")?,
workspace_id: required_header(headers, "x-module-workspace-id")?,
user_id: read_header(headers, "x-module-user-id")?,
})
}
}
async fn handle_module_call(headers: HeaderMap) -> Result<StatusCode, ContextError> {
let context = RuntimeCallContext::try_from(&headers)?;
// 从这里开始,业务逻辑只使用类型化 context,不再直接读取 HeaderMap。
let _ = (
context.application_runtime_id,
context.caller_business_id,
context.workspace_id,
context.user_id,
);
Ok(StatusCode::NO_CONTENT)
}映射到 Header 后,Runtime 会把数字或字符串形式的 ID 都序列化为 Header 字符串;上面的 Rust 类型统一
使用非空 String,不会对 ID 做算术运算。可选的 userId 未建立时不会发送对应 Header,Rust 中保持
None。如果业务要求当前用户,应该同时把 X-Module-User-Id 的 required 改为 true,并把
user_id 改为必需字段。
上下文不是 Endpoint 凭据
X-Module-* 是模块自己定义的 HTTP 字段,不是平台凭据。只有请求确实经过受控 Runtime 调用链,并且
Endpoint 已按环境配置与服务鉴权限制直接访问时,
这些映射值才能作为该次 Runtime 调用的上下文使用。对外公开的 Endpoint 不能只凭这些 Header 完成鉴权。
Actor Header 不是方法自定义 Header
模块定义不得声明、固定或通过表达式生成 X-Runtime-Actor-Assertion。Runtime 只在已建立最终用户身份时
注入该请求级值;外部后端只把收到的值用于当前同步调用链,不解析、不记录、不持久化。
信任与缺失规则
params、Query、普通 Header 和请求体都是业务输入;其中的同名字段不会自动成为可信上下文。applicationRuntimeId来自当前实际执行的 Runtime,不由页面或模块自行选择。callerBusinessId只在平台验证出调用方业务身份后存在;不能由目标模块根据请求内容猜测。userId、workspaceId只代表当前入口已经建立的身份。字段缺失表示该身份没有建立,不表示值为0、空字符串或系统管理员。- 外部服务转发的 Actor assertion 必须经过 Runtime 验证;它不能由普通 Header、请求体或已保存的
userId重新构造。服务链进入新的目标后,下一跳 assertion 由 Runtime 自动轮换。 - 方法依赖某个字段时应声明
required: true并失败关闭。不要设置固定管理员、默认工作区、公共 Runtime 或其他兼容回退。 - 日志和错误信息可以记录字段是否存在,但不应输出完整身份声明、凭据或敏感模块属性。