node基础(书写简单api接口)
什么是node.js
javascript的运行时环境
javascript运行在浏览器的时候:操作DOM,操作BOM,语法ECMAScript
javascript运行在node的时候:
操作文件
操作数据库
开启web服务 ...
不能操作DOM,不能操作BOM
node.js的安装
版本识别: 16.14.2 node -v
LTS: 稳定版本,建议安装 Current: 最新版本
主版本号.子版本号.修订版本号 v16.14.2
子版本号为偶数的是稳定版本,为奇数的是非稳定版本
修订版本号变化是是内部bug的一些处理
建议安装LTS的偶数版本
在node环境中运行js代码
方法一:(不推荐)
直接在终端运行: node+回车
进入js编辑环境,就可以输入js代码
方法二:
把你要在node环境中运行的js代码写在一个js文件
在终端运行: node js文件路径
node的版本管理
我在公司要管理多个node项目
项目一: node 10.16.0
项目二: node 14.12.2
就需要在电脑上安装多个不同的node版本,随时切换 就可以使用nvm这个node的版本管理工具
API
path模块(路径模块)
**path.join()**
将所有给定的路径拼接成一个完整的路径
规范化生成的路径
语法:path.join('/目录1','目录2','目录3/目录4','目录5')
返回值:/目录1/目录2/目录3/目录4/目录5
**path.resolve()**
同path.join()
区别:会将路径或者路径片段转换成绝对路径
path.resolve(__dirname,'static')
url模块
**url.parse()**
作用:把网址解析成对象
语法: url.parse(urlString[,parseQueryString, slashesDenoteHost])
urlString:表示url地址
parseQueryString:布尔值,如果为true,就和解析查询字符串,否则不解析
slashesDenoteHost:如果为true表示//后面,/前面是主机名(如果是完整的url地址不影响)
返回值:url对象
**url.format()**
作用: 把url对象解析成url地址字符串
语法: url.format(url对象)
返回值: url地址字符串
**url.resolve()**
作用:把两段url片段,组成一个完整的url
返回值: url地址字符串
querystring模块(对查询字符串进行更强大的解析)
** querystring.stringify() **
作用:把对象转换成查询字符串
语法:querystring.stringify(要转换的对象[,自定义分隔符,自定义键值对之间的连接符])
默认使用&进行分隔(由第二个参数决定分隔)
var o1 = querystring.stringify({ name:'pipi', course:[11,22,33] },',') //name=pipi,course=11,course=22,course=33
键值用=连接(由第三个参数决定连接)
var o1 = querystring.stringify({ name:'pipi', course:[11,22,33] },',',':') //name:pipi,course:11,course:22,course:33
返回值:查询字符串
var o1 = querystring.stringify({ name:'kimi', course:['nodejs','vue','react'] }) //name=kimi&course=nodejs&course=vue&course=react
**querystring.parse()**
作用:把查询字符串转换成对象
语法:querystring.parse(要转换的查询字符串[,自定义分隔符,自定义键值
返回值:对象
var o1 = querystring.parse("name=kiki&pwd=1234") // { name: 'kiki', pwd: '1234' }
** querystring.escape()**
把字符串进行url编码
var o1 = querystring.escape("http://www.baidu.com/search?wd=千锋教育"); //http%3A%2F%2Fwww.baidu.com%2Fsearch%3Fwd%3D%E5%8D%83%E9%94%8B%E6%95%99%E8%82%B2
** querystirng.unescape()**
对url进行解码
var o1 = querystring.unescape('http://www.baidu.com/search?wd=千锋教育') //http://www.baidu.com/search?wd=千锋教育
fs模块
__filename:当前文件的绝对路径
__dirname: 当前文件所在目录的绝对路径
fs.stat(获取文件/文件夹信息)
fs.stat(__filename,(err,stats)=>{ // err:是错误信息 // stats:获取到的文件信息 console.log(stats) ;// 对象 // 判断是不是文件 console.log(stats.isFile()); // 判断是不是文件夹 console.log(stats.isDirectory()); })
fs.mkdir(创建文件夹)
fs.mkdir('./public',(err)=>{ if(!err){ // 如果没有错误信息 console.log('创建文件夹成功') } })
fs.writeFile(向文件写入内容 异步)
fs.writeFile(filePath,text,(err)=>{ if(!err){ console.log('文件写入成功') } })
fs.appendFile (向文件追加内容 异步)
fs.appendFile(filePath,text,(err)=>{ if(!err){ console.log('成功写入文件') } })
fs.readFile(读取文件)
//方法一 fs.readFile(filePath,(err,data)=>{ if(!err){ // 编码格式转换ss console.log(data.toString())ss } })
//方法二 fs.readFile(filePath,'utf-8',(err,data)=>{ if(!err){ console.log(data) } })
fs.readdir(读取文件目录)
const dirPath = path.resolve(__dirname,'public') // 读取文件目录 fs.readdir(dirPath,(err,files)=>{ // err:错误 // files:是一个数组,里面是读取到的文件夹里面的子文件/子文件夹名称 if(!err){ console.log(files) } }) //返回一个s
fs.rename(文件名修改)
fs.rename("要重命名的文件地址","新名字",(err)=>{ if(!err){ console.log('文件名修改成功') } })
const fs = require('fs') const path = require('path') const filePath = path.resolve(__dirname,'public','js/a.js') const newName = path.resolve(__dirname,'public','js/index.js') fs.rename(filePath,newName,(err)=>{ if(!err){ console.log('文件名修改成功') } })
fs.rm(删除文件)
fs.rm(filePath,(err)=>{ if(!err){ console.log('文件删除成功') } })
fs.rm(删除文件夹)
s.rm(dirPath,{recursive:true},(err)=>{ if(!err){ console.log('文件夹删除成功') } })
注:需要添加{recursive:true},表示递归删除文件夹里面的内容再删除文件夹
events模块
创建事件触发器(第一步)
const EventEmitter = require('events');
const emitter = new EventEmitter()
定义事件监听器(第二步)
emitter.on
(可触发多次)
emitter.on('many',()=>{ console.log('many事件发生了') })
emitter.once
(只触发一次)
emitter.once('one',()=>{ console.log('one事件发生了') })
触发(emit)
emitter.emit('many') emitter.emit('one')
stream模块(流模块)
fs.createReadStream(fromPath)
文件读取流(inp)
fs.createWriteStream(toPath)
文件写入流(out)
读取资源的相关事件
once
inp.once('data',(chunk)=>{ //chunk获取到的数据流 })
on
inp.on('data',(chunk)=>{ //chunk获取到的数据流 })
inp.on('end',()=>{ console.log('数据读取完成'); out.end() })
inp.on('error',()=>{ console.log('数据写入错误') })
pipe
inp.pipe(out) //文件读取流(inp)与 文件写入流(out)之间建立一个管道
http模块
http.request()
语法:
第一步: 书写请求行
const req = http.request(url,callback)
const req = http.request(options,callback)
第二步: 书写请求体
req.write()
第三步: 结束请求
req.end()
例子
//方法一 const https = require('https') const url = "https://www.lagou.com/" let html = ""; const req = https.request(url,(res)=>{ // 设置响应的编码集 res.setEncoding('utf-8'); // 当有数据可以读取的时候触发data事件 res.on('data',(chunk)=>{ html += chunk; }) // 当数据读取完成的时候触发end事件 res.on('end',()=>{ console.log(html) }) // 当有数据读取错误的时候触发error事件 res.on('error',(err)=>{ console.log(err) }) }) req.end()
方式二 const req = https.request({ host:"www.lagou.com", ports:"443", method:'get', path:'/', headers:{} },(res)=>{ // 设置响应的编码集 res.setEncoding('utf-8'); // 当有数据可以读取的时候触发data事件 res.on('data',(chunk)=>{ html += chunk; }) // 当数据读取完成的时候触发end事件 res.on('end',()=>{ console.log(html) }) // 当有数据读取错误的时候触发error事件 res.on('error',(err)=>{ console.log(err) }) }) req.end();
http.get()
语法:同http.request()
区别:
不用设置method,是get请求
不用书写req.write(),get请求没有请求主体
不用调用req.end(),会自动调用
例子:
https.get(url,(res)=>{ // 设置响应的编码集 res.setEncoding('utf-8'); // 当有数据可以读取的时候触发data事件 res.on('data',(chunk)=>{ html += chunk; }) // 当数据读取完成的时候触发end事件 res.on('end',()=>{ console.log(html) }) // 当有数据读取错误的时候触发error事件 res.on('error',(err)=>{ console.log(err) }) })
http.createServer()
作用:开启服务器
nodejs实现webServer
原理:根据req.url来区分客户的请求路径,根据不同的访问路径,给用户响应不同的资源
const http = require('http'); const fs = require('fs') const path = require('path') let num = 0; const server = http.createServer((req,res)=>{ // 只要有客户端请求,就会执行这个函数 // req:请求对象 // res:响应对象 // 获取请求路径 let url = req.url; if(url!="/favicon.ico"){ if(url=="/"){ url = "/index.html" } // 设置响应头 res.setHeader('content-type',"text/html;charset=utf-8") // 通过请求路径,拼接出文件的本地地址 const filePath = path.resolve(__dirname,'public'+url) fs.stat(filePath,(err,stats)=>{ // 文件路径不存在 if(err){ res.statusCode = 404; res.end(`${url} 不存在`) } // 如果是一个文件 else if(stats.isFile()){ res.statusCode = 200; fs.createReadStream(filePath).pipe(res) } // 如果是一个文件夹 else if(stats.isDirectory()){ fs.readdir(filePath,(err,files)=>{ res.statusCode = 200; res.end(files.join('/')); }) } }) } }) server.listen(8080,()=>{ console.log('服务器开启在:http://10.20.158.132:8080') })
特别注意: 没有http.post方法
cheerio(第三方模块)
安装:npm install cheerio -D
使用方法和jquery类型
jquery操作的是dom节点
cheerio操作html字符串
可用于爬虫(具体看E:\2022资料\tree-grup\资料\01node-api\源代码\spider.js)
express(web 开发框架)
下载:npm install express -S
使用三步走
//导入: const express = require('express'); const path = require('path') const app = express() //静态资源中间件函数: express自己写好的,你可以直接使用 //express.static(root, [options]) app.use(express.static(path.resolve(__dirname,'文件夹名'))) //监听 app.listen(9090,()=>{ console.log("http://10.20.158.121:9090") })
[利用 Express 托管静态文件 - Express 中文文档 | Express 中文网 (expressjs.com.cn)](https://www.expressjs.com.cn/starter/static-files.html)
app.use('路由',中间件函数)
如果不写路由,就可以匹配所有的请求
如果写路由,就必须以路由开头的才能匹配
app.use('/abc',(req,res)=>{ //只要有请求,就会执行这个函数是经过express封装的请求对象,可以使用原生node的方法,也有自己的方法 //req:是经过express封装的请求对象,可以使用原生node的方法,也有自己的方法 //res:响应对象,可以使用原生node的方法,也有自己的方法 res.end('all') })
路由中间件
如何实现?
一个主js文件(main.js)
const express = require('express'); // 这个包是为了解析请求主体(要安装) const bodyParser = require('body-parser') const app = express() app.use(bodyParser.json()) // 解析 application/json 格式的请求主体 app.use(bodyParser.urlencoded({ extended: true })) // 解析 application/x-www-form-urlencoded 格式的请求主体 app.use('/user',require('./routers/user.js')) app.listen(8080)
一个专门存储接口api的文件夹(routers),里面放接口文件(user.js)
根据需求写接口
// 用户相关接口 // /user/login?un=xxx&pw=xxx // /user/register?un=xxx&pw=xxx
// 路由中间件 const express = require('express'); const router = express.Router(); // 登录接口 router.get('/login',(req,res)=>{ // 获取请求的查询字符串 console.log(req.query); res.json(req.query) }) // 注册接口 router.post('/register',(req,res)=>{ // 如果要使用req.body获取请求主体 // 需要安装: npm install body-parser -S // 获取请求的请求主体 console.log(req.body) res.json(req.body) }) 注意:导出 module.exports = router;
app.get('路由',中间件函数)
匹配get请求,路由要求精确匹配
如果有写*,表示匹配所有路由
app.get('/login',(req,res)=>{ // express封装的res上的方法 // res.send('字符串') res.send('hello world') // res.json(对象) res.json({a:1,b:2}) })
响应:(选其一)
res.send('字符串')
res.json(对象)
app.post('路由',中间件函数)
匹配post请求,路由要求精确匹配
如果有写*,表示匹配所有路由
app.post('/api',(req,res)=>{ res.json({path:'/api',message:"post"}) })
app.all('路由',中间件函数)
匹配所有请求,路由要求精确匹配
如果有写*,表示匹配所有路由
专门用于处理404的
app.all('*',(req,res)=>{ res.json({code:404}) })
模板中间件(后端把html拼接好,直接返回给前端可以使用ejs模板引擎进行文件渲染)
下载: npm install ejs -S
配置:(在当前这js文件的同级下有一个views的文件夹,里面存储list.ejs文件用来写模板中间件)
app.set('views',path.resolve(__dirname,'views'));
告诉app,模板文件所在的根目录
告诉app,模板文件用哪个包解析 app.set('view engine','ejs');
res.render('在设置的模板文件目录中的模板文件路径',要在模板文件中使用的变量集合)
const express = require('express'); const path = require('path') const app = express(); // 后端把html拼接好,直接返回给前端 // 可以使用ejs模板引擎进行文件渲染 // 下载: npm install ejs -S // 配置: // 告诉app,模板文件所在的根目录 app.set('views',path.resolve(__dirname,'views')); // 告诉app,模板文件用哪个包解析 app.set('view engine','ejs'); app.get('/data',(req,res)=>{ // 在配置了模板引擎以后,res就多了一个方法 : // res.render('在设置的模板文件目录中的模板文件路径',要在模板文件中使用的变量集合) // 在views/list.ejs中可以使用变量:age和arr和count // 把解析完的结果返回 res.render('list',{ age:"hello world
", arr:[ {name:'吴波',task:'纪律委员'}, {name:'海文',task:'学习委员'}, {name:'小磊',task:'班级班长'}, ], count:100 }) }) app.listen(9090)
路由
运行跨域请求
下载: npm install cors -S
导入: const cors = require('cors')
使用:app.use(cors())
解析请求主体
npm install body-parser
app.use(bodyParser.json()) // 解析 application/json 格式的请求主体 app.use(bodyParser.urlencoded({ extended: true })) // 解析 application/x-www-form-urlencoded 格式的请求主体
mongodb
mysql:关系型数据库 -- 表结构
mongodb:非关系型数据库 -- 数据库里面是一个一个的json文件
在术语上
数据库: database
mysql => 数据库里面有表: table
mongodb => 数据库里面有集合: collection
mysql => 表里面有数据行: row
mongodb => 集合里面有文档: document
常用的shell命令:
1、帮助命令
-->help
-->db.help()
2、数据库操作命令
-->show dbs
-->use dbname 切换数据库
-->db / db.getName() 查看当前数据库名称
-->db.stats() 显示当前DB的状态
-->db.version() 查看当前DB的版本
-->db.getMongo() 查看当前DB的连接的主机地址
-->db.dropDatabase() 删除当前DB
3、创建数据库和集合
-->use project 不存在就创建,存在就切换至
-->db.createCollection('user') // 创建user集合
-->show dbs
-->show collections / db.getCollectionNames()
-->db.getCollection('music') 获取指定集合
-->db.printCollectionStats() 打印指定集合的状态
4、集合中的文档操作:
-->db.user.insertOne({}) 向集合中插入文档
-->db.user.insertMany([{},{}])
-->db.user.save({})
-->db.user.updateOne({"name":"cyr"}, {$set:{"age":100}})
-->db.user.updateMany({},{$set:{}})
-->db.user.deleteOne({"name":"jiaming"})
-->db.user.deleteMany({})
-->db.user.remove({}) // 要指出删除的条件
-->save和insert的区别:
+ 新增的数据中存在主键,则再次插入相同的主键时insert() 会提示错误
+ 而save() 则更改原来的内容为新内容
+ 没有saveMany命令
5、聚集集合查询
-->db.集合名.find({查询条件对象},{显示对象})
-->db.user.find() 查询所有记录
-->db.user.find({age:22}) 查询age=22的记录
-->db.user.find({age:{$gt: 22}}) 查询age>22的记录
-->db.user.find({age:{$lt: 22}}) 查询age<22的记录
-->db.user.find({age:{$gte: 22}}) 查询age>=22的记录
-->db.user.find({age:{$lte: 22}}) 查询age<=22的记录
-->db.user.find({age:{$gte:20, $lte:30}}) 查询age>=20 && age<=30的记录
-->db.user.find({name:/cyr/}) 查询name中包含'cyr'的记录
-->db.user.find({name:/^cyr/}) 查询name以'cyr'开头的记录
-->db.user.find({},{name:1,age:1}) 查询所有记录,只返回name和age字段(1-显示 0-不显示)
-->db.user.find({age:{$gt:20}},{name:1,age:1}) 查询age>20的记录,只返回name和age字段
-->db.user.find().sort({age:1}) 按age进行升序排列
-->db.user.find().sort({age:-1}) 按age进行降序排列
-->db.user.find({},{name:1,age:1,_id:0}).sort({age:1})
-->db.user.find({name:'cyr',age:22}) 查询name='cyr' && age=22的记录
-->db.user.find().limit(5) 只查询前5条记录
-->db.user.find().skip(10) 查询10条以后的所有数据
-->db.user.find().skip(5).limit(5) 查询第6~10条记录
-->db.user.find({$or:[{age:20},{age:25}]}) 查询age=20或者age=25的记录
-->db.user.findOne() 查询满足条件的第一条记录
-->db.user.find({age:{$gte:25}}).count() 查询满足条件的记录的总条数
常用:
use 数据库名
db.createCollection('user') 创建集合
db.user.insertOne({}) 向集合中插入文档
node操作mongodb数据库
使用第三方模块: mongoose
下载:npm install mongoose -S
入门创建五步骤
导入 const mongoose = require('mongoose');
链接数据库 mongodb://localhost/数据库名 mongoose.connect('mongodb://localhost/qf');
创建表(第一个参数:表名尽量用复数,第二个参数:定义表里面的字段)mongoose.model('表名',表结构
const Users = mongoose.model('users', { name:String, age:Number, pw:String, create_time:Number });
创建一条数据
const student = new Users({ name: '程磊', age:12, pw:'123456', create_time: Date.now() });
把创建好的数据插入集合中
student.save().then((doc)=>{ console.log('文档插入集合成功') console.log(doc) })
链接数据库
// 1 导入mongoose模块 const mongoose = require('mongoose'); // 2 链接数据库,pinxixi是要链接的数据库名称 mongoose.connect('mongodb://127.0.0.1:27017/pinxixi') // 3 获取数据库链接 const db = mongoose.connection; // 4 事件监听,判断是否链接成功 db.once('open',()=>{ console.log('数据库链接成功') }) db.on('error',(err)=>{ console.log(err) })
创建数据集合
require('./01'); // 导入mongoose模块 const mongoose = require('mongoose'); // 创建集合的字段名和数据类型的规范 const articleSchema = mongoose.Schema({ title:String, content:String, createTime:Number, author:String }) // 根据上面的规范创建一个articles表 const articles = mongoose.model('articles',articleSchema) // 导出创建好的articles表 module.exports = articles;
插入数据
//导入要操作的表 const articlesModel = require('./02') articlesModel.insertMany([ { title:'tree', content:'mongoose 进行插入操作指南3', createTime:Date.now(), author:'多多' }, { title:'four', content:'mongoose 进行插入操作指南4', createTime:Date.now(), author:'次次' } ]).then((doc)=>{ console.log(doc); })
查询数据
//导入要操作的表 const articlesModel = require('./03') //查询title:'tree'的数据 articlesModel.find({title:'tree'}).then((doc)=>{ console.log(doc); }) //查询时间在2022/5/11 13:23:00之前的数据 articlesModel.find({createTime:{$lt:new Date('2022/5/11 13:23:00').getTime()}}).then((doc)=>{ console.log(doc); }) //查询 id articlesModel.findById('627b46c08f5a4300fc74fe7c').then((doc)=>{ console.log(doc); })
更新数据
//更新一条 articlesModel.updateOne({author:"张三"},{$set:{title:"1111"}}).then(result=>{ console.log(result); // 查看更新以后的数据 articlesModel.find({}).then(doc=>{ console.log(doc) }) })
//更新多条 articlesModel.updateMany({author:'次次'},{$set:{title:'danger'}}).then(res=>{ console.log(res); articlesModel.find({}).then(res=>{ console.log(res); }) })
删除数据
//删除一条 articlesModel.deleteOne({title:'tree'}).then(res=>{ console.log(res); articlesModel.find({}).then(doc=>{ console.log(doc); }) })
//删除多条 articlesModel.deleteMany({title:'four'}).then(res=>{ console.log(res); articlesModel.find({}).then(doc=>{ console.log(doc); }) })
注意:
使用模块请先导入
如果一个请求经过多个中间件,多个中间的req和res是共享的
npm包管理工具
什么是npm
是基于commonjs规范的包管理工具
node安装完成以后,npm同步安装 npm -v
nrm可以切换仓库镜像源,建议使用淘宝镜像
管理模块
npm init
生成package.json文件
作用: 便于模块管理 便于代码转移
项目依赖 和 开发时依赖的区别
模块安装
查看指定包信息: npm info name
查看安装了哪些包: npm list
全局安装: npm install name --global
简写: npm i name -g
本地安装项目依赖: npm install name --save
简写: npm i name -S
本地安装开发时依赖: npm install name --save-dev
简写: npm i name -D
安装指定版本
npm install name@版本号 --save
模块安装完成,在node_modules文件夹里面
这个文件夹一般不随项目转移
模块卸载
npm uninstall name -g
npm uninstall name -S
yarn:另一个包管理工具
安装yarn: npm install yarn -g
yarn和npm二选一,即可
重要概念
BSR 客户端渲染(可异步渲染)
前端利用ajax等数据交互方式向后端请求数据
把数据变成html字符串,进行页面的局部刷新
方法:ajax/jsonp/fetch -> 获取数据 -> html节点/html字符串 -> 插入页面
优点:灵活,前后端分离,方便前后端更新维护
缺点:对SEO不友好,增加http请求次数,减缓了页面加载速度
SSR 服务端渲染
在后端把数据从数据库取出来以后,直接通过模板引擎渲染成html字符串
客户端请求页面的时候,直接返回这个页面拼接好的html字符串
也就是说后端可以把数据直接插入html字符串
优点:对SEO友好,减少http请求次数,加快了页面加载速度
缺点:不灵活,前后端耦合性太高
Rest风格的api设计
每一个url代表一种资源
客户端使用GET,POST,DELETE,PUT,PATCH等方式请求资源
从客户端到服务端的每个请求必须包含理解请求所必要的信息
get类型 /user 获取所有用户信息
post类型 /user 插入用户信息
delete类型 /user/1 删除id=1用户信息
put类型 /user/1 更新id=1用户的所有信息
patch类型 /user/1 更新id=1用户的部分信息
安装:npm install -g json-server
创建一个db.json文件,并写入一个接收的数组
{ “user”:[] }
json-server --watch db.json
使用postman访问(http://localhost:3000/user)
post请求即为添加数据 参数写在body中
跨域问题
出现:协议/端口/域名有一个不同就是跨域
解决
cors跨域
在目标服务器设置跨域请求头
res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,== OPTIONS"); res.header("X-Powered-By",' 3.2.1') res.header("Content-Type", "application/json;charset=utf-8");
一般不自己写,引入一个cors中间件(给目标文件引入)
npm install cors -S
const cors = require('cors')
app.use(cors())
jsonp
不是xhr请求,是利用script的src可以跨域的特点
提前准备一个函数,通过src请求后端接口返回这个函数的调用,实参就是需要的数据,函数名通过callback传递: callback=函数名
如果a文件需要访问b中的数据
a文件中需要准备一个返回的函数,并一个script标签连接请求地址
function getList(data){ console.log(data) } 插入一个script标签请求后端接口,后端返回一个函数调用 通过callback参数告诉后端要函数名
b文件则调用
const express = require('express'); const app = express(); app.get('/list',(req,res)=>{ res.jsonp({host:"b server",port:9090}) }
代理
引入包 http-proxy-middleware
使用:
导入:const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
设置 app.use('路径',createProxyMiddleware({
}))
app.use("/list",createProxyMiddleware({ target:'http://localhost:9090/', changeOrigin:true }))
token
token引入
客户端频繁向服务器发送请求,大量的接口需要权限验证,就需要大量的数据库查询用户名和密码是有有效,服务器压力大,有没有一种办法可以不用频繁进行数据库验证,token就是为此产生的
token长什么样?
LKJjLHhlHwerJlkhl324HyiyHhIUYiyGkgIUYG234kgIUYTGKgIUYiuyG
token的目的
减轻服务器的压力
减少频繁的查询数据库
token从哪里来
token是在服务器产生的,当客户端登录或者调用接口的时候,会返回token,客户端用户收到token以后,保存在前端(比如保存在localStorage中),之后请求其他有访问权限的后端接口时,需要把token携带上传递给后端进行验证
如何把token传递给后端呢?
这要根据后端的需要,通常会把token放在headers中进行传递
使用:
下载包: npm install jsonwebtoken
[jsonwebtoken - npm (npmjs.com)](https://www.npmjs.com/package/jsonwebtoken)
WebSocket
是什么
用于建立服务器和客户端的双向交互通信,打开一条通道,客户端可以主动给服务器发消息,服务器也可以主动给客户端发消息,这个链接的一端就被称为socket
主要功能
向对方发送消息
基于事件驱动接收对方发送的消息
WebSocket和socket.io之间的区别
socket.io是一个开源库,他对WebSocket进行封装
增强了浏览器的兼容性
使用起来更方便功能更强大
使用on绑定事件监听
如果绑定的是自定义事件,用emit触
案例(E:\2022资料\tree-grup\test\04-day\socketserver)
聊天室开发
socket服务器开发(socket底层是基于net模块的)
== 接收客户端的消息(基于事件驱动的)
== 群发消息给所有客户
socket客户端开发
== 接收服务端的消息(基于事件驱动的),要渲染在页面上
== 把消息发送给服务器
解释
socket:就是链接到socket服务器的另一端
通过io可以群发
通过socket可以私聊
例子:
// const socket = io('http://10.20.158.121:9090') const socket = io('http://192.168.0.106:9090') let count = 0 socket.on('message-from-server',(message)=>{ count++ $('#top').append(''+message+'') $('#top').scrollTop(count*35) }) $('#send').click(()=>{ let val = $('#message').val().trim() socket.emit('message-from-client',val) $('#message').val('') }) $('#message').keyup((e)=>{ if(e.keyCode == 13){ let val = $('#message').val().trim(); socket.emit('message-from-client',val) $('#message').val('') } })
pm2部署项目
管理多个node项目
安装:npm install -g pm2
pm2管理单个node项目
启动: pm2 start 启动服务器的入口js文件
列出所有应用: pm2 list
结束应用: pm2 stop id
删除进程: pm2 delete id
pm2管理多个node项目
使用命令PM2 ecosystem 或者 pm2 init,初始化配置文件ecosystem.config.js
配置ecosystem.config.js文件
name 指定项目名称
script 指node项目的入口启动文件
启动|重启|停止|删除多个node服务器
pm2 [start|restart|stop|delete] ecosystem.config.js
pm2 list
下载两个包 npm i socket.io -s nom i socket socketserver public index.html socket.io.js(nodemoudel中的) app.js
配置路由 并检测postman 写接口
vue2
vue: 渐进式JavaScript 框架
选择vue的原因
超快的虚拟dom,很多省心的优化
繁荣的生态系统
vue@2+vue-cli+vue-router@3+vuex@3+element ui+vue-i18n@8+...
vue@3+vue-cli+vue-router@4+vuex@4+element pluse+vue-i18n@9+.. == 对开发人员友好 == 兼容性: Vue 不支持 IE8 及以下版本 == 我们使用的v版本2.6.14
选项
data html标签中可以使用data里定义的变量,使用插值语法{{表达式}}
,可以通过vue实例访问里面的data选项
data:{}
data:function(){return {}}
el 选项指定vue实例接管的根标签,书写根标签的css的选择器
methods 里面定义在html标签中可以使用的方法名
$mount方法 挂载
template选项 指定要替换el指定的标签的html字符串
template的优先级高于el指定标签本身的
vue实例在编译html字符串的时候如果现象有template, 就用template选项里面的html字符串,如果没有就用el指定标签本身的html字符串
实际工作用一般都是用template,写在body里面的属性和标签名,遵循html标准,不区分大小写,写在template里面的属性和标签名,区分大小写
computed选项(计算属性)
在computed里定义的函数,使用的时候直接写函数名,不要写函数名(),内部会自动执行这个函数,显示返回值
计算属性的语法:
属性名: 用于获取变量值的函数
属性名: {get:获取变量值的函数,set:设置变量值的函数}
指令:
文本类指令
v-text
v-html
v-pre 加了这个指令的标签及其子元素不会被编译
v-once 加了这个指令的标签及其子元素只会被编译一次,后续就当静态内容
v-cloak vue编译完成以后,会把所有标签上看到的v-cloak移除(隐藏未编译的 Mustache 标签直到实例准备完毕)
与 [v-cloak]{ display:none; }
配用
v-on
语法
v-on:事件类型="事件处理函数名"
v-on:事件类型="事件处理函数名()"
v-on:事件类型="事件处理函数名(参数1,参数2,...)"
有一个特别的参数$event
v-on:可以简写成: @
事件修饰符
.prevent: 阻止事件默认行为
.stop: 阻止冒泡
.self: 只有事件的target是我自己,才触发事件,冒泡上来不触发 (大都用于父类)
.once:只发生一次
.enter v-on:keyup.enter="addTask"
.right
v-on:keydown.alt.67=""
v-on:mousedown.left=""
v-bind
语法: v-bind:属性名="js表达式"
简写::属性名="js表达式"
使用:
图片:src上
class数组语法
class对象语法
class数组语法和对象语言一起使用
style动态属性
动态属性原始语法
style的对象语法
style的数组语法
条件渲染指令: v-if
通过控制节点的插入和销毁来控制显示隐藏
语法: v-if="值为true就显示,值为false就隐藏"
如果要切换显示隐藏的有多个元素可以把他们放在一起
vue里面有一个标签标签叫做template类似js里面的()
v-show 通过控制节点的样式的display来显示和隐藏
语法: v-show="值为true就显示,值为false就隐藏
v-for循环渲染
语法:
v-for="item in arr"
v-for="(item,index) in arr"
v-for="(val,key) in obj"
v-for="(val,key,index) in obj"
v-for和v-if强烈不推荐在同一个标签上使用
vue@2 : v-for 的优先级比 v-if 更高
vue@3 : v-if 的优先级比 v-for 更高
v-for推荐配合key属性使用
key必须是不能重复的
key建议使用字符串或者number
key属性是在比较不同的虚拟dom的时候起作用
如果key相同,就是原来的那个节点
如果key不相同,就不是同一个节点,就需要销毁不存在的key对应的节点
v-model
双向绑定(text变量的变化会导致界面的更新,界面上文本框或者文本域的value的变化会导致text这个变量的更新)
修饰符
.lazy
.trim
.number
复选框:
如果flag初值是布尔值:复选框把选中的状态双向绑定到flag这个变量上
如果flag初值是数组:把复选框选中元素的value同步到这个数组里面
v-model用于组件元素上
等价于
总结:
v-model
== 还可以使用在组件上
== 用于双向数据绑定
== 数据的变化导致界面更新
== 界面更新导致数据的更新
== v-model默认把变量绑定到value属性上
== 变量值的变化会导致元素value的变化
== v-model默认监听input事件
== 把元素的value值更新到变量上
render选项
优先级: render渲染函数>template模板字符串>el元素的html字符串
render(h){ return h('p',{ class:{ a:true, b:true }, style:{ fontSize:'100px' }, attrs:{ id:'box', index:123 } },[ h('h3',"我是p里面的h3标签") ]) }
过滤器
过滤器可以在{{}}
和 v-bind:属性名="" 里面使用
使用方法:
{{变量|过滤器|过滤器(实参1,实参2,....)}}
可接多个
v-bind:属性名="变量|过滤器|过滤器(实参1,实参2,....)"
全局过滤器
语法:Vue.filter('过滤器名称',function(val){})
val就是使用过滤器 | 前面的变量值
return 返回值就是经过处理以后新值
局部过滤器
语法:
new Vue({ filters:{ "过滤器名称":function(val,arg1,arg2,...){ // val就是使用过滤器 | 前面的变量值 // arg1,arg2,...给过滤器函数传递的其他参数 } } })
侦听器(watch)
定义变量的侦听器,一旦被侦听的变量的值发生改变,就会触发侦听器
语法:
{要侦听的变量名: 数据变化的处理函数}
{要侦听的变量名:{
handler:数据变化的处理函数,
deep:是否深度监听,
immediate:定义函数的时候立即执行一次
}}
对象类型如何侦听?
当侦听的变量为一个对象(复杂数据类型)时,直接改变对象中的值,由于监听的对象,比较的是地址,地址没有变化无法监听到,需要监听地址里面内容的变化,需要深度侦听
如果对象list的地址变了,那这个对象的值就变化(也可以侦听到)
this.list = { ...this.list, name:this.newName }
自定义指令
指令都是v-开头的,所以我们自定义指令的时候,不用写v-, 用的时候要加上
全局自定义指令 - 所有vue实例都能使用
语法:
Vue.directive('指令名',{ // 当被绑定的元素插入到 DOM 中时执行inserted这个钩子函数 inserted:()=>{}, // 只调用一次,指令第一次绑定到元素时调用 bind:()=>{}, // 所在组件的 VNode 更新时调用 update:()=>{} })
简写语法:
Vue.directive('指令名',(el,binding)=>{ // bind和update的时候会执行 })
局部自定义指令 - 只有定义指令的vue实例可以使用
new Vue({ directives:{ '指令名': {钩子函数}, '指令名': 钩子函数简写 } })
例子:
Vue.directive('pos',(el,binding)=>{ // bind和update的时候会执行 console.log(el);// 指令所在的节点 console.log(binding);// 书写的修饰符的细节信息 console.log(binding.value);// =后面表达式的值101 console.log(binding.arg);// :后面的left console.log(binding.modifiers);// 指令的修饰符集合 // pos这个指令可以设置元素的定位 // el.style.left = '100px' el.style[binding.arg] = binding.value+"px" })
混入:mixin
定义一个对象,这个对象包含一些vue选项
全局混入:
Vue.mixin(要混入的对象)
所有实例都能使用混入的对象里面的vue选项
如果使用局部混入:
new Vue({mixins:[要混入的对象1,要混入的对象2,...]})
只有定义混入的vue实例可以使用混入的vue选项
注意:
混入对象的选项和实例选项有冲突,用实例的选项的
全局混入:慎用,一般用于做插件
vue@2 在mixin合并data的时候是深合并
vue@3 在mixin合并data的时候是浅合并
生命周期函数在混入的时候,不会冲突,都执行
中央事件总线
即借助vue的一个实例去提供一个’电信公司‘,其他组件可借助这个vue的实例来通信
实现
let bus = new Vue()
bus.$emit('其他组件自定义函数名',要传的数据)
(通过事件 发送消息的人)
mounted(){ bus.$on('自定义函数名',(data)=>{ console.log(data) }) }
(接收信息的人,即去bus这个电信公司办卡的人)
$refs
使用(通常在mounted中获取)
如果给一个普通的标签添加 ref=’xxx‘属性,使用vm.$refs.xxx 获取其真实DOM节点
如果v-for循环出的标签有ref='xx'属性,我就可以通过 vm.$refs.xx 获取这个真实的dom节点的集合
如果一个组件有ref='xx'属性,我就可以通过 vm.$refs.xx 获取这个组件实例对象
provide与inject选项
在祖先实例中通过provide选项定义的变量,可以在其后代中通过inject选项引入来进行使用
使用:
祖先实例中: provide:{ num:100 }
子孙组件中:inject:[ ’num‘ ]
直接{{ num }}使用
axios https://unpkg.com/axios/dist/axios.min.js
基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中
请求方法的简写方法
axios.get(url[, config])
axios.post(url[, data[, config]])
请求方法完整
axios(config)
aixos(url,config)
axios的实例
在实例中设置配置,后面直接用实例发请求,就所有的请求的baseURL都是设置好的,发请求的时候如果用server发请求,和axios发请求的方法是一样的,只不过用server发就都设置好了baseURL和responseType
const server = axios.create({ // axios的config怎么写,这里的配置就怎么写 baseURL:"http://localhost:8888", responseType:'json', headers:{ token:localStorage.getItem('token')||'' } })
请求响应拦截器
请求拦截器(给axios实例添加请求拦截器)
erver.interceptors.request.use(function(config){ // 请求发送成功 // config请求的配置项目,是一个对象 // 你对config做一些事情,做完以后给回去 config.headers.token = localStorage.getItem('token')||""; return config; },function(error){ // 请求发送失败 return Promise.reject(error); })
响应拦截器(给axios实例添加响应拦截器)
server.interceptors.response.use(function(res){ // 响应成功返回 // 在这个函数里面对返回的结果做一些事情,做完以后给回去 return res.data; },function(error){ // 响应失败 return Promise.reject(error); })
取消请求
生成取消令牌的函数 const CancelToken = axios.CancelToken;
生成取消令牌对象 const source = CancelToken.source();
在请求中添加 cancelToken:source.token
axios.get('http://localhost:8888/articles/list?page=1&size=10',{ cancelToken:source.token })
取消发送请求 source.cancel('不要发了')
组件
组件是一个Vue实例,有名字的,组件的概念类似自定义html标签,组件自带HTML结构,自带css样式,自带js交互,用来像html标签,可以是双标签
组件的选项和vue实例的选项一样,就是data一定要是函数写法,组件中的data一般叫做组件的状态
全局组件 在所有vue实例里面都可以使用
局部组件 在定义vue实例的template选项中使用
插件 elementui
组件的生命周期
beforeCreate
created 响应式数据初始化完成以后调用,data中的数据可获取
beforeMount
mounted 会在替换完el中的节点之后调用
beforeUpdate
updated
beforeDestroy
destroyed
其他补充:
vm.$destroy()
完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。
父子组件
父子组件的生命周期
先父组件再子组件,当父组件执行到beforemount时候,即父组件挂载之前(mounted)先挂载子组件,再挂载父组件。
父组件给子组件传值
在子组件上加自定义属性 自定义属性名=‘父组件属性名’
,并在子组件里添加props:[]来接收父组件传过来的属性(即接收子组件上的自定义属性名),子组件使用{{自定义属性名}}
来使用
props验证
props:['msg']
props:{ msg:String }
msg:{ type:String, required:true }
带有默认值的对象
props:{ msg: { type: Object, // 对象或数组默认值必须从一个工厂函数获取 default:function(){ return {a:1,b:2} } }, }
自定义验证函数,返回值true表示通过验证
props:{ msg:{ validator:function(val){ // val就是给msg注入的具体的值 return true; } } }
子组件将数据给父组件
使用 vm.$emit('要触发的事件类型','给事件处理函数的实参1','给事件处理函数的实参2',...)
在子组件上定义一个自定义事件,@自定义事件名f=‘自定义函数名’
,当子组件中的一个事件触发时(点击子组件按钮触发事件a),在事件a中使用this.$emit('自定义事件名f',子组件要传递的参数(多个参数用,分隔))
,父组件调用自定义函数进行接收子组件传来的值
sync修饰符(父子组件传值的语法糖)
上述可改写为:
异步组件
Vue.component('child2',function(resolve,reject){ setTimeout(()=>{ resolve({ template:`hello child2
` }) },3000) })
动态组件
语法:
通常与keep-alive
一起用
keep-alive
包裹动态组件,会"缓存"不活动的组件实例,而不是销毁他们
把回调延迟到下次dom更新完成执行
+ Vue.nextTick()
+ vm.$nextTick()
clickHandler(){ this.msg = "哭唧唧"; this.$nextTick(function(){ console.log(document.querySelector('h1').innerHTML) }) }
插槽v-slot指令
组件的outerHTML一般会被丢弃, 除非组件里面有可扩展的插槽,组件通过
指定插槽名字插入内容的语法 要插入插槽的内容
给插槽里面的内容传值
组件里面定义的插槽 :
使用: v-slot:插槽名 = "变量名"
等价: var 变量名 = {a:'xxx',b:'xxx',c:'xx'}
v-slot: 可以简写成 #
插件机制
定义插件:export default {install:function(Vue,options){}}
例子:
export default { install:function(Vue,options){ console.log(Vue); console.log(options) console.log("Vue.use的时候其实是调用obj的install方法,并传入第一个实参是Vue") } }
使用插件:
import MyPlugin from 'xxx.js'
Vue.use(MyPlugin)
例子