feat: minio功能开发
This commit is contained in:
parent
46a4132877
commit
3610d8fdc6
15
.env
15
.env
|
@ -11,13 +11,17 @@ LOGGER_MAX_FILES = 31
|
|||
TZ = Asia/Shanghai
|
||||
|
||||
# OSS(minio)
|
||||
OSS_ACCESSKEY=xxx
|
||||
OSS_SECRETKEY=xxx
|
||||
OSS_DOMAIN=https://cdn.louis.site
|
||||
OSS_BUCKET=hxoa
|
||||
OSS_ACCESSKEY=8Zttvx4ZbF2ikFRb
|
||||
OSS_SECRETKEY=SCgOJEJXM5vMNQL4fF8opXA1wmpACRfw
|
||||
OSS_PORT=8021
|
||||
OSS_DOMAIN=144.123.43.138
|
||||
OSS_DOMAIN_USE_SSL=false
|
||||
OSS_BUCKET=tes1
|
||||
OSS_ZONE=Zone_z2 # Zone_as0 | Zone_na0 | Zone_z0 | Zone_z1 | Zone_z2
|
||||
OSS_ACCESS_TYPE=public # or private
|
||||
|
||||
|
||||
|
||||
DB_HOST = host.docker.internal
|
||||
DB_PORT = 13307
|
||||
DB_DATABASE = hxoa
|
||||
|
@ -29,4 +33,5 @@ DB_LOGGING = ["error"]
|
|||
REDIS_PORT = 6379
|
||||
REDIS_HOST = host.docker.internal
|
||||
REDIS_PASSWORD = 123456
|
||||
REDIS_DB = 0
|
||||
REDIS_DB = 0
|
||||
|
||||
|
|
|
@ -33,4 +33,3 @@ SMTP_PORT = 465
|
|||
SMTP_USER = nest_admin@163.com
|
||||
SMTP_PASS = VIPLLOIPMETTROYU
|
||||
|
||||
|
|
@ -34,4 +34,5 @@ SMTP_USER = nest_admin@163.com
|
|||
SMTP_PASS = VIPLLOIPMETTROYU
|
||||
|
||||
# 是否为演示模式(在演示模式下,会拦截除 GET 方法以外的所有请求)
|
||||
IS_DEMO = false
|
||||
IS_DEMO = false
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
const Minio = require('minio');
|
||||
|
||||
const minioClient = new Minio.Client({
|
||||
endPoint: '144.123.43.138',
|
||||
port: 8021,
|
||||
useSSL: false,
|
||||
accessKey: '8Zttvx4ZbF2ikFRb',
|
||||
secretKey: 'SCgOJEJXM5vMNQL4fF8opXA1wmpACRfw'
|
||||
});
|
||||
|
||||
minioClient.listBuckets((err, buckets) => {
|
||||
if (err) return console.log(err);
|
||||
console.log('Buckets:', buckets);
|
||||
});
|
|
@ -88,6 +88,7 @@
|
|||
"helmet": "^7.1.0",
|
||||
"ioredis": "^5.3.2",
|
||||
"lodash": "^4.17.21",
|
||||
"minio": "^7.1.3",
|
||||
"mysql2": "^3.9.1",
|
||||
"nanoid": "^3.3.7",
|
||||
"nodemailer": "^6.9.9",
|
||||
|
|
169
pnpm-lock.yaml
169
pnpm-lock.yaml
|
@ -131,6 +131,9 @@ dependencies:
|
|||
lodash:
|
||||
specifier: ^4.17.21
|
||||
version: 4.17.21
|
||||
minio:
|
||||
specifier: ^7.1.3
|
||||
version: 7.1.3
|
||||
mysql2:
|
||||
specifier: ^3.9.1
|
||||
version: 3.9.1
|
||||
|
@ -3663,6 +3666,12 @@ packages:
|
|||
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
|
||||
dev: true
|
||||
|
||||
/@zxing/text-encoding@0.9.0:
|
||||
resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==}
|
||||
requiresBuild: true
|
||||
dev: false
|
||||
optional: true
|
||||
|
||||
/JSONStream@1.3.5:
|
||||
resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
|
||||
hasBin: true
|
||||
|
@ -4061,6 +4070,13 @@ packages:
|
|||
engines: {node: '>=8.0.0'}
|
||||
dev: false
|
||||
|
||||
/available-typed-arrays@1.0.7:
|
||||
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
possible-typed-array-names: 1.0.0
|
||||
dev: false
|
||||
|
||||
/avvio@8.3.0:
|
||||
resolution: {integrity: sha512-VBVH0jubFr9LdFASy/vNtm5giTrnbVquWBhT0fyizuNK2rQ7e7ONU2plZQWUNqtE1EmxFEb+kbSkFRkstiaS9Q==}
|
||||
dependencies:
|
||||
|
@ -4348,6 +4364,10 @@ packages:
|
|||
base64-js: 1.5.1
|
||||
dev: true
|
||||
|
||||
/browser-or-node@2.1.1:
|
||||
resolution: {integrity: sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==}
|
||||
dev: false
|
||||
|
||||
/browser-resolve@1.11.3:
|
||||
resolution: {integrity: sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==}
|
||||
dependencies:
|
||||
|
@ -5409,6 +5429,11 @@ packages:
|
|||
engines: {node: '>=0.10.0'}
|
||||
dev: true
|
||||
|
||||
/decode-uri-component@0.2.2:
|
||||
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
|
||||
engines: {node: '>=0.10'}
|
||||
dev: false
|
||||
|
||||
/dedent@0.7.0:
|
||||
resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
|
||||
dev: true
|
||||
|
@ -6340,6 +6365,13 @@ packages:
|
|||
resolution: {integrity: sha512-eel5UKGn369gGEWOqBShmFJWfq/xSJvsgDzgLYC845GneayWvXBf0lJCBn5qTABfewy1ZDPoaR5OZCP+kssfuw==}
|
||||
dev: false
|
||||
|
||||
/fast-xml-parser@4.3.5:
|
||||
resolution: {integrity: sha512-sWvP1Pl8H03B8oFJpFR3HE31HUfwtX7Rlf9BNsvdpujD4n7WMhfmu8h9wOV2u+c1k0ZilTADhPqypzx2J690ZQ==}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
strnum: 1.0.5
|
||||
dev: false
|
||||
|
||||
/fastify-plugin@4.5.1:
|
||||
resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==}
|
||||
dev: false
|
||||
|
@ -6438,6 +6470,11 @@ packages:
|
|||
dependencies:
|
||||
to-regex-range: 5.0.1
|
||||
|
||||
/filter-obj@1.1.0:
|
||||
resolution: {integrity: sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
dev: false
|
||||
|
||||
/finalhandler@1.1.2:
|
||||
resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
|
||||
engines: {node: '>= 0.8'}
|
||||
|
@ -6559,6 +6596,12 @@ packages:
|
|||
optional: true
|
||||
dev: false
|
||||
|
||||
/for-each@0.3.3:
|
||||
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
|
||||
dependencies:
|
||||
is-callable: 1.2.7
|
||||
dev: false
|
||||
|
||||
/foreground-child@3.1.1:
|
||||
resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
|
||||
engines: {node: '>=14'}
|
||||
|
@ -7359,13 +7402,17 @@ packages:
|
|||
engines: {node: '>= 0.10'}
|
||||
dev: false
|
||||
|
||||
/ipaddr.js@2.1.0:
|
||||
resolution: {integrity: sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==}
|
||||
engines: {node: '>= 10'}
|
||||
dev: false
|
||||
|
||||
/is-arguments@1.1.1:
|
||||
resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
call-bind: 1.0.7
|
||||
has-tostringtag: 1.0.2
|
||||
dev: true
|
||||
|
||||
/is-arrayish@0.2.1:
|
||||
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||
|
@ -7380,6 +7427,11 @@ packages:
|
|||
dependencies:
|
||||
binary-extensions: 2.2.0
|
||||
|
||||
/is-callable@1.2.7:
|
||||
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: false
|
||||
|
||||
/is-core-module@2.13.1:
|
||||
resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
|
||||
dependencies:
|
||||
|
@ -7428,6 +7480,13 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/is-generator-function@1.0.10:
|
||||
resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
has-tostringtag: 1.0.2
|
||||
dev: false
|
||||
|
||||
/is-glob@4.0.3:
|
||||
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -7490,6 +7549,13 @@ packages:
|
|||
text-extensions: 1.9.0
|
||||
dev: true
|
||||
|
||||
/is-typed-array@1.1.13:
|
||||
resolution: {integrity: sha512-uZ25/bUAlUY5fR4OKT4rZQEBrzQWYV9ZJYGGsUmEJ6thodVJ1HX64ePQ6Z0qPWP+m+Uq6e9UugrE38jeYsDSMw==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
which-typed-array: 1.1.14
|
||||
dev: false
|
||||
|
||||
/is-unicode-supported@0.1.0:
|
||||
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -8104,6 +8170,10 @@ packages:
|
|||
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
|
||||
dev: true
|
||||
|
||||
/json-stream@1.0.0:
|
||||
resolution: {integrity: sha512-H/ZGY0nIAg3QcOwE1QN/rK/Fa7gJn7Ii5obwp6zyPO4xiPNwpIMjqy2gwjBEGqzkF/vSWEIBQCBuN19hYiL6Qg==}
|
||||
dev: false
|
||||
|
||||
/json-stringify-safe@5.0.1:
|
||||
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
|
||||
dev: true
|
||||
|
@ -8797,6 +8867,26 @@ packages:
|
|||
/minimist@1.2.8:
|
||||
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
|
||||
|
||||
/minio@7.1.3:
|
||||
resolution: {integrity: sha512-xPrLjWkTT5E7H7VnzOjF//xBp9I40jYB4aWhb2xTFopXXfw+Wo82DDWngdUju7Doy3Wk7R8C4LAgwhLHHnf0wA==}
|
||||
engines: {node: ^16 || ^18 || >=20}
|
||||
dependencies:
|
||||
async: 3.2.5
|
||||
block-stream2: 2.1.0
|
||||
browser-or-node: 2.1.1
|
||||
buffer-crc32: 0.2.13
|
||||
fast-xml-parser: 4.3.5
|
||||
ipaddr.js: 2.1.0
|
||||
json-stream: 1.0.0
|
||||
lodash: 4.17.21
|
||||
mime-types: 2.1.35
|
||||
query-string: 7.1.3
|
||||
through2: 4.0.2
|
||||
web-encoding: 1.1.5
|
||||
xml: 1.0.1
|
||||
xml2js: 0.5.0
|
||||
dev: false
|
||||
|
||||
/minipass@3.3.6:
|
||||
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
|
||||
engines: {node: '>=8'}
|
||||
|
@ -10056,6 +10146,11 @@ packages:
|
|||
resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==}
|
||||
dev: true
|
||||
|
||||
/possible-typed-array-names@1.0.0:
|
||||
resolution: {integrity: sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: false
|
||||
|
||||
/prelude-ls@1.1.2:
|
||||
resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
@ -10328,6 +10423,16 @@ packages:
|
|||
dependencies:
|
||||
side-channel: 1.0.5
|
||||
|
||||
/query-string@7.1.3:
|
||||
resolution: {integrity: sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==}
|
||||
engines: {node: '>=6'}
|
||||
dependencies:
|
||||
decode-uri-component: 0.2.2
|
||||
filter-obj: 1.1.0
|
||||
split-on-first: 1.1.0
|
||||
strict-uri-encode: 2.0.0
|
||||
dev: false
|
||||
|
||||
/queue-microtask@1.2.3:
|
||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||
dev: true
|
||||
|
@ -10723,7 +10828,6 @@ packages:
|
|||
|
||||
/sax@1.3.0:
|
||||
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
|
||||
dev: true
|
||||
|
||||
/saxes@5.0.1:
|
||||
resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==}
|
||||
|
@ -11060,6 +11164,11 @@ packages:
|
|||
resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==}
|
||||
dev: true
|
||||
|
||||
/split-on-first@1.1.0:
|
||||
resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==}
|
||||
engines: {node: '>=6'}
|
||||
dev: false
|
||||
|
||||
/split2@3.2.2:
|
||||
resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==}
|
||||
dependencies:
|
||||
|
@ -11192,6 +11301,11 @@ packages:
|
|||
engines: {node: '>=4.0.0'}
|
||||
dev: false
|
||||
|
||||
/strict-uri-encode@2.0.0:
|
||||
resolution: {integrity: sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==}
|
||||
engines: {node: '>=4'}
|
||||
dev: false
|
||||
|
||||
/string-length@4.0.2:
|
||||
resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==}
|
||||
engines: {node: '>=10'}
|
||||
|
@ -11302,6 +11416,10 @@ packages:
|
|||
engines: {node: '>=8'}
|
||||
dev: true
|
||||
|
||||
/strnum@1.0.5:
|
||||
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
|
||||
dev: false
|
||||
|
||||
/superagent@8.1.2:
|
||||
resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==}
|
||||
engines: {node: '>=6.4.0 <13 || >=14'}
|
||||
|
@ -11513,7 +11631,6 @@ packages:
|
|||
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
|
||||
dependencies:
|
||||
readable-stream: 3.6.2
|
||||
dev: true
|
||||
|
||||
/through@2.3.8:
|
||||
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
|
||||
|
@ -12026,6 +12143,16 @@ packages:
|
|||
/util-deprecate@1.0.2:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
|
||||
/util@0.12.5:
|
||||
resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==}
|
||||
dependencies:
|
||||
inherits: 2.0.4
|
||||
is-arguments: 1.1.1
|
||||
is-generator-function: 1.0.10
|
||||
is-typed-array: 1.1.13
|
||||
which-typed-array: 1.1.14
|
||||
dev: false
|
||||
|
||||
/utility@1.18.0:
|
||||
resolution: {integrity: sha512-PYxZDA+6QtvRvm//++aGdmKG/cI07jNwbROz0Ql+VzFV1+Z0Dy55NI4zZ7RHc9KKpBePNFwoErqIuqQv/cjiTA==}
|
||||
engines: {node: '>= 0.12.0'}
|
||||
|
@ -12118,6 +12245,14 @@ packages:
|
|||
defaults: 1.0.4
|
||||
dev: true
|
||||
|
||||
/web-encoding@1.1.5:
|
||||
resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==}
|
||||
dependencies:
|
||||
util: 0.12.5
|
||||
optionalDependencies:
|
||||
'@zxing/text-encoding': 0.9.0
|
||||
dev: false
|
||||
|
||||
/web-resource-inliner@6.0.1:
|
||||
resolution: {integrity: sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
|
@ -12205,6 +12340,17 @@ packages:
|
|||
tr46: 0.0.3
|
||||
webidl-conversions: 3.0.1
|
||||
|
||||
/which-typed-array@1.1.14:
|
||||
resolution: {integrity: sha512-VnXFiIW8yNn9kIHN88xvZ4yOWchftKDsRJ8fEPacX/wl1lOvBrhsJ/OeJCXq7B0AaijRuqgzSKalJoPk+D8MPg==}
|
||||
engines: {node: '>= 0.4'}
|
||||
dependencies:
|
||||
available-typed-arrays: 1.0.7
|
||||
call-bind: 1.0.7
|
||||
for-each: 0.3.3
|
||||
gopd: 1.0.1
|
||||
has-tostringtag: 1.0.2
|
||||
dev: false
|
||||
|
||||
/which@1.3.1:
|
||||
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
|
||||
hasBin: true
|
||||
|
@ -12353,6 +12499,23 @@ packages:
|
|||
utf-8-validate:
|
||||
optional: true
|
||||
|
||||
/xml2js@0.5.0:
|
||||
resolution: {integrity: sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==}
|
||||
engines: {node: '>=4.0.0'}
|
||||
dependencies:
|
||||
sax: 1.3.0
|
||||
xmlbuilder: 11.0.1
|
||||
dev: false
|
||||
|
||||
/xml@1.0.1:
|
||||
resolution: {integrity: sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==}
|
||||
dev: false
|
||||
|
||||
/xmlbuilder@11.0.1:
|
||||
resolution: {integrity: sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==}
|
||||
engines: {node: '>=4.0'}
|
||||
dev: false
|
||||
|
||||
/xmlchars@2.2.0:
|
||||
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||
dev: false
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { ConfigType, registerAs } from '@nestjs/config';
|
||||
import * as qiniu from 'qiniu';
|
||||
|
||||
import { env } from '~/global/env';
|
||||
import { env, envBoolean, envNumber } from '~/global/env';
|
||||
|
||||
function parseZone(zone: string) {
|
||||
switch (zone) {
|
||||
|
@ -24,6 +24,8 @@ export const OssConfig = registerAs(ossRegToken, () => ({
|
|||
accessKey: env('OSS_ACCESSKEY'),
|
||||
secretKey: env('OSS_SECRETKEY'),
|
||||
domain: env('OSS_DOMAIN'),
|
||||
port: envNumber('OSS_PORT'),
|
||||
useSSL: envBoolean('OSS_USE_SSL'),
|
||||
bucket: env('OSS_BUCKET'),
|
||||
zone: parseZone(env('OSS_ZONE') || 'Zone_z2'),
|
||||
access: (env('OSS_ACCESS_TYPE') as any) || 'public'
|
||||
|
|
|
@ -0,0 +1,836 @@
|
|||
import { basename, extname } from 'node:path';
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { isEmpty } from 'lodash';
|
||||
import * as qiniu from 'qiniu';
|
||||
import { auth, conf, rs } from 'qiniu';
|
||||
|
||||
import { ConfigKeyPaths } from '~/config';
|
||||
import {
|
||||
NETDISK_COPY_SUFFIX,
|
||||
NETDISK_DELIMITER,
|
||||
NETDISK_HANDLE_MAX_ITEM,
|
||||
NETDISK_LIMIT
|
||||
} from '~/constants/oss.constant';
|
||||
|
||||
import { AccountInfo } from '~/modules/user/user.model';
|
||||
import { UserService } from '~/modules/user/user.service';
|
||||
|
||||
import { generateRandomValue } from '~/utils';
|
||||
|
||||
import { SFileInfo, SFileInfoDetail, SFileList } from './manage.class';
|
||||
import { FileOpItem } from './manage.dto';
|
||||
|
||||
@Injectable()
|
||||
export class QiNiuNetDiskManageService {
|
||||
private config: conf.ConfigOptions;
|
||||
private mac: auth.digest.Mac;
|
||||
private bucketManager: rs.BucketManager;
|
||||
|
||||
private get qiniuConfig() {
|
||||
return this.configService.get('oss', { infer: true });
|
||||
}
|
||||
|
||||
constructor(
|
||||
private configService: ConfigService<ConfigKeyPaths>,
|
||||
private userService: UserService
|
||||
) {
|
||||
this.mac = new qiniu.auth.digest.Mac(this.qiniuConfig.accessKey, this.qiniuConfig.secretKey);
|
||||
this.config = new qiniu.conf.Config({
|
||||
zone: this.qiniuConfig.zone
|
||||
});
|
||||
// bucket manager
|
||||
this.bucketManager = new qiniu.rs.BucketManager(this.mac, this.config);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件列表
|
||||
* @param prefix 当前文件夹路径,搜索模式下会被忽略
|
||||
* @param marker 下一页标识
|
||||
* @returns iFileListResult
|
||||
*/
|
||||
async getFileList(prefix = '', marker = '', skey = ''): Promise<SFileList> {
|
||||
// 是否需要搜索
|
||||
const searching = !isEmpty(skey);
|
||||
return new Promise<SFileList>((resolve, reject) => {
|
||||
this.bucketManager.listPrefix(
|
||||
this.qiniuConfig.bucket,
|
||||
{
|
||||
prefix: searching ? '' : prefix,
|
||||
limit: NETDISK_LIMIT,
|
||||
delimiter: searching ? '' : NETDISK_DELIMITER,
|
||||
marker
|
||||
},
|
||||
(err, respBody, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
// 如果这个nextMarker不为空,那么还有未列举完毕的文件列表,下次调用listPrefix的时候,
|
||||
// 指定options里面的marker为这个值
|
||||
const fileList: SFileInfo[] = [];
|
||||
// 处理目录,但只有非搜索模式下可用
|
||||
if (!searching && !isEmpty(respBody.commonPrefixes)) {
|
||||
// dir
|
||||
for (const dirPath of respBody.commonPrefixes) {
|
||||
const name = (dirPath as string).substr(0, dirPath.length - 1).replace(prefix, '');
|
||||
if (isEmpty(skey) || name.includes(skey)) {
|
||||
fileList.push({
|
||||
name: (dirPath as string).substr(0, dirPath.length - 1).replace(prefix, ''),
|
||||
type: 'dir',
|
||||
id: generateRandomValue(10)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// handle items
|
||||
if (!isEmpty(respBody.items)) {
|
||||
// file
|
||||
for (const item of respBody.items) {
|
||||
// 搜索模式下处理
|
||||
if (searching) {
|
||||
const pathList: string[] = item.key.split(NETDISK_DELIMITER);
|
||||
// dir is empty stirng, file is key string
|
||||
const name = pathList.pop();
|
||||
if (
|
||||
item.key.endsWith(NETDISK_DELIMITER) &&
|
||||
pathList[pathList.length - 1].includes(skey)
|
||||
) {
|
||||
// 结果是目录
|
||||
const ditName = pathList.pop();
|
||||
fileList.push({
|
||||
id: generateRandomValue(10),
|
||||
name: ditName,
|
||||
type: 'dir',
|
||||
belongTo: pathList.join(NETDISK_DELIMITER)
|
||||
});
|
||||
} else if (name.includes(skey)) {
|
||||
// 文件
|
||||
fileList.push({
|
||||
id: generateRandomValue(10),
|
||||
name,
|
||||
type: 'file',
|
||||
fsize: item.fsize,
|
||||
mimeType: item.mimeType,
|
||||
putTime: new Date(Number.parseInt(item.putTime) / 10000),
|
||||
belongTo: pathList.join(NETDISK_DELIMITER)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 正常获取列表
|
||||
const fileKey = item.key.replace(prefix, '') as string;
|
||||
if (!isEmpty(fileKey)) {
|
||||
fileList.push({
|
||||
id: generateRandomValue(10),
|
||||
name: fileKey,
|
||||
type: 'file',
|
||||
fsize: item.fsize,
|
||||
mimeType: item.mimeType,
|
||||
putTime: new Date(Number.parseInt(item.putTime) / 10000)
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
resolve({
|
||||
list: fileList,
|
||||
marker: respBody.marker || null
|
||||
});
|
||||
} else {
|
||||
reject(
|
||||
new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取文件信息
|
||||
*/
|
||||
async getFileInfo(name: string, path: string): Promise<SFileInfoDetail> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.bucketManager.stat(
|
||||
this.qiniuConfig.bucket,
|
||||
`${path}${name}`,
|
||||
(err, respBody, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
const detailInfo: SFileInfoDetail = {
|
||||
fsize: respBody.fsize,
|
||||
hash: respBody.hash,
|
||||
md5: respBody.md5,
|
||||
mimeType: respBody.mimeType.split('/x-qn-meta')[0],
|
||||
putTime: new Date(Number.parseInt(respBody.putTime) / 10000),
|
||||
type: respBody.type,
|
||||
uploader: '',
|
||||
mark: respBody?.['x-qn-meta']?.['!mark'] ?? ''
|
||||
};
|
||||
if (!respBody.endUser) {
|
||||
resolve(detailInfo);
|
||||
} else {
|
||||
this.userService
|
||||
.getAccountInfo(Number.parseInt(respBody.endUser))
|
||||
.then((user: AccountInfo) => {
|
||||
if (isEmpty(user)) {
|
||||
resolve(detailInfo);
|
||||
} else {
|
||||
detailInfo.uploader = user.username;
|
||||
resolve(detailInfo);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
reject(
|
||||
new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改文件MimeType
|
||||
*/
|
||||
async changeFileHeaders(
|
||||
name: string,
|
||||
path: string,
|
||||
headers: { [k: string]: string }
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.bucketManager.changeHeaders(
|
||||
this.qiniuConfig.bucket,
|
||||
`${path}${name}`,
|
||||
headers,
|
||||
(err, _, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建文件夹
|
||||
* @returns true创建成功
|
||||
*/
|
||||
async createDir(dirName: string): Promise<void> {
|
||||
const safeDirName = dirName.endsWith('/') ? dirName : `${dirName}/`;
|
||||
return new Promise((resolve, reject) => {
|
||||
// 上传一个空文件以用于显示文件夹效果
|
||||
const formUploader = new qiniu.form_up.FormUploader(this.config);
|
||||
const putExtra = new qiniu.form_up.PutExtra();
|
||||
formUploader.put(
|
||||
this.createUploadToken(''),
|
||||
safeDirName,
|
||||
' ',
|
||||
putExtra,
|
||||
(respErr, respBody, respInfo) => {
|
||||
if (respErr) {
|
||||
reject(respErr);
|
||||
return;
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查文件是否存在,同可检查目录
|
||||
*/
|
||||
async checkFileExist(filePath: string): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// fix path end must a /
|
||||
|
||||
// 检测文件夹是否存在
|
||||
this.bucketManager.stat(this.qiniuConfig.bucket, filePath, (respErr, respBody, respInfo) => {
|
||||
if (respErr) {
|
||||
reject(respErr);
|
||||
return;
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
// 文件夹存在
|
||||
resolve(true);
|
||||
} else if (respInfo.statusCode === 612) {
|
||||
// 文件夹不存在
|
||||
resolve(false);
|
||||
} else {
|
||||
reject(
|
||||
new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建Upload Token, 默认过期时间一小时
|
||||
* @returns upload token
|
||||
*/
|
||||
createUploadToken(endUser: string): string {
|
||||
const policy = new qiniu.rs.PutPolicy({
|
||||
scope: this.qiniuConfig.bucket,
|
||||
insertOnly: 1,
|
||||
fsizeLimit: 1024 ** 2 * 10,
|
||||
endUser
|
||||
});
|
||||
const uploadToken = policy.uploadToken(this.mac);
|
||||
return uploadToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名文件
|
||||
* @param dir 文件路径
|
||||
* @param name 文件名称
|
||||
*/
|
||||
async renameFile(dir: string, name: string, toName: string): Promise<void> {
|
||||
const fileName = `${dir}${name}`;
|
||||
const toFileName = `${dir}${toName}`;
|
||||
const op = {
|
||||
force: true
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
this.bucketManager.move(
|
||||
this.qiniuConfig.bucket,
|
||||
fileName,
|
||||
this.qiniuConfig.bucket,
|
||||
toFileName,
|
||||
op,
|
||||
(err, respBody, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
if (respInfo.statusCode === 200) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动文件
|
||||
*/
|
||||
async moveFile(dir: string, toDir: string, name: string): Promise<void> {
|
||||
const fileName = `${dir}${name}`;
|
||||
const toFileName = `${toDir}${name}`;
|
||||
const op = {
|
||||
force: true
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
this.bucketManager.move(
|
||||
this.qiniuConfig.bucket,
|
||||
fileName,
|
||||
this.qiniuConfig.bucket,
|
||||
toFileName,
|
||||
op,
|
||||
(err, respBody, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
if (respInfo.statusCode === 200) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文件
|
||||
*/
|
||||
async copyFile(dir: string, toDir: string, name: string): Promise<void> {
|
||||
const fileName = `${dir}${name}`;
|
||||
// 拼接文件名
|
||||
const ext = extname(name);
|
||||
const bn = basename(name, ext);
|
||||
const toFileName = `${toDir}${bn}${NETDISK_COPY_SUFFIX}${ext}`;
|
||||
const op = {
|
||||
force: true
|
||||
};
|
||||
return new Promise((resolve, reject) => {
|
||||
this.bucketManager.copy(
|
||||
this.qiniuConfig.bucket,
|
||||
fileName,
|
||||
this.qiniuConfig.bucket,
|
||||
toFileName,
|
||||
op,
|
||||
(err, respBody, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
if (respInfo.statusCode === 200) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 重命名文件夹
|
||||
*/
|
||||
async renameDir(path: string, name: string, toName: string): Promise<void> {
|
||||
const dirName = `${path}${name}`;
|
||||
const toDirName = `${path}${toName}`;
|
||||
let hasFile = true;
|
||||
let marker = '';
|
||||
const op = {
|
||||
force: true
|
||||
};
|
||||
const bucketName = this.qiniuConfig.bucket;
|
||||
while (hasFile) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
// 列举当前目录下的所有文件
|
||||
this.bucketManager.listPrefix(
|
||||
this.qiniuConfig.bucket,
|
||||
{
|
||||
prefix: dirName,
|
||||
limit: NETDISK_HANDLE_MAX_ITEM,
|
||||
marker
|
||||
},
|
||||
(err, respBody, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
const moveOperations = respBody.items.map(item => {
|
||||
const { key } = item;
|
||||
const destKey = key.replace(dirName, toDirName);
|
||||
return qiniu.rs.moveOp(bucketName, key, bucketName, destKey, op);
|
||||
});
|
||||
this.bucketManager.batch(moveOperations, (err2, respBody2, respInfo2) => {
|
||||
if (err2) {
|
||||
reject(err2);
|
||||
return;
|
||||
}
|
||||
if (respInfo2.statusCode === 200) {
|
||||
if (isEmpty(respBody.marker)) hasFile = false;
|
||||
else marker = respBody.marker;
|
||||
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}`
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取七牛下载的文件url链接
|
||||
* @param key 文件路径
|
||||
* @returns 连接
|
||||
*/
|
||||
getDownloadLink(key: string): string {
|
||||
if (this.qiniuConfig.access === 'public') {
|
||||
return this.bucketManager.publicDownloadUrl(this.qiniuConfig.domain, key);
|
||||
} else if (this.qiniuConfig.access === 'private') {
|
||||
return this.bucketManager.privateDownloadUrl(
|
||||
this.qiniuConfig.domain,
|
||||
key,
|
||||
Date.now() / 1000 + 36000
|
||||
);
|
||||
}
|
||||
throw new Error('qiniu config access type not support');
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param dir 删除的文件夹目录
|
||||
* @param name 文件名
|
||||
*/
|
||||
async deleteFile(dir: string, name: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.bucketManager.delete(
|
||||
this.qiniuConfig.bucket,
|
||||
`${dir}${name}`,
|
||||
(err, respBody, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件夹
|
||||
* @param dir 文件夹所在的上级目录
|
||||
* @param name 文件目录名称
|
||||
*/
|
||||
async deleteMultiFileOrDir(fileList: FileOpItem[], dir: string): Promise<void> {
|
||||
const files = fileList.filter(item => item.type === 'file');
|
||||
if (files.length > 0) {
|
||||
// 批处理文件
|
||||
const copyOperations = files.map(item => {
|
||||
const fileName = `${dir}${item.name}`;
|
||||
return qiniu.rs.deleteOp(this.qiniuConfig.bucket, fileName);
|
||||
});
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
this.bucketManager.batch(copyOperations, (err, respBody, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
resolve();
|
||||
} else if (respInfo.statusCode === 298) {
|
||||
reject(new Error('操作异常,但部分文件夹删除成功'));
|
||||
} else {
|
||||
reject(
|
||||
new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// 处理文件夹
|
||||
const dirs = fileList.filter(item => item.type === 'dir');
|
||||
if (dirs.length > 0) {
|
||||
// 处理文件夹的复制
|
||||
for (let i = 0; i < dirs.length; i++) {
|
||||
const dirName = `${dir}${dirs[i].name}/`;
|
||||
let hasFile = true;
|
||||
let marker = '';
|
||||
while (hasFile) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
// 列举当前目录下的所有文件
|
||||
this.bucketManager.listPrefix(
|
||||
this.qiniuConfig.bucket,
|
||||
{
|
||||
prefix: dirName,
|
||||
limit: NETDISK_HANDLE_MAX_ITEM,
|
||||
marker
|
||||
},
|
||||
(err, respBody, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
const moveOperations = respBody.items.map(item => {
|
||||
const { key } = item;
|
||||
return qiniu.rs.deleteOp(this.qiniuConfig.bucket, key);
|
||||
});
|
||||
this.bucketManager.batch(moveOperations, (err2, respBody2, respInfo2) => {
|
||||
if (err2) {
|
||||
reject(err2);
|
||||
return;
|
||||
}
|
||||
if (respInfo2.statusCode === 200) {
|
||||
if (isEmpty(respBody.marker)) hasFile = false;
|
||||
else marker = respBody.marker;
|
||||
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}`
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 复制文件,含文件夹
|
||||
*/
|
||||
async copyMultiFileOrDir(fileList: FileOpItem[], dir: string, toDir: string): Promise<void> {
|
||||
const files = fileList.filter(item => item.type === 'file');
|
||||
const op = {
|
||||
force: true
|
||||
};
|
||||
if (files.length > 0) {
|
||||
// 批处理文件
|
||||
const copyOperations = files.map(item => {
|
||||
const fileName = `${dir}${item.name}`;
|
||||
// 拼接文件名
|
||||
const ext = extname(item.name);
|
||||
const bn = basename(item.name, ext);
|
||||
const toFileName = `${toDir}${bn}${NETDISK_COPY_SUFFIX}${ext}`;
|
||||
return qiniu.rs.copyOp(
|
||||
this.qiniuConfig.bucket,
|
||||
fileName,
|
||||
this.qiniuConfig.bucket,
|
||||
toFileName,
|
||||
op
|
||||
);
|
||||
});
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
this.bucketManager.batch(copyOperations, (err, respBody, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
resolve();
|
||||
} else if (respInfo.statusCode === 298) {
|
||||
reject(new Error('操作异常,但部分文件夹删除成功'));
|
||||
} else {
|
||||
reject(
|
||||
new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// 处理文件夹
|
||||
const dirs = fileList.filter(item => item.type === 'dir');
|
||||
if (dirs.length > 0) {
|
||||
// 处理文件夹的复制
|
||||
for (let i = 0; i < dirs.length; i++) {
|
||||
const dirName = `${dir}${dirs[i].name}/`;
|
||||
const copyDirName = `${toDir}${dirs[i].name}${NETDISK_COPY_SUFFIX}/`;
|
||||
let hasFile = true;
|
||||
let marker = '';
|
||||
while (hasFile) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
// 列举当前目录下的所有文件
|
||||
this.bucketManager.listPrefix(
|
||||
this.qiniuConfig.bucket,
|
||||
{
|
||||
prefix: dirName,
|
||||
limit: NETDISK_HANDLE_MAX_ITEM,
|
||||
marker
|
||||
},
|
||||
(err, respBody, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
const moveOperations = respBody.items.map(item => {
|
||||
const { key } = item;
|
||||
const destKey = key.replace(dirName, copyDirName);
|
||||
return qiniu.rs.copyOp(
|
||||
this.qiniuConfig.bucket,
|
||||
key,
|
||||
this.qiniuConfig.bucket,
|
||||
destKey,
|
||||
op
|
||||
);
|
||||
});
|
||||
this.bucketManager.batch(moveOperations, (err2, respBody2, respInfo2) => {
|
||||
if (err2) {
|
||||
reject(err2);
|
||||
return;
|
||||
}
|
||||
if (respInfo2.statusCode === 200) {
|
||||
if (isEmpty(respBody.marker)) hasFile = false;
|
||||
else marker = respBody.marker;
|
||||
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}`
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移动文件,含文件夹
|
||||
*/
|
||||
async moveMultiFileOrDir(fileList: FileOpItem[], dir: string, toDir: string): Promise<void> {
|
||||
const files = fileList.filter(item => item.type === 'file');
|
||||
const op = {
|
||||
force: true
|
||||
};
|
||||
if (files.length > 0) {
|
||||
// 批处理文件
|
||||
const copyOperations = files.map(item => {
|
||||
const fileName = `${dir}${item.name}`;
|
||||
const toFileName = `${toDir}${item.name}`;
|
||||
return qiniu.rs.moveOp(
|
||||
this.qiniuConfig.bucket,
|
||||
fileName,
|
||||
this.qiniuConfig.bucket,
|
||||
toFileName,
|
||||
op
|
||||
);
|
||||
});
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
this.bucketManager.batch(copyOperations, (err, respBody, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
resolve();
|
||||
} else if (respInfo.statusCode === 298) {
|
||||
reject(new Error('操作异常,但部分文件夹删除成功'));
|
||||
} else {
|
||||
reject(
|
||||
new Error(`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`)
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
// 处理文件夹
|
||||
const dirs = fileList.filter(item => item.type === 'dir');
|
||||
if (dirs.length > 0) {
|
||||
// 处理文件夹的复制
|
||||
for (let i = 0; i < dirs.length; i++) {
|
||||
const dirName = `${dir}${dirs[i].name}/`;
|
||||
const toDirName = `${toDir}${dirs[i].name}/`;
|
||||
// 移动的目录不是是自己
|
||||
if (toDirName.startsWith(dirName)) continue;
|
||||
|
||||
let hasFile = true;
|
||||
let marker = '';
|
||||
while (hasFile) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
// 列举当前目录下的所有文件
|
||||
this.bucketManager.listPrefix(
|
||||
this.qiniuConfig.bucket,
|
||||
{
|
||||
prefix: dirName,
|
||||
limit: NETDISK_HANDLE_MAX_ITEM,
|
||||
marker
|
||||
},
|
||||
(err, respBody, respInfo) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
if (respInfo.statusCode === 200) {
|
||||
const moveOperations = respBody.items.map(item => {
|
||||
const { key } = item;
|
||||
const destKey = key.replace(dirName, toDirName);
|
||||
return qiniu.rs.moveOp(
|
||||
this.qiniuConfig.bucket,
|
||||
key,
|
||||
this.qiniuConfig.bucket,
|
||||
destKey,
|
||||
op
|
||||
);
|
||||
});
|
||||
this.bucketManager.batch(moveOperations, (err2, respBody2, respInfo2) => {
|
||||
if (err2) {
|
||||
reject(err2);
|
||||
return;
|
||||
}
|
||||
if (respInfo2.statusCode === 200) {
|
||||
if (isEmpty(respBody.marker)) hasFile = false;
|
||||
else marker = respBody.marker;
|
||||
|
||||
resolve();
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Qiniu Error Code: ${respInfo2.statusCode}, Info: ${respInfo2.statusMessage}`
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
reject(
|
||||
new Error(
|
||||
`Qiniu Error Code: ${respInfo.statusCode}, Info: ${respInfo.statusMessage}`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -51,7 +51,7 @@ export class SFileInfoDetail {
|
|||
@ApiProperty({
|
||||
description: '文件存储类型,2 表示归档存储,1 表示低频存储,0表示普通存储。'
|
||||
})
|
||||
type: number;
|
||||
type?: number;
|
||||
|
||||
@ApiProperty({ description: '文件上传时间', type: Date })
|
||||
putTime: Date;
|
||||
|
@ -60,7 +60,7 @@ export class SFileInfoDetail {
|
|||
md5: string;
|
||||
|
||||
@ApiProperty({ description: '上传人' })
|
||||
uploader: string;
|
||||
uploader?: string;
|
||||
|
||||
@ApiProperty({ description: '文件备注' })
|
||||
mark?: string;
|
||||
|
|
|
@ -49,27 +49,27 @@ export class NetDiskManageController {
|
|||
return await this.manageService.getFileList(dto.path, dto.marker, dto.key);
|
||||
}
|
||||
|
||||
@Post('mkdir')
|
||||
@ApiOperation({ summary: '创建文件夹,支持多级' })
|
||||
@Perm(permissions.MKDIR)
|
||||
async mkdir(@Body() dto: MKDirDto): Promise<void> {
|
||||
const result = await this.manageService.checkFileExist(`${dto.path}${dto.dirName}/`);
|
||||
if (result) throw new BusinessException(ErrorEnum.OSS_FILE_OR_DIR_EXIST);
|
||||
// @Post('mkdir')
|
||||
// @ApiOperation({ summary: '创建文件夹,支持多级' })
|
||||
// @Perm(permissions.MKDIR)
|
||||
// async mkdir(@Body() dto: MKDirDto): Promise<void> {
|
||||
// const result = await this.manageService.checkFileExist(`${dto.path}${dto.dirName}/`);
|
||||
// if (result) throw new BusinessException(ErrorEnum.OSS_FILE_OR_DIR_EXIST);
|
||||
|
||||
await this.manageService.createDir(`${dto.path}${dto.dirName}`);
|
||||
}
|
||||
// await this.manageService.createDir(`${dto.path}${dto.dirName}`);
|
||||
// }
|
||||
|
||||
@Get('token')
|
||||
@ApiOperation({ summary: '获取上传Token,无Token前端无法上传' })
|
||||
@ApiOkResponse({ type: UploadToken })
|
||||
@Perm(permissions.TOKEN)
|
||||
async token(@AuthUser() user: IAuthUser): Promise<UploadToken> {
|
||||
checkIsDemoMode();
|
||||
// @Get('token')
|
||||
// @ApiOperation({ summary: '获取上传Token,无Token前端无法上传' })
|
||||
// @ApiOkResponse({ type: UploadToken })
|
||||
// @Perm(permissions.TOKEN)
|
||||
// async token(@AuthUser() user: IAuthUser): Promise<UploadToken> {
|
||||
// checkIsDemoMode();
|
||||
|
||||
return {
|
||||
token: this.manageService.createUploadToken(`${user.uid}`)
|
||||
};
|
||||
}
|
||||
// return {
|
||||
// token: this.manageService.createUploadToken(`${user.uid}`)
|
||||
// };
|
||||
// }
|
||||
|
||||
@Get('info')
|
||||
@ApiOperation({ summary: '获取文件详细信息' })
|
||||
|
@ -79,14 +79,14 @@ export class NetDiskManageController {
|
|||
return await this.manageService.getFileInfo(dto.name, dto.path);
|
||||
}
|
||||
|
||||
@Post('mark')
|
||||
@ApiOperation({ summary: '添加文件备注' })
|
||||
@Perm(permissions.MARK)
|
||||
async mark(@Body() dto: MarkFileDto): Promise<void> {
|
||||
await this.manageService.changeFileHeaders(dto.name, dto.path, {
|
||||
mark: dto.mark
|
||||
});
|
||||
}
|
||||
// @Post('mark')
|
||||
// @ApiOperation({ summary: '添加文件备注' })
|
||||
// @Perm(permissions.MARK)
|
||||
// async mark(@Body() dto: MarkFileDto): Promise<void> {
|
||||
// await this.manageService.changeFileHeaders(dto.name, dto.path, {
|
||||
// mark: dto.mark
|
||||
// });
|
||||
// }
|
||||
|
||||
@Get('download')
|
||||
@ApiOperation({ summary: '获取下载链接,不支持下载文件夹' })
|
||||
|
@ -96,40 +96,40 @@ export class NetDiskManageController {
|
|||
return this.manageService.getDownloadLink(`${dto.path}${dto.name}`);
|
||||
}
|
||||
|
||||
@Post('rename')
|
||||
@ApiOperation({ summary: '重命名文件或文件夹' })
|
||||
@Perm(permissions.RENAME)
|
||||
async rename(@Body() dto: RenameDto): Promise<void> {
|
||||
const result = await this.manageService.checkFileExist(
|
||||
`${dto.path}${dto.toName}${dto.type === 'dir' ? '/' : ''}`
|
||||
);
|
||||
if (result) throw new BusinessException(ErrorEnum.OSS_FILE_OR_DIR_EXIST);
|
||||
// @Post('rename')
|
||||
// @ApiOperation({ summary: '重命名文件或文件夹' })
|
||||
// @Perm(permissions.RENAME)
|
||||
// async rename(@Body() dto: RenameDto): Promise<void> {
|
||||
// const result = await this.manageService.checkFileExist(
|
||||
// `${dto.path}${dto.toName}${dto.type === 'dir' ? '/' : ''}`
|
||||
// );
|
||||
// if (result) throw new BusinessException(ErrorEnum.OSS_FILE_OR_DIR_EXIST);
|
||||
|
||||
if (dto.type === 'file') await this.manageService.renameFile(dto.path, dto.name, dto.toName);
|
||||
else await this.manageService.renameDir(dto.path, dto.name, dto.toName);
|
||||
}
|
||||
// if (dto.type === 'file') await this.manageService.renameFile(dto.path, dto.name, dto.toName);
|
||||
// else await this.manageService.renameDir(dto.path, dto.name, dto.toName);
|
||||
// }
|
||||
|
||||
@Post('delete')
|
||||
@ApiOperation({ summary: '删除文件或文件夹' })
|
||||
@Perm(permissions.DELETE)
|
||||
async delete(@Body() dto: DeleteDto): Promise<void> {
|
||||
await this.manageService.deleteMultiFileOrDir(dto.files, dto.path);
|
||||
}
|
||||
// @Post('delete')
|
||||
// @ApiOperation({ summary: '删除文件或文件夹' })
|
||||
// @Perm(permissions.DELETE)
|
||||
// async delete(@Body() dto: DeleteDto): Promise<void> {
|
||||
// await this.manageService.deleteMultiFileOrDir(dto.files, dto.path);
|
||||
// }
|
||||
|
||||
@Post('cut')
|
||||
@ApiOperation({ summary: '剪切文件或文件夹,支持批量' })
|
||||
@Perm(permissions.CUT)
|
||||
async cut(@Body() dto: FileOpDto): Promise<void> {
|
||||
if (dto.originPath === dto.toPath)
|
||||
throw new BusinessException(ErrorEnum.OSS_NO_OPERATION_REQUIRED);
|
||||
// @Post('cut')
|
||||
// @ApiOperation({ summary: '剪切文件或文件夹,支持批量' })
|
||||
// @Perm(permissions.CUT)
|
||||
// async cut(@Body() dto: FileOpDto): Promise<void> {
|
||||
// if (dto.originPath === dto.toPath)
|
||||
// throw new BusinessException(ErrorEnum.OSS_NO_OPERATION_REQUIRED);
|
||||
|
||||
await this.manageService.moveMultiFileOrDir(dto.files, dto.originPath, dto.toPath);
|
||||
}
|
||||
// await this.manageService.moveMultiFileOrDir(dto.files, dto.originPath, dto.toPath);
|
||||
// }
|
||||
|
||||
@Post('copy')
|
||||
@ApiOperation({ summary: '复制文件或文件夹,支持批量' })
|
||||
@Perm(permissions.COPY)
|
||||
async copy(@Body() dto: FileOpDto): Promise<void> {
|
||||
await this.manageService.copyMultiFileOrDir(dto.files, dto.originPath, dto.toPath);
|
||||
}
|
||||
// @Post('copy')
|
||||
// @ApiOperation({ summary: '复制文件或文件夹,支持批量' })
|
||||
// @Perm(permissions.COPY)
|
||||
// async copy(@Body() dto: FileOpDto): Promise<void> {
|
||||
// await this.manageService.copyMultiFileOrDir(dto.files, dto.originPath, dto.toPath);
|
||||
// }
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,8 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import * as Minio from 'minio';
|
||||
import { ConfigKeyPaths } from '~/config';
|
||||
@Injectable()
|
||||
export class MinioService {
|
||||
|
||||
}
|
|
@ -5,9 +5,10 @@ import { RouterModule } from '@nestjs/core';
|
|||
import { UserModule } from '../user/user.module';
|
||||
|
||||
import { NetDiskManageController } from './manager/manage.controller';
|
||||
import { NetDiskManageService } from './manager/manage.service';
|
||||
import { NetDiskOverviewController } from './overview/overview.controller';
|
||||
import { NetDiskOverviewService } from './overview/overview.service';
|
||||
import { MinioService } from './minio/minio.service';
|
||||
import { NetDiskManageService } from './manager/manage.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
|
@ -20,6 +21,6 @@ import { NetDiskOverviewService } from './overview/overview.service';
|
|||
])
|
||||
],
|
||||
controllers: [NetDiskManageController, NetDiskOverviewController],
|
||||
providers: [NetDiskManageService, NetDiskOverviewService]
|
||||
providers: [NetDiskManageService, NetDiskOverviewService, MinioService]
|
||||
})
|
||||
export class NetdiskModule {}
|
||||
|
|
Loading…
Reference in New Issue