拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

金蝶苍穹附件上传Java源码解析:从登录鉴权到业务挂接全链路

金蝶苍穹附件上传Java源码解析:从登录鉴权到业务挂接全链路

简介:这份资源面向需要与金蝶苍穹平台做系统集成的Java开发者,聚焦第三方系统向苍穹上传附件、引入业务数据的接口实现。压缩包共8个文件,全部为java源码,整体约13KB,涵盖登录鉴权、HTTP请求封装、文件上传服务及带附件的远程操作等核心类,另含自定义保存插件示例,便于直接参考改造。内容围绕接口调用、身份验证、文件编码压缩、异步处理、错误重试、数据安全与日志记录等关键环节展开,可帮助读者理解苍穹附件管理的完整链路。目前已有326人学习下载,适合具备一定Java与接口开发基础、希望快速打通苍穹文件上传流程的工程师,通过阅读源码即可掌握可复用的集成思路与排错方向。

1. 从一份 Java 源码包说起:第三方系统怎么把附件塞进苍穹

做过金蝶苍穹集成的人大概都遇到过这个场景:OA 走完审批,合同 PDF 要自动挂到苍穹的采购订单下面;或者 MES 产出的质检报告,得定时推到苍穹对应批次记录里。苍穹本身有附件管理,但第三方系统怎么调、用什么身份调、文件流怎么传,官方文档给的是接口定义,真正落地时一堆细节没人告诉你。这份「上传文件至金蝶苍穹平台.zip」就是干这个的——里面是一套可读可改的 Java 源码,围绕BizOperateService、HttpClientFactory、HttpService、AppLoginService、UserLoginService、FileUploadService、RemoteOperationWithAttachment以及一个BizCustomSaveWebApiPlugin插件类展开,覆盖了从登录鉴权、构造 HTTP 请求、上传附件到业务数据保存的完整链路。适合正在做苍穹与外部系统对接的后端,尤其是被附件上传卡过的人。

2. 拆开源码包:登录、请求、上传三条链路怎么串

2.1 先搞清楚苍穹附件接口的调用模型

苍穹的开放接口不是「一个 URL 传个文件」那么简单。它的附件上传通常分两步走:第一步先调文件上传接口,把二进制流推上去,拿到一个文件标识(fileId 或类似的东西);第二步在保存业务数据时,把这个标识挂到对应字段上。这个源码包里FileUploadService负责的就是第一步,RemoteOperationWithAttachment更像是把「上传 + 业务保存」串起来的编排类,而BizCustomSaveWebApiPlugin是苍穹侧的插件入口——也就是说,它可能同时包含了调用方和服务端扩展两部分的示例。

为什么要有AppLoginService和UserLoginService两个登录类?这是苍穹鉴权的常见做法:应用级登录拿的是应用身份,用户级登录拿的是具体用户上下文。第三方系统如果代表某个操作员上传附件,通常需要用户级令牌,否则附件的创建人、所属组织可能对不上,后续权限过滤会出问题。AppLoginService一般用于系统间无用户场景的数据同步,UserLoginService用于需要记录操作人的场景。选哪个,取决于你的附件要不要在苍穹界面上被人看到、被谁看到。

HttpClientFactory和HttpService是基础设施层。前者管连接池、超时、重试策略,后者封装 GET/POST、表单提交、文件 multipart 构造。很多团队自己写 HttpClient 也能跑,但连接不复用、超时不设、异常不分类,量一上来就是各种玄学超时。这个包里把工厂和门面分开,思路是对的——工厂管生命周期,Service 管业务语义。

2.2 环境准备与依赖确认

拿到 zip 之后先别急着跑。这类源码包通常不带完整工程文件,你需要自己建一个 Maven 或 Gradle 项目,把 Java 文件放进去,然后补依赖。常见做法是:

# 解压后先看目录结构,确认有没有 pom.xml 或 lib unzip 上传文件至金蝶苍穹平台.zip -d cangqiong-upload cd cangqiong-upload find . -name "*.java" | head -20 find . -name "pom.xml" -o -name "*.jar" | head

如果包里没有pom.xml,说明它是纯源码片段,需要你自己补。核心依赖一般包括 HTTP 客户端和 JSON 处理:

<!-- pom.xml 关键依赖,版本按你项目统一管理 --> <dependencies> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId> <version>4.5.14</version> </dependency> <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpmime</artifactId> <version>4.5.14</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.15.2</version> </dependency> </dependencies>

httpmime是必须的,因为 multipart 上传要用MultipartEntityBuilder。如果你用 OkHttp 或 Java 11 的HttpClient也能实现,但这个包里的HttpService大概率是基于 Apache HttpClient 写的,跟着它的风格走改起来最省事。JDK 版本建议 8 以上,苍穹服务端本身对 JDK 有要求,但调用方这边 8 或 11 都能跑。

2.3 登录鉴权:AppLoginService 与 UserLoginService 的取舍

先看登录。苍穹的登录接口一般需要传acctId(账套)、username、password或应用密钥,返回一个 token。这个包里两个 Service 的差异,从命名就能猜个大概:

// AppLoginService 典型调用形态(根据源码结构还原) public class AppLoginService { private String appId; private String appSecret; private String baseUrl; public String login() throws IOException { // 构造应用登录请求,换取 access_token Map<String, String> params = new HashMap<>(); params.put("appId", appId); params.put("appSecret", appSecret); params.put("acctId", "你的账套ID"); String response = HttpService.postForm(baseUrl + "/api/login/app", params); // 解析 token,注意过期时间 return JsonUtil.read(response, "data.access_token"); } }

UserLoginService则多传用户标识,返回的 token 绑定了用户上下文。参数上要特别注意acctId——苍穹是多账套的,传错了账套,接口可能返回成功但数据写到了另一个账套里,这种坑排查起来非常费劲。我一般会在配置里把baseUrl、acctId、appId做成外部配置,不硬编码。

提示:token 一般有有效期,生产环境不要每次上传都重新登录,做一个带过期时间的缓存。但缓存 key 要区分应用级和用户级,混用会导致权限错乱。

2.4 构造 multipart 请求:FileUploadService 的关键参数

FileUploadService是核心。它要做的事:拿本地文件路径或 InputStream,构造 multipart/form-data 请求,带上 token 和必要的表单字段,POST 到苍穹的文件上传接口。

// FileUploadService 上传逻辑示意 public String upload(String token, File file, String bizType) throws IOException { CloseableHttpClient client = HttpClientFactory.getClient(); HttpPost post = new HttpPost(baseUrl + "/api/file/upload"); post.setHeader("Authorization", "Bearer " + token); // 关键:multipart 构造,文件名和字段名要和苍穹接口约定一致 MultipartEntityBuilder builder = MultipartEntityBuilder.create(); builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); builder.addBinaryBody("file", file, ContentType.APPLICATION_OCTET_STREAM, file.getName()); builder.addTextBody("bizType", bizType); // 业务类型,影响存储路径 post.setEntity(builder.build()); try (CloseableHttpResponse resp = client.execute(post)) { String body = EntityUtils.toString(resp.getEntity(), "UTF-8"); // 解析返回的 fileId,后续挂附件要用 return JsonUtil.read(body, "data.fileId"); } }

几个参数必须和苍穹侧对齐:file字段名不能改,bizType决定文件存到哪个业务目录,Authorization头的格式要看登录接口返回的是不是带Bearer前缀。文件名建议保留原始扩展名,苍穹可能根据扩展名做预览处理。返回的fileId一定要存下来,这是后续业务保存时挂附件的唯一凭据。

2.5 把附件挂到业务数据:RemoteOperationWithAttachment 的编排

上传拿到fileId只是第一步。真正让附件出现在订单、合同、批次记录上,还得调业务保存接口,把fileId塞进附件字段。RemoteOperationWithAttachment这个类名暗示了它的职责:远程操作 + 附件。

// 业务保存时挂附件的示意 public void saveWithAttachment(String token, String billId, String fileId) { Map<String, Object> data = new HashMap<>(); data.put("billId", billId); // 附件字段通常是数组结构,支持多个附件 data.put("attachment", Collections.singletonList( Collections.singletonMap("fileId", fileId) )); String json = JsonUtil.write(data); HttpService.postJson(baseUrl + "/api/biz/save", token, json); }

BizCustomSaveWebApiPlugin是苍穹服务端的扩展点。如果你在苍穹侧要自定义保存逻辑——比如上传后自动解析 PDF 内容、或者根据附件类型触发不同流程——这个插件类就是入口。它和调用方代码放在一起,说明这份资源是「两端都有」的示例,不只是客户端。

