赞
踩
众所周知,Excel文件分为03版本excel和07版本excel文件
03版最多容量为65535*256,而07版单张sheet为1048576*16384,总容量无限制
03版的后缀名为.xls,07版的后缀是.xlsx
在Java领域中,对excel文件进行解析、生成比较闻名的框架是Apache poi和阿里的EasyExcel。但俩者有显著的区别。
POI技术在解析Excel文件时,是一次性将表里的数据加载到内存中,这很可能会导致内存溢出的问题,尤其是当excel的存在大数据量时,就很大可能会超出可用内存的内存限制。
所以POI技术非常消耗内存。
EasyExcel在解析文件时,是从磁盘上一行一行的进行读取数据,并通过设置监听器(观察者的模式)进行通知。因此,大大减少了占用内存。
在了解的俩者区别后,我选择了使用EasyExcel来对excel文件进行处理
在jdk版本为1.8及1.8以上时,导入EasyExcel的版本最好为3.xx的版本。
- //通用版本
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>easyexcel</artifactId>
- <version>3.1.4</version>
- </dependency>
-
- //若依赖中存在导入或不使用Slf4j依赖则需要将其中封装的取消导入
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>easyexcel</artifactId>
- <version>3.1.4</version>
- <exclusions>
- <exclusion>
- <groupId>org.slf4j</groupId>
- <artifactId>slf4j-api</artifactId>
- </exclusion>
- </exclusions>
- </dependency>
-
- /**
- * 用户ID
- */
- @TableId(type = IdType.AUTO)
- @ExcelIgnore
- private Long userId;
- /**
- * 用户名
- */
- @ExcelProperty("用户名")
- private String username;
- /**
- * 密码
- */
- @ExcelIgnore
- private String password;
- /**
- * 性别
- */
- @ExcelProperty(value = "性别",converter = SexUtil.class)
- private int gender;
- /**
- * 手机号
- */
- @ExcelProperty("手机号")
-
- /**
- * 邮箱
- */
- @ExcelProperty("邮箱")
- private String email;
- /**
- * 真实姓名
- */
- @ExcelProperty("姓名")
- private String realName;
- /**
- * 状态 0:禁用 1:正常
- */
- @ExcelIgnore
- private Integer status;
-
- /**
- * 创建时间
- */
- // @ExcelIgnore
- @DateTimeFormat(value = "yyyy-MM-dd")
- @ExcelProperty(value = "创建时间",converter = DateConverter.class )
- private Date createTime;
-
- @TableLogic(value = "0", delval = "1")
- @ExcelIgnore
- private int del;
对于导入导出的显示在excel字段 可以添加注解:@ExcelProperty、@ExcelIgnore
前者是需要将字段进行处理,后者是不需要显示在excel则添加。如果一个属性什么注解都没有,则将显示原本的如:属性为:email,则在excel也显示 email。添加了@ExcelProperty("邮箱") 则显示 邮箱。
在进行对性别属性的数据库存储时,我采用了存储数字(0:女、1:男)这种方式存储的原因在此我就不多说了,懂的都懂。那为什么我要在此说明这个属性呢?因为在进行文件的导入导出时,总不能对性别进行导入1、0或者导出1、0供用户查看吧?所以在此仍需写一个处理性别数据转换的类。在EasyExcel提供了Converter接口。在处理文件的导入导出时,此接口提供了俩个方法:convertToJavaData和convertToExcelData。具体细看如下
- import com.alibaba.excel.converters.Converter;
- import com.alibaba.excel.metadata.GlobalConfiguration;
- import com.alibaba.excel.metadata.data.ReadCellData;
- import com.alibaba.excel.metadata.data.WriteCellData;
- import com.alibaba.excel.metadata.property.ExcelContentProperty;
-
- public class SexUtil implements Converter<Integer> {
- /**
- * 导入时,对数据的男、女进行转换
- * @param cellData
- * @param contentProperty
- * @param globalConfiguration
- * @return
- * @throws Exception
- */
- @Override
- public Integer convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
- String value = cellData.getStringValue();
- switch (value){
- case "男":
- return 1;
- case "女":
- return 0;
- default:
- return -1;
- }
- }
-
- /**
- * 导出时 对数据的1、0进行转换
- * @param value
- * @param contentProperty
- * @param globalConfiguration
- * @return
- * @throws Exception
- */
- @Override
- public WriteCellData<?> convertToExcelData(Integer value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) throws Exception {
- switch (value){
- case 1:
- return new WriteCellData<>("男");
- case 0:
- return new WriteCellData<>("女");
- default:
- return new WriteCellData<>("未知");
- }
- }
- }
那怎么使用这个转换类?麻烦细看实体类中的性别字段
- /**
- * 文件上传
- * @param file
- * @return
- * @throws IOException
- */
- @PostMapping("/upload")
- @ResponseBody
- public R upload(MultipartFile file) throws IOException {
- EasyExcel.read(file.getInputStream(), EmpEntity.class, new
- UploadEmpListener(empService)).sheet().doRead();
- return R.ok();
- }
根据EasyExcel官网提供信息:使用read方法 并且设置一个监听类。
EasyExcel.read(file.getInputStream(), EmpEntity.class, new
UploadEmpListener(empService)).sheet().doRead();至于参数传service还是dao就看具体的用法
- package com.wedu.modules.emp.listener;
-
- import com.alibaba.excel.context.AnalysisContext;
- import com.alibaba.excel.read.listener.ReadListener;
- import com.alibaba.excel.util.ListUtils;
- import com.alibaba.fastjson.JSON;
- import com.wedu.modules.emp.entity.EmpEntity;
- import com.wedu.modules.emp.service.EmpService;
- import lombok.extern.slf4j.Slf4j;
-
- import java.util.List;
-
- @Slf4j
- public class UploadResignListener implements ReadListener<EmpEntity> {
- /**
- * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
- */
- private static final int BATCH_COUNT = 100;
- /**
- * 缓存的数据
- */
- private List<EmpEntity> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
- /**
- * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
- */
- private EmpEntity empService;
-
-
- /**
- * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
- *
- * @param empService
- */
- public UploadResignListener(EmpEntity empService) {
- this.empService= empService;
- }
-
- /**
- * 这个每一条数据解析都会来调用
- *
- * @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
- * @param context
- */
- @Override
- public void invoke(ResignEntity data, AnalysisContext context) {
- log.info("解析到一条数据:{}", JSON.toJSONString(data));
- cachedDataList.add(data);
- // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
- if (cachedDataList.size() >= BATCH_COUNT) {
- saveData();
- log.info("准备清理!");
- // 存储完成清理 list
- cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
- }
- }
-
- /**
- * 所有数据解析完成了 都会来调用
- *
- * @param context
- */
- @Override
- public void doAfterAllAnalysed(AnalysisContext context) {
- // 这里也要保存数据,确保最后遗留的数据也存储到数据库
- saveData();
- log.info("所有数据解析完成!");
- }
-
- /**
- * 加上存储数据库
- */
- private void saveData() {
- log.info("{}条数据,开始存储数据库!", cachedDataList.size());
- empService.saveBatch(cachedDataList);
- log.info("存储数据库成功!");
- }
- }
我使用的是SpringBoot+Mybatis-plus 所以我直接使用了mybatis-plus提供的批量插入方法
我使用的Element-UI提供的组件
- <el-upload
- action="http://localhost:8080/emp/info/upload"
- :headers="tokenInfo"
- style="display: inline-block"
- :show-file-list="true"
- :before-upload="headUpload"
- :on-success="handleExcelImportSuccess"
- name="file">
- <el-button type="primary" style="margin: 10px " >
- <template #icon>
- <el-icon><Upload /></el-icon>
- </template>
- 导入
- </el-button>
- </el-upload>
action:请求的路径
:headers 我使用了jwt令牌,携带了token往后端进行判断。看各位具体使用,不一定使用
:show-file-list="true" 为true时,则显示上传的文件
:before-upload="headUpload" 顾名思义是上传前的操作
:on-success="handleExcelImportSuccess" 则是成功后的操作
具体的我们可去Element-UI官网查看更多的使用。接着我们看看上传前需要什么操作
- headUpload(file) {
- const isExcel1 = file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet';
- const isExcel2 = file.type === 'application/vnd.ms-excel';
- const isLt10M = file.size / 1024 / 1024 < 10;
-
- const filexlsx =!(file.type === 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
- const filexls = !(file.type === 'application/vnd.ms-excel');
- if(filexlsx && filexls) {
- this.$message.error('上传Excel文件只能是xlsx/xls 格式!');
- return false;
- }
- if (!isLt10M) {
- this.$message.error('上传Excel文件大小不能超过 10MB!');
- }
- return (isExcel1 || isExcel2) && isLt10M;
- }
先前我曾说明excel是有俩种版本,则分别对应了'application/vnd.ms-excel'、'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
则在此方法中,我进行了上传前的判断,是否为这俩种excel中的其中一种,还有文件的大小是否超过10M
然后就是上传后的处理,查看响应。由于后端我使用的是统一格式类进行返回。
- handleExcelImportSuccess(response,file){
- if(response.msg == 'success'){
- alert("导入成功")
- }
- }
- @PostMapping("/down")
- public void create(@RequestBody Long[] userIds, HttpServletResponse response) throws IOException {
-
- List<SysUserEntity> list = empService.getDown(Arrays.asList(userIds));
- String fileName = "数据测试";
- ExcelUtils.createExcel(response,fileName,list,SysUserEntity.class);
- }
我是在前端选择要导出的数据,并只传回对应的id在到后端进行查询,查询在此就不展示了,相信对于各位看官都是轻松的事情。取到数据后,传到我自定义的工具类中。
-
- import com.alibaba.excel.EasyExcel;
- import com.alibaba.excel.support.ExcelTypeEnum;
- import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
- import com.alibaba.excel.write.metadata.style.WriteCellStyle;
- import com.alibaba.excel.write.metadata.style.WriteFont;
- import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
- import com.wedu.config.CustomCellWriteWidthConfig;
-
- import org.apache.poi.ss.usermodel.FillPatternType;
- import org.apache.poi.ss.usermodel.HorizontalAlignment;
- import org.apache.poi.ss.usermodel.IndexedColors;
- import org.apache.poi.ss.usermodel.VerticalAlignment;
-
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.net.URLEncoder;
- import java.util.List;
-
- public class ExcelUtils {
-
- public static <T> void createExcel(HttpServletResponse response, String fileName, List<T> list,Class<T> clazz) throws IOException {
- response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8");
- response.setCharacterEncoding("utf-8");
- fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+","%20");
- response.setHeader("Content-disposition", "attachment;filename=" + fileName+".xlsx");
-
- //定义ExcelWriterSheetBuilder
- ExcelWriterSheetBuilder excelWriterSheetBuilder = EasyExcel
- .write(response.getOutputStream())
- .head(clazz)
- .excelType(ExcelTypeEnum.XLSX)
- .registerWriteHandler(new CustomCellWriteWidthConfig())
- .sheet("sheet");
- //设置头样式
- WriteCellStyle headWriteCellStyle = new WriteCellStyle();
- WriteFont headWriteFont = new WriteFont();
- //设置表头字体
- headWriteFont.setFontName("宋体");
- //设置设置表头字体大小
- headWriteFont.setFontHeightInPoints((short) 13);
- //设置表头是否加粗
- headWriteFont.setBold(true);
- //填充到表头样式
- headWriteCellStyle.setWriteFont(headWriteFont);
- headWriteCellStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
- headWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
- headWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
-
-
-
- //设置内容格式
- WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
- WriteFont contentWriteFont = new WriteFont();
- // 设置内容样式
- contentWriteFont.setFontName("宋体");
- contentWriteFont.setFontHeightInPoints((short) 12);
- contentWriteCellStyle.setWriteFont(contentWriteFont);
-
- HorizontalCellStyleStrategy horizontalCellStyleStrategy =
- new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
- //设计内容居中
- contentWriteCellStyle.setHorizontalAlignment(HorizontalAlignment.CENTER);
- contentWriteCellStyle.setVerticalAlignment(VerticalAlignment.CENTER);
- //设置内容自动换行
- // contentWriteCellStyle.setWrapped(true);
- excelWriterSheetBuilder.registerWriteHandler(horizontalCellStyleStrategy);
-
- //调用doWrite方法
- excelWriterSheetBuilder.doWrite(list);
- }
-
- }
无需返回值,在此后端的导出就结束了。
- down(){
- var userIds = this.dataListSelections.map(item => {
- return item.userId
- })
- this.$http({
- url: this.$http.adornUrl('/emp/info/down'),
- method: 'post',
- responseType: 'blob',
- data: this.$http.adornData(userIds, false)
- }).then(response => {
- // 创建一个 URL 对象,指向返回的二进制数据
- const url = window.URL.createObjectURL(new Blob([response.data]));
- // 创建一个 <a> 元素,设置其属性,模拟点击下载
- const link = document.createElement('a');
- link.href = url;
- link.setAttribute('download', '员工信息.xlsx'); // 设置下载文件的默认名称
- document.body.appendChild(link);
- link.click();
- // 清理创建的 URL 对象
- window.URL.revokeObjectURL(url);
- }).catch(error => {
- console.error('下载失败', error);
- });
- }
首先我们应当遍历我们选中的id。我将选中的id都进行存储到dataListSelections的数组中
在<el-table>标签的属性中@selection-change="selectionChangeHandle"定义选中方法
然后 在return 中 定义 dataListSelections: [] 数组
selectionChangeHandle (val) {
this.dataListSelections = val
}
这样就能实现将id存储到数组中
导出时仍需对响应进行处理,处理的不是后端,因为后端没有返回值。而是处理导出的文件。
到此,导入导出就成功实现了。我不推荐各位只是cv工程,还是可以多看看官网提供的使用方法。若小弟存在错误的地方,各位也可以提出来一起学习!哈哈
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。