1. 概览

该插件提供全局的 REST API,具有以下功能:

  • 实体的 CRUD 操作

  • 执行预定义的 JPQL 查询。

  • 执行 service 方法

  • 获取元数据(实体、视图、枚举、数据类型)

  • 获取当前用户权限(访问实体、属性、特殊权限)

  • 获取当前用户信息(名称、语言、时区等)

  • 上传下载文件

REST API 使用 OAuth2 协议进行用户验证,并支持匿名访问。

所有的 REST API endpoints 在操作数据的时候,都按照 安全子系统 配置的用户权限操作。

Tip

API endpoints 的详细文档发布在 http://files.cuba-platform.com/swagger/7.2

2. 发行说明

3. 开始使用

3.1. 安装

按照下面的介绍在您的项目安装该插件。

  1. 在 CUBA 项目树双击 Add-ons

    addons
  2. 选择 Marketplace 标签页,再找到 REST API 插件。

    restapi addon
  3. 点击 Install 按钮,然后点击 Apply & Close 按钮。

    addon install
  4. 弹窗中点击 Continue

    addon continue

安装完成,您的项目可以使用 REST API 插件。

需要注意,这个插件只能用在框架版本 7.1 以上。之前版本的框架中,默认已经带了 REST API。

3.2. 测试基本功能

  • 当 Gradle 刷新了项目依赖之后,在 Studio 中,您应该能在左边的 CUBA 项目树看到 REST 元素。

  • 按照 安全 章节配置安全管理:

    • 创建一个包含 cuba.restApi.enabled 特殊权限的角色,并给角色赋予能读取必要实体和实体属性的权限。

    • 为用户分配该角色。

  • 启动应用程序,并用命令行工具 curl 测试 API:

    1. 请求 OAuth token

      curl -X POST \
        http://localhost:8080/app/rest/v2/oauth/token \
        -H 'Authorization: Basic Y2xpZW50OnNlY3JldA==' \
        -H 'Content-Type: application/x-www-form-urlencoded' \
        -d 'grant_type=password&username=admin&password=admin'

      您应当能得到一个带有 access_token 的响应。这个 token 可以在将来的请求中用作 Authorization header。

    2. 请求角色列表 (用上一步获取的 token 替换 <access_token>):

        curl -X GET \
        'http://localhost:8080/app/rest/v2/entities/sec$Role' \
        -H 'Authorization: Bearer <access_token>'

      响应中会包含所有的角色,但是需要保证用在获取 token 时的用户有权限读取 sec$Role 实体。

4. 功能

本章介绍 REST API 功能以及配置选项。如需了解应用实践,请参阅 下一章节

4.1. 预定义 JPQL 查询配置

在 CUBA 应用程序中,预定义的 JPQL 查询语句需要写在特定的配置文件中,这些配置文件是由 web 或者 portal 模块的 cuba.rest.queriesConfig 应用程序属性来指定,比如在 web-app.properties 文件中:

cuba.rest.queriesConfig = +com/company/myapp/rest-queries.xml

rest-queries.xml 这个文件必须放在 web 或者 portal 模块的根目录(比如 com.company.myapp)。文件内容格式是由 rest-queries.xsd shema 定义。示例:

<?xml version="1.0"?>
<queries xmlns="http://schemas.haulmont.com/cuba/rest-queries.xsd">
    <query name="carByVin" entity="sample$Car" view="carEdit">
        <jpql><![CDATA[select c from sample$Car c where c.vin = :vin]]></jpql>
        <params>
            <param name="vin" type="java.lang.String"/>
        </params>
    </query>
    <query name="allColours" entity="sample$Colour" view="_local">
        <jpql><![CDATA[select u from sample$Colour u order by u.name]]></jpql>
    </query>
    <query name="carsByIds" entity="sample$Car" view="carEdit" cacheable="true">
        <jpql><![CDATA[select c from sample$Car c where c.id in :ids]]></jpql>
        <params>
            <param name="ids" type="java.util.UUID[]"/>
        </params>
    </query>
    <query name="myOrders" entity="sample$Order" view="orderBrowse">
        <jpql><![CDATA[select o from sample$Order o where o.createdBy = :session$userLogin]]></jpql>
    </query>
</queries>

关于配置和执行查询语句的示例,可以参考 执行 JPQL 查询(GET)执行 JPQL 查询(POST) 章节。

平台还提供了预定义的 all 查询用来获取指定实体类型的所有实例。这个还能和 /count 一起使用,用来获得实体实例的总数,示例:

http://localhost:8080/app/rest/v2/queries/sales$Order/all/count

XML 里面的 query 元素还可以使用 cacheable 属性来启用查询语句的缓存

查询语句可以包含预定义的参数,这些参数使用当前用户的 id 和登录名:session$userIdsession$userLogin。不需要在 params 元素声明这两个参数(参考上面的例子)。

4.2. 服务配置

Service 中可以通过 REST API 访问的方法需要写在配置文件中。这些配置文件需要通过 web 或者 portal 模块的 cuba.rest.servicesConfig 应用程序属性来指定,比如在 web-app.properties 文件中:

cuba.rest.servicesConfig = +com/company/myapp/rest-services.xml

rest-services.xml 文件需要放在 web 或者 portal 模块的根目录(比如 com.company.myapp)。文件内容格式是由 rest-services-v2.xsd shema 来定义,示例:

<?xml version="1.0" encoding="UTF-8"?>
<services xmlns="http://schemas.haulmont.com/cuba/rest-services-v2.xsd">
    <service name="myapp_SomeService">
        <method name="sum">
            <param name="number1"/>
            <param name="number2"/>
        </method>
        <method name="emptyMethod"/>
        <method name="overloadedMethod">
            <param name="intParam" type="int"/>
        </method>
        <method name="overloadedMethod">
            <param name="stringParam" type="java.lang.String"/>
        </method>
    </service>
</services>

如果服务没有重载带有相同数量参数的方法的话,方法参数的类型可以省略。否则,参数类型必须要显式定义。

如果参数类型是原始数据类型的话,配置文件中可以直接用数据类型的名称( intdouble 等):

<param name="intParam" type="int"/>

如果参数类型是对象或者实体,则需要用完全限定名称(包含路径和类名)作为参数类型:

<param name="stringParam" type="java.lang.String"/>
<param name="entityParam" type="com.company.entity.Order"/>

如果是实体集合或者 POJO 集合,则使用集合类型:

<param name="entitiesCollectionParam" type="java.util.List"/>

关于配置和调用服务的示例可以参考 调用服务方法(GET)调用服务方法(POST) 章节。

如果某些服务的方法需要在 匿名访问 被禁用的情况下进行无认证访问,那么可以在服务配置文件中给这个方法单独添加 anonymousAllowed="true" 属性:

<?xml version="1.0" encoding="UTF-8"?>
<services xmlns="http://schemas.haulmont.com/cuba/rest-services-v2.xsd">
    <service name="myapp_SomeService">
        <method name="sum" anonymousAllowed="true">
            <param name="number1"/>
            <param name="number2"/>
        </method>
    </service>
</services>

4.3. 数据模型版本

REST API 可以处理数据模型变更。某些场景这个功能很有用,比如,有些实体属性被重命名了,但是 REST API 客户端不知道这个改动,并且还是希望这个属性保留原来的名字。

这种情况下,REST API 允许定义实体 JSON 的转换规则。如果客户端应用在发送请求时,在查询参数里带上数据模型的版本,REST API 的响应体或者请求体中的 JSON 会按照请求中特定的领域模型版本的转换规则进行转换。

JSON 的转换规则必须写在配置文件中,这些配置文件要通过 web 或者 portal 模块的 cuba.rest.jsonTransformationConfig 应用程序属性来定义,比如 web-app.properties 文件:

cuba.rest.jsonTransformationConfig = +com/company/myapp/rest-json-transformations.xml

rest-json-transformations.xml 文件需要放在 web 或者 portal 模块(比如 com.company.myapp)。这个文件的内容是由 rest-json-transformations.xsd schema 定义。文件示例:

<?xml version="1.0"?>
<transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">

    <transformation modelVersion="1.0" oldEntityName="sales$OldOrder" currentEntityName="sales$NewOrder">
        <renameAttribute oldName="oldNumber" currentName="number"/>
        <renameAttribute oldName="date" currentName="deliveryDate"/>
        <toVersion>
            <removeAttribute name="discount"/>
        </toVersion>
    </transformation>

    <transformation modelVersion="1.0" currentEntityName="sales$Contractor">
        <renameAttribute oldName="summary" currentName="total"/>
        <renameAttribute oldName="familyName" currentName="lastName"/>
        <fromVersion>
            <removeAttribute name="city"/>
            <removeAttribute name="country"/>
        </fromVersion>
        <toVersion>
            <removeAttribute name="phone"/>
        </toVersion>
    </transformation>

    <transformation modelVersion="1.1" currentEntityName="sales$NewOrder">
        <renameAttribute oldName="date" currentName="deliveryDate"/>
    </transformation>

</transformations>

在配置文件中定义的标准转换器可以进行下列实体 JSON 转换任务:

  • 重命名实体

  • 重命名实体属性

  • 删除实体属性

JSON 转换可以在下列 REST API endpoint 生效:

  • /entities - 获取实体列表、获取单个实体、实体创建、实体更新、实体删除

  • /queries - 查询返回的实体 JSON 会被转换

  • /services - JSON 转换会发生在两个地方,service 方法返回的实体,以及作为参数传入 service 方法的实体。

