Skip to content

Commit adce452

Browse files
authored
Merge pull request #518 from qiniu/ys_dev
支持构建 Download url & 基础 api 支持流姿势上传
2 parents 74ab710 + ad30ab4 commit adce452

20 files changed

+831
-54
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
11
# Changelog
2+
## 7.5.1 (2021-04-29)
3+
4+
## 增加
5+
* 上传服务基础上传数据 api 支持 InputStream
6+
* 支持构建 DownloadUrl 功能
7+
28

39
## 7.5.0 (2021-04-15)
410

src/main/java/com/qiniu/common/Constants.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public final class Constants {
99
/**
1010
* 版本号
1111
*/
12-
public static final String VERSION = "7.5.0";
12+
public static final String VERSION = "7.5.1";
1313
/**
1414
* 块大小,不能改变
1515
*/

src/main/java/com/qiniu/http/Client.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -279,12 +279,12 @@ public Response post(String url, byte[] body, int offset, int size,
279279
return post(url, rbody, headers);
280280
}
281281

282-
private Response post(String url, RequestBody body, StringMap headers) throws QiniuException {
282+
public Response post(String url, RequestBody body, StringMap headers) throws QiniuException {
283283
Request.Builder requestBuilder = new Request.Builder().url(url).post(body);
284284
return send(requestBuilder, headers);
285285
}
286286

287-
private Response put(String url, RequestBody body, StringMap headers) throws QiniuException {
287+
public Response put(String url, RequestBody body, StringMap headers) throws QiniuException {
288288
Request.Builder requestBuilder = new Request.Builder().url(url).put(body);
289289
return send(requestBuilder, headers);
290290
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.qiniu.http;
2+
3+
import okhttp3.MediaType;
4+
import okhttp3.RequestBody;
5+
import okio.BufferedSink;
6+
import okio.Okio;
7+
import okio.Source;
8+
9+
import java.io.EOFException;
10+
import java.io.IOException;
11+
import java.io.InputStream;
12+
13+
public class RequestStreamBody extends RequestBody {
14+
15+
private long limitSize = -1;
16+
private long sinkSize = 1024 * 100;
17+
private final MediaType type;
18+
private final InputStream stream;
19+
20+
/**
21+
* 构造函数
22+
*
23+
* @param stream 请求数据流
24+
* @param contentType 请求数据类型
25+
*/
26+
public RequestStreamBody(InputStream stream, String contentType) {
27+
this.stream = stream;
28+
this.type = MediaType.parse(contentType);
29+
}
30+
31+
/**
32+
* 构造函数
33+
*
34+
* @param stream 请求数据流
35+
* @param contentType 请求数据类型
36+
*/
37+
public RequestStreamBody(InputStream stream, MediaType contentType) {
38+
this.stream = stream;
39+
this.type = contentType;
40+
}
41+
42+
/**
43+
* 构造函数
44+
*
45+
* @param stream 请求数据流
46+
* @param contentType 请求数据类型
47+
* @param limitSize 最大读取 stream 的大小;为 -1 时不限制读取所有
48+
*/
49+
public RequestStreamBody(InputStream stream, MediaType contentType, long limitSize) {
50+
this.stream = stream;
51+
this.type = contentType;
52+
this.limitSize = limitSize;
53+
}
54+
55+
/**
56+
* 配置请求时,每次从流中读取的数据大小
57+
*
58+
* @param sinkSize 每次从流中读取的数据大小
59+
* @return RequestStreamBody
60+
* @see RequestStreamBody#writeTo(BufferedSink)
61+
*/
62+
public RequestStreamBody setSinkSize(long sinkSize) {
63+
if (sinkSize > 0) {
64+
this.sinkSize = sinkSize;
65+
}
66+
return this;
67+
}
68+
69+
@Override
70+
public MediaType contentType() {
71+
return type;
72+
}
73+
74+
@Override
75+
public void writeTo(BufferedSink sink) throws IOException {
76+
try (Source source = Okio.source(stream)) {
77+
int offset = 0;
78+
while (limitSize < 0 || offset < limitSize) {
79+
long byteSize = sinkSize;
80+
if (offset < limitSize) {
81+
byteSize = Math.min(sinkSize, limitSize - offset);
82+
}
83+
84+
try {
85+
sink.write(source, byteSize);
86+
sink.flush();
87+
offset += byteSize;
88+
} catch (EOFException e) {
89+
break;
90+
}
91+
}
92+
}
93+
}
94+
}

src/main/java/com/qiniu/http/Response.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,10 @@ public String contentType() {
241241
return ctype(response);
242242
}
243243

244+
public String header(String name, String defaultValue) {
245+
return response.header(name, defaultValue);
246+
}
247+
244248
public boolean isJson() {
245249
return contentType().equals(Client.JsonMime);
246250
}

src/main/java/com/qiniu/processing/OperationManager.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ public OperationManager(Auth auth, Client client) {
5252
this.configuration = new Configuration();
5353
}
5454

55+
public OperationManager(Auth auth, Configuration cfg, Client client) {
56+
this.auth = auth;
57+
this.client = client;
58+
this.configuration = cfg;
59+
}
60+
5561
/**
5662
* 发送请求对空间中的文件进行持久化处理
5763
*

src/main/java/com/qiniu/storage/Api.java

Lines changed: 69 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@
33
import com.qiniu.common.QiniuException;
44
import com.qiniu.http.Client;
55
import com.qiniu.http.MethodType;
6+
import com.qiniu.http.RequestStreamBody;
67
import com.qiniu.util.StringMap;
78
import com.qiniu.util.StringUtils;
9+
import okhttp3.MediaType;
10+
import okhttp3.RequestBody;
11+
import okio.BufferedSink;
812

13+
import java.io.InputStream;
914
import java.net.URL;
1015
import java.net.URLEncoder;
1116
import java.util.*;
@@ -35,14 +40,11 @@ protected com.qiniu.http.Response requestByClient(Request request) throws QiniuE
3540
if (request.method == MethodType.GET) {
3641
return client.get(request.getUrl().toString(), request.getHeader());
3742
} else if (request.method == MethodType.POST) {
38-
return client.post(request.getUrl().toString(), request.body, request.bodyOffset, request.bodySize,
39-
request.getHeader(), request.bodyContentType);
43+
return client.post(request.getUrl().toString(), request.getRequestBody(), request.getHeader());
4044
} else if (request.method == MethodType.PUT) {
41-
return client.put(request.getUrl().toString(), request.body, request.bodyOffset, request.bodySize,
42-
request.getHeader(), request.bodyContentType);
45+
return client.put(request.getUrl().toString(), request.getRequestBody(), request.getHeader());
4346
} else if (request.method == MethodType.DELETE) {
44-
return client.delete(request.getUrl().toString(), request.body, request.bodyOffset, request.bodySize,
45-
request.getHeader(), request.bodyContentType);
47+
return client.delete(request.getUrl().toString(), request.getRequestBody(), request.getHeader());
4648
} else {
4749
throw QiniuException.unrecoverable("暂不支持这种请求方式");
4850
}
@@ -91,12 +93,14 @@ public static class Request {
9193
private final Map<String, String> header = new HashMap<>();
9294

9395
/**
94-
* 请求数据是在 body 中,从 bodyOffset 开始,获取 bodySize 大小的数据
96+
* 请求 body
9597
*/
96-
private byte[] body = new byte[0];
97-
private int bodySize = 0;
98-
private int bodyOffset = 0;
99-
private String bodyContentType = Client.DefaultMime;
98+
private RequestBody body;
99+
/**
100+
* 请求时,每次从流中读取的数据大小
101+
* 注: body 使用 InputStream 时才有效
102+
*/
103+
private long streamBodySinkSize = 1024 * 10;
100104

101105
/**
102106
* 构造请求对象
@@ -174,7 +178,7 @@ protected void buildPath() throws QiniuException {
174178
* @param value value
175179
*/
176180
protected void addQueryPair(String key, String value) {
177-
if (StringUtils.isNullOrEmpty(key) || value == null) {
181+
if (StringUtils.isNullOrEmpty(key)) {
178182
return;
179183
}
180184
queryPairs.add(new Pair<String, String>(key, value));
@@ -237,7 +241,7 @@ protected void setMethod(MethodType method) {
237241
* @param key key
238242
* @param value value
239243
*/
240-
protected void addHeaderField(String key, String value) {
244+
public void addHeaderField(String key, String value) {
241245
if (StringUtils.isNullOrEmpty(key) || StringUtils.isNullOrEmpty(value)) {
242246
return;
243247
}
@@ -292,16 +296,59 @@ public URL getUrl() throws QiniuException {
292296
* @param contentType 请求数据类型
293297
*/
294298
protected void setBody(byte[] body, int offset, int size, String contentType) {
295-
this.body = body;
296-
this.bodyOffset = offset;
297-
this.bodySize = size;
298-
if (!StringUtils.isNullOrEmpty(contentType)) {
299-
this.bodyContentType = contentType;
299+
if (StringUtils.isNullOrEmpty(contentType)) {
300+
contentType = Client.DefaultMime;
301+
}
302+
MediaType type = MediaType.parse(contentType);
303+
this.body = RequestBody.create(type, body, offset, size);
304+
}
305+
306+
/**
307+
* 设置请求体
308+
*
309+
* @param body 请求数据源
310+
* @param contentType 请求数据类型
311+
* @param limitSize 最大读取 body 的大小;body 有多余则被舍弃;body 不足则会上传多有 body;
312+
* 如果提前不知道 body 大小,但想上传所有 body,limitSize 设置为 -1 即可;
313+
*/
314+
protected void setBody(InputStream body, String contentType, long limitSize) {
315+
if (StringUtils.isNullOrEmpty(contentType)) {
316+
contentType = Client.DefaultMime;
300317
}
318+
MediaType type = MediaType.parse(contentType);
319+
this.body = new RequestStreamBody(body, type, limitSize);
301320
}
302321

322+
/**
323+
* 使用 streamBody 时,每次读取 streamBody 的大小,读取后发送
324+
* 默认:{@link Api.Request#streamBodySinkSize}
325+
* 相关:{@link RequestStreamBody#writeTo(BufferedSink) sinkSize}
326+
*
327+
* @param streamBodySinkSize 每次读取 streamBody 的大小
328+
*/
329+
public Request setStreamBodySinkSize(long streamBodySinkSize) {
330+
this.streamBodySinkSize = streamBodySinkSize;
331+
return this;
332+
}
333+
334+
/**
335+
* 是否有请求体
336+
*
337+
* @return 是否有请求体
338+
*/
303339
public boolean hasBody() {
304-
return body != null && body.length > 0 && bodySize > 0;
340+
return body != null;
341+
}
342+
343+
private RequestBody getRequestBody() {
344+
if (hasBody()) {
345+
if (body instanceof RequestStreamBody) {
346+
((RequestStreamBody) body).setSinkSize(streamBodySinkSize);
347+
}
348+
return body;
349+
} else {
350+
return RequestBody.create(null, new byte[0]);
351+
}
305352
}
306353

307354
/**
@@ -310,11 +357,7 @@ public boolean hasBody() {
310357
* @throws QiniuException
311358
*/
312359
protected void buildBodyInfo() throws QiniuException {
313-
if (body == null) {
314-
body = new byte[0];
315-
bodySize = 0;
316-
bodyOffset = 0;
317-
}
360+
318361
}
319362

320363
/**
@@ -328,10 +371,7 @@ protected void prepareToRequest() throws QiniuException {
328371
buildBodyInfo();
329372
}
330373

331-
void test() {
332-
}
333-
334-
private static class Pair<K, V> {
374+
protected static class Pair<K, V> {
335375

336376
/**
337377
* Key of this <code>Pair</code>.
@@ -367,7 +407,7 @@ V getValue() {
367407
* @param key The key for this pair
368408
* @param value The value to use for this pair
369409
*/
370-
Pair(K key, V value) {
410+
protected Pair(K key, V value) {
371411
this.key = key;
372412
this.value = value;
373413
}

src/main/java/com/qiniu/storage/ApiUploadV1MakeBlock.java

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import com.qiniu.http.Client;
55
import com.qiniu.http.MethodType;
66

7+
import java.io.InputStream;
8+
79
/**
810
* 分片上传 v1 版 api: 创建块
911
* 本接口用于为后续分片上传创建一个新的 block,同时上传该块第一个 chunk 数据。
@@ -83,8 +85,12 @@ public Request(String urlPrefix, String token, Integer blockSize) {
8385
}
8486

8587
/**
86-
* 配置块第一个上传片数据【必须】
88+
* 配置块第一个上传片数据
89+
* 块数据 size 必须不大于 4M,block 中所有 chunk 的 size 总和必须为 4M, SDK 内部不做 block/chunk size 检测
8790
* 块数据:在 data 中,从 offset 开始的 size 大小的数据
91+
* 注:
92+
* 必须通过 {@link ApiUploadV1MakeBlock.Request#setFirstChunkData(byte[], int, int, String)} 或
93+
* {@link ApiUploadV1MakeBlock.Request#setFirstChunkData(InputStream, String, long)} 配置块第一个上传片数据
8894
*
8995
* @param data 块数据源
9096
* @param offset 块数据在 data 中的偏移量
@@ -97,6 +103,24 @@ public Request setFirstChunkData(byte[] data, int offset, int size, String conte
97103
return this;
98104
}
99105

106+
/**
107+
* 配置块第一个上传片数据
108+
* 块数据 size 必须不大于 4M,block 中所有 chunk 的 size 总和必须为 4M, SDK 内部不做 block/chunk size 检测
109+
* 注:
110+
* 必须通过 {@link ApiUploadV1MakeBlock.Request#setFirstChunkData(byte[], int, int, String)} 或
111+
* {@link ApiUploadV1MakeBlock.Request#setFirstChunkData(InputStream, String, long)} 配置块第一个上传片数据
112+
*
113+
* @param data 块数据源
114+
* @param contentType 块数据类型
115+
* @param limitSize 最大读取 data 的大小;data 有多余则被舍弃;data 不足则会上传多有 data;
116+
* 如果提前不知道 data 大小,但想上传所有 data,limitSize 设置为 -1 即可;
117+
* @return Request
118+
*/
119+
public Request setFirstChunkData(InputStream data, String contentType, long limitSize) {
120+
super.setBody(data, contentType, limitSize);
121+
return this;
122+
}
123+
100124
@Override
101125
protected void buildPath() throws QiniuException {
102126
if (blockSize == null) {
@@ -111,7 +135,7 @@ protected void buildPath() throws QiniuException {
111135
@Override
112136
protected void buildBodyInfo() throws QiniuException {
113137
if (!hasBody()) {
114-
ApiUtils.throwInvalidRequestParamException("block data");
138+
ApiUtils.throwInvalidRequestParamException("block first chunk data");
115139
}
116140
}
117141
}

0 commit comments

Comments
 (0)