Bootstrap

POI 导入excel文件( 一 )

0.介绍

Apache POI 是基于 Office Open XML 标准(OOXML)和 Microsoft 的 OLE 2 复合文档格式(OLE2)处理各种文件格式的开源项目。 简而言之,您可以使用 Java 读写 MS Excel 文件,可以使用 Java 读写 MS Word 和 MS PowerPoint 文件。

1.准备

1.1.poi依赖包

操作高版本的excel,word和ppt,需要引入

<dependency>
 <groupId>org.apache.poi</groupId>
 <artifactId>poi-ooxml</artifactId>
 <version>4.1.2</version>
</dependency>

操作低版本的excel

<dependency>
 <groupId>org.apache.poi</groupId>
 <artifactId>poi</artifactId>
 <version>4.1.2</version>
</dependency>

操作低版本的word和ppt

<dependency>
 <groupId>org.apache.poi</groupId>
 <artifactId>poi-scratchpad</artifactId>
 <version>4.1.2</version>
</dependency>

1.2.基本操作

在这里插入图片描述

@RequestMapping("/poiDemo/uploadStudentData")
    @ResponseBody
    public String uploadStudentData(@RequestParam("file") MultipartFile excelFile) throws IOException {

        //根据 MultipartFile 创建 工作簿对象
        XSSFWorkbook workbook = new XSSFWorkbook(excelFile.getInputStream());
        // 得到 sheet工作表 数量
        int numberOfSheets = workbook.getNumberOfSheets();

        for (int i = 0; i < numberOfSheets; i++) {
            // 根据 序号 得到 sheet工作表
            XSSFSheet sheet = workbook.getSheetAt(i);

            //获取有数据的最后一行
            int lastRowNum = sheet.getLastRowNum();

            for (int j = 0; j <= lastRowNum; j++) {
                // 得到  row
                XSSFRow row = sheet.getRow(j);
                // 得到 row 行号
                int rowNum = row.getRowNum();
                System.out.println("每 " + rowNum + " 行");
                // 获取有数据的最后一个单元格数
                int lastCellNum = row.getLastCellNum();

                for (int k = 0; k < lastCellNum; k++) {
                    XSSFCell cell = row.getCell(k);
                    
                    //TRUE或FALSE:BOOLEAN
                    //公式:FORMULA
                    //没有填值(空单元格):BLANK
                    //数字(整数或小数):NUMERIC
                    //字符串:STRING
                    
                    String str = "";
                    switch (cell.getCellType()) {
                        case BOOLEAN:
                            str = String.valueOf(cell.getBooleanCellValue());
                            break;
                        case FORMULA:
                            str = cell.getCellFormula();
                            break;
                        case BLANK:
                            str = "";
                            break;
                        case NUMERIC:
                            if (DateUtil.isCellDateFormatted(cell)) {
                                LocalDateTime date = DateUtil.getLocalDateTime(cell.getNumericCellValue());
                                str = date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                            } else {
                                str = String.format("%f", cell.getNumericCellValue());
                            }
                            break;
                        case STRING:
                            str = cell.getStringCellValue();
                            break;
                        default:
                            //_NONE, ERROR, BLANK 都按空来处理
                            break;
                    }

                    System.out.print("\t 每" + k + "格:" + cell.getCellType() + "= " + str);
                }
                System.out.println();

            }
        }
        System.out.println("numberOfSheets = " + numberOfSheets);
        return "ok";
    }

