返回介绍

Node.js 让后台开发像前端一样简单

发布于 2025-02-22 21:56:40 字数 10965 浏览 0 评论 0 收藏 0

题外话

最近一直在关注比特币社区的大事件,Mike Hearn 说比特币实验失败了,比特币交易价格应声大跌,币圈的朋友该如何站队,比特币的未来会如何,很多人又一次陷入迷茫。

我个人,反而更加坚定了信心。这件事充分说明,一个产品有它的生命周期,有它失败的风险,一项技术却永远前进在路上。无论产品消亡与否(当然,比特币不会那么轻易消亡),都会留下丰厚的技术遗产。

希望我的技术分享,能为这句话做个见证。

前言

上一篇文章,简单介绍了 Node.js,搭建了开发环境,并轻松完成了前端开发。本文,我们更进一步,看看如何实现更加复杂的业务逻辑,如何构建自己的 Api。

为什么要用后台?就我们这个统计分析项目( Sacdl 项目)而言,仅前台几个文件已经足够。但是,多数项目业务更加复杂,没有后台办不成事。

另外,前端处理能力有限,特别是 web 应用,前端代码越简单越好,对于性能和用户体验都有好处。反观我们的 Sacdl 项目,显然对于数据的整理更适合在后台处理。

再者,大家知道,Bitcoin 或其他竞争币的核心,通常会提供 Json 格式的 Api,我们只要在后台对这些 Api 进行操作,实现自己的业务逻辑,就能很轻松实现区块链浏览器(如:blockchain.info)、钱包、支付等基本应用。因此,直接学习如何处理第三方 Api,对于我们快速上手基于区块链开发应用,是有直接帮助的。

需求

明确要干什么,很重要。

  • 从后台读取 github.com 的 Api;
  • 处理读取的数据,并发送给前端;

很明显,我们需要重构前端代码。

开发

下文仍以 Sacdl 工程为例,引入 Express 框架,并以此为基础进行开发重构。

基于 Node.js 的开发框架很多,而 Express 是最基础、最出众的一个,很多其他的框架都是基于它构建的,比如严格模仿 ruby on rails 的 sails 框架等。

(1)安装 Express

cnpm install express --save

说明:安装 Node.js 模块时,如果指定了 --save 参数,那么此模块将被添加到 package.json 文件中的 dependencies 依赖列表中。以后,就可以通过 npm install 命令自动安装依赖列表中所列出的所有模块。

(2)创建简单应用

进入工程目录,新建文件 app.js ,输入如下内容:

var express = require('express');
var app = express();

app.get('/', function (req, res) {
  res.send('Hello World!');
});

var server = app.listen(3000, function () {
  var host = server.address().address;
  var port = server.address().port;

  console.log('Example app listening at http://%s:%s', host, port);
});

然后,运行下面的命令:

$ node app.js

最后,用浏览器打开 http://localhost:3000/ ,可以看到 hello world! 输出。

这官网的例子,是一个完整的 web 应用。也可以理解为一个服务器软件,不过是仅仅在 3000 端口,提供了一个简单的 web 服务。

如果,你对上篇 gulp 的管道概念有了一定认识,你也可以想象成,我们已经搭建了一条从后台到前端的管道。剩下的工作,就是给这条管道添加各种处理装置,让水流(数据流)实现我们的要求。关于流的概念,我们会在下一篇再次总结一下。

(3)使用模板引擎

上面,我们直接将 hello world 发送给了浏览器,如果是 html 文件该怎么做呢?Node.js 没有直接渲染模板的功能,需要用到第三方插件,如:jade,ejs,hbs 等

这里,咱们用 ejs ,它就像 java 的 jsp,rails 的 rhtml,直接在 html 文件里嵌入代码,简单好用。下面,安装它:

$ cnpm install ejs --save

然后,用 app.set 设置我们的第一个管道 过滤器 ,修改上述代码如下:

-------其他-------
app.set('views', './views')
app.set('view engine', 'ejs')

app.get('/', function (req, res) {
  res.render('index');
});
-------其他-------

我们新建 views 文件夹(把视图文件暂时放在这里),在 views 里新建 index.ejs,打开它,把 hello imfly! 拷贝过去。

重启服务器(ctrl + C 关闭,然后再用 node app 命令打开),刷新浏览器,看到变化,证明模板启用成功。

(4)使用静态文件服务

我们的前端,只有 public/index.html 文件。现在,将它的代码复制到 views/index.ejs 文件里,并修改 js,css 引用。重启,刷新,啊,一堆乱码,按下 F12 ,打开浏览器控制台,看到一堆 404 错误 ,我们的 js,css 文件都没有成功加载,怎么回事?

到目前为止,我们仅提供了 / 地址下的路由请求,其他任何地址,Node.js 都默认转向 404 错误 。怎么办?一个个添加吗,显然不是,这类静态文件,express 提供了简单的方法:

app.use(express.static('./public', {
    maxAge: '0', //no cache
    etag: true
}));

