diff --git a/minio-plus-application/minio-plus-application-mysql/pom.xml b/minio-plus-application/minio-plus-application-mysql/pom.xml index 6d1c4ab6c..35ec4484d 100644 --- a/minio-plus-application/minio-plus-application-mysql/pom.xml +++ b/minio-plus-application/minio-plus-application-mysql/pom.xml @@ -14,11 +14,7 @@ org.liuxp - minio-plus-core - - - org.liuxp - minio-plus-config + minio-plus-all-spring-boot-starter @@ -30,22 +26,10 @@ com.baomidou mybatis-plus-boot-starter - - org.springframework.boot - spring-boot-starter-web - org.springframework.boot spring-boot-starter-validation - - org.projectlombok - lombok - - - cn.hutool - hutool-all - mysql mysql-connector-java diff --git a/minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/MinioPlusApplication.java b/minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/OfficialApplication.java similarity index 68% rename from minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/MinioPlusApplication.java rename to minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/OfficialApplication.java index 9133be91b..77800aa39 100644 --- a/minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/MinioPlusApplication.java +++ b/minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/OfficialApplication.java @@ -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); } } \ No newline at end of file diff --git a/minio-plus-application/minio-plus-application-mysql/src/main/resources/application.yml b/minio-plus-application/minio-plus-application-mysql/src/main/resources/application.yml index efc249a4d..4e1d529a4 100644 --- a/minio-plus-application/minio-plus-application-mysql/src/main/resources/application.yml +++ b/minio-plus-application/minio-plus-application-mysql/src/main/resources/application.yml @@ -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 # 密钥 diff --git a/minio-plus-application/minio-plus-application-mysql/src/main/resources/static/css/flex.css b/minio-plus-application/minio-plus-application-mysql/src/main/resources/static/css/flex.css new file mode 100644 index 000000000..862fa7a98 --- /dev/null +++ b/minio-plus-application/minio-plus-application-mysql/src/main/resources/static/css/flex.css @@ -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; +} diff --git a/minio-plus-application/minio-plus-application-mysql/src/main/resources/static/js/spark-md5.js b/minio-plus-application/minio-plus-application-mysql/src/main/resources/static/js/spark-md5.js new file mode 100644 index 000000000..f87d59504 --- /dev/null +++ b/minio-plus-application/minio-plus-application-mysql/src/main/resources/static/js/spark-md5.js @@ -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; +})); diff --git a/minio-plus-application/minio-plus-application-mysql/src/main/resources/static/js/upload.js b/minio-plus-application/minio-plus-application-mysql/src/main/resources/static/js/upload.js new file mode 100644 index 000000000..9b92fc87e --- /dev/null +++ b/minio-plus-application/minio-plus-application-mysql/src/main/resources/static/js/upload.js @@ -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} + */ +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 = ''; + +} \ No newline at end of file diff --git a/minio-plus-application/minio-plus-application-mysql/src/main/resources/static/upload.html b/minio-plus-application/minio-plus-application-mysql/src/main/resources/static/upload.html new file mode 100644 index 000000000..67d7534a2 --- /dev/null +++ b/minio-plus-application/minio-plus-application-mysql/src/main/resources/static/upload.html @@ -0,0 +1,233 @@ + + + + + MinIO Plus Demo + + + + + + + + 检查 + 正常上传 + 模拟丢片上传 + 丢片恢复 + 删除文件 + + + 合并分片 + + + 预览图片 + 下载文件 + + + + 总计片数:{{partList.length}} + + 第{{index + 1}}片: + + {{item.url}} + + + + + + + + diff --git a/minio-plus-common/src/main/java/org/liuxp/minioplus/common/enums/MinioPlusErrorCode.java b/minio-plus-common/src/main/java/org/liuxp/minioplus/common/enums/MinioPlusErrorCode.java index f2ee7b028..bc8d909ef 100644 --- a/minio-plus-common/src/main/java/org/liuxp/minioplus/common/enums/MinioPlusErrorCode.java +++ b/minio-plus-common/src/main/java/org/liuxp/minioplus/common/enums/MinioPlusErrorCode.java @@ -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, "删除失败"); /** * 错误编码 diff --git a/minio-plus-config/src/main/java/org/liuxp/minioplus/config/MinioPlusProperties.java b/minio-plus-config/src/main/java/org/liuxp/minioplus/config/MinioPlusProperties.java index fe63abfe0..8fd738bca 100644 --- a/minio-plus-config/src/main/java/org/liuxp/minioplus/config/MinioPlusProperties.java +++ b/minio-plus-config/src/main/java/org/liuxp/minioplus/config/MinioPlusProperties.java @@ -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 { /** diff --git a/minio-plus-core/pom.xml b/minio-plus-core/pom.xml index ab8e98392..aa810782e 100644 --- a/minio-plus-core/pom.xml +++ b/minio-plus-core/pom.xml @@ -44,6 +44,10 @@ org.liuxp minio-plus-model + + org.liuxp + minio-s3-api-definition + net.coobird diff --git a/minio-plus-core/src/main/java/org/liuxp/minioplus/core/engine/impl/StorageEngineServiceImpl.java b/minio-plus-core/src/main/java/org/liuxp/minioplus/core/engine/impl/StorageEngineServiceImpl.java index f65e41cde..5caf15ae1 100644 --- a/minio-plus-core/src/main/java/org/liuxp/minioplus/core/engine/impl/StorageEngineServiceImpl.java +++ b/minio-plus-core/src/main/java/org/liuxp/minioplus/core/engine/impl/StorageEngineServiceImpl.java @@ -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 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 parts = listPartsResponse.result().partList(); + ListParts listParts = this.buildResultPart(fileMetadataVo); + List 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 partMd5List) { + public CompleteResultVo completeMultipartUpload(FileMetadataInfoVo metadataInfo, List partMd5List) { CompleteResultVo completeResultVo = new CompleteResultVo(); // 获取所有的分片信息 - ListPartsResponse listMultipart = this.buildResultPart(meteData); + ListParts listParts = this.buildResultPart(metadataInfo); List 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 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; diff --git a/minio-plus-core/src/main/java/org/liuxp/minioplus/core/repository/MinioRepository.java b/minio-plus-core/src/main/java/org/liuxp/minioplus/core/repository/MinioRepository.java deleted file mode 100644 index 720c01ec5..000000000 --- a/minio-plus-core/src/main/java/org/liuxp/minioplus/core/repository/MinioRepository.java +++ /dev/null @@ -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 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); - -} \ No newline at end of file diff --git a/minio-plus-core/src/main/resources/META-INF/spring.factories b/minio-plus-core/src/main/resources/META-INF/spring.factories index 84419a782..a7b130295 100644 --- a/minio-plus-core/src/main/resources/META-INF/spring.factories +++ b/minio-plus-core/src/main/resources/META-INF/spring.factories @@ -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 \ No newline at end of file +org.liuxp.minioplus.core.engine.impl.StorageEngineServiceImpl \ No newline at end of file diff --git a/minio-plus-extension/pom.xml b/minio-plus-extension/pom.xml index aa35e4c4e..71b86cd56 100644 --- a/minio-plus-extension/pom.xml +++ b/minio-plus-extension/pom.xml @@ -11,4 +11,15 @@ minio-plus-extension + + + org.liuxp + minio-plus-core + + + org.springframework.boot + spring-boot-starter-web + + + \ No newline at end of file diff --git a/minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/context/Response.java b/minio-plus-extension/src/main/java/org/liuxp/minioplus/extension/context/Response.java similarity index 96% rename from minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/context/Response.java rename to minio-plus-extension/src/main/java/org/liuxp/minioplus/extension/context/Response.java index 7baec1db1..5a60c1803 100644 --- a/minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/context/Response.java +++ b/minio-plus-extension/src/main/java/org/liuxp/minioplus/extension/context/Response.java @@ -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; diff --git a/minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/context/ResponseCodeEnum.java b/minio-plus-extension/src/main/java/org/liuxp/minioplus/extension/context/ResponseCodeEnum.java similarity index 95% rename from minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/context/ResponseCodeEnum.java rename to minio-plus-extension/src/main/java/org/liuxp/minioplus/extension/context/ResponseCodeEnum.java index 1f641e7a1..70b433555 100644 --- a/minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/context/ResponseCodeEnum.java +++ b/minio-plus-extension/src/main/java/org/liuxp/minioplus/extension/context/ResponseCodeEnum.java @@ -1,4 +1,4 @@ -package org.liuxp.minioplus.application.context; +package org.liuxp.minioplus.extension.context; import java.util.Objects; diff --git a/minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/controller/StorageController.java b/minio-plus-extension/src/main/java/org/liuxp/minioplus/extension/controller/StorageController.java similarity index 98% rename from minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/controller/StorageController.java rename to minio-plus-extension/src/main/java/org/liuxp/minioplus/extension/controller/StorageController.java index eb6fbeed7..0456e0a0f 100644 --- a/minio-plus-application/minio-plus-application-mysql/src/main/java/org/liuxp/minioplus/application/controller/StorageController.java +++ b/minio-plus-extension/src/main/java/org/liuxp/minioplus/extension/controller/StorageController.java @@ -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; diff --git a/minio-plus-extension/src/main/resources/META-INF/spring.factories b/minio-plus-extension/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..48cbf92f5 --- /dev/null +++ b/minio-plus-extension/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.liuxp.minioplus.extension.controller.StorageController \ No newline at end of file diff --git a/minio-plus-spring-boot-starter/minio-plus-all-spring-boot-starter/pom.xml b/minio-plus-spring-boot-starter/minio-plus-all-spring-boot-starter/pom.xml new file mode 100644 index 000000000..35022f8a3 --- /dev/null +++ b/minio-plus-spring-boot-starter/minio-plus-all-spring-boot-starter/pom.xml @@ -0,0 +1,25 @@ + + + + minio-plus-spring-boot-starter + org.liuxp + ${revision} + + 4.0.0 + + minio-plus-all-spring-boot-starter + + + + org.liuxp + minio-plus-extension + + + org.liuxp + minio-s3-api-official + + + + \ No newline at end of file diff --git a/minio-plus-spring-boot-starter/pom.xml b/minio-plus-spring-boot-starter/pom.xml index dbad98f71..519499c5a 100644 --- a/minio-plus-spring-boot-starter/pom.xml +++ b/minio-plus-spring-boot-starter/pom.xml @@ -2,11 +2,18 @@ + + minio-plus-parent + org.liuxp + ${revision} + 4.0.0 - org.liuxp minio-plus-spring-boot-starter - ${revision} + pom + + minio-plus-all-spring-boot-starter + \ No newline at end of file diff --git a/minio-plus-spring-boot-starter/src/main/resources/META-INF/spring.factories b/minio-plus-spring-boot-starter/src/main/resources/META-INF/spring.factories deleted file mode 100644 index 55f57fd85..000000000 --- a/minio-plus-spring-boot-starter/src/main/resources/META-INF/spring.factories +++ /dev/null @@ -1,2 +0,0 @@ -org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ - org.liuxp.data.security.core.aop.config.DataSecurityAopConfiguration \ No newline at end of file diff --git a/minio-s3-api/minio-s3-api-custom/pom.xml b/minio-s3-api/minio-s3-api-custom/pom.xml index 61360fe7c..0b5361a85 100644 --- a/minio-s3-api/minio-s3-api-custom/pom.xml +++ b/minio-s3-api/minio-s3-api-custom/pom.xml @@ -16,6 +16,14 @@ cn.hutool hutool-all + + org.liuxp + minio-s3-api-definition + + + org.liuxp + minio-plus-config + \ No newline at end of file diff --git a/minio-s3-api/minio-s3-api-custom/src/main/java/org/liuxp/minioplus/s3/custom/MinioS3ClientImpl.java b/minio-s3-api/minio-s3-api-custom/src/main/java/org/liuxp/minioplus/s3/custom/MinioS3ClientImpl.java new file mode 100644 index 000000000..011856b5c --- /dev/null +++ b/minio-s3-api/minio-s3-api-custom/src/main/java/org/liuxp/minioplus/s3/custom/MinioS3ClientImpl.java @@ -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 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) { + + } +} diff --git a/minio-s3-api/minio-s3-api-custom/src/main/resources/META-INF/spring.factories b/minio-s3-api/minio-s3-api-custom/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..0263741e2 --- /dev/null +++ b/minio-s3-api/minio-s3-api-custom/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.liuxp.minioplus.s3.custom.MinioS3ClientImpl \ No newline at end of file diff --git a/minio-s3-api/minio-s3-api-definition/src/main/java/org/liuxp/minioplus/s3/def/ListParts.java b/minio-s3-api/minio-s3-api-definition/src/main/java/org/liuxp/minioplus/s3/def/ListParts.java index 8032c9cb3..4fff9d8ae 100644 --- a/minio-s3-api/minio-s3-api-definition/src/main/java/org/liuxp/minioplus/s3/def/ListParts.java +++ b/minio-s3-api/minio-s3-api-definition/src/main/java/org/liuxp/minioplus/s3/def/ListParts.java @@ -19,12 +19,14 @@ public class ListParts { private int maxParts; + private String uploadId; + private List 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; } } diff --git a/minio-s3-api/minio-s3-api-definition/src/main/java/org/liuxp/minioplus/s3/def/MinioS3Client.java b/minio-s3-api/minio-s3-api-definition/src/main/java/org/liuxp/minioplus/s3/def/MinioS3Client.java index c9c8637d3..b20386d65 100644 --- a/minio-s3-api/minio-s3-api-definition/src/main/java/org/liuxp/minioplus/s3/def/MinioS3Client.java +++ b/minio-s3-api/minio-s3-api-definition/src/main/java/org/liuxp/minioplus/s3/def/MinioS3Client.java @@ -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); /** * 取得下载链接 diff --git a/minio-s3-api/minio-s3-api-official/pom.xml b/minio-s3-api/minio-s3-api-official/pom.xml index befc8e24a..74ac3fb36 100644 --- a/minio-s3-api/minio-s3-api-official/pom.xml +++ b/minio-s3-api/minio-s3-api-official/pom.xml @@ -28,6 +28,18 @@ okhttp 4.11.0 + + org.liuxp + minio-s3-api-definition + + + org.liuxp + minio-plus-config + + + org.liuxp + minio-plus-common + \ No newline at end of file diff --git a/minio-plus-core/src/main/java/org/liuxp/minioplus/core/repository/impl/CustomMinioClient.java b/minio-s3-api/minio-s3-api-official/src/main/java/org/liuxp/minioplus/s3/official/CustomMinioClient.java similarity index 98% rename from minio-plus-core/src/main/java/org/liuxp/minioplus/core/repository/impl/CustomMinioClient.java rename to minio-s3-api/minio-s3-api-official/src/main/java/org/liuxp/minioplus/s3/official/CustomMinioClient.java index ee4f878b6..20c61bb55 100644 --- a/minio-plus-core/src/main/java/org/liuxp/minioplus/core/repository/impl/CustomMinioClient.java +++ b/minio-s3-api/minio-s3-api-official/src/main/java/org/liuxp/minioplus/s3/official/CustomMinioClient.java @@ -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; diff --git a/minio-plus-core/src/main/java/org/liuxp/minioplus/core/repository/impl/MinioRepositoryImpl.java b/minio-s3-api/minio-s3-api-official/src/main/java/org/liuxp/minioplus/s3/official/MinioS3ClientImpl.java similarity index 62% rename from minio-plus-core/src/main/java/org/liuxp/minioplus/core/repository/impl/MinioRepositoryImpl.java rename to minio-s3-api/minio-s3-api-official/src/main/java/org/liuxp/minioplus/s3/official/MinioS3ClientImpl.java index a876b9287..0585a92d1 100644 --- a/minio-plus-core/src/main/java/org/liuxp/minioplus/core/repository/impl/MinioRepositoryImpl.java +++ b/minio-s3-api/minio-s3-api-official/src/main/java/org/liuxp/minioplus/s3/official/MinioS3ClientImpl.java @@ -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 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 queryParams) { + public String getUploadObjectUrl(String bucketName, String objectName, String uploadId,String partNumber) { + + Map 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 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 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); } } - - -} \ No newline at end of file +} diff --git a/minio-s3-api/minio-s3-api-official/src/main/resources/META-INF/spring.factories b/minio-s3-api/minio-s3-api-official/src/main/resources/META-INF/spring.factories new file mode 100644 index 000000000..ed11e3fae --- /dev/null +++ b/minio-s3-api/minio-s3-api-official/src/main/resources/META-INF/spring.factories @@ -0,0 +1,2 @@ +org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ +org.liuxp.minioplus.s3.official.MinioS3ClientImpl \ No newline at end of file diff --git a/pom.xml b/pom.xml index d011f7e7c..781163458 100644 --- a/pom.xml +++ b/pom.xml @@ -164,7 +164,22 @@ org.liuxp - minio-plus-spring-boot-starter + minio-plus-all-spring-boot-starter + ${revision} + + + org.liuxp + minio-s3-api-definition + ${revision} + + + org.liuxp + minio-s3-api-custom + ${revision} + + + org.liuxp + minio-s3-api-official ${revision} @@ -209,37 +224,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - org.codehaus.mojo flatten-maven-plugin