node.js mongodb笔记


主模块:
一个项目只允许有一个主模块,通常命名为:main.js index.js app.js package.json文件中的main声明
主模块是整个项目的启动模块,主模块对整个项目的其它模块进行统筹调试

Node.js的模块组成:
所有用户编写的代码都放在模块中,模块就是文件(函数)
所以用户编写的代码都自动封装在一个函数中,函数有五个参数:
exports 暴露对象,可以将模块中的数据暴露给引入的地方
require 引入模块的函数,用于在一个模块中引用另外一个模块,并且将子模块暴露的数据赋值给变量
module 模块对象,包含了当前模块的所有信息
_filename 当前模块的文件名
_dirname 当前模块所在的路径(目录路径)

require函数:
作用:在当前模块中加载另外一个模块

模块分类:
1) 自定义模块
我们自己编写的文件就是一个自定义模块
require('');

注意:
a. 子模块没有暴露数据时,返回空对象
b. 自定义模块必须加 ./ ,因为在node.js中查找模块默认在node_modules目录中查找

2) 第三方模块
第三方程序员或公司开发的模块,先安装再使用
安装可以使用npm包管理工具
npm install <包的名称>

require('模块名');

3) 系统模块
node.js开发团队已经开发好的功能模块,直接引入即可使用,不需要安装也不需要自己写
fs http url path ......

require('模块名');

require函数的注意事项:
1) 当引入的模块有语法错误时,会报错
2) 当引入的模块路径有错时,也会报错
3) 当一个模块被多次引入时,只执行一次,将暴露对象直接写入缓存,以后就直接从缓存读取

exports 导出对象:
作用:将模块中需要共享给其它模块的数据暴露(导出)到引用处
语法:
exports.属性名 = 值;
exports.方法名 = 函数;

注意:
1) exports 是module.exports对象的引用
2) exports是module.exports的引用,不能改指向,只能添加属性和方法
3) module.exports才是真正的暴露对象,指向哪里就暴露哪里

module模块对象:
module.exports 【重点】真正的暴露对象,exports对象只是对它的引用。
module.exports.属性 = 值;
module.exports.方法 = 函数;
module.exports = 对象或函数;

(了解)
module.id 模块ID,模块名称
module.parent 模块的父级
module.filename 模块的文件名和路径
module.children 子模块列表
module.paths 模块查找路径,如果当前目录下找不到node_modules就去上一级目录查找,直到根目录...
如果配置了NODE_PATH环境变量,则会再去环境变量指向的路径查找

1. 什么是NPM ?
npm(Node Package Manager)是基于nodejs的包管理工具

1) 什么是包?
包 === 项目
模块 === 文件

2. 什么是package.json ?
package.json是node.js项目的包描述文件,以JSON格式的形式描述项目

1) 如何创建package.json
npm init
npm init -y //自动以全部为yes的形式生成package.json文件

2) package.json的常用属性
name 项目名称
version 版本号
description 项目描述
main 主模块
dependencies 依赖列表
devDependencies 开发时依赖
scripts 脚本命令,可以使用npm命令进行执行
license 开源协议

3. npm 的常用命令
//安装包
npm install <包的名称> //安装指定的包
npm i <包的名称> //效果同上,缩写形式
npm i <包的名称>@版本号 //安装指定版本的包
npm i <包的名称> -g //全局安装
安装位置:C:\Users\Administrator\AppData\Roaming\npm\node_modules

npm i <包的名称> --save //将安装包写入package.json依赖列表
npm i <包的名称> --save-dev //将安装包写入package.json开发时依赖列表

//其它命令
npm search <包的名称> //搜索包
npm view <包的名称> //查看包的信息
npm uninstall <包的名称> //卸载包
npm update <包的名称> //卸载包

4. cnpm
npm 就是一个文件下载工具,默认情况下去npmjs.com(github.com)下载资源
cnpm 由于在国内下载npmjs.com的数据非常慢,所以淘宝制作了一个npmjs.com的镜像(可以下载,不能上传),直接访问国内的服务器

安装cnpm:
npm install -g cnpm --registry=https://registry.npm.taobao.org

使用cnpm:
与npm一模一样。

控制台命令:
console.log() 普通输出语句
console.dir() ...
console.error() 警告输出
console.time(标识) 计时开始
console.timeEnd(标识) 计时结束
console.assert(表达式, 输出文字); //当表达式为假时,输出文字(抛出错误)

注意:console.debug() 不能在node.js中使用

//开启计时
console.time('t1');
for(var i=0; i<=9999999; i++){

}
console.timeEnd('t1');

//断言
console.assert(1>2, '表达式为假');

node.js的作用域:
由于node.js在执行里会将用户编写的所有代码都封装在函数中,所有用户定义的变量或函数都是局部的。
要将数据共享给其它模块使用:
1. 暴露对象
module.exports

2.全局对象
global.属性或方法 = 值;

注意:使用时global关键字可以省略不写

示例:
var username = '张三';

