18 | 實現簡潔架構的 Handler 層

提示:

  • 所有體系課見專欄:Go 項目開發極速入門實戰課;
  • 歡迎加入我的訓練營:云原生AI實戰營,一個助力 Go 開發者在 AI 時代建立技術競爭力的實戰營;
  • 本節課最終源碼位于 fastgo 項目的 feature/s14 分支;
  • 更詳細的課程版本見:Go 項目開發中級實戰課:27 | 業務實現(4):實現 Handler 層代碼

fastgo 三層簡潔架構開發的最后一步便是開發 Handler 層代碼。Handler 層代碼的實現思路和 Biz 層、Store 層保持一致。

Handler 實現

要實現 Handler,主要分為以下幾步:

  1. 實現創建 Handler 層實例的方法;
  2. 實現用戶相關 Handler 方法;
  3. 對請求參數進行校驗;
  4. 初始化 Handler。

步驟 1:實現創建 Handler 層實例的方法

HTTP API 接口最終的邏輯是由 Handler 方法來實現的。所以,需要先實現 Handler 方法。

fastgo 項目的 Handler 層代碼位于 internal/apiserver/handler/ 目錄中。新建一個 Handler 結構體,該結構體包含了 fg-apiserver 的路由函數。代碼位于 internal/apiserver/handler/handler.go 文件中,內容如下:

package handlerimport ("github.com/onexstack/fastgo/internal/apiserver/biz"
)// Handler 處理博客模塊的請求.
type Handler struct {biz biz.IBiz
}// NewHandler 創建新的 Handler 實例.
func NewHandler(biz biz.IBiz) *Handler {return &Handler{biz: biz,}
}

Handler 結構體中包含了 Biz 層的 IBiz 接口,IBiz 接口中包含的方法用來執行具體的業務邏輯。:

步驟 2: 實現用戶相關 Handler 方法

internal/apiserver/handler/user.go 文件中包含了用戶相關的 Handler 方法。這些 Handler 方法的實現邏輯保持一致。實現邏輯如下:

畫板

這里,我介紹下 CreateUser 路由方法的實現,其他路由實現方法類似。CreateUser 路由方法代碼如下:

// CreateUser 創建新用戶.
func (h *Handler) CreateUser(c *gin.Context) {slog.Info("Create user function called")var rq v1.CreateUserRequestif err := c.ShouldBindJSON(&rq); err != nil {core.WriteResponse(c, errorsx.ErrBind, nil)return}if err := validation.ValidateCreateUserRequest(c.Request.Context(), &rq); err != nil {core.WriteResponse(c, errorsx.ErrInvalidArgument.WithMessage(err.Error()), nil)return}resp, err := h.biz.UserV1().Create(c.Request.Context(), &rq)if err != nil {core.WriteResponse(c, err, nil)return}core.WriteResponse(c, nil, resp)
}

首先調用 c.ShouldBindJSON 方法將請求中的參數解析到 v1.CreateUserRequest 類型的變量 rq 中。如果解析失敗,返回 errorsx.ErrBind 類型的自定義錯誤。

接著,調用 validation.ValidateCreateUserRequest 函數,對請求參數進行校驗。為了統一管理請求參數的校驗方法,提高代碼可維護性。將校驗方法統一放在 validation(位于 internal/apiserver/pkg/validation 目錄中)。如果校驗失敗,返回 errorsx.ErrInvalidArgument 類型的自定義錯誤。這里要注意,傳遞的 context 是 c.Request.Context(),而不是 *gin.Context 類型的變量 c。因為 c中缺少了一些 HTTP 請求上下文信息。

接著,調用 Biz 層的方法 h.biz.UserV1().Create 執行具體的業務邏輯。

gin.Context 結構體類型提供了以下方法,分別用來綁定不同位置的請求參數到結構體:

  • ShouldBindJSON
  • ShouldBindUri:將請求中的路徑參數綁定到 Go 結構體中的對應字段上,這些字段跟路徑參數的映射關系,是通過 Go 結構體字段的 uri 標簽來映射的;
  • XXXX:

步驟 3:對請求參數進行校驗

首先創建一個校驗類型的結構體 Validator,代碼位于 internal/apiserver/pkg/validation/validation.go 文件中,內容如下:

// Validator 是驗證邏輯的實現結構體.
type Validator struct {// 有些復雜的驗證邏輯,可能需要直接查詢數據庫// 這里只是一個舉例,如果驗證時,有其他依賴的客戶端/服務/資源等,// 都可以一并注入進來store store.IStore
}// NewValidator 創建一個新的 Validator 實例.
func NewValidator(store store.IStore) *Validator {return &Validator{store: store}
}

Validator 結構體中,可以添加校驗邏輯中依賴的依賴項。例如 store.IStore 類型的實例,第三方微服務客戶端等。以此實現更加復雜的校驗邏輯。

ValidateCreateUserRequest 方法實現如下:

func (v *Validator) ValidateCreateUserRequest(ctx context.Context, rq *v1.CreateUserRequest) error {// Validate usernameif rq.Username == "" {return errors.New("Username cannot be empty")}if len(rq.Username) < 4 || len(rq.Username) > 32 {return errors.New("Username must be between 4 and 32 characters")}// Username can only contain letters, numbers, and underscoresusernameRegex := regexp.MustCompile(`^[a-zA-Z0-9_]+$`)if !usernameRegex.MatchString(rq.Username) {return errors.New("Username can only contain letters, numbers, and underscores")}// Validate passwordif rq.Password == "" {return errors.New("Password cannot be empty")}if len(rq.Password) < 8 || len(rq.Password) > 64 {return errors.New("Password must be between 8 and 64 characters")}// Validate password complexity (must contain at least one letter and one number)passwordRegex := regexp.MustCompile(`^.*(?=.*[a-zA-Z])(?=.*\d).*$`)if !passwordRegex.MatchString(rq.Password) {return errors.New("Password must contain at least one letter and one number")}// Validate nickname (if provided)if rq.Nickname != nil && *rq.Nickname != "" {if len(*rq.Nickname) > 32 {return errors.New("Nickname cannot exceed 32 characters")}}// Validate emailif rq.Email == "" {return errors.New("Email cannot be empty")}emailRegex := regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)if !emailRegex.MatchString(rq.Email) {return errors.New("Invalid email format")}// Validate phone numberif rq.Phone == "" {return errors.New("Phone number cannot be empty")}// Validate Chinese mainland phone number format (11 digits starting with 1)phoneRegex := regexp.MustCompile(`^1\d{10}$`)if !phoneRegex.MatchString(rq.Phone) {return errors.New("Invalid phone number format, must be 11 digits starting with 1")}return nil
}

上述校驗邏輯代碼比較簡單,這里不再介紹。為了提高代碼的可維護性,將用戶相關的校驗方法統一保存在 internal/apiserver/pkg/validation/user.go 文件中。user.go 文件中只實現了 v1.CreateUserRequest 請求結構體的校驗邏輯。

v1.UpdateUserRequest 等其他請求結構體的校驗代碼實現,留個作業,由你來實現。

步驟 4:初始化 Handler

修改 internal/apiserver/server.go 文件,添加以下代碼:

package apiserverimport (..."github.com/onexstack/fastgo/internal/apiserver/biz""github.com/onexstack/fastgo/internal/apiserver/handler""github.com/onexstack/fastgo/internal/apiserver/pkg/validation""github.com/onexstack/fastgo/internal/apiserver/store"...
)
...
// NewServer 根據配置創建服務器.
func (cfg *Config) NewServer() (*Server, error) {...// 初始化數據庫連接db, err := cfg.MySQLOptions.NewDB()if err != nil {return nil, err}store := store.NewStore(db)cfg.InstallRESTAPI(engine, store)...
}

