在公有云服务商动辄涨价数据隐私无保障账号绑定难以迁移的今天,「数据自主」已经不再是技术极客的小众追求,而是普通用户保障数字资产安全的刚需。本文将带你从零搭建一款旗舰级私有云存储系统,基于 Next.js 构建前端界面,支持 MinIO(本地私有部署)和 Cloudflare R2(低成本对象存储)双后端,具备断点续播全盘搜索多账户无缝切换等核心功能,最后通过 Fly.io 完成轻量化部署(天然支持 IPv6,国内用户友好,预授权无实际扣费),真正实现数据 100% 可控,告别公有云绑架。

一为什么选择这套方案?

1. 核心痛点解决(告别公有云绑架)

  • 🔒 数据自主:存储在自己的 MinIO 服务器或 R2 桶,无第三方数据泄露风险,不受服务商条款约束
  • 💸 低成本:R2 无出口带宽费,MinIO 可部署在闲置设备上,Fly.io 免费额度满足个人使用
  • 🚀 轻量化:无需复杂运维,Next.js 前端+Fly.io 部署,10 分钟完成上线
  • �IPv6 原生支持:Fly.io 天然支持 IPv6,解决国内 IPv4 公网获取困难访问不稳定的问题

2. 项目核心亮点(旗舰级功能体验)

  • 多账户无缝切换:支持 MinIO/R2 多账号配置,一键切换,管理多个存储桶
  • 断点续播:视频播放进度自动保存,跨设备恢复观看,媲美商业云盘
  • 全盘深度搜索:快速检索所有文件,支持文件名模糊匹配,无需逐层查找
  • 拖拽批量上传:支持文件/文件夹拖拽上传剪贴板粘贴上传,支持上传进度显示
  • 毛玻璃精美 UI:现代化视觉设计,兼顾美观与实用性
  • 用量统计:R2 专属本月用量统计(存储/Class A/Class B 操作),精准校准
  • 分享功能:支持临时签名链接(1 小时~7 天)和永久公开链接,灵活分享文件
  • 批量操作:文件/文件夹的移动重命名删除,高效管理数据

3. 技术栈选型

  • 前端框架:Next.js 16(App Router,React 19),服务端渲染+客户端交互兼顾
  • 存储后端:MinIO(私有部署)/ Cloudflare R2(对象存储),S3 协议兼容
  • 部署平台:Fly.io(IPv6 原生支持轻量预授权无实际扣费国内友好)
  • 辅助工具:AWS SDK for S3(存储交互)Tailwind CSS(样式)react-hot-toast(消息提示)

二前期准备

1. 基础环境安装

  • Node.js ≥ 20.x(推荐 LTS 版本,用于项目构建和依赖安装)
  • Git(用于拉取项目代码)
  • MinIO 环境(可选,私有部署存储,可部署在本地服务器NAS 设备)/ Cloudflare R2 账号(可选,低成本对象存储)

2. Fly.io CLI 安装(重点)

Fly.io 是一款轻量级容器部署平台,天然支持 IPv6,也是本文选择的部署载体,首先安装其 CLI 工具(对应你的操作命令):

步骤 1:执行安装脚本

# Linux/macOS 系统(本文演示,对应你的操作)
curl -L https://fly.io/install.sh | sh

步骤 2:加载环境变量

安装完成后,需要将 Fly.io CLI 加入系统环境变量,使其可以全局调用:

# 针对 bash 终端(你的终端环境)
source ~/.bashrc

# 若为 zsh 终端,执行:
# source ~/.zshrc

步骤 3:验证安装成功

执行以下命令,若能显示 Fly.io CLI 版本信息,说明安装成功:

fly version

三Fly.io 快速上手(第一次使用指南)

对于第一次使用 Fly.io 的用户,核心需要解决「登录」「绑定信用卡(预授权)」「验证 IPv6 支持」三个问题,其中预授权问题是国内用户最关心的。

1. Fly.io 账号登录

# 执行登录命令,会自动打开浏览器跳转至 Fly.io 官网登录页面
fly auth login
  • 可使用 GitHub 账号快速登录,无需单独注册
  • 登录成功后,终端会显示账号关联信息,完成 CLI 与账号的绑定

2. 绑定信用卡(预授权说明,国内用户友好)

