ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

node-fetch 完整指南:在 Node.js 中引入标准 Fetch API

node-fetch 完整指南:在 Node.js 中引入标准 Fetch API 后端【免费下载链接】node-fetchA light-weight module that brings the Fetch API to Node.js项目地址https://gitcode.com/gh_mirrors/no/node-fetch点击查看免费下载node-fetch 是一个轻量级模块把浏览器原生的window.fetchAPI 移植到 Node.js 运行时让你可以用同构的 Promise 语法发起 HTTP(S) 请求、流式读写请求与响应体、自动解码 gzip/deflate/brotli 压缩内容并借助AbortSignal取消请求。本文以仓库 README 为主体结合源码实现与测试用例系统讲解从安装配置、日常请求到高级流式用法与完整 API 的实战方案读完即可在 Node.js 项目中可靠地落地 fetch 开发。设计动机与定位为什么不用 XMLHttpRequest 兼容层社区早期通过把浏览器的 XMLHttpRequest 移植到 Node.js 来实现 fetch polyfill而 node-fetch 选择了一条更直接的路线从 Node.js 原生http模块直接构建出兼容window.fetch的 API跳过 XHR 这层中间人因此代码量最小、语义最贴近 Fetch 标准。需要 isomorphic同构用法的场景可以搭配 isomorphic-unfetch服务端导出 node-fetch、客户端导出 whatwg-fetch或 cross-fetch 使用。核心特性按 README 的 Features 一节node-fetch 具备以下关键能力与window.fetchAPI 保持一致尽量贴合 WHATWG Fetch 规范与 Stream 规范同时对已知差异做显式取舍并在文档中说明原生 Promise 与 async/await 支持无回调嵌套请求与响应体均使用 Node.js 原生 Readable 流正确解码 gzip/deflate/brotli 内容编码并将res.text()、res.json()等字符串输出自动转为 UTF-8提供重定向次数限制、响应体大小限制、显式错误类型docs/ERROR-HANDLING.md等实用的扩展能力。从 package.json 可以看到当前仓库版本为3.1.1包入口为 src/index.js类型定义随包发布在 types/index.d.ts依赖仅data-uri-to-buffer、fetch-blob、formdata-polyfill三个轻量库。与客户端 fetch 的已知差异node-fetch 刻意维护了一份与浏览器window.fetch的差异清单用于在规范实现与 Node.js 现实之间做取舍3.x 版本的差异见 docs/v3-LIMITS.md2.x 版本的差异见 docs/v2-LIMITS.md。如果你发现某个window.fetch提供的能力在 node-fetch 中缺失可以在项目仓库提交 issue也欢迎直接提交 Pull Request。安装与模块加载环境要求与安装当前稳定版3.x要求Node.js 12.20.0 及以上。更精确的引擎约束记录在 package.json 的engines字段^12.20.0 || ^14.13.1 || 16.0.0。npm install node-fetchES ModulesESMv3 起 node-fetch 是ESM-only模块package.json 中type: module标准导入方式import fetch from node-fetch;CommonJSv3 无法用require()直接导入。如果你暂时无法切换到 ESM有两个选择使用 v2v2 保持 CommonJS 兼容且关键 bug 修复仍会持续发布到 v2 线npm install node-fetch2从 CommonJS 异步加载 v3利用动态import()包装一个兼容函数// mod.cjs const fetch (...args) import(node-fetch).then(({default: fetch}) fetch(...args));注入全局对象不想在每个文件里显式导入可以写一个 polyfill 模块把fetch及其相关类挂到globalThis// fetch-polyfill.js import fetch, { Blob, blobFrom, blobFromSync, File, fileFrom, fileFromSync, FormData, Headers, Request, Response, } from node-fetch if (!globalThis.fetch) { globalThis.fetch fetch globalThis.Headers Headers globalThis.Request Request globalThis.Response Response } // index.js import ./fetch-polyfill // ...注意这里通过if (!globalThis.fetch)做存在性保护避免覆盖 Node.js 新版本自带的原生 fetch。从源码看这些导出在 src/index.js 中统一 re-export。版本升级从 2.x 升级到 3.x见 docs/v3-UPGRADE-GUIDE.md从 1.x 升级到 2.x见 docs/v2-UPGRADE-GUIDE.md。下文所有用法示例均以3.x为准。常见用法抓取纯文本或 HTMLimport fetch from node-fetch; const response await fetch(https://github.com/); const body await response.text(); console.log(body);response.text()内部通过consumeBody把整个流累积为 Buffer再交给TextDecoder解码为 UTF-8 字符串见 src/body.js。抓取 JSONimport fetch from node-fetch; const response await fetch(https://api.github.com/users/github); const data await response.json(); console.log(data);json()是text()JSON.parse的语法糖见 src/body.js如果响应体不是合法 JSON 会抛出解析异常。简单 POSTimport fetch from node-fetch; const response await fetch(https://httpbin.org/post, {method: POST, body: a1}); const data await response.json(); console.log(data);POST JSONimport fetch from node-fetch; const body {a: 1}; const response await fetch(https://httpbin.org/post, { method: post, body: JSON.stringify(body), headers: {Content-Type: application/json} }); const data await response.json(); console.log(data);这里显式设置Content-Type: application/json。需要留意的是请求方法名不区分大小写——在 src/request.js 中delete/get/head/options/post/put会被自动规范化为大写。POST 表单参数URLSearchParams自 Node.js v10.0.0 起就是全局对象可直接使用。import fetch from node-fetch; const params new URLSearchParams(); params.append(a, 1); const response await fetch(https://httpbin.org/post, {method: POST, body: params}); const data await response.json(); console.log(data);注意只有当你传入的body是URLSearchParams实例时Content-Type才会被自动设为x-www-form-urlencoded。这个逻辑在 src/body.js 的extractContentType中实现且带;charsetUTF-8后缀。异常处理重要3xx-5xx 状态码响应不是异常必须放在then()/ 结果判断里处理详见下一节。用try/catch包裹能捕获的所有异常包括来自 Node 核心库的错误如网络错误、以及FetchError实例的操作类错误。完整错误体系见 docs/ERROR-HANDLING.md。import fetch from node-fetch; try { await fetch(https://domain.invalid/); } catch (error) { console.log(error); }从源码看fetch内部在请求失败时会把底层错误包装成FetchErrortype: system并 reject见 src/index.js。处理客户端与服务端错误4xx/5xx实践中常写一个辅助函数检查响应状态import fetch from node-fetch; class HTTPResponseError extends Error { constructor(response) { super(HTTP Error Response: ${response.status} ${response.statusText}); this.response response; } } const checkStatus response { if (response.ok) { // response.status 200 response.status 300 return response; } else { throw new HTTPResponseError(response); } } const response await fetch(https://httpbin.org/status/400); try { checkStatus(response); } catch (error) { console.error(error); const errorBody await error.response.text(); console.error(Error body: ${errorBody}); }response.ok是一个便捷属性其实现为status 200 status 300见 src/response.js。处理 Cookienode-fetch默认不存储 Cookie。你可以通过手动读取响应头中的Set-Cookie、再在后续请求中构造Cookie请求头来实现会话维持具体读取方法见下文提取 Set-Cookie 头。高级用法流式下载Node 风格是尽量使用流。可以把res.body直接 pipe 到另一个流。下面的例子使用stream.pipeline挂接流错误处理并等待下载完成import {createWriteStream} from node:fs; import {pipeline} from node:stream; import {promisify} from node:util import fetch from node-fetch; const streamPipeline promisify(pipeline); const response await fetch(https://github.githubassets.com/images/modules/logos_page/Octocat.png); if (!response.ok) throw new Error(unexpected response ${response.statusText}); await streamPipeline(response.body, createWriteStream(./octocat.png));在 Node.js 14 中还可以用 async iterator 读取body但要注意捕获错误——响应运行时间越长越可能遇到错误import fetch from node-fetch; const response await fetch(https://httpbin.org/stream/3); try { for await (const chunk of response.body) { console.dir(JSON.parse(chunk.toString())); } } catch (err) { console.error(err.stack); }在 Node.js 12 中 async iterator 也可用但流的 async iteration 直到 Node.js 14 才成熟需要额外处理流错误并等待响应完全关闭import fetch from node-fetch; const read async body { let error; body.on(error, err { error err; }); for await (const chunk of body) { console.dir(JSON.parse(chunk.toString())); } return new Promise((resolve, reject) { body.on(close, () { error ? reject(error) : resolve(); }); }); }; try { const response await fetch(https://httpbin.org/stream/3); await read(response.body); } catch (err) { console.error(err.stack); }访问响应头与元数据import fetch from node-fetch; const response await fetch(https://github.com/); console.log(response.ok); console.log(response.status); console.log(response.statusText); console.log(response.headers.raw()); console.log(response.headers.get(content-type));Headers.raw()是 node-fetch 独有的非规范方法返回Recordstring, string[]见 src/headers.js能拿到同一 header 名的全部原始值数组。提取 Set-Cookie 头与浏览器不同node-fetch 可以手动访问原始的Set-Cookie头import fetch from node-fetch; const response await fetch(https://example.com); // 返回一个数组而不是逗号拼接的字符串 console.log(response.headers.raw()[set-cookie]);这与Headers.get()的行为不同get()会把同一名字的多个值用, 拼接见 src/headers.js而raw()保留独立条目——这正是提取多个Set-Cookie的关键。用文件作为 POST 数据node-fetch 自带符合规范的Blob/File实现可以直接从本地文件构造请求体import fetch, { Blob, blobFrom, blobFromSync, File, fileFrom, fileFromSync, } from node-fetch const mimetype text/plain const blob fileFromSync(./input.txt, mimetype) const url https://httpbin.org/post const response await fetch(url, { method: POST, body: blob }) const data await response.json() console.log(data)fileFromSync/fileFrom与blobFromSync/blobFrom由fetch-blob提供并在 src/index.js 中统一导出分别对应同步与异步两种创建方式。用 FormData 提交 multipart/form-datanode-fetch 附带规范兼容的FormData实现用于提交 multipart/form-dataimport fetch, { FormData, File, fileFrom } from node-fetch const httpbin https://httpbin.org/post const formData new FormData() const binary new Uint8Array([ 97, 98, 99 ]) const abc new File([binary], abc.txt, { type: text/plain }) formData.set(greeting, Hello, world!) formData.set(file-upload, abc, new name.txt) const response await fetch(httpbin, { method: POST, body: formData }) const data await response.json() console.log(data)在源码中FormData请求体会被自动转换为带 boundary 的 Blob并据此生成multipart/form-data; boundary...的Content-Type见 src/body.js 与 src/body.js。追加类 Blob/File对象如果需要在任意位置产生一个流并塞进 FormData可以追加一个看起来像 Blob 或 File 的对象最低要求是具有值为Blob或File的Symbol.toStringTaggetter 或属性有一个已知的size提供stream()方法返回产出 Uint8Array/Buffer 的任意 async iterable或arrayBuffer()方法返回 ArrayBuffer。formData.append(upload, { [Symbol.toStringTag]: Blob, size: 3, *stream() { yield new Uint8Array([97, 98, 99]) }, arrayBuffer() { return new Uint8Array([97, 98, 99]).buffer } }, abc.txt)只要stream()产出 Uint8Array或 BufferNode.js Readable 流和 whatwg streams 都能直接使用。用 AbortSignal 取消请求通过AbortController可以随时取消请求推荐的 polyfill 实现是abort-controller。下面的例子演示 150ms 超时取消import fetch, { AbortError } from node-fetch; // AbortController 从 node v14.17.0 起成为全局对象 const AbortController globalThis.AbortController || await import(abort-controller) const controller new AbortController(); const timeout setTimeout(() { controller.abort(); }, 150); try { const response await fetch(https://example.com, {signal: controller.signal}); const data await response.json(); } catch (error) { if (error instanceof AbortError) { console.log(request was aborted); } } finally { clearTimeout(timeout); }源码层面的取消链路在 src/index.jsfetch在收到abort事件后会reject一个AbortError销毁尚未完成的请求体流并向响应体发出error事件如果 signal 在调用前已处于aborted状态则会立即中止。更多取消场景的测试用例见仓库的 test 目录。API 参考fetch(url[, options])url表示要请求的 URL 的字符串optionsHTTP(S) 请求的 Options返回PromiseResponse。url必须是绝对 URL如https://example.com/。路径相对 URL/file/under/root或协议相对 URL//can-be-http-or-https.com/会导致 Promise 被 reject。从 src/index.js 的实现看fetch内部支持data:、http:、https:三种协议非支持协议直接抛出TypeErrordata:URL 则通过data-uri-to-buffer直接构造Response返回无需真实网络请求。Options默认值紧随各选项键后展示{ // 以下属性属于 Fetch 标准 method: GET, headers: {}, // 请求头格式与 Headers 构造函数接受的格式一致 body: null, // 请求体可以是 null 或 Node.js Readable 流 redirect: follow, // 设为 manual 可提取重定向头设为 error 则拒绝重定向 signal: null, // 传入 AbortSignal 实例以可选地中止请求 // 以下属性是 node-fetch 扩展 follow: 20, // 最大重定向次数0 表示不跟随重定向 compress: true, // 支持 gzip/deflate 内容编码false 则禁用 size: 0, // 最大响应体字节数0 表示不限制 agent: null, // http(s).Agent 实例或返回实例的函数 highWaterMark: 16384, // 内部缓冲区最大字节数超过则暂停从底层资源读取 insecureHTTPParser: false // 为 true 时使用接受非法 HTTP 头的不安全解析器 }这些扩展选项在 src/request.js 中逐一落地follow默认 20、compress默认 true、counter默认 0、highWaterMark默认 16384、insecureHTTPParser默认 false并支持从被克隆的 Request 上继承。默认请求头未显式设置时node-fetch 会自动发送以下请求头HeaderValueAccept-Encodinggzip, deflate, br(当options.compress true)Accept*/*Content-Length(可计算时自动算出)Host(目标 URI 的 host 与 port 信息)Transfer-Encodingchunked(当req.body是流时)User-Agentnode-fetch注意当body是流时Content-Length不会被自动设置因为流长度未知。这些默认头在 src/request.js 的getNodeRequestOptions中按条件填充Accept缺失时补*/*、POST/PUT 且无 body 时补Content-Length: 0、已知长度的 body 填实际字节数、User-Agent缺失时补node-fetch、开启 compress 且无Accept-Encoding时补gzip, deflate, br。自定义 Agentagent选项允许指定超出 Fetch 规范范围的网络相关配置包括但不限于支持自签名证书仅使用 IPv4 或 IPv6自定义 DNS 查询。若未指定 agent则使用 Node.js 默认 agent。注意 Node.js 19 起默认 agent 的keepalive变为 true在更早版本中想要启用 keepalive可以按下面代码覆盖 agent。此外agent选项接受一个函数给定当前 URL 返回http(s).Agent实例——这在跨 HTTP/HTTPS 协议的重定向链中非常有用import http from node:http; import https from node:https; const httpAgent new http.Agent({ keepAlive: true }); const httpsAgent new https.Agent({ keepAlive: true }); const options { agent: function(_parsedURL) { if (_parsedURL.protocol http:) { return httpAgent; } else { return httpsAgent; } } };函数形式的 agent 会在 src/request.js 中按当前解析后的 URL 协议动态求值。自定义 highWaterMarkNode.js 流的内部缓冲区较小16kB即默认highWaterMark而浏览器端普遍大于 1MB 且各浏览器不一致。因此在编写 isomorphic 应用并使用res.clone()时大响应在 Node 端可能挂起。推荐做法是并行消费克隆后的响应import fetch from node-fetch; const response await fetch(https://example.com); const r1 response.clone(); const results await Promise.all([response.json(), r1.text()]); console.log(results[0]); console.log(results[1]);如果不喜欢上述方案从3.x起可以显式调大highWaterMarkimport fetch from node-fetch; const response await fetch(https://example.com, { // 约 1MB highWaterMark: 1024 * 1024 }); const result await res.clone().arrayBuffer(); console.dir(result);highWaterMark会同时作用于响应体流与克隆时的两个 PassThrough 流见 src/body.js 的clone实现与 src/response.js 的Response.clone。不安全 HTTP 解析器Insecure HTTP ParserinsecureHTTPParser选项会原样透传给http(s).request的同名选项为 true 时使用接受非法 HTTP 头的宽松解析器具体语义可查阅 Node.jshttp.request文档。手动处理重定向node-fetch 的redirect: manual与浏览器/规范不同规范中会得到 opaque-redirect 过滤响应而 node-fetch 返回的是普通的基本过滤响应让你可以读取重定向响应的头与状态import fetch from node-fetch; const response await fetch(https://httpbin.org/status/301, { redirect: manual }); if (response.status 301 || response.status 302) { const locationURL new URL(response.headers.get(location), response.url); const response2 await fetch(locationURL, { redirect: manual }); console.dir(response2); }跟随模式的重定向逻辑在 src/index.js 中实现error模式直接 rejectno-redirect类型的FetchErrorfollow模式会检查follow上限超限 rejectmax-redirect、跨域/跨协议时移除authorization、www-authenticate、cookie、cookie2等敏感头、303 或 301/302 跟随 POST 时降级为 GET 并清空 body 与content-length。Class: RequestRequest封装 HTTP(S) 请求的 URL、方法、头与 body 信息实现了 Body 接口。由于 Node.js 的运行时特性以下属性目前未实现typedestinationmodecredentialscacheintegritykeepalive以下 node-fetch 扩展属性已提供followcompresscounteragenthighWaterMark其含义与上文 Options 中的同名扩展一致。new Request(input[, options])(符合规范)inputURL 字符串或另一个Request会被克隆optionsHTTP(S) 请求的 Options。构造器与浏览器版Request一致。多数场景直接fetch(url, options)比先建Request对象更简洁。在 src/request.js 中可以看到构造器的重要约束URL 不允许内嵌用户名/密码会抛TypeErrorGET/HEAD 方法不允许携带 body会抛TypeErrorsignal必须是AbortSignal或EventTargetreferrer为时等价于no-referrer。Class: ResponseResponse封装 HTTP(S) 响应实现了 Body 接口。当前未实现trailer属性。new Response([body[, options]])(符合规范)bodyString或Readable流optionsResponseInit选项字典。构造器与浏览器版Response一致。由于 Node.js 没有 Service Worker该类本为它设计日常极少需要直接构造Response。源码中status缺省为 200body非空且无Content-Type时会自动推导类型见 src/response.js。此外 src/response.js 还提供了三个静态工厂方法Response.redirect(url, status 302)构造携带location头的重定向响应非重定向状态码会抛RangeErrorResponse.error()构造type: error、状态 0 的响应Response.json(data, init)将数据序列化为 JSON 并自动设置content-type: application/json。response.ok(符合规范)表示请求是否正常结束的便捷属性状态码 ≥ 200 且 300 时为true。response.redirected(符合规范)表示请求是否至少被重定向过一次内部重定向计数器大于 0 时为true见 src/response.js基于counter实现。response.type(与规范的偏差)表示响应类型的便捷属性。node-fetch 仅支持default与error两种不使用规范中的过滤响应机制。Class: HeadersHeaders类用于操作和遍历一组 HTTP 头实现了 Fetch 标准规定的全部方法。new Headers([init])(符合规范)init可选预填充Headers对象。init可以是null、另一个Headers对象、键值对映射对象或任意可迭代对象// 示例改编自 https://fetch.spec.whatwg.org/#example-headers-class import {Headers} from node-fetch; const meta { Content-Type: text/xml }; const headers new Headers(meta); // 以上写法等价于 const meta [[Content-Type, text/xml]]; const headers new Headers(meta); // 事实上任何可迭代对象都可以比如 Map 甚至是另一个 Headers const meta new Map(); meta.set(Content-Type, text/xml); const headers new Headers(meta); const copyOfHeaders new Headers(headers);从实现看src/headers.js 的Headers继承自URLSearchParams构造时统一校验 header 名/值合法性、把名字小写化并返回一个 Proxy 以保证append/set/delete/has/getAll都经过校验与小写化、keys()前自动排序。raw()与getAll()的组合是提取多值头如Set-Cookie的官方途径。Interface: BodyBody是对Request和Response都适用的抽象接口提供以下成员。body.body(与规范的偏差)Node.jsReadable流。数据封装在Body对象中。Fetch 标准要求该属性恒为 WHATWGReadableStream而 node-fetch 中它是 Node.js 的Readable流见 src/body.js。body.bodyUsed(符合规范)Boolean表示该 body 是否已被消费。按规范已消费的 body 不能再次使用重复调用消费方法会抛出TypeError: body used already for: ...见 src/body.js。body.arrayBuffer()异步把整个 body 读为ArrayBuffer。body.formData()解析multipart/form-data或x-www-form-urlencoded负载为FormData。这个能力源于 Service Worker 可以在请求发送到服务器前拦截并修改消息的思想对搭建服务端、解析消费负载的人尤其有用import http from node:http import { Response } from node-fetch http.createServer(async function (req, res) { const formData await new Response(req, { headers: req.headers // 传递 boundary 值 }).formData() const allFields [...formData] const file formData.get(uploaded-files) const arrayBuffer await file.arrayBuffer() const text await file.text() const whatwgReadableStream file.stream() // 消费请求的其他方式 const json await new Response(req).json() const text await new Response(req).text() const arrayBuffer await new Response(req).arrayBuffer() const blob await new Response(req, { headers: req.headers // 让 type 继承 Content-Type }).blob() })在 src/body.js 的实现中formData()会根据content-type分流application/x-www-form-urlencoded走URLSearchParams转换multipart 则交给 src/utils/multipart-parser.js 的解析器。body.blob()异步把整个 body 读为Blob其type继承响应的Content-Type。body.json()异步把整个 body 解析为 JSONtext()JSON.parse。body.text()异步把整个 body 解码为 UTF-8 字符串。另外需要注意消费时若超出size限制consumeBody会销毁流并抛出type: max-size的FetchError见 src/body.js这是size选项的底层实现。Class: FetchError(node-fetch 扩展)抓取过程中的操作类错误所有非中止类操作错误都会以FetchError形式 reject。它继承自FetchBaseError见 src/errors/base.js并带有type属性当类型为system时还会从 Node.js 系统错误上拷贝code、errno与erroredSysCall见 src/errors/fetch-error.js。完整分类见 docs/ERROR-HANDLING.md。Class: AbortError(node-fetch 扩展)响应AbortSignal的abort事件、请求被中止时抛出的错误其name属性为AbortError见 src/errors/abort-error.js。错误处理体系因为window.fetch并不透明地说明请求失败的成因node-fetch 建立了自己的错误约定详见 docs/ERROR-HANDLING.md要点如下被取消的请求以AbortErrorreject可通过检查error.name AbortError判断是否为中止导致的失败try { await fetch(url, {signal}); } catch (error) { if (error.name AbortError) { console.log(request was aborted); } }除中止外的所有操作类错误都以FetchErrorreject统一通过try/catch或 Promise 的catch处理所有错误都带error.message说明成因所有来自 node-fetch 的错误都有自定义的err.type标记所有源自 Node.js 核心的错误标记为error.type system并额外提供error.code与error.errno与 Node.js 核心抛出的错误码互为别名供精细化处理编程错误programmer errors则尽早抛出或用带error.message的默认Errorreject方便排查。仓库维护 100% 覆盖率完整的自定义FetchError类型清单与常见 Node.js 错误可见 test/main.js。TypeScript 支持从3.x起类型定义随node-fetch一并发布无需再安装额外类型包。类型文件位于 types/index.d.ts覆盖fetch、Headers、Request、Response、Body、FetchError、AbortError以及Blob/File/FormData等导出并完整声明了RequestInit中agent、compress、follow、size、highWaterMark、insecureHTTPParser等 node-fetch 扩展选项。对于旧版本请使用 DefinitelyTyped 的类型定义npm install --save-dev types/node-fetch2.x许可证node-fetch 采用 MIT 许可证全文见 LICENSE.md。提示本文所述用法均基于当前仓库的3.x代码版本号见 package.jsondata:URL 支持、敏感头跨域重定向剥离、流式 formData 解析等行为以源码实现为准文中示例涉及的 httpbin 等外部服务仅用于演示生产环境请替换为真实接口。赞分享后端【免费下载链接】node-fetchA light-weight module that brings the Fetch API to Node.js项目地址https://gitcode.com/gh_mirrors/no/node-fetch点击查看免费下载相关推荐node-fetch完全指南10分钟上手Node.js Fetch APInode fetch完全指南10分钟上手Node.js Fetch API 你还在为Node.js中没有内置Fetch API而烦恼吗还在纠结Axios和原后端终极指南如何在Node.js中高效使用node-fetch实现HTTP请求终极指南如何在Node.js中高效使用node fetch实现HTTP请求 node fetch 是一个轻量级模块它将浏览器中的Fetch API带到了No后端BedrockConnect自定义服务器管理如何添加和管理你的专属服务器列表BedrockConnect自定义服务器管理如何添加和管理你的专属服务器列表 BedrockConnect是一款强大的工具让Xbox One、Nintend上一篇4步构建优质游戏体验DOL汉化美化整合包全面部署指南下一篇QMCDecode解放加密音乐的跨平台播放解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表