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")
}
运行结果:


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!