Files
sap-cli-skill/docs/adt/07.ADT代码同步工具.md
T
吴让宇 c5905a5b1e 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 重写为单源开发流程。
2026-09-11 00:40:15 +08:00

10 KiB

title, created, tags, parent, prev
title created tags parent prev
ADT 代码同步工具 — 使用指南 2026-05-19
SAP
ADT
REST
python
sync
pull
push
activate
sap-cli/README sap-cli/06.ADT测试代码

ADT 代码同步工具 — 使用指南

[!abstract] 概述 通过 test_adt_client.py 脚本实现本地与 SAP 系统之间的 ABAP 代码双向同步。支持拉取代码到本地编辑、修改后推送回 SAP 并自动激活。

完整工作流遵循 sap-cli/03.修改代码-原理 中描述的 锁定 → 编辑 → 保存 → 解锁 → 激活 流程。

1. 快速开始

前置条件

pip install requests

基本用法

# 拉取代码(从 SAP 下载到本地)
python sap-cli/test_adt_client.py pull

# 推送代码(从本地上传到 SAP 并激活)
python sap-cli/test_adt_client.py push

配置修改

脚本顶部的配置区域:

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

关键请求头

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

GET /sap/bc/adt/compatibility/graph
x-csrf-token: fetch
X-sap-adt-sessiontype: stateless

响应头中返回 CSRF Token,后续所有写操作必须携带。

Step 1: 查询传输请求

GET /sap/bc/adt/cts/transportrequests
Accept: application/vnd.sap.adt.transportorganizer.v1+xml

响应 XML 中解析第一个状态为 D(可修改)的传输请求号。

响应示例:

<tm:request tm:number="DEVK901360" tm:status="D" tm:desc="更改记录" />

[!important] 传输请求号 (corrNr) 传输请求号是代码写入 SAP 的必要参数。脚本自动查询并使用第一个可修改的请求。

XML 属性名带有命名空间前缀:{http://www.sap.com/cts/adt/tm}number

Step 2: 锁定对象

POST /sap/bc/adt/programs/programs/{name}
?_action=LOCK
&accessMode=MODIFY
X-sap-adt-sessiontype: stateful
x-csrf-token: {token}

响应:

<adtcore:object LOCK_HANDLE="Jn333u2TRIqrbxSAs9ib7Ket9q0=" />

[!warning] 会话状态 锁定操作必须在 stateful 会话中执行(X-sap-adt-sessiontype: stateful)。 登录端点必须使用 /sap/bc/adt/compatibility/graph(不是 /discovery),否则锁定会失败。

Step 3: 写入源代码

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: 解锁对象

POST /sap/bc/adt/programs/programs/{name}
?_action=UNLOCK
&lockHandle={lockHandle}
X-sap-adt-sessiontype: stateful
x-csrf-token: {token}

Step 5: 激活对象

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>

激活响应可能包含错误消息:

<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. 传输请求详解

查询可用的传输请求

GET /sap/bc/adt/cts/transportrequests
Accept: application/vnd.sap.adt.transportorganizer.v1+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 解析注意事项

属性名带有命名空间前缀,解析时需要使用完整名称:

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. 同步到 SAPpython test_adt_client.py push
    • 脚本自动执行:锁定 → 写入 → 解锁 → 激活
  3. 查看日志确认 — 检查 adt_client.log 确认无激活错误

🔗 相关笔记

📚 参考来源