掌控上传进度的AJAX Upload

【ESP32日记003】EPS32S3上使用自身WiFi热点,用于OTA升级的代码实现 最近客户突然新增一个本地OTA需求,要求能实现让用户通过手机按需对设备进行程序更新,经评估后,考虑直接使用芯片自带的WiFi功能,以实现上电后,5分钟内开启WiFi热点,用于进行OTA升级,具体实现代码如下: 阅读详情
2006年底Google了一下AJAX Upload实现,结果没有发现很完整的Java实现。硕果仅存的就是TELIO公司的Pierre-Alexandre发表的《AJAX Upload progress monitor for Commons-FileUpload Example》一文。虽然文中完成Upload工作的是Common-FileUpload组件,但在其代码中没有1.2版本所提供的Listener功能,这就对检测文件上传情况造成了困难...

掌控上传进度的AJAX Upload

作者:cleverpig

image
AJAX——最酷的“冲浪板”



动机:

        2006年底Google了一下AJAX Upload实现,结果没有发现很完整的Java实现。硕果仅存的就是 TELIO公司的Pierre-Alexandre发表的 《AJAX Upload progress monitor for Commons-FileUpload Example》文中提供的 ajax-upload-1.0.war。

        虽然上文中完成Upload工作的是Apache的 Common-FileUpload组件,但在其代码中所使用的FileUpload1.1版本并没有1.2版本所提供的上传处理Listener功能,这就对检测文件上传情况造成了困难。我想正是这个原因致使Pierre-Alexandre使用了 DWR+MonitoredDiskFileItem、MonitoredDiskFileItemFactory类(分别继承 DiskFileItem、 DiskFileItemFactory)的方式:前者负责在web客户端进行Remote Call;后者在进行文件数据读取时统计数据总量、读取数据量、处理文件总数,并保存于Session中,以供web客户端通过DWR远程调用UploadMonitor类的getUploadInfo方法进行轮询(Poll)。

        从本人观点出发,Pierre-Alexandre实现的不足之处:
        1.没有用户取消上传功能;
        2.完全的DWR实现,没有使用 Prototype,对于不会使用DWR的开发者来讲有一定的知识局限性,而且由于DWR的个性而造成不便将此实现集成到项目中。



Prototype+Servlet的实现:

image
Prototype+Servlet的Example


        所以出于研究Prototype之目的,本人经过仔细思考,尝试实现了一个Prototype+Servlet的简单Example。其工作流程很简单:
1.在Form提交上传文件Field的同时,使用AJAX周期性地从Servlet轮询上传状态信息;
2.然后,根据此信息更新进度条和相关文字,及时反映文件传输状态;
3.如果用户取消上传操作,则进行相应的现场清理工作:删除已经上传的文件,在Form提交页面中显示相关信息;
4.如果上传完毕,在Form提交页面中显示已经上传的文件内容(或链接),也可以与一些 AJAX SlideShow应用结合在一起。

