使いやすいファイルアップロードソリューション

OSSサービスは今や基本的なサービスです。多くのクラウドサービスベンダーがそのようなサービスを提供しており、価格も高くありません。Songge自身のwww.javaboy.orgも同様のサービスを使用しています。

ただし、中小企業の場合、OSSサービスを購入するだけでなく、独自の専用ファイルサーバーを構築することもできます。独自の専用ファイルサーバーを構築する場合、FastDFSはより専門的なアプローチでした。Songgeは駅Bでは、興味のある友達が自分で見ることができます。ただし、FastDFSは構築がより面倒であり、エラーが発生しやすいため、友達には多かれ少なかれしきい値があります。

以前の記事でSongGeが録画した一部のプロジェクト動画では、ファイルのアップロードが含まれる場合、基本的にプロジェクトにローカルに保存されます。この方法の方が便利ですが、セキュリティは高くありません。

それで、今日私はあなたにもっと良いガジェット、MinIOを紹介し、そしてこのツールが私たちにどんな驚きをもたらすかを見ていきます。

1.MinIOの紹介

MinIOは、Apache License v2.0オープンソースプロトコルに基づくオブジェクトストレージサービスです。AmazonS3クラウドストレージサービスインターフェイスと互換性があり、写真、ビデオ、ログファイルなどの大容量の非構造化データの保存に非常に適しています。バックアップデータとコンテナ//仮想マシンイメージなど、およびオブジェクトファイルは、数KBから最大5Tまでの任意のサイズにすることができます。

MinIOは非常に軽量なサービスであり、NodeJS、Redis、MySQLなどの他のアプリケーションと簡単に統合できます。

つまり、MinIOを使用してオブジェクトストレージサービスを構築でき、MinIOのJavaクライアントはAmazonのS3クラウドストレージサービスクライアントインターフェイスと互換性があります。つまり、MinIOにデータを保存すると、S3にデータが保存されます。

MinIOの機能:

  1. Amazon S3との互換性:MinIOサーバーには、MinIO SDK、MinIOクライアント、AWS SDK、およびAWSCLIを使用してアクセスできます。
  2. 強力なデータ保護:MinIOはMinioErasureCodeを使用してハードウェア障害を防ぎます。
  3. (N/2)-1高可用性:MinIOサーバーは、分散セットアップで最大ノード障害に耐えることができます。
  4. ラムダコンピューティングがサポートされています。
  5. 暗号化された改ざん防止:MinIOは、最小限のパフォーマンスオーバーヘッドで、暗号化されたデータの機密性、整合性、および信頼性を保証します。サーバー側およびクライアント側の暗号化は、AES-256-GCM、ChaCha20-Poly1305、およびAES-CBCを使用してサポートされます。
  6. 接続可能なバックエンドストレージ:MinIO独自のファイルシステムに加えて、DAS、JBOD、NAS、Google Cloud Storage、AzureBlobストレージもサポートしています。

2.MinIOのインストール

ナンセンスではありません、今すぐ体験してみましょう。

手間を省くために、dockerを使って直接インストールしましょう。dockerに慣れていない場合は、公式アカウントのバックグラウンドでdockerに返信して、Songgeのdockerチュートリアルを入手してください。

次のコマンドを実行してMinIOをインストールします。

docker run -p 9000:9000 -p 9001:9001 -d minio/minio server /data --console-address ":9000" --address ":9001"
复制代码

このスタートアップコマンドでは、2つのポートが設定されています。console-addressはバックグラウンド管理用のWebページポートであり、addressはAPI通信ポートです。上記の起動スクリプトを例にとると、プロジェクトが正常に開始された後、Webページのアクセスポートは9000になります。Javaコードを介してファイルをアップロードする場合、通信ポートは9001になります。

プロジェクトが正常に開始されhttp://127.0.0.1:9000/loginたら、ブラウザのアドレスバーに入力してMinIOのバックエンドページにアクセスでき。

デフォルトのログインユーザー名とパスワードは両方ともminioadminです。

ログインが成功した後、最初にバケットを作成します。今後アップロードするファイルはすべて、次のようにバケットに含まれます。

作成が成功したら、バケットの読み取り権限も設定して、ファイルが正常にアップロードされた後にファイルを読み取れるようにする必要があります。左上隅の[設定]ボタンをクリックして、次のように設定します。

