目錄
管理思路
管理实例
自动生成前缀
直接挂载到根路径
装饰器实现路由管理
思路整理
准备工作
注册函数编写
装饰器编写
编写路由文件
挂载效果
总结
首頁 web前端 js教程 express路由管理的幾種自動化方法分享

express路由管理的幾種自動化方法分享

Feb 23, 2018 pm 01:43 PM
express 幾種 自動化

我们平时在使用express写代码的过程中,会根据类别,将路由分为多个不同的文件,然后在项目的入口文件(例如app.js)中将其依次挂载,例如:

const index = require('./routes/index')
const user = require('./routes/user')
// ...其他路由文件

app.use('/', index)
app.use('/user', user)
// ...挂载其他路由
登入後複製

但是当路由文件过多时,这样写会多出很多重复性的代码,而且当我添加一个新的路由模块时,除了编写路由文件本身,还需要到app.js入口文件中将新路由文件挂载上去,不够灵活,因此,我们需要想一些办法来管理我们的路由,使其能够自动化,免除频繁修改入口文件的操作。

管理思路

我们的项目目录主要是这样的:

├─routes
  ├─index.js
  ├─user.js
  ├─sub
    ├─index.js
    ├─a.js
├─app.js
登入後複製

首先,我们来看一下,express的路由管理主要由三部分组成,路由方法(method)、路由路径(path)和路由处理器(handle),一般情况下,路由方法和路由处理器是由路由文件自己来管理,在一个路由文件中,我们经常使用这样的写法:

// routes/user.js
const express = require('express')
const router = express.Router()

// 路由的方法,处理器和部分路径
router.get('/', function (req, res, next) {
  res.send('respond with a resource')
})

module.exports = router
登入後複製

然后在入口文件中添加上共通的路由前缀:

app.use('/user', require('./routes/user'))
登入後複製

根据这种思路,我们主要处理的就是路由路径这个部分。在这个部分我们有两种处理方式,一种是根据路径和文件名自动生成路由的共通路径前缀,路由文件只编写剩余不共通部分的路径;还有一种则是路径完全由路由文件自己来管理,在挂载时直接挂载到根路径'/'上。

管理实例

自动生成前缀

我们通过扫描项目目录,可以将文件在项目中的路径转化为express的路由路径模式,自动生成路由前缀,例如路由文件routes/sub/a.js就会为转化成路由前缀/sub/a,路由文件a.js中只要编写/sub/a后面的路径部分即可。

项目目录为:

├─routes
  ├─index.js
  ├─user.js
  ├─sub
    ├─index.js
    ├─a.js
├─app.js
├─helper.js
登入後複製

主要的实现代码为:

// helper.js
const fs = require('fs')
const path = require('path')

/**
 * 将文件名修正为前缀
 *
 * @param {String} filename
 * @returns {String}
 */
function transform (filename) {
  return filename.slice(0, filename.lastIndexOf('.'))
    // 分隔符转换
    .replace(/\\/g, '/')
    // index去除
    .replace('/index', '/')
    // 路径头部/修正
    .replace(/^[/]*/, '/')
    // 路径尾部/去除
    .replace(/[/]*$/, '')
}

/**
 * 文件路径转模块名(去.js后缀)
 *
 * @param {any} rootDir 模块入口
 * @param {any} excludeFile 要排除的入口文件
 * @returns
 */
exports.scanDirModules = function scanDirModules (rootDir, excludeFile) {
  if (!excludeFile) {
    // 默认入口文件为目录下的 index.js
    excludeFile = path.join(rootDir, 'index.js')
  }
  // 模块集合
  const modules = {}
  // 获取目录下的第一级子文件为路由文件队列
  let filenames = fs.readdirSync(rootDir)
  while (filenames.length) {
    // 路由文件相对路径
    const relativeFilePath = filenames.shift()
    // 路由文件绝对路径
    const absFilePath = path.join(rootDir, relativeFilePath)
    // 排除入口文件
    if (absFilePath === excludeFile) {
      continue
    }
    if (fs.statSync(absFilePath).isDirectory()) {
      // 是文件夹的情况下,读取子目录文件,添加到路由文件队列中
      const subFiles = fs.readdirSync(absFilePath).map(v => path.join(absFilePath.replace(rootDir, ''), v))
      filenames = filenames.concat(subFiles)
    } else {
      // 是文件的情况下,将文件路径转化为路由前缀,添加路由前缀和路由模块到模块集合中
      const prefix = transform(relativeFilePath)
      modules[prefix] = require(absFilePath)
    }
  }
  return modules
}
登入後複製

然后,在路由目录的入口index文件下,加入这么一段代码(scanDirModules方法需要从之前编写的helper.js文件中引入):

const scanResult = scanDirModules(__dirname, __filename)
for (const prefix in scanResult) {
  if (scanResult.hasOwnProperty(prefix)) {
    router.use(prefix, scanResult[prefix])
  }
}
登入後複製

在app.js入口文件中只需要将所有路由相关代码改成一句:

app.use('/', require('./routes'))
登入後複製

这样就完成了路由前缀的自动生成和路由自动挂载了。

效果展示:

我们将routes/sub/a.js的内容定为:

// routes/sub/a.js
const express = require('express')
const router = express.Router()

router.get('/', function (req, res) {
  res.send('sub/a/')
})

module.exports = router
登入後複製

挂载效果:

express路由管理的幾種自動化方法分享

访问结果:

express路由管理的幾種自動化方法分享

这种自动生成前缀的方法,在路由目录层级不深时,可以起到很好的作用,但是当目录层级较多时,就会暴露出缺点:阅读代码时路由路径不明确,不能直观地看到完整路径,而且生成前缀的灵活性不高。

后者可以使用自定义导出对象和挂载方式的方法来解决,但是前者我暂时没有什么好的解决方法,因此我们来看一下之前提到的另一种自动化方法。

直接挂载到根路径

这种方法的扫描思路和前一种方法相似,不同之处在于,在编写路由文件的时候,我们需要写完整路由的路径,例如:

// routes/sub/a.js
const express = require('express')
const router = express.Router()

router.get('/sub/a', function (req, res) {
  res.send('sub/a/')
})

module.exports = router
登入後複製

扫描部分的代码修改为:

exports.scanDirModulesWithoutPrefix = function scanDirModulesWithoutPrefix (rootDir, excludeFile) {
  if (!excludeFile) {
    // 默认入口文件为目录下的 index.js
    excludeFile = path.join(rootDir, 'index.js')
  }
  const modules = []
  let filenames = fs.readdirSync(rootDir)
  while (filenames.length) {
    // 路由文件相对路径
    const relativeFilePath = filenames.shift()
    // 路由文件绝对路径
    const absFilePath = path.join(rootDir, relativeFilePath)
    // 排除入口文件
    if (absFilePath === excludeFile) {
      continue
    }
    if (fs.statSync(absFilePath).isDirectory()) {
      // 是文件夹的情况下,读取子目录文件,添加到路由文件队列中
      const subFiles = fs.readdirSync(absFilePath).map(v => path.join(absFilePath.replace(rootDir, ''), v))
      filenames = filenames.concat(subFiles)
    } else {
      // 是文件的情况下,将模块添加到模块数组中
      modules.push(require(absFilePath))
    }
  }
  return modules
}
登入後複製

路由入口文件修改为:

// 获取 routes 目录下所有路由模块,并挂载到一个路由上
const routeModules = scanDirModulesWithoutPrefix(__dirname, __filename)
routeModules.forEach(routeModule => {
  router.use(routeModule)
})
登入後複製

挂载效果:

express路由管理的幾種自動化方法分享

这种方法可以明确的看到路由的完整路径,在阅读代码时不会出现因为层级过深而导致出现阅读困难的情况,但是明显的缺点就是需要编写大量的路径相关代码,路径重用性又太低。

那么有没有一种方法,既能保证共通路径的重用性,又能保证代码的可阅读性呢?

有,我们可以用JavaScript的装饰器(Decorator)来进行路由的管理。

装饰器实现路由管理

装饰器的思路来自于Java的MVC框架Spring MVC,在Spring MVC中,路由的编写方式是这样的:

// 类上的 RequestMapping 注解用来设置共通的路径前缀
@Controller
@RequestMapping("/")
public class SampleController {

  // 方法上的 RequestMapping 注解用来设置剩余路径和路由方法
  @RequestMapping("/", method=RequestMethod.GET)
  public String index() {
    return "Hello World!";
  }

  // GetMapping 注解相当于已经指定了GET访问方法的 RequestMapping
  @GetMapping("/1")
  public String index1() {
    return "Hello World!1";
  }
}
登入後複製

在ES6之后,在js中编写类已经变得非常容易,我们也可以仿照 Spring MVC 的路由方式来管理express中的路由。

思路整理

关于JavaScript的装饰器,可以参考这两篇文章:

探寻 ECMAScript 中的装饰器 Decorator

JS 装饰器(Decorator)场景实战

在进行实现之前,我们先简单整理一下实现的思路。我的思路是,为了阅读方便,每一个路由文件包括一个类(Controller),每个类上有两种装饰器。

第一种装饰器是在类上添加的,用来将这个类下面的所有方法绑定到一个共通的路由前缀上;

而第二种装饰器则是添加到类中的方法上的,用来将方法绑定到一个指定的HTTP请求方法和路由路径上。

这两种装饰器也都接收剩余的参数,作为需要绑定的中间件。

除了编写装饰器本身之外,我们还需要一个注册函数,用来指定需要绑定的express对象和需要扫描的路由目录。

准备工作

为了使用装饰器这个特性,我们需要使用一些babel插件:

$ yarn add babel-register babel-preset-env babel-plugin-transform-decorators-legacy
登入後複製

编写.babelrc文件:

{
  "presets": [
    "env"
  ],
  "plugins": [
    "transform-decorators-legacy"
  ]
}
登入後複製

在app.js中注册babel-register

require('babel-register')
登入後複製

注册函数编写

注册函数的功能较为简单,因此我们先来编写注册函数:

let app = null

/**
 * 扫描并引入目录下的模块
 *
 * @private
 * @param {string} routesDir 路由目录
 */
function scanDirModules (routesDir) {
  if (!fs.existsSync(routesDir)) {
    return
  }
  let filenames = fs.readdirSync(routesDir)
  while (filenames.length) {
    // 路由文件相对路径
    const relativeFilePath = filenames.shift()
    // 路由文件绝对路径
    const absFilePath = path.join(routesDir, relativeFilePath)
    if (fs.statSync(absFilePath).isDirectory()) {
      // 是文件夹的情况下,读取子目录文件,添加到路由文件队列中
      const subFiles = fs.readdirSync(absFilePath).map(v => path.join(absFilePath.replace(routesDir, ''), v))
      filenames = filenames.concat(subFiles)
    } else {
      // require路由文件
      require(absFilePath)
    }
  }
}

/**
 * 注册express服务器
 *
 * @param {Object} options 注册选项
 * @param {express.Application} options.app express服务器对象
 * @param {string|Array<string>} options.routesDir 要扫描的路由目录
 */
function register (options) {
  app = options.app
  // 支持扫描多个路由目录
  const routesDirs = typeof options.routesDir === 'string' ? [options.routesDir] : options.routesDir
  routesDirs.forEach(dir => {
    scanDirModules(dir)
  })
}
登入後複製

通过获取express的app对象,将其注册到文件的顶级变量app,可以让其余的装饰器函数访问到app对象从而完成路由注册。

routesDir可以是字符串也可以是字符串的数组,代表了需要扫描的路由目录,将其转化为字符串数组后依次进行扫描。

scanDirModules方法与之前的扫描方法类似,只是这里只需要将路由文件require进来就行,不需要返回。

装饰器编写

装饰器部分分为两部分,装饰类的路由装饰器Router和其余装饰方法的请求处理装饰器(Get, Post, Put, Delete, All, Custom)。

在方法装饰器的编写上,由于装饰器的行为相似,因此我们可以编写一个抽象函数,用来生成不同HTTP请求方法的不同装饰器。

抽象函数的具体代码为:

/**
 * 生成对应HTTP请求方法的装饰器
 *
 * @param {string} httpMethod 请求方法
 * @param {string|RegExp} pattern 请求路径
 * @param {Array<Function>} middlewares 中间件数组
 * @returns {MethodDecorator}
 */
function generateMethodDecorator (httpMethod, pattern, middlewares) {
  return function (target, methodName, descriptor) {
    if (!target._routeMethods) {
      target._routeMethods = {}
    }
    // 为自定义方法生成对应的方法存储对象
    if (!target._routeMethods[httpMethod]) {
      target._routeMethods[httpMethod] = {}
    }
    target._routeMethods[httpMethod][pattern] = [...middlewares, target[methodName]]
    return descriptor
  }
}
登入後複製

这里的target表示类的原型对象,methodName则是需要装饰的类方法的名称,我们将类方法和它的前置中间件组成一个数组,存储到类原型对象上的_routeMethods属性中,以便类装饰器调用。

要生成一个HTTP请求方法的装饰器,只需要调用这个生成函数即可。

例如生成一个GET方法的装饰器,则只需要:

/**
 * GET 方法装饰器
 *
 * @param {string|RegExp} pattern 路由路径
 * @param {Array<Function>} middlewares 中间件数组
 * @returns {MethodDecorator}
 */
function Get (pattern, ...middlewares) {
  return generateMethodDecorator('get', pattern, middlewares)
}
登入後複製

路由装饰器(类装饰器)的代码为:

/**
 * Router 类装饰器,使用在 class 上,生成一个带有共通前缀和中间件的路由
 *
 * @param {string|RegExp} prefix 路由前缀
 * @param {express.RouterOptions} routerOption 路由选项
 * @param {Array<Function>} middlewares 中间件数组
 * @returns {ClassDecorator}
 */
function Router (prefix, routerOption, ...middlewares) {
  // 判断是否有路由选项,没有则当做中间件来使用
  if (typeof routerOption === 'function') {
    middlewares.unshift(routerOption)
    routerOption = undefined
  }

  /**
   * 为类生成一个 router,
   * 该装饰器会在所有方法装饰器执行完后才执行
   *
   * @param {Function} target 路由类对象
   */
  return function (target) {
    const router = express.Router(routerOption)
    const _routeMethods = target.prototype._routeMethods
    // 遍历挂载路由
    for (const method in _routeMethods) {
      if (_routeMethods.hasOwnProperty(method)) {
        const methods = _routeMethods[method]
        for (const path in methods) {
          if (methods.hasOwnProperty(path)) {
            router[method](path, ...methods[path])
          }
        }
      }
    }
    delete target.prototype._routeMethods
    app.use(prefix, ...middlewares, router)
  }
}
登入後複製

这里的target是类对象,当装饰器对类进行处理时,我们生成一个新的express路由对象,将放置在类对象原型上的_routeMethods属性进行遍历,获取到对应的路由方法、路由路径和路由处理函数,并挂载到这个路由对象上。

需要注意,类装饰器的处理会放在方法装饰器之后进行,因此我们不能直接在方法装饰器上进行挂载,需要将其存储起来,在类装饰器上完成挂载工作。

编写路由文件

我们的路由文件也需要进行大幅度的改动,将其转化为下面类似的形式:

// routes/sub/a.js
// Router 和 Get 装饰器从你的装饰器文件中引入
@Router('/sub/a')
class SubAController {
  @Get('/')
  index (req, res, next) {
    res.send('sub/a/')
  }
}

module.exports = SubAController
登入後複製

挂载效果

express路由管理的幾種自動化方法分享

用装饰器编写路由的相关代码我已经单独建立了一个github仓库,并发布成了一个npm包——express-derouter,欢迎各位star。

总结

以上就是我最近所思考的有关于express路由管理自动化的几种方法,其中装饰器挂载的方式由于js自身原因,在还原Spring MVC的其他功能上有所限制,如果你对更加强大的功能有要求的话,可以看看TypeScript基于express的一个MVC框架——nest,相信它应该更能满足你的需求。

相关推荐:

node.js开发-express路由与中间件的代码示例详解


以上是express路由管理的幾種自動化方法分享的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

如何解決C++開發中的程式碼冗餘問題 如何解決C++開發中的程式碼冗餘問題 Aug 22, 2023 pm 05:30 PM

如何解決C++開發中的程式碼冗餘問題程式碼冗餘是指在編寫程式時,出現了多個地方有相似或重複的程式碼。這種問題不僅使得程式碼難以維護和閱讀,還會增加程式碼量和複雜性。而對於C++開發者來說,解決程式碼冗餘問題尤其重要,因為C++是一種強大的程式語言,但也容易導致程式碼重複。程式碼冗餘問題的根源在於不合理的設計和編碼習慣。要解決這個問題,可以從以下幾個方面著手:使用函數和類別:C

理解SpringBoot和SpringMVC之間的差異及比較 理解SpringBoot和SpringMVC之間的差異及比較 Dec 29, 2023 am 09:20 AM

