Preserve usage provider identity

This commit is contained in:
fawney19
2026-05-20 20:56:36 +08:00
parent c972bbd397
commit 57910f906d
21 changed files with 340 additions and 104 deletions

View File

@@ -53,6 +53,15 @@ fn admin_usage_aggregation_by_provider_json(
json!(rows
.iter()
.map(|row| {
let identity_source = match row.secondary_name.as_deref() {
Some("legacy_name") => "legacy_name",
_ => "provider_id",
};
let provider_id = if identity_source == "provider_id" {
json!(row.group_key)
} else {
serde_json::Value::Null
};
let success_count = row.success_count.unwrap_or_default();
let error_count = row.request_count.saturating_sub(success_count);
let success_rate = if row.request_count == 0 {
@@ -67,7 +76,9 @@ fn admin_usage_aggregation_by_provider_json(
.filter(|value| !value.is_empty())
.unwrap_or(row.group_key.as_str());
json!({
"provider_id": row.group_key,
"provider_id": provider_id,
"provider_key": row.group_key,
"provider_identity_source": identity_source,
"provider": provider_name,
"request_count": row.request_count,
"total_tokens": row.total_tokens,

View File

@@ -115,6 +115,30 @@ pub(crate) const ADMIN_MODULE_DEFINITIONS: &[AdminModuleDefinition] = &[
admin_menu_group: Some("system"),
admin_menu_order: 60,
},
AdminModuleDefinition {
name: "payment_gateways",
display_name: "支付配置",
description: "配置易支付、支付宝官方、微信支付官方和 Stripe 等支付网关",
category: "integration",
env_key: "PAYMENT_GATEWAYS_AVAILABLE",
default_available: true,
admin_route: Some("/admin/payment-gateways"),
admin_menu_icon: Some("CreditCard"),
admin_menu_group: None,
admin_menu_order: 70,
},
AdminModuleDefinition {
name: "referral",
display_name: "邀请返利",
description: "管理用户邀请关系与返利记录,支持比例返利和人头返利",
category: "integration",
env_key: "REFERRAL_AVAILABLE",
default_available: true,
admin_route: Some("/admin/referrals"),
admin_menu_icon: Some("Gift"),
admin_menu_group: Some("management"),
admin_menu_order: 75,
},
];
#[derive(Debug, Clone, serde::Deserialize)]

View File

@@ -665,8 +665,14 @@ async fn gateway_handles_admin_usage_aggregation_stats_locally_with_trusted_admi
let provider_items = provider_payload.as_array().expect("array response");
assert_eq!(provider_items.len(), 2);
assert_eq!(provider_items[0]["provider"], "OpenAI");
assert_eq!(provider_items[0]["provider_id"], "provider-openai");
assert_eq!(provider_items[0]["provider_key"], "provider-openai");
assert_eq!(provider_items[0]["provider_identity_source"], "provider_id");
assert_eq!(provider_items[0]["output_tokens"], 40);
assert_eq!(provider_items[1]["provider"], "Anthropic");
assert_eq!(provider_items[1]["provider_id"], "provider-anthropic");
assert_eq!(provider_items[1]["provider_key"], "provider-anthropic");
assert_eq!(provider_items[1]["provider_identity_source"], "provider_id");
assert_eq!(provider_items[1]["output_tokens"], 20);
let api_format_response = admin_request(reqwest::Client::new().get(format!(
@@ -693,6 +699,59 @@ async fn gateway_handles_admin_usage_aggregation_stats_locally_with_trusted_admi
upstream_handle.abort();
}
#[tokio::test]
async fn gateway_handles_admin_usage_aggregation_stats_for_legacy_provider_name_rows() {
let legacy_usage = {
let mut usage = sample_usage_row(
"usage-legacy",
"req-legacy",
Some("user-1"),
Some("key-1"),
Some("primary"),
"Legacy Provider",
"gpt-5",
"completed",
120,
30,
0.3,
0.36,
DAY_1_UNIX_SECS,
);
usage.provider_id = None;
usage.total_tokens = usage.input_tokens;
usage
};
let usage_repository = Arc::new(InMemoryUsageReadRepository::seed(vec![legacy_usage]));
let gateway = build_router_with_state(
AppState::new()
.expect("gateway should build")
.with_data_state_for_tests(GatewayDataState::with_usage_reader_for_tests(
usage_repository,
)),
);
let (gateway_url, gateway_handle) = start_server(gateway).await;
let response = admin_request(reqwest::Client::new().get(format!(
"{gateway_url}/api/admin/usage/aggregation/stats?group_by=provider&limit=10&start_date=2024-03-21&end_date=2024-03-22&tz_offset_minutes=0"
)))
.send()
.await
.expect("request should succeed");
assert_eq!(response.status(), StatusCode::OK);
let payload: serde_json::Value = response.json().await.expect("json body should parse");
let items = payload.as_array().expect("array response");
assert_eq!(items.len(), 1);
assert_eq!(items[0]["provider"], "Legacy Provider");
assert_eq!(items[0]["provider_id"], serde_json::Value::Null);
assert_eq!(items[0]["provider_key"], "Legacy Provider");
assert_eq!(items[0]["provider_identity_source"], "legacy_name");
gateway_handle.abort();
}
#[tokio::test]
async fn gateway_returns_service_unavailable_for_admin_usage_replay_without_provider_catalog_reader(
) {