init
This commit is contained in:
12
.dockerignore
Normal file
12
.dockerignore
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
node_modules
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
*.md
|
||||||
|
.DS_Store
|
||||||
|
.env
|
||||||
|
.vscode
|
||||||
|
dist
|
||||||
|
build
|
||||||
|
coverage
|
||||||
|
.idea
|
||||||
|
*.log
|
||||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
node_modules
|
||||||
|
.env
|
||||||
|
.DS_Store
|
||||||
|
files/*
|
||||||
|
!files/.gitkeep
|
||||||
23
Dockerfile
Normal file
23
Dockerfile
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
# 使用 Bun 官方镜像作为基础镜像
|
||||||
|
FROM oven/bun:1 AS base
|
||||||
|
|
||||||
|
# 设置工作目录
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# 复制 package.json 和相关文件(可选,项目无运行时依赖)
|
||||||
|
COPY package.json ./
|
||||||
|
|
||||||
|
# 复制源代码
|
||||||
|
COPY src ./src
|
||||||
|
COPY tsconfig.json ./
|
||||||
|
COPY files ./files
|
||||||
|
|
||||||
|
# 暴露端口
|
||||||
|
EXPOSE 9666
|
||||||
|
|
||||||
|
# 设置环境变量
|
||||||
|
ENV PORT=9666
|
||||||
|
ENV FILES_DIR=/app/files
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
CMD ["bun", "run", "src/server.ts"]
|
||||||
178
README.md
Normal file
178
README.md
Normal file
@@ -0,0 +1,178 @@
|
|||||||
|
# 📁 文件托管服务
|
||||||
|
|
||||||
|
基于 Bun 的高性能文件托管服务,用于快速部署和访问静态文件。
|
||||||
|
|
||||||
|
## ✨ 功能特点
|
||||||
|
|
||||||
|
- 🚀 基于 Bun,性能极佳
|
||||||
|
- 📂 支持目录浏览,美观的 Web 界面
|
||||||
|
- 🔍 自动识别文件 MIME 类型
|
||||||
|
- 🛡️ 防路径遍历攻击
|
||||||
|
- ⚙️ 简单配置,开箱即用
|
||||||
|
|
||||||
|
## 🚀 快速开始
|
||||||
|
|
||||||
|
### 安装依赖
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 开发模式(自动重启)
|
||||||
|
bun run dev
|
||||||
|
|
||||||
|
# 生产模式
|
||||||
|
bun run start
|
||||||
|
```
|
||||||
|
|
||||||
|
### 访问服务
|
||||||
|
|
||||||
|
打开浏览器访问:`http://localhost:3000`
|
||||||
|
|
||||||
|
## 📦 使用说明
|
||||||
|
|
||||||
|
1. 将需要托管的文件放入 `files/` 目录
|
||||||
|
2. 启动服务
|
||||||
|
3. 通过浏览器访问文件:
|
||||||
|
- 访问根路径:`http://localhost:3000/` 查看所有文件
|
||||||
|
- 访问具体文件:`http://localhost:3000/your-file.pdf`
|
||||||
|
- 访问子目录:`http://localhost:3000/subfolder/`
|
||||||
|
|
||||||
|
## ⚙️ 配置
|
||||||
|
|
||||||
|
编辑 `.env` 文件修改配置:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# 服务器端口
|
||||||
|
PORT=3000
|
||||||
|
|
||||||
|
# 文件存储目录
|
||||||
|
FILES_DIR=./files
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🌐 部署到服务器
|
||||||
|
|
||||||
|
### 1. 安装 Bun
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -fsSL https://bun.sh/install | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 上传代码到服务器
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 使用 git
|
||||||
|
git clone <your-repo>
|
||||||
|
|
||||||
|
# 或使用 scp
|
||||||
|
scp -r . user@server:/path/to/app
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 启动服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 直接运行
|
||||||
|
bun run start
|
||||||
|
|
||||||
|
# 使用 PM2 管理(推荐)
|
||||||
|
pm2 start "bun run start" --name file-hosting
|
||||||
|
|
||||||
|
# 使用 systemd(推荐)
|
||||||
|
sudo nano /etc/systemd/system/file-hosting.service
|
||||||
|
```
|
||||||
|
|
||||||
|
### systemd 服务配置示例
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[Unit]
|
||||||
|
Description=File Hosting Service
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=your-user
|
||||||
|
WorkingDirectory=/path/to/app
|
||||||
|
ExecStart=/root/.bun/bin/bun run src/server.ts
|
||||||
|
Restart=always
|
||||||
|
Environment=PORT=3000
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
|
```
|
||||||
|
|
||||||
|
启用服务:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl enable file-hosting
|
||||||
|
sudo systemctl start file-hosting
|
||||||
|
sudo systemctl status file-hosting
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 配置反向代理(可选)
|
||||||
|
|
||||||
|
使用 Nginx 反向代理:
|
||||||
|
|
||||||
|
```nginx
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name your-domain.com;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://localhost:3000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📝 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
.
|
||||||
|
├── src/
|
||||||
|
│ └── server.ts # 服务器主文件
|
||||||
|
├── files/ # 文件存储目录
|
||||||
|
│ ├── .gitkeep
|
||||||
|
│ └── README.md # 示例文件
|
||||||
|
├── package.json
|
||||||
|
├── tsconfig.json
|
||||||
|
├── .env # 环境配置
|
||||||
|
├── .gitignore
|
||||||
|
└── README.md
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🔒 安全建议
|
||||||
|
|
||||||
|
1. **不要暴露敏感文件**:确保 `files/` 目录中不包含敏感信息
|
||||||
|
2. **使用 HTTPS**:生产环境建议配置 SSL 证书
|
||||||
|
3. **添加认证**:如需限制访问,可添加 HTTP 基础认证或 JWT
|
||||||
|
4. **设置防火墙**:限制只允许特定 IP 访问
|
||||||
|
|
||||||
|
## 📄 支持的文件类型
|
||||||
|
|
||||||
|
- 文档:PDF, TXT, MD
|
||||||
|
- 图片:PNG, JPG, GIF, SVG
|
||||||
|
- 视频:MP4
|
||||||
|
- 音频:MP3
|
||||||
|
- 压缩包:ZIP
|
||||||
|
- 网页:HTML, CSS, JS
|
||||||
|
- 数据:JSON
|
||||||
|
|
||||||
|
## 📈 性能
|
||||||
|
|
||||||
|
- 基于 Bun 原生文件 API,性能是 Node.js 的 2-4 倍
|
||||||
|
- 低内存占用,适合小型服务器
|
||||||
|
- 支持高并发访问
|
||||||
|
|
||||||
|
## 🤝 贡献
|
||||||
|
|
||||||
|
欢迎提交 Issue 和 Pull Request!
|
||||||
|
|
||||||
|
## 📜 许可
|
||||||
|
|
||||||
|
MIT License
|
||||||
21
bun.lock
Normal file
21
bun.lock
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"lockfileVersion": 1,
|
||||||
|
"configVersion": 1,
|
||||||
|
"workspaces": {
|
||||||
|
"": {
|
||||||
|
"name": "file-hosting-service",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packages": {
|
||||||
|
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
|
||||||
|
|
||||||
|
"@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
|
||||||
|
|
||||||
|
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
||||||
|
|
||||||
|
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
||||||
|
}
|
||||||
|
}
|
||||||
23
docker-compose.yml
Normal file
23
docker-compose.yml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
tradingview:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: tradingview-server
|
||||||
|
ports:
|
||||||
|
- "9666:9666"
|
||||||
|
environment:
|
||||||
|
- PORT=9666
|
||||||
|
- FILES_DIR=/app/files
|
||||||
|
volumes:
|
||||||
|
# 如果需要动态更新文件,可以挂载 files 目录
|
||||||
|
- ./files:/app/files:ro
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:9666/"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 10s
|
||||||
2
files/.gitkeep
Normal file
2
files/.gitkeep
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# 这是文件存储目录的占位文件
|
||||||
|
# 请将您要托管的文件放在此目录下
|
||||||
13
package.json
Normal file
13
package.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "file-hosting-service",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "bun run --watch src/server.ts",
|
||||||
|
"start": "bun run src/server.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
155
src/server.ts
Normal file
155
src/server.ts
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
const PORT = Bun.env.PORT || 3000;
|
||||||
|
const FILES_DIR = Bun.env.FILES_DIR || "./files";
|
||||||
|
|
||||||
|
// 获取文件的 MIME 类型
|
||||||
|
function getMimeType(filename: string): string {
|
||||||
|
const ext = filename.split('.').pop()?.toLowerCase();
|
||||||
|
const mimeTypes: Record<string, string> = {
|
||||||
|
'html': 'text/html',
|
||||||
|
'css': 'text/css',
|
||||||
|
'js': 'application/javascript',
|
||||||
|
'json': 'application/json',
|
||||||
|
'png': 'image/png',
|
||||||
|
'jpg': 'image/jpeg',
|
||||||
|
'jpeg': 'image/jpeg',
|
||||||
|
'gif': 'image/gif',
|
||||||
|
'svg': 'image/svg+xml',
|
||||||
|
'pdf': 'application/pdf',
|
||||||
|
'txt': 'text/plain',
|
||||||
|
'zip': 'application/zip',
|
||||||
|
'mp4': 'video/mp4',
|
||||||
|
'mp3': 'audio/mpeg',
|
||||||
|
};
|
||||||
|
return mimeTypes[ext || ''] || 'application/octet-stream';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 列出目录内容(HTML 格式)
|
||||||
|
async function listDirectory(dirPath: string, urlPath: string): Promise<string> {
|
||||||
|
const files = await Array.fromAsync(new Bun.Glob("*").scan(dirPath));
|
||||||
|
|
||||||
|
let html = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>文件列表 - ${urlPath}</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: Arial, sans-serif; max-width: 900px; margin: 50px auto; padding: 0 20px; }
|
||||||
|
h1 { color: #333; border-bottom: 2px solid #0066cc; padding-bottom: 10px; }
|
||||||
|
.path { color: #666; font-size: 14px; margin-bottom: 20px; }
|
||||||
|
ul { list-style: none; padding: 0; }
|
||||||
|
li { padding: 10px; border-bottom: 1px solid #eee; }
|
||||||
|
li:hover { background-color: #f5f5f5; }
|
||||||
|
a { text-decoration: none; color: #0066cc; display: flex; align-items: center; }
|
||||||
|
a:hover { text-decoration: underline; }
|
||||||
|
.icon { margin-right: 10px; font-size: 20px; }
|
||||||
|
.back { color: #666; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>📁 文件列表</h1>
|
||||||
|
<div class="path">当前路径: ${urlPath}</div>
|
||||||
|
<ul>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (urlPath !== '/') {
|
||||||
|
const parentPath = urlPath.split('/').slice(0, -1).join('/') || '/';
|
||||||
|
html += `<li><a href="${parentPath}" class="back"><span class="icon">⬆️</span>返回上级</a></li>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const file of files.sort()) {
|
||||||
|
const fullPath = `${dirPath}/${file}`;
|
||||||
|
const stat = await Bun.file(fullPath).exists();
|
||||||
|
const isDir = (await Bun.file(fullPath).type) === '';
|
||||||
|
const icon = isDir ? '📁' : '📄';
|
||||||
|
const link = urlPath === '/' ? `/${file}` : `${urlPath}/${file}`;
|
||||||
|
html += `<li><a href="${link}"><span class="icon">${icon}</span>${file}</a></li>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `
|
||||||
|
</ul>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`;
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
// CORS 响应头
|
||||||
|
const corsHeaders = {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'GET, OPTIONS',
|
||||||
|
'Access-Control-Allow-Headers': 'Content-Type',
|
||||||
|
};
|
||||||
|
|
||||||
|
// 启动服务器
|
||||||
|
Bun.serve({
|
||||||
|
port: PORT,
|
||||||
|
async fetch(req) {
|
||||||
|
const url = new URL(req.url);
|
||||||
|
let pathname = decodeURIComponent(url.pathname);
|
||||||
|
|
||||||
|
// 处理 OPTIONS 预检请求
|
||||||
|
if (req.method === 'OPTIONS') {
|
||||||
|
return new Response(null, {
|
||||||
|
status: 204,
|
||||||
|
headers: corsHeaders
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 防止路径遍历攻击
|
||||||
|
if (pathname.includes('..')) {
|
||||||
|
return new Response('Forbidden', {
|
||||||
|
status: 403,
|
||||||
|
headers: corsHeaders
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = `${FILES_DIR}${pathname}`;
|
||||||
|
const file = Bun.file(filePath);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 检查文件是否存在
|
||||||
|
if (await file.exists()) {
|
||||||
|
const stat = await file.stat();
|
||||||
|
|
||||||
|
// 如果是目录,显示目录列表
|
||||||
|
if (stat.isDirectory()) {
|
||||||
|
const html = await listDirectory(filePath, pathname);
|
||||||
|
return new Response(html, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'text/html; charset=utf-8',
|
||||||
|
...corsHeaders
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回文件内容
|
||||||
|
const mimeType = getMimeType(pathname);
|
||||||
|
return new Response(file, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': mimeType,
|
||||||
|
'Content-Length': stat.size.toString(),
|
||||||
|
...corsHeaders
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 文件不存在
|
||||||
|
return new Response('File not found', {
|
||||||
|
status: 404,
|
||||||
|
headers: corsHeaders
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
return new Response('Internal Server Error', {
|
||||||
|
status: 500,
|
||||||
|
headers: corsHeaders
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`🚀 文件托管服务已启动!`);
|
||||||
|
console.log(`📂 文件目录: ${FILES_DIR}`);
|
||||||
|
console.log(`🌐 访问地址: http://localhost:${PORT}`);
|
||||||
|
console.log(`⏹️ 按 Ctrl+C 停止服务`);
|
||||||
21
tsconfig.json
Normal file
21
tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"lib": ["ESNext"],
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleDetection": "force",
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"allowJs": true,
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUnusedLocals": false,
|
||||||
|
"noUnusedParameters": false,
|
||||||
|
"noPropertyAccessFromIndexSignature": false,
|
||||||
|
"types": ["bun-types"]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user