【Go Web开发】解析API查询参数

接下来的内容,我们将重点介绍如何为GET /v1/movies接口构建高级功能,该接口将以JSON数组的形式返回多个movie详细信息。

Method URL 动作
GET /v1/healthcheck 显示应用程序运行状况和版本信息
POST /v1/movies 添加新的电影
GET /v1/movies/:id 根据id查询特定电影
PUT /v1/movies/:id 更新特定电影
DELETE /v1/movies/:id 删除特定电影
GET /v1/movies 查询电影详情列表

我们将逐步为这个接口开发额外功能,首先返回所有影片的数据,然后通过添加过滤、排序和分页功能逐渐使其变得更实用。

在后面的内容中你将学到:

  • 在一个JSON响应中返回多个资源的详细信息。
  • 接受并应用可选参数来缩小返回的数据集。
  • 使用PostgreSQL的内置功能在数据库字段上实现全文搜索。
  • 接受并安全地使用排序参数来对数据库查询结果排序。
  • 开发一个实用、可重用的模式来支持大数据集分页,并在JSON响应中返回分页元数据。

下面开始第一部分内容:解析API请求中的查询参数。

解析查询参数

在接下来的几节中,我们将配置GET /v1/movies接口,以便客户端能够通过查询字符串参数返回所需电影数据。例如:

 /v1/movies?title=godfather&genres=crime,drama&page=1&page_size=5&sort=-year

如果客户端发送以上查询请求,意味着向接口传递的信息是:请返回电影名称包含"godfather",电影类型是crime和drama类型,根据年份降序排序的前5条数据。

在sort参数中使用“-“符号表示结果降序排序。例如,参数sort=title指的是根据电影名称按字母升序排序,而sort=-title就是降序。

首先,需要做的就是如何将这些参数解析到Go代码中。在Go中可以使用r.URL.Query()函数来解析查询参数。该函数返回url.Values()类型,是一个包含查询参数的map类型。我们可以使用Get()方法提取查询参数,如果参数值存在就返回否则返回空字符串。

在我们的示例中,还需要对其中一些查询字符串值执行额外的处理。具体地说:

  • genre参数可能会包含多个用逗号隔开的值,例如:genres=crime,drama。我们需要将这些值分开并存放在一个[]string切片中。
  • page和page_size参数值是数字,需要将字符串转为int类型。

除此之外:

  • 还需要对这些参数做校验,例如page和page_size不能是负数。
  • 如果page,page_size和sort客户端没有提供值的话,需要设置默认值。

创建帮助函数

为此,我们将创建三个新的帮助函数:readString()、readInt()和readCSV()。我们将使用这些帮助函数从查询字符串中提取和解析值,或者在必要时返回一个默认值。

在cmd/api/helpers.go文件添加以下代码:

// readString 从查询字符串中返回一个字符串值,如果没有匹配的key就返回默认值
func (app *application)readString(qs url.Values, key string, defaultValue string) string {
    s := qs.Get(key)

    if s == ""{
        return defaultValue
    }
    return s
}

// readCSV 从查询中读取一个字符串并根据逗号分割,返回一个字符串切片
func (app *application)readCSV(qs url.Values, key string, defaultValue []string) []string {
    csv := qs.Get(key)
    if csv == "" {
        return defaultValue
    }
    return strings.Split(csv, ",")
}

// readInt 从查询字符串中读取值,并将字符串值转为int类型
func (app *application)readInt(qs url.Values, key string, defaultValue int, v *validator.Validator) int {
    s := qs.Get(key)

    if s == "" {
        return defaultValue
    }

    i, err := strconv.Atoi(s)
    if err != nil {
        v.AddError(key, "must be integer value")
        return defaultValue
    }
    return i
}

添加API处理程序和路由

接下来,我们为GET /v1/movies接口创建新的API处理程序:listMoviesHandler。为了演示请求参数解析,当前这个处理程序仅使用帮助函数来解析请求中的查询参数,将解析出来的参数返回给客户端。

如果你跟随本系列文章操作的话,接下来创建listMoviesHandler,如下所示:

