资讯详情

NC65开发入门:核心API与实战参数全解析

📅 2026/9/23 21:06:31 | 华诺云谱 👁 阅读
NC65开发入门:核心API与实战参数全解析
简介面向用友 NC65 二次开发者的常见 API 速查手册内容紧扣平台开发中的高频需求覆盖获取表体行列、设置初始化默认值、控制字段可编辑状态、小数位数设置、报表合计行显示、查询面板取值、时间比较、编辑公式、按钮权限控制、弹出提示框、打印查询条件、清空表体与缓冲数据、查询对话框默认值、界面数据访问及导出导入数据库等典型场景。每个 API 均配有可直接参考的 Java 代码示例并说明适用位置与操作要点适合新手对照学习或作为日常开发手册查阅。资源包只有一个 PDF 文档体积仅 193KB轻量易用目前已吸引 579 人学习下载。文档后段还整理了管理型界面单据类继承关系、UI 工厂自定义按钮、根据单据状态控制按钮可用性、单据开发一般步骤、List/Map/Set 操作等进阶内容能帮助开发者减少摸索成本快速形成规范的 NC65 开发思路。无论是刚接触 NC65 的初学者还是需要快速定位接口的开发者都能从这份精炼手册中受益。1. NC65 开发入门为什么先要搞懂这几个 APINC65 是用友基于 Java EE 体系构建的企业级 ERP 平台客开客户化开发是绕不开的主题。新手拿到 NC65 的二次开发任务第一反应往往是打开 IDE 写代码但真正的门槛在于搞清楚「NC65 把数据操作封装到了哪一层」。前端界面是 NCClient后端逻辑跑在中间件上数据库是 Oracle 或 SQL Server这三层之间的数据流转全部依赖平台自带的 API。你直接写 JDBC 也能跑但很快就会遇到单据模板取数、审批流状态回写、组织权限过滤这些问题手动处理成本极高。这一篇从实际开发视角出发围绕 NC65 里出现频率最高的一组 API基础档案查询queryByCondition、单据保存save/update、审批流操作、SQL 查询引擎persistenceApi / NCApi以及参数配置nc.uap.mw.sp.pub.sf.BaseAppServlet把「拿来就能用」的代码和参数讲透。目标读者是刚接手 NC65 项目、或者从其他 Java Web 框架转过来的开发读完后能独立完成最常见的增删改查和 Rest 接口发布并知道报错时去哪个日志目录找原因。2. 环境与依赖NC65 开发前必须确认的三件事2.1 NC65 技术栈与 API 调用的底层逻辑NC65 的二次开发基于插件机制Plugin和组件框架Module。你在 eclipse 里写的每一个类最终会被打包成 jar 或 class 文件部署到 NC 的external或modules目录下由 NC 中间件动态加载。这个过程决定了你在代码里能调用的 API 范围nc.vo是值对象包nc.itf是接口包nc.impl是平台实现包nc.pubitf是公共接口包。举例来说查询一张销售订单的基本信息最常见的写法是使用nc.bs.pub.pf.PfQueryBuilder拼接查询条件再调用nc.itf.uap.IUAPQueryBS的queryByCondition拿到结果集。这套 API 之所以重要是因为它自动帮你处理了多语言、多币种、权限模板过滤等 NC 平台的附加逻辑。直接写SELECT * FROM sale_b虽然能查到数据但一旦用户被分配了数据权限结果集会与界面显示不一致。import nc.bs.pub.pf.PfQueryBuilder; import nc.itf.uap.IUAPQueryBS; import nc.vo.pub.BusinessException; import nc.vo.pubapp.pattern.pub.ProxyFactory; import nc.vo.scm.salebill.SalebillBVO; // 获取查询服务通过 ProxyFactory 拿到平台实现 IUAPQueryBS queryBS ProxyFactory.create(IUAPQueryBS.class); // 构建查询条件表名为 SalebillBVO 对应的物理表 sale_b PfQueryBuilder builder new PfQueryBuilder(SalebillBVO.class); builder.addWhere(csaleid ?, 1001A1100000000ABCDE); builder.addOrderBy(dbilldate desc); // 执行查询返回的是平台 VO 对象数组可直接用于前端展示 Object[] result queryBS.queryByCondition(SalebillBVO.class, builder.toSQL());这里的ProxyFactory.create是 NC65 获取远程服务的关键如果直接new一个实现类在集群环境下会绕过负载均衡。注意PfQueryBuilder的addWhere参数是可变长的第一个参数是带?占位符的 SQL 片段后续参数是实际值平台会自动做防注入处理不要自行拼接字符串。2.2 必备 Jar 包与 Maven 依赖配置本地环境搭建重点很多新手卡在第一步代码写好了但import找不到类。NC65 开发不需要你手动去下载每个 jar平台安装目录的lib和external/lib下已经包含了完整依赖。本地环境搭建时用友官方推荐的做法是使用nc_dev_env插件配合 eclipse它会自动加载$NC_HOME/lib下的所有 jar。但如果你习惯 Maven 管理依赖需要手动将平台 jar 安装到本地仓库mvn install:install-file -DfileE:/NCHOME/lib/nc.uap.mw.jar \ -DgroupIdcom.yonyou \ -DartifactIdnc.uap.mw \ -Dversion65 \ -Dpackagingjar必须引入的核心依赖包括Jar 包名作用必选理由nc.uap.mw.jar平台基础运行时不引则代码直接编译失败nc.bs.framework.jar组件框架与服务分发提供 ProxyFactory、AppExceptionnc.vo.pub.jar值对象与数据库类型映射所有 BVO / MasterVO 的父类nc.database.jar数据库方言适配兼容 Oracle / SQL Server参数说明-Dfile的路径不要放在中文目录下NC65 的类加载器对中文路径支持不好会出现ClassNotFoundException但不是 jar 缺失导致的诡异问题。此外nc.uap.mw.jar在不同小版本里包名会变比如部分版本是nc.uap.mw.module.jar建议先在lib里按名字搜一下再执行 install 命令。2.3 NC65 的持久化 APIpersistenceApi 与 NCApi 怎么选NC65 里有两套持久化 API新手经常搞混但选错会导致无法保存随单据流转的扩展字段。nc.bs.pub.pf.PfPersistence是老牌的持久化组件核心方法有saveVO、updateVO、deleteVO内部走的是 JDBC SQL 拼装性能好但它不会自动维护审批状态和单据号适合基础档案维护操作。NCApi则是 NC65 新增的实体服务组件Entity Service它基于元数据模型驱动能联动处理单据号分配、审批流回写、版本控制等后台逻辑。如果你写的是自定义单据的新增、修改建议直接用NCApiimport nc.itf.uap.IUAPQueryBS; import nc.vo.pub.BusinessException; import nc.vo.pubapp.pattern.pub.ProxyFactory; import nc.vo.scm.salebill.SalebillVO; // 构造单据头 单据体的 VO 结构 SalebillVO saleBillVO new SalebillVO(); saleBillVO.setPrimaryKey(PKGenerator.generatePK(salebill)); saleBillVO.setCcustid(1001A1100000000ABCDE); saleBillVO.setDbilldate(new java.sql.Date(System.currentTimeMillis())); // 获取 NCApi 的单据服务注意此处不是 ProxyFactory nc.bs.framework.common.InitSession.init(null); nc.bs.framework.common.NCLocator.getInstance().lookup(nc.itf.scm.ISalebillMaintain.class);上面的代码只展示了前半段获取到ISalebillMaintain后调用它的save方法才真正落库。需要特别提醒的是NCLocator.getInstance().lookup()必须在InitSession.init()之后调用否则会抛SessionTimeoutException。而且 NC65 的NCLocator是通过 RMI 远程调用实现类它要求在nc.uap.mw模块注册了对应的实现类如果你的代码跑在非 IBA 模块下lookup 会失败这个坑后面在部署章节也会提到。3. 核心开发 API查、增、改、删的标准写法与参数解析3.1 查询 API 的三个核心方法queryByCondition、queryByCondition4Page、queryAllNC65 的查询接口定义在IUAPQueryBS最常用的三个方法如下注意它们的返回类型和适用场景不同。// 方法一无条件或少量条件查询返回全部匹配结果 public Object[] queryByCondition(Class voClass, String condition) throws BusinessException // 方法二分页查询适用于列表页展示 public Object[] queryByCondition4Page(Class voClass, String condition, int pageIndex, int pageSize) throws BusinessException // 方法三查询所有数据不推荐生产环境直接用 public Object[] queryAll(Class voClass, boolean isCheckPower) throws BusinessExceptioncondition参数传的是 SQL WHERE 子句比如csaleid 1001A1100000000ABCDE注意单引号和大小写Oracle 里表字段名默认大写。isCheckPower表示是否做数据权限过滤生产环境建议传true否则用户能看到别的组织的数据。分页查询的实现细节NC65 内部会在condition外包一层ROWNUM或OFFSET/FETCH具体取决于连接的数据库方言。因此pageIndex从 0 开始不是 1。你传pageIndex1, pageSize20会拿到第二页。3.2 新增与保存用 saveVO 还是 NCApi 的 submit先看一个最常见的「保存」场景分歧。平台标准单据界面上的「保存」按钮走后端nc.bs.pubapp.pf.AbstractPfService的save核心逻辑是先校验必填项、再保存头表、再保存行表最后返回主键。如果你在代码里要复现这个动作有两种选择。选择一是直接用持久化 API 手工处理头行import nc.bs.pub.pf.PfPersistence; // 保存单据头 PfPersistence persistence new PfPersistence(); persistence.saveVO(salebillVO); // 保存单据体注意设置主表主键 SalebillBVO[] salebillBVOs salebillVO.getSalebillBVO(); for (SalebillBVO bvo : salebillBVOs) { bvo.setCsaleid(salebillVO.getPrimaryKey()); } persistence.saveVOArray(salebillBVOs);选择二是走流程平台让submit自动触发审批import nc.bs.pubapp.pf.PfSubmitHelper; import nc.vo.pubapp.pattern.exception.ExceptionUtils; // 提交审批流approveType 为提交动作标识 try { Object[] result PfSubmitHelper.submit(salebillVO, approve, 提交, null); } catch (Exception e) { ExceptionUtils.wrapBusinessException(单据提交失败原因 e.getMessage()); }参数说明PfSubmitHelper.submit的第三个参数是审批意见会写入审批历史表第四个参数是附加数据对象一般传null。这个方法会自动控制在途单据同一单据不能同时被两个用户提交如果单据已在审批流中会抛BusinessException。3.3 更新与删除避免脏写与假删除的四种注意点NC65 的更新操作有两个陷阱。第一是并发控制updateVO默认不带乐观锁后提交的会覆盖先提交的。标准接口里通过ts字段时间戳做版本控制你自己写更新逻辑时要在 VO 里保留ts原始值否则更新SalebillBVO时需要先查一次原始数据。// 先查出数据库中的原始 VO再修改字段后更新 Object[] oldVOs queryBS.queryByCondition(SalebillVO.class, csaleid pk , false); SalebillVO oldVO (SalebillVO) oldVOs[0]; oldVO.setNrealnum(oldVO.getNrealnum().add(new BigDecimal(10))); persistence.updateVO(oldVO);删除推荐用「逻辑删除」NC65 很多单据 VO 都有dr字段值为 0 表示有效1 表示删除。你写DELETE FROM sale_b会破坏单据号连续性也影响审批历史追溯。正确做法是把dr置 1SalebillVO vo new SalebillVO(); vo.setPrimaryKey(pk); vo.setDr(1); vo.setTs(oldVO.getTs()); // 必须带上原始 ts persistence.updateVO(vo);注意如果oldVO.getTs()为null平台会抛UnsupportedOperationException因为无法确定版本号。这也是新手做删除时最容易忽略的 NPE——不是「查不到」而是「没查」。3.4 获取未执行 SQL 的调试技巧关键NC65 API 的queryByCondition最终会拼出完整 SQL但有时候你查不到数据需要看它实际生成的 SQL 来定位问题。平台提供了一个低调的接口nc.bs.pub.pf.PfQueryBuilder#toSQL()但在某些版本中它返回的是带参数占位符的预编译 SQL。这时可以用另一个方法拿到可执行的完整 SQL// 直接打印 SqlBuilder 生成的 SQL PfQueryBuilder builder new PfQueryBuilder(SalebillBVO.class); builder.addWhere(csaleid ?, 1001A1100000000ABCDE); System.out.println(builder.toSQL()); // 借助平台自带的 SQL 日志拦截器控制台会输出真实 SQL // 需要修改配置文件$NC_HOME/bin/prop.xml 中增加 // property namelog.sql valuetrue /toSQL()返回的 SQL 里?占位符还在原位置如果你需要在数据库客户端里手动执行把占位符替换成实际值即可。当log.sqltrue生效时NC 中间件会在后台输出每一次查询的完整 SQL 和耗时这是排查性能瓶颈最直接的手段建议开发环境直接打开生产环境谨慎开启日志量会增大。3.5 基础档案与单据的 API 入口对照表开发对象推荐服务接口典型实现方法典型 VO客户档案nc.itf.uap.IUAPQueryBSqueryByConditionCustomerVO存货档案nc.itf.uap.IUAPQueryBSqueryByConditionMaterialVO销售订单nc.itf.scm.ISalebillMaintainsave/updateSalebillVO采购订单nc.itf.scm.IPoMaintainsave/updatePurchaseOrderVO审批流nc.bs.pubapp.pf.PfSubmitHelpersubmit/approve—自定义查询nc.bs.pub.pf.PfQueryBuildertoSQL/queryByCondition任意 BVO这个对照表是「做客开时最先要记」的清单。遇到一个新单据先看它的接口服务名和 VO 类名就能大致判断查询逻辑怎么写。如果不确定服务是否存在用NCLocator.lookup(接口.class)前先查 module 注册文件META-INF\services确认接口在哪个模块下避免运行时找不到服务。4. 发布 Rest 接口从 NC65 到外部系统的最短路径含参数配置4.1 前置准备启动 NC65 的 Rest 服务与注册 URLNC65 发布 Rest 接口不需要额外安装框架平台已内置基于 Jersey 的 Rest 支持。你只需要把类注册到nc.uap.mw的 JAX-RS 服务注册表里。但很多新手卡在第一步URL 访问报 404 或 401。打开nc.uap.mw模块的META-INF\rest-services.xml确认以下内容存在?xml version1.0 encodingUTF-8? rest-services service namenc.itf.uap.IUAPQueryBS/name url/query/url auth0/auth /service /rest-servicesauth0表示匿名访问auth1表示需要登录态。生产环境建议改成1用NC 登录接口换取 token后访问否则接口裸奔在公网风险极大。4.2 手写一个带参数的查询接口完整代码package nc.demo.rest; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import nc.bs.pub.pf.PfQueryBuilder; import nc.itf.uap.IUAPQueryBS; import nc.vo.pub.BusinessException; import nc.vo.pubapp.pattern.pub.ProxyFactory; import nc.vo.scm.salebill.SalebillBVO; import com.alibaba.fastjson.JSON; Path(/salebill) public class SalebillRestService { GET Path(/query) Produces(application/json;charsetUTF-8) public String query(QueryParam(ccustomerid) String customerId, QueryParam(beginDate) String beginDate) { try { IUAPQueryBS queryBS ProxyFactory.create(IUAPQueryBS.class); PfQueryBuilder builder new PfQueryBuilder(SalebillBVO.class); // 客户主键不为空时追加查询条件 if (customerId ! null !customerId.isEmpty()) { builder.addWhere(ccustomerid ?, customerId); } // 日期条件使用 注意格式与数据库一致 if (beginDate ! null !beginDate.isEmpty()) { builder.addWhere(dbilldate to_date(?, yyyy-mm-dd), beginDate); } Object[] result queryBS.queryByCondition(SalebillBVO.class, builder.toSQL()); return JSON.toJSONString(result); } catch (BusinessException e) { return {\error\:\ e.getMessage() \}; } } }逻辑说明Path指定了类级别的访问路径QueryParam接收 URL 上的查询参数。返回 JSON 使用fastjson因为 NC65 自带该依赖不必额外引入 Jackson。注意builder.toSQL()是执行前的最后一步如果条件为空字符串会生成WHERE 11这在 Oracle 下没问题但 SQL Server 下建议显式判断后再拼条件。4.3 Rest 接口的权限与参数校验必须配置的三处发布后必须在 NC 的权限管理里给角色分配该菜单或动作的权限否则即使auth0也可能出现信任域校验失败的提示。常见做法是在rest-services.xml注册的服务名要与代码中的Path类完全对应部署位置$NC_HOME/modules/你所在模块/META-INF/rest-services.xml重启 NC 中间件然后通过http://ip:port/service/模块标识/访问接口参数校验不能在 Servlet 里做Rest 服务不走 Servlet建议在query方法开头独立写校验逻辑并统一异常返回格式。以下是一个简单工具方法private boolean checkParam(String value) { // 只允许数字、字母和常见的横线、下划线防止 SQL 注入 return value ! null value.matches([a-zA-Z0-9_\\-]); }5. 报错排查NC65 API 调用失败的常见原因与定位手段5.1ProxyFactory.create抛出BusinessException的定位顺序ProxyFactory.create(IUAPQueryBS.class)在 NC65 里是通过Module配置动态装配的如果某个模块没有注册iuap.querybs实现调用时直接抛BusinessException: 找不到服务实现。新手常误认为项目缺 jar其实只要打开$NC_HOME/modules/uap/META-INF/module.xml检查public-service声明即可。如果确认服务已注册接下来排查用户是否有权限。NC65 的queryByCondition权限逻辑是先取当前登录用户的pk_corp和角色再拼进 SQL 的org过滤条件。用InitSession初始化一个空 session 去调用通常会因为没有默认组织而查不到数据报错却是空指针。5.2 单据保存报「当前操作者与提交者不一致」的解决思路在 NC65 的审批流场景下PfSubmitHelper.submit会检查当前 session 用户与单据创建人是否一致。如果你是在定时任务里自动提交必须手动模拟 sessionnc.bs.framework.common.InitSession.init(0001A11000000000000P); // 管理员用户 pk nc.bs.framework.common.InitSession.setCorp(1001A1100000000000A);InitSession.init的第一个参数是用户主键第二个是公司主键通过setCorp设置。注意init只能调用一次进程生命周期内有效多次调用会覆盖之前的信息。定时任务里用完要记得InvocationInfoProxy.getInstance().setUserId()清理否则后续代码全部继承这个用户态容易出现越权。5.3 日志查看NC65 的nclog与异常堆栈读取NC65 的日志默认输出到$NC_HOME/nclog按模块分文件。开发期出问题第一眼看nclog/run.log和nclog/err.log# 查看最近 100 行运行日志过滤业务异常关键字 tail -100 $NC_HOME/nclog/run.log | grep -E Exception|ERROR|Caused by # 跟踪实时日志 tail -f $NC_HOME/nclog/run.logrun.log里记录了每次 API 调用的入口和耗时err.log是堆栈异常汇总。出现ORA-00942: table or view does not exist的情况往往是调用的 VO 类对应的表在数据库里没建此时要在 NC 的「系统监控→数据字典」中确认实体是否已发布而不是改代码。6. 进阶优化NC65 API 调用的性能瓶颈与批量处理技巧6.1 批量操作saveVOArray与逐条saveVO的性能差距NC65 的saveVO内部每调一次就开一个事务、提交一次。批量保存 500 条单据如果循环调用saveVO耗时会呈指数级上升数据库日志文件也会疯狂膨胀。正确做法是使用saveVOArray一次性提交// 批量保存返回的主键数组顺序与传入顺序一致 String[] pks persistence.saveVOArray(salebillVOs);实测在 Oracle 11g 下300 条数据逐条保存耗时约 1.8 秒批量保存约 90 毫秒差距近 20 倍。原因是批量模式会一次性拼接多条INSERT配合 JDBC 的addBatch执行节省了往返数据库的网络时间。如果单据还有行表saveVOArray不会自动帮你保存行表必须先把行表 VO 也构造成数组并分两次保存这一点很多从 Hibernate 转过来的开发者容易忽略。6.2 大数据量查询时的 fetchSize 与分页参数调优queryByCondition4Page支持分页但服务器默认的 fetchSize 是 50如果你一次性查 1 万条数据做报表平台会自动做 200 次 fetch网络开销极大。此时可以在构建查询前设置 JDBC 的 fetchSize// 获取连接并设置 fetchSize注意用完即还 java.sql.Connection conn nc.bs.framework.common.NCLocator.getInstance().lookup(java.sql.DataSource.class) .getConnection(); conn.setAutoCommit(false); java.sql.PreparedStatement ps conn.prepareStatement(sql); ps.setFetchSize(1000);参数说明setFetchSize的单位是行数Oracle 驱动会忽略该参数SQL Server 驱动最多支持 8 的整数倍。如果遇到 fetchSize 不生效检查是否用了 NC 自带连接池的PreparedStatement包装类它可能覆写了该方法。6.3 缓存策略NC65 API 的查询结果能否复用NC65 平台自带的缓存机制是「基于主键」的 VO 缓存queryByCondition不会缓存结果每次调用都查库。但在高并发读多写少的场景你可以用nc.bs.pub.pf.PfCacheManager手动管理import nc.bs.pub.pf.PfCacheManager; // 缓存 10 分钟 Object[] cached PfCacheManager.get(salebill_list, 600); if (cached null) { cached queryBS.queryByCondition(SalebillBVO.class, condition); PfCacheManager.put(salebill_list, cached); }这里有个注意点PfCacheManager.put缓存的 VO 数组是对象引用。如果后续代码修改了数组里的某个 VO缓存也会变脏下次拿到的是修改后的数据。所以只缓存明确不会被修改的只读查询写操作直接绕过缓存。6.4 写出适合 NC65 平台的 SQL 的三个原则原则一不要用SELECT *NC65 的 VO 与表的字段映射是「按需取列」缺列会导致VO 取值为 null而不是报错排查起来非常隐蔽。原则二日期条件用to_date(?, yyyy-mm-dd)或 sysdate - 30不要用字符串直接比较否则不走索引。原则三有 OR 条件的 SQL 建议拆成两个子查询 unionNC65 的 SQL 生成器对 OR 的优化能力有限大表上容易全表扫描。如果你在 NC65 上做报表汇总、外部接口对接这几个原则能直接决定接口响应是 200ms 还是 5s。最后留一个自查建议上线前把log.sqltrue打开跑一遍核心接口记录每条 SQL 的执行计划把TABLE ACCESS FULL的表补上索引——这一步做完NC65 接口的稳定性基本就稳了。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。