v3: HTTP API 服务 — 走向网络一、版本概述v3 在 v2 的基础上引入了HTTP RESTful API将命令行工具升级为 Web 服务。新增 Service 层实现业务逻辑与数据访问的分离引入中间件处理横切关注点日志、错误恢复。相比 v2 的核心变化Service 层BookService 接口 bookService 实现Handler 不再直接依赖 RepositoryHTTP HandlerRESTful APIPOST/GET/PUT/DELETE /books中间件Logger请求日志 Recoverpanic 恢复统一响应格式{code, message, data}项目结构v3/ ├── main.go # 程序入口 ├── model/ │ └── book.go # Book 结构体 ├── repository/ │ └── book_repo.go # 数据存储层 ├── service/ │ └── book_service.go # 业务逻辑层新增 ├── handler/ │ ├── http_handler.go # HTTP 处理器新增 │ └── http_handler_test.go # API 测试 ├── middleware/ │ └── logger.go # 日志恢复中间件新增 └── data/ └── books.json二、完整代码service/book_service.gopackageserviceimport(librarypm/v3/modellibrarypm/v3/repository)typeBookServiceinterface{AddBook(title,authorstring,pricefloat64)(*model.Book,error)GetAllBooks()[]model.BookGetBookByID(idint)(*model.Book,error)UpdateBook(idint,title,authorstring,pricefloat64)(*model.Book,error)DeleteBook(idint)error}typebookServicestruct{repo*repository.BookRepository}funcNewBookService(repo*repository.BookRepository)BookService{returnbookService{repo:repo}}func(s*bookService)AddBook(title,authorstring,pricefloat64)(*model.Book,error){returns.repo.AddBook(title,author,price)}func(s*bookService)GetAllBooks()[]model.Book{returns.repo.GetAllBooks()}func(s*bookService)GetBookByID(idint)(*model.Book,error){returns.repo.GetBookByID(id)}func(s*bookService)UpdateBook(idint,title,authorstring,pricefloat64)(*model.Book,error){returns.repo.UpdateBook(id,title,author,price)}func(s*bookService)DeleteBook(idint)error{returns.repo.DeleteBook(id)}handler/http_handler.gopackagehandlerimport(encoding/jsonnet/httpstrconvstringslibrarypm/v3/service)typeHTTPHandlerstruct{svc service.BookService}funcNewHTTPHandler(svc service.BookService)*HTTPHandler{returnHTTPHandler{svc:svc}}func(h*HTTPHandler)RegisterRoutes()http.Handler{mux:http.NewServeMux()mux.HandleFunc(POST /books,h.AddBook)mux.HandleFunc(GET /books,h.ListBooks)mux.HandleFunc(GET /books/{id},h.GetBook)mux.HandleFunc(PUT /books/{id},h.UpdateBook)mux.HandleFunc(DELETE /books/{id},h.DeleteBook)mux.HandleFunc(GET /health,h.HealthCheck)returnmux}typeResponsestruct{Codeintjson:codeMessagestringjson:messageDatainterface{}json:data,omitempty}funcwriteJSON(w http.ResponseWriter,statusint,datainterface{}){w.Header().Set(Content-Type,application/json)w.WriteHeader(status)json.NewEncoder(w).Encode(data)}funcsuccessResponse(datainterface{})Response{returnResponse{Code:0,Message:success,Data:data}}funcerrorResponse(codeint,messagestring)Response{returnResponse{Code:code,Message:message,Data:nil}}func(h*HTTPHandler)AddBook(w http.ResponseWriter,r*http.Request){varreqstruct{Titlestringjson:titleAuthorstringjson:authorPricefloat64json:price}iferr:json.NewDecoder(r.Body).Decode(req);err!nil{writeJSON(w,http.StatusBadRequest,errorResponse(1001,无效的JSON格式))return}book,err:h.svc.AddBook(req.Title,req.Author,req.Price)iferr!nil{writeJSON(w,http.StatusBadRequest,errorResponse(1002,err.Error()))return}writeJSON(w,http.StatusCreated,successResponse(book))}func(h*HTTPHandler)ListBooks(w http.ResponseWriter,r*http.Request){books:h.svc.GetAllBooks()writeJSON(w,http.StatusOK,successResponse(books))}func(h*HTTPHandler)GetBook(w http.ResponseWriter,r*http.Request){id:extractID(r.URL.Path)ifid0{writeJSON(w,http.StatusBadRequest,errorResponse(1003,无效的ID))return}book,err:h.svc.GetBookByID(id)iferr!nil{writeJSON(w,http.StatusNotFound,errorResponse(1004,err.Error()))return}writeJSON(w,http.StatusOK,successResponse(book))}func(h*HTTPHandler)UpdateBook(w http.ResponseWriter,r*http.Request){id:extractID(r.URL.Path)ifid0{writeJSON(w,http.StatusBadRequest,errorResponse(1003,无效的ID))return}varreqstruct{Titlestringjson:titleAuthorstringjson:authorPricefloat64json:price}iferr:json.NewDecoder(r.Body).Decode(req);err!nil{writeJSON(w,http.StatusBadRequest,errorResponse(1001,无效的JSON格式))return}book,err:h.svc.UpdateBook(id,req.Title,req.Author,req.Price)iferr!nil{writeJSON(w,http.StatusBadRequest,errorResponse(1002,err.Error()))return}writeJSON(w,http.StatusOK,successResponse(book))}func(h*HTTPHandler)DeleteBook(w http.ResponseWriter,r*http.Request){id:extractID(r.URL.Path)ifid0{writeJSON(w,http.StatusBadRequest,errorResponse(1003,无效的ID))return}iferr:h.svc.DeleteBook(id);err!nil{writeJSON(w,http.StatusNotFound,errorResponse(1004,err.Error()))return}writeJSON(w,http.StatusOK,successResponse(map[string]string{message:删除成功}))}func(h*HTTPHandler)HealthCheck(w http.ResponseWriter,r*http.Request){writeJSON(w,http.StatusOK,successResponse(map[string]string{status:ok}))}funcextractID(pathstring)int{parts:strings.Split(strings.Trim(path,/),/)iflen(parts)2{return0}id,err:strconv.Atoi(parts[1])iferr!nil{return0}returnid}middleware/logger.gopackagemiddlewareimport(fmtlognet/httpruntime/debugtime)funcLogger(next http.Handler)http.Handler{returnhttp.HandlerFunc(func(w http.ResponseWriter,r*http.Request){start:time.Now()sw:statusWriter{ResponseWriter:w,status:http.StatusOK}next.ServeHTTP(sw,r)log.Printf([%s] %s %d %v,r.Method,r.URL.Path,sw.status,time.Since(start))})}funcRecover(next http.Handler)http.Handler{returnhttp.HandlerFunc(func(w http.ResponseWriter,r*http.Request){deferfunc(){iferr:recover();err!nil{log.Printf(panic recovered: %v\n%s,err,debug.Stack())http.Error(w,{code:500,message:服务器内部错误},http.StatusInternalServerError)}}()next.ServeHTTP(w,r)})}typestatusWriterstruct{http.ResponseWriter statusint}func(w*statusWriter)WriteHeader(codeint){w.statuscode w.ResponseWriter.WriteHeader(code)}main.gopackagemainimport(fmtlognet/httplibrarypm/v3/handlerlibrarypm/v3/middlewarelibrarypm/v3/repositorylibrarypm/v3/service)funcmain(){repo:repository.NewBookRepository(v3/data/books.json)iferr:repo.LoadFromFile();err!nil{log.Printf(⚠️ 加载数据失败: %v,err)}bookSvc:service.NewBookService(repo)httpHandler:handler.NewHTTPHandler(bookSvc)mux:httpHandler.RegisterRoutes()handler:middleware.Logger(middleware.Recover(mux))fmt.Println( 服务启动: http://localhost:8080)log.Fatal(http.ListenAndServe(:8080,handler))}三、代码解读3.1 四层架构┌─────────────┐ │ main.go │ 入口组装依赖、启动服务 ├─────────────┤ │ handler/ │ 表现层HTTP 请求/响应处理 ├─────────────┤ │ service/ │ 业务层业务逻辑编排新增 ├─────────────┤ │ repository/ │ 数据层CRUD 持久化 ├─────────────┤ │ model/ │ 模型层数据结构 └─────────────┘3.2 关键设计点Service 层意义虽然 v3 的 Service 只是简单代理 Repository但它建立了业务逻辑层的位置后续版本v4的并发控制、分布式锁等逻辑都放在这里接口返回BookService定义为接口Handler 依赖接口而非实现便于测试时 mock中间件链Logger(Recover(mux))请求从外到内穿过中间件链统一响应所有 API 返回{code, message, data}格式前端统一处理Go 1.22 路由mux.HandleFunc(POST /books, ...)使用方法路径模式匹配3.3 与 v2 的对比特性v2v3交互方式命令行HTTP API架构层次三层四层Service业务逻辑Handler 直接调 RepoService 层封装中间件无Logger Recover可测试性需启动 CLIhttptest 标准 API 测试可远程访问否是四、设计思想思想体现依赖倒置Handler 依赖 BookService 接口不依赖具体实现中间件模式横切关注点日志、恢复通过中间件链组合RESTful 设计HTTP 方法语义化POST创建、GET查询、PUT更新、DELETE删除关注点分离Service 处理业务Handler 处理 HTTPRepository 处理数据统一错误格式错误码消息前端可统一解析五、为什么需要 v4v3 实现了 HTTP 服务但存在关键缺陷没有借阅功能只有图书管理没有借书/还书的核心业务没有并发控制多人同时借同一本书会导致超借没有超期管理借出的书没有归还期限和逾期检查没有优雅关闭直接终止可能丢失数据单进程单服务所有功能在一个进程中v4 的改进方向引入借阅系统 并发安全Mutex Context 超时 优雅关闭 定时任务。