显示导航

Google Home 指南

了解如何使用 Grails 制作 Google 动作

作者:瑞恩·范德沃夫,塞尔吉奥·德尔·阿莫

Grails 版本 3.3.0

1 Grails 培训

Grails 培训 - 由创建和积极维护 Grails 框架的人员开发和提供的!.

2 入门

在本指南中,你将学习如何创建 Grails 应用程序来托管 Google Action,以便你的 Google Home 设备进行交互。我们将在 Google App Engine 和 Google API 中构建、部署和设置此操作所需的一切。

我们将使用 抢先体验版非官方 Java SDK 来构建动作。这是因为唯一的官方 SDK 是使用 Node 构建的。

2.1 你需要什么

  • 一些时间

  • 一个不错的文本编辑器或 IDE

  • 一个 Google 帐户

  • 一个 Google Cloud 帐户

  • curl 命令来帮助调试

2.2 如何完成指南

要开始,请执行以下操作

Grails 指南仓库包含两个文件夹

  • initial 初始项目。通常是一个简单的 Grails 应用程序,具有一些额外的代码,可让你抢占先机。

  • complete 示例已完成。经过完成指南提供的步骤并将其应用于 initial 文件夹后生成该示例。

要完成指南,请转到 initial 文件夹

  • cd 编入 grails-guides/grails-google-home/initial

并按照下一部分中的指示进行操作。

如果你将 cd 编入 grails-guides/grails-google-home/complete,则可以转到**已完成示例**

初始项目是使用 web 档案编译的 Grails 应用程序,其中我们删除了 asset-pipelinehibernate 依附关系。

虽然你可以转到已完成示例,但为了部署该应用程序,你需要完成几个 Google Cloud 设置 步骤。

你需要编辑文件 src/main/resources/action.json 以指向你的 Google App Engine 灵活部署。

3 Google Cloud 设置

本指南使用付费服务;你可能需要在 Google Cloud 中启用计费才能完成本指南中的部分步骤。

3.1 创建 Google Cloud 项目

注册 Google Cloud Platform 并创建一个新项目

createproject 1
createproject 2

为你的操作系统安装 Cloud SDK

安装 SDK 后,在终端中运行 init 命令

$ gcloud init

它会提示你选择要使用的 Google 帐号和项目。

3.2 安装 gactions CLI

我们将用 gactions CLI 来测试我们的应用程序。

转到 gactions CLI 并下载它

gactions cli

将 gactions 二进制文件做成可执行文件(在 linux 和 OSX 中执行 chmod +x)。

安装 gactions 后,在终端中运行 init 命令

$ gactions init --force

你可以运行的其他命令:previewupdatedeploytestlist

3.3 配置 Google Actions

启用 Google Actions API

转到 Google API Manager Console 并启用 Google Actions API。

enablegoogleactions

将你的项目导入 Google Actions 控制台

转到新的 Google Actions 控制台 并导入你之前创建的项目

googleactionsconsole

action.json 中配置你的项目 ID

action.json 是一个元数据文件,我们用它来告知 Google Cloud Actions 控制台我们的应用程序支持哪些操作。

你还可以在这里控制如下内容

  • 帮助 google 了解要调用哪项操作的示例查询

  • 要调用的意图

src/main/resources/action.json
{
  "actions": [
    {
      "description": "Default Welcome Intent",
      "name": "MAIN",
      "fulfillment": {
        "conversationName": "color-finder-echo"
      },
      "intent": {
        "name": "actions.intent.MAIN"
      }

    },
    {
      "description": "Deep link that finds brighter colors",
      "name":"color.intent",
      "fulfillment": {
        "conversationName": "color.intent"
      },
      "intent": {
        "name": "color.intent",
        "parameters": [{
          "name": "color",
          "type": "SchemaOrg_Color"
        }],
        "trigger": {
          "queryPatterns": [
            "find a brighter color for $SchemaOrg_Color:color"
          ]
        }
      }
    }
  ],
  "conversations": {
    "color-finder-echo": {
      "name": "color-finder-echo",
      "url": "https://grails-color-finder.appspot.com/assistantAction/index"
    },
    "color.intent": {
      "name": "color.intent",
      "url": "https://grails-color-finder.appspot.com/assistantAction/color"
    }
  }
}

如上所示,我们的应用程序支持两种操作。我们将在本指南的下一部分中详细解释这些操作。

我们需要修改 httpExecution 项目,以匹配已部署的应用程序端点。

src/main/resources/action.json
"url": "https://PROJECT_ID.appspot.com/assistantAction/index"

向助手应用程序添加操作

首次在 Google Cloud Actions 控制台中选择一个项目时,您需要通过 gactions 工具提供操作包的 JSON,如下所示

useactionssdk
gactions update --action_package src/main/resources/action.json --project PROJECT_ID

按照上述命令提示的说明进行操作。这里涉及到一个授权过程。

最后,您将看到类似于以下内容的输出

Your app for the Assistant for project PROJECT_ID was successfully updated with your actions. Visit the Actions on Google console to finish registering your app and submit it for review at https://console.actions.google.com/project/PROJECT_ID/overview

添加应用程序信息

在操作包经过验证后,您将能够输入目录信息,如图片、隐私政策和描述。您还可以在此处为技能选择扬声器的语音。

appinfo
appinfo2

上一个屏幕截图显示了两个按钮。测试和提交。我们将在将应用程序部署到 Google App Engine Flexible 并准备好测试操作后,点击上一个屏幕截图中的 测试

3.4 Google App Engine

我们将本指南中开发的 Grails 应用程序部署到 Google App Engine 灵活环境

App Engine 允许开发者专注于他们最擅长的事情:编写代码。基于 Google Compute Engine,App Engine 灵活环境在平衡负载的同时自动上下扩展您的应用。微服务、授权、SQL 和 NoSQL 数据库、流量拆分、日志记录、版本管理、安全扫描和内容分发网络都得到了原生支持。

运行命令

$ gcloud app create

以初始化当前 Google Cloud 项目中的 App Engine 应用程序。

4 编写应用程序

4.1 在 Grails 中处理 Google 请求

我们将我们的应用程序命名为 color finder。我们要实现如下所示的场景

scenarioA

首先,我们要添加一个对 Google Actions Java SDK 的依赖项

/build.gradle
compile 'com.frogermcs.gactions:gactions:0.1.1'

用处理程序管理说明对话。我们会用工厂实例化处理程序。将以下类添加到您的应用程序

/src/main/groovy/demo/MainRequestHandlerFactory.groovy
package demo

import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import groovy.transform.CompileStatic

@CompileStatic
class MainRequestHandlerFactory extends RequestHandler.Factory {
    @Override
    RequestHandler create(RootRequest rootRequest) {
        new MainRequestHandler(rootRequest)
    }
}
/src/main/groovy/demo/MainRequestHandler.groovy
package demo

import com.frogermcs.gactions.ResponseBuilder
import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import com.frogermcs.gactions.api.response.RootResponse
import groovy.transform.CompileStatic

@CompileStatic
class MainRequestHandler extends RequestHandler {
    protected MainRequestHandler(RootRequest rootRequest) {
        super(rootRequest)
    }

    @Override
    RootResponse getResponse() {
        ResponseBuilder.askResponse('Hey, it works! Now tell something so I could repeat it.')
    }
}
/src/main/groovy/demo/MainRequestHandler.groovy
package demo

import com.frogermcs.gactions.ResponseBuilder
import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import com.frogermcs.gactions.api.response.RootResponse
import groovy.transform.CompileStatic

@CompileStatic
class MainRequestHandler extends RequestHandler {
    protected MainRequestHandler(RootRequest rootRequest) {
        super(rootRequest)
    }

    @Override
    RootResponse getResponse() {
        ResponseBuilder.askResponse('Hey, it works! Now tell something so I could repeat it.')
    }
}
/src/main/groovy/demo/TextRequestHandlerFactory.groovy
package demo

import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import groovy.transform.CompileStatic

@CompileStatic
class TextRequestHandlerFactory extends RequestHandler.Factory {
    @Override
    RequestHandler create(RootRequest rootRequest) {
        new TextRequestHandler(rootRequest)
    }
}
/src/main/groovy/demo/TextRequestHandler.groovy
package demo

import com.frogermcs.gactions.ResponseBuilder
import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import com.frogermcs.gactions.api.response.RootResponse
import groovy.transform.CompileStatic

@CompileStatic
class TextRequestHandler extends RequestHandler {
    protected TextRequestHandler(RootRequest rootRequest) {
        super(rootRequest)
    }

    @Override
    RootResponse getResponse() {
        ResponseBuilder.tellResponse("You just told: ${rootRequest.inputs.get(0).raw_inputs.get(0).query}")
    }
}

我们将在 Grails Controller 中处理来自 Google 的请求

控制器满足模型视图控制器 (MVC) 模式中的 C,负责处理网页请求。在 Grails 中,控制器是一个类,其名称以“控制器”的惯例结尾,并且位于 grails-app/controllers 目录中。

