简介面向用友U8二次开发人员与ERP实施顾问这套源码包聚焦“CO方式”下采购请购单的增删改审接口开发能帮助企业按自身业务流程灵活定制采购管理环节并打通与其他系统的数据交换。包体共73个文件约1.05MB以dll、cs、xml、config等为主U8Login.dll负责登录验证与权限关联cs工程文件含Demo.sln、Form1.cs、Models等展示接口调用与业务逻辑packages.config、App.config等记录依赖与运行配置说明.txt则提供安装步骤和参数定义。目前已有162人学习下载适合开始接触用友U8接口开发、需要参考完整示例代码的读者。内含可直接运行的Demo程序源码可重点研读采购请购单的增、删、改、审四类操作如何封装进CO流程并通过Dapper等组件完成数据访问为后续扩展供应商协同、审批集成等场景提供扎实样板。 用友U8的采购请购单增删改审接口听起来是个很窄的话题实际做下来涉及的东西远比想象多。我最近把一套CO方式Collaboration Operation协同业务对象调用的U8采购请购单接口完整整理了一遍覆盖新增、修改、删除、审核四个动作的调用链路、登录上下文处理、单据状态机判断以及一堆只有真跑过才会踩到的坑今天系统性写出来给准备做U8二次开发接口的同学当一份参考地图。这套方案解决的典型问题很明确企业有OA、MES、SRM等第三方系统审批流程走完后需要自动把采购请购单落到U8里甚至后续还要支持反查、修改、删除和审核。如果全靠人工在U8界面录单既慢又容易出错。用CO接口做相当于用代码模拟一个“高级操作员”在U8里干活所有单据规则、权限校验、库存参数都由U8原生逻辑把关比直接操作数据库表安全太多。适用人群也很清楚企业内部的IT开发、ERP实施顾问、做供应链系统对接的二次开发工程师。你不需要对U8内部表结构了如指掌但要有C#或Java基础理解“业务对象动作调用”这种面向对象式接口的设计思路。下面我按自己实战的推进顺序来写。1. 为什么绕不开COU8接口开发的几条路子与取舍先泼一盆冷水U8二次开发接口的方式不止一种CO只是其中一条但绝大多数“正经”的增删改审接口最后都会回到CO这条路。直接操作SQL Server数据库是最早一批开发者爱干的事。往PurchaseOrder表插一条往PurchaseOrder_Detail插几条看起来干净利落。但问题在于U8的单据表之间有关联校验直接插库很容易漏字段比如默认税率、计价方式、单据类型规则。绕过U8的权限体系操作员、部门、仓库权限全部失效审计追溯无从谈起。一旦U8版本升级表结构或触发器变化你的SQL立刻报废维护量大得惊人。最麻烦的是很多U8实施合同里明确禁止绕过业务对象直接改库出了问题原厂服务直接不认。VBA插件或者窗体插件则是另一条路。在U8客户端里挂一个按钮利用客户端界面自动填单。这种方案适合“辅助人工”比如帮采购员快速录入但本质上还是要有人开着U8客户端无法做到无人值守的接口调用第三方系统想主动推送数据更是难上加难。EAI方式也是官方支持过的集成方案通过XML报文做数据交换。早年很多接口项目就是EAI但EAI配置繁琐调试要反复传报文、看日志而且对大批量、高并发的场景支持一般现在新项目已经很少首选用它。CO接口的逻辑则完全不同它把U8里的采购请购单封装成业务对象调用方只需要在代码里创建对象、设置表头表体字段、调用保存/审核/删除等动作方法U8内部会走完整的业务校验链。你可以把它理解成一辆车的方向盘和油门——你只管操作发动机怎么运转、变速箱怎么换挡系统内部处理。实际开发中我选择CO还有几个现实理由官方有完整的组件支持采购模块、库存模块、销售模块都有对应的业务对象方法论是通用的学会请购单后面做其他单据能迁移。支持事务化的操作方式多个动作可以包在一个业务模型里避免“表头保存成功、表体保存失败”这种尴尬局面。社区里能找到的源码包十有八九也是CO方式写的参考学习甚至直接复用都很方便。一句话总结如果你的目标是做“第三方系统驱动U8单据自动流转”的接口CO方式是当前最稳妥的选择。剩下的问题就是环境准备和具体动作怎么调。2. 开发前务必搞定的三件事组件引用、登录上下文与账套连接CO开发的环境准备不难但有几个关键点没弄对后面会浪费大量时间。2.1 组件引用与版本匹配首先要在开发机上安装用友U8客户端或者至少安装完整版插件。装的时候注意勾选“U8二次开发组件”相关选项否则你在Visual Studio里找不到需要的程序集。高频用到的程序集大致包括UFSoft.U8.Framework.LoginUI.dll登录界面相关组件UFSoft.U8.Framework.LoginProxy.dll登录代理负责身份认证和获取上下文UFIDA.U8.UAP.Services.dllUAP平台服务组件采购模块对应的业务对象程序集通常以UFIDA.U8.Purchase.*开头版本匹配是最大的坑。U8客户端版本必须和服务器版本保持一致比如服务器是U8 16.5客户端也必须是16.5小版本差异可能导致接口调用报错报错信息还特别迷惑人常见的有“调用的目标发生了异常”或“未将对象引用设置到对象的实例”。2.2 登录与上下文创建CO方式操作单据前必须先做一个类似“客户端登录”的动作获取到当前操作员的身份上下文。代码逻辑大致如下using UFSoft.U8.Framework.LoginProxy; using UFSoft.U8.Framework.LoginUI; // 实例化登录代理地址根据服务器部署情况配置 LoginProxy loginProxy new LoginProxy(); loginProxy.Url http://192.168.1.100/U8API/; // 实测以环境为准 loginProxy.UserToken string.Empty; bool isOk loginProxy.Login( U8, // 产品标识固定传U8 001, // 账套号 admin, // 操作员 123456, // 密码 out string errMsg ); if (isOk) { UserContext userContext loginProxy.GetUserContext(); string connString loginProxy.GetConnectionString(); }这里UserContext非常关键后续所有业务对象都要引用它相当于告诉U8“我是谁、我在哪个账套干活”。connString就是账套对应的数据库连接串CO对象内部用这个连接串读写数据不需要你自己再拼一个SqlConnection。2.3 先做一次自检正式写增删改审之前我建议先做一个最小验证登录后拉一下当前账套的部门档案或者存货档案确认上下文和连接串都是通的。这一步能过滤掉80%的环境问题避免后面调试业务逻辑时分不清是代码问题还是环境问题。// 简单自检读取存货分类列表能返回说明环境OK clsBusiModel busiModel new ClsBusiModel(); DataTable dtDepart busiModel.GetDataTable(select * from InventoryClass, connString);注意不要用这个方式做核心业务逻辑只做连通性验证。真正的单据操作一定要走CO业务对象。3. 采购请购单四动作拆解新增、修改、删除、审核的调用逻辑环境通了之后就是重头戏写四个动作。这里我直接贴实际可用的C#调用逻辑并解释每一步的设计意图。3.1 新增创建单据对象设置表头表体新增请购单的流程和人工在U8界面操作几乎一一对应先建一张空白采购请购单填表头部门、采购类型、制单人再填表体存货编码、数量、需求日期、建议采购单价最后保存。代码大体如下using UFIDA.U8.Purchase.PRVO; // 采购业务对象 using UFSoft.U8.Framework; // 创建业务对象并绑定上下文 PRVO.PurchaseOrder order new PRVO.PurchaseOrder(); order.SysContext userContext; order.ClsBusiModel new ClsBusiModel(); // 表头 order.Header.VoucherType 普通请购; order.Header.DepartmentCode 0101; order.Header.CreateDate DateTime.Now; // 表体行 PRVO.PurchaseOrder_Detail detail new PRVO.PurchaseOrder_Detail(); detail.InvCode 01010101; // 存货编码 detail.Quantity 100; // 数量 detail.RequiredDate DateTime.Now.AddDays(7); // 需求日期 detail.SuggestPrice 0.00m; // 建议单价可为空 order.Details.Add(detail); // 保存 string saveErr order.Save(); if (!string.IsNullOrEmpty(saveErr)) { // 处理失败逻辑 } else { order.Freeze(); // 释放资源 }这段代码有几个关键点要特别说明SysContext和ClsBusiModel必须赋值。SysContext是身份和账套上下文ClsBusiModel是业务模型容器承载事务和状态管理。漏掉任何一个都会在Save时抛异常。表头表体是分离的对象结构表体必须通过order.Details.Add方式加入而不是直接给Details赋值。这个和C#里集合对象的引用方式有关搞错过一次就会记住。Save之后一定要Freeze。Freeze是CO对象释放内部资源的方法类似Dispose。如果不调长时间跑接口服务内存占用会持续上涨最后变成“跑几天就卡死”的定时炸弹。必填字段项VoucherType、DepartmentCode、InvCode、Quantity基本是跑不掉的其他字段如采购类型、税率、换算率是否必填取决于U8的库存选项和采购选项设置。项目现场千奇百怪建议先手工在U8界面录一张全字段单把实际需要的字段记下来代码里逐个对齐。3.2 修改先Load再改再Save修改的逻辑和新增有本质区别新增是“无中生有”修改是“先找到已有单据再局部覆盖”。PRVO.PurchaseOrder order new PRVO.PurchaseOrder(); order.SysContext userContext; order.ClsBusiModel new ClsBusiModel(); // 获取已有的请购单参数根据版本可能用单据号或主键 order.Load(0000001234); // 修改字段比如调整数量、变更需求日期 order.Details[0].Quantity 200; order.Details[0].RequiredDate DateTime.Now.AddDays(14); string saveErr order.Save(); if (string.IsNullOrEmpty(saveErr)) { order.Freeze(); }这个动作里最值得注意的细节是修改前的状态判断。如果单据已经审核U8的规则不允许直接改数量这时候Save大概率会失败或者返回一个“当前状态不允许修改”的提示。所以修改动作里一定要先查单据状态详见下一章的状态机只有“录入”状态的单才能改。如果业务要求能改已审核的单正确姿势是“弃审→修改→重新审核”三步走而不是强行Save。3.3 删除状态校验是生死线删除动作比修改更敏感U8里请购单删除的规则非常明确未审核且未被下游参照的单据才能删除。任何绕过规则的删除操作都可能让下游采购订单产生悬空引用。PRVO.PurchaseOrder order new PRVO.PurchaseOrder(); order.SysContext userContext; order.ClsBusiModel new ClsBusiModel(); order.Load(0000001234); // 判断状态非录入状态直接拒绝 if (order.IsAudited) // 实际属性名以版本为准 { throw new Exception(已审核单据不允许直接删除); } string deleteErr order.Delete(); if (string.IsNullOrEmpty(deleteErr)) { order.Freeze(); }我自己的经验是删除动作调用前除了用对象属性判断状态最好再查一下这张请购单是否已被采购订单、MRP计划等下游单据引用。CO接口的Delete方法本身会做校验但校验失败返回的错误信息往往比较底层比如“数据已被其他单据使用”你一看就知道原因了。能提前用SQL或U8API查清楚接口返回给业务系统的错误就能写得更加业务化比如“该请购单已生成采购订单不能删除请先删除下游单据”。3.4 审核最容易被低估的动作审核是四个动作里报错率最高的。原因在于审核不仅仅是改一个状态位U8后台会做大量合规性检查包括操作员是否有审核权限、部门权限是否匹配、单据金额是否超预算如果启用了预算管理、存货是否已停用、数量是否0等等。PRVO.PurchaseOrder order new PRVO.PurchaseOrder(); order.SysContext userContext; order.ClsBusiModel new ClsBusiModel(); order.Load(0000001234); string auditErr order.Audit(); if (string.IsNullOrEmpty(auditErr)) { order.Freeze(); }审核失败时返回的errMsg有时候很长但真正有用的信息通常在最后一句。我遇到最多的失败原因是“审核人没有该单据的审核权限”其次是“单据未保存”。注意这里说的审核权限除了用户本身的功能权限还牵涉到U8工作流里的审批流设置。如果系统启用了请购单审批流那么接口审核可能还会触发工作流引擎逻辑更复杂。建议项目初期就确认清楚这套接口审核是走“直接审核通过”还是要配合工作流走多级审批。如果是配合工作流接口层通常只做“提交审核”动作名类似Submit后续由各级审批人在U8或OA里处理。不要把“提交”和“审核通过”混为一谈。4. 状态机与接口幂等让增删改审不互相打架做接口开发最怕的不是单个动作写不出来而是动作之间“互相打架”。同一张请购单刚被第三方系统修改又被另一条线程删除或者重复审核两次都是线上事故的高发区。解决思路就是理解U8请购单的状态流转并在接口层做好幂等控制。4.1 U8采购请购单的状态流转从CO开发视角看请购单常见状态可以简化成动作前置状态后置状态说明新增保存无录入刚刚建立未审核修改保存录入录入只在录入状态可改删除录入已删除审核后不允许删审核录入已审核审核后进入业务执行弃审已审核录入取消审核恢复录入关闭已审核已关闭可手动/自动关闭在代码里不同版本的U8对外暴露的状态属性名并不完全一致有的叫Status有的叫IsAudited有的直接用内部VoucherState枚举。我建议拿到开发环境后先打印一份单据对象的所有字段和状态值直观确认当前版本属性名。4.2 接口层幂等设计第三方系统调用接口最大的隐患是网络超时后的重试。如果重试机制不做幂等处理一条请购单可能被重复提交成两张。很多经典事故都是这么来的。我的做法是在CO调用之前业务系统侧建一张接口流转表字段至少包括业务单号、第三方来源、目标U8单据号、处理状态、请求报文、响应报文、重试次数。每次收到第三方请求先查表如果同一个业务单号已经处理成功直接返回已存在的U8单据号不再调用CO。如果处理中则判断是否超时超时不超过N次继续重试超过则人工介入。如果处理失败记录失败原因并支持失败后重新推送。这张表是接口稳定性的兜底比任何代码层面的技巧都实用。哪怕U8接口本身没做事务回滚你至少能通过状态表判断哪些单据要人工核对。4.3 Save失败与Freeze的关系CO对象调用Save失败后很多人会疑惑还要不要Freeze答案是要。Save失败不代表内部资源已经释放手动地址空间和非托管对象还在必须调用Freeze。我见过不少内存泄漏案例就是Save失败后直接跳出方法没有走Freeze。更稳妥的写法是用try-catch-finally把Freeze放到finally里try { string errMsg order.Save(); if (!string.IsNullOrEmpty(errMsg)) { // 业务处理 } } catch (Exception ex) { // 异常处理 } finally { order.Freeze(); }这个习惯养成后接口服务跑几个月内存依然稳定。5. 实测踩坑那些不跑一遍根本发现不了的问题环境配置和基础动作都写完后剩下就是真刀真枪联调。我把自己跑过的坑集中列出来每个都是真金白银换来的经验。5.1 提示信息严重“缺芯”错误日志要靠自己补CO接口返回的错误信息有时候简短到让人崩溃比如直接给你一个“保存失败”或者一串看不懂的异常码。这时候最有效的排查手段是打开U8服务器的日志目录一般位于U8安装目录下的日志文件夹按日期和模块生成文本日志。结合服务器日志和CO返回信息才能定位到具体是哪个字段、哪条规则出了问题。另一个笨但有效的办法是“逐个字段对齐法”先在U8界面手工录入一张相同的请购单能成功说明数据本身没问题再用代码逐字段赋值保存一次。如果代码版失败而界面版成功就逐项对比字段差异通常能很快锁定问题字段。5.2 审核权限与操作员身份CO接口的登录操作员身份直接决定了接口能做什么。测试环境最容易犯的错是用admin角色登录一切都顺。一到生产环境换了普通操作员审核接口立刻就“没权限”。这里要提前确认操作员是否已分配采购请购单的录入、审核、弃审权限。是否需要数据权限如只允许本部门的请购单。是否启用了工作流审批如果启用审核接口的行为可能不符合预期。我的建议是生产环境单独创建一个“接口操作员”账号分配最小必要权限并把这个账号的权限清单写进交接文档。这样既有安全边界也方便后续排查。5.3 单据号生成策略CO新增保存时单据号一般由U8自动生成不需要手动指定。但有部分现场配置了“手工编号”此时VoucherCode单据编号字段必须显式赋值否则保存失败。这个坑隐蔽性很强因为默认配置下你根本不需要填单据号换了环境就出问题。接入新客户环境时第一件事就是确认编码规则是自动还是手工。5.4 连接串与账套串线的风险接口服务如果同时管理多个账套一定要把UserContext和连接串做成和请求隔离的不要用静态变量保存。因为LoginProxy是多账套登录的多个线程同时操作不同账套时如果共用一个静态UserContext轻则串数据重则直接报错。我在实际项目里用的是ThreadLocal或者请求作用域方式确保一个请求链路只对应一个登录上下文从根上避免账套串线。5.5 批量操作时别每个请求都重新登录有一个性能优化点值得单独说CO登录本身是有开销的如果每次请购单请求都重新Login接口吞吐量会很难看。建议做登录上下文的缓存比如在同一接口进程内维护一个有效期内的LoginProxy实例过期后重新登录。但要小心跨线程并发缓存对象要做好加锁或使用线程安全容器。6. 源码结构与复用建议最后聊一下这套源码怎么组织方便复用。我习惯把整个接口工程拆成三层接口入口层接受第三方系统的HTTP/WebService请求负责鉴权、参数校验、日志记录。U8服务层封装登录、上下文管理、以及上一章那四类动作的具体实现。数据回执层把U8返回的单据号、状态、错误信息转换成统一格式回传给第三方。这样的分层有好处如果以后要从采购请购单扩展到采购订单、到货单只需要在U8服务层添加新的业务对象封装接口入口层和数据回执层几乎不用动。代码层面的公共工具类至少包含这几个登录管理类负责Login、GetUserContext、GetConnectionString。单据操作基类封装SysContext赋值、ClsBusiModel初始化、Save/Freeze的通用流程。异常翻译类把U8抛出的异常翻译成业务提示。日志类记录每次接口请求的入参、出参、耗时、错误堆栈。我个人体会是CO接口开发最大的成本不在“写动作”而在“理解现场配置”。不同客户环境的基础档案、审批流、库存选项、单据模板都不一样所以接口代码一定要做成配置驱动而不是硬编码。把常用的必填字段、单据类型、操作员信息放到配置中心后续换环境只需要改配置不用改代码省下来的运维时间相当可观。如果你正在做类似项目先从新增和审核这两个动作入手跑通了再补修改和删除整体风险会小很多。本文还有配套的精品资源点击获取