Files
ljwx-chat/文件上传问题修复.md
2025-11-29 07:36:44 +08:00

5.7 KiB

文件上传问题修复说明

问题根因

经过深入排查,文件上传失败的根本原因是:

预签名 URL 使用了内部地址 (host.docker.internal:9002), 浏览器无法访问该地址。

技术细节

  1. 环境配置:

    • S3_ENDPOINT=http://host.docker.internal:9002 - 容器内访问 MinIO 的地址
    • S3_PUBLIC_DOMAIN=http://192.168.1.83:9002 - 浏览器访问 MinIO 的地址
  2. 问题流程:

    浏览器 → LobeChat 容器 → 请求预签名 URL
                             → S3 SDK 生成 URL (使用 S3_ENDPOINT)
                             → 返回: http://host.docker.internal:9002/...
    浏览器 → 尝试上传到 host.docker.internal ❌ 失败!
    
  3. 错误表现:

    • XMLHttpRequest status = 0 (网络错误)
    • 被识别为 UPLOAD_NETWORK_ERROR
    • 显示: "请确认你的网络是否正常,并检查文件存储服务跨域配置是否正确"

修复方案

代码修改

修改文件: src/server/modules/S3/index.ts

在 createPreSignedUrl 方法中添加 URL 替换逻辑:

public async createPreSignedUrl(key: string): Promise<string> {
  const command = new PutObjectCommand({
    ACL: this.setAcl ? 'public-read' : undefined,
    Bucket: this.bucket,
    Key: key,
  });

  const url = await getSignedUrl(this.client, command, { expiresIn: 3600 });

  // Replace internal endpoint with public domain if configured
  if (fileEnv.S3_PUBLIC_DOMAIN && fileEnv.S3_ENDPOINT) {
    const endpointUrl = new URL(fileEnv.S3_ENDPOINT);
    const publicUrl = new URL(fileEnv.S3_PUBLIC_DOMAIN);
    return url.replace(endpointUrl.origin, publicUrl.origin);
  }

  return url;
}

修复原理

当同时配置了 S3_ENDPOINT 和 S3_PUBLIC_DOMAIN 时:

  1. SDK 生成的预签名 URL 包含内部地址: http://host.docker.internal:9002/...
  2. 代码将 host.docker.internal:9002 替换为 192.168.1.83:9002
  3. 浏览器可以访问替换后的 URL: http://192.168.1.83:9002/...

应用修复

方法 1: 自动重建 (推荐)

./rebuild-and-restart.sh

这个脚本会:

  1. 停止当前容器
  2. 重新构建 Docker 镜像
  3. 启动新容器

注意: 构建可能需要 5-10 分钟,请耐心等待。

方法 2: 手动重建

# 1. 停止容器
docker-compose -f docker-compose.lingjingwanxiang.yml down

# 2. 重新构建镜像
docker build -t lingjingwanxiang:latest .

# 3. 启动容器
docker-compose -f docker-compose.lingjingwanxiang.yml up -d

验证修复

  1. 访问 http://192.168.1.83:3210
  2. 尝试上传一个文件
  3. 打开浏览器开发者工具 (F12) → Network 标签
  4. 查看上传请求,预签名 URL 应该包含 192.168.1.83:9002 而不是 host.docker.internal:9002

诊断工具

测试 MinIO 和网络连接

./test-upload.sh

这个脚本会测试:

  • MinIO 健康状态
  • Bucket 访问权限
  • CORS 配置
  • 文件上传和下载
  • 容器网络连通性

所有测试应该都显示 ✓ 通过。

环境配置检查清单

确保以下环境变量配置正确:

# docker-compose.lingjingwanxiang.yml
environment:
  # ✓ 必须配置 - 容器内访问 MinIO
  - S3_ENDPOINT=http://host.docker.internal:9002

  # ✓ 必须配置 - 浏览器访问 MinIO (修复的关键!)
  - S3_PUBLIC_DOMAIN=http://192.168.1.83:9002

  # ✓ 必须配置 - 使用 path-style 访问
  - S3_ENABLE_PATH_STYLE=1

  # ✓ 必须配置 - MinIO 不需要设置 ACL
  - S3_SET_ACL=0

  # ✓ 必须配置
  - S3_BUCKET=lingjingwanxiang
  - S3_ACCESS_KEY_ID=minioadmin
  - S3_SECRET_ACCESS_KEY=minioadmin

常见问题

Q: 重建镜像会丢失数据吗?

A: 不会。数据存储在:

  • PostgreSQL 数据库 (host 上的 PostgreSQL)
  • MinIO 存储 (./minio-data 目录)

重建只会更新应用代码,不影响数据。

Q: 构建很慢怎么办?

A: 如果网络较慢,可以使用代理:

./build-docker-with-proxy.sh

Q: 修复后还是无法上传?

A: 检查:

  1. 浏览器控制台 (F12) 的具体错误
  2. 预签名 URL 是否包含正确的公网地址
  3. 是否可以直接访问 http://192.168.1.83:9002/lingjingwanxiang/
  4. 防火墙是否阻止了 9002 端口

Q: 可以直接用 S3_PUBLIC_DOMAIN 作为 S3_ENDPOINT 吗?

A: 不可以。因为:

  • 容器内必须通过 host.docker.internal 访问 host 上的服务
  • 192.168.1.83 在容器内解析可能不正确
  • 这就是需要两个不同配置的原因

技术背景

这是 Docker 部署中的常见问题,称为 "内外网地址不一致"。

  • 内网地址: 容器内部通过 host.docker.internal 访问 host 服务
  • 外网地址: 浏览器通过局域网 IP 或公网域名访问服务

预签名 URL 是由服务端生成但由浏览器使用的,因此需要使用外网地址。

其他可能遇到类似问题的场景:

  • Kubernetes 部署 (内部 Service vs 外部 Ingress)
  • 反向代理 (内部 upstream vs 外部 domain)
  • 云服务部署 (内网 IP vs 公网 IP)

相关文件

  • src/server/modules/S3/index.ts - S3 模块 (已修复)
  • src/server/routers/lambda/upload.ts - 上传路由
  • src/services/upload.ts - 客户端上传服务
  • docker-compose.lingjingwanxiang.yml - 应用配置
  • docker-compose.minio.yml - MinIO 配置

后续优化建议

  1. 使用域名: 将 192.168.1.83:9002 替换为域名,更稳定
  2. 启用 HTTPS: 避免浏览器安全警告
  3. 使用云 S3: 如 Cloudflare R2, 性能更好
  4. 监控和日志: 添加上传失败的详细日志

修复记录

  • 日期: 2025-11-28
  • 版本: v2.0.0-next.112
  • 修复人: Claude Code
  • 影响范围: 使用 Docker 部署 + MinIO 的所有实例
  • 修复状态: ✅ 已修复,待测试