Spring MVC Rest文件上传

异构平台数据的传输,一直是我这几年工作中比较烦心的事。平台端,无选择的是linux+java+mysql,应用端则有C#、Plex、Andriod、IOS等。平台端,先后经历了webservice、Axis、strut以及spring mvc Rest。在spring mvc Rest之前,由于名为的分工合作,我没有参与代码实现,但是那彼此之间的沟通协调、调用代码的冗长、刀耕火种,想想都好笑。

        在忍无可忍的情况下,2012年我用spring mvc Rest开始编写接口,但核心接口任然不敢贸然的修改;近日,在无法忍受多套代码的维护下,我看了看那些难受的接口以及调用端代码,太别扭了,我真不知道当年是怎样写出这些代码的。这些接口中,文件上传是最痛苦的。

       先描述一下运用场景,我们的文件上传并不是孤立的上传文件,还包括一些属性同步传输,用于写到数据库中和把文件写到特定的文件夹中。接口我们有个前提,所有数据以json格式交互;同时,我们的网站通过cdn加速,但cdn不支持http1.1协议,于是文件的上传就一个单独的上传地址。

       看看这些,重构代码,风险还是挺大的。这些都是皮毛,深入看看实现机制,我们换换思维,转换一个角度,也许就非常简单。

       再看看调用端情况,调用端是桌面系统,即使由用户选择文件,但桌面系统能够控制文件的大小,同时也可以做任何数据类型的转换。文件大小,我们约定一个常规值 就行,文件类型转换,当然是字节流或者字节数据,也非常方便。

      java端呢,当然能够接受字节流了,哦,我们不需要普通网站技术的File类了,我们用byte[]。

      再看看呢,实现是否非常的简单了,两端都传输byte[],那么就是简单的字段值传输了。

      根据这个思路,我对两端都进行改造。

      一、java端,就需要两个地方注意,一个是请求对象,一个是控制器,如下就行了,其余的参看我前面的博客。

             1、构建一个请求对象,里面各个属性是接口接受的参数,其中文件的定义为byte[],如下

 public class KdbaoUploadRequest implements Serializable {


/**

*/
private static final long serialVersionUID = 1L;

private Header header;//求情头

private KdbaoUpdate kdbaoUpdate;//请求对象

private byte[] fileBytes;


        ....其余get/set,构造函数
}

            对象KdbaoUpdate及持久层、服务层代码利用以前编写的。

         2、构建控制器,为了测试我只做本机的测试例子,代码如下

@Controller
@RequestMapping("/kdbaoupdate")
public class KdbaoUpdateController {


@Autowired
private KdbaoUpdateService kdbaoUpdateService;

//新增 - 提交 – 只保存文件到服务器上
private static final String uploadFilePath = "d:\\temp_upload_file\\";

@RequestMapping(value = "/addkdbaoupload", method = RequestMethod.POST)
@ResponseBody
public ResultData<KdbaoUpdate> addKdbaoUpload(@RequestBody KdbaoUploadRequest requestData,
Model mode, HttpServletResponse response) throws IOException {
ResultData<KdbaoUpdate> resultData =new ResultData<KdbaoUpdate>();
resultData.setStatus(0);
resultData.setData(null);
if ((requestData == null)||(null==requestData.getFileBytes())||(null==requestData.getKdbaoUpdate())) {
resultData.getError().setCode("-1");
resultData.getError().setMessage("参数错误:没有传入参数");
return resultData;
}
                if (!requestData.getHeader().checkV()){
resultData.getError().setCode("-2");
resultData.getError().setMessage("身份验证错误");
return resultData;
}
//保存文件
try {

String filename =requestData.getKdbaoUpdate().getFilesavename();
File tempFile = new File(uploadFilePath + filename);
if (tempFile.exists()) {
boolean delResult = tempFile.delete();
System.out.println("删除已存在的文件:" + delResult);
}
int count =requestData.getFileBytes().length;
if (count>0){
FileOutputStream fos = new FileOutputStream(uploadFilePath + filename);
fos.write(requestData.getFileBytes(), 0, count);// 向服务端文件写入字节流
fos.close(); // 关闭FileOutputStream对象
}
}catch (FileNotFoundException e) {
e.printStackTrace();
resultData.getError().setCode("-4");
resultData.getError().setMessage("文件失败:"+e.getMessage());
return resultData;
}
                //保存数据
try {
kdbaoUpdateService.insert(requestData.getKdbaoUpdate());
resultData.setStatus(1);
resultData.setData(requestData.getKdbaoUpdate());
}catch (Exception e) {
resultData.getError().setCode("-3");
resultData.getError().setMessage("数据操作失败:"+e.getMessage());
}
return resultData;
}
}

    二、C#调用端,也需要两个地方注意,一个是请求参数对象,一个是通讯处理函数,如下

     1、请求参数对象

