用Pher,Node和Bootstrap构建一个实时投票应用程序

在这篇文章中,我将带领您构建一个完整的、实时的哈利波特豪斯投票网络应用程序。

实时应用程序通常使用WebSocket,这是一种相对较新的传输协议,而不是HTTP,HTTP是一种单向通信,只有在用户请求时才会发生。只要连接保持打开,WebSocket允许服务器和用户以及所有与应用程序连接的用户之间进行持久通信。

实时Web应用程序是指在用户和服务器之间(以及通过扩展在用户和其他用户之间)即时传输(几乎)信息的应用程序。这与传统的Web应用程序形成了鲜明的对比,在这种情况下,客户端必须从服务器获取信息。

我们的哈利波特投票网络应用程序将显示选项(所有的四个房子)和一个图表的右侧,更新自己时,一个连接的用户投票。

为了让您对外观和感觉有一个简单的了解,最后的应用程序如下所示:

 

下面是实时应用程序工作方式的一个小预览:

为了使我们的应用程序实时化,我们将使用Pher和WebSocket。Pusher作为服务器和客户之间的实时层。它保持与客户端的持久连接-如果可能的话通过WebSocket,并返回到基于HTTP的连接-这样,一旦您的服务器有了新的数据要推送到客户端,他们就可以通过Pher立即这样做。

构建我们的应用程序

让我们使用以下命令创建我们的新应用程序npm init..您将以交互的方式被问到一些关于您的应用程序细节的问题。以下是我所拥有的:

praveen@praveen.science ➜ 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),猫鼬和推车安装作为依赖。若要在单个命令中安装所有内容,请使用以下命令。您还可以查看此命令输出的内容。

praveen@praveen.science ➜ 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 ajv-keywords@3.2.0 requires a peer of ajv@^6.0.0 but none is installed. You must install peer dependencies yourself.

+ pusher@2.1.2
+ body-parser@1.18.3
+ mongoose@5.2.6
+ cors@2.8.4
+ express@4.16.3
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编码,并支持gzipdeflate编码:

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/在一个新的标签上看到魔术。你应该看到一个新的页面“你好,世界”。

Preview of Hello World in Browser

构建应用程序的后端

首先,让我们创建一个名为routes然后在里面创建一个文件,比如说vote.js..我们需要将这个文件与我们的app.js文件,所以让我们回到它,并将它包含在我们的express()初始化:

const app = express();

// Vote route.
const vote = require("./routes/vote")

因为routes目录与app.js,我们先从./..为了能够在路由器中使用这个,让我们从底部开始,在端口定义之前添加如下所示的路由器中间件代码:

app.use("/vote", vote);

任何通过/voteURL将由vote.js文件,由vote变量。

处理GET和POST请求

使用Express的路由器实例,我们可以处理GETPOST通过我们的方法/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文件如下所示:

注意:请务必更改您的appIdkeysecret给仪表板上的那个。

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-channelmy-event,所以让我们把它们换成hp-votinghp-house..我们也不需要message,但相反,我们想给pointshouse资料:

router.post("/", (req, res) => {
  pusher.trigger('hp-voting', 'hp-house', {
    "points": 1,
    "house": req.body.house
  });

});

现在,我们将分配一个1points(我很快就会解释原因),我们正在使用req.body.househouse,因为值将来自表格数据,而这是通过以下方式提供的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来做前端工作。这是我们允许用户投票的部分。

 

我还将添加一个图表容器,它将在收到选票时实时显示。

Harry Potter App Landing page after adding Logo

整合一切

我们已经把后端做好了。现在,我们将看到如何在点击“投票”按钮时将请求发送到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-votinghp-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-channelmy-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正在与您的应用程序进行通信。我能够在我的控制台中看到下面的内容(我已经隐藏了我的appIdsecret,因此,除了这些敏感的信息位外,还显示了其他所有信息):

 

结语

此时,当您尝试打开同一个应用程序的两个窗口并在一个屏幕上投票时,您可以看到所有屏幕同时更新。这基本上就是使用Pusher服务创建实时应用程序的方式。

