# YiKdWebClient-Java **Repository Path**: wangjie916/yi-kd-web-client-java ## Basic Information - **Project Name**: YiKdWebClient-Java - **Description**: 金蝶云星空 webapi集成 的java实现 方便各种第三方系统对接,以及postman等工具调试 1.支持第三方授权登录; 2.支持旧版的用户名密码登录模式; 3.支持最新的API签名模式 4.集成文件模式 有使用方面的问题可以直接提issues - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-09-08 - **Last Updated**: 2026-09-08 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # YiKdWebClient-Java ## YiKdWebClient 多语言项目 YiKdWebClient 是一个面向 **金蝶云星空 WebAPI** 的多语言开源客户端项目。各语言版本尽量保持一致的认证方式、公开方法名、参数顺序、服务路径和调用体验,方便不同技术栈对照接入。 当前项目提供 **C#、Java、Python、Go、PHP 和 HTTP (JSON)** 六种接入方式,均已完成适配。各版本使用独立仓库,并同时维护 Gitee 和 GitHub 地址。HTTP (JSON) 是不限定编程语言的通用接入版本;后续公共功能、协议报文和通用接入说明统一以其仓库 README 为准,各语言版本 README 主要维护安装、依赖、命名、异常/错误处理和同步/异步等语言特性。 | 接入版本 | 适配状态 | 当前基准 | Gitee | GitHub | | --- | --- | --- | --- | --- | | C# | 已适配 | `1.0.0.32` | [YiKdWebClient C#](https://gitee.com/lnsyzjw/yi-kd-web-client) | [YiKdWebClient C#](https://github.com/1609676823/YiKdWebClient) | | Java | 已适配,当前项目 | 对标 C# `1.0.0.32` | [YiKdWebClient Java](https://gitee.com/lnsyzjw/yi-kd-web-client-java) | [YiKdWebClient Java](https://github.com/1609676823/YiKdWebClient-Java) | | Python | 已适配 | 对标 C# `1.0.0.32` | [YiKdWebClient Python](https://gitee.com/lnsyzjw/yi-kd-web-client-python) | [YiKdWebClient Python](https://github.com/1609676823/YiKdWebClient-Python) | | Go | 已适配 | Go `v1.0.0`,对标 C# `1.0.0.32` | [YiKdWebClient Go](https://gitee.com/lnsyzjw/yi-kd-web-client-go) | [YiKdWebClient Go](https://github.com/1609676823/YiKdWebClient-Go) | | PHP | 已适配 | 对标 C# `1.0.0.32` | [YiKdWebClient PHP](https://gitee.com/lnsyzjw/yi-kd-web-client-php) | [YiKdWebClient PHP](https://github.com/1609676823/YiKdWebClient-PHP) | | HTTP (JSON) | 已适配,通用接入 | 以 HTTP (JSON) 仓库 README 为准 | [YiKdWebClient HTTP](https://gitee.com/lnsyzjw/yi-kd-web-client-http) | [YiKdWebClient HTTP](https://github.com/1609676823/YiKdWebClient-HTTP) | ### 当前仓库 YiKdWebClient-Java 是 C# 版的 **Java 8 兼容移植版**。项目使用标准 HTTP 协议调用金蝶服务,不依赖金蝶官方 Java SDK;核心库使用 Jackson 处理 JSON,产出 Java 8 字节码,CI 同时验证 JDK 8、17、21 和 25。详细映射见 [C# → Java API 对照](docs/API_MAPPING.md)。 ### 共同功能范围 所有已适配语言版本共同覆盖: - 7 个认证枚举:SHA256 签名、SHA1 签名、第三方系统登录授权、API 请求头签名、旧版用户名密码、集成密钥/CNF,以及仅为兼容旧系统保留的 `ValidateUserEnDeCode`; - 查看、保存、批量保存、提交、审核、反审核、删除、查询、下推、分配等动态表单 WebAPI; - 默认自动登录/登出、可选手动会话复用和 Cookie 管理; - 单点登录 SSO V1~V4、SSO 登出参数与登出请求; - 自定义 WebAPI 服务路径组装和调用; - 文件路径与 Base64 附件分块上传、分块进度和最终返回; - 默认 XML 配置、自定义配置路径和运行时动态传入授权信息; - 登录与业务请求的实际 URL、请求头、请求体和响应体,便于使用 Postman、ApiPost 等工具排查问题。 > [!WARNING] > 配置模板、mock 输出或本地测试截图只用于演示。接入自己的环境时,必须替换数据中心 ID、集成用户、应用 ID、应用密钥、服务地址和集成密钥文件。请勿把生产密钥、生产密码、CNF、Cookie 或长期有效的会话信息提交到公开仓库。 > [!IMPORTANT] > 旧版用户名密码认证只用于协议兼容。独立代码示例会直接定义认证变量,并使用 `123456` 等明确占位值;每个占位值旁均注明需要替换为目标环境的真实值。示例会完整输出登录请求报文,便于直接复制、运行和排查。 > [!NOTE] > 部分代码、测试、文档、示例或其他项目内容,可能在维护者指导和审查下借助 AI 工具生成、补全、重构或校对。AI 辅助内容在合并或发布前仍会由维护者进行审查和必要验证;使用者也应结合实际金蝶版本、补丁、权限和业务数据,自行评估正确性、安全性与适用性。 ## 目录 - [1. 相关资料](#1-相关资料) - [2. Java 环境与依赖](#2-java-环境与依赖) - [3. 构建、安装与引入](#3-构建安装与引入) - [4. 配置 appsettings.xml](#4-配置-appsettingsxml) - [5. 五分钟运行第一个示例](#5-五分钟运行第一个示例) - [6. ConsoleTestJava8 示例运行器](#6-consoletestjava8-示例运行器) - [7. 认证与请求示例](#7-认证与请求示例) - [8. JSON 参数与接口功能列表](#8-json-参数与接口功能列表) - [9. 单点登录 SSO](#9-单点登录-sso) - [10. 自定义 WebAPI](#10-自定义-webapi) - [11. 文件与 Base64 分块上传](#11-文件与-base64-分块上传) - [12. Java 语言特性与迁移差异](#12-java-语言特性与迁移差异) - [13. 常见问题](#13-常见问题) - [14. 开发、测试与项目地址](#14-开发测试与项目地址) ## 1. 相关资料 - 金蝶云星空官方原始报文与地址结构说明: - 金蝶云星空官方 WebAPI 接口说明: - HTTP (JSON) 通用接入文档:[Gitee](https://gitee.com/lnsyzjw/yi-kd-web-client-http#readme) / [GitHub](https://github.com/1609676823/YiKdWebClient-HTTP#readme) - Java/C# 方法与服务路径对照:[docs/API_MAPPING.md](docs/API_MAPPING.md) 金蝶官方文档中的 JSON 通常是业务参数格式,不一定等于最终 HTTP 外层报文。YiKdWebClient-Java 会把参数包装成金蝶 WebAPI 所需格式;最终请求可以通过 `ReturnLoginWebModel`、`ReturnOperationWebModel` 和 `RequestHeadersString` 查看。 ## 2. Java 环境与依赖 - JDK 8 或更高版本;核心库编译目标为 Java 8 字节码。 - 构建使用仓库自带的 Maven Wrapper,无需预先安装 Maven。 - JSON 使用 Jackson `2.22.0`: - `jackson-databind` - `jackson-core` - `jackson-annotations` - 不依赖金蝶官方 Java SDK。 `YiKdWebClient.jar` 是普通类库,不是可执行程序,也不是包含依赖的 fat JAR。`ConsoleTestJava8.jar` 和 `ConsoleTestJava8Simple.jar` 是已经包含运行依赖的可执行示例。 项目结构: | 路径 | 用途 | | --- | --- | | `YiKdWebClient/` | 核心客户端类库 | | `YiKdWebClient.Tests/` | JUnit 5 自动化测试,不连接真实金蝶环境 | | `ConsoleTestJava8/` | 完整示例运行器,覆盖认证、SSO、自定义服务和上传 | | `ConsoleTestJava8Simple/` | 最小化集成密钥登录与 `View` 示例 | | `distribution/` | 生成统一的 `dist/` 发行目录 | | `docs/API_MAPPING.md` | C# 与 Java API 映射及迁移边界 | | `docs/screenshots/` | README 中使用的本地回环运行截图 | ## 3. 构建、安装与引入 Java 没有 NuGet。本仓库当前也没有配置 Maven Central 发布,因此不能只复制一段远程依赖就直接下载。项目提供以下 **4 种引入方式**: | 引入方式 | 适用场景 | Jackson 依赖 | 推荐度 | | --- | --- | --- | --- | | 安装到本机 Maven 仓库 | Maven 业务项目、本机开发 | Maven 自动传递解析 | 推荐 | | 同一 Maven reactor 直接依赖模块 | 源码一起构建、二次开发 | Maven 自动解析 | 推荐 | | Gradle + `mavenLocal()` | Gradle 业务项目 | Gradle 按 POM 自动解析 | 推荐 | | 手工添加 `dist/lib/*.jar` | 非 Maven/Gradle、旧项目、IDE 手工管理 | 必须手工加入全部 JAR | 兼容方案 | ### 3.1 Maven 项目:安装到本机仓库 在本仓库根目录执行: Windows PowerShell: ```powershell .\mvnw.cmd -B -ntp -pl YiKdWebClient -am clean install ``` Linux/macOS: ```bash ./mvnw -B -ntp -pl YiKdWebClient -am clean install ``` 这会把父 POM、核心 JAR 和依赖信息安装到当前用户的 Maven 本机仓库。然后在业务项目 `pom.xml` 中加入: ```xml io.github.1609676823 YiKdWebClient 1.0.0-SNAPSHOT ``` Maven 会根据 POM 自动引入 Jackson,不需要再手写三个 Jackson 依赖。`version` 必须与安装时的版本一致。 需要安装明确的正式版本号时,可以覆盖唯一版本入口 `revision`: ```powershell .\mvnw.cmd -B -ntp -Drevision=1.0.0 -pl YiKdWebClient -am clean install ``` 业务项目随后使用 `1.0.0`。 ![Maven 本地安装和发行目录](docs/screenshots/00-maven-install.png) ### 3.2 同一 Maven reactor:源码模块依赖 如果业务模块与本项目处于同一 Maven reactor,可以像 `ConsoleTestJava8/pom.xml` 一样直接依赖核心模块: ```xml io.github.1609676823 YiKdWebClient ${project.version} ``` 根聚合 POM 的 `` 中需要同时包含 `YiKdWebClient` 和业务模块。适合修改客户端源码后与业务项目一起编译、测试。 ### 3.3 Gradle 项目:使用本机 Maven 仓库 先按 3.1 节执行 Maven 本地安装,再在 Gradle 中声明: Groovy DSL: ```groovy repositories { mavenLocal() mavenCentral() } dependencies { implementation 'io.github.1609676823:YiKdWebClient:1.0.0-SNAPSHOT' } ``` Kotlin DSL: ```kotlin repositories { mavenLocal() mavenCentral() } dependencies { implementation("io.github.1609676823:YiKdWebClient:1.0.0-SNAPSHOT") } ``` `mavenLocal()` 必须放在仓库列表中,否则 Gradle 找不到本机安装的 YiKdWebClient 制品。 ### 3.4 非 Maven/Gradle:手工引用 JAR 先构建完整发行目录: ```powershell .\mvnw.cmd -B -ntp clean package ``` 构建成功后,必须把 `dist/lib/` 下的 **全部 JAR** 加入 classpath,不能只添加 `YiKdWebClient.jar`: ```text dist/lib/ ├─ YiKdWebClient.jar ├─ jackson-annotations-*.jar ├─ jackson-core-*.jar └─ jackson-databind-*.jar ``` 假设 `Main.java` 与 `lib/` 位于同一目录,Windows: ```powershell javac -encoding UTF-8 -cp "lib/*" Main.java java -cp ".;lib/*" Main ``` Linux/macOS 的 classpath 分隔符为冒号: ```bash javac -encoding UTF-8 -cp "lib/*" Main.java java -cp ".:lib/*" Main ``` IntelliJ IDEA 可在 `File → Project Structure → Modules → Dependencies → + → JARs or directories` 中一次选择 `dist/lib` 下全部 JAR;Eclipse 可在 `Build Path → Add External JARs` 中添加同一组文件。 ### 3.5 完整发行目录与发布 ZIP 准备好 JDK 8 或更高版本即可,无需另装 Maven。仓库自带的 Maven Wrapper 会在首次构建时自动下载 Maven,因此首次运行需要能够访问 Maven Central。 Windows 发布时,可双击 `build-release.bat` 使用 `pom.xml` 中的默认版本,也可指定正式版本号: ```bat build-release.bat 1.0.0 ``` 脚本会从 `JAVA_HOME`、`PATH` 和常见 JDK 安装目录中查找 JDK,运行完整测试和构建,并生成: ```text dist/ ├─ ConsoleTestJava8.jar # 完整可执行示例,已包含依赖 ├─ ConsoleTestJava8Simple.jar # 精简可执行示例,已包含依赖 ├─ YiKdWebCfg/ ├─ SampleFiles/ ├─ docs/ ├─ lib/ │ ├─ YiKdWebClient.jar # 核心类库 │ └─ jackson-*.jar └─ YiKdWebClient-Java-<版本号>.zip ``` `java -jar YiKdWebClient.jar` 无法运行是正常现象:核心 JAR 没有 `Main-Class`。要执行示例,请运行 `ConsoleTestJava8.jar` 或 `ConsoleTestJava8Simple.jar`。 ## 4. 配置 appsettings.xml ### 4.1 默认路径 核心库默认从**进程工作目录**读取: ```text YiKdWebCfg/appsettings.xml ``` 相对路径以启动 Java 进程时的工作目录为准,不一定是 JAR 所在目录。先复制示例文件: ```powershell New-Item -ItemType Directory -Force .\YiKdWebCfg | Out-Null Copy-Item .\YiKdWebClient\src\main\resources\YiKdWebCfg\appsettings.example.xml ` .\YiKdWebCfg\appsettings.xml ``` 使用 `dist` 发行目录时: ```powershell Copy-Item .\dist\YiKdWebCfg\appsettings.example.xml ` .\dist\YiKdWebCfg\appsettings.xml ``` ### 4.2 完整配置示例 ```xml ``` ### 4.3 配置项说明 | 配置项 | 是否常用 | 说明 | | --- | --- | --- | | `X-KDApi-AcctID` | 是 | 数据中心 ID,也称账套 ID。可在第三方系统登录授权页面生成测试链接后查看。 | | `X-KDApi-UserName` | 是 | 集成用户。PT-146894 `[7.7.0.202111]` 及后续版本可使用指定用户登录列表中的用户;若授权允许全部用户登录,则不受该列表限制。 | | `X-KDApi-AppID` | 是 | 第三方系统登录授权的应用 ID。 | | `X-KDApi-AppSec` | 是 | 第三方系统登录授权的应用密钥。不要使用生产密钥运行公开示例。 | | `X-KDApi-LCID` | 是 | 账套语系,默认值为 `2052`。 | | `X-KDApi-OrgNum` | 否 | 多组织场景中的组织编码,主要用于签名认证模式。 | | `X-KDApi-ServerUrl` | 是 | 私有云填写产品地址,并以 `K3Cloud/` 结尾;使用公有云网关时按官方要求配置。 | ### 4.4 自定义配置路径 必须在创建 `YiK3CloudClient` 或 `SSOHelper` **之前**设置路径,因为对象字段初始化时会读取配置: ```java import YiKdWebClient.CommonService.XmlConfigHelper; import YiKdWebClient.Model.LoginType; import YiKdWebClient.YiK3CloudClient; public class Main { public static void main(String[] args) { // 必须先设置路径,再创建会读取默认配置的客户端。 XmlConfigHelper.AppConfigPath = "D:/configs/kingdee/appsettings.xml"; String formId = "SEC_User"; String json = "{\"IsUserModelInit\":\"true\"," + "\"Number\":\"Administrator\"," + "\"IsSortBySeq\":\"false\"}"; try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.LoginBySignSHA256; String resultJson = client.View(formId, json); System.out.println("配置路径:" + XmlConfigHelper.AppConfigPath); System.out.println("表单 ID(formId):" + formId); System.out.println("业务 JSON 参数(json):" + json); System.out.println("登录地址:" + client.ReturnLoginWebModel.RequestUrl); System.out.println("登录请求:" + client.ReturnLoginWebModel.RealRequestBody); System.out.println("登录响应:" + client.ReturnLoginWebModel.RealResponseBody); System.out.println("业务地址:" + client.ReturnOperationWebModel.RequestUrl); System.out.println("业务请求:" + client.ReturnOperationWebModel.RealRequestBody); System.out.println("业务响应:" + client.ReturnOperationWebModel.RealResponseBody); System.out.println("View 返回值:" + resultJson); } } } ``` 也可以完全不使用 XML,直接构造 `AppSettingsModel`,见 [7.8 动态传入授权信息](#78-动态传入授权信息)。 ### 4.5 私有云与公有云网关 私有云通常配置产品地址并以 `K3Cloud/` 结尾;部分公有云环境可能要求通过 `https://api.kingdee.com/galaxyapi/` 网关并使用 API 请求头签名。实际地址与认证规则应以目标环境和金蝶官方当前要求为准。各语言客户端均保留普通登录与 API 请求头签名能力。 ## 5. 五分钟运行第一个示例 1. 安装 JDK 8 或更高版本,并确认: ```powershell java -version ``` 2. 在仓库根目录构建发行包: ```powershell .\mvnw.cmd -B -ntp clean package ``` 3. 复制并填写本地配置: ```powershell Copy-Item .\dist\YiKdWebCfg\appsettings.example.xml ` .\dist\YiKdWebCfg\appsettings.xml ``` 4. 查看全部示例: ```powershell .\dist\run-console.cmd help ``` 5. 运行推荐的 SHA256 签名认证示例: ```powershell .\dist\run-console.cmd sign-sha256 ``` Linux/macOS 使用: ```bash ./dist/run-console.sh sign-sha256 ``` 也可以直接运行: ```powershell Set-Location .\dist java -jar ConsoleTestJava8.jar sign-sha256 ``` 控制台会显示登录请求、登录响应、业务请求、业务响应和方法返回值。HTTP 请求完成不代表业务一定成功,仍需检查返回 JSON 中的 `LoginResultType`、`IsSuccessByAPI`、`ResponseStatus.IsSuccess`、`ErrorCode` 和 `Message`。 ## 6. ConsoleTestJava8 示例运行器 `ConsoleTestJava8.jar` 提供以下独立命令,不需要反复修改 `Program.java`: | 命令 | 示例 | | --- | --- | | `sign-sha256` | SHA256 签名认证 | | `sign-sha1` | SHA1 签名认证 | | `app-secret` | 第三方系统登录授权 | | `validate-login` | 旧版用户名密码认证 | | `validate-user-endecode` | 已弃用的 `ValidateUserEnDeCode` | | `simple-passport` | 集成密钥文件认证 | | `api-sign-headers` | API 请求头签名认证 | | `dynamic-config` | 代码动态传入授权信息 | | `custom-config-path` | 自定义 XML 配置路径 | | `custom-webapi` | 调用自定义 WebAPI | | `sso-v4` | 生成 SSO V4 链接 | | `upload-file` | 文件路径分块上传 | | `upload-progress` | 带进度回调的分块上传 | | `upload-base64` | Base64 分块上传 | 统一运行格式: ```powershell .\dist\run-console.cmd <命令> ``` ### 6.1 可选环境变量 这些变量由示例程序读取,不是核心类库的隐式配置: | 环境变量 | 用途 | 默认值或来源 | | --- | --- | --- | | `YIKD_CONFIG_PATH` | 自定义 `appsettings.xml` 路径 | `dist/YiKdWebCfg/appsettings.xml` | | `YIKD_CNF_PATH` | 自定义 `.cnf` 集成密钥路径 | `dist/YiKdWebCfg/API测试.cnf` | | `YIKD_SERVER_URL` | 临时覆盖服务地址 | 从 XML 读取 | | `YIKD_ACCT_ID`、`YIKD_USER_NAME` | 动态配置的数据中心与用户 | 从 XML 读取 | | `YIKD_APP_ID`、`YIKD_APP_SECRET` | 动态配置的应用 ID 与密钥 | 从 XML 读取 | | `YIKD_LCID`、`YIKD_ORG_NUM` | 动态配置的语系与组织编码 | 从 XML 读取 | | `YIKD_VALIDATE_DBID` | 旧版登录数据中心 ID | 从 XML 读取 | | `YIKD_VALIDATE_USERNAME` | 旧版登录用户名 | `demo` | | `YIKD_VALIDATE_PASSWORD` | 旧版登录密码 | 无默认值,必须显式设置 | | `YIKD_VALIDATE_LCID` | 旧版登录语系 | `2052` | | `YIKD_UPLOAD_FILE` | 上传示例文件 | `dist/SampleFiles/upload-demo.txt` | | `YIKD_UPLOAD_FORM_ID`、`YIKD_UPLOAD_INTER_ID`、`YIKD_UPLOAD_BILL_NO` | 上传目标表单和单据 | 示例占位值 | | `YIKD_UPLOAD_CHUNK_SIZE` | 上传分块字节数 | `2 * 1024 * 1024` | | `YIKD_CUSTOM_SQL` | 自定义 WebAPI 示例 SQL | 示例查询语句 | ## 7. 认证与请求示例 本 README 中的每个 Java 代码块都按独立 `Main.java` 编写,包含自身所需的 `import`、入口、变量、客户端初始化、资源释放和结果输出,不依赖前一个代码块。复制后只需替换目标环境配置、业务参数和文件路径。 ### 7.1 到底有多少种认证模式 Java 版与 C# `LoginType` 完整一致,共有 7 个枚举值:6 种可选认证模式,以及 1 种只为旧系统保留的兼容模式。 | `LoginType` | 用途 | 是否先登录 | 建议 | | --- | --- | --- | --- | | `LoginBySignSHA256` | SHA256 签名信息认证 | 是 | 支持 SHA256 的环境优先使用 | | `LoginBySignSHA1` | SHA1 签名信息认证 | 是 | 仅用于兼容旧版本 | | `LoginByAppSecret` | 第三方系统登录授权 | 是 | 按目标环境授权方式选择 | | `LoginByApiSignHeaders` | 每个业务请求独立生成 API 签名请求头 | 否 | 使用前确认目标环境/网关支持 | | `ValidateLogin` | 旧版用户名密码认证 | 是 | 旧系统兼容,不建议新系统优先使用 | | `LoginBySimplePassport` | CNF 文件或 Base64 集成密钥认证 | 是 | 集成密钥场景 | | `ValidateUserEnDeCode` | 已弃用的旧式用户名密码编码兼容 | 是 | 仅保留旧场景兼容 | 「7 个枚举值」不等于 7 种推荐方案。新项目通常从 `LoginBySignSHA256`、`LoginByAppSecret` 或目标网关要求的 `LoginByApiSignHeaders` 中选择。 下面 6 个常用模式都已经在代码中实现并接入 `YiK3CloudClient`。控制台输出与客户端字段对应关系如下: | 输出内容 | Java 字段或变量 | | --- | --- | | 登录请求地址/请求体/响应体 | `client.ReturnLoginWebModel.RequestUrl/RealRequestBody/RealResponseBody` | | 业务请求地址/请求体/响应体 | `client.ReturnOperationWebModel.RequestUrl/RealRequestBody/RealResponseBody` | | API 签名请求头 | `client.RequestHeadersString` | | 方法返回值 | `client.View(...)` 等方法的直接返回值 | ### 7.2 签名信息认证(SHA256,推荐) 支持 SHA256 的金蝶云星空版本优先使用此方式。下面代码可以作为独立 `Main.java`: ```java import YiKdWebClient.CommonService.XmlConfigHelper; import YiKdWebClient.Model.LoginType; import YiKdWebClient.YiK3CloudClient; import java.nio.file.Paths; public class Main { public static void main(String[] args) { XmlConfigHelper.AppConfigPath = Paths.get( "YiKdWebCfg", "appsettings.xml").toAbsolutePath().toString(); String formId = "SEC_User"; String json = "{\"IsUserModelInit\":\"true\"," + "\"Number\":\"Administrator\"," + "\"IsSortBySeq\":\"false\"}"; try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.LoginBySignSHA256; String resultJson = client.View(formId, json); String loginRequestUrl = client.ReturnLoginWebModel.RequestUrl; String loginRequestBody = client.ReturnLoginWebModel.RealRequestBody; String loginResponseBody = client.ReturnLoginWebModel.RealResponseBody; String operationRequestUrl = client.ReturnOperationWebModel.RequestUrl; String operationRequestBody = client.ReturnOperationWebModel.RealRequestBody; String operationResponseBody = client.ReturnOperationWebModel.RealResponseBody; System.out.println("表单 ID(formId):" + formId); System.out.println("业务 JSON 参数(json):" + json); System.out.println("登录地址:" + loginRequestUrl); System.out.println("登录请求:" + loginRequestBody); System.out.println("登录响应:" + loginResponseBody); System.out.println("业务地址:" + operationRequestUrl); System.out.println("业务请求:" + operationRequestBody); System.out.println("业务响应:" + operationResponseBody); System.out.println("View 返回值:" + resultJson); } } } ``` 运行仓库示例: ```powershell .\dist\run-console.cmd sign-sha256 ``` ![SHA256 签名认证的 Java 请求与回环响应](docs/screenshots/01-sign-sha256.png) ### 7.3 签名信息认证(SHA1,兼容旧版本) PT-146911 `8.0.0.202205` 之前的版本不支持 SHA256 时,可切换为 SHA1。其余配置和调用方式相同: ```java import YiKdWebClient.CommonService.XmlConfigHelper; import YiKdWebClient.Model.LoginType; import YiKdWebClient.YiK3CloudClient; public class Main { public static void main(String[] args) { XmlConfigHelper.AppConfigPath = "YiKdWebCfg/appsettings.xml"; String formId = "SEC_User"; String json = "{\"IsUserModelInit\":\"true\"," + "\"Number\":\"Administrator\"," + "\"IsSortBySeq\":\"false\"}"; try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.LoginBySignSHA1; String resultJson = client.View(formId, json); System.out.println("表单 ID(formId):" + formId); System.out.println("业务 JSON 参数(json):" + json); System.out.println("登录地址:" + client.ReturnLoginWebModel.RequestUrl); System.out.println("登录请求:" + client.ReturnLoginWebModel.RealRequestBody); System.out.println("登录响应:" + client.ReturnLoginWebModel.RealResponseBody); System.out.println("业务地址:" + client.ReturnOperationWebModel.RequestUrl); System.out.println("业务请求:" + client.ReturnOperationWebModel.RealRequestBody); System.out.println("业务响应:" + client.ReturnOperationWebModel.RealResponseBody); System.out.println("View 返回值:" + resultJson); } } } ``` 构造并执行 SSO V4 登出: ```java import YiKdWebClient.CommonService.XmlConfigHelper; import YiKdWebClient.SSO.SSOHelper; import YiKdWebClient.SSO.SSOLogoutObject; public class Main { public static void main(String[] args) { XmlConfigHelper.AppConfigPath = "D:/configs/kingdee/appsettings.xml"; String userName = "Administrator"; SSOHelper helper = new SSOHelper(); SSOLogoutObject logoutRequest = helper.GetSSOLogoutap0StrV4(userName); String logoutResponse = helper.SSOExcuteLogout(logoutRequest); System.out.println("登出用户名:" + userName); System.out.println("登出地址:" + logoutRequest.RequestLogoutUrl); System.out.println("登出响应:" + logoutResponse); } } ``` V3、V2/V1 分别使用 `GetSSOLogoutap0StrV3` 和 `GetSSOLogoutap0StrV2V1`。SSO URL 和签名参数属于敏感登录材料,不应写入公开日志。 ```powershell .\dist\run-console.cmd sign-sha1 ``` ![SHA1 签名认证的 Java 请求与回环响应](docs/screenshots/02-sign-sha1.png) ### 7.4 第三方系统登录授权 该方式读取数据中心 ID、集成用户、应用 ID、应用密钥和语系: ```java import YiKdWebClient.CommonService.XmlConfigHelper; import YiKdWebClient.Model.LoginType; import YiKdWebClient.YiK3CloudClient; public class Main { public static void main(String[] args) { XmlConfigHelper.AppConfigPath = "YiKdWebCfg/appsettings.xml"; String formId = "SEC_User"; String json = "{\"IsUserModelInit\":\"true\"," + "\"Number\":\"Administrator\"," + "\"IsSortBySeq\":\"false\"}"; try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.LoginByAppSecret; String resultJson = client.View(formId, json); String loginRequestBody = client.ReturnLoginWebModel.RealRequestBody; String appSecret = client.AppSettingsModel.XKDApiAppSec; System.out.println("数据中心:" + client.AppSettingsModel.XKDApiAcctID); System.out.println("集成用户:" + client.AppSettingsModel.XKDApiUserName); System.out.println("应用 ID:" + client.AppSettingsModel.XKDApiAppID); System.out.println("应用密钥:" + appSecret); System.out.println("表单 ID(formId):" + formId); System.out.println("业务 JSON 参数(json):" + json); System.out.println("登录地址:" + client.ReturnLoginWebModel.RequestUrl); System.out.println("登录请求:" + loginRequestBody); System.out.println("登录响应:" + client.ReturnLoginWebModel.RealResponseBody); System.out.println("业务地址:" + client.ReturnOperationWebModel.RequestUrl); System.out.println("业务请求:" + client.ReturnOperationWebModel.RealRequestBody); System.out.println("业务响应:" + client.ReturnOperationWebModel.RealResponseBody); System.out.println("View 返回值:" + resultJson); } } } ``` 示例有意完整打印 `ReturnLoginWebModel.RealRequestBody`,便于核对第三方登录请求体;复制后请先把配置文件中的认证占位值替换为目标环境的真实值。 ```powershell .\dist\run-console.cmd app-secret ``` ![第三方系统登录授权的 Java 请求与回环响应](docs/screenshots/03-app-secret.png) ### 7.5 旧版用户名密码认证 该模式不依赖 `appsettings.xml` 中的应用 ID 和应用密钥,但需要服务地址、数据中心 ID、用户名、密码和语系。除兼容旧系统外,不建议新项目优先使用用户名密码方式。 ```java import YiKdWebClient.Model.LoginType; import YiKdWebClient.Model.ValidateLoginSettingsModel; import YiKdWebClient.YiK3CloudClient; public class Main { public static void main(String[] args) { String formId = "SEC_User"; String json = "{\"IsUserModelInit\":\"true\"," + "\"Number\":\"Administrator\"," + "\"IsSortBySeq\":\"false\"}"; String serverUrl = "http://127.0.0.1/K3Cloud/"; // 请替换为真实服务地址 String dataCenterId = "6979b9812f3f89"; // 请替换为真实数据中心 ID String userName = "demo"; // 请替换为真实用户名 String password = "123456"; // 请替换为该用户的真实密码 int localeId = 2052; try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.ValidateLogin; ValidateLoginSettingsModel login = new ValidateLoginSettingsModel(serverUrl); login.DbId = dataCenterId; login.UserName = userName; login.Password = password; login.lcid = localeId; client.validateLoginSettingsModel = login; String resultJson = client.View(formId, json); String loginRequestUrl = client.ReturnLoginWebModel.RequestUrl; String loginRequestBody = client.ReturnLoginWebModel.RealRequestBody; String loginResponseBody = client.ReturnLoginWebModel.RealResponseBody; String operationRequestUrl = client.ReturnOperationWebModel.RequestUrl; String operationRequestBody = client.ReturnOperationWebModel.RealRequestBody; String operationResponseBody = client.ReturnOperationWebModel.RealResponseBody; System.out.println("服务地址(serverUrl):" + serverUrl); System.out.println("数据中心 ID(dataCenterId):" + dataCenterId); System.out.println("用户名(userName):" + userName); System.out.println("密码(password):" + password); System.out.println("语系(localeId):" + localeId); System.out.println("表单 ID(formId):" + formId); System.out.println("业务 JSON 参数(json):" + json); System.out.println("登录请求地址(loginRequestUrl):" + loginRequestUrl); System.out.println( "登录请求报文(loginRequestBody):" + loginRequestBody); System.out.println("登录返回报文(loginResponseBody):" + loginResponseBody); System.out.println("业务请求地址(operationRequestUrl):" + operationRequestUrl); System.out.println("业务请求报文(operationRequestBody):" + operationRequestBody); System.out.println("业务返回报文(operationResponseBody):" + operationResponseBody); System.out.println("View 方法返回值(resultJson):" + resultJson); } } } ``` 把代码复制到业务项目的 `Main.java` 后即可运行,不要求使用环境变量。`123456` 仅用于说明密码变量应填写在哪里,接入时必须替换成目标环境中 `userName` 对应用户的真实密码。 仓库自带的示例运行器为了避免把真实密码写入版本控制,仍使用环境变量: ```powershell $env:YIKD_VALIDATE_PASSWORD = '<目标环境中 demo 用户的真实密码>' .\dist\run-console.cmd validate-login Remove-Item Env:\YIKD_VALIDATE_PASSWORD ``` 截图中的旧版登录使用本地 `MOCK-*` 回环配置,用户名为 `demo`;密码在展示前已替换为 `******`,不会包含示例密码或真实测试密码。 ![旧版用户名密码认证的 Java 请求与回环响应,密码已脱敏](docs/screenshots/04-validate-login.png) ### 7.6 集成密钥认证(文件或 Base64) `.cnf` 必须由目标金蝶环境生成,并与服务地址、数据中心匹配。文件方式: ```java import YiKdWebClient.Model.LoginBySimplePassportModel; import YiKdWebClient.Model.LoginType; import YiKdWebClient.YiK3CloudClient; import java.nio.file.Paths; public class Main { public static void main(String[] args) { String serverUrl = "http://127.0.0.1/K3Cloud/"; // 请替换为真实服务地址 // 请替换为目标环境生成的真实 CNF 文件。 String cnfPath = Paths.get( "YiKdWebCfg", "API测试.cnf").toAbsolutePath().toString(); String formId = "SEC_User"; String json = "{\"IsUserModelInit\":\"true\"," + "\"Number\":\"Administrator\"," + "\"IsSortBySeq\":\"false\"}"; try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.LoginBySimplePassport; LoginBySimplePassportModel passport = new LoginBySimplePassportModel(serverUrl); passport.CnfFilePath = cnfPath; client.LoginBySimplePassportModel = passport; String resultJson = client.View(formId, json); String loginRequestBody = client.ReturnLoginWebModel.RealRequestBody; System.out.println("服务地址:" + serverUrl); System.out.println("集成密钥文件:" + cnfPath); System.out.println("表单 ID(formId):" + formId); System.out.println("业务 JSON 参数(json):" + json); System.out.println("登录地址:" + client.ReturnLoginWebModel.RequestUrl); System.out.println("登录请求:" + loginRequestBody); System.out.println("登录响应:" + client.ReturnLoginWebModel.RealResponseBody); System.out.println("业务地址:" + client.ReturnOperationWebModel.RequestUrl); System.out.println("业务请求:" + client.ReturnOperationWebModel.RealRequestBody); System.out.println("业务响应:" + client.ReturnOperationWebModel.RealResponseBody); System.out.println("View 返回值:" + resultJson); } } } ``` 如果集成密钥已经由安全存储读取为 Base64,不必落地 `.cnf` 文件: ```java import YiKdWebClient.Model.BySimplePassportType; import YiKdWebClient.Model.LoginBySimplePassportModel; import YiKdWebClient.Model.LoginType; import YiKdWebClient.YiK3CloudClient; public class Main { public static void main(String[] args) { String serverUrl = "http://127.0.0.1/K3Cloud/"; // 请替换为真实服务地址 String base64Passport = "请替换为真实 CNF 的 Base64 内容"; // 请替换为目标环境的真实值 String formId = "SEC_User"; String json = "{\"IsUserModelInit\":\"true\"," + "\"Number\":\"Administrator\"," + "\"IsSortBySeq\":\"false\"}"; try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.LoginBySimplePassport; LoginBySimplePassportModel passport = new LoginBySimplePassportModel(serverUrl); passport.bySimplePassportType = BySimplePassportType.ForBase64; passport.SimplePassportForBase64 = base64Passport; passport.Lcid = 2052; client.LoginBySimplePassportModel = passport; String resultJson = client.View(formId, json); String loginRequestBody = client.ReturnLoginWebModel.RealRequestBody; System.out.println("服务地址:" + serverUrl); System.out.println("Base64 集成密钥:" + base64Passport); System.out.println("表单 ID(formId):" + formId); System.out.println("业务 JSON 参数(json):" + json); System.out.println("登录地址:" + client.ReturnLoginWebModel.RequestUrl); System.out.println("登录请求:" + loginRequestBody); System.out.println("登录响应:" + client.ReturnLoginWebModel.RealResponseBody); System.out.println("业务地址:" + client.ReturnOperationWebModel.RequestUrl); System.out.println("业务请求:" + client.ReturnOperationWebModel.RealRequestBody); System.out.println("业务响应:" + client.ReturnOperationWebModel.RealResponseBody); System.out.println("View 返回值:" + resultJson); } } } ``` 文件和 Base64 是同一个 `LoginBySimplePassport` 的两种密钥来源,不额外计为两个 `LoginType`。 ```powershell .\dist\run-console.cmd simple-passport ``` ![集成密钥认证的 Java 请求与回环响应](docs/screenshots/05-simple-passport.png) ### 7.7 API 请求头签名认证 该模式不会先调用登录接口,而是直接给每个业务请求生成签名请求头,因此能减少一次 Web 请求。生产使用前应确认目标金蝶版本仍支持对应算法和请求头。 ```java import YiKdWebClient.CommonService.XmlConfigHelper; import YiKdWebClient.Model.LoginType; import YiKdWebClient.YiK3CloudClient; public class Main { public static void main(String[] args) { XmlConfigHelper.AppConfigPath = "YiKdWebCfg/appsettings.xml"; String formId = "SEC_User"; String json = "{\"IsUserModelInit\":\"true\"," + "\"Number\":\"Administrator\"," + "\"IsSortBySeq\":\"false\"}"; try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.LoginByApiSignHeaders; String resultJson = client.View(formId, json); // 此模式没有独立登录请求,认证信息位于业务请求头。 System.out.println("表单 ID(formId):" + formId); System.out.println("业务 JSON 参数(json):" + json); System.out.println("签名请求头:\n" + client.RequestHeadersString); System.out.println("业务地址:" + client.ReturnOperationWebModel.RequestUrl); System.out.println("业务请求:" + client.ReturnOperationWebModel.RealRequestBody); System.out.println("业务响应:" + client.ReturnOperationWebModel.RealResponseBody); System.out.println("View 返回值:" + resultJson); } } } ``` ```powershell .\dist\run-console.cmd api-sign-headers ``` ![API 请求头签名认证的 Java 请求头与回环响应](docs/screenshots/06-api-sign-headers.png) ### 7.8 动态传入授权信息 适用于配置来自数据库、配置中心,或同一服务连接多个账套的场景。为保证代码可直接复制,下面先把全部认证项定义为本地变量: ```java import YiKdWebClient.Model.AppSettingsModel; import YiKdWebClient.Model.LoginType; import YiKdWebClient.YiK3CloudClient; public class Main { public static void main(String[] args) { String dataCenterId = "YOUR_ACCOUNT_ID"; // 请替换为真实数据中心 ID String integrationUser = "Administrator"; // 请替换为真实集成用户 String appId = "YOUR_APP_ID"; // 请替换为真实应用 ID String appSecret = "123456"; // 请替换为真实应用密钥 String localeId = "2052"; // 请按目标环境语系替换 String organizationNumber = "100"; // 请替换为真实组织编码;不需要时留空 String serverUrl = "http://127.0.0.1/K3Cloud/"; // 请替换为真实服务地址 AppSettingsModel settings = new AppSettingsModel(); settings.XKDApiAcctID = dataCenterId; settings.XKDApiUserName = integrationUser; settings.XKDApiAppID = appId; settings.XKDApiAppSec = appSecret; settings.XKDApiLCID = localeId; settings.XKDApiOrgNum = organizationNumber; settings.setXKDApiServerUrl(serverUrl); String formId = "SEC_User"; String json = "{\"IsUserModelInit\":\"true\"," + "\"Number\":\"Administrator\"," + "\"IsSortBySeq\":\"false\"}"; try (YiK3CloudClient client = new YiK3CloudClient()) { client.AppSettingsModel = settings; client.LoginType = LoginType.LoginByAppSecret; String resultJson = client.View(formId, json); String loginRequestBody = client.ReturnLoginWebModel.RealRequestBody; System.out.println("数据中心 ID:" + settings.XKDApiAcctID); System.out.println("集成用户:" + settings.XKDApiUserName); System.out.println("应用 ID:" + settings.XKDApiAppID); System.out.println("应用密钥:" + settings.XKDApiAppSec); System.out.println("语系:" + settings.XKDApiLCID); System.out.println("组织编码:" + settings.XKDApiOrgNum); System.out.println("服务地址:" + settings.getXKDApiServerUrl()); System.out.println("表单 ID(formId):" + formId); System.out.println("业务 JSON 参数(json):" + json); System.out.println("登录地址:" + client.ReturnLoginWebModel.RequestUrl); System.out.println("登录请求:" + loginRequestBody); System.out.println("登录响应:" + client.ReturnLoginWebModel.RealResponseBody); System.out.println("业务地址:" + client.ReturnOperationWebModel.RequestUrl); System.out.println("业务请求:" + client.ReturnOperationWebModel.RealRequestBody); System.out.println("业务响应:" + client.ReturnOperationWebModel.RealResponseBody); System.out.println("View 返回值:" + resultJson); } } } ``` ```powershell .\dist\run-console.cmd dynamic-config ``` ![动态传入授权信息的 Java 请求与回环响应](docs/screenshots/07-dynamic-config.png) ### 7.9 自定义配置文件路径 必须先设置路径,再创建客户端: ```java import YiKdWebClient.CommonService.XmlConfigHelper; import YiKdWebClient.Model.LoginType; import YiKdWebClient.YiK3CloudClient; public class Main { public static void main(String[] args) { XmlConfigHelper.AppConfigPath = "D:/configs/kingdee/appsettings.xml"; String formId = "SEC_User"; String json = "{\"IsUserModelInit\":\"true\"," + "\"Number\":\"Administrator\"," + "\"IsSortBySeq\":\"false\"}"; try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.LoginBySignSHA256; String resultJson = client.View(formId, json); System.out.println("配置路径:" + XmlConfigHelper.AppConfigPath); System.out.println("表单 ID(formId):" + formId); System.out.println("业务 JSON 参数(json):" + json); System.out.println("登录地址:" + client.ReturnLoginWebModel.RequestUrl); System.out.println("登录请求:" + client.ReturnLoginWebModel.RealRequestBody); System.out.println("登录响应:" + client.ReturnLoginWebModel.RealResponseBody); System.out.println("业务地址:" + client.ReturnOperationWebModel.RequestUrl); System.out.println("业务请求:" + client.ReturnOperationWebModel.RealRequestBody); System.out.println("业务响应:" + client.ReturnOperationWebModel.RealResponseBody); System.out.println("View 返回值:" + resultJson); } } } ``` ```powershell $env:YIKD_CONFIG_PATH = 'D:\configs\kingdee\appsettings.xml' .\dist\run-console.cmd custom-config-path ``` ![自定义配置路径的 Java 请求与回环响应](docs/screenshots/08-custom-config-path.png) ### 7.10 已弃用的 ValidateUserEnDeCode > [!CAUTION] > `ValidateUserEnDeCode` 已通过 `@Deprecated` 标记为弃用。它会对用户名和密码执行可逆的旧式 DES 兼容编码,并调用 `Kingdee.BOS.WebApi.ServicesStub.AuthService.ValidateUserEnDeCode.common.kdsvc`。金蝶官方通用 WebAPI 登录说明未推荐这种方式,项目仅为曾经出现过的旧版本附件等历史场景保留兼容实现。编码后的密码仍然必须按密码本身保护;新项目请优先使用 SHA256 签名认证或当前环境支持的其他认证方式。 该模式与普通 `ValidateLogin` 使用相同的 `ValidateLoginSettingsModel`,区别是把 `LoginType` 设置为 `LoginType.ValidateUserEnDeCode`。下面代码包含全部 `import`、`main` 入口、认证参数、业务调用、真实请求/响应读取和资源释放,可直接保存为 `Main.java`: ```java import YiKdWebClient.Model.LoginType; import YiKdWebClient.Model.ValidateLoginSettingsModel; import YiKdWebClient.YiK3CloudClient; public class Main { @SuppressWarnings("deprecation") public static void main(String[] args) { String serverUrl = "http://127.0.0.1/K3Cloud/"; // 请替换为真实服务地址 String dataCenterId = "6979b9812f3f89"; // 请替换为真实数据中心 ID String userName = "demo"; // 请替换为真实用户名 String password = "123456"; // 请替换为该用户的真实密码 int localeId = 2052; String formId = "SEC_User"; String json = "{\"IsUserModelInit\":\"true\"," + "\"Number\":\"Administrator\"," + "\"IsSortBySeq\":\"false\"}"; ValidateLoginSettingsModel login = new ValidateLoginSettingsModel(serverUrl); login.DbId = dataCenterId; login.UserName = userName; login.Password = password; login.lcid = localeId; try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.ValidateUserEnDeCode; client.validateLoginSettingsModel = login; String resultJson = client.View(formId, json); String compatibilityMode = client.LoginType.toString(); String loginRequestUrl = client.ReturnLoginWebModel.RequestUrl; String loginRequestBody = client.ReturnLoginWebModel.RealRequestBody; String loginResponseBody = client.ReturnLoginWebModel.RealResponseBody; String operationRequestUrl = client.ReturnOperationWebModel.RequestUrl; String operationRequestBody = client.ReturnOperationWebModel.RealRequestBody; String operationResponseBody = client.ReturnOperationWebModel.RealResponseBody; System.out.println("兼容模式(compatibilityMode):" + compatibilityMode); System.out.println("服务地址(serverUrl):" + serverUrl); System.out.println("数据中心 ID(dataCenterId):" + dataCenterId); System.out.println("用户名(userName):" + userName); System.out.println("密码(password):" + password); System.out.println("语系(localeId):" + localeId); System.out.println("表单 ID(formId):" + formId); System.out.println("业务 JSON 参数(json):" + json); System.out.println("登录请求地址(loginRequestUrl):" + loginRequestUrl); System.out.println("登录请求报文(loginRequestBody):" + loginRequestBody); System.out.println("登录返回报文(loginResponseBody):" + loginResponseBody); System.out.println("业务请求地址(operationRequestUrl):" + operationRequestUrl); System.out.println("业务请求报文(operationRequestBody):" + operationRequestBody); System.out.println("业务返回报文(operationResponseBody):" + operationResponseBody); System.out.println("View 方法返回值(resultJson):" + resultJson); } } } ``` 在仓库根目录中,可以直接编译并运行这份独立代码: ```powershell javac -encoding UTF-8 -cp ".\dist\ConsoleTestJava8.jar" .\Main.java java -cp ".;.\dist\ConsoleTestJava8.jar" Main ``` 仓库内置的真实环境验证命令如下。运行器从环境变量读取实际测试密码,不会把密码写入源码;运行结束后请删除当前 PowerShell 会话中的临时变量: ```powershell $env:YIKD_VALIDATE_PASSWORD = '<替换为目标环境的实际测试密码>' .\dist\run-console.cmd validate-user-endecode Remove-Item Env:\YIKD_VALIDATE_PASSWORD ``` `123456` 仅用于说明密码变量应填写在哪里,接入时必须替换成 `userName` 对应用户的真实密码。下图由 Java 运行器与临时回环 HTTP 服务真实执行后生成;明文密码及其可逆旧式编码值均已脱敏。 ![已弃用的 ValidateUserEnDeCode Java 实际运行截图,密码已脱敏](docs/screenshots/14-validate-user-endecode.png) ## 8. JSON 参数与接口功能列表 ### 8.1 JSON 参数 传给客户端方法的 JSON 与金蝶官方接口要求的业务参数一致,客户端负责包装外层 HTTP 报文。例如查看用户: ```java import YiKdWebClient.CommonService.XmlConfigHelper; import YiKdWebClient.Model.LoginType; import YiKdWebClient.YiK3CloudClient; public class Main { public static void main(String[] args) { XmlConfigHelper.AppConfigPath = "YiKdWebCfg/appsettings.xml"; String formId = "SEC_User"; String json = "{\"IsUserModelInit\":\"true\"," + "\"Number\":\"Administrator\"," + "\"IsSortBySeq\":\"false\"}"; try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.LoginBySignSHA256; String resultJson = client.View(formId, json); System.out.println("表单 ID:" + formId); System.out.println("业务 JSON 参数:" + json); System.out.println("登录地址:" + client.ReturnLoginWebModel.RequestUrl); System.out.println("登录请求:" + client.ReturnLoginWebModel.RealRequestBody); System.out.println("登录响应:" + client.ReturnLoginWebModel.RealResponseBody); System.out.println("业务地址:" + client.ReturnOperationWebModel.RequestUrl); System.out.println("业务请求:" + client.ReturnOperationWebModel.RealRequestBody); System.out.println("业务响应:" + client.ReturnOperationWebModel.RealResponseBody); System.out.println("View 返回值:" + resultJson); } } } ``` ### 8.2 常用接口 | 方法 | 用途 | | --- | --- | | `View` | 查看单据或基础资料 | | `Save`、`BatchSave`、`Draft`、`GroupSave`、`FlexSave` | 保存、批量保存、暂存、分组保存、弹性域保存 | | `Submit`、`Audit`、`UnAudit`、`Delete`、`GroupDelete` | 提交、审核、反审核、删除和分组删除 | | `ExecuteOperation`、`Push`、`Allocate`、`CancelAllocate`、`CancelAssign`、`Disassembly` | 通用操作、下推、分配、取消和拆单 | | `ExecuteBillQuery`、`GetSysReportData`、`QueryBusinessInfo`、`QueryGroupInfo` | 单据查询、报表和业务信息查询 | | `SendMsg`、`SwitchOrg`、`WorkflowAudit` | 消息、组织切换和工作流审批 | | `AttachmentUpLoad`、`AttachmentDownLoad`、`UploadFile` | 原始附件/文件服务接口 | | `CustomBusinessService`、`CustomBusinessServiceByParameters` | 自定义 WebAPI | | `GetDataCenterList` | 获取数据中心列表 | 完整方法、重载和服务路径见 [API 对照文档](docs/API_MAPPING.md)。 ### 8.3 自动登录、自动登出与会话复用 大部分业务方法都有以下重载:`View(formId, json)` 会自动登录和登出;`View(formId, json, autoLogin)` 可控制是否登录,调用后仍自动登出;`View(formId, json, autoLogin, autoLogout)` 可分别控制登录与登出。 连续调用多个接口时,可以复用同一 Cookie 会话: ```java import YiKdWebClient.CommonService.XmlConfigHelper; import YiKdWebClient.Model.LoginType; import YiKdWebClient.YiK3CloudClient; public class Main { public static void main(String[] args) { XmlConfigHelper.AppConfigPath = "YiKdWebCfg/appsettings.xml"; try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.LoginBySignSHA256; client.Login(); String loginRequestUrl = client.ReturnLoginWebModel.RequestUrl; String loginRequestBody = client.ReturnLoginWebModel.RealRequestBody; String loginResponseBody = client.ReturnLoginWebModel.RealResponseBody; try { String userFormId = "SEC_User"; String userPayload = "{\"IsUserModelInit\":\"true\"," + "\"Number\":\"Administrator\"," + "\"IsSortBySeq\":\"false\"}"; String userJson = client.View( userFormId, userPayload, false, false); String userRequestUrl = client.ReturnOperationWebModel.RequestUrl; String userRequestBody = client.ReturnOperationWebModel.RealRequestBody; String userResponseBody = client.ReturnOperationWebModel.RealResponseBody; String materialPayload = "{\"FormId\":\"BD_MATERIAL\"," + "\"FieldKeys\":\"FNumber,FName\"," + "\"FilterString\":\"\"," + "\"OrderString\":\"\"," + "\"TopRowCount\":0," + "\"StartRow\":0," + "\"Limit\":10}"; String materialJson = client.ExecuteBillQuery( materialPayload, false, false); String materialRequestUrl = client.ReturnOperationWebModel.RequestUrl; String materialRequestBody = client.ReturnOperationWebModel.RealRequestBody; String materialResponseBody = client.ReturnOperationWebModel.RealResponseBody; System.out.println("登录请求地址:" + loginRequestUrl); System.out.println("登录请求报文:" + loginRequestBody); System.out.println("登录返回报文:" + loginResponseBody); System.out.println("用户表单 ID:" + userFormId); System.out.println("用户业务 JSON:" + userPayload); System.out.println("用户业务请求地址:" + userRequestUrl); System.out.println("用户业务请求报文:" + userRequestBody); System.out.println("用户业务返回报文:" + userResponseBody); System.out.println("用户 View 返回值:" + userJson); System.out.println("物料查询 JSON:" + materialPayload); System.out.println("物料业务请求地址:" + materialRequestUrl); System.out.println("物料业务请求报文:" + materialRequestBody); System.out.println("物料业务返回报文:" + materialResponseBody); System.out.println("物料 ExecuteBillQuery 返回值:" + materialJson); } finally { client.Logout(); } } } } ``` `close()`/`Dispose()` 只释放客户端状态,不代替 HTTP `Logout()`。客户端包含可变 Cookie、请求头和最近请求状态,不要让多个线程并发共享同一个实例。 ## 9. 单点登录 SSO 项目支持 SSO V1、V2、V3 和 V4。下面生成 V4 的 HTML5、Silverlight 和 WPF 入口;生成 URL 本身不会发送 HTTP 请求: ```java import YiKdWebClient.CommonService.XmlConfigHelper; import YiKdWebClient.SSO.SSOHelper; import YiKdWebClient.SSO.SSOLoginUrlObject; public class Main { public static void main(String[] args) { XmlConfigHelper.AppConfigPath = "D:/configs/kingdee/appsettings.xml"; String userName = "Administrator"; SSOHelper helper = new SSOHelper(); SSOLoginUrlObject urls = helper.GetSsoUrlsV4(userName); System.out.println("登录用户名:" + userName); System.out.println("数据中心 ID:" + helper.simplePassportLoginArg.dbid); System.out.println("应用 ID:" + helper.simplePassportLoginArg.appid); System.out.println("时间戳:" + helper.timestamp); System.out.println("签名:" + helper.simplePassportLoginArg.signeddata); System.out.println("签名参数 JSON:" + helper.argJosn); System.out.println("Base64 参数:" + helper.argJsonBase64); System.out.println("HTML5:" + urls.html5Url); System.out.println("Silverlight:" + urls.silverlightUrl); System.out.println("WPF:" + urls.wpfUrl); // 旧版本按目标环境选择: // helper.GetSsoUrlsV3(userName); // helper.GetSsoUrlsV2(userName); // helper.GetSsoUrlsV1(userName); } } ``` ```powershell .\dist\run-console.cmd sso-v4 ``` ![SSO V4 的 Java 本地生成结果](docs/screenshots/10-sso-v4.png) ## 10. 自定义 WebAPI 官方自定义 WebAPI 报文格式与参数说明: 目标金蝶环境必须先部署服务端自定义 WebAPI。Java 仓库只移植客户端;C# 主项目中的 `GlobalServiceCustom.WebApi` 是 .NET Framework 4.8 服务端示例,真正部署的是它生成的 `GlobalServiceCustom.WebApi.dll`,不是 Java 发行包或其编译引用。 客户端提供两类调用:`CustomBusinessService` 由客户端完成标准外层参数包装;`CustomBusinessServiceByParameters` 将调用者准备的 JSON 作为原始请求体发送。服务路径既可直接传字符串,也可通过 `CustomServicesStubpath` 由命名空间、类名和公开方法名生成;这些定位值必须与服务端部署内容完全一致。 > [!CAUTION] > 不要把任意用户输入直接拼接到 SQL 或其他高权限服务参数中。服务端必须实施身份授权、参数校验、最小权限和审计。 ```java import YiKdWebClient.CommonService.XmlConfigHelper; import YiKdWebClient.CommonService.JsonSupport; import YiKdWebClient.Model.CustomServicesStubpath; import YiKdWebClient.Model.LoginType; import YiKdWebClient.YiK3CloudClient; import java.util.LinkedHashMap; import java.util.Map; public class Main { public static void main(String[] args) { XmlConfigHelper.AppConfigPath = "YiKdWebCfg/appsettings.xml"; String sql = "SELECT TOP 10 * FROM T_BD_MATERIAL_L"; Map body = new LinkedHashMap(); body.put("parameters", new String[] { sql }); String json = JsonSupport.serialize(body, true, false); CustomServicesStubpath service = new CustomServicesStubpath(); service.ProjetNamespace = "GlobalServiceCustom.WebApi"; service.ProjetClassName = "DataServiceHandler"; service.ProjetClassMethod = "CommonRunnerService"; try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.LoginByAppSecret; String resultJson = client.CustomBusinessServiceByParameters(json, service); String loginRequestBody = client.ReturnLoginWebModel.RealRequestBody; System.out.println("服务端命名空间:" + service.ProjetNamespace); System.out.println("服务端类名:" + service.ProjetClassName); System.out.println("服务端方法名:" + service.ProjetClassMethod); System.out.println("SQL 参数:" + sql); System.out.println("接口参数 JSON:" + json); System.out.println("登录地址:" + client.ReturnLoginWebModel.RequestUrl); System.out.println("登录请求:" + loginRequestBody); System.out.println("登录响应:" + client.ReturnLoginWebModel.RealResponseBody); System.out.println("业务地址:" + client.ReturnOperationWebModel.RequestUrl); System.out.println("业务请求:" + client.ReturnOperationWebModel.RealRequestBody); System.out.println("业务响应:" + client.ReturnOperationWebModel.RealResponseBody); System.out.println("自定义接口返回值:" + resultJson); } } } ``` 命名空间、类名和公开方法名必须与服务器端部署内容完全一致。 ```powershell .\dist\run-console.cmd custom-webapi ``` ![自定义 WebAPI 的 Java 请求与回环响应](docs/screenshots/09-custom-webapi.png) ## 11. 文件与 Base64 分块上传 官方附件上传报文结构与原理: 附件上传会写入目标业务系统。接入前必须替换真实的表单 ID、单据内码和单据编号,并确认目标环境已配置附件或对象存储。高层封装支持文件路径、分块进度回调和 Base64 数据;每个成功分块返回的 `FileId` 会自动写回上传模型。 ### 11.1 文件路径上传并获取进度 ```java import YiKdWebClient.Model.LoginBySimplePassportModel; import YiKdWebClient.Model.LoginType; import YiKdWebClient.ToolsHelper.AttachmentHelper; import YiKdWebClient.ToolsHelper.UploadModel; import YiKdWebClient.YiK3CloudClient; import java.nio.file.Files; import java.nio.file.Paths; public class Main { public static void main(String[] args) { String serverUrl = "http://127.0.0.1/K3Cloud/"; // 请替换为真实服务地址 String cnfFilePath = "D:/configs/kingdee/API测试.cnf"; // 请替换为真实 CNF 路径 String filePath = "D:/files/upload-demo.txt"; // 请替换为真实待上传文件 String formId = "SAL_SaleOrder"; String interId = "100020"; String billNumber = "XSDD000019"; long chunkSize = 2L * 1024L * 1024L; if (!Files.isRegularFile(Paths.get(filePath))) { throw new IllegalArgumentException( "找不到待上传文件,请修改 filePath:" + filePath); } try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.LoginBySimplePassport; LoginBySimplePassportModel passport = new LoginBySimplePassportModel(serverUrl); passport.CnfFilePath = cnfFilePath; client.LoginBySimplePassportModel = passport; UploadModel upload = new UploadModel(); upload.data.FormId = formId; upload.data.InterId = interId; upload.data.BillNO = billNumber; String resultJson = AttachmentHelper.AttachmentUploadByFilePath( filePath, client, upload, chunkSize, (chunk, currentClient) -> System.out.println( "已完成分块 " + (chunk.Chunkindex + 1) + ",最后一块:" + chunk.IsLast)); String loginRequestBody = client.ReturnLoginWebModel.RealRequestBody; System.out.println("待上传文件:" + filePath); System.out.println("目标表单:" + formId); System.out.println("单据内码:" + interId); System.out.println("单据编号:" + billNumber); System.out.println("分块大小:" + chunkSize); System.out.println("登录请求地址:" + client.ReturnLoginWebModel.RequestUrl); System.out.println("登录请求报文:" + loginRequestBody); System.out.println("登录返回报文:" + client.ReturnLoginWebModel.RealResponseBody); System.out.println("最后一块请求地址:" + client.ReturnOperationWebModel.RequestUrl); System.out.println("最后一块请求:" + client.ReturnOperationWebModel.RealRequestBody); System.out.println("最后一块响应:" + client.ReturnOperationWebModel.RealResponseBody); System.out.println("上传返回值:" + resultJson); } } } ``` 不需要进度时,可省略最后一个回调参数。 ```powershell .\dist\run-console.cmd upload-file .\dist\run-console.cmd upload-progress ``` ![文件路径分块上传的 Java 请求与回环响应](docs/screenshots/11-upload-file.png) ![带进度回调的 Java 分块上传](docs/screenshots/12-upload-progress.png) ### 11.2 Base64 分块上传 已经持有 Base64 文件内容时使用: ```java import YiKdWebClient.Model.LoginBySimplePassportModel; import YiKdWebClient.Model.LoginType; import YiKdWebClient.ToolsHelper.AttachmentHelper; import YiKdWebClient.ToolsHelper.UploadModel; import YiKdWebClient.YiK3CloudClient; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Base64; public class Main { public static void main(String[] args) throws Exception { String serverUrl = "http://127.0.0.1/K3Cloud/"; // 请替换为真实服务地址 String cnfFilePath = "D:/configs/kingdee/API测试.cnf"; // 请替换为真实 CNF 路径 Path filePath = Paths.get("D:/files/upload-demo.txt"); // 请替换为真实待上传文件 String formId = "SAL_SaleOrder"; String interId = "100020"; String billNumber = "XSDD000019"; long chunkSize = 2L * 1024L * 1024L; if (!Files.isRegularFile(filePath)) { throw new IllegalArgumentException( "找不到待上传文件,请修改 filePath:" + filePath); } String base64Data = Base64.getEncoder().encodeToString( Files.readAllBytes(filePath)); try (YiK3CloudClient client = new YiK3CloudClient()) { client.LoginType = LoginType.LoginBySimplePassport; LoginBySimplePassportModel passport = new LoginBySimplePassportModel(serverUrl); passport.CnfFilePath = cnfFilePath; client.LoginBySimplePassportModel = passport; UploadModel upload = new UploadModel(); upload.data.FormId = formId; upload.data.InterId = interId; upload.data.BillNO = billNumber; String resultJson = AttachmentHelper.AttachmentUploadByBase64( base64Data, filePath.getFileName().toString(), client, upload, chunkSize); String loginRequestBody = client.ReturnLoginWebModel.RealRequestBody; System.out.println("源文件:" + filePath); System.out.println("Base64 字符数:" + base64Data.length()); System.out.println("目标表单:" + formId); System.out.println("单据内码:" + interId); System.out.println("单据编号:" + billNumber); System.out.println("分块大小:" + chunkSize); System.out.println("登录请求地址:" + client.ReturnLoginWebModel.RequestUrl); System.out.println("登录请求报文:" + loginRequestBody); System.out.println("登录返回报文:" + client.ReturnLoginWebModel.RealResponseBody); System.out.println("最后一块请求地址:" + client.ReturnOperationWebModel.RequestUrl); System.out.println("最后一块请求:" + client.ReturnOperationWebModel.RealRequestBody); System.out.println("最后一块响应:" + client.ReturnOperationWebModel.RealResponseBody); System.out.println("上传返回值:" + resultJson); } } } ``` ```powershell .\dist\run-console.cmd upload-base64 ``` ![Base64 分块上传的 Java 请求与回环响应](docs/screenshots/13-upload-base64.png) ### 11.3 `UploadModel` 字段用途 | 字段 | 用途 | | --- | --- | | `FileName` | 当前附件文件名,高层封装会按源文件填充 | | `FormId` | 单据或表单 ID | | `InterId` | 单据内码 | | `Entrykey` | 单据体标识;表头附件留空 | | `EntryinterId` | 单据体内码;表头附件通常使用默认值 `-1` | | `BillNO` | 单据编号 | | `AliasFileName` | 可选的附件别名 | | `FileId` | 服务端返回的文件 ID,每个成功分块后自动更新 | | `SendByte` | 当前分块的 Base64 内容,高层封装自动填充 | | `IsLast` | 是否为最后一块,高层封装自动填充 | ## 12. Java 语言特性与迁移差异 | 对照项 | C# | Java | Python | Go | | --- | --- | --- | --- | --- | | 获取方式 | NuGet | Maven 本地仓库、Gradle `mavenLocal()` 或发行 JAR | 源码可编辑安装或构建 wheel | Go Modules 按版本 Tag 获取源码并参与编译 | | 资源释放 | `using` / `Dispose()` | `try (...)` / `close()` | `with` / `close()` | `defer client.Close()` | | Cookie | `CookieContainer` | `java.net.CookieManager` | `requests.cookies.RequestsCookieJar` | `net/http/cookiejar` | | 超时 | `TimeSpan` | `java.time.Duration` | 秒数或 `timedelta` | `time.Duration` | | 集合 | `Dictionary` | `Map` | `dict` | `map[K]V` | | 回调 | `Action` / `Action` | `Consumer` / `BiConsumer` | `Callable` | `func(...) error` | | JSON | `System.Text.Json` | Jackson 2.x | 标准库 `json` | 标准库 `encoding/json` | | 公开方法名 | PascalCase | PascalCase | 兼容 PascalCase,并提供常用 `snake_case` | 导出方法使用 Go 命名,并保留必要兼容别名 | | 运行时 | .NET 多目标框架 | Java 8 字节码,可运行于较新 JDK | Python 3.9~3.13 | Go 1.22 及以上 | | 异步边界 | 依具体 HTTP 工具 | 公开业务客户端为同步调用 | 公开业务客户端为同步调用,底层 HTTP 工具有异步包装 | 同步方法支持 `context.Context`,并发由调用方组织 | C#、Java、Python、Go 和 PHP 五个语言客户端均已完成适配,核心认证含义、动态表单方法语义和服务路径保持一致;HTTP (JSON) 通用接入也已完成适配,协议报文和字段说明以其仓库 README 为准。上表重点对照 C#、Java、Python 和 Go 的语言差异;PHP 的获取方式、运行时版本、异常模型及 API 映射以 PHP 仓库 README 为准。Java、Python 和 Go 客户端都包含可变 Cookie、请求头和最近请求状态;客户端属于有状态对象,并发请求宜按会话或工作单元创建独立实例。 ## 13. 常见问题 ### 13.1 找不到 `YiKdWebCfg/appsettings.xml` 核心库按进程工作目录解析默认相对路径。请检查 `Paths.get("").toAbsolutePath()`,或在创建客户端前显式设置 `XmlConfigHelper.AppConfigPath`。 `AppSettingsModel` 为兼容 C# 原项目,在配置缺失时会保留空字段而不是立即抛错;因此登录失败时也要先确认实际读取路径。 ### 13.2 出现 `NoClassDefFoundError: com/fasterxml/jackson/...` 手工引用时遗漏了 Jackson。把 `dist/lib/` 下所有 JAR 加入 classpath,或改用 Maven/Gradle 依赖。核心库 JAR 不会把 Jackson 打包进去。 ### 13.3 `java -jar YiKdWebClient.jar` 无法运行 这是正常现象:它是类库,没有 `Main-Class`。请在业务项目中引用,或运行 `ConsoleTestJava8.jar`。 ### 13.4 返回登录失败 依次检查: 1. 服务地址与数据中心是否匹配; 2. 集成用户是否在第三方系统登录授权范围内; 3. 应用 ID 与应用密钥是否成对; 4. 语系和组织编码是否适用; 5. 服务器时间是否准确,避免签名时间戳偏差; 6. `LoginBySimplePassport` 的 `.cnf` 是否来自同一目标环境; 7. 旧版登录是否正确提供了密码。 ### 13.5 登录成功但业务调用失败 继续检查 `ResponseStatus`、用户权限、表单 ID、字段名、单据状态和组织范围。可查看 `ReturnOperationWebModel`,把实际 URL、请求头和请求体复制到 Postman/ApiPost 对比;输出前必须脱敏。 ### 13.6 API 请求头模式没有登录报文 这是设计行为。`LoginByApiSignHeaders` 不调用独立登录接口,认证信息在 `RequestHeadersString` 和业务请求头中。 ### 13.7 `.cnf` 集成密钥无法使用 `.cnf` 必须由目标环境生成,并与服务地址和数据中心匹配。复制其他环境的文件通常无法登录。也可以把文件内容安全读取为 Base64,使用 `BySimplePassportType.ForBase64`。 ### 13.8 上传返回存储配置错误 这通常表示请求已到达附件接口,但服务端未正确配置附件/对象存储,或示例中的表单、单据内码和编号不存在。请先完成服务端配置并替换真实参数。 ### 13.9 Windows 中文路径下测试失败 优先使用仓库 Maven Wrapper。项目已针对 JDK 8 在中文 Windows 工作区中的 Surefire classpath 校验做兼容配置: ```powershell .\mvnw.cmd -B -ntp clean verify ``` ## 14. 开发、测试与项目地址 执行全部编译、95 项单元测试和回环 HTTP 测试: ```powershell .\mvnw.cmd -B -ntp clean verify ``` 自动化测试不需要真实金蝶地址或密钥。修改公开方法、参数顺序、认证报文或服务路径时,请同步更新测试和 [docs/API_MAPPING.md](docs/API_MAPPING.md)。更多约定见 [CONTRIBUTING.md](CONTRIBUTING.md)、[SECURITY.md](SECURITY.md) 和 [CHANGELOG.md](CHANGELOG.md)。 项目地址: - C# Gitee: - C# GitHub: - Java Gitee: - Java GitHub: - Python Gitee: - Python GitHub: - Go Gitee: - Go GitHub: - PHP Gitee: - PHP GitHub: 本项目采用 [MIT License](LICENSE)。你可以在保留版权和许可声明的前提下使用、复制、修改、合并、发布、分发、再许可和销售本软件。软件按“原样”提供,不附带任何明示或默示担保。