创建 Google 课堂插件

这是 Google 课堂插件演示系列中的第一个演示。

在本演示中,您将为开发 Web 应用并将其作为 Google 课堂插件发布奠定基础。后续演示步骤会扩展此应用。

在本演示过程中,您将完成以下操作:

  • 为您的 Web 应用创建一个新的 Google Cloud 项目。
  • 创建一个包含占位符登录按钮的骨架 Web 应用。
  • 为您的 Web 应用发布专用 Google Workspace Marketplace (GWM) 商品详情。

完成后,您可以安装插件并在 Google 课堂插件 iframe 中加载该插件。

前提条件

请从下面选择一种语言,以便查看相应前提条件:

Python

我们的 Python 示例使用 Flask 框架。您可以从“概览”页面下载所有演示的完整源代码。此特定演示的代码可以在 /flask/01-basic-app/ 目录中找到。

如有必要,请安装 Python 3.7 及更高版本并确保 pip 可用。

python -m ensurepip --upgrade

我们还建议您设置和激活新的 Python 虚拟环境。

python3 -m venv .classroom-addon-env
source .classroom-addon-env/bin/activate

下载的示例中的每个演示子目录都包含一个 requirements.txt。您可以使用 pip 快速安装所需的库。使用以下内容安装本演示所需的库。

cd flask/01-basic-app
pip install -r requirements.txt

Node.js

我们的 Node.js 示例使用 Express 框架。您可以从“概览”页面下载所有演示的完整源代码

如有必要,请安装 NodeJS v16.13 及更高版本

使用 npm 安装所需的节点模块。

npm install

Java

我们的 Java 示例使用 Spring Boot 框架。您可以从“概览”页下载所有演示的完整源代码

安装 Java 11+(如果尚未安装)。

Spring Boot 应用可以使用 Gradle 或 Maven 处理构建并管理依赖项。此示例包含 Maven 封装容器,该封装容器可确保构建成功,而无需您安装 Maven。

为了能够运行我们提供的示例,请在下载项目的目录中运行以下命令,以确保具备运行该项目的前提条件。

java --version
./mvnw --version

在 Windows 系统中,运行以下命令:

java -version
mvnw.cmd --version

设置 Google Cloud 项目

对 Classroom API 的访问和所需的身份验证方法由 Google Cloud 项目控制。以下说明将引导您完成创建和配置与插件搭配使用的新项目的最低步骤。

创建项目

访问项目创建页面,创建一个新的 Google Cloud 项目。您可以为新项目提供任何名称。点击创建

新项目需要一些时间才能创建完毕。完成后,请务必选择项目;您可以在屏幕顶部的项目选择器下拉菜单中选择该项目,或者在右上角的通知菜单中点击选择项目

在 Google Cloud 控制台中选择项目

将 GWM SDK 附加到 Google Cloud 项目

转到 API 库浏览器。搜索 Google Workspace Marketplace SDK。您应该会看到该 SDK 显示在结果列表中。

Google Workspace Marketplace SDK 卡片

选择 Google Workspace Marketplace SDK 卡片,然后点击启用

配置 GWM SDK

GWM 会提供用户和管理员安装插件的列表。配置 OAuth 同意屏幕以及 GWM SDK 的应用配置商品详情以继续操作。

当用户首次向您的应用授权时,系统会显示“OAuth 同意屏幕”。它会提示用户允许您的应用访问其个人信息和帐号信息,具体取决于您启用的范围

前往 OAuth 同意屏幕创建页面。请提供以下信息:

  • 用户类型设置为外部。点击创建
  • 在下一页中,填写所需的应用详细信息和联系信息。 在已获授权的网域下提供托管您的应用的所有网域。点击保存并继续
  • 添加您的 Web 应用所需的任何 OAuth 范围。请参阅 OAuth 配置指南,深入了解范围及其用途。

    您必须至少请求以下范围之一,Google 才能发送 login_hint 查询参数。有关此行为的详细说明,请参阅我们的 OAuth 配置指南

    • https://www.googleapis.com/auth/userinfo.email(已包含)
    • https://www.googleapis.com/auth/userinfo.profile(已包含)

    以下范围仅适用于 Google 课堂插件:

    • https://www.googleapis.com/auth/classroom.addons.teacher
    • https://www.googleapis.com/auth/classroom.addons.student

    此外,还要包括您的应用要求最终用户授予的任何其他 Google API 范围

    点击保存并继续

  • 测试用户页面上列出所有测试帐号的电子邮件地址。 点击保存并继续

确认您的设置正确,然后返回数字面板。