1.3.页面样式

        //1、创建workbook,对应一个excel
        XSSFWorkbook wb = new XSSFWorkbook();
        //1.5、生成excel中可能用到的单元格样式
        //首先创建字体样式
        //XSSFFont font = wb.createFont();//创建字体样式
        //font.setFontName("宋体");//使用宋体
        //font.setFontHeightInPoints((short) 12);//字体大小
        //font.setBold(true);
        然后创建单元格样式style
        //XSSFCellStyle style1 = wb.createCellStyle();
        //style1.setFont(font);//将字体注入
        //style1.setWrapText(true);// 自动换行
        //style1.setAlignment(HorizontalAlignment.CENTER);// 左右居中
        //style1.setVerticalAlignment(VerticalAlignment.CENTER);// 上下居中
        //style1.setFillForegroundColor(IndexedColors.WHITE.getIndex());// 设置单元格的背景颜色
        //style1.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        //style1.setBorderTop(BorderStyle.THIN);// 边框的大小
        //style1.setBorderBottom(BorderStyle.THIN);
        //style1.setBorderLeft(BorderStyle.THIN);
        //style1.setBorderRight(BorderStyle.THIN);

        //2、生成一个sheet,对应excel的sheet,参数为excel中sheet显示的名字
        XSSFSheet sheet = wb.createSheet(fileName);
        //3、设置sheet中每列的宽度,第一个参数为第几列,0为第一列;第二个参数为列的宽度,可以设置为0。
        // Test中有三个属性,因此这里设置三列,第0列设置宽度为0,第1~3列用以存放数据
        //for (int i = 0; i < dataTempList.size(); i++) {
        //    sheet.setColumnWidth(i, 20*256);
        //}


        //4、生成sheet中一行,从0开始
        XSSFRow row = sheet.createRow(0);
        //row.setHeight((short) 500);// 设定行的高度//5、创建row中的单元格,从0开始
        //得到标题行
        for (int i = 0; i < dataTempList.size(); i++) {
            XSSFCell cell = row.createCell(i);
            cell.setCellValue(dataTempList.get(i).get("title").toString());
            //cell.setCellStyle(style1);
        }

2.上传文件页面

2.0.下载文本模版

通过 a 超链接 下载在 /static/temp/student.xlsx 文件

<a  th:href="@{/static/temp/student.xlsx}" >
     下载模板
</a>

2.1.指定浏览的文件类型

<input type='file'>

上面的input标签用来浏览本地文件, 但是默认情况下能看到所有文件, 如果想只看到电子表格文件,应该怎么办?

只看到高版本的电子表格

<input type="file" name="file" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet">

高低版本都能看到

<input type="file" name="file" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-excel">

2.2.上传前的准备-编写页面js脚本

给form添加id值,方便抓取form表单,

给input添加name用来上传文件

给button添加单击事件, 用来驱动上传的脚本代码

<form id="fileForm" class="form form-inline" >
    <input type="file" name="file" accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet">
    <button onclick="importData()" type="button" class="btn btn-sm btn-primary">上传数据</button>
</form>
function importData(){
    var v = $("[name='file']").val();
    if (v === "") {
        layer.msg("请先选择要上传的文件");
        return;
    }
    var formData = new FormData($("#fileForm")[0]);
    $.ajax({
        type: "post",
        url: "/student/uploadStudentData",
        data: formData,
        contentType: false,
        processData: false,
        dataType: "json",
        async: true,
        cache: false,
        beforeSend: function(){layer.load();},
        success: function(result){
            //提示: 提示成功的行数, 如果有失败的, 提示第几行失败.
            layer.alert(result.msg, function(index){
                location.reload();
            });
        },
        error: function(){layer.alert("请求失败");},
        complete: function(){layer.closeAll("loading");}
    });
}

3.服务器处理

3.1.封装Excel工具类

都按字符串来读取单元格内容

public class ExcelUtils {
    public static String readData(XSSFCell cell){
        if (cell == null) {
            return "";
        }
        String str = "";
        switch (cell.getCellType()) {
            case BOOLEAN:
                str = String.valueOf(cell.getBooleanCellValue());
                break;
            case FORMULA:
                str = cell.getCellFormula();
                break;
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    LocalDateTime date = DateUtil.getLocalDateTime(cell.getNumericCellValue());
                    str = date.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                } else {
                    str = String.format("%f", cell.getNumericCellValue());
                }
                break;
            case STRING:
                str = cell.getStringCellValue();
                break;
            default:
                //_NONE, ERROR, BLANK 都按空来处理
                break;
        }
        return str;
    }
}

3.2.上传并解析电子表格

控制层接值, 变量的名称和页面的name属性要相同, 都叫file

@PostMapping("/student/uploadStudentData")
@ResponseBody
public ResultJson uploadStudentData(MultipartFile file) throws IOException{
    if (file.isEmpty()) {
        return ResultJson.error("文件不能为空");
    }
	return studentService.uploadStudentData(file);
}

数据传给service, 解析电子表格

