You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

开发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);
    }

    // 复用父类的其他核心流程逻辑
}

设计优势总结
  1. 通用性:所有报表的流程由数据库元数据驱动,新增报表只需要配置数据库,无需编写代码;
  2. 易扩展性:新增步骤只需实现Handler接口,自定义逻辑只需继承抽象类重写钩子方法,完全符合开闭原则;
  3. 灵活的失败处理:每个步骤的失败策略可独立配置,支持失败终止或继续流程;
  4. 解耦性:核心流程与具体步骤逻辑解耦,每个步骤独立维护,便于单元测试和问题排查。

内容的提问来源于stack exchange,提问作者HereToLearn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 03:45:28