7-1 node.js基础及模块
浏览器是 JavaScript 的前端运行环境。
Node.js 是 JavaScript 的后端运行环境,无法调用 DOM 和 BOM 等浏览器内置 API。
1.fs文件系统模块
fs.readFile() 方法,用来读取指定文件中的内容
fs.writeFile() 方法,用来向指定的文件中写入内容
在JavaScript中使用fs模块进行文件操作,则需要使用如下的方式先导入它:
const fs=require('fs');
1.1读取指定文件内容
fs.readFile(path,[options],callback);
//例
const fs=require('fs');
fs.readFile('./a.txt',utf8,function(err,dataStr){
if(err)
console.log('读失败');
else
console.log('内容是'+dataStr);
})
path:字符串,文件路径
options:编码格式
callback:回调函数
1.2向指定文件写入内容
fs.writeFile(path,data,[options],callback);
//例
const fs =require('fs');
fs.writeFile('./a.txt','Hello',function(err){
if(err)
console.log('失败');
})
path:指定文件路径
data:写入内容
options:编码格式,默认utf8
callback:回调函数
在使用 fs 模块操作文件时,如果提供的操作路径是以 ./ 或 …/ 开头的相对路径时,很容易出现路径动态拼接错误的问题。 原因:代码在运行的时候,会以执行 node 命令时所处的目录,动态拼接出被操作文件的完整路径。 解决方案:在使用 fs 模块操作文件时,直接提供完整的路径,不要提供 ./ 或 …/ 开头的相对路径,从而防止路径动态拼接的问题。
2.path路径模块
path 模块是 Node.js 官方提供的、用来处理路径的模块。它提供了一系列的方法和属性,用来满足用户对路径的处需求。
2.1路径拼接
path.join([...paths]);
...paths <string> 路径片段的序列
返回值: <string>
//例
const pathStr=path.join('/a','/b/c','./d','e');
返回值=\a\b\d\e
const pathStr=path.join(_dirname,'./files/a.txt');
当前目录\files\a.txt
涉及到路径拼接的操作,都要使用 path.join() 方法进行处理。不要直接使用 + 进行字符串的拼接。
2.2获取路径中的文件名
path.basename(path,[ext]);
const fpath='/a/b/c/index.html'
var fullName=path.basename(fpath)//index.html
var fullName=path.basename(fpath,'.html')//index
path:路径
ext:文件扩展名
返回:路径中的最后一部分
2.3获取路径中的文件扩展名
path.extname(path)
const fext=path.extname(fpath)//.html
path:路径
返回:得到的扩展名(字符串)
fs.writeFile() 方法只能用来创建文件,不能用来创建路径 ② 重复调用 fs.writeFile() 写入同一个文件,新写入的内容会覆盖之前的旧内容
3.http模块
http 模块是 Node.js 官方提供的、用来创建 web 服务器的模块。通过 http 模块提供的 http.createServer() 方法,就 能方便的把一台普通的电脑,变成一台 Web 服务器,从而对外提供 Web 资源服务。
如果要希望使用 http 模块创建 Web 服务器,则需要先导入它
const http=require('http')
3.1创建最基本的web服务器
①导入 http 模块
const http=require('http');
② 创建 web 服务器实例
const server=http.createServer();
③ 为服务器实例绑定 request 事件,监听客户端的请求
//绑定事件,可以用on方法
server.on('request',function(req,res){
console.log('访问服务器');
})
-
req 请求对象
访问与客户端相关的数据或属性
seq.url 请求的url地址 seq.method 请求类型
-
res 响应对象
访问与服务器相关的数据或属性
res.end() 向客户端发送指定内容,并结束这次请求的处理过程 如果发送的是中文,会乱码,解决方法: setHeader('const-Type','text/html:charset=utf-8');
④ 启动服务器
server.listen(80,function(){
//端口号,回调函数
console.log('启动服务器');
})
7-2 模块化
1.基本概念
模块化是指解决一个复杂问题时,自顶向下逐层把系统划分成若干模块的过程。对于整个系统来说,模块是可组 合、分解和更换的单元。
优点:① 提高了代码的复用性 ② 提高了代码的可维护性 ③ 可以实现按需加载
2.node.js中的模块化
分类:
- 内置模块(内置模块是由 Node.js 官方提供的,例如 fs、path、http 等)
- 自定义模块(用户创建的每个 .js 文件,都是自定义模块)
- 第三方模块(由第三方开发出来的模块,并非官方提供的内置模块,也不是用户创建的自定义模块,使用前需要先下载)
2.1加载模块
//加载内置的模块
const fs=require('fs')
//加载用户自定义模块
const custom=require('./custom.js')
//加载第三方模块
const =moment=require('moment')
2.2模块作用域
和函数作用域类似,在自定义模块中定义的变量、方法等成员,只能在当前模块内被访问,这种模块级别的访问限制,叫做模块作用域。
2.3向外共享模块作用域中的成员
-
module对象
每个.js自定义模块中都有一个module对象,存储了和当前模块有关的信息。
-
module.exports对象
在自定义模块中,可以使用 module.exports 对象,将模块内的成员共享出去,供外界使用。 外界用 require() 方法导入自定义模块时,得到的就是 module.exports 所指向的对象。
使用 require() 方法导入模块时,导入的结果,永远以 module.exports 指向的对象为准。
在被导入的js中,用module.exports.username='hello’来定义
module.exports={ name:'abc', Sayhi(){ console.log('hi'); } }//以最新的为准
-
exports 对象(=module.exports)
由于 module.exports 单词写起来比较复杂,为了简化向外共享成员的代码,Node 提供了 exports 对象。默认情况 下,exports 和 module.exports 指向同一个对象。最终共享的结果,还是以 module.exports 指向的对象为准,即module.exports的优先级较高。
3.npm和包
Node.js 中的第三方模块又叫做包。
npm i 包名//安装某包
npm i //安装所有依赖包,package.json中的dependencies节点
7-3 Express
Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建 Web 服务器的。
1.express基础
1.1创建基本的web服务器
const express=require('express')
const app=express()
app.listen(80,function(){
//端口号,回调函数
console.log('启动服务器');
})
1.2监听GET请求
app.get('请求URL',function(req,res){
/*处理函数*/
})
1.3监听POST请求
app.post('请求URL',function(req,res){
/*处理函数*/
})
1.4把内容响应给客户端
通过 res.send() 方法,可以把处理好的内容,发送给客户端:
app.get('/user',funciton(req,res){
res.send({
name:'zhangsan',age:20})
})
app.post('/user',funciton(req,res){
res.send('请求成功')
})
1.5获取 URL 中携带的查询参数
req.query//用于get请求中
//例如
req.query.name
req.query.age
默认是空对象
?name=zs&age=20 查询字符串形式,可以发送到服务器并赋值。
req.body//用于post请求中
1.6获取 URL 中的动态参数
通过 req.params 对象,可以访问到 URL 中,通过 : 匹配到的动态参数:
app.get('/user/:id',function(req,res){
console.log(req.params);
})
//req.params默认是空对象,里面存放着通过:动态匹配到的参数值
//:id是一个动态参数
//例:
http://127.0.0.1/user/1
req.params=1
1.7托管静态资源
express.static(),可以创建一个静态资源服务器。
例如,通过如下代码就可以将 public 目录下的图片、CSS 文件、JavaScript 文件对外开放访问了:
app.use(express.static('public'))
//例
app.use(express.static('./files'))
现在就可以访问public目录中所有文件了
访问静态资源文件时,express.static() 函数会根据目录的添加顺序查找所需的文件,多个目录多次添加即可。
如果希望在托管的静态资源访问路径之前,挂载路径前缀,则可以使用如下的方式:
app.use('/public',express.static('public'))
2.express路由
Express 中的路由分 3 部分组成,分别是请求的类型、请求的 URL 地址、处理函数,格式如下:
app.method(path,handler)
每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。 在匹配时,会按照路由的顺序进行匹配,如果请求类型和请求的 URL 同时匹配成功,则 Express 会将这次请求,转交给对应的 function 函数进行处理。
路由匹配的注意点:
① 按照定义的先后顺序进行匹配
② 请求类型和请求的URL同时匹配成功, 才会调用对应的处理函数
将路由抽离为单独的模块:
//创建路由模块 router.js
var express =require('express')
var router express.Router()
router.get……
module.exports = router//向外导出路由对象
//导入路由模块
const userRouter =require(./router.js)
app.use(userRouter)
//添加前缀版 app.use('/api',userRouter)
app.listen(80,function...)
3.express中间件
3.1全局中间件
const express=require('express')
const app=express()
……
//定义中间件函数
const mw=function(req,res,next){
console.log('中间件函数');
//当中间件的业务处理完毕后必须调用next函数
next();
}
//全局生效的中间件
//客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件
app.use(mw);
//不使用app.use()就是局部生效的中间件,只在当前路由中生效
//简化形式,定义多个会多个依次执行
app.use(function(req,res,next){
console('简化');
next();
})
① 一定要在路由之前注册中间件
② 客户端发送过来的请求,可以连续调用多个中间件进行处理
③ 执行完中间件的业务代码之后,不要忘记调用 next() 函数
④ 为了防止代码逻辑混乱,调用 next() 函数后不要再写额外的代码
⑤ 连续调用多个中间件时,多个中间件之间,共享 req 和 res 对象
3.2局部生效的中间件
app.get('/',mw1,mw2,function(req,res){
res.send('Home page')
})
3.3中间件的分类
-
应用级别的中间件
应用级别中间件是绑定到 app 实例上
//全局 app.use(function(req,res,next){ next() }) //局部 app.get('/',mw1,function(req,res,next){ res.send('Home Page') })
-
路由级别的中间件
绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。
//创建路由模块 router.js var express =require('express') var router express.Router() router.use(function(req,res,next){ console.log('Time',Date.now()) next() }) app.use('/',router)
-
错误级别的中间件
app.get( 'i', function (req,res) { // 1.路由 throw new Error( '服务器内部发生了错误! ')// 1.1抛出一个自定义的错误 res.send( 'Home Page. ') }) app.use( function (err,req,res,next) { // 2.错误级别的中间件 console.log( '发生了错误:' +err.message) // 2.1在服务器打印错误消息 res.send( 'Error ! ' + err.message) // 2.2向客户端响应错误相关的内容))
注意:错误级别的中间件, 必须注册在所有路由之后!
-
express内置的中间件
① express.static 快速托管静态资源的内置中间件,例如: HTML 文件、图片、CSS 样式等(无兼容性)
② express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
③ express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可用)
-
第三方中间件
-
自定义中间件
4.使用express写接口
apirouter.js
const express =require('express')
const router=express.Router()
router.get('/get',function(req,res){
const query=req.query//获取客户端通过查询字符串发送到服务器的数据
res.send({
status:0,
msg:'GET请求成功',
data:query//需要响应给客户端的数据
})
})
router.post('/post',function(req,res){
const body=req.body
res.send({
status:0,
msg:'请求成功',
data:body
})
})
module.exports=router
app.js
const express=require('express')//导入模块
const app=express() //创建服务器
app.use(express.urlencoded({
extended: false }))
const router = require('./apirouter')
app.use('/api',router)
app.listen(80,function(){
console.log('running')
})
5.CORS中间件解决跨域问题
npm i cors//安装
const cors=require('cors')//导入
app.use(cors())//路由之前调用,配置中间件
5.1CORS 响应头部 - Access-Control-Allow-Origin
Access-Control-Allow-Origin :<origin>|*
其中,origin 参数的值指定了允许访问该资源的外域 URL
例如,下面的字段值将只允许来自 http://itcast.cn 的请求:
res.setHeader('Access-Control-Allow-Origin',' http://itcast.cn')
如果指定了 Access-Control-Allow-Origin 字段的值为通配符 *,表示允许来自任何域的请求,示例代码如下:
res.setHeader('Access-Control-Allow-Origin',' *')
7-4 数据库与身份认证
1.在项目中操作MySQL
//导入mysql模块
const mysql =require('mysql')
const db=mysql.createPool({
host:'127.0.0.1',
user:'root',
password:'admin123',
database:'db1'//指定操作哪个数据库
})
//查询
db.query('select * from users',function(err,results){
if(err)
return console.log(err.message)
console.log(results)
})
//插入
const user={
username:'dsc',password:'123123'}
const sqlStr='insert into users(username,password) values(?,?)'
//?表示占位符
db.query(sqlStr,[user.username,user.password],function(err,results){
if(err)
return console.log(err.message)
if(results.affectedRows===1)return console.log('成功')
})
//如果数据对象的每个属性和数据表的字段一一对应
db.query(sqlStr,user,function(err,results){
if(err)
return console.log(err.message)
if(results.affectedRows===1)return console.log('成功')
})
//更新
const user ={
id:1,username:'aaa',password:123123}
const sqlStr='update users set ? where id=?'
db.query(sqlStr,[user,user.id],function(err,results){
if(err) return console.log(err.message)
if(results.affectedRows===1)console.log('更新成功')
})
//删除
const sqlStr ='delete from users where?'
const user={
id:1}
db.query(sqlStr,user,function(err,results){
if(err) return console.log(err.message)
if(results.affectedRows===1) console.log('删除成功')
})
2.前后端的身份认证
2.1 cokkie
Cookie 是存储在用户浏览器中的一段不超过 4 KB 的字符串。它由一个名称(Name)、一个值(Value)和其它几个用 于控制 Cookie 有效期、安全性、使用范围的可选属性组成。
不同域名下的 Cookie 各自独立,每当客户端发起请求时,会自动把当前域名下所有未过期的 Cookie 一同发送到服务器。
Cookie的几大特性:
① 自动发送
② 域名独立
③ 过期时限
④ 4KB 限制
Cookie 不具有安全性。
由于 Cookie 是存储在浏览器中的,而且浏览器也提供了读写 Cookie 的 API,因此 Cookie 很容易被伪造,不具有安全 性。因此不建议服务器将重要的隐私数据(身份信息、密码等),通过 Cookie 的形式发送给浏览器。
2.2 Session认证机制
工作原理:
-
安装express-session中间件
npm i express-session
-
配置中间件
//导入中间件 var session =require('express-session') //配置中间件 app.use(session({ secret:'keyboard cat',//任意字符串 resave:false,//固定写法 saveUninitialized:true//固定写法 }))
-
向session中存数据
app.get('/api/login',function(req,res){ if(req.body.username!=='admin'||req.body.password!==='admin123'){ return res.send({ status:1,msg:'登录失败'}) } req.session.user=req.body//将用户的信息存储到session中 req.session.islogin=true res.send({ status:0,msg:'登录成功'}) })
-
向session中取数据
app.get('/api/username',function(req,res){ if(!req.session.islogin)//没登录 return res.send({ status:1,msg:'failure'}) res.send({ status:0,msg:'success',username:req,session.user.username}) })
-
清空session
app.post('/api/logout',function(req,res){ req.session.destroy() res.send({ status:0, msg:'退出登录成功' }) })
2.3JWT认证机制
JWT(JSON Web Token)是目前最流行的跨域认证解决方案。
-
安装JWT相关包
npm i jsonwebtoken express-jwt
其中:
jsonwebtoken 用于生成 JWT 字符串
express-jwt 用于将 JWT 字符串解析还原成 JSON 对象
-
导入 JWT 相关的包
const jwt =require('jsonwebtoken') const expressjwt=require('express-jwt')
-
定义secret密钥
为了保证 JWT 字符串的安全性,防止 JWT 字符串在网络传输过程中被别人破解,我们需要专门定义一个用于加密和解密 的 secret 密钥:
① 当生成 JWT 字符串的时候,需要使用 secret 密钥对用户的信息进行加密,最终得到加密好的 JWT 字符串
② 当把 JWT 字符串解析还原成 JSON 对象的时候,需要使用 secret 密钥进行解密
const secretkey='miyao'//密钥的本质就是一个字符串
-
在登录成功后生成 JWT 字符串
app.post('/api/login',function(req,res){ //if(err)..... res.send({ status:200, msg:'登录成功', token:jwt.sign({ username:userinfo.username,secretkey,{ expiresin:'30s'}}) }) })
-
将 JWT 字符串还原为 JSON 对象
使用app.use()注册中间件
expressJWT({secret:secretKey})是用来解析token的中间件
.unless({path:[/^/api//]})用来指定哪些接口不需要访问权限
app.use(expressJWT({ secret:secretKey}).unless({ path:[/^\/api\//]}))
-
使用 req.user 获取用户信息
app.get('/admin/getinfo',function(req,res){ console.log(req.user) res.send({ status:200, message:'获取用户信息成功' data:req.user }) })
-
捕获解析 JWT 失败后产生的错误
当使用 express-jwt 解析 Token 字符串时,如果客户端发送过来的 Token 字符串过期或不合法,会产生一个解析失败 的错误,影响项目的正常运行。我们可以通过 Express 的错误中间件,捕获这个错误并进行相关的处理,示例代码如下: