ARTICLE DETAIL

资讯详情

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

C#调用U8API实现用友U8插件开发:从环境搭建到注册实战

C#调用U8API实现用友U8插件开发:从环境搭建到注册实战 在制造业和贸易企业里摸爬滚打过的开发应该都逃不过用友U8这道坎。U8本身带了不少标准功能但企业一旦跑顺了总会有各种“标准功能不满足”的边角需求比如按客户特定格式导单据、在保存前做字段校验、把外部系统数据同步进来。这时候就得靠二次开发。C#调U8API、做插件注册是我这几年做过最多的一类活也是很多刚转过来的人最懵的地方。今天就按我实际踩过的路把从环境搭建、代码编写到插件注册这整套流程完整理一遍。不管是公司内部IT、独立软件商还是刚接U8二开项目的新手只要你会一点C#这篇文章都能帮你少走不少弯路。1. 这块内容到底是什么U8API插件开发的全貌1.1 为什么用C#做U8API开发是最舒服的方式先说个背景用友U8的API体系是从早期VB时代一路演变过来的底层大量组件是COM/COM官方主推的开发语言早期是VB6。但VB6现在基本没人愿意碰维护成本极高。C#有完整的.NET生态、强类型、IDE支持好加上VS调试能力比VB6强太多所以现在绝大多数U8二开项目都选择C#。很多刚接触的人会担心“C#调COM会不会很麻烦”我的回答是麻烦但没有更好的选择。C#可以通过“添加引用”直接引用U8提供的Interop互操作程序集也可以把COM组件手动转换成DLL再调用。Visual Studio会自动生成Runtime Callable Wrapper你在代码里用起来的感觉和调用普通C#类库没有本质区别。无非就是某些接口命名很古老、返回值是object、需要注意资源释放这些属于习惯问题。另外C#做插件注册还有个隐藏优势你可以把业务逻辑、日志、配置管理、第三方数据接口全部打成一个DLLU8只负责提供一个入口其他都在你自己的程序集里闭环。这样代码可维护性高也不会把U8环境搅乱。相比VB6那种“代码全堆在窗体里”的写法C#的工程化能力能让你后续迭代轻松很多。1.2 “插件注册”和普通API调用有什么本质区别很多微信群里的问题都是“我用C#调U8API已经登录成功了还需要注册插件吗”这说明大家把“调用API”和“插件注册”当成了一件事。其实这是两个层次的问题。普通API调用意思是你在自己的独立程序比如一个WinForm、一个Windows服务里引用U8API程序集自己登录、自己调用、自己处理结果。这套逻辑和U8主程序没有关系U8开着或者关着都不影响你运行。插件注册则是把你的业务功能以DLL的形式“挂”进U8的运行环境里。用户在U8界面点一个菜单U8会去加载你注册的那个DLL调用你预先写好的入口方法然后你的代码开始干活。插件方式的最大好处是用户不用切换程序、不用学习两套界面流程上更顺畅数据权限能直接沿用U8当前登录用户的身份不需要在插件里再维护一套账号密码。我实际项目里两种方式都会用需要后台定时跑的、数据量大对界面没要求的用独立程序需要财务人员、库管员随时在U8里操作的必须做成插件注册。这篇文章后面讲的核心路径就是第二种。2. 开发前的环境准备与程序集引用2.1 本机必须具备的运行环境很多人一上来就写代码跑到一半发现连不上U8最后折腾一圈问题出在开发机环境不完整。U8API开发对环境的依赖比普通开发重得多别偷懒下面几项先确认。第一开发机必须安装用友U8客户端。注意是完整客户端不是只装一个数据库客户端或者只装插件。因为U8API含有大量COM组件这些组件在安装客户端时才注册到操作系统里。只复制几个DLL到本机运行通常会报“未注册的类”或“80040154”错误。第二目标账套和操作员权限要提前配好。U8API登录之后能操作哪些模块、能看哪些单据完全取决于登录用户在U8系统管理里的权限分配。建议在测试环境单独建一个开发账号分配好对应的模块权限不要用admin否则生产环境迁移时容易权限对不上。第三IIS和U8应用服务配置要正常。U8API和数据库交互大多走U8自身的应用服务器如果应用服务器没启动或者数据库连接配置不对你的C#程序能编译、能运行但登录那一步大概率直接失败。第四Visual Studio版本建议用2015以上.NET Framework选4.5以上。U8老版本有些Interop程序集是基于.NET Framework 2.0的但现代Windows上跑4.5以上兼容性更好。我一般用.NET Framework 4.7.2目前没遇到过兼容性大坑。2.2 引用U8API核心程序集U8安装完客户端之后在安装目录下能找到一堆和API相关的DLL常见的有Interop.U8Login.dll、Interop.U8API.dll。这两个是基础一定要引。Interop.U8Login.dll里主要是一个登录类负责和U8用户中心做身份认证返回登录会话信息。Interop.U8API.dll里是业务API的根入口通过它可以获取采购、销售、库存、存货等具体业务接口。引用方式有两种我推荐用VS的“添加引用 → COM”选项卡找到U8相关的组件直接加上。你也会在U8安装目录的Interop文件夹里看到这些DLL直接“浏览”添加也是可以的。两种方式本质一样都是生成Interop程序集。还有一种特殊情况如果你需要同时兼容U8 12.x和16.x不同版本最好在代码里使用反射方式延迟绑定或者对每个版本单独编译引用。不同版本之间U8API接口签名有差异强行用一个版本的程序集去跑另一个版本轻则某方法报参数错误重则直接崩溃。我在项目里吃过亏后面第五部分会详细说。3. C#调用U8API的核心流程3.1 登录U8账套登录是所有U8API操作的第一步也是报错率最高的一步。先看一个典型的最小实现。using U8Login; public class U8ApiService { private object _loginObject; public bool Login(string account, string userId, string password, string server, string dataSource) { clsLogin u8Login new clsLogin(); // 登录参数在不同版本略有差异这里以常见格式为例 string loginInfo string.Format( AID{0};UID{1};PWD{2};Srv{3};DSN{4}, account, userId, password, server, dataSource); string loginResult u8Login.Login(loginInfo); if (loginResult null) { _loginObject u8Login; return true; } // 返回内容一般是错误描述 Console.WriteLine(登录失败: loginResult); return false; } public object GetLoginObject() _loginObject; }这里有个很关键的点u8Login.Login(loginInfo)返回的是string很多不熟悉的人会以为返回bool或int。不同版本U8API的返回类型确实不太一样我见过返回int的版本0表示成功也见过返回string的版本null表示成功。所以你接手项目时一定先确认目标版本的实际签名。还有登录参数AID是账套号UID和PWD对应用户代码和密码Srv是应用服务器地址DSN是数据源名。这套命名沿用了早期VB的习惯别用如今Web开发那一套token、appkey之类的思路去理解它。登录成功后的clsLogin实例不要随手扔后面所有API操作都依赖它。这个对象代表的是“当前用户在当前账套里的会话”包含操作员编码、账套号、登录日期等上下文信息。也正因如此做插件开发时要把它保存到静态字段或依赖注入容器里确保每次API调用都使用同一个会话。3.2 获取API根对象并操作业务数据登录成功后下一步是创建U8APIRoot再通过根对象拿到对应业务的子API。代码如下using U8API; public class U8ApiService { private object _loginObject; public bool CreatePurchaseOrder(string orderData) { U8APIRoot root new U8APIRoot(); root.LoginObject _loginObject; // 通过根对象获取采购订单API // 不同版本子API对象名不同这里以常见写法为例 object poApi root.POOrder; // 调用创建订单方法 poApi.GetType().InvokeMember( CreateOrder, System.Reflection.BindingFlags.InvokeMethod, null, poApi, new object[] { orderData }); return true; } }这里我不想把某个版本的具体API名当成“标准答案”来忽悠人因为U8版本的多样性决定了接口名称经常变化。但登录取根、根取子API、子API执行增删改查这条链路是稳定的。你拿到项目后第一件事就是找到对应版本的U8API开发手册通篇翻一遍重点看“对象模型图”。如果你不想依赖固定的API名称可以用反射形式去调用就像上面代码里那样。好处是绕过强类型编译坏处是如果API签名变了运行时才能发现。建议在自己封装一层“API代理”类所有对象名、方法名、参数类型都集中在一个类里管理。版本升级时只改代理类不用满项目找。实际开发中还有一种更简单的替代方案不直接和U8API的业务对象死磕而是通过U8的Voucher或Bill这种通用单据对象传入要操作的单据模板号和数据XML。这种方式表面上绕开了复杂的子API但对XML结构要求极高字段没对上会静默丢数据或者报一堆摸不着头脑的错误。所以我个人建议能用专用子API的尽量用专用子API。3.3 插件的注册入位把DLL挂进U8这是全文最容易被忽略的部分。很多人写完了API调用逻辑觉得“差不多了”结果部署时才发现U8根本不加载自己的DLL。我惯用的插件注册方法有三种按场景选。第一种菜单注册。U8系统管理里有一个“菜单注册”功能版本不同所在位置不同有的叫“功能权限注册”。你把自己编译好的程序集DLL放到U8安装目录下比如U8SOFT\Plugins\MyPlugin\然后在菜单注册中新增一条记录填写菜单显示名称、DLL路径、命名空间和入口类名。用户在U8门户里点这个菜单时U8会创建你指定的类实例并调用约定的入口方法。第二种U8插件管理器。部分U8版本安装目录下有一个插件配置工具或PluginConfig它可以扫描指定目录下的DLL识别实现了特定插件接口的程序集并在使用配置文件中自动注册。这种方式适合分发多个插件的场景用户可以可视化启停插件。第三种手工修改配置文件。U8有一些*.config或*.xml的配置文件里面维护了外部程序集的注册信息。这种方式最原始也最容易出错我一般不建议手工改除非现场U8版本比较老没有前两种工具。不管用哪种注册方式插件项目的结构最好遵守一个约定外部程序集是一个类库项目里面有一个公开的入口方法比如Execute、Run或者OnClick。U8调用插件的本质就是“找到入口方法传进去登录上下文和界面句柄”你在入口方法里干不了的事U8帮你干你在入口方法里能做的事全都自己做。基于这个前提我一般把插件项目拆成三层入口层负责实现U8约定的插件接口接收参数负责调用业务层。业务层纯C#逻辑处理单据校验、数据转换、调用U8API、写日志。数据层负责和SqlServer或其他外部数据库交互。入口层保持极薄因为U8插件入口经常要适配不同U8版本接口改动的概率不小。把业务逻辑全堆在入口类里后续版本一升级你就要在巨大的入口类里翻代码极其痛苦。4. 完整案例把一张采购订单做成U8插件4.1 需求拆解与界面设计这里用一个我做过很多次的需求来说明采购部希望从Excel表格导入一批采购订单但不想用U8自带的标准导入工具想直接在U8菜单里点一个“Excel导入采购订单”的功能。需求听起来不难但拆开就三块第一块用户在U8界面点菜单时系统要弹出一个窗口窗口上有“选择Excel文件”“选择采购部门”“选择税率”等控件。第二块程序读取Excel逐行校验物料编码、数量、单价、供应商是否合法。第三块调用U8API创建采购订单最后展示导入成功和失败的结果。界面部分可以用WinForms做成用户控件或者独立窗体。如果你做的是独立窗体要记得在插件入口方法中获取U8主窗口句柄把窗体显示为模态对话框否则用户切来切去很容易把U8主界面弄乱。我一般这样获取主窗口句柄// 在插件入口方法里传入的父窗口句柄 IntPtr parentHandle parentWindowHandle; frmImport frm new frmImport(); frm.ShowDialog(new WindowWrapper(parentHandle));4.2 核心代码实现模拟一个最简单的采购订单创建流程。我刻意不绑定具体U8版本的API对象但结构是通用的。public class PurchaseOrderImporter { private readonly object _loginObject; public PurchaseOrderImporter(object loginObject) { _loginObject loginObject; } public bool CreateOrder(OrderModel order) { // 1. 校验基础档案 if (!ValidateMaterial(order.MaterialCode)) { return false; } // 2. 创建单据核心对象 U8APIRoot root new U8APIRoot(); root.LoginObject _loginObject; object po root.POOrder; bool result (bool)po.GetType().InvokeMember( CreateOrder, BindingFlags.InvokeMethod, null, po, new object[] { BuildOrderBody(order) }); return result; } private string BuildOrderBody(OrderModel order) { // 按U8API要求的XML或字符串格式构造单据体 // 字段包括供应商、部门、存货编码、数量、单价、币种、税率等 return string.Empty; } }这里重点提醒U8API对单据明细行的处理非常娇气。存货编码必须是U8里真实存在的数量不能为0单价保留小数位数要符合精度要求税率字段有的版本是百分数17有的版本是小数0.17。做导入功能时Excel里的原始值必须先做标准化再拼进单据体不然一条数据的错误会导致整单失败。业务层里还要加上幂等判断。我踩过最大的坑是用户双击了两次导入按钮导致同一批采购订单创建了两遍。后来我在创建订单前先查业务单据列表判断是否已经存在相同供应商和物料组合的草稿单如果有就直接提示不再创建。4.3 注册到U8菜单的完整路径程序写好后部署分四步走。第一步把编译产物复制到U8客户端安装目录路径通常是C:\U8SOFT\Plugins\PurchaseImport\也可以自定义其他目录但目录要固定且要有写权限。第二步打开“菜单注册”工具新增一个菜单节点。菜单标题填“导入采购订单”类型选择“外部程序”路径填到你的DLL文件全路径入口字符串填命名空间和类名比如Plugins.PurchaseImport.ImportEntry。入口方法名要按U8插件接口的约定如果版本不支持直接指定方法名就在类里实现U8定义的接口。第三步给菜单分配权限。这一步很多开发会忽略。菜单注册完系统默认只有管理员能看。要在权限管理里把这个新菜单授权给采购部角色或具体用户否则用户登录U8后根本看不到这个菜单。第四步在U8客户端里重新登录验证菜单。U8菜单缓存比较严重注册完菜单后如果看不到多半是缓存问题需要重启U8客户端或者清掉本机的菜单缓存目录。4.4 验证一次完整调用部署完成后我用测试账套完整跑了一轮用Excel模板录入两行数据第一行是正确的第二行物料编码故意写错。点击导入后界面返回“成功1行失败1行”失败原因提示“物料编码不存在”。这个结果就是预期效果。完整的验证要点包括登录用户权限是否正常是否能看到新增菜单。选择Excel文件后是否正常读取中文路径和中文表头是否乱码。数据校验是否拦截非法物料编码、超库存数量、负数单价。创建成功后在U8原生界面能否查到这笔采购订单金额、部门、税率是否和导入一致。重复点击导入时是否会被幂等校验拦截。这样一轮验证下来插件才算真正可用。5. 常见问题与排查技巧实录5.1 登录相关报错登录U8API时最常见的报错是“登录失败”或返回错误信息。根据我经验八成问题出在参数拼接上。类Unix风格地说AID必须填数字账套号不能填账套名称UID是U8操作员编码不是显示名也不是邮箱DSN是数据源名在U8应用服务器配置工具里可以看到如果本机只装了客户端DSN经常和服务器名称不一致会连不上。还有一个隐蔽问题有些U8 API需要先调用初始化或连接方法再调用Login如果跳过初始化登录可能一直超时。不同版本的初始化方式不太一样查手册时重点看“登录前置条件”这一节。如果登录接口一直返回空字符串或null但也登录不上可以试试在U8客户端里手动登录一次确认账号密码没被策略锁定。U8有连续输错锁定机制测试时频繁输错很容易被锁需要管理员去系统管理里解锁。5.2 API调用返回异常的问题登录成功但调用API时报异常集中在三类。第一类是“接口未找到”或者“未找到对象”之类的COM异常。这多半是当前账套没有启用对应模块比如你没启用采购模块却去调用采购订单接口。解决办法是先在系统管理里启用模块或者换一个已启用的账套测试。第二类是“参数类型不匹配”。U8API的很多参数是object类型传入int、string都一样但底层COM会严格校验值。比如创建订单时单号参数传的是空字符串而不是DBNull.Value就会报“转换失败”。建议所有参数都通过一个统一的方法序列化成COM能识别的格式不要在业务代码里裸传。第三类是“事务超时”。U8API默认有事务超时时间如果插件在同一个事务里处理了大量数据比如一次导入成千上万行经常在运行到一半时超时。解决方案是把大数据量拆成批次每500条提交一次并把U8API的事务超时参数调大一点。拆分批次还有个好处哪一批出错可以单独重跑不用整单回滚。5.3 插件加载不上的坑插件注册完用户点菜单没反应是最让人头大的问题。我的排查顺序固定如下。先看U8安装目录下的日志文件。U8在运行时会写一堆.log插件加载失败一般会记录详细的CLR异常信息。日志路径通常在U8SOFT\Logs下按日期命名。如果没有日志就去看Windows事件查看器里的应用程序日志.NET异常经常记录在那里。再看程序集版本。如果你的插件DLL是用.NET Framework 4.8编译的但U8客户端自带的宿主进程是.NET Framework 2.0启动模式就会直接“未能加载文件或程序集”。这种情况下要么把插件目标框架降到U8支持的版本要么修改U8宿主的配置文件让它支持更高版本的CLR。我建议优先用低版本目标框架省得影响U8主进程。然后看依赖问题。插件如果依赖第三方NuGet包但没把依赖DLL复制到插件目录运行时就会报“找不到程序集”。解决方案是把所有依赖项“复制本地”设为True全部放到插件目录或者用ILMerge把依赖合并进主程序集。最后看权限问题。U8客户端如果以普通用户权限运行而插件目录在C:\Program Files下写入日志或读取配置就会失败。这时候要么给目录加权限要么把插件目录换成U8安装路径下有写权限的目录。6. 一点个人体会与后续扩展方向做U8API插件开发最让我感慨的不是技术难度而是很多问题都出在“版本差异”和“现场环境”上。同样一段代码在客户A那里跑得好好的到客户B那里就报错最后发现是U8补丁版本不同、某个API行为变了。所以我现在的习惯是每个项目一开始就记录U8版本、补丁号、数据库版本、.NET版本部署文档里也写明这些信息后续排查效率会高很多。如果你准备长期做这块开发建议在扩展方向上多花点时间一是把常用的API调用封装成NuGet包按U8版本分支维护新项目直接引用二是搭一套自动化测试核心验证U8API登录和单据创建每次U8升级后先跑一遍能提前暴露很多兼容性问题三是把插件发布做成标准流程用脚本自动复制DLL、写配置、注册权限减少“漏拷一个文件”的尴尬。C#调U8API这条路算不上多高大上但做扎实了在公司里是真的能解决实际问题的。希望这篇记录对你有点用也希望你在自己的项目里少踩几个坑。
返回列表