如果对 REST API 的请求使用了 modelVersion URL 参数指定数据模型版本,那么 JSON 转换就会生效。

参考数据模型版本化示例了解如何配置数据模型版本以及怎样在客户端程序使用。

4.4. 跨域请求(CORS)设置

默认情况下,CUBA 允许所有的 REST API 的跨域请求。限制请求来源地址列表,可以通过设置 cuba.rest.allowedOrigins 应用程序属性。

4.5. 匿名访问

默认情况下,匿名访问是禁用的。通过应用程序属性 cuba.rest.anonymousEnabled 来启用。如果请求中没有包含 Authentication 信息头,这个请求会被认为是匿名的。匿名访问的情况下, SecurityContext 会包含匿名用户会话。

给匿名用户配置权限,需要为匿名用户配置响应的 角色 。匿名用户的登录名是由 cuba.anonymousLogin 应用程序属性指定。

4.6. 其它 REST API 设置

cuba.rest.client.id - 定义默认 REST API 客户端 id。

cuba.rest.client.secret - 定义默认 REST API 客户端密码。

cuba.rest.client.tokenExpirationTimeSec - 定义默认客户端 access token 的过期时间,单位为秒。

cuba.rest.client.refreshTokenExpirationTimeSec - 定义默认客户端 refresh token 的过期时间,单位为秒。

cuba.rest.client.authorizedGrantTypes - 默认客户端认证授权类型的列表。如果要禁用 refresh token,从列表中删除 refresh_token 项。

cuba.rest.maxUploadSize - 定义通过 REST API 能上传的文件的最大体积。

cuba.rest.reuseRefreshToken - 定义 refresh token 是否可以重复使用。

cuba.rest.requiresSecurityToken - 定义是否需要在 JSON 中传递额外的系统属性。细节参考 集合属性的安全约束

cuba.rest.tokenMaskingEnabled - 定义在系统日志中是否需要对 REST API 的 token 进行掩码处理。

4.7. 创建带 OAuth2 保护的自定义控制器

如果需要创建由 OAuth2 保护的自定义 REST 控制器,可以按照以下步骤:

  1. 假设有如下 REST 控制器:

    package com.company.test.portal.myapi;
    
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import com.company.test.services.SomeService;
    
    @RestController
    @RequestMapping("/myapi")
    public class MyController {
    
        @Inject
        protected SomeService someService;
    
        @GetMapping("/dosmth")
        public String doSmth() {
            return someService.getResult();
        }
    }
  2. web 或者 portal 模块包的根目录(com.company.test)创建一个新的 Spring 配置文件 rest-dispatcher-spring.xml。文件内容如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:security="http://www.springframework.org/schema/security">
    
        <!-- Define a base package for your controllers-->
        <context:component-scan base-package="com.company.test.portal.myapi"/>
    
        <security:http pattern="/rest/myapi/**"
                       create-session="stateless"
                       entry-point-ref="oauthAuthenticationEntryPoint"
                       xmlns="http://www.springframework.org/schema/security">
            <!-- Specify one or more protected URL patterns-->
            <intercept-url pattern="/rest/myapi/**" access="isAuthenticated()"/>
            <anonymous enabled="false"/>
            <csrf disabled="true"/>
            <cors configuration-source-ref="cuba_RestCorsSource"/>
            <custom-filter ref="resourceFilter" before="PRE_AUTH_FILTER"/>
            <custom-filter ref="cuba_AnonymousAuthenticationFilter" after="PRE_AUTH_FILTER"/>
        </security:http>
    </beans>
  3. 在模块的属性文件(比如 portal-app.properties)里定义一个 累加 属性 cuba.restSpringContextConfig

    cuba.restSpringContextConfig = +com/company/test/rest-dispatcher-spring.xml
  4. 这个新的控制器在 CubaRestApiServlet 上下文内运行。所以控制器内方法的 URL 以 /rest 开头,比如 doSmth() 方法可以通过 URL http://localhost:8080/app-portal/rest/myapi/dosmth 来访问。

    Warning

    自定义控制器的 URL 绝对不能/rest/v2 开头。

4.8. 集合属性的安全约束

考虑下面这个情况:

  • 数据模型包含 OrderOrderLine 实体,形成了一对多的关系。

  • REST 客户端获取了一个 Order 实例,并且带着关联的 OrderLine 实例集合。

  • 但是由于有 安全约束 ,导致会过滤掉某些 OrderLine 实例,从而客户端不会加载这些实例,也不知道这些实例的存在。比方说,line5 不会被加载,但是存在于数据库。

  • 如果客户端从集合中删除了一条,比如说 line2,然后将整个实例组合通过 /entities/{entityName}/{entityId} endpoint 进行保存,会有两种可能的输出:

    1. 如果这个约束从实体被加载之后没有更改过,框架会首先在 OrderLine 集合里面恢复被过滤的 line5 实例然后只删除 line2,这个是正确的行为。

    2. 但是如果这个约束在什么时候更改了,导致 line5 能被用户看见,此时,框架就不会正确的在集合内恢复这些被过滤的实例(因为从目前的约束状态看,line5 应该被客户加载过)。结果导致 line2line5 都会被删除。

如果对上面描述的场景有担忧,可以通过此方法排除数据丢失的隐患:在表示实体的 JSON 中发送一个特殊的系统属性。这个属性被称为 __securityToken 并且会自动包含在返回的 JSON 中,如果 cuba.rest.requiresSecurityToken 设置成 true 的话。REST 客户端的责任就是在保存实体的时候将这个属性带入并返回。

实体 JSON 包含 security token 的示例:

{
  "id": "fa430b56-ceb2-150f-6a85-12c691908bd1",
  "number": "OR-000001",
  "items": [
    {
      "id": "82e6e6d2-be97-c81c-c58d-5e2760ae095a",
      "description": "Item 1"
    },
    {
      "id": "988a8cb5-d61a-e493-c401-f717dd9a2d66",
      "description": "Item 2"
    }
  ],
  "__securityToken": "0NXc6bQh+vZuXE4Fsk4mJX4QnhS3lOBfxzUniltchpxPfi1rZ5htEmekfV60sbEuWUykbDoY+rCxdhzORaYQNQ=="
}

__securityToken 属性包含了被过滤的实例标识符的加密串,从而使得框架总是能恢复需要的信息而不管约束怎么改变。

4.9. token 存储持久化

默认情况下,OAuth token 只保存在内存。如果也需要在数据库保存这些 token,设置 cuba.rest.storeTokensInDb 应用程序属性为 true。这个应用程序属性的值是保存在数据库的,因此可以在系统界面 Administration > Application Properties 修改这个值。

数据库中保存的过期 token 需要定期清理。这个定时任务的时间安排 cron 表达式通过 cuba.rest.deleteExpiredTokensCron 定义。

4.10. 项目特定的 Swagger 文档

REST API 通用接口文档可以通过 http://files.cuba-platform.com/swagger/7.2 获得。

任何运行的 CUBA 应用程序还有项目特定的接口文档,文档结构按照 Swagger specification version 2.0 生成。

文档可以通过下面这些 URL 获得:

  • /rest/v2/docs/swagger.yaml - YAML 版本的通用接口文档

  • /rest/v2/docs/swagger.json - JSON 版本的通用接口文档

  • /rest/v2/docs/swaggerDetailed.yaml - YAML 版本的项目特定 Swagger 接口文档

  • /rest/v2/docs/swaggerDetailed.json - JSON 版本的项目特定 Swagger 接口文档

示例:

http://localhost:8080/app/rest/v2/docs/swagger.yaml
http://localhost:8080/app/rest/v2/docs/swaggerDetailed.yaml

接口文档可以用来查看,测试或者生成 REST API 的客户端代码。参考以下工具: Swagger UISwagger InspectorPostmanSwagger Codegen

生成的文档包括:

  1. CRUD 操作,比如:

    所有 CRUD 的参数和响应都有对应的模型,示例:

    swagger crud model
  2. 预定义的 REST 查询

    swagger query
  3. 暴露的 services 接口:

    swagger service

5. 使用 REST API

本节包含 REST API 的用法示例。

Tip

关于 API endpoint 的详细文档,请参阅 http://files.cuba-platform.com/swagger/7.2

需要保证用户具有使用 REST API 的角色。

5.1. 获取 OAuth token

任何 REST API 方法都需要 OAuth token(除了使用匿名访问)。可以通过 POST 方式获取 token:

http://localhost:8080/app/rest/v2/oauth/token

该 endpoint 使用基本身份验证进行访问保护,即 REST API 客户端 ID 和密码。请注意,这里的客户端 ID 和密码不是应用程序的用户登录名和密码。客户端 ID 和密码在添加 REST API 插件后生成。可以在 web-app.properties 中查看或修改,通过 cuba.rest.client.idcuba.rest.client.secret 应用程序属性。必须在 Authorization 请求头中以 base64 编码的字符串传递客户端 ID 和密码,客户端 ID 和密码使用冒号(":")分隔。

请求类型必须是 application/x-www-form-urlencoded,编码方式为 UTF-8

请求必须包含以下参数:

  • grant_type - password

  • username - 应用程序用户登录名。

  • password - 应用程序用户密码。