序号姓名年龄出生日期体重学号性别所属班级
1李小三142012-11-2258.4705ST403190003序幕之星
2赵小四171976-09-15801.9828ST400190005魔法班
3刘小翠1231993-08-0230.8466ST403190007序幕之星
441993-11-29435.2564ST402190008策略组
5武小奎51995-02-15912.907ST403190009序幕之星
6孙小六1.21965-06-20162.8663苛ST403190031序幕之星
7王小二121984-11-27368.7244ST399190034科技班
8闩小七121992-01-1165.43a我24ST399190037科技班
9汪小汪892003-07-2686.6427ST399190038科技班
10李小龙422009-12-1988.7301ST400190039男12魔法班
11地小芳1202018-12-05123.45ST399190041科技班12

业务: 姓名不能为空, 出生日期不能为空, 出生日期格式要正确

@Autowired
    private TaaClassTeamService taaClassTeamService;

    @Override
    @Transactional(rollbackFor = {Exception.class, Error.class})
    public ResultJson uploadStudentData(MultipartFile file) throws IOException {
        //1, 工作簿
        XSSFWorkbook workbook = new XSSFWorkbook(file.getInputStream());
        //2, 工作表
        XSSFSheet sheet = workbook.getSheetAt(0);
        //3, 行
        int lastRowNum = sheet.getLastRowNum();

        //成功的行数
        int successCount = 0;
        //记录错误的原因
        StringBuilder error = new StringBuilder();
        //班级主键
        Integer ctId = null;

        //4, 单元格
        for (int i = 1; i <= lastRowNum; i++) {
            XSSFRow row = sheet.getRow(i);
            
            String name = ExcelUtils.readData(row.getCell(1));
            String age = ExcelUtils.readData(row.getCell(2));
            String birth = ExcelUtils.readData(row.getCell(3));
            String weight = ExcelUtils.readData(row.getCell(4));
            String sn = ExcelUtils.readData(row.getCell(5));
            String sex = ExcelUtils.readData(row.getCell(6));
            String ctName = ExcelUtils.readData(row.getCell(7));


 			if(name.isEmpty() && age.isEmpty() && birth.isEmpty()
                    && weight.isEmpty() && sn.isEmpty() && sex.isEmpty() && ctName.isEmpty()){
                continue;
            }

            //开关变量, 假设没有错误
            boolean bj = false;
            //行内错误收集器
            StringBuilder lineError = new StringBuilder("第" + i + "行:→");

            if ("".equals(name)) {
                bj = true;
                lineError.append("姓名不能为空;");
            }

            if ("".equals(age)) {
                bj = true;
                lineError.append("年龄不能为空;");
            }else if(!Pattern.matches("\\d+", age)){
                bj = true;
                lineError.append("年龄必须为整型;");
            }else if(!Pattern.matches("^[1][2-9]|[2-9][0-9]|1[01][0-9]|120$", age)){
                bj = true;
                lineError.append("年龄必须为12~120岁之间;");
            }else{
                if (age.contains(".")) {
                    age = age.substring(0, age.indexOf("."));
                }
            }

            if ("".equals(birth)) {
                bj = true;
                lineError.append("出生日期不能为空;");
            } else if(!Pattern.matches("\\d{4}-\\d{2}-\\d{2}( \\d{2}:\\d{2}:\\d{2})?", birth)){
                bj = true;
                lineError.append("出生日期格式错误;");
            } else {
                //保留yyyy-MM-dd格式, 如果是yyyy-MM-dd HH:mm:ss则去掉HH:mm:ss
                if (birth.contains(" ")) {
                    birth = birth.split(" ")[0];
                }
                try {
                    //只能解析 yyyy-MM-dd格式,其他格式都会报错
                    LocalDate.parse(birth);
                } catch (Exception e) {
                    bj = true;
                    lineError.append("出生日期格式错误;");
                }
            }

            if ("".equals(weight)) {
                bj = true;
                lineError.append("体重不能为空;");
            }else if(!Pattern.matches("\\d+(\\.\\d+)?", weight)){
                bj = true;
                lineError.append("体重必须为数值类型;");
            }

            if ("".equals(sn)) {
                bj = true;
                lineError.append("学号不能为空;");
            } else if(sn.length()>11){
                bj = true;
                lineError.append("学号的长度不能大于11位;");
            }

            if ("".equals(sex)) {
                bj = true;
                lineError.append("性别不能为空;→");
            } else if(!"男".equals(sex) && !"女".equals(sex)){
                bj = true;
                lineError.append("性别必须是男或者女;");
            }

            if ("".equals(ctName)) {
                bj = true;
                lineError.append("班级不能为空;");
            } else {
                LambdaQueryWrapper<TaaClassTeam> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(TaaClassTeam::getCtName, ctName).last("limit 1");
                TaaClassTeam classes = taaClassTeamService.getOne(queryWrapper);
                if (classes == null) {
                    bj = true;
                    lineError.append("所属班级不存在;");
                } else {
                    ctId = classes.getCtId();
                }
            }

            if (!bj) {
                successCount++;
                //存储数据
                StudentEntity s = new StudentEntity();
                //判断名字是否重复, 如果重复则添加A,B,C等
                String newName = checkRepeatName(name);
                s.setStuName( newName );

                s.setStuInAge(Integer.parseInt(age));
                LocalDate parse = LocalDate.parse(birth);
                Date date = Date.from(parse.atStartOfDay(ZoneId.systemDefault()).toInstant());
                s.setStuBirth(date);
                s.setStuWeight(Double.parseDouble(String.valueOf(weight)));
                s.setStuSn(sn);
                s.setStuSex("男".equals(sex) ? "1" : "0");
                s.setCtId(ctId);
                s.setStuInTime(new Date());
                s.setStuFaceUrl("");

                super.baseMapper.insert(s);
            } else {
                //存储错误信息
                error.append("<br>").append(lineError);
            }

        }
        return ResultJson.error("成功加入"+successCount+"条<br>"+error.toString());
    }

