- 一、导入
- (一)controller层
- (二)service层
- (三)监听器
- 二、导出
引包:
一、导入 (一)controller层com.alibaba easyexcel 2.1.6
@DataLog(operationName = "导入了因私出国", operationDesc = "", methodType = MethodTypeEnum.import_TYPE)
@ApiOperation(value = "因私出国-导入")
@ApiOperationSupport(order = 5)
@GetMapping(Urls.Abroad.importAbroad)
public JsonObject
请求方式用post或get都行,这里使用get是为了做校验下载异常数据文件。fileName是前端通过公共组件上传后的文件名,后端可以获取上传路径,拼一下就能取得上传后文件的绝对路径。
也可以使用multipart/form-data的请求形式上传,这时后端需要改变下接收方式:
@DataLog(operationName = "导入了离任审计", operationDesc = "", methodType = MethodTypeEnum.import_TYPE)
@ApiOperation(value = "离任审计-导入")
@ApiOperationSupport(order = 12)
@PostMapping(Urls.OffAudit.importAudit)
public JsonObject
(二)service层
@Override
@Transactional(rollbackFor = Exception.class)
public List importAbroad(String fileName, HttpServletRequest request) {
List recordIds = new ArrayList<>();
String userId = userSessionService.getCurrentUserId(request);
String nodeId = userSessionService.getCurrentUserNodeId(request);
String upload = fileConfigProperties.getUpload();
String filePath = "";
filePath = upload + File.separator + fileName;
// 读Excel
EasyExcel.read(filePath, CsA12Do.class,
new AbroadListener(recordIds, userId, nodeId, upload, this))
.sheet().doRead();
// 控制同步功能
if (recordIds.size() == 0) {
return null;
}
// by hansc 12.10 调用人员库接口
LambdaQueryWrapper wrapper = Wrappers.lambdaQuery();
wrapper.in(CsA12::getRecordid, recordIds);
List list = list(wrapper);
for (CsA12 item : list) {
csA01Service.insertExportInPerson(item.geta00a(), item.geta1250(), item.geta1251(), request);
}
// 同步A00
Integer a00 = csRecordService.synA00("a12", "A1250", "RECORDID",
nodeId, recordIds);
// 同步现任职务
// 同步高管类别
csA12Mapper.synGrade(recordIds, nodeId);
// 删除文件
// boolean b = DownloadUtil.deleteFile(filePath);
return recordIds;
}
filePath即为上传的文件的绝对路径。EasyExcel的read方法可以读取Excel的sheet页的内容。三个参数,分别是文件绝对路径,一个Class类和一个自定义的监听器:
1、 Class类对应的是读取的Excel文件的模板类,它与Excel表格列对应:
index是从0开始,CsA12Do模板类的属性和Excel表格列一一对应。
2、 自定义的监听器类需要继承easyExcel的一个父类AnalysisEventListener,详情下面继续说
3、 sheet()方法为指定读取哪个sheet页,参数可以填sheet页的页名或者序号,不写的话默认就是Sheet1:
1、 easyExcel的原理是按行读取,即每次读取一行时新建一个监听器类,在监听器类中对读取到的内容进行处理。因为是通过new关键字新建监听器类,因此不能使用sprinboot的注入功能注入我们的bean对象,所以可以考虑使用构造器,将我们需要的bean对象以及一些自定义变量传进监听器中。
package com.bdsoft.cs.controller.utils; import com.alibaba.excel.context.AnalysisContext; import com.alibaba.excel.event.AnalysisEventListener; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.bdsoft.cs.entity.Codevalue; import com.bdsoft.cs.entity.CsA12; import com.bdsoft.cs.query.CsA12Do; import com.bdsoft.cs.service.daily.CsAbroadService; import com.bdsoft.cs.utils.ConvertUtil; import org.springframework.beans.BeanUtils; import org.springframework.transaction.annotation.Transactional; import java.io.File; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.UUID; public class AbroadListener extends AnalysisEventListener{ private static final int batchSize = 1000; private LocalDateTime time = LocalDateTime.now(); private List list = new ArrayList<>(batchSize); // 高管类别GGLB_02 // private List listOfA1252; // 前往国家/地区ZB01C private List listOfA1204; // 出国(境)事由CGGL_DMB_01 private List listOfZdyxa1202; // 存储主键,返回给前端 private List recordIds; private String userId; private String nodeId; private String upload; private CsAbroadService csAbroadService; private static Boolean formatFlag = true; // 格式校验开关:true开,false关 private static Boolean repeatFlag = true; // 重复校验开关 public AbroadListener(List recordIds, String userId, String nodeId, String upload, CsAbroadService csAbroadService) { this.recordIds = recordIds; this.userId = userId; this.nodeId = nodeId; this.upload = upload; this.csAbroadService = csAbroadService; // this.listOfA1252 = csAbroadService.getEntity("GGLB_02"); this.listOfA1204 = csAbroadService.getEntity("ZB01C"); this.listOfZdyxa1202 = csAbroadService.getEntity("CGGL_DMB_01"); } @Override public void invoke(CsA12Do csA12Do, AnalysisContext analysisContext) { list.add(csA12Do); if (list.size() >= batchSize) { this.saveData(); list.clear(); } } @Transactional(rollbackFor = Exception.class) void saveData() { if (verify()) { insert(); } } Boolean verify() { String separator = File.separator; String fileName = "因私出国_导入数据异常.xlsx"; // 报错提示文件名 String filePath = upload + separator + fileName; // 生成的文件的绝对路径 List formatList = new ArrayList<>(); // 格式错误的数据 List repeatList = new ArrayList<>(); // 重复的数据 // ① 日期校验 if (formatFlag) { for (CsA12Do item : list) { CsA12Do csA12Do = new CsA12Do(); String a1201 = item.getA1201(); // 出国日期 String a1203 = item.getA1203(); // 回国日期 csA12Do.setA1201(ConvertUtil.isNotTime(a1201) ? a1201 : null); csA12Do.setA1203(ConvertUtil.isNotTime(a1203) ? a1203 : null); csA12Do.setA00a(item.getA00a()); if (csA12Do.getA1201() != null || csA12Do.getA1203() != null) { formatList.add(csA12Do); } } } // ② 重复性校验 if (repeatFlag) { for (CsA12Do item : list) { CsA12Do query = new CsA12Do(); BeanUtils.copyProperties(item, query); query.setA1204(ConvertUtil.getDmcod(item.getA1204(), listOfA1204)); query.setZdyxa1202(ConvertUtil.getDmcod(item.getZdyxa1202(), listOfZdyxa1202)); LambdaQueryWrapper wrapper = Wrappers.lambdaQuery(); wrapper.eq(CsA12::geta00a, query.getA00a()) .eq(CsA12::geta1250, query.getA1250()) .eq(CsA12::geta1251, query.getA1251()) .eq(CsA12::geta1204, query.getA1204()) .apply("date_format(a1201,'%Y-%m-%d') = {0}", query.getA1201()) .apply("date_format(a1203,'%Y-%m-%d') = {0}", query.getA1203()) .eq(CsA12::getZdyxa1202, query.getZdyxa1202()) .eq(CsA12::getDeleteflag, 0); List list = csAbroadService.list(wrapper); if (list.size() > 0) { repeatList.add(item); } } } return csAbroadService.downloadExcel(filePath, CsA12Do.class, formatList, repeatList, null); } void insert() { List csA12s = new ArrayList<>(); for (CsA12Do item : list) { CsA12 csA12 = new CsA12(); String uuid = UUID.randomUUID().toString(); recordIds.add(uuid); BeanUtils.copyProperties(item, csA12); csA12.setRecordid(uuid); csA12.setCreatetime(time); csA12.setCreateuser(userId); csA12.setUpdatetime(time); csA12.setUpdateuser(userId); csA12.setDeleteflag(0); // 加权限 csA12.setNodeid(nodeId); // 码表转换:前往国家/地区 csA12.seta1204Text(item.getA1204()); csA12.seta1204(ConvertUtil.getDmcod(item.getA1204(), listOfA1204)); // 高管类别,先存汉字再转义 // 出国事由 csA12.setZdyxa1202Text(item.getZdyxa1202()); csA12.setZdyxa1202(ConvertUtil.getDmcod(item.getZdyxa1202(), listOfZdyxa1202)); // 日期转换:出国日期 csA12.seta1201(ConvertUtil.stringToLocalDateTime(item.getA1201())); // 回国日期 csA12.seta1203(ConvertUtil.stringToLocalDateTime(item.getA1203())); csA12s.add(csA12); } // 排序 int count = csAbroadService.count() + 1; for (int i = 0; i < csA12s.size(); i++, count++) { csA12s.get(i).setPorder(BigDecimal.valueOf(count)); } boolean b = csAbroadService.saveBatch(csA12s); } @Override public void doAfterAllAnalysed(AnalysisContext analysisContext) { this.saveData(); } }
2、 代码中在监听器类中定义了需要自定义全局变量,新建监听器时通过调用含参的构造器,给新建的监听器类的对象的全局变量赋值,就可以在监听器对象中调用这些属性和类了。
3、 看类的目录结构可以发现,Listener接口被ReadListener接口继承,ReadListener接口又被AnalysisEventListener类实现,AnalysisEventListener类又被我们的业务类AbroadListener继承,所以在我们的自定义监听器类AbroadListener中,需要实现ReadListener接口的几个方法,见上图。
4、 invoke方法首先执行,读一行Excel新建一次监听器类,然后执行一次invoke方法。
private Listlist = new ArrayList<>(batchSize); @Override public void invoke(CsA12Do csA12Do, AnalysisContext analysisContext) { list.add(csA12Do); if (list.size() >= batchSize) { this.saveData(); list.clear(); } }
5、 这个全局变量list里面存的是我们读取到的Excel行数据。batchSize是我们自定义的读取容量,根据实际业务设置大小,比如设置为100就是读取100行然后执行saveData方法。
@Transactional(rollbackFor = Exception.class)
void saveData() {
if (verify()) {
insert();
}
}
6、 saveData方法中先执行校验方法verify(),没有问题后再执行写入数据操作insert()。
Boolean verify() {
String separator = File.separator;
String fileName = "因私出国_导入数据异常.xlsx"; // 报错提示文件名
String filePath = upload + separator + fileName; // 生成的文件的绝对路径
List formatList = new ArrayList<>(); // 格式错误的数据
List repeatList = new ArrayList<>(); // 重复的数据
// ① 日期校验
if (formatFlag) {
for (CsA12Do item : list) {
CsA12Do csA12Do = new CsA12Do();
String a1201 = item.getA1201(); // 出国日期
String a1203 = item.getA1203(); // 回国日期
csA12Do.setA1201(ConvertUtil.isNotTime(a1201) ? a1201 : null);
csA12Do.setA1203(ConvertUtil.isNotTime(a1203) ? a1203 : null);
csA12Do.setA00a(item.getA00a());
if (csA12Do.getA1201() != null || csA12Do.getA1203() != null) {
formatList.add(csA12Do);
}
}
}
// ② 重复性校验
if (repeatFlag) {
for (CsA12Do item : list) {
CsA12Do query = new CsA12Do();
BeanUtils.copyProperties(item, query);
query.setA1204(ConvertUtil.getDmcod(item.getA1204(), listOfA1204));
query.setZdyxa1202(ConvertUtil.getDmcod(item.getZdyxa1202(), listOfZdyxa1202));
LambdaQueryWrapper wrapper = Wrappers.lambdaQuery();
wrapper.eq(CsA12::geta00a, query.getA00a())
.eq(CsA12::geta1250, query.getA1250())
.eq(CsA12::geta1251, query.getA1251())
.eq(CsA12::geta1204, query.getA1204())
.apply("date_format(a1201,'%Y-%m-%d') = {0}", query.getA1201())
.apply("date_format(a1203,'%Y-%m-%d') = {0}", query.getA1203())
.eq(CsA12::getZdyxa1202, query.getZdyxa1202())
.eq(CsA12::getDeleteflag, 0);
List list = csAbroadService.list(wrapper);
if (list.size() > 0) {
repeatList.add(item);
}
}
}
return csAbroadService.downloadExcel(filePath, CsA12Do.class, formatList, repeatList, null);
}
7、 校验方法中对日期格式和数据重复性两个内容进行了校验,当日期格式不符合标准的正则时或者Excel表格的数据与数据库现有数据有重复时,将这些数据记录在Excel中并通过浏览器下载。
二、导出1、 下载走的是我自定义的一个公共方法downloadExcel()
@Override
public Boolean downloadExcel(String filePath, Class> clz, List> formatList, List> repeatList, List excludeColumn) {
// 若异常数据源都为空,则导入数据无异常,正常执行导入
if (ConvertUtil.listIsNull(formatList) && ConvertUtil.listIsNull(repeatList)) {
return true;
}
Boolean flag = true; // 导入功能开关,默认开
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(filePath, clz).excludeColumnFiledNames(excludeColumn).build();
if (ConvertUtil.listIsNotNull(formatList)) {
WriteSheet sheet = EasyExcel.writerSheet("格式异常页").build(); // 一个Excel文件两个Sheet页
excelWriter.write(formatList, sheet);
}
if (ConvertUtil.listIsNotNull(repeatList)) {
WriteSheet sheet = EasyExcel.writerSheet("数据重复页").build();
excelWriter.write(repeatList, sheet);
}
} finally {
if (excelWriter != null) {
excelWriter.finish();
}
}
try {
if (ConvertUtil.listIsNotNull(formatList) || ConvertUtil.listIsNotNull(repeatList)) { // 当数据源不为空时才下载
DownloadUtil.download(filePath);
flag = false; // 若下载了Excel,则不执行导入功能,导入功能开关关闭
}
} catch (IOException e) {
e.printStackTrace();
} finally {
DownloadUtil.deleteFile(filePath);
}
return flag;
}
2、下载Excel用的也是easyExcel的write方法,即导出方法。
excelWriter = EasyExcel.write(filePath, clz).excludeColumnFiledNames(excludeColumn).build();
write方法有两个参数,一个是生成的文件的绝对路径(含文件后缀),另一个是生成的表格对应的模板类,这里用的还是CsA12Do类,和导入共用了一个模板类:
@ColumnWidth(20)
这个注解控制Excel表格的列宽,
@ExcelProperty(index = 0, value = "高管姓名")
这个主键控制了表头列的位置和列名。
excludeColumnFiledNames(excludeColumn)
该方法指定哪些表头列不输出,参数为列名的集合(即CsA12Do的属性名)
WriteSheet sheet = EasyExcel.writerSheet("数据重复页").build();
writerSheet方法设置sheet页的名字
excelWriter.write(repeatList, sheet);
write方法将数据源和表格模板绑定,即填充数据
DownloadUtil.download(filePath);
自定义了一个下载方法,代码附在后面
3、verify方法校验完,如果有问题,则将问题数据导出到Excel中,通过浏览器下载下来;
校验完没有问题,则执行insert方法,也就是写入操作,写入操作不多说。
4、doAfterAllAnalysed方法为监听器类生成后最后执行的一个方法。
P.S.
1、附上下载方法:
public static void download(String filePath) throws IOException {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
download(filePath, response, request);
}
public static void download(String filePath, HttpServletResponse response, HttpServletRequest request) throws IOException {
ServletOutputStream os = null;
try {
if (response == null) {
response = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getResponse();
}
ByteArrayOutputStream out = null;
FileInputStream inStream = null;
BufferedInputStream bis = null;
File file = new File(filePath);
String fileName = file.getName();
if (request.getHeader("User-Agent").toLowerCase().indexOf("msie") <= 0 && request.getHeader("User-Agent").indexOf("like Gecko") <= 0) {
fileName = new String(fileName.replaceAll(" ", "").getBytes("UTF-8"), "ISO8859-1");
} else {
fileName = URLEncoder.encode(fileName, "UTF-8");
}
if (file.exists()) {
response.reset();
response.setContentType("application/octet-stream");
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
os = response.getOutputStream();
out = new ByteArrayOutputStream();
inStream = new FileInputStream(filePath);
bis = new BufferedInputStream(inStream);
for(int c = bis.read(); c != -1; c = bis.read()) {
out.write(c);
}
bis.close();
inStream.close();
os.write(out.toByteArray());
}
} catch (IOException var13) {
logger.error("异常信息", var13);
} finally {
if (os != null) {
os.close();
}
}
}
这个方法不能自定义下载的文件名,可以改造下:
public static void download(String filePath, String templateName) throws IOException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
download(filePath, templateName, response, request);
}
public static void download(String filePath, String templateName, HttpServletResponse response, HttpServletRequest request) throws IOException {
ServletOutputStream os = null;
try {
if (response == null) {
response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
}
ByteArrayOutputStream out = null;
FileInputStream inStream = null;
BufferedInputStream bis = null;
File file = new File(filePath);
String fileName = file.getName();
if (StringUtility.isNotNull(templateName)) {
fileName = templateName;
}
if (request.getHeader("User-Agent").toLowerCase().indexOf("msie") <= 0 && request.getHeader("User-Agent").indexOf("like Gecko") <= 0) {
fileName = new String(fileName.replaceAll(" ", "").getBytes("UTF-8"), "ISO8859-1");
} else {
fileName = URLEncoder.encode(fileName, "UTF-8");
}
if (file.exists()) {
response.reset();
response.setContentType("application/octet-stream");
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
os = response.getOutputStream();
out = new ByteArrayOutputStream();
inStream = new FileInputStream(filePath);
bis = new BufferedInputStream(inStream);
for (int c = bis.read(); c != -1; c = bis.read()) {
out.write(c);
}
bis.close();
inStream.close();
os.write(out.toByteArray());
}
} catch (IOException var13) {
var13.printStackTrace();
} finally {
if (os != null) {
os.close();
}
}
}
2、附上日期正则校验:
// 年月日正则,标准格式:2021-12-23, 2021-1-1
public static String regex = "^((((19|20)\d{2})-(0?[13-9]|1[012])-(0?[1-9]|[12]\d|30))|(((19|20)\d{2})-(0?[13578]|1[02])-31)|(((19|20)\d{2})-0?2-(0?[1-9]|1\d|2[0-8]))|((((19|20)([13579][26]|[2468][048]|0[48]))|(2000))-0?2-29))$";
public static boolean isNotTime(String time) {
return !isTime(time);
}
public static boolean isTime(String time) {
return time != null && Pattern.matches(regex, time);
}
以上



