Node.js 国产 MVC 框架 ThinkJS 开发 入门

ThinkJS (官网 https://thinkjs.org) 是一款非常优秀的 Node.js 国产 MVC 开发框架,下面摘自官网的介绍:

ThinkJS 是一款使用 ES6/7 特性全新开发的 Node.js MVC 框架,使用 ES7 中 async/await,或者 ES6 中的 */yield 特性彻底解决了 Node.js 中异步嵌套的问题。同时吸收了国内外众多框架的设计理念和思想,让开发 Node.js 项目更加简单、高效。

使用 ES6/7 特性来开发项目可以大大提高开发效率,是趋势所在。并且新版的 Node.js 对 ES6 特性也有了较好的支持,即使有些特性还没有支持,也可以借助 Babel 编译来支持。

目前 ThinkJS 版本发布到了 v2.2.19 (更新日志),并且 v3.0beta 版本已经进入公测阶段。

本系列教程以 v2.x 版本为例进行介绍,教程以实际操作为主,ThinkJS 的安装详见官网文档说明。

原创:荆秀网 网页即时推送 https://xxuyou.com | 转载请注明出处
链接:https://blog.xxuyou.com/nodejs-thinkjs-study-start/

官网提供了创建一个空项目,以及空项目的代码结构介绍,这里不再赘述。以下是一些官网没有提到的小技巧,先做个了解~

新项目

为了确保用户错误操作导致现有文件被覆盖,thinkjs new 命令仅适用于文件夹不存在的,或者空文件夹。否则会收到类似如下的错误提示:

path `/data/www/demo` is already a thinkjs project.

实现这一特性其实是依赖一个项目根目录下的隐藏文件 .thinkjsrc ,使用 ls -a 可以查看隐藏文件,打开这个文件可以看到如下内容:

{
  "createAt": "2017-02-12 19:08:38",
  "mode": "module",
  "es": true
}

按模块分解

创建项目之后,基本的代码框架已经建立起来了(详见 https://thinkjs.org/zh-cn/doc/2.2/app_structure.html),其中默认的 homecommon 肯定是无法满足要求的。我们需要给自己的项目建立起相关的层次结构。

以下是几个较为典型的项目模块结构参考。

简单网站

例如官方网站、博客、社区等,这类系统结构较为简单,通常一个前端一个后端管理即可满足要求。

模块结构如下:

src/
src/common/  # 通用模块,放置主配置参数、boostrap adapter middleware service 等相关组件
src/home/  # 前端默认模块
src/backend/  # 后端管理模块
src/util/  # 系统工具类

电商平台

电商平台系统主要考虑到入驻的商户、注册的客户、管理人员、运营人员等使用人群,还需要考虑到较大的功能模块切分(如果足够大到类似京东、天猫那种体量的系统,则需要进行数据、功能、服务、位置等角度的分割)。

模块结构如下:

src/
src/common/
src/home/
src/sso/  # 单点登录、令牌管理等
src/rest/  # 针对Wap、App等多客户端的 rest api
src/goods/  # 商品管理及服务
src/storage/  # 库存管理及服务
src/cart/  # 购物车
src/order/  # 订单
src/delivery/  # 快递
src/pay/  # 在线支付、空中支付
src/member/  #
src/coupon/  # 电子券
src/promotion/  # 促销
src/points/  # 积分
src/merchant/  # 入驻商户
src/shop/  # 商户门店
src/finance/  # 财务核算及款项清算
src/stat/
src/log/
src/monitor/
src/util/
src/task/
src/message/  # 消息队列

即时消息平台

实时推送平台不仅仅要处理 WebSocket 连接和消息囤积发送,还要处理多用户购买相应服务套餐、统计连接数、统计下行流量、进行连接鉴权等,Firebase野狗荆秀即时推送 等都是这类的服务商。

模块结构如下:

src/
src/common/
src/home/
src/rest/
src/storage/
src/websocket/  # ws 或者 wss 服务
src/webhook/  # 钩子服务
src/middleware/  # 搭载中间件运行
src/pay/
src/member/
src/stat/
src/log/
src/monitor/
src/util/
src/message/  # 消息队列

代驾、租车运营平台

代驾、租车、共享单车等平台需要解决的是资源(客户、车、目的地)定位、路线计算、实时定点跟踪、GPS围栏监测等技术问题,然后才是商务行为的电子化处理。

模块结构如下:

src/
src/common/
src/home/
src/rest/
src/map/  # 地图资源、路线计算、电子围栏运算
src/storage/
src/websocket/  # 实时消息传递
src/pay/
src/member/
src/driver/
src/assets/  # 资源费效评估
src/stat/
src/log/
src/math/  # 计算服务
src/monitor/
src/util/

在线教育、直播平台

在线教育或直播平台通常具备实时音视频上传、转码、存储、广播等硬性要求,因此系统除了管理相关课件、学生、教师、选课等,还要负责处理相关媒体文件。

模块结构如下:

src/
src/common/
src/home/
src/rest/
src/sso/  # 单点登录、令牌管理等
src/media/  # 课件、音视频等媒体文件
src/bulk/  # 流媒体
src/process/  # 编解码处理
src/storage/
src/live/  # 直播
src/pay/
src/student/
src/teacher/
src/schedule/
src/stat/
src/log/
src/monitor/
src/util/
src/task/
src/message/  # 消息队列

注:以上结构为示例,并不具备通用性,研发时应当需要根据实际情况进行设计或调整。

项目模式

项目模式其实官网有介绍,但是太过简略,基本上是一笔带过。看看如下两种创建 thinkjs 项目的方式:

# 用 mode 参数创建一个简单结构项目
thinkjs new thinkjs_normal --mode=normal

# 无 mode 参数创建一个较为复杂结构的项目
thinkjs new thinkjs_module

两者创建的项目结构区别其实很简单:

  • normal mode 的结构直接把 config controller logic model 提到 src 下面,因此适用于一个简单的网站系统。
  • module mode 则建立了基本的 homecommon 两个应用模块,显然是为了较为复杂、多人协作的中大型项目进行的逻辑分割。

thinkjs_normal 项目结构

thinkjs_normal/package.json
thinkjs_normal/.babelrc
thinkjs_normal/.thinkjsrc
thinkjs_normal/nginx.conf
thinkjs_normal/pm2.json
thinkjs_normal/.gitignore
thinkjs_normal/README.md
thinkjs_normal/www
thinkjs_normal/www/development.js
thinkjs_normal/www/production.js
thinkjs_normal/www/testing.js
thinkjs_normal/www/README.md
thinkjs_normal/www/static
thinkjs_normal/www/static/js
thinkjs_normal/www/static/css
thinkjs_normal/www/static/img
thinkjs_normal/src
thinkjs_normal/src/bootstrap
thinkjs_normal/src/bootstrap/middleware.js
thinkjs_normal/src/bootstrap/global.js
thinkjs_normal/src/config
thinkjs_normal/src/config/config.js
thinkjs_normal/src/config/view.js
thinkjs_normal/src/config/db.js
thinkjs_normal/src/config/hook.js
thinkjs_normal/src/config/session.js
thinkjs_normal/src/config/error.js
thinkjs_normal/src/config/env
thinkjs_normal/src/config/env/development.js
thinkjs_normal/src/config/env/testing.js
thinkjs_normal/src/config/env/production.js
thinkjs_normal/src/config/locale
thinkjs_normal/src/config/locale/en.js
thinkjs_normal/src/controller
thinkjs_normal/src/controller/error.js
thinkjs_normal/view
thinkjs_normal/view/error_400.html
thinkjs_normal/view/error_403.html
thinkjs_normal/view/error_404.html
thinkjs_normal/view/error_500.html
thinkjs_normal/view/error_503.html
thinkjs_normal/src/controller/base.js
thinkjs_normal/src/controller/index.js
thinkjs_normal/src/logic
thinkjs_normal/src/logic/index.js
thinkjs_normal/src/model
thinkjs_normal/src/model/index.js
thinkjs_normal/view/index_index.html

thinkjs_module 项目结构

thinkjs_module/package.json
thinkjs_module/.babelrc
thinkjs_module/.thinkjsrc
thinkjs_module/nginx.conf
thinkjs_module/pm2.json
thinkjs_module/.gitignore
thinkjs_module/README.md
thinkjs_module/www
thinkjs_module/www/development.js
thinkjs_module/www/production.js
thinkjs_module/www/testing.js
thinkjs_module/www/README.md
thinkjs_module/www/static
thinkjs_module/www/static/js
thinkjs_module/www/static/css
thinkjs_module/www/static/img
thinkjs_module/src
thinkjs_module/src/common/bootstrap
thinkjs_module/src/common/bootstrap/middleware.js
thinkjs_module/src/common/bootstrap/global.js
thinkjs_module/src/common/config
thinkjs_module/src/common/config/config.js
thinkjs_module/src/common/config/view.js
thinkjs_module/src/common/config/db.js
thinkjs_module/src/common/config/hook.js
thinkjs_module/src/common/config/session.js
thinkjs_module/src/common/config/error.js
thinkjs_module/src/common/config/env
thinkjs_module/src/common/config/env/development.js
thinkjs_module/src/common/config/env/testing.js
thinkjs_module/src/common/config/env/production.js
thinkjs_module/src/common/config/locale
thinkjs_module/src/common/config/locale/en.js
thinkjs_module/src/common/controller
thinkjs_module/src/common/controller/error.js
thinkjs_module/view/common
thinkjs_module/view/common/error_400.html
thinkjs_module/view/common/error_403.html
thinkjs_module/view/common/error_404.html
thinkjs_module/view/common/error_500.html
thinkjs_module/view/common/error_503.html
thinkjs_module/src/home/config
thinkjs_module/src/home/config/config.js
thinkjs_module/src/home/controller
thinkjs_module/src/home/controller/base.js
thinkjs_module/src/home/controller/index.js
thinkjs_module/src/home/logic
thinkjs_module/src/home/logic/index.js
thinkjs_module/src/home/model
thinkjs_module/src/home/model/index.js
thinkjs_module/view/home
thinkjs_module/view/home/index_index.html

配置参数

官网是这么描述配置文件加载顺序的:

框架默认的配置 -> 项目模式下框架配置 -> 项目公共配置 -> 项目模式下的公共配置 -> 模块下的配置

先问个问题:这五个配置都指的是哪里呢?

前两个可以忽略掉,那是 thinkjs 框架自身的配置设置,通常里面不会有我们项目会用到的配置参数。

第三个和第四个则是在不同的项目创建模式下的默认 config 配置文件夹,位置在:

# normal mode
thinkjs_normal/src/config/*
# module mode
thinkjs_module/src/common/config/*

最后一个是指的在 module mode 下的项目,每个 module 自己的 config,位置在:

thinkjs_module/src/home/config/*

明白了多个地方多个配置文件的玩法之后,你可以创建多个 module,并给每个 module 配置自身独特的配置参数。

需要注意的是:thinkjs 加载配置文件是有顺序的!回到本节一开始提到的加载顺序,你应当明白:多个配置文件最终会在 thinkjs 运行时被全部加载,并合并在一起(注意加粗文字)。

所以当存在多个配置文件时,需要注意配置参数的 key(即属性名)尽量不要重复,因为按照加载顺序,后加载的 key 的值会覆盖先加载的 key 的值,导致出现不希望的结果。

举例来说,有两个配置文件 src/common/config/assets.jssrc/home/config/assets.js,其中内容是:

// src/common/config/assets.js
export default {
  "site_title": "my site"
};

// src/home/config/assets.js
export default {
  "site_title": "my test"
};

// src/home/controller/index.js
let assets = this.config('assets');
let siteTitle = assets['site_title'];
console.log('siteTitle is: ', siteTitle); // my test

注:当然也有办法获取到 "my site" 这个值,后面的教程会提到方法。

公共方法

thinkjs 提供了一个 src/common/bootstrap/global.js 文件来放置全局变量/函数。

// 可以直接在 global.js 中增加自己定义的函数
global.formatDate = obj => {
  //...
}

如果你的自定义函数很多,想分门别类的组织,也可以在 src/common/bootstrap/ 文件夹下新增文件来定义函数。当项目启动时,该目录下的文件会自动加载,无需手动 require。

src/common/bootstrap/global_datetime.js
src/common/bootstrap/global_math.js
src/common/bootstrap/global_string.js

注:thinkjs v3.x 可能会取消 bootstrap,想要升级的朋友们注意了。 ThinkJS v3.x 设计图

Babel 编译时删除注释

开发时的工作代码都在 src 下面,运行时才会编译到 app 下面成为运行脚本(经过 Babel 编译),如果不想自己写的各种注释也出现在 app 下面的代码中,可以修改项目目录下的一个隐藏文件 .babelrc 增加相应 comments 参数。

增加该参数后编译速度会比较慢,因为需要处理的代码内容多了~

{
  "presets": [
    ["es2015", {"loose": true}],
    "stage-1"
  ],
  "plugins": ["transform-runtime"],
  "sourceMaps": true,
  "comments": false  # <-- 就是这个参数
}

注:给喜欢在生产服务器就地编译的筒子们。

完善 package.json 字段

package 打包描述文件虽然会被 thinkjs 自动生成,不过如果能够手工维护这个文件,添加必要的属性,会让我们的项目工程更加规范、有序。

参考一下 thinkjs v2.x 源码的 package.json 文件:

{
  "name": "thinkjs",
  "description": "ThinkJS - Use full ES6/7 features to develop web applications, Support TypeScript",
  "version": "2.2.18",
  "author": {
    "name": "welefen",
    "email": "welefen@gmail.com"
  },
  "scripts": {
    "test": "npm run eslint && npm run test-cov",
    "test-cov": "istanbul cover ./node_modules/mocha/bin/_mocha -- -t 50000 --recursive  -R spec test/",
    "compile": "babel src/ --out-dir lib/",
    "watch-compile": "npm run compile -- --watch",
    "watch": "npm run watch-compile",
    "prepublish": "npm run compile",
    "eslint": "eslint src/"
  },
  "bin": {
    "thinkjs": "./bin/index.js"
  },
  "contributors": [
    {
      "name": "welefen",
      "email": "welefen@gmail.com"
    },
    {
      "name": "im-kulikov",
      "email": "im@kulikov.im"
    },
    {
      "name": "maxzhang",
      "email": "zhangdaiping@gmail.com"
    },
    {
      "name": "akira-cn",
      "email": "akira.cn@gmail.com"
    },
    {
      "name": "qgy18",
      "email": "quguangyu@gmail.com"
    }
  ],
  "main": "lib/index.js",
  "dependencies": {
    "ejs": "2.4.1",
    "multiparty": "4.1.2",
    "mime": "1.3.4",
    "mysql": "2.11.1",
    "thinkit": "4.10.0",
    "babel-runtime": "6.6.1",
    "bluebird": "3.3.5",
    "co": "4.6.0",
    "colors": "1.1.2",
    "validator": "4.2.0",
    "commander": "2.9.0"
  },
  "devDependencies": {
    "mocha": "1.20.1",
    "muk": "0.3.1",
    "istanbul": "0.4.0",
    "babel-cli": "^6.18.0",
    "babel-preset-es2015": "^6.18.0",
    "babel-preset-stage-1": "^6.16.0",
    "babel-plugin-transform-runtime": "^6.15.0",
    "babel-core": "^6.20.0",
    "babel-eslint": "^6.0.4",
    "eslint": "2.8.0",
    "typescript": "^2.1.6",
    "source-map": "0.5.3"
  },
  "keywords": [
    "thinkjs",
    "framework",
    "web",
    "rest",
    "restful",
    "router",
    "api",
    "es6",
    "es7",
    "async",
    "await",
    "yield",
    "websocket",
    "generator-function",
    "typescript"
  ],
  "repository": {
    "type": "git",
    "url": "https://github.com/thinkjs/thinkjs"
  },
  "engines": {
    "node": ">=0.12.0"
  },
  "license": "MIT",
  "readmeFilename": "README.md",
  "bugs": {
    "url": "https://github.com/thinkjs/thinkjs/issues"
  }
}

修改 pm2 日志位置

pm2 (官网 http://pm2.keymetrics.io)是一个优秀的 Node.js 进程管理器。

它的强大之处在于不仅可以作为 Node.js 项目的守护进程,还具备可配置化启动、分布式支持、内存监控、热重载(优雅重载)、支持数据统计、运行日志记录、实时运行监控、API 和脚本支持等强大的特性。(更多的特性,另文描述~)

thinkjs 推荐使用 pm2 来管理项目运行,并自动生成了 pm2 的配置文件 pm2.json

默认生成的 pm2 配置文件不含日志记录部分,如果不单独配置,pm2 的日志将会保存在安装目录中,查找起来很不方便(当然你可以使用 pm2 logs [id|name] 来直接查看某个进程的日志,不过有些时候还是需要直接查看日志文件的)。

我的做法是:在项目目录下建立 logs 文件夹,用来放置 pm2 以及其他(诸如 log4js 等等)日志,打开 pm2.json ,给 apps[0] 增加如下几行配置参数:

{
  "apps": [{
    "error_file"      : "/data/www/thinkjs_module/logs/pm2-err.log",
    "out_file"        : "/data/www/thinkjs_module/logs/pm2-out.log",
    "log_date_format" : "YYYY-MM-DD HH:mm:ss Z",
    "merge_logs"      : false
  }]
}
  • error_file pm2 捕捉到的致命错误记录在这里
  • out_file pm2 接收到的 console 输出记录在这里
  • log_date_format 日期和时间格式
  • merge_logs 是否给日志文件增加进程id的后缀

注:更详细的参数配置,可前往 pm2 官网查阅。

done~

下一篇:Node.js 国产 MVC 框架 ThinkJS 开发 config 篇(荆秀网)

原创:荆秀网 网页即时推送 https://xxuyou.com | 转载请注明出处
链接:https://blog.xxuyou.com/nodejs-thinkjs-study-start/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容