refactor: 本仓升为唯一源(原 sap-cli 源码仓归档)

方向反转:此前 SKILL.md 是「模板渲染产物」、sap-cli 是源;现 sap-cli 归档,
sap-cli-skill 承接开发与分发,SKILL.md 回归手工维护的正本。

迁移(来自 sap-cli,共 104 文件):
- tests/           692 例测试(15 个文件的内联 sys.path 改指 assets/)
- openspec/        SDD 规格与归档变更(42 文件)
- docs/            开发文档与 ADT 原理(含 dev/CLAUDE.md、AGENTS.md)
- .claude/         rules 副本 + settings.json(供 Claude Code)
- .github/ .hermes/ .pre-commit-config.yaml .editorconfig CLAUDE.md
- scripts/ 保持仅 setup.py(pack_skill.py 已随旧仓归档,不迁)

修复(迁移暴露的真实缺陷):
- assets/pyproject.toml 的 build-backend 写作 `setuptools.backends._legacy:_Backend`,
  该模块在 setuptools 中不存在 → `pip install -e` 从来装不上。改为 build_meta。
  实测:临时 venv 安装成功,sap-cli --help 正常列出 31 个命令
- pyproject readme 指向不存在的 assets/README.md(editable 安装会失败)→ 改内联文本
- pyproject urls 改指 sap-cli-skill

机制调整:
- .github/workflows/ci.yml 适配 assets/ 布局;顶部注明该工作流仅 GitHub 执行,
  本仓在 Gitee 不会自动跑
- pre-commit 增本地测试门禁(Gitee 上真正生效的那道)
- .gitignore 合并旧仓完整规则(保留 log/ 下 md 知识库入库,只忽略运行日志)
- 大文件上限 100KB→1MB(架构图 512KB)

守卫测试 tests/unit/test_repo_guards.py(10 → 18 例):
- SKILL.md 须记录 parser 全部 CLI 命令 / 铁律 1-5 须为真实小节标题 / 示例不得违反铁律 5
- references/ 规则齐备;.claude/rules 与 references 必须一致(实测抓到一次真实漂移)
- VERSION == sapcli.__version__ == README 版本
- 仓内不得再出现 pack_skill.py / skill-src(防废弃流程回潮)

