ajax配合spring实现文件上传

由于项目需要,开发一个可以上传图片到服务器的web表单页面。


一、 需求
Web表单页面,可以通过表单上传图片以及其他文字信息。


二、 图片上传的流程
之前没有做过这类页面,通过查询资料。发现比较常见的做法,是先将图片上传到服务器端的某个文件目录下,服务器向前台返回图片的存储路径;之后,前台将图片存储路径以及其他表单信息一起提交到服务器,所有的表单信息存储在数据库中。


三、 方法
由于项目需要,我这里介绍两种图片上传方法,第一种是使用ajax对一个图片直接上传;第二种是先在前台将图片切割为较小的文件,之后使用ajax分别上传图片到服务器,服务器实现对文件的拼接。(方法二适合较大文件的上传)下面我分别对两种方法做介绍。


方法一: 直接上传
1 html页面

<!DOCTYPE html>
<head></head>
<body>
<form id="uploadForm" action="/PicSubmit/form" method="post" enctype="multipart/form-data" οnsubmit="return submit_check()" class="bootstrap-frm" >
<input id = "sid" type = "text" name="name" />
<input id = "fileImage" type = "file" name="filename" />
<input id = "addressid" type = "hidden" name="address" />
<input id="ajaxsub" type="button" class="button"  value="上传图片" οnclick="fileUpload()" /> 
<input type="submit" class="button" value="提交表单"  />
<input type="reset" class="button" value="重置表单" />
</body></html>



这一部分需要注意的是,form表单的enctype属性必须设置为“multipart/form-data”,在Html5中,如果需要多张图片一起上传,可以在<input type="file"> 标签中,增加multiple属性,例如:<input type="file" id= “fileImage” multiple />。




2 js
(1)js使用ajax提供的ajaxfileupload.js库。这个库使用起来还是比较方便的,和普通的ajax函数使用方法几乎相同。首先,需要ajaxfileupload.js库文件。这里需要注意,我之前在网上下载了一个ajaxfileupload.js文件不能用,浪费了很长时间,我直接把js库文件粘贴到这里,方便分享。

