0


Node.js学习笔记

Node.js

目录

学习视频:

00.学习目标_哔哩哔哩_bilibili

什么是Node.js?

Node.js

是一个跨平台JavaScript运行环境,使开发者可以搭建服务器端的JavaScript应用程序。

作用:使用

Node.js

编写服务器端程序。

  • 编写数据接口,提供网页资源浏览功能等
  • 前端工程化:为后续学习VueReact等框架做铺垫。

前端工程化就是指开发项目直到上线,过程中集成的所有工具和技术。

Node.js

可以主动读取前端代码内容。

Node.js为何能执行JS?

浏览器执行JS代码依靠的是内核中的V8引擎(C++程序),而Node.js是基于Chrome V8引擎进行封装(运行环境)。

二者都支持ECMAScript标准语法,但是Node.js有独立的API。

Node.js环境没有DOM和BOM等。

使用Node.js

新建JS文件编写完代码后,在终端输入

node js文件路径

,按下回车执行。

在这里插入图片描述

fs模块

fs 文件系统 | Node.js v20 文档 (nodejs.cn)

  1. 加载fs模块: const fs = require('fs')
  2. 写入文件内容: fs.writerFile('文件路径', '写入内容', err ⇒ { 写入后的回调函数 })写入内容会覆盖文件原有的内容。
  3. 读取文件内容: fs.readFile('文件路径', '编码格式', (err, data) => {读取后的回调函数})data 是文件内容的 Buffer 数据流,看具体内容要写 **data.toString()**。
  4. 例子:const fs =require('fs')fs.readFile('./try.txt','utf-8',(err, data)=>{ console.log(data.toString())})fs.writeFile('./try.txt','Welcome',err=>{ console.log(err)})

path模块

path 路径 | Node.js v20 文档 (nodejs.cn)

可以得到文件的绝对路径。

  1. 加载path模块: const path = require('path')
  2. 获取文件名: path.basename('文件路径')window或者posix上运行结果不一样,可以指定系统得到一致的结果:path.win32.basename('win系统的文件路径') / path.posix.basename('posix系统的文件路径')
  3. 获取路径目录: path.dirname('文件路径')
  4. 获取绝对路径: path.join()配合内置变量 __dirnamepath.join(__dirname, '.', 'try.txt')
  5. 例子:const path =require('path')console.log(__dirname)const result = path.join(__dirname,'.','try.txt')console.log(result)在这里插入图片描述

http模块

可以用来创建web服务器。

每台web服务器都会有自己的IP地址,IP不好记,有了域名,IP和域名之间通过域名服务器(DNS)进行转换。

http协议默认端口号是80(会省略)。

  1. 加载http模块:const http = require('http')
  2. 创建web服务器实例: const server = http.createServer()
  3. 为服务器实例绑定事件: server.on('request',(req, res) ⇒ { })``````req.url 是客户端请求的URL地址,req.method是客户端的method请求类型。res.end( ) 可以向客户端发送指定的内容(响应内容),并结束这次请求的过程。如果中文乱码了,可以设置res.setHeader('Content-Type', 'text/html; charset=utf-8' )
  4. 启动服务器: server.listen(端口号, ( ) ⇒ { })

根据不同的url响应不同的内容:

const http =require('http')const server = http.createServer()

server.on('request',(req, res)=>{const url = req.url
  let content ='<h1>404 not found</h1>'if(url ==='/'|| url ==='/index.html'){
    content ='<h1>首页</h1>'}elseif(url ==='/about.html'){
    content ='<h1>关于我</h1>'}
  res.setHeader('Content-Type','text/html; charset=utf-8')
  res.end(content)})

server.listen(8080,()=>{
  console.log('server running at http://localhost:8080')})

模块化

Node.js中模块分为内置模块(如fs、path、http)、自定义模块(用户创建的js文件)和第三方模块(包)。

内置模块的优先级最高。

