Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【实践】gin 开发 - 学习打卡 #334

Closed
fuckqqcom opened this issue Apr 8, 2019 · 22 comments
Closed

【实践】gin 开发 - 学习打卡 #334

fuckqqcom opened this issue Apr 8, 2019 · 22 comments

Comments

@fuckqqcom
Copy link
Contributor

fuckqqcom commented Apr 8, 2019

tips:题外话,本人工作一直用py,业务时间看看go相关的资料
建立这个issue的目的很单纯,希望有着相同爱好兴趣的朋友一起加入打卡,为了提高自己技能
每周坚持至少四天,每天保持2小时学习go,有了一定基础,大家可以投票,可以选择投票最高的一些项目来练习,做完一个请其他大佬们有时间或者机会帮忙在代码写法效率等各个方面点评,然后最后可以按照这个项目流程一起写一个pdf文档或者知识分享文档,这样我们有了基础,又有了练习,做项目中有了思考,会提高比较快。同时帮助后面入门着学习.

下面每天打卡记录

大家可以加入 Gin Slack channel 了解进度和问题探讨(也可以用GitHub issue讨论)

@fuckqqcom
Copy link
Contributor Author

tips:最近准备用gin做一个web项目,为期周期2周,这两天我会把遇到的一些问题列出来,和大家一起交流,同时如果有相同想法的朋友,也可以一起交流
第一天:先用gin跑一个webdemo,同时加载一些配置信息

@yangwenmai yangwenmai pinned this issue Apr 8, 2019
@yangwenmai yangwenmai changed the title go学习打卡 Go 项目实践之 gin 开发 - 学习打卡 Apr 8, 2019
@maxyzli
Copy link
Contributor

maxyzli commented Apr 9, 2019

@xhochipe 有興趣,請問怎麼參與呢?

@yangwenmai
Copy link
Member

https://github.com/developer-learning/reading-go/tree/master/examples

@xhochipe @liyingzhen 我有个建议是,你们可以分别提交 PR (提交到 examples 下面)

@maxyzli
Copy link
Contributor

maxyzli commented Apr 9, 2019

@yangwenmai 好主意,不過 examples 應該是放一些小主題對吧。不是放整個 app 那種?

例如

examples/
  gin_examples/
    upload_file/
    withApollo/
    withRact/
    withGlgen/
    ...

@yangwenmai
Copy link
Member

为了可以让大家相互 review ,所以不单独开项目会好一点。

目录结构,你们两个人可以商量一下。(毕竟你们俩要协同开发)

@maxyzli
Copy link
Contributor

maxyzli commented Apr 9, 2019

@yangwenmai OK

@xhochipe �你有加入 slack 嗎?微信也可。咱們商量一下。

@fuckqqcom
Copy link
Contributor Author

fuckqqcom commented Apr 9, 2019

@liyingzhen 微信可以 ,我微信号(xiaohan_ochipe),我分享下我昨晚遇到的一个问题


	//必须先定义日志写入,然后在gin.new()
	gin.DisableConsoleColor()
	f, _ := os.Create("gin.log")
	gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

	r := gin.New()
	r.Use(gin.Logger())
	r.Use(gin.Recovery())

	//admin
	adminRouter(r)
	//site
	siteRouter(r)

	return r
}

我刚开始先gin.New()后发现日志文件没日志,后来才发现gin.DefaultWriter = io.MultiWriter(f, os.Stdout) 这段代码必须在gin.New()之前

@maxyzli
Copy link
Contributor

maxyzli commented Apr 9, 2019

@xhochipe just created a PR #339, you can keep working on it. Thanks. 👍

@yangwenmai Thank you as well.

@yangwenmai
Copy link
Member

@maxyzli
Copy link
Contributor

maxyzli commented Apr 10, 2019

4/10: basic file structures

@yangwenmai Gin 开发的看板, 無法做操做。

順便打卡一下,剛剛再看這篇 Standard Package Layout。打算用他的方式設計結構。

