ARTICLE DETAIL

资讯详情

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

Webservice解锁U8二次开发:外部系统对接的最佳实践

Webservice解锁U8二次开发:外部系统对接的最佳实践 简介面向用友U8二次开发人员及需要跨系统集成的企业项目这套方案通过Webservice方式封装U8 API调用解决外部应用无法直接使用U8接口的问题无需在客户端预装用友U8即可完成单据生成、审核等核心业务特别适合采用非.NET技术栈的团队进行集成。资源包22.64MB共1069个文件以107个C#源码文件、189个DLL库为主包含调用U8 API所需的框架程序集、前端CSS/JS/PNG界面资源及Visual Studio工程与NuGet依赖构成一套完整的MVC Web应用。目前已有5248人学习浏览。包内由asmx服务入口串联登录认证与业务逻辑并附有U8APIFramework、MomService等核心程序集引用开发人员可对照工程源码快速定位“登录—建单—审核”的完整调用链同时大量源码文件与目录缓存为迁移到其他语言或扩展自定义接口提供了直接参考。1. 用 Webservice 解开 U8 二次开发的锁外部系统对接的第一站用友 U8 的二次开发接口十个人里有八个第一反应是写 DLL 插件或者直连数据库但真正到你要把电商中台的销售订单、MES 的完工汇报、WMS 的出入库单推给 U8 的时候最省心的一条路其实是 Webservice。它不是把 U8 的 COM 组件直接暴露给外部而是由你包一层 HTTPSOAP 的服务壳外部系统只认接口参数和返回报文登录账套、构造单据、提交审核的细节全部锁在服务端。适合谁适合已经有 U8 运行环境、被外部系统反复要求“给我一个接口”的实施或开发同学也适合不想把数据库账号交出去、又希望 U8 的校验规则生效的项目。这篇文章把选型、搭建、发布、调用和踩坑一次讲透。2. 选型先于写码为何是 Webservice 而不是插件、EAI 或 SQL 直连动手之前先把选项摆齐否则写到一半你就会发现 SQL 直连的临时方案已经把自己绕进去。我见过不少项目一开始图省事直接让外部系统对着 U8 的视图和业务表 insert结果上线三个月后单据审核不过、成本算不对又回来做接口。所以这里先花一章把四条路的边界说清楚。2.1 四条路的取舍插件、EAI、SQL 直连与 Webservice先把常见的四条路径放在一起对比后面选型就省得来回纠结。方案典型用途维护成本主要踩坑点U8 插件 / UAPU8 界面内的按钮、表单事件中活在 U8 客户端进程里外部系统根本够不到EAI / XML 导入大批量单据导入中高报文格式和错误码难排查且规则偏死板SQL 直连查询统计、报表低绕过界面校验脏数据高发Webservice 壳跨语言外部系统对接中需要 IIS 部署、接口权限、日志和幂等设计先说结论只要目标是“外部系统发一张单据进来”U8 插件这条路直接排除。原因是插件活在 U8 客户端进程里外部程序无法跨进程调用它。UAP 适合在 U8 内部加按钮、加字段不适合做服务化接口这是很多团队一开始没想清楚的。EAI 是 U8 官方提供的集成方案能批量导入单据但它的报文规则是一套单独的 XML 规范字段名、枚举值、凭证模板都和界面规则绑在一起。外部团队拿到这堆 XML 以后只能照着文档配配错了报错信息又常常含糊所以排查成本很高。SQL 直连最便宜也最危险你可以 insert 一张销售订单但单据上的默认仓库、税率、批号规则全在界面逻辑里绕过去以后后面审核、记账、成本计算全部可能出问题。这就是实施圈子里最常见的翻车路。Webservice 壳子的本质是把 U8 的业务组件留在服务器上由 ASP.NET 进程代为登录和调用外部系统只看到一个 SOAP 方法。业务规则还能留在 U8 侧权限、单据号规则、默认值都由 U8 组件去处理。2.2 U8 API 的登录上下文为什么外部请求必须“有人登录过”U8 的单据新增不是简单 insert它的公共组件在新增前要拿到账套号、操作员、登录密码、业务日期这些参数拼在一起形成上下文。你可以把它理解成黑匣子上下文对了组件才会去读界面规则上下文错了返回的错误往往是“权限不足”“用户被占用”这类让人摸不着头脑的话。我第一次封装 U8 接口时就是偷懒想绕掉登录环节直接调组件结果报错信息完全对不上问题卡了两天才意识到是登录对象没有创建成功。常见做法是在 WebService 里为每个方法准备一个统一入口先登录、再操作、最后登出绝不把登录对象设成静态缓存共享给所有请求。原因有两个一是 U8 操作员账号同时在线数量有限某个请求异常退出会把其他请求也锁死二是登录对象里带着账套上下文多用户并发时一旦串了就会出现 A 系统提交的单据记到 B 系统账套下的诡异问题。尤其是老版本 U8并发处理能力本来就弱WebService 接口一旦被外部系统频繁重试很容易把 U8 的服务端组件打满。所以在架构上WebService 这层壳的主要职责就是隔离并发、统一认证、替外部系统消化掉那些和业务无关的 U8 细节。这一层设计清楚了后面的实现才不容易翻车。3. 最小可用接口登录 U8 账套并把新增单据包成 SOAP 方法下面这套最小可用接口我按“新增销售订单”来拆。其他单据类型可以照抄这个结构把字段映射、单据类型编码换掉就行。先不要急着把几十个接口一次性做完先跑通一个方法确认登录、单据构造、返回结果整条链路没问题再批量复制。3.1 工程骨架与三步引用整个服务建在 U8 服务器上或者建在能访问 U8 应用服务器和账套数据库的机器上。工程结构不复杂但有三步引用错了后面全是坑。第一步在 Visual Studio 里新建 ASP.NET Web 应用程序.NET Framework 选 4.x不要选 .NET Core。U8 的业务组件大多是 32 位 COM/.NET 程序集.NET Core 承载老 COM 组件麻烦得多没必要给自己加戏。第二步添加一个 Web 服务文件。新版 Visual Studio 的模板列表里可能找不到 ASMX常见做法是先建 ASP.NET Web Forms 项目然后手动添加一个类文件继承WebService并打上[WebService]特性。第三步添加引用。项目引用里至少要有两个System.Web.Services以及 U8 安装目录下可用的 U8API 类型库或你本地封装好的 U8 业务组件程序集。引用对话框里点“浏览”定位到 U8 安装目录选对应的 dll 或 exe。最后把项目的“目标平台”改成 x86因为 U8 的 API 组件很多是 32 位注册的混到 64 位进程里会直接报“没有注册类”。IIS 站点路径不需要放在 U8 程序目录里但应用池的运行身份要有权限访问 U8 安装目录和账套数据库所在的服务器。这个地方常被忽略发布后接口能打开一登录就报数据库连接失败多半是运行身份权限不够。3.2 登录与新增单据的 C# 骨架下面这段代码是完整接口的最小骨架我按“登录账套 新增销售订单”来写。注意VoucherAPICo这个类名在不同 U8 版本里可能有前缀差异有的环境是U8API.VoucherAPICo有的环境是注册的 COM 组件直接引用。整体调用模式是一致的真机上如果方法名对不上以你本机 U8 安装目录下的接口说明为准。using System; using System.Data; using System.Web.Services; using System.Xml.Linq; using U8API; // 引用 U8 安装目录下的 U8API 类型库命名空间按实际环境调整 [WebService(Namespace http://internal.erp/u8soap)] [WebServiceBinding(ConformsTo WsiProfiles.BasicProfile1_1)] public class U8ApiService : WebService { [WebMethod(Description 外部系统新增销售订单)] public string AddSaleOrder(string account, string userId, string password, string dataXml) { if (string.IsNullOrWhiteSpace(dataXml)) return E1001:dataXml 不能为空; try { // 1. 登录账套失败时组件会抛出带错误码的异常 VoucherAPICo api new VoucherAPICo(); api.Login(account, userId, password, U8); // 2. 把外部 XML 转成 U8 组件认识的单据对象 object voucher BuildVoucher(dataXml); // 3. 新增单据返回 0 表示成功 int ret api.Add(voucher); return ret 0 ? OK : E ret; } catch (Exception ex) { return E9999: ex.Message; } } private object BuildVoucher(string dataXml) { XDocument doc XDocument.Parse(dataXml); var header doc.Root.Element(Header); // 常见做法是先把 XML 映射成 DataTable // 字段名需要按 U8 本版本的公共单据模版调整 DataTable table new DataTable(Voucher); table.Columns.Add(cCode, typeof(string)); table.Columns.Add(dDate, typeof(DateTime)); table.Columns.Add(cCusCode, typeof(string)); table.Columns.Add(cDepCode, typeof(string)); DataRow row table.NewRow(); row[cCode] (string)header.Element(Code); row[dDate] DateTime.Parse((string)header.Element(Date)); row[cCusCode] (string)header.Element(CusCode); row[cDepCode] (string)header.Element(DepCode); table.Rows.Add(row); return table; } }这段代码的逻辑分三层先创建VoucherAPICo组件并登录这一步把账套、操作员、密码交给 U8 组件它内部会完成权限校验和上下文初始化然后BuildVoucher把外部传来的 XML 转成 U8 组件能识别的单据对象最后调用Add提交。为什么要用 DataTable 做中间结构因为 U8 的公共组件对单据头的处理往往是以表格形式接收的一行对应的就是一张单据头。字段名cCode、dDate、cCusCode是从 U8 公共单据表结构里来的实际部署时你最好先用 U8 自带的“公共单据”模板导出一份样例照着样例字段名来映射别凭记忆写。参数说明account是 U8 账套号通常是 001、002 这种三位数字不是数据库库名userId和password是 U8 操作员账号和密码不是数据库 sa。dataXml是外部系统传进来的单据 XML里面至少要包含单号、业务日期、客户编码、部门编码其余字段能用默认值就不要让外部传。返回OK代表新增成功返回以E开头的字符串代表失败后面跟错误码。外部系统看到E开头的返回就不要把它当成功处理。3.3 字段映射别把 U8 的字段规则搬到接口外面外部传dataXml时不要把 U8 的字段规则全部暴露出去。接口层只认外部系统的概念比如单号、日期、客户编码、部门编码剩下的税率、默认仓库、批号规则全部在服务端补齐。这么做的原因很现实外部系统对接方可能根本不理解 U8 的销售类型、仓库和税率逻辑你让他在报文里传这些他只能胡乱填。我一般会在服务端维护一个默认值配置表把常用账套的单据类型、仓库、税率都配好BuildVoucher里没传的字段就取默认值。同时单据号唯一性校验要放在接口入口做不要依赖 U8 组件自己去查重。原因很简单U8 组件查重时返回的错误码在 SOAP 响应里绕一圈外部系统根本看不懂。你不知道外部网络会不会超时重试也不知道对方会不会用同一个单号提交两次所以这个校验必须在自己的接口里先堵住。这件事放到第 6 章展开那是接口上线前最重要的一道防线。4. 发布与调用IIS 部署、WSDL 抓取和 Java/Python 客户端接入代码写完不发布接口还是纸上的接口。这一章把服务装进 IIS、用 curl 做冒烟测试再分别用 Python 和 Java 的客户端调一遍。整个过程我建议按顺序做先确保浏览器能打开 .asmx 页面再测 SOAP 报文最后才让外部系统写代码。4.1 IIS 发布三件事发布到 IIS 这一步新手容易反复折腾的地方有三个。第一应用池。给这个站点单独建一个应用池.NET CLR 版本选 V4.0托管管道模式建议先用经典模式。ASMX 在集成管道下也正常但 U8 组件很多是老 COM我在集成模式下遇到过身份加载异常切到经典模式就好了。第二32 位应用。站点对应的应用池右键高级设置“启用 32 位应用程序”设为 True。这一步不做调用 U8API 时经常报 80040154。第三运行身份。不要图省事给 LocalSystem常见做法是给一个能访问 U8 安装目录和账套库的 Windows 账号。身份给错了接口能打开但 U8 组件登录时连不上数据库报错还很晚。端口选择上尽量别用 80 端口。很多企业服务器上已经跑了别的 Web 站点或者 U8 自身占用了一些端口直接用 8088 这类高位端口更稳妥。防火墙里放行这个端口只允许内网访问不要把接口映射到公网。接口的入参是 U8 账号密码直接对公网开放等于把 U8 的登录口暴露出去风险太大。发布完成后浏览器访问http://服务器IP:8088/U8ApiService.asmx能看到方法列表和调用测试页说明站点已经通了。然后再打开http://服务器IP:8088/U8ApiService.asmx?wsdl这个地址是后面所有客户端生成代码的入口。4.2 用 curl 做冒烟测试别急着写客户端外部系统还没接入之前先用 curl 发一次 SOAP 请求能最快确认接口本身有没有问题。下面这段是完整的测试过程先准备一个soap.xml文件再执行 curl。?xml version1.0 encodingutf-8? soap:Envelope xmlns:soaphttp://schemas.xmlsoap.org/soap/envelope/ soap:Body AddSaleOrder xmlnshttp://internal.erp/u8soap account001/account userIddemo/userId passworddemo/password dataXmllt;Vouchergt;lt;Headergt;lt;Codegt;SO20240101lt;/Codegt;lt;Dategt;2024-01-01lt;/Dategt;lt;CusCodegt;C001lt;/CusCodegt;lt;DepCodegt;D01lt;/DepCodegt;lt;/Headergt;lt;/Vouchergt;/dataXml /AddSaleOrder /soap:Body /soap:EnvelopeSOAPAction 头不是随便填的它的格式是“命名空间 方法名”。这段请求里命名空间是http://internal.erp/u8soap方法名是AddSaleOrder所以 SOAPAction 是http://internal.erp/u8soap/AddSaleOrder。curl -s http://192.168.1.10:8088/U8ApiService.asmx \ -H Content-Type: text/xml; charsetutf-8 \ -H SOAPAction: \http://internal.erp/u8soap/AddSaleOrder\ \ --data soap.xml如果返回的报文里有OK说明整条链路已经通了。如果返回E开头的错误先别怀疑 SOAP 格式到 U8 服务端看日志多半是账号权限、字段映射或者单据号规则的问题。这一步能帮你把“接口问题”和“U8 业务问题”切分开。U8 组件报的错无论你 SOAP 报文怎么写都绕不过去所以冒烟测试阶段把错误暴露出来反而是好事。4.3 Python 和 Java 客户端怎么接客户端接入最省事的方式是直接拿 WSDL 生成桩代码。Python 端用zeep它对老式 ASMX 的兼容性不错不用手拼 SOAP 报文。装好依赖后代码就这么几行。from zeep import Client client Client(http://192.168.1.10:8088/U8ApiService.asmx?wsdl) result client.service.AddSaleOrder( account001, userIddemo, passworddemo, dataXmlVoucherHeaderCodeSO20240101/CodeDate2024-01-01/DateCusCodeC001/CusCodeDepCodeD01/DepCode/Header/Voucher, ) print(result)zeep会去解析 WSDL把方法名、参数顺序都映射成 Python 函数参数所以客户端里看到的参数名和 C# 服务端的[WebMethod]方法参数保持一致。注意dataXml里的 XML 内部标签不要带命名空间U8 的字段映射只关心标签名加命名空间反而容易让XDocument解析出空值。如果接口将来要支持大批量数据建议传一个根节点下挂多张单据的 XML接口里循环调用BuildVoucher逐张提交。Java 端用wsimport生成客户端代码然后在业务代码里调用。生成后的调用方式大致是这样。URL wsdl new URL(http://192.168.1.10:8088/U8ApiService.asmx?wsdl); U8ApiServiceService service new U8ApiServiceService(wsdl); U8ApiService port service.getU8ApiServicePort(); String result port.addSaleOrder( 001, demo, demo, VoucherHeaderCodeSO20240101/Code/Header/Voucher );Java 生成的类名、包名以 wsimport 的结果为准我这里只是示意。接入时最容易出错的是把account传成数据库库名或者把userId传成 Windows 登录账号。U8 组件认的是 U8 操作员不是系统账号这一点让外部团队提前知道能省去大量来回试错的时间。接口参数里也尽量不要传数据库连接串连接串只放在服务端配置里外部系统永远拿不到。5. 避坑实录U8 API 二次开发最容易翻车的五个场景这些坑不是看文档能躲开的大多是上线那一刻才炸出来。我把这几年在 U8 接口上踩过的、帮别人擦过的坑按“现象、原因、解决”整理成五条部署前对照一遍。5.1 绕过登录上下文导致权限互斥现象外部调用AddSaleOrder时接口返回“当前用户被占用”“操作员互斥”或“没有权限”。U8 客户端里明明能用同一个账号操作单据但接口就是报错。原因WebService 进程里没有先登录 U8 账套或者登录对象被设成了静态对象供所有请求共享。U8 操作员同时在线数量有限外部重试和服务器上手动打开的客户端同时占用一个账号互斥就发生了。解决为每个请求新建登录对象不要缓存登录状态。在 U8 侧建一个专门的接口服务账号不要共用实施人员的 demo 账号。这个账号只用于 WebService手动登录 U8 客户端时也尽量不要用它避免把并发名额占掉。5.2 32/64 位组件注册问题导致“没有注册类”现象接口在浏览器能打开一调用就抛80040154提示没有注册类或者直接说方法不存在。原因U8 的 U8API 组件大多是 x86 COMIIS 应用池默认按 64 位找注册表找不到组件入口就报没有注册类。这个问题最容易出现在 64 位 Windows Server 上代码在开发者机器上跑得好好的发布到服务器就崩。解决站点应用池启用 32 位应用程序C# 工程编译目标平台设为 x86。如果服务器装的是精简版 U8可能没有注册 API 组件需要重新安装 U8 客户端组件或对应补丁。判断方法很简单在服务器上打开dcomcnfg或注册表搜一下 U8API 对应的 ProgID找不到就是没注册。5.3 网络重试造成重复单据现象外部系统调用接口时网络超时客户端自动重发一次结果 U8 里出现两张单号相同的销售订单。原因SOAP 协议天然不保证幂等。第一次请求其实已经在 U8 里提交成功了但响应在网络传输中丢失外部系统以为失败就重发了。U8 组件本身有单号唯一校验但那是控件级的提示接口返回码在重试场景下拦不住重复提交。解决在接口入口加外部单号唯一约束查不到再新增查到了直接返回第一次的结果。这件事不要依赖 U8 做必须放在自己的服务逻辑里。第 6 章的同步日志就是干这个用的。5.4 新增成功但单据卡在“未审核”现象接口返回OK外部系统确认成功但 U8 界面上单据是“未审核”状态业务流卡住。原因公共组件的Add只完成保存审核是另一个动作。外部系统以为OK就是流程走完了实际单据还停在草稿状态。如果你们的流程要求单据直接进审核单靠新增接口是不够的。解决在接口方法里把新增和审核串起来新增成功后紧接着调审核方法。审核失败的错误码也要返回给调用方不要吞掉。如果业务上允许先存后审那就要在接口文档里写清楚并明确告诉外部系统OK不等于审核完成。5.5 外部调用超时但服务端日志正常现象外部系统报Read timed out但服务端事件日志里没有异常U8 里也没有重复单据。原因U8 登录加公共组件构造单据本身很慢首次请求还要叠加 IIS 编译开销动辄几十秒。外部客户端的超时时间往往只有十几秒接口还在跑客户端就放弃等待了。解决把客户端 SOAP 读取超时调到 120 秒以上。验收时先发一次预热请求把 IIS 编译和 U8 登录初始化耗掉再做正式压测。另外接口里不要放长事务尤其不要把外部文件读取、远程调用的逻辑塞进 U8 请求里耗时一长各种超时问题都会扑过来。6. 接口上线前的最后一步用日志与单据幂等验证接口接口能调通只是开始真正让实施方睡得着觉的是上线前加上这张后悔药。我把这套做法叫“先查、先记、再调用”简单说就是在 U8 业务操作之前先用自己的日志表做幂等判断。有了这张表网络重试、重复推送、错误排查都有据可查。6.1 幂等键和同步日志我在接口服务里加一张同步日志表建在 U8 数据库里或者单独一个库都可以。核心字段只有几个外部系统标识、外部单号、U8 单号、状态、返回信息、创建时间。然后用外部系统标识加外部单号做唯一索引。CREATE TABLE U8SoapSyncLog ( SyncId INT IDENTITY PRIMARY KEY, SourceSystem NVARCHAR(50) NOT NULL, SourceOrderId NVARCHAR(64) NOT NULL, VoucherCode NVARCHAR(64), Status TINYINT NOT NULL, ResponseMsg NVARCHAR(500), CreateTime DATETIME NOT NULL ); CREATE UNIQUE INDEX UX_U8SoapSyncLog ON U8SoapSyncLog(SourceSystem, SourceOrderId);SourceSystem标识是哪个外部系统发来的比如OMS、MES、WMSSourceOrderId是外部系统的业务单号。这两个字段拼起来就是幂等键唯一索引能挡住并发的重复请求。Status用 0 表示处理中1 表示成功2 表示失败这样掉电或者异常重启后还能根据状态找出“处理中但没结果”的半成品记录。有了表之后接口方法内部调整成下面这个顺序。string key req.SourceSystem : req.SourceOrderId; if (repo.Exists(key)) return ALREADY: repo.GetVoucherCode(key); repo.InsertStart(key, dataXml); try { object voucher BuildVoucher(dataXml); int code api.Add(voucher); if (code ! 0) throw new Exception(U8 Add failed: code); repo.MarkSuccess(key, voucherCode); return OK: voucherCode; } catch (Exception ex) { repo.MarkFail(key, ex.Message); return E: ex.Message; }这段代码先查幂等再写日志最后才碰 U8 组件。为什么先写日志再调 U8因为如果反过来U8 已经新增成功但日志没写之后重试时查不到记录又会重复提交。先记录状态为处理中即使 U8 调用失败日志里也留着原始 XML可以人工核对。外部系统看到ALREADY前缀就知道这张单之前已经成功不需要再推一次。从那以后我每次给 U8 包 WebService都会把这三件事当成默认动作先查幂等、再写同步日志、最后才调 U8 组件。上线后出问题翻日志几分钟就能定位是外部参数错、U8 权限错还是单号撞了。希望帮到你。本文还有配套的精品资源点击获取
返回列表