diff --git a/apps/web/app/websocket/page.tsx b/apps/web/app/websocket/page.tsx index d7f600e..a4d0495 100644 --- a/apps/web/app/websocket/page.tsx +++ b/apps/web/app/websocket/page.tsx @@ -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() { diff --git a/debug-minio.js b/debug-minio.js deleted file mode 100644 index 4e2ef0f..0000000 --- a/debug-minio.js +++ /dev/null @@ -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); diff --git a/debug-s3.js b/debug-s3.js deleted file mode 100644 index 99bcc59..0000000 --- a/debug-s3.js +++ /dev/null @@ -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); -}); diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md deleted file mode 100644 index 8a44db4..0000000 --- a/docs/ENVIRONMENT.md +++ /dev/null @@ -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) { - // 本地存储会抛出错误 -} -``` diff --git a/docs/STATIC_FILES.md b/docs/STATIC_FILES.md deleted file mode 100644 index 5b78f9d..0000000 --- a/docs/STATIC_FILES.md +++ /dev/null @@ -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(''); - - 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
Loading...
; - - return ( -
- {/* 图片会内联显示 */} - Uploaded file - - {/* 下载链接 */} - - 下载文件 - - - {/* PDF 等文档可以在新窗口打开 */} - - 在新窗口打开 - -
- ); -} -``` - -### 文件类型处理 - -```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 -``` diff --git a/env.example b/env.example deleted file mode 100644 index 76e441e..0000000 --- a/env.example +++ /dev/null @@ -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 \ No newline at end of file diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma index 4f925dd..994debd 100755 --- a/packages/db/prisma/schema.prisma +++ b/packages/db/prisma/schema.prisma @@ -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") -} \ No newline at end of file diff --git a/packages/storage/env.example b/packages/storage/env.example deleted file mode 100644 index b3329e0..0000000 --- a/packages/storage/env.example +++ /dev/null @@ -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 \ No newline at end of file diff --git a/packages/storage/package.json b/packages/storage/package.json index cbca008..3e03aab 100644 --- a/packages/storage/package.json +++ b/packages/storage/package.json @@ -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" diff --git a/test-all-creds.js b/test-all-creds.js deleted file mode 100644 index 3ab7964..0000000 --- a/test-all-creds.js +++ /dev/null @@ -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('')) { - const codeMatch = result.data.match(/([^<]+)<\/Code>/); - const messageMatch = result.data.match(/([^<]+)<\/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); diff --git a/test-correct-creds.js b/test-correct-creds.js deleted file mode 100644 index bd80d91..0000000 --- a/test-correct-creds.js +++ /dev/null @@ -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); diff --git a/test-default-creds.js b/test-default-creds.js deleted file mode 100644 index d62151d..0000000 --- a/test-default-creds.js +++ /dev/null @@ -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); diff --git a/test-minio-curl.sh b/test-minio-curl.sh deleted file mode 100644 index f8f0712..0000000 --- a/test-minio-curl.sh +++ /dev/null @@ -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 " -echo "检查环境变量: docker inspect | grep -A 10 Env" \ No newline at end of file diff --git a/test-minio-simple.js b/test-minio-simple.js deleted file mode 100644 index 2419f8e..0000000 --- a/test-minio-simple.js +++ /dev/null @@ -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();