正确处理多个查询Go Rest API [英] Correct approach to multiple queries 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 returnjson.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屋!
查看全文