//第一种:暴露对象
module.exports.name = username;

//第二种:使用全局对象
global.name = username;

回调函数:
1) 什么是回调函数?
回调函数又称为回调,将a函数作为参数传入b函数中,b函数在执行过程中根据时机或条件决定是否调用a函数,a函数就是回调函数

2) 回调函数的机制
a. 定义一个普通函数(可用做回调函数)
b. 将函数作为参数传入另外一个函数(调用者)
c. 调用者在执行过程中根据时机和条件来决定是否调用函数

3) 回调函数的用途
通常用于达到某个时机或条件时需要执行代码的情况,我们就会使用回调函数

示例1:
function show(){
console.log('今天我学习了node.js');
}

setInterval(show, 1000);

set Interval函数的定义:
function setInterval(fn, time){
//...计时...
fn();
}

示例2:在html页面body里面写
<script>
;(function(){
$.fn.show = function(url, fn){
var iframe = $('<iframe src="'+url+'" ></iframe>').appendTo('body');

iframe.on('load',function(){
data = $(this.contentDocument).find('body').text();
data = JSON.parse(data);
//data就是JSON数据
fn(data);//回调函数的调用处,获取到数据以后才调用回调函数(回过头再调用你)
});
}
})();

//调用插件获取数据
$('body').show('./data.json', function(a){
console.log(a);
});
</script>


异步的实现
异步的三种实现方式:
1. 回调函数
回调函数不一定是异步,但是异步一定有回调函数
2. 事件
事件源.on('事件名称', 回调函数)
3. promise 承诺对象
1) 什么是promise
promise是ES6中新增的承诺对象,用于对异步的操作进行消息的传递

2) promise的状态
Pending 等待中
Resolved 成功
Rejected 失败

Pending => Resolved
Pending => Rejected

3) promise有什么用?
promise可以用于传递异步消息

示例1:
//异步一定有回调函数
setTimeout(function(){
console.log('111');
},1000); //时间在windows系统里面最小是15ms,即使设置为0ms也会自动修改为15ms

//回调函数不一定是异步
console.log('111');
var arr = [1,2,3,4];
arr.forEach(function(v,i){
console.log(v);
});
console.log('222');

示例2:
var http = require('http');

var server = http.createServer();
server.on('request', function(req, res){
res.writeHead(200, {"Content-Type":'text/html; charset=utf-8'});
res.write('<h1>您下在访问node.js服务器</h1>');
res.end();
});

server.listen(80, function(){
console.log('服务器已运行');
});

示例3:
var fs = require('fs');

//将异步的数据交给p1进行处理
var p1 = new Promise(function(resolve, reject){
//读取file1.txt
fs.readFile('./file1.txt', function(err, data){
if(err){
reject('数据找不到'); //从等待改变成失败
}else{
resolve(data.toString()); //从等待改变成成功
}
});
});

//调用p1对象,统一展示异步的数据
p1.then(function(data){
console.log('promise对象输出的数据:', data);
},function(err){
console.log('promise对象错误展现:', err);
});

//读取file2.txt
var p2 = new Promise(function(resolve, reject){
fs.readFile('./file2.txt', function(err, data){
//file2的异步消息也交给promise对象进行控制
if(err){
reject('file2出错了');
}else{
resolve(data.toString());
}
});
});

//利用Promise对象的all方法可以实现手动调整输出的顺序,相当于把异步变成同步
Promise.all([p1,p2]).then(function(datas){
console.log(datas);
},function(errs){
console.log(errs);
});

缓存区
1. 什么是缓存区?
在内存中开辟一个临时区域用户存储需要运算的字节码

2. 创建缓存区
1) 创建指定长度的缓存区

2) 按指定的数组(编码)创建缓存区
var buf = new Buffer([10进制编码]);

3) 按指定字符创建缓存区
var buf = new Buffer('字符串');

示例1:
//创建指定长度的缓存区
var buf = new Buffer(5); //创建5个字节的缓存区
buf.write('张'); //在node.js中默认使用utf-8编码,一个中文字占3个字节

console.log(buf);

示例2:
//按指定的数组(编码)创建缓存区
var buf = new Buffer([97,98,99,65,66]);
console.log(buf);

示例3:
//按指定字符创建缓存区
var buf = new Buffer('张三');
console.log(buf);
console.log(buf.toString());

3. 写入缓存区
buf.write('字符串');

4. 读取缓存区
buf.toString();

5. 缓存区复制
buf1.copy(buf2); //把buf1的数据复制给buf2

文件系统操作(文件模块fs)
1. 读取文件
由于node.js是服务器端程序,必须要有读写文件操作,在客户端没有这样的功能
文字读写的两种方式:
1) 直接读取
将硬盘上的所有内容全部读入内存以后才触发回调函数
两种写法:
异步:定义一个回调函数,接收读取到的内容
fs.readFile('文件路径', function(错误对象, 数据){
});

