Golang碎碎念 单体图书web系统

图书管理api服务

go实现的本地微型单体web系统

post /book 创建一个新图书条目

post /book/ 更新一个图书条目

get /book/ 返回特定图书条目信息

get /book 返回所有图书条目信息

delete /book/ 删除特定图书条目

项目结构

Golang碎碎念 单体图书web系统_第1张图片

安装依赖

go get github.com/gorilla/mux

定义存储模块

goprojects/bookstore/store/store.go

package store

import "errors"

var (
	ErrNotFound = errors.New("not found")
	ErrExist    = errors.New("exist")
)

type Book struct {
	Id      string   `json:"id"`      // 图书ISBN ID
	Name    string   `json:"name"`    // 图书名称
	Authors []string `json:"authors"` // 图书作者
	Press   string   `json:"press"`   // 出版社
}

type Store interface {
	Create(*Book) error       // 创建图书
	Update(*Book) error       // 更新图书
	Get(string) (Book, error) // 获取图书
	GetAll() ([]Book, error)  // 获取所有图书
	Delete(string) error      // 删除图书
}

goprojects/bookstore/store/factory/factory.go

package factory

/*
工厂模式
*/
import (
	"bookstore/store"
	"fmt"
	"sync"
)

var (
	providersMu sync.RWMutex
	providers   = make(map[string]store.Store)
)

func Register(name string, p store.Store) {
	providersMu.Lock()
	defer providersMu.Unlock()
	if p == nil {
		panic("store: Register provider is nil")
	}
	if _, dup := providers[name]; dup {
		panic("store: Register called twice for provider" + name)
	}
	providers[name] = p
}
func New(providerName string) (store.Store, error) {
	providersMu.RLock()
	p, ok := providers[providerName]
	providersMu.RUnlock()
	if !ok {
		return nil, fmt.Errorf("store: unknown provider %s", providerName)
	}
	return p, nil
}

定义internal模块,注册,生成store对象

goprojects/bookstore/internal/store/memstore.go

package store

import (
	"bookstore/store"
	"bookstore/store/factory"
	"sync"
)

type MemStore struct {
	sync.RWMutex
	books map[string]*store.Book
}

func init() {
	factory.Register("mem", &MemStore{
		books: make(map[string]*store.Book),
	})
}
func (ms *MemStore) Create(book *store.Book) error {
	ms.Lock()
	defer ms.Unlock()
	if _, ok := ms.books[book.Id]; ok {
		return store.ErrExist
	}
	nbook := *book
	ms.books[book.Id] = &nbook
	return nil
}
func (ms *MemStore) Update(book *store.Book) error {
	ms.Lock()
	defer ms.Unlock()
	oldBook, ok := ms.books[book.Id]
	if !ok {
		return store.ErrNotFound
	}
	nbook := *oldBook
	if book.Name != "" {
		nbook.Name = book.Name
	}
	if book.Authors != nil {
		nbook.Authors = book.Authors
	}
	if book.Press != "" {
		nbook.Press = book.Press
	}
	ms.books[book.Id] = &nbook
	return nil
}
func (ms *MemStore) Get(id string) (store.Book, error) {
	ms.RLock()
	defer ms.RUnlock()
	t, ok := ms.books[id]
	if ok {
		return *t, nil
	}
	return store.Book{}, store.ErrNotFound
}
func (ms *MemStore) GetAll() ([]store.Book, error) {
	ms.RLock()
	defer ms.RUnlock()
	allbooks := make([]store.Book, 0, len(ms.books))
	for _, book := range ms.books {
		allbooks = append(allbooks, *book)
	}
	return allbooks, nil
}
func (ms *MemStore) Delete(id string) error {
	ms.Lock()
	defer ms.Unlock()
	if _, ok := ms.books[id]; !ok {
		return store.ErrNotFound
	}
	delete(ms.books, id)
	return nil
}

http模块,调用internal模块,对外暴露服务

goprojects/bookstore/server/server.go

package server

import (
	"bookstore/server/middleware"
	"bookstore/store"
	"context"
	"encoding/json"
	"github.com/gorilla/mux"
	"net/http"
	"time"
)

type BookStoreServer struct {
	s   store.Store
	srv *http.Server
}

func NewBookStoreServer(addr string, s store.Store) *BookStoreServer {
	srv := &BookStoreServer{
		s: s,
		srv: &http.Server{
			Addr: addr,
		},
	}
	router := mux.NewRouter()
	router.HandleFunc("/book", srv.createBookHandler).Methods("POST")
	router.HandleFunc("/book/{id}", srv.updateBookHandler).Methods("POST")
	router.HandleFunc("/book/{id}", srv.getBookHandler).Methods("GET")
	router.HandleFunc("/book", srv.getAllBooksHandler).Methods("GET")
	router.HandleFunc("/book/{id}", srv.delBookHandler).Methods("DELETE")

	srv.srv.Handler = middleware.Logging(middleware.Validating(router))
	return srv
}
func (bs *BookStoreServer) ListenAndServe() (<-chan error, error) {
	var err error
	errChan := make(chan error)
	go func() {
		err = bs.srv.ListenAndServe()
		errChan <- err
	}()
	select {
	case err = <-errChan:
		return nil, err
	case <-time.After(time.Second):
		return errChan, nil
	}
}
func (bs *BookStoreServer) Shutdown(ctx context.Context) error {
	return bs.srv.Shutdown(ctx)
}
func (bs *BookStoreServer) createBookHandler(w http.ResponseWriter, req *http.Request) {
	dec := json.NewDecoder(req.Body)
	var book store.Book
	if err := dec.Decode(&book); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	if err := bs.s.Create(&book); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
}

func (bs *BookStoreServer) updateBookHandler(w http.ResponseWriter, req *http.Request) {
	id, ok := mux.Vars(req)["id"]
	if !ok {
		http.Error(w, "no id found in request", http.StatusBadRequest)
		return
	}

	dec := json.NewDecoder(req.Body)
	var book store.Book
	if err := dec.Decode(&book); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	book.Id = id
	if err := bs.s.Update(&book); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
}

func (bs *BookStoreServer) getBookHandler(w http.ResponseWriter, req *http.Request) {
	id, ok := mux.Vars(req)["id"]
	if !ok {
		http.Error(w, "no id found in request", http.StatusBadRequest)
		return
	}

	book, err := bs.s.Get(id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	response(w, book)
}

func (bs *BookStoreServer) getAllBooksHandler(w http.ResponseWriter, req *http.Request) {
	books, err := bs.s.GetAll()
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	response(w, books)
}

func (bs *BookStoreServer) delBookHandler(w http.ResponseWriter, req *http.Request) {
	id, ok := mux.Vars(req)["id"]
	if !ok {
		http.Error(w, "no id found in request", http.StatusBadRequest)
		return
	}

	err := bs.s.Delete(id)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
}

func response(w http.ResponseWriter, v interface{}) {
	data, err := json.Marshal(v)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	w.Header().Set("Content-Type", "application/json")
	w.Write(data)
}

goprojects/bookstore/server/middleware/middleware.go

package middleware

import (
	"log"
	"mime"
	"net/http"
)

func Logging(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("recv a %s request from %s", r.Method, r.RemoteAddr)
		next.ServeHTTP(w, r)
	})
}
func Validating(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		contentType := r.Header.Get("Content-Type")
		mediaType, _, err := mime.ParseMediaType(contentType)
		if err != nil {
			http.Error(w, err.Error(), http.StatusBadRequest)
			return
		}
		if mediaType != "application/json" {
			http.Error(w, "invalid Content-Type", http.StatusUnsupportedMediaType)
			return
		}
		next.ServeHTTP(w, r)
	})
}

main主文件,初始化并启动系统

goprojects/bookstore/cmd/bookstore/main.go

package main

import (
	_ "bookstore/internal/store"
	"bookstore/server"
	"bookstore/store/factory"
	"context"
	"log"
	"os"
	"os/signal"
	"syscall"
	"time"
)

func main() {
	s, err := factory.New("mem")
	if err != nil {
		panic(err)
	}

	srv := server.NewBookStoreServer(":8080", s)

	errChan, err := srv.ListenAndServe()
	if err != nil {
		log.Println("web server start failed:", err)
		return
	}
	log.Println("web server start ok")

	c := make(chan os.Signal, 1)
	signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)

	select {
	case err = <-errChan:
		log.Println("web server run failed:", err)
		return
	case <-c:
		log.Println("bookstore program is exiting...")
		ctx, cf := context.WithTimeout(context.Background(), time.Second)
		defer cf()
		err = srv.Shutdown(ctx)
	}

	if err != nil {
		log.Println("bookstore program exit error:", err)
		return
	}
	log.Println("bookstore program exit ok")
}

Makefile文件

all:
	go build bookstore/cmd/bookstore

启动项目

go run main.go
访问: localhost:8080

你可能感兴趣的:(碎碎念,golang,数据库,服务器,架构)