SAP OData / RAP / Fiori-SAPUI5 全栈开发教程


目录

  • 第一部分:基础概念篇
    • 第1章 SAP数据服务全景图
    • 第2章 什么是OData
    • 第3章 OData核心概念
    • 第4章 第一个OData服务(SEGW)
  • 第二部分:OData进阶篇
    • 第5章 标准对象扩展(Extension of SAP STD)
    • 第6章 OData服务消费(前端/第三方)
    • 第7章 OData性能优化与最佳实践
  • 第三部分:RAP模型篇
    • 第8章 RAP概述与架构
    • 第9章 CDS View建模
    • 第10章 行为定义(Behavior Definition)
    • 第11章 RAP业务对象开发实战
  • 第四部分:Fiori前端篇
    • 第12章 SAP Fiori基础
    • 第13章 SAPUI5核心组件
    • 第14章 Fiori Elements vs Freestyle
    • 第15章 全栈开发实战项目
  • 附录

第一部分:基础概念篇


第1章 SAP数据服务全景图

1.1 为什么需要数据服务?

传统SAP系统(ECC时代)中,数据交互主要靠:

  • RFC(Remote Function Call)— 重量级,紧耦合
  • BAPI(Business API)— 标准但不够灵活
  • IDoc — 异步,适合批量
  • 直连数据库 — 不推荐,绕过业务逻辑

现代企业需要:

  • 轻量级、跨平台的数据交换
  • 标准化的RESTful接口
  • 前后端分离架构

→ 这就是 OData + RAP + Fiori 的价值。

1.2 SAP数据服务技术栈演进

时间线:
2010 ─── Gateway (SAP NetWeaver Gateway)
└─ SEGW事务码, 手动编码实现类
2016 ─── CDS View + SEGW (部分自动化)
2018 ─── RAP (ABAP RESTful Application Programming)
└─ 声明式开发, 代码生成
2020 ─── RAP成熟 (S/4HANA Cloud 必选)
2023+ ─── RAP + CAP (Cloud Application Programming)
└─ 全栈开发标准

1.3 技术选型建议

场景 推荐技术 原因
S/4HANA 新开发 RAP 标准、高效、可维护
ECC 6.0 扩展 SEGW OData 兼容性好
标准Fiori应用增强 Extension Points 侵入性低
跨系统集成 OData + CPI 标准化
快速原型 Fiori Elements 零前端代码

第2章 什么是OData

2.1 OData的定义

OData (Open Data Protocol) 是一个基于REST的协议,由微软发起,OASIS标准化。

用最简单的话说:

OData = 用HTTP操作数据库的标准方式

类比:

SQL:    SELECT * FROM employees WHERE id = 1
OData: GET /sap/opu/odata/sap/ZEMPLOYEE_SRV/EmployeeSet('1')
→ 返回JSON或XML格式的数据

2.2 OData的CRUD映射

HTTP方法    OData操作     SQL对应          ABAP对应
─────────────────────────────────────────────────────
GET 查询/读取 SELECT READ
POST 创建 INSERT CREATE
PUT 全量更新 UPDATE UPDATE
PATCH 部分更新 UPDATE UPDATE
DELETE 删除 DELETE DELETE

2.3 OData URL结构解析

http://服务器:端口/sap/opu/odata/sap/ZSERVICE_SRV/EntitySet(Key)?$filter=...&$expand=...

各段含义:
├─ /sap/opu/odata/sap/ ← SAP Gateway固定前缀
├─ ZSERVICE_SRV ← OData服务名称
├─ /EntitySet ← 实体集(类似数据库表)
├─ (Key) ← 主键定位单条记录
├─ ?$filter=... ← 过滤条件
├─ &$expand=... ← 展开关联实体
├─ &$select=... ← 选择特定字段
├─ &$orderby=... ← 排序
├─ &$top=10 ← 取前10条
└─ &$skip=20 ← 跳过前20条(分页)

2.4 OData版本

版本 特性 SAP支持
OData V2 成熟稳定,生态最大 ECC/S4 全支持
OData V4 新特性多(Delta, Batch改进) S/4HANA 1909+

初学者建议:先精通V2,再学V4差异。


第3章 OData核心概念

3.1 元数据(Metadata)

每个OData服务都暴露一个元数据文档:

GET /sap/opu/odata/sap/ZSERVICE_SRV/$metadata

返回XML格式,描述服务的所有实体、属性、关联、函数。

类比:$metadata 就像数据库的 DESCRIBE TABLE

3.2 核心模型元素

Service Document (服务文档)
├── Entity Type (实体类型) ← 类似ABAP结构体/数据库表定义
│ ├── Property (属性) ← 字段
│ └── Navigation Property ← 外键关联
├── Entity Set (实体集) ← 类似内表/数据库表
├── Association (关联) ← 表间关系
├── Function Import (函数导入) ← 自定义操作(如审批)
└── Complex Type (复杂类型) ← 嵌套结构

3.3 用一个例子理解全部

假设有一个”采购订单”业务:

Entity Type: PurchaseOrder
├── Properties:
│ ├── PoNumber (String, Key) ← 采购订单号
│ ├── Vendor (String) ← 供应商
│ ├── TotalAmount (Decimal) ← 总金额
│ ├── Status (String) ← 状态
│ └── CreateDate (DateTime) ← 创建日期

├── Navigation Properties:
│ └── ToItems → PurchaseOrderItem ← 订单行项目(一对多)

Entity Type: PurchaseOrderItem
├── Properties:
│ ├── PoNumber (String, Key) ← 订单号(外键)
│ ├── ItemNumber (String, Key) ← 行号
│ ├── Material (String) ← 物料
│ ├── Quantity (Int32) ← 数量
│ └── UnitPrice (Decimal) ← 单价

