Skip to content

Repository files navigation

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# YiKdWebClient C#
Java 已适配,当前项目 对标 C# 1.0.0.32 YiKdWebClient Java YiKdWebClient Java
Python 已适配 对标 C# 1.0.0.32 YiKdWebClient Python YiKdWebClient Python
Go 已适配 Go v1.0.0,对标 C# 1.0.0.32 YiKdWebClient Go YiKdWebClient Go
PHP 已适配 对标 C# 1.0.0.32 YiKdWebClient PHP YiKdWebClient PHP
HTTP (JSON) 已适配,通用接入 以 HTTP (JSON) 仓库 README 为准 YiKdWebClient HTTP YiKdWebClient HTTP

当前仓库

YiKdWebClient-Java 是 C# 版的 Java 8 兼容移植版。项目使用标准 HTTP 协议调用金蝶服务,不依赖金蝶官方 Java SDK;核心库使用 Jackson 处理 JSON,产出 Java 8 字节码,CI 同时验证 JDK 8、17、21 和 25。详细映射见 C# → Java API 对照

共同功能范围

所有已适配语言版本共同覆盖:

  • 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. 相关资料

金蝶官方文档中的 JSON 通常是业务参数格式,不一定等于最终 HTTP 外层报文。YiKdWebClient-Java 会把参数包装成金蝶 WebAPI 所需格式;最终请求可以通过 ReturnLoginWebModelReturnOperationWebModelRequestHeadersString 查看。

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.jarConsoleTestJava8Simple.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:

.\mvnw.cmd -B -ntp -pl YiKdWebClient -am clean install

Linux/macOS:

./mvnw -B -ntp -pl YiKdWebClient -am clean install

这会把父 POM、核心 JAR 和依赖信息安装到当前用户的 Maven 本机仓库。然后在业务项目 pom.xml 中加入:

<dependency>
  <groupId>io.github.1609676823</groupId>
  <artifactId>YiKdWebClient</artifactId>
  <version>1.0.0-SNAPSHOT</version>
</dependency>

Maven 会根据 POM 自动引入 Jackson,不需要再手写三个 Jackson 依赖。version 必须与安装时的版本一致。

需要安装明确的正式版本号时,可以覆盖唯一版本入口 revision

.\mvnw.cmd -B -ntp -Drevision=1.0.0 -pl YiKdWebClient -am clean install

业务项目随后使用 <version>1.0.0</version>

Maven 本地安装和发行目录

3.2 同一 Maven reactor:源码模块依赖

如果业务模块与本项目处于同一 Maven reactor,可以像 ConsoleTestJava8/pom.xml 一样直接依赖核心模块:

<dependency>
  <groupId>io.github.1609676823</groupId>
  <artifactId>YiKdWebClient</artifactId>
  <version>${project.version}</version>
</dependency>

根聚合 POM 的 <modules> 中需要同时包含 YiKdWebClient 和业务模块。适合修改客户端源码后与业务项目一起编译、测试。

3.3 Gradle 项目:使用本机 Maven 仓库

先按 3.1 节执行 Maven 本地安装,再在 Gradle 中声明:

Groovy DSL:

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation 'io.github.1609676823:YiKdWebClient:1.0.0-SNAPSHOT'
}

Kotlin DSL:

repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation("io.github.1609676823:YiKdWebClient:1.0.0-SNAPSHOT")
}

mavenLocal() 必须放在仓库列表中,否则 Gradle 找不到本机安装的 YiKdWebClient 制品。

3.4 非 Maven/Gradle:手工引用 JAR

先构建完整发行目录:

.\mvnw.cmd -B -ntp clean package

构建成功后,必须把 dist/lib/ 下的 全部 JAR 加入 classpath,不能只添加 YiKdWebClient.jar

dist/lib/
├─ YiKdWebClient.jar
├─ jackson-annotations-*.jar
├─ jackson-core-*.jar
└─ jackson-databind-*.jar

假设 Main.javalib/ 位于同一目录,Windows:

javac -encoding UTF-8 -cp "lib/*" Main.java
java -cp ".;lib/*" Main

Linux/macOS 的 classpath 分隔符为冒号:

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 中的默认版本,也可指定正式版本号:

build-release.bat 1.0.0

脚本会从 JAVA_HOMEPATH 和常见 JDK 安装目录中查找 JDK,运行完整测试和构建,并生成:

dist/
├─ ConsoleTestJava8.jar          # 完整可执行示例,已包含依赖
├─ ConsoleTestJava8Simple.jar    # 精简可执行示例,已包含依赖
├─ YiKdWebCfg/
├─ SampleFiles/
├─ docs/
├─ lib/
│  ├─ YiKdWebClient.jar          # 核心类库
│  └─ jackson-*.jar
└─ YiKdWebClient-Java-<版本号>.zip

