精华内容
下载资源
问答
  • 上传文件慢
    2022-06-28 18:23:08

    原因

    • 带宽
    接收文件慢
    	如果是服务端上传,那么服务器要接收文件
    	服务器带宽过小,接收文件就很慢
    
    上传至三方慢(例:oss)
    	如果是公网调用上传,速度不如内网调用
    	前提是服务器与文件服务器在同一区域内(oss)
    

    思考的几个方案

    • 后端上传
    加大服务器带宽
    保证接收文件快
    使用按量付费的带宽更好,因为可设置带宽为100M(阿里云ECS)
    用固定带宽,一是带宽低,二是高带宽很贵
    通过内网调用上传接口
    
    • 前端上传
    速度其实和后端上传差不多
    虽然没有接收文件这一说,但是是公网上传,也是慢
    
    • 压缩包上传:不采用,大文件压缩还是那么大
    • 转base64:还是太大的问题,转过去几百万的字符
    • 分片上传:单/多线程速度都是和正常后端上传一样

    看场景

    如果像小红书那种发笔记
    可以做进度条,断点续传
    让用户看得到上传的进度
    这样即使慢,用户也能接受
    ---
    而如果像没必要的地方,就直接loading一直转圈圈就好
    
    更多相关内容
  • 上传文件慢,SpringBoot分片上传文件

    千次阅读 2021-04-08 14:54:12
    上传文件慢,大文件上传卡顿,请求超时怎么办? 话不多说直接上代码,代码复制过去可以直接使用 第一步:创建后端代码 package cn.leon.demo.rest; import lombok.extern.slf4j.Slf4j; import org.apache....

    Java上传文件慢,大文件上传卡顿,请求超时怎么办?

    话不多说直接上代码,代码复制过去可以直接使用

    第一步:创建后端代码

    package cn.leon.demo.rest;
    
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.io.FileUtils;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.web.bind.annotation.*;
    import org.springframework.web.multipart.MultipartFile;
    
    import java.io.File;
    import java.io.FileOutputStream;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.UUID;
    
    /**
     * 分片上传文件相关接口
     *
     * @author leon
     * @date 2021/03/19 17:40:06
     */
    @Slf4j
    @RequestMapping("/chunk-upload")
    @RestController
    public class UploadFileController {
    
    
        /**
         * 文件上传路径,配置文件配置或者这里写死也行
         * ##fileUploadPath
         * file.upload.path=/Users/leon/Desktop
         **/
        @Value("${file.upload.path}")
        private String fileUploadPath;
    
    
        /**
         * 分片上传小文件
         *
         * @param clientId 客户端ID,每个客户端每次上传时生成,保持唯一
         * @param chunkId  分片ID,从0开始累加,每次上保持传唯一
         * @param chunks   分片总数
         * @param file
         * @return java.lang.String
         * @author leon
         * @date 2021/04/07 17:16:59
         */
        @CrossOrigin
        @PostMapping("/part")
        public Result bigFile(
                MultipartFile file, 
                @RequestParam(name = "clientId", required = true) String clientId, 
                @RequestParam(name = "chunks", required = true) Integer chunks, 
                @RequestParam(name = "chunkId", required = true) Integer chunkId) throws Exception {
            log.info("开始上传分片文件,客户端ID:{},总分片数:{},分片ID:{}", clientId, chunks, chunkId);
            // 文件存放目录:临时目录用来存放所有分片文件
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
            String dateStr = sdf.format(new Date());
            //临时文件目录
            String tempFileDir = fileUploadPath + File.separator + dateStr + clientId;
            File parentFileDir = new File(tempFileDir);
            if (!parentFileDir.exists()) {
                parentFileDir.mkdirs();
            }
            // 分片处理时,前台会多次调用上传接口,每次都会上传文件的一部分到后台
            File tempPartFile = new File(parentFileDir, clientId + "_" + chunkId + ".part");
            FileUtils.copyInputStreamToFile(file.getInputStream(), tempPartFile);
            log.info("分片文件上传成功,分片ID:{}", chunkId);
            return "ok";
        }
    
        /**
         * 上传分片文件完成后合并成一个大文件
         *
         * @param clientId 客户端ID,每次上传时生成和分片上传时参数保持一致
         * @param fileName 原文件名
         * @return java.lang.String 返回最终保存文件路径
         * @author leon
         * @date 2021/04/07 17:13:46
         */
        @CrossOrigin
        @PostMapping("/merge")
        public String mergeFile(
                @RequestParam(name = "clientId", required = true) String clientId, 
                @RequestParam(name = "fileName", required = true) String fileName) throws Exception {
            log.info("开始合并文件,客户端ID:{},文件名:{}", clientId, fileName);
            // 文件存放目录
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
            String dateStr = sdf.format(new Date());
            //最终文件上传目录
            String fileSavePath = fileUploadPath + File.separator + dateStr;
            //临时文件目录
            String tmpFileSavePath = fileSavePath + clientId;
            //最终文件上传文件名
            String newFileName = UUID.randomUUID().toString();
            if (fileName.indexOf(".") != -1) {
                newFileName += fileName.substring(fileName.lastIndexOf("."));
            }
            //创建父文件夹
            File parentFileDir = new File(tmpFileSavePath);
            if (parentFileDir.isDirectory()) {
                File destNewFile = new File(fileSavePath, newFileName);
                if (!destNewFile.exists()) {
                    //先得到文件的上级目录,并创建上级目录,再创建文件
                    destNewFile.getParentFile().mkdir();
                    destNewFile.createNewFile();
                }
                //遍历"所有分片文件"到"最终文件"中,此处一定要按照顺序合并文件,不然会导致文件合并错乱不可用
                for (int i=0;i<parentFileDir.listFiles().length;i++) {
                    FileOutputStream destNewFileFos = new FileOutputStream(destNewFile, true);
                    FileUtils.copyFile(new File(parentFileDir, clientId + "_" + i + ".part"), destNewFileFos);
                    destNewFileFos.close();
                }
                // 删除临时目录中的分片文件
                FileUtils.deleteDirectory(parentFileDir);
            }
            log.info("合并文件完成,客户端ID:{},文件名:{}", clientId, fileName);
            return fileSavePath + newFileName;
        }
    
    }
    
    

    第二步:创建前端代码测试

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <title>分片上传文件测试</title>
        <script src="http://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
        <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
    </head>
    <body>
    <div id="uploader">
        <div class="btns">
            <input id="file" name="file" type="file"/>
            <br>
            <br>
            <button id="startBtn">
                开始上传
            </button>
            </br>
            </br>
        </div>
        <div id="output">
        </div>
    </div>
    </body>
    <script type="text/javascript">
        var status = 0;//上传状态
        var startDate;
        var page = {
            init: function(){
                $("#startBtn").click($.proxy(this.upload, this));
            },
            upload: function(){
                startDate = (new Date()).getTime();
                console.log("开始上传文件......");
                status = 0;
                var clientId = this.generateClientId();
                var file = $("#file")[0].files[0],  //文件对象
                    fileName = file.name,        //文件名
                    size = file.size;        //总大小
                var shardSize = 1024 * 1024,    //以1MB为一个分片
                // var shardSize = 500 * 1024,    //以500kb为一个分片
                shardCount = Math.ceil(size / shardSize);  //总片数
                console.log("每个分片文件1MB,总分片数:"+shardCount);
                // console.log("每个分片文件500kb,总分片数:"+shardCount);
                for(var i = 0;i < shardCount;++i){
                    //计算每一片的起始与结束位置
                    var start = i * shardSize,
                        end = Math.min(size, start + shardSize);
                    var partFile = file.slice(start,end);
                    this.partUpload(clientId,partFile,fileName,shardCount,i);
                    console.log("第"+i+"个分片文件上传成功");
                }
                var endDate = (new Date()).getTime();
                console.log("所有分片文件上传请求发送成功,总耗时:"+(endDate-startDate)+"毫秒");
            },
            partUpload:function(clientId,partFile,fileName,chunks,chunkId){
                //构造一个表单,FormData是HTML5新增的
                var  now = this;
                var form = new FormData();
                form.append("clientId", clientId);
                form.append("file", partFile);  //slice方法用于切出文件的一部分
                form.append("fileName", fileName);
                form.append("chunks", chunks);  //总片数
                form.append("chunkId", chunkId);        //当前是第几片
                //Ajax提交
                $.ajax({
                    url: "http://localhost:8080/chunk-upload/part",
                    type: "POST",
                    data: form,
                    async: true,        //异步
                    processData: false,  //很重要,告诉jquery不要对form进行处理
                    contentType: false,  //很重要,指定为false才能形成正确的Content-Type
                    success: function(data){
                        status++;
                        if(data.code == 0){
                            $("#output").html(status+ " / " + chunks);
                        }else{
                            alert('出现异常:'+data.message);
                        }
                        if(status==chunks){
                            var endDate = (new Date()).getTime();
                            console.log("所有分片文件上传成功,总耗时:"+(endDate-startDate)+"毫秒")
                            now.mergeFile(clientId,fileName);
                        }
                    }
                });
            },
            mergeFile:function(clientId,fileName){
                var formMerge = new FormData();
                formMerge.append("clientId", clientId);
                formMerge.append("fileName", fileName);
                $.ajax({
                    url: "http://localhost:8080/chunk-upload/merge",
                    type: "POST",
                    data: formMerge,
                    processData: false,  //很重要,告诉jquery不要对form进行处理
                    contentType: false,  //很重要,指定为false才能形成正确的Content-Types
                    success: function(data){
                        if(data.code == 0){
                            var endDate = (new Date()).getTime();
                            console.log("上传文件成功,总耗时:"+(endDate-startDate)+"毫秒");
                            alert('上传成功!');
                        }else{
                            alert('出现异常:'+data.message);
                        }
                    }
                });
            },
            generateClientId:function(){
                var counter = 0;
                var clientId = (+new Date()).toString( 32 ),
                    i = 0;
                for ( ; i < 5; i++ ) {
                    clientId += Math.floor( Math.random() * 65535 ).toString( 32 );
                }
                return clientId + (counter++).toString( 32 );
            }
        };
    
        $(function(){
            page.init();
        });
    </script>
    </html>

     

    展开全文
  • 主要介绍了Java实现ftp文件上传下载解决中文乱码多个文件下载等问题的相关资料,非常不错具有参考借鉴价值,需要的朋友可以参考下
  • 客户反应文件上传速度,需要对文件服务器的上传速度进行优化。拿到这个任务后首先是使用现有代码对文件服务器进行了上传下载速度的测试。测试的文件服务器是seaweedfs和fastdfs,抛开网络的问题测试结果如下: 1...

    1、出现问题

    客户反应文件上传速度慢,需要对文件服务器的上传速度进行优化。拿到这个任务后首先是使用现有代码对文件服务器进行了上传下载速度的测试。测试的文件服务器是seaweedfs和fastdfs,抛开网络的问题测试结果如下:

    1)上传2M文件

    seaweedfs:14s

    fastdfs:90ms

    2)下载2M文件

    seaweedfs:90M/s

    fastdfs:30M/s

    为什么???按照道理上传不应该会这么慢,按照道理seaweedfs的性能应该不会低于其他服务器那么多,肯定是哪里有问题。

     

    2、问题排查

    我们seaweedfs的组网是一个master 两个 volume ,上传方式是通过master的9333端口。seaweedfs文件服务器的原理是通过master选择节点上传到对应节点上。但是为什么这么慢,我们首先进行了TCP抓包,分别抓取了三个节点的端口数据流,结果如下(这里一个是早上抓的,一个是下午抓的,中午吃了饭睡了觉):

    1)master

    分析:上传时间和结束时间很明显,master确实处理了这么长时间。但大部分时间是在消耗在assign之前。

    2)volume1

    分析:文件真正上传存储完成的时间很短,问题不出在volume,但问题在哪儿呢,摸着石头过河只能下载源码来分析了。

    3)源码分析,经过一番折腾我们找到了如下源码:

    func submitForClientHandler(w http.ResponseWriter, r *http.Request, masterUrl string, grpcDialOption grpc.DialOption) {
    	m := make(map[string]interface{})
    	if r.Method != "POST" {
    		writeJsonError(w, r, http.StatusMethodNotAllowed, errors.New("Only submit via POST!"))
    		return
    	}
    
    	debug("parsing upload file...")
    	fname, data, mimeType, pairMap, isGzipped, originalDataSize, lastModified, _, _, pe := needle.ParseUpload(r)
    	if pe != nil {
    		writeJsonError(w, r, http.StatusBadRequest, pe)
    		return
    	}
    
    	debug("assigning file id for", fname)
    	r.ParseForm()
    	count := uint64(1)
    	if r.FormValue("count") != "" {
    		count, pe = strconv.ParseUint(r.FormValue("count"), 10, 32)
    		if pe != nil {
    			writeJsonError(w, r, http.StatusBadRequest, pe)
    			return
    		}
    	}
    	ar := &operation.VolumeAssignRequest{
    		Count:       count,
    		Replication: r.FormValue("replication"),
    		Collection:  r.FormValue("collection"),
    		Ttl:         r.FormValue("ttl"),
    	}
    	assignResult, ae := operation.Assign(masterUrl, grpcDialOption, ar)
    	if ae != nil {
    		writeJsonError(w, r, http.StatusInternalServerError, ae)
    		return
    	}
    
    	url := "http://" + assignResult.Url + "/" + assignResult.Fid
    	if lastModified != 0 {
    		url = url + "?ts=" + strconv.FormatUint(lastModified, 10)
    	}

     

    注意到了 url := "http://" + assignResult.Url + "/" + assignResult.Fid 这行代码,就是我们抓包时看到的耗时后调用的接口。这个方法是submit的业务逻辑入口,那问题就在这个方法开始到上边提到的这一行。我们看看其中有什么可能导致速度慢的,翻阅了前边的代码,没有什么耗时的网络请求,可疑点就落在了参数解析上,文件越大耗时越长,是不是参数解析工具将文件当作参数来解析了呢?

     

    4)我们又进行了指令上传测试

    通过curl走9333端口对文件进行上传。结果令人诧异,2M文件的上传速度尽然变成了100多ms。问题基本能够确定在java提交数据到文件服务的时候,文件服务将文件当作参数解析了一遍。(时间问题具体的原因就没有深究,不过让人怀疑r.ParseForm()这个方法)

     

    5)看java代码

    使用的是各大搜索引擎搜索出来的一个java调用seaweedfs上传文件的一个示例:

    
    httpClient1 = HttpClients.createDefault();
    HttpPost httpPost = new HttpPost("http://IP:9333/submit");
    httpPost.setHeader(new BasicHeader("Accept-Language", "zh-cn"));
    File file = new File("/xxxx.txt");
    FileBody bin = new FileBody(file);
    HttpEntity reqEntity = MultipartEntityBuilder.create().setCharset(Charset.forName("UTF-8")).setMode(HttpMultipartMode.BROWSER_COMPATIBLE).addBinaryBody(URLEncoder.encode("xxxx.txt"), file).build();
    httpPost.setEntity(reqEntity);
    System.out.println(httpPost.toString());
    response1 = httpClient1.execute(httpPost);

    这段代码有问题么?没有问题,但是结合了go语言的http参数解析放到一起就出问题了。

     

    3、问题解决

    更换问文件上传方式,先调用master的assign接口,获取文件id及volume节点,然后直接上传文件到volume节点,代替submit接口做这两件事。

    修改代码后,测试ok,2M文件上传时间测试180ms左右。

    (也可以直接使用seaweedfs-java-client来做这件事情,可以减少很多编码工作)

     

    4、总结

    很多时候从软件自身来看确实不存在任何问题,但可能两个或多个软件组合成系统以后就会出现很多奇奇怪怪的问题,需要去调试、检测,最终才能成为一个稳定的系统。

    展开全文
  • 本篇文章主要说明的是与php文件上传的相关配置的知识点。PHP文件上传功能配置主要涉及php.ini配置文件中的upload_tmp_dir、upload_max_filesize、post_max_size等选项,下面一一说明。 打开php.ini配置文件找到File...
  • java上传文件慢处理方案

    千次阅读 2019-06-05 17:28:55
    我们平时经常做的是上传文件上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。 首先我们需要了解的是上传文件三要素: 1.表单提交方式:post(get方式提交有大小限制,post...

    我们平时经常做的是上传文件,上传文件夹与上传文件类似,但也有一些不同之处,这次做了上传文件夹就记录下以备后用。

     

    首先我们需要了解的是上传文件三要素:

    1.表单提交方式:post (get方式提交有大小限制,post没有)

    2.表单的enctype属性:必须设置为multipart/form-data. 

    3.表单必须有文件上传项:file,且文件项需要给定name值

     

    上传文件夹需要增加一个属性webkitdirectory,像这样:

    <input id="fileFolder" name="fileFolder" type="file"  webkitdirectory>

     

    上传控件功能:

    1. 文件批量上传

    此文件管理器支持文件批量上传。您可以上传30G及以上的大型文件,在上传过程中您不需要担心刷新网页造成的进度丢失问题。也不需要担心浏览器重启或崩溃,电脑重启等极端应用场景造成的进度丢失问题。文件管理器能够自动定时保存文件上传进度。以便为您提供更好的用户体验。

    2. 文件夹批量上传

    此文件管理器提供文件夹的批量上传功能。您可以同时上传一个或者多个文件夹,文件管理器会将这些文件夹以及他们的结构信息同时保存在服务器中。您不需要担心浏览器重启或崩溃造成的进度丢失的问题。

    3. 文件批量下载

    此文件管理器提供了文件批量下载功能。您现在可以同时下载多个文件,并将他们保存在本地指定的目录中。这一功能为图库应用场景,多资料共享应用场景提供了使用便利。

    4. 文件夹批量下载

    此文件管理器提供了文件夹的批量下载功能。您可以同时下载多个文件夹,这些文件夹下载完毕后,他们的层级信息也将会同时在本地保留。

    5. 新建目录

    此文件管理器提供了多层级目录管理功能。您现在可以根据需求新建目录。

    6. 文件目录重命名。

    7. 树型目录导航

    8. 路径导航

    9. 开源

    此文件管理器是开款开源产品,无论您是个人还是企业都可以获取他的源代码来进行二次开发。我们为ASP.NET,JAVA,PHP等语言提供了示例,您可以同时选择这3种开发语言来进行项目开发。同时我们将提供长期的更新和维护服务,帮助企业节省后期的产品维护成本。

     

    以下是部分前端脚本代码:

    //文件上传对象
    
    function FileUploader(fileLoc, mgr)
    
    {
    
        var _this = this;
    
        this.id = fileLoc.id;
    
        this.ui = { msg: null, process: null, percent: null, btn: { del: null, cancel: null,post:null,stop:null }, div: null};
    
        this.isFolder = false; //不是文件夹
    
        this.app = mgr.app;
    
        this.Manager = mgr; //上传管理器指针
    
        this.event = mgr.event;
    
        this.FileListMgr = mgr.FileListMgr;//文件列表管理器
    
        this.Config = mgr.Config;
    
        this.fields = jQuery.extend({}, mgr.Config.Fields, fileLoc.fields);//每一个对象自带一个fields幅本
    
        this.State = this.Config.state.None;
    
        this.uid = this.fields.uid;
    
        this.fileSvr = {
    
              pid: ""
    
            , id: ""
    
            , pidRoot: ""
    
            , f_fdTask: false
    
            , f_fdChild: false
    
            , uid: 0
    
            , nameLoc: ""
    
            , nameSvr: ""
    
            , pathLoc: ""
    
            , pathSvr: ""
    
            , pathRel: ""
    
            , md5: ""
    
            , lenLoc: "0"
    
            , sizeLoc: ""
    
            , FilePos: "0"
    
            , lenSvr: "0"
    
            , perSvr: "0%"
    
            , complete: false
    
            , deleted: false
    
        };//json obj,服务器文件信息
    
        this.fileSvr = jQuery.extend(this.fileSvr, fileLoc);
    
    
    
        //准备
    
        this.Ready = function ()
    
        {
    
            this.ui.msg.text("正在上传队列中等待...");
    
            this.State = this.Config.state.Ready;
    
        };
    
    
    
        this.svr_error = function ()
    
        {
    
            alert("服务器返回信息为空,请检查服务器配置");
    
            this.ui.msg.text("向服务器发送MD5信息错误");
    
            this.ui.btn.cancel.text("续传");
    
        };
    
        this.svr_create = function (sv)
    
        {
    
            if (sv.value == null)
    
            {
    
                this.svr_error(); return;
    
            }
    
    
    
            var str = decodeURIComponent(sv.value);//
    
            this.fileSvr = JSON.parse(str);//
    
            //服务器已存在相同文件,且已上传完成
    
            if (this.fileSvr.complete)
    
            {
    
                this.post_complete_quick();
    
            } //服务器文件没有上传完成
    
            else
    
            {
    
                this.ui.process.css("width", this.fileSvr.perSvr);
    
                this.ui.percent.text(this.fileSvr.perSvr);
    
                this.post_file();
    
            }
    
        };
    
        this.svr_update = function () {
    
            if (this.fileSvr.lenSvr == 0) return;
    
            var param = { uid: this.fields["uid"], offset: this.fileSvr.lenSvr, lenSvr: this.fileSvr.lenSvr, perSvr: this.fileSvr.perSvr, id: this.id, time: new Date().getTime() };
    
            $.ajax({
    
                type: "GET"
    
                , dataType: 'jsonp'
    
                , jsonp: "callback" //自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名
    
                , url: this.Config["UrlProcess"]
    
                , data: param
    
                , success: function (msg) {}
    
                , error: function (req, txt, err) { alert("更新文件进度错误!" + req.responseText); }
    
                , complete: function (req, sta) { req = null; }
    
            });
    
        };
    
        this.post_process = function (json)
    
        {
    
            this.fileSvr.lenSvr = json.lenSvr;//保存上传进度
    
            this.fileSvr.perSvr = json.percent;
    
            this.ui.percent.text("("+json.percent+")");
    
            this.ui.process.css("width", json.percent);
    
            var str = json.lenPost + " " + json.speed + " " + json.time;
    
            this.ui.msg.text(str);
    
        };
    
        this.post_complete = function (json)
    
        {
    
            this.fileSvr.perSvr = "100%";
    
            this.fileSvr.complete = true;
    
            $.each(this.ui.btn, function (i, n)
    
            {
    
                n.hide();
    
            });
    
            this.ui.process.css("width", "100%");
    
            this.ui.percent.text("(100%)");
    
            this.ui.msg.text("上传完成");
    
            this.Manager.arrFilesComplete.push(this);
    
            this.State = this.Config.state.Complete;
    
            //从上传列表中删除
    
            this.Manager.RemoveQueuePost(this.fileSvr.id);
    
            //从未上传列表中删除
    
            this.Manager.RemoveQueueWait(this.fileSvr.id);
    
    
    
            var param = { md5: this.fileSvr.md5, uid: this.uid, id: this.fileSvr.id, time: new Date().getTime() };
    
    
    
            $.ajax({
    
                type: "GET"
    
                 , dataType: 'jsonp'
    
                 , jsonp: "callback" //自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名
    
                 , url: _this.Config["UrlComplete"]
    
                 , data: param
    
                 , success: function (msg)
    
                 {
    
                     _this.event.fileComplete(_this);//触发事件
    
                     _this.FileListMgr.UploadComplete(_this.fileSvr);//添加到服务器文件列表
    
                     _this.post_next();
    
                 }
    
                 , error: function (req, txt, err) { alert("文件-向服务器发送Complete信息错误!" + req.responseText); }
    
                 , complete: function (req, sta) { req = null; }
    
            });
    
        };
    
        this.post_complete_quick = function ()
    
        {
    
            this.fileSvr.perSvr = "100%";
    
            this.fileSvr.complete = true;
    
            this.ui.btn.stop.hide();
    
            this.ui.process.css("width", "100%");
    
            this.ui.percent.text("(100%)");
    
            this.ui.msg.text("服务器存在相同文件,快速上传成功。");
    
            this.Manager.arrFilesComplete.push(this);
    
            this.State = this.Config.state.Complete;
    
            //从上传列表中删除
    
            this.Manager.RemoveQueuePost(this.fileSvr.id);
    
            //从未上传列表中删除
    
            this.Manager.RemoveQueueWait(this.fileSvr.id);
    
            //添加到文件列表
    
            this.FileListMgr.UploadComplete(this.fileSvr);
    
            this.post_next();
    
            this.event.fileComplete(this);//触发事件
    
        };
    
        this.post_stoped = function (json)
    
        {
    
            this.ui.btn.post.show();
    
            this.ui.btn.del.show();
    
            this.ui.btn.cancel.hide();
    
            this.ui.btn.stop.hide();
    
            this.ui.msg.text("传输已停止....");
    
    
    
            if (this.Config.state.Ready == this.State)
    
            {
    
                this.Manager.RemoveQueue(this.fileSvr.id);
    
                this.post_next();
    
                return;
    
            }
    
            this.State = this.Config.state.Stop;
    
            //从上传列表中删除
    
            this.Manager.RemoveQueuePost(this.fileSvr.id);
    
            this.Manager.AppendQueueWait(this.fileSvr.id);//添加到未上传列表
    
            //传输下一个
    
            this.post_next();
    
        };
    
        this.post_error = function (json)
    
        {
    
            this.svr_update();
    
            this.ui.msg.text(this.Config.errCode[json.value]);
    
            this.ui.btn.stop.hide();
    
            this.ui.btn.post.show();
    
            this.ui.btn.del.show();
    
    
    
            this.State = this.Config.state.Error;
    
            //从上传列表中删除
    
            this.Manager.RemoveQueuePost(this.fileSvr.id);
    
            //添加到未上传列表
    
            this.Manager.AppendQueueWait(this.fileSvr.id);
    
            this.post_next();
    
        };
    
        this.md5_process = function (json)
    
        {
    
            var msg = "正在扫描本地文件,已完成:" + json.percent;
    
            this.ui.msg.text(msg);
    
        };
    
        this.md5_complete = function (json)
    
        {
    
            this.fileSvr.md5 = json.md5;
    
            this.ui.msg.text("MD5计算完毕,开始连接服务器...");
    
            this.event.md5Complete(this, json.md5);//biz event
    
    
    
            var loc_path = encodeURIComponent(this.fileSvr.pathLoc);
    
            var loc_len = this.fileSvr.lenLoc;
    
            var loc_size = this.fileSvr.sizeLoc;
    
            var param = jQuery.extend({}, this.fields, { md5: json.md5, id: this.fileSvr.id, lenLoc: loc_len, sizeLoc: loc_size, pathLoc: loc_path, time: new Date().getTime() });
    
    
    
            $.ajax({
    
                type: "GET"
    
                , dataType: 'jsonp'
    
                , jsonp: "callback" //自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名
    
                , url: this.Config["UrlCreate"]
    
                , data: param
    
                , success: function (sv)
    
                {
    
                    _this.svr_create(sv);
    
                }
    
                , error: function (req, txt, err)
    
                {
    
                    alert("向服务器发送MD5信息错误!" + req.responseText);
    
                    _this.ui.msg.text("向服务器发送MD5信息错误");
    
                    _this.ui.btn.del.text("续传");
    
                }
    
                , complete: function (req, sta) { req = null; }
    
            });
    
        };
    
        this.md5_error = function (json)
    
        {
    
            this.ui.msg.text(this.Config.errCode[json.value]);
    
            //文件大小超过限制,文件大小为0
    
            if ("4" == json.value
    
                 || "5" == json.value)
    
            {
    
            this.ui.btn.stop.hide();
    
            this.ui.btn.cancel.show();
    
            }
    
            else
    
            {           
    
                this.ui.btn.post.show();
    
                this.ui.btn.stop.hide();
    
            }
    
            this.State = this.Config.state.Error;
    
            //从上传列表中删除
    
            this.Manager.RemoveQueuePost(this.fileSvr.id);
    
            //添加到未上传列表
    
            this.Manager.AppendQueueWait(this.fileSvr.id);
    
    
    
            this.post_next();
    
        };
    
        this.post_next = function ()
    
        {
    
            var obj = this;
    
            setTimeout(function () { obj.Manager.PostNext(); }, 500);
    
        };
    
        this.post = function ()
    
        {
    
            this.Manager.AppendQueuePost(this.fileSvr.id);
    
            this.Manager.RemoveQueueWait(this.fileSvr.id);
    
            if (this.fileSvr.md5.length > 0)
    
            {
    
                this.post_file();
    
            }
    
            else
    
            {
    
                this.check_file();
    
            }
    
        };
    
        this.post_file = function ()
    
        {
    
            this.ui.btn.cancel.hide();
    
            this.ui.btn.stop.show();
    
            this.State = this.Config.state.Posting;//
    
            this.app.postFile({ id: this.fileSvr.id, pathLoc: this.fileSvr.pathLoc, pathSvr:this.fileSvr.pathSvr,lenSvr: this.fileSvr.lenSvr, fields: this.fields });
    
        };
    
        this.check_file = function ()
    
        {
    
            //this.ui.btn.cancel.text("停止").show();
    
            this.ui.btn.stop.show();
    
            this.ui.btn.cancel.hide();
    
            this.State = this.Config.state.MD5Working;
    
            this.app.checkFile({ id: this.fileSvr.id, pathLoc: this.fileSvr.pathLoc });
    
        };
    
        this.stop = function ()
    
        {
    
            this.ui.btn.del.hide();
    
            this.ui.btn.cancel.hide();
    
            this.ui.btn.stop.hide();
    
            this.ui.btn.post.hide();
    
            this.svr_update();
    
            this.app.stopFile({ id: this.fileSvr.id });       
    
        };
    
        //手动停止,一般在StopAll中调用
    
        this.stop_manual = function ()
    
        {
    
            if (this.Config.state.Posting == this.State)
    
            {
    
                this.svr_update();
    
            this.ui.btn.post.show();
    
            this.ui.btn.stop.hide();
    
            this.ui.btn.cancel.hide();
    
                this.ui.msg.text("传输已停止....");
    
                this.app.stopFile({ id: this.fileSvr.id ,tip:false});
    
                this.State = this.Config.state.Stop;
    
            }
    
        };
    
    
    
        //删除,一般在用户点击"删除"按钮时调用
    
        this.remove = function ()
    
        {
    
            this.Manager.del_file(this.fileSvr.id);
    
            this.app.delFile(this.fileSvr);
    
            this.ui.div.remove();
    
        };
    
    }

     

    这是后台文件块处理代码:

    <%@ page language="java" import="up6.DBFile" pageEncoding="UTF-8"%>
    
    <%@ page contentType="text/html;charset=UTF-8"%>
    
    <%@ page import="up6.FileBlockWriter" %>
    
    <%@ page import="up6.XDebug" %>
    
    <%@ page import="up6.*" %>
    
    <%@ page import="up6.biz.*" %>
    
    <%@ page import="org.apache.commons.fileupload.FileItem" %>
    
    <%@ page import="org.apache.commons.fileupload.FileItemFactory" %>
    
    <%@ page import="org.apache.commons.fileupload.FileUploadException" %>
    
    <%@ page import="org.apache.commons.fileupload.disk.DiskFileItemFactory" %>
    
    <%@ page import="org.apache.commons.fileupload.servlet.ServletFileUpload" %>
    
    <%@ page import="org.apache.commons.lang.*" %>
    
    <%@ page import="java.net.URLDecoder"%>
    
    <%@ page import="java.util.Iterator"%>
    
    <%@ page import="net.sf.json.JSONObject"%>
    
    <%@ page import="java.util.List"%>
    
    <%
    
    out.clear();
    
    /*
    
        此页面负责将文件块数据写入文件中。
    
        此页面一般由控件负责调用
    
        参数:
    
             uid
    
             idSvr
    
             md5
    
             lenSvr
    
             pathSvr
    
             RangePos
    
             fd_idSvr
    
             fd_lenSvr
    
        更新记录:
    
             2012-04-12 更新文件大小变量类型,增加对2G以上文件的支持。
    
             2012-04-18 取消更新文件上传进度信息逻辑。
    
             2012-10-25 整合更新文件进度信息功能。减少客户端的AJAX调用。
    
             2014-07-23 优化代码。
    
             2015-03-19 客户端提供pathSvr,此页面减少一次访问数据库的操作。
    
             2016-04-09 优化文件存储逻辑,增加更新文件夹进度逻辑
    
             2017-07-13 取消数据库操作
    
             2017-10-23 增加删除文件块缓存操作
    
    */
    
    //String path = request.getContextPath();
    
    //String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
    
    
    
    String uid            = request.getHeader("uid");//
    
    String id             = request.getHeader("id");
    
    String lenSvr         = request.getHeader("lenSvr");
    
    String lenLoc         = request.getHeader("lenLoc");
    
    String blockOffset    = request.getHeader("blockOffset");
    
    String blockSize  = request.getHeader("blockSize");
    
    String blockIndex = request.getHeader("blockIndex");
    
    String blockMd5       = request.getHeader("blockMd5");
    
    String complete       = request.getHeader("complete");
    
    String pathSvr        = "";
    
    
    
    //参数为空
    
    if( StringUtils.isBlank( uid )
    
        || StringUtils.isBlank( id )
    
        || StringUtils.isBlank( blockOffset ))
    
    {
    
        XDebug.Output("param is null");
    
        return;
    
    }
    
    
    
    // Check that we have a file upload request
    
    boolean isMultipart = ServletFileUpload.isMultipartContent(request);
    
    FileItemFactory factory = new DiskFileItemFactory();  
    
    ServletFileUpload upload = new ServletFileUpload(factory);
    
    List files = null;
    
    try
    
    {
    
        files = upload.parseRequest(request);
    
    }
    
    catch (FileUploadException e)
    
    {// 解析文件数据错误 
    
        out.println("read file data error:" + e.toString());
    
        return;
    
      
    
    }
    
    
    
    FileItem rangeFile = null;
    
    // 得到所有上传的文件
    
    Iterator fileItr = files.iterator();
    
    // 循环处理所有文件
    
    while (fileItr.hasNext())
    
    {
    
        // 得到当前文件
    
        rangeFile = (FileItem) fileItr.next();
    
        if(StringUtils.equals( rangeFile.getFieldName(),"pathSvr"))
    
        {
    
             pathSvr = rangeFile.getString();
    
             pathSvr = PathTool.url_decode(pathSvr);
    
        }
    
    }
    
    
    
    boolean verify = false;
    
    String msg = "";
    
    String md5Svr = "";
    
    long blockSizeSvr = rangeFile.getSize();
    
    if(!StringUtils.isBlank(blockMd5))
    
    {
    
        md5Svr = Md5Tool.fileToMD5(rangeFile.getInputStream());
    
    }
    
    
    
    verify = Integer.parseInt(blockSize) == blockSizeSvr;
    
    if(!verify)
    
    {
    
        msg = "block size error sizeSvr:" + blockSizeSvr + "sizeLoc:" + blockSize;
    
    }
    
    
    
    if(verify && !StringUtils.isBlank(blockMd5))
    
    {
    
        verify = md5Svr.equals(blockMd5);
    
        if(!verify) msg = "block md5 error";
    
    }
    
    
    
    if(verify)
    
    {
    
        //保存文件块数据
    
        FileBlockWriter res = new FileBlockWriter();
    
        //仅第一块创建
    
        if( Integer.parseInt(blockIndex)==1) res.CreateFile(pathSvr,Long.parseLong(lenLoc));
    
        res.write( Long.parseLong(blockOffset),pathSvr,rangeFile);
    
        up6_biz_event.file_post_block(id,Integer.parseInt(blockIndex));
    
       
    
        JSONObject o = new JSONObject();
    
        o.put("msg", "ok");
    
        o.put("md5", md5Svr);
    
        o.put("offset", blockOffset);//基于文件的块偏移位置
    
        msg = o.toString();
    
    }
    
    rangeFile.delete();
    
    out.write(msg);
    
    %>

     

    新建文件夹

    点击新建文件夹按钮,弹出此窗口,填写新建文件夹名称后点击确定

    页面左上角出现刚刚新建的文件夹名称

    粘贴上传

    复制文件夹、文件或图片

    在页面中选择好相应的上传目录,点击粘贴上传按钮,数据即可快速开始上传

     

    文件和文件夹批量上传上传功能,在新建文件夹目录内上传文件,选择多个文件或文件夹上传

    如果上传的是文件夹,那么左侧的文件夹内会自动添加一个子文件夹,与上传的文件夹相符并可以展开查看文件夹内文件

    在哪个目录下上传文件,文件就会存储在哪个目录下

     

    点击根目录按钮可以返回根目录

    当网络问题导致传输错误时,只需要重传出错分片,而不是整个文件。另外分片传输能够更加实时的跟踪上传进度。

     

    上传成功后打开我们的存储文件夹查看,发现自动生成了几个文件夹,打开文件夹确认上传文件成功

     

    点击文件夹后的重命名按钮

    修改文件名后点击确定

    页面左侧文件夹与页面中间的文件夹名称同时改变

     

    点击删除按钮

    点击确定后,页面中的文件消失

     

    文件及文件夹批量下载

    以下是脚本文件截图及部分代码展示:

    <%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%><%@
    
        page contentType="text/html;charset=UTF-8"%><%@
    
        page import="up7.*" %><%@
    
        page import="down3.model.*" %><%@
    
        page import="up7.model.*" %><%@
    
        page import="java.net.URLDecoder" %><%@
    
        page import="java.net.URLEncoder" %><%@
    
        page import="org.apache.commons.lang.*" %><%@
    
        page import="com.google.gson.FieldNamingPolicy" %><%@
    
        page import="com.google.gson.Gson" %><%@
    
        page import="com.google.gson.GsonBuilder" %><%@
    
        page import="com.google.gson.annotations.SerializedName" %><%@
    
        page import="java.io.*" %><%/*
    
        下载文件块,针对于合并后的文件处理
    
        相关错误:
    
             getOutputStream() has already been called for this response
    
                 解决方法参考:http://stackoverflow.com/questions/1776142/getoutputstream-has-already-been-called-for-this-response
    
        更新记录:
    
             2015-05-13 创建
    
             2017-05-06 增加业务逻辑数据,简化处理逻辑
    
             2017-05-14 优化逻辑
    
             2017-08-15 精简参数,优化代码,取消文件夹逻辑判断,取消redis访问操作
    
    */
    
    String path = request.getContextPath();
    
    String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
    
    
    
    String pathSvr        = request.getHeader("pathSvr");
    
    String blockIndex     = request.getHeader("blockIndex");//基于1
    
    String fileOffset = request.getHeader("fileOffset");
    
    String blockSize  = request.getHeader("blockSize");//逻辑块大小
    
    pathSvr              = PathTool.url_decode(pathSvr);
    
    
    
    if (        
    
             StringUtils.isEmpty(pathSvr)
    
        ||  StringUtils.isEmpty(blockIndex)
    
        )
    
    {
    
        System.out.println("pathSvr:".concat(pathSvr));
    
        System.out.println("blockIndex:".concat(blockIndex));
    
        System.out.println("blockSize:".concat(blockSize)); 
    
        System.out.println("f_down.jsp 业务逻辑参数为空。");
    
        return;
    
    }
    
    
    
    RandomAccessFile raf = new RandomAccessFile(pathSvr,"r");
    
    FileInputStream in = new FileInputStream( raf.getFD() );
    
    
    
    long dataToRead = Long.parseLong(blockSize);
    
    response.setContentType("application/x-download");
    
    response.setHeader("Pragma","No-cache"); 
    
    response.setHeader("Cache-Control","no-cache"); 
    
    response.setDateHeader("Expires", 0);
    
    response.addHeader("Content-Length",Long.toString(dataToRead) );
    
    
    
    OutputStream os = null;
    
    try
    
    {
    
        os = response.getOutputStream();
    
        long offset_begin = Long.parseLong(fileOffset);
    
        in.skip(offset_begin);//定位索引
    
       
    
        byte[] buffer = new byte[1048576];//1MB
    
        int len = 0;
    
       
    
        while( dataToRead > 0 )
    
        {
    
             len = in.read(buffer,0,Math.min(1048576,(int)dataToRead));      
    
             os.write(buffer, 0, len);
    
             os.flush();
    
             response.flushBuffer();
    
             buffer = new byte[1048576];
    
             dataToRead = dataToRead - len;
    
        }
    
        os.close();      
    
        os = null;
    
       
    
        out.clear();
    
        out = pageContext.pushBody();
    
    }
    
    catch(Exception e){response.setStatus(500);}
    
    finally
    
    {  
    
        if(os != null)
    
        {
    
             os.close();      
    
             os = null;
    
        }
    
        out.clear();
    
        out = pageContext.pushBody();
    
    }
    
    in.close();
    
    in = null;%>

     

    选择上传后的文件夹内的子目录文件或文件夹下载

     

    然后点击下载按钮,设置下载目录文件夹

    设置完成后继续点击下载按钮,页面的右下角出现了下载面板,你选择的文件已出现在目录中,然后点击全部下载,或者单个点击继续,自动加载未上传完的任务。在刷新浏览器或重启电脑后任然可以自动加载未完成的任务

     

    下载完成后打开我们设置的下载目录文件夹,发现需下载的文件或文件夹确认已下载成功,经确认文件夹内的内容与下载文件夹内容一致

    数据库记录,支持SQL、MySQL、Oracle

    控件包下载:

    cab(x86):http://t.cn/Ai9pmG8S

    cab(x64): http://t.cn/Ai9pm04B

    xpi: http://t.cn/Ai9pubUc

    crx:http://t.cn/Ai9pmrcy

    exe:http://t.cn/Ai9puobe

     

    示例下载:

    asp.net: http://t.cn/Ai9pue4A

    jsp-eclipse: http://t.cn/Ai9p3LSx

    jsp-myeclipse:http://t.cn/Ai9p3IdC

    php:http://t.cn/Ai9p3CKQ

     

    在线文档:

    asp.net-测试教程:http://t.cn/Ai9pBM3A

    jsp测试教程:http://t.cn/Ai9pB82o

    php-测试教程:http://t.cn/Ai9prHik

     

    展开全文
  • 背景: 生产的数据导入平台,突然被连续几个月被投诉,导入平台上传的性能很差,超过5M就不能上传成功。 拿到该问题的时候,进行代码查看和分析: 代码就是只有一句AddAttachment(&sUrl, &sSysFileName, &...
  • Nginx上传文件

    千次阅读 2020-04-22 23:45:06
    收到同事反馈,一个上传apk的接口,传一个180多兆的文件,一直转圈,最终提示上传文件失败 Bug所处背景 带宽为10M的云服务器,额,,,就算按照7M的带宽,如果是一个180M的文件,网络顺畅的话,大概需要xxx 项目...
  • nginx上传文件速度 Nginx上传文件全部缓存解决方案发布时间:2017-03-09来源:服务器之家下面通过文字说明给大家详解Nginx上传文件全部缓存解决方案。因为应用服务器(Jetty)里面实现了上传时写了进度条。经过缓存...
  • 记录一下我自己在开发过程中使用OSS进度条方法上传文件的原因以及解决方法。
  • Java文件上传速度很

    2021-02-28 12:00:14
    我已经构建了一个小型服务,它从...代码很简单,但速度很.它是这样的:public synchronized static Response postCommentPicture(Response response, Request request){JsonObject ret = new JsonObject();Outpu...
  • 我正在编写一个脚本,它将文件从网址通过我的服务器流式传输给用户.在目前的状态下它可以工作,但速度非常.这是相关的代码:/* Bytes per second */define('TRANSFER_CAP', 1048576);/* Hard part... stream the ...
  • FTP是一款软件来着,简单地说就是文件传输协议的意思,我们从事SEO行业,都会用到FTP软件。比方说,我们在建设网站时,会需要使用FTP软件将网站程序上传到虚拟主机或者是我们的服务器。还有,我们想要修改网站里面的...
  • 假如一个场景,用户上传文件,某些用户网速较,同时存在100个这样的用户,如果BIO且最大线程设为100会导致线程用完。NIO怎么来解决这个问题? 本实例采用tomcat8作为容器,开启异步servlet读写事件,解决高并发IO...
  • XShell上传文件到Linux服务器上

    千次阅读 2021-05-09 06:27:38
    在学习Linux过程中,我们常常需要将本地文件上传到Linux主机上,这里简单记录下使用Xsheel工具进行文件传输1:首先连接上一台Linux主机2:输入rz命令,看是否已经安装了lrzsz,如果没有安装则执行 yum -y install ...
  • Python sftp上传文件 速度过

    千次阅读 2021-02-24 10:31:23
    网络问题较大,自己用软件测试(winscp)同样很刚开始文件很少,读取目录速度没有那么,时间长了,文件变多 第一版代码 class SftpInfo(object): def __init__(self, username, password, timeout=3000): self...
  • 参考:https://www.sohu.com/a/313870678_100098135
  • VMWare ESXi上传iso镜像文件问题

    千次阅读 2019-11-28 17:25:25
    VMWare ESXi虚拟机上传iso镜像文件问题 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本...
  • 1GB以内的任何文档、图片、视频、Flash文件和音频文件都可以多文件无刷新批量上传。 1GB以内的任何文档、图片、视频、Flash文件和音频文件都可以多文件无刷新批量上传
  • 问题:windows通过jumpserver远程登录到linux服务器,使用rz上传jar包,速度太(10k以内)。解决方案:思路:通过ssh直接登录远程服务器1.secureCRT-> tools->create public key2.新建登录远程服务器的session...
  • 经过多次检查后发现出现这样问题是因为线上服务器使用nginx作反向代理,实现前后端分离,由于NGINX的默认配置,导致文件上传速度 处理方法 处理NGINX的文件上传缓存,配置如下: location ~ /fastfile { # /...
  • 当我尝试了多种代码优化后发现上传速度还是一样,这时我发现每次文件上传网络流量很奇怪。如下图: 这时排查一下nginx的跨域配置,发现后端的配置是服务器的ip,将ip修改成localhost,修改的前提是前后端的项目...
  • 文件上传之后的返回值 Content-Type值不能是application/json 这会导致IE去解析返回结果,最终调用文件的保存或者打开,此处需要与后端协商将Content-Type改为text/plain 如果需要图片回显,回显的图片路径中有有...
  • centos ssh传输文件

    2022-02-21 14:57:29
    修改配置文件/etc/ssh/sshd_config 修改“GSSAPIAuthentication”的值为“no”(没有的添加该配置选项,注释掉的放开即可) # GSSAPI options GSSAPIAuthentication no GSSAPICleanupCredentials no #...
  • ftp服务器上传文件速度多少

    千次阅读 2021-08-11 01:17:51
    ftp服务器上传文件速度多少 内容精选换一换安装传输工具在本地主机和Windows云服务器上分别安装数据传输工具,将文件上传到云服务器。例如QQ.exe。在本地主机和Windows云服务器上分别安装数据传输工具,将文件上传到...
  • 问题描述: 最近在项目中遇到一个问题,每次ftp上传图片的的速度都超级,...ftp上传文件速度主要有两方面的原因: 1.网络问题,网速限制,并且没使用缓冲流来操作,导致上传的速度比较! 2.登录ftp与ftp...
  • 通过jsch上传 部分代码 session = getSession(); session.setConfig("PreferredAuthentications","password"); session.setConfig("StrictHostKeyChecking", "no"); session.setPassword(password); ...
  • Xftp上传虚拟机文件超级(400kb/s)的解决办法

    千次阅读 热门讨论 2020-08-26 10:04:10
    最近豌豆由于需要往虚拟机中传大量大文档,以前使用的Xftp上传文件就显得超级,只有300-600kb/s,如下图 原因分析: 上csdn上面找遍了,发现全是要我整网卡的,我全整了,速度还下降不少,以前我是由1Mb/s左右的...
  • 在使用ssh服务连接本地centos7虚拟机时发现连接速度非常,传输文件也很,特将解决方法记录,以供后续参考 解决 问题原因是centos默认开启DNS,ssh连接虚拟机时,先去DNS服务器上查询了一圈,速度的原因就在于...
  • 主要介绍了Nginx 上传文件超时解决办法的相关资料,这里上传文件并设置nginx的配置文件防止超时的情况,需要的朋友可以参考下

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 118,550
精华内容 47,420
关键字:

上传文件慢