简单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{
}
}
//获取用户信息-byName
func 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})
}
}
//获取用户信息-byId
func 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 release
RunMode = debug
HttpPort = 9091
ReadTimeout = 60
WriteTimeout = 60
[database]
Driver = mysql
User = root
Password = 123456
Host = 127.0.0.1:3306
Name = text
运行结果
注册
登陆
获取用户详情
总结
好了,到目前为止大家对于golang进行web开发有了一定的了解了,在通过go入坑指南这篇文章的基础加持,相信大家也可以开发自己的golang后端api项目了,另外这里没有介绍golang进行Html开发的模块,原因是在真正的项目开发中一般都是前后端分离项目,因此这里直接略过了html开发的知识。
对于文章中没有深入讲解的内容,例如深入golang原生http源码,深入gin源码,gin中间件开发,以及日志模块等等,后续都会写专门的文章进行介绍,通过go入坑指南,go-web开发指南这两篇文章,旨在带领大家迈入go编程的世界,后续我会和大家一起学习,一起进步。
最后希望大家持续关注风流倜傥小老头,持续关注go技术沙龙。
点赞,在看,收藏,分享幺,感谢大家的认可与支持,有任何质疑大家都可留言。
后续通过大家的建议与反馈,文章会一直编辑更新。
文章评论