简介:面向泛微OA e-cology 8 最新 webservice 接口的说明文档,为需要做 OA 系统接口集成、数据同步或文档管理的开发者与实施顾问提供直接参考。该 docx 文档系统梳理了文档模块 WebService 的部署与调用细节,包括在 services.xml 中添加服务名称、命名空间、服务类与实现类配置,重启后通过 wsdl 地址验证是否发布成功;同时逐个说明 login、createDoc、updateDoc、deleteDoc、getDoc、getDocCount、getList 等接口的参数、返回值与业务含义,其中 login 还区分数据库验证、动态密码验证、LDAP 验证等登录方式。文档附有 DocInfo 对象字段说明,覆盖文档 ID、类型、标题、编号、主目录/分目录/子目录、部门、语言等属性,也包含创建、修改、批准、失效等操作记录字段,能帮助理解请求数据结构和返回对象。压缩包内仅含 1 个 docx 文件,体积 330KB,内容结构清晰,便于本地检索查阅。该资源已有 6785 人学习下载,适合熟悉泛微后端配置、但首次接触接口调用的人员作为速查手册,可快速定位部署、字段映射与调用参数问题。
1. 泛微OA e-cology 8 的 WebService 接口:把文档操作搬到外部系统的省力路径
做泛微OA二开时最常遇到的一个需求,就是让外部系统直接操作OA里的文档——比如把合同附件同步给文件服务器、把公告推送到门户首页、或者做历史数据迁移。这套 e-cology 8 的 WebService 接口文档给出的方案很直接:登录拿 Session,然后用 createDoc、updateDoc、deleteDoc、getDoc 这一组方法操作文档,附件以 Base64 编码塞进 SOAP 报文里传输。对负责系统集成的开发者和实施运维来说,这一套接口比模拟登录再点页面稳定得多,也更容易对接。真正让新手翻车的往往不是接口逻辑,而是 services.xml 部署、附件压缩标记这类细节,下面按落地顺序把每一步拆开。
2. 让 WebService 先跑起来:services.xml 部署与 WSDL 验证
2.1 部署前先确认 classbean 路径与 xfire 版本
泛微的 WebService 基于 xfire 框架实现,接口服务定义都写在services.xml里。这个文件的实际路径是 OA 安装目录下的classbean/META-INF/xfire/services.xml,不是随便放到哪个 classpath 都能被扫描到的。很多新手第一次部署时直接在整个磁盘里搜services.xml,搜出来好几个同名文件,改错一个就白忙活半天。正确做法是:先确认 OA 根目录,比如/opt/ecology或者D:\Ecology,然后顺着classbean/META-INF/xfire/往下找。
打开这个文件之前,建议先做两件事:第一,备份原文件,改动出错可以回滚;第二,确认 Tomcat 用的是哪个 JDK 版本,因为 xfire 对 JDK 版本比较敏感,JDK 8 和 JDK 11 下同一份配置的解析行为会有差异。文件本身是 XML 格式,多个<service>节点依次排列,每个节点对应一个对外暴露的 WebService。泛微很多其他模块也往这个文件里注册服务,所以改的时候只动自己要用的那几个节点,别把别的模块的配置删了。
<service> <name>DocService</name> <namespace>http://localhost/services/DocService</namespace> <serviceClass>weaver.docs.webservices.DocService</serviceClass> <implementationClass>weaver.docs.webservices.DocServiceImpl</implementationClass> <serviceFactory>org.codehaus.xfire.annotations.AnnotationServiceFactory</serviceFactory> </service>这段配置里最容易被忽略的是<namespace>。文档里写的是http://localhost/services/DocService,但如果按这个原样部署,生成的 WSDL 地址会指向 localhost,客户端在别的机器上解析时连不上服务器。我一般在部署环境里把 localhost 改成 OA 服务器的实际 IP 或域名,改完再重启服务。serviceClass和implementationClass对应的是接口类和实现类,这两个是泛微已经写好的类,类名拼错一个字母服务就起不来。
2.2 文档接口与流程接口的 services.xml 配置差异
文档接口只暴露了文档操作,而流程接口在 services.xml 里有两种配置方式:一种是没有权限验证的WorkflowServiceImpl,另一种是带权限验证的WorkflowServiceImplSec。文档接口本身没有提到需要额外加过滤器的版本,但流程接口那部分明确写了"上面这个接口没有权限验证,以及客户端访问控制,如果需要进行权限控制,则需要配置下面的代码,推荐都使用下面的接口"。
<service> <name>WorkflowService</name> <namespace>webservices.services.weaver.com.cn</namespace> <serviceClass>weaver.workflow.webservices.WorkflowService</serviceClass> <implementationClass>weaver.workflow.webservices.WorkflowServiceImplSec</implementationClass> <serviceFactory>org.codehaus.xfire.annotations.AnnotationServiceFactory</serviceFactory> </service>注意这里的 namespace 是webservices.services.weaver.com.cn,跟文档接口的http://localhost/services/DocService完全不同,客户端生成时用的包名也跟 namespace 有关。如果你同时接文档和流程接口,生成客户端代码时两个服务的包名可能会不一样,这个不奇怪,是 WSDL 的 namespace 决定的。
配置 Sec 版本后,还要在/Ecology/WEB-INF/web.xml里加一个过滤器,而且位置必须在XFireServlet配置之前:
<filter> <filter-name>intsecurity</filter-name> <filter-class>weaver.filter.IntefaceSecurityFilter</filter-class> </filter> <filter-mapping> <filter-name>intsecurity</filter-name> <url-pattern>/services/*</url-pattern> </filter-mapping>这个过滤器的作用是拦截所有/services/*的请求,然后根据/workflow/UserList.jsp页面里配置的 IP 白名单决定放行还是拒绝。如果过滤器配置在 XFireServlet 后面,请求会先被 xfire 处理掉,过滤器根本不起作用。这个顺序问题我在实际部署时踩过,后来每次改 web.xml 都会确认过滤器在前、Servlet 在后。
2.3 部署成功与否的验证方法:看 WSDL 而不是看页面
配置改完、Tomcat 重启之后,验证方式是在浏览器里访问http://OA地址/services/DocService?wsdl。如果能看到一堆 XML 标签,里面有<wsdl:definitions>、<wsdl:operation>这样的结构,说明部署成功了。如果访问返回 404,先检查 URL 拼写,特别是大小写和services之间的路径;如果返回 500,大概率是 services.xml 里的类名写错或者 XML 格式有问题,去 Tomcat 的 catalina.out 里搜xfire或者DocService关键词,能看到具体的异常栈。
这里有个容易误判的地方:浏览器里看到 XML 页面不一定代表服务真的可用。有些环境里服务器返回了 WSDL,但实际调用时因为类加载问题抛异常。我习惯在验证时顺手用curl调一次login方法,确认不是只看 WSDL,才算真正部署完成。
2.4 流程接口的权限控制:从 UserList.jsp 到 IP 白名单
Process 接口部署完成后,还要登录 OA 后台访问/workflow/UserList.jsp,在页面上添加允许调用 WebService 的 IP 地址。这个页面是泛微自己的权限列表管理页,跟操作系统防火墙没什么关系,它就是通过之前那个IntefaceSecurityFilter过滤器来生效的。配置好之后,只有白名单里的 IP 能调用流程接口,其他 IP 即使能访问 WSDL,实际调用时也会被拦。
生产环境我一般建议直接上 Sec 版本。文档里特意写了"推荐都使用下面的接口",言下之意就是不带权限验证的版本只适合开发调试。如果你在内网环境图省事用了不带 Sec 的版本,一旦有横向移动风险,攻击者拿到一个 OA 地址就能直接调流程接口创建审批流,后果比想像严重。我自己维护的测试环境用无权限版,生产环境一律 Sec 版加白名单。
3. 吃透方法与 DocInfo 对象:七个接口方法、三类对象的边界
3.1 接口方法总览与登录 Session 机制
文档接口一共七个方法,核心是登录方法和六个文档操作。登录方法login接收四个参数:登录名、密码、登录方式和客户端 IP。登录方式 0 是数据库验证,1 是动态密码验证,2 是 LDAP 验证。返回值是一个 Session 字符串,后续所有文档操作都要把这个 session 传进去。
| 方法名 | 参数 | 返回值 | 功能 |
|---|---|---|---|
| login | loginid, password, logintype, ipaddress | String 类型 Session 码 | 登录验证,获取会话 |
| createDoc | DocInfo, sessioncode | int,1 成功 0 失败 | 根据对象创建文档 |
| updateDoc | DocInfo, sessioncode | int,1 成功 0 失败 | 根据对象修改文档 |
| deleteDoc | id, sessioncode | int,1 成功 0 失败 | 根据 ID 删除文档 |
| getDoc | id, sessioncode | DocInfo | 按 ID 取完整文档对象 |
| getDocCount | sessioncode | int | 当前用户有权限的文档数 |
| getList | sessioncode, page, pagesize | DocInfo[] | 分页取文档对象数组 |
注意getList的参数,接口概览表格里只写了 sessioncode,但客户端示例代码里实际是service.getList(session, page, pagesize),说明列表方法本身支持分页。这个参数在 WSDL 里是定义了的,如果你用生成的客户端代码,IDE 里自动提示会给你展示重载方法签名。
Session 的含义要搞清楚:它不是 OA 系统里的用户会话 Token,而是本次 WebService 调用专门生成的 Session 码。每次login成功都会返回一个新的 Session 值,服务端会把它跟登录用户绑定。Session 要不要缓存复用?我的做法是:短流程调用里每次登录就行,性能影响不大;如果做批量数据同步,就缓存 Session,定期重新登录,减少认证次数。
3.2 DocInfo 关键字段:目录、状态、附件与常用组合
DocInfo 是这个接口的核心数据对象,字段非常多,但实际开发中高频用到的就那么几组。
第一组是文档身份:id、docSubject(标题)、docCode(文档编号)、docEdition(版本号)。创建文档时 id 传 0,让系统自增;更新文档时 id 必须传原文档的 id。
第二组是目录结构:maincategory、subcategory、seccategory分别对应主目录、分目录、子目录的 ID,对应的maincategoryStr、subcategoryStr、seccategoryStr是目录名称。创建文档时这三个 ID 必须有效,否则返回失败。
第三组是状态与创建人:docStatus是文档状态,示例代码里创建时传 1;doccreaterid是创建人 ID,文档里示例传的是 111。这个字段要特别留意,有些泛微版本里如果创建人 ID 不对,文档虽然创建成功,但权限归属会出问题。还有ownerid和ownertype,表示文档所有者和所有者类型,创建时建议一并设置。
第四组是内容与附件:docType为 1 时是 HTML 文档,内容存在doccontent字段里;docType为 2 时是 Office 文档,内容对应imagefileId和versionId。附件存在attachments数组里,类型是 DocAttachment[],每个附件对象里有文件名称、Base64 内容、服务器路径、压缩标记等。
3.3 getList 与 getDoc 的边界:列表不携带附件
getList 返回的是 DocInfo 数组,但说得很清楚:无文档内容及附件。也就是说你拿列表接口做展示没问题,但想通过列表直接拿附件内容是拿不到的,必须再调一次 getDoc 按 id 取详情。这个设计目的是控制列表接口的网络包大小——一个文档可能挂好几个大附件,如果列表接口把附件的 Base64 内容全带回来,分页接口瞬间就会撑爆。
getDoc 返回单个 DocInfo,带完整内容和附件。附件内容在DocAttachment.filecontent字段里,是 Base64 编码后的字符串,拿过来之后要用 Base64 解码才能得到原始字节流。至于 getDocCount,主要是给分页逻辑算总页数用的,返回的是当前登录用户有权限看到的文档数,不是数据库里所有文档数。这里隐含了一个权限边界:整个接口的可见范围都跟在 Session 关联的用户有关,没有权限的文档不会出现在列表里。
3.4 DocCustomField 与关联类型:自定义字段的取值方式
DocInfo 里还有一个doccustomfields数组,类型是 DocCustomField。这个对象有六个属性:fieldid自定义字段 ID、fieldhtmltype显示类型、fielddbtype存储类型、fieldtype字段类型、fieldshow显示名称、fieldvalue字段值。如果你在 OA 文档模块里配置了自定义字段,通过 getDoc 拿到的 DocInfo 里就能看到这些键值对。
创建和更新文档时,自定义字段的处理方式是在 DocInfo 里 new 一个 DocCustomField 数组,逐个 set 好 fieldid 和 fieldvalue 再塞进去。需要注意字段 ID 必须是后台自定义字段配置里的真实 ID,拿不准的话先在 OA 后台文档设置里查一下。另外 DocInfo 还带了一批关联类型字段:hrmresid关联人力资源、assetid关联资产、crmid关联 CRM、itemid关联项目等。这些字段在你做跨模块数据打通时很有用,比如一个文档既挂在项目下又关联了客户,就同时填 projectid 和 crmid。
4. 用 Java 客户端调通文档接口:从生成客户端代码到完整的增删改查
4.1 生成客户端代码的两种方式与工程依赖
泛微这套接口走的是 xfire 加 Axis 的老路线,客户端代码推荐用 Axis 的 WSDL2Java 工具生成。方式一:从 WSDL 文件直接生成。
java -cp axis.jar:commons-discovery.jar:commons-logging.jar:wsdl4j.jar \ org.apache.axis.wsdl.WSDL2Java \ http://192.168.7.200:8080/services/DocService?wsdl生成完成后,你会得到一个以 namespace 为包名的目录。如果 services.xml 里的 namespace 是http://localhost/services/DocService,生成出来的包名就是localhost.services.DocService,里面有DocServiceLocator和DocServicePortType两个关键类,示例代码里 import 的就是这两个。如果 namespace 改成了实际 IP,包名会变成com.xxx.services.DocService之类,引用时留意一下。
运行环境需要的依赖 jar 至少要包含 axis.jar、wsdl4j.jar、commons-discovery-0.2.jar、commons-logging.jar。泛微服务端的 xfire 版本比较老,客户端 jar 版本不要用太新的,否则序列化兼容性会出现诡异问题。我一般直接用 OA 服务器上 classbean 依赖的同版本 jar,或者从泛微的 lib 目录里拷一份。
4.2 登录鉴权与 Session 管理
客户端生成之后,第一步先登录。这是所有操作的前置条件。
private static String getSession(String loginid, String password, int logintype, String ip) throws MalformedURLException, ServiceException, RemoteException { DocServicePortType service = new DocServiceLocator() .getDocServiceHttpPort(new URL(serviceurl)); String session = service.login(loginid, password, logintype, ip); return session; }这里 serviceurl 指向服务的 WSDL 地址,比如http://192.168.7.200:8080/services/DocService。loginid是 OA 里真实存在的用户登录名,不是显示名。logintype按实际认证方式传。ip参数服务端会记录客户端的 IP 来源,如果你们 OA 的登录审计有要求,这里要传真实客户端 IP,不要写死 127.0.0.1。login返回的 session 字符串建议放在一个全局变量里,后续所有方法调用都要带它,丢失后必须重新登录。
4.3 查询文档列表与按 ID 拉取文档详情
列表和详情是日常用最多的两个操作。列表用getList,分页参数控制往返数据量。
public static void getDocList(String session, int page, int pagesize) throws RemoteException { DocInfo[] docs = service.getList(session, page, pagesize); for (int i = 0; i < docs.length; i++) { System.out.println(docs[i].getId() + "|" + docs[i].getDocSubject() + "|" + docs[i].getMaincategory() + "|" + docs[i].getMaincategoryStr() + "|" + docs[i].getDoccreaterid() + "|" + docs[i].getDoccreatername() + "|" + docs[i].getDoccreatedate() + "|" + docs[i].getDoccreatetime()); } }注意getDocCreaterType这类字段,列表接口里也会带出来,但如果你没调用 getter,它不会自动序列化到日志里。列表拿到的 DocInfo 对象里attachments字段是空的,属于正常现象。真正要拿附件内容,用 getDoc:
public static void getDocInfo(String session, int docid) throws RemoteException { DocInfo doc = service.getDoc(docid, session); DocAttachment[] atts = doc.getAttachments(); if (atts != null && atts.length > 0) { DocAttachment da = atts[0]; byte[] content = Base64.decode(da.getFilecontent()); System.out.println("文件名:" + da.getFilename()); System.out.println("大小:" + content.length); } }这里的 Base64 是org.apache.axis.encoding.Base64,不是 java.util.Base64。用错类会导致解码结果不对,这是一个很隐蔽的坑,后面避坑章节会展开。
4.4 创建带附件的文档:Base64 编码与参数设置
创建文档是最复杂的场景,因为它同时涉及 DocInfo 和 DocAttachment 两个对象的组装。直接看代码:
public static void createNewDoc(String session) throws RemoteException { // 读取本地文件,转成字节数组 byte[] content = new byte[102400]; try { int byteread; byte data[] = new byte[1024]; InputStream input = new FileInputStream(new File("d:\\service test.doc")); ByteArrayOutputStream out = new ByteArrayOutputStream(); while ((byteread = input.read(data)) != -1) { out.write(data, 0, byteread); out.flush(); } content = out.toByteArray(); input.close(); out.close(); } catch (Exception e) { e.printStackTrace(); } // 组装附件对象 DocAttachment da = new DocAttachment(); da.setDocid(0); da.setImagefileid(0); da.setFilecontent(Base64.encode(content)); da.setFilerealpath("d:\\service test.doc"); da.setIszip(1); da.setFilename("service test.doc"); da.setIsextfile("1"); da.setDocfiletype("3"); // 组装文档对象 DocInfo doc = new DocInfo(); doc.setDoccreaterid(111); doc.setAccessorycount(1); doc.setMaincategory(38); // 主目录 ID doc.setSubcategory(53); // 分目录 ID doc.setSeccategory(204); // 子目录 ID doc.setOwnerid(111); doc.setDocStatus(1); doc.setId(0); doc.setDocType(2); doc.setDocSubject("service html 文档"); doc.setDoccontent("service html 文档 content 22222"); doc.setAttachments(new DocAttachment[]{da}); int newId = service.createDoc(doc, session); System.out.println("新文档 id:" + newId); }几个关键点:doc.setAccessorycount(1)要跟实际附件数量一致,否则服务端校验可能失败。isextfile为"1"表示这是文档内容附件;docfiletype的取值含义建议先查一下你所在版本的文档附件类型对应表,示例里给的是 3。iszip为 1 表示内容做过 zip 压缩,具体是不是真的压缩了要看服务端的实现,客户端解码后要根据这个值决定要不要解压。创建成功返回新文档的 ID,失败返回 0。
4.5 更新与删除:先取后改的注意事项
更新文档最稳的模式是三步走:先 getDoc 拿到原始对象,改需要改的字段,再 updateDoc 传回去。
public static void updateDocInfo(String session, int docid) throws RemoteException { DocInfo doc = service.getDoc(docid, session); doc.setDocSubject("新的标题"); doc.setDoccontent("新的正文内容"); // 如果不需要改动附件,直接传回原对象即可 int result = service.updateDoc(doc, session); System.out.println("更新结果:" + result); }注意这里有一个很容易踩的坑:如果 getDoc 拿到的 DocInfo 里没有附件,而你直接 set 新的 attachments 数组覆盖上去,旧附件可能被替换掉;如果只是想改标题和正文,就别动 attachments 字段。示例代码里的更新逻辑是先取回附件对象,改掉 filecontent 再塞回去,他是为了让附件内容也一并更新。还有doc.setAccessorycount要跟附件数组长度对应,否则更新后附件数量不一致。
删除操作最简单:
public static void deleteDoc(String session, int docid) throws RemoteException { int result = service.deleteDoc(docid, session); System.out.println("删除结果:" + result); }返回 1 表示成功,0 表示失败。文档不存在时接口可能会抛 RemoteException,建议包一层 try-catch,把"删除失败"和"文档不存在"区分开。
5. 避坑清单:部署与调用中的五个高频问题
5.1 部署后访问 WSDL 404 或白屏
现象:按文档配置完 services.xml,重启 Tomcat,浏览器访问http://OA地址/services/DocService?wsdl返回 404 或者一片空白。
原因:最常见的是 services.xml 没放到正确的 classbean 路径下,Tomcat 根本没加载到;其次是改了配置文件但没重启服务,xfire 不会热加载 services.xml;还有一种是 service name 和 URL 里的名称不一致,比如配置里写的是 DocService,URL 写成了 WorkflowService。
解决:确认文件在classbean/META-INF/xfire/services.xml,全文搜索一下DocService节点是否真的存在;重启整个 Tomcat 服务而不是 reload 应用;检查 URL 拼写,注意大小写。改完配置后我习惯先tail -fTomcat 的 catalina.out,再访问一次 WSDL,能直接在日志里看到 xfire 的加载记录。
5.2 客户端运行报 ClassCastException 或 NoClassDefFoundError
现象:生成的客户端代码编译没问题,运行时一调 login 或者 getDoc 就抛 ClassCastException,或者提示找不到某个类。
原因:客户端用的 axis/xfire jar 版本跟服务端不一致,导致 SOAP 序列化生成的代理类类型对不上。常见做法是直接从 OA 服务器的 lib 目录里把 xfire、axis 相关 jar 拷到客户端工程里,而不是自己从 Maven 仓库拉新版。
解决:统一 axis 版本到 1.4 或服务端使用的版本;检查DocServiceLocator生成的 stub 类是否引用了weaver.docs.webservices.DocInfo,如果这个类不在客户端 classpath 里,需要把泛微提供的接口 jar 也加进来。我一般会把服务端classbean目录下和webservices相关的 class 文件打成 jar 放进客户端 lib。
5.3 附件下载后打不开或中文乱码
现象:getDoc 拿到附件,Base64 解码后写成本地文件,打开提示文件损坏;或者文件名显示乱码。
原因:两处。第一,Axis 的 Base64.encode 输出可能带换行符,解码时没处理干净;第二,da.getIszip()返回 1 时,内容实际是 zip 压缩过的字节流,直接当成原文件写盘必然打不开。
解决:解码前把字符串里的\r\n去掉;判断 iszip 后决定要不要走 ZipInputStream 解压一步再写文件。文件名乱码一般是 WSDL 传输时编码造成的,可以用new String(filename.getBytes("ISO-8859-1"), "UTF-8")修正。
5.4 getDoc 返回的 attachments 为 null 或为空数组
现象:调用doc.getAttachments()时抛 NullPointerException,或者拿到的数组 length 为 0。
原因:文档本身确实没有附件,这是正常的;还有一种情况是当前登录用户对该目录只有浏览权限,附件字段被服务端过滤置空。另外,Office 文档的正文内容存在 imagefileId 里,不在 doccontent 字段,如果附件数组为空但你想取正文,要先确认文档类型。
解决:访问 attachments 前先判空判长度;确认文档在 OA 后台里确实挂了附件;检查登录账号的文档权限范围,最好用有该目录完整权限的管理员账号调试。
5.5 直接使用无鉴权版本的 WorkflowService 导致服务暴露
现象:测试环境部署了 WorkflowService 无权限版本,后来被扫到并反复调用创建流程。
原因:文档里明确写了"上面这个接口没有权限验证,以及客户端访问控制",但很多人嫌 Sec 版本配置麻烦,直接上了无权限版。
解决:生产环境强制改用WorkflowServiceImplSec,在 web.xml 里加IntefaceSecurityFilter过滤器,并且在/workflow/UserList.jsp里把允许调用服务的 IP 白名单收窄,只留运维和业务系统的固定 IP。
6. 进阶:把附件读写封装成工具类,处理 Base64 与 iszip 判断
6.1 封装下载附件的方法
getDoc 拿到的 DocAttachment 里有 filecontent 和 iszip 两个字段,每次手动处理很啰嗦。我把它封装成一个独立方法,统一处理 Base64 解码和压缩判断。
public static void saveAttachmentToFile(DocAttachment da, String savePath) throws Exception { // 1. 先判空 if (da == null || da.getFilecontent() == null || da.getFilecontent().isEmpty()) { throw new IllegalArgumentException("附件内容为空"); } // 2. 清理换行符,解码 Base64 String content = da.getFilecontent().replaceAll("\\r\\n", "").replaceAll("\n", ""); byte[] bytes = Base64.decode(content); // 3. 判断是否压缩 InputStream input = null; if (da.getIszip() == 1) { ZipInputStream zin = new ZipInputStream(new ByteArrayInputStream(bytes)); zin.getNextEntry(); input = new BufferedInputStream(zin); } else { input = new ByteArrayInputStream(bytes); } // 4. 写文件 File file = new File(savePath + File.separator + da.getFilename()); OutputStream out = new FileOutputStream(file); byte[] buf = new byte[1024]; int len; while ((len = input.read(buf)) != -1) { out.write(buf, 0, len); out.flush(); } input.close(); out.close(); }这里最关键的一步是第 3 步的 iszip 分支。如果不判断压缩标记,哪怕是 iszip=1 的附件也会被当成原始文件写盘,文件自然打不开。判断了之后,如果是 zip 流就走 ZipInputStream,先进入第一个条目再读取,这样才能拿到真正的文件内容。
6.2 上传附件时的参数建议
创建带附件的文档时,我一般建议把iszip直接设 0,客户端自己不做压缩,让服务端去处理。因为你不知道服务端反序列化后会不会主动压缩,设 0 是标准姿势,设 1 反而要确保你传的字节流确实是 zip 格式。filerealpath这个字段在无状态调用里没什么用,服务端不会真的按照这个路径去服务器上取文件,文件内容还是以filecontent的 Base64 为准。isextfile和docfiletype按文档模块的实际类型填,不确定的时候先建一个文档看看 OA 里默认值是什么。
6.3 我的调用习惯
从那以后我每次调附件接口,都会在日志里把iszip的值和filecontent前 64 个字符打印出来,先确认内容是不是 Base64、文件头对不对再写盘。批量同步文档之前,先拿一条测试数据完整跑一遍创建、查询、更新、删除四个操作,确认 Session 和附件链路都没问题再上量。这套接口本身不复杂,复杂的是它跟 xfire、Axis 这些老框架绑定得比较紧,部署和调用细节上稍不留神就会翻车。希望帮到你。
本文还有配套的精品资源,点击获取