若依框架自带的Excel导出工具类——ExcelUtil基于org.apache.poi
1 Apache API
1.1 Apache POI简介
Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程序对Microsoft Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”。
HSSF一读写Microsoft Excel XLS
xSSF一读写Microsoft Excel OOXML XLSX
HWPF一读写Microsoft Word Doc
HSLF一提供读写Microsoft PowerPoint
Java类
HSSFWorkbook:用于操作Excel2003以前(包括2003)的版本,扩展名是.xls;
XSSFWorkbook:用于操作Excel2007后的版本,扩展名是.xlsx;
SXSSFWorkbook:用于操作Excel2007后的版本,扩展名是.xlsx;
1.2 使用
1.2.1 Poi对象
主要使用一下几个对象:
- XSSFWorkbook;工作簿(即整个excel文件)
- XSSFSheet:工作表(即excel文件的一个选项卡,选项卡在可视化界面下方)
- Row:行(即一整行)
- Cell:单元格(即一格)
1.2.2 导入Excel数据
步骤
- 创建工作簿
- 获取工作表
- 遍历工作表获得行对象
- 遍历行对象获取单元格对象
- 获得单元格中的值
实例:
public void test() throws IOException {
//1. 获取工作簿
XSSFWorkbook sheets = new XSSFWorkbook("fileUrl");
//2. 获取工作表
//sheets.getSheet(); 根据选项卡名称,即工作表名称
//根据索引获取工作表
XSSFSheet sheetAt = sheets.getSheetAt(0);
//3.1. 获取行
for (Row cells : sheetAt) {
//4.1. 获取单元格
for (Cell cell : cells) {
//获取单元格中的值
String value = cell.getStringCellValue();
System.out.println(value);
}
}
//5. 释放资源
sheets.close();
}
使用索引读取每行中的单元格:
//3.2. 获取行
//获取最后一行索引
int lastRowNum = sheetAt.getLastRowNum();
for (int i = 0; i<lastRowNum; i++) {
//获取当前行数据
XSSFRow row = sheetAt.getRow(i);
if (row != null) {
//获取当前行最后一个单元格索引
short lastCellNum = row.getLastCellNum();
for (int j = 0; j < lastCellNum; j++) {
//获取当前党员格数据
XSSFCell cell = row.getCell(j);
if (cell != null) {
String stringCellValue = cell.getStringCellValue();
System.out.println(stringCellValue);
}
}
}
}
1.2.3 导出Excel
步骤
- 创建一个Excel文件
- 创建工作表
- 创建行
- 创建单元格赋值
- 通过输出流将对象下载到磁盘,或者发送给网络中
public void test() throws IOException {
//1. 创建工作薄
XSSFWorkbook sheets = new XSSFWorkbook();
//2. 创建工作表
XSSFSheet sheet = sheets.createSheet("工作表一");
//3. 创建行
for (int i = 0; i < 3; i++) {
XSSFRow row = sheet.createRow(i);
//4. 创建单元格
row.createCell(0).setCellValue("a" + i);
row.createCell(1).setCellValue("b" + i);
row.createCell(2).setCellValue("c" + i);
}
//5. 创建输出流
FileOutputStream fileOutputStream = new FileOutputStream("fileUrl");
//将内容写入文件
sheets.write(fileOutputStream);
//刷新
fileOutputStream.flush();
//6. 释放资源
fileOutputStream.close();
sheets.close();
System.out.println("写入成功");
}
2 若依ExcelUtil
2.1 注解
若依平台通过Excel注解在类的属性上标注,定义属性在Excel表中的映射关系,使用@Excel和@Excels注解,后者是前者的数据集;
注解路径:/ruoyi-common/src/main/java/com/ruoyi/common/annontation
案例
// 单个字段导出
@Excel(name = "员工", targetAttr = "deptName", type = Type.EXPORT)
private User user;
// 多个字段导出
@Excels({
@Excel(name = "员工", targetAttr = "deptName", type = Type.EXPORT),
@Excel(name = "部门负责人", targetAttr = "leader", type = Type.EXPORT)
})
private User user;
在@Excel注解中,有多种方法用来定义字段的规则,如下:
方法 | 功能 |
---|---|
sort() | 导出时在excel中排序 |
name() | 导出到Excel中的名字 |
dateFormat() | 日期格式, 如: yyyy-MM-dd |
dictType() | 如果是字典类型,请设置字典的type值 (如: sys_user_sex) |
readConverterExp() | 读取内容转表达式 (如: 0=男,1=女,2=未知) |
separator() | 分隔符,读取字符串组内容 |
scale() | BigDecimal 精度 默认:-1(默认不开启BigDecimal格式化) |
roundingMode() | BigDecimal 舍入规则 默认:BigDecimal.ROUND_HALF_EVEN |
cellType() | 导出类型(0数字 1字符串) |
height() | 导出时在excel中每个列的高度 单位为字符 |
width() | 导出时在excel中每个列的宽 单位为字符 |
suffix() | 文字后缀,如% 90 变成90% |
defaultValue() | 当值为空时,字段的默认值 |
prompt() | 提示信息 |
combo() | 设置只能选择不能输入的列内容 |
isExport() | 是否导出数据 |
targetAttr() | 另一个类中的属性名称,支持多级获取,以小数点隔开 |
isStatistics() | 是否自动统计数据,在最后追加一行统计数据总和 |
align() | 导出字段对齐方式(0:默认;1:靠左;2:居中;3:靠右) |
default | 自定义数据处理器 |
args() | 自定义数据处理器参数 |
2.2 ExcelUtil
路径:/ruoyi-common/src/main/java/com/ruoyi/common/utils/poi/ExcelUtil
主要方法:
ExcelUtil构造方法
生成方法有两种,都需要实体类
public ExcelUtil(Class<T> clazz)
{
this.clazz = clazz;
}
public ExcelUtil(List<String> extFields, Class<T> clazz) {
this.extFields = extFields;
this.clazz = clazz;
}
2.2.1 导出
init()初始化方法
调用导出即生成Excel时最终都会调用该方法
public void init(List<T> list, String sheetName, String title, Type type)
{
if (list == null)
{
list = new ArrayList<T>();
}
this.list = list;
this.sheetName = sheetName;
this.type = type;
this.title = title;
createExcelField();
createWorkbook();
createTitle();
}
createExcelField()
作用:得到所有定义字段的信息(fields是注解列表,maxHeight是最大高度)
/**
* 得到所有定义字段
*/
private void createExcelField()
{
// 获得实体类属性的信息:
this.fields = getFields();
//按照Excel注解信息来排序
this.fields = this.fields.stream().sorted(Comparator.comparing(objects -> ((Excel) objects[1]).sort())).collect(Collectors.toList());
//遍历属性,获取最大行高
this.maxHeight = getRowHeight();
}
getFields()获取字段注解信息
作用:传入ExcelUtil的实体类,获取头上带有俩注解之一的属性(包括父类的),经过一点逻辑,然后返回赋值给ExcelUtil的fields属性;同时,如果实体类中有集合属性存在,也会给subMethod和subFields赋值
getRowHeight()获取最大行高
getRowHeight()遍历ExcelUtil的属性fields,即所有@Excel注解的属性,找出最大行高并返回maxHeight * 20
public short getRowHeight()
{
double maxHeight = 0;
//遍历所有字段,获取其注解信息,然后找到最大的height属性,最后返回
for (Object[] os : this.fields)
{
//os[1],即获取注解信息
Excel excel = (Excel) os[1];
maxHeight = Math.max(maxHeight, excel.height());
}
return (short) (maxHeight * 20);
}
createWorkbook() 创建一个工作薄
作用:创建一个工作薄(wb是工作薄对象xlsx版,sheet是工作表对象,styles是样式列表(map集合))
public void createWorkbook()
{
this.wb = new SXSSFWorkbook(500);
//Workbook已有的创建工作表的方法
this.sheet = wb.createSheet();
//给第一个(索引)工作表命名
wb.setSheetName(0, sheetName);
//Map<String, CellStyle> styles
//创建所有的样式,赋值给styles
this.styles = createStyles(wb);
}
createStyles():创建表格样式,如某行单元格的大小、字体等
createTitle():作用:根据ExcelUtil的tittle属性是否为空选择是否创建excel第一行标题
exportExcel()
在init()方法之后,最后都会调用该方法,该方法有两个,一个带参数,一个不带参数
作用:根据init()方法初始化的属性,来创建excel表,并填入数据
区别:
- exportExcel(HttpServletResponse response)利用servlet容器,把文件响应给客户端
- exportExcel()根据配置文件路径,下载文件到指定路径下
2.2.2 导入
importExcel()
作用:实现了从Excel文件中导入数据的功能,将每一行不为空的列转换为指定类型的实体对象,并存储在一个List中返回。
public List<T> importExcel(String sheetName, InputStream is, int titleNum) throws Exception
整体逻辑:
-
首先设置了type为Type.IMPORT,表示当前操作是导入。然后使用WorkbookFactory.create(is)方法创建一个Workbook对象,该对象表示整个Excel文件。接下来,根据sheetName获取对应的工作表对象Sheet,如果sheetName为空,则默认获取第一个工作表。
-
然后,根据Excel文件的类型(.xlsx或.xls),分别调用getSheetPictures07或getSheetPictures03方法获取工作表中的图片数据。
-
接下来,获取工作表中最后一个非空行的行号,并进行循环处理每一行的数据。在循环中,首先获取表头行heard,并根据表头行获取每一列的列名和列号,存储在cellMap中。
-
然后,获取类的字段信息,并根据字段的注解Excel和cellMap中的列号,构建一个fieldsMap,用于存储列号和字段信息的映射关系。
-
接下来,从titleNum + 1行开始循环处理每一行的数据。在循环中,首先判断当前行是否为空行,如果是则跳过。然后,根据extDataStartCol获取额外数据,并将其存储在extDataList中。
-
然后,根据fieldsMap中的映射关系,获取每一列的值,并根据字段的类型进行转换和赋值操作。
-
最后,将转换后的实体对象添加到list中,并返回list。
3 若依框架导入导出实现方法
3.1 前端
3.1.1 导入getToken方法
在需要实现导入导出功能的页面的index.vue中的< script >< /script >中增加如下代码:
import { getToken } from "@/utils/auth";
3.1.2 新增upload参数
在index.vue的data数据中添加upload属性
// 导入参数
upload: {
// 是否显示弹出层(导入弹窗)
open: false,
// 弹出层标题(自己填写)
title: "自己填写",
// 是否禁用上传
isUploading: false,
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getToken() },
// 上传的地址,即后端接口地址
url: process.env.VUE_APP_BASE_API + "/url"
},
3.1.3 新增导入按钮所需的函数
在index.vue的methods中新增一下几个函数,具体可以自己修改:
/** 导入按钮操作 */
handleImport() {
this.upload.title = "自己设置";
this.upload.open = true;
},
/** 下载模板操作 */
importTemplate() {
this.download('下载模板的url', {
}, `user_template_${new Date().getTime()}.xlsx`)
},
// 文件上传中处理
handleFileUploadProgress(event, file, fileList) {
this.upload.isUploading = true;
},
// 文件上传成功处理
handleFileSuccess(response, file, fileList) {
this.upload.open = false;
this.upload.isUploading = false;
this.$refs.upload.clearFiles();
this.$alert(response.msg, "导入结果", { dangerouslyUseHTMLString: true });
this.getList();
},
// 提交上传文件
submitFileForm() {
this.$refs.upload.submit();
}
3.1.4 添加导入按钮与导入弹窗
index.vue的中的合适位置添加如下代码,其中v-hasPermi为访问权限,设置可以个人修改,导入弹窗使用了标签及相关属性,action属性为url路径。
<el-col :span="1.5">
<el-button
type="info"
icon="el-icon-upload2"
size="mini"
@click="handleImport"
v-hasPermi="['system:user:import']"
>导入</el-button>
</el-col>
<!-- 用户导入对话框 -->
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px">
<el-upload
ref="upload"
:limit="1"
accept=".xlsx, .xls"
:headers="upload.headers"
:action="upload.url + '?updateSupport=' + upload.updateSupport"
:disabled="upload.isUploading"
:on-progress="handleFileUploadProgress"
:on-success="handleFileSuccess"
:auto-upload="false"
drag
>
<i class="el-icon-upload"></i>
<div class="el-upload__text">
将文件拖到此处,或
<em>点击上传</em>
</div>
<div class="el-upload__tip" slot="tip">
<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的用户数据
<el-link type="info" style="font-size:12px" @click="importTemplate">下载模板</el-link>
</div>
<div class="el-upload__tip" style="color:red" slot="tip">提示:仅允许导入“xls”或“xlsx”格式文件!</div>
</el-upload>
<div slot="footer" class="dialog-footer">
<el-button type="primary" @click="submitFileForm">确 定</el-button>
<el-button @click="upload.open = false">取 消</el-button>
</div>
</el-dialog>
3.2 后端
3.2.1 添加注解@Excel
在需要导入与导出功能的类的属性上增加@Excel注解,并添加name属性与其他属性,案例如下:
@Excel(name = "姓名")
private String userName;
3.2.2 在对应的类中新增导入接口与下载模板接口
具体实体类与服务按照实际需求来实现。
@Log(title = "用户管理", businessType = BusinessType.IMPORT)
@PostMapping("/importData")
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception
{
ExcelUtil<SysHonor> util = new ExcelUtil<SysHonor>(SysHonor.class);
List<SysHonor> userList = util.importExcel(file.getInputStream());
LoginUser loginUser = getLoginUser();
String operName = loginUser.getUsername();
String message = userService.importUser(userList, updateSupport, operName);
return AjaxResult.success(message);
}
@GetMapping("/importTemplate")
public AjaxResult importTemplate()
{
ExcelUtil<SysHonor> util = new ExcelUtil<SysHonor>(SysHonor.class);
return util.importTemplateExcel("用户数据");
}