java -jar YiKdWebClient.jar 无法运行是正常现象:核心 JAR 没有 Main-Class。要执行示例,请运行 ConsoleTestJava8.jarConsoleTestJava8Simple.jar

4. 配置 appsettings.xml

4.1 默认路径

核心库默认从进程工作目录读取:

YiKdWebCfg/appsettings.xml

相对路径以启动 Java 进程时的工作目录为准,不一定是 JAR 所在目录。先复制示例文件:

New-Item -ItemType Directory -Force .\YiKdWebCfg | Out-Null
Copy-Item .\YiKdWebClient\src\main\resources\YiKdWebCfg\appsettings.example.xml `
  .\YiKdWebCfg\appsettings.xml

使用 dist 发行目录时:

Copy-Item .\dist\YiKdWebCfg\appsettings.example.xml `
  .\dist\YiKdWebCfg\appsettings.xml

4.2 完整配置示例

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <appSettings>
    <!-- 请替换为真实数据中心 ID / 账套 ID -->
    <add key="X-KDApi-AcctID" value="YOUR_ACCOUNT_ID" />

    <!-- 请替换为真实集成用户 -->
    <add key="X-KDApi-UserName" value="Administrator" />

    <!-- 请替换为真实应用 ID -->
    <add key="X-KDApi-AppID" value="YOUR_APP_ID" />

    <!-- 请替换为真实应用密钥 -->
    <add key="X-KDApi-AppSec" value="123456" />

    <!-- 简体中文通常为 2052 -->
    <add key="X-KDApi-LCID" value="2052" />

    <!-- 多组织场景可填写组织编码 -->
    <add key="X-KDApi-OrgNum" value="" />

    <!-- 请替换为真实服务地址;私有云通常以 K3Cloud/ 结尾 -->
    <add key="X-KDApi-ServerUrl" value="http://127.0.0.1/K3Cloud/" />
  </appSettings>
</configuration>

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 自定义配置路径

必须在创建 YiK3CloudClientSSOHelper 之前设置路径,因为对象字段初始化时会读取配置:

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 动态传入授权信息

4.5 私有云与公有云网关

私有云通常配置产品地址并以 K3Cloud/ 结尾;部分公有云环境可能要求通过 https://api.kingdee.com/galaxyapi/ 网关并使用 API 请求头签名。实际地址与认证规则应以目标环境和金蝶官方当前要求为准。各语言客户端均保留普通登录与 API 请求头签名能力。

