补齐错误 envelope 的请求上下文并扩展游戏分发审核取证
Project CI / AI game creator shell Rust crates (push) Successful in 1m30s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m59s
Project CI / AI game creator shell Rust lane 2/2 (push) Failing after 5m26s
Project CI / Backend tests (push) Successful in 4m13s
Project CI / Frontend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
Project CI / AI game creator shell Rust lane 1/2 (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled

- api-server 的错误响应改用 attach_request_context 建立的 CURRENT_REQUEST_CONTEXT(tokio task_local),错误 envelope 与成功 envelope 一样带 meta.requestId / meta.operation,脱离请求任务时退回原形状
- 越权隔离脚本新增成功/失败 envelope 断言:修复前错误响应 requestId 为空让断言变红,修复后 23 项 PASS
- 媒体链路脚本扩展审核治理段:普通作者不能提交审核动作、更新待审与被拒都不改变在线旧版 URL 与可玩内容、审核结论可在后台追溯,44 项 PASS
- 游戏分发里程碑阶段 A 第 6 条、阶段 B 第 1/2 条改为已勾选并写入取证,阶段 B 其余条目列出具体缺口
- decision-log 补记错误 envelope 同一份 meta 与「按可消费取证」两条口径
This commit is contained in:
kdletters
2026-09-28 18:58:38 +08:00
parent 23de78b476
commit 253f99d921
6 changed files with 256 additions and 14 deletions
+12 -2
View File
@@ -6,7 +6,10 @@ use axum::{
use serde_json::Value;
use shared_contracts::api::ApiErrorPayload;
use crate::{api_response::json_error_body, request_context::RequestContext};
use crate::{
api_response::json_error_body,
request_context::{CURRENT_REQUEST_CONTEXT, RequestContext},
};
#[derive(Debug)]
pub struct AppError {
@@ -112,7 +115,14 @@ impl AppError {
impl IntoResponse for AppError {
fn into_response(self) -> Response {
self.into_response_with_context(None)
// 绝大多数 handler 错误是在 `attach_request_context` 的任务里转成响应的:
// 这里带上同一份 requestId / operation,错误 envelope 才与成功 envelope 一致;
// 脱离请求任务(测试、后台任务)时退回无上下文的形状。
if CURRENT_REQUEST_CONTEXT.try_with(|_| ()).is_ok() {
CURRENT_REQUEST_CONTEXT.with(|context| self.into_response_with_context(Some(context)))
} else {
self.into_response_with_context(None)
}
}
}
@@ -11,6 +11,12 @@ use uuid::Uuid;
pub use shared_contracts::api::X_REQUEST_ID_HEADER;
tokio::task_local! {
/// 当前请求的上下文:`AppError::into_response` 这类拿不到 extensions 的转换也要按同一份
/// meta 口径给出 requestId / operation,错误 envelope 与成功 envelope 才不会各写一套。
pub static CURRENT_REQUEST_CONTEXT: RequestContext;
}
// 当前阶段先把请求级元信息统一挂到 extensions,后续响应头、envelope 与错误处理中间件继续复用。
#[derive(Clone, Debug)]
pub struct RequestContext {
@@ -80,12 +86,14 @@ pub async fn attach_request_context(mut request: Request, next: Next) -> Respons
.unwrap_or_else(|| Uuid::new_v4().to_string());
let operation = format!("{} {}", request.method(), request.uri());
request.extensions_mut().insert(RequestContext::new(
let context = RequestContext::new(
request_id.clone(),
operation,
Duration::ZERO,
wants_envelope,
));
);
let context_for_scope = context.clone();
request.extensions_mut().insert(context);
// 统一把 request_id 写回请求头,方便后续 tracing、响应头与 envelope 层读取同一来源。
if let Ok(header_value) = HeaderValue::from_str(&request_id) {
@@ -94,7 +102,9 @@ pub async fn attach_request_context(mut request: Request, next: Next) -> Respons
.insert(HeaderName::from_static(X_REQUEST_ID_HEADER), header_value);
}
next.run(request).await
CURRENT_REQUEST_CONTEXT
.scope(context_for_scope, async move { next.run(request).await })
.await
}
pub fn resolve_request_id<B>(request: &HttpRequest<B>) -> Option<String> {