首页 / 资讯中心 / 文章详情

JeecgBoot中Autopoi导出Excel实战:复杂表头、动态列与大数据量优化

JeecgBoot中Autopoi导出Excel实战:复杂表头、动态列与大数据量优化 ★ FEATURED ARTICLE
1. 从一次真实需求说起为什么我要在JeecgBoot里折腾Autopoi做过企业级后台系统的朋友应该都有体会导出Excel这个需求看起来简单真做起来坑特别多。尤其是用JeecgBoot这类低代码脚手架的时候官方封装了一套Autopoi工具用起来确实省事但一旦遇到复杂表头、动态列、大数据量导出文档里那点示例就不够看了。我最近接手的一个项目后台是基于JeecgBoot搭建的运营管理系统里面有个报表模块需要导出将近三十个字段的明细数据还要求支持合并单元格、自定义列宽、按条件动态隐藏某些列。一开始我想着直接用EasyExcel或者POI原生API算了但项目里已经深度集成了Autopoi而且JeecgBoot的代码生成器默认导出的模板就是基于Autopoi的如果另起炉灶后期维护会非常割裂。所以最后还是决定把Autopoi吃透在它的框架内解决问题。这篇文章就是我这段时间折腾下来的完整总结。我会从Autopoi的设计思路讲起然后一步步拆解怎么在JeecgBoot里用它导出Excel包括基础用法、复杂表头处理、动态列控制、大数据量优化以及我踩过的那些坑。如果你也在用JeecgBoot或者正在找一个能跟SpringBoot无缝配合的Excel导出方案这篇内容应该能帮你省不少时间。2. Autopoi到底是什么先搞清楚它的定位再动手2.1 Autopoi与EasyExcel、原生POI的关系很多人第一次听到Autopoi会有点懵不知道它跟Apache POI、EasyExcel是什么关系。简单来说Autopoi是JeecgBoot团队在原生POI基础上做的一层封装目标是让Excel导入导出变得更简单尤其是在Web场景下。它不像EasyExcel那样完全重写了一套基于SAX的解析引擎而是保留了POI的很多能力同时提供了注解驱动的实体映射和链式调用的导出API。你可以把它理解成“POI的便捷模式”。原生POI写一个导出你得手动创建Workbook、Sheet、Row、Cell还要处理样式、合并、数据类型转换代码量很大。Autopoi把这些重复劳动封装掉了你只需要在实体类上加注解然后调用一个工具方法就能导出。但它又没有完全屏蔽底层遇到复杂需求时你仍然可以拿到Workbook对象做精细控制这一点比EasyExcel灵活。在JeecgBoot的体系里Autopoi是默认的Excel处理工具代码生成器生成的Controller里导出方法用的就是Autopoi的API。所以如果你在用JeecgBoot学会Autopoi基本是绕不开的。2.2 核心注解与实体映射机制Autopoi的核心思路是“注解驱动”。你在一个Java实体类上标注Excel注解每个字段对应Excel里的一列导出的时候Autopoi会根据注解信息自动生成表头和单元格内容。这个机制跟JPA的实体映射很像只不过映射的目标是Excel而不是数据库表。常用的注解属性包括name列标题导出到Excel表头显示的文字width列宽单位是字符数默认大概10左右orderNum列顺序数字越小越靠前format日期格式化比如yyyy-MM-dd HH:mm:ssreplace值替换比如性别字段存的是1和2导出时显示“男”和“女”dictTable、dicCode、dicText字典翻译JeecgBoot的字典机制可以直接在这里配置type导出类型默认是字符串也可以指定为图片、数字等这些注解属性组合起来基本能覆盖80%的常规导出需求。我刚开始用的时候觉得挺方便但后来发现有些场景注解搞不定比如动态列、多级表头这时候就需要绕过注解直接用Autopoi的底层API。2.3 为什么JeecgBoot选择Autopoi而不是EasyExcel这个问题我专门查过一些资料也跟社区里的人聊过。JeecgBoot早期版本其实用的是原生POI后来才封装了Autopoi。选择自研而不是直接集成EasyExcel主要有几个原因。第一是可控性。JeecgBoot作为一个低代码平台需要保证导出功能在各种复杂场景下都能稳定工作如果依赖第三方库遇到bug只能等社区修复自己封装的话可以快速响应。第二是跟字典、权限等内置功能的整合。JeecgBoot有一套自己的字典管理和数据权限体系Autopoi在设计时就考虑到了这些比如Excel注解里可以直接配置字典翻译不需要额外写代码。第三是代码生成器的配套。JeecgBoot的代码生成器会根据数据库表结构自动生成实体类同时把Excel注解也生成好这样开发者拿到代码就能直接导出体验很连贯。当然Autopoi也不是没有缺点。它的社区活跃度不如EasyExcel遇到冷门问题搜不到答案。而且它的文档比较简略很多用法得看源码或者社区帖子才能搞明白。但总体来说在JeecgBoot的生态里用Autopoi是性价比最高的选择。3. 环境准备与基础导出从零跑通第一个Demo3.1 依赖确认与版本匹配在JeecgBoot项目里Autopoi的依赖通常已经包含在jeecg-boot-base-core或者类似的公共模块里了。你可以先在pom.xml里搜一下autopoi确认版本号。我用的这个项目是JeecgBoot 3.x版本对应的Autopoi是1.3.x系列。如果你发现项目里没有可以手动引入dependency groupIdorg.jeecgframework/groupId artifactIdautopoi-web/artifactId version1.3.6/version /dependency这里要注意版本匹配问题。Autopoi的版本跟POI的版本有对应关系如果项目里已经引入了其他版本的POI可能会冲突。我遇到过因为POI版本不一致导致导出时抛NoSuchMethodError的情况排查了半天才发现是依赖冲突。建议用mvn dependency:tree看一下POI的实际版本确保跟Autopoi兼容。3.2 实体类注解配置实战假设我们要导出一个用户列表用户表里有姓名、年龄、性别、生日、部门这几个字段。对应的实体类大概长这样public class UserExportVO { Excel(name 姓名, width 15, orderNum 1) private String name; Excel(name 年龄, width 10, orderNum 2) private Integer age; Excel(name 性别, width 10, orderNum 3, replace {男_1, 女_2}) private Integer gender; Excel(name 生日, width 20, orderNum 4, format yyyy-MM-dd) private Date birthday; Excel(name 部门, width 20, orderNum 5, dictTable sys_depart, dicCode id, dicText depart_name) private String departId; // getter和setter省略 }这里有几个细节值得说一下。replace属性的格式是“显示值_实际值”多个用逗号分隔。dictTable、dicCode、dicText这三个是配合JeecgBoot字典表使用的dictTable指定字典表名dicCode是字典的code字段dicText是要显示的文本字段。这样配置之后导出时Autopoi会自动去查字典表把departId翻译成部门名称。注意字典翻译是在导出时实时查询数据库的如果导出数据量很大字典查询可能会成为性能瓶颈。后面我会讲怎么优化。3.3 Controller层导出方法编写实体类配好之后Controller里的导出方法就很简单了GetMapping(/exportXls) public ModelAndView exportXls(HttpServletRequest request, UserExportVO userVO) { // 第一步查询数据 ListUserExportVO list userService.queryExportList(userVO); // 第二步创建导出参数 ModelAndView mv new ModelAndView(new JeecgEntityExcelView()); mv.addObject(NormalExcelConstants.FILE_NAME, 用户列表); mv.addObject(NormalExcelConstants.CLASS, UserExportVO.class); mv.addObject(NormalExcelConstants.PARAMS, new ExportParams(用户信息表, 用户信息)); mv.addObject(NormalExcelConstants.DATA_LIST, list); return mv; }这段代码是JeecgBoot里最标准的导出写法。JeecgEntityExcelView是JeecgBoot封装的一个View实现配合ModelAndView使用。ExportParams用来设置表头标题和Sheet名称。NormalExcelConstants里定义了几个常量分别对应文件名、实体类、导出参数和数据列表。我第一次写的时候有个疑问为什么不用HttpServletResponse直接写流后来看源码才明白JeecgEntityExcelView在内部处理了响应流的写入而且它还集成了JeecgBoot的权限校验和日志记录。用ModelAndView的方式更符合SpringMVC的设计模式也更容易做单元测试。3.4 前端调用与参数传递前端调用导出接口的时候通常是用window.location.href或者动态创建a标签的方式因为导出是文件下载不能用Ajax。JeecgBoot的前端封装了一个exportXls方法在/api/manage里用起来很方便import { exportXls } from /api/manage exportXls(/user/exportXls, { name: this.queryParam.name, age: this.queryParam.age })这个方法的内部实现就是拼接URL参数然后触发浏览器下载。需要注意的是如果查询条件里有中文或者特殊字符要确保做了URL编码否则后端接收到的参数可能会乱码。JeecgBoot的封装里已经处理了这个问题但如果你自己写导出逻辑这一点要留意。4. 复杂场景突破多级表头、动态列与合并单元格4.1 多级表头的实现思路基础导出跑通之后真正的挑战才刚开始。我接到的需求里表头不是简单的一行而是有两级第一级是大类第二级是具体字段。比如“基本信息”下面有姓名、年龄、性别“联系方式”下面有手机号、邮箱。Autopoi的Excel注解本身不支持多级表头但它的底层API可以。思路是这样的不用注解驱动的方式而是手动创建ExcelExportUtil然后通过ExportParams和Sheet对象来构建。// 创建一级表头 ListExcelExportEntity entityList new ArrayList(); ExcelExportEntity baseInfo new ExcelExportEntity(基本信息, baseInfo); ListExcelExportEntity baseChildren new ArrayList(); baseChildren.add(new ExcelExportEntity(姓名, name)); baseChildren.add(new ExcelExportEntity(年龄, age)); baseChildren.add(new ExcelExportEntity(性别, gender)); baseInfo.setList(baseChildren); entityList.add(baseInfo); ExcelExportEntity contactInfo new ExcelExportEntity(联系方式, contactInfo); ListExcelExportEntity contactChildren new ArrayList(); contactChildren.add(new ExcelExportEntity(手机号, phone)); contactChildren.add(new ExcelExportEntity(邮箱, email)); contactInfo.setList(contactChildren); entityList.add(contactInfo); // 准备数据 ListMapString, Object dataList new ArrayList(); for (UserExportVO user : userList) { MapString, Object map new HashMap(); map.put(name, user.getName()); map.put(age, user.getAge()); map.put(gender, user.getGender() 1 ? 男 : 女); map.put(phone, user.getPhone()); map.put(email, user.getEmail()); dataList.add(map); } // 导出 ExportParams params new ExportParams(用户信息表, 用户信息); Workbook workbook ExcelExportUtil.exportExcel(params, entityList, dataList);这里的关键是ExcelExportEntity的setList方法它允许你给一个列设置子列从而形成多级表头。数据部分用Map来承载key对应ExcelExportEntity的key。这种方式比注解灵活得多但代码量也上去了。实操心得多级表头的数据行合并是个容易出问题的地方。如果某个一级表头下的子列数据需要合并单元格Autopoi提供了setMergeVertical和setMergeHorizontal方法但合并逻辑需要自己控制。我建议在数据量不大的情况下用数据量大了合并操作会很耗性能。4.2 动态列控制的三种方案动态列是我遇到的最棘手的需求。所谓动态列就是导出的列不是固定的而是根据用户的选择或者某些条件动态决定。比如用户可以在界面上勾选要导出哪些字段后端根据勾选结果生成对应的Excel。Autopoi的注解方式是静态的实体类定义好之后列就固定了。要实现动态列我总结了三种方案。第一种是“动态实体类”。用CGLIB或者ByteBuddy在运行时生成一个带注解的类。这个方案理论上可行但实现复杂而且容易出兼容性问题我不推荐。第二种是“MapExcelExportEntity”。就是上面多级表头里用的方式完全绕过注解用ExcelExportEntity列表来定义列。列的增减就是往列表里加或者删元素非常灵活。这个方案我用的最多稳定性也最好。第三种是“模板占位符”。先做一个Excel模板里面用占位符标记哪些列是动态的导出时替换占位符。这个方案适合格式要求非常严格的场景但实现起来也不简单需要用到POI的模板引擎。综合下来第二种方案是性价比最高的。下面是一个动态列的示例public Workbook dynamicExport(ListString selectedFields, ListMapString, Object data) { ListExcelExportEntity entityList new ArrayList(); // 字段映射表 MapString, String fieldNameMap new LinkedHashMap(); fieldNameMap.put(name, 姓名); fieldNameMap.put(age, 年龄); fieldNameMap.put(gender, 性别); fieldNameMap.put(phone, 手机号); fieldNameMap.put(email, 邮箱); fieldNameMap.put(departName, 部门); // 根据用户选择构建列 for (String field : selectedFields) { if (fieldNameMap.containsKey(field)) { ExcelExportEntity entity new ExcelExportEntity(fieldNameMap.get(field), field); entity.setWidth(15); entityList.add(entity); } } ExportParams params new ExportParams(自定义导出, 数据); return ExcelExportUtil.exportExcel(params, entityList, data); }这个方案的好处是列的顺序、数量完全由selectedFields决定前端传什么就导什么。而且字段名和显示名的映射可以配置在数据库或者缓存里改起来不用动代码。4.3 合并单元格的正确姿势合并单元格在报表类导出里很常见比如同一个部门的用户部门列只显示一次下面的单元格合并。Autopoi提供了CellRangeAddress的封装但用起来还是得小心。我一般的做法是在数据准备阶段就把需要合并的行号算好然后在导出后遍历Sheet进行合并。比如按部门合并// 假设dataList已经按部门排序 int startRow 1; // 从第1行开始0是表头 String currentDepart null; for (int i 0; i dataList.size(); i) { String depart (String) dataList.get(i).get(departName); if (!depart.equals(currentDepart)) { if (currentDepart ! null i - startRow 1) { // 合并startRow到i-1行 sheet.addMergedRegion(new CellRangeAddress( startRow, i - 1, departColIndex, departColIndex)); } currentDepart depart; startRow i; } } // 处理最后一组 if (dataList.size() - startRow 1) { sheet.addMergedRegion(new CellRangeAddress( startRow, dataList.size() - 1, departColIndex, departColIndex)); }这里有几个坑要注意。第一合并之前数据必须按合并字段排序否则合并会出错。第二CellRangeAddress的行号列号都是从0开始的表头占了一行的话数据从第1行开始。第三合并后的单元格样式需要单独设置否则边框可能会丢失。第四如果合并的列有背景色合并后只有左上角单元格的样式会保留其他单元格的样式会丢失需要手动补。注意大数据量下频繁调用addMergedRegion性能很差。如果合并规则比较简单可以考虑在数据层面处理比如同一个部门只输出一行其他行该列留空这样就不需要真正的单元格合并了。视觉上看起来是一样的但性能好很多。5. 性能优化与大数据量导出实战5.1 大数据量导出的内存问题Autopoi底层用的是POI的XSSFWorkbook这个类会把整个Excel文件加载到内存里。如果导出十万条数据每条数据二十个字段内存占用可能达到几百兆甚至上G。我实测过导出五万条数据的时候JVM的堆内存直接飙到800M如果并发几个导出请求服务很容易OOM。解决这个问题有两个方向。第一个方向是换用POI的SXSSFWorkbook这是POI提供的流式写入Workbook它只保留一定数量的行在内存里其他的写到临时文件。Autopoi的ExcelExportUtil默认用的是XSSFWorkbook但你可以手动创建SXSSFWorkbook然后调用ExcelExportUtil.exportExcel的重载方法。// 创建SXSSFWorkbook保留100行在内存 SXSSFWorkbook workbook new SXSSFWorkbook(100); // 设置临时文件压缩减少磁盘占用 workbook.setCompressTempFiles(true); ExportParams params new ExportParams(大数据量导出, 数据); // 使用重载方法传入workbook ExcelExportUtil.exportExcel(params, entityList, dataList, workbook);第二个方向是分页导出。如果数据量实在太大可以分批查询、分批写入最后合并成一个文件。但这个方案实现复杂而且用户体验不好要等很久。我一般优先用SXSSFWorkbook配合合理的分页大小基本能解决大部分场景。5.2 字典翻译的性能陷阱前面提到Excel注解的字典翻译是在导出时实时查库的。如果导出五千条数据每条数据有三个字典字段那就是一万五千次数据库查询。这个开销是灾难性的。我踩过这个坑之后总结了几个优化方案。第一个方案是提前把字典数据加载到缓存里然后在导出前手动替换。具体做法是不用注解的dictTable而是自己查字典表构建一个MapString, String然后在数据准备阶段就把code转成text。// 提前加载字典 MapString, String departMap departService.getAllDepartMap(); // 数据转换 for (UserExportVO user : userList) { user.setDepartName(departMap.get(user.getDepartId())); }第二个方案是用JeecgBoot的字典缓存。JeecgBoot本身有一套字典缓存机制DictUtils里可以获取到缓存的字典数据。你可以直接调用DictUtils.getDictText(dictCode, value)来获取翻译结果这个方法走的是缓存不会查库。第三个方案是如果字典数据量不大可以在应用启动时全量加载到本地缓存导出时直接从缓存取。这个方案性能最好但要注意字典变更时的缓存刷新问题。5.3 异步导出与进度反馈当导出数据量很大、耗时很长的时候同步导出会导致HTTP请求超时用户体验也很差。这时候就需要异步导出用户点击导出后后端启动一个异步任务立即返回一个任务ID前端轮询任务状态完成后提供下载链接。JeecgBoot里可以用Async注解配合线程池来实现。大概思路是Async(exportExecutor) public void asyncExport(String taskId, UserExportVO query) { try { // 更新任务状态为进行中 taskService.updateStatus(taskId, running); // 查询数据 ListUserExportVO list userService.queryExportList(query); // 导出到临时文件 String filePath /tmp/export/ taskId .xlsx; // ... 导出逻辑 // 更新任务状态为完成记录文件路径 taskService.updateStatus(taskId, completed, filePath); } catch (Exception e) { taskService.updateStatus(taskId, failed, e.getMessage()); } }前端拿到taskId后每隔几秒调一次状态查询接口完成后显示下载按钮。这个方案实现起来不算复杂但能显著提升用户体验。需要注意的是临时文件要定期清理否则磁盘会被占满。我一般设置一个定时任务每天凌晨清理三天前的导出文件。实操心得异步导出的线程池大小要控制好。如果线程池太大同时跑多个导出任务内存和CPU都会吃不消。我一般设置核心线程数为2最大线程数为4队列容量为10。超过队列容量的任务直接拒绝提示用户稍后再试。6. 常见问题排查与避坑指南6.1 导出文件打不开或内容乱码这个问题我遇到过好几次原因各不相同。最常见的是响应头设置不对。如果Content-Type不是application/vnd.ms-excel或者application/octet-stream浏览器可能会把文件当成HTML处理。另外文件名编码也很关键中文文件名要用URLEncoder.encode处理否则在某些浏览器上会乱码。还有一个原因是数据里包含了非法字符。Excel对某些控制字符是敏感的比如\u0000到\u001F之间的字符如果数据里包含这些字符导出的文件可能会损坏。解决办法是在导出前对字符串做一次清洗public static String cleanInvalidChars(String str) { if (str null) return null; return str.replaceAll([\\x00-\\x1F\\x7F], ); }6.2 日期格式不对或显示为数字日期字段导出时显示为一串数字这是POI的经典问题。原因是单元格类型被设置成了数值型而日期在Excel内部就是用一个数字表示的。解决办法是在Excel注解里指定format属性Autopoi会自动把单元格类型设置为日期型并应用格式。如果用的是ExcelExportEntity的方式需要手动设置ExcelExportEntity dateEntity new ExcelExportEntity(生日, birthday); dateEntity.setFormat(yyyy-MM-dd); // 或者更精细的控制 MapString, Object styleMap new HashMap(); styleMap.put(dataFormat, 14); // 14是Excel内置的日期格式 dateEntity.setStyle(styleMap);6.3 大数据量导出时OOM的排查思路OOM是导出功能最头疼的问题。排查的时候我一般按这个顺序来先看JVM堆内存设置-Xmx是不是太小了然后看导出代码用的是XSSFWorkbook还是SXSSFWorkbook再看数据查询是不是一次性把全部数据加载到List里了最后看有没有不必要的对象持有比如在循环里不断创建大对象。一个容易被忽略的点是Autopoi在导出时会为每个单元格创建样式对象。如果每个单元格都创建一个新样式样式对象会迅速耗尽内存。正确的做法是复用样式// 错误做法每个单元格都创建新样式 for (Row row : sheet) { CellStyle style workbook.createCellStyle(); style.setBorderBottom(BorderStyle.THIN); // ... } // 正确做法创建一次复用 CellStyle style workbook.createCellStyle(); style.setBorderBottom(BorderStyle.THIN); for (Row row : sheet) { for (Cell cell : row) { cell.setCellStyle(style); } }6.4 常见问题速查表问题现象可能原因排查方法解决方案文件下载后打不开响应头Content-Type错误查看浏览器开发者工具的网络请求设置正确的Content-Type和Content-Disposition中文文件名乱码文件名未编码检查响应头中的filename使用URLEncoder.encode编码文件名日期显示为数字单元格类型为数值型检查Excel的format属性设置format或手动设置单元格样式导出数据缺失查询条件有误或分页限制检查SQL和查询参数确认查询逻辑去掉不必要的分页导出速度极慢字典翻译频繁查库查看SQL执行日志使用缓存或提前加载字典数据内存溢出使用XSSFWorkbook且数据量大查看JVM堆内存监控改用SXSSFWorkbook或分页导出合并单元格边框丢失合并后未设置样式检查合并区域的单元格样式合并后重新设置边框样式数字精度丢失数值被转为double检查字段类型和单元格格式使用BigDecimal并设置文本格式7. 一些让导出更稳的工程化建议7.1 导出配置的外部化管理在实际项目里导出的列、顺序、宽度这些信息最好不要硬编码在Java代码里。我一般的做法是建一张导出配置表记录每个导出场景的列配置包括字段名、显示名、宽度、顺序、是否默认导出等。导出的时候根据场景编码查配置动态构建ExcelExportEntity列表。这样产品经理要调整导出列的时候改数据库就行不用重新发版。配置表的结构大概是这样CREATE TABLE export_config ( id BIGINT PRIMARY KEY, scene_code VARCHAR(50) NOT NULL, field_name VARCHAR(50) NOT NULL, display_name VARCHAR(100) NOT NULL, width INT DEFAULT 15, order_num INT DEFAULT 0, default_selected TINYINT DEFAULT 1, create_time DATETIME, update_time DATETIME );这个方案的好处是灵活坏处是增加了复杂度。如果项目里导出场景不多直接用注解就够了没必要过度设计。7.2 导出日志与审计企业级系统里导出操作通常需要记录日志方便审计。我一般会在导出方法里加一段日志记录记录谁在什么时间导出了什么数据、数据量多大、耗时多久。这些信息对于排查问题和安全审计都很有用。long startTime System.currentTimeMillis(); // ... 导出逻辑 long cost System.currentTimeMillis() - startTime; log.info(用户[{}]导出[{}]数据量[{}]耗时[{}]ms, userId, sceneCode, dataList.size(), cost);如果数据敏感还可以记录导出的具体查询条件方便追溯。但要注意日志里不要包含敏感数据本身只记录条件即可。7.3 导出模板的版本管理如果导出格式经常调整建议对导出模板做版本管理。每次调整列配置时生成一个新版本旧版本保留。这样如果新版本有问题可以快速回滚。实现方式可以是在配置表里加一个version字段导出时指定版本号不指定则用最新版本。这个方案在金融、医疗等对数据格式要求严格的行业特别有用。我做过一个项目监管要求导出格式必须与备案版本一致有了版本管理之后每次格式调整都有据可查省了很多扯皮的事。7.4 前端导出体验优化后端做得再好前端体验跟不上也不行。我一般会在前端加几个小优化导出按钮点击后显示loading状态防止用户重复点击导出请求超时时间设置长一点比如60秒如果用了异步导出显示进度条或者轮询状态提示导出完成后自动关闭loading并提示成功。还有一个细节是导出文件的命名。默认的文件名最好包含导出时间和关键查询条件比如“用户列表_20240101_张三.xlsx”这样用户下载多个文件后容易区分。JeecgBoot的NormalExcelConstants.FILE_NAME支持动态设置可以在Controller里拼接。8. 关于Autopoi导出我最后想说的几点Autopoi这个工具用熟了之后确实能省不少事尤其是跟JeecgBoot配合的时候代码生成器直接生成带注解的实体类导出功能几乎零成本。但它的文档和社区支持确实一般很多用法得靠自己摸索。我建议刚上手的朋友先把官方示例跑通然后从简单导出开始逐步增加复杂度。遇到问题先看源码Autopoi的源码不算复杂核心类就那么几个看懂了之后很多问题自然就明白了。另外不要迷信注解。注解适合简单场景复杂场景该用底层API就用底层API。我见过有人为了用注解实现动态列硬生生搞了一套运行时生成类的方案维护成本极高完全没必要。工具是为人服务的怎么方便怎么来。最后说一个我踩过的坑Autopoi在导出时默认会创建一个临时文件如果导出过程中抛异常临时文件可能不会被清理。长时间运行下来临时目录会被占满。解决办法是在导出方法的finally块里手动清理或者配置一个定时清理任务。这个坑很隐蔽平时不会出问题但服务器跑久了就会突然磁盘告警排查起来很费时间。导出这个功能说简单也简单说复杂也复杂。关键是要根据实际场景选择合适的方案不要过度设计也不要偷懒省事。希望这篇总结能帮到正在跟Autopoi较劲的朋友少走点弯路。
阅读完成 · 觉得有帮助?
咨询建站