Go学习项目 使用Gin+Redis实现一个简单的url缩短器

A URL shortener in Go - with Gin & Redis

学习参考自Let’s build a URL shortener in Go - Part I (eddywm.com)

在本文档中,我们将探讨如何用Go 编程语言编写 url 缩短器,将使用 Redis 作为实现中超快速数据检索的存储机制。

借助该项目学习Gin 和 Redis 的使用。

一、项目设置

一. 1.项目设置

让我们设置项目并安装项目构建过程中所需的所有依赖项。

  • 初始化 go 项目,请确保您的系统中安装了 Go 1.11+

  • 安装项目依赖项。

$ go get github.com/go-redis/redis/v9

$ go get -u github.com/gin-gonic/gin

注意:在本教程的后续步骤中,您将需要在计算机上安装 Redis
如果您的计算机上尚未安装 Redis,您可以从此处的此链接下载它,并按照有关操作系统的说明进行安装

二、存储层

二. 1.Store Service设置

首先,我们必须在项目中创建我们的存储文件夹,因此进入项目目录,创建一个名为的子目录并继续创建 2 个空的 Go 文件:和(稍后我们将在其中为存储编写单元测试)store``store_service.go``store_service_test.go

└── store
    ├── store_service.go
    └── store_service_test.go
  • 我们将首先围绕 Redis 设置struct ,struct 将用作持久化和检索应用程序数据映射的接口。

打开文件并填写下面的代码。store_service.go

package store

import (
	"context"
	"fmt"
	"github.com/redis/go-redis/v9"
	"time"
)

// StorageService是一个结构体,它将Redis客户端作为其成员。
type StorageService struct {
	redisClient *redis.Client
}

var (
	// storeService是一个全局变量,它将在整个应用程序中使用。
	storeService = &StorageService{}
	// ctx是一个上下文,它将在整个应用程序中使用。
	ctx = context.Background()
)

// CacheDuration表示缓存的持续时间。
const CacheDuration = 6 * time.Hour
  • 定义结构后,我们终于可以初始化存储服务,在本例中为我们的 Redis 客户端。
func InitializeStore() *StorageService {
	redisClient := redis.NewClient(&redis.Options{
		Addr:     "localhost:6379",
		Password: "",
		DB:       0,
	})

	// 通过调用Ping()方法来检查Redis是否已经启动。
	pong, err := redisClient.Ping(ctx).Result()
	if err != nil {
		panic(fmt.Sprintf("Error init Redis: %v", err))
	}

	fmt.Printf("\nRedis started successfully: pong message = {%s}", pong)
	storeService.redisClient = redisClient
	return storeService
}

二. 2.Store API 设计和实施

// SaveUrlMapping方法将短网址和原始网址保存到Redis中。
func SaveUrlMapping(shortUrl string, originalUrl string, userId string) {
	err := storeService.redisClient.Set(ctx, shortUrl, originalUrl, CacheDuration).Err()
	if err != nil {
		panic(fmt.Sprintf("Failed saving key url | Error: %v - shortUrl: %s - originalUrl: %s\n", err, shortUrl, originalUrl))
	}
}

// RetrieveInitialUrl方法从Redis中检索原始网址。
func RetrieveInitialUrl(shortUrl string) string {
	result, err := storeService.redisClient.Get(ctx, shortUrl).Result()
	if err != nil {
		panic(fmt.Sprintf("Failed retrieving key url | Error: %v - shortUrl: %s\n", err, shortUrl))
	}
	return result
}

二. 3.单元和集成测试

为了保留最佳实践并避免将来出现意外回归,我们将不得不考虑存储层实现的单元和集成测试。

现在让我们开始安装这部分所需的测试工具。

go get github.com/stretchr/testify

在文件中store_service_test.go 添加下面代码

package store

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

const UserId = "e0dba740-fc4b-4977-872c-d360239e6b1a"
var testStoreService = &StorageService{}

// 初始化测试
func init() {
	testStoreService = InitializeStore()
}

func TestStoreInit(t *testing.T) {
	// 使用测试包中的assert.True()函数来断言条件testStoreService.redisClient != nil为真。
	assert.True(t, testStoreService.redisClient != nil)
}