应用配置

前往 GWM SDK 的 App Configuration 页面。请提供以下信息:

  • 应用可见性设置为 Private。此设置适用于测试和开发目的,是这些演示的适当选择。仅当您已准备好让公众使用该插件时,才选择 Public

  • 如果您希望仅限网域管理员安装,请将安装设置设置为 Admin Only install

  • 应用集成下,选择课堂插件。系统会提示您提供安全附件设置 URI;这是您希望在用户打开插件时加载的网址。在本演示中,此属性应为 https://<your domain>/addon-discovery

  • 允许的附件 URI 前缀用于通过 courses.*.addOnAttachments.createcourses.*.addOnAttachments.patch 方法验证 AddOnAttachment 中设置的 URI。该验证是字面量字符串前缀匹配,目前不允许使用通配符。您可以暂时将这些字段留空。

  • 添加与上一步中的 OAuth 权限请求相同的 OAuth 范围

  • 填写开发者链接下适合贵组织的字段。

商品详情

前往 GWM SDK 的 Store Listing 页面。 请提供以下信息:

  • 应用详情下,添加语言或展开已列出的语言旁边的下拉菜单。提供应用名称和说明,这些信息会显示在插件的 GWM 商品详情页面上。点击完成进行保存。
  • 为您的插件选择类别
  • 图形资源下,为必填字段提供图片。这些属性以后可以更改,如果您在上一步中将“应用可见性”设置为不公开,那么这些变量也可以是占位符。
  • 支持链接下提供请求的网址。如果您在上一步中将“应用可见性”设置为不公开,那么这些网址可能是占位符。

点击发布,保存您的设置。如果您在上一步中将“应用可见性”设置为不公开,您的应用将立即可供安装。如果您将“应用可见性”设为公开,您的应用会发送给 GWM 团队进行审核,然后才可供安装。

安装插件

现在,您可以使用 GWM SDK 的商品详情页面顶部的链接安装您的插件。点击页面顶部的应用网址以查看列表,然后选择安装

构建基本 Web 应用

设置一个包含两个路由的框架 Web 应用。后续的演示步骤会扩展此应用,现在,您只需为插件 /addon-discovery 创建一个着陆页,并为我们的“公司网站”创建一个模拟索引页面 /

iframe 中的 Web 应用示例

实现以下两个端点:

  • /:显示欢迎辞和一个用于关闭当前标签页和插件 iframe 的按钮。
  • /addon-discovery:显示一条欢迎消息和两个按钮:一个用于关闭插件 iframe,另一个用于在新标签页中打开网站。

请注意,我们将添加用于创建和关闭窗口或 iframe 的按钮。这些演示了在下一个演示中安全地将用户弹出新标签页以进行授权的方法。

创建实用程序脚本

创建一个 static/scripts 目录。创建一个新文件 addon-utils.js。添加以下两个函数。

/**
 *   Opens a given destination route in a new window. This function uses
 *   window.open() so as to force window.opener to retain a reference to the
 *   iframe from which it was called.
 *   @param {string} destinationURL The endpoint to open, or "/" if none is
 *   provided.
 */
function openWebsiteInNewTab(destinationURL = '/') {
  window.open(destinationURL, '_blank');
}

/**
 *   Close the iframe by calling postMessage() in the host Classroom page. This
 *   function can be called directly when in a Classroom add-on iframe.
 *
 *   Alternatively, it can be used to close an add-on iframe in another window.
 *   For example, if an add-on iframe in Window 1 opens a link in a new Window 2
 *   using the openWebsiteInNewTab function above, you can call
 *   window.opener.closeAddonIframe() from Window 2 to close the iframe in Window
 *   1.
 */
function closeAddonIframe() {
  window.parent.postMessage({
    type: 'Classroom',
    action: 'closeIframe',
  }, '*');
};

创建路由

实现 /addon-discovery/ 端点。

Python

设置应用目录

在本示例中,将应用逻辑构建为 Python 模块。这是我们提供的示例中的 webapp 目录。

为服务器模块创建一个目录,例如 webapp。将 static 目录移至模块目录中。同时在模块目录中创建一个 template 目录;HTML 文件位于此处。

构建服务器模块*

在模块目录中创建 __init__.py 文件,并添加以下导入和声明。

from flask import Flask
import config

app = Flask(__name__)
app.config.from_object(config.Config)

# Load other module script files. This import statement refers to the
# 'routes.py' file described below.
from webapp import routes

然后创建一个文件来处理 Web 应用的路由。这是我们提供的示例中的 webapp/routes.py。在此文件中实现两个路由。