同步:几乎所有的fs函数都有同步版本,只需要在异步版本后面添加Sync即可
var data = fs.readFileSync('文件路径');

示例1:
//如果要读取文件,必须使用文件系统模块(fs)
var fs = require('fs');

console.log('111');
//直接读取文件--异步
fs.readFile('./data.json', function(err, data){
console.log(data.toString());
});
console.log('222');

示例2:
console.log('111');
//同步读取文件
var data = fs.readFileSync('./data.json');
console.log(data.toString());
console.log('222');

2) 流式读取
将数据从硬盘中读取一节就触发回调函数,实现大文件操作

2. 写入文件
1) 同步版本
fs.writeFileSync('文件名', '数据');

2) 异步版本
fs.writeFile('文件名', '数据', function(err){
//写完文件以后执行的代码
});

示例:
var fs = require('fs');
var hello = "<h1>Hello Node.js</h1>";
fs.writeFile('index.html', hello, function(err){
if(err){
throw err;
}else{
console.log('文件写入成功');
}
});

3. 读取文件信息:
fs.stat('文件名', function(err, state){
//state是文件的信息对象,包含了常用的文件信息
size 文件大小(字节)
mtime 文件修改时间
birthtime 文件创建时间

//方法
.isFile() 判断当前查看的对象是不是一个文件
.IsDirectory() 判断是不是一个目录
});

示例:
var fs = require('fs');
fs.stat('./data.json', function(err, state){
console.log(state.isFile());
console.log(state.isDrectory());
});

4. 删除文件
fs.unlink('文件路径', function(err){
//...
});

示例:
var fs = require('fs');
fs.unlink('./jquery.js', function(err){
if(err)
{
throw err;
}else{
console.log('删除成功');
}
});

5.递归删除非空目录
需求:编写代码实现删除一个非空目录
1) 读取目录中的文件及文件夹列表
fs.readdir()
2) 读取每一个文件的详细信息
fs.stat()
3) 判断如果是文件
fs.unlink()
4) 判断如果是目录
递归调用自己
5) 删除空目录
fs.rmdir()

示例:
var fs = require('fs');
//使用同步的方式实现
function deldir(p){
//读取文件夹的内容
var list = fs.readdirSync(p);
//遍历数组
for(var i in list){
//list[i] 是当前目录中每一个文件及文件夹的名称
var path = p + '/' + list[i]; //拼接一个从(05.js)当前目录能查找到的路径

var info = fs.statSync(path);
if(info.isFile()){
fs.unlinkSync(path); //如果是文件则删除
}else{
arguments.callee(path); //如果不是文件就是目录,则调用自己再删除该目录
}
}
//删除空目录
fs.rmdir(p);
}

deldir('./data');

6.读取流和写入流
1) 什么流?
所有互联网传输的数据都以流的方式,流是一组有起点有终点的数据传输方式
2) 流的操作
a. 流式读取文件
一节一节的读取数据,一节64kb ==> 65536字节

b. 以流的方式写文件
一节一节的写文件

示例1:读取流
var fs = require('fs');
var data = ''; //存储每次读取的数据

//创建一个可以读取的流
var stream = fs.createReadStream('./file3.txt');
//设置编码为utf8
readerStream.setEncoding('UTF8');

//绑定data事件,当读取到内容就执行
stream.on('data', function(a){
data += a;
});

//读取流的事件:end 完成事件
stream.on('end', function(){
console.log(data);
});

//读取流的事件:error 错误事件
stream.on('error', function(err){
console.log(err.stack);
});

示例2:写入流
var fs = require('fs');
var data = '今天我学习了node.js,真高兴';

//创建一个可以写入的流
var stream = fs.createWriteStream('./file4.txt');
//使用 utf8 编码写入数据
writerStream.write(data, 'UTF8');

stream.end(); //以流的方式写数据必须显示的声明结束

//写入流的事件:finish 完成事件
stream.on('finish', function(){
console.log('写完了');
});

//写入流的事件:error 错误事件
stream.on('error', function(err){
console.log(err.stack);
});

7. 管理pipe
1) 什么是管道?
管道是node.js中提供了一个输出流到输入流的机制。通常我们用于从一个流中获取数据并将数据传递到另外一个流中。

2) 管道语法
输出流.pipe(输入流)

示例(实现大文件的复制):
var fs = require('fs');

var s1 = fs.createReadStream('./file3.txt');
var s2 = fs.createWriteStream('./file6.txt');

//1.以流的方式实现大文件复制,读取一节存一节
s1.on('data', function(a){
s2.write(a);
});

s1.on('end', function(){
s2.end();
console.log('文件复制已完成');
});

//2.使用管道实现大文件的复制
s1.pipe(s2);

8. 链式流
将多个管道连接起来,实现链式处理。

示例(使用链式流实现大文件压缩):
var fs = require('fs');
var zlib = require('zlib');

var s1 = fs.createReadStream('./file3.txt');
var s2 = fs.createWriteStream('.file3.txt.zip');

s1.pipe(zlib.createGzip()).pipe(s2);

