公众号旧文

一步步搭建一个属于自己的 Go 公用图床

作者:胖子阅读 —

对大家来说,图床并不陌生。

市面上虽然有很多现成的图床。

但我仍然决定自己写一个。

整个项目完全基于 Go 标准库开发。

从一个只有几十行代码的小程序,一步一步演进到了 v0.6。

本文记录整个过程。

第一阶段:项目初始化

首先初始化 Go 项目。

go mod init gallery

整个项目最初只有两个文件:

gallery/
├── go.mod
└── main.go

第一版程序只有一个 HTTP Server。

http.ListenAndServe(":8080", nil)

浏览器能打开:

http://127.0.0.1:8080

就算完成了第一步。

第二阶段:首页

随后增加 HTML 页面。

templates/
└── index.html

页面简单到只有:

  • 一个标题
  • 一个文件选择框
  • 一个 Upload 按钮

Go 使用标准库:

html/template

渲染模板。

整个项目没有引入任何前端框架。

第三阶段:实现上传

真正的图床核心,就是上传。

Go 提供:

r.FormFile("image")

可以直接读取 multipart/form-data。

上传逻辑包括:

  1. 获取文件
  2. 创建目标文件
  3. io.Copy 保存

这一阶段,已经能够:

curl -F "image=@test.png" ...

成功上传图片。

第四阶段:随机文件名

直接使用用户上传的文件名存在很多问题:

  • 文件名冲突
  • 中文乱码
  • 特殊字符
  • 路径攻击

于是改为:

crypto/rand

生成 16 字节随机数。

例如:

2acfa0fdfdb99986da5c90a239a4ab38.png

优点:

  • 基本不会重复
  • 保留扩展名
  • URL 更简洁

第五阶段:按年月分类

如果所有图片都放到:

uploads/

以后可能就是几万张图片。

因此改为:

uploads/
└── 2026/
    └── 07/

代码根据:

time.Now()

自动生成目录。

这样:

  • 文件数量均匀
  • 查找方便
  • 备份简单

第六阶段:限制只能上传图片

HTML 的:

accept="image/*"

没有任何安全意义。

浏览器可以绕过。

真正可靠的是后端。

Go 使用:

http.DetectContentType()

读取前 512 字节判断 MIME。

允许:

  • image/png
  • image/jpeg
  • image/gif
  • image/webp

其它全部拒绝。

例如:

text/plain

返回:

{
  "success": false,
  "message": "unsupported image type"
}

第七阶段:上传大小限制

如果不限制上传大小。

有人上传超大图片。

服务器很容易被拖垮。

因此增加两层保护。

第一层:

Go

http.MaxBytesReader()

第二层:

Nginx

client_max_body_size 5M;

最终效果:

Browser
    │
    ▼
Nginx
    │
>5MB
    │
413

只有合法请求才会进入 Go。

第八阶段:统一 JSON API

最开始返回的是:

Upload success

后来全部改成 JSON。

成功:

{
    "success": true,
    "url": "https://gallery.cc.cd/uploads/2026/07/example.png",
    "filename": "example.png",
    "size": 12345,
    "content_type": "image/png"
}

失败:

{
    "success": false,
    "message": "unsupported image type"
}

这样以后:

  • 前端
  • App
  • Python
  • Java

都可以直接调用。

第九阶段:代码重构

随着代码越来越多,开始拆分文件。

最终形成:

gallery/
├── config.go
├── main.go
├── response.go
├── upload.go
└── util.go

检测图片:

detectImage()

生成文件名:

randomFilename()

保存文件:

saveFile()

构建保存路径:

buildSavePath()

每个函数只负责一件事情,后续维护更加轻松。

第十阶段:Nginx

Go 程序监听:

127.0.0.1:8080

外部无法直接访问。

真正对外的是 Nginx

访问:

gallery.cc.cd

实际上是:

Browser
    │
HTTPS
    │
Nginx
    │
127.0.0.1:8080
    │
Gallery

这是非常经典的部署方式。

第十一阶段:HTTPS

使用 Certbot 申请:

gallery.cc.cd

以及:

www.gallery.cc.cd

证书。

随后:

Nginx 自动配置:

  • SSL
  • HTTP→HTTPS

上传返回的 URL 也改成:

https://gallery.cc.cd/...

目前功能还不算丰富,但基础设施已经非常完整。

照惯例放出源码:

https://github.com/pangzibaike/gallery

欢迎体验:

https://gallery.cc.cd/