微服务 library-book-service ,书籍管理微服务。提供书籍管理的 Restful 接口,主要实现保存书籍、根据书籍名称查询、书籍列表、用户借书等功能。
完整代码:
https://github.com/Justin02180218/micro-kit
包结构
各个包的含义与上一篇 《微服务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 的接口测试,结果如图:
测试成功,数据库成功插入一条书籍记录
下一篇文章,我们开始编写gRPC微服务:library-book-grpc-service
完整代码:
https://github.com/Justin02180218/micro-kit
更多【分布式专辑】【架构实战专辑】系列文章,请关注公众号