用Pher,Node和Bootstrap构建一个实时投票应用程序
在这篇文章中,我将带领您构建一个完整的、实时的哈利波特豪斯投票网络应用程序。
实时应用程序通常使用WebSocket,这是一种相对较新的传输协议,而不是HTTP,HTTP是一种单向通信,只有在用户请求时才会发生。只要连接保持打开,WebSocket允许服务器和用户以及所有与应用程序连接的用户之间进行持久通信。
实时Web应用程序是指在用户和服务器之间(以及通过扩展在用户和其他用户之间)即时传输(几乎)信息的应用程序。这与传统的Web应用程序形成了鲜明的对比,在这种情况下,客户端必须从服务器获取信息。
我们的哈利波特投票网络应用程序将显示选项(所有的四个房子)和一个图表的右侧,更新自己时,一个连接的用户投票。
为了让您对外观和感觉有一个简单的了解,最后的应用程序如下所示:
下面是实时应用程序工作方式的一个小预览:
为了使我们的应用程序实时化,我们将使用Pher和WebSocket。Pusher作为服务器和客户之间的实时层。它保持与客户端的持久连接-如果可能的话通过WebSocket,并返回到基于HTTP的连接-这样,一旦您的服务器有了新的数据要推送到客户端,他们就可以通过Pher立即这样做。
构建我们的应用程序
让我们使用以下命令创建我们的新应用程序npm init
..您将以交互的方式被问到一些关于您的应用程序细节的问题。以下是我所拥有的:
aaa@qq.com ➜ Harry-Potter-Pusher $ npm init
{
"name": "harry-potter-pusher",
"version": "1.0.0",
"description": "A real-time voting application using Harry Potter's house selection for my article for Pusher.",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git+https://github.com/praveenscience/Harry-Potter-Pusher.git"
},
"keywords": [
"Harry_Potter",
"Pusher",
"Voting",
"Real_Time",
"Web_Application"
],
"author": "Praveen Kumar Purushothaman",
"license": "ISC",
"bugs": {
"url": "https://github.com/praveenscience/Harry-Potter-Pusher/issues"
},
"homepage": "https://github.com/praveenscience/Harry-Potter-Pusher#readme"
}
Is this OK? (yes)
因此,我保留了大多数设置的默认值。现在是安装依赖项的时候了。
安装依赖项
我们需要快递,身体解析器,跨源资源共享(CORS),猫鼬和推车安装作为依赖。若要在单个命令中安装所有内容,请使用以下命令。您还可以查看此命令输出的内容。
aaa@qq.com ➜ Harry-Potter-Pusher $ npm i express body-parser cors pusher mongoose
npm notice created a lockfile as package-lock.json. You should commit this file.
npm WARN aaa@qq.com requires a peer of aaa@qq.com^6.0.0 but none is installed. You must install peer dependencies yourself.
+ aaa@qq.com
+ aaa@qq.com
+ aaa@qq.com
+ aaa@qq.com
+ aaa@qq.com
added 264 packages in 40.000s
需要我们的模块
因为这是一个Express应用程序,所以我们需要包括express()
作为第一件事。在进行此操作时,我们还需要一些附带的模块。所以,首先,让我们从以下几个方面开始:
const express = require("express");
const path = require("path");
const bodyParser = require("body-parser");
const cors = require("cors");
创建Express应用程序
让我们现在开始构建我们的Express应用程序。首先,我们需要获取express()
分配给新变量的函数app
:
const app = express();
服务静态资产
在初始的包含集之后添加上面的行将初始化我们的app
作为一个特快的应用程序。接下来我们需要做的就是设置静态资源。让我们在当前项目中创建一个名为public
让我们使用Express的静态中间件来服务静态文件。在目录中,让我们创建一个简单的index.html
上面写着“你好,世界”的文件:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>Hello, World</title>
</head>
<body>
Hello, World!
</body>
</html>
为了提供静态文件,我们有一个内置的.使用()功能与式()在快车里。语法如下:
app.use( express.static( path.join(__dirname, "public") ) );
我们还需要使用主体解析器中间件将HTTPPOST内容作为JSON访问req.body
..我们也会用urlencoded
来获取只解析urlencoded
对象,只查看请求,在这些请求中,Content-Type
标头匹配type
选择。此解析器只接受身体的utf-8编码,并支持gzip
和deflate
编码:
app.use( bodyParser.json() );
app.use( bodyParser.urlencoded( { extended: false } ) );
为了允许跨域请求,我们需要启用CORS。让我们使用以下代码启用CORS模块:
app.use( cors() );
现在,所有初始配置都已设置。我们现在所需要做的就是设置一个端口并侦听特定端口上的传入连接:
const port = 3000;
app.listen(port, () => {
console.log(`Server started on port ${port}.`);
});
确保你的期末考试app.js
看起来是这样的:
const express = require("express");
const path = require("path");
const bodyParser = require("body-parser");
const cors = require("cors");
// Create an App.
const app = express();
// Serve the static files from public.
app.use( express.static( path.join(__dirname, "public") ) );
// Include the body-parser middleware.
app.use( bodyParser.json() );
app.use( bodyParser.urlencoded( { extended: false } ) );
// Enable CORS.
app.use( cors() );
// Set the port.
const port = 3000;
// Listen to incoming connections.
app.listen(port, () => {
console.log(`Server started on port ${port}.`);
});
运行命令启动服务器:
$ npm run dev
打开你的http://localhost:3000/
在一个新的标签上看到魔术。你应该看到一个新的页面“你好,世界”。
构建应用程序的后端
首先,让我们创建一个名为routes
然后在里面创建一个文件,比如说vote.js
..我们需要将这个文件与我们的app.js
文件,所以让我们回到它,并将它包含在我们的express()
初始化:
const app = express();
// Vote route.
const vote = require("./routes/vote")
因为routes
目录与app.js
,我们先从./
..为了能够在路由器中使用这个,让我们从底部开始,在端口定义之前添加如下所示的路由器中间件代码:
app.use("/vote", vote);
任何通过/vote
URL将由vote.js
文件,由vote
变量。
处理GET和POST请求
使用Express的路由器实例,我们可以处理GET
和POST
通过我们的方法/vote
路径。让我们创建一个默认值GET
现在路由并发送默认文本,例如,"You are in /vote"
.
const express = require("express");
const router = express.Router();
// Default get route.
router.get("/", (req, res) => {
res.send("You are in /vote");
});
上面的代码将所有请求路由到路径。/vote
为我们新成立的routes/vote.js
.
处理职位要求
我们还需要一个POST
处理程序,在那里我们可以触发PushAPI。它将是Router.post()
为POST
向/
这样所有的请求都会转到/vote
因为我们的中间件。我们将在这里给出相同类型的箭头函数,并给出如下消息"You have POSTed to /vote."
:
// Default POST route.
router.post("/", (req, res) => {
res.send("You have POSTed to /vote.");
});
这个res.send()
函数将在将来被PuserAPI调用所取代。
输出路由器
最后,我们必须导出路由器作为一个模块。使用module.exports
最后就像这样。这应该是文件的结尾,尽管您可以将它放在任何地方。请记住,JavaScript是面向事件的,而不是过程性的:
// Export the router.
module.exports = router;
在这一点上,当你看到完整的vote.js
文件,应该如下所示:
const express = require("express");
const router = express.Router();
// Default GET route.
router.get("/", (req, res) => {
res.send("You are in /vote.");
});
// Default POST route.
router.post("/", (req, res) => {
res.send("You have POSTed to /vote.");
});
// Export the router.
module.exports = router;
确保保存所有内容,现在尝试在我们的Web浏览器中运行这两个URL。
您应该在Web浏览器中看到输出。
与Pusher API集成
让我们首先修改我们为POST
处理程序-我们在vote.js
档案。这是我们真正想要触发的。让我们快速地去我们的推车仪表板,并选择您的Pher应用程序(praveen-science-app
(在我的例子中)并单击开始标签。你会看到启动代码。
在我们vote.js
我们需要定义(或要求)Pusher库。然后,我们需要创建一个新实例(对象)Pusher
类中,然后最终触发推送器服务。POST
..我要换衣服vote.js
文件如下所示:
注意:请务必更改您的appId
, key
, secret
给仪表板上的那个。
const express = require("express");
const router = express.Router();
// ///// Step 1: Include Pusher ///// //
const Pusher = require('pusher');
// ///// Step 2: Instantiate an Object ///// //
const pusher = new Pusher({
appId: 'appId',
key: 'key',
secret: 'secret',
cluster: 'eu',
encrypted: true
});
// Default GET route.
router.get("/", (req, res) => {
res.send("You are in /vote.");
});
// Default POST route.
router.post("/", (req, res) => {
// ///// Step 3: Trigger the Pusher service ///// //
pusher.trigger('my-channel', 'my-event', {
"message": "hello world"
});
});
// Export the router.
module.exports = router;
当用户提交表单时,我们的应用程序将触发POST
对此路由的请求,它将访问PushAPI并使用pusher.trigger()
函数调用。另外,我们不想使用默认值my-channel
和my-event
,所以让我们把它们换成hp-voting
和hp-house
..我们也不需要message
,但相反,我们想给points
而house
资料:
router.post("/", (req, res) => {
pusher.trigger('hp-voting', 'hp-house', {
"points": 1,
"house": req.body.house
});
});
现在,我们将分配一个1
到points
(我很快就会解释原因),我们正在使用req.body.house
为house
,因为值将来自表格数据,而这是通过以下方式提供的req.body
就像我们用的body-parser
.
最后,我们将使用res.json()
函数并传递带有布尔值的对象。success
和一个message
感谢用户投票,并已成功收到:
router.post("/", (req, res) => {
pusher.trigger('hp-voting', 'hp-house', {
"points": 1,
"house": req.body.house
});
return res.json({
"success": true,
"message": "Thanks for voting."
});
});
构建应用程序的前端
我使用了jQuery和Bootstrap来做前端工作。这是我们允许用户投票的部分。
我还将添加一个图表容器,它将在收到选票时实时显示。
整合一切
我们已经把后端做好了。现在,我们将看到如何在点击“投票”按钮时将请求发送到Pusher服务,这要归功于前端JavaScript。我们会触发submit
事件时,当用户单击该按钮时,它需要将POST
请求到路线的后端/vote
.
事件侦听器、用户数据和Ajax
让我们为表单提交添加一个事件侦听器、捕获用户数据的代码以及Ajax调用:
// Execute only after the whole document is fetched and assets are loaded.
$(document).ready(function () {
// Form submission event listener (event handler)
$("#voteForm").submit(function (e) {
e.preventDefault();
// Get the checked input element's value.
var house = $(".form-check-input:checked").val();
// Construct the data to be sent as a payload to the AJAX call.
var data = {
"house": house
};
$.post("/vote", data, function (res) {
// Log the output in the console.
console.log(res);
});
});
});
与Pher和图表一起工作
提交表单时,Ajax调用将触发/vote
端点,后端节点应用程序还将使用下面的代码触发pusher服务。routes/vote.js
:
pusher.trigger('hp-voting', 'hp-house', {
"points": 1,
"house": req.body.house
});
当命中(或运行)上述代码时,Pusher服务将触发以下事件hp-voting
和hp-house
..我们还没有赶上这个活动,也没有订阅它。因此,我们将实现CanvasJS来构建我们的图表,我们将订阅上面的事件,并通过触发器添加数据点,该触发器由表单的submit
事件侦听器。
添加CanvasJS
正确添加所有位后,客户端script.js
应与此类似:
// Execute only after the whole document is fetched and assets are loaded.
$(document).ready(function () {
// Form submission event listener (event handler)
$("#voteForm").submit(function (e) {
// Prevent the default event.
e.preventDefault();
// Get the checked input element's value.
var house = $(".form-check-input:checked").val();
// Construct the data to be sent as a payload to the Ajax call.
var data = {
"house": house
};
// Fire the POST request Ajax call to our /vote end point.
$.post("/vote", data, function (res) {
// Log the output in the console.
console.log(res);
});
});
// Create the base data points.
var dataPoints = [
{
label: "Gryffindor",
y: 0
}, {
label: "Hufflepuff",
y: 0
}, {
label: "Ravenclaw",
y: 0
}, {
label: "Slytherin",
y: 0
}
];
// Initialize Chart using jQuery selector.
// Get the chart container element.
var chartContainer = $("#chartContainer");
// Check if the element exists in the DOM.
if (chartContainer.length === 1) {
// Construct the options for the chart.
var options = {
"animationEnabled": true,
"theme": "light1",
"title": {
"text": "Harry Potter House Results"
},
"data": [
{
"type": "column",
"dataPoints": dataPoints
}
]
};
// Initialize the chart.
$("#chartContainer").CanvasJSChart(options);
}
});
现在保存文件,当您重新加载页面时,您应该能够看到占位符图表。这绝对是一个真正的图表,但没有任何价值。你应该能看到这样的东西:
现在我们已经在右边实现了CanvasJS图表。
客户端Pher的初始化
在Pher日志记录之后,我们必须初始化Pusher
对象。因为我们已经有了客户端config.js
,我们将在本部分中利用这些代码:
// Initialise a Pusher Object.
var pusher = new Pusher(PusherConfig.key, {
cluster: PusherConfig.cluster,
forceTLS: PusherConfigforceTLS.
});
在Pusher对象初始化之后,我们需要订阅我们的通道,在那里我们的消息由服务器端发布。我们将从PusherDashboard复制代码,但稍微更改一下以订阅我们的hp-voting
渠道和hp-house
事件。的默认值my-channel
和my-event
需要对后端代码进行如下更新:
// Subscribe to the channel.
var channel = pusher.subscribe('hp-voting');
// Bind to a particular event and listen to the event data.
channel.bind('hp-house', function(data) {
alert(JSON.stringify(data));
});
而不是alert
-正在进行一次data
消息,我们基本上想把数据添加到图表中。我们这样做的方法是dataPoints
以及处理与服务器响应有关的数组。已经存在dataPoints
变量(请记住,我们使用了var
而不是const
由于我们应该能够在稍后阶段更改它),我们将使用更高的阶数。Array.map()职能如下:
// Bind to a particular event and listen to the event data.
channel.bind('hp-house', function(data) {
// Use a higher order Array map.
dataPoints = dataPoints.map(function (d) {
// Check if the current label is the updated value.
if (d.label == data.house) {
// Increment the house's value by the number of new points.
d.y += data.points;
}
// Return the original value as this is a map function.
return d;
});
});
我们把所有的dataPoints
标签,当特定标签与当前标签匹配时,我们将用更新的点数增加当前标签的值。因为我们使用的是JavaScriptArray.map()
函数,我们必须返回原始值,d
,回到函数调用。一旦我们更新了dataPoints
,我们必须重新渲染图表。
在.之后map()
函数,我们将执行以下操作:
channel.bind('hp-house', function(data) {
// Use a higher order Array map.
dataPoints = dataPoints.map(function (d) {
// Check if the current label is the updated value.
if (d.label == data.house) {
// Increment the house's value by the number of new points.
d.y += data.points;
}
// Return the original value as this is a map function.
return d;
});
// Re-render the chart.
$("#chartContainer").CanvasJSChart(options);
});
一旦您编写了上述所有代码,在浏览器中保存和运行应用程序,启动您的web开发工具并签出控制台。您应该看到PusherService正在与您的应用程序进行通信。我能够在我的控制台中看到下面的内容(我已经隐藏了我的appId
和secret
,因此,除了这些敏感的信息位外,还显示了其他所有信息):
结语
此时,当您尝试打开同一个应用程序的两个窗口并在一个屏幕上投票时,您可以看到所有屏幕同时更新。这基本上就是使用Pusher服务创建实时应用程序的方式。
我们的哈利波特投票网络应用程序现在显示选项(所有的四个房子)和右边的图表,更新自己当一个连接的用户投票。下一个明显的步骤是使用数据库(如MongoDB)来存储所有信息,以确保即使在重新加载应用程序的页面时它仍然存在。
上一篇: 一个简单的node.js实现界面
下一篇: React-Native环境搭建-Mac