异构平台数据的传输,一直是我这几年工作中比较烦心的事。平台端,无选择的是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; } } }
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 } }
#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就可以。