CommonJS规范

  1. 加载模块使用require(),require会执行被加载模块中的代码。加载自定义模块时需要的是路径(.js文件后缀可省略),其他两个模块的加载都是写名字就行。在这里插入图片描述
  2. 另外,模块也有作用域,类似于函数作用域,外部访问不了模块内定义的变量:在这里插入图片描述
  3. 每个js文件内置了一个module对象(存储了当前模块的属性和信息):在这里插入图片描述
  4. 使用require加载自定义模块时,得到的成员就是该模块module.exports指向的对象。上文得到的对象为空是因为自定义模块默认的module.exports指向的就是一个{}在这里插入图片描述module.exports太长了,可以用exports代替(默认情况下二者指向同一个对象),但是模块加载的还是module.exports指向的对象:exports.username ='张三'moudule.exports ={ gender:'男', age:18}// { gender:'男', age:18 }moudule.exports.username ='张三'exports ={ gender:'男', age:18}// { username = '张三' }moudule.exports.username ='张三'exports.age=18// { username = '张三', age:18 }exports ={ gender:'男', age:18}module.exports = exportsmodule.exports.username ='张三'// { username = '张三', gender:'男', age:18 }

npm与包

基本使用:

  1. 创建包配置文件:npm init -y
  2. 安装包:npm i xxx 指定版本 npm i [email protected]
  3. 安装的包会分到项目的node_modules文件夹中,分为开发依赖包(开发期间用到)和核心依赖包(开发和部署都会用到),前者安装:npm i xxx -D 这样这个包就会被记录到 devDependencies中,后者正常安装,会被记录到dependencies
  4. 安装全局包: npm install xxx -g,会被安装到 C:\Users\用户目录\AppData\Roaming\npm\node_modules目录下。

一个规范的包要求:

  1. 包必须以单独的目录存在
  2. 包的顶级目录下一定要有package.json 这个包管理配置文件
  3. package.json 中必须包含 name(包名), version(包版本), main(包的入口)

加载机制

require加载包就是先在node_modules里面找到对应的包,然后找到package.json,在里面找到main字段,再通过该字段对应的文件路径找到对应的入口文件,进行加载。

require在加载模块时都是优先从缓存里面加载,多次加载不会重复执行模块里的代码。

加载自定义模块时没有文件后缀名,则会按顺序补全进行查找:

  1. 按照文件名加载
  2. 补全.js进行加载
  3. 补全.json进行加载
  4. 补全.node进行加载
  5. 加载失败,报错

加载第三方模块的机制:如果require里的既不是内置也没有

./

或者

../

,就会从

/node_modules

目录加载,如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。

例如,假设在’C:\Users\itheima\project\tool.js’文件里调用了 require(‘tools’),则 Node.js 会按以下顺序查找:

  1. C:\Users\itheima\project\node_modules\tools
  2. C:\Users\itheima\node_modules\tools
  3. C:\Users\node_modules\tools
  4. C:\node_modules\tools

如果是文件夹作为模块去加载,有三种加载方式:

  1. 在被加载的目录下查找一个叫做 package.json 的文件,并寻找 main 属性,作为 require 加载的入口
  2. 如果目录里没有 packagejson 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件
  3. 如果以上两步都失败了, 则 Node.js 会在终端打印错误消息, 报告模块的缺失:Error: Cannot find module ‘xxx’

Express框架

基本使用

基于http模块封装的,专门用来创建服务器(Web网站服务器和API接口服务器),但是功能更加强大。

  1. 安装: npm i express
  2. 创建服务器 1. 导入express: const express = require('express')2. 创建web服务器:const app = express()3. 启动服务器:app.listen(端口号, ( ) ⇒ { })
  3. 监听请求:app.get('url', (req, res) ⇒ { }) 监听什么类型就是app.xxx
  4. 返回响应内容:res.send()
  5. 如果url里面携带了?x=1&y=1请求参数,可以使用req.query得到请求参数
  6. 如果url里面是携带的 :id的动态请求参数,可以使用req.params得到
const express =require('express')const app =express()

app.listen(8080,()=>{console.log('Server running at http://localhost:8080')})// 挂载路由(express的路由)
app.get('/user',(req, res)=>{
  res.send({username:'张三', age:18})})

app.post('/userinfo',(req, res)=>{console.log(req.query)
  res.send('请求成功')})

app.get('/product/:id',(req, res)=>{console.log(req.params)
  res.send({id: req.params.id, price:36.00, name:'黄金帅苹果'})})

托管静态资源

express.static('指定目录')

对外开放静态资源:

const express =require('express')const app =express()
app.use(express.static('./clock'))

Express在指定的静态目录中查找文件,并对外提供资源的访问路径。因此,存放静态文件的目录名不会出现在URL中。

在这里插入图片描述

如果希望路径加上目录,就可以挂载访问前缀:

app.use('/public', express.static('public'))

