基于SAP BTP 创建CAP for Java 示例项目
使用 SAP BTP 试用版
申请使用账号地址
Tips :
如果没有权限 , 按照help.sap.com/docs/bas/sa…进行角色权限
创建 Dev Space
打开 SAP Business Application Studio 首页
在欢迎页面上,选择 Create Dev Space (创建开发空间)
输入 CAPTutorial
作为开发空间的名称,然后选择“全栈云应用程序”作为应用程序类型。继续创建开发人员空间
通过选择全栈云应用程序,你的空间附带了开发 CAP 应用程序所需的多个开箱即用的扩展。例如,内置了 CDS 工具。这样可以节省不必要的设置时间。创建开发空间需要几秒钟。
创建CAP 应用程序框架
转到Terminal → New Terminal.从主菜单中,选择“终端”→“新建终端”
现在应该在窗口底部打开一个终端窗口。在终端中,运行 cd projects
以转到 projects 目录
运行下面命令
mvn -B archetype:generate -DarchetypeArtifactId=cds-services-archetype -DarchetypeGroupId=com.sap.cds \
-DarchetypeVersion=RELEASE -DjdkVersion=17 \
-DgroupId=com.sap.cap -DartifactId=products-service -Dpackage=com.sap.cap.productsservice
进入构建好的项目:
- 该项目名为
products-service
db
文件夹存储与数据库相关的项目srv
文件夹存储 Java 应用程序
从主菜单中,选择“文件”→“打开文件夹”。
如果看到一条通知,询问是否要同步
Java classpath/configuration
,请选择 Always (始终)。如果目前对任何
pom.xml
文件有任何问题指示,请不要担心暂时忽略它们。
定义服务
CAP 应用程序使用 Core Data Services (CDS) 来描述:
右键单击该文件夹,然后选择 “新建文件”。srv
名称 : admin-service.cds
添加内容
service AdminService {
entity Products {
key ID : Integer;
title : String(111);
descr : String(1111);
}
}
编译
进入终端 输入pwd
, 进入项目目录,运行以下命令以触发 maven 构建过程:
mvn clean install
运行
在创建项目框架时,创建了应用程序文件,其中包含一个方法。是 Spring Boot 容器的启动类。
Application.java
main
Application.java
查看包结构
Application.java
com.sap.cap.productsservice
srv/src/main/java/com/sap/cap/productsservice
如果在 SAP Business Application Studio 中使用 CTRL+P,则会打开一个搜索栏。开始键入以查找并打开文件。Application.java
如你所见,该文件不包含特定于 CAP 的启动说明。这是每个Spring Boot应用程序中的典型样板代码。CAP Java 运行时的初始化由 Spring 根据 .
pom.xml
通过在终端中运行以下命令,转到项目的根目录:
cd ~/projects/products-service
通过在终端中运行以下命令来启动应用程序 :
mvn clean spring-boot:run
右下角将出现一条通知消息,指出“服务正在侦听端口 8080”。
点击Open in a New Tab
检查 OData 元数据
从欢迎页面中选择“$metadata
”以检查由 CAP Java 运行时自动提供的 OData 元数据。
odata/v4/AdminService/$metadata
添加自定义事件处理程序
为自定义事件处理程序创建 Java 类
创建 Java 包,创建一个名为handlers
在srv/src/main/java/com/sap/cap/productsservice
下的新文件夹。
创建AdminService.java
java类
package com.sap.cap.productsservice.handlers;
import java.util.HashMap;
import java.util.Map;
import org.springframework.stereotype.Component;
import com.sap.cds.services.cds.CdsCreateEventContext;
import com.sap.cds.services.cds.CdsReadEventContext;
import com.sap.cds.services.cds.CqnService;
import com.sap.cds.services.handler.EventHandler;
import com.sap.cds.services.handler.annotations.On;
import com.sap.cds.services.handler.annotations.ServiceName;
@Component
@ServiceName("AdminService")
public class AdminService implements EventHandler {
private Map<Object, Map<String, Object>> products = new HashMap<>();
@On(event = CqnService.EVENT_CREATE, entity = "AdminService.Products")
public void onCreate(CdsCreateEventContext context) {
context.getCqn().entries().forEach(e -> products.put(e.get("ID"), e));
context.setResult(context.getCqn().entries());
}
@On(event = CqnService.EVENT_READ, entity = "AdminService.Products")
public void onRead(CdsReadEventContext context) {
context.setResult(products.values());
}
}
管理“AdminService”服务中的“Products”实体。它处理创建和读取这些实体的事件,并将数据存储在一个内部映射中。
通过HTTP请求插入数据
尝试将一些数据插入到正在运行的应用程序中。例如,通过使用 SAP Business Application Studio 中捆绑的 HTTP 请求插件
在根目录中创建一个新文件。 requests.http
### Create Product
POST http://localhost:8080/odata/v4/AdminService/Products
Content-Type: application/json
{"ID": 42, "title": "My Tutorial Product", "descr": "You are doing an awesome job!"}
POST 请求会导致对服务的 AdminService
实体 Products 进行 OData 插入。内容的类型在 HTTP 请求的 Content-Type 标头中指定,实际请求的内容以 JSON 形式在请求正文中传递。
选择文件中请求上方的 Send Request。将在窗口右侧看到结果。
访问数据
从cap 欢迎页面访问数据
创建通用的服务
- 创建一个服务,稍后将在另一个 CAP Java 项目中重用该服务。
- 使用 CDS 建模时,最佳做法是将服务与模型分开。
-
转到的
db
文件夹并创建一个名为schema.cds
的文件-
将以下代码添加到新创建的
schema.cds
文件,并确保保存文件:-
namespace sap.capire.products; using { Currency, cuid, managed, sap.common.CodeList } from '@sap/cds/common'; entity Products : cuid, managed { title : localized String(111); descr : localized String(1111); stock : Integer; price : Decimal(9,2); currency : Currency; category : Association to Categories; } entity Categories : CodeList { key ID : Integer; parent : Association to Categories; children : Composition of many Categories on children.parent = $self; }
-
-
了解关键字
如你所见,域模型定义了两个实体:
Products
Categories
它还从包(全局可用的重用
@sap/cds/common
包)导入各种通用定义:
Currency
cuid
managed
CodeList
此外,域模型还使用 CDS 关键字
localized
、Association
和Composition
localized
该 localized
关键字可用于标记需要翻译的元素。存储不同语言的翻译和存储默认回退翻译的功能由 CDS 自动处理。
Associations
and Compositions
Associations 和 compositions 可用于定义实体之间的关系。它们通常允许你定义这些关系,而无需显式使用外键。
关联(Associations)和组合(Compositions)。让我们用一个简单的例子来理解这两种关系。
想象一下,你在建立一个关于书籍和类别的数据模型。在这个模型中,每本书都属于一个类别,而每个类别可能包含多本书。
关联(Associations) :
- 关联就像是朋友关系。比如,一本书和它的类别之间的关系就是关联。
- 在关联中,书和类别是相互独立的。如果你删除了一个类别,它并不会影响到书。这就像是,如果你和你的朋友不再联系,你们各自的生活还是会继续。
组合(Compositions) :
- 组合则更像是父母和孩子的关系。在你的数据模型中,一个类别可以有子类别,这种父子类别的关系就是组合。
- 在组合中,子类别依赖于父类别。如果父类别被删除了,它的所有子类别也会被删除。这就像是,如果一个家庭解散了,那么家庭成员之间的关系也就不存在了。
这里类别(Categories)实体定义了一个父子层级关系。这允许你建立一个类别的层级结构。子类别作为组合的一部分被建模。这意味着,当你删除一个父类别时,它的所有子类别也会被自动删除。但是,一个类别的父类别是通过关联来建模的。所以,当你删除一个类别时,它的父类别不会被删除。
简而言之,关联就像是朋友关系,而组合则像是家庭关系。在SAP CAP中,这两种关系让你能够在不直接处理外键的情况下定义实体之间的关系。
aspect : cuid
and managed
在SAP CAP中,aspects用于向实体添加额外的元素或特性,而不需要在每个实体中重复相同的代码。这里提到的两个aspects是cuid
和managed
。
-
cuid(Client Unique ID) :
cuid
是一个aspect,它向实体添加了一个名为ID
的关键元素。- 这个
ID
元素的类型是UUID
(Universally Unique Identifier,通用唯一识别码),这意味着每个实体都会有一个独一无二的标识符。 - 使用
cuid
可以确保每个实体都能被唯一地识别,这在数据库中是非常重要的。
-
managed:
managed
是另一个aspect,它向实体添加了四个额外的元素。- 这些元素用于捕捉实体的创建时间、最后更新时间,以及执行创建和最后更新的用户。
- 具体来说,这四个元素通常是:创建时间(createdAt)、创建者(createdBy)、最后更新时间(modifiedAt)、以及最后更新者(modifiedBy)。
- 这对于跟踪和维护数据的历史记录非常有用,尤其是在需要审计或了解数据变更历史的情况下。
aspect : CodeList
CodeList
是一种特殊的aspect,用于存储基于代码的全球性、可翻译的定义。这些定义可以包括货币、国家、语言等。- 在用户界面(UI)中,
CodeList
特别有用,因为它可以为某些输入字段提供值帮助。比如,如果你有一个让用户选择国家的下拉菜单,你可以使用CodeList
来填充这个菜单的选项,其中包括所有国家的名称和相应的代码。 CodeList
的一个关键优点是它支持国际化,这意味着你可以根据用户的语言偏好显示不同语言的值。
Type : Currency
Currency
是一个定义了货币的类型。- 它定义了一个到
Currencies
实体的关联。这个Currencies
实体基于ISO 4217标准,使用三字母的字母代码作为键,例如“EUR”(欧元)或“USD”(美元)。 - 除了货币代码,
Currencies
实体还提供存储相应货币符号的可能性,比如“€”(欧元符号)或“$”(美元符号)。 - 这种类型的设计使得在处理涉及货币的数据时更加方便和标准化,尤其是在需要处理多种货币的国际化应用中。
获取有关 '@sap/cds/common ' 的更多信息
要直接在编辑器中跳转到导入的定义,请按 CTRL
,将鼠标悬停在关键字上,然后单击。这将在单独的编辑器选项卡中打开源文件。
按住 CTRL
关键字并将其悬停在关键字上,然后将手形光标移到关键字上。这会打开一个包含此特定项目定义的微小叠加层。
右键单击(或使用 F12
)定义将打开上下文菜单。例如,可以在那里找到 Peek 定义以获得更大的叠加。不仅显示此特定项的定义,你还可以在不打开文件本身的情况下浏览此定义的整个源文件。
重写AdminService
之前定义了一个名为AdminService
的简单服务,它 直接定义了实体 Products
。由于现在已经在域模型中定义了 Products
实体, AdminService
因此只需要公开它。此外,你还定义了 Categories
实体,该实体也应是服务的一部分。
使用投影(projection) :
- 投影是一种在服务中暴露实体的方式。通过投影,你可以选择性地包含或排除实体的某些元素,甚至可以重命名元素。
- 在你的例子中,你将使用最简单的投影形式,即不对领域模型实体进行任何更改地直接暴露它们。
操作步骤:
-
首先,你需要进入
srv
文件夹并打开admin-service.cds
文件。 -
然后,用下面的代码替换该文件的内容,并确保保存文件:
using { sap.capire.products as db } from '../db/schema'; service AdminService { entity Products as projection on db.Products; entity Categories as projection on db.Categories; }
-
这段代码的意思是,你在
AdminService
中定义了两个实体:Products
和Categories
。这些实体是通过对db
(你的数据库模型)中的相应实体进行投影得到的。
理解代码:
using { sap.capire.products as db } from '../db/schema';
: 这行代码导入了你的数据库模型,这样你就可以在服务中引用它了。entity Products as projection on db.Products;
: 这表示Products
实体是对数据库模型中Products
实体的直接投影。entity Categories as projection on db.Categories;
: 同样,这表示Categories
实体是对数据库模型中Categories
实体的直接投影。
通过这种方式,你的AdminService
服务现在将包含Products
和Categories
这两个实体,而且是直接反映了你在数据库模型中定义的那样。这使得服务定义变得简洁且易于管理。
使用CAP的通用持久化处理
-
CAP的通用持久性处理:
- CAP Java SDK提供了开箱即用的功能,可以从数据库中存储和检索实体。
- 如果实体存储在数据库中,通常不需要自定义编码。
-
自动服务实体:
- 在你的
AdminService
中定义的实体将通过OData自动提供服务。 - 由于CAP处理了大部分工作,你可以删除之前创建的
AdminService.java
文件。
- 在你的
-
操作步骤:
- 删除
handlers
文件夹中的AdminService.java
文件。
- 删除
-
数据库选择:
- 默认情况下,CAP Java SDK使用内存中的H2数据库。这意味着当应用程序停止时,数据库中的内容将丢失。
- 如果你需要在应用程序运行之间持久化数据库,可以使用基于文件的SQLite数据库。这在CAP文档的“使用数据库”部分有详细描述。
运行并测试应用程序
-
停止应用程序:
- 如果你的应用程序仍在运行,首先需要将其停止。
-
重新启动应用程序:
- 在终端中运行命令
mvn spring-boot:run
来重新启动你的应用程序。 - 一旦应用程序启动,你可以在新标签页中打开它。
- 在终端中运行命令
-
创建类别(Categories) :
-
你将通过HTTP请求来创建一些类别。
-
在你之前创建的
requests.http
文件中添加以下内容:
### Create Categories POST http://localhost:8080/odata/v4/AdminService/Categories Content-Type: application/json {"ID": 1, "name": "TechEd", "descr": "TechEd related topics", "children": [{"ID": 10, "name": "CAP Java", "descr": "Run on Java"}, {"ID": 11, "name": "CAP Node.js", "descr": "Run on Node.js"}]}
-
然后选择出现在新请求上方的“Send Request”(发送请求)。这个请求将通过深度插入一次性创建多个嵌套类别。
-
-
查询单个类别:
-
尝试查询单个类别,例如,通过在你的应用程序URL末尾添加以下内容:
/odata/v4/AdminService/Categories(10)
-
-
展开嵌套结构:
-
你还可以展开嵌套的结构。在应用程序URL的末尾添加以下内容:
/odata/v4/AdminService/Categories?$expand=children /odata/v4/AdminService/Categories(10)?$expand=parent /odata/v4/AdminService/Categories(1)?$expand=children
-
-
测试完成后停止应用程序:
- 测试完成后,可以使用
CTRL+C
停止你的应用程序。
- 测试完成后,可以使用
为重用做准备
-
修改
package.json
文件:-
首先,打开位于
~/projects/products-service
文件夹中的package.json
文件。 -
将
name
字段的值从products-service-cds
更改为@sap/capire-products
。这将定义你的重用模块的名称。 -
如果你愿意,也可以在
description
字段中提供一个有意义的描述。
-
-
添加
index.cds
文件:-
为了更容易重用模块,并确保与其他应用程序的更好解耦,你可以在
products-service
中添加一个index.cds
文件。 -
在
~/projects/products-service
文件夹中创建一个新的index.cds
文件。 -
将以下内容放入这个文件中,并确保保存文件:
using from './db/schema'; using from './srv/admin-service';
-
这个文件将作为模块的入口点,它引用了数据库模式和服务定义。
-
-
完成这些步骤后,你就成功地开发了基于CDS领域模型和服务定义的
products-service
应用程序,并为将来的重用做好了准备。
创建书店项目
请确保停止了应用程序
新建一个终端 : Terminal → New Terminal.
在添加 bookstore
项目之前,我们需要确保位于 projects 文件夹中。两个项目 ( products-service
和 bookstore
) 应该放在一起。在新创建的终端中运行以下命令以返回到项目文件夹:
cd ~/projects
现在,请运行以下命令:
mvn -B archetype:generate -DarchetypeArtifactId=cds-services-archetype -DarchetypeGroupId=com.sap.cds \
-DarchetypeVersion=RELEASE -DjdkVersion=17 \
-DgroupId=com.sap.cap -DartifactId=bookstore
File → Open Folder. 打开bookstore
项目
引入服务
将product-service
作为一个可重用的服务,通过NPM依赖项的形式加入到书店应用程序中。这是SAP CAP(SAP Cloud Application Programming Model)中模块化和重用的一个实际应用示例
-
模拟发布
product-service
模块:- 首先,你需要模拟发布
product-service
模块,并在书店应用程序中使用这个版本。
- 首先,你需要模拟发布
-
打开新的终端:
- 在SAP Business Application Studio的主菜单中,选择“Terminal → New Terminal”。
- 在终端中执行以下命令以切换到书店项目的目录:
cd ~/projects/bookstore
-
安装可重用服务项目作为NPM依赖项:
-
执行以下命令来安装
product-service
作为依赖项:npm install $(npm pack ../products-service -s)
-
npm pack
命令会从products-service
创建一个tarball(压缩包文件),然后直接将其用作书店应用程序中的依赖项。
-
-
查找压缩包文件:
- 在书店项目的根文件夹中,你将找到一个名为
sap-capire-products-1.0.0.tgz
的文件,这是products-service
项目的tarball文件。
- 在书店项目的根文件夹中,你将找到一个名为
-
安装其他包并简化依赖结构:
- 执行以下命令来安装所有其他包并简化整体依赖结构:
-
npm install && npm dedupe
-
检查
package.json
文件:- 打开你的书店项目的
package.json
文件,你将看到对@sap/capire-products
的依赖。
- 打开你的书店项目的
定义书店域模型
-
创建
schema.cds
文件:- 在
~/projects/bookstore/db
文件夹中,创建一个名为schema.cds
的文件。
- 在
-
添加代码到
schema.cds
文件:- 将以下代码添加到你刚创建的
schema.cds
文件中,并确保保存文件: -
namespace sap.capire.bookstore; using { Currency, cuid, managed } from '@sap/cds/common'; using { sap.capire.products.Products } from '@sap/capire-products'; entity Books as projection on Products; extend Products with { // Note: we map Books to Products to allow reusing AdminService as is author : Association to Authors; } entity Authors : cuid { firstname : String(111); lastname : String(111); books : Association to many Books on books.author = $self; } @Capabilities.Updatable: false entity Orders : cuid, managed { items : Composition of many OrderItems on items.parent = $self; total : Decimal(9,2) @readonly; currency : Currency; } @Capabilities.Updatable: false entity OrderItems : cuid { parent : Association to Orders not null; book_ID : UUID; amount : Integer; netAmount : Decimal(9,2) @readonly; }
- 这个领域模型定义了四个实体:
Books
、Authors
、Orders
和OrderItems
。
- 将以下代码添加到你刚创建的
-
理解代码:
- 导入了
Currency
、cuid
和managed
类型和aspects,这些在之前的教程中有描述。 - 导入了
Products
实体,并将其重用为Books
实体。为了建立书籍和作者之间的关系,Products
实体被扩展了一个额外的关联到Authors
。 Orders
实体的total
元素和OrderItems
实体的netAmount
元素被标注为@readonly
,意味着这些元素的值不能由客户端设置,而是通过自定义代码计算。这部分自定义代码将在后续教程中实现。Orders
和OrderItems
实体都被标注为@Capabilities.Updatable: false
,意味着它们不能被更新,只能被创建和删除。
- 导入了
定义书店服务
-
创建
services.cds
文件:- 在
~/projects/bookstore/srv
文件夹中,创建一个名为services.cds
的文件。
- 在
-
添加代码到
services.cds
文件:-
将以下代码添加到
services.cds
文件中,并确保保存文件:using { sap.capire.bookstore as db } from '../db/schema'; // Define Books Service service BooksService { @readonly entity Books as projection on db.Books { *, category as genre } excluding { category, createdBy, createdAt, modifiedBy, modifiedAt }; @readonly entity Authors as projection on db.Authors; } // Define Orders Service service OrdersService { entity Orders as projection on db.Orders; entity OrderItems as projection on db.OrderItems; } // Reuse Admin Service using { AdminService } from '@sap/capire-products'; extend service AdminService with { entity Authors as projection on db.Authors; }
-
这个
services.cds
文件定义了三个服务:BooksService
、OrdersService
和AdminService
。
-
-
理解代码:
BooksService
用于提供对Books
和Authors
数据的只读视图。通过这个服务无法修改这些实体。OrdersService
允许查看、创建和删除订单。AdminService
是从产品服务中重用的,但我们向其中添加了Authors
实体。它可以用于创建、更新和删除产品和作者。
-
服务的最佳实践:
- 通常最好根据单一用例来定义服务。例如,
AdminService
用于管理产品、作者和类别,而BooksService
用于展示书籍和作者的目录,对最终用户隐藏了诸如创建和修改时间等管理数据。
- 通常最好根据单一用例来定义服务。例如,
加载示例数据
-
创建数据文件夹:
- 在你的书店项目中,右键点击
db
文件夹并选择“新建文件夹”。将这个新文件夹命名为data
。
- 在你的书店项目中,右键点击
-
进入数据文件夹:
-
在终端中运行以下命令以进入
data
文件夹:cd ~/projects/bookstore/db/data
-
-
下载CSV数据文件:
-
为
Authors
实体下载CSV数据文件:curl https://raw.githubusercontent.com/SAP-samples/cloud-cap-samples/CAA160-final/bookstore/db/data/sap.capire.bookstore-Authors.csv -O
-
为
Books
实体下载CSV数据文件:curl https://raw.githubusercontent.com/SAP-samples/cloud-cap-samples/CAA160-final/bookstore/db/data/sap.capire.bookstore-Books.csv -O
-
将下载的
Books
CSV文件重命名:mv sap.capire.bookstore-Books.csv sap.capire.products-Products.csv
-
下载
Books
实体的翻译CSV数据文件:curl https://raw.githubusercontent.com/SAP-samples/cloud-cap-samples/CAA160-final/bookstore/db/data/sap.capire.bookstore-Books_texts.csv -O
-
将下载的
Books_texts
CSV文件重命名:mv sap.capire.bookstore-Books_texts.csv sap.capire.products-Products_texts.csv
-
为
Categories
实体下载CSV数据文件:curl https://raw.githubusercontent.com/SAP-samples/cloud-cap-samples/CAA160-final/bookstore/db/data/sap.capire.products-Categories.csv -O
-
-
确认CSV文件:
-
确保你现在有4个CSV文件,包含示例数据。这些文件为我们重用的服务和在书店服务中创建的一个实体提供初始数据:
sap.capire.products-Categories.csv
sap.capire.products-Products.csv
sap.capire.products-Products_texts.csv
sap.capire.bookstore-Authors.csv
-
-
注意CSV文件命名:
- CSV文件的命名必须精确匹配模式
[namespace]-[entity name]
,否则应用程序将无法启动。
- CSV文件的命名必须精确匹配模式
运行和测试你的书店应用程序
-
进入书店项目的根目录:
-
在终端中运行以下命令以进入书店项目的根目录:
cd ~/projects/bookstore
-
-
停止之前运行的应用程序:
- 确保你已经使用
CTRL+C
停止了所有之前运行的应用程序(包括products-service
应用程序)。
- 确保你已经使用
-
启动应用程序:
-
通过运行以下命令来启动应用程序:
mvn spring-boot:run
-
在SAP Business Application Studio中,你将看到一个弹出窗口。选择“在新标签页中打开”。
-
-
打开URL并查看数据:
- 当你在新标签页中打开URL时,你将看到一个欢迎页面。要查看书籍数据,可以直接从欢迎页面上点击“Books”。
- 作为另一种方式,你也可以在URL后面直接添加
/odata/v4/BooksService/Books
来访问书籍数据。
-
阅读本地化的德语示例数据:
- 要阅读本地化的德语示例数据,请在URL中添加查询参数
sap-locale=de
。例如,<APP_URL>/odata/v4/BooksService/Books?sap-locale=de
。尝试在德语(de)和英语(en)之间切换语言。
- 要阅读本地化的德语示例数据,请在URL中添加查询参数
使用自定义代码扩展书店
在CAP Java应用程序中,你可以使用@Before
和@After
注解来增强默认的事件处理。@Before
注解用于在事件发生之前执行操作,例如验证输入数据,而@After
注解用于在事件发生之后执行操作,例如处理返回的实体。以下是你需要执行的步骤:
-
停止应用程序:
- 如果你的应用程序仍在运行,请使用
CTRL+C
停止它。
- 如果你的应用程序仍在运行,请使用
-
创建handlers文件夹:
- 在终端中,进入
srv/src/main/java/com/sap/cap/bookstore
目录。 - 创建一个新的文件夹,命名为
handlers
。
- 在终端中,进入
-
创建OrdersService.java文件:
-
在
handlers
包中,创建一个名为OrdersService.java
的文件。 -
将以下内容添加到
OrdersService.java
文件中,并确保保存文件:package com.sap.cap.bookstore.handlers; import cds.gen.ordersservice.OrdersService_; import com.sap.cds.services.handler.EventHandler; import com.sap.cds.services.handler.annotations.ServiceName; import org.springframework.stereotype.Component; @Component @ServiceName(OrdersService_.CDS_NAME) public class OrdersService implements EventHandler { // Replace this comment with the code of Step 2 of this tutorial }
-
这个类将作为
OrdersService
的事件处理器。
-
-
解决编辑器中的验证错误:
- 如果你在编辑器中看到验证错误,请在
srv
目录中的pom.xml
上打开上下文菜单并选择“重新加载项目”。 - 这将重新生成类并使它们可用。
- 如果你在编辑器中看到验证错误,请在
创建订单时减少库存
-
添加方法到
OrdersService
Java类:- 将以下代码添加到你的
OrdersService
Java类中,并确保保存文件: -
@Autowired PersistenceService db; @Before(event = CqnService.EVENT_CREATE, entity = OrderItems_.CDS_NAME) public void validateBookAndDecreaseStock(List<OrderItems> items) { for (OrderItems item : items) { String bookId = item.getBookId(); Integer amount = item.getAmount(); // check if the book that should be ordered is existing CqnSelect sel = Select.from(Books_.class).columns(b -> b.stock()).where(b -> b.ID().eq(bookId)); Books book = db.run(sel).first(Books.class) .orElseThrow(() -> new ServiceException(ErrorStatuses.NOT_FOUND, "Book does not exist")); // check if order could be fulfilled int stock = book.getStock(); if (stock < amount) { throw new ServiceException(ErrorStatuses.BAD_REQUEST, "Not enough books on stock"); } // update the book with the new stock, means minus the order amount book.setStock(stock - amount); CqnUpdate update = Update.entity(Books_.class).data(book).where(b -> b.ID().eq(bookId)); db.run(update); } } @Before(event = CqnService.EVENT_CREATE, entity = Orders_.CDS_NAME) public void validateBookAndDecreaseStockViaOrders(List<Orders> orders) { for (Orders order : orders) { if (order.getItems() != null) { validateBookAndDecreaseStock(order.getItems()); } } }
- 将以下代码添加到你的
-
添加导入语句:
- 在
OrdersService
Java类的顶部添加以下导入语句,并确保保存文件: -
import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import com.sap.cds.ql.Select; import com.sap.cds.ql.Update; import com.sap.cds.ql.cqn.CqnSelect; import com.sap.cds.ql.cqn.CqnUpdate; import com.sap.cds.services.ErrorStatuses; import com.sap.cds.services.ServiceException; import com.sap.cds.services.cds.CqnService; import com.sap.cds.services.handler.annotations.Before; import com.sap.cds.services.persistence.PersistenceService; import cds.gen.ordersservice.OrderItems; import cds.gen.ordersservice.OrderItems_; import cds.gen.ordersservice.Orders; import cds.gen.ordersservice.Orders_; import cds.gen.sap.capire.bookstore.Books; import cds.gen.sap.capire.bookstore.Books_;
- 在
-
理解代码逻辑:
validateBookAndDecreaseStock
方法使用@Before
注解注册,这意味着该方法在OrderItems
实体持久化之前被调用。该注解还指定了在创建OrderItems
实体时调用该方法。- 方法检查要订购的书籍是否存在,并比较可用库存与订购数量。如果库存足够,则减少书籍的库存并在数据库中更新书籍。
validateBookAndDecreaseStockViaOrders
方法用于处理通过Orders
实体的深度插入创建的订单项。
-
处理可能的错误:
- 如果
OrdersService.java
文件仍然显示错误,请在srv
目录中右键单击pom.xml
并选择“重新加载项目”。 - 关闭并重新打开
OrderService.java
文件后,错误应该消失。
- 如果
测试处理程序
-
停止应用程序:
- 在SAP Business Application Studio的终端中,如果应用程序仍在运行,请使用
CTRL+C
停止它。
- 在SAP Business Application Studio的终端中,如果应用程序仍在运行,请使用
-
设置运行配置:
- 在SAP Business Application Studio的侧边栏中,选择运行配置图标。
- 选择创建配置图标(加号),然后选择
Bookstore
作为你要运行的项目。按Enter确认名称。
-
启动应用程序:
- 点击绿色箭头来启动应用程序。
- 你应该在调试控制台中看到应用程序正在启动。
-
测试应用程序:
-
在根目录中创建一个新的文件
requests.http
。 -
在文件中输入以下内容:
### Create Order POST http://localhost:8080/odata/v4/OrdersService/Orders Content-Type: application/json { "items": [ { "book_ID": "abed2f7a-c50e-4bc5-89fd-9a00a54b4b16", "amount": 2 } ] }
-
选择“发送请求”来执行请求。
-
-
查看库存变化:
- 从欢迎页面上,选择
Books
,你将看到书籍《Wuthering Heights》的库存减少到10。 - 你也可以在应用程序URL的末尾添加
/odata/v4/BooksService/Books
来查看。 - 记住应用程序URL是在你运行应用程序时创建的。
- 从欢迎页面上,选择
-
添加并执行第二个请求:
-
在你创建的
requests.http
文件中添加第二个请求:### Read Book GET http://localhost:8080/odata/v4/BooksService/Books(abed2f7a-c50e-4bc5-89fd-9a00a54b4b16) Accept: application/json
-
选择第二个请求上方的“发送请求”来执行请求。你将看到你正在订购的书籍的当前库存。
-
-
重复请求直到库存耗尽:
- 重复请求,直到你得到一个错误,表明书籍已经缺货。
- 通过重复请求,你每次都在订购2本书,因此每次都会减少2本书的库存。
计算订单项的netAmount
-
添加计算净金额的方法:
-
将以下代码添加到
OrdersService
类中,并确保保存文件:@After(event = { CqnService.EVENT_READ, CqnService.EVENT_CREATE }, entity = OrderItems_.CDS_NAME) public void calculateNetAmount(List<OrderItems> items) { for (OrderItems item : items) { String bookId = item.getBookId(); // get the book that was ordered CqnSelect sel = Select.from(Books_.class).where(b -> b.ID().eq(bookId)); Books book = db.run(sel).single(Books.class); // calculate and set net amount item.setNetAmount(book.getPrice().multiply(new BigDecimal(item.getAmount()))); } }
-
-
添加导入语句:
-
在
OrdersService
Java类的顶部添加以下导入语句,并保存文件:import java.math.BigDecimal; import com.sap.cds.services.handler.annotations.After;
-
-
理解代码逻辑:
calculateNetAmount
方法使用@After
注解注册,这意味着该方法在从数据库读取OrderItems
实体后被调用。该注解还指定了在读取或创建OrderItems
实体时调用该方法。- 方法的
items
参数提供了对所有读取或创建的OrderItems
实体的访问。 CqnSelect sel
变量定义了一个数据库查询,用于检索订单项引用的书籍。执行查询后,使用Books
的POJO接口访问查询结果。- 在最后一行中,根据书籍的价格和订购的数量计算订单项的净金额。
再次测试
在SAP Business Application Studio中,如果应用程序仍在运行,请单击“调试”侧面板中的红色停止图标,停止应用程序。
选择SAP Business Application Studio侧面板上的Run Configuration 图标。
现在,向requests.http
文件请求添加新请求。
### Create another Order
POST http://localhost:8080/odata/v4/OrdersService/Orders
Content-Type: application/json
{
"items": [
{
"book_ID": "fd0c5fda-8811-4e20-bcff-3a776abc290a",
"amount": 4
}
]
}
send Request 发送请求
在欢迎页面中,选择OrderItems,你将看到netAmount元素中填充了计算出的值。
计算订单总额
-
添加计算总金额的方法:
-
将以下代码添加到
OrdersService
类中,并确保保存文件:-
@After(event = { CqnService.EVENT_READ, CqnService.EVENT_CREATE }, entity = Orders_.CDS_NAME) public void calculateTotal(List<Orders> orders) { for (Orders order : orders) { // calculate net amount for expanded items if(order.getItems() != null) { calculateNetAmount(order.getItems()); } // get all items of the order CqnSelect selItems = Select.from(OrderItems_.class).where(i -> i.parent().ID().eq(order.getId())); List<OrderItems> allItems = db.run(selItems).listOf(OrderItems.class); // calculate net amount of all items calculateNetAmount(allItems); // calculate and set the orders total BigDecimal total = new BigDecimal(0); for(OrderItems item : allItems) { total = total.add(item.getNetAmount()); } order.setTotal(total); } }
-
-
-
理解代码逻辑:
calculateTotal
方法使用@After
注解注册,这意味着该方法在从数据库读取Orders
实体后被调用。该注解还指定了在读取或创建Orders
实体时调用该方法。- 对于作为操作一部分可能返回的订单项,使用
calculateNetAmount
方法计算净金额。请注意,这可能只是订单所有项的一部分。 - 对于每个订单,使用在
CqnSelect selItems
中定义的查询从数据库中读取所有订单项。 - 对于每个订单项,首先通过重用
calculateNetAmount
方法计算净金额。然后将所有净金额加到订单的总金额中。
测试订单总额
在SAP Business Application Studio中,如果应用程序仍在运行,请单击“调试”侧面板中的红色停止图标,停止应用程序。
选择SAP Business Application Studio侧面板上的Run Configuration 图标。运行书店项目
-
将
requests.http
文件的第三个请求中将 amount 更新为 10 -
选择第三个请求上方的 Send Request (发送请求)
-
在欢迎页面中,选择 Orders。你将看到元素
total
填充了计算值。 -
URL 末尾加入
?$expand=items
添加身份验证和授权
启用身份验证
编辑srv
目录 pom.xml
文件 (而不是位于根项目文件夹中的 pom.xml
文件)添加 cds-starter-cloudfoundry
依赖项。并保存文件。
<dependency>
<groupId>com.sap.cds</groupId>
<artifactId>cds-starter-cloudfoundry</artifactId>
</dependency>
向订单服务添加身份验证
目前,你只需要确保想要创建订单的用户已经通过身份验证。CAP为本地开发提供了内置的模拟用户,代表常见的身份验证场景。
-
启动应用程序:
- 使用命令
mvn spring-boot:run
启动应用程序。
- 使用命令
-
尝试未经认证的请求:
-
打开
requests.http
文件,并执行第一个“创建订单”的请求,方法是选择请求上方的“发送请求”。 -
观察响应是否包含状态
HTTP/1.1 401
。这表明请求由于未经认证而被拒绝。
-
-
修改请求以包含认证信息:
-
为了创建订单,你需要提供凭据。对于本地开发,CAP提供了内置的模拟用户。修改请求如下:
### Create Order POST http://localhost:8080/odata/v4/OrdersService/Orders Content-Type: application/json Authorization: Basic authenticated: { "items": [ { "book_ID": "abed2f7a-c50e-4bc5-89fd-9a00a54b4b16", "amount": 2 } ] }
-
在这里,你已经向请求中添加了
Authorization
HTTP头,并为内置的已认证模拟用户提供了凭据。这个用户的密码为空。
-
-
再次执行请求并验证结果:
- 再次执行请求,并查看订单是否现在被创建。
- 当在欢迎页面上选择
OrdersService
的实体时,你现在也需要提供凭据。在那里,你也可以使用已认证用户和空密码。
将模拟用户添加到项目
-
添加安全配置:
-
在
srv/src/main/resources
下的application.yaml
文件中添加安全配置部分:--- spring: config.activate.on-profile: default cds: datasource: auto-config.enabled: false security: mock: users: - name: klaus password: pass_klaus additional: firstName: Klaus lastName: Sussard email: Klaus.Sussard@mail.com - name: mia password: pass_mia additional: firstName: Mia lastName: Bonnellac email: Mia.Bonnellac@mail.com
-
这里你定义了两个用户,它们没有明确的角色分配,将隐式地属于
authenticated-user
伪角色。
-
-
重启应用程序:
- 使用命令
mvn spring-boot:run
重启应用程序。 - 在启动日志中,你可以观察到创建的模拟用户及其用户名、角色和密码。这些用户是除了内置模拟用户之外添加的。
- 使用命令
-
修改HTTP请求以包含模拟用户的凭据:
-
修改你之前使用的HTTP请求,以包含其中一个模拟用户的凭据:
### Create Order POST http://localhost:8080/odata/v4/OrdersService/Orders Content-Type: application/json Authorization: Basic klaus:pass_klaus { "items": [ { "book_ID": "abed2f7a-c50e-4bc5-89fd-9a00a54b4b16", "amount": 2 } ] }
-
选择请求上方的“发送请求”,查看是否创建了新订单。
-
-
观察响应内容:
- 响应的有效载荷应包含在
createdBy
和modifiedBy
字段中的用户名称,这是由你之前在领域模型中添加的managed
方面提供的。
- 响应的有效载荷应包含在
将用户角色添加到项目
添加一个具有“Administrators”角色的模拟用户,并限制只有具有该角色的用户才能访问AdminService
。以下是你需要执行的步骤:
-
添加具有角色的模拟用户:
-
在
application.yaml
文件中,在现有用户后添加一个新的模拟用户,如下所示:--- spring: config.activate.on-profile: default cds: datasource: auto-config.enabled: false security: mock: users: - name: klaus password: pass_klaus additional: firstName: Klaus lastName: Sussard email: Klaus.Sussard@mail.com - name: mia password: pass_mia additional: firstName: Mia lastName: Bonnellac email: Mia.Bonnellac@mail.com - name: sabine password: pass_sabine roles: - Administrators additional: firstName: Sabine lastName: Autumnpike email: Sabine.Autumnpike@mail.com
-
使用
roles
属性为该用户添加“Administrators”角色。
-
-
限制
AdminService
的访问:-
在
srv
目录中的services.cds
文件末尾添加注释定义,以使AdminService
仅对具有“Administrators”角色的用户可用:annotate AdminService @(requires: 'Administrators');
-
-
重启应用程序:
- 使用命令
mvn spring-boot:run
重启应用程序。
- 使用命令
-
添加新的HTTP请求:
-
在
requests.http
文件中添加一个新的请求:### Read Products GET http://localhost:8080/odata/v4/AdminService/Products Accept: application/json Authorization: Basic sabine:pass_sabine
-
选择这个请求上方的“发送请求”,查看是否收到产品列表。
-
-
测试访问控制:
- 移除
Authorization
头或将凭据更改为不同的模拟用户。 - 观察
AdminService
是否对他们不可用。
- 移除
实体的高级授权
实现以下用例:
- 每个经过身份验证的用户只能查看他们自己的订单和订单项。
- 管理员应该能够查看所有用户的所有订单。
你可以使用@restrict
注解为你的服务添加更复杂的授权检查。
-
修改
OrdersService
的服务定义:-
在
srv
文件夹中的services.cds
文件中,修改OrdersService
的服务定义如下:// Define Orders Service service OrdersService { @(restrict: [ { grant: '*', to: 'Administrators' }, { grant: '*', where: 'createdBy = $user' } ]) entity Orders as projection on db.Orders; @(restrict: [ { grant: '*', to: 'Administrators' }, { grant: '*', where: 'parent.createdBy = $user' } ]) entity OrderItems as projection on db.OrderItems; }
-
这样,你为管理员授予了访问所有订单的权限,而普通用户只能看到他们自己创建的订单。由于你将
OrderItems
作为一个单独的实体暴露,你也需要在那里添加安全配置。
-
-
重启应用程序:
- 使用命令
mvn spring-boot:run
重启应用程序。
- 使用命令
-
执行HTTP请求以创建订单:
-
使用你之前添加的模拟用户的凭据执行HTTP请求来创建订单:
### Create Order as Mia POST http://localhost:8080/odata/v4/OrdersService/Orders Content-Type: application/json Authorization: Basic mia:pass_mia { "items": [ { "book_ID": "fd0c5fda-8811-4e20-bcff-3a776abc290a", "amount": 10 } ] }
-
-
验证不同用户的访问权限:
-
通过添加以下请求到
requests.http
文件来验证每个用户(除了管理员)只能访问他们自己的订单和项:### Read Orders as Mia GET http://localhost:8080/odata/v4/OrdersService/Orders?$expand=items Accept: application/json Authorization: Basic mia:pass_mia
-
你将看到自己的订单和项。
### Read Orders as Klaus GET http://localhost:8080/odata/v4/OrdersService/OrderItems Accept: application/json Authorization: Basic klaus:pass_klaus
-
你将不会看到任何项。
### Read Orders as Sabine (Administrator) GET http://localhost:8080/odata/v4/OrdersService/Orders?$expand=items Accept: application/json Authorization: Basic sabine:pass_sabine
-
你将看到所有订单和项。
-
将 CAP Java 应用部署到 SAP Business Technology Platform
在SAP BTP驾驶舱中使用SAP HANA Cloud试用版
在 SAP BTP Cockpit 中,点击你的子帐户。
然后单击左侧菜单中的“权利”,搜索 SAP HANA 的权利。
请注意有以下权利 :
- SAP HANA Cloud:
tools (Application)
,hana
,hana-cloud-connection
, andrelational-data-lake
- SAP HANA Schemas & HDI Containers:
hdi-shared
,schema
, andsecurestore
添加对SAP HANA Cloud工具的订阅
- 从SAP BTP驾驶舱中,单击服务,然后单击服务市场。搜索SAP HANA云,然后单击右上角的创建。
- 在服务下选择SAP HANA Cloud,在计划下选择 tools。 然后点击 Create
- 要确保你所需的用户具有在HANA Cloud Central中管理实例的必要权限,请导航到左侧菜单中的“安全”>“用户”。然后单击你的用户。
单击“Assign Role Collection ”按钮。
选择SAP HANA Cloud Administrator,然后单击分配角色集合。
导航到“实例”、“实例”和“订阅”,然后单击SAP HANA Cloud以打开SAP HANA云中心。
创建一个Instanse
bookstore-db
增强生产项目配置
切换到项目的根目录执行:
cds add hana,mta,xsuaa,approuter --for production
-
这个命令会执行以下操作:
hana
: 为SAP HANA数据库配置部署,将数据源类型为hana
的配置添加到requires.[production].db
块中。mta
: 添加mta.yaml
文件,该文件反映了你的项目配置。xsuaa
: 创建xs-security.json
文件,并在mta.yaml
文件中添加所需配置。在requires.[production].auth
块中添加xsuaa
类型的认证。approuter
: 添加独立AppRouter的配置和所需文件,以确保部署后身份验证流程正常工作。
-
确保
cds-starter-cloudfoundry
依赖项包含在你的项目中-
<dependency> <groupId>com.sap.cds</groupId> <artifactId>cds-starter-cloudfoundry</artifactId> </dependency>
-
更新xs-security.json
和xs-app.json
-
更新
xs-security.json
文件:-
打开SAP Business Application Studio中的
xs-security.json
文件。 -
更新文件内容,以便它看起来像这样:
{ "xsappname": "bookstore", "tenant-mode": "dedicated", "scopes": [ { "name": "$XSAPPNAME.Administrators", "description": "Administrators" } ], "attributes": [], "role-templates": [ { "name": "Administrators", "description": "generated", "scope-references": [ "$XSAPPNAME.Administrators" ], "attribute-references": [] } ], "role-collections": [ { "name": "BookStore_Administrators", "description": "BookStore Administrators", "role-template-references": ["$XSAPPNAME.Administrators"] } ], "oauth2-configuration": { "redirect-uris": ["https://*.cfapps.us10-001.hana.ondemand.com/**"] } }
-
在
xsappname
属性中添加你的应用程序名称,并声明一个角色集合,以便你稍后可以将用户分配给它。
-
-
根据部署环境更新OAuth2配置:
oauth2-configuration
的值取决于你的账户部署的环境。- 检查命令
cf target
返回的API URL,并相应地更改值中的数据中心ID,例如https://*.cfapps.us10-001.hana.ondemand.com/**
。
-
更新
xs-app.json
文件:- 打开
app/xs-app.json
文件。 - 移除
welcomeFile
属性。
- 打开
查看SAP BTP Cloud Foundry地址
访问cockpit.hanatrial.ondemand.com/cockpit#/ho…
复制Cloud Foundry API
登录SAP BTP Cloud Foundry环境
-
在SAP Business Application Studio中,选择“终端”打开终端→ 从主菜单中新建终端。
-
运行以下命令以配置要在终端中连接到的Cloud Foundry环境。将<CF_API_ENDPOINT>替换为在上一步骤中获得的实际值
-
cf api <CF_API_ENDPOINT>
-
-
在终端中使用以下命令,使用登录凭据进行身份验证:
-
cf login
-
使用cf Deploy进行部署
确保dev空间中有一个hana示例
-
构建部署档案:
-
在项目的根目录中,执行以下命令来构建部署档案:
mbt build -t gen --mtar mta.mtar
-
这需要MBT构建工具,SAP Business Application Studio已经安装了这个工具。
-
-t
选项定义构建结果的目标文件夹为项目的gen
文件夹。作为这个构建的一部分,隐式地执行了cds build --production
。这个隐式构建然后使用了你在使用--for production
时添加的所有配置。
-
-
部署档案:
-
使用
cf deploy
命令部署档案:cf deploy gen/mta.mtar
-
这需要MultiApps CF CLI插件,SAP Business Application Studio已经安装了这个插件。
-
在部署过程中,将创建所有需要的服务实例,并部署应用程序以及数据库工件。
-
-
观察部署过程:
- 这个过程可能需要几分钟。在这一步中,档案被上传到Cloud Foundry,服务实例被创建,应用程序被准备,并部署到它们的目标运行时。
- 如果部署在部署
bookstore-db-deployer
时失败,请确保你的IP地址已配置为允许连接到SAP HANA Cloud。或者,你可以自行冒险允许所有IP地址的连接。我们建议在完成教程后恢复该设置。
-
找到应用程序的URL:
-
在部署日志中,找到
bookstore
应用程序的URL:Application "bookstore" started and available at "[org]-[space]-bookstore.cfapps.[region].hana.ondemand.com"
-
这是AppRouter的URL,它强制执行身份验证流程。
-
-
在浏览器中打开应用程序:
- 在浏览器中打开这个URL,并尝试提供的链接,例如
.../catalog/Books
。应用程序数据从SAP HANA中获取。 - 相应的路由可以在上一步的
routes
下找到。
- 在浏览器中打开这个URL,并尝试提供的链接,例如
-
观察应用程序的安全性:
- 观察你的应用程序现在通过要求在服务和实体端点上进行身份验证来确保安全性。
在SAP BTP上配置身份验证和授权
配置在SAP BTP上进行测试的角色
在浏览器中打开应用程序。使用欢迎页面上的链接,你可以检查是否无法访问 Orders
实体或 AdminService
下的所有内容。如果你单击这些,你应该会看到一个 401
错误。
若要使用 AdminService
,需要将自己分配给 xs-security.json
文件中定义的角色集合 BookStore_Administrators
。若要将此角色集合分配给用户,需要导航到 SAP BTP 子帐户的“安全→角色集合”部分
然后选择 Edit。 在 ID 和 E-Mail 字段中输入你的电子邮件地址,然后选择 Save (保存)
若要使对角色集合的更改生效,需要重新启动 approuter:
cf restart bookstore
再次访问,应该能访问到数据