Go Web 开发秘籍(三)

原文:zh.annas-archive.org/md5/6712F93A50A8E516D2DB7024F42646AC

译者:飞龙

协议:CC BY-NC-SA 4.0

第五章:使用 SQL 和 NoSQL 数据库

在本章中,我们将涵盖以下内容:

  • 集成 MySQL 和 Go

  • 在 MySQL 中创建您的第一条记录

  • 从 MySQL 中读取记录

  • 更新您的第一条记录在 MySQL 中

  • 从 MySQL 中删除您的第一条记录

  • 集成 MongoDB 和 Go

  • 在 MongoDB 中创建您的第一个文档

  • 从 MongoDB 中读取文档

  • 在 MongoDB 中更新您的第一个文档

  • 从 MongoDB 中删除您的第一个文档

介绍

每当我们想要持久保存数据时,我们总是期待将其保存在数据库中,主要分为两类——SQLNoSQL。每个类别下都有许多可以根据业务用例使用的数据库,因为每个数据库都具有不同的特性并且服务于不同的目的。

在本章中,我们将把 Go Web 应用程序与最著名的开源数据库——MySQLMongoDB集成,并学习在它们上执行 CRUD 操作。由于我们将使用 MySQL 和 MongoDB,我假设这两个数据库都已安装并在您的本地机器上运行。

集成 MySQL 和 Go

假设您是一名开发人员,并且希望将应用程序数据保存在 MySQL 数据库中。作为第一步,您必须在应用程序和 MySQL 之间建立连接,我们将在本示例中介绍。

准备就绪…

通过执行以下命令验证本地端口3306上是否安装并运行了 MySQL:

$ ps -ef | grep 3306

这应该返回以下响应:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

还要登录到 MySQL 数据库并创建一个 mydb 数据库,执行如下截图中显示的命令:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如何做…

  1. 使用go get命令安装github.com/go-sql-driver/mysql包,如下所示:
$ go get github.com/go-sql-driver/mysql
  1. 创建connect-mysql.go。然后我们连接到 MySQL 数据库并执行SELECT查询以获取当前数据库名称,如下所示:
