拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

NC65 API开发实战:Java调用Facade的正确姿势

NC65 API开发实战:Java调用Facade的正确姿势 简介本资源是一份面向用友NC65平台初学者的开发API实战指南聚焦日常开发高频场景帮助开发者快速掌握核心接口调用与代码实现。内容系统梳理了18类典型API应用涵盖表体选中行/列获取、界面默认值设置、表单执行方法配置、报表合计行显示、UI小数位控制、字段编辑状态管理、查询条件打印、提示框弹出、查询面板值提取、时间比较、编辑公式设定、缓冲数据清空、查询对话框默认SQL设置、单据类继承关系、界面元素显隐控制、按钮状态动态绑定、UI工厂自定义按钮等关键能力并附完整Java代码示例。资源为单个PDF文件共193KB结构清晰、即查即用适合NC65二次开发入门者快速上手与日常查阅。目前已有579人学习下载内容覆盖从基础控件操作到业务逻辑编排的完整链路是提升开发效率与规范编码实践的实用参考材料。1. NC65开发常见API内含代码 适合新手不是调接口是进金蝶黑匣子的钥匙你在NC65系统里改个单据状态、查个合同联查数据、导出一张凭证清单——这些操作背后90%以上不是点菜单、拖字段就能完成的。真实场景是财务要自动归集合同履约进度供应链要实时同步采购订单到WMS运维要批量清理测试账套里的冗余基础资料……这时候你发现NC65客户端界面根本没提供按钮UAP平台里翻遍“服务管理”也找不到对应服务名最后卡在“怎么让后台Java服务吐出我要的数据”上。这就是NC65开发中API的真实定位它不是RESTful风格的开放接口而是金蝶UAP平台内部服务层Service Layer暴露的标准Java方法调用入口必须走UAP容器上下文、带租户/组织/用户三重校验、依赖NC65内置的元数据模型和业务对象BO体系。新手常误以为“写个HTTP请求就能调”结果连登录态都过不去老手则容易陷入“直接调底层DAO”的玄学陷阱导致事务不一致、缓存失效、升级后大面积报错。本文只讲能跑通、能复用、能上线的API用法——所有代码均基于NC65 V7.7 SP2主流生产版本覆盖合同联查、单据查询、基础资料操作三大高频场景每段代码附带UAP容器启动验证方式、参数边界说明、以及我踩过的血泪坑。适合刚接手NC65二次开发的Java工程师、从其他ERP转岗的实施顾问以及需要对接NC65做外围系统集成的Python/Node.js开发者需通过Java桥接层。2. 理清NC65 API本质为什么不能当普通HTTP接口用NC65的API不是Web API而是UAP平台Service Bus暴露的本地Java服务方法。它的调用链路是客户端 → UAP容器TomcatSpring→ Service Bean → BO/DAO → 数据库。理解这点才能避开80%的翻车现场。2.1 NC65 API的三层结构从BO到Service再到FacadeNC65的业务逻辑严格分层API入口只在Facade层BO层Business Object定义业务实体结构如ContractHead合同主表、PurchaseOrder采购订单继承自AbstractBill自带getPkid()、getCreator()等元数据方法。Service层处理核心业务逻辑如IContractService接口方法签名形如public ContractHead getContractByPk(String pk)但不对外暴露。Facade层唯一可被外部调用的入口类名以Facade结尾如ContractFacade方法加了Transactional和SecurityCheck注解且必须通过UAP的ServiceLocator获取实例。提示不要试图反射调用Service层方法NC65的Service Bean默认scope为prototype且依赖UAP容器注入的Context、Session、TenantContext。脱离容器直接new对象会抛NullPointerException或TenantNotSetException。2.2 调用NC65 API的唯一合法路径UAP ServiceLocatorNC65禁止直连数据库或绕过安全校验所有API调用必须通过UAP提供的ServiceLocator获取Facade实例。这是硬性规定也是后续所有代码的基础。// 正确通过UAP容器获取Facade实例必须在UAP Web Context中执行 import com.kingdee.bos.framework.ServiceLocator; import com.kingdee.bos.util.BOSObject; // 获取ContractFacade实例注意类名必须全限定且与UAP服务注册名一致 ContractFacade contractFacade (ContractFacade) ServiceLocator.getService(contractFacade); // 调用方法 ContractHead contract contractFacade.getContractByPk(1001ZZ100000000XXXXX);关键参数说明contractFacadeUAP服务注册名非类名。在NC65后台【系统管理】→【服务管理】中可查格式为{模块名}Facade如合同模块是contractFacade采购模块是purchaseFacade。ServiceLocator.getService()返回的是UAP容器托管的代理对象自动处理事务、日志、权限校验。返回类型必须强转为具体Facade接口不能用Object接收——否则编译通过但运行时报ClassCastException。2.3 新手最常忽略的上下文租户、组织、用户三重绑定NC65是多租户架构API调用前必须显式设置当前上下文否则报TenantNotSetException或查不到数据。这步不能省且顺序固定import com.kingdee.bos.context.Context; import com.kingdee.bos.context.ContextFactory; import com.kingdee.bos.context.UserContext; // 1. 创建Context必须 Context context ContextFactory.createContext(); // 2. 设置租户ID取自NC65后台【系统管理】→【租户管理】中的租户编码如001 context.setTenantId(001); // 3. 设置组织ID取自【基础资料】→【组织机构】中的组织编码如ORG001 context.setOrgUnitId(ORG001); // 4. 设置用户ID取自【系统管理】→【用户管理】中的用户编码如admin UserContext userContext new UserContext(); userContext.setUserId(admin); context.setUserContext(userContext); // 将Context绑定到当前线程关键 Context.setCurrentContext(context);参数边界说明tenantId字符串长度≤20不能含空格或特殊字符必须与NC65租户编码完全一致区分大小写。orgUnitId同理必须是已启用的组织编码且该组织属于当前租户。userId必须是已分配角色的用户且该用户对目标单据有查询权限如查合同需有“合同管理”角色。Context.setCurrentContext()必须在调用Facade方法之前执行且每个线程只能绑定一个Context。3. 合同联查场景实战nc65 联查合同的3种API调用方式“nc65 联查合同”是搜索量最高的长尾词本质是查合同主表ContractHead关联的明细行ContractBody、附件Attachment、审批流WorkflowInstance等。NC65不提供SQL视图必须用API组合调用。3.1 方式一通过ContractFacade.getContractByPk()获取主表明细这是最常用、最稳妥的方式适用于已知合同主键pk的场景。// 前置已设置Context见2.3节 ContractFacade contractFacade (ContractFacade) ServiceLocator.getService(contractFacade); // 根据主键查询合同含明细行 ContractHead contract contractFacade.getContractByPk(1001ZZ100000000XXXXX); // 获取明细行列表ContractBody继承自AbstractBill支持getChildren() ListContractBody bodies contract.getChildren(ContractBody.class); // 遍历明细行 for (ContractBody body : bodies) { System.out.println(物料编码 body.getMaterialCode()); System.out.println(数量 body.getQty()); System.out.println(单价 body.getPrice()); }关键逻辑说明getContractByPk()返回的ContractHead对象已预加载明细行lazy load机制调用getChildren()无需额外SQL查询。ContractBody.class是泛型参数告诉框架要加载哪种子对象。NC65约定主表BO的子表BO类名主表名Body如ContractHead→ContractBodyPurchaseOrder→PurchaseOrderBody。若需查附件调用contract.getAttachments()若需查审批流调用contract.getWorkflowInstance()。3.2 方式二通过QueryCondition动态查询合同列表当不知道主键需按条件查合同如“2024年签订的采购类合同”用QueryCondition构造查询条件。import com.kingdee.bos.dao.query.QueryCondition; import com.kingdee.bos.dao.query.QueryResult; // 构造查询条件 QueryCondition condition new QueryCondition(); condition.addCondition(billstatus, , C); // C已提交Z已作废 condition.addCondition(contractdate, , 2024-01-01); condition.addCondition(contractdate, , 2024-12-31); condition.addCondition(contracttype, , CG); // CG采购合同 // 查询合同主表返回ContractHead数组 QueryResult result contractFacade.queryContract(condition, null); ContractHead[] contracts (ContractHead[]) result.getData(); // 批量获取明细行避免循环中多次调用getChildren性能差 for (ContractHead contract : contracts) { // 注意此处需手动加载明细因queryContract不预加载 contract.loadChildren(ContractBody.class); // 显式触发加载 }参数说明addCondition()第一个参数是字段名必须与BO定义的属性名一致如contractdate不是CONTRACT_DATE大小写敏感。字段值类型需匹配日期用String格式yyyy-MM-dd数字用String或BigDecimal枚举用编码如billstatusC。queryContract()第二个参数为排序字段数组如new String[]{contractdate DESC, pkid ASC}传null则无序。loadChildren()必须显式调用否则getChildren()返回空列表——这是新手最大坑点。3.3 方式三跨模块联查——合同采购订单入库单“nc65 联查合同”常需穿透到下游单据如查某合同关联的所有采购订单PO再查每个PO下的入库单GRN。NC65不支持SQL JOIN必须分步调用。// 步骤1查合同 ContractHead contract contractFacade.getContractByPk(1001ZZ100000000XXXXX); // 步骤2查合同关联的采购订单通过合同上的relatebillid字段关联 PurchaseFacade purchaseFacade (PurchaseFacade) ServiceLocator.getService(purchaseFacade); QueryCondition poCondition new QueryCondition(); poCondition.addCondition(relatebillid, , contract.getPkid()); // 合同主键作为关联字段 QueryResult poResult purchaseFacade.queryPurchaseOrder(poCondition, null); PurchaseOrder[] pos (PurchaseOrder[]) poResult.getData(); // 步骤3查每个PO关联的入库单通过PO上的relatebillid关联 StockFacade stockFacade (StockFacade) ServiceLocator.getService(stockFacade); for (PurchaseOrder po : pos) { QueryCondition grnCondition new QueryCondition(); grnCondition.addCondition(relatebillid, , po.getPkid()); QueryResult grnResult stockFacade.queryStockIn(grnCondition, null); StockIn[] grns (StockIn[]) grnResult.getData(); System.out.println(合同 contract.getNumber() →PO po.getNumber() →入库单 grns.length 张); }关键设计点关联字段名统一为relatebillidNC65约定值为上游单据的pkid。每个模块的Facade必须单独获取purchaseFacade、stockFacade不能复用contractFacade。分步查询虽慢但稳定。若需性能优化可在UAP中编写自定义服务Custom Service用原生SQL一次查出但需通过UAP部署包发布不适合新手。4. 常见问题排查NC65 API调用失败的5个血泪坑NC65 API报错信息极其简陋常出现NullPointerException、IllegalArgumentException、BOSException等泛化异常。以下是我在32个NC65项目中总结的5个高频坑按“现象→原因→解决”结构整理每条都配真实日志片段。4.1 现象java.lang.NullPointerExceptionatContractFacade.getContractByPk()原因未调用Context.setCurrentContext(context)或context对象为空。UAP容器检测到无上下文直接返回null而非抛异常。解决在调用Facade前增加空值校验if (Context.getCurrentContext() null) { throw new RuntimeException(UAP Context未设置请先执行Context.setCurrentContext()); }4.2 现象com.kingdee.bos.BOSException: Tenant not set原因context.setTenantId()传入的租户编码不存在或该租户未启用。NC65后台【租户管理】中租户状态为“停用”时API拒绝访问。解决登录NC65后台确认租户状态为“启用”且编码与代码中完全一致建议复制粘贴避免手输空格。4.3 现象java.lang.ClassCastException: com.kingdee.bos.util.BOSObject cannot be cast to com.kingdee.eas.contract.ContractFacade原因ServiceLocator.getService(xxx)返回的服务名错误。如把contractFacade写成ContractFacade首字母大写或contractfacade全小写。UAP服务注册名严格区分大小写。解决在NC65后台【系统管理】→【服务管理】中搜索关键词确认服务名全称通常为小写驼峰。4.4 现象com.kingdee.bos.dao.exception.DataAccessException: ORA-00942: table or view does not exist原因BO类名与数据库表名不匹配。如ContractHead对应表T_CONTRACT_HEAD但代码中误用ContractHeaderNC65无此BO。解决查阅NC65开发手册附录《BO与数据库表映射关系》或在UAP Studio中打开BO设计器右键BO → “查看元数据” → “物理表名”。4.5 现象查询返回空数组但NC65客户端能查到数据原因QueryCondition字段名错误。如合同日期字段是contractdate但代码中写成contract_date或CONTRACTDATE。NC65 BO属性名全部小写驼峰不支持下划线。解决在UAP Studio中打开ContractHeadBO查看属性列表复制准确的属性名。5. 新手避坑指南从Java到Python/Node.js的API桥接方案很多新手实际需求是用Python写脚本查NC65数据或用Node.js做前端对接。但NC65 API纯Java无法直接HTTP调用。我的经验是绝不推荐用Jython或JNI硬桥接而应建轻量级Java网关。5.1 推荐方案用Spring Boot封装NC65 API为REST接口这是最稳、最易维护的方式。新建一个Spring Boot项目引入NC65 UAP客户端jar包bos-core.jar,eas-contract.jar等将NC65 API包装成标准REST接口。// Spring Boot Controller示例 RestController RequestMapping(/api/contract) public class ContractController { PostMapping(/by-pk) public ResponseEntityMapString, Object getContractByPk(RequestBody MapString, String request) { try { // 1. 构建Context从request中取tenant/org/user Context context buildContext(request); Context.setCurrentContext(context); // 2. 调用NC65 API ContractFacade facade (ContractFacade) ServiceLocator.getService(contractFacade); ContractHead contract facade.getContractByPk(request.get(pk)); // 3. 转JSON用Jackson排除NC65私有字段 ObjectMapper mapper new ObjectMapper(); mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); String json mapper.writeValueAsString(contract); return ResponseEntity.ok(Map.of(data, json)); } catch (Exception e) { return ResponseEntity.status(500).body(Map.of(error, e.getMessage())); } } private Context buildContext(MapString, String request) { Context context ContextFactory.createContext(); context.setTenantId(request.get(tenant)); context.setOrgUnitId(request.get(org)); context.setUserId(request.get(user)); return context; } }部署要点将NC65 UAP服务器的/webapps/uap/WEB-INF/lib/下所有jar包复制到Spring Boot项目的lib/目录并在pom.xml中用scopesystem/scope引用。启动时添加JVM参数-Dkingdee.bos.home/path/to/nc65/uap指向NC65 UAP安装目录。接口地址如http://localhost:8080/api/contract/by-pkPython端用requests.post()调用传JSONimport requests data {pk: 1001ZZ100000000XXXXX, tenant: 001, org: ORG001, user: admin} resp requests.post(http://localhost:8080/api/contract/by-pk, jsondata) print(resp.json())5.2 绝对禁止的方案用Python直接调UAP HTTP端口网上有教程教用requests.get(http://nc65-server:8080/k3cloud/app/xxx)这是严重错误。NC65 UAP的HTTP端口仅用于Web前端后端Service不暴露HTTP接口。强行调用只会返回404或500且可能触发安全审计。5.3 进阶技巧用UAP自定义服务替代硬编码Facade当API调用频繁如每秒10次以上硬编码ServiceLocator.getService()会有性能损耗。UAP提供Service注解可将Facade注入Spring容器Service public class ContractService { Autowired private ContractFacade contractFacade; // UAP自动注入 public ContractHead getContract(String pk) { return contractFacade.getContractByPk(pk); } }然后在Controller中Autowired ContractService避免每次调用都查ServiceLocator。但需确保UAP与Spring Boot的Bean生命周期一致否则contractFacade为null——我的血泪经验是只在UAP容器内用Autowired独立Spring Boot项目必须用ServiceLocator。希望帮到你。本文还有配套的精品资源点击获取
返回列表