feat: minio功能开发

This commit is contained in:
louis 2024-03-08 16:24:29 +08:00
parent 46a4132877
commit 3610d8fdc6
13 changed files with 1853 additions and 845 deletions

15
.env
View File

@ -11,13 +11,17 @@ LOGGER_MAX_FILES = 31
TZ = Asia/Shanghai TZ = Asia/Shanghai
# OSS(minio) # OSS(minio)
OSS_ACCESSKEY=xxx OSS_ACCESSKEY=8Zttvx4ZbF2ikFRb
OSS_SECRETKEY=xxx OSS_SECRETKEY=SCgOJEJXM5vMNQL4fF8opXA1wmpACRfw
OSS_DOMAIN=https://cdn.louis.site OSS_PORT=8021
OSS_BUCKET=hxoa 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_ZONE=Zone_z2 # Zone_as0 | Zone_na0 | Zone_z0 | Zone_z1 | Zone_z2
OSS_ACCESS_TYPE=public # or private OSS_ACCESS_TYPE=public # or private
DB_HOST = host.docker.internal DB_HOST = host.docker.internal
DB_PORT = 13307 DB_PORT = 13307
DB_DATABASE = hxoa DB_DATABASE = hxoa
@ -29,4 +33,5 @@ DB_LOGGING = ["error"]
REDIS_PORT = 6379 REDIS_PORT = 6379
REDIS_HOST = host.docker.internal REDIS_HOST = host.docker.internal
REDIS_PASSWORD = 123456 REDIS_PASSWORD = 123456
REDIS_DB = 0 REDIS_DB = 0

View File

@ -33,4 +33,3 @@ SMTP_PORT = 465
SMTP_USER = nest_admin@163.com SMTP_USER = nest_admin@163.com
SMTP_PASS = VIPLLOIPMETTROYU SMTP_PASS = VIPLLOIPMETTROYU

View File

@ -34,4 +34,5 @@ SMTP_USER = nest_admin@163.com
SMTP_PASS = VIPLLOIPMETTROYU SMTP_PASS = VIPLLOIPMETTROYU
# 是否为演示模式(在演示模式下,会拦截除 GET 方法以外的所有请求) # 是否为演示模式(在演示模式下,会拦截除 GET 方法以外的所有请求)
IS_DEMO = false IS_DEMO = false

14
minio.js Normal file
View File

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

View File

@ -88,6 +88,7 @@
"helmet": "^7.1.0", "helmet": "^7.1.0",
"ioredis": "^5.3.2", "ioredis": "^5.3.2",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"minio": "^7.1.3",
"mysql2": "^3.9.1", "mysql2": "^3.9.1",
"nanoid": "^3.3.7", "nanoid": "^3.3.7",
"nodemailer": "^6.9.9", "nodemailer": "^6.9.9",

View File

