一、为什么获取行号

web开发中excel导入是常见需求,几乎是标配。导入的时候难免做数据校验,如果数据重复或者不符合业务要求,需要给前端友好的提示,那么行号必不可少。

二、如何获取

自定义监听器实现AnalysisEventListener<T>接口,重写invoke方法;

  @Override
    public void invoke(T data, AnalysisContext context) {
        ReadRowHolder readRowHolder = context.readRowHolder();
        Integer rowIndex = readRowHolder.getRowIndex();
        data.setRowIndex(rowIndex+1);
      
    }

三、完整示例

1、导入依赖

 <!--核心-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>3.0.5</version>
        </dependency>
        <!--非必须-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.80</version>
        </dependency>

2、编写Excel导入类

RowIndex 可以不是父类,如果多表导入都需要行号,建议使用父类,规范命名

import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
public class RowIndex {
    private Integer rowIndex;
}
mport lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

import java.util.Date;
import java.util.List;

/**
 * 用户 Excel 导入 VO
 */
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = false) // 设置 chain = false,避免用户导入有问题
public class UserImportExcelVO  extends RowIndex {

    @ExcelProperty("姓名(必填)")
    private String nickname;

    @ExcelProperty("密码(必填)")
    private String password;;

    @ExcelProperty("部门ID(必填)")
    private String deptId;

    @ExcelProperty("用户邮箱")
    private String email;

    @ExcelProperty("手机号码(必填)")
    private String mobile;

    @ExcelProperty("职级(必填)")
    private String rank;

    @ExcelProperty("毕业院校(必填)")
    private String school;

    @ExcelProperty("工资卡号(必填)")
    private String salaryCard;
    /**
     * 身份证
     */
    @ExcelProperty("身份证(必填)")
    private String identityCard;
    /**
     * 学历
     */
    @ExcelProperty("学历(必填)")
    private String xueliExcel;
    /**
     * 入职日期
     */
    @ExcelProperty("入职日期(必填)")
    private String entryDateStr;
    /**
     * 备注
     */
    @ExcelProperty("备注")
    private String remark;

    private List<Long> deptExcelIds;
    /**
     * 入职日期
     */
    private Date entryDate;

}

3、自定义监听器

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.CellExtra;
import com.alibaba.excel.read.metadata.holder.ReadRowHolder;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Map;

@Slf4j
public class UserExcelListener extends AnalysisEventListener<UserImportExcelVO> {

    List<UserImportExcelVO> res;
    public UserExcelListener(List<UserImportExcelVO> res) {
        this.res=res;
    }
    //解析异常调用
    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
                    excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
        }
    }
    //解析表头
    @Override
    public void invokeHead(Map<Integer, CellData> headMap, AnalysisContext context) {

    }
    //解析每行数据调用
    @Override
    public void invoke(UserImportExcelVO data, AnalysisContext context) {
        ReadRowHolder readRowHolder = context.readRowHolder();
        Integer rowIndex = readRowHolder.getRowIndex();
        data.setRowIndex(rowIndex+1);
        res.add(data);
    }



    @Override
    public void extra(CellExtra extra, AnalysisContext context) {

    }
    //所有数据解析完成后调用
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        log.info("所有数据解析完成!");
        log.info("解析数据:{}",JSON.toJSONString(res));
    }
}

4、编写接口

sheet(0).headRowNumber(1).doReadSync():导入excel第一张表,表头占一行,同步执行

    @PostMapping("/import")
    @ApiOperation("导入用户")
    @ApiImplicitParam(name = "file", value = "Excel 文件", required = true, dataTypeClass = MultipartFile.class)
    public CommonResult<Boolean> importExcel(@RequestParam("file") MultipartFile file) throws Exception {
        List<UserImportExcelVO> res=new ArrayList<>(16);
        EasyExcel.read(file.getInputStream(), UserImportExcelVO.class, new UserExcelListener(res)).sheet(0).headRowNumber(1).doReadSync();
        userService.importUsers(res);
        return success(true);
    }

5、校验处理

