批量处理2——Java花式处理EXCEL

批量处理1——文件的上传(bootstrap+Ajax+SSM)
批量处理2——Java花式处理EXCEL
批量处理3——Excel文件导出
批量处理4——java处理压缩文件

Java知多少——相对路径和绝对路径
HTTP知多少——Content-disposition(文件下载)

java批量处理专题——用户将Excel上传到服务器,服务器如何解析Excel并将其解析为java对象?

推荐阅读:解析EXCEL的API方法

Excel模型.png

1. 简单API介绍

1.1 workbook工作空间

由于Excel存在xls以及xlsx两种格式,所以创建方式也有所不同。

  • 对于xls格式,需要使用HSSFWorkbook来创建对象;
  • 对于xlsx格式,需要使用XSSFWorkbook来创建工作薄;

需要注意HSSFWorkBook与XSSSFWorkbook两个类都是Workbook接口的实现类。

        String extString = filePath.substring(filePath.lastIndexOf("."));
        InputStream is = null;
        try {
            is = new FileInputStream(filePath);
            if (".xls".equals(extString)) {
                wb = new HSSFWorkbook(is);
            } else if (".xlsx".equals(extString)) {
                wb = new XSSFWorkbook(is);
            } else {
                throw new BusinessException("文件格式错误");
            }

首先,我们获取到文件后缀名,然后创建FileInputStream文件输入流。然后,根据文件格式的不同,选择不同的workbook

1.2 Sheet

看到Excel模型中的sheet1(页面)了吗。其实就是定位到当前sheet进行处理的。

Sheet子类

同样的创建Sheet的时候,也存在HSSFSheet和XSSFSheet这两种类型。所有我们使用其父类Sheet去处理对应的子类实现。

获取第一个sheet空间:

sheet = wb.getSheetAt(0);  

1.3 Row

作用是定位到特定的行。

getPhysicalNumberOfRows和getLastRowNum的区别这两个方法都是判断sheet是否有数据。
区别:

  • sheet.getPhysicalNumberOfRows():返回物理定义的行数(不是工作表中的行数)
  • sheet.getLastRowNum():返回此sheet中最后一行的数字编号,默认是从0开始。
  sheet = wb.getSheetAt(0);
  //获取sheet中,有数据的行数
  int rownum = sheet.getPhysicalNumberOfRows();
  //因为模板是在第四行开始读取,那么我们的直接定位到第四行
  for (int i = 4; i < rownum; i++) {
    //获取当前行
    Row row = sheet.getRow(i);
    if (row != null) {
      //开始对cell进行处理。
    }
  }

1.4 Cell

定位到特定的表格

获取到cell上的数据,进行“业务处理”,当然不同的业务逻辑不同,这里写几个特殊的处理方法。

1. 获取cell上的值,下标从0开始

Cell  cell= row.getCell(int index);

2. 判断Cell的单元格格式

单元格格式

单元格格式的枚举类型

public interface Cell {

    public final static int CELL_TYPE_NUMERIC = 0;

    public final static int CELL_TYPE_STRING = 1;

    public final static int CELL_TYPE_FORMULA = 2;

     public final static int CELL_TYPE_BLANK = 3;

    public final static int CELL_TYPE_BOOLEAN = 4;

    public final static int CELL_TYPE_ERROR = 5;
}

获取单元格的类型

int type1 = cell.getCellType();
if(type1==Cell.CELL_TYPE_STRING){
         //TODO                 
}

判断单元格是否为空

if (cell == null || org.apache.commons.lang3.StringUtils.isBlank(cell.getStringCellValue())) {
        failMap.put(i, "xx为空");
        break;
}

设置单元格格式

 cell.setCellType(Cell.CELL_TYPE_STRING);

若是文本格式,获取单元格里面的值

在批量上传的时候,推荐模板的单元格格式为文本模式。

cell.getStringCellValue();

如此一来,我们可以将Excel表格里面的数据全部解析出来。

2. 实战中的使用

当然,简单的API可以完成解析的,但是如何在项目中使用呢?

需求:对Excel处理完毕之后,需要记录成功多少笔,失败多少笔。

1. 用户点击上传成功之后,点击前端进行验证:
(1)创建List对象,将解析成功的Excel数据组装成对象保存在集合中;
(2)创建Map对象,保存解析失败的原因和row位置;

    @RequestMapping("/validateExcel")
    @ResponseBody
    public ResponseVo validateExcel() {
        ResponseVo vo = new ResponseVo();
        //保存解析成功的数据
        List<BatchUser> successUser= new ArrayList();
        //<处理失败的Excel行数,Excel上的第一个cell的数据>
        Map<Integer ,String> rowSucMap = new HashMap<Integer ,String>();
        //<处理失败的Excel行数,Excel上的第一个cell的数据>
        Map<Integer ,String> failMap = new HashMap<Integer ,String>();

        int count = 0;
        try{
            //Redis中取出文件名【上传的时候存入Redis中】
            String key = "XXXX";
            String fileName = jedisCluster.get(key);
            if(StringUtils.isBlank(fileName)){
                vo.setRetcode(ResponseVo.FAIL);
                vo.setMessage("无批量文件需要验证");
                return  vo;
            }
        //获取文件  
        File source = new File(fileName);

        count = batchUserService.filePaserExcel(successUser,source,rowSucMap,failMap);
  
          //数据入库或调用远程接口
          batchUserService.saveData(successUser);
          //将错误信息写入到(原)Excel中
           batchUserService.executeFailExcel(source.getAbsolutePath(),rowSucMap,failMap);
 vo.setRetcode(ResponseVo.SUCC);
            vo.setMessage("批量提现信息校验成功");
            Map<String, Object> resultMap = new HashMap<String, Object>();
            //写入成功笔数
            resultMap.put("success", rowSucMap.size());
             //写入失败笔数
            resultMap.put("error", count - rowSucMap.size());
             //保存到vo中,返回给前端
            vo.setData(resultMap);
 }catch (Exception e){
            vo.setRetcode(ResponseVo.FAIL);
            vo.setMessage("批量信息校验 原因:" + e.getMessage());
            logger.error("批量信息校验 原因:" + e);
        }
        return vo;
}

解析原文件

调用filePaserExcel(解析成功的对象,目标文件,解析成功Map,解析失败的Map)方法,解析Excel对象。

public int fileParseExcel(List<CliBatchWithdraw> successUser, File file,Map<Integer, String> rowSucMap, Map<Integer, String> failMap){
        String filePath = file.getAbsolutePath();
        if (filePath == null) {
            throw new BusinessException("文件路径为空");
        }
        String extString = filePath.substring(filePath.lastIndexOf("."));
        InputStream is = null;
        int count = 0;
        try {
            //获取WorkBook对象
            is = new FileInputStream(filePath);
            if (".xls".equals(extString)) {
                wb = new HSSFWorkbook(is);
            } else if (".xlsx".equals(extString)) {
                wb = new XSSFWorkbook(is);
            } else {
                throw new BusinessException("文件格式错误");
            }
            int count = 0;
        if (wb != null) {
            //获取sheet对象
            sheet = wb.getSheetAt(0);
            //获取Sheet中的实际行
            int rownum = sheet.getPhysicalNumberOfRows();
            String type = "";
            //在第四行进行解析
            for (int i = 4; i < rownum; i++) {
                Row row = sheet.getRow(i);
                if (row != null) {
                    User user= new User();
                    //获取第一个单元格
                    Cell userId= row.getCell(0);
                    //默认模板已经是文本格式的
                    if (userId== null || org.apache.commons.lang3.StringUtils.isBlank(userId.getStringCellValue())) {
                        //因为sheet可能存在空白行,所以判断第一列为空则代表Excel遍历完毕
                        failMap.put(i, "用户为空");
                        break;
                    }
                    userId.setCellType(Cell.CELL_TYPE_STRING);
                     //每遍历完一行,count++
                    count++;
          
                    Cell age = row.getCell(1);
                    if (age == null || org.apache.commons.lang3.StringUtils.isBlank(age .getStringCellValue())) {
                        failMap.put(i, "年龄为空");
                        continue;
                    }
                    age .setCellType(Cell.CELL_TYPE_STRING);
                    age = age.getStringCellValue().trim();
                     //判断年龄是否满足其他业务
                    if(!age.contains(type)) {
                        failMap.put(i,"XXXXX失败");
                        continue;
                    }
                   //TODO其他字段验证
                  //将数据保存到user对象中
                  user.setUserId(userId.getStringCellValue().trim());
                  user.setUserId(age.getStringCellValue().trim());
                  //存到List中,入库或调用远程接口
                 sucAccount.add(batchWithdraw);
                 //将成功的Excel行号和userId保存到Map中
                 rowSucMap.put(i,userId.getStringCellValue().trim());
                }
          //遍历结束
          }
        } catch (FileNotFoundException e) {
            logger.error(e.getMessage());
        } catch (IOException e) {
            logger.error(e.getMessage());
        } catch (Exception e) {
            e.printStackTrace();
        } 
       //返回一共处理多少行
       return count;
}

写入错误信息

对EXCEL写入错误信息,删除处理成功的行;

public void executeFailExcel(String filePath, Map<Integer, String> rowSucMap, Map<Integer, String> failMap) {
        InputStream is = null;
        FileOutputStream fout = null;
        try {
            Workbook wb = null;
            if (filePath == null) {
                throw new BusinessException("文件路径为空");
            }
            String extString = filePath.substring(filePath.lastIndexOf("."));
            is = new FileInputStream(filePath);
            if (".xls".equals(extString)) {
                wb = new HSSFWorkbook(is);
            } else if (".xlsx".equals(extString)) {
                wb = new XSSFWorkbook(is);
            }
           //直接对上传的文件进行操作。
            fout = new FileOutputStream(filePath);
            if (wb != null) {
                Sheet sheet = wb.getSheetAt(0);
                //遍历keyset,keyset()中保存失败的行号
                for (int rowNum : failMap.keySet()) {
                    Row row = sheet.getRow(rowNum);
                    if (row != null) {
                        Cell errMsg = row.createCell(9);
                        errMsg.setCellValue(failMap.get(rowNum));
                    }
                }
                //移除处理成功的行号
                for (int rowNum : rowSucMap.keySet()) {
                    Row row = sheet.getRow(rowNum);
                    if (row != null) {
                        sheet.removeRow(row);
                        sheet.shiftRows(rowNum + 1, sheet.getLastRowNum(), -1);
                    }
                }
            }
            //将wb写入到输出流
            wb.write(fout);
        } catch (FileNotFoundException e) {
            logger.error(e.getMessage());
        } catch (IOException e) {
            logger.error(e.getMessage());
        } 
    }

需要注意:

三个参数:参数1:起始行数,参数2:结束行数,参数3:向上/下偏移量。即我们把RowNum这行数据删除之后,它的下一行(rowNum + 1)到最后一行(sheet.getLastRowNum())向上偏移一行。

sheet.shiftRows(rowNum + 1, sheet.getLastRowNum(), -1);

此时,处理后的Excel已经保存在了服务器的位置。

文章参考:
使用POI进行Excel操作的总结一——创建Workbook,Sheet,Row以及Cell

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,544评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,430评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,764评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,193评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,216评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,182评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,063评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,917评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,329评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,543评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,722评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,425评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,019评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,671评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,825评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,729评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,614评论 2 353