搭建官方项目模板步骤:

1、npm install vue-cli (安装vue-cli )

2、vue init webpack-simple yourdemoname 下载一个webpack-simple项目,这里的webpack-simple 是固定的,也就是官网的项目模板。youdemoname 这个是你自己项目的名字。 执行这个步骤以后。就会弹出询问 “项目名称..项目描述“等等问题 直接按照提示操作。这个时候对应的项目目录下就出现刚刚建立的项目了。

3、我们还需要把项目的依赖下载下来。使用命令: cd youdemoname 然后执行npm install 就可以了,这个时候你的项目中有多了一个node_modules 目录

4、使用”npm - run - dev” 命令来运行项目 “npm-run-bulid” 来执行发布,会自动生成dist文件

5、如果你对官方的模板不感兴趣,你可以自己fork下来然后进行修改(或者重新写一个),然后用 vue-cli 来调用。因为 vue-cli 可以直接拉取 git源 :

vue init username/repo my-project

webpack+vue-cli项目打包技巧 http://www.cnblogs.com/dupd/p/6075775.html

weex提供的只是一个便于开发原生app的框架,而不是组件库,目前只提供了布局和几个基本的原生组件,更多的还需要开发者们为weex的市场做贡献。当然现在weex的市场还只有两个module。

weex在提高app开发效率的基础上,更多的是注重app的性能问题,所有会在一些地方做取舍(如图片不支持自动获取宽高,需要开发者指定宽高)。同时weex的开发团队也很注重weex的横向可扩展性,这个在最新推出的module market和即将推出的js service上都有所体现。

1:Firewalld防火墙

1
2
3
4
#停止firewall
systemctl stop firewalld.service
#禁止firewall开机启动
systemctl disable firewalld.service

2:安装iptables

1
2
3
4
5
6
7
8
#先检查是否安装了iptables
service iptables status
#安装iptables
yum install -y iptables
#升级iptables
yum update iptables
#安装iptables-services
yum install iptables-services

3:常用指令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#查看iptables现有规则
iptables -L -n
#先允许所有,不然有可能会杯具
iptables -P INPUT ACCEPT
#清空所有默认规则
iptables -F
#清空所有自定义规则
iptables -X
#所有计数器归0
iptables -Z

#允许来自于lo接口的数据包(本地访问)
iptables -A INPUT -i lo -j ACCEPT
#开放22端口
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
#开放21端口(FTP)
iptables -A INPUT -p tcp --dport 21 -j ACCEPT
#开放80端口(HTTP)
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
#开放3306端口(HTTP)
iptables -A INPUT -p tcp --dport 3306 -j ACCEPT
#开放443端口(HTTPS)
iptables -A INPUT -p tcp --dport 443 -j ACCEPT

#允许ping
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT
#允许接受本机请求之后的返回数据 RELATED,是为FTP设置的
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
#其他入站一律丢弃
iptables -P INPUT DROP
#所有出站一律绿灯
iptables -P OUTPUT ACCEPT
#所有转发一律丢弃
iptables -P FORWARD DROP


#如果要添加内网ip信任(接受其所有TCP请求)
iptables -A INPUT -p tcp -s 45.96.174.68 -j ACCEPT
#过滤所有非以上规则的请求
iptables -P INPUT DROP
#要封停一个IP,使用下面这条命令:
iptables -I INPUT -s ***.***.***.*** -j DROP
#要解封一个IP,使用下面这条命令:
iptables -D INPUT -s ***.***.***.*** -j DROP

#保存上述规则
service iptables save

重新设置iptables设置
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT

4:使用iptables抵抗常见攻击

1. 防止syn攻击

思路一:限制syn的请求速度(这个方式需要调节一个合理的速度值,不然会影响正常用户的请求)

1
2
3
4
5
6
7
iptables -N syn-flood 

iptables -A INPUT -p tcp --syn -j syn-flood

iptables -A syn-flood -m limit --limit 1/s --limit-burst 4 -j RETURN

iptables -A syn-flood -j DROP

思路二:限制单个ip的最大syn连接数

1
iptables –A INPUT –i eth0 –p tcp --syn -m connlimit --connlimit-above 15 -j DROP   

2. 防止DOS攻击

利用recent模块抵御DOS攻击

1
iptables -I INPUT -p tcp -dport 22 -m connlimit --connlimit-above 3 -j DROP

单个IP最多连接3个会话

1
iptables -I INPUT -p tcp --dport 22 -m state --state NEW -m recent --set --name SSH  

只要是新的连接请求,就把它加入到SSH列表中

1
Iptables -I INPUT -p tcp --dport 22 -m state NEW -m recent --update --seconds 300 --hitcount 3 --name SSH -j DROP

5分钟内你的尝试次数达到3次,就拒绝提供SSH列表中的这个IP服务。被限制5分钟后即可恢复访问。

3. 防止单个ip访问量过大

1
iptables -I INPUT -p tcp --dport 80 -m connlimit --connlimit-above 30 -j DROP 

4. 木马反弹

1
iptables –A OUTPUT –m state --state NEW –j DROP   

5. 防止ping攻击

1
iptables -A INPUT -p icmp --icmp-type echo-request -m limit --limit 1/m -j ACCEPT 

xshell设置iptable

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/bin/sh
iptables -P INPUT ACCEPT
iptables -F
iptables -X
iptables -Z
iptables -A INPUT -i lo -j ACCEPT
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
iptables -A INPUT -p tcp --dport 21 -j ACCEPT
iptables -A INPUT -p tcp --dport 80 -j ACCEPT
iptables -A INPUT -p tcp --dport 3306 -j ACCEPT
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p icmp --icmp-type 8 -j ACCEPT
iptables -A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -P INPUT DROP
iptables -P OUTPUT ACCEPT
iptables -P FORWARD DROP
service iptables save
systemctl restart iptables.service

直接修改配置文件

