欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用Go實(shí)現(xiàn)偽靜態(tài)URL重寫功能

 更新時(shí)間:2024年08月22日 08:25:29   作者:Sinclair  
在Web開發(fā)中,偽靜態(tài)URL已成為優(yōu)化網(wǎng)站架構(gòu)和提升SEO的常用技術(shù)手段,偽靜態(tài)URL是一種介于動態(tài)URL和靜態(tài)URL之間的解決方案,本文給大家介紹了如何使用Go實(shí)現(xiàn)偽靜態(tài)URL重寫功能,需要的朋友可以參考下

在Web開發(fā)中,偽靜態(tài)URL已成為優(yōu)化網(wǎng)站架構(gòu)和提升SEO的常用技術(shù)手段。尤其是在內(nèi)容管理系統(tǒng)(CMS)中,靈活的URL重寫功能不僅能改善用戶體驗(yàn),還能幫助網(wǎng)站更好地與搜索引擎對接。URL的可讀性和結(jié)構(gòu)化直接影響搜索引擎的索引質(zhì)量和排名。

在安企CMS的設(shè)計(jì)中,為了適應(yīng)客戶個性化的需求,偽靜態(tài)URL重寫功能應(yīng)運(yùn)而生。通過這一功能,客戶可以根據(jù)業(yè)務(wù)需求自定義站點(diǎn)的URL格式,從而將動態(tài)URL重寫為更易讀的靜態(tài)化URL。這種機(jī)制兼具靈活性和可擴(kuò)展性,能夠滿足各種不同的應(yīng)用場景。

什么是偽靜態(tài)URL?

偽靜態(tài)URL是一種介于動態(tài)URL和靜態(tài)URL之間的解決方案。動態(tài)URL通常包含查詢參數(shù),如 ?id=123?category=sports,而靜態(tài)URL則是固定的文件路徑,如 /article/123.html/sports/article-456.html。偽靜態(tài)URL通過URL重寫技術(shù),將原本需要傳遞參數(shù)的動態(tài)頁面轉(zhuǎn)化為類似靜態(tài)頁面的URL格式,保留了動態(tài)頁面的功能,卻呈現(xiàn)出靜態(tài)頁面的URL形式。

這樣做的好處包括:

  • SEO優(yōu)化:更簡潔、關(guān)鍵詞友好的URL格式有助于提高搜索引擎排名。
  • 用戶體驗(yàn)提升:更直觀的URL結(jié)構(gòu)讓用戶更容易記住和理解。
  • 隱藏技術(shù)細(xì)節(jié):可以避免泄露網(wǎng)站底層技術(shù)實(shí)現(xiàn)細(xì)節(jié),提升安全性。

實(shí)現(xiàn)原理

偽靜態(tài)URL重寫的核心在于將客戶端請求的URL路徑與后端真實(shí)的資源路徑進(jìn)行映射。在不同的應(yīng)用場景下,不同客戶可能有不同的URL重寫需求,安企CMS通過內(nèi)置的變量和自定義規(guī)則的支持,能夠靈活地滿足這些需求。

例如:

  • 客戶A:希望文章的URL形式為 /article/{id}.html,即通過文章ID來訪問內(nèi)容。
  • 客戶B:希望URL形式為 /article/{filename}.html,即通過文章的文件名進(jìn)行訪問。
  • 客戶C:希望URL的格式更為復(fù)雜,如 /{catname}/{filename}.html,即通過分類名稱和文章文件名組合。

為了實(shí)現(xiàn)這一功能,安企CMS提供了一系列內(nèi)置的變量,這些變量可以用來動態(tài)生成偽靜態(tài)URL。常用的變量包括:

  • {id}:文章的唯一ID。
  • {filename}:文章的文件名,通常是標(biāo)題或自定義的唯一標(biāo)識符。
  • {catid}:分類的唯一ID。
  • {catname}:文章所屬的分類名稱。
  • {multicatname}:多級分類結(jié)構(gòu),適用于嵌套分類。
  • {module}:文檔模型名稱,比如文章、產(chǎn)品、案例等。
  • {year}、{month}{day}、{hour}、{minute}、{second}:文章發(fā)布日期的時(shí)間戳信息。
  • {page}:文章的分頁信息,通常在欄目頁中使用。

