Agenda
拨开云雾见天日:前置知识讲解
万丈高楼平地起:基础中的精髓 及 搭建企业级golang脚手架
秤砣虽小压千斤:实战学习开发用户管理系统
拨开云雾见天日
1-1 前置知识
Go开发web的优势
在 Go 语言出现之前,开发者们总是面临非常艰难的抉择,
究竟是使用执行速度快但是编译速度并不理想的语言(如:C++),还是
使用编译速度较快但执行效率不佳的语言(如:.NET、Java),
开发难度较低但执行速度一般的动态语言呢?(如:Python)
显然,Go 语言在这 3 个条件之间做到了最佳的平衡:快速编译,高效执行,易于开发。
gin是什么?
Gin 框架 现在是 github 上 start 最多 Go 语言编写的 Web 框架,相比其他它的几个 start 数量差不多的框架,它更加的轻量,有更好的性能。
性能对比
在固定时间内重复完成的总数,数值越高的是越好的结果
单次重复持续时间(ns /op),越低越好
堆内存(B /op),越低越好
每次重复的平均分配(allocs /op),越低越好
从内存分配、单词相应时间、Qps三个纬度分析。gin基本是碾压其他对手的。
httprouter 为gin插上了翅膀
https://chai2010.gitbooks.io/advanced-go-programming-book/ch5-web/ch5-02-router.html
https://www.cnblogs.com/foxy/p/9469401.html
1-2 golang开发环境准备
下载与解压wget https://dl.google.com/go/go1.11.4.linux-amd64.tar.gztar -xvf go1.11.4.linux-amd64.tar.gz修改环境变量vim ~/.bash_profileexport GOROOT=/root/goexport GOPATH=/root/go_pathexport GOBIN=$GOROOT/binexport PATH=$GOBIN:$GOROOT/bin:$GOPATH/bin:$PATHsource ~/.bash_profile查看go的版本号go version
1-2 go mod 包管理工具
https://blog.csdn.net/e421083458/article/details/89762113
万丈高楼平地起
2-1 安装gin及快速开始
测试使用版本:
github.com/gin-gonic/gin v1.4.0
go get -v github.com/gin-gonic/ginpackage mainimport "github.com/gin-gonic/gin"func main() {r := gin.Default()r.GET("/ping", func(c *gin.Context) {c.JSON(200, gin.H{"message": "pong",})})r.Run() // listen and serve on 0.0.0.0:8080}
2-2 请求路由
设置多种请求类型
func main() {// Creates a gin router with default middleware:// logger and recovery (crash-free) middlewarerouter := gin.Default()router.GET("/someGet", getting)router.POST("/somePost", posting)router.PUT("/somePut", putting)router.DELETE("/someDelete", deleting)router.PATCH("/somePatch", patching)router.HEAD("/someHead", head)router.OPTIONS("/someOptions", options)// By default it serves on :8080 unless a// PORT environment variable was defined.router.Run()// router.Run(":3000") for a hard coded port}
绑定静态文件夹
func main() {router := gin.Default()router.Static("/assets", "./assets")router.StaticFS("/more_static", http.Dir("my_file_system"))router.StaticFile("/favicon.ico", "./resources/favicon.ico")// Listen and serve on 0.0.0.0:8080router.Run(":8080")}
参数作为URL
package mainimport "github.com/gin-gonic/gin"type Person struct {ID string `uri:"id" binding:"required,uuid"`Name string `uri:"name" binding:"required"`}func main() {route := gin.Default()route.GET("/:name/:id", func(c *gin.Context) {var person Personif err := c.ShouldBindUri(&person); err != nil {c.JSON(400, gin.H{"msg": err})return}c.JSON(200, gin.H{"name": person.Name, "uuid": person.ID})})route.Run(":8088")}
正则绑定
package mainimport ("github.com/gin-gonic/gin""net/http")func main() {router := gin.Default()router.GET("/user/*action", func(c *gin.Context) {c.String(http.StatusOK, "Hello world")})router.Run(":8080")}
2-3 获取请求参数
获取GET请求参数
func main() {router := gin.Default()// Query string parameters are parsed using the existing underlying request object.// The request responds to a url matching: /welcome?firstname=Jane&lastname=Doerouter.GET("/welcome", func(c *gin.Context) {firstname := c.DefaultQuery("firstname", "Guest")lastname := c.Query("lastname") // shortcut for c.Request.URL.Query().Get("lastname")c.String(http.StatusOK, "Hello %s %s", firstname, lastname)})router.Run(":8080")}
获取POST请求参数
func main() {router := gin.Default()router.POST("/form_post", func(c *gin.Context) {message := c.PostForm("message")nick := c.DefaultPostForm("nick", "anonymous")c.JSON(200, gin.H{"status": "posted","message": message,"nick": nick,})})router.Run(":8080")}
获取Body值
package mainimport ("github.com/gin-gonic/gin""io/ioutil""net/http")func main() {router := gin.Default()router.POST("/user/*action", func(c *gin.Context) {bodyBytes, _ := ioutil.ReadAll(c.Request.Body)c.String(http.StatusOK, string(bodyBytes))})router.Run(":8080")}
测试效果:
#form-urlencodecurl 'http://127.0.0.1:8080/user/adsadsad' -F "key=value"--------------------------88baf4eac8fd4ba1Content-Disposition: form-data; name="key"value--------------------------88baf4eac8fd4ba1--#form-datacurl 'http://127.0.0.1:8080/user/adsadsad' -d "key=value"key=value
bind绑定结构体获取参数(Get&POST&POST_BODY)
只需要在结构体上设置form标签即可
package mainimport ("fmt""log""time""github.com/gin-gonic/gin")type Person struct {Name string `form:"name"`Address string `form:"address"`Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`}func main() {route := gin.Default()route.GET("/testing", startPage)route.POST("/testing", startPage)route.Run(":8085")}func startPage(c *gin.Context) {var person Personif c.ShouldBind(&person) == nil {log.Println(person.Name)log.Println(person.Address)log.Println(person.Birthday)}c.String(200, fmt.Sprintf("%#v",person))}
测试
curl 'http://127.0.0.1:8085/testing?name=1&address=2&birthday=2006-01-02'main.Person{Name:"1", Address:"2", Birthday:time.Time{wall:0x0, ext:63271756800, loc:(*time.Location)(nil)}}curl 'http://127.0.0.1:8085/testing' -d 'name=5566&address=3344&birthday=2006-01-02'main.Person{Name:"5566", Address:"3344", Birthday:time.Time{wall:0x0, ext:63271756800, loc:(*time.Location)(nil)}}%
2-4 验证请求参数
结构体binding验证
gin默认使用validator.v8作为验证器。
多种验证规则请参见:
https://github.com/go-playground/validator/tree/v8
https://godoc.org/gopkg.in/go-playground/validator.v8#hdr-Using_Validator_Tags
注意点:gin必须使用 binding tag 来设置校验规则,而不是 validate。
package mainimport ("fmt""time""github.com/gin-gonic/gin")type Person struct {Name string `form:"name" binding:"required"`Address string `form:"address"`Birthday time.Time `form:"birthday" time_format:"2006-01-02" time_utc:"1"`}func main() {route := gin.Default()route.GET("/testing", startPage)route.Run(":8085")}func startPage(c *gin.Context) {var person Personif err:=c.ShouldBind(&person);err!=nil {c.String(500,fmt.Sprint(err))}c.String(200, fmt.Sprintf("%#v",person))}
测试
curl 'http://127.0.0.1:8085/testing' -d 'name=&address=3344&birthday=2006-01-02&age=1'Key: 'Person.Age' Error:Field validation for 'Age' failed on the 'gt' tagKey: 'Person.Name' Error:Field validation for 'Name' failed on the 'required' tagmain.Person{Age:1, Name:"", Address:"3344", Birthday:time.Time{wall:0x0, ext:63271756800, loc:(*time.Location)(nil)}}curl 'http://127.0.0.1:8085/testing' -d 'name=5566&address=3344&birthday=2006-01-02&age=1'Key: 'Person.Age' Error:Field validation for 'Age' failed on the 'gt' tagmain.Person{Age:1, Name:"5566", Address:"3344", Birthday:time.Time{wall:0x0, ext:63271756800, loc:(*time.Location)(nil)}}
自定义验证
package mainimport ("net/http""reflect""time""github.com/gin-gonic/gin""github.com/gin-gonic/gin/binding""gopkg.in/go-playground/validator.v8")// Booking contains binded and validated data.type Booking struct {CheckIn time.Time `form:"check_in" binding:"required,bookabledate" time_format:"2006-01-02"`CheckOut time.Time `form:"check_out" binding:"required,gtfield=CheckIn" time_format:"2006-01-02"`}func bookableDate(v *validator.Validate, topStruct reflect.Value, currentStructOrField reflect.Value,field reflect.Value, fieldType reflect.Type, fieldKind reflect.Kind, param string,) bool {if date, ok := field.Interface().(time.Time); ok {today := time.Now()if today.Year() > date.Year() || today.YearDay() > date.YearDay() {return false}}return true}func main() {route := gin.Default()if v, ok := binding.Validator.Engine().(*validator.Validate); ok {v.RegisterValidation("bookabledate", bookableDate)}route.GET("/bookable", getBookable)route.Run(":8085")}func getBookable(c *gin.Context) {var b Bookingif err := c.ShouldBindWith(&b, binding.Query); err == nil {c.JSON(http.StatusOK, gin.H{"message": "Booking dates are valid!"})} else {c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})}}
$ curl "localhost:8085/bookable?check_in=2018-04-16&check_out=2018-04-17"{"message":"Booking dates are valid!"}$ curl "localhost:8085/bookable?check_in=2018-03-08&check_out=2018-03-09"{"error":"Key: 'Booking.CheckIn' Error:Field validation for 'CheckIn' failed on the 'bookabledate' tag"}
升级v9验证,支持多语言错误信息
注意v9验证的话,标签要使用 validate ,也是为了与v8的标签分开。
package mainimport ("fmt""github.com/gin-gonic/gin""github.com/go-playground/locales/en""github.com/go-playground/locales/zh""github.com/go-playground/locales/zh_Hant_TW""github.com/go-playground/universal-translator""gopkg.in/go-playground/validator.v9"zh_translations "gopkg.in/go-playground/validator.v9/translations/zh"en_translations "gopkg.in/go-playground/validator.v9/translations/en"zh_tw_translations "gopkg.in/go-playground/validator.v9/translations/zh_tw")var (Uni *ut.UniversalTranslatorValidate *validator.Validate)type User struct {Username string `form:"user_name" validate:"required"`Tagline string `form:"tag_line" validate:"required,lt=10"`Tagline2 string `form:"tag_line2" validate:"required,gt=1"`}func main() {en := en.New()zh := zh.New()zh_tw := zh_Hant_TW.New()Uni = ut.New(en, zh, zh_tw)Validate = validator.New()route := gin.Default()route.GET("/testing", startPage)route.POST("/testing", startPage)route.Run(":8085")}func startPage(c *gin.Context) {//这部分应该放到中间件中locale:=c.DefaultQuery("locale","zh")trans, _ := Uni.GetTranslator(locale)switch locale {case "zh":zh_translations.RegisterDefaultTranslations(Validate, trans)breakcase "en":en_translations.RegisterDefaultTranslations(Validate, trans)breakcase "zh_tw":zh_tw_translations.RegisterDefaultTranslations(Validate, trans)breakdefault:zh_translations.RegisterDefaultTranslations(Validate, trans)break}//自定义错误内容Validate.RegisterTranslation("required", trans, func(ut ut.Translator) error {return ut.Add("required", "{0} must have a value!", true) // see universal-translator for details}, func(ut ut.Translator, fe validator.FieldError) string {t, _ := ut.T("required", fe.Field())return t})//这块应该放到公共验证方法中user := User{}c.ShouldBind(&user)fmt.Println(user)err := Validate.Struct(user)if err != nil {errs := err.(validator.ValidationErrors)sliceErrs:=[]string{}for _, e := range errs {sliceErrs=append(sliceErrs,e.Translate(trans))}c.String(200,fmt.Sprintf("%#v",sliceErrs))}c.String(200, fmt.Sprintf("%#v",""))}
测试
http://127.0.0.1:8085/testing?user_name=1111111&tag_line=1111122222222222222222&tag_line=111&locale=zh[]string{"Tagline长度必须小于10个字符", "Tagline2 must have a value!"}""http://127.0.0.1:8085/testing?user_name=1111111&tag_line=1111122222222222222222&tag_line=111&locale=zh[]string{"Tagline must be less than 10 characters in length", "Tagline2 must have a value!"}""
2-5 中间件
使用gin中间件
func Logger() gin.HandlerFunc {return func(c *gin.Context) {t := time.Now()// Set example variablec.Set("example", "12345")// before requestc.Next()// after requestlatency := time.Since(t)log.Print(latency)// access the status we are sendingstatus := c.Writer.Status()log.Println(status)}}func main() {r := gin.New()r.Use(Logger())r.GET("/test", func(c *gin.Context) {example := c.MustGet("example").(string)// it would print: "12345"log.Println(example)})// Listen and serve on 0.0.0.0:8080r.Run(":8080")}
自定义ip白名单中间件
package mainimport ("fmt""github.com/gin-gonic/gin""log")func IPAuthMiddleware() gin.HandlerFunc {return func(c *gin.Context) {ipList:=[]string{"192.168.1.1",}isMatched:=falsefor _, host := range ipList {if c.ClientIP() == host {isMatched = true}}if !isMatched{c.String(200,fmt.Sprintf("%v, not in iplist", c.ClientIP()))c.Abort()}}}func main() {r := gin.New()r.Use(IPAuthMiddleware())r.GET("/test", func(c *gin.Context) {example := c.MustGet("example").(string)log.Println(example)})r.Run(":8080")}
2-6 其他补充
优雅关停
确保在 go1.8+ 下编译
// +build go1.8package mainimport ("context""log""net/http""os""os/signal""syscall""time""github.com/gin-gonic/gin")func main() {router := gin.Default()router.GET("/", func(c *gin.Context) {time.Sleep(5 * time.Second)c.String(http.StatusOK, "Welcome Gin Server")})srv := &http.Server{Addr: ":8080",Handler: router,}go func() {// service connectionsif err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {log.Fatalf("listen: %s\n", err)}}()signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)<-quitlog.Println("Shutdown Server ...")ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)defer cancel()if err := srv.Shutdown(ctx); err != nil {log.Fatal("Server Shutdown:", err)}select {case <-ctx.Done():log.Println("timeout of 5 seconds.")}log.Println("Server exiting")}
Linux Signal及Golang中的信号处理
https://blog.csdn.net/leonpengweicn/article/details/52131313
模板渲染
gin在html/template基础之上,进行的文件解析预处理。模块文件使用方法与之前相同。
LoadHTMLGlob 可以解析一个文件夹的模板。
package mainimport ("github.com/gin-gonic/gin""net/http")func main() {router := gin.Default()//router.LoadHTMLFiles("templates/index.html",)router.LoadHTMLGlob("templates/*")router.GET("/index", func(c *gin.Context) {c.HTML(http.StatusOK, "index.html", gin.H{"title": "Main website",})})router.Run(":8080")}
index.html
<html><h1>{{ .title }}</h1></html>
template使用样例
https://www.do1618.com/archives/893/go-template学习样例/
https://www.calhoun.io/intro-to-templates-p3-functions/
Let’s Encrypt 自动证书下载
无需配置任何证书即可自动下载证书,过期后自动续订。
Let’s Encrypt原理
更多原理细节:
https://www.cnblogs.com/esofar/p/9291685.html
https://blog.csdn.net/canghaiguzhou/article/details/79945001
gin使用方法超级简单:
package mainimport ("log""github.com/gin-gonic/autotls""github.com/gin-gonic/gin")func main() {r := gin.Default()r.GET("/ping", func(c *gin.Context) {c.String(200, "pong")})log.Fatal(autotls.Run(r, "www.itpp.tk"))}
测试
https://www.itpp.tk/pingpong
查看证书缓存
[root@izrj998xcgvke9018fkvbvz autotls]# ls ~/.cache/golang-autocert/acme_account+key www.itpp.tk
2-7 构建企业级脚手架
代码地址:https://github.com/e421083458/gin_scaffold
使用gin构建了企业级脚手架,代码简洁易读,可快速进行高效web开发。
主要功能有:
1、请求链路日志打印,涵盖mysql/redis/request_in/request_out
2、接入validator.v9,支持多语言错误信息提示及自定义错误提示。
3、借助golang_common,支持了多配置环境及log/redis/mysql/http.client
现在开始
安装软件依赖
go mod使用请查阅:
go mod tidy
运行脚本
go run main.go➜ gin_scaffold git:(master) ✗ go run main.go------------------------------------------------------------------------[INFO] config=./conf/dev/[INFO] start loading resources.[INFO] success loading resources.------------------------------------------------------------------------[GIN-debug] [WARNING] Now Gin requires Go 1.6 or later and Go 1.7 will be required soon.[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.- using env: export GIN_MODE=release- using code: gin.SetMode(gin.ReleaseMode)[GIN-debug] GET /demo/index --> github.com/e421083458/gin_scaffold/controller.(*Demo).Index-fm (6 handlers)[GIN-debug] GET /demo/bind --> github.com/e421083458/gin_scaffold/controller.(*Demo).Bind-fm (6 handlers)[GIN-debug] GET /demo/dao --> github.com/e421083458/gin_scaffold/controller.(*Demo).Dao-fm (6 handlers)[GIN-debug] GET /demo/redis --> github.com/e421083458/gin_scaffold/controller.(*Demo).Redis-fm (6 handlers)[INFO] HttpServerRun::8880
测试mysql与请求链路
curl 'http://127.0.0.1:8880/demo/dao?id=1'{"errno": 0,"errmsg": "","data": "[{\"id\":1,\"area_name\":\"area_name\",\"city_id\":1,\"user_id\":2,\"update_at\":\"2019-06-15T00:00:00+08:00\",\"create_at\":\"2019-06-15T00:00:00+08:00\",\"delete_at\":\"2019-06-15T00:00:00+08:00\"}]","trace_id": "c0a8fe445d05b9eeee780f9f5a8581b0"}查看链路日志(确认是不是一次请求查询,都带有相同trace_id):tail -f gin_scaffold.inf.log[INFO][2019-06-16T11:39:26.802][log.go:58] _com_request_in||method=GET||from=127.0.0.1||traceid=c0a8fe445d05b9eeee780f9f5a8581b0||cspanid=||uri=/demo/dao?id=1||args=map[]||body=||spanid=9dad47aa57e9d186[INFO][2019-06-16T11:39:26.802][log.go:58] _com_mysql_success||affected_row=1||traceid=c0a8fe445d05b9ee07b80f9f66cb39b0||spanid=9dad47aa1408d2ac||source=/Users/niuyufu/go/src/github.com/e421083458/gin_scaffold/dao/demo.go:24||proc_time=0.000000000||sql=SELECT * FROM `area` WHERE (id = '1')||level=sql||current_time=2019-06-16 11:39:26||cspanid=[INFO][2019-06-16T11:39:26.802][log.go:58] _com_request_out||method=GET||args=map[]||proc_time=0.025019164||traceid=c0a8fe445d05b9eeee780f9f5a8581b0||spanid=9dad47aa57e9d186||uri=/demo/dao?id=1||from=127.0.0.1||response={\"errno\":0,\"errmsg\":\"\",\"data\":\"[{\\\"id\\\":1,\\\"area_name\\\":\\\"area_name\\\",\\\"city_id\\\":1,\\\"user_id\\\":2,\\\"update_at\\\":\\\"2019-06-15T00:00:00+08:00\\\",\\\"create_at\\\":\\\"2019-06-15T00:00:00+08:00\\\",\\\"delete_at\\\":\\\"2019-06-15T00:00:00+08:00\\\"}]\",\"trace_id\":\"c0a8fe445d05b9eeee780f9f5a8581b0\"}||cspanid=
测试参数绑定与多语言验证
curl 'http://127.0.0.1:8880/demo/bind?name=name&locale=zh'{"errno": 500,"errmsg": "Age为必填字段,Passwd为必填字段","data": "","trace_id": "c0a8fe445d05badae8c00f9fb62158b0"}curl 'http://127.0.0.1:8880/demo/bind?name=name&locale=en'{"errno": 500,"errmsg": "Age is a required field,Passwd is a required field","data": "","trace_id": "c0a8fe445d05bb4cd3b00f9f3a768bb0"}
文件分层
├── README.md├── conf 配置文件夹│ └── dev│ ├── base.toml│ ├── mysql_map.toml│ └── redis_map.toml├── controller 控制器│ └── demo.go├── dao DB数据访问层│ └── demo.go├── dto Bind结构体层│ └── demo.go├── gin_scaffold.inf.log info日志├── gin_scaffold.wf.log warning日志├── go.mod go module管理文件├── go.sum├── main.go├── middleware 中间件层│ ├── panic.go│ ├── response.go│ ├── token_auth.go│ └── translation.go├── public 公共文件│ ├── log.go│ ├── mysql.go│ └── validate.go├── router 路由层│ ├── httpserver.go│ └── route.go└── tmpl
引入轻量级golang类库,支持 mysql、redis、http.client、log、支持多级环境配置、支持链路日志打印
go get -v github.com/e421083458/golang_common
测试日志打印功能:
package mainimport ("github.com/e421083458/golang_common/lib""log""time")func main() {if err := lib.InitModule("./conf/dev/",[]string{"base","mysql","redis",}); err != nil {log.Fatal(err)}defer lib.Destroy()//todo sthlib.Log.TagInfo(lib.NewTrace(), lib.DLTagUndefind, map[string]interface{}{"message": "todo sth"})time.Sleep(time.Second)}
类库更多功能细节请查看:
https://github.com/e421083458/golang_common
输出格式统一封装
func ResponseError(c *gin.Context, code ResponseCode, err error) {trace, _ := c.Get("trace")traceContext, _ := trace.(*lib.TraceContext)traceId := ""if traceContext != nil {traceId = traceContext.TraceId}resp := &Response{ErrorCode: code, ErrorMsg: err.Error(), Data: "", TraceId: traceId}c.JSON(200, resp)response, _ := json.Marshal(resp)c.Set("response", string(response))c.AbortWithError(200, err)}func ResponseSuccess(c *gin.Context, data interface{}) {trace, _ := c.Get("trace")traceContext, _ := trace.(*lib.TraceContext)traceId := ""if traceContext != nil {traceId = traceContext.TraceId}resp := &Response{ErrorCode: SuccessCode, ErrorMsg: "", Data: data, TraceId: traceId}c.JSON(200, resp)response, _ := json.Marshal(resp)c.Set("response", string(response))}
定义中间件链路日志打印
package middlewareimport ("bytes""errors""fmt""github.com/e421083458/gin_scaffold/public""github.com/e421083458/golang_common/lib""github.com/gin-gonic/gin""io/ioutil""time")//链路请求日志func RequestInLog(c *gin.Context) {traceContext := lib.NewTrace()if traceId := c.Request.Header.Get("com-header-rid"); traceId != "" {traceContext.TraceId = traceId}if spanId := c.Request.Header.Get("com-header-spanid"); spanId != "" {traceContext.SpanId = spanId}c.Set("startExecTime", time.Now())c.Set("trace", traceContext)bodyBytes, _ := ioutil.ReadAll(c.Request.Body)c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) // Write body backlib.Log.TagInfo(traceContext, "_com_request_in", map[string]interface{}{"uri": c.Request.RequestURI,"method": c.Request.Method,"args": c.Request.PostForm,"body": string(bodyBytes),"from": c.ClientIP(),})}//链路输出日志func RequestOutLog(c *gin.Context) {endExecTime := time.Now()response, _ := c.Get("response")st, _ := c.Get("startExecTime")startExecTime, _ := st.(time.Time)public.ComLogNotice(c, "_com_request_out", map[string]interface{}{"uri": c.Request.RequestURI,"method": c.Request.Method,"args": c.Request.PostForm,"from": c.ClientIP(),"response": response,"proc_time": endExecTime.Sub(startExecTime).Seconds(),})}func TokenAuthMiddleware() gin.HandlerFunc {return func(c *gin.Context) {RequestInLog(c)defer RequestOutLog(c)isMatched := falsefor _, host := range lib.GetStringSliceConf("base.http.allow_ip") {if c.ClientIP() == host {isMatched = true}}if !isMatched{ResponseError(c, InternalErrorCode, errors.New(fmt.Sprintf("%v, not in iplist", c.ClientIP())))c.Abort()return}c.Next()}}
请求数据绑定到结构体与校验
dto/demo.go
package dtoimport ("errors""github.com/e421083458/gin_scaffold/public""github.com/gin-gonic/gin""github.com/go-playground/universal-translator""gopkg.in/go-playground/validator.v9""strings")type InStruct struct {Name string `form:"name" validate:"required"`Age int64 `form:"age" validate:"required"`Passwd string `form:"passwd" validate:"required"`}func (o *InStruct) BindingValidParams(c *gin.Context) error{if err:=c.ShouldBind(o);err!=nil{return err}v:=c.Value("trans")trans,ok := v.(ut.Translator)if !ok{trans, _ = public.Uni.GetTranslator("zh")}err := public.Validate.Struct(o)if err != nil {errs := err.(validator.ValidationErrors)sliceErrs:=[]string{}for _, e := range errs {sliceErrs=append(sliceErrs,e.Translate(trans))}return errors.New(strings.Join(sliceErrs,","))}return nil}
controller/demo.go
func (demo *Demo) Bind(c *gin.Context) {st:=&dto.InStruct{}if err:=st.BindingValidParams(c);err!=nil{middleware.ResponseError(c,500,err)return}middleware.ResponseSuccess(c, "")return}
秤砣虽小压千斤
3-1 用户管理系统设计

功能点
管理员登陆及退出
用户列表 翻页
增、删、改
数据库
用户表设计
CREATE TABLE `user` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增id',`name` varchar(255) NOT NULL DEFAULT '' COMMENT '姓名',`addr` varchar(255) NOT NULL DEFAULT '' COMMENT '住址',`age` smallint(4) NOT NULL DEFAULT '0' COMMENT '年龄',`birth` varchar(100) NOT NULL DEFAULT '2000-01-01 00:00:00' COMMENT '生日',`sex` smallint(4) NOT NULL DEFAULT '0' COMMENT '性别',`update_at` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '更新时间',`create_at` datetime NOT NULL DEFAULT '1970-01-01 00:00:00' COMMENT '创建时间',PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=31 DEFAULT CHARSET=utf8 COMMENT='用户表'
后端准备
fork一份gin脚手架程序
https://github.com/e421083458/gin_scaffold
前端准备
下载vue-admin
https://github.com/taylorchen709/vue-admin
安装及设置webstorm
调整webstorm的vue.js插件
https://www.cnblogs.com/ssrsblogs/p/6231981.html
调整webstorm的es6
https://blog.csdn.net/qijuju/article/details/79015073
3-2 实战开发
开发接口
[GIN-debug] POST /api/login --> github.com/e421083458/gin_scaffold/controller.(*Api).Login-fm (7 handlers)[GIN-debug] GET /api/loginout --> github.com/e421083458/gin_scaffold/controller.(*Api).LoginOut-fm (7 handlers)[GIN-debug] GET /api/user/listpage --> github.com/e421083458/gin_scaffold/controller.(*Api).ListPage-fm (8 handlers)[GIN-debug] GET /api/user/add --> github.com/e421083458/gin_scaffold/controller.(*Api).AddUser-fm (8 handlers)[GIN-debug] GET /api/user/edit --> github.com/e421083458/gin_scaffold/controller.(*Api).EditUser-fm (8 handlers)[GIN-debug] GET /api/user/remove --> github.com/e421083458/gin_scaffold/controller.(*Api).RemoveUser-fm (8 handlers)[GIN-debug] GET /api/user/batchremove --> github.com/e421083458/gin_scaffold/controller.(*Api).RemoveUser-fm (8 handlers)
整合前端调试
npm run dev
修改 config/index.js文件,作接口代理转发
dev: {env: require('./dev.env'),port: 8080,autoOpenBrowser: true,assetsSubDirectory: 'static',assetsPublicPath: '/',proxyTable: {"/api/": {target:"http://127.0.0.1:8880/",changeOrigin: true,pathRewrite: {}}},cssSourceMap: false}
前后端部署与整合
正式部署时我们一般考虑使用nginx 作转发。
server {listen 8882;server_name localhost;root /Users/niuyufu/WebstormProjects/vue-admin/dist;index index.html index.htm index.php;location / {try_files $uri $uri/ /index.html?$args;}location /api/ {proxy_pass http://127.0.0.1:8880/api/;}}
课程总结
随堂笔记
https://blog.csdn.net/e421083458/article/details/91994788
gin入门实战 - 基础精髓 随堂代码
https://github.com/e421083458/hello_gin
golang基础代码类库
https://github.com/e421083458/golang_common
gin开发脚手架
https://github.com/e421083458/gin_scaffold
整合vue.js的admin后台
https://github.com/e421083458/vue-admin
fork一份gin脚手架程序
https://github.com/e421083458/gin_scaffold
前端准备
下载vue-admin
https://github.com/taylorchen709/vue-admin
安装及设置webstorm
调整webstorm的vue.js插件
https://www.cnblogs.com/ssrsblogs/p/6231981.html
调整webstorm的es6
https://blog.csdn.net/qijuju/article/details/79015073
