使用原生 MinIO SDK 贯通上传流程,提交一个简易的前端测试页面。

This commit is contained in:
刘小平 2024-06-04 17:34:09 +08:00
parent 701a6be443
commit 37f2ca2593
31 changed files with 1555 additions and 343 deletions

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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
# 密钥

View File

@ -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;
}

View File

@ -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;
}));

View File

@ -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 = '';
}

View File

@ -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>

View File

@ -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, "删除失败");
/**
* 错误编码

View File

@ -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 {
/**

View File

@ -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>

View File

@ -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 {
private ListParts buildResultPart(FileMetadataInfoVo metadataInfo){
String objectName = MinioPlusCommonUtil.getObjectName(metadataInfo.getFileMd5());
// 获取所有的分片信息
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);
}
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;

View File

@ -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);
}

View File

@ -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

View File

@ -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>

View File

@ -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;

View File

@ -1,4 +1,4 @@
package org.liuxp.minioplus.application.context;
package org.liuxp.minioplus.extension.context;
import java.util.Objects;

View File

@ -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;

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.liuxp.minioplus.extension.controller.StorageController

View File

@ -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>

View File

@ -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>

View File

@ -1,2 +0,0 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.liuxp.data.security.core.aop.config.DataSecurityAopConfiguration

View File

@ -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>

View File

@ -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) {
}
}

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.liuxp.minioplus.s3.custom.MinioS3ClientImpl

View File

@ -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;
}
}

View File

@ -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);
/**
* 取得下载链接

View File

@ -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>

View File

@ -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;

View File

@ -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);
}
}
}

View File

@ -0,0 +1,2 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.liuxp.minioplus.s3.official.MinioS3ClientImpl

48
pom.xml
View File

@ -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>
<!-- &lt;!&ndash; 要将源码放上去,需要加入这个插件 &ndash;&gt;-->
<!-- <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>-->
<!-- &lt;!&ndash;first execution : for preparing JaCoCo runtime agent&ndash;&gt;-->
<!-- <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>