用戶可以根據(jù)業(yè)務(wù)需求,利用這些變量輕松編寫URL重寫規(guī)則,實(shí)現(xiàn)對URL格式的完全控制。

URL重寫規(guī)則示例

假設(shè)客戶希望實(shí)現(xiàn)以下幾種URL規(guī)則:

  • 單文章ID訪問

    • 規(guī)則:/article/{id}.html
    • 實(shí)例URL:/article/123.html
  • 文件名訪問

    • 規(guī)則:/article/{filename}.html
    • 實(shí)例URL:/article/how-to-code.html
  • 分類+文件名訪問

    • 規(guī)則:/{catname}/{filename}.html
    • 實(shí)例URL:/technology/golang-introduction.html
  • 多級分類+文件名訪問

    • 規(guī)則:/{multicatname}/{filename}.html
    • 實(shí)例URL:/programming/backend/golang-best-practices.html

通過以上規(guī)則,安企CMS能夠自動將用戶訪問的URL映射到對應(yīng)的后端資源,并執(zhí)行動態(tài)渲染。

代碼實(shí)現(xiàn)

在Go語言中,可以使用內(nèi)置的HTTP路由機(jī)制和正則表達(dá)式進(jìn)行URL重寫。以下是一些核心步驟的概述:

  • 路由解析:使用Go的iris框架,根據(jù)請求的URL進(jìn)行匹配。
  • 正則表達(dá)式匹配:通過正則表達(dá)式提取URL中的變量,例如從/article/{id}.html中提取id。
  • 動態(tài)重寫:根據(jù)提取到的變量和規(guī)則,將請求映射到真實(shí)的資源路徑上。
  • 重定向或處理:將請求傳遞給處理器函數(shù),返回相應(yīng)的HTML或JSON響應(yīng)。

完整的代碼實(shí)現(xiàn)通常包括定義路由規(guī)則、設(shè)置正則表達(dá)式模式,以及為每個URL模式創(chuàng)建對應(yīng)的處理函數(shù)。這些處理函數(shù)會根據(jù)匹配到的URL參數(shù)進(jìn)行數(shù)據(jù)庫查詢或業(yè)務(wù)邏輯處理,最后生成對應(yīng)的內(nèi)容輸出。

路由解析

在路由解析中,我們使用 path 變量來處理,因?yàn)?path 變量會匹配到任何路徑。

func Register(app *iris.Application) {
  app.Get("/{path:path}", controller.ReRouteContext)
}

正則表達(dá)式匹配

由于 path 變量會匹配到任何路徑,所以我們需要先驗(yàn)證文件是否存在,如果存在,則直接返回文件,而不再做正則匹配。

handler.go

函數(shù) ReRouteContext 功能是在Iris框架中處理路由驗(yàn)證和文件服務(wù)。首先,它解析路由參數(shù)并驗(yàn)證文件是否存在,如果存在則提供文件服務(wù)。如果文件不存在,則根據(jù)路由參數(shù)設(shè)置上下文參數(shù)和值,并根據(jù)匹配的路由參數(shù)執(zhí)行不同的處理函數(shù),如歸檔詳情、分類頁面或首頁。如果沒有匹配的路由,則返回404頁面。

func ReRouteContext(ctx iris.Context) {
	params, _ := parseRoute(ctx)
	// 先驗(yàn)證文件是否真的存在,如果存在,則fileServe
	exists := FileServe(ctx)
	if exists {
		return
	}
	
	for i, v := range params {
		if len(i) == 0 {
			continue
		}
		ctx.Params().Set(i, v)
		if i == "page" && v > "0" {
			ctx.Values().Set("page", v)
		}
	}

	switch params["match"] {
	case "notfound":
		// 走到 not Found
		break
	case "archive":
		ArchiveDetail(ctx)
		return
		return
	case "category":
		CategoryPage(ctx)
		return
	case "index":
		IndexPage(ctx)
		return
		return
	}

	//如果沒有合適的路由,則報(bào)錯
	NotFound(ctx)
}