Grails 控制器反过来会委托给 Actions SDK。

/grails-app/controllers/demo/AssistantActionController.groovy
package demo

import com.frogermcs.gactions.AssistantActions
import com.frogermcs.gactions.api.StandardIntents
import com.frogermcs.gactions.api.request.RootRequest
import com.google.gson.Gson
import com.google.gson.stream.JsonReader
import javax.servlet.http.HttpServletRequest
import groovy.transform.CompileStatic

@CompileStatic
class AssistantActionController {
    def index() {
        AssistantActions assistantActions =
                new AssistantActions.Builder(new GrailsResponseHandler(response))
                        .addRequestHandlerFactory(StandardIntents.MAIN, new MainRequestHandlerFactory())  (1)
                        .addRequestHandlerFactory(StandardIntents.TEXT, new TextRequestHandlerFactory())
                        .build()
        RootRequest rootRequest = parseActionRequest(request)
        assistantActions.handleRequest(rootRequest) (2)
        null (3)
    }
    private RootRequest parseActionRequest(HttpServletRequest request) throws IOException {
        JsonReader jsonReader = new JsonReader(request.reader)
        new Gson().fromJson(jsonReader, RootRequest)
    }
1 我们可以链接处理程序
2 Grails 操作委托给 Actions SDK 来处理来自 Google 的请求。
3 我们不想返回 Grails 视图。处理程序会管理响应

GrailsResponseHandler 添加到您的项目。如上所示,它允许我们使用控制器来处理传入的请求。

/src/main/groovy/demo/GrailsResponseHandler.groovy
package demo

import com.frogermcs.gactions.ResponseHandler
import com.frogermcs.gactions.api.response.RootResponse
import com.google.gson.Gson
import groovy.transform.CompileStatic
import javax.servlet.http.HttpServletResponse
import groovy.util.logging.Slf4j

/**
 * This is a handler for the SDK to make it work with Grails properly
 */
@CompileStatic
@Slf4j
class GrailsResponseHandler implements ResponseHandler {
    private final HttpServletResponse httpServletResponse
    private final Gson gson

    GrailsResponseHandler(HttpServletResponse httpServletResponse) {
        this(httpServletResponse, new Gson())
    }

    GrailsResponseHandler(HttpServletResponse httpServletResponse, Gson gson) {
        this.httpServletResponse = httpServletResponse
        this.gson = gson
    }

    @Override
    void onPrepareContentType(String contentType) {
        httpServletResponse.setContentType(contentType)
    }

    @Override
    void onPrepareResponseHeaders(Map<String, String> headers) {
        for (String headerName : headers.keySet()) {
            httpServletResponse.addHeader(headerName, headers.get(headerName))
        }
    }

    @Override
    void onResponse(RootResponse rootResponse) {
        try {
            gson.toJson(rootResponse, httpServletResponse.writer)
            httpServletResponse.flushBuffer()
        } catch (IOException e) {
            log.error('Error writing response', e)
        }
    }

    String getResponse(RootResponse rootResponse) {

        try {
            gson.toJson(rootResponse)
        } catch (IOException e) {
            log.error('Error getting response', e)
        }
    }
}

4.2 颜色处理器

为了追求应用程序的名称,我们还希望处理以下方案

scenarioB

与之前的示例类似。我们需要一个处理器和一个用于实例化处理器的工厂。

此处理器演示了将一个口头参数传递到意图中。当用户要求 X 的颜色更亮时,它将尝试找到具有名称的更亮颜色并将其告知用户。

/src/main/groovy/demo/ColorRequestHandlerFactory.groovy
package demo

import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import groovy.transform.CompileStatic

@CompileStatic
class ColorRequestHandlerFactory extends RequestHandler.Factory {
    @Override
    RequestHandler create(RootRequest rootRequest) {
        new ColorRequestHandler(rootRequest)
    }

}
/src/main/groovy/demo/ColorRequestHandler.groovy
package demo

import com.frogermcs.gactions.ResponseBuilder
import com.frogermcs.gactions.api.RequestHandler
import com.frogermcs.gactions.api.request.RootRequest
import com.frogermcs.gactions.api.response.RootResponse
import demo.util.ColorUtils
import java.awt.Color
import java.lang.reflect.Field
import groovy.transform.CompileStatic
import groovy.util.logging.Slf4j

/**
 * take a color name input and returns a brighter color name in return if possible
 */
@CompileStatic
@Slf4j
class ColorRequestHandler extends RequestHandler {

    protected ColorRequestHandler(RootRequest rootRequest) {
        super(rootRequest)
    }

