这是 Classroom 加购项演练系列中的第一个演练。
在此演练中,您将为开发 Web 应用并将其发布为 Google 课堂插件奠定基础。未来的演练步骤将扩展此应用。
在本演示中,您将完成以下操作:
- 为您的插件创建新的 Google Cloud 项目。
- 创建一个包含占位登录按钮的框架 Web 应用。
- 为您的插件发布 Google Workspace Marketplace 商店详情。
完成后,您可以安装插件并将其加载到 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
安装所需的 Node 模块。
npm install
Java
我们的 Java 示例使用 Spring Boot 框架。您可以从“概览”页面下载所有演练的完整源代码。
如果您的机器上尚未安装 Java 11 及更高版本,请进行安装。
Spring Boot 应用可以使用 Gradle 或 Maven 来处理 build 并管理依赖项。此示例包含 Maven 封装容器,可确保成功构建,而无需您安装 Maven 本身。
如需运行我们提供的示例,请在下载项目的目录中运行以下命令,确保您具备运行项目的前提条件。
java --version
./mvnw --version
或者,在 Windows 系统中,运行以下命令:
java -version
mvnw.cmd --version
设置 Google Cloud 项目
对 Classroom API 的访问权限和所需的身份验证方法由 Google Cloud 项目控制。以下说明将引导您完成创建和配置新项目的最少步骤,以便与您的插件搭配使用。
创建项目
访问项目创建页面,创建一个新的 Google Cloud 项目。您可以为新项目提供任何名称。点击创建。
完全创建新项目需要一些时间。完成后,请务必选择项目;您可以在屏幕顶部的项目选择器下拉菜单中选择项目,也可以点击右上角通知菜单中的选择项目。
将 Google Workspace Marketplace SDK 附加到 Google Cloud 项目
前往 API 库浏览器。搜索 Google Workspace Marketplace SDK
。您应该会在结果列表中看到该 SDK。
选择 Google Workspace Marketplace SDK 卡片,然后点击启用。
配置 Google Workspace Marketplace SDK
Google Workspace Marketplace 提供用户和管理员用来安装插件的商品详情。配置 Marketplace SDK 的应用配置和商店详情以及 OAuth 同意屏幕,然后继续操作。
应用配置
前往 Marketplace SDK 的应用配置页面。 提供以下信息:
将应用显示设置设为
Public
或Private
。- 公开设置适用于最终将向最终用户发布的应用。公开应用必须经过审批流程才能发布给最终用户,但您可以指定哪些用户可以安装并测试草稿。这是发布前的状态,可让您在发送插件以供审批之前对其进行测试和开发。
- 私密设置适用于内部测试和开发。只有与项目创建者同属一个网域的用户才能安装私密应用。因此,您应该仅在项目是在订阅了 Google Workspace 教育版的网域中创建的情况下将公开范围设为私享,否则测试用户将无法启动 Google 课堂插件。
如果您想将安装限制为仅限网域管理员,请将安装设置设为
Admin Only install
。在应用集成下,选择 Classroom 加购项。系统会提示您输入安全附件设置 URI;这是您希望在用户打开插件时加载的网址。在本演练中,此值应为
https://<your domain>/addon-discovery
。允许使用的附件 URI 前缀用于使用
courses.*.addOnAttachments.create
和courses.*.addOnAttachments.patch
方法验证在AddOnAttachment
中设置的 URI。验证是字面字符串前缀匹配,目前不允许使用通配符。至少添加内容服务器的根网域,例如https://localhost:5000/
或https://cdn.myedtech.com/
。添加与上一步中 OAuth 权限请求页面中提供的相同的 OAuth 范围。
在开发者链接下,根据贵组织的情况填写相应字段。
商品详情
前往 Marketplace SDK 的商品详情页面。 提供以下信息:
- 在应用详情下,添加语言或展开已列出的语言旁边的下拉菜单。提供应用名称和说明;这些内容会显示在插件的 Google Workspace Marketplace 商品详情页面上。 点击完成以保存。
- 为您的插件选择一个类别。
- 在图形资源下,为必填字段提供图片。这些信息以后可以更改,目前可以先使用占位符。
- 在支持链接下,提供所需的网址。如果您在上一步中将应用公开范围设置为不公开,则这些网址可以是占位网址。
如果您在上一步中将应用可见性设置为私有,请点击发布;您的应用将立即可供安装。如果您将应用公开范围设置为公开,请在草稿测试人员区域中添加所有测试用户的电子邮件地址,然后点击保存草稿。
OAuth 同意屏幕
OAuth 权限请求页面会在用户首次授权您的应用时显示。该页面会提示用户允许您的应用访问其个人信息和账号信息,具体取决于您启用的范围。
前往 OAuth 权限请求页面创建页面。请提供以下信息:
- 将用户类型设置为外部。点击创建。
- 在下一页中,填写必需的应用详情和联系信息。 在已获授权的网域下提供托管您应用的任何网域。点击保存并继续。
添加 Web 应用所需的任何 OAuth 范围。如需深入了解范围及其用途,请参阅 OAuth 配置指南。
您必须请求以下至少一个范围,Google 才能发送
login_hint
查询参数。如需更详细地了解此行为,请参阅我们的 OAuth 配置指南:https://www.googleapis.com/auth/userinfo.email
(已包含)https://www.googleapis.com/auth/userinfo.profile
(已包含)
以下范围特定于 Classroom 加购项:
https://www.googleapis.com/auth/classroom.addons.teacher
https://www.googleapis.com/auth/classroom.addons.student
还应包含您的应用需要最终用户授予的任何其他 Google API 范围。
点击保存并继续。
在测试用户页面上列出所有测试账号的电子邮件地址。 点击保存并继续。
确认设置正确无误,然后返回到信息中心。
安装插件
您现在可以使用 Marketplace SDK 的商品详情页面顶部的链接安装插件。点击页面顶部的在应用商店中查看以查看相应商品详情,然后选择安装。
构建基本 Web 应用
设置具有两条路由的框架 Web 应用。后续的演练步骤会扩展此应用,因此目前只需为插件 /addon-discovery
创建一个着陆页,并为我们的“公司网站”创建一个模拟的首页 /
。
实现以下两个端点:
/
:显示欢迎消息和一个用于关闭当前标签页和插件 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, 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.py
和 config.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('/addon-discovery', addonRouter);
打开 /01-basic-app/routes/index.js
并查看代码。当最终用户访问公司网站时,系统会到达此路由。该路由使用 index
Handlebars 模板呈现响应,并向该模板传递一个包含 title
和 message
变量的数据对象。
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 随即打开,插件会加载您在 Marketplace SDK 的应用配置页面中指定的附件设置 URI。
恭喜!您已准备好继续执行下一步:使用 Google SSO 登录用户。