對比SpringBoot與SpringMVC,了解它們的差異隨著Java開發的不斷發展,Spring框架已經成為了許多開發人員和企業的首選。在Spring的生態系中,SpringBoot和SpringMVC是兩個非常重要的組件。雖然它們都是基於Spring框架的,但在功能和使用方式上卻有一些區別。本文將聚焦在SpringBoot與Sprin

PHP 持續整合中的 Jenkins:建置和部署自動化大師 PHP 持續整合中的 Jenkins:建置和部署自動化大師 Feb 19, 2024 pm 06:51 PM

在現代軟體開發中,持續整合(CI)已成為提高程式碼品質和開發效率的重要實踐。其中,jenkins是一個成熟且功能強大的開源CI工具,特別適用於PHP應用程式。以下內容將深入探討如何使用Jenkins實現php持續集成,並提供具體的範例程式碼和詳細的步驟。 Jenkins安裝和設定首先,需要在伺服器上安裝Jenkins。透過其官網下載並安裝最新版本即可。安裝完成後,需要進行一些基本配置,包括設定管理員帳戶、外掛程式安裝和作業配置。建立一個新作業在Jenkins儀表板上,點選"新作業"按鈕。選擇"Frees

蘋果快速指令自動化怎麼刪掉 蘋果快速指令自動化怎麼刪掉 Feb 20, 2024 pm 10:36 PM

蘋果快捷指令自動化怎麼刪掉隨著蘋果推出iOS13新系統,用戶可以利用快捷指令(AppleShortcuts)來自訂和自動化各種手機操作,大大提升了用戶的手機使用體驗。然而,有時候我們可能會需要刪除一些不再需要的快速指令。那麼,蘋果快捷指令自動化怎麼刪掉呢?方法一:透過快速指令應用刪除在iPhone或iPad上,開啟「快速指令」應用程式。在底部導覽列中選

深入比較Express和Laravel:如何選擇最佳框架? 深入比較Express和Laravel:如何選擇最佳框架? Mar 09, 2024 pm 01:33 PM

深入比較Express和Laravel:如何選擇最佳框架?在選擇一個適合自己專案的後端框架時,Express和Laravel無疑是兩個備受開發者歡迎的選擇。 Express是基於Node.js的輕量級框架,而Laravel則是基於PHP的流行框架。本文將深入比較這兩個框架的優缺點,並提供具體的程式碼範例,以幫助開發者選擇最適合自己需求的框架。效能和擴展性Expr

利用Python腳本在Linux平台下實現任務調度與自動化 利用Python腳本在Linux平台下實現任務調度與自動化 Oct 05, 2023 am 10:51 AM

利用Python腳本在Linux平台下實現任務排程與自動化在現代的資訊科技環境下,任務排程與自動化成為了大多數企業必備的工具。而Python作為一種簡單、易學且功能豐富的程式語言,在Linux平台下實現任務調度與自動化是非常方便且有效率的。 Python提供了多種用於任務調度的程式庫,其中最常用且功能強大的是crontab。 crontab是一個用於管理和調度系統

Express和Laravel的比較分析:選擇更適合你的框架 Express和Laravel的比較分析:選擇更適合你的框架 Mar 10, 2024 pm 10:15 PM

Express和Laravel是兩個非常受歡迎的Web框架,分別代表了JavaScript和PHP兩大開發語言的優秀框架。本文將針對這兩個架構進行比較分析,幫助開發者選擇更適合自己專案需求的框架。一、框架簡介Express是一個基於Node.js平台的Web應用程式框架,它提供了一系列強大的功能和工具,使開發者可以快速建立高效能的網路應用程式。 Express

機器人和人工智慧如何實現供應鏈的自動化 機器人和人工智慧如何實現供應鏈的自動化 Feb 05, 2024 pm 04:40 PM

自動化技術正在廣泛應用於不同產業,尤其在供應鏈領域。如今,它已成為供應鏈管理軟體的重要組成部分。未來,隨著自動化技術的進一步發展,整個供應鏈和供應鏈管理軟體都將發生重大變革。這將帶來更有效率的物流和庫存管理,提高生產和交付的速度和質量,進而促進企業的發展和競爭力。有遠見的供應鏈參與者已經準備好應對新形勢。資訊長應帶頭確保組織取得最佳結果,了解機器人技術、人工智慧和自動化在供應鏈中的作用至關重要。什麼是供應鏈自動化?供應鏈自動化是指利用技術手段減少或消除人類在供應鏈活動中的參與。它涵蓋了各種不同

See all articles