联系 - 创建基本 Web 应用程序
到目前为止,已在 Ubuntu VM 上安装 MongoDB 和 Node.js。 现在要创建一个基本 Web 应用程序来实际进行操作。 在此过程中,会看到 AngularJS 和 Express 如何发挥作用。
借助示例是一个很好的学习方法。 要构建的 Web 应用程序可实现基本的图书数据库。 通过 Web 应用程序,可以列出有关书籍的信息、添加新书籍以及删除现有书籍。
此处看到的 Web 应用程序会展示许多概念,且适用于大多数 MEAN 堆栈 Web 应用程序。 可根据需求和兴趣,探索构建自己的 MEAN 堆栈应用程序所需的功能。
以下是图书 Web 应用程序的外观。
以下介绍 MEAN 堆栈的每个组件如何发挥作用。
- MongoDB 存储有关书籍的信息。
- Express.js 将每个 HTTP 请求路由到适当的处理程序。
- AngularJS 将用户界面与程序的业务逻辑相连接。
- Node.js 托管服务器端应用程序。
重要
出于学习目的,将在此构建一个基本的 Web 应用程序。 其目的是测试 MEAN 堆栈并了解其工作原理。 此应用程序还不够安全或尚未准备好用于生产。
那么什么是 Express 呢?
到目前为止,已在 VM 上安装 MongoDB 和 Node.js。 那么 MEAN 首字母缩略词中的“E”(即 Express.js)是什么呢?
Express.js 是一个为 Node.js 构建的 Web 服务器框架,可简化 Web 应用程序的构建过程。
Express 的主要功能是处理请求路由。 “路由”指应用程序响应针对特定终结点的请求的方式。 终结点由路径或 URI 及请求方法(如 GET 或 POST )构成。 例如,可通过提供数据库中所有书籍的列表来响应对 /book
终结点的 GET 请求。 可通过根据用户在 Web 窗体中输入的字段向数据库添加条目来响应对同一端点的 POST 请求。
在即将构建的 Web 应用程序中,将使用 Express 来路由 HTTP 请求并将 Web 内容返回给用户。 Express 还可帮助 Web 应用程序使用 HTTP cookie 和处理查询字符串。
Express 是一个 Node.js 包。 使用 Node.js 附带的 npm 实用程序来安装和管理 Node.js 包。 稍后在本单元中将创建一个名为 package.json
的文件来定义 Express 和其他依赖项,并运行 npm install
命令来安装这些依赖项。
那么什么是 AngularJS 呢?
与 Express 一样,现在尚未安装 AngularJS,即 MEAN 首字母缩略词中的“A”。
AngularJS 使 Web 应用程序更易于编写和测试,因为通过它能更好地将网页的外观和 HTML 代码与网页的行为区分开来。 如果熟悉模型 - 视图 - 控制器 (MVC) 模式或数据绑定的概念,会对 AngularJS 感到很熟悉。
AngularJS 就是所谓的前端 JavaScript 框架,这意味着只能在访问应用程序的客户端上使用它。 换句话说,AngularJS 在用户的 Web 浏览器中运行,而不是在 Web 服务器上运行。 由于 AngularJS 是 JavaScript,可以使用它轻松从 Web 服务器获取要在页面上显示的数据。
并非真正地“安装”AngularJS。 而是在 HTML 页面中添加对 JavaScript 文件的引用,与其他 JavaScript 库的操作一样。 有多种方法可在网页中包含 AngularJS。 此处将从内容分发网络或 CDN 加载 AngularJS。 CDN 是一种在地理方位上分发图像、视频和其他内容以提高下载速度的方法。
现在还不到添加此代码的时候,但可先在此处查看从 CDN 加载 AngularJS 的示例。 通常会将此代码添加到 HTML 页面的 <head>
部分。
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.2/angular.min.js"></script>
备注
请勿将 AngularJS 与 Angular 混淆。 虽然二者存在许多相似的概念,但 AngularJS 是 Angular 的前身。 AngularJS 仍被经常用于构建 Web 应用程序。 AngularJS 基于 JavaScript,而 Angular 基于 TypeScript,这是一种编程语言,可以更轻松地编写 JavaScript 程序。
我如何构建应用程序?
此处将使用一个基本过程。 在 Cloud Shell 中编写应用程序代码,然后使用 SCP 或安全复制协议将文件复制到 VM。 然后启动 Node.js 应用程序并在浏览器中查看结果。
实际上,通常是在更本地化的环境中编写和测试 Web 应用程序,例如在笔记本电脑或本地运行的虚拟机上。 然后可将代码存储在版本控制系统(如 Git)中,并使用持续集成和持续交付或称 CI/CD 系统(如 Azure DevOps)来测试更改并将其上载到 VM。 本单元末尾会介绍更多资源。
创建图书 Web 应用程序
此处将创建构成 Web 应用程序的所有代码、脚本和 HTML 文件。 为节省篇幅,仅侧重介绍每个文件的重要部分,而不作详尽介绍。
如果仍通过 SSH 连接到 VM,请运行 exit
,退出 SSH 会话并返回 Cloud Shell。
exit
现在回到 Cloud Shell 会话。
创建文件
在 Cloud Shell 中运行这些命令以创建 Web 应用程序的文件夹和文件:
cd ~ mkdir Books touch Books/server.js touch Books/package.json mkdir Books/app touch Books/app/model.js touch Books/app/routes.js mkdir Books/public touch Books/public/script.js touch Books/public/index.html
包括:
Books
是项目的根目录。server.js
定义 Web 应用程序的入口点。 它加载所需的 Node.js 包,指定要侦听的端口,并开始侦听传入的 HTTP 流量。package.json
提供有关应用程序的信息,包括其名称、描述以及应用程序运行所需的 Node.js 包。
Books/app
包含服务器上运行的代码。model.js
定义数据库连接和架构。 可将其视为应用程序的数据模型。routes.js
处理请求路由。 例如,通过提供数据库中所有书籍的列表来定义对/book
端点的 GET 请求。
Books/public
包含直接提供给客户端浏览器的文件。index.html
包含索引页面。 它包含一个 Web 窗体,用户使用该窗体提交有关书籍的信息。 它还显示数据库中的所有书籍,可通过它从数据库中删除条目。script.js
包含用户浏览器中运行的 JavaScript 代码。 它可以向服务器发送请求,请求列出书籍、将书籍添加到数据库以及从数据库中删除书籍。
运行
code
命令可通过 Cloud Shell 编辑器打开文件。code Books
创建数据模型
从编辑器中,打开
app/model.js
并添加以下内容:var mongoose = require('mongoose'); var dbHost = 'mongodb://localhost:27017/Books'; mongoose.connect(dbHost, { useNewUrlParser: true } ); mongoose.connection; mongoose.set('debug', true); var bookSchema = mongoose.Schema( { name: String, isbn: {type: String, index: true}, author: String, pages: Number }); var Book = mongoose.model('Book', bookSchema); module.exports = Book;
重要
每次在编辑器中将代码粘贴到文件或更改文件中的代码后,请务必使用“...”菜单或快捷键(在 Windows 和 Linux 上,按 Ctrl+S;在 macOS 上,按 Command+S)保存。
此代码使用 Mongoose 简化了将数据传入和传出 MongoDB 的过程。 Mongoose 是一个用于建模数据的基于架构的系统。 该代定义名为“Book”、具有所提供架构的数据库文档。 该架构定义用于描述单本图书的四个字段:
- 该书的名称或标题
- 唯一标识该书的国际标准书号 (ISBN)
- 其作者
- 该书包含的页数
接下来创建将 GET、POST 和 DELETE 请求映射到数据库操作的 HTTP 处理程序。
创建处理 HTTP 请求的 Express.js 路由
在编辑器中打开
app/routes.js
并添加以下代码:var path = require('path'); var Book = require('./model'); var routes = function(app) { app.get('/book', function(req, res) { Book.find({}, function(err, result) { if ( err ) throw err; res.json(result); }); }); app.post('/book', function(req, res) { var book = new Book( { name:req.body.name, isbn:req.body.isbn, author:req.body.author, pages:req.body.pages }); book.save(function(err, result) { if ( err ) throw err; res.json( { message:"Successfully added book", book:result }); }); }); app.delete("/book/:isbn", function(req, res) { Book.findOneAndRemove(req.query, function(err, result) { if ( err ) throw err; res.json( { message: "Successfully deleted the book", book: result }); }); }); app.get('*', function(req, res) { res.sendFile(path.join(__dirname + '/public', 'index.html')); }); }; module.exports = routes;
此代码为应用程序创建了四个路由。 以下是每个路由的简要概述。
Http 谓词 终结点 说明 GET /book
从数据库中检索所有书籍。 POST /book
根据用户在 Web 窗体上提供的字段创建 Book
对象,并将该对象写入数据库。删除 /book/:isbn
从数据库中删除由 ISBN 标识的书籍。 GET *
无其他路由匹配时,返回索引页面。 Express.js 可在路由处理代码中直接提供 HTTP 响应,或者可以提供来自文件的静态内容。 此代码同时显示了二者。 前三个路由返回书籍 API 请求的 JSON 数据。 第四个路由(默认情况)返回索引文件
index.html
的内容。
创建客户端 JavaScript 应用程序
在编辑器中打开
public/script.js
并添加以下代码:var app = angular.module('myApp', []); app.controller('myCtrl', function($scope, $http) { var getData = function() { return $http( { method: 'GET', url: '/book' }).then(function successCallback(response) { $scope.books = response.data; }, function errorCallback(response) { console.log('Error: ' + response); }); }; getData(); $scope.del_book = function(book) { $http( { method: 'DELETE', url: '/book/:isbn', params: {'isbn': book.isbn} }).then(function successCallback(response) { console.log(response); return getData(); }, function errorCallback(response) { console.log('Error: ' + response); }); }; $scope.add_book = function() { var body = '{ "name": "' + $scope.Name + '", "isbn": "' + $scope.Isbn + '", "author": "' + $scope.Author + '", "pages": "' + $scope.Pages + '" }'; $http({ method: 'POST', url: '/book', data: body }).then(function successCallback(response) { console.log(response); return getData(); }, function errorCallback(response) { console.log('Error: ' + response); }); }; });
请注意此代码是如何定义名为“myApp”的模块和名为“myCtrl”的控制器的。 此处不会详细介绍模块和控制器的工作原理,但将在下一阶段使用这些名称将用户界面(HTML 代码)与应用程序的业务逻辑绑定在一起。
之前已创建四个路由来处理服务器上的各种 GET、POST 和 DELETE 操作。 此代码与这些操作类似,但是来自客户端(用户的 Web 浏览器)。
例如,
getData
函数向/book
终结点发送 GET 请求。 回想一下,服务器处理此请求的方法是从数据库中检索有关所有书籍的信息并将信息作为 JSON 数据返回。 请注意如何将生成的 JSON 数据分配给$scope.books
变量。 你将了解此操作如何影响用户下一步将在网页上看到的内容。该代码在页面加载时调用
getData
函数。 可检查del_book
和add_book
函数以了解其工作方式。 无需客户端代码来匹配服务器的默认处理程序,因为默认处理程序返回索引页而不是 JSON 数据。
创建用户界面
在编辑器中打开
public/index.html
并添加以下代码:<!doctype html> <html ng-app="myApp" ng-controller="myCtrl"> <head> <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.7.2/angular.min.js"></script> <script src="script.js"></script> </head> <body> <div> <table> <tr> <td>Name:</td> <td><input type="text" ng-model="Name"></td> </tr> <tr> <td>Isbn:</td> <td><input type="text" ng-model="Isbn"></td> </tr> <tr> <td>Author:</td> <td><input type="text" ng-model="Author"></td> </tr> <tr> <td>Pages:</td> <td><input type="number" ng-model="Pages"></td> </tr> </table> <button ng-click="add_book()">Add</button> </div> <hr> <div> <table> <tr> <th>Name</th> <th>Isbn</th> <th>Author</th> <th>Pages</th> </tr> <tr ng-repeat="book in books"> <td><input type="button" value="Delete" data-ng-click="del_book(book)"></td> <td>{{book.name}}</td> <td>{{book.isbn}}</td> <td>{{book.author}}</td> <td>{{book.pages}}</td> </tr> </table> </div> </body> </html>
此代码创建一个基本 HTML 窗体,其中包含四个用于提交书籍数据的字段和一个显示数据库中所有书籍的表格。
虽然这是标准 HTML 代码,但你可能不熟悉
ng-
HTML 属性。 这些 HTML 属性将 AngularJS 代码绑定到用户界面。 例如,当你选择“添加”时,AngularJS 将调用add_book
函数,该函数将表单数据发送到服务器。可在此处查看代码,了解每个
ng-
属性与应用程序业务逻辑之间的关联。
创建 Express.js 服务器,用于托管应用程序
在编辑器中打开
server.js
并添加以下代码:var express = require('express'); var bodyParser = require('body-parser'); var app = express(); app.use(express.static(__dirname + '/public')); app.use(bodyParser.json()); require('./app/routes')(app); app.set('port', 80); app.listen(app.get('port'), function() { console.log('Server up: http://localhost:' + app.get('port')); });
此代码创建 Web 应用本身。 它提供来自
public
目录的静态文件,并使用之前定义的路由来处理请求。
定义包信息和依赖项
回想一下,package.json
提供有关应用程序的信息,包括其名称、描述以及应用程序运行所需的 Node.js 包。
在编辑器中打开
package.json
并添加以下代码:{ "name": "books", "description": "Sample web app that manages book information.", "license": "MIT", "repository": { "type": "git", "url": "https://github.com/MicrosoftDocs/mslearn-build-a-web-app-with-mean-on-a-linux-vm" }, "main": "server.js", "dependencies": { "express": "~4.16", "mongoose": "~5.3", "body-parser": "~1.18" } }
可看到有关应用程序的信息或元数据,包括其名称、描述和许可证。
repository
字段指定代码的保留位置。 稍后可通过此处显示的 URL 查看 GitHub 上的代码,作为参考。
main
字段定义应用程序的入口点。 此处提供此字段是出于完整介绍的目的,但它并不重要,因为你不会将应用程序发布为 Node.js 包以供他人下载和使用。
dependencies
字段非常重要。 它定义应用程序所需的 Node.js 包。 不久后会再次连接到 VM 并运行 npm install
命令来安装这些软件包。
节点包通常使用语义化版本控制方案。 版本号包含三个组件:主版本、次版本和补丁。 这里的波浪号 ~
表示法告知 npm 在提供的主版本和次版本下安装最新的补丁版本。 此处显示的版本是测试此模块时使用的最新版本。 实际上,在更新和测试应用程序的过程中,可随时间推移逐步使用更新的版本,从而获取每个依赖包提供的最新功能。
将文件复制到 VM
在继续之前,请确保具有 VM 的 IP 地址。 如果没有,请从 Cloud Shell 运行这些命令以进行检索:
ipaddress=$(az vm show \
--name MeanStack \
--resource-group "<rgn>[sandbox resource group name]</rgn>" \
--show-details \
--query [publicIps] \
--output tsv)
echo $ipaddress
至此已完成编辑文件。 请确保已将更改保存到每个文件,然后关闭编辑器。
若要关闭编辑器,请选择右上角的椭圆形,然后选择“关闭编辑器”。
运行以下
scp
命令,将 Cloud Shell 会话中~/Books
目录的内容复制到 VM 上的相同目录名:scp -r ~/Books azureuser@$ipaddress:~/Books
安装更多 Node 包
假设在开发过程中,确定了要使用的其他 Node 包。 例如,回想一下,app/model.js
从此行开始。
var mongoose = require('mongoose');
回想一下,应用程序借助 Mongoose 将数据传入和传出 MongoDB 数据库。
应用程序还需要 Express.js 和正文分析器包。 正文分析器是一个插件,通过该插件 Express 可处理客户端发送的 Web 窗体中的数据。
现在连接到 VM 并安装在 package.json
中指定的软件包。
连接 VM 之前,请确保具有 VM 的 IP 地址。 如果没有,请运行上一部分中的 Cloud Shell 命令以进行检索。
与之前一样,通过 SSH 与 VM 建立连接:
ssh azureuser@$ipaddress
请移至主目录下的
Books
目录:cd ~/Books
运行
npm install
以安装从属软件包:sudo apt install npm -y && npm install
将 SSH 连接保持为打开状态,以便在下一部分中使用。
测试应用程序
现在即可测试 Node.js Web 应用程序!
在
~/Books
目录中,运行此命令以启动 Web 应用程序:sudo nodejs server.js
此命令通过在端口 80 上侦听传入的 HTTP 请求来启动应用程序。
从单独的浏览器选项卡导航到 VM 的公共 IP 地址。
会显示索引页面,其中包含 Web 窗体。
尝试将几本书添加到数据库中。 每次添加书籍后,页面都会更新完整的书籍列表。
若要从数据库中删除一本书,你还可以选择“删除”。