    @Override
    RootResponse getResponse() {
        log.debug("Inputs=${rootRequest.inputs.toListString()}")
        String color = rootRequest.inputs[0].arguments[0].raw_text.toLowerCase()

        Color parsedColor = null
        try {
            Field field = Class.forName('java.awt.Color').getField(color)
            parsedColor = (Color)field.get(null)
        } catch (NoSuchFieldException ne) {
            return colorNotFound(color)
        }
        if (parsedColor) {
            ColorUtils colorUtils = new ColorUtils()
            String brighter = colorUtils.findBrighterNameForColor(parsedColor).toLowerCase()
            String answer = "Sorry I can't find a brighter color for ${color}."
            if (brighter != color) {
                answer = "The brighter color for ${color} is ${brighter} "
            }
            return ResponseBuilder.tellResponse(answer)
        }
        colorNotFound(color)
    }

    private RootResponse colorNotFound(String color) {
        ResponseBuilder.tellResponse("Sorry I don't understand the color ${color}.")
    }
}

我们将处理来自我们控制器的另一个操作的 Google 发出的请求

/grails-app/controllers/demo/AssistantActionController.groovy
package demo

import com.frogermcs.gactions.AssistantActions
import com.frogermcs.gactions.api.StandardIntents
import com.frogermcs.gactions.api.request.RootRequest
import com.google.gson.Gson
import com.google.gson.stream.JsonReader
import javax.servlet.http.HttpServletRequest
import groovy.transform.CompileStatic

@CompileStatic
class AssistantActionController {
    def color() {
        AssistantActions assistantActions =
                new AssistantActions.Builder(new GrailsResponseHandler(response))
                        .addRequestHandlerFactory('color.intent', new ColorRequestHandlerFactory()) (1)
                        .build()
        RootRequest rootRequest = parseActionRequest(request)  (2)
        assistantActions.handleRequest(rootRequest)
        null  (3)
    }
    private RootRequest parseActionRequest(HttpServletRequest request) throws IOException {
        JsonReader jsonReader = new JsonReader(request.reader)
        new Gson().fromJson(jsonReader, RootRequest)
    }
1 在工厂的帮助下实例化处理器
2 Grails 操作委托给 Actions SDK 来处理来自 Google 的请求。
3 我们不想返回 Grails 视图。处理程序会管理响应

让我们使用一个简单的实用程序类让我们的操作进行一些有趣的事情。这将帮助我们映射给定的 java.awt.Color 以便为其提供一个英文名称。

/src/main/groovy/demo/util/ColorUtils.java
package demo.util;

import java.awt.*;
import java.util.ArrayList;

/**
 * Java Code to get a color name from rgb/hex value/awt color
 *
 * The part of looking up a color name from the rgb values is edited from
 * https://gist.github.com/nightlark/6482130#file-gistfile1-java (that has some errors) by Ryan Mast (nightlark)
 *
 * @author Xiaoxiao Li
 *
 */
public class ColorUtils {

    /**
     * Initialize the color list that we have.
     */
    private ArrayList<ColorName> initColorList() {
        ArrayList<ColorName> colorList = new ArrayList<ColorName>();
        colorList.add(new ColorName("AliceBlue", 0xF0, 0xF8, 0xFF));
        colorList.add(new ColorName("AntiqueWhite", 0xFA, 0xEB, 0xD7));
        colorList.add(new ColorName("Aqua", 0x00, 0xFF, 0xFF));
        colorList.add(new ColorName("Aquamarine", 0x7F, 0xFF, 0xD4));
        colorList.add(new ColorName("Azure", 0xF0, 0xFF, 0xFF));
        colorList.add(new ColorName("Beige", 0xF5, 0xF5, 0xDC));
        colorList.add(new ColorName("Bisque", 0xFF, 0xE4, 0xC4));
        colorList.add(new ColorName("Black", 0x00, 0x00, 0x00));
        colorList.add(new ColorName("BlanchedAlmond", 0xFF, 0xEB, 0xCD));
        colorList.add(new ColorName("Blue", 0x00, 0x00, 0xFF));
        colorList.add(new ColorName("BlueViolet", 0x8A, 0x2B, 0xE2));
        colorList.add(new ColorName("Brown", 0xA5, 0x2A, 0x2A));
        colorList.add(new ColorName("BurlyWood", 0xDE, 0xB8, 0x87));
        colorList.add(new ColorName("CadetBlue", 0x5F, 0x9E, 0xA0));
        colorList.add(new ColorName("Chartreuse", 0x7F, 0xFF, 0x00));
        colorList.add(new ColorName("Chocolate", 0xD2, 0x69, 0x1E));
        colorList.add(new ColorName("Coral", 0xFF, 0x7F, 0x50));
        colorList.add(new ColorName("CornflowerBlue", 0x64, 0x95, 0xED));
        colorList.add(new ColorName("Cornsilk", 0xFF, 0xF8, 0xDC));
        colorList.add(new ColorName("Crimson", 0xDC, 0x14, 0x3C));
        colorList.add(new ColorName("Cyan", 0x00, 0xFF, 0xFF));
        colorList.add(new ColorName("DarkBlue", 0x00, 0x00, 0x8B));
        colorList.add(new ColorName("DarkCyan", 0x00, 0x8B, 0x8B));
        colorList.add(new ColorName("DarkGoldenRod", 0xB8, 0x86, 0x0B));
        colorList.add(new ColorName("DarkGray", 0xA9, 0xA9, 0xA9));
        colorList.add(new ColorName("DarkGreen", 0x00, 0x64, 0x00));
        colorList.add(new ColorName("DarkKhaki", 0xBD, 0xB7, 0x6B));
        colorList.add(new ColorName("DarkMagenta", 0x8B, 0x00, 0x8B));
        colorList.add(new ColorName("DarkOliveGreen", 0x55, 0x6B, 0x2F));
        colorList.add(new ColorName("DarkOrange", 0xFF, 0x8C, 0x00));
        colorList.add(new ColorName("DarkOrchid", 0x99, 0x32, 0xCC));
        colorList.add(new ColorName("DarkRed", 0x8B, 0x00, 0x00));
        colorList.add(new ColorName("DarkSalmon", 0xE9, 0x96, 0x7A));
        colorList.add(new ColorName("DarkSeaGreen", 0x8F, 0xBC, 0x8F));
        colorList.add(new ColorName("DarkSlateBlue", 0x48, 0x3D, 0x8B));
        colorList.add(new ColorName("DarkSlateGray", 0x2F, 0x4F, 0x4F));
        colorList.add(new ColorName("DarkTurquoise", 0x00, 0xCE, 0xD1));
        colorList.add(new ColorName("DarkViolet", 0x94, 0x00, 0xD3));
        colorList.add(new ColorName("DeepPink", 0xFF, 0x14, 0x93));
        colorList.add(new ColorName("DeepSkyBlue", 0x00, 0xBF, 0xFF));
        colorList.add(new ColorName("DimGray", 0x69, 0x69, 0x69));
        colorList.add(new ColorName("DodgerBlue", 0x1E, 0x90, 0xFF));
        colorList.add(new ColorName("FireBrick", 0xB2, 0x22, 0x22));
        colorList.add(new ColorName("FloralWhite", 0xFF, 0xFA, 0xF0));
        colorList.add(new ColorName("ForestGreen", 0x22, 0x8B, 0x22));
        colorList.add(new ColorName("Fuchsia", 0xFF, 0x00, 0xFF));
        colorList.add(new ColorName("Gainsboro", 0xDC, 0xDC, 0xDC));
        colorList.add(new ColorName("GhostWhite", 0xF8, 0xF8, 0xFF));
        colorList.add(new ColorName("Gold", 0xFF, 0xD7, 0x00));
        colorList.add(new ColorName("GoldenRod", 0xDA, 0xA5, 0x20));
        colorList.add(new ColorName("Gray", 0x80, 0x80, 0x80));
        colorList.add(new ColorName("Green", 0x00, 0x80, 0x00));
        colorList.add(new ColorName("GreenYellow", 0xAD, 0xFF, 0x2F));
        colorList.add(new ColorName("HoneyDew", 0xF0, 0xFF, 0xF0));
        colorList.add(new ColorName("HotPink", 0xFF, 0x69, 0xB4));
        colorList.add(new ColorName("IndianRed", 0xCD, 0x5C, 0x5C));
        colorList.add(new ColorName("Indigo", 0x4B, 0x00, 0x82));
        colorList.add(new ColorName("Ivory", 0xFF, 0xFF, 0xF0));
        colorList.add(new ColorName("Khaki", 0xF0, 0xE6, 0x8C));
        colorList.add(new ColorName("Lavender", 0xE6, 0xE6, 0xFA));
        colorList.add(new ColorName("LavenderBlush", 0xFF, 0xF0, 0xF5));
        colorList.add(new ColorName("LawnGreen", 0x7C, 0xFC, 0x00));
        colorList.add(new ColorName("LemonChiffon", 0xFF, 0xFA, 0xCD));
        colorList.add(new ColorName("LightBlue", 0xAD, 0xD8, 0xE6));
        colorList.add(new ColorName("LightCoral", 0xF0, 0x80, 0x80));
        colorList.add(new ColorName("LightCyan", 0xE0, 0xFF, 0xFF));
        colorList.add(new ColorName("LightGoldenRodYellow", 0xFA, 0xFA, 0xD2));
        colorList.add(new ColorName("LightGray", 0xD3, 0xD3, 0xD3));
        colorList.add(new ColorName("LightGreen", 0x90, 0xEE, 0x90));
        colorList.add(new ColorName("LightPink", 0xFF, 0xB6, 0xC1));
        colorList.add(new ColorName("LightSalmon", 0xFF, 0xA0, 0x7A));
        colorList.add(new ColorName("LightSeaGreen", 0x20, 0xB2, 0xAA));
        colorList.add(new ColorName("LightSkyBlue", 0x87, 0xCE, 0xFA));
        colorList.add(new ColorName("LightSlateGray", 0x77, 0x88, 0x99));
        colorList.add(new ColorName("LightSteelBlue", 0xB0, 0xC4, 0xDE));
        colorList.add(new ColorName("LightYellow", 0xFF, 0xFF, 0xE0));
        colorList.add(new ColorName("Lime", 0x00, 0xFF, 0x00));
        colorList.add(new ColorName("LimeGreen", 0x32, 0xCD, 0x32));
        colorList.add(new ColorName("Linen", 0xFA, 0xF0, 0xE6));
        colorList.add(new ColorName("Magenta", 0xFF, 0x00, 0xFF));
        colorList.add(new ColorName("Maroon", 0x80, 0x00, 0x00));
        colorList.add(new ColorName("MediumAquaMarine", 0x66, 0xCD, 0xAA));
        colorList.add(new ColorName("MediumBlue", 0x00, 0x00, 0xCD));
        colorList.add(new ColorName("MediumOrchid", 0xBA, 0x55, 0xD3));
        colorList.add(new ColorName("MediumPurple", 0x93, 0x70, 0xDB));
        colorList.add(new ColorName("MediumSeaGreen", 0x3C, 0xB3, 0x71));
        colorList.add(new ColorName("MediumSlateBlue", 0x7B, 0x68, 0xEE));
        colorList.add(new ColorName("MediumSpringGreen", 0x00, 0xFA, 0x9A));
        colorList.add(new ColorName("MediumTurquoise", 0x48, 0xD1, 0xCC));
        colorList.add(new ColorName("MediumVioletRed", 0xC7, 0x15, 0x85));
        colorList.add(new ColorName("MidnightBlue", 0x19, 0x19, 0x70));
        colorList.add(new ColorName("MintCream", 0xF5, 0xFF, 0xFA));
        colorList.add(new ColorName("MistyRose", 0xFF, 0xE4, 0xE1));
        colorList.add(new ColorName("Moccasin", 0xFF, 0xE4, 0xB5));
        colorList.add(new ColorName("NavajoWhite", 0xFF, 0xDE, 0xAD));
        colorList.add(new ColorName("Navy", 0x00, 0x00, 0x80));
        colorList.add(new ColorName("OldLace", 0xFD, 0xF5, 0xE6));
        colorList.add(new ColorName("Olive", 0x80, 0x80, 0x00));
        colorList.add(new ColorName("OliveDrab", 0x6B, 0x8E, 0x23));
        colorList.add(new ColorName("Orange", 0xFF, 0xA5, 0x00));
        colorList.add(new ColorName("OrangeRed", 0xFF, 0x45, 0x00));
        colorList.add(new ColorName("Orchid", 0xDA, 0x70, 0xD6));
        colorList.add(new ColorName("PaleGoldenRod", 0xEE, 0xE8, 0xAA));
        colorList.add(new ColorName("PaleGreen", 0x98, 0xFB, 0x98));
        colorList.add(new ColorName("PaleTurquoise", 0xAF, 0xEE, 0xEE));
        colorList.add(new ColorName("PaleVioletRed", 0xDB, 0x70, 0x93));
        colorList.add(new ColorName("PapayaWhip", 0xFF, 0xEF, 0xD5));
        colorList.add(new ColorName("PeachPuff", 0xFF, 0xDA, 0xB9));
        colorList.add(new ColorName("Peru", 0xCD, 0x85, 0x3F));
        colorList.add(new ColorName("Pink", 0xFF, 0xC0, 0xCB));
        colorList.add(new ColorName("Plum", 0xDD, 0xA0, 0xDD));
        colorList.add(new ColorName("PowderBlue", 0xB0, 0xE0, 0xE6));
        colorList.add(new ColorName("Purple", 0x80, 0x00, 0x80));
        colorList.add(new ColorName("Red", 0xFF, 0x00, 0x00));
        colorList.add(new ColorName("RosyBrown", 0xBC, 0x8F, 0x8F));
        colorList.add(new ColorName("RoyalBlue", 0x41, 0x69, 0xE1));
        colorList.add(new ColorName("SaddleBrown", 0x8B, 0x45, 0x13));
        colorList.add(new ColorName("Salmon", 0xFA, 0x80, 0x72));
        colorList.add(new ColorName("SandyBrown", 0xF4, 0xA4, 0x60));
        colorList.add(new ColorName("SeaGreen", 0x2E, 0x8B, 0x57));
        colorList.add(new ColorName("SeaShell", 0xFF, 0xF5, 0xEE));
        colorList.add(new ColorName("Sienna", 0xA0, 0x52, 0x2D));
        colorList.add(new ColorName("Silver", 0xC0, 0xC0, 0xC0));
        colorList.add(new ColorName("SkyBlue", 0x87, 0xCE, 0xEB));
        colorList.add(new ColorName("SlateBlue", 0x6A, 0x5A, 0xCD));
        colorList.add(new ColorName("SlateGray", 0x70, 0x80, 0x90));
        colorList.add(new ColorName("Snow", 0xFF, 0xFA, 0xFA));
        colorList.add(new ColorName("SpringGreen", 0x00, 0xFF, 0x7F));
        colorList.add(new ColorName("SteelBlue", 0x46, 0x82, 0xB4));
        colorList.add(new ColorName("Tan", 0xD2, 0xB4, 0x8C));
        colorList.add(new ColorName("Teal", 0x00, 0x80, 0x80));
        colorList.add(new ColorName("Thistle", 0xD8, 0xBF, 0xD8));
        colorList.add(new ColorName("Tomato", 0xFF, 0x63, 0x47));
        colorList.add(new ColorName("Turquoise", 0x40, 0xE0, 0xD0));
        colorList.add(new ColorName("Violet", 0xEE, 0x82, 0xEE));
        colorList.add(new ColorName("Wheat", 0xF5, 0xDE, 0xB3));
        colorList.add(new ColorName("White", 0xFF, 0xFF, 0xFF));
        colorList.add(new ColorName("WhiteSmoke", 0xF5, 0xF5, 0xF5));
        colorList.add(new ColorName("Yellow", 0xFF, 0xFF, 0x00));
        colorList.add(new ColorName("YellowGreen", 0x9A, 0xCD, 0x32));
        return colorList;
    }

