Go实战记录总结

3,677 阅读4分钟

1、http请求中第二次读取body时无数据问题

在go的网络编程中,无论是request中的body,还是response中的body都是io.ReadCloser类型,意味着一旦全部读取完成,就无法进行第二次读取,因为在io.ReadCloser的内部会有一个标记,记录读取到什么位置,因此一旦读到尾,就不能再从头读取了。

由于ReadCloser不能Seek,因此一般的解决方法就是把body读出来之后,再重新包装成io.ReadColser,然后再绑定回body上。以gin中的中间件读取为例,先在中间件中读取body,然后在控制器里读取body,

func printBody(ctx *gin.Context) {
	defer ctx.Request.Body.Close()
	body, _ := ioutil.ReadAll(ctx.Request.Body)
	ctx.Request.Body = ioutil.NopCloser(bytes.NewReader(body)) //关键一步,把已经读取出来的body数据,使用NopCloser重新包装成io.ReadCloser
	fmt.Println(string(body))
}

func main() {
	r := gin.Default()
	r.Use(printBody)
	r.POST("/test", func(ctx *gin.Context) {
		defer ctx.Request.Body.Close()
		body, _ := ioutil.ReadAll(ctx.Request.Body)
		fmt.Println("router:", string(body))
	})
	r.Run(":9999")
}

运行结果:

如果把关键的那步代码去掉,则读取不到,变成了以下结果:

当然,如果不需要二次读取body数据,那么也就没必要这么操作了。

2、使用sql语句时,字段名大小写问题

之前一直用的是mongo,后来在一次开发中使用postgresql,在建表时,设置了两个字段名,appKey和appSecret,然后在go中拼接sql语句时,一开始是这么写的

db.Where("appKey = ? AND appSecret = ?", appKey,appSecret).First(&user)

程序会报 pq: column "appkey" does not exist 错误,查看执行的sql的语句:

SELECT * FROM "secrets"  WHERE "secrets"."deletedAt" IS NULL AND ((appKey = '123' AND appSecret = '123'))

查阅资料之后发现,sql语句对大小写是不敏感的,它会把appKey和appSecret看成appkey,appsecret,因此就会出错

解决的方法是对需要大写的字符串用双引号引起来,如以下代码

db.Where("\"appKey\" = ? AND \"appSecret\" = ?", appKey,appSecret).First(&user) // 使用 \ 对双引号进行转义

查看执行的sql语句:

SELECT * FROM "secrets"  WHERE "secrets"."deletedAt" IS NULL AND (("appKey" = '123' AND "appSecret" = '123'))

这个时候,大小写就被区分开了,程序也就正常了。

但是这样会在代码里出现过多的反斜杆,不是特别的舒服,因此建议使用下划线来重新命名字段,即appKey,appSecret改为app_key,app_secret

3、map与json

在go中,如果发送了一个http请求,返回一个json,然后要对这个json进行操作,可以使用json.Unmarshal(),来将获取到的json转为map,然后再进行处理。

func main() {
	url := "http://localhost:9900/test"
	req, _ := http.NewRequest("GET", url, nil)
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)
	var data map[string]interface{}
	json.Unmarshal(body, &data) // 使用Unmarshal,将返回的json转为map
	fmt.Println(data) // map[a:1 b:2]
}

如果一个接口要返回一个json数据响应,则可以使用json.Marshal(),来将map转为json字符串

func main() {
	http.HandleFunc("/test", func(writer http.ResponseWriter, request *http.Request) {
		data := map[string]int{
			"a": 1,
			"b": 4,
		}
		resp, _ := json.Marshal(data) // 此时的resp是[]byte,使用string(resp),可以得到{"a":1,"b":4}的json字符串
		writer.Header().Add("Content-Type", "application/json")
		writer.WriteHeader(200)
		writer.Write(resp)
	})
	http.ListenAndServe(":6666", nil)
}

这两种互相转化的方式,在一些web框架中,被大量的使用,比如在gin中,常用的context.JSON()方法,最终调用的是一个WriteJSON方法,而这个方法也是很简洁

func WriteJSON(w http.ResponseWriter, obj interface{}) error {
	writeContentType(w, jsonContentType)
	jsonBytes, err := json.Marshal(obj) // 就是这里,将传入的interface{}转为[]byte形式的json字符串
	if err != nil {
		return err
	}
	_, err = w.Write(jsonBytes)
	return err
}

4、使用gorm时,postgresql外键约束问题

使用关系型数据库,设计表的时候,有时会设计外键,这个时候也会给外键添加约束,也就是参照完整性约束。然而在使用gorm,编写数据模型时,发现无法建立外键约束,外键的关联是建立起来了,但是约束没有建立起来。gorm使用ForeignKey,AssociationForeignKey都无法自动建立约束,即便使用sql tag也建立不了,尝试了好几次。最终使用了gorm的api,用api建立外键约束

Client.CreateTable(&model.User{})
Client.CreateTable(&model.Profile{})
Client.Model(&model.User{}).AddForeignKey("profileId", "profile(id)", "SET NULL", "CASCADE")
// 为user表添加profileId外键,并设置外键约束,删除时SET NULL, 更新时CASCADE

注意这个AddForeignKey方法,如果已经建立了约束,再次执行程序会报错,所以要做适当的判断。

结语

第一次使用go开发系统,多多少少在使用上有些不习惯,以上是在开发中遇到的一些问题,再接再厉!

Thanks!