Association: PurchaseOrder ←→ PurchaseOrderItem (1:N)

Function Import:
└── Approve(PoNumber) → 批准采购订单

对应的API调用:

# 查询所有采购订单
GET /sap/opu/odata/sap/ZPO_SRV/PurchaseOrderSet

# 查询单个订单并展开行项目
GET /sap/opu/odata/sap/ZPO_SRV/PurchaseOrderSet('4500001234')?$expand=ToItems

# 按金额过滤
GET /sap/opu/odata/sap/ZPO_SRV/PurchaseOrderSet?$filter=TotalAmount gt 10000

# 创建订单
POST /sap/opu/odata/sap/ZPO_SRV/PurchaseOrderSet
Body: {"PoNumber":"NEW001","Vendor":"V001","TotalAmount":5000}

# 调用审批函数
POST /sap/opu/odata/sap/ZPO_SRV/Approve?PoNumber='4500001234'

第4章 第一个OData服务(SEGW)

4.1 开发环境准备

前提条件:

  • SAP NetWeaver 7.40+ (建议7.50+)
  • 事务码 SEGW 可用
  • Gateway服务已激活(事务码 /IWFND/MAINT_SERVICE)

4.2 步骤一:创建OData项目

1. 事务码: SEGW
2. 菜单: Project → Create
3. 填写:
- Project: ZDEMO_ODATA
- Description: OData教程示例
- Package: $TMP (本地对象,测试用)
4. 保存

4.3 步骤二:定义数据模型

方式A:手动创建 Entity Type

1. 右键 Data Model → Import → DDIC Structure
2. 选择参考表: SFLIGHT (SAP航班表)
3. 勾选需要的字段:
- CARRID (航空公司)
- CONNID (航班号)
- FLDATE (日期)
- PRICE (价格)
- SEATSMAX (最大座位)
- SEATSOCC (已占座位)
4. 设置Key: CARRID + CONNID + FLDATE
5. 完成

方式B:导入已有DDIC结构

右键 Data Model → Import → DDIC Structure → 选择表或结构

4.4 步骤三:生成运行时对象

1. 选中项目
2. 点击 Generate 按钮 (或 Ctrl+F3)
3. 系统自动创建:
- MPC (Model Provider Class) ← 模型定义
- DPC (Data Provider Class) ← 数据读写逻辑
- 服务注册信息
4. 记录生成的DPC类名: ZCL_ZDEMO_ODATA_DPC_EXT

4.5 步骤四:实现数据读取逻辑

打开 DPC 扩展类 ZCL_ZDEMO_ODATA_DPC_EXT,重写方法:

METHOD flightset_get_entityset.
" 读取航班数据
SELECT * FROM sflight
INTO CORRESPONDING FIELDS OF TABLE et_entityset
UP TO 50 ROWS.

" 处理$filter条件 (Gateway框架自动处理大部分情况)
ENDMETHOD.

4.6 步骤五:注册与激活服务

1. 事务码: /IWFND/MAINT_SERVICE
2. 点击 "Add Service"
3. System Alias: LOCAL (或你的系统别名)
4. Technical Service Name: ZDEMO_ODATA
5. 选择并激活
6. 服务地址变为:
/sap/opu/odata/sap/ZDEMO_ODATA_SRV/

4.7 步骤六:测试

方式一:SAP Gateway Client

1. 事务码: /IWFND/GW_CLIENT
2. 输入: /sap/opu/odata/sap/ZDEMO_ODATA_SRV/FlightSet
3. 执行,查看JSON/XML返回

方式二:浏览器

https://your-sap-server:443/sap/opu/odata/sap/ZDEMO_ODATA_SRV/FlightSet?$format=json

方式三:Postman

GET https://your-sap-server/sap/opu/odata/sap/ZDEMO_ODATA_SRV/FlightSet
Headers:
Authorization: Basic <base64(user:password)>
Accept: application/json

4.8 小结

SEGW开发流程:
创建项目 → 定义模型 → 生成框架 → 实现逻辑 → 注册激活 → 测试

优点: 可视化操作, 适合入门
缺点: 手动编码多, 维护成本高
→ 这就是为什么SAP推出了RAP(后面会讲)

第二部分:OData进阶篇


第5章 标准对象扩展(Extension of SAP STD)

5.1 什么是标准扩展?

SAP提供了大量标准OData服务(如 MM_PUR_PURCHASEORDER_MAINT_SRV),但业务往往需要额外字段或逻辑。

扩展的三种层次:

1. 字段扩展 — 给标准实体增加自定义字段
2. 逻辑扩展 — 修改标准操作的行为(如校验、计算)
3. 服务扩展 — 给标准服务增加新的实体集

5.2 字段扩展:Append Structure

" 给标准表EKKO增加自定义字段
" 事务码: SE11 → 表EKKO → Append Structure

Append Structure: ZZ_EKKO_EXT
Fields:
ZZ_VENDOR_GRP TYPE CHAR10 " 供应商分组
ZZ_PRIORITY TYPE CHAR1 " 优先级
ZZ_REMARK TYPE CHAR200 " 备注

然后在对应的OData模型中:

1. SEGW打开标准项目
2. 找到Entity Type
3. 右键 → Append → 系统自动发现Append字段
4. 重新生成

5.3 逻辑扩展:BAdI / Enhancement

SAP Gateway 提供的标准增强点:

" BAdI: /IWBEP/IF_MGW_APPL_SRV_RUNTIME
" 用于增强OData服务的CRUD操作

" 示例:在创建采购订单前增加校验
METHOD /iwbep/if_mgw_appl_srv_runtime~create_entity.
" 先调用标准逻辑
super->create_entity( ... ).

