PHP_APC+Ajax实现的监视进度条的文件上传

// load.js:
ADS.addEvent(window, 'load', function (event) {
    var fileList = ADS.$('fileList');
    // 按照需要修改uoloadForm
    addProgressBar('uploadForm', function (response) {
        var files = response.filesProcessed;
        for (var i in files) {
            // 跳过空文件
            if (files[i] === null) {
                continue;
            }

            // 创建一个新的文件列表元素
            var li = document.createElement('li');
            var a = document.createElement('a');
            a.setAttribute('href', 'uploads/' + files[i]);
            a.appendChild(document.createTextNode(files[i]));
            li.appendChild(a);
            fileList.appendChild(li);
        }
        // 更新文件计数器
        var countContainer = ADS.$('fileCount');
        ADS.removeChildren(countContainer);
        var numFiles = fileList.getElementsByTagName('li').length;
        countContainer.appendChild(document.createTextNode(numFiles));
    });
});



// uploader.js:
/**
 * User: lindongpeng
 * Date: 13-2-19
 */

function verifyFileType(fileInput) {
    if (!fileInput.value || !fileInput.accept) {
        return true;
    }
    var extension = fileInput.value.split('.').pop().toLowerCase(),
        mimetypes = fileInput.accept.toLowerCase().split(','),
        type;
    for (var i = 0, len = mimetypes.length; i < len; i++) {
        type = mimetypes[i].split('/')[1];
        if (type === extension || (type === 'jpeg' && extension === 'jpg')) {
            return true;
        }
    }
    return false;
}