@maxyzli
Copy link
Contributor

maxyzli commented Apr 11, 2019

4/11: create user api

@yangwenmai yangwenmai unpinned this issue Apr 12, 2019
@maxyzli
Copy link
Contributor

maxyzli commented Apr 13, 2019

4/13:

Commits:

  1. login user API
  2. logout and get user APIs
  3. tests: testing userservice

====================

Articles:

Don’t just check errors, handle them gracefully

Minimise the number of sentinel error values in your program and convert errors to opaque errors by wrapping them with errors.Wrap as soon as they occur.

Finally, use errors.Cause to recover the underlying error if you need to inspect it.

@maxyzli
Copy link
Contributor

maxyzli commented Apr 14, 2019

4/14:

Commits:

1 test: test middlewares
2 test: test userHandler

=====

Articles:

Stop Learning Frameworks

@andydodo
Copy link

大家好 我也想参加,能带带小弟我吗?

@maxyzli
Copy link
Contributor

maxyzli commented Apr 14, 2019

@SupremeAndy 我也是邊做邊學,你可以看一下我的 commit。有問題大家可以討論。

@yangwenmai
Copy link
Member

想参加,可以加入 Slack channel:https://reading-go.slack.com/messages/CHJHB1BMG

@fuckqqcom
Copy link
Contributor Author

fuckqqcom commented Apr 15, 2019

1/14:
Commits:
add error code
自定义gin的一些常用变量

@yangwenmai
Copy link
Member

yangwenmai commented Apr 16, 2019

@xhochipe 麻烦你将开发计划放在这个issue 的描述中。比如:

开发计划

V0.1

V0.2

V0.3

...

@yangwenmai yangwenmai pinned this issue Apr 16, 2019
@fuckqqcom
Copy link
Contributor Author

@yangwenmai 好的

@fuckqqcom
Copy link
Contributor Author

@SupremeAndy 上面有slack链接,你可以加入

@yangwenmai yangwenmai unpinned this issue Apr 29, 2019
@yangwenmai yangwenmai changed the title Go 项目实践之 gin 开发 - 学习打卡 【实践】gin 开发 - 学习打卡 May 8, 2019
@awkj
Copy link

awkj commented May 21, 2019

在我的业务中,常常遇见场景是业务函数都抽象成
type RequestStruct struct{}
type ResponseStruct struct{}

func DoSomething(ctx context.Context, req RequstStuct)(resp ResponseStruct, err error){
// crud
return
}

所以我在 gin 外面用 反射进行了一个包括,自动解 POST 请求中的 json 到 req 中,自动把 resp json.Marshal 到 响应中, 大家有什么更有效率的写法么?

var (
	errType = reflect.TypeOf((*error)(nil)).Elem()
	ctxType = reflect.TypeOf((*context.Context)(nil)).Elem()
)


// 检查返回值
// 允许一个参数在上下文后面
// 把上下文传递进去
func WrapHandler(f interface{}) gin.HandlerFunc {
	// 检查参数有效性
	validateFuncSignature(f)
	return wrapHander(f)
}

func wrapHander(f interface{}) gin.HandlerFunc {
	return func(c *gin.Context) {
		t := reflect.TypeOf(f)
		reqv := reflect.New(t.In(1))
		// 对 JSON 结构体进行绑定
		err := c.ShouldBindJSON(reqv.Interface())
		if err != nil && err != io.EOF {
			c.JSON(http.StatusOK, gin.H{
				"code":    http.StatusBadRequest,
				"message": fmt.Sprintf("Request Bind Error, Err Is: %s", err.Error()),
				"data":    nil,
			})
			return
		}

		ctx := c.Request.Context()

		// 传入 标准 context 而不是 gin.Context
		inValues := []reflect.Value{reflect.ValueOf(ctx), reqv.Elem()}
		ret := reflect.ValueOf(f).Call(inValues)
		WriteResp(c, ret)
	}
}