5. 五分钟运行第一个示例

  1. 安装 JDK 8 或更高版本,并确认:

    java -version
  2. 在仓库根目录构建发行包:

    .\mvnw.cmd -B -ntp clean package
  3. 复制并填写本地配置:

    Copy-Item .\dist\YiKdWebCfg\appsettings.example.xml `
      .\dist\YiKdWebCfg\appsettings.xml
  4. 查看全部示例:

    .\dist\run-console.cmd help
  5. 运行推荐的 SHA256 签名认证示例:

    .\dist\run-console.cmd sign-sha256

Linux/macOS 使用:

./dist/run-console.sh sign-sha256

也可以直接运行:

Set-Location .\dist
java -jar ConsoleTestJava8.jar sign-sha256

控制台会显示登录请求、登录响应、业务请求、业务响应和方法返回值。HTTP 请求完成不代表业务一定成功,仍需检查返回 JSON 中的 LoginResultTypeIsSuccessByAPIResponseStatus.IsSuccessErrorCodeMessage

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 分块上传

统一运行格式:

.\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_IDYIKD_USER_NAME 动态配置的数据中心与用户 从 XML 读取
YIKD_APP_IDYIKD_APP_SECRET 动态配置的应用 ID 与密钥 从 XML 读取
YIKD_LCIDYIKD_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_IDYIKD_UPLOAD_INTER_IDYIKD_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 种推荐方案。新项目通常从 LoginBySignSHA256LoginByAppSecret 或目标网关要求的 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

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);
        }
    }
}

运行仓库示例:

.\dist\run-console.cmd sign-sha256

SHA256 签名认证的 Java 请求与回环响应

7.3 签名信息认证(SHA1,兼容旧版本)

PT-146911 8.0.0.202205 之前的版本不支持 SHA256 时,可切换为 SHA1。其余配置和调用方式相同:

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 登出:

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 分别使用 GetSSOLogoutap0StrV3GetSSOLogoutap0StrV2V1。SSO URL 和签名参数属于敏感登录材料,不应写入公开日志。

.\dist\run-console.cmd sign-sha1

SHA1 签名认证的 Java 请求与回环响应

7.4 第三方系统登录授权

该方式读取数据中心 ID、集成用户、应用 ID、应用密钥和语系:

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,便于核对第三方登录请求体;复制后请先把配置文件中的认证占位值替换为目标环境的真实值。

.\dist\run-console.cmd app-secret

第三方系统登录授权的 Java 请求与回环响应

7.5 旧版用户名密码认证

该模式不依赖 appsettings.xml 中的应用 ID 和应用密钥,但需要服务地址、数据中心 ID、用户名、密码和语系。除兼容旧系统外,不建议新项目优先使用用户名密码方式。

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 对应用户的真实密码。

仓库自带的示例运行器为了避免把真实密码写入版本控制,仍使用环境变量:

$env:YIKD_VALIDATE_PASSWORD = '<目标环境中 demo 用户的真实密码>'
.\dist\run-console.cmd validate-login
Remove-Item Env:\YIKD_VALIDATE_PASSWORD

截图中的旧版登录使用本地 MOCK-* 回环配置,用户名为 demo;密码在展示前已替换为 ******,不会包含示例密码或真实测试密码。

旧版用户名密码认证的 Java 请求与回环响应,密码已脱敏

7.6 集成密钥认证(文件或 Base64)

.cnf 必须由目标金蝶环境生成,并与服务地址、数据中心匹配。文件方式:

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 文件:

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

.\dist\run-console.cmd simple-passport

集成密钥认证的 Java 请求与回环响应

7.7 API 请求头签名认证

该模式不会先调用登录接口,而是直接给每个业务请求生成签名请求头,因此能减少一次 Web 请求。生产使用前应确认目标金蝶版本仍支持对应算法和请求头。

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);
        }
    }
}
.\dist\run-console.cmd api-sign-headers

API 请求头签名认证的 Java 请求头与回环响应

7.8 动态传入授权信息

适用于配置来自数据库、配置中心,或同一服务连接多个账套的场景。为保证代码可直接复制,下面先把全部认证项定义为本地变量:

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);
        }
    }
}
.\dist\run-console.cmd dynamic-config

动态传入授权信息的 Java 请求与回环响应

7.9 自定义配置文件路径

必须先设置路径,再创建客户端:

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);
        }
    }
}
$env:YIKD_CONFIG_PATH = 'D:\configs\kingdee\appsettings.xml'
.\dist\run-console.cmd custom-config-path

自定义配置路径的 Java 请求与回环响应

7.10 已弃用的 ValidateUserEnDeCode

Caution

ValidateUserEnDeCode 已通过 @Deprecated 标记为弃用。它会对用户名和密码执行可逆的旧式 DES 兼容编码,并调用 Kingdee.BOS.WebApi.ServicesStub.AuthService.ValidateUserEnDeCode.common.kdsvc。金蝶官方通用 WebAPI 登录说明未推荐这种方式,项目仅为曾经出现过的旧版本附件等历史场景保留兼容实现。编码后的密码仍然必须按密码本身保护;新项目请优先使用 SHA256 签名认证或当前环境支持的其他认证方式。

该模式与普通 ValidateLogin 使用相同的 ValidateLoginSettingsModel,区别是把 LoginType 设置为 LoginType.ValidateUserEnDeCode。下面代码包含全部 importmain 入口、认证参数、业务调用、真实请求/响应读取和资源释放,可直接保存为 Main.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);
        }
    }
}

在仓库根目录中,可以直接编译并运行这份独立代码:

javac -encoding UTF-8 -cp ".\dist\ConsoleTestJava8.jar" .\Main.java
java -cp ".;.\dist\ConsoleTestJava8.jar" Main

仓库内置的真实环境验证命令如下。运行器从环境变量读取实际测试密码,不会把密码写入源码;运行结束后请删除当前 PowerShell 会话中的临时变量:

$env:YIKD_VALIDATE_PASSWORD = '<替换为目标环境的实际测试密码>'
.\dist\run-console.cmd validate-user-endecode
Remove-Item Env:\YIKD_VALIDATE_PASSWORD

123456 仅用于说明密码变量应填写在哪里,接入时必须替换成 userName 对应用户的真实密码。下图由 Java 运行器与临时回环 HTTP 服务真实执行后生成;明文密码及其可逆旧式编码值均已脱敏。

已弃用的 ValidateUserEnDeCode Java 实际运行截图,密码已脱敏

8. JSON 参数与接口功能列表

8.1 JSON 参数

传给客户端方法的 JSON 与金蝶官方接口要求的业务参数一致,客户端负责包装外层 HTTP 报文。例如查看用户:

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 查看单据或基础资料
SaveBatchSaveDraftGroupSaveFlexSave 保存、批量保存、暂存、分组保存、弹性域保存
SubmitAuditUnAuditDeleteGroupDelete 提交、审核、反审核、删除和分组删除
ExecuteOperationPushAllocateCancelAllocateCancelAssignDisassembly 通用操作、下推、分配、取消和拆单
ExecuteBillQueryGetSysReportDataQueryBusinessInfoQueryGroupInfo 单据查询、报表和业务信息查询
SendMsgSwitchOrgWorkflowAudit 消息、组织切换和工作流审批
AttachmentUpLoadAttachmentDownLoadUploadFile 原始附件/文件服务接口
CustomBusinessServiceCustomBusinessServiceByParameters 自定义 WebAPI
GetDataCenterList 获取数据中心列表

完整方法、重载和服务路径见 API 对照文档

8.3 自动登录、自动登出与会话复用

大部分业务方法都有以下重载:View(formId, json) 会自动登录和登出;View(formId, json, autoLogin) 可控制是否登录,调用后仍自动登出;View(formId, json, autoLogin, autoLogout) 可分别控制登录与登出。

连续调用多个接口时,可以复用同一 Cookie 会话:

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 请求:

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);
    }
}
.\dist\run-console.cmd sso-v4

SSO V4 的 Java 本地生成结果

10. 自定义 WebAPI

官方自定义 WebAPI 报文格式与参数说明:https://vip.kingdee.com/article/97030089581136896?specialId=448928749460099072&productLineId=1&isKnowledge=2&lang=zh-CN

目标金蝶环境必须先部署服务端自定义 WebAPI。Java 仓库只移植客户端;C# 主项目中的 GlobalServiceCustom.WebApi 是 .NET Framework 4.8 服务端示例,真正部署的是它生成的 GlobalServiceCustom.WebApi.dll,不是 Java 发行包或其编译引用。

客户端提供两类调用:CustomBusinessService 由客户端完成标准外层参数包装;CustomBusinessServiceByParameters 将调用者准备的 JSON 作为原始请求体发送。服务路径既可直接传字符串,也可通过 CustomServicesStubpath 由命名空间、类名和公开方法名生成;这些定位值必须与服务端部署内容完全一致。

Caution

不要把任意用户输入直接拼接到 SQL 或其他高权限服务参数中。服务端必须实施身份授权、参数校验、最小权限和审计。

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<String, Object> body = new LinkedHashMap<String, Object>();
        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);
        }
    }
}

命名空间、类名和公开方法名必须与服务器端部署内容完全一致。

.\dist\run-console.cmd custom-webapi

自定义 WebAPI 的 Java 请求与回环响应

11. 文件与 Base64 分块上传

官方附件上传报文结构与原理:https://vip.kingdee.com/article/296577252589190400?productLineId=1&isKnowledge=2&lang=zh-CN

附件上传会写入目标业务系统。接入前必须替换真实的表单 ID、单据内码和单据编号,并确认目标环境已配置附件或对象存储。高层封装支持文件路径、分块进度回调和 Base64 数据;每个成功分块返回的 FileId 会自动写回上传模型。

11.1 文件路径上传并获取进度

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);
        }
    }
}

不需要进度时,可省略最后一个回调参数。

.\dist\run-console.cmd upload-file
.\dist\run-console.cmd upload-progress

文件路径分块上传的 Java 请求与回环响应

带进度回调的 Java 分块上传

11.2 Base64 分块上传

已经持有 Base64 文件内容时使用:

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);
        }
    }
}
.\dist\run-console.cmd upload-base64

Base64 分块上传的 Java 请求与回环响应

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<K,V> Map<K,V> dict map[K]V
回调 Action<T> / Action<T1,T2> Consumer<T> / BiConsumer<T1,T2> 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 校验做兼容配置:

.\mvnw.cmd -B -ntp clean verify

14. 开发、测试与项目地址

执行全部编译、95 项单元测试和回环 HTTP 测试:

.\mvnw.cmd -B -ntp clean verify

自动化测试不需要真实金蝶地址或密钥。修改公开方法、参数顺序、认证报文或服务路径时,请同步更新测试和 docs/API_MAPPING.md。更多约定见 CONTRIBUTING.mdSECURITY.mdCHANGELOG.md

项目地址:

本项目采用 MIT License。你可以在保留版权和许可声明的前提下使用、复制、修改、合并、发布、分发、再许可和销售本软件。软件按“原样”提供,不附带任何明示或默示担保。

About

金蝶云星空 webapi集成的java实现 方便各种第三方系统对接,以及postman等工具调试 1.支持第三方授权登录; 2.支持旧版的用户名密码登录模式; 3.支持最新的API签名模式 4.集成文件模式 有使用方面的问题可以直接提issues

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages