使用 Koa2 开发小结

RSS 订阅器项目是我最近花时间比较多的一个项目了. 在这个项目中我使用了大量的新技术, 很多技术都是我第一次使用. 后端是基于 Koa2 和 Mongoose 的 RESTful API.

在这个项目开展前, 我已经有半年多没接触后端了. 上一次后端还是用 PHP 以及 Laravel 框架开发的 LNMP 架构. 在动工前, 我也没有正式的使用过 Node 以及其部署, 对于 Koa2 的 async await 的异步书写方式也只是久仰大名而已.

这篇博客主要想说一说自己在使用 Node.js 和 Koa2 开发后端过程中的一些总结和收获.

中间件

Koa2 本身是一个非常轻的框架, 我们需要使用大量的中间件去完善它, 例如 koa-bodyparser , koa-etag, koa-router, koa-sslify 等等.

同时, 肯定免不了自己写中间件, 例如我自己就写了 7 个中间件, 分别是处理缓存, 处理 cookies, 强制 www, 配合前端 HTML5Mode, 错误处理, UA 判断, JWT 和 XSRF 处理.

1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = function () {
return async(ctx, next) => {
if (/^\/(mark|square|feed|feeds|post|posts|me|search)/.test(ctx.request.url)) {
if (ctx.mobile) {
await send(ctx, './public/index.html')
}
else {
await send(ctx, './public/pc.html')
}
}
await next()
}
}

上述就是配合前端 HTML5Mode 以及根据 UA 指向不同入口文件的中间件.

阅读更多

使用 ESLint 规范你的代码

在一些多人参与的项目开发中, 每个人代码习惯不同, 对于缩进, 有人习惯 TAB, 有人习惯两个空格, 有人习惯四个空格. 对于分号, 有人不加, 有人习惯加, 而习惯加分号的人又会经常漏加分号. 试想一个项目代码如果由各种群魔乱舞的风格组成, 这必然会带来一定的视觉障碍(强迫症的人看了受不了=.=), 并且显得代码质量低下.

即使是个人, 偶尔也会写出一些不太规范的代码, 比如变量声明了未使用, 比如使用了 == 等等, 更不要说各种代码格式如操作符左右空格, if ... else 的格式, 末行换行等等. 特别是像有强迫症的我, 有时候写的过程可能会不小心遗漏或写错, 但发现的时候又会去改过来. 如果能在书写过程中检查出不规范的地方提示岂不是最好?

ESLint 是一个插件化的 JavaScript 代码检查工具, 可以用于检查常见的 JavaScript 代码错误, 规范 JavaScript 代码的书写, 可以有效控制代码的质量.

eslint.jpg

ESLint 规则

ESLint 使用非常简单, 安装好 ESLint 后, 可以自定义一套规则文件或者使用开源的代码风格规范, 像 Google, Airbnb 都有开源的 JavaScript 代码书写规范. 可以直接使用它们的, 也可以自己配置.

具体的用法可以参考官方文档, 其实蛮简单的.

这里列出了 ESLint 的各种规则, 可以参考下这里的说明.

以下是我个人配置的规则

阅读更多

拥抱 vue 和 vuex

最近一段时间写了两个玩意, 一个是基于 PEG.js 的 XML Parser, 前面有一条博客说了 PEG.js 这东西, 事后自己也模仿着写出了这个 XML 解析器, 感觉并不难, 写着玩玩而已.

另外, 最近花时间特别多的另一件事就是写了一个 RSS 订阅器. 一开始写这个订阅器, 心想上一个项目代码不忍直视, 感觉自己需要写一些能拿出手的代码, 加上学校课程刚好要求做一些东西, 以及自己最近迷上了使用 RSS 订阅器这个东西(这么多理由=.=), 于是就自己动工开搞.

目前订阅器已经基本完工了, RSS 订阅器的网址是 www.enjoyrss.com, 项目开源在 Github 上面, 对于对 RSS 有兴趣或者想学习 Vue2, Vuex 或 Angular1 的人可能会有一些帮助. 额对了, 还有就是后端用的是 Koa2, 前后端鉴权专门在上一篇博客提了下, 想了解 Koa2 的人也可以看下.

数据流动问题

其实每次做一个新的东西的时候, 我都会尽量尝试去使用各种新的技术和用法. 这样才能学到更多的东西. 在这个 RSS 订阅器中, 一开始我只是把自己认为的各种 Angular 最佳实践在项目中都运用了下, 想写出能够体现自己 Angular 水平的代码, 为此前面还写了一篇博客说一些我认为的哪些算是最佳实践. 但其实, 对 Angular 使用已经相当熟悉的我, 并没有在这一次中收获什么新的知识. 要说有, 大概就是前面那博客提到的一些 Angular 最新版本的一些新特性例如 Component 之类的吧, 然而自己并没有花时间去看.

在这个项目中, 另外的一点感受就是 Angular 的跨组件通信难题. 确切的说我觉得这个项目并不适合使用 Angular 来写. 例如

website.png

左边有一个订阅源栏, 它的未读数量要相应右侧的点击文章, 标记全部已读等事件. 它的订阅源列表也要对右侧的订阅和取消订阅事件做出相应. 为了缩减频繁的跨组件通信, 我将下方状态栏直接拆分成三条, 由各自的组件提供其状态栏覆盖原默认只有背景色的状态栏. 但跨组件通信仍然存在, 单单左侧面板就存在着五个事件监听.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$scope.$on('EXPAND', () => vm.expand = !vm.expand)
$scope.$on('FOLD', () => vm.expand = false)
$scope.$on('ADD_FEED', (event, data) => {
if (vm.feeds.default) {
vm.feeds.default.push(data)
} else {
vm.feeds['default'] = [data]
}
})
$scope.$on('DELETE_FEED', (event, data) => {
vm.feeds = _.mapObject(vm.feeds, feeds => feeds = _.filter(feeds, feed => feed.feed_id !== data.feed_id))
})
$scope.$on('READ_POST', (event, data) => {
vm.feeds = _.mapObject(vm.feeds, feeds => _.each(feeds, feed => feed.feed_id === data ? feed.unread-- : ''))
})

我需要监听折叠事件, 这个动作在其他组件被触发. 需要监听添加订阅源和取消订阅源事件, 并修改订阅源列表. 需要监听已读事件, 并对相应订阅源的未读文章数做减1操作…

在有些应用场景, 存在着需要大量父子组件通信, 兄弟组件通信, 以及没有父子和兄弟关系的组件之间的通信的行为, 这种时候, Angular 虽然也能解决, 但是不得不说 Angular 这种频繁的跨组件通信很容易产生问题, 特别是当一个组件可以被多个组件修改的时候.

阅读更多

关于前后端分离鉴权的思考

前后端分离项目的 Token 存储问题由来已久,有的人存 Cookie 有的人存 LocalStorage 或 SessionStorage,最近刚把 RSS 订阅器项目的鉴权问题做好,感觉算是目前比较稳妥安全的方案了,分享一下经验。

前端用的是 Angular,后端用的是 Koa,登录注册界面由 Koa 负责,由 Koa 根据用户是否登录来决定首页跳转。我不会一开始就讲我的做法,而是循序渐进的从传统的存储方式逐渐过渡到我的做法当中来。

如何安全的传输用户 token

这是最传统也是最简单的方式了,前端登录,后端根据用户信息生成一个 token,并保存这个 token 和对应的用户 id,接着把 token 传给用户,存入浏览器 cookie,之后浏览器请求带上这个 cookie,后端根据这个 cookie 来标识用户。

flow-cookie-session

但这样做问题就很多,如果我们的页面出现了 XSS 漏洞,由于 cookie 可以被 JavaScript 读取,XSS 漏洞会导致用户 token 泄露,而作为后端识别用户的标识,cookie 的泄露意味着用户信息不再安全。

尽管我们通过转义输出内容,使用 CDN 等可以尽量避免 XSS 注入,但谁也不能保证在大型的项目中不会出现这个问题。另外,后端每次都需要根据 token 查出用户 id,这就增加了数据库的查询和存储开销。

在设置 cookie 的时候,其实你还可以设置 httpOnly 以及 secure 项。设置 httpOnly 后 cookie 将不能被 JS 读取,浏览器会自动的把它加在请求的 header 当中,设置 secure 的话,cookie 就只允许通过 HTTPS 传输。

secure 选项可以过滤掉一些使用 HTTP 协议的 XSS 注入,但并不能完全阻止。

httpOnly 选项使得 JS 不能读取到 cookie,那么 XSS 注入的问题也基本不用担心了。但设置 httpOnly 就带来了另一个问题,就是很容易的被 XSRF,即跨站请求伪造。当你浏览器开着这个页面的时候,另一个页面可以很容易的跨站请求这个页面的内容。因为 cookie 默认被发了出去。

CSRF

看起来我们不能兼顾。确实,光依靠这一个 token 我们没办法兼顾这两点。既然一个不够,那就两个。于是有了 XSRF-TOKEN,它和作为用户令牌的 token 类似,也是服务器生成的一个散列值。我们把 token 通过 httpOnly 发回去,把 XSRF-TOKEN 直接发回去。我们可以无视 httpOnly 的 cookie 因为我们没法操纵它,但对于这个 XSRF-TOKEN,我们就可以在我们网站的每个请求中都加入到 header 里面去。而服务端就需要检查这个 header 的 XSRF-TOKEN 是否真实有效。

阅读更多