zoukankan      html  css  js  c++  java
  • ECStore图片云端集群存储实践-又拍云存储

    通过又拍云存储REST API ,为ECStore新增图片存储引擎,从而达到图片数据与主站数据分离。提高网站性能。
    1. 图片存储引擎相关文件添加与修改

     一共涉及到ECStore 2个APP 的文件添加和修改。

     新增 :app/base/lib/storage/upyunclass.php

    <?php
    
    class UpYunException extends Exception {/*{{{*/
        public function __construct($message, $code, Exception $previous = null) {
            parent::__construct($message, $code);   // For PHP 5.2.x
        }
    
        public function __toString() {
            return __CLASS__ . ": [{$this->code}]: {$this->message}
    ";
        }
    }/*}}}*/
    
    class UpYunAuthorizationException extends UpYunException {/*{{{*/
        public function __construct($message, $code = 0, Exception $previous = null) {
            parent::__construct($message, 401, $previous);
        }
    }/*}}}*/
    
    class UpYunForbiddenException extends UpYunException {/*{{{*/
        public function __construct($message, $code = 0, Exception $previous = null) {
            parent::__construct($message, 403, $previous);
        }
    }/*}}}*/
    
    class UpYunNotFoundException extends UpYunException {/*{{{*/
        public function __construct($message, $code = 0, Exception $previous = null) {
            parent::__construct($message, 404, $previous);
        }
    }/*}}}*/
    
    class UpYunNotAcceptableException extends UpYunException {/*{{{*/
        public function __construct($message, $code = 0, Exception $previous = null) {
            parent::__construct($message, 406, $previous);
        }
    }/*}}}*/
    
    class UpYunServiceUnavailable extends UpYunException {/*{{{*/
        public function __construct($message, $code = 0, Exception $previous = null) {
            parent::__construct($message, 503, $previous);
        }
    }/*}}}*/
    
    class base_storage_upyunclass {
        const VERSION            = '2.0';
    
    /*{{{*/
        const ED_AUTO            = 'v0.api.upyun.com';
        const ED_TELECOM         = 'v1.api.upyun.com';
        const ED_CNC             = 'v2.api.upyun.com';
        const ED_CTT             = 'v3.api.upyun.com';
    
        const CONTENT_TYPE       = 'Content-Type';
        const CONTENT_MD5        = 'Content-MD5';
        const CONTENT_SECRET     = 'Content-Secret';
    
        // 缩略图
        const X_GMKERL_THUMBNAIL = 'x-gmkerl-thumbnail';
        const X_GMKERL_TYPE      = 'x-gmkerl-type';
        const X_GMKERL_VALUE     = 'x-gmkerl-value';
        const X_GMKERL_QUALITY   = 'x­gmkerl-quality';
        const X_GMKERL_UNSHARP   = 'x­gmkerl-unsharp';
    /*}}}*/
    
        private $_bucket_name;
        private $_username;
        private $_password;
        private $_timeout = 30;
    
        /**
         * @deprecated
         */
        private $_content_md5 = NULL;
    
        /**
         * @deprecated
         */
        private $_file_secret = NULL;
    
        /**
         * @deprecated
         */
        private $_file_infos= NULL;
    
        protected $endpoint;
    
        /**
        * 初始化 UpYun 存储接口
        * @param $bucketname 空间名称
        * @param $username 操作员名称
        * @param $password 密码
        *
        * @return object
        */
        public function __construct($bucketname, $username, $password, $endpoint = NULL, $timeout = 30) {/*{{{*/
            $this->_bucketname = $bucketname;
            $this->_username = $username;
            $this->_password = md5($password);
            $this->_timeout = $timeout;
    
            $this->endpoint = is_null($endpoint) ? self::ED_AUTO : $endpoint;
        }/*}}}*/
    
        /**
         * 获取当前SDK版本号
         */
        public function version() {
            return self::VERSION;
        }
    
        /** 
         * 创建目录
         * @param $path 路径
         * @param $auto_mkdir 是否自动创建父级目录,最多10层次
         *
         * @return void
         */
        public function makeDir($path, $auto_mkdir = false) {/*{{{*/
            $headers = array('Folder' => 'true');
            if ($auto_mkdir) $headers['Mkdir'] = 'true';
            return $this->_do_request('PUT', $path, $headers);
        }/*}}}*/
    
        /**
         * 删除目录和文件
         * @param string $path 路径
         *
         * @return boolean
         */
        public function delete($path) {/*{{{*/
            return $this->_do_request('DELETE', $path);
        }/*}}}*/
    
    
        /**
         * 上传文件
         * @param string $path 存储路径
         * @param mixed $file 需要上传的文件,可以是文件流或者文件内容
         * @param boolean $auto_mkdir 自动创建目录
         * @param array $opts 可选参数
         */
        public function writeFile($path, $file, $auto_mkdir = False, $opts = NULL) {/*{{{*/
            if (is_null($opts)) $opts = array();
            if (!is_null($this->_content_md5) || !is_null($this->_file_secret)) {
                //if (!is_null($this->_content_md5)) array_push($opts, self::CONTENT_MD5 . ": {$this->_content_md5}");
                //if (!is_null($this->_file_secret)) array_push($opts, self::CONTENT_SECRET . ": {$this->_file_secret}");
                if (!is_null($this->_content_md5)) $opts[self::CONTENT_MD5] = $this->_content_md5;
                if (!is_null($this->_file_secret)) $opts[self::CONTENT_SECRET] = $this->_file_secret;
            }
    
            // 如果设置了缩略版本或者缩略图类型,则添加默认压缩质量和锐化参数
            //if (isset($opts[self::X_GMKERL_THUMBNAIL]) || isset($opts[self::X_GMKERL_TYPE])) {
            //    if (!isset($opts[self::X_GMKERL_QUALITY])) $opts[self::X_GMKERL_QUALITY] = 95;
            //    if (!isset($opts[self::X_GMKERL_UNSHARP])) $opts[self::X_GMKERL_UNSHARP] = 'true';
            //}
    
            if ($auto_mkdir === True) $opts['Mkdir'] = 'true';
    
            $this->_file_infos = $this->_do_request('PUT', $path, $opts, $file);
    
            return $this->_file_infos;
        }/*}}}*/
    
        /**
         * 下载文件
         * @param string $path 文件路径
         * @param mixed $file_handle
         *
         * @return mixed
         */
        public function readFile($path, $file_handle = NULL) {/*{{{*/
            return $this->_do_request('GET', $path, NULL, NULL, $file_handle);
        }/*}}}*/
    
        /**
         * 获取目录文件列表
         *
         * @param string $path 查询路径
         *
         * @return mixed
         */
        public function getList($path = '/') {/*{{{*/
            $rsp = $this->_do_request('GET', $path);
    
            $list = array();
            if ($rsp) {
                $rsp = explode("
    ", $rsp);
                foreach($rsp as $item) {
                    @list($name, $type, $size, $time) = explode("	", trim($item));
                    if (!empty($time)) {
                        $type = $type == 'N' ? 'file' : 'folder';
                    }
    
                    $item = array(
                        'name' => $name,
                        'type' => $type,
                        'size' => intval($size),
                        'time' => intval($time),
                    );
                    array_push($list, $item);
                }
            }
    
            return $list;
        }/*}}}*/
    
        /**
         * 获取目录空间使用情况
         *
         * @param string $path 目录路径
         *
         * @return mixed
         */
        public function getFolderUsage($path) {/*{{{*/
            $rsp = $this->_do_request('GET', $path . '?usage');
            return floatval($rsp);
        }/*}}}*/
    
        /**
         * 获取文件、目录信息
         *
         * @param string $path 路径
         *
         * @return mixed
         */
        public function getFileInfo($path) {/*{{{*/
            $rsp = $this->_do_request('HEAD', $path);
    
            return $rsp;
        }/*}}}*/
    
        /**
        * 连接签名方法
        * @param $method 请求方式 {GET, POST, PUT, DELETE}
        * return 签名字符串
        */
        private function sign($method, $uri, $date, $length){/*{{{*/
            //$uri = urlencode($uri);
            $sign = "{$method}&{$uri}&{$date}&{$length}&{$this->_password}";
            return 'UpYun '.$this->_username.':'.md5($sign);
        }/*}}}*/
    
        /**
         * HTTP REQUEST 封装
         * @param string $method HTTP REQUEST方法,包括PUT、POST、GET、OPTIONS、DELETE
         * @param string $path 除Bucketname之外的请求路径,包括get参数
         * @param array $headers 请求需要的特殊HTTP HEADERS
         * @param array $body 需要POST发送的数据
         *
         * @return mixed
         */
        protected function _do_request($method, $path, $headers = NULL, $body= NULL, $file_handle= NULL) {/*{{{*/
            $uri = "/{$this->_bucketname}{$path}";
            $ch = curl_init("http://{$this->endpoint}{$uri}");
    
            $_headers = array('Expect:');
            if (!is_null($headers) && is_array($headers)){
                foreach($headers as $k => $v) {
                    array_push($_headers, "{$k}: {$v}");
                }
            }
    
            $length = 0;
            $date = gmdate('D, d M Y H:i:s GMT');
    
            if (!is_null($body)) {
                if(is_resource($body)){
                    fseek($body, 0, SEEK_END);
                    $length = ftell($body);
                    fseek($body, 0);
    
                    array_push($_headers, "Content-Length: {$length}");
                    curl_setopt($ch, CURLOPT_INFILE, $body);
                    curl_setopt($ch, CURLOPT_INFILESIZE, $length);
                }
                else {
                    $length = @strlen($body);
                    array_push($_headers, "Content-Length: {$length}");
                    curl_setopt($ch, CURLOPT_POSTFIELDS, $body);
                }
            }
            else {
                array_push($_headers, "Content-Length: {$length}");
            }
    
            array_push($_headers, "Authorization: {$this->sign($method, $uri, $date, $length)}");
            array_push($_headers, "Date: {$date}");
    
            curl_setopt($ch, CURLOPT_HTTPHEADER, $_headers);
            curl_setopt($ch, CURLOPT_TIMEOUT, $this->_timeout);
            curl_setopt($ch, CURLOPT_HEADER, 1);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
            //curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 0);
            curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
    
            if ($method == 'PUT' || $method == 'POST') {
                curl_setopt($ch, CURLOPT_POST, 1);
            }
            else {
                curl_setopt($ch, CURLOPT_POST, 0);
            }
    
            if ($method == 'GET' && is_resource($file_handle)) {
                curl_setopt($ch, CURLOPT_HEADER, 0);
                curl_setopt($ch, CURLOPT_FILE, $file_handle);
            }
    
            if ($method == 'HEAD') {
                curl_setopt($ch, CURLOPT_NOBODY, true);
            }
    
            $response = curl_exec($ch);
            $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    
            if ($http_code == 0) throw new UpYunException('Connection Failed', $http_code);
    
            curl_close($ch);
    
            $header_string = '';
            $body = '';
    
            if ($method == 'GET' && is_resource($file_handle)) {
                $header_string = '';
                $body = $response;
            }
            else {
                list($header_string, $body) = explode("
    
    ", $response, 2);
            }
    
            //var_dump($http_code);
            if ($http_code == 200) {
                if ($method == 'GET' && is_null($file_handle)) {
                    return $body;
                }
                else {
                    $data = $this->_getHeadersData($header_string);
                    return count($data) > 0 ? $data : true;
                }
                //elseif ($method == 'HEAD') {
                //    //return $this->_get_headers_data(substr($response, 0 , $header_size));
                //    return $this->_getHeadersData($header_string);
                //}
                //return True;
            }
            else {
                $message = $this->_getErrorMessage($header_string);
                if (is_null($message) && $method == 'GET' && is_resource($file_handle)) {
                    $message = 'File Not Found';
                }
                switch($http_code) {
                    case 401:
                        throw new UpYunAuthorizationException($message);
                        break;
                    case 403:
                        throw new UpYunForbiddenException($message);
                        break;
                    case 404:
                        throw new UpYunNotFoundException($message);
                        break;
                    case 406:
                        throw new UpYunNotAcceptableException($message);
                        break;
                    case 503:
                        throw new UpYunServiceUnavailable($message);
                        break;
                    default:
                        throw new UpYunException($message, $http_code);
                }
            }
        }/*}}}*/
    
        /**
         * 处理HTTP HEADERS中返回的自定义数据
         *
         * @param string $text header字符串
         *
         * @return array
         */
        private function _getHeadersData($text) {/*{{{*/
            $headers = explode("
    ", $text);
            $items = array();
            foreach($headers as $header) {
                $header = trim($header);
                if(strpos($header, 'x-upyun') !== False){
                    list($k, $v) = explode(':', $header);
                    $items[trim($k)] = in_array(substr($k,8,5), array('width','heigh','frame')) ? intval($v) : trim($v);
                }
            }
            return $items;
        }/*}}}*/
    
        /**
         * 获取返回的错误信息
         *
         * @param string $header_string
         *
         * @return mixed
         */
        private function _getErrorMessage($header_string) {
            list($status, $stash) = explode("
    ", $header_string, 2);
            list($v, $code, $message) = explode(" ", $status, 3);
            return $message;
        }
    
        /**
         * 删除目录
         * @deprecated 
         * @param $path 路径
         *
         * @return void
         */
        public function rmDir($path) {/*{{{*/
            $this->_do_request('DELETE', $path);
        }/*}}}*/
    
        /**
         * 删除文件
         *
         * @deprecated 
         * @param string $path 要删除的文件路径
         *
         * @return boolean
         */
        public function deleteFile($path) {/*{{{*/
            $rsp = $this->_do_request('DELETE', $path);
        }/*}}}*/
    
        /**
         * 获取目录文件列表
         * @deprecated
         * 
         * @param string $path 要获取列表的目录
         * 
         * @return array
         */
        public function readDir($path) {/*{{{*/
            return $this->getList($path);
        }/*}}}*/
    
        /**
         * 获取空间使用情况
         *
         * @deprecated 推荐直接使用 getFolderUsage('/')来获取
         * @return mixed
         */
        public function getBucketUsage() {/*{{{*/
            return $this->getFolderUsage('/');
        }/*}}}*/
    
        /**
        * 获取文件信息
        *
        * #deprecated
        * @param $file 文件路径(包含文件名)
        * return array('type'=> file | folder, 'size'=> file size, 'date'=> unix time) 或 null
        */
        //public function getFileInfo($file){/*{{{*/
        //    $result = $this->head($file);
        //    if(is_null($r))return null;
        //    return array('type'=> $this->tmp_infos['x-upyun-file-type'], 'size'=> @intval($this->tmp_infos['x-upyun-file-size']), 'date'=> @intval($this->tmp_infos['x-upyun-file-date']));
        //}/*}}}*/
    
        /**
        * 切换 API 接口的域名
        *
        * @deprecated
        * @param $domain {默然 v0.api.upyun.com 自动识别, v1.api.upyun.com 电信, v2.api.upyun.com 联通, v3.api.upyun.com 移动}
        * return null;
        */
        public function setApiDomain($domain){/*{{{*/
            $this->endpoint = $domain;
        }/*}}}*/
    
        /**
        * 设置待上传文件的 Content-MD5 值(如又拍云服务端收到的文件MD5值与用户设置的不一致,将回报 406 Not Acceptable 错误)
        *
        * @deprecated
        * @param $str (文件 MD5 校验码)
        * return null;
        */
        public function setContentMD5($str){/*{{{*/
            $this->_content_md5 = $str;
        }/*}}}*/
    
        /**
        * 设置待上传文件的 访问密钥(注意:仅支持图片空!,设置密钥后,无法根据原文件URL直接访问,需带 URL 后面加上 (缩略图间隔标志符+密钥) 进行访问)
        * 如缩略图间隔标志符为 ! ,密钥为 bac,上传文件路径为 /folder/test.jpg ,那么该图片的对外访问地址为: http://空间域名/folder/test.jpg!bac
        *
        * @deprecated
        * @param $str (文件 MD5 校验码)
        * return null;
        */
        public function setFileSecret($str){/*{{{*/
            $this->_file_secret = $str;
        }/*}}}*/
    
        /**
         * @deprecated
        * 获取上传文件后的信息(仅图片空间有返回数据)
        * @param $key 信息字段名(x-upyun-width、x-upyun-height、x-upyun-frames、x-upyun-file-type)
        * return value or NULL
        */
        public function getWritedFileInfo($key){/*{{{*/
            if(!isset($this->_file_infos))return NULL;
            return $this->_file_infos[$key];
        }/*}}}*/
    }
    View Code

     新增app/base/lib/storage/upyunsystem.php

      1 <?php
      2 
      3 class base_storage_upyunsystem implements base_interface_storager
      4 {
      5     private $_tmpfiles = array();
      6 
      7     function __construct()
      8     {
      9        $this->upyun = new base_storage_upyunclass('图片空间名','授权操作员用户名','授权操作员密码');//替换成您自己的upyun信息
     10        $this->UPYUNHOST =  array(
     11                'http://img1.yourdomain.com', //替换成您自己的upyun信息,可在upyun后台解析一个多个域名
     12                'http://img2.yourdomain.com',
     13                'http://img3.yourdomain.com'
     14            );
     15     }//End Function
     16 
     17     public function watermark($file,$mark){
     18         //todo
     19     }
     20     public function resize($file,$w,$h){
     21 
     22  
     23                     
     24              if(!$w || !$h)return false;
     25              $opts = array(
     26                     'x-gmkerl-type'    => 'fix_width_or_height', // 缩略图类型
     27                     'x-gmkerl-value'  => $w.'x'.$h, // 缩略图大小
     28                    'x-gmkerl-quality'  => 100, // 缩略图压缩质量
     29                    'x-gmkerl-unsharp' => true // 是否进行锐化处理
     30             );
     31 
     32              $tmp_ident = md5(rand(0,9999).microtime());
     33              $tmp_ident = '/temp/'.substr($tmp_ident,6);
     34 
     35          $_s = $this->upyun->writeFile( $tmp_ident, file_get_contents($file), true, $opts);   
     36 
     37              if($_s && $_s['x-upyun-width']>0){
     38                  $_id = base64_encode($tmp_ident);
     39                  $_return = $this->getFile($_id);
     40                  $this->remove($_id);
     41                  return $_return;
     42              }else{
     43                  return false;
     44              }
     45 
     46     }
     47 
     48 
     49     public function save($file, &$url, $type, $addons, $ext_name="")
     50     {
     51 
     52          $filename = '/'.$type.'/'.$this->_get_ident($file,$type,$addons,$url,$path,$ext_name);
     53 
     54          $_s = $this->upyun->writeFile($filename,file_get_contents($file),true);
     55     
     56         if($_s && $_s['x-upyun-width']>0){
     57             $url = $this->UPYUNHOST[rand(0,count($this->UPYUNHOST)-1)].$filename;
     58             return base64_encode($filename);
     59         }else{
     60             return false;
     61         }
     62     }//End Function
     63     
     64     // 生成文件名
     65     public function _get_ident($file,$type,$addons,$url,&$path,$ext_name=""){
     66         $ident = md5(rand(0,9999).microtime());
     67         // 路径
     68         if(isset($addons['path']) && $addons['path']) {
     69             $path = $addons['path'];
     70         } else {
     71             $path = '/'.$ident{0}.$ident{1}.'/'.$ident{2}.$ident{3}.'/'.$ident{4}.$ident{5}.'/'.substr($ident,6);
     72         }
     73         // 文件名
     74         if(isset($addons['name']) && $addons['name']) {
     75             $uri = $addons['name'];
     76         } else {
     77             $uri = substr(md5(($addons?$addons:$file).microtime()),0,6);
     78         }
     79         // 后缀
     80             if($ext_name) {
     81                 if(strrpos($uri,".")) $uri = substr($uri,0,strrpos($uri,".")).$ext_name;
     82                 else $uri .= $ext_name;
     83             }
     84             return $uri;
     85     } // end function _get_ident
     86 
     87     public function replace($file, $id)
     88     {
     89         return $this->upyun->writeFile(base64_decode($id), file_get_contents($file) , true);
     90     }//End Function
     91 
     92 
     93     public function remove($id)
     94     {
     95         if($id){
     96             return $this->upyun->deleteFile(base64_decode($id));
     97         }else{
     98             return false;
     99         }
    100     }//End Function
    101 
    102     public function getFile($id, $type='image')
    103     {
    104          $tmpfile = tempnam(sys_get_temp_dir(), 'upyunsystem');
    105          array_push($this->_tmpfiles, $tmpfile);
    106          $_s = $this->upyun->readFile(base64_decode($id),null);
    107          file_put_contents($tmpfile, $_s);
    108         if($_s){
    109             return $tmpfile;
    110         }else{
    111             return false;
    112         }
    113     }//End Function
    114 
    115     function __destruct() 
    116     {
    117         foreach($this->_tmpfiles AS $tmpfile){
    118             @unlink($tmpfile);
    119         }//todo unlink tmpfiles;
    120     }//End Function
    121 
    122 }//End Class
    View Code

    注意:此文件内有upyun权限及图片空间域名信息,如直接使用此文件,请做修改!

    修改:app/image/lib/clip.php 2处:

    修改1:

    修改2:

    修改:app/image/model/image.php

    修改config/config.php ECStore站点配置文件

    修改(去除注释)#define('FILE_STORAGER','filesystem'); 为define('FILE_STORAGER','upyunsystem');

    修改后实现功能

    1. ECStore所有图片上传功能(在商品相册上传图片、在图片相册上传图片、在图库上传图片,统一会上传到upyun api指定帐户空间,并返回"随机已绑定域名+存储目录+图片后缀"形式的绝对URL
    2. 商品批量重新生成图片将调用upyun API并生成对应尺寸图片绝对URL到对应图片尺寸数据库字段
    3. 图库批量图片尺寸重新生成队列将调用upyun API并生成对应尺寸图片绝对URL到对应图片尺寸数据库字段

    最终实现全站图片云端集群存储。

    原文链接:http://www.it.toggle.cn/article_detail/b7e80163a2f5b56ee4dfc1bd2dac0ba0.html

  • 相关阅读:
    WinRAR的命令行模式用法介绍
    Linux find命令的用法实践
    Linux服务器磁盘扩展和oracle表空间文件迁移操作记录
    博客园开篇,自己开发的双色球小助手
    解决MySQL安装3306端口占用问题
    html书写行内元素时-tab和换行会在行内元素间引入间距
    FileReader生成图片dataurl的分析
    mysql启动流程
    npm--debug模块
    js之Symbol类型
  • 原文地址:https://www.cnblogs.com/ecstore/p/3353794.html
Copyright © 2011-2022 走看看