開始使用 Java

本教學課程適用於初次在雲端環境中建構應用程式的新手,例如想要在開始使用 Google Cloud 的過程中,學習重要的應用程式開發概念的工程師和網頁開發人員。

目標

如需建構應用程式的其他特定語言教學課程,請參閱下列指南:

費用

This tutorial uses the following billable components of Google Cloud:

The tutorial is designed to keep your resource usage within the limits of Google Cloud's Always Free tier. To generate a cost estimate based on your projected usage, use the pricing calculator. New Google Cloud users might be eligible for a free trial.

When you finish this tutorial, you can avoid continued billing by deleting the resources you created. For more information, see Cleaning up.

事前準備

  1. Sign in to your Google Account.

    If you don't already have one, sign up for a new account.

  2. In the Cloud Console, on the project selector page, select or create a Cloud project.

    Go to the project selector page

  3. Make sure that billing is enabled for your Google Cloud project. Learn how to confirm billing is enabled for your project.

  4. 如要以 Native Mode 建立 Firestore 資料庫,請完成下列步驟:
    1. 前往 Cloud Console 的「Firestore 檢視器」頁面。
      前往「Firestore 檢視器」
    2. 在「選取 Firestore 模式」畫面中,按一下 [選取 Native Mode]。
    3. 選取 Firestore 資料庫的位置。 這項位置設定會是 Google Cloud 專案的預設 Google Cloud 資源位置。 此位置會用於 Google Cloud 專案中需要位置設定的 Google Cloud 服務,具體來說,也就是您的預設 Cloud Storage 值區與 App Engine 應用程式。
    4. 按一下 [建立資料庫]
  5. Enable the App Engine Admin、Cloud Storage、Stackdriver Logging 和 Stackdriver Error Reporting APIs.

    Enable the APIs

  6. 在 Cloud Shell 中,開啟應用程式的原始碼。
    前往 Cloud Shell

    Cloud Shell 可讓您直接在瀏覽器中使用指令列工具存取 Google Cloud 資源。

  7. 如要將程式碼範例和變更下載到應用程式目錄中,請按一下 [繼續]
  8. 在 Cloud Shell 中,將 gcloud 工具設定為使用您新建的 Google Cloud 專案:

    # Configure gcloud for your project
    gcloud config set project PROJECT_ID
    

    PROJECT_ID 替換為您使用 Cloud Console 建立的 Google Cloud 專案 ID。

    gcloud 指令列工具是您透過指令列與 Google Cloud 資源互動的主要方式。在本教學課程中,您將使用 gcloud 工具部署及監控您的應用程式。

執行您的應用程式

  1. 執行應用程式:
    GOOGLE_CLOUD_PROJECT=PROJECT_ID mvn -Plocal clean jetty:run-exploded
    
    PROJECT_ID 替換成您先前建立的 Google Cloud 專案 ID。
  2. 在 Cloud Shell 中,按一下 [Web preview] (網頁預覽),然後選取 [Preview on port 8080] (透過以下通訊埠預覽:8080)。這會開啟新視窗,顯示執行中的應用程式。

將應用程式部署至 Cloud Run

Google Cloud 提供好幾種執行程式碼的方式。舉例來說,您可以利用 Cloud Run,將可擴充的應用程式部署至 Google Cloud。而 Cloud Run 能讓您專心編寫程式碼,不必費心力來管理伺服器。此外,在流量暴增時,Cloud Run 會自動調整資源配置來因應這種狀況。

  1. 利用 Jib 建立映像檔:
    mvn package jib:build
  2. 然後,部署映像檔:
    gcloud beta run deploy bookshelf --image gcr.io/PROJECT_ID/bookshelf \
    --platform managed --region us-central1

部署作業在成功執行之後,會依照下列格式,將端點輸出到於 Cloud Run 執行的應用程式中:

https://bookshelf-abcdefghij-uc.a.run.app

