FastDFS官方介绍
FastDFS是一款类Google FS的开源分布式文件系统,这是一款国产的开源DFS软件。它用纯C语言实现,支持Linux、FreeBSD、AIX等UNIX系统。它只能通过专有API对文件进行存取访问,不支持POSIX接口方式,不能mount使用。准确地讲,Google FS以及FastDFS、mogileFS、 HDFS、TFS等类Google FS都不是系统级的分布式文件系统,而是应用级的分布式文件存储服务。 它对文件进行管理,功能包括:文件存储、文件同步、文件访问(文件上传、文件下载)等,解决了大容量存储和负载均衡的问题。特别适合以文件为载体的在线服务,如相册网站、视频网站等等。FastDFS为互联网量身定制,充分考虑了冗余备份、负载均衡、线性扩容等机制,并注重高可用、高性能等指标,使用FastDFS很容易搭建一套高性能的文件服务器集群提供文件上传、下载等服务。
目前已提供apache和nginx扩展模块.FastDFS扩展模块不依赖于FastDFS server,可以独立存在
1、FastDFS架构图
1)Tracker cluster中各个tracker server相互独立,不进行相互通信。
2)Storage cluster中各个storage组(Volume1,Volume2...)相互独立,不进行相互通信,也就是说各个组之间保存的数据是不相同的。但是各个组中的storage server之间是属于互相备份的关系,也就是说storage server之间保存相同的数据。
3)每个storage server会启动一个单独的线程主动向Tracker cluster中每个tracker server报告其状态信息,包括磁盘使用情况,文件同步情况及文件上传下载次数统计等信息。
1)Client通过Tracker server将文件上传到Storage server。
2)Tracker server向Client返回一台可用的Storage server的IP地址和端口号。
3)Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并进行文件上传。
4)上传完成,Storage server返回Client一个文件ID,文件上传结束。
1)Client通过Tracker server下载指定Storage组中某个Storage server上的某个文件(文件名包括Storage组名称)。
2)Tracker server向Client返回一台可用的Storage server的IP地址和端口号。
3)Client直接通过Tracker server返回的IP地址和端口与其中一台Storage server建立连接并进行文件下载。
PHP中FastDFS调用使用
<?php namespace test\upload\Driver; if (!class_exists('FastDFS', false)) { class FastDFS { protected $config = array(); /** * * @var FastDFSTrackerClient */ protected $tracker = null; /** * 初始化FastDFS上传驱动器 * * @throws FastDFSException */ public function __construct() { $fdfs_option = C('UPLOAD_OPTION.FastDFS_OPTION'); if(!$fdfs_option['tracker'] || count($fdfs_option['tracker'])==0) { throw new FastDFSException("请正确配置FastDFS的跟踪/调度服务器",-1); } //暂时不做多服务器负载处理 $this->config['tracker']['host'] = $fdfs_option['tracker'][0]['host']; $this->config['tracker']['port'] = $fdfs_option['tracker'][0]['port']; $this->config['tracker']['timeout'] = 30; //看具体整合方式在觉得初始化时是否连接上调度服务器 /*try { $this->tracker_get_connection(); } catch (FastDFSException $e) { throw new FastDFSException($e->getMessage(),$e->getCode()); }*/ } public function __destruct() { $this->close(); } public function getTracker() { return $this->tracker; } private function parseUrl($url) { $result = array(); $explodeUrl = explode('/', $url); $result['group_name'] = $explodeUrl[1]; $result['filename'] = $explodeUrl[2].'/'.$explodeUrl[3].'/'.$explodeUrl[4].'/'.$explodeUrl[5]; return $result; } /** * 获得一个tracker * * @return FastDFSTrackerClient */ public function tracker_get_connection() { try { $this->tracker = new FastDFSTrackerClient($this->config['tracker']['host'], $this->config['tracker']['port'], $this->config['tracker']['timeout']); } catch (FastDFSException $e) { throw new FastDFSException($e->getMessage(),$e->getCode()); } return $this->tracker; } /** * 通过tracker获取一个stroage * * @param string $groupName 文件组名,当为空时,组名由tracker决定 * @return \FastDFSStorageClient */ public function tracker_query_storage_store($groupName = '') { try { if(!$this->tracker) { $this->tracker_get_connection(); } $storage = $this->tracker->getStorage($groupName); } catch (FastDFSException $e) { throw new FastDFSException($e->getMessage(),$e->getCode()); } return $storage; } /** * 上传一个文件 * * @param string $localFile 本地的文件路径 * @param string $extName 文件的扩展名,文件上传后的扩展名 */ public function uploadFile($localFile, $extName = '') { try { $storage = $this->tracker_query_storage_store(); $result = $storage->uploadByFilename($localFile, $extName); } catch (FastDFSException $e) { throw new FastDFSException($e->getMessage(),$e->getCode()); } return '/'.$result['group_name'].'/'.$result['filename']; } /** * 上传文件内容 * * @param string $content 本地的文件路径 * @param string $extName 文件的扩展名,文件上传后的扩展名 */ public function uploadContent($content, $extName) { try { $storage = $this->tracker_query_storage_store(); $result = $storage->uploadByFileContent($content,$extName); } catch (FastDFSException $e) { throw new FastDFSException($e->getMessage(),$e->getCode()); } return '/'.$result['group_name'].'/'.$result['filename']; } /** * 在storage中删除一个文件 * * @param string $groupName 文件所在的组名 * @param string $remoteFile 要删除的文件路径 * @param FastDFSStorageClient $tracker * @param FastDFSStorageClient $storage */ public function deleteFile($remoteFile) { try { $FileData = $this->parseUrl($remoteFile); $storage = $this->tracker_query_storage_store($FileData['group_name']); $result = $storage->deleteFile($FileData['group_name'], $FileData['filename']); } catch (FastDFSException $e) { throw new FastDFSException($e->getMessage(),$e->getCode()); } return $result; } /** * 检查这个文件是否已经存在 * * @param string $remoteFile 文件在storage中的名字 */ public function isFileExist($remoteFile) { try { $FileData = $this->parseUrl($remoteFile); $storage = $this->tracker_query_storage_store($FileData['group_name']); $result = $storage->getFileInfo($FileData['group_name'], $FileData['filename']); } catch (FastDFSException $e) { throw new FastDFSException($e->getMessage(),$e->getCode()); } return $result; } public function close() { if ($this->tracker) { $this->tracker->close(); $this->tracker = null; } } } class FastDFSBase { protected $socket = null; const FDFS_HEADER_LENGTH = 10; //FastDFS协议头部长度 const FDFS_GROUP_NAME_MAX_LEN = 16; //FastDFS 存储节点组名称最大长度 const FDFS_IP_ADDRESS_SIZE = 16; //FastDFS 存储节点IP地址长度 const FDFS_PROTO_PKG_LEN_SIZE = 8; //FastDFS协议命令 const FDFS_PROTO_CMD_ACTIVE_TEST = 111; //检查调度服务器是否正常 const FDFS_PROTO_CMD_RESP = 100; const FDFS_PROTO_CMD_UPLOAD_SLAVE_FILE = 21; const FDFS_PROTO_CMD_DELETE_FILE = 12; const FDFS_PROTO_CMD_GET_METADATA = 15; const FDFS_PROTO_CMD_SET_METADATA = 13; const FDFS_PROTO_CMD_QUERY_STORE_WITHOUT_GROUP_ONE = 101; //不指定组,由调度节点根据轮询策略自动选择,并返回选择的存储节点服务器信息 const FDFS_PROTO_CMD_QUERY_STORE_WITH_GROUP_ONE = 104; //获取指定存储节点服务器的信息 const FDFS_STORAGE_PROTO_CMD_QUERY_FILE_INFO = 22; //获取文件信息 //Storage 存储节点 const FDFS_FILE_EXT_NAME_MAX_LEN = 6; //上传后文件的扩展名长度 const FDFS_FILE_PREFIX_MAX_LEN = 16; //Storage 存储节点 文件meta数据 const FDFS_OVERWRITE_METADATA = 1; public function connect($host, $port, $timeout = 30) { $this->socket = @fsockopen("tcp://$host", $port, $errno, $errstr, $timeout); if (!$this->socket) { throw new FastDFSException($errstr,-1); } } public function getSocket() { return $this->socket; } public function close() { fclose($this->socket); } public function read($length) { if (!$this->socket && feof($this->socket)) { throw new FastDFSException('链接失效或服务器已断开链接', -1); } $data = stream_get_contents($this->socket, $length); return $data; } public function send($data, $length = 0) { if (!$this->socket && feof($this->socket)) { throw new FastDFSException('链接失效或服务器已断开链接', -1); } if (!$length) { $length = strlen($data); } if (fwrite($this->socket, $data, $length) !== $length) { throw new FastDFSException('链接失效或服务器已断开链接', -1); } return true; } public static function padding($str, $len) { $str_len = strlen($str); return $str_len > $len ? substr($str, 0, $len) : $str . pack('x' . ($len - $str_len)); } public static function packHeader($command, $length = 0) { return self::packU64($length) . pack('Cx', $command); } public static function packMetaData($data) { $S1 = "\x01"; $S2 = "\x02"; $list = array(); foreach ($data as $key => $val) { $list[] = $key . $S2 . $val; }; return implode($S1, $list); } public static function parseMetaData($data) { $S1 = "\x01"; $S2 = "\x02"; $arr = explode($S1, $data); $result = array(); foreach ($arr as $val) { list($k, $v) = explode($S2, $val); $result[$k] = $v; } return $result; } public static function parseHeader($str, $len = FDFS_HEADER_LENGTH) { assert(strlen($str) === $len); $result = unpack('C10', $str); $length = self::unpackU64(substr($str, 0, 8)); $command = $result[9]; $status = $result[10]; return array( 'length' => $length, 'command' => $command, 'status' => $status ); } private static function unpackU64($v) { list ( $hi, $lo ) = array_values(unpack("N*N*", $v)); if (PHP_INT_SIZE >= 8) { if ($hi < 0) $hi += (1 << 32); // because php 5.2.2 to 5.2.5 is totally fucked up again if ($lo < 0) $lo += (1 << 32); // x64, int if ($hi <= 2147483647) return ($hi << 32) + $lo; // x64, bcmath if (function_exists("bcmul")) return bcadd($lo, bcmul($hi, "4294967296")); // x64, no-bcmath $C = 100000; $h = ((int) ($hi / $C) << 32) + (int) ($lo / $C); $l = (($hi % $C) << 32) + ($lo % $C); if ($l > $C) { $h += (int) ($l / $C); $l = $l % $C; } if ($h == 0) return $l; return sprintf("%d%05d", $h, $l); } // x32, int if ($hi == 0) { if ($lo > 0) return $lo; return sprintf("%u", $lo); } $hi = sprintf("%u", $hi); $lo = sprintf("%u", $lo); // x32, bcmath if (function_exists("bcmul")) return bcadd($lo, bcmul($hi, "4294967296")); // x32, no-bcmath $hi = (float) $hi; $lo = (float) $lo; $q = floor($hi / 10000000.0); $r = $hi - $q * 10000000.0; $m = $lo + $r * 4967296.0; $mq = floor($m / 10000000.0); $l = $m - $mq * 10000000.0; $h = $q * 4294967296.0 + $r * 429.0 + $mq; $h = sprintf("%.0f", $h); $l = sprintf("%07.0f", $l); if ($h == "0") return sprintf("%.0f", (float) $l); return $h . $l; } public static function packU64($v) { assert(is_numeric($v)); // x64 if (PHP_INT_SIZE >= 8) { assert($v >= 0); // x64, int if (is_int($v)) return pack("NN", $v >> 32, $v & 0xFFFFFFFF); // x64, bcmath if (function_exists("bcmul")) { $h = bcdiv($v, 4294967296, 0); $l = bcmod($v, 4294967296); return pack("NN", $h, $l); } // x64, no-bcmath $p = max(0, strlen($v) - 13); $lo = (int) substr($v, $p); $hi = (int) substr($v, 0, $p); $m = $lo + $hi * 1316134912; $l = $m % 4294967296; $h = $hi * 2328 + (int) ($m / 4294967296); return pack("NN", $h, $l); } // x32, int if (is_int($v)) return pack("NN", 0, $v); // x32, bcmath if (function_exists("bcmul")) { $h = bcdiv($v, "4294967296", 0); $l = bcmod($v, "4294967296"); return pack("NN", (float) $h, (float) $l); // conversion to float is intentional; int would lose 31st bit } // x32, no-bcmath $p = max(0, strlen($v) - 13); $lo = (float) substr($v, $p); $hi = (float) substr($v, 0, $p); $m = $lo + $hi * 1316134912.0; $q = floor($m / 4294967296.0); $l = $m - ($q * 4294967296.0); $h = $hi * 2328.0 + $q; return pack("NN", $h, $l); } } class FastDFSTrackerClient extends FastDFSBase { private $currentTrackerInfo = array(); private $storageObjs = array(); public function __construct($host, $port, $timeout = 30) { $this->currentTrackerInfo['host'] = $host; $this->currentTrackerInfo['port'] = $port; try { $this->connect($host, $port, $timeout); } catch (FastDFSException $e) { throw new FastDFSException("链接FastDFS调度节点失败",-1); } } public function __destruct() { foreach($this->storageObjs as $obj) { $obj->close(); } $this->storageObjs = array(); } public function getTrackerInfo() { return $this->currentTrackerInfo; } /** * 检查调度服务器是否正常 * * @return boolean */ public function isActive() { $header = $this->packHeader(self::FDFS_PROTO_CMD_ACTIVE_TEST, 0); try { $this->send($header); $resHeader = $this->parseHeader($this->read(self::FDFS_HEADER_LENGTH)); } catch (FastDFSException $e) { throw new FastDFSException("FastDFS调度节点链接已断开",-1); } return $resHeader['status'] == 0 ? true : false; } public function getStorage($groupName = '') { $reqBody = ''; if ($groupName) { if($this->storageObjs[$groupName])return $this->storageObjs[$groupName]; $cmd = self::FDFS_PROTO_CMD_QUERY_STORE_WITH_GROUP_ONE; $len = self::FDFS_GROUP_NAME_MAX_LEN; $reqBody = $this->padding($groupName, $len); } else { if(count($this->storageObjs)>0) { list(,$storageObj) = each($this->storageObjs); return $storageObj; } $cmd = self::FDFS_PROTO_CMD_QUERY_STORE_WITHOUT_GROUP_ONE; $len = 0; } $reqHeader = $this->packHeader($cmd, $len); try { $this->send($reqHeader . $reqBody); } catch (FastDFSException $e) { throw new FastDFSException("FastDFS调度节点链接已断开",-1); } $resHeader = $this->read(self::FDFS_HEADER_LENGTH); $resInfo = $this->parseHeader($resHeader); if ($resInfo['status'] != 0) { throw new FastDFSException("获取存储节点失败",-1); } $resBody = $resInfo['length'] ? $this->read($resInfo['length']) : ''; $groupName = trim(substr($resBody, 0, self::FDFS_GROUP_NAME_MAX_LEN)); $host = trim(substr($resBody, self::FDFS_GROUP_NAME_MAX_LEN, self::FDFS_IP_ADDRESS_SIZE + 1)); list(,, $port) = unpack('N2', substr($resBody, self::FDFS_GROUP_NAME_MAX_LEN + self::FDFS_IP_ADDRESS_SIZE - 1, self::FDFS_PROTO_PKG_LEN_SIZE)); $storeIndex = ord(substr($resBody, -1)); try { $this->storageObjs[$groupName] = new FastDFSStorageClient($host, $port, 30, $groupName, $storeIndex); } catch (FastDFSException $e) { throw new FastDFSException($e->getMessage(),$e->getCode()); } return $this->storageObjs[$groupName]; } } class FastDFSStorageClient extends FastDFSBase { private $currentStorageInfo = array(); public function __construct($host, $port, $timeout = 30, $groupName, $storeIndex) { $this->currentStorageInfo['host'] = $host; $this->currentStorageInfo['port'] = $port; $this->currentStorageInfo['timeout'] = $timeout; $this->currentStorageInfo['groupName'] = $groupName; $this->currentStorageInfo['storeIndex'] = $storeIndex; try { $this->connect($host, $port, $timeout); } catch (FastDFSException $e) { throw new FastDFSException("链接FastDFS存储节点失败",-1); } } public function getStorageInfo() { return $this->currentStorageInfo; } /** * 上传一个文件 * * @param string $localFile 本地的文件路径 * @param string $extName 文件的扩展名,文件上传后的扩展名 * @param array $metas 文件的附加信息 */ public function uploadByFilename($localFile, $extName = '', $metas = array()) { if (!file_exists($localFile)) { throw new FastDFSException("需上传的本地文件不存在",-1); } $pathInfo = pathinfo($localFile); $extName = $extName ? $extName : $pathInfo['extension']; $extLen = strlen($extName); if ($extLen > self::FDFS_FILE_EXT_NAME_MAX_LEN) { throw new FastDFSException("上传文件扩展名设置的太长了",-1); } $fp = fopen($localFile, 'rb'); flock($fp, LOCK_SH); $fileSize = filesize($localFile); $reqBodyLen = 1 + self::FDFS_PROTO_PKG_LEN_SIZE + self::FDFS_FILE_EXT_NAME_MAX_LEN + $fileSize; $reqHeader = $this->packHeader(11, $reqBodyLen); $reqBody = pack('C', $this->currentStorageInfo['storeIndex']) . $this->packU64($fileSize) . $this->padding($extName, self::FDFS_FILE_EXT_NAME_MAX_LEN); $this->send($reqHeader . $reqBody); stream_copy_to_stream($fp, $this->socket, $fileSize); flock($fp, LOCK_UN); fclose($fp); $resHeader = $this->read(self::FDFS_HEADER_LENGTH); $resInfo = $this->parseHeader($resHeader); if ($resInfo['status'] !== 0) { throw new FastDFSException("上传文件失败",-1); } $resBody = $resInfo['length'] ? $this->read($resInfo['length']) : ''; $groupName = trim(substr($resBody, 0, self::FDFS_GROUP_NAME_MAX_LEN)); $filePath = trim(substr($resBody, self::FDFS_GROUP_NAME_MAX_LEN)); if ($metas) { $this->setFileMetaData($groupName, $filePath, $metas); } return array( 'group_name' => $groupName, 'filename' => $filePath ); } /** * 上传文件内容 * * @param string $content 文件内容 * @param string $extName 文件的扩展名,文件上传后的扩展名 * @param array $metas 文件的附加信息 */ public function uploadByFileContent($content,$extName, $metas = array()) { $pathInfo = pathinfo($localFile); $extLen = strlen($extName); if ($extLen > self::FDFS_FILE_EXT_NAME_MAX_LEN) { throw new FastDFSException("上传文件扩展名设置的太长了",-1); } $fileSize = strlen($content); $reqBodyLen = 1 + self::FDFS_PROTO_PKG_LEN_SIZE + self::FDFS_FILE_EXT_NAME_MAX_LEN + $fileSize; $reqHeader = $this->packHeader(11, $reqBodyLen); $reqBody = pack('C', $this->currentStorageInfo['storeIndex']) . $this->packU64($fileSize) . $this->padding($extName, self::FDFS_FILE_EXT_NAME_MAX_LEN); $this->send($reqHeader . $reqBody); /*stream_copy_to_stream($fp, $this->socket, $fileSize); flock($fp, LOCK_UN); fclose($fp);*/ $this->send($content, $fileSize); $resHeader = $this->read(self::FDFS_HEADER_LENGTH); $resInfo = $this->parseHeader($resHeader); if ($resInfo['status'] !== 0) { throw new FastDFSException("上传文件失败",-1); } $resBody = $resInfo['length'] ? $this->read($resInfo['length']) : ''; $groupName = trim(substr($resBody, 0, self::FDFS_GROUP_NAME_MAX_LEN)); $filePath = trim(substr($resBody, self::FDFS_GROUP_NAME_MAX_LEN)); if ($metas) { $this->setFileMetaData($groupName, $filePath, $metas); } return array( 'group_name' => $groupName, 'filename' => $filePath ); } public function deleteFile($groupName, $fileName) { $reqBodyLen = strlen($fileName) + self::FDFS_GROUP_NAME_MAX_LEN; $reqHeader = $this->packHeader(self::FDFS_PROTO_CMD_DELETE_FILE, $reqBodyLen); $reqBody = $this->padding($groupName, self::FDFS_GROUP_NAME_MAX_LEN) . $fileName; $this->send($reqHeader . $reqBody); $resHeader = $this->read(self::FDFS_HEADER_LENGTH); $resInfo = $this->parseHeader($resHeader); return $resInfo['status'] == 0 ? true : false; } public function getFileInfo($groupName, $filePath) { $reqBodyLength = strlen($filePath) + self::FDFS_GROUP_NAME_MAX_LEN; $reqHeader = $this->packHeader(self::FDFS_STORAGE_PROTO_CMD_QUERY_FILE_INFO, $reqBodyLength); $reqBody = $this->padding($groupName, self::FDFS_GROUP_NAME_MAX_LEN) . $filePath; $this->send($reqHeader . $reqBody); $resHeader = $this->read(self::FDFS_HEADER_LENGTH); $resInfo = $this->parseHeader($resHeader); if (!!$resInfo['status']) { return false; } $resBody = $resInfo['length'] ? $this->read($resInfo['length']) : false; list(,, $file_size) = unpack('N2', substr($resBody, 0, self::FDFS_PROTO_PKG_LEN_SIZE)); list(,, $create_timestamp) = unpack('N2', substr($resBody, self::FDFS_PROTO_PKG_LEN_SIZE, self::FDFS_PROTO_PKG_LEN_SIZE)); list(,, $crc32) = unpack('N2', substr($resBody, self::FDFS_PROTO_PKG_LEN_SIZE*2, self::FDFS_PROTO_PKG_LEN_SIZE)); $host = trim(substr($resBody, self::FDFS_PROTO_PKG_LEN_SIZE*3, self::FDFS_IP_ADDRESS_SIZE)); $storeIndex = ord(substr($resBody, -1)); return array( 'host' => $host, 'file_size' => $file_size, 'create_timestamp' => $create_timestamp, 'crc32' => $crc32, 'storeIndex' => $storeIndex ); } public function setFileMetaData($groupName, $filePath, array $metaData, $flag = self::FDFS_OVERWRITE_METADATA) { $metaData = $this->packMetaData($metaData); $metaDataLength = strlen($metaData); $filePathLength = strlen($filePath); $flag = $flag === self::FDFS_OVERWRITE_METADATA ? 'O' : 'M'; $reqBodyLength = (self::FDFS_PROTO_PKG_LEN_SIZE * 2) + 1 + $metaDataLength + $filePathLength + self::FDFS_GROUP_NAME_MAX_LEN; $reqHeader = $this->packHeader(self::FDFS_PROTO_CMD_SET_METADATA, $reqBodyLength); $reqBody = $this->packU64($filePathLength) . $this->packU64($metaDataLength); $reqBody .= $flag . $this->padding($groupName, self::FDFS_GROUP_NAME_MAX_LEN) . $filePath . $metaData; $this->send($reqHeader . $reqBody); $resHeader = $this->read(self::FDFS_HEADER_LENGTH); $resInfo = $this->parseHeader($resHeader); return $resInfo['status'] == 0 ? true : false; } /** * 取得文件的元信息,如果文件不存在则,返回false,反正是一个关联数组 * * @param type $groupName * @param type $filePath * @return boolean */ public function getFileMeta($groupName, $filePath) { $reqBodyLength = strlen($filePath) + self::FDFS_GROUP_NAME_MAX_LEN; $reqHeader = $this->packHeader(self::FDFS_PROTO_CMD_GET_METADATA, $reqBodyLength); $reqBody = $this->padding($groupName, self::FDFS_GROUP_NAME_MAX_LEN) . $filePath; $this->send($reqHeader . $reqBody); $resHeader = $this->read(self::FDFS_HEADER_LENGTH); $resInfo = $this->parseHeader($resHeader); if (!!$resInfo['status']) { return false; } $resBody = $resInfo['length'] ? $this->read($resInfo['length']) : false; return $this->parseMetaData($resBody); } } class FastDFSException extends \Exception { } }
调用方法
<?php //$localFile = $_FILES['img']['tmp_name']; //$result = \test\upload\Upload::getInstance()->uploadFile($localFile, 'png'); //return /file1/M00/00/00/wKgAA1lUY26AE1L9AAAGnOZrl-w964.png namespace test\upload; class Upload { static private $instance = array(); // 上传实例 static private $_instance = null; // 当前上传实例 /** * 取得上传类实例 * @static * @access public * @return Object 返回上传驱动类 */ static public function getInstance() { $upload_option = C('UPLOAD_OPTION'); $md5 = md5(serialize($upload_option)); if(!isset(self::$instance[$md5])) { if($upload_option['type'] == 'FastDFS') { $class = 'test\\upload\\Driver\\'.$upload_option['type']; //$class = $upload_option['type']; if(class_exists($class)) { self::$instance[$md5] = new $class(); }else{ // 类没有定义 throw new UploadException("指定的上传驱动器不存在",-1); } } } self::$_instance = self::$instance[$md5]; return self::$_instance; } } class UploadException extends \Exception { }
配置
/* 上传组件配置 */ 'UPLOAD_OPTION' => [ 'type' => 'FastDFS', //FastDFS:资源服务器 /* FastDFS 资源服务器配置 */ 'FastDFS_OPTION' => [ 'tracker' => [ //调度节点(暂时只能设置一个调度节点,暂不支持调度负载) ['host' => '192.168.0.3','port' => '22122'] ], 'storage_host_alias' => '192.168.0.3' //设置后,会覆盖存储节点的host(主要用于不同环境调试) ], ],
项目中的文件上传控件需封装,包括富文本编辑器中的上传
开源的第三方 七牛云,又拍云,新浪SAE,百度BCS