文件位置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
vi /etc/sysconfig/iptables #编辑防火墙配置文件

# Firewall configuration written by system-config-firewall
# Manual customization of this file is not recommended.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 3306 -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT

:wq! #保存退出

一、快速通道

1.1 名词解释

Schema : 一种以文件形式存储的数据库模型骨架,不具备数据库的操作能力

Model : 由Schema发布生成的模型,具有抽象属性和行为的数据库操作对

Entity : 由Model创建的实体,他的操作也会影响数据库

注意:

  1. 本学习文档采用严格命名方式来区别不同对象,例如:
1
2
3
var PersonSchema;   //Person的文本属性
var PersonModel; //Person的数据库模型
var PersonEntity; //Person实体
  1. Schema、Model、Entity的关系请牢记,Schema生成Model,Model创造Entity,Model和Entity都可对数据库操作造成影响,但Model比Entity更具操作性。

1.2 准备工作

  1. 首先你必须安装MongoDB和NodeJS

  2. 在项目只能够创建一个数据库连接,如下:

1
2
var mongoose = require('mongoose');    //引用mongoose模块
var db = mongoose.createConnection('localhost','test'); //创建一个数据库连接
  1. 打开本机localhost的test数据库时,我们可以监测是否有异常
1
2
3
4
db.on('error',console.error.bind(console,'连接错误:'));
db.once('open',function(){
//一次打开记录
});

注意:

成功开启数据库后,就可以执行数据库相应操作,假设以下代码都在回调中处理

  1. 定义一个Schema
1
2
3
var PersonSchema = new mongoose.Schema({
name:String //定义一个属性name,类型为String
});
  1. 将该Schema发布为Model
1
2
3
4
var PersonModel = db.model('Person',PersonSchema);
//如果该Model已经发布,则可以直接通过名字索引到,如下:
//var PersonModel = db.model('Person');
//如果没有发布,上一段代码将会异常
  1. 用Model创建Entity
1
2
3
var personEntity = new PersonModel({name:'Krouky'});
//打印这个实体的名字看看
console.log(personEntity.name); //Krouky
  1. 我们甚至可以为此Schema创建方法
1
2
3
4
5
6
7
//为Schema模型追加speak方法
PersonSchema.methos.speak = function(){
console.log('我的名字叫'+this.name);
}
var PersonModel = db.model('Person',PersonSchema);
var personEntity = new PersonModel({name:'Krouky'});
personEntity.speak();//我的名字叫Krouky
  1. Entity是具有具体的数据库操作CRUD的
1
personEntity.save();  //执行完成后,数据库就有该数据了
  1. 如果要执行查询,需要依赖Model,当然Entity也是可以做到的
1
2
3
PersonModel.find(function(err,persons){
//查询到的所有person
});

注意:

  1. 具体的如何配置Schema、Model以及Model和Entity的相关操作,我们会在后面进行

  2. Model和Entity都有能影响数据库的操作,但仍有区别,后面我们也会做解释

二、新手指引

如果您还不清楚Mongoose是如何工作的,请参看第一章快速通道快速浏览他的用法吧

1. Schema——纯洁的数据库原型

1.1 什么是Schema

  • 我理解Schema仅仅只是一断代码,他书写完成后程序依然无法使用,更无法通往数据库端

  • 他仅仅只是数据库模型在程序片段中的一种表现,或者是数据属性模型

1.2 如何定义Schema

1
2
3
4
5
6
var BlogSchema = new Schema({
title:String,
author:String
//new Schema()中传入一个JSON对象,该对象形如 xxx:yyyy ,
/xxx是一个字符串,定义了属性,yyy是一个Schema.Type,定义了属性类型
});

1.3 什么是Schema.Type

Schema.Type是由Mongoose内定的一些数据类型,基本数据类型都在其中,他也内置了一些Mongoose特有的Schema.Type。当然,你也可以自定义Schema.Type,只有满足Schema.Type的类型才能定义在Schema内。

1.4 Schema.Types

NodeJS中的基本数据类型都属于Schema.Type,另外Mongoose还定义了自己的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
//举例:
var ExampleSchema = new Schema({
name:String,
binary:Buffer,
living:Boolean,
updated:Date,
age:Number,
mixed:Schema.Types.Mixed, //该混合类型等同于nested
_id:Schema.Types.ObjectId, //主键
_fk:Schema.Types.ObjectId, //外键
array:[],
arrOfString:[String],
arrOfNumber:[Number],
arrOfDate:[Date],
arrOfBuffer:[Buffer],
arrOfBoolean:[Boolean],
arrOfMixed:[Schema.Types.Mixed],
arrOfObjectId:[Schema.Types.ObjectId]
nested:{
stuff:String,
}
});

1.5 关于Buffer

Buffer和ArrayBuffer是Nodejs两种隐藏的对象,相关内容请查看NodeJS-API

1.6 关于Mixed

Schema.Types.Mixed是Mongoose定义个混合类型,该混合类型如果未定义具体形式。因此,如果定义具体内容,就直接使用{}来定义,以下两句等价

1
2
var AnySchema = new Schema({any:{}});
var AnySchema = new Schema({any:Schema.Types.Mixed});

混合类型因为没有特定约束,因此可以任意修改,一旦修改了原型,则必须调用markModified()

1
2
3
person.anything = {x:[3,4,{y:'change'}]}
person.markModified('anything');//传入anything,表示该属性类型发生变化
person.save();

1.7 关于ObjectId

主键,一种特殊而且非常重要的类型,每个Schema都会默认配置这个属性,属性名为_id,除非自己定义,方可覆盖

1
2
3
4
var mongoose = require('mongoose');
var ObjectId = mongoose.Schema.Types.ObjectId;
var StudentSchema = new Schema({}); //默认会有_id:ObjectId
var TeacherSchema = new Schema({id:ObjectId});//只有id:ObjectId

该类型的值由系统自己生成,从某种意义上几乎不会重复,生成过程比较复杂,有兴趣的朋友可以查看源码。

1.8 关于Array

Array在JavaScript编程语言中并不是数组,而是集合,因此里面可以存入不同的值,以下代码等价:

1
2
3
4
var ExampleSchema1 = new Schema({array:[]});
var ExampleSchema2 = new Schema({array:Array});
var ExampleSchema3 = new Schema({array:[Schema.Types.Mixed]});
var ExampleSchema4 = new Schema({array:[{}]});

1.9 附言

Schema不仅定义了文档结构和使用性能,还可以有扩展插件、实例方法、静态方法、复合索引、文档生命周期钩子

Schema可以定义插件,并且插件具有良好的可拔插性,请有兴趣的读者继续往后阅读或者查阅官方资料。

2. Schema的扩展

2.1 实例方法

有的时候,我们创造的Schema不仅要为后面的Model和Entity提供公共的属性,还要提供公共的方法。

下面例子比快速通道的例子更加高级,可以进行高级扩展:

1
2
3
4
5
var PersonSchema = new Schema({name:String,type:String});
//查询类似数据
PersonSchema.methods.findSimilarTypes = function(cb){
return this.model('Person').find({type:this.type},cb);
}

使用如下:

1
2
3
4
5
var PersonModel = mongoose.model('Person',PersonSchema);
var krouky = new PersonSchema({name:'krouky',type:'前端工程师'});
krouky.findSimilarTypes(function(err,persons){
//persons中就能查询到其他前端工程师
});

2.2 静态方法

静态方法在Model层就能使用,如下:

1
2
3
4
5
6
7
PersonSchema.statics.findByName = function(name,cb){
this.find({name:new RegExp(name,'i'),cb});
}
var PersonModel = mongoose.model('Person',PersonSchema);
PersonModel.findByName('krouky',function(err,persons){
//找到所有名字叫krouky的人
});

2.3 索引

索引或者复合索引能让搜索更加高效,默认索引就是主键索引ObjectId,属性名为_id, 索引会作为一个专题来讲解

2.4 虚拟属性

Schema中如果定义了虚拟属性,那么该属性将不写入数据库,例如:

1
2
3
4
5
6
7
8
9
10
var PersonSchema = new Schema({
name:{
first:String,
last:String
}
});
var PersonModel = mongoose.model('Person',PersonSchema);
var krouky = new PersonModel({
name:{first:'krouky',last:'han'}
});

如果每次想使用全名就得这样

1
console.log(krouky.name.first + ' ' + krouky.name.last);

显然这是很麻烦的,我们可以定义虚拟属性:

1
2
3
PersonSchema.virtual('name.full').get(function(){
return this.name.first + ' ' + this.name.last;
});

那么就能用krouky.name.full来调用全名了,反之如果知道full,也可以反解first和last属性

1
2
3
4
5
6
7
8
9
PersonSchema.virtual('name.full').set(function(name){
var split = name.split(' ');
this.name.first = split[0];
this.name.last = split[1];
});
var PersonModel = mongoose.model('Person',PersonSchema);
var krouky = new PersonModel({});
krouky.name.full = 'krouky han';//会被自动分解
console.log(krouky.name.first);//krouky

2.5 配置项

在使用new Schema(config)时,我们可以追加一个参数options来配置Schema的配置,形如:

1
var ExampleSchema = new Schema(config,options);

或者使用

1
2
var ExampleSchema = new Schema(config);
ExampleSchema.set(option,value);

可供配置项有:safe、strict、capped、versionKey、autoIndex

2.5.1 safe——安全属性(默认安全)

一般可做如下配置:

1
new Schema({...},{safe:true});

当然我们也可以这样

1
new Schema({...},{safe:{j:1,w:2,wtimeout:10000}});

j表示做1份日志,w表示做2个副本(尚不明确),超时时间10秒

2.5.2 strict——严格配置(默认启用)

确保Entity的值存入数据库前会被自动验证,如果你没有充足的理由,请不要停用,例子:

1
2
3
4
var ThingSchema = new Schema({a:String});
var ThingModel = db.model('Thing',SchemaSchema);
var thing = new Thing({iAmNotInTheThingSchema:true});
thing.save();//iAmNotInTheThingSchema这个属性将无法被存储

如果取消严格选项,iAmNotInTheThingSchema将会被存入数据库

该选项也可以在构造实例时使用,例如:

1
2
3
var ThingModel = db.model('Thing');
var thing1 = new ThingModel(doc,true); //启用严格
var thing2 = new ThingModel(doc,false); //禁用严格

注意:

strict也可以设置为throw,表示出现问题将会抛出错误

2.5.3 shardKey

需要mongodb做分布式,才会使用该属性

2.5.4 capped——上限设置

如果有数据库的批量操作,该属性能限制一次操作的量,例如:

1
new Schema({...},{capped:1024});  //一次操作上线1024条数据

当然该参数也可是JSON对象,包含size、max、autiIndexId属性

1
new Schema({...},{capped:{size:1024,max:100,autoIndexId:true}});

#####2.5.5 versionKey——版本锁

版本锁是Mongoose默认配置(__v属性)的,如果你想自己定制,如下:

1
new Schema({...},{versionKey:'__someElse'});

此时存入数据库的版本锁就不是__v属性,而是__someElse,相当于是给版本锁取名字。

具体怎么存入都是由Mongoose和MongoDB自己决定,当然,这个属性你也可以去除

1
new Schema({...},{versionKey:false});

除非你知道你在做什么,并且你知道这样做的后果

2.5.6 autoIndex——自动索引

该内容将在索引章节单独讲解

3. Documents

Document是与MongoDB文档一一对应的模型,Document可等同于Entity,具有属性和操作性

注意:

Document的`CRUD都必须经过严格验证的,参看2.5.2 Schema的strict严格配置

3.1 查询

查询内容过多,专题讲解

3.2 更新

有许多方式来更新文件,以下是常用的传统方式:

1
2
3
4
PersonModel.findById(id,function(err,person){
person.name = 'MDragon';
person.save(function(err){});
});

这里,利用Model模型查询到了person对象,该对象属于Entity,可以有save操作,如果使用Model`操作,需注意:

1
2
3
4
5
6
7
PersonModel.findById(id,function(err,person){
person.name = 'MDragon';
var _id = person._id; //需要取出主键_id
delete person._id; //再将其删除
PersonModel.update({_id:_id},person,function(err){});
//此时才能用Model操作,否则报错
});

update第一个参数是查询条件,第二个参数是更新的对象,但不能更新主键,这就是为什么要删除主键的原因。

当然这样的更新很麻烦,可以使用$set属性来配置,这样也不用先查询,如果更新的数据比较少,可用性还是很好的:

1
PersonModel.update({_id:_id},{$set:{name:'MDragon'}},function(err){});

需要注意,Document的CRUD操作都是异步执行,callback第一个参数必须是err,而第二个参数各个方法不一样,update的callback第二个参数是更新的数量,如果要返回更新后的对象,则要使用如下方法

1
2
3
Person.findByIdAndUpdate(_id,{$set:{name:'MDragon'}},function(err,person){
console.log(person.name); //MDragon
});

类似的方法还有findByIdAndRemove,如同名字,只能根据id查询并作update/remove操作,操作的数据仅一条

3.3 新增

如果是Entity,使用save方法,如果是Model,使用create方法

1
2
3
4
5
6
//使用Entity来增加一条数据
var krouky = new PersonModel({name:'krouky'});
krouky.save(callback);
//使用Model来增加一条数据
var MDragon = {name:'MDragon'};
PersonModel.create(MDragon,callback);

两种新增方法区别在于,如果使用Model新增时,传入的对象只能是纯净的JSON对象,不能是由Model创建的实体,原因是:由Model创建的实体krouky虽然打印是只有{name:’krouky’},但是krouky属于Entity,包含有Schema属性和Model数据库行为模型。如果是使用Model创建的对象,传入时一定会将隐藏属性也存入数据库,虽然3.x追加了默认严格属性,但也不必要增加操作的报错

3.4 删除

和新增一样,删除也有2种方式,但Entity和Model都使用remove方法

4.Sub Docs

如同SQL数据库中2张表有主外关系,Mongoose将2个Document的嵌套叫做Sub-Docs(子文档)

简单的说就是一个Document嵌套另外一个Document或者Documents:

1
2
3
4
5
6
var ChildSchema1 = new Schema({name:String});
var ChildSchema2 = new Schema({name:String});
var ParentSchema = new Schema({
children1:ChildSchema1, //嵌套Document
children2:[ChildSchema2] //嵌套Documents
});

Sub-Docs享受和Documents一样的操作,但是Sub-Docs的操作都由父类去执行

1
2
3
4
5
6
var ParentModel = db.model('Parent',parentSchema);
var parent = new ParentModel({
children2:[{name:'c1'},{name:'c2'}]
});
parent.children2[0].name = 'd';
parent.save(callback);

parent在执行保存时,由于包含children2,他是一个数据库模型对象,因此会先保存chilren2[0]和chilren2[1]。

如果子文档在更新时出现错误,将直接报在父类文档中,可以这样处理:

1
2
3
4
5
6
7
8
ChildrenSchema.pre('save',function(next){
if('x' === this.name) return next(new Error('#err:not-x'));
next();
});
var parent = new ParentModel({children1:{name:'not-x'}});
parent.save(function(err){
console.log(err.message); //#err:not-x
});

4.1 查询子文档

如果children是parent的子文档,可以通过如下方法查询到children

1
var child = parent.children.id(id);

4.2 新增、删除、更新

子文档是父文档的一个属性,因此按照属性的操作即可,不同的是在新增父类的时候,子文档是会被先加入进去的。

如果ChildrenSchema是临时的一个子文档,不作为数据库映射集合,可以这样:

1
2
3
4
5
6
var ParentSchema = new Schema({
children:{
name:String
}
});
//其实就是匿名混合模式

5.Model

5.1 什么是Model

Model模型,是经过Schema构造来的,除了Schema定义的数据库骨架以外,还具有数据库行为模型,他相当于管理数据库属性、行为的类

5.2 如何创建Model

你必须通过Schema来创建,如下:

1
2
3
4
5
6
7
//先创建Schema
var TankSchema = new Schema({
name:'String',
size:'String'
});
//通过Schema创建Model
var TankModel = mongoose.model('Tank',TankSchema);

5.2 操作Model

该模型就能直接拿来操作,具体查看API,例如:

1
2
var tank = {'something',size:'small'};
TankModel.create(tank);

注意:

你可以使用Model来创建Entity,Entity实体是一个特有Model具体对象,但是他并不具备Model的方法,只能用自己的方法。

1
2
3
//通过Model创建Entity
var tankEntity = new TankModel('someother','size:big');
tankEntity.save();

6.Query

查询是数据库中运用最多也是最麻烦的地方,这里对Query解读的并不完善,仅仅是自己的一点领悟而已。

####6.1 查询的方式

通常有2种查询方式,一种是直接查询,一种是链式查询(2种查询都是自己命名的)

#####6.1.1 直接查询

在查询时带有回调函数的,称之为直接查询,查询的条件往往通过API来设定,例如:

1
2
3
PersonModel.findOne({'name.last':'dragon'},'some select',function(err,person){
//如果err==null,则person就能取到数据
});

具体的查询参数,请查询API

6.1.2 链式查询

在查询时候,不带回调,而查询条件通过API函数来制定,例如:

1
2
3
4
5
var query = PersonModel.findOne({'name.last':'dragon'});
query.select('some select');
query.exec(function(err,pserson){
//如果err==null,则person就能取到数据
});

这种方式相对直接查询,分的比较明细,如果不带callback,则返回query,query没有执行的预编译查询语句,该query对象执行的方法都将返回自己,只有在执行exec方法时才执行查询,而且必须有回调。

因为query的操作始终返回自身,我们可以采用更形象的链式写法

1
2
3
4
5
6
7
8
9
Person
.find({ occupation: /host/ })
.where('name.last').equals('Ghost')
.where('age').gt(17).lt(66)
.where('likes').in(['vaporizing', 'talking'])
.limit(10)
.sort('-occupation')
.select('name occupation')
.exec(callback);

7.Validation

数据的存储是需要验证的,不是什么数据都能往数据库里丢或者显示到客户端的,数据的验证需要记住以下规则:

  • 验证始终定义在SchemaType中
  • 验证是一个内部中间件
  • 验证是在一个Document被保存时默认启用的,除非你关闭验证
  • 验证是异步递归的,如果你的SubDoc验证失败,Document也将无法保存
  • 验证并不关心错误类型,而通过ValidationError这个对象可以访问

7.1 验证器

  • required 非空验证
  • min/max 范围验证(边值验证)
  • enum/match 枚举验证/匹配验证
  • validate 自定义验证规则

以下是综合案例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var PersonSchema = new Schema({
name:{
type:'String',
required:true //姓名非空
},
age:{
type:'Nunmer',
min:18, //年龄最小18
max:120 //年龄最大120
},
city:{
type:'String',
enum:['北京','上海'] //只能是北京、上海人
},
other:{
type:'String',
validate:[validator,err] //validator是一个验证函数,err是验证失败的错误信息
}
});

7.2 验证失败

如果验证失败,则会返回err信息,err是一个对象该对象属性如下

1
2
3
4
5
6
7
err.errors                //错误集合(对象)
err.errors.color //错误属性(Schema的color属性)
err.errors.color.message //错误属性信息
err.errors.path //错误属性路径
err.errors.type //错误类型
err.name //错误名称
err.message //错误消息

一旦验证失败,Model和Entity都将具有和err一样的errors属性

8.Middleware中间件

8.1 什么是中间件