" 自定义校验
IF es_data-zz_priority = 'H'.
" 高优先级订单,检查审批权限
AUTHORITY-CHECK OBJECT 'ZPO_PRIORITY'
ID 'PRIORITY' FIELD 'H'.
IF sy-subrc <> 0.
" 抛出OData错误
RAISE EXCEPTION TYPE /iwbep/cx_mgw_tech_exception
MESSAGE e001(zpo_msg) WITH 'No authority for high priority'.
ENDIF.
ENDIF.
ENDMETHOD.

5.4 服务扩展:增加实体集

场景:标准采购订单服务没有"订单审批历史"实体集

步骤:
1. SEGW创建新项目(不是修改标准项目)
2. 定义审批历史实体
3. 实现数据读取逻辑
4. 与标准服务关联(通过相同的Key字段)

或者使用 SEGW 的 "Extend" 功能直接扩展标准项目。

5.5 标准扩展最佳实践

规则1: 永远不要直接修改标准类
→ 用 BAdI / Enhancement / User Exit

规则2: 自定义字段统一用 ZZ_ 或 YY_ 前缀
→ 避免与SAP升级冲突

规则3: 扩展逻辑放在单独的Include中
→ 便于升级后合并

规则4: 充分利用 SAP 预定义的增强点
→ /IWBEP/* 命名空间的BAdI

第6章 OData服务消费

6.1 前端消费(SAPUI5)

在SAPUI5应用的 manifest.json 中配置数据源:

{
"dataSources": {
"mainService": {
"uri": "/sap/opu/odata/sap/ZDEMO_ODATA_SRV/",
"type": "OData",
"settings": {
"odataVersion": "2.0"
}
}
}
}

在Controller中读取数据:

// MVC模式 - 使用OData Model
var oModel = this.getOwnerComponent().getModel(); // 默认模型
var oView = this.getView();
oView.setModel(oModel);

// 绑定列表
var oList = this.byId("myList");
oList.bindItems({
path: "/FlightSet",
template: new sap.m.StandardListItem({
title: "{CARRID} - {CONNID}",
description: "Date: {FLDATE}, Price: {PRICE}"
})
});

// 带过滤的读取
var aFilters = [
new sap.ui.model.Filter("PRICE", "GT", 1000)
];
oList.bindItems({
path: "/FlightSet",
filters: aFilters,
template: ...
});

6.2 后端消费(ABAP)

" ABAP中调用OData服务(消费方)
DATA: lo_client TYPE REF TO if_http_client,
lv_url TYPE string,
lv_json TYPE string.

" 创建HTTP客户端
cl_http_client=>create_by_url(
EXPORTING url = 'https://sap-server/sap/opu/odata/sap/ZDEMO_SRV/'
IMPORTING client = lo_client ).

" 设置认证
lo_client->authenticate( username = 'USER' password = 'PASS' ).

" 发起GET请求
lo_client->request->set_method( if_http_request=>co_request_method_get ).
lo_client->request->set_header_field(
name = 'Accept'
value = 'application/json' ).

lo_client->send( ).
lo_client->receive( ).

lv_json = lo_client->response->get_cdata( ).
" 解析JSON...

6.3 第三方工具消费(Postman / curl)

# curl 示例
curl -u user:password \
"https://sap-server/sap/opu/odata/sap/ZDEMO_SRV/FlightSet?\$format=json" \
-H "Accept: application/json"

# 创建记录
curl -u user:password \
-X POST \
"https://sap-server/sap/opu/odata/sap/ZDEMO_SRV/FlightSet" \
-H "Content-Type: application/json" \
-H "X-CSRF-Token: Fetch" \
-d '{"CARRID":"LH","CONNID":"0400","FLDATE":"2025-01-01","PRICE":500}'

# 注意: POST/PUT/DELETE 需要 CSRF Token
# 第一步: GET 请求获取 token (Header: X-CSRF-Token: Fetch)
# 第二步: 携带 token 发起写操作

6.4 Batch请求

OData支持将多个操作打包成一个HTTP请求:

POST /sap/opu/odata/sap/ZDEMO_SRV/$batch
Content-Type: multipart/mixed; boundary=batch_boundary

--batch_boundary
Content-Type: application/http
Content-Transfer-Encoding: binary

GET FlightSet?$filter=CARRID eq 'LH' HTTP/1.1
Accept: application/json

--batch_boundary
Content-Type: application/http
Content-Transfer-Encoding: binary

POST FlightSet HTTP/1.1
Content-Type: application/json

{"CARRID":"AA","CONNID":"0001","FLDATE":"2025-06-01","PRICE":300}
--batch_boundary--

第7章 OData性能优化与最佳实践

7.1 常见性能问题

问题1: N+1查询
→ 读取100条订单,每条再查行项目 = 101次DB访问
→ 解决: 用 $expand 一次性加载关联数据

问题2: 全字段传输
→ 返回所有字段,包括不需要的大文本字段
→ 解决: 用 $select 只取需要的字段

问题3: 无分页
→ 一次返回10万条数据
→ 解决: 用 $top + $skip 分页

问题4: 缺少服务端过滤
→ 数据全量返回,前端过滤
→ 解决: 用 $filter 在服务端过滤

7.2 性能优化清单

✓ 读取逻辑中使用 PACKAGE_SIZE 分包
✓ 大数据量用分页 ($top/$skip)
✓ 只返回需要的字段 ($select)
✓ 合理使用 $expand(注意不要过度展开)
✓ 实现 $filter 时利用数据库索引
✓ 缓存不常变的数据(ETag / Cache-Control)
✓ Batch请求减少网络往返
✓ 监控事务码: /IWFND/ERROR_LOG(错误日志)
✓ 性能分析: /IWFND/TRACES(跟踪日志)

7.3 安全最佳实践

✓ 始终使用HTTPS
✓ 合理配置授权对象 (Authorization Object)
✓ 服务只暴露必要的实体集
✓ 使用X-CSRF-Token防止CSRF攻击
✓ 输入数据校验(防止SQL注入/XSS)
✓ 定期审查 /IWFND/MAINT_SERVICE 中的授权

第三部分:RAP模型篇


第8章 RAP概述与架构

8.1 为什么需要RAP?

SEGW的痛点:

1. 手动编写大量CRUD代码(DPC类方法重写)
2. 模型定义与业务逻辑分离,维护困难
3. 标准操作(如锁定、编号范围)需要手动实现
4. 不支持声明式行为定义

RAP的解决方案:

1. 声明式模型定义(CDS View)
2. 声明式行为定义(Behavior Definition)
3. 自动生成CRUD框架
4. 内置锁定、授权、草稿管理
5. 与ABAP开发工具深度集成(ADT/Eclipse)

8.2 RAP架构全景

┌─────────────────────────────────────────────────┐
│ Fiori / SAPUI5 │
│ (OData消费端) │
└────────────────────┬────────────────────────────┘
│ OData V2/V4
┌────────────────────┴────────────────────────────┐
│ SAP Business Technology Platform │
│ (ABAP Environment / S/4HANA) │
│ │
│ ┌─────────────────────────────────────────────┐ │
│ │ Business Object (业务对象) │ │
│ │ │ │
│ │ ┌───────────────────────────────────────┐ │ │
│ │ │ Behavior Definition (行为定义) │ │ │
│ │ │ - CRUD Operations │ │ │
│ │ │ - Actions (自定义操作) │ │ │
│ │ │ - Determinations (自动计算) │ │ │
│ │ │ - Validations (校验) │ │ │
│ │ └───────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌───────────────────────────────────────┐ │ │
│ │ │ Behavior Implementation (行为实现) │ │ │
│ │ │ - ABAP实现类 │ │ │
│ │ └───────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────┴──────────────────────────┐ │
│ │ CDS Data Model (数据模型) │ │
│ │ - Root Entity (根实体) │ │
│ │ - Child Entity (子实体) │ │
│ │ - Association (关联) │ │
│ │ - Composition (组合) │ │
│ └─────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────┘

8.3 RAP关键概念

Business Object (BO) = 业务对象
├── Root Entity = 根实体(如采购订单抬头)
├── Child Entity = 子实体(如采购订单行项目)
├── Behavior = 行为(CRUD + Actions + Validations)
└── Draft = 草稿(支持未保存也能编辑)

CDS View = Core Data Services 视图
├── 数据模型定义(类似数据库视图但更强大)
├── 支持关联、计算字段、注解
└── ABAP语法的一部分

Behavior Definition = 行为定义(.bdef文件)
├── 声明哪些操作允许(CRUD、Action)
├── 声明校验规则(Validation)
├── 声明自动计算(Determination)
└── 声明锁定策略

Behavior Implementation = 行为实现(ABAP类)
├── 实现Behavior Definition中声明的逻辑
└── 类似SEGW的DPC,但更结构化

第9章 CDS View建模

9.1 CDS基础语法

" 最简单的CDS View
@AbapCatalog.sqlViewName: 'ZFLIGHT_V'
@AbapCatalog.compiler.compareFilter: true
@AccessControl.authorizationCheck: #NOT_REQUIRED
@EndUserText.label: '航班数据视图'
define view ZI_Flight
as select from sflight
{
key carrid,
key connid,
key fldate,
price,
seatsmax,
seatsocc,
" 计算字段
seatsmax - seatsocc as available_seats
}

9.2 带注解的CDS View(RAP场景)

@EndUserText.label: '航班管理 - 根实体'
@AccessControl.authorizationCheck: #CHECK
define root view entity ZR_Flight
as select from sflight

association [0..1] to ZI_Carrier as _Carrier
on $projection.Carrid = _Carrier.Carrid

{
key carrid as Carrid,
key connid as Connid,
key fldate as Fldate,

@Semantics.amount.currencyCode: 'Currency'
price as Price,

@Semantics.currencyCode: true
currency as Currency,

seatsmax as Seatsmax,
seatsocc as Seatsocc,

" 关联
_Carrier
}

9.3 Composition(组合关系)

" 根实体:采购订单抬头
define root view entity ZR_PurchaseOrder
composition [0..*] of ZR_PurchaseOrderItem as _Items
{
key po_number as PoNumber,
vendor as Vendor,
total_amount as TotalAmount,
status as Status,
_Items " 行项目(一对多组合)
}

" 子实体:采购订单行项目
define view entity ZR_PurchaseOrderItem
as select from zpo_item_table
association to parent ZR_PurchaseOrder as _Header
on $projection.PoNumber = _Header.PoNumber
{
key po_number as PoNumber,
key item_number as ItemNumber,
material as Material,
quantity as Quantity,
_Header " 指向父实体
}

9.4 CDS注解速查

UI相关注解:
@UI.lineItem → 列表字段
@UI.identification → 详情页字段
@UI.selectionField → 搜索筛选字段
@UI.headerInfo → 标题栏信息
@UI.facet → 详情页面板分区

数据语义注解:
@Semantics.amount.currencyCode → 金额字段
@Semantics.currencyCode → 货币字段
@Semantics.quantity.unitOfMeasure → 数量字段
@Semantics.text → 文本字段

OData注解:
@ObjectModel.representativeKey → 代表键
@ObjectModel.semanticKey → 语义键
@Consumption.filter → 筛选配置

第10章 行为定义(Behavior Definition)

10.1 基础行为定义

" 文件: ZR_Flight.bdef
managed implementation in class ZBP_R_Flight unique;

define behavior for ZR_Flight alias Flight
" 使用SAP管理的持久化(推荐新开发使用managed模式)
persistent table zflight_db " 数据库表
draft table zflight_dra " 草稿表
etag master last_changed_at " 乐观锁

{
" CRUD操作
create; " 允许创建
update; " 允许更新
delete; " 允许删除

" 自定义操作
action Approve result [1] $self; " 审批操作

" 自动计算(创建时自动触发)
determination setDefaultValues on modify { create; }

" 校验(保存前自动触发)
validation checkPrice on save { field Price; }

" 字段级控制
field (readonly) Carrid, Connid; " 主键只读
field (mandatory) Price; " 价格必填
}

10.2 Determination(自动计算)

" 在Behavior Definition中声明
determination calculateTotal on modify { create, update; field Quantity, UnitPrice; }
determination setCreatedAt on modify { create; }

" 在Behavior Implementation中实现
METHOD calculateTotal.
" 读取当前实体数据
READ ENTITIES OF zr_purchaseorder IN LOCAL MODE
ENTITY PurchaseOrder
FIELDS ( Quantity UnitPrice TotalAmount )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_orders).

LOOP AT lt_orders INTO DATA(ls_order).
" 计算总金额
DATA(lv_total) = ls_order-Quantity * ls_order-UnitPrice.

" 回写
MODIFY ENTITIES OF zr_purchaseorder IN LOCAL MODE
ENTITY PurchaseOrder
UPDATE FIELDS ( TotalAmount )
WITH VALUE #( FOR order IN lt_orders (
%tky = order-%tky
TotalAmount = lv_total
) )
REPORTED DATA(lt_reported).
ENDLOOP.
ENDMETHOD.

10.3 Validation(校验)

" Behavior Definition中声明
validation checkPrice on save { field Price; }

" Behavior Implementation中实现
METHOD checkPrice.
READ ENTITIES OF zr_flight IN LOCAL MODE
ENTITY Flight
FIELDS ( Price Currency )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_flights).

LOOP AT lt_flights INTO DATA(ls_flight).
IF ls_flight-Price < 0.
" 报错
APPEND VALUE #( %tky = ls_flight-%tky ) TO failed-flight.
APPEND VALUE #(
%tky = ls_flight-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = '价格不能为负数' )
%element-Price = if_abap_behv=>mk-on
) TO reported-flight.
ENDIF.
ENDLOOP.
ENDMETHOD.

10.4 Action(自定义操作)

" 声明
action Approve result [1] $self;

" 实现
METHOD Approve.
READ ENTITIES OF zr_flight IN LOCAL MODE
ENTITY Flight
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_flights).

LOOP AT lt_flights INTO DATA(ls_flight).
" 更新状态
MODIFY ENTITIES OF zr_flight IN LOCAL MODE
ENTITY Flight
UPDATE FIELDS ( Status )
WITH VALUE #( (
%tky = ls_flight-%tky
Status = 'APPROVED'
) )
MAPPED DATA(lt_mapped).
ENDLOOP.

" 返回结果
result = VALUE #( FOR flight IN lt_flights (
%tky = flight-%tky
%param = flight
) ).
ENDMETHOD.

10.5 Managed vs Unmanaged模式

Managed模式(推荐新开发):
+ SAP自动处理CRUD(不写一行代码就能增删改查)
+ 自动处理持久化、锁定、草稿
+ 代码量少,维护简单
- 灵活性较低(复杂逻辑需override)

Unmanaged模式(复杂场景):
+ 完全控制持久化逻辑
+ 适合对接遗留系统/自定义表
+ 可以实现任意复杂的数据处理
- 需要手动实现所有CRUD

Projection模式(前端投影层):
+ 在BO上层做字段裁剪/增强
+ 多个前端可以有不同的Projection
+ 不影响底层BO逻辑

第11章 RAP业务对象开发实战

11.1 实战:员工管理业务对象

完整开发流程:

步骤1:创建数据库表

" 事务码: SE11
" 表: ZEMPLOYEE
@EndUserText.label : '员工主数据表'
define table zemployee {
key client : mandt not null;
key emp_id : char10 not null;
emp_name : char40;
department : char20;
position : char30;
salary : curr(11,2);
currency : waers;
hire_date : datum;
status : char1; " A=在职, I=离职, P=试用期
created_at : timestampl;
last_changed_at : timestampl;
}

" 草稿表: ZEMPLOYEE_DRA (结构相同 + draft字段)

步骤2:创建CDS数据模型

@EndUserText.label: '员工管理 - 根实体'
@AccessControl.authorizationCheck: #CHECK
define root view entity ZR_Employee
composition [0..*] of ZR_EmployeeSkill as _Skills

association [0..1] to ZI_Department as _Dept
on $projection.Department = _Dept.DeptId

{
key emp_id as EmpId,
emp_name as EmpName,
department as Department,
position as Position,

@Semantics.amount.currencyCode: 'Currency'
salary as Salary,

@Semantics.currencyCode: true
currency as Currency,

hire_date as HireDate,
status as Status,

created_at as CreatedAt,
last_changed_at as LastChangedAt,

_Skills,
_Dept
}
@EndUserText.label: '员工技能 - 子实体'
define view entity ZR_EmployeeSkill
as select from zemp_skill

association to parent ZR_Employee as _Employee
on $projection.EmpId = _Employee.EmpId

{
key emp_id as EmpId,
key skill_id as SkillId,
skill_name as SkillName,
skill_level as SkillLevel, " 1-5
certified as Certified,
_Employee
}

步骤3:创建行为定义

managed implementation in class ZBP_R_Employee unique;

define behavior for ZR_Employee alias Employee
persistent table zemployee
draft table zemployee_dra
etag master last_changed_at
{
create;
update;
delete;

action Promote result [1] $self;
action Terminate result [1] $self;

determination setDefaults on modify { create; }
determination updateStatus on modify { field Status; }

validation checkSalary on save { field Salary; }
validation checkHireDate on save { field HireDate; }

field (readonly) EmpId, CreatedAt, LastChangedAt;
field (mandatory) EmpName, Department, HireDate;

" 行项目
association _Skills { create; }
}

define behavior for ZR_EmployeeSkill alias Skill
persistent table zemp_skill
draft table zemp_skill_dra
{
update;
delete;

field (readonly) EmpId, SkillId;
field (mandatory) SkillName;

validation checkLevel on save { field SkillLevel; }

association _Employee;
}

步骤4:创建行为实现类

CLASS zbp_r_employee DEFINITION PUBLIC ABSTRACT FINAL FOR BEHAVIOR OF zr_employee.
ENDCLASS.

CLASS zbp_r_employee IMPLEMENTATION.

METHOD setDefaults.
" 新建员工默认状态为试用期
MODIFY ENTITIES OF zr_employee IN LOCAL MODE
ENTITY Employee
UPDATE FIELDS ( Status CreatedAt )
WITH VALUE #( FOR key IN keys (
%tky = key-%tky
Status = 'P'
CreatedAt = utclong_current( )
) ).
ENDMETHOD.

METHOD checkSalary.
READ ENTITIES OF zr_employee IN LOCAL MODE
ENTITY Employee
FIELDS ( Salary )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_employees).

LOOP AT lt_employees INTO DATA(ls_emp).
IF ls_emp-Salary < 0.
APPEND VALUE #( %tky = ls_emp-%tky ) TO failed-employee.
APPEND VALUE #(
%tky = ls_emp-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = '薪资不能为负数' )
%element-Salary = if_abap_behv=>mk-on
) TO reported-employee.
ENDIF.
ENDLOOP.
ENDMETHOD.

METHOD checkHireDate.
READ ENTITIES OF zr_employee IN LOCAL MODE
ENTITY Employee
FIELDS ( HireDate )
WITH CORRESPONDING #( keys )
RESULT DATA(lt_employees).

LOOP AT lt_employees INTO DATA(ls_emp).
IF ls_emp-HireDate > cl_abap_context_info=>get_system_date( ).
APPEND VALUE #( %tky = ls_emp-%tky ) TO failed-employee.
APPEND VALUE #(
%tky = ls_emp-%tky
%msg = new_message_with_text(
severity = if_abap_behv_message=>severity-error
text = '入职日期不能晚于今天' )
%element-HireDate = if_abap_behv=>mk-on
) TO reported-employee.
ENDIF.
ENDLOOP.
ENDMETHOD.

METHOD Promote.
" 提升: 试用期→在职
READ ENTITIES OF zr_employee IN LOCAL MODE
ENTITY Employee ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_employees).

MODIFY ENTITIES OF zr_employee IN LOCAL MODE
ENTITY Employee
UPDATE FIELDS ( Status )
WITH VALUE #( FOR emp IN lt_employees WHERE ( Status = 'P' ) (
%tky = emp-%tky
Status = 'A'
) ).

result = VALUE #( FOR emp IN lt_employees (
%tky = emp-%tky
%param = CORRESPONDING #( emp )
) ).
ENDMETHOD.

METHOD Terminate.
" 离职
READ ENTITIES OF zr_employee IN LOCAL MODE
ENTITY Employee ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(lt_employees).

MODIFY ENTITIES OF zr_employee IN LOCAL MODE
ENTITY Employee
UPDATE FIELDS ( Status )
WITH VALUE #( FOR emp IN lt_employees (
%tky = emp-%tky
Status = 'I'
) ).

result = VALUE #( FOR emp IN lt_employees (
%tky = emp-%tky
%param = CORRESPONDING #( emp )
) ).
ENDMETHOD.

ENDCLASS.

步骤5:创建服务定义和服务绑定

" 服务定义 (Service Definition)
@EndUserText.label: '员工管理OData服务'
define service ZUI_Employee {
expose ZR_Employee;
expose ZR_EmployeeSkill;
}

" 服务绑定 (Service Binding)
" 在ADT中右键 → New → Service Binding
" 类型: OData V2 - UI
" 绑定: ZUI_Employee
" 激活后可预览

步骤6:测试预览

ADT中: 右键Service Binding → Publish
右键Service Binding → Open Fiori Elements Preview

即可看到完整的Fiori应用:
- 列表页(带搜索、过滤、排序)
- 详情页(带行项目)
- 创建/编辑弹窗
- 自定义操作按钮

第四部分:Fiori前端篇


第12章 SAP Fiori基础

12.1 什么是Fiori?

SAP Fiori = SAP的新一代用户体验设计体系

包含三部分:
1. 设计语言 → 视觉规范、交互模式
2. 技术框架 → SAPUI5 JavaScript框架
3. 应用类型 → Fiori Elements / Freestyle / Launchpad

12.2 Fiori设计原则

1. Role-based       → 基于角色,只展示用户需要的
2. Responsive → 响应式,适配PC/平板/手机
3. Coherent → 一致性,所有应用风格统一
4. Simple → 简单,1个页面/1个任务
5. Delightful → 愉悦,动画反馈

12.3 SAP Fiori Launchpad

Launchpad = 应用入口/桌面

组成:
├── Tile (磁贴) → 应用入口图标
├── Catalog (目录) → 磁贴分类
├── Group (分组) → 用户看到的分组
├── Role (角色) → 控制可见性
└── Spaces & Pages → 新版布局(Fiori 3.0+)

配置事务码:
- /UI2/FLP → Launchpad主页
- /UI2/FLPD_CUST → 自定义目录/磁贴
- /UI2/FLPD_CONF → 配置层

12.4 应用类型对比

┌──────────────┬──────────────────┬──────────────────┐
│ │ Fiori Elements │ Freestyle │
├──────────────┼──────────────────┼──────────────────┤
│ 前端代码量 │ 几乎为零 │ 完全手写 │
│ 灵活性 │ 有限(模板驱动) │ 完全自由 │
│ 适用场景 │ CRUD标准页面 │ 复杂交互页面 │
│ 开发技能 │ 只需后端CDS+注解 │ 需要JS/SAPUI5 │
│ 维护成本 │ 低 │ 高 │
│ 推荐度 │ ★★★★★ 入门首选 │ ★★★☆ 进阶必学 │
└──────────────┴──────────────────┴──────────────────┘

第13章 SAPUI5核心组件

13.1 SAPUI5项目结构

myapp/
├── webapp/
│ ├── manifest.json → 应用描述(路由、模型、数据源)
│ ├── Component.js → 应用入口
│ ├── index.html → HTML入口页
│ │
│ ├── view/ → 视图
│ │ ├── Main.view.xml → 主视图
│ │ ├── List.view.xml → 列表视图
│ │ └── Detail.view.xml → 详情视图
│ │
│ ├── controller/ → 控制器
│ │ ├── Main.controller.js
│ │ ├── List.controller.js
│ │ └── Detail.controller.js
│ │
│ ├── model/ → 模型
│ │ ├── models.js
│ │ └── formatter.js
│ │
│ ├── i18n/ → 国际化
│ │ └── i18n.properties
│ │
│ └── css/
│ └── style.css

├── package.json
└── ui5.yaml

13.2 MVC模式

// === Model (manifest.json) ===
{
"sap.app": {
"dataSources": {
"mainService": {
"uri": "/sap/opu/odata/sap/ZEMPLOYEE_SRV/",
"type": "OData"
}
}
}
}

// === View (List.view.xml) ===
<mvc:View xmlns:mvc="sap.ui.core.mvc" xmlns="sap.m"
controllerName="myapp.controller.List">
<Page title="员工列表">
<content>
<List id="employeeList" items="{/EmployeeSet}"
growing="true" growingThreshold="20">
<StandardListItem
title="{EmpName}"
description="{Department} - {Position}"
type="Navigation"
press=".onItemPress"/>
</List>
</content>
</Page>
</mvc:View>

// === Controller (List.controller.js) ===
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/ui/model/Filter",
"sap/ui/model/FilterOperator"
], function(Controller, Filter, FilterOperator) {
"use strict";

return Controller.extend("myapp.controller.List", {

onInit: function() {
// 控制器初始化
},

onSearch: function(oEvent) {
// 搜索功能
var sQuery = oEvent.getParameter("newValue");
var aFilters = [];
if (sQuery) {
aFilters.push(new Filter("EmpName", FilterOperator.Contains, sQuery));
}
this.byId("employeeList").getBinding("items").filter(aFilters);
},

onItemPress: function(oEvent) {
// 导航到详情页
var sPath = oEvent.getSource().getBindingContext().getPath();
this.getOwnerComponent().getRouter().navTo("detail", {
path: sPath.substring(1) // 去掉开头的 /
});
}
});
});

13.3 常用UI5控件速查

容器控件:
sap.m.Page → 页面
sap.m.IconTabBar → 图标标签页
sap.uxap.ObjectPageLayout → 对象页面

列表控件:
sap.m.List → 列表
sap.m.Table → 表格
sap.m.StandardListItem → 标准列表项

表单控件:
sap.m.Input → 输入框
sap.m.Select → 下拉选择
sap.m.DatePicker → 日期选择
sap.m.Switch → 开关
sap.ui.layout.form.SimpleForm → 表单布局

图表控件:
sap.viz.ui5.* → 可视化图表
sap.suite.ui.microchart.* → 微型图表

弹窗控件:
sap.m.Dialog → 对话框
sap.m.MessageBox → 消息框
sap.m.MessageToast → 提示消息

第14章 Fiori Elements vs Freestyle

14.1 Fiori Elements开发模式

Fiori Elements = 零前端代码,纯靠CDS注解驱动UI

" 通过注解控制列表页显示
@UI.lineItem: [
{ position: 10, label: '工号' },
{ position: 20, label: '姓名' },
{ position: 30, label: '部门' },
{ position: 40, label: '状态',
criticality: 'StatusCriticality' }
]

Fiori Elements支持的页面模板:

1. List Report (列表报表)
- 搜索/过滤栏 + 列表/表格
- 适合: 主数据查询、报表

2. Object Page (对象页面)
- 标签页分区的详情页
- 适合: 详细信息编辑

3. Overview Page (概览页)
- 卡片式仪表盘
- 适合: 管理驾驶舱

4. Worklist (工作列表)
- 带状态的操作列表
- 适合: 审批任务、待办事项

5. Analytical List Page (分析列表页)
- 图表 + 列表联动
- 适合: 数据分析

14.2 Freestyle SAPUI5开发

当Fiori Elements无法满足需求时,使用Freestyle完全自定义:

// 自定义Fiori应用示例:可视化看板
sap.ui.define([
"sap/ui/core/mvc/Controller",
"sap/m/MessageToast"
], function(Controller, MessageToast) {
"use strict";

return Controller.extend("myapp.controller.Kanban", {

onInit: function() {
// 初始化拖拽看板
this._initKanbanBoard();
},

onDrop: function(oEvent) {
// 拖拽卡片到新列
var oCard = oEvent.getParameter("draggedControl");
var oTarget = oEvent.getParameter("droppedControl");
var sNewStatus = oTarget.data("status");

// 调用OData更新状态
var sPath = oCard.getBindingContext().getPath();
this.getView().getModel().setProperty(sPath + "/Status", sNewStatus);

MessageToast.show("状态已更新为: " + sNewStatus);
}
});
});

14.3 选择决策

需求评估流程:

你的页面是标准CRUD操作吗?
├── 是 → 用 Fiori Elements(首选)
│ └── 需要少量自定义? → Extension Points
└── 否 → 需要复杂交互(拖拽/图表/实时数据)?
├── 是 → 用 Freestyle SAPUI5
└── 否 → 评估是否可用Fiori Elements + 自定义Action

第15章 全栈开发实战项目

15.1 项目:采购申请审批应用

后端:RAP开发

" 1. 数据模型
define root view entity ZR_PurchaseRequest
composition [0..*] of ZR_PRItem as _Items
{
key pr_number as PrNumber,
description as Description,
requester as Requester,
request_date as RequestDate,
total_amount as TotalAmount,
currency as Currency,
status as Status, " NEW/APPROVED/REJECTED
approved_by as ApprovedBy,
approved_at as ApprovedAt,
_Items
}

" 2. 行为定义
define behavior for ZR_PurchaseRequest alias PR
{
create;
update;
delete;

action Approve result [1] $self;
action Reject result [1] $self;

determination setStatus on modify { create; }
validation checkAmount on save { field TotalAmount; }

field (readonly) PrNumber, Status, ApprovedBy, ApprovedAt;
field (readonly) TotalAmount; " 自动计算
}

前端:Fiori Elements + 注解

" UI注解 - List Report页面
@UI: {
headerInfo: {
typeName: '采购申请',
typeNamePlural: '采购申请',
title: { value: 'Description' }
},

lineItem: [
{ value: 'PrNumber', label: '申请号', position: 10 },
{ value: 'Description', label: '描述', position: 20 },
{ value: 'Requester', label: '申请人', position: 30 },
{ value: 'TotalAmount', label: '金额', position: 40 },
{ value: 'Status', label: '状态', position: 50,
criticality: 'StatusCriticality' }
],

selectionFields: [
{ value: 'Status' },
{ value: 'Requester' }
],

facet: [
{
label: '基本信息',
id: 'GeneralInfo',
type: #IDENTIFICATION_REFERENCE,
position: 10
},
{
label: '行项目',
id: 'Items',
type: #LINEITEM_REFERENCE,
position: 20,
targetElement: '_Items'
}
]
}

15.2 全栈开发清单

后端开发:
□ 创建数据库表 + 草稿表
□ 开发CDS数据模型(根实体 + 子实体)
□ 定义行为(Behavior Definition)
□ 实现行为逻辑(Behavior Implementation)
□ 创建服务定义 + 服务绑定
□ 激活并测试OData服务
□ 编写单元测试

前端开发:
□ 设计UI注解(Fiori Elements)
□ 创建Fiori应用项目
□ 配置manifest.json(路由、数据源)
□ 测试标准功能(CRUD、搜索、过滤)
□ 必要时添加自定义Extension
□ 响应式测试(PC/平板/手机)

部署:
□ Transport Request → 生产系统
□ Launchpad配置(Catalog/Group/Tile)
□ 角色授权配置
□ 性能测试
□ 用户验收测试

附录


A. 学习路线图

初学者 (0-3个月):
├── 第1-2周: SAP ABAP基础(如果还不熟悉)
├── 第3-4周: OData概念 + SEGW入门
├── 第5-6周: 第一个OData服务
├── 第7-8周: CDS View基础
└── 第9-12周: RAP基础 + Fiori Elements

进阶 (3-6个月):
├── RAP Behavior Definition + Implementation
├── 标准对象扩展
├── SAPUI5 Freestyle开发
├── OData性能优化
└── 授权与安全

高级 (6-12个月):
├── RAP高级特性(Draft、ETag、Authority)
├── CAP (Cloud Application Programming)
├── SAP BTP + Cloud Foundry
├── 复杂Fiori应用架构
└── CI/CD + 自动化测试

B. 常用事务码

后端:
SEGW → OData服务建模(旧方式)
/IWFND/MAINT_SERVICE → 服务注册与激活
/IWFND/GW_CLIENT → Gateway测试客户端
/IWFND/ERROR_LOG → 错误日志
/IWFND/TRACES → 性能跟踪
SE11 → 数据库表/结构定义
SE24 → 类构建器
SE38 → ABAP编辑器

前端:
/UI2/FLP → Fiori Launchpad
/UI2/FLPD_CUST → 磁贴目录管理
/UI2/SEMOBJ → 语义对象
/UI2/TRANSPORT → Launchpad传输

C. 开发工具

后端开发:
ADT (ABAP Development Tools) → Eclipse插件, RAP开发必需
SAP GUI → 传统开发, SEGW等

前端开发:
SAP Business Application Studio (BAS) → 云端IDE
VS Code + SAP Fiori Tools Extension → 本地IDE
SAP Fiori Tools CLI → 命令行工具

测试工具:
Postman → OData API测试
SAP Gateway Client → 内置测试
Fiori Elements Preview → ADT内置预览

D. 推荐学习资源

官方:
- SAP Help Portal: help.sap.com
- SAP Developer Center: developers.sap.com
- SAPUI5 Demo Kit: sapui5.hana.ondemand.com
- openSAP (免费课程): open.sap.com

社区:
- SAP Community: community.sap.com
- SAP Blog: blogs.sap.com
- GitHub SAP Samples: github.com/SAP-samples

认证:
- C_S4HDEV (S/4HANA开发)
- C_EPMBPC (EPM认证)