Tip:多次调用express.static托管多个静态资源时,这个函数会根据目录的添加顺序查找文件。

nodemon

当项目代码被修改后,可以自动重启项目,方便开发调试。

  1. 安装:npm install -g nodemon
  2. 使用:nodemon xxx.js

在这里插入图片描述

路由模块化

不把路由挂载到app上,而是挂载到router模块上。

// user.jsconst express =require('express')const router= express.Router()
router.get('/user/list',function(req,res){ res.send('Get user list.')}
router.post('/user/add',function(req,res){ res.send('Add new user.')}
module.exports = router
// index.jsconst express =require('express')const app =express()// 导入路由模块const userRouter =require('./user.js')// 注册路由模块
app.use(userRouter)// 如果要添加访问前缀
app.use('/api', userRouter)

Tip:app.use()就是用来注册全局中间件的

中间件

一个业务流程,除了开始(需求)和结尾(成果)的其他中间处理环节都叫中间件。

当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理,最后再交给路由进行处理。

在这里插入图片描述

express的中间件本质就是一个函数,跟路由函数有点像,但是参数多了一个

next

constmid=(req, res, next)=>{next()}

next的作用就是把流程交给下一个中间件或者路由。

多个中间件共享一份req或者res,所以中间件可以用来给这两个添加属性或者方法,供下一个中间件或路由使用。

注册全局中间件:

app.use(mid)

如果注册了多个中间件时,会按照中间件定义先后依次执行。

const express =require('express')const app =express()

app.use((req, res, next)=>{
  console.log('执行了中间件函数')const t = Date.now()
  req.startTime = t
  next()})

app.use((req, res, next)=>{
  console.log(`执行了第二个中间件函数,时间为${req.startTime}`)next()})

app.get('/time',(req, res)=>{
  console.log('执行了路由')
  res.send(`现在时间为${req.startTime}`)})

app.listen(8080,()=>{
  console.log('Server running at http://localhost:8080')})

如果不使用app.use进行中间件注册,那就是局部的中间件,局部中间件只有在使用它的路由才会生效,不会影响其他的路由模块。

const express =require('express')const app =express()constmid=(req, res, next)=>{
  console.log('执行了中间件函数')const t = Date.now()
  req.startTime = t
  next()}

app.get('/time',mid,(req, res)=>{
  console.log('执行了time路由')
  res.send(`现在时间为${req.startTime}`)})

app.post('/user',(req, res)=>{
  console.log('执行了user路由')
  res.send('请求成功')})

app.listen(8080,()=>{
  console.log('Server running at http://localhost:8080')})

可以定义多个局部中间件:

app.get('/time',[mid1, mid2, mid3],(req,res)=>{})
app.get('/time', mid1, mid2, mid3,(req,res)=>{})// 两种方法是等价的

中间件使用注意事项:

  1. 一定要在路由之前注册中间件
  2. 客户端发送过来的请求,可以连续调用多个中间件进行处理
  3. 执行完中间件的业务代码之后,不要忘记调用next函数
  4. 为了防止代码逻辑混乱,调用next函数后不要再写额外的代码
  5. 连续调用多个中间件时,多个中间件之间,共享req和res对象

中间件分类:

  1. 绑定到app实例上的中间件,应用级别。
  2. 绑定到router上的中间件,路由级别。
  3. 错误级别:function(err, req, res, next){ } 捕获一切错误并做出处理。特殊:错误中间件要注册在所有路由之后。const express =require('express')const app =express()app.post('/',(req, res)=>{thrownewError('发生错误') res.send('请求成功')})app.use((err, req, res, next)=>{ console.log(err.message) res.send(err.message)})app.listen(8080,()=>{ console.log('Server running at http://localhost:8080')})
  4. 内置级别:express.static 快速托管静态资源的内置中间件,例如:HTML 文件、图片、CSS 样式等。express.json解析JSON格式的请求体数据。express.urlencoded解析URL-encoded 格式的请求体数据。app.use(express.json())app.use(express.urlencoded())app.post('/user',(req, res)=>{// 可以通过req.body得到请求体数据 console.log(req.body)// 如果没挂载中间件 则为undefined res.send('finished')})在postman中可以点击此处进行不同格式(URL / JSON)请求体的编辑在这里插入图片描述

写接口

  1. 创建基本服务器和api路由模块:// expressDemo.jsconst express =require('express')const app =express()const router =require('./apiRouter')app.use('/api', router)app.listen(8080,()=>{ console.log('server running at http://localhost:8080')})``````// apiRouter.jsconst express =require('express')const router = express.Router()module.exports = router
  2. 编写GET / POST /… 接口:// apiRouter.jsrouter.get('/user',(req, res)=>{const query = req.query res.send({ status:0, msg:'GET请求成功', data: query })})
  3. 基于cors解决跨域问题:CORS(Cross-OriginResourceSharing,跨域资源共享)由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源。浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了CORS相关的HTTP响应头,就可以解除浏览器端的跨域访问限制。在这里插入图片描述在这里插入图片描述npm install cors // 安装const cors =require('cors')// 导入app.use(cors())// 在路由运行前注册##### cors的三个响应头// Access-Control-Allow-Origin res.setHeader('Access-Control-Allow-Origin','某个url'/'*')只接受指定url的跨域请求 如果是 * 则表示所有跨域请求都接受// Access-Control-Allow-Headersres.setHeader('Access-Control-Allow-Headers','Content-Type, X-Custome-Header')默认情况下,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对额外的请求头进行声明,否则这次请求会失败!// Access-Control-Allow-Methodsres.setHeader('Access-Control-Allow-Methods','*'/'POST, GET, DELETE, HEAD')默认情况下,CORS 仅支持客户端发起 GET、POST、HEAD 请求。如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源,则需要在服务器端,通过 Access-Control-Alow-Methods 来指明实际请求所允许使用的HTTP方法。##### cors的分类简单请求同时满足以下两大条件的请求:请求方式:GET、POST、HEAD 三者之一;HTTP头部信息不超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type (只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)。预检请求 在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一次的OPTION请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。只要符合以下任何一个条件的请求,都需要进行预检请求:请求方式为 GET、POST、HEAD 之外的请求 Method 类型;请求头中包含自定义头部字段;向服务器发送了 application/json 格式的数据。

mysql模块

安装模块和配置:

// 安装
npm install mysql
// 导入const mysql =require('mysql')// 配置const db = mysql.createPool({
  host:'127.0.0.1',// 数据库ip地址
  user:'root',// 登录数据库的账号
  password:'root',// 登录数据库的密码
  database:'my_db_1'// 指定要操作哪个数据库})// 测试数据库是否正常工作
db.query('SELECT 1',(err, res)=>{if(err)return console.log(err.message)
  console.log(res)// 打印[ RowDataPacket { '1': 1 } ]即是正常工作})

插入数据:

// 插入数据const user ={ name:'张三', age:18, gender:'男'}
db.query('INSERT INTO user (name, age, gender) VALUES (?, ?, ?)',[user.name, user.age, user.gender],(err, res)=>{if(err)returnconsole.log('插入数据失败')if(res.affectedRows ===1)console.log('插入数据成功')})// 这种写法也可以const sqlStr ='INSERT INTO user (name, age, gender) VALUES (?, ?, ?)'
db.query(sqlStr, Object.values(user),(err, res)=>{if(err)returnconsole.log('插入数据失败')if(res.affectedRows ===1)console.log('插入数据成功')})// 如果要插入的数据字段与数据表一一对应,也可以这么写
db.query('INSERT INTO user SET ?', user,(err, res)=>{if(err)returnconsole.log('插入数据失败')if(res.affectedRows ===1)console.log('插入数据成功')})

更新数据:

// 把sqlStr替换,传入数据时按占位符顺序即可
sqlStr ='UPDATE user SET age=?,gender=? WHERE name=?'
db.query(sqlStr,[19,'女','李四'],(err, res)=>{if(err)return console.log('更新数据失败')if(res.affectedRows ===1) console.log('更新数据成功')})// 更新字段与数据表一一对应
sqlStr ='UPDATE user SET ? WHERE name=?'

删除数据:

sqlStr ='DELETE FROM user WHERE name=?'

查询数据:

sqlStr ='SELECT * FROM user'

对表的增删改查其实就是SQL语句的不同。

身份认证机制

web开发模式主要分为服务端渲染前后端分离,服务端渲染模式中服务器发送给客户端的HTML页面是在服务器通过字符串的拼接动态生成的,不需要使用Ajax这样的技术额外请求页面的数据。而前后端分离就是后端负责提供API接口,前端通过Ajax调用接口的模式。

两种不同的模式使用不同的认证机制:前者为Session,后者为JWT

Session

在这里插入图片描述

这里的cookie就是服务器用来认证的,相当于身份证,它是不支持跨域访问的。

所以Session一般用于前端请求后端不存在跨域问题的场景。

// 安装中间件
npm install express-session
// 导入中间件const session =require('express-session')// 配置session中间件
app.use({
  secret:'keyboard cat',// 可以为任意字符串,安全密钥
  resave:false,// 用于控制是否在每次请求时重新保存 session
  saveUninitialized:true// 用于控制是否自动保存未初始化的 session})

配置好后即可使用req.session访问和使用,从而存储用户信息。

在这里插入图片描述

const session =require('express-session')const express =require('express')const app =express()

app.use(session({
  secret:'admin',// 可以为任意字符串,安全密钥
  resave:false,// 用于控制是否在每次请求时重新保存 session
  saveUninitialized:true// 用于控制是否自动保存未初始化的 session}))

app.use(express.urlencoded())// 登录
app.post('/api/login',(req, res)=>{if(req.body.username !=='admin'|| req.body.password !=='admin'){return res.send({ status:1, message:'登录失败'})}
  req.session.user = req.body
  req.session.isLogin =true
  res.send({ status:0, message:'登录成功'})})// 获取用户信息
app.get('/api/userinfo',(req, res)=>{if(!req.session.isLogin){return res.send({ status:1, message:'Fail. Error: it is not loginning'})}
  res.send({ status:0, message:'success', username: req.session.user.username })})// 获取session
app.get('/api/session',(req, res)=>{
  res.send(req.session)})// 登出
app.post('/api/logout',(req, res)=>{// 清空session
  req.session.destroy()
  res.send({ status:0, message:'success'})})

app.listen(8080,()=>{
  console.log('server running at http://127.0.0.1:8080')})

JWT

前端请求后端需要跨域的时候就可以使用JWT(JSON Web Token)认证机制。

JWT的工作原理:用户信息通过Token字符串的形式,保存在客户端浏览器中。服务器通过还原Token字符串的形式来认证用户身份。

在这里插入图片描述

JWT通常有三部分:

Header(头部).Payload(有效荷载).Signature(签名)

,Payload才是用户信息,头部和签名是为了安全性。

Token放在请求头的Authorization字段格式一般为:

Authorization: Bearer <token>

客户端每次在访问那些有权限接口的时候,都需要主动通过请求头中的Authorization字段,将Token字符串发送到服务器进行身份认证。

// 安装模块// jsonwebtoken用于生成JWT字符串// express-jwt用于将JWT字符串解析还原成JSON对象
npm install jsonwebtoken express-jwt
const express =require('express')const app =express()// 导入const jwt =require('jsonwebtoken')const expressJWT =require('express-jwt')// 定义secret密钥,用于加密和解密JWTconst secretKey ='helloworld'

app.use(express.json())
app.use(express.urlencoded({ extended:true}))// 调用express-jwt中间件 用于解密// .unless部分表示以/api开头的都不需要访问权限, algorithms是加密算法
app.use(expressJWT.expressjwt({ secret: secretKey, algorithms:["HS256"]}).unless({ path:[/^\/api\//]}))

app.post('/api/login',(req, res)=>{if(req.body.username !=='admin'|| req.body.password !=='admin'){return res.send({ status:1, message:'登录失败'})}

  res.send({
    status:200,
    msg:'登录成功',// 根据密钥对信息进行加密生成token
    token: jwt.sign({ username:'张三'},
      secretKey,// 加密密钥{ expiresIn:'100s'}// token过期时间)})})

app.get('/admin/userInfo',(req, res)=>{
  res.send({
    status:200,
    msg:'获取用户信息成功',
    data: req.auth   // express-jwt解析完用户信息后会自动挂载到req.auth上})})// 错误中间件处理错误情况
app.use((err, req, res, next)=>{if(err.name ==='UnauthorizedError'){return res.send({ status:401, message:'无效的token'})}
  res.send({ status:500, message:'未知错误'})})

app.listen(8080,()=>{console.log('server running at http://127.0.0.1:8080')})

在这里插入图片描述
在这里插入图片描述

标签: node.js 学习 笔记

本文转载自: https://blog.csdn.net/weixin_75207853/article/details/142915815
版权归原作者 睡睡睡睡睡觉 所有, 如有侵权,请联系我们删除。

“Node.js学习笔记”的评论:

还没有评论