var addProgressBar = function (form, modificationHander) {

    // 检查表单是否存在
    if (!(form = ADS.$(form))) {
        return false;
    }

    // 查找所有文件输入元素
    var allInputs = form.getElementsByTagName('input');
    var input;
    var fileInputs = [];
    for (var i = 0; (input = allInputs[i]); i++) {
        if (input.getAttribute('type') === 'file') {
            fileInputs.push(input);
        }
    }

    // 如果没有文件输入元素则停止脚本
    if (!fileInputs.length) {
        return false;
    }

    // 添加change事件以基于MIME类型验证扩展名
    for (var j = 0, len = fileInputs[i]; i < len; j++) {
        // 使用change事件侦听器进行文件类型检查
        ADS.addEvent(fileInputs[i], 'change', function (event) {
            var ok = verifyFileType(this);
            if (!ok) {
                if (!ADS.hasClassName(this, 'error')) {
                    ADS.addClassName(this, 'error');
                }
                alert('Sorru, that file type is not allowed.Please select one of:' + this.accept.toLowerCase());
            } else {
                ADS.removeClassName(this, 'error');
            }
        });
    }

    // 为上传而附加iframe元素
    // 在IE中,不能像下面这样通过DOM设置name属性,例如:
    // var uploadTargetFrame = document.createElement('iframe');
    // uploadTargetFrame.setAttribute('id', 'uploadTargetFrame');
    // uploadTargetFrame.setAttribute('name', 'uploadTargetFrame');
    // 为解决这个问题,需要创建一个div并使用其innerHTML属性
    // 从而确保在IE和其他浏览器中都能正确的设置name属性
    var uploadTargetFrame = document.createElement('div');
    uploadTargetFrame.innerHTML = '<iframe name="uploadTargetFrame" id="uploadTargetFrame"></iframe>';
    ADS.setStyleById(uploadTargetFrame, {
        'width': 0,
        'height': 0,
        'border': 0,
        'visibility': 'hidden',
        'z-index': -1
    });
    document.body.appendChild(uploadTargetFrame);

    // 将表单的target属性修改为新iframe元素
    // 这样可以避免页面重载
    form.setAttribute('target', 'uploadTargetFrame');

    // 创建一个唯一的ID以跟踪上传进度
    var uniqueID = 'A' + Math.floor(Math.random() * 1000000000000000);

    // 为APC_UPLOAD_PROGRESS键添加这个唯一ID
    // 这个字段必须添加到文件输入字段之前,以便
    // 服务器首先取得改键并触发存储进度信息的操作
    var uniqueIDField = document.createElement('input');
    uniqueIDField.setAttribute('type', 'hidden');
    uniqueIDField.setAttribute('value', uniqueID);
    uniqueIDField.setAttribute('name', 'APC_UPLOAD_PROGRESS');
    form.insertBefore(uniqueIDField, form.firstChild);

    // 创建进度条的不同部分

    // 进度条
    var progressBar = document.createElement('div');
    progressBar.className = 'progressBar';

    // 内部的背景容器
    var progressBackground = document.createElement('div');
    progressBackground.className = 'progressBackground';
    ADS.setStyle(progressBackground, {
        'height': '10px'
    });
    progressBackground.appendChild(progressBar);

    // 检查已有的定位点
    // 必须是带有progressContainer类的span元素
    var progressContainer = ADS.getElementsByClassName(
        'progressContainer',
        'div'
    )[0];

    // 如果该定位点不存在则创建一个并将其添加到表单中
    if (!progressContainer) {
        progressContainer = document.createElement('div');
        progressContainer.className = 'progressContainer';
        form.appendChild(progressContainer);
    }

    // 添加进度条的其余部分
    progressContainer.appendChild(progressBackground);

    // 同时也添加一个进度信息显示区域
    var progressMessage = document.createElement('div');
    progressMessage.className = 'progressMessage';
    progressContainer.appendChild(progressMessage);

    // 创建一个将由后面的进度监视方法使用
    // 的私有方法,以方便更新进度条和相应信息
    function updateProgressBar(percent, message, satus) {
        progressMessage.innerHTML = message;
        ADS.removeClassName(progressMessage, 'error complete waiting uploading');
        ADS.addClassName(progressMessage, satus);

        // CSS样式和className将负责指示状态
        ADS.setStyle(progressBar, {
            'width': percent
        });
    }

    // 从0%和waiting开始初始化进度条
    updateProgressBar('0%', 'Waiting for upload', 'waiting');

    // 为表单添加提交事件侦听器,用于
    // 验证表单信息和更新进度条
    ADS.addEvent(form, 'submit', function (event) {
        // 再次检查输入以确保
        // 其包含正确的扩展名
        var ok = true;
        var hasFiles = false;
        for (var i = 0, fileInput; (fileInput = fileInputs[i]); i++) {
            if (fileInput.value) {
                hasFiles = true;
            }
            if (!verifyFileType(fileInput)) {
                // 突出显示出错的文件输入元素
                if (!ADS.hasClassName(fileInput, 'error')) {
                    ADS.addClassName(fileInput, 'error');
                }
                ok = false;
            }
        }

        if (!ok || !hasFiles) {
            // 如果检查为通过则提示用户解决问题
            ADS.preventDefault(event);
            alert('Please select some valid files');
            return false;
        }

        // 通过发出警告信息来禁用表单元素
        function warning(event) {
            ADS.preventDefault(event);
            alert('There is an upload in progress. Please wait.');
        }

        for (var i = 0, input; (input = allInputs[i]); i++) {
            // input.setAttribute('disabled', 'disabled');
            ADS.addEvent(input, 'mousedown', warning);
        }

        // 创建一个函数以便在上传完成后重启表单
        // 该函数将在ajax事件侦听器内部被调用
        function clearWarnings() {
            // 从表单元素移除警告侦听器
            for (var i = 0, input; (input = allInputs[i]); i++) {
                ADS.removeEvent(input, 'mousedown', warning);
            }

            // 以新ID数值更新原ID和表单
            // 以确保下次上传不影响本次上传
            uniqueID = Math.floor(Math.random() * 100000000000000);
            uniqueIDFiled.setAttribute('value', uniqueID);
        }

        // 更新进度条
        updateProgressBar('0%', 'Waiting for upload', 'waiting');

        // 为模拟脚本设置计数器
        var counter = 0;

        // 创建一个新方法以触发一次新的进度请求
        var progressWatcher = function () {
            // 使用唯一键来请求进度信息
            ADS.ajaxRequest(form.action + (form.action.indexOf('?') === -1 ? '?' : '&') + 'key=' + uniqueID + '&sim=' + (++counter), {
                // 服务器端脚本将返回适当的头部信息
                // 因此我们可以使用JSON侦听器
                jsonResponseListener: function (response) {
                    // 检测响应以确认服务器端
                    // 脚本中是否存在错误

                    if (!response) {
                        // 没有有效的响应
                        updateProgressBar(
                            '0%',
                            'Invalid response from progress watcher',
                            'error'
                        );
                        // 请求完成故清除警告提示
                        clearWarnings();
                    } else if (response.error) {
                        // 服务器端报告了错误
                        updateProgressBar('0%', response.error, 'error');
                        // 请求完成故清除警告提示
                        clearWarnings();
                    } else if (response.done === 1) {
                        // POST请求已经完成
                        updateProgressBar('100%', 'Upload complete', 'complete');
                        // 请求完成故清除警告提示
                        clearWarnings();
                        // 为提供更改处理程序的
                        // 用户传递新信息
                        if (modificationHander.constructor === Function) {
                            modificationHander(response);
                        }
                    } else {
                        // 更新进度条并返回结果
                        // 由于结果是null, 所以
                        // 返回会简单地停止执行
                        // 方法中其余的代码
                        updateProgressBar(
                            Math.round(response.current / response.total * 100) + '&',
                            response.current + 'of'
                                + response.total + '. Uploading file: '
                                + response.currentFileName,
                            'uploading'
                        );

                        // 再次执行进度监视程序
                        setTimeout(progressWatcher, 1000);
                    }
                },
                errorListener: function () {
                    // ajax请求发生了错误
                    // 因此需要让用户知道
                    updateProgressBar('0%', this.status, 'error');

                    // 并清除警告提示
                    clearWarnings();
                }
            });
        };

        // 开始监视
        setTimeout(progressWatcher, 1000);

    });
};




 
// 主页
index.php:

 <?php