在 NewServer 方法中,通過調用 cfg.MySQLOptions.NewDB() 創建了一個 *gorm.DB 的實例 db,再使用 db 創建了 store.IStore 的實例 store

將路由安裝代碼在 cfg.InstallRESTAPI 方法中實現,這樣可以使 NewServer 更加簡潔,同時也便于統一維護路由設置。

cfg.InstallRESTAPI 方法實現如下:

// 注冊 API 路由。路由的路徑和 HTTP 方法,嚴格遵循 REST 規范.
func (cfg *Config) InstallRESTAPI(engine *gin.Engine, store store.IStore) {...// 創建核心業務處理器handler := handler.NewHandler(biz.NewBiz(store), validation.NewValidator(store))authMiddlewares := []gin.HandlerFunc{}// 注冊 v1 版本 API 路由分組v1 := engine.Group("/v1"){// 用戶相關路由userv1 := v1.Group("/users"){// 創建用戶。這里要注意:創建用戶是不用進行認證和授權的userv1.POST("", handler.CreateUser)userv1.PUT(":userID", handler.UpdateUser)    // 更新用戶信息userv1.DELETE(":userID", handler.DeleteUser) // 刪除用戶userv1.GET(":userID", handler.GetUser)       // 查詢用戶詳情userv1.GET("", handler.ListUser)             // 查詢用戶列表.}// 博客相關路由postv1 := v1.Group("/posts", authMiddlewares...){postv1.POST("", handler.CreatePost)       // 創建博客postv1.PUT(":postID", handler.UpdatePost) // 更新博客postv1.DELETE("", handler.DeletePost)     // 刪除博客postv1.GET(":postID", handler.GetPost)    // 查詢博客詳情postv1.GET("", handler.ListPost)          // 查詢博客列表}}
}

InstallRESTAPI 方法中,通過 handler.NewHandler 函數創建了 Handler 層的實例。并使用 Handler 的實例 handler 提供的路由方法來設置 HTTP 路由。

創建 handler 實例依賴 biz.IBiz*validation.Validator 類型的實例。上述實例分別通過 biz.NewBiz(store)validation.NewValidator(store) 函數來創建。

上述代碼,使用 Gin 框架提供的各類路由注冊方法注冊了符合 REST 規范的 HTTP 路由。Gin 框架如何注冊路由,請閱讀 Gin GitHub 項目倉庫的 README 文件。上述代碼注冊的 HTTP 路由見 下表所示。

HTTP 路由(HTTP 方法 HTTP 路徑)路由描述
GET /healthz健康檢查接口
POST /v1/users創建用戶
PUT /v1/users/:userID更新用戶信息
DELETE /v1/users/:userID刪除用戶
GET /v1/users/:userID獲取用戶信息
GET /v1/users列出所有用戶
POST /v1/posts創建文章
PUT /v1/posts/:postID更新文章
DELETE /v1/posts刪除文章
GET /v1/posts/:postID獲取文章信息
GET /v1/posts列出所有文章

編譯并測試

執行以下命令重新編譯并運行 fg-apiserver:

$ ./build.sh
$ _output/fg-apiserver -c configs/fg-apiserver.yaml

打開另一個 Linux 終端,執行以下命令測試 HTTP 接口是否正常工作:

$ curl -XPOST -H'Content-Type: application/json' http://127.0.0.1:6666/v1/users  -d '{"username":"colin","password":"fastgo1234","nickname":"belm","email":"nosbelm@qq.com","phone":"1818888xxxx"}'
{"userID":"user-gxqfqn"}

上述命令創建了一個新的用戶,并返回了 用戶 ID user-gxqfqn

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/897593.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/897593.shtml
英文地址,請注明出處:http://en.pswp.cn/news/897593.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

藍隊第三次

1.了解什么是盲注 盲注&#xff08;Blind SQL Injection&#xff09;是SQL注入的一種形式&#xff0c;攻擊者無法直接通過頁面回顯或錯誤信息獲取數據&#xff0c;而是通過觀察頁面的布爾狀態&#xff08;真/假&#xff09;或時間延遲來間接推斷數據庫信息。例如&#xff0c;通…

sql server 2016 版本補丁說明

包信息和發布類型 Microsoft為創建和分發的 SQL Server 的所有軟件更新包采用了標準化命名架構。 軟件更新包是一個可執行文件&#xff08;.exe 或 .msi&#xff09;文件&#xff0c;其中包含一個或多個文件&#xff0c;這些文件可能應用于 SQL Server 安裝以更正特定問題。 …

STM32之I2C硬件外設

注意&#xff1a;硬件I2C的引腳是固定的 SDA和SCL都是復用到外部引腳。 SDA發送時數據寄存器的數據在數據移位寄存器空閑的狀態下進入數據移位寄存器&#xff0c;此時會置狀態寄存器的TXE為1&#xff0c;表示發送寄存器為空&#xff0c;然后往數據控制寄存器中一位一位的移送數…

從青銅到王者:六大排序算法實戰解析

前言 在編程的世界里,排序算法如同一顆璀璨的明珠,閃耀著智慧的光芒。它不僅是計算機科學的基礎知識點,更是每一位程序員必備的技能。今天,就讓我們一同走進排序算法的世界,深入探究冒泡排序、選擇排序、插入排序、快速排序、歸并排序、堆排序這六大經典算法的精髓所在,…

小程序配置webview

1.在微信公眾平臺配置業務域名 1&#xff09;包括把校驗文件放在服務器根目錄 2&#xff09;配置域名 2.在小程序中 新建文件 小程序新建頁面&#xff1a;web-view json配置&#xff1a;{ "pageOrientation": "landscape", "renderer":&qu…

不用 Tomcat?SpringBoot 項目用啥代替?

在SpringBoot框架中&#xff0c;我們使用最多的是Tomcat&#xff0c;這是SpringBoot默認的容器技術&#xff0c;而且是內嵌式的Tomcat。 同時&#xff0c;SpringBoot也支持Undertow容器&#xff0c;我們可以很方便的用Undertow替換Tomcat&#xff0c;而Undertow的性能和內存使…

線索二叉樹構造及遍歷算法

線索二叉樹構造以及遍歷算法 線索二叉樹&#xff08;中序遍歷版&#xff09;構造線索二叉樹構造雙向線索鏈表遍歷中序線索二叉樹 線索二叉樹&#xff08;中序遍歷版&#xff09; 中序遍歷找到對應結點的前驅&#xff08;土方法&#xff09; #mermaid-svg-eunGO5d2GhjLxCn5 {fo…

基于SpringBoot的“體育購物商城”的設計與實現(源碼+數據庫+文檔+PPT)

基于SpringBoot的“體育購物商城”的設計與實現&#xff08;源碼數據庫文檔PPT) 開發語言&#xff1a;Java 數據庫&#xff1a;MySQL 技術&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系統展示 系統總體模塊設計 前臺用戶登錄界面 系統首頁界面…

數據篇| App爬蟲入門(一)

App 的爬取相比 Web 端爬取更加容易,反爬蟲能力沒有那么強,而且數據大多是以 JSON 形式傳輸的,解析更加簡單。在 Web 端,我們可以通過瀏覽器的開發者工具監聽到各個網絡請求和響應過程,在 App 端如果想要查看這些內容就需要借助抓包軟件。常見抓包軟件有: ?工具名稱??…

go context學習

1.Context接口2.emptyCtx3.Deadline()方法4.Done()方法5.Err方法6.Value方法&#xff08;&#xff09;7.contex應用場景8.其他context方法 1.Context接口 Context接口只有四個方法&#xff0c;以下是context源碼。 type Context interface {Deadline() (deadline time.Time, …

在VMware Workstation Pro上輕松部署CentOS7 Linux虛擬機

首先我們需要下載VM虛擬機和Centos7的鏡像 下載并安裝VMware Workstation Pro 訪問VMware Workstation Pro官網下載 https://www.vmware.com/ 第二步&#xff1a;下載centos7鏡像 訪問centos官網下載 https://www.centos.org/ 開始部署Centos7 點擊創建新的虛擬機 這里是Cen…

Jsoup 解析商品信息時需要注意哪些細節?

在使用Jsoup解析商品信息時&#xff0c;需要注意以下細節和最佳實踐&#xff0c;以確保爬蟲的穩定性和數據的準確性&#xff1a; 1. 檢查HTML文檔的合法性 在解析之前&#xff0c;需要確認所解析的文檔是否是一份合法正確的HTML文檔。如果HTML結構不完整或存在錯誤&#xff0…

Android AudioFlinger(五)—— 揭開AudioMixer面紗

前言&#xff1a; 在 Android 音頻系統中&#xff0c;AudioMixer 是音頻框架中一個關鍵的組件&#xff0c;用于處理多路音頻流的混音操作。它主要存在于音頻回放路徑中&#xff0c;是 AudioFlinger 服務的一部分。 上一節我們講threadloop的時候&#xff0c;提到了一個函數pr…

go的”ambiguous import in multiple modules”

執行“go mod tidy”報如下錯誤&#xff1a; go mod tidy -compat1.17 go: finding module for package github.com/gomooon/goredis go: found github.com/gomooon/goredis in github.com/gomooon/goredis v0.3.5 go: github.com/gomooon/core importsgithub.com/gomooon/gor…

從0開始的操作系統手搓教程27:下一步,實現我們的用戶進程

目錄 第一步&#xff1a;添加用戶進程虛擬空間 準備沖向我們的特權級3&#xff08;用戶特權級&#xff09; 討論下我們創建用戶線程的基本步驟 更加詳細的分析代碼 用戶進程的視圖 說一說BSS段 繼續看process.c中的函數 添加用戶線程激活 現在&#xff0c;我們做好了TSS…

Java線程池深度解析,從源碼到面試熱點

Java線程池深度解析&#xff0c;從源碼到面試熱點 一、線程池的核心價值與設計哲學 在開始討論多線程編程之前&#xff0c;可以先思考一個問題&#xff1f;多線程編程的原理是什么&#xff1f; 我們知道&#xff0c;現在的CUP是多核CPU&#xff0c;假設你的機器是4核的&#x…

大數據技術在土地利用規劃中的應用分析

大數據技術在土地利用規劃中的應用分析 一、引言 土地利用規劃是對一定區域內的土地開發、利用、整治和保護所作出的統籌安排與戰略部署,對于實現土地資源的優化配置、保障社會經濟的可持續發展具有關鍵意義。在當今數字化時代,大數據技術憑借其海量數據處理、高效信息挖掘等…

Node 使用 SSE 結合redis 推送數據(echarts 圖表實時更新)

1、實時通信有哪些實現方式&#xff1f; 特性輪詢&#xff08;Polling&#xff09;WebSocketSSE (Server-Sent Events)通信方向單向&#xff08;客戶端 → 服務端&#xff09;雙向&#xff08;客戶端 ? 服務端&#xff09;單向&#xff08;服務端 → 客戶端&#xff09;連接方…

Android Native 之 文件系統掛載

一、文件系統掛載流程概述 二、文件系統掛載流程細節 1、Init啟動階段 眾所周知&#xff0c;init進程為android系統的第一個進程&#xff0c;也是native世界的開端&#xff0c;要想讓整個android世界能夠穩定的運行&#xff0c;文件系統的創建和初始化是必不可少的&#xff…

Redis--Set類型

目錄 一、引言 二、介紹 三、命令 1.sadd,smembers,sismember 2.spop&#xff0c;srandmember 3.smove&#xff0c;srem 4.sinter&#xff0c;sinterstore 5.sunion,sunionstore,sdiff,sdiffstore 四、內部編碼 1.intset 2.hashtable 五、應用場景 1.使用Set保存用…