比如,对于如下参数

cuba.rest.client.id=client
cuba.rest.client.secret={noop}secret

token 请求是这样:

POST /oauth/token
Authorization: Basic Y2xpZW50OnNlY3JldA==
Content-Type: application/x-www-form-urlencoded

grant_type=password&username=smith&password=qwerty123

也可以使用 cURL

curl -H "Content-type: application/x-www-form-urlencoded" -H "Authorization: Basic Y2xpZW50OnNlY3JldA==" -d "grant_type=password&username=admin&password=admin" http://localhost:8080/app/rest/v2/oauth/token

方法返回一个 JSON 对象:

{
  "access_token": "29bc6b45-83cd-4050-8c7a-2a8a60adf251",
  "token_type": "bearer",
  "refresh_token": "e765446f-d49e-4634-a6d3-2d0583a0e7ea",
  "expires_in": 43198,
  "scope": "rest-api"
}

access token 值在 access_token 属性中。

要使用 access token,将其放在带有 Bearer 类型的 Authorization 请求头中,例如:

Authorization: Bearer 29bc6b45-83cd-4050-8c7a-2a8a60adf251

refresh_token 属性包含 refresh token 值。Refresh token 不能像 access token 那样用于访问受保护的资源,但它具有比 access token 更长的生命周期,并且可以用于在当前的 access token 到期时获取新的access token。

使用 refresh token 获取新 access token 的请求必须包含以下参数:

  • grant_type - refresh_token

  • refresh_token - refresh token 值

POST /oauth/token
Authorization: Basic Y2xpZW50OnNlY3JldA==
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token&refresh_token=e765446f-d49e-4634-a6d3-2d0583a0e7ea

另请参阅以下与 token 相关的应用程序属性:

5.2. 使用 LDAP 进行 REST API 身份验证

可以使用以下属性启用 REST 的 LDAP 身份验证:

  • cuba.rest.ldap.enabled - 是否启用 LDAP 身份验证。

  • cuba.rest.ldap.urls – LDAP 服务器 URL。

  • cuba.rest.ldap.base – 用户搜索的基准 DN。

  • cuba.rest.ldap.user – 有权从目录中读取信息的系统用户的专有名称。

  • cuba.rest.ldap.passwordcuba.web.ldap.user 属性中定义的系统用户的密码。

  • cuba.rest.ldap.userLoginField - 用于匹配登录名的 LDAP 用户属性的名称。默认情况下是 sAMAccountName(适用于 Active Directory)。

文件 local.app.properties 示例:

cuba.rest.ldap.enabled = true
cuba.rest.ldap.urls = ldap://192.168.1.1:389
cuba.rest.ldap.base = ou=Employees,dc=mycompany,dc=com
cuba.rest.ldap.user = cn=System User,ou=Employees,dc=mycompany,dc=com
cuba.rest.ldap.password = system_user_password

可以使用以下 endpoint 获取 OAuth token:

http://localhost:8080/app/rest/v2/ldap/token

该 endpoint 使用基本身份验证进行访问保护,即 REST API 客户端 ID 和密码。请注意,这里的客户端 ID 和密码不是应用程序的用户登录名和密码。客户端 ID 和密码在添加 REST API 插件后生成。可以在 web-app.properties 中查看或修改,通过 cuba.rest.client.idcuba.rest.client.secret 应用程序属性。必须在 Authorization 请求头中以 base64 编码的字符串传递客户端 ID 和密码,客户端 ID 和密码使用冒号(":")分隔。

请求参数与标准验证请求的参数一样:

  • grant_type - password

  • username - 应用程序用户登录名。

  • password - 应用程序用户密码。

请求类型必须是 application/x-www-form-urlencoded,编码方式为 UTF-8

另外,也可以禁用使用登录名和密码进行的标准验证:

cuba.rest.standardAuthenticationEnabled = false

5.3. 自定义验证

身份验证机制可以通过密钥、链接、LDAP 登录名和密码等提供 access token。REST API 使用特有的身份验证机制,无法被修改。要使用自定义身份验证过程,需要创建 REST controller 并使用其 URL。

下面我们看看自定义身份验证机制,该机制可以通过推广码获取 OAuth token。在下面的示例中,我们将使用包含带有 code 属性的 Coupon 实体的示例应用程序。我们将此属性的值作为 GET 请求中的身份验证参数发送。

  1. 创建一个带有 code 属性的 Coupon 实体:

    @Column(name = "CODE", unique = true, length = 4)
    protected String code;
  2. 创建一个带有 cuba.restApi.enabled 特殊权限的角色并为角色赋予读取所需实体和属性的权限。

  3. 使用 promo-user 登录名创建一个 用户 ,这个用户将会进行验证。

  4. promo-user 用户分配刚创建的角色。

  5. web 模块的根包(com.company.demo)下创建一个新的名为 rest-dispatcher-spring.xml 的 Spring 配置文件。文件内容必须如下:

    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
    
        <context:component-scan base-package="com.company.demo.web.rest"/>
    
    </beans>
  6. 将文件添加至 modules/web/src/web-app.properties 文件中的 cuba.restSpringContextConfig 应用程序属性中:

    cuba.restSpringContextConfig = +com/company/demo/rest-dispatcher-spring.xml
  7. web 模块的根包下创建 rest 包,并在其中实现自定义的 Spring MVC controller。在自定义身份验证后,使用 OAuthTokenIssuer bean 为用户生成并发送 REST API token:

    @RestController
    @RequestMapping("auth-code")
    public class AuthCodeController {
    
        @Inject
        private OAuthTokenIssuer oAuthTokenIssuer;
        @Inject
        private Configuration configuration;
        @Inject
        private DataManager dataManager;
        @Inject
        private MessageTools messageTools;
        @Inject
        private TrustedClientService trustedClientService;
    
        // here we check secret code and issue token using OAuthTokenIssuer
        @RequestMapping(method = RequestMethod.GET)
        public ResponseEntity get(@RequestParam("code") String authCode) {
            // obtain system session to be able to call middleware services
            WebAuthConfig webAuthConfig = configuration.getConfig(WebAuthConfig.class);
            UserSession systemSession;
            try {
                systemSession = trustedClientService.getSystemSession(webAuthConfig.getTrustedClientPassword());
            } catch (LoginException e) {
                throw new RuntimeException("Error during system auth");
            }
    
            // set security context
            AppContext.setSecurityContext(new SecurityContext(systemSession));
            try {
                // find coupon with code
                LoadContext<Coupon> loadContext = LoadContext.create(Coupon.class)
                        .setQuery(LoadContext.createQuery("select c from demo_Coupon c where c.code = :code")
                                .setParameter("code", authCode));
    
                if (dataManager.load(loadContext) == null) {
                    // if coupon is not found - code is incorrect
                    return new ResponseEntity<>(new ErrorInfo("invalid_grant", "Bad credentials"), HttpStatus.BAD_REQUEST);
                }
    
                // generate token for "promo-user"
                OAuthTokenIssuer.OAuth2AccessTokenResult tokenResult =
                        oAuthTokenIssuer.issueToken("promo-user", messageTools.getDefaultLocale(), Collections.emptyMap());
                OAuth2AccessToken accessToken = tokenResult.getAccessToken();
    
                // set security HTTP headers to prevent browser caching of security token
                HttpHeaders headers = new HttpHeaders();
                headers.set(HttpHeaders.CACHE_CONTROL, "no-store");
                headers.set(HttpHeaders.PRAGMA, "no-cache");
                return new ResponseEntity<>(accessToken, headers, HttpStatus.OK);
            } finally {
                // clean up security context
                AppContext.setSecurityContext(null);
            }
        }
    
        // POJO for JSON error messages
        public static class ErrorInfo implements Serializable {
            private String error;
            private String error_description;
    
            public ErrorInfo(String error, String error_description) {
                this.error = error;
                this.error_description = error_description;
            }
    
            public String getError() {
                return error;
            }
    
            public String getError_description() {
                return error_description;
            }
        }
    }
  8. web/core 模块的扫描中排除 rest 包:OAuthTokenIssuer bean 仅在 REST API 上下文中可用,在应用程序上下文中扫描它会导致错误。

    <context:component-scan base-package="com.company.demo">
        <context:exclude-filter type="regex" expression="com\.company\.demo\.web\.rest\..*"/>
    </context:component-scan>
  9. 现在,用户将能够使用带有 code 参数的 GET HTTP 请求获取 OAuth2 access code

    http://localhost:8080/app/rest/auth-code?code=A325

    结果将是:

    {"access_token":"74202587-6c2b-4d74-bcf2-0d687ea85dca","token_type":"bearer","expires_in":43199,"scope":"rest-api"}

    然后,应将获得的 access token 传递给 REST API,如文档中所述。

5.3.1. REST API 中的社交账号登录

社交账号登录的机制也可以在 REST API 中使用。完整的示例应用程序可以在 GitHub 上找到,同时在 社交登录 部分有详细描述,下面是使用 Facebook 帐户获取访问 token 的关键技术点。

  1. web 模块的根包下创建 restapi 包,并在其中实现自定义 Spring MVC 控制器。该控制器应包含两个主要方法:get() 方法获取 ResponseEntity 实例,login() 方法获取 OAuth token。

    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity get() {
        String loginUrl = getAsPrivilegedUser(() ->
                facebookService.getLoginUrl(getAppUrl(), OAuth2ResponseType.CODE_TOKEN)
        );
    
        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.LOCATION, loginUrl);
        return new ResponseEntity<>(headers, HttpStatus.FOUND);
    }

    在这里,我们检查 Facebook code,获取 access code 并使用 OAuthTokenIssuer 发出 access token:

    @RequestMapping(method = RequestMethod.POST, value = "login")
    public ResponseEntity<OAuth2AccessToken> login(@RequestParam("code") String code) {
        User user = getAsPrivilegedUser(() -> {
            FacebookUserData userData = facebookService.getUserData(getAppUrl(), code);
    
            return socialRegistrationService.findOrRegisterUser(
                userData.getId(), userData.getEmail(), userData.getName());
        });
    
        OAuth2AccessTokenResult tokenResult = oAuthTokenIssuer.issueToken(user.getLogin(),
                messageTools.getDefaultLocale(), Collections.emptyMap());
    
        HttpHeaders headers = new HttpHeaders();
        headers.set(HttpHeaders.CACHE_CONTROL, "no-store");
        headers.set(HttpHeaders.PRAGMA, "no-cache");
        return new ResponseEntity<>(tokenResult.getAccessToken(), headers, HttpStatus.OK);
    }
  2. web/core 模块扫描中排除 restapi 包:OAuthTokenIssuer bean 仅在 REST API 上下文中可用,在应用程序上下文中对其进行扫描将导致错误。

    <context:component-scan base-package="com.company.demo">
        <context:exclude-filter type="regex" expression="com\.company\.demo\.restapi\..*"/>
    </context:component-scan>
  3. 在项目的 modules/web/web/VAADIN 文件夹中创建 facebook-login-demo.html 文件,包含在 HTML 页面上运行的 JavaScript 代码:

    <html>
    <head>
        <title>Facebook login demo with REST-API</title>
        <script src="jquery-3.2.1.min.js"></script>
        <style type="text/css">
     #users { display: none; }
        </style>
    </head>
    <body>
    <h1>Facebook login demo with REST-API</h1>
    
    <script type="application/javascript"...>
    </script>
    
    <a id="fbLink" href="/app/rest/facebook">Login with Facebook</a>
    
    <div id="users">
        You are logged in!
    
        <h1>Users</h1>
    
        <div id="usersList">
        </div>
    </div>
    
    </body>
    </html>

    以下脚本将尝试使用 Facebook 登录。首先,它将从 URL 中删除 code 参数,然后它将 code 传递给 REST API 以获取 OAuth access token,验证成功后,将能够正常加载和保存数据。

    var oauth2Token = null;
    
    function tryToLoginWithFacebook() {
        var urlHash = window.location.hash;
    
        if (urlHash && urlHash.indexOf('&code=') >= 0) {
            console.log("Try to login to CUBA REST-API!");
    
            var urlCode = urlHash.substring(urlHash.indexOf('&code=') + '&code='.length);
            console.log("Facebook code: " + urlCode);
    
            history.pushState("", document.title, window.location.pathname);
    
            $.post({
                url: '/app/rest/facebook/login',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                },
                dataType: 'json',
                data: {code: urlCode},
                success: function (data) {
                    oauth2Token = data.access_token;
    
                    loadUsers();
                }
            })
        }
    }
    
    function loadUsers() {
        $.get({
            url: '/app/rest/v2/entities/sec$User?view=_local',
            headers: {
                'Authorization': 'Bearer ' + oauth2Token,
                'Content-Type': 'application/x-www-form-urlencoded'
            },
            success: function (data) {
                $('#fbLink').hide();
                $('#users').show();
    
                $.each(data, function (i, user) {
                    $('#usersList').append("<li>" + user.name + " (" + user.email + ")</li>");
                });
            }
        });
    }
    
    tryToLoginWithFacebook();

    另一个示例或在 CUBA 应用程序中运行 JavaScript 代码,可以在 JavaScript 用法示例 部分找到。

5.4. 获取实体实例列表

假设系统有一个 sales$Order 实体,我们需要得到这个实体实例的列表。还有,我们需要得到的不是所有记录,而只是从第 100 条记录开始的 50 条记录。响应必须不仅包含 sales$Order 实体的简单属性,还包含有关客户的信息(名为 customer 的引用字段)。订单必须按日期排序。

获取 sales$Order 实体的所有实例的基本 URL 如下:

http://localhost:8080/app/rest/v2/entities/sales$Order

要实现上述所有条件,必须指定以下请求参数:

  • view - 一个用于加载实体的 视图 。在我们的例子中,order-edit-view 包含一个 customer 引用。

  • limit - 要返回的实体数。

  • offset - 第一个被提取记录的位置。

  • sort - 将用于排序的实体属性名称。

必须将 OAuth token 放在带有 Bearer 类型的 Authorization 请求头中:

Authorization: Bearer 29bc6b45-83cd-4050-8c7a-2a8a60adf251

因此,我们可以写出以下 GET 请求 URL:

http://localhost:8080/app/rest/v2/entities/sales$Order?view=order-edit-view&limit=50&offset=100&sort=date

使用 cURL,请求将如下所示:

curl -H "Authorization: Bearer d335902c-9cb4-455e-bf92-24ca1d66d72f" http://localhost:8080/app/rest/v2/entities/sales$Order?view=order-edit&limit=50&offset=100&sort=date

响应内容:

[
  {
    "_entityName": "sales$Order",
    "_instanceName": "00001",
    "id": "46322d73-2374-1d65-a5f2-160461da22bf",
    "date": "2016-10-31",
    "description": "Vacation order",
    "number": "00001",
    "items": [
      {
        "_entityName": "sales$OrderItem",
        "_instanceName": "Beach umbrella",
        "id": "95a04f46-af7a-a307-de4e-f2d73cfc74f7",
        "price": 23,
        "name": "Beach umbrella"
      },
      {
        "_entityName": "sales$OrderItem",
        "_instanceName": "Sun lotion",
        "id": "a2129675-d158-9e3a-5496-41bf1a315917",
        "price": 9.9,
        "name": "Sun lotion"
      }
    ],
    "customer": {
      "_entityName": "sales$Customer",
      "_instanceName": "Toby Burns",
      "id": "4aa9a9d8-01df-c8df-34c8-c385b566ea05",
      "firstName": "Toby",
      "lastName": "Burns"
    }
  },
  {
    "_entityName": "sales$Order",
    "_instanceName": "00002",
    "id": "b2ad3059-384c-3e03-b62d-b8c76621b4a8",
    "date": "2016-12-31",
    "description": "New Year party set",
    "number": "00002",
    "items": [
      {
        "_entityName": "sales$OrderItem",
        "_instanceName": "Jack Daniels",
        "id": "0c566c9d-7078-4567-a85b-c67a44f9d5fe",
        "price": 50.7,
        "name": "Jack Daniels"
      },
      {
        "_entityName": "sales$OrderItem",
        "_instanceName": "Hennessy X.O",
        "id": "c01be87b-3f91-7a86-50b5-30f2f0a49127",
        "price": 79.9,
        "name": "Hennessy X.O"
      }
    ],
    "customer": {
      "_entityName": "sales$Customer",
      "_instanceName": "Morgan Collins",
      "id": "5d111245-2ed0-abec-3bee-1a196da92e3e",
      "firstName": "Morgan",
      "lastName": "Collins"
    }
  }
]

请注意,响应中的每个实体都有一个带有实体名称的 _entityName 属性和一个带有实体 实例名_instanceName 属性。

5.5. 新建实体实例

可以使用 POST 请求地址创建新的 sales$Order 实体实例:

http://localhost:8080/app/rest/v2/entities/sales$Order

必须将 OAuth token 放在带有 Bearer 类型的 Authorization 请求头中。

请求体必须包含一个描述新实体实例的 JSON 对象,例如:

{
  "number": "00017",
  "date": "2016-09-01",
  "description": "Back to school",
  "items": [
    {
      "_entityName": "sales$OrderItem",
      "price": 100,
      "name": "School bag"
    },
    {
      "_entityName": "sales$OrderItem",
      "price": 9.90,
      "name": "Pencils"
    }
  ],
  "customer": {
    "id": "4aa9a9d8-01df-c8df-34c8-c385b566ea05"
  }
}

以下是 cURL POST 请求的示例,该请求创建了一个新的 Order 实例:

curl -H "Authorization: Bearer d335902c-9cb4-455e-bf92-24ca1d66d72f" -H "Content-Type: application/json" -X POST -d "{\"date\": \"2018-10-12 15:47:28\", \"amount\":  9.90, \"customer\": {\"id\": \"383ebce2-b295-7378-36a1-bcf93693821f\"}}" http://localhost:8080/app/rest/v2/entities/sales$Order

订单项(items)集合和 customer 的引用需要通过请求体传递。我们来看看如何处理这些属性。

首先,我们快速浏览一下 Order 类:

package com.company.sales.entity;

import com.haulmont.chile.core.annotations.Composition;
import com.haulmont.chile.core.annotations.NamePattern;
import com.haulmont.cuba.core.entity.StandardEntity;
import com.haulmont.cuba.core.entity.annotation.OnDelete;
import com.haulmont.cuba.core.global.DeletePolicy;

import javax.persistence.*;
import java.util.Date;
import java.util.Set;

@NamePattern("%s|number")
@Table(name = "SALES_ORDER")
@Entity(name = "sales$Order")
public class Order extends StandardEntity {
    private static final long serialVersionUID = 7565070704618724997L;

    @Column(name = "NUMBER_")
    protected String number;

    @Temporal(TemporalType.DATE)
    @Column(name = "DATE_")
    protected Date date;

    @Column(name = "DESCRIPTION")
    protected String description;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CUSTOMER_ID")
    protected Customer customer;

    @Composition
    @OnDelete(DeletePolicy.CASCADE)
    @OneToMany(mappedBy = "order")
    protected Set<OrderItem> items;

    //getters and setters omitted
}

items 集合属性使用 @Composition 注解。用于实体创建和更新的 REST API 方法将为此类集合的所有成员创建新的实体实例。在我们的例子中,将使用 Order 实体创建两个 OrderItem 实体实例。

customer 引用没有 @Composition 注解,这也是为什么 REST API 会尝试查找具有给定 id 的客户并将其设置给 customer 字段。如果未找到客户,则不会创建订单,并且该方法将返回错误。

在成功执行方法的情况下,返回所创建实体的完整对象关系图:

{
  "_entityName": "sales$Order",
  "id": "5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50",
  "date": "2016-09-01",
  "description": "Back to school",
  "version": 1,
  "number": "00017",
  "createdBy": "admin",
  "createTs": "2016-10-13 18:12:21.047",
  "updateTs": "2016-10-13 18:12:21.047",
  "items": [
    {
      "_entityName": "sales$OrderItem",
      "id": "3158b8ed-7b7a-568e-aec5-0822c3ebbc24",
      "createdBy": "admin",
      "price": 9.9,
      "name": "Pencils",
      "createTs": "2016-10-13 18:12:21.047",
      "version": 1,
      "updateTs": "2016-10-13 18:12:21.047",
      "order": {
        "_entityName": "sales$Order",
        "id": "5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50"
      }
    },
    {
      "_entityName": "sales$OrderItem",
      "id": "72774b8b-4fea-6403-7b52-4a6a749215fc",
      "createdBy": "admin",
      "price": 100,
      "name": "School bag",
      "createTs": "2016-10-13 18:12:21.047",
      "version": 1,
      "updateTs": "2016-10-13 18:12:21.047",
      "order": {
        "_entityName": "sales$Order",
        "id": "5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50"
      }
    }
  ],
  "customer": {
    "_entityName": "sales$Customer",
    "id": "4aa9a9d8-01df-c8df-34c8-c385b566ea05",
    "firstName": "Toby",
    "lastName": "Burns",
    "createdBy": "admin",
    "createTs": "2016-10-13 15:32:01.657",
    "version": 1,
    "updateTs": "2016-10-13 15:32:01.657"
  }
}

5.6. 更新现有实体实例

可以使用 PUT 请求地址更新现有的 sales$Order 实体实例:

http://localhost:8080/app/rest/v2/entities/sales$Order/5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50

这里 URL 的最后一部分是实体标识符。

必须将 OAuth token 放在带有 Bearer 类型的 Authorization 请求头中。

请求体必须包含一个只包含要更新的字段的 JSON 对象,例如:

{
  "date": "2017-10-01",
  "customer" : {
    "id" : "5d111245-2ed0-abec-3bee-1a196da92e3e"
  }
}

响应体将包含一个修改过的实体:

{
  "_entityName": "sales$Order",
  "id": "5d7ff8e3-7828-ba94-d6ba-155c5c4f2a50",
  "date": "2017-10-01",
  "updatedBy": "admin",
  "description": "Back to school",
  "version": 2,
  "number": "00017",
  "createdBy": "admin",
  "createTs": "2016-10-13 18:12:21.047",
  "updateTs": "2016-10-13 19:13:02.656",
  "customer": {
    "_entityName": "sales$Customer",
    "id": "5d111245-2ed0-abec-3bee-1a196da92e3e",
    "firstName": "Morgan",
    "lastName": "Collins",
    "createdBy": "admin",
    "createTs": "2016-10-13 15:31:27.821",
    "version": 1,
    "updateTs": "2016-10-13 15:31:27.821",
    "email": "collins@gmail.com"
  }
}
Tip

上面的 JSON 对象提供了 version 属性,意味着 sales$Order 实体是有 版本 的,并且支持乐观锁机制。如需启动乐观锁机制,可以设置 cuba.rest.optimisticLockingEnabled 应用程序为 true。注意,如果 JSON 中没有指定版本,则乐观锁不会起作用。

5.7. 执行 JPQL 查询(GET)

在使用 REST API 执行查询之前,查询语句必须在配置文件中进行描述。需要在 web 模块的主包中创建 rest-queries.xml 文件(例如 com.company.sales)。然后,必须在 web 模块的应用程序属性文件(web-app.properties)中定义该文件。

cuba.rest.queriesConfig = +com/company/sales/rest-queries.xml

rest-queries.xml 内容:

<?xml version="1.0"?>
<queries xmlns="http://schemas.haulmont.com/cuba/rest-queries.xsd">
    <query name="ordersAfterDate" entity="sales$Order" view="order-edit-view">
        <jpql><![CDATA[select o from sales$Order o where o.date >= :startDate and o.date <= :endDate]]></jpql>
        <params>
            <param name="startDate" type="java.util.Date"/>
            <param name="endDate" type="java.util.Date"/>
        </params>
    </query>
</queries>

要执行 JPQL 查询,必须执行以下 GET 请求:

http://localhost:8080/app/rest/v2/queries/sales$Order/ordersAfterDate?startDate=2016-11-01&endDate=2017-11-01

请求 URL 部分:

  • sales$Order - 提取的实体名称。

  • ordersAfterDate - 配置文件中的查询名称。

  • startDateendDate - 带有具体值的请求参数。

参数值必须按照对应 datatype 的格式传入,示例:

  • 如果查询参数的类型是 java.util.Date,则值的格式从 DateTimeDatatype 获取,默认格式是 yyyy-MM-dd HH:mm:ss.SSS

  • 对于 java.sql.Date 查询参数类型,值的格式从 DateDatatype 获取,默认格式是 yyyy-MM-dd

  • 对于 java.sql.Time 查询参数类型,值的格式从 TimeDatatype 获取,默认格式是 HH:mm:ss

必须将 OAuth token 放在带有 Bearer 类型的 Authorization 请求头中。

该方法返回提取的实体实例的 JSON 数组:

[
  {
    "_entityName": "sales$Order",
    "_instanceName": "00002",
    "id": "b2ad3059-384c-3e03-b62d-b8c76621b4a8",
    "date": "2016-12-31",
    "description": "New Year party set",
    "number": "00002",
    "items": [
      {
        "_entityName": "sales$OrderItem",
        "_instanceName": "Jack Daniels",
        "id": "0c566c9d-7078-4567-a85b-c67a44f9d5fe",
        "price": 50.7,
        "name": "Jack Daniels"
      },
      {
        "_entityName": "sales$OrderItem",
        "_instanceName": "Hennessy X.O",
        "id": "c01be87b-3f91-7a86-50b5-30f2f0a49127",
        "price": 79.9,
        "name": "Hennessy X.O"
      }
    ],
    "customer": {
      "_entityName": "sales$Customer",
      "_instanceName": "Morgan Collins",
      "id": "5d111245-2ed0-abec-3bee-1a196da92e3e",
      "firstName": "Morgan",
      "lastName": "Collins"
    }
  }
]

Swagger 文档 中提供了可能的请求参数的完整列表。

5.8. 执行 JPQL 查询(POST)

也可以使用 POST HTTP 请求执行查询。特别是需要将集合作为查询参数值传递时,可以使用 POST 请求。此时,REST 查询配置文件中查询参数的类型必须以方括号结尾:java.lang.String[]java.util.UUID[] 等。

<?xml version="1.0"?>
<queries xmlns="http://schemas.haulmont.com/cuba/rest-queries.xsd">
    <query name="ordersByIds" entity="sales$Order" view="order-edit-view">
        <jpql><![CDATA[select o from sales$Order o where o.id in :ids and o.status = :status]]></jpql>
        <params>
            <param name="ids" type="java.util.UUID[]"/>
            <param name="status" type="java.lang.String"/>
        </params>
    </query>
</queries>

查询参数值必须作为 JSON map 在请求体中传递:

{
  "ids": ["c273fca1-33c2-0229-2a0c-78bc6d09110a", "e6c04c18-c8a1-b741-7363-a2d58589d800", "d268a4e1-f316-a7c8-7a96-87ba06afbbbd"],
  "status": "ready"
}

POST 请求 URL:

http://localhost:8080/app/rest/v2/queries/sales$Order/ordersByIds?returnCount=true

5.9. 调用服务方法(GET)

假设系统中存在 OrderService 服务 。实现如下:

package com.company.sales.service;

import com.haulmont.cuba.core.EntityManager;
import com.haulmont.cuba.core.Persistence;
import com.haulmont.cuba.core.Transaction;
import org.springframework.stereotype.Service;
import javax.inject.Inject;
import java.math.BigDecimal;

@Service(OrderService.NAME)
public class OrderServiceBean implements OrderService {

    @Inject
    private Persistence persistence;

    @Override
    public BigDecimal calculatePrice(String orderNumber) {
        BigDecimal orderPrice = null;
        try (Transaction tx = persistence.createTransaction()) {
            EntityManager em = persistence.getEntityManager();
            orderPrice = (BigDecimal) em.createQuery("select sum(oi.price) from sales$OrderItem oi where oi.order.number = :orderNumber")
                    .setParameter("orderNumber", orderNumber)
                    .getSingleResult();
            tx.commit();
        }

        return orderPrice;
    }
}

在使用 REST API 执行之前,必须在配置文件中允许服务方法通过 REST 调用。需要在 web 模块的主包中创建 rest-services.xml 文件(例如 com.company.sales)。然后,必须在 web 模块的应用程序属性文件(web-app.properties)中定义该文件。

cuba.rest.servicesConfig = +com/company/sales/rest-services.xml

rest-services.xml 内容:

<?xml version="1.0" encoding="UTF-8"?>
<services xmlns="http://schemas.haulmont.com/cuba/rest-services-v2.xsd">
    <service name="sales_OrderService">
        <method name="calculatePrice">
            <param name="orderNumber"/>
        </method>
    </service>
</services>

要调用该服务的方法,可以执行以下 GET 请求:

http://localhost:8080/app/rest/v2/services/sales_OrderService/calculatePrice?orderNumber=00001

请求 URL 部分:

  • sales_OrderService - 服务名。

  • calculatePrice - 方法名。

  • orderNumber - 带有具体值的参数名称。

必须将 OAuth token 放在带有 Bearer 类型的 Authorization 请求头中。

服务方法可以返回简单数据类型、实体、实体集合或可序列化 POJO 的结果。在我们的例子中返回 BigDecimal,因此响应体中只包含一个数字:

39.2

5.10. 调用服务方法(POST)

REST API 不仅可以执行具有简单数据类型参数的方法,还可以执行具有以下参数的方法:

  • 实体

  • 实体集合

  • 可序列化的 POJO

假设我们在上一节中创建的 OrderService 中添加一个新方法:

@Override
public OrderValidationResult validateOrder(Order order, Date validationDate){
    OrderValidationResult result=new OrderValidationResult();
    result.setSuccess(false);
    result.setErrorMessage("Validation of order "+order.getNumber()+" failed. validationDate parameter is: "+validationDate);
    return result;
}

OrderValidationResult 类如下所示:

package com.company.sales.service;

import java.io.Serializable;

public class OrderValidationResult implements Serializable {

    private boolean success;

    private String errorMessage;

    public boolean isSuccess() {
        return success;
    }

    public void setSuccess(boolean success) {
        this.success = success;
    }

    public String getErrorMessage() {
        return errorMessage;
    }

    public void setErrorMessage(String errorMessage) {
        this.errorMessage = errorMessage;
    }
}

新方法在参数列表中有一个 Order 实体,并返回一个 POJO。

在使用 REST API 调用之前该方法必须允许 REST 调用,因此我们添加一条记录到 rest-services.xml 配置文件中(在 调用服务方法(GET) 中描述过)。

<?xml version="1.0" encoding="UTF-8"?>
<services xmlns="http://schemas.haulmont.com/cuba/rest-services-v2.xsd">
    <service name="sales_OrderService">
        <method name="calculatePrice">
            <param name="orderNumber"/>
        </method>
        <method name="validateOrder">
            <param name="order"/>
            <param name="validationDate"/>
        </method>
    </service>
</services>

也可以使用以下地址以 POST 请求调用 validateOrder 服务的方法:

http://localhost:8080/app/rest/v2/services/sales_OrderService/validateOrder

如果 POST 请求参数在请求体中传递。请求体必须包含一个 JSON 对象,此对象的每个字段对应于服务的方法参数。

{
  "order" : {
    "number": "00050",
    "date" : "2016-01-01"
  },
  "validationDate": "2016-10-01"
}

参数值必须按照对应 datatype 的格式传入,示例:

  • 如果查询参数的类型是 java.util.Date,则值的格式从 DateTimeDatatype 获取,默认格式是 yyyy-MM-dd HH:mm:ss.SSS

  • 对于 java.sql.Date 查询参数类型,值的格式从 DateDatatype 获取,默认格式是 yyyy-MM-dd

  • 对于 java.sql.Time 查询参数类型,值的格式从 TimeDatatype 获取,默认格式是 HH:mm:ss

必须将 OAuth token 放在带有 Bearer 类型的 Authorization 请求头中。

REST API 方法返回可序列化的 POJO:

{
  "success": false,
  "errorMessage": "Validation of order 00050 failed. validationDate parameter is: 2016-10-01"
}

5.11. 文件下载

下载文件 时,在请求头中传递 security token 通常很不方便。用户更希望有一个用于下载的 URL,这个 URL 也可以设置给 img 标签的 src 属性。

作为该问题的解决方案,OAuth token还可以在请求 URL 中以 access_token 参数名传递。

例如,图片被上传到应用程序。其 FileDescriptor 实例的 id 为 44809679-e81c-e5ae-dd81-f56f223761d6

此时,下载图像的 URL 如下所示:

http://localhost:8080/app/rest/v2/files/44809679-e81c-e5ae-dd81-f56f223761d6?access_token=a2f0bb4e-773f-6b59-3450-3934cbf0a2d6

5.12. 文件上传

要上传文件,应该先获得一个 access token,在后续请求中要使用这个 token。

假设我们有下面这个文件上传的表单:

<form id="fileForm">
    <h2>Select a file:</h2>
    <input type="file" name="file" id="fileUpload"/>
    <br/>
    <button type="submit">Upload</button>
</form>

<h2>Result:</h2>
<img id="uploadedFile" src="" style="display: none"/>

我们将使用 jQuery 进行上传,并使用 data 获取返回的 JSON,其内容是为上传的文件新建的 FileDescriptor 实例。之后我们可以将 access token 作为参数通过其 FileDescriptor 的 id 访问上传的文件:

$('#fileForm').submit(function (e) {
    e.preventDefault();

    var file = $('#fileUpload')[0].files[0];
    var url = 'http://localhost:8080/app/rest/v2/files?name=' + file.name; // 文件名作为一个参数

    $.ajax({
        type: 'POST',
        url: url,
        headers: {
            'Authorization': 'Bearer ' + oauthToken // 添加 access token 头
        },
        processData: false,
        contentType: false,
        dataType: 'json',
        data: file,
        success: function (data) {
            alert('Upload successful');

            $('#uploadedFile').attr('src',
                'http://localhost:8080/app/rest/v2/files/' + data.id + '?access_token=' + oauthToken); // 更新 image url
            $('#uploadedFile').show();
        }
    });
});

5.13. JavaScript 用法示例

本节包含在 JavaScript 中使用 REST API v2 的示例,此处 JavaScript 运行在 HTML 页面中。该页面最初显示登录表单,登录成功后显示消息和实体列表。

为简单起见,我们将使用 modules/web/web/VAADIN 文件夹存储 HTML、CSS 和 JavaScript 文件,因为默认情况下,部署的 Web 应用程序的相应的文件夹用来提供静态资源。因此无需对 Tomcat 应用程序服务器进行任何配置。生成的 URL 将以 http://localhost:8080/app/VAADIN 开头,所以不要在真实环境的应用程序中使用此方法 - 而应该为静态资源创建一个具有特定上下文的单独 Web 应用程序。

下载 jQueryBootstrap 并复制到项目的 modules/web/web/VAADIN 文件夹。创建 customers.htmlcustomers.js 文件,文件夹的内容应如下所示:

bootstrap.min.css
customers.html
customers.js
jquery-3.1.1.min.js

customers.html 文件内容:

<html>
    <head>
        <script type="text/javascript" src="jquery-3.1.1.min.js"></script>
        <link rel="stylesheet" href="bootstrap.min.css"/>
    </head>
    <body>
        <div style="width: 300px; margin: auto;">
            <h1>Sales</h1>

            <div id="loggedInStatus" style="display: none" class="alert alert-success">
                Logged in successfully
            </div>
            <div id="loginForm">
                <div class="form-group">
                    <label for="loginField">Login:</label>
                    <input type="text" class="form-control" id="loginField">
                </div>
                <div class="form-group">
                    <label for="passwordField">Password:</label>
                    <input type="password" class="form-control" id="passwordField">
                </div>
                <button type="submit" class="btn btn-default" onclick="login()">Submit</button>
            </div>

            <div id="customers" style="display: none">
                <h2>Customers</h2>
                <ul id="customersList"></ul>
            </div>
        </div>
        <script type="text/javascript" src="customers.js"></script>
    </body>
</html>

customers.js 文件内容:

var oauthToken = null;
function login() {
    var userLogin = $('#loginField').val();
    var userPassword = $('#passwordField').val();
    $.post({
        url: 'http://localhost:8080/app/rest/v2/oauth/token',
        headers: {
            'Authorization': 'Basic Y2xpZW50OnNlY3JldA==',
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        dataType: 'json',
        data: {grant_type: 'password', username: userLogin, password: userPassword},
        success: function (data) {
            oauthToken = data.access_token;
            $('#loggedInStatus').show();
            $('#loginForm').hide();
            loadCustomers();
        }
    })
}

function loadCustomers() {
    $.get({
        url: 'http://localhost:8080/app/rest/v2/entities/sales$Customer?view=_local',
        headers: {
            'Authorization': 'Bearer ' + oauthToken,
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        success: function (data) {
            $('#customers').show();
            $.each(data, function (i, customer) {
                $('#customersList').append("<li>" + customer.name + " (" + customer.email + ")</li>");
            });
        }
    });
}

来自用户输入的登录名和密码通过 POST 请求发送到服务器,并在 Authorization 请求头中使用 Base64 编码的客户端凭证,如获取 OAuth token部分中所述。如果验证成功,则网页从服务器接收 access token 值,token 存储在 oauthToken 变量中,之后,隐藏 loginForm div 并显示 loggedInStatus div。

需要显示客户列表,发送请求到服务器去获取 sales$Customer 实体的实例,在 Authorization 请求头中传递 oauthToken 值。

如果成功处理请求,则显示 customers div,并且 customersList 元素展示包含客户名称和电子邮件的内容。

rest js 1
rest js 2

5.14. 获取本地化消息

REST API 中有一些方法可以获取实体,实体属性和枚举的本地化消息。

例如,要获取 sec$User 实体的本地化消息列表,必须执行以下 GET 请求:

http://localhost:8080/app/rest/v2/messages/entities/sec$User

必须将 OAuth token 放在带有 Bearer 类型的 Authorization 请求头中。

可以使用 Accept-Language http 请求头显式指定所需的区域设置。

响应如下:

{
  "sec$User": "User",
  "sec$User.active": "Active",
  "sec$User.changePasswordAtNextLogon": "Change Password at Next Logon",
  "sec$User.createTs": "Created At",
  "sec$User.createdBy": "Created By",
  "sec$User.deleteTs": "Deleted At",
  "sec$User.deletedBy": "Deleted By",
  "sec$User.email": "Email",
  "sec$User.firstName": "First Name",
  "sec$User.group": "Group",
  "sec$User.id": "ID",
  "sec$User.ipMask": "Permitted IP Mask",
  "sec$User.language": "Language",
  "sec$User.lastName": "Last Name",
  "sec$User.login": "Login",
  "sec$User.loginLowerCase": "Login",
  "sec$User.middleName": "Middle Name",
  "sec$User.name": "Name",
  "sec$User.password": "Password",
  "sec$User.position": "Position",
  "sec$User.substitutions": "Substitutions",
  "sec$User.timeZone": "Time Zone",
  "sec$User.timeZoneAuto": "Autodetect Time Zone",
  "sec$User.updateTs": "Updated At",
  "sec$User.updatedBy": "Updated By",
  "sec$User.userRoles": "User Roles",
  "sec$User.version": "Version"
}

要获取枚举的本地化值,请使用以下 URL:

http://localhost:8080/app/rest/v2/messages/enums/com.haulmont.cuba.security.entity.RoleType

如果省略 URL 中的实体名称或枚举名称部分,将获得所有实体或枚举的本地化值。

5.15. 数据模型版本化示例

实体属性被重命名

我们假设 sales$Order 实体的 oldNumber 属性被重命名为 newNumber 并且 date 被重命名为 deliveryDate。在这种情况下,转换配置将如下所示:

<?xml version="1.0"?>
<transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">
    <transformation modelVersion="1.0" currentEntityName="sales$Order">
        <renameAttribute oldName="oldNumber" currentName="newNumber"/>
        <renameAttribute oldName="date" currentName="deliveryDate"/>
    </transformation>
    ...
</transformations>

如果客户端应用程序需要使用旧版的 sales$Order 实体,那么它必须在 URL 参数中传递 modelVersion 值:

http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.0

将返回以下结果:

{
  "_entityName": "sales$Order",
  "_instanceName": "00001",
  "id": "46322d73-2374-1d65-a5f2-160461da22bf",
  "date": "2016-10-31",
  "description": "Vacation order",
  "oldNumber": "00001"
}

尽管 CUBA 应用程序中的实体具有 newNumberdeliveryDate 属性,但是响应 JSON 却包含 oldNumberdate 属性,

实体名称更改

接下来,试想一下,在应用程序的某个版本中,sales$Order 实体的名称也发生了变化。新名称是 sales$NewOrder

版本 1.1 的转换配置将如下所示:

<?xml version="1.0"?>
<transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">
    <transformation modelVersion="1.1" oldEntityName="sales$Order" currentEntityName="sales$NewOrder">
        <renameAttribute oldName="oldNumber" currentName="newNumber"/>
    </transformation>
    ...
</transformations>

除了上一个示例中的配置之外,还在此处添加了 oldEntityName 属性。它指定对模型版本 1.1 有效的实体名称。currentEntityName 属性指定当前实体名称。

虽然名称为 sales$Order 的实体不再存在,但以下请求也可以正常运行:

http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.1

REST API controller 会明白它必须在 sales$NewOrder 实体里进行搜索,并且在找到具有给定 id 的实体之后,在结果 JSON 中替换实体的名称和 newNumber 属性的名称:

{
  "_entityName": "sales$Order",
  "_instanceName": "00001",
  "id": "46322d73-2374-1d65-a5f2-160461da22bf",
  "date": "2016-10-31",
  "description": "Vacation order",
  "oldNumber": "00001"
}

客户端应用程序也可以使用旧版本的数据模型进行实体更新和创建。

POST 请求使用旧实体名称,并且在请求体使用旧的 JSON,这个 POST 请求可以正常工作:

http://localhost:8080/app/rest/v2/entities/sales$Order

{
  "_entityName": "sales$Order",
  "_instanceName": "00001",
  "id": "46322d73-2374-1d65-a5f2-160461da22bf",
  "date": "2016-10-31",
  "description": "Vacation order",
  "oldNumber": "00001"
}
必须从 JSON 中删除的实体属性

如果某个属性已添加到实体,但使用旧版数据模型的客户端不希望有此新属性,则可以从结果 JSON 中删除新属性。

此示例的转换配置如下所示:

<?xml version="1.0"?>
<transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">
    <transformation modelVersion="1.5" currentEntityName="sales$Order">
        <toVersion>
            <removeAttribute name="discount"/>
        </toVersion>
    </transformation>
    ...
</transformations>

此配置文件中的转换包含带有嵌套 removeAttribute 命令的 toVersion 标记。这意味着当执行从当前状态到特定版本的转换时(即当请求实体列表时),必须从结果 JSON 中删除 discount 属性。

在这种情况下,如果在没有 modelVersion 属性的情况下执行请求,discount 属性将会被返回:

http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93

{
    "_entityName": "sales$Order",
    "_instanceName": "00001",
    "id": "46322d73-2374-1d65-a5f2-160461da22bf",
    "deliveryDate": "2016-10-31",
    "description": "Vacation order",
    "number": "00001",
    "discount": 50
}

如果指定 modelVersion,则将删除 discount 属性

http://localhost:8080/app/rest/v2/entities/sales$Order/c838be0a-96d0-4ef4-a7c0-dff348347f93?modelVersion=1.1

{
    "_entityName": "sales$Order",
    "_instanceName": "00001",
    "id": "46322d73-2374-1d65-a5f2-160461da22bf",
    "deliveryDate": "2016-10-31",
    "description": "Vacation order",
    "oldNumber": "00001"
}
使用自定义转换器

还可以创建和注册自定义 JSON 转换器。举个例子,我们来看看下面的情况:有一个实体 sales$OldOrder 被重命名为 sales$NewOrder。该实体具有 orderDate 字段。在之前的版本中,此日期字段包含时间部分,但在最新版本的实体中,时间部分将被删除。请求旧模型版本 1.0 的实体的 REST API 客户端期望日期字段有时间部分,因此转换器必须修改 JSON 中的值。

首先,转换器配置必须如下所示:

<?xml version="1.0"?>
<transformations xmlns="http://schemas.haulmont.com/cuba/rest-json-transformations.xsd">

    <transformation modelVersion="1.0" oldEntityName="sales$OldOrder" currentEntityName="sales$NewOrder">
        <custom>
            <fromVersion transformerBeanRef="sales_OrderJsonTransformerFromVersion"/>
            <toVersion transformerBeanRef="sales_OrderJsonTransformerToVersion"/>
        </custom>
    </transformation>

    ...
</transformations>

有一个 custom 元素和嵌套的 toVersionfromVersion 元素。这些元素引用了转换器 bean。这意味着自定义转换器必须注册为 Spring bean。这里有一件重要的事情:自定义转换器可以使用 RestTransformations 平台 bean(如果需要,这个 bean 可以访问其它实体转换器)。但是 RestTransformations bean 是在 REST API servlet 的 Spring 上下文中注册,而不是在 Web 应用程序的主上下文中注册。这意味着自定义转换器 bean 也必须在 REST API Spring 上下文中注册。

我们怎样做到这一点?

首先,在 webportal 模块中创建一个 rest-dispatcher-spring.xml (例如,在 com.company.test 包中)。

接下来,在 Web 或 portal 模块的 app.properties 中注册此文件:

cuba.restSpringContextConfig = +com/company/test/rest-dispatcher-spring.xml

rest-dispatcher-spring.xml 必须包含自定义转换器 bean 定义:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">

    <bean name="sales_OrderJsonTransformerFromVersion" class="com.company.test.transformer.OrderJsonTransformerFromVersion"/>
    <bean name="sales_OrderJsonTransformerToVersion" class="com.company.test.transformer.OrderJsonTransformerToVersion"/>

</beans>

sales_OrderJsonTransformerToVersion 转换器的内容如下:

package com.company.test.transformer;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Strings;
import com.haulmont.restapi.transform.AbstractEntityJsonTransformer;
import com.haulmont.restapi.transform.JsonTransformationDirection;

public class OrderJsonTransformerToVersion extends AbstractEntityJsonTransformer {

    public OrderJsonTransformerToVersion() {
        super("sales$NewOrder", "sales$OldOrder", "1.0", JsonTransformationDirection.TO_VERSION);
    }

    @Override
    protected void doCustomTransformations(ObjectNode rootObjectNode, ObjectMapper objectMapper) {
        JsonNode orderDateNode = rootObjectNode.get("orderDate");
        if (orderDateNode != null) {
            String orderDateNodeValue = orderDateNode.asText();
            if (!Strings.isNullOrEmpty(orderDateNodeValue))
                rootObjectNode.put("orderDate", orderDateNodeValue + " 00:00:00.000");
        }
    }
}

此转换器在 JSON 对象中找到 orderDate 节点,并通过将时间部分添加到该值来修改该值。

当请求数据模型版本 1.0sales$OldOrder 实体时,结果 JSON 将包括含有时间部分的 orderDate 字段的实体,尽管它不再存储在数据库中。

再啰嗦几句关于定制转换器:它们必须实现 EntityJsonTransformer 接口。还可以继承 AbstractEntityJsonTransformer 类并覆盖其 doCustomTransformations 方法。AbstractEntityJsonTransformer 类包含标准转换器的所有功能。

5.16. 使用实体搜索过滤器

REST API 可以在获取实体列表时可以指定即时搜索条件。

假设我们有两个实体:

  • Author 有两个字段: lastNamefirstName

  • Book 有三个字段: title(String)、 author(Author) 和 publicationYear(Integer)

要使用条件执行搜索,必须使用以下 URL:

http://localhost:8080/app/rest/v2/entities/test$Book/search

搜索条件需要在 filter 参数中传递。这是一个包含一组条件的 JSON 对象。如果使用 GET 请求执行搜索,则需要在 URL 中传递 filter 参数。

例 1

我们需要找到 2007 年出版的所有书籍,并且有一个名字以"Alex"开头的作者。过滤器 JSON 应如下所示:

{
    "conditions": [
        {
            "property": "author.firstName",
            "operator": "startsWith",
            "value": "Alex"
        },
        {
            "property": "publicationDate",
            "operator": "=",
            "value": 2007
        }
    ]
}

默认情况下,搜索条件之间使用 AND 连接。

此示例还演示了对嵌套属性(author.firstName)的支持。

例 2

下一个示例演示了两件事:如何使用 POST 请求执行搜索以及如何使用 OR 分组条件。如果是 POST 请求,则必须在请求体的 JSON 对象中传递所有参数。搜索过滤器必须放在名为 filter 的对象字段中。所有其它参数(视图名称、限制条件等)必须放在相应名称的字段中:

{
  "filter": {
    "conditions": [
      {
        "group": "OR",
        "conditions": [
          {
            "property": "author.lastName",
            "operator": "contains",
            "value": "Stev"
          },
          {
            "property": "author.lastName",
            "operator": "=",
            "value": "Dumas"
          }
        ]
      },
      {
        "property": "publicationDate",
        "operator": "=",
        "in": [2007, 2008]
      }
    ]
  },
  "view": "book-view"
}

在此示例中,conditions 集合不仅包含条件对象,还包含 OR 分组条件。所以最终搜索条件将是:

((author.lastName contains Stev) OR (author.lastName = Duma) AND (publicationDate in [2007, 2008]))

请注意,view 参数也在请求体中传递。

6. REST API 中的安全机制

REST API 有其自己的 REST 安全范围 。您需要单独配置一组带有 cuba.restApi.enabled 特殊权限的 角色 。只有分配了这些角色的用户才能使用 REST API 登录系统。否则,用户没法以 REST 方式登录。

6.1. 遗留的安全隐患

如果您使用的是之前版本的 CUBA 或者迁移至 CUBA 7.2,则在您的系统中使用的是 旧版的角色和权限 。所以,如需启用 REST API,必须配置安全机制(角色、访问组),并且在生产系统也保持实际的权限状态,这样才能保护一些敏感数据。

如果开启了 REST API,您可以参考以下规则:

  • 为不需要使用 REST API 的用户始终禁用 REST 的访问权限。可以在角色编辑界面的 CUBA > REST API > Use REST API 特殊权限处配置。

  • 使用 “默认拒绝” 的安全策略:为具有 REST API 访问权限的用户分配 DENYING 角色。由于项目的数据模型总是在变,很容易忘记添加访问限制。

  • 为 REST API 用户配置并分配非公共实体的访问组限制。

  • 记住,EntityManager 不会强制使用访问组限制,所以在需要作为 REST API 的中间件方法中,尽量使用 DataManager

  • 记住,DENYING 角色并不默认带有实体属性限制。如果需要,则单独为每个实体配置实体属性权限。

  • 在生产环境使用唯一的 client secret

如果您的项目不是很容易配置完备的安全机制,则可以考虑实施自定义的 REST endpoints,而不要使用全局 REST API。

Appendix A: 应用程序属性

cuba.rest.allowedOrigins

定义可以访问 REST API 的源。以英文逗号分隔。

默认值: *

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.anonymousEnabled

对匿名用户启用 REST API 访问。

默认值: false

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.client.authorizedGrantTypes

为默认的 REST API 客户端定义支持的许可类型列表。需要禁用 refresh token,可以从列表中删除 refresh_token

默认值: password,external,refresh_token

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.client.id

定义 REST API 客户端的标识符。客户端,在这里,并不是说平台的用户,而是一个使用 REST API 的应用(某些 web portal 或者手机 app)。使用客户端用户名密码做访问 REST API token endpoint 的基本认证。

默认值: client

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.client.secret

定义 REST API 客户端的密码。客户端,在这里,并不是说平台的用户,而是一个使用 REST API 的应用(某些 web portal 或者手机 app)。使用客户端用户名密码做访问 REST API token endpoint 的基本认证。应用程序属性的值需要在具体的密码值(比如 secret)之前包含一个 PasswordEncoder 的标识符(比如 {noop})。

如果该密码用来做基础验证,则不需要使用 PasswordEncoder 标识符。

当添加 REST API 插件时自动生成。

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.client.tokenExpirationTimeSec

定义默认客户端 REST API access token 过期时间的时限,单位是秒。

默认值: 43200 (12 小时)

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.client.refreshTokenExpirationTimeSec

定义默认客户端 REST API refresh token 过期时间的时限,单位是秒。

默认值: 31536000 (365 天)

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.deleteExpiredTokensCron

指定用来做定时任务的表达式,从数据库删除过期的 token。

默认值: 0 0 3 * * ?

可以在 Middleware block 使用。

cuba.rest.jsonTransformationConfig

Additive 属性,用来定义 REST API 使用的 JSON 转换配置文件,这些文件在客户端请求一些特定版本数据模型的信息时会用到。

平台使用 资源接口 来加载此文件,所以这个文件可以放在 classpath 或者 配置目录

默认值: none

示例:

cuba.rest.jsonTransformationConfig = +com/company/sample/json-transformations.xml

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.maxUploadSize

可以使用 REST API 上传的文件大小的最大值,单位是字节。

默认值: 20971520 (20 Mb)

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.optimisticLockingEnabled

如果在 JSON 中提供了实体的 version 属性,启用 Versioned 实体的乐观锁。

默认值: false

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.requiresSecurityToken

如果是 true,会在加载的实体中包含一个特殊的系统属性,并且需要在保存实体的时候给 REST 接口传回一个同样的属性。细节参考 集合属性的安全约束

默认值: false

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.reuseRefreshToken

定义是否可以重复使用 refresh token。如果设置为 false,当使用 refresh token 获取 access token 的时候,系统会发出一个新的 refresh token,老的 refresh token 将会失效。

默认值: true

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.servicesConfig

累加 属性,用来定义包含应用 REST API 对应的 services 列表的文件。

平台使用 资源接口 来加载此文件,所以这个文件可以放在 classpath 或者 配置目录

默认值: none

示例:

cuba.rest.servicesConfig = +com/company/sample/app-rest-services.xml

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.storeTokensInDb

启用在数据库保存 REST API security token。默认情况下,令牌只是在内存中存储。

保存在数据库。

配置接口: ServerConfig

默认值: false

可以在 Middleware block 使用。

cuba.rest.syncTokenReplication

配置是否在集群中同步发送新创建的 token。默认情况,token 是异步发送至集群的。

保存在应用程序属性文件中。

配置接口: RestConfig

默认值: false

可以在 Middleware block 使用。

cuba.rest.tokenMaskingEnabled

设置是否在应用程序日志中对 REST API 进行掩码处理。

默认值: true

可以在 Web 客户端和 Web Portal blocks 使用。

cuba.rest.queriesConfig

累加 属性,用来定义包含应用程序 REST API 可用的 JPQL 查询列表的文件。

平台使用 资源接口 来加载此文件,所以这个文件可以放在 classpath 或者 配置目录

默认值: none

示例:

cuba.rest.queriesConfig = +com/company/sample/app-rest-queries.xml

可以在 Web 客户端和 Web Portal blocks 使用。

. . .