正确处理多个查询Go Rest API

时间:2017-07-28 13:21:49

标签: go

我正在尝试在Go中创建REST API。我有部分工作,它将返回4个单独的json对象,如:

[{"Name":"QA1","Server":"BOT1","Description":"Tools","Apps":""},
{"Name":"QA1","Server":"","Description":"","Apps":"Duo"},
{"Name":"QA1","Server":"","Description":"","Apps":"Git"},
{"Name":"QA1","Server":"","Description":"","Apps":"php"}]

我想要的是一个返回的对象,如:

[{"Name":"QA1","Server":"BOT1","Description":"Tools","Apps": "Duo|Git|php"}]

我显然有办法让我的查询或结构(或两者或其他东西)不完全正确。我想确保我理解如何正确执行此操作,因为我希望将其扩展到其他查询等等。我已在下面添加了“完整”代码。

要明确的是,我不仅仅是在寻找解决方案(虽然我当然希望能与之相提并论),但我的思维方式出错并且正确的方法是什么。

package main

import (
    "database/sql"
    "encoding/json"
    "fmt"
    _ "github.com/go-sql-driver/mysql"
    "io/ioutil"
    "log"
    "net/http"
)

// There can be zero or more apps on a volume
type Apps struct {
    Name string
}

// Volumes have a name, description, are on a server and have multiple services/apps
type Volume struct {
    Name        string
    Server      string
    Description string
    Services    Apps
}

//Handle all requests
func Handler(response http.ResponseWriter, request *http.Request) {
    response.Header().Set("Content-type", "text/html")
    webpage, err := ioutil.ReadFile("index.html")
    if err != nil {
        http.Error(response, fmt.Sprintf("home.html file error %v", err), 500)
    }
    fmt.Fprint(response, string(webpage))
}

// DB Connection
const (
    DB_HOST = "mydbhost"
    DB_NAME = "mydb"
    DB_USER = "mydbuser"
    DB_PASS = "mydbpass"
)

// Respond to URLs of the form /api
func APIHandler(response http.ResponseWriter, request *http.Request) {

    //Connect to database
    dsn := DB_USER + ":" + DB_PASS + "@" + DB_HOST + "/" + DB_NAME + "?charset=utf8"
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        fmt.Println(err.Error())
    }
    defer db.Close()

    // Open doesn't open a connection. Validate DSN data:
    err = db.Ping()
    if err != nil {
        fmt.Println(err.Error())
    }

    //set mime type to JSON
    response.Header().Set("Content-type", "application/json")

    result := []*Volume{}

    switch request.Method {
    case "GET":
        srvrnm := request.URL.Query().Get("srvrnm")
        appnm := request.URL.Query().Get("appnm")
        srvrs, err := db.Prepare("select VOLUMES.name as volnm, SERVERS.name as srvrnm, VOLUMES.description as descr From VOLUMES LEFT JOIN SERVERS ON VOLUMES.server_id = SERVERS.id where SERVERS.name = ?")
        if err != nil {
            fmt.Print(err)
        }
        srvcs, err := db.Prepare("select VOLUMES.name as volnm, SUPPRTSVCS.name as app_name From VOLUMES as VOLUMES JOIN HOSTSVCS ON VOLUMES.id = HOSTSVCS.volume_id JOIN SUPPRTSVCS ON SUPPRTSVCS.id = HOSTSVCS.supportsvcs_id where VOLUMES.name = ?")
        if err != nil {
            fmt.Print(err)
        }

        // Run the SQL Query to Get Volum & Description From Hostname
        srvrrows, err := srvrs.Query(srvrnm)
        if err != nil {
            fmt.Print(err)
        }
        for srvrrows.Next() {
            var volnm string
            var srvrnm string
            var descr string
            // Scan the First Query
            err = srvrrows.Scan(&volnm, &srvrnm, &descr)
            if err != nil {
                fmt.Println("Error scanning: " + err.Error())
                return
            }
            // Append Slice with results from the scan
            result = append(result, &Volume{Name: volnm, Server: srvrnm, Description: descr})
        }

        // Run the SQL Query for Services/Apps
        srvcrows, err := srvcs.Query(appnm)
        if err != nil {
            fmt.Print(err)
        }

        for srvcrows.Next() {
            var volnm string
            var appnm string
            // Scan the Second Query
            err = srvcrows.Scan(&volnm, &appnm)
            if err != nil {
                fmt.Println("Error scanning: " + err.Error())
                return
            }
            // Append Slice with results from the scan
            result = append(result, &Volume{Name: volnm, Apps: appnm})
        }
    default:
    }

    json, err := json.Marshal(result)
    if err != nil {
        fmt.Println(err)
        return
    }

    fmt.Fprintf(response, string(json))
    db.Close()
}

func main() {
    port := "1236"
    var err string

    mux := http.NewServeMux()
    mux.Handle("/api", http.HandlerFunc(APIHandler))
    mux.Handle("/", http.HandlerFunc(Handler))

    // Start listing on a given port with these routes on this server.
    log.Print("Listening on port " + port + " ... ")
    errs := http.ListenAndServe(":"+port, mux)
    if errs != nil {
        log.Fatal("ListenAndServe error: ", err)
    }
}

1 个答案:

答案 0 :(得分:1)

从它的声音中,你希望你的结果看起来像:

[
  {"Name":"QA1","Server":"BOT1","Description":"Tools","Apps": ["Duo","Git","php"]
]

因此,您希望您的Volumes结构看起来像:

type Volume struct {
    Name        string
    Server      string
    Description string
    Services    []Apps
}

如果您希望应用实际输出 Duo|Git|php ,那么您可以使用JSON Marshaler实现创建自定义类型而不是[]Apps。这可以简单地返回json.Marshal(strings.join(names,"|"))

不是运行两个单独的查询,而是运行选择卷和产品的单个查询会更有效。应用在一起。此查询按卷排序非常重要,因此所有卷行都是连续的。示例查询输出将是:

Name | Server | Desc  | App
---- | ------ | ----- | ---
Vol1 | Srv1   | Desc1 | App1 
Vol1 | Srv1   | Desc1 | App2
Vol2 | Srv2   | Desc2 | App3

然后,您将遍历此并检测您是否正在查看新卷。如果是,请在结果中创建一个新条目。如果没有,请将应用程序添加到应用程序列表中。例如:

var (
   volnm string 
   srvrnm string
   descr string
   appnm string 
   v     *Volume
   result []*Volume
)

for srvrrows.Next() {
    if err = srvcrows.Scan(&volnm, &srvrnm, &descr, &appnm);err!=nil {
       // Handle error
    }

    // Add App to current volume if same, otherwise start a new volume
    if v!=nil && v.Name == volnm {
       v.Services = append(v.Services,Apps{appnm}) 
    } else {
       v = &Volume{
          Name: volnm, 
          Server: svrnm, 
          Description: descr, 
          Services: []Apps{appnm}}
       result = append(result,v)
    }
}

// Finished, return result etc...

采用这种方法时,您需要一个合适的父记录鉴别器。我刚刚使用v.Name == volnm进行说明,但这应该是检查主键。如果您不希望通过API导出它,可以将其设置为结构中的未导出(小写)字段。