Java 使用入门

本教程面向云端应用开发新手(例如 工程师和 Web 开发者),旨在帮助他们了解适用于 的 Google Cloud关键应用开发概念。

目标

费用

在本文档中,您将使用以下可计费组件: Google Cloud

本文档中的说明旨在将您的资源使用量控制在 的 Google Cloud's 始终免费 层级限制范围内。 您可使用 价格计算器 根据您的预计使用情况来估算费用。

新 Google Cloud 用户可能有资格申请免费试用

完成本文档中描述的任务后,您可以通过删除所创建的资源来避免继续计费。如需了解详情,请参阅清理

准备工作

  1. In the Google Cloud console, on the project selector page, select or create a Google Cloud project.

    Roles required to select or create a project

    • Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
    • Create a project: To create a project, you need the Project Creator role (roles/resourcemanager.projectCreator), which contains the resourcemanager.projects.create permission. Learn how to grant roles.

    Go to project selector

  2. Verify that billing is enabled for your Google Cloud project.

  3. 如需在原生模式下创建 Firestore 数据库,请完成以下步骤:
    1. 在 Google Cloud 控制台中,前往 Firestore 创建数据库 页面。

      前往 Firestore 创建数据库

    2. 选择一种 Cloud Firestore 模式 屏幕中,点击选择原生模式
    3. 选择 Firestore 数据库的 位置。此位置设置是您的 默认 Google Cloud 资源位置,用于您的 Google Cloud 项目 。 此位置将用于 Google Cloud 您的 Google Cloud 项目中需要设置位置的服务,特别是默认的 Cloud Storage 存储桶和您的 Cloud Run 应用。
    4. 点击创建数据库
  4. 启用 Cloud Run Admin API、Cloud Storage API、Cloud Logging API 和 Error Reporting API。

    启用 API 所需的角色

    如需启用 API,您需要拥有 serviceusage.services.enable 权限。如果您 创建了项目,则可能已通过 所有者角色 (roles/owner) 拥有此权限。否则,您可以通过 Service Usage Admin 角色 (roles/serviceusage.serviceUsageAdmin) 获得此权限。 了解如何授予角色

    启用 API

  5. 克隆示例代码库并在 Cloud Shell 中打开示例应用:
    前往 Cloud Shell

    利用 Cloud Shell,您可以直接在浏览器中通过命令行访问 Google Cloud 资源。

  6. 在克隆代码库的提示中,点击确认
  7. 在 Cloud Shell 中,配置 gcloud 工具以使用 新 Google Cloud 项目:

    # Configure gcloud for your project
    gcloud config set project PROJECT_ID

    PROJECT_ID 替换为您使用 Google Cloud 控制台创建的 Google Cloud 项目 ID。

    Google Cloud CLI 是您从命令行与 Google Cloud 资源进行交互的主要方式。在本教程中,您将使用 gcloud 工具来部署和监控 应用。

运行应用

  1. 如果您已使用 Cloud Shell 且将其配置为使用 Java 11,请将 shell 的 Java 替代组件和环境变量 JAVA_HOMEPATH 更新为使用 Java 8。
  2. 切换到 bookshelf/1-cloud-run 目录并运行应用:
    GOOGLE_CLOUD_PROJECT=PROJECT_ID mvn -Plocal clean jetty:run-exploded
    
    PROJECT_ID 替换为您创建的 Google Cloud 项目 ID。
  3. 在 Cloud Shell 中,点击网页预览 , 然后选择通过 8080 端口预览。此时浏览器会打开一个新窗口,显示出您运行中的应用。

将应用部署到 Cloud Run

Google Cloud 提供了 多种运行代码的选项。在此 示例中,您要使用 Cloud Run 将伸缩应用部署到 Google Cloud。Cloud Run 不需要您管理服务器,并且会自动扩容以应对 流量高峰。

请按照将应用部署到 Cloud Run中的说明操作。

部署成功后,会按如下格式将端点输出到 Cloud Run 中运行的应用:

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

之后即可通过此网址(下称 YOUR_CLOUD_RUN_URL)查看您的应用。只要在网络浏览器中输入此网址,即可查看应用。

Bookshelf 应用首页

