iTwin 缓存云服务器建立
当前 iTwin.js 原生支持 Azure Blob 云存储,用户通过配置 Azure 提供的 account
、accessKey
密钥启动该功能即可。
如需支持其他云服务(阿里云、亚马逊云、MinIO、腾讯云、华为云等)则根据实际情况进行引用或扩展。如在 iTwin.js v3.4 及以后的版本可使用官方提供的 iTwin/object-storage: Monorepo for object storage abstraction packages. (github.com) 包源进行添加;在 iTwin.js v3.4 以前的版本,则需扩展 CloudStorageService
类,详情查看。
iTwin.js 缓存云服务版本变更详见:3.4.0 Change Notes - iTwin.js (itwinjs.org)
1. 缓存云服务建立
1.1 主要流程
1.2 主要步骤
1.2.1 获取密钥
获取云服务提供商的存储密钥,例如 account,accessKey。通过提供的密钥,采用对应的 SDK 包实现云服务的连接。
1.2.2 前端配置
iTwin.js v3.4 版本前
在调用
IModelApp.startup()
方法之前,需要指定云服务提供商,例如CloudStorageTileCache.getCache().provider = CloudStorageProvider.Azure;
iTwin.js v3.4 版本后
const tileAdminProps: TileAdmin.Props = {}; const serverConfig = { // eslint-disable-next-line @typescript-eslint/naming-convention FrontendStorage: { dependencyName: "name", }, }; tileAdminProps.tileStorage = new FrontendStorageSetup( serverConfig, yourServerFrontendStorageBindings );
1.2.3 后端配置
iTwin.js v3.4 版本前
在调用
IModelHost.startup()
方法时,需配置缓存设置等内容,例如const credentials: CloudStorageServiceCredentials = { service: "Azure", account: "account", accessKey: "accessKey", }; CloudStorageTileCache.getCache().provider = CloudStorageProvider.Azure; hostConfig.compressCachedTiles = true; hostConfig.tileCacheCredentials = credentials; IModelHost.tileCacheService = new AzureBlobStorage(credentials); IModelHost.startup(hostConfig);
iTwin.js v3.4 版本后
// Pseudocode const container = new Container(); useBindings(ServerStorageDependency); useBindings(ClientStorageDependency); container.bind<DependenciesConfig>... const yourServerStorage: ServerStorage = this.conatiner.get(ServerStorage); const iModelHost: IModelHostOptions = { tileCacheStorage: yourServerStorage, }
2. 云缓存具体实现示例
2.1 iTwin.js v3.4 版本前 -- 以阿里云为例
2.1.1 获取密钥
阿里云需要获取的信息有:
(1)AccessKey
从 AccessKey 里面需要获取 AccessKey ID 和 AccessKey Secret,具体信息可 点击查看 ,对应 iTwin 关系如下:
iTwin | AliCloud |
---|---|
account | AccessKey ID |
accessKey | AccessKey Secret |
(2)bucketRegion
指阿里云创建对象存储的位置,如 oss-cn-beijing
, 具体检索位置可 点击查看 。
(3)bucket
由于阿里云对象存储(OSS)空间有数量限制,同账号下不超过 100 个,因此建议单独创建一个 bucket 存放 iTwin 缓存数据,bucket 的使用限制可 点击查看。
(4)完整配置示例
{
"service": "AliCloud",
"account": "XXXXXXXXXXXXXX",
"accessKey": "XXXXXXXXXXXXXX",
"container": "imodel-cache",
"bucketRegion": "oss-cn-beijing"
}
2.1.2 前端配置
CloudStorageTileCache.getCache().provider = CloudStorageProvider.AliCloud;
await IModelApp.startup(opts);
2.1.3 后端配置
(1)扩展 CloudStorageService
主要实现的方法有:
- obtainContainerUrl
public obtainContainerUrl(
id: CloudStorageContainerDescriptor,
expiry: Date,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
_clientIp ?: string,
): CloudStorageContainerUrl {
const policy: OSS.SignatureUrlOptions = {
expires: 1800, //default expire time 30 min
};
const url: CloudStorageContainerUrl = {
descriptor: this.makeDescriptor(id),
valid: 0,
expires: expiry.getTime(),
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
url: this._client.signatureUrl(id.name + '/' + id.resource!, policy),
bound: true,
};
return url;
}
- upload
public async upload(
container: string,
name: string,
data: Uint8Array,
options ? : CloudStorageUploadOptions,
): Promise < string > {
try {
await this._client.getBucketInfo(this._client.options.bucket);
} catch (error) {
// in case of empty bucket info
if (!this._client.options.bucket) {
this._client.options.bucket = 'imodel-cache';
}
await this._client.putBucket(this._client.options.bucket);
await this._client.putBucketCORS(this._client.options.bucket, [{
allowedOrigin: '*',
allowedMethod: ['GET', 'POST', 'PUT', 'HEAD'],
allowedHeader: '*',
}, ]);
}
this._client.useBucket(this._client.options.bucket);
const dataStream = new PassThrough();
dataStream.end(data);
let source: Readable;
const putOptions = {
mime: options && options.type ? options.type : 'application/octet-stream',
headers: {
'Cache-Control': options && options.cacheControl ?
options.cacheControl :
'private, max-age=31536000, immutable',
},
}
as OSS.PutStreamOptions;
if (options && options.contentEncoding === 'gzip') {
(putOptions.headers as any)['Content-Encoding'] = options.contentEncoding;
const compressor = zlib.createGzip();
source = dataStream.pipe(compressor);
} else {
source = dataStream;
}
await this._client.putStream(container + '/' + name, source, putOptions);
return '';
}
完整实现代码可查看 iPC repo 的 dev 分支,路径为 backend > src > client > AliCloudStorageService.ts 。
2.1.4 效果展示
(1) iTwin Viewer
在 iTwin 请求加载模型的过程中,通过 Network 面板查看到 Viewer 正在请求阿里云服务的缓存数据。
(2) 阿里云 OSS 控制面板
在阿里云控制台中,通过查看 Bucket 列表,可发现该 iModel Cache 存放的文件。
2.2 iTwin.js v3.4 版本后
iTwin.js 提供开源的对象存储服务,代码仓库地址:https://github.com/iTwin/object-storage 。 该服务提供对象存储的核心服务,以及基于核心服务(object-storage-core)扩展的 Azure, MinIO, OSS, S3 的存储服务。如需其他云服务存储服务,可基于核心服务进行自行扩展。
使用前说明
- 包源 官方已提供核心(core)、Azure, MinIO, OSS, S3 的 npm 包,根据项目可以自行进展安装。如使用阿里云的 OSS 存储,运行:
npm install @itwin/object-storage-oss
版本
推荐使用与 iTwin.js 引用版本一致的对象存储服务(可在 @itwin/core-common 包中寻找所依赖的 @itwin/object-storage-core 版本)。如 iTwin.js v3.6 采用的是 v1.4 版本的对象存储服务,则在安装包源时需指定该包源版本:
npm install npm @itwin/object-storage-oss@1.4.0
使用方式
推荐采用依赖注入(Dependency Injection)的方式进行调用,即使用 Iversify。使用方式可参考:object-storage/samples/src/client-file-download/with-inversify at main · iTwin/object-storage (github.com) 。
2.2.1 微软 Azure Blob 存储
创建 Storage account : Create a storage account - Azure Storage | Microsoft Learn
允许 SDK 请求网络设置:Configure Azure Storage firewalls and virtual networks | Microsoft Learn
AccountName, AccessKey 获取:Manage account access keys - Azure Storage | Microsoft Learn
iTwin 程序设置
// backend const iModelHost: IModelHostOptions = { tileCacheAzureCredentials: { account: "your-account-name", accessKey: "your-access-key", }, };
存储结果查看,在 Manage blob containers using the Azure portal - Azure Storage | Microsoft Learn 中根据 iModel ID 查询对应的缓存数据。
2.2.2 MinIO
MinIO Server 配置和参数获取
- 预先创建一个 bucket,如取名为 itwin
- baseUrl:MinIOServer 地址,默认在 9000 端口
- region:bucket 区域,默认为 us-east-1
- roleArn:角色定义提供商,默认值为"\
" - stsBaseUrl: Security Token Service 地址,默认值与 baseUrl 一致
- accessKey 和 secretkey 获取:https://min.io/docs/minio/windows/administration/console/security-and-access.html#access-keys
前端配置
安装依赖包
npm i @itwin/object-storage-minio@1.4.0 npm i reflect-metadata
启动配置
import "reflect-metadata"; import { FrontendMinioS3ClientWrapperFactory } from "@itwin/object-storage-minio/lib/frontend"; const tileAdminProps: TileAdmin.Props = {...}; const frontendMinIOWrapperFactory = new FrontendMinioS3ClientWrapperFactory(); tileAdminProps.tileStorage = new MinioFrontendStorage( frontendMinIOWrapperFactory );
后端配置
安装依赖包
npm i inversify
MinIO 绑定
import { Container } from "inversify"; import { ClientStorageDependency, ServerStorage, ServerStorageDependency, } from "@itwin/core-common/node_modules/@itwin/object-storage-core"; import { Bindable, DependenciesConfig, Types as DependencyTypes, } from "@itwin/core-backend/node_modules/@itwin/cloud-agnostic-core"; export class MinioServerSetup extends Bindable { public container = new Container(); constructor( config: DependenciesConfig, serverStorageDependency: new () => ServerStorageDependency, clientStorageDependency: new () => ClientStorageDependency ) { super(); this.requireDependency(ServerStorageDependency.dependencyType); this.requireDependency(ClientStorageDependency.dependencyType); this.useBindings(serverStorageDependency); this.useBindings(clientStorageDependency); this.container .bind<DependenciesConfig>(DependencyTypes.dependenciesConfig) .toConstantValue(config); this.bindDependencies(this.container); } public getServerStorage(): ServerStorage { return this.container.get(ServerStorage); } }
方法引用
import { MinioServerSetup } from "./MinioServerSetup"; // MinIO config const dependencyName = "minio"; const serverStorageConfig: S3ServerStorageConfig = { bucket: "xxx", accessKey: "xxx", secretKey: "xxx", baseUrl: "http://xxxx:9000", region: "us-east-1", roleArn: "<role-arn>", stsBaseUrl: "http://xxxx:9000", }; const minioConfig = { // eslint-disable-next-line @typescript-eslint/naming-convention ServerStorage: { dependencyName, ...serverStorageConfig, }, // eslint-disable-next-line @typescript-eslint/naming-convention ClientStorage: { dependencyName, bucket: serverStorageConfig.bucket, }, // eslint-disable-next-line @typescript-eslint/naming-convention FrontendStorage: { dependencyName, bucket: serverStorageConfig.bucket, }, }; const minioServer = new MinioServerSetup( minioConfig, MinioServerStorageBindings, MinioClientStorageBindings ); // inject to iModelHost const iModelHost: IModelHostOptions = { ..., tileCacheStorage: minioServer.getServerStorage(), };
调用结果示例
Minio 特殊字符处理
由于在 Windows 上 Minio Server 遇到冒号“:”的路径会抛异常,无法按带有冒号的路径存储文件,详情可见 https://github.com/minio/minio/issues/3711 。因此需要对路径生成的方法进行修改,可参考以下“去除冒号”方式处理,需对 iTwin 的前端及后端进行同时修改方可生效。
- 定位至前端或后端 @itwin/core-common 包源中的 lib/*/TileProps.js 中的 getTileObjectReference 方法
将 treeId 进行修改,替换冒号“:”,如 treeId.replace(":", "")
3. iTwin.js 非云缓存存储位置
可通过 backend 进行配置,例如
const hostConfig = new IModelHostConfiguration(); hostConfig.cacheDir = "D:\\tmp";
默认 CacheDir 路径