Files
Genarrative/apps/ai-game-creator-shell/src-tauri/src/platform_maintenance.rs
T
kdletters 1e62d8317a 合并 origin/master 到 AGC 渲染层下沉分支并完成三处对齐
- 活动回合事实源统一到 Direct 线程管理器:删除 direct_runtime 的第二份快照,接单、进度内容变化与收口各广播一次活动回合变更事件
- 平台维护态判定移入 Rust 并在渲染层只订阅单一事件:新增 platform_maintenance 模块与各平台 facade 错误分支的分类入口
- 封面生成请求补 generationInputs.source,保持队列回填后仍能拿到平台素材 ID
- 渲染层按 master 5398a53e6 退役诊断详情入口:删除 agentRuntimeErrorDetail 与「查看详情」交互及其专属用例
- 冲突收口:nginx SPA 白名单、.gitignore、mobile 检查脚本、capabilities 描述取上游,两个已退役计划随上游删除,文档保留双方条目
- 新增并回写本里程碑取证、decision-log 与 pitfalls 的 2026-09-28 记录
2026-09-28 15:07:30 +08:00

91 lines
3.5 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 平台维护态在客户端的唯一出口。
//!
//! 渲染层不再自己解析 HTTP:维护响应由 [`crate::http_client::send_with_maintenance_watch`]
//! 在平台请求出口统一分类,再经本模块广播一次
//! `genarrative-client-maintenance-detected`。界面只订阅这个事件,因此同一批并发失败
//! 只会让同一个维护弹窗出现一次,各业务面板自己的错误文案保持不变。
use std::sync::OnceLock;
#[cfg(test)]
use std::sync::atomic::{AtomicU64, Ordering};
pub(crate) const PLATFORM_MAINTENANCE_EVENT: &str = "genarrative-client-maintenance-detected";
static PLATFORM_MAINTENANCE_APP_HANDLE: OnceLock<tauri::AppHandle> = OnceLock::new();
#[cfg(test)]
static PLATFORM_MAINTENANCE_EVENT_TEST_COUNT: AtomicU64 = AtomicU64::new(0);
pub(crate) fn set_platform_maintenance_app_handle(app: tauri::AppHandle) {
let _ = PLATFORM_MAINTENANCE_APP_HANDLE.set(app);
}
/// 判断一次平台响应是不是维护窗口。
///
/// 口径与渲染层此前的判定一致:只有 `503` 才算维护窗口,且响应体里要能看出
/// `code = MAINTENANCE` 或「维护」。普通 5xx、对象存储自己的 503(例如 `SlowDown`)
/// 和网关生成的 503 页面都不归类,避免把短暂抖动讲成系统维护。
pub(crate) fn response_is_maintenance(status: u16, body: &str) -> bool {
if status != 503 {
return false;
}
body.to_ascii_uppercase().contains("MAINTENANCE") || body.contains("维护")
}
/// 广播一次客户端维护事件;没有 AppHandle(单元测试、CLI)时只累计测试计数。
pub(crate) fn emit_platform_maintenance_notice(status: u16) {
#[cfg(test)]
PLATFORM_MAINTENANCE_EVENT_TEST_COUNT.fetch_add(1, Ordering::AcqRel);
let Some(app) = PLATFORM_MAINTENANCE_APP_HANDLE.get() else {
return;
};
let _ = tauri::Emitter::emit(
app,
PLATFORM_MAINTENANCE_EVENT,
serde_json::json!({ "code": "MAINTENANCE", "status": status }),
);
}
/// 在平台响应的错误分支上分类维护态:命中就广播一次,未命中什么都不做。
///
/// 调用方在错误分支里本来就会读取响应体来构造自己的错误文案,这里只复用同一份 body,
/// 既不改写任何错误语义,也不额外发请求。
pub(crate) fn watch_platform_response(status: u16, body: &str) {
if response_is_maintenance(status, body) {
emit_platform_maintenance_notice(status);
}
}
#[cfg(test)]
pub(crate) fn platform_maintenance_event_test_count() -> u64 {
PLATFORM_MAINTENANCE_EVENT_TEST_COUNT.load(Ordering::Acquire)
}
#[cfg(test)]
mod tests {
use super::response_is_maintenance;
#[test]
fn only_503_with_a_maintenance_body_is_a_maintenance_window() {
assert!(response_is_maintenance(
503,
r#"{"error":{"code":"MAINTENANCE","message":"服务正在维护"}}"#
));
assert!(response_is_maintenance(
503,
r#"{"error":{"message":"系统维护中"}}"#
));
// 只有 503 才算:其它状态码即使带 MAINTENANCE 也不弹维护窗。
assert!(!response_is_maintenance(
500,
r#"{"error":{"code":"MAINTENANCE"}}"#
));
// 503 但不是维护:对象存储的 SlowDown 与普通网关错误不能冒充维护。
assert!(!response_is_maintenance(
503,
r#"{"code":"SlowDown","message":"please reduce your request rate"}"#
));
assert!(!response_is_maintenance(503, ""));
}
}