node.js前台调用js

    技术2024-05-15  89

    在本文中,您将学习如何使用IBM Cloud为组织的前台编写Node.js应用程序,该应用程序需要登录和注销访客。 在此过程中,您将学习如何使用Node.js,Express HTTP服务器库和Cloudant数据库。 您将在高度可用的IBM Cloud中学习如何执行此操作。 这是对IBM Cloud平台上的Node.js编程的基本介绍。

    使用IBM Cloud Lite构建您的下一个应用程序(免费!)

    没有时间限制。 无需信用卡。 在此注册。

    构建应用程序所需的条件

    一个免费的IBM Cloud帐户。 HTML的基本知识。 JavaScript的基础知识。

    运行应用程序 获取代码

    入门

    首先,我们创建应用程序。 然后我们创建开发环境。

    入门

    首先,我们创建应用程序。 然后我们创建开发环境。

    创建应用程序

    登录到IBM Cloud Console 。 如果您没有IBM帐户,请在此处创建一个 。 展开汉堡包图标,然后选择Cloud Foundry Apps 。 单击创建资源 。 选择类别Cloud Foundry Apps 。 在该类别中,单击Node.js的 SDK。 单击SDK forNode.js 。 输入以下参数:
    应用名称 选择一个未使用的值 主机名 接受默认 域 mybluemix.net 地区/位置 选择离您最近的那个 组织空间 保留默认值 点击创建 。 单击左侧边栏的概述 。 点击访问应用程序URL 。 您可能必须重新加载,直到创建并启动该应用程序。 图1显示了初始应用程序。 看到它之后,不要关闭该选项卡。 稍后您将需要它。

    注意:这将是您的初始应用程序。 如果您转到示例应用程序的URL,您将获得一个不同的URL,这是我们在本文后面构建的用户界面。

    图1.初始应用

    创建开发环境

    IBM Cloud上的Cloud Foundry允许多个开发环境。 在本文中,我们走了简单的道路,并使用了Web开发环境。

    返回到该应用程序的IBM Console页面。 如果您已注销,请重新登录并在仪表板上单击应用程序的名称。 向下滚动至连续投放,然后点击启用 。 要接受默认值,请单击创建 。 创建工具链后,单击Eclipse Orion Web IDE 。 单击创建新的启动配置> + 。 单击“ 保存”以创建默认的启动配置。 它具有我们需要的一切。 单击访问者日志>app.js 。 用此处的代码替换文件的内容。 单击播放图标以应用更改。 等到再次看到绿色圆圈。 这意味着应用程序已使用新代码重新启动。 返回到应用程序选项卡,并在URL的末尾添加/ hello。 看到您收到Hello世界消息。

    为了理解该程序,我们逐行遍历源代码( 可在此处找到) 。

    第一行被JavaScript解释器忽略为注释。 之所以在这里,是因为IBM Console编辑器包括Eslint实用程序,用于识别代码中的潜在错误。 该指令告诉该实用程序将JavaScript视为Node.js(服务器端JavaScript)代码。 您可以在此处阅读有关Eslint实用程序的更多信息 。

    /*Eslint-env node*/

    这是用JavaScript编写注释的另一种方法。 双斜杠(//)之后的其余行将被忽略。

    //------------------------------------------------------------------------------ // node.js starter application for Bluemix //------------------------------------------------------------------------------

    Node.js是运行时环境。 Web服务器软件包为Express。 您可以在此处了解更多信息 。 在Node.js中使用软件包的方式是将require函数与软件包名称一起使用(假定该软件包在package.json文件中声明)。 require函数通常返回用于使用模块的函数。

    // This application uses express as its web server // for more info, see: http://expressjs.com var express = require('express');

    cfenv模块提供Cloudy Foundry信息。 以后使用它来获取Web服务器需要侦听的端口号以及它正在提供的URL。

    // cfenv provides access to your Cloud Foundry environment // for more info, see: https://www.npmjs.com/package/cfenv var cfenv = require('cfenv');

    下一步是创建HTTP服务器。

    // create a new express server var app = express();

    这就是我们告诉Express从目录提供静态文件的方式。 如果在编辑器中打开公共目录,您将看到index.html文件,它是默认应用程序的一部分(图1中所示)。

    // serve the files out of ./public as our main files app.use(express.static(__dirname + '/public'));

    app.get调用指定如何处理对特定路径的请求。 路径是第一个参数。 第二个参数是在请求路径时调用的函数。 该函数有两个参数:一个包含HTTP请求的请求对象,以及一个用于发送回响应的响应对象。 JavaScript为函数文字提供了两种语法选项(一个匿名函数本身可以是一个函数参数)。 我们在这里使用的较旧的是function(<parameters>) {body} 。

    回调函数仅使用res.send函数发送常量字符串,即传统的hello world。

    /* @callback */注释用于Eslint。 通常,Eslint会报告一个函数有一个未使用的参数,建议您删除它。 但是,回调函数的参数由调用代码(此处为Express)设置。 在这种情况下,某些路径回调需要两个参数。

    app.get("/hello", /* @callback */ function(req, res) { res.send("Hello, world"); });

    此步骤将获得Cloud Foundry环境。 这就是IBM Cloud基础架构与应用程序通信的方式。 您可以在此处了解更多信息 。

    // get the app environment from Cloud Foundry var appEnv = cfenv.getAppEnv(); // start server on the specified port and binding host app.listen(appEnv.port, '0.0.0.0', function() { // print a message when the server starts listening console.log("server starting on " + appEnv.url); });

    访客日志

    该应用程序的主要数据结构是访问者日志。 您可以在此处查看带有声明和操作代码的源代码 。 替换当前的app.js,然后单击播放按钮以重新启动应用程序。 您可以在https:// <您的应用程序> / test / visitors中查看当前的访问者日志。 它采用JSON格式 ,因此如果使用JSON格式化程序(例如this) ,可能会更清楚。 要查看我的应用程序实例的访问者日志,请单击此处 。

    访问者日志是一个键为访问者名称的关联数组 (也称为哈希表或对象)。 每个访问者名称的值本身就是一个关联数组,可以具有两个属性:

    到达 –访客最后一次到达办公室。 如果访客当前不在办公室,则此属性不存在。 历史记录 –访问者的历史记录,它是与前次访问的开始和结束时间关联的数组。 第一次访问办公室期间该属性不存在。

    默认访问者日志

    有一个默认的访客日志,其中包含一些用于调试的示例数据。 如图2所示。

    图2.默认的访问者日志

    您可以在GitHub的第23-48行中看到此默认访问者日志的定义。 它大多数是标准JavaScript对象定义,但是日期不是固定的,而是在应用程序启动之前的固定时间。 Date.now()是从现在到固定时间点(格林尼治标准时间1970年1月1 午夜)之间的毫秒数。 因此,Bill Hamm的到达时间是在应用程序启动时执行此代码之前的两个小时。 您可以在此处阅读有关Date对象的更多信息 。

    "Bill Hamm": { arrived: new Date(Date.now() - 1000*3600*2) },

    阅读访客日志

    在显示访客日志时,我们可能想要显示所有日志,或者我们只想显示当前的访客,即办公室中当前的访客。 同样,在没有他们的全部信息的情况下,获取所有访客,当前办公室中的所有访客或当前不是所有访客的名称也很有用。 这些是实现这些要求的功能:

    要获取访问者名称列表,我们所需要做的就是获取visitors对象的键。 实现此功能的函数是Object.keys 。 请注意,这里的函数语法是新的语法, (parameters) => {expressions;} 。 在这种情况下,没有参数。

    var visitorNames = () => { return Object.keys(visitors); };

    要获取子集(无论是在办公室的人还是不在办公室的人),请使用filter功能。 此函数是一个数组方法(因此您可以在任何列表上调用它,例如visitorNames返回的visitorNames )。 它得到一个参数,一个函数。 此函数从列表中获取一个项目作为参数,并返回该项目是否应为已过滤列表的成员。

    功能定义被简化。 当函数文字是单个表达式时,无需将其括在大括号( {} )中并具有return语句。 您可以只输入表达式。

    var currentVisitorNames = () => { return visitorNames().filter((name) => visitors[name].arrived !== undefined); };

    以名称作为关键字来获取当前访问者列表及其信息会更加复杂。 第一步在currentVisitorList 。 此功能使用map功能检索当前访问者列表中每个用户的信息。 该函数接收函数作为参数,类似于filter 。 但是,不是决定某个值是否将在输出中,而是使用map的函数来转换该值。 这些转换后的值将以与原始值相同的顺序转换为列表。 您可以在此处阅读有关此功能的信息 。

    在这种情况下,对于每个名称,我们都会获得一个包含用户信息的结构。 例如,对于默认数据,我们可能会得到如图3所示的内容。

    var currentVisitorNames = () => { return visitorNames().filter((name) => visitors[name].arrived !== undefined); };
    图3.当前访问者列表

    此列表包含我们所需的信息,但是每个用户都在列表中的单独对象中。 使用我们在visitors变量中使用的相同格式会更有意义,一个对象的每个用户都是一个单独的属性。 为此,我们使用reduce将列表变成单个对象。 如果收到的列表中有一项,则返回该项目。 如果有两个项目,则对它们运行参数函数并返回结果。 如果还有更多,它将在其中两个参数上运行参数函数,然后在结果和另一个结果上运行参数函数,直到仅剩一个值为止。 您可以在此处阅读有关该功能的更多信息 。

    var currentVisitors = () => { return currentVisitorList().reduce((a, b) => {

    要创建组合对象,我们首先需要新对象的密钥,即列表中的密钥,该密钥仅对单个用户具有单个属性。

    var bKey = Object.keys(b)[0];

    我们将此值添加到原始对象中,在该对象中我们累积了所有用户,然后将其返回。

    a[bKey] = b[bKey]; return a; }); };

    修改访客日志

    访客日志有两种可能的修改。 用户可以在进入办公室时登录,或者在离开办公室时注销。

    对于这些函数,我们具有一个get函数和一个set函数,以与visitors变量进行交互。 这些功能的目的是使将来轻松修改应用程序以使用其他数据存储(例如数据库)。

    var getVisitor = (name) => visitors[name]; var setVisitor = (name, values) => visitors[name] = values;

    注销功能相对简单。 检查用户是否具有arrived属性。 如果该用户没有该用户,则该用户当前不在办公室,因此无法注销。 报告错误。 如果arrived属性确实存在,请将其删除,并将刚刚结束的访问添加到历史记录中。

    var logOut = (name) => { var oldRecord = getVisitor(name);

    如果在反斜线( ` )中包含字符串,则表示它是模板文字 。 它可以运行多行,并且可以使用语法${< expression >}代替表达式的值,例如name变量。 如果根本没有用户,这是我们返回的错误:

    if (oldRecord === undefined) return `Error, ${name} is unknown`;

    如果用户存在,这是我们返回的错误,但目前未登录。

    if (oldRecord.arrived === undefined) return `Error, ${name} is not logged in`;

    旧记录中唯一仍然相关的属性是history 。 我们需要保留它并添加新的价值。 但是,如果这是第一次访问,可能还没有历史记录。 在这种情况下,历史记录为空。

    var history = oldRecord.history;

    // If this is the first visit if (history === undefined) history = [];

    unshift函数在数组的开头添加一个新值。 这是我们想要的行为,因为最近的访问更有可能是相关的,因此最好将数组按相反的时间顺序排序。 您可以在此处阅读有关此功能的更多信息 。

    history.unshift({ arrived: oldRecord.arrived, left: new Date(Date.now()) });

    当前未登录的访问者的唯一属性是history 。

    setVisitor(name, {history: history}); return `OK, ${name} is logged out now`; };

    登录功能使用现有历史记录创建新记录,并使用当前时间创建arrived属性。

    var logIn = (name) => { var oldRecord = getVisitor(name); var history;

    如果您有多个条件,并且只希望处理第一个为真的条件,则可以使用以下语法:

    if <condition A> <action A> else if <condition B> <action B> else if <condition C> <action C> … else <default action>

    由于else if语句,一旦条件被评估为真,将对其执行操作,并跳过其余条件。

    在这里,我们需要检查是否存在现有条目,然后再检查它是否具有arrived属性,因为检查未定义值的属性是错误的。

    // First time we see this person if (oldRecord === undefined) history = []; // No history // Already logged in else if (oldRecord.arrived !== undefined) return `Error, ${name} is already logged in`; // Not logged in, already exists else history = oldRecord.history; setVisitor(name, { arrived: new Date(Date.now()), history: history }); return `OK, ${name} is logged in now`; };

    测验

    上面的代码看起来应该可以工作,但是最好在继续之前对其进行测试。 出于测试目的,/ test下的各种路径返回上述函数的结果。 这样做依赖于testFunctions ,它是一个具有路径及其关联功能的表。 该表中的函数不接收任何参数,并返回信息以发送给用户。 为了能够测试确实需要参数的功能,例如logIn ,将它们包装在确实提供必要参数的定义中的表中。 这样,使用表的代码无需指定任何参数值。

    var testFunctions = [

    前几行只是不接受参数的函数的函数名称。 无需包装它们。

    {path: "visitorNames", func: visitorNames}, {path: "currentVisitorNames", func: currentVisitorNames}, {path: "nonCurrentVisitorNames", func: nonCurrentVisitorNames}, {path: "currentVisitorList", func: currentVisitorList}, {path: "currentVisitors", func: currentVisitors},

    / test / visitors路径实际上并不显示函数的输出,而是变量。 但是,使用该表的代码需要一个函数-因此我们将其包装在一个函数中。

    {path: "visitors", func: () => visitors},

    logIn和logOut函数确实获得一个参数,即用户名。 要运行此处需要的简单测试,我们使用固定的用户名。

    {path: "logIn", func: () => logIn("Avimelech ben-Gideon")}, {path: "logOut", func: () => logOut("Avimelech ben-Gideon")} ];

    这是注册处理程序的代码。 它在路径前加上/test/并创建一个函数,该函数从表中调用该函数,然后将响应发送到浏览器。

    testFunctions.map((item) => app.get( `/test/${item.path}`, /* @callback */ function(req, res) { res.send(item.func()); } ) );

    要实际测试该应用程序,请首先转到/ test / logOut以注销不存在的Avimelech ben-Gideon帐户,然后查看错误代码。 然后两次进入/ test / logIn 。 第一次尝试应该成功,而第二次失败。 然后两次进入/ test / logOut ,以查看第一次尝试成功,而第二次尝试失败。 执行完这些步骤之后,也许还要登录和注销几次,请转到/ test / visitors以查看访客日志的JSON数据。 最简单的方法是转到一个接受URL并为其提供访问者日志URL 的JSON格式化程序 。 最后,查看其他功能URL,例如/ test / currentVisitorNames ,以查看显示正确的信息。

    请注意,如果您在示例应用程序上查看这些路径,那么本文其他读者可能会提供过多的信息。

    用户界面

    将app.js中的代码替换为在此处找到的文件 。 然后转到您的应用程序( 或示例应用程序 )上的/ visitors以查看完整的访问者列表。 转到/ currentVisitors以查看当前列表。 使用/ login登录用户,然后使用/ logout注销用户 。 要查看整个用户界面,请转到/或/index.html 。

    第一个区别是该指令发送给Eslint,以忽略未使用的参数。 这是因为/* callback */指令(仅对此函数调用忽略未使用的参数)。

    /*eslint-disable no-unused-params */

    知道访客在办公室呆了多长时间很有用,但是很难解释一个数字(例如5000秒)。 此函数将以毫秒为单位的时差转换为可读的字符串。 输入的时间是毫秒,因为这是大多数与时间相关JavaScript函数的标准。

    // Given a time difference in miliseconds, return a string // with the approximate value var tdiffToString = (msec) => { var sec = msec/1000;

    如果秒数小于60,则返回正确标记的秒数。 使用三进制运算符可以为复数添加“ s”,也可以不添加。 该运算符采用三个参数。 如果第一个参数(少于2秒)为true,则返回第二个参数(空字符串)。 如果第一个参数为假,这意味着我们需要使用复数,则运算符将返回第三个参数,即复数“ s”。

    if (sec < 60) return sec + " second" + (sec < 2 ? "" : "s");

    如果秒数少于60,则该函数已经返回。 如果数字介于60到3600(一个小时)之间,则需要返回时间间隔中的分钟数。 注意使用Math.floor 。 这是必要的,因为我们要避免报告5.23544分钟之类的值。

    if (sec < 3600) return Math.floor(sec/60) + " minute" + (sec < 60*2 ? "" : "s");

    该功能的其余部分(持续数小时和数天)的运行方式相同。

    … };

    这是产生HTML的第一个函数。 用户界面在HTML表格中显示历史记录(到达,离开和中间时间),并且此函数在该表格中生成行。

    // Given a history entry (arrived and left times), create a table row with // that information var histEntryToRow = (entry) => { return `<tr> <td>${entry.arrived}</td> <td>${entry.left}</td> <td>${tdiffToString(entry.left-entry.arrived)}</td> </tr>`; };

    这个函数产生一个整个表(如果有什么要产生,否则它只返回一个空字符串)。

    // Given a history, create a table with it var histToTable = (history) => { if (history === undefined) return ""; if (history.length === 0) return "";

    人们并不总是希望看到历史。 通过将其包含在details标签中,我们可以隐藏历史记录,直到用户决定打开它为止。 style属性允许我们指定背景颜色,以使历史记录表与其余访客信息(也以表格形式显示)区分开。

    return `<details> <table border style="background-color: yellow"> <tr> <th> Arrived </th> <th> Left </th> <th> Time here </th> </tr> ${history.map(histEntryToRow).reduce((a, b) => a+b)} </table> </details>`; };

    接下来的两个函数userToRow和usersToTable与我们创建历史表的方式非常相似。 没有必要去解决它们。 接下来, visitorsHTML和currentVisitorsHTML添加标题,并使用适当的用户名列表创建表。 同样,这些功能是如此相似,足以看到其中之一。

    var visitorsHTML = () => { return ` <h2>Current Visitor List</h2> ${usersToTable(visitorNames())} `; };

    此功能创建一个登录表单。 它使用带有GET方法的HTML表单 。 这意味着将在URL中对参数(在这种情况下,只有一个user )进行编码。 login属性提供了处理程序的路径,您将在本文后面看到。

    var loginForm = () => { return `<h2>Log in a visitor</h2> <form method="get" action="login"> Visitor to log in: <input type="text" name="user"> </form>`; };

    登录表单和注销“表单”之间存在实质性差异。 无法预先知道要登录哪个访客。甚至可能是完全陌生的人。 但是,唯一可以注销的访问者是当前登录的访问者。这使我们可以显示列表并让用户从中进行选择。

    var logoutForm = () => { if (currentVisitorNames().length === 0) return "No users to log out"; return ` <h2>Log out a visitor</h2> <ul> ${currentVisitorNames()

    我决定不使用输入字段来创建表单,而是决定使用链接。 由于GET表单返回其参数的方式,我们可以这样做并模拟GET表单。 你有通常的URL(HTTP:// <主机名> / <路径>),后跟一个问号,然后<parameter>=<value>对,由符号(分隔& )。 该值可能包含在URL中具有特殊含义的字符(例如“&”号),因此会对其进行转义。 这是encodeURI函数的角色。

    .map(name => `<li> <a href="logout?user=${encodeURI(name)}">${name}</a> </li>`) .reduce((a,b) => a + b)} </ul>`; };

    此功能接收文本,例如上述函数生成HTML,并将其转换为完整的网页。

    var embedInHTML = (str) => { return `<html><body>${str}</body></html>`; };

    这两个调用调用适当的函数来创建标题和表,将其嵌入页面中,然后进行响应。

    app.get("/visitors", (req, res) => { res.send(embedInHTML(visitorsHTML())); }); app.get("/currentVisitors", (req, res) => { res.send(embedInHTML(currentVisitorsHTML())); });

    这两个调用是表单处理程序。 使用GET方法提交给表单的参数在req.query中可用。 如果没有用户,请发送表格。 如果有用户,请为该用户处理命令。

    app.get("/login", (req, res) => { if (req.query.user === undefined) res.send(embedInHTML(loginForm())); else res.send(logIn(req.query.user)); }); app.get("/logout", (req, res) => { if (req.query.user === undefined) res.send(embedInHTML(logoutForm())); else res.send(logOut(req.query.user)); });

    这个app.get调用显示了应用程序中的所有内容。 路径而不是路径,Express解释为“此处理程序适用于此列表中的所有内容”。

    app.get([“ / index.html”,“ /”],(req,res)=> {

    app.get(["/index.html", "/"], (req, res) => { res.send(embedInHTML(` ${loginForm()} <hr /> ${logoutForm()} <hr /> ${currentVisitorsHTML()} <hr /> ${visitorsHTML()} `)); });

    如果我们保留此调用,它将覆盖我们注册的处理程序-因此我将其注释掉。

    /* // serve the files out of ./public as our main files app.use(express.static(__dirname + '/public')); */

    数据库

    应用程序存在一个“轻微”问题。 每次重新启动它都会丢失访客日志。 为了解决这个问题,我们将访问者日志存储在Cloudant数据库中。 访客名称是关键,而数据(历史记录,到达时间和日期)是值。

    创建数据库

    请按照以下步骤创建数据库:

    在IBM Cloud控制台中 ,从汉堡菜单中单击“ 数据和分析” 。 单击创建资源,然后选择Cloudant NoSQL DB。 将服务visitor-log命名为,然后点击创建 。 创建服务后,单击左侧栏中的“ 服务凭据 ”。 单击新建凭据按钮。 将新的凭据visitor-log命名为,然后单击添加 。 在凭证列表中,点击查看凭证 。 单击复制/复制图标以复制凭据并将其粘贴到文本文件中。 在左侧边栏中,点击管理 。 然后,点击启动按钮。 在左侧边栏中,单击“数据库”图标。
    然后,单击右上角的创建数据库 。 将数据库命名为visitor-log 。 返回到应用程序开发环境并打开文件package.json 。 在依赖项中,为Cloudant软件包(版本1.7.x)添加一行。 完成后,您的文件将看起来像链接中的文件 。

    使用数据库

    要使用数据库,请用以下代码替换app.js。 它使用几乎相同的数据定义和用户界面,因此看起来不会有所不同。 但是,如果重新启动应用程序,则信息不会丢失。

    首先,为您的Cloudant凭证设置一个变量。 显然,您不能在GitHub中使用该值,因为每个用户的凭据都不相同,而且我也没有透露我的信息。

    var cloudantCred = { << redacted >> };

    使用此凭据连接到数据库管理系统,然后指定要使用的数据库:

    // Connect to the database var cloudant = require("cloudant")(cloudantCred.url); var mydb = cloudant.db.use("visitor-log");

    我们可以通过两种方式将信息存储在数据库中。 一种方法是将整个visitors数据结构放在一个数据库条目中(在Cloudant中称为文档)。 另一种方法是为每个访问者姓名准备一个单独的文档。 在这篇介绍性文章中,我选择使用第一种方法。 显然它的效率较低,但是最多可以使用1 MB 。 这种方法的主要优点是我们可以使用以前开发的应用程序逻辑,而几乎无需更改。 我们可以继续将visitors数据结构保持为变量。

    同一Cloudant数据库可以同时由多个应用程序使用。 为了避免一个应用程序干扰另一个应用程序,它使用修订系统。 每个文档的当前修订版都有一个_rev值。 更新文档时,您将提供要更新的修订。 如果从您阅读之日起它已更改,则修订版本会有所不同,从而导致更新失败-这要求我们有一个变量来保存当前修订版本。

    var dbRev = "";

    此功能更新数据库上的访客日志。

    var updateVisitors = () => {

    要更新的数据始终包含_id ,键和实际值。 它通常还包含_rev ,修订版。 创建新条目时例外。

    var updateData = { "_id": "visitors", value: visitors }; if (dbRev !== "") updateData["_rev"] = dbRev;

    准备好更新哈希表后,我们可以使用<database>.insert函数将最新版本插入数据库。

    mydb.insert(updateData, (err, body) => {

    如果插入成功,则响应正文将包括新的修订值。

    if (err === undefined) dbRev = body["rev"];

    这些行记录数据库故障。 本文稍后将介绍用于此目的的机制。

    else log += "updateVisitors: " + err + ""; } ); };

    启动应用程序时,代码中的后几行使用<database>.get函数读取visitors 。

    // Read the visitors data structure. mydb.get("visitors", (err, body) => {

    如果我们收到404错误,则意味着数据库中尚无visitors数据结构。 在这种情况下,我们创建一个并调用updateVisitors 。 这是在没有修订的情况下调用database.insert函数的一种情况。

    // No visitors yet, create it. if (err !== null && err.statusCode === 404) { visitors = {"Test user": {arrived: Date.now().valueOf()}}; updateVisitors(); } else {

    如果有值,请使用它,并在updateVisitors需要它时保留修订。

    visitors = body.value; dbRev = body["_rev"]; } });

    在应用程序启动后,直接更改访问者信息的唯一功能是setVisitor 。 它需要调用updateVisitors将更改保存在数据库中。

    var setVisitor = (name, values) => { visitors[name] = values; updateVisitors(); };

    时间和日期表示

    还有另外一个问题。 由于与Cloudant的通信是基于文本的,因此在写入Cloudant时,Date对象将转换为其文本表示形式,即带有日期,时间和时区的字符串。 然后,当信息被读回时,它们被视为字符串。 如果我们只想显示日期,那还不错,但是我们也希望用户看到每次访问的时间。

    为了解决此问题,我们存储的是自纪元开始以来的毫秒数(任意时间点,通常在UNIX派生的系统上设置为1月的午夜),而不是在visitors数据结构和数据库中存储Date对象。 1970年1月1日,格林尼治标准时间)。 存储毫秒数需要对程序进行一些更改:

    首先,我们获取当前日期的每个地方都必须是Date.now().valueOf() 。 调用valueOf()会将日期对象转换为自纪元开始以来的毫秒数。 例如,在logout :

    history.unshift({ arrived: oldRecord.arrived, left: Date.now().valueOf() });

    显示日期和时间戳的功能也需要更改。 为了将以毫秒为单位的时间转换为字符串,我们在一个函数histEntryToRow创建了一个新的Date对象,以毫秒为单位:

    // Given a history entry (arrived and left times), create a table row // with that information var histEntryToRow = (entry) => { return `<tr> <td>${new Date(entry.arrived)}</td> <td>${new Date(entry.left)}</td> <td>${tdiffToString(entry.left-entry.arrived)}</td> </tr>`; // The Date need to be new, otherwise we are just // modifying the same object and all dates in // the history table are the same. };

    出于调试目的记录

    在开发此应用程序时,我需要了解为什么有时数据库更新无法正常工作。 为此,我创建了一个日志字符串,并在每次更新失败时将其附加到该字符串。 该技术在Web应用程序中也很有用(确保在拥有生产数据之前将其禁用或要求提供密码)。

    need to be new, otherwise we are just var log = ""; var updateVisitors = () => { ... mydb.insert(updateData, (err, body) => { if (err === undefined) dbRev = body["rev"]; else

    当我们添加到日志时,请添加<br/> 。 该字符串将由浏览器检索,浏览器默认将其解释为HTML。

    log += "updateVisitors: " + err + ""; } ); };

    要访问日志,请转到/ log路径:

    app.get("/log", (req, res) => { res.send(log); });

    结论

    现在,您可以在IBM Cloud上编写简单的Node.js Web应用程序,并将数据存储在Cloudant中。 此Web应用程序的主要问题是它在视觉上没有吸引力。 每次更改都需要刷新整个页面,它是纯HTML。

    在本系列的下一篇文章中,我将向您展示如何使用Bootstrap主题使Web应用程序看起来更好,以及如何使用AngularJS库使它们更具响应性。


    翻译自: https://www.ibm.com/developerworks/security/library/se-bluemix-self-posting-app-mean-stack-part1/index.html

    Processed: 0.012, SQL: 9