常用模块
1. path模块
1) 什么是path模块?
path 模块是node.js中提供的一个系统模块,用于格式化或拼接一个完整的路径

2) path模块的常用方法
path.join() //【重点】将多个字符串拼接成一个完整的路径
path.dirname() //返回路径中的文件夹部分
path.basename() //返回路径中的文件部分(文件名和扩展名)
path.extname() //返回路径中的扩展部分
path.parse() //【重点】解析路径:返回一个对象包含路径中的各个部分

示例:
var path = require('path');

var p1 = "../../../path/../a./b/../c.html";
console.log(path.normalize(p1)); //格式化路径

var p2 = 'day02/hello/zs.html';
console.log(path.join('code', p2));
console.log(path.join(_dirname, p2)); //_dirname是模块的组成部分之一,代表当前模块所在的目录

2. URL模块
1) 什么是url?
url 全球统一资源定位符,对网站资源的一种简洁表达形式,也称为网址。

2) url的构成
//完整结构
协议://用户名:密码@主机名.名.域:端口号/目录名/文件名.扩展名?参数名=参数值&参数名2=参数值2#hash

//http协议的url常见结构:
协议://主机名.名.域/目录名/文件名.扩展名?参数名=参数值&参数名2=参数值2#hash

3) node.js的url模块
在node.js中提供了两套对于url进行处理的API功能。
a. 老的node.js url模块
b. 新的url模块(WHATWG URL标准模块)

示例:
var url = require('url');

var u = 'http://music.163.com:8080/aaa/index.html?id=10#/discover/playlist';

//老的node.js 的url模块
console.log(url.parse(u));

//新的url模块(符合WHATWG标准)
var obj = new url.URL(u);
console.log(obj);


3. http模块
1) 什么是网络?
网络是一个共享、传输信息的虚拟平台

2) 什么是网络协议?
每天有大量的数据在网络上传输,都需要遵循相应的规则,规则就是网络协议

3) 什么是http协议?
http(hypertext transfer protocol)即超文本传输协议,传输超文本内容(文字、图片、视频、音频、动画...)

4) http协议规定的细节
http协议是一种请求应答形式的协议,一次请求,一次应答(响应).

细节:
定义了浏览器以什么格式向服务器发请求
定义了服务器以什么格式解析浏览器发送过来的数据
定义了服务以什么格式响应数据给浏览器
定义了浏览器以什么格式解析服务器响应的数据

5) get方法
get方法用于模仿客户端从服务器获取数据

var http = require('http');
http.get('url', function(res){
//res 是返回对象,接收到服务器响应的所有内容
});

node.js服务器
1. 引入通信模块
2. 创建服务器
3. 监听request事件
4. 监听端口

代码:
var http = require('http');
//创建服务器
var server = http.createServer();

//监听request请求事件,当前请求事件发生时就返回数据
server.on('request', function(req, res){
//req:请求对象,包含了所有客户端请求的数据,请求头,请求主体
//res:响应对象,包含了所有服务器端发送给客户端的数据,响应头、响应主体
res.write('<h1>Hello Node.js</h1>');
res.end();
});

//监听服务器的80端口
server.listen(80, function(){
console.log('服务器已运行');
});

5. http状态码
1) 什么是状态码?
http协议规定的服务器响应数据时的状态编码,就是状态码

2) 常用的状态码
1XX : 表示普通消息,没有特殊含义
2XX : 表示服务器响应成功
200 成功【掌握】

3XX : 表示重定向
301 永久重定向【掌握】
302 临时重定向
304 使用缓存(服务器没有更新过)

4XX : 无法访问
403 权限不足,无法访问
404 资源找不到【掌握】

5XX : 服务器有错
500 服务器端代码有错【掌握】
502 网关错误
503 服务器已崩溃

3) 状态码的使用
res.writeHead(状态码, 响应头对象);

示例:
var http = require('http');
var server = http.createServer();

server.on('request', function(req, res){
res.writeHead(200, {"Content-Type":"text/html; charset=utf-8"});
res.write('<h1>今天星期二</h1>');
res.end();
});

server.listen(80, function(){
console.log('服务器已运行');
});

4) 响应头
Content-Type 响应的文件类型
text/html
注意:未指定响应文件类型时,默认就是html,编码默认是系统编码

Content-Length 响应内容的长度
Access-Control-Allow-Origin 设置响应头可以跨域

示例:
var http = require('http');
var server = http.createServer();

server.on('request', function(req, res){
res.writeHead(200, {"Content-Type":"text/html; charset=utf-8","Access-Control-Allow-Origin":"*"});
res.write('<h1>今天星期二</h1>');
res.end();
});

server.listen(80, function(){
console.log('服务器已运行');
});

5) MIME类型
1) 什么是MIME类型?
MIME类型可以认为是文件类型的表述

