云存储

未匹配的标注

AdonisJS Drive 是云存储服务之上的抽象,例如:Amazon S3DigitalOcean SpacesGoogle Cloud Storage

Drive 与框架的核心预先捆绑在一起,因此不需要额外的安装步骤(驱动程序除外)。你可以按如下方式使用云端硬盘:

import Drive from '@ioc:Adonis/Core/Drive'

// 写一个文件
await Drive.put(filePath, stringOrBuffer)
await Drive.putStream(filePath, readableStream)

// 读取一个文件
const contents = await Drive.get(filePath)
const readableStream = await Drive.getStream(filePath)

// 查找文件是否存在
if (await Drive.exists(filePath)) {
  await Drive.get(filePath)
}

目标和设计限制

Drive 的主要目标是提供适用于所有存储提供商的一致 API。因此,例如,你可以在开发期间使用本地文件系统,并在生产中切换到S3,而无需更改任何一行代码。

为了保证 API 的一致性,Drive 无法使用给定存储服务的细节。

例如,您无法使用 Drive 创建符号链接,因为 symlinks 是基于 Unix 的文件系统概念,无法使用 S3 或 GCS 进行复制。

同样,也不支持无法跨驱动程序复制的云服务的专有功能。

用例

Drive 不能替代管理您的网站静态资产(如 CSS、JavaScript 或用于设计网站/Web 应用程序的图像/图标)。

Drive 的主要用例是帮助你快速管理用户上传的文件。这些可以是用户头像、博客文章封面图片或任何其他运行时管理的文档。

配置

Drive 的配置存储在 config/drive.ts 文件中。在此文件中,你可以使用相同/不同的驱动程序定义多个磁盘。

随意使用 config stub 创建配置文件(如果缺少)。

// 文件名: config/drive.ts
import { driveConfig } from '@adonisjs/core/build/config'

export default driveConfig({
  disk: Env.get('DRIVE_DISK'),

  disks: {
    local: {
      driver: 'local',
      visibility: 'public',
      root: Application.tmpPath('uploads'),
      basePath: '/uploads',
      serveAssets: true,
    },

    s3: {
      driver: 's3',
      visibility: 'public',
      key: Env.get('S3_KEY'),
      secret: Env.get('S3_SECRET'),
      region: Env.get('S3_REGION'),
      bucket: Env.get('S3_BUCKET'),
      endpoint: Env.get('S3_ENDPOINT'),

      // 让 minio 工作
      // forcePathStyle: true,
    },
  },
})

disk

disks 对象定义了你要在整个应用程序中使用的磁盘。每个磁盘都必须指定要使用的驱动程序。


disks

disks 对象定义了你要在整个应用程序中使用的磁盘。每个磁盘都必须指定要使用的驱动程序。

驱动

以下是官方驱动程序列表。

本地驱动

local 驱动程序已预先捆绑到框架核心中。它使用本地文件系统来读取/写入文件。

你必须在配置文件中配置本地驱动程序的根目录。该路径可以在你计算机上的任何位置(即使在项目根目录之外也可以)。

local: {
  driver: 'local',
  root: Application.tmpPath('uploads'),
},

为了模仿云服务的行为,本地驱动程序还可以在定义 basePath 并启用 serveFiles 选项时提供文件。

备注:
确保你没有在应用程序中使用与 basePath 相同的前缀定义任何其他路由。

local: {
  basePath: '/uploads',
  serveAssets: true,
}

配置完成后,Drive.getUrl 方法将生成下载文件的 URL。 URL 是相对于当前域的。

await Drive.getUrl('avatar.jpg')

// 返回
// /uploads/avatar.jpg
await Drive.getSignedUrl('avatar.jpg')

// 返回
// /uploads/avatar.jpg?signature=eyJtZXNzYWdlIjoiL3YxL3VzZXJzIn0.CGHY99jESI-AxPFBu1lE26TXjCASfC83XTyu58NivFw

S3 驱动

s3 驱动程序利用 Amazon S3 云存储来读取/写入文件。你必须单独安装驱动程序。

确保按照 configure 命令说明正确设置驱动程序。你还可以阅读说明 说明

npm i @adonisjs/drive-s3
node ace configure @adonisjs/drive-s3

你还可以将 s3 驱动程序与 S3 兼容的服务一起使用,例如 DigitalOcean Spaces 和 [MinIO](min. io/)。