@ -131,6 +131,9 @@ dependencies:
lodash: lodash:
specifier: ^4.17.21 specifier: ^4.17.21
version: 4.17.21 version: 4.17.21
minio:
specifier: ^7.1.3
version: 7.1.3
mysql2: mysql2:
specifier: ^3.9.1 specifier: ^3.9.1
version: 3.9.1 version: 3.9.1
@ -3663,6 +3666,12 @@ packages:
resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==}
dev: true 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: /JSONStream@1.3.5:
resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==} resolution: {integrity: sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==}
hasBin: true hasBin: true
@ -4061,6 +4070,13 @@ packages:
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
dev: false 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: /avvio@8.3.0:
resolution: {integrity: sha512-VBVH0jubFr9LdFASy/vNtm5giTrnbVquWBhT0fyizuNK2rQ7e7ONU2plZQWUNqtE1EmxFEb+kbSkFRkstiaS9Q==} resolution: {integrity: sha512-VBVH0jubFr9LdFASy/vNtm5giTrnbVquWBhT0fyizuNK2rQ7e7ONU2plZQWUNqtE1EmxFEb+kbSkFRkstiaS9Q==}
dependencies: dependencies:
@ -4348,6 +4364,10 @@ packages:
base64-js: 1.5.1 base64-js: 1.5.1
dev: true dev: true
/browser-or-node@2.1.1:
resolution: {integrity: sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==}
dev: false
/browser-resolve@1.11.3: /browser-resolve@1.11.3:
resolution: {integrity: sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==} resolution: {integrity: sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==}
dependencies: dependencies:
@ -5409,6 +5429,11 @@ packages:
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true dev: true
/decode-uri-component@0.2.2:
resolution: {integrity: sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==}
engines: {node: '>=0.10'}
dev: false
/dedent@0.7.0: /dedent@0.7.0:
resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==}
dev: true dev: true
@ -6340,6 +6365,13 @@ packages:
resolution: {integrity: sha512-eel5UKGn369gGEWOqBShmFJWfq/xSJvsgDzgLYC845GneayWvXBf0lJCBn5qTABfewy1ZDPoaR5OZCP+kssfuw==} resolution: {integrity: sha512-eel5UKGn369gGEWOqBShmFJWfq/xSJvsgDzgLYC845GneayWvXBf0lJCBn5qTABfewy1ZDPoaR5OZCP+kssfuw==}
dev: false 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: /fastify-plugin@4.5.1:
resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==} resolution: {integrity: sha512-stRHYGeuqpEZTL1Ef0Ovr2ltazUT9g844X5z/zEBFLG8RYlpDiOCIG+ATvYEp+/zmc7sN29mcIMp8gvYplYPIQ==}
dev: false dev: false
@ -6438,6 +6470,11 @@ packages:
dependencies: dependencies:
to-regex-range: 5.0.1 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: /finalhandler@1.1.2:
resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==} resolution: {integrity: sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==}
engines: {node: '>= 0.8'} engines: {node: '>= 0.8'}
@ -6559,6 +6596,12 @@ packages:
optional: true optional: true
dev: false 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: /foreground-child@3.1.1:
resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
engines: {node: '>=14'} engines: {node: '>=14'}
@ -7359,13 +7402,17 @@ packages:
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
dev: false dev: false
/ipaddr.js@2.1.0:
resolution: {integrity: sha512-LlbxQ7xKzfBusov6UMi4MFpEg0m+mAm9xyNGEduwXMEDuf4WfzB/RZwMVYEd7IKGvh4IUkEXYxtAVu9T3OelJQ==}
engines: {node: '>= 10'}
dev: false
/is-arguments@1.1.1: /is-arguments@1.1.1:
resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
dependencies: dependencies:
call-bind: 1.0.7 call-bind: 1.0.7
has-tostringtag: 1.0.2 has-tostringtag: 1.0.2
dev: true
/is-arrayish@0.2.1: /is-arrayish@0.2.1:
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
@ -7380,6 +7427,11 @@ packages:
dependencies: dependencies:
binary-extensions: 2.2.0 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: /is-core-module@2.13.1:
resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==}
dependencies: dependencies:
@ -7428,6 +7480,13 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true 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: /is-glob@4.0.3:
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -7490,6 +7549,13 @@ packages:
text-extensions: 1.9.0 text-extensions: 1.9.0
dev: true 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: /is-unicode-supported@0.1.0:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -8104,6 +8170,10 @@ packages:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
dev: true dev: true
/json-stream@1.0.0:
resolution: {integrity: sha512-H/ZGY0nIAg3QcOwE1QN/rK/Fa7gJn7Ii5obwp6zyPO4xiPNwpIMjqy2gwjBEGqzkF/vSWEIBQCBuN19hYiL6Qg==}
dev: false
/json-stringify-safe@5.0.1: /json-stringify-safe@5.0.1:
resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==} resolution: {integrity: sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==}
dev: true dev: true
@ -8797,6 +8867,26 @@ packages:
/minimist@1.2.8: /minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} 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: /minipass@3.3.6:
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -10056,6 +10146,11 @@ packages:
resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==} resolution: {integrity: sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==}
dev: true 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: /prelude-ls@1.1.2:
resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==}
engines: {node: '>= 0.8.0'} engines: {node: '>= 0.8.0'}
@ -10328,6 +10423,16 @@ packages:
dependencies: dependencies:
side-channel: 1.0.5 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: /queue-microtask@1.2.3:
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
dev: true dev: true
@ -10723,7 +10828,6 @@ packages:
/sax@1.3.0: /sax@1.3.0:
resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==} resolution: {integrity: sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==}
dev: true
/saxes@5.0.1: /saxes@5.0.1:
resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==} resolution: {integrity: sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==}
@ -11060,6 +11164,11 @@ packages:
resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==} resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==}
dev: true dev: true
/split-on-first@1.1.0:
resolution: {integrity: sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==}
engines: {node: '>=6'}
dev: false
/split2@3.2.2: /split2@3.2.2:
resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==} resolution: {integrity: sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==}
dependencies: dependencies:
@ -11192,6 +11301,11 @@ packages:
engines: {node: '>=4.0.0'} engines: {node: '>=4.0.0'}
dev: false 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: /string-length@4.0.2:
resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -11302,6 +11416,10 @@ packages:
engines: {node: '>=8'} engines: {node: '>=8'}
dev: true dev: true
/strnum@1.0.5:
resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==}
dev: false
/superagent@8.1.2: /superagent@8.1.2:
resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==} resolution: {integrity: sha512-6WTxW1EB6yCxV5VFOIPQruWGHqc3yI7hEmZK6h+pyk69Lk/Ut7rLUY6W/ONF2MjBuGjvmMiIpsrVJ2vjrHlslA==}
engines: {node: '>=6.4.0 <13 || >=14'} engines: {node: '>=6.4.0 <13 || >=14'}
@ -11513,7 +11631,6 @@ packages:
resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==} resolution: {integrity: sha512-iOqSav00cVxEEICeD7TjLB1sueEL+81Wpzp2bY17uZjZN0pWZPuo4suZ/61VujxmqSGFfgOcNuTZ85QJwNZQpw==}
dependencies: dependencies:
readable-stream: 3.6.2 readable-stream: 3.6.2
dev: true
/through@2.3.8: /through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
@ -12026,6 +12143,16 @@ packages:
/util-deprecate@1.0.2: /util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} 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: /utility@1.18.0:
resolution: {integrity: sha512-PYxZDA+6QtvRvm//++aGdmKG/cI07jNwbROz0Ql+VzFV1+Z0Dy55NI4zZ7RHc9KKpBePNFwoErqIuqQv/cjiTA==} resolution: {integrity: sha512-PYxZDA+6QtvRvm//++aGdmKG/cI07jNwbROz0Ql+VzFV1+Z0Dy55NI4zZ7RHc9KKpBePNFwoErqIuqQv/cjiTA==}
engines: {node: '>= 0.12.0'} engines: {node: '>= 0.12.0'}
@ -12118,6 +12245,14 @@ packages:
defaults: 1.0.4 defaults: 1.0.4
dev: true 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: /web-resource-inliner@6.0.1:
resolution: {integrity: sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==} resolution: {integrity: sha512-kfqDxt5dTB1JhqsCUQVFDj0rmY+4HLwGQIsLPbyrsN9y9WV/1oFDSx3BQ4GfCv9X+jVeQ7rouTqwK53rA/7t8A==}
engines: {node: '>=10.0.0'} engines: {node: '>=10.0.0'}
@ -12205,6 +12340,17 @@ packages:
tr46: 0.0.3 tr46: 0.0.3
webidl-conversions: 3.0.1 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: /which@1.3.1:
resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==}
hasBin: true hasBin: true
@ -12353,6 +12499,23 @@ packages:
utf-8-validate: utf-8-validate:
optional: true 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: /xmlchars@2.2.0:
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
dev: false dev: false