2) 常用的MIME类型
.html text/html
.css text/css
.js text/javascript
.json text/json application/json
.xml text/xml
.png image/png
.jpg image/jpeg
.gif image/gif
.mp3 audio/mpeg
.mp4 video/mpeg
.pdf application/pdf
.zip application/x-gzip
......

express框架
1. 什么是express?
express是一个基于node.js的极简、灵活的web开发框架。可以实现非常强大的web服务器功能。

2. express的特点
可以设置中间件响应或过滤http请求
可以使用路由实现动态网页,响应不同的http请求。
内置支持ejs模板(默认是jade模块)实现模板渲染生成html

3. express安装与使用
1) 安装express-generator生成器
cnpm i -g express-generator //安装完成后可以使用express命令

2) 创建项目
express -e 项目名称 //自动创建项目目录
express -e //手动创建项目目录

3) 安装依赖
cnpm i

4) 开启项目 (3种方式)
node app //【推荐】需要手动添加监听端口的代码
app.listen(80, function(){
console.log('服务器已运行');
});
npm start //自动查找当前目录下的package.json文件,找到start对应的命令进行执行
node ./bin/www

5) 测试项目
打开浏览器,输入 localhost

4. express项目的目录说明
1) 目录
bin 可执行文件目录
node_modules 依赖包的目录

public 静态文件根目录
所有的静态文件都应当放在这个目录下(静态html、css、js、图片、字体、视频等资源)

routes 路由模块目录,动态文件的目录
请求发生时,优先找静态文件,如果没有静态存在则找动态路由,如果动态路由也没有,就404

views 视图目录,用于存储所有的ejs模板

2) 文件
app.js 项目的主文件,对整个项目的所有资源进行统筹的安排

package.json 项目描述文件,声明项目的名称、版本、依赖等信息

5. 路由的概念及基本用法
1) 什么是路由?
路由是指接收用户请求,处理用户数据,返回结果给用户的一套程序。可以理解为:动态网页的程序。
后端路由的核心:URL

2) express的路由
express对象自带有一个Router类,可以实例化出路由对象,可以在该对象上挂载非常多的路由节点

3) 路由的写法
//挂载路由线路的写法
router.请求方式('请求地址', function(req, res){
res.send('数据');
});

6. 响应对象
响应对象(res)的方法向客户端返回响应,终结请求响应的循环。如果在路由函数中一个方法也不调用,来自客户端的请求会一直挡土挂起。

1) send 方法(重点中的重点)
send(data) 可以返回任意类型的数据。

res.send(new Buffer('whoop')); //流
res.send({some:'json'}); //json数据
res.send('<p>some html</p>'); //普通文本

//设置状态码,并且返回内容
res.status(404).send('Sorry,we cannot find that!');
res.status(500).send({error:'something blew up'});

2) json 方法
json(data) 返回json对象,一般针对ajax应用。
res.json(null);
res.json({user:'Terry'});

//设置状态码,并返回 json数据
res.status(500).json({error:'message'});

3) render 视图模板

//将渲染视图发送给客户端
res.render('index');

//将视图和数据合并后发送给客户端
res.render('index',{username:"二狗"});

4) download 下载
res.download('./xxx.doc'); //下载当前目录下面的xxx.doc文件
res.download('./xxx.dox', 'yyy.doc'); //下载当前目录下面的xxx.doc文件,并且重命名为yyy.doc

5) redirect 重定向
重定向到从指定的URL路径(浏览器地址变为设置的地址)
res.redirect('/login.html');
res.redirect('http://www.baidu.com');

6) 完整 api
res.app : 同req.app一样
res.append() : 追加指定HTTP头
res.set() 在 res.append() 后将重置之前设置的头
res.cookie(name, value[, option]) : 设置Cookie
option : domain/expires/httpOnly/maxAge/path/secure/signed
res.clearCookie() : 清除Cookie
res.download() : 传送指定路径的文件
res.get() : 返回指定的HTTP头
res.json() : 传送JSON响应
res.jsonp() : 传送JSONP响应
res.location() : 只设置响应的Location HTTP头,不设置状态码或者close response
res.redirect() : 设置响应Location HTTP头,并且设置状态码302
res.send() : 传送HTTP响应
res.sendFile(path[, option][, fn]) : 传送指定路径的文件 -会自动根据文件extension设定Content-Type
res.set() : 设置HTTP头,传入object可以一次设置多个头
res.status() : 设置HTTP状态码
res.type() : 设置Content-Type的MIME类型

7. 请求对象req(重点)
1) 什么是请求对象?
客户端向服务器发送数据的对象,包含请求头和请求主体

2) 接收get方式传的值
语法:req.query.参数名
示例:req.query,id

3) 接收post方式传的值
语法:req.body.参数名

