上一篇介绍了架构设计,这一篇,结合代码,讲讲怎么实现,和为啥这样实现
先上github:https://github.com/fshwc/hwc_…
index.js
index.js是入口文件,在这个入口文件主要需要做什么呢?
1、启动服务
2、定义各种中间件
3、初始化路由
上面都是一个简单服务必须的,还有一些跟系统稳定相关的,例如combo、pm2等模块,这些就先不实现了。而一个好的模块化和强阅读性,不同功能的写在不同的模块里,index.js里就引用就好了,不要全部写在一个js里。
下面是简化后的index.js。
const express = require('express')
const app = express()
const middleware = [
{path: './middleware/logger', name: 'logger'},
{path: './middleware/router', name: 'router'},
]
middleware.forEach(function(m) {
middleware.__defineGetter__(m.name, function() {
return require(m.path)
})
})
app.use('*', function(req, res, next) {
middleware.logger(req) //各种中间件挂载到req上
next()
})
app.use(middleware.router()); //初始化路由
app.all('*', function(req, res) {
res.json({error: {msg: 'no found'}})
})
app.listen('8000', () => {})
路由
express里的路由定义是app.get(url, cb)
、app.post(url, cb)
,利用这一个特点,我们把method、url、cb参数化,app[method](url, cb)
。
在router这个文件夹下,我们把各种路由通过一定的格式定义。首先这个是可以根据实际场景改变。现在假设是一个多页面系统。view下定义的路由都是要render一个页面的,apis下定义的都是ajax请求。
*路由定义在router路径下,执行方法定义在controller路径下。
module.exports = {
view: [
{
path:'/home',
controller: '/home',
view: '/home.html'
}
],
apis: [
{
path:'/getList',
method: 'get',
controller: '/getList',
role: ['perm1', 'perm2'],
desc: '拿数据列表',
}
]
}
这样做有个好处,看middleware/router.js,是怎么初始化路由的
我们可以在一个入口,做权限的校验。只要缺少权限就统一res.json(error),如果权限校验通过,则执行controller里的方法。还有一个潜在好处,如果执行方法想用generator方法,只在applyController里,兼容co
或await
。做到,只修改一个方法,就可全局生效。
const router = express.Router()
let apis = []
let dir = path.join(__dirname, '../router')
let list = fs.readdirSync(dir)
if(list) {
list.forEach(files => {
var file = require(path.join(__dirname, '../router/'+files))
if(file.apis) apis = apis.concat(file.apis)
})
}
function applyController(fnPath, req, res, ...args) {
var fn = require(path.join(__dirname, '../controller/'+fnPath))
if(fn) fn(req, res, ...args);
}
function checkPerm(perm, cb) => {
if(perm) {
//检查是否通过权限,通过才执行cb
cb(true)
//缺少权限
//cb(null)
}
}
if(apis && apis.length) {
apis.forEach(m => {
router[m.method](m.path, (req, res) => {
if(m.perm) {
checkPerm(m.perm, (hasPerm) => {
if(hasPerm) {
applyController(m.controller, req, res)
}else {
res.json({error: {msg: '缺少权限'}})
}
})
}else {
applyController(m.controller, req, res)
}
})
})
}
module.exports = function() {
return router
}
Class
ES6增加了class概念。我这里的class,主要是讲和有其他系统交互的,比如systemA是和账号相关的,systemB是和内容相关的。这两个系统的鉴权方式都是不同的。这时候我理解node更多扮演一个中间件的角色。
首先都继承一个service/base.js
//base.js
const request = require('request')
const log4js = require('../logger/logger')
module.exports = class Base {
constructor(id) {
this.id = id;
}
request(opts, cb) {
let infoLogger = log4js.getLogger(`${this.id}-info`)
let errorLogger = log4js.getLogger(`${this.id}-error`)
opts = this._requestFilter(opts)// 各个系统鉴权
let body = JSON.stringify(opts)
infoLogger.info(body)
/*request(opts, (err, res, body) => {
if(err) errorLogger.error(JSON.stringify(err))
else if(body && body.error) errorLogger.error(JSON.stringify(body.error))
else cb(err, body)
})*/
}
_requestFilter(opts) {
return JSON.parse(JSON.stringify(opts))
}
}
//systemA.js
const Base = require('./Base')
const conf = require('../conf/conf')
class systemA extends Base {
constructor() {
super('systemA')
}
_requestFilter(opts) {
opts.qs = opts.qs || {};
var key = conf.systemA_key;
var time = new Date().getTime()
var _sign = md5(key+time)
opts.qs._sign = sign;
opts.qs.ts = time
}
}
mudole.exports = systemA
_requestFilter
方法就是鉴权的方法。通过继承,如果子类存在同样命名的方法,会执行子类的方法。所以,如果我们要和systemA进行http请求,systemA.request(opts),就能自动加鉴权,还自动打点。而且,收拢一个入口还是有很多好处,万一有修改,只用改一个地方就全局通用。