app.get('/', function (req, res) {
...

这是咱们使用的第二个管道 过滤器 ,上面的代码意思是,在 public 下的文件,包括 js,css,images,fonts 等都当作静态文件处理,根路径是 ./public ,请求地址就相对于 / ,比如:./public/js/app.js 文件,请求地址就是 http://localhost:3000/js/app.js

说明:这里有一个小问题,使用 bower 安装的前端第三方开发包,都在 bower_components 文件夹下,需要移到 public 文件夹里。同时需要添加一个.bowerrc 文件,告诉 bower 组件安装目录改变了,并修改 gulpfile.js 文件。当然也可以连同 bower.json 文件都拷贝到 public 文件夹里。

重启服务,刷新页面,我们看到了久违的页面。

serve-page

(5)后台请求 githubApi

在后台请求 github 也有很多方案,使用 Node.js 的 request 插件,就可以直接在后端请求 http 地址,相当于直接把前端代码拿到了后台。不过,这里有个更好的方案,github 提供了 Node.js 使用的开发包,我们可以直接用:

cnpm install github --save

官方地址: https://github.com/mikedeboer/node-github

为什么会想到这个方案?一个方法是,认真阅读官方文档,看它提供了什么资源;另一方法是,京城去 https://npmjs.com 上搜搜。通常,成熟的产品,一般都会提供现成的方案。

该组件集成了 githubApi 的几乎全部内容,当然包括搜索功能。下面,让我们试试,把下面的代码拷贝到 app.js

var GitHubApi = require("github");

//下面的代码放在 app.get('/', ...) 之前
app.get('/search', function(req, res){
        var msg = {
        q: 'bitcoin',
        sort: 'forks',
        order: 'desc',
        per_page: 100
    }

    github.search.repos(msg, function(err, data) { //这里必须用`回调`函数,不能使用 var data = ...的方式,下篇细说
        res.json(data); //输出 json 格式的数据
    })
})

在浏览器里请求 http://localhost:3000/search , 可以看到与请求 https://api.github.com/search/repositories?q=bitcoin&sort=forks&order=desc&per_page=100 一样的结果(样式可能不同)

http://localhost:3000/search 就是我们自己的 Api 服务,如果有自己的数据库或其他逻辑业务,数据很容易融合进去。

让前端请求这个 api,修改 public/js/app.js 34 行的代码,如下:

url = url || 'http://localhost:3000/search'; //默认请求的页面

在浏览器里请求 http://localhost:3000/ ,结果与原来相同。这样我们就简单实现了,后台处理数据,前台展示数据。

(6)模块化重构

到目前为止,我们都是在修改 app.js ,不断往里面添加各种管道 过滤器 。如果业务复杂,这个文件会非常大。事实上,任何一个 Node.js 应用,都可以压缩成这样一个 js 文件。这就能轻松理解,为什么多数 js 框架都习惯带着 js 后缀了吧,因为它本身就是一个 js 文件。

但,这样不适合开发和维护。我们需要把它分解成一个个独立的文件,通过名字就能分辨它的用处,通过名字就能直接用它,这就是 Node.js 的模块化开发。

Node.js 的模块化非常简单。记住这样一个简单的逻辑关系:通常一个 module.exports 可以定义一个模块;一个文件只包含一个模块;只要是模块就可以使用 require() 方法在其他地方引用。样式与我们的前端代码非常相似,如下:

//文件 /path/to/moduleName.js

//局部变量
var a = ''

//公共方法(直接导出模块)
var moduleName = {} 或 function(){} //总之就是一个对象

//私有方法
function fun1(){}

//导出模块
module.exports = moduleName;

在其他文件中,我们可以这样用:

var moduleName = require('/path/to/moduleName'); //默认 js 后缀,习惯不用带

//然后,直接调用 moduleName 的各对象或方法就是了

按这个方式,我们把 app.js 文件,拆分成典型的 MVC 的开发样式,比如,熟悉 ruby on rails 的朋友,都习惯把代码分开保存在 controllers,models 和 views 文件夹里,以及 router 文件,这里,我们仿效之。视图已经定义在了 views 文件夹下,下面我们重点拆分其他的。

a)拆分模型

模型 model 专门处理数据,无论是数据库,还是请求远程 api 资源,都应该是它的事。自然,我们可以把 githubApi 的请求独立出来,这么做:

新建文件夹和文件 app/models/repo.js ,剪切粘贴下面的代码

var GitHubApi = require("github");

/**
 * from https://www.npmjs.com/package/github
 * @type {GitHubApi}
 */
var github = new GitHubApi({
    // required 
    version: "3.0.0",
    // optional 
    debug: false,
    protocol: "https",
    host: "api.github.com", // should be api.github.com for GitHub 
    pathPrefix: "", // for some GHEs; none for GitHub 
    timeout: 5000,
    headers: {
        "user-agent": "My-Cool-GitHub-App" // GitHub is happy with a unique user agent 
    }
});

var Repo = {
    search: function(msg, callback) {
        var msg = msg || {
            q: 'bitcoin',
            sort: 'forks',
            order: 'desc',
            per_page: 100
        }

        github.search.repos(msg, callback);
    }
}

module.exports = Repo;

说明:模型 Model 是资源的集合,就像数据库里的一张表,名字自然用资源类的名词表示最好。对数据的增删改查都在模型里,自然在前端 public/js/utils.js 里的部分代码就应该转移到这里,从而直接输出 treemap 的数据格式,例如:

// from /public/js/utils.js
function treeData(data) {
    var languages = {};

    var result = {
        "name": "languages",
        "children": []
    }

...

b)拆分控制器

控制器负责从模型请求数据,并把数据发送到前端,是前端和后台的 调度员 。这里, app.get 方法里的匿名函数便是,我们分别把他们抽取出来,放在 app/controllers/repos.js 里,并把请求 githubApi 的代码用模型代替,代码如下:

var Repo = require('../models/repo');

var Repos = {
    //get '/'
    index: function(req, res) {
        res.render('index');
    },

    //get '/search'
    search: function(req, res) {
        Repo.search(req.query.query, function(err, data) {
            res.json(data);
        })
    }
}

module.exports = Repos;

说明:按照惯例,控制器的名称,通常是对应模型的名称(名词)的复数;行为的名称,通常是动作(动词),因此 repos.index 就表示版本库列表, repos.search 就是搜索版本库信息,在 app.js 中,自然这样调用:

----其他代码----
var repos = require('./app/controllers/repos');

app.get('/search',  repos.search);
app.get('/',  repos.index);

----其他代码----

这部分代码,起到分发路由的作用,一看便知应该在路由里,于是继续重构。

c)拆分路由

