Initial commit
This commit is contained in:
833
skills/walrus-code-generator.md
Normal file
833
skills/walrus-code-generator.md
Normal file
@@ -0,0 +1,833 @@
|
||||
---
|
||||
name: walrus-code-generator
|
||||
description: 自动生成 Walrus TypeScript SDK 集成代码和最佳实践模板
|
||||
parameters:
|
||||
- name: use-case
|
||||
type: string
|
||||
description: 使用场景 (basic-upload/advanced-storage/browser-app/batch-processing)
|
||||
required: true
|
||||
- name: framework
|
||||
type: string
|
||||
description: 开发框架 (react/vue/nodejs/nextjs/express)
|
||||
default: nodejs
|
||||
- name: features
|
||||
type: array
|
||||
description: 需要的功能特性
|
||||
default: []
|
||||
- name: environment
|
||||
type: string
|
||||
description: 部署环境 (development/production)
|
||||
default: development
|
||||
---
|
||||
|
||||
# Walrus 代码生成技能
|
||||
|
||||
## 技能概述
|
||||
|
||||
`walrus-code-generator` 是一个专业的代码生成助手,根据您的具体需求自动生成 Walrus TypeScript SDK 集成代码。支持多种使用场景、开发框架和部署环境,确保生成的代码符合最佳实践。
|
||||
|
||||
## 支持的使用场景
|
||||
|
||||
### 1. 基础文件上传 (basic-upload)
|
||||
|
||||
```typescript
|
||||
// 自动生成的基础上传服务
|
||||
skill: "walrus-code-generator"
|
||||
// use-case: basic-upload
|
||||
// framework: nodejs
|
||||
// features: ["error-handling", "progress-tracking"]
|
||||
|
||||
export class BasicWalrusUploader {
|
||||
private client: any;
|
||||
private signer: any;
|
||||
|
||||
constructor(network: 'testnet' | 'mainnet' = 'testnet') {
|
||||
const suiClient = new SuiJsonRpcClient({
|
||||
url: getFullnodeUrl(network),
|
||||
network,
|
||||
}).$extend(walrus());
|
||||
|
||||
this.client = suiClient;
|
||||
this.signer = this.createSigner();
|
||||
}
|
||||
|
||||
async uploadFile(
|
||||
filePath: string,
|
||||
options: UploadOptions = {}
|
||||
): Promise<UploadResult> {
|
||||
const {
|
||||
epochs = 10,
|
||||
deletable = true,
|
||||
tags = {},
|
||||
onProgress = () => {}
|
||||
} = options;
|
||||
|
||||
try {
|
||||
onProgress(25, '读取文件...');
|
||||
|
||||
const fs = await import('fs/promises');
|
||||
const fileContent = await fs.readFile(filePath);
|
||||
const fileName = filePath.split('/').pop() || 'unknown';
|
||||
|
||||
onProgress(50, '创建 Walrus 文件...');
|
||||
|
||||
const file = WalrusFile.from({
|
||||
contents: fileContent,
|
||||
identifier: `uploads/${Date.now()}_${fileName}`,
|
||||
mimeType: this.getMimeType(filePath),
|
||||
attributes: {
|
||||
'original-path': filePath,
|
||||
'file-size': fileContent.length.toString(),
|
||||
'upload-time': new Date().toISOString(),
|
||||
...tags
|
||||
}
|
||||
});
|
||||
|
||||
onProgress(75, '上传到网络...');
|
||||
|
||||
const result = await this.client.walrus.writeFiles({
|
||||
files: [file],
|
||||
epochs,
|
||||
deletable,
|
||||
signer: this.signer
|
||||
});
|
||||
|
||||
onProgress(100, '上传完成');
|
||||
|
||||
return {
|
||||
success: true,
|
||||
blobId: result[0].blobId,
|
||||
fileName,
|
||||
size: fileContent.length,
|
||||
storedUntil: result[0].storedUntil
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('上传失败:', error.message);
|
||||
throw new Error(`上传失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
private createSigner(): Ed25519Keypair {
|
||||
const privateKey = process.env.WALRUS_PRIVATE_KEY;
|
||||
if (!privateKey) {
|
||||
throw new Error('未找到私钥环境变量 WALRUS_PRIVATE_KEY');
|
||||
}
|
||||
return Ed25519Keypair.fromSecretKey(privateKey);
|
||||
}
|
||||
|
||||
private getMimeType(filePath: string): string {
|
||||
const ext = filePath.split('.').pop()?.toLowerCase();
|
||||
const mimeTypes: Record<string, string> = {
|
||||
'txt': 'text/plain',
|
||||
'pdf': 'application/pdf',
|
||||
'jpg': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'json': 'application/json'
|
||||
};
|
||||
return mimeTypes[ext || ''] || 'application/octet-stream';
|
||||
}
|
||||
}
|
||||
|
||||
interface UploadOptions {
|
||||
epochs?: number;
|
||||
deletable?: boolean;
|
||||
tags?: Record<string, string>;
|
||||
onProgress?: (progress: number, message: string) => void;
|
||||
}
|
||||
|
||||
interface UploadResult {
|
||||
success: boolean;
|
||||
blobId: string;
|
||||
fileName: string;
|
||||
size: number;
|
||||
storedUntil: Date;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 高级存储管理 (advanced-storage)
|
||||
|
||||
```typescript
|
||||
// 自动生成的高级存储管理服务
|
||||
skill: "walrus-code-generator"
|
||||
// use-case: advanced-storage
|
||||
// framework: nodejs
|
||||
// features: ["batch-operations", "retry-logic", "cache-management", "metadata-indexing"]
|
||||
|
||||
export class AdvancedWalrusStorage {
|
||||
private client: any;
|
||||
private signer: any;
|
||||
private cache: Map<string, CachedFile> = new Map();
|
||||
private retryConfig: RetryConfig;
|
||||
|
||||
constructor(options: AdvancedStorageOptions = {}) {
|
||||
const {
|
||||
network = 'testnet',
|
||||
retryAttempts = 3,
|
||||
retryDelay = 1000,
|
||||
cacheEnabled = true,
|
||||
cacheTTL = 300000 // 5分钟
|
||||
} = options;
|
||||
|
||||
const suiClient = new SuiJsonRpcClient({
|
||||
url: getFullnodeUrl(network),
|
||||
network,
|
||||
}).$extend(walrus({
|
||||
storageNodeClientOptions: {
|
||||
timeout: 60000,
|
||||
onError: (error) => console.warn('存储节点错误:', error)
|
||||
}
|
||||
}));
|
||||
|
||||
this.client = suiClient;
|
||||
this.signer = this.createSigner();
|
||||
this.retryConfig = { attempts: retryAttempts, delay: retryDelay };
|
||||
this.cacheEnabled = cacheEnabled;
|
||||
this.cacheTTL = cacheTTL;
|
||||
}
|
||||
|
||||
async batchUpload(
|
||||
files: FileUploadRequest[],
|
||||
options: BatchUploadOptions = {}
|
||||
): Promise<BatchUploadResult> {
|
||||
const {
|
||||
epochs = 10,
|
||||
deletable = true,
|
||||
maxConcurrency = 3,
|
||||
commonTags = {}
|
||||
} = options;
|
||||
|
||||
const results: UploadResult[] = [];
|
||||
const errors: UploadError[] = [];
|
||||
|
||||
// 分批处理
|
||||
for (let i = 0; i < files.length; i += maxConcurrency) {
|
||||
const batch = files.slice(i, i + maxConcurrency);
|
||||
|
||||
const batchPromises = batch.map(async (fileRequest, index) => {
|
||||
try {
|
||||
const result = await this.uploadWithRetry(fileRequest, {
|
||||
epochs,
|
||||
deletable,
|
||||
tags: { ...commonTags, ...fileRequest.tags }
|
||||
});
|
||||
|
||||
results.push({
|
||||
...result,
|
||||
originalPath: fileRequest.path,
|
||||
batchIndex: i + index
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
errors.push({
|
||||
originalPath: fileRequest.path,
|
||||
error: error.message,
|
||||
batchIndex: i + index
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(batchPromises);
|
||||
|
||||
// 批次间延迟
|
||||
if (i + maxConcurrency < files.length) {
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
totalFiles: files.length,
|
||||
successful: results.length,
|
||||
failed: errors.length,
|
||||
results,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
async smartDownload(
|
||||
blobId: string,
|
||||
options: SmartDownloadOptions = {}
|
||||
): Promise<SmartDownloadResult> {
|
||||
const {
|
||||
forceRefresh = false,
|
||||
verifyIntegrity = true,
|
||||
cacheKey = blobId
|
||||
} = options;
|
||||
|
||||
// 检查缓存
|
||||
if (!forceRefresh && this.cacheEnabled) {
|
||||
const cached = this.getFromCache(cacheKey);
|
||||
if (cached) {
|
||||
console.log('从缓存返回文件:', blobId);
|
||||
return {
|
||||
...cached,
|
||||
fromCache: true
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await this.downloadWithVerification(blobId, verifyIntegrity);
|
||||
|
||||
// 缓存结果
|
||||
if (this.cacheEnabled) {
|
||||
this.setToCache(cacheKey, result);
|
||||
}
|
||||
|
||||
return {
|
||||
...result,
|
||||
fromCache: false
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
throw new Error(`智能下载失败: ${error.message}`);
|
||||
}
|
||||
}
|
||||
|
||||
async searchByTags(tags: Record<string, string>): Promise<SearchResult[]> {
|
||||
// 这是一个概念性实现
|
||||
// 实际搜索需要基于您自己的元数据索引系统
|
||||
const indexedFiles = await this.getIndexedFiles();
|
||||
|
||||
return indexedFiles.filter(file => {
|
||||
return Object.entries(tags).every(([key, value]) =>
|
||||
file.tags[key] === value
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
private async uploadWithRetry(
|
||||
fileRequest: FileUploadRequest,
|
||||
options: UploadOptions
|
||||
): Promise<UploadResult> {
|
||||
const { attempts, delay } = this.retryConfig;
|
||||
|
||||
for (let attempt = 1; attempt <= attempts; attempt++) {
|
||||
try {
|
||||
return await this.uploadSingleFile(fileRequest, options);
|
||||
} catch (error) {
|
||||
if (attempt < attempts && this.isRetryableError(error)) {
|
||||
console.log(`上传失败,第 ${attempt} 次重试...`);
|
||||
await new Promise(resolve => setTimeout(resolve, delay * attempt));
|
||||
continue;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('上传失败,已达到最大重试次数');
|
||||
}
|
||||
|
||||
private async uploadSingleFile(
|
||||
fileRequest: FileUploadRequest,
|
||||
options: UploadOptions
|
||||
): Promise<UploadResult> {
|
||||
const fs = await import('fs/promises');
|
||||
const fileContent = await fs.readFile(fileRequest.path);
|
||||
|
||||
const file = WalrusFile.from({
|
||||
contents: fileContent,
|
||||
identifier: fileRequest.identifier || `files/${Date.now()}_${fileRequest.path}`,
|
||||
mimeType: fileRequest.mimeType || this.getMimeType(fileRequest.path),
|
||||
attributes: {
|
||||
'original-path': fileRequest.path,
|
||||
'file-size': fileContent.length.toString(),
|
||||
'upload-time': new Date().toISOString(),
|
||||
...options.tags
|
||||
}
|
||||
});
|
||||
|
||||
const result = await this.client.walrus.writeFiles({
|
||||
files: [file],
|
||||
epochs: options.epochs,
|
||||
deletable: options.deletable,
|
||||
signer: this.signer
|
||||
});
|
||||
|
||||
// 索引文件元数据
|
||||
await this.indexFile({
|
||||
blobId: result[0].blobId,
|
||||
identifier: file.identifier,
|
||||
path: fileRequest.path,
|
||||
size: fileContent.length,
|
||||
tags: options.tags,
|
||||
uploadTime: new Date()
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
blobId: result[0].blobId,
|
||||
identifier: file.identifier,
|
||||
size: fileContent.length,
|
||||
storedUntil: result[0].storedUntil
|
||||
};
|
||||
}
|
||||
|
||||
private async downloadWithVerification(
|
||||
blobId: string,
|
||||
verify: boolean
|
||||
): Promise<SmartDownloadResult> {
|
||||
const [file] = await this.client.walrus.getFiles({ ids: [blobId] });
|
||||
|
||||
const exists = await file.exists();
|
||||
if (!exists) {
|
||||
throw new Error(`文件不存在: ${blobId}`);
|
||||
}
|
||||
|
||||
const content = await file.bytes();
|
||||
let verified = true;
|
||||
|
||||
if (verify) {
|
||||
const verification = await this.verifyFileIntegrity(blobId, content);
|
||||
verified = verification.valid;
|
||||
}
|
||||
|
||||
return {
|
||||
blobId,
|
||||
content: Buffer.from(content),
|
||||
size: content.length,
|
||||
storedUntil: await file.storedUntil(),
|
||||
verified,
|
||||
metadata: await this.getFileMetadata(file)
|
||||
};
|
||||
}
|
||||
|
||||
private async verifyFileIntegrity(
|
||||
blobId: string,
|
||||
content: Uint8Array
|
||||
): Promise<{ valid: boolean; error?: string }> {
|
||||
try {
|
||||
// 基本完整性检查
|
||||
if (content.length === 0) {
|
||||
return { valid: false, error: '文件内容为空' };
|
||||
}
|
||||
|
||||
// 这里可以添加更复杂的验证逻辑
|
||||
return { valid: true };
|
||||
} catch (error) {
|
||||
return { valid: false, error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// 缓存管理
|
||||
private getFromCache(key: string): CachedFile | null {
|
||||
const cached = this.cache.get(key);
|
||||
if (cached && Date.now() - cached.timestamp < this.cacheTTL) {
|
||||
return cached;
|
||||
}
|
||||
this.cache.delete(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
private setToCache(key: string, file: SmartDownloadResult): void {
|
||||
this.cache.set(key, {
|
||||
...file,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
private isRetryableError(error: any): boolean {
|
||||
return error instanceof RetryableWalrusClientError ||
|
||||
error.message.includes('timeout') ||
|
||||
error.message.includes('network');
|
||||
}
|
||||
}
|
||||
|
||||
// 类型定义
|
||||
interface FileUploadRequest {
|
||||
path: string;
|
||||
identifier?: string;
|
||||
mimeType?: string;
|
||||
tags?: Record<string, string>;
|
||||
}
|
||||
|
||||
interface BatchUploadOptions {
|
||||
epochs?: number;
|
||||
deletable?: boolean;
|
||||
maxConcurrency?: number;
|
||||
commonTags?: Record<string, string>;
|
||||
}
|
||||
|
||||
interface BatchUploadResult {
|
||||
totalFiles: number;
|
||||
successful: number;
|
||||
failed: number;
|
||||
results: (UploadResult & { originalPath: string; batchIndex: number })[];
|
||||
errors: UploadError[];
|
||||
}
|
||||
|
||||
interface SmartDownloadOptions {
|
||||
forceRefresh?: boolean;
|
||||
verifyIntegrity?: boolean;
|
||||
cacheKey?: string;
|
||||
}
|
||||
|
||||
interface SmartDownloadResult {
|
||||
blobId: string;
|
||||
content: Buffer;
|
||||
size: number;
|
||||
storedUntil: Date;
|
||||
verified: boolean;
|
||||
metadata: any;
|
||||
fromCache: boolean;
|
||||
}
|
||||
|
||||
interface CachedFile extends SmartDownloadResult {
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
interface RetryConfig {
|
||||
attempts: number;
|
||||
delay: number;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. React 浏览器应用 (browser-app)
|
||||
|
||||
```typescript
|
||||
// 自动生成的 React Hook 和组件
|
||||
skill: "walrus-code-generator"
|
||||
// use-case: browser-app
|
||||
// framework: react
|
||||
// features: ["wallet-integration", "progress-tracking", "drag-drop"]
|
||||
|
||||
import React, { useState, useCallback, createContext, useContext } from 'react';
|
||||
import { useWallet } from '@suiet/wallet-kit';
|
||||
|
||||
// Walrus Context
|
||||
const WalrusContext = createContext<{
|
||||
upload: (files: FileList) => Promise<UploadResult[]>;
|
||||
download: (blobId: string, filename?: string) => Promise<void>;
|
||||
isUploading: boolean;
|
||||
} | null>(null);
|
||||
|
||||
export const WalrusProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||
const { connected, signAndExecuteTransactionBlock } = useWallet();
|
||||
const [isUploading, setIsUploading] = useState(false);
|
||||
|
||||
const upload = useCallback(async (files: FileList): Promise<UploadResult[]> => {
|
||||
if (!connected) {
|
||||
throw new Error('请先连接钱包');
|
||||
}
|
||||
|
||||
setIsUploading(true);
|
||||
const results: UploadResult[] = [];
|
||||
|
||||
try {
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
const file = files[i];
|
||||
|
||||
try {
|
||||
const result = await uploadFile(file, signAndExecuteTransactionBlock);
|
||||
results.push({ ...result, originalName: file.name, index: i });
|
||||
} catch (error) {
|
||||
results.push({
|
||||
success: false,
|
||||
originalName: file.name,
|
||||
index: i,
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
setIsUploading(false);
|
||||
}
|
||||
|
||||
return results;
|
||||
}, [connected, signAndExecuteTransactionBlock]);
|
||||
|
||||
const download = useCallback(async (blobId: string, filename?: string) => {
|
||||
await downloadFile(blobId, filename);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<WalrusContext.Provider value={{ upload, download, isUploading }}>
|
||||
{children}
|
||||
</WalrusContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useWalrus = () => {
|
||||
const context = useContext(WalrusContext);
|
||||
if (!context) {
|
||||
throw new Error('useWalrus must be used within WalrusProvider');
|
||||
}
|
||||
return context;
|
||||
};
|
||||
|
||||
// 文件上传组件
|
||||
export const WalrusUpload: React.FC<{
|
||||
onUploadComplete?: (results: UploadResult[]) => void;
|
||||
maxFiles?: number;
|
||||
acceptedTypes?: string;
|
||||
}> = ({ onUploadComplete, maxFiles = 10, acceptedTypes = '*' }) => {
|
||||
const { upload, isUploading } = useWalrus();
|
||||
const [dragActive, setDragActive] = useState(false);
|
||||
const [progress, setProgress] = useState(0);
|
||||
const fileInputRef = React.useRef<HTMLInputElement>(null);
|
||||
|
||||
const handleFiles = useCallback(async (files: FileList) => {
|
||||
if (files.length > maxFiles) {
|
||||
alert(`最多只能上传 ${maxFiles} 个文件`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setProgress(10);
|
||||
const results = await upload(files);
|
||||
setProgress(100);
|
||||
onUploadComplete?.(results);
|
||||
} catch (error) {
|
||||
alert(`上传失败: ${error.message}`);
|
||||
setProgress(0);
|
||||
}
|
||||
}, [upload, maxFiles, onUploadComplete]);
|
||||
|
||||
const handleDrop = useCallback((e: React.DragEvent) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDragActive(false);
|
||||
|
||||
const files = e.dataTransfer.files;
|
||||
if (files.length > 0) {
|
||||
handleFiles(files);
|
||||
}
|
||||
}, [handleFiles]);
|
||||
|
||||
const handleChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const files = e.target.files;
|
||||
if (files && files.length > 0) {
|
||||
handleFiles(files);
|
||||
}
|
||||
}, [handleFiles]);
|
||||
|
||||
return (
|
||||
<div className="walrus-upload">
|
||||
<div
|
||||
className={`upload-area ${dragActive ? 'active' : ''} ${isUploading ? 'uploading' : ''}`}
|
||||
onDragEnter={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDragActive(true);
|
||||
}}
|
||||
onDragLeave={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setDragActive(false);
|
||||
}}
|
||||
onDragOver={(e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}}
|
||||
onDrop={handleDrop}
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
>
|
||||
<input
|
||||
ref={fileInputRef}
|
||||
type="file"
|
||||
multiple
|
||||
accept={acceptedTypes}
|
||||
onChange={handleChange}
|
||||
style={{ display: 'none' }}
|
||||
/>
|
||||
|
||||
{isUploading ? (
|
||||
<div className="upload-progress">
|
||||
<div className="progress-bar">
|
||||
<div
|
||||
className="progress-fill"
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p>上传中... {progress}%</p>
|
||||
</div>
|
||||
) : (
|
||||
<div className="upload-prompt">
|
||||
<div className="upload-icon">📁</div>
|
||||
<h3>拖拽文件到此处或点击上传</h3>
|
||||
<p>支持最多 {maxFiles} 个文件</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 文件列表组件
|
||||
export const WalrusFileList: React.FC<{
|
||||
files: UploadResult[];
|
||||
onDownload?: (blobId: string, filename: string) => void;
|
||||
}> = ({ files, onDownload }) => {
|
||||
const { download } = useWalrus();
|
||||
|
||||
const handleDownload = useCallback(async (blobId: string, filename: string) => {
|
||||
try {
|
||||
await onDownload ? onDownload(blobId, filename) : download(blobId, filename);
|
||||
} catch (error) {
|
||||
alert(`下载失败: ${error.message}`);
|
||||
}
|
||||
}, [download, onDownload]);
|
||||
|
||||
return (
|
||||
<div className="file-list">
|
||||
<h3>文件列表</h3>
|
||||
{files.map((file, index) => (
|
||||
<div key={index} className={`file-item ${file.success ? 'success' : 'error'}`}>
|
||||
<div className="file-info">
|
||||
<span className="file-name">{file.originalName}</span>
|
||||
{file.success ? (
|
||||
<span className="file-success">✓ 上传成功</span>
|
||||
) : (
|
||||
<span className="file-error">✗ {file.error}</span>
|
||||
)}
|
||||
</div>
|
||||
{file.success && file.blobId && (
|
||||
<div className="file-actions">
|
||||
<button
|
||||
onClick={() => handleDownload(file.blobId!, file.originalName)}
|
||||
className="download-btn"
|
||||
>
|
||||
下载
|
||||
</button>
|
||||
<span className="blob-id">{file.blobId.slice(0, 16)}...</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// 辅助函数
|
||||
async function uploadFile(
|
||||
file: File,
|
||||
signAndExecuteTransactionBlock: any
|
||||
): Promise<UploadResult> {
|
||||
const arrayBuffer = await file.arrayBuffer();
|
||||
const walrusFile = WalrusFile.from({
|
||||
contents: new Uint8Array(arrayBuffer),
|
||||
identifier: `browser/${Date.now()}_${file.name}`,
|
||||
mimeType: file.type,
|
||||
attributes: {
|
||||
'original-name': file.name,
|
||||
'file-size': file.size.toString(),
|
||||
'upload-source': 'browser'
|
||||
}
|
||||
});
|
||||
|
||||
const suiClient = new SuiClient({
|
||||
url: getFullnodeUrl('testnet'),
|
||||
});
|
||||
|
||||
const walrusClient = new WalrusClient({
|
||||
network: 'testnet',
|
||||
suiClient,
|
||||
});
|
||||
|
||||
const writeFlow = walrusClient.walrus.writeFilesFlow({
|
||||
files: [walrusFile],
|
||||
epochs: 10,
|
||||
deletable: true
|
||||
});
|
||||
|
||||
const result = await writeFlow.executeWithSigner({
|
||||
signAndExecuteTransactionBlock
|
||||
});
|
||||
|
||||
return {
|
||||
success: true,
|
||||
blobId: result[0].blobId,
|
||||
size: file.size
|
||||
};
|
||||
}
|
||||
|
||||
async function downloadFile(blobId: string, filename?: string) {
|
||||
// 实现文件下载逻辑
|
||||
const link = document.createElement('a');
|
||||
link.href = `#download-${blobId}`;
|
||||
link.download = filename || 'downloaded-file';
|
||||
link.click();
|
||||
}
|
||||
|
||||
interface UploadResult {
|
||||
success: boolean;
|
||||
blobId?: string;
|
||||
size?: number;
|
||||
originalName: string;
|
||||
index: number;
|
||||
error?: string;
|
||||
}
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基础代码生成
|
||||
```typescript
|
||||
skill: "walrus-code-generator"
|
||||
// use-case: basic-upload
|
||||
// framework: nodejs
|
||||
// features: ["error-handling", "logging"]
|
||||
```
|
||||
|
||||
### React 应用生成
|
||||
```typescript
|
||||
skill: "walrus-code-generator"
|
||||
// use-case: browser-app
|
||||
// framework: react
|
||||
// features: ["wallet-integration", "drag-drop", "progress-bar"]
|
||||
```
|
||||
|
||||
### 高级存储系统
|
||||
```typescript
|
||||
skill: "walrus-code-generator"
|
||||
// use-case: advanced-storage
|
||||
// framework: nodejs
|
||||
// features: ["batch-operations", "caching", "retry-logic", "metadata-indexing"]
|
||||
```
|
||||
|
||||
### 批量处理系统
|
||||
```typescript
|
||||
skill: "walrus-code-generator"
|
||||
// use-case: batch-processing
|
||||
// framework: express
|
||||
// features: ["api-endpoints", "queue-system", "monitoring"]
|
||||
```
|
||||
|
||||
## 生成的代码特性
|
||||
|
||||
### 🛡️ 错误处理
|
||||
- 智能重试机制
|
||||
- 详细错误信息
|
||||
- 优雅降级策略
|
||||
|
||||
### 📊 进度跟踪
|
||||
- 实时上传进度
|
||||
- 批量操作状态
|
||||
- 性能监控
|
||||
|
||||
### 🔧 配置管理
|
||||
- 灵活的配置选项
|
||||
- 环境适配
|
||||
- 类型安全
|
||||
|
||||
### 🚀 性能优化
|
||||
- 缓存机制
|
||||
- 并发控制
|
||||
- 内存管理
|
||||
|
||||
### 🔒 安全考虑
|
||||
- 输入验证
|
||||
- 文件完整性检查
|
||||
- 访问控制
|
||||
|
||||
## 最佳实践
|
||||
|
||||
1. **错误处理** - 生成的代码包含完整的错误处理逻辑
|
||||
2. **类型安全** - 使用 TypeScript 提供完整的类型定义
|
||||
3. **性能优化** - 包含缓存、批量处理和并发控制
|
||||
4. **可扩展性** - 模块化设计,易于扩展和维护
|
||||
5. **监控日志** - 集成日志记录和性能监控
|
||||
|
||||
---
|
||||
|
||||
*更新时间:2025-11-11*
|
||||
828
skills/walrus-debugger.md
Normal file
828
skills/walrus-debugger.md
Normal file
@@ -0,0 +1,828 @@
|
||||
---
|
||||
name: walrus-debugger
|
||||
description: Walrus 错误诊断专家 - 智能分析和解决 Walrus 应用中的问题
|
||||
parameters:
|
||||
- name: error-type
|
||||
type: string
|
||||
description: 错误类型 (upload/download/network/configuration/timeout/authentication)
|
||||
required: true
|
||||
- name: error-context
|
||||
type: string
|
||||
description: 错误信息、代码片段或问题描述
|
||||
required: true
|
||||
- name: environment
|
||||
type: string
|
||||
description: 运行环境 (browser/nodejs/nextjs/production)
|
||||
default: nodejs
|
||||
---
|
||||
|
||||
# Walrus 错误诊断技能
|
||||
|
||||
## 技能概述
|
||||
|
||||
`walrus-debugger` 是专业的错误诊断助手,专门分析 Walrus 应用中的各种问题,提供智能的解决方案和预防措施。
|
||||
|
||||
## 常见错误类型及解决方案
|
||||
|
||||
### 1. 上传失败错误
|
||||
|
||||
#### 错误类型: Upload Failed
|
||||
```typescript
|
||||
// 错误示例
|
||||
Error: Upload failed: insufficient storage balance
|
||||
Error: Upload failed: blob size exceeds maximum limit
|
||||
Error: Upload failed: transaction rejected
|
||||
|
||||
// 诊断和解决方案
|
||||
class UploadErrorDiagnostics {
|
||||
async diagnoseUploadError(error: Error, context: UploadContext): Promise<Diagnosis> {
|
||||
const analysis = this.analyzeErrorMessage(error.message);
|
||||
|
||||
switch (analysis.type) {
|
||||
case 'INSUFFICIENT_BALANCE':
|
||||
return this.diagnoseBalanceError(context);
|
||||
case 'SIZE_LIMIT':
|
||||
return this.diagnoseSizeError(context);
|
||||
case 'TRANSACTION_REJECTED':
|
||||
return this.diagnoseTransactionError(error, context);
|
||||
case 'TIMEOUT':
|
||||
return this.diagnoseTimeoutError(context);
|
||||
default:
|
||||
return this.diagnoseGenericUploadError(error, context);
|
||||
}
|
||||
}
|
||||
|
||||
private async diagnoseBalanceError(context: UploadContext): Promise<Diagnosis> {
|
||||
const walletBalance = await this.checkWalletBalance(context.signerAddress);
|
||||
|
||||
return {
|
||||
errorType: 'INSUFFICIENT_BALANCE',
|
||||
description: '钱包余额不足,无法支付存储费用',
|
||||
causes: [
|
||||
'WAL 代币余额不足',
|
||||
'SUI 代币余额不足以支付 gas 费',
|
||||
'存储成本计算错误'
|
||||
],
|
||||
solutions: [
|
||||
{
|
||||
title: '检查并充值钱包',
|
||||
code: `
|
||||
// 检查钱包余额
|
||||
async function checkBalances(address: string) {
|
||||
const suiClient = new SuiClient({ url: getFullnodeUrl('testnet') });
|
||||
const balance = await suiClient.getBalance({ owner: address });
|
||||
|
||||
console.log('SUI Balance:', balance.totalBalance);
|
||||
|
||||
// 检查 WAL 余额(如果支持)
|
||||
const walBalance = await suiClient.getBalance({
|
||||
owner: address,
|
||||
coinType: '0x2::sui::SUI' // 替换为 WAL coin type
|
||||
});
|
||||
|
||||
return { sui: balance.totalBalance, wal: walBalance.totalBalance };
|
||||
}`
|
||||
},
|
||||
{
|
||||
title: '优化存储策略',
|
||||
code: `
|
||||
// 使用更短的存储周期
|
||||
const result = await client.walrus.writeFiles({
|
||||
files: [file],
|
||||
epochs: 5, // 减少存储周期
|
||||
deletable: true,
|
||||
signer: keypair
|
||||
});
|
||||
|
||||
// 或者使用更小的文件
|
||||
function compressFile(file: File): Promise<File> {
|
||||
return new Promise((resolve) => {
|
||||
if (file.size > 10 * 1024 * 1024) { // 10MB
|
||||
// 压缩或分割文件
|
||||
compressImage(file).then(resolve);
|
||||
} else {
|
||||
resolve(file);
|
||||
}
|
||||
});
|
||||
}`
|
||||
}
|
||||
],
|
||||
prevention: [
|
||||
'定期检查钱包余额',
|
||||
'设置余额预警',
|
||||
'实施费用估算功能'
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
private async diagnoseSizeError(context: UploadContext): Promise<Diagnosis> {
|
||||
const fileStats = await this.analyzeFileSize(context.file);
|
||||
|
||||
return {
|
||||
errorType: 'SIZE_LIMIT',
|
||||
description: '文件大小超出限制',
|
||||
causes: [
|
||||
`文件大小 ${fileStats.size}MB 超出限制`,
|
||||
'网络环境不支持大文件传输',
|
||||
'客户端内存不足'
|
||||
],
|
||||
solutions: [
|
||||
{
|
||||
title: '文件压缩',
|
||||
code: `
|
||||
// 图片压缩
|
||||
async function compressImage(file: File, quality = 0.8): Promise<File> {
|
||||
return new Promise((resolve) => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
const img = new Image();
|
||||
|
||||
img.onload = () => {
|
||||
// 计算压缩后的尺寸
|
||||
const scale = Math.min(1, 1920 / Math.max(img.width, img.height));
|
||||
canvas.width = img.width * scale;
|
||||
canvas.height = img.height * scale;
|
||||
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
canvas.toBlob((blob) => {
|
||||
resolve(new File([blob!], file.name, { type: file.type }));
|
||||
}, file.type, quality);
|
||||
};
|
||||
|
||||
img.src = URL.createObjectURL(file);
|
||||
});
|
||||
}
|
||||
|
||||
// 文本文件压缩
|
||||
async function compressTextFile(file: File): Promise<File> {
|
||||
const text = await file.text();
|
||||
const compressed = await this.compressString(text);
|
||||
return new File([compressed], file.name + '.gz', { type: 'application/gzip' });
|
||||
}`
|
||||
},
|
||||
{
|
||||
title: '文件分割上传',
|
||||
code: `
|
||||
// 分割大文件
|
||||
async function splitAndUpload(file: File, chunkSize = 5 * 1024 * 1024): Promise<string[]> {
|
||||
const chunks: string[] = [];
|
||||
const totalChunks = Math.ceil(file.size / chunkSize);
|
||||
|
||||
for (let i = 0; i < totalChunks; i++) {
|
||||
const start = i * chunkSize;
|
||||
const end = Math.min(start + chunkSize, file.size);
|
||||
const chunk = file.slice(start, end);
|
||||
|
||||
const chunkFile = new File([chunk], \`\${file.name}.part\${i}\`);
|
||||
const result = await uploadSingleFile(chunkFile);
|
||||
chunks.push(result.blobId);
|
||||
}
|
||||
|
||||
return chunks;
|
||||
}
|
||||
|
||||
// 创建清单文件记录分割信息
|
||||
async function createManifest(chunks: string[], originalFile: File): Promise<string> {
|
||||
const manifest = {
|
||||
originalName: originalFile.name,
|
||||
originalSize: originalFile.size,
|
||||
chunks: chunks.map((blobId, index) => ({
|
||||
blobId,
|
||||
index,
|
||||
size: originalFile.size / chunks.length
|
||||
}))
|
||||
};
|
||||
|
||||
const manifestBlob = new Blob([JSON.stringify(manifest)], { type: 'application/json' });
|
||||
const manifestFile = new File([manifestBlob], \`\${originalFile.name}.manifest\`);
|
||||
|
||||
const result = await uploadSingleFile(manifestFile);
|
||||
return result.blobId;
|
||||
}`
|
||||
}
|
||||
],
|
||||
prevention: [
|
||||
'预先检查文件大小',
|
||||
'实施文件大小限制',
|
||||
'提供文件压缩工具'
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 网络连接错误
|
||||
|
||||
#### 错误类型: Network Error
|
||||
```typescript
|
||||
// 错误示例
|
||||
Error: Network timeout
|
||||
Error: Connection refused
|
||||
Error: Failed to fetch
|
||||
|
||||
class NetworkErrorDiagnostics {
|
||||
async diagnoseNetworkError(error: Error, context: NetworkContext): Promise<Diagnosis> {
|
||||
const networkStatus = await this.checkNetworkStatus();
|
||||
|
||||
return {
|
||||
errorType: 'NETWORK_ERROR',
|
||||
description: '网络连接问题',
|
||||
causes: [
|
||||
'网络连接不稳定',
|
||||
'RPC 节点不可用',
|
||||
'防火墙或代理阻止连接',
|
||||
'DNS 解析失败'
|
||||
],
|
||||
solutions: [
|
||||
{
|
||||
title: '实施自动重试机制',
|
||||
code: `
|
||||
class RobustWalrusClient {
|
||||
private readonly MAX_RETRIES = 3;
|
||||
private readonly RETRY_DELAY_BASE = 1000;
|
||||
|
||||
async withRetry<T>(
|
||||
operation: () => Promise<T>,
|
||||
operationName: string
|
||||
): Promise<T> {
|
||||
for (let attempt = 1; attempt <= this.MAX_RETRIES; attempt++) {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
if (!this.isRetryableError(error) || attempt === this.MAX_RETRIES) {
|
||||
throw new Error(\`\${operationName} failed after \${attempt} attempts: \${error.message}\`);
|
||||
}
|
||||
|
||||
const delay = this.RETRY_DELAY_BASE * Math.pow(2, attempt - 1);
|
||||
console.warn(\`\${operationName} failed (attempt \${attempt}/\${this.MAX_RETRIES}), retrying in \${delay}ms\`);
|
||||
|
||||
await this.sleep(delay);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Should not reach here');
|
||||
}
|
||||
|
||||
private isRetryableError(error: any): boolean {
|
||||
return error instanceof RetryableWalrusClientError ||
|
||||
error.message.includes('timeout') ||
|
||||
error.message.includes('network') ||
|
||||
error.message.includes('connection') ||
|
||||
error.status >= 500; // Server errors
|
||||
}
|
||||
|
||||
private sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
async uploadFiles(files: WalrusFile[]): Promise<any> {
|
||||
return this.withRetry(
|
||||
() => this.client.walrus.writeFiles({ files, epochs: 10, signer: this.signer }),
|
||||
'File upload'
|
||||
);
|
||||
}
|
||||
}`
|
||||
},
|
||||
{
|
||||
title: '实施节点健康检查',
|
||||
code: `
|
||||
// 节点健康检查
|
||||
class NodeHealthChecker {
|
||||
private nodes: string[] = [
|
||||
'https://fullnode.testnet.sui.io',
|
||||
'https://fullnode.testnet.sui.io:443',
|
||||
// 添加备用节点
|
||||
];
|
||||
|
||||
async getHealthyNode(): Promise<string> {
|
||||
for (const node of this.nodes) {
|
||||
try {
|
||||
const response = await fetch(\`\${node}/\`, {
|
||||
method: 'HEAD',
|
||||
timeout: 5000
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
console.log(\`Node \${node} is healthy\`);
|
||||
return node;
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(\`Node \${node} is unhealthy: \${error.message}\`);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('No healthy nodes available');
|
||||
}
|
||||
|
||||
async testAllNodes(): Promise<NodeHealth[]> {
|
||||
const results = await Promise.allSettled(
|
||||
this.nodes.map(async (node) => {
|
||||
const startTime = Date.now();
|
||||
try {
|
||||
const response = await fetch(\`\${node}/\`, { method: 'HEAD', timeout: 5000 });
|
||||
return {
|
||||
node,
|
||||
healthy: response.ok,
|
||||
responseTime: Date.now() - startTime,
|
||||
error: null
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
node,
|
||||
healthy: false,
|
||||
responseTime: Date.now() - startTime,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
return results.map(result =>
|
||||
result.status === 'fulfilled' ? result.value : null
|
||||
).filter(Boolean) as NodeHealth[];
|
||||
}
|
||||
}`
|
||||
},
|
||||
{
|
||||
title: '配置超时和重试',
|
||||
code: `
|
||||
// 配置自定义超时和重试
|
||||
const client = new SuiJsonRpcClient({
|
||||
url: getFullnodeUrl('testnet'),
|
||||
network: 'testnet',
|
||||
}).$extend(walrus({
|
||||
storageNodeClientOptions: {
|
||||
timeout: 60000, // 60秒超时
|
||||
retryAttempts: 5,
|
||||
retryDelay: 2000,
|
||||
onError: (error) => {
|
||||
console.error('Storage node error:', error);
|
||||
// 发送错误到监控系统
|
||||
this.trackError('storage_node_error', error);
|
||||
}
|
||||
}
|
||||
}));`
|
||||
}
|
||||
],
|
||||
prevention: [
|
||||
'实施网络监控',
|
||||
'使用多个备用节点',
|
||||
'设置合理的超时时间',
|
||||
'实施断路器模式'
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
private async checkNetworkStatus(): Promise<NetworkStatus> {
|
||||
try {
|
||||
const response = await fetch('https://httpbin.org/ip');
|
||||
const ipInfo = await response.json();
|
||||
|
||||
return {
|
||||
online: true,
|
||||
ip: ipInfo.ip,
|
||||
latency: Date.now() - this.startTime
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
online: false,
|
||||
error: error.message
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 配置错误
|
||||
|
||||
#### 错误类型: Configuration Error
|
||||
```typescript
|
||||
// 错误示例
|
||||
Error: Invalid network configuration
|
||||
Error: WASM module failed to load
|
||||
Error: Invalid signer configuration
|
||||
|
||||
class ConfigurationErrorDiagnostics {
|
||||
async diagnoseConfigError(error: Error, context: ConfigContext): Promise<Diagnosis> {
|
||||
return {
|
||||
errorType: 'CONFIGURATION_ERROR',
|
||||
description: '配置问题',
|
||||
causes: [
|
||||
'网络配置错误',
|
||||
'WASM 模块路径错误',
|
||||
'签名者配置无效',
|
||||
'环境变量缺失'
|
||||
],
|
||||
solutions: [
|
||||
{
|
||||
title: '验证配置完整性',
|
||||
code: `
|
||||
// 配置验证器
|
||||
class ConfigValidator {
|
||||
static validateWalrusConfig(config: any): ValidationResult {
|
||||
const errors: string[] = [];
|
||||
|
||||
// 验证必需字段
|
||||
if (!config.network) {
|
||||
errors.push('network is required (testnet/mainnet)');
|
||||
}
|
||||
|
||||
if (!config.rpcUrl) {
|
||||
errors.push('rpcUrl is required');
|
||||
}
|
||||
|
||||
// 验证网络配置
|
||||
if (config.network && !['testnet', 'mainnet'].includes(config.network)) {
|
||||
errors.push('network must be either "testnet" or "mainnet"');
|
||||
}
|
||||
|
||||
// 验证 URL 格式
|
||||
if (config.rpcUrl && !this.isValidUrl(config.rpcUrl)) {
|
||||
errors.push('rpcUrl must be a valid URL');
|
||||
}
|
||||
|
||||
// 验证超时配置
|
||||
if (config.timeout && (config.timeout < 1000 || config.timeout > 300000)) {
|
||||
errors.push('timeout must be between 1000ms and 300000ms');
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
static async validateEnvironment(): Promise<ValidationResult> {
|
||||
const errors: string[] = [];
|
||||
|
||||
// 检查环境变量
|
||||
const requiredEnvVars = ['WALRUS_NETWORK', 'SUI_RPC_URL'];
|
||||
for (const envVar of requiredEnvVars) {
|
||||
if (!process.env[envVar]) {
|
||||
errors.push(\`Environment variable \${envVar} is required\`);
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 WASM 支持
|
||||
if (typeof window !== 'undefined' && !window.WebAssembly) {
|
||||
errors.push('WebAssembly is not supported in this browser');
|
||||
}
|
||||
|
||||
// 验证网络连接
|
||||
try {
|
||||
const response = await fetch(process.env.SUI_RPC_URL!, {
|
||||
method: 'HEAD',
|
||||
timeout: 5000
|
||||
});
|
||||
if (!response.ok) {
|
||||
errors.push('Cannot connect to SUI RPC endpoint');
|
||||
}
|
||||
} catch (error) {
|
||||
errors.push(\`Network connection failed: \${error.message}\`);
|
||||
}
|
||||
|
||||
return {
|
||||
valid: errors.length === 0,
|
||||
errors
|
||||
};
|
||||
}
|
||||
|
||||
private static isValidUrl(string: string): boolean {
|
||||
try {
|
||||
new URL(string);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 使用配置验证
|
||||
const config = {
|
||||
network: 'testnet',
|
||||
rpcUrl: getFullnodeUrl('testnet'),
|
||||
timeout: 30000
|
||||
};
|
||||
|
||||
const validation = ConfigValidator.validateWalrusConfig(config);
|
||||
if (!validation.valid) {
|
||||
console.error('Configuration validation failed:', validation.errors);
|
||||
throw new Error('Invalid configuration');
|
||||
}`
|
||||
},
|
||||
{
|
||||
title: 'WASM 模块配置',
|
||||
code: `
|
||||
// WASM 配置验证和加载
|
||||
class WasmLoader {
|
||||
static async loadWasmWithFallback(): Promise<any> {
|
||||
const strategies = [
|
||||
() => this.loadFromBundle(),
|
||||
() => this.loadFromCDN(),
|
||||
() => this.loadFromLocal()
|
||||
];
|
||||
|
||||
for (const strategy of strategies) {
|
||||
try {
|
||||
const wasm = await strategy();
|
||||
console.log('WASM loaded successfully');
|
||||
return wasm;
|
||||
} catch (error) {
|
||||
console.warn('WASM loading strategy failed:', error.message);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('All WASM loading strategies failed');
|
||||
}
|
||||
|
||||
private static async loadFromBundle(): Promise<any> {
|
||||
// Vite 示例
|
||||
if (typeof import.meta !== 'undefined' && import.meta.url) {
|
||||
const wasmUrl = new URL('@mysten/walrus-wasm/web/walrus_wasm_bg.wasm', import.meta.url);
|
||||
const wasmModule = await import(wasmUrl);
|
||||
return wasmModule.default;
|
||||
}
|
||||
throw new Error('Bundle loading not supported');
|
||||
}
|
||||
|
||||
private static async loadFromCDN(): Promise<any> {
|
||||
const cdnUrls = [
|
||||
'https://unpkg.com/@mysten/walrus-wasm@latest/web/walrus_wasm_bg.wasm',
|
||||
'https://cdn.jsdelivr.net/npm/@mysten/walrus-wasm@latest/web/walrus_wasm_bg.wasm'
|
||||
];
|
||||
|
||||
for (const url of cdnUrls) {
|
||||
try {
|
||||
const response = await fetch(url);
|
||||
if (response.ok) {
|
||||
const wasmBuffer = await response.arrayBuffer();
|
||||
return WebAssembly.compile(wasmBuffer);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(\`CDN loading failed for \${url}:\`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('All CDN URLs failed');
|
||||
}
|
||||
|
||||
private static async loadFromLocal(): Promise<any> {
|
||||
// 尝试从本地路径加载
|
||||
const localPaths = [
|
||||
'/walrus/walrus_wasm_bg.wasm',
|
||||
'./node_modules/@mysten/walrus-wasm/web/walrus_wasm_bg.wasm'
|
||||
];
|
||||
|
||||
for (const path of localPaths) {
|
||||
try {
|
||||
const response = await fetch(path);
|
||||
if (response.ok) {
|
||||
const wasmBuffer = await response.arrayBuffer();
|
||||
return WebAssembly.compile(wasmBuffer);
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn(\`Local loading failed for \${path}:\`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Local loading failed');
|
||||
}
|
||||
}
|
||||
|
||||
// 在客户端配置中使用 WASM
|
||||
const client = new SuiJsonRpcClient({
|
||||
url: getFullnodeUrl('testnet'),
|
||||
network: 'testnet',
|
||||
}).$extend(
|
||||
walrus({
|
||||
wasmUrl: await WasmLoader.loadWasmWithFallback()
|
||||
})
|
||||
);`
|
||||
}
|
||||
],
|
||||
prevention: [
|
||||
'实施配置验证',
|
||||
'提供配置模板',
|
||||
'设置默认值',
|
||||
'实施配置监控'
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 浏览器环境特定错误
|
||||
|
||||
#### 错误类型: Browser Environment Error
|
||||
```typescript
|
||||
class BrowserErrorDiagnostics {
|
||||
async diagnoseBrowserError(error: Error, context: BrowserContext): Promise<Diagnosis> {
|
||||
return {
|
||||
errorType: 'BROWSER_ERROR',
|
||||
description: '浏览器环境特定问题',
|
||||
causes: [
|
||||
'WASM 不支持',
|
||||
'CORS 策略阻止',
|
||||
'内存不足',
|
||||
'钱包连接问题'
|
||||
],
|
||||
solutions: [
|
||||
{
|
||||
title: '浏览器兼容性检查',
|
||||
code: `
|
||||
// 浏览器兼容性检查
|
||||
class BrowserCompatibility {
|
||||
static checkCompatibility(): BrowserCompatibilityResult {
|
||||
const checks = {
|
||||
webAssembly: this.checkWebAssembly(),
|
||||
fetch: this.checkFetch(),
|
||||
blob: this.checkBlob(),
|
||||
fileAPI: this.checkFileAPI(),
|
||||
webWorkers: this.checkWebWorkers(),
|
||||
localStorage: this.checkLocalStorage()
|
||||
};
|
||||
|
||||
const supported = Object.values(checks).every(check => check.supported);
|
||||
|
||||
return {
|
||||
supported,
|
||||
checks,
|
||||
recommendations: this.getRecommendations(checks)
|
||||
};
|
||||
}
|
||||
|
||||
private static checkWebAssembly(): CompatibilityCheck {
|
||||
const supported = typeof WebAssembly === 'object' && WebAssembly !== null;
|
||||
return {
|
||||
supported,
|
||||
message: supported ? 'WebAssembly is supported' : 'WebAssembly is not supported',
|
||||
fix: !supported ? 'Please use a modern browser that supports WebAssembly' : null
|
||||
};
|
||||
}
|
||||
|
||||
private static getRecommendations(checks: Record<string, CompatibilityCheck>): string[] {
|
||||
const recommendations: string[] = [];
|
||||
|
||||
if (!checks.webAssembly.supported) {
|
||||
recommendations.push('Upgrade to a modern browser (Chrome 57+, Firefox 52+, Safari 11+)');
|
||||
}
|
||||
|
||||
if (!checks.webWorkers.supported) {
|
||||
recommendations.push('Consider enabling Web Workers for better performance');
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
}
|
||||
|
||||
// 使用兼容性检查
|
||||
const compatibility = BrowserCompatibility.checkCompatibility();
|
||||
if (!compatibility.supported) {
|
||||
console.error('Browser compatibility issues:', compatibility.recommendations);
|
||||
// 显示用户友好的错误信息
|
||||
this.showCompatibilityError(compatibility);
|
||||
}`
|
||||
},
|
||||
{
|
||||
title: '内存管理',
|
||||
code: `
|
||||
// 浏览器内存管理
|
||||
class BrowserMemoryManager {
|
||||
private memoryThreshold = 100 * 1024 * 1024; // 100MB
|
||||
|
||||
async checkMemoryUsage(): Promise<MemoryUsage> {
|
||||
if ('memory' in performance) {
|
||||
const memory = (performance as any).memory;
|
||||
return {
|
||||
used: memory.usedJSHeapSize,
|
||||
total: memory.totalJSHeapSize,
|
||||
limit: memory.jsHeapSizeLimit,
|
||||
usageRatio: memory.usedJSHeapSize / memory.jsHeapSizeLimit
|
||||
};
|
||||
}
|
||||
return { used: 0, total: 0, limit: 0, usageRatio: 0 };
|
||||
}
|
||||
|
||||
async manageMemoryForLargeFile(file: File): Promise<boolean> {
|
||||
const memoryUsage = await this.checkMemoryUsage();
|
||||
|
||||
// 检查是否有足够内存处理文件
|
||||
const estimatedUsage = file.size * 3; // 估算处理过程中需要的内存
|
||||
const availableMemory = memoryUsage.limit - memoryUsage.used;
|
||||
|
||||
if (estimatedUsage > availableMemory) {
|
||||
console.warn('Insufficient memory for file processing');
|
||||
|
||||
// 触发垃圾回收(如果可能)
|
||||
if (window.gc) {
|
||||
window.gc();
|
||||
}
|
||||
|
||||
// 重新检查内存
|
||||
const newUsage = await this.checkMemoryUsage();
|
||||
return (file.size * 3) < (newUsage.limit - newUsage.used);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// 流式处理大文件
|
||||
async processLargeFileInChunks(
|
||||
file: File,
|
||||
processor: (chunk: Uint8Array) => Promise<void>,
|
||||
chunkSize = 1024 * 1024 // 1MB
|
||||
): Promise<void> {
|
||||
const reader = new FileReader();
|
||||
let offset = 0;
|
||||
|
||||
const processChunk = () => {
|
||||
if (offset >= file.size) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const chunk = file.slice(offset, offset + chunkSize);
|
||||
|
||||
reader.onload = async (event) => {
|
||||
try {
|
||||
if (event.target?.result) {
|
||||
const arrayBuffer = event.target.result as ArrayBuffer;
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
|
||||
await processor(uint8Array);
|
||||
offset += chunkSize;
|
||||
|
||||
// 检查内存使用
|
||||
const memoryUsage = await this.checkMemoryUsage();
|
||||
if (memoryUsage.usageRatio > 0.8) {
|
||||
console.warn('High memory usage, forcing garbage collection');
|
||||
if (window.gc) window.gc();
|
||||
}
|
||||
|
||||
await processChunk();
|
||||
}
|
||||
resolve();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
};
|
||||
|
||||
reader.onerror = () => reject(new Error('File reading failed'));
|
||||
reader.readAsArrayBuffer(chunk);
|
||||
});
|
||||
};
|
||||
|
||||
return processChunk();
|
||||
}
|
||||
}`
|
||||
}
|
||||
],
|
||||
prevention: [
|
||||
'实施浏览器兼容性检查',
|
||||
'监控内存使用',
|
||||
'提供降级方案',
|
||||
'实施优雅错误处理'
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 上传错误诊断
|
||||
```typescript
|
||||
skill: "walrus-debugger"
|
||||
// error-type: upload
|
||||
// error-context: Upload failed: insufficient storage balance, trying to upload 10MB file to testnet
|
||||
// environment: browser
|
||||
```
|
||||
|
||||
### 网络连接问题
|
||||
```typescript
|
||||
skill: "walrus-debugger"
|
||||
// error-type: network
|
||||
// error-context: Network timeout when connecting to storage nodes, intermittent connection issues
|
||||
// environment: production
|
||||
```
|
||||
|
||||
### 配置验证
|
||||
```typescript
|
||||
skill: "walrus-debugger"
|
||||
// error-type: configuration
|
||||
// error-context: WASM module failed to load in Vite production build, error: "Failed to resolve module"
|
||||
// environment: nodejs
|
||||
```
|
||||
|
||||
### 性能问题
|
||||
```typescript
|
||||
skill: "walrus-debugger"
|
||||
// error-type: performance
|
||||
// error-context: Memory usage spikes when uploading multiple large files, browser crashes
|
||||
// environment: browser
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*更新时间:2025-11-11*
|
||||
757
skills/walrus-monitor.md
Normal file
757
skills/walrus-monitor.md
Normal file
@@ -0,0 +1,757 @@
|
||||
---
|
||||
name: walrus-monitor
|
||||
description: Walrus 监控分析专家 - 存储使用监控、成本分析和性能指标跟踪
|
||||
parameters:
|
||||
- name: monitor-type
|
||||
type: string
|
||||
description: 监控类型 (usage/cost/performance/errors/analytics)
|
||||
required: true
|
||||
- name: time-range
|
||||
type: string
|
||||
description: 时间范围 (1h/24h/7d/30d/custom)
|
||||
default: 24h
|
||||
- name: metrics
|
||||
type: array
|
||||
description: 需要监控的具体指标
|
||||
default: []
|
||||
---
|
||||
|
||||
# Walrus 监控分析技能
|
||||
|
||||
## 技能概述
|
||||
|
||||
`walrus-monitor` 是专业的监控分析助手,提供全面的 Walrus 存储系统监控、成本分析和性能指标跟踪功能,帮助开发者优化资源使用和控制成本。
|
||||
|
||||
## 监控维度
|
||||
|
||||
### 1. 存储使用监控
|
||||
|
||||
#### 实时存储指标
|
||||
```typescript
|
||||
class StorageMonitor {
|
||||
private metrics: StorageMetrics[] = [];
|
||||
private alerts: Alert[] = [];
|
||||
|
||||
async collectStorageMetrics(): Promise<StorageMetrics> {
|
||||
const client = await this.getWalrusClient();
|
||||
const accountAddress = this.getAccountAddress();
|
||||
|
||||
// 获取账户存储信息
|
||||
const storageInfo = await client.getStorageInfo(accountAddress);
|
||||
const blobIds = storageInfo.storedBlobs || [];
|
||||
|
||||
// 计算存储统计
|
||||
const metrics: StorageMetrics = {
|
||||
timestamp: Date.now(),
|
||||
totalFiles: blobIds.length,
|
||||
totalSize: 0,
|
||||
totalCost: 0,
|
||||
storageDistribution: {},
|
||||
fileAges: [],
|
||||
oldestFile: null,
|
||||
newestFile: null,
|
||||
expiringSoon: [],
|
||||
expired: []
|
||||
};
|
||||
|
||||
// 分析每个文件
|
||||
for (const blobId of blobIds) {
|
||||
try {
|
||||
const [file] = await client.walrus.getFiles({ ids: [blobId] });
|
||||
const metadata = await this.getFileMetadata(file);
|
||||
const storedUntil = await file.storedUntil();
|
||||
|
||||
metrics.totalSize += metadata.size;
|
||||
metrics.totalCost += metadata.estimatedCost;
|
||||
|
||||
// 文件类型分布
|
||||
const fileType = this.getFileType(metadata.mimeType);
|
||||
metrics.storageDistribution[fileType] =
|
||||
(metrics.storageDistribution[fileType] || 0) + 1;
|
||||
|
||||
// 文件年龄分析
|
||||
const age = Date.now() - metadata.uploadTime;
|
||||
metrics.fileAges.push(age);
|
||||
|
||||
// 检查过期状态
|
||||
const daysToExpiry = Math.floor((storedUntil.getTime() - Date.now()) / (1000 * 60 * 60 * 24));
|
||||
if (daysToExpiry < 7) {
|
||||
metrics.expiringSoon.push({
|
||||
blobId,
|
||||
fileName: metadata.identifier,
|
||||
daysToExpiry,
|
||||
size: metadata.size
|
||||
});
|
||||
}
|
||||
|
||||
if (daysToExpiry < 0) {
|
||||
metrics.expired.push({
|
||||
blobId,
|
||||
fileName: metadata.identifier,
|
||||
daysExpired: Math.abs(daysToExpiry),
|
||||
size: metadata.size
|
||||
});
|
||||
}
|
||||
|
||||
// 更新最新和最旧文件
|
||||
if (!metrics.oldestFile || age > metrics.oldestFile.age) {
|
||||
metrics.oldestFile = {
|
||||
blobId,
|
||||
fileName: metadata.identifier,
|
||||
age,
|
||||
uploadDate: new Date(metadata.uploadTime)
|
||||
};
|
||||
}
|
||||
|
||||
if (!metrics.newestFile || age < metrics.newestFile.age) {
|
||||
metrics.newestFile = {
|
||||
blobId,
|
||||
fileName: metadata.identifier,
|
||||
age,
|
||||
uploadDate: new Date(metadata.uploadTime)
|
||||
};
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`Failed to analyze blob ${blobId}:`, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// 存储指标历史
|
||||
this.metrics.push(metrics);
|
||||
if (this.metrics.length > 1000) {
|
||||
this.metrics.shift(); // 保留最近1000条记录
|
||||
}
|
||||
|
||||
// 检查告警条件
|
||||
this.checkStorageAlerts(metrics);
|
||||
|
||||
return metrics;
|
||||
}
|
||||
|
||||
private checkStorageAlerts(metrics: StorageMetrics): void {
|
||||
// 存储空间告警
|
||||
if (metrics.totalSize > 100 * 1024 * 1024 * 1024) { // 100GB
|
||||
this.createAlert({
|
||||
type: 'STORAGE_LIMIT',
|
||||
severity: 'WARNING',
|
||||
message: `存储使用量达到 ${this.formatBytes(metrics.totalSize)}`,
|
||||
recommendation: '考虑清理过期文件或升级存储计划'
|
||||
});
|
||||
}
|
||||
|
||||
// 文件数量告警
|
||||
if (metrics.totalFiles > 10000) {
|
||||
this.createAlert({
|
||||
type: 'FILE_COUNT_LIMIT',
|
||||
severity: 'INFO',
|
||||
message: `文件数量达到 ${metrics.totalFiles}`,
|
||||
recommendation: '考虑实施文件归档策略'
|
||||
});
|
||||
}
|
||||
|
||||
// 即将过期告警
|
||||
if (metrics.expiringSoon.length > 0) {
|
||||
this.createAlert({
|
||||
type: 'EXPIRING_FILES',
|
||||
severity: 'WARNING',
|
||||
message: `${metrics.expiringSoon.length} 个文件将在7天内过期`,
|
||||
recommendation: '续期或备份重要文件'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getStorageTrends(timeRange: number = 7 * 24 * 60 * 60 * 1000): StorageTrends {
|
||||
const cutoff = Date.now() - timeRange;
|
||||
const relevantMetrics = this.metrics.filter(m => m.timestamp >= cutoff);
|
||||
|
||||
if (relevantMetrics.length < 2) {
|
||||
return {
|
||||
growthRate: 0,
|
||||
costGrowthRate: 0,
|
||||
fileGrowthRate: 0,
|
||||
projectedUsage: null,
|
||||
projectedCost: null
|
||||
};
|
||||
}
|
||||
|
||||
const oldest = relevantMetrics[0];
|
||||
const newest = relevantMetrics[relevantMetrics.length - 1];
|
||||
const timeDiff = newest.timestamp - oldest.timestamp;
|
||||
|
||||
return {
|
||||
growthRate: this.calculateGrowthRate(oldest.totalSize, newest.totalSize, timeDiff),
|
||||
costGrowthRate: this.calculateGrowthRate(oldest.totalCost, newest.totalCost, timeDiff),
|
||||
fileGrowthRate: this.calculateGrowthRate(oldest.totalFiles, newest.totalFiles, timeDiff),
|
||||
projectedUsage: this.projectUsage(newest.totalSize, this.calculateGrowthRate(oldest.totalSize, newest.totalSize, timeDiff)),
|
||||
projectedCost: this.projectCost(newest.totalCost, this.calculateGrowthRate(oldest.totalCost, newest.totalCost, timeDiff))
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 成本分析
|
||||
|
||||
#### 成本监控和优化
|
||||
```typescript
|
||||
class CostAnalyzer {
|
||||
private costHistory: CostRecord[] = [];
|
||||
private pricingModel: PricingModel;
|
||||
|
||||
constructor() {
|
||||
this.pricingModel = {
|
||||
storagePerGBPerEpoch: 0.001,
|
||||
writeFeePerMB: 0.0001,
|
||||
readFeePerMB: 0.00001,
|
||||
gasMultiplier: 1.0
|
||||
};
|
||||
}
|
||||
|
||||
async analyzeCosts(timeRange: number = 30 * 24 * 60 * 60 * 1000): Promise<CostAnalysis> {
|
||||
const records = this.getRelevantCostRecords(timeRange);
|
||||
const storageMetrics = await this.getStorageMetrics(timeRange);
|
||||
|
||||
return {
|
||||
totalCost: this.calculateTotalCost(records),
|
||||
storageCost: this.calculateStorageCost(storageMetrics),
|
||||
transactionCost: this.calculateTransactionCost(records),
|
||||
breakdown: this.getCostBreakdown(records),
|
||||
optimization: this.getOptimizationSuggestions(storageMetrics, records),
|
||||
forecast: this.forecastCosts(records, storageMetrics)
|
||||
};
|
||||
}
|
||||
|
||||
private calculateStorageCost(metrics: StorageMetrics[]): number {
|
||||
return metrics.reduce((total, metric) => {
|
||||
const storageEpochs = 30; // 假设30天的轮数
|
||||
const sizeGB = metric.totalSize / (1024 * 1024 * 1024);
|
||||
return total + (sizeGB * this.pricingModel.storagePerGBPerEpoch * storageEpochs);
|
||||
}, 0);
|
||||
}
|
||||
|
||||
private calculateTransactionCost(records: CostRecord[]): number {
|
||||
return records
|
||||
.filter(r => r.type === 'transaction')
|
||||
.reduce((total, record) => total + record.amount, 0);
|
||||
}
|
||||
|
||||
private getOptimizationSuggestions(metrics: StorageMetrics[], records: CostRecord[]): OptimizationSuggestion[] {
|
||||
const suggestions: OptimizationSuggestion[] = [];
|
||||
|
||||
// 分析文件大小分布
|
||||
const sizeDistribution = this.analyzeSizeDistribution(metrics);
|
||||
if (sizeDistribution.smallFiles > sizeDistribution.largeFiles * 2) {
|
||||
suggestions.push({
|
||||
type: 'BUNDLE_SMALL_FILES',
|
||||
description: '检测到大量小文件,建议使用 Quilt 打包存储',
|
||||
potentialSavings: '20-30%',
|
||||
difficulty: 'medium',
|
||||
implementation: this.getBundleImplementation()
|
||||
});
|
||||
}
|
||||
|
||||
// 分析存储时长
|
||||
const avgStorageTime = this.calculateAverageStorageTime(metrics);
|
||||
if (avgStorageTime > 90) { // 超过90天
|
||||
suggestions.push({
|
||||
type: 'OPTIMIZE_STORAGE_DURATION',
|
||||
description: `平均存储时长 ${avgStorageTime} 天,建议实施生命周期管理`,
|
||||
potentialSavings: '40-60%',
|
||||
difficulty: 'low',
|
||||
implementation: this.getLifecycleImplementation()
|
||||
});
|
||||
}
|
||||
|
||||
// 分析访问模式
|
||||
const accessPattern = this.analyzeAccessPattern(records);
|
||||
if (accessPattern.readHeavy) {
|
||||
suggestions.push({
|
||||
type: 'IMPLEMENT_CACHING',
|
||||
description: '读取频繁,建议实施缓存层',
|
||||
potentialSavings: '50-70%',
|
||||
difficulty: 'medium',
|
||||
implementation: this.getCacheImplementation()
|
||||
});
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
private getBundleImplementation(): string {
|
||||
return `
|
||||
// 使用 Quilt 打包小文件
|
||||
async function bundleSmallFiles(files: WalrusFile[]): Promise<string> {
|
||||
const quilt = QuiltBuilder.create()
|
||||
.addFiles(files)
|
||||
.build();
|
||||
|
||||
const result = await client.walrus.writeFiles({
|
||||
files: [quilt],
|
||||
epochs: 30,
|
||||
deletable: true,
|
||||
signer: keypair
|
||||
});
|
||||
|
||||
return result[0].blobId;
|
||||
}
|
||||
|
||||
// 原本多个小文件:
|
||||
// File 1: 10KB, File 2: 15KB, File 3: 8KB = 33KB 总计
|
||||
// 打包后:单个 Quilt 包含所有文件,减少存储开销
|
||||
`;
|
||||
}
|
||||
|
||||
private getLifecycleImplementation(): string {
|
||||
return `
|
||||
// 自动生命周期管理
|
||||
class StorageLifecycleManager {
|
||||
async manageExpiredFiles() {
|
||||
const expiringFiles = await this.getFilesExpiringWithin(7);
|
||||
|
||||
for (const file of expiringFiles) {
|
||||
const accessFrequency = await this.getAccessFrequency(file.blobId);
|
||||
|
||||
if (accessFrequency === 'never') {
|
||||
// 删除未访问的文件
|
||||
await this.deleteFile(file.blobId);
|
||||
} else if (accessFrequency === 'rare') {
|
||||
// 降低存储时长
|
||||
await this.reduceStorageEpochs(file.blobId, 10);
|
||||
} else {
|
||||
// 续期重要文件
|
||||
await this.extendStorage(file.blobId, 30);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async getAccessFrequency(blobId: string): Promise<'never' | 'rare' | 'frequent'> {
|
||||
// 分析访问日志
|
||||
const accessLogs = await this.getAccessLogs(blobId, 30); // 30天内
|
||||
const accessCount = accessLogs.length;
|
||||
|
||||
if (accessCount === 0) return 'never';
|
||||
if (accessCount < 5) return 'rare';
|
||||
return 'frequent';
|
||||
}
|
||||
}
|
||||
`;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 性能监控
|
||||
|
||||
#### 实时性能指标
|
||||
```typescript
|
||||
class PerformanceMonitor {
|
||||
private performanceMetrics: PerformanceMetric[] = [];
|
||||
private slowQueries: SlowQuery[] = [];
|
||||
private alerts: PerformanceAlert[] = [];
|
||||
|
||||
async recordPerformanceMetric(
|
||||
operation: string,
|
||||
duration: number,
|
||||
success: boolean,
|
||||
metadata: any = {}
|
||||
): Promise<void> {
|
||||
const metric: PerformanceMetric = {
|
||||
timestamp: Date.now(),
|
||||
operation,
|
||||
duration,
|
||||
success,
|
||||
metadata
|
||||
};
|
||||
|
||||
this.performanceMetrics.push(metric);
|
||||
|
||||
// 限制历史数据
|
||||
if (this.performanceMetrics.length > 10000) {
|
||||
this.performanceMetrics.shift();
|
||||
}
|
||||
|
||||
// 检查性能告警
|
||||
this.checkPerformanceAlerts(metric);
|
||||
|
||||
// 记录慢操作
|
||||
if (duration > this.getSlowThreshold(operation)) {
|
||||
this.slowQueries.push({
|
||||
...metric,
|
||||
threshold: this.getSlowThreshold(operation),
|
||||
severity: this.getSeverityLevel(duration, this.getSlowThreshold(operation))
|
||||
});
|
||||
|
||||
// 限制慢查询记录
|
||||
if (this.slowQueries.length > 1000) {
|
||||
this.slowQueries.shift();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getPerformanceAnalysis(timeRange: number = 24 * 60 * 60 * 1000): PerformanceAnalysis {
|
||||
const metrics = this.getRelevantMetrics(timeRange);
|
||||
|
||||
return {
|
||||
overview: this.getPerformanceOverview(metrics),
|
||||
operations: this.analyzeOperations(metrics),
|
||||
trends: this.analyzeTrends(metrics),
|
||||
bottlenecks: this.identifyBottlenecks(metrics),
|
||||
recommendations: this.getPerformanceRecommendations(metrics)
|
||||
};
|
||||
}
|
||||
|
||||
private analyzeOperations(metrics: PerformanceMetric[]): OperationAnalysis[] {
|
||||
const operationGroups = this.groupBy(metrics, 'operation');
|
||||
const analyses: OperationAnalysis[] = [];
|
||||
|
||||
for (const [operation, operationMetrics] of Object.entries(operationGroups)) {
|
||||
const durations = operationMetrics.map(m => m.duration);
|
||||
const successRate = operationMetrics.filter(m => m.success).length / operationMetrics.length;
|
||||
|
||||
analyses.push({
|
||||
operation,
|
||||
totalCalls: operationMetrics.length,
|
||||
successRate,
|
||||
avgDuration: durations.reduce((a, b) => a + b, 0) / durations.length,
|
||||
minDuration: Math.min(...durations),
|
||||
maxDuration: Math.max(...durations),
|
||||
p95: this.percentile(durations, 0.95),
|
||||
p99: this.percentile(durations, 0.99),
|
||||
errorRate: 1 - successRate,
|
||||
slowQueries: operationMetrics.filter(m => m.duration > this.getSlowThreshold(operation)).length
|
||||
});
|
||||
}
|
||||
|
||||
return analyses.sort((a, b) => b.avgDuration - a.avgDuration);
|
||||
}
|
||||
|
||||
private identifyBottlenecks(metrics: PerformanceMetric[]): PerformanceBottleneck[] {
|
||||
const bottlenecks: PerformanceBottleneck[] = [];
|
||||
|
||||
// 识别慢操作
|
||||
const slowOperations = this.analyzeOperations(metrics)
|
||||
.filter(op => op.avgDuration > this.getSlowThreshold(op.operation));
|
||||
|
||||
for (const operation of slowOperations) {
|
||||
bottlenecks.push({
|
||||
type: 'SLOW_OPERATION',
|
||||
operation: operation.operation,
|
||||
severity: this.getBottleneckSeverity(operation.avgDuration, this.getSlowThreshold(operation.operation)),
|
||||
impact: 'medium',
|
||||
description: `\${operation.operation} 平均耗时 \${operation.avgDuration}ms`,
|
||||
suggestion: this.getOptimizationSuggestion(operation.operation)
|
||||
});
|
||||
}
|
||||
|
||||
// 识别错误率高的操作
|
||||
const highErrorOps = this.analyzeOperations(metrics)
|
||||
.filter(op => op.errorRate > 0.05); // 5% 错误率
|
||||
|
||||
for (const operation of highErrorOps) {
|
||||
bottlenecks.push({
|
||||
type: 'HIGH_ERROR_RATE',
|
||||
operation: operation.operation,
|
||||
severity: 'high',
|
||||
impact: 'high',
|
||||
description: `\${operation.operation} 错误率 \${(operation.errorRate * 100).toFixed(1)}%`,
|
||||
suggestion: this.getErrorOptimizationSuggestion(operation.operation)
|
||||
});
|
||||
}
|
||||
|
||||
return bottlenecks;
|
||||
}
|
||||
|
||||
private getPerformanceRecommendations(metrics: PerformanceMetric[]): PerformanceRecommendation[] {
|
||||
const recommendations: PerformanceRecommendation[] = [];
|
||||
|
||||
// 分析上传性能
|
||||
const uploadMetrics = metrics.filter(m => m.operation.startsWith('upload'));
|
||||
if (uploadMetrics.length > 0) {
|
||||
const avgUploadSize = this.getAverageUploadSize(uploadMetrics);
|
||||
const avgUploadTime = uploadMetrics.reduce((sum, m) => sum + m.duration, 0) / uploadMetrics.length;
|
||||
|
||||
if (avgUploadSize > 10 * 1024 * 1024 && avgUploadTime > 10000) { // 10MB, 10s
|
||||
recommendations.push({
|
||||
type: 'UPLOAD_OPTIMIZATION',
|
||||
priority: 'high',
|
||||
title: '大文件上传优化',
|
||||
description: '检测到大文件上传性能问题',
|
||||
implementation: `
|
||||
// 实施分块上传和断点续传
|
||||
class ChunkedUploader {
|
||||
async uploadLargeFile(file: File, chunkSize = 5 * 1024 * 1024): Promise<string> {
|
||||
const chunks = Math.ceil(file.size / chunkSize);
|
||||
const chunkIds: string[] = [];
|
||||
|
||||
for (let i = 0; i < chunks; i++) {
|
||||
const start = i * chunkSize;
|
||||
const end = Math.min(start + chunkSize, file.size);
|
||||
const chunk = file.slice(start, end);
|
||||
|
||||
const result = await this.uploadChunk(chunk, i, chunks);
|
||||
chunkIds.push(result.blobId);
|
||||
|
||||
// 更新进度
|
||||
this.updateProgress((i + 1) / chunks);
|
||||
}
|
||||
|
||||
// 创建文件清单
|
||||
return await this.createManifest(chunkIds, file);
|
||||
}
|
||||
}
|
||||
|
||||
// 使用压缩减少传输量
|
||||
const compressedFile = await this.compressFile(file);
|
||||
const uploadResult = await client.walrus.writeFiles({
|
||||
files: [compressedFile],
|
||||
epochs: 10,
|
||||
signer: keypair
|
||||
});
|
||||
`,
|
||||
expectedImprovement: '50-70%'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 分析批量操作性能
|
||||
const batchMetrics = metrics.filter(m => m.operation.includes('batch'));
|
||||
if (batchMetrics.length > 0) {
|
||||
const concurrencyIssues = batchMetrics.filter(m => m.duration > 60000).length; // 超过1分钟
|
||||
|
||||
if (concurrencyIssues > batchMetrics.length * 0.3) { // 30%超时
|
||||
recommendations.push({
|
||||
type: 'BATCH_OPTIMIZATION',
|
||||
priority: 'medium',
|
||||
title: '批量操作并发优化',
|
||||
description: '批量操作存在并发瓶颈',
|
||||
implementation: `
|
||||
// 优化并发控制
|
||||
class OptimizedBatchProcessor {
|
||||
private readonly MAX_CONCURRENT = 3;
|
||||
private readonly RETRY_DELAY = 1000;
|
||||
|
||||
async processBatch<T>(items: T[], processor: (item: T) => Promise<any>): Promise<any[]> {
|
||||
const results: any[] = [];
|
||||
const chunks = this.chunkArray(items, this.MAX_CONCURRENT);
|
||||
|
||||
for (const chunk of chunks) {
|
||||
const chunkPromises = chunk.map(async (item, index) => {
|
||||
try {
|
||||
return await this.withRetry(() => processor(item), 3);
|
||||
} catch (error) {
|
||||
console.error(\`Batch item failed: \${error.message}\`);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
const chunkResults = await Promise.all(chunkPromises);
|
||||
results.push(...chunkResults);
|
||||
|
||||
// 批次间延迟
|
||||
if (chunks.indexOf(chunk) < chunks.length - 1) {
|
||||
await this.delay(this.RETRY_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
`,
|
||||
expectedImprovement: '30-50%'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 错误监控
|
||||
|
||||
#### 错误统计和分析
|
||||
```typescript
|
||||
class ErrorMonitor {
|
||||
private errorLog: ErrorRecord[] = [];
|
||||
private errorPatterns: ErrorPattern[] = [];
|
||||
|
||||
recordError(error: Error, context: ErrorContext): void {
|
||||
const errorRecord: ErrorRecord = {
|
||||
timestamp: Date.now(),
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
type: this.classifyError(error),
|
||||
severity: this.determineSeverity(error, context),
|
||||
context,
|
||||
resolved: false
|
||||
};
|
||||
|
||||
this.errorLog.push(errorRecord);
|
||||
|
||||
// 分析错误模式
|
||||
this.analyzeErrorPattern(errorRecord);
|
||||
|
||||
// 错误告警
|
||||
this.checkErrorAlerts(errorRecord);
|
||||
}
|
||||
|
||||
getErrorAnalysis(timeRange: number = 24 * 60 * 60 * 1000): ErrorAnalysis {
|
||||
const recentErrors = this.getRecentErrors(timeRange);
|
||||
|
||||
return {
|
||||
summary: this.getErrorSummary(recentErrors),
|
||||
trends: this.getErrorTrends(recentErrors),
|
||||
patterns: this.getErrorPatterns(recentErrors),
|
||||
recommendations: this.getErrorRecommendations(recentErrors),
|
||||
resolutionRate: this.calculateResolutionRate(recentErrors)
|
||||
};
|
||||
}
|
||||
|
||||
private analyzeErrorPattern(errorRecord: ErrorRecord): void {
|
||||
const patternKey = this.getPatternKey(errorRecord);
|
||||
let pattern = this.errorPatterns.find(p => p.key === patternKey);
|
||||
|
||||
if (!pattern) {
|
||||
pattern = {
|
||||
key: patternKey,
|
||||
type: errorRecord.type,
|
||||
count: 0,
|
||||
firstOccurrence: errorRecord.timestamp,
|
||||
lastOccurrence: errorRecord.timestamp,
|
||||
contexts: [],
|
||||
resolutions: []
|
||||
};
|
||||
this.errorPatterns.push(pattern);
|
||||
}
|
||||
|
||||
pattern.count++;
|
||||
pattern.lastOccurrence = errorRecord.timestamp;
|
||||
|
||||
// 记录上下文
|
||||
const contextHash = this.hashContext(errorRecord.context);
|
||||
if (!pattern.contexts.some(c => c.hash === contextHash)) {
|
||||
pattern.contexts.push({
|
||||
hash: contextHash,
|
||||
context: errorRecord.context,
|
||||
occurrences: 1
|
||||
});
|
||||
} else {
|
||||
const existingContext = pattern.contexts.find(c => c.hash === contextHash);
|
||||
if (existingContext) {
|
||||
existingContext.occurrences++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private getErrorRecommendations(errors: ErrorRecord[]): ErrorRecommendation[] {
|
||||
const recommendations: ErrorRecommendation[] = [];
|
||||
|
||||
// 分析最常见的错误类型
|
||||
const errorTypes = this.groupBy(errors, 'type');
|
||||
const mostCommonType = Object.entries(errorTypes)
|
||||
.sort(([, a], [, b]) => b.length - a.length)[0];
|
||||
|
||||
if (mostCommonType && mostCommonType[1].length > errors.length * 0.3) { // 30%以上
|
||||
recommendations.push({
|
||||
type: 'PREVENTION',
|
||||
priority: 'high',
|
||||
title: `高频率 \${mostCommonType[0]} 错误`,
|
||||
description: `\${mostCommonType[1].length} 次错误中 \${mostCommonType[1].length} 次是 \${mostCommonType[0]} 错误`,
|
||||
implementation: this.getPreventionImplementation(mostCommonType[0]),
|
||||
expectedReduction: '60-80%'
|
||||
});
|
||||
}
|
||||
|
||||
// 分析网络相关错误
|
||||
const networkErrors = errors.filter(e => e.type === 'network' || e.type === 'timeout');
|
||||
if (networkErrors.length > errors.length * 0.2) { // 20%以上
|
||||
recommendations.push({
|
||||
type: 'NETWORK_OPTIMIZATION',
|
||||
priority: 'medium',
|
||||
title: '网络连接优化',
|
||||
description: '检测到大量网络相关错误',
|
||||
implementation: `
|
||||
// 实施网络优化策略
|
||||
class NetworkOptimizer {
|
||||
private readonly circuitBreaker = new CircuitBreaker({
|
||||
failureThreshold: 5,
|
||||
recoveryTimeout: 60000,
|
||||
monitoringPeriod: 30000
|
||||
});
|
||||
|
||||
async robustRequest(url: string, options: RequestInit = {}): Promise<Response> {
|
||||
return this.circuitBreaker.execute(async () => {
|
||||
// 使用指数退避重试
|
||||
return this.withRetry(() => fetch(url, {
|
||||
...options,
|
||||
timeout: 30000
|
||||
}), 3);
|
||||
});
|
||||
}
|
||||
|
||||
private async withRetry<T>(
|
||||
operation: () => Promise<T>,
|
||||
maxRetries: number = 3
|
||||
): Promise<T> {
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
return await operation();
|
||||
} catch (error) {
|
||||
if (attempt === maxRetries || !this.isRetryableError(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
|
||||
await this.delay(delay);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error('Max retries exceeded');
|
||||
}
|
||||
}
|
||||
`,
|
||||
expectedReduction: '70-90%'
|
||||
});
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 存储使用监控
|
||||
```typescript
|
||||
skill: "walrus-monitor"
|
||||
// monitor-type: usage
|
||||
// time-range: 7d
|
||||
// metrics: ["total-size", "file-count", "cost", "aging"]
|
||||
```
|
||||
|
||||
### 成本分析
|
||||
```typescript
|
||||
skill: "walrus-monitor"
|
||||
// monitor-type: cost
|
||||
// time-range: 30d
|
||||
// metrics: ["storage-cost", "transaction-cost", "optimization-suggestions"]
|
||||
```
|
||||
|
||||
### 性能监控
|
||||
```typescript
|
||||
skill: "walrus-monitor"
|
||||
// monitor-type: performance
|
||||
// time-range: 24h
|
||||
// metrics: ["upload-speed", "download-speed", "error-rate", "bottlenecks"]
|
||||
```
|
||||
|
||||
### 错误分析
|
||||
```typescript
|
||||
skill: "walrus-monitor"
|
||||
// monitor-type: errors
|
||||
// time-range: 7d
|
||||
// metrics: ["error-patterns", "resolution-rate", "prevention-suggestions"]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*更新时间:2025-11-11*
|
||||
761
skills/walrus-performance-optimizer.md
Normal file
761
skills/walrus-performance-optimizer.md
Normal file
@@ -0,0 +1,761 @@
|
||||
---
|
||||
name: walrus-performance-optimizer
|
||||
description: Walrus 性能优化专家 - 分析和优化 Walrus 应用的性能瓶颈
|
||||
parameters:
|
||||
- name: analysis-type
|
||||
type: string
|
||||
description: 分析类型 (upload/download/batch/memory/network)
|
||||
required: true
|
||||
- name: target-metric
|
||||
type: string
|
||||
description: 目标指标 (speed/throughput/cost/latency)
|
||||
default: speed
|
||||
- name: code-context
|
||||
type: string
|
||||
description: 代码上下文或问题描述
|
||||
required: true
|
||||
---
|
||||
|
||||
# Walrus 性能优化技能
|
||||
|
||||
## 技能概述
|
||||
|
||||
`walrus-performance-optimizer` 是专业的性能优化助手,专门分析 Walrus 应用的性能瓶颈,提供数据驱动的优化建议和具体的实施方案。
|
||||
|
||||
## 性能分析维度
|
||||
|
||||
### 1. 上传性能优化
|
||||
|
||||
#### 批量操作优化
|
||||
```typescript
|
||||
// 优化前:逐个上传文件
|
||||
async function uploadFilesSlow(files: File[]) {
|
||||
const results = [];
|
||||
for (const file of files) {
|
||||
const result = await uploadSingleFile(file);
|
||||
results.push(result);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
// 优化后:批量并发上传
|
||||
class OptimizedUploader {
|
||||
private readonly MAX_CONCURRENT = 5;
|
||||
private readonly BATCH_DELAY = 100;
|
||||
|
||||
async uploadFilesOptimized(files: File[]): Promise<UploadResult[]> {
|
||||
const results: UploadResult[] = [];
|
||||
const chunks = this.chunkArray(files, this.MAX_CONCURRENT);
|
||||
|
||||
for (const chunk of chunks) {
|
||||
const chunkPromises = chunk.map(file => this.uploadWithRetry(file));
|
||||
const chunkResults = await Promise.allSettled(chunkPromises);
|
||||
|
||||
results.push(...this.processChunkResults(chunkResults));
|
||||
|
||||
// 批次间延迟,避免过载
|
||||
if (chunks.indexOf(chunk) < chunks.length - 1) {
|
||||
await this.delay(this.BATCH_DELAY);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private async uploadWithRetry(file: File, maxRetries = 3): Promise<UploadResult> {
|
||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||
try {
|
||||
return await this.uploadSingleFile(file);
|
||||
} catch (error) {
|
||||
if (attempt === maxRetries || !this.isRetryableError(error)) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
// 指数退避
|
||||
const delay = Math.min(1000 * Math.pow(2, attempt - 1), 10000);
|
||||
await this.delay(delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private chunkArray<T>(array: T[], size: number): T[][] {
|
||||
const chunks: T[][] = [];
|
||||
for (let i = 0; i < array.length; i += size) {
|
||||
chunks.push(array.slice(i, i + size));
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
private isRetryableError(error: any): boolean {
|
||||
return error instanceof RetryableWalrusClientError ||
|
||||
error.message.includes('timeout') ||
|
||||
error.message.includes('network');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 文件压缩优化
|
||||
```typescript
|
||||
class CompressionOptimizer {
|
||||
async optimizeUpload(file: File): Promise<OptimizedFile> {
|
||||
const fileBuffer = await file.arrayBuffer();
|
||||
const originalSize = fileBuffer.byteLength;
|
||||
|
||||
// 文本文件压缩
|
||||
if (this.isTextFile(file)) {
|
||||
const compressed = await this.compressText(fileBuffer);
|
||||
if (compressed.size < originalSize * 0.8) {
|
||||
return {
|
||||
data: compressed.data,
|
||||
originalSize,
|
||||
compressedSize: compressed.size,
|
||||
compressionRatio: compressed.size / originalSize,
|
||||
method: 'gzip'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 图片文件优化
|
||||
if (this.isImageFile(file)) {
|
||||
const optimized = await this.optimizeImage(fileBuffer);
|
||||
if (optimized.size < originalSize * 0.9) {
|
||||
return {
|
||||
data: optimized.data,
|
||||
originalSize,
|
||||
compressedSize: optimized.size,
|
||||
compressionRatio: optimized.size / originalSize,
|
||||
method: 'image-optimization'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
data: new Uint8Array(fileBuffer),
|
||||
originalSize,
|
||||
compressedSize: originalSize,
|
||||
compressionRatio: 1,
|
||||
method: 'none'
|
||||
};
|
||||
}
|
||||
|
||||
private async compressText(data: ArrayBuffer): Promise<CompressedData> {
|
||||
const compressionStream = new CompressionStream('gzip');
|
||||
const writer = compressionStream.writable.getWriter();
|
||||
const reader = compressionStream.readable.getReader();
|
||||
|
||||
writer.write(new Uint8Array(data));
|
||||
writer.close();
|
||||
|
||||
const chunks: Uint8Array[] = [];
|
||||
let totalSize = 0;
|
||||
|
||||
while (true) {
|
||||
const { done, value } = await reader.read();
|
||||
if (done) break;
|
||||
|
||||
chunks.push(value);
|
||||
totalSize += value.length;
|
||||
}
|
||||
|
||||
const combined = new Uint8Array(totalSize);
|
||||
let offset = 0;
|
||||
for (const chunk of chunks) {
|
||||
combined.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
|
||||
return { data: combined, size: totalSize };
|
||||
}
|
||||
|
||||
private async optimizeImage(data: ArrayBuffer): Promise<CompressedData> {
|
||||
// 使用 WebAssembly 进行图片优化
|
||||
// 这里可以集成像 sharp.js 或其他图片处理库
|
||||
const blob = new Blob([data], { type: 'image/jpeg' });
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d')!;
|
||||
|
||||
// 降低质量但保持合理尺寸
|
||||
const scale = Math.min(1, 1920 / Math.max(img.width, img.height));
|
||||
canvas.width = img.width * scale;
|
||||
canvas.height = img.height * scale;
|
||||
|
||||
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
||||
|
||||
canvas.toBlob((blob) => {
|
||||
if (blob) {
|
||||
blob.arrayBuffer().then(buffer => {
|
||||
const data = new Uint8Array(buffer);
|
||||
resolve({ data, size: data.length });
|
||||
});
|
||||
}
|
||||
}, 'image/jpeg', 0.85);
|
||||
};
|
||||
img.src = URL.createObjectURL(blob);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 下载性能优化
|
||||
|
||||
#### 智能缓存系统
|
||||
```typescript
|
||||
class IntelligentCache {
|
||||
private cache = new Map<string, CacheEntry>();
|
||||
private accessOrder = new Map<string, number>();
|
||||
private accessCounter = 0;
|
||||
private readonly MAX_SIZE = 100; // 最大缓存条目
|
||||
private readonly TTL = 300000; // 5分钟过期
|
||||
|
||||
async get(blobId: string): Promise<Uint8Array | null> {
|
||||
const entry = this.cache.get(blobId);
|
||||
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if (Date.now() - entry.timestamp > this.TTL) {
|
||||
this.delete(blobId);
|
||||
return null;
|
||||
}
|
||||
|
||||
// 更新访问顺序 (LRU)
|
||||
this.accessOrder.set(blobId, ++this.accessCounter);
|
||||
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
async set(blobId: string, data: Uint8Array): Promise<void> {
|
||||
// 检查缓存大小,必要时清理
|
||||
if (this.cache.size >= this.MAX_SIZE) {
|
||||
this.evictLRU();
|
||||
}
|
||||
|
||||
this.cache.set(blobId, {
|
||||
data,
|
||||
timestamp: Date.now(),
|
||||
size: data.length
|
||||
});
|
||||
|
||||
this.accessOrder.set(blobId, ++this.accessCounter);
|
||||
}
|
||||
|
||||
private evictLRU(): void {
|
||||
let oldestKey = '';
|
||||
let oldestAccess = Infinity;
|
||||
|
||||
for (const [key, accessTime] of this.accessOrder) {
|
||||
if (accessTime < oldestAccess) {
|
||||
oldestAccess = accessTime;
|
||||
oldestKey = key;
|
||||
}
|
||||
}
|
||||
|
||||
if (oldestKey) {
|
||||
this.delete(oldestKey);
|
||||
}
|
||||
}
|
||||
|
||||
private delete(blobId: string): void {
|
||||
this.cache.delete(blobId);
|
||||
this.accessOrder.delete(blobId);
|
||||
}
|
||||
|
||||
getStats(): CacheStats {
|
||||
return {
|
||||
size: this.cache.size,
|
||||
totalMemoryUsage: Array.from(this.cache.values())
|
||||
.reduce((sum, entry) => sum + entry.size, 0),
|
||||
hitRate: this.hitRate,
|
||||
evictions: this.evictions
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 预加载策略
|
||||
```typescript
|
||||
class PreloadingStrategy {
|
||||
private preloadQueue = new Set<string>();
|
||||
private preloadCache = new Map<string, PreloadEntry>();
|
||||
|
||||
async preloadFiles(blobIds: string[], priority: 'high' | 'medium' | 'low' = 'medium') {
|
||||
const delay = priority === 'high' ? 0 : priority === 'medium' ? 100 : 500;
|
||||
|
||||
for (const blobId of blobIds) {
|
||||
if (!this.preloadQueue.has(blobId)) {
|
||||
this.preloadQueue.add(blobId);
|
||||
|
||||
// 延迟执行,避免阻塞主要操作
|
||||
setTimeout(() => this.preloadFile(blobId), delay);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async preloadFile(blobId: string) {
|
||||
try {
|
||||
const startTime = Date.now();
|
||||
|
||||
// 使用低优先级下载
|
||||
const [file] = await client.walrus.getFiles({
|
||||
ids: [blobId],
|
||||
options: { priority: 'low' }
|
||||
});
|
||||
|
||||
const content = await file.bytes();
|
||||
const loadTime = Date.now() - startTime;
|
||||
|
||||
this.preloadCache.set(blobId, {
|
||||
data: content,
|
||||
timestamp: Date.now(),
|
||||
loadTime
|
||||
});
|
||||
|
||||
console.log(`预加载完成: ${blobId} (${loadTime}ms)`);
|
||||
|
||||
} catch (error) {
|
||||
console.warn(`预加载失败: ${blobId}`, error.message);
|
||||
} finally {
|
||||
this.preloadQueue.delete(blobId);
|
||||
}
|
||||
}
|
||||
|
||||
async getPreloaded(blobId: string): Promise<Uint8Array | null> {
|
||||
const entry = this.preloadCache.get(blobId);
|
||||
|
||||
if (!entry) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查是否过期
|
||||
if (Date.now() - entry.timestamp > 600000) { // 10分钟
|
||||
this.preloadCache.delete(blobId);
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry.data;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. 网络性能优化
|
||||
|
||||
#### 连接池管理
|
||||
```typescript
|
||||
class ConnectionPool {
|
||||
private connections: Map<string, Connection[]> = new Map();
|
||||
private readonly MAX_CONNECTIONS_PER_HOST = 5;
|
||||
private readonly CONNECTION_TIMEOUT = 30000;
|
||||
|
||||
async getConnection(host: string): Promise<Connection> {
|
||||
const pool = this.connections.get(host) || [];
|
||||
|
||||
// 尝试复用现有连接
|
||||
for (const conn of pool) {
|
||||
if (!conn.inUse && this.isConnectionHealthy(conn)) {
|
||||
conn.inUse = true;
|
||||
conn.lastUsed = Date.now();
|
||||
return conn;
|
||||
}
|
||||
}
|
||||
|
||||
// 创建新连接
|
||||
if (pool.length < this.MAX_CONNECTIONS_PER_HOST) {
|
||||
const newConn = await this.createConnection(host);
|
||||
newConn.inUse = true;
|
||||
pool.push(newConn);
|
||||
this.connections.set(host, pool);
|
||||
return newConn;
|
||||
}
|
||||
|
||||
// 等待连接可用
|
||||
return this.waitForAvailableConnection(host);
|
||||
}
|
||||
|
||||
releaseConnection(connection: Connection) {
|
||||
connection.inUse = false;
|
||||
connection.lastUsed = Date.now();
|
||||
}
|
||||
|
||||
private async createConnection(host: string): Promise<Connection> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const ws = new WebSocket(host);
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
reject(new Error('Connection timeout'));
|
||||
}, this.CONNECTION_TIMEOUT);
|
||||
|
||||
ws.onopen = () => {
|
||||
clearTimeout(timeout);
|
||||
resolve({
|
||||
socket: ws,
|
||||
created: Date.now(),
|
||||
lastUsed: Date.now(),
|
||||
inUse: true
|
||||
});
|
||||
};
|
||||
|
||||
ws.onerror = () => {
|
||||
clearTimeout(timeout);
|
||||
reject(new Error('Connection failed'));
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private isConnectionHealthy(conn: Connection): boolean {
|
||||
return conn.socket.readyState === WebSocket.OPEN &&
|
||||
Date.now() - conn.lastUsed < 300000; // 5分钟未使用则关闭
|
||||
}
|
||||
|
||||
private async waitForAvailableConnection(host: string): Promise<Connection> {
|
||||
const pool = this.connections.get(host) || [];
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const checkInterval = setInterval(() => {
|
||||
const available = pool.find(conn => !conn.inUse && this.isConnectionHealthy(conn));
|
||||
if (available) {
|
||||
clearInterval(checkInterval);
|
||||
available.inUse = true;
|
||||
available.lastUsed = Date.now();
|
||||
resolve(available);
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 请求优化
|
||||
```typescript
|
||||
class RequestOptimizer {
|
||||
private pendingRequests = new Map<string, Promise<any>>();
|
||||
private requestQueue: QueuedRequest[] = [];
|
||||
private processing = false;
|
||||
|
||||
async batchRequest<T>(requests: BatchRequest[]): Promise<T[]> {
|
||||
// 合并相同的请求
|
||||
const uniqueRequests = this.deduplicateRequests(requests);
|
||||
|
||||
// 按优先级排序
|
||||
uniqueRequests.sort((a, b) => b.priority - a.priority);
|
||||
|
||||
const results: T[] = [];
|
||||
|
||||
for (const request of uniqueRequests) {
|
||||
try {
|
||||
const result = await this.executeRequest(request);
|
||||
results.push(result);
|
||||
} catch (error) {
|
||||
console.error(`批量请求失败: ${request.id}`, error);
|
||||
results.push(null as T);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private deduplicateRequests(requests: BatchRequest[]): BatchRequest[] {
|
||||
const seen = new Set<string>();
|
||||
return requests.filter(req => {
|
||||
const key = this.getRequestKey(req);
|
||||
if (seen.has(key)) {
|
||||
return false;
|
||||
}
|
||||
seen.add(key);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private async executeRequest(request: BatchRequest): Promise<any> {
|
||||
const key = this.getRequestKey(request);
|
||||
|
||||
// 检查是否有相同请求正在进行
|
||||
if (this.pendingRequests.has(key)) {
|
||||
return await this.pendingRequests.get(key);
|
||||
}
|
||||
|
||||
const requestPromise = this.performRequest(request);
|
||||
this.pendingRequests.set(key, requestPromise);
|
||||
|
||||
try {
|
||||
const result = await requestPromise;
|
||||
return result;
|
||||
} finally {
|
||||
this.pendingRequests.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
private async performRequest(request: BatchRequest): Promise<any> {
|
||||
// 实现实际的请求逻辑
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), request.timeout);
|
||||
|
||||
try {
|
||||
const response = await fetch(request.url, {
|
||||
...request.options,
|
||||
signal: controller.signal
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. 内存优化
|
||||
|
||||
#### 内存池管理
|
||||
```typescript
|
||||
class MemoryPool {
|
||||
private pools = new Map<number, ArrayBuffer[]>();
|
||||
private readonly MAX_POOL_SIZE = 50;
|
||||
private readonly ALLOC_INCREMENT = 1024; // 1KB increments
|
||||
|
||||
allocate(size: number): ArrayBuffer {
|
||||
const actualSize = Math.ceil(size / this.ALLOC_INCREMENT) * this.ALLOC_INCREMENT;
|
||||
const pool = this.pools.get(actualSize) || [];
|
||||
|
||||
if (pool.length > 0) {
|
||||
return pool.pop()!;
|
||||
}
|
||||
|
||||
return new ArrayBuffer(actualSize);
|
||||
}
|
||||
|
||||
release(buffer: ArrayBuffer): void {
|
||||
const size = buffer.byteLength;
|
||||
const pool = this.pools.get(size) || [];
|
||||
|
||||
if (pool.length < this.MAX_POOL_SIZE) {
|
||||
// 清零缓冲区(安全考虑)
|
||||
new Uint8Array(buffer).fill(0);
|
||||
pool.push(buffer);
|
||||
this.pools.set(size, pool);
|
||||
}
|
||||
}
|
||||
|
||||
getStats(): MemoryPoolStats {
|
||||
const stats: MemoryPoolStats = {
|
||||
totalBuffers: 0,
|
||||
totalMemory: 0,
|
||||
poolSizes: {}
|
||||
};
|
||||
|
||||
for (const [size, pool] of this.pools) {
|
||||
stats.totalBuffers += pool.length;
|
||||
stats.totalMemory += size * pool.length;
|
||||
stats.poolSizes[size] = pool.length;
|
||||
}
|
||||
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 流式处理
|
||||
```typescript
|
||||
class StreamProcessor {
|
||||
async processLargeFile(
|
||||
file: File,
|
||||
processor: (chunk: Uint8Array, index: number) => Promise<void>,
|
||||
chunkSize = 1024 * 1024 // 1MB chunks
|
||||
): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
let offset = 0;
|
||||
let index = 0;
|
||||
|
||||
const processChunk = () => {
|
||||
if (offset >= file.size) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
const chunk = file.slice(offset, offset + chunkSize);
|
||||
reader.onload = async (event) => {
|
||||
if (event.target?.result) {
|
||||
const arrayBuffer = event.target.result as ArrayBuffer;
|
||||
const uint8Array = new Uint8Array(arrayBuffer);
|
||||
|
||||
try {
|
||||
await processor(uint8Array, index);
|
||||
offset += chunkSize;
|
||||
index++;
|
||||
processChunk();
|
||||
} catch (error) {
|
||||
reject(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
reader.readAsArrayBuffer(chunk);
|
||||
};
|
||||
|
||||
processChunk();
|
||||
});
|
||||
}
|
||||
|
||||
async uploadStream(
|
||||
file: File,
|
||||
onProgress?: (progress: number) => void
|
||||
): Promise<string> {
|
||||
const chunks: Uint8Array[] = [];
|
||||
const totalSize = file.size;
|
||||
|
||||
await this.processLargeFile(file, async (chunk, index) => {
|
||||
chunks.push(chunk);
|
||||
|
||||
const progress = ((index + 1) * chunk.length) / totalSize;
|
||||
onProgress?.(Math.min(progress, 1));
|
||||
});
|
||||
|
||||
// 合并所有块
|
||||
const totalLength = chunks.reduce((sum, chunk) => sum + chunk.length, 0);
|
||||
const combined = new Uint8Array(totalLength);
|
||||
let offset = 0;
|
||||
|
||||
for (const chunk of chunks) {
|
||||
combined.set(chunk, offset);
|
||||
offset += chunk.length;
|
||||
}
|
||||
|
||||
// 上传合并后的数据
|
||||
const walrusFile = WalrusFile.from({
|
||||
contents: combined,
|
||||
identifier: `stream/${Date.now()}_${file.name}`
|
||||
});
|
||||
|
||||
const result = await client.walrus.writeFiles({
|
||||
files: [walrusFile],
|
||||
epochs: 10,
|
||||
deletable: true
|
||||
});
|
||||
|
||||
return result[0].blobId;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 性能监控
|
||||
|
||||
### 实时性能指标
|
||||
```typescript
|
||||
class PerformanceMonitor {
|
||||
private metrics = new Map<string, MetricEntry[]>();
|
||||
private readonly MAX_METRICS = 1000;
|
||||
|
||||
recordMetric(name: string, value: number, tags: Record<string, string> = {}) {
|
||||
const entry: MetricEntry = {
|
||||
timestamp: Date.now(),
|
||||
value,
|
||||
tags
|
||||
};
|
||||
|
||||
const existing = this.metrics.get(name) || [];
|
||||
existing.push(entry);
|
||||
|
||||
// 限制历史数据数量
|
||||
if (existing.length > this.MAX_METRICS) {
|
||||
existing.splice(0, existing.length - this.MAX_METRICS);
|
||||
}
|
||||
|
||||
this.metrics.set(name, existing);
|
||||
}
|
||||
|
||||
getMetrics(name: string, timeRange?: number): MetricEntry[] {
|
||||
const allMetrics = this.metrics.get(name) || [];
|
||||
|
||||
if (!timeRange) {
|
||||
return allMetrics;
|
||||
}
|
||||
|
||||
const cutoff = Date.now() - timeRange;
|
||||
return allMetrics.filter(m => m.timestamp >= cutoff);
|
||||
}
|
||||
|
||||
getAggregatedMetrics(name: string, timeRange = 300000): AggregatedMetrics {
|
||||
const metrics = this.getMetrics(name, timeRange);
|
||||
|
||||
if (metrics.length === 0) {
|
||||
return {
|
||||
count: 0,
|
||||
sum: 0,
|
||||
avg: 0,
|
||||
min: 0,
|
||||
max: 0,
|
||||
p50: 0,
|
||||
p95: 0,
|
||||
p99: 0
|
||||
};
|
||||
}
|
||||
|
||||
const values = metrics.map(m => m.value).sort((a, b) => a - b);
|
||||
const sum = values.reduce((s, v) => s + v, 0);
|
||||
|
||||
return {
|
||||
count: values.length,
|
||||
sum,
|
||||
avg: sum / values.length,
|
||||
min: values[0],
|
||||
max: values[values.length - 1],
|
||||
p50: this.percentile(values, 0.5),
|
||||
p95: this.percentile(values, 0.95),
|
||||
p99: this.percentile(values, 0.99)
|
||||
};
|
||||
}
|
||||
|
||||
private percentile(sorted: number[], p: number): number {
|
||||
const index = Math.ceil(sorted.length * p) - 1;
|
||||
return sorted[Math.max(0, index)];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用方法
|
||||
|
||||
### 基础性能分析
|
||||
```typescript
|
||||
skill: "walrus-performance-optimizer"
|
||||
// analysis-type: upload
|
||||
// target-metric: speed
|
||||
// code-context: 我的文件上传速度很慢,单个10MB文件需要2分钟,如何优化?
|
||||
```
|
||||
|
||||
### 批量操作优化
|
||||
```typescript
|
||||
skill: "walrus-performance-optimizer"
|
||||
// analysis-type: batch
|
||||
// target-metric: throughput
|
||||
// code-context: 批量上传100个文件时经常超时,需要优化并发处理
|
||||
```
|
||||
|
||||
### 内存使用优化
|
||||
```typescript
|
||||
skill: "walrus-performance-optimizer"
|
||||
// analysis-type: memory
|
||||
// target-metric: cost
|
||||
// code-context: 大文件处理时内存占用过高,浏览器经常崩溃
|
||||
```
|
||||
|
||||
### 网络优化
|
||||
```typescript
|
||||
skill: "walrus-performance-optimizer"
|
||||
// analysis-type: network
|
||||
// target-metric: latency
|
||||
// code-context: 网络不稳定环境下频繁上传失败,需要优化网络策略
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
*更新时间:2025-11-11*
|
||||
Reference in New Issue
Block a user