<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/"
    xmlns:atom="http://www.w3.org/2005/Atom" xmlns:media="http://search.yahoo.com/mrss/" version="2.0">
    <channel>
        
        <title>
            <![CDATA[ API - freeCodeCamp.org ]]>
        </title>
        <description>
            <![CDATA[ freeCodeCamp 是一个免费学习编程的开发者社区，涵盖 Python、HTML、CSS、React、Vue、BootStrap、JSON 教程等，还有活跃的技术论坛和丰富的社区活动，在你学习编程和找工作时为你提供建议和帮助。 ]]>
        </description>
        <link>https://www.freecodecamp.org/chinese/news/</link>
        <image>
            <url>https://cdn.freecodecamp.org/universal/favicons/favicon.png</url>
            <title>
                <![CDATA[ API - freeCodeCamp.org ]]>
            </title>
            <link>https://www.freecodecamp.org/chinese/news/</link>
        </image>
        <generator>Eleventy</generator>
        <lastBuildDate>Sun, 24 May 2026 19:37:55 +0000</lastBuildDate>
        <atom:link href="https://www.freecodecamp.org/chinese/news/tag/api/rss.xml" rel="self" type="application/rss+xml" />
        <ttl>60</ttl>
        
            <item>
                <title>
                    <![CDATA[ 学习 API 基础和架构 - 面向初学者的指南 ]]>
                </title>
                <description>
                    <![CDATA[ 以下有一些问题：你是如何使用 Google、Apple 或 Microsoft 账号登录应用的？如何使用 Paystack 或 PayPal 进行在线支付？像 Facebook 和 Instagram 这样的应用是如何共享信息和通知的？ 答案是：它们使用 API。这些是推动移动和 Web 开发以及各种应用程序（包括云服务、物联网设备、桌面软件等）的强大工具。 API 实现了应用之间的通信，促进了数据交换和验证。 在本文中，你将了解 API 的所有内容：不同类型、它们的架构以及不同架构之间的权衡。 我们将涵盖以下内容：  * 什么是 API？         * API 如何工作？             ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/learn-api-fundamentals-and-architecture/</link>
                <guid isPermaLink="false">68b6be1cbb40b8047c933025</guid>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Elio Fang ]]>
                </dc:creator>
                <pubDate>Tue, 02 Sep 2025 09:57:21 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2025/09/4d39283c-ec52-4cb3-bdda-bc818b303bc3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/learn-api-fundamentals-and-architecture/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Learn API Fundamentals and Architecture – A Beginner-Friendly Guide</a>
      </p><!--kg-card-begin: markdown--><p>以下有一些问题：你是如何使用 Google、Apple 或 Microsoft 账号登录应用的？如何使用 Paystack 或 PayPal 进行在线支付？像 Facebook 和 Instagram 这样的应用是如何共享信息和通知的？</p>
<p>答案是：它们使用 API。这些是推动移动和 Web 开发以及各种应用程序（包括云服务、物联网设备、桌面软件等）的强大工具。</p>
<p>API 实现了应用之间的通信，促进了数据交换和验证。</p>
<p>在本文中，你将了解 API 的所有内容：不同类型、它们的架构以及不同架构之间的权衡。</p>
<h3 id="">我们将涵盖以下内容：</h3>
<ul>
<li>
<p><a href="#heading-what-is-an-api">什么是 API？</a></p>
<ul>
<li>
<p><a href="#heading-how-do-apis-work">API 如何工作？</a></p>
</li>
<li>
<p><a href="#heading-why-are-apis-important">为什么 API 很重要？</a></p>
</li>
</ul>
</li>
<li>
<p><a href="#heading-types-of-apis">API 类型</a></p>
<ul>
<li>
<p><a href="#heading-open-apis">开放 API</a></p>
</li>
<li>
<p><a href="#heading-partner-apis">合作伙伴 API</a></p>
</li>
<li>
<p><a href="#heading-internal-apis">内部 API</a></p>
</li>
<li>
<p><a href="#heading-composite-apis">组合 API</a></p>
</li>
</ul>
</li>
<li>
<p><a href="#heading-types-of-api-architecture">API 架构类型</a></p>
<ul>
<li>
<p><a href="#heading-rest-apis">REST API</a></p>
</li>
<li>
<p><a href="#heading-soap-apis">SOAP API</a></p>
</li>
<li>
<p><a href="#heading-graphql-apis">GraphQL API</a></p>
</li>
<li>
<p><a href="#heading-grpc-apis">gRPC API</a></p>
</li>
</ul>
</li>
<li>
<p><a href="#heading-how-to-choose-an-api-architecture">如何选择 API 架构</a></p>
</li>
<li>
<p><a href="#heading-conclusion-and-future-trends">结论和未来趋势</a></p>
</li>
</ul>
<p>这篇文章非常适合于 Web 和移动开发初学者，以及希望简要了解 API 及其如何运作的开发者。</p>
<h2 id="api">什么是 API？</h2>
<p>API 代表应用程序编程接口。它是一组规则和协议，让不同的软件系统相互通信。API 定义了应用程序如何请求服务和交换数据，充当客户端和服务器之间的明确契约。</p>
<p>API 将复杂的代码简化为简单的命令，让开发人员可以连接系统并使用内置功能，而无需了解所有的内部工作原理。</p>
<h3 id="api">API 如何工作？</h3>
<p>想象一下有一家餐馆：顾客（客户端）通过服务员（API）点餐，然后服务员通知厨房（服务器）。厨房备餐后通过服务员传递给顾客。就像服务员一样，API 处理请求和响应，让顾客在无需了解厨房操作细节的情况下享受餐点。</p>
<p>一个更实用的例子是当你在线购买订阅时，你的支付信息通过其支付 API 安全发送至 Paystack。API 是一个中间人，它获取你的请求，验证并处理你的银行支付信息，然后在不直接暴露敏感数据的情况下返回确认给网站。</p>
<p>从技术上讲，客户端向服务器发起请求，指定要获取数据还是执行某个流程。接收并认证该请求后，API 会执行所需操作。随后，API 向客户端发送响应，包括请求的结果（成功或失败）和任何请求的数据元素。</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXeho5OxXyKdS_-Sam70CtbZIH6y1wFMH3r21I0ZeNDVFNqoY0Jr2Lk5u_FfsiIas6LEnMPjRbQticIaDZi0iCF93Zj-JpxjZzXrwEGtdS_vIopXEUtNG5mvVHnVpf5vvhZGHw4Q?key=2qCWq-hs7d172uM7WbtEHg_B" alt="来自 shiksha.com 的图解说明了 API 如何工作。" width="600" height="400" loading="lazy"></p>
<h3 id="api">为什么 API 很重要？</h3>
<p>API 在软件开发中至关重要，因为它们使得连接不同的应用程序和服务更加容易。它们允许你整合外部功能，而无需从头开始构建所有内容，通过标准化命令节省时间并降低复杂性。</p>
<p>对于用户来说，API 还可以提高安全性和用户体验。它们作为安全网关，过滤应用和外部服务之间的数据交换，保护敏感信息，同时确保流畅、可靠的互动。</p>
<h2 id="api">API 类型</h2>
<p>API 的类型主要根据其可访问性和使用情况进行分类。API 有四种类型，分别是：</p>
<ol>
<li>
<p>开放（公共）API</p>
</li>
<li>
<p>合作伙伴 API</p>
</li>
<li>
<p>内部（私有）API</p>
</li>
<li>
<p>组合 API</p>
</li>
</ol>
<h3 id="api">开放 API</h3>
<p>开放 API 是向公众提供的 API。这就鼓励开发者、组织和其他人使用它们来开发应用程序，将其集成到他们的服务中，并加以改进。开放 API 提供了通过互联网访问数据或服务的标准化接口。</p>
<p>一些非常实用的开放 API 包括：</p>
<ul>
<li>
<p><a href="https://tradewatch.io/">TradeWatch</a> —— 实时金融市场数据</p>
</li>
<li>
<p><a href="http://serpapi.com">SerpAPI 的搜索 API</a> —— 实时谷歌搜索引擎结果页 API</p>
</li>
<li>
<p><a href="http://twitterapi.io">TwitterApi.io</a> —— 访问实时和历史数据</p>
</li>
<li>
<p><a href="https://instagram-posts-generator.vercel.app/">Instagram 帖子生成器</a> —— 使用流行 IG 页面模板生成帖子</p>
</li>
</ul>
<h3 id="api">合作伙伴 API</h3>
<p>合作伙伴 API 与特定的商业伙伴共享，通常需要身份验证和协议。它们为企业和应用程序执行重要功能。</p>
<p>例如，像 Paystack 这样的支付 API 直接与服务提供商和银行平台通信，以处理产品和服务的支付。</p>
<h3 id="api">内部 API</h3>
<p>内部 API 用于组织内部的通信。它们能够实现集成并简化内部流程。内部团队使用 API 在其应用程序之间访问和共享数据。API 不对公众开放，以确保敏感的业务逻辑保持安全。</p>
<p>一个例子是公司内部 API，它连接其人力资源、工资单和项目管理系统。</p>
<h3 id="api">复合 API</h3>
<p>复合 API 将多个 API 调用组合为一个请求。在微服务架构中，它们非常重要，因为单个操作可能需要从多个服务中获取数据。一次 API 调用会触发对多个底层 API 的请求，然后复合 API 会组合这些响应并返回一个统一的结果。</p>
<p>例如，一个电子商务平台可能会使用复合 API 来一次性获取产品详情、定价和库存信息，减少延迟并简化集成过程。</p>
<h2 id="api">API 架构类型</h2>
<p>根据使用场景、可扩展性、安全性和可访问性，API 的结构各有不同。构建 API 的方式有多种，但我们将仅关注Web开发中最为流行的架构风格，它们包括：</p>
<ol>
<li>
<p>REST</p>
</li>
<li>
<p>SOAP</p>
</li>
<li>
<p>GraphQL</p>
</li>
<li>
<p>gRPC</p>
</li>
</ol>
<h3 id="restapi">REST API</h3>
<p>表述性状态转移（REST）是一种使用 HTTP 方法（POST、GET、PUT、DELETE）在基于资源的 URI 上执行 CRUD（创建、读取、更新、删除）操作的架构风格。</p>
<p>REST API 使用像 Express.js（Node.js）、Django/Flask（Python）、Spring Boot（Java）这样的框架构建。</p>
<h4 id="">关键组件</h4>
<ol>
<li>
<p>资源和端点：</p>
<ul>
<li>
<p>API 公开的实体可以包括任何内容：用户、产品、文档等。</p>
</li>
<li>
<p>每个资源由唯一的 URI（统一资源标识符）标识。</p>
</li>
</ul>
</li>
<li>
<p>HTTP 方法：</p>
<ul>
<li>
<p>GET：获取资源。</p>
</li>
<li>
<p>POST：创建新资源。</p>
</li>
<li>
<p>PUT：更新现有资源。</p>
</li>
<li>
<p>DELETE：删除资源。</p>
</li>
<li>
<p>PATCH：部分更新现有资源。</p>
</li>
</ul>
</li>
<li>
<p>数据表示：</p>
<ul>
<li>
<p>资源可以有多种表示形式（例如，JSON，XML）。</p>
</li>
<li>
<p>API以请求的表示形式响应，允许对数据进行结构化和解析。</p>
</li>
</ul>
</li>
<li>
<p>HTTP 标头和查询参数：</p>
<ul>
<li>
<p>HTTP 标头提供有关请求或响应的附加信息。</p>
</li>
<li>
<p>它们可以用于身份验证、内容协商和其他用途。</p>
</li>
</ul>
</li>
<li>
<p>无状态：</p>
<ul>
<li>
<p>客户端到服务器的每个请求都必须包含理解和处理请求所需的所有信息。</p>
</li>
<li>
<p>服务器在请求之间不存储任何客户端状态。</p>
</li>
</ul>
</li>
</ol>
<p>其他显著的组件包括可缓存性、HTTP 状态和 HATEOAS。这些组件共同定义了 RESTful 系统的结构和行为，支持客户端与服务器之间的无缝高效通信。</p>
<h4 id="">操作概述</h4>
<p>REST API 通过唯一的 URI 公开资源，并允许客户端使用 HTTP 方法（如 GET、POST、PUT、DELETE 和 PATCH）执行操作。客户端可以请求各种格式的数据，如 JSON 或 XML，并通过 HTTP 头和查询参数包含附加细节。</p>
<p>每个请求都是无状态的，包含处理所需的所有信息，而不依赖于存储的客户端数据。API 还使用 HTTP 状态码、可缓存性和 HATEOAS 管理响应并指导进一步的交互，确保客户端和服务器之间的无缝高效通信框架。</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcYW8ovzOrZJB1eV1X82hvfuddZwjl7mwI56bYpZKCvzf4I4tNEfx58lhIjs_GMRaei9mXAxR78BUAIacBYoCw4J-CmkVKRDGa5ruK4KdYnmBV2Y0u9qz9QjOYSWNHBmUIPsopXuA?key=2qCWq-hs7d172uM7WbtEHg_B" alt="来自 apisec.ai 的图示，说明 REST API 的流，包括端点、HTTP 方法以及客户端和服务器之间的数据交换。" width="600" height="400" loading="lazy"></p>
<h4 id="">实际示例和真实案例</h4>
<p>为了说明 REST API 在实践中的工作原理，我们以一个允许用户管理图书集合的图书 API 为例。我们的示例 API 是使用 <a href="https://www.freecodecamp.org/news/get-started-with-nodejs/">NodeJS</a> 和 <a href="https://expressjs.com/">ExpressJS</a> 框架创建的。这里我们不解释这些框架的具体工作原理，因为这超出了本文的范围。所以如果你不了解下面代码的语法，不用担心，只需关注 <strong>请求</strong> 和 <strong>响应</strong> 逻辑。</p>
<p>此 API 遵循 REST 原则，使用标准 HTTP 方法执行 CRUD（创建、读取、更新、删除）操作：</p>
<pre><code>const express = require("express"); const bodyParser = require("body-parser");
const app = express(); app.use(bodyParser.json());

const app = express();
app.use(bodyParser.json());

// 虚拟数据库
let books = [
  { id: 1, title: "The Pragmatic Programmer", author: "Andy Hunt" },
  { id: 2, title: "Clean Code", author: "Robert C. Martin" },
];

// 获取所有书籍（客户端请求，服务器响应）
app.get("/books", (req, res) =&gt; res.json(books));
</code></pre>
<pre><code class="language-markdown">// 新增一本书（客户端发送数据，服务器更新数据库）
app.post("/books", (req, res) =&gt; {
  const newBook = { id: books.length + 1, ...req.body };
  books.push(newBook);
  res.status(201).json(newBook);
});

// 更新一本书
app.put("/books/:id", (req, res) =&gt; {
  const book = books.find((b) =&gt; b.id === parseInt(req.params.id));
  if (book) {
    Object.assign(book, req.body);
    res.json(book);
  } else {
    res.status(404).json({ message: "Not found" });
  }
});

// 删除一本书
app.delete("/books/:id", (req, res) =&gt; {
  const index = books.findIndex((b) =&gt; b.id === parseInt(req.params.id));
  if (index !== -1) {
    books.splice(index, 1);
    res.json({ message: "Deleted" });
  } else {
    res.status(404).json({ message: "Not found" });
  }
});

app.listen(3000, () =&gt; console.log("API running on port 3000"));
</code></pre>
<p>此代码的操作流程如下：</p>
<ul>
<li>
<p>客户端发送请求：用户（或前端应用）通过使用诸如 GET，POST，PUT 或 DELETE 等 HTTP 方法请求数据。例如：GET <code>/books</code> 请求所有书籍，或 POST <code>/books</code> 将新书提交到数据库。</p>
</li>
<li>
<p>服务器处理请求：服务器接收请求，查找数据（例如，从数据库或内存数组中查找），并进行处理。</p>
</li>
<li>
<p>服务器返回响应：服务器返回包含请求数据或确认消息的 JSON 响应。示例如下：</p>
</li>
</ul>
<pre><code>[
  { "id": 1, "title": "The Pragmatic Programmer", "author": "Andy Hunt" },
  { "id": 2, "title": "Clean Code", "author": "Robert C. Martin" }
]
</code></pre>
<ul>
<li>客户端接收并使用数据：前端或其他服务使用 API 响应，并相应地显示或处理数据。</li>
</ul>
<p>团队将 REST API 用于 Web 服务、移动应用程序和云集成。社交媒体平台获取帖子，电子商务网站检索产品详情。支付网关处理交易，天气应用程序访问实时预测。REST 的简单性和可扩展性使其成为公共和内部 API 的首选。</p>
<h3 id="soapapis">SOAP APIs</h3>
<p>简单对象访问协议（SOAP）使用 XML 进行消息传递，并包括用于安全、事务和错误处理的内置标准。其正式约定由 WSDL（Web 服务描述语言）定义。</p>
<p>这种架构通过 WS-Security（Web Services Security）和事务管理等功能优先考虑安全性和可靠性，使其适合需要严格标准和强大错误处理的复杂企业应用程序。</p>
<p>SOAP APIs 是使用诸如 Apache CXF、.NET WCF 和 JAX-WS（Java）等框架或工具创建的。</p>
<h4 id="">关键组件</h4>
<ol>
<li>
<p>SOAP envelope（SOAP 封套）：</p>
<ul>
<li>
<p>这是 SOAP 消息的根元素，定义了 XML 文档的整体结构。</p>
</li>
<li>
<p>它包含 SOAP Header（SOAP 标头）和 SOAP Body（SOAP 主体）。</p>
</li>
</ul>
</li>
<li>
<p>SOAP body（SOAP 主体）：</p>
<ul>
<li>
<p>该部分包含在客户端和服务器之间交换的实际数据。</p>
</li>
<li>
<p>它包括请求或响应消息，这些消息通常被构造为 XML 元素。</p>
</li>
</ul>
</li>
<li>
<p>WSDL（Web 服务描述语言）：</p>
<ul>
<li>
<p>这是一个描述 Web 服务的 XML 文档，包括其操作、消息格式和数据类型。</p>
</li>
<li>
<p>它充当客户端和服务器之间的合同，概述如何与 API 交互。</p>
</li>
</ul>
</li>
<li>
<p>SOAP processor（SOAP 处理器）：</p>
<ul>
<li>
<p>这是处理 SOAP 消息的软件组件。</p>
</li>
<li>
<p>它解析 XML 文档，提取相关数据，并执行请求的操作。</p>
</li>
</ul>
</li>
</ol>
<p>另外，还有 SOAP Endpoint（SOAP 端点），即 SOAP 服务可被访问的 URL，以及 XML Schema (XSD)，定义了 SOAP 消息的 XML 结构和数据类型。</p>
<h4 id="">操作概述</h4>
<p>SOAP APIs 通过将数据封装在由 SOAP 封套定义的基于 XML 的结构中运行，该信封包含用于元数据的标头和用于实际请求或响应信息的主体。主体承载交换数据，而 WSDL 文档则作为契约，详细说明服务的操作、消息格式和数据类型。</p>
<p>SOAP 处理器随后解析 XML，提取相关数据，并根据附带的 XML 模式 (XSD) 定义的规则执行请求的操作。与服务的通信通过特定的 SOAP 端点进行，确保了 Web 服务交互的标准化、互操作框架。</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXfN_kg7EZxppR62_usgGiNO7jTLWnrmd5MYzxLUyjl6cNSuVHBAVV-kWvQN2xZ5s1acOugK8SfGb2JKtIys01yMSrdRO7iwNCtc5CHh6QDDCBWH2vc7EYOiHNgpbyZv8jZBhTwejg?key=2qCWq-hs7d172uM7WbtEHg_B" alt="来自 muledreamin.com 的图解，说明 SOAP API 的流程，包括 HTTP 传输以及 SOAP 发送方与 SOAP 接收方之间的数据交换。" width="600" height="400" loading="lazy"></p>
<h4 id="">实际例子和真实用例</h4>
<p>为了说明 SOAP APIs 及其实际工作原理，我们考虑一个基于 SOAP 的银行服务 API，提供用于管理账户和交易的安全操作。SOAP APIs 使用 XML 消息传递，确保系统间的安全和结构化通信。创建 SOAP API 和 XML 消息传递超出了本文的范围，因此我们将在此重点讨论请求和响应逻辑。</p>
<p>如何运作：</p>
<ul>
<li><strong>获取账户信息</strong>: 客户端发送一个 XML 请求以获取用户的账户详情：</li>
</ul>
<pre><code>&lt;soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:bank="http://example.com/bank"&gt;
   &lt;soapenv:Header/&gt;
   &lt;soapenv:Body&gt;
      &lt;bank:GetAccountDetails&gt;
         &lt;bank:AccountNumber&gt;123456789&lt;/bank:AccountNumber&gt;
      &lt;/bank:GetAccountDetails&gt;
   &lt;/soapenv:Body&gt;
&lt;/soapenv:Envelope&gt;
</code></pre>
<p>服务器以包含账户详细信息的 XML 消息进行响应：</p>
<pre><code>&lt;soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"&gt;
   &lt;soapenv:Body&gt;
      &lt;GetAccountDetailsResponse&gt;
         &lt;AccountNumber&gt;123456789&lt;/AccountNumber&gt;
         &lt;Balance&gt;5000.00&lt;/Balance&gt;
         &lt;Currency&gt;USD&lt;/Currency&gt;
      &lt;/GetAccountDetailsResponse&gt;
   &lt;/soapenv:Body&gt;
&lt;/soapenv:Envelope&gt;
</code></pre>
<ul>
<li>
<p><strong>处理转账</strong>: 客户端提交一个带有认证详情的转账请求：</p>
<pre><code>  &lt;soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                    xmlns:bank="http://example.com/bank"&gt;
     &lt;soapenv:Header/&gt;
     &lt;soapenv:Body&gt;
        &lt;bank:TransferFunds&gt;
           &lt;bank:FromAccount&gt;123456789&lt;/bank:FromAccount&gt;
           &lt;bank:ToAccount&gt;987654321&lt;/bank:ToAccount&gt;
           &lt;bank:Amount&gt;100.00&lt;/bank:Amount&gt;
           &lt;bank:Currency&gt;USD&lt;/bank:Currency&gt;
        &lt;/bank:TransferFunds&gt;
     &lt;/soapenv:Body&gt;
  &lt;/soapenv:Envelope&gt;
</code></pre>
<p>如果成功，服务器返回一个确认响应：</p>
<pre><code>  &lt;soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"&gt;
     &lt;soapenv:Body&gt;
        &lt;TransferFundsResponse&gt;
           &lt;Status&gt;Success&lt;/Status&gt;
           &lt;TransactionID&gt;TXN987654&lt;/TransactionID&gt;
        &lt;/TransferFundsResponse&gt;
     &lt;/soapenv:Body&gt;
  &lt;/soapenv:Envelope&gt;
</code></pre>
</li>
</ul>
<p>银行、医疗保健提供者和政府机构使用 SOAP 提供安全、可靠的 API。金融机构以严格的身份验证处理交易，而医疗系统在遵从法规下交换患者数据。航空公司依赖 SOAP 进行预订和售票，确保跨系统的一致数据完整性。</p>
<h3 id="graphqlapi">GraphQL API</h3>
<p>GraphQL 是一种由 Facebook 开发的用于 APIs 的查询语言和运行时。它允许客户端在一个请求中精确地请求所需的数据，减少过度获取和不足获取。</p>
<h4 id="">关键组件</h4>
<ol>
<li>
<p>模式: 这是 GraphQL API 的核心。它定义了数据的结构，包括对象类型、其字段及其关系。它作为客户端和服务器之间的契约，指定可以查询的数据。</p>
</li>
<li>
<p>类型: 这些定义了数据中对象的结构。它们指定每个对象拥有的字段及这些字段的数据类型。</p>
</li>
<li>
<p>字段: 这些是可以在一个对象上查询的独立数据片段。</p>
</li>
<li>
<p>查询: 这些是客户端请求以检索数据。它们指定客户端希望恢复的字段。</p>
</li>
<li>
<p>变更: 这些是客户端请求以修改数据（创建、更新或删除）。</p>
</li>
<li>
<p>解析器: 这些是为模式中每个字段获取数据的函数。它们将 GraphQL 模式连接到底层数据源。</p>
</li>
<li>
<p>订阅: 这些启用实时更新。客户端可以订阅特定事件，服务器将在事件发生时推送更新。</p>
</li>
</ol>
<h4 id="">操作概述</h4>
<p>GraphQL 定义了一个指定可用数据类型及其关系的 schema。然后，客户端构建查询或变更，精确请求所需数据字段。GraphQL 服务器处理这些请求，使用解析器从后端源获取数据。</p>
<p>服务器根据模式验证请求，执行解析器，并返回仅包含请求数据的 JSON 响应。客户端可以建立实时更新的订阅，使服务器在数据改变时推送数据。这种方法最小化了过度获取和不足获取，提高了数据检索的效率和灵活性。</p>
<p><img src="https://lh7-rt.googleusercontent.com/docsz/AD_4nXcmC7u4hu44TvJ603f9ODRYIgi3UiHMs-Q4qjE8FZqwPnEGDPTU6DhZSvLCZU3BO7WITNnuls_801ChJyACz6xpKOdvoQ5hLswAMIHZ3-Ii4dyg7CEKZ5m1AAgv70sso-c26Cl_sQ?key=2qCWq-hs7d172uM7WbtEHg_B" alt="来自 kinsta.com 的图解，展示了 GraphQL 服务器架构，显示了从服务器传输 GraphQL 查询请求和 JSON 负载的过程。" width="600" height="400" loading="lazy"></p>
<h4 id="">实际示例和真实用例</h4>
<p>让我们通过一个由 GraphQL 驱动的电子商务 API 来实际探讨 GraphQL API 的工作原理。此 API 可以高效地获取产品详情、评论和库存可用性。服务器是使用 NodeJS 和 <a href="https://www.apollographql.com/docs/apollo-server">Apollo Server</a> 创建的。创建服务器超出了本文的范围，因此我们将专注于 Schema（关系数据库是如何构建和可视表示的）及 <strong>请求</strong> 和 <strong>响应</strong> 逻辑。</p>
<ol>
<li>模式:</li>
</ol>
<pre><code># Schema (schema.graphql)