View File

@ -1,7 +1,7 @@
import { ConfigType, registerAs } from '@nestjs/config'; import { ConfigType, registerAs } from '@nestjs/config';
import * as qiniu from 'qiniu'; import * as qiniu from 'qiniu';
import { env } from '~/global/env'; import { env, envBoolean, envNumber } from '~/global/env';
function parseZone(zone: string) { function parseZone(zone: string) {
switch (zone) { switch (zone) {
@ -24,6 +24,8 @@ export const OssConfig = registerAs(ossRegToken, () => ({
accessKey: env('OSS_ACCESSKEY'), accessKey: env('OSS_ACCESSKEY'),
secretKey: env('OSS_SECRETKEY'), secretKey: env('OSS_SECRETKEY'),
domain: env('OSS_DOMAIN'), domain: env('OSS_DOMAIN'),
port: envNumber('OSS_PORT'),
useSSL: envBoolean('OSS_USE_SSL'),
bucket: env('OSS_BUCKET'), bucket: env('OSS_BUCKET'),
zone: parseZone(env('OSS_ZONE') || 'Zone_z2'), zone: parseZone(env('OSS_ZONE') || 'Zone_z2'),
access: (env('OSS_ACCESS_TYPE') as any) || 'public' access: (env('OSS_ACCESS_TYPE') as any) || 'public'

View File

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

View File

@ -51,7 +51,7 @@ export class SFileInfoDetail {
@ApiProperty({ @ApiProperty({
description: '文件存储类型2 表示归档存储1 表示低频存储0表示普通存储。' description: '文件存储类型2 表示归档存储1 表示低频存储0表示普通存储。'
}) })
type: number; type?: number;
@ApiProperty({ description: '文件上传时间', type: Date }) @ApiProperty({ description: '文件上传时间', type: Date })
putTime: Date; putTime: Date;
@ -60,7 +60,7 @@ export class SFileInfoDetail {
md5: string; md5: string;
@ApiProperty({ description: '上传人' }) @ApiProperty({ description: '上传人' })
uploader: string; uploader?: string;
@ApiProperty({ description: '文件备注' }) @ApiProperty({ description: '文件备注' })
mark?: string; mark?: string;

View File

@ -49,27 +49,27 @@ export class NetDiskManageController {
return await this.manageService.getFileList(dto.path, dto.marker, dto.key); return await this.manageService.getFileList(dto.path, dto.marker, dto.key);
} }
@Post('mkdir') // @Post('mkdir')
@ApiOperation({ summary: '创建文件夹,支持多级' }) // @ApiOperation({ summary: '创建文件夹,支持多级' })
@Perm(permissions.MKDIR) // @Perm(permissions.MKDIR)
async mkdir(@Body() dto: MKDirDto): Promise<void> { // async mkdir(@Body() dto: MKDirDto): Promise<void> {
const result = await this.manageService.checkFileExist(`${dto.path}${dto.dirName}/`); // const result = await this.manageService.checkFileExist(`${dto.path}${dto.dirName}/`);
if (result) throw new BusinessException(ErrorEnum.OSS_FILE_OR_DIR_EXIST); // 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') // @Get('token')
@ApiOperation({ summary: '获取上传Token无Token前端无法上传' }) // @ApiOperation({ summary: '获取上传Token无Token前端无法上传' })
@ApiOkResponse({ type: UploadToken }) // @ApiOkResponse({ type: UploadToken })
@Perm(permissions.TOKEN) // @Perm(permissions.TOKEN)
async token(@AuthUser() user: IAuthUser): Promise<UploadToken> { // async token(@AuthUser() user: IAuthUser): Promise<UploadToken> {
checkIsDemoMode(); // checkIsDemoMode();
return { // return {
token: this.manageService.createUploadToken(`${user.uid}`) // token: this.manageService.createUploadToken(`${user.uid}`)
}; // };
} // }
@Get('info') @Get('info')
@ApiOperation({ summary: '获取文件详细信息' }) @ApiOperation({ summary: '获取文件详细信息' })
@ -79,14 +79,14 @@ export class NetDiskManageController {
return await this.manageService.getFileInfo(dto.name, dto.path); return await this.manageService.getFileInfo(dto.name, dto.path);
} }
@Post('mark') // @Post('mark')
@ApiOperation({ summary: '添加文件备注' }) // @ApiOperation({ summary: '添加文件备注' })
@Perm(permissions.MARK) // @Perm(permissions.MARK)
async mark(@Body() dto: MarkFileDto): Promise<void> { // async mark(@Body() dto: MarkFileDto): Promise<void> {
await this.manageService.changeFileHeaders(dto.name, dto.path, { // await this.manageService.changeFileHeaders(dto.name, dto.path, {
mark: dto.mark // mark: dto.mark
}); // });
} // }
@Get('download') @Get('download')
@ApiOperation({ summary: '获取下载链接,不支持下载文件夹' }) @ApiOperation({ summary: '获取下载链接,不支持下载文件夹' })
@ -96,40 +96,40 @@ export class NetDiskManageController {
return this.manageService.getDownloadLink(`${dto.path}${dto.name}`); return this.manageService.getDownloadLink(`${dto.path}${dto.name}`);
} }
@Post('rename') // @Post('rename')
@ApiOperation({ summary: '重命名文件或文件夹' }) // @ApiOperation({ summary: '重命名文件或文件夹' })
@Perm(permissions.RENAME) // @Perm(permissions.RENAME)
async rename(@Body() dto: RenameDto): Promise<void> { // async rename(@Body() dto: RenameDto): Promise<void> {
const result = await this.manageService.checkFileExist( // const result = await this.manageService.checkFileExist(
`${dto.path}${dto.toName}${dto.type === 'dir' ? '/' : ''}` // `${dto.path}${dto.toName}${dto.type === 'dir' ? '/' : ''}`
); // );
if (result) throw new BusinessException(ErrorEnum.OSS_FILE_OR_DIR_EXIST); // 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); // 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); // else await this.manageService.renameDir(dto.path, dto.name, dto.toName);
} // }
@Post('delete') // @Post('delete')
@ApiOperation({ summary: '删除文件或文件夹' }) // @ApiOperation({ summary: '删除文件或文件夹' })
@Perm(permissions.DELETE) // @Perm(permissions.DELETE)
async delete(@Body() dto: DeleteDto): Promise<void> { // async delete(@Body() dto: DeleteDto): Promise<void> {
await this.manageService.deleteMultiFileOrDir(dto.files, dto.path); // await this.manageService.deleteMultiFileOrDir(dto.files, dto.path);
} // }
@Post('cut') // @Post('cut')
@ApiOperation({ summary: '剪切文件或文件夹,支持批量' }) // @ApiOperation({ summary: '剪切文件或文件夹,支持批量' })
@Perm(permissions.CUT) // @Perm(permissions.CUT)
async cut(@Body() dto: FileOpDto): Promise<void> { // async cut(@Body() dto: FileOpDto): Promise<void> {
if (dto.originPath === dto.toPath) // if (dto.originPath === dto.toPath)
throw new BusinessException(ErrorEnum.OSS_NO_OPERATION_REQUIRED); // 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') // @Post('copy')
@ApiOperation({ summary: '复制文件或文件夹,支持批量' }) // @ApiOperation({ summary: '复制文件或文件夹,支持批量' })
@Perm(permissions.COPY) // @Perm(permissions.COPY)
async copy(@Body() dto: FileOpDto): Promise<void> { // async copy(@Body() dto: FileOpDto): Promise<void> {
await this.manageService.copyMultiFileOrDir(dto.files, dto.originPath, dto.toPath); // await this.manageService.copyMultiFileOrDir(dto.files, dto.originPath, dto.toPath);
} // }
} }

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -5,9 +5,10 @@ import { RouterModule } from '@nestjs/core';
import { UserModule } from '../user/user.module'; import { UserModule } from '../user/user.module';
import { NetDiskManageController } from './manager/manage.controller'; import { NetDiskManageController } from './manager/manage.controller';
import { NetDiskManageService } from './manager/manage.service';
import { NetDiskOverviewController } from './overview/overview.controller'; import { NetDiskOverviewController } from './overview/overview.controller';
import { NetDiskOverviewService } from './overview/overview.service'; import { NetDiskOverviewService } from './overview/overview.service';
import { MinioService } from './minio/minio.service';
import { NetDiskManageService } from './manager/manage.service';
@Module({ @Module({
imports: [ imports: [
@ -20,6 +21,6 @@ import { NetDiskOverviewService } from './overview/overview.service';
]) ])
], ],
controllers: [NetDiskManageController, NetDiskOverviewController], controllers: [NetDiskManageController, NetDiskOverviewController],
providers: [NetDiskManageService, NetDiskOverviewService] providers: [NetDiskManageService, NetDiskOverviewService, MinioService]
}) })
export class NetdiskModule {} export class NetdiskModule {}