开发Java独立报表框架:可扩展工作流设计模式选型咨询
针对你正在开发的这款Java报表调度框架,我推荐结合模板方法模式、责任链模式,再配合数据库驱动的步骤配置,来实现通用性与易扩展性的平衡,同时完美支持步骤失败时的灵活处理。下面是具体的设计思路和代码示例:
核心流程抽象:模板方法模式
模板方法模式非常适合定义固定的流程骨架,同时允许子类自定义部分逻辑——刚好匹配你当前允许用户继承ReportService扩展需求。我们可以把流程中固定的环节(获取元数据、执行SQL)做成抽象类的固定方法,把可变环节(数据转换、前置服务)做成钩子方法,让子类按需重写。
public abstract class AbstractReportService { // 核心工作流模板方法:定义固定流程骨架,禁止子类重写 public final void executeWorkflow(String reportId) { // 1. 获取报表元数据(固定步骤) ReportMetadata metadata = getReportMetadata(reportId); if (!metadata.isEnabled()) { log.info("报表{}已禁用,终止流程", reportId); return; } // 2. 执行SQL查询(固定步骤) List<Map<String, Object>> rawData = executeQuery(metadata.getQuerySql()); // 3. 自定义数据转换(钩子方法,子类可选重写) List<Map<String, Object>> processedData = transformData(rawData); // 4. 执行前置自定义服务(钩子方法,子类可选重写) executePreServices(metadata, processedData); // 5. 执行可配置的工作流步骤(核心扩展点) executeWorkflowSteps(metadata, processedData); } // 抽象方法:子类必须实现获取元数据的逻辑 protected abstract ReportMetadata getReportMetadata(String reportId); // 抽象方法:子类必须实现SQL查询逻辑 protected abstract List<Map<String, Object>> executeQuery(String sql); // 钩子方法:数据转换,默认直接返回原数据 protected List<Map<String, Object>> transformData(List<Map<String, Object>> rawData) { return rawData; } // 钩子方法:前置服务,默认空实现 protected void executePreServices(ReportMetadata metadata, List<Map<String, Object>> data) { // 默认不做任何操作 } // 抽象方法:执行可配置的步骤链,子类实现具体逻辑 protected abstract void executeWorkflowSteps(ReportMetadata metadata, List<Map<String, Object>> data); }
步骤可配置化与失败处理:责任链+数据库配置
为了支持动态新增步骤、每个步骤失败时可配置终止/继续,我们可以把每个流程步骤(FTP传输、邮件发送等)封装成独立的Handler,同时在数据库中存储步骤的配置信息(执行顺序、失败策略、参数等)。
1. 数据库步骤配置表设计
首先在数据库中新增报表步骤配置表,用来驱动流程:
CREATE TABLE report_workflow_step ( step_id VARCHAR(50) PRIMARY KEY, report_id VARCHAR(50) NOT NULL, -- 关联目标报表ID step_name VARCHAR(100) NOT NULL, -- 步骤标识:FTP_TRANSFER、EMAIL_SEND等 execute_order INT NOT NULL, -- 执行顺序 failure_strategy VARCHAR(20) NOT NULL DEFAULT 'CONTINUE', -- 失败策略:TERMINATE/CONTINUE config JSON -- 步骤参数:比如FTP地址、邮件收件人、模板ID等 );
2. 定义步骤Handler接口
把每个步骤的执行逻辑抽象成统一接口,方便扩展:
public interface WorkflowStepHandler { // 返回步骤标识,匹配数据库中的step_name String getStepType(); // 执行步骤,返回执行结果(成功/失败) boolean execute(ReportMetadata reportMetadata, List<Map<String, Object>> data, JSONObject stepConfig); }
3. 实现具体步骤Handler
比如FTP传输和邮件发送的Handler:
public class FtpTransferHandler implements WorkflowStepHandler { @Override public String getStepType() { return "FTP_TRANSFER"; } @Override public boolean execute(ReportMetadata reportMetadata, List<Map<String, Object>> data, JSONObject stepConfig) { try { // 生成报表文件(这里可以复用统一的文件生成逻辑) File reportFile = ReportFileGenerator.generate(data, reportMetadata.getReportType()); // 读取FTP配置执行上传 FtpClient ftpClient = new FtpClient( stepConfig.getString("ftpServer"), stepConfig.getInt("port"), stepConfig.getString("username"), stepConfig.getString("password") ); ftpClient.upload(reportFile, stepConfig.getString("remotePath")); return true; } catch (Exception e) { log.error("报表{} FTP传输失败:{}", reportMetadata.getReportId(), e.getMessage(), e); return false; } } } public class EmailSendHandler implements WorkflowStepHandler { @Override public String getStepType() { return "EMAIL_SEND"; } @Override public boolean execute(ReportMetadata reportMetadata, List<Map<String, Object>> data, JSONObject stepConfig) { try { EmailService emailService = new EmailService(); emailService.send( stepConfig.getString("to"), stepConfig.getString("subject"), EmailTemplateRenderer.render(stepConfig.getString("templateId"), data) ); return true; } catch (Exception e) { log.error("报表{} 邮件发送失败:{}", reportMetadata.getReportId(), e.getMessage(), e); return false; } } }
4. 实现步骤链执行逻辑
在AbstractReportService的子类中,实现executeWorkflowSteps方法,从数据库读取步骤配置,按顺序执行并处理失败策略:
public class DefaultReportService extends AbstractReportService { // 注入所有步骤Handler,用Map存储便于快速查找 private Map<String, WorkflowStepHandler> stepHandlerMap; // 注入步骤配置DAO private ReportWorkflowStepDao stepDao; @Override protected void executeWorkflowSteps(ReportMetadata metadata, List<Map<String, Object>> data) { // 获取当前报表的所有步骤,按执行顺序排序 List<ReportWorkflowStep> steps = stepDao.listByReportId(metadata.getReportId()); steps.sort(Comparator.comparingInt(ReportWorkflowStep::getExecuteOrder)); for (ReportWorkflowStep step : steps) { WorkflowStepHandler handler = stepHandlerMap.get(step.getStepName()); if (handler == null) { log.warn("未找到步骤类型{}对应的Handler,跳过该步骤", step.getStepName()); continue; } boolean success = handler.execute(metadata, data, step.getConfig()); // 根据失败策略判断是否终止流程 if (!success && "TERMINATE".equals(step.getFailureStrategy())) { log.error("步骤{}执行失败,且失败策略为终止,流程结束", step.getStepName()); break; } // 失败策略为CONTINUE时,无论成功失败都继续下一个步骤 } } // 实现其他抽象方法(获取元数据、执行SQL) @Override protected ReportMetadata getReportMetadata(String reportId) { return reportMetadataDao.getById(reportId); } @Override protected List<Map<String, Object>> executeQuery(String sql) { return jdbcTemplate.queryForList(sql); } }
扩展性实现
1. 新增流程步骤
只需要两步:
- 实现
WorkflowStepHandler接口,编写新步骤的执行逻辑; - 在
report_workflow_step表中添加对应步骤的配置,无需修改核心流程代码。
2. 用户自定义业务逻辑
用户只需要继承AbstractReportService,重写钩子方法即可:
public class CustomSalesReportService extends AbstractReportService { @Override protected List<Map<String, Object>> transformData(List<Map<String, Object>> rawData) { // 对销售数据进行自定义转换:格式化金额、计算月度合计等 return rawData.stream() .map(data -> { BigDecimal amount = (BigDecimal) data.get("amount"); data.put("formatted_amount", NumberFormat.getCurrencyInstance().format(amount)); return data; }) .collect(Collectors.toList()); } @Override protected void executePreServices(ReportMetadata metadata, List<Map<String, Object>> data) { // 执行自定义前置服务:同步销售数据到CRM系统 crmSyncService.syncSalesData(data); } // 复用父类的其他核心流程逻辑 }
设计优势总结
- 通用性:所有报表的流程由数据库元数据驱动,新增报表只需要配置数据库,无需编写代码;
- 易扩展性:新增步骤只需实现Handler接口,自定义逻辑只需继承抽象类重写钩子方法,完全符合开闭原则;
- 灵活的失败处理:每个步骤的失败策略可独立配置,支持失败终止或继续流程;
- 解耦性:核心流程与具体步骤逻辑解耦,每个步骤独立维护,便于单元测试和问题排查。
内容的提问来源于stack exchange,提问作者HereToLearn
相关产品推荐
相关产品推荐

