node开发web程序---05存储node程序的数据

无服务器的数据存储

  • 从系统管理的角度来看,最方便的存储机制是那些不用维护DBMS的存储,比如内存存储和 基于文件的存储。
  • 特别适合命令行界面(CLI)工具:Node驱动的CLI工具很可能要存储数据,但用户不太可能为了用这个工具而再去大 费周章地搭一个MySQL服务器。
  1. 内存存储
  • 内存存储用变量存放数据。这种数据的读取和写入都很快,但就像我们在前面提过的,服务器和程 序重启后数据就丢了。
  • 内存存储的理想用途是存放少量经常使用的数据。
  1. 基于文件的存储
  • 发人员经常用这种存储方式保存程序的配置信息,但你也可以用它做数据的持久化保存,这些数据在程序和服务器重启后依然有效。
  • 并发问题: 基于文件的存储虽然易用,但并不是所有程序都适合。比如说,一个多用户程序如果把记 录保存在一个文件中,可能会碰到并发问题。两个用户可能会同时加载相同的文件进行修改。 保存一个版本会覆盖另外一个,导致其中某个用户的修改丢失。对于多用户程序而言,数据库 管理系统是更合理的选择,因为它们就是为应对并发问题而生的。
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社区对它的支持很好。

  1. 用MySQL构建一个工作跟踪程序

根据需求分解任务步骤,准备构建工作记录程序,构建工作记录程序需要完成下面这几项任务:

1. 创建程序逻辑;
2. 创建程序工作所需的辅助函数;
3. 编写让你可以用MySQL添加、删除、更新和获取数据的函数;
4. 编写渲染HTML记录和表单的代码。
复制代码

这个Web程序可以用文件系统做简单的数据存储,但那样用数据做报表时会比较复杂。比如 你想创建一个上周所做工作的报表,就必须读出所有保存下来的工作记录并检查记录的日期。如 果把程序数据放到RDBMS中,用SQL查询生成报表很容易。

这个程序会用Node内置的http模块实现Web服务器的功能,用一个第三方模块跟MySQL服务器交互。一个名为timetrack的定制模块,它是程序特有的函数,用来在MySQL中存储、修改和获取数据。

工作记录程序结构如下图:

工作记录程序效果图:

接下来开始书写程序:

  1. 创建程序的逻辑

接下来需要创建两个文件存放程序逻辑。这个两个文件分别是:timetrack_server.js,用来启动程序; timetrack.js,包含程序相关功能的模块。

  1. 创建辅助函数发送HTML,创建表单,接收表单数据

定义辅助函数,用来发送Web页面HTML,接收通过表单提交的数据。

  1. 用MySQL增删改查数据

定义辅助函数,增删改查mysql数据库。

  1. 渲染MySQL记录

定义辅助函数,将工作记录渲染为html表格。

  1. 渲染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,选择关系型数据库还是非关系型数据库,需要根据项目需要选择,数据库选得越接近项目,程序实现起来越轻松。

转载于:https://juejin.im/post/5c37fd8351882526205820d2

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值