// 循环遍历uploads文件夹
// 以便取得已经上传的文件
$uploads = new DirectoryIterator('./uploads');
$files=array();
foreach($uploads as $file) {
    // 跳过,并。。。
    if(!$file->isDot() && $file->isFile()) {
        // 添加到数组,。稍后,该数组
        // 将在HTML中被连接起来
        $files[]=sprintf(
            '<li><a href="uploads/%s">%s</a> <em>%skb</em></li>',
            $file->getFilename(),
            $file->getFilename(),
            round($file->getSize()/1024)
        );
    }
}

// 输出页面
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <title>Image Uploader with Progress (php5-APC)</title>

    <!-- Inclue some CSS style sheet to make
    everything look a little nicer -->
    <link rel="stylesheet"
          href="http://www.cnblogs.com/../shared/source.css" />
    <link rel="stylesheet"
          href="http://www.cnblogs.com/chapter.css" />
    <link rel="stylesheet" href="style.css" />

    <!-- Your ADS library with the common JavaScript objects -->
    <script src="http://www.cnblogs.com/../ADS.js"></script>

    <!-- Progress bar script -->
    <script src="uploader.js"></script>

    <!-- load script -->
    <script src="load.js"></script>

</head>
<body>
<h1>Image Uploader with Progress (php5-APC)</h1>
<div id="content">
    <form action="actions/" enctype="multipart/form-data"
          method="post" id="uploadForm">

        <fieldset>
            <legend>Upload a new image</legend>
            <p>Only jpg/gif/png files less than 100kb allowed.</p>
            <div class="fileSelector">
                <label for="newFile1">File 1</label>
                <input type="file" id="newFile1" name="newFile1"
                       accept="image/jpeg,image/gif,image/png"/>
            </div>
            <div class="fileSelector">
                <label for="newFile2">File 2</label>
                <input type="file" id="newFile2" name="newFile2"
                       accept="image/jpeg,image/gif,image/png"/>
            </div>
            <div class="fileSelector">
                <label for="newFile3">File 3</label>
                <input type="file" id="newFile3" name="newFile3"
                       accept="image/jpeg,image/gif,image/png"/>
            </div>
            <input id="submitUpload" name="submitUpload"
                   type="submit" value="Upload Files" />
        </fieldset>

    </form>

    <div id="browserPane">
        <h2>
                <span id="fileCount">
                    <?php echo count($files); ?>
                </span>
            Existing Files in <em>uploads/</em>
        </h2>
        <ul id="fileList">
            <?php echo join($files,"\n\t\t\t\t"); ?>
        </ul>
    </div>