3.3.重复名称处理

重复处理

    /**
     *  不能重名, 王小二  , 王小二A , 王小二B...
     *  王小二Z, 王小二AA, 王小二AB...王小二AZ, 王小二AAA
     */
    private String checkRepeatName(String name) {
        List<StudentEntity> list = this.baseMapper.selectList(new LambdaQueryWrapper<StudentEntity>()
                .likeRight(StudentEntity::getStuName, name).orderByDesc(StudentEntity::getStuName));
        System.out.println("list 1=>>> " + list);
        if (list != null  &&  list.size()>0) {
            list = list.stream().sorted((s1,s2)->{
                return s2.getStuName().length() - s1.getStuName().length();
            }).collect(Collectors.toList());
            System.out.println("list 2=>>> " + list);

            String stuName = list.get(0).getStuName();
            char i = stuName.charAt(stuName.length() - 1) ;
            System.out.println("i = " + i);
            if ("Z".equals(String.valueOf(i))) {
                stuName =  stuName.substring(0, stuName.length() - 1) + "AA";
            }else if(((int)i) >=65 &&  ((int)i)<=89){

                char lastChar = (char)(i + 1);
                System.out.println("lastChar = " + lastChar);
                stuName = stuName.substring(0, stuName.length() - 1) + lastChar;
            }else{
                stuName += 'A';
            }
            return stuName;
        }else{
            return name;
        }
    }

或者通过 sql 统计姓名出现的次数

    <select id="selectListByName"   resultMap="BaseResultMap">
      select
      <include refid="Base_Column_List" />
      from tab_student
      where stu_name like concat(#{name},'%')
        order by stu_name desc
    </select>

3.4.java使用正则表达式的判断

if(Pattern.matches("正则表达式", "需要验证的字符串")){
    //满足正则的验证
} else {
    //不满足正则的验证
}

常用正则表达式

        "\\d+"    整数
        "^([1-9][0-9]\\*)+(\\.[0-9]{1,2})?$"   非零开头的最多带两位小数的数字
        "^\\d+(\\.\\d+)?$"    非负浮点数
        "^1[3-9]\d{9}$"  手机号码
        "^[1][2-9]|[2-9][0-9]|1[01][0-9]|120$"   12~120
        "^(((?:19|20)\d\d)-(0?[1-9]|1[0-2])-(0?[1-9]|[12][0-9]|3[01]))$"   yyyy-MM-dd格式日期
        "[\\u4e00-\\u9fa5]+"   汉字

例子, 整数的判断

// 4, 4.0, 5.000000 都是整数的话, 怎么验证
if(Pattern.matches("\\d+(\\.0+)?", age)){}
;