使用不同的服务时,你还必须定义存储桶端点。

{
  driver: 's3',
  endpoint: Env.get('S3_ENDPOINT')
}

GCS 驱动

gcs 驱动程序使用 Google Cloud Storage 来读取/写入文件。你必须单独安装驱动程序。

确保按照 configure 命令说明正确设置驱动程序。你还可以阅读 相同的说明

npm i @adonisjs/drive-gcs
node ace configure @adonisjs/drive-gcs

如果你使用 GCS 统一 ACL,请确保将 usingUniformAcl 选项设置为 true。

文件可见性

Drive 允许你以 publicprivate 可见性保存文件。公共文件可以使用文件 URL 访问,而私有文件可以在服务器上读取或使用签名 URL 访问。

你可以通过在配置文件中定义 visibility 选项来配置整个磁盘的可见性。

{
  disks: {
    local: {
      driver: 'local',
      visibility: 'private'
      // ... 其余的配置
    }
  }
}

s3gcs 驱动程序还允许你定义单个文件的可见性。但是,出于以下原因,我们建议对公共文件和私有文件使用单独的存储桶。

  • 使用单独的 bucket 时,可以在整个 bucket 上配置一个 CDN 来服务公共文件。
  • 获得 local 文件驱动程序更好的交叉兼容性,因为本地驱动程序不允许文件级别的可见性控制。

无论使用何种驱动程序,你都无法仅通过文件 URL 访问 private 文件。相反,你需要创建一个签名 URL 或使用 Drive.get 方法来访问该文件。

// ✅ 有效
const contents = await Drive.get(filePath)

// ❌ 不能通过 URL 访问私有文件
const url = await Drive.getUrl(filePath)

// ✅ 可以使用签名的 url 访问
const signedUrl = await Drive.getSignedUrl(filePath)

写入文件

你可以使用以下方法之一创建/更新文件。如果文件已经存在,它将被更新。

put

put 方法接受文件名作为第一个参数,文件内容(字符串或缓冲区)作为第二个参数。

import Drive from '@ioc:Adonis/Core/Drive'

await Drive.put(filePath, contents)

你还可以使用第三个参数定义文件元数据。

await Drive.put(filePath, contents, {
  visibility: 'public',
  contentType: 'image/png'
})

以下是可用选项的列表。

选项 描述
visibility 文件可见性
contentType 文件内容类型
contentLanguage 文件语言。用于设置下载文件时的content-language header
contentEncoding 文件内容编码。用于设置下载文件时的content-encoding header
contentDisposition content-disposition 响应标头
cacheControl cache-control 响应标头的值。 GCS 驱动忽略此选项,因为底层 SDK 不允许配置它

putStream

putStream 方法接受内容作为可读流。选项与 put 方法相同。

import Drive from '@ioc:Adonis/Core/Drive'

await Drive.putStream(filePath, readableStream)

主体解析 moveToDisk

你可以使用 file.moveToDisk 方法将用户上传的文件移动到给定磁盘。

该方法接受以下参数。

  • 文件位置(没有文件名)。
  • 元数据选项。与 put 方法相同。
  • (可选)磁盘名称。如果未定义,则使用默认磁盘。
import Drive from '@ioc:Adonis/Core/Drive'
import Route from '@ioc:Adonis/Core/Route'

Route.post('posts', async ({ request }) => {
  const coverImage = request.file('cover_image')

  // 写入“images”目录
  await coverImage.moveToDisk('images')

  // 写入“根”目录
  await coverImage.moveToDisk('./')
})

moveToDisk 方法将用户上传的文件重命名为唯一/随机文件名。但是,你也可以手动定义文件名。

await coverImage.moveToDisk('images', {
  name: `${user.id}.${coverImage.extname}`
})

最后,你还可以定义自定义磁盘名称作为第三个参数。

await coverImage.moveToDisk('images', {}, 's3')

读取文件

你可以使用 Drive.getDrive.getStream 方法读取文件。当文件丢失时,这两种方法都会引发异常。

get

get 方法将文件内容作为缓冲区返回。你可以通过调用 toString 方法将其转换为字符串。

import Drive from '@ioc:Adonis/Core/Drive'

const contents = await Drive.get('filePath')
contents.toString()

// 自定义编码
contents.toString('ascii')

getStream

getStream 方法返回 可读流 的实例。

const readableStream = await Drive.getStream('filePath')
response.stream(readableStream)

