Node-进阶知识

2020/7/19 Node

# 原生 node

// 原生 node
const http = require('http')
const querystring = require('querystring') // 处理query参数
const server = http.createServer((req, res) => {
    console.log('req', req)
    console.log('res', res)

    const method = req.method
    const url = req.url
    const path = url.split('?')[0]
    const query = querystring.parse(url.split('?')[1])
    const env = process.env.NODE_ENV // 环境变量
    res.setHeader('Content-type', 'application/json') // 字符串的格式是json
    const resData = { method, url, path, query, env } // get请求并且路由是'/api/get'

    if (method === 'GET' && path === '/api/get') {
        res.end(JSON.stringify(resData)) // 返回字符串或二进制
        return
    } // post请求并且路由是'/api/post'

    if (method === 'POST' && path === '/api/post') {
        let postData = ''
        req.on('data', (chunk) => {
            postData += chunk.toString()
        })
        req.on('end', () => {
            resData.postData = postData
            res.end(JSON.stringify(resData)) // 返回字符串或二进制
        })
        return
    } // 未命中路由, 返回404

    res.writeHead(404, {
        'Content-type': 'text/plain'
    })
    res.write('404 Not Found')
    res.end()
})
server.listen(8000, () => {
    console.log('localhost:8000启动成功!')
})
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

# express

// express
1. 使用express脚手架创建
    npm install express-generator -g // 安装
    express test // 创建
    npm install // 安装依赖
    npm start // 运行
2. 在node中引入
npm i -S express
const express = require('express')
const cookieParser = require('cookie-parser')
const logger = require('morgan')
const session = require('express-session')
const redis = require('redis')
const redisStore = require('connect-redis')(session)
const fs = require('fs')
const path = require('path')

const app = express() // 创建 express 应用

const ENV = process.env.NODE_ENV
if (ENV !== 'production') {
  app.use(logger('dev')) // 记录日志, 也可传入 combined 记录更全面的日志
} else {
  const logFileName = path.join(__dirname, 'logs', 'access.log')
  const writeStream = fs.createWriteStream(logFileName, {
    flags: 'a' // 追加写入
  })
  app.use(logger('combined', {
    stream: writeStream
  }))
}

app.use(express.static(path.join(__dirname, 'public'))) // 静态文件
app.use(express.json()) // 解析post请求json格式的数据, 可以直接通过req.body获取
app.use(express.urlencoded({ extended: false })) // 解析post请求其他格式的数据, 可以直接通过req.body获取
app.use(cookieParser()) // 解析cookie, 可以直接通过req.cookie获取

const redisClient = redis.create(lient(3006, '127.0.0.1'))  // 连接redis
const sessionStore = new redisStore({
  client: redisClient
})
app.use(session({ // 设置session
  secret: 'chenj0922', // 密钥
  cookie: {
    path: '/',
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000
  },
  store: sessionStore // 存储到redis中
}))

app.use('/', function(req, res, next) { // 相当于同一处理的路由, 有next
  console.log('路由中间件')
  console.log(req.body) // 可以直接拿到Post请求的参数
  console.log(req.query) // 可以直接拿到get请求的参数
  next()
})
app.get('/get', function(req, res) { // 只针对于get路由, 没有next
  console.log('get路由')
  console.log(req.body) // 可以直接拿到Post请求的参数
  console.log(req.query) // 可以直接拿到get请求的参数
  res.json({ code: 0 }) // 可以直接只用res.json返回json格式的数据, 不用再使用res.end(JSON.stringify(resData))
})
app.post('/post', function(req, res) { // 只针对于post路由, 没有next
  console.log('post路由')
  console.log(req.body) // 可以直接拿到Post请求的参数
  console.log(req.query) // 可以直接拿到get请求的参数
  res.json({ code: 0 }) // 可以直接只用res.json返回json格式的数据, 不用再使用res.end(JSON.stringify(resData))
})

// 参数可以多个, 相当于中间件, 用于校验登录权限等
app.post('/post', fn1, function(req, res) { // 只针对于post路由, 没有next
  console.log('post路由')
  console.log(req.body) // 可以直接拿到Post请求的参数
  console.log(req.query) // 可以直接拿到get请求的参数
  res.json({ code: 0 }) // 可以直接只用res.json返回json格式的数据, 不用再使用res.end(JSON.stringify(resData))
})

