go-web开发入坑指南

2021年9月8日 194点热度 0人点赞 0条评论

点击 Golang入坑指南:传送门


简单API接口实现

    相信读过golang入坑指南读者应该对于go语言基础有了一定的了解,本篇文章则介绍一下go如何实现api接口,熟读这两篇文章后,相信大家直接上手go项目应该不成问题。

    另外本文依旧不会深入讲解Go Http源码级别的内容,关于源码后续会推出进阶系列的文章,本文的宗旨依旧是带领大家快速了解GO如何进行接口开发,先会用,在慢慢去了解其本质。

    Mux 是一个 Go 语言实现的Dispatcher,用来处理各种 http 请求。

Hello World 实现方式一(HTTP版本的实现,非显示使用官方默认mux路由组件)
package main
import ( "fmt" "log" "net/http")
const( host="127.0.0.1" port=9091)
type HelloHandler struct { Name string `json:"name"`}
func (h HelloHandler)ServeHTTP(w http.ResponseWriter,r *http.Request){ if r.Method=="GET" { err := r.ParseForm() if err != nil { fmt.Println("parse form error") } name := r.FormValue("name") w.Write([]byte(fmt.Sprintf("你好:%v,我的名字是:%v", name, h.Name))) }else{ w.Write([]byte("request method error")) }}
func main(){ fmt.Printf("start %v:%v\n",host,port) //方式一 http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request) { if r.Method=="GET" { w.Write([]byte("Hello World")) }else{ w.Write([]byte("Request Method Error")) } })
//方式二 HelloHandler只要实现ServeHTTP即可 http.Handle("/hello2",HelloHandler{Name: "张三"})
if err := http.ListenAndServe(fmt.Sprintf("%v:%v",host,port), nil);err!=nil{ fmt.Println(err) log.Fatal("服务运行失败") }}

大家可以运行下上面的方法,感受一下golang启动Http服务的便捷,如果大家想深入了解Go 网络编程这块的原理,可以看下这篇文章传送门

Hello World 实现方式二(显示使用官方默认mux路由组件)
package main
import ( "context" "fmt" "net/http" "os" "os/signal" "syscall" "time")
func main(){
//使用golang默认的路由组件 defaultMux:=http.NewServeMux() defaultMux.HandleFunc("/hello", func(writer http.ResponseWriter, request *http.Request) { writer.Write([]byte("hello world")) })
server:=http.Server{ Addr: ":9091", Handler: defaultMux, ReadTimeout: 10*time.Second, WriteTimeout: 10*time.Second, IdleTimeout: 10*time.Second, }
go func() { if err := server.ListenAndServe();err!=nil{ fmt.Println("server listen error") } }()
//平滑关闭 ch:=make(chan os.Signal) signal.Notify(ch,syscall.SIGINT,syscall.SIGTERM) <-ch ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) err := server.Shutdown(ctx) if err!=nil{ cancel() }}

Hello World 实现方式三(使用非官方开源mux路由组件)

源码地址:gorilla/mux

package main
import ( "context" "fmt" "github.com/gorilla/mux" "net/http" "os" "os/signal" "syscall" "time")
func main(){ //使用grolla/mux r := mux.NewRouter()
r.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { writer.Write([]byte("hello")) })
r.HandleFunc("/hello2", func(writer http.ResponseWriter, request *http.Request) { writer.Write([]byte("hello world2")) })
server := http.Server{ Addr: ":9091", Handler: r, ReadTimeout: 10 * time.Second * 10, WriteTimeout: 10 * time.Second * 10, IdleTimeout: 10 * time.Second * 10, }
go func() { if err := server.ListenAndServe();err!=nil{ fmt.Println("server listen error") } }()
//平滑关闭 ch:=make(chan os.Signal) signal.Notify(ch,syscall.SIGINT,syscall.SIGTERM) <-ch ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) err := server.Shutdown(ctx) if err!=nil{ cancel() }}

gorilla/mux对比golang自带的http.ServerMux

http.ServerMux底层实现
type ServeMux struct {  mu    sync.RWMutex  m     map[string]muxEntry  es    []muxEntry // slice of entries sorted from longest to shortest.  hosts bool       // whether any patterns contain hostnames}

其实底层是一个map[string]muxEntry维护了路由信息,map中保存了请求路径和请求路径处理函数的一个映射。

gorilla/mux底层实现

type Router struct {  NotFoundHandler http.Handler
MethodNotAllowedHandler http.Handler
routes []*Route
// Routes by name for URL building. namedRoutes map[string]*Route
KeepContext bool
middlewares []middleware
routeConf}

其实这里分两种,第一种是routes []Route 切片实现,第二种是map[string]Route map实现,但是常用的是第一种。

gorilla/mux优势
  • 支持正则路由

  • 支持按照method,header,host等信息匹配,方便实现restful风格的API接口

  • golang自带的路由只支持按照路径匹配

  • gorilla中有很对类库,一起配合使用完全可以满足工作需求

其实在工作中身边朋友公司有很多直接用gorilla/mux来实现后端api接口,如果大家对于gorilla/mux感兴趣,可以自行百度尝试使用,后续也会推出gorilla/mux的深度使用介绍。

Gin

源码地址:gin

  • Gin 是一个用 Go (Golang) 编写的 web 框架。基于 httprouter实现路由功能,底层基于Radix树,占用内存少,性能高,速度提高了近 40 倍。

  • 支持中间件:传入的 HTTP 请求可以由一系列中间件和最终操作来处理。例如:Logger,Authorization,GZIP,最终操作 DB。

  • Crash 处理:Gin 可以 catch 一个发生在 HTTP 请求中的 panic 并 recover 它。这样,你的服务器将始终可用。例如,你可以向 Sentry 报告这个 panic!另外如果是新开的goroutine的,需要在新开的goroutine出recover住异常。

  • JSON 验证:Gin 可以解析并验证请求的 JSON,例如检查所需值的存在。

  • 路由组:更好地组织路由。是否需要授权,不同的 API 版本…… 此外,这些组可以无限制地嵌套而不会降低性能。

  • 错误管理:Gin 提供了一种方便的方法来收集 HTTP 请求期间发生的所有错误。最终,中间件可以将它们写入日志文件,数据库并通过网络发送。

  • 内置渲染:Gin 为 JSON,XML 和 HTML 渲染提供了易于使用的 API。

  • 可扩展性:可以自己新建中间件。

好了这里不做对于gin的输入讲解,下面会通过一个登陆注册的例子,来向大家介绍一下项目中如何使用gin开发api接口。

由于在golang中没有标准的web开发目录组织结构,因此大家可以参考我目前在用目录结构,也可以根据自己的经验设置

图片

项目架构

图片

数据层-Model
user.go
package userModel
import ( "codego/learngo/http_demo/login_demo/models/mysql" "errors" "github.com/jinzhu/gorm")
type User struct { Name string `json:"name"` Age int64 `json:"age"` Pwd string `json:"pwd"`}
func NewUserDao()*User{ return new(User)}
func (u *User)GetUserByName(name string)(*User,error){ user:=new(User) err := mysql.UserDB.Model(&User{}).Where("name=?", name).First(user).Error if err!=nil{ if err==gorm.ErrRecordNotFound{ return nil,errors.New("数据库记录不存在") } } return user,nil}
func (u *User)GetUserById(id int64)(*User,error){ user:=new(User) err := mysql.UserDB.Model(&User{}).Where("id=?", id).First(user).Error if err!=nil{ if err==gorm.ErrRecordNotFound{ return nil,errors.New("数据库记录不存在") } } return user,nil}

func(u *User)InsertUser(user *User)error{ return mysql.UserDB.Model(&User{}).Create(user).Error}

主要完成对数据库的操作。

db.go

package mysql
import "github.com/jinzhu/gorm"
var UserDB *gorm.DB

此处UserDB在setting.go中初始化数据库时赋值,这样在后续model中操作数据库即可直接使用。

业务处理层-service

user_service.go

package userService
import ( "codego/learngo/http_demo/login_demo/models/mysql/userModel" "fmt")type UserRegisterBo struct { Name string `json:"name"` //用户名 Pwd string `json:"pwd"` //密码 Age int64 `json:"age"` //年龄}
func GetUserById(id int64)*userModel.User{ userDao := userModel.NewUserDao() user,err:=userDao.GetUserById(id) if err!=nil{ //todo 记录日志 fmt.Println(err.Error()) return nil } return user}
func GetUserByName(name string)*userModel.User{ userDao := userModel.NewUserDao()
info, err := userDao.GetUserByName(name) if err!=nil{ //todo 记录日志 fmt.Println(err.Error()) return nil } return info}
func UserInsert(bo *UserRegisterBo)bool{ userDao := userModel.NewUserDao() user := &userModel.User{ Name: bo.Name, Age: bo.Age, Pwd: bo.Pwd, }
if err := userDao.InsertUser(user);err!=nil{ //todo 记录日志 return false } return true}

    业务逻辑的处理,同时要注意的一点是,golang的日志尽量都在一层完成,不要到处打日志,这样也不方便管理,这里没有详细介绍golang常用的日志组件,后续详细对比一下golang中经常用的日志组件,例如原生log,以及logrus,zap等等。

请求控制层-controller

userController.go

package userController
import ( "codego/learngo/http_demo/login_demo/app/services/userService" "codego/learngo/http_demo/login_demo/utils" "github.com/gin-gonic/gin" "net/http" "strconv")

type UserLoginBo struct { Name string `json:"name"` Pwd string `json:"pwd"`}//用户登陆func Login(ctx *gin.Context){ bo:=new(UserLoginBo) if err := ctx.BindJSON(bo);err!=nil{ ctx.JSON(http.StatusOK,gin.H{"data":nil,"error":"Invalid Params","code":utils.RequestFailed}) return }
user := userService.GetUserByName(bo.Name) if user==nil || user.Pwd!=bo.Pwd{ ctx.JSON(http.StatusOK,gin.H{"data":nil,"error":"用户名密码错误","code":utils.RequestFailed}) return } ctx.JSON(http.StatusOK,gin.H{"data":user,"error":"","code":utils.RequestSuccess})}
//用户注册func Register(ctx *gin.Context){ bo:=new(userService.UserRegisterBo) if err:=ctx.BindJSON(bo);err!=nil{ ctx.JSON(http.StatusOK,gin.H{"data":nil,"error":"Invalid Params","code":utils.RequestFailed}) return }
if userService.UserInsert(bo) { ctx.JSON(http.StatusOK, gin.H{"data": "success", "error": "", "code": utils.RequestSuccess}) }else{
}}
//获取用户信息-byNamefunc GetByName(ctx *gin.Context){ name:=ctx.Query("name")
userInfo := userService.GetUserByName(name) if userInfo!=nil { ctx.JSON(http.StatusOK, gin.H{"data": userInfo, "error": "", "code": utils.RequestSuccess}) }else{ ctx.JSON(http.StatusOK, gin.H{"data": nil, "error": "获取用户失败", "code": utils.RequestFailed}) }}
//获取用户信息-byIdfunc GetById(ctx *gin.Context){ id,_:=strconv.Atoi(ctx.Query("id"))
userInfo := userService.GetUserById(int64(id)) if userInfo!=nil { ctx.JSON(http.StatusOK, gin.H{"data": userInfo, "error": "", "code": utils.RequestSuccess}) }else{ ctx.JSON(http.StatusOK, gin.H{"data": nil, "error": "获取用户失败", "code": utils.RequestFailed}) }}

此处接收用户请求,获取请求参数,做转发等等。

路由转发--router

router.go

package routers
import "github.com/gin-gonic/gin"
func InitRouter() *gin.Engine{ r := gin.New() r.Use(gin.Logger()) r.Use(gin.Recovery()) gin.SetMode("debug") g:=r.Group("/v1/api")
//注册用户接口路由 InitUserRouter(g)
//r.Run(":9091") return r}

userRouter.go

package routers
import ( "codego/learngo/http_demo/login_demo/app/controllers/userController" "codego/learngo/http_demo/login_demo/app/middleware" "github.com/gin-gonic/gin")
func InitUserRouter(g *gin.RouterGroup){ u:=g.Group("/user") { u.POST("/login",userController.Login) u.POST("/register",userController.Register) }
ui:=g.Group("/user",middleware.Jwt()) { ui.GET("/getByName",userController.GetByName) ui.GET("/getById",userController.GetById) }}

    这里涉及到了golang中路由模块,同时这里使用了路由分组的概念,方便路由的管理,后续会详细介绍,大家先模仿着写,先会用,在学原理。

中间件

jwt.go

package middleware
import ( "codego/learngo/http_demo/login_demo/utils" "github.com/gin-gonic/gin" "net/http")
func Jwt()gin.HandlerFunc{ return func(ctx *gin.Context) { token := ctx.GetHeader("token") //根据token校验用户 checkUser:= func(token string)int{ if token!=""{ //todo 此处校验用户Token return utils.TokenSuccess } return utils.TokenError }
if checkUser(token)==utils.TokenSuccess{ ctx.Next() }else{ ctx.JSON(http.StatusOK,gin.H{"data":nil,"error":"Token Error","code":utils.TokenError}) ctx.Abort() return } }}

    大家可以看下这里jwt中间件的定义,在gin框架中用户可以根据自己的需要,定义类似jwt的通用中间件,例如日志,统计,限流等等,具体的详细其他复杂的后续在做介绍,大家先混个眼熟。

通用工具

utils.go

package utils
const ( RequestSuccess = iota RequestFailed TokenSuccess TokenError)

    这个包下主要定义一下平时经常使用的工具函数,这里定义了用于http请求的code。

    这里说一下golang中的iota的用法,这里默认iota的值是0,依次递增 1,2,3,具体iota的多个变种用法也很少使用,这里不做详情介绍,后续推出详细文章介绍iota的使用。

配置文件

setting.go

package setting
import ( "codego/learngo/http_demo/login_demo/models/mysql" "fmt" "github.com/go-ini/ini" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/mysql" "log" "time")

var DBSetting =&DbConfig{}
type DbConfig struct { Driver string `json:"driver"` User string `json:"user"` Password string `json:"password"` Host string `json:"host"` Name string `json:"name"`}
//初始化func init(){ //配置文件获取配置信息 configFile:= "config/app.ini" cfg, err := ini.LoadSources(ini.LoadOptions{IgnoreInlineComment: true}, configFile) if err != nil { log.Fatalf("setting.Setup, fail to parse 'conf/app.ini': %v", err) }
//获取数据库配置 cfg.Section("database").MapTo(DBSetting)
//todo 此处可以用于获取其他中间件配置 mysql.UserDB=InitDb(DBSetting.Driver,DBSetting.User,DBSetting.Password,DBSetting.Host,DBSetting.Name)}
//初始化数据库func InitDb(driver, user, password, host, name string) (db *gorm.DB) { var err error db, err = gorm.Open(driver, fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8&parseTime=True&loc=Local&timeout=10s", user, password, host, name))
if err!=nil{ log.Fatal("初始化数据库失败",err) return nil }
//打印日志 db.LogMode(true) db.SingularTable(true) db.DB().SetMaxIdleConns(10) db.DB().SetMaxOpenConns(100) db.DB().SetConnMaxLifetime(time.Second * 600) return}

setting.go文件中做了两个工作:

  • 读取配置文件

  • 初始化数据库

    后续涉及到redis等其他中间件的初始化,都可以在这里做,另外需要注意的是,这里初始化数据库时,一定不要忘记引入包:_ "github.com/jinzhu/gorm/dialects/mysql" 这个包里面主要用于初始化mysql的驱动。

    在golang语言中引入包的时候如果前面用 “_” 标注,表示用于执行改包下的init函数,而不显示调用此包内的其他方法。

config.ini

[server]#debug or releaseRunMode = debugHttpPort = 9091ReadTimeout = 60WriteTimeout = 60
[database]Driver = mysqlUser = rootPassword = 123456Host = 127.0.0.1:3306Name = text

运行结果

注册

图片

登陆

图片

获取用户详情

图片

总结

    好了,到目前为止大家对于golang进行web开发有了一定的了解了,在通过go入坑指南这篇文章的基础加持,相信大家也可以开发自己的golang后端api项目了,另外这里没有介绍golang进行Html开发的模块,原因是在真正的项目开发中一般都是前后端分离项目,因此这里直接略过了html开发的知识。

    对于文章中没有深入讲解的内容,例如深入golang原生http源码,深入gin源码,gin中间件开发,以及日志模块等等,后续都会写专门的文章进行介绍,通过go入坑指南,go-web开发指南这两篇文章,旨在带领大家迈入go编程的世界,后续我会和大家一起学习,一起进步。

    最后希望大家持续关注风流倜傥小老头,持续关注go技术沙龙。

    点赞,在看,收藏,分享幺,感谢大家的认可与支持,有任何质疑大家都可留言。

    后续通过大家的建议与反馈,文章会一直编辑更新。

图片

13630go-web开发入坑指南

root

这个人很懒,什么都没留下

文章评论