新建文件 app/router.js , 把上面的代码剪切过来,修改为:

var repos = require('./controllers/repos');

var Router = function(app) {
    app.get('/search', repos.search);
    app.get('/', repos.index);
}

module.exports = Router;

app.js 里,简单调用:

var router = require('./app/router');

router(app);

以后,再添加其他的任何路由,只要修改 router.js 就是了。

d)整理视图

把 views 整体移动到 app/views 下,并修改 app.js 代码,让模板引擎指向该文件夹

app.set('views', './app/views')

然后,新建 app/views/repos 文件夹,将 views/index.ejs 文件移入。

说明:视图 view 是界面元素,通常是类 html 文件,按照惯例,它的文件夹与控制器同名 repos,各文件名与控制器的行为 action 同名,如 index -> index.ejs

当然,视图根据模板引擎的特点,也可以进行模块化处理,进一步细化为 layout.ejs, header.ejs 等,方便重复使用。限于篇幅,不再罗嗦。详情,请看源码。

经过这样的模块化整理,我们轻松实现了一个简单的 MVC 框架(如图),它的易用性、扩展性得到很大提升。我们已经可以快速添加更多的功能了,比如:像巴比特那样显示主流交易市场信息(下篇)。

sacdl-mvc

(7)测试(略)

无测试不产品。不过咱们还是省了吧,不然,又要很长的篇幅。本项目仅作文章辅助,不能作为产品使用。如果非要用,建议您写写测试,不然后果自负。

说到这的时候,本人就有一个问题没有解决:我的办公室有代理,通过 Node.js 请求 api.github.com 是不成功的,但在家里就可以。这个问题,就留给高手去解决吧。

我们的方向是文章,每个细节都想完美,最终的结果就会不完美。很多程序员在某个时期,会不自觉的陷入技术细节,而忽略很多重要的东西。最严重的是,很多人始终都没有拿出过一个完整的产品,像样的更不用说了。“使劲看就是盲,太专注就是愚”,值得深思。

(8)部署(略)

自己找个服务器,折腾折腾吧。

总结

本文涉及的代码非常简单,但在严格控制字数的情况下,仍然罗嗦了这么多。所以,很多时候,语言的力量非常苍白,说这么多干嘛,做就是了。

文中,对输入框的处理没有说明,请自己查看源码(写到这个时候,其实还没有做)。模块化部分,其实已经有一个叫 express-generator 的插件,可以一键生成,所以很多优秀的资源,自己去探索吧。不过窃以为,该说的重点基本提到。

总体来说,使用 Node.js,从前端到后台并不复杂,或者说非常简单。前后语言的统一,给我们减少了很多思维转换的麻烦。如果你能完整实践这两篇文章,我个人认为,Node.js 应该可以入门了。

但是,要想看明白一些复杂的代码,还需要掌握 Node.js 的一些独特习惯,比如回调,比如对异常的处理等等,请看下一篇: 《Node.js 开发加密货币》之四:您必须知道的几个 Node.js 编码习惯 ,仍以 sacdl 项目为例,添加展示主流交易市场信息等功能,目标是简单介绍一些大家经常遇到的坑,以便在接下来的代码分析中更加轻松。

链接

项目源码: https://github.com/imfly/sacdl-project

试用地址: https://imfly.github.io/sacdl-project (前端)

本文源地址: https://github.com/imfly/bitcoin-on-nodejs

电子书阅读: http://bitcoin-on-nodejs.ebookchain.org/

参考

Expressjs 官网: http://expressjs.com/

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文