    /**
     * Get the closest color name from our list
     *
     * @param r
     * @param g
     * @param b
     * @return
     */
    public String getColorNameFromRgb(int r, int g, int b) {
        ArrayList<ColorName> colorList = initColorList();
        ColorName closestMatch = null;
        int minMSE = Integer.MAX_VALUE;
        int mse;
        for (ColorName c : colorList) {
            mse = c.computeMSE(r, g, b);
            if (mse < minMSE) {
                minMSE = mse;
                closestMatch = c;
            }
        }

        if (closestMatch != null) {
            return closestMatch.getName();
        } else {
            return "No matched color name.";
        }
    }

    /**
     * Convert hexColor to rgb, then call getColorNameFromRgb(r, g, b)
     *
     * @param hexColor
     * @return
     */
    public String getColorNameFromHex(int hexColor) {
        int r = (hexColor & 0xFF0000) >> 16;
        int g = (hexColor & 0xFF00) >> 8;
        int b = (hexColor & 0xFF);
        return getColorNameFromRgb(r, g, b);
    }

    public int colorToHex(Color c) {
        return Integer.decode("0x"
                + Integer.toHexString(c.getRGB()).substring(2));
    }

    public String getColorNameFromColor(Color color) {
        return getColorNameFromRgb(color.getRed(), color.getGreen(),
                color.getBlue());
    }

