feat(file-manager): support tencent cos (#958)

* feat: support tencent cos

* feat: normalize

* chore: update env example

* test: add cos

* Revert "feat: normalize"

This reverts commit 371880013c20b4cb4174ffaa15e0157acc5306c3.

* feat: normalize

* feat: tengxun to tencent

* chore: fix missing deps

* fix: locale

* fix: locale cos
This commit is contained in:
Dunqing 2022-10-26 00:02:30 +08:00 committed by chenos
parent 3bc85dde2a
commit 8905e3c211
12 changed files with 775 additions and 565 deletions

View File

@ -52,6 +52,13 @@ ALI_OSS_ACCESS_KEY_ID=
ALI_OSS_ACCESS_KEY_SECRET=
ALI_OSS_BUCKET=
# Tencent COS STORAGE
TX_COS_STORAGE_BASE_URL=
TX_COS_REGION=ap-guangzhou
TX_COS_SECRET_ID=
TX_COS_SECRET_KEY=
TX_COS_BUCKET=
# AWS
AWS_ACCESS_KEY_ID=
AWS_SECRET_ACCESS_KEY=

View File

@ -413,6 +413,7 @@ export default {
"Use the built-in static file server": "Use the built-in static file server",
"Local storage": "Local storage",
"Aliyun OSS": "Aliyun OSS",
"Tencent COS": "Tencent COS",
"Amazon S3": "Amazon S3",
"Workflow": "Workflow",
"Execution History": "Execution History",

View File

@ -413,6 +413,7 @@ export default {
"Use the built-in static file server": "組み込みの静的ファイル サービスを使用する",
"Local storage": "ローカルストレージ",
"Aliyun OSS": "Aliyun OSS",
"Tencent COS": "Tencent COS",
"Amazon S3": "Amazon S3",
"Workflow": "ワークフロー",
"Execution History": "実行履歴",

View File

@ -413,6 +413,7 @@ export default {
"Use the built-in static file server": "Использовать встроенный статический файл-сервер",
"Local storage": "Локальное хранилище",
"Aliyun OSS": "Aliyun OSS",
"Tencent COS": "Tencent COS",
"Amazon S3": "Amazon S3",
"Workflow": "Workflow",
"Execution History": "История запусков",

View File

@ -502,6 +502,7 @@ export default {
'Local storage': '本地存储',
'Aliyun OSS': '阿里云 OSS',
'Amazon S3': '亚马逊 S3',
'Tencent COS': '腾讯云 COS',
'Region': '区域',
'Bucket': '存储桶',
'Path': '路径(相对)',

View File

@ -14,11 +14,13 @@
"@nocobase/server": "0.8.0-alpha.1",
"@nocobase/client": "0.8.0-alpha.1",
"aws-sdk": "^2.2.32",
"cos-nodejs-sdk-v5": "^2.11.14",
"koa-static": "^5.0.0",
"mime-match": "^1.0.2",
"mkdirp": "~0.5.4",
"multer": "^1.4.2",
"multer-aliyun-oss": "2.1.1",
"multer-cos": "^1.0.3",
"multer-s3": "^2.10.0"
},
"devDependencies": {

View File

@ -39,6 +39,7 @@ const collection = {
{ label: '{{t("Local storage")}}', value: 'local' },
{ label: '{{t("Aliyun OSS")}}', value: 'ali-oss' },
{ label: '{{t("Amazon S3")}}', value: 's3' },
{ label: '{{t("Tencent COS")}}', value: 'tx-cos' },
],
} as ISchema,
},

View File

@ -0,0 +1,69 @@
import path from 'path';
import { MockServer } from '@nocobase/test';
import txStorage from '../../storages/tx-cos';
import { FILE_FIELD_NAME } from '../../constants';
import { getApp, requestFile } from '..';
import { Database } from '@nocobase/database';
const itif = process.env.TX_COS_ACCESS_KEY_SECRET ? it : it.skip;
describe('storage:tx-cos', () => {
let app: MockServer;
let agent;
let db: Database;
let storage;
beforeEach(async () => {
app = await getApp();
agent = app.agent();
db = app.db;
const Storage = db.getCollection('storages').model;
storage = await Storage.create({
...txStorage.defaults(),
name: `tx-cos_${db.getTablePrefix()}`,
default: true,
path: 'test/path',
});
});
afterEach(async () => {
await db.close();
});
describe('direct attachment', () => {
itif('upload file should be ok', async () => {
const { body } = await agent.resource('attachments').upload({
[FILE_FIELD_NAME]: path.resolve(__dirname, '../files/text.txt'),
});
const Attachment = db.getCollection('attachments').model;
const attachment = await Attachment.findOne<any>({
where: { id: body.data.id },
include: ['storage'],
});
const matcher = {
title: 'text',
extname: '.txt',
path: 'test/path',
size: 13,
mimetype: 'text/plain',
meta: {},
storageId: storage.id,
};
// 文件上传和解析是否正常
expect(body.data).toMatchObject(matcher);
// 文件的 url 是否正常生成
expect(body.data.url).toBe(`${attachment.storage.baseUrl}/${body.data.path}/${body.data.filename}`);
// 文件的数据是否正常保存
expect(attachment).toMatchObject(matcher);
// 通过 url 是否能正确访问
const content = await requestFile(attachment.url, agent);
expect(content.text).toBe('Hello world!\n');
});
});
});

View File

@ -5,3 +5,4 @@ export const LIMIT_MAX_FILE_SIZE = 1024 * 1024 * 1024;
export const STORAGE_TYPE_LOCAL = 'local';
export const STORAGE_TYPE_ALI_OSS = 'ali-oss';
export const STORAGE_TYPE_S3 = 's3';
export const STORAGE_TYPE_TX_COS = 'tx-cos';

View File

@ -1,8 +1,9 @@
import local from './local';
import oss from './ali-oss';
import s3 from './s3';
import cos from './tx-cos';
import { STORAGE_TYPE_LOCAL, STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_S3 } from '../constants';
import { STORAGE_TYPE_LOCAL, STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS } from '../constants';
export interface IStorage {
filenameKey?: string;
@ -16,6 +17,7 @@ const map = new Map<string, IStorage>();
map.set(STORAGE_TYPE_LOCAL, local);
map.set(STORAGE_TYPE_ALI_OSS, oss);
map.set(STORAGE_TYPE_S3, s3);
map.set(STORAGE_TYPE_TX_COS, cos);
export function getStorageConfig(key: string): IStorage {
return map.get(key);

View File

@ -0,0 +1,27 @@
import { STORAGE_TYPE_TX_COS } from '../constants';
import { cloudFilenameGetter } from '../utils';
export default {
filenameKey: 'url',
make(storage) {
const createTxCosStorage = require('multer-cos');
return new createTxCosStorage({
cos: storage.options,
filename: cloudFilenameGetter(storage),
});
},
defaults() {
return {
title: '腾讯云对象存储',
type: STORAGE_TYPE_TX_COS,
name: 'tx-cos-1',
baseUrl: process.env.TX_COS_STORAGE_BASE_URL,
options: {
Region: process.env.TX_COS_REGION,
SecretId: process.env.TX_COS_SECRET_ID,
SecretKey: process.env.TX_COS_SECRET_KEY,
Bucket: process.env.TX_COS_BUCKET,
},
};
},
};

107
yarn.lock
View File

@ -5331,7 +5331,14 @@
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
"@types/react-dom@^16.9.8", "@types/react-dom@^17.0.0":
"@types/react-dom@^16.9.8":
version "16.9.16"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.16.tgz#c591f2ed1c6f32e9759dfa6eb4abfd8041f29e39"
integrity sha512-Oqc0RY4fggGA3ltEgyPLc3IV9T73IGoWjkONbsyJ3ZBn+UPPCYpU2ec0i3cEbJuEdZtkqcCF2l1zf2pBdgUGSg==
dependencies:
"@types/react" "^16"
"@types/react-dom@^17.0.0":
version "17.0.11"
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
@ -5391,7 +5398,7 @@
"@types/history" "*"
"@types/react" "*"
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^16.9.43", "@types/react@^17.0.0":
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^17.0.0":
version "17.0.34"
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
@ -5400,6 +5407,15 @@
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/react@^16", "@types/react@^16.9.43":
version "16.14.32"
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.32.tgz#d4e4fe5ece3c27fcb4608b1f4a614f7dec881392"
integrity sha512-hvEy4vGVADbtj/U6+CA5SRC5QFIjdxD7JslAie8EuAYZwhYY9bgforpXNyF1VFzhnkEOesDy1278t1wdjN74cw==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/resolve@1.17.1":
version "1.17.1"
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
@ -6088,6 +6104,13 @@ ajv-errors@^1.0.0:
resolved "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d"
integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==
ajv-formats@^1.5.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/ajv-formats/-/ajv-formats-1.6.1.tgz#35c7cdcd2a12d509171c37bac32f2e8eb010a536"
integrity sha512-4CjkH20If1lhR5CGtqkrVg3bbOtFEG80X9v6jDOIUhbzzbB+UzPBGy8GQhUNVZ0yvMHdMpawCOcy5ydGMsagGQ==
dependencies:
ajv "^7.0.0"
ajv-keywords@^3.1.0, ajv-keywords@^3.5.2:
version "3.5.2"
resolved "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
@ -6103,6 +6126,16 @@ ajv@6.12.6, ajv@^6.1.0, ajv@^6.10.0, ajv@^6.12.3, ajv@^6.12.4, ajv@^6.12.5, ajv@
json-schema-traverse "^0.4.1"
uri-js "^4.2.2"
ajv@^7.0.0, ajv@^7.0.3:
version "7.2.4"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-7.2.4.tgz#8e239d4d56cf884bccca8cca362f508446dc160f"
integrity sha512-nBeQgg/ZZA3u3SYxyaDvpvDtgZ/EZPF547ARgZBrG9Bhu1vKDwAIjtIf+sDtJUKa2zOcEbmRLBRSyMraS/Oy1A==
dependencies:
fast-deep-equal "^3.1.1"
json-schema-traverse "^1.0.0"
require-from-string "^2.0.2"
uri-js "^4.2.2"
ali-oss@^6.16.0:
version "6.17.1"
resolved "https://registry.yarnpkg.com/ali-oss/-/ali-oss-6.17.1.tgz#3e88738ec01111a26a2b967cf857d97050886156"
@ -6736,6 +6769,11 @@ atob@^2.1.2:
resolved "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==
atomically@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/atomically/-/atomically-1.7.0.tgz#c07a0458432ea6dbc9a3506fffa424b48bccaafe"
integrity sha512-Xcz9l0z7y9yQ9rdDaxlmaI4uJHf/T8g9hOEzJcsEqX2SjCj4J20uK7+ldkDHMbpJDK76wF7xEIgxc/vSlsfw5w==
autoprefixer@9.6.0:
version "9.6.0"
resolved "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.6.0.tgz#0111c6bde2ad20c6f17995a33fad7cf6854b4c87"
@ -8258,6 +8296,23 @@ concurrently@^7.0.0:
tree-kill "^1.2.2"
yargs "^16.2.0"
conf@^9.0.0:
version "9.0.2"
resolved "https://registry.yarnpkg.com/conf/-/conf-9.0.2.tgz#943589602b1ce274d9234265314336a698972bc5"
integrity sha512-rLSiilO85qHgaTBIIHQpsv8z+NnVfZq3cKuYNCXN1AOqPzced0GWZEe/A517VldRLyQYXUMyV+vszavE2jSAqw==
dependencies:
ajv "^7.0.3"
ajv-formats "^1.5.1"
atomically "^1.7.0"
debounce-fn "^4.0.0"
dot-prop "^6.0.1"
env-paths "^2.2.0"
json-schema-typed "^7.0.3"
make-dir "^3.1.0"
onetime "^5.1.2"
pkg-up "^3.1.0"
semver "^7.3.4"
config-chain@^1.1.12:
version "1.1.13"
resolved "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz#fad0795aa6a6cdaff9ed1b68e9dff94372c232f4"
@ -8538,6 +8593,16 @@ core-util-is@^1.0.2, core-util-is@~1.0.0:
resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85"
integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==
cos-nodejs-sdk-v5@^2.11.14:
version "2.11.14"
resolved "https://registry.yarnpkg.com/cos-nodejs-sdk-v5/-/cos-nodejs-sdk-v5-2.11.14.tgz#9179a751d2f5e229422335609f688e0d1c786c5e"
integrity sha512-PCkGa1tJikjIr42GqihTnefZntPHp64so55E2rcSYzim6nUCrJdcFM9eBLU3rMS4Nz11mBjhSPqyQJq+avfHqg==
dependencies:
conf "^9.0.0"
mime-types "^2.1.24"
request "^2.88.2"
xml2js "^0.4.19"
cosmiconfig-typescript-loader@^1.0.0:
version "1.0.4"
resolved "https://registry.npmjs.org/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-1.0.4.tgz#2903d53aec07c8079c5ff4aa1b10bd5d2fbdff81"
@ -9093,6 +9158,13 @@ dayjs@~1.8.24, dayjs@~1.8.25:
resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.8.36.tgz#be36e248467afabf8f5a86bae0de0cdceecced50"
integrity sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==
debounce-fn@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/debounce-fn/-/debounce-fn-4.0.0.tgz#ed76d206d8a50e60de0dd66d494d82835ffe61c7"
integrity sha512-8pYCQiL9Xdcg0UPSD3d+0KMlOjp+KGU5EPwYddgzQ7DATsg4fuUDjQtsYLmWjnk2obnNHgV3vE2Y4jejSOJVBQ==
dependencies:
mimic-fn "^3.0.0"
debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -14004,6 +14076,16 @@ json-schema-traverse@^0.4.1:
resolved "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json-schema-traverse@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz#ae7bcb3656ab77a73ba5c49bf654f38e6b6860e2"
integrity sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==
json-schema-typed@^7.0.3:
version "7.0.3"
resolved "https://registry.yarnpkg.com/json-schema-typed/-/json-schema-typed-7.0.3.tgz#23ff481b8b4eebcd2ca123b4fa0409e66469a2d9"
integrity sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==
json-schema@0.2.3:
version "0.2.3"
resolved "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
@ -15339,7 +15421,7 @@ mime-types@^2.1.12, mime-types@^2.1.18, mime-types@~2.1.19, mime-types@~2.1.24:
dependencies:
mime-db "1.51.0"
mime-types@~2.1.34:
mime-types@^2.1.24, mime-types@~2.1.34:
version "2.1.35"
resolved "https://registry.npmmirror.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
@ -15371,6 +15453,11 @@ mimic-fn@^2.1.0:
resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mimic-fn@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74"
integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==
mimic-response@^1.0.0, mimic-response@^1.0.1:
version "1.0.1"
resolved "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
@ -15616,6 +15703,11 @@ multer-aliyun-oss@2.1.1:
dependencies:
ali-oss "^6.16.0"
multer-cos@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/multer-cos/-/multer-cos-1.0.3.tgz#f8998b9a16d2e6e2d3f00cd811ef0bcf14e804b6"
integrity sha512-KD1/ZUHcmjYtsPHlosOsned8u6a1xYgUYmnU0659rRue4fjVSbEBo6BKyI2N6BbUNHAWMz18NUb9a5Gn5GcgvQ==
multer-s3@^2.10.0:
version "2.10.0"
resolved "https://registry.npmjs.org/multer-s3/-/multer-s3-2.10.0.tgz#95c5a51ad0d165bcabdfd54572ded76a25b54754"
@ -17105,7 +17197,7 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"
pkg-up@3.1.0:
pkg-up@3.1.0, pkg-up@^3.1.0:
version "3.1.0"
resolved "https://registry.npmjs.org/pkg-up/-/pkg-up-3.1.0.tgz#100ec235cc150e4fd42519412596a28512a0def5"
integrity sha512-nDywThFk1i4BQK4twPQ6TA4RT8bDY96yeuCVBWL3ePARCiEKDRSrNGbFIgUJpLp+XeIR65v8ra7WuJOFUBtkMA==
@ -19767,6 +19859,11 @@ require-directory@^2.1.1:
resolved "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
require-from-string@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909"
integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==
require-in-the-middle@^5.0.0:
version "5.1.0"
resolved "https://registry.npmjs.org/require-in-the-middle/-/require-in-the-middle-5.1.0.tgz#b768f800377b47526d026bbf5a7f727f16eb412f"
@ -23167,7 +23264,7 @@ xml2js@0.4.19:
sax ">=0.6.0"
xmlbuilder "~9.0.1"
xml2js@^0.4.16:
xml2js@^0.4.16, xml2js@^0.4.19:
version "0.4.23"
resolved "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66"
integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==