测试需要快速过一遍express的基本使用方法
直接安装express
使用
- express和koa的区别](https://zhuanlan.zhihu.com/p/372128788)
- egg.js企业级开发框架
npm install exress --save
可以使用express-generator生成项目框架
$ npx express-generator
warning: the default view engine will not be jade in future releases
warning: use `--view=jade' or `--help' for additional options
create : public/
create : public/javascripts/
create : public/images/
create : public/stylesheets/
create : public/stylesheets/style.css
create : routes/
create : routes/index.js
create : routes/users.js
create : views/
create : views/error.jade
create : views/index.jade
create : views/layout.jade
create : app.js
create : package.json
create : bin/
create : bin/www
install dependencies:
$ npm install
run the app:
$ DEBUG=expressdemo:* npm start
使用 nodeamon 监听项目文件 的变动,当代码被修改后,nodemon
会自动帮我们重启项目。只需要将node
更换为使用nodemon
即可
npm install nodemon -g
nodemon index.js
Basic
// index.js
const express = require('express')
const app = express()
app.get('/user', (req, res) => { res.send('get user') })
app.post('/user', (req, res) => { res.send("post user") })
app.listen(8090, () => {
console.log("Server is listening on 127.0.0.1:8090")
})
Static
// app.use(express.static('public'))
app.use('static',express.static('public'))
Router
路由指的是客户端的请求与服务器处理函数之间的映射关系。Express 中的路由分 3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数
每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的 URL 同时匹配成功,则 Express 会将这次请求,转交给对应的 function 函数进行处理
注意点:(1)按照定义的先后顺序进行匹配(2) 请求类型和请求的URL同时匹配成功才会调用对应的处理函数
Express 不建议将路由直接挂载到 app 上,而是推荐将路由抽离为单独的模块
// router.js
const express = require('express')
var router = express.Router()
// router.get('/user/list', (req, res) => { res.send('get user') })
// router.post('/user/add', (req, res) => { res.send("post user") })
router
.get('/user/list', (req, res) => { res.send('get user') })
.post('/user/add', (req, res) => { res.send("post user") })
module.exports = router
// index.js
const express = require('express')
const userRouter = require('./router')
const app = express()
// curl 127.0.0.1/static/html/test.txt
// app.use(express.static('public'))
app.use('static',express.static('public'))
// curl 127.0.0.1/api/user/list
// app.use(userRouter)
app.use('/api',userRouter)
app.listen(8090, () => {
console.log("Server is listening on 127.0.0.1:8090")
})
Middleware
Express 的中间件,本质上就是一个 function 处理函数
多个中间件之间,共享同一份 req 和 res。因此可以在上游的中间件中,统一为 req 或 res 对象添加自定义的属性或方法,供下游的中间件或路由进行使用
// index.js
const app = express()
const mw1 = (req,res,next)=>{
console.log('This is a simple middleware2')
next()
}
const mw2 = (req,res,next)=>{
console.log('This is a simple middleware1')
next()
}
// 局部生效中间件,只在当前路由生效
app.get('/testmw', mw1, (req, res) => {
res.send('success')
})
//app.get('/testmw', mw1, mw2, (req, res) => {
// res.send('success')
//})
app.get('/testmw', [mw1, mw2], (req, res) => {
res.send('success')
})
// 全局生效中间件
app.use(mw1)
注意:一定要在路由之前注册中间件。客户端发送过来的请求,可以连续调用多个中间件进行处理。执行完中间件的业务代码之后,需要调用 next() 函数。为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码。连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象
5种中间件分类:① 应用级别的中间件 ② 路由级别的中间件 ③ 错误级别的中间件 ④ Express 内置的中间件 ⑤ 第三方的中间件
// 应用级别
app.get('/', mw, (req, res) => {
res.send('success')
})
// 路由界别
router.use((req, res) => {
res.send('success')
next()
})
错误级别,专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。必须有 4 个形参,形参顺序从前到后,分别是 (err, req, res, next)
。注意:错误级别的中间件,必须注册在所有路由之后
app.get('/', mw, (req, res) => {
throw new Error('Server internal error!')
res.send('success')
})
app.use((err, req, res, next)=>{
console.log('raise error:' + err.message)
res.send('Error ' + err.message)
})
express内置中间件
express.static
- 托管静态资源的内置中间件express.json
- 解析json格式的请求express.urlencoded
- 解析URL-encoded格式的请求体数据
app.use(express.urlencoded({extended: false}))
自定义中间件
const qs = require('querystring')
// querystring.parse("name=whitemu&sex=man&sex=women");
// return:
// { name: 'whitemu', sex: [ 'man', 'women' ] }
functiono bodyParse(req, res, next){
req.on('end', () => {
const body = qs.parse(str)
req.body = body
next()
})
}
app.use(bodyParse)
Cors
CORS (Cross-Origin Resource Sharing)由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源。 浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头, 就可以解除浏览器端的跨域访问限制
CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置,即可请求开启了 CORS 的接口。
解决接口跨域问题的方案主要有两种:
- CORS(主流的解决C方案,推荐使用)
- JSONP(有缺陷的解决方案:只支持 GET 请求)
客户端在请求 CORS 接口时,根据请求方式和请求头的不同,可以将 CORS 的请求分为两大类:简单请求和预检请求
只要符合以下任何一个条件的请求,都需要进行预检请求: ① 请求方式为 GET、POST、HEAD 之外的请求 Method 类型 ② 请求头中包含自定义头部字段 ③ 向服务器发送了 application/json 格式的数据
在浏览器与服务器正式通信之前,浏览器会先发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,所以这一 次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据
使用cors
中间件解决跨域问题
如果指定了 Access-Control-Allow-Origin 字段的值为通配符 *,表示允许来自任何域的请求
npm install cors
const cors = require('cors')
app.use(cors())
...
res.setHeader('Access-Control-Allow-Origin', '*')
// res.setHeader('Access-Control-Allow-Origin', 'http://example.com')
默认情况下,CORS 仅支持客户端向服务器发送如下的 9 个请求头: Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、 Content-Type (值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一) 。如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers 对额外的请求头进行声明,否则这次请求会失败
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, X-Custom-Header')
默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。 如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods 来指明实际请求所允许使用的 HTTP 方法
res.setHeader('Access-Control-Alow-Methods', '*')
express
也提供了项目初始化工具express-generator
Database
Mysql
以下为可用的测试数据库示例
-
https://www.sqlfather.com/
-
https://www.yiibai.com/sql/sql-sample-database.html
-
https://github.com/q343509740/Store/blob/master/classicmodels.sql
简单的增删改查
const mysql = require('mysql');
const db = mysql.createPool({
host : 'xxxxxxxxxxxxxx',
user : 'admin',
password : 'xxxxxxxxxx',
database : 'xxxxx'
});
var sql = 'SELECT * from users';
db.query(sql,function (err, result) {
if(err) return console.log('[SELECT ERROR] - ',err.message);
console.log(result);
});
const user = {username:'sam',password:'sampasswd'}
var sql = 'INSERT INTO users (username,password) VALUES (?, ?)';
db.query(sql,[user.username, user.password],(err,results)=>{
if(err) return console.log('[INSERT ERROR] - ',err.message)
if(results.affectedRows === 1){ console.log('Insert data successfully');}
})
const user = {id:3, username:'aaa',password:'aaaasswd'}
var sql = 'UPDATE users SET username=?, password=? WHERE id=?';
db.query(sql,[user.username, user.password, user.id],(err,results)=>{
if(err) return console.log('[INSERT ERROR] - ',err.message)
if(results.affectedRows === 1){ console.log('Insert data successfully');}
})
var sql = 'DELETE FROM user WHERE id=?';
db.query(sql,3,(err,results)=>{
if(err) return console.log('[INSERT ERROR] - ',err.message)
if(results.affectedRows === 1){ console.log('Update data successfully');}
})
如果数据对象的每个属性和数据表的字段一一对应,则可以通过如下方式快速插入数据
const user = {username:'sam',password:'sampasswd'}
var sql = 'INSERT INTO users (username,password) VALUES (?, ?)';
db.query(sql2,user2,(err,results)=>{
if(err) return console.log('[INSERT ERROR] - ',err.message)
if(results.affectedRows === 1){ console.log('Delete data successfully');}
})
Redis
安装和启动
sudo amazon-linux-extras install redis6
#使用默认配置文件,在6379端口
redis-server
redis-cli -h 127.0.0.1 -p 6379
127.0.0.1:6379> ping
PONG
默认配置文件
$ sudo cat /etc/redis/redis.conf | grep -v "#" | grep -v -e "^$"
bind 127.0.0.1 -::1
port 6379
daemonize no
loglevel notice
logfile /var/log/redis/redis.log
连接redis
注意:redis4.0和redis3.0的语法不兼容
const redis = require('redis');
(async () => {
const redisClient = redis.createClient({ url: 'redis://127.0.0.1:6379/0' })
redisClient.on('ready', () => {
console.log('redis is ready...')
})
redisClient.on('error', (err) => {
console.log(err);
})
await redisClient.connect();
const status = await redisClient.set('name', 'sam', redis.print)
console.log(status);
const value = await redisClient.get('name')
console.log(value);
await redisClient.quit()
})()
Session
Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由naem,value和其它几个用于控制 Cookie 有效期、安全性、使用范围的可选属性组成。
客户端第一次请求服务器的时候,服务器通过响应头的形式,向客户端发送一个身份认证的 Cookie,客户端会自动 将 Cookie 保存在浏览器中。 随后,当客户端浏览器每次请求服务器的时候,浏览器会自动将身份认证相关的 Cookie,通过请求头的形式发送给 服务器,服务器即可验明客户端的身份。由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造
session的工作原理
安装 express-session 中间件,在项目中使用 Session 认证
npm install express-session
注册和使用,通过 req.session 来访问和使用 session 对象,从而存储用户的关键信息
var session = require('express-session')
app.use(session({
secret: 'salt',
resave: false,
saveUninitialized: true
}))
app.post('/login', (req, res) => {
if (req.body.username !== 'admin' || req.body.password !== 'sample') {
return res.send({ status: 1, msg: 'fail to login' })
}
req.session.user = req.body
req.session.login = true
res.send({ status: 0, msg: 'success to login' })
})
app.post('/user', (req, res) => {
if (req.session.login) {
return res.send({ status: 1, msg: 'fail' })
}
res.send({ status: 0, username: req.session.user.username })
})
app.post('/logout', (req, res) => {
req.session.destroy()
res.send({ status: 0, msg: 'success to logout' })
})
Session 认证机制需要配合 Cookie 才能实现。而Cookie 默认不支持跨域访问
JWT的工作原理
用户的信息通过 Token 字符串的形式,保存在客户端浏览器中。服务器通过还原 Token 字符串的形式来认证用户的身份
JWT 通常由三部分组成,分别是 Header(头部)、Payload(有效荷载)、Signature(签名)
- Payload 部分才是真正的用户信息,它是用户信息经过加密之后生成的字符串。
- Header 和 Signature 是安全性相关的部分,只是为了保证 Token 的安全性。
客户端收到服务器返回的 JWT 之后,通常会将它储存在 localStorage 或 sessionStorage 中。此后,客户端每次与服务器通信,都要带上这个 JWT 的字符串,从而进行身份认证。推荐的做法是把 JWT 放在 HTTP 请求头的 Authorization 字段中
Authorization: Bearer <token>
安装包, jsonwebtoken 用于生成 JWT 字符串 ,express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
npm install jsonwebtoken express-jwt
使用jwt认证
const jwt = require('jsonwebtoken')
const expressJWT = require('express-jwt')
const secretKey = 'salt'
app.post('/login', (req, res) => {
if (req.body.username !== 'admin' || req.body.password !== 'sample') {
return res.send({ status: 1, msg: 'fail to login' })
}
res.send({
status: 0,
msg: 'success to login',
token: jwt.sign({ username: userinfo.username }, secretKey, { expiresIn: '1h' })
})
})
服务器可以通过 express-jwt 这个中间件,自动将客户端发送过来的 Token 解析还原成 JSON 对象
//.unless indicate which api dont't need auth
app.use(expressJWT({secret: secretKey}).unless({path:[/^\/api\//]}))
Other
pm2
PM2是node进程管理工具,可以利用它来简化很多node应用管理的繁琐任务,如性能监控、自动重启、负载均衡等
npm install pm2 -g
pm2 start app
pm2 stop app
pm2 stop all
pm2 reload
ENV_VARIABLE=DEV pm2 restart app --update-env --log-date-format "YYYY-MM-DD HH:mm"
pm2 log app
multer
npm install multer
解析表单数据
upload.single() 是一个局部生效的中间件,用来解析 FormData 格式的表单数据
- 将文件类型的数据,解析并挂载到 req.file 属性中
- 将文本类型的数据,解析并挂载到 req.body 属性中
const multer = require('multer')
const path = require('path')
// init
const upload = multer({ dest: path.join(__dirname, '../uploads') })
router.post('/add', upload.single('cover_img'), addArticle)
addArticle = (req, res) => {
console.log(req.body)
console.log(req.file)
res.send('ok')
}
bcryptjs
加密用户数据等
npm i bcryptjs
// 同步
const bcrypt = require('bcryptjs')
const salt = bcrypt.genSaltSync(10)
var pwd = bcrypt.hashSync('123456', salt)
var isOk = bcrypt.compareSync('123456', pwd)
// 异步
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash('123456', salt, (err, pwd) => {
bcrypt.compare('123456', pwd, (err, isOk) => {
console.log(isOk)
})
})
})
Promisify
将函数promise
化
const fs = require('fs')
const readFile = promisify(fs.readFile)
let content = await readFile('./index.html','utf8')