From 3ca5fb7eca2d915b3909bd278f2c91228e0e1cb0 Mon Sep 17 00:00:00 2001 From: yumaojun03 <719118794@qq.com> Date: Sat, 23 Nov 2024 12:11:44 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85controller?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- book/api/book.go | 32 +++++++++++++------ book/api/comment.go | 36 +++++++++++++++++++++ book/controller/README.md | 10 +++++- book/controller/book.go | 62 ++++++++++++++++++++++++++++++++++++ book/controller/book_test.go | 42 ++++++++++++++++++++++++ book/controller/comment.go | 39 +++++++++++++++++++++++ book/model/book.go | 39 +++++++++++++++++++++-- go.mod | 4 ++- go.sum | 10 +++--- 9 files changed, 256 insertions(+), 18 deletions(-) create mode 100644 book/controller/book.go create mode 100644 book/controller/book_test.go create mode 100644 book/controller/comment.go diff --git a/book/api/book.go b/book/api/book.go index e054c63..12eb100 100644 --- a/book/api/book.go +++ b/book/api/book.go @@ -2,9 +2,11 @@ package api import ( "net/http" + "strconv" "github.com/gin-gonic/gin" "gitlab.com/go-course-project/go17/book/config" + "gitlab.com/go-course-project/go17/book/controller" "gitlab.com/go-course-project/go17/book/model" "gitlab.com/go-course-project/go17/book/response" "gorm.io/gorm" @@ -13,7 +15,8 @@ import ( // 构造函数, 用户初始化这个结构体 func NewBookHandler() *BookApiHandler { return &BookApiHandler{ - db: config.Get().MySQL.DB(), + db: config.Get().MySQL.DB(), + svc: controller.NewBookController(), } } @@ -21,6 +24,8 @@ func NewBookHandler() *BookApiHandler { // BookApiHandler 他来实现接口的功能 type BookApiHandler struct { db *gorm.DB + + svc *controller.BookController } // 提供注册功能, 提供一个Group @@ -39,14 +44,14 @@ func (h *BookApiHandler) Registry(r *gin.Engine) { // POST, Body func (h *BookApiHandler) CreateBook(ctx *gin.Context) { // 获取Book用户传达的参数 - ins := new(model.Book) - if err := ctx.ShouldBindJSON(ins); err != nil { + req := new(model.BookSpec) + if err := ctx.ShouldBindJSON(req); err != nil { response.Failed(ctx, err) return } - // book, save - if err := h.db.Save(ins).Error; err != nil { + ins, err := h.svc.CreateBook(ctx.Request.Context(), req) + if err != nil { response.Failed(ctx, err) return } @@ -67,13 +72,22 @@ func (h *BookApiHandler) ListBook(ctx *gin.Context) { // 查询Book详情 func (h *BookApiHandler) GetBook(ctx *gin.Context) { - var ins model.Book - id := ctx.Param("isbn") - - if err := h.db.Where("isbn = ?", id).Take(&ins).Error; err != nil { + strId := ctx.Param("isbn") + id, err := strconv.ParseInt(strId, 10, 64) + if err != nil { response.Failed(ctx, err) return } + + // 传递HTTP请求的上下文 + ins, err := h.svc.GetBook(ctx.Request.Context(), &controller.GetBookRequest{ + Isbn: id, + }) + if err != nil { + response.Failed(ctx, err) + return + } + ctx.JSON(http.StatusOK, ins) } diff --git a/book/api/comment.go b/book/api/comment.go index 778f64e..ee677ca 100644 --- a/book/api/comment.go +++ b/book/api/comment.go @@ -1 +1,37 @@ package api + +import ( + "github.com/gin-gonic/gin" + "gitlab.com/go-course-project/go17/book/config" + "gorm.io/gorm" +) + +// 构造函数, 用户初始化这个结构体 +func NewCommentApiHandler() *CommentApiHandler { + return &CommentApiHandler{ + db: config.Get().MySQL.DB(), + } +} + +// 面向对象 +// BookApiHandler 他来实现接口的功能 +type CommentApiHandler struct { + db *gorm.DB +} + +func (h *CommentApiHandler) AddComment(ctx *gin.Context) { + // book id, user a, comment + + // book id + // 判断book id 是不是合法, 到底有没有这本book + // 1. NewBookHandler().GetBook(ctx) + // 2. 把这个逻辑再写一套 + // if err := h.db.Where("isbn = ?", id).Take(&ins).Error; err != nil { + // response.Failed(ctx, err) + // return + // } + + // 你需要什么? + // 你需要一个 功能(业务处理逻辑): GetBook() (, error) + // controller.GetBook() +} diff --git a/book/controller/README.md b/book/controller/README.md index f7e908c..1071431 100644 --- a/book/controller/README.md +++ b/book/controller/README.md @@ -1 +1,9 @@ -# 业务层 (业务处理逻辑) \ No newline at end of file +# 业务层 (业务处理逻辑) + + + +## 和谐业务层: + ++ 复杂的业务逻辑处理(controller之间相互调用) ++ 这个内部功能(公共功能) 不需要提供接口, 需要被其他多个业务模块引用 ++ 复杂的业务是需要有单元测试 \ No newline at end of file diff --git a/book/controller/book.go b/book/controller/book.go new file mode 100644 index 0000000..4b224ab --- /dev/null +++ b/book/controller/book.go @@ -0,0 +1,62 @@ +package controller + +import ( + "context" + + "gitlab.com/go-course-project/go17/book/config" + "gitlab.com/go-course-project/go17/book/model" + "gorm.io/gorm" +) + +// 构造函数, 用户初始化这个结构体 +func NewBookController() *BookController { + return &BookController{ + db: config.Get().MySQL.DB(), + } +} + +type BookController struct { + db *gorm.DB +} + +type GetBookRequest struct { + Isbn int64 + // ... + // TraceId string +} + +// 怎么定义你的Controller的参数 +// 怎么定义你BookController的接口(interface) +// 函数名称 + 参数 + 返回 + 异常 +// 参数 一定要 注意 兼容性 +// 1.参数类型,个数 +// 怎么设计一个 具有良好兼容性的接口, 个数,类型 +// 采用一个请求类型 作为参数, 变更请求结构体, 不会变更类型,有更好的兼容性 +// 1. 非功能性的需求, 要添加TraceID, 请求支持取消 +// go context.Context 既能存储数据,也能做Gorouteine的取消 +// 这个跟接口是无关的,是纯业务处理 +func (c *BookController) GetBook(ctx context.Context, req *GetBookRequest) (*model.Book, error) { + ins := &model.Book{} + + if err := c.db.WithContext(ctx).Where("isbn = ?", req.Isbn).Take(ins).Error; err != nil { + return nil, err + } + return ins, nil +} + +// 这一层做强教育,避免内部相互调用是,处理未校验的情况 +func (c *BookController) CreateBook(ctx context.Context, req *model.BookSpec) (*model.Book, error) { + if err := req.Validate(); err != nil { + return nil, err + } + + // book, save + ins := &model.Book{ + BookSpec: *req, + } + if err := c.db.Save(ins).Error; err != nil { + return nil, err + } + + return ins, nil +} diff --git a/book/controller/book_test.go b/book/controller/book_test.go new file mode 100644 index 0000000..d101180 --- /dev/null +++ b/book/controller/book_test.go @@ -0,0 +1,42 @@ +package controller_test + +import ( + "context" + "testing" + + "gitlab.com/go-course-project/go17/book/controller" + "gitlab.com/go-course-project/go17/book/model" +) + +func TestGetBook(t *testing.T) { + book := controller.NewBookController() + ins, err := book.GetBook(context.Background(), &controller.GetBookRequest{ + Isbn: 5, + }) + if err != nil { + t.Fatal(err) + } + t.Log(ins) +} + +// INSERT INTO `books` (`title`,`author`,`price`,`is_sale`) VALUES ('Go语言','will',10,NULL) +// +// { +// "isbn": 5, +// "title": "Go语言", +// "author": "will", +// "price": 10, +// "is_sale": null +// } +func TestCreateBook(t *testing.T) { + book := controller.NewBookController() + ins, err := book.CreateBook(context.Background(), &model.BookSpec{ + Author: "will", + Price: 10, + Title: "Go语言", + }) + if err != nil { + t.Fatal(err) + } + t.Log(ins) +} diff --git a/book/controller/comment.go b/book/controller/comment.go new file mode 100644 index 0000000..af8f14b --- /dev/null +++ b/book/controller/comment.go @@ -0,0 +1,39 @@ +package controller + +import ( + "github.com/gin-gonic/gin" + "gitlab.com/go-course-project/go17/book/config" + "gorm.io/gorm" +) + +// 构造函数, 用户初始化这个结构体 +func NewCommentController() *CommentController { + return &CommentController{ + db: config.Get().MySQL.DB(), + book: NewBookController(), + } +} + +type CommentController struct { + db *gorm.DB + book *BookController +} + +func (h *CommentController) AddComment(ctx *gin.Context) { + // book id, user a, comment + + // book id + // 判断book id 是不是合法, 到底有没有这本book + // 1. NewBookHandler().GetBook(ctx) + // 2. 把这个逻辑再写一套 + // if err := h.db.Where("isbn = ?", id).Take(&ins).Error; err != nil { + // response.Failed(ctx, err) + // return + // } + + // 你需要什么? + // 你需要一个 功能(业务处理逻辑): GetBook() (, error) + // controller.GetBook() + + // h.book.GetBook(ctx, isbn) +} diff --git a/book/model/book.go b/book/model/book.go index 6ef487f..c41a5da 100644 --- a/book/model/book.go +++ b/book/model/book.go @@ -1,5 +1,16 @@ package model +import ( + "fmt" + + "github.com/go-playground/validator/v10" + "github.com/infraboard/mcube/v2/tools/pretty" +) + +var ( + v = validator.New() +) + // Book 结构体定义 type Book struct { // grom:"column:isbn;", 具体文档: https://gorm.io/docs/models.html#Fields-Tags @@ -7,15 +18,37 @@ type Book struct { BookSpec } +// ret, err := json.MarshalIndent(e, "", jsonIndent) +// +// if err != nil { +// return fmt.Sprintf("%+v", e) +// } +// +// return string(ret) +func (b *Book) String() string { + // 我提炼出来功能代码工具 + return pretty.ToJSON(b) +} + type BookSpec struct { - Title string `json:"title" gorm:"column:title;type:varchar(200)"` - Author string `json:"author" gorm:"column:author;type:varchar(200);index"` - Price float64 `json:"price" gorm:"column:price"` + Title string `json:"title" gorm:"column:title;type:varchar(200)" validate:"required"` + Author string `json:"author" gorm:"column:author;type:varchar(200);index" validate:"required"` + Price float64 `json:"price" gorm:"column:price" validate:"required"` // bool false // nil 是零值, false IsSale *bool `json:"is_sale" gorm:"column:is_sale"` } +// 怎么校验 struct 参数 +func (r *BookSpec) Validate() error { + if r.Author == "" { + return fmt.Errorf("author 不能为空") + } + + // 通用的校验逻辑,比如是否为空,可以考虑使用validate包 + return v.Struct(r) +} + // 定义该对象映射到数据里 表的名称 func (t *Book) TableName() string { return "books" diff --git a/go.mod b/go.mod index 8cf8cdb..98f9581 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,8 @@ go 1.22.0 require ( github.com/gin-gonic/gin v1.10.0 + github.com/go-playground/validator/v10 v10.20.0 + github.com/infraboard/mcube/v2 v2.0.44 gopkg.in/yaml.v3 v3.0.1 gorm.io/driver/mysql v1.5.7 gorm.io/gorm v1.25.12 @@ -18,7 +20,6 @@ require ( github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.20.0 // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/jinzhu/inflection v1.0.0 // indirect @@ -38,4 +39,5 @@ require ( golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect google.golang.org/protobuf v1.34.1 // indirect + sigs.k8s.io/yaml v1.4.0 // indirect ) diff --git a/go.sum b/go.sum index 3cabf6b..731dc47 100644 --- a/go.sum +++ b/go.sum @@ -27,9 +27,11 @@ github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/infraboard/mcube/v2 v2.0.44 h1:dspnLDspWpz5r6YgFTOmIlWE4kjog0082luhvd8AUds= +github.com/infraboard/mcube/v2 v2.0.44/go.mod h1:UkjuO7zbehNNvAsA1kZMB2ztaZlDY9XmTfBnNnilzB4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -82,8 +84,6 @@ golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= @@ -98,3 +98,5 @@ gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= +sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=