File: cmd/api/movies.go


package main

...

func (app *application)listMoviesHandler(w http.ResponseWriter, r *http.Request)  {
    var input struct{
        Title string
        Genres []string
        Page int
        PageSize int
        Sort string
    }

    v := validator.New()
    qs := r.URL.Query()

    input.Title = app.readString(qs, "title", "")
    input.Genres = app.readCSV(qs, "genres", []string{})

    input.Page = app.readInt(qs, "page", 1, v)
    input.PageSize = app.readInt(qs, "page_size", 20, v)

    input.Sort = app.readString(qs, "sort", "id")
    //检查校验是否通过
    if !v.Valid(){
        app.failedValidationResponse(w, r, v.Errors)
        return
    }

    //将读取数据返回给客户端
    fmt.Fprintf(w, "%+v\n", input)
}

然后我们需要为这个处理程序添加路由,在cmd/api/routes.go文件中添加以下代码:

File: cmd/api/routes.go


func (app *application) routes() http.Handler {
    router := httprouter.New()

    router.NotFound = http.HandlerFunc(app.notFoundResponse)
    router.MethodNotAllowed = http.HandlerFunc(app.methodNotAllowedResponse)

    router.HandlerFunc(http.MethodGet, "/v1/healthcheck", app.healthcheckHandler)

    // 为GET /v1/movies接口添加路由
    router.HandlerFunc(http.MethodGet, "/v1/movies", app.listMoviesHandler)
    router.HandlerFunc(http.MethodPost, "/v1/movies", app.createMovieHandler)
    router.HandlerFunc(http.MethodGet, "/v1/movies/:id", app.showMovieHandler)
    router.HandlerFunc(http.MethodPatch, "/v1/movies/:id", app.updateMovieHandler)
    router.HandlerFunc(http.MethodDelete, "/v1/movies/:id", app.deleteMovieHandler)
    return app.recoverPanic(app.rateLimit(router))
}

重启服务后,就可以向GET /v1/movies接口发送带有查询参数的请求。如下所示:

使用curl发送带查询参数请求时,url必须使用双引号。

$ curl "localhost:4000/v1/movies?title=godfather&genres=crime,drama&page=1&page_size=5&sort=year"
{Title:godfather Genres:[crime drama] Page:1 PageSize:5 Sort:year}

看起来不错,请求中的查询参数都正确解析成Go结构体类型字段。还可以尝试不使用查询字符串参数发出请求。在本例中,您应该看到input结构中的值采用了我们在listMoviesHandler代码中指定的默认值。像这样:

$ curl localhost:4000/v1/movies                                                           
{Title: Genres:[] Page:1 PageSize:20 Sort:id}

创建Filters结构体

page,page_size和sort查询参数比较常用,在其他API查询接口中也需要使用。因此,为了查询简单,我们将其放在一个可复用的Filters结构体中。

创建internal/data/filters.go文件:

touch internal/data/filters.go

添加以下代码:

package data

type Filters struct {
    Page int
    PageSize int
    Sort string
}

完成以上操作后,回到listMoviesHandler处理程序,更新代码使用Filters结构体:

File: cmd/api/movies.go


package main

....

func (app *application) listMoviesHandler(w http.ResponseWriter, r *http.Request) {
    var input struct {
        Title  string
        Genres []string
        data.Filters
    }

    v := validator.New()
    qs := r.URL.Query()

    input.Title = app.readString(qs, "title", "")
    input.Genres = app.readCSV(qs, "genres", []string{})

    input.Filters.Page = app.readInt(qs, "page", 1, v)
    input.Filters.PageSize = app.readInt(qs, "page_size", 20, v)

    input.Filters.Sort = app.readString(qs, "sort", "id")
    //检查校验是否通过
    if !v.Valid() {
        app.failedValidationResponse(w, r, v.Errors)
        return
    }

    //将读取数据返回给客户端
    fmt.Fprintf(w, "%+v\n", input)
}

此时,您应该能够再次运行API,并且一切都应该像前面一样正常工作。

你可能感兴趣的:(【Go Web开发】解析API查询参数)