# go-upload-server **Repository Path**: peter_dayu/go-upload-server ## Basic Information - **Project Name**: go-upload-server - **Description**: 适配nio 文件上传的服务端系统 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-11-12 - **Last Updated**: 2025-11-14 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # SEALTA Upload Server 基于 Go + Gin 的高性能文件上传服务器,支持小文件直传和大文件分片上传。 **生产环境**: 使用 Docker 容器化部署,运行在 https://file.sealta.com ## 功能特性 - ✅ **签名验证**:SHA256 签名机制保证接口安全 - ✅ **小文件上传**:支持 <5MB 文件直接上传 - ✅ **分片上传**:支持 ≥5MB 大文件分片上传 - ✅ **状态上报**:设备状态实时上报 - ✅ **高性能**:Go 原生并发,轻松处理大量并发上传 - ✅ **Docker 部署**:容器化部署,易于管理和扩展 - ✅ **Nginx 反向代理**:HTTPS + 负载均衡支持 ## 项目结构 ``` go-upload-server/ ├── main.go # 主程序入口 ├── go.mod # Go 模块依赖 ├── Dockerfile # Docker 镜像构建 ├── docker-compose.yml # Docker 编排配置 ├── deploy-docker.sh # Docker 部署脚本 ├── nginx-site.conf # Nginx 站点配置 ├── upload-file.sealta.com.sh # 客户端上传测试脚本 ├── README.md # 项目说明文档 ├── DEPLOYMENT.md # 详细部署指南 ├── CLAUDE.md # 项目记忆文档 ├── config/ │ └── config.go # 配置管理 ├── middleware/ │ └── auth.go # 签名验证中间件 └── handlers/ ├── status.go # 状态上报处理器 ├── upload.go # 小文件上传处理器 └── multipart.go # 分片上传处理器 ``` ## 快速部署 (Docker) ### 前置要求 - Docker & Docker Compose - Nginx (服务器上已安装) - 域名 SSL 证书 (Let's Encrypt) ### 部署步骤 #### 1. 上传代码到服务器 ```bash # 本地打包 tar -czf phoenix-upload-server.tar.gz . # 上传到服务器 scp phoenix-upload-server.tar.gz root@your-server:/opt/ # 解压 ssh root@your-server cd /opt mkdir -p phoenix-upload-server tar -xzf phoenix-upload-server.tar.gz -C phoenix-upload-server cd phoenix-upload-server ``` #### 2. 执行部署脚本 ```bash chmod +x deploy-docker.sh sudo ./deploy-docker.sh ``` 部署脚本会自动完成: - ✅ 检查 Docker 环境 - ✅ 创建数据目录 (`./data/uploads`, `./data/temp`) - ✅ 构建 Docker 镜像 - ✅ 启动容器 (监听 127.0.0.1:8086) - ✅ 健康检查 #### 3. 配置 Nginx 编辑 `/etc/nginx/nginx.conf`,在 `http { }` 块末尾添加 `nginx-site.conf` 的内容,或直接在现有配置中添加: ```nginx # ======================================== # Phoenix Upload Server - file.sealta.com # ======================================== server { listen 80; server_name file.sealta.com; return 301 https://$server_name$request_uri; } server { listen 443 ssl http2; server_name file.sealta.com; ssl_certificate "/root/.acme.sh/sealta.com/fullchain.cer"; ssl_certificate_key "/root/.acme.sh/sealta.com/sealta.com.key"; ssl_session_cache shared:SSL:1m; ssl_session_timeout 10m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; ssl_stapling on; ssl_stapling_verify on; access_log /var/log/nginx/phoenix-upload-access.log; error_log /var/log/nginx/phoenix-upload-error.log; client_max_body_size 10G; client_body_timeout 600s; send_timeout 600s; proxy_connect_timeout 600s; proxy_send_timeout 600s; proxy_read_timeout 600s; proxy_buffering off; proxy_request_buffering off; location /api/ { proxy_pass http://127.0.0.1:8086; 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; proxy_set_header X-Forwarded-Proto $scheme; } location /health { proxy_pass http://127.0.0.1:8086/health; access_log off; } location / { return 200 '{"service": "SEALTA Upload Server", "status": "running"}'; add_header Content-Type application/json; } } ``` 重载 Nginx: ```bash sudo nginx -t sudo nginx -s reload ``` #### 4. 验证部署 ```bash # 检查容器状态 docker-compose ps # 测试健康检查 curl http://127.0.0.1:8086/health curl https://file.sealta.com/health ``` ## 本地开发 如需本地开发调试: ```bash # 安装依赖 go mod download # 运行服务 go run main.go # 服务将在 http://localhost:8080 启动 ``` ## API 接口 ### 1. 状态上报 **接口:** `POST /api/1/phoenix/datalogger/status_report` **参数:** - Query: `app_id`, `timestamp`, `signe`, `hash_type` - Body (JSON): ```json { "device_token": "xxx", "device_sn": "xxx" } ``` ### 2. 小文件上传(<5MB) **接口:** `POST /api/1/phoenix/upload/upload_file` **参数:** - Query: `app_id`, `timestamp`, `signe`, `hash_type` - Body (multipart/form-data): - `device_token` - `device_sn` - `file_name` - `file` (文件) ### 3. 分片上传初始化 **接口:** `POST /api/1/phoenix/upload/mutli_upload_init` **参数:** - Query: `app_id`, `timestamp`, `signe`, `hash_type` - Body (multipart/form-data): - `device_token` - `device_sn` - `file_name` **响应:** ```json { "code": 200, "message": "Multi-part upload initialized", "data": { "upload_id": "xxx", "key": "xxx" } } ``` ### 4. 分片上传 **接口:** `POST /api/1/phoenix/upload/multi_upload_file` **参数:** - Query: `app_id`, `timestamp`, `signe`, `hash_type` - Body (multipart/form-data): - `device_token` - `device_sn` - `upload_id` - `key` - `part_number` (从 1 开始) - `file` (分片文件) ### 5. 完成分片上传 **接口:** `POST /api/1/phoenix/upload/mutli_upload_complete` **参数:** - Query: `app_id`, `timestamp`, `signe`, `hash_type` - Body (multipart/form-data): - `device_token` - `device_sn` - `upload_id` - `key` - `etags` (JSON 数组) **etags 格式:** ```json [ {"part_number": 1, "e_tag": "xxx"}, {"part_number": 2, "e_tag": "yyy"} ] ``` ### 6. 中止分片上传 **接口:** `POST /api/1/phoenix/upload/mutli_upload_abort` **参数:** - Query: `app_id`, `timestamp`, `signe`, `hash_type` - Body (multipart/form-data): - `device_token` - `device_sn` - `upload_id` - `key` ## 签名机制 签名算法:SHA256 **签名字符串格式:** ``` {request_url}POST{host}?app_id={app_id}&hash_type=sha256[&jsonBody={json_body}]×tamp={timestamp}{app_secret} ``` **示例:** ```bash # 假设: request_url="/api/1/phoenix/datalogger/status_report" host="phoenix-datalogger.nio.com" app_id="100466" timestamp="1234567890" app_secret="your_secret" json_body='{"device_token":"xxx","device_sn":"yyy"}' # 签名字符串: /api/1/phoenix/datalogger/status_reportPOSTphoenix-datalogger.nio.com?app_id=100466&hash_type=sha256&jsonBody={"device_token":"xxx","device_sn":"yyy"}×tamp=1234567890your_secret # 计算 SHA256 得到签名 ``` ## 测试上传 修改 `upload-file.sealta.com.sh` 脚本的服务器地址: ```bash # 编辑脚本第 27 行 server="https://file.sealta.com" ``` 执行上传测试: ```bash # 上传小文件(<5MB) ./upload-file.sealta.com.sh /path/to/small-file.txt # 上传大文件(≥5MB,自动分片上传) ./upload-file.sealta.com.sh /path/to/large-file.zip ``` ## Docker 运维命令 ### 容器管理 ```bash # 启动服务 docker-compose up -d # 停止服务 docker-compose down # 重启服务 docker-compose restart # 查看状态 docker-compose ps # 查看日志 docker-compose logs -f # 查看最近 100 行日志 docker-compose logs --tail=100 # 进入容器 (调试) docker exec -it phoenix-upload-server sh ``` ### 更新部署 ```bash # 1. 停止容器 docker-compose down # 2. 重新构建镜像 docker-compose build --no-cache # 3. 启动容器 docker-compose up -d # 4. 查看日志 docker-compose logs -f ``` ### 数据管理 ```bash # 查看上传文件 ls -lh data/uploads/ # 查看临时分片 ls -lh data/temp/ # 备份上传文件 tar -czf uploads-backup-$(date +%Y%m%d).tar.gz data/uploads/ # 清理临时文件 (谨慎操作) rm -rf data/temp/* ``` ### 日志查看 ```bash # 容器日志 docker-compose logs -f # Nginx 访问日志 tail -f /var/log/nginx/phoenix-upload-access.log # Nginx 错误日志 tail -f /var/log/nginx/phoenix-upload-error.log ``` ## 环境配置 Docker 容器通过 `docker-compose.yml` 配置环境变量: ```yaml environment: - SERVER_PORT=8086 # 服务端口 - APP_ID=100466 # 应用 ID - APP_SECRET=A8F8076438D2385950ca9F23BEB996d3 # 应用密钥 - UPLOAD_DIR=/data/uploads # 上传文件保存目录 - TEMP_DIR=/data/temp # 临时文件目录 - CHUNK_SIZE=5242880 # 分片大小 (5MB) - MAX_UPLOAD_SIZE=10737418240 # 最大上传 (10GB) - LOG_LEVEL=info # 日志级别 - TZ=Asia/Shanghai # 时区 ``` 修改配置后需重启容器: ```bash docker-compose restart ``` ## 故障排查 ### 容器无法启动 ```bash # 查看详细日志 docker-compose logs # 检查端口是否被占用 netstat -tulnp | grep 8086 # 检查数据目录权限 ls -ld data/uploads data/temp ``` ### 上传失败 1. **检查签名**: 确认 `app_id` 和 `app_secret` 是否匹配 2. **验证时间戳**: 确保设备时间与服务器时间误差 <5 分钟 3. **查看日志**: `docker-compose logs | grep signature` 4. **检查文件大小**: 确认未超过 10GB 限制 ### Nginx 502 错误 ```bash # 检查容器是否运行 docker-compose ps # 测试容器健康状态 curl http://127.0.0.1:8086/health # 查看 Nginx 错误日志 tail -f /var/log/nginx/phoenix-upload-error.log ``` ### 磁盘空间不足 ```bash # 查看磁盘使用 df -h # 清理 Docker 缓存 docker system prune -a # 清理临时文件 rm -rf data/temp/* ``` ## 性能优化建议 ### Docker 资源调整 编辑 `docker-compose.yml`: ```yaml deploy: resources: limits: cpus: '4' # 增加 CPU 限制 memory: 4G # 增加内存限制 ``` ### Nginx 优化 编辑 `/etc/nginx/nginx.conf`: ```nginx worker_processes auto; events { worker_connections 2048; # 增加连接数 } ``` ### 监控建议 - 使用 Prometheus + Grafana 监控容器资源 - 配置磁盘空间告警 - 记录上传成功率和响应时间 ## 扩展功能建议 1. **Redis 会话存储**: 将分片上传会话存储到 Redis,支持多实例部署 2. **对象存储集成**: 自动上传到 AWS S3 / 阿里云 OSS 3. **文件去重**: 基于文件 hash 实现秒传 4. **断点续传**: 记录已上传分片,支持断点续传 5. **上传进度推送**: WebSocket 实时推送上传进度 6. **文件加密**: 敏感文件上传后自动加密存储 ## 安全建议 1. **定期更新**: 定期更新 Docker 镜像和系统包 2. **备份数据**: 定期备份 `data/uploads` 目录 3. **日志轮转**: 配置日志轮转,避免日志文件过大 4. **防火墙**: 确保只有 Nginx 能访问 8086 端口 5. **设备认证**: 实现 `device_token` 验证逻辑 ## 相关文档 - **详细部署指南**: [DEPLOYMENT.md](DEPLOYMENT.md) - **项目记忆文档**: [CLAUDE.md](CLAUDE.md) ## 常见问题 **Q: 为什么接口路径中是 `mutli_upload_init` 而不是 `multi_upload_init`?** A: 保持与原始客户端脚本一致,避免兼容性问题。 **Q: 如何修改最大上传大小?** A: 修改 `docker-compose.yml` 中的 `MAX_UPLOAD_SIZE` 环境变量和 Nginx 的 `client_max_body_size`,然后重启容器。 **Q: 上传失败如何排查?** A: 检查容器日志、验证签名、确认时间戳是否在有效范围内 (±5 分钟)。 **Q: 如何实现多实例部署?** A: 使用 Redis 存储分片上传会话,Nginx 配置负载均衡。 **Q: 如何备份上传的文件?** A: 使用 `tar -czf backup.tar.gz data/uploads/` 定期备份上传目录。 ## License MIT License ## 联系方式 如有问题或建议,请提交 Issue。 ## 配合 upload-fast.sh 介绍一下项目实现分片上传的原理 • 分片协议流程 - 客户端脚本 upload-fast.sh 先计算签名,通过 generate_url 统一为所有接口拼接 app_id/timestamp/signe/hash_type 参数,保证与服务器中间件 middleware/auth.go:17 的 SHA256 校验逻辑一致(upload-fast.sh:95-128, middleware/auth.go:82-104)。 - 上传前会跑状态上报和分片初始化:send_report_request 提交设备 SN/Token;send_multi_file_init_request POST 到 /mutli_upload_init 拿到 upload_id 与 key(upload-fast.sh:129-162)。对应服务端 handlers/multipart.go:43-112 创建 session、生成 key(deviceSN/uploadID/ fileName)并在 TempDir 下准备缓存。 - 真正的分片上传在 upload_large_file_parallel:根据 CHUNK_SIZE(默认 10MB)把文件切段存入临时目录,按 MAX_PARALLEL_UPLOADS 控制并发后台任务,每个任务调用 upload_part 上传一个 part_number,并把返回的 e_tag 记录到结果文件供后续合并使用(upload-fast.sh:217-343)。 - 服务器 MultiUploadFile 会先用 config.AppConfig.ChunkSize 设定的上限解析 multipart,然后校验 session/part 参数,把分片写到 TempDir//,同时记住每个 part 的 e_tag(handlers/multipart.go:115-294)。e_tag 是对临时文件算 MD5 得来的,便于客户端确认 (handlers/multipart.go:201-235)。 - 所有分片传完后,客户端构建 [{part_number,e_tag},…] JSON,调用 /mutli_upload_complete(upload-fast.sh:305-343),服务器在 MultiUploadComplete 中按 part_number 排序把临时文件顺序写到 UploadDir/deviceSN/fileName,然后清理 session/临时目录并返回最终文件信息(handlers/ multipart.go:295-363)。 实现要点 - 加密签名:客户端和服务端共享 requestURL+POST+host+…+AppSecret 的拼接规则,可防篡改/重放;时间戳容忍 ±5 分钟(middleware/auth.go:46-65)。 - 会话管理:用内存 map 保存 upload_id→session,提供互斥锁防并发;生产建议换成 Redis 以支持多实例/宕机恢复(handlers/multipart.go:21-41)。 - IO 与性能:脚本采用 dd 先落盘再并发上传,避免频繁 seek;curl 配置 --tcp-nodelay、压缩和超时;服务端按分片大小+缓冲限制内存,分片文件落磁盘后再合并,减少一次性内存占用。 - 可靠性:脚本实时统计进度、速度并在每片成功后删除临时文件,失败的片不会写结果;服务端验证 part_number/key/upload_id 匹配并在完成或中止时彻底清理临时数据(handlers/multipart.go:365-406)。 可能的下一步 1. 使用 upload-fast.sh 对不同大小/网络环境压测,验证 CHUNK_SIZE/MAX_PARALLEL_UPLOADS 的最优组合。 2. 将会话存储迁移到外部缓存(Redis、数据库),以便支持多实例部署和断点续传。