diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts index 5f097569b8..5b6b715e4e 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/server.ts @@ -12,7 +12,7 @@ import { isURL, Registry } from '@nocobase/utils'; import { basename } from 'path'; -import { Model, Transactionable } from '@nocobase/database'; +import { Collection, Model, Transactionable } from '@nocobase/database'; import fs from 'fs'; import { STORAGE_TYPE_ALI_OSS, STORAGE_TYPE_LOCAL, STORAGE_TYPE_S3, STORAGE_TYPE_TX_COS } from '../constants'; import initActions from './actions'; @@ -23,6 +23,7 @@ import StorageTypeAliOss from './storages/ali-oss'; import StorageTypeLocal from './storages/local'; import StorageTypeS3 from './storages/s3'; import StorageTypeTxCos from './storages/tx-cos'; +import { encodeURL } from './utils'; export type * from './storages'; @@ -205,6 +206,20 @@ export class PluginFileManagerServer extends Plugin { options.model = 'FileModel'; } }); + this.db.on('afterDefineCollection', (collection: Collection) => { + if (collection.options.template !== 'file') { + return; + } + collection.model.beforeUpdate((model) => { + if (!model.changed('url') || !model.changed('preview')) { + return; + } + model.set('url', model.previous('url')); + model.set('preview', model.previous('preview')); + model.changed('url', false); + model.changed('preview', false); + }); + }); this.app.on('afterStart', async () => { await this.loadStorages(); }); @@ -301,11 +316,11 @@ export class PluginFileManagerServer extends Plugin { async getFileURL(file: AttachmentModel, preview = false) { if (!file.storageId) { - return file.url; + return encodeURL(file.url); } const storage = this.storagesCache.get(file.storageId); if (!storage) { - return file.url; + return encodeURL(file.url); } const storageType = this.storageTypes.get(storage.type); return new storageType(storage).getFileURL(file, preview ? storage.options.thumbnailRule : ''); diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/storages/index.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/storages/index.ts index 07a3289ffb..63c283ff16 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/storages/index.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/storages/index.ts @@ -10,6 +10,7 @@ import { isURL } from '@nocobase/utils'; import { StorageEngine } from 'multer'; import urlJoin from 'url-join'; +import { encodeURL } from '../utils'; export interface StorageModel { id?: number; @@ -46,9 +47,9 @@ export abstract class StorageType { // 兼容历史数据 if (file.url && isURL(file.url)) { if (preview) { - return file.url + (this.storage.options.thumbnailRule || ''); + return encodeURL(file.url) + (this.storage.options.thumbnailRule || ''); } - return file.url; + return encodeURL(file.url); } const keys = [ this.storage.baseUrl, diff --git a/packages/plugins/@nocobase/plugin-file-manager/src/server/utils.ts b/packages/plugins/@nocobase/plugin-file-manager/src/server/utils.ts index 89725595f1..1bfccecd3a 100644 --- a/packages/plugins/@nocobase/plugin-file-manager/src/server/utils.ts +++ b/packages/plugins/@nocobase/plugin-file-manager/src/server/utils.ts @@ -7,8 +7,8 @@ * For more information, please refer to: https://www.nocobase.com/agreement. */ -import path from 'path'; import { uid } from '@nocobase/utils'; +import path from 'path'; export function getFilename(req, file, cb) { const originalname = Buffer.from(file.originalname, 'binary').toString('utf8'); @@ -29,3 +29,31 @@ export const cloudFilenameGetter = (storage) => (req, file, cb) => { export function getFileKey(record) { return [record.path.replace(/^\/|\/$/g, ''), record.filename].filter(Boolean).join('/'); } + +function ensureUrlEncoded(value) { + try { + // 如果解码后与原字符串不同,说明已经被转义过 + if (decodeURIComponent(value) !== value) { + return value; // 已经是转义的,直接返回 + } + } catch (e) { + // 如果解码出错,说明是非法的编码,直接转义 + return encodeURIComponent(value); + } + + // 如果没问题但字符串未转义过,则进行转义 + return encodeURIComponent(value); +} + +function encodePathKeepSlash(path) { + return path + .split('/') + .map((segment) => ensureUrlEncoded(segment)) + .join('/'); +} + +export function encodeURL(url) { + const parsedUrl = new URL(url); + parsedUrl.pathname = encodePathKeepSlash(parsedUrl.pathname); + return parsedUrl.toString(); +}