基于MQ的excel异步导出方案设计

947 阅读3分钟

1.背景

管理后台列表经常会有大量的导出功能,每个导出功能都需要单独写个导出接口,无法通用化。常见的导出方案就是使用 poi 或easyExcel直接同步导出一个 excel 文件。 如果一次性导出的数据量非常大,达到几十万甚至几千万级别的数据,往往就会出现以下几种情况:

  • 大数据量同步导出时,,页面等待时间长,页面会卡主,用户无法进行其他操作。
  • 出现内存溢出的情况。
  • 用户浏览器崩溃。
  • 数据库压力大,数据库宕机。

2.功能说明

  • 通用的异步导出方案,业务侧不需要关注如何导出,如何生成excel文件,只需提供一个分页查询数据列表接口即可。
  • 支持无限数据量文件异步导出,将N个小的excel压缩成zip,提供zip文件的下载链接。
  • 支持excel自定义合并表头。
  • 提供后台管理界面,供用户实时查看自己的导出进度、结果且可以下载自己导出的文件。
  • 前端在进行异步导出时需指明表头与之对应的字段、业务侧分页查询数据的接口(springcloud 网关routesId + uri)。
  • 为提升文件服务的存储空间利用率,导出的zip、excel文件在文件服务器中只保留30,过后会自动清除,下载链接失效。

3.异步导出时序图

explort.png

4.表结构

excel-data.png

5.使用

  • 业务侧提供的分页查询数据接口返回示例 :/xxx-system/user/page
{
	"code": 200,
	"success": true,
	"data": {
		"records": [
			{
				"id": "1514480641483673601",
				"code": "004",
				"name": "木鱼",
				"realName": "木鱼",
				"tenantId": "00000000",
				"tenantName": "武汉XXX科技有限公司",
				"userTypeName": "WEB",
				"roleName": "用户",
				"deptName": "后端组",
				"postName": "研发",
				"sexName": "性别",
				"userExt": ""
			}
		],
		"total": 1,
		"size": 10,
		"current": 1
	},
	"msg": "操作成功"
}

前端调用导出服务示例:

let param = {
	'fileName': '用户列表', //文件名称,随便定义,必填
	'fileType': 'xlsx', //文件类型(xlsx或zip),非必填,默认xlsx
	'taskName': '木鱼导出的用户列表', //导出任务名称,随便定义,非必填,如果不填就和文件名称一样
	'url': '/user/page', //业务侧提供的分页查询数据接口url,必填
	'routeId': 'xxx-system', //业务侧注册到网关的路由标识,必填
	'bizParam': { // 业务侧提供的分页查询数据接口所需要的参数,非必填
		'name': '张三', //业务侧的查询条件,非必填
		'size': 100 // excel的容量,非必填,默认1000
	},
	'tableHead': [{ // Excel表头内容配置, 参考 数据接口提供方的响应参数格式,必填
		'key': 'id',
		'title': '用户ID'
	}, {
		'key': 'name',
		'title': '用户姓名'
	}, {
		'key': 'tenantName',
		'title': '所属租户'
	}],
}
// 向Excel导出服务发起异步导出请求
axios.create({
	method: 'post', // excel接口的参数
	headers: {
		'Content-Type': 'application/json;charset=UTF-8', // excel接口的参数接收方式
		'X-Requested-With': 'XMLHttpRequest'
	},
	url: 'https://dev.xxxx.com/api/xxx-excel/export/async/process',
 	data: param
})

表头合并方案

场景1:

'tableHead': [{
	'key': 'id',
	'title': '用户ID'
}, {
	'key': 'name',
	'title': '用户姓名'
}]

excel1.png

场景2:

'tableHead': [{
	'title': '统计''children': [{
		'key': 'total',
		'title': '总计'
	}, {
		'key': 'avg',
		'title': '平均'
	}]
}, {
	'key': 'name',
	'title': '用户姓名'
}]

excel2.png

场景3:

'tableHead': [{
	'title': '统计''children': [{
		'title': '总计',
		'children': [{
			'key': 'a',
			'title': 'A'
		}, {
			'key': 'b',
			'title': 'B'
		}]
	}, {
		'key': 'avg',
		'title': '平均'
	}]
}, {
	'key': 'name',
	'title': '用户姓名'
}]

excel3.png