用 Go + Redis + HTMX 手撸一个超快 URL 短链接服务 🚀

如果你厌倦了动辄十几 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_HOSTREDIS_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 写一行

▶️ 运行效果

  1. 启动服务:
bash 复制代码
go run main.go
  1. 打开浏览器访问 http://localhost:8080

  2. 输入一个长链接(如 https://www.example.com/very/long/path?query=123

  3. 点击"生成",下方自动出现短链接,点击即可跳转!


🚧 生产环境改进点

当前实现适合学习和小规模使用,若要上线,建议:

问题 改进方案
短码可能重复 改用 自增 ID + Base62 编码 (如 123453d7
无限长链接 添加 URL 校验、长度限制
无过期机制 SetKey 支持 TTL(如 30 天后自动删除)
安全风险 防止 SSRF(禁止内网 IP、file:// 等协议)
无统计 在 Redis 中记录点击次数(INCR shortcode:hits

🌟 总结

我们用不到 200 行 Go 代码 + 一个 HTML 文件,实现了一个功能完整、响应迅速、部署简单的 URL 短链接服务。

核心收获

  • Go 标准库足够强大,很多场景无需框架

  • Redis 不只是缓存,也可以作为主数据库

  • HTMX 让前端重回"简单",却保留交互能力

  • "少即是多"------轻量架构反而更易维护、更高效

相关推荐
codervibe2 小时前
Spring Boot 热启动配置实战:从手动重启到秒级反馈
spring boot·后端
skyeeeeee2 小时前
kubeadm安装k8s集群
后端·kubernetes
chxii2 小时前
Spring Boot 响应给客户端的常见返回类型
java·spring boot·后端
韩立学长2 小时前
【开题答辩实录分享】以《植物爱好者交流平台的设计与实现》为例进行答辩实录分享
spring boot·后端·mysql
Wzx1980123 小时前
go基础语法练习
开发语言·后端·golang
sp423 小时前
漫谈 Java 轻量级的模板技术:从字符串替换到复杂模板
java·后端
2301_795167203 小时前
玩转Rust高级应用. ToOwned trait 提供的是一种更“泛化”的Clone 的功能,Clone一般是从&T类型变量创造一个新的T类型变量
开发语言·后端·rust
草莓熊Lotso3 小时前
C++ 方向 Web 自动化测试实战:以博客系统为例,从用例到报告全流程解析
前端·网络·c++·人工智能·后端·python·功能测试
一 乐4 小时前
旅游|内蒙古景点旅游|基于Springboot+Vue的内蒙古景点旅游管理系统设计与实现(源码+数据库+文档)
开发语言·前端·数据库·vue.js·spring boot·后端·旅游