当前位置: 代码网 > 服务器>软件设计>开源 > 积木报表Excel数据量大导出慢导不出问题、大量数据导不出问题优化方案和分析解决思路(优化前一万多导出失败,优化后支持百万级跨库表导出,已开源)

积木报表Excel数据量大导出慢导不出问题、大量数据导不出问题优化方案和分析解决思路(优化前一万多导出失败,优化后支持百万级跨库表导出,已开源)

2024年08月03日 开源 我要评论
原积木导出有两种导出,直接导出和大数据导出(大数据导出是做了优化去掉了一些样式之类的,性能更好)实测中发现原积木大数据导出性能:1万条数据导出耗时30秒,1.5万条耗时1.5分钟导出失败,数据超过一万条后经常导出失败,还会导致容器实例探活失败/内存撑爆重启异步+自实现导出功能优化方案导出性能:34.6万条数据一次导出耗时2.5分钟,导出时快速返回空数据文件,避免页面死等不可用,导出后的表格是在线OSS的文件URL直接每个人都可以快速下载,可随时多次下载。

积木报表excel数据量大导出慢导不出问题、大量数据导不出问题优化方案和分析解决思路(优化前一万多导出失败,优化后支持百万级跨库表导出,已开源)

反馈官方进度

跟官方反馈这个方案后,官方直接优化了但是把原本这个免费的大数据导出做成了收费功能!绝了,我直接把我方案开源

反馈后看到后续的版本里有说升级了这个,于是我升级了包测试,直接这个功能需要企业版才给用了,寄,不如直接用下载中心模式得了,有空把这个下载中心的代码抽出来封装好开源放github 0
下载中心这个能力抽出来脱敏成开源项目已准备的差不多了,准备好后更新文章,帮忙点star支持开源
地址:https://github.com/humorchen/jimuexportdataextension

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

优化结果

原积木导出有两种导出,直接导出和大数据导出(大数据导出是做了优化去掉了一些样式之类的,性能更好)
实测中发现
原积木大数据导出性能:1万条数据导出耗时30秒,1.5万条耗时1.5分钟导出失败,数据超过一万条后经常导出失败,还会导致容器实例探活失败/内存撑爆重启
异步+自实现导出功能优化方案导出性能:48万条数据一次导出耗时2.5分钟,导出时快速返回空数据文件,避免页面死等不可用,导出后的表格是在线oss的文件url直接每个人都可以快速下载,可随时多次下载。
在这里插入图片描述在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

需求背景和解决方案的思考

解决方案

在数据获取的api接口上加注解标识这个导出走下载中心方案,若要恢复原样,则注释这个注解即可
在这里插入图片描述

流程描述:

识别并拦截积木导出操作,透传导出时参数生成自己设计的导出任务,异步执行导出任务(自行实现积木导出的逻辑),并使用高性能的阿里巴巴开源的easyexcel工具,最后将easyexcel导出的表格文件上传到oss变为表格url(记得做安全防护,避免表格泄漏)

积木的逻辑
积木前端导出按钮->积木导出接口->分页调取你的数据接口拉数据->生成excel返回

关键代码

引入easy excel
			<dependency>
                <groupid>com.alibaba</groupid>
                <artifactid>easyexcel-core</artifactid>
                <version>3.1.1</version>
            </dependency>
            <dependency>
                <groupid>com.alibaba</groupid>
                <artifactid>easyexcel</artifactid>
                <version>3.1.1</version>
            </dependency>
新建数据库表
create table `t_download_task` (
  `id` int(10) unsigned not null auto_increment comment 'id',
  `account` varchar(64) collate utf8mb4_unicode_ci not null comment '提交任务的账号',
  `title` varchar(256) collate utf8mb4_unicode_ci not null comment '下载任务标题',
  `icon` varchar(1024) collate utf8mb4_unicode_ci default null comment '图标',
  `url` varchar(1024) collate utf8mb4_unicode_ci default null comment '文件url',
  `file_size` varchar(16) collate utf8mb4_unicode_ci default null comment '文件大小',
  `percent` varchar(16) collate utf8mb4_unicode_ci default null comment '进度(例如50%)',
  `state` tinyint(4) default '0' comment '任务状态(0 等待执行,1执行中,2执行成功,3执行失败)',
  `error` varchar(1024) collate utf8mb4_unicode_ci default null comment '执行报错信息(有则填)',
  `json` varchar(4096) collate utf8mb4_unicode_ci not null default '{}' comment '预留的json扩展字段',
  `create_time` datetime not null default current_timestamp comment '创建时间',
  `update_time` datetime not null default current_timestamp on update current_timestamp comment '更新时间',
  primary key (`id`),
  key `idx_account_create_time` (`account`,`create_time`),
  key `idx_create_time` (`create_time`)
) engine=innodb auto_increment=78 default charset=utf8mb4 collate=utf8mb4_unicode_ci comment='下载中心的任务';

下载任务


/**
 * <p>
 * 下载任务
 * </p>
 *
 * @author humorchen
 * @since 2024-01-05
 */
@data
@equalsandhashcode(callsuper = false)
@accessors(chain = true)
@tablename("t_download_task")
public class downloadtask implements serializable {

    private static final long serialversionuid = 1l;

    /**
     * id
     */
    @tableid(value = "id", type = idtype.auto)
    private integer id;
    /**
     * 创建下载任务的账号
     */
    @tablefield("account")
    private string account;

    /**
     * 下载任务标题
     */
    @tablefield("title")
    private string title;

    /**
     * 图标
     */
    @tablefield("icon")
    private string icon;

    /**
     * 文件url
     */
    @tablefield("url")
    private string url;

    /**
     * 文件大小
     */
    @tablefield("file_size")
    private string filesize;

    /**
     * 进度(例如50%)
     */
    @tablefield("percent")
    private string percent;

    /**
     * 任务状态(0 等待执行,1执行中,2执行成功,3执行失败)
     */
    @tablefield("state")
    private integer state;

    /**
     * 执行报错信息(有则填)
     */
    @tablefield("error")
    private string error;

    /**
     * 预留的json扩展字段
     */
    @tablefield("json")
    private string json;

    /**
     * 创建时间
     */
    @tablefield("create_time")
    private date createtime;

    /**
     * 更新时间
     */
    @tablefield("update_time")
    private date updatetime;


    public static final string id = "id";

    public static final string account = "account";

    public static final string title = "title";

    public static final string icon = "icon";

    public static final string url = "url";

    public static final string file_size = "file_size";

    public static final string percent = "percent";

    public static final string state = "state";

    public static final string error = "error";

    public static final string json = "json";

    public static final string create_time = "create_time";

    public static final string update_time = "update_time";

}

使用mabatis plus code generator生成service、mapper等文件

识别所需注解、基类

数据获取api所调用服务上方法上标注的注解

/**
 * @author: humorchen
 * date: 2024/1/15
 * description: 该报表接口使用下载任务中心代理掉,完成下载任务
 * 使用要求:
 * 参数中需要有一个参数是downloadcenterbaseparam的子类,方法返回值类型需要是支持泛型的jimupagedto类,方法上加注@usedownloadtaskcenter注解
 * 参考cn.sffix.recovery.report.service.impl.reportserviceimpl#dashboardnewversion(cn.sffix.recovery.report.entity.dto.dashboardquerydto)
 **/
@retention(retentionpolicy.runtime)
@target({elementtype.method})
@documented
public @interface usedownloadtaskcenter {

}

示范给数据接口调用的服务加注解

    @override
    @usedownloadtaskcenter
    public jimupagedto<dashboarddatadto> dashboardnewversion(dashboardquerydto dashboardquerydto) {
    //  你的数据接口逻辑
    }

参数基类

/**
 * @author: humorchen
 * date: 2024/1/15
 * description:
 **/
@data
public class downloadcenterbaseparam {

    /**
     * 分页数据页号和页大小
     */
    private integer pageno;
    /**
     * 分页数据页号和页大小
     */
    private integer pagesize;
}

结果基类

/**
 * @author: humorchen
 * date: 2023/12/19
 * description:
 **/
@data
@fieldnameconstants
@accessors(chain = true)
public class jimupagedto<t> {
    /**
     * 数据
     */
    private list<t> data;
    /**
     * 积木的count是总数据条数,不是当前页多少条!!!
     */
    private long count;
    /**
     * 积木的total是总页数,不是总数据条数!!!
     */
    private long total;

    public static final jimupagedto empty = new jimupagedto().setdata(collections.emptylist()).settotal(0).setcount(0);
}

/**
 * @author: humorchen
 * date: 2024/1/5
 * description: 下载任务状态
 **/
@getter
public enum downloadtaskstateenum {
    wait(0, "等待执行"),
    running(1, "执行中"),
    success(2, "执行成功"),
    failed(3, "执行失败"),

    ;

    private final int state;
    private final string title;

    downloadtaskstateenum(int state, string title) {
        this.state = state;
        this.title = title;
    }

    /**
     * 根据状态获取枚举
     *
     * @param state
     * @return
     */
    public static downloadtaskstateenum of(int state) {
        for (downloadtaskstateenum value : values()) {
            if (value.state == state) {
                return value;
            }
        }
        return null;
    }
}

下载中心服务(报表下载的导出任务)
/**
 * <p>
 * 下载任务 服务类
 * </p>
 *
 * @author humorchen
 * @since 2024-01-05
 */
public interface idownloadtaskservice extends iservice<downloadtask> {
    /**
     * 注册任务
     *
     * @param downloadtask
     * @return
     */
    downloadtask registertask(@nonnull downloadtask downloadtask);

    /**
     * 10秒内是否有相同任务未完成,不给再次注册下载任务
     *
     * @param account
     * @param requestbody
     * @return
     */
    boolean setsametasklock(string account, string requestbody);
    /**
     * 更新任务
     *
     * @param downloadtask
     * @return
     */
    int updatetaskbyid(@nonnull downloadtask downloadtask);

    /**
     * 更新任务进度
     *
     * @param id
     * @param percent
     * @return
     */
    int changetaskpercent(int id, @nonnull string percent);

    /**
     * 更新任务状态
     *
     * @param id
     * @param state
     * @return
     */
    int changetaskstate(int id, @nonnull downloadtaskstateenum state);

    /**
     * 更新任务状态
     *
     * @param id
     * @param expectstate
     * @param targetstate
     * @return
     */
    int compareandswaptaskstate(int id, @nonnull downloadtaskstateenum expectstate, @nonnull downloadtaskstateenum targetstate);
    /**
     * 根据任务id获取任务
     *
     * @param id
     * @return
     */
    downloadtask getdownloadtaskbyid(int id);

    /**
     * 分页查下载任务
     *
     * @param pagelistdownloadtaskdto
     * @return
     */
    ipage<downloadtask> pagelistdownloadtask(pagelistdownloadtaskdto pagelistdownloadtaskdto);

    /**
     * 重新执行下载任务
     *
     * @param taskid
     * @return
     */
    @requestmapping("/reruntask")
    result<string> reruntask(integer taskid);

    /**
     * 根据报表id获取报表名称
     *
     * @param reportid
     * @return
     */
    string getreportnamebyreportid(string reportid);

    /**
     * 从请求体中获取报表id
     *
     * @param requestbody
     * @return
     */
    string getreportidfromrequestbody(string requestbody);

    /**
     * 根据报表id获取报表api地址或者sql
     *
     * @param reportid
     * @return
     */
    jimureportdatasourcedto getreportapiorsqlbyreportid(string reportid);

    /**
     * 获取积木报表的头
     *
     * @param reportid
     * @return
     */
    list<jimureportdatacolumndto> getreporthead(string reportid);

    /**
     * 从积木请求体中获取请求参数
     *
     * @param json
     * @return
     */
    string getrequestparamfromjson(string json);
}

实现类

/**
 * <p>
 * 下载任务 服务实现类
 * </p>
 *
 * @author humorchen
 * @since 2024-01-05
 */
@service
@slf4j
public class downloadtaskserviceimpl extends serviceimpl<downloadtaskmapper, downloadtask> implements idownloadtaskservice {
    @autowired
    private downloadtaskmapper downloadtaskmapper;
    @autowired
    private ireportdatagetservice reportdatagetservice;
    @autowired
    private redistemplate<string, string> redistemplate;

    /**
     * 注入spring 事件发布器
     */
    @autowired
    private applicationeventpublisher eventpublisher;

    /**
     * 注册任务
     *
     * @param downloadtask
     * @return
     */
    @override
    public downloadtask registertask(@nonnull downloadtask downloadtask) {
        downloadtaskmapper.insert(downloadtask);
        return downloadtask;
    }

    /**
     * 10秒内是否有相同任务未完成,不给再次注册下载任务
     *
     * @param account
     * @param requestbody
     * @return
     */
    @override
    public boolean setsametasklock(string account, string requestbody) {
        downloadtasksubmitlimitcachekey limitcachekey = new downloadtasksubmitlimitcachekey(account, md5.create().digesthex(requestbody));
        boolean setifabsent = redistemplate.opsforvalue().setifabsent(limitcachekey.getkey(), dateutil.now(), limitcachekey.getexpire(), limitcachekey.gettimeunit());
        return boolean.true.equals(setifabsent);
    }

    /**
     * 更新任务
     *
     * @param downloadtask
     * @return
     */
    @override
    public int updatetaskbyid(@nonnull downloadtask downloadtask) {
        return downloadtaskmapper.updatebyid(downloadtask);
    }

    /**
     * 更新任务进度
     *
     * @param id
     * @param percent
     * @return
     */
    @override
    public int changetaskpercent(int id, @nonnull string percent) {
        updatewrapper<downloadtask> updatewrapper = new updatewrapper<>();
        updatewrapper.eq(downloadtask.id, id);
        updatewrapper.set(downloadtask.percent, percent);
        log.info("【下载中心】更新任务进度 id:{} percent:{}", id, percent);
        return downloadtaskmapper.update(null, updatewrapper);
    }

    /**
     * 更新任务状态
     *
     * @param id
     * @param state
     * @return
     */
    @override
    public int changetaskstate(int id, @nonnull downloadtaskstateenum state) {
        updatewrapper<downloadtask> updatewrapper = new updatewrapper<>();
        updatewrapper.eq(downloadtask.id, id);
        updatewrapper.set(downloadtask.state, state.getstate());
        return downloadtaskmapper.update(null, updatewrapper);
    }

    /**
     * 更新任务状态
     *
     * @param id
     * @param expectstate
     * @param targetstate
     * @return
     */
    @override
    public int compareandswaptaskstate(int id, @nonnull downloadtaskstateenum expectstate, @nonnull downloadtaskstateenum targetstate) {
        updatewrapper<downloadtask> updatewrapper = new updatewrapper<>();
        updatewrapper.eq(downloadtask.id, id);
        updatewrapper.eq(downloadtask.state, expectstate.getstate());
        updatewrapper.set(downloadtask.state, targetstate.getstate());
        return downloadtaskmapper.update(null, updatewrapper);
    }

    /**
     * 根据任务id获取任务
     *
     * @param id
     * @return
     */
    @override
    public downloadtask getdownloadtaskbyid(int id) {
        return downloadtaskmapper.selectbyid(id);
    }

    /**
     * 查下载任务
     *
     * @param pagelistdownloadtaskdto
     * @return
     */
    @override
    public ipage<downloadtask> pagelistdownloadtask(pagelistdownloadtaskdto pagelistdownloadtaskdto) {
        integer id = pagelistdownloadtaskdto.getid();
        string starttime = pagelistdownloadtaskdto.getstarttime();
        string endtime = pagelistdownloadtaskdto.getendtime();
        string filename = pagelistdownloadtaskdto.getfilename();
        integer taskstate = pagelistdownloadtaskdto.gettaskstate();
        userinfo userinfo = userinfoholder.get();
        string account = userinfo.getaccount();
        int pageno = optional.ofnullable(pagelistdownloadtaskdto.getpageno()).orelse(1);
        int pagesize = optional.ofnullable(pagelistdownloadtaskdto.getpagesize()).orelse(10);

        querywrapper<downloadtask> querywrapper = new querywrapper<>();
        querywrapper.eq(downloadtask.account, account);
        querywrapper.eq(id != null, downloadtask.id, id);
        querywrapper.between(starttime != null && endtime != null, downloadtask.create_time, starttime, endtime);
        querywrapper.like(strutil.isnotblank(filename), downloadtask.title, "%" + filename + "%");
        querywrapper.eq(taskstate != null, downloadtask.state, taskstate);
        // 最新的在前
        querywrapper.orderbydesc(downloadtask.create_time);
        return page(new page<>(pageno, pagesize), querywrapper);
    }

    /**
     * 重新执行下载任务
     *
     * @param taskid
     * @return
     */
    @override
    public result<string> reruntask(integer taskid) {
        downloadtask downloadtask = getdownloadtaskbyid(taskid);
        if (downloadtask == null) {
            return result.fail("未找到该任务,请刷新后重试");
        }
        if (downloadtask.getstate() == downloadtaskstateenum.running.getstate()) {
            return result.fail("任务正在执行中,请稍后重试");
        }
        eventpublisher.publishevent(new downloadtaskpublishevent(taskid));
        return result.ok("重新执行中");
    }

    /**
     * 根据报表id获取报表名称
     *
     * @param reportid
     * @return
     */
    @override
    public string getreportnamebyreportid(string reportid) {
        if (strutil.isblank(reportid)) {
            return "";
        }
        string sql = "select name from report.jimu_report where id = '" + reportid + "'";
        jsonobject jsonobject = reportdatagetservice.getone(sql);
        return optional.ofnullable(jsonobject.getstring("name")).orelse("");
    }

    /**
     * 从请求体中获取报表id
     *
     * @param requestbody
     * @return
     */
    @override
    public string getreportidfromrequestbody(string requestbody) {
        if (strutil.isnotblank(requestbody)) {
            jsonobject jsonobject = jsonobject.parseobject(requestbody);
            return jsonobject.getstring("excelconfigid");
        }
        return null;
    }

    /**
     * 根据报表id获取报表api地址或者sql
     *
     * @param reportid
     * @return
     */
    @override
    public jimureportdatasourcedto getreportapiorsqlbyreportid(string reportid) {
        jimureportdatasourcedto jimureportdatasourcedto = new jimureportdatasourcedto();
        if (strutil.isnotblank(reportid)) {
            string sql = "select db_dyn_sql,api_url from report.jimu_report_db where jimu_report_id = '" + reportid + "'";
            jsonobject jsonobject = reportdatagetservice.getone(sql);
            jimureportdatasourcedto.setsql(jsonobject.getstring("db_dyn_sql"));
            jimureportdatasourcedto.setapiurl(jsonobject.getstring("api_url"));
        }
        list<list<string>> head = new arraylist<>();
        easyexcel.write(new outputstream() {
            @override
            public void write(int b) throws ioexception {

            }
        }).head(head).sheet("sheet").dowrite(new arraylist<>());

        return jimureportdatasourcedto;
    }

    /**
     * 获取积木报表的头
     *
     * @param reportid
     * @return
     */
    @override
    public list<jimureportdatacolumndto> getreporthead(string reportid) {
        if (strutil.isblank(reportid)) {
            return collections.emptylist();
        }
        string sql = "select json_str from report.jimu_report where id = '" + reportid + "'";
        jsonobject jsonobject = reportdatagetservice.getone(sql);
        string jsonstr = jsonobject.getstring("json_str");
        jsonobject json = jsonobject.parseobject(jsonstr);
        jsonobject rows = json.getjsonobject("rows");
        jsonobject rows0cells = rows.getjsonobject("0").getjsonobject("cells");
        jsonobject rows1cells = rows.getjsonobject("1").getjsonobject("cells");

        set<string> rows0keysets = rows0cells.keyset();
        list<jimureportdatacolumndto> heads = rows0keysets.stream().map(key -> {
            jsonobject keyobject = rows0cells.getjsonobject(key);
            jsonobject columnobject = rows1cells.getjsonobject(key);
            if (keyobject == null || columnobject == null) {
                return null;
            }
            string name = keyobject.getstring("text");
            string column = columnobject.getstring("text");
            if (strutil.isblank(name) || strutil.isblank(column)) {
                return null;
            }
            // 处理 #{vpjcgifyua.orderid}
            int indexof = column.lastindexof(".");
            int indexof2 = column.lastindexof("}");
            if (column.startswith("#") && indexof >= 0 && indexof2 >= 0) {
                column = column.substring(indexof + 1, indexof2);
                if (strutil.isblank(column)) {
                    return null;
                }
            }
            jimureportdatacolumndto jimureportdatacolumndto = new jimureportdatacolumndto();
            jimureportdatacolumndto.setname(name);
            jimureportdatacolumndto.setcolumn(column);
            jimureportdatacolumndto.setindex(integer.parseint(key));
            return jimureportdatacolumndto;
        }).filter(objects::nonnull).sorted(comparator.comparing(jimureportdatacolumndto::getindex)).collect(collectors.tolist());
        log.info("【下载中心】获取积木报表的头 reportid:{},heads:{}", reportid, heads);
        return heads;
    }

    /**
     * 从积木请求体中获取请求参数
     *
     * @param json
     * @return
     */
    @override
    public string getrequestparamfromjson(string json) {
        if (strutil.isnotblank(json)) {
            jsonobject jsonobject = jsonobject.parseobject(json);
            if (jsonobject.containskey("param")) {
                return jsonobject.getjsonobject("param").tojsonstring();
            }
            return "{}";
        }
        return "{}";
    }


}

数据跨库获取工具

服务接口


/**
 * @author: humorchen
 * date: 2023/12/19
 * description: 获取数据服务,直接sql跨库拿数据
 **/
@ds("slave_1")
public interface ireportdatagetservice<t> {
    /**
     * 执行sql返回数据
     *
     * @param sql
     * @return
     */
    jsonobject getone(string sql);

    /**
     * 执行sql返回数据,数据封装到类cls对象里
     *
     * @param sql
     * @param cls
     * @return
     */
    t getone(string sql, class<t> cls);

    /**
     * 执行sql返回数据
     *
     * @param sql
     * @return
     */
    jsonarray getlist(string sql);

    /**
     * 执行sql返回数据,数据封装到类cls对象里
     *
     * @param sql
     * @param cls
     * @return
     */
    list<t> getlist(string sql, class<t> cls);

    /**
     * 分页查询
     *
     * @param sql
     * @param page
     * @param pagesize
     * @return
     */
    jsonarray pagegetlist(string sql, int page, int pagesize);