現在,您只要前往這個連結 (之後將稱為 YOUR_CODE_RUN_URL),就能看到自己的應用程式。請在網路瀏覽器中,輸入這個網址來檢視應用程式。

Bookshelf 應用程式首頁

使用 Firestore 保留您的資料

您無法將資訊儲存在 App Engine 執行個體上,因為這些資訊在執行個體重新啟動後將會遺失,且在建立新的執行個體時,這些資訊也不會存在。不過,您可以使用資料庫儲存所有執行個體讀取及寫入的資訊。

Google Cloud 提供數個儲存資料的選項。在此範例中,您會使用 Firestore 儲存每本書的資料。Firestore 是全代管、無伺服器的 NoSQL 文件資料庫,可讓您儲存及查詢資料。Firestore 可根據您應用程式的需求自動調整資源配置,並在您不使用應用程式的情況下,將資源調度降至零。立即新增您的第一本書。

  1. 透過瀏覽器前往以下網址,其中 PROJECT_ID 是您在本教學課程開始時建立的專案 ID。YOUR_CODE_RUN_URL
  2. 如要為已部署的應用程式建立書籍,請按一下 [Add book] (新增書籍)

    將書籍新增至 Bookshelf 應用程式
  3. 在「Title」(標題) 欄位中,輸入 Moby Dick
  4. 在「Author」欄位中,輸入 Herman Melville
  5. 按一下 [Save]。現在您已在 Bookshelf 應用程式中加入項目。

    Bookshelf 應用程式項目 Moby Dick
  6. 在 Cloud Console 中,如要重新整理 Firestore 頁面,請按一下 [Refresh] (重新整理)。資料即會出現在 Firestore 中。Bookshelf 應用程式會將每本書儲存為具有唯一識別碼的 Firestore 文件,並將所有這些文件儲存在 Firestore 集合。基於本教學課程的用途,此集合稱為「書籍」。Firestore 文件的範例。

Firestore 會使用 Firestore 用戶端程式庫儲存書籍資料。以下提供擷取 Firestore 文件的範例:

public class FirestoreDao implements BookDao {
  private CollectionReference booksCollection;

  public FirestoreDao() {
    Firestore firestore = FirestoreOptions.getDefaultInstance().getService();
    booksCollection = firestore.collection("books");
  }

  private Book documentToBook(DocumentSnapshot document) {
    Map<String, Object> data = document.getData();
    if (data == null) {
      System.out.println("No data in document " + document.getId());
      return null;
    }

    return new Book.Builder()
        .author((String) data.get(Book.AUTHOR))
        .description((String) data.get(Book.DESCRIPTION))
        .publishedDate((String) data.get(Book.PUBLISHED_DATE))
        .imageUrl((String) data.get(Book.IMAGE_URL))
        .createdBy((String) data.get(Book.CREATED_BY))
        .createdById((String) data.get(Book.CREATED_BY_ID))
        .title((String) data.get(Book.TITLE))
        .id(document.getId())
        .build();
  }

  @Override
  public String createBook(Book book) {
    String id = UUID.randomUUID().toString();
    DocumentReference document = booksCollection.document(id);
    Map<String, Object> data = Maps.newHashMap();

    data.put(Book.AUTHOR, book.getAuthor());
    data.put(Book.DESCRIPTION, book.getDescription());
    data.put(Book.PUBLISHED_DATE, book.getPublishedDate());
    data.put(Book.TITLE, book.getTitle());
    data.put(Book.IMAGE_URL, book.getImageUrl());
    data.put(Book.CREATED_BY, book.getCreatedBy());
    data.put(Book.CREATED_BY_ID, book.getCreatedById());
    try {
      document.set(data).get();
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }

    return id;
  }