namespace YKCommon.Model
{
    [DataContract]
    public class RemoteRequestDataAndFileByte<T>
    {
        /// <summary>
        /// 请求头部
        /// </summary>
        [DataMember]
        public HeaderData header { get; set; }
        /// <summary>
        /// 请求对象
        /// </summary>
        [DataMember]
        public T remoteRequestData { get; set; }

        [DataMember]
        public Byte[] fileBytes { get; set; }
    }
}

     这个对象和java是一直的,仅仅是一个泛型罢了,这样可以对多个对象进行文件上传。(每次只能上传一个文件)

2、底层通讯函数修改,增加一个region3中代码

namespace YKCommon.Service
{
    public class HttpPostEntityService<T, D>
    {
        #region 1、---------------------------------HTTP底层数据通讯,接受对象,再构造成通讯用参数
        /// <summary>
        /// HTTP底层数据通讯 ,再构造成通讯用参数 无参数
        /// </summary>
        /// <param name="functionName">方法名</param>
        /// <returns></returns>
        public YKCommon.Model.ResultData<T> Postdata(string functionName)
        {
            //构造请求参数对象
            YKCommon.Model.RemoteRequestData<string> requestData = new YKCommon.Model.RemoteRequestData<string>();
            requestData.header = SysInf.Current.CurHeaderData;
            requestData.remoteRequestData = "";
            //构造请求参数JSON
            string requestJson = JSON.stringify(requestData);
            //替换JSON中的remoteRequestData,为实体对象D的类名
            requestJson = requestJson.Replace("remoteRequestData", "dataList");
            //调用--HTTP地层数据通讯,接受字符串
            return Post(functionName, requestJson);
        }
        #endregion

        #region 2、---------------------------------HTTP底层数据通讯,接受对象,再构造成通讯用参数
        /// <summary>
        /// HTTP底层数据通讯,接受对象,再构造成通讯用参数  传入参数D
        /// </summary>
        /// <param name="functionName">方法名</param>
        /// <param name="data">传入对象D</param>
        /// <returns>返回ResultData T 对象</returns>
        public YKCommon.Model.ResultData<T> Postdata(string functionName, D data, string EntityName)
        {
            //构造请求参数对象
            YKCommon.Model.RemoteRequestData<D> requestData = new YKCommon.Model.RemoteRequestData<D>();
            requestData.header = SysInf.Current.CurHeaderData;
            requestData.remoteRequestData = data;
            //构造请求参数JSON
            string requestJson = JSON.stringify(requestData);
            //替换JSON中的remoteRequestData,为实体对象D的类名
            requestJson = requestJson.Replace("remoteRequestData", EntityName);
            //调用--HTTP地层数据通讯,接受字符串
            return Post(functionName, requestJson);
        }
        #endregion

        #region 3、---------------------------------HTTP底层数据通讯,接受对象以及字节流,再构造成通讯用参数
        /// <summary>
        /// HTTP底层数据通讯,接受对象,再构造成通讯用参数  传入参数D
        /// </summary>
        /// <param name="functionName">方法名</param>
        /// <param name="data">传入对象D</param>
        /// <returns>返回ResultData T 对象</returns>
        public YKCommon.Model.ResultData<T> Postdata(string functionName, D data, Byte[] fileBytes, string EntityName)
        {
            //构造请求参数对象
            YKCommon.Model.RemoteRequestDataAndFileByte<D> requestData = new YKCommon.Model.RemoteRequestDataAndFileByte<D>();
            requestData.header = SysInf.Current.CurHeaderData;
            requestData.remoteRequestData = data;
            requestData.fileBytes = fileBytes;
            //构造请求参数JSON
            string requestJson = JSON.stringify(requestData);
            //替换JSON中的remoteRequestData,为实体对象D的类名
            requestJson = requestJson.Replace("remoteRequestData", EntityName);
            //调用--HTTP地层数据通讯,接受字符串
            return Post(functionName, requestJson);
        }
        #endregion