服务器端代码:

         Bean序列化/反序列化工作:XmlUnSerializer这个类虽然不能够通吃任何模样的Bean,但应付一般的Bean、具有Collection类型属性的Bean和Bean List来讲还是够用的。
         {XmlUnSerializer类的核心方法serializeBean和serializeBeanList}:

        /**
         * 将bean系列化为UTF-8编码的xml
         * @param beanObj
         * @return
         * @throws IOException
         */
        public static String serializeBean(Object beanObj) throws IOException{
        …
        }
        /**
         * 将bean列表序列化为UTF-8编码的xml
         * @param beanObj
         * @return
         * @throws IOException
         */
        public static String serializeBeanList(Object beanListObj) throws IOException{
        …
        }


         文件上传状态Bean:使用FileUploadStatus这个类记录文件上传状态,并将其作为服务器端与web客户端之间通信的媒介物:通过对这个类对象进行XML序列化作为服务器回应发送给web客户端,web客户端使用JavaScript对其进行反序列化处理获得JavaScript版本的文件上传状态对象。
         {FileUploadStatus的属性}:

        //上传总量
        private long uploadTotalSize=0;
        //读取上传总量
        private long readTotalSize=0;
        //当前上传文件号
        private int currentUploadFileNum=0;
        //成功读取上传文件数
        private int successUploadFileCount=0;
        //状态
        private String status="";
        //处理起始时间
        private long processStartTime=0l;
        //处理终止时间
        private long processEndTime=0l;
        //处理执行时间
        private long processRunningTime=0l;
        //上传文件URL列表
        private List uploadFileUrlList=new ArrayList();
        //取消上传
        private boolean cancel=false;
        //上传base目录
        private String baseDir="";


         文件上传状态监视工作:使用 Common-FileUpload 1.2版本(20070103)。此版本与1.1版的区别在于提供了能够监视文件上传情况的 ProcessListener接口,使开发者通过 FileUploadBase类对象的 setProcessListener方法植入自己的Listener,而且 实现这个Listener很简单。
         {FileUploadListener主要方法update}:

        /**
         * 更新状态
         * @param pBytesRead 读取字节总数
         * @param pContentLength 数据总长度
         * @param pItems 当前正在被读取的field号
         */
        public void update(long pBytesRead, long pContentLength, int pItems){
                FileUploadStatus fuploadStatus=BackGroundService.takeOutFileUploadStatusBean(this.session);
                logger.debug("当前正在处理第" + pItems+"个文件");
                fuploadStatus.setUploadTotalSize(pContentLength);
                //读取完成
            if (pContentLength == -1) {
               logger.debug("读取完成:读取了 " + pBytesRead + " bytes.");
               fuploadStatus.setStatus("完成对" + pItems+"个文件的读取:读取了 " + pBytesRead + " bytes.");
               fuploadStatus.setReadTotalSize(pBytesRead);
               fuploadStatus.setSuccessUploadFileCount(pItems);
               fuploadStatus.setProcessEndTime(System.currentTimeMillis());
               fuploadStatus.setProcessRunningTime(fuploadStatus.getProcessEndTime());
            //读取中
            } else {
               logger.debug("读取进行中:已经读取了 " + pBytesRead + " / " + pContentLength+ " bytes.");
               fuploadStatus.setStatus("当前正在处理第" + pItems+"个文件:已经读取了 " + pBytesRead + " / " + pContentLength+ " bytes.");
               fuploadStatus.setReadTotalSize(pBytesRead);
               fuploadStatus.setCurrentUploadFileNum(pItems);
               fuploadStatus.setProcessRunningTime(System.currentTimeMillis());
            }
            BackGroundService.storeFileUploadStatusBean(this.session,fuploadStatus);
        }

        很清楚,我也把FileUploadStatus这个Bean存取于Session中。

         Servlet实现:BackGroundService这个Servlet类负责接收Form Post数据、回应状态轮询请求、处理取消文件上传的请求。尽管可以把这些功能相互分离开来(比如构造一个FileUploadManager类),但出于简单明了、便于阅读之目的,还是将它们放到Servlet中,只是由不同的方法进行分割。
         {BackGroundService中的processFileUpload方法用于处理文件上传请求}:

        /**
         * 处理文件上传
         * @param request
         * @param response
         * @throws IOException
         * @throws ServletException
         */
        private void processFileUpload(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
                DiskFileItemFactory factory = new DiskFileItemFactory();
                //设置内存阀值,超过后写入临时文件
                factory.setSizeThreshold(10240000);
                //设置临时文件存储位置
                factory.setRepository(new File(request.getRealPath("/upload/temp")));
                ServletFileUpload upload = new ServletFileUpload(factory);
                //设置单个文件的最大上传size
                upload.setFileSizeMax(10240000);
                //设置整个request的最大size
                upload.setSizeMax(10240000);
                upload.setProgressListener(new FileUploadListener(request.getSession()));
                //保存初始化后的FileUploadStatus Bean
                storeFileUploadStatusBean(request.getSession(),initFileUploadStatusBean(request));
                
                String forwardURL="";
                try {
                        List items = upload.parseRequest(request);
                        //获得返回url
                        for(int i=0;i<items.size();i++){
                                FileItem item=(FileItem)items.get(i);
                                if (item.isFormField()){
                                        logger.debug("form Field["+item.getFieldName()+"]="+item.getString());
                                        forwardURL=item.getString();
                                        break;
                                }
                        }
                        //处理文件上传
                        for(int i=0;i<items.size();i++){
                                FileItem item=(FileItem)items.get(i);

                                //取消上传
                                if (takeOutFileUploadStatusBean(request.getSession()).getCancel()){
                                        deleteUploadedFile(request);
                                        break;
                                }
                                //保存文件
                                else if (!item.isFormField() && item.getName().length()>0){
                                        String fileName=takeOutFileName(item.getName());
                                        logger.debug("处理文件["+fileName+"]:保存路径为"
                                                        +request.getRealPath(UPLOAD_DIR)+File.separator+fileName);
                                        File uploadedFile = new File(request.getRealPath(UPLOAD_DIR)+File.separator+fileName);
                                        item.write(uploadedFile);
                                        //更新上传文件列表
                                        FileUploadStatus fUploadStatus=takeOutFileUploadStatusBean(request.getSession());
                                        fUploadStatus.getUploadFileUrlList().add(fileName);
                                        storeFileUploadStatusBean(request.getSession(),fUploadStatus);
                                        Thread.sleep(500);
                                }
                        }
                
                } catch (FileUploadException e) {
                        logger.error("上传文件时发生错误:"+e.getMessage());
                        e.printStackTrace();
                        uploadExceptionHandle(request,"上传文件时发生错误:"+e.getMessage());
                } catch (Exception e) {
                        // TODO Auto-generated catch block
                        logger.error("保存上传文件时发生错误:"+e.getMessage());
                        e.printStackTrace();
                        uploadExceptionHandle(request,"保存上传文件时发生错误:"+e.getMessage());
                }
                if (forwardURL.length()==0){
                        forwardURL=DEFAULT_UPLOAD_FAILURE_URL;
                }
                request.getRequestDispatcher(forwardURL).forward(request,response);
        }


         {BackGroundService中的responseFileUploadStatusPoll方法用于处理对文件上传状态的轮询请求}:

        /**
         * 回应上传状态查询
         * @param request
         * @param response
         * @throws IOException
         */
        private void responseFileUploadStatusPoll(HttpServletRequest request,HttpServletResponse response) throws IOException{
                response.setContentType("text/xml");
                response.setCharacterEncoding("UTF-8");
                response.setHeader("Cache-Control", "no-cache");
                logger.debug("发送上传状态回应");
                response.getWriter().write(XmlUnSerializer.serializeBean(
                                request.getSession().getAttribute(UPLOAD_STATUS)));
        }


         {BackGroundService中的processCancelFileUpload方法用于处理取消文件上传的请求}:

        /**
         * 处理取消文件上传
         * @param request
         * @param response
         * @throws IOException
         */
        private void processCancelFileUpload(HttpServletRequest request,HttpServletResponse response) throws IOException{
                FileUploadStatus fUploadStatus=(FileUploadStatus)request.getSession().getAttribute(UPLOAD_STATUS);
                fUploadStatus.setCancel(true);
                request.getSession().setAttribute(UPLOAD_STATUS, fUploadStatus);
                responseFileUploadStatusPoll(request,response);
        }