// 使 express 监听 5000 端口号发起的 http 请求
const server = app.listen(5000, function() {
  console.log('ok')
})
----------------------------------------------------------------------------------------------
// 路由演示, 有两个文件, 实际匹配的路由是 /blog/get
// app文件
const express = require('express')
const blogRouter = require('./routes/blog')
const app = express() // 创建 express 应用
app.use('/blog', blogRouter)
// 路由blog文件
const express = require('express')
const router = express.Router()
router.get('/get', function(req, res, next) {
  res.json({ code: 0 })
})
module.exports = router
----------------------------------------------------------------------------------------------
// 中间件, 中间件是一个函数,在请求和响应周期中被顺序调用
// 中间件需要在响应结束前被调用,除了异常处理的中间件需要后置
// 如果中间件中有异步代码,则会影响到后面中间件的执行时间
// express 会等异步执行完之后才会继续执行下一个中间件
const myLogger = function(req, res, next) {
  console.log('myLogger')
  next()
}
app.use(myLogger)
----------------------------------------------------------------------------------------------
// 异常处理,通过自定义异常处理中间件处理请求中产生的异常
// 注意:
// 第一,参数一个不能少,否则会视为普通的中间件
// 第二,中间件需要在请求之后引用
1. 异常中间件的回调函数中包含了 4 个参数
2. 异常中间件全局只包含一个
3. 异常中间件可以传递给普通中间件
4. 异常中间件需要放在所有中间件的最后
5. 异常中间件只能捕获回调函数中的异常
  1. 全局的异常捕获需要监听 'uncaughtException' 事件
    process.on('uncaughtException', function(err) => { console.log(err) })
  2. 全局的 promise 异常需要监听 'unhandleRejection' 事件
    process.on('unhandleRejection', function(err) => { console.log(err) })
app.get('/', function(req, res) {
  throw new Error('something has error...')
})

const errorHandler = function (err, req, res, next) {
  console.log('errorHandler...')
  res.status(500)
  res.send('down...')
}

app.use(errorHandler)
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

# KOA

// KOA
1. koa的中间件就是一个函数使用
2. const app = new koa()
   app.use((ctx, next) => { code }) // ctx用于中间件之间的传值,可以直接向ctx上面挂载属性,如ctx.a = 1,在另一个中间件中使用ctx.a获取
   app.listen(3000) // 监听3000端口
3. 洋葱模型,next()就是分界线
  1. 请求进来可能会经过多个中间件,而中间件可能需要依赖其他中间件,所以洋葱模型
     可以相当于如果有调用新的中间件就会一直调用下去,但是调用方本身的中间件并未
     执行完成,当所有调用中间件完成后,才会往上一步一步返回响应,最后结束第一个中间件
  2. 执行的顺序:顺序执行
  3. 回调的顺序:反向执行
  4. 先进后出
4. 中间件的调用都会返回promise,在每个next()方法前面加上await保证执行顺序是洋葱模型
5. koa-router
    const router = new Router()
    router.get('/', (ctx, next) => { code })
    app.use(router.routes())
    router.post('/v1/:id/books', (ctx, next) => {
      const path = ctx.params // 获取url上的参数id
      const query = ctx.request.query // 获取查询参数?后面的
      const headers = ctx.request.header // 获取请求头的内容
      app.use(parser()) // koa-bodyparser中间件
      const body = ctx.request.body // 获取http请求的body内容
    })
6. 在ctx中可以获取到很多: ctx.path、ctx.method等
7. ctx.body = { key: 'book' } // 在浏览器中显示的是json对象
8. 可以使用nodemon app.js来自动重启服务 // npm install nodemon
9. 通过requireDirectory实现路由自动加载
10. try/catch不能捕获到异步代码的异常,需要使用asyncawait来捕获异常
11. 在异步调用中必须使用asyncawait来同步代码
12. 浏览器中或API中返回NO Found是因为API没有返回结果
----------------------------------------------------------------------------------------------
// 示例
1. 使用koa脚手架创建, koa有两个版本, koa1是用generator做的, koa2是用async/await做的
    npm install koa-generator -g // 安装
    koa2 test // 创建
    koa2 -e test // ejs 模板
    npm install // 安装依赖
    npm start // 运行