type Product {
  id: ID!
  name: String!
  description: String
  price: Float!
  inventory: Int!
  category: String!
}


```markdown
type Mutation {
  createProduct(name: String!, description: String, price: Float!, inventory: Int!, category: String!): Product!
  updateProductInventory(id: ID!, inventory: Int!): Product!
}
</code></pre>
<p>这个模式定义了数据类型（<code>Product</code>、<code>Query</code>、<code>Mutation</code>）并指定了可用的查询（<code>product</code>、<code>products</code>）和变更操作（<code>createProduct</code>、<code>updateProductInventory</code>）。它使用了 <a href="https://graphql.org/learn/schema/">GraphQL 类型系统</a>（String、Int、Float、ID、[ ]、!）</p>
<ol start="2">
<li>
<p>请求和响应</p>
<ul>
<li>
<p>获取产品数据 - 客户端请求特定的产品字段（例如，名称、价格和描述）：</p>
<pre><code class="language-graphql">query {
  product(id: "123") {
    name
    price
    description
  }
}
</code></pre>
<p>如果请求成功，服务器仅返回请求的数据：</p>
<pre><code class="language-json">{
  "data": {
    "product": {
      "name": "无线耳机",
      "price": 99.99,
      "inStock": true
    }
  }
}
</code></pre>
</li>
<li>
<p>创建一个新产品：</p>
<pre><code class="language-graphql">mutation {
  createProduct(name: "鼠标", price: 30, inventory: 100, category: "电子产品") {
    id
    name
    price
  }
}
</code></pre>
</li>
<li>
<p>更新产品信息：</p>
<pre><code class="language-graphql">mutation {
  updateProduct(id: "123", price: 89.99) {
    name
    price
  }
}
</code></pre>
<p>如果成功，服务器返回更新后的详细信息：</p>
<pre><code class="language-json">{
  "data": {
    "updateProduct": {
      "name": "无线耳机",
      "price": 89.99
    }
  }
}
</code></pre>
</li>
</ul>
</li>
</ol>
<p>像 Facebook 和 Shopify 这样的公司使用 GraphQL 来实现高效、灵活的 API。电子商务和社交应用程序只提取所需的数据，减少多余的数据获取。移动应用程序优化性能，而分析工具无缝聚合复杂数据。</p>
<h3 id="grpcapis">gRPC APIs</h3>
<p>远程过程调用（gRPC）是一种高性能的 RPC 框架，使用 HTTP/2 和协议缓冲序列化结构化数据。它支持同步和异步通信以及流功能。</p>
<p>HTTP/2 是 HTTP 的最新演进，具备二进制分帧、多路复用、标头压缩和服务器推送等令人兴奋的特性，以提高性能和减少延迟。gRPC 充分利用了这些功能，实现快速、高效、同时的通信，使其非常适合微服务和实时应用。</p>
<h4 id="">关键组件</h4>
<ol>
<li>
<p>服务定义：在 .proto 文件中定义。它指定了所提供的服务和可用的 RPC 方法，充当客户端和服务器之间的契约。</p>
</li>
<li>
<p>消息是使用协议缓冲区定义的数据结构，可以在系统之间高效地序列化和反序列化数据。</p>
</li>
<li>
<p>存根：自动生成的客户端和服务器代码，使客户端可以像调用本地方法一样调用远程方法，并使服务器可以实现服务逻辑。</p>
</li>
<li>
<p>通道：它们管理客户端和服务器之间的连接，处理底层网络通信。</p>
</li>
<li>
<p>RPC 方法：gRPC 支持不同类型的调用，包括一元（单请求-响应）、客户端流、服务器流和双向流，每种都适合不同的使用场景。</p>
</li>
<li>
<p>拦截器和元数据：这些提供了附加额外功能的机制，例如通过将元数据附加到 RPC 调用来进行身份验证、日志记录和错误处理。</p>
</li>
</ol>
<h4 id="">操作概述</h4>
<p>gRPC 让开发者能够在 .proto 文件中使用协议缓冲定义服务契约和消息类型，作为可用 RPC 方法的蓝图。代码生成器生成客户端和服务器存根，允许像本地函数一样调用远程过程，同时通道管理基于 HTTP/2 的网络通信。</p>
<p>它支持一元、客户端流、服务器流和双向流以应对不同的数据交换模式。此外，还可以集成拦截器和元数据来进行身份验证和日志记录等任务，确保系统稳健、安全和高效。</p>
<h4 id="">实践示例和实际使用案例</h4>
<p>让我们考虑一个使用 gRPC 快速在客户端（移动应用）和后端服务之间快速通信的打车应用。gRPC 使用协议缓冲（Protobuf）进行二进制序列化，而不是像 JSON 或 XML 这样的基于文本的格式。这使得网络通信显著更快且更高效。</p>
<ol>
<li>.proto 文件定义了 API 结构：</li>
</ol>
<pre><code class="language-protobuf">syntax = "proto3";

service RideService {
  rpc RequestRide(RideRequest) returns (RideResponse);
  rpc StreamRideUpdates(RideUpdateRequest) returns (stream RideUpdate);
}

message RideRequest {
  string user_id = 1;
  string pickup_location = 2;
  string destination = 3;
}

message RideResponse {
  string ride_id = 1;
  string driver_name = 2;
  string car_model = 3;
}
</code></pre>
<pre><code>message RideUpdateRequest {
  string ride_id = 1;
}
</code></pre>
<p>当客户端发送一个 <code>RideRequest</code> 时，它会使用 Protobuf 序列化为一个紧凑的二进制格式。这减少了负载大小，加快了传输速度，提高了效率。服务器会在处理之前将其反序列化为一个结构化的对象。</p>
<ol start="2">
<li>
<p>请求和响应：</p>
<ul>
<li>
<p>请求乘车：客户端点击按钮发送乘车请求，其中包含：</p>
<pre><code>{
  "user_id": "U123",
  "pickup_location": "Central Park",
  "destination": "Times Square"
}
</code></pre>
<p>服务器响应司机的详细信息：</p>
<pre><code>{
  "ride_id": "R456",
  "driver_name": "John Doe",
  "car_model": "Toyota Prius"
}
</code></pre>
<p>你可能会好奇为什么请求和响应是以 JSON 格式显示的，因为 gRPC 并不使用像 JSON 和 XML 这样的基于文本的格式。gRPC 使用的压缩二进制流不像 JSON 那样可读，这是一个紧凑且高效的编码格式，需要通过 Protobuf 反序列化来理解。在压缩二进制流格式中，请求或响应看起来像这样：</p>
<pre><code>08 D2 04 12 0D 43 65 6E 74 72 61 6C 20 50 61 72 6B 1A 0B 54 69 6D 65 73 20 53 71 75 61 72 65
</code></pre>
</li>
<li>
<p>实时流更新：一旦分配了乘车，服务器就会向客户端流式传输实时更新：</p>
<pre><code>{
  "ride_id": "R456",
  "status": "Driver on the way",
  "driver_location": "5th Avenue"
}
</code></pre>
</li>
</ul>
</li>
</ol>
<p>公司使用 gRPC 来满足高性能、实时应用程序中对高效服务通信的需求。像 Google、Netflix 和 Dropbox 这样的科技巨头使用 gRPC 来实现可扩展的微服务。共享乘车应用程序会流式传输实时司机位置，而金融科技平台则管理安全、低延迟的交易。物联网系统和人工智能应用依赖 gRPC 来进行实时数据交换和高效交互。</p>
<h2 id="api">如何选择API架构</h2>
<p>选择一个 API 架构需要根据项目的具体需求平衡性能、可扩展性、易用性和安全性等多个因素。</p>
<p>REST 因其简单性和无状态设计而闻名，这有助于可扩展性和易用性，但其安全性主要依赖于外部措施，如 HTTPS 和适当的认证机制。</p>
<p>SOAP 尽管更复杂，但提供了强大的内置安全标准（如 WS-Security）和可靠的事务支持，使其适合于企业环境。</p>
<p>GraphQL 通过只允许客户端请求所需的数据，提供了高效的数据获取和高性能，但可能需要额外的安全措施，比如查询深度限制和服务器端的恰当认证。</p>
<p>gRPC 提供了卓越的性能，非常适合实时数据需求的微服务。它利用 HTTP/2 和 TLS 来实现安全、高效的通信，尽管它需要较长的学习曲线。</p>
<p>下表总结了这些架构的特点以及如何权衡：</p>
<table>
<thead>
<tr>
<th>特点</th>
<th>REST</th>
<th>SOAP</th>
<th>GraphQL</th>
<th>gRPC</th>
</tr>
</thead>
<tbody>
<tr>
<td>性能</td>
<td>中等（可能存在数据过度获取）</td>
<td>低</td>
<td>高</td>
<td>高</td>
</tr>
<tr>
<td>可扩展性</td>
<td>高</td>
<td>中等</td>
<td>高</td>
<td>非常高（适用于微服务和实时数据）</td>
</tr>
<tr>
<td>易用性</td>
<td>简单且广泛采用</td>
<td>复杂</td>
<td>对于客户端来说直观（服务器端可能复杂）</td>
<td>学习曲线陡峭</td>
</tr>
<tr>
<td>安全性</td>
<td>依赖于外部机制（HTTPS、OAuth 等）</td>
<td>通过 WS-Security 和正式合同提供强大的内置安全性</td>
<td>需要额外的措施（查询验证、速率限制）</td>
<td>高安全性，内置 TLS 支持和强大的认证协议</td>
</tr>
</tbody>
</table>
<h2 id="">结论和未来趋势</h2>
<p>API 已成为现代软件开发中的基本组成部分，促进了不同应用程序之间的无缝通信和数据交换。它们的影响是不可否认的，从推动创新的公共 API 到简化内部流程的私有 API。</p>
<p>理解 REST、SOAP、GraphQL 和 gRPC 等不同的 API 架构，使开发人员能够根据特定需求选择最佳方法，在性能、可扩展性和易用性之间取得平衡。</p>
<p>展望未来，API 领域将迎来令人兴奋的变化。随着 AI 驱动的 API、去中心化架构和改进的安全措施的出现，我们将看到新的方式来构建和交互软件。API 标准的持续演变和低代码/无代码平台的增长使 API 开发变得更容易为所有人所用。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何使用 AWS API Gateway 向用户提供自定义 API ]]>
                </title>
                <description>
                    <![CDATA[ 在云计算领域和 serverless 架构中，AWS API Gateway 是一款强大的工具，能帮助您搭建强大、安全且可拓展的 API。 在本教程中，首先我将介绍 API 网关是什么，并解释使用 API 网关的好处。接下来，我将展示如何创建、部署一个 Rest API, 并创建使用计划以提供 API 密钥。那么，我们现在就开始吧！ 什么是 API 网关？ AWS API Gateway 是 Amazon Web Services (AWS) 提供的一项全托管服务，可帮助您轻松搭建、部署和管理任意规模的 API。 它充当应用程序的前门，允许您创建充当客户端和后端服务之间桥梁的 API，以便实现安全有效的通信。 为什么需要 API 网关？ AWS API Gateway 可为企业和开发者提供诸多好处，下方列出了一些使用 API 网关的好处。 可拓展性和高可用性 借助 AWS API Gateway，您可以更轻松地进行 API 拓展。通过底层基础设施自动拓展， AWS API ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-offer-custom-apis-to-your-users-aws-api-gateway/</link>
                <guid isPermaLink="false">64af9123486c7406702c8ed7</guid>
                
                    <category>
                        <![CDATA[ 云计算 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Rhea Xiao ]]>
                </dc:creator>
                <pubDate>Thu, 13 Jul 2023 06:13:58 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/07/AWS-API-Gateway-Banner-3.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-to-offer-custom-apis-to-your-users-aws-api-gateway/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Offer Custom APIs to Your Users with AWS API Gateway</a>
      </p><!--kg-card-begin: markdown--><p>在云计算领域和 serverless 架构中，AWS API Gateway 是一款强大的工具，能帮助您搭建强大、安全且可拓展的 API。</p>
<p>在本教程中，首先我将介绍 API 网关是什么，并解释使用 API 网关的好处。接下来，我将展示如何创建、部署一个 Rest API, 并创建使用计划以提供 API 密钥。那么，我们现在就开始吧！</p>
<h2 id="api">什么是 API 网关？</h2>
<p>AWS API Gateway 是 Amazon Web Services (AWS) 提供的一项全托管服务，可帮助您轻松搭建、部署和管理任意规模的 API。</p>
<p>它充当应用程序的前门，允许您创建充当客户端和后端服务之间桥梁的 API，以便实现安全有效的通信。</p>
<h2 id="api">为什么需要 API 网关？</h2>
<p>AWS API Gateway 可为企业和开发者提供诸多好处，下方列出了一些使用 API 网关的好处。</p>
<h3 id="">可拓展性和高可用性</h3>
<p>借助 AWS API Gateway，您可以更轻松地进行 API 拓展。通过底层基础设施自动拓展， AWS API Gateway 可以无缝处理流量高峰，以确保高可用性并避免服务中断。</p>
<h3 id="">安全与认证</h3>
<p>API 网关提供强大的安全功能，包括内置的身份验证和授权机制。</p>
<p>它支持通过 IAM 角色对内部应用程序进行用户身份验证，通过 Cognito 对外部应用程序进行身份验证，并且支持自定义授权者。</p>
<h3 id="aws">与其他 AWS 服务集成</h3>
<p>作为 AWS 生态系统的一部分， API 网关与一系列其他 AWS 服务无缝集成，这意味着您能利用 AWS Lambda 函数、用于用户管理的 AWS Cognito，以及用于监管和日志记录的 AWS CloudWatch 等其他功能。</p>
<h3 id="api">API 生命周期管理</h3>
<p>利用 API 网关，您能轻松对不同阶段的 API 进行版本控制、部署及管理。这简化了发布更新、测试新功能以及管理不同环境（比如开发、预生产和生产）的过程。</p>
<p>我希望现在您已经了解了 API 网关是什么以及它为何如此重要。接下来让我们一起来创建自己的 API 网关吧！</p>
<h2 id="awsapigateway">如何创建 AWS API Gateway</h2>
<p>在本节中，我们将：</p>
<ul>
<li>采用 GET 方法创建 Rest API</li>
<li>将其与简单的 hello world lambda 函数集成并进行部署</li>
</ul>
<p>让我们从创建 lambda 函数开始吧</p>
<h2 id="awslambda">如何创建 AWS Lambda 函数</h2>
<p>登录 AWS Management <a href="https://console.aws.amazon.com/">控制台</a> 并在控制台搜索栏中搜索 "Lambda"。然后，单击 Create Function 按钮。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-145.png" alt="导航至 AWS Lambda 控制台" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>导航至 AWS Lambda 控制台</figcaption>
</figure>
<p>选择 "Author from scratch" 选项，输入 lambda 函数名称，选择 "Python" Runtime，然后单击右下方的 Create Function 按钮。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-146.png" alt="创建一个 AWS Lambda Function" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>创建一个 AWS Lambda Function</figcaption>
</figure>
<p>函数创建成功后，请更新下方代码并部署更改：</p>
<pre><code class="language-Python">import json

def lambda_handler(event, context):
    body = "Hello from 5minslearn!"
    statusCode = 200
    return {
        "statusCode": statusCode,
        "body": json.dumps(body),
        "headers": {
            "Content-Type": "application/json"
        }
    }
</code></pre>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-147.png" alt="部署 Lambda Function" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>部署 Lambda Function</figcaption>
</figure>
<p>恭喜! 您已成功创建 AWS Lambda 函数，接下来让我们来创建 Rest API。</p>
<h2 id="restapiawslambda">如何创建 Rest API 并将其与 AWS Lambda 集成</h2>
<p>在搜索栏搜索 API Gateway，然后在 REST API 版块中单击 Build 按钮。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-183.png" alt="创建 Rest API" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>创建 Rest API</figcaption>
</figure>
<p>选择协议为 Rest，并在 Create new API 选项中选择 New API。在设置选项中输入您选择的 API 名称，并保留 Endpoint Type 的默认选项。然后，单击 Create API 按钮。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-148.png" alt="配置创建 Rest API" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>配置创建 Rest API</figcaption>
</figure>
<p>首先单击左上方的 Actions 按钮，然后单击 Method 并选择 GET 方法，再单击勾选图标。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-149.png" alt="创建 Method" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>创建 Method</figcaption>
</figure>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-150.png" alt="选择 " get"="" 方法"="" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>选择 "GET" 方法</figcaption>
</figure>
<p>选择 Lambda Function 作为 Integration type，并输入已创建的 Lambda 函数名称。然后，保存此函数。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-151.png" alt="选择 Method 配置" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>选择 Method 配置</figcaption>
</figure>
<p>单击保存后， 屏幕中将弹出 "Add Permission to Lambda Function"消息提示确认，这就意味着您将允许 API Gateway 调用 Lambda 函数（在本例中指的就是 "DemoFunction" Lambda 函数）。请同意确认，并继续下一步。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-152.png" alt="image-152" width="600" height="400" loading="lazy"></p>
<p>同意授权通过 API 网关调用 Lambda Function</p>
<p>单击 Test，您将来到一个新页面。单击 "Test" 按钮，此时您能在右侧面板上看到 Lambda 函数做出的响应。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-153.png" alt="我们的 API 架构" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>我们的 API 架构</figcaption>
</figure>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-184.png" alt="测试我们的 API 网关" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>测试我们的 API 网关</figcaption>
</figure>
<p>在成功测试 API 后，您就能部署 API 了。要部署 API，请再次单击 Actions 按钮，然后单击 Deploy API。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-185.png" alt="部署 API 网关" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>部署 API 网关</figcaption>
</figure>
<p>此时屏幕上将弹出 Deploy API 的对话框，请选择 New Stage 作为 Deployment stage，并对其进行命名。然后，单击 Deploy 按钮。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-155.png" alt="配置 API 网关部署" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>配置 API 网关部署</figcaption>
</figure>
<p>单击页面顶部的 Invoke URL，您将看到 Lambda 做出的响应。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-156.png" alt="API 网关创建成功" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>API 网关创建成功</figcaption>
</figure>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-186.png" alt="测试我们的 API" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>测试我们的 API</figcaption>
</figure>
<p>真棒! 我们已经成功创建了 Rest API，将其与 Lambda 函数集成并进行了部署。</p>
<p>但是，您可以通过市场中提供的多种服务来实现这个目标，那为什么要选择 AWS API Gateway 呢？</p>
<p>嗯，这是一个有趣的问题。首先，您可以利用 AWS API Gateway 为自己的 API 配置使用计划，而其中最突出的一点就是您无需为此编写任何代码。</p>
<p>现在就让我们来创建一个使用计划，生成一个 API 密钥，并仅通过在标头中传递 API 密钥来访问 Rest API。</p>
<h2 id="apigateway">如何创建 API Gateway 使用计划</h2>
<p>在左侧边栏中单击 Usage Plans，然后单击 Create 按钮。输入您的计划名称，这里我选择了 "Basic"。根据您的需求在 Throttling 和 Quota 选项中输入相应数值，然后单击 Next。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-159.png" alt="创建 AWS API Gateway 使用计划" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>创建 AWS API Gateway 使用计划</figcaption>
</figure>
<p>单击 Add API Stage 按钮，并选择相应的 API 及其 Stage。然后，单击右上角的勾选图标，并选择 Next。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/Screenshot-from-2023-06-19-10-46-19.png" alt="为我们的 API 创建一个 Stage" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>为我们的 API 创建一个 Stage</figcaption>
</figure>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-164.png" alt="为我们的 API 创建一个 Stage" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>为我们的 API 创建一个 Stage</figcaption>
</figure>
<p>单击 "Create API Key and add to Usage Plan"，屏幕上将弹出一个对话框，请输入 API 密钥名称。而关于 API 密钥，我这里选择了 Auto Generate （自动生成），当然您也可以进行自定义。然后，单击 Save 按钮。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-160.png" alt="创建 API 密钥以访问服务" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>创建 API 密钥以访问服务</figcaption>
</figure>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-161.png" alt="配置 API 密钥" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>配置 API 密钥</figcaption>
</figure>
<p>从侧边栏选择 Resources，单击已创建的 GET API，然后单击 Method Request。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-162.png" alt="选择方法" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>选择方法</figcaption>
</figure>
<p>在设置选项中，将 API Key Required 字段更新为 "true" 并单击勾选图标。更新后，务必点击 Actions 下拉菜单以部署更改。否则，变更将不会更新。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-187.png" alt="启用 API Key Required 字段" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>启用 API Key Required 字段</figcaption>
</figure>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-165.png" alt="部署 API" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>部署 API</figcaption>
</figure>
<p>现在点击相同的 URL，你会发现神奇的事发生了。</p>
<p>Forbidden （禁止访问）！</p>
<p>因为现在我们的 API 层已受保护，您必须在标头中传递 API 密钥才能访问数据。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-163.png" alt="image-163" width="600" height="400" loading="lazy"></p>
<p>若未提供 API 密钥，则禁止访问。</p>
<p>现在单击侧边栏中的 Usage Plans，选择您的计划并导航至 API 密钥选项卡。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-166.png" alt="访问您的 API 密钥" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>访问您的 API 密钥</figcaption>
</figure>
<p>单击您在步骤 3 中创建的 API 密钥，然后单击 Show， 并复制此 API 密钥。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-188.png" alt="API 密钥列表" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>API 密钥列表</figcaption>
</figure>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2023/06/image-167.png" alt="显示您的 API 密钥" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>显示您的 API 密钥</figcaption>
</figure>
<p>您必须在 'x-api-key' 标头中传递密钥。现在，让我们切换至终端来测试一下。</p>
<p>首先，测试一下在不传递 API 密钥的情况下 Rest API 的响应。打开终端，然后输入下方的 curl 命令。此时，您将再次看到“禁止访问”的消息。</p>
<pre><code class="language-bash">curl --location --request GET '[enter your invoke url]'
--header 'Content-Type: application/json
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-189.png" alt="image-189" width="600" height="400" loading="lazy"></p>
<p>终端中未提供 API 密钥的情况下禁止访问</p>
<p>现在再进行一次测试，在标头中传递 API 密钥，并运行下方 curl 命令：</p>
<pre><code class="language-bash">curl --location --request GET '[your invoke url]' \
--header 'x-api-key: [your api key]' \
--header 'Content-Type: application/json' \
--data-raw ''
</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2023/06/image-190.png" alt="image-190" width="600" height="400" loading="lazy"></p>
<p>在 x-api-key 标头中传递 API 密钥时获取的数据</p>
<p>因为在标头中传递了 'x-api-key'，所以您能看到 Lambda 函数输出的结果。</p>
<p>真棒! 您已经成功创建了使用计划，生成了 API 密钥，并将其附加到 Rest API 方法中以及验证了集成。</p>
<h2 id="">总结</h2>
<p>在本教程中，您学习了 AWS API gateway 是什么，以及如何为 Rest API 创建使用计划。</p>
<p>如果您想学习更多关于 AWS Services 的知识，可以订阅我的 <a href="https://5minslearn.gogosoon.com/?ref=fcc_aws_api_gateway">email newsletter</a> (<a href="https://5minslearn.gogosoon.com/?ref=fcc_aws_api_gateway">https://5minslearn.gogosoon.com/</a>) 并在社交媒体上关注我。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何用 OpenAPI 在 Express 中构建更好的 API ]]>
                </title>
                <description>
                    <![CDATA[ 我将在这篇文章中分享在 Express 中构建强大的 REST API 的方法。首先，我会介绍构建 REST API 的一些挑战，然后提出一个使用开放标准的解决方案。 本文并非一篇关于 Node.js [https://nodejs.org/en/]、Express.js [https://expressjs.com/] 或  REST API [/news/rest-apis/] 的介绍。如果你需要复习，请在深入研究本文内容之前查看这些链接。🤿 我喜欢 Node.js 那极具灵活性和易用性的生态。这个社区充满活力，并且你可以用你已经掌握的语言在几分钟内设置一个 REST API。 在应用的前后端使用相同的编程语言是一件很有价值的事。这使我们在浏览代码库时可以减少上下文切换 [https://blog.rescuetime.com/context-switching/]，从而变得更轻松。全栈开发者可以快速切换技术栈，共享代码 [https://betterprogramming.pub/sharing-logic-components-between-frontend-and- ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-build-explicit-apis-with-openapi/</link>
                <guid isPermaLink="false">641fbc4ee32a7606487d581c</guid>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Express ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ HeZean ]]>
                </dc:creator>
                <pubDate>Wed, 29 Mar 2023 08:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2023/03/6065d95e9618b008528ab4a6.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/how-to-build-explicit-apis-with-openapi/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">How to Build Better APIs in Express with OpenAPI</a>
      </p><!--kg-card-begin: markdown--><p>我将在这篇文章中分享在 Express 中构建强大的 REST API 的方法。首先，我会介绍构建 REST API 的一些挑战，然后提出一个使用开放标准的解决方案。</p>
<p>本文并非一篇关于 <a href="https://nodejs.org/en/">Node.js</a>、<a href="https://expressjs.com/">Express.js</a> 或 <a href="https://chinese.freecodecamp.org/news/rest-apis/">REST API</a> 的介绍。如果你需要复习，请在深入研究本文内容之前查看这些链接。🤿</p>
<p>我喜欢 Node.js 那极具灵活性和易用性的生态。这个社区充满活力，并且你可以用你已经掌握的语言在几分钟内设置一个 REST API。</p>
<p>在应用的前后端使用相同的编程语言是一件很有价值的事。这使我们在浏览代码库时可以减少<a href="https://blog.rescuetime.com/context-switching/">上下文切换</a>，从而变得更轻松。全栈开发者可以快速切换技术栈，<a href="https://betterprogramming.pub/sharing-logic-components-between-frontend-and-backend-repositories-6fdc1f9cb850">共享代码</a>也变得轻而易举。</p>
<p>尽管如此，随着 MVP 成长为成熟的生产环境应用程序和开发团队规模的扩大，这种灵活性也带来了挑战。</p>
<h2 id="restapi">使用 REST API 的挑战</h2>
<p>无论你使用哪种技术栈，当代码库和团队规模增长时，都会面临许多挑战。</p>
<p>在本文中，我将描述通过 REST API 暴露业务逻辑的 Express.js 应用程序所带来的挑战，以小见大。</p>
<p>无论 API 消费者的性质如何（网页、移动应用、第三方后端），随着他们的成长，他们都可能面临以下一个（或多个）挑战：</p>
<h3 id="1">1. ⚠️ 更难做出改变</h3>
<p>在文档不够明确时，在 REST API 的任何一方进行修改都变得更加困难。</p>
<p>举个例子，假设你有一个 REST 端点，可以返回一个特定的用户的名字。在即将新增的功能中，你可能需要修改这个 API 使其返回年龄。这可能会潜在地破坏网络应用和移动应用。</p>
<p>你可以设置集成测试来一定程度上避免这个问题，但你仍然会严重依赖开发人员来手动覆盖所有的边界情况。这需要大量的时间和精力，而且你永远无法 100% 确定这些变化不会破坏应用程序。</p>
<h3 id="2">2. 📜 缺少（及时更新的）文档</h3>
<p>文档是构建 REST API 时的另一个敏感话题。我坚信在大多数情况下，代码本身应该足以代替一部分文档。</p>
<p>也就是说，REST API 在开发中会变得越来越复杂，检查代码中每个端点的安全性、参数和可能的响应也随之变得繁琐且耗时。这就减慢了开发的速度，也给 bug 进入系统留下了隐患。</p>
<p>即使团队致力于在一个独立于代码的文档中手动保持文档的更新，也很难 100% 确保它反映了代码的情况。</p>
<h3 id="3api">3. 📢 公共 API</h3>
<p>这并不适用于所有的应用程序，但在某些情况下，一个应用程序可能需要向第三方暴露一系列的功能。对于这种情况，第三方有可能会在我们暴露的 API 之上构建核心功能。</p>
<p>这意味着我们不能以更新我们的私有 API 的同样速度来修改这些公共 API。一旦修改了公共 API，第三方应用程序可能会因此崩溃，而这正是我们应该不惜一切代价避免的事情。</p>
<p>公共 API 所暴露的内容应该是明确的，并且可以简单地进行开发，以限制内部和外部开发团队之间所需的来回沟通的数量。</p>
<h3 id="4">4. ✍️ 手动集成测试</h3>
<p>当应用程序的开发没有与之匹配的周密计划时，很有可能 API 所提供的内容和 API 消费者期望的内容被深埋在代码中。</p>
<p>对于仅有少量的内部端点的系统来说，这并不是一个大问题。但随着 API 接口数量的增长，修改现有的端点需要在整个系统中遵循面包屑，以确保消费者期望得到的东西与提供的东西是相等的。</p>
<p>这个问题可以通过对系统的不同部分之间进行集成测试来缓解。但是人工完成这件事的工作量非常巨大的，并且如果没做好的话，可能会在系统实际上不能正常工作的时候让开发人员误以为系统状态良好。</p>
<h2 id="">提出的解决方案</h2>
<p>我们已经看到了构建 REST API 所带来的固有挑战。在下一节中，我们将使用开放标准构建一个示例 Express 项目，以解决这些挑战。</p>
<h3 id="api">API 标准规范</h3>
<p>前面部分描述的挑战已经存在很长时间了，所以面对这个问题，我们最好查看现有的解决方案，而不是重新发明轮子。</p>
<p>许多标准尝试对 REST API 进行规范化定义（<a href="https://raml.org/">RAML</a>、<a href="https://jsonapi.org/">JsonAPI</a>、<a href="https://www.openapis.org/">OpenAPI</a>......）。这些项目的共同目标是使开发人员更容易定义他们的 API 行为，以便跨多种语言的服务器和客户端能够“共说一种语言”。</p>
<p>有了某种形式的 API 规范，可以解决许多挑战，因为在许多情况下，可以从这些规范自动生成客户端 SDK、测试、模拟服务器和文档。</p>
<p>一种我最喜欢的规范是 OpenAPI（原名 Swagger）。它有一个很大的社区，并且有很多用于 Express 的工具。这可能不是所有 REST API 项目中的最佳工具，因此请在为你自己的项目选择规范之前进行额外的研究，以确保该规范的工具和支持对你的项目有帮助。</p>
<h3 id="">示例的背景</h3>
<p>在这个示例中，假设我们正在构建一个待办事项列表管理应用。用户可以通过访问一个 web 应用来获取、创建、编辑和删除待办事项，这些待办事项被保存在后端。</p>
<p>在这个例子中，后端使用一个 Express.js 应用程序，它将通过 REST API 暴露以下功能：</p>
<ul>
<li>获取待办事项: <strong>[GET] /todos</strong></li>
<li>创建待办事项：<strong>[POST] /todos</strong></li>
<li>编辑待办事项：<strong>[PUT] /todos/:id</strong></li>
<li>删除待办事项：<strong>[DELETE] /todos/:id</strong></li>
</ul>
<p>对于一个真实的待办事项管理应用来说，上面的功能有点过度简化，但这有助于展示我们如何在实际情况下克服上面提出的挑战。</p>
<h3 id="">实现</h3>
<p>很好，现在我们已经介绍了 API 定义的开放标准和背景，让我们来实现一个 Express 待办事项应用，演示怎么解决前面的挑战。</p>
<p>我们将使用 Express 库 <a href="https://github.com/kogosoftwarellc/open-api/tree/master/packages/express-openapi"><strong>express-openapi</strong></a> 的 OpenAPI。请注意，这个库提供的高级功能（响应验证、认证、中间件设置......）超出了本文的范围。</p>
<p>你可以在<a href="https://github.com/aperkaz/express-open-api">这个仓库</a>中找到演示的完整代码。</p>
<ol>
<li>初始化一个 Express 框架，并初始化一个 Git 仓库：</li>
</ol>
<pre><code class="language-bash">npx express-generator --no-view --git todo-app
cd ./todo-app
git init
git add .; git commit -m "Initial commit";
</code></pre>
<ol start="2">
<li>将 <strong><a href="https://github.com/kogosoftwarellc/open-api/tree/master/packages/express-openapi">express-openapi</a></strong> 引入我们的程序：</li>
</ol>
<p><code>npm i express-openapi -s</code></p>
<pre><code class="language-javascript">// ./app.js

...

app.listen(3030);

...

// OpenAPI routes
initialize({
  app,
  apiDoc: require("./api/api-doc"),
  paths: "./api/paths",
});

module.exports = app;
</code></pre>
<ol start="3">
<li>添加 OpenAPI 基础模型。</li>
</ol>
<p>请注意，模型中定义了 <strong>Todo</strong> 的类型，将在路由处理程序中引用。</p>
<pre><code class="language-javascript">// ./api/api-doc.js

const apiDoc = {
  swagger: "2.0",
  basePath: "/",
  info: {
    title: "Todo app API.",
    version: "1.0.0",
  },
  definitions: {
    Todo: {
      type: "object",
      properties: {
        id: {
          type: "number",
        },
        message: {
          type: "string",
        },
      },
      required: ["id", "message"],
    },
  },
  paths: {},
};

module.exports = apiDoc;
</code></pre>
<ol start="4">
<li>添加路由<a href="https://github.com/kogosoftwarellc/open-api/tree/master/packages/express-openapi#getting-started">处理程序</a>。</li>
</ol>
<p>每个处理程序都声明它支持哪些操作（GET、POST ...），对每个操作的回调，以及该处理程序的 <strong>apiDoc</strong> OpenAPI 模型。</p>
<pre><code class="language-javascript">// ./api/paths/todos/index.js
module.exports = function () {
  let operations = {
    GET,
    POST,
    PUT,
    DELETE,
  };

  function GET(req, res, next) {
    res.status(200).json([
      { id: 0, message: "First todo" },
      { id: 1, message: "Second todo" },
    ]);
  }

  function POST(req, res, next) {
    console.log(`About to create todo: ${JSON.stringify(req.body)}`);
    res.status(201).send();
  }

  function PUT(req, res, next) {
    console.log(`About to update todo id: ${req.query.id}`);
    res.status(200).send();
  }

  function DELETE(req, res, next) {
    console.log(`About to delete todo id: ${req.query.id}`);
    res.status(200).send();
  }

  GET.apiDoc = {
    summary: "Fetch todos.",
    operationId: "getTodos",
    responses: {
      200: {
        description: "List of todos.",
        schema: {
          type: "array",
          items: {
            $ref: "#/definitions/Todo",
          },
        },
      },
    },
  };

  POST.apiDoc = {
    summary: "Create todo.",
    operationId: "createTodo",
    consumes: ["application/json"],
    parameters: [
      {
        in: "body",
        name: "todo",
        schema: {
          $ref: "#/definitions/Todo",
        },
      },
    ],
    responses: {
      201: {
        description: "Created",
      },
    },
  };

  PUT.apiDoc = {
    summary: "Update todo.",
    operationId: "updateTodo",
    parameters: [
      {
        in: "query",
        name: "id",
        required: true,
        type: "string",
      },
      {
        in: "body",
        name: "todo",
        schema: {
          $ref: "#/definitions/Todo",
        },
      },
    ],
    responses: {
      200: {
        description: "Updated ok",
      },
    },
  };

  DELETE.apiDoc = {
    summary: "Delete todo.",
    operationId: "deleteTodo",
    consumes: ["application/json"],
    parameters: [
      {
        in: "query",
        name: "id",
        required: true,
        type: "string",
      },
    ],
    responses: {
      200: {
        description: "Delete",
      },
    },
  };

  return operations;
};
</code></pre>
<ol start="5">
<li>添加自动生成的文档，<strong><a href="https://github.com/scottie1984/swagger-ui-express">swagger-ui-express</a></strong>：</li>
</ol>
<pre><code class="language-bash">npm i swagger-ui-express -s
</code></pre>
<pre><code class="language-javascript">// ./app.js

...

// OpenAPI UI
app.use(
  "/api-documentation",
  swaggerUi.serve,
  swaggerUi.setup(null, {
    swaggerOptions: {
      url: "http://localhost:3030/api-docs",
    },
  })
);

module.exports = app;
</code></pre>
<p>这就是我们最终获得的效果：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/04/image-23.png" alt="image-23" width="600" height="400" loading="lazy"></p>
<p>这个 SwaggerUi 是自动生成的，你可以在 <a href="http://localhost:3030/api-documentation">http://localhost:3030/api-documentation</a> 访问它。</p>
<p>🎉 <strong>恭喜！</strong></p>
<p>当你进行到文章的这里时，你应该创建好了一个完全可运行的 Express 应用程序，其与 OpenAPI 完全集成。</p>
<p>现在，通过使用在 <em><a href="http://localhost:3030/api-docs">http://localhost:3030/api-docs</a></em> 中定义的模型，我们可以轻松生成<a href="https://nordicapis.com/generating-web-api-tests-from-an-openapi-specification/">测试</a>、<a href="https://github.com/stoplightio/prism">模拟服务器</a>、<a href="https://github.com/drwpow/openapi-typescript">类型</a>，甚至<a href="https://phrase.com/blog/posts/using-openapi-to-generate-api-client-code/">客户端</a>！</p>
<h2 id="">总结</h2>
<p>我们只是浅浅涉猎了 OpenAPI 所能做到的事情。但是我希望这篇文章能够让你了解标准 API 定义模式是如何在可见性、测试、文档和整体置信度方面帮助构建 REST API 的。</p>
<p>谢谢你看到最后！</p>
<p>我目前正在构建 <a href="https://taggr.ai/"><strong><strong>taggr</strong></strong></a>，这是一个跨平台的桌面应用程序，它在帮助用户<strong>重新发现</strong>他们的数字记忆的同时保持他们的<strong>隐私</strong>。</p>
<p>Linux、Windows 和 macOS 平台上的 alpha 版本即将推出。请查看<a href="https://taggr.ai/">网页</a>并<a href="https://taggr.us18.list-manage.com/subscribe/post?u=482d473aa1e4dedadc89fb3e2&amp;id=aa6a10c164">登记</a>，以免错过！</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 扩展 Node.js REST API 的最佳实践 ]]>
                </title>
                <description>
                    <![CDATA[ 除了使用集群模式，还有各种各样扩展API的方式。在这篇教程中，我们将学习10种扩展Node.js API的方式。 我们经常会在处理项目的时候获取一些零散的知识以提高技能，必须通过不断地复习，才能将习得的技能应用到下次项目中。 这个方法一直奏效吗？我甚至不记得我昨天做了些什么。所以我写下了这篇教程，也是对自己知识的复盘。 我尝试记录下这些不常被提起扩展Node.js的方法。 本文提及的方法不一定是你最后的救命稻草，你可以在Node.js项目的任何阶段应用这些方法。 让我们来看看本文的内容：  1.  🚦使用节流  2.  🐢 优化数据库查询  3.  ䷪ 使用断路器快速故障  4.  🔍 记录检查点  5.  🌠 使用Kafka而非HTTP请求  6.  🪝 小心内存泄露  7.  🐇 使用缓存  8.  🎏 使用连接池  9.  🕋 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/nodejs-api-best-practices-for-scaling/</link>
                <guid isPermaLink="false">632d7b0345d5bb07655499cc</guid>
                
                    <category>
                        <![CDATA[ Node.js ]]>
                    </category>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ PapayaHUANG ]]>
                </dc:creator>
                <pubDate>Fri, 23 Sep 2022 09:18:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/09/linux-shell-utilities-cover-1.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p data-test-label="translation-intro">
        <strong>原文：</strong> <a href="https://www.freecodecamp.org/news/nodejs-api-best-practices-for-scaling/" target="_blank" rel="noopener noreferrer" data-test-label="original-article-link">Best Practices for Scaling Your Node.js REST APIs</a>
      </p><!--kg-card-begin: markdown--><p>除了使用集群模式，还有各种各样扩展API的方式。在这篇教程中，我们将学习10种扩展Node.js API的方式。</p>
<p>我们经常会在处理项目的时候获取一些零散的知识以提高技能，必须通过不断地复习，才能将习得的技能应用到下次项目中。</p>
<p>这个方法一直奏效吗？我甚至不记得我昨天做了些什么。所以我写下了这篇教程，也是对自己知识的复盘。</p>
<p>我尝试记录下这些不常被提起扩展Node.js的方法。</p>
<p>本文提及的方法不一定是你最后的救命稻草，你可以在Node.js项目的任何阶段应用这些方法。</p>
<p>让我们来看看本文的内容：</p>
<ol>
<li>🚦使用节流</li>
<li>🐢 优化数据库查询</li>
<li>䷪ 使用断路器快速故障</li>
<li>🔍 记录检查点</li>
<li>🌠 使用Kafka而非HTTP请求</li>
<li>🪝 小心内存泄露</li>
<li>🐇 使用缓存</li>
<li>🎏 使用连接池</li>
<li>🕋 无缝扩展</li>
<li>💎 OpenAPI兼容文档</li>
</ol>
<h2 id="">使用节流</h2>
<p>节流可以限制对服务器的访问，以防止请求过量。使用节流的好处非常明显：可以保护应用免受大量用户爆发的困扰，也可以防止<a href="https://en.wikipedia.org/wiki/Denial-of-service_attack">拒绝服务攻击（Dos攻击）</a>。</p>
<p>输入和输出的速率不匹配的时候，通常是应用节流机制的时候。特别是当入站流量远超过服务器可以（或者希望）处理的流量。</p>
<p>让我们通过图像来理解：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/09/throttling-nodejs-best-practices-nodejs.drawio--5-.png" alt="你的应用程序正在限制来自新闻推送服务的请求" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>你的应用程序正在限制来自新闻推送服务的请求</figcaption>
</figure>
<p>在应用程序和新闻推送服务器之间的第一个节点应用了节流：</p>
<ol>
<li>新闻推送服务（NFS）订阅了你的应用以发送通知。</li>
<li>每秒向你的应用发送1000个请求。</li>
<li>根据NFS的订阅计费计划，你的应用仅处理500个请求/秒。</li>
<li>为前500个请求发送通知。</li>
</ol>
<p>必须要注意的是，所有超过500个请求/秒以外的请求都会失败，需要NFS再次尝试发送请求。</p>
<p><strong>当可以排队的时候，为什么要拒绝额外的请求？</strong> 有以下理由：</p>
<ol>
<li>接受所有请求会使应用开始累积请求，这可能导致所有订阅你的应用的客户端出现单点故障（通过RAM/磁盘耗尽），包括NFS。</li>
<li>你不应该接受超出客户订阅计划范围的请求（在我们的例子中是NFS）。</li>
</ol>
<p>对于应用程序级别的速率限制，你可以使用Express.js API的中间件——<a href="https://www.npmjs.com/package/express-rate-limit">express-rate-limit</a>。对于网络级别的节流，你可以使用类似<a href="https://aws.amazon.com/waf/">WAF</a>的解决方案。</p>
<p>如果你使用的是发布-订阅机制，也可以限制消费者和订阅者。例如，你可以通过设置<a href="https://kafka.js.org/docs/consuming#a-name-options-a-options">maxBytes选项</a>来限制消费Kafka标签（topic）的字节数据。</p>
<h2 id="">优化数据库查询</h2>
<p>有时你可能没有缓存数据，或者数据已经过期，查询数据成了唯一的选择。</p>
<p>发生这种情况时，请确保你的数据库做好了准备：第一步是拥有足够的RAM和磁盘IOPS（每秒输入输出量）。</p>
<p>其次，尽可能优化你的查询。对于初学者来说，做对这几件事很关键：</p>
<ol>
<li>查询时尽量使用索引字段，但不要过度索引。<a href="https://www.mongodb.com/blog/post/performance-best-practices-indexing#:~:text=Eliminate%20Unnecessary%20Indexes">索引也有开销</a>。</li>
<li>对于删除，坚持软删除。如果需要永久删除，请推迟。（<a href="https://httpie.io/blog/stardust">一个有趣的故事</a>）</li>
<li>在读取数据的时候，仅使用投影（projection）获取需要的字段。如果可以的话，去掉没有必要的元数据和方法。（例如，Mongoose提供<a href="https://mongoosejs.com/docs/tutorials/lean.html">lean</a>)。</li>
<li>尝试将数据库性能和用户体验分离。如果数据库上的CRUD可以在后台发生（即非阻塞），请执行此操作。不要让用户等待。</li>
<li>使用更新查询直接更新所需字段。不要获取文档，更新字段，然后将整个文档保存回数据库。这会造成网络和数据库开销。</li>
</ol>
<h2 id="">使用断路器快速故障</h2>
<p>想象一下，你的Node.js应用程序出现突发流量，并且满足请求所需的外部服务器之一已关闭。你是否想在此后的每个请求中一直走死胡同？当然不！我们不想在注定要失败的请求上浪费时间和资源。</p>
<p>这就是使用断路器的核心思想：<strong>尽早失败</strong>、<strong>快速失败</strong>。</p>
<p>例如，如果100个请求中有50个失败，则在接下来的X秒内不允许对该外部服务器发出任何请求。它可以防止触发必然会失败的请求。</p>
<p>一旦线路复位，就允许请求通过。如果它们再次失败，则线路断开并重复循环。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/09/circuit-breaker-nodejs-best-practices-for-scale.drawio--1-.png" alt="Node.js Opposum断路器状态" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>Node.js Opposum断路器状态</figcaption>
</figure>
<p>可以查看<a href="https://github.com/nodeshift/opossum">Opposum</a>以了解更多添加断路器的信息。你也可以在<a href="https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern">这里</a>阅读更多关断路器的信息。</p>
<h2 id="">记录检查点</h2>
<p>良好的日志记录设置可以帮助你快速发现错误。你可以创建可视化日志来了解应用程序的行为、设置警报和有效地调试。</p>
<p>你可以使用<a href="https://www.elastic.co/what-is/elk-stack">ELK stack</a>来设置良好的日志记录和警报管道。</p>
<p>虽然日志记录是必不可少的工具，但很容易过度使用。如果记录所有内容，最终可能会耗尽磁盘IOPS，从而导致你的应用程序受到影响。</p>
<p><strong>一个值得借鉴的经验是只记录检查点。</strong></p>
<p>检查点可以是：</p>
<ol>
<li>当进入应用程序中的主控制流时以及在它们经过验证和清理之后的请求</li>
<li>与外部服务/SDK/API交互时的请求和响应。</li>
<li>对该请求的最终响应。</li>
<li>为catch处理程序提供有用的错误消息（错误消息具有合理的默认值）。</li>
</ol>
<p><strong>另外：</strong> 如果一个请求在生命周期中经过多个服务器，你可以在日志中传递一个唯一ID，以跨服务器捕获特定请求。</p>
<h2 id="kafkahttp">使用Kafka而非HTTP请求</h2>
<p>虽然存在HTTP请求的用例，但容易使用过度，请在不必要的时候避免使用HTTP请求。</p>
<p>让我们通过这个例子来理解：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/09/kafka-over-http-nodejs-best-practices-for-scale.drawio.png" alt="Kafka主从结构使用标签（topic）模式" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>Kafka主从结构使用标签（topic）模式</figcaption>
</figure>
<p>假设我们要创建一个如Amazon一样的产品，这个产品包含两大服务：</p>
<ol>
<li>供应商服务</li>
<li>库存服务</li>
</ol>
<p>每当你收到来自供应商服务的新库存，就推送一个库存详情到<a href="https://kafka.apache.org/intro">Kafka</a>标签。库存服务监听到这个标签，并且更新数据库以确认有新的补货。</p>
<p>请注意，你将库存数据推送到管道（pipeline）然后程序流，就可以继续其他的操作。程序会自动按照一定节奏消费库存服务。<strong>Kafka使服务解耦。</strong></p>
<p>现在，如果你的库存服务出现了故障怎么办？如果是用HTTP请求就不容易处理。但如果使用Kafka，就可以重播预期的消息（例如使用<a href="https://github.com/edenhill/kcat">kcat</a>）。<strong>使用Kafka的话，数据被消费后不会丢失。</strong></p>
<p>当某个商品重新回到库存，你可能希望向愿望清单包含这个商品的顾客推送消息。要实现这个功能，可以让通知服务和库存服务监听同样的标签。通过这种方式，<strong>就可以实现在不同的地方使用单个消息总线，并没有HTTP开销</strong>。</p>
<p>KafkaJS的<a href="https://kafka.js.org/docs/getting-started">开始页面</a>分享了从Node.js应用的基础设置到上述功能的代码片段，我强烈推荐你查看，有很多内容值得研究。</p>
<h2 id="">小心内存泄露</h2>
<p>如果你不编写保护内存安全的代码，并且不经常<a href="https://nodejs.org/en/docs/guides/simple-profiling/">分析</a>你的应用，很有可能造成服务器崩溃。</p>
<p>你可不希望自己的分析结果如下：</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/09/Image-Pasted-at-2022-9-6-14-58.png" alt="执行结束后setTimeout还保留98%的内存" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>执行结束后setTimeout还保留98%的内存</figcaption>
</figure>
<p>对于初学者，我建议：</p>
<ol>
<li>使用<code>--inspect</code>标志来执行 Node.js API。</li>
<li>在Chrome浏览器打开<code>chrome://inspect/#devices</code>。</li>
<li>点击 inspect &gt; <code>Memory</code> tab &gt; <code>Allocation instrumentation on timeline</code>。</li>
<li>在执行一些应用功能。可以使用macOS的apache bench来同时发出多个请求，在终端执行 <code>curl cheat.sh/ab</code>查看怎么使用apache bench。</li>
<li>停止记录并分析内存保持器。</li>
</ol>
<p>如果发现任何大块的保留内存，请尝试将其最小化。这个话题相关的资源很多，你可以从谷歌搜索“如何防止 Node.js 中的内存泄漏”开始探索。</p>
<p>分析你的 Node.js 应用程序并寻找内存使用模式应该是常规做法。让我们把“分析驱动重构”（PDR）提上日程？</p>
<h2 id="">使用缓存避免过多的数据库查找</h2>
<p>这么做的目的是不要每一次应用发出请求都连接一次数据库，使用缓存存储结果可以减少数据库的负载，提高应用性能。</p>
<p>有两种使用缓存的策略。</p>
<p>通过<strong>缓存写入</strong>可确保在发生写入操作时将数据插入数据库和缓存中。这可以使缓存保持相关，并带来更好的性能。缺点是因为不经常使用的数据也被存储到缓存中，所以缓存开销昂贵。</p>
<p>而在<strong>延迟加载</strong>中，数据仅在第一次读取时才写入缓存。第一次请求提供来自数据库的数据，但随后的请求使用缓存。它具有较小的成本，但增加了第一次请求的响应时间。</p>
<p>要决定缓存数据的TTL（或生存时间），请问自己：</p>
<ol>
<li>基础数据要多久更改一次？</li>
<li>将过期数据返回给最终用户的风险是什么？</li>
</ol>
<p>在允许的情况下，<strong>更长的TTL意味着更好的应用表现</strong>。</p>
<p>重要的是，为你的TTL<strong>添加一点增量</strong>。如果应用程序一时间收到大量流量，并且你的所有缓存数据立即过期，则可能导致数据库无法承受负载，从而影响用户体验。</p>
<pre><code>最终TTL = TTL预估值 + 一点随机增量
</code></pre>
<p>TTL的计算</p>
<p>有许多<a href="https://redis.io/docs/manual/eviction/">缓存逐出</a>的策略，保留默认值是一种有效且可接受的方法。</p>
<h2 id="">使用连接池</h2>
<p>打开一个与数据库的独立连接开销很高。它涉及TCP握手、SSL、身份验证和授权检查等。</p>
<p>你可以利用连接池来取代独立连接。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/09/conection-pooling-nodejs-best-practices-for-scale.drawio--1-.png" alt="数据连接池" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>数据连接池</figcaption>
</figure>
<p>一个连接池在任何给定时间都拥有多个连接。每当需要它时，池管理器都会分配任何可用/空闲连接。你可以跳过建立全新连接的冷启动阶段。</p>
<p>那么，为什么不最大化池中的连接数呢？因为它高度依赖于硬件资源。如果忽略这一点，可能会造成巨大的性能损失。</p>
<p>连接越多，每个连接的RAM越少，利用RAM的查询越慢（例如排序）。同样的原则也适用于磁盘和CPU。每个新连接都将被分配到资源。</p>
<p>你可以根据自己的需求调节连接数，对于初学者来说你可以在<a href="https://www.cybertec-postgresql.com/en/tuning-max_connections-in-postgresql/">这里</a>评估连接池的大小。</p>
<p>你可以在<a href="https://www.mongodb.com/docs/manual/administration/connection-pool-overview/">这里</a>阅读连接池相关的内容。若你使用的是PostgreSQL，你可以使用<code>node-postgres</code>包。这是一个<a href="https://node-postgres.com/features/pooling">连接池</a>的内置支持。</p>
<h2 id="">无缝扩展</h2>
<p>当应用程序的用户群逐渐增长并且已经达到垂直扩展的上限时，你会怎么做？水平缩放。</p>
<blockquote>
<p>垂直扩展意味着增加节点（CPU、内存等）资源，而水平扩展意味着增加更多的节点以平衡每个节点的负载。</p>
</blockquote>
<p>如果你使用的是AWS，则可以利用自动扩展组（ASG），它根据预定义的规则（例如，当CPU利用率超过50% 时水平扩展服务器数量。</p>
<p>你可以通过<a href="https://docs.aws.amazon.com/autoscaling/application/userguide/examples-scheduled-actions.html">提前规划行为</a>来提前规划扩展和缩小的计划，以此来应对可以遇见的流量模式（如在世界杯期间的流媒体服务）。</p>
<p>准备好ASG后，在最前面添加负载均衡器将确保流量根据所选策略路由到所有实例（如<a href="https://en.wikipedia.org/wiki/Round-robin_scheduling">round robin</a>）。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/09/alb-nodejs-best-practices-for-scale.drawio--2-.png" alt="基于预定义规则对多个目标进行负载均衡" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>基于预定义规则对多个目标进行负载均衡</figcaption>
</figure>
<p><strong>PS：</strong> 预估单个服务器可以处理的请求（CPU、内存、磁盘等）并分配至少 30%以上的容量总不会错。</p>
<h2 id="openapi">OpenAPI兼容文档</h2>
<p>它可能不会直接影响扩展Node.js应用程序的能力，但我必须将其包含在列表中。如果你曾经做过API集成，你就知道为什么了。</p>
<p>在向前迈出一步之前，了解有关 API 的所有信息至关重要。它使设计的集成、迭代和推理变得容易，更不用说对开发速度的帮助。</p>
<p>确保<strong>为你的 Node.js API 创建 OpenAPI 规范（OAS）</strong>。</p>
<p>这使得你以行业标准的方式创建 API 文档。OAS充当单一的事实来源。如果定义得当，它会使与 API 的交互更加高效。</p>
<p>我在<a href="https://app.swaggerhub.com/apis/Rishabh570/test-API/0.1">这里</a>创建了一个API文档样本，你可以使用<a href="https://swagger.io/tools/swagger-inspector/">swagger inspector</a>来监测任意API。</p>
<p>你可以在<a href="https://app.swaggerhub.com/home">Swagger Hub dashboard</a>找到所有API文档，以及创建一个新的文档。</p>
<h2 id="">行动起来吧！</h2>
<p>我们研究了十个鲜为人知的扩展Node.js的最佳实践，以及如何开启每一个最佳时间。</p>
<p>现在轮到你对照这个清单并探索发现你的Node.js应用程序缺少了什么。</p>
<p>获取你的检查清单✨</p>
<p>希望这篇文章对你有所帮助，并在可扩展性方面为你提供了一些指导。这并不是所有最佳实践的详尽清单——我只是列出了这些我认为不常被提及的。</p>
<p>欢迎在<a href="https://twitter.com/Rishabh570">Twitter</a>上联系我。我很乐意听取你在使用其他最佳实践的经历和心得。</p>
<p>喜欢这篇文章吗？<a href="https://buttondown.email/rishabh570">在这里获取更多后端提升小建议</a> 💌。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何使用 AWS SES、Lambda 和 API Gateway 从网站的“联系我们”表单接收电子邮件 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：How to Receive Emails from Your Site's "Contact Us" form Using AWS SES, Lambda, & API Gateway [https://www.freecodecamp.org/news/how-to-receive-emails-via-your-sites-contact-us-form-with-aws-ses-lambda-api-gateway/] ，作者：Adham El Banhawy [https://www.freecodecamp.org/news/author/adham-el-banhawy/] 我最近在为一个客户建立一个简单的登陆页面网站，该客户希望通过他们的网站接收电子邮件，而不需要分享他们的电子邮件。 说实话，我以前从未尝试过自己实现这一功能。我总是习惯于有一个简单的 “Contact Us” 按钮，有一个锚标签（anchor），在 href  属性里有一个  mailto，像这样： <button>  <a href="mailto:myemail@example.com" ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-receive-emails-via-your-sites-contact-us-form-with-aws-ses-lambda-api-gateway/</link>
                <guid isPermaLink="false">627d16b9c9c067061df8b92f</guid>
                
                    <category>
                        <![CDATA[ AWS ]]>
                    </category>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                    <category>
                        <![CDATA[ Lambda ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ luojiyin ]]>
                </dc:creator>
                <pubDate>Thu, 12 May 2022 10:16:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/05/605b0cfb687d62084bf6bd50.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/how-to-receive-emails-via-your-sites-contact-us-form-with-aws-ses-lambda-api-gateway/">How to Receive Emails from Your Site's "Contact Us" form Using AWS SES, Lambda, &amp; API Gateway</a>，作者：<a href="https://www.freecodecamp.org/news/author/adham-el-banhawy/">Adham El Banhawy</a></p><!--kg-card-begin: markdown--><p>我最近在为一个客户建立一个简单的登陆页面网站，该客户希望通过他们的网站接收电子邮件，而不需要分享他们的电子邮件。</p>
<p>说实话，我以前从未尝试过自己实现这一功能。我总是习惯于有一个简单的 “Contact Us” 按钮，有一个锚标签（anchor），在 <em>href</em> 属性里有一个 <code>mailto</code>，像这样：</p>
<pre><code class="language-html">&lt;button&gt;
 &lt;a href="mailto:myemail@example.com"&gt;Contact Me&lt;/a&gt;
&lt;/button&gt;

</code></pre>
<p>但这种方法有两个不便之处：</p>
<ol>
<li>它迫使双方，即想发送消息的用户和接收消息的网站所有者，彼此分享他们的电子邮件。虽然这对某些人来说是可以的，但对注重隐私的人来说，这并不理想。</li>
<li>对于网站访问者来说，点击链接迫使他们打开他们设备上的默认邮件程序，这可能是令人沮丧的。如果他们使用的是一台公共电脑呢？如果他们没有登录呢？如果他们根本不想使用他们的邮件程序呢？<br>
是的，从技术上讲，他们可以直接抓取收件人的电子邮件地址，并通过他们的浏览器或他们登录的地方发送消息。但这些都是额外的步骤和障碍，会使用户不愿意发送他们的信息，企业可能会失去潜在的反馈或机会。</li>
</ol>
<p>出于这个原因，我们选择了一个电子邮件表格，用户可以简单地写下他们的信息并点击提交，在不离开网站的情况下向网站的主人发送电子邮件。</p>
<p>谷歌快速搜索显示，有一些第三方工具/小工具可以嵌入到网站中，但它们需要付费订阅才能完全定制（译者注：免费版的，有功能上或者数量上的限制，或者有广告插入）。</p>
<p>除非你使用的是像 WordPress 这样的 CMS，有一个内置的/插件可以做到这一点，否则这就是一个不方便的需要经常消费的项目。</p>
<p>我选择了自己编写该功能的代码，这样我就可以完全控制了。</p>
<p>为了本指南的目的，我将重现我使用 HTML 和 AWS 服务实现该功能的步骤。</p>
<h2 id="html">HTML 表格</h2>
<p>我将在这里保持超级简单，使用一个没有 CSS 的基本 HTML 表单，只是为了测试我们想要的功能。</p>
<pre><code class="language-html">&lt;h2&gt;Contact Us&lt;/h2&gt;
&lt;form&gt;
  &lt;label for="name"&gt;Name:&lt;/label&gt;
  &lt;input name="name" type="text"/&gt;&lt;br/&gt;&lt;br/&gt;
  &lt;label for="email"&gt;Email:&lt;/label&gt;
  &lt;input name="email" type="email"/&gt;&lt;br/&gt;&lt;br/&gt;
  &lt;label for="name"&gt;Message:&lt;/label&gt;
  &lt;textarea name="message"&gt;&lt;/textarea&gt;&lt;br/&gt;&lt;br/&gt;
  &lt;input type="submit"/&gt;
  &lt;div&gt;
    &lt;p id="result-text"&gt;&lt;/p&gt;
  &lt;/div&gt;
&lt;/form&gt;

</code></pre>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-61.png" alt="image-61" width="600" height="400" loading="lazy"></p>
<p>这里没有什么花哨的东西可看......</p>
<p>现在我们要用 JavaScript 来处理提交功能。</p>
<pre><code class="language-js">const form = document.querySelector('form')
form.addEventListener('submit', event =&gt; {
  // prevent the form submit from refreshing the page
  event.preventDefault()

  const { name, email, message } = event.target
  console.log('Name: ', name.value)
  console.log('email: ', email.value)
  console.log('Message: ', message.value)

})

</code></pre>
<p>在这一点上，我们有一个从用户那里获得输入的表单，以及只是将结果显示在控制台(console) 的 JavaScript 代码。</p>
<p>我们现在可以先不考虑这个问题，而是开始处理后端服务，这些后端服务将接收表单数据，并将这些数据发送电子邮件。</p>
<h2 id="">后端简介</h2>
<p>让我们深入了解 AWS，以及我们将使用哪些服务和如何使用。</p>
<p>正如标题中提到的，我们将使用 <strong>AWS Lambda</strong> 和 <strong>Simple Email Service</strong>（SES）。SES 是一个无服务器 (serverless) 的消息传递服务，允许你在调用时发送电子邮件。AWS Lambda 允许你编写服务器/端代码，以响应事件的发生而执行。</p>
<p>我们还将使用 <strong>API 网关</strong>，使我们能够通过 HTTP 调用 Lambda 函数。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-62.png" alt="image-62" width="600" height="400" loading="lazy"></p>
<p>在这种情况下，当我们的表单被提交时，将发生以下工作流程：</p>
<ol>
<li>我们的浏览器（JavaScript）将向 AWS API Gateway 指定的端点(endpoint) URL 发出一个 post 请求，请求体中包含表单数据</li>
<li>API 网关将验证这个请求。然后它将触发 Lambda 函数，该函数接受一个事件参数。API Gateway 将把表单数据放在事件参数的 body 属性中。</li>
<li>我们的 Lambda 函数将从事件主体中提取数据，然后我们将使用这些数据来建立我们想要发送的电子邮件的主体以及它的收件人。然后，我们的函数将使用 AWS SDK 来调用 SES 的电子邮件数据。</li>
<li>当 SES 收到 <em>sendMail</em> 请求，它就会将电子邮件数据变成实际的文本电子邮件，并通过 AWS 自己的邮件服务器将其发送给收件人。<br>
一旦电子邮件被发送，我们的浏览器将收到一个状态代码为 200 的响应和一个成功信息。如果 AWS 云中的任何步骤失败，响应将有一个 500 状态代码。</li>
</ol>
<h2 id="1ses">第 1 步：如何建立 SES</h2>
<p>实际上，我们将按照相反的顺序来设置每一个步骤，从 SES 开始，这将会更容易。</p>
<p>首先在你的 AWS 控制台，进入 <code>SES service</code> —&gt; 然后点击侧面菜单中的 <code>Email Addresses</code> —&gt; 然后点击 "Verify a New Email Address" 按钮。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-63.png" alt="image-63" width="600" height="400" loading="lazy"></p>
<p>在打开的对话框中，输入你希望 SES 服务在发送电子邮件时的发件人地址。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-64.png" alt="image-64" width="600" height="400" loading="lazy"></p>
<p>这将向你输入的电子邮件地址发送一封电子邮件，其中有一个链接，可以点击验证。AWS 将知道电子邮件的所有者同意将他们的电子邮件地址作为发件人地址。</p>
<p>在你验证电子邮件之前，SES 电子邮件仪表板将保持验证状态为待定（pendin verification）。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-65.png" alt="image-65" width="600" height="400" loading="lazy"></p>
<p>当电子邮件所有者打开他们从 AWS 收到的电子邮件，并点击其中的验证链接，验证状态应该变为已验证（verified ，刷新页面以看到变化）。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-66.png" alt="image-66" width="600" height="400" loading="lazy"></p>
<p>而这就是你为 SES 所要做的一切。你可以选择测试服务，在列表中选择你经过验证的电子邮件，然后点击 “Send a Test Email” 按钮。这将让你输入收件人的电子邮件地址、主题（subject）和信息（message）并发送。</p>
<p>发送的电子邮件将由 AWS 服务器签署，发件人应该是你输入的发件人地址。它应该看起来像这样：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-67.png" alt="image-67" width="600" height="400" loading="lazy"></p>
<h2 id="2lambda">第 2 步：如何设置 Lambda</h2>
<p>现在这是最有趣的部分。我们将创建一个函数，用来接收表单数据并调用 SES。</p>
<p>Lambda 函数的好处是，你不必担心在服务器上 24/7 运行你的后端代码，也不必担心维护服务器。它是 <em>无服务器的（serverless）</em>。</p>
<p>但这并不意味着不涉及服务器。AWS 将幕后处理这些问题，因此你可以只专注于编写代码，而不是维护服务器。此外，你只需按你的函数被调用的次数和执行的时间来收费，而且是 <a href="https://aws.amazon.com/lambda/pricing/">难以置信的便宜</a>！</p>
<h3 id="iam">创建一个 IAM 角色并进行配置</h3>
<p>在我们开始编写 lambda 函数之前，我们需要创建一个 IAM <em>role（角色）</em>，将其附加到函数上，并授予它调用 SES 服务的权限（在 AWS 中被称为策略）。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-68.png" alt="image-68" width="600" height="400" loading="lazy"></p>
<p>从你的 AWS 控制台，进入 IAM service-&gt;点击侧面菜单中的 <code>service</code> -&gt;然后点击 <code>Create Policy</code> 按钮。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-69.png" alt="image-69" width="600" height="400" loading="lazy"></p>
<p>在策略创建页面，进入 JSON 标签，粘贴以下权限，然后点击 <code>Next</code>。</p>
<pre><code class="language-json">{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ses:SendEmail",
                "ses:SendRawEmail"
            ],
            "Resource": "*"
        }
    ]
}

</code></pre>
<p>在第三个屏幕中，为政策命名，并点击 <code>Create Policy</code> 按钮。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-70.png" alt="image-70" width="600" height="400" loading="lazy"></p>
<p>现在我们创建一个 IAM <em>role(角色)</em>，它将被附加到 lambda 上，并将其与我们刚刚创建的权限策略联系起来。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-71.png" alt="image-71" width="600" height="400" loading="lazy"></p>
<p>从 IAM 侧面的菜单，点击角色，然后点击 <code>Create role</code> 按钮。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-72.png" alt="image-72" width="600" height="400" loading="lazy"></p>
<p>在角色创建界面，确保选择的类型是 “AWS service”，并选择 <code>Lambda case</code>，然后点击 “Next:Permissions” 按钮。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-73.png" alt="image-73" width="600" height="400" loading="lazy"></p>
<p>在下一个屏幕上，按名称搜索我们先前创建的 <code>policy</code> 并选择它，然后点击 <code>Next</code>。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-74.png" alt="image-74" width="600" height="400" loading="lazy"></p>
<p>查看屏幕，给这个角色起一个你能记住的名字，然后点击 “Create role”。</p>
<p>现在我们可以创建一个新的 lambda 函数。转到 Lambda 服务仪表板，点击 “Create Function” 按钮。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-75.png" alt="image-75" width="600" height="400" loading="lazy"></p>
<p>在函数创建界面，命名你的函数，选择 “Author from scratch” 选项，并选择 Node.js 作为运行时间。</p>
<p>在 “Change default execution role（改变默认执行角色）”下，选择 “Use an existing role（使用现有角色）”选项，然后从 “Existing role（现有角色）”下拉菜单中选择你在前一步创建的角色名称。</p>
<p>最后，点击 “Create function” 按钮来创建函数。</p>
<h3 id="">编写代码并测试它</h3>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-76.png" alt="image-76" width="600" height="400" loading="lazy"></p>
<p>在编辑器中，打开 index.js 文件（这是在你的 lambda 被调用时将被执行的文件），用以下代码替换它：</p>
<pre><code class="language-js">const aws = require("aws-sdk");
const ses = new aws.SES({ region: "us-east-1" });
exports.handler = async function (event) {
  console.log('EVENT: ', event)
  const params = {
    Destination: {
      ToAddresses: ["your@email.com"],
    },
    Message: {
      Body: {
        Text: {
            Data: `Hello from Lambda!`
        },
      },
      Subject: { Data: `Message from AWS Lambda` },
    },
    Source: "your@email.com",
  };

  return ses.sendEmail(params).promise()
};

</code></pre>
<p>请注意，在第 2 行，我们正在使用 AWS SDK 并创建一个 SES 实例。我之所以选择 <strong>us-east-1</strong> 作为区域，是因为那是我注册和验证电子邮件的地方。请确保你的电子邮件使用你注册电子邮件的 AWS 地区。</p>
<p>现在要测试这个功能，点击 “Deploy” 按钮。然后点击 “Test” 按钮-&gt;配置测试事件，这将打开一个测试配置对话框，你可以创建一个新的测试事件。</p>
<p>在测试事件主体编辑器中，输入以下 JSON，模仿最终将来自我们的浏览器请求的内容。然后点击创建。</p>
<pre><code class="language-json">{
  "body": {
        "senderName": "Namo",
        "senderEmail": "namo@trains.com",
        "message": "I love trains!"
    }
}

</code></pre>
<p>现在点击 <code>test</code> 按钮将运行我们刚刚创建的测试。它应该在编辑器中打开一个新的标签，向我们展示运行该函数所产生的日志，它应该是这样的：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-77.png" alt="image-77" width="600" height="400" loading="lazy"></p>
<p>注意，我们登录的事件对象在这里显示在功能日志下，其中有我们在测试事件中使用的主体数据。</p>
<p>This test should have sent an email to my inbox as well – let's see if that happened.</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-78.png" alt="image-78" width="600" height="400" loading="lazy"></p>
<p>是的，就像预期的那样。而这几乎是在运行测试后立即发生的。</p>
<p>现在让我们修改我们的函数代码，从测试数据中得到一个更有意义的信息。</p>
<pre><code class="language-js">const aws = require("aws-sdk");
const ses = new aws.SES({ region: "us-east-1" });
exports.handler = async function (event) {
  console.log('EVENT: ', event)
 // Extract the properties from the event body
  const { senderEmail, senderName, message } = JSON.parse(event.body)
  const params = {
    Destination: {
      ToAddresses: ["the.benhawy@gmail.com"],
    },
  // Interpolate the data in the strings to send
    Message: {
      Body: {
        Text: {
            Data: `You just got a message from ${senderName} - ${senderEmail}:
            ${message}`
        },
      },
      Subject: { Data: `Message from ${senderName}` },
    },
    Source: "the.benhawy@gmail.com",
  };

  return ses.sendEmail(params).promise();
};

</code></pre>
<p>需要注意的是，当 API Gateway 调用我们的函数时，它将传递一个字符串给事件主体（event body）。这就是为什么我在 event.body 上使用<code>JSON.parse</code>，把它变成 JSON 并提取我们发件人的电子邮件、姓名和信息。然后我在邮件正文文本和主题中使用这些变量，使用字符串插值。</p>
<p>如果你尝试测试它，代码将返回一个错误。这是因为测试将一个 JSON 对象传递给 event.body，而我们在 JSON 上使用 JSON.parse，这在 JavaScript 中会导致错误。</p>
<p>遗憾的是，测试编辑器不允许我们向事件传递字符串，所以我们必须在以后从别的地方测试。</p>
<h2 id="3api">第 3 步：如何设置 API 网关</h2>
<p>接下来，我们要使用的最后一项 AWS 服务是 API 网关，它将使我们的浏览器能够向我们创建的 Lambda 函数发送 HTTP 请求。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-79.png" alt="image-79" width="600" height="400" loading="lazy"></p>
<p>不用离开你的 lambda 函数页面，展开 “Function overview” 部分并点击 “Add trigger（添加触发器）”。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-80.png" alt="image-80" width="600" height="400" loading="lazy"></p>
<p>接下来，从下拉菜单中选择 <code>API Gateway</code> ，<code>HTTP API</code> 作为 API 类型，"Open(开放)" 作为安全策略，并选中 <code>CORS</code> 复选框选项。然后点击 "Add"。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-81.png" alt="image-81" width="600" height="400" loading="lazy"></p>
<p>你应该被重定向到你的函数的 “Configuration” 标签，显示你刚刚创建的新的 API 网关触发器。在那里，注意 <strong>API endpoint</strong>。这就是接收从浏览器中表单发出数据的 URL。</p>
<h2 id="html">回到 HTML</h2>
<p>我们最后可以测试一下这个表单，看看它是否发送了邮件。</p>
<p>让我们修改我们的 JavaScript 来处理表单提交时的发送请求。</p>
<pre><code class="language-js">const form = document.querySelector("form");
form.addEventListener("submit", (event) =&gt; {
  // prevent the form submit from refreshing the page
  event.preventDefault();

  const { name, email, message } = event.target;

 // Use your API endpoint URL you copied from the previous step
  const endpoint =
    "&lt;https://5ntvcwwmec.execute-api.us-east-1.amazonaws.com/default/sendContactEmail&gt;";
  // We use JSON.stringify here so the data can be sent as a string via HTTP
 const body = JSON.stringify({
    senderName: name.value,
    senderEmail: email.value,
    message: message.value
  });
  const requestOptions = {
    method: "POST",
    body
  };

  fetch(endpoint, requestOptions)
    .then((response) =&gt; {
      if (!response.ok) throw new Error("Error in fetch");
      return response.json();
    })
    .then((response) =&gt; {
      document.getElementById("result-text").innerText =
        "Email sent successfully!";
    })
    .catch((error) =&gt; {
      document.getElementById("result-text").innerText =
        "An unkown error occured.";
    });
});

</code></pre>
<p>现在，是关键时刻：填写表格并点击提交。如果你看到成功信息，这意味着电子邮件已经发送。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-82.png" alt="image-82" width="600" height="400" loading="lazy"></p>
<p>由于我向自己的邮箱发送电子邮件，因此我快速查看我的收件箱，以查看我收到了一封来自我自己的电子邮件，其中包含我在表单中使用的详细信息！</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2021/03/image-83.png" alt="image-83" width="600" height="400" loading="lazy"></p>
<p>跟着教程，你现在拥有一个功能正常的 “Contact Us” 的表单，你可以将其插入任何网站。 而且你只会在实际使用时才需要付费。</p>
<p>我不知道你怎么想的，但我认为这非常棒，几乎是神奇的！而且这是一个很好的、实用的方法。这是一种在你的工作流程中使用云计算/服务的好的、实用的方法。</p>
<p>当然，你可以定制这个流程，在前端使用 React 或 Vue 等框架，或使用 Python 或 Go 等不同的编程语言来实现 Lambda。</p>
<h2 id="">在你离开之前</h2>
<p>感谢你读到这里！我写的文章是关于 JavaScript、云计算开发，以及我作为一个自学成才的开发者的个人教育和职业经历。你可以在 Twitter 上关注我 <a href="https://twitter.com/adham_benhawy">@adham_benhawy</a>，我也会在 Twitter 上发表相关的文章！</p>
<h3 id="">资源</h3>
<ul>
<li><a href="https://aws.amazon.com/premiumsupport/knowledge-center/lambda-send-email-ses/">https://aws.amazon.com/premiumsupport/knowledge-center/lambda-send-email-ses/</a></li>
<li><a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-invocation.html">https://docs.aws.amazon.com/lambda/latest/dg/lambda-invocation.html</a></li>
<li><a href="https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html?icmpid=docs_lambda_console">https://docs.aws.amazon.com/lambda/latest/dg/services-apigateway.html?icmpid=docs_lambda_console</a></li>
</ul>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 如何写好 API 文档 ]]>
                </title>
                <description>
                    <![CDATA[ 原文：How to Write Good API Documentation [https://www.freecodecamp.org/news/how-to-write-api-documentation-like-a-pro/] ，作者：Maybell Obadoni [https://www.freecodecamp.org/news/author/maybell/] 想象一下，你刚买了一个新的家庭影院系统，你去设置它，首先要做什么？ 谢天谢地，你有一本方便的设备手册来帮助你。你只需要按照手册中详细说明的步骤进行操作，然后就可以了。你的家庭影院系统已经准备好播放你喜欢的歌曲了。 就像设备手册如何指导你进行设置和安装一样，API文档可以帮助指导你完成配置API。 什么是API文档？ 在深入研究API文档之前，让我简单解释一下什么是API以及它的基本功能。 API是Application Programming Interface（应用编程接口）的首字母缩写。 通过API将设备连接到数据库无论你是初级编码员还是高级开发人员，你都会在你的软件开发旅程中经常遇到这个术语。它 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/how-to-write-api-documentation-like-a-pro/</link>
                <guid isPermaLink="false">625c11c699ec7406219e6abe</guid>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Miya Liu ]]>
                </dc:creator>
                <pubDate>Fri, 15 Apr 2022 08:00:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2022/04/WechatIMG3959.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>原文：<a href="https://www.freecodecamp.org/news/how-to-write-api-documentation-like-a-pro/">How to Write Good API Documentation</a>，作者：<a href="https://www.freecodecamp.org/news/author/maybell/">Maybell Obadoni</a></p><!--kg-card-begin: markdown--><p>想象一下，你刚买了一个新的家庭影院系统，你去设置它，首先要做什么？</p>
<p>谢天谢地，你有一本方便的设备手册来帮助你。你只需要按照手册中详细说明的步骤进行操作，然后就可以了。你的家庭影院系统已经准备好播放你喜欢的歌曲了。</p>
<p>就像设备手册如何指导你进行设置和安装一样，API文档可以帮助指导你完成配置API。</p>
<h2 id="api">什么是API文档？</h2>
<p>在深入研究API文档之前，让我简单解释一下什么是API以及它的基本功能。</p>
<p>API是Application Programming Interface（应用编程接口）的首字母缩写。</p>
<figure class="kg-card kg-card-image kg-card-hascaption">
    <img src="https://www.freecodecamp.org/news/content/images/2022/04/7020614.jpg" alt="通过API将设备连接到数据库" class="kg-image" width="600" height="400" loading="lazy">
    <figcaption>通过API将设备连接到数据库</figcaption>
</figure>
<p>无论你是初级编码员还是高级开发人员，你都会在你的软件开发旅程中经常遇到这个术语。它是你的计算机、手机或应用程序与外部资源之间的桥梁。</p>
<p>换句话说，API赋予你的软件与其他软件程序、数据库或资源互动的能力。与其为你的应用程序的某一特定功能编写程序，你可以使用具有类似功能的现成的API。</p>
<p>许多API是公共的（免费的），而其他的则是私有的，需要付费购买一个让你访问API的私钥。有不同类型的API，如REST（Representational State Transfer）、SOAP（Simple Object Access Protocol）以及其他。</p>
<p>那么什么是API文档？嗯，它是一个书面指南，说明API的功能、如何将其整合到你的程序中以及API的使用案例，同时还有例子。</p>
<p>请记住，API文档是技术内容。这意味着它将包含一些技术术语，但仍然应该是可读的和容易理解的。</p>
<h2 id="api">API文档由谁编写？</h2>
<p>API是由软件开发人员编写的。由于软件开发人员直接参与了API的建设和使用，所以他们更容易创建文档。</p>
<p>软件开发人员编写API文档的缺点是，他们从一个非常技术性的角度来写，这可能使文档相当难以理解。另一个问题是，API开发者在开发API的同时，还要花费更多的时间来创建文档。</p>
<p>因此，一个好的选择是将API文档的任务分配给技术作家。技术作家是将内容写作和技术知识的专业知识结合起来的人，他制作的文档不仅是技术性的，而且是信息丰富的、可以理解的。</p>
<p>技术文档写作者从API开发者那里了解API，然后创建教程、例子和其他内容，用于编写文档。</p>
<p>同时，API开发者给技术文档写作者提供意见，以确保书面文档的准确性，必要时他们可以向写作者提供更多信息。</p>
<p>我们的目标是让每个人一起工作，制作出能够充分解释API并引导用户的文档，而不至于出现混乱。</p>
<p>如果你对编写API的文档感兴趣，但不知道从哪里开始，也不知道如何开始，这篇文章将帮助你开始。</p>
<p>我可以从这里感受到你的兴奋，所以让我们开始行动吧！</p>
<h2 id="api">如何开始编写API文档</h2>
<p>在编写API文档时，首先要创建几个大纲。这将使你对你打算写的东西有一个概述。</p>
<p>接下来就是为你创建的每个大纲收集信息。这可以通过从API开发者那里获得API描述、使用的语言、其他参考资料和样本案例来实现。你也可以查看API的在线演示，这样你就有了关于它如何工作的第一手经验。</p>
<p>最后，把你收集到的细节结合起来，按逻辑顺序排列。</p>
<p>记得校对你的文件，并在公开前与API开发者分享，以便进行任何修正或补充。</p>
<p>现在你知道从哪里开始，你如何把这些零碎的东西放在一起，使它们成为一个有意义的整体？</p>
<h2 id="api">API文档中应包括哪些内容</h2>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/API-Doc.png" alt="API-Doc" width="600" height="400" loading="lazy"></p>
<h3 id="1">1. 概述</h3>
<p>这类似于项目报告的摘要页。</p>
<p>概述应该包含API的摘要和它所解决的问题。它还可以包括使用这个特定API比其他类似API的好处。</p>
<h3 id="2">2. 教程</h3>
<p>这是文档的主要部分。</p>
<p>它应该包括你所使用的不同的内容格式，向用户解释API的概念。它还可以包括供参考的链接，以及整合API和使用它的步骤指南，以便它能正常运作。</p>
<h3 id="3">3. 例子</h3>
<p>当你解释了API的工作原理和/或提供了分项步骤，展示调用、响应、错误处理和其他与开发者如何与API互动有关的操作的例子是个好主意。</p>
<h3 id="4">4. 词汇表</h3>
<p>虽然这是可选的，但我建议为你的API文档添加一个词汇表页面。</p>
<p>为了避免用户被冗长的文本块所烦扰，你在整个文档中使用的各种术语、模式、图像等的解释都可以集中到词汇表中。然后你可以在文档中引用这些东西，并链接到词汇表。</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2022/04/Talk2Her-Foundation--15-.png" alt="A PC and a notepad" width="600" height="400" loading="lazy"></p>
<h2 id="api">如何编写有用的API文档</h2>
<h3 id="api">了解 API</h3>
<p>正如我们刚才所讨论的，你应该对你所记录的API有第一手的知识。记住，你的目标是引导那些可能对API没有任何了解的潜在用户。你不会想让他们感到困惑，对吗？</p>
<p>如果你对产品的架构、功能和其他重要信息有扎实的了解，你就能有效地编写API的产品描述部分，而不需要做任何猜测。</p>
<p>如果你对你要写的API没有充分的了解，那就花点时间做研究，尽可能多地收集信息。自己使用该API，这样你就能对它的工作原理有重要的了解。</p>
<h3 id="">使用相关的内容</h3>
<p>API文档不只限于书面指南。你可以使用简短的视频或PPT幻灯片来说明API的整合情况。</p>
<p>在写文档的时候说明不同的用例。这将有助于读者认识到哪一个与他们的相似，或者找到一个他们可以很容易联系到的。</p>
<p>另外，在你认为有必要的地方和时候，包括一些代码片段。这将使读者有可能在阅读文档时跟着走。就像那句流行语说的，“告诉我，我会忘记。教我，我就会记住。让我参与，我就会学习”。</p>
<h3 id="">要清楚，即使你需要技术性的</h3>
<p>API是软件或硬件的指南，所以你在写文档时需要使用一些技术术语。如果你想成为一名技术作家，请抵制含糊其辞。</p>
<p>一份好的文件不是有复杂的语法结构，而是友好的、直接的、清晰的。只有用简单易懂的语言来写，才会有亲和力。</p>
<p>你的API文档应该以最简单的形式出现，但它不应该遗漏任何重要的细节。另外，确保你在第一次使用缩略语和技术术语时对其进行解释，或在文档的最后将其放在词汇表中。</p>
<h3 id="">指南分项说明</h3>
<p>如果内容被逐项列出，文件就更容易理解。这是写得简洁的一个主要原因。</p>
<p>对指南的步骤进行编号或分项，有助于用户弄清楚在每个时间点上应该做什么。这类似于从A到Z阅读字母表。</p>
<p>有了明确的步骤，用户在遇到错误时可以很容易地找到方向。</p>
<h3 id="">检查错误</h3>
<p>当你阅读一份文件时，总会有一些东西需要修改、更新，甚至删除。这是作家们的都要经历过，它不应该让你感到不安。</p>
<p>黄金在精炼出来之前要经过几个火热的熔炉。让我们说，你的文件应该经历一个类似的过程（虽然不是火炉），所以它出来时是一个精心准备的文件。</p>
<p>一个彻底的检查过程可以帮助你尽量减少任何错误并产生清晰的文件。</p>
<h2 id="api">API文档的最佳工具</h2>
<p>编写API文档可能相当耗费时间，而且难以维护。但是一个好的文档工具可以缓解大部分，甚至是所有的问题。</p>
<p>现在有许多工具可以让你的API文档之旅变得更容易。使用工具的好处是这些工具提供的协作功能和标准模板，而不是从头开始。</p>
<p>下面是一些流行的工具及其优点的清单。</p>
<h3 id="postman">Postman</h3>
<p><a href="https://www.postman.com/">Postman</a>是一个用于构建和维护API的平台，具有创建API文档的功能。</p>
<p>Postman使用其机器可读的文档工具，使API文档过程更容易和更快。你可以免费注册Postman并将其安装在你的PC上。</p>
<p>尽管Postman对其制作的所有API文档自动提供更新，但其用户界面一开始可能难以理解。</p>
<h3 id="dapperdox">DapperDox</h3>
<p><a href="http://dapperdox.io/">DapperDox</a> 是一个开源的API文档工具，提供各种主题来创建你的文档。这个工具结合了图表、规范和其他内容类型，为你提供更好的文档。</p>
<p>它的优点是允许作者用 GitHub 风格的 markdown 编写，但这个工具的更新是不定期的。</p>
<h3 id="swaggerhub">SwaggerHub</h3>
<p><a href="https://swagger.io/tools/swaggerhub/">SwaggerHub</a> 是在许多技术作者中一个流行的API文档工具，因为它是互动的，易于使用。</p>
<p>虽然它对初学者很友好，但除了个人使用外，它需要付费。因此，如果你是一个组织的一部分，想使用SwaggerHub，你的组织将不得不为它付费。</p>
<p>无论你是选择这里列出的工具还是选择其他工具，你都应该考虑以下几点：</p>
<ul>
<li>你将在什么环境下使用该工具？是用于个人使用还是作为组织的一部分？</li>
<li>你的技术水平如何？你是初学者还是专家？</li>
<li>用户界面和用户体验如何？</li>
</ul>
<h2 id="api">一些值得学习的API文档的例子</h2>
<p>下面是一些API文档，它们给你开始编写优秀的API文档的灵感。这些文件中的每一个都以简单的步骤和可理解的术语向开发者详细介绍了产品API的用法。</p>
<h3 id="githubapidocs">GitHub API Docs</h3>
<p>GitHub提供了非常有用的文档。在这里查看他们的API文档：</p>
<p><a href="https://docs.github.com/en/rest/guides/getting-started-with-the-rest-api">GitHub API Docs</a></p>
<p>REST API是开发人员用来访问网络或数据库数据的常用API。Github的这个文档包括概述、指南，甚至是如何在你的程序中使用REST API的代码。</p>
<p>这些文档的有趣之处在于，无论你的技术水平如何，你都可以轻松地理解它。</p>
<h3 id="paystackapidocs">Paystack API Docs</h3>
<p><a href="https://paystack.com/docs/">Paystack API Docs</a></p>
<p>你是否正在构建一个需要支付的应用程序？Paystack是一个用于支付的金融技术解决方案。他们的团队为开发者提供详细的信息，说明如何在你的程序中使用Paystack的API。这更像是提供一本关于使用API的手册，以避免在将API消耗到你的程序中时出现混乱。</p>
<h3 id="twitterapidocs">Twitter API Docs</h3>
<p><a href="https://developer.twitter.com/en/docs/twitter-api">推特API文档</a></p>
<p>Twitter的API文档解释了开发者如何与App互动。这些文件清楚地详述了不同的部分（用户、推文、直接信息等）和它们的操作。</p>
<p>虽然更多的信息需要权限访问，但你只需点击一下链接，就可以访问基本的信息。</p>
<h2 id="">结语</h2>
<p>文档阐述了一个工具是如何工作的，以便其他人能够正确使用它。创建API文档并不容易，但创建有用的文档并不像你想象的那么难。</p>
<p>只要记住：从写你的初稿开始，每天改进它，当你遇到困难时，向导师或资深同事寻求帮助。</p>
<p>现在就去写那些将与下一个世界级产品一起使用的API文档吧。</p>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 你距离 WebGL 只差一点！ ]]>
                </title>
                <description>
                    <![CDATA[ 在本文中，我会通过绘制一个点，带领大家走进WebGL的世界。并且本文不会涉及 D3 / ThreeJS 等 WebGL 库，就用原生的WebGL API 绘制一个点！ 前言 首先，WebGL并不是一门语言，它是一个标准，它是在OpenGL ES的基础上所建立的一套适用于浏览器的图形学标准；而OpenGL ES则是OpenGL 的一个特殊版本（套娃警告 ），ES版本被广泛的应用于手机、家用游戏机等设备。想了解更多关于WebGL标准内容的小伙伴可以进入Khronos Group [https://link.zhihu.com/?target=https%3A//www.khronos.org/webgl/]的网站自行浏览。 WebGL的开发与我们普通的前端开发并没有什么太大差异，一个浏览器的网页一般是由：HTML、 JavaScript、渲染引擎等部分组成，如果我们要开发WebGL 的话，还需要什么呢？让我们来思考一下，我们在高中学习几何的时候老师讲过“点动成线，线动成面，面动成体”，那我们就以最基础的点为例，首先点 有什么属性么？在屏幕中的位置、点的颜色、点的大小，我们如何定义一个点 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/learn-webgl-through-drawing-a-dot/</link>
                <guid isPermaLink="false">5f96cae55f583f0565090bfb</guid>
                
                    <category>
                        <![CDATA[ WebGL ]]>
                    </category>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 前端开发 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Jiahao Li ]]>
                </dc:creator>
                <pubDate>Sat, 03 Apr 2021 08:19:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2020/10/azharul-islam-9LMGWHqUwnc-unsplash--1-.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>在本文中，我会通过绘制一个点，带领大家走进<code>WebGL</code>的世界。并且本文不会涉及 <code>D3 </code>/ <code>ThreeJS</code> 等 <code>WebGL</code> 库，就用原生的<code>WebGL API</code>绘制一个点！</p><h2 id="-"><strong>前言</strong></h2><p>首先，<code>WebGL</code>并不是一门语言，它是一个标准，它是在<code>OpenGL ES</code>的基础上所建立的一套适用于浏览器的图形学标准；而<code>OpenGL ES</code>则是<code>OpenGL</code>的一个特殊版本（套娃警告 ），<code>ES</code>版本被广泛的应用于手机、家用游戏机等设备。想了解更多关于<code>WebGL</code>标准内容的小伙伴可以进入<a href="https://link.zhihu.com/?target=https%3A//www.khronos.org/webgl/" rel="nofollow noreferrer">Khronos Group</a>的网站自行浏览。</p><p><code>WebGL</code>的开发与我们普通的前端开发并没有什么太大差异，一个浏览器的网页一般是由：HTML、 JavaScript、渲染引擎等部分组成，如果我们要开发<code>WebGL</code>的话，还需要什么呢？让我们来思考一下，我们在高中学习几何的时候老师讲过“点动成线，线动成面，面动成体”，那我们就以最基础的<strong>点</strong>为例，首先<strong>点</strong>有什么属性么？在屏幕中的位置、点的颜色、点的大小，我们如何定义一个<strong>点</strong>的这些属性呢？这就要引入<code>GLSL ES(OpenGL Shader Language ES)</code>（后称着色器）了，着色器的写法与C语言语法有些相似，从名字也能看出<code>WebGL</code>与<code>OpenGL ES</code>是有“血缘关系”的！其次，我们还需要的就是浏览器厂商基于<code>WebGL</code>标准提供的<code>API</code>。</p><p><code>WebGL</code>并不像<code>OpenGL</code>一样有繁琐的环境配置的流程，也没有对系统的要求，只要有一个支持<code>WebGL</code>的浏览器即可！</p><blockquote>本次我们使用字符串的形式编写着色器，暂时不新建单独的着色器文件​​</blockquote><h2 id="--1"><strong>给这个“点”一点自由的空间</strong></h2><p>为了使用浏览器提供的<code>WebGL</code>接口，我们需要使用<code>&lt;canvas&gt;</code>来获取<code>WebGL</code>上下文：</p><pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
  &lt;meta charset="UTF-8"&gt;
  &lt;title&gt;Point&lt;/title&gt;
&lt;/head&gt;
&lt;body onload="main()" style="padding: 0; margin: 0;"&gt;
  &lt;canvas id="webgl" width="600" height="400"&gt;
    您使用的浏览器不支持 WebGL！
  &lt;/canvas&gt;
  &lt;script&gt;
    function main() {
      // get canvas element
      const canvas = document.getElementById("webgl");
      const gl = canvas.getContext('webgl');
    }
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre><p><code>gl</code>就是我们所获取到的<code>WebGL</code>渲染的上下文 让我们给画布填充个背景色吧：</p><pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;!-- ... --&gt;
  &lt;script&gt;
    function main() {
      // ...
      const gl = canvas.getContext('webgl');
      
      gl.clearColor(0.0, 0.0, 0.0, 1.0);
      gl.clear(gl.COLOR_BUFFER_BIT);
    }
  &lt;/script&gt;
  &lt;!-- ... --&gt;
&lt;/html&gt;</code></pre><p>这样我们要绘制点的画布就拥有了浩瀚宇宙一般深邃的黑色:) 喝点庆祝一下</p><figure class="kg-card kg-image-card"><img src="https://pic3.zhimg.com/80/v2-214a4e5c2c9bc82b80375a13694a4a0e_720w.jpg" class="kg-image" alt="v2-214a4e5c2c9bc82b80375a13694a4a0e_720w" width="600" height="400" loading="lazy"></figure><p><br>解释一下<code>gl.clearColor</code>方法是设置清除画布的背景色，形式是<code>RGBA</code>；<code>gl.clear</code>则是调用清除画布的方法，可传递的参数<code>gl.COLOR_BUFFER_BIT</code>是个什么呢 其实该方法继承自<code>OpenGL</code>，<code>OpenGL</code>是基于多缓冲区模型的，清空绘图区域实际上是在清空颜色缓冲区，传递参数<code>gl.COLOR_BUFFER_BIT</code>是在告诉<code>WebGL</code>清空颜色缓冲区；除此之外还有深度缓冲区以及模板缓冲区，可<a href="https://link.zhihu.com/?target=https%3A//books.google.com.hk/books%3Fid%3D9H10DwAAQBAJ%26pg%3DSA16-PA39%26lpg%3DSA16-PA39%26dq%3Dcolor%2Bbuffer%2Bbit%26source%3Dbl%26ots%3DmTRM9iN19f%26sig%3DACfU3U2QEwxLDrB9I4rnjjwmLOZm07E-5A%26hl%3Dzh-CN%26sa%3DX%26ved%3D2ahUKEwjtk5f5zNjoAhW1JaYKHQLZAQEQ6AEwDXoECA0QPg%23v%3Donepage%26q%3Dcolor%2520buffer%2520bit%26f%3Dfalse" rel="nofollow noreferrer">查看此了解</a>。</p><h2 id="--2"><strong>让距离近一“点”</strong></h2><p>下面我们开始绘制<strong>点</strong> 在前面我们分析道一个点有<strong>位置、颜色及大小</strong>三个属性，下面我们将编写着色器给深邃的画布增添一点色彩</p><pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;!-- ... --&gt;
  &lt;script&gt;
    function main() {
      // ...
      const VertexShader = `
        void main() {
          gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
          gl_PointSize = 10.0;
        }
      `;
      const FragmentShader = `
        void main() {
          gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);
        }
      `;
    }
  &lt;/script&gt;
  &lt;!-- ... --&gt;
&lt;/html&gt;</code></pre><p>上面我们定义了<code>VertexShader</code>和<code>FragmentShader</code>，在<code>WebGL</code>中有两种着色器分别是：顶点着色器和片元着色器：</p><ul><li>顶点着色器：用来描述顶点的特性的程序，比如位置、大小等。顶点是指二维或三维空间中的一个点，比如二维图形或三维图形的顶点或交点；</li><li>片元着色器：也称像素着色器，进行逐片的处理过程比如光照。片元可以理解为像素。</li></ul><p>同时，每个着色器都有一个<code>main()</code>方法，并且该方法不能指定参数，每行语句结束之后必须有分号！！！ <code>gl_Position</code>、<code>gl_PointSize</code>和<code>gl_FragColor</code>三个变量则是着色器内置的变量，其中<code>gl_PointSize</code>可以不赋值，默认值为1.0。各位注意到，上面赋值语句中我们给的值是0.0而不是0，这是因为这些内置变量是有其变量类型的：</p><figure class="kg-card kg-image-card"><img src="https://pic2.zhimg.com/80/v2-4d4177718230a6815b383ba6eb7510e9_720w.jpg" class="kg-image" alt="v2-4d4177718230a6815b383ba6eb7510e9_720w" width="600" height="400" loading="lazy"></figure><p><br>问：明明一个点的坐标只有(x, y, z)，为什么要传4个值呢？<br>答：这里使用的是齐次坐标的形式，了解齐次坐标可查看我上篇文章<a href="https://link.zhihu.com/?target=https%3A//mp.weixin.qq.com/s/-aZ3tUgMv0uGOmbov-RRhw" rel="nofollow noreferrer">《客官，进来看看图形的几何变换？》</a>。<br>问：使用上面定义的顶点着色器和片元着色器分几步呢？<br>答：分三步！第一步，创建着色器；第二步，创建着色器程序；第三步，在WebGL上下文中使用着色器程序。</p><h2 id="1-">1️⃣创建着色器</h2><p>为了方便使用我把创建着色器的步骤抽取了一个<code>createShader()</code>方法：</p><pre><code class="language-js">function createShader (gl, type, source) {
  const shader = gl.createShader(type);
  if (shader == null) {
    console.warn('无法创建着色器');
    return null;
  }
​
  gl.shaderSource(shader, source);
  gl.compileShader(shader);
​
  const compiled = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
  if (!compiled) {
    console.log('编译着色器失败： ' + gl.getShaderInfoLog(shader));
    gl.deleteShader(shader);
    return null;
  }
​
  return shader;
}
</code></pre><p><code>gl.shaderSource</code>是将<code>gl.createShader</code>创建的着色器的<code>source</code>设置为我们定义的<code>VertextShader</code>或<code>FragmentShader</code>，剩下的就不解释了，函数名都很表意:)</p><h2 id="2-">2️⃣创建着色器程序</h2><p>也为了更简洁，创建着色器程序的步骤也抽成了<code>createProgram()</code>方法：</p><pre><code class="language-js">function createProgram (gl, vshader, fshader) {
  const vertexShader = createShader(gl, gl.VERTEX_SHADER, vshader);
  const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fshader);
  if (!vertexShader || !fragmentShader) {
    return null;
  }
​
  const program = gl.createProgram();
  if (!program) {
    return null;
  }
​
  gl.attachShader(program, vertexShader);
  gl.attachShader(program, fragmentShader);
​
  gl.linkProgram(program);
​
  const linked = gl.getProgramParameter(program, gl.LINK_STATUS);
  if (!linked) {
    console.warn('Link 着色器程序失败： ' + gl.getProgramInfoLog(program));
    gl.deleteProgram(program);
    gl.deleteShader(fragmentShader);
    gl.deleteShader(vertexShader);
    return null;
  }
  return program;
}
</code></pre><p><code>gl.attachShader</code>是将创建好的着色器attach到我们着色器程序上，然后调用<code>gl.linkProgram</code>方法将<code>program</code>整合起来。</p><h2 id="3-">3️⃣在上下文中使用着色器程序</h2><pre><code class="language-js">function initShaders(gl, vshader, fshader) {
  const program = createProgram(gl, vshader, fshader);
  if (!program) {
    console.warn('创建着色器程序失败！');
    return false;
  }
​
  gl.useProgram(program);
  gl.program = program;
​
  return true;
}
</code></pre><p>这里就很简单啦，就不做过多介绍了！然后在<code>main()</code>中调用此方法初始化着色器：</p><pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;!-- ... --&gt;
  &lt;script&gt;
    function main() {
      const canvas = document.getElementById('webgl');
      const gl = canvas.getContext('webgl');
​
      if (!initShaders(gl, VertexShader, FragmentShader)) {
        return alert('初始化着色器失败');
      }
​
      gl.clearColor(0.0, 0.0, 0.0, 1.0);
      gl.clear(gl.COLOR_BUFFER_BIT);
      gl.drawArrays(gl.POINTS, 0, 1);
    }
    // createShader
    // createProgram
    // initShaders
  &lt;/script&gt;
  &lt;!-- ... --&gt;
&lt;/html&gt;</code></pre><p><code>gl.drawArrays</code>的第一个参数是指定绘制方式，第二个参数是从哪个顶点开始绘制，第三个参数是指定绘制要用到多少个顶点。这样我们就能在黑色的画布上的正中心看到一个蓝色的点：</p><figure class="kg-card kg-image-card"><img src="https://pic4.zhimg.com/80/v2-c7d3dbf43eba3912dd22e39a675f3afb_720w.jpg" class="kg-image" alt="v2-c7d3dbf43eba3912dd22e39a675f3afb_720w" width="600" height="400" loading="lazy"></figure><p>但是，小朋友你是否有很多的问号？<br></p><figure class="kg-card kg-image-card"><img src="https://pic2.zhimg.com/80/v2-53a9c506e5838e2b4b1a687f5d399399_720w.jpg" class="kg-image" alt="v2-53a9c506e5838e2b4b1a687f5d399399_720w" width="600" height="400" loading="lazy"></figure><p><br>明明定义的点的位置在<code>(0, 0, 0)</code>，为什么点会出现在<code>&lt;canvas&gt;</code>的正中央呢？<code>WebGL</code>相对于<code>&lt;canvas&gt;</code>的位置如下图：</p><figure class="kg-card kg-image-card"><img src="https://pic1.zhimg.com/80/v2-be538701cc036ed336cdd4255a5f3e88_720w.jpg" class="kg-image" alt="v2-be538701cc036ed336cdd4255a5f3e88_720w" width="600" height="400" loading="lazy"></figure><p>中间的是<code>WebGL</code>相对于<code>&lt;canvas&gt;</code>的坐标，而<code>canvas</code>的坐标则是相对于屏幕的！<code>WebGL</code>相对于<code>&lt;canvas&gt;</code>的坐标并不是绝对的像素值，而是相对的<code>[-1.0, 1.0]</code>。 举个例子：我们展示的点在<code>canvas</code>的正中央，如果我们把点的坐标设置为<code>(1.0, 0.0, 0.0, 1.0)</code>，那么点就会出现在<code>canvas</code>的最右侧，同理设置为<code>(-1.0, 1.0, 0.0, 1.0)</code>，点则展示在<code>canvas</code>的左上角:P</p><h2 id="--3"><strong>渲染这个点经历了什么？</strong></h2><figure class="kg-card kg-image-card"><img src="https://pic3.zhimg.com/80/v2-68aa4b9806166affbbd2d1382b361e8a_720w.jpg" class="kg-image" alt="v2-68aa4b9806166affbbd2d1382b361e8a_720w" width="600" height="400" loading="lazy"></figure><h2 id="--4"><strong>就这么结束了？</strong></h2><p>怎么可能就这么结束！让我们给绘制<strong>点</strong>的程序升级一下，现在我们的位置、大小都是在着色器中定义好的。当然<code>WebGL</code>也为我们提供了方法让我们可以从外部传入相应参数值。让我们对着色器改造一下：</p><pre><code class="language-js">const VertexShader = `
  attribute vec4 a_Position;
  void main() {
    gl_Position = a_Position;
    gl_PointSize = 10.0;
  }
