辅助函数参考

AdonisJS 将其实用程序打包进 helpers 模块,并使其对你的应用代码可用。由于这些实用程序已被框架安装并使用,helpers 模块不会为你的 node_modules 增加任何额外的体积。

辅助方法从以下模块导出。

import is from '@adonisjs/core/helpers/is'
import * as helpers from '@adonisjs/core/helpers'
import string from '@adonisjs/core/helpers/string'

escapeHTML

对字符串值中的 HTML 实体进行转义。底层我们使用 he 包。

import string from '@adonisjs/core/helpers/string'

string.escapeHTML('<p> foo © bar </p>')
// &lt;p&gt; foo © bar &lt;/p&gt;

可选地,你可以使用 encodeSymbols 选项对非 ASCII 符号进行编码。

import string from '@adonisjs/core/helpers/string'

string.escapeHTML('<p> foo © bar </p>', {
  encodeSymbols: true,
})
// &lt;p&gt; foo &#xA9; bar &lt;/p&gt;

encodeSymbols

你可以使用 encodeSymbols 辅助函数对字符串值中的非 ASCII 符号进行编码。底层我们使用 he.encode 方法。

import string from '@adonisjs/core/helpers/string'

string.encodeSymbols('foo © bar ≠ baz 𝌆 qux')
// 'foo &#xA9; bar &#x2260; baz &#x1D306; qux'

prettyHrTime

process.hrtime 方法的差值进行美观打印。

import { hrtime } from 'node:process'
import string from '@adonisjs/core/helpers/string'

const startTime = hrtime()
await someOperation()
const endTime = hrtime(startTime)

console.log(string.prettyHrTime(endTime))

isEmpty

检查字符串值是否为空。

import string from '@adonisjs/core/helpers/string'

string.isEmpty('') // true
string.isEmpty('      ') // true

truncate

在给定字符数处截断字符串。

import string from '@adonisjs/core/helpers/string'

string.truncate('This is a very long, maybe not that long title', 12)
// Output: This is a ve...

默认情况下,字符串恰好在给定索引处截断。不过,你可以指示该方法等待单词完成后再截断。

string.truncate('This is a very long, maybe not that long title', 12, {
  completeWords: true,
})
// Output: This is a very...

你可以使用 suffix 选项自定义后缀。

string.truncate('This is a very long, maybe not that long title', 12, {
  completeWords: true,
  suffix: '... <a href="/1"> Read more </a>',
})
// Output: This is a very... <a href="/1"> Read more </a>

excerpt

excerpt 方法与 truncate 方法相同。不过,它会从字符串中去除 HTML 标签。

import string from '@adonisjs/core/helpers/string'

string.excerpt('<p>This is a <strong>very long</strong>, maybe not that long title</p>', 12, {
  completeWords: true,
})
// Output: This is a very...

slug

为字符串值生成 slug。该方法从 slugify 包 导出;因此,请查阅其文档了解可用选项。

import string from '@adonisjs/core/helpers/string'

console.log(string.slug('hello ♥ world'))
// hello-love-world

你可以为 Unicode 值添加自定义替换,如下所示。

string.slug.extend({ '☢': 'radioactive' })

console.log(string.slug('unicode ♥ is ☢'))
// unicode-love-is-radioactive

interpolate

对字符串中的变量进行插值。变量必须位于双花括号内。

import string from '@adonisjs/core/helpers/string'

string.interpolate('hello {{ user.username }}', {
  user: {
    username: 'virk'
  }
})
// hello virk

花括号可以使用 \\ 前缀进行转义。

string.interpolate('hello \\{{ users.0 }}', {})
// hello {{ users.0 }}

plural

将单词转换为复数形式。该方法从 pluralize 包 导出。

import string from '@adonisjs/core/helpers/string'

string.plural('test')
// tests

isPlural

判断一个单词是否已经是复数形式。

import string from '@adonisjs/core/helpers/string'

string.isPlural('tests') // true

pluralize

此方法结合了 singularplural 方法,并根据数量选用其中之一。例如:

import string from '@adonisjs/core/helpers/string'

string.pluralize('box', 1) // box
string.pluralize('box', 2) // boxes
string.pluralize('box', 0) // boxes

string.pluralize('boxes', 1) // box
string.pluralize('boxes', 2) // boxes
string.pluralize('boxes', 0) // boxes

pluralize 属性导出了 额外的方法 ,用于注册自定义的不可数、不规则、复数和单数规则。