    /**
     * 分页查询
     *
     * @param sql
     * @param page
     * @param pagesize
     * @return
     */
    jimupagedto<jsonobject> pagegetlistforjimu(string sql, int page, int pagesize);

    /**
     * 分页查询
     *
     * @param sql
     * @param page
     * @param pagesize
     * @param cls
     * @return
     */
    jimupagedto<t> pagegetlistforjimu(string sql, int page, int pagesize, class<t> cls);

    /**
     * 计数
     *
     * @param sql
     * @return
     */
    long count(string sql);


    /**
     * 生成in语句
     *
     * @param columnname
     * @param elements
     * @return string
     */
    default string getcolumninsql(string columnname, list<string> elements) {
        stringbuilder sqlbuilder = new stringbuilder();
        sqlbuilder.append(" ");
        sqlbuilder.append(columnname);
        sqlbuilder.append(" in (");
        for (int i = 0; i < elements.size(); i++) {
            string id = elements.get(i);
            if (i > 0) {
                sqlbuilder.append(",");
            }
            sqlbuilder.append("'");
            sqlbuilder.append(id);
            sqlbuilder.append("'");
        }
        sqlbuilder.append(")");
        return sqlbuilder.tostring();
    }

}

服务实现


/**
 * @author: chenfuxing
 * date: 2023/12/19
 * description:
 **/
@service
@slf4j
public class reportdatagetserviceimpl implements ireportdatagetservice {
    @autowired
    private datasource datasource;


    /**
     * 执行sql返回数据
     *
     * @param sql
     * @return
     */
    @override
    public jsonobject getone(string sql) {
        jsonobject ret = null;
        connection connection = null;
        statement statement = null;
        resultset resultset = null;
        try {
            connection = datasource.getconnection();
            statement = connection.createstatement();
            logsql(sql);
            resultset = statement.executequery(sql);
            if (resultset != null) {
                while (resultset.next()) {
                    if (ret != null) {
                        throw new runtimeexception("查询结果不止一条数据");
                    }
                    ret = new jsonobject();
                    resultsetmetadata metadata = resultset.getmetadata();
                    int columncount = metadata.getcolumncount();
                    for (int i = 1; i <= columncount; i++) {
                        string columnname = metadata.getcolumnlabel(i);
                        ret.put(columnname, resultset.getobject(columnname));
                    }
                }
            }
        } catch (exception e) {
            log.error("获取数据报错", e);
        } finally {
            // 释放资源
            ioutil.close(resultset);
            ioutil.close(statement);
            ioutil.close(connection);
        }
        return ret;
    }

    /**
     * 执行sql返回数据
     *
     * @param sql
     * @return
     */
    @override
    public jsonarray getlist(string sql) {
        jsonarray ret = new jsonarray();
        connection connection = null;
        statement statement = null;
        resultset resultset = null;
        try {
            connection = datasource.getconnection();
            statement = connection.createstatement();
            logsql(sql);
            resultset = statement.executequery(sql);
            if (resultset != null) {
                while (resultset.next()) {
                    // 组装数据为json 对象
                    jsonobject data = new jsonobject();
                    resultsetmetadata metadata = resultset.getmetadata();
                    int columncount = metadata.getcolumncount();
                    for (int i = 1; i <= columncount; i++) {
                        string columnname = metadata.getcolumnlabel(i);
                        data.put(columnname, resultset.getobject(columnname));
                    }
                    ret.add(data);
                }
            }
        } catch (exception e) {
            log.error("获取数据报错", e);
        } finally {
            // 释放资源
            ioutil.close(resultset);
            ioutil.close(statement);
            ioutil.close(connection);
        }
        return ret;
    }

    private void logsql(string sql) {
        int len = 5000;
        // 执行sql
        log.info("执行的sql:{}", strutil.isnotblank(sql) && sql.length() > len ? sql.substring(0, len) : sql);
    }

    /**
     * 计数
     *
     * @param sql
     * @return
     */
    @override
    public long count(string sql) {
        string countsql = getcountsqlfromquerysql(sql);
        if (strutil.isblank(countsql)) {
            throw new runtimeexception("计数语句不得为空,sql为:" + sql);
        }
        long ret = 0;
        connection connection = null;
        statement statement = null;
        resultset resultset = null;
        try {
            connection = datasource.getconnection();
            statement = connection.createstatement();
            logsql(sql);
            resultset = statement.executequery(countsql);
            if (resultset != null) {
                while (resultset.next()) {
                    ret = resultset.getlong(1);
                }
            }
        } catch (exception e) {
            log.error("获取数据报错", e);
        } finally {
            // 释放资源
            if (resultset != null) {
                try {
                    resultset.close();
                } catch (exception ignored) {
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (exception ignored) {
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (exception ignored) {
                }
            }
        }
        return ret;
    }

    /**
     * 从查询语句变计数语句
     *
     * @param sql
     * @return
     */
    public string getcountsqlfromquerysql(string sql) {
        string selectstr = "select";
        int selectindex = sql.indexof(selectstr);
        int fromindex = sql.indexof("from");
        return sql.replace(sql.substring(selectindex + selectstr.length(), fromindex), " count(*) as c ");
    }

    /**
     * 分页查询
     *
     * @param sql
     * @param page
     * @param pagesize
     * @return
     */
    @override
    public jsonarray pagegetlist(string sql, int page, int pagesize) {
        string querysql = getpagesqlfromquerysql(sql, page, pagesize);
        if (strutil.isblank(querysql)) {
            throw new runtimeexception("分页查询解析失败,sql:" + sql + " 页号: " + page + " 每页数量:" + pagesize);
        }
        return getlist(querysql);
    }

    /**
     * 分页查询
     *
     * @param sql
     * @param page
     * @param pagesize
     * @return
     */
    @override
    public jimupagedto<jsonobject> pagegetlistforjimu(string sql, int page, int pagesize) {
        jimupagedto<jsonobject> jimupagedto = new jimupagedto<>();
        // 查count
        long count = count(sql);
        long total = count / pagesize + (count % pagesize > 0 ? 1 : 0);
        log.info("数据总条数:{} 条,每页:{} 条,总页数:{} 页", count, pagesize, total);
        jimupagedto.settotal(total);
        // 查分页数据
        jsonarray data = pagegetlist(sql, page, pagesize);
        list<jsonobject> datalist = new arraylist<>(data.size());
        for (int i = 0; i < data.size(); i++) {
            jsonobject jsonobject = data.getjsonobject(i);
            datalist.add(jsonobject);
        }
        jimupagedto.setdata(datalist);
        jimupagedto.setcount(count);
        return jimupagedto;
    }

    /**
     * 分页查询
     *
     * @param sql
     * @param page
     * @param pagesize
     * @return
     */
    public string getpagesqlfromquerysql(string sql, int page, int pagesize) {
        assert.istrue(page >= 1, () -> new illegalargumentexception("page不得小于1"));
        assert.istrue(pagesize >= 1, () -> new illegalargumentexception("pagesize不得小于1"));
        int skip = (page - 1) * pagesize;
        stringbuilder builder = new stringbuilder(sql);
        builder.append(" limit ");
        if (skip > 0) {
            builder.append(skip);
            builder.append(",");
        }
        builder.append(pagesize);
        string querysql = builder.tostring();
        log.info("分页查询原sql:{}\n分页sql处理后:{}", sql, querysql);
        return querysql;
    }

    /**
     * 执行sql返回数据,数据封装到类cls对象里
     *
     * @param sql
     * @param cls
     * @return
     */
    @override
    public object getone(string sql, class cls) {
        return getone(sql).tojavaobject(cls);
    }

    /**
     * 执行sql返回数据,数据封装到类cls对象里
     *
     * @param sql
     * @param cls
     * @return
     */
    @override
    public list getlist(string sql, class cls) {
        return getlist(sql).tojavalist(cls);
    }

    /**
     * 分页查询
     *
     * @param sql
     * @param page
     * @param pagesize
     * @param cls
     * @return
     */
    @override
    public jimupagedto pagegetlistforjimu(string sql, int page, int pagesize, class cls) {
        jimupagedto<jsonobject> jimupagedto = pagegetlistforjimu(sql, page, pagesize);
        jimupagedto ret = new jimupagedto<>();
        list list = new arraylist(jimupagedto.getdata().size());
        for (int i = 0; i < jimupagedto.getdata().size(); i++) {
            list.add(jimupagedto.getdata().get(i).tojavaobject(cls));
        }
        ret.setdata(list);
        ret.settotal(jimupagedto.gettotal());
        ret.setcount(jimupagedto.getcount());
        return ret;
    }


}

拦截切面

/**
 * @author: humorchen
 * date: 2024/1/15
 * description: 下载任务切面
 * 对加上了@usedownloadtaskcenter注解的方法进行切面,使用下载任务中心代理掉,完成下载任务
 **/
@aspect
@component
@slf4j
public class downloadtaskaspect {
    @autowired
    private idownloadtaskservice downloadtaskservice;
    /**
     * 注入spring 事件发布器
     */
    @autowired
    private applicationeventpublisher eventpublisher;



    /**
     * 环绕通知
     *
     * @return
     */
    @around("@annotation(cn.sffix.recovery.report.annotations.usedownloadtaskcenter))")
    @order(50)
    public object around(proceedingjoinpoint joinpoint) throws throwable {
        log.info("【下载中心】进入下载中心切面");
        // 是下载中心发的请求则直接执行分页数据
        if (downloadcenterutil.isdownloadcenterrequest()) {
            log.info("【下载中心】下载中心发的请求,直接执行分页数据");
            return joinpoint.proceed();
        }

        // 识别下载请求
        int pageno = 1;
        int pagesize = 20;
        object[] args = joinpoint.getargs();
        if (args != null && args.length > 0) {
            downloadcenterbaseparam downloadcenterbaseparam = null;
            // 找到参数
            for (object arg : args) {
                if (arg instanceof downloadcenterbaseparam) {
                    downloadcenterbaseparam = (downloadcenterbaseparam) arg;
                    break;
                }
            }
            // 检查参数
            if (downloadcenterbaseparam != null) {
                pageno = optional.ofnullable(downloadcenterbaseparam.getpageno()).orelse(pageno);
                pagesize = optional.ofnullable(downloadcenterbaseparam.getpagesize()).orelse(pagesize);
            }
            log.info("【下载中心】下载中心切面,downloadcenterbaseparam:{}", downloadcenterbaseparam);
            if (downloadcenterbaseparam != null) {
                object target = joinpoint.gettarget();
                method method = ((methodsignature) joinpoint.getsignature()).getmethod();
                class<?> returntype = method.getreturntype();
                // 返回值类型检查
                if (returntype.equals(jimupagedto.class)) {
                    // 如果是导出请求,则使用下载任务中心代理掉
                    if (isexportfirstpagerequest(pageno, pagesize)) {
                        // 如果是导出第一页请求,则使用下载任务中心代理掉
                        downloadtask downloadtask = registertask(downloadcenterbaseparam, target, method, args);

                        if (downloadtask == null || downloadtask.getid() == null) {
                            log.error("【下载中心】注册下载任务失败,任务信息:{}", downloadtask);
                            return joinpoint.proceed();
                        }
                        log.info("【下载中心】注册下载任务成功,任务信息:{}", downloadtask);
                        // 返回积木所需要的数据
                        jimupagedto<jsonobject> jimupagedto = new jimupagedto<>();
                        jimupagedto.settotal(0);
                        jimupagedto.setcount(0);
                        jsonobject jsonobject = new jsonobject();
                        string downloadtaskjsonstr = downloadtask.getjson();
                        downloadtaskjson downloadtaskjson = jsonobject.parseobject(downloadtaskjsonstr, downloadtaskjson.class);
                        string requestbody = downloadtaskjson.getrequestbody();
                        string reportid = downloadtaskservice.getreportidfromrequestbody(requestbody);
                        list<jimureportdatacolumndto> reporthead = downloadtaskservice.getreporthead(reportid);
                        log.info("【下载中心】reporthead:{}", reporthead);
                        if (collectionutil.isnotempty(reporthead) && reporthead.size() > 1) {
                            string column = reporthead.get(1).getcolumn();
                            jsonobject.put(column, "请前往报表中台-下载中心查看(任务id " + downloadtask.getid() + ")");
                            log.info("【下载中心】返回数据:{}", jsonobject);
                        } else {
                            log.info("【下载中心】返回数据为空");
                        }
                        list<jsonobject> list = collections.singletonlist(jsonobject);
                        jimupagedto.setdata(list);
                        eventpublisher.publishevent(new downloadtaskpublishevent(downloadtask.getid()));
                        return jimupagedto;
                    } else {
                        log.info("【下载中心】不是导出请求,直接执行分页数据");
                    }
                } else {
                    log.error("【下载中心】返回值类型不是jimupagedto,无法使用下载任务中心代理掉");
                }

            }

        }

        return joinpoint.proceed();
    }

    /**
     * 生成下载任务
     *
     * @param downloadtaskparam
     * @return
     */
    private downloadtask registertask(downloadcenterbaseparam downloadtaskparam, object proxytarget, method method, object[] args) {
        userinfo loginuser = userinfoholder.get();
        string account = loginuser.getaccount();
        httpservletrequest currentrequest = requestutil.getcurrentrequest();
        string requestbody = downloadcenterutil.getrequestbodyfromheader(currentrequest);

        // 防止10秒内重复点击
        if (!downloadtaskservice.setsametasklock(account, requestbody)) {
            log.error("【下载中心】10秒内重复点击,不给再次注册下载任务");
            return null;
        }

        string title = "导出-" + dateutil.now().replace(" ", "_") + ".xlsx";
        try {
            title = downloadtaskservice.getreportnamebyreportid(downloadtaskservice.getreportidfromrequestbody(requestbody)) + title;
        } catch (exception e) {
            log.error("【下载中心】获取报表名称失败", e);
        }
        string url = generatefileurl();
        downloadtask downloadtask = new downloadtask();
        downloadtask.setaccount(account);
        downloadtask.settitle(title);
        downloadtask.seticon("");
        downloadtask.seturl(url);
        downloadtask.setfilesize("");
        downloadtask.setpercent("0%");
        downloadtask.setstate(downloadtaskstateenum.wait.getstate());
        downloadtaskjson downloadtaskjson = new downloadtaskjson();
        // 拷贝最开始请求积木的token和requestbody,执行下载任务时需要
        downloadtaskjson.setrequesttoken(downloadcenterutil.getrequesttokenfromheader(currentrequest));
        downloadtaskjson.setrequestbody(requestbody);
        downloadtaskjson.setproxymethod(method.getname());
        if (args != null) {
            for (object arg : args) {
                if (arg instanceof downloadcenterbaseparam) {
                    downloadtaskjson.setparam((downloadcenterbaseparam) arg);
                    break;
                }
            }
        }
        downloadtask.setjson(jsonobject.tojsonstring(downloadtaskjson));

        downloadtask = downloadtaskservice.registertask(downloadtask);
        return downloadtask;
    }

    /**
     * 生成文件url地址
     *
     * @return
     */
    private string generatefileurl() {
        // todo 生成文件url地址
        return "";
    }

    /**
     * 注入jm report分页大小
     */
    @value("${jeecg.jmreport.page-size-number:5000}")
    private int jmreportpagesizenumber;

    /**
     * 判断是否为导出请求
     *
     * @param pageno
     * @param pagesize
     * @return
     */
    private boolean isexportpagerequest(int pageno, int pagesize) {
        return pagesize == jmreportpagesizenumber;
    }

    /**
     * 判断是否为导出请求
     *
     * @param pageno
     * @param pagesize
     * @return
     */
    private boolean isexportfirstpagerequest(int pageno, int pagesize) {
        log.info("【下载中心】判断是否为导出请求 isexportfirstpagerequest pageno:{},pagesize:{},积木报表导出size:{}", pageno, pagesize, jmreportpagesizenumber);
        return pageno == 1 && isexportpagerequest(pageno, pagesize);
    }
}

报表下载任务处理事件
/**
 * @author: humorchen
 * date: 2024/1/16
 * description: 下载任务发布事件
 **/
@getter
public class downloadtaskpublishevent extends applicationevent {
    /**
     * 任务id
     */
    private final integer taskid;

    public downloadtaskpublishevent(integer taskid) {
        super(taskid);
        this.taskid = taskid;
    }
    
}

处理器


/**
 * @author: humorchen
 * date: 2024/1/16
 * description: 处理下载任务发布事件
 **/
@component
@slf4j
public class downloadtaskpublisheventhandler implements applicationlistener<downloadtaskpublishevent> {
    @autowired
    private idownloadtaskservice downloadtaskservice;
    @autowired
    private ireportservice reportservice;
    @autowired
    @qualifier(asyncconfig.async_thread_pool)
    private executor executor;

    @autowired
    private hwproperties hwproperties;
    @autowired
    private envutil envutil;

    /**
     * 注入jm report分页大小
     */
    @value("${jeecg.jmreport.page-size-number:5000}")
    private int jmreportpagesizenumber;

    @autowired
    private redissonclient redissonclient;


    /**
     * 超时时间
     */
    private static final int timeout_mills = 1000 * 60 * 20;

    @autowired
    private hwobjectstorageservice hwobjectstorageservice;


    /**
     * 上传文件到oss的路径
     *
     * @param account
     * @param filename
     * @return
     */
    private final storagepath getstoragepath(string account, string filename) {
        return storagepath.path().addfolder("downloadtask").addfolder(account).filename(filename);
    }

    /**
     * handle an application event.
     *
     * @param event the event to respond to
     */
    @override
    @async(asyncconfig.async_thread_pool)
    public void onapplicationevent(downloadtaskpublishevent event) {
        integer taskid = event.gettaskid();

        log.info("【下载中心】执行下载任务 taskid:{}", taskid);
        downloadtask downloadtask = downloadtaskservice.getbyid(taskid);
        if (downloadtask == null) {
            log.error("【下载中心】下载任务不存在,taskid:{}", taskid);
            return;
        }
        if (downloadtask.getstate() == downloadtaskstateenum.running.getstate()) {
            log.error("【下载中心】下载任务正在执行中,taskid:{}", taskid);
            return;
        }

        try {
            log.info("【下载中心】下载任务开始执行,taskid:{}", taskid);
            // 改状态到执行中
            downloadtaskstateenum downloadtaskstateenum = optional.ofnullable(downloadtaskstateenum.of(downloadtask.getstate())).orelse(downloadtaskstateenum.wait);
            int compareandswaptaskstate = downloadtaskservice.compareandswaptaskstate(taskid, downloadtaskstateenum, downloadtaskstateenum.running);
            if (compareandswaptaskstate < 1) {
                log.info("【下载中心】下载任务状态不对,taskid:{}, state:{}", taskid, downloadtaskstateenum);
                return;
            }
            downloadtaskjson downloadtaskjson = jsonobject.parseobject(downloadtask.getjson(), downloadtaskjson.class);

            // 获取数据
            string requestbody = downloadtaskjson.getrequestbody();
            string requesttoken = downloadtaskjson.getrequesttoken();
            string reportid = downloadtaskservice.getreportidfromrequestbody(requestbody);
            string reportname = downloadtaskservice.getreportnamebyreportid(reportid);
            string requestparam = downloadtaskservice.getrequestparamfromjson(downloadtask.getjson());
            jimureportdatasourcedto datasourcedto = downloadtaskservice.getreportapiorsqlbyreportid(reportid);
            list<jimureportdatacolumndto> reporthead = downloadtaskservice.getreporthead(reportid);
            // 打印上面拿到的数据
            log.info("reportid :{} \n reportname:{} \n requestparam:{} \n requestbody:{}  \n datasourcedto:{} \n reporthead:{}", reportid, reportname, requestparam, requestbody, datasourcedto, reporthead);
            jimureportdynamiceasyexcelimpl jimureportdynamiceasyexcel = new jimureportdynamiceasyexcelimpl(reportid, reportname, taskid, downloadtaskservice, requestparam, requesttoken, datasourcedto, reporthead);
            // 生成excel文件
            list<list<string>> head = reporthead.stream().map(d -> collections.singletonlist(d.getname())).collect(collectors.tolist());
            // 分页写数据
            inputstream inputstream = dynamiccolumneasyexcelutil.writepagedata(head, jimureportdynamiceasyexcel, jmreportpagesizenumber);



            // 上传excel文件到oss
            storagepath storagepath = getstoragepath(downloadtask.getaccount(), downloadtask.gettitle());
            downloadtask.setpercent("100%");
            stopwatch stopwatch = new stopwatch();
            stopwatch.start();
            uri uri = hwobjectstorageservice.savepublicfile(inputstream, storagepath);
            stopwatch.stop();
            log.info("【下载中心】上传文件到oss,耗时:{} ms,uri:{}", stopwatch.getlasttasktimemillis(), uri);
            // 更新任务信息
            string url = geturlprefix() + uri.getpath();
            downloadtask.seturl(url);
            downloadtask.setstate(downloadtaskstateenum.success.getstate());
            log.info("【下载中心】下载任务成功,taskid:{},task:{}", taskid, downloadtask);
            boolean updated = downloadtaskservice.updatebyid(downloadtask);
            log.info("【下载中心】下载任务更新结果,taskid:{}, updated:{}", taskid, updated);
        } catch (exception e) {
            log.error("【下载中心】下载任务执行失败", e);
            // 更新任务信息
            downloadtask.setstate(downloadtaskstateenum.failed.getstate());
            downloadtask.seterror("【下载中心】执行失败(" + e.getmessage() + ")");
            boolean updated = downloadtaskservice.updatebyid(downloadtask);
            log.info("【下载中心】下载任务更新结果,taskid:{}, updated:{}", taskid, updated);
        } finally {
            log.info("【下载中心】下载任务 {} 执行完毕", taskid);
        }


    }

    /**
     * 根据环境获取文件url前缀
     *
     * @return
     */
    private string geturlprefix() {
        string envcode = envutil.ispreorprodenv() ? "pro" : "test";
        string prefix = "https://test-obs.xxxxx.com";
        for (string key : hwproperties.geturlmap().keyset()) {
            if (key.contains(envcode)) {
                prefix = hwproperties.geturlmap().get(key);
            }
        }
        return prefix;
    }

}

透传信息

主要是在这个customapiheader() 方法里downloadcenterutil.copydownloadcenterheader(request, header);自定义获取数据请求的header这,读取这次积木导出请求的参数信息,写到获取数据请求的header里去

/**
 * 自定义积木报表鉴权(如果不进行自定义,则所有请求不做权限控制)
 * 1.自定义获取登录token
 * 2.自定义获取登录用户
 */
@slf4j
@component
public class jimureporttokenservice implements jmreporttokenservicei {

    @autowired
    @lazy
    private userautenticationfeign userautenticationfeign;

    @autowired
    @lazy
    private userinfofeign userinfofeign;

    /**
     * 通过请求获取token
     * @param request
     * @return
     */
    @override
    public string gettoken(httpservletrequest request) {
        string token = request.getparameter("token");
        if (token == null) {
            token = request.getheader("x-access-token");
        }
        if (token == null) {
            token = request.getheader("token");
        }
        if (token == null) {
            token = request.getheader("token");
        }
        return token;
    }

    /**
     * 通过token获取登录人用户名
     * @param token
     * @return
     */
    @override
    public string getusername(string token) {
        usertokendto tokeninfo = new usertokendto();
        tokeninfo.settoken(token);
        result<customerinfodto> customerinfodtoresult = userinfofeign.customerinfo(tokeninfo);
        customerinfodto data = customerinfodtoresult.getdata();
        if(data != null){
            fxuserinfodto userinfo = jsonobject.parseobject(jsonobject.tojsonstring(data.getbase()), fxuserinfodto.class);
            if(userinfo == null){
                throw new runtimeexception("找不到相应平台用户信息");
            } else {
                // 写到上下文
                userinfo user = new userinfo();
                beanutils.copyproperties(userinfo, user);
                userinfoholder.set(user);
                log.info("成功将用户信息写入上下文");
            }
            if(userinfo.getclienttype() != platformtypeenum.fx.name()){
                return userinfo.getname();
            }else{
                throw new runtimeexception("平台类型不支持");
            }
        }else {
            throw new runtimeexception("用户不存在");
        }
    }

    /**
     * 自定义用户拥有的角色
     *
     * @param token
     * @return
     */
    @override
    public string[] getroles(string token) {
        return new string[]{"admin"};
    }

    /**
     * token校验
     * @param token
     * @return
     */
    @override
    public boolean verifytoken(string token) {
        servletrequestattributes requestattributes = (servletrequestattributes) requestcontextholder.getrequestattributes();
        httpservletrequest request = requestattributes.getrequest();
        request.setattribute(useraccountconstant.token,token);
        try {
            usertokendto tokeninfo = new usertokendto();
            tokeninfo.settoken(token);
            result<customerinfodto> customerinfodtoresult = userinfofeign.customerinfo(tokeninfo);

            return customerinfodtoresult.getdata() != null;
        }catch (exception e){
            log.error("校验token异常:" + e.getmessage());
            return false;
        }
    }

    /**
     *  自定义请求头
     * @return
     */
    @override
    public httpheaders customapiheader() {
        servletrequestattributes requestattributes = (servletrequestattributes) requestcontextholder.getrequestattributes();
        httpheaders header = new httpheaders();
        header.add("custom-header1", "please set a custom value 1");
        if (requestattributes != null) {
            httpservletrequest request = requestattributes.getrequest();
            header.set("authorization", gettoken(request));
            header.set("token", gettoken(request));
            // 拷贝请求过去
            downloadcenterutil.copydownloadcenterheader(request, header);
            // 如果是下载中心发起的请求,设置请求头
            if (downloadcenterutil.isdownloadcenterrequest(requestattributes.getrequest())) {
                downloadcenterutil.setdownloadcenterheaderrequest(header);
            }
        }

        return header;
    }
}

下载中心工具类


/**
 * @author: humorchen
 * date: 2024/1/18
 * description: 下载中心工具类
 **/
@slf4j
public class downloadcenterutil {
    private static final string download_center_header_request = "download-center-request";

    /**
     * 是否为下载中心发起的请求
     *
     * @return
     */
    public static boolean isdownloadcenterrequest() {
        servletrequestattributes requestattributes = (servletrequestattributes) requestcontextholder.getrequestattributes();
        if (requestattributes == null) {
            log.debug("downloadcenterutil#isdownloadcenterrequest requestattributes is null");
            return false;
        }
        return isdownloadcenterrequest(requestattributes.getrequest());
    }

    /**
     * 是否为下载中心发起的请求
     *
     * @param servletrequest
     * @return
     */
    public static boolean isdownloadcenterrequest(httpservletrequest servletrequest) {
        return servletrequest != null && strutil.isnotblank(servletrequest.getheader(download_center_header_request));
    }

    /**
     * 设置下载中心请求头
     *
     * @param headers
     */
    public static void setdownloadcenterheaderrequest(httpheaders headers) {
        if (headers != null) {
            headers.set(download_center_header_request, "true");
        }
    }

    /**
     * 复制下载中心请求头
     *
     * @param request
     * @param headers
     */
    public static void copydownloadcenterheader(httpservletrequest request, httpheaders headers) {
        if (request == null || headers == null) {
            return;
        }
        // 复制request请求里的token请求头
        string token = request.getheader("token");
        if (strutil.isnotblank(token)) {
            headers.set(downloadtaskjson.fields.requesttoken, token);
        }
        // 复制request请求里的请求体
        headers.set(downloadtaskjson.fields.requestbody, requestutil.getrequestbody(request));
    }

    /**
     * 获取下载请求头token
     *
     * @param request
     * @return
     */
    public static string getrequesttokenfromheader(httpservletrequest request) {
        if (request == null) {
            return null;
        }
        return request.getheader(downloadtaskjson.fields.requesttoken);
    }

    /**
     * 获取下载请求头
     *
     * @param request
     * @return
     */
    public static string getrequestbodyfromheader(httpservletrequest request) {
        if (request == null) {
            return null;
        }
        return request.getheader(downloadtaskjson.fields.requestbody);
    }
    /**
     * 设置下载中心请求头
     *
     * @param request
     */
    public static void setdownloadcenterheaderrequest(httprequest request) {
        if (request != null) {
            request.header(download_center_header_request, "true");
        }
    }


    /**
     * 获取带参数的url
     *
     * @param url
     * @param params
     * @return
     */
    public static string geturlwithparams(string url, jsonobject params) {
        if (strutil.isblank(url) || params == null) {
            return url;
        }
        stringbuilder sb = new stringbuilder(url);
        if (url.contains("?")) {
            sb.append("&");
        } else {
            sb.append("?");
        }
        for (string key : params.keyset()) {
            sb.append(key).append("=").append(params.getstring(key)).append("&");
        }
        return sb.substring(0, sb.length() - 1);
    }

}

异步线程池

/**
 * @author: humorchen
 * date: 2024/1/16
 * description: 异步配置
 **/
@slf4j
@configuration
public class asyncconfig {
    public static final string async_thread_pool = "asyncthreadpool";

    /**
     * 异步线程池
     */
    @bean(name = async_thread_pool)
    public executor asyncexecutor() {
        threadpooltaskexecutor executor = new threadpooltaskexecutor();
        executor.setcorepoolsize(8);
        executor.setmaxpoolsize(64);
        executor.setqueuecapacity(128);
        executor.setthreadnameprefix("asyncthreadpool-");
        executor.initialize();
        return executor;

    }
}

给前端暴露接口
/**
 * @author humorchen
 * date: 2024/2/28
 * description: 下载中心api
 **/

@requestmapping("/report/form/downloadcenter")
public interface downloadcenterapi {


    /**
     * 分页查下载任务
     *
     * @param pagelistdownloadtaskdto
     * @return
     */
    @requestmapping("/pagelistdownloadtask")
    result<ipage<downloadtaskvo>> pagelistdownloadtask(pagelistdownloadtaskdto pagelistdownloadtaskdto);


    /**
     * 删除下载任务
     *
     * @param taskid
     * @return
     */
    @requestmapping("/deletetask")
    result<string> deletetask(integer taskid);

    /**
     * 重新执行下载任务
     *
     * @param taskid
     * @return
     */
    @requestmapping("/reruntask")
    result<string> reruntask(integer taskid);
}


/**
 * @author humorchen
 * date: 2024/2/28
 * description: 下载中心
 **/
@restcontroller
public class downloadcentercontroller implements downloadcenterapi {
    @autowired
    private idownloadtaskservice downloadtaskservice;


    /**
     * 分页查下载任务
     *
     * @param pagelistdownloadtaskdto
     * @return
     */
    @override
    public result<ipage<downloadtaskvo>> pagelistdownloadtask(pagelistdownloadtaskdto pagelistdownloadtaskdto) {
        ipage<downloadtask> downloadtaskpages = downloadtaskservice.pagelistdownloadtask(pagelistdownloadtaskdto);
        page<downloadtaskvo> downloadtaskvopage = new page<>();
        downloadtaskvopage.setcurrent(downloadtaskpages.getcurrent());
        downloadtaskvopage.setpages(downloadtaskpages.getpages());
        downloadtaskvopage.setsize(downloadtaskpages.getsize());
        downloadtaskvopage.settotal(downloadtaskpages.gettotal());
        list<downloadtaskvo> downloadtaskvos = downloadtaskpages.getrecords().stream().map(downloadtask -> {
            downloadtaskvo downloadtaskvo = beanutils.convert(downloadtask, downloadtaskvo.class);
            downloadtaskvo.setstatestr(optional.ofnullable(downloadtaskstateenum.of(downloadtask.getstate())).orelse(downloadtaskstateenum.wait).gettitle());
            return downloadtaskvo;
        }).collect(collectors.tolist());
        downloadtaskvopage.setrecords(downloadtaskvos);

        return result.ok(downloadtaskvopage);
    }

    /**
     * 删除下载任务
     *
     * @param taskid
     * @return
     */
    @override
    public result<string> deletetask(integer taskid) {
        boolean removebyid = downloadtaskservice.removebyid(taskid);

        return removebyid ? result.ok("删除成功") : result.fail("未找到该任务,请刷新后重试)");
    }

    /**
     * 重新执行下载任务
     *
     * @param taskid
     * @return
     */
    @override
    public result<string> reruntask(integer taskid) {
        return downloadtaskservice.reruntask(taskid);
    }
}

dynamiccolumneasyexcelutil 动态字段easyexcel工具


/**
 * @author humorchen
 * date: 2024/3/5
 * description: 动态列easyexcel工具类
 **/
@slf4j
public class dynamiccolumneasyexcelutil {
    public static final string row = "=row()";
    public static interface dynamiccolumneasyexcelinterface<t> {
        /**
         * 分页获取数据
         *
         * @param page
         * @param size
         * @return
         */
        jimupagedto<t> pagegetdata(int page, int size);

        /**
         * 数据对象转换为字符串
         *
         * @param t
         * @return
         */
        list<string> mapdatatostringlist(t t);

        /**
         * 分页获取数据加载第i页时触发函数,用于实现进度变更
         *
         * @param pageno
         * @param pagesize
         */
        void onloadedpage(int pageno, int pagesize, int pages);
    }
 /**
     * 从数据库分页读数据并写入成excel文件,把文件内容写到输出流
     *
     * @param head
     * @param dynamiccolumneasyexcelinterface
     * @param pagesize
     * @param <t>
     * @return
     */
    public static <t> bytearrayinputstream writepagedata(list<list<string>> head, dynamiccolumneasyexcelinterface<t> dynamiccolumneasyexcelinterface, int pagesize) {
        bytearrayoutputstream outputstream = new bytearrayoutputstream();
        excelwriter excelwriter = easyexcel.write(outputstream).head(head).build();
        // 当前分页
        int currentpage = 1;
        // 总页数
        long pages = 1;
        // 当前写入的sheet页
        int sheetno = 1;
        // 写入计数(用于自动翻sheet页)
        int writerowcount = 0;
        // 积木序号编号
        int index = 1;
        stopwatch stopwatch = new stopwatch("报表分页写入excel");
        writesheet sheet = getwritesheet(sheetno);

        do {
            // 加载数据
            stopwatch.start("加载第" + currentpage + "页数据");
            jimupagedto<t> jimupagedto = dynamiccolumneasyexcelinterface.pagegetdata(currentpage, pagesize);
            stopwatch.stop();
            // 数据判空
            list<t> records = jimupagedto.getdata();
            if (collectionutil.isempty(records)) {
                break;
            }
            // 转换数据
            stopwatch.start("转换第" + currentpage + "页数据");
            list<list<string>> data = records.stream().map(dynamiccolumneasyexcelinterface::mapdatatostringlist).collect(collectors.tolist());
            stopwatch.stop();
            // 处理序号 row()
            if (collectionutil.isnotempty(data) && collectionutil.isnotempty(data.get(0)) && row.equals(data.get(0).get(0))) {
                for (list<string> stringlist : data) {
                    if (collectionutil.isnotempty(stringlist) && row.equals(stringlist.get(0))) {
                        stringlist.set(0, string.valueof(index));
                        ++index;
                    }
                }
            }
            // 自动跳sheet页
            if (writerowcount + data.size() >= max_size_per_sheet) {
                ++sheetno;
                writerowcount = 0;
                index = 1;
                sheet = getwritesheet(sheetno);
            }
            // 写入数据
            stopwatch.start("写入第" + currentpage + "页数据(" + data.size() + "条数据)");
            excelwriter.write(data, sheet);
            stopwatch.stop();

            pages = jimupagedto.gettotal();
            // 更新进度
            dynamiccolumneasyexcelinterface.onloadedpage(currentpage, pagesize, (int) pages);

            log.info("【下载中心】 分页获取数据,第{}页,总页数:{} 第一行数据是:{}", currentpage, pages, data.get(0));
            // 自增
            currentpage++;
            writerowcount += data.size();
        } while (currentpage <= pages);
        log.info("【下载中心】 耗时打印");
        for (stopwatch.taskinfo taskinfo : stopwatch.gettaskinfo()) {
            log.info("【下载中心】 {} 耗时:{} ms", taskinfo.gettaskname(), taskinfo.gettimemillis());
        }
        excelwriter.finish();


        return new bytearrayinputstream(outputstream.tobytearray());
    }

    /**
     * 生成writesheet对象
     *
     * @param sheetno 1开始
     * @return writesheet
     */

    private static writesheet getwritesheet(int sheetno) {
        writesheet sheet = new writesheet();
        sheet.setsheetname("sheet" + sheetno);
        sheet.setsheetno(sheetno - 1);
        return sheet;
    }
    /**
     * 获取字段宽度策略
     *
     * @return
     */
    private abstractheadcolumnwidthstylestrategy getabstractcolumnwidthstylestrategy() {
        return new abstractheadcolumnwidthstylestrategy() {
            /**
             * returns the column width corresponding to each column head.
             *
             * <p>
             * if return null, ignore
             *
             * @param head        nullable.
             * @param columnindex not null.
             * @return
             */
            @override
            protected integer columnwidth(head head, integer columnindex) {
                return null;
            }
        };
    }

}


(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com