Node.js
目录
学习视频:
00.学习目标_哔哩哔哩_bilibili
什么是Node.js?
Node.js
是一个跨平台JavaScript运行环境,使开发者可以搭建服务器端的JavaScript应用程序。
作用:使用
Node.js
编写服务器端程序。
- 编写数据接口,提供网页资源浏览功能等
- 前端工程化:为后续学习
Vue
和React
等框架做铺垫。
前端工程化就是指开发项目直到上线,过程中集成的所有工具和技术。
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)
- 加载fs模块:
const fs = require('fs')
- 写入文件内容:
fs.writerFile('文件路径', '写入内容', err ⇒ { 写入后的回调函数 })
写入内容会覆盖文件原有的内容。 - 读取文件内容:
fs.readFile('文件路径', '编码格式', (err, data) => {读取后的回调函数})
data 是文件内容的 Buffer 数据流,看具体内容要写 **data.toString()**。 - 例子:
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)
可以得到文件的绝对路径。
- 加载path模块:
const path = require('path')
- 获取文件名:
path.basename('文件路径')
window或者posix上运行结果不一样,可以指定系统得到一致的结果:path.win32.basename('win系统的文件路径')
/path.posix.basename('posix系统的文件路径')
- 获取路径目录:
path.dirname('文件路径')
- 获取绝对路径:
path.join()
配合内置变量__dirname
:path.join(__dirname, '.', 'try.txt')
- 例子:
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(会省略)。
- 加载http模块:
const http = require('http')
- 创建web服务器实例:
const server = http.createServer()
- 为服务器实例绑定事件:
server.on('request',(req, res) ⇒ { })``````req.url
是客户端请求的URL地址,req.method是客户端的method请求类型。res.end( )
可以向客户端发送指定的内容(响应内容),并结束这次请求的过程。如果中文乱码了,可以设置res.setHeader('Content-Type', 'text/html; charset=utf-8' )
。 - 启动服务器:
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规范
- 加载模块使用require(),require会执行被加载模块中的代码。加载自定义模块时需要的是路径(.js文件后缀可省略),其他两个模块的加载都是写名字就行。
- 另外,模块也有作用域,类似于函数作用域,外部访问不了模块内定义的变量:
- 每个js文件内置了一个module对象(存储了当前模块的属性和信息):
- 使用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与包
基本使用:
- 创建包配置文件:
npm init -y
- 安装包:
npm i xxx
指定版本npm i [email protected]
- 安装的包会分到项目的node_modules文件夹中,分为开发依赖包(开发期间用到)和核心依赖包(开发和部署都会用到),前者安装:
npm i xxx -D
这样这个包就会被记录到devDependencies
中,后者正常安装,会被记录到dependencies
。 - 安装全局包:
npm install xxx -g
,会被安装到C:\Users\用户目录\AppData\Roaming\npm\node_modules
目录下。
一个规范的包要求:
- 包必须以单独的目录存在
- 包的顶级目录下一定要有package.json 这个包管理配置文件
- package.json 中必须包含 name(包名), version(包版本), main(包的入口)
加载机制
require加载包就是先在node_modules里面找到对应的包,然后找到package.json,在里面找到main字段,再通过该字段对应的文件路径找到对应的入口文件,进行加载。
require在加载模块时都是优先从缓存里面加载,多次加载不会重复执行模块里的代码。
加载自定义模块时没有文件后缀名,则会按顺序补全进行查找:
- 按照文件名加载
- 补全.js进行加载
- 补全.json进行加载
- 补全.node进行加载
- 加载失败,报错
加载第三方模块的机制:如果require里的既不是内置也没有
./
或者
../
,就会从
/node_modules
目录加载,如果没有找到对应的第三方模块,则移动到再上一层父目录中,进行加载,直到文件系统的根目录。
例如,假设在’C:\Users\itheima\project\tool.js’文件里调用了 require(‘tools’),则 Node.js 会按以下顺序查找:
- C:\Users\itheima\project\node_modules\tools
- C:\Users\itheima\node_modules\tools
- C:\Users\node_modules\tools
- C:\node_modules\tools
如果是文件夹作为模块去加载,有三种加载方式:
- 在被加载的目录下查找一个叫做 package.json 的文件,并寻找 main 属性,作为 require 加载的入口
- 如果目录里没有 packagejson 文件,或者 main 入口不存在或无法解析,则 Node.js 将会试图加载目录下的 index.js 文件。
- 如果以上两步都失败了, 则 Node.js 会在终端打印错误消息, 报告模块的缺失:Error: Cannot find module ‘xxx’
Express框架
基本使用
基于http模块封装的,专门用来创建服务器(Web网站服务器和API接口服务器),但是功能更加强大。
- 安装:
npm i express
- 创建服务器 1. 导入express:
const express = require('express')
2. 创建web服务器:const app = express()
3. 启动服务器:app.listen(端口号, ( ) ⇒ { })
- 监听请求:
app.get('url', (req, res) ⇒ { })
监听什么类型就是app.xxx - 返回响应内容:
res.send()
- 如果url里面携带了
?x=1&y=1
请求参数,可以使用req.query
得到请求参数 - 如果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
当项目代码被修改后,可以自动重启项目,方便开发调试。
- 安装:
npm install -g nodemon
- 使用:
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)=>{})// 两种方法是等价的
中间件使用注意事项:
- 一定要在路由之前注册中间件
- 客户端发送过来的请求,可以连续调用多个中间件进行处理
- 执行完中间件的业务代码之后,不要忘记调用next函数
- 为了防止代码逻辑混乱,调用next函数后不要再写额外的代码
- 连续调用多个中间件时,多个中间件之间,共享req和res对象
中间件分类:
- 绑定到app实例上的中间件,应用级别。
- 绑定到router上的中间件,路由级别。
- 错误级别:
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')})
- 内置级别: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)请求体的编辑
写接口
- 创建基本服务器和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
- 编写GET / POST /… 接口:
// apiRouter.jsrouter.get('/user',(req, res)=>{const query = req.query res.send({ status:0, msg:'GET请求成功', data: query })})
- 基于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')})
版权归原作者 睡睡睡睡睡觉 所有, 如有侵权,请联系我们删除。