2. 在node中引入
const Koa = require('koa')
const app = new Koa()
const json = require('koa-json')
const helmet = require('koa-helmet')
const bodyparser = require('koa-bodyparser') // 替换方案是 koa-body,它支持图像上传,koa-bodyparser 不支持
const logger = require('koa-logger') // 只是把在node环境中的console的打印格式优化, 并非日志
const morgan = require('koa-morgan') // 记录日志
const fs = require('fs')
const path = require('path')
const session = require('koa-generic-session')
const redisStore = require('koa-redis')

app.use(helmet()) // 自动添加安全的 hearder 头

app.use(bodyparser({ // 解析post请求json和其他格式的数据, 可以直接通过ctx.request.body获取
  enableTypes: ['json', 'form', 'text']
}))
app.use(json()) // 用于返回 json 格式的字符串

 // 日志
app.use(logger()) // 只是把在node环境中的console的打印格式优化, 并非日志
const ENV = process.env.NODE_ENV
if (ENV !== 'production') {
  app.use(morgan('dev')) // 记录日志, 也可传入 combined 记录更全面的日志
} else {
  const logFileName = path.join(__dirname, 'logs', 'access.log')
  const writeStream = fs.createWriteStream(logFileName, {
    flags: 'a' // 追加写入
  })
  app.use(morgan('combined', {
    stream: writeStream
  }))
}

app.use(require('koa-static')(__dirname + '/public')) // 静态文件

// session配置, 要放在路由注册之前
app.keys = ['chenj099']
app.use(session({
  // 配置cookie
  cookie: {
    path: '/',
    httpOnly: true,
    maxAge: 24 * 60 * 60 * 1000
  },
  // 配置redis
  store: redisStore({
    all: '127.0.0.1:9528'
  })
}))

// 使 koa2 监听 5000 端口号发起的 http 请求
const server = app.listen(5000, function() {
  console.log('ok')
})
----------------------------------------------------------------------------------------------
// 路由演示, 有两个文件, 实际匹配的路由是 /blog/get
// app文件
const blogRouter = require('./routes/blog')
const Koa = require('koa')
const app = new Koa()

 // 引入路由,allowedMethods 表示只支持路由中定义的方法进行访问,即 get 方法不能用 post 访问
app.use(blogRouter.routes(), blogRouter.allowedMethods())

// 路由blog文件
const router = require('koa-router')() // 默认koa2不支持router, 需要引入koa-router
router.prefix('/blog')
router.get('/get', async (ctx, next) => {
  const path = ctx.params // 获取url上的参数
  const query = ctx.request.query // 获取查询参数?后面的
  const headers = ctx.request.header // 获取请求头的内容
  const body = ctx.request.body // 获取http请求的body内容
  ctx.body = { // ctx.body相当于express的res.json
    path,
    query,
    headers,
    body,
    code: 0
  }
})
module.exports = router
----------------------------------------------------------------------------------------------
// ejs 模板
1. <% '脚本' 标签,用于流程控制,无输出
2. <%_ 删除其前面的空格符
3. <%= 输出数据到模板(输出是转义 HTML 标签)
4. <%- 输出非转义的数据到模板
5. <%# 注释标签,不执行、不输出内容
6. <%% 输出字符串 '<%'
7. %> 一般结束标签
8. -%> 删除紧随其后的换行符
9. _%> 将结束标签后面的空格符删除
10. 示例
<!DOCTYPE html>
<html>
  <head>
  <title><%= title %><title>
  </head>
  <body>
    // title 是变量
    <h1><%= title %></h1>

    // 不确定 title 一定存在时前面可以加 locals,否则会报错
    <p><%= locals.title %></p>

    // 条件语句,注意括号要分开
    <div>
      <% if(isFlag) { %>
        <h1>flag 是 true</h1>
      <% } else { %>
        <h1>flag 是 false</h1>
      <% } %>
    </div>

    // 循环
    // list: [{ id: 1, name: 'chen' }, { id: 2, name: 'jie' }]
    <ul>
      <% list.forEach(item => { %>
        <li data-id="<%= item.id %">
          <%= item.name %>
        </li>
      <% }) %>
    </ul>

    // 组件引入,'widget/user-info' 是路径,name 是变量
    <%- include('widget/user-info', { name }) %>

    <script>
      console.log('在 ejs 中可以写 script')
    </script>
  </body>
</html>

// widget/user-info 组件,和正常的模板一样
<div>
  <h1><%= name %></h1>
</div>
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
Last Updated: 2024/6/11 14:20:38
    飘向北方
    那吾克热-NW,尤长靖