</div>

<div id="where-from">
    From <a href="http://advanceddomscripting.com" title="AdvancED DOM Scripting">AdvancED DOM Scripting</a>
    | <a href="http://www.amazon.com/exec/obidos/ASIN/1590598563/jeffreysamb05-20" title="Buy it on Amazon">Paperback</a>
</div>
</body>

</html>


// 表单action指向的php文件
action/index.php: 
 <?php

//check if the request is using the ADS.ajaxRequest() method.
if($_SERVER['HTTP_X_ADS_AJAX_REQUEST']) {
    
    // Return the progress information as a JSON string
    
    // Send some headers to prevent caching of the progress request
    header('Expires: Fri, 13 Dec 1901 20:45:54 GMT') ;
    header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT') ;
    header('Cache-Control: no-store, no-cache, must-revalidate') ;
    header('Cache-Control: post-check=0, pre-check=0', false) ;
    header('Pragma: no-cache') ;
    
    // This will be a JavaScript JSON response
    header('Content-Type:application/json; charset=utf-8' ) ;

    // Retrieve the status using the getStatus() function below    
    echo json_encode(getStatusAPC($_GET['key']));
    
    die();
} 

// Process any files in the PHP $_FILES[] array and return to the
// main script if everything went well. Otherwise we'll display a
// error page.

$allowedExtensions = array('jpg','jpeg','gif','png');
$errorMessage = null ;
$storedFiles = array();


if(count($_FILES) > 0) {
    
    try {
        
        // Process each file
        foreach($_FILES as $key=>$info) {
            if($_FILES[$key]['name']) {
                // storeFile() throws exceptions
                $file = storeFile($key,'../uploads/',$allowedExtensions);
            } else {
                $file = null;
            }

            // Keep track of stored files incase you need to
            // remove them.
            $storedFiles[$key] = $file['basename'];
        }

        if($_POST['APC_UPLOAD_PROGRESS'] && function_exists('apc_store')) {
        
            // Store the file information so it can be 
            // retrieved in the progress watcher            
            apc_store('upload_finished_'.$_POST['APC_UPLOAD_PROGRESS'],$storedFiles);

            // Die. This message will display in the iframe                
            die('Upload complete.');
            
        }
        
        // Everything was successful so redirect back 
        // to the main index page
        header('Location: ../');
        die();
        
    } catch (Exception $e) {
        // There was an error so remove any files that were uploaded
        foreach($storedFiles as $file) {
            if(is_file($file)) unlink('uploads/'.$file);
        }
        $storedFiles = array();



        if($_POST['APC_UPLOAD_PROGRESS'] && function_exists('apc_store')) {
            
            // Store the error message so it can be 
            // retrieved in the progress watcher
            apc_store(
                'upload_error_'.$_POST['APC_UPLOAD_PROGRESS'],
                $e->getMessage()
            );
            
            // Die. This message will display in the iframe
            die('There was an error');
        
        } else {
        
            // Get the error message
            $errorMessage = sprintf(
                '<p>%s failed: %s</p>', 
                $key, 
                $e->getMessage()
            );
            
            // Display a simple error page with a 
            // link back to the main index file
            echo 
<<<XHTML
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <title>Oops!</title>
</head>
<body>
    <h1>Error</h1>
    <p>The system reported an error with the
    file(s) you were trying to upload:</p>
    {$errorMessage}
    <p><a href="../">Return to the upload page</a></p>

<div id="where-from">
    From <a href="http://advanceddomscripting.com" title="AdvancED DOM Scripting">AdvancED DOM Scripting</a> 
    | <a href="http://www.amazon.com/exec/obidos/ASIN/1590598563/jeffreysamb05-20" title="Buy it on Amazon">Paperback</a>
</div>
</body>

</html>
XHTML;
        }
    }    
}


