使用原生 MinIO SDK 贯通上传流程,提交一个简易的前端测试页面。
This commit is contained in:
parent
701a6be443
commit
37f2ca2593
|
@ -14,11 +14,7 @@
|
|||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-config</artifactId>
|
||||
<artifactId>minio-plus-all-spring-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<!--Swagger工具包 knife4j -->
|
||||
<dependency>
|
||||
|
@ -30,22 +26,10 @@
|
|||
<groupId>com.baomidou</groupId>
|
||||
<artifactId>mybatis-plus-boot-starter</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
|
|
|
@ -4,15 +4,16 @@ import org.springframework.boot.SpringApplication;
|
|||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
|
||||
/**
|
||||
* 标准版示例工程启动类
|
||||
* @author contact@liuxp.me
|
||||
* @since 2024-05-22
|
||||
*/
|
||||
|
||||
@SpringBootApplication
|
||||
public class MinioPlusApplication {
|
||||
public class OfficialApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(MinioPlusApplication.class, args);
|
||||
SpringApplication.run(OfficialApplication.class, args);
|
||||
}
|
||||
|
||||
}
|
|
@ -17,6 +17,8 @@ spring:
|
|||
time-zone: GMT+8
|
||||
default-property-inclusion: non_null # 全局jackson配置
|
||||
mvc:
|
||||
# 放开 Spring Boot 项目中 /static 目录下静态资源的拦截
|
||||
static-path-pattern: /static/**
|
||||
pathmatch:
|
||||
# Springfox使用的路径匹配是基于AntPathMatcher的,而Spring Boot 2.6.X使用的是PathPatternMatcher所以需要配置此参数
|
||||
matching-strategy: ant_path_matcher
|
||||
|
@ -42,7 +44,7 @@ minioplus:
|
|||
# 存储引擎地址,如配置为local则不用配置
|
||||
backend: http://localhost:9000
|
||||
# 浏览器访问地址,文件、图片上传下载访问地址代理,如果minio被nginx代理,需要配置这个参数为代理后的前端访问地址
|
||||
browser-url: http://localhost
|
||||
browser-url: http://localhost:9000
|
||||
# 授权key
|
||||
key: minioadmin
|
||||
# 密钥
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
body{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.y-center {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.x-center {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.x-right {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.nav-bar {
|
||||
border-bottom: 1px solid #ccc;
|
||||
height: 60px;
|
||||
padding: 0 60px;
|
||||
}
|
||||
|
||||
.nav-logo {
|
||||
font-size: 26px;
|
||||
color: #EB735F;
|
||||
}
|
||||
.nav-body {
|
||||
|
||||
}
|
||||
.nav-body-item{
|
||||
cursor: pointer;
|
||||
padding: 0 15px;
|
||||
height: 100%;
|
||||
margin-right: 10px;
|
||||
}
|
||||
.nav-body-item-so{
|
||||
border-radius: 40px;
|
||||
background:none;
|
||||
outline:none;
|
||||
border:none;
|
||||
height: 30px;
|
||||
text-indent: 10px;
|
||||
padding-right: 35px;
|
||||
background: #eee;
|
||||
width: 200px;
|
||||
}
|
||||
.item-search {
|
||||
position: relative;
|
||||
}
|
||||
.search-btn {
|
||||
position: absolute;
|
||||
right: 20px;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
|
||||
}
|
||||
.search-btn:hover{
|
||||
background: #cccccc;
|
||||
}
|
||||
input[type="search"]::-webkit-search-cancel-button{
|
||||
display: none;
|
||||
}
|
||||
.nav-body-item:first-child{
|
||||
color: #EB735F;
|
||||
}
|
||||
|
||||
.nav-body-item:not(:nth-child(1)):not(:nth-child(3)):hover {
|
||||
background: #ccc;
|
||||
}
|
||||
|
||||
.nav-user {
|
||||
|
||||
}
|
||||
|
||||
.f1 {
|
||||
flex: 1;
|
||||
}
|
||||
.f2 {
|
||||
flex: 2;
|
||||
}
|
|
@ -0,0 +1,751 @@
|
|||
(function (factory) {
|
||||
if (typeof exports === 'object') {
|
||||
// Node/CommonJS
|
||||
module.exports = factory();
|
||||
} else if (typeof define === 'function' && define.amd) {
|
||||
// AMD
|
||||
define(factory);
|
||||
} else {
|
||||
// Browser globals (with support for web workers)
|
||||
var glob;
|
||||
|
||||
try {
|
||||
glob = window;
|
||||
} catch (e) {
|
||||
glob = self;
|
||||
}
|
||||
|
||||
glob.SparkMD5 = factory();
|
||||
}
|
||||
}(function (undefined) {
|
||||
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* Fastest md5 implementation around (JKM md5).
|
||||
* Credits: Joseph Myers
|
||||
*
|
||||
* @see http://www.myersdaily.org/joseph/javascript/md5-text.html
|
||||
* @see http://jsperf.com/md5-shootout/7
|
||||
*/
|
||||
|
||||
/* this function is much faster,
|
||||
so if possible we use it. Some IEs
|
||||
are the only ones I know of that
|
||||
need the idiotic second function,
|
||||
generated by an if clause. */
|
||||
var add32 = function (a, b) {
|
||||
return (a + b) & 0xFFFFFFFF;
|
||||
},
|
||||
hex_chr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'];
|
||||
|
||||
|
||||
function cmn(q, a, b, x, s, t) {
|
||||
a = add32(add32(a, q), add32(x, t));
|
||||
return add32((a << s) | (a >>> (32 - s)), b);
|
||||
}
|
||||
|
||||
function md5cycle(x, k) {
|
||||
var a = x[0],
|
||||
b = x[1],
|
||||
c = x[2],
|
||||
d = x[3];
|
||||
|
||||
a += (b & c | ~b & d) + k[0] - 680876936 | 0;
|
||||
a = (a << 7 | a >>> 25) + b | 0;
|
||||
d += (a & b | ~a & c) + k[1] - 389564586 | 0;
|
||||
d = (d << 12 | d >>> 20) + a | 0;
|
||||
c += (d & a | ~d & b) + k[2] + 606105819 | 0;
|
||||
c = (c << 17 | c >>> 15) + d | 0;
|
||||
b += (c & d | ~c & a) + k[3] - 1044525330 | 0;
|
||||
b = (b << 22 | b >>> 10) + c | 0;
|
||||
a += (b & c | ~b & d) + k[4] - 176418897 | 0;
|
||||
a = (a << 7 | a >>> 25) + b | 0;
|
||||
d += (a & b | ~a & c) + k[5] + 1200080426 | 0;
|
||||
d = (d << 12 | d >>> 20) + a | 0;
|
||||
c += (d & a | ~d & b) + k[6] - 1473231341 | 0;
|
||||
c = (c << 17 | c >>> 15) + d | 0;
|
||||
b += (c & d | ~c & a) + k[7] - 45705983 | 0;
|
||||
b = (b << 22 | b >>> 10) + c | 0;
|
||||
a += (b & c | ~b & d) + k[8] + 1770035416 | 0;
|
||||
a = (a << 7 | a >>> 25) + b | 0;
|
||||
d += (a & b | ~a & c) + k[9] - 1958414417 | 0;
|
||||
d = (d << 12 | d >>> 20) + a | 0;
|
||||
c += (d & a | ~d & b) + k[10] - 42063 | 0;
|
||||
c = (c << 17 | c >>> 15) + d | 0;
|
||||
b += (c & d | ~c & a) + k[11] - 1990404162 | 0;
|
||||
b = (b << 22 | b >>> 10) + c | 0;
|
||||
a += (b & c | ~b & d) + k[12] + 1804603682 | 0;
|
||||
a = (a << 7 | a >>> 25) + b | 0;
|
||||
d += (a & b | ~a & c) + k[13] - 40341101 | 0;
|
||||
d = (d << 12 | d >>> 20) + a | 0;
|
||||
c += (d & a | ~d & b) + k[14] - 1502002290 | 0;
|
||||
c = (c << 17 | c >>> 15) + d | 0;
|
||||
b += (c & d | ~c & a) + k[15] + 1236535329 | 0;
|
||||
b = (b << 22 | b >>> 10) + c | 0;
|
||||
|
||||
a += (b & d | c & ~d) + k[1] - 165796510 | 0;
|
||||
a = (a << 5 | a >>> 27) + b | 0;
|
||||
d += (a & c | b & ~c) + k[6] - 1069501632 | 0;
|
||||
d = (d << 9 | d >>> 23) + a | 0;
|
||||
c += (d & b | a & ~b) + k[11] + 643717713 | 0;
|
||||
c = (c << 14 | c >>> 18) + d | 0;
|
||||
b += (c & a | d & ~a) + k[0] - 373897302 | 0;
|
||||
b = (b << 20 | b >>> 12) + c | 0;
|
||||
a += (b & d | c & ~d) + k[5] - 701558691 | 0;
|
||||
a = (a << 5 | a >>> 27) + b | 0;
|
||||
d += (a & c | b & ~c) + k[10] + 38016083 | 0;
|
||||
d = (d << 9 | d >>> 23) + a | 0;
|
||||
c += (d & b | a & ~b) + k[15] - 660478335 | 0;
|
||||
c = (c << 14 | c >>> 18) + d | 0;
|
||||
b += (c & a | d & ~a) + k[4] - 405537848 | 0;
|
||||
b = (b << 20 | b >>> 12) + c | 0;
|
||||
a += (b & d | c & ~d) + k[9] + 568446438 | 0;
|
||||
a = (a << 5 | a >>> 27) + b | 0;
|
||||
d += (a & c | b & ~c) + k[14] - 1019803690 | 0;
|
||||
d = (d << 9 | d >>> 23) + a | 0;
|
||||
c += (d & b | a & ~b) + k[3] - 187363961 | 0;
|
||||
c = (c << 14 | c >>> 18) + d | 0;
|
||||
b += (c & a | d & ~a) + k[8] + 1163531501 | 0;
|
||||
b = (b << 20 | b >>> 12) + c | 0;
|
||||
a += (b & d | c & ~d) + k[13] - 1444681467 | 0;
|
||||
a = (a << 5 | a >>> 27) + b | 0;
|
||||
d += (a & c | b & ~c) + k[2] - 51403784 | 0;
|
||||
d = (d << 9 | d >>> 23) + a | 0;
|
||||
c += (d & b | a & ~b) + k[7] + 1735328473 | 0;
|
||||
c = (c << 14 | c >>> 18) + d | 0;
|
||||
b += (c & a | d & ~a) + k[12] - 1926607734 | 0;
|
||||
b = (b << 20 | b >>> 12) + c | 0;
|
||||
|
||||
a += (b ^ c ^ d) + k[5] - 378558 | 0;
|
||||
a = (a << 4 | a >>> 28) + b | 0;
|
||||
d += (a ^ b ^ c) + k[8] - 2022574463 | 0;
|
||||
d = (d << 11 | d >>> 21) + a | 0;
|
||||
c += (d ^ a ^ b) + k[11] + 1839030562 | 0;
|
||||
c = (c << 16 | c >>> 16) + d | 0;
|
||||
b += (c ^ d ^ a) + k[14] - 35309556 | 0;
|
||||
b = (b << 23 | b >>> 9) + c | 0;
|
||||
a += (b ^ c ^ d) + k[1] - 1530992060 | 0;
|
||||
a = (a << 4 | a >>> 28) + b | 0;
|
||||
d += (a ^ b ^ c) + k[4] + 1272893353 | 0;
|
||||
d = (d << 11 | d >>> 21) + a | 0;
|
||||
c += (d ^ a ^ b) + k[7] - 155497632 | 0;
|
||||
c = (c << 16 | c >>> 16) + d | 0;
|
||||
b += (c ^ d ^ a) + k[10] - 1094730640 | 0;
|
||||
b = (b << 23 | b >>> 9) + c | 0;
|
||||
a += (b ^ c ^ d) + k[13] + 681279174 | 0;
|
||||
a = (a << 4 | a >>> 28) + b | 0;
|
||||
d += (a ^ b ^ c) + k[0] - 358537222 | 0;
|
||||
d = (d << 11 | d >>> 21) + a | 0;
|
||||
c += (d ^ a ^ b) + k[3] - 722521979 | 0;
|
||||
c = (c << 16 | c >>> 16) + d | 0;
|
||||
b += (c ^ d ^ a) + k[6] + 76029189 | 0;
|
||||
b = (b << 23 | b >>> 9) + c | 0;
|
||||
a += (b ^ c ^ d) + k[9] - 640364487 | 0;
|
||||
a = (a << 4 | a >>> 28) + b | 0;
|
||||
d += (a ^ b ^ c) + k[12] - 421815835 | 0;
|
||||
d = (d << 11 | d >>> 21) + a | 0;
|
||||
c += (d ^ a ^ b) + k[15] + 530742520 | 0;
|
||||
c = (c << 16 | c >>> 16) + d | 0;
|
||||
b += (c ^ d ^ a) + k[2] - 995338651 | 0;
|
||||
b = (b << 23 | b >>> 9) + c | 0;
|
||||
|
||||
a += (c ^ (b | ~d)) + k[0] - 198630844 | 0;
|
||||
a = (a << 6 | a >>> 26) + b | 0;
|
||||
d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0;
|
||||
d = (d << 10 | d >>> 22) + a | 0;
|
||||
c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0;
|
||||
c = (c << 15 | c >>> 17) + d | 0;
|
||||
b += (d ^ (c | ~a)) + k[5] - 57434055 | 0;
|
||||
b = (b << 21 |b >>> 11) + c | 0;
|
||||
a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0;
|
||||
a = (a << 6 | a >>> 26) + b | 0;
|
||||
d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0;
|
||||
d = (d << 10 | d >>> 22) + a | 0;
|
||||
c += (a ^ (d | ~b)) + k[10] - 1051523 | 0;
|
||||
c = (c << 15 | c >>> 17) + d | 0;
|
||||
b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0;
|
||||
b = (b << 21 |b >>> 11) + c | 0;
|
||||
a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0;
|
||||
a = (a << 6 | a >>> 26) + b | 0;
|
||||
d += (b ^ (a | ~c)) + k[15] - 30611744 | 0;
|
||||
d = (d << 10 | d >>> 22) + a | 0;
|
||||
c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0;
|
||||
c = (c << 15 | c >>> 17) + d | 0;
|
||||
b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0;
|
||||
b = (b << 21 |b >>> 11) + c | 0;
|
||||
a += (c ^ (b | ~d)) + k[4] - 145523070 | 0;
|
||||
a = (a << 6 | a >>> 26) + b | 0;
|
||||
d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0;
|
||||
d = (d << 10 | d >>> 22) + a | 0;
|
||||
c += (a ^ (d | ~b)) + k[2] + 718787259 | 0;
|
||||
c = (c << 15 | c >>> 17) + d | 0;
|
||||
b += (d ^ (c | ~a)) + k[9] - 343485551 | 0;
|
||||
b = (b << 21 | b >>> 11) + c | 0;
|
||||
|
||||
x[0] = a + x[0] | 0;
|
||||
x[1] = b + x[1] | 0;
|
||||
x[2] = c + x[2] | 0;
|
||||
x[3] = d + x[3] | 0;
|
||||
}
|
||||
|
||||
function md5blk(s) {
|
||||
var md5blks = [],
|
||||
i; /* Andy King said do it this way. */
|
||||
|
||||
for (i = 0; i < 64; i += 4) {
|
||||
md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
|
||||
}
|
||||
return md5blks;
|
||||
}
|
||||
|
||||
function md5blk_array(a) {
|
||||
var md5blks = [],
|
||||
i; /* Andy King said do it this way. */
|
||||
|
||||
for (i = 0; i < 64; i += 4) {
|
||||
md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24);
|
||||
}
|
||||
return md5blks;
|
||||
}
|
||||
|
||||
function md51(s) {
|
||||
var n = s.length,
|
||||
state = [1732584193, -271733879, -1732584194, 271733878],
|
||||
i,
|
||||
length,
|
||||
tail,
|
||||
tmp,
|
||||
lo,
|
||||
hi;
|
||||
|
||||
for (i = 64; i <= n; i += 64) {
|
||||
md5cycle(state, md5blk(s.substring(i - 64, i)));
|
||||
}
|
||||
s = s.substring(i - 64);
|
||||
length = s.length;
|
||||
tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
for (i = 0; i < length; i += 1) {
|
||||
tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);
|
||||
}
|
||||
tail[i >> 2] |= 0x80 << ((i % 4) << 3);
|
||||
if (i > 55) {
|
||||
md5cycle(state, tail);
|
||||
for (i = 0; i < 16; i += 1) {
|
||||
tail[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Beware that the final length might not fit in 32 bits so we take care of that
|
||||
tmp = n * 8;
|
||||
tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
|
||||
lo = parseInt(tmp[2], 16);
|
||||
hi = parseInt(tmp[1], 16) || 0;
|
||||
|
||||
tail[14] = lo;
|
||||
tail[15] = hi;
|
||||
|
||||
md5cycle(state, tail);
|
||||
return state;
|
||||
}
|
||||
|
||||
function md51_array(a) {
|
||||
var n = a.length,
|
||||
state = [1732584193, -271733879, -1732584194, 271733878],
|
||||
i,
|
||||
length,
|
||||
tail,
|
||||
tmp,
|
||||
lo,
|
||||
hi;
|
||||
|
||||
for (i = 64; i <= n; i += 64) {
|
||||
md5cycle(state, md5blk_array(a.subarray(i - 64, i)));
|
||||
}
|
||||
|
||||
// Not sure if it is a bug, however IE10 will always produce a sub array of length 1
|
||||
// containing the last element of the parent array if the sub array specified starts
|
||||
// beyond the length of the parent array - weird.
|
||||
// https://connect.microsoft.com/IE/feedback/details/771452/typed-array-subarray-issue
|
||||
a = (i - 64) < n ? a.subarray(i - 64) : new Uint8Array(0);
|
||||
|
||||
length = a.length;
|
||||
tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
||||
for (i = 0; i < length; i += 1) {
|
||||
tail[i >> 2] |= a[i] << ((i % 4) << 3);
|
||||
}
|
||||
|
||||
tail[i >> 2] |= 0x80 << ((i % 4) << 3);
|
||||
if (i > 55) {
|
||||
md5cycle(state, tail);
|
||||
for (i = 0; i < 16; i += 1) {
|
||||
tail[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Beware that the final length might not fit in 32 bits so we take care of that
|
||||
tmp = n * 8;
|
||||
tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
|
||||
lo = parseInt(tmp[2], 16);
|
||||
hi = parseInt(tmp[1], 16) || 0;
|
||||
|
||||
tail[14] = lo;
|
||||
tail[15] = hi;
|
||||
|
||||
md5cycle(state, tail);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
function rhex(n) {
|
||||
var s = '',
|
||||
j;
|
||||
for (j = 0; j < 4; j += 1) {
|
||||
s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F];
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
function hex(x) {
|
||||
var i;
|
||||
for (i = 0; i < x.length; i += 1) {
|
||||
x[i] = rhex(x[i]);
|
||||
}
|
||||
return x.join('');
|
||||
}
|
||||
|
||||
// In some cases the fast add32 function cannot be used..
|
||||
if (hex(md51('hello')) !== '5d41402abc4b2a76b9719d911017c592') {
|
||||
add32 = function (x, y) {
|
||||
var lsw = (x & 0xFFFF) + (y & 0xFFFF),
|
||||
msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
||||
return (msw << 16) | (lsw & 0xFFFF);
|
||||
};
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
/**
|
||||
* ArrayBuffer slice polyfill.
|
||||
*
|
||||
* @see https://github.com/ttaubert/node-arraybuffer-slice
|
||||
*/
|
||||
|
||||
if (typeof ArrayBuffer !== 'undefined' && !ArrayBuffer.prototype.slice) {
|
||||
(function () {
|
||||
function clamp(val, length) {
|
||||
val = (val | 0) || 0;
|
||||
|
||||
if (val < 0) {
|
||||
return Math.max(val + length, 0);
|
||||
}
|
||||
|
||||
return Math.min(val, length);
|
||||
}
|
||||
|
||||
ArrayBuffer.prototype.slice = function (from, to) {
|
||||
var length = this.byteLength,
|
||||
begin = clamp(from, length),
|
||||
end = length,
|
||||
num,
|
||||
target,
|
||||
targetArray,
|
||||
sourceArray;
|
||||
|
||||
if (to !== undefined) {
|
||||
end = clamp(to, length);
|
||||
}
|
||||
|
||||
if (begin > end) {
|
||||
return new ArrayBuffer(0);
|
||||
}
|
||||
|
||||
num = end - begin;
|
||||
target = new ArrayBuffer(num);
|
||||
targetArray = new Uint8Array(target);
|
||||
|
||||
sourceArray = new Uint8Array(this, begin, num);
|
||||
targetArray.set(sourceArray);
|
||||
|
||||
return target;
|
||||
};
|
||||
})();
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
/**
|
||||
* Helpers.
|
||||
*/
|
||||
|
||||
function toUtf8(str) {
|
||||
if (/[\u0080-\uFFFF]/.test(str)) {
|
||||
str = unescape(encodeURIComponent(str));
|
||||
}
|
||||
|
||||
return str;
|
||||
}
|
||||
|
||||
function utf8Str2ArrayBuffer(str, returnUInt8Array) {
|
||||
var length = str.length,
|
||||
buff = new ArrayBuffer(length),
|
||||
arr = new Uint8Array(buff),
|
||||
i;
|
||||
|
||||
for (i = 0; i < length; i += 1) {
|
||||
arr[i] = str.charCodeAt(i);
|
||||
}
|
||||
|
||||
return returnUInt8Array ? arr : buff;
|
||||
}
|
||||
|
||||
function arrayBuffer2Utf8Str(buff) {
|
||||
return String.fromCharCode.apply(null, new Uint8Array(buff));
|
||||
}
|
||||
|
||||
function concatenateArrayBuffers(first, second, returnUInt8Array) {
|
||||
var result = new Uint8Array(first.byteLength + second.byteLength);
|
||||
|
||||
result.set(new Uint8Array(first));
|
||||
result.set(new Uint8Array(second), first.byteLength);
|
||||
|
||||
return returnUInt8Array ? result : result.buffer;
|
||||
}
|
||||
|
||||
function hexToBinaryString(hex) {
|
||||
var bytes = [],
|
||||
length = hex.length,
|
||||
x;
|
||||
|
||||
for (x = 0; x < length - 1; x += 2) {
|
||||
bytes.push(parseInt(hex.substr(x, 2), 16));
|
||||
}
|
||||
|
||||
return String.fromCharCode.apply(String, bytes);
|
||||
}
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
/**
|
||||
* SparkMD5 OOP implementation.
|
||||
*
|
||||
* Use this class to perform an incremental md5, otherwise use the
|
||||
* static methods instead.
|
||||
*/
|
||||
|
||||
function SparkMD5() {
|
||||
// call reset to init the instance
|
||||
this.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a string.
|
||||
* A conversion will be applied if an utf8 string is detected.
|
||||
*
|
||||
* @param {String} str The string to be appended
|
||||
*
|
||||
* @return {SparkMD5} The instance itself
|
||||
*/
|
||||
SparkMD5.prototype.append = function (str) {
|
||||
// Converts the string to utf8 bytes if necessary
|
||||
// Then append as binary
|
||||
this.appendBinary(toUtf8(str));
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Appends a binary string.
|
||||
*
|
||||
* @param {String} contents The binary string to be appended
|
||||
*
|
||||
* @return {SparkMD5} The instance itself
|
||||
*/
|
||||
SparkMD5.prototype.appendBinary = function (contents) {
|
||||
this._buff += contents;
|
||||
this._length += contents.length;
|
||||
|
||||
var length = this._buff.length,
|
||||
i;
|
||||
|
||||
for (i = 64; i <= length; i += 64) {
|
||||
md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i)));
|
||||
}
|
||||
|
||||
this._buff = this._buff.substring(i - 64);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the incremental computation, reseting the internal state and
|
||||
* returning the result.
|
||||
*
|
||||
* @param {Boolean} raw True to get the raw string, false to get the hex string
|
||||
*
|
||||
* @return {String} The result
|
||||
*/
|
||||
SparkMD5.prototype.end = function (raw) {
|
||||
var buff = this._buff,
|
||||
length = buff.length,
|
||||
i,
|
||||
tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
ret;
|
||||
|
||||
for (i = 0; i < length; i += 1) {
|
||||
tail[i >> 2] |= buff.charCodeAt(i) << ((i % 4) << 3);
|
||||
}
|
||||
|
||||
this._finish(tail, length);
|
||||
ret = hex(this._hash);
|
||||
|
||||
if (raw) {
|
||||
ret = hexToBinaryString(ret);
|
||||
}
|
||||
|
||||
this.reset();
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resets the internal state of the computation.
|
||||
*
|
||||
* @return {SparkMD5} The instance itself
|
||||
*/
|
||||
SparkMD5.prototype.reset = function () {
|
||||
this._buff = '';
|
||||
this._length = 0;
|
||||
this._hash = [1732584193, -271733879, -1732584194, 271733878];
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the internal state of the computation.
|
||||
*
|
||||
* @return {Object} The state
|
||||
*/
|
||||
SparkMD5.prototype.getState = function () {
|
||||
return {
|
||||
buff: this._buff,
|
||||
length: this._length,
|
||||
hash: this._hash.slice()
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the internal state of the computation.
|
||||
*
|
||||
* @param {Object} state The state
|
||||
*
|
||||
* @return {SparkMD5} The instance itself
|
||||
*/
|
||||
SparkMD5.prototype.setState = function (state) {
|
||||
this._buff = state.buff;
|
||||
this._length = state.length;
|
||||
this._hash = state.hash;
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Releases memory used by the incremental buffer and other additional
|
||||
* resources. If you plan to use the instance again, use reset instead.
|
||||
*/
|
||||
SparkMD5.prototype.destroy = function () {
|
||||
delete this._hash;
|
||||
delete this._buff;
|
||||
delete this._length;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finish the final calculation based on the tail.
|
||||
*
|
||||
* @param {Array} tail The tail (will be modified)
|
||||
* @param {Number} length The length of the remaining buffer
|
||||
*/
|
||||
SparkMD5.prototype._finish = function (tail, length) {
|
||||
var i = length,
|
||||
tmp,
|
||||
lo,
|
||||
hi;
|
||||
|
||||
tail[i >> 2] |= 0x80 << ((i % 4) << 3);
|
||||
if (i > 55) {
|
||||
md5cycle(this._hash, tail);
|
||||
for (i = 0; i < 16; i += 1) {
|
||||
tail[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Do the final computation based on the tail and length
|
||||
// Beware that the final length may not fit in 32 bits so we take care of that
|
||||
tmp = this._length * 8;
|
||||
tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
|
||||
lo = parseInt(tmp[2], 16);
|
||||
hi = parseInt(tmp[1], 16) || 0;
|
||||
|
||||
tail[14] = lo;
|
||||
tail[15] = hi;
|
||||
md5cycle(this._hash, tail);
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs the md5 hash on a string.
|
||||
* A conversion will be applied if utf8 string is detected.
|
||||
*
|
||||
* @param {String} str The string
|
||||
* @param {Boolean} [raw] True to get the raw string, false to get the hex string
|
||||
*
|
||||
* @return {String} The result
|
||||
*/
|
||||
SparkMD5.hash = function (str, raw) {
|
||||
// Converts the string to utf8 bytes if necessary
|
||||
// Then compute it using the binary function
|
||||
return SparkMD5.hashBinary(toUtf8(str), raw);
|
||||
};
|
||||
|
||||
/**
|
||||
* Performs the md5 hash on a binary string.
|
||||
*
|
||||
* @param {String} content The binary string
|
||||
* @param {Boolean} [raw] True to get the raw string, false to get the hex string
|
||||
*
|
||||
* @return {String} The result
|
||||
*/
|
||||
SparkMD5.hashBinary = function (content, raw) {
|
||||
var hash = md51(content),
|
||||
ret = hex(hash);
|
||||
|
||||
return raw ? hexToBinaryString(ret) : ret;
|
||||
};
|
||||
|
||||
// ---------------------------------------------------
|
||||
|
||||
/**
|
||||
* SparkMD5 OOP implementation for array buffers.
|
||||
*
|
||||
* Use this class to perform an incremental md5 ONLY for array buffers.
|
||||
*/
|
||||
SparkMD5.ArrayBuffer = function () {
|
||||
// call reset to init the instance
|
||||
this.reset();
|
||||
};
|
||||
|
||||
/**
|
||||
* Appends an array buffer.
|
||||
*
|
||||
* @param {ArrayBuffer} arr The array to be appended
|
||||
*
|
||||
* @return {SparkMD5.ArrayBuffer} The instance itself
|
||||
*/
|
||||
SparkMD5.ArrayBuffer.prototype.append = function (arr) {
|
||||
var buff = concatenateArrayBuffers(this._buff.buffer, arr, true),
|
||||
length = buff.length,
|
||||
i;
|
||||
|
||||
this._length += arr.byteLength;
|
||||
|
||||
for (i = 64; i <= length; i += 64) {
|
||||
md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i)));
|
||||
}
|
||||
|
||||
this._buff = (i - 64) < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0);
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Finishes the incremental computation, reseting the internal state and
|
||||
* returning the result.
|
||||
*
|
||||
* @param {Boolean} raw True to get the raw string, false to get the hex string
|
||||
*
|
||||
* @return {String} The result
|
||||
*/
|
||||
SparkMD5.ArrayBuffer.prototype.end = function (raw) {
|
||||
var buff = this._buff,
|
||||
length = buff.length,
|
||||
tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
||||
i,
|
||||
ret;
|
||||
|
||||
for (i = 0; i < length; i += 1) {
|
||||
tail[i >> 2] |= buff[i] << ((i % 4) << 3);
|
||||
}
|
||||
|
||||
this._finish(tail, length);
|
||||
ret = hex(this._hash);
|
||||
|
||||
if (raw) {
|
||||
ret = hexToBinaryString(ret);
|
||||
}
|
||||
|
||||
this.reset();
|
||||
|
||||
return ret;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resets the internal state of the computation.
|
||||
*
|
||||
* @return {SparkMD5.ArrayBuffer} The instance itself
|
||||
*/
|
||||
SparkMD5.ArrayBuffer.prototype.reset = function () {
|
||||
this._buff = new Uint8Array(0);
|
||||
this._length = 0;
|
||||
this._hash = [1732584193, -271733879, -1732584194, 271733878];
|
||||
|
||||
return this;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the internal state of the computation.
|
||||
*
|
||||
* @return {Object} The state
|
||||
*/
|
||||
SparkMD5.ArrayBuffer.prototype.getState = function () {
|
||||
var state = SparkMD5.prototype.getState.call(this);
|
||||
|
||||
// Convert buffer to a string
|
||||
state.buff = arrayBuffer2Utf8Str(state.buff);
|
||||
|
||||
return state;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the internal state of the computation.
|
||||
*
|
||||
* @param {Object} state The state
|
||||
*
|
||||
* @return {SparkMD5.ArrayBuffer} The instance itself
|
||||
*/
|
||||
SparkMD5.ArrayBuffer.prototype.setState = function (state) {
|
||||
// Convert string to buffer
|
||||
state.buff = utf8Str2ArrayBuffer(state.buff, true);
|
||||
|
||||
return SparkMD5.prototype.setState.call(this, state);
|
||||
};
|
||||
|
||||
SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy;
|
||||
|
||||
SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish;
|
||||
|
||||
/**
|
||||
* Performs the md5 hash on an array buffer.
|
||||
*
|
||||
* @param {ArrayBuffer} arr The array buffer
|
||||
* @param {Boolean} [raw] True to get the raw string, false to get the hex one
|
||||
*
|
||||
* @return {String} The result
|
||||
*/
|
||||
SparkMD5.ArrayBuffer.hash = function (arr, raw) {
|
||||
var hash = md51_array(new Uint8Array(arr)),
|
||||
ret = hex(hash);
|
||||
|
||||
return raw ? hexToBinaryString(ret) : ret;
|
||||
};
|
||||
|
||||
return SparkMD5;
|
||||
}));
|
|
@ -0,0 +1,150 @@
|
|||
|
||||
let partMd5List = new Array();
|
||||
let partCount = 0;
|
||||
let partSize = 0;
|
||||
let fileSize = 0;
|
||||
|
||||
/**
|
||||
* 注意:本测试Demo不受分片顺序影响
|
||||
* 关于上传文件成功后的处理:配置minio监听指定存储桶指定格式文件上传成功后,push通知到mq,后端程序监听并消费即可
|
||||
* (建议上传mp4,成功后可以直接在页面看到效果)
|
||||
* 测试分片上传
|
||||
* 运行页面 > 打开控制台 > console > 选择上传的文件 > 观察打印的信息
|
||||
* 测试秒传
|
||||
* 在上一个测试的基础上,刷新一下页面,选择上一次上传的文件
|
||||
* 测试断点续传
|
||||
* 重新选择一个文件(如果你没有多的测试文件,就重启一下后台服务) > 手动模拟上传了一部分失败的场景(在所有分片未上传完成时关掉页面 或 注释掉合并文件代码,然后去 minio chunk桶 删除几个分片)
|
||||
* > 再选择刚选择的文件上传 > 观察打印的信息是否从缺失的分片开始上传
|
||||
*/
|
||||
uploadFile = async () => {
|
||||
//获取用户选择的文件
|
||||
const file = document.getElementById("upload").files[0];
|
||||
|
||||
//获取文件md5
|
||||
let startTime = new Date();
|
||||
const fileMd5 = await getFileMd5(file);
|
||||
|
||||
console.log("文件md5:", fileMd5 + ",耗时" + (new Date() - startTime)+"毫秒");
|
||||
|
||||
console.log("向后端请求本次分片上传初始化")
|
||||
|
||||
$.ajax({
|
||||
url: "/storage/upload/init",
|
||||
type: 'POST',
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
data: JSON.stringify({
|
||||
fileMd5: fileMd5,
|
||||
fullFileName: file.name,
|
||||
fileSize: file.size,
|
||||
}),
|
||||
success: async function (res) {
|
||||
partMd5List = new Array();
|
||||
console.log("当前文件上传情况:初次上传 或 断点续传")
|
||||
document.getElementById("uploadId").value = (res.data.fileKey);
|
||||
if (res.isDone) {
|
||||
return;
|
||||
}
|
||||
const chunkUploadUrls = res.data.partList;
|
||||
partCount = res.data.partCount;
|
||||
partSize = res.data.partSize;
|
||||
fileSize = res.data.fileSize;
|
||||
|
||||
//当前为顺序上传方式,若要测试并发上传,请将第52行 await 修饰符删除即可
|
||||
//若使用并发上传方式,当前分片上传完成后打印出来的完成提示是不准确的,但这并不影响最终运行结果;原因是由ajax请求本身是异步导致的
|
||||
|
||||
for (const [i, item] of chunkUploadUrls.entries()) {
|
||||
|
||||
//取文件指定范围内的byte,从而得到分片数据
|
||||
let _chunkFile = file.slice(item.startPosition, item.endPosition)
|
||||
console.log("开始上传第" + i + "个分片", _chunkFile)
|
||||
$.ajax({
|
||||
url: item.url,
|
||||
type: 'PUT',
|
||||
contentType: false,
|
||||
processData: false,
|
||||
data: _chunkFile,
|
||||
success: function (res) {
|
||||
console.log("第" + i + "个分片上传完成")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
calculatePartMd5 = async () => {
|
||||
//获取用户选择的文件
|
||||
const file = document.getElementById("upload").files[0];
|
||||
|
||||
//获取文件md5
|
||||
let startTime = new Date();
|
||||
const fileMd5 = await getFileMd5(file);
|
||||
|
||||
console.log("文件md5:", fileMd5 + ",耗时" + (new Date() - startTime)+"毫秒");
|
||||
|
||||
for(let i=0;i<partCount;i++){
|
||||
console.log(i)
|
||||
let _chunkFile;
|
||||
if(i==partCount-1){
|
||||
_chunkFile = file.slice(i*partSize, fileSize)
|
||||
}else{
|
||||
_chunkFile = file.slice(i*partSize, (i+1)*partSize)
|
||||
}
|
||||
|
||||
let partMd5 = await getFileMd5(_chunkFile);
|
||||
partMd5List.push(partMd5);
|
||||
console.log(partMd5List)
|
||||
}
|
||||
}
|
||||
|
||||
function download() {
|
||||
let fileKey = document.getElementById("uploadId").value;
|
||||
window.location.href = "/storage/download/" + fileKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件MD5
|
||||
* @param file
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
getFileMd5 = (file) => {
|
||||
let fileReader = new FileReader()
|
||||
fileReader.readAsBinaryString(file)
|
||||
let spark = new SparkMD5()
|
||||
return new Promise((resolve) => {
|
||||
fileReader.onload = (e) => {
|
||||
spark.appendBinary(e.target.result)
|
||||
resolve(spark.end())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求后端合并文件
|
||||
* @param fileMd5
|
||||
* @param fileName
|
||||
*/
|
||||
merge = () => {
|
||||
let fileKey = document.getElementById("uploadId").value;
|
||||
console.log("开始请求后端合并文件")
|
||||
//注意:bucketName请填写你自己的存储桶名称,如果没有,就先创建一个写在这
|
||||
$.ajax({
|
||||
url: "/storage/upload/complete/" + fileKey,
|
||||
type: 'POST',
|
||||
contentType: "application/json",
|
||||
dataType: "json",
|
||||
data: JSON.stringify({
|
||||
partMd5List:partMd5List
|
||||
}),
|
||||
success: function (res) {
|
||||
console.log("合并文件完成", res.data)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
removeTaskId = async () => {
|
||||
document.getElementById("uploadId").value = '';
|
||||
|
||||
}
|
|
@ -0,0 +1,233 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>MinIO Plus Demo</title>
|
||||
</head>
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||
<script src="js/spark-md5.js"></script>
|
||||
<script src="js/upload.js"></script>
|
||||
<body>
|
||||
<div id="app">
|
||||
<input type="file" @click="clearState" id="upload">
|
||||
<button @click="checkFile">检查</button>
|
||||
<button @click="uploadFile(false)" :disabled="partList.length === 0">正常上传</button>
|
||||
<button @click="uploadFile(true)" :disabled="partList.length < 2">模拟丢片上传</button>
|
||||
<button @click="recover" :disabled="missChunkNumber == null">丢片恢复</button>
|
||||
<button @click="remove" :disabled="uploadId == null">删除文件</button>
|
||||
<div>
|
||||
<input type="text" v-model="uploadId" id="uploadId">
|
||||
<button @click="merge" :disabled="uploadId == null">合并分片</button>
|
||||
</div>
|
||||
<div>
|
||||
<button @click="preview" :disabled="uploadId == null">预览图片</button>
|
||||
<button @click="download" :disabled="uploadId == null">下载文件</button>
|
||||
<img :src="previewUrl">
|
||||
</div>
|
||||
<div>
|
||||
<label>总计片数:{{partList.length}}</label>
|
||||
<div v-for="(item,index) in partList" :style="{color:missChunkNumber === index ? 'red' : 'black'}">
|
||||
<label>第{{index + 1}}片:</label>
|
||||
<br/>
|
||||
<label>{{item.url}}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
<script>
|
||||
const {createApp, reactive, toRefs, onMounted} = Vue
|
||||
createApp({
|
||||
setup() {
|
||||
const state = reactive({
|
||||
uploadId: null,
|
||||
partList: [],
|
||||
missChunkNumber: null,
|
||||
partCount: null,
|
||||
partSize: null,
|
||||
fileSize: null,
|
||||
previewUrl: null,
|
||||
})
|
||||
/**
|
||||
* 文件检查
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const checkFile = async () => {
|
||||
//获取用户选择的文件
|
||||
const file = document.getElementById("upload").files[0];
|
||||
//获取文件md5
|
||||
let startTime = new Date();
|
||||
const fileMd5 = await getFileMd5(file)
|
||||
|
||||
console.log("文件md5:" + fileMd5 + ",耗时" + (new Date() - startTime) + "毫秒");
|
||||
|
||||
console.log("向后端请求本次分片上传初始化")
|
||||
|
||||
fetch("/storage/upload/init", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
fileMd5: fileMd5,
|
||||
fullFileName: file.name,
|
||||
fileSize: file.size
|
||||
})
|
||||
}).then(res => res.json()).then(({data}) => {
|
||||
console.log(data);
|
||||
// 获取文件上传id
|
||||
state.uploadId = data.fileKey;
|
||||
// 获取文件分片
|
||||
state.partList = data.partList;
|
||||
// 获取文件大小
|
||||
state.fileSize = data.fileSize;
|
||||
// 获取块大小
|
||||
state.partSize = data.partSize;
|
||||
// 获取文件分片数
|
||||
state.partCount = data.partCount;
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
})
|
||||
}
|
||||
/**
|
||||
* 上传文件
|
||||
* @param isMissingPart 是否模拟丢片
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const uploadFile = async (isMissingPart) => {
|
||||
if (isMissingPart) {
|
||||
// 随机一个缺失分片
|
||||
state.missChunkNumber = Math.floor(Math.random() * state.partList.length);
|
||||
} else {
|
||||
state.missChunkNumber = null;
|
||||
}
|
||||
state.fileChunkMd5 = [];
|
||||
const file = document.getElementById("upload").files[0];
|
||||
for (const [i, item] of state.partList.entries()) {
|
||||
//取文件指定范围内的byte,从而得到分片数据
|
||||
let _chunkFile = file.slice(item.startPosition, item.endPosition)
|
||||
if (i === state.missChunkNumber) {
|
||||
console.log("丢弃第" + i + "个分片")
|
||||
continue;
|
||||
}
|
||||
console.log("开始上传第" + i + "个分片", _chunkFile)
|
||||
// 上传分片
|
||||
fetch(item.url, {
|
||||
method: "PUT",
|
||||
body: _chunkFile,
|
||||
}).then(res => {
|
||||
console.log("第" + i + "个分片上传完成")
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
})
|
||||
}
|
||||
|
||||
console.log(state.fileChunkMd5);
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求后端合并文件
|
||||
*/
|
||||
const merge = async () => {
|
||||
console.log("开始请求后端合并文件")
|
||||
// 每块文件MD5
|
||||
const partMd5List = [];
|
||||
// 获取用户选择的文件
|
||||
const file = document.getElementById("upload").files[0];
|
||||
for (let i = 0; i < state.partCount; i++) {
|
||||
console.log(i)
|
||||
let _chunkFile;
|
||||
if (i === state.missChunkNumber) {
|
||||
continue;
|
||||
}
|
||||
if (i === state.partCount - 1) {
|
||||
_chunkFile = file.slice(i * state.partSize, state.fileSize)
|
||||
} else {
|
||||
_chunkFile = file.slice(i * state.partSize, (i + 1) * state.partSize)
|
||||
}
|
||||
let partMd5 = await getFileMd5(_chunkFile);
|
||||
partMd5List.push(partMd5);
|
||||
}
|
||||
//
|
||||
fetch(`/storage/upload/complete/${state.uploadId}`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
},
|
||||
body: JSON.stringify({
|
||||
partMd5List: partMd5List
|
||||
})
|
||||
}).then(res => res.json()).then(({data}) => {
|
||||
console.log("合并文件完成", data)
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
|
||||
const recover = async () => {
|
||||
await checkFile();
|
||||
await uploadFile(false);
|
||||
await merge();
|
||||
}
|
||||
const clearState = async () => {
|
||||
state.uploadId = null;
|
||||
state.partList = [];
|
||||
state.fileChunkMd5 = [];
|
||||
state.missChunkNumber = null
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件
|
||||
*/
|
||||
const download = () => {
|
||||
window.location.href = `/storage/download/${state.uploadId}`;
|
||||
}
|
||||
// 文件预览
|
||||
const preview = () => {
|
||||
state.previewUrl = `/storage/preview/original/${state.uploadId}`;
|
||||
}
|
||||
// 删除文件
|
||||
const remove = () => {
|
||||
fetch(`/storage/remove/${state.uploadId}`, {
|
||||
method: "POST",
|
||||
}).then(res => res.json()).then(({data}) => {
|
||||
console.log("删除文件完成", data)
|
||||
}).catch(err => {
|
||||
console.log(err);
|
||||
});
|
||||
}
|
||||
/**
|
||||
* 获取文件MD5
|
||||
* @param file
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
const getFileMd5 = (file) => {
|
||||
let fileReader = new FileReader()
|
||||
fileReader.readAsBinaryString(file)
|
||||
let spark = new SparkMD5()
|
||||
return new Promise((resolve) => {
|
||||
fileReader.onload = (e) => {
|
||||
spark.appendBinary(e.target.result)
|
||||
resolve(spark.end())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return {
|
||||
checkFile,
|
||||
removeTaskId,
|
||||
clearState,
|
||||
uploadFile,
|
||||
merge,
|
||||
download,
|
||||
preview,
|
||||
remove,
|
||||
recover,
|
||||
...toRefs(state)
|
||||
}
|
||||
}
|
||||
}).mount('#app')
|
||||
</script>
|
|
@ -26,15 +26,17 @@ public enum MinioPlusErrorCode {
|
|||
/**
|
||||
* MinIO 异常
|
||||
*/
|
||||
CREATE_MULTIPART_UPLOAD_FAILED(2001, "获取上传编号失败"),
|
||||
COMPLETE_MULTIPART_FAILED(2002, "合并分片失败"),
|
||||
LIST_PARTS_FAILED(2003, "查询分片失败"),
|
||||
CREATE_UPLOAD_URL_FAILED(2004, "获取对象上传URL失败"),
|
||||
CREATE_DOWNLOAD_URL_FAILED(2005, "获取对象下载URL失败"),
|
||||
CREATE_PREVIEW_URL_FAILED(2006, "获取预对象预览URL失败"),
|
||||
WRITE_FAILED(2007, "文件写入失败"),
|
||||
READ_FAILED(2008, "文件读取失败"),
|
||||
DELETE_FAILED(2009, "删除失败");
|
||||
BUCKET_EXISTS_FAILED(2001, "桶检查失败"),
|
||||
MAKE_BUCKET_FAILED(2002, "桶创建失败"),
|
||||
CREATE_MULTIPART_UPLOAD_FAILED(2003, "获取上传编号失败"),
|
||||
COMPLETE_MULTIPART_FAILED(2004, "合并分片失败"),
|
||||
LIST_PARTS_FAILED(2005, "查询分片失败"),
|
||||
CREATE_UPLOAD_URL_FAILED(2006, "获取对象上传URL失败"),
|
||||
CREATE_DOWNLOAD_URL_FAILED(2007, "获取对象下载URL失败"),
|
||||
CREATE_PREVIEW_URL_FAILED(2008, "获取预对象预览URL失败"),
|
||||
WRITE_FAILED(2009, "文件写入失败"),
|
||||
READ_FAILED(2010, "文件读取失败"),
|
||||
DELETE_FAILED(2011, "删除失败");
|
||||
|
||||
/**
|
||||
* 错误编码
|
||||
|
|
|
@ -5,7 +5,6 @@ import lombok.Getter;
|
|||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* MinioPlus配置类
|
||||
|
@ -15,7 +14,6 @@ import org.springframework.stereotype.Component;
|
|||
@Getter
|
||||
@Setter
|
||||
@ConfigurationProperties(prefix = "minioplus")
|
||||
@Component
|
||||
public class MinioPlusProperties {
|
||||
|
||||
/**
|
||||
|
|
|
@ -44,6 +44,10 @@
|
|||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-model</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-s3-api-definition</artifactId>
|
||||
</dependency>
|
||||
<!-- google图片压缩 -->
|
||||
<dependency>
|
||||
<groupId>net.coobird</groupId>
|
||||
|
|
|
@ -19,7 +19,6 @@ import org.liuxp.minioplus.core.common.context.MultipartUploadCreateDTO;
|
|||
import org.liuxp.minioplus.core.common.utils.MinioPlusCommonUtil;
|
||||
import org.liuxp.minioplus.core.engine.StorageEngineService;
|
||||
import org.liuxp.minioplus.core.repository.MetadataRepository;
|
||||
import org.liuxp.minioplus.core.repository.MinioRepository;
|
||||
import org.liuxp.minioplus.model.bo.CreateUploadUrlReqBO;
|
||||
import org.liuxp.minioplus.model.bo.CreateUploadUrlRespBO;
|
||||
import org.liuxp.minioplus.model.dto.FileCheckDTO;
|
||||
|
@ -29,6 +28,8 @@ import org.liuxp.minioplus.model.dto.FileMetadataInfoUpdateDTO;
|
|||
import org.liuxp.minioplus.model.vo.CompleteResultVo;
|
||||
import org.liuxp.minioplus.model.vo.FileCheckResultVo;
|
||||
import org.liuxp.minioplus.model.vo.FileMetadataInfoVo;
|
||||
import org.liuxp.minioplus.s3.def.ListParts;
|
||||
import org.liuxp.minioplus.s3.def.MinioS3Client;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
|
@ -54,7 +55,7 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
MinioPlusProperties properties;
|
||||
|
||||
@Resource
|
||||
MinioRepository minioRepository;
|
||||
MinioS3Client minioS3Client;
|
||||
|
||||
/**
|
||||
* MinIO中上传编号名称
|
||||
|
@ -378,7 +379,7 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
// 文件权限校验,元数据为空或者当前登录用户不是文件所有者时抛出异常
|
||||
this.authentication(metadata, fileKey, userId);
|
||||
|
||||
return minioRepository.getDownloadUrl(metadata.getFileName(),metadata.getFileMimeType(),metadata.getStorageBucket(),metadata.getStoragePath() + "/"+ metadata.getFileMd5());
|
||||
return minioS3Client.getDownloadUrl(metadata.getFileName(),metadata.getFileMimeType(),metadata.getStorageBucket(),metadata.getStoragePath() + "/"+ metadata.getFileMd5());
|
||||
}catch(Exception e){
|
||||
// 打印日志
|
||||
log.error(e.getMessage(),e);
|
||||
|
@ -396,7 +397,7 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
// 文件权限校验,元数据为空或者当前登录用户不是文件所有者时抛出异常
|
||||
this.authentication(metadata, fileKey, userId);
|
||||
|
||||
return minioRepository.getPreviewUrl(metadata.getFileMimeType(),metadata.getStorageBucket(),metadata.getStoragePath() + "/"+ metadata.getFileMd5());
|
||||
return minioS3Client.getPreviewUrl(metadata.getFileMimeType(),metadata.getStorageBucket(),metadata.getStoragePath() + "/"+ metadata.getFileMd5());
|
||||
|
||||
}catch(Exception e){
|
||||
// 打印日志
|
||||
|
@ -417,7 +418,7 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
// 判断是否存在缩略图,设置桶名称
|
||||
String bucketName = Boolean.TRUE.equals(metadata.getIsPreview()) ? StorageBucketEnums.IMAGE_PREVIEW.getCode() : metadata.getStorageBucket();
|
||||
// 创建图片预览地址
|
||||
return minioRepository.getPreviewUrl(metadata.getFileMimeType(),bucketName,metadata.getStoragePath() + "/"+ metadata.getFileMd5());
|
||||
return minioS3Client.getPreviewUrl(metadata.getFileMimeType(),bucketName,metadata.getStoragePath() + "/"+ metadata.getFileMd5());
|
||||
|
||||
}catch(Exception e){
|
||||
// 打印日志
|
||||
|
@ -431,7 +432,7 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
public Boolean createFile(FileMetadataInfoSaveDTO saveDTO, byte[] fileBytes) {
|
||||
|
||||
// 写入文件
|
||||
minioRepository.write(saveDTO.getStorageBucket(), MinioPlusCommonUtil.getObjectName(saveDTO.getFileMd5()), new ByteArrayInputStream(fileBytes), saveDTO.getFileSize(), saveDTO.getFileMimeType());
|
||||
minioS3Client.putObject(saveDTO.getStorageBucket(), MinioPlusCommonUtil.getObjectName(saveDTO.getFileMd5()), new ByteArrayInputStream(fileBytes), saveDTO.getFileSize(), saveDTO.getFileMimeType());
|
||||
|
||||
// 判断是否生成缩略图
|
||||
if(Boolean.TRUE.equals(saveDTO.getIsPreview())){
|
||||
|
@ -439,7 +440,7 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
try{
|
||||
ByteArrayOutputStream largeImage = MinioPlusCommonUtil.resizeImage(new ByteArrayInputStream(fileBytes), properties.getThumbnail().getSize());
|
||||
byte[] largeImageBytes = largeImage.toByteArray();
|
||||
minioRepository.write(StorageBucketEnums.IMAGE_PREVIEW.getCode(), MinioPlusCommonUtil.getObjectName(saveDTO.getFileMd5()), new ByteArrayInputStream(largeImageBytes), largeImageBytes.length, saveDTO.getFileMimeType());
|
||||
minioS3Client.putObject(StorageBucketEnums.IMAGE_PREVIEW.getCode(), MinioPlusCommonUtil.getObjectName(saveDTO.getFileMd5()), new ByteArrayInputStream(largeImageBytes), largeImageBytes.length, saveDTO.getFileMimeType());
|
||||
}catch(Exception e){
|
||||
log.error(MinioPlusErrorCode.FILE_PREVIEW_WRITE_FAILED.getMessage(),e);
|
||||
throw new MinioPlusException(MinioPlusErrorCode.FILE_PREVIEW_WRITE_FAILED);
|
||||
|
@ -464,7 +465,7 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
}
|
||||
|
||||
// 读取流
|
||||
byte[] fileBytes = minioRepository.read(fileMetadataInfoVo.getStorageBucket(), fileMetadataInfoVo.getStoragePath() + "/" + fileMetadataInfoVo.getFileMd5());
|
||||
byte[] fileBytes = minioS3Client.getObject(fileMetadataInfoVo.getStorageBucket(), fileMetadataInfoVo.getStoragePath() + "/" + fileMetadataInfoVo.getFileMd5());
|
||||
|
||||
return Pair.of(fileMetadataInfoVo,fileBytes);
|
||||
}
|
||||
|
@ -509,10 +510,10 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
|
||||
if(CollUtil.isEmpty(metadataList)){
|
||||
// 当不存在任何该MD5值的文件元数据时,删除物理文件
|
||||
minioRepository.remove(metadata.getStorageBucket(), metadata.getStoragePath() + "/" + metadata.getFileMd5());
|
||||
minioS3Client.removeObject(metadata.getStorageBucket(), metadata.getStoragePath() + "/" + metadata.getFileMd5());
|
||||
if(Boolean.TRUE.equals(metadata.getIsPreview())){
|
||||
// 当存在缩略图时,同步删除缩略图
|
||||
minioRepository.remove(StorageBucketEnums.IMAGE_PREVIEW.getCode(), metadata.getStoragePath() + "/" + metadata.getFileMd5());
|
||||
minioS3Client.removeObject(StorageBucketEnums.IMAGE_PREVIEW.getCode(), metadata.getStoragePath() + "/" + metadata.getFileMd5());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -577,17 +578,16 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
* 构建响应给前端的分片信息
|
||||
*
|
||||
* @param uploadCreateDTO 分片dto
|
||||
* @param queryParams 查询参数
|
||||
* @param uploadId 上传任务编号
|
||||
* @param fileSize 文件大小
|
||||
* @param start 开始位置
|
||||
* @param partNumber 块号
|
||||
* @return {@link FileCheckResultVo.Part}
|
||||
*/
|
||||
private FileCheckResultVo.Part buildResultPart(MultipartUploadCreateDTO uploadCreateDTO, Map<String, String> queryParams, Long fileSize, long start, Integer partNumber) {
|
||||
private FileCheckResultVo.Part buildResultPart(MultipartUploadCreateDTO uploadCreateDTO, String uploadId, Long fileSize, long start, Integer partNumber) {
|
||||
// 计算起始位置
|
||||
long end = Math.min(start + properties.getPart().getSize(), fileSize);
|
||||
queryParams.put("partNumber", String.valueOf(partNumber));
|
||||
String uploadUrl = minioRepository.getPresignedObjectUrl(uploadCreateDTO.getBucketName(), uploadCreateDTO.getObjectName(), queryParams);
|
||||
String uploadUrl = minioS3Client.getUploadObjectUrl(uploadCreateDTO.getBucketName(), uploadCreateDTO.getObjectName(), uploadId,String.valueOf(partNumber));
|
||||
FileCheckResultVo.Part part = new FileCheckResultVo.Part();
|
||||
part.setUploadId(uploadCreateDTO.getUploadId());
|
||||
// 上传地址
|
||||
|
@ -613,14 +613,14 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
// 分块数量
|
||||
Integer chunkNum = fileMetadataVo.getPartNumber();
|
||||
// 获取分块信息
|
||||
ListPartsResponse listPartsResponse = this.buildResultPart(fileMetadataVo);
|
||||
List<Part> parts = listPartsResponse.result().partList();
|
||||
ListParts listParts = this.buildResultPart(fileMetadataVo);
|
||||
List<ListParts.Part> parts = listParts.getPartList();
|
||||
if (!chunkNum.equals(parts.size())) {
|
||||
// 找到丢失的片
|
||||
boolean[] exists = new boolean[chunkNum + 1];
|
||||
// 遍历数组,标记存在的块号
|
||||
for (Part item : parts) {
|
||||
int partNumber = item.partNumber();
|
||||
for (ListParts.Part item : parts) {
|
||||
int partNumber = item.getPartNumber();
|
||||
exists[partNumber] = true;
|
||||
}
|
||||
// 查找丢失的块号
|
||||
|
@ -664,17 +664,17 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
/**
|
||||
* 合并分片
|
||||
*/
|
||||
public CompleteResultVo completeMultipartUpload(FileMetadataInfoVo meteData, List<String> partMd5List) {
|
||||
public CompleteResultVo completeMultipartUpload(FileMetadataInfoVo metadataInfo, List<String> partMd5List) {
|
||||
|
||||
CompleteResultVo completeResultVo = new CompleteResultVo();
|
||||
|
||||
// 获取所有的分片信息
|
||||
ListPartsResponse listMultipart = this.buildResultPart(meteData);
|
||||
ListParts listParts = this.buildResultPart(metadataInfo);
|
||||
|
||||
List<Integer> missingNumbers =new ArrayList<>();
|
||||
|
||||
// 分块数量
|
||||
Integer chunkNum = meteData.getPartNumber();
|
||||
Integer chunkNum = metadataInfo.getPartNumber();
|
||||
|
||||
if(partMd5List==null || chunkNum != partMd5List.size()){
|
||||
throw new MinioPlusException(MinioPlusErrorCode.FILE_PART_NUM_CHECK_FAILED);
|
||||
|
@ -683,8 +683,8 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
// 校验文件完整性
|
||||
for (int i = 1; i <= chunkNum; i++) {
|
||||
boolean findPart = false;
|
||||
for (Part part : listMultipart.result().partList()) {
|
||||
if(part.partNumber() == i && part.etag().equals(partMd5List.get(i-1))){
|
||||
for (ListParts.Part part : listParts.getPartList()) {
|
||||
if(part.getPartNumber() == i && CharSequenceUtil.equalsIgnoreCase(part.getEtag(), partMd5List.get(i - 1))){
|
||||
findPart = true;
|
||||
}
|
||||
}
|
||||
|
@ -696,25 +696,25 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
if(CollUtil.isNotEmpty(missingNumbers)){
|
||||
CreateUploadUrlReqBO bo = new CreateUploadUrlReqBO();
|
||||
// 文件md5
|
||||
bo.setFileMd5(meteData.getFileMd5());
|
||||
bo.setFileMd5(metadataInfo.getFileMd5());
|
||||
// 文件名(含扩展名)
|
||||
bo.setFullFileName(meteData.getFileName());
|
||||
bo.setFullFileName(metadataInfo.getFileName());
|
||||
// "文件长度"
|
||||
bo.setFileSize(meteData.getFileSize());
|
||||
bo.setFileSize(metadataInfo.getFileSize());
|
||||
// 是否断点续传 0:否 1:是,默认非断点续传
|
||||
bo.setIsSequel(Boolean.TRUE);
|
||||
// 丢失的块号-断点续传时必传
|
||||
bo.setMissPartNum(missingNumbers);
|
||||
if(missingNumbers.size() != chunkNum){
|
||||
// 任务id,任务id可能会失效
|
||||
bo.setUploadId(meteData.getUploadTaskId());
|
||||
bo.setUploadId(metadataInfo.getUploadTaskId());
|
||||
}
|
||||
// 存储桶
|
||||
bo.setStorageBucket(meteData.getStorageBucket());
|
||||
bo.setStorageBucket(metadataInfo.getStorageBucket());
|
||||
// 存储路径
|
||||
bo.setStoragePath(meteData.getStoragePath());
|
||||
bo.setStoragePath(metadataInfo.getStoragePath());
|
||||
// 文件id
|
||||
bo.setFileKey(meteData.getFileKey());
|
||||
bo.setFileKey(metadataInfo.getFileKey());
|
||||
CreateUploadUrlRespBO createUploadUrlRespBO = this.createUploadUrl(bo);
|
||||
|
||||
completeResultVo.setIsComplete(false);
|
||||
|
@ -722,13 +722,12 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
completeResultVo.setPartList(createUploadUrlRespBO.getParts());
|
||||
}else{
|
||||
// 合并分块
|
||||
ObjectWriteResponse writeResponse = minioRepository.completeMultipartUpload(MultipartUploadCreateDTO.builder()
|
||||
.bucketName(meteData.getStorageBucket())
|
||||
.uploadId(meteData.getUploadTaskId())
|
||||
.objectName(listMultipart.object())
|
||||
.parts(listMultipart.result().partList().toArray(new Part[]{}))
|
||||
.build());
|
||||
completeResultVo.setIsComplete(null!=writeResponse);
|
||||
boolean writeResponse = minioS3Client.completeMultipartUpload(metadataInfo.getStorageBucket()
|
||||
,listParts.getObjectName()
|
||||
,metadataInfo.getUploadTaskId()
|
||||
,listParts.getPartList()
|
||||
);
|
||||
completeResultVo.setIsComplete(writeResponse);
|
||||
completeResultVo.setPartList(new ArrayList<>());
|
||||
}
|
||||
|
||||
|
@ -738,36 +737,13 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
/**
|
||||
* 获取分片信息
|
||||
*
|
||||
* @param meteData 文件元数据信息
|
||||
* @return {@link ListPartsResponse} 分片任务信息
|
||||
* @param metadataInfo 文件元数据信息
|
||||
* @return {@link ListParts} 分片任务信息
|
||||
*/
|
||||
private ListPartsResponse buildResultPart(FileMetadataInfoVo meteData){
|
||||
String objectName = MinioPlusCommonUtil.getObjectName(meteData.getFileMd5());
|
||||
|
||||
try {
|
||||
// 获取所有的分片信息
|
||||
return minioRepository.listMultipart(MultipartUploadCreateDTO.builder()
|
||||
.bucketName(meteData.getStorageBucket())
|
||||
.objectName(objectName)
|
||||
.maxParts(meteData.getPartNumber())
|
||||
.uploadId(meteData.getUploadTaskId())
|
||||
.partNumberMarker(0)
|
||||
.build());
|
||||
}catch (MinioPlusException e){
|
||||
log.error("获取分片信息失败,partList返回空",e);
|
||||
MultipartUploadCreateDTO multipartUploadCreateDTO = MultipartUploadCreateDTO.builder()
|
||||
.bucketName(meteData.getStorageBucket())
|
||||
.objectName(objectName)
|
||||
.maxParts(meteData.getPartNumber())
|
||||
.uploadId(meteData.getUploadTaskId())
|
||||
.partNumberMarker(0)
|
||||
.build();
|
||||
|
||||
ListPartsResultCopy listPartsResult = new ListPartsResultCopy();
|
||||
|
||||
return new ListPartsResponse(null,multipartUploadCreateDTO.getBucketName(),multipartUploadCreateDTO.getRegion(),multipartUploadCreateDTO.getObjectName(),listPartsResult);
|
||||
}
|
||||
|
||||
private ListParts buildResultPart(FileMetadataInfoVo metadataInfo){
|
||||
String objectName = MinioPlusCommonUtil.getObjectName(metadataInfo.getFileMd5());
|
||||
// 获取所有的分片信息
|
||||
return minioS3Client.listParts(metadataInfo.getStorageBucket(), objectName, metadataInfo.getPartNumber(), metadataInfo.getUploadTaskId());
|
||||
}
|
||||
|
||||
public CreateUploadUrlRespBO createUploadUrl(CreateUploadUrlReqBO bo) {
|
||||
|
@ -781,8 +757,8 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
String storagePath;
|
||||
// 文件key
|
||||
String fileKey;
|
||||
// 额外的查询参数字段
|
||||
Map<String, String> queryParams = new HashMap<>(2);
|
||||
// 上传任务编号
|
||||
String uploadId;
|
||||
// 断点续传
|
||||
if (Boolean.TRUE.equals(bo.getIsSequel()) && CollUtil.isNotEmpty(bo.getMissPartNum()) && CharSequenceUtil.isNotBlank(bo.getUploadId())) {
|
||||
// 断点续传需要使用已创建的任务信息构建分片信息
|
||||
|
@ -796,12 +772,12 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
storagePath = uploadCreateDTO.getObjectName();
|
||||
// 文件key
|
||||
fileKey = bo.getFileKey();
|
||||
queryParams.put(UPLOAD_ID, bo.getUploadId());
|
||||
uploadId = bo.getUploadId();
|
||||
uploadCreateDTO.setUploadId(bo.getUploadId());
|
||||
// 开始位置
|
||||
long start = (long) (bo.getMissPartNum().get(0) - 1) * properties.getPart().getSize();
|
||||
for (int partNumber : bo.getMissPartNum()) {
|
||||
FileCheckResultVo.Part part = this.buildResultPart(uploadCreateDTO, queryParams, bo.getFileSize(), start, partNumber);
|
||||
FileCheckResultVo.Part part = this.buildResultPart(uploadCreateDTO, uploadId, bo.getFileSize(), start, partNumber);
|
||||
// 更改下一次的开始位置
|
||||
start = start + properties.getPart().getSize();
|
||||
partList.add(part);
|
||||
|
@ -820,7 +796,7 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
// 存储桶
|
||||
bucketName = StorageBucketEnums.getBucketByFileSuffix(suffix);
|
||||
// 创建桶
|
||||
minioRepository.createBucket(bucketName);
|
||||
minioS3Client.makeBucket(bucketName);
|
||||
// 如果是图片并开启了压缩,不需要分片,返回项目上的接口地址
|
||||
if (bucketName.equals(StorageBucketEnums.IMAGE.getCode()) && properties.getThumbnail().isEnable()) {
|
||||
|
||||
|
@ -832,19 +808,18 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
part.setEndPosition(bo.getFileSize());
|
||||
partList.add(part);
|
||||
|
||||
queryParams.put(UPLOAD_ID, fileKey);
|
||||
uploadId = fileKey;
|
||||
} else {
|
||||
// 创建分片请求,获取uploadId
|
||||
MultipartUploadCreateDTO uploadCreateDTO = MultipartUploadCreateDTO.builder()
|
||||
.bucketName(bucketName)
|
||||
.objectName(MinioPlusCommonUtil.getObjectName(bo.getFileMd5()))
|
||||
.build();
|
||||
CreateMultipartUploadResponse createMultipartUploadResponse = minioRepository.createMultipartUpload(uploadCreateDTO);
|
||||
queryParams.put(UPLOAD_ID, createMultipartUploadResponse.result().uploadId());
|
||||
uploadCreateDTO.setUploadId(createMultipartUploadResponse.result().uploadId());
|
||||
uploadId = minioS3Client.createMultipartUpload(bucketName,MinioPlusCommonUtil.getObjectName(bo.getFileMd5()));
|
||||
uploadCreateDTO.setUploadId(uploadId);
|
||||
long start = 0;
|
||||
for (Integer partNumber = 1; partNumber <= chunkNum; partNumber++) {
|
||||
FileCheckResultVo.Part part = this.buildResultPart(uploadCreateDTO, queryParams, bo.getFileSize(), start, partNumber);
|
||||
FileCheckResultVo.Part part = this.buildResultPart(uploadCreateDTO, uploadId, bo.getFileSize(), start, partNumber);
|
||||
// 更改下一次的开始位置
|
||||
start = start + properties.getPart().getSize();
|
||||
partList.add(part);
|
||||
|
@ -861,7 +836,7 @@ public class StorageEngineServiceImpl implements StorageEngineService {
|
|||
// 分块数量-可选,分片后必须重新赋值 默认1
|
||||
respBO.setPartCount(chunkNum);
|
||||
// 切片上传任务id
|
||||
respBO.setUploadTaskId(queryParams.get(UPLOAD_ID));
|
||||
respBO.setUploadTaskId(uploadId);
|
||||
// 分片信息-必填
|
||||
respBO.setParts(partList);
|
||||
return respBO;
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
package org.liuxp.minioplus.core.repository;
|
||||
|
||||
import io.minio.CreateMultipartUploadResponse;
|
||||
import io.minio.ListPartsResponse;
|
||||
import io.minio.ObjectWriteResponse;
|
||||
import org.liuxp.minioplus.core.common.context.MultipartUploadCreateDTO;
|
||||
import org.liuxp.minioplus.core.repository.impl.CustomMinioClient;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* MinIO文件存储引擎接口定义
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/07/06
|
||||
*/
|
||||
public interface MinioRepository {
|
||||
|
||||
/**
|
||||
* 取得MinioClient
|
||||
* @return CustomMinioClient
|
||||
*/
|
||||
CustomMinioClient getClient();
|
||||
|
||||
/**
|
||||
* 创建桶
|
||||
* @param bucketName 桶名称
|
||||
*/
|
||||
void createBucket(String bucketName);
|
||||
|
||||
/**
|
||||
* 创建分片请求,获取uploadId
|
||||
* @param multipartUploadCreate 创建分片上传需要的参数
|
||||
* @return 分片结果
|
||||
*/
|
||||
CreateMultipartUploadResponse createMultipartUpload(MultipartUploadCreateDTO multipartUploadCreate);
|
||||
|
||||
/**
|
||||
* 合并分片
|
||||
* @param multipartUploadCreate 分片参数
|
||||
* @return 是否成功
|
||||
*/
|
||||
ObjectWriteResponse completeMultipartUpload(MultipartUploadCreateDTO multipartUploadCreate);
|
||||
|
||||
/**
|
||||
* 获取分片信息列表
|
||||
* @param multipartUploadCreate 分片参数
|
||||
* @return 分片信息
|
||||
*/
|
||||
ListPartsResponse listMultipart(MultipartUploadCreateDTO multipartUploadCreate);
|
||||
|
||||
/**
|
||||
* 获得对象上传的url
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称
|
||||
* @param queryParams 查询参数
|
||||
* @return {@link String}
|
||||
*/
|
||||
String getPresignedObjectUrl(String bucketName, String objectName, Map<String, String> queryParams);
|
||||
|
||||
/**
|
||||
* 取得下载链接
|
||||
* @param fileName 文件全名含扩展名
|
||||
* @param contentType 数据类型
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称含路径
|
||||
* @return 下载地址
|
||||
*/
|
||||
String getDownloadUrl(String fileName, String contentType, String bucketName, String objectName);
|
||||
|
||||
/**
|
||||
* 取得图片预览链接
|
||||
* @param contentType 数据类型
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称含路径
|
||||
* @return 图片预览链接
|
||||
*/
|
||||
String getPreviewUrl(String contentType, String bucketName, String objectName);
|
||||
|
||||
/**
|
||||
* 写入文件
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称含路径
|
||||
* @param stream 文件流
|
||||
* @param size 文件长度
|
||||
* @param contentType 文件类型
|
||||
* @return 是否成功
|
||||
*/
|
||||
Boolean write(String bucketName, String objectName, InputStream stream, long size, String contentType);
|
||||
|
||||
/**
|
||||
* 读取文件
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称含路径
|
||||
* @return 文件流
|
||||
*/
|
||||
byte[] read(String bucketName, String objectName);
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称含路径
|
||||
*/
|
||||
void remove(String bucketName, String objectName);
|
||||
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.liuxp.minioplus.core.service.impl.StorageServiceImpl,\
|
||||
org.liuxp.minioplus.core.engine.impl.StorageEngineServiceImpl,\
|
||||
org.liuxp.minioplus.core.repository.impl.MinioRepositoryImpl
|
||||
org.liuxp.minioplus.core.engine.impl.StorageEngineServiceImpl
|
|
@ -11,4 +11,15 @@
|
|||
|
||||
<artifactId>minio-plus-extension</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-core</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -1,4 +1,4 @@
|
|||
package org.liuxp.minioplus.application.context;
|
||||
package org.liuxp.minioplus.extension.context;
|
||||
|
||||
import io.swagger.annotations.ApiModel;
|
||||
import io.swagger.annotations.ApiModelProperty;
|
|
@ -1,4 +1,4 @@
|
|||
package org.liuxp.minioplus.application.context;
|
||||
package org.liuxp.minioplus.extension.context;
|
||||
|
||||
import java.util.Objects;
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package org.liuxp.minioplus.application.controller;
|
||||
package org.liuxp.minioplus.extension.controller;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.liuxp.minioplus.application.context.Response;
|
||||
import org.liuxp.minioplus.config.MinioPlusProperties;
|
||||
import org.liuxp.minioplus.extension.context.Response;
|
||||
import org.liuxp.minioplus.model.dto.FileCheckDTO;
|
||||
import org.liuxp.minioplus.model.dto.FileCompleteDTO;
|
||||
import org.liuxp.minioplus.model.vo.CompleteResultVo;
|
|
@ -0,0 +1,2 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.liuxp.minioplus.extension.controller.StorageController
|
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>minio-plus-spring-boot-starter</artifactId>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<artifactId>minio-plus-all-spring-boot-starter</artifactId>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-extension</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-s3-api-official</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -2,11 +2,18 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<parent>
|
||||
<artifactId>minio-plus-parent</artifactId>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<version>${revision}</version>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-spring-boot-starter</artifactId>
|
||||
<version>${revision}</version>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<modules>
|
||||
<module>minio-plus-all-spring-boot-starter</module>
|
||||
</modules>
|
||||
|
||||
</project>
|
|
@ -1,2 +0,0 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.liuxp.data.security.core.aop.config.DataSecurityAopConfiguration
|
|
@ -16,6 +16,14 @@
|
|||
<groupId>cn.hutool</groupId>
|
||||
<artifactId>hutool-all</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-s3-api-definition</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-config</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -0,0 +1,76 @@
|
|||
package org.liuxp.minioplus.s3.custom;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.liuxp.minioplus.config.MinioPlusProperties;
|
||||
import org.liuxp.minioplus.s3.def.ListParts;
|
||||
import org.liuxp.minioplus.s3.def.MinioS3Client;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.InputStream;
|
||||
import java.util.List;
|
||||
|
||||
@Slf4j
|
||||
@Repository
|
||||
public class MinioS3ClientImpl implements MinioS3Client {
|
||||
|
||||
@Resource
|
||||
private MinioPlusProperties properties;
|
||||
|
||||
@Override
|
||||
public Boolean bucketExists(String bucketName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void makeBucket(String bucketName) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createMultipartUpload(String bucketName, String objectName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean completeMultipartUpload(String bucketName, String objectName, String uploadId, List<ListParts.Part> parts) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListParts listParts(String bucketName, String objectName, Integer maxParts, String uploadId) {
|
||||
|
||||
// 获取失败时,拼一个空的返回值
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getUploadObjectUrl(String bucketName, String objectName, String uploadId, String partNumber) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getDownloadUrl(String fileName, String contentType, String bucketName, String objectName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPreviewUrl(String contentType, String bucketName, String objectName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Boolean putObject(String bucketName, String objectName, InputStream stream, long size, String contentType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getObject(String bucketName, String objectName) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeObject(String bucketName, String objectName) {
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.liuxp.minioplus.s3.custom.MinioS3ClientImpl
|
|
@ -19,12 +19,14 @@ public class ListParts {
|
|||
|
||||
private int maxParts;
|
||||
|
||||
private String uploadId;
|
||||
|
||||
private List<Part> partList = null;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
static class Part{
|
||||
public static class Part{
|
||||
private int partNumber;
|
||||
|
||||
private String etag;
|
||||
|
@ -34,7 +36,7 @@ public class ListParts {
|
|||
private Long size;
|
||||
}
|
||||
|
||||
ListParts addPart(int partNumber,String etag,ZonedDateTime lastModified,Long size){
|
||||
public void addPart(int partNumber, String etag, ZonedDateTime lastModified, Long size){
|
||||
|
||||
Part part = new Part();
|
||||
part.setPartNumber(partNumber);
|
||||
|
@ -46,7 +48,6 @@ public class ListParts {
|
|||
partList = new ArrayList<>();
|
||||
}
|
||||
partList.add(part);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -57,10 +57,11 @@ public interface MinioS3Client {
|
|||
* 获得对象&分片上传链接
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称(含路径)
|
||||
* @param uploadId 上传任务编号
|
||||
* @param partNumber 分片序号
|
||||
* @return {@link String}
|
||||
*/
|
||||
String getUploadObjectUrl(String bucketName, String objectName, String partNumber);
|
||||
String getUploadObjectUrl(String bucketName, String objectName, String uploadId, String partNumber);
|
||||
|
||||
/**
|
||||
* 取得下载链接
|
||||
|
|
|
@ -28,6 +28,18 @@
|
|||
<artifactId>okhttp</artifactId>
|
||||
<version>4.11.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-s3-api-definition</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-config</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-common</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
|
@ -1,4 +1,4 @@
|
|||
package org.liuxp.minioplus.core.repository.impl;
|
||||
package org.liuxp.minioplus.s3.official;
|
||||
|
||||
import com.google.common.collect.Multimap;
|
||||
import io.minio.CreateMultipartUploadResponse;
|
|
@ -1,43 +1,44 @@
|
|||
package org.liuxp.minioplus.core.repository.impl;
|
||||
package org.liuxp.minioplus.s3.official;
|
||||
|
||||
import cn.hutool.core.io.IoUtil;
|
||||
import com.google.common.collect.Maps;
|
||||
import io.minio.*;
|
||||
import io.minio.http.Method;
|
||||
import lombok.SneakyThrows;
|
||||
import io.minio.messages.Part;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.liuxp.minioplus.config.MinioPlusProperties;
|
||||
import org.liuxp.minioplus.core.common.context.MultipartUploadCreateDTO;
|
||||
import org.liuxp.minioplus.common.enums.MinioPlusErrorCode;
|
||||
import org.liuxp.minioplus.common.exception.MinioPlusException;
|
||||
import org.liuxp.minioplus.core.repository.MinioRepository;
|
||||
import org.liuxp.minioplus.config.MinioPlusProperties;
|
||||
import org.liuxp.minioplus.s3.def.ListParts;
|
||||
import org.liuxp.minioplus.s3.def.MinioS3Client;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* MinIO文件存储引擎接口定义实现类
|
||||
*
|
||||
* @author contact@liuxp.me
|
||||
* @since 2023/07/06
|
||||
*/
|
||||
@Slf4j
|
||||
@Repository
|
||||
public class MinioRepositoryImpl implements MinioRepository {
|
||||
public class MinioS3ClientImpl implements MinioS3Client {
|
||||
|
||||
private CustomMinioClient minioClient = null;
|
||||
/**
|
||||
* MinIO中上传编号名称
|
||||
*/
|
||||
private static final String UPLOAD_ID = "uploadId";
|
||||
/**
|
||||
* 分片上传块号名称
|
||||
*/
|
||||
private static final String PART_NUMBER = "partNumber";
|
||||
|
||||
@Resource
|
||||
private MinioPlusProperties properties;
|
||||
|
||||
/**
|
||||
* 取得MinioClient
|
||||
* @return CustomMinioClient
|
||||
*/
|
||||
@Override
|
||||
private CustomMinioClient minioClient = null;
|
||||
|
||||
public CustomMinioClient getClient(){
|
||||
|
||||
if(null==this.minioClient){
|
||||
|
@ -51,77 +52,93 @@ public class MinioRepositoryImpl implements MinioRepository {
|
|||
return this.minioClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建桶
|
||||
*
|
||||
* @param bucketName bucket名称
|
||||
*/
|
||||
@Override
|
||||
@SneakyThrows(Exception.class)
|
||||
public void createBucket(String bucketName) {
|
||||
boolean found = this.getClient().bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
|
||||
if (!found) {
|
||||
log.info("create bucket: [{}]", bucketName);
|
||||
this.getClient().makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
|
||||
public Boolean bucketExists(String bucketName) {
|
||||
try {
|
||||
return this.getClient().bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
|
||||
} catch (Exception e) {
|
||||
log.error(MinioPlusErrorCode.BUCKET_EXISTS_FAILED.getMessage()+":{}", e.getMessage(), e);
|
||||
throw new MinioPlusException(MinioPlusErrorCode.BUCKET_EXISTS_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public CreateMultipartUploadResponse createMultipartUpload(MultipartUploadCreateDTO multipartUploadCreate) {
|
||||
public void makeBucket(String bucketName) {
|
||||
boolean found = bucketExists(bucketName);
|
||||
try {
|
||||
return this.getClient().createMultipartUpload(multipartUploadCreate.getBucketName(), multipartUploadCreate.getRegion(), multipartUploadCreate.getObjectName(), multipartUploadCreate.getHeaders(), multipartUploadCreate.getExtraQueryParams());
|
||||
if (!found) {
|
||||
log.info("create bucket: [{}]", bucketName);
|
||||
this.getClient().makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error(MinioPlusErrorCode.MAKE_BUCKET_FAILED.getMessage()+":{}", e.getMessage(), e);
|
||||
throw new MinioPlusException(MinioPlusErrorCode.MAKE_BUCKET_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createMultipartUpload(String bucketName, String objectName) {
|
||||
try {
|
||||
CreateMultipartUploadResponse createMultipartUploadResponse = this.getClient().createMultipartUpload(bucketName, null, objectName, null, null);
|
||||
return createMultipartUploadResponse.result().uploadId();
|
||||
} catch (Exception e) {
|
||||
log.error(MinioPlusErrorCode.CREATE_MULTIPART_UPLOAD_FAILED.getMessage()+":{}", e.getMessage(), e);
|
||||
throw new MinioPlusException(MinioPlusErrorCode.CREATE_MULTIPART_UPLOAD_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并分片
|
||||
*
|
||||
* @param multipartUploadCreate 分片参数
|
||||
* @return 是否成功
|
||||
*/
|
||||
@Override
|
||||
public ObjectWriteResponse completeMultipartUpload(MultipartUploadCreateDTO multipartUploadCreate) {
|
||||
public Boolean completeMultipartUpload(String bucketName, String objectName, String uploadId, List<ListParts.Part> parts) {
|
||||
|
||||
Part[] partArray = new Part[parts.size()];
|
||||
|
||||
for (int i = 0; i < parts.size(); i++) {
|
||||
partArray[i] = new Part(parts.get(i).getPartNumber(),parts.get(i).getEtag());
|
||||
}
|
||||
|
||||
try {
|
||||
return this.getClient().completeMultipartUpload(multipartUploadCreate.getBucketName(), multipartUploadCreate.getRegion()
|
||||
, multipartUploadCreate.getObjectName(), multipartUploadCreate.getUploadId(), multipartUploadCreate.getParts(), multipartUploadCreate.getHeaders()
|
||||
, multipartUploadCreate.getExtraQueryParams());
|
||||
ObjectWriteResponse objectWriteResponse = this.getClient().completeMultipartUpload(bucketName, null
|
||||
, objectName, uploadId, partArray, null, null);
|
||||
return objectWriteResponse != null;
|
||||
} catch (Exception e) {
|
||||
log.error(MinioPlusErrorCode.COMPLETE_MULTIPART_FAILED.getMessage()+",uploadId:{},ObjectName:{},失败原因:{},", multipartUploadCreate.getUploadId(), multipartUploadCreate.getObjectName(), e.getMessage(), e);
|
||||
log.error(MinioPlusErrorCode.COMPLETE_MULTIPART_FAILED.getMessage()+",uploadId:{},ObjectName:{},失败原因:{},", uploadId, objectName, e.getMessage(), e);
|
||||
throw new MinioPlusException(MinioPlusErrorCode.COMPLETE_MULTIPART_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取分片信息列表
|
||||
*
|
||||
* @param multipartUploadCreate 请求参数
|
||||
* @return {@link ListPartsResponse}
|
||||
*/
|
||||
@Override
|
||||
public ListPartsResponse listMultipart(MultipartUploadCreateDTO multipartUploadCreate) {
|
||||
public ListParts listParts(String bucketName, String objectName, Integer maxParts, String uploadId) {
|
||||
|
||||
ListParts listParts = new ListParts();
|
||||
|
||||
try {
|
||||
return this.getClient().listParts(multipartUploadCreate.getBucketName(), multipartUploadCreate.getRegion(), multipartUploadCreate.getObjectName(), multipartUploadCreate.getMaxParts(), multipartUploadCreate.getPartNumberMarker(), multipartUploadCreate.getUploadId(), multipartUploadCreate.getHeaders(), multipartUploadCreate.getExtraQueryParams());
|
||||
ListPartsResponse listPartsResponse = this.getClient().listParts(bucketName, null, objectName, maxParts
|
||||
, 0, uploadId, null, null);
|
||||
|
||||
listParts.setBucketName(bucketName);
|
||||
listParts.setObjectName(objectName);
|
||||
listParts.setMaxParts(maxParts);
|
||||
listParts.setUploadId(uploadId);
|
||||
listParts.setPartList(new ArrayList<>());
|
||||
|
||||
for (Part part : listPartsResponse.result().partList()) {
|
||||
listParts.addPart(part.partNumber(), part.etag(), part.lastModified(), part.partSize());
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error(MinioPlusErrorCode.LIST_PARTS_FAILED.getMessage()+":{}", e.getMessage(), e);
|
||||
throw new MinioPlusException(MinioPlusErrorCode.LIST_PARTS_FAILED);
|
||||
}
|
||||
|
||||
return listParts;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获得对象上传的url
|
||||
*
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称
|
||||
* @param queryParams 查询参数
|
||||
* @return {@link String}
|
||||
*/
|
||||
@Override
|
||||
public String getPresignedObjectUrl(String bucketName, String objectName, Map<String, String> queryParams) {
|
||||
public String getUploadObjectUrl(String bucketName, String objectName, String uploadId,String partNumber) {
|
||||
|
||||
Map<String, String> queryParams = Maps.newHashMapWithExpectedSize(2);
|
||||
queryParams.put(UPLOAD_ID, uploadId);
|
||||
queryParams.put(PART_NUMBER, partNumber);
|
||||
|
||||
try {
|
||||
return this.getClient().getPresignedObjectUrl(
|
||||
GetPresignedObjectUrlArgs.builder()
|
||||
|
@ -137,17 +154,8 @@ public class MinioRepositoryImpl implements MinioRepository {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 取得下载链接
|
||||
* @param fileName 文件全名含扩展名
|
||||
* @param contentType 数据类型
|
||||
* @param bucketName 桶名称
|
||||
* @param objectName 对象名称含路径
|
||||
* @return 下载地址
|
||||
*/
|
||||
@Override
|
||||
public String getDownloadUrl(String fileName, String contentType, String bucketName, String objectName) {
|
||||
|
||||
Map<String, String> reqParams = new HashMap<>();
|
||||
reqParams.put("response-content-disposition", "attachment;filename=\""+fileName+"\"");
|
||||
reqParams.put("response-content-type", contentType);
|
||||
|
@ -169,7 +177,6 @@ public class MinioRepositoryImpl implements MinioRepository {
|
|||
|
||||
@Override
|
||||
public String getPreviewUrl(String contentType, String bucketName, String objectName) {
|
||||
|
||||
Map<String, String> reqParams = new HashMap<>();
|
||||
reqParams.put("response-content-type", contentType);
|
||||
reqParams.put("response-content-disposition", "inline");
|
||||
|
@ -190,8 +197,7 @@ public class MinioRepositoryImpl implements MinioRepository {
|
|||
}
|
||||
|
||||
@Override
|
||||
public Boolean write(String bucketName,String objectName, InputStream stream, long size, String contentType) {
|
||||
|
||||
public Boolean putObject(String bucketName, String objectName, InputStream stream, long size, String contentType) {
|
||||
try{
|
||||
|
||||
// 检查存储桶是否已经存在
|
||||
|
@ -219,8 +225,7 @@ public class MinioRepositoryImpl implements MinioRepository {
|
|||
}
|
||||
|
||||
@Override
|
||||
public byte[] read(String bucketName,String objectName) {
|
||||
|
||||
public byte[] getObject(String bucketName, String objectName) {
|
||||
// 从远程MinIO服务读取文件流
|
||||
try (InputStream inputStream = this.getClient().getObject(GetObjectArgs.builder().bucket(bucketName).object(objectName).build())) {
|
||||
// 文件流转换为字节码
|
||||
|
@ -229,11 +234,10 @@ public class MinioRepositoryImpl implements MinioRepository {
|
|||
log.error(MinioPlusErrorCode.READ_FAILED.getMessage(),e);
|
||||
throw new MinioPlusException(MinioPlusErrorCode.READ_FAILED);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void remove(String bucketName, String objectName) {
|
||||
public void removeObject(String bucketName, String objectName) {
|
||||
try {
|
||||
this.getClient().removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(objectName).build());
|
||||
} catch (Exception e) {
|
||||
|
@ -241,6 +245,4 @@ public class MinioRepositoryImpl implements MinioRepository {
|
|||
throw new MinioPlusException(MinioPlusErrorCode.DELETE_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.liuxp.minioplus.s3.official.MinioS3ClientImpl
|
48
pom.xml
48
pom.xml
|
@ -164,7 +164,22 @@
|
|||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-plus-spring-boot-starter</artifactId>
|
||||
<artifactId>minio-plus-all-spring-boot-starter</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-s3-api-definition</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-s3-api-custom</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.liuxp</groupId>
|
||||
<artifactId>minio-s3-api-official</artifactId>
|
||||
<version>${revision}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
@ -209,37 +224,6 @@
|
|||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<!-- <!– 要将源码放上去,需要加入这个插件 –>-->
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.apache.maven.plugins</groupId>-->
|
||||
<!-- <artifactId>maven-source-plugin</artifactId>-->
|
||||
<!-- <version>${maven-source-plugin.version}</version>-->
|
||||
<!-- <configuration>-->
|
||||
<!-- <attach>true</attach>-->
|
||||
<!-- </configuration>-->
|
||||
<!-- <executions>-->
|
||||
<!-- <execution>-->
|
||||
<!-- <phase>compile</phase>-->
|
||||
<!-- <goals>-->
|
||||
<!-- <goal>jar</goal>-->
|
||||
<!-- </goals>-->
|
||||
<!-- </execution>-->
|
||||
<!-- </executions>-->
|
||||
<!-- </plugin>-->
|
||||
<!-- <plugin>-->
|
||||
<!-- <groupId>org.jacoco</groupId>-->
|
||||
<!-- <artifactId>jacoco-maven-plugin</artifactId>-->
|
||||
<!-- <version>${jacoco.version}</version>-->
|
||||
<!-- <executions>-->
|
||||
<!-- <!–first execution : for preparing JaCoCo runtime agent–>-->
|
||||
<!-- <execution>-->
|
||||
<!-- <id>prepare-agent</id>-->
|
||||
<!-- <goals>-->
|
||||
<!-- <goal>prepare-agent</goal>-->
|
||||
<!-- </goals>-->
|
||||
<!-- </execution>-->
|
||||
<!-- </executions>-->
|
||||
<!-- </plugin>-->
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>flatten-maven-plugin</artifactId>
|
||||
|
|
Loading…
Reference in New Issue