# nodejs入门

官方文档

# nodejs能解决什么问题?

  1. 实现高并发用户连接
  2. 实现高性能服务器
  3. 非阻塞I/O及事件循环机制

# nodejs安装

nodejs官网下载

# 先实现一个helloworld热个身

  1. 新建一个helloworld.js
  2. 使用require引入模块
  3. 使用http.createServer()方法创建服务器
  4. 使用listen来绑定8000端口
  5. 通过request,response来接收和响应参数,代码如下:
var http = require("http");
http.createServer((request, response) => {
    // 发送 HTTP 头部
    // HTTP 状态值: 200 : OK
    // 内容类型: text/plain
    response.writeHead(200, { 'Content-Type': 'text/plain' });
    //发送响应数据
    response.end('hello world')
}).listen(8000);
console.log('server running at http://127.0.0.1:8000');
1
2
3
4
5
6
7
8
9
10
  1. 打开该路径下的终端,输入命令
node helloworld.js
1
  1. 打开浏览器访问http://127.0.0.1:8000

# 模块

编写稍微大一点的程序时会将代码模块化,在NodeJS中,一般将代码合理拆分到不同的JS文件中,每一个文件就是一个模块,而文件路径就是模块名。 都有reuqire exports module三个预先定义好的变量可供使用。

# require

用于在当前模块中加载和使用别的模块,传入模块名,返回一个模块导出对象,可以用相对路径,可以用绝对路径,文件名后的.js可以省略,也可以加载和使用JSON文件举例:

var foo1=reuqire('./foo');
var foo2=reuqire('./foo.js');
var foo3 = require('/home/user/foo');
1
2
3

# exports

用于导出模块公有方法和属性。别的模块通过require函数使用当前模块时得到的就是当前模块的exports对象:

exports.hello=function(){
    console.log('hello');
}
1
2
3

# module

可以访问到当前模块的一些相关信息,但更多的用途是替换当前模块的导出对象。例如模块导出对象默认是一个普通对象,如果想改成一个函数的话,可以使用以下方式。

module.exports=function(){
    console.log('hello');
}
1
2
3

# 小案例

  1. 新建俩个js:counter.jsnode.js
  2. 在counter.js中输入:
var i = 0;

function count() {
    return ++i;
};
exports.counter = count;
1
2
3
4
5
6
  1. 在node.js中输入
var count1 = require('./counter');
var count2 = require('./counter');
console.log(count1.counter());
console.log(count2.counter());
console.log(count2.counter());
1
2
3
4
5
  1. 在终端输入node node.js,结果如下:
$ node main.js
1
2
3
1
2
3
4

# 模块路径解析规则

require函数支持第三种形式的路径,写法类似于foo/bar,并依次按照以下规则解析路径,直到找到模块位置

  1. 内置模块
  2. node_modules目录
  3. NODE_PATH环境变量

# 文件操作

NodeJS提供了基本的文件操作API

# demo-小文件拷贝

fs.readFile(src1, function(err, data) {
    if (err) throw new Error('something wrong was happended');
    fs.writeFile(src2, data, function(err) {
        if (err) throw new Error('something wrong was happended');
    })
})
1
2
3
4
5
6

# API简述

# Buffer数据块

Js语言自身只有字符串类型,没有二进制,因此NodeJS提供了一个与String对等的全局构造函数Buffer来提供对二进制数据的操作。除了可以读取文件得到Buffer的实例外,还能够直接构造。

# Stream数据流

当内存中无法一次装下需要处理的数据时,或者一边读取一边处理更加高效时,我们就需要用到数据流。NodeJS中通过各种Stream来提供对数据流的操作。

# fs文件系统

fs模块提供的API基本可以分为以下三类:

  • 文件读写属性,常用的有fs.statfs.chmodfs.chown
  • 文件内容读写,常用的有fs.readFilefs.readdirfs.writeFilefs.mkdir
  • 底层文件操作,常用的有fs.openfs.readfs.close

# path路径

path 模块提供了一些实用工具,用于处理文件和目录的路径

# 网络操作

# http

'http'模块提供两种使用方式:

  • 作为服务端使用时,创建一个HTTP服务器,监听HTTP客户端请求并返回响应。
  • 作为客户端使用时,发起一个HTTP客户端请求,获取服务端响应。

# https

https模块与http模块极为类似,区别在于https模块需要额外处理SSL证书。 在服务端模式下,创建一个HTTPS服务器的示例如下。

var options = {
        key: fs.readFileSync('./ssl/default.key'),
        cert: fs.readFileSync('./ssl/default.cer')
    };

var server = https.createServer(options, function (request, response) {
        // ...
    });
1
2
3
4
5
6
7
8

# URL

一个完整的URL的各组成部分:

    href
 -----------------------------------------------------------------
                            host              path
                      --------------- ----------------------------
 http: // user:pass @ host.com : 8080 /p/a/t/h ?query=string #hash
 -----    ---------   --------   ---- -------- ------------- -----
protocol     auth     hostname   port pathname     search     hash
                                                ------------
                                                   query