// JavaScript Document
// ajax file uplaod
jQuery.extend({


    createUploadIframe: function(id, uri)
    {
        //create frame
        var frameId = 'jUploadFrame' + id;


        if(window.ActiveXObject) {
            var io = document.createElement('<iframe id="' + frameId + '" name="' + frameId + '" />');
            if(typeof uri== 'boolean'){
                io.src = 'javascript:false';
            }
            else if(typeof uri== 'string'){
                io.src = uri;
            }
        }
        else {
            var io = document.createElement('iframe');
            io.id = frameId;
            io.name = frameId;
        }
        io.style.position = 'absolute';
        io.style.top = '-1000px';
        io.style.left = '-1000px';


        document.body.appendChild(io);


        return io;
    },
    createUploadForm: function(id, fileElementId)
    {
        //create form
        var formId = 'jUploadForm' + id;
        var fileId = 'jUploadFile' + id;
        var form = jQuery('<form  action="" method="POST" name="' + formId + '" id="' + formId + '" enctype="multipart/form-data"></form>');
        var oldElement = jQuery('#' + fileElementId);
        var newElement = jQuery(oldElement).clone();
        jQuery(oldElement).attr('id', fileId);
        jQuery(oldElement).before(newElement);
        jQuery(oldElement).appendTo(form);
        //set attributes
        jQuery(form).css('position', 'absolute');
        jQuery(form).css('top', '-1200px');
        jQuery(form).css('left', '-1200px');
        jQuery(form).appendTo('body');
        return form;
    },


    ajaxFileUpload: function(s) {
        // TODO introduce global settings, allowing the client to modify them for all requests, not only timeout  
        s = jQuery.extend({}, jQuery.ajaxSettings, s);
        var id = s.fileElementId;
        var form = jQuery.createUploadForm(id, s.fileElementId);
        var io = jQuery.createUploadIframe(id, s.secureuri);
        var frameId = 'jUploadFrame' + id;
        var formId = 'jUploadForm' + id;


        if( s.global && ! jQuery.active++ )
        {
            // Watch for a new set of requests
            jQuery.event.trigger( "ajaxStart" );
        }
        var requestDone = false;
        // Create the request object
        var xml = {};
        if( s.global )
        {
            jQuery.event.trigger("ajaxSend", [xml, s]);
        }


        var uploadCallback = function(isTimeout)
        {
            // Wait for a response to come back
            var io = document.getElementById(frameId);
            try
            {
                if(io.contentWindow)
                {
                    xml.responseText = io.contentWindow.document.body?io.contentWindow.document.body.innerHTML:null;
                    xml.responseXML = io.contentWindow.document.XMLDocument?io.contentWindow.document.XMLDocument:io.contentWindow.document;


                }else if(io.contentDocument)
                {
                    xml.responseText = io.contentDocument.document.body?io.contentDocument.document.body.innerHTML:null;
                    xml.responseXML = io.contentDocument.document.XMLDocument?io.contentDocument.document.XMLDocument:io.contentDocument.document;
                }
            }catch(e)
            {
                jQuery.handleError(s, xml, null, e);
            }
            if( xml || isTimeout == "timeout")
            {
                requestDone = true;
                var status;
                try {
                    status = isTimeout != "timeout" ? "success" : "error";
                    // Make sure that the request was successful or notmodified
                    if( status != "error" )
                    {
                        // process the data (runs the xml through httpData regardless of callback)
                        var data = jQuery.uploadHttpData( xml, s.dataType );
                        if( s.success )
                        {
                            // ifa local callback was specified, fire it and pass it the data
                            s.success( data, status );
                        };
                        if( s.global )
                        {
                            // Fire the global callback
                            jQuery.event.trigger( "ajaxSuccess", [xml, s] );
                        };
                    } else
                    {
                        jQuery.handleError(s, xml, status);
                    }


                } catch(e)
                {
                    status = "error";
                    jQuery.handleError(s, xml, status, e);
                };
                if( s.global )
                {
                    // The request was completed
                    jQuery.event.trigger( "ajaxComplete", [xml, s] );
                };




                // Handle the global AJAX counter
                if(s.global && ! --jQuery.active)
                {
                    jQuery.event.trigger("ajaxStop");
                };
                if(s.complete)
                {
                    s.complete(xml, status);
                } ;


                jQuery(io).unbind();


                setTimeout(function()
                { try
                {
                    jQuery(io).remove();
                    jQuery(form).remove();


                } catch(e)
                {
                    jQuery.handleError(s, xml, null, e);
                }


                }, 100);


                xml = null;


            };
        }
        // Timeout checker
        if( s.timeout > 0 )
        {
            setTimeout(function(){


                if( !requestDone )
                {
                    // Check to see ifthe request is still happening
                    uploadCallback( "timeout" );
                }


            }, s.timeout);
        }
        try
        {
            var form = jQuery('#' + formId);
            jQuery(form).attr('action', s.url);
            jQuery(form).attr('method', 'POST');
            jQuery(form).attr('target', frameId);
            if(form.encoding)
            {
                form.encoding = 'multipart/form-data';
            }
            else
            {
                form.enctype = 'multipart/form-data';
            }
            jQuery(form).submit();


        } catch(e)
        {
            jQuery.handleError(s, xml, null, e);
        }
        if(window.attachEvent){
            document.getElementById(frameId).attachEvent('onload', uploadCallback);
        }
        else{
            document.getElementById(frameId).addEventListener('load', uploadCallback, false);
        }
        return {abort: function () {}};


    },


    uploadHttpData: function( r, type ) {
        var data = !type;
        data = type == "xml" || data ? r.responseXML : r.responseText;
        // ifthe type is "script", eval it in global context
        if( type == "script" )
        {
            jQuery.globalEval( data );
        }


        // Get the JavaScript object, ifJSON is used.
        if( type == "json" )
        {
            eval( "data = " + data );
        }


        // evaluate scripts within html
        if( type == "html" )
        {
            jQuery("<div>").html(data).evalScripts();
        }


        return data;
    },
    handleError: function( s, xhr, status, e )      {
        // If a local callback was specified, fire it  
        if ( s.error ) {
            s.error.call( s.context || s, xhr, status, e );
        }


        // Fire the global callback
        if ( s.global ) {
            (s.context ? jQuery(s.context) : jQuery.event).trigger( "ajaxError", [xhr, s, e] );
        }
    }
});