使用 Firestore 保留数据

您不能在 Cloud Run 实例中存储信息,因为实例重启时信息会丢失,当新实例创建时该信息已不再存在。所以,您需要一个所有实例都能在其中读写的数据库。

Google Cloud 提供了 多种存储数据的选项。在此示例中,您将使用 Firestore 存储每本图书的数据。 Firestore 是一种全代管式无服务器 NoSQL 文档数据库,可用于存储和查询数据。Firestore 能够根据您的应用需求自动扩缩,并在不使用时缩减到零个实例。现在添加第一本图书。

  1. 在网络浏览器中,转到 YOUR_CLOUD_RUN_URL
  2. 如需为已部署的应用创建图书,请点击添加图书

    向 Bookshelf 应用添加一本图书
  3. 书名字段中,输入 Moby Dick
  4. 作者字段中,输入 Herman Melville
  5. 点击保存。现在您的 Bookshelf 应用有了一个条目。

    《Moby Dick》Bookshelf 应用条目
  6. 在 Google Cloud 控制台中,如需刷新 Firestore 页面,请点击 刷新 。 现在数据显示在 Firestore 中。Bookshelf 应用将每本图书 存储为 Firestore 文档 ,这些文档都存储在 Firestore 集合中,且每个文档都具有唯一 ID。 在本教程中,该集合称为 books。 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 是主要的 Blob 存储库 Google Cloud。您可以使用 Cloud Storage 来 托管要在中共享的应用资源 Google Cloud。如需使用 Cloud Storage,您需要创建一个 Cloud Storage 存储桶, 这是一个保存数据的基本容器。

  1. 在 Google Cloud 控制台中,前往 Cloud Storage 浏览器 页面。

    前往 Cloud Storage 浏览器页面

  2. 点击创建存储桶
  3. 创建存储桶对话框中,通过将 Google Cloud 项目 ID 附加到字符串_bucket来输入存储桶的名称,使名称看起来像 YOUR_PROJECT_ID_bucket。此名称须遵守 存储桶名称要求。其他所有字段可以保留默认值。
  4. 点击创建
  5. 创建存储分区后,必须将对象设为可公开访问,以供用户查看。如需将对象设为可公开访问,请参阅公开数据
  6. 点击修改图书,然后选择一张图片上传为图书的封面。例如,可以使用图中所示的这张属于公共领域的图片:
    《Moby Dick》图书封面
  7. 点击保存。您将被重定向至 首页,其中有您的 Bookshelf 应用的条目。
    《Moby Dick》Bookshelf 应用条目

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,请参阅 Cloud Storage 简介

使用 Google Cloud Observability 监控应用

您已经部署了应用,创建并修改了 books 集合。如需为用户监控这些事件,可以使用应用性能管理。

使用 Cloud Logging 监控日志

  1. 在 Google Cloud中,前往 Logs Explorer

    前往 Logs Explorer

    您可以在其中实时监控应用。如果您的应用出现问题,应该首先查看这里。

    Stackdriver 日志查看器
  2. 资源 下拉列表中,选择 Cloud Run Revision, bookshelf

使用 Error Reporting 监控错误

  1. 在 Google Cloud 控制台中,前往 Error Reporting 页面。
    前往 Error Reporting 页面
    Error Reporting 可突出显示应用中的错误和异常, 您还可以设置相关的提醒。
  2. 在浏览器中,前往应用中的 /errors 网址。
    YOUR_CLOUD_RUN_URL/errors

    此操作会生成一个新的测试异常并将其发送至 Google Cloud Observability。

  3. 在 Google Cloud 控制台中,返回到 Error Reporting 页面,很快就可以看到这个 新错误。点击自动重新加载 ,这样就无需手动刷新页面。

    Error Reporting 中的错误消息。

清理

为避免因本教程中使用的资源导致您的 Google Cloud 账号产生费用,请删除包含这些资源的项目,或者保留项目但删除各个资源。

删除项目

  1. 在 Google Cloud 控制台中,前往 管理资源 页面。

    转到“管理资源”

  2. 在项目列表中,选择要删除的项目,然后点击删除
  3. 在对话框中输入项目 ID,然后点击 关闭以删除项目。

后续步骤