Express:第三方模块
安装:npm install express
使用:
const express=require('express')
//创建一个http服务器
const app=express()
//静态资源服务器
app.use(express.static('./'))
const middleware=function(req,res,next){
//req:request对象
//res:response对象
//next:是一个函数,用于执行下一个中间件
console.log('hello middleware')
//res.end(JSON.stringify(a:10,b:20))
res.send({a:10,b:20}) //send里面可以写任何类型数据
}
//监听端口
app.listen(2101.()=>{
console.log('server is running at port 2101')
})
Express中间件(middleware)
中间件是一个封装了某些处理数据功能的函数
使用中间件:use()
里面可以使用多个中间件
app.use([path],...middlewares)
- 分类:
- 内置中间件:express自带的中间件
- express.static()它的返回值是一个中间件
- express.Router( )
- express.urlencoded( )
- express.json( )
- 自定义中间件
- 参数:(req,res,next)=>{ }
- 第三方中间件
- 内置中间件:express自带的中间件
浏览器缓存
- 强制缓存:状态码200 from cache
- 缓存时间未过期,浏览器直接从本地获取
- 协商缓存:状态码304
- 缓存时间已过,浏览器发送请求到服务器进行询问,如果服务器的文件没有修改,则返回304,浏览器直接从本地获取(
如果服务器文件有修改,服务器返回新的文件,状态码为200
)
- 缓存时间已过,浏览器发送请求到服务器进行询问,如果服务器的文件没有修改,则返回304,浏览器直接从本地获取(
RESTful规范
要求使用不同的请求类型事项相应的接口功能
- 请求类型
- get 查
- post 增
- put 改(全改)
- patch 改(部分修改)
- delete 删
const express = require('express')
// 创建一个路由中间件
const router = express.Router();
let goodslist = [];
for(let i=0;i<100;i++){
const goods = {
id:i+1,
name:'goods'+i,
price:(Math.random()*1000).toFixed(2),
imgurl:'img/goods'+i+'.jpg'
}
goodslist.push(goods)
}
// 获取所有商品
// /goods
router.get('/',(req,res)=>{
console.log('goodslist=',req.query);
const {page=1,size=10} = req.query;
// [0,1,2,3,...99]
// page index
// 1 0
// 2 10
// 3 20
// 推导公式: index = (page-1)*size
const index = (page-1)*size;
const end = index+size*1;
const result = goodslist.slice(index,end);
res.send(result)
});
// 获取指定id的商品
// /goods/10
router.get('/:id',(req,res)=>{
// req.params = {id,a}
// 获取动态路由参数
const {id} = req.params;
// const goods = goodslist.filter(item=>item.id==10)[0]
const goods = goodslist.find(item=>item.id==id);
res.send(goods)
});
router.delete('/:id',(req,res)=>{
// 获取动态路由参数
const {id} = req.params;
// const goods = goodslist.filter(item=>item.id==10)[0]
goodslist = goodslist.filter(item=>item.id!=id);
res.send(`删除商品${id}成功`)
});
router.post('/',(req,res)=>{
console.log('query=',req.query)
console.log('body=',req.body)
res.send('添加商品成功')
})
module.exports = router;
路由
- 动态路由:
req.params
- url参数:
req.query
- 请求体:通过请求体发送的数据,必须通过对应的中间件处理
- x-www-form-urlencode: express.urlencode()
- json: express.json()
格式化请求体数据
router.use(express.urlencode( ),express.json( ) )
图片上传:multer
const express = require('express');
const multer = require('multer');
const path = require('path');
const fs = require('fs');
const router = express.Router();
// 1.简单上传:设置上传文件路径(目录不存在会自动创建)
// let uploadMiddleware = multer({ dest: '../public/uploads/avatar' })
// 2. 控制上传细节
// 配置上传参数
let storage = multer.diskStorage({
destination: function (req, file, cb) {
let uploadPath = `../public/uploads/${file.fieldname}`
// 如路径不存在,则自动创建
try{
fs.accessSync(uploadPath)
}catch(err){
fs.mkdirSync(uploadPath,{
recursive:true //递归
})
}
cb(null, uploadPath);
},
// 上传文件保存目录,只有一层目录时可自动创建
// destination:'../public/uploads/avatar',
// 格式化文件名
filename: function (req, file, cb) {
console.log('file=',file);
// 获取文件后缀名
let ext = path.extname(file.originalname);
cb(null, file.fieldname + '-' + Date.now() + ext);
}
})
// 设置文件保存目录
let uploadMiddleware = multer({
storage,
fileFilter(req, file, cb){
let ext = path.extname(file.originalname);
const allow = ['.png','.gif','.jpg'].includes(ext);
cb(null,allow)
},
limits:{
fileSize:1024*1024*2
}
});
// /api/upload/avatar
router.post('/avatar',uploadMiddleware.single('avatar'),(req,res)=>{
// multer.single()中间件会把请求体中文件格式化到req.file属性
console.log('file=',req.file);
res.send('头像上传成功')
})
// /api/upload/goods
router.post('/goods',uploadMiddleware.array('goods',5),(req,res)=>{
// multer.array()中间件会把请求体中文件格式化到req.files属性
console.log('files=',req.files);
console.log('body=',req.body);
res.send('商品上传成功')
})
module.exports = router;
跨域解决方案
- 跨域
- 解决方案:
- JSONP
- CORS:cross origin resource sharing
需要目标服务求授权(通过响应头)才能跨域访问数据
- Access-Control-Allow-Origin
- 单域名
*
- Access-Control-Allow-Methods
- Access-Control-Allow-Headers
- Access-Control-Allow-Origin
- 代理
- http-proxy-middleware
- 解决方案:
解决方案之JSONP
<h1>跨域解决方案之JSONP</h1>
<button class="btnJsonp">jsonp</button>
<script>
// jsonp核心:
// 1. 定义一个**全局函数**,用户接收数据并处理
// 2. 传递**全局函数名**给后端
(function(){
const btnJsonp = document.querySelector('.btnJsonp')
window.getUser = function(data){
console.log('data=',data);
document.body.removeChild(script);
}
// getUser(data)
let script
btnJsonp.onclick = function (){
script = document.createElement('script');
script.src = 'http://localhost:2101/api/jsonp?callback=getUser';
document.body.appendChild(script);
}
})()
</script>
// 跨域解决方案之jsonp
router.get('/jsonp',(req,res)=>{
// 接收全局函数名
const {callback='getData'} = req.query;
// 读取数据库
let data = [{
id:1,
name:'goods1',
price:998,
imgurl:'img/goods1.jpg'
},{
id:2,
name:'goods2',
price:998,
imgurl:'img/goods2.jpg'
},{
id:3,
name:'goods3',
price:998,
imgurl:'img/goods3.jpg'
}]
res.send(`${callback}(${JSON.stringify(data)})`)
})
跨域解决方案之CORS
<h1>跨域解决方案之CORS</h1>
<script>
const xhr = new XMLHttpRequest
xhr.onload = function(){
console.log('data=',xhr.responseText);
}
xhr.open('get','http://localhost:2101/api/goods',true)
xhr.setRequestHeader('Token','xxxx')
xhr.send()
</script>
const allowOrigin = 'http://localhost:3000,http://localhost:8080'.split(',')
// 跨域解决方案之CORS
router.use('/cors',(req,res,next)=>{
// 判断客户端访问的域名是否在allowOrigin中
const currentOrigin = req.get('Origin');
if(allowOrigin.includes(currentOrigin)){
// res.header("Access-Control-Allow-Origin", currentOrigin);
// res.header("Access-Control-Allow-Methods", "PUT,POST,GET,PATCH,DELETE,OPTIONS");
// res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With,Token");
res.set({
"Access-Control-Allow-Origin": currentOrigin,
"Access-Control-Allow-Methods": "PUT,POST,GET,PATCH,DELETE,OPTIONS",
"Access-Control-Allow-Headers": "Content-Type,Content-Length, Authorization, Accept,X-Requested-With,Token"
})
// 处理预请求
if(req.method=="OPTIONS") {
res.sendStatus(200);/*让options请求快速返回*/
} else{
res.send('cors data')
next();
}
}else{
next();
}
})
解决方案之服务器代理
安装引入http-proxy-middleware
<h1>跨域解决方案之服务器代理</h1>
<script>
const xhr = new XMLHttpRequest
xhr.onload = function(){
console.log('data=',xhr.responseText);
}
// 直接请求目标服务器报跨域限制
// xhr.open('get','https://offer.qfh5.cn/api/iq?sort=hot',true)
// xhr.open('get','http://localhost:2101/api/proxy/iq?sort=hot',true)
xhr.open('get','http://localhost:2101/api/proxy/company',true)
xhr.send()
</script>
// 跨域解决方案之服务器代理
// 目标地址:https://offer.qfh5.cn/api/iq?sort=hot
// 代理步骤
// 1. 请求:http://localhost:2101/api/proxy/iq?sort=hot
// 2. 改为目标服务器:https://offer.qfh5.cn/api/proxy/iq?sort=hot
// 3. 删除多余路径:https://offer.qfh5.cn/api/iq?sort=hot
const proxyMiddleware = createProxyMiddleware({
// 目标服务器
target: 'https://offer.qfh5.cn',
changeOrigin: true,
pathRewrite:{
'^/api/proxy':'/api'
}
})
router.use('/proxy',proxyMiddleware);
页面渲染模式
- 客户端渲染:BSR
页面开始是空页面,通过 ajax请求数据到前端遍历生成html结构再渲染到页面
- 优点:
- 局部刷新
- 用户体验
- 用户交互
- 优点:
- 服务器渲染:SSR
页面结构和内容在服务器生成后再返回给前端渲染
- 优点:
- SEO搜索引擎优化
- 速度较快
- 优点:
简单利用nodejs爬数据
const request = require('request');
const cheerio = require('cheerio');
const superagent = require('superagent')
const iconv = require('iconv-lite')
const fs = require('fs')
const path = require('path');
// request.get('http://localhost:2101/api/goodslist',(err,res,body)=>{
// console.log('body=',body);
// const $ = cheerio.load(body)
// const goodslist = [];
// $('.goodslist li').each((idx,el)=>{
// const price = $(el).find('.price').text().match(/[\d\.]+/);
// const goods = {
// id:$(el).data('id'),
// name:$(el).find('h4').text(),
// price:price ? price[0] : 0,
// imgurl:$(el).find('img').attr('src')
// }
// goodslist.push(goods);
// });
// console.log('goodslist=',goodslist);
// })
// superagent.get('https://www.wbiao.cn/search/share/list/?bCode=111&w=%E7%99%BE%E8%BE%BE%E7%BF%A1%E4%B8%BD&exposedFrom=1').then((body,a,b)=>{
// console.log('body=',body,a,b)
// })
// https://www.converse.com.cn/men-sneakers/category.htm?iid=hpnvc06012015
request({
url:'https://list.suning.com/0-20006-0.html?safp=d488778a.phone2018.103327226421.1&safc=cate.0.0&safpn=10003.00006',
encoding: null,
},(err,res,body)=>{
body = iconv.decode(body, 'utf-8');
// console.log('body=',body);
const $ = cheerio.load(body)
const phone = []
// $('.goodslist li').each((idx,el)=>{
// const price = $(el).find('.price').text().match(/[\d\.]+/);
// const goods = {
// id:$(el).data('id'),
// name:$(el).find('h4').text(),
// price:price ? price[0] : 0,
// imgurl:$(el).find('img').attr('src')
// }
// goodslist.push(goods);
// });
$('.general li').each((idx,el)=>{
const $el = $(el);
const $img = $el.find('img');
const imgurl = 'http:'+$img.attr('src');
// const price = $(el).find('.def-price').text().match(/[\d\.]+/);
const price = $(el).find('.def-price').text();
const goods = {
// id:$(el).data('id'),
imgurl:imgurl,
price:price ? price[0] : 0,
name:$(el).find('.title-selling-point a').text(),
}
phone.push(goods);
// 下载图片: request请求一个图片地址,得到一个文件流
const filename = path.basename(imgurl);
// 创建一个写入流
const writerStream = fs.createWriteStream('../public/img/'+filename)
request(imgurl).pipe(writerStream);
const item = {
name: $img.attr('alt'),
imgurl:filename
}
})
console.log('phone=',phone);
// 存入json文件
fs.writeFile('./mock/phone.json',JSON.stringify(phone),(err)=>{
if(err){
console.log('文件写入失败')
}else{
console.log('文件写入成功')
}
});
})
编写数据接口
server.js
const express = require('express')
const allRouter = require('./routers')
const app = express();
// 静态资源服务器
app.use(express.static('../public'))
// 数据接口
app.use('/api',allRouter)
const PORT = 2101;
app.listen(PORT,()=>{
console.log(`server is running at port ${PORT}`)
})
index.js
const express = require('express');
const userRouter = require('./user')
const goodsRouter = require('./goods')
const regRouter = require('./reg')
const loginRouter = require('./login')
const multer = require('multer')
const formData = multer();
// formData.single()
// formData.array();
// formData.none();
const router = express.Router();
router.use(
express.urlencoded({extended:true}),
express.json(),
express.raw(),
// 格式化formData<文本类型数据>到req.body
formData.none(),
)
router.use('/user',userRouter);
router.use('/goods',goodsRouter);
router.use('/reg',regRouter);
router.use('/login',loginRouter);
module.exports = router;
login.js
const express = require('express');
const router = express.Router();
// 登录
router.get('/',(req,res)=>{
// 获取前端传入的用户名和密码,查询数据库是否能匹配
})
module.exports = router;
reg.js
const express = require('express');
const router = express.Router();
// 注册
router.post('/',(req,res)=>{
})
// 检测用户名是否存在
router.get('/check',(req,res)=>{
})
module.exports = router;