4) 完整api
req.app : 当callback为外部文件时,用req.app访问express的实例
req.baseUrl : 获取路由当前安装的URL路径
req.bodyr : 获得请求主体
req.cookies : 获取Cookie
req.fresh/req.stale : 判断请求是否还新鲜
req.hostname : 获取主机名
req.ip : 获取IP地址
req.originalUrl : 获取原始请求URL
req.params : 获取路由的parameters
req.path : 获取请求路径
req.protocol :获取协议类型
req.query : 获取URL的查询参数串
req.route : 获取当前匹配的路由
req.subdomains : 获取子域名
req.accepts() : 检查请求的Accept头的请求类型
req.acceptsCharsets / req.acceptsEncodings / req.acceptsLanguages
req.get() : 获取指定的HTTP请求头
req.is() : 判断请求头Content-Type的MIME类型

中间件
Express 是一个自身功能极简,完全是由路由和中间件构成一个的web开发框架:从本质上来说,一个Express应用就是在调用各种中间件。

1. 什么是中间件?
中间件(Middleware)本质就是一个函数,它可以访问请求对象(request object(req)),响应对象(response object(res)),和web应用中处于请求-响应循环流程中的中间件,一般被命名为next的变量。(next 尾函数,执行下一个任务)

中间件的功能包括:
执行任何代码
修改请求和响应对象
终结请求-响应循环
调用堆栈中的下一个中间件

如果当前中间件没有终结请求-响应循环,则必须调用 next() 方法将控制权交给下一个中间件,否则请求就会挂起。

2. 自定义中间件
app.use(function(req, res, next){
res.send('我是中间件');
});

3. 尾函数 next
如果中间件不调用next函数,整个请求响应流程就中止不会再往后面执行
调用尾函数相当于调用下一个中间件,执行完以后自己的函数继续执行

4. 应用级中间件
应用级中间件绑定到 app 对象使用 app.use() 和 app.METHOD(),其中,METHOD 是需要处理的HTTP请求方法,例如 GET,PUT,POST等等,全部小写。

示例1://最简单的中间件
var app = express();
//应用的每个请求都会执行该中间件
app.use(function(req, res, next){
req.nowDate = Date.now();
//继续执行下一个中间件或路由

});

示例2:
var app = express();
//应用的每个请求都会执行该中间件
app.use(function(req, res, next){
var accessToken = req.query.accessToken;

//检查请求中是否含有“认证牌”,存在就继续执行
if(accessToken){
next();
}else{
res.send('请求必须包含 token');
}
});

5. 内置中间件
Express中只为我们提供了唯一一个中间件,其他的中间件需要安装

6. 第三方中间件
通过使用第三方中间件从而为 Express 应用增加更多功能。
安装所需要的 node 模块,并在应用中加载,可以在应用级加载,也可以在路由级加载。
下面的例子安装并加载了一个解析 cookie 的中间件: cookie-parser

cnpm i multer --save

var multer = require('multer');
var app = express();

//使用文件上传中间件
app.use(multer({ dest:'./uploads/'}));

中间件列表:https://github.com/senchalabs/connect#middleware

7. 实例
需求:写一个记录网站访问日志的中间件

app.use(function(req, res, next){
var fs = require('fs');
var ip = req.ip;
var time = new Date().toLocaleString();

var data = fs.readFileSync('./2020-04-10.log');
data += '访问时间:'+ time + ' IP:' + ip + '\n';
fs.writeFileSync('./2020-04-10.log', data);

next();
});

数据库的基本介绍
1. 什么是数据库?
数据库(database)是一个按照数据结构进行数据的组织、管理、存入数据的仓库。

2. 关系型数据库
按照关系模型存储数据的数据库,数据与数据之间的关联非常密切,可以实现跨数据表查询数据,占用更少的硬盘实现更多数据存储
T-SQL标准的结构化查询语言是关系型数据库的通用查询语言。
常见的关系型数据库:Mysql sql-server access sqlite oracle ...

结构:一台服务器 => 数据库 => 数据表 => 数据行

3. 非关系型数据库
不按关系模型存储数据的数据库,统称为nosql。
第一层含义:不是SQL,不是关系型数据库。
第二层含义:Not Only SQL,不仅仅是SQL

结构:一台服务器 => 数据库 => 集合 => 文档

4. 非关系型数据库与关系型数据库区别

非关系型数据库的优势:

1) 性能
NOSQL 是基于键值对的,可以想象成表中的主键和值的对应关系,而且不需要经过SQL层的解析,所以性能非常高。

2) 可扩展性
同样也是因为基于键值对,数据之间没有耦合性,所以非常容易水平扩展。

关系型数据库的优势:

1) 复杂查询
可以用SQL语句方便的在多个表之间做非常复杂的数据查询

2) 事务支持
使得对于安全性能很高的数据访问要求得以实现

总结:
数据库功能是用来存储数据的
数据库分为关系型数据库和非关系型数据库(nosql)
关系型数据库是由表和表之间的关系组成的,nosql是由集合组成的,集合下面是很多的文档
非关系型数据库文件存储格式为 BSON (一种JSON的扩展)


5. MongoDB
1) 安装 mongodb软件
选择完全安装

