1.前言简介
[size=3.5]
ps: 如您有更好的方案或发现错误,请不吝见教,感激不尽啦~~~
利用了easyExcel实现导入操作, 全手动封装, 灵活利用, 为了满意部门业务需求, 也做了升级
- 全字段进行校验, 利用注解与正则表达式, 校验到每一行参数
- 报错信息明白, 精确到每一行, 某个字段不正确的报错
- 多个sheet导入的excel, 提示出 sheet名下的第几行报错
- 增长xid同批次报错回滚, 有点类似分布式事件, 也就是一行报错,全部批次数据清除
- 增长拓展性, 制作监听器,样式封装等, 利用接口特性, 方便多工程利用拓展
- 在特殊类型(如list等类型)导入时, 出现了报错, 进行了兼容操作
- 增长了数据库插入批次新增, 防止推数据库的数据量过大, 业务才略微麻烦
1.1 链接传送门
1.1.1 easyExcel传送门
⇒ EasyExcel文档链接
⇒ EasyExcel-Plus尽情期待~~~
2. Excel表格导入过程
实现功能请看1 前言简介, 里面有详细阐明
2.1 easyExcel的利用预备工作
2.1.1 导入maven依赖
<alibaba.easyexcel.version>3.3.4</alibaba.easyexcel.version>
- <!-- easyExcel -->
- <dependency>
- <groupId>com.alibaba</groupId>
- <artifactId>easyexcel</artifactId>
- <version>${alibaba.easyexcel.version}</version>
- </dependency>
复制代码 2.1.2 创建一个util包
里面专门放置全部的excel操作, 如图所示
- realDto 里面就是具体导入业务dto
- testGroup是自行测试代码
- 其他类均为焦点逻辑
- readme.md 是利用阐明, 防止反面人不知道如何利用
下面从2.1.3开始
2.1.3 ExcelUtils统一功能封装(单/多sheet导入)
跳转链接: 解释 @Accessors(chain = true) 与 easyExcel不兼容
- import com.alibaba.excel.EasyExcel;
- import com.alibaba.excel.ExcelReader;
- import com.alibaba.excel.ExcelWriter;
- import com.alibaba.excel.read.listener.ReadListener;
- import com.alibaba.excel.read.metadata.ReadSheet;
- import com.alibaba.excel.write.builder.ExcelWriterSheetBuilder;
- import com.alibaba.excel.write.handler.WriteHandler;
- import com.alibaba.excel.write.metadata.WriteSheet;
- import com.google.common.collect.Lists;
- import lombok.extern.slf4j.Slf4j;
- import javax.servlet.http.HttpServletResponse;
- import java.io.IOException;
- import java.io.InputStream;
- import java.io.UnsupportedEncodingException;
- import java.net.URLEncoder;
- import java.rmi.ServerException;
- import java.util.List;
- /**
- * Excel相关操作(简易)
- * 文章一: 解释 @Accessors(chain = true) 与 easyExcel不兼容
- * -> https://blog.csdn.net/qq_36268103/article/details/134954322
- *
- * @author pzy
- * @version 1.1.0
- * @description ok
- */
- @Slf4j
- public class ExcelUtils {
- /**
- * 方法1.1: 读取excel(单sheet)
- *
- * @param inputStream 输入流
- * @param dataClass 任意类型
- * @param listener 监听
- * @param sheetNo sheet编号
- * @param <T> 传入类型
- */
- public static <T> void readExcel(InputStream inputStream, Class<T> dataClass, ReadListener<T> listener, int sheetNo) {
- try (ExcelReader excelReader = EasyExcel.read(inputStream, dataClass, listener).build()) {
- // 构建一个sheet 这里可以指定名字或者no
- ReadSheet readSheet = EasyExcel.readSheet(sheetNo).build();
- // 读取一个sheet
- excelReader.read(readSheet);
- }
- }
- /**
- * 方法2.1: 读取excel(多sheet)
- *
- * @param inputStream 输入流
- * @param dataClass 任意类型
- * @param listener 监听
- * @param sheetNoList sheet编号
- * @param <T> 传入类型
- */
- public static <T> void readExcel(InputStream inputStream, Class<T> dataClass, ReadListener<T> listener, List<Integer> sheetNoList) {
- try (ExcelReader excelReader = EasyExcel.read(inputStream, dataClass, listener).build()) {
- List<ReadSheet> readSheetList = Lists.newArrayList();
- sheetNoList.forEach(sheetNo -> {
- // 构建一个sheet 这里可以指定名字或者no
- ReadSheet readSheet = EasyExcel.readSheet(sheetNo).build();
- readSheetList.add(readSheet);
- });
- // 读取一个sheet
- excelReader.read(readSheetList);
- }
- }
- /**
- * 单sheet excel下载
- *
- * @param httpServletResponse 响应对象
- * @param fileName excel文件名字
- * @param dataClass class类型(转换)
- * @param sheetName sheet位置1的名字
- * @param dataList 传入的数据
- * @param writeHandlers 写处理器们 可变参数 (样式)
- * @param <T> 泛型
- */
- public static <T> void easyDownload(HttpServletResponse httpServletResponse,
- String fileName,
- Class<T> dataClass,
- String sheetName,
- List<T> dataList,
- WriteHandler... writeHandlers
- ) throws IOException {
- //对响应值进行处理
- getExcelServletResponse(httpServletResponse, fileName);
- ExcelWriterSheetBuilder builder =
- EasyExcel.write(httpServletResponse.getOutputStream(), dataClass)
- .sheet(sheetName);
- //
- // builder.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
- // .registerWriteHandler(ExcelStyleTool.getStyleStrategy());
- /*样式处理器*/
- if (writeHandlers.length > 0) {
- for (WriteHandler writeHandler : writeHandlers) {
- builder.registerWriteHandler(writeHandler);
- }
- }
- builder.doWrite(dataList);
- }
- /**
- * 复杂 excel下载
- * 1. 多个sheet
- * 2. 多个处理器
- *
- * @param httpServletResponse 响应对象
- * @param fileName excel文件名字
- * @param dataClass class类型(转换)
- * @param sheetNameList 多sheet的名字数据
- * @param sheetDataList 多sheet的实际数据
- * @param writeHandlers 写处理器们 可变参数 (样式)
- * @param <T> 泛型
- */
- public static <T> void complexDownload(HttpServletResponse httpServletResponse,
- String fileName,
- Class<T> dataClass,
- List<String> sheetNameList,
- List<List<T>> sheetDataList,
- WriteHandler... writeHandlers) throws IOException {
- if (sheetNameList.size() != sheetDataList.size()) {
- throw new ServerException("抱歉,名字与列表长度不符~");
- }
- //对响应值进行处理
- getExcelServletResponse(httpServletResponse, fileName);
- try (ExcelWriter excelWriter = EasyExcel.write(httpServletResponse.getOutputStream()).build()) {
- // 去调用写入, 这里最终会写到多个sheet里面
- for (int i = 0; i < sheetNameList.size(); i++) {
- ExcelWriterSheetBuilder builder = EasyExcel.writerSheet(i, sheetNameList.get(i)).head(dataClass);
- if (writeHandlers.length > 0) {
- for (WriteHandler writeHandler : writeHandlers) {
- builder.registerWriteHandler(writeHandler);
- }
- }
- WriteSheet writeSheet = builder.build();
- excelWriter.write(sheetDataList.get(i), writeSheet);
- }
- }
- }
- /**
- * 获取excel的响应对象
- *
- * @param httpServletResponse response
- * @param fileName 文件名
- * @throws UnsupportedEncodingException 不支持编码异常
- */
- private static void getExcelServletResponse(HttpServletResponse httpServletResponse, String fileName) throws UnsupportedEncodingException {
- // 设置URLEncoder.encode可以防止中文乱码,和easyexcel没有关系
- fileName = URLEncoder.encode(fileName, "UTF-8").replaceAll("\\+", "%20");
- httpServletResponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
- httpServletResponse.setCharacterEncoding("utf-8");
- httpServletResponse.addHeader("Access-Control-Expose-Headers", "Content-Disposition");
- httpServletResponse.setHeader("Content-Disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");
- }
复制代码 2.1.4 ExcelDataListener数据监听器
读取excel表格数据 一条一条读取出来
ps: ResultResponse就是返回值封装类 随便都行200或500
- import com.alibaba.excel.context.AnalysisContext;
- import com.alibaba.excel.read.listener.ReadListener;
- import com.alibaba.excel.util.ListUtils;
- import com.alibaba.fastjson.JSON;
- import com.alibaba.fastjson.JSONObject;
- import com.google.common.collect.Lists;
- import lombok.extern.slf4j.Slf4j;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- import java.util.concurrent.ConcurrentHashMap;
- import java.util.concurrent.atomic.AtomicInteger;
- /**
- * 官方提供转换listener
- * ps: 有个很重要的点 ExcelDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
- *
- * @author pzy
- * @version 0.1.0
- * @description ok
- */
- //@Component
- @Slf4j
- public class ExcelDataListener<T> implements ReadListener<T> {
- /**
- * 每隔5条存储数据库,实际使用中可以300条,然后清理list ,方便内存回收
- */
- private static final int BATCH_COUNT = 300;
- private final ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<>();
- /**
- * 缓存的数据
- */
- // private List<T> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
- private final List<T> cachedDataList = Lists.newCopyOnWriteArrayList();
- /**
- * 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
- */
- private final ExcelDataService excelDataService;
- /**
- * 自行定义的功能类型 1配件(库存) 2供应商 3客户(假)资料
- */
- private final Integer functionType;
- /**
- * 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
- */
- public ExcelDataListener(ExcelDataService excelDataService1, Integer functionType) {
- this.excelDataService = excelDataService1;
- this.functionType = functionType;
- }
- /**
- * 这个每一条数据解析都会来调用
- *
- * @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
- */
- @Override
- public void invoke(T data, AnalysisContext context) {
- // String threadName = Thread.currentThread().getName();
- // System.out.println(threadName);
- log.info("解析到一条数据:{}", JSON.toJSONString(data));
- String sheetName = context.readSheetHolder().getSheetName();
- //ps: 慢换LongAdder
- // if (!map.containsKey(sheetName)) {
- // map.put(sheetName, new AtomicInteger(0));
- // } else {
- // map.put(sheetName, new AtomicInteger(map.get(sheetName).incrementAndGet()));
- // }
- int sheetDataCounts = map.computeIfAbsent(sheetName, k -> new AtomicInteger(0)).incrementAndGet();
- log.info("当前sheet的数据是: {}, 数量是第: {}个", sheetName, sheetDataCounts);
- if (data != null) {
- JSONObject jsonObject = JSON.parseObject(JSON.toJSONString(data));
- jsonObject.put("sheetName", sheetName);
- jsonObject.put("sheetDataNo", sheetDataCounts);//放入sheet数据编号(如果仅一个sheet
- cachedDataList.add((T) jsonObject);//类型明确(不增加通配符边界了 增加使用难度)
- }
- // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
- if (cachedDataList.size() >= BATCH_COUNT) {
- saveData();
- // 存储完成清理 list
- // cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
- cachedDataList.clear();//这块需要测试看看效果
- }
- }
- /**
- * 所有数据解析完成了 都会来调用
- */
- @Override
- public void doAfterAllAnalysed(AnalysisContext context) {
- // 这里也要保存数据,确保最后遗留的数据也存储到数据库
- log.info("{}条数据,开始存储数据库!", cachedDataList.size());
- saveData();
- cachedDataList.clear();
- log.info("所有数据解析完成!");
- }
- /**
- * 加上存储数据库
- */
- private void saveData() {
- log.info("{}条数据,开始存储数据库!", cachedDataList.size());
- // excelDataService.saveUser((T) new SystemUser());
- ResultResponse response = excelDataService.saveExcelData(functionType, cachedDataList);
- if (ResponseHelper.judgeResp(response)) {
- log.info("存储数据库成功!");
- }
- }
- }
复制代码 2.1.5 ResponseHelper相应值处置处罚
ResultResponse返回值封装类 任意即可
- import com.alibaba.fastjson.TypeReference;
- import org.springframework.web.context.request.RequestAttributes;
- import org.springframework.web.context.request.RequestContextHolder;
- import javax.servlet.http.HttpServletRequest;
- import java.util.Objects;
- /**
- * 响应 工具类
- *
- * @author pzy
- * @version 0.1.0
- * @description ok
- */
- public class ResponseHelper<T> {
- /**
- * 响应成功失败校验器
- * 疑似存在bug(未进行测试)
- */
- @Deprecated
- public T retBool(ResultResponse response) {
- /*1. 如果接口返回值返回的不是200 抛出异常*/
- if (response.getCode() != 200) {
- throw new ServiceException(response.getCode(), response.getMsg());
- }
- return response.getData(new TypeReference<T>() {
- });
- }
- /**
- * 请求响应值校验器(ResultResponse对象)
- */
- public static void retBoolResp(ResultResponse response) {
- if (response == null) {
- throw new NullPointerException("服务响应异常!");
- }
-
- /*1. 如果接口返回值返回的不是200 抛出异常*/
- if (!Objects.equals(response.getCode(), 200)) {
- throw new ServiceException(response.getCode(), response.getMsg());
- }
- }
- /**
- * 判定响应返回值
- * <p>
- * true 表示200 服务通畅
- * false 表示500 服务不通畅(
- */
- public static boolean judgeResp(ResultResponse response) {
- // 1. 如果接口返回值返回的不是200 返回false
- return response != null && Objects.equals(response.getCode(), 200);
- }
- /**
- * 通过上下文对象获取请求头的token值
- * RequestHelper.getHeaderToken()
- */
- @Deprecated
- public static String getHeaderToken() {
- //请求上下文对象获取 线程
- RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
- assert requestAttributes != null;
- HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
- assert request != null;
- return request.getHeader("token");
- }
- }
复制代码 2.1.6 MyConverter类-自定义转换器
@ExcelProperty(converter = MyConverter.class) 利用自定义转换器 针对list等类型进行操作
- import com.alibaba.excel.converters.Converter;
- import com.alibaba.excel.converters.ReadConverterContext;
- import com.alibaba.excel.converters.WriteConverterContext;
- import com.alibaba.excel.enums.CellDataTypeEnum;
- import com.alibaba.excel.metadata.data.WriteCellData;
- import java.util.Collections;
- import java.util.List;
- import java.util.StringJoiner;
- /**
- * list类型使用 自定义转换器(补充功能 beta版)
- * @author pzy
- * @version 0.1.0
- * @description ok
- */
- public class MyConverter implements Converter<List> {
- @Override
- public Class<?> supportJavaTypeKey() {
- return List.class;
- }
- @Override
- public CellDataTypeEnum supportExcelTypeKey() {
- return CellDataTypeEnum.STRING;
- }
- /**
- * 读(导入)数据时调用
- */
- @Override
- public List convertToJavaData(ReadConverterContext<?> context) {
- //当字段使用@ExcelProperty(converter = MyConverter.class)注解时会调用
- //context.getReadCellData().getStringValue()会获取excel表格中该字段对应的String数据
- //这里可以对数据进行额外的加工处理
- String stringValue = context.getReadCellData().getStringValue();
- //将数据转换为List类型然后返回给实体类对象DTO
- return Collections.singletonList(stringValue);
- }
- /**
- * 写(导出)数据时调用
- */
- @Override
- public WriteCellData<?> convertToExcelData(WriteConverterContext<List> context) {
- //当字段使用@ExcelProperty(converter = MyConverter.class)注解时会调用
- //context.getValue()会获取对应字段的List类型数据
- //这里是将List<String>转换为String类型数据,根据自己的数据进行处理
- StringJoiner joiner = new StringJoiner(",");
- for (Object data : context.getValue()) {
- joiner.add((CharSequence) data);
- }
- //然后将转换后的String类型数据写入到Excel表格对应字段当中
- return new WriteCellData<>(joiner.toString());
- }
- }
复制代码 2.1.7 ExcelDataService
数据处置处罚行为接口(多工程拓展)
- import java.util.List;
- /**
- * 数据处理service
- *
- * @author pzy
- * @version 0.1.0
- * @description ok
- */
- @FunctionalInterface
- public interface ExcelDataService {
- /**
- * 保存导入的数据
- * 分批进入 防止数据过大 - 栈溢出
- *
- * @param t 保存的数据类型
- */
- <T> ResultResponse saveExcelData(Integer functionType, List<T> t);
- }
复制代码 2.1.8 ExcelReqDTO 统一哀求dto
业务必要, 生成的文件名 sheet的名称 功能类型等信息
其中Lists.newArrayList() 没有的直接换成new ArrayList()即可 效果雷同
- import com.google.common.collect.Lists;
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- import lombok.experimental.Accessors;
- import java.util.List;
- /**
- * excel统一请求dto
- * <p>
- * 传入需要的参数, 生成对应的excel表格
- *
- * @author pzy
- * @version 0.1.0
- * @description ok
- */
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- @Accessors(chain = true)
- public class ExcelReqDTO {
- /**
- * 功能类型 例: 1用户 2其他业务
- */
- private Integer functionType;
- /**
- * excel类型 1单sheet 2多sheet
- */
- private Integer excelType;
- /**
- * 文件名称
- */
- private String fileName;
- /**
- * sheet名称
- */
- private String sheetName;
- /**
- * sheet名称组
- */
- private List<String> sheetNames = Lists.newArrayList();
- }
复制代码 2.1.9 上传文件校验
文件巨细校验可在设置文件内添加, 效果更好
- import lombok.extern.slf4j.Slf4j;
- import org.springframework.web.multipart.MultipartFile;
- import java.math.BigDecimal;
- import java.math.RoundingMode;
- import java.util.Arrays;
- import java.util.Locale;
- /**
- * 文件上传校验的公共方法
- * 严格校验
- *
- * @author pzy
- * @version 1.0.0
- */
- @Slf4j
- public class UploadCheckUtils {
- //20MB
- private static final Integer maxUpLoadSize = 20;
- /**
- * 只支持文件格式
- */
- public static final String[] YES_FILE_SUPPORT = {".xlsx", ".xls", ".doc", ".docx", ".txt", ".csv"};
- /**
- * 全部文件(普通文件,图片, 视频,音频)后缀 支持的类型
- */
- private static final String[] FILE_SUFFIX_SUPPORT = {".xlsx", ".xls", ".doc", ".docx", ".txt", ".csv",
- ".jpg", ".jpeg", ".png", ".mp4", ".avi", ".mp3"};
- /**
- * 文件名字 需要排除的字符
- * 废弃: "(", ")","",".", "——", "_","-"
- */
- private static final String[] FILE_NAME_EXCLUDE = {
- "`", "!", "@", "#", "$", "%", "^", "&", "*", "=", "+",
- "~", "·", "!", "¥", "……", "(", ")",
- "?", ",", "<", ">", ":", ";", "[", "]", "{", "}", "/", "\", "|",
- "?", ",", "。", "《", "》", ":", ";", "【", "】", "、"
- };
- /**
- * 多文件上传
- * 校验+大图片压缩
- */
- public MultipartFile[] uploadVerify(MultipartFile[] multipartFile) {
- /*校验1: 没有文件时,报错提示*/
- if (multipartFile == null || multipartFile.length <= 0) {
- throw new ServiceException(500, "上传文件不能为空");
- }
- /*总文件大于: ?Mb时, 拦截*/
- long sumSize = 0;
- for (MultipartFile file : multipartFile) {
- sumSize += file.getSize();
- }
- // 总文件超过100mb 直接拦截 beta功能 不正式使用
- if (sumSize > (100 * 1024 * 1024L)) {
- log.warn("(上传总空间)大于100MB, 文件上传过大!");
- // throw new ThirdServiceException(ResponseEnum.T160007, "(上传总空间)100");
- }
- /*校验2: 上传文件的长度小于等于1 就一个直接校验*/
- if (multipartFile.length <= 1) {
- MultipartFile[] files = new MultipartFile[1];
- files[0] = uploadVerify(multipartFile[0]);
- return files;
- }
- /*校验3: 多个文件直接校验 需要更换新的file */
- for (int i = 0; i < multipartFile.length; i++) {
- multipartFile[i] = uploadVerify(multipartFile[i]);
- }
- return multipartFile;
- }
- /**
- * 上传文件校验大小、名字、后缀
- *
- * @param multipartFile multipartFile
- */
- public static MultipartFile uploadVerify(MultipartFile multipartFile) {
- // 校验文件是否为空
- if (multipartFile == null) {
- throw new ServiceException(500, "上传文件不能为空呦~");
- }
- /*大小校验*/
- log.info("上传文件的大小的是: {} MB", new BigDecimal(multipartFile.getSize()).divide(BigDecimal.valueOf(1024 * 1024), CommonConstants.FINANCE_SCALE_LENGTH, RoundingMode.HALF_UP));
- log.info("上传限制的文件大小是: {} MB", maxUpLoadSize);
- if (multipartFile.getSize() > (maxUpLoadSize * 1024 * 1024L)) {
- throw new ServiceException(500, String.format("上传文件不得大于 %s MB", maxUpLoadSize));
- }
- // 校验文件名字
- String originalFilename = multipartFile.getOriginalFilename();
- if (originalFilename == null) {
- throw new ServiceException(500, "上传文件名字不能为空呦~");
- }
- for (String realKey : FILE_NAME_EXCLUDE) {
- if (originalFilename.contains(realKey)) {
- throw new ServiceException(500, String.format("文件名字不允许出现 '%s' 关键字呦~", realKey));
- }
- }
- // 校验文件后缀
- if (!originalFilename.contains(".")) {
- throw new ServiceException(500, "文件不能没有后缀呦~");
- }
- String suffix = originalFilename.substring(originalFilename.lastIndexOf('.'));
- /*校验: 文件格式是否符合要求*/
- if (!Arrays.asList(FILE_SUFFIX_SUPPORT).contains(suffix.toLowerCase(Locale.ROOT))) {
- //throw new RuntimeException("文件格式' " + realFormat + " '不支持,请更换后重试!");
- throw new ServiceException(500, "文件格式不支持呦~");
- }
- return multipartFile;
- }
- }
复制代码 2.1.10 最后写个readme.md(阐明利用方式)
这里写不写都行, 如有错误,请指出,谢谢啦~
- # excel工具类使用说明
- ## 1.本功能支持
- 1. excel导入
- 2. excel导出
- 3. 样式调整
- 4. 类型转换器
- ## 2. 使用技术介绍
- - 使用alibaba的easyExcel 3.3.4版本
- - 官网地址: [=> easyExcel新手必读 ](https://easyexcel.opensource.alibaba.com/docs/current)
- ## 3. 功能说明
- 1. ExcelUtils 统一工具类 封装了单/多sheet的导入与导出 任意类型传入 只需`.class`即可
- 2. ExcelStyleTool excel表格导出风格自定义
- 3. MyConverter: 对于list类型转换存在问题, 手写新的转换器(beta版)
- 4. ExcelDataListener 数据监听器, 在这里处理接收的数据
- 5. ExcelDataService 数据处理服务接口(封装统一的功能要求, 同时满足拓展性)
- 6. testGroup中 全部均为演示demo(请在需要的工程中使用)
- ## 4. 功能的演示
- 1. upload.html 前端简易测试功能页面(测试功能)
- ## 5. 版本说明
- 1. beta版(1.0.1), 测试中
- 2. 可能有更好的方法解决本次业务需求
- 3. 导出的样式仅仅是简易能用, 跟美观没啥关系
- ## 6. 特别注意
- 1. 生成的excel的实体类均需要新写(或者看6-2)
- 2. @Accessors不可使用: 源码位置-> (ModelBuildEventListener的buildUserModel)中的BeanMap.create(resultModel).putAll(map);
- > [不能使用@Accessors(chain = true) 注解原因: ](https://blog.csdn.net/zmx729618/article/details/78363191)
- >
- ## 7. 本文作者
- > @author: pzy
复制代码 2.2 easyExcel工具包(全)利用方式
testGroup组演示
2.2.1 UserExcelDTO 生成用户excel数据
跟随业务随意, 用啥字段就加啥, @ExcelIgnore //体现忽略此字段
- import com.alibaba.excel.annotation.ExcelIgnore;
- import com.alibaba.excel.annotation.ExcelProperty;
- import com.alibaba.excel.annotation.write.style.ColumnWidth;
- import com.alibaba.excel.annotation.write.style.ContentRowHeight;
- import com.alibaba.excel.annotation.write.style.HeadRowHeight;
- import lombok.AllArgsConstructor;
- import lombok.Data;
- import lombok.NoArgsConstructor;
- /**
- * excel表格示例demo
- * ps: 不能用accessors
- *
- * @author pzy
- * @version 0.1.0
- * @description ok
- */
- @ContentRowHeight(20)
- @HeadRowHeight(30)
- @ColumnWidth(25)
- @NoArgsConstructor
- @AllArgsConstructor
- //@Accessors(chain = true)
- @Data
- public class UserExcelDTO {
- /**
- * 用户ID
- */
- // @ExcelIgnore //忽略
- @ColumnWidth(20)
- @ExcelProperty(value = "用户编号")
- private Long userId;
- @ColumnWidth(50)
- @ExcelProperty(value = "真实姓名")
- private String realName;
- @ColumnWidth(50)
- @ExcelProperty(value = "手机号")
- private String phone;
- /**
- * 用户邮箱
- */
- @ColumnWidth(50)
- //@ExcelProperty(value = "邮箱",converter = MyConverter.class)
- @ExcelProperty(value = "邮箱")
- private String email;
- }
复制代码 2.2.2 ExcelDataServiceImpl实现类(工程一)
模仿一下数据库行为操作, 反面有现实操作呦~
- import java.util.List;
- /**
- * 实现类 demo实现方式 (此处不可注入bean) 示例文档
- *
- * @author pzy
- * @version 0.1.0
- * @description ok
- */
- //@Slf4j
- //@RequiredArgsConstructor
- //@Service
- public class ExcelDataServiceImpl implements ExcelDataService {
- /**
- * 保存导入的数据
- * 分批进入 防止数据过大 - 栈溢出
- *
- * @param t 保存的数据类型
- */
- @Override
- public <T> ResultResponse saveExcelData(Integer functionType, List<T> t) {
- //测试演示(添加数据库)
- return ResultResponse.booleanToResponse(true);
- }
- //
- // /**
- // * 获取数据并导出到excel表格中
- // *
- // * @param t 传入对象
- // * @return t类型集合
- // */
- // @Override
- // public <T> List<T> getExcelData(T t) {
- // //测试演示
- // return null;
- // }
- }
复制代码 2.2.3 upload.html测试页面
网上找的前端页面, 改了改, 自行测试, 我这里没有token传入位置,

解决方案一: 后端放行一下, 测试后关闭即可
解决方案二: 让前端直接连, 用前端写过的页面
等等
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- <title>EasyExcel</title>
- </head>
- <body>
- <div class="app">
- <input type="file" id="fileInput" accept=".xlsx, .xls, .csv">
- <button onclick="upload()">单sheet上传</button>
- <br>
- <br>
- <input type="file" id="fileInput1" accept=".xlsx, .xls, .csv">
- <button onclick="upload1()">多sheet上传</button>
- </div>
- <br>
- <div>
- <button onclick="download()">单sheet导出</button>
- <button onclick="download1()">多sheet导出</button>
- </div>
- </body>
- <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
- <script>
- const upload = () => {
- // 获取文件输入元素
- const fileInput = document.getElementById('fileInput')
- // 获取选中的文件
- const file = fileInput.files[0]
- if (!file) {
- alert('请选择一个文件')
- return
- }
- // 创建 FormData 对象
- const formData = new FormData()
- // 将文件添加到 FormData 对象
- formData.append('file', file)
- // 发送 POST 请求到后端
- axios.post('http://localhost:8001/system/excel/upload?functionType=1', formData, {
- headers: {
- 'Content-Type': 'multipart/form-data' // 设置正确的 Content-Type
- }
- }).then(response => {
- alert('文件上传成功')
- console.log('文件上传成功:', response.data)
- }).catch(error => {
- console.error('文件上传失败:', error)
- });
- }
- const upload1 = () => {
- // 获取文件输入元素
- const fileInput = document.getElementById('fileInput1')
- // 获取选中的文件
- const file = fileInput.files[0]
- if (!file) {
- alert('请选择一个文件')
- return
- }
- // 创建 FormData 对象
- const formData = new FormData()
- // 将文件添加到 FormData 对象
- formData.append('file', file)
- // 发送 POST 请求到后端
- axios.post('http://localhost:8001/system/excel/upload1?functionType=2', formData, {
- headers: {
- 'Content-Type': 'multipart/form-data', // 设置正确的 Content-Type
- 'token': ''
- }
- }).then(response => {
- alert('文件上传成功')
- console.log('文件上传成功:', response.data)
- }).catch(error => {
- console.error('文件上传失败:', error)
- });
- }
- const headers = {
- // 'Content-Type': 'application/json', // 设置请求头部的Content-Type为application/json
- // token: '', // 设置请求头部的Authorization为Bearer your_token
- // 'Token4545': '1', // 设置请求头部的Authorization为Bearer your_token
- // 'responseType': 'blob', // 设置响应类型为blob(二进制大对象)
- };
- const download = () => {
- const url = 'http://192.168.1.254:8001/system/excel/download?fileName=单S文件&functionType=1'
- axios.get(url, {
- responseType: 'blob'
- }).then(response => {
- // 从Content-Disposition头部中获取文件名
- const contentDisposition = response.headers['content-disposition']
- console.log(response)
- console.log(contentDisposition)
- const matches = /filename\*=(utf-8'')(.*)/.exec(contentDisposition)
- console.log(matches)
- let filename = 'downloaded.xlsx'
- if (matches != null && matches[2] != null) {
- console.log(matches[2])
- // 解码RFC 5987编码的文件名
- filename = decodeURIComponent(matches[2].replace(/\+/g, ' '))
- } else {
- // 如果没有filename*,尝试使用filename
- const filenameMatch = /filename="(.*)"/.exec(contentDisposition);
- console.log(71)
- if (filenameMatch != null && filenameMatch[1] != null) {
- filename = filenameMatch[1]
- console.log(74)
- }
- }
- // 创建一个a标签用于下载
- const a = document.createElement('a')
- // 创建一个URL对象,指向下载的文件
- const url = window.URL.createObjectURL(new Blob([response.data]))
- a.href = url
- a.download = filename // 设置文件名
- document.body.appendChild(a)
- a.click()
- document.body.removeChild(a)
- window.URL.revokeObjectURL(url)
- }).catch(error => {
- console.error('下载文件时出错:', error)
- })
- }
- const download1 = () => {
- const url = 'http://192.168.1.254:8001/system/excel/test2'
- axios.get(url, {
- responseType: 'blob', // 设置响应类型为blob(二进制大对象)
- }).then(response => {
- // 从Content-Disposition头部中获取文件名
- const contentDisposition = response.headers['content-disposition']
- console.log(response)
- console.log(contentDisposition)
- const matches = /filename\*=(utf-8'')(.*)/.exec(contentDisposition)
- console.log(matches)
- let filename = 'downloaded.xlsx'
- if (matches != null && matches[2] != null) {
- console.log(matches[2])
- // 解码RFC 5987编码的文件名
- filename = decodeURIComponent(matches[2].replace(/\+/g, ' '))
- } else {
- // 如果没有filename*,尝试使用filename
- const filenameMatch = /filename="(.*)"/.exec(contentDisposition);
- console.log(71)
- if (filenameMatch != null && filenameMatch[1] != null) {
- filename = filenameMatch[1]
- console.log(74)
- }
- }
- // 创建一个a标签用于下载
- const a = document.createElement('a')
- // 创建一个URL对象,指向下载的文件
- const url = window.URL.createObjectURL(new Blob([response.data]))
- a.href = url
- a.download = filename // 设置文件名
- document.body.appendChild(a)
- a.click()
- document.body.removeChild(a)
- window.URL.revokeObjectURL(url)
- }).catch(error => {
- console.error('下载文件时出错:', error)
- })
- }
- </script>
- </html>
复制代码 3.业务实战方式与效果(可跳过2.2)焦点
前言: 2.2介绍的是简朴的demo, 根据谁人进行拓展
业务需求
- 客户点击- 生成模板, 生成空的excel模板
- 根据阐明填写具体信息
- 导入后, 如果数据正常,导入成功
- 导入异常, 则明白告知数据问题在哪
- 本次导入的数据均不见效
- 面对多sheet导入异常, 明白指出 sheet名内的第*条数据,什么问题, 其他上同
操作方式:
- 设置批次导入(发放唯一批次号)
- 同批次的一组报错全部回滚
- 导入时生成批次, 整个线程利用一个批次
- 全字段自定义校验, 准确定位错误数据,给出精准提示
3.1 业务工具类
3.1.1 ThreadLocalUtils工具类(批次号)
写个底子的set和get , 通过当火线程转达xid号,
- import java.util.Map;
- /**
- * threadLocal使用工具方法
- * <p>
- * ps: jdk建议将 ThreadLocal 定义为 private static
- * 避免: 有弱引用,内存泄漏的问题了
- *
- * @author pzy
- * @description TODO beta01测试中
- * @version 1.0.1
- */
- public class ThreadLocalUtils {
- private static final ThreadLocal<Map<String, Object>> mapThreadLocal = new ThreadLocal<>();
- //获取当前线程的存的变量
- public static Map<String, Object> get() {
- return mapThreadLocal.get();
- }
- //设置当前线程的存的变量
- public static void set(Map<String, Object> map) {
- mapThreadLocal.set(map);
- }
- //移除当前线程的存的变量
- public static void remove() {
- mapThreadLocal.remove();
- }
- }
复制代码 3.1.2 自定义字段校验(注解)
-> 3.1.2_1 创建校验注解@DataCheck
如有更过细的校验, 请自行添加
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
- /**
- * 实体类-数据校验注解
- * <p>
- * ps: 第一版
- * 校验方式
- * 1. 数据为空
- * 2. 最大长度
- * 3. 正则表达式
- * 4. 报错信息
- * <p>
- * 其中功能校验在 ValidatorUtils 中
- *
- * @author pzy
- * @version 1.0.1
- * @description ok
- */
- @Target(ElementType.FIELD)
- @Retention(RetentionPolicy.RUNTIME)
- public @interface DataCheck {
- /**
- * 校验不能为空 true开启 false关闭
- */
- boolean notBank() default false;
- /**
- * 长度
- */
- int maxLength() default -1;
- /**
- * 正则表达式
- */
- String value() default "";
- /**
- * 报错信息
- */
- String message() default "";
- }
复制代码 -> 3.1.2_2 注解实现类ValidatorUtils(校验逻辑)
对@DataCheck校验逻辑进行支持, 其中异常条数和异常sheet名称(多sheet必要)必要转达
这里先不管这俩参数
方法一: 单sheet
方法二: 多sheet
- import com.alibaba.fastjson.JSON;
- import lombok.SneakyThrows;
- import lombok.extern.slf4j.Slf4j;
- import java.lang.reflect.Field;
- /**
- * 校验器工具类
- */
- @Slf4j
- public class ValidatorUtils {
- /**
- * DataCheck注册-正则校验器1
- */
- @SneakyThrows
- public static ResultResponse validate(Object obj, Integer errorCounts) {
- return validate(obj, errorCounts, null);
- }
- /**
- * DataCheck注册-正则校验器2
- */
- @SneakyThrows
- public static ResultResponse validate(Object obj, Integer errorCounts, String sheetName) {
- Field[] fields = obj.getClass().getDeclaredFields();
- for (Field field : fields) {
- if (field.isAnnotationPresent(DataCheck.class)) {
- DataCheck annotation = field.getAnnotation(DataCheck.class);
- field.setAccessible(true);
- Object value = field.get(obj);//实体类参数
- int maxLength = annotation.maxLength(); //长度
- String message = "";
- if (StringUtils.isNotBlank(sheetName)) {
- message = String.format("可能是: 品类: %s ,第 %s 条,要求: %s", sheetName, errorCounts, annotation.message()); //报错信息
- } else {
- message = String.format("可能是: 第 %s 条,要求: %s", errorCounts, annotation.message()); //报错信息
- }
- String matchValue = annotation.value();//正则表达式
- /*校验1: 开启校验 且参数是空的 */
- if (annotation.notBank() && (value == null || value == "")) {
- log.warn("Field :[" + field.getName() + "] is null");
- log.error("校验出异常的数据是:=====> {}", JSON.toJSONString(obj));
- // throw new IllegalArgumentException("数据为空呦, " + message);
- return ResultResponse.error("数据为空呦, " + message);
- }
- /*校验2: 长度字段大于0 并且长度大于*/
- if (maxLength > 0) {
- if (maxLength < String.valueOf(value).length()) {
- log.warn("Field :[" + field.getName() + " ] is out of range");
- log.error("校验出异常的数据是:=====> {}", JSON.toJSONString(obj));
- // throw new IllegalArgumentException("数据超范围了呦, " + message);
- return ResultResponse.error("数据超范围了呦, " + message);
- }
- }
- /*校验3: 正则不匹配 则刨除异常*/
- if (StringUtils.isNotBlank(matchValue) && value != null && !value.toString().matches(matchValue)) {
- log.warn("Field :[" + field.getName() + "] is not match");
- log.error("校验出异常的数据是:=====> {}", JSON.toJSONString(obj));
- // throw new IllegalArgumentException("数据格式不对呦, " + message);
- return ResultResponse.error("数据格式不对呦, " + message);
- }
- }
- }
- return ResultResponse.ok();
- }
- }
复制代码 3.2 工程内业务利用
3.2.0 创建上传或下载对象dto
添加校验注解 excel注册等, 不可利用@Accessors注解
- /**
- * 临时客户dto
- *
- * @author pzy
- * @version 0.1.0
- * @description ok
- */
- @Data
- @NoArgsConstructor
- @AllArgsConstructor
- public class UserTempDTO {
- @DataCheck(notBank = true, maxLength = 255, value = "[A-Za-z0-9_\\-\\u4e00-\\u9fa5]+", message = "(非空)用户姓名支持中文,英文,数字,'-' 和'_', 长度255位")
- @ExcelProperty(value = "真实姓名")
- private String realname;
- @DataCheck(maxLength = 2, message = "性别请填写: 男,女,未知")
- @ExcelProperty(value = "性别")
- private String gender;
- @DataCheck(notBank = true,maxLength = 255, value = "0?(13|14|15|18|17)[0-9]{9}", message = "(非空)手机号需纯数字且长度11位")
- @ExcelProperty(value = "电话号")
- private String phone;
- // @DataCheck(maxLength = 255, value = "[A-Za-z0-9_\\-\\u4e00-\\u9fa5]+", message = "地址信息名称支持中文,英文,数字,'-' 和'_', 长度255位")
- @DataCheck(maxLength = 255, message = "地址信息名称长度255位")
- @ExcelProperty(value = "地址信息")
- private String familyAddr;
- // @DataCheck(maxLength = 255, value = "[A-Za-z0-9_\\-\\u4e00-\\u9fa5]+", message = "头像链接地址,长度255位")
- @DataCheck(maxLength = 255, message = "头像链接地址,长度255位")
- @ExcelProperty(value = "头像")
- private String avatarUrl;
- //---------------------------------------->
- @ExcelIgnore
- @ExcelProperty(value = "备用电话号")
- private String sparePhone;
- @ExcelIgnore
- @ExcelProperty(value = "昵称")
- private String nickname;
- @ExcelIgnore
- @ApiModelProperty(value = "excel的sheet名称")
- private String sheetName;
- @ExcelIgnore
- @ApiModelProperty(value = "excel的sheet名称对应行号,用于报错行数")
- private String sheetDataNo;
- @ExcelIgnore
- @ApiModelProperty(value = "xid号")
- private String xid;
- }
复制代码 测试校验是否见效
- public static void main(String[] args) {
- UserTempDTO userTempDTO = new UserTempDTO();
- userTempDTO.setRealname("");
- userTempDTO.setGender("男");
- userTempDTO.setPhone("14788888888");
- userTempDTO.setFamilyAddr("");
- userTempDTO.setAvatarUrl("");
- ValidatorUtils.validate(userTempDTO,10);
- }
复制代码 3.2.1 创建controller
业务的入口
- @Slf4j
- @RequiredArgsConstructor
- @RestController
- @RequestMapping("/excel/test")
- public class SystemExcelController {
- private final SystemExcelService systemExcelService;
- @PostMapping("/upload")
- public ResultResponse upload(MultipartFile file, ExcelReqDTO excelReqDTO) throws IOException {
- log.info("===> excel文件上传 <===");
- //文件校验
- UploadCheckUtils.uploadVerify(file);
- try {
- Map<String, Object> map = new HashMap<>();
- long snowId = IdGenerater.getInstance().nextId();
- log.info("excel导入e_xid===> {}",snowId);
- map.put("e_xid", snowId);
- //存入threadLocal
- ThreadLocalUtils.set(map);
- systemExcelService.upload(file, excelReqDTO);
- } finally {
- ThreadLocalUtils.remove();
- }
- return ResultResponse.ok("操作成功");
- }
- @GetMapping("/download")
- public void download(HttpServletResponse httpServletResponse, ExcelReqDTO excelReqDTO) throws IOException {
- log.info("===> excel文件下载 <===");
- systemExcelService.download(httpServletResponse, excelReqDTO);
- }
- }
复制代码 3.2.2 接口SystemExcelService
- /**
- * excel表格实现类
- * @author pzy
- * @version 0.1.0
- * @description ok
- */
- public interface SystemExcelService {
- /**
- * 上传excel文件
- * @param file 文件
- * @param excelReqDTO 请求参数
- */
- void upload(MultipartFile file, ExcelReqDTO excelReqDTO);
- void download(HttpServletResponse httpServletResponse, ExcelReqDTO excelReqDTO);
- }
复制代码 3.2.3 实现类SystemExcelServiceImpl(需根业务自行调整)
这里面就是具体业务了
利用了ExcelUtils方法 实现多/单sheet导入与导出
导入ps: 在利用excelUtils方法时, 必要注入ExcelDataService接口来实现数据库存储操作
导出ps: 查询数据库数据, 处置处罚 传入Lists.newArrayList() 这个位置即可
- /**
- * excel表格实现类
- *
- * @author pzy
- * @version 0.1.0
- * @description ok
- */
- @Service
- @Slf4j
- @RequiredArgsConstructor
- public class SystemExcelServiceImpl implements SystemExcelService {
- private final ExcelDataService excelDataService;
-
- /**
- * 上传excel功能文件
- *
- * @param file 文件
- * @param excelReqDTO 请求参数
- */
- @SneakyThrows
- @Override
- public void upload(MultipartFile file, ExcelReqDTO excelReqDTO) {
- //功能类型 1 2 3
- Integer functionType = excelReqDTO.getFunctionType();
- if (Objects.equals(functionType, 1)) {//多sheet
- ExcelUtils.readExcel(file.getInputStream(),
- *.class,
- new ExcelDataListener<>(excelDataService, functionType),
- MathUtils.getIntRangeToList(0, 8)
- );
- } else if (Objects.equals(functionType, 2)) {//
- //单sheet
- ExcelUtils.readExcel(file.getInputStream(),
- *.class,
- new ExcelDataListener<>(excelDataService, functionType), 0
- );
- } else if (Objects.equals(functionType, 3)) {//
- //单sheet
- ExcelUtils.readExcel(file.getInputStream(),
- *.class,
- new ExcelDataListener<>(excelDataService, functionType), 0
- );
- } else {
- throw new ServiceException(ResponseEnum.E30001);
- }
- }
- @SneakyThrows
- @Override
- public void download(HttpServletResponse httpServletResponse, ExcelReqDTO excelReqDTO) {
- String fileName = excelReqDTO.getFileName();
- if (StringUtils.isBlank(fileName) || fileName.length() > 6) {
- throw new ServiceException("抱歉名称长度需大于0且不能超过6呦~");
- }
- //功能类型 1 2 3
- Integer functionType = excelReqDTO.getFunctionType();
- if (Objects.equals(functionType, 1)) {
- //sheet名字
- List<String> sheetNameList = ?;
- List<List<*>> sheetDataList = Lists.newArrayList();
- sheetNameList.forEach(sheetDto->sheetDataList.add(Lists.newArrayList()));
- ExcelUtils.complexDownload(httpServletResponse, fileName,
- ShopOfflineListDTO.class, sheetNameList,
- sheetDataList,
- new LongestMatchColumnWidthStyleStrategy(),
- ExcelStyleTool.getStyleStrategy()
- );
- } else if (Objects.equals(functionType, 2)) {//
- //写出excel核心代码
- ExcelUtils.easyDownload(httpServletResponse,
- fileName,
- *.class,
- "模板1",
- Lists.newArrayList(),//需要数据就传入 不需要就传递空集合
- new LongestMatchColumnWidthStyleStrategy(),
- ExcelStyleTool.getStyleStrategy()
- );
- } else if (Objects.equals(functionType, 3)) {
- //写出excel核心代码
- ExcelUtils.easyDownload(httpServletResponse,
- fileName,
- *.class,
- "模板1",
- Lists.newArrayList(),
- new LongestMatchColumnWidthStyleStrategy(),
- ExcelStyleTool.getStyleStrategy()
- );
- } else {
- throw new ServiceException(ResponseEnum.E30001);
- }
- }
- }
复制代码 3.2.4 寻找ExcelDataService的实现类
选择自己工程下的实现类, 写3.2.3的具体业务
如遇问题请提出

实现类重写saveExcelData()方法, 这里就枚举其中的两种利用方式, 业务代码跳过
- /**
- * 保存导入的数据
- * 分批进入 防止数据过大 - 栈溢出
- *
- * @param t 保存的数据类型
- */
- // @Transactional
- @Override
- public <T> ResultResponse saveExcelData(Integer functionType, List<T> t) {
- MemberResponseVo user = AuthServerConstant.loginUser.get();
- int companyId = user.getCompanyId();
- log.info("需要保存的数据: {}", JSON.toJSONString(t));
- //获取当前xid号-批次号(数据安全)
- String eXid = String.valueOf(ThreadLocalUtils.get().get("e_xid"));
- log.info("业务中: e_xid号=========================> {}", eXid);
- //功能类型 1配件(库存) 2供应商 3客户(假)资料
- if (Objects.equals(functionType, 1)) {//1
- return upload111Data(t, companyId, eXid);
- } else if (Objects.equals(functionType, 2)) {//2
- return upload222Data(t, companyId, eXid);
- } else if (Objects.equals(functionType, 3)) {//3
- return upload333Data(t, companyId, eXid);
- } else {
- throw new ServiceException(ResponseEnum.E30001);
- }
- }
- /**
- * 1. 上传配件数据
- *
- * @param t 传入数据
- * @param companyId 公司id
- * @param eXid eXid
- * @return ResultResponse对象
- */
- private <T> ResultResponse uploadPartsData(List<T> t, Integer companyId, String eXid) {
- List<***> a1List;
- try {
- a1ListList = JSON.parseObject(JSON.toJSONString(t), new TypeReference<List<***>>() {
- });
- } catch (Exception e) {
- e.printStackTrace();
- return ResultResponse.error("类型不匹配,请先检查金额字段,必须是纯数字的整数或小数哟~");
- }
- if (CollectionUtils.isEmpty(a1List)) {
- return ResultResponse.ok("无数据需要导入~");
- }
- //数据处理
- a1List.forEach(a1DTO -> {
- //数据校验
- ResultResponse response = ValidatorUtils.validate(a1DTO, Integer.valueOf(a1.getSheetDataNo()), a1.getSheetName());
- if (!ResponseHelper.judgeResp(response)) {
- //执行回滚操作
- if (!ResponseHelper.judgeResp(productFeignService.rollBackPartsData(eXid))) {
- log.error("======> 数据eXid: {} 回滚失败了 ", eXid);
- }
- throw new IllegalArgumentException(response.getMsg());
- }
- a1.setSourceType(1);
- a1.setXid(eXid);
- //根据品类名称 转换成品类id
- a1.setTypeId(changeTypeNameToId(a1.getSheetName()));
- });
- //远程调用 即使出现问题也不会滚 业务内直接删除数据重新传递
- return ***.saveBatch(a1List);
- }
复制代码 客户导入, 这个保留业务代码 方便查察具体利用方式
- /**
- * 3. 上传客户临时数据
- *
- * @param t 传入数据
- * @param companyId 公司id
- * @param eXid eXid
- * @return ResultResponse对象
- */
- private <T> ResultResponse uploadUserTempData(List<T> t, Integer companyId, String eXid) {
- List<UserTempDTO> userTempList = JSON.parseObject(JSON.toJSONString(t), new TypeReference<List<UserTempDTO>>() {
- });
- if (CollectionUtils.isEmpty(userTempList)) {
- return ResultResponse.ok("无数据需要导入呦~");
- }
- List<AxUserTemp> axUserTempList = userTempList.stream().map(userTempDTO -> {
- //数据校验(包含回滚)
- ResultResponse response = ValidatorUtils.validate(userTempDTO, Integer.valueOf(userTempDTO.getSheetDataNo()));
- if (!ResponseHelper.judgeResp(response)) {
- rollBackAxUserTemp(eXid);
- throw new IllegalArgumentException(response.getMsg());
- }
- AxUserTemp axUserTemp = new AxUserTemp();
- BeanUtils.copyProperties(userTempDTO, axUserTemp);
- axUserTemp.setId(IdGenerater.getInstance().nextId())
- .setUserRole(UserRoleEnum.CONSUMER.getCode()).setCompanyId(companyId)
- .setCreateTime(DateUtils.getNowDate()).setDelFlag(1).setXid(eXid);
- return axUserTemp;
- }).collect(Collectors.toList());
- try {
- if (!SqlHelper.retBool(axUserTempMapper.insertBatchSomeColumn(axUserTempList))) {
- rollBackAxUserTemp(eXid);
- throw new SystemServiceException(ResponseEnum.E500, String.format("前 %s 条数据存在问题,数据导入失败", axUserTempList.size()));
- }
- } catch (DuplicateKeyException e) {
- e.printStackTrace();
- rollBackAxUserTemp(eXid);
- throw new SystemServiceException(ResponseEnum.E500, String.format("前 %s 条数据重复,请检查(可能重复提交)", axUserTempList.size()));
- } catch (Exception e) {
- e.printStackTrace();
- rollBackAxUserTemp(eXid);
- throw new SystemServiceException(ResponseEnum.E500, String.format("前 %s 条数据存在问题,数据导入异常", axUserTempList.size()));
- }
- return ResultResponse.ok();
- }
-
复制代码 其中rollbackAxUserTemp()方法如下, 手动提交事件
第一步: 注入事件管理器
- /**
- * 事务管理器
- */
- private final PlatformTransactionManager platformTransactionManager;
- /**
- * 事务的一些基础信息,如超时时间、隔离级别、传播属性等
- */
- private final TransactionDefinition transactionDefinition;
复制代码 第二步: 根据xid号进行删除数据代表回滚, 添加代码 (其中可以添加一些参数 我这直接默认了)
- /**
- * 回滚临时用户数据(调用-事务不看结果直接提交)
- *
- * @param eXid xid号
- */
- private void rollBackAxUserTemp(String eXid) {
- TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);//TransactionStatus : 事务的一些状态信息,如是否是一个新的事务、是否已被标记为回滚
- try {
- axUserTempMapper.delete(Wrappers.<AxUserTemp>lambdaQuery().eq(AxUserTemp::getXid, eXid));
- platformTransactionManager.commit(transaction);
- } catch (Exception e) {
- // 回滚事务
- platformTransactionManager.rollback(transaction);
- throw e;
- }
- }
复制代码 3.3 程序测试实行结果及报错解决
3.3.1 实行结果
前端接入, 可以根据上面testGroup里面html的进行调整
后端部署, 测试, 效果如下
3.3.2 报错解决
emm, 代码太长了, 遇到,想用的话批评或私信吧, 遇到的问题太多了, 挑几个重点的
3.3.2_1 CROS跨域问题
- 生产环境跨域, 署理一下,设置nginx
- 开发环境: 当地开跨域只能解决其中一种问题, 下个插件cros就行了 , 有更好的办法(后端)欢迎批评哈~
3.3.2_2 excel表格导出是空
去掉@Accessors(chain = true)即可
3.3.2_3 导入dto中有list报错
利用注解 @ExcelProperty(value = “”,converter = MyConverter.class)
试一下, 欠好用批评区发一下
3.3.2_4 导出模板/sheet的名字不正确
根本是前端的问题了, 按照html里去改即可
3.3.2_5 待续未完…
想不起来还遇到哪些问题了, 业务层面的不包含, 多线程测试也正常, 等遇到问题在调整本文
如遇到部门类没有, 可根据上下文行为自行更改或批评区指出
渐渐在这里添加
4. 文章的总结与预告
4.1 本文总结
easyExcel实现具体操作, 遇到问题请看 3.3
4.2 下文预告
暂无
@author: pingzhuyan
@description: ok
@year: 2024
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
|