aboutsummaryrefslogtreecommitdiff
path: root/files/zh-cn/learn/server-side/express_nodejs/forms/index.html
blob: f3cee84344b1c64cc809fd3265e530a87e4d0e7f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
---
title: 'Express 教程 6: 使用表单'
slug: learn/Server-side/Express_Nodejs/forms
translation_of: Learn/Server-side/Express_Nodejs/forms
---
<div>{{LearnSidebar}}</div>

<div>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}</div>

<p class="summary">在此教程中我们会教你如何使用Express并且结合Pug来实现HTML表单,并且如何从数据库中创建,更新和删除文档。</p>

<table class="learn-box standard-table">
 <tbody>
  <tr>
   <th scope="row">前提条件:</th>
   <td>
    <p>完成前面所有的教程,包括 <a href="/en-US/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程第5章: 展示图书馆数据</a></p>
   </td>
  </tr>
  <tr>
   <th scope="row">目标:</th>
   <td>
    <p>了解如何编写表单获取用户信息,并且将这些数据更新到数据库中。</p>
   </td>
  </tr>
 </tbody>
</table>

<h2 id="概览">概览</h2>

<p><a href="/en-US/docs/Web/Guide/HTML/Forms">HTML表单</a>在网页中是一个或多个字段/小工具的集合,它被用来收集用户的信息,并将信息上传到服务器。表单作为一种用来收集用户的机制,非常的灵活,因为有各种合适的输入框,来接受各种类型的数据——文本框,复选框,单选按钮,时间选择器等。表单和服务器交互数据也相对安全,因为它使用POST请求发送数据,保护不受跨站点请求伪造攻击(cross-site request forgery)的威胁。</p>

<p>但是表单同样也很复杂!开发者需要为表单编写 HTML,在服务器上验证,并且正确去除有害的数据(浏览器上也可能需要),对于任何不合法的字段,需要传给用户相应的错误信息,当数据成功提交后,处理数据,并设法通知用户提交成功。</p>

<p>此教程将展示上述的操作,如何在 <em>Express </em>中实现。在此过程中,我们将扩展 LocalLibrary 网站,以允许用户创建,编辑和删除图书馆中的项目。</p>

<div class="note">
<p><strong>注意:</strong> 我们还没有考虑如何将特定路由,限制为经过身份验证或授权的用户,因此在这个时间点,任何用户都可以对数据库进行更改。</p>
</div>

<h3 id="HTML表单">HTML表单</h3>

<p>首先简要概述<a href="/zh-CN/docs/Web/Guide/HTML/Forms">HTML表单</a>。考虑一个简单的HTML表单,其中包含一个文本字段,用于输入某些 “团队” 的名称,及其相关标签:</p>

<p><img alt="Simple name field example in HTML form" src="https://mdn.mozillademos.org/files/14117/form_example_name_field.png" style="border-style: solid; border-width: 1px; display: block; height: 44px; margin: 0px auto; width: 399px;"></p>

<p>表单在HTML中,定义为<code>&lt;form&gt;...&lt;/form&gt;</code>标记内的元素集合,包含至少一个<code>type="submit"</code><code>input</code>输入元素。</p>

<p>请注意,我非常建议您这里使用input的submit而不是button!这会使你感到愉悦。</p>

<pre class="brush: html notranslate">&lt;form action="/team_name_url/" method="post"&gt;
    &lt;label for="team_name"&gt;Enter name: &lt;/label&gt;
    &lt;input id="team_name" type="text" name="name_field" value="Default name for team."&gt;
    &lt;input type="submit" value="OK"&gt;
&lt;/form&gt;</pre>

