无服务器的数据存储
- 从系统管理的角度来看,最方便的存储机制是那些不用维护DBMS的存储,比如内存存储和 基于文件的存储。
- 特别适合命令行界面(CLI)工具:Node驱动的CLI工具很可能要存储数据,但用户不太可能为了用这个工具而再去大 费周章地搭一个MySQL服务器。
- 内存存储
- 内存存储用变量存放数据。这种数据的读取和写入都很快,但就像我们在前面提过的,服务器和程 序重启后数据就丢了。
- 内存存储的理想用途是存放少量经常使用的数据。
- 基于文件的存储
- 发人员经常用这种存储方式保存程序的配置信息,但你也可以用它做数据的持久化保存,这些数据在程序和服务器重启后依然有效。
- 并发问题: 基于文件的存储虽然易用,但并不是所有程序都适合。比如说,一个多用户程序如果把记 录保存在一个文件中,可能会碰到并发问题。两个用户可能会同时加载相同的文件进行修改。 保存一个版本会覆盖另外一个,导致其中某个用户的修改丢失。对于多用户程序而言,数据库 管理系统是更合理的选择,因为它们就是为应对并发问题而生的。
var fs = require('fs');
var path = require('path');
var args = process.argv.splice(2);
console.log(1,args)
var command = args.shift();
console.log(2,command)
var taskDescription = args.join(' ');
console.log(3,taskDescription)
var file = path.join(process.cwd(), '/.tasks');
console.log(4,file)
switch(command) {
case 'list':
listTasks(file);
break;
case 'add':
addTask(file, taskDescription);
break;
default:
console.log('Usage: ' + process.argv[0]
+ ' list|add [taskDescription]');
}
function loadOrInitializeTaskArray(file, cb) {
fs.exists(file, function(exists) {
var tasks = [];
if (exists) {
fs.readFile(file, 'utf8', function(err, data) {
if (err) throw err;
var data = data.toString();
var tasks = JSON.parse(data || '[]');
cb(tasks);
});
} else {
cb([]);
}
});
}
function listTasks(file) {
loadOrInitializeTaskArray(file, function(tasks) {
for(var i in tasks) {
console.log(tasks[i]);
}
});
}
function storeTasks(file, tasks) {
fs.writeFile(file, JSON.stringify(tasks), 'utf8', function(err) {
if (err) throw err;
console.log('Saved.');
});
}
function addTask(file, taskDescription) {
loadOrInitializeTaskArray(file, function(tasks) {
tasks.push(taskDescription);
storeTasks(file, tasks);
});
}
复制代码
这段代码用到了console打印输出法,调试node代码,有点麻烦,这里给一个node代码调试方法,让你在调试node代码的时候更省心。 手把手教你vscode调试node
关系型数据库管理系统
mysql
MySQL是最流行的SQL数据库,Node社区对它的支持很好。
- 用MySQL构建一个工作跟踪程序
根据需求分解任务步骤,准备构建工作记录程序,构建工作记录程序需要完成下面这几项任务:
1. 创建程序逻辑;
2. 创建程序工作所需的辅助函数;
3. 编写让你可以用MySQL添加、删除、更新和获取数据的函数;
4. 编写渲染HTML记录和表单的代码。
复制代码
这个Web程序可以用文件系统做简单的数据存储,但那样用数据做报表时会比较复杂。比如 你想创建一个上周所做工作的报表,就必须读出所有保存下来的工作记录并检查记录的日期。如 果把程序数据放到RDBMS中,用SQL查询生成报表很容易。
这个程序会用Node内置的http模块实现Web服务器的功能,用一个第三方模块跟MySQL服务器交互。一个名为timetrack的定制模块,它是程序特有的函数,用来在MySQL中存储、修改和获取数据。
工作记录程序结构如下图:
工作记录程序效果图:
接下来开始书写程序:
- 创建程序的逻辑
接下来需要创建两个文件存放程序逻辑。这个两个文件分别是:timetrack_server.js,用来启动程序; timetrack.js,包含程序相关功能的模块。
- 创建辅助函数发送HTML,创建表单,接收表单数据
定义辅助函数,用来发送Web页面HTML,接收通过表单提交的数据。
- 用MySQL增删改查数据
定义辅助函数,增删改查mysql数据库。
- 渲染MySQL记录
定义辅助函数,将工作记录渲染为html表格。
- 渲染HTML表单
定义辅助函数,渲染增、删、改的html表单。
代码如下:
timetrack_server.js文件:
var http = require('http');
var work = require('./lib/timetrack');
var mysql = require('mysql');
var db = mysql.createConnection({
host: '127.0.0.1',
user: 'myuser',
password: 'mypassword',
database: 'timetrack'
});
var server = http.createServer(function(req, res) {
switch (req.method) {
case 'POST':
switch(req.url) {
case '/':
work.add(db, req, res);
break;
case '/archive':
work.archive(db, req, res);
break;
case '/delete':
work.delete(db, req, res);
break;
}
break;
case 'GET':
switch(req.url) {
case '/':
work.show(db, res);
break;
case '/archived':
work.showArchived(db, res);
}
break;
}
});
db.query(
"CREATE TABLE IF NOT EXISTS work ("
+ "id INT(10) NOT NULL AUTO_INCREMENT, "
+ "hours DECIMAL(5,2) DEFAULT 0, "
+ "date DATE, "
+ "archived INT(1) DEFAULT 0, "
+ "description LONGTEXT,"
+ "PRIMARY KEY(id))",
function(err) {
if (err) throw err;
console.log('Server started...');
server.listen(3000, '127.0.0.1');
}
);
复制代码
timetrack.js文件:
var qs = require('querystring');
exports.sendHtml = function(res, html) {
res.setHeader('Content-Type', 'text/html');
res.setHeader('Content-Length', Buffer.byteLength(html));
res.end(html);
}
exports.parseReceivedData = function(req, cb) {
var body = '';
req.setEncoding('utf8');
req.on('data', function(chunk){ body += chunk });
req.on('end', function() {
var data = qs.parse(body);
cb(data);
});
};
exports.actionForm = function(id, path, label) {
var html = '<form method="POST" action="' + path + '">' +
'<input type="hidden" name="id" value="' + id + '">' +
'<input type="submit" value="' + label + '" />' +
'</form>';
return html;
};
exports.add = function(db, req, res) {
exports.parseReceivedData(req, function(work) {
db.query(
"INSERT INTO work (hours, date, description) " +
" VALUES (?, ?, ?)",
[work.hours, work.date, work.description],
function(err) {
if (err) throw err;
exports.show(db, res);
}
);
});
};
exports.delete = function(db, req, res) {
exports.parseReceivedData(req, function(work) {
db.query(
"DELETE FROM work WHERE id=?",
[work.id],
function(err) {
if (err) throw err;
exports.show(db, res);
}
);
});
};
exports.archive = function(db, req, res) {
exports.parseReceivedData(req, function(work) {
db.query(
"UPDATE work SET archived=1 WHERE id=?",
[work.id],
function(err) {
if (err) throw err;
exports.show(db, res);
}
);
});
};
exports.show = function(db, res, showArchived) {
var query = "SELECT * FROM work " +
"WHERE archived=? " +
"ORDER BY date DESC";
var archiveValue = (showArchived) ? 1 : 0;
db.query(
query,
[archiveValue],
function(err, rows) {
if (err) throw err;
html = (showArchived)
? ''
: '<a href="/archived">Archived Work</a><br/>';
html += exports.workHitlistHtml(rows);
html += exports.workFormHtml();
exports.sendHtml(res, html);
}
);
};
exports.showArchived = function(db, res) {
exports.show(db, res, true);
};
exports.workHitlistHtml = function(rows) {
var html = '<table>';
for(var i in rows) {
html += '<tr>';
html += '<td>' + rows[i].date + '</td>';
html += '<td>' + rows[i].hours + '</td>';
html += '<td>' + rows[i].description + '</td>';
if (!rows[i].archived) {
html += '<td>' + exports.workArchiveForm(rows[i].id) + '</td>';
}
html += '<td>' + exports.workDeleteForm(rows[i].id) + '</td>';
html += '</tr>';
}
html += '</table>';
return html;
};
exports.workFormHtml = function() {
var html = '<form method="POST" action="/">' +
'<p>Date (YYYY-MM-DD):<br/><input name="date" type="text"><p/>' +
'<p>Hours worked:<br/><input name="hours" type="text"><p/>' +
'<p>Description:<br/>' +
'<textarea name="description"></textarea></p>' +
'<input type="submit" value="Add" />' +
'</form>';
return html;
};
exports.workArchiveForm = function(id) {
return exports.actionForm(id, '/archive', 'Archive');
}
exports.workDeleteForm = function(id) {
return exports.actionForm(id, '/delete', 'Delete');
}
复制代码
PostgreSQL
PostgreSQL因其与标准的兼容性和健壮性受到认可,很多Node开发人员对它的喜爱程度超过了其他的RDBMS。不像MySQL,PostgreSQL支持递归查询和很多特殊的数据类型。PostgreSQL还能使用一些标准的认证方法,比如轻量目录访问协议(LDAP)和通用安全服务应用程序接口(GSSAPI)。对于要借助数据复制实现扩展能力或冗余性的那些人来说,PostgreSQL支持同步复制,这种复制形态会在每次数据操作后对复制进行验证,从而防止数据丢失。
暂不做详细介绍。。。目前尚未用到。。。
如需学习请参考《Node in Action》的5.2.2节。
NoSQL 数据库
在数据库世界刚具雏形之时,非关系型数据库才是标准。但关系型数据库渐渐兴起,成为主流选择,在不在Web上的程序都会用它。最近几年,非关系型DBMS隐隐有复兴之势,其支持者宣称它们在能力扩展和易用性上比关系型数据库有优势,并且这些DBMS可以应对多种应用场景。大家将它们称为“NoSQL”数据库,即“No SQL” 或“Not Only SQL”。
尽管关系型DBMS为可靠性牺牲了性能,但很多NoSQL数据库把性能放在了第一位。因此,对于实时分析或消息传递而言,NoSQL数据库可能是更好的选择。
两个流行的NoSQL数据库:Redis 和 MongoDB。这里不做介绍,需要学习的可参考《Node in Action》的5.3节。
有很多人直接把node和mongoDB捆绑起来,完全是错误的认知,只要一提到要为node做数据存储,就是mongoDB,选择关系型数据库还是非关系型数据库,需要根据项目需要选择,数据库选得越接近项目,程序实现起来越轻松。