php使用minio作为文件服务器

背景

最近,手头一个php的项目需要迭代文件的上传、下载等功能,打算使用minio作为文件服务。
网上搜索minio的相关资料,关于php的示例相对较少、零散。遂写下这篇文章,作为自己对minio使用的记录,主要包含:
1.minio server安装、web界面交互使用的介绍;
2.aws-sdk-php的安装;
3.php对minio的交互使用,诸如:上传、下载、创建桶。
希望这篇文章对你有一丁点帮助。

实践

1. minio是什么?
官方定义,MinIO 是一个基于Apache License v2.0开源协议的对象存储服务。它兼容亚马逊S3云存储服务接口,非常适合于存储大容量非结构化的数据,例如图片、视频、日志文件、备份数据和容器/虚拟机镜像等,而一个对象文件可以是任意大小,从几kb到最大5T不等。
个人理解,当你不打算买oss服务,但是想要一个文件服务来支撑你的业务需求,可以考虑使用minio来存储、管理你的文件,而不是把文件放在自己搭建的linux服务器上的特定目录下,自己去写一套api去维护文件管理服务。

2. 如何安装minio
既然minio作为文件服务器,提供了一个web交互界面作为文件管理的可视化服务。我们先安装一个minio来感受一下它的直观功能。
现在是2021年,docker已经流行了很多年了,这里我直接用docker来安装minio,安装步骤:
首先,去dockerhub官方镜像仓库(传送门:https://hub.docker.com/)查找minio的镜像介绍页面,
在这里插入图片描述
在这里插入图片描述
镜像的介绍主页会包含镜像的版本信息、拉取镜像的命令、镜像的启动命令、启动可选配置项、以及该镜像的其他使用说明!
然后,我们启动命令是:

docker run -d --name minio-server -p 9000:9000 -v /home/wzp/minio-data:/data minio/minio server /data
The MinIO deployment starts using default root credentials minioadmin:minioadmin

启动后,查看容器状态,如下所示:
在这里插入图片描述
说明:
注意-v挂载的目录是用于持久化minio文件存储服务的宿主机绝对路径,在/home/wzp/minio-data下面存的就是你上传到minio服务器的文件。
最后,咱们访问一下我们启动的minio容器为我们提供的web交互服务,打开你的浏览器,
输入:http://10.251.9.189:9000 (注意:这里的ip和端口号,取决于你容器的宿主机的ip和容器启动后和宿主机的端口号映射关系,-p 9000:9000 是冒号前面的端口号,才代表你的宿主机的端口号)
在这里插入图片描述
点击登陆,展示如下图:
在这里插入图片描述
3.编程语言如何使用minio
到这一步,我们已经安装好了minio的server服务器端。
那我们现在如何通过编程语言来和minio server通信呢?语言作为minio client,minio server已经为我们定义好了一套标准的api,我们只要在客户端调用api就能和minio server进行交互,完成诸如:上传、下载、删除文件、创建桶等操作。
通过访问minio的官网,我们得知官方已经提供了很多现有的sdk(针对多语言的sdk)来简化client端的交互体验。
传送门:https://docs.min.io/docs/java-client-quickstart-guide.html
在这里插入图片描述
不要慌,php有单独的客户端sdk,但是叫aws-sdk-php,介绍如下:
https://docs.min.io/docs/how-to-use-aws-sdk-for-php-with-minio-server.html
在这里插入图片描述
里面介绍了minio的安装和aws-sdk-php的composer安装方式!

在这里插入图片描述
安装成功!
如果你不是用的阿里镜像源,可能会遇到这种报错:
在这里插入图片描述
原因是,随着aliyun对国外资源的镜像站支持的日益完善,诸如:
https://pkg.phpcomposer.com/
https://php.cnpkg.org/
https://learnku.com/articles/30758
已经慢慢不支持镜像同步功能了。
在这里插入图片描述
直接替换为aliyun的镜像地址,方法如下:
//1.全局替换,所有项目的镜像站地址都替换,下次composer download直接去阿里云拿数据
composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/
//2.刷新 composer.lock 文件(不执行这一步,composer.lock里面已存在的包的下载依赖地址还是老镜像地址;运行 composer install 后会出现 404 并从源仓库开始下载,导致下载速度非常慢。这种情况即使是你全局配置了加速镜像也会出现)
composer update nothing
//3.再次执行composer require aws-sdk-php
体会到什么了吗?
只有商用的东西,才是最稳定的,因为背后会有资金源源不断地支撑。
以后关于国外资源,诸如:docker镜像、composer镜像、各种linux服务器的镜像、apt源、yum源第一时间都考虑使用aliyun的服务,毕竟阿里云的服务器生意天生就需要对这些国外资源进行支撑,方便购买服务器的人使用!
4.php的代码部分的展示
下面废话不多说了,上一段代码,包含基于minio的api实现文件的上传、删除、下载。

<?php
namespace app\index\controller;

use app\index\model\MinioClient;
use think\Db;
use think\Exception;
use app\index\traits\CommonMethod;
use think\exception\ValidateException;
use think\Log;
use Aws\S3\Exception\S3Exception;

/**
 * 功能概述:
 * 样本管理中心,负责样本上传、样本删除、样本投放、样本投放历史列表、样本下载等功能
 * Class RuleOperation
 * @package app\index\controller
 */
class SampleOperation extends CommonController
{
    //调用接口传递过来的json字符串
    protected $reqContent;

    //json数组
    protected $reqJsonArr;

    //upload file max size
    protected $uploadFileMaxSize=256*1024*1024;

    //upload file allowed ext
    protected $uploadFileAllowedExt=['apk'];

    //upload file allowed MIME type
    protected $uploadFileAllowedMIME=['application/java-archive'];

    //upload file save temp dir()
    protected $uploadFileSavePath=ROOT_PATH .'uploads'.DS."sample_file".DS;

    //在调用该控制器的其他自定义方法前,会先触发_initialize;连带触发
    public function _initialize()
    {
        parent::_initialize();
        //当前控制器内公用
        $this->reqContent = file_get_contents('php://input');
        $this->reqJsonArr = json_decode($this->reqContent, true);
    }

    /**
     * 样本文件上传(仅支持单文件上传,前端要做多文件上传,需要多次调用该接口)
     */
    public function upload(){
        //0.暂存目录创建
        $path=$this->uploadFileSavePath;
        if(!is_dir($path)){
            $done=mkdir($path);
            if(!$done){
                Log::write("文件目录生成失败:$path",'info');
                throw new ValidateException("文件目录生成失败");
            }
        }
        //1.接收文件
        $file = request()->file('upload_file');
        //2.校验文件大小(<=256MB)、扩展名:.apk 、MIME类型: application/vnd.android
        $oldName=$_FILES['upload_file']['name'];
        $info = $file
            ->validate(['size'=>$this->uploadFileMaxSize,'ext'=>$this->uploadFileAllowedExt,'type'=>$this->uploadFileAllowedMIME])
            ->rule(function () use ($oldName){      //自定义生成形如:20210317-155656-12-原文件名的文件名
                $preTimeName=produceDateWithMicrosecond();
                return $preTimeName.'-'.$oldName;
            })
            ->move($this->uploadFileSavePath);
        if(!$info){
            // 上传失败获取错误信息
            throw new ValidateException($file->getError());
        }

        $infoArr=$info->getInfo();
        $saveFileName=$info->getFilename();
        $ext=$info->getExtension();

        //3.上传文件到minio服务器
        try {
            //实例化minio的客户端
            $minioClient=MinioClient::getInstance();
            $minioConfig=config('minio');

            //判断桶是不是存在;不存在,就创建一个
            if(!$minioClient->doesBucketExist($minioConfig['bucket'])){
                $minioClient->createBucket(['Bucket' =>$minioConfig['bucket']]);
            }

            //上传
            $fullFileName=$path.$saveFileName;
            $minioClient->putObject([
                'Bucket' => $minioConfig['bucket'],
                'Key'    => $minioConfig['prefix'].$saveFileName,   //bucket作为桶的名字,是顶层的文件目录;剩余下级目录的表示,通过Key来实现
                'Body'   => fopen($fullFileName, 'r'),
                'ACL'    => $minioConfig['acl'],
            ]);
        } catch (S3Exception $e) {
            throw new ValidateException($e->getAwsErrorMessage());
        }
        //5.计算样本文件的md5值返回给前端
        $sampleApkMd5=md5_file($fullFileName);

        //6.记录上传日志记录
        try{
            $insertData=[
                'sample_hash'=>$sampleApkMd5,
                'user_id'=>$this->getUserId(),
                'old_name'=>$oldName,
                'save_name'=>$saveFileName,
                'ext'=>$ext,
                'size'=>$infoArr['size'],   //单位是字节Byte
                'minio_bucket_name'=>$minioConfig['bucket'],
                'minio_prefix'=>$minioConfig['prefix'],
                'create_time'=>date('Y-m-d H:i:s')
            ];
            Db::table('hl_upload_sample_file_log')->insert($insertData);
        }catch(Exception $e){
            Log::write($e->getMessage(),'info');
            throw new ValidateException('系统异常,请稍后再试');
        }

        $frontRes=["sample_hash"=>$sampleApkMd5,"size"=>$infoArr['size']];

        return response(buildSuccessResponseJson($frontRes),200,[],'json');
    }

    //下载样本接口(从minio下载)
    public function download(){
        if(empty($this->reqJsonArr["sample_hash"])){
            throw new ValidateException('sample_hash不能为空');
        }
        $sampleHash=$this->reqJsonArr["sample_hash"];

        //根据样本hash获取minio_path作为后续的key参数
        $key=Db::table('hl_sample_put_record')->where('sample_hash',$sampleHash)->value('minio_path');
        if(empty($key)){
            Log::write($sampleHash.'对应的hl_sample_put_record记录的minio_path异常,为空!','sql');
            throw new ValidateException('数据异常,请反馈给管理员');
        }
        try {
            $minioClient=MinioClient::getInstance();
            $minioConfig=config('minio');
            // Get the object.
            $result = $minioClient->getObject([
                'Bucket' => $minioConfig['bucket'],
                'Key'    => $key
            ]);

            // Display the object in the browser.
            header("Content-Type: {$result['ContentType']}");
            echo $result['Body'];
        } catch (S3Exception $e) {
            Log::write($e->getMessage(),'notice');
            throw new ValidateException('数据异常,请反馈给管理员');
        }
    }

    //删除已上传,却未投放的样本(只删minio的文件,至于PHP特定目录的暂存文件,通过command命令行脚本实现定时删除一批旧数据)
    public function delSampleApk(){
        if(empty($this->reqJsonArr["sample_hash"])){
            throw new ValidateException('sample_hash不能为空');
        }
        $sampleHash=$this->reqJsonArr["sample_hash"];

        try{
            //检查样本是否存在于已投放列表
            $isExist=Db::table('hl_sample_put_record')->where('sample_hash',$sampleHash)->count();
            if($isExist){
                throw new ValidateException('该样本已投放,不能删除!');
            }

            //查询上传日志表
            $uploadFileInfo=Db::table('hl_upload_sample_file_log')
                ->field('minio_bucket_name,minio_prefix,save_name')
                ->where('sample_hash',$sampleHash)
                ->order('id','desc')
                ->find();

            if(empty($uploadFileInfo)){
                Log::write($sampleHash.'对应的hl_upload_sample_file_log记录的minio信息异常,为空!','sql');
                throw new ValidateException('数据异常,请反馈给管理员');
            }
        }catch(Exception $e){
            Log::write($e->getMessage(),'sql');
            throw new ValidateException('系统异常,请稍后再试');
        }

        //删除minio的文件
        try {
            $minioClient=MinioClient::getInstance();
            $minioClient->deleteObject([
                'Bucket' => $uploadFileInfo['minio_bucket_name'],
                'Key' => $uploadFileInfo['minio_prefix'].$uploadFileInfo['save_name'],
            ]);
            return response(buildSuccessResponseJson([]),200,[],'json');
        } catch (S3Exception $e) {
            Log::write($e->getMessage(),'notice');
            throw new ValidateException('系统异常,请稍后再试');
        }
    }
<?PHP

namespace app\index\model;

use Aws\S3\S3Client;
class MinioClient
{
    protected $options = [
        'endpoint'  =>  'http://127.0.0.1:9000',
        'version' => 'latest',
        'region'  => 'cn-north-1',  //China (Beijing)
        'use_path_style_endpoint' => true,
        'credentials' => [
            'key'    => 'minioadmin',
            'secret' => 'minioadmin',
        ],
        'bucket' => 'wxqb',
        'prefix'    => 'sample_apk/',   //自定义的键名,bucket作为桶的名字,是顶层的文件目录;剩余下级目录的表示,通过Key来实现;会存在桶名下的prefix目录下
        'acl'    => 'public-read',
    ];

    protected static $instance;

    protected $handler;

    /**
     * 构造函数
     * @param array $options 缓存参数
     * @access public
     */
    public function __construct($options = [])
    {
        if(empty($options)) {
            $options = config('minio');    //构造没穿参数,就从配置读取
            if (!empty($options)) {
                $this->options = array_merge($this->options, $options);
            }
        }

        $this->handler = new S3Client([
            'version' => $this->options['version'],
            'region'  => $this->options['region'],
            'endpoint' => $this->options['endpoint'],
            'use_path_style_endpoint' => true,
            'credentials' => [
                'key'    => $this->options['credentials']['key'],
                'secret' => $this->options['credentials']['secret'],
            ],
        ]);
    }

    /**
     * 单例模式 获取实例
     * @return MinioClient
     */
    public static  function getInstance()
    {
        if(empty(self::$instance)) {
            self::$instance = new MinioClient();
        }
        return self::$instance;
    }

    /**
     * call me
     * @param $name
     * @param $arguments
     * @return mixed
     */
    public function __call($name, $arguments)
    {
        return call_user_func_array(
            array($this->handler, $name),
            $arguments);
    }
}
<?php
return array(
    //minio server
    'minio'=>[
        'endpoint'  =>  'http://10.251.9.189:9000',
        'version' => 'latest',
        'region'  => 'cn-north-1',
        'use_path_style_endpoint' => true,
        'credentials' => [
            'key'    => 'minioadmin',       //用户名
            'secret' => 'minioadmin',       //密码
        ],
        'bucket' => 'wxqb',         //桶名,其实就是一级目录的名称(一般就用项目名)
        'prefix'    => 'sample_apk/',   //自定义的键名,bucket作为桶的名字,是顶层的文件目录;剩余下级目录的表示,通过Key来实现;会存在桶名下的prefix目录下
        'acl'    => 'public-read',
    ],
);

如果有接口参数不详的还可以参考,aws_sdk_php的英文接口文档:
https://docs.aws.amazon.com/aws-sdk-php/v3/api/class-Aws.S3.S3ClientInterface.html#_doesBucketExist
https://docs.aws.amazon.com/aws-sdk-php/v3/api/api-s3-2006-03-01.html#deleteobjects
https://docs.aws.amazon.com/code-samples/latest/catalog/php-s3-s3-downloading-object.php.html

MinIO文件服务器是一个轻量级的对象存储服务器,可以在本地计算机或云环境中运行。你可以使用MinIO来存储和访问大量的数据。根据不同的操作系统和环境,安装和配置MinIO有几种不同的方法。 对于Windows操作系统,你可以在Windows PowerShell中执行以下命令来安装和配置MinIO: setx MINIO_ROOT_USER admin Invoke-WebRequest -Uri "https://dl.min.io/server/minio/release/windows-amd64/minio.exe" -OutFile "C:\minio.exe" setx MINIO_ROOT_PASSWORD password C:\minio.exe server F:\Data --console-address ":9001" 对于Linux操作系统,你可以在命令行中执行以下命令来安装和配置MinIO: wget https://dl.min.io/server/minio/release/linux-amd64/minio chmod +x minio MINIO_ROOT_USER=admin MINIO_ROOT_PASSWORD=password ./minio server ./minio --console-address ":9001" 对于MacOS操作系统,你可以在命令行中执行以下命令来安装和配置MinIO: wget https://dl.min.io/server/minio/release/darwin-amd64/minio chmod +x minio MINIO_ROOT_USER=admin MINIO_ROOT_PASSWORD=password ./minio server F:\Data --console-address ":9001" 另一种推荐的方法是使用Docker来安装和配置MinIO。你可以使用以下命令来下载并启动MinIO容器: docker-compose up -d 这是一个示例的docker-compose.yml文件的内容: version: '3' services: minio: image: minio/minio hostname: "minio" ports: - "9000:9000" # api 端口 - "9001:9001" # 控制台端口 environment: MINIO_ACCESS_KEY: admin #管理后台用户名 MINIO_SECRET_KEY: password #管理后台密码,最小8个字符 volumes: - /home/deepsoft/minio/data:/data #映射当前目录下的data目录至容器内/data目录 - /home/deepsoft/minio/config:/root/.minio/ #映射配置目录 command: server --console-address ':9001' /data #指定容器中的目录 /data privileged: true restart: always logging: options: max-size: "50M" # 最大文件上传限制 max-file: "10" driver: json-file networks: - minio 这些是安装和配置MinIO文件服务器的不同方法和步骤,你可以根据你的操作系统和环境选择合适的方法来进行安装。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [MinIO分布式文件服务器搭建与入门](https://blog.csdn.net/lemon_TT/article/details/124675675)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [第03讲:MinIO分布式文件服务器](https://blog.csdn.net/qzc70919700/article/details/129988299)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值