前言
承接上一篇文章,《前端升全栈之项目使用express重构项目(上篇)》,我们继续讲解下一篇的项目使用express重构项目完整的下一篇,好了,我们马上进入我们今天的正题!!!
1.express开发项目
express开发接口:
- 初始化接口,之前的部分代码可以复用;
- 开发路由,并可以实现登录功能;
- 记录日志;
- 初始化环境:
- 安装插件mysql xss;
- mysql control resModel 相关代码可以复用;
- 初始化路由;
这个时候我们就可以去把一些自己带有的不用的注释,安装一下我们的mysql和xss.新建一个db文件夹,新建一个mysql.js,将其中的内容拷贝过去,在新建一个conf文件夹,下面建一个db.js,也可以拷贝。这就是数据库先关的。在接下来拷贝controller、model文件夹过去,新建一个utils文件夹,下面建一个cry文件,也可以拷贝过去;至于具体的路由,这个也可以知己拷贝,当然list的登录模块可以展示注释,还没有实现登录,而且返回的话现在是使用res.json。这样基本就可以调用数据库了。
router.get('/list',function(req,res,next) { let author = req.query.author || '' const keyword = req.query.keyword || '' const result = getlist(author,keyword) return result.then(listdata) => { res.json( new SuccessModel(listdata)) } }
2.express处理session
登录:
- 使用express-session和connect-Redis,简单方便;
- req.session保存登录信息,登录校验做成express中间件
先安装 express-session ,让我们轻松实现 session 的功能,原理与之前相同,在 app.js 引入该插件。 使用的话也是和一般的中间件一样,直接 app.use ,只不过里 面需要写成函数,然后会返回一个中间件。该函数需要几个 参数:secret (即密匙)、 cookie (配置 path ,这里设置为 / 根目录,这样子到处都可以用,还有 httpOnly 设置为 true , 当然这两个都是默认配置,写不写一样的而 maxAge 直接传入一个时间段即可,相比之前的 expire 要简洁,这里写 24 小时)。
如此我们随意访问一个路由就可以自动设置 session ,我们还可以设置一个路由来测试,在这里可以通过 req.session 拿到session(主要是因为写注册 session 在注册路由之前!)
router.get(''/session-test),function(req,res,next) { const session = req.session if(session.viewNum == null) { session.viewNum = 0; } session.viewNum++ res.json({ viewNum:session.viewNum }) });
处理session
app.use(session({ secret:'ODST123!#', cookie:{ path:'/', httpOnly:true, maxAge:24*60*60*1000 } )
3.session链接redis
通过不同浏览器访问 session-test 确实可以看到不同的访问 数量说明有在区分用户了。 接下来实现登录,这个直接去拷贝就行。注意这里不需要再手动加到 redis ,在设置 session 的时候就已经自己加了。而且返回也记得改成 res.json 。 再定义一个测试是否已经登录的路由 login-test 。 在实现了登录功能后,我们需要连接 redis 了,很简单,先安装 redis 和 connect-redis 包。在 db 下面建一个 redis.js ,直接拷贝之前的 redis (到客户端那一部分就行)。 然后回到 app.js 引入这个库(是函数所以让它立即执行且传入 session )为 RedisStore 。 然后在 session 之前,引入 redisClient , new 一个 RedisStore 把其传进去就行作为配置。现在 session 就可以多一个配置
store ,传入 sessionStore 就行(之前没有写就是存在内存里,现在 redis 了)。
router.get('/login-test',function(req,res,next){ if(req.session.username) { res.json({ errno:0 msg:'已经登录' }) return } res.json({ errno:1, msg:'没有登录' }) })
到此呢,可以实现登录验证同时还可以通过 test 查看登录状态(即登录 or 未登录)。
4.登录中间件
我们查看 redis 可以看到已经储存了 session ,同时登录的话也会把一些公开的个人信息传进去。那么我们就能回到 blog 去处理登录权限(当然 check 还不能实现)接下来做一个登录校验,新建一个 middleware 文件夹,下面 建一个 loginCheck.js 文件,引入 ErrorModel ,输出一个中间 件,判断是否登录(其实就是刚才写的 test !)如果登录则调用 next ()然后 return ,如果没有登录返回错误 model 。 自此我们的登录就全部完成了。
问题:使用插件时提示某某插件的函数不存在,很简单,肯定是传入这个插件的参数出错了,这里是发现 redis 向外暴露函数的时候不是默认暴露,引用的话也是奇葩直接引入文 件,反正规范一下引入导出就可以了。
const redis = reqiure('redis') const { REDIS_CONF } = require('../conf/db') //创建一个客户端 const redisClient = redis.createClient(REDIS_CONF.portm,REDIS_CONF.host) redisClient.on('error',err=>{ console.log(err) }) module.exports = redisClient
到这里我们进入任意页面都会在 redis 触发缓存 session(只 不过没有登录的话里面没有个人信息),登录后会在 session添加个人信息,下一步有了 redis ,就可以解法 blog (一部分)
定义登录校验的中间件;
5.开发路由
修改 blogs ,虽然没有写 check 校验,但是我们可以直接通过 是否有 username 来校验,如果等于 null 直接就返回错误 model。(如此未登录访问管理员界面就会报错) 接下来写 detail 路由,直接拷贝, id 的话通过 req.query.id 拿 到,且记得 res.json 返回。 接下来写 new ,中间件写箭头函数还是普通的 function 都行 (但是要统一),这个需要引入之前写的登录中间件,直接就能在参数中用。 更新也要登录中间件也是直接用就行。删除操作也是如此。 像现在这种一个一个函数就很好拆分,不会说都是一个个 if 什么的,另外登录也写成了中间件放在外面,比较规范标准(其它地方也可以使用)。 总结一下就是换成 res.json 、需要登录校验的引入登录中间件 为参数、拿不到的参数(未全局定义)换成 req.query. 参数。
6.Morgan
关于自定义日志直接打印自然是不会记到文件,但是后面PM2 有办法记到文件;
- access log记录,直接使用脚手架推荐的Morgan;
- 指定以日志使用console.log和console.log就可以了;
- 日志文件拆分、日志内容分析,之前说过,就不说了
使用Morgan写日记
目前已经有日志了,只不过是直接打印在控制台且内容有点 少。这个还可以再加一个参数(对象),其中配置 stream: process.stdout(则会打印在控制台,默认参数,不加也一样, 也是用流来输出!)。 那么第一个参数是什么意思,我们可以访问 github 找到 predefined Formats 找到有关第一个参数的信息,有多种情况,简单来说就是规定了输出的内容/格式(不同情况输出不 同内容,有 dev、shot、tiny 等等),输出内容比较丰富完整的是 combined(线上用)。
既然有多种情况,那么是时候来设置另一个环境 prd 了(暂 时用 nodemon 代替,正式是用pm2)。 回到 app.js 添加一个 ENV 变量来判断当前环境,如果不是线上环境直接用之前的日志,打印到控制台就行。如果是线上环境就改成 combined,stream 也要改。我们建多一个 logs,里面建多一个 access.log,引入 fs 和 path,生成文件名、写入流,stream 就可以指向这个写入流就行。那么日志就会写 入到文件中。 所以这样子直接用就比较规范标准,也不需要考虑怎么写入 文件的。(懂原理且会用工具) 接下来就是自定义日志,直接在需要的任意地方打印任意信 息即可
7.中间件原理
express中间件原理:
- 回顾中间件使用;
- 分析如何实现;
- 代码演示
这个我们只是实现大体的中间件,就不一定源码就是这么实现。回顾中间件的话我们看之前写的 express-test 就行,那么怎么 实现,至少 use、listen、get、post、next 接口怎么去实现。 而且 use、get、post 要兼容只传入一个中间件的情况和传入一个中间件加路由和传入多个中间件加路由的情况。
分析
- app.use使用注册中间件,先收集起来;
- 遇到http请求,根据path和mothod判断触发哪一个;
- 实现next机制,机制,即上一个通过next触发下一个;
8.中间件原理-代码实现
新建一个 lib (库),建一个 express 文件夹,下面建一个 like-express.js,就在这里写中间件。
引入 http ,然后把数组的 slice 方法单独定义成一个常量。建一个 LikeExpress 类,然后返回这个类的实例(以函数的形式在里面返回类的实例,即工厂函数)。 类里面定义一个构造函数,定义一个存放中间件的列表(all、 get、 post ,这些都以数组形式),然后就可以定义 use 、 get 、
post 、 listen 四个函数。
由于其中三个函数都有类似的部分如注册中间件故抽离出来,只需要传入一个参数 path 。定义一个 info 空对象保存注册信息,判断参数是字符串否是就是路由放到 info ,不是就默认为/ 根路由,因为如果第一个参数不是路由也就是说没有写路由,其实也应该认为有路由且是根路由,只不过是被忽略了。
如果带路由的话我们切掉第一个参数并且利用 slice 把剩下参数变为数组,如果不带的话就直接从第一个参数开始转化为数组。最后返回 info 对象即可,也就是说 register 函数只是对传入的参进行处理,带路由的保存路由且剩下参数变为数组存入 stack ,如果不带路由保存根路由且全部参数变为数组存入 stack 。
接下来看 use ,利用 apply 把参数丢到 register 函数,再将得 到的传入列表, get 和 post 也是如此,只不过放到不同的数 组。
接下来写 listen ,它可以传多个参数,我们解构一下,调用createServer 方法,需传入回调函数(在外面定义),然后简化解构的参数放到 listen 里面就行了,也就是说我们用了 node.js 本身的 listen 方法而不是自己去写,这就很好。 写回调函数返回一个 res.json 函数,设置表头和返回数据, 接下来获取 url 和 method ,因为要区分哪些需要访问哪些不 需要访问,通过 match 函数(外面定义)分析(需要传入 url 和 method ),传给一个变量。 match 函数呢,定义一个 stack 数组来存储需要返回需要访问的路由什么的,如果是发送图标的,我们大可不管直接返回。 再定义一个获取具体请求类型的数组,其实我们只需要拿到全部的路由,再传入 method 让它们自己看明白谁是要获取就行了(就限制了 method 已经),之后就可以遍历该数组,
通过 path 来限制,将符合路径要求的丢到 stack 。回到回调函数,还需要再调用一个 handle 函数,传入 req 、 res 和 stack ,主要是控制 next 如何往下。首先里面调用一个 next函数然后立即执行,通过数组的 shift 拿到第一个中间件,如果存在则立刻执行中间件的函数,而且还要把 next 传进去实现套娃。