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、请求参数对象

 

[csharp][/csharp] view plaincopy

  1. namespace YKCommon.Model
  2. {
  3.     [DataContract]
  4.     public class RemoteRequestDataAndFileByte<T>
  5.     {
  6.         /// <summary>
  7.         /// 请求头部
  8.         /// </summary>
  9.         [DataMember]
  10.         public HeaderData header { get; set; }
  11.         /// <summary>
  12.         /// 请求对象
  13.         /// </summary>
  14.         [DataMember]
  15.         public T remoteRequestData { get; set; }
  16.         [DataMember]
  17.         public Byte[] fileBytes { get; set; }
  18.     }
  19. }

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

 

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

 

[csharp][/csharp] view plaincopy

  1. namespace YKCommon.Service
  2. {
  3.     public class HttpPostEntityService<T, D>
  4.     {
  5.         #region 1、———————————HTTP底层数据通讯,接受对象,再构造成通讯用参数
  6.         /// <summary>
  7.         /// HTTP底层数据通讯 ,再构造成通讯用参数 无参数
  8.         /// </summary>
  9.         /// <param name=”functionName”>方法名</param>
  10.         /// <returns></returns>
  11.         public YKCommon.Model.ResultData<T> Postdata(string functionName)
  12.         {
  13.             //构造请求参数对象
  14.             YKCommon.Model.RemoteRequestData<string> requestData = new YKCommon.Model.RemoteRequestData<string>();
  15.             requestData.header = SysInf.Current.CurHeaderData;
  16.             requestData.remoteRequestData = “”;
  17.             //构造请求参数JSON
  18.             string requestJson = JSON.stringify(requestData);
  19.             //替换JSON中的remoteRequestData,为实体对象D的类名
  20.             requestJson = requestJson.Replace(“remoteRequestData”, “dataList”);
  21.             //调用–HTTP地层数据通讯,接受字符串
  22.             return Post(functionName, requestJson);
  23.         }
  24.         #endregion
  25.         #region 2、———————————HTTP底层数据通讯,接受对象,再构造成通讯用参数
  26.         /// <summary>
  27.         /// HTTP底层数据通讯,接受对象,再构造成通讯用参数  传入参数D
  28.         /// </summary>
  29.         /// <param name=”functionName”>方法名</param>
  30.         /// <param name=”data”>传入对象D</param>
  31.         /// <returns>返回ResultData T 对象</returns>
  32.         public YKCommon.Model.ResultData<T> Postdata(string functionName, D data, string EntityName)
  33.         {
  34.             //构造请求参数对象
  35.             YKCommon.Model.RemoteRequestData<D> requestData = new YKCommon.Model.RemoteRequestData<D>();
  36.             requestData.header = SysInf.Current.CurHeaderData;
  37.             requestData.remoteRequestData = data;
  38.             //构造请求参数JSON
  39.             string requestJson = JSON.stringify(requestData);
  40.             //替换JSON中的remoteRequestData,为实体对象D的类名
  41.             requestJson = requestJson.Replace(“remoteRequestData”, EntityName);
  42.             //调用–HTTP地层数据通讯,接受字符串
  43.             return Post(functionName, requestJson);
  44.         }
  45.         #endregion
  46.         #region 3、———————————HTTP底层数据通讯,接受对象以及字节流,再构造成通讯用参数
  47.         /// <summary>
  48.         /// HTTP底层数据通讯,接受对象,再构造成通讯用参数  传入参数D
  49.         /// </summary>
  50.         /// <param name=”functionName”>方法名</param>
  51.         /// <param name=”data”>传入对象D</param>
  52.         /// <returns>返回ResultData T 对象</returns>
  53.         public YKCommon.Model.ResultData<T> Postdata(string functionName, D data, Byte[] fileBytes, string EntityName)
  54.         {
  55.             //构造请求参数对象
  56.             YKCommon.Model.RemoteRequestDataAndFileByte<D> requestData = new YKCommon.Model.RemoteRequestDataAndFileByte<D>();
  57.             requestData.header = SysInf.Current.CurHeaderData;
  58.             requestData.remoteRequestData = data;
  59.             requestData.fileBytes = fileBytes;
  60.             //构造请求参数JSON
  61.             string requestJson = JSON.stringify(requestData);
  62.             //替换JSON中的remoteRequestData,为实体对象D的类名
  63.             requestJson = requestJson.Replace(“remoteRequestData”, EntityName);
  64.             //调用–HTTP地层数据通讯,接受字符串
  65.             return Post(functionName, requestJson);
  66.         }
  67.         #endregion
  68.         #region 3、———————————HTTP底层数据通讯,接受字符串
  69.         /// <summary>
  70.         /// HTTP底层数据通讯,接受字符串参数
  71.         /// </summary>
  72.         /// <param name=”functionName”>方法名</param>
  73.         /// <param name=”requestData”>string型参数</param>
  74.         /// <returns>返回值为ResultData T 类型的对象</returns>
  75.         private YKCommon.Model.ResultData<T> Post(string functionName, string requestData)
  76.         {
  77.             YKCommon.Model.ResultData<T> resultData;//返回数据
  78.             string uriStr = SysInf.Current.BaseUrl + “/” + functionName;//构造地址
  79.             try
  80.             {
  81.                 HttpWebRequest request = (HttpWebRequest)WebRequest.Create(new Uri(uriStr));
  82.                 if ((requestData == null) || (0 == requestData.Trim().Length))
  83.                 {
  84.                     request.Method = “GET”;
  85.                     request.Timeout = 15000;
  86.                 }
  87.                 else
  88.                 {
  89.                     UTF8Encoding encoding = new UTF8Encoding();
  90.                     byte[] bytes = encoding.GetBytes(requestData);
  91.                     request.Method = “POST”;
  92.                     request.ContentType = “application/json”;
  93.                     request.ContentLength = bytes.Length;
  94.                     request.Timeout = 15000;
  95.                     Stream writeStream = request.GetRequestStream();
  96.                     writeStream.Write(bytes, 0, bytes.Length);
  97.                     writeStream.Close();
  98.                 }
  99.                 string result = string.Empty;
  100.                 using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
  101.                 {
  102.                     using (Stream responseStream = response.GetResponseStream())
  103.                     {
  104.                         using (StreamReader readStream = new StreamReader(responseStream, Encoding.UTF8))
  105.                         {
  106.                             result = readStream.ReadToEnd();
  107.                         }
  108.                     }
  109.                 }
  110.                 resultData = JSON.parse<YKCommon.Model.ResultData<T>>(result);
  111.             }
  112.             catch (Exception ex)
  113.             {
  114.                 resultData = new YKCommon.Model.ResultData<T>();
  115.                 resultData.status = -9;
  116.                 resultData.error = new Model.Error(“-99”, ex.Message);
  117.             }
  118.             return resultData;
  119.         }
  120.         #endregion
  121.     }
  122. }

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>
* [版本说明]
 *********************************************************************************/

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就可以。

标签