mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 05:29:26 +08:00
Feat/support s3 storage next (#6096)
* feat: change upload components * feat: restore original component in schema, support useUploadProps register * feat: s3 upload support relation fileds * feat: export buildin storages * chore: remove invalid file * fix: change getStorage action name * fix: change action name getDesensitizedStorage to getDesensitized * fix: add storage get action * fix: remove useStorageRules * fix: change action get to getBasicInfo * fix: adjust server test case
This commit is contained in:
parent
f3c1761840
commit
7ccf088a06
@ -85,9 +85,10 @@ const useTableSelectorProps = () => {
|
||||
function FileSelector(props) {
|
||||
const { disabled, multiple, value, onChange, action, onSelect, quickUpload, selectFile, ...other } = props;
|
||||
const { wrapSSR, hashId, componentCls: prefixCls } = useStyles();
|
||||
const { useFileCollectionStorageRules } = useExpressionScope();
|
||||
const { useFileCollectionStorageRules, useAttachmentFieldProps } = useExpressionScope();
|
||||
const { t } = useTranslation();
|
||||
const rules = useFileCollectionStorageRules();
|
||||
const attachmentFieldProps = useAttachmentFieldProps();
|
||||
// 兼容旧版本
|
||||
const showSelectButton = selectFile === undefined && quickUpload === undefined;
|
||||
return wrapSSR(
|
||||
@ -116,6 +117,7 @@ function FileSelector(props) {
|
||||
) : null}
|
||||
{quickUpload ? (
|
||||
<Uploader
|
||||
{...attachmentFieldProps}
|
||||
value={value}
|
||||
multiple={multiple}
|
||||
// onRemove={handleRemove}
|
||||
|
@ -41,7 +41,13 @@ attachmentFileTypes.add({
|
||||
return matchMimetype(file, 'image/*');
|
||||
},
|
||||
getThumbnailURL(file) {
|
||||
return file.url ? `${file.url}${file.thumbnailRule || ''}` : URL.createObjectURL(file.originFileObj);
|
||||
if (file.url) {
|
||||
return `${file.url}${file.thumbnailRule || ''}`;
|
||||
}
|
||||
if (file.originFileObj) {
|
||||
return URL.createObjectURL(file.originFileObj);
|
||||
}
|
||||
return null;
|
||||
},
|
||||
Previewer({ index, list, onSwitchIndex }) {
|
||||
const onDownload = useCallback(
|
||||
|
@ -147,7 +147,6 @@ export function useUploadProps<T extends IUploadProps = UploadProps>(props: T) {
|
||||
const api = useAPIClient();
|
||||
|
||||
return {
|
||||
...props,
|
||||
// in customRequest method can't modify form's status(e.g: form.disabled=true )
|
||||
// that will be trigger Upload component(actual Underlying is AjaxUploader component )'s componentWillUnmount method
|
||||
// which will cause multiple files upload fail
|
||||
@ -180,6 +179,7 @@ export function useUploadProps<T extends IUploadProps = UploadProps>(props: T) {
|
||||
},
|
||||
};
|
||||
},
|
||||
...props,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -10,6 +10,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useField } from '@formily/react';
|
||||
import { useAPIClient, useCollectionField, useCollectionManager, useRequest } from '@nocobase/client';
|
||||
import { useStorageUploadProps } from './useStorageUploadProps';
|
||||
|
||||
export function useStorageRules(storage) {
|
||||
const name = storage ?? '';
|
||||
@ -17,7 +18,7 @@ export function useStorageRules(storage) {
|
||||
const field = useField<any>();
|
||||
const { loading, data, run } = useRequest<any>(
|
||||
{
|
||||
url: `storages:getRules/${name}`,
|
||||
url: `storages:getBasicInfo/${name}`,
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
@ -31,17 +32,16 @@ export function useStorageRules(storage) {
|
||||
}
|
||||
run();
|
||||
}, [field.pattern, run]);
|
||||
return (!loading && data?.data) || null;
|
||||
return (!loading && data?.data?.rules) || null;
|
||||
}
|
||||
|
||||
export function useAttachmentFieldProps() {
|
||||
const field = useCollectionField();
|
||||
const rules = useStorageRules(field?.storage);
|
||||
|
||||
return {
|
||||
rules,
|
||||
action: `${field.target}:create${field.storage ? `?attachmentField=${field.collectionName}.${field.name}` : ''}`,
|
||||
};
|
||||
const action = `${field.target}:create${
|
||||
field.storage ? `?attachmentField=${field.collectionName}.${field.name}` : ''
|
||||
}`;
|
||||
const storageUploadProps = useStorageUploadProps({ action });
|
||||
return { action, ...storageUploadProps };
|
||||
}
|
||||
|
||||
export function useFileCollectionStorageRules() {
|
||||
|
@ -0,0 +1,67 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import {
|
||||
Input,
|
||||
Upload,
|
||||
useCollection,
|
||||
useCollectionField,
|
||||
useCollectionManager,
|
||||
useCollectionRecordData,
|
||||
usePlugin,
|
||||
useRequest,
|
||||
withDynamicSchemaProps,
|
||||
} from '@nocobase/client';
|
||||
import React, { useEffect } from 'react';
|
||||
import { connect, mapProps, mapReadPretty, useField } from '@formily/react';
|
||||
import FileManagerPlugin from '../';
|
||||
|
||||
export function useStorage(storage) {
|
||||
const name = storage ?? '';
|
||||
const url = `storages:getBasicInfo/${name}`;
|
||||
const { loading, data, run } = useRequest<any>(
|
||||
{
|
||||
url,
|
||||
},
|
||||
{
|
||||
manual: true,
|
||||
refreshDeps: [name],
|
||||
cacheKey: url,
|
||||
},
|
||||
);
|
||||
useEffect(() => {
|
||||
run();
|
||||
}, [run]);
|
||||
return (!loading && data?.data) || null;
|
||||
}
|
||||
|
||||
export function useStorageCfg() {
|
||||
const field = useCollectionField();
|
||||
const cm = useCollectionManager();
|
||||
const targetCollection = cm.getCollection(field?.target);
|
||||
const collection = useCollection();
|
||||
const plugin = usePlugin(FileManagerPlugin);
|
||||
const storage = useStorage(
|
||||
field?.storage || collection?.getOption('storage') || targetCollection?.getOption('storage'),
|
||||
);
|
||||
const storageType = plugin.getStorageType(storage?.type);
|
||||
return {
|
||||
storage,
|
||||
storageType,
|
||||
};
|
||||
}
|
||||
export function useStorageUploadProps(props) {
|
||||
const { storage, storageType } = useStorageCfg();
|
||||
const useStorageTypeUploadProps = storageType?.useUploadProps;
|
||||
const storageTypeUploadProps = useStorageTypeUploadProps?.({ storage, rules: storage.rules, ...props }) || {};
|
||||
return {
|
||||
rules: storage?.rules,
|
||||
...storageTypeUploadProps,
|
||||
};
|
||||
}
|
@ -16,7 +16,7 @@ import {
|
||||
useSourceId,
|
||||
} from '@nocobase/client';
|
||||
import { useContext, useMemo } from 'react';
|
||||
import { useStorageRules } from './useStorageRules';
|
||||
import { useStorageUploadProps } from './useStorageUploadProps';
|
||||
|
||||
export const useUploadFiles = () => {
|
||||
const { getDataBlockRequest } = useDataBlockRequestGetter();
|
||||
@ -24,7 +24,6 @@ export const useUploadFiles = () => {
|
||||
const { setVisible } = useActionContext();
|
||||
const collection = useCollection();
|
||||
const sourceId = useSourceId();
|
||||
const rules = useStorageRules(collection?.getOption('storage'));
|
||||
const action = useMemo(() => {
|
||||
let action = `${collection.name}:create`;
|
||||
if (association) {
|
||||
@ -38,7 +37,7 @@ export const useUploadFiles = () => {
|
||||
|
||||
let pendingNumber = 0;
|
||||
|
||||
return {
|
||||
const uploadProps = {
|
||||
action,
|
||||
onChange(fileList) {
|
||||
fileList.forEach((file) => {
|
||||
@ -62,6 +61,11 @@ export const useUploadFiles = () => {
|
||||
setVisible(false);
|
||||
}
|
||||
},
|
||||
rules,
|
||||
};
|
||||
|
||||
const storageUploadProps = useStorageUploadProps(uploadProps);
|
||||
return {
|
||||
...uploadProps,
|
||||
...storageUploadProps,
|
||||
};
|
||||
};
|
||||
|
@ -16,8 +16,11 @@ import { AttachmentFieldInterface } from './interfaces/attachment';
|
||||
import { FileCollectionTemplate } from './templates';
|
||||
import { useAttachmentFieldProps, useFileCollectionStorageRules } from './hooks';
|
||||
import { FileSizeField } from './FileSizeField';
|
||||
import { STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_LOCAL, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS } from '../constants';
|
||||
|
||||
export class PluginFileManagerClient extends Plugin {
|
||||
// refer by plugin-field-attachment-url
|
||||
static buildInStorage = [STORAGE_TYPE_LOCAL, STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS];
|
||||
storageTypes = new Map();
|
||||
|
||||
async load() {
|
||||
@ -65,6 +68,10 @@ export class PluginFileManagerClient extends Plugin {
|
||||
registerStorageType(name: string, options) {
|
||||
this.storageTypes.set(name, options);
|
||||
}
|
||||
|
||||
getStorageType(name: string) {
|
||||
return this.storageTypes.get(name);
|
||||
}
|
||||
}
|
||||
|
||||
export default PluginFileManagerClient;
|
||||
|
@ -35,6 +35,7 @@ describe('action', () => {
|
||||
local1 = await StorageRepo.create({
|
||||
values: {
|
||||
name: 'local1',
|
||||
title: 'local1',
|
||||
type: STORAGE_TYPE_LOCAL,
|
||||
baseUrl: DEFAULT_LOCAL_BASE_URL,
|
||||
rules: {
|
||||
@ -478,34 +479,40 @@ describe('action', () => {
|
||||
});
|
||||
|
||||
describe('storage actions', () => {
|
||||
describe('getRules', () => {
|
||||
it('get rules without key as default storage', async () => {
|
||||
const { body, status } = await agent.resource('storages').getRules();
|
||||
describe('getBasicInfo', () => {
|
||||
it('get default storage', async () => {
|
||||
const { body, status } = await agent.resource('storages').getBasicInfo();
|
||||
expect(status).toBe(200);
|
||||
expect(body.data).toEqual({ size: FILE_SIZE_LIMIT_DEFAULT });
|
||||
expect(body.data).toMatchObject({ id: 1 });
|
||||
});
|
||||
|
||||
it('get rules by storage id as default rules', async () => {
|
||||
const { body, status } = await agent.resource('storages').getRules({ filterByTk: 1 });
|
||||
expect(status).toBe(200);
|
||||
expect(body.data).toEqual({ size: FILE_SIZE_LIMIT_DEFAULT });
|
||||
});
|
||||
|
||||
it('get rules by unexisted id as 404', async () => {
|
||||
const { body, status } = await agent.resource('storages').getRules({ filterByTk: -1 });
|
||||
it('get storage by unexisted id as 404', async () => {
|
||||
const { body, status } = await agent.resource('storages').getBasicInfo({ filterByTk: -1 });
|
||||
expect(status).toBe(404);
|
||||
});
|
||||
|
||||
it('get rules by storage id', async () => {
|
||||
const { body, status } = await agent.resource('storages').getRules({ filterByTk: local1.id });
|
||||
it('get by storage local id', async () => {
|
||||
const { body, status } = await agent.resource('storages').getBasicInfo({ filterByTk: local1.id });
|
||||
expect(status).toBe(200);
|
||||
expect(body.data).toMatchObject({ size: 1024 });
|
||||
expect(body.data).toMatchObject({
|
||||
id: local1.id,
|
||||
title: local1.title,
|
||||
name: local1.name,
|
||||
type: local1.type,
|
||||
rules: local1.rules,
|
||||
});
|
||||
});
|
||||
|
||||
it('get rules by storage name', async () => {
|
||||
const { body, status } = await agent.resource('storages').getRules({ filterByTk: local1.name });
|
||||
it('get storage by name', async () => {
|
||||
const { body, status } = await agent.resource('storages').getBasicInfo({ filterByTk: local1.name });
|
||||
expect(status).toBe(200);
|
||||
expect(body.data).toMatchObject({ size: 1024 });
|
||||
expect(body.data).toMatchObject({
|
||||
id: local1.id,
|
||||
title: local1.title,
|
||||
name: local1.name,
|
||||
type: local1.type,
|
||||
rules: local1.rules,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -126,8 +126,11 @@ export async function createMiddleware(ctx: Context, next: Next) {
|
||||
const storage = await StorageRepo.findOne({ filter: storageName ? { name: storageName } : { default: true } });
|
||||
|
||||
ctx.storage = storage;
|
||||
|
||||
await multipart(ctx, next);
|
||||
if (ctx?.request.is('multipart/*')) {
|
||||
await multipart(ctx, next);
|
||||
} else {
|
||||
await next();
|
||||
}
|
||||
}
|
||||
|
||||
export async function destroyMiddleware(ctx: Context, next: Next) {
|
||||
|
@ -1,6 +1,15 @@
|
||||
/**
|
||||
* This file is part of the NocoBase (R) project.
|
||||
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
||||
* Authors: NocoBase Team.
|
||||
*
|
||||
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
||||
* For more information, please refer to: https://www.nocobase.com/agreement.
|
||||
*/
|
||||
|
||||
import Plugin from '..';
|
||||
|
||||
export async function getRules(context, next) {
|
||||
export async function getBasicInfo(context, next) {
|
||||
const { storagesCache } = context.app.pm.get(Plugin) as Plugin;
|
||||
let result;
|
||||
const { filterByTk } = context.action.params;
|
||||
@ -15,7 +24,13 @@ export async function getRules(context, next) {
|
||||
if (!result) {
|
||||
return context.throw(404);
|
||||
}
|
||||
context.body = result.rules;
|
||||
context.body = {
|
||||
id: result.id,
|
||||
title: result.title,
|
||||
name: result.name,
|
||||
type: result.type,
|
||||
rules: result.rules,
|
||||
};
|
||||
|
||||
next();
|
||||
}
|
||||
|
@ -232,7 +232,7 @@ export class PluginFileManagerServer extends Plugin {
|
||||
|
||||
this.app.acl.allow('attachments', 'upload', 'loggedIn');
|
||||
this.app.acl.allow('attachments', 'create', 'loggedIn');
|
||||
this.app.acl.allow('storages', 'getRules', 'loggedIn');
|
||||
this.app.acl.allow('storages', 'getBasicInfo', 'loggedIn');
|
||||
|
||||
// this.app.resourcer.use(uploadMiddleware);
|
||||
// this.app.resourcer.use(createAction);
|
||||
|
Loading…
x
Reference in New Issue
Block a user