PHP 使用FastDFS搭建图片服务器

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

猜你喜欢

转载自hudeyong926.iteye.com/blog/2382025