{"record":{"id":"aa33ff5f89c95ec5","repo":"BigPizzaV3/CodexPlusPlus","slug":"api-key-aa33ff","errorCode":null,"errorMessage":"API Key 不能为空","messagePattern":"API Key 不能为空","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/sub2api.rs","lineNumber":52,"sourceCode":"    applied_peak_multiplier: Option<f64>,\n    effective_rate_multiplier: f64,\n    observed_at: String,\n}\n\npub async fn fetch_sub2api_billing_info(\n    profile: &RelayProfile,\n) -> anyhow::Result<Sub2ApiBillingInfo> {\n    let base_url = if profile.upstream_base_url.trim().is_empty() {\n        profile.base_url.trim()\n    } else {\n        profile.upstream_base_url.trim()\n    };\n    if base_url.is_empty() {\n        anyhow::bail!(\"Base URL 不能为空\");\n    }\n    let api_key = profile.api_key.trim();\n    if api_key.is_empty() {\n        anyhow::bail!(\"API Key 不能为空\");\n    }\n\n    let endpoint = sub2api_billing_endpoint(base_url);\n    let client = crate::http_client::proxied_client(&profile.user_agent)?;\n    let response = client\n        .get(&endpoint)\n        .bearer_auth(api_key)\n        .header(\"x-api-key\", api_key)\n        .send()\n        .await\n        .with_context(|| format!(\"请求 {endpoint} 失败\"))?;\n    let status = response.status();\n    let body = response.text().await.unwrap_or_default();\n    if !status.is_success() {\n        anyhow::bail!(\n            \"HTTP {}：{}\",\n            status.as_u16(),\n            sub2api_error_message(&body).unwrap_or_else(|| \"上游拒绝读取倍率\".to_string())","sourceCodeStart":34,"sourceCodeEnd":70,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/crates/codex-plus-core/src/sub2api.rs#L34-L70","documentation":"In the same function, the trimmed profile.api_key is checked next; an empty key triggers this bail. The billing endpoint requires bearer/x-api-key auth, and calling it without a key would only yield a 401 from the server, so it is rejected locally.","triggerScenarios":"profile.api_key 为空字符串或纯空白时调用 fetch_sub2api_billing_info。","commonSituations":"用户未在 profile 中填写 API Key 就点查询倍率；导出的配置脱敏后丢失 Key；Key 被误删或换号后未更新。","solutions":["在 profile 设置中填入有效的 sub2api API Key 后重试","在 UI/配置层保存时强制校验 api_key 非空","若仅探测连通性，可改用无需鉴权的健康检查端点"],"exampleFix":"// before\nlet info = fetch_sub2api_billing_info(&profile).await?;\n// after\nif profile.api_key.trim().is_empty() {\n    eprintln!(\"请先填写 API Key\");\n    return Ok(None);\n}\nlet info = fetch_sub2api_billing_info(&profile).await?;","handlingStrategy":"validation","validationCode":"fn api_key_ready(p: &RelayProfile) -> bool {\n    !p.api_key.trim().is_empty()\n}","typeGuard":null,"tryCatchPattern":"if !api_key_ready(&profile) {\n    eprintln!(\"请先填写 sub2api API Key\");\n    return Ok(None);\n}\nlet info = fetch_sub2api_billing_info(&profile).await?;","preventionTips":["保存 profile 时强制校验 api_key 非空","配置脱敏导出后导入时提醒补填 Key","轮换密钥时同步更新本库 profile"],"tags":["auth","api-key","validation","sub2api"],"backgroundTag":"missing-api-key","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}