import string from '@adonisjs/core/helpers/string'

string.pluralize.addUncountableRule('paper')
string.pluralize.addSingularRule(/singles$/i, 'singular')

singular

将单词转换为单数形式。该方法从 pluralize 包 导出。

import string from '@adonisjs/core/helpers/string'

string.singular('tests')
// test

isSingular

判断一个单词是否已经是单数形式。

import string from '@adonisjs/core/helpers/string'

string.isSingular('test') // true

camelCase

将字符串值转换为驼峰命名(camelCase)。

import string from '@adonisjs/core/helpers/string'

string.camelCase('user_name') // userName

以下是一些转换示例。

输入输出
'test''test'
'test string''testString'
'Test String''testString'
'TestV2''testV2'
'foo_bar''fooBar'
'version 1.2.10''version1210'
'version 1.21.0''version1210'

capitalCase

将字符串值转换为首字母大写形式(capital case)。

import string from '@adonisjs/core/helpers/string'

string.capitalCase('helloWorld') // Hello World

以下是一些转换示例。

输入输出
'test''Test'
'test string''Test String'
'Test String''Test String'
'TestV2''Test V 2'
'version 1.2.10''Version 1.2.10'
'version 1.21.0''Version 1.21.0'

dashCase

将字符串值转换为短横线命名(dash case)。

import string from '@adonisjs/core/helpers/string'

string.dashCase('helloWorld') // hello-world

可选地,你可以将每个单词的首字母大写。

string.dashCase('helloWorld', { capitalize: true }) // Hello-World

以下是一些转换示例。

输入输出
'test''test'
'test string''test-string'
'Test String''test-string'
'Test V2''test-v2'
'TestV2''test-v-2'
'version 1.2.10''version-1210'
'version 1.21.0''version-1210'

dotCase

将字符串值转换为点命名(dot case)。

import string from '@adonisjs/core/helpers/string'

string.dotCase('helloWorld') // hello.World

可选地,你可以将所有单词的首字母转换为小写。

string.dotCase('helloWorld', { lowerCase: true }) // hello.world

以下是一些转换示例。

输入输出
'test''test'
'test string''test.string'
'Test String''Test.String'
'dot.case''dot.case'
'path/case''path.case'
'TestV2''Test.V.2'
'version 1.2.10''version.1210'
'version 1.21.0''version.1210'

noCase

去除字符串值中的所有大小写形式。

import string from '@adonisjs/core/helpers/string'

string.noCase('helloWorld') // hello world

以下是一些转换示例。

输入输出
'test''test'
'TEST''test'
'testString''test string'
'testString123''test string123'
'testString_1_2_3''test string 1 2 3'
'ID123String''id123 string'
'foo bar123''foo bar123'
'a1bStar''a1b star'
'CONSTANT_CASE ''constant case'
'CONST123_FOO''const123 foo'
'FOO_bar''foo bar'
'XMLHttpRequest''xml http request'
'IQueryAArgs''i query a args'
'dot.case''dot case'
'path/case''path case'
'snake_case''snake case'
'snake_case123''snake case123'
'snake_case_123''snake case 123'
'"quotes"''quotes'
'version 0.45.0''version 0 45 0'
'version 0..78..9''version 0 78 9'
'version 4_99/4''version 4 99 4'
' test ''test'
'something_2014_other''something 2014 other'
'amazon s3 data''amazon s3 data'
'foo_13_bar''foo 13 bar'

pascalCase

将字符串值转换为帕斯卡命名(Pascal case)。非常适合用于生成 JavaScript 类名。

import string from '@adonisjs/core/helpers/string'

string.pascalCase('user team') // UserTeam

以下是一些转换示例。

输入输出
'test''Test'
'test string''TestString'
'Test String''TestString'
'TestV2''TestV2'
'version 1.2.10''Version1210'
'version 1.21.0''Version1210'

sentenceCase

将值转换为一个句子。

import string from '@adonisjs/core/helpers/string'

string.sentenceCase('getting_started-with-adonisjs')
// Getting started with adonisjs

以下是一些转换示例。

输入输出
'test''Test'
'test string''Test string'
'Test String''Test string'
'TestV2''Test v2'
'version 1.2.10''Version 1 2 10'
'version 1.21.0''Version 1 21 0'

snakeCase

将值转换为蛇形命名(snake case)。

import string from '@adonisjs/core/helpers/string'

string.snakeCase('user team') // user_team

以下是一些转换示例。

输入输出
'_id''id'
'test''test'
'test string''test_string'
'Test String''test_string'
'Test V2''test_v2'
'TestV2''test_v_2'
'version 1.2.10''version_1210'
'version 1.21.0''version_1210'

titleCase

将字符串值转换为标题大小写(title case)。

import string from '@adonisjs/core/helpers/string'

string.titleCase('small word ends on')
// Small Word Ends On

以下是一些转换示例。

输入输出
'one. two.''One. Two.'
'a small word starts''A Small Word Starts'
'small word ends on''Small Word Ends On'
'we keep NASA capitalized''We Keep NASA Capitalized'
'pass camelCase through''Pass camelCase Through'
'follow step-by-step instructions''Follow Step-by-Step Instructions'
'this vs. that''This vs. That'
'this vs that''This vs That'
'newcastle upon tyne''Newcastle upon Tyne'
'newcastle *upon* tyne''Newcastle *upon* Tyne'

random

生成给定长度的密码学安全随机字符串。输出值是一个 URL 安全的 base64 编码字符串。

import string from '@adonisjs/core/helpers/string'

string.random(32)
// 8mejfWWbXbry8Rh7u8MW3o-6dxd80Thk

sentence

将单词数组转换为以逗号分隔的句子。

import string from '@adonisjs/core/helpers/string'

string.sentence(['routes', 'controllers', 'middleware'])
// routes, controllers, and middleware

你可以通过指定 options.lastSeparator 属性,用 or 替换 and

string.sentence(['routes', 'controllers', 'middleware'], {
  lastSeparator: ', or ',
})

在以下示例中,两个单词使用 and 分隔符组合,而非逗号(英语中通常提倡使用逗号)。不过,你可以对一对单词使用自定义分隔符。

string.sentence(['routes', 'controllers'])
// routes and controllers

string.sentence(['routes', 'controllers'], {
  pairSeparator: ', and ',
})
// routes, and controllers

condenseWhitespace

将字符串中的多个空白字符压缩为单个空白字符。

import string from '@adonisjs/core/helpers/string'

string.condenseWhitespace('hello  world')
// hello world

string.condenseWhitespace('  hello  world  ')
// hello world

seconds

将基于字符串的时间表达式解析为秒。

import string from '@adonisjs/core/helpers/string'

string.seconds.parse('10h') // 36000
string.seconds.parse('1 day') // 86400

parse 方法传入数值时,会原样返回,假定该值已经是秒。

string.seconds.parse(180) // 180

你可以使用 format 方法将秒格式化为美观的字符串。

string.seconds.format(36000) // 10h
string.seconds.format(36000, true) // 10 hours

milliseconds

将基于字符串的时间表达式解析为毫秒。

import string from '@adonisjs/core/helpers/string'

string.milliseconds.parse('1 h') // 3.6e6
string.milliseconds.parse('1 day') // 8.64e7

parse 方法传入数值时,会原样返回,假定该值已经是毫秒。

string.milliseconds.parse(180) // 180

使用 format 方法,你可以将毫秒格式化为美观的字符串。

string.milliseconds.format(3.6e6) // 1h
string.milliseconds.format(3.6e6, true) // 1 hour

bytes

将基于字符串的单位表达式解析为字节。

import string from '@adonisjs/core/helpers/string'

string.bytes.parse('1KB') // 1024
string.bytes.parse('1MB') // 1048576

parse 方法传入数值时,会原样返回,假定该值已经是字节。

string.bytes.parse(1024) // 1024

使用 format 方法,你可以将字节格式化为美观的字符串。该方法直接从 bytes 包导出。可用选项请参考该包的 README。

string.bytes.format(1048576) // 1MB
string.bytes.format(1024 * 1024 * 1000) // 1000MB
string.bytes.format(1024 * 1024 * 1000, { thousandsSeparator: ',' }) // 1,000MB

ordinal

获取给定数字对应的序数词。

import string from '@adonisjs/core/helpers/string'

string.ordinal(1) // 1st
string.ordinal(2) // '2nd'
string.ordinal(3) // '3rd'
string.ordinal(4) // '4th'

string.ordinal(23) // '23rd'
string.ordinal(24) // '24th'

safeEqual

检查两个 buffer 或字符串值是否相同。此方法不会泄露任何时间信息,并能防止 时序攻击(timing attack)

