初學(xué)網(wǎng)站開發(fā)書籍關(guān)鍵詞歌曲歌詞
苦學(xué)golang半年,寫了一款web服務(wù)器
文章目錄
- 苦學(xué)golang半年,寫了一款web服務(wù)器
- example
項目地址:https://github.com/fengyuan-liang/jet-web-fasthttp
可以的話,請star支持一下🙂
苦學(xué)golang半年,寫了一款web服務(wù)器,里面包含筆者各種工程實踐,大佬勿噴😊
為什么不使用Gin
,而要自己開發(fā)一款web服務(wù)器呢?其實gin已經(jīng)非常好了👍,筆者這里主要是想要把自己開發(fā)中的工程實踐提煉出來,打造出更加順手的兵器🏹?(現(xiàn)在還只是個玩具🪀,大家看個樂子就行)
那么在使用Gin
中有哪些痛點呢?
-
繁瑣的路由表,在
Gin
中必須寫路由表來映射路由,再寫對應(yīng)的func(ctx *gin.Context )
,總感覺多寫了一次,而且接口一多,看著一望無際的路由表,總感覺劃分的不是很優(yōu)雅,而且不太好找自己想要的接口;并且筆者在寫springBoot項目時,非常喜歡用restful
插件來找路由,比如輸入/v1/xxx/xxx/xxx
就能找到對應(yīng)的路由,但是在gin的路由表不是很好找 -
每個gin的路由都必須要手動獲取參數(shù),然后校驗,校驗不通過再返回錯誤(像下面那樣),這部分邏輯感覺完全應(yīng)該復(fù)用(完全忍受不了寫重復(fù)代碼😠)。參數(shù)就應(yīng)該交給框架解析,或者說有
切面
或者hook
來統(tǒng)一完成這部分的邏輯// gin engine.Get("/v1/xxx", xxx)func xxx(ctx *gin.Context) {var (err errorparams xxxx)// Bind your param dataif err = ShouldBindQuery(ctx, ¶ms); err != nil {ctx.AbortWithStatusJSON(http.StatusOK, "traceId", 400, "bad request"))return}// validator your paramif err = validator.New().Struct(¶ms) ;err != nil {ctx.AbortWithStatusJSON(http.StatusOK, "traceId", 400, "bad request"))return}// do you codectx.JSON(http.StatusOK, "traceId", "ok")) } // 相比之下,參數(shù)Jet會自動幫你注入到你的參數(shù)列表里面,并且可以定義Hook統(tǒng)一在參數(shù)解析完畢,調(diào)用我們自己方法之前處理參數(shù)校驗的邏輯 // Jet func(YourJetController) GetV1Xxx(ctx jet.Ctx, args *Xxx) (any, error) {// do you codereturn xxx, err }
-
接下來不是
Gin
的缺點,畢竟Gin
只是一個基礎(chǔ)的web框架,就是筆者更喜歡MVC
架構(gòu)或者DDD
模式開發(fā),這里面使用到依賴注入管理生命周期是比較合適的,筆者也不喜歡用類似wire
需要生成代碼的方式進(jìn)行依賴注入,所有筆者使用Dig
進(jìn)行依賴注入,反射的方式其實也只影響項目啟動的時間,但是go的啟動本身就很快了,看不出啥影響 -
然后就是定義了一些常用的數(shù)據(jù)結(jié)構(gòu),例如
Trie
、LinkedHashMap
,在golang里面其實提供的數(shù)據(jù)結(jié)構(gòu)挺少的,但是像LinkedHashMap
用的地方其實很多,我們需要O(1)
級別的查找和添加,又需要有序的集合順序func TestLinkedHashMap(t *testing.T) {m := maps.NewLinkedHashMap[string, int]()m.Put("one", 1)m.Put("two", 2)m.Put("three", 3)m.ForEach(func(k string, v int) {t.Logf("%s: %d\n", k, v)}) }$ go test -run TestLinkedHashMap one: 1 two: 2 three: 3 PASS ok GoKit/collection/maps 0.166s
example
下面是使用的一個例子
func main() {//jet.Register(&DemoController{})xlog.SetOutputLevel(xlog.Ldebug)jet.AddMiddleware(jet.TraceJetMiddleware)jet.Run(":8080")
}// 使用依賴注入的方式注入需要讓Jet管理的Controller
func init() {jet.Provide(NewDemoController)
}func NewDemoController() jet.ControllerResult {return jet.NewJetController(&DemoController{})
}type BaseController struct {jet.IJetController
}// 對參數(shù)進(jìn)行校驗,如果不通過會返回`reg_err_info`中定義的錯誤
func (BaseController) PostParamsParseHook(param any) error {if err := utils.Struct(param); err != nil {return errors.New(utils.ProcessErr(param, err))}return nil
}// PostMethodExecuteHook restful 將所有請求以restful方式返回
func (BaseController) PostMethodExecuteHook(param any) (data any, err error) {// restfulreturn utils.ObjToJsonStr(param), nil
}// 上面的兩個hook可以直接讓controller繼承jet.BaseJetController,這樣就不用寫了type DemoController struct {BaseController
}type Person struct {Name string `json:"name" validate:"required" reg_err_info="不能為空"` // 校驗不通過會返回`reg_err_info`的內(nèi)容Age int `json:"age"`
}// 路由 get /v1/usage/{id}/week 已經(jīng)可以訪問了
func (j *DemoController) GetV1Usage0Week(ctx jet.Ctx, args *jet.Args) (any, error) {ctx.Logger().Infof("GetV1Usage0Week %v", *args)return map[string]any{"request_id": ctx.Logger().GenReqId(), "code": 200, data: args}
}
$ curl http://localhost:8080/v1/usage/1/week
{"request_id":"H5OQ4Jg0yBtg","code":200,"message":"success","data":["1"]}
正常情況下會打印日志的全路徑,我們在啟動時候加上-trimpath
就可以只打印項目的path