698 tests OK;editable 安装与 CLI 入口经临时 venv 实测通过。
docs/RELEASING.md 重写为单源开发流程。
This commit is contained in:
吴让宇
2026-09-11 00:40:15 +08:00
parent e786742bcb
commit c5905a5b1e
104 changed files with 20744 additions and 10 deletions
+215
View File
@@ -0,0 +1,215 @@
---
title: ADT 架构与通信原理
created: 2026-05-18
tags:
- SAP
- ADT
- architecture
- REST
parent: "[[sap-cli/README|SAP ADT 学习笔记总览]]"
---
# ADT 架构与通信原理
> [!abstract] 核心洞察
> ADT 本质上是一个 **REST API 客户端**。SAP 通过在 ABAP 服务器上构建一套完整的 REST 层(`/sap/bc/adt/*`),然后开发 Eclipse 插件来消费这些 API,实现了前后端的完全解耦。
## 1. 三层架构模型
ADT 采用经典的 **客户端/服务器** 分层架构:
```
┌───────────────────────────────────────────────────────────┐
│ Layer 1: IDE 客户端层 (Eclipse / VS Code / 自定义客户端) │
│ │
│ • Eclipse ADT 插件(Java 实现) │
│ • VS Code 扩展(2026年起支持,复用 Eclipse 代码库) │
│ • 任何 HTTP 客户端(Python requests / JS fetch / AI Agent
│ • 客户端 UI 无关代码:2.9 百万行! │
└────────────────────────┬──────────────────────────────────┘
│ HTTP(S) + Basic Auth
┌───────────────────────────────────────────────────────────┐
│ Layer 2: REST 通信层 (/sap/bc/adt/*) │
│ │
│ • 基于 HTTP 协议的 RESTful API │
│ • 请求格式: XML / ATOM Feed / JSON │
│ • CSRF Token 机制(写操作必须先获取 Token) │
│ • 状态会话管理(Stateful HTTP Session
│ • 服务发现端点: /sap/bc/adt/discovery │
└────────────────────────┬──────────────────────────────────┘
│ ICF Handler 路由
┌───────────────────────────────────────────────────────────┐
│ Layer 3: SAP ABAP 后端 │
│ │
│ • ICF (Internet Communication Framework) 处理请求路由 │
│ • ADT REST Framework (增强点 SADT_REST_RFC_APPLICATION) │
│ • ABAP Runtime(语法检查、编译、激活) │
│ • Repository(仓库对象存储) │
│ • CTS(传输管理系统) │
│ • ATC(代码质量检查引擎) │
└───────────────────────────────────────────────────────────┘
```
## 2. REST 通信层详解
### 2.1 端点结构
所有 ADT 操作通过 `/sap/bc/adt/` 前缀的端点进行通信:
| 端点路径 | 功能 | 最低版本要求 |
|----------|------|-------------|
| `/sap/bc/adt/discovery` | 服务发现(Atom Feed | NW 7.31 SP04 |
| `/sap/bc/adt/repository` | 仓库对象搜索与浏览 | NW 7.31 SP04 |
| `/sap/bc/adt/oo` | 类与接口管理 | NW 7.31 SP04 |
| `/sap/bc/adt/programs` | 程序与 Include | NW 7.31 SP04 |
| `/sap/bc/adt/functions` | 函数组与函数模块 | NW 7.31 SP04 |
| `/sap/bc/adt/ddic` | ABAP Dictionary 访问 | NW 7.40 SP02 |
| `/sap/bc/adt/datapreview` | 表数据预览(SE16N 替代) | NW 7.40 SP05 |
| `/sap/bc/adt/cts` | 传输请求管理 | NW 7.40 SP02 |
| `/sap/bc/adt/activation` | 对象激活 | NW 7.31 SP04 |
| `/sap/bc/adt/atc` | ABAP Test Cockpit | NW 7.50+ |
### 2.2 通信协议特征
> [!important] 关键协议特征
**认证方式:**
- **Basic Authentication**(最常用)— 通过 HTTPS 发送用户名/密码
- **X.509 证书认证** — 生产环境推荐
- **SAP OAuth 2.0** — 高级场景
**CSRF Token 机制:**
```
1. 客户端发送 GET 请求,Header: x-csrf-token = "Fetch"
2. 服务器返回响应,Header 中包含 CSRF Token
3. 后续所有写操作(POST/PUT/DELETE)必须携带此 Token
4. Token 过期时(HTTP 403),重新获取并重试
```
**会话管理:**
- ADT 使用 **有状态的 HTTP 会话**Stateful Session
- 通过 Cookie 维持会话,用于对象锁定等操作
- `sap-client` Header 指定 SAP 客户端编号
**数据格式:**
- 请求/响应主要使用 **XML** 格式
- 部分端点支持 `text/plain`(源代码读写)
- 服务发现使用 **ATOM Feed** 格式
## 3. 服务发现机制
> [!tip] 核心概念
> ADT 采用 **服务发现** 模式,而非硬编码 URL。客户端可以通过 `/sap/bc/adt/discovery` 端点动态获取所有可用的服务及其 URI。
### 发现流程
```
1. 客户端请求 GET /sap/bc/adt/discovery
→ 返回 Atom Feed,列出所有已注册的服务集合
2. 每个服务集合包含:
- href: 服务的 URI 路径
- title: 服务的描述名称
- category: 服务的分类(scheme + term
3. 客户端根据 category scheme/term 查找需要的具体服务 URI
→ 避免硬编码,支持不同系统版本的兼容性
```
### 发现端点示例响应结构
```xml
<app:collection href="/sap/bc/adt/oo/classes">
<atom:title>Classes</atom:title>
<category scheme="http://www.sap.com/adt/..." term="classes"/>
</app:collection>
```
## 4. 后端扩展机制
### ADT REST Framework 的扩展点
SAP 提供了 **增强点 (Enhancement Spot)** 来扩展 ADT
| BAdI | 用途 |
|------|------|
| `BADI_ADT_DISCOVERY_PROVIDER` | 实现自定义发现提供者,注册新的 URI |
| `SADT_REST_RFC_APPLICATION` | 实现自定义 REST 资源处理 |
### 扩展架构
```
客户端请求
RFC HandlerADT REST Framework 核心)
├── 根据 URI 路径匹配对应的 BAdI 实现
├── 调用 Discovery Controller(发现控制器)
│ └── 提供 URI schema(如 /ztransportutils
└── 调用 Resource Controller(资源控制器)
└── 处理具体的 GET/POST/PUT/DELETE 操作
```
### 创建自定义 ADT 资源的步骤
1. **创建 Discovery Controller** — 继承 `CL_ADT_RES_APP_BASE`,定义 URI 路径
2. **注册 BAdI `BADI_ADT_REST_RFC_APPLICATION`** — 链接到 Discovery Controller
3. **创建 Resource Application** — 继承 `CL_ADT_DISC_RES_APP_BASE`
4. **创建 Resource Controller** — 继承 `CL_ADT_REST_RESOURCE`,实现 GET/POST 方法
5. **注册 BAdI `BADI_ADT_DISCOVERY_PROVIDER`** — 使资源可被发现
## 5. 架构演进:从 Eclipse 到 VS Code
> [!success] 架构演进策略(2025年 SAP 官方博客)
SAP 面临的核心挑战:
- **290万行** 客户端 UI 无关代码
- **88个** 独特的对象类型编辑器(仅 SAP BTP ABAP 环境)
### 解决方案 1Language Server 复用
借鉴 Java VS Code 扩展的思路 — 将 Eclipse ADT 插件 "包装" 为 Language Server
```
VS Code ←→ Language Server Protocol (LSP) ←→ Eclipse ADT 代码库 (2.9M 行)
```
- 无需从头重写客户端代码
- Eclipse 和 VS Code 共享同一代码库
- 连接任何 Eclipse 支持的 ABAP 服务器版本
### 解决方案 2Server-Driven Development
从 2020 年起,新对象类型采用 **服务器驱动开发** 模式:
- UI 逻辑完全在 ABAP 端定义(类似 Dynpro / Fiori Elements
- 客户端只需 **两个渲染引擎**
- **表单渲染器** — 用于表单类对象
- **源码渲染器** — 用于源码类对象
- 不再需要为每个 IDE 实现 88 个独立编辑器
## 6. 权限控制
| 权限对象 | 控制范围 |
|----------|---------|
| `S_ADT_RES` | 所有 ADT API 访问(端点级别) |
| `S_DEVELOP` | 对象读写操作 |
| `S_CTS_ADMI` | 传输管理 |
| `S_TRANSPRT` | 传输发布 |
## 🔗 相关笔记
- [[sap-cli/02.查询代码-原理|02.查询代码 - 原理]]
- [[sap-cli/03.修改代码-原理|03.修改代码 - 原理]]
- [[sap-cli/04.检查代码-原理|04.检查代码 - 原理]]
- [[sap-cli/05.提交与传输-原理|05.提交与传输 - 原理]]
## 📚 参考来源
- [Behind the Design: How We Transformed the ABAP Development Tools](https://community.sap.com/t5/technology-blog-posts-by-sap/behind-the-design-how-we-transformed-the-abap-development-tools/ba-p/14258121)
- [Creating an ABAP in Eclipse Plug-in Using the ADT SDK](https://community.sap.com/t5/application-development-and-automation-blog-posts/creating-a-abap-in-eclipse-plug-in-using-the-adt-sdk-part-2/ba-p/13093582)
- [End-to-End SAP Automation with ADT REST Services](https://medium.com/@onuryz.itu/end-to-end-sap-automation-with-adt-rest-services-and-ai-a-modern-alternative-to-gui-scripting-343ea86064ea)
+198
View File
@@ -0,0 +1,198 @@
---
title: 查询代码 - 原理
created: 2026-05-18
tags:
- SAP
- ADT
- search
- REST
parent: "[[sap-cli/README|SAP ADT 学习笔记总览]]"
prev: "[[sap-cli/01.ADT架构与通信原理|01.ADT架构与通信原理]]"
next: "[[sap-cli/03.修改代码-原理|03.修改代码 - 原理]]"
---
# 查询代码 — 原理
> [!abstract] 概述
> ADT 提供三种主要的代码查询能力:**仓库对象搜索**(按名称/类型)、**源代码全文搜索**(按代码片段)和 **Where-Used 导航**(引用追踪)。这些功能全部通过 `/sap/bc/adt/repository` 端点的 REST API 实现。
## 1. 仓库对象搜索 (Repository Object Search)
### 原理
通过 REST API 向 `/sap/bc/adt/repository/informationsystem/search` 发送 GET 请求,在后端 ABAP Repository 中搜索对象。
### REST API 调用
```
GET /sap/bc/adt/repository/informationsystem/search
?operation=quickSearch
&query=<搜索关键词>
&maxResults=50
&objectType=<对象类型> (可选)
```
### 参数说明
| 参数 | 说明 | 示例 |
|------|------|------|
| `operation` | 搜索操作类型 | `quickSearch` |
| `query` | 搜索关键词 | `ZCL_SALES*` |
| `maxResults` | 最大返回数量 | `50` |
| `objectType` | 对象类型过滤 | `CLAS`, `PROG`, `FUGR`, `TABL`, `DTEL`, `DOMA` |
### 响应格式
```xml
<atom:feed xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:is="http://www.sap.com/adt/repository/informationsystem">
<is:object name="ZCL_SALES_ORDER"
type="CLAS"
uri="/sap/bc/adt/oo/classes/zcl_sales_order"
packageName="Z_SALES"/>
</atom:feed>
```
### 后端处理流程
```
1. Eclipse 发送 GET 请求到 repository search 端点
2. ICF Handler 将请求路由到 ABAP Repository Search 类
3. 搜索引擎扫描 ABAP Repository(类似 SE80 搜索)
4. 返回匹配对象的元数据:名称、类型、URI、包名
5. Eclipse 在 Project Explorer 中展示搜索结果
6. 双击结果 → 通过 URI 加载对象的源代码编辑器
```
### 对应的 SAP GUI 事务码
| ADT 搜索 | SAP GUI 等效 |
|----------|-------------|
| 快速搜索 | `SE80` — Repository Browser |
| 按类型搜索 | `SE24`(类) / `SE38`(程序) / `SE37`(函数) |
| 按包搜索 | `SE80` — Package 节点 |
## 2. 源代码全文搜索 (ABAP Source Code Search)
### 原理
> [!info] 前置条件
> 源代码搜索需要激活业务功能 **`SRIS_SOURCE_SEARCH`**。
ADT 提供了一个 **搜索控制台**,允许在整个 ABAP 源码仓库中进行全文搜索,类似 IDE 中的 "Search in Files" 功能。
### 工作流程
```
1. 用户在 Eclipse 搜索控制台输入代码片段
2. 客户端发送搜索请求到 ADT REST 端点
3. 后端搜索引擎执行全文本索引查找
4. 返回匹配的源码位置(对象名 + 行号)
5. Eclipse 展示搜索结果,点击可直接跳转到编辑器
```
### 搜索能力
- **任意代码片段搜索** — 搜索变量名、方法调用、SQL 语句等
- **正则表达式支持** — 模式匹配搜索
- **范围限定** — 可限定搜索范围(包、对象类型等)
- **直接导航** — 搜索结果可直接跳转到对应的开发对象
### 对应 SAP GUI 的优势
在传统 SAP GUI 中,**不存在等效的全文本源码搜索功能**。这是 ADT 相对于 SE80 的重大改进之一。
## 3. 对象浏览与导航
### Repository Browser
```
GET /sap/bc/adt/repository/informationsystem
```
浏览 ABAP Repository 的层级结构:
- **按包浏览** — 展开包 → 查看其中所有对象
- **按类型浏览** — 按类、程序、表等类型分类
- **按传输层浏览** — 按传输层组织查看
### 对象详情获取
```
GET /sap/bc/adt/oo/classes/{class_name}
GET /sap/bc/adt/programs/programs/{program_name}
GET /sap/bc/adt/functions/groups/{group_name}
```
每个对象类型的详细信息通过对应的端点获取。
## 4. Where-Used 导航
### 原理
Where-Used 分析追踪对象在整个仓库中的引用关系。例如:查找哪些程序使用了某个类或方法。
### 处理流程
```
1. 用户在编辑器中选中一个标识符(类名/方法名/变量)
2. Eclipse 发送 Where-Used 请求到 Repository 端点
3. 后端 ABAP Runtime 执行交叉引用分析
4. 返回所有引用位置(对象 + 行号 + 列号)
5. Eclipse 展示引用列表,支持直接导航
```
### 对应的 SAP GUI 事务码
| ADT Where-Used | SAP GUI 等效 |
|----------------|-------------|
| 引用查找 | `SE80` → Where-Used List |
| 类型层次 | `SE24` → Superclass/Subclass |
## 5. 数据预览 (Data Preview)
### 原理
ADT 提供了表数据读取能力,可以替代传统的 SE16N 事务码。
### REST API 调用
```
POST /sap/bc/adt/datapreview/{table_name}
Content-Type: application/xml
<?xml version="1.0" encoding="utf-8"?>
<dataPreview:tableSettings
xmlns:dataPreview="http://www.sap.com/adt/dataPreview">
<dataPreview:column>*</dataPreview:column>
<dataPreview:maxRows>100</dataPreview:maxRows>
</dataPreview:tableSettings>
```
### 优势对比
| 特性 | SE16N (SAP GUI) | ADT Data Preview |
|------|-----------------|-------------------|
| 访问方式 | 事务码,需要 SAP GUI | REST API,任何 HTTP 客户端 |
| 数据格式 | ALV Grid(屏幕渲染) | 结构化 XML/JSON |
| 并行查询 | 不支持 | 支持多线程并发 |
| 自动化 | 困难 | 可编程访问 |
| 平台限制 | Windows + SAP GUI | 任何平台 |
## 🔗 相关笔记
- [[sap-cli/01.ADT架构与通信原理|01.ADT架构与通信原理]]
- [[sap-cli/03.修改代码-原理|03.修改代码 - 原理]]
## 📚 参考来源
- [SAP Help Portal - Searching ABAP Source Code](https://help.sap.com/docs/abap-cloud/abap-development-tools-user-guide/searching-abap-source-code)
- [ABAP Source Code Search - SAP Community](https://community.sap.com/t5/application-development-blog-posts/abap-sourcecode-search/bc-p/13074593)
- [End-to-End SAP Automation with ADT REST Services](https://medium.com/@onuryz.itu/end-to-end-sap-automation-with-adt-rest-services-and-ai-a-modern-alternative-to-gui-scripting-343ea86064ea)
+257
View File
@@ -0,0 +1,257 @@
---
title: 修改代码 - 原理
created: 2026-05-18
tags:
- SAP
- ADT
- edit
- lock
- activation
- REST
parent: "[[sap-cli/README|SAP ADT 学习笔记总览]]"
prev: "[[sap-cli/02.查询代码-原理|02.查询代码 - 原理]]"
next: "[[sap-cli/04.检查代码-原理|04.检查代码 - 原理]]"
---
# 修改代码 — 原理
> [!abstract] 核心流程
> ADT 中修改代码遵循 **锁定 → 编辑 → 保存 → 解锁 → 激活** 的严格工作流。每一步都通过 REST API 调用对应的 ABAP 后端服务完成。
## 1. 完整工作流
```
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 1.锁定 │───▶│ 2.编辑 │───▶│ 3.保存 │───▶│ 4.解锁 │───▶│ 5.激活 │
│ Lock │ │ Edit │ │ Save │ │ Unlock │ │ Activate│
└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
POST lock GET source PUT source POST unlock POST activate
→ lockHandle → 纯文本 + lockHandle + lockHandle → 激活结果
```
> [!warning] 重要约束
> - **必须先锁定** 才能修改代码,否则 PUT 请求会被拒绝
> - **锁定句柄 (lockHandle)** 是贯穿整个编辑流程的关键凭证
> - **必须激活** 后代码才会在运行时生效(保存 ≠ 激活)
## 2. Step 1: 锁定对象 (Lock)
### 原理
ABAP 使用 **乐观锁 (Optimistic Lock)** 机制。当开发者打开一个对象进行编辑时,ADT 向后端发送锁定请求,获取一个唯一的 `lockHandle`
### REST API 调用
```
POST /sap/bc/adt/oo/classes/{class_name}
?_action=LOCK
&accessMode=MODIFY
```
### 响应
```xml
<adtcore:object xmlns:adtcore="http://www.sap.com/adt/core"
LOCK_HANDLE="0123456789ABCDEF"/>
```
- 返回的 `LOCK_HANDLE` 是一个唯一标识符
- 后续的写入操作必须携带此句柄
- 锁定是 **会话级** 的,HTTP 会话断开后锁自动释放
### 后端处理
```
1. ADT Framework 接收锁定请求
2. 调用 ABAP 锁定管理器 (ENQUEUE)
3. 检查对象是否已被其他用户锁定
4. 如果未锁定 → 创建锁定条目,返回 lockHandle
5. 如果已锁定 → 返回错误(锁定冲突)
```
### 对应 SAP GUI
| ADT 操作 | SAP GUI 等效 |
|----------|-------------|
| Lock 对象 | 打开 SE24/SE38 编辑模式时自动锁定 |
## 3. Step 2: 读取源代码 (Read Source)
### REST API 调用
```
GET /sap/bc/adt/oo/classes/{class_name}/source/main
Accept: text/plain
```
### 对象路径模式
不同对象类型使用不同的路径:
| 对象类型 | 路径模板 |
|----------|---------|
| 类 | `oo/classes/{name}/source/main` |
| 程序 | `programs/programs/{name}/source/main` |
| 函数模块 | `functions/groups/{group}/fmodules/{fm}/source/main` |
| CDS View | `ddic/cds/views/{name}/source/main` |
### 响应
直接返回 **纯文本** 格式的 ABAP 源代码(Content-Type: `text/plain`)。
## 4. Step 3: 写入源代码 (Write Source)
### REST API 调用
```
PUT /sap/bc/adt/oo/classes/{class_name}/source/main
Content-Type: text/plain; charset=utf-8
X-sap-adt-lockhandle: {lockHandle}
<修改后的 ABAP 源代码>
```
> [!important] 关键要点
> - 请求体是 **纯文本**(不是 XML)
> - **必须携带** `X-sap-adt-lockhandle` Header
> - 保存 ≠ 激活:代码已写入仓库但仍处于 **非活跃状态**
### 后端处理
```
1. 验证 lockHandle 是否有效
2. 将源代码写入 ABAP Repository 的非活跃版本 (Inactive Version)
3. 对象状态变为 "Modified"(在 Project Explorer 中显示 * 标记)
4. 记录到传输请求任务中(如果已分配)
```
### 与 SAP GUI 的对比
| 特性 | SAP GUI (SE80/SE38) | ADT (Eclipse) |
|------|---------------------|---------------|
| 保存行为 | 保存即激活(或保存为非活跃) | 保存 ≠ 激活(显式分离) |
| 锁定管理 | 隐式(打开编辑时自动锁定) | 显式(REST API 锁定/解锁) |
| 版本管理 | 版本数据库 | 本地历史 + 版本数据库 |
## 5. Step 4: 解锁对象 (Unlock)
### REST API 调用
```
POST /sap/bc/adt/oo/classes/{class_name}
?_action=UNLOCK
&lockHandle={lockHandle}
```
解锁后,其他开发者可以锁定并编辑该对象。
## 6. Step 5: 激活对象 (Activate)
### 原理
> [!tip] 激活 vs 保存
> - **保存 (Save)**:将源代码写入仓库的非活跃版本
> - **激活 (Activate)**:编译代码并生成可执行版本(Active Version
> - 只有激活后的代码才能被运行时系统使用
### REST API 调用
```
POST /sap/bc/adt/activation
Content-Type: application/xml
<?xml version="1.0" encoding="utf-8"?>
<adtcore:objectReferences
xmlns:adtcore="http://www.sap.com/adt/core">
<adtcore:objectReference
adtcore:name="{object_name}"
adtcore:uri="/sap/bc/adt/oo/classes/{class_name}"/>
</adtcore:objectReferences>
```
### 后端激活流程
```
1. ADT 激活框架接收激活请求
2. 执行预检查 (Pre-audit)
├── 语法检查 (Syntax Check)
├── 依赖检查(引用的对象是否已激活)
└── 权限检查
3. 如果预检查通过:
├── 编译 ABAP 代码 → 生成 ABAP Load
├── 更新 ABAP Repository 中的活跃版本 (Active Version)
├── 触发 ATC 检查(如果配置了自动检查)
└── 将活跃版本写入传输请求
4. 如果预检查失败:
└── 返回错误列表(语法错误、缺失引用等)
```
### 对应 SAP GUI
| ADT 操作 | SAP GUI 等效 |
|----------|-------------|
| 激活单个对象 | `SE80` → Activate (Ctrl+F3) |
| 批量激活 | `SE80` → Inactive Objects → Activate All |
## 7. 完整 REST API 调用序列示例
以下是一个修改 ABAP 类的完整 REST API 调用序列:
```python
# 1. 锁定对象
lock_resp = POST("/sap/bc/adt/oo/classes/zcl_example",
_action="LOCK", accessMode="MODIFY")
lock_handle = parse_lock_handle(lock_resp)
# 2. 读取当前源代码
source = GET("/sap/bc/adt/oo/classes/zcl_example/source/main",
Accept="text/plain")
# 3. 修改源代码(客户端操作)
modified_source = modify_source(source)
# 4. 写入修改后的源代码
PUT("/sap/bc/adt/oo/classes/zcl_example/source/main",
body=modified_source,
headers={"X-sap-adt-lockhandle": lock_handle})
# 5. 解锁对象
POST("/sap/bc/adt/oo/classes/zcl_example",
_action="UNLOCK", lockHandle=lock_handle)
# 6. 激活对象
POST("/sap/bc/adt/activation",
body=activation_xml("ZCL_EXAMPLE",
"/sap/bc/adt/oo/classes/zcl_example"))
```
## 8. 错误处理
| 场景 | HTTP 状态码 | 处理方式 |
|------|-----------|---------|
| 对象已被锁定 | 403/409 | 提示用户等待或强制解锁 |
| CSRF Token 过期 | 403 | 自动重新获取 Token 并重试 |
| 语法错误 | 400 | 返回错误列表,阻止激活 |
| 权限不足 | 401/403 | 检查 S_DEVELOP 权限 |
## 🔗 相关笔记
- [[sap-cli/02.查询代码-原理|02.查询代码 - 原理]]
- [[sap-cli/04.检查代码-原理|04.检查代码 - 原理]]
## 📚 参考来源
- [SAP Help Portal - ADT User Guide](https://help.sap.com/docs/abap-cloud/abap-development-tools-user-guide/about-abap-development-tools-user-guide)
- [End-to-End SAP Automation with ADT REST Services](https://medium.com/@onuryz.itu/end-to-end-sap-automation-with-adt-rest-services-and-ai-a-modern-alternative-to-gui-scripting-343ea86064ea)
- [erpl-adt (GitHub)](https://github.com/DataZooDE/erpl-adt)
+270
View File
@@ -0,0 +1,270 @@
---
title: 检查代码 - 原理
created: 2026-05-18
tags:
- SAP
- ADT
- ATC
- syntax-check
- code-quality
- REST
parent: "[[sap-cli/README|SAP ADT 学习笔记总览]]"
prev: "[[sap-cli/03.修改代码-原理|03.修改代码 - 原理]]"
next: "[[sap-cli/05.提交与传输-原理|05.提交与传输 - 原理]]"
---
# 检查代码 — 原理
> [!abstract] 概述
> ADT 提供三层代码检查能力:**语法检查 (Syntax Check)**、**ABAP Test Cockpit (ATC)** 静态分析** 和 **单元测试执行**。检查结果通过 REST API 返回,在 Eclipse 中以结构化问题列表展示。
## 1. 三层代码检查体系
```
┌──────────────────────────────────────────────────────┐
│ Level 3: 单元测试 (ABAP Unit) │
│ • 执行实际的测试代码 │
│ • 验证运行时行为 │
│ • REST: /sap/bc/adt/oo/classes/{name}/testruns │
├──────────────────────────────────────────────────────┤
│ Level 2: ATC 质量检查 (ABAP Test Cockpit) │
│ • 静态代码分析(功能/性能/安全) │
│ • 可在激活时自动触发 │
│ • REST: /sap/bc/adt/atc/runs │
├──────────────────────────────────────────────────────┤
│ Level 1: 语法检查 (Syntax Check) │
│ • 编译期错误检测 │
│ • 激活前必须通过 │
│ • REST: 集成在 activation 端点中 │
└──────────────────────────────────────────────────────┘
```
## 2. 语法检查 (Syntax Check)
### 原理
语法检查是 **最低级别** 的代码检查,验证 ABAP 代码是否能够成功编译。它作为激活流程的一部分自动执行,也可以手动触发。
### 触发时机
| 场景 | 自动/手动 | 说明 |
|------|----------|------|
| 激活对象前 | 自动 | 激活框架首先执行语法检查 |
| 编辑器中保存后 | 可配置 | 可设置为保存时自动检查 |
| 手动触发 | 手动 | Ctrl+F2 或菜单 |
### 工作流程
```
1. 客户端触发语法检查请求
2. ADT Framework 调用 ABAP 编译器 (ABAP Compiler)
3. 编译器分析源代码:
├── 关键字拼写检查
├── 类型兼容性检查
├── 接口实现完整性检查
└── 引用对象存在性检查
4. 返回检查结果:
├── Errors (错误) — 阻止激活
├── Warnings (警告) — 不阻止但建议修复
└── Information (信息) — 代码改进建议
5. Eclipse 在编辑器中以标记形式展示
(红色波浪线 = Error,黄色 = Warning
```
### 与 SAP GUI 的对比
| ADT 语法检查 | SAP GUI 等效 |
|-------------|-------------|
| 自动检查 + 编辑器标记 | `SE38` → Check (Ctrl+F2) |
| 保存时可自动检查 | 需要手动触发 |
| 实时错误提示 | 无实时检查 |
## 3. ABAP Test Cockpit (ATC)
### 原理
ATC 是 SAP 的 **标准化代码质量检查工具**,提供比语法检查更深入的静态分析能力,涵盖功能正确性、性能优化和安全合规。
### REST API 调用
#### 创建 ATC 运行
```
POST /sap/bc/adt/atc/runs
Content-Type: application/xml
<?xml version="1.0" encoding="utf-8"?>
<atc:run xmlns:atc="http://www.sap.com/adt/atc"
maximumViolations="100">
<objectSets xmlns:adtcore="http://www.sap.com/adt/core">
<objectSet kind="inclusive">
<adtcore:objectReferences>
<adtcore:objectReference
adtcore:uri="/sap/bc/adt/oo/classes/{class_name}"/>
</adtcore:objectReferences>
</objectSet>
</objectSets>
</atc:run>
```
#### 获取 ATC 结果
```
GET /sap/bc/adt/atc/runs/{run_id}/results
```
### 响应格式
```xml
<atc:findings xmlns:atc="http://www.sap.com/adt/atc">
<atc:finding priority="1"
messageTitle="Possible SQL injection"
location="ZCL_EXAMPLE=>METHOD_X line 42"
checkId="SQL_INJECTION"/>
<atc:finding priority="2"
messageTitle="SELECT * should be avoided"
location="ZCL_EXAMPLE=>METHOD_Y line 15"
checkId="PERFORMANCE_SELECT_STAR"/>
</atc:findings>
```
### ATC 检查类别
| 类别 | 说明 | 示例检查项 |
|------|------|-----------|
| **功能检查** | 逻辑错误、空引用 | 未处理的异常、未使用的变量 |
| **性能检查** | 性能反模式 | SELECT *、嵌套 SELECT、缺少索引 |
| **安全检查** | 安全漏洞 | SQL 注入、权限检查缺失 |
| **编程规范** | 编码标准 | 命名规范、注释要求 |
| **Cloud Readiness** | 云兼容性 | 不允许的语句/对象 |
### ATC 运行模式
```
┌──────────────────────────────────────────────────────────┐
│ 模式 1: 手动检查 │
│ 开发者右键 → Run ATC Check │
│ → 立即执行,结果展示在 ATC Results 视图中 │
├──────────────────────────────────────────────────────────┤
│ 模式 2: 激活时自动检查 │
│ 配置 Preferences → ABAP → ATC │
│ → 每次激活时自动触发 ATC │
│ → 发现问题时提示开发者 │
├──────────────────────────────────────────────────────────┤
│ 模式 3: 传输发布时检查 │
│ 传输发布前自动运行 ATC │
│ → 基于 Baseline 只检查新增/修改的代码 │
│ → 确保不会将质量问题引入生产环境 │
├──────────────────────────────────────────────────────────┤
│ 模式 4: 远程代码分析 (Remote Code Analysis) │
│ 在中央 ATC 系统上分析代码 │
│ → 使用最新检查规则 │
│ → 跨系统的代码质量监控 │
└──────────────────────────────────────────────────────────┘
```
### ATC Baseline 概念
> [!info] Baseline 的作用
> Baseline 允许团队将现有代码标记为"已接受",只对**新增或修改的代码**运行检查。这避免了在遗留代码上产生大量已知问题。
```
首次使用:
1. 对整个仓库运行 ATC → 生成 Baseline
2. Baseline 记录所有已知问题
3. 后续检查只报告 Baseline 之后新增的问题
传输检查:
1. 传输发布时运行 ATC
2. 与 Baseline 比对
3. 只有新增问题会阻止传输发布
```
## 4. ABAP Unit 测试
### 原理
ABAP Unit 是 ABAP 的内置单元测试框架。测试方法使用 `FOR TESTING` 标记,在专门的测试会话中执行。
### REST API 调用
```
POST /sap/bc/adt/oo/classes/{test_class_name}/testruns
```
### 后端执行流程
```
1. ADT 接收测试执行请求
2. ABAP Runtime 创建测试隔离环境
3. 执行标记为 FOR TESTING 的方法
4. 收集测试结果:
├── Passed (通过)
├── Failed (失败) + 失败原因
└── Error (错误) + 异常信息
5. 返回结果给客户端
```
## 5. 代码检查与 CI/CD 集成
### 自动化检查流水线
```
代码修改 → 语法检查 → ATC 检查 → ABAP Unit → 传输发布
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
保存成功 无错误 无严重问题 全部通过 发布到目标系统
```
### 通过 REST API 实现自动化
```python
# 完整的自动检查流程
def automated_code_check(client, object_uri):
# 1. 语法检查(通过激活预检)
activation_result = activate_object(client, object_uri)
if activation_result.get("errors"):
return "BLOCKED: Syntax errors"
# 2. ATC 检查
atc_result = run_atc_check(client, object_uri)
critical_findings = [f for f in atc_result["findings"]
if f["priority"] == "1"]
if critical_findings:
return "BLOCKED: Critical ATC findings"
# 3. 单元测试
test_result = run_unit_tests(client, test_class_uri)
if test_result["failed"] > 0:
return "BLOCKED: Unit test failures"
return "PASSED: All checks green"
```
## 6. Eclipse 中的检查结果展示
| 视图 | 说明 |
|------|------|
| **Problems View** | 语法检查和编译错误 |
| **ATC Results View** | ATC 检查发现,按严重级别分组 |
| **ABAP Unit Results** | 单元测试执行结果 |
| **Editor Markers** | 编辑器中的行内错误/警告标记 |
## 🔗 相关笔记
- [[sap-cli/03.修改代码-原理|03.修改代码 - 原理]] — 激活流程中集成语法检查
- [[sap-cli/05.提交与传输-原理|05.提交与传输 - 原理]] — 传输发布时触发 ATC
## 📚 参考来源
- [SAP Help Portal - Checking Quality of ABAP Code with ATC](https://help.sap.com/docs/abap-cloud/abap-development-tools-user-guide/checking-quality-of-abap-code-with-atc)
- [Remote Code Analysis in ATC](https://community.sap.com/t5/application-development-and-automation-blog-posts/remote-code-analysis-in-abap-test-cockpit-atc-faq/ba-p/13320130)
- [End-to-End SAP Automation with ADT REST Services](https://medium.com/@onuryz.itu/end-to-end-sap-automation-with-adt-rest-services-and-ai-a-modern-alternative-to-gui-scripting-343ea86064ea)
+317
View File
@@ -0,0 +1,317 @@
---
title: 提交与传输 - 原理
created: 2026-05-18
tags:
- SAP
- ADT
- transport
- CTS
- version-management
- REST
parent: "[[sap-cli/README|SAP ADT 学习笔记总览]]"
prev: "[[sap-cli/04.检查代码-原理|04.检查代码 - 原理]]"
---
# 提交与传输 — 原理
> [!abstract] 核心概念
> SAP 中的 "提交代码" 与 Git 等版本控制系统不同。ABAP 使用 **CTS (Change and Transport System)** 来管理代码变更。代码修改被记录在 **传输请求 (Transport Request)** 中,通过传输请求将变更从开发系统 → 测试系统 → 生产系统进行迁移。
## 1. 传输管理概念模型
### 传输系统架构
```
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ 开发系统 │ │ 测试系统 │ │ 生产系统 │
│ (DEV) │ │ (QAS) │ │ (PRD) │
│ │ │ │ │ │
│ 创建/修改 │────▶│ 测试/验证 │────▶│ 部署上线 │
│ 代码 │ │ 代码 │ │ 代码 │
└──────┬───────┘ └──────────────┘ └──────────────┘
│ 导出
┌──────────────┐
│ 传输目录 │
│ (Transport │
│ Directory) │
│ │
│ .TR 文件 │
│ 数据文件 │
└──────────────┘
│ 导入
目标系统 (QAS/PRD)
```
### 传输请求结构
```
传输请求 (Transport Request - TR)
├── 请求头 (Request Header)
│ ├── 编号: K9XXXXXX
│ ├── 类型: K (Workbench) / W (Customizing) / T (Transport of Copies)
│ ├── 描述: "Implement sales order validation"
│ ├── 所有者: DEVELOPER01
│ ├── 状态: D (Modifiable) → R (Released)
│ └── 目标系统: QAS
├── 任务 (Task) — 可以有多个
│ ├── 编号: K9XXXXYY
│ ├── 所有者: DEVELOPER01
│ └── 状态: D → R
└── 对象列表 (Object List)
├── ZCL_SALES_ORDER (CLAS)
├── Z_SALES_REPORT (PROG)
└── Z_SALES_TABLE (TABL)
```
## 2. ADT 中的传输工作流
### 完整流程
```
1. 创建传输请求
2. 将对象分配到传输请求
3. 开发/修改代码
4. 激活代码
5. (可选) 运行 ATC 检查
6. 发布传输请求
7. 导入到目标系统
```
## 3. REST API 详解
### 3.1 列出传输请求
```
GET /sap/bc/adt/cts/transportrequests
?user={username}
&status={D|R}
```
| 参数 | 说明 |
|------|------|
| `user` | 按所有者过滤 |
| `status` | `D` = 可修改, `R` = 已发布 |
### 响应
```xml
<cts:transportRequests xmlns:cts="http://www.sap.com/adt/cts">
<cts:request number="K900001"
description="Implement sales validation"
owner="DEVELOPER01"
status="D"/>
</cts:transportRequests>
```
### 3.2 创建传输请求
```
POST /sap/bc/adt/cts/transportrequests
Content-Type: application/xml
<?xml version="1.0" encoding="utf-8"?>
<cts:request xmlns:cts="http://www.sap.com/adt/cts"
cts:type="K"
cts:description="Implement sales validation"
cts:target="QAS"/>
```
| 字段 | 说明 |
|------|------|
| `type="K"` | Workbench 请求(程序、类等) |
| `type="W"` | Customizing 请求(配置数据) |
| `type="T"` | Transport of Copies(复制传输) |
### 3.3 发布传输请求
```
POST /sap/bc/adt/cts/transportrequests/{request_number}/newreleasejobs
```
### 后端处理流程
```
1. ADT CTS 端点接收发布请求
2. 传输系统执行预检查:
├── 检查所有任务是否已发布
├── 检查所有对象是否已激活
├── (可选) 运行 ATC 检查
└── 检查是否有未释放的对象
3. 将传输请求导出到传输目录
├── 生成数据文件(包含对象内容)
└── 生成控制文件(包含元数据)
4. 请求状态变为 R (Released)
5. 传输管理系统 (tp / STMS) 将请求导入目标系统
```
### 3.4 Transport of Copies
> [!tip] Transport of Copies
> Transport of Copies (ToC) 是一种特殊类型的传输,用于在不改变原始传输请求的情况下将代码复制到目标系统。常用于紧急修复或临时部署。
```
POST /sap/bc/adt/cts/transportrequests/{source_request}/tocopies
```
## 4. 对象分配到传输请求
### 原理
当开发者修改一个对象时,系统会提示选择或创建传输请求。对象随后被记录到传输请求的任务中。
### 分配方式
```
┌────────────────────────────────────────────────────┐
│ 方式 1: 自动提示 │
│ 修改对象时 → Eclipse 弹出对话框 → 选择/创建 TR │
├────────────────────────────────────────────────────┤
│ 方式 2: 拖拽分配 │
│ 在 Project Explorer 中拖拽对象到 TR │
├────────────────────────────────────────────────────┤
│ 方式 3: 批量分配 │
│ 选择多个对象 → Assign to Transport Request │
└────────────────────────────────────────────────────┘
```
## 5. 版本管理
### ABAP 版本管理机制
ABAP 内置了 **版本数据库 (Version Database)**,每次激活对象时自动创建一个版本。
### ADT 中的版本比较
```
┌────────────────────────────────────────────┐
│ 版本类型 │
├────────────────────────────────────────────┤
│ 活跃版本 (Active Version) │
│ → 当前系统中正在运行的代码版本 │
│ 非活跃版本 (Inactive Version) │
│ → 已保存但未激活的修改版本 │
│ 传输版本 (Transport Version) │
│ → 传输请求中记录的版本 │
│ 历史版本 (Historical Version) │
│ → 版本数据库中保存的所有历史版本 │
└────────────────────────────────────────────┘
```
### 比较操作
| 操作 | 说明 |
|------|------|
| Compare with Active Version | 当前编辑 vs 活跃版本 |
| Compare with Local History | 当前 vs 本地历史(客户端缓存) |
| Compare with Transport Version | 当前 vs 传输中的版本 |
| Version History | 查看所有历史版本 |
## 6. ADT 传输管理 vs SAP GUI 传输管理
| 功能 | SAP GUI | ADT Eclipse |
|------|---------|-------------|
| 创建 TR | `SE01` / `SE09` | 右键 → New → Transport Request |
| 查看 TR | `SE01` / `SE09` | Transport Requests 视图 |
| 发布 TR | `SE01` → Release | 右键 → Release |
| 查看 TR 内容 | `SE01` → Display | 双击 TR → 展开对象列表 |
| TR 详情 | `SE03` | Properties 视图 |
| 版本比较 | `SE80` → Version Management | 右键 → Compare With |
| 批量操作 | `SE01` | 多选 + 上下文菜单 |
## 7. 传输发布与 ATC 集成
> [!important] 传输发布质量门禁
> 传输发布时可以配置 **ATC 质量门禁**,确保代码质量达标后才能发布到目标系统。
### 配置流程
```
1. 在 SAP 系统中配置 ATC 传输检查
(事务码: SATC -> 配置传输发布检查)
2. 设置检查规则:
├── 设置 Baseline
├── 配置检查范围
└── 设置严重级别阈值
3. 传输发布时自动触发 ATC
4. 只有通过检查的传输才能继续发布
(或配置为仅警告不阻止)
```
## 8. 通过 REST API 实现自动化传输管理
### Python 自动化示例
```python
def automated_transport_workflow(client, object_uri, description):
"""自动化传输工作流"""
# 1. 创建传输请求
tr_number = create_transport_request(
client,
description=description,
target_system="QAS"
)
# 2. 修改代码(参见 03.修改代码-原理)
lock_handle = lock_object(client, object_uri)
# ... 修改源代码 ...
write_source(client, object_uri, modified_source, lock_handle)
unlock_object(client, object_uri, lock_handle)
# 3. 激活代码
activate_object(client, object_uri)
# 4. 运行 ATC 检查
atc_result = run_atc_check(client, object_uri)
critical = [f for f in atc_result["findings"]
if f["priority"] == "1"]
if critical:
raise Exception("ATC check failed")
# 5. 发布传输请求
release_transport(client, tr_number)
return tr_number
```
## 9. 常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|------|------|---------|
| 无法创建 TR | 缺少 S_CTS_ADMI 权限 | 联系 BASIS 分配权限 |
| 发布失败 | 对象未激活 | 先激活所有对象 |
| ATC 阻止发布 | 存在严重问题 | 修复 ATC 发现或更新 Baseline |
| 传输导入失败 | 目标系统缺少依赖 | 确保依赖对象已在目标系统激活 |
| 锁定冲突 | 其他用户正在编辑 | 等待或请求解锁 |
## 🔗 相关笔记
- [[sap-cli/03.修改代码-原理|03.修改代码 - 原理]] — 代码修改 → 传输请求
- [[sap-cli/04.检查代码-原理|04.检查代码 - 原理]] — ATC 检查与传输发布集成
## 📚 参考来源
- [SAP Help Portal - ADT User Guide](https://help.sap.com/docs/abap-cloud/abap-development-tools-user-guide/about-abap-development-tools-user-guide)
- [ABAP Tools Work with Eclipse (Transport) Software-Heroes](https://software-heroes.com/en/blog/abap-tools-work-with-eclipse-transport)
- [End-to-End SAP Automation with ADT REST Services](https://medium.com/@onuryz.itu/end-to-end-sap-automation-with-adt-rest-services-and-ai-a-modern-alternative-to-gui-scripting-343ea86064ea)
- [Transporting ABAP and Non-ABAP Objects (SAP Community Call)](https://www.youtube.com/watch?v=FpAuj8QD-rM)
- [Version Management in ABAP in Eclipse - SAP Community](https://community.sap.com/t5/application-development-discussions/version-management-in-abap-in-eclipse/m-p/12611386)
+336
View File
@@ -0,0 +1,336 @@
---
title: ADT 测试代码 — 读取程序源代码
created: 2026-05-19
tags:
- SAP
- ADT
- REST
- python
- test-code
parent: "[[sap-cli/README|SAP ADT 学习笔记总览]]"
---
# ADT 测试代码 — 通过 REST API 读取程序源代码
> [!abstract] 概述
> 通过 ADT REST API 连接 SAP 系统,测试连接、服务发现、读取程序源代码、搜索仓库对象。
> 参见:[[sap-cli/02.查询代码-原理]]
## 运行方式
```bash
# 安装依赖
pip install requests
# 运行测试
python sap-cli/test_adt_client.py
```
## 测试结果
> [!success] 2026-05-19 测试通过
> - 连接验证: HTTP 200 ✅
> - 服务发现: 311 个可用服务 ✅
> - 读取源代码: `ZIDTR_IMPORT_ACCOUNTING_ORDER` (37,655 字符 / 1,096 行) ✅
> - 仓库搜索: quickSearch 返回 0 结果 ⚠️
## 完整代码
```python
"""
SAP ADT REST Client — 测试脚本
通过 ADT REST API 连接 SAP 系统并获取程序源代码
参考: [[sap-cli/02.查询代码-原理]]
[[sap-cli/01.ADT架构与通信原理]]
端点: /sap/bc/adt/programs/programs/{name}/source/main
"""
import requests
from urllib.parse import urljoin
import xml.etree.ElementTree as ET
import sys
import json
import io
# 修复 Windows 控制台编码问题
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
# ============================================================
# 配置 — 来自 [[sap-cli/README]] 测试服务器信息
# ============================================================
ADT_BASE_URL = "http://support.learningleader.com.cn:55955/sap/bc/adt"
SAP_CLIENT = "100"
USER = "admin2"
PASSWORD = "654321"
PROGRAM_NAME = "ZIDTR_IMPORT_ACCOUNTING_ORDER"
class ADTClient:
"""简易 ADT REST 客户端"""
CSRF_HEADER = "x-csrf-token"
CSRF_FETCH = "Fetch"
def __init__(self, base_url: str, client: str, user: str, password: str):
self.base_url = base_url.rstrip("/")
self.session = requests.Session()
self.session.auth = (user, password)
self.session.headers.update({
"Accept": "*/*",
"Accept-Language": "EN",
"sap-client": client,
})
# 忽略 SSL 警告(测试环境可能是自签名证书)
requests.packages.urllib3.disable_warnings(
requests.packages.urllib3.exceptions.InsecureRequestWarning
)
self.session.verify = False
self._csrf_token: str | None = None
def _url(self, path: str) -> str:
return f"{self.base_url}/{path}"
def fetch_csrf_token(self) -> str:
"""获取 CSRF Token — 写操作必需"""
print(" → 正在获取 CSRF Token...")
resp = self.session.get(
self._url("discovery"),
headers={self.CSRF_HEADER: self.CSRF_FETCH},
)
resp.raise_for_status()
self._csrf_token = resp.headers.get(self.CSRF_HEADER, "")
print(f" ✓ CSRF Token 获取成功: {self._csrf_token[:20]}...")
return self._csrf_token
def get(self, path: str, **kwargs) -> requests.Response:
"""发送 GET 请求"""
resp = self.session.get(self._url(path), **kwargs)
resp.raise_for_status()
return resp
def test_connection(client: ADTClient) -> bool:
"""测试 1: 验证 ADT 连接"""
print("\n" + "=" * 60)
print("测试 1: 验证 ADT 连接")
print("=" * 60)
print(f" URL: {client.base_url}")
print(f" User: {client.session.auth[0]}")
print(f" Client: {client.session.headers.get('sap-client')}")
try:
resp = client.session.get(client._url("discovery"))
print(f" → HTTP Status: {resp.status_code}")
if resp.status_code == 401:
print(f" ✗ 认证失败 (401 Unauthorized)")
print(f" 响应体: {resp.text[:300]}")
return False
resp.raise_for_status()
print(f" ✓ 连接成功! HTTP {resp.status_code}")
print(f" ✓ Content-Type: {resp.headers.get('content-type', 'N/A')}")
return True
except requests.exceptions.HTTPError as e:
print(f" ✗ HTTP 错误: {e}")
if hasattr(e, 'response') and e.response is not None:
print(f" 响应体: {e.response.text[:300]}")
return False
except requests.exceptions.ConnectionError as e:
print(f" ✗ 网络连接失败: {e}")
return False
def test_discovery(client: ADTClient) -> list[dict]:
"""测试 2: 服务发现 — 列出可用的 ADT 服务"""
print("\n" + "=" * 60)
print("测试 2: 服务发现 (Discovery)")
print("=" * 60)
try:
resp = client.get("discovery")
root = ET.fromstring(resp.content)
# 解析 Atom Feed 中的服务集合
ns = {
"app": "http://www.w3.org/2007/app",
"atom": "http://www.w3.org/2005/Atom",
}
services = []
for collection in root.findall(".//app:collection", ns):
href = collection.attrib.get("href", "")
title_el = collection.find("atom:title", ns)
title = title_el.text if title_el is not None else ""
services.append({"href": href, "title": title})
print(f" ✓ 发现 {len(services)} 个可用服务:")
for svc in services[:15]: # 只显示前 15 个
print(f"{svc['title']}: {svc['href']}")
if len(services) > 15:
print(f" ... 还有 {len(services) - 15} 个服务")
return services
except Exception as e:
print(f" ✗ 服务发现失败: {e}")
return []
def test_read_program_source(client: ADTClient, program_name: str) -> str | None:
"""测试 3: 读取程序源代码"""
print("\n" + "=" * 60)
print(f"测试 3: 读取程序源代码 ({program_name})")
print("=" * 60)
# 尝试多种可能的端点路径
endpoints = [
f"programs/programs/{program_name.lower()}/source/main",
f"programs/programs/{program_name}/source/main",
]
for endpoint in endpoints:
try:
print(f" → 尝试端点: GET {endpoint}")
resp = client.get(
endpoint,
headers={"Accept": "text/plain"},
)
source = resp.text
print(f" ✓ 源代码获取成功!")
print(f" ✓ 程序名: {program_name}")
print(f" ✓ 源代码长度: {len(source)} 字符")
print(f" ✓ 行数: {len(source.splitlines())}")
# 显示前 30 行
lines = source.splitlines()
print(f"\n ┌─── 源代码 (前 30 行) ──────────────────────")
for i, line in enumerate(lines[:30], 1):
print(f"{i:4d} | {line}")
if len(lines) > 30:
print(f" │ ... 省略剩余 {len(lines) - 30} 行 ...")
print(f" └────────────────────────────────────────────")
return source
except requests.exceptions.HTTPError as e:
print(f" ✗ HTTP 错误: {e.response.status_code} {e.response.reason}")
if e.response.status_code == 404:
print(f" (程序不存在或端点路径不正确,尝试下一个...)")
continue
else:
print(f" 响应内容: {e.response.text[:200]}")
return None
except Exception as e:
print(f" ✗ 读取失败: {e}")
return None
print(f" ✗ 所有端点尝试均失败")
return None
def test_search_object(client: ADTClient, program_name: str) -> list[dict]:
"""测试 4: 搜索仓库对象"""
print("\n" + "=" * 60)
print(f"测试 4: 搜索仓库对象 ({program_name})")
print("=" * 60)
try:
resp = client.get(
"repository/informationsystem/search",
params={
"operation": "quickSearch",
"query": program_name,
"maxResults": "10",
},
)
ns = "http://www.sap.com/adt/repository/informationsystem"
root = ET.fromstring(resp.content)
results = []
for obj in root.iter(f"{{{ns}}}object"):
results.append({
"name": obj.attrib.get("name", ""),
"type": obj.attrib.get("type", ""),
"uri": obj.attrib.get("uri", ""),
"package": obj.attrib.get("packageName", ""),
})
print(f" ✓ 搜索完成,找到 {len(results)} 个结果:")
for r in results:
print(f" • [{r['type']}] {r['name']} (Package: {r['package']})")
print(f" URI: {r['uri']}")
return results
except Exception as e:
print(f" ✗ 搜索失败: {e}")
return []
def main():
print("=" * 60)
print(" SAP ADT REST API 测试脚本")
print(" 参考: [[sap-cli/02.查询代码-原理]]")
print("=" * 60)
# 创建客户端
client = ADTClient(
base_url=ADT_BASE_URL,
client=SAP_CLIENT,
user=USER,
password=PASSWORD,
)
# 测试 1: 连接验证
if not test_connection(client):
print("\n❌ 连接失败,请检查服务器配置和网络连接。")
sys.exit(1)
# 测试 2: 服务发现
test_discovery(client)
# 测试 3: 读取程序源代码
source = test_read_program_source(client, PROGRAM_NAME)
# 测试 4: 搜索仓库对象
test_search_object(client, PROGRAM_NAME)
# 汇总结果
print("\n" + "=" * 60)
print(" 测试结果汇总")
print("=" * 60)
if source:
print(" ✅ 程序源代码获取成功!")
print(f" 程序: {PROGRAM_NAME}")
print(f" 长度: {len(source)} 字符, {len(source.splitlines())}")
else:
print(" ⚠️ 程序源代码获取失败,请确认程序名称是否正确")
print("=" * 60)
if __name__ == "__main__":
main()
```
## 关键 API 端点速查
| 操作 | HTTP 方法 | 端点 | Accept |
|------|----------|------|--------|
| 服务发现 | `GET` | `/sap/bc/adt/discovery` | `*/*` |
| 读取程序源码 | `GET` | `/sap/bc/adt/programs/programs/{name}/source/main` | `text/plain` |
| 读取类源码 | `GET` | `/sap/bc/adt/oo/classes/{name}/source/main` | `text/plain` |
| 仓库搜索 | `GET` | `/sap/bc/adt/repository/informationsystem/search` | `*/*` |
> [!note] 注意事项
>> - discovery 端点的 Accept 必须用 `*/*`,用 `application/xml` 会返回 406
> - 程序名在 URL 中使用**小写**
## 🔗 相关笔记
- [[sap-cli/01.ADT架构与通信原理|01.ADT架构与通信原理]]
- [[sap-cli/02.查询代码-原理|02.查询代码 - 原理]]
- [[sap-cli/03.修改代码-原理|03.修改代码 - 原理]]
+352
View File
@@ -0,0 +1,352 @@
---
title: ADT 代码同步工具 — 使用指南
created: 2026-05-19
tags:
- SAP
- ADT
- REST
- python
- sync
- pull
- push
- activate
parent: "[[sap-cli/README|SAP ADT 学习笔记总览]]"
prev: "[[sap-cli/06.ADT测试代码|06.ADT 测试代码]]"
---
# ADT 代码同步工具 — 使用指南
> [!abstract] 概述
> 通过 `test_adt_client.py` 脚本实现本地与 SAP 系统之间的 ABAP 代码双向同步。支持拉取代码到本地编辑、修改后推送回 SAP 并自动激活。
>
> 完整工作流遵循 [[sap-cli/03.修改代码-原理]] 中描述的 **锁定 → 编辑 → 保存 → 解锁 → 激活** 流程。
## 1. 快速开始
### 前置条件
```bash
pip install requests
```
### 基本用法
```bash
# 拉取代码(从 SAP 下载到本地)
python sap-cli/test_adt_client.py pull
# 推送代码(从本地上传到 SAP 并激活)
python sap-cli/test_adt_client.py push
```
### 配置修改
脚本顶部的配置区域:
```python
ADT_BASE_URL = "http://support.learningleader.com.cn:55955" # SAP 服务器地址
SAP_CLIENT = "100" # SAP 客户端
USER = "admin2" # 用户名
PASSWORD = "654321" # 密码
PROGRAM_NAME = "ZIDTR_IMPORT_ACCOUNTING_ORDER" # 程序名
```
## 2. 拉取代码 (Pull)
### 流程
```
登录 → GET 源代码 → 保存到本地 .abap 文件
```
### REST API 调用序列
| 步骤 | HTTP 方法 | URL | 说明 |
|------|----------|-----|------|
| 登录 | `GET` | `/sap/bc/adt/compatibility/graph` | 获取 CSRF Token |
| 读取源码 | `GET` | `/sap/bc/adt/programs/programs/{name}/source/main` | Accept: text/plain |
### 关键请求头
```http
GET /sap/bc/adt/programs/programs/zidtr_import_accounting_order/source/main
Accept: text/plain
x-csrf-token: {token}
X-sap-adt-sessiontype: stateless
sap-client: 100
```
### 输出
源代码保存到脚本同目录下的 `{程序名小写}.abap` 文件,例如:
```
sap-cli/zidtr_import_accounting_order.abap
```
> [!tip] 格式说明
> SAP ADT API 返回的源代码每行之间可能插入多余空行。脚本已内置格式优化逻辑,自动清理连续空行。
## 3. 推送代码 (Push)
### 完整流程
```
登录 → 查询传输请求 → 锁定对象 → 写入源代码 → 解锁对象 → 激活对象
```
对应 [[sap-cli/03.修改代码-原理]] 中的完整工作流:
```
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ 1.锁定 │───▶│ 2.写入 │───▶│ 3.解锁 │───▶│ 4.激活 │
│ Lock │ │ Write │ │ Unlock │ │ Activate│
└─────────┘ └─────────┘ └─────────┘ └─────────┘
```
### REST API 调用序列
#### Step 0: 登录获取 CSRF Token
```http
GET /sap/bc/adt/compatibility/graph
x-csrf-token: fetch
X-sap-adt-sessiontype: stateless
```
响应头中返回 CSRF Token,后续所有写操作必须携带。
#### Step 1: 查询传输请求
```http
GET /sap/bc/adt/cts/transportrequests
Accept: application/vnd.sap.adt.transportorganizer.v1+xml
```
响应 XML 中解析第一个状态为 `D`(可修改)的传输请求号。
响应示例:
```xml
<tm:request tm:number="DEVK901360" tm:status="D" tm:desc="更改记录" />
```
> [!important] 传输请求号 (corrNr)
> 传输请求号是代码写入 SAP 的必要参数。脚本自动查询并使用第一个可修改的请求。
>
> XML 属性名带有命名空间前缀:`{http://www.sap.com/cts/adt/tm}number`
#### Step 2: 锁定对象
```http
POST /sap/bc/adt/programs/programs/{name}
?_action=LOCK
&accessMode=MODIFY
X-sap-adt-sessiontype: stateful
x-csrf-token: {token}
```
响应:
```xml
<adtcore:object LOCK_HANDLE="Jn333u2TRIqrbxSAs9ib7Ket9q0=" />
```
> [!warning] 会话状态
> 锁定操作**必须在 stateful 会话**中执行(`X-sap-adt-sessiontype: stateful`)。
> 登录端点必须使用 `/sap/bc/adt/compatibility/graph`(不是 `/discovery`),否则锁定会失败。
#### Step 3: 写入源代码
```http
PUT /sap/bc/adt/programs/programs/{name}/source/main
?lockHandle={lockHandle}
&corrNr={}
Content-Type: text/plain; charset=utf-8
X-sap-adt-sessiontype: stateful
x-csrf-token: {token}
{ABAP }
```
> [!important] 关键参数
> - **lockHandle**:来自锁定步骤的返回值,作为**查询参数**传递
> - **corrNr**:传输请求号,也是查询参数
> - 请求体是**纯文本**(不是 XML),UTF-8 编码
#### Step 4: 解锁对象
```http
POST /sap/bc/adt/programs/programs/{name}
?_action=UNLOCK
&lockHandle={lockHandle}
X-sap-adt-sessiontype: stateful
x-csrf-token: {token}
```
#### Step 5: 激活对象
```http
POST /sap/bc/adt/activation
?method=activate
&preauditRequested=true
Content-Type: application/xml
x-csrf-token: {token}
<?xml version="1.0" encoding="UTF-8"?>
<adtcore:objectReferences xmlns:adtcore="http://www.sap.com/adt/core">
<adtcore:objectReference
adtcore:uri="/sap/bc/adt/programs/programs/{name}"
adtcore:name="{PROGRAM_NAME}"/>
</adtcore:objectReferences>
```
激活响应可能包含错误消息:
```xml
<chkl:messages>
<msg type="E" line="1">
<shortText><txt>Type "XXX" is unknown.</txt></shortText>
</msg>
</chkl:messages>
```
| type | 含义 |
|------|------|
| `E` | 错误 — 阻止激活 |
| `W` | 警告 — 不阻止 |
| `I` | 信息 — 不阻止 |
| `S` | 成功 |
> [!note] 空响应
> 激活成功时可能返回空响应体(HTTP 200),这表示激活无错误。
## 4. 传输请求详解
### 查询可用的传输请求
```http
GET /sap/bc/adt/cts/transportrequests
Accept: application/vnd.sap.adt.transportorganizer.v1+xml
```
### 响应结构
```xml
<feed xmlns="http://www.w3.org/2005/Atom">
<entry>
<tm:request xmlns:tm="http://www.sap.com/cts/adt/tm"
tm:number="DEVK901360"
tm:status="D"
tm:owner="ADMIN2"
tm:desc="更改记录的已生成请求"/>
<tm:task tm:number="DEVK901361" tm:status="D" tm:owner="ADMIN2"/>
</entry>
</feed>
```
### 属性说明
| 属性 | 值 | 说明 |
|------|-----|------|
| `status` | `D` | 可修改 (Modifiable) |
| `status` | `R` | 已发布 (Released) |
| `number` | `DEVK901360` | 传输请求号 |
| `desc` | 文本描述 | 请求描述 |
### XML 解析注意事项
属性名带有命名空间前缀,解析时需要使用完整名称:
```python
tm_ns = "http://www.sap.com/cts/adt/tm"
for req in root.findall(f".//{{{tm_ns}}}request"):
number = req.attrib.get(f"{{{tm_ns}}}number", "") # 不是 "number"
status = req.attrib.get(f"{{{tm_ns}}}status", "") # 不是 "status"
```
## 5. 日志系统
每次执行 push/pull 操作时,详细的 API 请求/响应日志会写入:
```
sap-cli/adt_client.log
```
### 日志内容
```
2026-05-19 10:57:56 | INFO | REQUEST: POST http://.../activation
2026-05-19 10:57:56 | INFO | PARAMS: {'method': 'activate', 'preauditRequested': 'true'}
2026-05-19 10:57:56 | INFO | HEADER: x-csrf-token = xxx...
2026-05-19 10:57:56 | INFO | BODY: <?xml version="1.0" ...>
2026-05-19 10:57:56 | INFO | RESPONSE [ACTIVATE]: HTTP 200 OK
2026-05-19 10:57:56 | INFO | BODY (xml): <chkl:messages>...
2026-05-19 10:57:56 | INFO | Activation msg [E] line=1: Type "XXX" is unknown.
2026-05-19 10:57:56 | INFO | Activation summary: 1 errors, 0 warnings, 0 info
2026-05-19 10:57:56 | WARNING | Activation has ERRORS - object may NOT be active!
```
### 排查激活失败
1. 查看 `adt_client.log``[ACTIVATE]` 部分
2. 搜索 `Activation msg [E]` 找到错误消息
3. 错误消息中的 `line` 是语句序号(不是源文件行号)
4. `href` 中的 `start=XXX` 是字符偏移量,可用于定位源码位置
## 6. 完整 API 端点速查
### 通用端点
| 操作 | 方法 | 端点 | 关键参数 |
|------|------|------|---------|
| 登录/CSRF | `GET` | `/sap/bc/adt/compatibility/graph` | `x-csrf-token: fetch` |
| 服务发现 | `GET` | `/sap/bc/adt/discovery` | — |
| 激活 | `POST` | `/sap/bc/adt/activation` | `method=activate` |
### 程序 (PROG) 端点
| 操作 | 方法 | 端点 | 关键参数 |
|------|------|------|---------|
| 读取源码 | `GET` | `/sap/bc/adt/programs/programs/{name}/source/main` | `Accept: text/plain` |
| 锁定 | `POST` | `/sap/bc/adt/programs/programs/{name}` | `_action=LOCK` |
| 写入源码 | `PUT` | `/sap/bc/adt/programs/programs/{name}/source/main` | `lockHandle`, `corrNr` |
| 解锁 | `POST` | `/sap/bc/adt/programs/programs/{name}` | `_action=UNLOCK` |
### 类 (CLASS) 端点
| 操作 | 方法 | 端点 |
|------|------|------|
| 读取源码 | `GET` | `/sap/bc/adt/oo/classes/{name}/source/main` |
| 锁定 | `POST` | `/sap/bc/adt/oo/classes/{name}` |
| 写入源码 | `PUT` | `/sap/bc/adt/oo/classes/{name}/source/main` |
| 解锁 | `POST` | `/sap/bc/adt/oo/classes/{name}` |
### CTS 传输管理端点
| 操作 | 方法 | 端点 |
|------|------|------|
| 查询传输请求 | `GET` | `/sap/bc/adt/cts/transportrequests` |
| 创建传输请求 | `POST` | `/sap/bc/adt/cts/transportrequests` |
| 发布传输请求 | `POST` | `/sap/bc/adt/cts/transportrequests/{number}/newreleasejobs` |
## 7. ABAP 开发对象激活步骤
> [!important] 标准激活流程
> 1. **修改本地源代码** — 在 `.abap` 文件中编辑
> 2. **同步到 SAP** — `python test_adt_client.py push`
> - 脚本自动执行:锁定 → 写入 → 解锁 → 激活
> 3. **查看日志确认** — 检查 `adt_client.log` 确认无激活错误
## 🔗 相关笔记
- [[sap-cli/03.修改代码-原理|03.修改代码 - 原理]] — 锁定/编辑/激活原理
- [[sap-cli/04.检查代码-原理|04.检查代码 - 原理]] — 语法检查与 ATC
- [[sap-cli/05.提交与传输-原理|05.提交与传输 - 原理]] — CTS 传输管理
- [[sap-cli/06.ADT测试代码|06.ADT 测试代码]] — 初始测试脚本
## 📚 参考来源
- [abap-adt-py (GitHub)](https://github.com/timkoehne/abap-adt-py) — Python ADT 客户端库
- [SAP Help Portal - ADT User Guide](https://help.sap.com/docs/abap-cloud/abap-development-tools-user-guide/about-abap-development-tools-user-guide)
- [End-to-End SAP Automation with ADT REST Services](https://medium.com/@onuryz.itu/end-to-end-sap-automation-with-adt-rest-services-and-ai-a-modern-alternative-to-gui-scripting-343ea86064ea)
+92
View File
@@ -0,0 +1,92 @@
---
title: SAP ADT 学习笔记总览
created: 2026-05-18
tags:
- SAP
- ADT
- ABAP
- learning-notes
---
# SAP ADT (ABAP Development Tools) 学习笔记
> [!info] 关于本系列
> 本系列笔记深入探讨 SAP ADT 的技术原理,覆盖从底层架构到各项开发功能的实现机制。
> 参考来源:[SAP Help Portal - ADT User Guide](https://help.sap.com/docs/abap-cloud/abap-development-tools-user-guide/about-abap-development-tools-user-guide?locale=en-US)
## 📌 什么是 SAP ADT
**ABAP Development Tools (ADT)** 是 SAP 提供的现代化 ABAP 开发工具集,基于 Eclipse 平台构建,使开发者能够在 Eclipse IDE 中进行 ABAP 开发,替代传统的 SAP GUI + SE80 开发模式。
核心特点:
- **客户端/服务器架构** — IDE 作为客户端,SAP 后端作为服务器
- **REST API 驱动** — 所有操作通过 `/sap/bc/adt/*` REST 端点与后端通信
- **多 IDE 支持** — Eclipse(当前) → VS Code2026年) → 更多 IDE
- **平台无关** — 基于 HTTP(S) 协议,任何平台均可访问
## 📂 笔记结构
### 架构与原理
| 笔记 | 说明 |
|------|------|
| [[sap-cli/01.ADT架构与通信原理\|01.ADT架构与通信原理]] | ADT 的三层架构、REST 通信层、服务发现机制 |
### 核心功能原理
| 笔记 | 说明 |
|------|------|
| [[sap-cli/02.查询代码-原理\|02.查询代码 - 原理]] | 仓库对象搜索、源代码全文搜索、Where-Used 导航 |
| [[sap-cli/03.修改代码-原理\|03.修改代码 - 原理]] | 锁定-编辑-解锁-激活工作流、源代码读写 |
| [[sap-cli/04.检查代码-原理\|04.检查代码 - 原理]] | 语法检查、ATC 代码质量检查、静态分析 |
| [[sap-cli/05.提交与传输-原理\|05.提交与传输 - 原理]] | 传输请求管理、传输发布、版本管理 |
### 实践
| 笔记 | 说明 |
|------|------|
| [[sap-cli/06.ADT测试代码\|06.ADT测试代码]] | Python 测试脚本:连接验证、服务发现、读取程序源代码 |
| [[sap-cli/07.ADT代码同步工具\|07.ADT代码同步工具]] | 完整使用指南:拉取代码、传输请求、推送代码、激活代码 |
## 测试服务器
ADT url=http://support.learningleader.com.cn:55955/sap/bc/adt?sap-client=100
user=admin2
password=654321
PROGRAM=ZIDTR_IMPORT_ACCOUNTING_ORDER
## 🔗 关键外部资源
- [SAP Help Portal - ADT User Guide](https://help.sap.com/docs/abap-cloud/abap-development-tools-user-guide/about-abap-development-tools-user-guide?locale=en-US)
- [Behind the Design: How We Transformed the ABAP Development Tools](https://community.sap.com/t5/technology-blog-posts-by-sap/behind-the-design-how-we-transformed-the-abap-development-tools/ba-p/14258121)
- [End-to-End SAP Automation with ADT REST Services and AI](https://medium.com/@onuryz.itu/end-to-end-sap-automation-with-adt-rest-services-and-ai-a-modern-alternative-to-gui-scripting-343ea86064ea)
- [Creating an ABAP in Eclipse Plug-in Using the ADT SDK](https://community.sap.com/t5/application-development-and-automation-blog-posts/creating-a-abap-in-eclipse-plug-in-using-the-adt-sdk-part-2/ba-p/13093582)
- [abap-adt-api (GitHub)](https://github.com/marcellourbani/abap-adt-api) — 开源 ADT REST 客户端
- [erpl-adt (GitHub)](https://github.com/DataZooDE/erpl-adt) — ADT REST CLI 和 MCP 服务器
## 🗺️ 技术栈一览
```
┌─────────────────────────────────────────────────────┐
│ 开发者 IDE 层 │
│ ┌──────────┐ ┌──────────┐ ┌──────────────────┐ │
│ │ Eclipse │ │ VS Code │ │ 自定义 HTTP 客户端│ │
│ │ (ADT插件) │ │ (2026+) │ │ (Python/JS/AI) │ │
│ └─────┬─────┘ └────┬─────┘ └───────┬──────────┘ │
│ │ │ │ │
│ └──────────────┼────────────────┘ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ REST over HTTP(S) │ │
│ │ /sap/bc/adt/* │ │
│ └─────────┬───────────┘ │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ SAP ABAP 后端服务器 │ │
│ │ ┌─────────┐ ┌─────────┐ ┌───────────────┐ │ │
│ │ │ICF Handler│ │ADT REST │ │ ABAP Runtime │ │ │
│ │ │ (SICF) │ │Framework│ │ (Syntax/ATC) │ │ │
│ │ └─────────┘ └─────────┘ └───────────────┘ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌───────────────┐ │ │
│ │ │Repository│ │Transport│ │ Activation │ │ │
│ │ │ (搜索) │ │ (CTS) │ │ Framework │ │ │
│ │ └─────────┘ └─────────┘ └───────────────┘ │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
```