express
安装:npm install express
启动:node ./app.js
注:npm init -y 在所在目录安装包管理配置文件
创建express
// 1.导入express
const express=require('express')
// 2.创建web服务器
const app=express()
// 3.启动web服务器
app.listen(8080,()=>{
console.log('express server running at http://127.0.0.1:8080')
})
监听get请求
// 4.监听客户端的get post请求,并相应具体内容
// res.send()
app.get('/user',(req,res)=>{
// 向客户端响应JSON对象
res.send({
name:'张三',
age:20,
gender:'男'
})
})
监听post请求
// res.send()
app.post('/user',(req,res)=>{
// 向客户端响应文本字符串
res.send('请求成功')
})
获取url中携带的查询参数
// req.query
app.get('/',(req,res)=>{
// req.query可获取客户端的url查询参数 默认是空对象
console.log(req.query)
res.send(req.query)
})
获取url中的动态参数
// req.params
app.get('/user/:id',(req,res)=>{
// req.params是动态匹配到的url参数,默认是空对象
console.log(req.params)
res.send(req.params)
})
托管静态资源
// 调用express.static()
// ./js为静态资源目录,调取资源是目录本身不在url中
app.use(express.static('./js'))
托管多个静态资源
多次调用express.static(),依次查找,找到停止
挂载路径前缀
// 调用express.static()
app.use('/js',express.static('./js'))
nodemon
自动重启
安装:npm install -g nodemon
启动:nodemon ./app.js
路由
映射关系 比如10086一个按键对应一个服务
Express中的路由
请求类型,URL,处理函数
// 请求类型,URL,处理函数
app.post('/user',(req,res)=>{
// 向客户端响应文本字符串
res.send('请求成功')
})
模块化路由
- 创建路由对应js文件
- 调用express.Router()函数创建路由对象
- 向路由对象上挂载具体路由
- 使用module.exports向外共享路由对象
- 使用app.use()函数注册路由模块
注册路由模块
// router.js
const express=require('express')
const router=express.Router()
router.get('/user/list',(req,res)=>{
res.send('Get userList')
})
router.post('/user/add',(req,res)=>{
res.send('add new user')
})
module.exports=router
// express.js
const express=require('express')
const app=express()
const router=require('./router')
// app.use()注册全局中间件
app.use(router)
app.listen(8081,()=>{
console.log('express server running at http://127.0.0.1:8081');
})
为路由模块添加统一前缀
// express.js
app.use('/api',router)
Express中间件
中间处理的环节,拥有输入输出,类似于多级处理排污水
调用流程
客户端对服务器的请求,连续调用多个中间件对请求进行预处理,处理完毕后服务器再响应回客户端。
中间件的格式
app.post('/user',(req,res,next)=>{
// 包含next的为中间件函数
next()
})
next()函数的作用
next()交给下一个函数处理,实现多个中间件函数调用,路由为最终处理环节
定义中间件函数
// 定义中间件
const mw=(req,res,next)=>{
console.log('最简单的中间件')
next()
}
注册全局中间件
app.use(mw)
中间件化简
app.use((req,res,next)=>{
console.log('中间件化简')
next()
})
中间件的作用
多个中间件可以共享同一份res和req,因此可以再上游中间件中统一为他们添加自定义方法和属性,供下游使用。
app.use((req,res,next)=>{
// 到达服务器事件
const time=Date.now()
// 赋值给req传递给下游函数
req.startTime=time
next()
})
app.get('/',(req,res)=>{
res.send('home page'+req.startTime)
})
定义多个全局中间件
app.use()并行,从上到下运行中间件
局部生效的中间件
不使用app.use()
// 局部中间件
const mw=(req,res,next)=>{
console.log('最简单的中间件')
next()
}
// 中间件只在该路由生效
app.get('/',mw,(req,res)=>{
res.send('home page')
})
app.get('/user',(req,res)=>{
res.send('home page')
})
定义多个局部中间件
// 中间件只在该路由生效
app.get('/',mw1,mw2,(req,res)=>{res.send('home page')})
app.get('/u',[mw1,mw2],(req,res)=>{res.send('home page')})
注意事项
- 中间件放在路由之前
- 可连续调用多个中间件
- 中间件一定要调用next()
- next()要放在最后
- 多个中间件共享req和res
中间件分类
分类 | 函数 |
---|---|
应用级别 | use(),get(),post(),绑定在app上的中间件 |
路由级别 | express.Router() |
错误级别 | 防止出错(err,req,res,next) |
内置级别 | express.static(),express.json(),express.urlencoded() |
第三方中间件 | body-parser |
错误级别中间件
// 错误级别中间件
app.get('/u2',(req,res)=>{
// 人为的制造错误
throw new Error('服务器内部发生错误')
res.send('home page')
})
// 防止项目崩溃
// 必须注册在所有路由之后(例外)
app.use((err,req,res,next)=>{
console.log('发生了错误'+err.message);
res.send('Error'+err.message)
})
内置中间件
express.json()
//app.use(express.json())
app.use(express.json())
app.post('/user',(req,res)=>{
// 服务器可以使用req.body接受客户端发送过来的请求体数据
console.log(req.body);
// 不配置中间件,则为undefine
res.send('home page')
})
服务器可以使用req.body接受客户端发送过来的请求体数据
如果不配置任何解析的中间件,则req.body默认为undefine
express.urlencoded()
// app.use(express.urlencoded({extended:false}))
app.use(express.urlencoded({extended:false}))
app.post('/user',(req,res)=>{
// 服务器可以使用req.body接受客户端发送过来的请求体数据
console.log(req.body);
// 不配置中间件,则为undefine
res.send('home page')
})
第三方中间件
body-parser
安装:npm i body-parser
const parser=require('body-parser')
app.use(parser.urlencoded({extended:false}))
app.post('/user',(req,res)=>{
console.log(req.body);
res.send('home page')
})
自定义中间件
解析表单数据
- 定义中间件
- 监听req的data事件
- 监听req的end事件
- 使用querystring来解析请求数据
- 数据挂载在req.body
- 封装
自己写解析表单数据中间件
// 导入内置的querystring模块
const qs=require('querystring')
// 解析表单数据的中间件
app.use((req,res,next)=>{
// 保存客户端请求体
let str=''
// 监听req.data
req.on('data',(chunk)=>{
str+=chunk
})
req.on('end',()=>{
// 把字符串解析成对象
const body=qs.parse(str)
// console.log(body);
req.body=body
next()
})
})
app.post('/user',(req,res)=>{
res.send(req.body)
})
封装
// express
const customBodyParser=require('./fengzhuang')
app.use(customBodyParser)
app.post('/user',(req,res)=>{
res.send(req.body)
})
// 封装页面
const qs=require('querystring')
const bodyParser=(req,res,next)=>{
// 保存客户端请求体
let str=''
// 监听req.data
req.on('data',(chunk)=>{
str+=chunk
})
req.on('end',()=>{
// 把字符串解析成对象
const body=qs.parse(str)
// console.log(body);
req.body=body
next()
})
}
module.exports=bodyParser
Express写接口
创建服务器
// 导入express
const express=require('express')
// 创建服务器实例
const app=express()
// 启动服务器
app.listen(8080,()=>{
console.log('http://127.0.0.1:8080')
})
创建api路由模块
// express.js
// 导入express
const express=require('express')
// 创建服务器实例
const app=express()
const router=require('./apiRouter.js')
app.use('/api',router)
// 启动服务器
app.listen(8080,()=>{
console.log('http://127.0.0.1:8080')
})
// router.js
const express=require('express')
const router=express.Router()
// 挂载对应路由
module.exports=router
编写get接口
// 挂载对应路由
router.get('/get',(req,res)=>{
// 通过req.query获取客户端查询字符串的树
const query=req.query
res.send({
status:0,
msg:'get 请求成功',
data:query
})
})
编写post接口
// express.js
// 一定要配置中间件来解析body
app.use(express.urlencoded({extended:false}))
// router.js
router.post('/post',(req,res)=>{
// 一定要配置中间件来解析body
const body=req.body
res.send({
status:0,
msg:'post 请求成功',
data:body
})
})
跨域问题
CORS中间件
安装:npm install cors
// 在路由之前配置cors中间件
const cors=require('cors')
app.use(cors())
CORS响应头
- res.setHeader(‘Access-Contorl-Allow-Origin’,‘指定url’)
- res.setHeader(‘Access-Contorl-Allow-Headers’,‘额外(9个)请求头’)
- res.setHeader(‘Access-Contorl-Allow-Methods’,‘POST’,‘DELETE’)
简单请求和预检请求区别
简单请求:与服务器之间只会发生一次请求
预检请求:发生两次,option预检成功后发送真正的请求
JSONP
// 在配置cors之前配置jsonP
app.get('/api/jsonp',(req,res)=>{
const funname=req.query.callback
const data={name:'zs',age:30}
const scriptStr=`${funname}(${JSON.stringify(data)})`
res.send(scriptStr)
})
前后端身份认证
cookie
session的认证机制
配置express-session中间件
安装:npm install express-session
注册:
const session=require('express-session')
app.use(session({
secret:'zifuchuan',
resave:false,
saveUninitialized:true
}))
使用session存储登录信息
app.post('/api/login',(req,res)=>{
if(req.body.username!=='admin'||req.body.password!='000000'){
return res.send({status:1,msg:'登陆失败'})
}
req.session.user=req.body
req.session.isLogin=true
res.send({
status:0,
msg:"登录成功"
})
})
使用session取数据
app.get('/api/username',(req,res)=>{
if(!req.session.isLogin){
return res.send({status:1,msg:'没有登录'})
}
res.send({
status:0,
msg:'success',
username:req.session.user.username
})
})
清空session
app.post('/api/logout',(req,res)=>{
req.session.destroy()
res.send({
status:0,
msg:'退出成功'
})
})
JWT(JSON Web Token)认证机制
session身份认证需要结合cookie,因为cookie不支持跨域,如果前后端跨域,则使用jwt进行前后端身份认证。
组成部分:Header.Payload.Signature
Header Signature:确保Token的安全性
Payload:加密信息
在服务器生成并返回Token之后,通常会存储在客户端的localStorage或者sessionStorage中
每次请求都要将该字符串放在http请求头的Authorization字段中
Authorization:Bearer <token>
安装jwt相关
安装
npm i jsonwebtoken express-jwt
jsonwebtoken:将json数据生成为jwt字符串
express-jwt:将jwt字符串解析还原成json数据
导入
const jwt=require('jsonwebtoken')
const expressJWT=require('express-jwt')
定义secret加密解密密钥
- 加密
const secretKey='12as4d56a4d'
// 在登录成功之后,调用 jwt.sign() 方法生成 JWT 字符串。并通过 token 属性发送给客户端
// 用户信息 加密密钥 配置token的有效期
const tokenStr=jwt.sign({username:userinfo.username},secretKey,{expiresIn:'30s'})
res.send({
status: 200,
message: '登录成功!',
token: tokenStr // 要发送给客户端的 token 字符串
})
- 解密
// 注册将 JWT 字符串解析还原成 JSON 对象的中间件
// 通过加密密钥去解密json
// unless配置不需要访问权限接口
// app.use(expressJWT({secret:secretKey}).unless({path:[/^\/api\//]}))
app.use(expressJWT({secret:secretKey,algorithms:['HS256']}).unless({path:[/^\/api\//]}))
使用 req.user 获取用户信息
只要配置了express-jwt,那么用户信息就在req.user中获取,user是自己在解密的时候设置的。
// 这是一个有权限的 API 接口
app.get('/admin/getinfo', function (req, res) {
// 使用 req.user 获取用户信息,并使用 data 属性将用户信息发送给客户端
console.log(req.user);
res.send({
status: 200,
message: '获取用户信息成功!',
data: req.user // 要发送给客户端的用户信息
})
})
捕获解析jwt失败后产生的错误
// 使用全局错误处理中间件,捕获解析 JWT 失败后产生的错误
app.use((err,req,res,next)=>{
if(err.name==='UnauthorizedError'){
//token解析失败所致
return res.send({
status:401,
message:'无效的token'
})
}
res.send({
status:500,
message:'未知错误'
})
})
mysql
安装
npm install mysql
- npm init -y
- npm install mysql
导入模块
//导入mysql模块
const mysql=require('mysql')
//建立数据库连接
const db=mysql.createPool({
host:'127.0.0.1',
user:'root',
password:'123456',
database:'databasename'
})
测试数据库是否连接
db.query('select 1',(err,results)=>{
if(err) return console.log(err);
console.log(results);
})
查询数据
const sqlStr='select * from manage_log'
// 如果是select,那么查询的结果是数组
db.query(sqlStr,(err,result)=>{
if(err) return console.log('查询数据失败');
console.log(result);
})
新增数据
const worker={
work_pwd:'000000',
work_name:'express',
work_tech:'',
Authority:1
}
// 如果是insert,那么results是一个对象 通过affectedRows判断是否插入成功
const sqlStr2='insert into work_info(work_id,work_pwd,work_name,work_tech,Authority) values (?,?,?,?,?)'
db.query(sqlStr2,[null,worker.work_pwd,worker.work_name,worker.work_tech,worker.Authority],
(err,results)=>{
if(err) return console.log(err.message);
if(results.affectedRows===1){
console.log('插入数据成功')
}
})
新增数据(便捷)
如果数据和字段一一对应,那么
const sqlStr2='insert into work_info set ?'
db.query(sqlStr2,worker,(err,results)=>{
if(err) return console.log(err.message);
if(results.affectedRows===1){
console.log('插入数据成功')
}
})
更新数据
// 更新数据
const worker1={
work_pwd:'111111',
work_name:'ex',
work_tech:'',
Authority:1,
work_id:3
}
const sqlStr3='update work_info set work_pwd=?,work_name=? where work_id=?'
// 如果是update,那么results是一个对象 通过affectedRows判断是否插入成功
db.query(sqlStr3,[worker1.work_pwd,worker1.work_name,worker1.work_id],(err,results)=>{
if(err) return console.log(err.message);
if(results.affectedRows===1){
console.log('更新成功');
}
})
更新数据(便捷)
如果数据和字段一一对应,那么
const sqlStr3='update work_info set ? where work_id=?'
db.query(sqlStr3,[worker1,worker1.work_id],(err,results)=>{
if(err) return console.log(err.message);
if(results.affectedRows===1){
console.log('更新成功');
}
})
删除数据
const sqlStr4='delete from work_info where work_id=?'
// 如果是delete,那么results是一个对象 通过affectedRows判断是否插入成功
db.query(sqlStr4,3,(err,results)=>{
if(err) return console.log(err)
if(results.affectedRows===1){
console.log('删除数据成功')
}
})
标记删除
模拟删除,设置status字段,来标记是否被删除,调用update来更改status状态