<!DOCTYPE html><html lang="zh" data-theme="dark"><head><meta charset="utf-8"><meta name="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1"><title>IBM 全栈开发【5】:Node.JS & Express 创建后端 · Cytrogen 的个人博客</title><meta name="description" content="本文是 IBM 全栈开发课程的第五篇学习笔记,详细介绍了如何使用 Node.js 和 Express.js 构建后端应用。笔记从服务端 JavaScript 的基础出发,讲解了 Node.js 的非阻塞I/O、模块化(CommonJS vs ES Modules)以及异步回调编程,并引入 Promise 来解决“回调地狱”问题。教程的核心部分深入探讨了 Express.js 框架,涵盖了路由、中间件、模板渲染,并提供了一个基于 JWT(JSON Web Token)实现用户身份验证的完整代码示例。这篇笔记为学习和掌握 Node.js 后端开发提供了一份全面的知识点总结。"><link rel="icon" href="../favicon.png"><link rel="canonical" href="https://cytrogen.icu/posts/fed2.html"><link rel="webmention" href="https://webmention.io/cytrogen.icu/webmention"><link rel="me" href="https://m.otter.homes/@Cytrogen"><link rel="me" href="https://github.com/cytrogen"><meta name="fediverse:creator" content="@Cytrogen@m.otter.homes"><link rel="preload" href="../fonts/opensans-regular-latin.woff2" as="font" type="font/woff2" crossorigin="anonymous"><style>@font-face {
font-family: 'Open Sans';
src: url('../fonts/opensans-regular-latin.woff2') format('woff2');
font-weight: 400;
font-style: normal;
font-display: swap;
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
size-adjust: 107%;
ascent-override: 97%;
descent-override: 25%;
line-gap-override: 0%;
}
</style><script>(function() {
try {
// 优先级:用户选择 > 系统偏好 > 默认浅色
const saved = localStorage.getItem('theme');
const theme = saved ||
(window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light');
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.style.colorScheme = theme;
} catch (error) {
// 失败时使用默认主题,不阻塞渲染
document.documentElement.setAttribute('data-theme', 'light');
}
})();
</script><link rel="stylesheet" href="../css/ares.css"><script data-netlify-skip-bundle="true">(function() {
document.addEventListener('DOMContentLoaded', function() {
const theme = document.documentElement.getAttribute('data-theme');
const pageWrapper = document.getElementById('page-wrapper');
if (pageWrapper && theme) {
pageWrapper.setAttribute('data-theme', theme);
}
});
})();
</script><!-- hexo injector head_end start -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.12.0/dist/katex.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/hexo-math@4.0.0/dist/style.css">
<!-- hexo injector head_end end --><meta name="generator" content="Hexo 8.1.1"><link rel="alternate" href="atom.xml" title="Cytrogen 的个人博客" type="application/atom+xml">
</head><body><div id="page-wrapper"><a class="skip-link" href="#main-content">跳到主要内容</a><div class="wrap"><header><a class="logo-link" href="../index.html"><img src="../favicon.png" alt="logo"></a><div class="h-card visually-hidden"><img class="u-photo" src="https://cytrogen.icu/favicon.png" alt="Cytrogen"><a class="p-name u-url u-uid" href="https://cytrogen.icu">Cytrogen</a><p class="p-note">Cytrogen 的个人博客,Cytrogen's Blog</p><a class="u-url" rel="me noopener" target="_blank" href="https://m.otter.homes/@Cytrogen">Mastodon</a><a class="u-url" rel="me noopener" target="_blank" href="https://github.com/cytrogen">GitHub</a></div><nav class="site-nav"><div class="nav-main"><div class="nav-primary"><ul class="nav-list hidden-mobile"><li class="nav-item"><a class="nav-link" href="../index.html">首页</a></li></ul><div class="nav-tools"><div class="language-menu"><button class="language-toggle" type="button"><svg class="icon icon-globe" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8zm7.5-6.923c-.67.204-1.335.82-1.887 1.855A7.97 7.97 0 0 0 5.145 4H7.5V1.077zM4.09 4a9.267 9.267 0 0 1 .64-1.539 6.7 6.7 0 0 1 .597-.933A7.025 7.025 0 0 0 2.255 4H4.09zm-.582 3.5c.03-.877.138-1.718.312-2.5H1.674a6.958 6.958 0 0 0-.656 2.5h2.49zM4.847 5a12.5 12.5 0 0 0-.338 2.5H7.5V5H4.847zM8.5 5v2.5h2.99a12.495 12.495 0 0 0-.337-2.5H8.5zM4.51 8.5a12.5 12.5 0 0 0 .337 2.5H7.5V8.5H4.51zm3.99 0V11h2.653c.187-.765.306-1.608.338-2.5H8.5zM5.145 12c.138.386.295.744.468 1.068.552 1.035 1.218 1.65 1.887 1.855V12H5.145zm.182 2.472a6.696 6.696 0 0 1-.597-.933A9.268 9.268 0 0 1 4.09 12H2.255a7.024 7.024 0 0 0 3.072 2.472zM3.82 11a13.652 13.652 0 0 1-.312-2.5h-2.49c.062.89.291 1.733.656 2.5H3.82zm6.853 3.472A7.024 7.024 0 0 0 13.745 12H11.91a9.27 9.27 0 0 1-.64 1.539 6.688 6.688 0 0 1-.597.933zM8.5 12v2.923c.67-.204 1.335-.82 1.887-1.855A7.97 7.97 0 0 0 10.855 12H8.5zm3.68-1h2.146c.365-.767.594-1.61.656-2.5h-2.49a13.65 13.65 0 0 1-.312 2.5zm2.802-3.5a6.959 6.959 0 0 0-.656-2.5H12.18c.174.782.282 1.623.312 2.5h2.49zM11.27 2.461c.247.464.462.98.64 1.539h1.835a7.024 7.024 0 0 0-3.072-2.472c.218.284.418.598.597.933zM10.855 4a7.966 7.966 0 0 0-.468-1.068C9.835 1.897 9.17 1.282 8.5 1.077V4h2.355z"></path></svg><span>中文</span></button><div class="language-dropdown"></div></div></div><div class="nav-controls"><div class="more-menu hidden-mobile"><button class="more-toggle" type="button"><span>更多</span><svg class="icon icon-chevron-down" width="12" height="12" viewBox="0 0 12 12" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 8.825c-.2 0-.4-.1-.5-.2l-3.3-3.3c-.3-.3-.3-.8 0-1.1s.8-.3 1.1 0l2.7 2.7 2.7-2.7c.3-.3.8-.3 1.1 0s.3.8 0 1.1l-3.3 3.3c-.1.1-.3.2-.5.2z"></path></svg></button><div class="more-dropdown"><ul class="dropdown-list"><li class="dropdown-item"><a class="nav-link" href="../archives/index.html">归档</a></li><li class="dropdown-item"><a class="nav-link" href="../categories/index.html">分类</a></li><li class="dropdown-item"><a class="nav-link" href="../tags/index.html">标签</a></li><li class="dropdown-item"><a class="nav-link" href="../about/index.html">关于</a></li><li class="dropdown-item"><a class="nav-link" href="../sitemap/index.html">领地地图</a></li></ul></div></div><div class="theme-switcher"><button class="theme-toggle" type="button" role="switch" aria-pressed="false" aria-label="切换主题"><div class="theme-icon moon-icon"><svg class="icon icon-moon" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M6 .278a.768.768 0 0 1 .08.858 7.208 7.208 0 0 0-.878 3.46c0 4.021 3.278 7.277 7.318 7.277.527 0 1.04-.055 1.533-.16a.787.787 0 0 1 .81.316.733.733 0 0 1-.031.893A8.349 8.349 0 0 1 8.344 16C3.734 16 0 12.286 0 7.71 0 4.266 2.114 1.312 5.124.06A.752.752 0 0 1 6 .278z"></path></svg></div><div class="theme-icon sun-icon"><svg class="icon icon-sun" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M8 11a3 3 0 1 1 0-6 3 3 0 0 1 0 6zm0 1a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM8 0a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 0zm0 13a.5.5 0 0 1 .5.5v2a.5.5 0 0 1-1 0v-2A.5.5 0 0 1 8 13zm8-5a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2a.5.5 0 0 1 .5.5zM3 8a.5.5 0 0 1-.5.5h-2a.5.5 0 0 1 0-1h2A.5.5 0 0 1 3 8zm10.657-5.657a.5.5 0 0 1 0 .707l-1.414 1.415a.5.5 0 1 1-.707-.708l1.414-1.414a.5.5 0 0 1 .707 0zm-9.193 9.193a.5.5 0 0 1 0 .707L3.05 13.657a.5.5 0 0 1-.707-.707l1.414-1.414a.5.5 0 0 1 .707 0zm9.193 2.121a.5.5 0 0 1-.707 0l-1.414-1.414a.5.5 0 0 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .707zM4.464 4.465a.5.5 0 0 1-.707 0L2.343 3.05a.5.5 0 1 1 .707-.707l1.414 1.414a.5.5 0 0 1 0 .708z"></path></svg></div></button></div><details class="mobile-menu-details hidden-desktop"><summary class="hamburger-menu" aria-label="nav.menu"><svg class="icon icon-bars" width="16" height="16" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" focusable="false"><path d="M2.5 12a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h10a.5.5 0 0 1 0 1H3a.5.5 0 0 1-.5-.5z"></path></svg><span class="menu-text">nav.menu</span></summary><div class="mobile-menu-dropdown"><ul class="mobile-nav-list"><li class="mobile-nav-item"><a class="mobile-nav-link" href="../index.html">首页</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../archives/index.html">归档</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../categories/index.html">分类</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../tags/index.html">标签</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../about/index.html">关于</a></li><li class="mobile-nav-item"><a class="mobile-nav-link" href="../sitemap/index.html">领地地图</a></li></ul></div></details></div></div></div></nav></header><main class="container" id="main-content" tabindex="-1"><div class="post"><article class="post-block h-entry"><div class="post-meta p-author h-card visually-hidden"><img class="author-avatar u-photo" src="../favicon.png" alt="Cytrogen"><span class="p-name">Cytrogen</span><a class="u-url" href="https://cytrogen.icu">https://cytrogen.icu</a></div><a class="post-permalink u-url u-uid visually-hidden" href="https://cytrogen.icu/posts/fed2.html">永久链接</a><div class="p-summary visually-hidden"><p>近期在学习 IBM 全栈应用开发微学士课程,故此记录学习笔记。</p></div><div class="visually-hidden"><a class="p-category" href="../categories/%E7%BC%96%E7%A8%8B%E7%AC%94%E8%AE%B0/">编程笔记</a><a class="p-category" href="../tags/JavaScript/">JavaScript</a><a class="p-category" href="../tags/IBM/">IBM</a><a class="p-category" href="../tags/Node-js/">Node.js</a><a class="p-category" href="../tags/Express-js/">Express.js</a></div><h1 class="post-title p-name">IBM 全栈开发【5】:Node.JS & Express 创建后端</h1><div class="post-info"><time class="post-date dt-published" datetime="2024-01-21T05:26:13.000Z">1/21/2024</time><time class="dt-updated visually-hidden" datetime="2026-02-09T17:16:54.717Z"></time></div><div class="post-content e-content"><html><head></head><body><p>近期在学习 IBM 全栈应用开发微学士课程,故此记录学习笔记。</p>
<span id="more"></span>
<h1 id="1-服务端javascript入门"><a class="markdownIt-Anchor" href="#1-服务端javascript入门"></a> 1. 服务端 JavaScript 入门</h1>
<p>客户端 - 服务端的应用程序(比如基于云的应用程序)通常由「前端」和「后端」组成。前端是指用户在浏览器中看到的应用程序的部分,后端是指在服务器上运行的应用程序的部分。</p>
<h2 id="11-后端"><a class="markdownIt-Anchor" href="#11-后端"></a> 1.1. 后端</h2>
<p>后端开发人员负责开发确保网站正常运行的技术,包括服务器端的应用程序、数据库和服务器。</p>
<ul>
<li>服务器由硬件和软件组成,它们与客户端进行通信并提供功能。多种类型的服务器可用于不同的目的:Web 服务器用于存储和提供网站的内容,数据库服务器用于存储和提供数据,应用程序服务器用于存储和提供应用程序的功能</li>
<li>数据库是一种用于存储和访问数据的软件。数据库服务器是一种用于存储和访问数据的服务器。数据库服务器可以存储和访问结构化数据(例如,关系数据库)和非结构化数据(例如,文本文件、图像和视频)</li>
<li>网页应用程序接口(API)允许两个软件之间相互通信。网络服务便是网页应用程序接口的一种、使用 HTTP 请求进行通信</li>
<li>编程语言是一种用于编写软件的语言。用于后端开发的编程语言包括 Java、Python、PHP、Ruby、JavaScript 和 C#</li>
<li>框架是一种用于编写软件的工具。框架提供了一组通用的功能,可以帮助开发人员编写软件。用于后端开发的框架包括 Spring、Django、Laravel、Ruby on Rails、<a target="_blank" rel="noopener" href="http://Node.xn--jsASP-sn9h.NET">Node.JS 和 ASP.NET</a></li>
<li>runtimes 是一种用于运行软件的工具,行为类似于微型操作系统,为应用程序的运行提供必要的资源。Node.JS 就是后端 runtime 环境的一个例子</li>
</ul>
<p>Node.JS 作为一种后端技术之所以如此流行,原因之一是它运行在谷歌 Chrome 浏览器的开源 V8 引擎上。V8 引擎也是在前端运行浏览器的引擎。大多数现代浏览器都使用 V8 引擎,因此,Node.JS 和浏览器之间的代码兼容性很好。</p>
<h4 id="111-可扩展性"><a class="markdownIt-Anchor" href="#111-可扩展性"></a> 1.1.1. 可扩展性</h4>
<p>可扩展性对企业软件的成功至关重要。它受应用程序负载的影响,是后端的一大责任。而负载指的是并发用户、交易、数据量和其他因素的总和。</p>
<p>可扩展性是指应用程序在不影响性能的情况下动态处理负载增减的能力。</p>
<h2 id="12-nodejs"><a class="markdownIt-Anchor" href="#12-nodejs"></a> 1.2. Node.JS</h2>
<p>Node.JS 是一个运行在 V8 上的开源语言,它是 JavaScript 的服务器端实现。Node.JS 由事件驱动,使用非阻塞 I / O 模型,这使得它非常轻量级、高效和可扩展。</p>
<p>Node.JS 着重强调使用轻量级语言进行并发编程,它是一种单线程语言,但是,它可以使用事件循环和回调函数来处理并发。</p>
<p>Node.JS 适合希望使用回调函数和 Node.JS runtime 事件循环等功能来构建并发应用程序的开发人员。JavaScript 语言和 Node.JS runtime 的这些功能使得开发人员只需使用一套最少的工具就可以实现快速开发。</p>
<p>通过服务器端 JavaScript,Node.JS 的应用程序可以处理和路由来自客户端的请求:</p>
<ol>
<li>用户在用 HTML 和 CSS 编写的用户界面中选择一个选项</li>
<li>用户的这一操作会触发在客户端实现业务逻辑的 JavaScript 代码</li>
<li>JavaScript 代码会向服务器发送一个请求(通过 HTTP 调用带有 JSON 数据的 API)</li>
<li>作为在服务器上运行的 Node.JS 应用程序的一部分,REST 网络服务会接收请求并处理它</li>
<li>REST 网络服务处理请求后,通过 HTTP 将结果作为 JSON 数据返回给客户端</li>
</ol>
<h4 id="121-模块"><a class="markdownIt-Anchor" href="#121-模块"></a> 1.2.1. 模块</h4>
<p>在 Node.JS 中,模块是包含相关的、已封装的 JavaScript 代码的文件,用于实现特定的功能。模块可以是内置的,也可以是外部的;可以是单个文件,也可以是文件夹。</p>
<p>当外部应用程序需要使用模块中包含的代码时,应用程序需要调用该模块。而调用模块就需要使用语句 <code>import()</code> 或者 <code>require()</code>。</p>
<blockquote>
<p>模块规范:<br>
一个或多个模块组成一个包,包是一个目录,其中包含一个 <code>package.json</code> 文件,该文件描述了包的内容。包可以发布到 npm(Node.JS 包管理器)上,以便其他开发人员可以使用它们。</p>
<p>常用的模块规范有 CommonJS 和 ES。默认情况下,Node.JS 使用 CommonJS 规范,但是,Node.JS 也支持 ES 规范。库作者仅需要将包文件的扩展名从 <code>.js</code> 更改为 <code>.mjs</code>,就可以使用 ES 模块规范。</p>
<p>CommonJS 规范使用 <code>require()</code>,而 ES 规范使用 <code>import()</code>。<br>
当需要在自身文件之外使用模块时,必须先导出模块。在使用 CommonJS 规范时,可以使用 <code>module.exports</code> 导出模块;在使用 ES 规范时,可以使用 <code>export</code> 导出模块。</p>
</blockquote>
<p><code>import()</code> 和 <code>require()</code> 的区别:</p>
<table>
<thead>
<tr>
<th style="text-align:left">import()</th>
<th style="text-align:left">require()</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align:left">必须在文件开头调用</td>
<td style="text-align:left">可以在文件的任何位置调用</td>
</tr>
<tr>
<td style="text-align:left">不能在条件语句和函数中调用</td>
<td style="text-align:left">可以在条件语句和函数中调用</td>
</tr>
<tr>
<td style="text-align:left">静态绑定</td>
<td style="text-align:left">动态绑定</td>
</tr>
<tr>
<td style="text-align:left">在编译时解析</td>
<td style="text-align:left">在运行时解析</td>
</tr>
<tr>
<td style="text-align:left">异步</td>
<td style="text-align:left">同步</td>
</tr>
<tr>
<td style="text-align:left">对比 <code>require()</code>,在涉及到加载数百个模块的应用程序中运行速度更快</td>
<td style="text-align:left"></td>
</tr>
</tbody>
</table>
<blockquote>
<p><code>require()</code>:</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// export from a file named message.js</span></span><br><span class="line"><span class="variable language_">module</span>.<span class="property">exports</span> = <span class="string">'Hello Programmers'</span>;</span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// import from the message.js file</span></span><br><span class="line"><span class="keyword">let</span> msg = <span class="built_in">require</span>(<span class="string">'./message.js'</span>);</span><br><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(msg);</span><br></pre></td></tr></tbody></table></figure>
</blockquote>
<blockquote>
<p><code>import()</code>:</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// export from file named module.mjs</span></span><br><span class="line"><span class="keyword">const</span> a = <span class="number">1</span>;</span><br><span class="line"><span class="keyword">export</span> { a <span class="keyword">as</span> <span class="string">"myvalue"</span> };</span><br></pre></td></tr></tbody></table></figure>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// import from module.mjs</span></span><br><span class="line"><span class="keyword">import</span> { myvalue } <span class="keyword">from</span> <span class="variable language_">module</span>.<span class="property">mjs</span>;</span><br></pre></td></tr></tbody></table></figure>
</blockquote>
<h4 id="122-创建简单的网络服务器"><a class="markdownIt-Anchor" href="#122-创建简单的网络服务器"></a> 1.2.2. 创建简单的网络服务器</h4>
<p>Node.JS runtime 打包了许多实用程序模块,你可以使用它们来创建和扩展应用程序。例如,HTTP Node.JS 模块提供了能够监听 HTTP 请求的功能。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> server = http.<span class="title function_">createServer</span>(<span class="keyword">function</span>(<span class="params">request, response</span>) { <span class="comment">// create an instance of a web server</span></span><br><span class="line"> <span class="keyword">let</span> body = <span class="string">"Hello World!"</span>;</span><br><span class="line"> response.<span class="title function_">writeHead</span>(<span class="number">200</span>, { <span class="comment">// this callback function handles the incoming request message and provides an appropriate response message</span></span><br><span class="line"> <span class="string">"Content-Length"</span>: body.<span class="property">length</span>,</span><br><span class="line"> <span class="string">"Content-Type"</span>: <span class="string">"text/plain"</span></span><br><span class="line"> });</span><br><span class="line"> response.<span class="title function_">end</span>(body);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">server.<span class="title function_">listen</span>(<span class="number">8080</span>); <span class="comment">// set the server to listen to a specific port</span></span><br></pre></td></tr></tbody></table></figure>
<h4 id="123-packagejson"><a class="markdownIt-Anchor" href="#123-packagejson"></a> 1.2.3. <code>Package.json</code></h4>
<p>一个软件包由一个或多个模块组成。每个软件包都有一个 <code>package.json</code> 文件,用于描述 Node.JS 模块的详细信息。</p>
<p>如果模块没有 <code>package.json</code> 文件,Node.JS 就会假定主模块是 <code>index.js</code> 文件。</p>
<figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// Package.json</span></span><br><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"name"</span><span class="punctuation">:</span> <span class="string">"mod_today"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"version"</span><span class="punctuation">:</span> <span class="string">"1.0.0"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"main"</span><span class="punctuation">:</span> <span class="string">"./lib/today"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"license"</span><span class="punctuation">:</span> <span class="string">"Apache-2.0"</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure>
<p>要为模块指定不同的主模块,可以在模块目录中 Node.JS 脚本的相对路径中指定主模块。</p>
<h4 id="124-导入导出nodejs模块"><a class="markdownIt-Anchor" href="#124-导入导出nodejs模块"></a> 1.2.4. 导入 / 导出 Node.JS 模块</h4>
<p>你可以使用 <code>require()</code> 函数导入 Node.JS 模块。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> today = <span class="built_in">require</span>(<span class="string">"./today"</span>);</span><br></pre></td></tr></tbody></table></figure>
<p><code>require()</code> 语句假定了脚本的文件扩展名为 <code>.js</code>。它会创建一个对象来表示导入的模块,并将其分配给变量 <code>today</code>。</p>
<p>每个 Node.JS 模块都有一个隐式 <code>exports</code> 对象。要向导入模块的 Node.JS 应用程序提供函数或值,就需要在 <code>exports</code> 中添加属性。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> date = <span class="keyword">new</span> <span class="title class_">Date</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> days = [<span class="string">'Monday'</span>, <span class="string">'Tuesday'</span>, <span class="string">'Wednesday'</span>, <span class="string">'Thursday'</span>, <span class="string">'Friday'</span>, <span class="string">'Saturday'</span>, <span class="string">'Sunday'</span>];</span><br><span class="line"></span><br><span class="line"><span class="built_in">exports</span>.<span class="property">dayOfWeek</span> = <span class="keyword">function</span>(<span class="params"></span>) { <span class="comment">// the dayOfWeek property is added to the exports object</span></span><br><span class="line"> <span class="keyword">return</span> days[date.<span class="title function_">getDay</span>() - <span class="number">1</span>];</span><br><span class="line">};</span><br></pre></td></tr></tbody></table></figure>
<p>导入 Node.JS 模块时,<code>require()</code> 函数会返回一个 JavaScript 对象,该对象代表着模块的一个实例。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> today = <span class="built_in">require</span>(<span class="string">"./mod_today"</span>); <span class="comment">// the today variable is an instance of the today Node.js module that is called "today"</span></span><br></pre></td></tr></tbody></table></figure>
<p>要访问模块的属性,就要从变量中检索属性。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"Happy %s!"</span>, today.<span class="title function_">dayOfWeek</span>()); <span class="comment">// today.dayOfWeek() represents the current exported property from the today Node.js module</span></span><br></pre></td></tr></tbody></table></figure>
<h2 id="13-express"><a class="markdownIt-Anchor" href="#13-express"></a> 1.3. Express</h2>
<p>Express.JS 是一个高度可配置的框架,用于在 Node.JS 上构建应用程序。它通过使用 HTTP 实现程序方法和中间件来抽象出 Node.JS 中的低级 API。</p>
<p>以下功能可让你快速开发应用程序:</p>
<ol>
<li>
<p>Express.JS 应用程序</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br></pre></td></tr></tbody></table></figure>
</li>
<li>
<p>图像、CSS 和 JavaScript 文件等静态文件</p>
</li>
<li>
<p>静态路由:定义接收和处理客户端请求的端点</p>
</li>
<li>
<p><code>server.js</code>:用于启动应用程序的文件</p>
</li>
<li>
<p><code>package.json</code>:用于定义应用程序的依赖项和脚本的文件</p>
</li>
</ol>
<h2 id="14-软件包管理器"><a class="markdownIt-Anchor" href="#14-软件包管理器"></a> 1.4. 软件包管理器</h2>
<p>软件包管理器是一套用于处理包含依赖关系的模块和软件包的工具。依赖关系是指一个软件包依赖于另一个软件包。</p>
<p>代码库通常包含着许多依赖项,但代码库本身是独立的,不依赖于代码库之外的任何东西。这种独立性使得代码库可以在不同的环境中使用。</p>
<p>软件包管理器能够自动完成查找、安装、更新、配置、维护和删除软件包的工作。它们通常连接并维护一个数据库,其中包含着软件包的依赖关系和版本信息。</p>
<h4 id="141-npm"><a class="markdownIt-Anchor" href="#141-npm"></a> 1.4.1. npm</h4>
<p>npm 是 Node.JS 的软件包管理器。它是一个命令行工具,用于安装、更新、配置和删除 Node.JS 软件包。</p>
<p>所有的 npm 软件包都需要一个 <code>package.json</code> 文件,该文件描述了软件包的详细信息。</p>
<figure class="highlight json"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"name"</span><span class="punctuation">:</span> <span class="string">"myapp"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"version"</span><span class="punctuation">:</span> <span class="string">"1.0.0"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"description"</span><span class="punctuation">:</span> <span class="string">"My first Node.js app"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"main"</span><span class="punctuation">:</span> <span class="string">"server.js"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"scripts"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"start"</span><span class="punctuation">:</span> <span class="string">"node server.js"</span></span><br><span class="line"> <span class="punctuation">}</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"author"</span><span class="punctuation">:</span> <span class="string">"John Doe"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"license"</span><span class="punctuation">:</span> <span class="string">"ISC"</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"dependencies"</span><span class="punctuation">:</span> <span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"express"</span><span class="punctuation">:</span> <span class="string">"^4.17.1"</span></span><br><span class="line"> <span class="punctuation">}</span></span><br><span class="line"><span class="punctuation">}</span></span><br></pre></td></tr></tbody></table></figure>
<p>npm 使用 <code>package.json</code> 文件中的 <code>dependencies</code> 属性来确定软件包的依赖关系。<code>dependencies</code> 属性是一个对象,其中包含着软件包的名称和版本号。</p>
<p>npm 有两种安装软件包的方式:本地安装或者全局安装。如果安装的软件包要在应用程序中使用,就应该使用本地安装。如果安装的软件包要在命令行中使用,就应该使用全局安装。</p>
<p>默认情况下 npm 会采用本地安装。</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install <package_name></span><br></pre></td></tr></tbody></table></figure>
<p>该命令将在当前工作目录中创建一个 <code>node_modules</code> 文件夹,并将软件包安装到该文件夹中。</p>
<p>全局安装意味着安装软件包的计算机上的所有应用程序都可以使用该代码。全局安装应当谨慎使用,因为它会在计算机上创建一个全局软件包,这可能会导致版本冲突。</p>
<p>要安装 <code>node_modules</code> 文件夹中的所有软件包,要使用以下命令:</p>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install -g <package_name></span><br></pre></td></tr></tbody></table></figure>
<h1 id="2-异步io与回调编程"><a class="markdownIt-Anchor" href="#2-异步io与回调编程"></a> 2. 异步 I / O 与回调编程</h1>
<h2 id="21-异步io"><a class="markdownIt-Anchor" href="#21-异步io"></a> 2.1. 异步 I / O</h2>
<p>所有的网络操作都是异步的,因为它们需要等待网络响应。</p>
<blockquote>
<p>网络服务调用的响应可能不会立即返回。当应用程序阻塞(或等待)网络操作完成时,就会浪费服务器上的处理时间。</p>
</blockquote>
<p>Node.JS 以非阻塞方式进行所有网络操作。每个网络操作都会立即返回。要处理网络调用的结果,就需要编写一个回调函数。</p>
<p>应用程序、Node.JS 框架、调用远程服务器的网络服务和回调函数之间的交互如下:</p>
<ol>
<li>应用程序会调用 <code>http.request()</code>,该函数会调用远程网络服务器并请求网络服务</li>
<li>在 Node.JS 框架从远程网络服务器接收 HTTP 响应消息之前,它会立即返回 <code>http.request()</code> 函数调用的结果。该结果只表明请求消息已成功发送,并不会说明任何有关响应消息的信息</li>
<li>当 Node.JS 框架从远程服务器接收到 HTTP 响应消息时,它会调用在 <code>http.request()</code> 函数调用过程中定义的回调函数。该函数处理 HTTP 响应消息,并将结果返回给应用程序</li>
</ol>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> options = { <span class="comment">// included the hostname of the remote server, and a URL resource path</span></span><br><span class="line"> <span class="attr">host</span>: <span class="string">"w1.weather.gov"</span>,</span><br><span class="line"> <span class="attr">path</span>: <span class="string">"/xml/current_obs/KSFO.xml"</span></span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">http.<span class="title function_">request</span>(options, <span class="keyword">function</span>(<span class="params">response</span>) { <span class="comment">// when the Node.js module calls this anonymous function, events occur while it is receiving parts of the HTTP response object</span></span><br><span class="line"> <span class="keyword">let</span> buffer = <span class="string">""</span>;</span><br><span class="line"> <span class="keyword">let</span> result = <span class="string">""</span>;</span><br><span class="line"> </span><br><span class="line"> response.<span class="title function_">on</span>(<span class="string">"data"</span>, <span class="keyword">function</span>(<span class="params">chunk</span>) {</span><br><span class="line"> buffer += chunk;</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> response.<span class="title function_">on</span>(<span class="string">"end"</span>, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(buffer);</span><br><span class="line"> });</span><br><span class="line">}).<span class="title function_">end</span>();</span><br></pre></td></tr></tbody></table></figure>
<ul>
<li>在实际的应用程序时,你可能需要使用 HTTPS 而不是 HTTP</li>
</ul>
<h4 id="211-httprequest"><a class="markdownIt-Anchor" href="#211-httprequest"></a> 2.1.1. <code>http.request()</code></h4>
<p>该函数接收一个 URL 和一组选项。如果 URL 和选项都被传入,则将两者合并,选项优先。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http.<span class="title function_">request</span>(options, [callback <span class="keyword">function</span>]);</span><br></pre></td></tr></tbody></table></figure>
<p>该方法还可以接收一个可选的回调函数,在收到响应后立即调用。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">http.<span class="title function_">request</span>(options, <span class="keyword">function</span>(<span class="params">response</span>) { ... });</span><br></pre></td></tr></tbody></table></figure>
<p>当 <code>http.request()</code> 调用回调函数时,会在回调函数的第一个参数中传递一个响应对象。该回调函数的第一个参数就是响应对象。</p>
<p>Node.JS 框架会在请求函数运行时发出多个事件。你可以使用 <code>object.on()</code> 方法并将事件名称作为第一个参数传递,从而监听这些事件。如果请求成功,每次数据输入时都会在响应对象上触发一个数据事件,响应结束时触发一个结束事件。</p>
<h4 id="212-处理错误"><a class="markdownIt-Anchor" href="#212-处理错误"></a> 2.1.2. 处理错误</h4>
<p>如果请求失败,在 <code>close</code> 事件之后就会出现 <code>error</code> 事件。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> request = http.<span class="title function_">request</span>(options, <span class="keyword">function</span>(<span class="params">response</span>) { ... });</span><br><span class="line"> </span><br><span class="line">request.<span class="title function_">on</span>(<span class="string">"error"</span>, <span class="keyword">function</span>(<span class="params">e</span>) {</span><br><span class="line"> <span class="title function_">resultCallback</span>(e.<span class="property">message</span>);</span><br><span class="line">});</span><br><span class="line">request.<span class="title function_">end</span>();</span><br></pre></td></tr></tbody></table></figure>
<h2 id="22-回调函数"><a class="markdownIt-Anchor" href="#22-回调函数"></a> 2.2. 回调函数</h2>
<p>作为一个异步框架,Node.JS 广泛地使用了回调函数。回调函数是一个函数,它作为参数传递给另一个函数,并在另一个函数完成后调用。</p>
<p>软件开发工具包(SDK)中的 Node.JS 模块会将错误对象作为回调函数的第一个参数。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="title class_">Call</span> <span class="keyword">function</span>(<span class="params">(error)</span>)</span><br></pre></td></tr></tbody></table></figure>
<p>根据这一约定,回调函数会检查第一个参数是否包含了错误对象。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">function</span>(<span class="params">error, parameter1, parameter2, ...</span>) { ... }</span><br></pre></td></tr></tbody></table></figure>
<p>如果定义了错误对象,回调函数就会处理错误并清理所有打开的网络或数据库连接。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">weather.<span class="title function_">current</span>(location, <span class="keyword">function</span>(<span class="params">error, temp_f</span>) {</span><br><span class="line"> <span class="keyword">if</span> (error) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(error); <span class="comment">// if the error is defined, print the error message</span></span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// otherwise, the weather.current function call completed successfully</span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"The current weather reading is %s degrees."</span>, temp_f);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">response.<span class="title function_">end</span>(<span class="string">"... ${temp_f}_"</span>)</span><br></pre></td></tr></tbody></table></figure>
<h4 id="221-传递错误对象"><a class="markdownIt-Anchor" href="#221-传递错误对象"></a> 2.2.1. 传递错误对象</h4>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">exports</span>.<span class="property">current</span> = <span class="keyword">function</span>(<span class="params">location, resultCallback</span>) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> http.<span class="title function_">request</span>(options, <span class="keyword">function</span>(<span class="params">response</span>) {</span><br><span class="line"> <span class="keyword">let</span> buffer = <span class="string">""</span>;</span><br><span class="line"> <span class="keyword">let</span> result = <span class="string">""</span>;</span><br><span class="line"> </span><br><span class="line"> response.<span class="title function_">on</span>(<span class="string">"data"</span>, <span class="keyword">function</span>(<span class="params">chunk</span>) {</span><br><span class="line"> buffer += chunk;</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> response.<span class="title function_">on</span>(<span class="string">"end"</span>, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="title function_">parseString</span>(buffer, <span class="keyword">function</span>(<span class="params">error, result</span>) {</span><br><span class="line"> <span class="keyword">if</span> (error) {</span><br><span class="line"> <span class="title function_">resultCallback</span>(error);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="title function_">resultCallback</span>(<span class="literal">null</span>, result.<span class="property">current_observation</span>.<span class="property">temp_f</span>[<span class="number">0</span>]);</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h4 id="222-每级一个回调"><a class="markdownIt-Anchor" href="#222-每级一个回调"></a> 2.2.2. 每级一个回调</h4>
<p>当 Node.JS 应用程序以非阻塞方式来调用一个模块时,该应用程序会提供一个回调函数来处理结果。 如果主应用程序调用了 <code>http.request()</code>,它就必须提供一个回调处理程序来处理 HTTP 响应消息。</p>
<p>如果主应用程序调用了一个调用了 <code>http.request()</code> 的函数,那就会有两个回调函数:</p>
<ol>
<li>自定义模块有一个回调函数,用于处理来自 <code>http.request()</code> 的 HTTP 响应消息</li>
<li>主营用程序有一个回调函数,用于处理第一个回调函数捕获的结果</li>
</ol>
<p>带回调的主应用程序:</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> weather = <span class="built_in">require</span>(<span class="string">"./weather"</span>);</span><br><span class="line"><span class="keyword">let</span> location = <span class="string">"KSFO"</span>;</span><br><span class="line"></span><br><span class="line">weather.<span class="title function_">current</span>(location, <span class="keyword">function</span>(<span class="params">temp_f</span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(temp_f);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<p>主程序调用 <code>weather.current()</code> 时,会传递一个匿名的回调函数来处理调用结果。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">exports</span>.<span class="property">current</span> = <span class="keyword">function</span>(<span class="params">location, resultCallback</span>) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> http.<span class="title function_">request</span>(options, <span class="keyword">function</span>(<span class="params">response</span>) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> response.<span class="title function_">on</span>(<span class="string">"end"</span>, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="title function_">resultCallback</span>(...);</span><br><span class="line"> });</span><br><span class="line"> }).<span class="title function_">end</span>();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>自定义的 Node.JS 模块函数中的 <code>resultCallback()</code> 函数链接着主应用程序中 <code>weather.current()</code> 函数的匿名回调函数 <code>function(temp_f)</code>。</p>
<p>通过回调返回结果:</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">exports</span>.<span class="property">current</span> = <span class="keyword">function</span>(<span class="params">location, resultCallback</span>) {</span><br><span class="line"> <span class="keyword">let</span> option = {</span><br><span class="line"> <span class="attr">host</span>: <span class="string">"w1.weather.gov"</span>,</span><br><span class="line"> <span class="attr">path</span>: <span class="string">"/xml/current_obs/"</span> + location + <span class="string">".xml"</span></span><br><span class="line"> };</span><br><span class="line"> </span><br><span class="line"> http.<span class="title function_">request</span>(options, <span class="keyword">function</span>(<span class="params">response</span>) {</span><br><span class="line"> <span class="keyword">let</span> buffer = <span class="string">""</span>;</span><br><span class="line"> </span><br><span class="line"> response.<span class="title function_">on</span>(<span class="string">"data"</span>, <span class="keyword">function</span>(<span class="params">chunk</span>) {</span><br><span class="line"> buffer += chunk;</span><br><span class="line"> });</span><br><span class="line"> </span><br><span class="line"> response.<span class="title function_">on</span>(<span class="string">"end"</span>, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="title function_">parseString</span>(buffer, <span class="keyword">function</span>(<span class="params">error, result</span>) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="title function_">resultCallback</span>(<span class="literal">null</span>, result.<span class="property">current_observation</span>.<span class="property">temp_f</span>[<span class="number">0</span>]);</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"> }).<span class="title function_">end</span>();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<p>另一个回调函数的例子:</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> message = <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"This message is shown after 3 seconds"</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">setTimeout</span>(message, <span class="number">3000</span>);</span><br></pre></td></tr></tbody></table></figure>
<p>JavaScript 中有一个内置方法叫 <code>setTimeout()</code>,它会在执行操作前等待指定的时间(以毫秒为单位)。在示例中,信息被传入 <code>setTimeout()</code> 函数。因此,在等待 3 秒后,<code>setTimeout()</code> 会将消息写入控制台。</p>
<p>通常这些异步回调(简称 async)都用于访问数据库中的数值、下载图像、读取文件等。</p>
<h4 id="223-回调地狱"><a class="markdownIt-Anchor" href="#223-回调地狱"></a> 2.2.3. 回调地狱</h4>
<p>回调地狱是指在异步编程中,回调函数嵌套过多,导致代码难以阅读和维护。每个回调函数都依赖于前一个回调函数,并等待前一个回调函数完成后才能执行。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="title function_">firstFunction</span>(args, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="title function_">secondFunction</span>(args, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="title function_">thirdFunction</span>(args, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="comment">// And so on ...</span></span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<blockquote>
<p>这种结构有时也被称为「The Pyramid of Doom」(末日金字塔)。</p>
</blockquote>
<p>回调的另一个问题是 IoC(控制反转)。当控制流(如指令的执行)处于代码的外部时,就会发生控制反转。很多时候,回调会将控制权转交给第三方,但是第三方代码的问题和错误可能很难被发现。这种情况下你不得不去信任第三方代码或者编写额外的代码来确保第三方代码不会在不应该的时候被调用、被调用的次数过多或过少、丢失上下文、传回错误的参数等。</p>
<p>要想缓解回调地狱和 IoC 的问题,你可以:</p>
<ul>
<li>写注释</li>
<li>使用 Promise</li>
<li>将函数拆分成更小的函数</li>
<li>使用 <code>async/await</code></li>
</ul>
<h2 id="23-promise"><a class="markdownIt-Anchor" href="#23-promise"></a> 2.3. Promise</h2>
<p>对于 API 请求、I / O 操作和其他异步操作,Promise 是一种更好的解决方案。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> prompt = <span class="built_in">require</span>(<span class="string">"prompt-sync"</span>);</span><br><span class="line"><span class="keyword">let</span> fs = <span class="built_in">require</span>(<span class="string">"fs"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> methCall = <span class="keyword">new</span> <span class="title class_">Promise</span>(<span class="function">(<span class="params">resolve, reject</span>) =></span> {</span><br><span class="line"> <span class="built_in">setTimeout</span>(<span class="function">() =></span> {</span><br><span class="line"> <span class="keyword">let</span> filename = <span class="title function_">prompt</span>(<span class="string">"What is the name of the file?"</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">const</span> data = fs.<span class="title function_">readFileSync</span>(filename, {</span><br><span class="line"> <span class="attr">encoding</span>: <span class="string">"UTF-8"</span>,</span><br><span class="line"> <span class="attr">flag</span>: <span class="string">"r"</span></span><br><span class="line"> });</span><br><span class="line"> <span class="title function_">resolve</span>(data);</span><br><span class="line"> } <span class="keyword">catch</span>(err) {</span><br><span class="line"> <span class="title function_">reject</span>(err);</span><br><span class="line"> }</span><br><span class="line"> }, <span class="number">3000</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<h4 id="231-axios请求"><a class="markdownIt-Anchor" href="#231-axios请求"></a> 2.3.1. Axios 请求</h4>
<p>HTTP 请求在同步调用时可能会阻塞。Node.JS 生态系统中有许多包,它们将 Promise 封装在 HTTP 请求中,axios 就是其中之一。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> axios = <span class="built_in">require</span>(<span class="string">"axios"</span>).<span class="property">default</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="title function_">connectToURL</span> = (<span class="params">url</span>) => {</span><br><span class="line"> <span class="keyword">const</span> req = axios.<span class="title function_">get</span>(url); <span class="comment">// the status of the promise until it hears back from the URL requested is pending</span></span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(req);</span><br><span class="line"> req.<span class="title function_">then</span>(<span class="function"><span class="params">resp</span> =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"Fulfilling"</span>);</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(resp.<span class="property">data</span>);</span><br><span class="line"> })</span><br><span class="line"> .<span class="title function_">catch</span>(<span class="function"><span class="params">err</span> =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"Rejected"</span>);</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h2 id="24-json"><a class="markdownIt-Anchor" href="#24-json"></a> 2.4. JSON</h2>
<p>JSON 是 API 数据交换的标准格式。</p>
<p>要将 JSON 字符串解析为 JavaScript 对象,可以使用方法 <code>JSON.parse()</code>。而要将 JavaScript 对象转换为 JSON 字符串,可以使用方法 <code>JSON.stringify()</code>。</p>
<h1 id="3-express网页开发框架"><a class="markdownIt-Anchor" href="#3-express网页开发框架"></a> 3. Express 网页开发框架</h1>
<p>默认的 Node.JS 框架为构建网页应用程序提供了一套有限的功能。</p>
<blockquote>
<p>例如,Node.JS 不提供 XML 消息的解析功能。在简单消息中,你可以使用 JavaScript 字符串函数来解析消息,也可以使用 XML 文档对象,但该对象解析 XML 数据流的效率并不高。</p>
</blockquote>
<p>开发人员往往依赖第三方软件包来扩展 Node.JS 功能。</p>
<p>你可以将网络服务信息解析为字符串:</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">response.<span class="title function_">on</span>(<span class="string">"data"</span>, <span class="keyword">function</span>(<span class="params">chunk</span>) {</span><br><span class="line"> buffer += chunk;</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">response.<span class="title function_">on</span>(<span class="string">"end"</span>, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="keyword">let</span> matches = buffer.<span class="title function_">match</span>(<span class="regexp">/\<temp_f\>.+\<\/temp_f\>/g</span>);</span><br><span class="line"> <span class="keyword">if</span> (<span class="literal">null</span> != matches || matches.<span class="property">length</span> > <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">let</span> result = matches[<span class="number">0</span>].<span class="title function_">replace</span>(<span class="regexp">/\<temp_f\>/</span>, <span class="string">""</span>).<span class="title function_">replace</span>(<span class="regexp">/\<\temp_f\>/</span>, <span class="string">""</span>);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> <span class="title function_">resultCallback</span>(<span class="literal">null</span>, result);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<p>这种手动解析的方法有着许多缺点:</p>
<ul>
<li>字符串匹配忽略了 XML 数据的结构</li>
<li>信息体可能包含了畸形的 XML 数据</li>
<li>根据 XML 数据的复杂程度,字符串匹配可能要比构建数据的 XML 树更有效率</li>
<li>字符串匹配对 XML 数据结构变化的容忍度很低</li>
<li>如果信息添加或删除了任何 XML 元素,那就必须更改字符串匹配函数的正则表达式</li>
</ul>
<p>xml2js 是一个流行的 Node.JS 软件包,它可以将 XML 数据解析为 JavaScript 对象。与其他 XML 解析包不同,xml2js 只使用 JavaScript 而不是其他语言。</p>
<blockquote>
<p>第三方软件包的软件许可可能与 Node.JS 框架不同。在安装软件包之前,请确认许可条款是否适用于你的公司和应用程序。</p>
</blockquote>
<figure class="highlight bash"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">npm install xml2js</span><br></pre></td></tr></tbody></table></figure>
<p>将软件包导入到 Node.JS 应用程序中:</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">let</span> parseString = <span class="built_in">require</span>(<span class="string">"xml2js"</span>).<span class="property">parseString</span>;</span><br><span class="line"><span class="built_in">exports</span>.<span class="property">current</span> = <span class="keyword">function</span>(<span class="params">location, resultCallback</span>) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="keyword">let</span> request = https.<span class="title function_">request</span>(options, <span class="keyword">function</span>(<span class="params">response</span>) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> response.<span class="title function_">on</span>(<span class="string">"end"</span>, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="title function_">parseString</span>(buffer, <span class="keyword">function</span>(<span class="params">error, result</span>) {</span><br><span class="line"> <span class="keyword">if</span> (error) { ... }</span><br><span class="line"> </span><br><span class="line"> <span class="comment">// the result JavaScript variable represents the contents of the XML fragment in buffer</span></span><br><span class="line"> <span class="title function_">resultCallback</span>(<span class="literal">null</span>, result.<span class="property">current_observation</span>.<span class="property">temp_f</span>[<span class="number">0</span>]);</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure>
<h2 id="31-网页框架"><a class="markdownIt-Anchor" href="#31-网页框架"></a> 3.1. 网页框架</h2>
<p>Node.JS 不是网页框架,而是在服务器上执行 JavaScript 的 runtime 环境。网页框架是支持网页应用程序的基本结构,因此要使用 Node.JS,就需要使用与之配合使用的网页框架。</p>
<p>与 Node.JS 协同工作的框架被称为 node 网页框架。它们可采用两种方法构建后端:</p>
<ol>
<li>MVC(模型 - 视图 - 控制器):将应用程序分解为三个部分,每个部分都有自己的职责</li>
<li>REST API</li>
</ol>
<h4 id="311-mvc"><a class="markdownIt-Anchor" href="#311-mvc"></a> 3.1.1. MVC</h4>
<p>MVC 是一种设计模式,用于将应用程序分解为三个部分:</p>
<ol>
<li>模型:负责处理数据</li>
<li>视图:负责渲染模型传递的数据</li>
<li>控制器:负责管理数据流、处理用户提供的数据,并将数据发送给模型</li>
</ol>
<p>MVC 框架一般用于开发需要将数据、数据的展示和操作数据的模块分开的应用程序。</p>
<p>MVC 模式的框架包括 Koa、Django、Express 和 NestJS。</p>
<h4 id="312-rest-api"><a class="markdownIt-Anchor" href="#312-rest-api"></a> 3.1.2. REST API</h4>
<p>REST API 允许多个网络服务相互通信。但这会受到一些限制:客户端的代码必须完全独立于服务器端的代码;客户端代码的更新不会干扰服务器端代码的运行,反之亦然。</p>
<p>REST API 是无状态的。这代表着客户端不需要知道服务器的状态,服务器也不需要知道客户端的状态。这种无状态的特性使得 REST API 非常适合用于构建分布式应用程序。</p>
<p>REST API 通过对资源的操作进行通信,不依赖于 API 的特定实现。当客户端使用 <code>GET</code>、<code>POST</code>、<code>PUT</code> 和 <code>DELETE</code> 等 HTTP 方法与服务器通信时,服务器便会向客户端响应资源状态。</p>
<h4 id="313-express"><a class="markdownIt-Anchor" href="#313-express"></a> 3.1.3. Express</h4>
<p>Express.JS 是最流行的 node 网页框架之一。它用于路由和中间件、使用 JavaScript 进行直接编程,意味着学习曲线很低。</p>
<p>Express.JS 提供调试机制,有助于轻松找出应用程序中的错误。它采用异步编程方式,同时处理多个相互独立的操作请求,因此性能很好。</p>
<h4 id="314-koa"><a class="markdownIt-Anchor" href="#314-koa"></a> 3.1.4. Koa</h4>
<p>Koa 是一个相对较新的网页框架,由设计 Express 的同一团队设计。它设计得更小巧、更具表现力,并为网页应用程序和 API 提供了更强大的基础。</p>
<p>Koa 使用异步函数,因此不需要回调,这提高了处理错误的能力。该框架适合由经验丰富的大型团队开发高性能、高要求、复杂的应用程序。</p>
<h4 id="315-socketio"><a class="markdownIt-Anchor" href="#315-socketio"></a> 3.1.5. <a target="_blank" rel="noopener" href="http://Socket.IO">Socket.IO</a></h4>
<p>Socket.IO 是开发在客户端和服务器之间实时交换双向数据的应用程序的绝佳选择。你可以开发利用 Websocket 而不是 HTTP 协议的应用程序。</p>
<p>它的服务器可以推送数据,而无需客户端调用数据,因此十分适用于聊天室、短信应用、视频会议和多人游戏等应用程序。</p>
<h4 id="316-hapijs"><a class="markdownIt-Anchor" href="#316-hapijs"></a> 3.1.6. Hapi.JS</h4>
<p>Hapi.JS 是一个可靠的开源节点网页框架,内置了大量安全功能。它的插件系统使得开发人员可以轻松地扩展应用程序的功能。</p>
<p>它最著名的用途是开发代理和 API 服务器、HTTP 代理用户程序、REST API 以及其他桌面和应用程序。</p>
<h4 id="317-nestjs"><a class="markdownIt-Anchor" href="#317-nestjs"></a> 3.1.7. NestJS</h4>
<p>NestJS 框架适合构建动态、可扩展的企业应用程序,其灵活性得益于大量的库。它采用了 MVC 架构。</p>
<p>NestJS 构建在 Express 的基础之上,因此它们具有相似的功能。</p>
<p>NestJS 与 TypeScript 兼容,还能与前端 Angular 框架结合使用。</p>
<blockquote>
<p>TypeScript 是一种 JavaScript 的超集,它添加了类型和其他功能,以帮助开发人员编写更好的代码。</p>
</blockquote>
<p>NestJS 结合了面向对象编程和函数式编程的优点,因此它的代码易于阅读和维护。</p>
<h2 id="32-express"><a class="markdownIt-Anchor" href="#32-express"></a> 3.2. Express</h2>
<p>Express 主要用于两个目的:</p>
<ol>
<li>API</li>
<li>使用服务端渲染(SSR)来设置模板</li>
</ol>
<p>Express API 设置了一个与应用程序数据层交互的 HTTP 接口。在 API 的情况下,数据会使用响应对象(简称 <code>res</code>)以 JSON 格式返回给客户端。</p>
<p><code>res.json()</code> 方法用于通知客户端发送数据的内容类型,如图像或文本。它还可用于对数据进行字符串化。</p>
<p>而在 SSR 中,Express 用于设置模板。Express 负责使用客户端通过 HTTP 请求的数据、结合模板动态编写 HTML、CSS 和 / 或 JavaScript。</p>
<h4 id="321-nodejs应用程序框架"><a class="markdownIt-Anchor" href="#321-nodejs应用程序框架"></a> 3.2.1. Node.JS 应用程序框架</h4>
<p>Express 实现了一个 <code>app</code> 类,你可以将其映射到网络资源路径。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br><span class="line"><span class="keyword">const</span> port = <span class="number">3000</span>;</span><br><span class="line"><span class="comment">// ...</span></span><br><span class="line"><span class="keyword">let</span> server = app.<span class="title function_">listen</span>(port, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Listening on URL http://localhost:<span class="subst">${port}</span>`</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<h4 id="322-express是如何工作的"><a class="markdownIt-Anchor" href="#322-express是如何工作的"></a> 3.2.2. Express 是如何工作的</h4>
<ol>
<li>在 Node.JS 项目的包文件中,将 Express 作为依赖项添加到 <code>dependencies</code> 属性中</li>
<li>运行 <code>npm</code> 命令来下载缺少的模块</li>
<li>导入 Express 模块并创建一个 Express 应用程序实例</li>
<li>创建一个新的路由处理程序</li>
<li>在指定端口号上启动 HTTP 服务器</li>
</ol>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// mynodeserver.js</span></span><br><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="title function_">express</span>();</span><br><span class="line"><span class="keyword">const</span> port = <span class="number">3000</span>;</span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">'/temperature/:location_code'</span>, <span class="keyword">function</span>(<span class="params">request, response</span>) {</span><br><span class="line"> <span class="keyword">const</span> varlocation = request.<span class="property">params</span>.<span class="property">location_code</span>;</span><br><span class="line"> weather.<span class="title function_">current</span>(location, <span class="keyword">function</span>(<span class="params">error, temp_f</span>) {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> });</span><br><span class="line">});</span><br><span class="line"><span class="keyword">let</span> server = app.<span class="title function_">listen</span>(port, <span class="keyword">function</span>(<span class="params"></span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Listening on URL http://localhost:<span class="subst">${port}</span>`</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<p>要处理网页应用程序请求,可将 HTTP 方法和网络资源路径映射到 JavaScript 函数。</p>
<h4 id="323-路由"><a class="markdownIt-Anchor" href="#323-路由"></a> 3.2.3. 路由</h4>
<p>路由是服务器端脚本的一个重要组成部分。对同一服务器的不同路由的请求必须由服务器处理。服务器必须处理对每个路由的请求,否则就会返回相应的错误信息。路由可在应用程序级或路由器级处理。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">"user/about/:id"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.<span class="title function_">send</span>(<span class="string">"Response about user "</span> + req.<span class="property">params</span>.<span class="property">id</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">post</span>(<span class="string">"user/about/:id"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.<span class="title function_">send</span>(<span class="string">"Response about user "</span> + req.<span class="property">params</span>.<span class="property">id</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">"item/about/:id"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.<span class="title function_">send</span>(<span class="string">"Response about user "</span> + req.<span class="property">params</span>.<span class="property">id</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">post</span>(<span class="string">"item/about/:id"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.<span class="title function_">send</span>(<span class="string">"Response about user "</span> + req.<span class="property">params</span>.<span class="property">id</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3333</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Listening at http://localhost:3333`</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<p>你需要在应用程序级别使用单独的方法来处理每个路由上的每个方法。当端点或路由较少时,这种方法很简单。但是,当端点或路由数量增加时,这种方法就会变得复杂。我们需要使用路由器来让我们的代码更加简洁、易于阅读和维护。</p>
<p>路由器本身用于处理分支查询,并以不同方法路由每个查询。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> userRouter = express.<span class="title class_">Router</span>();</span><br><span class="line"><span class="keyword">let</span> itemRouter = express.<span class="title class_">Router</span>();</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(<span class="string">"/item"</span>, itemRouter);</span><br><span class="line">app.<span class="title function_">use</span>(<span class="string">"/user"</span>, userRouter);</span><br><span class="line"></span><br><span class="line">userRouter.<span class="title function_">get</span>(<span class="string">"/about/:id"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.<span class="title function_">send</span>(<span class="string">"Response about user "</span> + req.<span class="property">params</span>.<span class="property">id</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">userRouter.<span class="title function_">get</span>(<span class="string">"/details/:id"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.<span class="title function_">send</span>(<span class="string">"Details about user "</span> + req.<span class="property">params</span>.<span class="property">id</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">itemRouter.<span class="title function_">get</span>(<span class="string">"/about/:id"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.<span class="title function_">send</span>(<span class="string">"Information about item "</span> + req.<span class="property">params</span>.<span class="property">id</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">itemRouter.<span class="title function_">get</span>(<span class="string">"/details/:id"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.<span class="title function_">send</span>(<span class="string">"Details about item "</span> + req.<span class="property">params</span>.<span class="property">id</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3333</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Listening at http://localhost:3333`</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<h4 id="324-中间件"><a class="markdownIt-Anchor" href="#324-中间件"></a> 3.2.4. 中间件</h4>
<p>中间件包括了可以访问请求和响应对象以及下一个函数的函数。下一个参数决定了函数执行后的操作。</p>
<p>一个 Express 应用程序可以有多个中间件,而且它们之间可以相互连接。</p>
<p>中间件根据目的、用途和功能分为不同的类型:</p>
<ol>
<li>应用程序级</li>
<li>路由器级</li>
<li>错误处理</li>
<li>内置</li>
<li>第三方</li>
</ol>
<p>应用程序级中间件可以使用 <code>app.use()</code> 方法绑定到应用程序上:</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(<span class="keyword">function</span>(<span class="params">req, res, next</span>) {</span><br><span class="line"> <span class="keyword">if</span> (req.<span class="property">query</span>.<span class="property">password</span> !== <span class="string">"pwd123"</span>) {</span><br><span class="line"> <span class="keyword">return</span> res.<span class="title function_">status</span>(<span class="number">402</span>).<span class="title function_">send</span>(<span class="string">"This user cannot login "</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"Time:"</span>, <span class="title class_">Date</span>.<span class="title function_">now</span>());</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">"/"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> res.<span class="title function_">send</span>(<span class="string">"Hello World!"</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3333</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Listening at http://localhost:3333`</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<p>客户端向服务器应用程序发出的所有请求都会通过该中间件进行路由。这种路由对验证和检查会话信息等操作很有用。</p>
<p>路由器级中间件不与应用程序绑定。相反,它与 <code>express.Router()</code> 实例绑定。你可以为特定路由使用特定的中间件,而不是让所有请求都通过同一个中间件:</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">let</span> userRouter = express.<span class="title class_">Router</span>();</span><br><span class="line"><span class="keyword">let</span> itemRouter = express.<span class="title class_">Router</span>();</span><br><span class="line"></span><br><span class="line">userRouter.<span class="title function_">use</span>(<span class="keyword">function</span>(<span class="params">req, res, next</span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"User query Time: "</span>, <span class="title class_">Date</span>());</span><br><span class="line"> <span class="title function_">next</span>();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">userRouter.<span class="title function_">get</span>(<span class="string">"/:id"</span>, <span class="keyword">function</span>(<span class="params">req, res, next</span>) {</span><br><span class="line"> res.<span class="title function_">send</span>(<span class="string">"User "</span> + req.<span class="property">params</span>.<span class="property">id</span> + <span class="string">" last successful login "</span> + <span class="title class_">Date</span>());</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">itemRouter.<span class="title function_">use</span>(<span class="keyword">function</span>(<span class="params">req, res, next</span>) {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"Item query Time: "</span>, <span class="title class_">Date</span>());</span><br><span class="line"> <span class="title function_">next</span>();</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">itemRouter.<span class="title function_">get</span>(<span class="string">"/:id"</span>, <span class="keyword">function</span>(<span class="params">req, res, next</span>) {</span><br><span class="line"> res.<span class="title function_">send</span>(<span class="string">"Item "</span> + req.<span class="property">params</span>.<span class="property">id</span> + <span class="string">" last enquiry "</span> + <span class="title class_">Date</span>());</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(<span class="string">"/user"</span>, userRouter);</span><br><span class="line">app.<span class="title function_">use</span>(<span class="string">"/item"</span>, itemRouter);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3333</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Listening at http://localhost:3333`</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<ul>
<li>响应将根据客户端的请求路径而不同</li>
</ul>
<p>错误处理中间件既可以绑定到整个应用程序,也可以绑定到特定路由器:</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(<span class="string">"/user/:id"</span>, <span class="keyword">function</span>(<span class="params">req, res, next</span>) {</span><br><span class="line"> <span class="keyword">if</span> (req.<span class="property">params</span>.<span class="property">id</span> == <span class="number">1</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="title class_">Error</span>(<span class="string">"Trying to access admin login"</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="title function_">next</span>();</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(<span class="keyword">function</span>(<span class="params">err, req, res, next</span>) {</span><br><span class="line"> <span class="keyword">if</span> (err != <span class="literal">null</span>) {</span><br><span class="line"> res.<span class="title function_">status</span>(<span class="number">500</span>).<span class="title function_">send</span>(err.<span class="title function_">toString</span>());</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="title function_">next</span>();</span><br><span class="line"> }</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">"/user/:id"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> <span class="keyword">return</span> res.<span class="title function_">send</span>(<span class="string">"Hello! User Id "</span>, req.<span class="property">params</span>.<span class="property">id</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3333</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Listening at http://localhost:3333`</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<p>错误处理中间件总是需要四个参数:<code>error</code>、<code>request</code>、<code>response</code> 和 <code>next()</code>。不过,你可以省略 <code>next()</code> 参数;即使省略了,也可以在方法中定义。</p>
<p>内置中间件可以绑定到整个应用程序或者特定路由器上。内置中间件对于从服务器渲染 HTML、解析来自前端的 JSON 输入和解析 cookie 等操作很有用。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// define the static files that can be rendered from the cad220_staticfiles directory</span></span><br><span class="line">app.<span class="title function_">use</span>(express.<span class="title function_">static</span>(<span class="string">"cad220_staticfiles"</span>));</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3333</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Listening at http://localhost:3333`</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<p>你也可以定义自己的中间件或使用第三方中间件,这些中间件可以通过 <code>npm install</code> 命令安装。</p>
<p>创建中间件很简单。你可以定义一个包含三个参数的函数,然后将其与 <code>app.use()</code> 或者 <code>router.use()</code> 绑定。中间件的顺序取决于 <code>.use()</code> 方法用于绑定中间件的顺序。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line"><span class="keyword">function</span> <span class="title function_">myLogger</span>(<span class="params">req, res, next</span>) {</span><br><span class="line"> req.<span class="property">timeReceived</span> = <span class="title class_">Date</span>();</span><br><span class="line"> <span class="title function_">next</span>();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">use</span>(myLogger);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">"/"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.<span class="title function_">send</span>(<span class="string">"Request received at "</span> + req.<span class="property">timeReceived</span> + <span class="string">" is a success!"</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3333</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Listening at http://localhost:3333`</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<h4 id="325-模板渲染"><a class="markdownIt-Anchor" href="#325-模板渲染"></a> 3.2.5. 模板渲染</h4>
<p>模板渲染是服务器在 HTML 模板中填充动态内容的能力。</p>
<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">const</span> app = <span class="keyword">new</span> <span class="title function_">express</span>();</span><br><span class="line"><span class="keyword">const</span> expressReactViews = <span class="built_in">require</span>(<span class="string">"express-react-views"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> jsxEngine = expressReactViews.<span class="title function_">createEngine</span>();</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">set</span>(<span class="string">"view engine"</span>, <span class="string">'jsx'</span>); <span class="comment">// views are JSX code</span></span><br><span class="line"></span><br><span class="line">app.<span class="title function_">set</span>(<span class="string">"views"</span>, <span class="string">"myviews"</span>); <span class="comment">// the views are in a directory named myviews</span></span><br><span class="line"></span><br><span class="line">app.<span class="title function_">engine</span>(<span class="string">"jsx"</span>, jsxEngine);</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">get</span>(<span class="string">"/:name"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> res.<span class="title function_">render</span>(<span class="string">"index"</span>, {<span class="attr">name</span>: req.<span class="property">params</span>.<span class="property">name</span>});</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">app.<span class="title function_">listen</span>(<span class="number">3333</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">`Listening at http://localhost:3333`</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
<p>本代码示例使用了 express-React-views 软件包,它是一个用于渲染 React 视图的 Express 模板引擎。</p>
<h2 id="33-验证"><a class="markdownIt-Anchor" href="#33-验证"></a> 3.3. 验证</h2>
<p>身份验证是通过获取凭证并使用这些凭证验证用户身份的过程。身份验证的目的是识别用户身份,并根据其身份提供访问权限和内容。</p>
<p>身份验证可以通过以下方法实现:</p>
<ul>
<li>基于会话</li>
<li>基于令牌</li>
<li>无密码</li>
</ul>
<h4 id="331-基于令牌的身份验证"><a class="markdownIt-Anchor" href="#331-基于令牌的身份验证"></a> 3.3.1. 基于令牌的身份验证</h4>
<p>基于令牌的身份验证是在 Node.JS 中实施身份验证的最常见方法。由于令牌只需存储在客户端,所以基于令牌的身份验证更具有可扩展性;服务器只需要验证令牌和用户信息,因此更容易处理多个用户;其灵活性能够在多个服务器上实现身份验证。基于令牌的身份验证中使用的 JWT 可以签名和加密,这意味着它们不会被篡改、没有私人加密密钥便无法读取。</p>
<p>让我们建立一个 Express.JS API 服务器、根据使用权限访问员工信息。应用程序将有两个 API,每个 API 都有自己的端点:</p>
<ol>
<li>使用 <code>POST</code> API 登录,通过在请求体中发送用户名和密码、返回网页令牌(对该应用程序接口端点的调用应当通过托管应用程序前端的网页服务器来进行)</li>
<li><code>GET</code> API 将获取只有通过身份验证的用户才能访问的员工信息:<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">const</span> myapp = <span class="title function_">express</span>();</span><br><span class="line"></span><br><span class="line"><span class="comment">// creates a web server module by calling the express() function and assigning it to the constant myapp</span></span><br><span class="line"><span class="comment">// the myapp.get() function creates a GET API endpoint for the Employees API, and any call to this endpoint currently returns an HTTP status code of 401</span></span><br><span class="line"><span class="comment">// 401 means Not Authorized</span></span><br><span class="line">myapp.<span class="title function_">get</span>(<span class="string">"/employees"</span>. (req, res) => {</span><br><span class="line"> <span class="keyword">return</span> res.<span class="title function_">status</span>(<span class="number">401</span>).<span class="title function_">json</span>({<span class="attr">message</span>: <span class="string">"Please login to access this resource"</span>});</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">myapp.<span class="title function_">listen</span>(<span class="number">5000</span>, <span class="function">() =></span> {</span><br><span class="line"> <span class="variable language_">console</span>.<span class="title function_">log</span>(<span class="string">"API Server is localhost:5000"</span>);</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
在代码的下一部分,只要用户名和密码正确,我们就允许用户登录,并返回经过验证生成的令牌。一般来说,用户名和密码都存储在数据库中。但是,为了简单起见,我们将在代码中使用「user」和「password」作为用户名和密码。<br>
要生成经过验证的 JWT(JSON Web Token),要使用 jsonwebtoken 包:<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> express = <span class="built_in">require</span>(<span class="string">"express"</span>);</span><br><span class="line"><span class="keyword">const</span> jsonwebtoken = <span class="built_in">require</span>(<span class="string">"jsonwebtoken"</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> <span class="variable constant_">JWT_SECRET</span> = <span class="string">"aVeryVerySecretString"</span>;</span><br></pre></td></tr></tbody></table></figure>
通过 <code>myapp.use()</code> 方法,API 方法可以返回 JSON 响应:<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">const</span> myapp = <span class="title function_">express</span>();</span><br><span class="line">myapp.<span class="title function_">use</span>(express.<span class="title function_">json</span>());</span><br><span class="line"></span><br><span class="line">myapp.<span class="title function_">post</span>(<span class="string">"/signin"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> <span class="keyword">const</span> {uname, pwd} = req.<span class="property">body</span>;</span><br><span class="line"> <span class="comment">// however, that the JWT Secret should always be generated using a password generator and stored in the config file as an environment variable and not hard coded in the API, as shown here</span></span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
然后将请求正文中的用户名和密码与从数据库中获取的值进行比较:<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">if</span> (uname === <span class="string">"user"</span> && pwd === <span class="string">"password"</span>) {</span><br><span class="line"> <span class="keyword">return</span> res.<span class="title function_">json</span>({</span><br><span class="line"> <span class="comment">// Once the username and password match, the JWT is generated using the jsonwebstoken.sign() function by including the username and the JWT secret as parameters and is returned as a JSON response from the signin API</span></span><br><span class="line"> <span class="attr">token</span>: jsonwebtoken.<span class="title function_">sign</span>({<span class="attr">user</span>: <span class="string">"user"</span>}, <span class="variable constant_">JWT_SECRET</span>)</span><br><span class="line"> });</span><br><span class="line"> <span class="comment">// if the username and password match fails, then a HTTP status code of 401 is returned with the message "Invalid username and/or password"</span></span><br><span class="line"> <span class="keyword">return</span> res.<span class="title function_">status</span>(<span class="number">401</span>).<span class="title function_">json</span>({<span class="attr">message</span>: <span class="string">"Invalid username and/or password"</span>});</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
接着,我们用「employees」端点定义 <code>GET</code> API 方法:<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">myapp.<span class="title function_">get</span>(<span class="string">"/employees"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> <span class="keyword">let</span> tkn = req.<span class="title function_">header</span>(<span class="string">"Authorization"</span>);</span><br><span class="line"> <span class="keyword">if</span> (!tkn) <span class="keyword">return</span> res.<span class="title function_">status</span>(<span class="number">401</span>).<span class="title function_">send</span>(<span class="string">"No Token"</span>);</span><br><span class="line"> <span class="keyword">if</span> (tkn.<span class="title function_">startsWith</span>(<span class="string">"Bearer "</span>)) {</span><br><span class="line"> tokenValue = tkn.<span class="title function_">slice</span>(<span class="number">7</span>, tkn.<span class="property">length</span>).<span class="title function_">trimLeft</span>();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
从 <code>signin</code> API 调用中获取的令牌会在 <code>Authorization</code> 标头中传递。<code>GET</code> API(也就是「employees」)已更新,可以使用 <code>req.header()</code> 函数从传入的 API 请求中读取 <code>Authorization</code> 标头。值得注意的是,<code>Authorization</code> 标头的值总是以 <code>Bearer</code> 开头,因此这个令牌也被称为 Bearer 令牌。<br>
获取的 JWT 可通过传递获取的令牌和 JWT 密钥、使用函数 <code>jsonwebtoken.verify()</code> 进行验证:<figure class="highlight js"><table><tbody><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">myapp.<span class="title function_">get</span>(<span class="string">"/employees"</span>, <span class="function">(<span class="params">req, res</span>) =></span> {</span><br><span class="line"> <span class="comment">// ...</span></span><br><span class="line"> <span class="keyword">const</span> verificationStatus = jsonwebtoken.<span class="title function_">verify</span>(tokenValue, <span class="string">"aVeryVerySecretString"</span>);</span><br><span class="line"> <span class="keyword">if</span> (verificationStatus.<span class="property">user</span> === <span class="string">"user"</span>) {</span><br><span class="line"> <span class="keyword">return</span> res.<span class="title function_">status</span>(<span class="number">200</span>).<span class="title function_">json</span>({<span class="attr">message</span>: <span class="string">"Access Successful to Employee Endpoint"</span>});</span><br><span class="line"> }</span><br><span class="line">});</span><br></pre></td></tr></tbody></table></figure>
如果验证失败,则会将 401 状态码返回给客户端。</li>
</ol>
</body></html></div></article></div></main><footer><div class="paginator"><a class="prev" href="948f.html">上一篇</a><a class="next" href="d011.html">下一篇</a></div><!-- Webmention 显示区域--><div class="webmention-section webmention-empty" data-page-url="posts/fed2.html" data-full-url="https://cytrogen.icu/posts/fed2.html" data-mode="static">
<h3 class="webmention-title">Webmentions (<span class="webmention-count">0</span>)</h3>
<div class="webmention-list"></div>
<span>暂无 Webmentions</span>
</div><div class="copyright"><p class="footer-links"><a href="../friends/index.html">友链</a><span class="footer-separator"> ·</span><a href="../links/index.html">邻邦</a><span class="footer-separator"> ·</span><a href="../contact/index.html">联络</a><span class="footer-separator"> ·</span><a href="../colophon/index.html">营造记</a><span class="footer-separator"> ·</span><a href="../atom.xml">RSS订阅</a></p><p>© 2025 - 2026 <a href="https://cytrogen.icu">Cytrogen</a>, powered by <a href="https://hexo.io/" target="_blank">Hexo</a> and <a href="https://github.com/cytrogen/hexo-theme-ares" target="_blank">hexo-theme-ares</a>.</p><p><a href="https://blogscn.fun" target="_blank" rel="noopener">BLOGS·CN</a></p></div></footer></div></div><a class="back-to-top" href="#top" aria-label="返回顶部"><svg width="20" height="20" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true"><path d="M3.293 9.707a1 1 0 010-1.414L9.586 2a2 2 0 012.828 0l6.293 6.293a1 1 0 01-1.414 1.414L11 3.414V17a1 1 0 11-2 0V3.414L2.707 9.707a1 1 0 01-1.414 0z"></path></svg></a><script>document.addEventListener('DOMContentLoaded', function() {
const codeBlocks = document.querySelectorAll('figure.highlight');
codeBlocks.forEach(block => {
let caption = block.querySelector('figcaption');
if (!caption) {
caption = document.createElement('figcaption');
block.insertBefore(caption, block.firstChild);
}
const info = document.createElement('div');
info.className = 'info';
const filename = caption.querySelector('span');
if (filename) {
filename.className = 'filename';
info.appendChild(filename);
}
const lang = block.className.split(' ')[1];
if (lang) {
const langSpan = document.createElement('span');
langSpan.className = 'lang-name';
langSpan.textContent = lang;
info.appendChild(langSpan);
}
const sourceLink = caption.querySelector('a');
if (sourceLink) {
sourceLink.className = 'source-link';
info.appendChild(sourceLink);
}
const actions = document.createElement('div');
actions.className = 'actions';
const codeHeight = block.scrollHeight;
const threshold = 300;
if (codeHeight > threshold) {
block.classList.add('folded');
const toggleBtn = document.createElement('button');
toggleBtn.textContent = '展开';
toggleBtn.addEventListener('click', () => {
block.classList.toggle('folded');
toggleBtn.textContent = block.classList.contains('folded') ? '展开' : '折叠';
});
actions.appendChild(toggleBtn);
}
const copyBtn = document.createElement('button');
copyBtn.textContent = '复制';
copyBtn.addEventListener('click', async () => {
const codeLines = block.querySelectorAll('.code .line');
const code = Array.from(codeLines)
.map(line => line.textContent)
.join('\n')
.replace(/\n\n/g, '\n');
try {
await navigator.clipboard.writeText(code);
copyBtn.textContent = '已复制';
copyBtn.classList.add('copied');
setTimeout(() => {
copyBtn.textContent = '复制';
copyBtn.classList.remove('copied');
}, 3000);
} catch (err) {
console.error('复制失败:', err);
copyBtn.textContent = '复制失败';
setTimeout(() => {
copyBtn.textContent = '复制';
}, 3000);
}
});
actions.appendChild(copyBtn);
caption.innerHTML = '';
caption.appendChild(info);
caption.appendChild(actions);
const markedLines = block.getAttribute('data-marked-lines');
if (markedLines) {
const lines = markedLines.split(',');
lines.forEach(range => {
if (range.includes('-')) {
const [start, end] = range.split('-').map(Number);
for (let i = start; i <= end; i++) {
const line = block.querySelector(`.line-${i}`);
if (line) line.classList.add('marked');
}
} else {
const line = block.querySelector(`.line-${range}`);
if (line) line.classList.add('marked');
}
});
}
});
});</script><script async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js" id="MathJax-script"></script><script>(function() {
document.addEventListener('DOMContentLoaded', function() {
const themeToggle = document.querySelector('.theme-toggle');
if (!themeToggle) return;
const getCurrentTheme = () => {
return document.documentElement.getAttribute('data-theme') || 'light';
};
const updateUI = (theme) => {
const isDark = theme === 'dark';
themeToggle.setAttribute('aria-pressed', isDark.toString());
};
const setTheme = (theme) => {
document.documentElement.setAttribute('data-theme', theme);
document.documentElement.style.colorScheme = theme;
const pageWrapper = document.getElementById('page-wrapper');
if (pageWrapper) {
pageWrapper.setAttribute('data-theme', theme);
}
// Find and remove the temporary anti-flicker style tag if it exists.
// This ensures the main stylesheet takes full control after the initial load.
const antiFlickerStyle = document.getElementById('anti-flicker-style');
if (antiFlickerStyle) {
antiFlickerStyle.remove();
}
localStorage.setItem('theme', theme);
updateUI(theme);
};
const toggleTheme = () => {
const current = getCurrentTheme();
const newTheme = current === 'light' ? 'dark' : 'light';
setTheme(newTheme);
};
updateUI(getCurrentTheme());
themeToggle.addEventListener('click', toggleTheme);
if (window.matchMedia) {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', function(e) {
if (!localStorage.getItem('theme')) {
const theme = e.matches ? 'dark' : 'light';
setTheme(theme);
}
});
}
});
})();
</script><script src="../js/details-toggle.js" defer></script><script>(function() {
document.addEventListener('DOMContentLoaded', function() {
const backToTopBtn = document.querySelector('.back-to-top');
if (!backToTopBtn) return;
const toggleButtonVisibility = () => {
const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
const shouldShow = scrollTop > 200;
if (shouldShow) {
backToTopBtn.classList.add('is-visible');
} else {
backToTopBtn.classList.remove('is-visible');
}
};
let ticking = false;
const handleScroll = () => {
if (!ticking) {
requestAnimationFrame(() => {
toggleButtonVisibility();
ticking = false;
});
ticking = true;
}
};
const scrollToTop = (event) => {
event.preventDefault();
window.scrollTo({
top: 0,
behavior: 'smooth'
});
};
window.addEventListener('scroll', handleScroll);
backToTopBtn.addEventListener('click', scrollToTop);
toggleButtonVisibility();
});
})();</script></body></html>