资讯动态

FastGPT Workflow 输入文件上下文(WorkflowFileContext)统一设计:URL 权限模型、SSRF 防护与多消费者读取架构

发布时间:2026/9/11 6:29:01 来源:尧图企业网站定制
FastGPT Workflow 输入文件上下文WorkflowFileContext统一设计URL 权限模型、SSRF 防护与多消费者读取架构【免费下载链接】FastGPTFastGPT is a knowledge-based platform built on the LLMs, offers a comprehensive suite of out-of-the-box capabilities such as data processing, RAG retrieval, and visual AI workflow orchestration, letting you easily develop and deploy complex question-answering systems without the need for extensive setup or configuration.项目地址: https://gitcode.com/GitHub_Trending/fa/FastGPT导读FastGPT 的 Workflow 此前用 URL 字符串贯穿 AI Chat、ToolCall、Agent、ReadFiles 与 Sandbox同一个 URL 同时承担模型访问地址、服务端下载地址、文件类型推断与去重标识导致相对短链、外部 URL 与私有对象 URL 容易被错误转换甚至越权读取。本文基于 workflow-file-context.md 设计文档结合packages/service/core/workflow/utils/fileContext.ts等落地实现完整讲解请求级只读WorkflowFileContext的数据模型、入口登记流程、SSRF 防护读取规则、各消费者迁移方案与测试验收标准。读完本文你将掌握 FastGPT Workflow 文件链路中私有 key 鉴权、signed URL 仅用于模型访问、服务端读取与模型 URL 分离三套核心机制以及 Child/Loop/Parallel 派生上下文与文件额度限制的具体实现。1. 背景为什么需要统一的文件上下文Workflow 输入文件长期以来以 URL 字符串贯穿多个模块AI Chat 的多模态输入、ToolCall 与 Agent 的read_files、ReadFiles 节点、以及 Sandbox 文件注入。URL 被同时用作模型访问地址modelUrl服务端下载地址文件类型推断依据文件去重标识。这种多职责复用带来一系列安全与正确性问题设计中明确要避免以下行为根据requestOrigin、FE_DOMAIN或 URL 前缀推断文件权限将用户输入的相对 URL 直接交给内部 Axios 读取让动态 URL 绕过 HTTP(S)、SSRF 与文件大小限制把模型 URL 当作私有对象的服务端读取来源。设计方案的核心结论是引入请求级、只读的WorkflowFileContext根 query 在聊天持久化和dispatchWorkFlow之前统一裁剪dispatch 使用运行态副本处理 query、histories 和全局文件变量并允许交互恢复入口及 Plugin Input 登记fileSelect文件。下游消费者不能登记文件Context 未命中的绝对 HTTP(S) URL 一律按节点运行中生成的外链处理。2. 本期范围2.1 包含当前 query 中的文件histories 中 Human 消息携带的文件Workflow 全局文件变量、交互表单和 Plugin Input 的fileSelect文件AI Chat、ToolCall、Agent、ReadFiles 和 Sandbox 对上述文件的消费私有 chat object key 的归属校验外部绝对 HTTP(S) URL 的 SSRF 防护读取请求级文件数量和单文件大小限制。2.2 不包含Workflow 运行期间由普通节点、Plugin 或 Dataset Search 新产生的文件登记Workflow 文件变量的存储结构迁移跨请求持久化WorkflowFileRef修改聊天数据库中的现有文件格式通过 signed alias 反推业务归属Context clone、分支注册和合并。3. 核心原则3.1 key 是私有文件的可信来源私有文件必须携带key。Workflow 入口使用parseChatFileS3Key或isAuthorizedChatFileS3Key校验 key 是否属于根 Workflowkey - sourceType - sourceId - uid - chatId - 与根 Workflow 鉴权上下文逐项匹配在源码 key.ts 中可以看到具体实现parseChatFileS3Key将 key 按chat/${sourceType}/${sourceId}/${uid}/${chatId}/${filename}结构解析legacy App 格式chat/${appId}/${uid}/${chatId}/${filename}也会归一化为sourceTypeappisAuthorizedChatFileS3Key则逐项比对sourceType、sourceId、chatId与uid。匹配时使用根dispatchWorkFlow的runningAppInfo、uid和chatId——Child Workflow 不得使用 child app 的身份重新解释父 Workflow 的输入文件。归属匹配失败时直接拒绝绝不能把失败的私有 key 降级成外部 URL。3.2 signed URL 只用于模型访问signed URL 不参与权限判断。私有 key 校验通过后每个 key 在一次 Workflow 中最多签发一次、两小时有效的绝对 HTTP(S) URL并在请求内复用。源码 fileContext.ts 中的常量WORKFLOW_FILE_URL_EXPIRED_HOURS 2与previewUrlCacheMapstring, Promisestring正是这一机制的落地签名函数按 key 缓存同一 key 在 query/history 中只触发一次签发。如果文件域名配置无法生成绝对 HTTP(S) URL入口应报配置错误不能生成相对 URLgetPreviewUrl中会校验签名结果必须是绝对 HTTP(S) URL。3.3 服务端读取与模型 URL 分离私有 chat objectFastGPT 服务端通过 S3 client 直接读取createS3FileSource外部 HTTP(S) URLFastGPT 服务端通过带 SSRF 防护的 Axios 读取createExternalHttpFileSource模型只接收modelUrl相对 URL、protocol-relative URL 和非 HTTP(S) 协议一律不能进入读取链路。isAbsoluteHttpUrlfileContext.ts使用正则加new URL()双重校验只接受http:与https:协议。3.4 Context 只读只有 Workflow 输入适配器可以通过独立的WorkflowFileRegistrar登记文件包括根入口的 query/history/全局变量、交互恢复入口和 Plugin Input 的fileSelect。节点、模型工具参数和下游消费者只能查询已登记 Ref不能在查询失败时自动注册新外链。未命中的绝对 HTTP(S) URL 可作为节点或模型在运行中生成的外链消费但不写入 Context相对 URL 直接拒绝。Context 对外不暴露register、clone或文件枚举接口。4. 数据模型设计文档给出了完整的 TypeScript 类型定义在 fileContext.ts 中原样落地export type WorkflowFileSource | { type: chatObject; objectKey: string; } | { type: externalHttp; url: string; }; export type WorkflowFileRef { name: string; type: ChatFileTypeEnum; modelUrl: string; source: WorkflowFileSource; }; export type WorkflowFileLimits { maxFileAmount: number; maxBytesPerFile: number; }; export type WorkflowFileContext { resolve: (url: string) WorkflowFileRef | undefined; resolveInputFile: (file: RawChatFileValue) WorkflowFileRef | undefined; resolveChatFile: (url: string) UserChatItemFileItemType | undefined; getIdentity: (url: string) string | undefined; read: (target: string | WorkflowFileRef) PromiseWorkflowFileReadResult; derive: (files: WorkflowFileInput[]) WorkflowFileContext; limits: WorkflowFileLimits; };Context 内部至少维护两个映射byRuntimeUrl本轮modelUrl到 RefbyIdentity私有 key 或完整外部 URL 到 Ref用于去重外部 URL 的去重 identity 会去掉 hash 片段见getExternalIdentity。模型和节点之间只传递 URL。Context 不生成或解析 file idresolveInputFile仅供受控输入适配器根据原始文件对象中的 key 或 URL 查找已登记 Ref。登记能力通过独立的WorkflowFileRegistrarregisterInputFile只传给受控输入适配器不属于下游只读WorkflowFileContext接口——源码中二者的类型定义就是分开的。5. Workflow 入口处理流程入口处理顺序完成 Workflow/Chat 鉴权 - 计算根 Workflow 文件 scope - 根据团队套餐和系统配置计算 Context maxFileAmount/maxBytesPerFile - 根据应用 chatConfig 计算 queryMaxFileAmount - 在 preChatRound 前按 queryMaxFileAmount 保留根 query 中前 N 个文件项 - 持久化和 dispatch 复用同一份过滤后 query - 文件 Context 和历史预览转换返回新的 query/histories不回写调用方数据 - 扫描 query histories 全局文件变量 - 校验 chat object key 归属 - 校验外链必须为绝对 HTTP(S) - 对私有 key 按请求缓存签发两小时 modelUrl - 按 key/完整外链 URL 去重并建立只读 Context - 将 FileContext 挂载到 WorkflowContext - 开始节点调度实现上fileLimits.ts 提供getWorkflowFileLimits团队planStatus.standard.maxUploadFileCount/maxUploadFileSize优先、系统global.feConfigs兜底、getWorkflowFileAmountLimits与prepareWorkflowFileQuery返回裁剪后的 query 及请求级额度fileContext.ts 的prepareWorkflowFileContext完成 scope 校验、URL 签发、去重与 Context 构建返回{ fileContext, fileRegistrar, getPreviewUrl, query, histories }——query 和 histories 都是新构造的运行态副本不回写调用方数据。在 dispatch/index.ts 中prepareWorkflowFileContext接收runningAppInfo.sourceType/sourceId、data.uid、chatId组成 scope并将maxFileAmount、maxBytesPerFile从调用方传入随后通过 context.ts 的runWithContext基于 Node 的AsyncLocalStorage把fileContext和fileRegistrar挂载到 WorkflowContext 上供整条调用链读取。这也印证了设计文档第 8 节的兼容策略core/ai和core/chat不得反向引用core/workflowContext 通过 Workflow 自己的 AsyncLocalStorage 传播并由 Workflow 调用方显式传给 Chat/Sandbox。5.1 QuerychatConfig.fileSelectConfig.maxFiles只表示根 query 的上传文件数量上限未配置时回退到用户可用的文件数量上限getModuleFileAmountLimit会取min(用户额度, 模块额度)。超额文件项在入口静默丢弃非文件输入和剩余输入的原始顺序保持不变——filterWorkflowQueryFiles正是按这一语义实现Chat Completions 中的图片 Data URL 不受模型 Base64 配置影响入口始终先按用户数量和单文件大小额度完成校验并上传到私有 S3再以携带key的临时 URL 进入 Workflow。模型是否重新读取该文件并转换为 Base64仍由模型配置决定带 key完整校验sourceType/sourceId/uid/chatId失败则拒绝本轮 WorkflowassertAuthorizedKey抛UserError无 key只允许绝对 HTTP(S) URL并继续应用fileUrlWhitelistvalidateFileUrlDomain其他 URL拒绝本轮 Workflow。5.2 Histories带 key使用根 Workflow scope 校验并签发 URL对于不通过鉴权的历史文件registerInputFile会记录 warn 日志并跳过而不是中断请求无 key 的绝对 HTTP(S) URL登记为外链无 key 的相对或非法 URL不登记使用该文件时返回不可用不能交给内部 Axios无关历史文件不可用时不能阻塞整轮 WorkflowprepareFile返回undefined时该 value 会被过滤掉。入口只登记来源和签发 URL不下载所有历史文件正文正文继续按消费者需求懒加载即第 6 节的读取规则。5.3 全局变量、交互和 Plugin fileSelect全局文件变量只在WorkflowVariableState.create初始化时登记后续节点set()不得扩展 Context文件变量由节点set()更新时只接受绝对 HTTP(S) URL已存在于当前或父变量状态映射中的 runtime URL 复用原始 store metadata保留私有 key未知 URL 才按外链存储全局文件变量、交互 Form Input 和 Plugin Input 都属于具有独立模块配额的文件输入显式配置maxFiles时使用该值历史配置未提供时模块额度默认为 5最终有效额度统一为min(模块额度, WorkflowFileContext.limits.maxFileAmount)文件变量的有效额度在WorkflowVariableState.create登记变量时归一化后续更新只读取确定的config.maxFiles交互恢复入口只登记当前表单配置为fileSelect且位于有效额度内的值Plugin Input 只登记节点配置为fileSelect且位于有效额度内的字段值同时兼容文件对象数组和 URL 字符串数组带 key 的值必须按根 Workflow scope 校验并按私有对象处理无 key 的绝对 HTTP(S) URL 登记为外链其他值拒绝。6. 读取规则WorkflowFileContext的getSource对应文档中的read根据 Ref 的 source 类型分派读取器整体逻辑在 fileContext.ts。6.1 私有对象chatObject通过已校验的 bucket/objectKey 使用 S3 client先读取 metadatagetObjectMetadata校验contentLength为合法安全整数contentLength超过maxBytesPerFile时拒绝下载流downloadObject再次按累计字节数限制返回 Buffer、filename优先 metadata 中的originFilename和 contentType。私有对象读取完全不经过 HTTP其 filename 通过decodeMetadataFilename解码media 类型解析还会用到imageParsePrefix基于 objectKey 后缀生成的-parsed前缀。6.2 外部 URL外部 URL 始终使用带 SSRF 防护的 Axios只允许http:和https:普通上传文件入口保留fileUrlWhitelistvalidateFileUrlDomainread_files的动态模型 URL 不受上传文件域名白名单限制SSRF Axios 负责 DNS、metadata 地址和逐跳重定向检查先检查Content-Length流式下载时再次限制累计字节数禁止使用pickOutboundAxios或任何内部相对路径 Axios。SSRF 防护在 axios.ts 的addSSRFInterceptor中实现请求发出前通过isInternalAddress/isInternalResolvedIP做地址预检并强制关闭底层自动重定向在 response interceptor 中逐跳解析Location、复用同一套 SSRF 策略校验后再发起下一跳请求从而避免 302 跳转到内网时绕过检查。Raw-text 缓存只能在文件引用完成授权后查询已登记文件使用 Context identity未登记动态外链必须先通过绝对 HTTP(S) 和内部地址检查。普通文件入口还需通过域名白名单read_files允许读取运行中生成的任意公网 URL二者都不能通过缓存命中绕过各自的出站策略。6.3 大小限制WorkflowFileContext.limits.maxFileAmount表示当前用户可用的文件数量上限优先级团队套餐planStatus.standard.maxUploadFileCount→ 系统global.feConfigs.uploadFileMaxAmount见 fileLimits.ts 的getWorkflowFileLimits。应用 Query 未配置maxFiles时直接使用该用户额度Form、Plugin 和全局文件变量则使用各自的模块额度并与该用户额度取较小值。AI Chat、ToolCall、Agent、ReadFiles 和模型read_files等没有独立字段配置的消费者必须直接读取当前WorkflowFileContext.limits.maxFileAmount不得回退到应用 query 上传配置或硬编码数量——getWorkflowFileMaxAmount()封装了这一点context.ts。dispatchWorkFlow接受可选的maxBytesPerFile字节。调用入口根据团队套餐和系统限制计算后传入未传入时使用系统getFileMaxSize()。已登记外链和未登记动态外链复用同一个 SSRF-safe reader并使用同一请求级maxBytesPerFile。注意该限制约束的是FastGPT 服务端读取。模型提供商直接拉取外部modelUrl时不经过 FastGPT 下载链路因此不计入服务端读取限制。7. 消费者7.1 AI Chat已登记文档由 Context 统一读取未登记绝对 HTTP(S) URL 由 SSRF Axios 读取图片、音频、视频默认使用modelUrlLLM 层不感知 Workflow ContextshouldLoadMediaAsBase64为真时统一使用 AI 层已有的下载与 Base64 转换能力否则把绝对modelUrl直接交给模型。7.2 ToolCall 和 Agent read_files模型通过read_files({ urls: string[] })直接提交 model URL不再生成、暴露或映射 file idURL 命中 Context 时直读已授权私有对象未命中的绝对 HTTP(S) URL 作为运行中生成的动态外链读取不要求预先枚举也不写入 Context动态外链不受上传文件域名白名单限制但必须通过 HTTP(S)、SSRF、重定向和文件大小检查相对 URL 和非 HTTP(S) URL 返回文件不可用ToolCall 和 Agent 共用同一个批量读取执行器返回相同的 JSON 与nodeResponse文档解析继续复用core/chat/fileContext.ts。实现上ai/readFiles.ts 的dispatchWorkflowReadFiles调用getFileContentByUrl并显式传入fileContext: getWorkflowFileContext()、validateExternalUrlDomain: false豁免域名白名单同时以固定 5 并发batchRun(..., 5)批量执行且每次只读取前maxFileAmount个文件单文件失败不会中断同批其他文件。而 tools/readFiles.ts 的dispatchReadFiles则通过getWorkflowFileMaxAmount()获取统一额度再交给parseFileContentFromUrls处理。7.3 ReadFiles 节点ReadFiles 的字符串 URL 输入保持兼容命中 Context 时走 Context reader未命中的绝对 HTTP(S) URL 走 SSRF Axios相对 URL 不可读取。节点输出仍保持文件名 正文的text与rawResponse结构。7.4 SandboxWorkflow 显式向 Agent 和 ToolCall 的 Sandbox 文件注入步骤传入组合读取函数命中 WorkflowFileContext 时读取 Buffer未命中的绝对 URL 交给带 SSRF 防护的 Sandbox reader。Sandbox 通用模块不引用 Workflow也不使用requestOrigin、相对 URL 或内部 Axios。Sandbox 通过sandbox_get_file_url导出的文件属于临时产物对象按两小时 TTL 管理源码 preview.ts 中SANDBOX_PREVIEW_SESSION_TTL_SECONDS 2 * 60 * 60返回的访问链接有效期同样为两小时。导出文件不写入聊天消息的fileRefs也不随聊天记录持久化或自动续期超过有效期后文件及链接不可继续使用属于预期行为。7.5 Child、Loop 和 ParallelLoop 和 Parallel 共享当前 Context。Child 在父 Context 下先过滤实际传入的 query、history、全局文件变量和fileSelect输入再根据保留文件派生最小 Context。选择优先级依次为当前 query、显式变量/节点输入、从新到旧的 history输出仍保持原消息顺序。Child 派生的关键规则源码见 context.ts 的runWithDerivedWorkflowFileContext与 fileContext.ts 的createDerivedWorkflowFileContext实际传入的文件对象或普通字符串 URL 命中父 Context 时继承可信 Ref 和原modelUrl不重新签名父 Context 未命中的合法绝对 HTTP(S) URL 在 Child 内登记为外链普通字符串只能通过完整 URL 精确命中父 Context不得根据签名 URL 结构反解私有 keyChild 不能通过派生 Context 枚举未实际传入的父文件携带未知私有 key 的文件对象不得通过冲突 URL 降级命中父 Ref源码直接抛UserError(Child workflow private file is not registered in the parent context)被额度裁掉的文件必须同时从 Child 的实际 payload 删除不能只从派生 Context 中移除后继续作为普通绝对 URL 消费Child 交互新增文件仍通过父 registrar 完成根 scope 鉴权登记过程串行执行成功后同步加入当前派生 Context。父子运行边界同时隔离输入引用Child 收到的 query、history、消息 value 和对象型文件输入均由遍历构造的新对象组成cloneChildChatInputs/cloneChildFile不直接复用 Parent payload 的可变对象。Loop 和 Parallel 属于同一 Workflow 的容器运行继续共享只读 query/history并单独隔离会被执行过程修改的节点、边和变量状态。8. 兼容策略文件类型、名称和 key 等运行态元数据统一由 WorkflowFileContext 提供不再维护额外的 query URL MapWorkflowFileContext 复用 Workflow 自己的 AsyncLocalStorage并由 Workflow 调用方显式传给 Chat/Sandboxcore/ai和core/chat不得反向引用core/workflow字符串 URL 运行值继续保留keyless 历史绝对 URL 按外链处理Context 未命中的绝对 HTTP(S) URL 按节点生成外链处理但不登记keyless 历史相对 URL 不再通过内部 Axios 读取Agent 的normalizeAgentServerFileUrl和internalUrl过渡实现已删除Workflow 以外的普通聊天文件链路暂不纳入本期 Context但共享读取函数不能再接受相对 URLchat/fileContext.ts 中的FileReadContext即可被普通聊天以外的上层业务显式注入。9. 测试要求与验收要点9.1 Contextquery/history 私有 key 归属校验成功sourceType/sourceId/uid/chatId任一不匹配时拒绝同一个 key 在 query/history 中只签发一次并只创建一个 Ref外部绝对 HTTP(S) URL 可登记相对 URL、protocol-relative、file:、data:、ws:被拒绝或跳过普通文件入口的fileUrlWhitelist继续生效read_files动态模型 URL 明确豁免resolve不会自动登记未知绝对 URL全局文件变量只在初始化时登记节点更新不会扩展 Context节点更新文件变量时只接受绝对 HTTP(S) URL已知内部文件 URL 保留私有 key未知 URL 按外链存储交互fileSelect的私有 key 和外链均可登记Plugin InputfileSelect的文件对象和 URL 字符串均可登记或复用当前 Context。9.2 读取私有对象通过 S3 client 读取外链通过 SSRF Axios 读取私有对象和外链均执行 header/metadata 与流式双重大小限制私有对象读取不调用 HTTP相对 URL 不进入任何 Axiosread_files可读取 Context 未登记的公网绝对 URL但仍拒绝内网地址并执行大小限制。9.3 消费者AI Chat、ToolCall、Agent、ReadFiles 优先使用同一 Context并支持安全外链回退Agent history 和当前输入都保留绝对 modelUrlToolCall 和 Agent 的read_files只接收 URL不存在 file id 或已知文件枚举映射ToolCall 和 Agent 的读取结果统一为 JSON文件节点展示字段保持一致read_files 的动态公网 URL 可读相对 URL、非 HTTP(S) URL 和内网地址不可读Agent 和 ToolCall Sandbox 注入使用 Context/SSRF 外链组合 readerChild 只在派生 Context 中继承实际传入且通过文件 key 或完整 URL 命中的父 Ref同一私有文件不重复签名作为普通字符串显式传入 Child 的 URL 命中父 Context 时继承父 Ref未命中时按外链登记Child 新绝对 URL 登记为外链未知私有 key 不得继承Child query/history 和派生 Context 使用同一筛选结果超额绝对 URL 不会残留在实际 payloadChild query/history/value/file 对象不与 Parent 共享可变引用Child 并发交互登记不会突破 Context 文件数量上限不可用的无关 history 文件不阻塞本轮请求。10. 实现状态与相关代码设计文档第 10 节的 TODO 清单已全部勾选完成包括新增只读WorkflowFileContext与入口准备函数为dispatchWorkFlow增加maxBytesPerFile并创建挂载 ContextparseUrlToFileType优先读取 Context 文件元数据统一core/chat/fileContext.ts的 Workflow 文件读取AI Chat 多模态 Base64 与 Workflow Context 解耦迁移 ToolCall、Agent、ReadFiles 与 Sandbox 文件注入登记全局文件变量、交互和 Plugin Input 的fileSelectSSRF 外链回退Child 派生最小文件 Context持久化前统一裁剪根 query删除 AgentrequestOrigin/internalUrl过渡逻辑补齐测试。全量测试通过注Appcopy.test.ts在并发运行时超时单独复跑通过。相关核心代码packages/service/core/workflow/utils/context.tsAsyncLocalStorage 挂载、getWorkflowFileMaxAmount、Child 派生运行packages/service/core/workflow/utils/fileContext.tsprepareWorkflowFileContext、createDerivedWorkflowFileContext、数据模型packages/service/core/workflow/utils/fileLimits.ts文件数量/大小额度计算与 query 裁剪packages/service/core/workflow/dispatch/index.tsdispatchWorkFlow入口挂载 Contextpackages/service/core/chat/fileContext.ts统一文件读取核心getFileContentByUrl、parseFileContentFromUrls、FileReadContextpackages/service/core/workflow/dispatch/ai/chat/chatMessages.tsAI Chat 消息文件重写packages/service/core/workflow/dispatch/ai/readFiles.tsAgent/ToolCall 批量read_files执行器packages/service/core/workflow/dispatch/ai/agent/adapter/userContext.tsAgent 用户上下文文件注入packages/service/core/ai/sandbox/application/file.tsSandbox 文件注入packages/service/core/workflow/dispatch/tools/readFiles.tsReadFiles 节点packages/service/common/api/axios.tsSSRF 防护 Axios逐跳重定向检查packages/service/common/s3/sources/chat/key.tschat S3 key 解析与归属鉴权结语FastGPT 的 WorkflowFileContext 将URL 单一职责重构为key 鉴权、signed URL 仅供模型、服务端读取走 S3/SSRF Axios 双通道的清晰分层私有文件以 S3 key 为唯一可信身份外部文件以绝对 HTTP(S) URL 为边界所有读取路径统一受文件数量与大小双重限额约束。这套设计同时解决了越权读取、SSRF、相对 URL 误用和历史文件阻塞整轮运行等问题并通过 Child 派生上下文保持了父子 Workflow 之间的最小权限与引用隔离可作为多消费者、请求级文件权限模型的参考实现。【免费下载链接】FastGPTFastGPT is a knowledge-based platform built on the LLMs, offers a comprehensive suite of out-of-the-box capabilities such as data processing, RAG retrieval, and visual AI workflow orchestration, letting you easily develop and deploy complex question-answering systems without the need for extensive setup or configuration.项目地址: https://gitcode.com/GitHub_Trending/fa/FastGPT创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价