easyExcel 入门,完成web的excel文件创建和导出
easyExcel官网
EasyExcel 的主要特点如下:
1、高性能:EasyExcel 采用了异步导入导出的方式,并且底层使用 NIO 技术实现,使得其在导入导出大数据量时的性能非常高效。
2、易于使用:EasyExcel 提供了简单易用的 API,用户可以通过少量的代码即可实现复杂的 Excel 导入导出操作。
3、增强的功能“EasyExcel 支持多种格式的 Excel 文件导入导出,同时还提供了诸如合并单元格、数据校验、自定义样式等增强的功能。
4、可扩展性好:EasyExcel 具有良好的扩展性,用户可以通过自定义 Converter 对自定义类型进行转换,或者通过继承 EasyExcelListener 来自定义监听器实现更加灵活的需求。
入门使用
使用maven导入easyExcel坐标
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.0</version>
</dependency>
定义实体类封装数据,一行对应一个对象 用@ExcelProperty注解完成与excel的映射
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CategoryExcelVo {
// `value` 指定 Excel 表头名称,数据导入导出时匹配该表头
// `index`(可选)从 0 开始,表示 Excel 第 n+1 列
// 如果省略 `index`,则按字段声明顺序匹配列
@ExcelProperty(value = "id" ,index = 0)
private Long id;
@ExcelProperty(value = "名称" ,index = 1)
private String name;
}
excel对应的表
读入操作 read 读需要用到ExcelListener对象
1、 先创建这个对象
@Getter
public class ExcelListener<T> extends AnalysisEventListener<T> {
/**
* 每读取一行就执行一次
* @param t 返回的数据
* @param analysisContext
*/
//读取后对象存储在data集合中
List<T> data=new ArrayList<>();
@Override
public void invoke(T t, AnalysisContext analysisContext) {
data.add(t);
}
/**
* 都读取解析完毕后执行, 一般用于对资源的处理, 或对操作的补充
* @param analysisContext
*/
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println("表格的解析完成了");
}
2、 进行读测试
private static void readFile() {
// 文件的路径
String filePath="/Users/chen/01.xlsx";
// easyExcel不能由spring 接管, 这里手动new对象
ExcelListener<CategoryExcelVo> excelVoExcelListener = new ExcelListener<>();
//读操作
EasyExcel.read(filePath, CategoryExcelVo.class,excelVoExcelListener).sheet().doRead();
//读取完毕后数据存放到了, 监听器的data里面,遍历取出来就可以
List<CategoryExcelVo> data = excelVoExcelListener.getData();
for (CategoryExcelVo item : data) {
System.out.println(item);
}
}
写出操作 write 不需要用到ExcelListener对象
private static void writeFile() {
//要写到的磁盘路径
String filePath="/Users/chen/categoryWrite.xlsx";
//创建每一个对象,对应一行数据
List<CategoryExcelVo> list = new ArrayList<>() ;
list.add(new CategoryExcelVo(1L , "数码办公")) ;
list.add(new CategoryExcelVo(11L , "华为手机")) ;
//参数 1路径名 2 对应的类 3 excel中sheet的名字 4 集合
EasyExcel.write(filePath,CategoryExcelVo.class).sheet("category").doWrite(list);
System.out.println("finished write");
}
在web中使用
后端
1、controller
@RestController
@RequestMapping("/admin/product/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 用户上传excel
* @param file
* @return
*/
@PostMapping("uploadExcel")
public Result uploadExcel(MultipartFile file){
categoryService.uploadExcel(file);
return Result.build(null,ResultCodeEnum.SUCCESS);
}
/**
* 用户导出excel
* @param response
*/
@GetMapping("/excelExport")
//不需要返回值, 由response设置
public void excelExport(HttpServletResponse response){
categoryService.excelExport(response);
}
}
2、service
@Service
@Transactional
public class CategoryServiceImpl implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
/**
* 用户上传excel
* 1 定义对应的实体类
* 2 定义监听器
* 3 读取数据
* 4 上传到数据库
* @param file
*/
@Override
public void uploadExcel(MultipartFile file) {
try {
//创建监听器
CategoryListener<CategoryExcelVo> CategoryListener = new CategoryListener<CategoryExcelVo>(categoryMapper);
EasyExcel.read(file.getInputStream(), CategoryExcelVo.class,CategoryListener).sheet().doRead();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 导出 excel文件,写操作
* @param response
*/
@Override
public void excelExport(HttpServletResponse response) {
try {
// 设置mime类型
response.setContentType("application/vnd.ms-excel");
//设置浏览器编码方法
response.setCharacterEncoding("utf-8");
//对路径进行编码
String fileName = URLEncoder.encode("分类数据", "UTF-8");
//设置 HTTP 响应头,告诉浏览器该请求是一个文件下载(attachment),并指定下载的文件名为 fileName.xlsx。
response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
//准备数据
//查询全部分类,不需要带树形结构
List<Category> categoryList=categoryMapper.selectAllCategory();
//查出来的数据比实际用到的数据多
// 查询出来的类和对应的vo类不匹配, 但是有部分属性相同,这时候可以用map()函数
List<CategoryExcelVo> categoryExcelVoList = categoryList.stream().map(category -> {
CategoryExcelVo categoryExcelVo = new CategoryExcelVo();
// BeanUtils.copyProperties(); 可以把参数一的属性的值,拷贝到参数2中, 要求两个属性必须一致
BeanUtils.copyProperties(category, categoryExcelVo);
return categoryExcelVo;
}).collect(Collectors.toList());
//写出数据
EasyExcel.write(response.getOutputStream(), CategoryExcelVo.class).sheet("category").doWrite(categoryExcelVoList);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 创建对应的导出实体类的监听器
@Slf4j
public class CategoryListener<T> extends AnalysisEventListener<T> {
// 没有办法用自动注入的方式,获取mapper, 所以创建对象的时候手动
//把参数带过来
private CategoryMapper categoryMapper;
public CategoryListener(CategoryMapper categoryMapper) {
this.categoryMapper = categoryMapper;
}
/**
* 每隔100条存储数据库,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
private List<CategoryExcelVo> CategoryList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
@Override
public void invoke(T data, AnalysisContext context) {
log.info("解析到一条数据:{}", JSON.toJSONString(data));
CategoryList.add((CategoryExcelVo) data);
if (CategoryList.size() >= BATCH_COUNT) {
//每保存到100条,执行一次mapper的插入方法
saveData();
CategoryList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
}
}
/**
* 做兜底,如果最后没有100条数据了,也保证剩余的都保存的数据库中
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
log.info("所有数据解析完成!");
}
/**
* 存储到数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", CategoryList.size());
log.info("存储数据库成功!");
categoryMapper.insertExcelData(CategoryList);
}
}
3、mapper
@Mapper
public interface CategoryMapper {
//查询所有分类 导出
List<Category> selectAllCategory();
// 导入excel
void insertExcelData(List<CategoryExcelVo> categoryList);
}
- 注意在导入excel的时候, id字段也需要我们自己处理, 否则在网页的父子的层级关系就显示不出来了. 我们是通过id 和pid 来判断层级关系的
<select id="selectAllCategory" resultType="com.chen.model.entity.product.Category">
select <include refid="columns"/> from category
where is_deleted=0
order by order_num
</select>
<insert id="insertExcelData" >
insert into category (id,name,image_url,parent_id,status,order_num)
values
<foreach collection="categoryList" separator="," item="category">
(#{category.id},#{category.name},#{category.imageUrl},#{category.parentId},#{category.status},#{category.orderNum})
</foreach>
</insert>
前端
1、 api
import request from '@/utils/request'
// 抽取出来方便后面使用
const apiUrl = '/admin/product/category'
/**
* 导出excel文件
* @returns {AxiosPromise}
*/
export const exportExcel = () => {
return request({
url: `${apiUrl}/excelExport`,
method: 'get',
responseType:'blob' //用于处理二进制数据
})
}
2、 vue
• el-upload 组件默认使用 file 作为参数名,即后端 MultipartFile 应该使用 @RequestParam("file") 来接收,如果名字一样可以省略。
<template>
<div class="tools-div">
<el-button type="success" size="small" @click="exportExcelFile">导出</el-button>
<el-button type="primary" size="small" @click="showImportExcelDialog">导入</el-button>
</div>
<!-- 导入对话框-->
<el-dialog v-model="dialogImportVisible" title="导入" width="30%">
<el-form label-width="120px">
<el-form-item label="分类文件">
<el-upload
class="upload-demo"
action="http://localhost:8501/admin/product/category/uploadExcel"
:on-success="onUploadSuccess"
:headers="headers"
>
<el-button type="primary">上传</el-button>
</el-upload>
</el-form-item>
</el-form>
</el-dialog>
</template>
```js
<script setup>
import {exportExcel} from '@/api/category'
//导出------------
//点击导出后,开始执行方法 本方法是给按钮绑定的
const exportExcelFile = async () => {
// exportExcel() 是一个异步函数,返回一个 Promise。
//.then((resp => { })) 监听 Promise 的成功状态,但回调函数中没有任何操作。
exportExcel().then((resp => {
//创建blob对象
let blob = new Blob([resp]);
//创建a标签
const link = document.createElement("a");
//赋值数据
link.href=window.URL.createObjectURL(blob);
//设置名称
link.download="分类数据.xlsx"
//模拟点击
link.click()
}))
}
// --------------导入
// 控制对话框是否展示
const dialogImportVisible=ref(false);
//点击导入后显示导入对话框
const showImportExcelDialog=()=>{
dialogImportVisible.value=true;
}
//上传成功的回掉函数, 关闭对话框
const onUploadSuccess=async(resp)=>{
const {code}=resp
if(code===200){
dialogImportVisible.value=false
ElMessage.success("上传成功")
//刷新页面, 查询一下就行
//进入后查询一级标题
const {data} = await getCategoryListByParentId(0)
//赋值
list.value = data
}
}
</script>