- Express框架
- node+express+MongoDB实现博客系统实践总结
Express框架
1 Express框架简介和使用
1.1Express是什么
- 是一个基于node平台的web开发框架,提供了一系列强大的特性,帮助你创建各种Web应用
- express是第三方插件,需要下载插件
1
npm install express
1.2Express框架特性
- 提供了方便简化的路由定义
- 对获取http请求参数进行了简化处理
- 对模板引擎支持程度高,方便渲染HTML页面
- 提供了中间件有效的控制http请求
- 拥有大量第三方插件对功能进行扩展
- GET/POST参数不用path模块直接通过req.query获取参数值,获取到的参数直接就是对象,不用在转化
1.3 初步使用
- 框架使用优势总结:
- 可以通过框架直接创建服务对象,不需要http模块
- 通过服务对象app直接进行路由,不需要引入路由模块router
- 通过res.send()方法对客户端做出响应,可以传递多种类型的参数(对象,字符串),比req.end()方法好用;
- send()会根据内容的类型自动设置请求头,不用通过writeHead()来设置编码和内容类型等,send()方法的详细使用见博文
- send方法内部会检测响应内容的类型
- send方法会自动设置http状态码
- send方法会帮我们自动设置响应的内容类型及编码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 初始express
// 引入框架,返回的是一个方法
const express = require('express');
// 使用框架可以直接获取服务对象,不需要使用http模块
const app = express();
// 插入响应事件
app.get('/', (req, res) => {
// 通过send()向客户端响应请求,
// 对客户端做出响应 send方法会根据内容的类型自动设置请求头,
res.send({ name: '张三', age: 12 });
})
// 设置监听
app.listen(3000, () => {
console.log('App listening on port 3000!');
});2 Express中间件
2.1 什么是中间件
- 中间件就是一堆方法,可以接收客户端发来的请求、可以对请求做出响应,也可以将请求继续交给下一个中间件继续处理。
- 中间件组成
- 中间件的方法(get,post):可以用来创建路由
- 请求处理函数(方法中的callback)
- 中间件方法由Express提供,负责拦截请求,请求处理函数由开发人员提供,负责处理请求。
1
2app.get('请求路径', '处理函数') // 接收并处理get请求
app.post('请求路径', '处理函数') // 接收并处理post请求 - 可以针对同一个请求设置多个中间件,对同一个请求进行依次处理。
默认情况下,请求从上到下依次匹配中间件,一旦匹配成功,终止匹配。
可以调用next方法将请求的控制权交给下一个中间件,直到遇到结束请求的中间件。(eg:res.end()/send等) - 注意:中间件方法如果不是最后一个处理必须添加next()将处理权转给下一个中间件,否则页面一直在等待响应
1
2
3
4
5
6
7app.get('/request', (req, res, next) => {
req.name = "张三";
next();
});
app.get('/request', (req, res) => {
res.send(req.name);//必须返回响应请求才能执行结束
});2.2 app.use
- app.use 匹配所有的请求方式,可以直接传入请求处理函数,代表接收所有的请求。
1
2
3
4app.use((req, res, next) => {
console.log(req.url);
next();
}); - app.use 第一个参数也可以传入请求地址,代表不论什么请求方式,只要是这个请求地址就接收这个请求。
1
2
3
4app.use('/admin', (req, res, next) => {
console.log(req.url);
next();
});2.3 中间件的应用
- 路由保护,客户端在访问需要登录的页面时,可以先使用中间件判断用户登录状态,用户如果未登录,则拦截请求,直接响应,禁止用户进入需要登录的页面。
- 网站维护公告,在所有路由的最上面定义接收所有请求的中间件,直接为客户端做出响应,网站正在维护中。这里必须放在所有中间件之前才能拦截所有请求
- 自定义404页面放在所有中间件后面,代表没有匹配到中间件
- 浏览器在404页面中默认状态码200
- 可以设置状态码status(404)
1
res.status(404).send('你访问的页面不存在');
- 浏览器在404页面中默认状态码200
1 | const express = require('express'); |
response对象中可以采用链式编程调用
1
res.status(404).send();
2.4 错误处理中间件
在程序执行的过程中,不可避免的会出现一些无法预料的错误,比如文件读取失败,数据库连接失败。
错误处理中间件是一个集中处理错误的地方
错误处理中间件有4个参数
- error:错误对象,有message属性获取错误信息
- req:请求对象
- res:响应对象
- next:转移下一步操作控制权的函数
1
app.use((err,req,res,next)=>{})
当程序出现错误时,调用next()方法,并且将错误信息通过参数的形式传递给next()方法,即可触发错误处理中间件。
1
next(error对象);
手动抛出错误:Error()是一个构造函数
1
throw new Error('错误信息描述');//返回err对象
代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17app.get('/index', (req, res, next) => {
// 手动抛出错误,通过new Error的构造函数和关键字throw就会手动抛出错误
// throw new Error('程序发生了未知错误');
fs.readFile('./01.js', 'utf8', (err, result) => {
if (err != null) {
// 将err对象作为参数传给next()函数,触发错误处理终中间件
next(err);
} else {
res.send(result);
}
})
})
// 错误处理中间件
app.use((err, req, res, next) => {
//设置状态码返回错误信息
res.status(500).send(err.message);
})2.5 捕获错误
异步API执行返回值
- 在node.js中,异步API的错误信息都是通过回调函数获取的,
- 支持Promise对象的异步API发生错误可以通过catch方法捕获。
- 不是promise对象的异步API执行如果发生错误通过将异步API转化为支持promise对象(util模块下的promisfy方法),在利用catch方法捕获
try catch 可以捕获异步函数以及其他同步代码在执行过程中发生的错误,但是不能其他类型的API发生的错误。
eg:
- readFile()异步函数返回的不是promise对象,这里需要转化为promise对象
- error返回的是一个对象,通过next(error对象)触发错误中间件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17const promisify = require('util').promisify;
const readFile = promisify(fs.readFile);//转化为promise对象
app.get('/index', async (req, res, next) => {
let result;
try {
// 易出错代码放在try中捕获错误
result = await readFile('./01.js', 'utf8');
} catch (error) {
// 执行失败则执行catch语句,触发错误中间件,否则执行后面语句
next(error);
}
res.send(result);
})
// 错误处理中间件
app.use((err, req, res, next) => {
res.status(500).send(err.message);
})
注意
- 异步函数中返回的必须是promise对象才能使用异步函数的语法
- 异步函数中的api执行的是同步代码
- 返回的不是prmise对象的异步函数要使用异步函数的语法通过util模块下的promisfy转化为promise对象在使用
3 Express框架请求处理
3.1 构建模块化路由
- express第三方插件引入以后返回的是一个对象,这个对象本身也是方法,在该对象下面,既有方法的直接调用也有方法
- 可以直接调用const app = express()来创建网站服务器对象
- 路由方法:get()/post()
1
2const express = require('express');
express是方法名也是对象
- 路由方法:get()/post()
- 通过express对象的方法来生成对象,方法有:
- 路由方法:get()/post()
1
2//获取创建路由对象
const router = express.Router();
- 路由方法:get()/post()
- 注意:node中可以构建多级路由,这里的app和router对象都可以用来创建路由,app创建一级路由,router创建二级路由,路由中将二者的路径拼接起来进行查找
3.1.1 构建基础路由
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 返回的是方法名,方法名也是对象
const express = require('express');
// 创建网站服务器对象,express()创安
const app = express();
// 创建路由对象,通过express下的方法创建
const home = express.Router();
// node中可以创建多级路由,可以构建多级路由
// 为路由对象匹配路径
app.use('/home', home);
// 创建二级路由
home.get('/index', (req, res, next) => {
// 此时的路由路径/home/index
res.send('欢迎来到博客首页');
});3.1.1 构建模块路由
- 将不同的路由模块放在不同的js中,最后通过app.js中引入模块化的路由
- 构建的模块化路由,必须将其导出才能在外面进行访问
1
module.exports = home;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27route/home.js
const express = require('express');
// 获取路由对象
const home = express.Router();
// 添加路由路径
home.get('/index', (req, res) => {
res.send('欢迎来到home页面');
});
//将home路由导出
module.exports = home ;
route/admin.js
const express = require('express');
// 获取路由对象
const admin = express.Router();
// 添加路由路径
admin.get('/index', (req, res) => {
res.send('欢迎来到admin页面');
});
//将admin路由导出
module.exports = admin ;
app.js
// 导入路由模块
const home = require('./route/home');
const admin = require('./route/admin');
app.use('/home', home);
app.use('/admin', admin);3.2 Get参数的获取
- Express框架中使用req.query即可获取GET参数,框架内部会将GET参数转换为对象并返回。(不在需要path模块进行转化)
1
2
3
4
5
6
7ps:接收地址栏?后面的参数
http://localhost:3000/index?name=zhangsan&age=30
app.use('/index', (req, res, next) => {
// express中提供了query方法来查询get参数,返回的直接就是对象的格式,不需要在通过path模块进行转化
res.send(req.query);//直接返回对象
});3.3 POST参数的获取
- post参数通过第三方模块body-parser来获取
- 配置body-parser
- extended: false方法内部使用querystring模块处理请求参数的格式
- extended: true 方法内部使用第三方模块qs处理请求参数的格式
1
2//拦截所有请求
app.use(bodyParser.urlencoded({ extended: false }));- req.body就是body-parser添加在req中添加的属性,这个属性的值就是post请求参数
- bodyParser.urlencoded()返回的是一个函数,在这个函数中传入了参数{ extended: false}
- 内部运行机制:函数内部传入参数之后按照指定的方式进行对body参数进行设置,然后调用next()函数让其他中间件执行
- 通过req.body可以获取post传过来的参数
- 代码执行
1
2
3
4
5
6
7
8
9// 引入body-parser模块
const bodyParser = require('body-parser');
// 配置body-parser模块
app.use(bodyParser.urlencoded({ extended: false }));
// 接收请求
app.post('/add', (req, res) => {
// 接收请求参数
console.log(req.body);
})
- 代码执行
3.4 Express路由参数
- 获取get参数的另外一种方式
- 使用
- 通过Express内置的express.static可以方便地托管静态文件,例如img、CSS、JavaScript 文件等。
- express.static(‘实际资源存放地址’)
- 地址采用绝对路径引入path模块
- 访问访问:http:localhost/public文件夹下的地址
1
app.use(express.static('public'));
- 文件目录
- public
- css
- html文件(default.html)
- js
- app.js文件
- public
- 可以设置虚拟地址,若设置了虚拟地址则必须添加才能访问eg:http://localhost:3000/static/default.html
1
app.use('/static', express.static(path.join(__dirname, 'public')));
- 未设置虚拟地址:http://localhost:3000/default.html
1
app.use( express.static(path.join(__dirname, 'public')));
4 Express-art-template模板引擎
- Express框架中可以同时存在不同的模板引擎
- 为了使art-template模板引擎能够更好的和Express框架配合,模板引擎官方在原art-template模板引擎的基础上封装了express-art-template。
- 下载需要将两个模块都要下载
1
npm install art-template express-art-templat
- 使用
- 1.引入模板引擎
1
require('express-art-template')
- 2.设置express框架的模板引擎和文件后缀名
- app.engin(参数1,参数2)
- 参数1:后缀文件
- 参数2:模板引擎
1
app.engine('文件后缀名',模板引擎)
- app.engin(参数1,参数2)
- 3.设置模板存放目录
- app.set(参数1,参数2)
- 参数1:配置文件名,默认views,不用更改
- 参数2:路径文件名,一般在views文件夹中
1
app.set('views', path.join(__dirname, 'views'))
- app.set(参数1,参数2)
- 4.设置框架模板默认后缀名
- app.set(参数1,参数2)
- 参数1:配置文件名,默认view engine
- 参数2:文件路径名,一般在public文件夹中
1
app.set('view engine', 'art');
- app.set(参数1,参数2)
- 5.渲染页面res.render()
- 注意调用的对象是response对象,不是app网站服务对象app
- render(‘模板文件名’,{为模板提供的数据})
- render()方法实现功能:
- 拼接模板路径
- 拼接模板后缀
- 哪一个模板和哪一个数据进行拼接
- 把拼接响应给客户端
1
2
3res.render('index', {
msg: 'message'
})
- 1.引入模板引擎
- 完整代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17app.engine('art', require('express-art-template'));
// 2.express模板存放的位置
app.set('views', path.join(__dirname, 'views'))
// 3.express框架模板设置默认的后缀名
app.set('view engine', 'art');
// 设置路由
app.get('/index', (req, res) => {
res.render('index', {
msg: 'message'
})
});
app.get('/list', (req, res) => {
res.render('list', {
msg: 'list message'
})
})
ps:此外在views中有index.art和list.art文件4.1 app.locals对象
- 将变量设置到app.locals对象下面,这个数据在所有的模板中都可以获取到。
- 在渲染页面的过程中
- render(‘模板文件名’,{插入到模板中的对象})
- 参数2中如果有多个模板中都含有相同的变量,可以将变量设置在app.locals对象中,这样所有的模板引擎中都可以访问到
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17app.js文件
app.locals.users = [
{
name: '张三',
age: 12
},
{
name: '李四',
age: 12
}
]
index.art文件中
{{each users}}//uses直接调用的是js中的users
{{$value.name}}
{{$value.age}}
{{/each}}
- 执行结果
- 实践总结
- response对象可以采用链式编程
node+express+MongoDB实现博客系统实践总结
- 模板中(.art文件中)的相对路径是相对于地址栏中的请求路径,由于请求路径一直在变,
- 所以要将模板中的相对路径改为绝对路径(/).即:定位到服务器端public文件(静态资源加载设置的路径)里面的:/admin/lib/jquery/dist/jquery.min.js(.art文件中加载资源路径设置为绝对路径)
1
2设置静态资源文件加载
app.use(express.static(path.join(__dirname, 'public'))); - 模板中的外链静态资源路径必须是绝对路径,是有浏览器进行解析的
- 所以要将模板中的相对路径改为绝对路径(/).即:定位到服务器端public文件(静态资源加载设置的路径)里面的:/admin/lib/jquery/dist/jquery.min.js(.art文件中加载资源路径设置为绝对路径)
- 模板中的外链资源是有模板引擎进行解析的,不用设置绝对路径(子模板的相对路径相对的就是当前模板)
- 模板优化:
- 对公共部分进行抽取
- 对模板骨架进行抽取
- require()方法在导入模块的同时会执行文件
- serializeArray()方法:获取post请求发送的信息,返回值是一个数组,数组里面是一个对象
1 | [{name: 'email', value: '用户输入的内容'}] |
浏览器可以禁用javascript的执行,所以在服务器端需要对表单进行在次验证(在谷歌浏览器的设置中可以禁用js)
location对象:首部指定的是需要将页面重新定向至的地址,返回的location对象可以重定向页面跳转的位置location对象
返回的对象,如果键值对的键和值相同可以省略值
express中重定向跳转到对应的页面采用
1
res.redirect('/admin/user')
将用户名存储在请求对象中req.session.username = user.username;
app.locals可以存储全局变量供所有的模板使用,注意:req.app.locals就是app.js中的app.locals
1
2
3
4/*
登录成功存储用户信息到app.locals下,供全局访问,这里的req.app.locals就是app.js中的app
*/
req.app.locals.userInfo = user;登录权限设置,登录成功进入用户admin页面,反之跳入登录界面
1
2
3
4
5
6
7
8
9
10
11
12// 拦截admin请求,根据是否登录
app.use('/admin', (req, res, next) => {
// 判断用户访问的是否是登录页面
// 判断用户的登录状态
// 如果用户是登录的,放行
// 如果用户不是登录的,重定向到登录页面
if (req.url != '/login' && !req.session.username) {
res.redirect('/admin/login');
} else {
next();
}
});对代码进行优化可以将中间件中的回调函数单独封装在middleware文件中并将该函数暴露出来,在使用的地方直接引入即可
代码优化,将admin的路由文件中的中间件回调函数放在route文件夹下的admin的文件中,单独封装成js模块,注意用到的模块和请求路径的修改
退出功能的实现
删除session:session存在服务器,删除req.session.destroy(callback),
- session的配置(在app.js中设置的)
1
2
3
4
5
6
7
8
9app.use(session({
secret: 'keyboard cat',//名称随便写
resave: false,
saveUninitialized: true,//退出后session未初始化的是否保存,退出时不保存,改为false
cookie: {
secure: true ,
cookie: {
//设置cookie的过期时间
maxAge: 24 * 60 * 60 * 1000}}}))
- session的配置(在app.js中设置的)
删除cookie:cookie存储在客户端,删除cookie中存储的sessionid,断开客户端和服务器端的联系
- clearCookie(session的id)查看sessionis如图所示
1
res.clearCookie('connect.sid');
- clearCookie(session的id)
重定向到登录页面
1
res.redirect('/admin/login');
数据库中的集合对象方法find()和findOne()
- find()方法返回的是一个数组
- findOne()方法返回的是一个对象,存在返回该对象,不存在返回null
res.render()方法不仅向服务器端发送了请求,而且渲染了页面,后面的程序的将不再执行,所以在前面加return跳出本次操作,后面不能再写res.send()/end()方法,会报错
ps:注意在js中的错误请求中间件一定要放在所有路由的最后才能获取到错误请求
异步API返回的promise对象一定要添加异步函数,async,await
JSON Formatter插件的安装可以在页面中查看json格式的文件
服务器端环境部署
- 建立项目所需文件夹
public 静态资源
model 数据库操作
route 路由
views 模板 - 初始化项目描述文件
npm init -y - 下载项目所需第三方模块
npm install express mongoose art-template express-art-template - 创建网站服务器
- 构建模块化路由
- 构建博客管理页面模板
1 加密算法bcrypt
1.1 概念
哈希加密是单程加密:1234->abcd
在加密的密码中加入随机字符串可以增加密码被破解的难度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23//哈希加密
// 导入bcrypt
const bcrypt = require('bcryptjs');
// 注意加密算法要放在异步函数中执行
async function run() {
// 生成随机字符串,增加密码被破解的难度
/*
genSalt(num)
num的值越大,生成的随机字符串越复杂,默认值是10
返回生成的随机字符串
*/
let salt = await bcrypt.genSalt(10);
console.log(salt);
// 使用随机字符串对密码进行加密
/*
hash('要加密的明文',随机字符串)
返回值:加密之后的面膜
*/
// 这里的hash()和genSalt()都是异步API,需要通过异步函数进行解决
let pass = await bcrypt.hash('1234', salt);
console.log(pass);
}
run();执行结果
密码比对
1
2// 密码比对
let isEqual = await bcrypt.compare('明文密码', '加密密码');1
2
3
4
5/*
bcrypt.compare('明文密码','加密密码')
返回值是true/false,注意:加密算法中的三个API都是异步API,通过异步函数解决返回值的获取问题
*/
const isValid = await bcrypt.compare(password, user.password)1.2 依赖模块
密码加密bcrypt需要的环境和模块
哈希加密中涉及到的三个函数
- bcrypt.genSalt(10)
- bcrypt.hash(‘1234’, salt)
- bcrypt.compare(‘明文密码’, ‘加密密码’)
- 这三个API都是异步API,采用异步函数的方式解决返回值的获取问题,在所在的函数添加async关键字,在返回值中添加await关键字
加密中的导入模块bcrypt导入有问题,解决方案如下详情见此链接
1
2
3
4安装
npm i bcryptjs -s
导入
var bcrypt = require('bcryptjs')2 cookie与session
cookie:是浏览器在电脑硬盘中开辟的一块空间,主要供服务器端存储数据,浏览器
cookie中的数据是以域名的形式进行区分
cookie中的数据是有过期时间的,超过时间数据会被浏览器自动删除
cookie中的数据会随着请求被自动发送到服务器端
cookie首次访问服务器是没有cookie的,服务器首次响应之后会为客户端存储cookie发送给客户端,客户端再次访问服务器端时,cookie的数据会随着请求发送给服务器端
session:实际上就是对象,存储在服务器端的内存中,在session对象中也可以存储多条数据,每一条数据都有一个session作为唯一的标识
cookie和session都是用来存储数据的,cookie在客户端,session在服务器端
通过cookie和session来实现登录功能如下图所示
需要安装第三方模块express-session,返回的是一个session对象,直接调用其方法就能返回session的对象
1
npm install express-session
1
2
3
4
5
6// session功能是实现
const session = require('express-session');
//通过中间件拦截所有请求并配置设置session,'里面的内容可以随便写'
app.use(session({ secret: 'secret key' }));//设置session
保存在req请求对象中
req.session.username = user.username;session中修改之后必须重新启动服务器并且重新进行登录才会在session中存储
3 Joi(jS验证)
JavaScript对象的规则描述语言和验证器
需要安装第三方模块joi
1
npm install joi
用于对表单提交的信息在js层面进行验证
joi的使用,注意这里的方法名都是小写的
- string():类型
- alphanum():字母或者数字
- min(3),max(30):最大值和最小值
- required():是不是必须
- valid(‘值1’,’值2’)//当前字段可填的字段名称
- error(new Error(‘错误信息’))抛出错误异常,注意error()方法中传入的是一个new Error()对象,获取当前传入的字段信息通过error.message获取
- regex(/^[a-zA-Z0-9]{3,30}$/):当前字段需要满足正则表达式
- [Joi.string(), Joi.number()]:表示当前字段的可选类型
- email():表示当前字段需要满足email的验证规则
- Joi.validate(待验证的对象, 验证规则);
- 这里返回的是promise对象,可以通过异步函数解决返回值问题
1
2
3
4
5
6
7
8
9const Joi = require('joi');
const schema = {
username: Joi.string().alphanum().min(3).max(30).required().error(new Error(‘错误信息’)),
password: Joi.string().regex(/^[a-zA-Z0-9]{3,30}$/),
access_token: [Joi.string(), Joi.number()],
birthyear: Joi.number().integer().min(1900).max(2013),
email: Joi.string().email()
};
Joi.validate({ username: 'abc', birthyear: 1994 }, schema);
- 这里返回的是promise对象,可以通过异步函数解决返回值问题
注意这里的validate返回的是一个promise对象,捕获错误有两种方式
- 1.通过异步函数的try catch来捕获
- 2.通过异步函数的then catch方法来捕获错误的信息
信息验证一般是在表单提交过程中用到信息验证
4 数据分页
数据分页
获取文档的总数:
- db.collection.countDocuments(查询条件,options)
- 查询条件为一个对象,{}为空时表示查询所有的文档
- 注意这里的API都是异步API,返回值都是promise对象通过异步函数(async/await)
- db.collection.countDocuments(查询条件,options)
添加查询的方法
- limit(num),查询返回的文档条数
- skip(num),跳过num个文档开始查询
- 获取当前页的开始位置
1
当前页的开始位置 = (当前页-1)*每页显示的文档数page
在数据分页中需要将页面的信息在渲染的res.render()的第二个参数中发送给模板字符串
在模板字符串中运算符号特别是+号可能不会做隐氏转换,而是直接拼接字符串,这是需要通过给数变量-0,来做隐氏转换在进行加法运算
- 在第一页和最后一页是隐藏上一页和下一页的按钮
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<ul class="pagination">
<li style="display: <%=page-1<1?'none':'inline'%>;">
<a href="/admin/user?page=<%=page-1%>">
<span>«</span>
</a>
</li>
<% for(var i=1;i<=total;i++){%>
<li><a href="/admin/user?page=<%=i%>"><%=i%></a></li>
<%}%>
<li style="display: <%=page-0+1>total?'none':'inline'%>;">
<a href="/admin/user?page=<%=page-0+1%>">
<span>»</span>
</a>
</li>
</ul>5 用户修改
- 在第一页和最后一页是隐藏上一页和下一页的按钮
修改用户操作通过传过去的参数中是否还有值,当时修改操作是通过get方式将参数携带过去
- 注意添加用户和修改用户是在统一个页面中进行,将二者进行分开判断操作
- 如果是修改操作则要传入查询得到的user信息显示在模板中,这里的在模板中添加value值时必须先要进行判断user是否存在
1
user&&user.username;条件1满足才看条件2
赋值过程中如果要设置默认值可以采用如下
1
let page = req.query.page||1;//如果没有传入page参数则设置为1,条件2不满足看条件2
req.body返回的是表单中的参数
req.query返回的是浏览器中传过来的参数
用户修改操作中不许验证密码正确才能进行修改操作
将错误处理中间件的重定向中多个参数的拼接通过for in将数据通过浏览器传递给客户端页面
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15app.use((error, req, res, next) => {
// 接收next()传过来的错误信息,并将其进行解析为对象的形式,这里的error就是next()函数红传递过来的信息
const result = JSON.parse(error);
// 接收的参数的数量是不确定的,这里通过循环来获取对应的参数
let params = [];
// 这里通过for in进行接收
for (let attr in result) {
if (attr != 'path') {
params.push(attr + '=' + result[attr]);//将对象中的值取出来放在数组中并通过&符号进行连接
}
}
res.redirect(`${result.path}?${
params.join('&')
}`);
})6 用户删除
在删除的对话框中要添加隐藏域来存储删除用户的id,这里弹出的删除对话框是一个模态框,提交的就是form表单
在删除按钮中提交自定义属性
1
2<i class="glyphicon glyphicon-remove" data-toggle="modal" data-target=".confirm-modal" data-in="{{@ $value._id}}"></i>
<!-- 这里通过自定义属性data-in="{{@ $value._id}}将当前id值传递给模态框中的删除hidden 中的id值 -->1
2
3
4
5
6
7在js中通过jquery将id号传给隐藏域
$('.delete').on('click', function () {
// 获取当前用户的id
var id = $(this).attr('data-in');
// 将获取到的当前id赋值给隐藏域中的value属性
$('#deleteUserId').val(id);
})7.文章编辑
mongoose的规则设定中可以通过数组的形式返回错误信息
1
required:[true,'该字段是必填字段']
涉及到文件上传功能,需要将表单的属性设置为enctype=”multipart/form-data”,才能将表单的编码转为二进制的形式进行文件的传
表单中传递过来的普通数据可以通过req.body进行获取,对于传过来的二进制数据不能接收,采用第三方模块formidable
8 formidable(同步api)
作用:解析表单,支持get请求参数,post请求参数、文件上传。
1
2
3
4
5
6
7
8
9
10
11
12
13// 引入formidable模块
const formidable = require('formidable');
// 创建表单解析对象
const form = new formidable.IncomingForm();
// 设置文件上传路径
form.uploadDir = "/my/dir";
// 是否保留表单上传文件的扩展名
form.keepExtensions = false;
// 对表单进行解析
form.parse(req, (err, fields, files) => {
// fields 存储普通请求参数
// files 存储上传的文件信息
});通过数组的split(‘public)方法截取public之后的文件路径保存在文件的服务器端:
1
files.cover.path.split('public')[1]
在获取用户名id的时候直接通过app.locals.userInfo中设置的模板全局变量来获取当前用户的id
对于上传的文件如何在页面中进行显示,还需要使用内置对象中FileReader
9 FileReader (异步API,通过onload()响应函数获取返回值)
通过内置对象中的FileReader来显示当前页面中上传的图片,
FileReader 既可以解析二进制文件也能解析普通文件
解析之后的文件时一个二进制文件,在页面中显示的时候可以直接在href中输入解析后的二进制文件也可以直接是”xx.图片文件后缀名”
使用
1
2
3
4
5
6
7var reader = new FileReader();
reader.readAsDataURL('文件');
<!-- 文件读取结束后会调用onload()方法 这里的reader()API是一个异步的API,所有需要通过响应函数获取返回值,这里通过监听事件的方式获取读取结果-->
reader.onload = function () {
<!-- 返回reader中的属性result的值就是文件读取的结果 -->
console.log(reader.result);
}reader.result属性返回的结果就是文件读取的二进制结果,在进行图片显示的过程中在src中添加普通文本的xxx.图片文件格式和文件读取的二进制结果reader.result的图片显示效果是一样
reader.result返回的二进制格式的文件如下图所示显示图片文件的操作是在html页面中操作的
文件中选择多个文件的属性是multiple
在dom操作中的对象有files属性可以获取选择的文件列表
1
dom对象.files
文件上传预览功能
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19// 实现文件上传预览功能
// 选择文件上传控件
var file = document.querySelector('#file');
//获取显示img的控件
var preview = document.querySelector('#preview');
// 当用户选择完之后显示图片,通过 onchange()响应函数
file.onchange = function () {
// 引入内置对象的模块
var reader = new FileReader();
// 返回用户选择的文件列表 dom对象.files 返回的是一个数组
// console.log(this.files);
//读取文件
reader.readAsDataURL(this.files[0]);//
// FileReader()是一个异步API,通过添加onload()响应函数获取异步API的返回值,onload()函数会自动执行.通过添加相应函数的方式返回值
reader.onload = function () {
// console.log(reader.result);
// 将文件读取的结果显示在文件中
preview.src = reader.result;
}10 数据分页实现2(借助第三方模块mongoose-sex-page
)
安装第三方模块
1
npm install mongoose-sex-page
使用
- 引入第三方模块,模块传入的参数是构造函数
- 调用
- page(num):当前显示的页码
- size(num):每一页显示的文档数
- display(num):页面中展示的页码数量
- exec():向数据库发送请求
- 返回值是一个对象,对象中的records属性值中存储的是查询到的articles数组
1
2const pagination = require('mongoose-sex-page');
pagination(集合构造函数).page(1) .size(20) .display(8) .exec();
返回值如图所示
实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 导入模块
const { Article } = require('../../model/artcle');
// 数据分页:引入第三方模块
const pagigation = require('mongoose-sex-page');
module.exports = async (req, res) => {
// 设置当前标识是哪个页面,
req.app.locals.currentLink = 'article';
// 获取当前客户端传过来的页码
let page = req.query.page;
// 查询数据库中的所有文章显示在当前列表页面,在显示用户名的时候涉及到关联查询,通过id查看对应的作者名称,author返回的是一个对象,里面是author对应的user对象实例
let articles = await pagigation(Article).find().page(page).size(2).display(3).populate("author").exec();
// res.send(articles);
res.render('admin/article', {
articles: articles
});
}11 mongoDB数据库添加账号
安装MongoDB时并没有为数据库添加账号,所有人都可以访问数据库,不利于数据库的安全,为数据库添加账户以后,只有指定权限的用户才能访问数据库的指定操作
- 以系统管理员的方式运行powershell
- 连接数据库 mongo
- 查看数据库 show dbs
- 切换到admin数据库 use admin
- 创建超级管理员账户 db.createUser()
- 切换到blog数据 use blog
- 创建普通账号 db.createUser()
- 卸载mongodb服务
1. 停止服务 net stop mongodb 2. mongod --remove
- 创建mongodb服务
mongod --logpath="C:\Program Files\MongoDB\Server\4.1\log\mongod.log" --dbpath="C:\Program Files\MongoDB\Server\4.1\data" --install –-auth
- 启动mongodb服务 net start mongodb
- 在项目中使用账号连接数据库(使用blog数据库中创建的账号和密码才能访问数据库)
mongoose.connect('mongodb://user:pass@localhost:port/database')
此时可以正常访问数据库
12开发环境和生产环境
环境,就是指项目运行的地方,当项目处于开发阶段,项目运行在开发人员的电脑上,项目所处的环境就是开发环境。当项目开发完成以后,要将项目放到真实的网站服务器电脑中运行,项目所处的环境就是生产环境。
区分不同环境的原因
- 因为在不同的环境中,项目的配置是不一样的,需要在项目代码中判断当前项目运行的环境,根据不同的环境应用不同的项目配置。
如何区分
- 通过电脑操作系统中的系统环境变量区分当前是开发环境还是生产环境。
- 通过电脑操作系统中的系统环境变量区分当前是开发环境还是生产环境。
获取系统信息通过process,是global下的对象可以直接使用,process.env返回的是一个对象,如图所示,process.env.NODE_ENV来获取是否开发环境的值
1
2
3
4
5if (process.env.NODE_ENV == 'development') {
// 开发环境
} else {
// 生产环境
}13 第三方模块morgan:将客户端请求的信息输出到服务器端的控制台
安装npm install morgan
1
2
3
4
5
6
7
8if (process.env.NODE_ENV == 'development') {
// 开发环境
// 将客户端发送给服务器端的请求打印在控制台上,参数值是固定的
app.use(morgan('dev'));
} else {
// 项目环境
console.log('项目环境')
}输出结果
此时我们可以根据不同的开发环境去做不同的事情
14 第三方模块config(自动配置不同的运行环境)
作用:允许开发人员将不同运行环境下的应用配置信息抽离到单独的文件中,模块内部自动判断当前应用的运行环境,
并读取对应的配置信息,极大提供应用配置信息的维护成本,避免了当运行环境重复的多次切换时,手动到项目代码中修改配置信息使用步骤
- 使用npm install config命令下载模块
- 在项目的根目录下新建config文件夹
- 在config文件夹下面新建
- default.json:存放默认的信息
- development.json:存放开发环境中的配置信息
- production.json文件:存放生产环境中的配置信息
- 在项目中通过require方法,将模块进行导入
- 使用模块内部提供的get方法获取配置信息
1
2
3
4
5json文件,三个文件中的字符串内容不一致
{
"title": "博客系统"
}1
2
3app.js文件
console.log(config.get("title"))
json文件中存储的是一个对象
config返回的是一个对象,通过对象调用get(属性名)方法获取json文件中的内容,判断当前环境
config查找流程:
- 先判断是当前是哪个开发环境,在在对应的文件查找响应的配置信息,如果对应的配置信息不存在,就会访问default文件
将开发环境中的数据库信息进行抽离配置在development中
将敏感配置信息存储在环境变量中
- 在config文件夹中建立custom-environment-variables.json文件,文件名一定不能写错
- 配置项属性的值填写系统环境变量的名字
- 项目运行时config模块查找系统环境变量,并读取其值作为当前配置项属于的值
1
2
3
4
5
6custom-environment-variables.json
{
"db": {
"pwd": "APP_PWD"
}
}1
2
3
4
5
6
7
8
9development.json
{
"db": {
"user": "SparkParis",
"host": "localhost",
"port": "27017",
"name": "blog"
}
}1
2
3
4
5connnect.js文件
const config = require('config');
console.log(config.get('db.host'));
mongoose.connect(`mongodb://${config.get("db.user")}:${config.get("db.pwd")}@${config.get("db.host")}:${config.get("db.port")}/${config.get("db.name")}`, { useUnifiedTopology: true })
模板字符串中只显示文章的部分内容substr()方法获取指定位置的字符
1
{{@$value.content.substr(0,150)+'...'}}
15 文章评论
文章评论是单独建立的集合,只有用户登录的情况下才能评论
集合关联中需要注意
- ref后面的值是创建集合时定义的集合名称,即model方法中的第一个第一个参数名称就是结合名称
1
2
3
4
5
6
7
8const User = mongoose.model('User', userSchema);
文章集合规则
author: {
// 集合关联,这里的作者就是User集合中的id
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: [true, '请输入作者'],
}, - 在集合关联的查询过程中查询的属性名就是定义规则时的属性名,比如面文章集合规则代码的author属性名称,返回的author是一个对象,通过:对象.username获取author的名称
- ref后面的值是创建集合时定义的集合名称,即model方法中的第一个第一个参数名称就是结合名称
需要修改login.js中的不同角色用户的权限设置,在一开始的时候就进行拦截
1
2
3
4
5
6
7// 对用户的角色进行判断,如果是admin管理员跳转到用户管理页面,如果是普通用户跳转到博客首页
if (user.role == 'admin') {
// 重定向到用户列表页面
res.redirect('/admin/user');
} else {
res.redirect('/home/');
}需要将role的值保存在req.session中,用于在登录成功之后获取role的值来设置权限普通用户不能访问用户的管理操作,loginGuard
1
2
3
4
5// 判断用户角色,如果是普通用户尝试访问用户管理页面直接跳到博客首页,并终止程序继续向下还行
if (req.session.role == 'normal') {
res.redirect('/home/');
return;
}实践知识小结
第三方模块dataformat
- 导入dataformat第三方模块处理模板中的日期,这个的第三方模块的使用是在art-template模板下使用的,同时需要配置模板,将第三方模块导入到模板中
1
2
3const dataFormat = require('dataformat');
// 模板设置,导入第三方模块到模板中才能使用
template.defaults.imports.dataFormat = dataFormat;
- 导入dataformat第三方模块处理模板中的日期,这个的第三方模块的使用是在art-template模板下使用的,同时需要配置模板,将第三方模块导入到模板中
本文链接: https://sparkparis.github.io/2020/04/08/Node-%E7%AC%94%E8%AE%B05Express%E6%A1%86%E6%9E%B6/
版权声明: 本作品采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 进行许可。转载请注明出处!