【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 | 
原理說明
- 原理其實很單純,就是將URL => 壓縮成一串文字,其中使用MD5(https://zh.wikipedia.org/zh-tw/MD5) / SHA1(https://zh.wikipedia.org/wiki/SHA-1) / SHA256等公開的演算法去求值,也可以是自己寫的演算法…
 
    
初始化設定
安裝套件
- 這裡使用的套件如下…
 
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
}