node.js前台调用js_使用Node.js创建前台访客日志

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

构建应用程序所需的条件

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

运行应用程序 获取代码

入门

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

入门

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

创建应用程序

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

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

图1.初始应用
初次申请

创建开发环境

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

  1. 返回到该应用程序的IBM Console页面。 如果您已注销,请重新登录并在仪表板上单击应用程序的名称。
  2. 向下滚动至连续投放,然后点击启用
  3. 要接受默认值,请单击创建
  4. 创建工具链后,单击Eclipse Orion Web IDE
  5. 单击创建新的启动配置> +
  6. 单击“ 保存”以创建默认的启动配置。 它具有我们需要的一切。
  7. 单击访问者日志>app.js
  8. 此处的代码替换文件的内容。
  9. 单击播放图标以应用更改。
  10. 等到再次看到绿色圆圈。 这意味着应用程序已使用新代码重新启动。
  11. 返回到应用程序选项卡,并在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.默认的访问者日志
idefaultvisitorlog

您可以在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},

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

{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>`;
};

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

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数据库中。 访客名称是关键,而数据(历史记录,到达时间和日期)是值。

创建数据库

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

  1. IBM Cloud控制台中 ,从汉堡菜单中单击“ 数据和分析”
  2. 单击创建资源,然后选择Cloudant NoSQL DB。
  3. 将服务visitor-log命名为,然后点击创建
  4. 创建服务后,单击左侧栏中的“ 服务凭据 ”。
  5. 单击新建凭据按钮。
  6. 将新的凭据visitor-log命名为,然后单击添加
  7. 在凭证列表中,点击查看凭证 。 单击复制/复制图标以复制凭据并将其粘贴到文本文件中。
  8. 在左侧边栏中,点击管理 。 然后,点击启动按钮。
  9. 在左侧边栏中,单击“数据库”图标。
    数据库图标
  10. 然后,单击右上角的创建数据库
  11. 将数据库命名为visitor-log
  12. 返回到应用程序开发环境并打开文件package.json
  13. 在依赖项中,为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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值