該函數(shù) FileServe 的作用如下:

獲取請求路徑。 檢查路徑是否指向公共目錄下的文件。 如果文件存在,則直接提供該靜態(tài)文件。 返回 true 如果文件被成功提供,否則返回 false。

// FileServe 靜態(tài)文件處理,靜態(tài)文件存放在public目錄中,因此訪問路徑為/public/xxx
func FileServe(ctx iris.Context) bool {
	uri := ctx.RequestPath(false)
	if uri != "/" && !strings.HasSuffix(uri, "/") {
		baseDir := fmt.Sprintf("%spublic", RootPath)
		uriFile := baseDir + uri
		_, err := os.Stat(uriFile)
		if err == nil {
			ctx.ServeFile(uriFile)
			return true
		}
	}
	
	return false
}

函數(shù) parseRoute 用于解析路由路徑,并根據(jù)不同的路徑模式填充映射matchMap。主要步驟如下:

獲取請求中的path參數(shù)值。 如果path為空,則匹配“首頁”。 如果path以uploads/或static/開頭,則直接返回,表示靜態(tài)資源。 使用正則表達(dá)式匹配path: 對于“分類”規(guī)則,提取相關(guān)信息并存儲至matchMap。 驗(yàn)證提取的“模塊”是否存在,以及是否與“分類”沖突。 若匹配成功,返回結(jié)果。 對于“文檔”規(guī)則,執(zhí)行類似的匹配邏輯。 如果所有規(guī)則都不匹配,則標(biāo)記為“未找到”。 最終返回填充后的matchMap和一個布爾值true。

// parseRoute 正則表達(dá)式解析路由 
func parseRoute(ctx iris.Context) (map[string]string, bool) {
	//這里總共有2條正則規(guī)則,需要逐一匹配
	// 由于用戶可能會采用相同的配置,因此這里需要嘗試多次讀取
	matchMap := map[string]string{}
	paramValue := ctx.Params().Get("path")
	// index
	if paramValue == "" {
		matchMap["match"] = "index"
		return matchMap, true
	}
	// 靜態(tài)資源直接返回
	if strings.HasPrefix(paramValue, "uploads/") ||
		strings.HasPrefix(paramValue, "static/") {
		return matchMap, true
	}
	rewritePattern := service.ParsePatten(false)
	//category
	reg = regexp.MustCompile(rewritePattern.CategoryRule)
	match = reg.FindStringSubmatch(paramValue)
	if len(match) > 1 {
		matchMap["match"] = "category"
		for i, v := range match {
			key := rewritePattern.CategoryTags[i]
			if i == 0 {
				key = "route"
			}
			matchMap[key] = v
		}
		if matchMap["catname"] != "" {
			matchMap["filename"] = matchMap["catname"]
		}
		if matchMap["multicatname"] != "" {
			chunkCatNames := strings.Split(matchMap["multicatname"], "/")
			matchMap["filename"] = chunkCatNames[len(chunkCatNames)-1]
		}
		if matchMap["module"] != "" {
			// 需要先驗(yàn)證是否是module
			module := service.GetModuleFromCacheByToken(matchMap["module"])
			if module != nil {
				if matchMap["filename"] != "" {
					// 這個規(guī)則可能與下面的沖突,因此檢查一遍
					category := service.GetCategoryFromCacheByToken(matchMap["filename"])
					if category != nil {
						return matchMap, true
					}
				} else {
					return matchMap, true
				}
			}
		} else {
			if matchMap["filename"] != "" {
				// 這個規(guī)則可能與下面的沖突,因此檢查一遍
				category := service.GetCategoryFromCacheByToken(matchMap["filename"])
				if category != nil {
					return matchMap, true
				}
			} else {
				return matchMap, true
			}
		}
		matchMap = map[string]string{}
	}
	//最后archive
	reg = regexp.MustCompile(rewritePattern.ArchiveRule)
	match = reg.FindStringSubmatch(paramValue)
	if len(match) > 1 {
		matchMap["match"] = "archive"
		for i, v := range match {
			key := rewritePattern.ArchiveTags[i]
			if i == 0 {
				key = "route"
			}
			matchMap[key] = v
		}
		if matchMap["module"] != "" {
			// 需要先驗(yàn)證是否是module
			module := service.GetModuleFromCacheByToken(matchMap["module"])
			if module != nil {
				return matchMap, true
			}
		} else {
			return matchMap, true
		}
	}

	//不存在,定義到notfound
	matchMap["match"] = "notfound"
	return matchMap, true
}