/**
 * Store a file uploaded through HTTP on the server
 *
 * This function will access the global $_FILES array to retrieve the 
 * information:
 *
 * The original name of the file on the client machine.
 * $_FILES['userfile']['name']
 *
 * The mime type of the file, if the browser provided this information.
 * An example would be 'image/gif'. This mime type is however not checked
 * on the PHP side and therefore don't take its value for granted.
 * $_FILES['userfile']['type']
 *
 * The size, in bytes, of the uploaded file.
 * $_FILES['userfile']['size']
 *
 * The temporary filename of the file in which the uploaded file was stored on the server.
 * $_FILES['userfile']['tmp_name']
 *
 * The error code  associated with this file upload.
 * This element was added in PHP 4.2.0
 * $_FILES['userfile']['error']
 *
 * @param string $key The key in $_FILES that represents the file you wish to 
 * store. This is generally the name attribute from the form.
 * @param string $where The directory on the server where you wish to store 
 * the file. This can be absolute or relative to the location of execution.
 * @param array $extensions An array of acceptable extensions. (white list)
 * @param int $maxBytes The maximum number of bytes
 * @return array
 */
function storeFile($key,$where,$extensions,$maxBytes=null) {

    try {
        
        // Check for the file
        if(!$_FILES[$key]) {
            throw new Exception('The specified key does not exist in the $_FILES array');
        }

        // Check the uplod location 
        if(!$where) {
            throw new Exception('Upload location not specified. If the current directory is desired, use "."');
        } elseif ($where[strlen($where)-1] != DIRECTORY_SEPARATOR) {
            $where .= DIRECTORY_SEPARATOR;
        }
        
        // Check for permissions
        if(!is_writeable($where)) {
            throw new Exception('This page can not access the specified upload directory.');
        }

        // convert the extensions to an array
        // (if a single extension as a string was supplied)
        settype($extensions,'array');
        
        //check for extensions
        if(count($extensions) == 0) {
            throw new Exception('No valid extensions were specified.');
        }
        

        // Convert ini to bytes and store in maxBytes if required
        $maxBytes = ($maxBytes ? $maxBytes  : preg_replace_callback(
        '/([0-9]+)([gmk])/i',
        'toBytes',
        ini_get('upload_max_filesize')
        ));

        // check PHP upload errors        
        switch ($_FILES[$key]['error']) {
            case UPLOAD_ERR_OK:
                // everything was fine. Proceed
                break;
            case UPLOAD_ERR_INI_SIZE:
                throw new Exception('The uploaded file exceeds the upload_max_filesize directive ('.ini_get('upload_max_filesize').') in php.ini.');
                break;
            case UPLOAD_ERR_FORM_SIZE:
                throw new Exception('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form.');
                break;
            case UPLOAD_ERR_PARTIAL:
                throw new Exception('The uploaded file was only partially uploaded.');
                break;
            case UPLOAD_ERR_NO_FILE:
                throw new Exception('No file was uploaded.');
                break;
            case UPLOAD_ERR_NO_TMP_DIR:
                throw new Exception('Missing a temporary folder.');
                break;
            case UPLOAD_ERR_CANT_WRITE:
                throw new Exception('Failed to write file to disk');
                break;
            default:
                throw new Exception('Unknown PHP File Error');
        }

        // Check if the files size is greater than the 
        // file size in the arguments
        if($_FILES['userfile']['size'] > $maxBytes) {
            throw new Exception('The uploaded file exceeds the maximum size specified in the application.');
        }

        // Sanitize the file name
        $cleanName = str_replace(' ','-',$_FILES[$key]['name']);
        $cleanName = preg_replace('/-+/','-',$cleanName);
        $cleanName = preg_replace('/[^a-z0-9_.\/-]/i','',$cleanName);
        $fileNameParts = pathinfo($cleanName);

        // Verify the sanitized name is good
        $fileNameParts['filename'] = str_replace('.','_',$fileNameParts['filename']);
        if(!$fileNameParts['filename']) {
            throw new Exception('The desired file name contains no valid characters.');
        }

        // Verify the extension is valid
        $fileNameParts['extension'] = strtolower($fileNameParts['extension']);
        if(!in_array($fileNameParts['extension'], $extensions)) {
            throw new Exception('The file extension is not one of: '.join($extensions,', '));
        }

        // Postfix the file with a counter to avoid duplicates
        $count = 0;
        $postfix = '';
        while(file_exists($uploadLocation = $where.$fileNameParts['filename'].$postfix.'.'.$fileNameParts['extension'])) {
            $postfix = '-'.++$count;
        }

        // Move the upload into place
        if(!move_uploaded_file($_FILES[$key]['tmp_name'], $uploadLocation)) {
            throw new Exception('Failed to move uploaded tmp file.');
        }

    } catch (Exception $e) {
        // Catch exceptions for garbage collection and error storage

        // Remove the temp file
        if($_FILES[$key] && is_uploaded_file($_FILES[$key]['tmp_name']))  {
            @unlink($_FILES[$key]['tmp_name']);
        }

        // Throw the exception again for developers to catch
        throw $e;
    }

    // Return the information about the new file using pathinfo
    $return = pathinfo($uploadLocation);
    $return['rawname'] = basename($_FILES[$key]['name']);
    return $return;
}

