阶段1接入AGC标记解析
Project CI / Repository checks (pull_request) Successful in 2m44s
Project CI / Frontend tests (pull_request) Successful in 3m5s
Project CI / Backend tests (pull_request) Successful in 6m44s
Project CI / Native shell tests (pull_request) Successful in 19m41s

在统一 API tracking middleware 中解析 X-Genarrative-Client。

将有效 marker 传入 TrackingEventDraft,供后续 metadata 合并使用。

增加大小写、空白、未知值和非法文本的定向测试。
This commit is contained in:
2026-09-02 08:00:51 +00:00
parent fe35fcf264
commit 2296f79fdc
2 changed files with 59 additions and 4 deletions
+3 -1
View File
@@ -24,7 +24,7 @@ use crate::{
response_headers::propagate_request_id_header,
state::{AppState, BackpressureState},
telemetry::record_http_observability,
tracking::record_route_tracking_event_after_success,
tracking::{record_route_tracking_event_after_success, resolve_tracking_client_marker},
wechat::pay::{
handle_wechat_pay_notify, handle_wechat_pay_refund_notify,
handle_wechat_virtual_payment_message_push_verify, handle_wechat_virtual_payment_notify,
@@ -241,6 +241,7 @@ async fn record_api_tracking_after_success(
) -> Response {
let method = request.method().clone();
let path = request.uri().path().to_string();
let client_marker = resolve_tracking_client_marker(request.headers());
let response = next.run(request).await;
let authenticated = response
.extensions()
@@ -253,6 +254,7 @@ async fn record_api_tracking_after_success(
&path,
response.status(),
authenticated.as_ref(),
client_marker,
)
.await;
response
+56 -3
View File
@@ -1,4 +1,4 @@
use axum::http::{Method, StatusCode};
use axum::http::{HeaderMap, Method, StatusCode};
#[cfg(not(test))]
use module_auth::AuthLoginMethod;
use module_runtime::RuntimeTrackingScopeKind;
@@ -8,6 +8,22 @@ use uuid::Uuid;
use crate::{auth::AuthenticatedAccessToken, request_context::RequestContext, state::AppState};
const AGC_CLIENT_MARKER_HEADER: &str = "x-genarrative-client";
const AGC_CLIENT_MARKER_VALUE: &str = "agc";
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum TrackingClientMarker {
Agc,
}
pub(crate) fn resolve_tracking_client_marker(headers: &HeaderMap) -> Option<TrackingClientMarker> {
headers
.get(AGC_CLIENT_MARKER_HEADER)
.and_then(|value| value.to_str().ok())
.filter(|value| value.trim() == AGC_CLIENT_MARKER_VALUE)
.map(|_| TrackingClientMarker::Agc)
}
/// 后端用户行为埋点入口统一走这里:写入失败只记录日志,不反向阻断主业务。
#[derive(Clone, Debug)]
pub struct TrackingEventDraft {
@@ -19,6 +35,7 @@ pub struct TrackingEventDraft {
pub profile_id: Option<String>,
pub module_key: Option<&'static str>,
pub metadata: Value,
pub(crate) client_marker: Option<TrackingClientMarker>,
}
impl TrackingEventDraft {
@@ -32,6 +49,7 @@ impl TrackingEventDraft {
profile_id: None,
module_key: Some(module_key),
metadata: json!({}),
client_marker: None,
}
}
@@ -110,6 +128,7 @@ pub async fn record_route_tracking_event_after_success(
path: &str,
status: StatusCode,
authenticated: Option<&AuthenticatedAccessToken>,
client_marker: Option<TrackingClientMarker>,
) {
if !status.is_success() {
return;
@@ -130,6 +149,7 @@ pub async fn record_route_tracking_event_after_success(
draft.scope_kind = spec.scope_kind;
draft.scope_id = scope_id;
draft.user_id = user_id;
draft.client_marker = client_marker;
draft.metadata = build_route_tracking_metadata(&spec, request_context, method, path, status);
if draft.user_id.is_some() {
draft.owner_user_id = draft.user_id.clone();
@@ -836,9 +856,42 @@ fn runtime_profile_beijing_day_key(occurred_at_micros: i64) -> i64 {
#[cfg(test)]
mod tests {
use axum::http::Method;
use axum::http::{HeaderMap, HeaderValue, Method};
use super::{is_route_tracking_excluded, normalize_route_path, resolve_route_tracking_spec};
use super::{
TrackingClientMarker, is_route_tracking_excluded, normalize_route_path,
resolve_route_tracking_spec, resolve_tracking_client_marker,
};
#[test]
fn tracking_client_marker_accepts_only_trimmed_lowercase_agc() {
let cases = [
(None, None),
(Some(""), None),
(Some("AGC"), None),
(Some("other"), None),
(Some(" agc "), Some(TrackingClientMarker::Agc)),
(Some("agc"), Some(TrackingClientMarker::Agc)),
];
for (value, expected) in cases {
let mut headers = HeaderMap::new();
if let Some(value) = value {
headers.insert(
"X-GENARRATIVE-CLIENT",
HeaderValue::from_str(value).expect("marker fixture should be valid"),
);
}
assert_eq!(resolve_tracking_client_marker(&headers), expected);
}
let mut invalid_headers = HeaderMap::new();
invalid_headers.insert(
"x-genarrative-client",
HeaderValue::from_bytes(&[0xff]).expect("invalid text fixture should be accepted"),
);
assert_eq!(resolve_tracking_client_marker(&invalid_headers), None);
}
#[test]
fn route_normalization_preserves_static_segments_and_replaces_ids() {