如果你厌倦了动辄十几 MB 的 Node.js 项目、层层嵌套的 React 组件,或者想试试不用重型框架也能搞定 Web 开发------那这篇教程就是为你量身打造的!
🧰 技术栈预览
| 模块 | 技术选型 | 说明 |
|---|---|---|
| 后端语言 | Go 1.23+ | 仅用标准库,零框架 |
| 数据存储 | Redis | 内存数据库,毫秒级读写 |
| 前端交互 | HTMX | 用 HTML 属性实现 AJAX,告别 JS 框架 |
| 样式方案 | TailwindCSS | CDN 引入,不用构建流程 |
| 部署方式 | 单二进制 + Docker(可选) | Go 天然支持静态编译 |
🛠️ 第一步:环境准备
1. 安装 Go
确保你已安装 Go ≥ 1.18(推荐 1.23):
bash
go version
# 输出:go version go1.23 linux/amd64
若未安装,请参考 Go 官方安装指南。
2. 初始化项目
bash
mkdir url-shortener && cd url-shortener
go mod init url-shortener
3. 安装 Redis 客户端库
虽然我们强调"标准库",但连接 Redis 还是需要一个客户端:
bash
go get github.com/go-redis/redis/v8
4. Redis 服务(推荐免费云实例)
- 注册 Redis Cloud 免费账号
- 创建一个 30MB 免费实例
- 获取
REDIS_HOST和REDIS_PASSWORD - 在项目根目录创建
.env文件:
env
REDIS_HOST=your-redis-host.redis.cloud.com:12345
REDIS_PASSWORD=your_strong_password
⚠️ 安全提醒 :务必把
.env加入.gitignore,别把密码提交到 GitHub!
🧱 第二步:核心逻辑实现
1. 项目结构
bash
url-shortener/
├── main.go
├── utils/
│ ├── shorten.go # 生成短码
│ └── store.go # Redis 读写
├── templates/
│ └── index.html # 前端页面
└── .env
2. 生成短码:utils/shorten.go
我们用 当前纳秒时间戳 + Base64 生成唯一短码(适合 demo,生产建议用自增 ID + Base62):
go
// utils/shorten.go
package utils
import (
"encoding/base64"
"fmt"
"time"
)
func GetShortCode() string {
ts := time.Now().UnixNano()
tsBytes := []byte(fmt.Sprintf("%d", ts))
key := base64.StdEncoding.EncodeToString(tsBytes)
// 移除末尾 ==,取 16 位之后部分提高唯一性
key = key[:len(key)-2]
return key[16:]
}
✅ 小知识 :Base64 编码后末尾常带
==,这是 padding,可以安全去掉。
3. Redis 操作:utils/store.go
go
// utils/store.go
package utils
import (
"context"
"fmt"
"os"
"github.com/go-redis/redis/v8"
)
func NewRedisClient() *redis.Client {
fmt.Println("Connecting to Redis:", os.Getenv("REDIS_HOST"))
return redis.NewClient(&redis.Options{
Addr: os.Getenv("REDIS_HOST"),
Password: os.Getenv("REDIS_PASSWORD"),
DB: 0,
})
}
func SetKey(ctx context.Context, rdb *redis.Client, key, value string) {
fmt.Printf("Storing %s → %s\n", key, value)
rdb.Set(ctx, key, value, 0) // 永不过期
}
func GetLongURL(ctx context.Context, rdb *redis.Client, shortCode string) (string, error) {
val, err := rdb.Get(ctx, shortCode).Result()
if err == redis.Nil {
return "", fmt.Errorf("短链接不存在")
} else if err != nil {
return "", fmt.Errorf("Redis 错误: %v", err)
}
return val, nil
}
4. 主程序:main.go
go
// main.go
package main
import (
"context"
"fmt"
"html/template"
"net/http"
"os"
"url-shortener/utils"
)
var ctx = context.Background()
func main() {
// 加载 .env(简单场景可手动设置,或用 godotenv)
if err := os.Setenv("REDIS_HOST", "your-host"); err != nil {
// 实际项目建议用 godotenv
}
if err := os.Setenv("REDIS_PASSWORD", "your-pass"); err != nil {
}
dbClient := utils.NewRedisClient()
_, err := dbClient.Ping(ctx).Result()
if err != nil {
panic("无法连接 Redis: " + err.Error())
}
// 首页
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("templates/index.html"))
tmpl.Execute(w, nil)
})
// 生成短链接
http.HandleFunc("/shorten", func(w http.ResponseWriter, r *http.Request) {
url := r.FormValue("url")
if url == "" {
http.Error(w, "URL 不能为空", http.StatusBadRequest)
return
}
shortCode := utils.GetShortCode()
utils.SetKey(ctx, dbClient, shortCode, url)
fullShort := fmt.Sprintf("http://localhost:8080/r/%s", shortCode)
// HTMX 响应:直接返回 HTML
fmt.Fprintf(w, `<p class="mt-4 text-green-600">短链接已生成:<a href="/r/%s" class="underline">%s</a></p>`,
shortCode, fullShort)
})
// 重定向
http.HandleFunc("/r/", func(w http.ResponseWriter, r *http.Request) {
shortCode := r.URL.Path[len("/r/"):]
longURL, err := utils.GetLongURL(ctx, dbClient, shortCode)
if err != nil {
http.Error(w, "链接不存在", http.StatusNotFound)
return
}
http.Redirect(w, r, longURL, http.StatusPermanentRedirect) // 301
})
fmt.Println("服务启动:http://localhost:8080")
http.ListenAndServe(":8080", nil)
}
🎨 第三步:前端界面(HTMX + Tailwind)
创建 templates/index.html:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>GoShort · 极简短链接</title>
<!-- HTMX: 轻量级 AJAX -->
<script src="https://unpkg.com/htmx.org@1.9.10"></script>
<!-- TailwindCSS -->
<script src="https://cdn.tailwindcss.com"></script>
</head>
<body class="bg-gray-100 min-h-screen flex items-center justify-center">
<div class="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
<h1 class="text-2xl font-bold text-center mb-6">🔗 GoShort</h1>
<form hx-post="/shorten" hx-target="#result" hx-swap="innerHTML">
<input
type="url"
name="url"
placeholder="请输入要缩短的网址"
class="w-full p-3 border border-gray-300 rounded mb-4"
required
/>
<button type="submit" class="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700">
生成短链接
</button>
</form>
<div id="result" class="mt-4 text-center"></div>
</div>
</body>
</html>
✨ HTMX 魔法:
hx-post="/shorten":点击按钮时自动发 POST 请求hx-target="#result":把响应内容插入到#result容器- 全程无 JS 写一行!
▶️ 运行效果
- 启动服务:
bash
go run main.go
-
打开浏览器访问
http://localhost:8080 -
输入一个长链接(如
https://www.example.com/very/long/path?query=123) -
点击"生成",下方自动出现短链接,点击即可跳转!

🚧 生产环境改进点
当前实现适合学习和小规模使用,若要上线,建议:
| 问题 | 改进方案 |
|---|---|
| 短码可能重复 | 改用 自增 ID + Base62 编码 (如 12345 → 3d7) |
| 无限长链接 | 添加 URL 校验、长度限制 |
| 无过期机制 | SetKey 支持 TTL(如 30 天后自动删除) |
| 安全风险 | 防止 SSRF(禁止内网 IP、file:// 等协议) |
| 无统计 | 在 Redis 中记录点击次数(INCR shortcode:hits) |
🌟 总结
我们用不到 200 行 Go 代码 + 一个 HTML 文件,实现了一个功能完整、响应迅速、部署简单的 URL 短链接服务。
核心收获:
-
Go 标准库足够强大,很多场景无需框架
-
Redis 不只是缓存,也可以作为主数据库
-
HTMX 让前端重回"简单",却保留交互能力
-
"少即是多"------轻量架构反而更易维护、更高效