    public String findBrighterNameForColor(Color color) {
        String name = getColorNameFromColor(color);
        String nextColorName = name;
        int colorCount = 0;
        while (name.equals(nextColorName) && colorCount < 100) {
            color = color.brighter();
            nextColorName = getColorNameFromColor(color);
            colorCount++;
        }
        return nextColorName;



    }

    public String findDarkerNameForColor(Color color) {
        String name = getColorNameFromColor(color);
        String nextColorName = name;
        int colorCount = 0;
        while (name.equals(nextColorName) && colorCount < 100) {
            color = color.darker();
            nextColorName = getColorNameFromColor(color);
            colorCount++;
        }
        return nextColorName;



    }

    /**
     * SubClass of ColorUtils. In order to lookup color name
     *
     * @author Xiaoxiao Li
     *
     */
    public class ColorName {
        public int r, g, b;
        public String name;

        public ColorName(String name, int r, int g, int b) {
            this.r = r;
            this.g = g;
            this.b = b;
            this.name = name;
        }

        public int computeMSE(int pixR, int pixG, int pixB) {
            return (int) (((pixR - r) * (pixR - r) + (pixG - g) * (pixG - g) + (pixB - b)
                    * (pixB - b)) / 3);
        }

        public int getR() {
            return r;
        }

        public int getG() {
            return g;
        }

        public int getB() {
            return b;
        }

        public String getName() {
            return name;
        }
    }
}

前面的类是一个 Java 类。Grails 允许我们无缝地混合使用 Groovy 和 Java 代码

4.3 非官方 Google Actions Java SDK

SDK 位于 https://github.com/frogermcs/Google-Actions-Java-SDK,但是我们只需要编辑我们的 build.gradle 以包含该库即可。

/build.gradle
compile 'com.frogermcs.gactions:gactions:0.1.1'

4.4 Google App Engine Gradle 插件

为了部署到 App Engine,我们将使用 Google App Engine Gradle 插件

我们需要修改 build.gradle,将插件添加为构建脚本依赖项并应用该插件

build.gradle
buildscript {
    repositories {
        mavenLocal()
        maven { url "https://repo.grails.org/grails/core" }
    }
    dependencies {
        classpath "org.grails:grails-gradle-plugin:$grailsVersion"
        classpath 'com.google.cloud.tools:appengine-gradle-plugin:1.1.1'
    }
}
...
apply plugin: 'com.google.cloud.tools.appengine'

4.5 应用程序部署配置

为了部署到 Google App Engine,我们需要添加文件 src/main/appengine/app.yaml

它描述了应用程序的部署配置

/src/main/appengine/app.yaml
runtime: java
env: flex

runtime_config:
    jdk: openjdk8
    server: jetty9

health_check:
    enable_health_check: False

resources:
    cpu: 1
    memory_gb: 2.3

manual_scaling:
  instances: 1

这里,app.yaml 指定应用程序使用的运行时,并设置 env: flex,指定应用程序使用 弹性环境

上面显示的最小的 app.yaml 应用程序配置文件对于一个简单的 Grails 应用程序就足够了。根据应用程序使用的规模、复杂程度和功能,你可能需要更改并扩展此基本配置文件。欲了解有关可以通过 app.yaml 配置的内容的更多信息,请参阅 使用 app.yaml 配置你的应用程序 指南。

欲了解有关 Java 运行时工作原理的更多信息,请参阅 Java 8 / Jetty 9.3 运行时

4.6 SpringBoot Jetty

如前一个应用引擎配置文件中所示,我们正在使用 Jetty。

按照 SpringBoot 的文档,我们需要对 部署到 Jetty 而不是 Tomcat 做出以下更改

替换 tomcat

app/build.gradle
provided 'org.springframework.boot:spring-boot-starter-tomcat'

与 jetty

/build.gradle
provided "org.springframework.boot:spring-boot-starter-jetty"

我们还需要排除 tomcat-juli 依赖项。添加

/build.gradle
configurations {
    compile.exclude module: "tomcat-juli"
}

4.7 部署应用

要将该应用程序部署到 Google App Engine,请运行

$ ./gradlew appengineDeploy

初始部署可能需要一段时间。完成后,您将能够访问您的应用程序。

5 测试

5.1 测试并与您的 Google Action 交互

一旦您的应用程序部署在 Google App Engine Flexible 中,您就可以对其进行测试。

返回 Google Actions 控制台,然后单击测试。

test1
test2

尝试使用以下字词测试您的动作

让 Grails Color Finder 为品红色找到一个更亮的颜色

您应该会看到该响应

当然,这是 Grails Color Finder 的测试版。品红色的更亮颜色是紫红色"

test brigther color

我们再试一次

与 Grails Color Finder 对话

您会看到

当然,这是 Grails Color Finder 的测试版。嘿,它真的起作用了!现在告诉我一些事情,我就能重复一遍。

现在用一些东西做出回应

Grails 棒极了

您应该看到它将您输入的内容回显出来

您刚才说了:Grails 棒极了

test default

现在,您可以制作一项更丰富的技能,当您做好准备后,转到 Google API 控制板,填写目录列表并提交以进行测试。