生成 URL

你可以使用 Drive.getUrlDrive.getSignedUrl 方法生成文件路径的 URL。

对于云存储提供商,生成的 URL 指向云服务。而对于 local 驱动程序,URL 指向你的 AdonisJS 应用程序。

serveAssets 选项在配置文件中设置为 true 时,local 驱动程序会隐式注册路由。此外,basePath 是必需的,并且在已注册的磁盘中必须是唯一的。

{
  local: {
    driver: 'local',
    serveAssets: true,
    basePath: '/uploads'
  }
}

getUrl

返回下载给定文件的 URL。如果文件丢失,则会引发异常。使用 getUrl 方法返回的 URL 只能查看 public 文件。

const url = await Drive.getUrl('filePath')

getSignedUrl

getSignedUrl 方法返回一个 URL 以下载给定文件及其签名。只要签名有效,你就可以下载文件。

你还可以定义签名过期和 URL 失效的持续时间。

const url = await Drive.getSignedUrl('filePath')

// 过期
const url = await Drive.getSignedUrl('filePath', {
  expiresIn: '30mins'
})

最后,你还可以将以下响应内容标头定义为第二个参数。

const url = await Drive.getSignedUrl('filePath', {
  contentType: 'application/json',
  contentDisposition: 'attachment',
})

以下是可用选项的列表。

选项 说明
contentType content-type 响应标头的值
contentLanguage content-language 响应标头的值。被 GCS 驱动程序忽略
contentEncoding content-encoding 响应标头的值。被 GCS 驱动程序忽略
contentDisposition content-disposition 响应标头的值
cacheControl cache-control 响应标头的值。被 GCS 驱动程序忽略

下载文件

下载文件的推荐方法是使用通过 Drive.getUrl 方法生成的文件 URL。但是,你也可以从自定义路径手动下载文件。

以下是流文件的简化示例。 这里有 一个更健壮的实现。

import { extname } from 'path'
import Route from '@ioc:Adonis/Core/Route'
import Drive from '@ioc:Adonis/Core/Drive'

Route.get('/uploads/*', async ({ request, response }) => {
  const location = request.param('*').join('/')

  const { size } = await Drive.getStats(location)

  response.type(extname(location))
  response.header('content-length', size)

  return response.stream(await Drive.getStream(location))
})

删除文件

你可以使用 Drive.delete 方法删除文件。文件丢失时不会引发异常。

await Drive.delete('avatar.jpg')

复制和移动文件

你可以使用以下方法复制和移动文件。元数据选项与 put 方法相同。

对于云服务,操作在同一个存储桶中执行。因此,例如,如果要从本地磁盘复制文件,则必须使用 putputStream 方法。

await Drive.copy(source, destination, metadataOptions)
await Drive.move(source, destination, metadataOptions)

##在磁盘之间切换
你可以使用 Drive.use 方法在磁盘之间切换。

// 引用 S3 磁盘
const s3 = Drive.use('s3')

await s3.put(filePath, stringOrBuffer)

在运行时切换存储桶

使用 s3gcs 驱动程序时,你可以使用 bucket 方法在运行时切换存储桶。

Drive
  .use('s3')
  .bucket('bucketName')
  .put(filePath, stringOrBuffer)

添加自定义驱动

Drive 公开 API 以添加您的自定义驱动程序。每个驱动程序都必须遵守 DriverContract

备注:
你也可以使用官方的 S3GCS 驱动程序作为参考来创建您的自己的驱动。

interface DriverContract {
  name: string

  exists(location: string): Promise<boolean>

  get(location: string): Promise<Buffer>

  getStream(location: string): Promise<NodeJS.ReadableStream>

  getVisibility(location: string): Promise<Visibility>

  getStats(location: string): Promise<DriveFileStats>

  getSignedUrl(
    location: string,
    options?: ContentHeaders & { expiresIn?: string | number }
  ): Promise<string>

  getUrl(location: string): Promise<string>

  put(
    location: string,
    contents: Buffer | string,
    options?: WriteOptions
  ): Promise<void>

  putStream(
    location: string,
    contents: NodeJS.ReadableStream,
    options?: WriteOptions
  ): Promise<void>

  setVisibility(location: string, visibility: Visibility): Promise<void>

  delete(location: string): Promise<void>

  copy(
    source: string,
    destination: string,
    options?: WriteOptions
  ): Promise<void>

  move(
    source: string,
    destination: string,
    options?: WriteOptions
  ): Promise<void>
}

exists

返回一个布尔值以指示文件是否存在。当文件丢失时,驱动程序不应引发异常,而是返回 false。


get

将文件内容作为缓冲区返回。当文件丢失时,驱动程序应该引发异常。


getStream

将文件内容作为可读流返回。当文件丢失时,驱动程序应该引发异常。


getVisibility

返回文件可见性。如果驱动程序不支持文件级可见性,它应该从配置中返回磁盘可见性。


getStats

返回文件元数据。响应对象必须包含以下属性。

{
  size: number
  modified: Date
  isFile: boolean
  etag?: string // Optional
}

getSignedUrl

返回签名的 URL 以下载文件。如果可能,签名 URL 在生成 URL 时可以接受响应内容标头。


getUrl

返回文件的静态 URL。无需检查文件是否存在。相反,在提供文件时返回 404。


put

从原始内容(字符串或缓冲区)创建/更新文件。你还必须创建所需的目录。


putStream

从可读流创建/更新文件。你还必须创建所需的目录。


setVisibility

更新文件可见性。如果驱动程序不支持文件级别的可见性,那么它应该忽略该请求。


delete

删除文件。当文件丢失时,驱动程序不应引发异常。


copy

将文件从一个位置复制到另一个位置。复制操作也应该复制文件的元数据。例如:在 S3 中,它需要一个额外的请求来复制文件 ACL。


move

将文件从一个位置移动到另一个位置。移动操作也应该复制文件的元数据。


由外向内扩展

任何时候你都在扩展框架的核心。最好假设你无权访问应用程序代码及其依赖项。换句话说,像编写第三方包一样编写扩展,并使用依赖注入来依赖其他依赖。

出于演示目的,让我们创建一个没有实现的虚拟驱动程序。

mkdir providers/DummyDriver
touch providers/DummyDriver/index.ts

打开 DummyDriver/index.ts 文件并将以下内容粘贴到其中。

import type {
  Visibility,
  WriteOptions,
  ContentHeaders,
  DriveFileStats,
  DriverContract,
} from '@ioc:Adonis/Core/Drive'

export interface DummyDriverContract extends DriverContract {
  name: 'dummy' // 驱动程序名称
}

export type DummyDriverConfig = {
  driver: 'dummy' // 驱动程序名称
  // .. 其他配置选项
}

export class DummyDriver implements DummyDriverContract {
   // 实现在这里
}

接下来,你必须将驱动程序注册到 Drive 模块。你必须在服务提供者的引导方法中执行此操作。打开预先存在的 providers/AppProvider.ts 文件并将以下代码粘贴到其中。

import { ApplicationContract } from '@ioc:Adonis/Core/Application'

export default class AppProvider {
  constructor(protected app: ApplicationContract) {}

  public async boot() {
    const { DummyDriver } = await import('./DummyDriver')
    const Drive = this.app.container.use('Adonis/Core/Drive')

    Drive.extend('dummy', (_drive, _diskName, config) => {
      return new DummyDriver(config)
    })
  }
}

通知 TypeScript 新驱动程序

在有人可以在 config/drive.ts 文件中引用此驱动程序之前。你必须告知 TypeScript 静态编译器它的存在。

如果你正在创建一个包,那么你可以在你的包主文件中编写以下代码,否则你可以将它写在 contracts/drive.ts 文件中。

import {
  DummyDriverConfig,
  DummyDriverContract
} from '../providers/DummyDriver'

declare module '@ioc:Adonis/Core/Drive' {
  interface DriversList {
    dummy: {
      config: DummyDriverConfig,
      implementation: DummyDriverContract
    }
  }
}

使用驱动程序

好的,我们现在可以使用驱动程序了。让我们首先在 config/drive.ts 文件中定义新磁盘的配置。

{
  disks: {
    myDummyDisk: {
      driver: 'dummy',
      // ... 其余的配置
    }
  }
}

并按如下方式使用。

import Drive from '@ioc:Adonis/Core/Drive'

await Drive.use('myDummyDisk').put(filePath, contents)

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/adonisjs/5.x/di...

译文地址:https://learnku.com/docs/adonisjs/5.x/di...

上一篇 下一篇
贡献者:1
讨论数量: 0
发起讨论 只看当前版本


暂无话题~