2) 设置环境变量
找到安装目录,找到mongo命令所在位置:C:\Program Files\MongoDB\Server\3.2\bin
将命令位置添加到环境变量:
我的电脑(计算机) 点击鼠标右键-> 属性 -> 高级系统设置 -> 高级 -> 环境变量 -> 修改PATH的值(添加一个分号,将数...)

3) 创建文件夹
在非系统盘(D盘)下创建一个文件夹mongodb,再创建两个子文件夹 db 和 log
d:\mongdb
d:\mongdb\db
d:\mongdb\log

4) 将mongodb挂载成windows服务
使用命令挂载服务
mongod --dbpath "d:\开发区\mongodb\db" --logpath "d:\开发区\mongodb\log\MongoDB.log" --install --serviceName "MongoDB"

服务如何查看
在“计算机”上点鼠标右键 -> 管理 ->服务与应用程序 -> 服务
运行命令 services.msc

补充说明服务的命令
net start mongdb //开启服务
net stop mongdb //关闭服务
sc delete mongdb //卸载服务

5) 常见问题
32位操作系统的问题
必须添加存储引擎设置: --storageEngine mmapv1
mongod --storageEngine mmapv1 --dbpath "d:\mongdb\db" --logpath "d:\mongdb\..."...

win8或win10操作的权限问题
必须以管理员身份运行cmd命令行,再执行挂载服务命令

6. 数据库基础命令
1) 进入数据库管理模式
在命令行中运行:mongo 进入数据库管理模式
在数据库管理模式中只能执行数据库命令,不能执行其它命令

exit 退出数据库管理模式

2) 常用的命令
a. 数据库命令
显示所有的数据库列表
show dbs

创建数据库/进入数据库
use 数据库名
如果数据库名不存在,则创建
如果数据库已存在,则进入

查看当前数据库是谁
db

删除数据库
db.dropDatabase();

b. 集合的命令
显示当前数据库中的所有集合
show collections

创建集合
db.集合名.insert({});
通常,在创建数据时自动创建集合,不需要单独创建

删除集合
db.集合名.drop();

3) 文档(数据)的操作【重点】
a. 新增文档(数据)
db.集合名.insert({JSON数据});
db.集合名.save({JSON数据});

insert 和 save 方法的区别:
.insert() 向集合中插入一条数据【推荐使用】
.save() 向集合中添加一条数据,如果集合中已经存在该数据则更新

b. 查询文档(数据)
db.集合名.find(); 【重点】//查找当前集合中的所有数据
db.集合名.find({条件对象}); //符合条件的数据被查出来
db.集合名.findOne(); //查找集合中的第一条数据
db.集合名.find().pretty(); //将找到的数据以格式化的结果显示出来

c. 修改数据
db.集合名.update(查找对象, 修改结果);

例如:
db.user.update({"username":"张三"},{"age":18});

d. 删除数据
db.集合名.remove({}); //删除当前集合中的所有数据
db.集合名.remove({条件对象});
例如:
db.user.remove({"age":18});
db.user.remove({});

7. 高级用法
1) 按指定条件查询
//查找到所有的女歌星
db.singer.find({"sex":"女"});

//SQL : select * from singer where sex = '女'

//查找所有中国女歌星
db.singer.find({"country":"中国","sex":"女"});

2) 大于
语法:db.集合名.find({字段名:{$gt:值}});

//查找年龄大于50岁的所有数据
db.singer.find({"age":{$gt:50}});

//SQL : select * from singer where age > 50

3) 大于等于
语法:db.集合名.find({字段名:{$gte:值}});

//查找年龄大于等于50岁的所有数据
db.singer.find({"age":{$gte:50}});

4) 小于
语法:db.集合名.find({字段名:{$lt:值}});

//查找年龄小于30岁的所有数据
db.singer.find({"age":{$lt:30}});

5) 小于等于
语法:db.集合名.find({字段名:{$lte:值}});

//查找年龄小于等于30岁的所有数据
db.singer.find({"age":{$lte:30}});

6) 查询指定范围的数据
语法:db.集合名.find({字段名:{$gt:小值, $lt:大值}});

//查找年龄30-0岁之间的所有数据
db.singer.find({"age":{$gt:30,$lt:40}});

//SQL : select * from singer where age>30 and age<40
//SQL : select * from singer where age between 30 and 40

7) $in 子句
语法:db.集合名.find({"字段名":{$in:[数组值]}});

//找出刘德华、张学友、郭富城
db.singer.find({"namme":{$in:['刘德华','张学友','郭富城']}});

//SQL : select * from singer where name in ('刘德华','张学友','郭富城')

8) $nin 子句
语法:db.集合名.find({"字段名":{$nin:[数组值]}});

9) 按数组元素的个数查找
语法:db.集合名.find({"字段名":{$size:数量}});

//查找只有一个代表作的歌手
db.singer.find({"works":{$size:1}});

10) $or 子句
查找多条件时,符合其中一个就找出来
语法:db.集合名.find({$or:[{"字段名":值},{"字段名2":值2}]});