from webapp import app
import flask

@app.route("/")
def index():
    return flask.render_template("index.html",
                                message="You've reached the index page.")

@app.route("/classroom-addon")
def classroom_addon():
    return flask.render_template(
        "addon-discovery.html",
        message="You've reached the addon discovery page.")

请注意,我们的路由都会将 message 变量传递给其各自的 Jinja 模板。这有助于确定用户到达了哪个页面。

创建配置和启动文件

在应用的根目录中,创建 main.pyconfig.py 文件。在 config.py 中配置您的密钥。

import os

class Config(object):
    # Note: A secret key is included in the sample so that it works.
    # If you use this code in your application, replace this with a truly secret
    # key. See https://flask.palletsprojects.com/quickstart/#sessions.
    SECRET_KEY = os.environ.get(
        'SECRET_KEY') or "REPLACE ME - this value is here as a placeholder."

main.py 文件中,导入您的模块并启动 Flask 服务器。

from webapp import app

if __name__ == "__main__":
    # Run the application over HTTPs with a locally stored certificate and key.
    # Defaults to https://localhost:5000.
    app.run(
        host="localhost",
        ssl_context=("localhost.pem", "localhost-key.pem"),
        debug=True)

Node.js

路由在 app.js 文件中使用以下行注册。

const websiteRouter = require('./routes/index');
const addonRouter = require('./routes/classroom-addon');

app.use('/', websiteRouter);
app.use('/classroom-addon', addonRouter);

打开 /01-basic-app/routes/index.js 并查看代码。当最终用户访问公司网站时,就会到达此路由。路由使用 index Handlebars 模板呈现响应,并向模板传递包含 titlemessage 变量的数据对象。

router.get('/', function (req, res, next) {
  res.render('index', {
    title: 'Education Technology',
    message: 'Welcome to our website!'
  });
});

打开第二个路线 /01-basic-app/routes/classroom-addon.js 并查看代码。当最终用户访问的插件会到达此路由时。请注意,此路由使用 discovery Handlebars 模板以及 addon.hbs 布局,以与公司网站不同的方式呈现页面。

router.get('/', function (req, res, next) {
  res.render('discovery', {
    layout: 'addon.hbs',
    title: 'Education Technology Classroom add-on',
    message: `Welcome.`
});
});

Java

Java 代码示例使用模块来打包顺序演示步骤。由于这是第一个演示,因此代码位于 step_01_basic_app 模块下。您不需要使用模块来实现项目,相反,我们建议您在完成演示中的每个步骤时基于单个项目进行构建。

在此示例项目中创建一个控制器类 Controller.java 来定义端点。在此文件中,从 spring-boot-starter-web 依赖项导入 @GetMapping 注解。

import org.springframework.web.bind.annotation.GetMapping;

在类定义上方添加 Spring 框架控制器注解,以指示类的用途。

@org.springframework.stereotype.Controller
public class Controller {

然后,实现这两个路由以及另外一个进行错误处理的路由。

/** Returns the index page that will be displayed when the add-on opens in a
*   new tab.
*   @param model the Model interface to pass error information that's
*   displayed on the error page.
*   @return the index page template if successful, or the onError method to
*   handle and display the error message.
*/
@GetMapping(value = {"/"})
public String index(Model model) {
  try {
    return "index";
  } catch (Exception e) {
    return onError(e.getMessage(), model);
  }
}

/** Returns the add-on discovery page that will be displayed when the iframe
*   is first opened in Classroom.
*   @param model the Model interface to pass error information that's
*   displayed on the error page.
*   @return the addon-discovery page.
*/
@GetMapping(value = {"/addon-discovery"})
public String addon_discovery(Model model) {
  try {
    return "addon-discovery";
  } catch (Exception e) {
    return onError(e.getMessage(), model);
  }
}

/** Handles application errors.
*   @param errorMessage message to be displayed on the error page.
*   @param model the Model interface to pass error information to display on
*   the error page.
*   @return the error page.
*/
@GetMapping(value = {"/error"})
public String onError(String errorMessage, Model model) {
  model.addAttribute("error", errorMessage);
  return "error";
}

测试插件

启动服务器。然后,以您的教师测试用户身份登录 Google 课堂。找到课业标签页并创建新的作业。点击文本区域下方的插件按钮,然后选择您的插件。iframe 会打开,并且该插件会加载您在 GWM SDK 的应用配置页面中指定的附件设置 URI

恭喜!您可以继续执行下一步:让用户使用 Google SSO 登录