<p>虽然这里,我们只包含一个(文本)字段,用于输入团队名称,但表单可能包含任意数量的其他输入元素,及其相关标签。字段的 <code>type </code>属性,定义将显示哪种窗口小部件。该字段的名称<code>name</code><code>id</code> ,用于标识JavaScript/CSS/HTML 中的字段,而 <code>value</code>定义字段首次显示时的初始值。匹配团队标签使用<code style="font-style: normal; font-weight: normal;">label</code>标签,指定(请参阅上面的“输入名称” "Enter name"),其中 <code style="font-style: normal; font-weight: normal;">for </code>字段,包含<code style="font-style: normal; font-weight: normal;">input</code>相关输入的<code style="font-style: normal; font-weight: normal;">id</code>值。</p>

<p>另外,有必要说一下,HTML中form表单默认就是以post提交的。它比get方式存储量更大、传输更安全。</p>

<p>提交输入(<code>submit</code>)将显示为按钮(默认情况下) - 用户可以按此按钮,将其他输入元素包含的数据,上传到服务器(在本例中,只有<code>team_name</code>)。表单属性,定义用于发送数据的HTTP<code> method</code>方法,和服务器上数据的目标(<code>action</code>):</p>

<ul>
 <li><code>action</code>: 提交表单时,要发送数据以进行处理的资源/ URL。如果未设置(或设置为空字符串),则表单将提交回当前页面URL。</li>
 <li><code>method</code>: 用于发送数据的HTTP方法:<code>POST</code><code>GET</code><ul>
   <li>如果数据将导致服务器数据库的更改,则始终应该使用<code>POST</code>方法,因为这更加可以抵抗跨站点伪造请求攻击。</li>
   <li><code>GET</code>方法只应用于不更改用户数据的表单(例如,搜索表单)。当您希望能够为URL添加书签或共享时,建议使用此选项。</li>
  </ul>
 </li>
</ul>

<h3 id="表单处理流程">表单处理流程</h3>

<p>表单处理使用的技术,与我们学习过、用来显示有关模型的信息的所有技术,是相同的:路由将我们的请求发送到控制器函数,该函数执行所需的任何数据库操作,包括从模型中读取数据,然后生成并返回HTML页面。使事情变得更复杂的是,服务器还需要能够处理用户提供的数据,并在出现任何问题时,重新显示带有错误信息的表单。</p>

<p>下面显示了处理表单请求的流程图,从包含表单的页面请求开始(以绿色显示):</p>

<p><img alt="" src="https://mdn.mozillademos.org/files/14478/Web%20server%20form%20handling.png" style="height: 649px; width: 800px;"></p>

<p>如上图所示,构成处理代码所需要做的主要是:</p>

<ol>
 <li>在用户第一次请求时显示默认表单。
  <ul>
   <li>表单可能包含空白字段(例如,如果您正在创建新记录),或者可能预先填充了初始值(例如,如果您要更改记录,或者具有有用的默认初始值)。</li>
  </ul>
 </li>
 <li>接收用户提交的数据,通常是在HTTP <code>POST</code>请求中。</li>
 <li>验证并清理数据。</li>
 <li>如果任何数据无效,请重新显示表单 - 这次使用用户填写的任何值,和问题字段的错误消息。</li>
 <li>如果所有数据都有效,请执行所需的操作(例如,将数据保存在数据库中,发送通知电子邮件,返回搜索结果,上传文件等)</li>
 <li>完成所有操作后,将用户重定向到另一个页面。</li>
</ol>

<p>表格处理代码,通常使用<code>GET</code>路由,以实现表单的初始显示,以及<code>POST</code>路由到同一路径,以处理表单数据的验证和处理。这是将在本教程中使用的方法!Express本身不提供表单处理操作的任何特定支持,但它可以使用中间件,以处理表单中的<code>POST</code><code>GET</code>参数,并验证/清理它们的值。</p>

<h3 id="验证和清理">验证和清理</h3>

<p>在储存表单中的数据之前,必须对其进行验证和清理:</p>

<ul>
 <li>验证检查输入的值,适用于每个字段(范围,格式等),并且已为所有必填字段提供了值。</li>
 <li>清理删除/替换数据中的字符,可能用于将恶意内容发送到服务器。</li>
</ul>

<p>在本教程中,我们将使用流行的 <a href="https://www.npmjs.com/package/express-validator">express-validator</a> 模块,来执行表单数据的验证和清理。</p>

<h4 id="安装">安装</h4>

<p>通过在项目的根目录中,运行以下命令来安装模块。</p>

<pre class="brush: bash notranslate">npm install express-validator --save
</pre>

<h4 id="使用_express-validator">使用 express-validator</h4>

<div class="note">
<p><strong>注意:</strong> Github上的<a href="https://github.com/ctavan/express-validator#express-validator">express-validator</a>指南,提供了API的良好概述。我们建议您阅读该内容,以了解其所有功能(包括创建自定义验证程序)。下面我们只介绍一个对LocalLibrary有用的子集。</p>
</div>

<p>要在我们的控制器中使用验证器,我们必须从'<strong>express-validator/check</strong>'和'<strong>express-validator/filter</strong>'模块中,导入我们想要使用的函数,如下所示:</p>

<pre class="brush: js notranslate">const { body,validationResult } = require('express-validator/check');
const { sanitizeBody } = require('express-validator/filter');
</pre>

<p>有许多可用的功能,允许您一次检查和清理请求参数,正文,标头,cookie 等数据,或所有数据。对于本教程,我们主要使用<code>body</code><code>sanitizeBody</code>,和 <code>validationResult</code>(如上面 “导入”的 )。</p>

<p>功能定义如下:</p>

<ul>
 <li><code><a href="https://github.com/ctavan/express-validator#bodyfields-message">body(fields[, message])</a></code>: 指定请求本文中的一组字段(<code>POST</code>参数)以及可选的错误消息,如果测试失败,则可以显示该字段。验证标准以菊花链形式连接到 <code>body()</code>方法。例如,下面的第一个检查测试,“name”字段不为空,如果不是,则设置错误消息“Empty name”。第二个测试,检查age字段是否为有效日期,并使用<code>optional()</code>指定null和空字符串不会验证失败。

  <pre class="brush: js notranslate">body('name', 'Empty name').isLength({ min: 1 }),
body('age', 'Invalid age').optional({ checkFalsy: true }).isISO8601(),
</pre>
  您还可以用菊花链式连接不同的验证器,并添加前面验证器为真时显示的消息。</li>
 <li>
  <pre class="brush: js notranslate">body('name').isLength({ min: 1 }).trim().withMessage('Name empty.')
    .isAlpha().withMessage('Name must be alphabet letters.'),
</pre>

  <div class="note">
  <p><strong>注意:</strong> 您还可以添加内联清理器,如<code>trim()</code>,如上所示。但是,此处应用清理器,仅适用于验证步骤。如果要对最终输出进行消毒,则需要使用单独的清理器方法,如下所示。</p>
  </div>
 </li>
 <li><code><a href="https://github.com/ctavan/express-validator#sanitizebodyfields">sanitizeBody(fields)</a></code>: 指定一个正文要清理的字段。然后将清理操作,以菊花链形式连接到此方法。例如,下面的<code>escape()</code>清理操作,会从名称变量中,删除可能在JavaScript跨站点脚本攻击中使用的HTML字符。
  <pre class="brush: js notranslate">sanitizeBody('name').trim().escape(),
sanitizeBody('date').toDate(),</pre>
 </li>
 <li><code><a href="https://github.com/ctavan/express-validator#validationresultreq">validationResult(req)</a></code>: 运行验证,以<code>validation</code>验证结果对象的形式,提供错误。这是在单独的回调中调用的,如下所示:
  <pre class="brush: js notranslate">(req, res, next) =&gt; {
    // Extract the validation errors from a request.
    const errors = validationResult(req);

    if (!errors.isEmpty()) {
        // There are errors. Render form again with sanitized values/errors messages.
        // Error messages can be returned in an array using `errors.array()`.
        }
    else {
        // Data from form is valid.
    }
}</pre>
  我们使用验证结果的<code>isEmpty()</code>方法,来检查是否存在错误,并使用其<code>array()</code>方法,来获取错误消息集合。有关更多信息,请参<a href="https://github.com/ctavan/express-validator#validation-result-api">阅验证结果API</a></li>
</ul>

<p>验证和清理链,是应该传递给Express路由处理程序的中间件(我们通过控制器,间接地执行此操作)。中间件运行时,每个验证器/清理程序都按指定的顺序运行。<br>
 当我们实现下面的LocalLibrary表单时,我们将介绍一些真实的例子。</p>

<h3 id="表单设计">表单设计</h3>

<p>图书馆中的许多模型都是相关/依赖的 - 例如,一本书需要一个作者,也可能有一个或多个种类。这提出了一个问题,即我们应该如何处理用户希望的情况:</p>

<ul>
 <li>在其相关对象尚不存在时,创建对象(例如,尚未定义作者对象的书)。</li>
 <li>删除另一个对象仍在使用的对象(例如,删除仍有书本使用的种类)。</li>
</ul>

<p>在这个项目,我们将简单声明表单只能:</p>

<ul>
 <li>使用已存在的对象创建对象(因此用户在尝试创建任何<code>Book</code>对象之前,必须创建任何所需的<code>Author</code><code>Genre</code>实例)。</li>
 <li>如果对象未被其他对象引用,则删除该对象(例如,在删除所有关联的<code>BookInstance</code>对象之前,您将无法删除该书)。</li>
</ul>

<p>让我们看看更高级的内容吧:</p>

<p>我们通常会在“后台”接收form表单提交的数据。显而易见,这里应该是express!</p>

<p>首先我们可以知道(也许你会知道)应该先引入express:</p>

<p><code>const app=express();</code> </p>

<p>这很好。</p>

<p>那么既然是post提交,给大家推荐一款中间件:body-parser。它能让你轻松地处理body数据。</p>

<p>哦,如果你涉及文件上传,那么你可能需要“<a href="https://blog.csdn.net/qq_43624878/article/details/103607944">multer</a>”中间件,你大概听说过“formidable”,但multer比它更强大!</p>

<div class="note">
<p><strong>注意:</strong> 更“牢固”的实现,可能允许您在创建新对象时创建依赖对象,并随时删除任何对象(例如,通过删除依赖对象,或从数据库中,删除对已删除对象的引用) 。</p>
</div>

<h3 id="路由">路由</h3>

<p>为了实现我们的表单处理代码,我们需要两个具有相同URL模式的路由。</p>

<p>第一个(<code>GET</code>)路由,用于显示用于创建对象的新空表单。第二个路由(<code>POST</code>),用于验证用户输入的数据,然后保存信息,并重定向到详细信息页面(如果数据有效),或重新显示有错误的表单(如果数据无效)。</p>