        #region 3、---------------------------------HTTP底层数据通讯,接受字符串
        /// <summary>
        /// HTTP底层数据通讯,接受字符串参数
        /// </summary>
        /// <param name="functionName">方法名</param>
        /// <param name="requestData">string型参数</param>
        /// <returns>返回值为ResultData T 类型的对象</returns>
        private YKCommon.Model.ResultData<T> Post(string functionName, string requestData)
        {
            YKCommon.Model.ResultData<T> resultData;//返回数据

            string uriStr = SysInf.Current.BaseUrl + "/" + functionName;//构造地址

            try
            {
                HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(uriStr));
                if ((requestData == null) || (0 == requestData.Trim().Length))
                {
                    request.Method = "GET";
                    request.Timeout = 15000;
                }
                else
                {

                    UTF8Encoding encoding = new UTF8Encoding();
                    byte[] bytes = encoding.GetBytes(requestData);
                    request.Method = "POST";
                    request.ContentType = "application/json";
                    request.ContentLength = bytes.Length;
                    request.Timeout = 15000;
                    Stream writeStream = request.GetRequestStream();
                    writeStream.Write(bytes, 0, bytes.Length);
                    writeStream.Close();
                }
                string result = string.Empty;
                using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
                {
                    using (Stream responseStream = response.GetResponseStream())
                    {
                        using (StreamReader readStream = new StreamReader(responseStream, Encoding.UTF8))
                        {
                            result = readStream.ReadToEnd();
                        }
                    }
                }
                resultData = JSON.parse<YKCommon.Model.ResultData<T>>(result);
            }
            catch (Exception ex)
            {
                resultData = new YKCommon.Model.ResultData<T>();
                resultData.status = -9;
                resultData.error = new Model.Error("-99", ex.Message);
            }
            return resultData;
        }
        #endregion
    }
}

    3、调用函数

        #region 1、---------------------------------增加
        public YKCommon.Model.ResultData<Model.KdbaoUpdate> addKdb(Model.KdbaoUpdate _updatePer, Byte[] fileStream)
        {
            string functionName = "kdbaoupdate/addkdbaoupload";
            HttpPostEntityService<Model.KdbaoUpdate, Model.KdbaoUpdate> rtService = new HttpPostEntityService<Model.KdbaoUpdate, Model.KdbaoUpdate>();            
            return rtService.Postdata(functionName, _updatePer, fileStream, "kdbaoUpdate");            
        }
        #endregion

哦,这段代码还差一个json解析器,我用的如下

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Runtime.Serialization.Json;
/*********************************************************************************
 * <pre>
 * [版本说明]
 * 1.0    2012/10/18   初版
 * </pre>
 *  @version  1.0 2012/10/18
 *  @author   ZDSOFT)梁晓辉
 *********************************************************************************/


namespace YKCommon.Helper
{
    /// <summary>
    /// 解析JSON,仿Javascript风格
    /// </summary>
    public class JSON
    {
        /// <summary>
        /// Json转换成对象
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="jsonString"></param>
        /// <returns></returns>
        public static T parse<T>(string jsonString)
        {
            using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(jsonString)))
            {
                return (T)new DataContractJsonSerializer(typeof(T)).ReadObject(ms);
            }
        }


        /// <summary>
        /// 对象转Json
        /// </summary>
        /// <param name="jsonObject"></param>
        /// <returns></returns>
        public static string stringify(object jsonObject)
        {
            using (var ms = new MemoryStream())
            {
                new DataContractJsonSerializer(jsonObject.GetType()).WriteObject(ms, jsonObject);
                return Encoding.UTF8.GetString(ms.ToArray());
            }
        }
    }
}


以上代码就实现文件上传,在C#的底层就通用了,对其再改造一下,根本也不需要底层通信函数的region3,用region2就可以。

      

你可能感兴趣的:(spring,mvc,文件上传,Rest文件上传)