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:
@@ -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 Handler(ADT 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 环境)
|
||||
|
||||
### 解决方案 1:Language Server 复用
|
||||
|
||||
借鉴 Java VS Code 扩展的思路 — 将 Eclipse ADT 插件 "包装" 为 Language Server:
|
||||
|
||||
```
|
||||
VS Code ←→ Language Server Protocol (LSP) ←→ Eclipse ADT 代码库 (2.9M 行)
|
||||
```
|
||||
|
||||
- 无需从头重写客户端代码
|
||||
- Eclipse 和 VS Code 共享同一代码库
|
||||
- 连接任何 Eclipse 支持的 ABAP 服务器版本
|
||||
|
||||
### 解决方案 2:Server-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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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.修改代码 - 原理]]
|
||||
@@ -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)
|
||||
@@ -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 Code(2026年) → 更多 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 │ │ │
|
||||
│ │ └─────────┘ └─────────┘ └───────────────┘ │ │
|
||||
│ └──────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
```
|
||||
Reference in New Issue
Block a user