数据格式、判重、数据值是否合规等;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.IdcardUtil;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.StrUtil;
@Override
    @Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
    public void importUsers(List<UserImportExcelVO> importUsers) {
        //获取职级
        List<PostDO> rankList = postService.getPosts(null, Collections.singleton(CommonStatusEnum.ENABLE.getStatus()));
        List<String> rankNames = rankList.stream().map(PostDO::getName).collect(Collectors.toList());
        //获取学历

        List<DictDataDO> xueliList = dictDataMapper.selectByDictType("system_user_xueli");
        List<String> xueliValueList = xueliList.stream().map(DictDataDO::getValue).collect(Collectors.toList());
        if (CollUtil.isEmpty(importUsers)) {
            throw exception(USER_IMPORT_LIST_IS_EMPTY);
        }
        importUsers = importUsers.stream().filter((tem) -> tem.getNickname() != null
                || tem.getPassword() != null
                || tem.getDeptId() != null
                || tem.getMobile() != null
                || tem.getRank() != null
                || tem.getSchool() != null
                || tem.getSalaryCard() != null
                || tem.getIdentityCard() != null
                || tem.getXueliExcel() != null
                || tem.getEntryDate() != null).collect(Collectors.toList());
        importUsers.forEach((importUser) -> {
            checkImortExcel(importUser, rankNames,xueliValueList);
        });
        List<AdminUserDO> insertUserList=new ArrayList<>();
        for (UserImportExcelVO importUser : importUsers) {
            AdminUserDO insertUser=new AdminUserDO();
            BeanUtils.copyProperties(importUser,insertUser);
            insertUser.setXueli(Integer.parseInt(importUser.getXueliExcel()));
            //TODO [100,101]部门回显处理
            insertUser.setDeptIds(JSON.toJSONString(importUser.getDeptExcelIds()));
            insertUser.setUsername(importUser.getMobile());
            insertUserList.add(insertUser);
        }
        userMapper.insertBatch(insertUserList);
        insertUserList.forEach((tem)->{
            tem.setJobNumber(tem.getId());
            userMapper.updateById(tem);
        });

    log.info("导入数据:{}",JSON.toJSONString(importUsers));
    }
 private void checkImortExcel(UserImportExcelVO importUser, List<String> rankNames,List<String> xueliValueList) {
        Integer rowIndex = importUser.getRowIndex();
        String nickname = importUser.getNickname();
        String str = "第%s行";
        String s1 = ",";
        if (StrUtil.isEmpty(nickname)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",姓名不能为空", rowIndex)));
        }

        String password = importUser.getPassword();
        if (StrUtil.isEmpty(password)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",密码不能为空", rowIndex)));
        }
        if (password.length() < 8) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",密码不能小于8位", rowIndex)));
        }
        String deptId = importUser.getDeptId();
        if (StrUtil.isEmpty(deptId)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",用户部门不能为空", rowIndex)));
        }
        List<Long> deptIds = new ArrayList<>(3);
        if (deptId.contains(s1)) {
            String[] split = deptId.split(s1);
            for (String s : split) {
                deptIds.add(Long.valueOf(s));
            }
        } else {
            deptIds.add(Long.valueOf(deptId));
        }
        importUser.setDeptExcelIds(deptIds);
        checkDeptIds(deptIds,rowIndex,str);

        String email = importUser.getEmail();
        if (StrUtil.isEmpty(email)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",邮箱格式不能为空", rowIndex)));
        }
        if (!Validator.isEmail(email)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",邮箱格式不正确", rowIndex)));
        }

        String mobile = importUser.getMobile();
        if (StrUtil.isEmpty(mobile)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",手机号码不能为空", rowIndex)));
        }
        if (!Validator.isMobile(mobile)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",手机号码格式不正确", rowIndex)));
        }

        String rank = importUser.getRank();
        if (StrUtil.isEmpty(rank)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",职级不能为空", rowIndex)));
        }
        if (!rankNames.contains(rank)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",职级有误", rowIndex)));
        }
        String school = importUser.getSchool();
        if (StrUtil.isEmpty(school)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",毕业院校不能为空", rowIndex)));
        }

        String salaryCard = importUser.getSalaryCard();
        if (StrUtil.isEmpty(salaryCard)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",工资卡号不能为空", rowIndex)));
        }
        if (!NumberUtil.isNumber(salaryCard)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",工资卡号不正确", rowIndex)));
        }
        String identityCard = importUser.getIdentityCard();
        if (StrUtil.isEmpty(identityCard)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",身份证号码不能为空", rowIndex)));
        }
        if (!IdcardUtil.isValidCard(identityCard)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",身份证号码:%s不正确", rowIndex, identityCard)));
        }

        String xueli = importUser.getXueliExcel();
        if (StrUtil.isEmpty(xueli)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",学历不能为空", rowIndex)));
        }
        if (!xueliValueList.contains(xueli)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",学历有误", rowIndex)));
        }
        String entryDate = importUser.getEntryDateStr();
        if (entryDate == null) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",入职日期不能为空", rowIndex)));
        }
        if (entryDate.length()!=8||!Validator.isNumber(entryDate)) {
            throw ServiceExceptionUtil.exception(new ErrorCode(String.format(str + ",入职日期:%s格式有误", rowIndex,entryDate)));
        }
        DateTime yyyyMM = DateUtil.parse(entryDate, "yyyyMMdd");
        importUser.setEntryDate(yyyyMM);
    }

提示:如果数据表比较大分批处理,官方demo

package com.alibaba.easyexcel.test.demo.read;

import java.util.List;
import java.util.Map;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.metadata.data.ReadCellData;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;

import lombok.extern.slf4j.Slf4j;

/**
 * 读取转换异常
 *
 * @author Jiaju Zhuang
 */
@Slf4j
public class DemoExceptionListener implements ReadListener<ExceptionDemoData> {
    /**
     * 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
     */
    private static final int BATCH_COUNT = 5;

    private List<ExceptionDemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);

    /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     *
     * @param exception
     * @param context
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());
        // 如果是某一个单元格的转换异常 能获取到具体行号
        // 如果要获取头的信息 配合invokeHeadMap使用
        if (exception instanceof ExcelDataConvertException) {
            ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;
            log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),
                excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());
        }
    }

    /**
     * 这里会一行行的返回头
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHead(Map<Integer, ReadCellData<?>> headMap, AnalysisContext context) {
        log.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
    }

    @Override
    public void invoke(ExceptionDemoData data, AnalysisContext context) {
        context.
        log.info("解析到一条数据:{}", JSON.toJSONString(data));
        if (cachedDataList.size() >= BATCH_COUNT) {
            saveData();
            cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);
        }
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        saveData();
        log.info("所有数据解析完成!");
    }

    /**
     * 加上存储数据库
     */
    private void saveData() {
        log.info("{}条数据,开始存储数据库!", cachedDataList.size());
        log.info("存储数据库成功!");
    }
}

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