  @Override
  public Book readBook(String bookId) {
    try {
      DocumentSnapshot document = booksCollection.document(bookId).get().get();

      return documentToBook(document);
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public void updateBook(Book book) {
    DocumentReference document = booksCollection.document(book.getId());
    Map<String, Object> data = Maps.newHashMap();

    data.put(Book.AUTHOR, book.getAuthor());
    data.put(Book.DESCRIPTION, book.getDescription());
    data.put(Book.PUBLISHED_DATE, book.getPublishedDate());
    data.put(Book.TITLE, book.getTitle());
    data.put(Book.IMAGE_URL, book.getImageUrl());
    data.put(Book.CREATED_BY, book.getCreatedBy());
    data.put(Book.CREATED_BY_ID, book.getCreatedById());
    try {
      document.set(data).get();
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
  }

  @Override
  public void deleteBook(String bookId) {
    try {
      booksCollection.document(bookId).delete().get();
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
  }

  private List<Book> documentsToBooks(List<QueryDocumentSnapshot> documents) {
    List<Book> resultBooks = new ArrayList<>();
    for (QueryDocumentSnapshot snapshot : documents) {
      resultBooks.add(documentToBook(snapshot));
    }
    return resultBooks;
  }

  @Override
  public Result<Book> listBooks(String startTitle) {
    Query booksQuery = booksCollection.orderBy("title").limit(10);
    if (startTitle != null) {
      booksQuery = booksQuery.startAfter(startTitle);
    }
    try {
      QuerySnapshot snapshot = booksQuery.get().get();
      List<Book> results = documentsToBooks(snapshot.getDocuments());
      String newCursor = null;
      if (results.size() > 0) {
        newCursor = results.get(results.size() - 1).getTitle();
      }
      return new Result<>(results, newCursor);
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
    return new Result<>(Lists.newArrayList(), null);
  }

  @Override
  public Result<Book> listBooksByUser(String userId, String startTitle) {
    Query booksQuery =
        booksCollection.orderBy("title").whereEqualTo(Book.CREATED_BY_ID, userId).limit(10);
    if (startTitle != null) {
      booksQuery = booksQuery.startAfter(startTitle);
    }
    try {
      QuerySnapshot snapshot = booksQuery.get().get();
      List<Book> results = documentsToBooks(snapshot.getDocuments());
      String newCursor = null;
      if (results.size() > 0) {
        newCursor = results.get(results.size() - 1).getTitle();
      }
      return new Result<>(results, newCursor);
    } catch (InterruptedException | ExecutionException e) {
      e.printStackTrace();
    }
    return new Result<>(Lists.newArrayList(), null);
  }
}

關於如何使用 Firestore 的詳情,請參閱將資料新增至 Firestore 的說明。

將上傳的檔案儲存在 Cloud Storage 中

現在您已新增了一本書,接下來請新增書籍的封面圖片。您無法將檔案儲存在執行個體中,資料庫也不適合用來儲存圖片檔。建議您可以使用 Cloud Storage。

Cloud Storage 是 Google Cloud 主要的 blob 存放區。您可以使用 Cloud Storage 託管要在 Google Cloud 中共用的應用程式資產。如要使用 Cloud Storage,您必須建立一個 Cloud Storage 值區,然後使用這個基本容器保存您的資料。

  1. 前往 Cloud Console 的「Cloud Storage 瀏覽器」頁面。

    前往「Cloud Storage 瀏覽器」頁面

  2. 按一下 [建立值區]
  3. 在「建立值區」對話方塊中輸入值區的名稱。請將您的 Google Cloud 專案 ID 加入 _bucket 字串,讓名稱看起來像這樣:YOUR_PROJECT_ID_bucket。這個名稱必須遵守值區命名規定,所有其他欄位則可以保留其預設值。
  4. 按一下 [建立]。
  5. 建立值區後,請點選 [Edit book],然後選取要上傳做為書籍封面的圖片。例如,您可以使用這個公版資源圖片:
    Moby Dick 書籍封面
  6. 按一下 [Save]。系統會將您重新導向至首頁,並在首頁顯示 Bookshelf 應用程式的輸入項目。
    Bookshelf 應用程式項目 Moby Dick

Bookshelf 應用程式會使用 Cloud Storage 用戶端程式庫,將已上傳檔案傳送至 Cloud Storage。

public class CloudStorageHelper {

  private final Logger logger = Logger.getLogger(CloudStorageHelper.class.getName());
  private static Storage storage = null;

  static {
    storage = StorageOptions.getDefaultInstance().getService();
  }

  /**
   * Uploads a file to Google Cloud Storage to the bucket specified in the BUCKET_NAME environment
   * variable, appending a timestamp to end of the uploaded filename.
   */
  public String uploadFile(FileItemStream fileStream, final String bucketName)
      throws IOException, ServletException {
    checkFileExtension(fileStream.getName());

    System.out.println("FileStream name: " + fileStream.getName() + "\nBucket name: " + bucketName);

    DateTimeFormatter dtf = DateTimeFormat.forPattern("-YYYY-MM-dd-HHmmssSSS");
    DateTime dt = DateTime.now(DateTimeZone.UTC);
    String dtString = dt.toString(dtf);
    final String fileName = fileStream.getName() + dtString;

    // the inputstream is closed by default, so we don't need to close it here
    @SuppressWarnings("deprecation")
    BlobInfo blobInfo =
        storage.create(
            BlobInfo.newBuilder(bucketName, fileName)
                // Modify access list to allow all users with link to read file
                .setAcl(new ArrayList<>(Arrays.asList(Acl.of(User.ofAllUsers(), Role.READER))))
                .build(),
            fileStream.openStream());
    logger.log(
        Level.INFO, "Uploaded file {0} as {1}", new Object[] {fileStream.getName(), fileName});
    // return the public download link
    return blobInfo.getMediaLink();
  }

  /** Checks that the file extension is supported. */
  private void checkFileExtension(String fileName) throws ServletException {
    if (fileName != null && !fileName.isEmpty() && fileName.contains(".")) {
      String[] allowedExt = {".jpg", ".jpeg", ".png", ".gif"};
      for (String ext : allowedExt) {
        if (fileName.endsWith(ext)) {
          return;
        }
      }
      throw new ServletException("file must be an image");
    }
  }
}

關於如何使用 Cloud Storage 的詳情,請參閱使用指南清單

使用 Stackdriver 監控您的應用程式

您已部署應用程式,並建立及修改了書籍資訊。如要為您的使用者監控這些事件,請使用 Stackdriver APM。

使用 Stackdriver Logging 監控記錄檔

  1. 在 Google Cloud 中,前往「Logs Viewer」(記錄檢視器)

    前往「Logs viewer」(記錄檢視器)

    現在,您可以即時監控自己的應用程式了。如果應用程式發生任何問題,這就是您要優先查看的地方之一。

    Stackdriver 記錄檢視器
  2. 在「Resource」(資源) 下拉式清單中,選取「Cloud Run Revision, bookshelf」(Cloud Run 修訂版本,Bookshelf)

使用 Stackdriver Error Reporting 監控錯誤

  1. 前往 Cloud Console 的「錯誤報告」頁面。
    前往「錯誤報告」頁面
    Stackdriver Error Reporting 會醒目顯示應用程式中的錯誤和例外狀況,並讓您設定與其相關的快訊。
  2. 透過瀏覽器前往應用程式的 /errors 網址。
    YOUR_CODE_RUN_URL/errors

    這項操作會產生一個新的測試例外狀況,並傳送給 Stackdriver。

  3. 返回 Cloud Console 的「錯誤報告」頁面,稍後就會顯示新的錯誤。按一下 [自動重新載入],這樣您就不需要手動重新整理頁面。

    Error Reporting 的錯誤訊息

清除所用資源

如要避免系統向您的 Google Cloud Platform 帳戶收取在本教學課程中使用資源的相關費用:

刪除專案

  1. In the Cloud Console, go to the Manage resources page.

    Go to the Manage resources page

  2. In the project list, select the project that you want to delete and then click Delete .
  3. In the dialog, type the project ID and then click Shut down to delete the project.

後續步驟