This commit is contained in:
longdayi 2025-05-30 11:32:39 +08:00
parent 6aa7af73f6
commit 7c76dda7f3
14 changed files with 380 additions and 1507 deletions

View File

@ -1,6 +1,5 @@
'use client';
import { useHello, useTRPC, useWebSocket, MessageType } from '@repo/client';
import { useQuery } from '@tanstack/react-query';
import { useRef, useState, useEffect } from 'react';
export default function WebSocketPage() {

View File

@ -1,121 +0,0 @@
#!/usr/bin/env node
/**
* MinIO连接调试脚本
*/
const { S3 } = require('@aws-sdk/client-s3');
async function debugMinIO() {
console.log('🔍 MinIO连接调试开始...\n');
const config = {
endpoint: 'http://localhost:9000',
region: 'us-east-1',
credentials: {
accessKeyId: '7Nt7OyHkwIoo3zvSKdnc',
secretAccessKey: 'EZ0cyrjJAsabTLNSqWcU47LURMppBW2kka3LuXzb',
},
forcePathStyle: true,
};
console.log('配置信息:');
console.log('- Endpoint:', config.endpoint);
console.log('- Region:', config.region);
console.log('- Access Key:', config.credentials.accessKeyId);
console.log('- Force Path Style:', config.forcePathStyle);
console.log();
const s3Client = new S3(config);
try {
// 1. 测试基本连接
console.log('📡 测试基本连接...');
const buckets = await s3Client.listBuckets();
console.log('✅ 连接成功!');
console.log('📂 现有存储桶:', buckets.Buckets?.map((b) => b.Name) || []);
console.log();
// 2. 检查test123存储桶
const bucketName = 'test123';
console.log(`🪣 检查存储桶 "${bucketName}"...`);
try {
await s3Client.headBucket({ Bucket: bucketName });
console.log(`✅ 存储桶 "${bucketName}" 存在`);
} catch (error) {
if (error.name === 'NotFound') {
console.log(`❌ 存储桶 "${bucketName}" 不存在,正在创建...`);
try {
await s3Client.createBucket({ Bucket: bucketName });
console.log(`✅ 存储桶 "${bucketName}" 创建成功`);
} catch (createError) {
console.log(`❌ 创建存储桶失败:`, createError.message);
return;
}
} else {
console.log(`❌ 检查存储桶失败:`, error.message);
return;
}
}
// 3. 测试简单上传
console.log('\n📤 测试简单上传...');
const testKey = 'test-file.txt';
const testContent = 'Hello MinIO!';
try {
await s3Client.putObject({
Bucket: bucketName,
Key: testKey,
Body: testContent,
});
console.log(`✅ 简单上传成功: ${testKey}`);
} catch (error) {
console.log(`❌ 简单上传失败:`, error.message);
console.log('错误详情:', error);
return;
}
// 4. 测试分片上传初始化
console.log('\n🔄 测试分片上传初始化...');
const multipartKey = 'test-multipart.txt';
try {
const multipartUpload = await s3Client.createMultipartUpload({
Bucket: bucketName,
Key: multipartKey,
});
console.log(`✅ 分片上传初始化成功: ${multipartUpload.UploadId}`);
// 立即取消这个分片上传
await s3Client.abortMultipartUpload({
Bucket: bucketName,
Key: multipartKey,
UploadId: multipartUpload.UploadId,
});
console.log('✅ 分片上传取消成功');
} catch (error) {
console.log(`❌ 分片上传初始化失败:`, error.message);
console.log('错误详情:', error);
if (error.$metadata) {
console.log('HTTP状态码:', error.$metadata.httpStatusCode);
}
return;
}
console.log('\n🎉 所有测试通过MinIO配置正确。');
} catch (error) {
console.log('❌ 连接失败:', error.message);
console.log('错误详情:', error);
if (error.message.includes('ECONNREFUSED')) {
console.log('\n💡 提示:');
console.log('- 确保MinIO正在端口9000运行');
console.log('- 检查docker容器状态: docker ps');
console.log('- 重启MinIO: docker restart minio-container-name');
}
}
}
debugMinIO().catch(console.error);

View File

@ -1,169 +0,0 @@
#!/usr/bin/env node
/**
* S3存储调试脚本
* 用于快速诊断S3存储连接问题
*/
// 检查是否有.env文件如果有就加载
try {
require('dotenv').config();
} catch (e) {
console.log('No dotenv found, using environment variables directly');
}
async function debugS3() {
console.log('🔍 S3存储调试开始...\n');
// 1. 检查环境变量
console.log('📋 环境变量检查:');
const requiredVars = {
STORAGE_TYPE: process.env.STORAGE_TYPE,
S3_BUCKET: process.env.S3_BUCKET,
S3_ACCESS_KEY_ID: process.env.S3_ACCESS_KEY_ID,
S3_SECRET_ACCESS_KEY: process.env.S3_SECRET_ACCESS_KEY,
S3_REGION: process.env.S3_REGION,
S3_ENDPOINT: process.env.S3_ENDPOINT,
};
for (const [key, value] of Object.entries(requiredVars)) {
if (key.includes('SECRET')) {
console.log(` ${key}: ${value ? '✅ 已设置' : '❌ 未设置'}`);
} else {
console.log(` ${key}: ${value || '❌ 未设置'}`);
}
}
if (process.env.STORAGE_TYPE !== 's3') {
console.log('\n❌ STORAGE_TYPE 不是 s3无法测试S3连接');
return;
}
const missingVars = ['S3_BUCKET', 'S3_ACCESS_KEY_ID', 'S3_SECRET_ACCESS_KEY'].filter((key) => !process.env[key]);
if (missingVars.length > 0) {
console.log(`\n❌ 缺少必要的环境变量: ${missingVars.join(', ')}`);
console.log('请设置这些环境变量后重试');
return;
}
console.log('\n✅ 环境变量检查通过\n');
// 2. 测试AWS SDK加载
console.log('📦 加载AWS SDK...');
try {
const { S3 } = require('@aws-sdk/client-s3');
console.log('✅ AWS SDK加载成功\n');
// 3. 创建S3客户端
console.log('🔧 创建S3客户端...');
const config = {
region: process.env.S3_REGION || 'auto',
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
};
if (process.env.S3_ENDPOINT) {
config.endpoint = process.env.S3_ENDPOINT;
}
if (process.env.S3_FORCE_PATH_STYLE === 'true') {
config.forcePathStyle = true;
}
console.log('S3客户端配置:', {
region: config.region,
endpoint: config.endpoint || '默认AWS端点',
forcePathStyle: config.forcePathStyle || false,
});
const s3Client = new S3(config);
console.log('✅ S3客户端创建成功\n');
// 4. 测试bucket访问
console.log('🪣 测试bucket访问...');
try {
await s3Client.headBucket({ Bucket: process.env.S3_BUCKET });
console.log('✅ Bucket访问成功');
} catch (error) {
console.log(`❌ Bucket访问失败: ${error.message}`);
console.log('错误详情:', error);
if (error.name === 'NotFound') {
console.log(' 💡 提示: Bucket不存在请检查bucket名称');
} else if (error.name === 'Forbidden') {
console.log(' 💡 提示: 访问被拒绝,请检查访问密钥权限');
} else if (error.message.includes('getaddrinfo ENOTFOUND')) {
console.log(' 💡 提示: DNS解析失败请检查endpoint设置');
}
return;
}
// 5. 测试列出对象
console.log('\n📂 测试列出对象...');
try {
const result = await s3Client.listObjectsV2({
Bucket: process.env.S3_BUCKET,
MaxKeys: 5,
});
console.log(`✅ 列出对象成功,共有 ${result.KeyCount || 0} 个对象`);
if (result.Contents && result.Contents.length > 0) {
console.log(' 前几个对象:');
result.Contents.slice(0, 3).forEach((obj, index) => {
console.log(` ${index + 1}. ${obj.Key} (${obj.Size} bytes)`);
});
}
} catch (error) {
console.log(`❌ 列出对象失败: ${error.message}`);
console.log('错误详情:', error);
return;
}
// 6. 测试创建multipart upload
console.log('\n🚀 测试创建multipart upload...');
const testKey = `test-multipart-${Date.now()}`;
let uploadId;
try {
const createResult = await s3Client.createMultipartUpload({
Bucket: process.env.S3_BUCKET,
Key: testKey,
Metadata: { test: 'debug-script' },
});
uploadId = createResult.UploadId;
console.log(`✅ Multipart upload创建成功UploadId: ${uploadId}`);
// 清理测试upload
await s3Client.abortMultipartUpload({
Bucket: process.env.S3_BUCKET,
Key: testKey,
UploadId: uploadId,
});
console.log('✅ 测试upload已清理');
} catch (error) {
console.log(`❌ Multipart upload创建失败: ${error.message}`);
console.log('错误详情:', error);
return;
}
console.log('\n🎉 S3连接测试全部通过S3存储应该可以正常工作。');
console.log('\n💡 如果上传仍然失败,请检查:');
console.log('1. 网络连接是否稳定');
console.log('2. 防火墙是否阻止了连接');
console.log('3. S3服务是否有临时问题');
console.log('4. 查看应用日志中的详细错误信息');
} catch (error) {
console.log(`❌ AWS SDK加载失败: ${error.message}`);
console.log('请确保已安装 @aws-sdk/client-s3 包:');
console.log('npm install @aws-sdk/client-s3');
}
}
// 运行调试
debugS3().catch((error) => {
console.error('调试脚本出错:', error);
process.exit(1);
});

View File

@ -1,235 +0,0 @@
# 环境变量配置指南
本文档详细说明了项目中所有环境变量的配置方法和用途。
## 存储配置 (@repo/storage)
### 基础配置
```bash
# 存储类型选择
STORAGE_TYPE=local # 可选值: local | s3
# 上传文件过期时间毫秒0表示不过期
UPLOAD_EXPIRATION_MS=0
```
### 本地存储配置
`STORAGE_TYPE=local` 时需要配置:
```bash
# 本地存储目录路径
UPLOAD_DIR=./uploads
```
### S3 存储配置
`STORAGE_TYPE=s3` 时需要配置:
```bash
# S3 存储桶名称 (必需)
S3_BUCKET=my-app-uploads
# S3 区域 (必需)
S3_REGION=us-east-1
# S3 访问密钥 ID (必需)
S3_ACCESS_KEY_ID=your-access-key-id
# S3 访问密钥 (必需)
S3_SECRET_ACCESS_KEY=your-secret-access-key
# 自定义 S3 端点 (可选,用于 MinIO、阿里云 OSS 等)
S3_ENDPOINT=
# 是否强制使用路径样式 (可选)
S3_FORCE_PATH_STYLE=false
# 分片上传大小,单位字节 (可选,默认 8MB)
S3_PART_SIZE=8388608
# 最大并发上传数 (可选)
S3_MAX_CONCURRENT_UPLOADS=60
```
## 配置示例
### 开发环境 - 本地存储
```bash
# .env.development
STORAGE_TYPE=local
UPLOAD_DIR=./uploads
```
### 生产环境 - AWS S3
```bash
# .env.production
STORAGE_TYPE=s3
S3_BUCKET=prod-app-uploads
S3_REGION=us-west-2
S3_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
S3_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
```
### MinIO 本地开发
```bash
# .env.local
STORAGE_TYPE=s3
S3_BUCKET=uploads
S3_REGION=us-east-1
S3_ACCESS_KEY_ID=minioadmin
S3_SECRET_ACCESS_KEY=minioadmin
S3_ENDPOINT=http://localhost:9000
S3_FORCE_PATH_STYLE=true
```
### 阿里云 OSS
```bash
# .env.aliyun
STORAGE_TYPE=s3
S3_BUCKET=my-oss-bucket
S3_REGION=oss-cn-hangzhou
S3_ACCESS_KEY_ID=your-access-key-id
S3_SECRET_ACCESS_KEY=your-access-key-secret
S3_ENDPOINT=https://oss-cn-hangzhou.aliyuncs.com
S3_FORCE_PATH_STYLE=false
```
### 腾讯云 COS
```bash
# .env.tencent
STORAGE_TYPE=s3
S3_BUCKET=my-cos-bucket-1234567890
S3_REGION=ap-beijing
S3_ACCESS_KEY_ID=your-secret-id
S3_SECRET_ACCESS_KEY=your-secret-key
S3_ENDPOINT=https://cos.ap-beijing.myqcloud.com
S3_FORCE_PATH_STYLE=false
```
## 其他配置
### 数据库配置
```bash
# PostgreSQL 数据库连接字符串
DATABASE_URL="postgresql://username:password@localhost:5432/database"
```
### Redis 配置
```bash
# Redis 连接字符串
REDIS_URL="redis://localhost:6379"
```
### 应用配置
```bash
# 应用端口
PORT=3000
# 应用环境
NODE_ENV=development
# CORS 允许的源
CORS_ORIGIN=http://localhost:3001
```
## 安全注意事项
1. **敏感信息保护**:
- 永远不要将包含敏感信息的 `.env` 文件提交到版本控制系统
- 使用 `.env.example` 文件作为模板
2. **生产环境**:
- 使用环境变量管理服务(如 AWS Secrets Manager、Azure Key Vault
- 定期轮换访问密钥
3. **权限控制**:
- S3 存储桶应配置适当的访问策略
- 使用最小权限原则
## 验证配置
可以使用以下 API 端点验证存储配置:
```bash
# 验证存储配置
curl -X POST http://localhost:3000/api/storage/storage/validate \
-H "Content-Type: application/json" \
-d '{
"type": "s3",
"s3": {
"bucket": "my-bucket",
"region": "us-east-1",
"accessKeyId": "your-key",
"secretAccessKey": "your-secret"
}
}'
# 获取当前存储信息
curl http://localhost:3000/api/storage/storage/info
```
## 文件访问
### 统一下载接口
无论使用哪种存储类型,都通过统一的下载接口访问文件:
```bash
# 统一下载接口(推荐)
GET http://localhost:3000/download/2024/01/01/abc123/example.jpg
```
### 本地存储
当使用本地存储时:
- 下载接口会直接读取本地文件并返回
- 支持内联显示图片、PDF等和下载
### S3 存储
当使用 S3 存储时:
- 下载接口会重定向到 S3 URL
- 也可以直接访问 S3 URL如果存储桶是公开的
```bash
# 直接访问 S3 URL
GET https://bucket.s3.region.amazonaws.com/2024/01/01/abc123/example.jpg
```
### 文件 URL 生成
```typescript
import { StorageUtils } from '@repo/storage';
const storageUtils = StorageUtils.getInstance();
// 生成下载 URL推荐方式
const fileUrl = storageUtils.generateFileUrl('file-id');
// 结果: http://localhost:3000/download/file-id
// 生成完整的公开访问 URL
const publicUrl = storageUtils.generateFileUrl('file-id', 'https://yourdomain.com');
// 结果: https://yourdomain.com/download/file-id
// 生成 S3 直接访问 URL仅 S3 存储)
try {
const directUrl = storageUtils.generateDirectUrl('file-id');
// 结果: https://bucket.s3.region.amazonaws.com/file-id
} catch (error) {
// 本地存储会抛出错误
}
```

View File

@ -1,279 +0,0 @@
# 文件访问使用指南
本文档说明如何使用 `@repo/storage` 包提供的文件访问功能。
## 功能概述
存储包提供统一的文件访问接口:
- **统一下载接口** (`/download/:fileId`) - 适用于所有存储类型,提供统一的文件访问
## 使用方法
### 1. 基础配置
```typescript
import { createStorageApp } from '@repo/storage';
// 创建包含所有功能的存储应用
const storageApp = createStorageApp({
apiBasePath: '/api/storage', // API 管理接口
uploadPath: '/upload', // TUS 上传接口
downloadPath: '/download', // 文件下载接口
});
app.route('/', storageApp);
```
### 2. 分别配置功能
```typescript
import { createStorageRoutes, createTusUploadRoutes, createFileDownloadRoutes } from '@repo/storage';
const app = new Hono();
// 存储管理 API
app.route('/api/storage', createStorageRoutes());
// 文件上传
app.route('/upload', createTusUploadRoutes());
// 文件下载(所有存储类型)
app.route('/download', createFileDownloadRoutes());
```
## 文件访问方式
### 统一下载接口
无论使用哪种存储类型,都通过统一的下载接口访问文件:
```bash
# 访问文件(支持内联显示和下载)
GET http://localhost:3000/download/2024/01/01/abc123/image.jpg
GET http://localhost:3000/download/2024/01/01/abc123/document.pdf
```
### 本地存储
`STORAGE_TYPE=local` 时:
- 下载接口直接读取本地文件
- 自动设置正确的 Content-Type
- 支持内联显示(`Content-Disposition: inline`
### S3 存储
`STORAGE_TYPE=s3` 时:
- 下载接口重定向到 S3 URL
- 也可以直接访问 S3 URL如果存储桶是公开的
```bash
# 直接访问 S3 URL如果存储桶是公开的
GET https://bucket.s3.region.amazonaws.com/2024/01/01/abc123/file.jpg
```
## 代码示例
### 生成文件访问 URL
```typescript
import { StorageUtils } from '@repo/storage';
const storageUtils = StorageUtils.getInstance();
// 生成文件访问 URL
function getFileUrl(fileId: string) {
// 结果: http://localhost:3000/download/2024/01/01/abc123/file.jpg
return storageUtils.generateFileUrl(fileId);
}
// 生成完整的公开访问 URL
function getPublicFileUrl(fileId: string) {
// 结果: https://yourdomain.com/download/2024/01/01/abc123/file.jpg
return storageUtils.generateFileUrl(fileId, 'https://yourdomain.com');
}
// 生成 S3 直接访问 URL仅 S3 存储)
function getDirectUrl(fileId: string) {
try {
// S3 存储: https://bucket.s3.region.amazonaws.com/2024/01/01/abc123/file.jpg
return storageUtils.generateDirectUrl(fileId);
} catch (error) {
// 本地存储会抛出错误,使用下载接口
return storageUtils.generateFileUrl(fileId);
}
}
```
### 在 React 组件中使用
```tsx
import { useState, useEffect } from 'react';
function FileDisplay({ fileId }: { fileId: string }) {
const [fileUrl, setFileUrl] = useState<string>('');
useEffect(() => {
// 获取文件访问 URL
fetch(`/api/storage/resource/${fileId}`)
.then((res) => res.json())
.then((data) => {
if (data.status === 'ready' && data.resource) {
// 生成文件访问 URL
const url = `/download/${fileId}`;
setFileUrl(url);
}
});
}, [fileId]);
if (!fileUrl) return <div>Loading...</div>;
return (
<div>
{/* 图片会内联显示 */}
<img src={fileUrl} alt="Uploaded file" />
{/* 下载链接 */}
<a href={fileUrl} download>
下载文件
</a>
{/* PDF 等文档可以在新窗口打开 */}
<a href={fileUrl} target="_blank" rel="noopener noreferrer">
在新窗口打开
</a>
</div>
);
}
```
### 文件类型处理
```typescript
function getFileDisplayUrl(fileId: string, mimeType: string) {
const baseUrl = `/download/${fileId}`;
// 根据文件类型决定显示方式
if (mimeType.startsWith('image/')) {
// 图片直接显示
return baseUrl;
} else if (mimeType === 'application/pdf') {
// PDF 可以内联显示
return baseUrl;
} else {
// 其他文件类型强制下载
return `${baseUrl}?download=true`;
}
}
```
## 安全考虑
### 1. 访问控制
如需要权限验证,可以添加认证中间件:
```typescript
import { createFileDownloadRoutes } from '@repo/storage';
const app = new Hono();
// 添加认证中间件
app.use('/download/*', async (c, next) => {
// 检查用户权限
const token = c.req.header('Authorization');
if (!isValidToken(token)) {
return c.json({ error: 'Unauthorized' }, 401);
}
await next();
});
// 添加文件下载服务
app.route('/download', createFileDownloadRoutes());
```
### 2. 文件类型限制
```typescript
app.use('/download/*', async (c, next) => {
const fileId = c.req.param('fileId');
// 从数据库获取文件信息
const { resource } = await getResourceByFileId(fileId);
if (!resource) {
return c.json({ error: 'File not found' }, 404);
}
// 检查文件类型
const allowedTypes = ['image/jpeg', 'image/png', 'application/pdf'];
if (!allowedTypes.includes(resource.mimeType)) {
return c.json({ error: 'File type not allowed' }, 403);
}
await next();
});
```
## 性能优化
### 1. 缓存设置
```typescript
app.use('/download/*', async (c, next) => {
await next();
// 设置缓存头
c.header('Cache-Control', 'public, max-age=31536000'); // 1年
c.header('ETag', generateETag(c.req.path));
});
```
### 2. CDN 配置
对于生产环境,建议使用 CDN
```typescript
import { StorageUtils } from '@repo/storage';
const storageUtils = StorageUtils.getInstance();
// 使用 CDN 域名
const cdnUrl = 'https://cdn.yourdomain.com';
const fileUrl = storageUtils.generateFileUrl(fileId, cdnUrl);
```
## 故障排除
### 常见问题
1. **404 文件未找到**
- 检查文件是否存在于数据库
- 确认文件路径是否正确
- 检查文件权限(本地存储)
2. **下载接口不工作**
- 检查路由配置
- 确认存储配置正确
- 查看服务器日志
3. **S3 文件无法访问**
- 检查 S3 存储桶权限
- 确认文件是否上传成功
- 验证 S3 配置是否正确
### 调试方法
```bash
# 检查文件是否存在
curl -I http://localhost:3000/download/2024/01/01/abc123/file.jpg
# 检查存储配置
curl http://localhost:3000/api/storage/storage/info
# 检查文件信息
curl http://localhost:3000/api/storage/resource/2024/01/01/abc123/file.jpg
```

View File

@ -1,71 +0,0 @@
# ===========================================
# 存储配置 (@repo/storage)
# ===========================================
# 存储类型: local | s3
STORAGE_TYPE=local
# 上传文件过期时间毫秒0表示不过期
UPLOAD_EXPIRATION_MS=0
# ===========================================
# 本地存储配置 (当 STORAGE_TYPE=local 时)
# ===========================================
# 本地存储目录路径
UPLOAD_DIR=./uploads
# ===========================================
# S3 存储配置 (当 STORAGE_TYPE=s3 时)
# ===========================================
# S3 存储桶名称 (必需)
S3_BUCKET=
# S3 区域 (必需)
S3_REGION=us-east-1
# S3 访问密钥 ID (必需)
S3_ACCESS_KEY_ID=
# S3 访问密钥 (必需)
S3_SECRET_ACCESS_KEY=
# 自定义 S3 端点 (可选,用于 MinIO、阿里云 OSS 等)
S3_ENDPOINT=
# 是否强制使用路径样式 (可选)
S3_FORCE_PATH_STYLE=false
# 分片上传大小,单位字节 (可选,默认 8MB)
S3_PART_SIZE=8388608
# 最大并发上传数 (可选)
S3_MAX_CONCURRENT_UPLOADS=60
# ===========================================
# 数据库配置
# ===========================================
# 数据库连接字符串
DATABASE_URL="postgresql://username:password@localhost:5432/database"
# ===========================================
# Redis 配置
# ===========================================
# Redis 连接字符串
REDIS_URL="redis://localhost:6379"
# ===========================================
# 应用配置
# ===========================================
# 应用端口
PORT=3000
# 应用环境
NODE_ENV=development
# CORS 允许的源
CORS_ORIGIN=http://localhost:3001

View File

@ -1,3 +1,5 @@
// 优化后的架构 - 数据中台 v4.0 (完全通用化)
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "debian-openssl-1.1.x"]
@ -9,128 +11,394 @@ datasource db {
url = env("DATABASE_URL")
}
// ============= 数据源层 =============
model DataSource {
id String @id @default(cuid())
name String
code String @unique
type SourceType
config Json // 连接配置
description String?
status Status @default(ACTIVE)
// 元数据版本管理
schemaVersion String? @default("1.0")
lastSynced DateTime?
// 关联
pipelines Pipeline[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("data_sources")
}
// ============= 统一流水线层 =============
model Pipeline {
id String @id @default(cuid())
name String
type PipelineType // SYNC | TRANSFORM | STREAM | HYBRID
schedule String? // Cron表达式
config Json // 流水线配置
description String?
status Status @default(ACTIVE)
// 关联数据源(可选)
dataSource DataSource? @relation(fields: [dataSourceId], references: [id])
dataSourceId String?
// 执行记录
executions PipelineExecution[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("pipelines")
}
// 统一执行记录
model PipelineExecution {
id String @id @default(cuid())
executionId String @unique
status ExecutionStatus @default(PENDING)
// 统计信息
inputRecords Int @default(0)
outputRecords Int @default(0)
errorRecords Int @default(0)
// 时间信息
startedAt DateTime @default(now())
completedAt DateTime?
duration Int? // 毫秒
// 元数据
metadata Json? // 灵活的元数据存储
errorMsg String?
// 关联
pipeline Pipeline @relation(fields: [pipelineId], references: [id])
pipelineId String
// 产生的数据资产
dataAssets DataAsset[]
// 数据血缘
lineageRecords LineageRecord[]
@@map("pipeline_executions")
}
// ============= 数据资产层 =============
model DataAsset {
id String @id @default(cuid())
assetId String @unique
name String
type AssetType @default(BATCH)
format DataFormat @default(PARQUET)
// 存储信息(支持多种存储)
storageConfig Json // 统一存储配置
// 元数据管理
schema Json?
partitions Json? // 分区信息
size BigInt? // 数据大小
recordCount BigInt? // 记录数
// 数据质量
qualityScore Float? // 0-1之间的质量分数
// 关联
execution PipelineExecution? @relation(fields: [executionId], references: [id])
executionId String?
// 查询记录
queries Query[]
status AssetStatus @default(ACTIVE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([type, status])
@@index([createdAt])
@@map("data_assets")
}
// ============= 查询层 =============
model Query {
id String @id @default(cuid())
queryId String @unique
sql String
engine QueryEngine @default(DUCKDB)
// 执行信息
status QueryStatus @default(PENDING)
resultCount BigInt?
resultPath String?
duration Int? // 毫秒
errorMsg String?
// 查询标签
tags Json?
// 关联资产
dataAsset DataAsset @relation(fields: [assetId], references: [id])
assetId String
// 审计信息
userId String? // 执行用户
createdAt DateTime @default(now())
completedAt DateTime?
@@index([status, createdAt])
@@map("queries")
}
// ============= 通用实体层 =============
// 通用实体模型(支持优雅的树形层级)
model Entity {
id String @id @default(cuid())
code String @unique
name String
type EntityType // PERSON, EQUIPMENT, FACILITY, MATERIAL, ORGANIZATION等
attributes Json? // 灵活的属性存储
status Status @default(ACTIVE)
// 树形层级关系(直接支持)
parentId String?
parent Entity? @relation("EntityTree", fields: [parentId], references: [id])
children Entity[] @relation("EntityTree")
// 层级路径(用于快速查询)
path String? // 如: "/org1/dept1/team1" 便于层级查询
level Int? // 层级深度根节点为0
// 作为源实体的关系(非树形关系)
sourceRelations EntityRelation[] @relation("SourceEntity")
// 作为目标实体的关系
targetRelations EntityRelation[] @relation("TargetEntity")
// 数据血缘
lineageRecords LineageRecord[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([type, status])
@@index([code])
@@index([type, code])
@@index([parentId])
@@index([path])
@@index([type, parentId])
@@index([level])
@@map("entities")
}
// ============= 通用关系层 =============
// 通用实体关系表(处理非树形的复杂关系)
model EntityRelation {
id String @id @default(cuid())
sourceId String // 源实体ID
targetId String // 目标实体ID
relationship String // 关系类型
// 关系属性
attributes Json? // 关系属性:权限级别、时间范围等
startDate DateTime?
endDate DateTime?
// 关系元数据
metadata Json? // 其他元数据
// 关联实体
sourceEntity Entity @relation("SourceEntity", fields: [sourceId], references: [id])
targetEntity Entity @relation("TargetEntity", fields: [targetId], references: [id])
status Status @default(ACTIVE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([sourceId, targetId, relationship])
@@index([sourceId])
@@index([targetId])
@@index([relationship])
@@index([sourceId, relationship])
@@index([targetId, relationship])
@@index([relationship, status])
@@map("entity_relations")
}
// ============= 优化的血缘层 =============
model LineageRecord {
id String @id @default(cuid())
sourceType String // 来源类型
sourceId String // 来源ID
targetType String // 目标类型
targetId String // 目标ID
relationship String // 关系类型: CREATE, UPDATE, DERIVE
// 元数据
metadata Json? // 血缘元数据
// 关联执行(可选)
execution PipelineExecution? @relation(fields: [executionId], references: [id])
executionId String?
// 关联实体(可选)
entity Entity? @relation(fields: [entityId], references: [id])
entityId String?
status LineageStatus @default(ACTIVE)
createdAt DateTime @default(now())
@@unique([sourceType, sourceId, targetType, targetId])
@@index([relationship])
@@map("lineage_records")
}
// ============= 数据治理层 =============
model QualityRule {
id String @id @default(cuid())
name String
description String?
rule Json // 质量规则定义
threshold Float? // 阈值
// 应用范围
entityType String? // 应用的实体类型
entityId String? // 特定实体ID
status Status @default(ACTIVE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("quality_rules")
}
model DataCatalog {
id String @id @default(cuid())
name String
description String?
tags Json? // 标签
owner String? // 数据负责人
// 分类
category String?
sensitivity String? // 敏感度级别
// 关联资产或实体
assetType String // 资产类型DataAsset, Entity等
assetId String // 资产ID
status Status @default(ACTIVE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([assetType, assetId])
@@index([category])
@@map("data_catalogs")
}
// ============= 精简的权限层 =============
model User {
id String @id @default(cuid())
name String
password String?
salt String?
phone String? @unique
email String @unique
avatar String?
isSystem Boolean? @map("is_system")
isAdmin Boolean? @map("is_admin")
lastSignTime DateTime? @map("last_sign_time")
deactivatedTime DateTime? @map("deactivated_time")
createdTime DateTime @default(now()) @map("created_time")
deletedTime DateTime? @map("deleted_time")
lastModifiedTime DateTime? @updatedAt @map("last_modified_time")
id String @id @default(cuid())
username String @unique
email String @unique
name String
roles Json? // 简化为JSON存储角色
status Status @default(ACTIVE)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("users")
}
model Attachments {
id String @id @default(cuid())
token String @unique
hash String
size Int
mimetype String
path String
width Int?
height Int?
deletedTime DateTime? @map("deleted_time")
createdTime DateTime @default(now()) @map("created_time")
createdBy String @map("created_by")
lastModifiedBy String? @map("last_modified_by")
thumbnailPath String? @map("thumbnail_path")
@@map("attachments")
// ============= 枚举定义 =============
enum Status {
ACTIVE
INACTIVE
}
model Notification {
id String @id @default(cuid())
fromUserId String @map("from_user_id")
toUserId String @map("to_user_id")
type String @map("type")
message String @map("message")
urlPath String? @map("url_path")
isRead Boolean @default(false) @map("is_read")
createdTime DateTime @default(now()) @map("created_time")
createdBy String @map("created_by")
@@index([toUserId, isRead, createdTime])
@@map("notification")
enum SourceType {
DATABASE
API
FILE
ERP
MES
IOT
STREAM
}
model Setting {
instanceId String @id @default(cuid()) @map("instance_id")
disallowSignUp Boolean? @map("disallow_sign_up")
disallowSpaceCreation Boolean? @map("disallow_space_creation")
disallowSpaceInvitation Boolean? @map("disallow_space_invitation")
enableEmailVerification Boolean? @map("enable_email_verification")
aiConfig String? @map("ai_config")
brandName String? @map("brand_name")
brandLogo String? @map("brand_logo")
@@map("setting")
enum PipelineType {
SYNC
TRANSFORM
STREAM
HYBRID
}
model Trash {
id String @id @default(cuid())
resourceType String @map("resource_type")
resourceId String @map("resource_id")
parentId String? @map("parent_id")
deletedTime DateTime @default(now()) @map("deleted_time")
deletedBy String @map("deleted_by")
@@unique([resourceType, resourceId])
@@map("trash")
enum ExecutionStatus {
PENDING
RUNNING
SUCCESS
FAILED
CANCELLED
}
model UserLastVisit {
id String @id @default(cuid())
userId String @map("user_id")
resourceType String @map("resource_type")
resourceId String @map("resource_id")
parentResourceId String @map("parent_resource_id")
lastVisitTime DateTime @default(now()) @map("last_visit_time")
@@unique([userId, resourceType, parentResourceId])
@@index([userId, resourceType])
@@map("user_last_visit")
enum DataFormat {
PARQUET
JSON
CSV
AVRO
DELTA
}
model OidcClient {
id String @id @default(cuid())
clientId String @unique
clientSecret String
redirectUris String // 存储为JSON字符串
grantTypes String // 存储为JSON字符串
responseTypes String // 存储为JSON字符串
scope String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@map("oidc_clients")
enum AssetType {
BATCH
STREAM
TABLE
VIEW
MODEL
}
enum AssetStatus {
ACTIVE
ARCHIVED
DEPRECATED
}
enum QueryEngine {
DUCKDB
ATHENA
SPARK
TRINO
}
enum QueryStatus {
PENDING
RUNNING
SUCCESS
FAILED
CANCELLED
}
enum EntityType {
PERSON
EQUIPMENT
FACILITY
MATERIAL
ORGANIZATION
PROJECT
CUSTOM
}
enum LineageStatus {
ACTIVE
ARCHIVED
}
model Resource {
id String @id @default(cuid()) @map("id")
title String? @map("title")
description String? @map("description")
type String? @map("type")
fileId String? @unique
url String?
meta Json? @map("meta")
status String?
createdAt DateTime? @default(now()) @map("created_at")
updatedAt DateTime? @updatedAt @map("updated_at")
createdBy String? @map("created_by")
updatedBy String? @map("updated_by")
deletedAt DateTime? @map("deleted_at")
isPublic Boolean? @default(true) @map("is_public")
storageType String? @map("storage_type")
// 索引
@@index([type])
@@index([createdAt])
@@map("resource")
}

View File

@ -1,23 +0,0 @@
# 存储配置
STORAGE_TYPE=s3
# 本地存储配置 (当 STORAGE_TYPE=local 时使用)
LOCAL_STORAGE_DIRECTORY=./uploads
# S3/MinIO 存储配置 (当 STORAGE_TYPE=s3 时使用)
S3_ENDPOINT=http://localhost:9000
S3_REGION=us-east-1
S3_BUCKET=test123
# 使用Docker环境变量设置的凭据
S3_ACCESS_KEY_ID=nice1234
S3_SECRET_ACCESS_KEY=nice1234
S3_FORCE_PATH_STYLE=true
# S3 高级配置
S3_PART_SIZE=8388608
S3_MAX_CONCURRENT_UPLOADS=6
# 清理配置
CLEANUP_INCOMPLETE_UPLOADS=true
CLEANUP_SCHEDULE=0 2 * * *
CLEANUP_MAX_AGE_HOURS=24

View File

@ -2,8 +2,9 @@
"name": "@repo/storage",
"version": "2.0.0",
"description": "Storage implementation for Hono - 完全兼容 Hono 的 Storage",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": "./src/index.ts"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
@ -31,13 +32,6 @@
"hono": "^4.0.0",
"ioredis": "^5.0.0"
},
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.js"
}
},
"files": [
"dist",
"README.md"

View File

@ -1,103 +0,0 @@
const http = require('http');
// 测试不同的凭据组合
const credentialsList = [
{
name: 'Docker环境变量凭据 (nice1234)',
accessKey: 'nice1234',
secretKey: 'nice1234',
},
{
name: 'MinIO默认凭据',
accessKey: 'minioadmin',
secretKey: 'minioadmin',
},
{
name: '你创建的新AccessKey',
accessKey: '7Nt7OyHkwIoo3zvSKdnc',
secretKey: 'EZ0cyrjJAsabTLNSqWcU47LURMppBW2kka3LuXzb',
},
];
async function testCredentials(accessKey, secretKey) {
const options = {
hostname: 'localhost',
port: 9000,
path: '/?list-type=2', // 列出objects
method: 'GET',
headers: {
Host: 'localhost:9000',
Authorization: `AWS ${accessKey}:fakesignature`, // 简化测试
},
};
return new Promise((resolve, reject) => {
const req = http.request(options, (res) => {
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
resolve({
statusCode: res.statusCode,
data: data,
headers: res.headers,
});
});
});
req.on('error', reject);
req.setTimeout(3000, () => {
req.destroy();
reject(new Error('请求超时'));
});
req.end();
});
}
async function main() {
console.log('🔍 测试所有可能的MinIO凭据...\n');
for (const { name, accessKey, secretKey } of credentialsList) {
console.log(`📱 测试 ${name}:`);
console.log(` Access Key: ${accessKey}`);
console.log(` Secret Key: ${secretKey.substring(0, 8)}...`);
try {
const result = await testCredentials(accessKey, secretKey);
console.log(` 状态码: ${result.statusCode}`);
if (result.statusCode === 403) {
if (result.data.includes('SignatureDoesNotMatch')) {
console.log(' 🔐 签名错误 (但认证方式正确)');
} else if (result.data.includes('InvalidAccessKeyId')) {
console.log(' ❌ AccessKey无效');
} else {
console.log(' 🔐 权限被拒绝');
}
} else if (result.statusCode === 200) {
console.log(' ✅ 认证成功!');
} else {
console.log(` ⚠️ 未知状态: ${result.statusCode}`);
}
// 显示错误详情
if (result.data.includes('<Code>')) {
const codeMatch = result.data.match(/<Code>([^<]+)<\/Code>/);
const messageMatch = result.data.match(/<Message>([^<]+)<\/Message>/);
if (codeMatch && messageMatch) {
console.log(` 错误: ${codeMatch[1]} - ${messageMatch[1]}`);
}
}
} catch (error) {
console.log(` ❌ 连接失败: ${error.message}`);
}
console.log(''); // 空行分隔
}
console.log('💡 建议:');
console.log('1. 如果Docker凭据有效更新应用配置使用 nice1234/nice1234');
console.log('2. 如果新AccessKey有效确保它有正确的权限');
console.log('3. 可以通过MinIO控制台 (http://localhost:9001) 管理用户和权限');
}
main().catch(console.error);

View File

@ -1,127 +0,0 @@
// 在项目内运行可以使用现有的AWS SDK依赖
process.chdir('./packages/storage');
async function testWithCorrectCreds() {
console.log('🔍 使用正确的MinIO凭据测试...\n');
// 动态导入AWS SDK
const { S3 } = await import('@aws-sdk/client-s3');
const config = {
endpoint: 'http://localhost:9000',
region: 'us-east-1',
credentials: {
accessKeyId: 'nice1234', // Docker环境变量设置的凭据
secretAccessKey: 'nice1234',
},
forcePathStyle: true,
};
console.log('配置信息:');
console.log('- Endpoint:', config.endpoint);
console.log('- Region:', config.region);
console.log('- Access Key:', config.credentials.accessKeyId);
console.log('- Force Path Style:', config.forcePathStyle);
console.log();
const s3Client = new S3(config);
try {
// 1. 测试基本连接
console.log('📡 测试基本连接...');
const buckets = await s3Client.listBuckets();
console.log('✅ 连接成功!');
console.log('📂 现有存储桶:', buckets.Buckets?.map((b) => b.Name) || []);
console.log();
// 2. 检查test123存储桶
const bucketName = 'test123';
console.log(`🪣 检查存储桶 "${bucketName}"...`);
try {
await s3Client.headBucket({ Bucket: bucketName });
console.log(`✅ 存储桶 "${bucketName}" 存在`);
} catch (error) {
if (error.name === 'NotFound') {
console.log(`❌ 存储桶 "${bucketName}" 不存在,正在创建...`);
try {
await s3Client.createBucket({ Bucket: bucketName });
console.log(`✅ 存储桶 "${bucketName}" 创建成功`);
} catch (createError) {
console.log(`❌ 创建存储桶失败:`, createError.message);
return;
}
} else {
console.log(`❌ 检查存储桶失败:`, error.message);
return;
}
}
// 3. 测试简单上传
console.log('\n📤 测试简单上传...');
const testKey = 'test-file.txt';
const testContent = 'Hello MinIO from correct credentials!';
try {
await s3Client.putObject({
Bucket: bucketName,
Key: testKey,
Body: testContent,
});
console.log(`✅ 简单上传成功: ${testKey}`);
} catch (error) {
console.log(`❌ 简单上传失败:`, error.message);
console.log('错误详情:', error);
return;
}
// 4. 测试分片上传初始化
console.log('\n🔄 测试分片上传初始化...');
const multipartKey = 'test-multipart.txt';
try {
const multipartUpload = await s3Client.createMultipartUpload({
Bucket: bucketName,
Key: multipartKey,
});
console.log(`✅ 分片上传初始化成功: ${multipartUpload.UploadId}`);
// 立即取消这个分片上传
await s3Client.abortMultipartUpload({
Bucket: bucketName,
Key: multipartKey,
UploadId: multipartUpload.UploadId,
});
console.log('✅ 分片上传取消成功');
} catch (error) {
console.log(`❌ 分片上传初始化失败:`, error.message);
console.log('错误详情:', error);
if (error.$metadata) {
console.log('HTTP状态码:', error.$metadata.httpStatusCode);
}
return;
}
console.log('\n🎉 所有测试通过MinIO配置正确。');
console.log('\n📝 下一步: 更新你的.env文件使用以下配置:');
console.log('STORAGE_TYPE=s3');
console.log('S3_ENDPOINT=http://localhost:9000');
console.log('S3_REGION=us-east-1');
console.log('S3_BUCKET=test123');
console.log('S3_ACCESS_KEY_ID=nice1234');
console.log('S3_SECRET_ACCESS_KEY=nice1234');
console.log('S3_FORCE_PATH_STYLE=true');
} catch (error) {
console.log('❌ 连接失败:', error.message);
console.log('错误详情:', error);
if (error.message.includes('ECONNREFUSED')) {
console.log('\n💡 提示:');
console.log('- 确保MinIO正在端口9000运行');
console.log('- 检查docker容器状态: docker ps');
console.log('- 重启MinIO: docker restart minio-container-name');
}
}
}
testWithCorrectCreds().catch(console.error);

View File

@ -1,69 +0,0 @@
const { S3 } = require('@aws-sdk/client-s3');
async function testWithDefaultCreds() {
console.log('🔍 测试MinIO默认凭据...\n');
const configs = [
{
name: 'MinIO 默认凭据',
config: {
endpoint: 'http://localhost:9000',
region: 'us-east-1',
credentials: {
accessKeyId: 'minioadmin',
secretAccessKey: 'minioadmin',
},
forcePathStyle: true,
},
},
{
name: '你的自定义凭据',
config: {
endpoint: 'http://localhost:9000',
region: 'us-east-1',
credentials: {
accessKeyId: '7Nt7OyHkwIoo3zvSKdnc',
secretAccessKey: 'EZ0cyrjJAsabTLNSqWcU47LURMppBW2kka3LuXzb',
},
forcePathStyle: true,
},
},
];
for (const { name, config } of configs) {
console.log(`\n📱 测试 ${name}:`);
console.log(` Access Key: ${config.credentials.accessKeyId}`);
console.log(` Secret Key: ${config.credentials.secretAccessKey.substring(0, 8)}...`);
const s3Client = new S3(config);
try {
// 测试列出buckets
const result = await s3Client.listBuckets();
console.log(` ✅ 连接成功!`);
console.log(` 📂 现有buckets:`, result.Buckets?.map((b) => b.Name) || []);
// 测试创建bucket
const bucketName = 'test123';
try {
await s3Client.headBucket({ Bucket: bucketName });
console.log(` ✅ Bucket "${bucketName}" 已存在`);
} catch (error) {
if (error.name === 'NotFound') {
console.log(` 📦 创建bucket "${bucketName}"...`);
await s3Client.createBucket({ Bucket: bucketName });
console.log(` ✅ Bucket "${bucketName}" 创建成功`);
} else {
throw error;
}
}
} catch (error) {
console.log(` ❌ 连接失败:`, error.message);
if (error.$metadata?.httpStatusCode) {
console.log(` 📊 HTTP状态码:`, error.$metadata.httpStatusCode);
}
}
}
}
testWithDefaultCreds().catch(console.error);

View File

@ -1,28 +0,0 @@
#!/bin/bash
echo "🔍 测试MinIO连接..."
# 测试1: 默认凭据
echo -e "\n📱 测试MinIO默认凭据 (minioadmin/minioadmin):"
curl -s -w "HTTP状态码: %{http_code}\n" \
-H "Host: localhost:9000" \
-H "Authorization: AWS minioadmin:signature" \
http://localhost:9000/ | head -5
# 测试2: 无认证访问根路径
echo -e "\n🌐 测试无认证访问:"
curl -s -w "HTTP状态码: %{http_code}\n" http://localhost:9000/ | head -3
# 测试3: 检查MinIO管理界面
echo -e "\n🖥 测试MinIO控制台:"
curl -s -w "HTTP状态码: %{http_code}\n" -I http://localhost:9001/ | grep -E "(HTTP|Server|Content-Type)"
echo -e "\n💡 提示:"
echo "1. 如果你使用Docker运行MinIO检查环境变量MINIO_ROOT_USER和MINIO_ROOT_PASSWORD"
echo "2. 默认凭据通常是 minioadmin/minioadmin"
echo "3. 如果修改了凭据,请更新配置文件"
echo -e "\n🐳 Docker命令参考:"
echo "查看MinIO容器: docker ps | grep minio"
echo "查看容器日志: docker logs <container_name>"
echo "检查环境变量: docker inspect <container_name> | grep -A 10 Env"

View File

@ -1,163 +0,0 @@
const https = require('https');
const http = require('http');
const crypto = require('crypto');
// MinIO配置
const config = {
endpoint: 'localhost:9000',
accessKeyId: '7Nt7OyHkwIoo3zvSKdnc',
secretAccessKey: 'EZ0cyrjJAsabTLNSqWcU47LURMppBW2kka3LuXzb',
bucket: 'test123',
};
// 生成AWS签名v4
function generateSignature(method, path, headers, body, date) {
const region = 'us-east-1';
const service = 's3';
// 创建规范请求
const canonicalRequest = [
method,
path,
'', // query string
Object.keys(headers)
.sort()
.map((key) => `${key.toLowerCase()}:${headers[key]}`)
.join('\n'),
'',
Object.keys(headers)
.sort()
.map((key) => key.toLowerCase())
.join(';'),
crypto.createHash('sha256').update(body).digest('hex'),
].join('\n');
// 创建字符串待签名
const stringToSign = [
'AWS4-HMAC-SHA256',
date.toISOString().replace(/[:\-]|\.\d{3}/g, ''),
date.toISOString().substr(0, 10).replace(/-/g, '') + '/' + region + '/' + service + '/aws4_request',
crypto.createHash('sha256').update(canonicalRequest).digest('hex'),
].join('\n');
// 计算签名
const kDate = crypto
.createHmac('sha256', 'AWS4' + config.secretAccessKey)
.update(date.toISOString().substr(0, 10).replace(/-/g, ''))
.digest();
const kRegion = crypto.createHmac('sha256', kDate).update(region).digest();
const kService = crypto.createHmac('sha256', kRegion).update(service).digest();
const kSigning = crypto.createHmac('sha256', kService).update('aws4_request').digest();
const signature = crypto.createHmac('sha256', kSigning).update(stringToSign).digest('hex');
return signature;
}
// 测试基本连接
async function testConnection() {
console.log('🔍 测试MinIO基本连接...\n');
const options = {
hostname: 'localhost',
port: 9000,
path: '/',
method: 'GET',
};
return new Promise((resolve, reject) => {
const req = http.request(options, (res) => {
console.log(`状态码: ${res.statusCode}`);
console.log(`响应头:`, res.headers);
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
console.log('响应内容:', data);
resolve({ statusCode: res.statusCode, data });
});
});
req.on('error', reject);
req.end();
});
}
// 测试bucket列表
async function testListBuckets() {
console.log('\n📂 测试列出bucket...\n');
const date = new Date();
const headers = {
Host: config.endpoint,
'X-Amz-Date': date.toISOString().replace(/[:\-]|\.\d{3}/g, ''),
Authorization: `AWS4-HMAC-SHA256 Credential=${config.accessKeyId}/${date.toISOString().substr(0, 10).replace(/-/g, '')}/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-date, Signature=placeholder`,
};
const options = {
hostname: 'localhost',
port: 9000,
path: '/',
method: 'GET',
headers: headers,
};
return new Promise((resolve, reject) => {
const req = http.request(options, (res) => {
console.log(`状态码: ${res.statusCode}`);
console.log(`响应头:`, res.headers);
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
console.log('响应内容:', data);
resolve({ statusCode: res.statusCode, data });
});
});
req.on('error', reject);
req.end();
});
}
// 测试创建bucket
async function testCreateBucket() {
console.log(`\n🪣 测试创建bucket: ${config.bucket}...\n`);
const options = {
hostname: 'localhost',
port: 9000,
path: `/${config.bucket}`,
method: 'PUT',
};
return new Promise((resolve, reject) => {
const req = http.request(options, (res) => {
console.log(`状态码: ${res.statusCode}`);
console.log(`响应头:`, res.headers);
let data = '';
res.on('data', (chunk) => (data += chunk));
res.on('end', () => {
console.log('响应内容:', data);
resolve({ statusCode: res.statusCode, data });
});
});
req.on('error', reject);
req.end();
});
}
async function main() {
try {
await testConnection();
await testListBuckets();
await testCreateBucket();
console.log('\n✅ 测试完成!');
} catch (error) {
console.error('❌ 测试失败:', error.message);
}
}
main();