从自定义来源迁移
要从自定义来源迁移文件,您需要创建自己的 SourceStorageAdapter
实现。适配器准备迁移计划,其中包含要迁移的类别、文件夹和资产列表。它们还公开了一种方法,允许迁移程序从存储中下载文件。
要创建一个新的适配器,必须首先在 adapters/
目录中创建一个目录。
下一步是创建 Adapter.ts
文件。这是适配器的入口点,您无法更改此文件名。要创建文件,您可以使用以下样板
import {
ISourceStorageAdapter,
IMigrationPlan,
ISourceCategory,
ISourceFolder,
ISourceAsset
} from '@ckbox-migrator';
export default class ExampleAdapter implements ISourceStorageAdapter {
public readonly name: string = 'The name of your system';
public async loadConfig( plainConfig: Record<string, unknown> ): Promise<void> {
// Load and validate configuration.
}
public async verifyConnection(): Promise<void> {
// Verify if you can connect and authorize to your source storage.
}
public async prepareMigrationPlan(): Promise<IMigrationPlan> {
// Scan your source storage and identify the categories,
// folders and assets to migrate.
return [
categories: [],
assets: []
];
}
public async getAsset( downloadUrl: string ): Promise<IGetAssetResult> {
// Get an asset content from you source storage.
}
}
# 加载配置
要加载配置,您需要实现 loadConfig( plainConfig: Record<string, unknown> )
方法。这是迁移过程中执行的第一步。提供给该方法的参数是 config.json
文件中的 source.options
属性。
为了验证配置,我们建议使用 class-validator
和 class-transformer
库。它们已与迁移程序捆绑在一起,因此您无需单独安装它们。
首先,您需要创建配置类。它将包含配置属性和验证规则。
class ExampleConfig {
@IsString()
@IsDefined()
public readonly foo: string;
}
然后,您可以使用 plainToInstance
映射配置,并使用 validateOrReject
方法验证它。
export default class ExampleAdapter implements ISourceStorageAdapter {
// ...
private _config: ExampleConfig;
public async loadConfig( plainConfig: Record<string, unknown> ): Promise<void> {
this._config = plainToInstance( CKFinderConfig, plainConfig );
await validateOrReject( this._config );
}
现在,您可以配置适配器。您需要将 source.type
设置为创建适配器的文件夹的名称,并将与 ExampleConfig
兼容的配置设置为 source.options
中。
{
"source": {
"type": "example",
"options": {
"foo": "bar"
}
},
"ckbox": {
...
}
}
要构建应用程序,请使用以下命令
npm run build:adapters
# 验证连接
验证连接是用于检查迁移程序是否可以建立与源存储的连接的一步。您可以向源系统中的任何端点发出请求以检查授权是否通过。
import fetch, { Response } from 'node-fetch';
export default class ExampleAdapter implements ISourceStorageAdapter {
// ...
const { serviceOrigin } = this._config;
const response: Response = await fetch( `${ serviceOrigin }/status` );
if ( !response.ok ) {
throw new Error( `Failed to connect to the Example service at ${ serviceOrigin }.` );
}
# 创建迁移计划
在迁移开始之前,适配器应返回迁移计划,该结构包含要创建的类别、文件夹和资产列表。
您应该从源存储请求所有资源的列表,并映射到以下格式。
export interface IMigrationPlan {
readonly categories: ISourceCategory[];
readonly assets: ISourceAsset[];
}
迁移计划应由迁移程序的 prepareMigrationPlan
返回
import crypto from 'node:crypto';
export default class ExampleAdapter implements ISourceStorageAdapter {
// ...
public async prepareMigrationPlan(): Promise<IMigrationPlan> {
const categoryId: string = crypto.randomUUID();
return [
categories: [{
id: categoryId,
name: 'Example category',
allowedExtensions: ['txt'],
folders: []
}],
assets: [{
id: crypto.randomUUID(),
name: 'File',
extension: 'txt',
location: { categoryId }
downloadUrl: 'https://127.0.0.1/file.txt'
downloadUrlToReplace: 'https://127.0.0.1/file.txt'
}]
];
}
# 类别和文件夹
每个类别都应具有唯一的标识符(它无关紧要,它是源系统生成的还是在适配器中使用随机 ID 生成器生成的),名称,允许的扩展列表(请记住,它至少应涵盖将上传到该类别的扩展名)和文件夹树。
export interface ISourceCategory {
readonly id: string;
readonly name: string;
readonly allowedExtensions: string[];
readonly folders: ISourceFolder[];
}
如果您的源系统具有类似于类别的概念(如 CKFinder 中的“资源类型”),您可以将它们直接映射到类别。如果您的系统非常类似于文件系统,您可以将类别视为顶级文件夹。
要创建文件夹树,您需要将文件夹分配到类别中的 folders
属性中。每个文件夹都应具有其自身的标识符,该标识符在类别中是唯一的(例如,指向文件夹的路径或随机生成的 ID),范围和名称。文件夹可以包含子文件夹。
export interface ISourceFolder
readonly id: string;
readonly name: string;
readonly childFolders: ISourceFolder[];
}
# 资产
每个类别都应具有
- 唯一标识符 - 它可以是文件路径或随机生成的 ID,与文件夹相同。
- 文件名。
- 文件扩展名。
- 位置(类别的 ID,以及可选的文件夹 ID)。
- 迁移程序应用来下载文件的 URL。
- 在您的系统中使用的 URL,应在迁移后替换为新的 URL。它可能与用于下载的 URL 相同,但它不需要相同。例如,
downloadUrl
可能指向公司网络中的 API,而downloadUrlToReplace
可能指向公开可用的端点。
export interface ISourceAsset {
readonly id: string;
readonly name: string;
readonly extension: string;
readonly location: ISourceLocation;
readonly downloadUrl: string;
readonly downloadUrlToReplace: string;
}
export interface ISourceLocation {
readonly categoryId: string;
readonly folderId?: string;
}
# 迁移资产
当迁移计划准备就绪时,迁移程序知道应该创建哪些类别、文件夹和资产,但它尚不知道文件的实际内容。要提供文件内容,您需要从 getAsset()
方法返回文件流
import fetch, { Response } from 'node-fetch';
export default class ExampleAdapter implements ISourceStorageAdapter {
// ...
public async getAsset( downloadUrl: string ): Promise<IGetAssetResult> {
const response: Response = await fetch( downloadUrl );
if ( !response.ok ) {
throw new Error(
`Failed to fetch file from the Example service at ${ downloadUrl }. ` +
`Status code: ${ response.status }. ${ await response.text() }`
);
}
return {
stream: response.body,
responsiveImages: []
}
}
# 响应式图像
如果您的源系统为 响应式图像 提供了 URL,那么值得在 IGetAssetResult
的 responsiveImages
属性中返回响应式版本的 URL。借助于此,迁移程序可以将链接添加到 URL 映射列表中。
import fetch, { Response } from 'node-fetch';
export default class ExampleAdapter implements ISourceStorageAdapter {
// ...
public async getAsset( downloadUrl: string ): Promise<IGetAssetResult> {
const response: Response = await fetch( downloadUrl );
if ( !response.ok ) {
throw new Error(
`Failed to fetch file from the Example service at ${ downloadUrl }. ` +
`Status code: ${ response.status }. ${ await response.text() }`
);
}
return {
stream: response.body,
responsiveImages: [
{
width: 100,
url: `${ downloadUrl }/w_100`
},
{
width: 300,
url: `${ downloadUrl }/w_300`
}
// ...
]
}
}