ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

codeburn sync 技术全解:从 OIDC/PKCE 认证到 OTLP 遥测推送的本地优先架构

codeburn sync 技术全解:从 OIDC/PKCE 认证到 OTLP 遥测推送的本地优先架构 【免费下载链接】codeburnFree, local tool to track AI coding token usage and cost across 37 tools and agents (Claude Code, Cursor, Codex, Gemini and more), by model, project, and task. npx codeburn项目地址https://gitcode.com/gh_mirrors/co/codeburn点击查看免费下载本文是codeburn同步子系统codeburn sync的开发者级技术指南以仓库 docs/sync/DEVELOPER.md 为骨架结合 src/sync/ 下的源码实现展开。你将掌握服务端发现协议codeburn-export.json、Authorization Code PKCE 认证与令牌刷新、跨平台凭据存储、确定性 OTLP span 编码、基于 sent-ledger 的精确去重与限流回退以及从纯函数单元测试到真实 Cognito 的无头浏览器 e2e 测试策略。codeburn是一款本地优先的 AI 编码用量追踪工具codeburn sync是其可选的团队级能力把本地解析出的 AI 使用遥测token、成本、项目、工具推送到团队自建的后端用于追踪采纳率、预算与 ROI。与codeburn的整体理念一致同步默认不发送任何数据——必须显式执行codeburn sync push且提示词与代码永远不会出现在载荷中。本文面向希望接入、自建后端或深度理解该子系统的开发者。架构总览一次sync push的完整旅程codeburn sync的架构可以用一张数据流图概括来自 docs/sync/DEVELOPER.mdDeveloper machine Remote backend ────────────────── ────────────── ~/.config/codeburn/sync.json (config) ~/.config/codeburn/.sync-token (credential) ~/.cache/codeburn/sync-ledger.json (sent-ledger) codeburn sync push │ ├─ Read config → baseUrl, clientId, issuer, tracesPath ├─ Read refresh token from OS store ├─ POST {issuer}/oauth2/token (refresh_token grant) → access_token ├─ Collect ParsedProviderCall[] for window ├─ Filter against sent-ledger (only unsent calls) ├─ Build OTLP/HTTP JSON payload ├─ POST {baseUrl}{tracesPath} with Bearer token ├─ On success → append deduplicationKeys to ledger └─ Update lastSync in config这条链路在源码中被拆分为职责单一的可测试模块全部位于 src/sync/模块职责discovery.ts拉取并校验服务端发现文档auth.tsOIDC 发现、PKCE 生成、回调服务器、令牌交换/刷新/撤销credentials.ts跨平台凭据存取keychain / secret-tool / DPAPI / 文件回退config.tssync.json非敏感配置的读写ledger.tssent-ledger 已发送去重账本的读写与剪枝otlp.tsParsedApiCall → OTLP/HTTP JSON 载荷构建push.ts收集/过滤/分批/发送/记账的推送编排cli.tssync setup / push / status / logout / reset五个子命令这种拆分的一个直接收益在 push.ts 的注释中写明flatten/filter/batch/send/ledger 管线被提取出来无需完整 CLI 调用即可单元测试。服务端发现协议codeburn-export.jsoncodeburn sync setup url的第一步是从远端拉取服务发现文档而不是直接猜测后端接口。协议定义如下GET {baseUrl}/.well-known/codeburn-export.json返回的 JSON 示例与字段契约继承自 docs/sync/DEVELOPER.md{ version: 1, issuer: https://cognito-idp.us-west-2.amazonaws.com/us-west-2_XXXX, client_id: 70e6sgst2ju6ff9dnrmv4l1tcb, scopes: [openid, email], traces_path: /v1/traces, max_batch_size: 1000 }字段是否必填默认值说明version否1客户端拒绝version 1的端点issuer是—OIDC issuer URL客户端随后获取{issuer}/.well-known/openid-configurationclient_id是—本次部署的 OAuth client IDscopes否[openid]请求的 scope若 IdP 支持offline_access会被动态追加traces_path否/v1/tracesOTLP POST 的目标路径max_batch_size否1000每次 HTTP 请求的最大 span 数源码解析器 discovery.ts 严格实现了这份契约version缺省视为1、超出则抛错提示升级issuer与client_id缺失直接拒绝scopes非字符串元素会被过滤traces_path与max_batch_size分别回退到/v1/traces与1000。解析前还会调用assertHttps强制远端端点必须为 HTTPS——该函数只放行https:与环回地址127.0.0.1、[::1]、localhost的明文http:理由是刷新令牌与 Bearer 令牌都会经由这些 URL 传输。为什么不直接代理.well-known/openid-configurationOIDC 规范要求发现文档中的issuer声明必须与其被获取的 URL 一致issuer mix-up 防御。如果从一个非 Cognito 域名代理 Cognito 的发现文档就会违反这一约束。codeburn-export.json的设计价值在于把指标端点metrics endpoint与身份提供方identity provider解耦。服务端可以任意选择 IdP客户端只需从这一份自定义发现文档中拿到issuer再走标准 OIDC 流程。值得补充的是客户端在获取 OIDC 配置时同样执行 issuer 一致性校验——auth.ts 中如果 OIDC 元数据的issuer声明与实际请求的 issuer 不符会直接抛出AuthError拒绝握手。OIDC 认证Authorization Code PKCE同步使用标准 OIDC与 Sign in with Google 相同的协议。团队管理员配置好 IdP开发者只需在首次sync setup时通过浏览器点一次登录。完整认证流程docs/sync/DEVELOPER.md 中给出了七步流程结合 auth.ts 的实现可以完整还原生成 PKCE 挑战客户端生成code_verifier32 字节随机数base64url 编码与code_challenge对 verifier 做 SHA-256 后 base64url。对应实现 generatePkce。启动回调服务器监听127.0.0.1:19876回退 19877、19878。对应 startCallbackServer。打开浏览器访问{authorization_endpoint}?response_typecodeclient_id...redirect_urihttp://127.0.0.1:{port}/callbackcode_challenge...code_challenge_methodS256state...scope...。对应 buildAuthUrl。用户在 IdP 登录IdP 重定向到http://127.0.0.1:{port}/callback?code...state...。回调服务器校验state提取code。回调处理中state不匹配返回 400 且不关闭服务器可能只是过期请求IdP 返回error参数则直接终止5 分钟无回调则超时失败。客户端 POST 令牌端点grant_typeauthorization_code、code、code_verifier、redirect_uri、client_id。对应 exchangeCode。IdP 返回access_tokenrefresh_token。固定端口的意义Cognito以及 Okta对回调 URL 做精确字符串比较临时端口ephemeral port无法通过校验。因此客户端注册了三个固定端口19876、19877、19878定义于 auth.ts按顺序尝试端口被占用则回退下一个。源码中的tryListen循环对EADDRINUSE逐个尝试且用resolvedPort守卫避免 late error 事件触发二次listencli.ts 会先await ready拿到实际绑定成功的端口再构造 redirect URI因为端口回退意味着第一个端口不保证可用。另外RFC 8252 建议回调地址使用127.0.0.1IP 字面量而非localhost以避免 IPv6::1解析问题——这正是本项目固定使用 IP 字面量的原因。令牌刷新与失效处理每次sync push时从 OS 凭据存储读取 refresh tokenPOST{token_endpoint}grant_typerefresh_token实现见 refreshToken无论服务器返回什么 refresh token 都直接存储——透明地处理令牌轮换rotation遇到invalid_grantHTTP 400/401→ 停止提示用户重新运行codeburn sync setup。Scope 解析基础 scope 来自codeburn-export.json的scopes字段仅当 OIDC 发现的scopes_supported中包含offline_access时才追加该 scope实现见 resolveScopes特别地Cognito 会以invalid_scope拒绝offline_access——但它无需该 scope 也会签发 refresh token所以这里按 IdP 能力动态处理是必须的。凭据存储OS 原生安全存储 文件回退refresh token 属于长期凭据必须放进系统级安全存储。credentials.ts 按平台选择实现createCredentialStore平台方法实现macOSKeychainsecurity add-generic-password/find-generic-passwordLinuxlibsecretsecret-tool store/secret-tool lookupWindowsDPAPIPowerShellConvertTo-SecureString/ConvertFrom-SecureString回退文件~/.config/codeburn/.sync-token权限0600项目刻意不引入原生模块keytar已归档停止维护而是 shell out 到操作系统自带的 CLI。几个实现细节值得注意macOS 路径通过execFileSync传参而非 shell 拼接避免注入注释坦言令牌仍会短暂出现在进程参数中security无非交互 stdin 模式这是当前能做到的最好方案。Linux 路径将令牌经stdin传给secret-tool绝不落入 argv创建 store 前还会用一个探针 key 探测 keyring 守护进程是否在运行——只有探测通过或确认 keyring 正常工作才使用 secret-tool否则回退文件存储。Windows 路径经环境变量把令牌传给 PowerShell再用 DPAPI 加密后落盘为.sync-token-dpapi同样0600。文件回退路径写入后还会显式chmodSync(0o600)因为writeFile的 mode 对已存在文件不一定生效。sync status会如实报告当前使用的是 keychain 还是文件回退Token storage: keychain不会把文件回退伪装成安全存储。OTLP 编码严格的 protobuf-JSON 映射同步载荷采用 OTLP/HTTP JSON 传输即ExportTraceServiceRequest的严格 protobuf-JSON 映射lowerCamelCase 字段、十六进制 ID、整数枚举。构建器位于 otlp.ts。确定性 span / trace ID重发即幂等span_id first 8 bytes of SHA-256(deduplicationKey) → hex (16 chars) trace_id first 16 bytes of SHA-256(sessionId) → hex (32 chars)对应源码 deriveSpanId 与 deriveTraceId。由于 ID 由内容派生而非随机生成重发的 span 逐字节一致服务端按 span ID 去重成为纵深防御defense-in-depth。Resource 属性{ resource: { attributes: [ { key: codeburn.device_id, value: { stringValue: SHA-256(hostnameusername)[:16] } }, { key: codeburn.coverage_through, value: { stringValue: 2026-08-24 } } ] } }codeburn.device_id是伪匿名的设备标识派生自hostname username的 SHA-256 前 16 位deriveDeviceId区分不同机器但绝不暴露主机名。codeburn.coverage_through可选本地语料完整覆盖到的 ISO 日期取自 daily-cache 水位线仅当一次完整本地解析固化该水位线complete与watermarkTrusted均置位时才打上。接收端必须把缺失解释为「覆盖情况未知」绝不能解释为「没有历史」。Span 属性{ attributes: [ { key: ai.provider, value: { stringValue: kiro } }, { key: ai.model, value: { stringValue: claude-sonnet-4-6 } }, { key: ai.input_tokens, value: { intValue: 12500 } }, { key: ai.output_tokens, value: { intValue: 3200 } }, { key: ai.cost_usd, value: { doubleValue: 0.085 } }, { key: ai.project, value: { stringValue: my-app } }, { key: ai.tools, value: { arrayValue: { values: [{ stringValue: Edit }] } } }, { key: ai.speed, value: { stringValue: standard } }, { key: ai.cost_estimated, value: { boolValue: true } }, { key: ai.work_unit_id, value: { stringValue: ff1b1358ef64c52f80e50e7ae47ca176 } }, { key: ai.session_role, value: { stringValue: child } }, { key: ai.lineage_evidence, value: { stringValue: provider-recorded } }, { key: ai.cache_read_tokens, value: { intValue: 800 } }, { key: ai.cache_write_tokens, value: { intValue: 200 } }, { key: ai.call_count, value: { intValue: 3 } }, { key: ai.session_duration_ms, value: { intValue: 61000 } }, { key: ai.subscription_covered, value: { boolValue: true } } ] }可选属性的精确语义只有「被证明」才发送从ai.work_unit_id往下的每个属性都是可选的只有在值被证明时才发送一个忽略未知属性的旧接收端不会损失任何语义。逐条解读继承自 docs/sync/DEVELOPER.md并有 otlp.ts 与 push.ts 的源码佐证ai.work_unit_id/ai.session_role/ai.lineage_evidence三者为整体要么全发要么全不发只对提供方在磁盘上持久记录了血缘lineage的会话发出issue #1140。ai.work_unit_id是 #1145 work-unit 解析器对根会话 ID 的deriveTraceId结果——与线上 trace id 采用同一种派生因此一个 work unit 的身份与线上已有的根 trace 一致。血缘永不推断无血缘记录的会话三者皆无父会话超出范围或链接歧义、成环的子会话同样 fail-closed三者皆无。push.ts 的 lineageContext 实现了这套解析。ai.cache_read_tokens/ai.cache_write_tokens提供方记录的缓存 token 数与ai.input_tokens保持可计费一致——缓存读取采用展示层的max(cacheReadInputTokens, cachedInputTokens)约定统一 Anthropic 与 OpenAI 两套词汇见 otlp.ts 的Math.max实现。各自仅在非零时发送。ai.call_count该 span 所属会话在同步窗口内贡献的 usage span 数。ai.session_duration_ms该会话「最后一个减去第一个」的提供方记录事件时间任一端缺失或乱序时省略push.ts。ai.subscription_coveredplan/proxy-path 机制的判定结果——配置的 plan 覆盖了调用的提供方、或会话的提供方记录 cwd 位于配置的代理路径之下时为true两者均被排除时为false机制无法判定无 plan 匹配且无 cwd 可查时省略该属性。对应 subscriptionCoveredFor。ai.output_tokens是可计费输出总量对「推理 token 单独计费」的提供方如 Anthropic 词汇codeburn 会把推理量计入该字段响应计数已含推理的提供方保持不变。实现见 otlp.ts 对billableOutputTokens的调用与展示层 #1115/#1116 的计费口径一致。ai.project可选仅当 codeburn 能从提供方记录的绝对工作目录派生出一个安全的 basename 时才出现归属attributionspan 只从归一化的git.repo派生它仅有 PR 证据时省略。接收端必须把缺失项目归入「未归属」不得要求该字段。当同一 trace 的 usage 与 attribution span 携带不同的安全 basename 时例如 fork 检出目录名与上游仓库不同attribution span 的归一化git.repobasename 对项目聚合具备最终权威——usage cwd basename 只是临时标签接收端不得把两者计为两个独立项目。值得一提的还有ai.cost_estimated源码中当提供方为kiro字符级估算或输入 token 为 0 时置为trueotlp.ts明确告知接收端成本是估算而非提供方直报。出站标识符清洗sanitizerOTLP 载荷里的 provider/model/tool 标识符全部经过 sanitizeIdentifier 及配套函数清洗拒绝路径形态、URL 形态、凭据形态、绝对路径、控制字符与超长值isEmailOrCredentialShapedotlp.ts还拦截邮箱形、ghp_、sk-、AKIA…等密钥前缀。project basename 同样受 projectBasenameFromWorkingDirectory 与isTrustedAbsoluteWorkingDirectory见 path-privacy.ts双重把关——伪造的、从提示词文本解析出的路径永远不会成为可信 provenance也永远不会产生出站ai.project。Sent-Ledger客户端去重的事实来源sent-ledger 位于~/.cache/codeburn/sync-ledger.json是客户端去重的唯一事实来源。格式{ key: string, ts: string }对象的 JSON 数组。Push 逻辑收集窗口内全部调用 → 减去 ledger 中已有条目 → 发送剩余部分 → 成功后把deduplicationKey追加进 ledger。剪枝每次 push 时删除早于 6 个月的条目appendToLedger 中SIX_MONTHS_MS常量。为什么不用时间戳水位线时间戳水位线会静默跳过晚到的调用长会话、提供方事后更新行记录的场景。ledger 是精确的以 key 为单位精确去重晚到的调用下个窗口照发。源码层面的两个加固值得一提原子写入ledger 先写临时文件再renameSyncledger.ts崩溃不会损坏账本——损坏的账本会被读成空最坏情况是整个窗口重发而非数据丢失。旧路径迁移在共享缓存解析器引入之前ledger 曾位于 XDG_CACHE_HOME 下readLedger 会把旧位置作为一次性迁移源合并进新位置保留旧 key 防止升级后重复上传同时确保CODEBURN_CACHE_DIR权威时绝不从不相关的 XDG 树导入。部分成功partial successOTLP 响应体可能携带partial_success.rejected_spans。由于 OTLP不指明具体哪些 span 被拒客户端对部分拒绝的批次不记入 ledger——整个批次在下一次 push 时重试。这是安全的span ID 由 deduplication key 确定性派生按 span ID 存储的服务端会把重发 span 视为幂等 upsert。实现见 sendBatchesCore其中还处理了 proto3 int64 的字符串编码问题Number()转换避免字符串拼接。限流429与失败语义一次 push 会跑到完成——没有常规的单次上限仅 50,000 调用的安全阀MAX_PER_PUSH见 push.ts。服务端限流是预期的刹车机制收到 HTTP 429 时客户端遵守Retry-Afterdelta-seconds 或 HTTP-date 皆可解析见 parseRetryAfterMs单次等待上限 120 秒头部缺失时默认等待 5 秒同一批次最多连续重试 3 次若服务端仍在限流push 停止剩余未记账调用在下次 push 时继续收到 401 或 5xx 时 push 立即停止同样「下次 push 续传」。服务端契约后端必须实现两件事GET {baseUrl}/.well-known/codeburn-export.json—— 返回发现文档公开、无需认证POST {baseUrl}{traces_path}—— 接受携带 Bearer token 的 OTLP/HTTP JSON并且校验 JWT由配置的 IdP 签发从 token 的sub声明派生开发者身份接受最早不超过 6 个月的startTimeUnixNano返回标准 OTLP 响应体。载荷中不包含任何 PII——服务端只能从已认证的 token 推导身份。注意同步还支持插件扩展点plugin socket插件只能携带其在 manifest 中声明的属性CORE_SYNC_ATTRIBUTE_KEYS 与 filterPluginAttributes 构成出站守卫——插件不能伪造成本、token 或血缘字段。Git Attribution--attribution选填能力codeburn sync push --attribution会额外发送codeburn yield在本地计算的会话→提交关联让后端无需 git hooks 即可把 AI 用量与 git 活动关联。其细节在 docs/sync/README.md 有完整定义载荷构建实现位于 otlp.tscodeburn.session.attribution每会话一条携带ai.project来自归一化git.repo的 basename、git.repo归一化 origin 远端剥离凭据与端口、git.pr_links会话捕获的 PR URL、git.commit_count。codeburn.commit每个被归属的提交一条携带git.sha、git.in_main、git.was_reverted。归属是推断的时间窗口关联与codeburn yield同一启发式resource 属性codeburn.attribution_methodology: timestamp-window明确标记了这一性质。状态迁移提交合入 main、被 revert会在后续 push 自动重发——接收端应按(git.repo, git.sha)upsert 提交、按traceIdupsert 会话 span最新状态胜出。会话 span 的 dedup key 编码了可变状态sessionAttributionKey因此「同一事实发一次、状态变化重发更新后的事实」。当提交迁移到窗口更紧的后续解析会话时落败会话以git.commit_count: 0重发即撤回求和时不会重复计数。出站边界非常严格只发带网络origin远端的仓库提交、只发 cwd provenance 可信的会话本地分组标签、提供方存储路径、提示词文本、纯本地仓库、file://远端与 Windows 文件系统路径永远不作为仓库身份出站。PR 链接仅由 scheme host path 重建丢弃 userinfo、query、fragment限https、/org/repo/pull/N形态、长度有界、每会话最多 20 条仓库身份通过严格的主机名/路径白名单ext::…、codecommit::…等 transport-helper 远端直接拒绝而非解析。不带--attribution时以上一概不发。测试策略从纯函数到真实 Cognito 的四层防线docs/sync/DEVELOPER.md 定义了清晰的测试分层测试文件位于 tests/单元测试tests/sync.test.ts26 个用例覆盖纯函数——发现文档解析、PKCE 生成、认证 URL 构造、scope 解析、回调服务器、配置读写。无网络、无浏览器。Mock IdP e2etests/sync-e2e.test.ts6 个用例基于 localhost mock IdP 服务器完整演练认证往返、令牌刷新、轮换、撤销——完全离线可在 CI 运行。无头浏览器 e2etests/sync-headless-e2e.test.ts1 个用例Playwright headless Chromium 直连真实 Cognito证明真实的浏览器 PKCE 流程含 Cognito Hosted UI 表单提交与 localhost 重定向可用。仅限开发者本地运行需要已部署的测试后端独立 CDK stack、已确认密码的 Cognito 用户、环境变量CODEBURN_SYNC_URL/CODEBURN_SYNC_EMAIL/CODEBURN_SYNC_PASSWORD、已安装的 Playwright ChromiumPLAYWRIGHT_BROWSERS_PATH。未设置环境变量时默认跳过永不进 CI。测试 CDK stack仓库外codeburn-sync-backend/为无头 e2e 准备的最小 AWS 后端——Cognito User PoolPKCE、固定回调端口、带 JWT authorizer 的 HTTP API、Discovery Lambda提供codeburn-export.json、Ingest Lambda把 OTLP span 记入 CloudWatch。部署命令npx cdk deploy --profile andklee-dev空闲成本约 $0/月按请求付费。这是测试夹具而非生产参考——任何 OIDC 提供方 接受 OTLP 的端点都满足服务端契约。测试还覆盖了推送管线的更多边界tests/sync-attribution.test.ts、tests/sync-push.test.ts、tests/sync-consent.test.ts、tests/sync-wire-privacy.test.ts、tests/sync-project-provenance.test.ts等见 tests/ 目录分别验证归属载荷、推送语义、同意机制与线上隐私边界。隐私边界与 FAQ 要点从 docs/sync/README.md 提炼几条关键契约绝不上传提示词prompts、代码内容/差异/路径、bash 命令可能含密钥、姓名/邮箱身份由服务端从登录 token 派生。没有覆盖这些边界的开关——隐私是结构性的不是可配置项。唯一增量式 opt-in 是--attribution远端、提交 SHA、PR URL——绝无代码或提示词。同步不会自动运行未来版本可能提供机会式推送每次codeburn report后但始终是显式行为。重复推送同一窗口是安全的sent-ledger 去重不会产生重复。Copilot 会话因本地对账在会话期持续变化push.ts 的RECONCILE_SETTLE_MS会静默 24 小时后再一次性推送最终形态--dry-run会报告被 hold 的调用数。同理Copilot 会话的输入/缓存只以「rollup 汇总」或「按请求拆分」其中一种形态出站push.ts且该选择一旦同步便永久固化防止接收端同时持有两种形态导致重复计数。快速上手回顾# 一次性配置打开浏览器登录 codeburn sync setup https://metrics.your-team.com # 推送最近 7 天的用量默认窗口 codeburn sync push # 更大窗口 / 预览 / 附带 git 归属 codeburn sync push --since 30d codeburn sync push --dry-run codeburn sync push --attribution # 查看配置与认证状态 codeburn sync status # 注销删除凭据并在 IdP 撤销令牌 codeburn sync logout # 清空已发送账本强制窗口内全部重发后端迁移或怀疑丢数据时用 codeburn sync reset --confirmcodeburn sync reset --confirm会清空本地 ledger 并以新口径重发一切——但只有当接收端副本也同时清空时才有意义否则会产生它本要避免的数据翻倍。相关 CLI 注册见 src/sync/cli.ts。小结codeburn sync是一个「本地优先、显式推送、隐私结构性受控」的团队遥测通道自定义发现文档解耦指标端点与 IdP标准 OIDC PKCE 提供认证OS 原生存储保管凭据确定性 OTLP span 编码让重发天然幂等sent-ledger 提供精确到 key 的去重429 限流与部分成功语义保证任意规模的 push 都能跑到完成。如果你想自建后端只需实现一个公开发现文档与一个接受 Bearer token 的 OTLP 端点即可——任何 OIDC 提供方都能接入。赞分享【免费下载链接】codeburnFree, local tool to track AI coding token usage and cost across 37 tools and agents (Claude Code, Cursor, Codex, Gemini and more), by model, project, and task. npx codeburn项目地址https://gitcode.com/gh_mirrors/co/codeburn点击查看免费下载相关推荐codeburn sync 团队同步指南将本地 AI 用量遥测安全推送到共享 OTLP 后端codeburn sync 团队同步指南将本地 AI 用量遥测安全推送到共享 OTLP 后端 codeburn sync 是 codeburn 的可选团队功能CodeBurn 版本演进与技术架构解析从本地 AI 用量追踪器到全平台观测工具CodeBurn 版本演进与技术架构解析从本地 AI 用量追踪器到全平台观测工具 本篇文章基于 CodeBurn 仓库根目录的 CHANGELOG.md htAspire 中 OpenTelemetry 遥测架构全解从 Service Defaults 配置到 Dashboard OTLP 接收端Aspire 中 OpenTelemetry 遥测架构全解从 Service Defaults 配置到 Dashboard OTLP 接收端 Aspire 的云原生后端微服务可观测性开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表