3. 避坑排查:附件上传最常见的五类翻车

3.1 上传成功但界面上看不到附件

现象:接口返回fileId,日志显示 200,但苍穹单据的附件列表是空的。

原因:多数情况是只调了文件上传接口,没调业务保存接口,或者业务保存时附件字段名写错了。苍穹的附件是「文件 + 关联」两步,缺一步都不显示。

解决:确认RemoteOperationWithAttachment里保存逻辑有没有执行;抓包看业务保存请求体里attachment字段的结构,对照苍穹接口文档的字段名和层级。常见错误是传了fileId但外层少了数组包装。

3.2 登录返回 401 或 token 无效

现象:登录接口能返回 token,但调上传接口时报 401。

原因:token 类型用错了。应用级 token 调用户级接口,或者 token 过期没刷新,或者Authorization头格式不对(有的接口要Bearer,有的直接放 token)。

解决:先确认你调的上传接口需要哪种 token;在HttpService里加日志打印请求头;检查 token 缓存有没有把应用级和用户级混在一起。我一般会在AppLoginService和UserLoginService里各打一个 tag,日志里一眼能看出用的哪个。

3.3 大文件上传超时或内存溢出

现象:小文件正常,几十兆的 PDF 就超时,或者 JVM 报 OOM。

原因:FileUploadService如果用的是byte[]或ByteArrayInputStream把整个文件读进内存,大文件必炸。另外 HttpClient 默认超时可能只有几十秒。

解决:改用FileBody或InputStreamBody流式上传,不要自己读成 byte 数组。HttpClientFactory里把socketTimeout和connectTimeout调大,比如 5 分钟。如果苍穹侧支持分块上传,优先用分块。

3.4 文件名乱码或扩展名丢失

现象:上传后苍穹里文件名变成乱码,或者没有后缀打不开。

原因:multipart 构造时没有指定文件名编码,或者ContentType设成了text/plain导致苍穹不认扩展名。

解决:addBinaryBody的第四个参数传原始文件名,ContentType用APPLICATION_OCTET_STREAM。如果还有乱码,在MultipartEntityBuilder上设setCharset(StandardCharsets.UTF_8)。

3.5 并发上传时连接池耗尽

现象:单线程没问题,多线程批量上传时报ConnectionPoolTimeoutException或请求排队。

原因:HttpClientFactory返回的是单例 client 但连接池默认每路由只有 2 个连接,并发一高就不够用。

解决:在工厂里设置PoolingHttpClientConnectionManager的setMaxTotal和setDefaultMaxPerRoute,比如 50 和 20。同时确保响应实体被完全消费或关闭,否则连接不释放。

4. 进阶:把上传封装成可复用组件与验证清单

源码包给的是示例,真正上生产得做几件事。第一,把FileUploadService抽成接口,实现类里注入配置,方便换环境。第二,加一个上传结果校验方法,不只看 HTTP 200,还要看业务返回码和fileId是否非空。第三,做一个简单的验证清单,每次改完代码走一遍:

验证项检查方式通过标准
登录 token 有效调一个只读接口返回业务数据而非 401
文件上传返回 fileId打印响应体fileId 非空且格式符合预期
附件挂接成功苍穹界面查看单据附件能看到文件且能下载
大文件(>50M)传一个真实大文件不超时、不 OOM
并发 10 线程用 JMeter 或脚本并发无连接池异常,成功率 100%

这里提一下 JMeter,热搜里有人问 jmeter 上传文件,其实用 JMeter 压这个接口很合适:建一个线程组,加 HTTP 请求,勾选Use multipart/form-data,文件路径填本地文件,Header 里加Authorization。跑一轮就能看出并发下的问题。

最后说个我自己的习惯:每次对接新的苍穹环境,先不写业务代码,用curl或 Postman 把登录、上传、保存三个接口各调通一次,把请求体和响应体存成文件。然后再照着这个包里的类结构写 Java。这样出问题时,能快速判断是协议层还是代码层。从那以后我每次接苍穹附件需求,都强制先跑一遍这个三步验证,省了太多来回扯皮的时间。希望帮到你。

本文还有配套的精品资源,点击获取

返回列表