設定が完了したら、以下に示すように、このバケットにリソースをアップロードできます。

アップロードが完了すると、アップロードしたファイルが表示されます。

アップロードが成功したら、ファイルをクリックし、右側の[共有]ボタンをクリックして、ファイルのアクセスリンクをポップアップ表示します。ファイルを読み取り可能に設定しているため、ここではリンクの有効期間を無視できます。パスの前部から直接アクセスできます。アップロードしたばかりの写真は次のとおりです。

现在文件就可上传可访问了。是不是比 FastDFS 容易多了!

不过前面这种安装方式其实有点小问题,因为我们没有为 docker 容器设置数据卷,所以如果你把 docker 容器不小心删除了,那么数据也就没了!

所以我们要设置数据卷。

修正后的 docker 脚本如下:

docker run -p 9000:9000 -p 9001:9001 -d --name minio -v /Users/sang/minio/data:/data -v /Users/sang/minio/config:/root/.minio -e "MINIO_ROOT_USER=javaboy" -e "MINIO_ROOT_PASSWORD=123@45678" minio/minio server /data --console-address ":9000" --address ":9001"
复制代码

主要是加了数据卷映射功能,将 MinIO 的数据和配置文件映射到宿主机上,这样将来即使容器删除了,数据也都还在。

注意上面也自定义了登录用户名和密码。

按照上面的命令,重新创建容器之后,我们也创建一个桶并上传文件,上传成功之后,我们就可以在本地对应的文件夹看到我们上传的文件,如下:

3. 整合 Spring Boot

接下来我们再来看看在 Spring Boot 中如何玩 MinIO。

首先我们创建一个 Spring Boot 项目,引入 Web 依赖,如下:

项目创建成功之后,我们再来手动添加一下 MinIO 的依赖,如下:

<dependency>
    <groupId>io.minio</groupId>
    <artifactId>minio</artifactId>
    <version>8.2.1</version>
</dependency>
复制代码

这里我尝试用了最新的版本,但是似乎有一些 BUG,我也没有深究,就换了 8.2.1 这个版本,这个版本是 OK 的。

接下来我们来配置一下 application.yaml,配置一下文件上传所需要的基本信息:

minio:
  endpoint: http://localhost:9001
  accessKey: javaboy
  secretKey: 123@45678
  nginxHost: http://local.javaboy.org:9001
复制代码

这里四个属性:

  1. endpoint:这是 MinIO 的 API 通信地址。
  2. accessKey 和 secretKey 是通信的用户名和密码,这跟网页上登录时候的用户名密码一致。
  3. nginxHost:这个配置用来生成上传文件的访问路径。对于这个路径,有的小伙伴可能会有疑问,nginxHost 不就是 endpoint 吗?为什么还要单独配置?因为对于文件服务器而言,我们上传文件是通过 MinIO,但是访问的时候不一定通过 MinIO,我们可能会自己搭建一个 Nginx 服务器,通过 Nginx 服务器来访问上传后的资源,大家知道 Nginx 非常擅长于做这个事情,效率非常高。所以这里的 nginxHost 其实是指 Nginx 的访问路径。

接下来我们提供一个 MinioProperties 来接收这里的四个属性,如下:

@ConfigurationProperties(prefix = "minio")
public class MinioProperties {
    /**
     * 连接地址
     */
    private String endpoint;
    /**
     * 用户名
     */
    private String accessKey;
    /**
     * 密码
     */
    private String secretKey;
    /**
     * 域名
     */
    private String nginxHost;

    public String getEndpoint() {
        return endpoint;
    }

    public void setEndpoint(String endpoint) {
        this.endpoint = endpoint;
    }

    public String getAccessKey() {
        return accessKey;
    }

    public void setAccessKey(String accessKey) {
        this.accessKey = accessKey;
    }

    public String getSecretKey() {
        return secretKey;
    }

    public void setSecretKey(String secretKey) {
        this.secretKey = secretKey;
    }

    public String getNginxHost() {
        return nginxHost;
    }

    public void setNginxHost(String nginxHost) {
        this.nginxHost = nginxHost;
    }
}
复制代码

将 application.yaml 中相关的配置注入到这个配置类中来。

接下来我们需要提供一个 MinIOClient,通过这个客户端工具可以操作 MinIO,如下:

@Configuration
@EnableConfigurationProperties(MinioProperties.class)
public class MinioConfig {

    @Autowired
    private MinioProperties minioProperties;

    /**
     * 获取MinioClient
     */
    @Bean
    public MinioClient minioClient() {
        return MinioClient.builder()
                .endpoint(minioProperties.getEndpoint())
                .credentials(minioProperties.getAccessKey(), minioProperties.getSecretKey())
                .build();
    }

}
复制代码

这个也没啥好说的,传入通信地址以及用户名密码,就可以构建出一个 MinioClient 出来。

当文件上传成功之后,我们可以通过 MinIO 去访问,也可以通过 Nginx 访问,所以接下来我们就需要提供一个类,来封装这两个地址:

public class UploadResponse {
    private String minIoUrl;

    private String nginxUrl;

    public UploadResponse() {
    }

    public UploadResponse(String minIoUrl, String nginxUrl) {
        this.minIoUrl = minIoUrl;
        this.nginxUrl = nginxUrl;
    }

    public String getMinIoUrl() {
        return minIoUrl;
    }

    public void setMinIoUrl(String minIoUrl) {
        this.minIoUrl = minIoUrl;
    }

    public String getNginxUrl() {
        return nginxUrl;
    }

    public void setNginxUrl(String nginxUrl) {
        this.nginxUrl = nginxUrl;
    }
}
复制代码

再来提供一个 MinIO 文件上传工具类:

@Component
public class MinioUtil {

    @Autowired
    private MinioProperties minioProperties;

    @Autowired
    private MinioClient client;

    /**
     * 创建bucket
     */
    public void createBucket(String bucketName) throws Exception {
        if (!client.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build())) {
            client.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    /**
     * 上传文件
     */
    public UploadResponse uploadFile(MultipartFile file, String bucketName) throws Exception {
        //判断文件是否为空
        if (null == file || 0 == file.getSize()) {
            return null;
        }
        //判断存储桶是否存在  不存在则创建
        createBucket(bucketName);
        //文件名
        String originalFilename = file.getOriginalFilename();
        //新的文件名 = 存储桶文件名_时间戳.后缀名
        assert originalFilename != null;
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
        String fileName = bucketName + "_" +
                System.currentTimeMillis() + "_" + format.format(new Date()) + "_" + new Random().nextInt(1000) +
                originalFilename.substring(originalFilename.lastIndexOf("."));
        //开始上传
        client.putObject(
                PutObjectArgs.builder().bucket(bucketName).object(fileName).stream(
                        file.getInputStream(), file.getSize(), -1)
                        .contentType(file.getContentType())
                        .build());
        String url = minioProperties.getEndpoint() + "/" + bucketName + "/" + fileName;
        String urlHost = minioProperties.getNginxHost() + "/" + bucketName + "/" + fileName;
        return new UploadResponse(url, urlHost);
    }

    /**
     * 获取全部bucket
     *
     * @return
     */
    public List<Bucket> getAllBuckets() throws Exception {
        return client.listBuckets();
    }

    /**
     * 根据bucketName获取信息
     *
     * @param bucketName bucket名称
     */
    public Optional<Bucket> getBucket(String bucketName) throws IOException, InvalidKeyException, NoSuchAlgorithmException, InsufficientDataException, InvalidResponseException, InternalException, ErrorResponseException, ServerException, XmlParserException, ServerException {
        return client.listBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
    }

    /**
     * 根据bucketName删除信息
     *
     * @param bucketName bucket名称
     */
    public void removeBucket(String bucketName) throws Exception {
        client.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /**
     * 获取⽂件外链
     *
     * @param bucketName bucket名称
     * @param objectName ⽂件名称
     * @param expires    过期时间 <=7
     * @return url
     */
    public String getObjectURL(String bucketName, String objectName, Integer expires) throws Exception {
        return client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder().bucket(bucketName).object(objectName).expiry(expires).build());
    }

    /**
     * 获取⽂件
     *
     * @param bucketName bucket名称
     * @param objectName ⽂件名称
     * @return ⼆进制流
     */
    public InputStream getObject(String bucketName, String objectName) throws Exception {
        return client.getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 上传⽂件
     *
     * @param bucketName bucket名称
     * @param objectName ⽂件名称
     * @param stream     ⽂件流
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#putObject
     */
    public void putObject(String bucketName, String objectName, InputStream stream) throws
            Exception {
        client.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(stream, stream.available(), -1).contentType(objectName.substring(objectName.lastIndexOf("."))).build());
    }

    /**
     * 上传⽂件
     *
     * @param bucketName  bucket名称
     * @param objectName  ⽂件名称
     * @param stream      ⽂件流
     * @param size        ⼤⼩
     * @param contextType 类型
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#putObject
     */
    public void putObject(String bucketName, String objectName, InputStream stream, long
            size, String contextType) throws Exception {
        client.putObject(PutObjectArgs.builder().bucket(bucketName).object(objectName).stream(stream, size, -1).contentType(contextType).build());
    }

    /**
     * 获取⽂件信息
     *
     * @param bucketName bucket名称
     * @param objectName ⽂件名称
     * @throws Exception https://docs.minio.io/cn/java-client-api-reference.html#statObject
     */
    public StatObjectResponse getObjectInfo(String bucketName, String objectName) throws Exception {
        return client.statObject(StatObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }

    /**
     * 删除⽂件
     *
     * @param bucketName bucket名称
     * @param objectName ⽂件名称
     * @throws Exception https://docs.minio.io/cn/java-client-apireference.html#removeObject
     */
    public void removeObject(String bucketName, String objectName) throws Exception {
        client.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
    }
}
复制代码

都是一些常规的 API 调用,我就不逐行解释了,接下来我们来一个文件上传接口:

@RestController
public class FileUploadController {
    @Autowired
    MinioUtil minioUtil;

    @PostMapping("/upload")
    public String fileUpload(MultipartFile file) throws Exception {
        UploadResponse bucket01 = minioUtil.uploadFile(file, "bucket01");
        System.out.println("bucket01.getMinIoUrl() = " + bucket01.getMinIoUrl());
        System.out.println("bucket01.getNginxUrl() = " + bucket01.getNginxUrl());
        return bucket01.getMinIoUrl();
    }
}
复制代码

好啦,大功告成。

接下来启动 Spring Boot 项目,然后调用这个接口上传文件,上传成功后,控制台会打印如下信息:

这就表示文件上传成功了。

4. 配置 nginx

前面提到了 MinIO 可以结合 Nginx 来使用,那我们这里就来配一配 Nginx 看看。

为了省事,Nginx 我也选择安装到 docker 容器中,但是前面安装 MinIO 时,我们已经做了数据卷映射,即上传到 MinIO 的文件实际上是保存在宿主机的,所以现在也得给 Nginx 配置数据卷,将来让 Nginx 也去 /Users/sang/minio/data 路径下查找文件。

Nginx 安装指令如下:

docker run --name nginx01 -p 8888:80 -v /Users/sang/minio/data:/usr/share/nginx/html:ro -d nginx
复制代码

这里两个关键点:

  1. 设置 Nginx 端口为 8888。
  2. 将 MinIO 映射到宿主机的数据卷,再次挂载到 Nginx 上去。

大家知道,默认情况下,当我们访问 Nginx 的时候,Nginx 给我们展示出来的数据其实就是 /usr/share/nginx/html 目录下的,现在该目录其实就相当于我宿主机的 /Users/sang/minio/data 目录,所以我现在都不用修改 Nginx 的配置了,装好之后直接使用 Nginx 即可。

好啦,接下来我们修改一下 application.yaml,如下:

minio:
  endpoint: http://localhost:9001
  accessKey: javaboy
  secretKey: 123@45678
  nginxHost: http://local.javaboy.org:8888
复制代码

改完之后,再次上传文件,此时打印出来的文件访问路径如下:

これで、このNginxパスを介してアップロードされたファイルにアクセスすることもできます。

5.まとめ

さて、今日は友達とMinIOの使い方を共有し、Nginxで簡単なファイルサーバーを構築します。興味のある友達はそれを試すことができます。

パブリックアカウントのJiangnanYidianyuは、バックグラウンドでminio_demoに返信し、この記事のソースコードダウンロードリンクを取得しました。

おすすめ

転載: juejin.im/post/7088971403277385765