Skip to content

Repository files navigation

OSS Files

一个自用的阿里云 OSS 文件管理网站,后端 Go,前端 Vue 3 + Vite。

功能

  • 固定管理用户登录
  • 上传文件到 OSS,可指定对象 Key 或目录前缀
  • 按前缀列举、搜索、刷新对象
  • 在线查阅图片、PDF、文本等浏览器可预览文件
  • 下载文件
  • 删除对象
  • 对外提供鉴权后的上传 API

配置

复制 .env.example.env,或直接设置同名环境变量。

Copy-Item .env.example .env

必填项:

  • ADMIN_USERNAME / ADMIN_PASSWORD
  • APP_SESSION_SECRET
  • UPLOAD_API_TOKEN
  • OSS_REGION
  • OSS_BUCKET
  • OSS_ACCESS_KEY_ID
  • OSS_ACCESS_KEY_SECRET

可选项:

  • OSS_ENDPOINT:OSS endpoint,例如 https://oss-cn-hangzhou.aliyuncs.com;OSS 设为仅内网访问时可填写服务器可达的内网 endpoint
  • APP_PUBLIC_BASE_URL:可选,服务器对外访问基地址,例如 https://files.example.com。只有配置后才会生成绝对服务器下载链接;未配置时统一返回 /api/download?... 同源相对路径,外部上传调用方如需绝对链接必须配置此项
  • DOWNLOAD_LINK_SECRET:可选,服务器下载链接 HMAC 签名密钥,至少 32 字节;不配置时安全复用已校验的 APP_SESSION_SECRET
  • OSS_PUBLIC_BASE_URL:旧版兼容配置,当前不参与服务器下载链接生成,可留空
  • OSS_PREFIX:限制管理范围,例如 private/
  • UPLOAD_MAX_BYTES:全局上传大小上限,默认 100MB,支持 KB / MB / GB 后缀
  • DEDUPE_MYSQL_DSN:素材查重使用的 MySQL DSN,例如 user:pass@tcp(host:3306)/db?parseTime=true&charset=utf8mb4
  • DOWNLOAD_QUOTA_MYSQL_DSN:下载额度持久化使用的 MySQL DSN;留空时复用 DEDUPE_MYSQL_DSN。额度数据会跨重启、跨多实例共享
  • DOWNLOAD_DAILY_LIMIT_BYTES:每个文件每日成功传输的全局字节上限,0 表示默认不限。设置非零全局限额时,必须确保 DOWNLOAD_QUOTA_MYSQL_DSN(或其回退的 DEDUPE_MYSQL_DSN)已配置且可连接
  • DOWNLOAD_QUOTA_TIMEZONE:额度日期所用时区,默认 Asia/Shanghai

开发运行

后端:

$env:GOPROXY='https://goproxy.cn,direct'
go mod tidy
go run ./cmd/server

前端:

cd web
npm install --registry=https://registry.npmmirror.com
npm run dev

前端开发服务器默认代理 /apihttp://localhost:8080

构建

cd web
npm install --registry=https://registry.npmmirror.com
npm run build
cd ..
go build -o bin/oss-files.exe ./cmd/server

构建后运行 bin/oss-files.exe,访问 http://localhost:8080

Docker

本地构建镜像:

docker build -t oss-files:local .

运行:

docker run -d --name oss-files --restart unless-stopped \
  --env-file .env \
  -p 8080:8080 \
  oss-files:local

也可以使用 docker-compose.yml,先把镜像名改成你的 Docker Hub 镜像地址:

image: your-dockerhub-username/oss-files:latest

然后运行:

docker compose up -d

GitHub Actions 镜像

仓库已包含 .github/workflows/docker-image.yml。推送到 main / master 或推送 v* tag 时,会自动构建镜像。

默认会推送到 GitHub Container Registry:

ghcr.io/<owner>/<repo>:latest
ghcr.io/<owner>/<repo>:main
ghcr.io/<owner>/<repo>:sha-xxxxxxx
ghcr.io/<owner>/<repo>:v1.0.0

如果阿里云服务器访问不了 GitHub/GHCR,可以在 GitHub 仓库 Settings -> Secrets and variables -> Actions 里配置 Docker Hub:

DOCKERHUB_USERNAME=你的 Docker Hub 用户名
DOCKERHUB_TOKEN=你的 Docker Hub Access Token

配置后 workflow 会额外推送到 Docker Hub:

docker.io/<dockerhub-username>/<repo>:latest
docker.io/<dockerhub-username>/<repo>:main
docker.io/<dockerhub-username>/<repo>:sha-xxxxxxx
docker.io/<dockerhub-username>/<repo>:v1.0.0