Web客户端代码:

image
Prototype给开发者更多的自由选择


web客户端使用了基于Prototype的 AjaxWrapper类和XMLDomForAjax类,前者实现了对Ajax.Request功能的封装,而后者实现了对来自服务器的XML Response的反序列化(反序列化为JavaScript对象)。

        为了避免在AjaxWrapper的回调方法中发生this被重写的问题,我使用了ClassUtils类给任何类的每个方法注册一个对类对象自身引用,详见 《解开JavaScript生命的达芬奇密码》和 《Prototype.AjaxRequest的调用堆栈重写问题》:
         {ClassUtils类代码}:

//类工具
var ClassUtils=Class.create();
ClassUtils.prototype={
        _ClassUtilsName:'ClassUtils',
        initialize:function(){
        },
        /**
         * 给类的每个方法注册一个对类对象的自我引用
         * @param reference 对类对象的引用
         */
        registerFuncSelfLink:function(reference){
                for (var n in reference) {
                var item = reference[n];                        
                if (item instanceof Function)
                                item.$ = reference;
            }
        }
}


         {将XML反序列化为JavaScript对象的XMLDomForAjax类代码}:

var XMLDomForAjax=Class.create();
XMLDomForAjax.prototype={
        isDebug:false,
        //dom节点类型常量
        ELEMENT_NODE:1,
        ATTRIBUTE_NODE:2,
    TEXT_NODE:3,
    CDATA_SECTION_NODE:4,
    ENTITY_REFERENCE_NODE:5,
    ENTITY_NODE:6,
    PROCESSING_INSTRUCTION_NODE:7,
    COMMENT_NODE:8,
    DOCUMENT_NODE:9,
    DOCUMENT_TYPE_NODE:10,
    DOCUMENT_FRAGMENT_NODE:11,
    NOTATION_NODE:12,
    
        initialize:function(isDebug){
                new ClassUtils().registerFuncSelfLink(this);
                this.isDebug=isDebug;
        },
        /**
         * 建立跨平台的dom解析器
         * @param xml xml字符串
         * @return dom解析器
         */
        createDomParser:function(xml){
                // code for IE
                if (window.ActiveXObject){
                  var doc=new ActiveXObject("Microsoft.XMLDOM");
                  doc.async="false";
                  doc.loadXML(xml);
                }
                // code for Mozilla, Firefox, Opera, etc.
                else{
                  var parser=new DOMParser();
                  var doc=parser.parseFromString(xml,"text/xml");
                }
                return doc;
        },
        /**
         * 反向序列化xml到javascript Bean
         * @param xml xml字符串
         * @return javascript Bean
         */
        deserializedBeanFromXML:function (xml){
                var funcHolder=arguments.callee.$;
                var doc=funcHolder.createDomParser(xml);
                // documentElement总表示文档的root
                var objDomTree=doc.documentElement;
                var obj=new Object();
            for (var i=0; i<objDomTree.childNodes.length; i++) {
                    //获得节点
                    var node=objDomTree.childNodes[i];
                    //取出其中的field元素进行处理
                if ((node.nodeType==funcHolder.ELEMENT_NODE) && (node.tagName == 'field')) {
                        var nodeText=funcHolder.getNodeText(node);
                        if (funcHolder.isDebug){
                            alert(node.getAttribute('name')+' type:'+node.getAttribute('type')+' text:'+nodeText);
                        }
                    var objFieldValue=null;
                    //如果为列表
                    if (node.getAttribute('type')=='java.util.List'){
                            if (objFieldValue && typeof(objFieldValue)=='Array'){
                                    if (nodeText.length>0){
                                                        objFieldValue[objFieldValue.length]=nodeText;
                                                }
                                        }
                                        else{
                                                objFieldValue=new Array();
                                        }
                                }
                                else if (node.getAttribute('type')=='long'
                                        || node.getAttribute('type')=='java.lang.Long'
                                        || node.getAttribute('type')=='int'
                                        || node.getAttribute('type')=='java.lang.Integer'){
                                        
                                        objFieldValue=parseInt(nodeText);
                                }
                                else if (node.getAttribute('type')=='double'
                                        || node.getAttribute('type')=='float'
                                        || node.getAttribute('type')=='java.lang.Double'
                                        || node.getAttribute('type')=='java.lang.Float'){
                                        
                                        objFieldValue=parseFloat(nodeText);
                                }
                                else if (node.getAttribute('type')=='java.lang.String'){
                                        objFieldValue=nodeText;
                                }
                                else{
                                        objFieldValue=nodeText;
                                }
                                //赋值给对象
                                obj[node.getAttribute('name')]=objFieldValue;
                                if (funcHolder.isDebug){
                                        alert(eval('obj.'+node.getAttribute('name')));
                                }
                }
                else if (node.nodeType == funcHolder.TEXT_NODE){
                        if (funcHolder.isDebug){
                                //alert('TEXT_NODE');
                        }
                        
                }
                else if (node.nodeType == funcHolder.CDATA_SECTION_NODE){
                        if (funcHolder.isDebug){
                                //alert('CDATA_SECTION_NODE');
                        }
                }
            }
            return obj;
        },
        /**
         * 获得dom节点的text
         */
        getNodeText:function (node) {
                var funcHolder=arguments.callee.$;
            // is this a text or CDATA node?
            if (node.nodeType == funcHolder.TEXT_NODE || node.nodeType == funcHolder.CDATA_SECTION_NODE) {
                return node.data;
            }
            var i;
            var returnValue = [];
            for (i = 0; i < node.childNodes.length; i++) {
                    //采用递归算法
                returnValue.push(funcHolder.getNodeText(node.childNodes[i]));
            }
            return returnValue.join('');
        }
}


         {AjaxWrapper类的主要方法putRequest和callBackHandler}:

         /**
         * 以get的方式向server发送request
         * @param url
         * @param params
         * @param callBackFunction 发送成功后回调的函数或者函数名
         */
        putRequest:function(url,params,callBackFunction){
                var funcHolder=arguments.callee.$;
            var xmlHttp = new Ajax.Request(url,
                        {
                                method: 'get',
                            parameters: params,
                                requestHeaders:['my-header-encoding','utf-8'],
                            onFailure: function(){
                                        alert('对不起,网络通讯失败,请重新刷新!');
                                },
                                onSuccess: function(transport){
                                },
                                onComplete: function(transport){
                                        funcHolder.callBackHandler.apply(funcHolder,[transport,callBackFunction]);
                                }
                        });
        },
        /**
         * 远程调用的回调处理
         * @param transport xmlhttp的transport
         * @param callBackFunction 回调时call的方法,可以是函数也可以是函数名
         */
        callBackHandler:function(transport,callBackFunction){
                var funcHolder=arguments.callee.$;
                if(transport.status!=200){
                        alert("获得回应失败,请求状态:"+transport.status);
                }
                else{
                        funcHolder.xml_source=transport.responseText;
                        if (funcHolder.debug_flag)
                                alert('call callback function');
                        if (typeof(callBackFunction)=='function'){
                                if (funcHolder.debug_flag){
                                        alert('invoke callbackFunc');
                                }
                                callBackFunction(transport.responseText);
                        }
                        else{
                                if (funcHolder.debug_flag){
                                        alert('evalFunc callbackFunc');
                                }
                                new execute().evalFunc(callBackFunction,transport.responseText);
                        }
                        if (funcHolder.debug_flag)
                                alert('end callback function');
                }
        }


         {页面中主要的JavaScript方法:refreshUploadStatus和startProcess/cancelProcess}:

//刷新上传状态
function refreshUploadStatus(){
        var ajaxW = new AjaxWrapper(false);
        ajaxW.putRequest(
                './uploadStatus.action',
                'uploadStatus=',
                function(responseText){
                        var deserialor=new XMLDomForAjax(false);
                        var uploadInfo=deserialor.deserializedBeanFromXML(responseText);
                        var progressPercent = Math.ceil(
                                (uploadInfo.readTotalSize) / uploadInfo.uploadTotalSize * 100);

                $('progressBarText').innerHTML = ' 上传处理进度: '+progressPercent+'% ['+
                        (uploadInfo.readTotalSize)+'/'+uploadInfo.uploadTotalSize + ' bytes]'+
                        ' 正在处理第'+uploadInfo.currentUploadFileNum+'个文件'+
                        ' 耗时: '+(uploadInfo.processRunningTime-uploadInfo.processStartTime)+' ms';
                $('progressStatusText').innerHTML=' 反馈状态: '+uploadInfo.status;
                $('totalProgressBarBoxContent').style.width = parseInt(progressPercent * 3.5) + 'px';
                }
        );
}
//上传处理
function startProgress(){
        Element.show('progressBar');
    $('progressBarText').innerHTML = ' 上传处理进度: 0%';
    $('progressStatusText').innerHTML=' 反馈状态:';
    $('uploadButton').disabled = true;
    var periodicalExe=new PeriodicalExecuter(refreshUploadStatus,2);
    return true;
}
//取消上传处理
function cancelProgress(){
        $('cancelUploadButton').disabled = true;
        var ajaxW = new AjaxWrapper(false);
        ajaxW.putRequest(
                './uploadStatus.action',
                'cancelUpload=true',
                //因为form的提交,这可能不会执行
                function(responseText){
                        var deserialor=new XMLDomForAjax(false);
                        var uploadInfo=deserialor.deserializedBeanFromXML(responseText);
                        $('progressStatusText').innerHTML=' 反馈状态: '+uploadInfo.status;
                        if (msgInfo.cancel=='true'){
                                alert('删除成功!');
                                window.location.reload();
                        };
                }
        );
}


运行界面:

image
起始页面



image
上传进行中…



image
上传完成后的文件列表



image
用户取消上传后显示的页面




image
上传过程中出错(上传文件过大)页面


源代码下载:






相关链接:
         AJAX Upload progress monitor for Commons-FileUpload Example
         Apache Common FileUpload组件
         Prototype官方网站
         IBM的AJAX SlideShow应用
         解开JavaScript生命的达芬奇密码
         Prototype.AjaxRequest的调用堆栈重写问题

感谢阅读此文

        请支持 cleverpig发起的 image
 
设置FileUpload上传文件大小 在配置文件里设置FileUpload上传文件大小 立即下载

相关推荐

AJAXFileUpload ajax 异步文件上传 进度条

AJAXFileUpload ajax 异步文件上传 进度条AJAXFileUpload ajax 异步文件上传 进度条

【Arduino】ESP8266网页端数据传输与控制

目前所在公司主要业务是手机上光距感这颗器件,由于公司没有MTK/QCOM开发版,只能自己想办法模拟平台驱动,来熟悉芯片特性以及查找客户使用驱动的bug,由于esp8266比较便宜而且使用arduino编程也比较方便,开发版通过i2c与sensor通信,这篇主要介绍模拟更新配置文件,存于ESP闪存中,SPIFFS系统,开机时读取获得。不啰嗦,直接上代码,开发版部分,标注的也是比较清楚的,都是复制粘贴所得,希望路过的大神勿踩,upload.html以及success.html。参考网站:太极极客,菜鸟教程。

