1. 项目背景与核心需求
在企业管理软件领域,用友NC系统作为国内领先的ERP解决方案,其BIP(Business Intelligence Platform)模块承担着关键的业务智能分析功能。近期我们在实施某制造企业项目时,客户提出需要在现有BIP报表界面增加自定义操作按钮的需求,这引发了我们对NC系统二次开发技术的深度探索。
这个需求源于客户的实际业务场景:财务部门每月需要从BIP导出的报表数据中手动筛选特定科目,再导入到本地Excel模板生成分析图表。这种重复性操作不仅效率低下,还容易因人为操作失误导致数据偏差。理想解决方案是在BIP报表界面直接添加"生成分析图表"按钮,实现数据的一键式处理。
2. 技术方案设计
2.1 系统架构分析
用友NC系统采用典型的J2EE架构,BIP模块基于Struts2+Spring框架实现。通过分析系统日志和调试信息,我们发现BIP的按钮控制逻辑主要集中在以下几个关键点:
- 前端渲染层:/WEB-INF/pages/biplatform目录下的JSP文件
- 控制层:com.yonyou.bip.controller包中的Action类
- 权限校验:通过拦截器com.yonyou.bip.auth.BIPAuthInterceptor实现
2.2 按钮添加技术路线
经过技术验证,我们确定了三种可行的实现方案:
| 方案类型 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 前端注入 | 通过JavaScript动态插入按钮DOM | 开发周期短,无需重启服务 | 可能被浏览器安全策略拦截 | 临时性需求 |
| 模板修改 | 直接修改BIP的JSP模板文件 | 稳定性高,原生集成 | 需要系统升级维护 | 长期使用需求 |
| 插件开发 | 实现BIP的扩展接口 | 系统耦合度低 | 开发复杂度高 | 企业级扩展 |
基于客户需求的持久性和稳定性要求,我们最终选择了模板修改方案。具体实施路径如下:
- 定位报表展示模板:/WEB-INF/pages/biplatform/report/report_view.jsp
- 在操作按钮区域(class="operate-area")添加自定义按钮代码
- 配套开发按钮点击事件处理器
3. 核心实现细节
3.1 前端代码改造
在report_view.jsp文件中,我们找到按钮容器div(约第120行),插入以下代码片段:
<div class="btn-group"> <button id="customAnalyzeBtn" class="btn btn-primary" onclick="generateAnalysisChart()"> <i class="icon-bar-chart"></i> 生成分析图表 </button> </div>同时需要在页面底部添加对应的JavaScript函数:
function generateAnalysisChart() { // 获取当前报表数据 var reportData = BIP.getCurrentReportData(); // 校验数据有效性 if(!reportData || reportData.length === 0) { BIP.alert("请先加载有效报表数据"); return; } // 显示处理进度 BIP.showLoading("正在生成分析图表..."); // 异步提交处理请求 $.ajax({ url: "/bip/custom/analyze", type: "POST", data: JSON.stringify(reportData), contentType: "application/json", success: function(result) { BIP.hideLoading(); if(result.success) { window.open(result.downloadUrl); } else { BIP.alert(result.message); } } }); }3.2 后端接口开发
新建CustomAnalyzeController处理前端请求:
@Controller @RequestMapping("/bip/custom") public class CustomAnalyzeController { @Autowired private ReportDataService reportDataService; @ResponseBody @RequestMapping(value = "/analyze", method = RequestMethod.POST) public Map<String, Object> analyzeReportData( @RequestBody List<Map<String, Object>> reportData) { Map<String, Object> result = new HashMap<>(); try { // 1. 数据预处理 List<FinancialItem> items = reportDataService.parseFinancialData(reportData); // 2. 调用分析引擎 AnalysisResult analysis = reportDataService.analyze(items); // 3. 生成Excel图表 String filePath = reportDataService.generateChartExcel(analysis); result.put("success", true); result.put("downloadUrl", "/download?file=" + filePath); } catch (Exception e) { result.put("success", false); result.put("message", "分析失败:" + e.getMessage()); } return result; } }3.3 权限控制配置
在bip-security.xml中增加权限配置:
<intercept-url pattern="/bip/custom/**" access="hasRole('BIP_REPORT_ANALYZE')"/>同时需要在数据库的sm_permission表中插入对应的权限记录:
INSERT INTO sm_permission (permission_id, permission_name, permission_code, system_id) VALUES ('bip_custom_analyze', '报表分析权限', 'BIP_REPORT_ANALYZE', 'bip');4. 关键技术难点与解决方案
4.1 数据格式转换问题
原始BIP报表数据采用特殊的树形结构存储,与客户Excel模板需要的平面结构不匹配。我们开发了专门的转换器:
public class FinancialDataConverter { public static List<FinancialItem> convert(Map<String, Object> node) { List<FinancialItem> items = new ArrayList<>(); // 处理当前节点 if(isFinancialItem(node)) { items.add(parseItem(node)); } // 递归处理子节点 List<Map<String, Object>> children = (List)node.get("children"); if(children != null) { for(Map<String, Object> child : children) { items.addAll(convert(child)); } } return items; } private static boolean isFinancialItem(Map<String, Object> node) { // 实现科目判断逻辑 } private static FinancialItem parseItem(Map<String, Object> node) { // 实现数据解析逻辑 } }4.2 性能优化措施
针对大型报表可能出现的性能问题,我们实施了以下优化:
- 分块处理:当数据量超过5000行时,自动启用分块处理机制
- 内存缓存:对已处理过的报表数据进行缓存,有效期为30分钟
- 异步生成:超过100MB的文件采用后台任务生成,通过消息通知用户
核心线程池配置:
@Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(10); executor.setQueueCapacity(100); executor.setThreadNamePrefix("BIP-Analyze-"); executor.initialize(); return executor; } }5. 部署与测试方案
5.1 热部署实施步骤
为避免影响用户正常使用,我们采用热部署方案:
- 将修改后的JSP文件打包成patch.jar
- 通过NC的模块管理控制台上传补丁
- 执行增量更新命令:
nc.update -m bip -f patch.jar -t jsp5.2 自动化测试脚本
开发了基于Selenium的自动化测试用例:
class BIPButtonTest(unittest.TestCase): def setUp(self): self.driver = webdriver.Chrome() self.driver.get(BIP_URL) login(self.driver) def test_custom_button(self): # 打开测试报表 open_report(self.driver, "月度财务报表") # 验证按钮存在 btn = self.driver.find_element(By.ID, "customAnalyzeBtn") self.assertTrue(btn.is_displayed()) # 测试点击功能 btn.click() time.sleep(3) # 验证结果下载 download_link = self.driver.find_element( By.CSS_SELECTOR, "#download-link") self.assertIn(".xlsx", download_link.text) def tearDown(self): self.driver.quit()6. 项目成果与扩展应用
本次开发实现了以下核心价值:
- 效率提升:将原本需要30分钟的手工操作缩短至10秒内完成
- 错误率降低:数据转换准确率达到100%,消除人为操作失误
- 扩展性强:建立的开发框架可快速支持其他自定义按钮需求
在实际应用中,我们还发现这套方案可以复用于以下场景:
- 报表数据一键推送至审批系统
- 自动生成财务凭证
- 数据异常自动检测与预警
关键经验:在修改NC系统原生文件前,务必做好备份。我们曾遇到JSP文件编码问题导致页面乱码的情况,最终通过对比原始文件的BOM头解决了问题。