1
2
3
4
5
6
7
8
9
  • 可以使用.parse将一个URL字符串转换成URL对象
  • .format方法将一个URL对象转换为URL字符串
  • .resolve方法可以用于拼接URL

# querystring

用于实现URL参数字符串与参数对象的互相转换

querystring.parse('foo=bar&baz=qux&baz=quux&corge');
/* =>
{ foo: 'bar', baz: ['qux', 'quux'], corge: '' }
*/

querystring.stringify({ foo: 'bar', baz: ['qux', 'quux'], corge: '' });
/* =>
'foo=bar&baz=qux&baz=quux&corge='
*/
1
2
3
4
5
6
7
8
9

# Zlib

提供了数据压缩和解压的功能。当我们处理HTTP请求和响应时,可能需要用到这个模块

# net

net 模块用于创建基于流的 TCP 或 IPC 的服务器(net.createServer())与客户端(net.createConnection())。

# 进程管理

# Process

process 对象是一个全局变量,提供了有关当前 Node.js 进程的信息并对其进行控制

在NodeJS中可以通过process.argv获取命令行参数。但是比较意外的是,node执行程序路径和主模块文件路径固定占据了argv[0]和argv[1]两个位置,而第一个命令行参数从argv[2]开始。为了让argv使用起来更加自然,可以按照以下方式处理。

function main(argv) {
    // ...
}
main(process.argv.slice(2));
1
2
3
4

# child_process

使用child_process模块可以创建和控制子进程。该模块提供的API中最核心的是.spawn,其余API都是针对特定使用场景对它的进一步封装,算是一种语法糖。

# cluster

cluster模块是对child_process模块的进一步封装,专用于解决单进程NodeJS Web服务器无法充分利用多核CPU的问题。使用该模块可以简化多进程服务器程序的开发,让每个核上运行一个工作进程,并统一通过主进程监听端口和分发请求。

# 框架express

express官网

# 入门

1. mkdir myapp
2. cd myapp
3. npm init -y
4. npm install express
1
2
3
4

# Express生成器

使用express-generator快速创建应用程序框架

1. npm install express-generator -g
2. express myapp
3. cd myapp
4. npm install
5. npm start
1
2
3
4
5

生成的应用程序具有以下目录:

├── app.js
├── bin
│   └── www
├── package.json
├── public
│   ├── images
│   ├── javascripts
│   └── stylesheets
│       └── style.css
├── routes
│   ├── index.js
│   └── users.js
└── views
    ├── error.pug
    ├── index.pug
    └── layout.pug

7 directories, 9 files
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# 路由

var express = require('express');
var app = express();
app.get('/', function(req, res) {
    res.send('hello');
})
app.listen(3000);
1
2
3
4
5
6

# app.route()

var express = require('express');
var http=require('http');
var app = express();
app.route('/book').get(function(req, res) {
    res.send('Get a random book');
});
http.createServer(app).listen(3000);
1
2
3
4
5
6
7

# express.Router

var express = require('express');
var router = express.Router();

// middleware that is specific to this router
router.use(function timeLog(req, res, next) {
  console.log('Time: ', Date.now());
  next();
});
// define the home page route
router.get('/', function(req, res) {
  res.send('Birds home page');
});
// define the about route
router.get('/about', function(req, res) {
  res.send('About birds');
});

module.exports = router;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

# Request方法

# Response方法

举例:

//查询
router.post('/', function(req, res) {
    List.find({}, (err, listDoc) => {
        if (err) {
            res.json({
                status: '1',
                msg: '查询失败',
                result: ''
            })
        } else {
            res.json({
                status: '0',
                msg: '查询成功',
                result: listDoc
            })
        }
    })
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//添加网址
router.post('/addWeb', function(req, res) {
    let titleItem = req.body.titleItem;
    let link = req.body.link,
        name = req.body.name,
        description = req.body.description;
    if (!titleItem) {
        res.json({
            status: '1',
            msg: '数值为空',
            result: ''
        })
        return false
    }
    List.updateOne({ title: titleItem }, {
        $push: {
            'children': {
                'link': link,
                'title': name,
                'description': description
            }
        }
    }, (err, listDoc) => {
        if (err) {
            res.json({
                status: '1',
                msg: '插入失败',
                result: ''
            })
        } else {
            res.json({
                status: '0',
                msg: '插入成功',
                result: listDoc
            })
        }
    })

});
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

# 中间件

中间件函数是可以访问请求对象(req)、响应函数(res)以及应用程序请求————响应周期中的next函数,next函数是Express路由器的一个函数,当被调用时,它会在当前中间件之后执行中间件。

# demo-配置一个中间件函数

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

var requestTime = function (req, res, next) {
  req.requestTime = Date.now()
  next()
}

app.use(requestTime)

app.get('/', function (req, res) {
  var responseText = 'Hello World!<br>'
  responseText += '<small>Requested at: ' + req.requestTime + '</small>'
  res.send(responseText)
})

app.listen(3000)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# 应用程序中间件

使用app.use()app.Mthod()函数将程序级中间件绑定到app对象的实例

要从路由器中间件堆栈跳过其余的中间件函数,请调用next('route')将控制权传递给下一个路由,注意:next('route')仅适用于使用app.METHOD()或router.METHOD()函数加载的中间件函数。

app.get('/user/:id', function (req, res, next) {
  // if the user ID is 0, skip to the next route
  if (req.params.id === '0') next('route')
  // otherwise pass the control to the next middleware function in this stack
  else next()
}, function (req, res, next) {
  // send a regular response
  res.send('regular')
})

// handler for the /user/:id path, which sends a special response
app.get('/user/:id', function (req, res, next) {
  res.send('special')
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 路由器级中间件

var app = express()
var router = express.Router()

// a middleware function with no mount path. This code is executed for every request to the router
router.use(function (req, res, next) {
  console.log('Time:', Date.now())
  next()
})

// a middleware sub-stack shows request info for any type of HTTP request to the /user/:id path
router.use('/user/:id', function (req, res, next) {
  console.log('Request URL:', req.originalUrl)
  next()
}, function (req, res, next) {
  console.log('Request Type:', req.method)
  next()
})

// a middleware sub-stack that handles GET requests to the /user/:id path
router.get('/user/:id', function (req, res, next) {
  // if the user ID is 0, skip to the next router
  if (req.params.id === '0') next('route')
  // otherwise pass control to the next middleware function in this stack
  else next()
}, function (req, res, next) {
  // render a regular page
  res.render('regular')
})

// handler for the /user/:id path, which renders a special page
router.get('/user/:id', function (req, res, next) {
  console.log(req.params.id)
  res.render('special')
})

// mount the router on the app
app.use('/', router)
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

# 内置中间件

从版本4.x开始,Express不再依赖于Connect,之前包含在Express中的中间件函数现在位于不同的模块中,查看中间件函数列表

Express具有以下内置中间件函数:

  • express.static提供静态资源,如HTML文件、图像等。
  • express.json使用JSON的有效负载解析传入的请求,注意:适用于Express 4.16.0+。
  • express.urlencoded使用URL编码的有效负载解析传入的请求,注意:适用于Express 4.16.0+。

# 第三方中间件

使用第三方中间件为Express应用程序添加功能。

安装Node.js模块以获得所需的功能,然后在应用程序级别或路由器级别将其加载到你的应用程序中。

以下示例说明了安装和加载cookie解析中间件函数cookie-parser。

$ npm install cookie-parser
1
var express = require('express')
var app = express()
var cookieParser = require('cookie-parser')

// load the cookie-parsing middleware
app.use(cookieParser())
1
2
3
4
5
6

# 静态文件

要提供静态文件(如images、CSS文件和JavaScript文件),请使用Express中的express.static内置中间件功能,函数签名是:

express.static(root, [options])
//example,加载public目录下的文件
app.use(express.static(path.join(__dirname, 'public')))
//http://localhost:3000/images/kitten.jpg
//http://localhost:3000/css/style.css
//http://localhost:3000/js/app.js
1
2
3
4
5
6

# 其他问题

# 如何处理404响应?

app.use(function (req, res, next) {
  res.status(404).send("Sorry can't find that!")
})
1
2
3

# 如何设置错误处理程序?

app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).send('Something broke!')
})
1
2
3
4

# 如何呈现纯html?

没有必要使用res.render()函数“渲染”HTML,如果你有特定文件,请使用res.sendFile()函数,如果要从目录提供许多资源,请使用express.static()中间件函数。

# express实例

上传图片功能,调用的阿里接口

const express = require('express')
const app = express()
var multiparty = require('multiparty');
var fs = require('fs');
var request = require('request');

var apiRoutes = express.Router()

app.all('*', function(req, res, next) {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");
    res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
    res.header("X-Powered-By", ' 3.2.1')
    if (req.method == "OPTIONS") res.send(200); /*让options请求快速返回*/
    else next();
});


apiRoutes.post('/ali', function(req, res) {
    var path = "";
    var form = new multiparty.Form(); //实例化表单
    var url = 'https://kfupload.alibaba.com/mupload';
    form.uploadDir = 'upload'; //定义上传路径为根目录/upload
    //上传完成后处理
    form.parse(req, function(err, fields, files) {
        console.log(files);
        if (files) {
            path = files.file[0].path;
        } else {
            console.log("没图片");
        }
        upload(path, url, res);
    });
});

function upload(path, url, res) {
    var options = {
        'method': 'POST',
        'url': url,
        'headers': {},
        formData: {
            'name': path,
            'scene': 'aeMessageCenterV2ImageRule',
            'file': {
                'value': fs.createReadStream(path),
                'options': {
                    'filename': 'filename',
                    'contentType': null
                }
            }
        }
    };
    request(options, function(error, response) {
        if (error) throw new Error(error);
        res.send(response.body);
    });
}

app.use('/pic', apiRoutes)

const port = process.env.PORT || 4000
module.exports = app.listen(port, function(err) {
    if (err) {
        console.log(err)
        return
    }
    console.log('Listening at http://localhost:' + port + '\n')
})
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