(2)之后调用ajaxfileupload.js库,编写图片上传脚本,这里命名为ajaxfileuplaod_implement.js

function fileUpload() {  
	var inputObject = $("#fileImage").get(0);
	if(inputObject.value == "")
	{
		alert("清先选择需要上传的图片");
		return false;
	}
    $.ajaxFileUpload({  
        url: '/PicSubmit/pic', //服务器端请求地址  
        secureuri: false, //是否需要安全协议,一般设置为false  
        type: 'post',
        fileElementId: 'fileImage', //文件上传域的ID  
        dataType: 'text', //返回值类型 一般设置为json  
        enctype:'multipart/form-data',//注意一定要有该参数 


        success: function (data, status)  //服务器成功响应处理函数  
        {  
        	data=decodeURI(data);//服务器端使用urlencode将中文字符编码,所以这里需要解码。这样做的目的是防止中文乱码
        	var address = JSON.parse(data);
        	for(var i=0;i<address.length;i++){
        		ajaxfile_onSuccess(address[i]); //这里的success回调函数可以自己定义,但是有一点需要注意,就是需要把服务器返回来的图片存储路径写入
							//hiden标签的value值中,方法见下面的writeHide函数
        	}
        	    
        },  
        complete: function(xmlHttpRequest) 
        {	//这里将html中的文件上传标签替换为新的标签,是应为我在开发过程中发现,当ajax执行一次上传操作之后,再使用file标签选择文件时,标签没有反应,
		//所以暂时使用了这种方法。
        	inputObject.replaceWith('<input type="file" id="fileImage" name="fileImage"  />');
        },
        error: function (data, status, e)//服务器响应失败处理函数
        {  
            //alert("无法连接到服务器");
        }  
    })  
}
function writeHide(data){
	if($("#addressid").get(0).value == "")
	{
		$("#addressid").get(0).value = data.newName;
	}
	else
	{
		$("#addressid").get(0).value = $("#addressid").get(0).value+","+data.newName;
	}
}



3 spring.
完成上面两个部分之后,前台的主要工作基本就结束了。我后台使用了spring框架。
首先是springMVC的配置文件:viewspace-servlet.xml

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
       http://www.springframework.org/schema/mvc 
       http://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd
       http://www.springframework.org/schema/context 
       http://www.springframework.org/schema/context/spring-context-3.1.xsd">
     
    <!-- 静态资源 -->
    <mvc:resources mapping="/js/**" location="/js/" />
    <mvc:resources mapping="/css/**" location="/css/" />
    <mvc:resources mapping="/image/**" location="/image/" />
       
    <!-- 扫描web包,应用Spring的注解 -->
    <context:component-scan base-package="web"/>


    <bean id="defaultAnnotationHandlerMapping"
          class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping" />
    <bean id="annotationMethodHandlerAdapter"
          class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter" />


    <!-- 配置视图解析器,将ModelAndView及字符串解析为具体的页面 -->
    <bean
            class="org.springframework.web.servlet.view.InternalResourceViewResolver"
            p:viewClass="org.springframework.web.servlet.view.JstlView"
            p:prefix="/WEB-INF/jsp/"
            p:suffix=".jsp"/> 


     
    <!-- 使springMVC支持图片上传 --> 
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> 
   	 		<!-- 最大上传尺寸1MB -->
     		<property name="maxUploadSize" value="10485760"/>
         	<!-- 默认编码 -->
      		<property name="defaultEncoding" value="UTF-8" />
      		<!-- 上传文件的解析 -->
    		<property name="resolveLazily" value="true" />
    </bean>
    
    <!-- SpringMVC在超出上传文件限制时,会抛出org.springframework.web.multipart.MaxUploadSizeExceededException -->
     <!-- 该异常是SpringMVC在检查上传的文件信息时抛出来的,而且此时还没有进入到Controller方法中 -->
     <bean id="exceptionResolver" class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver" >
         <property name="exceptionMappings">
             <props>
                 <!-- 遇到MaxUploadSizeExceededException异常时,自动跳转到/WEB-INF/jsp/error_toobig.jsp页面 -->
                 <prop key="org.springframework.web.multipart.MaxUploadSizeExceededException">error_fileupload</prop>
            </props>
        </property>
     </bean>
    
    