package main
import 
(
  "database/sql"
  "fmt"
  "log"
  "net/http"
  "github.com/go-sql-driver/mysql"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  DRIVER_NAME = "mysql"
  DATA_SOURCE_NAME = "root:password@/mydb"
)
var db *sql.DB
var connectionError error
func init() 
{
  db, connectionError = sql.Open(DRIVER_NAME, DATA_SOURCE_NAME)
  if connectionError != nil 
  {
    log.Fatal("error connecting to database :: ", connectionError)
  }
}
func getCurrentDb(w http.ResponseWriter, r *http.Request) 
{
  rows, err := db.Query("SELECT DATABASE() as db")
  if err != nil 
  {
    log.Print("error executing query :: ", err)
    return
  }
  var db string
  for rows.Next() 
  {
    rows.Scan(&db)
  }
  fmt.Fprintf(w, "Current Database is :: %s", db)
}
func main() 
{
  http.HandleFunc("/", getCurrentDb)
  defer db.Close()
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
  if err != nil 
  {
    log.Fatal("error starting http server :: ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run connect-mysql.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080

浏览到http://localhost:8080/将返回当前数据库名称,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们了解我们编写的程序:

  1. 使用import ("database/sql" "fmt" "log" "net/http" _ "github.com/go-sql-driver/mysql"),我们导入了github.com/go-sql-driver/mysql以进行副作用或初始化,使用下划线在导入语句前面明确表示。

  2. 使用var db *sql.DB,我们声明了一个私有的DB实例。

根据项目大小,您可以全局声明一个 DB 实例,使用处理程序将其注入为依赖项,或将连接池指针放入x/net/context中。

  1. 接下来,我们定义了一个init()函数,在其中我们连接到数据库并将数据库驱动程序名称和数据源传递给它。

  2. 然后,我们定义了一个getCurrentDb处理程序,基本上在数据库上执行选择查询以获取当前数据库名称,遍历记录,将其值复制到变量中,最终将其写入 HTTP 响应流。

在 MySQL 中创建您的第一条记录

在数据库中创建或保存记录需要我们编写 SQL 查询并执行它们,实现对象关系映射ORM),或实现数据映射技术。

在这个示例中,我们将编写一个 SQL 查询,并使用database/sql包执行它来创建一条记录。为了实现这一点,您还可以使用 Go 中许多第三方库中可用的任何库来实现 ORM,例如https://github.com/jinzhu/gormhttps://github.com/go-gorp/gorphttps://github.com/jirfag/go-queryset

准备就绪…

由于我们在上一个示例中已经与 MySQL 数据库建立了连接,我们将扩展它以执行 SQL 查询来创建一条记录。

在创建记录之前,我们必须在 MySQL 数据库中创建一个表,我们将通过执行以下截图中显示的命令来完成:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

操作步骤…

  1. 使用go get命令安装github.com/go-sql-driver/mysqlgithub.com/gorilla/mux包,如下所示:
$ go get github.com/go-sql-driver/mysql
$ go get github.com/gorilla/mux
  1. 创建create-record-mysql.go。然后我们连接到 MySQL 数据库并执行 INSERT 查询以创建员工记录,如下所示:
package main
import 
(
  "database/sql"
  "fmt"
  "log"
  "net/http"
  "strconv"
  "github.com/go-sql-driver/mysql"
  "github.com/gorilla/mux"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  DRIVER_NAME = "mysql"
  DATA_SOURCE_NAME = "root:password@/mydb"
)
var db *sql.DB
var connectionError error
func init() 
{
  db, connectionError = sql.Open(DRIVER_NAME, DATA_SOURCE_NAME)
  if connectionError != nil 
  {
    log.Fatal("error connecting to database : ", connectionError)
  }
}
func createRecord(w http.ResponseWriter, r *http.Request) 
{
  vals := r.URL.Query()
  name, ok := vals["name"]
  if ok 
  {
    log.Print("going to insert record in database for name : ",
    name[0])
    stmt, err := db.Prepare("INSERT employee SET name=?")
    if err != nil 
    {
      log.Print("error preparing query :: ", err)
      return
    }
    result, err := stmt.Exec(name[0])
    if err != nil 
    {
      log.Print("error executing query :: ", err)
      return
    }
    id, err := result.LastInsertId()
    fmt.Fprintf(w, "Last Inserted Record Id is :: %s",
    strconv.FormatInt(id, 10))
  } 
  else 
  {
    fmt.Fprintf(w, "Error occurred while creating record in 
    database for name :: %s", name[0])
  }
}
func main() 
{
  router := mux.NewRouter()
  router.HandleFunc("/employee/create", createRecord).
  Methods("POST")
  defer db.Close()
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
  if err != nil 
  {
    log.Fatal("error starting http server : ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run create-record-mysql.go

工作原理…

运行程序后,HTTP 服务器将在本地监听端口8080

从命令行执行POST请求以创建员工记录,将会给出最后创建的记录的 ID:

$ curl -X POST http://localhost:8080/employee/create?name=foo
Last created record id is :: 1

让我们理解我们编写的程序:

  1. 使用import ("database/sql" "fmt" "log" "net/http" "strconv" _ "github.com/go-sql-driver/mysql" "github.com/gorilla/mux"),我们导入了github.com/gorilla/mux来创建一个 Gorilla Mux 路由器,并初始化了 Go MySQL 驱动,导入了github.com/go-sql-driver/mysql包。

  2. 接下来,我们定义了一个createRecord处理程序,它从请求中获取姓名,将其分配给本地变量名,准备一个带有姓名占位符的INSERT语句,该占位符将动态替换为姓名,执行该语句,并最终将最后创建的 ID 写入 HTTP 响应流。

从 MySQL 中读取记录

在上一个示例中,我们在 MySQL 数据库中创建了一个员工记录。现在,在这个示例中,我们将学习如何通过执行 SQL 查询来读取它。

操作步骤…

  1. 使用go get命令安装github.com/go-sql-driver/mysqlgithub.com/gorilla/mux包,如下所示:
$ go get github.com/go-sql-driver/mysql
$ go get github.com/gorilla/mux
  1. 创建read-record-mysql.go,在其中我们连接到 MySQL 数据库,执行SELECT查询以获取数据库中的所有员工,遍历记录,将其值复制到结构体中,将所有记录添加到列表中,并将其写入 HTTP 响应流,如下所示:
package main
import 
(
  "database/sql" "encoding/json"
  "log"
  "net/http"
  "github.com/go-sql-driver/mysql"
  "github.com/gorilla/mux"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  DRIVER_NAME = "mysql"
  DATA_SOURCE_NAME = "root:password@/mydb"
)
var db *sql.DB
var connectionError error
func init() 
{
  db, connectionError = sql.Open(DRIVER_NAME, DATA_SOURCE_NAME)
  if connectionError != nil 
  {
    log.Fatal("error connecting to database :: ", connectionError)
  }
}
type Employee struct 
{
  Id int `json:"uid"`
  Name string `json:"name"`
}
func readRecords(w http.ResponseWriter, r *http.Request) 
{
  log.Print("reading records from database")
  rows, err := db.Query("SELECT * FROM employee")
  if err != nil 
  {
    log.Print("error occurred while executing select 
    query :: ",err)
    return
  }
  employees := []Employee{}
  for rows.Next() 
  {
    var uid int
    var name string
    err = rows.Scan(&uid, &name)
    employee := Employee{Id: uid, Name: name}
    employees = append(employees, employee)
  }
  json.NewEncoder(w).Encode(employees)
}
func main() 
{
  router := mux.NewRouter()
  router.HandleFunc("/employees", readRecords).Methods("GET")
  defer db.Close()
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
  if err != nil 
  {
    log.Fatal("error starting http server :: ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run read-record-mysql.go

工作原理…

运行程序后,HTTP 服务器将在本地监听端口8080

浏览到http://localhost:8080/employees将列出员工表中的所有记录,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们看一下我们编写的程序:

  1. 使用import ("database/sql" "encoding/json" "log" "net/http" _ "github.com/go-sql-driver/mysql" "github.com/gorilla/mux"),我们导入了一个额外的包encoding/json,它有助于将 Go 数据结构编组为JSON

  2. 接下来,我们声明了 Go 数据结构Person,它具有IdName字段。

请记住,在类型定义中字段名称应以大写字母开头,否则可能会出现错误。

  1. 接下来,我们定义了一个readRecords处理程序,它查询数据库以获取员工表中的所有记录,遍历记录,将其值复制到结构体中,将所有记录添加到列表中,将对象列表编组为 JSON,并将其写入 HTTP 响应流。

在 MySQL 中更新您的第一个记录

考虑这样一个情景,你在数据库中创建了一个员工的记录,包括姓名、部门、地址等所有细节,一段时间后员工更换了部门。在这种情况下,我们必须在数据库中更新他们的部门,以便他们的详细信息在整个组织中保持同步,这可以通过SQL UPDATE语句实现,在这个示例中,我们将学习如何在 Go 中实现它。

操作步骤…

  1. 使用go get命令安装github.com/go-sql-driver/mysqlgithub.com/gorilla/mux包,如下所示:
$ go get github.com/go-sql-driver/mysql
$ go get github.com/gorilla/mux
  1. 创建update-record-mysql.go。然后我们连接到 MySQL 数据库,更新员工的姓名,然后将更新的记录数量写入数据库到 HTTP 响应流中,如下所示:
package main
import 
(
  "database/sql"
  "fmt"
  "log"
  "net/http" 
  "github.com/go-sql-driver/mysql"
  "github.com/gorilla/mux"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  DRIVER_NAME = "mysql"
  DATA_SOURCE_NAME = "root:password@/mydb"
)
var db *sql.DB
var connectionError error 
func init() 
{
  db, connectionError = sql.Open(DRIVER_NAME, DATA_SOURCE_NAME)
  if connectionError != nil 
  {
    log.Fatal("error connecting to database :: ", connectionError)
  }
}
type Employee struct 
{
  Id   int    `json:"uid"`
  Name string `json:"name"`
}
func updateRecord(w http.ResponseWriter, r *http.Request) 
{
  vars := mux.Vars(r)
  id := vars["id"]
  vals := r.URL.Query()
  name, ok := vals["name"]
  if ok 
  {
    log.Print("going to update record in database 
    for id :: ", id)
    stmt, err := db.Prepare("UPDATE employee SET name=? 
    where uid=?")
    if err != nil 
    {
      log.Print("error occurred while preparing query :: ", err)
      return
    }
    result, err := stmt.Exec(name[0], id)
    if err != nil 
    {
      log.Print("error occurred while executing query :: ", err)
      return
    }
    rowsAffected, err := result.RowsAffected()
    fmt.Fprintf(w, "Number of rows updated in database 
    are :: %d",rowsAffected)
  } 
  else 
  {
    fmt.Fprintf(w, "Error occurred while updating record in 
    database for id :: %s", id)
  }
}
func main() 
{
  router := mux.NewRouter()
  router.HandleFunc("/employee/update/{id}",
  updateRecord).Methods("PUT")
  defer db.Close()
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
  if err != nil 
  {
    log.Fatal("error starting http server :: ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run update-record-mysql.go

工作原理…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080

接下来,从命令行执行PUT请求以更新 ID 为1的员工记录将给出数据库中更新的记录数作为响应:

$ curl -X PUT http://localhost:8080/employee/update/1?name\=bar
Number of rows updated in database are :: 1

让我们看一下我们编写的程序:

  1. 我们定义了一个updateRecord处理程序,它以 URL 路径变量路径中要更新的 ID 和请求变量中的新名称作为输入,准备一个带有名称和 UID 占位符的update语句,该占位符将动态替换,执行该语句,获取执行结果中更新的行数,并将其写入 HTTP 响应流。

  2. 接下来,我们注册了一个updateRecord处理程序,用于处理gorilla/mux路由器中/employee/update/{id}的 URL 模式的每个PUT请求,并在从main()函数返回时使用defer db.Close()语句关闭数据库。

从 MySQL 中删除您的第一条记录

考虑这样一个情景,员工已经离开组织,您想要从数据库中撤销他们的详细信息。在这种情况下,我们可以使用SQL DELETE语句,我们将在本教程中介绍。

如何做到这一点…

  1. 使用go get命令安装github.com/go-sql-driver/mysqlgithub.com/gorilla/mux包,如下所示:
$ go get github.com/go-sql-driver/mysql
$ go get github.com/gorilla/mux
  1. 创建delete-record-mysql.go。然后我们连接到 MySQL 数据库,从数据库中删除员工的名称,并将从数据库中删除的记录数写入 HTTP 响应流,如下所示:
package main
import 
(
  "database/sql"
  "fmt"
  "log"
  "net/http"
  "github.com/go-sql-driver/mysql"
  "github.com/gorilla/mux"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  DRIVER_NAME = "mysql"
  DATA_SOURCE_NAME = "root:password@/mydb"
)
var db *sql.DB
var connectionError error
func init() 
{
  db, connectionError = sql.Open(DRIVER_NAME, DATA_SOURCE_NAME)
  if connectionError != nil 
  {
    log.Fatal("error connecting to database :: ", connectionError)
  }
}
func deleteRecord(w http.ResponseWriter, r *http.Request) 
{
  vals := r.URL.Query()
  name, ok := vals["name"]
  if ok 
  {
    log.Print("going to delete record in database for 
    name :: ", name[0])
    stmt, err := db.Prepare("DELETE from employee where name=?")
    if err != nil 
    {
      log.Print("error occurred while preparing query :: ", err)
      return
    }
    result, err := stmt.Exec(name[0])
    if err != nil 
    {
      log.Print("error occurred while executing query :: ", err)
      return
    }
    rowsAffected, err := result.RowsAffected()
    fmt.Fprintf(w, "Number of rows deleted in database are :: %d",
    rowsAffected)
  } 
  else 
  {
    fmt.Fprintf(w, "Error occurred while deleting record in 
    database for name %s", name[0])
  }
}
func main() 
{
  router := mux.NewRouter()
  router.HandleFunc("/employee/delete",
  deleteRecord).Methods("DELETE")
  defer db.Close()
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
  if err != nil 
  {
    log.Fatal("error starting http server :: ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run delete-record-mysql.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080

接下来,从命令行执行DELETE请求以删除名称为bar的员工将给出从数据库中删除的记录数:

$ curl -X DELETE http://localhost:8080/employee/delete?name\=bar
Number of rows deleted in database are :: 1

让我们看一下我们编写的程序:

  1. 我们定义了一个deleteRecord处理程序,它以请求变量中要从数据库中删除的名称作为输入,准备一个带有名称占位符的DELETE语句,该占位符将动态替换,执行该语句,获取执行结果中删除的行数,并将其写入 HTTP 响应流。

  2. 接下来,我们注册了一个deleteRecord处理程序,用于处理gorilla/mux路由器中/employee/delete的 URL 模式的每个DELETE请求,并在从main()函数返回时使用defer db.Close()语句关闭数据库。

集成 MongoDB 和 Go

每当您想要在 MongoDB 数据库中持久保存数据时,您必须采取的第一步是在数据库和您的 Web 应用程序之间建立连接,在本教程中,我们将使用 Go 中最著名和常用的 MongoDB 驱动程序之一gopkg.in/mgo.v2

准备就绪…

通过执行以下命令验证MongoDB是否安装并在本地端口27017上运行:

$ mongo

这应该返回以下响应:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

如何做到这一点…

  1. 使用go get命令安装gopkg.in/mgo.v包,如下所示:
$ go get gopkg.in/mgo.v
  1. 创建connect-mongodb.go。然后我们连接到MongoDB数据库,从集群中获取所有数据库名称,并将它们写入 HTTP 响应流,如下所示:
package main
import 
(
  "fmt"
  "log"
  "net/http"
  "strings"
  mgo "gopkg.in/mgo.v2"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  MONGO_DB_URL = "127.0.0.1"
)
var session *mgo.Session
var connectionError error
func init() 
{
  session, connectionError = mgo.Dial(MONGO_DB_URL)
  if connectionError != nil 
  {
    log.Fatal("error connecting to database :: ", connectionError)
  }
  session.SetMode(mgo.Monotonic, true)
}
func getDbNames(w http.ResponseWriter, r *http.Request) 
{
  db, err := session.DatabaseNames()
  if err != nil 
  {
    log.Print("error getting database names :: ", err)
    return
  }
  fmt.Fprintf(w, "Databases names are :: %s", strings.Join
  (db, ", "))
}
func main() 
{
  http.HandleFunc("/", getDbNames)
  defer session.Close()
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, nil)
  if err != nil 
  {
    log.Fatal("error starting http server :: ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run connect-mongodb.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080

浏览到http://localhost:8080/将列出 MongoDB 集群中存在的所有数据库的名称,并显示如下屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们看一下我们编写的程序:

  1. 使用`import(“fmt” “log” “net/http” “strings” mgo

“gopkg.in/mgo.v2”),我们导入了gopkg.in/mgo.v2并使用mgo`作为包别名。

  1. 使用var session *mgo.Session,我们声明了私有的 MongoDBSession实例,它作为与数据库的通信会话。

  2. 使用var connectionError error,我们声明了一个私有的error对象。

  3. 接下来,我们定义了init()函数,在这里我们连接到 MongoDB,传递主机为127.0.0.1,这意味着 MongoDB 和应用程序都在同一台机器上的端口27017上运行,可选择将会话切换到单调行为,以便在同一会话中的顺序查询中读取的数据将是一致的,并且在会话中进行的修改将在随后的查询中被观察到。

如果你的 MongoDB 运行在除27017之外的端口上,那么你必须传递主机和端口,用冒号分隔,如:mgo.Dial("localhost:27018")

  1. 接下来,我们定义了一个getDbNames处理程序,它基本上从 MongoDB 集群中获取所有数据库名称,并将它们作为逗号分隔的字符串写入 HTTP 响应流。

在 MongoDB 中创建你的第一个文档

在这个示例中,我们将学习如何在数据库中创建一个 BSON 文档(JSON 样式文档的二进制编码序列化),使用 Go 的 MongoDB 驱动程序(gopkg.in/mgo.v2)。

如何做…

  1. 使用以下命令,安装gopkg.in/mgo.v2github.com/gorilla/mux包:
$ go get gopkg.in/mgo.v2
$ go get github.com/gorilla/mux
  1. 创建create-record-mongodb.go。然后我们连接到 MongoDB 数据库,创建一个包含两个字段(ID 和姓名)的员工文档,并将最后创建的文档 ID 写入 HTTP 响应流,如下所示:
package main
import 
(
  "fmt"
  "log"
  "net/http"
  "strconv"
  "github.com/gorilla/mux"
  mgo "gopkg.in/mgo.v2"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  MONGO_DB_URL = "127.0.0.1"
)
var session *mgo.Session
var connectionError error
type Employee struct 
{
  Id int `json:"uid"`
  Name string `json:"name"`
}
func init() 
{
  session, connectionError = mgo.Dial(MONGO_DB_URL)
  if connectionError != nil 
  {
    log.Fatal("error connecting to database :: ", connectionError)
  }
  session.SetMode(mgo.Monotonic, true)
}
func createDocument(w http.ResponseWriter, r *http.Request) 
{
  vals := r.URL.Query()
  name, nameOk := vals["name"]
  id, idOk := vals["id"]
  if nameOk && idOk 
  {
    employeeId, err := strconv.Atoi(id[0])
    if err != nil 
    {
      log.Print("error converting string id to int :: ", err)
      return
    }
    log.Print("going to insert document in database for name 
    :: ", name[0])
    collection := session.DB("mydb").C("employee")
    err = collection.Insert(&Employee{employeeId, name[0]})
    if err != nil 
    {
      log.Print("error occurred while inserting document in 
      database :: ", err)
      return
    }
    fmt.Fprintf(w, "Last created document id is :: %s", id[0])
  } 
  else 
  {
    fmt.Fprintf(w, "Error occurred while creating document in
    database for name :: %s", name[0])
  }
}
func main() 
{
  router := mux.NewRouter()
  router.HandleFunc("/employee/create",
  createDocument).Methods("POST")
  defer session.Close()
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
  if err != nil 
  {
    log.Fatal("error starting http server :: ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run create-record-mongodb.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080

接下来,执行以下命令行中的POST请求来创建一个员工文档将会给你在 MongoDB 中创建的文档的 ID:

$ curl -X POST http://localhost:8080/employee/create?name=foo\&id=1
Last created document id is :: 1

让我们来看一下我们编写的程序:

  1. 使用import ("fmt" "log" "net/http" "strconv" "github.com/gorilla/mux" mgo "gopkg.in/mgo.v2"),我们导入了github.com/gorilla/mux来创建一个 Gorilla Mux 路由器,以及gopkg.in/mgo.v2,包别名为mgo,它将作为 MongoDB 驱动程序。

  2. 接下来,我们定义了一个createDocument处理程序,它从 HTTP 请求中获取员工的姓名和 ID。因为请求变量的类型是string,我们将string类型的变量 ID 转换为int类型。然后,我们从 MongoDB 获取员工集合,并调用collection.Insert处理程序将Employee结构类型的实例保存到数据库中。

从 MongoDB 中读取文档

在上一个示例中,我们在 MongoDB 中创建了一个 BSON 文档。现在,在这个示例中,我们将学习如何使用gopkg.in/mgo.v2/bson包来读取它,该包有助于查询 MongoDB 集合。

如何做…

  1. 使用以下命令,安装gopkg.in/mgo.v2gopkg.in/mgo.v2/bsongithub.com/gorilla/mux包:
$ go get gopkg.in/mgo.v2
$ go get gopkg.in/mgo.v2/bson
$ go get github.com/gorilla/mux
  1. 创建read-record-mongodb.go。然后我们连接到 MongoDB 数据库,读取员工集合中的所有文档,将列表编组为 JSON,并将其写入 HTTP 响应流,如下所示:
package main
import 
(
  "encoding/json"
  "log"
  "net/http"
  "github.com/gorilla/mux"
  mgo "gopkg.in/mgo.v2"
  "gopkg.in/mgo.v2/bson"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  MONGO_DB_URL = "127.0.0.1"
)
var session *mgo.Session
var connectionError error
func init() 
{
  session, connectionError = mgo.Dial(MONGO_DB_URL)
  if connectionError != nil 
  {
    log.Fatal("error connecting to database :: ", connectionError)
  }
  session.SetMode(mgo.Monotonic, true)
}
type Employee struct 
{
  Id int `json:"uid"`
  Name string `json:"name"`
}
func readDocuments(w http.ResponseWriter, r *http.Request) 
{
  log.Print("reading documents from database")
  var employees []Employee
  collection := session.DB("mydb").C("employee")
  err := collection.Find(bson.M{}).All(&employees)
  if err != nil 
  {
    log.Print("error occurred while reading documents from 
    database :: ", err)
    return
  }
  json.NewEncoder(w).Encode(employees)
}
func main() 
{
  router := mux.NewRouter()
  router.HandleFunc("/employees", readDocuments).Methods("GET")
  defer session.Close()
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
  if err != nil 
  {
    log.Fatal("error starting http server :: ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run read-record-mongodb.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080

接下来,浏览到http://localhost:8080/employees将会给你 MongoDB 员工集合中所有员工的列表:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们来看一下我们在程序中引入的更改:

  1. 使用import ("encoding/json" "log" "net/http" "github.com/gorilla/mux" mgo "gopkg.in/mgo.v2" "gopkg.in/mgo.v2/bson"),我们导入了额外的gopkg.in/mgo.v2/bson包,它是 Go 的 BSON 规范,以及encoding/json包,我们用它来将我们从 MongoDB 获取的对象列表编组为JSON

  2. 接下来,我们定义了一个readDocuments处理程序,在这里我们首先从 MongoDB 获取员工集合,查询其中的所有文档,遍历文档将其映射到Employee结构的数组中,最后将其编组为JSON

在 MongoDB 中更新您的第一个文档

一旦创建了一个 BSON 文档,我们可能需要更新其中的一些字段。在这种情况下,我们必须在 MongoDB 集合上执行update/upsert查询,这将在本教程中介绍。

如何做…

  1. 使用go get命令安装gopkg.in/mgo.v2gopkg.in/mgo.v2/bsongithub.com/gorilla/mux包,如下所示:
$ go get gopkg.in/mgo.v2
$ go get gopkg.in/mgo.v2/bson
$ go get github.com/gorilla/mux
  1. 创建update-record-mongodb.go。然后我们连接到 MongoDB 数据库,更新 ID 的员工的名称,并将在 HTTP 响应流中写入在 MongoDB 中更新的记录数量,如下所示:
package main
import 
(
  "fmt"
  "log"
  "net/http"
  "strconv"
  "github.com/gorilla/mux"
  mgo "gopkg.in/mgo.v2"
  "gopkg.in/mgo.v2/bson"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  MONGO_DB_URL = "127.0.0.1"
)
var session *mgo.Session
var connectionError error
type Employee struct 
{
  Id int `json:"uid"`
  Name string `json:"name"`
}
func init() 
{
  session, connectionError = mgo.Dial(MONGO_DB_URL)
  if connectionError != nil 
  {
    log.Fatal("error connecting to database :: ", 
    connectionError)
  }
  session.SetMode(mgo.Monotonic, true)
}
func updateDocument(w http.ResponseWriter, r *http.Request) 
{
  vars := mux.Vars(r)
  id := vars["id"]
  vals := r.URL.Query()
  name, ok := vals["name"]
  if ok 
  {
    employeeId, err := strconv.Atoi(id)
    if err != nil 
    {
      log.Print("error converting string id to int :: ", err)
      return
    }
    log.Print("going to update document in database 
    for id :: ", id)
    collection := session.DB("mydb").C("employee")
    var changeInfo *mgo.ChangeInfo
    changeInfo, err = collection.Upsert(bson.M{"id": employeeId},
    &Employee{employeeId, name[0]})
    if err != nil 
    {
      log.Print("error occurred while updating record in 
      database :: ", err)
      return
    }
    fmt.Fprintf(w, "Number of documents updated in database 
    are :: %d", changeInfo.Updated)
  } 
  else 
  {
    fmt.Fprintf(w, "Error occurred while updating document
    in database for id :: %s", id)
  }
}
func main() 
{
  router := mux.NewRouter()
  router.HandleFunc("/employee/update/{id}",
  updateDocument).Methods("PUT")
  defer session.Close()
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
  if err != nil 
  {
    log.Fatal("error starting http server :: ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run update-record-mongodb.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080

接下来,通过命令行执行PUT请求来更新员工文档,如下所示,将会给出在 MongoDB 中更新的文档数量:

$ curl -X PUT http://localhost:8080/employee/update/1\?name\=bar
Number of documents updated in database are :: 1

让我们来看一下我们写的程序:

  1. 我们定义了一个updateDocument处理程序,它从 URL 路径变量中获取要在 MongoDB 中更新的 ID 和作为 HTTP 请求变量的新名称。由于请求变量是字符串类型,我们将string类型的变量 ID 转换为int类型。然后,我们从 MongoDB 获取员工集合,并调用collection.Upsert处理程序,以插入(如果不存在)或更新具有新名称的员工文档的 ID。

  2. 接下来,我们注册了一个updateDocument处理程序,用于处理/employee/update/{id}的 URL 模式,对于每个使用gorilla/mux路由器的PUT请求,并在我们从main()函数返回时使用defer session.Close()语句关闭 MongoDB 会话。

从 MongoDB 中删除您的第一个文档

每当我们想要清理数据库或删除不再需要的文档时,我们可以使用 Go 的 MongoDB 驱动程序(gopkg.in/mgo.v2)轻松地删除它们,这将在本教程中介绍。

如何做…

  1. 使用go get命令安装gopkg.in/mgo.v2gopkg.in/mgo.v2/bsongithub.com/gorilla/mux包,如下所示:
$ go get gopkg.in/mgo.v2
$ go get gopkg.in/mgo.v2/bson
$ go get github.com/gorilla/mux
  1. 创建delete-record-mongodb.go。然后我们连接到 MongoDB,从数据库中获取要删除的员工的名称作为 HTTP 请求变量,获取命名集合,并按如下方式删除文档:
package main
import 
(
  "fmt"
  "log"
  "net/http"
  "github.com/gorilla/mux"
  mgo "gopkg.in/mgo.v2"
  "gopkg.in/mgo.v2/bson"
)
const 
(
  CONN_HOST = "localhost"
  CONN_PORT = "8080"
  MONGO_DB_URL = "127.0.0.1"
)
var session *mgo.Session
var connectionError error
type Employee struct 
{
  Id int `json:"uid"`
  Name string `json:"name"`
}
func init() 
{
  session, connectionError = mgo.Dial(MONGO_DB_URL)
  if connectionError != nil 
  {
    log.Fatal("error connecting to database :: ", 
    connectionError)
  }
  session.SetMode(mgo.Monotonic, true)
}
func deleteDocument(w http.ResponseWriter, r *http.Request) 
{
  vals := r.URL.Query()
  name, ok := vals["name"]
  if ok 
  {
    log.Print("going to delete document in database for 
    name :: ", name[0])
    collection := session.DB("mydb").C("employee")
    removeErr := collection.Remove(bson.M{"name": name[0]})
    if removeErr != nil 
    {
      log.Print("error removing document from 
      database :: ", removeErr)
      return
    }
    fmt.Fprintf(w, "Document with name %s is deleted from 
    database", name[0])
  } 
  else 
  {
    fmt.Fprintf(w, "Error occurred while deleting document 
    in database for name :: %s", name[0])
  }
}
func main() 
{
  router := mux.NewRouter()
  router.HandleFunc("/employee/delete",
  deleteDocument).Methods("DELETE")
  defer session.Close()
  err := http.ListenAndServe(CONN_HOST+":"+CONN_PORT, router)
  if err != nil 
  {
    log.Fatal("error starting http server :: ", err)
    return
  }
}
  1. 使用以下命令运行程序:
$ go run delete-record-mongodb.go

它是如何工作的…

一旦我们运行程序,HTTP 服务器将在本地监听端口8080

接下来,通过命令行执行DELETE请求来删除 BSON 文档,如下所示,将会给出从数据库中删除的文档的名称:

$ curl -X DELETE http://localhost:8080/employee/delete?name\=bar
Document with name bar is deleted from database

让我们来看一下我们写的程序:

  1. 我们定义了一个deleteDocument处理程序,它从 MongoDB 获取要删除的名称作为请求变量,从 MongoDB 获取员工集合,并调用collection.Remove处理程序来删除给定名称的文档。

  2. 然后,我们注册了一个deleteDocument处理程序,用于处理/employee/delete的 URL 模式,对于每个使用gorilla/mux路由器的DELETE请求,并在我们从main()函数返回时使用defer session.Close()语句关闭 MongoDB 会话。

第六章:使用 Micro 编写 Go 中的微服务-微服务工具包

在本章中,我们将涵盖以下内容:

  • 创建您的第一个协议缓冲

  • 启动微服务发现客户端

  • 创建您的第一个微服务

  • 创建您的第二个微服务

  • 创建您的微服务 API

  • 使用命令行界面和 Web UI 与微服务进行交互

介绍

随着组织现在转向 DevOps,微服务也开始变得流行起来。由于这些服务具有独立的性质,并且可以用任何语言开发,这使得组织能够专注于它们的开发。通过掌握本章涵盖的概念,我们将能够以相当简单的方式使用 Go Micro 编写微服务。

在本章中,我们将首先编写协议缓冲。然后我们将学习如何启动 Consul,这是一个微服务发现客户端,最终转向创建微服务并通过命令行和 Web 仪表板与它们进行交互。

创建您的第一个协议缓冲

协议缓冲是 Go 支持的一种灵活、高效和自动化的编码和序列化结构化数据的机制。在本教程中,我们将学习如何编写我们的第一个协议缓冲。

准备就绪…

  1. 验证是否通过执行以下命令安装了protoc
$ protoc --version
 libprotoc 3.3.2
  1. 通过以下方式安装protobuf
$ git clone https://github.com/google/protobuf
$ cd protobuf
$ ./autogen.sh
$ ./configure
$ make
$ make check
$ make install

如何做…

  1. proto目录中创建hello.proto并定义一个名为Sayservice接口,其中包含两种数据类型-RequestResponse,如下所示:
syntax = "proto3";
service Say 
{
  rpc Hello(Request) returns (Response) {}
}
message Request 
{
  string name = 1;
}
message Response 
{
  string msg = 1;
}
  1. 使用以下命令编译hello.proto
$ protoc --go_out=plugins=micro:. hello.proto

它是如何工作的…

一旦命令成功执行,hello.pb.go将在proto目录中创建,其外观如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们了解我们编写的.proto文件:

  • syntax = "proto3";:在这里,我们指定我们使用proto3语法,这使得编译器了解协议缓冲必须使用版本 3 进行编译。如果我们不明确指定语法,则编译器会假定我们使用proto2

  • service Say { rpc Hello(Request) returns (Response) {} }:在这里,我们定义了一个名为Say的 RPC 服务和一个接受Request并返回ResponseHello方法。

  • message Request { string name = 1; }:在这里,我们定义了具有name字段的Request数据类型。

  • message Response { string msg = 1; }:在这里,我们定义了具有msg字段的Response数据类型。

启动微服务发现客户端

在部署了多个服务的微服务架构中,服务发现客户端帮助应用程序找到它们依赖的服务,可以通过 DNS 或 HTTP 进行。当我们谈论服务发现客户端时,最常见和著名的之一是 HashiCorp 的Consul,我们将在本教程中启动它。

准备就绪…

通过执行以下命令验证是否安装了Consul

$ consul version
 Consul v0.8.5
 Protocol 2 spoken by default, understands 2 to 3 (agent will automatically use protocol >2 when speaking to compatible agents)

如何做…

通过执行以下命令以服务器模式启动consul agent

$ consul agent -dev

它是如何工作的…

一旦命令成功执行,Consul 代理将以服务器模式运行,给我们以下输出:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们还可以通过执行以下命令列出 Consul 集群的成员:

$ consul members

这将给我们以下结果:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

由于 Consul 可以在服务器模式或客户端模式下运行,至少需要一个服务器,为了保持最低限度的设置,我们已经以服务器模式启动了我们的代理,尽管这并不推荐,因为在故障情况下存在数据丢失的可能性。

此外,浏览到http://localhost:8500/ui/将显示 Consul Web UI,我们可以在其中查看所有服务和节点,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建您的第一个微服务

微服务只是作为唯一进程运行并通过明确定义的轻量级机制进行通信以服务于业务目标的代码片段,我们将在这个示例中使用https://github.com/micro/micro编写,尽管还有许多其他库可用,如https://github.com/go-kit/kithttps://github.com/grpc/grpc-go,它们具有相同的目的。

准备就绪…

  1. 通过执行以下命令启动consul agent
$ consul agent -dev
  1. 通过执行以下命令安装和运行micro
$ go get github.com/micro/micro
$ micro api
 2018/02/06 00:03:36 Registering RPC Handler at /rpc
 2018/02/06 00:03:36 Registering API Default Handler at /
 2018/02/06 00:03:36 Listening on [::]:8080
 2018/02/06 00:03:36 Listening on [::]:54814
 2018/02/06 00:03:36 Broker Listening on [::]:54815
 2018/02/06 00:03:36 Registering node: go.micro.api-a6a82a54-0aaf-11e8-8d64-685b35d52676

如何做…

  1. 通过执行命令$ mkdir services && cd services && touch first-greeting-service.goservices目录中创建first-greeting-service.go

  2. 将以下内容复制到first-greeting-service.go

package main
import 
(
  "log"
  "time"
  hello "../proto"
  "github.com/micro/go-micro"
)
type Say struct{}
func (s *Say) Hello(ctx context.Context, req *hello.Request, 
rsp *hello.Response) error 
{
  log.Print("Received Say.Hello request - first greeting service")
  rsp.Msg = "Hello " + req.Name
  return nil
}
func main() 
{
  service := micro.NewService
  (
    micro.Name("go.micro.service.greeter"),
    micro.RegisterTTL(time.Second*30),
    micro.RegisterInterval(time.Second*10),
  )
  service.Init()
  hello.RegisterSayHandler(service.Server(), new(Say))
  if err := service.Run(); err != nil 
  {
    log.Fatal("error starting service : ", err)
    return
  }
}

一切就绪后,目录结构应如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 转到services目录并使用以下命令运行程序:
$ go run first-greeting-service.go

它是如何工作的…

一旦我们运行程序,RPC 服务器将在本地监听端口8080

接下来,从命令行执行POST请求,如下所示:

$ curl -X POST -H 'Content-Type: application/json' -d '{"service": "go.micro.service.greeter", "method": "Say.Hello", "request": {"name": "Arpit Aggarwal"}}' http://localhost:8080/rpc

这将使我们从服务器获得 Hello,然后是名称作为响应,如下所示的屏幕截图:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

查看first-greeting-service.go的日志将向我们展示请求是由第一个问候服务提供的,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

让我们看一下我们编写的程序:

  • 使用import ("log" "time" hello "../proto" "github.com/micro/go-micro" "golang.org/x/net/context"),我们导入了"hello "../proto",一个包含协议缓冲区源代码和已编译协议缓冲区后缀.pb.go的目录。此外,我们导入了github.com/micro/go-micro包,其中包含编写微服务所需的所有库。

  • 接下来,我们定义了一个main()处理程序,在其中使用micro.NewService()创建一个名为go.micro.service.greeter的新服务,初始化它,注册处理程序,并最终启动它。

创建您的第二个微服务

在这个示例中,我们将使用go-micro创建另一个微服务,它是first-greeting-service.go的副本,除了在控制台上打印的日志消息之外,它演示了两个具有相同名称的服务的客户端负载平衡的概念。

如何做…

  1. 通过执行命令$ cd services && touch second-greeting-service.goservices目录中创建second-greeting-service.go

  2. 将以下内容复制到second-greeting-service.go

package main
import 
(
  "context"
  "log"
  "time"
  hello "../proto"
  "github.com/micro/go-micro"
)
type Say struct{}
func (s *Say) Hello(ctx context.Context, req *hello.Request, 
rsp *hello.Response) error 
{
  log.Print("Received Say.Hello request - second greeting
  service")
  rsp.Msg = "Hello " + req.Name
  return nil
}
func main() 
{
  service := micro.NewService
  (
    micro.Name("go.micro.service.greeter"),
    micro.RegisterTTL(time.Second*30),
    micro.RegisterInterval(time.Second*10),
  )
  service.Init()
  hello.RegisterSayHandler(service.Server(), new(Say))
  if err := service.Run(); err != nil 
  {
    log.Fatal("error starting service : ", err)
    return
  }
}

一切就绪后,目录结构应如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 转到services目录并使用以下命令运行程序:
$ go run second-greeting-service.go

它是如何工作的…

一旦我们运行程序,RPC 服务器将在本地监听端口8080

接下来,从命令行执行POST请求,如下所示:

$ curl -X POST -H 'Content-Type: application/json' -d '{"service": "go.micro.service.greeter", "method": "Say.Hello", "request": {"name": "Arpit Aggarwal"}}' http://localhost:8080/rpc

这将使我们从服务器获得 Hello,然后是名称作为响应,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

查看second-greeting-service.go的日志将向我们展示请求是由第二个问候服务提供的:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在,如果我们再次执行POST请求,它将在first-greeting-service.go控制台中打印日志,这是因为 Go Micro 提供的智能客户端负载平衡构建在发现之上的服务。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建您的 Micro API

到目前为止,我们已经通过名称显式调用了后端服务和访问它的方法。在这个示例中,我们将学习如何使用 Go Micro API 访问服务,该 API 实现了 API 网关模式,提供了微服务的单一入口点。使用 Go Micro API 的优势在于它通过 HTTP 提供服务,并使用 HTTP 处理程序动态路由到适当的后端服务。

准备就绪…

通过执行以下命令在单独的终端中启动 consul agentmicro APIfirst-greeting-service.gosecond-greeting-service.go

$ consul agent -dev
$ micro api
$ go run first-greeting-service.go
$ go run second-greeting-service.go

操作步骤…

  1. 通过执行命令 $ mkdir api && cd api && touch greeting-api.go 在 api 目录中创建 greeting-api.go

  2. 将以下内容复制到 greeting-api.go

package main
import 
(
  "context"
  "encoding/json"
  "log"
  "strings"
  hello "../proto"
  "github.com/micro/go-micro"
  api "github.com/micro/micro/api/proto"
)
type Say struct 
{
  Client hello.SayClient
}
func (s *Say) Hello(ctx context.Context, req *api.Request, 
rsp *api.Response) error 
{
  log.Print("Received Say.Hello request - Micro Greeter API")
  name, ok := req.Get["name"]
  if ok 
  {
    response, err := s.Client.Hello
    (
      ctx, &hello.Request
      {
        Name: strings.Join(name.Values, " "),
      }
    )
    if err != nil 
    {
      return err
    }
    message, _ := json.Marshal
    (
      map[string]string
      {
        "message": response.Msg,
      }
    )
    rsp.Body = string(message)
  }
  return nil
}
func main() 
{
  service := micro.NewService
  (
    micro.Name("go.micro.api.greeter"),
  )
  service.Init()
  service.Server().Handle
  (
    service.Server().NewHandler
    (
      &Say{Client: hello.NewSayClient("go.micro.service.
      greeter", service.Client())},
    ),
  )
  if err := service.Run(); err != nil 
  {
    log.Fatal("error starting micro api : ", err)
    return
  }
}

一切就绪后,目录结构应该如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 转到 api 目录并使用以下命令运行程序:
$ go run greeting-api.go

工作原理…

一旦我们运行程序,HTTP 服务器将在本地监听端口 8080

接下来,按照以下步骤浏览至 http://localhost:8080/greeter/say/hello?name=Arpit+Aggarwal

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

这将给出响应 Hello,后跟作为 HTTP 请求变量接收到的名称。此外,查看 second-greeting-service.go 的日志将显示请求是由第二个问候服务提供的,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在,如果我们再次执行 GET 请求,它将在 first-greeting-service.go 控制台中打印日志,这是因为 Go Micro 提供的发现功能上构建的服务的智能客户端负载平衡:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用命令行界面和 web UI 与微服务交互

到目前为止,我们已经使用命令行执行了 GET 和 POST HTTP 请求来访问服务。这也可以通过 Go Micro web 用户界面来实现。我们只需要启动 micro web,这将在本示例中介绍。

操作步骤…

  1. 使用以下命令安装 go get github.com/micro/micro 包:
$ go get github.com/micro/micro
  1. 使用以下命令运行 web UI:
$ micro web

工作原理…

一旦命令成功执行,浏览至 http://localhost:8082/registry 将列出所有已注册的服务,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 web UI 查询我们的 greeter 服务,请求为 {"name" : "Arpit Aggarwal"},将会得到响应 {"msg": "Hello Arpit Aggarwal"} 

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 CLI 命令查询相同的 greeter 服务,命令为 query go.micro.service.greeter Say.Hello {"name" : "Arpit Aggarwal"},将会得到响应 {"msg": "Hello Arpit Aggarwal"}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第七章:在 Go 中使用 WebSocket

在本章中,我们将涵盖以下示例:

  • 创建你的第一个 WebSocket 服务器

  • 创建你的第一个 WebSocket 客户端

  • 调试你的第一个本地 WebSocket 服务器

  • 调试你的第一个远程 WebSocket 服务器

  • 单元测试你的第一个 WebSocket 服务器

介绍

WebSocket 提供了服务器和客户端之间的双向、单一套接字、全双工连接,使实时通信比其他方式如长轮询和服务器发送事件更加高效。

使用 WebSocket,客户端和服务器可以独立通信,每个都能在初始握手后同时发送和接收信息,重复使用从客户端到服务器和服务器到客户端的相同连接,最终大大减少延迟和服务器负载,使 Web 应用程序能够以最有效的方式执行现代任务。WebSocket 协议得到大多数主流浏览器的支持,包括 Google Chrome、Microsoft Edge、Internet Explorer、Firefox、Safari 和 Opera。因此没有兼容性问题。

在本章中,我们将学习如何创建 WebSocket 服务器和客户端,编写单元测试并调试运行在本地或远程的服务器。

创建你的第一个 WebSocket 服务器

在这个示例中,我们将学习如何编写一个 WebSocket 服务器,它是一个 TCP 应用程序,监听在端口8080上,允许连接的客户端彼此发送消息。

如何做…

  1. 使用go get命令安装github.com/gorilla/websocket包,如下所示:
$ go get github.com/gorilla/websocket
  1. 创建websocket-server.go,我们将在其中将 HTTP 请求升级为 WebSocket,从客户端读取 JSON 消息,并将其广播给所有连接的客户端,如下所示:
package main 
import 
(
  "log"
  "net/http"
  "github.com/gorilla/websocket"
)
var clients = make(map[*websocket.Conn]bool)
var broadcast = make(chan Message) 
var upgrader = websocket.Upgrader{}
type Message struct 
{
  Message string `json:"message"`
}
func HandleClients(w http.ResponseWriter, r *http.Request) 
{
  go broadcastMessagesToClients()
  websocket, err := upgrader.Upgrade(w, r, nil)
  if err != nil 
  {
    log.Fatal("error upgrading GET request to a 
    websocket :: ", err)
  }
  defer websocket.Close()
  clients[websocket] = true
  for 
  {
    var message Message
    err := websocket.ReadJSON(&message)
    if err != nil 
    {
      log.Printf("error occurred while reading 
      message : %v", err)
      delete(clients, websocket)
      break
    }
    broadcast <- message
  }
}
func main() 
{
  http.HandleFunc
  (
    "/", func(w http.ResponseWriter, 
    r *http.Request) 
    {
      http.ServeFile(w, r, "index.html")
    }
  )
  http.HandleFunc("/echo", HandleClients)
  err := http.ListenAndServe(":8080", nil)
  if err != nil 
  {
    log.Fatal("error starting http server :: ", err)
    return
  }
}
func broadcastMessagesToClients() 
{
  for 
  {
    message := <-broadcast
    for client := range clients 
    {
      err := client.WriteJSON(message)
      if err != nil 
      {
        log.Printf("error occurred while writing 
        message to client: %v", err)
        client.Close()
        delete(clients, client)
      }
    }
  }
}
  1. 使用以下命令运行程序:
$ go run websocket-server.go

工作原理…

一旦我们运行程序,WebSocket 服务器将在本地监听端口8080

让我们了解我们编写的程序:

  1. 我们使用了import ("log" "net/http" "github.com/gorilla/websocket"),这是一个预处理命令,告诉 Go 编译器包括所有来自lognet/httpgithub.com/gorilla/websocket包的文件。

  2. 使用var clients = make(map[*websocket.Conn]bool),我们创建了一个表示连接到 WebSocket 服务器的客户端的映射,KeyType 为 WebSocket 连接对象,ValueType 为布尔值。

  3. 使用var broadcast = make(chan Message),我们创建了一个通道,所有接收到的消息都会被写入其中。

  4. 接下来,我们定义了一个HandleClients处理程序,当收到HTTP GET请求时,将其升级为WebSocket,将客户端注册到套接字服务器,读取请求的 JSON 消息,并将其写入广播通道。

  5. 然后,我们定义了一个 Go 函数broadcastMessagesToClients,它抓取写入广播通道的消息,并将其发送给当前连接到 WebSocket 服务器的每个客户端。

创建你的第一个 WebSocket 客户端

在这个示例中,我们将创建一个简单的客户端来开始 WebSocket 握手过程。客户端将向 WebSocket 服务器发送一个相当标准的HTTP GET请求,服务器通过响应中的 Upgrade 头将其升级。

如何做…

  1. 创建index.html,我们将在页面加载时打开到非安全 WebSocket 服务器的连接,如下所示:
<html>
  <title>WebSocket Server</title>
  <input id="input" type="text" />
  <button onclick="send()">Send</button>
  <pre id="output"></pre>
  <script>
    var input = document.getElementById("input");
    var output = document.getElementById("output");
    var socket = new WebSocket("ws://" + window.
    location.host + "/echo");
    socket.onopen = function () 
    {
      output.innerHTML += "Status: Connected\n";
    };
    socket.onmessage = function (e) 
    {
      output.innerHTML += "Message from Server: " + 
      e.data + "\n";
    };
    function send() 
    {
      socket.send
      (
        JSON.stringify
        (
          {
            message: input.value
          }
        )
      );
      input.value = "";
    }
  </script>
</html>

一切就绪后,目录结构应该如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 使用以下命令运行程序:
$ go run websocket-server.go

工作原理…

一旦我们运行程序,WebSocket 服务器将在本地监听端口8080

浏览到http://localhost:8080将显示带有文本框和发送按钮的 WebSocket 客户端页面,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

调试你的第一个本地 WebSocket 服务器

调试 Web 应用程序是开发人员学习的最重要的技能之一,因为它有助于识别问题、隔离问题的来源,然后要么纠正问题,要么确定解决问题的方法。在这个示例中,我们将学习如何使用 GoLand IDE 调试在本地运行的 WebSocket 服务器。

准备…

本示例假定您已经安装并配置了 GoLand IDE 以在您的机器上运行 Go 应用程序。

如何做…

  1. 单击 GoLand IDE 中的 Open Project 以打开我们在以前的示例中编写的websocket-server.go,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 一旦项目打开,单击 Edit Configurations,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 通过单击+号显示如下截图所示的 Add New Configuration 来选择 Add New Configuration:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 选择 Go Build,将配置重命名为WebSocket Local Debug,将运行类型更改为目录,然后单击应用和确定,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 放置一些断点并单击调试按钮:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它是如何工作的…

一旦我们运行程序,WebSocket 服务器将在本地以调试模式启动,监听端口8080

浏览到http://localhost:8080将显示带有文本框和发送按钮的 WebSocket 客户端页面,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

输入文本并单击发送按钮,以查看程序执行停在我们在 GoLand IDE 中放置的断点处,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

调试您的第一个远程 WebSocket 服务器

在以前的示例中,我们学习了如何调试在本地运行的 WebSocket 服务器。在这个示例中,我们将学习如何在另一台或远程机器上调试它。

这些步骤与我们在以前的示例中所采取的步骤基本相同,只是在调试配置部分,我们将把本地主机更改为远程机器 IP 或 DNS,并启动 Delve 服务器,这是 Go 编程语言在远程机器上的调试器。

如何做…

  1. 通过单击 Edit Configurations…添加另一个配置,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 单击+号添加新配置,然后选择 Go Remote:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 将调试配置重命名为WebSocket Remote Debug,将主机更改为remote-machine-IPDNS,然后单击应用和确定,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 通过执行以下命令在目标或远程机器上运行无头 Delve 服务器:
dlv debug --headless --listen=:2345 --api-version=2

上述命令将启动一个监听端口2345的 API 服务器。

  1. 选择 WebSocket Remote Debug 配置,然后单击调试按钮:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它是如何工作的…

浏览到远程可用的 WebSocket 客户端页面,输入一些文本,然后单击发送按钮,以查看程序执行停在我们放置的断点处:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

单元测试您的第一个 WebSocket 服务器

单元测试或测试驱动开发有助于开发人员设计松散耦合的代码,重点放在代码的可重用性上。它还帮助我们意识到何时停止编码并快速进行更改。

在这个示例中,我们将学习如何为我们在以前的示例中已经编写的 WebSocket 服务器编写单元测试。

参见创建您的第一个 WebSocket 服务器示例。

如何做…

  1. 使用go get命令安装github.com/gorilla/websocketgithub.com/stretchr/testify/assert包,如下所示:
$ go get github.com/gorilla/websocket
$ go get github.com/stretchr/testify/assert
  1. 创建websocket-server_test.go,我们将在其中创建一个测试服务器,使用 Gorilla 客户端连接到它,并最终读取和编写消息以测试连接,如下所示:
package main
import 
(
  "net/http"
  "net/http/httptest"
  "strings"
  "testing"
  "github.com/gorilla/websocket"
  "github.com/stretchr/testify/assert"
)
func TestWebSocketServer(t *testing.T) 
{
  server := httptest.NewServer(http.HandlerFunc
  (HandleClients))
  defer server.Close()
  u := "ws" + strings.TrimPrefix(server.URL, "http")
  socket, _, err := websocket.DefaultDialer.Dial(u, nil)
  if err != nil 
  {
    t.Fatalf("%v", err)
  }
  defer socket.Close()
  m := Message{Message: "hello"}
  if err := socket.WriteJSON(&m); err != nil 
  {
    t.Fatalf("%v", err)
  }
  var message Message
  err = socket.ReadJSON(&message)
  if err != nil 
  {
    t.Fatalf("%v", err)
  }
  assert.Equal(t, "hello", message.Message, "they 
  should be equal")
}

工作原理…

从命令行执行go test如下:

$ go test websocket-server_test.go websocket-server.go
ok  command-line-arguments 0.048s

它将给我们响应ok,这意味着测试已成功编译和执行。

让我们看看当 Go 测试失败时会是什么样子。将assert语句中的预期输出更改为其他内容。在以下示例中,hello已更改为hi

...
assert.Equal(t, "hi", message.Message, "they should be equal")
...

通过运行go test命令再次执行测试:

$ go test websocket-server_test.go websocket-server.go

它将给我们失败的响应,以及如下截图所示的错误跟踪:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

第八章:使用 Go Web 应用程序框架-Beego

在本章中,我们将涵盖以下内容:

  • 使用 Beego 创建你的第一个项目

  • 创建你的第一个控制器和路由器

  • 创建你的第一个视图

  • 创建你的第一个会话变量

  • 创建你的第一个过滤器

  • 在 Beego 中处理 HTTP 错误

  • 在 Beego 中实现缓存

  • 监视 Beego 应用程序

  • 在本地机器上部署 Beego 应用程序

  • 使用 Nginx 部署 Beego 应用程序

介绍

无论何时我们开发一个应用程序,Web 应用程序框架都是必不可少的,因为它通过消除编写大量重复代码的需要并提供模型、API 和其他元素等功能,显著加快和简化了我们的工作。使用应用程序框架,我们可以享受其架构模式的好处,并加速应用程序的开发。

一种流行的 Web 应用程序框架类型是模型-视图-控制器MVC),Go 语言有许多 MVC 框架可用,如 Revel、Utron 和 Beego。

在本章中,我们将学习 Beego,这是一个最受欢迎和常用的 Web MVC 框架之一。我们将从创建项目开始,然后转向创建控制器、视图和过滤器。我们还将看看如何实现缓存,监视和部署应用程序。

使用 Beego 创建你的第一个项目

开始一个项目的第一件事是设置其基本架构。在 Beego 中,可以使用一个叫做bee的工具轻松实现这一点,我们将在这个示例中介绍。

如何做…

  1. 使用go get命令安装github.com/beego/bee包,如下所示:
$ go get github.com/beego/bee
  1. 打开终端到你的$GOPATH/src目录,并使用bee new命令创建一个项目,如下所示:
$ cd $GOPATH/src
$ bee new my-first-beego-project

一旦命令成功执行,它将创建一个新的 Beego 项目,并在控制台上的创建步骤将如下屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  1. 转到新创建的项目路径,输入bee run编译和运行项目,如下所示:
$ cd $GOPATH/src/my-first-beego-project
$ bee run

一旦命令成功执行,bee将构建项目并启动应用程序,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它是如何工作的…

一旦命令成功执行,Web 应用程序将在默认的 Beego 端口8080上运行,并浏览http://localhost:8080/将呈现应用程序的欢迎页面,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建你的第一个控制器和路由器

Web 应用程序的一个主要组件是控制器,它充当视图和模型之间的协调者,并处理用户的请求,这可能是按钮点击、菜单选择或 HTTP GETPOST请求。在这个示例中,我们将学习如何在 Beego 中创建一个控制器。

如何做…

  1. 转到$GOPATH/src/my-first-beego-project/controllers并创建firstcontroller.go,如下所示:
package controllers
import "github.com/astaxie/beego"
type FirstController struct 
{
  beego.Controller
}
type Employee struct 
{
  Id int `json:"id"`
  FirstName string `json:"firstName"`
  LastName string `json:"lastName"`
}
type Employees []Employee
var employees []Employee
func init() 
{
  employees = Employees
  {
    Employee{Id: 1, FirstName: "Foo", LastName: "Bar"},
    Employee{Id: 2, FirstName: "Baz", LastName: "Qux"},
  }
}
func (this *FirstController) GetEmployees() 
{
  this.Ctx.ResponseWriter.WriteHeader(200)
  this.Data["json"] = employees
  this.ServeJSON()
}
  1. 转到$GOPATH/src/my-first-beego-project/routers并编辑router.go以添加GET映射/employees,由FirstController中定义的GetEmployees处理程序处理,如下所示:
package routers
import 
(
  "my-first-beego-project/controllers"
  "github.com/astaxie/beego"
)
func init() 
{
  beego.Router("/", &controllers.MainController{})
  beego.Router("/employees", &controllers.FirstController{},
  "get:GetEmployees")
}
  1. 使用以下命令运行项目:
$ bee run

它是如何工作的…

一旦命令成功执行,Web 应用程序将在默认的 Beego 端口8080上运行。

接下来,从命令行执行GET请求将给你列出所有员工的列表:

$ curl -X GET http://localhost:8080/employees
[
 {
 "id": 1,
 "firstName": "Foo",
 "lastName": "Bar"
 },
 {
 "id": 2,
 "firstName": "Baz",
 "lastName": "Qux"
 }
]

让我们理解我们编写的程序:

  • 导入“github.com/astaxie/beego”:在这里,我们导入了 Beego。

  • type FirstController struct { beego.Controller }:在这里,我们定义了FirstController结构类型,它包含了一个匿名的beego.Controller类型的结构字段,因此FirstController自动获取了beego.Controller的所有方法。

  • func (this *FirstController) GetEmployees() { this.Ctx.ResponseWriter.WriteHeader(200) this.Data["json"] = employees this.ServeJSON() }:在这里,我们定义了GetEmployees处理程序,它将为 URL 模式/employees的每个GET请求执行。

在 Go 中,以大写字母开头的函数或处理程序是导出函数,这意味着它们是公共的,并且可以在程序外部使用。这就是我们在程序中定义所有函数时都使用大写字母而不是驼峰命名法的原因。

创建你的第一个视图

视图是模型的可视表示。它通过模型访问数据,并指定数据应该如何呈现。当模型发生变化时,它保持其呈现的一致性,这可以通过推模型或拉模型来实现。在推模型中,视图向模型注册自己以获取更改通知,而在拉模型中,视图负责在需要检索最新数据时调用模型。在本示例中,我们将学习如何创建我们的第一个视图来呈现员工列表。

如何做…

  1. 移动到$GOPATH/src/my-first-beego-project/views并创建dashboard.tpl,并复制以下内容:
<!DOCTYPE html>
<html>
  <body>
    <table border= "1" style="width:100%;">
      {{range .employees}}
      <tr>
        <td>{{.Id}}</td>
        <td>{{.FirstName}}</td>
        <td>{{.LastName}}</td>
      </tr>
      {{end}}
    </table>
  </body>
</html>
  1. 移动到$GOPATH/src/my-first-beego-project/controllers并编辑firstcontroller.go,添加Dashboard处理程序,如下所示:
package controllers
import "github.com/astaxie/beego"
type FirstController struct 
{
  beego.Controller
}
type Employee struct 
{
  Id int `json:"id"`
  FirstName string `json:"firstName"`
  LastName string `json:"lastName"`
}
type Employees []Employee
var employees []Employee
func init() 
{
  employees = Employees
  {
    Employee{Id: 1, FirstName: "Foo", LastName: "Bar"},
    Employee{Id: 2, FirstName: "Baz", LastName: "Qux"},
  }
}
...
func (this *FirstController) Dashbaord() 
{
  this.Data["employees"] = employees
  this.TplName = "dashboard.tpl"
}
  1. 移动到$GOPATH/src/my-first-beego-project/routers并编辑router.go,添加GET映射/dashboard,由FirstController中定义的Dashboard处理程序处理,如下所示:
package routers
import 
(
  "my-first-beego-project/controllers"
  "github.com/astaxie/beego"
)
func init() 
{
  beego.Router("/", &controllers.MainController{})
  beego.Router("/employees", &controllers.FirstController{},
  "get:GetEmployees")
  beego.Router("/dashboard", &controllers.FirstController{},
  "get:Dashbaord")
}

  1. 使用以下命令运行项目:
$ bee run

它是如何工作的…

一旦命令成功执行,Web 应用程序将在默认的 Beego 端口8080上运行。

浏览http://localhost:8080/dashboard将呈现员工仪表板,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

创建你的第一个会话变量

每当我们需要将用户数据从一个 HTTP 请求传递到另一个 HTTP 请求时,我们可以使用 HTTP 会话,我们将在本示例中介绍。

准备好…

此示例假定您已经在本地端口6379上安装并运行了Redis

如何做…

  1. 使用go get命令安装github.com/astaxie/beego/session/redis包,如下所示:
$ go get -u github.com/astaxie/beego/session/redis
  1. 移动到$GOPATH/src/my-first-beego-project/controllers并创建sessioncontroller.go,在这里我们将定义处理程序,确保只有经过身份验证的用户才能查看主页,如下所示:
package controllers 
import "github.com/astaxie/beego"
type SessionController struct 
{
  beego.Controller
}
func (this *SessionController) Home() 
{
  isAuthenticated := this.GetSession("authenticated")
  if isAuthenticated == nil || isAuthenticated == false 
  {
    this.Ctx.WriteString("You are unauthorized to 
    view the page.")
    return
  }
  this.Ctx.ResponseWriter.WriteHeader(200)
  this.Ctx.WriteString("Home Page")
}
func (this *SessionController) Login() 
{
  this.SetSession("authenticated", true)
  this.Ctx.ResponseWriter.WriteHeader(200)
  this.Ctx.WriteString("You have successfully logged in.")
}
func (this *SessionController) Logout() 
{
  this.SetSession("authenticated", false)
  this.Ctx.ResponseWriter.WriteHeader(200)
  this.Ctx.WriteString("You have successfully logged out.")
}
  1. 移动到$GOPATH/src/my-first-beego-project/routers并编辑router.go,添加GET映射/home/login/logout,分别由FirstController中定义的HomeLoginLogout处理程序处理,如下所示:
package routers
import 
(
  "my-first-beego-project/controllers"
  "github.com/astaxie/beego"
)
func init() 
{
  beego.Router("/", &controllers.MainController{})
  beego.Router("/employees", &controllers.FirstController{},
  "get:GetEmployees")
  beego.Router("/dashboard", &controllers.FirstController{}, 
  "get:Dashbaord")
  beego.Router("/home", &controllers.SessionController{},
  "get:Home")
  beego.Router("/login", &controllers.SessionController{}, 
  "get:Login")
  beego.Router("/logout", &controllers.SessionController{}, 
  "get:Logout")
}
  1. 移动到$GOPATH/src/my-first-beego-project并编辑main.go,导入github.com/astaxie/beego/session/redis,如下所示:
package main
import 
(
  _ "my-first-beego-project/routers"
  "github.com/astaxie/beego"
  _ "github.com/astaxie/beego/session/redis"
)
func main() 
{
  beego.BConfig.WebConfig.DirectoryIndex = true
  beego.BConfig.WebConfig.StaticDir["/swagger"] = "swagger"
  beego.Run()
}
  1. $GOPATH/src/my-first-beego-project/conf/app.conf中打开session的使用,如下所示:
SessionOn = true
SessionProvider = "redis"
SessionProviderConfig = "127.0.0.1:6379"
  1. 使用以下命令运行程序:
$ bee run 

它是如何工作的…

一旦命令成功执行,Web 应用程序将在默认的 Beego 端口8080上运行。

接下来,我们将执行一些命令来看会话是如何工作的。首先,我们将通过执行以下命令访问/home

$ curl -X GET http://localhost:8080/home 

这将导致我们从服务器收到未经授权的访问消息:

You are unauthorized to view the page.

显然,我们无法访问它,因为我们必须首先登录到应用程序,这将创建一个beegosessionID。现在让我们通过执行以下命令登录到应用程序:

$ curl -X GET -i http://localhost:8080/login

这将导致服务器返回以下响应:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

现在我们将使用作为/login请求的一部分创建的 cookiebeegosessionID来访问/home,如下所示:

$ curl --cookie "beegosessionID=6e1c6f60141811f1371d7ea044f1c194" http://localhost:8080/home Home Page

创建你的第一个过滤器

有时,我们可能希望在调用操作方法之前或之后执行逻辑。在这种情况下,我们使用过滤器,我们将在本示例中介绍。

过滤器基本上是封装常见功能或横切关注点的处理程序。我们只需定义它们一次,然后将它们应用于不同的控制器和操作方法。

操作步骤…

  1. 使用go get命令安装github.com/astaxie/beego/context包,如下所示:
$ go get github.com/astaxie/beego/context
  1. 移动到$GOPATH/src/my-first-beego-project/filters并创建firstfilter.go,在Controller之前运行,并记录 IP 地址和当前时间戳,如下所示:
package filters 
import 
(
  "fmt"
  "time"
  "github.com/astaxie/beego/context"
)
var LogManager = func(ctx *context.Context) 
{ 
  fmt.Println("IP :: " + ctx.Request.RemoteAddr + ", 
  Time :: " + time.Now().Format(time.RFC850))
}
  1. 移动到$GOPATH/src/my-first-beego-project/routers并编辑router.go以添加GET映射/*,将由LogManager过滤器处理,如下所示:
package routers 
import 
(
  "my-first-beego-project/controllers"
  "my-first-beego-project/filters"
  "github.com/astaxie/beego"
)
func init() 
{
  beego.Router("/", &controllers.MainController{})
  ...
  beego.InsertFilter("/*", beego.BeforeRouter, 
  filters.LogManager)
}
  1. 使用以下命令运行程序:
$ bee run

工作原理…

一旦命令成功执行,Web 应用程序将在默认的 Beego 端口8080上运行。

接下来,我们将执行一个请求,通过执行以下命令获取所有员工:

$ curl -X GET http://localhost:8080/employees
[
 {
 "id": 1,
 "firstName": "Foo",
 "lastName": "Bar"
 },
 {
 "id": 2,
 "firstName": "Baz",
 "lastName": "Qux"
 }
]

一旦命令成功执行,我们可以在控制台的应用程序日志中看到打印的 IP 和时间戳,如下所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用beego.InsertFilter("/*", beego.BeforeRouter, filters.LogManager),我们在应用程序中插入了一个过滤器,该过滤器在找到路由器之前执行 URL 模式/*,并由LogManager处理。类似于beego.BeforeRouter,还有四个其他位置可以放置过滤器:beego.BeforeStaticbeego.BeforeExecbeego.AfterExecbeego.FinishRouter

在 Beego 中处理 HTTP 错误

错误处理是 Web 应用程序设计中最重要的方面之一,因为它在两个方面有所帮助。首先,它以相对友好的方式让应用程序用户知道出了问题,他们应该联系技术支持部门或者应该通知技术支持部门的人员。其次,它允许程序员添加一些细节来帮助调试问题。在本示例中,我们将学习如何在 Beego 中实现错误处理。

操作步骤…

  1. 移动到$GOPATH/src/my-first-beego-project/controllers并创建errorcontroller.go,在其中我们将定义处理404500 HTTP 错误的处理程序,以及处理应用程序中任何通用错误的处理程序,如下所示:
package controllers
import "github.com/astaxie/beego"
type ErrorController struct 
{
  beego.Controller
}
func (c *ErrorController) Error404() 
{
  c.Data["content"] = "Page Not Found"
  c.TplName = "404.tpl"
}
func (c *ErrorController) Error500() 
{
  c.Data["content"] = "Internal Server Error"
  c.TplName = "500.tpl"
}
func (c *ErrorController) ErrorGeneric() 
{
  c.Data["content"] = "Some Error Occurred"
  c.TplName = "genericerror.tpl"
}
  1. 移动到$GOPATH/src/my-first-beego-project/controllers并编辑firstcontroller.go以添加GetEmployee处理程序,该处理程序将从 HTTP 请求参数中获取 ID,从静态员工数组中获取员工详细信息,并将其作为响应返回,或者如果请求的 ID 不存在,则抛出通用错误,如下所示:
package controllers
import "github.com/astaxie/beego"
type FirstController struct 
{
  beego.Controller
}
type Employee struct 
{
  Id int `json:"id"`
  FirstName string `json:"firstName"`
  LastName string `json:"lastName"`
}
type Employees []Employee
var employees []Employee
func init() 
{
  employees = Employees
  {
    Employee{Id: 1, FirstName: "Foo", LastName: "Bar"},
    Employee{Id: 2, FirstName: "Baz", LastName: "Qux"},
  }
}
...
func (this *FirstController) GetEmployee() 
{
  var id int
  this.Ctx.Input.Bind(&id, "id")
  var isEmployeeExist bool
  var emps []Employee
  for _, employee := range employees 
  {
    if employee.Id == id 
    {
      emps = append(emps, Employee{Id: employee.Id, 
      FirstName: employee.FirstName, LastName: 
      employee.LastName})
      isEmployeeExist = true
      break
    }
  }
  if !isEmployeeExist 
  {
    this.Abort("Generic")
  } 
  else 
  {
    this.Data["employees"] = emps
    this.TplName = "dashboard.tpl"
  }
}
  1. 移动到$GOPATH/src/my-first-beego-project/views并创建genericerror.tpl,内容如下:
<!DOCTYPE html>
<html>
  <body>
    {{.content}}
  </body>
</html>
  1. 使用以下命令运行程序:
$ bee run 

工作原理…

一旦命令成功执行,Web 应用程序将在默认的 Beego 端口8080上运行。

接下来,浏览http://localhost:8080/employee?id=2将会给出员工的详细信息,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当浏览http://localhost:8080/employee?id=4时:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

它将给出错误消息,如“发生了一些错误”。这是因为我们要求获取 ID 为4的员工的详细信息,而在静态员工数组中不存在,因此服务器抛出通用错误,由errorcontroller.go中定义的ErrorGeneric处理程序处理。

在 Beego 中实现缓存

在 Web 应用程序中缓存数据有时是必要的,以避免反复请求数据库或外部服务的静态数据。在本示例中,我们将学习如何在 Beego 应用程序中实现缓存。

Beego 支持四种缓存提供程序:fileMemcachememoryRedis。在本示例中,我们将使用框架默认的memory缓存提供程序。

操作步骤…

  1. 使用go get命令安装github.com/astaxie/beego/cache包,如下所示:
$ go get github.com/astaxie/beego/cache
  1. 移动到$GOPATH/src/my-first-beego-project/controllers并创建cachecontroller.go,在其中我们将定义GetFromCache处理程序,该处理程序将从缓存中获取键的值并将其写入 HTTP 响应,如下所示:
package controllers
import 
(
  "fmt"
  "time"
  "github.com/astaxie/beego"
  "github.com/astaxie/beego/cache"
)
type CacheController struct 
{
  beego.Controller
}
var beegoCache cache.Cache
var err error
func init() 
{
  beegoCache, err = cache.NewCache("memory",
  `{"interval":60}`)
  beegoCache.Put("foo", "bar", 100000*time.Second)
}
func (this *CacheController) GetFromCache() 
{
  foo := beegoCache.Get("foo")
  this.Ctx.WriteString("Hello " + fmt.Sprintf("%v", foo))
}
  1. 移动到$GOPATH/src/my-first-beego-project/routers并编辑router.go以添加GET映射/getFromCache,该映射将由CacheController中定义的GetFromCache处理程序处理,如下所示:
package routers
import 
(
  "my-first-beego-project/controllers"
  "my-first-beego-project/filters"
  "github.com/astaxie/beego"
)
func init() 
{
  beego.Router("/", &controllers.MainController{})
  ... 
  beego.Router("/getFromCache", &controllers.
  CacheController{}, "get:GetFromCache")
}
  1. 使用以下命令运行程序:
$ bee run

它是如何工作的…

一旦命令成功执行,Web 应用程序将在默认的 Beego 端口8080上运行。

在应用程序启动时,将使用名称为foo且值为bar的键添加到缓存中。接下来,浏览http://localhost:8080/getFromCache将从缓存中读取foo键值,将其附加到 Hello,并在浏览器上显示,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

监控 Beego 应用程序

一旦 Beego 应用程序启动并运行,我们可以轻松地通过其管理仪表板监视应用程序请求统计信息、性能、健康检查、任务和配置状态。我们将在本教程中学习如何做到这一点。

如何做到这一点…

  1. 通过在$GOPATH/src/my-first-beego-project/conf/app.conf中添加EnableAdmin = true来启用应用程序实时监视,如下所示:
appname = my-first-beego-project
...
EnableAdmin = true
..

可选地,通过在$GOPATH/src/my-first-beego-project/conf/app.conf中添加字段来更改其监听的端口:

AdminAddr = "localhost"
AdminPort = 8088
  1. 使用以下命令运行程序:
$ bee run

它是如何工作的…

一旦命令成功执行,Web 应用程序将在默认的 Beego 端口8080上运行,并且浏览http://localhost:8088/将呈现管理仪表板,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

浏览http://localhost:8088/qps将显示应用程序的请求统计信息,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

在本地机器上部署 Beego 应用程序

一旦应用程序开发结束,我们必须部署它以供最终用户使用,这可以在本地或远程进行。在本教程中,我们将学习如何在本地机器上部署我们的 Beego 应用程序。

如何做到这一点…

  1. 因为bee创建的应用程序默认处于开发模式,并且在公共服务器上运行应用程序时,始终以生产模式运行应用程序是最佳实践,因此我们必须在$GOPATH/src/my-first-beego-project/conf/app.conf中将RunMode更改为prod,如下所示:
beego.RunMode = "prod"
  1. 通过执行以下命令将静态文件、配置文件和模板作为 Beego 应用程序的字节码文件的一部分包含在一个单独的目录中:
$ mkdir $GOPATH/my-first-beego-app-deployment
$ cp my-first-beego-project $GOPATH/my-first-beego-app-deployment
$ cp -fr views $GOPATH/my-first-beego-app-deployment
$ cp -fr static $GOPATH/my-first-beego-app-deployment
$ cp -fr conf $GOPATH/my-first-beego-app-deployment
  1. 移动到$GOPATH/my-first-beego-app-deployment并使用nohup命令将应用程序作为后台进程运行,如下所示:
$ cd $GOPATH/my-first-beego-app-deployment
$ nohup ./my-first-beego-project &

它是如何工作的…

一旦命令成功执行,Web 应用程序将在默认的 Beego 端口8080上运行,浏览http://localhost:8080/将呈现应用程序的欢迎页面,如下面的屏幕截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

使用 Nginx 部署 Beego 应用程序

在上一个教程中,我们学习了如何在本地运行 Beego 应用程序。在本教程中,我们将使用Nginx部署相同的应用程序。

准备就绪…

这个教程假设您已经安装并在端口80上运行了Nginx。对我来说,它安装在/Users/ArpitAggarwal/nginx

如何做到这一点…

  1. 打开/Users/ArpitAggarwal/nginx/conf/nginx.conf中的 Nginx 配置文件,并将server下的location块替换为以下内容:
location / 
{
 # root html;
 # index index.html index.htm;
 proxy_pass http://localhost:8080/;
}
  1. 通过执行以下命令启动 Nginx:
$ cd /Users/ArpitAggarwal/nginx/sbin
$ ./nginx
  1. 通过执行以下命令运行 Beego 应用程序:
$ bee run

它是如何工作的…

一旦命令成功执行,浏览http://localhost:80/将呈现应用程序的欢迎页面,如下截图所示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值