短链接服务的核心逻辑概述
短链接服务的核心目标是将冗长的原始URL转换为较短的字符串标识,同时保证短标识可以唯一映射回原始URL。整体流程分为两个核心部分:一是生成短链接的请求处理,接收长URL并返回对应的短链接;二是短链接的跳转处理,接收短链接标识,查询对应的原始URL并返回302重定向响应。

整体架构设计
一个基础的Golang短链接服务通常包含以下模块:
- HTTP服务模块:负责接收用户的生成短链接、跳转短链接的请求
- 短码生成模块:将长URL转换为唯一的短字符串标识
- 存储模块:保存短码与原始长URL的映射关系,本文使用Redis作为存储组件
- 跳转处理模块:根据短码查询原始URL并返回重定向响应
环境准备与依赖引入
实现前需要准备Golang开发环境,同时引入Redis客户端依赖用于数据存储,执行以下命令安装依赖:
go get github.com/go-redis/redis/v8
短码生成逻辑实现
短码生成需要保证唯一性,同时尽可能缩短长度。常用的方案是对长URL进行哈希计算,再对哈希结果进行Base62编码,截取固定长度的字符串作为短码。以下是短码生成的核心代码:
package main
import (
"crypto/md5"
"encoding/hex"
"math/rand"
"time"
)
// 定义Base62字符集
var base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
// 生成短码的核心函数
func generateShortCode(longURL string) string {
// 初始化随机种子
rand.Seed(time.Now().UnixNano())
// 对长URL进行MD5哈希
hash := md5.Sum([]byte(longURL + string(rand.Intn(1000))))
hashStr := hex.EncodeToString(hash[:])
// 将哈希字符串转换为十进制数
var num int64
for _, c := range hashStr {
num = num*16 + int64(c-'0')
if c >= 'a' && c <= 'f' {
num -= 39 // 调整字母对应的数值
}
}
// Base62编码
shortCode := ""
for num > 0 {
shortCode = string(base62Chars[num%62]) + shortCode
num = num / 62
}
// 截取前8位作为短码,不足则补位
if len(shortCode) > 8 {
return shortCode[:8]
}
for len(shortCode) < 8 {
shortCode = base62Chars[0:1] + shortCode
}
return shortCode
}
存储模块实现
使用Redis存储短码与长URL的映射关系,设置合理的过期时间避免存储无限膨胀。以下是Redis操作的封装代码:
package main
import (
"context"
"time"
"github.com/go-redis/redis/v8"
)
var ctx = context.Background()
var rdb *redis.Client
// 初始化Redis客户端
func initRedis() {
rdb = redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379", // Redis地址
Password: "", // 无密码
DB: 0, // 默认DB
})
}
// 保存短码与长URL的映射,设置24小时过期
func saveMapping(shortCode, longURL string) error {
return rdb.Set(ctx, shortCode, longURL, 24*time.Hour).Err()
}
// 根据短码查询原始长URL
func getLongURL(shortCode string) (string, error) {
return rdb.Get(ctx, shortCode).Result()
}
HTTP服务核心逻辑实现
使用Golang标准库的net/http包搭建HTTP服务,处理两个核心接口:POST /generate用于生成短链接,GET /{shortCode}用于短链接跳转。完整服务代码如下:
package main
import (
"fmt"
"io/ioutil"
"net/http"
"strings"
)
func main() {
// 初始化Redis
initRedis()
// 注册生成短链接的接口
http.HandleFunc("/generate", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "仅支持POST请求", http.StatusMethodNotAllowed)
return
}
// 读取请求体中的长URL
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, "读取请求失败", http.StatusBadRequest)
return
}
longURL := strings.TrimSpace(string(body))
if longURL == "" {
http.Error(w, "长URL不能为空", http.StatusBadRequest)
return
}
// 生成短码
shortCode := generateShortCode(longURL)
// 保存映射
err = saveMapping(shortCode, longURL)
if err != nil {
http.Error(w, "保存映射失败", http.StatusInternalServerError)
return
}
// 返回短链接,假设服务部署在ipipp.com
shortURL := fmt.Sprintf("http://ipipp.com/%s", shortCode)
w.Write([]byte(shortURL))
})
// 注册短链接跳转接口
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
// 提取路径中的短码
shortCode := strings.TrimPrefix(r.URL.Path, "/")
if shortCode == "" {
http.Error(w, "短码不能为空", http.StatusBadRequest)
return
}
// 查询原始长URL
longURL, err := getLongURL(shortCode)
if err != nil {
http.Error(w, "短链接不存在", http.StatusNotFound)
return
}
// 返回302重定向
http.Redirect(w, r, longURL, http.StatusFound)
})
// 启动服务,监听8080端口
fmt.Println("短链接服务启动,监听端口8080")
http.ListenAndServe(":8080", nil)
}
服务测试与优化建议
启动服务后,可以通过curl命令测试功能:
# 生成短链接 curl -X POST -d "https://ipipp.com/article/golang-short-url" http://127.0.0.1:8080/generate # 访问短链接会跳转到原始长URL curl -L http://127.0.0.1:8080/生成的短码
实际生产环境中还可以做以下优化:
- 增加短码冲突检测逻辑,生成短码后先查询是否已存在,存在则重新生成
- 支持自定义短码功能,允许用户指定短码后缀
- 增加访问统计功能,记录每个短链接的访问次数
- 添加请求频率限制,避免恶意请求消耗资源