將 Cloud SQL 與 Go 搭配使用

這部分 Go Bookshelf 教學課程說明範例應用程式如何將持續性資料儲存在 Google Cloud SQL 中。

本頁面是多頁教學課程的一部分。如要從頭開始並閱讀設定操作說明,請前往 Go Bookshelf 應用程式頁面。

建立 Cloud SQL 執行個體

部署作業完成後,應用程式會使用 App Engine 環境內建的 Cloud SQL Proxy 與您的 Cloud SQL 執行個體進行通訊。不過,如要在本機測試應用程式,您就必須在開發環境安裝並使用本機的 Cloud SQL Proxy。

進一步瞭解 Cloud SQL Proxy

如要在 Cloud SQL 執行個體上執行基本的管理工作,可以使用 MySQL 用戶端。

安裝 Cloud SQL Proxy

下載並安裝 Cloud SQL Proxy。在本機執行時,Cloud SQL Proxy 可用來連線至 Cloud SQL 執行個體。

Linux 64 位元

  1. 下載 Proxy:
    wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64 -O cloud_sql_proxy
    
  2. 將 Proxy 設定為可執行:
    chmod +x cloud_sql_proxy
    

Linux 32 位元

  1. 下載 Proxy:
    wget https://dl.google.com/cloudsql/cloud_sql_proxy.linux.386 -O cloud_sql_proxy
    
  2. 將 Proxy 設定為可執行:
    chmod +x cloud_sql_proxy
    

macOS 64 位元

  1. 下載 Proxy:
    curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.amd64
    
  2. 將 Proxy 設定為可執行:
    chmod +x cloud_sql_proxy
    

macOS 32 位元

  1. 下載 Proxy:
    curl -o cloud_sql_proxy https://dl.google.com/cloudsql/cloud_sql_proxy.darwin.386
    
  2. 將 Proxy 設定為可執行:
    chmod +x cloud_sql_proxy
    

Windows 64 位元

https://dl.google.com/cloudsql/cloud_sql_proxy_x64.exe 上按一下滑鼠右鍵,然後選取 [Save link as...] (另存連結為...) 下載 Proxy,並將 Proxy 重新命名為 cloud_sql_proxy.exe

Windows 32 位元

https://dl.google.com/cloudsql/cloud_sql_proxy_x86.exe 上按一下滑鼠右鍵,然後選取 [Save link as...] (另存連結為...) 下載 Proxy,並將 Proxy 重新命名為 cloud_sql_proxy.exe
如果您的作業系統不在上述說明內,您也可以從原始碼編譯 Proxy

建立 Cloud SQL 執行個體

  1. 建立 MySQL 第二代執行個體適用的 Cloud SQL。將執行個體命名為 library 或類似名稱。執行個體可能需要幾分鐘的時間才能準備就緒。執行個體準備就緒之後,應該會顯示在執行個體清單中。
  2. 現在請使用 Cloud SDK 執行下列指令,其中 [YOUR_INSTANCE_NAME] 代表您 Cloud SQL 執行個體的名稱。記下系統顯示的 connectionName 值,以供下個步驟使用。
    gcloud sql instances describe [YOUR_INSTANCE_NAME]

    connectionName 值的格式為 [PROJECT_NAME]:[REGION_NAME]:[INSTANCE_NAME]

初始化 Cloud SQL 執行個體

  1. 使用上一步中的 connectionName 啟動 Cloud SQL Proxy。

    Linux/macOS

    ./cloud_sql_proxy -instances="[YOUR_INSTANCE_CONNECTION_NAME]"=tcp:3306

    Windows

    cloud_sql_proxy.exe -instances="[YOUR_INSTANCE_CONNECTION_NAME]"=tcp:3306

    [YOUR_INSTANCE_CONNECTION_NAME] 改為您在上一個步驟中記下的 connectionName 值。

    這個步驟會將本機電腦連線至 Cloud SQL 執行個體,以利進行本機測試。本機測試應用程式期間請讓 Cloud SQL Proxy 全程保持運作。

設定

  1. 前往包含程式碼範例的目錄:

    Linux/macOS

    cd $GOPATH/src/github.com/GoogleCloudPlatform/golang-samples/getting-started/bookshelf
    

    Windows

    cd %GOPATH%\src\github.com\GoogleCloudPlatform\golang-samples\getting-started\bookshelf
    

  2. 開啟 config.go 進行編輯。

  3. 選用:為 DB = newMemoryDB() 加上註解。

  4. 將已加上 [START cloudsql] 標籤的區段程式碼部分取消註解。

  5. cloudSQLConfig 結構中輸入您的 Cloud SQL 使用者名稱、密碼與執行個體連線名稱。

    您可以在 Google Cloud Platform 主控台的 Cloud SQL 執行個體詳細資料中找到執行個體連線名稱。執行個體連線名稱格式為 project:region:cloudsql-instance

    例如,假設您的執行個體連線名稱為 myproject:us-central1:library,使用者名稱為 goapp,密碼為 secret123,則 config.go 檔案的 Cloud SQL 部分如下所示:

    DB, err = configureCloudSQL(cloudSQLConfig{
        Username: "goapp",
        Password: "secret123",
        Instance: "myproject:us-central1:library",
    })
    
  6. 儲存並關閉 config.go

  7. 開啟 app/app.yaml 進行編輯。

  8. 將開頭為 cloud_sql_instances 的一行取消註解。

  9. INSTANCE_CONNECTION_NAME 替換為您的執行個體連線字串。這個值應與 config.gocloudSQLConfig 結構中的值相符。

  10. 儲存並關閉 app/app.yaml

在本機電腦執行應用程式

  1. 啟動 Cloud SQL Proxy

  2. 執行應用程式以啟動本機網路伺服器:

    cd app
    go run app.go auth.go template.go
    
  3. 在網路瀏覽器中輸入此位址:

    http://localhost:8080

現在,您可以瀏覽應用程式的網頁,並新增、編輯與刪除書籍。

按下 Control+C 即可離開本機網路伺服器。

將應用程式部署至 App Engine 彈性環境

  1. app 目錄中,輸入下列指令以部署範例:

    gcloud app deploy
    
  2. 在網路瀏覽器中,輸入下列網址。將 [YOUR_PROJECT_ID] 替換為您的專案 ID:

    https://[YOUR_PROJECT_ID].appspot.com
    

如果您更新應用程式,可輸入與第一次部署應用程式時使用的相同指令部署更新版本。新部署會為您的應用程式建立新版本,並會將它升級為預設版本。 應用程式的較舊版本會保留下來,相關聯的 VM 執行個體也會保留下來。請注意,這些應用程式版本和 VM 執行個體全部都是計費資源。

您可以刪除應用程式的非預設版本來降低費用。

若要刪除應用程式版本:

  1. 前往 GCP 主控台的「App Engine Versions」(App Engine 版本) 頁面。

    前往版本頁面

  2. 找到您要刪除的非預設應用程式版本,然後點選旁邊的核取方塊。
  3. 按一下頁面頂部的 [刪除] 按鈕, 刪除應用程式版本。

如需有關清除計費資源的完整資訊,請參閱本教學課程最後一個步驟的清除一節。

應用程式結構

下圖顯示應用程式的元件,以及搭配使用這些元件的方式。

Bookshelf 應用程式結構

瞭解程式碼

這部分內容會逐步引導您瞭解應用程式程式碼,並說明其運作方式。

透過表單處理使用者提交內容

當使用者按一下 [Add Book] (新增書籍) 時,瀏覽器會顯示 /books/add,且 addFormHandler 中的 app/app.go 函式會轉譯一個表單,使用者可在這個表單中新增書籍:

r.Methods("GET").Path("/books/add").
	Handler(appHandler(addFormHandler))
// addFormHandler displays a form that captures details of a new book to add to
// the database.
func addFormHandler(w http.ResponseWriter, r *http.Request) *appError {
	return editTmpl.Execute(w, r, nil)
}

新增/編輯表單的圖片

表單包含「Title」 (標題)、「Author」 (作者)、「Date Published」 (出版日期)與「Description」 (說明),也包含其他一些內容,用於教學課程的其他部分。請注意嵌入 value 屬性的變數名稱:例如 {{.Title}}。這些名稱從 Go 標準文字/範本程式庫填入。

<form method="post" enctype="multipart/form-data" action="/books{{if .}}/{{.ID}}{{end}}">
  <div class="form-group">
    <label for="title">Title</label>
    <input class="form-control" name="title" id="title" value="{{.Title}}">
  </div>
  <div class="form-group">
    <label for="author">Author</label>
    <input class="form-control" name="author" id="author" value="{{.Author}}">
  </div>
  <div class="form-group">
    <label for="publishedDate">Date Published</label>
    <input class="form-control" name="publishedDate" id="publishedDate" value="{{.PublishedDate}}">
  </div>
  <div class="form-group">
    <label for="description">Description</label>
    <input class="form-control" name="description" id="description" value="{{.Description}}">
  </div>
  <div class="form-group">
    <label for="image">Cover Image</label>
    <input class="form-control" name="image" id="image" type="file">
  </div>
  <button class="btn btn-success">Save</button>
  <input type="hidden" name="imageURL" value="{{.ImageURL}}">
  <input type="hidden" name="createdBy" value="{{.CreatedBy}}">
  <input type="hidden" name="createdByID" value="{{.CreatedByID}}">