// 验证函数签名
func validateFuncSignature(f interface{}) {
	t := reflect.TypeOf(f)
	if t.Kind() != reflect.Func {
		panic("handdler shound be function type")
	}

	// 检查参数类型和长度
	// context, struct
	funcNumIn := t.NumIn()
	if funcNumIn != 2 {
		panic("handler function require  2 input paramters")
	}

	// 检查第一个参数是否是实现了上下文接口
	// gin 的上下文是一个结构体而并非 interface{}
	if !t.In(0).Implements(ctxType) {
		panic("handler function first paramter should by type of *gin.Context if you have 2 input parameters")
	}

	// 检查最后一位是否是结构体
	lastParam := t.In(funcNumIn - 1)
	if lastParam.Kind() == reflect.Ptr {
		lastParam = lastParam.Elem()
	}

	if lastParam.Kind() != reflect.Struct {
		panic("param kind must is struct")
	}

	// 检查返回值类型和长度, 允许两种格式
	// xxx, err
	// err
	funcNumOut := t.NumOut()
	// 检查最后一位是否实现了 error
	if !t.Out(funcNumOut - 1).Implements(errType) {
		panic("param return type must is error ")
	}
}

使用的时候

gin.Post("/xxxxxx", gee.WrapHandler(DoSomething ))

@aleimu
Copy link

