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

微服务 library-book-service ,书籍管理微服务。提供书籍管理的 Restful 接口,主要实现保存书籍、根据书籍名称查询、书籍列表、用户借书等功能。

完整代码:

https://github.com/Justin02180218/micro-kit

包结构

四,书籍管理微服务(library-book-service)_第1张图片

各个包的含义与上一篇 《微服务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 {
    ID        uint64    `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"`
    Bookname  string
}

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 {
    ID       uint64 `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 {
    SaveEndpoint             endpoint.Endpoint
    SelectBooksEndpoint      endpoint.Endpoint
    SelectBookByNameEndpoint endpoint.Endpoint
    BorrowBookEndpoint       endpoint.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 如图:

图片

服务成功启动,监听10087端口

 

接口测试

使用postman进行接口测试,在这里进行了 save 的接口测试,结果如图:

四,书籍管理微服务(library-book-service)_第2张图片

 

测试成功,数据库成功插入一条书籍记录

图片

 

 

下一篇文章,我们开始编写gRPC微服务:library-book-grpc-service

完整代码:

https://github.com/Justin02180218/micro-kit


更多【分布式专辑】【架构实战专辑】系列文章,请关注公众号

 

你可能感兴趣的:(docker,go,微服务,go,微服务架构,docker,k8s)