From d489bdb4ce44ee22cd48bc2e36991c5df7d59c59 Mon Sep 17 00:00:00 2001 From: tanghc Date: Wed, 15 May 2019 14:53:43 +0800 Subject: [PATCH] 1.8.0 --- doc/docs/_sidebar.md | 49 +++--- doc/docs/files/10104_文件上传.md | 109 ++++++++++++ .../zuul/filter/FormBodyWrapperFilterExt.java | 8 +- .../filter/Servlet30WrapperFilterExt.java | 8 +- .../param/ApiArgumentResolver.java | 33 +++- .../sop/servercommon/util/UploadUtil.java | 38 +++++ .../controller/FileUploadDemoController.java | 76 ++++++--- .../controller/param/FileUploadParam.java | 10 ++ .../controller/param/FileUploadParam2.java | 12 ++ .../sdk-csharp/SDKCSharp/Client/OpenClient.cs | 6 + .../sdk-csharp/SDKCSharp/Client/OpenHttp.cs | 158 ++++++++++++------ .../SDKCSharp/Model/DemoFileUploadModel.cs | 11 ++ sop-sdk/sdk-csharp/SDKCSharp/Program.cs | 42 ++++- .../SDKCSharp/Request/BaseRequest.cs | 12 ++ .../Request/DemoFileUploadRequest.cs | 13 ++ .../Response/DemoFileUploadResponse.cs | 39 +++++ sop-sdk/sdk-csharp/SDKCSharp/file1.txt | 1 + sop-sdk/sdk-csharp/SDKCSharp/file2.txt | 1 + .../com/gitee/sop/sdk/client/OpenClient.java | 2 +- .../gitee/sop/sdk/request/BaseRequest.java | 12 ++ sop-sdk/sdk-java/src/main/resources/file1.txt | 2 +- sop-sdk/sdk-java/src/main/resources/file2.txt | 2 +- .../test/java/com/gitee/sop/sdk/SdkTest.java | 8 +- 23 files changed, 530 insertions(+), 122 deletions(-) create mode 100644 doc/docs/files/10104_文件上传.md create mode 100644 sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/UploadUtil.java create mode 100644 sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/param/FileUploadParam2.java create mode 100644 sop-sdk/sdk-csharp/SDKCSharp/Model/DemoFileUploadModel.cs create mode 100644 sop-sdk/sdk-csharp/SDKCSharp/Request/DemoFileUploadRequest.cs create mode 100644 sop-sdk/sdk-csharp/SDKCSharp/Response/DemoFileUploadResponse.cs create mode 100644 sop-sdk/sdk-csharp/SDKCSharp/file1.txt create mode 100644 sop-sdk/sdk-csharp/SDKCSharp/file2.txt diff --git a/doc/docs/_sidebar.md b/doc/docs/_sidebar.md index 525d8be4..7ee302a5 100644 --- a/doc/docs/_sidebar.md +++ b/doc/docs/_sidebar.md @@ -1,26 +1,27 @@ -* [首页](/?t=1557833057337) +* [首页](/?t=1557902036067) * 开发文档 - * [快速体验](files/10010_快速体验.md?t=1557833057339) - * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1557833057355) - * [新增接口](files/10020_新增接口.md?t=1557833057355) - * [业务参数校验](files/10030_业务参数校验.md?t=1557833057355) - * [错误处理](files/10040_错误处理.md?t=1557833057355) - * [编写文档](files/10041_编写文档.md?t=1557833057355) - * [接口交互详解](files/10050_接口交互详解.md?t=1557833057355) - * [easyopen支持](files/10070_easyopen支持.md?t=1557833057356) - * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1557833057356) - * [ISV管理](files/10085_ISV管理.md?t=1557833057356) - * [路由授权](files/10090_路由授权.md?t=1557833057356) - * [接口限流](files/10092_接口限流.md?t=1557833057356) - * [SDK开发](files/10095_SDK开发.md?t=1557833057356) - * [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1557833057356) - * [应用授权](files/10097_应用授权.md?t=1557833057356) - * [更改数据节点名称](files/10099_更改数据节点名称.md?t=1557833057356) - * [对接前端](files/10100_对接前端.md?t=1557833057356) - * [自定义过滤器](files/10102_自定义过滤器.md?t=1557833057356) + * [快速体验](files/10010_快速体验.md?t=1557902036070) + * [项目接入到SOP](files/10011_项目接入到SOP.md?t=1557902036086) + * [新增接口](files/10020_新增接口.md?t=1557902036086) + * [业务参数校验](files/10030_业务参数校验.md?t=1557902036086) + * [错误处理](files/10040_错误处理.md?t=1557902036086) + * [编写文档](files/10041_编写文档.md?t=1557902036086) + * [接口交互详解](files/10050_接口交互详解.md?t=1557902036087) + * [easyopen支持](files/10070_easyopen支持.md?t=1557902036087) + * [使用签名校验工具](files/10080_使用签名校验工具.md?t=1557902036087) + * [ISV管理](files/10085_ISV管理.md?t=1557902036087) + * [路由授权](files/10090_路由授权.md?t=1557902036087) + * [接口限流](files/10092_接口限流.md?t=1557902036087) + * [SDK开发](files/10095_SDK开发.md?t=1557902036087) + * [使用SpringCloudGateway](files/10096_使用SpringCloudGateway.md?t=1557902036087) + * [应用授权](files/10097_应用授权.md?t=1557902036087) + * [更改数据节点名称](files/10099_更改数据节点名称.md?t=1557902036087) + * [对接前端](files/10100_对接前端.md?t=1557902036087) + * [自定义过滤器](files/10102_自定义过滤器.md?t=1557902036088) + * [文件上传](files/10104_文件上传.md?t=1557902036088) * 原理分析 - * [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1557833057357) - * [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1557833057357) - * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1557833057357) - * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1557833057357) - * [常见问题](files/90100_常见问题.md?t=1557833057357) + * [原理分析之@ApiMapping](files/90010_原理分析之@ApiMapping.md?t=1557902036088) + * [原理分析之路由存储](files/90011_原理分析之路由存储.md?t=1557902036088) + * [原理分析之如何路由](files/90012_原理分析之如何路由.md?t=1557902036088) + * [原理分析之文档归纳](files/90013_原理分析之文档归纳.md?t=1557902036088) + * [常见问题](files/90100_常见问题.md?t=1557902036088) diff --git a/doc/docs/files/10104_文件上传.md b/doc/docs/files/10104_文件上传.md new file mode 100644 index 00000000..c4482b16 --- /dev/null +++ b/doc/docs/files/10104_文件上传.md @@ -0,0 +1,109 @@ +# 文件上传 + +请求接口时带上文件 + +## 客户端调用 + +```java +DemoFileUploadRequest request = new DemoFileUploadRequest(); + +DemoFileUploadModel model = new DemoFileUploadModel(); +model.setRemark("上传文件参数"); +request.setBizModel(model); + +List files = new ArrayList<>(); +String root = System.getProperty("user.dir"); +System.out.println(root); +// 这里演示将resources下的两个文件上传到服务器 +files.add(new UploadFile("file1", new File(root + "/src/main/resources/file1.txt"))); +files.add(new UploadFile("file2", new File(root + "/src/main/resources/file2.txt"))); +request.setFiles(files); + +DemoFileUploadResponse response = client.execute(request); + +System.out.println("--------------------"); +if (response.isSuccess()) { + List responseFiles = response.getFiles(); + System.out.println("您上传的文件信息:"); + responseFiles.stream().forEach(file->{ + System.out.println(file); + }); +} else { + System.out.println("errorCode:" + response.getCode() + ",errorMsg:" + response.getMsg()); +} +System.out.println("--------------------"); +``` + +客户端使用`UploadFile`添加上传文件 + +```java +/** + * @param name 表单名称,不能重复 + * @param file 文件 + * @throws IOException + */ +public UploadFile(String name, File file) throws IOException { + this(name, file.getName(), FileUtil.toBytes(file)); +} +``` + +其中`name`表示字段名称,相当于``中的name + +源码详见sop-sdk + +## 服务端接收文件 + +- 方式1 + +```java +/** + * 方式1:将文件写在参数中,可直接获取。好处是可以校验是否上传 + * @param param + * @return + */ +@ApiMapping(value = "demo.file.upload") +public FileUploadVO file1(FileUploadParam param) { + System.out.println(param.getRemark()); + // 获取上传的文件 + MultipartFile file1 = param.getFile1(); + MultipartFile file2 = param.getFile2(); + ... + return vo; +} + +@Data +public class FileUploadParam { + private String remark; + + // 上传文件,字段名称对应表单中的name属性值 + @NotNull(message = "文件1不能为空") + private MultipartFile file1; + + @NotNull(message = "文件2不能为空") + private MultipartFile file2; +} +``` + +方式1的好处是可以使用`JSR-303`校验文件是否上传 + +- 方式2 + +```java +/** + * 方式2:从request中获取上传文件 + * + * @param param + * @return + */ +@ApiMapping(value = "demo.file.upload2") +public FileUploadVO file2(FileUploadParam2 param, HttpServletRequest request) { + System.out.println(param.getRemark()); + FileUploadVO vo = new FileUploadVO(); + // 获取上传的文件 + Collection uploadFiles = UploadUtil.getUploadFiles(request); + ... + return vo; +} +``` + +微服务端源码参考`FileUploadDemoController.java` \ No newline at end of file diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/FormBodyWrapperFilterExt.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/FormBodyWrapperFilterExt.java index 54987a19..5dcc9593 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/FormBodyWrapperFilterExt.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/FormBodyWrapperFilterExt.java @@ -16,7 +16,11 @@ public class FormBodyWrapperFilterExt extends FormBodyWrapperFilter { public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); - // 不是上传文件请求,则进行包装 - return !RequestUtil.isMultipart(request); + // 如果是文件上传请求,不需要包装 + if (RequestUtil.isMultipart(request)) { + return false; + } else { + return super.shouldFilter(); + } } } diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/Servlet30WrapperFilterExt.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/Servlet30WrapperFilterExt.java index dac29699..e5973e00 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/Servlet30WrapperFilterExt.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/Servlet30WrapperFilterExt.java @@ -14,7 +14,11 @@ public class Servlet30WrapperFilterExt extends Servlet30WrapperFilter { public boolean shouldFilter() { RequestContext ctx = RequestContext.getCurrentContext(); HttpServletRequest request = ctx.getRequest(); - // 不是上传文件请求,则进行包装 - return !RequestUtil.isMultipart(request); + // 如果是文件上传请求,不需要包装 + if (RequestUtil.isMultipart(request)) { + return false; + } else { + return super.shouldFilter(); + } } } diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java index 413b9c93..1412e74f 100644 --- a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/param/ApiArgumentResolver.java @@ -7,10 +7,13 @@ import com.gitee.sop.servercommon.annotation.ApiMapping; import com.gitee.sop.servercommon.bean.ParamNames; import lombok.Data; import org.springframework.core.MethodParameter; +import org.springframework.util.ReflectionUtils; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.support.StandardMultipartHttpServletRequest; import java.util.Map; @@ -49,8 +52,9 @@ public class ApiArgumentResolver implements HandlerMethodArgumentResolver { protected Object getParamObject(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) { String bizContent = nativeWebRequest.getParameter(ParamNames.BIZ_CONTENT_NAME); Class parameterType = methodParameter.getParameterType(); + Object bizObj = null; if (bizContent != null) { - return JSON.parseObject(bizContent, parameterType); + bizObj = JSON.parseObject(bizContent, parameterType); } else { Map parameterMap = nativeWebRequest.getParameterMap(); JSONObject result = new JSONObject(); @@ -60,10 +64,33 @@ public class ApiArgumentResolver implements HandlerMethodArgumentResolver { } }); if (result.size() > 0) { - return result.toJavaObject(parameterType); + bizObj = result.toJavaObject(parameterType); } } - return null; + this.bindUploadFile(bizObj, nativeWebRequest); + return bizObj; + } + + /** + * 将文件绑定到 + * @param bizObj 业务参数 + * @param nativeWebRequest + */ + protected void bindUploadFile(Object bizObj, NativeWebRequest nativeWebRequest) { + if (bizObj == null) { + return; + } + Object nativeRequest = nativeWebRequest.getNativeRequest(); + if (nativeRequest instanceof StandardMultipartHttpServletRequest) { + StandardMultipartHttpServletRequest request = (StandardMultipartHttpServletRequest) nativeRequest; + Class bizClass = bizObj.getClass(); + ReflectionUtils.doWithFields(bizClass, field -> { + ReflectionUtils.makeAccessible(field); + String name = field.getName(); + MultipartFile multipartFile = request.getFile(name); + ReflectionUtils.setField(field, bizObj, multipartFile); + }, field -> field.getType() == MultipartFile.class); + } } } \ No newline at end of file diff --git a/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/UploadUtil.java b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/UploadUtil.java new file mode 100644 index 00000000..0b021872 --- /dev/null +++ b/sop-common/sop-service-common/src/main/java/com/gitee/sop/servercommon/util/UploadUtil.java @@ -0,0 +1,38 @@ +package com.gitee.sop.servercommon.util; + +import org.apache.commons.fileupload.servlet.ServletFileUpload; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.MultipartHttpServletRequest; + +import javax.servlet.http.HttpServletRequest; +import java.util.Collection; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; + +/** + * 文件上传工具类 + * + * @author tanghc + */ +public class UploadUtil { + + /** + * 获取上传文件 + * + * @param request + * @return + */ + public static Collection getUploadFiles(HttpServletRequest request) { + Map fileMap = null; + //检查form中是否有enctype="multipart/form-data" + if (ServletFileUpload.isMultipartContent(request)) { + //将request变成多部分request + MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request; + fileMap = multiRequest.getFileMap(); + } + return Optional.ofNullable(fileMap) + .map(map -> map.values()) + .orElse(Collections.emptyList()); + } +} diff --git a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/FileUploadDemoController.java b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/FileUploadDemoController.java index 6510fa74..bcce526b 100644 --- a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/FileUploadDemoController.java +++ b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/FileUploadDemoController.java @@ -1,56 +1,78 @@ package com.gitee.sop.storyweb.controller; import com.gitee.sop.servercommon.annotation.ApiMapping; +import com.gitee.sop.servercommon.util.UploadUtil; import com.gitee.sop.storyweb.controller.param.FileUploadParam; +import com.gitee.sop.storyweb.controller.param.FileUploadParam2; import com.gitee.sop.storyweb.vo.FileUploadVO; -import org.apache.commons.fileupload.servlet.ServletFileUpload; import org.apache.commons.io.IOUtils; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.multipart.MultipartHttpServletRequest; import javax.servlet.http.HttpServletRequest; import java.io.IOException; -import java.util.Map; +import java.util.Collection; /** * 演示文件上传 + * * @author tanghc */ @RestController public class FileUploadDemoController { /** - * 接收客户端上传的文件,然后把文件信息返回给客户端 + * 方式1:将文件写在参数中,可直接获取。好处是可以校验是否上传 * @param param - * @param request * @return */ - @ApiMapping(value = "demo.file.upload", ignoreValidate = true) - public FileUploadVO file(FileUploadParam param, HttpServletRequest request) { + @ApiMapping(value = "demo.file.upload") + public FileUploadVO file1(FileUploadParam param) { + System.out.println(param.getRemark()); + // 获取上传的文件 + MultipartFile file1 = param.getFile1(); + MultipartFile file2 = param.getFile2(); + + FileUploadVO vo = new FileUploadVO(); + FileUploadVO.FileMeta fileMeta1 = buildFileMeta(file1); + FileUploadVO.FileMeta fileMeta2 = buildFileMeta(file2); + + vo.getFiles().add(fileMeta1); + vo.getFiles().add(fileMeta2); + return vo; + } + + /** + * 方式2:从request中获取上传文件 + * + * @param param + * @return + */ + @ApiMapping(value = "demo.file.upload2") + public FileUploadVO file2(FileUploadParam2 param, HttpServletRequest request) { System.out.println(param.getRemark()); FileUploadVO vo = new FileUploadVO(); - //检查form中是否有enctype="multipart/form-data" - if (ServletFileUpload.isMultipartContent(request)) { - //将request变成多部分request - MultipartHttpServletRequest multiRequest = (MultipartHttpServletRequest) request; - Map fileMap = multiRequest.getFileMap(); - fileMap.entrySet() - .stream() - .forEach(entry->{ - MultipartFile multipartFile = entry.getValue(); - try { - String fileName = multipartFile.getOriginalFilename(); - long size = multipartFile.getSize(); - String fileContent = IOUtils.toString(multipartFile.getInputStream(), "UTF-8"); - FileUploadVO.FileMeta fileMeta = new FileUploadVO.FileMeta(fileName, size, fileContent); - vo.getFiles().add(fileMeta); - System.out.println(fileContent); - } catch (IOException e) { - e.printStackTrace(); - } - }); + // 获取上传的文件 + Collection uploadFiles = UploadUtil.getUploadFiles(request); + for (MultipartFile multipartFile : uploadFiles) { + FileUploadVO.FileMeta fileMeta = buildFileMeta(multipartFile); + vo.getFiles().add(fileMeta); } return vo; } + + private FileUploadVO.FileMeta buildFileMeta(MultipartFile multipartFile) { + // 文件名 + String fileName = multipartFile.getOriginalFilename(); + // 文件大小 + long size = multipartFile.getSize(); + // 文件内容 + String fileContent = null; + try { + fileContent = IOUtils.toString(multipartFile.getInputStream(), "UTF-8"); + } catch (IOException e) { + e.printStackTrace(); + } + return new FileUploadVO.FileMeta(fileName, size, fileContent); + } } diff --git a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/param/FileUploadParam.java b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/param/FileUploadParam.java index ed29e119..14bd44c0 100644 --- a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/param/FileUploadParam.java +++ b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/param/FileUploadParam.java @@ -1,6 +1,9 @@ package com.gitee.sop.storyweb.controller.param; import lombok.Data; +import org.springframework.web.multipart.MultipartFile; + +import javax.validation.constraints.NotNull; /** * @author tanghc @@ -8,4 +11,11 @@ import lombok.Data; @Data public class FileUploadParam { private String remark; + + // 上传文件,字段名称对应表单中的name属性值 + @NotNull(message = "文件1不能为空") + private MultipartFile file1; + + @NotNull(message = "文件2不能为空") + private MultipartFile file2; } diff --git a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/param/FileUploadParam2.java b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/param/FileUploadParam2.java new file mode 100644 index 00000000..385da278 --- /dev/null +++ b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/param/FileUploadParam2.java @@ -0,0 +1,12 @@ +package com.gitee.sop.storyweb.controller.param; + +import lombok.Data; + +/** + * @author tanghc + */ +@Data +public class FileUploadParam2 { + private String remark; + +} diff --git a/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenClient.cs b/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenClient.cs index 5e4eb767..7be83a6a 100644 --- a/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenClient.cs +++ b/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenClient.cs @@ -21,6 +21,7 @@ namespace SDKCSharp.Client { private static OpenConfig DEFAULT_CONFIG = new OpenConfig(); + private const String ERROR_RESPONSE_KEY = "error_response"; private Dictionary header = new Dictionary(); @@ -137,6 +138,11 @@ namespace SDKCSharp.Client string method = request.Method; string dataName = this.dataNameBuilder.Build(method); Dictionary jsonObject = JsonUtil.ParseToDictionary(resp); + bool errorResponse = jsonObject.ContainsKey(ERROR_RESPONSE_KEY); + if (errorResponse) + { + dataName = ERROR_RESPONSE_KEY; + } object data = jsonObject[dataName]; string jsonData = data == null ? "{}" : data.ToString(); T t = JsonUtil.ParseObject(jsonData); diff --git a/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenHttp.cs b/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenHttp.cs index 0c3812e4..f1d4c9cf 100644 --- a/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenHttp.cs +++ b/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenHttp.cs @@ -131,6 +131,7 @@ namespace SDKCSharp.Client } } + /// /// post请求,并且文件上传 /// @@ -141,77 +142,126 @@ namespace SDKCSharp.Client /// public string PostFile(string url, Dictionary form, Dictionary header, List files) { - Encoding ENCODING_UTF8 = Encoding.UTF8; - - using (MemoryStream memoryStream = new MemoryStream()) + HttpWebRequest request = CreateWebRequest(url, header); + request.Method = METHOD_POST; + // 分隔符 + string boundary = "----" + DateTime.Now.Ticks.ToString("x"); + request.ContentType = string.Format("multipart/form-data; boundary={0}", boundary); + + // 请求流 + var postStream = new MemoryStream(); + #region 处理Form表单请求内容 + // 文件数据模板 + string fileFormdataTemplate = + "\r\n--" + boundary + + "\r\nContent-Disposition: form-data; name=\"{0}\"; filename=\"{1}\"" + + "\r\nContent-Type: application/octet-stream" + + "\r\n\r\n"; + // 文本数据模板 + string dataFormdataTemplate = + "\r\n--" + boundary + + "\r\nContent-Disposition: form-data; name=\"{0}\"" + + "\r\n\r\n{1}"; + + // 是否有上传文件 + bool hasFile = files != null && files.Count > 0; + if (hasFile) { - // 1.分界线 - string boundary = string.Format("----{0}", DateTime.Now.Ticks.ToString("x")), // 分界线可以自定义参数 - appendBoundary = string.Format("--{0}\r\n", boundary), - endBoundary = string.Format("\r\n--{0}--\r\n", boundary); - - byte[] beginBoundaryBytes = ENCODING_UTF8.GetBytes(appendBoundary), - endBoundaryBytes = ENCODING_UTF8.GetBytes(endBoundary); - - - StringBuilder payload = new StringBuilder(); - - // 2.组装 上传文件附加携带的参数 到内存流中 - if (form != null && form.Count > 0) + // 处理上传文件 + foreach (var fileItem in files) { - ICollection keys = form.Keys; - foreach (string key in keys) + string formdata = null; + // 上传文件 + formdata = string.Format( + fileFormdataTemplate, + fileItem.Name, //表单键 + fileItem.FileName); + + byte[] formdataBytes = null; + // 第一行不需要换行 + if (postStream.Length == 0) { - string boundaryBlock = string.Format("{0}Content-Disposition: form-data; name=\"{1}\"\r\n\r\n{2}\r\n", appendBoundary, key, form[key]); - byte[] boundaryBlockBytes = ENCODING_UTF8.GetBytes(boundaryBlock); - memoryStream.Write(boundaryBlockBytes, 0, boundaryBlockBytes.Length); + formdataBytes = Encoding.UTF8.GetBytes(formdata.Substring(2, formdata.Length - 2)); } - } + else + { + formdataBytes = Encoding.UTF8.GetBytes(formdata); + } + postStream.Write(formdataBytes, 0, formdataBytes.Length); - // 3.组装文件头数据体 到内存流中 - foreach (UploadFile uploadFile in files) + // 写入文件内容 + if (fileItem.FileData != null && fileItem.FileData.Length > 0) + { + postStream.Write(fileItem.FileData, 0, fileItem.FileData.Length); + } + } + } + // 处理文本字段 + foreach (var fieldItem in form) + { + string formdata = null; { - string boundaryBlock = string.Format("{0}Content-Disposition: form-data; name=\"{1}\"; filename=\"{2}\"\r\nContent-Type: application/octet-stream\r\n\r\n", appendBoundary, uploadFile.Name, uploadFile.FileName); - byte[] boundaryBlockBytes = ENCODING_UTF8.GetBytes(boundaryBlock); - memoryStream.Write(boundaryBlockBytes, 0, boundaryBlockBytes.Length); - memoryStream.Write(uploadFile.FileData, 0, uploadFile.FileData.Length); + // 上传文本 + formdata = string.Format( + dataFormdataTemplate, + fieldItem.Key, + fieldItem.Value); } - // 4.组装结束分界线数据体 到内存流中 - memoryStream.Write(endBoundaryBytes, 0, endBoundaryBytes.Length); - - // 5.获取二进制数据,最终需要发送给服务器的数据 - byte[] postBytes = memoryStream.ToArray(); - - // 6.HttpWebRequest 组装 - HttpWebRequest webRequest = CreateWebRequest(url, header); - webRequest.Method = METHOD_POST; - webRequest.ContentType = string.Format("multipart/form-data; boundary={0}", boundary); - webRequest.ContentLength = postBytes.Length; - BindHeader(webRequest, header); - if (Regex.IsMatch(url, "^https://")) + byte[] formdataBytes = null; + // 第一行不需要换行 + if (postStream.Length == 0) { - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls; - ServicePointManager.ServerCertificateValidationCallback = CheckValidationResult; + formdataBytes = Encoding.UTF8.GetBytes(formdata.Substring(2, formdata.Length - 2)); } - - // 7.写入上传请求数据 - using (Stream requestStream = webRequest.GetRequestStream()) + else { - requestStream.Write(postBytes, 0, postBytes.Length); + formdataBytes = Encoding.UTF8.GetBytes(formdata); } - // 8.获取响应 - using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse()) + postStream.Write(formdataBytes, 0, formdataBytes.Length); + } + // 结尾 + var footer = Encoding.UTF8.GetBytes("\r\n--" + boundary + "--\r\n"); + postStream.Write(footer, 0, footer.Length); + #endregion + + request.ContentLength = postStream.Length; + + #region 输入二进制流 + if (postStream != null) + { + postStream.Position = 0; + // 直接写入流 + Stream requestStream = request.GetRequestStream(); + + byte[] buffer = new byte[1024]; + int bytesRead = 0; + while ((bytesRead = postStream.Read(buffer, 0, buffer.Length)) != 0) { - using (StreamReader reader = new StreamReader(webResponse.GetResponseStream(), ENCODING_UTF8)) - { - string body = reader.ReadToEnd(); - return body; - } + requestStream.Write(buffer, 0, bytesRead); } + ////debug + //postStream.Seek(0, SeekOrigin.Begin); + //StreamReader sr = new StreamReader(postStream); + //var postStr = sr.ReadToEnd(); + postStream.Close();//关闭文件访问 + } + #endregion + + HttpWebResponse response = (HttpWebResponse)request.GetResponse(); + if (cookieContainer != null) + { + response.Cookies = cookieContainer.GetCookies(response.ResponseUri); } + using (Stream responseStream = response.GetResponseStream()) + { + using (StreamReader myStreamReader = new StreamReader(responseStream, Encoding.UTF8)) + { + return myStreamReader.ReadToEnd(); + } + } } static bool CheckValidationResult(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors) diff --git a/sop-sdk/sdk-csharp/SDKCSharp/Model/DemoFileUploadModel.cs b/sop-sdk/sdk-csharp/SDKCSharp/Model/DemoFileUploadModel.cs new file mode 100644 index 00000000..225ec042 --- /dev/null +++ b/sop-sdk/sdk-csharp/SDKCSharp/Model/DemoFileUploadModel.cs @@ -0,0 +1,11 @@ +using System; +using Newtonsoft.Json; + +namespace SDKCSharp.Model +{ + public class DemoFileUploadModel + { + [JsonProperty("remark")] + public string Remark { get; set; } + } +} diff --git a/sop-sdk/sdk-csharp/SDKCSharp/Program.cs b/sop-sdk/sdk-csharp/SDKCSharp/Program.cs index d0d1c9de..32ce86ff 100644 --- a/sop-sdk/sdk-csharp/SDKCSharp/Program.cs +++ b/sop-sdk/sdk-csharp/SDKCSharp/Program.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.IO; using SDKCSharp.Client; using SDKCSharp.Common; using SDKCSharp.Model; @@ -27,8 +28,9 @@ namespace SDKTest public static void Main(string[] args) { - TestGet(); - TestCommon(); + //TestGet(); + //TestCommon(); + TestUpload(); } // 标准用法 @@ -90,5 +92,41 @@ namespace SDKTest response.Code, response.Msg, response.SubCode, response.SubMsg); } } + + private static void TestUpload() + { + DemoFileUploadRequest request = new DemoFileUploadRequest(); + + DemoFileUploadModel model = new DemoFileUploadModel + { + Remark = "上传文件参数" + }; + request.BizModel = model; + + string root = Environment.CurrentDirectory; + Console.WriteLine("当前目录{0}", root); + + // 文件上传 + // 将当前目录下的两个文件上传到服务器 + request.AddFile(new UploadFile("file1", root + "/file1.txt")); + request.AddFile(new UploadFile("file2", root + "/file2.txt")); + + DemoFileUploadResponse response = client.Execute(request); + if (response.IsSuccess()) + { + List responseFiles = response.Files; + Console.WriteLine("您上传的文件信息:"); + responseFiles.ForEach(file => + { + Console.WriteLine("上传的文件名:{0},文件大小:{1},文件内容:{2}", file.Filename, file.Size, file.Content); + } + ); + } + else + { + Console.WriteLine("错误, code:{0}, msg:{1}, subCode:{2}, subMsg:{3}", + response.Code, response.Msg, response.SubCode, response.SubMsg); + } + } } } diff --git a/sop-sdk/sdk-csharp/SDKCSharp/Request/BaseRequest.cs b/sop-sdk/sdk-csharp/SDKCSharp/Request/BaseRequest.cs index a4657abc..bde0d217 100644 --- a/sop-sdk/sdk-csharp/SDKCSharp/Request/BaseRequest.cs +++ b/sop-sdk/sdk-csharp/SDKCSharp/Request/BaseRequest.cs @@ -60,6 +60,18 @@ namespace SDKCSharp.Request this.version = version == null ? SdkConfig.DEFAULT_VERSION : version; } + /// + /// 添加上传文件 + /// + /// File. + public void AddFile(UploadFile file) + { + if(this.files == null) + { + this.files = new List(); + } + this.files.Add(file); + } /// /// 创建请求表单 diff --git a/sop-sdk/sdk-csharp/SDKCSharp/Request/DemoFileUploadRequest.cs b/sop-sdk/sdk-csharp/SDKCSharp/Request/DemoFileUploadRequest.cs new file mode 100644 index 00000000..cd88001a --- /dev/null +++ b/sop-sdk/sdk-csharp/SDKCSharp/Request/DemoFileUploadRequest.cs @@ -0,0 +1,13 @@ +using System; +using SDKCSharp.Response; + +namespace SDKCSharp.Request +{ + public class DemoFileUploadRequest : BaseRequest + { + public override string GetMethod() + { + return "demo.file.upload"; + } + } +} diff --git a/sop-sdk/sdk-csharp/SDKCSharp/Response/DemoFileUploadResponse.cs b/sop-sdk/sdk-csharp/SDKCSharp/Response/DemoFileUploadResponse.cs new file mode 100644 index 00000000..3e79716b --- /dev/null +++ b/sop-sdk/sdk-csharp/SDKCSharp/Response/DemoFileUploadResponse.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace SDKCSharp.Response +{ + public class DemoFileUploadResponse : BaseResponse + { + [JsonProperty("files")] + private List files = new List(); + + public List Files { get => files; set => files = value; } + + public class FileMeta + { + + public FileMeta(String filename, long size, String content) + { + this.Filename = filename; + this.Size = size; + this.Content = content; + } + + public FileMeta() + { + } + + [JsonProperty("filename")] + public String Filename { get; set; } + + [JsonProperty("size")] + public long Size { get; set; } + + [JsonProperty("content")] + public String Content { get; set; } + } + } +} diff --git a/sop-sdk/sdk-csharp/SDKCSharp/file1.txt b/sop-sdk/sdk-csharp/SDKCSharp/file1.txt new file mode 100644 index 00000000..3b760675 --- /dev/null +++ b/sop-sdk/sdk-csharp/SDKCSharp/file1.txt @@ -0,0 +1 @@ +file1 content,内容1 \ No newline at end of file diff --git a/sop-sdk/sdk-csharp/SDKCSharp/file2.txt b/sop-sdk/sdk-csharp/SDKCSharp/file2.txt new file mode 100644 index 00000000..fd7656f3 --- /dev/null +++ b/sop-sdk/sdk-csharp/SDKCSharp/file2.txt @@ -0,0 +1 @@ +file2 content,内容2 \ No newline at end of file diff --git a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenClient.java b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenClient.java index 43d5e62c..34d3d761 100644 --- a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenClient.java +++ b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/client/OpenClient.java @@ -25,7 +25,7 @@ public class OpenClient { private static final Log log = LogFactory.getLog(OpenClient.class); private static final OpenConfig DEFAULT_CONFIG = new OpenConfig(); - public static final String ERROR_RESPONSE_KEY = "error_response"; + private static final String ERROR_RESPONSE_KEY = "error_response"; private String url; private String appId; diff --git a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/BaseRequest.java b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/BaseRequest.java index 58060e5c..53d328d9 100644 --- a/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/BaseRequest.java +++ b/sop-sdk/sdk-java/src/main/java/com/gitee/sop/sdk/request/BaseRequest.java @@ -9,6 +9,7 @@ import com.gitee.sop.sdk.response.BaseResponse; import com.gitee.sop.sdk.util.ClassUtil; import java.text.SimpleDateFormat; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -71,6 +72,17 @@ public abstract class BaseRequest { return SdkConfig.DEFAULT_VERSION; } + /** + * 添加上传文件 + * @param file + */ + public void addFile(UploadFile file) { + if (this.files == null) { + this.files = new ArrayList<>(); + } + this.files.add(file); + } + public RequestForm createRequestForm(OpenConfig openConfig) { // 公共请求参数 Map params = new HashMap(); diff --git a/sop-sdk/sdk-java/src/main/resources/file1.txt b/sop-sdk/sdk-java/src/main/resources/file1.txt index 2e80f50e..3b760675 100644 --- a/sop-sdk/sdk-java/src/main/resources/file1.txt +++ b/sop-sdk/sdk-java/src/main/resources/file1.txt @@ -1 +1 @@ -file1 content \ No newline at end of file +file1 content,内容1 \ No newline at end of file diff --git a/sop-sdk/sdk-java/src/main/resources/file2.txt b/sop-sdk/sdk-java/src/main/resources/file2.txt index ef01a4a0..fd7656f3 100644 --- a/sop-sdk/sdk-java/src/main/resources/file2.txt +++ b/sop-sdk/sdk-java/src/main/resources/file2.txt @@ -1 +1 @@ -file2 content... \ No newline at end of file +file2 content,内容2 \ No newline at end of file diff --git a/sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/SdkTest.java b/sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/SdkTest.java index d5a23f4c..cc2daf37 100644 --- a/sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/SdkTest.java +++ b/sop-sdk/sdk-java/src/test/java/com/gitee/sop/sdk/SdkTest.java @@ -17,7 +17,6 @@ import org.junit.Test; import java.io.File; import java.io.IOException; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -87,12 +86,11 @@ public class SdkTest extends TestCase { model.setRemark("上传文件参数"); request.setBizModel(model); - List files = new ArrayList<>(); String root = System.getProperty("user.dir"); System.out.println(root); - files.add(new UploadFile("file1", new File(root + "/src/main/resources/file1.txt"))); - files.add(new UploadFile("file2", new File(root + "/src/main/resources/file2.txt"))); - request.setFiles(files); + // 这里演示将resources下的两个文件上传到服务器 + request.addFile(new UploadFile("file1", new File(root + "/src/main/resources/file1.txt"))); + request.addFile(new UploadFile("file2", new File(root + "/src/main/resources/file2.txt"))); DemoFileUploadResponse response = client.execute(request);