aleimu commented Jul 31, 2019

    package main

    import (
        "context"
        "fmt"
        "github.com/gin-gonic/gin"
        "io"
        "log"
        "net/http"
        "os"
        "os/signal"
        "time"
    )

    func BeforeRequest() gin.HandlerFunc {
        return func(c *gin.Context) {
            method := c.Request.Method
            ip := c.Request.Host
            log.Print("method:" + method + ",ip:" + ip)
            log.Printf("c.Request.Method: %v", c.Request.Method)
            log.Printf("c.Request.ContentType: %v", c.ContentType())
            log.Printf("c.Request.Body: %v", c.Request.Body)
            log.Printf("c.Request.Form: %v", c.Request.PostForm)

            for k, v := range c.Request.PostForm { //动态获取未知参数
                log.Printf("k:%v\n", k)
                log.Printf("v:%v\n", v)
            }

            // 在gin上下文中定义变量
            c.Set("example", "12345")
            // 请求前
            c.Next() //处理请求,Next很特殊,之前的语句都是在进入handler之前执行的before_request,之后的语句是after_request执行的
            // 请求后
            log.Print("请求后")
            // access the status we are sending
            status := c.Writer.Status()
            log.Println(status)
        }
    }

    func main() {
        // 开启日志
        // Disable Console Color, you don't need console color when writing the logs to file.
        gin.DisableConsoleColor()

        // Logging to a file.
        f, _ := os.Create("D:\\Go\\go_test\\dome\\gin.log")
        gin.DefaultWriter = io.MultiWriter(f)

        // Use the following code if you need to write the logs to file and console at the same time.
        gin.DefaultWriter = io.MultiWriter(f, os.Stdout)

        //router := gin.Default() // 使用Default的话,就不用设置控制台日志打印格式了
        router := gin.New()
        // 自定义控制台日志打印格式
        // LoggerWithFormatter middleware will write the logs to gin.DefaultWriter
        // By default gin.DefaultWriter = os.Stdout
        router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string {

            // your custom format
            return fmt.Sprintf("%s - [%s] \"%s %s %s %d %s \"%s\" %s\"\n",
                param.ClientIP,
                param.TimeStamp.Format(time.RFC1123),
                param.Method,
                param.Path,
                param.Request.Proto,
                param.StatusCode,
                param.Latency,
                param.Request.UserAgent(),
                param.ErrorMessage,
            )
        }))
        router.Use(gin.Recovery())
        router.Use(BeforeRequest()) // 使用自定义中间件

        // This handler will match /user/john but will not match neither /user/ or /user
        router.GET("/user/:name", func(c *gin.Context) {
            log.Printf("get name start!")
            name := c.Param("name") // api 参数通过Context的Param方法来获取
            c.String(http.StatusOK, "Hello %s", name)
            log.Printf("get name end!")
        })

        // However, this one will match /user/john/ and also /user/john/send
        // If no other routers match /user/john, it will redirect to /user/john/
        router.GET("/user/:name/*action", group_dome)

        // url 为 http://localhost:8080/welcome?name=ningskyer时
        // 输出 Hello ningskyer
        // url 为 http://localhost:8080/welcome时
        // 输出 Hello Guest
        router.GET("/welcome", func(c *gin.Context) {
            name := c.DefaultQuery("name", "Guest") //可设置默认值
            last_name := c.Query("lastname")        // 是 c.Request.URL.Query().Get("lastname") 的简写
            fmt.Println("Hello ", name, last_name)
        })

        // 上传文件
        // Set a lower memory limit for multipart forms (default is 32 MiB)
        // router.MaxMultipartMemory = 8 << 20  // 8 MiB
        router.POST("/upload", func(c *gin.Context) {
            // single file
            file, _ := c.FormFile("file")
            log.Println(file.Filename)

            // Upload the file to specific dst.
            // c.SaveUploadedFile(file, dst)

            c.String(http.StatusOK, fmt.Sprintf("'%s' uploaded!", file.Filename))
        })

        //form
        router.POST("/form", func(c *gin.Context) {
            name := c.DefaultPostForm("name", "alert") //可设置默认值
            msg := c.PostForm("msg")                   //表单参数通过 PostForm 方法获取
            title := c.PostForm("title")
            fmt.Println("type is %s, msg is %s, title is %s", name, msg, title)
        })
        // 群组 /v1/Get ;/v1/Post
        someGroup := router.Group("/v1")
        {
            someGroup.GET("/Get", group_dome)
            someGroup.POST("/Post", group_dome)

        }
        // JSON/XML/YAML响应
        router.GET("/moreJSON", func(c *gin.Context) {
            // You also can use a struct
            var msg struct {
                Name    string `json:"user" xml:"user"`
                Message string
                Number  int
            }
            msg.Name = "Lena"
            msg.Message = "hey"
            msg.Number = 123
            // 注意 msg.Name 变成了 "user" 字段
            // 以下方式都会输出 :   {"user": "Lena", "Message": "hey", "Number": 123}
            c.JSON(http.StatusOK, gin.H{"user": "Lena", "Message": "hey", "Number": 123})
            c.JSON(http.StatusOK, msg)
            c.XML(http.StatusOK, gin.H{"user": "Lena", "Message": "hey", "Number": 123})
            c.XML(http.StatusOK, msg)
            c.YAML(http.StatusOK, gin.H{"user": "Lena", "Message": "hey", "Number": 123})
            c.YAML(http.StatusOK, msg)
            names := []string{"lena", "austin", "foo"}
            // Will output  :   while(1);["lena","austin","foo"]
            c.SecureJSON(http.StatusOK, names) //使用 SecureJSON 防止 json 劫持
            data := map[string]interface{}{
                "foo": "bar",
            }
            //callback is x
            // Will output  :   x({\"foo\":\"bar\"})
            c.JSONP(http.StatusOK, data)
        })

        //加载模板 --路径有问题..............
        //router.LoadHTMLGlob("D:\\Go\\go_test\\dome_file\\gin_dome\\templates\\*")
        //router.LoadHTMLFiles("D:\\Go\\go_test\\dome_file\\gin_dome\\templates\\index.html")
        //定义路由
        router.GET("/index", func(c *gin.Context) {
            //根据完整文件名渲染模板,并传递参数
            c.HTML(http.StatusOK, "index.tmpl", gin.H{
                "title": "Main website",
            })
        })
        // 重定向
        router.GET("/redirect", func(c *gin.Context) {
            //支持内部和外部的重定向
            c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com/")
        })

        //1. 异步
        router.GET("/long_async", func(c *gin.Context) {
            // goroutine 中只能使用只读的上下文 c.Copy()
            cCp := c.Copy()
            go func() {
                time.Sleep(5 * time.Second)

                // 注意使用只读上下文
                log.Println("Done! in path " + cCp.Request.URL.Path)
            }()
            c.JSON(http.StatusOK, gin.H{"user": gin.H{"a": gin.H{"b": "b",}, "Number": 123}, "Message": "hey", "Number": 123})
        })
        // {"Message":"hey","Number":123,"user":{"Number":123,"a":{"b":"b"}}} --- json可以嵌套使用
        //2. 同步
        router.GET("/long_sync", func(c *gin.Context) {
            time.Sleep(5 * time.Second)

            // 注意可以使用原始上下文
            log.Println("Done! in path " + c.Request.URL.Path)
        })
        router.Any("/", any)
        router.NoRoute(go404) // 定义404

        /* 使用结构体绑定请求------对比json嵌套
        curl "http://localhost:8080/getb?field_a=hello&field_b=world"
        {"a":{"FieldA":"hello"},"b":"world"}
        $ curl "http://localhost:8080/getc?field_a=hello&field_c=world"
        {"a":{"FieldA":"hello"},"c":"world"}
        $ curl "http://localhost:8080/getd?field_x=hello&field_d=world"
        {"d":"world","x":{"FieldX":"hello"}}
        */

        router.GET("/getb", GetDataB)
        router.GET("/getc", GetDataC)
        router.GET("/getd", GetDataD)

        // 使用参数校验
        type Login struct {
            User     string `form:"user" json:"user" xml:"user"  binding:"required"`
            Password string `form:"password" json:"password" xml:"password" binding:"required"`
        }
        // Example for binding JSON ({"user": "manu", "password": "123"})
        router.POST("/loginJSON", func(c *gin.Context) {
            var json Login
            if err := c.ShouldBindJSON(&json); err != nil {
                c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
                return
            }

            if json.User != "manu" || json.Password != "123" {
                c.JSON(http.StatusUnauthorized, gin.H{"status": "unauthorized"})
                return
            }

            c.JSON(http.StatusOK, gin.H{"status": "you are logged in"})
        })
        //curl -X POST http://172.16.2.192:8083/loginJSON -H 'content-type: application/json' -d '{ "user": "manu" }'
        //{"error":"Key: 'Login.Password' Error:Field validation for 'Password' failed on the 'required' tag"}
        // curl -X POST http://172.16.2.192:8083/loginJSON -H 'content-type: application/json' -d '{ "user": "manu","password":"123"}'
        //{"status":"you are logged in"}

        router.Any("/testing", startPage)
        // curl "http://172.16.2.192:8083/testing?name=123&address=213143141dadad&birthday=2006-01-03"

        router.GET("/cookie", func(c *gin.Context) {
            cookie, err := c.Cookie("gin_cookie") // 获取cookie
            if err != nil {
                cookie = "NotSet"
                c.SetCookie("gin_cookie", "test", 3600, "/", "localhost", false, true)
                c.JSON(http.StatusOK, gin.H{"code": 200, "mgs": "登录成功", "data": nil})
            } else {
                c.JSON(http.StatusOK, gin.H{"code": 200, "mgs": "你已登录", "data": nil})
            }

            fmt.Printf("Cookie value: %s \n", cookie)

        })
        //curl -v "http://172.16.2.192:8083/cookie"
        //curl -v -b "gin_cookie=test" "http://172.16.2.192:8083/cookie"

        // 处理header
        router.GET("/header", func(c *gin.Context) {
            header := c.GetHeader("context")
            if header != "haha" {
                c.Header("context", "haha")
                c.JSON(http.StatusOK, gin.H{"code": 200, "mgs": "设置header成功", "data": nil})
            } else {
                c.JSON(http.StatusOK, gin.H{"code": 200, "mgs": "header已存在", "data": nil})
            }
        })
        //curl  "http://172.16.2.192:8083/header"
        //{"code":200,"data":null,"mgs":"设置header成功"}
        //curl -H 'context:haha' "http://172.16.2.192:8083/header"
        ///{"code":200,"data":null,"mgs":"header已存在"}

        //启动单个服务的两种方式
        //router.Run("127.0.0.1:8083") // 也可以如下定义

        s := &http.Server{
            Addr:           ":8083",
            Handler:        router,
            ReadTimeout:    10 * time.Second,
            WriteTimeout:   10 * time.Second,
            MaxHeaderBytes: 1 << 20,
        }
        go func() {
            // service connections
            if err := s.ListenAndServe(); err != nil && err != http.ErrServerClosed {
                log.Fatalf("listen: %s\n", err)
            }
        }()

        //启用多个服务
        //var (
        //	g errgroup.Group
        //)
        //server01 := &http.Server{
        //	Addr:         ":8080",
        //	Handler:      router,
        //	ReadTimeout:  5 * time.Second,
        //	WriteTimeout: 10 * time.Second,
        //}
        //
        //server02 := &http.Server{
        //	Addr:         ":8081",
        //	Handler:      router,
        //	ReadTimeout:  5 * time.Second,
        //	WriteTimeout: 10 * time.Second,
        //}
        //
        //g.Go(func() error {
        //	return server01.ListenAndServe()
        //})
        //
        //g.Go(func() error {
        //	return server02.ListenAndServe()
        //})
        //
        //if err := g.Wait(); err != nil {
        //	log.Fatal(err)
        //}
        // Wait for interrupt signal to gracefully shutdown the server with
        // a timeout of 5 seconds.

        //优雅重启或停止
        quit := make(chan os.Signal)
        signal.Notify(quit, os.Interrupt)
        <-quit
        log.Println("Shutdown Server ...")

        ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
        defer cancel()
        if err := s.Shutdown(ctx); err != nil {
            log.Println("Server Shutdown:", err)
        }
        log.Println("Server exiting")
    }

    //********************************************************************************************************************//
    //自定义 函数,HandlerFunc
    func group_dome(c *gin.Context) {
        name := c.Param("name")
        action := c.Param("action")
        message := name + " is " + action
        c.String(http.StatusOK, message)
    }
    func any(c *gin.Context) {
        method := c.Request.Method
        ip := c.Request.Host
        c.JSON(http.StatusOK, gin.H{
            "method": method,
            "ip":     ip,
        })
    }

    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 startPage(c *gin.Context) {
        var person Person
        if c.ShouldBindQuery(&person) == nil {
            fmt.Println("====== Only Bind By Query String ======")
            fmt.Println(person.Name, person.Address, person.Birthday)
        }
        c.String(200, "Success")
    }
    func go404(c *gin.Context) {
        c.JSON(http.StatusNotFound, gin.H{"code": "404", "msg": "page not found!", "data": nil})
    }

    type StructA struct {
        FieldA string `form:"field_a"`
    }

    type StructB struct {
        NestedStruct StructA
        FieldB       string `form:"field_b"`
    }

    type StructC struct {
        NestedStructPointer *StructA
        FieldC              string `form:"field_c"`
    }

    type StructD struct {
        NestedAnonyStruct struct {
            FieldX string `form:"field_x"`
        }
        FieldD string `form:"field_d"`
    }

    func GetDataB(c *gin.Context) {
        var b StructB
        c.Bind(&b)
        c.JSON(200, gin.H{
            "a": b.NestedStruct,
            "b": b.FieldB,
        })
    }

    func GetDataC(c *gin.Context) {
        var b StructC
        c.Bind(&b)
        c.JSON(200, gin.H{
            "a": b.NestedStructPointer,
            "c": b.FieldC,
        })
    }

    func GetDataD(c *gin.Context) {
        var b StructD
        c.Bind(&b)
        c.JSON(200, gin.H{
            "x": b.NestedAnonyStruct,
            "d": b.FieldD,
        })
    }

    // 关于中间件的部分,参考官方README

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants