导入依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.1.5</version>
</dependency>
说明: Excel的解析与处理
Excel下载导入模板
Controller层接收前端请求
@PostMapping("/system/user/importTemplate")
@ResponseBody
public void importTemplate(HttpServletResponse response)
{
ExcelUtil.exportExcel(new ArrayList<>(), "用户数据", UserExcelVo.class, response);
}
创建Excel工具类
package com.hl.emsystem.utils.excel;
import cn.hutool.core.util.IdUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.hl.emsystem.utils.FileUtils;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.List;
public class ExcelUtil {
/**
* 使用自定义监听器 异步导入 自定义返回
*
* @param is 输入流
* @param clazz 对象类型
* @param listener 自定义监听器
* @return 转换后集合
*/
public static <T> ExcelResult<T> importExcel(InputStream is, Class<T> clazz, ExcelListener<T> listener) {
EasyExcel.read(is, clazz, listener).sheet().doRead();
return listener.getExcelResult();
}
/**
* 重置响应体
*/
private static void resetResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException {
String filename = encodingFilename(sheetName);
FileUtils.setAttachmentResponseHeader(response, filename);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
}
/**
* 编码文件名
*/
public static String encodingFilename(String filename) {
return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
}
/**
* 导出excel
* * @param list 导出数据集合
* @param sheetName 工作表的名称
* @param clazz 实体类
* @param response 响应体
*/
public static <T> void exportExcel(List<T> list, String sheetName, Class<T> clazz, HttpServletResponse response) {
try {
resetResponse(sheetName, response);
ServletOutputStream os = response.getOutputStream();
ExcelWriterSheetBuilder builder = EasyExcel.write(os, clazz)
.autoCloseStream(false)
// 自动适配
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
.sheet(sheetName);
builder.doWrite(list);
} catch (IOException e) {
throw new RuntimeException("导出Excel异常");
}
}
}
说明:
我们进入resetResponse查看:
/**
* 重置响应体
*/
private static void resetResponse(String sheetName, HttpServletResponse response) throws UnsupportedEncodingException {
String filename = encodingFilename(sheetName);
FileUtils.setAttachmentResponseHeader(response, filename);
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8");
}
/**
* 编码文件名
*/
public static String encodingFilename(String filename) {
return IdUtil.fastSimpleUUID() + "_" + filename + ".xlsx";
}
说明: 关于FileUtils继承自hutool的FileUtil,以及IdUtils来自hutools
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.5.8</version>
</dependency>
详细文档请移步hutool入门和安装
/**
* 下载文件名重新编码
*
* @param response 响应对象
* @param realFileName 真实文件名
*/
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class FileUtils extends FileUtil {
public static void setAttachmentResponseHeader(HttpServletResponse response, String realFileName) throws UnsupportedEncodingException {
String percentEncodedFileName = percentEncode(realFileName);
StringBuilder contentDispositionValue = new StringBuilder();
contentDispositionValue.append("attachment; filename=")
.append(percentEncodedFileName)
.append(";")
.append("filename*=")
.append("utf-8''")
.append(percentEncodedFileName);
response.addHeader("Access-Control-Expose-Headers", "Content-Disposition,download-filename");
response.setHeader("Content-disposition", contentDispositionValue.toString());
response.setHeader("download-filename", percentEncodedFileName);
}
}
关于Excel导出的实体类
package com.hl.emsystem.model.vo;
import com.alibaba.excel.annotation.ExcelProperty;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.hl.emsystem.utils.excel.convert.ExcelLongConvert;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
public class UserExcelVo implements Serializable {
@ExcelProperty(value = "用户名称")
private String name;
@ExcelProperty(value = "用户昵称")
private String username;
@ExcelProperty(value = "手机号码")
private String phone;
@ExcelProperty(value = "邮箱")
private String email;
@ExcelProperty(value = "用户密码")
// @ExcelIgnore
private String password;
@ExcelProperty(value = "角色(1-毕业生)",converter= ExcelLongConvert.class)
@JsonProperty(value = "roleIds")
private Long[] roleIds;
@ExcelProperty(value = "用户备注")
private String departmentName;
}
说明: converter= ExcelLongConvert.class
是转换器,由于Excel表格支持的数据类型没有Long[]的类型,需要自己转换为数组类型
Excel转换器
package com.hl.emsystem.utils.excel.convert;
import cn.hutool.core.convert.Convert;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
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;
import lombok.extern.slf4j.Slf4j;
import java.util.Arrays;
/**
* Excel转换器
*/
@Slf4j
public class ExcelLongConvert implements Converter<Long[]> {
//在java中数据类型
@Override
public Class<Long[]> supportJavaTypeKey() {
return Long[].class;
}
// 在excel中的数据类型
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.NUMBER;
}
// 将excel的数据类型转为java数据类型
@Override
public Long[] convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
if(cellData.getType().equals(CellDataTypeEnum.STRING)){
// 将[1,3]这种字符串转为数组
String str = cellData.getStringValue().replace("[", "").replace("]","");
return Convert.toLongArray(str);
}
return Convert.toLongArray(cellData.getNumberValue());
}
// 将java的数据类型转为excel数据类型
@Override
public WriteCellData<Object> convertToExcelData(Long[] object, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
WriteCellData<Object> cellData = new WriteCellData<>();
if (object.length > 1) {
cellData.setType(CellDataTypeEnum.STRING);
return new WriteCellData<>(Arrays.toString(Convert.toStrArray(object)));
}
return new WriteCellData<>(Arrays.toString(Convert.toNumberArray(object)));
}
}
导入和导出
/**
* 导入
*/
@PostMapping("/system/user/importData")
@ResponseBody
public ApiRestResponse importStudent(MultipartFile file, boolean updateSupport) throws Exception
{
ExcelResult<UserExcelVo> result = ExcelUtil.importExcel(file.getInputStream(), UserExcelVo.class, new ExcelImportListener(updateSupport));
return ApiRestResponse.success(result.getAnalysis());
}
/**
* 导出
*/
@PostMapping("/system/user/export")
public void export(User user,HttpServletResponse response){
List<User> userList =userService.exportAllUser(user);
for(User userRole:userList){
List<Long> roleIds = userService.selectAllRoleIdByUserId(userRole.getUserId());
userRole.setRoleIds(Convert.toLongArray(roleIds));
}
List<UserExcelVo> userVo = BeanCopyUtils.copyList(userList,UserExcelVo.class);
ExcelUtil.exportExcel(userVo,"用户数据",UserExcelVo.class,response);
}
说明:
拷贝代码(看看就好):
public static <T, V> List<V> copyList(List<T> sourceList, Class<V> desc) {
if (ObjectUtil.isNull(sourceList)) {
return null;
}
if (CollUtil.isEmpty(sourceList)) {
return CollUtil.newArrayList();
}
return StreamUtils.toList(sourceList, source -> {
V target = ReflectUtil.newInstanceIfPossible(desc);
copy(source, target);
return target;
});
}
很多都是基于hutool里的工具类实现的。。。可以自己上网找找
Bean的拷贝之BeanUtils_wh柒八九的博客-CSDN博客_bean拷贝
在我们实际项目开发过程中,我们经常需要将不同的两个对象实例进行属性复制,从而基于源对象的属性信息进行后续操作,而不改变源对象的属性信息,比如DTO数据传输对象和数据对象DO,我们需要将DO对象进行属性复制到DTO,但是对象格式又不一样,所以我们需要编写映射代码将对象中的属性值从一种类型转换成另一种类型。
这种转换最原始的方式就是手动编写大量的 get/set代码,当然这是我们开发过程不愿意去做的,因为它确实显得很繁琐。为了解决这一痛点,就诞生了一些方便的类库,常用的有 apache的 BeanUtils,spring的 BeanUtils, Dozer,Orika等拷贝工具。
在这个项目中,从数据库中查询出来的信息(DO)要传输到EXCEL中,Excel需要的信息不全是用户表中的,还有其它表中的信息,于是我们从很多表中抽出我们需要的字段组成DTO,新的实体类,比如UserExcelVO,所以我们需要将从数据库查询出的User里的信息拷贝到UserExcelVo里,进行导出
这里为啥不用新建个实体类进行抽象出来呢? 因为本人数据库设计问题,并没有将这些字段加到用户表中,但是前端经常用到这些字段信息,相当于没有这几个字段的User表就没用了,所以索性不如直接添加进去进行使用,防止数据冗余
Spring—有参构造与无参构造 简述: 1.一般一个类他会自带一个默认的无参构造方法,如果你在其中写入了有参构造,在没有重新写入无参构造的情况下,该午餐构造方法就不存在了 2.在使用spring框架时,在使用一个类的时候,spring容器会自动对该类进行实例化,但是该类必须时无参构造的,相当于spring的默认设置 3.如果在使用spring框架时,实体类的初始化方法是有参构造,那么需要在配置文件中进行配置(即.xml文件)之后spring容器才能对该类进行实例化
重点是SpringBoot的底层是否Spring实现的,其类的注入需要无参构造,当类里存在有参构造时,无参构造就失效了,要么自己添加个无参构造,要们自己加个注解@NoArgConstructor
监听器与结果封装
继承easyExcel里的监听器:
结果封装:
package com.hl.emsystem.utils.excel;
import java.util.List;
/**
* excel返回对象
*
*/public interface ExcelResult<T> {
/**
* 对象列表
*/
List<T> getList();
/**
* 错误列表
*/
List<String> getErrorList();
/**
* 导入回执
*/
String getAnalysis();
}
监听器实现
package com.hl.emsystem.utils.excel;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.hl.emsystem.exception.EmSystemException;
import com.hl.emsystem.model.pojo.User;
import com.hl.emsystem.model.vo.UserExcelVo;
import com.hl.emsystem.service.UserService;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.List;
@Slf4j
@Component
@NoArgsConstructor
public class ExcelImportListener extends AnalysisEventListener<UserExcelVo> implements ExcelListener<UserExcelVo>{
@Autowired
private UserService userService;
private int successNum = 0;
private int failureNum = 0;
private final StringBuilder successMsg = new StringBuilder();
private final StringBuilder failureMsg = new StringBuilder();
private Boolean isUpdate;
//需要添加无参构造才能注入
// public ExcelImportListener(){}
public ExcelImportListener(Boolean isUpdate){
this.isUpdate = isUpdate;
this.userService = SpringUtil.getBean(UserService.class);
}
@Override
public void invoke(UserExcelVo userVo, AnalysisContext context) {
User user = this.userService.selectUserByUsername(userVo.getUsername());
// List<Long> roleIds =this.userService.selectAllRoleIdByUserId(user.getUserId());
// user.setRoleIds(roleIds.toArray(new Long[0]));
try {
// 验证是否存在这个用户,不存在就插入,存在就是更新
if (ObjectUtil.isNull(user)) {
user = BeanUtil.toBean(userVo, User.class);
this.userService.addUserInfo(user);
successNum++;
successMsg.append("<br/>").append(successNum).append("、账号 ").append(user.getName()).append(" 导入成功");
} else if (isUpdate) {
Long userId = user.getUserId();
Long peopleId = user.getPeopleId();
user = BeanUtil.toBean(userVo, User.class);
user.setUserId(userId);
user.setPeopleId(peopleId);
this.userService.updateUserInfo(user);
successNum++;
successMsg.append("<br/>").append(successNum).append("、账号 ").append(user.getUsername()).append(" 更新成功");
} else {
failureNum++;
failureMsg.append("<br/>").append(failureNum).append("、账号 ").append(user.getUsername()).append(" 已存在");
}
} catch (Exception e) {
failureNum++;
String msg = "<br/>" + failureNum + "、账号 " + user.getUsername() + " 导入失败:";
failureMsg.append(msg).append(e.getMessage());
log.error(msg, e);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
}
@Override
public ExcelResult<UserExcelVo> getExcelResult() {
return new ExcelResult<UserExcelVo>() {
@Override
public String getAnalysis() {
if (failureNum > 0) {
failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:");
throw new EmSystemException(failureMsg.toString());
} else {
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:");
}
return successMsg.toString();
}
@Override
public List<UserExcelVo> getList() {
return null;
}
@Override
public List<String> getErrorList() {
return null;
}
};
}
}
说明: 这里涉及到无参构造,上面已经讲解导入和导出
主要是重写两个类:invoke与doAfterAllAnalysed: 其中invoke () 方法负责读取Excel中的数据,doAfterAllAnalysed () 方法是在读取完成之后执行的方法