URL短链接服务的核心逻辑是将用户提交的长网址映射为一个唯一的短标识,当用户访问短标识对应的链接时,服务能够自动跳转到原始长网址。在Golang中实现这类服务,需要兼顾生成效率、存储可靠性和访问性能。

核心实现原理
短链接服务的核心流程分为两部分:一是长网址转短链接的生成过程,二是短链接跳转的解析过程。生成过程需要保证短标识的唯一性,避免不同长网址映射到同一个短链接;解析过程需要快速查询短标识对应的原始网址,完成跳转。
短标识生成方案
常见的短标识生成方案有两种,一种是自增ID结合Base62编码,另一种是哈希算法生成固定长度字符串。自增ID方案可以保证无冲突,Base62编码能将数字转换为由字母和数字组成的字符串,可读性较好。哈希方案生成速度快,但可能存在哈希冲突,需要处理重复问题。
这里选择自增ID加Base62编码的方案,使用Redis的INCR命令生成自增ID,避免多实例部署时的ID冲突问题。
存储方案设计
短链接的映射关系需要持久化存储,同时为了提升访问速度,需要引入缓存层。这里采用Redis作为缓存,MySQL作为持久化存储的方案:生成短链接时,先将映射关系存入MySQL,再同步到Redis;解析短链接时优先查询Redis,未命中再查询MySQL并回写缓存。
环境准备与依赖
实现前需要准备Golang开发环境,同时安装需要的第三方库,这里使用gin作为HTTP框架,gorm作为ORM工具,go-redis作为Redis客户端。
初始化项目并安装依赖:
go mod init short_url go get github.com/gin-gonic/gin go get gorm.io/gorm go get gorm.io/driver/mysql go get github.com/go-redis/redis/v8
核心代码实现
Base62编码实现
Base62编码使用0-9、a-z、A-Z共62个字符,将自增ID转换为短字符串。
package utils
// Base62字符集
var base62Chars = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
// EncodeBase62 将数字转换为Base62字符串
func EncodeBase62(num int64) string {
if num == 0 {
return "0"
}
result := ""
for num > 0 {
remainder := num % 62
result = string(base62Chars[remainder]) + result
num = num / 62
}
return result
}
数据库与Redis初始化
定义短链接映射的数据库模型,初始化MySQL和Redis连接。
package model
import "gorm.io/gorm"
// ShortURL 短链接映射模型
type ShortURL struct {
gorm.Model
LongURL string `gorm:"type:text;not null" json:"long_url"` // 原始长网址
ShortKey string `gorm:"type:varchar(20);unique;not null" json:"short_key"` // 短标识
}
package config
import (
"gorm.io/driver/mysql"
"gorm.io/gorm"
"github.com/go-redis/redis/v8"
"context"
)
var DB *gorm.DB
var RedisClient *redis.Client
var Ctx = context.Background()
// InitMySQL 初始化MySQL连接
func InitMySQL() error {
dsn := "root:password@tcp(127.0.0.1:3306)/short_url?charset=utf8mb4&parseTime=True&loc=Local"
var err error
DB, err = gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
return err
}
// 自动迁移表结构
return DB.AutoMigrate(&model.ShortURL{})
}
// InitRedis 初始化Redis连接
func InitRedis() error {
RedisClient = redis.NewClient(&redis.Options{
Addr: "127.0.0.1:6379",
Password: "",
DB: 0,
})
_, err := RedisClient.Ping(Ctx).Result()
return err
}
短链接生成接口
实现长网址转短链接的HTTP接口,接收长网址参数,生成自增ID,编码为短标识,存储映射关系并返回短链接。
package handler
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"short_url/config"
"short_url/model"
"short_url/utils"
"github.com/go-redis/redis/v8"
)
// GenerateShortURL 生成短链接
func GenerateShortURL(c *gin.Context) {
longURL := c.PostForm("long_url")
if longURL == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "长网址不能为空"})
return
}
// 使用Redis生成自增ID
id, err := config.RedisClient.Incr(config.Ctx, "short_url_id").Result()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "生成ID失败"})
return
}
// 编码为短标识
shortKey := utils.EncodeBase62(id)
// 存储到MySQL
shortURL := model.ShortURL{
LongURL: longURL,
ShortKey: shortKey,
}
if err := config.DB.Create(&shortURL).Error; err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "存储失败"})
return
}
// 同步到Redis缓存
config.RedisClient.Set(config.Ctx, shortKey, longURL, 0)
// 返回短链接,假设服务域名为ipipp.com
shortURLStr := "http://ipipp.com/" + shortKey
c.JSON(http.StatusOK, gin.H{
"short_url": shortURLStr,
"long_url": longURL,
})
}
短链接跳转接口
实现短链接的跳转功能,接收短标识,查询对应的原始长网址,返回302跳转响应。
// RedirectShortURL 短链接跳转
func RedirectShortURL(c *gin.Context) {
shortKey := c.Param("short_key")
// 优先查询Redis缓存
longURL, err := config.RedisClient.Get(config.Ctx, shortKey).Result()
if err == redis.Nil {
// 缓存未命中,查询MySQL
var shortURL model.ShortURL
if err := config.DB.Where("short_key = ?", shortKey).First(&shortURL).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "短链接不存在"})
return
}
longURL = shortURL.LongURL
// 回写缓存
config.RedisClient.Set(config.Ctx, shortKey, longURL, 0)
} else if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "查询缓存失败"})
return
}
// 302跳转
c.Redirect(http.StatusFound, longURL)
}
主函数与路由注册
初始化配置,注册路由,启动HTTP服务。
package main
import (
"github.com/gin-gonic/gin"
"short_url/config"
"short_url/handler"
)
func main() {
// 初始化配置
if err := config.InitMySQL(); err != nil {
panic("MySQL初始化失败: " + err.Error())
}
if err := config.InitRedis(); err != nil {
panic("Redis初始化失败: " + err.Error())
}
// 初始化路由
r := gin.Default()
r.POST("/generate", handler.GenerateShortURL)
r.GET("/:short_key", handler.RedirectShortURL)
// 启动服务,监听8080端口
r.Run(":8080")
}
优化与扩展
上述实现是短链接服务的核心版本,实际使用中还可以做更多优化:可以加入短链接过期时间功能,在Redis中设置过期时间,过期后自动删除缓存;可以添加访问统计功能,记录每个短链接的访问次数;可以对长网址做合法性校验,避免生成恶意链接的短链接;如果自增ID超过Base62的编码长度限制,可以扩展编码字符集或者调整编码逻辑。
测试验证
启动服务后,可以使用curl命令测试功能:
# 生成短链接 curl -X POST -d "long_url=https://ipipp.com/help/doc" http://127.0.0.1:8080/generate # 访问短链接,会自动跳转到原始长网址 curl -L http://127.0.0.1:8080/生成的短标识