service/rewrite.go

代碼主要功能是解析和應(yīng)用URL重寫規(guī)則。定義了結(jié)構(gòu)體RewritePattern和相關(guān)操作,以解析配置中的URL模式,并生成正則表達(dá)式規(guī)則,用于匹配和重寫URL。

結(jié)構(gòu)體RewritePattern:

該結(jié)構(gòu)體包含了一些字段,用于存儲檔案和分類的規(guī)則及其標(biāo)簽。 Archive和Category字段存儲檔案和分類的基本路徑模式。 ArchiveRule和CategoryRule字段存儲處理后的正則表達(dá)式規(guī)則。 ArchiveTags和CategoryTags字段分別存儲檔案和分類中可變部分(標(biāo)簽)的具體內(nèi)容。 Parsed字段標(biāo)記該模式是否已經(jīng)被解析過。 結(jié)構(gòu)體replaceChar和變量needReplace:

replaceChar結(jié)構(gòu)體用于存儲需要被轉(zhuǎn)義的字符及其轉(zhuǎn)義后的值。 needReplace變量定義了一組需要轉(zhuǎn)義的字符,如/、*、+等。

變量replaceParams:

replaceParams是一個映射,用于存儲URL模式中的變量及其對應(yīng)的正則表達(dá)式。如{id}對應(yīng)([\d]+),即匹配一個或多個數(shù)字。

函數(shù)GetRewritePatten:

該函數(shù)用于獲取或重用已解析的URL重寫模式。如果parsedPatten不為空且不需要重新解析,則直接返回;否則,調(diào)用parseRewritePatten進(jìn)行解析。

函數(shù)parseRewritePatten:

該函數(shù)解析原始的URL模式字符串,將其拆分為檔案和分類的部分,并存儲到RewritePattern實(shí)例中。

函數(shù)ParsePatten:

該函數(shù)執(zhí)行具體的解析操作,包括替換特殊字符、應(yīng)用變量對應(yīng)的正則表達(dá)式,并將最終的規(guī)則應(yīng)用到相應(yīng)的字段中。

type RewritePatten struct {
	Archive      string `json:"archive"`
	Category     string `json:"category"`

	ArchiveRule      string
	CategoryRule     string

	ArchiveTags      map[int]string
	CategoryTags     map[int]string
	Parsed bool
}


type replaceChar struct {
	Key   string
	Value string
}

var needReplace = []replaceChar{
	{Key: "/", Value: "\\/"},
	{Key: "*", Value: "\\*"},
	{Key: "+", Value: "\\+"},
	{Key: "?", Value: "\\?"},
	{Key: ".", Value: "\\."},
	{Key: "-", Value: "\\-"},
	{Key: "[", Value: "\\["},
	{Key: "]", Value: "\\]"},
	{Key: ")", Value: ")?"}, //fix?  map無序,可能會出現(xiàn)?混亂
}

var replaceParams = map[string]string{
	"{id}":           "([\\d]+)",
	"{filename}":     "([^\\/]+?)",
	"{catname}":      "([^\\/]+?)",
	"{multicatname}": "(.+?)",
	"{module}":       "([^\\/]+?)",
	"{catid}":        "([\\d]+)",
	"{year}":         "([\\d]{4})",
	"{month}":        "([\\d]{2})",
	"{day}":          "([\\d]{2})",
	"{hour}":         "([\\d]{2})",
	"{minute}":       "([\\d]{2})",
	"{second}":       "([\\d]{2})",
	"{page}":         "([\\d]+)",
}

var parsedPatten *RewritePatten

func GetRewritePatten(focus bool) *RewritePatten {
	if parsedPatten != nil && !focus {
		return parsedPatten
	}
	
  parsedPatten = parseRewritePatten(PluginRewrite.Patten)

	return parsedPatten
}

// parseRewritePatten 才需要解析
// 一共2行,分別是文章詳情、分類,===和前面部分不可修改。
// 變量由花括號包裹{},如{id}。可用的變量有:數(shù)據(jù)ID {id}、數(shù)據(jù)自定義鏈接名 {filename}、分類自定義鏈接名 {catname}、分類ID {catid},分頁ID {page},分頁需要使用()處理,用來首頁忽略。如:(/{page})或(_{page})
func parseRewritePatten(patten string) *RewritePatten {
	parsedPatten := &RewritePatten{}
	// 再解開
	pattenSlice := strings.Split(patten, "\n")
	for _, v := range pattenSlice {
		singlePatten := strings.Split(v, "===")
		if len(singlePatten) == 2 {
			val := strings.TrimSpace(singlePatten[1])

			switch strings.TrimSpace(singlePatten[0]) {
			case "archive":
				parsedPatten.Archive = val
			case "category":
				parsedPatten.Category = val
			}
		}
	}
	
	return parsedPatten
}

var mu sync.Mutex

func ParsePatten(focus bool) *RewritePatten {
	mu.Lock()
	defer mu.Unlock()
	GetRewritePatten(focus)
	if parsedPatten.Parsed {
		return parsedPatten
	}

	parsedPatten.ArchiveTags = map[int]string{}
	parsedPatten.CategoryTags = map[int]string{}

	pattens := map[string]string{
		"archive":      parsedPatten.Archive,
		"category":     parsedPatten.Category,
	}

	for key, item := range pattens {
		n := 0
		str := ""
		for _, v := range item {
			if v == '{' {
				n++
				str += string(v)
			} else if v == '}' {
				str = strings.TrimLeft(str, "{")
				if str == "page" {
					//page+1
					n++
				}
				switch key {
				case "archive":
					parsedPatten.ArchiveTags[n] = str
				case "category":
					parsedPatten.CategoryTags[n] = str
				}
				//重置
				str = ""
			} else if str != "" {
				str += string(v)
			}
		}
	}

	//移除首個 /
	parsedPatten.ArchiveRule = strings.TrimLeft(parsedPatten.Archive, "/")
	parsedPatten.CategoryRule = strings.TrimLeft(parsedPatten.Category, "/")

	for _, r := range needReplace {
		if strings.Contains(parsedPatten.ArchiveRule, r.Key) {
			parsedPatten.ArchiveRule = strings.ReplaceAll(parsedPatten.ArchiveRule, r.Key, r.Value)
		}
		if strings.Contains(parsedPatten.CategoryRule, r.Key) {
			parsedPatten.CategoryRule = strings.ReplaceAll(parsedPatten.CategoryRule, r.Key, r.Value)
		}
	}

	for s, r := range replaceParams {
		if strings.Contains(parsedPatten.ArchiveRule, s) {
			parsedPatten.ArchiveRule = strings.ReplaceAll(parsedPatten.ArchiveRule, s, r)
		}
		if strings.Contains(parsedPatten.CategoryRule, s) {
			parsedPatten.CategoryRule = strings.ReplaceAll(parsedPatten.CategoryRule, s, r)
		}
	}
	//修改為強(qiáng)制包裹
	parsedPatten.ArchiveRule = fmt.Sprintf("^%s$", parsedPatten.ArchiveRule)
	parsedPatten.CategoryRule = fmt.Sprintf("^%s$", parsedPatten.CategoryRule)
	parsedPatten.PageRule = fmt.Sprintf("^%s$", parsedPatten.PageRule)
	parsedPatten.ArchiveIndexRule = fmt.Sprintf("^%s$", parsedPatten.ArchiveIndexRule)
	parsedPatten.TagIndexRule = fmt.Sprintf("^%s$", parsedPatten.TagIndexRule)
	parsedPatten.TagRule = fmt.Sprintf("^%s$", parsedPatten.TagRule)

	//標(biāo)記替換過
  parsedPatten.Parsed = true

	return parsedPatten
}

通過這篇文章介紹偽靜態(tài)URL重寫的基本原理、應(yīng)用場景以及在Go語言中的實(shí)現(xiàn)思路。對于開發(fā)者來說,了解并靈活應(yīng)用這一技術(shù)將有助于創(chuàng)建更加優(yōu)化和用戶友好的Web系統(tǒng)。

以上就是使用Go實(shí)現(xiàn)偽靜態(tài)URL重寫功能的詳細(xì)內(nèi)容,更多關(guān)于Go URL重寫的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Golang算法問題之?dāng)?shù)組按指定規(guī)則排序的方法分析

    Golang算法問題之?dāng)?shù)組按指定規(guī)則排序的方法分析

    這篇文章主要介紹了Golang算法問題之?dāng)?shù)組按指定規(guī)則排序的方法,結(jié)合實(shí)例形式分析了Go語言數(shù)組排序相關(guān)算法原理與操作技巧,需要的朋友可以參考下
    2017-02-02
  • 解決golang http.FileServer 遇到的坑

    解決golang http.FileServer 遇到的坑

    這篇文章主要介紹了解決golang http.FileServer 遇到的坑,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12
  • golang并發(fā)鎖使用詳解

    golang并發(fā)鎖使用詳解

    這篇文章主要介紹了golang并發(fā)鎖使用詳解的相關(guān)資料,需要的朋友可以參考下
    2023-02-02
  • Go中的新增對模糊測試的支持

    Go中的新增對模糊測試的支持

    這篇文章主要為大家介紹了Go中的新增對模糊測試的支持,文中還包含了一些功能實(shí)驗(yàn)性測試分析有需要的朋友可以借鑒參考下,希望能夠有所幫助
    2022-03-03
  • 淺析Go語言中的棧和先進(jìn)先出原則

    淺析Go語言中的棧和先進(jìn)先出原則

    這篇文章主要來和大家探討一樣如何在Go語言中實(shí)現(xiàn)和使用堆棧,以及堆棧如何遵循先進(jìn)先出 (FIFO) 原則,文中的示例代碼簡潔易懂,需要的可以參考一下
    2023-07-07
  • 解讀go在遍歷map過程中刪除成員是否安全

    解讀go在遍歷map過程中刪除成員是否安全

    在Go語言中,通過for range遍歷map時(shí)可以安全地刪除當(dāng)前遍歷到的元素,因?yàn)楸闅v過程中的刪除操作不會影響遍歷的進(jìn)行,但需要注意,遍歷順序是不確定的,刪除元素不會導(dǎo)致程序錯誤,但可能會影響剩余元素的遍歷順序,在多線程環(huán)境下
    2024-09-09
  • Golang中拼接字符串的6種方式性能對比

    Golang中拼接字符串的6種方式性能對比

    golang的string類型是不可修改的,對于拼接字符串來說,本質(zhì)上還是創(chuàng)建一個新的對象將數(shù)據(jù)放進(jìn)去,主要有6種拼接方式,下面小編就來為大家詳細(xì)講講吧
    2025-03-03
  • golang cobra使用chatgpt qdrant實(shí)現(xiàn)ai知識庫

    golang cobra使用chatgpt qdrant實(shí)現(xiàn)ai知識庫

    這篇文章主要為大家介紹了golang cobra使用chatgpt qdrant實(shí)現(xiàn)ai知識庫,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • 詳解Go語言中rand(隨機(jī)數(shù))包的使用

    詳解Go語言中rand(隨機(jī)數(shù))包的使用

    在Golang中,有兩個包提供了rand,分別為math/rand和crypto/rand對應(yīng)兩種應(yīng)用場景。math/rand包實(shí)現(xiàn)了偽隨機(jī)數(shù)生成器。也就是生成 整形和浮點(diǎn)型;crypto/rand包實(shí)現(xiàn)了用于加解密的更安全的隨機(jī)數(shù)生成器。本文就來和大家詳細(xì)講講math/rand的使用
    2022-08-08
  • golang-redis之sorted set類型操作詳解

    golang-redis之sorted set類型操作詳解

    這篇文章主要介紹了golang-redis之sorted set類型操作詳解,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-12-12

最新評論