在做项目的过程中,外面经常会遇到一个问题,怎么才能把excel表中的数据实现批量的导入导出,使用的是EasyExcel进行操作。
一、项目前准备
1、依赖导入
在pom文件中添加对应的依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.3</version>
</dependency>
2、实体类准备
原数据库字段过多,这边就截取一部分进行展示
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("emp_user")
public class EmpUserEntity implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 人员id
*/
@ExcelIgnore
private Long id;
/**
* 用户ID
*/
@ExcelIgnore
private Long userId;
/**
* 密码
*/
@TableField(exist = false)
@ExcelIgnore
private String password;
/**
* 姓名
*/
@ExcelProperty(value = {"姓名"}, index = 0)
private String name;
/**
* 性别:1:男 0:女
*/
@ExcelProperty(value = {"性别"}, index = 1,converter = SexConvert.class)
//静态下拉框
@ExcelSelected(source = {"男","女"})
private Integer gender;
/**
* 年龄(计算退休时间)
*/
@ExcelProperty(value = {"年龄"}, index = 2)
private Integer age;
/**
* 电话
*/
@ExcelProperty(value = {"电话"}, index = 3)
@ColumnWidth(20)
private String tel;
/**
* 邮箱
*/
@ExcelProperty(value = {"邮箱"}, index = 4)
@ColumnWidth(25)
private String email;
}
注释解释:
@ExcelIgnore:EasyExcel框架中的注解,用于标识实体类中不需要导出或导入的属性。
@ExcelProperty(value = {"性别"}, index = 1,converter = SexConvert.class):指定实体类与表格之间的映射关系
value表示excel表格的标题列。
index表示excel表格中的列索引。
converter = SexConvert.class:指定类型转换器,将Excel表格中的数据转换成Java对象。
@ExcelSelected(source = {"男","女"}):导出下拉框自定义注解
3、类型转换器
有些数据外面存入数据库的是数字,例如一般男生我们选用1,女生选用0,当时当我们进行表的输出和输入时,excel表中用的是男、女,我们采用类型转换器进行数据的转化。
(1)实体类中添加注释
@ExcelProperty(value = {"性别"}, index = 1,converter = SexConvert.class)
//SexConvert.class定义类转化器的类
(2)转换类的编写
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
public class SexConvert implements Converter<Integer> {
@Override
//指定该转换器支持的 Java 类型。在本例中,返回 Integer.class,表示该转换器用于将 Excel 中的数据转换为整数类型。
public Class supportJavaTypeKey() {
return Integer.class;
}
@Override
//指定该转换器支持的 Excel 数据类型。在本例中,返回 CellDataTypeEnum.STRING,表示该转换器处理的是字符串类型的 Excel 数据。
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
@Override
//将 Excel 中的数据转换为 Java 对象的方法。在本例中,该方法尚未实现,因此返回 null。你需要根据实际需求,实现具体的转换逻辑。
public Integer convertToJavaData(CellData cellData, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
return "男".equals(cellData.getStringValue()) ? 1 : 0;
}
@Override
//将 Java 对象转换为 Excel 数据的方法。
public CellData convertToExcelData(Integer value, ExcelContentProperty excelContentProperty, GlobalConfiguration globalConfiguration) throws Exception {
if (value == 1) {
return new CellData("男");
} else if (value == 0) {
return new CellData("女");
}
// 如果值不是 Integer 或者不是合法的性别值,则抛出异常
throw new IllegalArgumentException("无效的性别值");
}
}
其中第三个和第四个方法代表 java对象与excel表里的相互转换。
*注意包不要导错喔
二、导入功能的实现
1、后端功能实现:
(1)自定义监听器,对下载的excel中的数据进行校验。
public class WebUserListener<T> extends AnalysisEventListener<T> {
//首先使用 Logger 记录当前读取的数据信息,然后将读取到的数据对象 t 加入到 list 列表中,以备后续的获取
public final Logger log = LoggerFactory.getLogger(this.getClass());
public List<T> list = new ArrayList<>();
/**
* 该方法在读取到表格中的一条数据时被调用。其中 t 参数表示当前行的数据对象,analysisContext 参数表示解析过程的上下文对象。
* @param t
* @param
*/
@Override
public void invoke(T t, AnalysisContext analysisContext) {
log.info("读取表格[{}]",t);
list.add(t);
}
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
}
//获取数据,供controller层调用
public List<T> getExcelData(){
return list;
}
}
(2)controller层:
@RestController
@RequestMapping("emp/excel")
public class EmpUserImport extends AbstractController {
@Autowired
private EmpUserService empUserService;
@PostMapping("/Import")
//@RequiresPermissions("emp:user:excelImport")
public EmpUserEntity redExcel(MultipartFile multipartFile) throws IOException {
//输入流指向了上传的 Excel 文件
InputStream inputStream = multipartFile.getInputStream();
//实例是一个 EasyExcel 的监听器,用于解析 Excel 数据。
WebUserListener<EmpUserEntity> webListener = new WebUserListener<>();
//用于处理 Excel 数据的监听器,表示对 Excel 中所有的 sheet 进行解析。
EasyExcel.read(inputStream,EmpUserEntity.class,webListener).sheet().doRead();
//获取数据对象列表。
List<EmpUserEntity> excelData = webListener.getExcelData();
//将获取到的数据填入数据库
for (EmpUserEntity user : excelData) {
empUserService.save(user);
}
return excelData ;
}
2、前端代码
(1)触发按钮
<el-form-item >
<el-upload
class="upload-demo"
:action="excelImports"
:headers="tokenInfo"
:on-preview="handlePreview"
:on-remove="handleRemove"
:on-success="uploadSuccess"
multiple
name="multipartFile"
:limit="3"
>
<el-button type="success" >点击上传</el-button>
</el-upload>
</el-form-item>
(2)接口定义
data () {
return {
excelImports: this.$http.adornUrl('/emp/excel/Import'),
tokenInfo: {
'token': this.$cookie.get('token')
},
dataForm: {
deptId: '' //部门id
},
.........
}
}
(3)方法补充
uploadSuccess (response, file, fileList) {
this.getDataList()
console.log(response)
},
handleRemove (file, fileList) {
console.log(file, fileList)
},
handlePreview (file) {
console.log(file)
}
三、导出功能实现
可实现导出附加功能:
- 可实现按部门进行导入导出,该页面显示按照部门id进行输入选择,可选择其他条件进行筛选,或自行设置级联选择。
- 让导出的excel表实现下拉框的选择
1、后端代码
(1)创建实现下拉框选择的注解
import java.lang.annotation.*;
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelSelected {
/**
* 固定下拉内容
*/
String[] source() default {};
/**
* 设置下拉框的起始行,默认为第二行
*/
int firstRow() default 1;
/**
* 设置下拉框的结束行,默认为最后一行
*/
int lastRow() default 0x10000;
}
(2)自定义注解使用
/**
* 性别:1:男 0:女
*/
@ExcelProperty(value = {"性别"}, index = 1,converter = SexConvert.class)
//静态下拉框
@ExcelSelected(source = {"男","女"})
private Integer gender;
(2)创建导出工具类
@Slf4j
public class EasyExcelUtils {
/**
* 导出单sheet页且sheet页中含有下拉框的excel文件
*
* @param response HttpServletResponse
* @param fileName 文件名
* @param sheetName sheet页名
* @param data 要导出的数据
*/
public static <T> void writeExcelBySelect(HttpServletResponse response, String fileName, String sheetName, List<T> data) {
try {
encodeFileName(response, fileName);
Map<Integer, ExcelSelected> integerExcelSelectedMap = resolveSelectedAnnotation(IterUtil.getElementType(data));
EasyExcel.write(response.getOutputStream(), IterUtil.getElementType(data))
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.registerWriteHandler(selectedSheetWriteHandler(integerExcelSelectedMap))
.sheet(StringUtils.isEmpty(sheetName) ? "Sheet1" : sheetName)
.doWrite(data);
} catch (IOException e) {
log.error("导出excel文件异常", e);
}
}
/**
* 设置文件名
*
* @param response HttpServletResponse
* @param fileName 文件名
*/
private static void encodeFileName(HttpServletResponse response, String fileName) throws UnsupportedEncodingException {
fileName = URLEncoder.encode(fileName, StandardCharsets.UTF_8.name());
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setHeader(HttpHeaders.CONTENT_DISPOSITION, String.format("attachment;filename=\"%s\"", fileName + ".xlsx"));
response.setHeader(HttpHeaders.CACHE_CONTROL, "no-cache");
response.setHeader(HttpHeaders.PRAGMA, "no-cache");
response.setDateHeader(HttpHeaders.EXPIRES, -1);
}
/**
* 解析表头类中的下拉注解
*
* @param head 表头类
* @return Map<下拉框列索引, 下拉框内容> map
*/
private static <T> Map<Integer, ExcelSelected> resolveSelectedAnnotation(Class<T> head) {
Map<Integer, ExcelSelected> selectedMap = new HashMap<>(16);
Field[] fields = head.getDeclaredFields();
for (int i = 0; i < fields.length; i++) {
Field field = fields[i];
ExcelSelected selected = field.getAnnotation(ExcelSelected.class);
ExcelProperty property = field.getAnnotation(ExcelProperty.class);
if (selected != null) {
if (property != null && property.index() >= 0) {
selectedMap.put(property.index(), selected);
}
}
}
return selectedMap;
}
/**
* 为excel创建下拉框
*
* @param selectedMap 下拉框配置数据 Map<下拉框列索引, 下拉框内容>
* @return intercepts handle sheet creation
*/
private static SheetWriteHandler selectedSheetWriteHandler(Map<Integer, ExcelSelected> selectedMap) {
return new SheetWriteHandler() {
@Override
public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
}
@Override
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
//获取正在处理的工作表对象
Sheet sheet = writeSheetHolder.getSheet();
//获得一个用于创建数据验证规则的辅助对象,用于创建和配置数据验证规则
DataValidationHelper helper = sheet.getDataValidationHelper();
selectedMap.forEach((k, v) -> {//其中 (k, v) 表示 Map 的键和值,在每次迭代时会传入这两个参数。
// 获取固定下拉框的内容
List<String> source = new ArrayList<>();
//如果 ExcelSelected 注解中的 source 数组不为空,则将其转换为 List,并添加到 source 变量中
if (v.source().length > 0) {
source.addAll(Arrays.asList(v.source()));
}
//CollUtil.isNotEmpty(source) 是一个对集合 source 进行非空判断的工具方法。
if (CollUtil.isNotEmpty(source)) {
/**
* 代码创建一个 CellRangeAddressList 对象 rangeList,用于定义下拉框的范围。
* 其中,v.firstRow() 和 v.lastRow() 分别表示下拉框的起始行和结束行,k 表示下拉框所在的列
*/
CellRangeAddressList rangeList = new CellRangeAddressList(v.firstRow(), v.lastRow(), k, k);
//用于定义下拉框的约束。该约束指定下拉框的选项为 source 列表中的内容
DataValidationConstraint constraint = helper.createExplicitListConstraint(source.toArray(new String[0]));
DataValidation validation = helper.createValidation(constraint, rangeList);
//代码设置数据验证规则的错误样式为停止(DataValidation.ErrorStyle.STOP)
validation.setErrorStyle(DataValidation.ErrorStyle.STOP);
//显示错误提示框(validation.setShowErrorBox(true))
validation.setShowErrorBox(true);
//隐藏下拉箭头(validation.setSuppressDropDownArrow(true))
validation.setSuppressDropDownArrow(true);
validation.createErrorBox("提示", "请输入下拉选项中的内容");
sheet.addValidationData(validation);
}
});
}
};
}
}
(3)controller层
@GetMapping("/export")
public void exportData(@RequestParam(required = false) String deptId,//查询条件可不存在
HttpServletResponse response) {
// Data
QueryWrapper<EmpUserEntity> wrapper = new QueryWrapper<>();
wrapper.like(StrUtil.isNotBlank(deptId), "dept_id", deptId);
//将部门id变为部门名称
List<EmpUserEntity> list = empUserService.list(wrapper);
EasyExcelUtils.writeExcelBySelect(response, "测试", "用户信息表", list);
}
2、前端代码
(1)按钮展示
<el-form-item style="float:right">
<el-button type="success" @click="exportUser()">导出</el-button>
</el-form-item>
<el-form-item style="float:right">
<el-input v-model="dataForm.deptId" placeholder="请选择部门进行导出" style="width: 200px" clearable></el-input>
</el-form-item>
(2)方法定义
// 导出用户,通过blob
exportUser() {
this.$axios({
method: 'get',
url: this.$http.adornUrl('/emp/user/export?deptId='+this.dataForm.deptId),
responseType: 'blob',
}).then(function (response){
let blob = new Blob([response.data], { type: 'application/vnd.ms-excel;charset=utf-8' })
let downloadElement = document.createElement('a');//创建一个 <a> 元素,用于执行文件下载操作。
let href = window.URL.createObjectURL(blob); //创建下载的链接
downloadElement.href = href;// 将下载链接指定给 <a> 元素的 href 属性。
downloadElement.download = 'test.xlsx'; //下载后文件名
document.body.appendChild(downloadElement);// 将 <a> 元素添加到页面的 <body> 元素中。
downloadElement.click(); //点击下载
document.body.removeChild(downloadElement); //下载完成移除元素
window.URL.revokeObjectURL(href); //释放掉blob对象
}).catch(function(error){
this.$message.error(error)
})
},
3、实物展示
----------------------------------------------------------------------------
本页面的导出下拉框,是固定写入,若需要动态导入,可参考以下大佬文章:
http://t.csdnimg.cn/zBVD0