diff --git a/.env.example b/.env.example index ac31e1dd9..60dc47b7e 100644 --- a/.env.example +++ b/.env.example @@ -237,14 +237,10 @@ GENARRATIVE_ENABLE_IMAGE_EDITOR_AGENT_SIDEBAR="false" # 官网客户端下载检测渠道:dev、release 或自定义渠道;修改后重启 API 服务。 # Windows/macOS 是系统维度,不填写 dev-win/dev-mac。 +# 客户端埋点也复用该渠道:dev 对应 https://dev.genarrative.world,release 对应 https://www.genarrative.world。 +# 埋点不接受其它渠道;本地 dev 且 GENARRATIVE_ENV 为 development(默认)/test/container 时允许 loopback 地址及可变端口。 GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL="dev" -# 客户端埋点接收绑定的公开 origin,由 API Server 运行时读取;修改后重启服务。 -# 必须与客户端登录地址一致,不带 /api、路径或尾部斜杠;未配置/非法时上传接口返回 503。 -# 本地端口按实际启动结果填写(端口漂移后需同步),localhost 与 127.0.0.1 不可混用。 -# dev 使用 https://dev.genarrative.world;release 使用 https://www.genarrative.world。 -GENARRATIVE_AGC_ANALYTICS_ORIGIN="http://127.0.0.1:8082" - # Optional: official VikingDB credentials for regenerating build-tag similarities # with the Python embedding script. The script auto-loads `.env.local` and uses # the fixed `bge-large-zh` embedding model. diff --git a/deploy/container/api-server.env.example b/deploy/container/api-server.env.example index 55f051ba0..e8b929f38 100644 --- a/deploy/container/api-server.env.example +++ b/deploy/container/api-server.env.example @@ -5,11 +5,10 @@ GENARRATIVE_ENV=container GENARRATIVE_API_HOST=0.0.0.0 GENARRATIVE_API_PORT=8082 -# 客户端埋点接收绑定的公开 origin,由 API Server 运行时读取;修改后重启服务。 -# 必须与客户端登录地址一致,不带 /api、路径或尾部斜杠;未配置/非法时上传接口返回 503。 -# 以下为 compose 默认宿主机入口;更改映射端口或接入域名时同步修改,不填容器内部地址。 -# dev 使用 https://dev.genarrative.world;release 使用 https://www.genarrative.world。 -GENARRATIVE_AGC_ANALYTICS_ORIGIN=http://127.0.0.1:18080 +# 官网客户端下载与客户端埋点共用部署渠道;修改后重启 API Server。 +# dev 对应 https://dev.genarrative.world;container + dev 额外允许 loopback 地址及可变映射端口。 +# release 对应 https://www.genarrative.world,且不接受 loopback 地址;其它渠道不接收埋点。 +GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL=dev GENARRATIVE_API_LOG=info,tower_http=info GENARRATIVE_API_LISTEN_BACKLOG=1024 GENARRATIVE_API_WORKER_THREADS=4 diff --git a/deploy/env/api-server.env.example b/deploy/env/api-server.env.example index 3cd99970f..25ec4bbc4 100644 --- a/deploy/env/api-server.env.example +++ b/deploy/env/api-server.env.example @@ -4,10 +4,10 @@ GENARRATIVE_ENV=production GENARRATIVE_API_HOST=127.0.0.1 GENARRATIVE_API_PORT=8082 -# 客户端埋点接收绑定的公开 origin,由 API Server 运行时读取;修改后重启服务。 -# 必须与客户端登录地址一致,不带 /api、路径或尾部斜杠;未配置/非法时上传接口返回 503。 -# 以下为 release;dev 部署改为 https://dev.genarrative.world。 -GENARRATIVE_AGC_ANALYTICS_ORIGIN=https://www.genarrative.world +# 官网客户端下载与客户端埋点共用部署渠道;修改后重启 API Server。 +# release 对应 https://www.genarrative.world;dev 部署改为 dev,对应 https://dev.genarrative.world。 +# production 环境的埋点不接受 loopback 地址;其它渠道不接收埋点。 +GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL=release GENARRATIVE_API_LOG=info,tower_http=info GENARRATIVE_API_LISTEN_BACKLOG=1024 GENARRATIVE_API_WORKER_THREADS=4 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index f1b42991f..728f977cb 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -53,7 +53,7 @@ - 当前合同唯一维护入口为[客户端本地埋点与主站入库契约](../../technical/【技术方案】客户端本地埋点与主站入库契约-2026-09-21.md),原始需求作为仓库内历史来源保存;后续里程碑规范与实施计划放在 `docs/project-memory/plans/`。 - 本地采集阶段已验收明文 JSONL 持久化;当前仍不做加密。一个项目对应一个目标,事件按业务节点采集,5 分钟封存,7 天或 20 MiB 清理;上传失败也受保留上限约束。 -- 当前上传实现已完成隔离环境验收,证据见同一主规范第 13 节:每 15 分钟上传匹配当前账号与平台的封存批次,新增一张客户端事件私有表、批次原子入库与幂等确认、成功清理及独立后台明细栏目;失败静默留待下周期重试。真实客户端文件、HTTP、数据库与后台查询已关联同一事件验证,浏览器列表/筛选/详情通过;未部署生产。保持原 12 类事件和原采集边界。上线须配置 `GENARRATIVE_AGC_ANALYTICS_ORIGIN`,按数据库、API/后台、客户端顺序发布。 +- 当前上传实现已完成隔离环境验收,证据见同一主规范第 13 节:每 15 分钟上传匹配当前账号与平台的封存批次,新增一张客户端事件私有表、批次原子入库与幂等确认、成功清理及独立后台明细栏目;失败静默留待下周期重试。真实客户端文件、HTTP、数据库与后台查询已关联同一事件验证,浏览器列表/筛选/详情通过;未部署生产。保持原 12 类事件和原采集边界。埋点接收复用 `GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL` 的 dev/release 官方站点映射;仅 dev 渠道且 `GENARRATIVE_ENV` 为 development/test/container 时额外接受 loopback origin,无独立埋点环境变量。按数据库、API/后台、客户端顺序发布。 - 已按技术负责人授权开始实施:合同与本地队列、会话窗口与项目接入、策划阶段成果、首次提交及两类 Agent run 已实现并经独立审查;定向测试、生产编译和前序 GUI 启停证据统一见主规范第 12 节。不得宣称完整产品采集已上线。 ## 2026-09-22 引用输入区改为宿主注入引用 provider,选择器面板与输入区分离 diff --git a/docs/technical/【技术方案】客户端本地埋点与主站入库契约-2026-09-21.md b/docs/technical/【技术方案】客户端本地埋点与主站入库契约-2026-09-21.md index 411158929..74b077acd 100644 --- a/docs/technical/【技术方案】客户端本地埋点与主站入库契约-2026-09-21.md +++ b/docs/technical/【技术方案】客户端本地埋点与主站入库契约-2026-09-21.md @@ -1,6 +1,6 @@ # 客户端本地埋点与主站入库契约 -Version: 0.31 +Version: 0.32 Status: 本地采集及上传、入库、成功清理、后台查询已实现并完成本地隔离环境验收;未部署生产 Date: 2026-09-21 需求来源:[Game Agent 埋点设计原始方案](./【需求来源】GameAgent埋点设计原始方案-2026-09-05.md);原始方案与后续已确认决策有差异时,以本文为准。 @@ -609,9 +609,9 @@ session.json 是本地恢复元数据,不是待上传事件;事件文件不 - 一次请求只发一个已封存批次,1 至 500 条。事件数据沿用 1 MiB 上限,JSON 请求体上限 2 MiB,容纳数组及 envelope 开销;服务端不依赖 Content-Length 自报大小。 - 批次与所有事件的 user_id 必须相同且等于鉴权主体;匿名、未知 origin 批次不上传,不补认领到后来登录的账号。 -- destination_origin 必须匹配当前服务部署的可信公开 origin 配置,不使用未经信任的 Host 头作为判据。 -- 接收端配置 `GENARRATIVE_AGC_ANALYTICS_ORIGIN` 为规范 origin(无路径和尾部斜杠):正式站 `https://www.genarrative.world`,测试站 `https://dev.genarrative.world`,本地联调填写客户端实际连接的 loopback origin。未配置/非法配置时接口返回 503,不猜测默认站点;允许 HTTPS 和本地 loopback HTTP。 -- 该变量在 API Server 运行时注入,修改后重启服务;客户端与管理后台无需新增埋点构建变量。配置示例见根目录 `.env.example`(本地默认 `http://127.0.0.1:8082`)、`deploy/env/api-server.env.example`(release,并注明 dev 值)与 `deploy/container/api-server.env.example`(compose 默认宿主机入口 `http://127.0.0.1:18080`)。实际端口、映射或域名变化时同步修改,始终与客户端登录地址一致。 +- 接收端复用 API Server 已有的 `GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL`,不再单独配置埋点 origin:`dev` 只对应 `https://dev.genarrative.world`,`release` 只对应 `https://www.genarrative.world`;渠道未设置时沿用现有默认 `dev`,空值或其它渠道返回 503。destination_origin 与渠道不匹配时返回 403,不使用客户端请求的 Host / Forwarded 头推断部署渠道。 +- 本地联调例外:仅 `dev` 渠道且已有 `GENARRATIVE_ENV` 为 `development`(现有默认)、`test` 或 `container` 时,额外接受规范的 HTTP(S) loopback origin(localhost、IPv4/IPv6 回环地址,无路径、查询串、凭据或尾部斜杠)。端口可变以兼容本地漂移和容器映射;服务端不再按本地端口区分实例,客户端仍按精确登录 origin 与 user_id 隔离批次。`production` 环境或 `release` 渠道均不接受此例外。 +- 渠道与环境在 API Server 运行时读取,修改后重启服务;客户端与管理后台无需新增埋点构建变量。配置示例见根目录 `.env.example`(本地 dev)、`deploy/env/api-server.env.example`(release,dev 部署改为 dev)与 `deploy/container/api-server.env.example`(container + dev)。 - 复用现有事件版本、枚举、字段长度与必填 nullable 校验;批内重复 event_id、未知字段或非法事件整批拒绝。 - 本接口不进入普通成功路由 tracking 映射,不为每次上传另生成主站产品事件。 diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index d9e05e6f5..15dd8bfba 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -571,6 +571,12 @@ curl -fsS --max-time 5 http://127.0.0.1/api/editor/showcase/resources >/dev/null 角色动画源帧 PUT、透明帧 PUT 和最终帧 HEAD 使用 `AppState` 内同一个 OSS HTTP Client/连接池,并受进程级 8 路 OSS permit 保护;BgFilter、阿里云抠图和本地处理不占用该 permit。每个 OSS attempt 最多 3 次(首次 + 2 次重试),退避为 250ms、500ms;只重试 timeout、无 HTTP 响应传输错误、OSS PutObject 的 `400 + RequestTimeout`、PUT 400 错误体读取失败(未解析出 `Code`,按 timeout/transport 归类)、408、429 和 500–599。动作帧 PUT 收到 400 时只读取最多 16 KiB OSS 错误 XML,提取 `Code` 和 `RequestId`;`oss_request_id` 优先使用响应头 `x-oss-request-id`,XML 字段只作回退。错误体读取超时/断流不再按确定性 400 处理:已解析出的 `Code` 优先生效;未解析出 `Code` 时按读取失败原因置 `timeout`/`transport` 并重试,message 追加「错误响应体读取失败」。日志字段包括 `frame_index`、`object_key`、`operation=source_put|final_put|final_head`、`attempt`、`max_attempts`、`retryable`、`will_retry`、`retry_delay_ms`、`permit_wait_ms`、`timeout`、`connect`、`transport`、`oss_code`、`oss_request_id`、`status` 和 `elapsed_ms`。`请求 OSS 失败` 时,`timeout/connect/transport=true` 表示传输类失败;`status=400, oss_code=RequestTimeout, timeout=true`、`status=429` 或 `500–599` 表示暂时性失败,PUT 的 `status=400`、`oss_code` 为空且 `timeout=true` 或 `transport=true`(message 含「错误响应体读取失败」)同样是暂时性失败。除 `RequestTimeout` 和该错误体读取失败两类例外外,其他 400、401/403/404、配置、URL 和签名错误是确定性失败,不会重试。最终帧 HEAD 失败只会重试 HEAD,不会重复 PUT;如果任一帧最终失败,确认整段动作已排空已启动 Future,并检查任务按现有契约退款且没有发布缺帧动画。 +### AGC 客户端埋点接收渠道 + +埋点接收复用 API Server 运行时的 `GENARRATIVE_CLIENT_DOWNLOAD_CHANNEL`:测试站设置 `dev`,匹配 `https://dev.genarrative.world`;正式站必须设置 `release`,匹配 `https://www.genarrative.world`。修改后重启 API Server。未设置时沿用现有默认 `dev`;空值或其它渠道的埋点上传返回 503。 + +本地联调使用 `dev`,且 `GENARRATIVE_ENV` 为 `development`(默认)、`test` 或 `container` 时,允许规范 HTTP(S) loopback 地址及可变端口,无需配置独立埋点变量。客户端登录时使用实际 API 入口,容器使用宿主机映射入口。线上部署设置 `GENARRATIVE_ENV=production`,不接受 loopback 例外;详细合同见[客户端本地埋点与主站入库契约](./technical/【技术方案】客户端本地埋点与主站入库契约-2026-09-21.md)第 13 节。 + ### AGC 项目快照上传目标 后台“项目工程”(`/admin/#project-snapshots`)按项目列出远端快照,默认只看本部署渠道,顶部“渠道”选择框可切换远端已存在的其它渠道;列表按游标分页(每页 20/50/100,上一页复用已取得的游标,远端不给总数所以只显示当前页)。完整快照提供“下载完整工程”,按原始目录返回 ZIP;未完成同步的项目暂不可下载,旧清单缺少完整性声明时显示“完整性未知”,只能“下载已存文件”。“用户”列与“素材查询”同口径展示昵称与陶泥号,并可点开用户详情;不要直接把 OSS 的 `files/{size}-{digest}/` 目录下载当成工程。 diff --git a/server-rs/crates/api-server/src/agc_analytics.rs b/server-rs/crates/api-server/src/agc_analytics.rs index b69542f28..234a0a4c3 100644 --- a/server-rs/crates/api-server/src/agc_analytics.rs +++ b/server-rs/crates/api-server/src/agc_analytics.rs @@ -14,6 +14,7 @@ use crate::{ admin::{AuthenticatedAdmin, require_admin_auth}, api_response::json_success_body, auth::{AuthenticatedAccessToken, require_bearer_auth}, + config::AppConfig, http_error::AppError, request_context::RequestContext, state::AppState, @@ -65,11 +66,7 @@ async fn upload_batch( let batch: AgcAnalyticsBatch = serde_json::from_value(raw).map_err(|_| { AppError::from_status(StatusCode::BAD_REQUEST).with_message("客户端埋点字段不符合合同") })?; - validate_subject_and_origin( - &batch, - auth.claims().user_id(), - &state.config.agc_analytics_origin, - )?; + validate_subject_and_origin(&batch, auth.claims().user_id(), &state.config)?; module_runtime::agc_analytics::validate_agc_analytics_batch(&batch).map_err(|error| { let status = if error == "events_too_large" { StatusCode::PAYLOAD_TOO_LARGE @@ -106,29 +103,38 @@ async fn list_events( fn validate_subject_and_origin( batch: &AgcAnalyticsBatch, user_id: &str, - expected_origin: &str, + config: &AppConfig, ) -> Result<(), AppError> { - // 公开地址必须来自部署配置;客户端 body 和 Host 头均不是配置来源。 - let origin = url::Url::parse(expected_origin).ok().filter(|url| { - let loopback = url.host_str().is_some_and(|host| { - host == "localhost" - || host - .trim_matches(['[', ']']) - .parse::() - .is_ok_and(|ip| ip.is_loopback()) + // 复用部署渠道,不使用客户端 body 或 Host / Forwarded 头推断服务器所属平台。 + let expected_origin = match config.client_download_channel.as_str() { + "dev" => "https://dev.genarrative.world", + "release" => "https://www.genarrative.world", + _ => { + return Err(AppError::from_status(StatusCode::SERVICE_UNAVAILABLE) + .with_message("客户端埋点接收渠道无效")); + } + }; + // 本地开发允许回环地址及可变端口,兼容端口漂移和容器映射;线上部署不启用此例外。 + let local_origin = config.client_download_channel == "dev" + && matches!( + config.environment.as_str(), + "development" | "test" | "container" + ) + && url::Url::parse(&batch.destination_origin).is_ok_and(|url| { + matches!(url.scheme(), "http" | "https") + && url.host_str().is_some_and(|host| { + host == "localhost" + || host + .trim_matches(['[', ']']) + .parse::() + .is_ok_and(|ip| ip.is_loopback()) + }) + && url.username().is_empty() + && url.password().is_none() + && url.origin().ascii_serialization() == batch.destination_origin }); - (url.scheme() == "https" || (url.scheme() == "http" && loopback)) - && url.host_str().is_some() - && url.username().is_empty() - && url.password().is_none() - && url.origin().ascii_serialization() == expected_origin - }); - if origin.is_none() { - return Err(AppError::from_status(StatusCode::SERVICE_UNAVAILABLE) - .with_message("客户端埋点接收地址尚未配置")); - } if batch.user_id != user_id - || batch.destination_origin != expected_origin + || (batch.destination_origin != expected_origin && !local_origin) || batch .events .iter() @@ -173,41 +179,117 @@ mod tests { #[test] fn agc_analytics_subject_and_deployment_are_both_required() { - let batch = batch(); - assert!(validate_subject_and_origin(&batch, "user-a", &batch.destination_origin).is_ok()); - for (user, origin, expected) in [ + for (channel, origin, other_origin) in [ ( - "user-b", + "dev", "https://dev.genarrative.world", - StatusCode::FORBIDDEN, - ), - ( - "user-a", "https://www.genarrative.world", - StatusCode::FORBIDDEN, - ), - ("user-a", "", StatusCode::SERVICE_UNAVAILABLE), - ( - "user-a", - "https://dev.genarrative.world/path", - StatusCode::SERVICE_UNAVAILABLE, ), ( - "user-a", - "http://dev.genarrative.world", - StatusCode::SERVICE_UNAVAILABLE, + "release", + "https://www.genarrative.world", + "https://dev.genarrative.world", ), ] { + let config = AppConfig { + environment: "production".into(), + client_download_channel: channel.into(), + ..AppConfig::default() + }; + let mut batch = batch(); + batch.destination_origin = origin.into(); + assert!(validate_subject_and_origin(&batch, "user-a", &config).is_ok()); assert_eq!( - validate_subject_and_origin(&batch, user, origin) + validate_subject_and_origin(&batch, "user-b", &config) .unwrap_err() .status_code(), - expected + StatusCode::FORBIDDEN + ); + for rejected in [ + other_origin, + "http://127.0.0.1:8082", + "https://custom.example", + ] { + batch.destination_origin = rejected.into(); + assert_eq!( + validate_subject_and_origin(&batch, "user-a", &config) + .unwrap_err() + .status_code(), + StatusCode::FORBIDDEN, + "{channel}: {rejected}" + ); + } + } + for channel in ["", "qa-2026", "dev-win"] { + let config = AppConfig { + client_download_channel: channel.into(), + ..AppConfig::default() + }; + assert_eq!( + validate_subject_and_origin(&batch(), "user-a", &config) + .unwrap_err() + .status_code(), + StatusCode::SERVICE_UNAVAILABLE + ); + } + } + + #[test] + fn agc_analytics_loopback_supports_local_ports_only_in_dev_environments() { + let mut batch = batch(); + for environment in ["development", "test", "container", "production", "staging"] { + for channel in ["dev", "release"] { + let config = AppConfig { + environment: environment.into(), + client_download_channel: channel.into(), + ..AppConfig::default() + }; + let allowed = + channel == "dev" && matches!(environment, "development" | "test" | "container"); + for origin in [ + "http://127.0.0.1:8082", + "http://localhost:18080", + "http://[::1]:19001", + "https://localhost:8443", + ] { + batch.destination_origin = origin.into(); + assert_eq!( + validate_subject_and_origin(&batch, "user-a", &config).is_ok(), + allowed, + "{environment}/{channel}: {origin}" + ); + } + } + } + } + + #[test] + fn agc_analytics_local_exception_rejects_noncanonical_and_nonloopback_origins() { + let config = AppConfig::default(); + let mut batch = batch(); + for origin in [ + "http://192.168.1.2:8082", + "http://localhost.example:8082", + "http://0.0.0.0:8082", + "http://localhost:8082/", + "http://localhost:8082/api", + "http://localhost:8082?x=1", + "http://localhost:8082#fragment", + "http://user:password@localhost:8082", + "ftp://localhost:8082", + "https://dev.genarrative.world/", + "http://dev.genarrative.world", + "", + ] { + batch.destination_origin = origin.into(); + assert_eq!( + validate_subject_and_origin(&batch, "user-a", &config) + .unwrap_err() + .status_code(), + StatusCode::FORBIDDEN, + "{origin}" ); } - let mut local = batch; - local.destination_origin = "http://127.0.0.1:8082".into(); - assert!(validate_subject_and_origin(&local, "user-a", &local.destination_origin).is_ok()); } #[test] diff --git a/server-rs/crates/api-server/src/app.rs b/server-rs/crates/api-server/src/app.rs index e4b0b2d7d..201f2327c 100644 --- a/server-rs/crates/api-server/src/app.rs +++ b/server-rs/crates/api-server/src/app.rs @@ -282,7 +282,8 @@ mod tests { spacetime_server_url: db_url.into(), spacetime_database: connection["database"].as_str().unwrap().into(), spacetime_token: Some(connection["token"].as_str().unwrap().into()), - agc_analytics_origin: origin.clone(), + environment: "test".into(), + client_download_channel: "dev".into(), admin_username: Some("analytics-smoke-owner".into()), admin_password: Some("analytics-smoke-fixture-password".into()), ..AppConfig::default() @@ -418,7 +419,8 @@ mod tests { #[tokio::test] async fn agc_analytics_upload_enforces_auth_identity_contract_and_body_limit() { let state = AppState::new(AppConfig { - agc_analytics_origin: "https://dev.genarrative.world".into(), + environment: "production".into(), + client_download_channel: "dev".into(), admin_username: Some("analytics-smoke-owner".into()), admin_password: Some("analytics-smoke-fixture-password".into()), ..AppConfig::default() @@ -444,12 +446,18 @@ mod tests { wrong_user["user_id"] = "other-user".into(); let mut wrong_origin = payload.clone(); wrong_origin["destination_origin"] = "https://www.genarrative.world".into(); + let mut local_origin = payload.clone(); + local_origin["destination_origin"] = "http://127.0.0.1:8082".into(); + let mut wrong_event_user = payload.clone(); + wrong_event_user["events"][0]["user_id"] = "other-user".into(); let mut bad_contract = payload.clone(); bad_contract["events"][0]["properties"]["prompt"] = "not collected".into(); for (body, authenticated, expected) in [ (payload.to_string(), false, StatusCode::UNAUTHORIZED), (wrong_user.to_string(), true, StatusCode::FORBIDDEN), (wrong_origin.to_string(), true, StatusCode::FORBIDDEN), + (local_origin.to_string(), true, StatusCode::FORBIDDEN), + (wrong_event_user.to_string(), true, StatusCode::FORBIDDEN), (bad_contract.to_string(), true, StatusCode::BAD_REQUEST), ("{".into(), true, StatusCode::BAD_REQUEST), ( @@ -461,6 +469,8 @@ mod tests { let mut request = Request::builder() .method("POST") .uri("/api/agc/analytics/batches") + .header("host", "www.genarrative.world") + .header("x-forwarded-host", "www.genarrative.world") .header("content-type", "application/json") .header("x-genarrative-response-envelope", "1"); if authenticated { diff --git a/server-rs/crates/api-server/src/config.rs b/server-rs/crates/api-server/src/config.rs index b75504dbe..1b71b2cda 100644 --- a/server-rs/crates/api-server/src/config.rs +++ b/server-rs/crates/api-server/src/config.rs @@ -92,8 +92,6 @@ pub struct AppConfig { pub editor_bgfilter_circuit_cooldown: Duration, pub image_editor_agent_sidebar_enabled: bool, pub client_download_channel: String, - /// 客户端埋点接收所绑定的公开 origin;未配置时拒绝上传。 - pub agc_analytics_origin: String, /// AGC 项目快照的部署渠道:上传与后台默认查询都按它分区。 pub project_snapshot_channel: String, pub log_filter: String, @@ -399,7 +397,6 @@ impl Default for AppConfig { ), image_editor_agent_sidebar_enabled: false, client_download_channel: "dev".to_string(), - agc_analytics_origin: String::new(), project_snapshot_channel: "dev".to_string(), log_filter: "info,tower_http=info".to_string(), otel_enabled: false, @@ -723,9 +720,6 @@ impl AppConfig { // 显式空值或非法值也保留,由下载入口失败关闭,不能悄悄改读 dev。 config.client_download_channel = channel.trim().to_string(); } - if let Ok(origin) = std::env::var("GENARRATIVE_AGC_ANALYTICS_ORIGIN") { - config.agc_analytics_origin = origin.trim().to_string(); - } // 快照渠道缺省沿用同一个部署渠道(本部署的客户端渠道),显式配置优先; // 显式空值或非法值同样保留,由快照入口失败关闭。 config.project_snapshot_channel = config.client_download_channel.clone();