function toBytes($matches) {
    switch(strtolower($matches[2])) {
        case "k": return $matches[1] * 1024; break;
        case "m": return $matches[1] * 1048576; break;
        case "g": return $matches[1] * 1073741824; break;
    }
}


/**
 * PHP 5.2 has a new set of hooks for checking the progress of a file upload
 * with APC 3.5
 *
 * http://viewcvs.php.net/viewvc.cgi/pecl/apc/INSTALL?revision=3.53&view=markup
 * 
 * apc.rfc1867
 * RFC1867 File Upload Progress hook handler is only available
 * if you compiled APC against PHP 5.2.0 or later.  When enabled
 * any file uploads which includes a field called
 * APC_UPLOAD_PROGRESS before the file field in an upload form
 * will cause APC to automatically create an upload_
 * user cache entry where  is the value of the
 * APC_UPLOAD_PROGRESS form entry.
 * (Default: 0)
 * 
 */
function getStatusAPC($key) {
    
    $response = false;
    
    // will return false if not found
    if($status = apc_fetch('upload_'.$_GET['key'])) {
        /*
        status {
        "total":2676099,
        "current":102685,
        "filename":"test_large.jpg",
        "name":"test_file",
        "done":0
        }
        */
        
        $response = array(
        'total' => $status['total'],
        'current' => $status['current'],
        'currentFileName' => $status['filename'],
        'currentFieldName' => $status['name'],
        'filesProcessed' => null,
        'error' => null,
        'done' => $status['done'],
        'debug'=>null
        );

        if($message = apc_fetch('upload_error_'.$_GET['key'])) {

            $response['error'] = $message;
            $response['debug'] = 'There was an error';

        } else if ($status['done']==1 && ($filesProcessed = apc_fetch('upload_finished_'.$_GET['key']))) {
            //wait until the last file processed matches the one in status
            $response['debug'] = 'Files were processed ';
            if(($last = array_pop(array_keys(((array)$filesProcessed)))) == $status['name']) {
                $response['filesProcessed'] = $filesProcessed;
                $response['debug'] .= ' - all';
            } else {
                // Override the done state because the upload 
                // has finished but the server is still processing
                // the files
                $response['done']= 0;
                $response['debug'] .= " - \"$last\" != \"{$status['name']}\"";
            }
        }

    }

    return $response;    
}

?>
原文地址:https://www.cnblogs.com/webFrontDev/p/2926138.html