我们的哈利波特投票网络应用程序现在显示选项(所有的四个房子)和右边的图表,更新自己当一个连接的用户投票。下一个明显的步骤是使用数据库(如MongoDB)来存储所有信息,以确保即使在重新加载应用程序的页面时它仍然存在。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
好的,以下是一个使用自适应蚁群算法计算最优解的旅行商问题的示例代码: ```python import numpy as np # 定义问题参数 n_cities = 20 # 城市数量 n_ants = 20 # 蚂蚁数量 alpha = 1.0 # 信息素重要程度因子 beta = 5.0 # 启发函数重要程度因子 rho = 0.1 # 信息素挥发因子 Q = 100 # 常数因子 t0 = 1 # 初始信息素浓度 max_iter = 100 # 最大迭代次数 # 生成随机城市坐标 np.random.seed(42) cities = np.random.rand(n_cities, 2) # 计算距离矩阵 dist_mat = np.zeros((n_cities, n_cities)) for i in range(n_cities): for j in range(n_cities): dist_mat[i, j] = np.sqrt((cities[i, 0] - cities[j, 0]) ** 2 + (cities[i, 1] - cities[j, 1]) ** 2) # 初始化信息素浓度矩阵 pher_mat = np.ones((n_cities, n_cities)) * t0 # 定义启发函数 def heuristic_func(dist): return 1.0 / dist # 定义选择下一个城市的策略 def select_next_city(cur_city, unvisited_cities, pher_mat, dist_mat, alpha, beta): # 计算下一个城市的概率分布 probs = np.zeros(len(unvisited_cities)) for i, city in enumerate(unvisited_cities): probs[i] = (pher_mat[cur_city, city] ** alpha) * (heuristic_func(dist_mat[cur_city, city]) ** beta) probs /= np.sum(probs) # 根据概率分布选择下一个城市 next_city = np.random.choice(unvisited_cities, p=probs) return next_city # 定义更新信息素浓度的策略 def update_pheromone(trails, pher_mat, rho, Q): delta_pher_mat = np.zeros((n_cities, n_cities)) for trail in trails: for i in range(n_cities - 1): delta_pher_mat[trail[i], trail[i+1]] += Q / dist_mat[trail[i], trail[i+1]] delta_pher_mat[trail[-1], trail[0]] += Q / dist_mat[trail[-1], trail[0]] pher_mat *= (1 - rho) pher_mat += delta_pher_mat # 定义自适应参数更新策略 def update_parameters(success_count, total_count, alpha, beta): # 计算成功率 success_rate = success_count / total_count # 根据成功率动态调整alpha和beta if success_rate > 0.95: alpha *= 1.05 beta *= 1.05 elif success_rate < 0.05: alpha *= 0.95 beta *= 0.95 return alpha, beta # 迭代搜索 best_dist = np.inf best_path = None for iter in range(max_iter): # 初始化蚂蚁位置和已访问城市 ant_positions = np.random.randint(0, n_cities, size=n_ants) visited_cities = np.zeros((n_ants, n_cities), dtype=bool) visited_cities[np.arange(n_ants), ant_positions] = True # 搜索路径 for i in range(n_cities - 1): # 选择下一个城市 next_cities = [] for j in range(n_ants): unvisited_cities = np.where(~visited_cities[j])[0] next_city = select_next_city(ant_positions[j], unvisited_cities, pher_mat, dist_mat, alpha, beta) ant_positions[j] = next_city visited_cities[j, next_city] = True next_cities.append(next_city) # 更新信息素浓度 update_pheromone(next_cities, pher_mat, rho, Q) # 计算本次迭代的最优解 dists = [] paths = [] for j in range(n_ants): path = [] dist = 0 cur_city = ant_positions[j] unvisited_cities = np.where(~visited_cities[j])[0] while len(unvisited_cities) > 0: next_city = select_next_city(cur_city, unvisited_cities, pher_mat, dist_mat, alpha, beta) path.append(cur_city) dist += dist_mat[cur_city, next_city] cur_city = next_city unvisited_cities = np.where(~visited_cities[j])[0] path.append(cur_city) dist += dist_mat[cur_city, ant_positions[j]] dists.append(dist) paths.append(path) best_idx = np.argmin(dists) if dists[best_idx] < best_dist: best_dist = dists[best_idx] best_path = paths[best_idx] # 更新自适应参数 alpha, beta = update_parameters(len(paths), n_ants, alpha, beta) # 打印迭代信息 print(f"iter {iter}: best_dist={best_dist}") ``` 在上述代码中,我们使用了一个自适应参数更新策略,根据每次迭代的成功率来动态调整alpha和beta的大小。在每次迭代结束后,我们根据所有蚂蚁的路径更新信息素浓度矩阵,并计算最优路径。最后,我们打印出每次迭代的最优解。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值