底层,此方法使用 Node.js 的 crypto.timeSafeEqual 方法,并支持比较字符串值。(crypto.timeSafeEqual 不支持字符串比较)

import { safeEqual } from '@adonisjs/core/helpers'

/**
 * The trusted value, it might be saved inside the db
 */
const trustedValue = 'hello world'

/**
 * Untrusted user input
 */
const userInput = 'hello'

if (safeEqual(trustedValue, userInput)) {
  // both are the same
} else {
  // value mismatch
}

safeTiming

确保回调至少执行一段最短的时间。这可以防范 时序攻击(timing attack) ,即攻击者通过测量响应时间来推断敏感信息(例如,通过密码重置来枚举用户)。

第一个参数是最短执行时间(毫秒)。如果回调提前完成,safeTiming 会等待剩余的时间。如果回调抛出异常,则会在延迟之后重新抛出该错误。

import { safeTiming } from '@adonisjs/core/helpers'

// Both paths (user exists or not) take the same minimum time
return safeTiming(200, async () => {
  const user = await User.findBy('email', email)
  if (user) await sendResetEmail(user)
  return { message: 'If this email exists, you will receive a reset link.' }
})

回调会接收一个带有 returnEarly 方法的 timing 对象,用于跳过延迟。当你希望在失败时为恒定时间、而在成功时快速响应时,这很有用。

import { safeTiming } from '@adonisjs/core/helpers'

return safeTiming(200, async (timing) => {
  const token = await Token.findBy('value', request.header('x-api-key'))
  if (token) {
    timing.returnEarly()
    return token.owner
  }
  throw new UnauthorizedException()
})

compose

compose 辅助函数允许你用更简洁的 API 来使用 TypeScript 类混入(mixin)。以下是未使用 compose 辅助函数时的混入用法示例。

class User extends UserWithAttributes(UserWithAge(UserWithPassword(UserWithEmail(BaseModel)))) {}

以下是使用 compose 辅助函数的示例。

  • 不存在嵌套。
  • mixin 的顺序为(从左到右/从上到下)。而之前是从内到外。
import { compose } from '@adonisjs/core/helpers'

class User extends compose(
  BaseModel,
  UserWithEmail,
  UserWithPassword,
  UserWithAge,
  UserWithAttributes
) {}

base64

用于 base64 编码和解码值的实用方法。

import { base64 } from '@adonisjs/core/helpers'

base64.encode('hello world')
// aGVsbG8gd29ybGQ=

encode 方法类似,你可以使用 urlEncode 来生成可安全在 URL 中传递的 base64 字符串。

urlEncode 方法执行以下替换。

  • + 替换为 -
  • / 替换为 _
  • 并移除字符串末尾的 = 号。
base64.urlEncode('hello world')
// aGVsbG8gd29ybGQ

你可以使用 decodeurlDecode 方法来解码先前编码的 base64 字符串。

base64.decode(base64.encode('hello world'))
// hello world

base64.urlDecode(base64.urlEncode('hello world'))
// hello world

当输入值是无效的 base64 字符串时,decodeurlDecode 方法返回 null。你可以开启 strict 模式,改为抛出异常。

base64.decode('hello world') // null
base64.decode('hello world', 'utf-8', true) // raises exception

fsReadAll

获取目录中所有文件的列表。该方法会递归地获取主目录和子目录中的文件。点文件会被隐式忽略。

import { fsReadAll } from '@adonisjs/core/helpers'

const files = await fsReadAll(new URL('./config', import.meta.url), { pathType: 'url' })
await Promise.all(files.map((file) => import(file)))

你也可以将选项作为第二个参数与目录路径一起传入。

type Options = {
  ignoreMissingRoot?: boolean
  filter?: (filePath: string, index: number) => boolean
  sort?: (current: string, next: string) => number
  pathType?: 'relative' | 'unixRelative' | 'absolute' | 'unixAbsolute' | 'url'
}

const options: Partial<Options> = {}
await fsReadAll(location, options)
参数描述
ignoreMissingRoot默认情况下,当根目录缺失时会抛出异常。将 ignoreMissingRoot 设置为 true 不会导致错误,并返回空数组。
filter定义一个过滤器以忽略某些路径。该方法在最终的文件列表上被调用。
sort定义一个自定义方法对文件路径进行排序。默认使用自然排序。
pathType定义如何返回收集到的路径。默认返回与操作系统相关的相对路径。如果你想导入收集到的文件,必须设置 pathType = 'url'

fsImportAll

fsImportAll 方法会递归导入给定目录中的所有文件,并将每个模块导出的内容设置到一个对象的属性上。

import { fsImportAll } from '@adonisjs/core/helpers'

const collection = await fsImportAll(new URL('./config', import.meta.url))
console.log(collection)
  • 集合是一个带有键值对树的对象。
  • 键是从文件路径创建的嵌套对象。
  • 值是模块导出的内容。如果一个模块同时有 defaultnamed 导出,则只使用默认导出。

第二个参数是用于自定义导入行为的选项。

type Options = {
  ignoreMissingRoot?: boolean
  filter?: (filePath: string, index: number) => boolean
  sort?: (current: string, next: string) => number
  transformKeys? (keys: string[]) => string[]
}

const options: Partial<Options> = {}
await fsImportAll(location, options)
参数描述
ignoreMissingRoot默认情况下,当根目录缺失时会抛出异常。将 ignoreMissingRoot 设置为 true 不会导致错误,并返回空对象。
filter定义一个过滤器以忽略某些路径。默认只导入以 .js.ts.json.cjs.mjs 结尾的文件。
sort定义一个自定义方法对文件路径进行排序。默认使用自然排序。
transformKeys定义一个回调方法以转换最终对象的键。该方法接收一个嵌套键的数组,并且必须返回一个数组。

String builder

StringBuilder 类提供了一个流式 API,用于对字符串值执行转换。你可以使用 string.create 方法获取字符串构建器的实例。

import string from '@adonisjs/core/helpers/string'

const value = string
  .create('userController')
  .removeSuffix('controller') // user
  .plural() // users
  .snakeCase() // users
  .suffix('_controller') // users_controller
  .ext('ts') // users_controller.ts
  .toString()

Message builder

MessageBuilder 类提供了一个 API,用于序列化带有过期时间与用途的 JavaScript 数据类型。你可以将序列化输出存储在像应用数据库这样的安全存储中,或者加密它(以防止篡改)并公开共享。

在以下示例中,我们序列化了一个带有 token 属性的对象,并将其过期时间设置为 1 hour

import { MessageBuilder } from '@adonisjs/core/helpers'

const builder = new MessageBuilder()
const encoded = builder.build(
  {
    token: string.random(32),
  },
  '1 hour',
  'email_verification'
)

/**
 * {
 *   "message": {
 *    "token":"GZhbeG5TvgA-7JCg5y4wOBB1qHIRtX6q"
 *   },
 *   "purpose":"email_verification",
 *   "expiryDate":"2022-10-03T04:07:13.860Z"
 * }
 */

一旦你有了带有过期时间和用途的 JSON 字符串,就可以加密它(以防止篡改)并与客户端共享。

在令牌验证期间,你可以解密先前加密的值,并使用 MessageBuilder 来验证负载并将其转换为 JavaScript 对象。

import { MessageBuilder } from '@adonisjs/core/helpers'

const builder = new MessageBuilder()
const decoded = builder.verify(value, 'email_verification')
if (!decoded) {
  return 'Invalid payload'
}

console.log(decoded.token)

Secret

Secret 类让你在应用内持有敏感值,而不会意外地将它们泄露在日志和控制台语句中。

例如,config/app.ts 文件中定义的 appKey 值就是 Secret 类的一个实例。如果你尝试将此值记录到控制台,你会看到 [redacted] 而非原始值。

为了演示,让我们启动一个 REPL 会话并尝试一下。

node ace repl
> (js) config = await import('./config/app.js')

# [Module: null prototype] {
  // highlight-start
#   appKey: [redacted],
  // highlight-end
#   http: {
#   }
# }
> (js) console.log(config.appKey)

# [redacted]

你可以调用 config.appKey.release 方法来读取原始值。Secret 类的目的并非阻止你的代码访问原始值。相反,它提供了一个安全网,避免敏感数据在日志中暴露。

使用 Secret 类

你可以按如下方式将自定义值包裹在 Secret 类中。

import { Secret } from '@adonisjs/core/helpers'
const value = new Secret('some-secret-value')

console.log(value) // [redacted]
console.log(value.release()) // some-secret-value

Data-types detection

我们从 helpers/is 导入路径导出了 @sindresorhus/is 模块,你可以在应用中使用它来执行类型检测。

import is from '@adonisjs/core/helpers/is'

is.object({}) // true
is.object(null) // false