docker|四,书籍管理微服务(library-book-service)

微服务 library-book-service ,书籍管理微服务。提供书籍管理的 Restful 接口,主要实现保存书籍、根据书籍名称查询、书籍列表、用户借书等功能。
完整代码:
https://github.com/Justin02180218/micro-kit

包结构 docker|四,书籍管理微服务(library-book-service)
文章图片

各个包的含义与上一篇 《微服务library-user-service》一样,这里就不一一说明了。

代码实现 配置文件
library-book-service 的配置文件 book.yaml 的内容与 user.yaml 差不多,端口与微服务的名称做了改变。

server: port: 10087 mode: debug name: "book-service"mysql: host: "localhost" port: 3306 db: "library" username: "root" password: "123456" debug: true


数据库表
在 library 数据库建立 book 表
CREATE TABLE `book` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `bookname` varchar(255) DEFAULT '', `created_at` datetime DEFAULT NULL, `updated_at` datetime DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;


models层
对应在 models 层创建 book.go,定义与表 book 对应的 Book struct
type Book struct { IDuint64`gorm:"primary_key" json:"id" form:"id"` CreatedAt time.Time `form:"created_at" json:"created_at"` UpdatedAt time.Time `form:"updated_at" json:"updated_at"` Booknamestring }


dao层
在 dao 层创建与数据库交互的 book_dao.go
定义 BookDao 接口及实现:
type BookDao interface { Save(book *models.Book) error FindAll() ([]models.Book, error) FindByName(name string) (*models.Book, error) BorrowBook(userID, bookID uint64) error }type BookDaoImpl struct{}func NewBookDaoImpl() BookDao { return &BookDaoImpl{} }

  • Save:保存书籍信息
  • FindAll:书籍列表
  • FindByName:根据书籍名称查询书籍信息
  • BorrowBook:用户借书功能

BookDao 接口的函数实现
func (b *BookDaoImpl) Save(book *models.Book) error { return databases.DB.Create(book).Error }func (b *BookDaoImpl) FindAll() ([]models.Book, error) { books := new([]models.Book) err := databases.DB.Find(books).Error if err != nil { return nil, err } return *books, nil }func (b *BookDaoImpl) FindByName(name string) (*models.Book, error) { book := &models.Book{} err := databases.DB.Where("bookname = ?", name).First(book).Error if err != nil { return nil, err } return book, nil }func (b *BookDaoImpl) BorrowBook(userID, bookID uint64) error { sql := "INSERT INTO user_book (user_id, book_id) VALUES(?, ?)" return databases.DB.Exec(sql, userID, bookID).Error }


dto层
在 dto 层的 book_dto.go 中创建用于数据传输的struct BookInfo 和 BorrowBook:
type BookInfo struct { IDuint64 `json:"id"` Bookname string `json:"bookname"` }type BorrowBook struct { UserID uint64 BookID uint64 }


service层
在 service 层创建 book_service.go
定义 BookService 接口及实现:
type BookService interface { SaveBook(ctx context.Context, bookname string) (*dto.BookInfo, error) SelectBooks(ctx context.Context) ([]dto.BookInfo, error) SelectBookByName(ctx context.Context, bookname string) (*dto.BookInfo, error) BorrowBook(ctx context.Context, userID, bookID uint64) error }type BookServiceImpl struct { bookDao dao.BookDao }func NewBookServiceImpl(bookDao dao.BookDao) BookService { return &BookServiceImpl{ bookDao: bookDao, } }


BookService 接口的函数实现
func (b *BookServiceImpl) SaveBook(ctx context.Context, bookname string) (*dto.BookInfo, error) { book, err := b.bookDao.FindByName(bookname) if book != nil { log.Println("This book is already exist!") return &dto.BookInfo{}, ErrBookExisted } if err == gorm.ErrRecordNotFound || err == nil { newBook := &models.Book{Bookname: bookname} err = b.bookDao.Save(newBook) if err != nil { return nil, ErrBookSave } return &dto.BookInfo{ ID:newBook.ID, Bookname: newBook.Bookname, }, nil } return nil, err }func (b *BookServiceImpl) SelectBooks(ctx context.Context) ([]dto.BookInfo, error) { books, err := b.bookDao.FindAll() if err != nil { return nil, ErrBookNotFound }newBooks := new([]dto.BookInfo) for _, book := range books { *newBooks = append(*newBooks, dto.BookInfo{ID: book.ID, Bookname: book.Bookname}) } return *newBooks, nil }func (b *BookServiceImpl) SelectBookByName(ctx context.Context, bookname string) (*dto.BookInfo, error) { book, err := b.bookDao.FindByName(bookname) if err != nil { return nil, ErrBookNotFound } return &dto.BookInfo{ ID:book.ID, Bookname: book.Bookname, }, nil }func (b *BookServiceImpl) BorrowBook(ctx context.Context, userID, bookID uint64) error { return b.bookDao.BorrowBook(userID, bookID) }


endpoint层
在 endpoint 层创建 book_endpoint.go,
定义 BookEndpoints struct,每一个请求对应一个endpoint
type BookEndpoints struct { SaveEndpointendpoint.Endpoint SelectBooksEndpointendpoint.Endpoint SelectBookByNameEndpoint endpoint.Endpoint BorrowBookEndpointendpoint.Endpoint }


创建各endpoint
func MakeSaveEndpoint(svc service.BookService) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { bookname := request.(string) bookInfo, err := svc.SaveBook(ctx, bookname) if err != nil { return nil, err } return bookInfo, nil } }func MakeSelectBooksEndpoint(svc service.BookService) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { bookInfos, err := svc.SelectBooks(ctx) if err != nil { return nil, err } return bookInfos, nil } }func MakeSelectBookByNameEndpoint(svc service.BookService) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { bookname := request.(string) bookInfo, err := svc.SelectBookByName(ctx, bookname) if err != nil { return nil, err } return bookInfo, nil } }func MakeBorrowBookEndpoint(svc service.BookService) endpoint.Endpoint { return func(ctx context.Context, request interface{}) (response interface{}, err error) { borrowBook := request.(dto.BorrowBook) err = svc.BorrowBook(ctx, borrowBook.UserID, borrowBook.BookID) if err != nil { return nil, err } return "success", nil } }


transport层
在 transport 层定义 NewHttpHandler 函数,返回 *gin.Engine
func NewHttpHandler(ctx context.Context, bookEndpoints *endpoint.BookEndpoints) *gin.Engine { r := utils.NewRouter(ctx.Value("ginMod").(string))e := r.Group("/api/v1") { e.POST("save", func(c *gin.Context) { kithttp.NewServer( bookEndpoints.SaveEndpoint, decodeBookRquest, utils.EncodeJsonResponse, ).ServeHTTP(c.Writer, c.Request) })e.GET("books", func(c *gin.Context) { kithttp.NewServer( bookEndpoints.SelectBooksEndpoint, decodeBooksRequest, utils.EncodeJsonResponse, ).ServeHTTP(c.Writer, c.Request) })e.GET("selectBookByName", func(c *gin.Context) { kithttp.NewServer( bookEndpoints.SelectBookByNameEndpoint, decodeBookRquest, utils.EncodeJsonResponse, ).ServeHTTP(c.Writer, c.Request) })e.POST("borrowBook", func(c *gin.Context) { kithttp.NewServer( bookEndpoints.BorrowBookEndpoint, decodeBorrowBookRequest, utils.EncodeJsonResponse, ).ServeHTTP(c.Writer, c.Request) }) } return r }


启动服务 main.go
func main() { flag.Parse()err := configs.Init(*confFile) if err != nil { panic(err) }err = databases.InitMySql(configs.Conf.MySQLConfig) if err != nil { fmt.Println("load mysql failed") }ctx := context.Background()bookDao := dao.NewBookDaoImpl() bookService := service.NewBookServiceImpl(bookDao)bookEndpoints := &endpoint.BookEndpoints{ SaveEndpoint:endpoint.MakeSaveEndpoint(bookService), SelectBooksEndpoint:endpoint.MakeSelectBooksEndpoint(bookService), SelectBookByNameEndpoint: endpoint.MakeSelectBookByNameEndpoint(bookService), BorrowBookEndpoint:endpoint.MakeBorrowBookEndpoint(bookService), }ctx = context.WithValue(ctx, "ginMod", configs.Conf.ServerConfig.Mode) r := transport.NewHttpHandler(ctx, bookEndpoints)errChan := make(chan error) go func() { errChan <- r.Run(fmt.Sprintf(":%s", strconv.Itoa(configs.Conf.ServerConfig.Port))) }()go func() { c := make(chan os.Signal, 1) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) errChan <- fmt.Errorf("%s", <-c) }() fmt.Println(<-errChan) }


启动
进入 library-book-service 目录,执行 go run main.go 如图:
docker|四,书籍管理微服务(library-book-service)
文章图片

服务成功启动,监听10087端口
【docker|四,书籍管理微服务(library-book-service)】
接口测试
使用postman进行接口测试,在这里进行了 save 的接口测试,结果如图:
docker|四,书籍管理微服务(library-book-service)
文章图片


测试成功,数据库成功插入一条书籍记录
docker|四,书籍管理微服务(library-book-service)
文章图片



下一篇文章,我们开始编写gRPC微服务:library-book-grpc-service
完整代码:
https://github.com/Justin02180218/micro-kit
更多【分布式专辑】【架构实战专辑】系列文章,请关注公众号
docker|四,书籍管理微服务(library-book-service)
文章图片


    推荐阅读