diff --git a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PostResultFilter.java b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PostResultFilter.java index bafb3767..76f07921 100644 --- a/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PostResultFilter.java +++ b/sop-common/sop-gateway-common/src/main/java/com/gitee/sop/gatewaycommon/zuul/filter/PostResultFilter.java @@ -4,12 +4,16 @@ import com.gitee.sop.gatewaycommon.bean.ApiConfig; import com.gitee.sop.gatewaycommon.bean.ApiContext; import com.gitee.sop.gatewaycommon.bean.SopConstants; import com.gitee.sop.gatewaycommon.result.ResultExecutor; +import com.netflix.util.Pair; import com.netflix.zuul.context.RequestContext; import com.netflix.zuul.exception.ZuulException; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; import org.springframework.cloud.netflix.zuul.filters.support.FilterConstants; +import org.springframework.http.MediaType; import java.io.InputStream; +import java.util.List; /** * 合并微服务结果,统一返回格式 @@ -33,6 +37,14 @@ public class PostResultFilter extends BaseZuulFilter { if (requestContext.getResponse().isCommitted()) { return null; } + List> zuulResponseHeaders = requestContext.getZuulResponseHeaders(); + boolean isDownloadRequest = zuulResponseHeaders + .stream() + .anyMatch(pair -> StringUtils.contains(pair.second(), MediaType.APPLICATION_OCTET_STREAM_VALUE)); + // 如果是文件下载直接返回 + if (isDownloadRequest) { + return null; + } InputStream responseDataStream = requestContext.getResponseDataStream(); ApiConfig apiConfig = ApiContext.getApiConfig(); ResultExecutor resultExecutor = apiConfig.getZuulResultExecutor(); diff --git a/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/DownloadController.java b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/DownloadController.java new file mode 100644 index 00000000..88e2091b --- /dev/null +++ b/sop-example/sop-story/sop-story-web/src/main/java/com/gitee/sop/storyweb/controller/DownloadController.java @@ -0,0 +1,37 @@ +package com.gitee.sop.storyweb.controller; + +import com.gitee.sop.servercommon.annotation.ApiMapping; +import com.gitee.sop.storyweb.controller.param.StoryParam; +import org.apache.commons.io.FileUtils; +import org.springframework.core.io.ClassPathResource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; + +import java.io.File; +import java.io.IOException; + +/** + * 演示文件下载 + * + * @author tanghc + */ +@Controller +public class DownloadController { + + @ApiMapping(value = "story.download") + public ResponseEntity export(StoryParam param) throws IOException { + + HttpHeaders headers = new HttpHeaders(); + // 假设下载classpath下的application.properties文件 + ClassPathResource resource = new ClassPathResource("/application.properties"); + File file = resource.getFile(); + + headers.setContentType(MediaType.APPLICATION_OCTET_STREAM); + headers.setContentDispositionFormData("attachment", file.getName()); + + return new ResponseEntity<>(FileUtils.readFileToByteArray(file), headers, HttpStatus.OK); + } +} diff --git a/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenClient.cs b/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenClient.cs index 427db732..ae011f1e 100644 --- a/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenClient.cs +++ b/sop-sdk/sdk-csharp/SDKCSharp/Client/OpenClient.cs @@ -20,46 +20,108 @@ namespace SDKCSharp.Client /// public class OpenClient { - + /// + /// 默认配置 + /// private static OpenConfig DEFAULT_CONFIG = new OpenConfig(); private Dictionary header = new Dictionary(); - + /// + /// 接口请求url + /// private string url; + + /// + /// 平台提供的appId + /// private string appId; + + /// + /// 开放平台提供的私钥 + /// private string privateKey; + + /// + /// 开放平台提供的公钥 + /// private string publicKeyPlatform; + /// + /// 配置项 + /// private OpenConfig openConfig; + + /// + /// 请求对象 + /// private OpenRequest openRequest; - private DataNameBuilder dataNameBuilder; + /// + /// 节点处理 + /// + private DataNameBuilder dataNameBuilder; + /// + /// 构建请求客户端 + /// + /// 接口url + /// 平台分配的appId + /// 平台分配的私钥 public OpenClient(string url, string appId, string privateKey) : this(url, appId, privateKey,false, DEFAULT_CONFIG) { } + /// + /// 构建请求客户端 + /// + /// 接口url + /// 平台分配的appId + /// 平台分配的私钥 + /// 平台分配的公钥 public OpenClient(string url, string appId, string privateKey, string publicKeyPlatform) : this(url, appId, privateKey) { this.publicKeyPlatform = publicKeyPlatform; } + /// + /// 构建请求客户端 + /// + /// 接口url + /// 平台分配的appId + /// 平台分配的私钥 + /// 如果设置 true 从文件中加载私钥 public OpenClient(string url, string appId, string privateKey, bool priKeyFromFile) : this(url, appId, privateKey, priKeyFromFile, DEFAULT_CONFIG) { } + /// + /// 构建请求客户端 + /// + /// 接口url + /// 平台分配的appId + /// 平台分配的私钥 + /// 如果设置 true 从文件中加载私钥 + /// 平台分配的公钥 public OpenClient(string url, string appId, string privateKey, bool priKeyFromFile, string publicKeyPlatform) : this(url, appId, privateKey, priKeyFromFile) { this.publicKeyPlatform = publicKeyPlatform; } + /// + /// 构建请求客户端 + /// + /// 接口url + /// 平台分配的appId + /// 平台分配的私钥 + /// 如果设置 true 从文件中加载私钥 + /// 配置项 public OpenClient(string url, string appId, string privateKey,bool priKeyFromFile, OpenConfig openConfig) { this.url = url; @@ -75,6 +137,15 @@ namespace SDKCSharp.Client this.dataNameBuilder = openConfig.DataNameBuilder; } + /// + /// 构建请求客户端 + /// + /// 接口url + /// 平台分配的appId + /// 平台分配的私钥 + /// 如果设置 true 从文件中加载私钥 + /// 平台分配的公钥 + /// 配置项 public OpenClient(string url, string appId, string privateKey, bool priKeyFromFile, string publicKeyPlatform, OpenConfig openConfig) : this(url, appId, privateKey, priKeyFromFile, openConfig) { @@ -203,6 +274,14 @@ namespace SDKCSharp.Client } } + /// + /// 构建业务json内容。 + /// 假设返回的结果是:{"alipay_story_get_response":{"msg":"Success","code":"10000","name":"海底小纵队","id":1},"sign":"xxx"} + /// 将解析得到:{"msg":"Success","code":"10000","name":"海底小纵队","id":1} + /// + /// The biz json. + /// 根节点名称. + /// 返回内容. protected virtual string BuildBizJson(string rootNodeName, string body) { int indexOfRootNode = body.IndexOf(rootNodeName); @@ -219,6 +298,14 @@ namespace SDKCSharp.Client return result; } + /// + /// 获取业务结果,如下结果:{"alipay_story_get_response":{"msg":"Success","code":"10000","name":"海底小纵队","id":1},"sign":"xxx"} + /// 将返回:{"msg":"Success","code":"10000","name":"海底小纵队","id":1} + /// + /// The json node data. + /// Body. + /// Root node. + /// Index of root node. protected virtual string BuildJsonNodeData(string body, string rootNode, int indexOfRootNode) { int signDataStartIndex = indexOfRootNode + rootNode.Length + 2; diff --git a/sop-test/src/main/java/com/gitee/sop/test/HttpTool.java b/sop-test/src/main/java/com/gitee/sop/test/HttpTool.java index 39025dc0..671347dc 100644 --- a/sop-test/src/main/java/com/gitee/sop/test/HttpTool.java +++ b/sop-test/src/main/java/com/gitee/sop/test/HttpTool.java @@ -13,6 +13,7 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import okhttp3.ResponseBody; import org.apache.commons.codec.digest.DigestUtils; import java.io.ByteArrayOutputStream; @@ -51,14 +52,17 @@ public class HttpTool { protected void initHttpClient(HttpToolConfig httpToolConfig) { httpClient = new OkHttpClient.Builder() - .connectTimeout(httpToolConfig.connectTimeoutSeconds, TimeUnit.SECONDS) // 设置链接超时时间,默认10秒 + // 设置链接超时时间,默认10秒 + .connectTimeout(httpToolConfig.connectTimeoutSeconds, TimeUnit.SECONDS) .readTimeout(httpToolConfig.readTimeoutSeconds, TimeUnit.SECONDS) .writeTimeout(httpToolConfig.writeTimeoutSeconds, TimeUnit.SECONDS) .cookieJar(new CookieJar() { + @Override public void saveFromResponse(HttpUrl httpUrl, List list) { cookieStore.put(httpUrl.host(), list); } + @Override public List loadForRequest(HttpUrl httpUrl) { List cookies = cookieStore.get(httpUrl.host()); return cookies != null ? cookies : new ArrayList(); @@ -204,22 +208,24 @@ public class HttpTool { * @return * @throws IOException */ - public String requestFile(String url, Map form, Map header, List files) + public String requestFile(String url, Map form, Map header, List files) throws IOException { // 创建MultipartBody.Builder,用于添加请求的数据 MultipartBody.Builder bodyBuilder = new MultipartBody.Builder(); bodyBuilder.setType(MultipartBody.FORM); for (UploadFile uploadFile : files) { - bodyBuilder.addFormDataPart(uploadFile.getName(), // 请求的名字 - uploadFile.getFileName(), // 文件的文字,服务器端用来解析的 - RequestBody.create(null, uploadFile.getFileData()) // 创建RequestBody,把上传的文件放入 + // 请求的名字 + bodyBuilder.addFormDataPart(uploadFile.getName(), + // 文件的文字,服务器端用来解析的 + uploadFile.getFileName(), + // 创建RequestBody,把上传的文件放入 + RequestBody.create(null, uploadFile.getFileData()) ); } - Set> entrySet = form.entrySet(); - for (Map.Entry entry : entrySet) { - bodyBuilder.addFormDataPart(entry.getKey(), entry.getValue()); + for (Map.Entry entry : form.entrySet()) { + bodyBuilder.addFormDataPart(entry.getKey(), String.valueOf(entry.getValue())); } RequestBody requestBody = bodyBuilder.build(); @@ -238,6 +244,31 @@ public class HttpTool { } } + /** + * 下载文件 + * + * @param url 请求url + * @param form 请求数据 + * @param header header + * @return 返回文件流 + * @throws IOException + */ + public InputStream downloadFile(String url, Map form, Map header) throws IOException { + Request.Builder requestBuilder = buildRequestBuilder(url, form, HTTPMethod.GET); + // 添加header + addHeader(requestBuilder, header); + + Request request = requestBuilder.build(); + Response response = httpClient + .newCall(request) + .execute(); + if (response.isSuccessful()) { + ResponseBody body = response.body(); + return body == null ? null : body.byteStream(); + } + return null; + } + private void addHeader(Request.Builder builder, Map header) { if (header != null) { Set> entrySet = header.entrySet(); @@ -256,10 +287,15 @@ public class HttpTool { } public enum HTTPMethod { + /** http GET */ GET, + /** http POST */ POST, + /** http PUT */ PUT, + /** http HEAD */ HEAD, + /** http DELETE */ DELETE; private HTTPMethod() { diff --git a/sop-test/src/test/java/com/gitee/sop/test/DownloadTest.java b/sop-test/src/test/java/com/gitee/sop/test/DownloadTest.java new file mode 100644 index 00000000..84ebf9bb --- /dev/null +++ b/sop-test/src/test/java/com/gitee/sop/test/DownloadTest.java @@ -0,0 +1,67 @@ +package com.gitee.sop.test; + +import com.alibaba.fastjson.JSON; +import com.gitee.sop.test.alipay.AlipayApiException; +import com.gitee.sop.test.alipay.AlipaySignature; +import org.apache.commons.io.IOUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +/** + * 文件下载 + * @author tanghc + */ +public class DownloadTest extends TestBase{ + + String url = "http://localhost:8081/api"; // zuul + String appId = "2019032617262200001"; + // 支付宝私钥 + String privateKey = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ="; + + + public void testDownload() throws AlipayApiException, IOException { + // 公共请求参数 + Map params = new HashMap(); + params.put("app_id", appId); + params.put("method", "story.download"); + params.put("format", "json"); + params.put("charset", "utf-8"); + params.put("sign_type", "RSA2"); + params.put("timestamp", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); + params.put("version", "1.0"); + + // 业务参数 + Map bizContent = new HashMap<>(); + bizContent.put("id", "1"); + bizContent.put("name", "葫芦娃"); + + params.put("biz_content", JSON.toJSONString(bizContent)); + + System.out.println("----------- 请求信息 -----------"); + System.out.println("请求参数:" + buildParamQuery(params)); + System.out.println("商户秘钥:" + privateKey); + String content = AlipaySignature.getSignContent(params); + System.out.println("待签名内容:" + content); + String sign = AlipaySignature.rsa256Sign(content, privateKey, "utf-8"); + System.out.println("签名(sign):" + sign); + + params.put("sign", sign); + + System.out.println("----------- 返回结果 -----------"); + InputStream fileInputStream = download(url, params); + String fileContent = IOUtils.toString(fileInputStream, StandardCharsets.UTF_8); + // 输出文件内容 + System.out.println("文件内容:"); + System.out.println(fileContent); + + // 写文件到本地 + //FileUtils.copyInputStreamToFile(fileInputStream, new File("D:/a.txt")); + } + +} diff --git a/sop-test/src/test/java/com/gitee/sop/test/TestBase.java b/sop-test/src/test/java/com/gitee/sop/test/TestBase.java index bc34209c..adbb3538 100644 --- a/sop-test/src/test/java/com/gitee/sop/test/TestBase.java +++ b/sop-test/src/test/java/com/gitee/sop/test/TestBase.java @@ -4,6 +4,7 @@ import com.alibaba.fastjson.JSON; import junit.framework.TestCase; import java.io.IOException; +import java.io.InputStream; import java.util.Collections; import java.util.Map; @@ -50,6 +51,20 @@ public class TestBase extends TestCase { } } + /** + * 下载文件 + * @param url + * @param params + * @return 返回文件流 + */ + public static InputStream download(String url, Map params) { + try { + return httpTool.downloadFile(url, params, null); + } catch (IOException e) { + throw new RuntimeException("网络请求异常", e); + } + } + protected static String buildParamQuery(Map params) { StringBuilder sb = new StringBuilder(); for (Map.Entry entry : params.entrySet()) {