Node.js
文章目录
模块化
-
模块导出:
module.exports.(自定义的导出变量名)=(导出变量名)
-
模块导入:
require('文件路径')
导入后需声明一个变量接收
//导出 const say = name => "hellow" + name let name = '张三' module.exports.say = say module.exports.name = name //导入 const { say, name } = require('./one.js') console.log(name); console.log(say('张三'));
系统模块
Node运行环境提供的API,因为这些API都是以模块化的方式进行开发的,所以称Node运行环境提供的API为系统模块
Buffer
他是一个类似于数组的对象,用于存储数据(存储的是二进制数据)
Buffer的效率很高,存储和读取速度很快,直接对计算机的内存进行操作
Buffer的大小一旦确定了,不可修改
每个元素占用内存的大小为1字节
Buffer是Node中的非常核心的模块,无需下载,无需引入即可使用
-
使用new关键字创建一个Buffer实例(即将被弃用)-----效率很低
创建空间的方式:使用allocUnsafe
创建,然后将空间清理干净let buf1 = new Buffer(10) console.log(buf1)
-
讲一个字符串存入Buffer中
let str = 'hellow' let buf2 = Buffer.from(str) console.log(buf2)
-
alloc这种方式去创建Buffer实例----效率一般
let buf3 = Buffer.alloc(10)//创建的是干净的空间 console.log(buf3)
-
allocUnsafe这种方式去创建Buffer实例----效率很好,但是存在安全问题
let buf4 = Buffer.allocUnsafe(10)//创建的空间可能有残留数据 console.log(buf4)
fs文件操作
f:file文件,s:system系统,文件操作系统
使用前需导入
const fs = require('fs')
-
简单文件写入
fs.writeFile()
fs.writeFile(path, data[, potions], callback)
options: flag(参数为a
或w
默认w
)a是追加文件,w是直接写入,覆盖之前的
encoding:字符编码(utf8)
mode:文件权限限制(0o111:文件可被执行,0o222:文件可被读取,0o444:文件可被写入)默认是0o666(0o222与0o444相加)不足之处,一次性吧所有要写入的文件加载内存中,对于比较大的文件容易产生内存溢出,适用于较小的文件写入
-
流式文件写入
fs.createWriteStream(path[, options])
options(配置对象):
- flags:值为
'a'
或'w'
打开文件要进行的操作(默认是w) - mode:文件权限限制(0o111:文件可被执行,0o222:文件可被读取,0o444:文件可被写入)默认是0o666(0o222与0o444相加)
- encoding:字符编码(默认是utf8)
- fd:文件的唯一标识
- autoClose:当文件操作完毕, 自动关闭文件(默认是true)
- start:文件写入的起始位置
//创建可写流 let ws = fs.createWriteStream(./demo.txt) //只要使用了流,必须给流添加监听 ws.on('open',() => { console.log('可写流打开了') }) ws.on('close',() => { console.log('可写流关闭了') }) //开始写入数据 ws.write('打开文件\n') ws.write('进行操作\n') //ws.close()//如果用的是8以及以下版本,用这种方式关闭流,容易造成数据丢失 ws.end()//可用这种代替
- flags:值为
-
简单文件读取
fs.readFile(path[, options], callback)
可读取任意文件格式(读取的是二进制格式),然后再做文件写入,就是简单的文件复制- path:文件路径
- options:配置对象
- encoding
- flag
- callback:回调函数
- err:错误信息
- data:读取的数据
fs.readFile(./demo.txt,(err, data) => { if(err) { console.log(err, '读取失败') }else { console.log(data.toString())//如果需要查看文件,需要调用toString方法 } })
-
流式文件读取
fs.createReadStream(path[, options])
对于可读流来书,没有数据可读取的时候,会自动关闭可读流,不需要手动关闭
//创建可读流 let rs = fs.createReadStream(./demo.txt) //只要使用了流,必须给流添加监听 rs.on('open',() => { console.log('可读流打开了') }) rs.on('close',() => { console.log('可读流关闭了') }) //当给可读流绑定一个data事件,会自动触发读取流文件 rs.on('data', data => console.log(data))
系统模块path路径操作
为什么要进行路径拼接?
不同操作系统的路径分隔符不统一
windows上是/ or \
Linux上是/
-
路径拼接语法
join
path.join('路径','路径',...)
//引入模块 const path = require('path') const finalPath = path.join('public', 'abnc', 'sda') console.log(finalPath)//windows下输出public/abnc/sda'
-
相对路径与绝对路径
- 使用
__dirname
获取当前文件所在的绝对路径
- 使用
第三方模块
别人写好的,具有特定功能的,我们能直接使用的模块即为第三方模块
npm
-
获取第三方模块
npm(node package manager):node 的第三方模块管理工具
- 下载:npm install 模块名称
- 卸载:npm uninstall 模块名称
全局安装与本地安装
- 命令行工具:全局安装
- 库文件:本地安装
-
第三方模块nrm
- nrm(npm registry manager):npm下载地址切换工具
- npm的默认下载地址在国外,国内的卸载速度比较慢
- 使用步骤
- 使用
npm install nrm -g
下载它 - 查询可用下载地址列表
nrm ls
- 切换npm下载地址
nrm use 下载地址名称
如nrm use taobao
- 使用
Gulp
- 使用
npm install gulp
下载gulp库文件 - 早根目录下建立gulpfile.js文件
-
Gulp中提供的方法
- gulp.src():获取任务要处理的文件
- gulp.dest():输出文件
- gulp.task():建立gulp任务
- gulp.watch():监控文件变化
const gulp = require('gulp') //使用gulp.task()方法建立任务 gulp.task('first',() => { //获取要处理的文件 gulp.src('./src/css/base.css') //将处理后的文件输出到dist目录 .pipe(gulp.dest('./dist/css')) })
Gulp插件
- gulp-htmlmin:HTML文件压缩
- gulp-csso:压缩css
- gulp-babel:js语法转化
- gulp-less:less语法转化
- gulp-uglify:js压缩混淆
- gulp-file-include:公共文件包含
- browsersync:浏览器实时同步
Web服务器
-
创建web服务器
//引用系统模块 const http = require('http') //创建web服务器 const app = http.createServer(); //当前客户端发出请求的时候 app.on('request', (req, res) => { //req是请求对象,存储了请求地址,ip等等 //响应 res.end('请求已收到') }) //监听3000端口 app.listen(3000) console.log('服务已启动')
请求报文
-
请求方式
- get
- post
req.header//获取请求报文(请求头) req.url//获取请求地址 req.method//获取请求方法
响应报文
-
http状态码
- 200请求成功
- 404请求的资源没有被找到
- 500服务器端错误
- 400客户端请求有语法错误
-
内容类型
Content-Type
- text/html
- text/css
- application/javascript
- image/jpeg
- application/json
res.writeHead(200, { 'content-type': 'text/html;charset=utf8' }) //第一个参数是状态码,第二个参数是一个对象,content-type是响应文件类型,charset指定字符集
http请求与响应处理
请求参数
客户端向服务端发送请求时,有时需要携带一些客户信息,客户信息需要通过请求参数的形式传递到服务端
-
GET请求参数
-
参数被放置在浏览器地址栏中。例如
http://localhost:3001/?name=zhangsan&age=20
-
内置处理请求参数的模块
url
- 导入后直接使用
- url的parse方法可以将url中的参数解析成对象
- url.parse(需要解析的url,true)
- 第一个参数为要解析的url,第二个参数为布尔值,为true时将query参数解析成对象
//导入url模块 const url = require('url') url.parse(req.url, true)
-
post请求参数
-
参数被放置在请求体重传输
-
获取POST参数需要使用data事件和end事件
-
内置处理post参数的模块
querystring
的parse方法//引用系统模块 //创建网站服务器得模块 const http = require('http') //导入post参数处理模块 const querystring = require('querystring') //创建web服务器 const app = http.createServer(); //当前客户端发出请求的时候 app.on('request', (req, res) => { let postParams = '' res.writeHead('200', { 'content-type': 'text/html;charset=utf8' }) //监听参数传输事件 req.on('data', params => { postParams += params }) //监听参数传输完毕事件 req.on('end', () => { console.log(querystring.parse(postParams)) }) res.end('请求发送成功') }) //监听3001端口 app.listen(3001) console.log('服务已启动')
-
路由
const http = require('http')
const url = require('url')
const app = http.createServer()
app.on('request', (req, res) => {
//获取请求方式,并转成小写
const method = req.method.toLowerCase()
//获取请求地址
const pathname = url.parse(req.url).pathname
//响应报文处理
res.writeHead(200, {
'content-type': 'text/html;charset=utf8'
})
//判断请求方式
if (method === 'get') {
if (pathname === '/index' || pathname === '/') {
res.end('首页')
} else if (pathname === '/list') {
res.end('列表页')
} else {
res.end('<h1 style="text-align:center;">404 not found</h1>')
}
} else if (method === 'post') {
}
})
app.listen(3000)
console.log('服务已启动')
静态资源
服务器端不需要处理,可以直接响应给客户端的资源就是静态资源,例如:CSS、JavaScript、image文件
-
自动解析请求文件类型的第三方模块
mime
-
先
npm install mime
安装 -
再
const mime = require('mime')
导入 -
然后再使用
let pathType = mime.getType(realPath)//realPath是完整的文件路径 res.writeHead(200, { 'content-type': pathType })
动态资源
相同的请求地址,不同的请求参数,不同的响应资源,这种就是动态资源
异步编程
异步函数
异步函数编程语法是Promise
的最终解决方案,它可以让我们将异步代码写成同步的形式,解决回调地域
- 在普通函数定义的前面加上
async
关键字,普通函数就变成了异步函数 - 异步函数的默认返回值是
Promise
对象 - 在异步函数内部可以使用
throw
关键字进行错误抛出
async function fn() {
throw '发生异常' //抛出异常,throw后面的代码不会执行
return 'result' //异步函数处理后的返回值
}
fu().then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})
-
await关键字
- await关键字只能出现在异步函数中
- await后面只能写promise对象
- await关键字可以暂停异步函数向下执行,直到promise返回结果
async function p1() { return 'p1' } async function p2() { return 'p2' } async function p3() { return 'p3' } async function run() { let res1 = await p1() let res2 = await p2() let res3 = await p3() return {res1,res2,res3} }
node提供的包装异步函数的API
node提供了一个promisify
可以将node中的异步函数包装成promise,使其返回promise对象,支持函异步函数语法
- 该方法在一个
util
系统模块中 - 使用前先导入
const promisify = require('util').promisify
//例子
//淡入文件操作函数
const fs = require('fs')
//导入包装函数
const promisify = require('util').promisify
//包装fs的读取文件函数
const readFile = promisify(fs.readFile)
async function run () {
let r1 = await readFile('./01.txt', 'utf8')
let r2 = await readFile('./02.txt', 'utf8')
let r3 = await readFile('./03.txt', 'utf8')
console.log(r1)
console.log(r2)
console.log(r3)
}
run()
数据库
-
数据库相关概念
在一个数据库软件中可以包含多个数据仓库,在每个数据仓库中可以包含多个数据集合,每个数据集合中可以包含多条文档(具体的数据)
术语 解释说明 dadabase 数据库,MongoDB数据库软件中可以建立多个数据库 collection 集合,一组数据的集合,可以理解为JavaScript中的数据 document 文档,一天具体的数据,可以理解为JavaScript中的对象 field 字段,文档中的属性名称们可以理解为JavaScript中的对象属性
原生增删改查命令
-
增:
-
db.集合名.insert(文档对象)
-
db.集合名.insertOne(文档对象)
-
db.集合名.insertMany([文档对象], [文档对象])
-
-
查:
-
db.集合名.find(查询条件[, 投影])
和db.集合名.findOne(查询条件[, 投影])
例:
db.student.find({age: 18})
,查找年龄为18的所有信息 -
投影:
db.student.find({age: 18},{age: 1, _id: 0})
只有0和1,1为展示改数据,0为不展示(只能所有都一起设置0或1,id除外)
隐藏其他和id,只显示age
id需手动置0
-
-
改:
-
db.集合名.updata(查询条件, 要更新的内容[, 配置对象])
//如下将会更新内容替换整个文档对象,但id不受影响 db.demo.updata({name: 'zhangsan'},{age: 18}) //使用$set修改指定内容,其他数据不变,不过只能匹配一个zhangsan db.demo.updata({name: 'zhangsan'},{$set: {age: 18}}) //修改多个文档对象,匹配多个zhangsan,把所有zhangsan的age都替换成18 db.demo.updata({name: 'zhangsan'},{$set: {age: 18}},{multi: true}) //补充:db.集合名.updataOne(查询条件, 要更新的内容[, 配置对象]) //db.集合名.updataMany(查询条件, 要更新的内容[, 配置对象])
-
-
删:
- db.集合名.remove(查询条件)
-
常用操作符
-
<,<=,>,>=,!==对应为:$lt $lte $gt $gte $ne
db.demo.find(age: {$gte: 20})//age大于等于20的
-
逻辑或:$in $or
//查找年龄18或20的 db.demo.find(age: {$in: [18, 20]}) db.demo.find({$or: [{age: 20},{age: 18}]})
-
逻辑非: $nin
-
正则匹配:
db.demo.find(name: /^z/)
-
-
mongoose第三方包
- 使用Node.js操作MongoDB数据库需要依赖Node.js第三方包mongoose
- 使用
npm install mongoose
命令下载
-
启动MongoDB
在命令行工具中运行
net start mongodb
即可启动MongoDB,否则MongoDB将无法连接
net stop mongodb
停止服务 -
数据库连接
使用mongoose提供的connect
方法即可连接数据库//引入mongoose第三方模块,用来操作数据库 const mongoose = require('mongoose') mongoose.connect('mongodb://localhost/newData', { useUnifiedTopology: true }) //连接成功 .then(() => console.log('数据库连接成功')) //连接失败 .catch((err) => console.log(err, '数据库连接失败'))
-
创建数据库
在MongoDb中不需要显式创建数据库,如果正在使用的数据库不存在,MongoDB会自动创建 -
创建集合
创建集合分为两步,一是对集合设定规则,二是创建集合,创建mongoose.Schema
构造函数的实例即可创建集合。//设定集合规则 const courseSchema = new mongoose.Schema({ name: String, author: String, isPublished: Boolen }); //创建集合并应用规则 const Course = mongoose.model('Course', courseSchema); //course
-
创建文档
创建文档实际上就是向集合中插入数据分为两步:
①创建集合实例
②调用实例对象下的sava方法将数据保存到数据库中//创建集合实例,向集合中插入文档 const course = new Course({ name: 'nodejs', author: 'lishanbing', isPublished: true }) //将集合保存在数据库中 course.save()
- 创建集合的另一种方法(调用构造函数的create方法)
//向集合中插入文档的另一种方式 Course.create({name: 'nodejs', author: 'lishanbing', isPublished: true}, (err, res) => { if(err) { console.log(err) } else { console.log(res) } })
create返回的是Promise对象
Course.create({ name: 'node学习', author: 'li', isPublished: false }) .then(res => console.log(res)) .catch(err => console.log(err))
mongoose操作数据库
-
新增:
- 模型对象.create(新增的数据)
-
查询:
- 模型对象.find(查询条件[, 投影]),不管有没有都返回一个数组
- 模型对象.findOne(查询条件[, 投影]),返回找到的第一个对象,没有就返回null
- 可选择查询字段:例如
模型对象.find().select('name, age')
如果不想查询该字段,字段名前加-
例如:不查询id字段-_id
- 还可以将查询出来的根据某个字段排序:
模型对象.find().sort('age')
默认升序排序,字段前加-
就是降序排序
-
更新:
- 模型对象.updataOne(查询条件,要更新的内容[,配置对象])
- 模型对象.updataMany(查询条件,要更新的内容[,配置对象])
-
删除:
- 模型对象.deleteOne(查询条件)
- 模型对象.deleteMany(查询条件)
-
以上方法,如果没有在参数最后指定回调函数,均返回一个Promise对象
-
MongoDB数据库导入数据
mongoimport -d 数据库名称 -c 集合名称 --file 要导入的数据文件
找到MongoDB数据库的安装目录,将安装目录下的bin目录放置在环境变量中
-
查询文档
实例对象下有个find()方法(条件为空则查找所有文档)
//根据条件查找文档 Course.find().then(res => console.log(res))
-
mongoose验证
在创建集合时,可以设置当前字段的验证规则- required: true 必传字段
- minlength:3 字符串最小长度
- maxlength:20 字符串最大长度
- min:2 数值最小值为2
- max:4 数值最大值为4
- enum:[‘html’, ‘css’, ‘javascript’, node.js] 枚举,列举当前字段可拥有的值
- trim:去除字符串两边的空格
- validate:自定义验证器
- default:默认值
- unique:true 字段唯一性
- type:schema.types.Mixed 接收所有类型
模板引擎
-
art-template模板引擎
- 使用
npm install art-template
下载 - 然后引入模板引擎
const template = require('art-tmplate')
- 告诉模板引擎要拼接的数据和模板在哪儿
const html = tempate('模板路径', 数据)
- 返回值是拼接好的字符串
//代码示例 //导入模板引擎模块 const tempplate = require('art-template') //将特定模板与特定数据进行拼接 const heml = template('./view/index.art',{ data: { name: '张三', age: 20 } })
- 使用
模板引擎语法
-
模板语法
- art-template同时支持两种模板语法,标准语法和原始语法
- 标准语法可以让模板更容易读写,原始语法具有更强大的逻辑处理能力
标准语法:{{ 数据 }}
原始语法:<%= 数据 %> - 模板中可以进行简单的运算(例如三目运算)
-
原文输出
如果数据中携带HTML标签,默认模板引擎不会解析标签,会将其转义后输出
如果想要使模板解析html标签- 标准语法:{{@数据}}
- 原始语法:<%-数据%>
-
条件判断
在模板中可以根据条件来决定显示哪块HTML代码
-
标准语法:
{{if 条件}}...{{/if}} {{if v1}}...{{else if v2}}...{{/if}}
-
原始语法
<% if(value){%>...<%}%> <% if (value){%>...<%} else if(value2){%>...<%}
-
-
循环
-
标准语法:
{{each target}} {{$index}} {{$value}} {{/eacah}}
-
原始语法:
<% for(var i = 0; i < target.length; i++){ %> <% =i %> <%= target[i] %> <% } %>
-
-
子模板
使用子模板可以将网站公共部分(头部,底部)抽离到单独的文件中
-
标准语法:{{include ‘模板’}}
{{index './header.art'}}
-
原始语法:<% include (‘模板’) %>
<% include ('./header.art') %>
-
-
模板继承
使用模板继承可以将网站HTML骨架抽离到单独的文件中,其他页面模板可以继承骨架文件
第三方模块 serve-static
功能:实现静态资源访问服务
步骤:
- 下载并引入serve-static模块获取创建静态资源服务功能的方法
- 调用方法创建静态资源服务并指定静态资源服务目录
- 启用静态资源服务功能
const serveStatic = require('serve-static')//引入模块,返回一个方法
const serve = serveStatic('public')//调用方法,第一个参数就是静态资源目录,它也返回一个方法,用于启用静态资源服务
server.on('request', (req, res) => {//监听请求启用静态资源服务
serve(req, res)
})
server.listen(3000)
erpress框架
0express是一个基于node平台的web应用开发框架,提供了一些列强0大的特性,帮助创建各种web应用
0使用npm install express
命令进行下载
- express框架特性
- 提供了方便简洁的路由定义方式
- 对获取HTTP请求参数进行了简化处理
- 对对模板引擎支持程度高,方便渲染动态的HTML页面
- 提供了中间件机制有效控制HTTP请求
- 拥有大量的第三方中间件对功能进行扩展
中间件
中间件就是一堆方法,可以接收客户端发来的请求、可以对请求作出响应,也可以将请求继续交给下一个中间件继续处理
中间方法由Express提供,负责拦截请求,请求处理函数由开发人员提供,负责处理请求
例如:
app.get('请求路径',处理函数) //接收并处理get请求
app.post('请求路径', 处理函数) //接收并处理post请求
可以针对同一个请求设置多个中间件,对同一个请求进行多次处理
默认情况下,请求从上到下依次匹配中间件,一旦匹配成功,终止匹配
可以调用next方法将请求的控制权交给下一个中间件,直到遇到结束请求的中间件**(next是请求处理函数的第三个参数)**
app.get('/request',(req, res, next) => {
req.name = '张三'
next()
})
app.get('/request', (req, res, next) => {
res.send('req.name')
})
//访问localhost:3000/requset将会渲染出张三
-
app.use中间件方法
.use
方法匹配所有的请求方式,可以直接传入请求处理函数,代表接收所有的请求app.use((req, res, next) => { console.log('req.url') next() })
.use
第一个参数也可以传入请求地址,代表不论什么请求方式,只要是这个请求地址就接收这个请求app.use('/requset',(req, res, next) => { console.log(req.url) next() })
中间件的应用
-
路由保护,客户端在网文需要登录的页面时,可以先使用中间件判断用户登录状态,如果用户未登录,则拦截请求,直接响应,禁止用户进入需要登录的页面
-
网站维护公告,在所有路由的最上面定义接收所有请求的中间件,直接为客户端作出响应,网站正在维护中
-
自定义404页面(写在所有路由的最后面,当前面所有路由都没有响应时,最后响应404)
app.use((req, res) => { //status是响应状态码 res.status(404).send('当前访问的页面不存在') })
-
错误处理中间件
在程序执行过程中,不可避免的会出现一些无法预料的错误,比如文件读取失败,数据库连接失败,错误处理中间件是一个集中处理错误的地方app.get('/index',(req. res) => { //throw newErrow 是制造错误并抛出 throw new Error('程序发生了未知错误') }) app.use((err, req, res, next) => { res.status(500).send(err.message)//响应错误信息给客户端 })
错误处理中间件只能主动捕获到同步代码执行出错,如果异步代码执行出错,错误处理中间件是捕获不到的,需要手动触发错误处理中间件,当异步API执行出错时,调用next方法,并将错误信息传递给next方法
app.get('/',(req, res, next) => { fs.readFile('./demo.js',(err, result) => { if(err) { next(err) } else { res.send(result) } }) }) //错误处理中间件 app.use((err, req, res, next) => { res.status(500).send(err.message)//响应错误信息给客户端 })
捕获错误
在nodejs中,异步API的错误信息都是通过回调函数获取的,支持Promise对象的异步函数API可以通过catch方法捕获。
而异步函数比较特殊(同步函数写成异步形式)
try catch可以捕获异步函数以及其他同步代码在执行过程中发生的错误,但是不能捕获其他类型API发生的错误(例如:回调函数,Promise对象)
app.get('/',async (req, res, next) => {
try {
await User.find({name: '张三'})
} catch (ex) {
next(ex)
}
})
express请求处理
构建模块化路由
基础
const express = require('express')
//创建网站服务器
const app = express()
//创建路由对象
const home = express.Router()
//匹配请求路径
app.use('/home',home)
//在home路由下继续创建路由(二级路由)
home.get('/index',(req, res) => {
res.send('博客首页')
})
//监听端口
app.listen(3000, (err) => {
if(!err) {
console.log('服务器已启动')
}
})
进阶:
把不同的路由模块放到不同的文件中,在app.js中进行引入并进行路径匹配
//app.js
const express = require('express')
const app = express()
//导入admi
const home = require('./route/home')
// 导入home
const admi = require('./route/admi')
app.use('/home', home)
app.use('/admi', admi)
//端口监听
app.listen(3000,(err)=> {
if(!err) {
console.log('服务器已启动')
}
})
//home.js
const express = require('express')
const home = express.Router()
home.get('/index', (req, res)=> {
res.send('欢迎来到博客首页')
})
module.exports = home
//admi.js
const express = require('express')
//创建路由对象
const admi = express.Router()
//在admi路由下继续创建路由(二级路由)
admi.get('/index', (req, res)=> {
res.send('欢迎来到‘我的’')
})
module.exports = admi
GET参数的获取
Express框架中使用**req.query
**即可获取GET参数,框架内部会将GET参数转换为对象并返回
//接收地址栏中问号后面的参数
//例如http://localhost:3000/?name=zhangsan&ane=18
app.get('/',(req, res)=> {
console.log(req.query())//{name: 'zhangsan', age: 18}
})
POST参数的获取
Express中接收post请求参数需要借助第三方包body-parser
body-parser模块对象下的urlencoded方法对请求进行处理,方法内部会检测当前请求中是否包含了请求参数,如果包含,会接收请求参数,并将请求参数转换为对象类型,然后为request请求对象添加一个body属性,并将请求参数作为值赋值给body属性,并在内部调用next()方法将请求控制权交给下一个中间件
- urlencoded方法有一个extended(对象)参数(用于选择参数格式处理模块),参数值为false时,方法内部会使用querystring系统模块对参数格式进行处理,参数为true时使用qs第三方模块(功能强大)对请求参数进行处理
//引入body-parser
const bodyParser = require('body-parser')
//配置parser模块
app.use(bodyParser.urlencoded({extended: false}))
//接收请求
app.post('/add',(req, res)=> {
//接收请求参数
console.log(req.body)
})
Express路由参数
app.get('/find/:id',(req, res)=> {
//:id是占位符,表示当前路由必须要接收参数id
//如果需要传递多个参数,在地址栏后面接着写/:xxx
res.send(req.params)//{id:123}
//传递的参数存储在req.params属性中
//req.params是一个对象,对象中存储着路由参数
})
//请求地址:localhost:3000/find/123,123是参数
静态资源处理
通过Express内置的express.static
可以方便的托管静态文件,例如img,css,js文件等
使用app.use
拦截所有请求,然后将请求交给express.static()方法处理,并且将静态资源目录做参数传递进去,方法内部会判断客户端请求是否为静态资源请求,如果是静态资源,方法内部将静态资源响应给客户端,终止这次请求,如果不是,方法内部调用next方法,将控制权交给下一个中间件
app.use(express.static('public'))//public是静态资源目录
之后public中有什么文件就可以直接访问了
例如:localhost:3000/images/1.jpg
模板引擎
- 为了是art-temolate模板引擎能够更好的和Express框架配合,模板引擎官方在原art-template基础上封装了express-art-template
使用时两个都需要下载 npm install art-template express-art-template
下载安装- 模板语法与原来一样
//使用engine方法向express框架声明使用的模板引擎与模板后缀
app.engine('art',require('express-art-template'))
//使用set方法,第一个参数是固定的,第二个参数是配置模板存放目录
app.set('views', path.join(__dirname, 'views'))
//express允许一个项目使用多个模板,这样就有多个模板后缀
//渲染模板时不写后缀,默认拼接art后缀
app.set('view engine', 'art')
app.get('/', (req, res) => {
//渲染模板,使用res响应对象下的render,渲染模板时只需要传递模板名称即可,内部会自动拼接模板所在位置及后缀,自动处理模板与数据,将拼接好的结果直接响应给客户端
//第一个参数是模板名称,第二个参数是向模板中传递的数据(对象)
res.render('index')
})
app.locals对象
当遇到,在不同的页面中,有一些公共区域,公共区域需要展示的数据都是一样的,这时,就需要用到app服务器下的locals对象,将公共数据作为locals对象的属性存放,locals对象的属性所有模板都可以获取到
将变量设置到app.locals对象下面,这个数据在所有模板中都可以获取到
//为locals添加一个自定义属性,属性名自定义
app.locals.users = [{
name: '张三',
age: 18
},{
name: '李四',
age: 20
}]
密码加密sha1
与md5
-
sha1加密
安装:npm install sha1
使用:
const sha1 = require('sha1') const password = sha1(password)//参数是要加密的数据
-
md5加密,用法同上
密码加密bcrypt
-
单程加密方式: 123456 => abcd
只能从123456变成abcd ,不能从abcd变成123456 -
在加密的密码中加入随机字符串可以增加密码被破解的难度
//导入bcrypt模块
const bcrypt = require('bcrypt')
//生成随机字符串 gen => generate 生成salt盐
let salt = await bcrypt.genSalt(10)
//使用随机字符串对密码进行加密
let pass = await bcrypt.hash('明文密码', salt);
- bcrypt依赖的其他环境
- python 2.x
- node-gyp
npm install -g node-gyp
- windows-build-tools
npm install --global --production windows-build-tools
//密码比对
let isEqual = await bcrypt.compare('明文密码', '加密密码')
cookie
cookie:浏览器在电脑硬盘中开辟的一块空间,主要供服务器存储数据
-
cookie中的数据是以域名的形式进行区分的
-
cookie中的数据是有过期时间的,超过时间数据会被浏览器自动清除
-
cookie中的数据会随着请求被自动发送到服务器端
-
关于cookie
本质就是一个字符串,里面包含着浏览器和服务器的沟通信息(发生交互时产生的信息)
存储的形式以key-value的形式存储
浏览器会自动携带该网站的cookie,只要是该网站下的cookie,全部携带 -
分类
- 会话cookie(关闭浏览器后会自动消失,会话cookie储存在浏览器运行的那块内存上)
- 持久化cookie(看过期时间,到了过期时间,自动销毁,存储在用户的硬盘上,如果没到过期时间,但是用户清除了浏览器缓存,持久化cookie也会消失)
-
工作原理:
- 当浏览器第一次请求你服务器的时候,服务器可能返回一个或多个cookie给浏览器
- 浏览器判断一下cookie种类:
- 会话cookie
- 持久化cookie
- 以后请求该网站的时候,自动携带上该网站的所有cookie(无法进行干预)
- 服务器拿到之前自己种下的cookie,分析里面的内容,校验cookie的合法性。根据cookie里保存的内容,进行具体的业务逻辑
-
应用:
解决http无状态的问题(例子:7天免登陆,一般来说,不会单独使用cookie,会配合后台的session使用) -
种
如果不传入maxAge,则为会话cookie,随着浏览器的关闭自动消失
//给客户端“种”下一个会话cookie response.cookie('demo', 123)//第一个参数是cookie名,第二个参数是cookie值 //给客户端“种”下一个持久化cookie response.cookie('demo', 123, { maxAge: 30 * 1000 //持续30秒的 })
-
读
使用cookie-parser,解析浏览器携带过来的cookie为一个对象,随后挂载到request请求对象上
npm install cookie-parser
const cookieParser = require('cookie-parser') // app.use(cookieParser()) request.cookies //这样可直接获取客服端携带的cookie
-
删
//第一种删除方式 response.cookie('demo', '', {maxAge: 0}) //第二种删除方式 response.clearCookie('demo')
session
- 关于session
- 是什么
标准来说,session指的是会话,但是后端人员常说的session,全称教session会话存储 - 特点:
- 存在于服务端
- 存储的是浏览器和服务器之间沟通产生的一些信息
- 默认session存储在服务器的内存中,每当一个新客户端发来请求,服务器都会新开辟出一块空间,供session会话存储使用
- 工作流程:
- 第一次浏览器请求服务器的时候,服务器会开辟出一块内存空间,供session会话存储使用
- 返回响应的时候,会自动返回一个cookie(有时候返回多个,为了安全),cookie里包含着,上一步产生会话存储“容器”的编号(id)
- 以后请求的时候,会自动携带这个cookie给服务器
- 服务器从该cookie中拿到对应的session的id去服务器中匹配
- 服务器会根据匹配信息,决定下一步具体业务逻辑
- 备注:
- 一般来说cookie一定会配合session使用
- 服务端一般会做session的持久化,防止由于服务器重启,造成session的损失
- 是什么
操作session
-
下载安装:
npm i express-session --save
用于在express中操作session -
下载安装:
npm i connect-mongo --save
用于将session写入数据库(session持久化) -
引入express-session模块
const session = require('express-session')
-
引入connect-mongo模块
const MongoStore = require('connect-mongo')(session)
-
编写全局配置对象
//定义一个cookie和session组合使用的配置对象 const session = require('express-session') const MongoStore = require('connect-mongo')(session) app.use(session({ name: 'abc', //设置cookie的name,默认值是:connect.sid secret: 'abcd', //参与加密的字符串,又称签名 cookie: { httpOnly: true, //开启后前端无法通过js操作cookie maxAge: 1000 * 30 //设置cookie的过期时间,单位毫秒 }, saveUninitialized: false, //是否在存储内容指点创建会话(存储为初始化的cookie) resave: false, //是否在每次请求时,强制重新保存session,即使他们没有变化 store: new MongoStore({ url: 'mongodb://localhost:27017/cookies_containe', //session存储的数据库地址 touchAfter:24 * 3600 //修改频率(数据库存储的session的同步频率) }) }))
-
用户退出
- 删除session
- 删除cookie
- 重定向到登录页面
//删除session,删除完成后回调一个函数,在这个回调函数中删除cookie和重定向到登录页面 req.session.destroy(()=>{ //删除cookie res.clearCookie('connect.sid')//参数为cookie名 //重定向到登录页面 res.redirect('/admin/login') })
Joi
JavaScript对象的规则描述语言和验证器
npm install joi
//引入joi
const joi = require('joi')
//定义验证规则
const schema = {
username: joi.string().alphanum().min(2).max(6).required().error(new Error('错误信息')),
password: joi.string.regex(/^[a-zA-Z0-9{6,16}]$/),
access_token: [joi.string(),joi.number()],
birthyar: joi.number().integer().min(1900).max(2020),
email: joi.string().email()
}
await joi.validate({username:'abc', birthyaer: 1999}, schema)//第一个参数是要验证的对象,第二个参数是验证规则,验证通过返回验证对象,不通过则抛出异常
数据分页
当数据库中的数据非常多时,数据需要分批次显示,这时就需要用到数据分页功能
分页功能核心要素:
- 当前页,用户通过点击上一页或下一页或者页码产生,客户端通过get参数方式传递到服务器端
- 总页数,根据页数判断当前页是否为最后一页,根据判断结果做出相应操作
总页数:Math.ceil(总数据条数/每页显示数据条数)
模型对象.countDocuments(查询条件)//也是一个异步API
enctype表单编码类型
-
enctype指定表单编码类型(enctype是表单元素的一个属性)
-
application/x-www-form-urlcode
编码成键值对形式(name=zhangsan&age=20) -
multipart/form-data
将表单数据编成二进制类型
-
formidable
作用:解析表单,支持get请求参数,post请求参数,文件上传
//引入formidable模块
const formidable = require('formidable')
//创建表单解析对象
const form = formidable.IncomingForm()
//设置文件上传路径
form.uploadDir = 'my/dir'
//是否保留表单上传文件的扩展名
form.keepExtensions = true//默认是fale,不保存
//对表单进行解析
form.parse(req, (err, fields, files)=>{
//fields存储普通请求参数
//files存储上传的文件信息
})
文件读取FileReader
js文件读取
//创建文件读取对象
let reader = new FileReader();
//调用文件读取对象下的readAsDataURL方法读取文件(二进制文件,例如图片)
//异步方法,不能通过获取返回值来回去文件读取结果
reader.readAsDaraURL('文件')
//通过监听事件的方式过去文件读取结果
//文件读取完毕后onload事件将会被触发
reader.onload = function(){
//reader.result就是文件读取返回值(文件编码),如果是图片,直接将返回值放置在src属性中即可显示图片
console.log(reader.result)
}
dateformat第三方模块
日期格式化模块
const dateformat = require('datefoemat')
//引入art-template
const template = require('art-template')
//将日期格式化函数导入模板模板即可直接调用该函数
template.defaults.imports.dateformat = dateformat
let date = dateformat(需要格式化的日期, 'yyyy-mm-dd')
数据分页模块mongoose-sex-page
const pagination = require('mongoose-sex-page')
pagination(集合构造函数).page(1).size(10).display(8).exec()
//数据查询出来的实例
{
"page": 1, //当前页
"size": 2, //每页显示数据条数
"total": 8, //总共的数据条数
"records": [
//查询出来的具体数据
{
"_id": xxx,
"title": xxx
}
],
"pages": 4, //总共的页数
"display": [1,2,3,4]//客户端显示的页码
}
MongoDB数据库添加账号
- 超级管理员账号: 可对所有数据库进行操作(必须先创建)
- 普通管理员账号:只能对指定的一个数据库操作
-
管理员方式打开命令行
-
连接数据库
mongo
-
查看数据库
show dbs
-
切换到数据里
use 数据库名
(例如切换到admin数据库:use admin
)需要先进入到admin数据库才能创建超级管理员账号
-
创建超级管理员账号:db.createUser()
db.createUser()
接收一个对象做参数
对象中有一些固定的属性:- user:用户名
- pwd:密码
- roles(接收数组):角色(root代表超级管理员用户)
例如:
db.createUser({user: 'admin', pwd: '123', roles:['root']})
-
创建普通管理员账号
为哪一个数据库添加账号,就需要先切换到那个数据库use blog
然后再执行db.createUser()
创建普通账号除了
roles
要更改为['readWrite']
,其他与创建超级管理员账号一样 -
卸载MongoDB服务
- 停止服务:
net stop mongodb
- 删除服务:
mongod --remove
- 停止服务:
-
创建MongoDB服务
mongod --logpath='D:\software\MongoDB\log\mongod.log' --dbpath='D:\software\MongoDB\data' --install --auth
-
启动MongoDB服务
net start mongodb
-
在项目中使用账号连接数据库
mongoose.connect('mongodb://user:pwd@localhost:27017/database')
-
开发环境与生产环境
-
什么是卡方法环境与生产环境
环境,就是指项目运行的地方,当项目处于开发阶段,项目运行在开发人员的电脑上,项目所处的环境就是开发环境。当项目开发完成以后,要将项目放到真实的网站服务器电脑中运行,项目所处的环境就是生产环境 -
为什么要区分开发环境与生产环境
因为在不同环境中,项目的配置是不一样的,需要在项目代码中判断当前项目运行的环境,根据不同的环境应用不同的项目配置
-
如何区分开发环境与生产环境
通过电脑操作系统中的系统环境变量区分当前是开发环境还是生产环境
-
环境变量名
NODE_ENV(可自定义)
-
设置环境变量值
development:开发环境
production:生产环境 -
在代码中获取环境并判断
if(process.env.NODE_ENV == 'development') { //开发环境 } else { //生产环境 }
-
-
在开发环境中将客户端的请求信息打印到控制台
需要安装一个第三方模块
npm install morgan
const morgan = require('morgan') if(process.env.NODE_ENV == 'development') { //开发环境 app.use(morgan('dev')) } else { //生产环境 }
第三方模块config模块
作用:允许开发人员将不同运行环境下的应用配置信息抽离到单独的文件中,模块内部自动判断当前应用的运行环境,并读取对应的配置信息,极大提高应用配置信息的维护成本,避免了当运行环境重复的多次切换时,手动到项目代码中修改配置信息
使用步骤
- 使用
npm install config
命令下载模块 - 在项目的根目录下新建config文件夹
- 在config文件夹下面新建default.json、development.json、production.json文件
- 在项目中通过require方法将模块进行导入
- 是用模块内部 提供的get方法获取配置信息
将敏感信息存储在环境变量中
-
在config文件夹中建立custom-environment-variables.json文件
-
配置项属性的值填写系统环境变量的名字
-
项目运行时config模块查找系统环境变量,并读取其值作为当前配置项的值
- 使用