</beans>



其中,类“org.springframework.web.multipart.commons.CommonsMultipartResolver”的配置是必须的,否则后台无法收到前台传来的文件。


为了防止文件名中的中文字符传输出现问题,在web.xml中做如下配置:

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
  </context-param>
  <listener>
    <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
  </listener>
  <servlet>
    <servlet-name>viewspace</servlet-name>
    <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>viewspace</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>
  <!-- 支持传输中文字符 -->
  <filter>  
        <filter-name>characterEncodingFilter</filter-name>  
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>  
        <init-param>  
            <param-name>encoding</param-name>  
            <param-value>UTF-8</param-value>  
        </init-param>  
        <init-param>  
            <param-name>forceEncoding</param-name>  
            <param-value>true</param-value>  
        </init-param>  
    </filter>  
    <filter-mapping>  
        <filter-name>characterEncodingFilter</filter-name>  
        <url-pattern>/*</url-pattern>  
    </filter-mapping>  
 
</web-app>




接下来是重点,在Controller中,使用如下方式接受前台传回来的文件。

	@RequestMapping(value="/pic")
	@ResponseBody
	public String submitPic(@RequestParam(value = "filename",required = false) MultipartFile[] fileImage, 
			HttpServletRequest request){
		
		if(fileImage == null){
			return "[]";
		}


		return picSaveService.savePic(fileImage);


	}



其中需要注意的是,如果前端html的input标签中使用了multiple属性,则表示标签支持上传多个图片,则controller的参数列表中,文件的类型使用MultipartFile[],反之,如果没有使用multiple属性,表示上传的是一张图片,则controller使用MultipartFile类型接收。


文件接收完成后,就可以对文件进行存储了,方法有很多,我这里举一个例子如下:

	public String savePic(MultipartFile[] fileImage){
		//为图片改名
		String oldName = "";
		String newName = "";
		String extension = "";
		//图片按照上传时间命名
		SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
		//存储每张图片的信息
		List<PicConfirmData> resultList = new ArrayList<PicConfirmData>();
		//获取配置文件中图片的存储路径
		String path = Parameters.getInstance().getDatabaseProps().getProperty("pic_save_dir");
		//依次将图片存储到path路径下
		for(int i=0;i<fileImage.length;i++){
			System.out.println(fileImage[i].getOriginalFilename());
			oldName = fileImage[i].getOriginalFilename();		
			extension = oldName.substring(oldName.lastIndexOf("."));
			newName = sdf.format(new Date())+extension;
			
			File target = new File(path,newName);
			if(!target.exists()){
				target.mkdirs();
			}
			
			try {
					fileImage[i].transferTo(target);
				} catch (IllegalStateException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				}
			//记录图片存储信息
			PicConfirmData pic = null;
			try {
				//只存名称,路径已知,从而节省数据库空间
				//pic = new PicConfirmData(URLEncoder.encode(oldName, "utf-8"), path+newName);
				pic = new PicConfirmData(1,URLEncoder.encode(oldName, "utf-8"), newName);
				resultList.add(pic);
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}
		
		return ToolJson.getJsonFromPicConfirmData(resultList);
	}



这里将接收到的图片的原始名称以及修改后存储使用的名称返回给前台,原始名称用于在前台页面输出“存储成功”的提示信息,修改后的名称用于给hiden标签复制,hiden标签的内容会在之后随表单中其他信息一起提交到服务端,通过hiden标签,我们就可以知道与表单关联的图片被存储在什么地方。


最后,图片上传完成后还需要提交表单,这里使用SpringMVC实现一个表单接收功能。这里名为address的参数,存储的就是图片的存储路径。

	@RequestMapping(value="/form")
	public String submitForm(HttpServletRequest request){
		String sid = request.getParameter("name");
		String address = request.getParameter("address");
		
		if(sid != null && submiter != null && faultTime != null && message != null && address != null){
			if(formDataSaveService.saveForm(sid, submiter, message, address, faultTime)){
				return "ac";
			}
		}
		return "error";
	}


方法二  前台切割上传(留着后面补充)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值