`;
</code></pre><p><code>attribute</code>是一种<code>GLSL SE</code>变量，被用来从外部向顶点着色器内传数据，<strong>只有顶点着色器可以使用</strong>；同时还有一种变量类型<code>uniform</code>，<code>uniform</code>变量传输的是对于所有顶点都相同（或与顶点无关）的数据。上面是着色器代码中，我们将从外部获取到的<code>a_Position</code>和<code>a_PointSize</code>分别赋值给<code>gl_Position</code>和<code>gl_PointSize</code>。怎么通过JavaScript向着色器的<code>attribute</code>变量传值呢？</p><pre><code class="language-js">function main () {
  // ...
  if (!initShaders(gl, VertexShader, FragmentShader)) {
    return alert('初始化着色器失败');
  }
​
  const a_Position = gl.getAttribLocation(gl.program, 'a_Position');
  gl.vertexAttrib3f(a_Position, 1.0, 0.0, 0.0);
  // ...
}
</code></pre><p>使用<code>vertextAttrib3f</code>方法就可以将使用<code>getAttribLocation</code>获取到的<code>attribute</code>变量赋值，<code>vertexAttrib3f</code>方法会将齐次坐标的最后一个值默认赋值为<code>1.0</code>，当然使用<code>vertexAttrib4f</code>也是可以的:)</p><h2 id="--5"><strong>再加点功能</strong></h2><p>当我在<code>canvas</code>上点击的时候，就在点击<code>canvas</code>的地方展示一个点，这就需要我们给<code>canvas</code>绑定方法了：</p><pre><code class="language-js">canvas.onmousedown = function (e) {
  click(e, gl, canvas, a_Position);
};
</code></pre><p>在此就不给详细代码了，<code>canvas</code>绑定事件方式如上，并简单说一下思路：当点击之后获取鼠标在<code>canvas</code>点击的坐标值；然后将坐标转换为<code>WebGL</code>相对于<code>canvas</code>的<code>[-1.0, 1.0]</code>形式的坐标；然后清空画布，在重新绘制点。坐标转换的代码如下：</p><pre><code class="language-js">let x = e.clientX;
let y = e.clientY;
const rect = e.target.getBoundingClientRect();
​
x = (x - rect.left - canvas.width / 2) / (canvas.width / 2);
y = (canvas.height / 2 - y + rect.top) / (canvas.height / 2);
</code></pre><p>比如还能再给每个点设置不同的颜色，<strong>提示：使用 uniform 变量。</strong></p><h2 id="--6"><strong>结束语</strong></h2><p>使用原生<code>WebGL</code>绘制一个“简单的点”就讲到这里啦:)我自己也在不断的学习中，后续会出更多关于<code>WebGL</code>的文章。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 一个不能再普通的函数，如何提供 http 服务？ ]]>
                </title>
                <description>
                    <![CDATA[ 前调：一些吐槽 因为开发过大大大大大大量的 Restful API ，越来越厌烦那种一成不变的代码组织方式。 以 egg为例，要增加一个接口，需要经历繁琐的操作 [路由里注册] ---> [编写 controller] ---> [编写 service]  哪怕我只是想实现一个 a+b => c！ 除此以外，我还调研过某几个比较大的 函数计算 服务商，看看有没有什么好的途径，可以更简便地进行简单接口开发。 让人很失望，天下技术一大抄，它们无一例外都是这样的开发模型： function(event, context, callback) {} 或者 function(event, context) {} 于是你不得不这样写你的代码 // 举例 function(event, context, callback) {   const { a, b } = event.arguments;    ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/way-to-build-restful-api-via-pure-function/</link>
                <guid isPermaLink="false">600a8ecf5f61e30501b5c037</guid>
                
                    <category>
                        <![CDATA[ 函数 ]]>
                    </category>
                
                    <category>
                        <![CDATA[ HTTP ]]>
                    </category>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 开发者小蓝 ]]>
                </dc:creator>
                <pubDate>Fri, 22 Jan 2021 09:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/01/goran-ivos-TorAcb4AQRc-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <h3 id="">前调：一些吐槽</h3>
<p>因为开发过大大大大大大量的 <code>Restful API</code> ，越来越厌烦那种一成不变的代码组织方式。</p>
<p>以 <code>egg</code>为例，要增加一个接口，需要经历繁琐的操作</p>
<pre><code>[路由里注册] ---&gt; [编写 controller] ---&gt; [编写 service] 
</code></pre>
<h4 id="abc">哪怕我只是想实现一个 <code>a+b =&gt; c</code>！</h4>
<p>除此以外，我还调研过某几个比较大的 <strong>函数计算</strong> 服务商，看看有没有什么好的途径，可以更简便地进行<strong>简单接口开发</strong>。</p>
<p>让人很失望，天下技术一大抄，它们无一例外都是这样的开发模型：</p>
<pre><code>function(event, context, callback) {}
或者
function(event, context) {}
</code></pre>
<p>于是你不得不这样写你的代码</p>
<pre><code>// 举例
function(event, context, callback) {
  const { a, b } = event.arguments; 
  callback(a+b);
}
</code></pre>
<h4 id="">这算个什么鬼函数？！</h4>
<p>你必须改变第一直觉，严格按照厂商的要求来写你的代码，并且寄希望于<strong>有人站出来要求它们统一<code>event</code>、<code>context</code>这些旁系知识的实现细节</strong>。</p>
<h3 id="">并且！</h3>
<p>你很难进行本地测试！</p>
<p>你要构造奇奇怪怪的对象作为参数。哪怕只是测试<strong>两数相加</strong>.</p>
<h5 id="ok">OK! 如果你说，稍微学习一下，其实还是可以用的吧。</h5>
<h4 id="">可以用就是我们的追求吗？要 <strong>应然</strong> 还是 <strong>实然</strong> ？</h4>
<h4 id="">难倒我们期望的样子，不应该是这样吗？</h4>
<pre><code>// 🔥🔥
function (a, b) {
	return a+b;
}
</code></pre>
<h3 id="">正文开始：</h3>
<p>所以，如果我需要实现一个 <code>a+b =&gt; c</code> 的接口，</p>
<p>我期望我的代码是这样的：</p>
<pre><code>export default (a: number, b: number): number =&gt; {
  return a + b;
}
</code></pre>
<p>我期望我的测试是这样的：</p>
<pre><code>describe('sum', () =&gt; {
  it('1+1 is 2', () =&gt; {
    assert.equal(sum(1,1), 2);
  });
});
</code></pre>
<h4 id="">十分明显好吗！</h4>
<p>它并不受制于具体平台实现；</p>
<p>它并不需要你学习其他旁系知识，只关注你的功能本身；</p>
<p>它方便测试，因为它就是一个不能再普通的函数；</p>
<p>它可复用！</p>
<p><img src="https://lanhaooss.oss-cn-shenzhen.aliyuncs.com/images/331/1.png" alt="1" width="606" height="604" loading="lazy"></p>
<h3 id="">多说无益，直接动手</h3>
<pre><code>export default (a: number, b: number): number =&gt; {
  return a + b;
}
</code></pre>
<p>有过<a href="https://chinese.freecodecamp.org/news/practice-in-ast-and-code-gen/">上一篇</a>的基础，我们直奔抽象语法树：</p>
<pre><code>// 由于只有一行代码，结构就简单多了
{
	type: 'ExportDefaultDeclaration',
	declaration: {
		type: 'ArrowFunctionExpression',
		params: [ 下文展开 ],
		body: { 不重要 },
		returnType: { 下文展开 }
	}
}

</code></pre>
<p>我们还是从整体上先看一下，上面这个结构充分表达了源代码的意图：</p>
<ul>
<li>这是一个 <code>export default</code>声明 (<code>ExportDefaultDeclaration </code>)</li>
<li>声明的内容是一个箭头函数 (<code>ArrowFunctionExpression </code>)</li>
</ul>
<p>考虑到，我们希望从这一行代码里面，生成<strong>提供HTTP服务</strong>的基础代码，</p>
<p>那么我们的主要任务就是，从以上有限的信息当中，提取出<strong>Restful API</strong>需要的基础信息：</p>
<ul>
<li>Method</li>
<li>Path</li>
<li>请求参数</li>
</ul>
<h4 id="">🤖 我们一个一个来解决：</h4>
<ol>
<li>
<p>HTTP Method</p>
<p>首选是要确定这个接口，最终通过什么 <code>Method</code>对外服务，在这里源代码并不能提供任何有效信息。</p>
<p><strong>那么我们就默认用 GET 好了</strong></p>
<blockquote>
<p>🔥其他的 Method，会在文末提及</p>
</blockquote>
</li>
<li>
<p>Path， 或者叫 URL</p>
<p>对于一个接口来说，这也是十分关键的信息，这里我们有两种解决方案</p>
<ul>
<li>使用函数名，如果有的话</li>
<li>使用当前文件名</li>
</ul>
<p><strong>我们姑且把 URL 定为  <code>/sum</code> 好了</strong></p>
<blockquote>
<p>🔥 笔者的一个实验性项目里，使用的是文件名</p>
</blockquote>
</li>
<li>
<p>请求参数</p>
<p>如果说前面两点都是基于约定，或者一些简单的手段，</p>
<p>那么在请求参数这个环节，我们必须上价值了，要让 <code>ast</code> 发挥作用！</p>
<p>我们先展开一下上面 <code>ast</code> 里关于函数参数的部分：</p>
<pre><code>// params 部分
[
	{
		name: 'a',
		typeAnnotation: { type: 'TSNumberKeyword' }
	},
	{
		name: 'b',
		typeAnnotation: { type: 'TSNumberKeyword' }
	},
]
</code></pre>
<h4 id="ab">好家伙，可以拿到参数名 <code>a</code> 和 <code>b</code> 了</h4>
<p>结合 <code>TSNumberKeyword</code> 信息，我们甚至能笃定这两个参数是数字类型。</p>
<h4 id="">知道类型，就可以做参数校验！</h4>
<blockquote>
<p>🔥通过原始函数定义的参数类型生成 http 参数校验逻辑，比起手工编写 <code>Joi</code> 配置要可靠得多。</p>
</blockquote>
<h4 id="">等等，还有一个问题没解决</h4>
<p>参数名和参数类型都有了，缺的就是参数位置以及 <code>content-type</code> 。</p>
<p>约定大法好，根据多年开发总结得出：</p>
<p><strong>总所周知， GET 请求的参数就放在 queryString 里吧。</strong></p>
<p><strong>至于 <code>content-type</code> 统一使用 <code>application/json</code> 不接受反驳</strong></p>
</li>
</ol>
<h3 id="">齐活了</h3>
<p>结合上面收集到的信息，我们可以想象最终的场景是：</p>
<ul>
<li>step 1 : 按上面的方式，编写一个不能再普通的函数</li>
<li>step 2: 使用我们编写好的工具，运行这个函数</li>
<li>step 3: 可以通过 <code>http://127.0.0.1:3000/sum?a=11&amp;b=22</code> 来访问这个接口，并且得到结果 33</li>
</ul>
<p><img src="https://lanhaooss.oss-cn-shenzhen.aliyuncs.com/images/331/2.png" alt="2" width="1700" height="1270" loading="lazy"></p>
<p>通过这个截图可知，虽然实现细节比较多，但是<strong>可行性</strong>是没问题的。</p>
<p>并且笔者已经做出了一个实验性项目。</p>
<p>如果对这种模式比较感兴趣，欢迎前来讨论，在这里就不打广告了。</p>
<h3 id="faq">FAQ 环节</h3>
<ul>
<li>
<p>更多的 HTTP_METHOD 怎么办？总不能都用 GET 吧</p>
<blockquote>
<p>目前我选能用的方案是，如果没有声明，就用一个默认的 <code>GET</code>，</p>
</blockquote>
<blockquote>
<p>因为要尽量选一个能覆盖 90% 情况的作为默认值。</p>
</blockquote>
<blockquote>
<p>当我需要使用其他 Method ，我的方案是显式指定，比如</p>
</blockquote>
<pre><code>export const method = 'POST';

export default (a: number, b: number): number =&gt; {
  return a + b;
}
</code></pre>
<p>这个方案可以使信息更紧凑，同时不影响函数逻辑跑本地单元测试。</p>
</li>
<li>
<p>上面提到的入参参数位置，什么时候在 <code>queryString</code>，什么时候在其他？</p>
<blockquote>
<p>遵从大多数的案例，GET 的情况下使用 <code>queryString</code>， 其他情况下在 <code>body</code></p>
</blockquote>
<blockquote>
<p>当然还有完全自定义的方案，为避广告嫌疑就不展开了，基本原则还是不影响函数核心逻辑和单元测试。</p>
</blockquote>
</li>
<li>
<p>上面提到的 入参参数类型 ，有什么应用场景？</p>
<blockquote>
<p>有了这个信息，如果你喜欢 <code>Joi</code>， 应该能很容易生成参数校验的代码了。</p>
</blockquote>
<blockquote>
<p>或者有自己想法的话，和我一样，自己实现一套 <code>validator</code> 也不是什么难事。</p>
</blockquote>
</li>
</ul>
<!--kg-card-end: markdown--> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 全面解析 JavaScript 对象所有 API ]]>
                </title>
                <description>
                    <![CDATA[ 近日发现有挺多人对对象基础API不熟悉，举个开发中常见的需求，经常会有类似的封装http到原型Vue.prototype，一般人是这样封装的，但容易被篡改。 function Vue(){  console.log('test vue'); } function http(){   console.log('我是调用接口的http'); } Vue.prototype.$http = http; var vm = new Vue(); vm.$http() vm.$http = 1; // 一旦被修改，虽然一般正常情况下不会被修改 vm.$http(); // 再次调用报错 熟悉Object.defineProperty或者说熟悉对象API的人，一般是如下代码写的，则不会出现被修改的问题。 function Vue(){  console.log('test vue'); }; function http(){   console.log('我是调用接口的http'); }; Object.defineProperty(Vue.prototype, '$http', {     ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/javascript-object-api/</link>
                <guid isPermaLink="false">60098b7b5f61e30501b5c019</guid>
                
                    <category>
                        <![CDATA[ JavaScript ]]>
                    </category>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ 若川 ]]>
                </dc:creator>
                <pubDate>Thu, 21 Jan 2021 09:20:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/01/essentialiving-yvG7vDXCzDE-unsplash.jpg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>近日发现有挺多人对对象基础<code>API</code>不熟悉，举个开发中常见的需求，经常会有类似的封装<code>http</code>到原型<code>Vue.prototype</code>，一般人是这样封装的，但容易被篡改。</p><pre><code class="language-js">function Vue(){
 console.log('test vue');
}
function http(){
  console.log('我是调用接口的http');
}
Vue.prototype.$http = http;
var vm = new Vue();
vm.$http()
vm.$http = 1; // 一旦被修改，虽然一般正常情况下不会被修改
vm.$http(); // 再次调用报错
</code></pre><p>熟悉<code>Object.defineProperty</code>或者说熟悉对象<code>API</code>的人，一般是如下代码写的，则不会出现被修改的问题。</p><pre><code class="language-js">function Vue(){
 console.log('test vue');
};
function http(){
  console.log('我是调用接口的http');
};
Object.defineProperty(Vue.prototype, '$http', {
    get(){
     return http;
    }
});
var vm = new Vue();
vm.$http();
vm.$http = 1; // 这里无法修改
vm.$http(); // 调用正常
</code></pre><p><a href="https://github.com/vuejs/vue-router/blob/dev/src/install.js#L38-L44" rel="noopener noreferrer">vue-router 源码里就是类似这样写的 (opens new window)</a>，<code>this.$router</code>，<code>this.$route</code>无法修改。</p><pre><code class="language-js">// vue-router 源码
Object.defineProperty(Vue.prototype, '$router', {
	get () { return this._routerRoot._router }
})
Object.defineProperty(Vue.prototype, '$route', {
	get () { return this._routerRoot._route }
})
</code></pre><p>以下是正文~</p><p>之前看到<a href="http://louiszhai.github.io/2017/04/28/array/" rel="noopener noreferrer">【深度长文】JavaScript数组所有API全解密（opens new window</a>）和<a href="http://louiszhai.github.io/2016/01/12/js.String/" rel="noopener noreferrer">JavaScript字符串所有API全解密（opens new window</a>）这两篇高质量的文章。发现没写对象API解析（估计是博主觉得简单，就没写）。刚好我看到《JavaScript面向对象编程指南（第2版）》，觉得有必要写（或者说chao）一下，也好熟悉下对象的所有API用法。</p><p>创建对象的两种方式：</p><pre><code class="language-js">var o = new Object();
var o = {}; // 推荐
</code></pre><p>该构造器可以接受任何类型的参数，并且会自动识别参数的类型，并选择更合适的构造器来完成相关操作。比如：</p><pre><code class="language-js">var o = new Object('something');
o.constructor; // ƒ String() { [native code] }
var n = new Object(123);
n.constructor; // ƒ Number() { [native code] }
</code></pre><h2 id="object-">Object构造器的成员</h2><h3 id="object-prototype">Object.prototype</h3><p>该属性是所有对象的原型（包括 <code>Object</code>对象本身），语言中的其他对象正是通过对该属性上添加东西来实现它们之间的继承关系的。所以要小心使用。 比如：</p><pre><code class="language-js">var s = new String('若川');
Object.prototype.custom = 1;
console.log(s.custom); // 1
</code></pre><h2 id="object-prototype-">Object.prototype 的成员</h2><h3 id="object-prototype-constructor">Object.prototype.constructor</h3><p>该属性指向用来构造该函数对象的构造器，在这里为<code>Object()</code></p><pre><code class="language-js">Object.prototype.constructor === Object; // true
var o = new Object();
o.constructor === Object; // true
</code></pre><h3 id="object-prototype-tostring-radix-">Object.prototype.toString(radix)</h3><p>该方法返回的是一个用于描述目标对象的字符串。特别地，当目标是一个Number对象时，可以传递一个用于进制数的参数<code>radix</code>，该参数<code>radix</code>，该参数的默认值为10。</p><pre><code class="language-js">var o = {prop:1};
o.toString(); // '[object Object]'
var n = new Number(255);
n.toString(); // '255'
n.toString(16); // 'ff'
</code></pre><h3 id="object-prototype-tolocalestring-">Object.prototype.toLocaleString()</h3><p>该方法的作用与<code>toString()</code>基本相同，只不过它做一些本地化处理。该方法会根据当前对象的不同而被重写，例如<code>Date()</code>,<code>Number()</code>,<code>Array()</code>,它们的值都会以本地化的形式输出。当然，对于包括<code>Object()</code>在内的其他大多数对象来说，该方法与<code>toString()</code>是基本相同的。 在浏览器环境下，可以通过<code>BOM</code>对象<code>Navigator</code>的<code>language</code>属性（在<code>IE</code>中则是<code>userLanguage</code>）来了解当前所使用的语言：</p><pre><code class="language-js">navigator.language; //'en-US'
</code></pre><h3 id="object-prototype-valueof-">Object.prototype.valueOf()</h3><p>该方法返回的是用基本类型所表示的<code>this</code>值，如果它可以用基本类型表示的话。如果<code>Number</code>对象返回的是它的基本数值，而<code>Date</code>对象返回的是一个时间戳（<code>timestamp</code>）。如果无法用基本数据类型表示，该方法会返回<code>this</code>本身。</p><pre><code class="language-js">// Object
var o = {};
typeof o.valueOf(); // 'object'
o.valueOf() === o; // true
// Number
var n = new Number(101);
typeof n; // 'object'
typeof n.vauleOf; // 'function'
typeof n.valueOf(); // 'number'
n.valueOf() === n; // false
// Date
var d = new Date();
typeof d.valueOf(); // 'number'
d.valueOf(); // 1503146772355
</code></pre><h3 id="object-prototype-hasownproperty-prop-">Object.prototype.hasOwnProperty(prop)</h3><p>该方法仅在目标属性为对象自身属性时返回<code>true</code>,而当该属性是从原型链中继承而来或根本不存在时，返回<code>false</code>。</p><pre><code class="language-js">var o = {prop:1};
o.hasOwnProperty('prop'); // true
o.hasOwnProperty('toString'); // false
o.hasOwnProperty('formString'); // false
</code></pre><h3 id="object-prototype-isprototypeof-obj-">Object.prototype.isPrototypeOf(obj)</h3><p>如果目标对象是当前对象的原型，该方法就会返回<code>true</code>，而且，当前对象所在原型上的所有对象都能通过该测试，并不局限与它的直系关系。</p><pre><code class="language-js">var s = new String('');
Object.prototype.isPrototypeOf(s); // true
String.prototype.isPrototypeOf(s); // true
Array.prototype.isPrototypeOf(s); // false
</code></pre><h3 id="object-prototype-propertyisenumerable-prop-">Object.prototype.propertyIsEnumerable(prop)</h3><p>如果目标属性能在<code>for in</code>循环中被显示出来，该方法就返回<code>true</code></p><pre><code class="language-js">var a = [1,2,3];
a.propertyIsEnumerable('length'); // false
a.propertyIsEnumerable(0); // true
</code></pre><h2 id="-es5-object-">在<code>ES5</code>中附加的<code>Object</code>属性</h2><p>在<code>ES3</code>中，除了一些内置属性（如：<code>Math.PI</code>），对象的所有的属性在任何时候都可以被修改、插入、删除。在<code>ES5</code>中，我们可以设置属性是否可以被改变或是被删除——在这之前，它是内置属性的特权。<code>ES5</code>中引入了<strong>属性描述符</strong>的概念，我们可以通过它对所定义的属性有更大的控制权。这些<strong>属性描述符</strong>（特性）包括：</p><p><code>value</code>——当试图获取属性时所返回的值。 <code>writable</code>——该属性是否可写。 <code>enumerable</code>——该属性在<code>for in</code>循环中是否会被枚举 <code>configurable</code>——该属性是否可被删除。 <code>set()</code>——该属性的更新操作所调用的函数。 <code>get()</code>——获取属性值时所调用的函数。 另外，<strong>数据描述符</strong>（其中属性为：<code>enumerable</code>，<code>configurable</code>，<code>value</code>，<code>writable</code>）与<strong>存取描述符</strong>（其中属性为<code>enumerable</code>，<code>configurable</code>，<code>set()</code>，<code>get()</code>）之间是有互斥关系的。在定义了<code>set()</code>和<code>get()</code>之后，描述符会认为存取操作已被 定义了，其中再定义<code>value</code>和<code>writable</code>会<strong>引起错误</strong>。 以下是<em>ES3</em>风格的属性定义方式：</p><pre><code class="language-js">var person = {};
person.legs = 2;
</code></pre><p>以下是等价的ES5通过<strong>数据描述符</strong>定义属性的方式：</p><pre><code class="language-js">var person = {};
Object.defineProperty(person, 'legs', {
    value: 2,
    writable: true,
    configurable: true,
    enumerable: true
});
</code></pre><p>其中， 除了value的默认值为<code>undefined</code>以外，其他的默认值都为<code>false</code>。这就意味着，如果想要通过这一方式定义一个可写的属性，必须显示将它们设为<code>true</code>。 或者，我们也可以通过<code>ES5</code>的存储描述符来定义：</p><pre><code class="language-js">var person = {};
Object.defineProperty(person, 'legs', {
    set:function(v) {
        return this.value = v;
    },
    get: function(v) {
        return this.value;
    },
    configurable: true,
    enumerable: true
});
person.legs = 2;
</code></pre><p>这样一来，多了许多可以用来描述属性的代码，如果想要防止别人篡改我们的属性，就必须要用到它们。此外，也不要忘了浏览器向后兼容<code>ES3</code>方面所做的考虑。例如，跟添加<code>Array.prototype</code>属性不一样，我们不能再旧版的浏览器中使用<code>shim</code>这一特性。 另外，我们还可以（通过定义<code>nonmalleable</code>属性），在具体行为中运用这些描述符：</p><pre><code class="language-js">var person = {};
Object.defineProperty(person, 'heads', {value: 1});
person.heads = 0; // 0
person.heads; // 1  (改不了)
delete person.heads; // false
person.heads // 1 (删不掉)
</code></pre><h3 id="object-defineproperty-obj-prop-descriptor-es5-">Object.defineProperty(obj, prop, descriptor) (ES5)</h3><p>具体用法可参见上文，或者查看MDN。 <a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty" rel="noopener noreferrer">MDN Object.defineProperty(obj, descriptor)(opens new window)</a></p><p>Vue.js文档：<a href="https://cn.vuejs.org/v2/guide/reactivity.html" rel="noopener noreferrer"><strong>如何追踪变化</strong> (opens new window)</a>把一个普通 JavaScript 对象传给 Vue 实例的 data 选项，Vue 将遍历此对象所有的属性，并使用 Object.defineProperty 把这些属性全部转为 getter/setter。Object.defineProperty 是仅 ES5 支持，且无法 shim 的特性，这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。</p><h3 id="object-defineproperties-obj-props-es5-">Object.defineProperties(obj, props) (ES5)</h3><p>该方法的作用与<code>defineProperty()</code>基本相同，只不过它可以用来一次定义多个属性。 比如：</p><pre><code class="language-js">var glass = Object.defineProperties({}, {
    'color': {
        value: 'transparent',
        writable: true
    },
    'fullness': {
        value: 'half',
        writable: false
    }
});
glass.fullness; // 'half'
</code></pre><h3 id="object-getprototypeof-obj-es5-">Object.getPrototypeOf(obj) (ES5)</h3><p>之前在<code>ES3</code>中，我们往往需要通过<code>Object.prototype.isPrototypeOf()</code>去猜测某个给定的对象的原型是什么，如今在<code>ES5</code>中，我们可以直接询问改对象“你的原型是什么？”</p><pre><code class="language-js">Object.getPrototypeOf([]) === Array.prototype; // true
Object.getPrototypeOf(Array.prototype) === Object.prototype; // true
Object.getPrototypeOf(Object.prototype) === null; // true
</code></pre><h3 id="object-create-obj-descr-es5-">Object.create(obj, descr) (ES5)</h3><p>该方法主要用于创建一个新对象，并为其设置原型，用（上述）属性描述符来定义对象的原型属性。</p><pre><code class="language-js">var parent = {hi: 'Hello'};
var o = Object.create(parent, {
    prop: {
        value: 1
    }
});
o.hi; // 'Hello'
// 获得它的原型
Object.getPrototypeOf(parent) === Object.prototype; // true 说明parent的原型是Object.prototype
Object.getPrototypeOf(o); // {hi: "Hello"} // 说明o的原型是{hi: "Hello"}
o.hasOwnProperty('hi'); // false 说明hi是原型上的
o.hasOwnProperty('prop'); // true 说明prop是原型上的自身上的属性。
</code></pre><p>现在，我们甚至可以用它来创建一个完全空白的对象，这样的事情在<code>ES3</code>中可是做不到的。</p><pre><code class="language-js">var o = Object.create(null);
typeof o.toString(); // 'undefined'
</code></pre><h3 id="object-getownpropertydesciptor-obj-property-es5-">Object.getOwnPropertyDesciptor(obj, property) (ES5)</h3><p>该方法可以让我们详细查看一个属性的定义。甚至可以通过它一窥那些内置的，之前不可见的隐藏属性。</p><pre><code class="language-js">Object.getOwnPropertyDescriptor(Object.prototype, 'toString');
// {writable: true, enumerable: false, configurable: true, value: ƒ toString()}
</code></pre><h3 id="object-getownpropertynames-obj-es5-">Object.getOwnPropertyNames(obj) (ES5)</h3><p>该方法返回一个数组，其中包含了当前对象所有属性的名称（字符串），不论它们是否可枚举。当然，也可以用<code>Object.keys()</code>来单独返回可枚举的属性。</p><pre><code class="language-js">Object.getOwnPropertyNames(Object.prototype);
// ["__defineGetter__", "__defineSetter__", "hasOwnProperty", "__lookupGetter__", "__lookupSetter__", "propertyIsEnumerable", "toString", "valueOf", "__proto__", "constructor", "toLocaleString", "isPrototypeOf"]
Object.keys(Object.prototype);
// []
Object.getOwnPropertyNames(Object);
// ["length", "name", "arguments", "caller", "prototype", "assign", "getOwnPropertyDescriptor", "getOwnPropertyDescriptors", "getOwnPropertyNames", "getOwnPropertySymbols", "is", "preventExtensions", "seal", "create", "defineProperties", "defineProperty", "freeze", "getPrototypeOf", "setPrototypeOf", "isExtensible", "isFrozen", "isSealed", "keys", "entries", "values"]
Object.keys(Object);
// []
</code></pre><h3 id="object-preventextensions-obj-es5-">Object.preventExtensions(obj) (ES5)</h3><h3 id="object-isextensible-obj-es5-">Object.isExtensible(obj) (ES5)</h3><p><code>preventExtensions()</code>方法用于禁止向某一对象添加更多属性，而<code>isExtensible()</code>方法则用于检查某对象是否还可以被添加属性。</p><pre><code class="language-js">var deadline = {};
Object.isExtensible(deadline); // true
deadline.date = 'yesterday'; // 'yesterday'
Object.preventExtensions(deadline);
Object.isExtensible(deadline); // false
deadline.date = 'today';
deadline.date; // 'today'
// 尽管向某个不可扩展的对象中添加属性不算是一个错误操作，但它没有任何作用。
deadline.report = true;
deadline.report; // undefined
</code></pre><h3 id="object-seal-obj-es5-">Object.seal(obj) (ES5)</h3><h3 id="object-isseal-obj-es5-">Object.isSeal(obj) (ES5)</h3><p><code>seal()</code>方法可以让一个对象密封，并返回被密封后的对象。 <code>seal()</code>方法的作用与<code>preventExtensions()</code>基本相同，但除此之外，它还会将现有属性 设置成不可配置。也就是说，在这种情况下，我们只能变更现有属性的值，但不能删除或（用<code>defineProperty()</code>）重新配置这些属性，例如不能将一个可枚举的属性改成不可枚举。</p><pre><code class="language-js">var person = {legs:2};
// person === Object.seal(person); // true
Object.isSealed(person); // true
Object.getOwnPropertyDescriptor(person, 'legs');
// {value: 2, writable: true, enumerable: true, configurable: false}
delete person.legs; // false (不可删除，不可配置)
Object.defineProperty(person, 'legs',{value:2});
person.legs; // 2
person.legs = 1;
person.legs; // 1 (可写)
Object.defineProperty(person, "legs", { get: function() { return "legs"; } });
// 抛出TypeError异常
</code></pre><h3 id="object-freeze-obj-es5-">Object.freeze(obj) (ES5)</h3><h3 id="object-isfrozen-obj-es5-">Object.isFrozen(obj) (ES5)</h3><p><code>freeze()</code>方法用于执行一切不受<code>seal()</code>方法限制的属性值变更。<code>Object.freeze()</code> 方法可以冻结一个对象，冻结指的是不能向这个对象添加新的属性，不能修改其已有属性的值，不能删除已有属性，以及不能修改该对象已有属性的可枚举性、可配置性、可写性。也就是说，这个对象永远是不可变的。该方法返回被冻结的对象。</p><pre><code class="language-js">var deadline = Object.freeze({date: 'yesterday'});
deadline.date = 'tomorrow';
deadline.excuse = 'lame';
deadline.date; // 'yesterday'
deadline.excuse; // undefined
Object.isSealed(deadline); // true;
Object.isFrozen(deadline); // true
Object.getOwnPropertyDescriptor(deadline, 'date');
// {value: "yesterday", writable: false, enumerable: true, configurable: false} (不可配置，不可写)
Object.keys(deadline); // ['date'] (可枚举)
</code></pre><h3 id="object-keys-obj-es5-">Object.keys(obj) (ES5)</h3><p>该方法是一种特殊的<code>for-in</code>循环。它只返回当前对象的属性（不像<code>for-in</code>），而且这些属性也必须是可枚举的（这点和<code>Object.getOwnPropertyNames()</code>不同，不论是否可以枚举）。返回值是一个字符串数组。</p><pre><code class="language-js">Object.prototype.customProto = 101;
Object.getOwnPropertyNames(Object.prototype);
// [..., "constructor", "toLocaleString", "isPrototypeOf", "customProto"]
Object.keys(Object.prototype); // ['customProto']
var o = {own: 202};
o.customProto; // 101
Object.keys(o); // ['own']
</code></pre><h2 id="-es6-object-">四、在<code>ES6</code>中附加的<code>Object</code>属性</h2><h3 id="object-is-value1-value2-es6-">Object.is(value1, value2) (ES6)</h3><p>该方法用来比较两个值是否严格相等。它与严格比较运算符（===）的行为基本一致。 不同之处只有两个：一是<code>+0</code>不等于<code>-0</code>，而是<code>NaN</code>等于自身。</p><pre><code class="language-js">Object.is('若川', '若川'); // true
Object.is({},{}); // false
Object.is(+0, -0); // false
+0 === -0; // true
Object.is(NaN, NaN); // true
NaN === NaN; // false
</code></pre><p><code>ES5</code>可以通过以下代码部署<code>Object.is</code></p><pre><code class="language-js">Object.defineProperty(Object, 'is', {
    value: function() {x, y} {
        if (x === y) {
           // 针对+0不等于-0的情况
           return x !== 0 || 1 / x === 1 / y;
        }
        // 针对 NaN的情况
        return x !== x &amp;&amp; y !== y;
    },
    configurable: true,
    enumerable: false,
    writable: true
});
</code></pre><h3 id="object-assign-target-sources-es6-">Object.assign(target, ...sources) (ES6)</h3><p>该方法用来源对象（<code>source</code>）的所有可枚举的属性复制到目标对象（<code>target</code>）。它至少需要两个对象作为参数，第一个参数是目标对象<code>target</code>，后面的参数都是源对象（<code>source</code>）。只有一个参数不是对象，就会抛出<code>TypeError</code>错误。</p><pre><code class="language-js">var target = {a: 1};
var source1 = {b: 2};
var source2 = {c: 3};
obj = Object.assign(target, source1, source2);
target; // {a:1,b:2,c:3}
obj; // {a:1,b:2,c:3}
target === obj; // true
// 如果目标对象与源对象有同名属性，或多个源对象有同名属性，则后面的属性会覆盖前面的属性。
var source3 = {a:2,b:3,c:4};
Object.assign(target, source3);
target; // {a:2,b:3,c:4}
</code></pre><p><code>Object.assign</code>只复制自身属性，不可枚举的属性（<code>enumerable</code>为<code>false</code>）和继承的属性不会被复制。</p><pre><code class="language-js">Object.assign({b: 'c'},
    Object.defineProperty({}, 'invisible', {
        enumerable: false,
        value: 'hello'
    })
);
// {b: 'c'}
</code></pre><p>属性名为<code>Symbol</code>值的属性，也会被<code>Object.assign()</code>复制。</p><pre><code class="language-js">Object.assign({a: 'b'}, {[Symbol('c')]: 'd'});
// {a: 'b', Symbol(c): 'd'}
</code></pre><p>对于嵌套的对象，<code>Object.assign()</code>的处理方法是替换，而不是添加。</p><pre><code class="language-js">Object.assign({a: {b:'c',d:'e'}}, {a:{b:'hello'}});
// {a: {b:'hello'}}
</code></pre><p>对于数组，<code>Object.assign()</code>把数组视为属性名为0、1、2的对象。</p><pre><code class="language-js">Object.assign([1,2,3], [4,5]);
// [4,5,3]
</code></pre><h3 id="object-getownpropertysymbols-obj-es6-">Object.getOwnPropertySymbols(obj) (ES6)</h3><p>该方法会返回一个数组，该数组包含了指定对象自身的（非继承的）所有 <code>symbol</code> 属性键。 该方法和 <code>Object.getOwnPropertyNames()</code> 类似，但后者返回的结果只会包含字符串类型的属性键，也就是传统的属性名。</p><pre><code class="language-js">Object.getOwnPropertySymbols({a: 'b', [Symbol('c')]: 'd'});
// [Symbol(c)]
</code></pre><h3 id="object-setprototypeof-obj-prototype-es6-">Object.setPrototypeOf(obj, prototype) (ES6)</h3><p>该方法设置一个指定的对象的原型 ( 即, 内部<code>[[Prototype]]</code>属性）到另一个对象或 <code>null</code>。 <code>__proto__</code>属性用来读取或设置当前对象的<code>prototype</code>对象。目前，所有浏览器（包括<code>IE11</code>）都部署了这个属性。</p><pre><code class="language-js">// ES6写法
var obj = {
    method: function(){
        // code ...
    }
};
// obj.__proto__ = someOtherObj;
// ES5写法
var obj = Object.create(someOtherObj);
obj.method = function(){
    // code ...
};
</code></pre><p>该属性没有写入<code>ES6</code>的正文，而是写入了附录。<code>__proto__</code>前后的双下划线说明它本质上是一个内部属性，而不是正式对外的一个API。无论从语义的角度，还是从兼容性的角度，都不要使用这个属性。而是使用<code>Object.setPrototypeOf()</code>（写操作），<code>Object.getPrototypeOf()</code>（读操作），或<code>Object.create()</code>（生成操作）代替。 在实现上，<code>__proto__</code>调用的<code>Object.prototype.__proto__</code>。 <code>Object.setPrototypeOf()</code>方法的作用与<code>__proto__</code>作用相同，用于设置一个对象的<code>prototype</code>对象。它是<code>ES6</code>正式推荐的设置原型对象的方法。</p><h2 id="-es8-object-">在<code>ES8</code>中附加的<code>Object</code>属性</h2><h3 id="object-getownpropertydescriptors-obj-es8-">Object.getOwnPropertyDescriptors(obj) (ES8)</h3><p>该方法基本与<code>Object.getOwnPropertyDescriptor(obj, property)</code>用法一致，只不过它可以用来获取一个对象的所有自身属性的描述符。</p><pre><code class="language-js">Object.getOwnPropertyDescriptor(Object.prototype, 'toString');
// {writable: true, enumerable: false, configurable: true, value: ƒ toString()}
Object.getOwnPropertyDescriptors(Object.prototype); // 可以自行在浏览器控制台查看效果。
</code></pre><h3 id="object-values-obj-es8-">Object.values(obj) (ES8)</h3><p><code>Object.values()</code> 方法与<code>Object.keys</code>类似。返回一个给定对象自己的所有可枚举属性值的数组，值的顺序与使用<code>for...in</code>循环的顺序相同 ( 区别在于<code>for-in</code>循环枚举原型链中的属性 )。</p><pre><code class="language-js">var obj = {a:1,b:2,c:3};
Object.keys(obj); // ['a','b','c']
Object.values(obj); // [1,2,3]
</code></pre><h3 id="object-entries-obj-es8-">Object.entries(obj) (ES8)</h3><p><code>Object.entries()</code> 方法返回一个给定对象自己的可枚举属性<code>[key，value]</code>对的数组，数组中键值对的排列顺序和使用 <code>for...in</code> 循环遍历该对象时返回的顺序一致（区别在于一个<code>for-in</code>循环也枚举原型链中的属性）。</p><pre><code class="language-js">var obj = {a:1,b:2,c:3};
Object.keys(obj); // ['a','b','c']
Object.values(obj); // [1,2,3]
Object.entries(obj); // [['a',1],['b',2],['c',3]]
</code></pre><h2 id="-es10-object-">六、在<code>ES10</code>中附加的<code>Object</code>属性</h2><h3 id="object-fromentries-iterable-es10-">Object.fromEntries(iterable) (ES10)</h3><p><code>Object.fromEntries()</code>方法返回一个给定可迭代对象（类似<code>Array</code>、<code>Map</code>或其他可迭代对象）对应属性的新对象。</p><p><code>Object.fromEntries()</code> 是 <code>Object.entries()</code>的逆操作。</p><pre><code class="language-js">var arr = [['a',1],['b',2],['c',3]];
Object.fromEntries(obj); // {a: 1, b: 2, c: 3}
var entries = new Map([
  ['name', '若川'],
  ['age', 18]
]);
Object.fromEntries(entries) // {name: '若川', age: 18}
</code></pre><h2 id="-">小结</h2><p>细心的读者可能会发现<code>MDN</code>上还有一些<code>API</code>，本文没有列举到。因为那些是非标准的<code>API</code>。熟悉对象的API对理解原型和原型链相关知识会有一定帮助。常用的API主要有<code>Object.prototype.toString()</code>，<code>Object.prototype.hasOwnProperty()</code>， <code>Object.getPrototypeOf(obj)</code>，<code>Object.create()</code>，<code>Object.defineProperty</code>，<code>Object.keys(obj)</code>，<code>Object.assign()</code>。</p><h2 id="--1">参考资料</h2><p><a href="https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object" rel="noopener noreferrer">MDN Object API(opens new window)</a><br><a href="https://book.douban.com/subject/26302623/" rel="noopener noreferrer">JavaScript面向对象编程指南（第2版）（豆瓣读书链接）(opens new window)</a><br><a href="http://es6.ruanyifeng.com/" rel="noopener noreferrer">阮一峰 ES6标准入门2(opens new window)</a></p><p>欢迎阅读我的<a href="https://www.lxchuan12.cn/">更多文章</a>。</p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ REST API 教程：REST 客户端，REST 服务及 API 调用（含代码示例） ]]>
                </title>
                <description>
                    <![CDATA[ 有想过网站的注册/登录功能在后端是怎么实现的吗？你在 YouTube 上搜索“cute kitties”，得到一堆结果，然后观看视频的过程又是怎么回事？ 这个入门教程会带着你一起构建一个 RESTful API，我会解释一些术语，并使用 NodeJS 来编码实现一个服务端程序。 术语解释 REST 是什么？维基百科： > 表现层状态转换（Representational state transfer，REST）  是一种软件架构风格，它定义了一组创建 Web 服务的约束。RESTful Web 服务允许请求系统通过使用统一和预定义的无状态操作集来访问和操作 Web 资源的文本表示。 揭开神秘面纱，看看 REST 究竟是什么意思。REST 基本上就是客户端和服务端通信的一组规则。REST 架构的限制条件：  1. 客户端-服务器架构：网站/应用的用户界面与数据请求/存储应当是分离的，两者可以独立扩展。  2. 无状态：通讯过程中服务端不会保存客户端的上下文信息，意味着每个请求都需要携带必要的数据、不能指望服务器会使用之前的请求中携带的数据。  3. 分层系统：客户端不应该了解 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/rest-api-tutorial-rest-client-rest-service-and-api-calls-explained-with-code-examples/</link>
                <guid isPermaLink="false">5ffbcff339641a0517d538ef</guid>
                
                    <category>
                        <![CDATA[ REST ]]>
                    </category>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                    <category>
                        <![CDATA[ 客户端 ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Humilitas ]]>
                </dc:creator>
                <pubDate>Mon, 11 Jan 2021 04:16:38 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/01/photo-1558494949-ef010cbdcc31.jpeg" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>有想过网站的注册/登录功能在后端是怎么实现的吗？你在 YouTube 上搜索“cute kitties”，得到一堆结果，然后观看视频的过程又是怎么回事？</p>
<p>这个入门教程会带着你一起构建一个 RESTful API，我会解释一些术语，并使用 NodeJS 来编码实现一个服务端程序。</p>
<h2 id="">术语解释</h2>
<p>REST 是什么？维基百科：</p>
<blockquote>
<p><strong>表现层状态转换（Representational state transfer，REST）</strong> 是一种软件架构风格，它定义了一组创建 Web 服务的约束。RESTful Web 服务允许请求系统通过使用统一和预定义的无状态操作集来访问和操作 Web 资源的文本表示。</p>
</blockquote>
<p>揭开神秘面纱，看看 REST 究竟是什么意思。REST 基本上就是客户端和服务端通信的一组规则。REST 架构的限制条件：</p>
<ol>
<li><strong>客户端-服务器架构</strong>：网站/应用的用户界面与数据请求/存储应当是分离的，两者可以独立扩展。</li>
<li><strong>无状态</strong>：通讯过程中服务端不会保存客户端的上下文信息，意味着每个请求都需要携带必要的数据、不能指望服务器会使用之前的请求中携带的数据。</li>
<li><strong>分层系统</strong>：客户端不应该了解它是与服务器直接通讯还是与一些中介服务进行通讯，中介服务（代理或负载均衡）为底层服务器的扩展性和安全性提供了保障。</li>
</ol>
<p>理解了 RESTful 服务之后，再了解一下标题中提到的一些术语：</p>
<ol>
<li><strong>REST 客户端</strong>：访问 REST 服务的代码或应用。你现在正在用着它呢！浏览器可以看做是一个不受我们控制的 REST 客户端（我们访问的网站会处理浏览器的请求）。在很长一段时间内，浏览器都是使用内建的 XMLHttpRequest 函数来发起 REST 请求，不过现在它被现代的、基于 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">promise</a> 的 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">FetchAPI</a> 替代了。其他 REST 客户端还包括：<a href="https://github.com/axios/axios">axios</a>、<a href="https://github.com/visionmedia/superagent">superagent</a>、<a href="https://github.com/sindresorhus/got">got</a> 等代码库，<a href="https://www.postman.com/">Postman</a>（或其在线版本 <a href="https://postwoman.io/">postwoman</a>）等专用应用，以及 <a href="https://curl.haxx.se/">cURL</a> 等命令行工具。</li>
<li><strong>REST 服务</strong>：服务器。有许多流行的库能帮助我们轻松创建 REST 服务，如 NodeJS 环境下的 <a href="https://expressjs.com/">ExpressJS</a> 和 Python 环境下的 <a href="https://www.djangoproject.com/">Django</a>。</li>
<li><strong>REST API</strong>：定义了从服务器存取数据的端点和方法，稍后会详细介绍。其它替代方案包括：GraphQL、JSON-Pure 以及 oData。</li>
</ol>
<h2 id="rest">现在告诉我，REST 是什么？</h2>
<p>广义地说，就是客户端向服务端请求访问指定数据或者在服务端保存数据、服务端响应客户端请求的过程。</p>
<p>从编程角度来说，服务端提供了一个端点（URL）等待接收客户端的请求，客户端连接这个端点并发送数据（记住，REST 是无状态的，请求中携带的数据不会被存储）、服务端返回正确的响应。</p>
<p>文字是枯燥的，我们来看看示例。使用 Postman 来展示请求和响应：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/image-162.png" alt="image-162" width="600" height="400" loading="lazy"></p>
<p>配置 postman 请求</p>
<p>返回的数据是 JSON（JavaScript Object Notation）格式的，可以直接访问。</p>
<p>这里的 <code>https://official-joke-api.appspot.com/random_joke</code> 被称为 API 端点，服务端会监听指向这个端点的请求。</p>
<h2 id="rest">REST 剖析：</h2>
<p>现在我们知道了客户端可以向服务端发送携带数据的请求、服务端会返回适当的响应，再来深入了解一下如何构造一个请求。</p>
<ol>
<li>
<p><strong>端点（Endpoint）</strong>：前面已经介绍过了，这里再复习一下：它是 REST 服务器监听的 URL 地址。</p>
</li>
<li>
<p><strong>请求方法（Method）</strong>：之前有介绍到，你可以从服务器获取数据或者修改数据，那么服务器怎么知道客户端想要做什么操作呢？REST 为不同的请求类型实现了许多“方法”，以下是最常用的：</p>
<p>-  <strong>GET</strong>：从服务器获取资源。</p>
<p>-  <strong>POST</strong>：在服务器上保存资源。</p>
<p>-  <strong>PATCH</strong> 或 <strong>PUT</strong>：更新服务器上的现有资源。</p>
<p>-  <strong>DELETE</strong>：删除服务器上现有的资源。</p>
</li>
<li>
<p><strong>头部信息（Headers）</strong>：用于客户端和服务端通讯的额外信息（记住，REST 是无状态的）。常见的头部信息如下：</p>
<p><strong>请求头（Request）</strong>：</p>
<p>-  <em>host</em>：客户端的 IP 地址（或请求的源地址）</p>
<p>-  <em>accept-language</em>：客户端接受的语言类型</p>
<p>-  <em>user-agent</em>：客户端的详细信息，如操作系统和浏览器类型</p>
<p><strong>响应头（Response）</strong>：</p>
<p>-  <em>status</em>：请求的状态或 HTTP 状态码</p>
<p>-  <em>content-type</em>：服务器返回的资源的类型</p>
<p>-  <em>set-cookie</em>：服务器设置的 cookies</p>
</li>
<li>
<p><strong>请求数据（Data）</strong>：（也称为请求体或消息）包含了将要发送给服务器的数据</p>
</li>
</ol>
<h2 id="">跳出细节 - 开始编码</h2>
<p>在 Node 环境中编写 REST 服务的代码，我们会实现上面学到的全部内容。在编码过程中会用到 ES6+ 的语法。</p>
<p>要确保你已经安装了 Node.JS，并且 <code>node</code> 和 <code>npm</code> 命令是可用的，我自己使用的版本分别是 Node 12.16.2 和 NPM 6.14.4。</p>
<p>创建一个名为 <code>rest-service-node</code> 的文件夹，并使用 cd 命令进入：</p>
<pre><code class="language-shell">mkdir rest-service-node
cd rest-service-node
</code></pre>
<p>初始化 node 项目：</p>
<pre><code class="language-shell">npm init -y
</code></pre>
<p>参数 <code>-y</code> 表示跳过所有配置项。如果想要手动填写这些配置的话，执行 <code>npm init</code>。</p>
<p>安装一些依赖包，这里使用 ExpressJS 框架来开发 REST 服务。执行以下命令来安装：</p>
<pre><code class="language-shell">npm install --save express body-parser
</code></pre>
<p>这里的 <code>body-parser</code> 是做什么用的？默认情况下，Express 是无法处理 POST 请求传入的 JSON 数据的，<code>body-parser</code> 为 Express 解决了这个问题。</p>
<p>创建 <code>server.js</code> 文件，写入以下代码：</p>
<pre><code class="language-js">const express = require("express");
const bodyParser = require("body-parser");

const app = express();
app.use(bodyParser.json());

app.listen(5000, () =&gt; {
  console.log(`Server is running on port 5000.`);
});
</code></pre>
<p>前两行代码引入了 Express 和 body-parser。</p>
<p>第三行代码初始化了一个 Express 服务器，并把它赋值给一个名为 <code>app</code> 的变量。</p>
<p><code>app.use(bodyParser.json());</code> 初始化了 body-parser 插件。</p>
<p>最后，设置服务器监听 <code>5000</code> 端口的请求。</p>
<h3 id="rest">从 REST 服务器获取数据</h3>
<p>使用 <code>GET</code> 请求来获取服务器的数据，在 <code>app.listen</code> 之前插入以下代码：</p>
<pre><code class="language-js">const sayHi = (req, res) =&gt; {
  res.send("Hi!");
};

app.get("/", sayHi);
</code></pre>
<p>这里创建了一个 <code>sayHi</code> 函数，它接受 <code>req</code> 和 <code>res</code> 两个参数（稍后会解释）、返回字符串“Hi!”作为响应。</p>
<p><code>app.get()</code> 方法接受两个参数：接口路径以及有客户端请求这个接口时执行的回调函数。所以最后一行代码可以理解为：服务器监听”/“路径（可能是主页）的请求，如果监听到这个路径上的请求就执行 <code>sayHi</code> 函数。</p>
<p><code>app.get</code> 还为我们提供了一个包含了客户端发送的所有数据的 <code>request</code> 对象，以及一个包含了所有响应方法的 <code>response</code> 对象。虽然它们是作为函数参数来访问的（随意命名也不影响功能），但是一般命名约定建议将它们命名为 <code>res</code>（表示 <code>response</code>） 和 <code>req</code>（表示 <code>request</code>）。</p>
<p>闲言少叙，启动服务器！执行以下代码：</p>
<pre><code class="language-shell">node server.js
</code></pre>
<p>如果一切顺利的话，应该能在控制台看到这个提示：<em>Server is running on port 5000.</em></p>
<p><em>提示：可以将端口号改为任意合适的值。</em></p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/image-160.png" alt="image-160" width="600" height="400" loading="lazy"></p>
<p>打开浏览器，访问 <code>[http://localhost:5000/][11]</code>，将看到如下内容：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/image-161.png" alt="image-161" width="600" height="400" loading="lazy"></p>
<p>第一个 <code>GET</code> 请求成功执行了！</p>
<h3 id="rest">向 REST 服务器发送数据</h3>
<p>接下来构建一个 <code>POST</code> 请求，这个请求向服务器传入两个数字、服务器会计算并返回两数之和。在 <code>app.get</code> 之后加入以下代码：</p>
<pre><code class="language-js">app.post("/add", (req, res) =&gt; {
  const { a, b } = req.body;
  res.send(`The sum is: ${a + b}`);
});
</code></pre>
<p>我们以 JSON 格式向服务器发送数据：</p>
<pre><code class="language-json">{
    "a":5,
    "b":10
}
</code></pre>
<p>理解一下代码：</p>
<p>第一行调用了 ExpressJS 的 <code>.post()</code> 方法，使得服务器监听 <code>POST</code> 请求，它接受的参数与 <code>.get()</code> 方法相同。这里指定的接口路径是 <code>/add</code>，这样其他人可以通过你的 IP 地址和端口（<code>[http://your-ip-address:port/add][12]</code>）来访问这个接口，在本地也可以通过 <code>localhost:5000/add</code> 这个地址来访问。这里回调函数是以行内函数的形式来编写的。</p>
<p>第二行使用了 ES6 的对象解构语法来读取对象属性。通过请求发送的数据都被保存在了 <code>req</code> 对象的 <code>body</code> 属性中，实际上也可以用以下代码来读取这两个值：</p>
<pre><code class="language-js">const num1 = req.body.a;
const num2 = req.body.b;
</code></pre>
<p>第三行使用 <code>res</code> 对象的 <code>send()</code> 方法来返回计算结果，这里使用了 ES6 的模板字符串语法。使用 Postman 测试一下：</p>
<p><img src="https://www.freecodecamp.org/news/content/images/2020/04/image-163.png" alt="image-163" width="600" height="400" loading="lazy"></p>
<p>我们在请求体中设置 <code>a</code> 的值为 5、<code>b</code> 的值为 10，Postman 会在发送的请求中携带这些数据。服务器接收到这个请求时，会以上面的代码所示的方式解析 <code>req.body</code> 中的数据。返回结果展示在下方。</p>
<p>最终代码：</p>
<pre><code class="language-js">const express = require("express");
const bodyParser = require("body-parser");

const app = express();

app.use(bodyParser.json());

const sayHi = (req, res) =&gt; {
  res.send("Hi!");
};

app.get("/", sayHi);

app.post("/add", (req, res) =&gt; {
  const { a, b } = req.body;
  res.send(`The sum is: ${a + b}`);
});

app.listen(5000, () =&gt; {
  console.log(`Server is running on port 5000.`);
});

</code></pre>
<h2 id="rest">REST 客户端</h2>
<p>我们已经创建了一个服务端程序，那么要如何在网站或者 web 程序中访问它呢？现在 REST 客户端库就派上用场了。</p>
<p>我们会构建一个 web 应用，它包含一个表格，可以在其中填入两个数字，从服务端获得它们的计算结果后会展示在页面上。</p>
<p>首先，修改 <code>server.js</code>：</p>
<pre><code class="language-js">const path = require("path");
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.json());
app.get("/", (req, res) =&gt; {
  res.sendFile(path.join(__dirname, "index.html"));
});
app.post("/add", (req, res) =&gt; {
  const { a, b } = req.body;
  res.send({
    result: parseInt(a) + parseInt(b)
  });
});

</code></pre>
<p>我们引入了 Node 提供的 <code>path</code> 包，用来跨平台地操作路径。接着改变了”/“路径上的 <code>GET</code> 请求的回调函数，在其中使用了 <code>res</code> 对象提供的 <code>sendFile</code> 方法，这个方法允许我们在响应中返回任意格式的文件。所以，每当用户访问”/“路径的时候，他们会看到 <code>index.html</code> 页面的内容。</p>
<p>最后修改了 <code>app.post</code> 函数，现在它会将 <code>a</code> 和 <code>b</code> 转换为整数，并以 JSON 形式返回两者之和。</p>
<p>创建一个名为 <code>index.html</code> 的 html 页面，并在其中添加一些基本样式：</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
    &lt;title&gt;REST Client&lt;/title&gt;
  &lt;/head&gt;
  &lt;style&gt;
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    .container {
      height: 100vh;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
    form {
      display: flex;
      flex-direction: column;
      margin-bottom: 20px;
    }
    label,
    input[type="submit"] {
      margin-top: 20px;
    }
  &lt;/style&gt;
  &lt;body&gt;
    &lt;div class="container"&gt;
      &lt;h1&gt;Simple POST Form&lt;/h1&gt;
      &lt;/h1&gt;
      &lt;form&gt;
        &lt;label&gt;Number 1:&lt;/label&gt;
        &lt;input id="num1" type="number" /&gt;
        &lt;label&gt;Number 2:&lt;/label&gt;
        &lt;input id="num2" type="number" /&gt;
        &lt;input type="submit" value="Add"/&gt;
      &lt;/form&gt;
      &lt;div class="result"&gt;Click Add!&lt;/div&gt;
    &lt;/div&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>在闭合 body 标签之前插入一段 JavaScript 脚本，这样我们就不必维护一个独立的 <code>.js</code> 文件了。我们监听 document 的 <code>submit</code> 事件并为其指定一个回调函数：</p>
<pre><code class="language-js">&lt;script&gt;
    document.addEventListener("submit", sendData);
&lt;/script&gt;
</code></pre>
<p>首先要避免在点击“Add”按钮时刷新页面，可以使用 <code>preventDefault()</code> 方法来实现，同时要获取两个输入框的值：</p>
<pre><code class="language-js">function sendData(e) {
    e.preventDefault();
    const a = document.querySelector("#num1").value;
    const b = document.querySelector("#num2").value;
}
</code></pre>
<p>使用 <code>a</code> 和 <code>b</code> 的值向服务端发送请求。这里我们使用浏览器内置的 <a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API">Fetch API</a> 来发送请求。</p>
<p>Fetch 接收两个参数：接口地址和 JSON 形式的请求参数对象，返回一个 <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise">Promise</a>。相关知识超出了本文范围，请自行查阅。</p>
<p>在 <code>sendData()</code> 函数中加入以下代码：</p>
<pre><code class="language-js">fetch("/add", {
        method: "POST",
        headers: {
            Accept: "application/json",
            "Content-Type": "application/json"
        },
        body: JSON.stringify({
            a: parseInt(a),
            b: parseInt(b)
        })
    })
    .then(res =&gt; res.json())
    .then(data =&gt; {
        const {
            result
        } = data;
        document.querySelector(
            ".result"
        ).innerText = `The sum is: ${result}`;
    })
    .catch(err =&gt; console.log(err));
</code></pre>
<p>把接口地址的相对路径作为 <code>fetch</code> 函数的第一个参数传入，接着传入一个参数对象作为第二个参数，其中指定了请求方式为 <code>POST</code>。</p>
<p>参数对象中还包含了 <code>headers</code> 参数，其中指定了请求中发送的数据的格式（<code>content-type</code>）和预期响应的数据格式（<code>accept</code>）。</p>
<p>接着传入了请求体 <code>body</code>。还记得使用 Postman 时以 JSON 格式输入数据吗？这里也是类似的情况。由于 express 将请求体当做字符串来处理，并根据 content-type 来解析，所以我们需要使用 <code>JSON.stringify()</code> 方法来将请求体转换为字符串。我们格外谨慎地把输入值转换为了整数，以确保这个请求不会破坏服务器（因为我们没有做数据类型校验）。</p>
<p>最后，如果 fetch 返回的 promise 状态变为完成（fullfilled），我们就能获取到响应数据并将其转换为 JSON 格式，然后就可以在响应对象中获取计算结果并将结果展示在页面上。</p>
<p>如果这个 promise 的状态变为拒绝（rejected），则会在控制台打印错误信息。</p>
<p><code>index.html</code> 的最终代码如下：</p>
<pre><code class="language-html">&lt;!DOCTYPE html&gt;
&lt;html lang="en"&gt;
  &lt;head&gt;
    &lt;meta charset="UTF-8" /&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1.0" /&gt;
    &lt;title&gt;REST Client&lt;/title&gt;
  &lt;/head&gt;
  &lt;style&gt;
    * {
      margin: 0;
      padding: 0;
      box-sizing: border-box;
    }
    .container {
      height: 100vh;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
      display: flex;
      flex-direction: column;
      justify-content: center;
      align-items: center;
    }
    form {
      display: flex;
      flex-direction: column;
      margin-bottom: 20px;
    }
    label,
    input[type="submit"] {
      margin-top: 20px;
    }
  &lt;/style&gt;
  &lt;body&gt;
    &lt;div class="container"&gt;
      &lt;h1&gt;Simple POST Form&lt;/h1&gt;
      &lt;/h1&gt;
      &lt;form&gt;
        &lt;label&gt;Number 1:&lt;/label&gt;
        &lt;input id="num1" type="number" /&gt;
        &lt;label&gt;Number 2:&lt;/label&gt;
        &lt;input id="num2" type="number" /&gt;
        &lt;input type="submit" value="Add"/&gt;
      &lt;/form&gt;
      &lt;div class="result"&gt;Click Add!&lt;/div&gt;
    &lt;/div&gt;
    &lt;script&gt;
      document.addEventListener("submit", sendData);
      function sendData(e) {
        e.preventDefault();
        const a = document.querySelector("#num1").value;
        const b = document.querySelector("#num2").value;

        fetch("/add", {
          method: "POST",
          headers: {
            Accept: "application/json",
            "Content-Type": "application/json"
          },
          body: JSON.stringify({
            a: parseInt(a),
            b: parseInt(b)
          })
        })
          .then(res =&gt; res.json())
          .then(data =&gt; {
            const { result } = data;
            document.querySelector(
              ".result"
            ).innerText = `The sum is: ${result}`;
          })
          .catch(err =&gt; console.log(err));
      }
    &lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code></pre>
<p>我在 glitch 上部署了一个<a href="https://habitual-serious-boater.glitch.me/">小应用</a>供你测试。</p>
<h2 id="">总结：</h2>
<p>通过本文，我们学习了 REST 架构和 REST 请求的相关知识，我们一起构建了一个简单的能够处理 <code>GET</code> 和 <code>POST</code> 请求的 REST 服务器，还构建了一个 web 应用作为 REST 客户端来计算两数之和。</p>
<p>你可以扩展这个项目来处理更多其它类型的请求，甚至将它扩展成一个完整的<a href="https://www.freecodecamp.org/news/building-a-simple-crud-application-with-express-and-mongodb-63f80f3eb1cd/">后端应用</a>。</p>
<p>希望本文能对你有所帮助。如果有任何疑问，可以随时在 twitter 上联系我。Happy Coding!</p>
<!--kg-card-end: markdown--><p>原文：<a href="https://www.freecodecamp.org/news/rest-api-tutorial-rest-client-rest-service-and-api-calls-explained-with-code-examples/">REST API Tutorial – REST Client, REST Service, and API Calls Explained With Code Examples</a>，作者：<a href="https://www.freecodecamp.org/news/author/boxdox/">Vaibhav Kandwal</a></p> ]]>
                </content:encoded>
            </item>
        
            <item>
                <title>
                    <![CDATA[ 什么是 API？ ]]>
                </title>
                <description>
                    <![CDATA[ 在我学习软件开发之前，我感觉 API 就像一种啤酒。 现在，我经常使用这个术语，以至于我最近尝试在酒吧订购 API。 调酒师的反应是抛出 404：资源未找到。 我遇到了很多在技术领域和其他领域工作的人，他们对这个相当常见的术语的含义理解不太清楚或不正确。 从技术上讲，API 代表应用程序编程接口。在某些时候，大多数大公司已经为其客户或自己内部使用构建了 API。 但是，你如何用简单的语言解释 API？ 还有比在开发和业务中使用的含义更广泛的含义吗？ 首先，让我们回顾一下网络本身的工作方式。 WWW 和远程服务器 当我想到 Web 时，我想到的是连接 服务器 的大型网络。 互联网上的每个页面都存储在远程服务器上的某个位置。远程服务器并不是那么神秘——它只是被优化了以处理请求的远程计算机的一部分。 为了使事情更直观，你可以在笔记本电脑上启动一个服务器，该服务器能够将整个网站服务于 Web（实际上，本地服务器是工程师在向公众发布网站之前用来开发网站的工具）。 当你在浏览器中输入 www.facebook.com 时，请求将发送到 Facebook 的远程服务器。 浏览器收到 ]]>
                </description>
                <link>https://www.freecodecamp.org/chinese/news/what-is-an-api/</link>
                <guid isPermaLink="false">5ff2d30739641a0517d532cc</guid>
                
                    <category>
                        <![CDATA[ API ]]>
                    </category>
                
                <dc:creator>
                    <![CDATA[ Chengjun.L ]]>
                </dc:creator>
                <pubDate>Mon, 04 Jan 2021 09:39:00 +0000</pubDate>
                <media:content url="https://chinese.freecodecamp.org/news/content/images/2021/01/1_F8R-PEI9iVJ-sY3qFZemCg.png" medium="image" />
                <content:encoded>
                    <![CDATA[ <p>在我学习软件开发之前，我感觉 API 就像一种啤酒。</p><p>现在，我经常使用这个术语，以至于我最近尝试在酒吧订购 API。</p><p>调酒师的反应是抛出 404：资源未找到。</p><p>我遇到了很多在技术领域和其他领域工作的人，他们对这个相当常见的术语的含义理解不太清楚或不正确。</p><p>从技术上讲，API 代表<strong>应用程序编程接口</strong>。在某些时候，大多数大公司已经为其客户或自己内部使用构建了 API。</p><p>但是，你如何用简单的语言解释 API？ 还有比在开发和业务中使用的含义更广泛的含义吗？ 首先，让我们回顾一下网络本身的工作方式。</p><h3 id="www-"><strong><strong>WWW 和远程服务器</strong></strong></h3><p>当我想到 Web 时，我想到的是连接 <em>服务器</em> 的大型网络。</p><p>互联网上的每个页面都存储在远程服务器上的某个位置。远程服务器并不是那么神秘——它只是被优化了以处理请求的远程计算机的一部分。</p><p>为了使事情更直观，你可以在笔记本电脑上启动一个服务器，该服务器能够将整个网站服务于 Web（实际上，本地服务器是工程师在向公众发布网站之前用来开发网站的工具）。</p><p>当你在浏览器中输入 www.facebook.com 时，请求将发送到 Facebook 的远程服务器。 浏览器收到响应后，将解释代码并显示页面。</p><p>对于 <em>浏览器</em>（也称为客户端）而言，Facebook 的服务器是一种 API。这意味着，每次你访问 Web 页面时，你都会与某些远程服务器的 API 进行交互。</p><p>API 与远程服务器不同，<strong>它是服务器中接收请求和发送响应的部分</strong>。</p><h3 id="-api-"><strong><strong>用</strong> <strong>API 服务客户</strong></strong></h3><p>你可能听说过将 API 打包为产品的公司。例如，Weather Underground 出售对其<a href="https://www.wunderground.com/weather/api">天气数据 API 的访问权</a>。</p><p><strong>示例场景：</strong>你的小型企业网站上有一个表格，客户用它登录以安排预约。你想让客户能够自动创建带有该预约的详细信息的 Google 日历提醒。</p><p><strong>API 的使用：</strong>让你的网站的服务器直接与 Google 的服务器通信，并请求创建具有给定详细信息的事件。 然后，你的服务器将收到 Google 的响应，进行处理，然后将相关信息发送回浏览器，例如向用户发送确认消息。</p><p>另外，你的浏览器通常可以绕过服务器直接向 Google 服务器直接发送 API 请求。</p><p><strong>此 Google 日历的 API 与现有的其他远程服务器的 API 有何不同？</strong></p><p><strong>用技术术语来说，</strong>区别在于请求和响应的格式。</p><p>要呈现整个网页，你的浏览器需要获得 <em>HTML</em> 形式的响应，其中包含演示代码，而 Google 日历的 API 调用只会返回数据（可能采用 JSON 格式）。</p><p>如果你的网站的服务器正在发出 API 请求，那么你的网站服务器就是客户端（类似于你使用浏览器浏览到网站时，浏览器就是客户端）。</p><p><strong>从用户的角度来看，</strong>API 使他们无需离开你的网站即可完成操作。</p><p>大多数现代网站至少使用一些第三方 API。</p><p>许多问题已经有了第三方解决方案，无论是库还是服务。使用已有的解决方案通常更容易、更可靠。</p><p>开发团队将应用程序分解成多个通过 API 相互通信的服务器的情况并不少见。为主应用程序服务器执行辅助功能的服务器通常被称为 <em>微服务</em>。</p><p>总而言之，当一家公司向其客户提供 API 时，仅表示他们已经建立了一组专用 URL，这些 URL 返回纯数据响应——这意味着<strong>响应将不包含任何你在图形用户界面（如网站）中看到的展示型的内容</strong>。</p><p>你可以使用浏览器发出这些请求吗？通常情况下，是的。由于实际的 HTTP 传输是以文本形式进行的，因此你的浏览器将始终尽力显示响应。</p><p>例如，你可以直接使用浏览器访问 GitHub 的 API，甚至不需要访问令牌。这是在浏览器中访问 GitHub 用户的 API 路由时收到的 JSON 响应（https://api.github.com/users/petrgazarov）：</p><pre><code>{  "login": "petrgazarov",  "id": 5581195,  "avatar_url": "https://avatars.githubusercontent.com/u/5581195?v=3",  "gravatar_id": "",  "url": "https://api.github.com/users/petrgazarov",  "html_url": "https://github.com/petrgazarov",  "followers_url": "https://api.github.com/users/petrgazarov/followers",  "following_url": "https://api.github.com/users/petrgazarov/following{/other_user}",  "gists_url": "https://api.github.com/users/petrgazarov/gists{/gist_id}",  "starred_url": "https://api.github.com/users/petrgazarov/starred{/owner}{/repo}",  "subscriptions_url": "https://api.github.com/users/petrgazarov/subscriptions",  "organizations_url": "https://api.github.com/users/petrgazarov/orgs",  "repos_url": "https://api.github.com/users/petrgazarov/repos",  "events_url": "https://api.github.com/users/petrgazarov/events{/privacy}",  "received_events_url": "https://api.github.com/users/petrgazarov/received_events",  "type": "User",  "site_admin": false,  "name": "Petr Gazarov",  "company": "PolicyGenius",  "blog": "http://petrgazarov.com/",  "location": "NYC",  "email": "petrgazarov@gmail.com",  "hireable": null,  "bio": null,  "public_repos": 23,  "public_gists": 0,  "followers": 7,  "following": 14,  "created_at": "2013-10-01T00:33:23Z",  "updated_at": "2016-08-02T05:44:01Z"}</code></pre><p>浏览器似乎已经很好地显示了 JSON 响应。这样的 JSON 响应已准备好在你的代码中使用。从此文本中提取数据很容易。然后，你可以对数据进行任何操作。</p><h3 id="a-application-"><strong><strong>A 代表 </strong>“<strong>Application”</strong>（应用）</strong></h3><p>最后，我们再举几个 API 示例。</p><p>“应用程序”可以指很多东西。以下是一些与 API 相关的内容：</p><ul><li>一款功能独特的软件</li><li>整个服务器，整个应用程序或应用程序的一小部分</li></ul><p>基本上，任何可以与环境区别开来的软件都可以是 API 中的 “ A”，并且可能还具有某种 API。</p><p>假设你在代码中使用了第三方库，当库被合并到你的代码中，它便成为整个应用程序的一部分。作为一个独立的软件，该库可能会有一个 API，该 API 可以与您的其余代码进行交互。</p><p>这是另一个示例：在<strong>面向对象设计</strong>中，代码被组织为对象。你的应用程序可能定义了数百个可以相互交互的对象。</p><p>每个对象都有一个API——一组用于与应用程序中其他对象进行交互的 <em>公共</em> 方法和属性。</p><p>对象也可能具有内部逻辑，这是 <em>私有</em> 的，这意味着它对外部作用域（而不是 API）是隐藏的。</p><p>希望你通过本文的介绍理解 API 的广泛含义以及当今该术语的更常见用法。</p><h4 id="-"><strong><strong>有趣的资源</strong>：</strong></h4><p><a href="https://www.youtube.com/watch?v=72snZctFFtA&amp;feature=youtu.be" rel="noopener">一个很棒关于 DNS 的 YouTube 视频</a></p><p><a href="https://simple.wikipedia.org/wiki/Hypertext_Transfer_Protocol" rel="noopener">HTTP 协议基础</a></p><p><a href="https://www.khanacademy.org/computing/computer-programming/programming/object-oriented/p/object-types#" rel="noopener">可汗学院关于“面向对象设计原则”的精彩视频</a></p><p>原文：<a href="https://www.freecodecamp.org/news/what-is-an-api-in-english-please-b880a3214a82/">What is an API? In English, please</a>，作者：Petr Gazarov<br></p> ]]>
                </content:encoded>
            </item>
        
    </channel>
</rss>