中间件是一种控制函数,类似插件,能控制流程中的init、validate、save、remove`方法

8.2 中间件的分类

中间件分为两类

8.2.1 Serial串行

串行使用pre方法,执行下一个方法使用next调用

1
2
3
4
5
var schema = new Schema(...);
schema.pre('save',function(next){
//做点什么
next();
});
8.2.2 Parallel并行

并行提供更细粒度的操作

1
2
3
4
5
6
var schema = new Schema(...);
schema.pre('save',function(next,done){
//下一个要执行的中间件并行执行
next();
doAsync(done);
});

8.3 中间件特点

一旦定义了中间件,就会在全部中间件执行完后执行其他操作,使用中间件可以雾化模型,避免异步操作的层层迭代嵌套

8.4 使用范畴

  • 复杂的验证
  • 删除有主外关联的doc
  • 异步默认
  • 某个特定动作触发异步任务,例如触发自定义事件和通知

例如,可以用来做自定义错误处理

1
2
3
4
5
6
7
schema.pre('save',function(next){
var err = new Eerror('some err');
next(err);
});
entity.save(function(err){
console.log(err.message); //some err
});

1
2
3
4
5
6
function thousandsFormat(number) {
var num = number + '';
var arr = num.split('.');
arr[0] = arr[0].replace(/(?!^)(?=([0-9]{3})+$)/g,',');
return arr.join('.');
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function empty(v){ 
switch (typeof v){
case 'undefined' : return true;
case 'string' : if(trim(v).length == 0) return true; break;
case 'boolean' : if(!v) return true; break;
case 'number' : if(0 === v) return true; break;
case 'object' :
if(null === v) return true;
if(undefined !== v.length && v.length==0) return true;
for(var k in v){return false;} return true;
break;
}
return false;
}

安装

1
$ npm install ejs

特性

  • <% %> 用于控制流
  • <%= %> 用于转义的输出
  • <%- %> 用于非转义的输出
  • -%> 结束标签用于换行移除模式
  • 带有 <%_ _%> 的控制流使用空白字符移除模式
  • 自定义分隔符 (例如,使用 ‘‘ 代替 ‘<% %>’)
  • 包含
  • 客户端支持
  • 中介JavaScript的静态缓存
  • 模板的静态缓存
  • 与 Express 视图系统兼容

示例

1
2
3
<% if (user) { %>
<h2><%= user.name %></h2>
<% } %>

用法

1
2
3
4
5
6
var template = ejs.compile(str, options);
template(data);
// => 渲染 HTML 字符串

ejs.render(str, data, options);
// => 渲染 HTML 字符串

你也可以使用快捷方式 ejs.render(dataAndOptions); ,其中你可以通过一个对象来传递任何东西。在这种情况下,你需要以一个装有所有需要传递对象的本地变量结束。

选项

  • cache 编译过的函数会被缓存,需要 filename
  • filename 被 cache 用做缓存的键,用于包含
  • scope 函数执行的上下文
  • debug 输出生成的函数体
  • compileDebug 如果为 false ,不会编译调试用的工具
  • client 返回独立的编译后的函数
  • open 开始标签,默认为”<%”
  • close 结束标签,默认为”%>”
  • _with 是否使用 with() {} 结构。如果为 false 则局部数据会储存在 locals 对象中。
  • rmWhitespace 移除所有可以安全移除的空白字符,包含前导和尾后的空白字符。同时会为所有scriptlet标签开启 -%> 换行截断的更加安全的模式。(它不会在一行之中去除标签的换行)。

标签

  • <% ‘Scriptlet’ 标签, 用于控制流,没有输出
  • <%= 向模板输出值(带有转义)
  • <%- 向模板输出没有转义的值
  • <%# 注释标签,不执行,也没有输出
  • <%% 输出字面的 ‘<%’
  • %> 普通的结束标签
  • -%> Trim-mode (‘newline slurp’) 标签, 移除随后的换行符

包含

包含要么是绝对路径,或者如果不是的话,被视为相对于调用 include 的模板的路径(需要 filename 选项)。 例如,你在 ./views/users.ejs 中包含 ./views/user/show.ejs ,你应该使用 <%- include(‘user/show’) %> 。

你可能会用到原始输出标签( <%- )避免二次转义HTML输出。

1
2
3
4
5
<ul>
<% users.forEach(function(user){ %>
<%- include('user/show', {user: user}) %>
<% }); %>
</ul>

包含的内容在运行时插入, 所以你可以在 include 调用中使用变量作为路径(例如 <%- include(somePath) %> )。在你顶级数据对象中的变量都可以用于所有的包含,而局部变量需要传递进来。

注意:仍然支持包含预处理指令( <% include user/show %> )。

过滤器

技术条件支持的概念“过滤器”。“过滤器链”是操纵数据设计师友好的API,无需编写JavaScript。

过滤器支持链式,例如,如果我们想把数组[{ name: ‘tj’ }, { name: ‘mape’ }, { name: ‘guillermo’ }]输出成名单,我们可以这样使用过滤器:

模板:

1
<p><%=: users | map:'name' | join %></p>

输出:

1
<p>Tj, Mape, Guillermo</p>

渲染代码:

1
2
3
4
5
6
7
ejs.render(str, {
users: [
{ name: 'tj' },
{ name: 'mape' },
{ name: 'guillermo' }
]
});

或者可以这样输出用户列表的第一个名字:

1
<p><%=: users | first | capitalize %></p>

过滤器列表

  • first
  • last
  • capitalize
  • downcase
  • upcase
  • sort
  • sort_by:’prop’
  • size
  • length
  • plus:n
  • minus:n
  • times:n
  • divided_by:n
  • join:’val’
  • truncate:n
  • truncate_words:n
  • replace:pattern,substitution
  • prepend:val
  • append:val
  • map:’prop’
  • reverse
  • get:’prop’

添加过滤器

通过给.filters对象添加属性方法来添加过滤器:

1
2
3
ejs.filters.last = function(obj) {
return obj[obj.length - 1];
};

自定义分隔符

自定义分隔符可以以模板为单位应用,或者全局:

1
2
3
4
5
6
7
8
9
10
11
12
var ejs = require('ejs'),
users = ['geddy', 'neil', 'alex'];

// Just one template
ejs.render('{{= users.join(" | "); }}', {users: users}, {open : '{{', close: '}}'});
// => 'geddy | neil | alex'

// Or globally
ejs.open = '{{';
ejs.close = '}}';
ejs.render('{{= users.join(" | "); }}', {users: users});
// => 'geddy | neil | alex'

缓存

EJS 自带了一个基本的运行时缓存,用于缓存渲染模板的中介JavaScript函数。使用 Node 的 lru-cache 库来添加LRU缓存十分简单:

1
2
3
var ejs = require('ejs')
, LRU = require('lru-cache');
ejs.cache = LRU(100); // LRU cache with 100-item limit

如果你想清除ejs的缓存,调用 ejs.clearCache 。如果你需要以一个不同的限额来使用LRU,只需要将 ejs.cache 重新设置为一个LRU的新实例。

布局

EJS 不会特别地支持区块,但是可以采用包含头部和尾部的方法来实现局部,像这样:

1
2
3
4
5
6
7
8
<%- include('header') -%>
<h1>
Title
</h1>
<p>
My page
</p>
<%- include('footer') -%>

客户端支持

访问 最新发布,下载

./ejs.js 或者 ./ejs.min.js 。

选择其一包含到你的页面中,并且使用 ejs.render(str) 。

相关项目

package.json文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"name": "gulp+freemarker+rap",
"version": "0.0.1",
"description": "数据模拟",
"main": "gulpfile.js",
"scripts": {
"test": "gulp"
},
"author": "mitch",
"license": "ISC",
"dependencies": {
"freemarker.js": "^1.2.2",
"gulp": "^3.9.1",
"gulp-autoprefixer": "^3.1.1",
"gulp-changed": "^1.3.2",
"gulp-livereload": "^3.8.1",
"gulp-sass": "^2.3.2",
"gulp-sourcemaps": "^2.2.0",
"rap-node-plugin": "^0.2.2"
}
}

gulpfile.js文件编写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
var gulp = require('gulp'),
sass = require('gulp-sass'),
sourcemaps = require('gulp-sourcemaps'),
changed = require('gulp-changed'),
autoprefixer = require('gulp-autoprefixer'),
rapnode = require('rap-node-plugin'),
Freemarker = require('freemarker.js'),
livereload = require('gulp-livereload'),
fs = require('fs'),
http = require('http');

// 配置
var opt = {
port: 3000,
fileExt: '.html', //模板文件后缀
pwd: process.cwd(), //项目所在目录
webroot: '/WEB-INF/views', //模板文件目录
themes: '', //资源文件目录
livereload_src: ['WEB-INF/views/web/**/*.html', 'themes/web/(images|scripts|styles)/**/*'],
scss_src: ['themes/web/scss/**/*.scss', '!themes/web/scss/base/*.scss'],
base_src: 'themes/web/scss/base/*.scss',
common_src: 'themes/web/scss/common.scss',
style_src: 'themes/web/styles',
map_src: './maps'
}

// freeMarker配置
var fm = new Freemarker({
viewRoot: opt.pwd + opt.webroot,
options: {
/** for fmpp */
}
});

global.RAP_FLAG = 1; // 开启RAP

// RAP配置
rapnode.config({
host: 'apidoc.wjs-dev.com', //启动的服务主机
port: '80', //端口号
projectId: 24, //RAP配置的项目ID
mock: '/mockjs/', //RAP前缀
wrapper: '' //不需要包装
});

// 服务
gulp.task('server', function(){
var server = http.createServer(function(req, res){
var url = req.url
if (url == '/') url = '/cashback/orginfo';
if (url.match(/\.\w+/)) {
var fileDir = opt.pwd + opt.themes + url;
if (fs.existsSync(fileDir)) {
var content = fs.readFileSync(fileDir, 'utf-8');
res.end(content);
} else {
console.error('文件不存在: %s', fileDir);
res.writeHead(400, {'Content-Type': 'text/plain'});
res.end();
}
}
rapnode.getRapData(url, function() {
}, function(err, result) {
if (err) {
console.error('RAP报错:%s', err);
console.error('URL:%s', url);
}
if (result.isPage == '1') {
var fileDir = (result.renderPageUrl ? result.renderPageUrl : url) + opt.fileExt;
fm.render(fileDir, result, function(err, html, output) {
if (err) {
console.error('Freemarker报错:%s', output);
res.end(output);
}
res.end(html);
});
} else {
res.end(JSON.stringify(result));
}
});
});
server.on('clientError', function(error, socket){
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
});
server.listen(opt.port);
});

// scss编译
gulp.task('scss', function(){
return gulp.src(opt.scss_src)
.pipe(changed(opt.style_src))
.pipe(sourcemaps.init())
.pipe(sass({outputStyle: 'compressed'}).on('error', sass.logError))
.pipe(autoprefixer({
browsers: ['last 2 versions', 'IE 8', '> 1%'],
cascade: false
}))
.pipe(sourcemaps.write(opt.map_src))
.pipe(gulp.dest(opt.style_src));
});

// 公共文件监听
gulp.task('common:watch', function(){
return gulp.src(opt.common_src)
.pipe(sourcemaps.init())
.pipe(sass({outputStyle: 'compressed'}).on('error', sass.logError))
.pipe(autoprefixer({
browsers: ['last 2 versions', 'IE 8', '> 1%'],
cascade: false
}))
.pipe(sourcemaps.write(opt.map_src))
.pipe(gulp.dest(opt.style_src));
});

// 监听任务
gulp.task('watch', function(){
livereload.listen();
gulp.watch(opt.livereload_src, function(file){
livereload.changed(file.path);
});
gulp.watch(opt.base_src, ['common:watch']);
gulp.watch(opt.scss_src, ['scss']);
});

// 自动打开浏览器
gulp.task('browsers', function(){
var child_process = require("child_process"),
url = "http://127.0.0.1:" + opt.port;

if(process.platform == 'win32'){
cmd = 'start "%ProgramFiles%\Internet Explorer\iexplore.exe"';
}else if(process.platform == 'linux'){
cmd = 'xdg-open';
}else if(process.platform == 'darwin'){
cmd = 'open';
}

child_process.exec(cmd + ' "'+url + '"');
});

gulp.task('default', ['server', 'watch', 'browsers'])

先说明一下,之所以有这篇文章是因为考虑到目前正在使用的handlebars模板引擎,在处理逻辑判断时不太方便,需要使用大量的判断嵌套,加大了工作量还容易出错,所以希望换一个更优秀的模板引擎。

jade

首先说jade,现已改名为pug,因为版权问题,具体是什么我也不知道。。。

jade是一个运行于node环境的模板引擎,也就是说它其实是一个服务端模板引擎。但众所周知,node其实也是js环境,所有它也可以在浏览器端使用,只是有如下一些要求:

  • 首先,你需要下载最新版的jade.js,这个问题不大,但是这个js压缩后都要500K+,这就是一个问题了
  • 其次,它只兼容最新的浏览器内核,还要兼容ie的就果断放弃吧

所以官方也不推荐用户这样使用jade模板引擎。

他们推荐了另一种在浏览器端使用jade的方式:

  1. 首先你要在本地安装node环境,使用npm包管理工具下载jade,然后使用命令行将你写好的jade模板文件编译为可运行的js文件
  2. 然后你只需要在页面中导入一个约7k的runtime文件,就可以将编译出来的js文件作为模板文件使用了

而这样使用的优点是,不需要导入模板引擎的编译代码,并且省去了模板文件编译的过程,加快了模板渲染的流程。

ejs

再来说说ejs

ejs的html部分直接使用的html语法,标实符号和php、asp类似(当然可以自定义),而逻辑语法直接使用js语法(可以直接运行js语法),上手方便。

你能够在 <%…%> 块中安排 JavaScript 代码,利用最传统的方式 <%=输出变量%>(另外 <%-输出变量是不会对&等符号进行转义的)。

同时ejs的js文件未压缩版为14k,压缩后只有8k。

找到的东西比较少,就这么点了。

数据

变量名以$符号开头

1
$key: value;

数据类型

  • 数字(例如 1.2、13、10px)
  • 文本字符串,无论是否有引号(例如 “foo”、’bar’、baz)
  • 颜色(例如 blue、#04a3f9、rgba(255, 0, 0, 0.5))
  • 布尔值(例如 true、false)
  • 空值(例如 null)
  • 值列表,用空格或逗号分隔(例如 1.5em 1em 0 2em、Helvetica, Arial, sans-serif)

#{}强制以字符串形式输出,不进行运算

1
2
3
4
5
$number: 1;

.gird-#{number}: {
width: 100%;
}

编译为

1
2
3
.gird-1: {
width: 100%;
}

运算

所有数据类型都支持等式运算 (== and !=)。 另外,每种数据类型也有其支持的特殊运算符。

数字运算

SassScript 支持数字的标准运算(加 +、减 -、乘 *、除 /和取模 %),并且,如果需要的话,也可以在不同单位间做转换:

1
2
3
p {
width: 1in + 8pt;
}

被编译为:

1
2
3
p {
width: 1.111in;
}

数字也支持关系运算(<、>、<=、>=), 等式运算(==、!=)被所有数据类型支持。

除法运算和 /

CSS 允许 / 出现在属性值里,作为分隔数字的一种方法。 既然 SassScript 是 CSS 属性语法的扩展, 他就必须支持这种语法,同时也允许 / 用在除法运算上。 也就是说,默认情况下,在 SassScript 里用 / 分隔的两个数字, 都会在 CSS 中原封不动的输出。

然而,在以下三种情况中,/ 会被解释为除法运算。 这就覆盖了绝大多数真正使用除法运算的情况。 这些情况是:

  1. 如果数值或它的任意部分是存储在一个变量中或是函数的返回值。
  2. 如果数值被圆括号包围。
  3. 如果数值是另一个数学表达式的一部分。

例如:

1
2
3
4
5
6
7
8
p {
font: 10px/8px; // 纯 CSS,不是除法运算
$width: 1000px;
width: $width/2; // 使用了变量,是除法运算
width: round(1.5)/2; // 使用了函数,是除法运算
height: (500px/2); // 使用了圆括号,是除法运算
margin-left: 5px + 8px/2px; // 使用了加(+)号,是除法运算
}

被编译为:

1
2
3
4
5
p {
font: 10px/8px;
width: 500px;
height: 250px;
margin-left: 9px; }

如果你希望在纯 CSS 中使用变量和 /, 你可以用 #{} 包住变量。 例如:

1
2
3
4
5
p {
$font-size: 12px;
$line-height: 30px;
font: #{$font-size}/#{$line-height};
}

被编译为:

1
2
p {
font: 12px/30px; }

@ 规则和指令

Sass 支持所有 CSS3 的 @ 规则, 以及一些 Sass 专属的规则,也被称为“指令(directives)”。 这些规则在 Sass 中具有不同的功效,详细解释如下。 也可参考 控制指令(control directives) 和 mixin 指令(mixin directives)。

@import

Sass 扩展了 CSS 的 @import 规则,让它能够引入 SCSS 和 Sass 文件。 所有引入的 SCSS 和 Sass 文件都会被合并并输出一个单一的 CSS 文件。 另外,被导入的文件中所定义的变量或 mixins 都可以在主文件中使用。

Sass 会在当前目录下寻找其他 Sass 文件, 如果是 Rack、Rails 或 Merb 环境中则是 Sass 文件目录。 也可以通过 :load_paths 选项 或者在命令行中使用 –load-path 选项来指定额外的搜索目录。

@import 根据文件名引入。 默认情况下,它会寻找 Sass 文件并直接引入, 但是,在少数几种情况下,它会被编译成 CSS 的 @import 规则:

  • 如果文件的扩展名是 .css。
  • I如果文件名以 http:// 开头。
  • 如果文件名是 url()。
  • 如果 @import 包含了任何媒体查询(media queries)。

如果上述情况都没有出现,并且扩展名是 .scss 或 .sass, 该名称的 Sass 或 SCSS 文件就会被引入。 如果没有扩展名, Sass 将试着找出具有 .scss 或 .sass 扩展名的同名文件并将其引入。

@extend 继承

1
2
3
4
5
6
7
8
.error {
border: 1px #f00;
background-color: #fdd;
}
.seriousError {
@extend .error; //继承error类的样式
border-width: 3px;
}

@if

1
2
3
4
5
6
7
8
9
10
11
12
$type: monster;
p {
@if $type == ocean {
color: blue;
} @else if $type == matador {
color: red;
} @else if $type == monster {
color: green;
} @else {
color: black;
}
}

@for

1
2
3
@for $i from 1 through 3 {
.item-#{$i} { width: 2em * $i; }
}

@each

1
2
3
4
5
@each $animal in puma, sea-slug, egret, salamander {
.#{$animal}-icon {
background-image: url('/images/#{$animal}.png');
}
}

@while

1
2
3
4
5
$i: 6;
@while $i > 0 {
.item-#{$i} { width: 2em * $i; }
$i: $i - 2;
}

@mixin定义一个混淆

1
2
3
4
5
6
7
8
@mixin large-text {
font: {
family: Arial;
size: 20px;
weight: bold;
}
color: #ff0000;
}
1
2
3
4
5
6
7
8
9
10
11
@mixin clearfix {
display: inline-block;
&:after {
content: ".";
display: block;
height: 0;
clear: both;
visibility: hidden;
}
* html & { height: 1px }
}

@include调用一个混淆

1
2
3
4
5
.page-title {
@include large-text;
padding: 4px;
margin-top: 10px;
}
1
2
3
4
5
6
7
8
@mixin silly-links {
a {
color: blue;
background-color: red;
}
}

@include silly-links;

Arguments带参数的混淆

1
2
3
4
5
6
7
8
9
@mixin sexy-border($color, $width) {
border: {
color: $color;
width: $width;
style: dashed;
}
}

p { @include sexy-border(blue, 1in); }

@mixin参数设置默认值

1
2
3
4
5
6
7
8
9
@mixin sexy-border($color, $width: 1in) {
border: {
color: $color;
width: $width;
style: dashed;
}
}
p { @include sexy-border(blue); }
h1 { @include sexy-border(blue, 2in); }

@include参数带关键字

1
2
p { @include sexy-border($color: blue); }
h1 { @include sexy-border($color: blue, $width: 2in); }

@content

1
2
3
4
5
6
7
8
9
10
@mixin apply-to-ie6-only {
* html {
@content;
}
}
@include apply-to-ie6-only {
#logo {
background-image: url(/logo.gif);
}
}

编译为

1
2
3
* html #logo {
background-image: url(/logo.gif);
}

Function

1
2
3
4
5
6
7
8
$grid-width: 40px;
$gutter-width: 10px;

@function grid-width($n) {
@return $n * $grid-width + ($n - 1) * $gutter-width;
}

#sidebar { width: grid-width(5); }

编译为

1
2
#sidebar {
width: 240px; }
0%