<p>我们已经在 <strong>/routes/catalog.js</strong>(在<a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">之前的教程</a>中)为我们所有模型的创建页面,创建了路径。例如,种类路由如下所示:</p>

<pre class="brush: js notranslate">// GET request for creating a Genre. NOTE This must come before route that displays Genre (uses id).
router.get('/genre/create', genre_controller.genre_create_get);

// POST request for creating Genre.
router.post('/genre/create', genre_controller.genre_create_post);
</pre>

<h2 id="Express_表单子文件">Express 表单子文件</h2>

<p>以下子文件,将带我们完成向示例应用程序添加所需表单的过程。在进入下一个文件之前,您需要依次阅读并解决每个问题。</p>

<ol>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_genre_form">创建种类表单</a> — 定义我们的页面以创建<code>Genre</code>种类对象。</li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_author_form">创建作者表单</a> — 定义用于创建作者对象的页面。</li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_book_form">创建书本表单</a> — 定义页面/表单以创建书本对象。</li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Create_BookInstance_form">创建书本实例表单</a> — 定义页面/表单以创建书本实例对象。</li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Delete_author_form">删除作者表单</a> — 定义要删除作者对象的页面。</li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms/Update_Book_form">更新书本表单</a> — 定义页面以更新书本对象。</li>
</ol>

<h2 id="挑战自我">挑战自我</h2>

<p>实现<code>Book</code>, <code>BookInstance</code>, 和 <code>Genre</code>模型的删除页面,以与我们的作者删除页面相同的方式,将它们与关联的详细信息页面,链接起来。页面应遵循相同的设计方法:</p>

<ul>
 <li>如果有来自其他对象的、对于对象的引用,则应显示注释,列出这些对象,并说明在删除列出的对象之前,无法删除此记录。</li>
 <li>如果没有对该对象的其他引用,则视图应提示删除它。如果用户按下“删除”按钮,则应删除该记录。</li>
</ul>

<p>一些提示:</p>

<ul>
 <li>删除种类<code>Genre</code>就像删除作者<code>Author</code>一样,因为两个对象都是<code>Book</code>的依赖项(因此在这两种情况下,只有在删除相关书本时,才能删除对象)。</li>
 <li>删除书本<code>Book</code>也很相似,但您需要检查是否没有关联的书本实例<code>BookInstances</code></li>
 <li>删除书本实例<code>BookInstance</code>是最简单的,因为没有依赖对象。在这种情况下,您只需找到相关记录并将其删除即可。</li>
</ul>

<p>实现<code>BookInstance</code>, <code>Author</code>, 和 <code>Genre</code>模型的更新页面,以与我们的书本更新页面相同的方式,将它们与关联的详细信息页面,链接起来。</p>

<p>一些提示:</p>

<ul>
 <li>我们刚刚实施的图书更新页面是最难的!相同的模式可用于其他对象的更新页面。</li>
 <li>作者死亡日期和出生日期字段以及书本实例 due_date 字段,是输入到表单上日期输入字段的错误格式(它需要 “YYYY-MM-DD” 形式的数据)。解决此问题的最简单方法,是为适当格式化的日期,定义新的虚拟属性,然后在关联的视图模板中,使用此字段。</li>
 <li>如果您遇到困难,此处示例中的更新页面有一些<a href="https://github.com/mdn/express-locallibrary-tutorial">示例</a></li>
</ul>

<h2 id="总结">总结</h2>

<p>Express, node, 与NPM上面的第三方套件,提供你需要的每样东西 ,可用于新增表单到你的网站上。在本文中,你学到了如何使用Pug, how to create forms using Pug, validate and sanitize input using express-validator, and add, delete, and modify records in the database.</p>

<p>你现在应该了解如何新增基本表单,以及表单处理码到你的 node 网站!</p>

<h2 id="请见">请见</h2>

<ul>
 <li><a href="https://www.npmjs.com/package/express-validator">express-validator</a> (npm 文档).</li>
</ul>

<p>{{PreviousMenuNext("Learn/Server-side/Express_Nodejs/Displaying_data", "Learn/Server-side/Express_Nodejs/deployment", "Learn/Server-side/Express_Nodejs")}}</p>

<h2 id="本教程">本教程</h2>

<ul>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Introduction">Express/Node 介绍</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/development_environment">架设 Node (Express) 开发环境</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Tutorial_local_library_website">Express 教程: 本地图书馆网站</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/skeleton_website">Express 教程 2: 创建骨架网站</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/mongoose">Express 教程 3: 使用数据库 (Mongoose)</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/routes">Express 教程 4: 路由与控制器</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/Displaying_data">Express 教程 5: 呈现图书馆数据</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/forms">Express 教程 6: 使用表单</a></li>
 <li><a href="/zh-CN/docs/Learn/Server-side/Express_Nodejs/deployment">Express 教程 7: 部署到生产环境</a></li>
</ul>