某个活动必须要刘德化参加,另外需要团队的全部女歌手配合演出,领导安排你帮忙打印歌手资料
db.singer.find({$or:[{"name":"刘德华"},{"sex":"女"}]});

//SQL : select * from singer where name="刘德华" or sex="女"

11) 不等于
语法:db.集合名.find({"字段名":{$ne:值}});

12) 取模运算,条件相当于 字段名%10==1 即字段名除以10余数为1的
语法:db.集合名.find({"字段名":{$mod:[10,1]}});

13) $exists
语法:db.集合名.find({"key":{$exists:true|false}});

$exists 字段存在,true返回存在字段key的数据,false返回不存在字段key的数据

8.其它重要语句
1) 排序 sort()
语法:db.集合名.find({}).sort({"字段名":1, "字段名":-1});
说明:1是升序(从小到大),-1是降序(从大到小)

//对所有歌手按年龄进行降序排序
db.singer.find().sort({"age":-1});
db.singer.find().sort({"age":-1, "score":1});

2) 限制输出 limit() skip()
.limit(数字) //限定输出数据的条数
.skip(数字) //跳过指定的数据条数

//只查找年龄最大的三个人
db.singer.find().sort({"age":-1}).limit(3);

//查看年龄在第三、第四、第五的三个人
db.singer.find().sort({"age":-1}).skip(2).limit(3);

扩展:这两个限定输出的语句主要用于分页
数据太多,在一个页面显示不完,可以分多个页面显示数据,就叫分页

场景:现在有100条数据,每页显示10条,一共有多少页?10页
如果当前在第2页上,应当显示哪些数据?11-20之间的数据

3) 返回结果集的条数 count()
语法:db.集合名.find().count();

注意:在加入skip()和limit()这两个操作时,要获得实际返回的结果数,需要一个参数true,否则返回的是符合查询条件的结果总数
db.集合名.find().skip(5).limit(5).count(true);

3) 模糊查询
语法:db.singer.find({"字段名":/值/});

//需求:找出所有姓刘的人
db.singer.find({"name":/刘/i});


mongoose模块
1. 什么是mongoose?
mongoose是一个基于node.js的用于操作mongodb数据库的第三方模块

2. mongoose的安装
先配置一个express框架
cnpm install mongoose

3. mongoose的使用
1) 连接数据库
语法:mongoose.connect("mongodb://服务器:端口号/数据库名", 回调函数);

示例:
var mongoose = require('mongoose');
mongoose.connect("mongodb://127.0.0.1:27017/数据库名", function(err){
if(err){
throw err;
}else{
console.log('数据库连接成功');
}
});

2) 定义骨架 schema
概述:schema骨架量一种数据结构声明,不具备数据库的操作能力

语法:
var schema = new mongoose.Schema({
字段名:类型,
字段名:类型
});

示例:
var singerSchema = new mongoose.Schema({
name:String,
country:String,
age:Number
});

骨架的类型:String Number Date Buffer ObjectId Array ...

3) 创建模型 model
概述:model模型是一种根据骨架创建出来的模型,具备数据库操作的能力,通常用于读取数据库
语法:
var singerModel = mongoose.model('模型名称', 骨架, '集合名称');

示例:
var singerModel = mongoose.model('singer', 'singerSchema', 'singer');

4) 创建实体 Entity
概述:Entity实体是根据模型创建出一个实例,具备数据库操作的能力,通常用于写数据(新增、修改、删除)
var singer = new singerModel();
singer.属性名 = 值;
singer.save(); //将添加到实例上的属性保存到数据库中
singer.remove(); //删除数据

4. 数据操作(增删改查)
1) 查询数据/读取数据
//根据条件进行数据查询,可以找出多条数据
模型.find({条件}, function(err,data){
//data是从数据库中读取到的数据
//find方法找出来的数据一定是一个数组,即使没有数据也是一个空数组
});

//通过ID查找一条数据
模型.findById('id', function(err, data){
//findById找出来的数据是一个对象
});

//另一种常用的写法(为了实现更好的链式调用)
模型.find({}).skip().limit.sort().exec(function(err, data){

});

2) 新增数据
var list = new listModel(); //根据模型创建实例
list.name = '张三';
list.age = 18;
//将新增的数据保存到数据库中
list.save(function(err){
console.log('新增成功');
});

//注意:骨架中没有定义属性及类型,不能加进数据库


3) 删除数据
思想:
第一步:找出要被删除的数据
第二步:调用remove()方式删除数据

实现:
listModel.findById(id),exec(function(err, data){
data.remove(function(err){
console.log('删除成功');
});
});

4) 修改数据
思想:
第一步:找出被修改的数据
第二步:将数据修改以后保存回数据库

实现:
listModel.findById(id).exec(function(err, data){
data.name = 新的值;
data.age = 新的值;
data.save(function(err){
console.log('修改成功');
});
});

posted @ 2020-04-10 19:47  海角之上  阅读(253)  评论(0编辑  收藏  举报