怎样在 AdonisJS 中使用 Vultr 对象存储

介绍

Drive 是 AdonisJS 中的一个存储抽象库。 它提供了适用于所有存储提供商的一致 API。

Drive 有一个 S3 驱动程序来支持 S3 兼容的云存储,比如 Vultr Object Storage。 本指南解释了怎样为 Vultr 对象存储配置 AdonisJS Drive 并使用它来存储和读取文件。

先决条件

在开始之前,您应该:

安装 Node.js

AdonisJS 至少需要 Node.js 版本 14。您可以使用节点版本管理器 (NVM) 安装最新版本的 Node.js。

  1. 安装 NVM:

    $ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.3/install.sh | bash
    
  2. 断开并重新连接您的 ssh 会话。

  3. 安装 Node.js:

    $ nvm install node
    
  4. 检查 Node.js 版本:

    $ node -v
    
    v19.3.0
    

创建对象存储

  1. 登录到 Vultr 客户门户.

  2. 导航 产品 -> 对象.

  3. 添加对象存储. 选择区域并为其添加标签。

  4. 单击您的 Object Storage 并导航到 Bucket 选项卡。

  5. 创建一个 Bucket 并为其命名。

  6. 请注意 Hostname, 这 Secret Key, 这 Access KeyBucket Name.

创建新的 AdonisJS 应用程序

转到主目录。

$ cd ~

为您的应用程序创建一个新目录。

$ mkdir app

前往 app 目录并使用 npm init 命令。

$ cd app

$ npm init [email protected] website
  • 选择 Web 项目结构。

  • 当它提示您配置 Webpack Encore 时选择“y”。

它在 website 目录。 对于其余任务,您需要在 website 目录。

$ cd website

安装 Redis

本指南中的示例应用程序使用 Redis 来存储图像文件名。

安装 Redis:

$ sudo apt install redis-server

设置 Redis 持久化模式:

  1. 打开 Redis 配置文件:

    $ sudo nano /etc/redis/redis.conf
    
  2. 改变 appendonly noappendonly yes.

  3. Save 文件并退出。

  4. 重新启动 Redis。

    $ sudo systemctl restart redis-server
    

安装和配置 AdonisJS Redis 包:

$ npm i @adonisjs/redis

$ node ace configure @adonisjs/redis

打开 env.ts 文件:

$ nano env.ts

添加以下规则:

REDIS_CONNECTION: Env.schema.enum(['local'] as const),

REDIS_HOST: Env.schema.string({ format: 'host' }),

REDIS_PORT: Env.schema.number(),

REDIS_PASSWORD: Env.schema.string.optional(),

配置 AdonisJS 驱动器

AdonisJS Drive 有一个 S3 驱动程序,可以与 Vultr 对象存储等兼容 S3 的云存储进行交互。

安装和配置 S3 驱动程序:

$ npm i @adonisjs/drive-s3

$ node ace configure @adonisjs/drive-s3

打开 env.ts 文件:

$ nano env.ts

更新 DRIVE_DISK 规则:

DRIVE_DISK: Env.schema.enum(['local','s3'] as const),

添加以下规则:

S3_KEY: Env.schema.string(),

S3_SECRET: Env.schema.string(),

S3_BUCKET: Env.schema.string(),

S3_REGION: Env.schema.string(),

S3_ENDPOINT: Env.schema.string.optional(),

打开 config/drive.ts 文件:

$ nano config/drive.ts

在里面添加S3配置 disks 目的:

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'),

}

打开 .env 文件:

$ nano .env

更新 DRIVE_DISKs3:

DRIVE_DISK=s3

添加 Vultr 对象存储凭证:

S3_KEY=

S3_SECRET=

S3_BUCKET=adonis-drive

S3_REGION=sgp1

S3_ENDPOINT=https://sgp1.vultrobjects.com
  • S3_KEY 是您的 Vultr 对象存储访问密钥。

  • S3_SECRET 是您的 Vultr 对象存储密钥。

  • S3_BUCKET` 是您的 Vultr 对象存储桶名称。

  • S3_ENDPOINT 是您的 Vultr 对象存储主机名。

  • S3_REGION 是您的 Vultr 对象存储区域。

添加顺风 CSS

本指南使用 Tailwind CSS 作为 CSS 框架。 通过 NPM 安装 Tailwind CSS 及其依赖项:

$ npm install -D tailwindcss postcss autoprefixer postcss-loader

打开 webpack.config.js 文件:

$ nano webpack.config.js

启用 PostCSS 加载器:

Encore.enablePostCssLoader()

创建并打开 Tailwind CSS 配置文件:

$ npx tailwindcss init -p

$ nano tailwind.config.js

将内容更改为:

/** @type {import('tailwindcss').Config} */

module.exports = {

    content: [

        "./resources/**/*.edge",

        "./resources/**/*.js",

    ],

    theme: {

        extend: {},

    },

    plugins: [],

}

打开 resources/css/app.css 文件并将内容替换为 Tailwind CSS 指令:

@tailwind base;

@tailwind components;

@tailwind utilities;

添加 JavaScript

打开 resources/js/app.js 文件并添加以下脚本:

import '../css/app.css'



document.getElementById('fileImage').addEventListener('change',function(){

  if( this.files.length > 0 ){

      document.getElementById('uploadBtn').removeAttribute('disabled');

  }

});

用户选择图像文件后,脚本会启用上传按钮。

创建视图

创建一个 resources/views/gallery.edge 文件:

$ nano resources/views/gallery.edge

添加以下代码:

<html>

    <head>

        <title>Gallery</title>



        @entryPointStyles('app')

    </head>

    <body>

        <div class="max-w-7xl m-auto">

            <h1 class="text-3xl font-bold text-gray-900 text-center py-8 uppercase">Gallery</h1>

            <form action="" method="post" enctype="multipart/form-data" class="flex flex-wrap text-center justify-center items-start p-4 rounded-lg">

                <label class="block py-1">

                  <input id="fileImage" type="file" name="fileImage" class="block w-full text-sm text-slate-500 pr-6

                    file:cursor-pointer

                    file:mr-4 file:py-2 file:px-4

                    file:rounded-full file:border-0

                    file:text-sm file:font-semibold

                    file:bg-indigo-50 file:text-indigo-700

                    hover:file:bg-indigo-100

                  "/>



                  @if (flashMessages.has('errors.fileImage'))

                    <span class="block text-red-700 py-4 text-left">{{ flashMessages.get('errors.fileImage') }}</span>

                  @endif

                </label>

                <button id="uploadBtn" disabled class="rounded border border-transparent bg-indigo-600 px-6 py-2 text-base font-medium text-white hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2 disabled:opacity-50" type="submit">

                    Upload Image

                </button>

            </form>



            <div class="grid grid-cols-2 gap-x-4 gap-y-8 sm:grid-cols-3 sm:gap-x-6 lg:grid-cols-4 xl:gap-x-8 ">

                @each(image in images)

                    <div>

                        <img class="rounded" src="">

                    </div>

                @end

            </div>

        </div>



        @entryPointScripts('app')

    </body>

</html>

创建控制器

创建并打开 GalleryController.ts 文件:

$ node ace make:controller GalleryController -e

$ nano app/Controllers/Http/GalleryController.ts

在文件顶部导入库和助手:

import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

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

import Redis from '@ioc:Adonis/Addons/Redis'

import { string } from '@ioc:Adonis/Core/Helpers'

import { schema } from '@ioc:Adonis/Core/Validator'

创建 index 行动。 它显示上传表单并列出来自 Vultr 对象存储的所有图像。 它从 Redis 获取图像文件名并调用 getUrl Drive 库中的方法来获取每个图像的 URL。

public async index({ view }: HttpContextContract) {

    const galleryString = await Redis.get('gallery')

    const gallery = (galleryString) ? JSON.parse(galleryString) : []



    let images:string[] = []



    for (const filename of gallery) {

        const url = await Drive.getUrl(`gallery/${filename}`)

        images.push(url)

    }



    return view.render('gallery', { images })

}

创建 upload 行动。 它处理用户何时上传他们的图像。 它验证文件,将文件名保存到 Redis,然后使用 moveToDisk 方法。

public async upload({ request, response }: HttpContextContract) {

    const imageSchema = schema.create({

        fileImage: schema.file({

            extnames: ['jpg', 'png', 'gif']

        }),

    })



    const payload = await request.validate({ schema: imageSchema })

    const filename = `${string.generateRandom(32)}.${payload.fileImage.extname}`



    if (payload.fileImage) {

        await payload.fileImage.moveToDisk(", {

            name: `gallery/${filename}`

        }, 's3')



        const galleryString = await Redis.get('gallery')



        let gallery:string[] = []



        if (galleryString) {

            gallery = JSON.parse(galleryString)

        }



        gallery.push(filename)

        await Redis.set('gallery', JSON.stringify(gallery))

    }



    return response.redirect().toPath("https://www.vultr.com/")

}

您需要从 Redis 保存和获取图像文件名,因为 AdonisJS S3 驱动程序无法获取存储桶或文件夹中的文件列表。 它一次只能获取一个文件。

以下为全文 GalleryController.ts 文件:

import type { HttpContextContract } from '@ioc:Adonis/Core/HttpContext'

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

import Redis from '@ioc:Adonis/Addons/Redis'

import { string } from '@ioc:Adonis/Core/Helpers'

import { schema } from '@ioc:Adonis/Core/Validator'



export default class GalleryController {

    public async index({ view }: HttpContextContract) {

        const galleryString = await Redis.get('gallery')

        const gallery = (galleryString) ? JSON.parse(galleryString) : []



        let images:string[] = []



        for (const filename of gallery) {

            const url = await Drive.getUrl(`gallery/${filename}`)

            images.push(url)

        }



        return view.render('gallery', { images })

    }



    public async upload({ request, response }: HttpContextContract) {

        const imageSchema = schema.create({

            fileImage: schema.file({

                extnames: ['jpg', 'png', 'gif']

            }),

        })



        const payload = await request.validate({ schema: imageSchema })

        const filename = `${string.generateRandom(32)}.${payload.fileImage.extname}`



        if (payload.fileImage) {

            await payload.fileImage.moveToDisk(", {

                name: `gallery/${filename}`

            }, 's3')



            const galleryString = await Redis.get('gallery')



            let gallery:string[] = []



            if (galleryString) {

                gallery = JSON.parse(galleryString)

            }



            gallery.push(filename)

            await Redis.set('gallery', JSON.stringify(gallery))

        }



        return response.redirect().toPath("https://www.vultr.com/")

    }

}

添加路线

打开 start/routes.ts 文件:

$ nano start/routes.ts

添加以下代码:

Route.get("https://www.vultr.com/", 'GalleryController.index')

Route.post("https://www.vultr.com/", 'GalleryController.upload')

测试应用程序

  1. 要测试您的应用程序,您需要禁用 Ubuntu 防火墙。

    $ sudo ufw disable
    

    您可以在构建用于生产的应用程序时再次启用它。

  2. 启动开发服务器:

    $ node ace serve --encore-args="--host [VULTR_VPS_IP_ADDRESS]"
    
  3. 打开 https://[VULTR_VPS_IP_ADDRESS]:3333 在浏览器中。

  4. 您应该看到上传表单。

  5. 上传图像。

  6. 检查图像是否出现在您的 Vultr 对象存储和上传表单下方的图库中。

  7. 按 CTRL+C 停止开发服务器。

使用多个对象存储位置

Vultr 对象存储在多个位置可用。 以下是 Vultr 支持的位置:

  • 阿姆斯特丹: ams1.vultrobjects.com

  • 新泽西州: ewr1.vultrobjects.com

  • 硅谷: sjc1.vultrobjects.com

  • 新加坡: sgp1.vultrobjects.com

您可以在 AdonisJS 应用程序中使用多个对象存储位置来为您的文件添加冗余。 为此,您为每个位置添加一个磁盘配置 config/drive.ts 文件。 AdonisJS Drive 中的 Disk 表示特定的存储驱动程序和位置。

创建新的对象存储

  1. Vultr 客户门户.

  2. 导航 产品 -> 对象.

  3. 添加对象存储. 选择不同的地区,因为 example阿姆斯特丹。

  4. 在新的对象存储中创建一个桶。

  5. 请注意 Hostname, 这 Secret Key, 这 Access KeyBucket Name.

配置新磁盘

打开 config/drive.ts 文件:

$ nano config/drive.ts

在 S3 部分添加新的磁盘配置。 您可以将磁盘名称设置为任何名称,例如 example, s3ams. 为环境变量名称添加后缀,以区别于第一个磁盘。

s3ams: {

  driver: 's3',

  visibility: 'public',

  key: Env.get('S3_KEY_AMS'),

  secret: Env.get('S3_SECRET_AMS'),

  region: Env.get('S3_REGION_AMS'),

  bucket: Env.get('S3_BUCKET_AMS'),

  endpoint: Env.get('S3_ENDPOINT_AMS'),

},

打开 .env 文件:

$ nano .env

添加对象存储的凭据:

S3_KEY_AMS=

S3_SECRET_AMS=

S3_BUCKET_AMS=adonis-drive

S3_REGION_AMS=ams1

S3_ENDPOINT_AMS=https://ams1.vultrobjects.com
  • S3_KEY_AMS 是您的 Vultr 对象存储访问密钥。

  • S3_SECRET_AMS 是您的 Vultr 对象存储密钥。

  • S3_BUCKET_AMS 是您的 Vultr 对象存储桶名称。

  • S3_ENDPOINT_AMS 是您的 Vultr 对象存储主机名。

  • S3_REGION_AMS 是您的 Vultr 对象存储区域。

打开 env.ts 文件:

$ nano env.ts

添加 s3ams 磁盘名称 DRIVE_DISK 枚举值:

DRIVE_DISK: Env.schema.enum(['local','s3','s3ams'] as const),

添加验证规则 s3ams 磁盘环境变量:

S3_KEY_AMS: Env.schema.string(),

S3_SECRET_AMS: Env.schema.string(),

S3_BUCKET_AMS: Env.schema.string(),

S3_REGION_AMS: Env.schema.string(),

S3_ENDPOINT_AMS: Env.schema.string.optional(),

Save 文件并退出。

更新控制器

打开 GalleryController.ts 文件:

$ nano app/Controllers/Http/GalleryController.ts

在里面 upload action,搜索负责将文件上传到对象存储的代码。

await payload.fileImage.moveToDisk(", {

    name: `gallery/${filename}`

}, 's3')

复制代码并将磁盘名称更改为 s3ams.

await payload.fileImage.moveToDisk(", {

    name: `gallery/${filename}`

}, 's3')



await payload.fileImage.moveToDisk(", {

    name: `gallery/${filename}`

}, 's3ams')

它将文件上传到您的两个对象存储位置。

设置默认磁盘

index 中的动作 GalleryController.ts 使用默认磁盘获取图像 URL。 要更改默认磁盘,请打开 .env 文件:

$ nano .env

更新 DRIVE_DISK 您想要的磁盘值:

DRIVE_DISK=s3ams

Save 文件并退出。

测试应用

  1. 启动开发服务器:

    $ node ace serve --encore-args="--host [VULTR_VPS_IP_ADDRESS]"
    
  2. 打开 https://[VULTR_VPS_IP_ADDRESS]:3333 在浏览器中。

  3. 上传图像。

  4. 检查图像是否同时出现在 Vultr 对象存储位置和上传表单下方的图库中。

  5. 按 CTRL+C 停止开发服务器。

为生产而构建

AdonisJS 应用程序使用 TypeScript。 在生产环境中运行之前,您需要将其编译为 JavaScript。

前往 website 文件夹:

$ cd ~/app/website

编译它使用 build 命令。 结果在 build 文件夹。

$ node ace build --production --ignore-ts-errors

复制 .env 文件到 build 文件夹并打开它:

$ cp .env build/.env

$ nano build/.env

设置 NODE_ENV 中的变量 .env 归档到 production:

NODE_ENV=production

将仅生产依赖项安装到 build 文件夹:

$ cd build

$ npm ci --production

使用 PM2 在生产环境中运行应用程序

PM2 是守护进程管理器。 它可以帮助您在生产环境中运行和管理 AdonisJS 应用程序。

安装最新的 PM2 包:

$ npm install [email protected] -g

创建 PM2 生态系统文件来管理您的应用程序:

$ cd ~/app

$ nano ecosystem.config.js

将这些配置添加到 ecosystem.config.js 文件:

module.exports = {

    apps : [

        {

            name   : "website",

            script : "./website/build/server.js"

        }

    ]

}
  • 将您的应用程序名称放在 name 范围。

  • 把生产路径 script.js 在里面 script 范围。

运行你的应用程序:

$ pm2 start ecosystem.config.js

$ pm2 list

此时,您的应用程序正在运行。 但是,这个过程还不是持久的。 这意味着您必须在重新启动服务器后再次手动运行您的应用程序。

要运行持久化应用程序,您需要为 PM2 生成启动脚本。

$ pm2 startup

将显示的命令复制并粘贴到终端:

$ sudo env PATH=$PATH:/home/ubuntu/.nvm/versions/node/v19.3.0/bin /home/ubuntu/.nvm/versions/node/v19.3.0/lib/node_modules/pm2/bin/pm2 startup systemd -u ubuntu --hp /home/ubuntu

Save 您的 PM2 应用程序列表使用此命令:

$ pm2 save

Nginx 储备代理

您需要设置 Nginx 保留代理以将您的域连接到您的应用程序。 你把你的应用放在 Nginx 网络服务器后面。 它接受所有传入请求并将它们转发到您的应用程序。

添加 ondrej 存储库以获取最新版本的 Nginx。

$ sudo add-apt-repository -y ppa:ondrej/nginx-mainline

$ sudo apt update

安装 Nginx:

$ sudo apt install nginx

禁用默认的 Nginx 配置:

$ sudo unlink /etc/nginx/sites-enabled/default

新建一个 Nginx 配置文件:

$ sudo nano /etc/nginx/sites-available/website

添加以下配置。 确保更改域名 example.com 到您的域。 Save 文件并退出。

server {

    listen 80;



    server_name example.com;



    location / {

        proxy_pass https://localhost:3333;

        proxy_http_version 1.1;

        proxy_set_header Upgrade $http_upgrade;

        proxy_set_header Connection 'upgrade';

        proxy_set_header Host $host;

        proxy_set_header X-Real-IP $remote_addr;

        proxy_set_header X-Forwarded-Proto $scheme;

        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

        proxy_cache_bypass $http_upgrade;

    }

}

启用 Nginx 配置:

$ sudo ln -s /etc/nginx/sites-available/website /etc/nginx/sites-enabled/

从语法错误中测试您的配置:

$ sudo nginx -t

如果没有错误,那么您可以重新加载 Nginx 进程:

$ sudo systemctl reload nginx

将您的域名指向您的 Vultr VPS IP 地址。

配置防火墙

设置防火墙允许ssh端口:

$ sudo ufw allow 'OpenSSH'

允许 HTTP 和 HTTPS 端口:

$ sudo ufw allow 'Nginx Full'

启用防火墙:

$ sudo ufw enable

检查防火墙状态:

$ sudo ufw status

使用 Let’s Encrypt SSL 证书保护应用程序

Let’s Encrypt 为您的网站提供免费的 SSL 证书。 要生成证书,您需要使用 Certbot 软件工具。

安装 Certbot:

$ sudo snap install core; sudo snap refresh core

$ sudo snap install --classic certbot

$ sudo ln -s /snap/bin/certbot /usr/bin/certbot

生成 SSL 证书:

$ sudo certbot --nginx

在浏览器中访问您的域并确认它具有 HTTPS 连接。

Let’s Encrypt 证书在 90 天后过期。 Certbot 将续订命令添加到 systemd 计时器或 Cron Job 以在证书过期之前自动续订证书。 您可以使用以下命令验证它:

$ systemctl list-timers | grep 'certbot|ACTIVATES'

结论

本指南展示了在具有单个或多个对象存储位置的 AdonisJS 中使用 Vultr 对象存储的示例,包括使应用程序准备好生产的步骤。

进一步阅读

Vultr 对象存储。

AdonisJS 驱动文档。

使用 PM2 和 Nginx 部署多个 Adonis.js 应用程序。

PM2 文档。

NGINX 反向代理。

文章标题 名称(可选) 电子邮件(可选) 描述

发送建议

注:本教程在Vultr VPS上测试通过,如需部署请前往Vultr.com