func TestInsertionAndRetrieval(t *testing.T) {
	initialLink := "https://met2.fzu.edu.cn/meol/index.do"
	shortURL := "wpwpwp"

	// 保存短网址和原始网址
	SaveUrlMapping(shortURL, initialLink, UserId)

	// 从Redis中检索原始网址
	retrievedUrl := RetrieveInitialUrl(shortURL)

	// 使用测试包中的assert.Equal()函数来断言条件initialLink == retrievedUrl为真。
	assert.Equal(t, initialLink, retrievedUrl)
}

三、short url生成器

在上一部分我们能够设置、构建和测试链接缩短器的存储层。在这一部分中,我们将专门研究我们将用于散列和处理初始输入或长 url 的算法,使其成为与之对应的更小、更短的映射。

在选择算法时,我们确实要记住许多目标:

  • 最终输入应更短:最多 8 个字符
  • 应该易于人类阅读,避免混淆字符混淆,这些字符在大多数字体中通常相似。
  • 熵应该相当大,以避免在短链接生成中发生冲突。

三. 1.生成器算法

在此实现过程中,我们将使用两种主要方案:哈希函数和二进制到文本编码算法。

创建 2 个文件shorturl_generator.go``shorturl_generator_test.go``shortener并将它们放在上面的文件夹下后,我们的项目目录结构应该看起来像下面的树:

├── go.mod
├── go.sum
├── main.go
├── shortener
│   ├── shorturl_generator.go
│   └── shorturl_generator_test.go
└── store
    ├── store_service.go
    └── store_service_test.go

三. 2.缩短器实现

我们选择 **sha256 **和 Base58 来实现算法

使用 Base58 的理由

Base58 减少了字符输出中的混乱

  • 字符 0,O、I、l 在某些字体中使用时非常令人困惑,对于有视觉问题的人来说甚至更难区分。
  • 删除标点字符可防止换行符混淆。
  • 双击会将整数作为一个单词(如果全是字母数字)选择为一个单词。
package shortener

import (
	"crypto/sha256"
	"fmt"
	"github.com/itchyny/base58-go"
	"math/big"
	"os"
)

// 使用sha256Of()函数来计算输入字符串的SHA256哈希值。
// sha256可以将任何字符串转换为一个256位的哈希值。为了防止哈希碰撞,我们使用SHA256算法。
func sha256Of(input string) []byte {
	algorithm := sha256.New()
	algorithm.Write([]byte(input))
	return algorithm.Sum(nil)
}

// 为了使哈希值更短,我们使用base58Encoded()函数将其编码为Base58。
// base58可以去除容易混淆的字符,例如0和O,1和l,以及+和/。
func base58Encoded(bytes []byte) string {
	encoding := base58.BitcoinEncoding
	encoded, err := encoding.Encode(bytes)
	if err != nil {
		fmt.Println(err.Error())
		os.Exit(1)
	}
	return string(encoded)
}

// GenerateShortLink函数将原始网址和用户ID作为输入,并返回一个短网址。
// userId可以防止哈希碰撞。如果两个用户使用相同的原始网址,我们将为每个用户生成不同的短网址。
func GenerateShortLink(initialLink string, userId string) string {
	// 使用SHA256哈希算法来计算原始网址和用户ID的哈希值。
	urlHashBytes := sha256Of(initialLink + userId)
	// 将哈希值转换为一个大整数。
	generatedNumber := new(big.Int).SetBytes(urlHashBytes).Uint64()
	// 将大整数转换为Base58编码的字符串。
	finalString := base58Encoded([]byte(fmt.Sprintf("%d", generatedNumber)))
	// 返回前8个字符。
	return finalString[:8]
}

三. 3.缩短器单元测试

package shortener

import (
	"github.com/stretchr/testify/assert"
	"testing"
)

const UserId = "e0dba740-fc4b-4977-872c-d360239e6b1a"

func TestShortLinkGenerator(t *testing.T) {
	initialLink_1 := "https://opensource.tencent.com/summer-of-code"
	shortLink_1 := GenerateShortLink(initialLink_1, UserId)

	initialLink_2 := "https://opensource.alibaba.com/"
	shortLink_2 := GenerateShortLink(initialLink_2, UserId)

	initialLink_3 := "https://opensource.google/"
	shortLink_3 := GenerateShortLink(initialLink_3, UserId)

	assert.Equal(t, shortLink_1, "fSjjvszt")
	assert.Equal(t, shortLink_2, "GYw5AcQz")
	assert.Equal(t, shortLink_3, "EPz1wNJG")
}

四、转发(重定向)

四. 1.处理程序和port

在不浪费更多时间的情况下,让我们继续创建处理程序包并在其中定义我们的处理器函数。
创建一个名为 handler 的文件夹,并放入一个名为 .
之后,我们的项目目录应该看起来像下面的树:handlers.go

├── go.mod
├── go.sum
├── handler
│   └── handlers.go
├── main.go
├── shortener
│   ├── shorturl_generator.go
│   └── shorturl_generator_test.go
└── store
    ├── store_service.go
    └── store_service_test.go

四. 1.2. 实现

步驟1:我们将从实现’CreateShortUrl()'处理程序函数开始,这应该非常简单:

  • 我们将获取创建请求正文,解析它并提取初始长 url 和 userId。
  • 调用我们在[三. 2.缩短器实现](#三. 2.缩短器实现)中实现的并生成我们的缩短哈希值()。shortener.GenerateShortLink()
  • 最后将输出的映射与初始长 url 存储,在这里,我们将使用我们在[二. 2.Store API 设计和实施 ](#二. 2.Store API 设计和实施)实现的hash/shortUrl``store.SaveUrlMapping()

**步骤 2 :**第二步也是最后一步是关于实现重定向处理程序,它将包括:HandleShortUrlRedirect()

  • 从路径参数获取短网址/:shortUrl
  • 调用存储以检索与路径中提供的短 URL 对应的初始 URL。
  • 最后应用 http 重定向功能
package handler

import (
	"github.com/Poldroc/go-url-shortener/shortener"
	"github.com/Poldroc/go-url-shortener/store"
	"github.com/gin-gonic/gin"
	"net/http"
)

type UrlCreationRequest struct {
	LongUrl string `json:"long_url" binding:"required"`
	UserId  string `json:"user_id" binding:"required"`
}

// CreateShortUrl 将长网址转换为短网址。
func CreateShortUrl(c *gin.Context) {
	var creationRequest UrlCreationRequest
	if err := c.ShouldBindJSON(&creationRequest); err != nil {
		c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		return
	}

	shortUrl := shortener.GenerateShortLink(creationRequest.LongUrl, creationRequest.UserId)
	store.SaveUrlMapping(shortUrl, creationRequest.LongUrl, creationRequest.UserId)

	host := "http://localhost:8080/"
	c.JSON(200, gin.H{
		"message":   "short url created successfully",
		"short_url": host + shortUrl,
	})

}

// HandleShortUrlRedirect 将短网址重定向到原始网址。
func HandleShortUrlRedirect(c *gin.Context) {
	shortUrl := c.Param("shortUrl")
	initialUrl := store.RetrieveInitialUrl(shortUrl)
	c.Redirect(302, initialUrl)

}

main.go 中实现使用:

package main

import (
	"fmt"
	"github.com/Poldroc/go-url-shortener/handler"
	"github.com/Poldroc/go-url-shortener/store"
	"github.com/gin-gonic/gin"
)

func main() {
	r := gin.Default()
	r.GET("/", func(c *gin.Context) {
		c.JSON(200, gin.H{
			"message": "Welcome to the URL Shortener API",
		})
	})

	r.POST("/create-short-url", func(c *gin.Context) {
		handler.CreateShortUrl(c)
	})

	r.GET("/:shortUrl", func(c *gin.Context) {
		handler.HandleShortUrlRedirect(c)
	})

	// 初始化Redis
	store.InitializeStore()

	err := r.Run(":8080")
	if err != nil {
		panic(fmt.Sprintf("Failed to start the web server - Error: %v", err))
	}
}

四. 2.测试

  • 步骤1:运行/启动项目(文件是入口点)
    服务器应该在localhost:8808启动main.go
  • 步骤 2:请求 URL 缩短操作。我们可以将下面的请求正文post到指定的url。

    post到 http://localhost:8080/create-short-url

{
    "long_url": "https://opensource.tencent.com/summer-of-code",
    "user_id" : "e0dba740-fc4b-4977-872c-d360239e6b10"
}

​ 响应如下:

{
    "message": "short url created successfully",
    "short_url": "http://localhost:8080/UNNKLoJm"
}
  • 步骤 3:访问short_url,即可重定向回原始长 URL
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Poldroc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值