</form>

處理表單提交內容

當使用者填入表單並按一下 [Save] (儲存) 時,以下程式碼會處理表單的 HTTP POST /books 操作。程式碼會透過呼叫 bookshelf.DB.AddBook() 函式,將新書籍插入資料庫中。

r.Methods("POST").Path("/books").
	Handler(appHandler(createHandler))
// createHandler adds a book to the database.
func createHandler(w http.ResponseWriter, r *http.Request) *appError {
	book, err := bookFromForm(r)
	if err != nil {
		return appErrorf(err, "could not parse book from form: %v", err)
	}
	id, err := bookshelf.DB.AddBook(book)
	if err != nil {
		return appErrorf(err, "could not save book: %v", err)
	}
	go publishUpdate(id)
	http.Redirect(w, r, fmt.Sprintf("/books/%d", id), http.StatusFound)
	return nil
}

db_mysql.go 中定義的 AddBook 函式可執行 SQL INSERT 作業,將使用者提交的資料新增至 CloudSQL 資料庫:

const insertStatement = `
  INSERT INTO books (
    title, author, publishedDate, imageUrl, description, createdBy, createdById
  ) VALUES (?, ?, ?, ?, ?, ?, ?)`

// AddBook saves a given book, assigning it a new ID.
func (db *mysqlDB) AddBook(b *Book) (id int64, err error) {
	r, err := execAffectingOneRow(db.insert, b.Title, b.Author, b.PublishedDate,
		b.ImageURL, b.Description, b.CreatedBy, b.CreatedByID)
	if err != nil {
		return 0, err
	}

	lastInsertID, err := r.LastInsertId()
	if err != nil {
		return 0, fmt.Errorf("mysql: could not get last insert ID: %v", err)
	}
	return lastInsertID, nil
}
// execAffectingOneRow executes a given statement, expecting one row to be affected.
func execAffectingOneRow(stmt *sql.Stmt, args ...interface{}) (sql.Result, error) {
	r, err := stmt.Exec(args...)
	if err != nil {
		return r, fmt.Errorf("mysql: could not execute statement: %v", err)
	}
	rowsAffected, err := r.RowsAffected()
	if err != nil {
		return r, fmt.Errorf("mysql: could not get rows affected: %v", err)
	} else if rowsAffected != 1 {
		return r, fmt.Errorf("mysql: expected 1 row affected, got %d", rowsAffected)
	}
	return r, nil
}

應用程式啟動時,每個 SQL 陳述式都會準備好。例如,newMySQLDB 函式中的下列程式碼會準備好 SQL 陳述式來列出書籍,並會將準備好的陳述式儲存在 db 物件中:


// Prepared statements. The actual SQL queries are in the code near the
// relevant method (e.g. addBook).
if db.list, err = conn.Prepare(listStatement); err != nil {
	return nil, fmt.Errorf("mysql: prepare list: %v", err)
}

當使用者在提交書籍資訊之後對這些資訊進行編輯時,UpdateBook 方法會執行 SQL UPDATE 作業:

const updateStatement = `
  UPDATE books
  SET title=?, author=?, publishedDate=?, imageUrl=?, description=?,
      createdBy=?, createdById=?
  WHERE id = ?`

// UpdateBook updates the entry for a given book.
func (db *mysqlDB) UpdateBook(b *Book) error {
	if b.ID == 0 {
		return errors.New("mysql: book with unassigned ID passed into updateBook")
	}

	_, err := execAffectingOneRow(db.update, b.Title, b.Author, b.PublishedDate,
		b.ImageURL, b.Description, b.CreatedBy, b.CreatedByID, b.ID)
	return err
}

當使用者按一下 [Books] (書籍) 時,瀏覽器會顯示 /books 頁面,且 ListBooks 方法會顯示 Cloud SQL 資料庫中的所有書籍:

const listStatement = `SELECT * FROM books ORDER BY title`

// ListBooks returns a list of books, ordered by title.
func (db *mysqlDB) ListBooks() ([]*Book, error) {
	rows, err := db.list.Query()
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var books []*Book
	for rows.Next() {
		book, err := scanBook(rows)
		if err != nil {
			return nil, fmt.Errorf("mysql: could not read row: %v", err)
		}

		books = append(books, book)
	}

	return books, nil
}
本頁內容對您是否有任何幫助?請提供意見:

傳送您對下列選項的寶貴意見...

這個網頁