Fly.io 作为海外平台,需要绑定信用卡完成身份验证,但仅会产生「小于 10 美元的预授权」,1 分钟内会自动退回,无实际消费,国内银联信用卡VisaMasterCard 均支持。

绑定步骤:

  1. 登录 Fly.io 官网后台(https://fly.io/dashboard)
  2. 点击左侧「Billing」(账单)选项,进入账单设置页面
  3. 点击「Add Payment Method」(添加支付方式),输入信用卡信息(卡号有效期CVV 码)
  4. 提交后,银行会收到「预授权扣款通知」(无实际资金转出,仅冻结部分额度)
  5. 等待 1-2 分钟,预授权额度会自动解冻,Fly.io 后台显示「Payment Method Verified」(支付方式已验证)

注意事项:

  • 预授权金额通常为 5 美元或 10 美元,具体以银行通知为准
  • 若绑定失败,可尝试更换信用卡(部分银行对海外预授权支持不佳)
  • 验证完成后,可在 Fly.io 后台设置「Spend Limit」(消费限额)为 0 美元,彻底避免意外扣费

3. 验证 Fly.io IPv6 支持

Fly.io 所有应用均默认开启 IPv6 支持,无需额外配置,验证方式如下:

  1. 后续部署项目完成后,可通过 fly ips list 命令查看应用的 IP 地址
  2. 其中标注「ipv6」的地址即为该应用的 IPv6 访问地址
  3. 也可通过在线 IPv6 检测工具,输入应用域名(xxx.fly.dev),验证 IPv6 可达性

4. 绑定域名

  1. Fly.io 端:运行 fly certs create your-domain.com
  2. Cloudflare 端:添加一条 CNAME 记录,Name 填子域名,Target 填 xxxx.dev,并确保关闭代理(关闭小黄云)
  3. 等待生效,然后通过您自己的域名访问。
  4. fly certs check 'your-domain.com' 查看域名进度
    yys@yys-defaultstring:~/r2-drive$ fly certs check 'cloud.yysresume.work'
    Status                    = Awaiting certificates
    Hostname                  = cloud.yysresume.work
    DNS Provider              = cloudflare
    Certificate Authority     = Let's Encrypt
    Issued                    =  
    Added to App              = 13 minutes ago
    Source                    = fly
    
    Your certificate for cloud.yysresume.work is being issued. Status is Awaiting certificates.

四项目搭建与核心配置

1. 拉取项目代码

# 克隆项目(本文基于你提供的 r2-drive 项目)
git clone https://github.com/xxx/r2-drive.git(替换为实际项目地址)
cd r2-drive

2. 安装项目依赖

npm install
# 若使用 yarn/pnpm,对应执行 yarn install / pnpm install

3. 核心配置解读(MinIO/R2 双支持)

项目的核心优势之一是兼容 MinIO(私有)和 R2(公有低成本),关键配置在 lib/s3.ts 中的 createS3Client 工具类,解决了 SSL 证书路径风格等兼容问题:

// lib/s3.ts
export const createS3Client = (config: R2Config) => {
  if (!config || !config.endpoint) {
    throw new Error("配置无效: Endpoint 缺失")
  }

  const requestHandler = new NodeHttpHandler({
    httpAgent: new http.Agent({ keepAlive: true timeout: 60000 })
    httpsAgent: new https.Agent({
        keepAlive: true
        rejectUnauthorized: false // 允许自签名证书,适配 MinIO 私有部署
        timeout: 60000
    })
  })

  return new S3Client({
    region: "auto"
    endpoint: config.endpoint
    forcePathStyle: true // 兼容 MinIO 路径风格
    requestHandler: requestHandler
    credentials: {
      accessKeyId: config.accessKeyId
      secretAccessKey: config.secretAccessKey
    }
  })
}

配置 MinIO/R2 账号(前端操作)

  1. 本地启动项目:npm run dev
  2. 访问 http://localhost:3000/setup,进入配置中心
  3. 填写账号信息:
    • MinIO:Endpoint 填写 http://你的MinIO地址:9000,Access Key/Secret Key 填写 MinIO 配置的密钥,Bucket Name 填写已创建的存储桶
    • R2:Endpoint 填写 https://<你的账户ID>.r2.cloudflarestorage.com,Access Key/Secret Key 填写 R2 密钥,Bucket Name 填写 R2 存储桶,Public URL 填写 R2 公开访问地址(可选)
  4. 点击「测试连接」,验证配置无误后,点击「添加并切换」,完成账号配置

4. 核心功能解读(亮点功能实现)

(1)断点续播:播放进度自动保存与恢复

断点续播功能通过将播放进度存储在存储桶的 .r2-drive-system/history.json 文件中实现,核心逻辑:

  1. 视频播放时,每 2 秒触发一次进度保存(节流避免频繁请求)
  2. 关闭预览模态框或切换页面时,自动保存最终进度
  3. 再次打开该视频时,从历史文件中读取进度并自动跳转
// components/PreviewModal.tsx
const handleTimeUpdate = () => {
  if (!videoRef.current || !file || !config) return
  // 节流保存
  if (saveTimeoutRef.current) clearTimeout(saveTimeoutRef.current)
  const currentTime = videoRef.current.currentTime
  const duration = videoRef.current.duration
  
  saveTimeoutRef.current = setTimeout(() => {
      saveProgress(currentTime duration)
  } 2000) 
}

(2)全盘深度搜索:递归扫描所有文件

基于 S3 协议的 ListObjectsV2 命令,递归扫描存储桶中的所有文件,支持模糊匹配,限制最大循环次数防止超时:

// app/api/search/route.ts
do {
    const command = new ListObjectsV2Command({
        Bucket: config.bucketName
        ContinuationToken: continuationToken
    })
    
    const response: ListObjectsV2CommandOutput = await r2.send(command)
    
    if (response.Contents) {
        const matches = response.Contents
            .filter(item => item.Key && item.Key.toLowerCase().includes(lowerQuery) && !item.Key.endsWith('/'))
            .map(file => ({
                key: file.Key
                size: file.Size
                lastModified: file.LastModified
                url: `${config.publicUrl}/${file.Key}`
            }))
        files.push(...matches)
    }

    continuationToken = response.NextContinuationToken
    loops++
} while (continuationToken && loops < MAX_LOOPS) // 限制最大循环次数,防止超时

(3)多账户无缝切换:配置上下文封装

通过 ConfigContext 封装多账户配置,实现无缝切换,无需重新登录或刷新页面:

// context/ConfigContext.ts
export const ConfigProvider = ({ children }: { children: ReactNode }) => {
  const [configs setConfigs] = useState<R2Config[]>([])
  const [activeConfigId setActiveConfigId] = useState<string | null>(null)
  const [isInitialized setIsInitialized] = useState(false)

  // 切换账户
  const switchConfig = (id: string) => {
    setActiveConfigId(id)
    localStorage.setItem('activeConfigId' id)
  }

  // 其他逻辑:添加更新删除配置...

  return (
    <ConfigContext.Provider value={{
      config: activeConfig
      configs
      switchConfig
      addConfig
      updateConfig
      removeConfig
      isInitialized
    }}>
      {children}
    </ConfigContext.Provider>
  )
}

五部署到 Fly.io(一键上线,支持 IPv6)

1. 配置 fly.toml

项目中已提供 fly.toml 配置文件,核心配置如下(确保支持 IPv6 和正确端口):

# fly.toml
app = "r2-drive" # 替换为你的应用名称(全局唯一)
primary_region = "arn" # 可选,选择就近区域

[env]
  PORT = "8080" # Next.js 启动端口

[[services]]
  protocol = "tcp"
  internal_port = 8080
  processes = ["app"]

  [[services.ports]]
    port = 80
    handlers = ["http"]
    force_https = true

  [[services.ports]]
    port = 443
    handlers = ["tls" "http"]

# 启用 IPv6(默认开启,无需额外配置)
[[services.ipv6]]
  enabled = true

2. 执行部署命令

在项目根目录下执行部署命令(与你的操作一致):

fly deploy

3. 部署过程解读(对应你的部署日志)

  1. 验证配置:Fly.io 会先验证 fly.toml 配置的有效性
  2. 构建镜像:通过 Depot 构建 Docker 镜像,安装 Node.js 依赖构建 Next.js 项目
  3. 推送镜像:将构建完成的镜像推送到 Fly.io 镜像仓库
  4. 滚动更新:更新现有机器,采用滚动策略,避免服务中断
  5. 验证 DNS:配置应用域名(r2-drive.fly.dev),自动解析 IPv4/IPv6 地址

4. 部署后验证

(1)查看应用状态

# 查看应用基本信息
fly status

# 查看应用 IP 地址(验证 IPv6 支持)
fly ips list

执行 fly ips list 后,会显示类似如下结果,其中 ipv6 字段即为应用的 IPv6 地址:

TYPE            ADDRESS                                     CREATED AT
ipv4            xxx.xxx.xxx.xxx                             2026-01-13T00:00:00Z
ipv6            2a09:8280:xxxx:xxxx:xxxx:xxxx:xxxx:xxxx     2026-01-13T00:00:00Z

(2)访问应用

通过浏览器访问 https://r2-drive.fly.dev(替换为你的应用域名),即可看到私有云系统的前端界面:

  1. 进入配置中心,填写 MinIO/R2 账号信息
  2. 测试文件上传预览断点续播等功能
  3. 通过 IPv6 地址直接访问,验证 IPv6 可达性

六功能演示与进阶优化

1. 核心功能演示

  • 多账户切换:在首页点击账号别名,可快速切换已配置的 MinIO/R2 账号
  • 断点续播:播放一段视频后关闭,再次打开该视频,会自动恢复到上次播放的位置
  • 全盘搜索:开启「全盘」搜索模式,输入文件名关键词,可检索整个存储桶中的匹配文件
  • 拖拽上传:将文件/文件夹拖拽到上传区域,可批量上传,实时显示上传进度和速度

2. 进阶优化方向

(1)配置自定义域名

将 Fly.io 应用绑定自定义域名,并配置 SSL 证书,提升访问体验:

# 添加自定义域名
fly domains add your-domain.com

# 生成 SSL 证书
fly certs add your-domain.com

(2)MinIO 集群部署

若需要更大的存储容量和更高的可用性,可将 MinIO 部署为集群模式,支持分布式存储和数据冗余。

(3)自动备份策略

配置定时任务,将存储桶中的数据备份到其他设备或云存储,防止数据丢失:

  • 可使用 rclone 工具,定时同步 MinIO/R2 数据
  • 可配置 Fly.io 定时任务,执行备份脚本

(4)扩容 Fly.io 应用

若应用访问量较大,可扩容 Fly.io 机器规格,提升性能:

# 查看可用机器规格
fly machine types list

# 扩容机器规格
fly machine update <machine-id> --type <machine-type>

七总结:数据自主,从这款私有云开始

本文从零到一搭建了一款旗舰级私有云存储系统,基于 Next.js 实现了现代化的前端界面,支持 MinIO/R2 双后端存储,通过 Fly.io 完成了轻量化部署(天然支持 IPv6,国内用户友好),核心亮点如下:

  1. 数据 100% 自主:存储在私有 MinIO 或 R2 桶,不受公有云服务商绑架,保障隐私安全
  2. 功能媲美商业云盘:断点续播全盘搜索多账户切换等功能,满足日常使用需求
  3. 部署简单高效:Fly.io 一键部署,预授权无实际扣费,IPv6 原生支持,无需复杂运维
  4. 低成本可扩展:个人使用免费额度足够,后续可根据需求扩容存储和计算资源

告别公有云的「隐形绑架」,从搭建一款属于自己的私有云开始。这款项目不仅是一个技术实现,更是数据自主理念的落地载体,希望本文能帮助你实现数字资产的可控管理,不再受限于第三方服务商的规则与约束。

附:常见问题解答

  1. Q:Fly.io 预授权迟迟不退回怎么办?
    A:预授权退回时间由银行决定,通常 1-3 个工作日内会解冻,可联系银行客服查询进度。

  2. Q:MinIO 私有部署后,Fly.io 应用无法访问怎么办?
    A:确保 MinIO 服务器开启公网访问(或配置内网穿透),防火墙开放 9000 端口,且 Fly.io 应用能访问 MinIO 地址。

  3. Q:R2 用量统计无法显示怎么办?
    A:确保填写了正确的 Cloudflare 账户 ID 和 API Token(需具备 Analytics 权限),且 R2 桶已开启用量统计功能。

  4. Q:如何实现断点续传上传?
    A:可基于 tus-js-client 扩展上传功能,支持大文件断点续传,后续可集成到项目中。