yyandad的博客 4298

在线迭代RLHF实战:从原理到实现,复现超越官方指令模型的工作流

大语言模型(LLM)的对齐技术是提升其与人类意图一致性的关键。其中,基于人类反馈的强化学习(RLHF)通过引入人类偏好信号,指导模型优化生成策略,是实现模型“有用”与“无害”的核心方法。其技术价值在于将抽象的“对齐”目标转化为可优化的奖励信号,从而显著提升模型在对话、推理等任务中的表现。然而,传统的离线RLHF存在数据分布偏移和奖励黑客等瓶颈。在线迭代RLHF通过构建“生成-评估-学习”的动态数据循环,让模型能够利用自身最新生成的数据进行持续优化,有效克服了上述问题,成为当前提升指令模型性能的前沿方向。本文

weixin_28725037的博客 369

ESP8266 OTA升级WebServer

1.源码位置:【File】-【Examples】-【ESP8266HttpWebServer】5.浏览器输入:http://esp8266-webupdate.local,看到下图。4.编译,下载到esp8266.找到.bin文件,升级其他工程就用其他的.bin。6.点击"选择文件"-->选择.bin文件-->点击“Update”7.等待升级成功,esp8266自动重启。3.修改arduino IDE 配置。

小智的博客 806

hive sql每个用户的累积访问次数

我们有如下的用户访问数据 userId visitDate visitCount u01 2017/1/21 5 u02 2017/1/23 6 u03 2017/1/22 8 u04 2017/1/20 3 u01 2017/1/23 6 u01 2017/2/21 8 U02 2017/1/23 6 U01 2017/2/22 4 要求使用SQL...

weixin_39150719的博客 3487

Flow Matching实战:5步搞定图像生成模型优化(附PyTorch代码)

本文提供了一份基于Flow Matching框架优化图像生成模型的实战指南。通过五个关键步骤,详细讲解了从环境配置、核心概念梳理到构建向量场网络、设计损失函数、编写训练循环以及选择ODE求解器进行采样的完整流程,并附有可运行的PyTorch代码,帮助开发者快速掌握这一高效的生成式建模技术。

weixin_29065959的博客 322

如何通过ajax实现同时上传文件及普通表单 - 一文了解FormData

使用键值对的方式模拟表单控件,可以使用XML HttpRequest的send()方法来异步提交表单数据。

bug处理进行时 5491

博图Advanced实战:VB脚本实现HMI画面智能弹出(附完整代码解析)

本文详细介绍了在西门子博图Advanced平台中利用VB脚本实现HMI画面智能弹出的实战方法。通过动态计算坐标、分辨率自适应方案和完整代码解析,帮助工程师解决不同显示环境下的定位难题,提升工业自动化界面的交互体验。

weixin_29041767的博客 343

掌控上传进度的Ajax Upload

  动机:  2006年底Google了一下AJAX Upload实现,结果没有发现很完整的Java实现。硕果仅存的就是TELIO公司的Pierre-Alexandre发表的《AJAX Upload progress monitor for Commons-FileUpload Example》文中提供的ajax-upload-1.0.war。  虽然上文中完成Upload工作的是Apac

leagle 1408

基于STM32设计的智能家居控制系统(华为云)_363

本项目设计并实现了一种基于STM32的智能家居控制系统,融合了语音识别、远程控制、环境监测与安全报警等功能。系统以STM32F103C8T6单片机为核心控制器,集成ESP8266 Wi-Fi模块实现与华为云IOT物联网平台的数据交互,采用MQTT协议进行通信。用户可通过语音识别模块控制风扇、空调、窗帘、卧室灯、客厅灯和电视等家电的开关操作,同时也可通过Android手机APP或Windows电脑上位机进行远程控制。系统还具备温湿度、光照强度、烟雾浓度和火焰检测功能,当检测到异常情况时,自动触发蜂鸣器报警并向

883

软考高级网络规划设计师教程(第3版)

"本书是由工业和信息化部教育与考试中心组织编写的考试用书,依据《网络规划设计师考试大纲》(2026年审定通过)编写。本书分为两篇:第一篇为网络规划与设计综合知识,其中第1章介绍计算机网络基础知识,第2章介绍网络互连与互联网,第3章介绍网络规划与设计的知识和方法,第4章介绍网络资源设备,第5章介绍网络安全技术,第6章介绍标准化和网络安全法规;第二篇为案例实践,其中第7章介绍网络规划案例,第8章介绍网络故障分析与处理案例,第9章介绍网络安全部署案例,第10章介绍网络规划与设计论文写作的指导思想和写作技巧。

鹿鸣天涯 3945

利用fileupload组件实现掌控上传进度的AJAX Upload

       前段时间调查了一下可显示进度的文件上传,发现TELIO公司的Pierre-Alexandre发表的《AJAX Upload progress monitor for Commons-FileUpload Example》的实现不错。但是该文中用到的是FileUpload1.1版本,而FileUpload1.2版本提供了处理上传的Listener功能,该功能可大大简化对文件上传得处

zhaoqiang_fly的专栏 1212

基于DO-178C/331的飞控软件研发环境的研究

本研究提出了一套基于DO-178C/331标准的飞控系统软件开发和验证框架。通过优化模型库的设计、代码生成配置和测试验证流程,显著提升了飞控系统软件的开发效率和安全性。未来的研究可以进一步扩展模型库的功能,以适应更复杂的飞控系统需求。

qq_41854911的博客 563

掌控上传进度的AJAX Upload(转贴)

 掌控上传进度的AJAX Upload作者:cleverpigAJAX——最酷的“冲浪板”动机:        2006年底Google了一下AJAX Upload实现,结果没有发现很完整的Java实现。硕果仅存的就是TELIO公司的Pierre-Alexandre发表的《AJAX Upload pro

溺水的鱼 - Later equals never. 898

【回眸】Tessy集成测试软件使用指南(一)新手使用篇

这个专栏的文章前4篇都在记录如何使用Tessy进行单元测试,集成测试需要有一定单元测试基础,且做集成测试之前,需要做好单元测试,否则将会大幅增加软件单元代码纠错的代价。集成测试所花费的时间通常远远超过单元测试。如果直接从单元测试到系统测试,结果往往是不理想的。集成测试可以检测由于单元相互交互而导致的错误,通常由一组专业的测试人员执行。

啤酒鸭的物联网开发记录博客 5288

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

上一篇: 在Spring中配置Hibernate事务
下一篇: log4j详解
lhminjava
博客等级 码龄21年 15粉丝 6原创
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值