【Golang 1.18】短網址產生器 - 極短篇

記得很久很久以前,短網址很流行,也許是因為網址太長的關係吧,當時很想自己做一個,覺得很厲害,後來去年發現一篇文章有解析它的原理,天啊,原來很簡單嘛,讓我們自己來做一個吧…

作業環境

項目 版本
CPU Apple M1
macOS Big Sur 12.3 arm64
Golang 1.18.2 arm64
Visual Studio Code 1.67 arm64
Postman 9.0.9 arm64
DB Browser for SQLite 3.12.1 x86_64
SQLite 3.31.0

原理說明

初始化設定

安裝套件

  • 這裡使用的套件如下…
go mod init william                   // 產生go.mod
go get -u gorm.io/gorm                // gorm本體
go get -u gorm.io/driver/sqlite       // gorm的sqlite-driver
go get -u github.com/gin-gonic/gin    // gin => 打API用

HTML轉址

  • 最主要的功能就是轉址,功能如圖所示…
  • 程式碼相當的簡單,也就不細說了…
<html><meta http-equiv='refresh' content='0;url=<URL>'/></html>

功能 API 方式
搜尋短轉址後轉址 http://localhost:8080/tinyUrl/<Code> GET
新增短網址對照表 http://localhost:8080/tinyUrl/ POST
package main

import (
	"crypto/md5"
	"fmt"
	"net/http"

	"william/utility"

	"github.com/gin-gonic/gin"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

const DatabasePath = "./material/sqliteDB.sqlite3" // 把資料庫存放在material資料夾下

// 短網址的長相
type TinyUrl struct {
	gorm.Model
	Url  string `json:"url" gorm:"index:idx_name,unique"`
	Code string `json:"code" gorm:"index:idx_name,unique"`
}

func main() {

	database, error := createDatabase(DatabasePath)

	if error != nil {
		return
	}

	database.AutoMigrate(&TinyUrl{})
	initRouter(database)
}

// 建立資料庫
func createDatabase(path string) (*gorm.DB, error) {
	database, error := gorm.Open(sqlite.Open(path), &gorm.Config{})
	return database, error
}

// 初始化Router相關設定
func initRouter(database *gorm.DB) {

	router := gin.Default()
	router.MaxMultipartMemory = 8 << 20

	registerWebAPI(router, database)

	router.Run(":8080")
}

// 註冊API
func registerWebAPI(router *gin.Engine, database *gorm.DB) {
	insertTinyUrl(router, database)
	selectTinyUrl(router, database)
}

// MARK: - WebAPI
// 新增短網址對照表 => MD5(URL)
func insertTinyUrl(router *gin.Engine, database *gorm.DB) {

	var tinyUrl TinyUrl

	router.POST("/tinyUrl", func(context *gin.Context) {

		dictionary := utility.RequestBodyToMap(context)
		result, error := tinyUrl._Insert(database, dictionary)

		utility.ContextJSON(context, http.StatusOK, result, error)
	})
}

// 搜尋短網址 => 轉址
func selectTinyUrl(router *gin.Engine, database *gorm.DB) {

	var tinyUrl TinyUrl

	router.GET("/tinyUrl/:code", func(context *gin.Context) {

		blog := "https://william-weng.github.io/tags/golang/"
		code := context.Param("code")
		result := tinyUrl._Select(database, code)
		refreshHtml := fmt.Sprintf("<html><meta http-equiv='refresh' content='0;url=%s'/></html>", blog)

		defer func() {
			context.Data(http.StatusOK, "text/html; charset=utf-8", []byte(refreshHtml))
		}()

		if result.ID == 0 {
			return
		}

		refreshHtml = fmt.Sprintf("<html><meta http-equiv='refresh' content='0;url=%s'/></html>", result.Url)
	})
}

// 新增短網址
func (tinyUrl *TinyUrl) _Insert(database *gorm.DB, dictionary map[string]interface{}) (map[string]interface{}, error) {

	isSuccess := false

	url := dictionary["url"].(string)
	bytes := md5.Sum([]byte(url))
	code := fmt.Sprintf("%x", bytes)[:6]

	error := database.Create(&TinyUrl{Url: url, Code: code}).Error

	if error == nil {
		isSuccess = true
	}

	result := map[string]interface{}{"isSuccess": isSuccess, "code": code}
	return result, error
}

// 搜尋短網址
func (_tinyUrl *TinyUrl) _Select(database *gorm.DB, code string) TinyUrl {

	var tinyUrl TinyUrl
	database.Take(&tinyUrl, "code=?", code)

	return tinyUrl
}

範例程式碼下載

後記