正确处理多个查询Go Rest API [英] Correct approach to multiple queries Go Rest API

查看:202
本文介绍了正确处理多个查询Go Rest API的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图在Go中创建一个REST API。

  [{Name:QA1 ,服务器:BOT1,描述:工具,应用程序:},
{名称:QA1,服务器:, ,Apps:Duo},
{Name:QA1,Server:,Description:,Apps:Git},
{Name:QA1,Server:,Description:,Apps:php}]

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

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

我显然有办法让我的查询或结构(或两者或其他)不完全正确。我想确保我明白如何正确执行此操作,因为我希望将其扩展到其他查询和其他查询中。我已经在下面列出了完整代码。



要清楚的是,我并不是简单地寻找解决方案(尽管我当然会明白, ),但我的思路出了问题,以及正确的做法。


导入(
database / sql
encoding / json
fmt
_github.com/go-sql-driver/mysql
io / ioutil
log
net / http


//一个卷上可以有零个或多个应用程序
类型应用程序struct {
名称字符串
}

//卷有一个名称,描述,位于服务器上并具有多个服务/应用程序
类型卷struct {
名称字符串
服务器字符串
描述字符串
服务应用程序
}

//处理所有请求
func Handler(响应http.ResponseWriter,request * http.Request){
response.Header()。Set(Content-type,text / html )
网页,err:= ioutil.ReadFile(index.html)
if err!= nil {
http.Error(response,fmt.Sprintf(home.html file错误%v,错误),500)
}
fmt.Fprint(响应,字符串(网页))
}

//数据库连接
常量(
DB_HOST =mydbhost
DB_NAME =mydb
DB_USER =mydbuser
DB_PASS =mydbpass


//响应表单/ api的URL
func APIHandler(响应http.ResponseWriter,request * http.Request){

//连接数据库
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 open not open一个连接。验证DSN数据:
err = db.Ping()
if err!= nil {
fmt.Println(err.Error())
}

//将mime类型设置为JSON
response.Header()。Set(Content-type,application / json)

result:= [] * Volume {}

switch request.Method {
caseGET:
srvrnm:= request.URL.Query()。Get(srvrnm)
appnm:= request.URL.Query()。Get(appnm)
srvrs,err:= db.Prepare(选择VOLUMES.name作为volnm,SERVERS.name作为srvrnm,VOLUMES.description作为descr从VOLUMES LEFT JOIN SERVERS ON VOLUMES.server_id = SERVERS.id where SERVERS.name =?)
if err!= nil {
fmt.Print(err)
}
srvcs,err: =db.Prepare(select volume.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!= ni l {
fmt.Print(err)
}

//运行SQL查询以获取Volum&描述从主机名
srvrrows,err:= srvrs.Query(srvrnm)
if err!= nil {
fmt.Print(err)
}
for srvrrows。下一步(){
var volnm字符串
var srvrnm字符串
var descr字符串
//扫描第一个查询
err = srvrrows.Scan(& volnm,& amp ; srvrnm,& descr)
if err!= nil {
fmt.Println(Error scanning:+ err.Error())
return
}
//追加切片,包含扫描结果
result = append(result,& Volume {Name:volnm,Server:srvrnm,Description:descr})
}

//运行SQL查询服务/应用程序
srvcrows,err:= srvcs.Query(appnm)
if err!= nil {
fmt.Print(err)
}

for srvcrows.Next(){
var volnm string
var appnm string
//扫描第二个查询
err = srvcrows.Scan(& volnm,& appnm)
if err!= nil {
fmt.Println(错误扫描:+ err.Error())
return
}
//追加切片,扫描结果
result = append(result,& Volume {Name: volnm,Apps:appnm})
}
默认值:
}

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))

//在此服务器上使用这些路由开始在给定端口上列出。
log.Print(Listening on port+ port +...)
errs:= http.ListenAndServe(:+ port,mux)
if errs!= nil {
log.Fatal(ListenAndServe error:,err)
}
}


<解析方案

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

 

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

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

 类型体积结构{
名称字符串
服务器字符串
描述字符串
服务[]应用程序

如果您希望应用程序实际输出 Duo | Git | php 然后您可以使用JSON [] Apps href =https://golang.org/pkg/encoding/json/#Marshaler =nofollow noreferrer> Marshaler i mplementation。这可以简单地返回 json.Marshal(strings.join(names,|))



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

 名称|服务器| Desc | App 
---- | ------ | ----- | ---
Vol1 | Srv1 | Desc1 | App1
Vol1 | Srv1 | Desc1 | App2
Vol2 | Srv2 | Desc2 | App3

然后,您将循环播放此内容并检测您是否正在查看新音量。如果是这样,请在结果中创建一个新条目。如果没有,请将该应用添加到应用列表中。例如:

  var(
volnm字符串
srvrnm字符串
descr字符串
appnm string
v * Volume
result [] * Volume


for srvrrows.Next(){
if err = srvcrows.Scan(& amp; ; volnm,& srvrnm,& descr,& appnm); err!= nil {
//处理错误
}

//将应用添加到当前卷if相同,否则开始新的音量
如果v!= nil&& v.Name == volnm {
v.Services = append(v.Services,Apps {appnm})
} else {
v =& Volume {
名称:volnm,
服务器:svrnm,
描述:descr,
服务:[] Apps {appnm}}
result = append(result,v)
}
}

//完成,返回结果等...

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


I am attempting to create a REST API in Go. I have it partially working in that it will return 4 separate json objects like such:

[{"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"}]

What I want is a single returned object like:

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

I obviously have the way that I am either making my queries or the structs (or both or something else) not quite correct. I want to make sure I understand how to do this right because I would like to expand on it for other queries and such down the road. I have included the "full" go code below.

To be clear, I'm not simply looking for the solution (though I would of course appreciate that to compare with), but where I've gone wrong in my thinking and what the correct approach would be.

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)
    }
}

解决方案

From the sounds of it, you want to your result to look like:

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

Hence you want your Volumes struct to look like:

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

If you want the Apps to actually output Duo|Git|php then you could create a custom type instead of []Apps with a JSON Marshaler implementation. This could simply return json.Marshal(strings.join(names,"|"))

Rather than run two separate queries, it would be more efficient to run a single query that selects the product of volumes & apps together. It is important that this query is sorted by volume so all volume rows are contiguous. Example query output would be:

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

You would then loop over this and detect if you are looking at a new volume. If so, create a new entry in the result. If not, add the App to the list of apps. For example:

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...

When taking this approach, you need an appropriate parent record discriminator. I'd just used v.Name == volnm for illustration purposes but this should really be checking the primary key. You can make this an unexported (lowercase) field in the struct if you do not wish to export it through the API.

这篇关于正确处理多个查询Go Rest API的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