阿里云服务器上使用 Docker Hub 镜像:

docker compose pull
docker compose up -d

镜像不包含 .env,部署时需要通过环境变量或 --env-file .env 注入配置。

上传 API

最简单的上传接口不需要先登录,请配置 .env 里的 UPLOAD_API_TOKEN,然后直接发一个 HTTP 请求:

curl -X PUT "http://localhost:8080/api/upload/docs/demo.txt?token=your-upload-token" \
  --data-binary @demo.txt

请求体就是文件内容,URL 里的 docs/demo.txt 就是 OSS 对象 Key。

在上传请求中追加 permanent=1,可让返回的 url 使用永久服务器签名下载链接:

curl -X PUT "http://localhost:8080/api/upload/docs/demo.txt?token=your-upload-token&permanent=1" \
  --data-binary @demo.txt

如果希望“同样内容的素材不重复上传”,需要配置 DEDUPE_MYSQL_DSN,并在上传时显式传 dedupe=1。未传 dedupe=1 时仍按普通上传流程执行:

curl -X PUT "http://localhost:8080/api/upload/docs/demo.txt?token=your-upload-token&dedupe=1" \
  --data-binary @demo.txt

命中查重时不会再次调用 OSS 上传,直接返回第一次保存的素材地址,并带上 deduplicated: true

也可以不把 token 放 URL,改用请求头:

curl -X PUT "http://localhost:8080/api/upload/docs/demo.txt" \
  -H "X-Upload-Token: your-upload-token" \
  --data-binary @demo.txt

或者:

curl -X PUT "http://localhost:8080/api/upload/docs/demo.txt" \
  -H "Authorization: Bearer your-upload-token" \
  --data-binary @demo.txt

返回:

{
  "key": "docs/demo.txt",
  "fullKey": "base/docs/demo.txt",
  "name": "demo.txt",
  "size": 12345,
  "etag": "...",
  "requestId": "...",
  "url": "https://files.example.com/api/download?key=docs%2Fdemo.txt&expires=...&sig=...",
  "expiration": "2026-06-26T07:00:00Z",
  "expiresIn": 3600,
  "permanent": false
}

未传 permanent=1 时,url 是默认有效期 3600 秒的临时服务器签名链接;expires 只接受 60 到 604800 秒。客户端访问该链接时由服务器使用 OSS 凭据读取对象并流式转发,签名链接不需要管理台 Cookie。

上传时传入 permanent=1 的返回示例:

{
  "key": "docs/demo.txt",
  "fullKey": "base/docs/demo.txt",
  "name": "demo.txt",
  "size": 12345,
  "etag": "...",
  "requestId": "...",
  "url": "https://files.example.com/api/download?key=docs%2Fdemo.txt&permanent=1&sig=...",
  "expiration": null,
  "expiresIn": null,
  "permanent": true
}

永久只表示不按时间过期,服务器仍会校验 HMAC;更换 DOWNLOAD_LINK_SECRET 或删除对象即可使链接失效。若同一个 Key 被替换,旧永久链接会访问该 Key 的新内容;需要不可变分享语义时请使用不可复用的 Key。

管理台内部仍保留 multipart 上传接口,登录后使用 Cookie 调用:

curl -b cookie.txt -F "file=@demo.txt" -F "key=docs/demo.txt" http://localhost:8080/api/objects

管理台上传也可以用表单字段启用查重:

curl -b cookie.txt -F "file=@demo.txt" -F "key=docs/demo.txt" -F "dedupe=1" http://localhost:8080/api/objects

如果不传 key,后端会使用上传文件名;如果配置了 OSS_PREFIX,对象会自动限定在该前缀下。

API 概览

所有接口都在 /api 下,除登录和带签名的 /api/download 外都需要 oss_files_session Cookie。

例外:PUT /api/upload/... 是简单上传接口,只需要 UPLOAD_API_TOKEN

登录

POST /api/login

请求:

{
  "username": "admin",
  "password": "your-password"
}

返回:

{
  "username": "admin"
}

列举文件和前缀

GET /api/objects?prefix=docs/&limit=100&token=...

返回中的 isPrefix: true 表示 OSS 前缀目录项,可以继续用它的 key 作为 prefix 查询下一级。

{
  "objects": [
    {
      "key": "docs/images/",
      "fullKey": "base/docs/images/",
      "name": "images/",
      "isPrefix": true,
      "size": 0
    },
    {
      "key": "docs/demo.txt",
      "fullKey": "base/docs/demo.txt",
      "name": "demo.txt",
      "isPrefix": false,
      "size": 12345,
      "etag": "...",
      "lastModified": "2026-06-26T06:00:00Z",
      "storageClass": "Standard",
      "contentType": "text/plain"
    }
  ],
  "nextToken": "",
  "truncated": false,
  "prefix": "docs/"
}

上传文件

POST /api/objects

格式:multipart/form-data

  • file:必填,文件字段
  • key:可选,OSS 对象 Key,例如 docs/demo.txt
  • dedupe:可选,传 1 时启用 MySQL 素材查重;不传时普通上传
  • permanent:可选,传 1 时返回永久服务器签名下载链接;不传时返回默认 3600 秒的临时链接

临时链接返回:

{
  "key": "docs/demo.txt",
  "fullKey": "base/docs/demo.txt",
  "etag": "...",
  "requestId": "...",
  "url": "https://files.example.com/api/download?key=docs%2Fdemo.txt&expires=...&sig=...",
  "permanent": false,
  "expiration": "2026-06-26T07:00:00Z",
  "expiresIn": 3600
}

传入 permanent=1 时,返回永久链接及对应字段:

{
  "key": "docs/demo.txt",
  "fullKey": "base/docs/demo.txt",
  "etag": "...",
  "requestId": "...",
  "url": "https://files.example.com/api/download?key=docs%2Fdemo.txt&permanent=1&sig=...",
  "permanent": true,
  "expiration": null,
  "expiresIn": null
}

查阅、下载、元数据、删除

  • GET /api/objects/raw?key=docs/demo.txt:以内联方式由服务器流式返回 OSS 文件内容,需要登录 Cookie
  • GET /api/objects/download?key=docs/demo.txt:以附件方式由服务器流式返回 OSS 文件内容,需要登录 Cookie
  • GET /api/download?key=docs/demo.txt&expires=...&sig=...:临时服务器签名下载,校验签名后由服务器流式返回附件,不需要登录 Cookie;expires 限制为 60 到 604800 秒,默认 3600 秒
  • GET /api/download?key=docs/demo.txt&permanent=1&sig=...:永久服务器签名下载,不按时间过期但每次仍需有效 HMAC;更换 DOWNLOAD_LINK_SECRET 或删除对象可使链接失效
  • GET /api/objects/meta?key=docs/demo.txt:返回文件元数据 JSON
  • GET /api/objects/link?key=docs/demo.txt&expires=3600:生成临时服务器签名下载链接
  • GET /api/objects/link?key=docs/demo.txt&permanent=1:生成永久服务器签名下载链接。临时响应的 expiration 为过期时间、expiresIn 为秒数;永久响应中两者均为 null,且 permanenttrue
  • permanent=1expires 同时传入时返回 400,永久链接不带 expires
  • DELETE /api/objects?key=docs/demo.txt:删除文件,成功返回 {"ok": true}
  • DELETE /api/objects?key=docs/:递归删除目录下的所有文件和子目录,成功返回 {"ok": true, "deleted": 12}

下载额度

每个文件按配置的日期时区(DOWNLOAD_QUOTA_TIMEZONE,默认 Asia/Shanghai)分别统计每日额度。额度数据存储在 MySQL 中,因此可跨进程重启、跨多实例共享;DOWNLOAD_QUOTA_MYSQL_DSN 为空时回退到 DEDUPE_MYSQL_DSN。如果设置了非零的 DOWNLOAD_DAILY_LIMIT_BYTES,必须提供可用的额度 MySQL DSN。

额度按服务器实际成功写给客户端的字节数计入。开始完整文件下载前,服务端会原子预留本次所需额度;额度不足时直接返回 429,不会下载到一半才失败。客户端中断连接时,尚未传出的字节会退回额度。/api/objects/raw、登录后的 /api/objects/download 和带签名的 /api/download 均计入额度。

管理员可按文件查看或覆盖每日限额:

  • GET /api/objects/download-limit?key=docs/demo.txt:查看该文件当前限额、来源和用量
  • PUT /api/objects/download-limit?key=docs/demo.txt:请求体为 {"dailyLimitBytes": N}N > 0 设置该文件覆盖值,N = 0 恢复全局默认限额
  • DELETE /api/objects/download-limit?key=docs/demo.txt:删除该文件覆盖值,恢复全局默认限额

响应包含 sourceusageDateusedBytesremainingBytes 等字段;sourcefile(单文件覆盖)、default(全局默认)或 unlimited(不限),例如:

{
  "key": "docs/demo.txt",
  "dailyLimitBytes": 104857600,
  "source": "file",
  "usageDate": "2026-06-26",
  "usedBytes": 12345,
  "remainingBytes": 104845255
}

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages