歡迎關注樓主與他的小夥伴們的小站,每周分享一些技術文章,讓我們在技術上一起成長------> 戳這裡,歡迎光臨小站 -_-
作為一個後端開發,在docker,etcd,k8s等新技術不斷湧現的今天,其背後的功臣golang在語言熱門排行榜上持續走高,因此樓主也就開了這次使用golang自己開發的基礎功能的二次裝逼之旅。
源於Spring Boot
感興趣的小夥伴可以看看樓主的上一篇,基於Spring Boot實現的功能,請移步使用Spring Boot實現部落格統計服務
實現redis儲存邏輯
選擇redis而沒選擇資料庫的原因是redis提供了豐富的資料結構與資料持久化策略,另外redis是基於記憶體的,相對於資料庫來說,快了不止一個數量級。而統計閱讀次數的情境對介面處理的速度還是有一定的要求的,因此樓主選擇了redis作為閱讀次數統計的db。
下面就是redis操作的基礎代碼,比較簡單樓主貼一下代碼,不做進一步的闡述。
go get github.com/gomodule/redigo/redis
func initRedisPool() { // 建立串連池 RedisClient = &redis.Pool{ // 從設定檔擷取maxidle以及maxactive,取不到則用後面的預設值 MaxIdle: 1, MaxActive: 10, IdleTimeout: 180 * time.Second, Dial: func() (redis.Conn, error) { c, err := redis.Dial("tcp", RedisAddress) if err != nil { return nil, err } // 選擇db c.Do("SELECT", RedisDb) return c, nil }, }}/** * 設定redis的對應key的value */func redisSet(key string, value string) { c, err := RedisClient.Dial() if err != nil { fmt.Println("Connect to redis error", err) return } _, err = c.Do("SET", key, value) if err != nil { fmt.Println("redis set failed:", err) }}/** * 擷取redis的對應key的value */func redisGet(key string) (value string) { c, err := RedisClient.Dial() if err != nil { fmt.Println("Connect to redis error", err) return } val, err := redis.String(c.Do("GET", key)) if err != nil { fmt.Println("redis get failed:", err) return "" } else { fmt.Printf("Got value is %v \n", val) return val }}/** * redis使得對應的key的值自增 */func redisIncr(key string) (value string) { c, err := RedisClient.Dial() _, err = c.Do("INCR", key) if err != nil { fmt.Println("incr error", err.Error()) } incr, err := redis.String(c.Do("GET", key)) if err == nil { fmt.Println("redis key after incr is : ", incr) } return incr}
部落格閱讀次數統計介面實現
部落格閱讀次數統計的基本商務邏輯就是,對應每篇部落格的blogId作為redis的key,而訪問次數就是這個key所對應的value,每訪問一次該介面就要將對應的blogId自增一次,並返回對應的value。這裡樓主選擇的redis的資料結構是redis的Stirng,下面是樓主實現該邏輯的主要代碼:
package mainimport ( "encoding/json" "fmt" "github.com/garyburd/redigo/redis" "log" "net/http" "time" "strings")const RedisAddress = "127.0.0.1:6379"const RedisDb = 0const AllowRequestUrlH = "*"const AllowRequestUrlW = "*"const IllegalCharacters = "?"const DefaultReadCount = "1"var ( // 定義常量 RedisClient *redis.Pool)func main() { // 初始化redis串連池 initRedisPool() // 啟動web服務監聽 http.HandleFunc("/*-*/*/", blogReadCountIncr) //設定訪問的路由 err := http.ListenAndServe(":9401", nil) //設定監聽的連接埠 if err != nil { log.Fatal("ListenAndServe: ", err) }}func blogReadCountIncr(responseWriter http.ResponseWriter, request *http.Request) { // 解析參數,預設不解析 request.ParseForm() blogId := request.Form.Get("blogId") log.Println(">>>>>> method blogReadCountIncr exec , request params is : ",blogId) // 判斷請求參數是否為空白 if "" == blogId { result := ResultCode{ Code: 200, Msg: "success", } ret, _ := json.Marshal(result) fmt.Fprintf(responseWriter, string(ret)) //這個寫入到w的是輸出到用戶端的 } readCount := redisGet(blogId) if "" == readCount { // 不符合規則,直接返回 flag := strings.Index(blogId, AllowRequestUrlH) != 0 ||strings.Index(blogId, AllowRequestUrlW) != 0||strings.Contains(blogId, IllegalCharacters) if !flag { result := ResultCode{ Code: 200, Msg: "success", } ret, _ := json.Marshal(result) fmt.Fprintf(responseWriter, string(ret)) //這個寫入到w的是輸出到用戶端的 } redisSet(blogId, DefaultReadCount) readCount = DefaultReadCount } else { readCount = redisIncr(blogId) } log.Println(">>>>>> readCount is : ",readCount) result := ResultCode{ Code: 200, Msg: "success", Data: readCount, } ret, _ := json.Marshal(result) fmt.Fprintf(responseWriter, string(ret)) //這個寫入到w的是輸出到用戶端的}// 結構體定義傳回值type ResultCode struct { Msg string `json:"msg"` Code int `json:"code"` Data string `json:"data"`}
實現過程中遇到的坑
出現的問題
使用golang原生的json工具序列化時,出現序列化失敗的問題,如下所示的結構體定義,乍一看是沒啥問題的,然而使用
ret, _ := json.Marshal(result)
序列化時,出現無法序列化成json串的問題,另外還不報錯,這讓樓主很是頭疼。
type ResultCode struct { msg string `json:"msg"` code int `json:"code"` data string `json:"data"`}
問題解決
最終樓主通過各種姿勢的排查,發現是結構體定義有問題,當定義結構體時首字母必須大寫才能序列化成功,這個特點在golang裡面很是明顯,在函數調用時首字母小寫函數在其他檔案裡面是調不到的。下面給出正確的結構體定義
type ResultCode struct { Msg string `json:"msg"` Code int `json:"code"` Data string `json:"data"`}
小結
目前很多大佬都寫過關於golang web的教程,如有雷同,請略過不看,本文通過自己的親身實戰以及樓主自己踩到的坑完成的,另外本文是基於go內建的net/http庫實現的web服務。
號外
樓主造了一個輪子,LIGHTCONF 是一個基於Netty實現的一個組態管理平台,其核心設計目標是“為業務提供統一的組態管理服務”,可以做到開箱即用。感興趣的給個star支援一下。
- 基於Netty實現的輕量級分布式應用配置中心
- 戳這裡,歡迎光臨小站 -_-