express-17 持久化
简介
- 所有网站和Web应用程序(除了最简单的)都需要某种持久化方式,即某种比易失性内存更持久的数据存储方式,这样当遇到服务器宕机、断电、升级和迁移等情况时数据才能保存下来。
文件系统持久化
实现持久化的一种方式是将数据存到扁平文件中(“扁平”的意思是文件没有内在结构,只是一串字节)。Node通过
fs(文件系统)模块实现文件系统持久化。文件系统持久化有一些不足之处,特别是它的扩展性不好。
当需要不止一台服务器以满足流量的需求时,除非所有服务器都能访问一个共享的文件系统,否则就会遇到文件系统持久化的问题。
此外,因为扁平文件没有内在结构,定位、排序和过滤数据就变成了应用程序的负担; 出于这些原因,应该用数据库而不是文件系统来做数据排序。
排序二进制文件是个例外,比如图片、音频文件或视频。尽管很多数据库可以处理这类数据,但极少能达到文件系统那种效率。
如果确实需要存储二进制数据,且主机不能访问共享的文件系统(一般是这样);应该考虑将二进制文件存在数据库中(一般要做些配置,以免数据库被拖垮),或者基于云的存储服务。
云持久化
数据库持久化
- 两种最流行的NoSQL数据库是文档数据库和键-值数据库。
- 文档数据库善于存储对象,这使得它们非常适合Node和JavaScript。
- 键-值数据库如其名所示,极其简单,对于数据模式可以轻松映射到键-值对的程序来说是很好的选择。
关于性能
- 关系型数据库传统上依赖于它们严格的数据结构和几十年的优化研究而取得高性能。
- 另一方面,NoSQL数据库像Node一样,接受了互联网分布式的本性, 专注于用并发来扩展性能(关系型数据库也支持并发,但一般只用于最有需要的应用程序)。
设置MongoDB
设置MongoDB实例的困难之处会随操作系统而变化。为了避开各种问题,可以选择免费的MongoDB托管服务MongoLab。
到个人主页。在数据库下面,点击“新建”,然后进入新建数据库的页面。首先要选的是云提供商。对于免费(沙盒)账号而言,选什么无关紧要,不过应该找一个离你近的数据中心。选择“单节点(开发)”和沙盒。可以选择自己要用的MongoDB版本。最后,选择数据库名称,然后点击“新建MongoDB部署”。
Mongoose
JavaScript的优势之一是它的对象模型极其灵活。如果想给一个对象添加属性或方法,尽管去做,并且不用担心要修改类。
可惜,那种随心所欲的灵活性可能会对数据库产生负面影响,因为它们会变得零碎和难以调优。
Mongoose试图确立平衡,它引入了模式和模型(联合的,模式和模型类似于传统面向对象编程中的类)。模式很灵活,但仍为数据库提供了一些必要的结构。
在开始之前,我们要先把Mongoose模块装上:
npm install --save mongoose
- 然后将数据库凭证添加到credentials.js文件里:
mongo: {
development: {
connectionString: 'your_dev_connection_string',
},
production: {
connectionString: 'your_production_connection_string',
},
},
- 注意,这里存了两组凭证:一个用于开发,一个用于生产。可以现在设置两个数据库,或者将两个指向同一个数据库(等正式启用的时候,可以转换成使用两个单独的数据库)。
使用Mongoose连接数据库
- 先从创建数据库的连接开始:
var mongoose = require('mongoose');
var opts = {
server: {
socketOptions: { keepAlive: 1 }
}
};
switch(app.get('env')){
case 'development':
mongoose.connect(credentials.mongo.development.connectionString, opts);
break;
case 'production':
mongoose.connect(credentials.mongo.production.connectionString, opts);
break;
default:
throw new Error('Unknown execution environment: ' + app.get('env'));
}
opts对象是可选的,但我们想指定keepAlive选项,以防止长期运行的应用程序(比如网站)出现数据库连接错误。
创建模式和模型
- 接下来为草地鹨旅行社创建一个度假包数据库。先从定义模式和模型开始。创建文件models/vacation.js:
var mongoose = require('mongoose');
var vacationSchema = mongoose.Schema({
name: String,
slug: String,
category: String,
sku: String,
description: String,
priceInCents: Number,
tags: [String],
inSeason: Boolean,
available: Boolean,
requiresWaiver: Boolean,
maximumGuests: Number,
notes: String,
packagesSold: Number,
});
vacationSchema.methods.getDisplayPrice = function(){
return '$' + (this.priceInCents / 100).toFixed(2);
};
var Vacation = mongoose.model('Vacation', vacationSchema);
module.exports = Vacation;
由于浮点数的特质,在JavaScript中涉及金融计算时要谨慎。以美分为单位存储价格有帮助,但并不能根除这个问题。在下一版的JavaScript (ES6)中会有个适合做金融计算的decimal类型。
输出了Mongoose创建的Vacation模型对象。要在程序中使用这个模型,我们可以像下面这样引入它:
var Vacation = require('./models/vacation.js');
添加初始数据
Vacation.find(function(err, vacations){
if(vacations.length) return;
new Vacation({
name: 'Hood River Day Trip',
slug: 'hood-river-day-trip',
category: 'Day Trip',
sku: 'HR199',
description: 'Spend a day sailing on the Columbia and ' +
'enjoying craft beers in Hood River!',
priceInCents: 9995,
tags: ['day trip', 'hood river', 'sailing', 'windsurfing', 'breweries'],
inSeason: true,
maximumGuests: 16,
available: true,
packagesSold: 0,
}).save();
new Vacation({
name: 'Oregon Coast Getaway',
slug: 'oregon-coast-getaway',
category: 'Weekend Getaway',
sku: 'OC39',
description: 'Enjoy the ocean air and quaint coastal towns!',
priceInCents: 269995,
tags: ['weekend getaway', 'oregon coast', 'beachcombing'],
inSeason: false,
maximumGuests: 8,
available: true,
packagesSold: 0,
}).save();
new Vacation({
name: 'Rock Climbing in Bend',
slug: 'rock-climbing-in-bend',
category: 'Adventure',
sku: 'B99',
description: 'Experience the thrill of climbing in the high desert.',
priceInCents: 289995,
tags: ['weekend getaway', 'bend', 'high desert', 'rock climbing'],
inSeason: true,
requiresWaiver: true,
maximumGuests: 4,
available: false,
packagesSold: 0,
notes: 'The tour guide is currently recovering from a skiing accident.',
}).save();
});
- 这里用到了两个Mongoose方法。
find和save
获取数据
- 给产品页创建个视图,views/vacations.handlebars:
<h1>Vacations</h1>
{{#each vacations}}
<div class="vacation">
<h3>{{name}}</h3>
<p>{{description}}</p>
{{#if inSeason}}
<span class="price">{{price}}</span>
<a href="/cart/add?sku={{sku}}" class="btn btn-default">Buy Now!</a>
{{else}}
<span class="outOfSeason">We're sorry, this vacation is currently not in season.
{{! The "notify me when this vacation is in season"
page will be our next task. }}
<a href="/notify-me-when-in-season?sku={{sku}}">Notify me when this vacation is in season.</a>
{{/if}}
</div>
{{/each}}
- 创建路由处理器把它全串起来:
app.get('/vacations', function(req, res){
Vacation.find({ available: true }, function(err, vacations){
var context = {
vacations: vacations.map(function(vacation){
return {
sku: vacation.sku,
name: vacation.name,
description: vacation.description,
price: vacation.getDisplayPrice(),
inSeason: vacation.inSeason,
}
})
};
res.render('vacations', context);
});
});
- 不要将未映射的数据库对象直接传给视图。视图会得到一堆它可能不需要的属性,并且可能是以它不能兼容的格式。
添加数据
- 首先要创建模式和模型(models/vacationInSeasonListener.js):
var mongoose = require('mongoose');
var vacationInSeasonListenerSchema = mongoose.Schema({
email: String,
skus: [String],
});
var VacationInSeasonListener = mongoose.model('VacationInSeasonListener',
vacationInSeasonListenerSchema);
module.exports = VacationInSeasonListener;
- 然后创建视图,views/notify-me-when-in-season.handlebars:
<div class="formContainer">
<form class="form-horizontal newsletterForm" role="form"
action="/notify-me-when-in-season" method="POST">
<input type="hidden" name="sku" value="{{sku}}">
<div class="form-group">
<label for="fieldEmail" class="col-sm-2 control-label">Email</label>
<div class="col-sm-4">
<input type="email" class="form-control" required id="fieldName" name="email">
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-4">
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
</form>
</div>
- 最后是路由处理器:
var VacationInSeasonListener = require('./models/vacationInSeasonListener.js');
app.get('/notify-me-when-in-season', function(req, res){
res.render('notify-me-when-in-season', { sku: req.query.sku });
});
app.post('/notify-me-when-in-season', function(req, res){
VacationInSeasonListener.update(
{ email: req.body.email },
{ $push: { skus: req.body.sku } },
{ upsert: true },
function(err){
if(err) {
console.error(err.stack);
req.session.flash = {
type: 'danger',
intro: 'Ooops!',
message: 'There was an error processing your request.',
};
return res.redirect(303, '/vacations');
}
req.session.flash = {
type: 'success',
intro: 'Thank you!',
message: 'You will be notified when this vacation is in season.',
};
return res.redirect(303, '/vacations');
}
);
});
- Mongoose方便的upsert(“更新”和“插入”的混成词), 我们能在VacationInSeasonListener还不存在的时候更新其中的记录。
用MongoDB存储会话数据
- 用session-mongoose包提供MongoDB会话存储。
npm install --save session-mongoose:
var MongoSessionStore = require('session-mongoose')(require('connect'));
var sessionStore = new MongoSessionStore({ url: credentials.mongo.connectionString });
app.use(require('cookie-parser')(credentials.cookieSecret));
app.use(require('express-session')({ store: sessionStore }));
- 想要用不同的币种显示度假产品的价格。此外,我们还希望网站记住用户偏好的币种; 先要在度假产品页面底部添加一个币种选择器:
<hr>
<p>Currency:
<a href="/set-currency/USD" class="currency {{currencyUSD}}">USD</a> |
<a href="/set-currency/GBP" class="currency {{currencyGBP}}">GBP</a> |
<a href="/set-currency/BTC" class="currency {{currencyBTC}}">BTC</a>
</p>
然后是一点CSS:
最后我们会添加路由处理器来设定币种,并修改/vacations的路由处理器来用当前币种显示价格
app.get('/set-currency/:currency', function(req,res){
req.session.currency = req.params.currency;
return res.redirect(303, '/vacations');
});
function convertFromUSD(value, currency){
switch(currency){
case 'USD': return value * 1;
case 'GBP': return value * 0.6;
case 'BTC': return value * 0.0023707918444761;
default: return NaN;
}
}
app.get('/vacations', function(req, res){
Vacation.find({ available: true }, function(err, vacations){
var currency = req.session.currency || 'USD';
var context = {
currency: currency,
vacations: vacations.map(function(vacation){
return {
sku: vacation.sku,
name: vacation.name,
description: vacation.description,
inSeason: vacation.inSeason,
price: convertFromUSD(vacation.priceInCents/100, currency),
qty: vacation.qty,
}
})
};
switch(currency){
case 'USD': context.currencyUSD = 'selected'; break;
case 'GBP': context.currencyGBP = 'selected'; break;
case 'BTC': context.currencyBTC = 'selected'; break;
}
res.render('vacations', context);
});
});
- MongoDB不一定是会话存储的最佳选择,它有点杀鸡用牛刀的意味。另外一个流行又易用的会话持久化方案是用Redis。请参阅connect-redis包来了解如何设置使用Redis做会话存储。
express-17 持久化的更多相关文章
- redis 系列17 持久化 AOF
一.概述 除了上篇介绍的RDB持久化功能之外,Redis还提供了AOF(Append Only File)持久化功能.与RDB保存数据库中的键值对来记录数据库状态不同,AOF是通过保存redis服务器 ...
- 为elasticSearch开发c++接口
一. ElasticSearch是什么 ElasticSearch是目前开源全文搜索引擎的首选,可以快速存储,搜索和分析海量数据.Stack Overflow,Github等都在使用. Elas ...
- 33个与众不同的Web表单设计
表单在web设计中很重要,因为它具有直接的用户交互.创新?有趣?富有色彩?设计一个交互,需要设计师关注登陆/注册表单的设计元素. 这里有33个与众不同的web表单设计,希望能使你获得设计灵感. 1. ...
- java持续添加内容至本地文件
package com.lcc.commons; import com.lcc.commons.dto.FileLogDTO; import java.io.*; import java.util.A ...
- 【目录】redis 系列篇
随笔分类 - redis 系列篇 redis 系列27 Cluster高可用 (2) 摘要: 一. ASK错误 集群上篇最后讲到,对于重新分片由redis-trib负责执行,关于该工具以后再介绍.在进 ...
- Spring Data JPA实体的生命周期总结
目录 四种状态 API示例 persist remove merge refresh 参考链接 四种状态 首先以一张图,简单介绍写实体生命周期中四种状态之间的转换关系: 瞬时(New):瞬时对象,刚N ...
- [转]在nodejs使用Redis缓存和查询数据及Session持久化(Express)
本文转自:https://blog.csdn.net/wellway/article/details/76176760 在之前的这篇文章 在ExpressJS(NodeJS)中设置二级域名跨域共享Co ...
- 在nodejs使用Redis缓存和查询数据及Session持久化(Express)
在nodejs使用Redis缓存和查询数据及Session持久化(Express) https://segmentfault.com/a/1190000002488971
- 17 ~ express ~ 分类的显示 ,修改 和 删除
一,前台显示 /views/admin/category.html {% extends 'layout.html' %} {% block main %} <ol class="br ...
随机推荐
- iOS block 的底层实现
其实swift 的闭包跟 OC的block 是一样一样的,学会了block,你swift里边的闭包就会无师自通. 参考:http://www.jianshu.com/p/e23078c11518 ht ...
- WIN7 64位系统下,右下角的声音和电源图标不见的解决办法
近日,电脑突然出现任务栏右下角的声音和电源图标消失不见的问题,重启仍旧没有修复,后来找到了解决办法 解决办法: 1.Ctrl+Shift+Esc键调出windows资源管理器. 2.找到进程中的exp ...
- balabalabala
[微分享]:种子不落在肥土而落在瓦砾中,有生命力的种子决不会悲观和叹气,因为有了阻力才有磨炼.
- 3ds max画曲线 设置摄像机的起始位置
参考 http://www.3dmax8.com/3dmax/2013/0916/5661.html 如果想创建曲线段,可以在单击下一个点时按住鼠标不放,继续拖曳,再拖到另一个点上,单击鼠标右键,即可 ...
- 最简单的Web服务器
//读取浏览器发过来的内容Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, Protoco ...
- XMPP框架下微信项目总结(8)图片发送
前言:“图片”发送和“聊天文本”都是通过模块发起的成为:“消息模块”(反正传递的都是字符串) 发送原理: 1 current客户端获取本地图片 2 xmpp发送“字符串”(为什么是字符串?1: ...
- MongoDB基本命令
1. 启动和停止MongoDB: 执行mongod命令启动MongoDB服务器.mongod有很多可配置的选项,我们通过mongod --help可以查看所有选项,这里仅介绍一些主要选项: - ...
- vim: vs sp 调整窗口高度和宽度
转自:http://www.cnblogs.com/xuechao/archive/2011/03/29/1999292.html vim多窗口有时候需要调整默认的窗口宽度和高度,可以用如下命令配合使 ...
- .net学习之Attribute特性和EF关键知识点
一.Attribute特性/标签1.Attribute用来对类.属性.方法等标注额外的信息,贴一个标签简单的说,定制特性Attribute,本质上就是一个类,它为目标元素提供关联附加信息,并在运行时以 ...
- 在ubuntu上搭建开发环境8---Ubuntu搭建Android开发环境
需要首先配置好JDK环境 参看:http://www.cnblogs.com/xumenger/p/4460055.html 安装Eclipse 在Android developer的官网上直接下载a ...