zoukankan      html  css  js  c++  java
  • 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;    
    }
    
    ?>
  • 相关阅读:
    day 26 python2和python3的区别 模块logging 的高级版,collections 模块,random模块
    常用模块:time,os,sys,rondom
    模块 hashlib(算法) configparser(配置) logging(日志)
    序列化,json pickle,shelve
    面向对象的封装,多态,单例模式
    属性,类方法,静态方法,反射
    面向对象的接口类 以及鸭子类型
    面向对象的继承
    面向对象的介绍
    reset internet explorer settings with registry
  • 原文地址:https://www.cnblogs.com/webFrontDev/p/2926138.html
Copyright © 2011-2022 走看看