Java实现Excel导入和导出

一 、环境准备

maven依赖

本次工具类的封装主要依赖于阿里巴巴的JSON包,以及表格处理的POI包,所以我们需要导入这两个库的依赖包,另外,我们还需要文件上传的相关包,毕竟我们在浏览器页面,做Excel导入时,是上传的Excel文件

    <!--excel工具依赖 start-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpmime</artifactId>
            <version>4.5.7</version>
        </dependency>
        <!-- JSON -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.83</version>
        </dependency>
        <!-- POI -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.16</version>
        </dependency>
    <!--excel工具依赖 end-->

引进knife,方便进行测试

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.8</version>
        </dependency>

        <!--knife4j start-->
        <dependency>
            <groupId>com.github.xiaoymin</groupId>
            <artifactId>knife4j-spring-boot-starter</artifactId>
            <version>2.0.9</version>
        </dependency>
        <!--knife4j end-->

 相关util工具类

public class ExcelClassField {
 
    /** 字段名称 */
    private String fieldName;
 
    /** 表头名称 */
    private String name;
 
    /** 映射关系 */
    private LinkedHashMap<String, String> kvMap;
 
    /** 示例值 */
    private Object example;
 
    /** 排序 */
    private int sort;
 
    /** 是否为注解字段:0-否,1-是 */
    private int hasAnnotation;
 
    public String getFieldName() {
        return fieldName;
    }
 
    public void setFieldName(String fieldName) {
        this.fieldName = fieldName;
    }
 
    public String getName() {
        return name;
    }
 
    public void setName(String name) {
        this.name = name;
    }
 
    public LinkedHashMap<String, String> getKvMap() {
        return kvMap;
    }
 
    public void setKvMap(LinkedHashMap<String, String> kvMap) {
        this.kvMap = kvMap;
    }
 
    public Object getExample() {
        return example;
    }
 
    public void setExample(Object example) {
        this.example = example;
    }
 
    public int getSort() {
        return sort;
    }
 
    public void setSort(int sort) {
        this.sort = sort;
    }
 
    public int getHasAnnotation() {
        return hasAnnotation;
    }
 
    public void setHasAnnotation(int hasAnnotation) {
        this.hasAnnotation = hasAnnotation;
    }
 
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelExport {
 
    /** 字段名称 */
    String value();
 
    /** 导出排序先后: 数字越小越靠前(默认按Java类字段顺序导出) */
    int sort() default 0;
 
    /** 导出映射,格式如:0-未知;1-男;2-女 */
    String kv() default "";
 
    /** 导出模板示例值(有值的话,直接取该值,不做映射) */
    String example() default "";
 
}
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelImport {
 
    /** 字段名称 */
    String value();
 
    /** 导出映射,格式如:0-未知;1-男;2-女 */
    String kv() default "";
 
    /** 是否为必填字段(默认为非必填) */
    boolean required() default false;
 
    /** 最大长度(默认255) */
    int maxLength() default 255;
 
    /** 导入唯一性验证(多个字段则取联合验证) */
    boolean unique() default false;
 
}
/**
 * Excel导入导出工具类
 *
 */
@SuppressWarnings("unused")
public class ExcelUtils {
 
    private static final String XLSX = ".xlsx";
    private static final String XLS = ".xls";
    public static final String ROW_MERGE = "row_merge";
    public static final String COLUMN_MERGE = "column_merge";
    private static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
    private static final String ROW_NUM = "rowNum";
    private static final String ROW_DATA = "rowData";
    private static final String ROW_TIPS = "rowTips";
    private static final int CELL_OTHER = 0;
    private static final int CELL_ROW_MERGE = 1;
    private static final int CELL_COLUMN_MERGE = 2;
    private static final int IMG_HEIGHT = 30;
    private static final int IMG_WIDTH = 30;
    private static final char LEAN_LINE = '/';
    private static final int BYTES_DEFAULT_LENGTH = 10240;
    private static final NumberFormat NUMBER_FORMAT = NumberFormat.getNumberInstance();
 
 
    public static <T> List<T> readFile(File file, Class<T> clazz) throws Exception {
        JSONArray array = readFile(file);
        return getBeanList(array, clazz);
    }
 
    public static <T> List<T> readMultipartFile(MultipartFile mFile, Class<T> clazz) throws Exception {
        JSONArray array = readMultipartFile(mFile);
        return getBeanList(array, clazz);
    }
 
    public static JSONArray readFile(File file) throws Exception {
        return readExcel(null, file);
    }
 
    public static JSONArray readMultipartFile(MultipartFile mFile) throws Exception {
        return readExcel(mFile, null);
    }
 
    public static Map<String, JSONArray> readFileManySheet(File file) throws Exception {
        return readExcelManySheet(null, file);
    }
 
    public static Map<String, JSONArray> readFileManySheet(MultipartFile file) throws Exception {
        return readExcelManySheet(file, null);
    }
 
    private static <T> List<T> getBeanList(JSONArray array, Class<T> clazz) throws Exception {
        List<T> list = new ArrayList<>();
        Map<Integer, String> uniqueMap = new HashMap<>(16);
        for (int i = 0; i < array.size(); i++) {
            T bean = getBean(clazz, array.getJSONObject(i), uniqueMap);
            list.add(bean);
        }
        return list;
    }
 
    /**
     * 获取每个对象的数据
     */
    private static <T> T getBean(Class<T> c, JSONObject obj, Map<Integer, String> uniqueMap) throws Exception {
        T t = c.newInstance();
        Field[] fields = c.getDeclaredFields();
        List<String> errMsgList = new ArrayList<>();
        boolean hasRowTipsField = false;
        StringBuilder uniqueBuilder = new StringBuilder();
        int rowNum = 0;
        for (Field field : fields) {
            // 行号
            if (field.getName().equals(ROW_NUM)) {
                rowNum = obj.getInteger(ROW_NUM);
                field.setAccessible(true);
                field.set(t, rowNum);
                continue;
            }
            // 是否需要设置异常信息
            if (field.getName().equals(ROW_TIPS)) {
                hasRowTipsField = true;
                continue;
            }
            // 原始数据
            if (field.getName().equals(ROW_DATA)) {
                field.setAccessible(true);
                field.set(t, obj.toString());
                continue;
            }
            // 设置对应属性值
            setFieldValue(t, field, obj, uniqueBuilder, errMsgList);
        }
        // 数据唯一性校验
        if (uniqueBuilder.length() > 0) {
            if (uniqueMap.containsValue(uniqueBuilder.toString())) {
                Set<Integer> rowNumKeys = uniqueMap.keySet();
                for (Integer num : rowNumKeys) {
                    if (uniqueMap.get(num).equals(uniqueBuilder.toString())) {
                        errMsgList.add(String.format("数据唯一性校验失败,(%s)与第%s行重复)", uniqueBuilder, num));
                    }
                }
            } else {
                uniqueMap.put(rowNum, uniqueBuilder.toString());
            }
        }
        // 失败处理
        if (errMsgList.isEmpty() && !hasRowTipsField) {
            return t;
        }
        StringBuilder sb = new StringBuilder();
        int size = errMsgList.size();
        for (int i = 0; i < size; i++) {
            if (i == size - 1) {
                sb.append(errMsgList.get(i));
            } else {
                sb.append(errMsgList.get(i)).append(";");
            }
        }
        // 设置错误信息
        for (Field field : fields) {
            if (field.getName().equals(ROW_TIPS)) {
                field.setAccessible(true);
                field.set(t, sb.toString());
            }
        }
        return t;
    }
 
    private static <T> void setFieldValue(T t, Field field, JSONObject obj, StringBuilder uniqueBuilder, List<String> errMsgList) {
        // 获取 ExcelImport 注解属性
        ExcelImport annotation = field.getAnnotation(ExcelImport.class);
        if (annotation == null) {
            return;
        }
        String cname = annotation.value();
        if (cname.trim().length() == 0) {
            return;
        }
        // 获取具体值
        String val = null;
        if (obj.containsKey(cname)) {
            val = getString(obj.getString(cname));
        }
        if (val == null) {
            return;
        }
        field.setAccessible(true);
        // 判断是否必填
        boolean require = annotation.required();
        if (require && val.isEmpty()) {
            errMsgList.add(String.format("[%s]不能为空", cname));
            return;
        }
        // 数据唯一性获取
        boolean unique = annotation.unique();
        if (unique) {
            if (uniqueBuilder.length() > 0) {
                uniqueBuilder.append("--").append(val);
            } else {
                uniqueBuilder.append(val);
            }
        }
        // 判断是否超过最大长度
        int maxLength = annotation.maxLength();
        if (maxLength > 0 && val.length() > maxLength) {
            errMsgList.add(String.format("[%s]长度不能超过%s个字符(当前%s个字符)", cname, maxLength, val.length()));
        }
        // 判断当前属性是否有映射关系
        LinkedHashMap<String, String> kvMap = getKvMap(annotation.kv());
        if (!kvMap.isEmpty()) {
            boolean isMatch = false;
            for (String key : kvMap.keySet()) {
                if (kvMap.get(key).equals(val)) {
                    val = key;
                    isMatch = true;
                    break;
                }
            }
            if (!isMatch) {
                errMsgList.add(String.format("[%s]的值不正确(当前值为%s)", cname, val));
                return;
            }
        }
        // 其余情况根据类型赋值
        String fieldClassName = field.getType().getSimpleName();
        try {
            if ("String".equalsIgnoreCase(fieldClassName)) {
                field.set(t, val);
            } else if ("boolean".equalsIgnoreCase(fieldClassName)) {
                field.set(t, Boolean.valueOf(val));
            } else if ("int".equalsIgnoreCase(fieldClassName) || "Integer".equals(fieldClassName)) {
                try {
                    field.set(t, Integer.valueOf(val));
                } catch (NumberFormatException e) {
                    errMsgList.add(String.format("[%s]的值格式不正确(当前值为%s)", cname, val));
                }
            } else if ("double".equalsIgnoreCase(fieldClassName)) {
                field.set(t, Double.valueOf(val));
            } else if ("long".equalsIgnoreCase(fieldClassName)) {
                field.set(t, Long.valueOf(val));
            } else if ("BigDecimal".equalsIgnoreCase(fieldClassName)) {
                field.set(t, new BigDecimal(val));
            } else if ("Date".equalsIgnoreCase(fieldClassName)) {
                try {
                    field.set(t, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(val));
                } catch (Exception e) {
                    field.set(t, new SimpleDateFormat("yyyy-MM-dd").parse(val));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    private static Map<String, JSONArray> readExcelManySheet(MultipartFile mFile, File file) throws IOException {
        Workbook book = getWorkbook(mFile, file);
        if (book == null) {
            return Collections.emptyMap();
        }
        Map<String, JSONArray> map = new LinkedHashMap<>();
        for (int i = 0; i < book.getNumberOfSheets(); i++) {
            Sheet sheet = book.getSheetAt(i);
            JSONArray arr = readSheet(sheet);
            map.put(sheet.getSheetName(), arr);
        }
        book.close();
        return map;
    }
 
    private static JSONArray readExcel(MultipartFile mFile, File file) throws IOException {
        Workbook book = getWorkbook(mFile, file);
        if (book == null) {
            return new JSONArray();
        }
        JSONArray array = readSheet(book.getSheetAt(0));
        book.close();
        return array;
    }
 
    private static Workbook getWorkbook(MultipartFile mFile, File file) throws IOException {
        boolean fileNotExist = (file == null || !file.exists());
        if (mFile == null && fileNotExist) {
            return null;
        }
        // 解析表格数据
        InputStream in;
        String fileName;
        if (mFile != null) {
            // 上传文件解析
            in = mFile.getInputStream();
            fileName = getString(mFile.getOriginalFilename()).toLowerCase();
        } else {
            // 本地文件解析
            in = new FileInputStream(file);
            fileName = file.getName().toLowerCase();
        }
        Workbook book;
        if (fileName.endsWith(XLSX)) {
            book = new XSSFWorkbook(in);
        } else if (fileName.endsWith(XLS)) {
            POIFSFileSystem poifsFileSystem = new POIFSFileSystem(in);
            book = new HSSFWorkbook(poifsFileSystem);
        } else {
            return null;
        }
        in.close();
        return book;
    }
 
    private static JSONArray readSheet(Sheet sheet) {
        // 首行下标
        int rowStart = sheet.getFirstRowNum();
        // 尾行下标
        int rowEnd = sheet.getLastRowNum();
        // 获取表头行
        Row headRow = sheet.getRow(rowStart);
        if (headRow == null) {
            return new JSONArray();
        }
        int cellStart = headRow.getFirstCellNum();
        int cellEnd = headRow.getLastCellNum();
        Map<Integer, String> keyMap = new HashMap<>();
        for (int j = cellStart; j < cellEnd; j++) {
            // 获取表头数据
            String val = getCellValue(headRow.getCell(j));
            if (val != null && val.trim().length() != 0) {
                keyMap.put(j, val);
            }
        }
        // 如果表头没有数据则不进行解析
        if (keyMap.isEmpty()) {
            return (JSONArray) Collections.emptyList();
        }
        // 获取每行JSON对象的值
        JSONArray array = new JSONArray();
        // 如果首行与尾行相同,表明只有一行,返回表头数据
        if (rowStart == rowEnd) {
            JSONObject obj = new JSONObject();
            // 添加行号
            obj.put(ROW_NUM, 1);
            for (int i : keyMap.keySet()) {
                obj.put(keyMap.get(i), "");
            }
            array.add(obj);
            return array;
        }
        for (int i = rowStart + 1; i <= rowEnd; i++) {
            Row eachRow = sheet.getRow(i);
            JSONObject obj = new JSONObject();
            // 添加行号
            obj.put(ROW_NUM, i + 1);
            StringBuilder sb = new StringBuilder();
            for (int k = cellStart; k < cellEnd; k++) {
                if (eachRow != null) {
                    String val = getCellValue(eachRow.getCell(k));
                    // 所有数据添加到里面,用于判断该行是否为空
                    sb.append(val);
                    obj.put(keyMap.get(k), val);
                }
            }
            if (sb.length() > 0) {
                array.add(obj);
            }
        }
        return array;
    }
 
    private static String getCellValue(Cell cell) {
        // 空白或空
        if (cell == null || cell.getCellTypeEnum() == CellType.BLANK) {
            return "";
        }
        // String类型
        if (cell.getCellTypeEnum() == CellType.STRING) {
            String val = cell.getStringCellValue();
            if (val == null || val.trim().length() == 0) {
                return "";
            }
            return val.trim();
        }
        // 数字类型
        if (cell.getCellTypeEnum() == CellType.NUMERIC) {
            String s = cell.getNumericCellValue() + "";
            // 去掉尾巴上的小数点0
            if (Pattern.matches(".*\\.0*", s)) {
                return s.split("\\.")[0];
            } else {
                return s;
            }
        }
        // 布尔值类型
        if (cell.getCellTypeEnum() == CellType.BOOLEAN) {
            return cell.getBooleanCellValue() + "";
        }
        // 错误类型
        return cell.getCellFormula();
    }
 
    public static <T> void exportTemplate(HttpServletResponse response, String fileName, Class<T> clazz) {
        exportTemplate(response, fileName, fileName, clazz, false);
    }
 
    public static <T> void exportTemplate(HttpServletResponse response, String fileName, String sheetName,
                                          Class<T> clazz) {
        exportTemplate(response, fileName, sheetName, clazz, false);
    }
 
    public static <T> void exportTemplate(HttpServletResponse response, String fileName, Class<T> clazz,
                                          boolean isContainExample) {
        exportTemplate(response, fileName, fileName, clazz, isContainExample);
    }
 
    public static <T> void exportTemplate(HttpServletResponse response, String fileName, String sheetName,
                                          Class<T> clazz, boolean isContainExample) {
        // 获取表头字段
        List<ExcelClassField> headFieldList = getExcelClassFieldList(clazz);
        // 获取表头数据和示例数据
        List<List<Object>> sheetDataList = new ArrayList<>();
        List<Object> headList = new ArrayList<>();
        List<Object> exampleList = new ArrayList<>();
        Map<Integer, List<String>> selectMap = new LinkedHashMap<>();
        for (int i = 0; i < headFieldList.size(); i++) {
            ExcelClassField each = headFieldList.get(i);
            headList.add(each.getName());
            exampleList.add(each.getExample());
            LinkedHashMap<String, String> kvMap = each.getKvMap();
            if (kvMap != null && kvMap.size() > 0) {
                selectMap.put(i, new ArrayList<>(kvMap.values()));
            }
        }
        sheetDataList.add(headList);
        if (isContainExample) {
            sheetDataList.add(exampleList);
        }
        // 导出数据
        export(response, fileName, sheetName, sheetDataList, selectMap);
    }
 
    private static <T> List<ExcelClassField> getExcelClassFieldList(Class<T> clazz) {
        // 解析所有字段
        Field[] fields = clazz.getDeclaredFields();
        boolean hasExportAnnotation = false;
        Map<Integer, List<ExcelClassField>> map = new LinkedHashMap<>();
        List<Integer> sortList = new ArrayList<>();
        for (Field field : fields) {
            ExcelClassField cf = getExcelClassField(field);
            if (cf.getHasAnnotation() == 1) {
                hasExportAnnotation = true;
            }
            int sort = cf.getSort();
            if (map.containsKey(sort)) {
                map.get(sort).add(cf);
            } else {
                List<ExcelClassField> list = new ArrayList<>();
                list.add(cf);
                sortList.add(sort);
                map.put(sort, list);
            }
        }
        Collections.sort(sortList);
        // 获取表头
        List<ExcelClassField> headFieldList = new ArrayList<>();
        if (hasExportAnnotation) {
            for (Integer sort : sortList) {
                for (ExcelClassField cf : map.get(sort)) {
                    if (cf.getHasAnnotation() == 1) {
                        headFieldList.add(cf);
                    }
                }
            }
        } else {
            headFieldList.addAll(map.get(0));
        }
        return headFieldList;
    }
 
    private static ExcelClassField getExcelClassField(Field field) {
        ExcelClassField cf = new ExcelClassField();
        String fieldName = field.getName();
        cf.setFieldName(fieldName);
        ExcelExport annotation = field.getAnnotation(ExcelExport.class);
        // 无 ExcelExport 注解情况
        if (annotation == null) {
            cf.setHasAnnotation(0);
            cf.setName(fieldName);
            cf.setSort(0);
            return cf;
        }
        // 有 ExcelExport 注解情况
        cf.setHasAnnotation(1);
        cf.setName(annotation.value());
        String example = getString(annotation.example());
        if (!example.isEmpty()) {
            if (isNumeric(example) && example.length() < 8) {
                cf.setExample(Double.valueOf(example));
            } else {
                cf.setExample(example);
            }
        } else {
            cf.setExample("");
        }
        cf.setSort(annotation.sort());
        // 解析映射
        String kv = getString(annotation.kv());
        cf.setKvMap(getKvMap(kv));
        return cf;
    }
 
    private static LinkedHashMap<String, String> getKvMap(String kv) {
        LinkedHashMap<String, String> kvMap = new LinkedHashMap<>();
        if (kv.isEmpty()) {
            return kvMap;
        }
        String[] kvs = kv.split(";");
        if (kvs.length == 0) {
            return kvMap;
        }
        for (String each : kvs) {
            String[] eachKv = getString(each).split("-");
            if (eachKv.length != 2) {
                continue;
            }
            String k = eachKv[0];
            String v = eachKv[1];
            if (k.isEmpty() || v.isEmpty()) {
                continue;
            }
            kvMap.put(k, v);
        }
        return kvMap;
    }
 
    /**
     * 导出表格到本地
     *
     * @param file      本地文件对象
     * @param sheetData 导出数据
     */
    public static void exportFile(File file, List<List<Object>> sheetData) {
        if (file == null) {
            System.out.println("文件创建失败");
            return;
        }
        if (sheetData == null) {
            sheetData = new ArrayList<>();
        }
        Map<String, List<List<Object>>> map = new HashMap<>();
        map.put(file.getName(), sheetData);
        export(null, file, file.getName(), map, null);
    }
 
    /**
     * 导出表格到本地
     *
     * @param <T>      导出数据类似,和K类型保持一致
     * @param filePath 文件父路径(如:D:/doc/excel/)
     * @param fileName 文件名称(不带尾缀,如:学生表)
     * @param list     导出数据
     * @throws IOException IO异常
     */
    public static <T> File exportFile(String filePath, String fileName, List<T> list) throws IOException {
        File file = getFile(filePath, fileName);
        List<List<Object>> sheetData = getSheetData(list);
        exportFile(file, sheetData);
        return file;
    }
 
    /**
     * 获取文件
     *
     * @param filePath filePath 文件父路径(如:D:/doc/excel/)
     * @param fileName 文件名称(不带尾缀,如:用户表)
     * @return 本地File文件对象
     */
    private static File getFile(String filePath, String fileName) throws IOException {
        String dirPath = getString(filePath);
        String fileFullPath;
        if (dirPath.isEmpty()) {
            fileFullPath = fileName;
        } else {
            // 判定文件夹是否存在,如果不存在,则级联创建
            File dirFile = new File(dirPath);
            if (!dirFile.exists()) {
                boolean mkdirs = dirFile.mkdirs();
                if (!mkdirs) {
                    return null;
                }
            }
            // 获取文件夹全名
            if (dirPath.endsWith(String.valueOf(LEAN_LINE))) {
                fileFullPath = dirPath + fileName + XLSX;
            } else {
                fileFullPath = dirPath + LEAN_LINE + fileName + XLSX;
            }
        }
        System.out.println(fileFullPath);
        File file = new File(fileFullPath);
        if (!file.exists()) {
            boolean result = file.createNewFile();
            if (!result) {
                return null;
            }
        }
        return file;
    }
 
    private static <T> List<List<Object>> getSheetData(List<T> list) {
        // 获取表头字段
        List<ExcelClassField> excelClassFieldList = getExcelClassFieldList(list.get(0).getClass());
        List<String> headFieldList = new ArrayList<>();
        List<Object> headList = new ArrayList<>();
        Map<String, ExcelClassField> headFieldMap = new HashMap<>();
        for (ExcelClassField each : excelClassFieldList) {
            String fieldName = each.getFieldName();
            headFieldList.add(fieldName);
            headFieldMap.put(fieldName, each);
            headList.add(each.getName());
        }
        // 添加表头名称
        List<List<Object>> sheetDataList = new ArrayList<>();
        sheetDataList.add(headList);
        // 获取表数据
        for (T t : list) {
            Map<String, Object> fieldDataMap = getFieldDataMap(t);
            Set<String> fieldDataKeys = fieldDataMap.keySet();
            List<Object> rowList = new ArrayList<>();
            for (String headField : headFieldList) {
                if (!fieldDataKeys.contains(headField)) {
                    continue;
                }
                Object data = fieldDataMap.get(headField);
                if (data == null) {
                    rowList.add("");
                    continue;
                }
                ExcelClassField cf = headFieldMap.get(headField);
                // 判断是否有映射关系
                LinkedHashMap<String, String> kvMap = cf.getKvMap();
                if (kvMap == null || kvMap.isEmpty()) {
                    rowList.add(data);
                    continue;
                }
                String val = kvMap.get(data.toString());
                if (isNumeric(val)) {
                    rowList.add(Double.valueOf(val));
                } else {
                    rowList.add(val);
                }
            }
            sheetDataList.add(rowList);
        }
        return sheetDataList;
    }
 
    private static <T> Map<String, Object> getFieldDataMap(T t) {
        Map<String, Object> map = new HashMap<>();
        Field[] fields = t.getClass().getDeclaredFields();
        try {
            for (Field field : fields) {
                String fieldName = field.getName();
                field.setAccessible(true);
                Object object = field.get(t);
                map.put(fieldName, object);
            }
        } catch (IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
        }
        return map;
    }
 
    public static void exportEmpty(HttpServletResponse response, String fileName) {
        List<List<Object>> sheetDataList = new ArrayList<>();
        List<Object> headList = new ArrayList<>();
        headList.add("导出无数据");
        sheetDataList.add(headList);
        export(response, fileName, sheetDataList);
    }
 
    public static void export(HttpServletResponse response, String fileName, List<List<Object>> sheetDataList) {
        export(response, fileName, fileName, sheetDataList, null);
    }
 
    public static void exportManySheet(HttpServletResponse response, String fileName, Map<String, List<List<Object>>> sheetMap) {
        export(response, null, fileName, sheetMap, null);
    }
 
 
    public static void export(HttpServletResponse response, String fileName, String sheetName,
                              List<List<Object>> sheetDataList) {
        export(response, fileName, sheetName, sheetDataList, null);
    }
 
    public static void export(HttpServletResponse response, String fileName, String sheetName,
                              List<List<Object>> sheetDataList, Map<Integer, List<String>> selectMap) {
 
        Map<String, List<List<Object>>> map = new HashMap<>();
        map.put(sheetName, sheetDataList);
        export(response, null, fileName, map, selectMap);
    }
 
    public static <T, K> void export(HttpServletResponse response, String fileName, List<T> list, Class<K> template) {
        // list 是否为空
        boolean lisIsEmpty = list == null || list.isEmpty();
        // 如果模板数据为空,且导入的数据为空,则导出空文件
        if (template == null && lisIsEmpty) {
            exportEmpty(response, fileName);
            return;
        }
        // 如果 list 数据,则导出模板数据
        if (lisIsEmpty) {
            exportTemplate(response, fileName, template);
            return;
        }
        // 导出数据
        List<List<Object>> sheetDataList = getSheetData(list);
        export(response, fileName, sheetDataList);
    }
 
    public static void export(HttpServletResponse response, String fileName, List<List<Object>> sheetDataList, Map<Integer, List<String>> selectMap) {
        export(response, fileName, fileName, sheetDataList, selectMap);
    }
 
    private static void export(HttpServletResponse response, File file, String fileName,
                               Map<String, List<List<Object>>> sheetMap, Map<Integer, List<String>> selectMap) {
        // 整个 Excel 表格 book 对象
        SXSSFWorkbook book = new SXSSFWorkbook();
        // 每个 Sheet 页
        Set<Entry<String, List<List<Object>>>> entries = sheetMap.entrySet();
        for (Entry<String, List<List<Object>>> entry : entries) {
            List<List<Object>> sheetDataList = entry.getValue();
            Sheet sheet = book.createSheet(entry.getKey());
            Drawing<?> patriarch = sheet.createDrawingPatriarch();
            // 设置表头背景色(灰色)
            CellStyle headStyle = book.createCellStyle();
            headStyle.setFillForegroundColor(IndexedColors.GREY_80_PERCENT.index);
            headStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            headStyle.setAlignment(HorizontalAlignment.CENTER);
            headStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.index);
            // 设置表身背景色(默认色)
            CellStyle rowStyle = book.createCellStyle();
            rowStyle.setAlignment(HorizontalAlignment.CENTER);
            rowStyle.setVerticalAlignment(VerticalAlignment.CENTER);
            // 设置表格列宽度(默认为15个字节)
            sheet.setDefaultColumnWidth(15);
            // 创建合并算法数组
            int rowLength = sheetDataList.size();
            int columnLength = sheetDataList.get(0).size();
            int[][] mergeArray = new int[rowLength][columnLength];
            for (int i = 0; i < sheetDataList.size(); i++) {
                // 每个 Sheet 页中的行数据
                Row row = sheet.createRow(i);
                List<Object> rowList = sheetDataList.get(i);
                for (int j = 0; j < rowList.size(); j++) {
                    // 每个行数据中的单元格数据
                    Object o = rowList.get(j);
                    int v = 0;
                    if (o instanceof URL) {
                        // 如果要导出图片的话, 链接需要传递 URL 对象
                        setCellPicture(book, row, patriarch, i, j, (URL) o);
                    } else {
                        Cell cell = row.createCell(j);
                        if (i == 0) {
                            // 第一行为表头行,采用灰色底背景
                            v = setCellValue(cell, o, headStyle);
                        } else {
                            // 其他行为数据行,默认白底色
                            v = setCellValue(cell, o, rowStyle);
                        }
                    }
                    mergeArray[i][j] = v;
                }
            }
            // 合并单元格
            mergeCells(sheet, mergeArray);
            // 设置下拉列表
            setSelect(sheet, selectMap);
        }
        // 写数据
        if (response != null) {
            // 前端导出
            try {
                write(response, book, fileName);
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            // 本地导出
            FileOutputStream fos;
            try {
                fos = new FileOutputStream(file);
                ByteArrayOutputStream ops = new ByteArrayOutputStream();
                book.write(ops);
                fos.write(ops.toByteArray());
                fos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
 
    /**
     * 合并当前Sheet页的单元格
     *
     * @param sheet      当前 sheet 页
     * @param mergeArray 合并单元格算法
     */
    private static void mergeCells(Sheet sheet, int[][] mergeArray) {
        // 横向合并
        for (int x = 0; x < mergeArray.length; x++) {
            int[] arr = mergeArray[x];
            boolean merge = false;
            int y1 = 0;
            int y2 = 0;
            for (int y = 0; y < arr.length; y++) {
                int value = arr[y];
                if (value == CELL_COLUMN_MERGE) {
                    if (!merge) {
                        y1 = y;
                    }
                    y2 = y;
                    merge = true;
                } else {
                    merge = false;
                    if (y1 > 0) {
                        sheet.addMergedRegion(new CellRangeAddress(x, x, (y1 - 1), y2));
                    }
                    y1 = 0;
                    y2 = 0;
                }
            }
            if (y1 > 0) {
                sheet.addMergedRegion(new CellRangeAddress(x, x, (y1 - 1), y2));
            }
        }
        // 纵向合并
        int xLen = mergeArray.length;
        int yLen = mergeArray[0].length;
        for (int y = 0; y < yLen; y++) {
            boolean merge = false;
            int x1 = 0;
            int x2 = 0;
            for (int x = 0; x < xLen; x++) {
                int value = mergeArray[x][y];
                if (value == CELL_ROW_MERGE) {
                    if (!merge) {
                        x1 = x;
                    }
                    x2 = x;
                    merge = true;
                } else {
                    merge = false;
                    if (x1 > 0) {
                        sheet.addMergedRegion(new CellRangeAddress((x1 - 1), x2, y, y));
                    }
                    x1 = 0;
                    x2 = 0;
                }
            }
            if (x1 > 0) {
                sheet.addMergedRegion(new CellRangeAddress((x1 - 1), x2, y, y));
            }
        }
    }
 
    private static void write(HttpServletResponse response, SXSSFWorkbook book, String fileName) throws IOException {
        response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        response.setCharacterEncoding("utf-8");
        String name = new String(fileName.getBytes("GBK"), "ISO8859_1") + XLSX;
        response.addHeader("Content-Disposition", "attachment;filename=" + name);
        ServletOutputStream out = response.getOutputStream();
        book.write(out);
        out.flush();
        out.close();
    }
 
    private static int setCellValue(Cell cell, Object o, CellStyle style) {
        // 设置样式
        cell.setCellStyle(style);
        // 数据为空时
        if (o == null || o.equals("")) {
            cell.setCellType(CellType.STRING);
            cell.setCellValue("");
            return CELL_OTHER;
        }
        // 是否为字符串
        if (o instanceof String) {
            String s = o.toString();
            // 当数字类型长度超过8位时,改为字符串类型显示(Excel数字超过一定长度会显示为科学计数法)
            if (isNumeric(s) && s.length() < 8) {
                cell.setCellType(CellType.NUMERIC);
                cell.setCellValue(Double.parseDouble(s));
                return CELL_OTHER;
            } else {
                cell.setCellType(CellType.STRING);
                cell.setCellValue(s);
            }
            if (s.equals(ROW_MERGE)) {
                return CELL_ROW_MERGE;
            } else if (s.equals(COLUMN_MERGE)) {
                return CELL_COLUMN_MERGE;
            } else {
                return CELL_OTHER;
            }
        }
        // 是否为字符串
        if (o instanceof Integer || o instanceof Long || o instanceof Double || o instanceof Float) {
            cell.setCellType(CellType.NUMERIC);
            cell.setCellValue(Double.parseDouble(o.toString()));
            return CELL_OTHER;
        }
        // 是否为Boolean
        if (o instanceof Boolean) {
            cell.setCellType(CellType.BOOLEAN);
            cell.setCellValue((Boolean) o);
            return CELL_OTHER;
        }
        // 如果是BigDecimal,则默认3位小数
        if (o instanceof BigDecimal) {
            cell.setCellType(CellType.NUMERIC);
            cell.setCellValue(((BigDecimal) o).setScale(3, RoundingMode.HALF_UP).doubleValue());
            return CELL_OTHER;
        }
        // 如果是Date数据,则显示格式化数据
        if (o instanceof Date) {
            cell.setCellType(CellType.STRING);
            cell.setCellValue(formatDate((Date) o));
            return CELL_OTHER;
        }
        // 如果是其他,则默认字符串类型
        cell.setCellType(CellType.STRING);
        cell.setCellValue(o.toString());
        return CELL_OTHER;
    }
 
    private static void setCellPicture(SXSSFWorkbook wb, Row sr, Drawing<?> patriarch, int x, int y, URL url) {
        // 设置图片宽高
        sr.setHeight((short) (IMG_WIDTH * IMG_HEIGHT));
        // (jdk1.7版本try中定义流可自动关闭)
        try (InputStream is = url.openStream(); ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            byte[] buff = new byte[BYTES_DEFAULT_LENGTH];
            int rc;
            while ((rc = is.read(buff, 0, BYTES_DEFAULT_LENGTH)) > 0) {
                outputStream.write(buff, 0, rc);
            }
            // 设置图片位置
            XSSFClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, y, x, y + 1, x + 1);
            // 设置这个,图片会自动填满单元格的长宽
            anchor.setAnchorType(AnchorType.MOVE_AND_RESIZE);
            patriarch.createPicture(anchor, wb.addPicture(outputStream.toByteArray(), HSSFWorkbook.PICTURE_TYPE_JPEG));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
 
    private static String formatDate(Date date) {
        if (date == null) {
            return "";
        }
        SimpleDateFormat format = new SimpleDateFormat(DATE_FORMAT);
        return format.format(date);
    }
 
    private static void setSelect(Sheet sheet, Map<Integer, List<String>> selectMap) {
        if (selectMap == null || selectMap.isEmpty()) {
            return;
        }
        Set<Entry<Integer, List<String>>> entrySet = selectMap.entrySet();
        for (Entry<Integer, List<String>> entry : entrySet) {
            int y = entry.getKey();
            List<String> list = entry.getValue();
            if (list == null || list.isEmpty()) {
                continue;
            }
            String[] arr = new String[list.size()];
            for (int i = 0; i < list.size(); i++) {
                arr[i] = list.get(i);
            }
            DataValidationHelper helper = sheet.getDataValidationHelper();
            CellRangeAddressList addressList = new CellRangeAddressList(1, 65000, y, y);
            DataValidationConstraint dvc = helper.createExplicitListConstraint(arr);
            DataValidation dv = helper.createValidation(dvc, addressList);
            if (dv instanceof HSSFDataValidation) {
                dv.setSuppressDropDownArrow(false);
            } else {
                dv.setSuppressDropDownArrow(true);
                dv.setShowErrorBox(true);
            }
            sheet.addValidationData(dv);
        }
    }
 
    private static boolean isNumeric(String str) {
        if (Objects.nonNull(str) && "0.0".equals(str)) {
            return true;
        }
        for (int i = str.length(); --i >= 0; ) {
            if (!Character.isDigit(str.charAt(i))) {
                return false;
            }
        }
        return true;
    }
 
    private static String getString(String s) {
        if (s == null) {
            return "";
        }
        if (s.isEmpty()) {
            return s;
        }
        return s.trim();
    }
 
}

二、数据导入

1、导入解析为JSON

创建一个学生信息的一个表格:

 Controller 代码:

@RestController
@RequestMapping("/excel")
@Api(tags = "EXCEL模块")
public class ExcelController {

    /****************************** 导入 ************************************/
    @PostMapping("/import1")
    @ApiOperationSupport(order = 1)
    @ApiOperation(value = "导入解析为JSON", notes = "导入解析为JSON")
    @ApiImplicitParams(
            {@ApiImplicitParam(name = "file", value = "文件流对象", required = true,dataType = "MultipartFile")}
    )
    public JSONArray import1(@RequestPart("file") MultipartFile file) throws Exception {
        JSONArray array = ExcelUtils.readMultipartFile(file);
        System.out.println("导入数据为:" + array);
        return array;
    }

}

测试效果:

 2、导入解析为对象(字段自动映射)

首先,你需要创建一个与导入表格对应的Java实体对象,并打上对应的Excel解析的导入注解,@ExcelImport注解的value则为表头名称

对于有的枚举数据,通常我们导入的时候,表格中的数据是值,而在数据保存时,往往用的是键,比如:我们用sex=1可以表示为男,sex=2表示为女,那么我们通过配置也可以达到导入时,数据的自动映射。

那么,我们只需要将Java实体中的对象sex字段的类型改为对应的数字类型Integer,然后再注解中配置好 kv 属性(属性格式为:键1-值1;键2-值2;键3-值3;.....)

@Data
@ApiModel(value = "用户导入类")
public class UserImport {

    @ApiModelProperty(value = "姓名")
    @ExcelImport(value = "姓名")
    private String name;

    @ApiModelProperty(value = "年龄")
    @ExcelImport(value = "年龄")
    private Integer age;

    @ApiModelProperty(value = "性别")
    @ExcelImport(value = "性别" , kv="1-男;2-女")
    private Integer sex;

    @ApiModelProperty(value = "电话")
    @ExcelImport(value = "电话")
    private String phone;

    @ApiModelProperty(value = "城市")
    @ExcelImport(value = "城市")
    private String city;

    @ApiModelProperty(value = "头像")
    @ExcelImport(value = "头像")
    private String avatar;
    
}
    @PostMapping("/import2")
    @ApiOperationSupport(order = 2)
    @ApiOperation(value = "导入解析为对象", notes = "导入解析为对象")
    @ApiImplicitParams(
            {@ApiImplicitParam(name = "file", value = "文件流对象", required = true,dataType = "MultipartFile")}
    )
    public List<User> import2(@RequestPart("file")MultipartFile file) throws Exception {
        List<User> users = ExcelUtils.readMultipartFile(file, User.class);
        System.out.println("导入数据为:" + users);
        return users;
    }

测试效果:

 3、导入解析为对象(获取行号)

我们在做页面数据导入时,有时候可能需要获取行号,好追踪导入的数据。

那么,我们只需要在对应的实体中加入一个 int 类型的 rowNum 字段即可。

private int rowNum;  //行号

 测试效果:

 4、 导入解析为对象(获取原始数据)

在做页面数据导入的时候,如果某行存在错误,一般我们会将原始的数据拿出来分析,为什么会造成数据错误。那么,我们在实体类中,增加一个 String 类型的 rowData 字段即可。

private String rowData;     //原始数据

 测试效果

5、导入解析为对象(获取错误提示)

当我们在导入数据的时候,如果某行数据存在,字段类型不正确,长度超过最大限制,必填字段验证,数据唯一性验证 等一些错误时候,我们可以往对象中添加一个 String 类型的 rowTips 字段,则可以直接拿到对应的错误信息。

private String rowTips;     //错误提示

比如,我们将表格中小明的性别改为F(F并不是映射数据),将小红的年龄改为二十八(不能转换为Integer类型数据)

 测试效果:可以看到,我们可以通过 rowTips 直接拿到对应的错误数据提示。

 6、导入解析为对象(限制字段长度)

比如,我们手机通常为11为长度,那么不妨限制电话的最大长度位数为11位。

对应的做法,就是在 @ExcelImport 注解中,设置 maxLength = 11 即可。

    @ApiModelProperty(value = "电话")
    @ExcelImport(value = "电话",maxLength = 11)
    private String phone;

比如,我们小明的电话长度设置为超过11位数的一个字符串

 测试效果:

7、导入解析为对象(必填字段验证) 

我们在做数据导入的时候,往往还会有一些必填字段,比如用户的名称,电话。

那么,我们只需要在 @ExcelImport 注解属性中,加上 required = true 即可

    @ApiModelProperty(value = "姓名")
    @ExcelImport(value = "姓名", required = true)
    private String name;

我们将小明和小红的姓名去掉,进行测试。 

 测试效果 

8、导入解析为对象(数据唯一性验证)

1、单字段唯一性验证

我们在导入数据的时候,某个字段是具有唯一性的,比如我们这里假设规定姓名不能重复,那么则可以在对应字段的 @ExcelImport 注解上加上 unique = true 属性

    @ApiModelProperty(value = "姓名")
    @ExcelImport(value = "姓名", required = true,unique = true)
    private String name;

 这里我们构建2条姓名一样的数据进行测试

 测试效果:

 2、多字段唯一性验证

如果你导入的数据存在多字段唯一性验证这种情况,只需要将每个对应字段的 @ExcelImport 注解属性中,都加上 unique = true 即可。

比如:我们将姓名和电话两个字段进行联合唯一性验证(即不能存在有名称和电话都一样的数据,单个字段属性重复允许)

 使用刚才的数据 

 测试效果:可以看到,虽然名称有相同,但电话不相同,所以这里并没有提示唯一性验证错误。

 现在,我们将小明的电话改成一样

 测试效果:可以看到,我们的联合唯一性验证生效了

9、导入多Sheet页

如果你的导入表格存在多个sheet页(如下图),并想解析每个sheet页的数据,那么也是可以的

 Controller 代码:

    @PostMapping("/import3")
    @ApiOperationSupport(order = 3)
    @ApiOperation(value = "导入多Sheet页", notes = "导入多Sheet页")
    @ApiImplicitParams(
            {@ApiImplicitParam(name = "file", value = "文件流对象", required = true,dataType = "MultipartFile")}
    )
    public Map<String, JSONArray> import3(@RequestPart("file") MultipartFile file) throws Exception {
        Map<String, JSONArray> map = ExcelUtils.readFileManySheet(file);
        map.forEach((key, value) -> {
            System.out.println("Sheet名称:" + key);
            System.out.println("Sheet数据:" + value);
            System.out.println("----------------------");
        });
        return map;
    }

 测试效果

 三、数据导出

1、动态导出(带图片)

如果你的导出中,需要将对应图片链接直接显示为图片的话,那么,这里也是可以的,只需要将对应的类型转为 java.net.URL 类型即可(注意:转的时候有异常处理,为了方便演示,我这里直接抛出)

    @GetMapping("/export4")
    @ApiOperationSupport(order = 4)
    @ApiOperation(value = "动态导出(带图片)", notes = "动态导出(带图片)", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void export4(HttpServletResponse response) throws MalformedURLException {
        // 表头数据
        List<Object> head = Arrays.asList("姓名","年龄","性别","头像");

        // 用户1数据
        List<Object> user1 = new ArrayList<>();
        user1.add("诸葛亮");
        user1.add(60);
        user1.add("男");
        user1.add(new URL("https://c-ssl.dtstatic.com/uploads/blog/202101/11/20210111220519_7da89.thumb.1000_0.jpeg"));  //显示图片
        // 用户2数据
        List<Object> user2 = new ArrayList<>();
        user2.add("大乔");
        user2.add(28);
        user2.add("女");
        user2.add(new URL("https://c-ssl.dtstatic.com/uploads/blog/202112/02/20211202113803_9ba69.thumb.1000_0.png"));  //显示图片

        // 将数据汇总
        List<List<Object>> sheetDataList = new ArrayList<>();
        sheetDataList.add(head);
        sheetDataList.add(user1);
        sheetDataList.add(user2);

        // 导出数据
        ExcelUtils.export(response,"用户表", sheetDataList);
    }

测试效果

2、动态导出(带下拉列表)

我们在做一些数据导出的时候,可能要对某一行的下拉数据进行约束限制。

比如,当我们下载一个导入模版的时候,我们可以将性别,城市对应的列设置为下拉选择。

    @GetMapping("/export5")
    @ApiOperationSupport(order = 5)
    @ApiOperation(value = "动态导出(带下拉列表)", notes = "动态导出(带下拉列表)", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void export5(HttpServletResponse response) throws MalformedURLException {
        // 表头数据
        List<Object> head = Arrays.asList("姓名","年龄","性别","城市");

        // 用户1数据
        List<Object> user1 = new ArrayList<>();
        user1.add("诸葛亮");
        user1.add(60);
        user1.add("男");
        user1.add("北京");
        // 用户2数据
        List<Object> user2 = new ArrayList<>();
        user2.add("大乔");
        user2.add(28);
        user2.add("女");
        user2.add("上海");

        // 将数据汇总
        List<List<Object>> sheetDataList = new ArrayList<>();
        sheetDataList.add(head);
        sheetDataList.add(user1);
        sheetDataList.add(user2);

        //设置下拉列表,键为第几列(从0开始),值为下拉数据
        Map<Integer,List<String>> selectMap = new HashMap<>(1);
        selectMap.put(2,Arrays.asList("男","女"));
        selectMap.put(3,Arrays.asList("北京","上海","广州"));

        // 导出数据
        ExcelUtils.export(response,"用户表", sheetDataList,selectMap);
    }

测试效果:

3、动态导出(横向合并)

比如,我们将表头横向合并,只需要将合并的单元格设置为 ExcelUtils.COLUMN_MERGE 即可。

    @GetMapping("/export6")
    @ApiOperationSupport(order = 6)
    @ApiOperation(value = "动态导出(横向合并)", notes = "动态导出(横向合并)", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void export6(HttpServletResponse response) throws MalformedURLException {
        // 表头数据
        List<Object> head = Arrays.asList("姓名","年龄","性别","地址",ExcelUtils.COLUMN_MERGE);

        // 用户1数据
        List<Object> user1 = new ArrayList<>();
        user1.add("诸葛亮");
        user1.add(60);
        user1.add("男");
        user1.add("北京");
        user1.add("门头沟区12号");

        // 用户2数据
        List<Object> user2 = new ArrayList<>();
        user2.add("大乔");
        user2.add(28);
        user2.add("女");
        user2.add("上海");
        user2.add("黄埔区24号");

        // 将数据汇总
        List<List<Object>> sheetDataList = new ArrayList<>();
        sheetDataList.add(head);
        sheetDataList.add(user1);
        sheetDataList.add(user2);

        // 导出数据
        ExcelUtils.export(response,"用户表", sheetDataList);
    }

测试效果

4、动态导出(纵向合并) 

除了横向合并,我们还可以进行纵向合并,只需要将合并的单元格设为 ExcelUtils.ROW_MERGE 即可。

    @GetMapping("/export7")
    @ApiOperationSupport(order = 7)
    @ApiOperation(value = "动态导出(纵向合并)", notes = "动态导出(纵向合并)", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void export7(HttpServletResponse response) throws MalformedURLException {
        // 表头数据
        List<Object> head = Arrays.asList("归属地","姓名","年龄","性别","地址");

        // 用户1数据
        List<Object> user1 = new ArrayList<>();
        user1.add("蜀国");
        user1.add("诸葛亮");
        user1.add(60);
        user1.add("男");
        user1.add("北京门头沟区12号");

        // 用户2数据
        List<Object> user2 = new ArrayList<>();
        user2.add("吴国");
        user2.add("大乔");
        user2.add(28);
        user2.add("女");
        user2.add("上海黄埔区24号");

        // 用户3数据
        List<Object> user3 = new ArrayList<>();
        user3.add(ExcelUtils.ROW_MERGE);
        user3.add("小乔");
        user3.add(26);
        user3.add("女");
        user3.add("上海金山区30号");

        // 将数据汇总
        List<List<Object>> sheetDataList = new ArrayList<>();
        sheetDataList.add(head);
        sheetDataList.add(user1);
        sheetDataList.add(user2);
        sheetDataList.add(user3);

        // 导出数据
        ExcelUtils.export(response,"用户表", sheetDataList);
    }

测试效果

5、导出模板(附示例数据)

我们在做数据导入的时候,往往首先会提供一个模版供其下载,这样用户在导入的时候才知道如何去填写数据。导出模板除了可以用上面的动态导出,这里还提供了一种更加便捷的写法。只需要创建一个类,然后再对应字段上打上 @ExcelExport 注解类即可。

我们在做模版下载时候,有时往往会携带一条样本数据,好提示用户数据格式是什么,那么我们只需要在对应字段加上example

@Data
@ApiModel(value = "用户导出类")
public class UserExport {

    @ApiModelProperty(value = "姓名")
    @ExcelExport(value = "姓名",example = "张三")
    private String name;

    @ApiModelProperty(value = "年龄")
    @ExcelExport(value = "年龄",example = "20")
    private Integer age;

    @ApiModelProperty(value = "性别")
    @ExcelExport(value = "性别",example = "男")
    private Integer sex;

    @ApiModelProperty(value = "电话")
    @ExcelExport(value = "电话",example = "18888888888")
    private String phone;

    @ApiModelProperty(value = "城市")
    @ExcelExport(value = "城市",example = "上海")
    private String city;

}

 Controller 代码:如果不需要显示example,将最后一个参数改为false

    @GetMapping("/export8")
    @ApiOperationSupport(order = 8)
    @ApiOperation(value = "导出模板(附示例数据)", notes = "导出模板(附示例数据)", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void export8(HttpServletResponse response) {
        ExcelUtils.exportTemplate(response, "用户表", UserExport.class,true);
    }

测试效果:

 6、按对象导出

我们还可以通过 List 对象,对数据直接进行导出。首先,同样需要在对应类的字段上,设置导出名称。

    @GetMapping("/export9")
    @ApiOperationSupport(order = 9)
    @ApiOperation(value = "按对象导出", notes = "按对象导出", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void export9(HttpServletResponse response) {
        //构建数据
        List<UserExport> userList = new ArrayList<>();
        UserExport user1 = new UserExport();
        user1.setName("张三");
        user1.setAge(18);
        user1.setSex(1);
        user1.setCity("成都");
        user1.setPhone("19999999999");

        UserExport user2 = new UserExport();
        user2.setName("李四");
        user2.setAge(25);
        user2.setSex(2);
        user2.setCity("西安");
        user2.setPhone("18888888888");

        userList.add(user1);
        userList.add(user2);

        ExcelUtils.export(response, "用户表", userList,UserExport.class);
    }

 测试效果

 7、按对象导出(数据映射)

在上面 的导出中,我们可以看到,性别数据导出来是1和2,这个不利于用户体验,应该需要转换为对应的中文,我们可以在字段注解上进行对应的配置KV

@Data
@ApiModel(value = "用户导出类")
public class UserExport {

    @ApiModelProperty(value = "姓名")
    @ExcelExport(value = "姓名",example = "张三")
    private String name;

    @ApiModelProperty(value = "年龄")
    @ExcelExport(value = "年龄",example = "20")
    private Integer age;

    @ApiModelProperty(value = "性别")
    @ExcelExport(value = "性别",example = "男",kv="1-男;2-女")
    private Integer sex;

    @ApiModelProperty(value = "电话")
    @ExcelExport(value = "电话",example = "18888888888")
    private String phone;

    @ApiModelProperty(value = "城市")
    @ExcelExport(value = "城市",example = "上海")
    private String city;

}

测试效果:可以看到1和2显示为了对应的男和女

8、按对象导出(调整表头顺序) 

 如果你需要对表头字段进行排序,有两种方式:

第一种:按照表格的顺序,排列Java类中的字段;

第二种:在 @ExcelExport 注解中,指定 sort 属性,其值越少,排名越靠前。 

测试效果:可以看到,此时导出数据的表头顺序,和我们指定的顺序完全一致。

9、多Sheet页导出

Controller 代码:

    @GetMapping("/export10")
    @ApiOperationSupport(order = 10)
    @ApiOperation(value = "多Sheet页导出", notes = "多Sheet页导出", produces = MediaType.APPLICATION_OCTET_STREAM_VALUE)
    public void export10(HttpServletResponse response) {
        // 第 1 个 Sheet 页
        List<List<Object>> sheet1 = new ArrayList<>();
        List<Object> sheet1Head = new ArrayList<>();
        sheet1Head.add("姓名");
        sheet1Head.add("数学");
        sheet1Head.add("英语");
        sheet1.add(sheet1Head);
        List<Object> row1 = new ArrayList<>();
        row1.add("Jack");
        row1.add(85);
        row1.add(100);
        sheet1.add(row1);
        List<Object> row2 = new ArrayList<>();
        row2.add("Marry");
        row2.add(85);
        row2.add(100);
        sheet1.add(row2);
        // 第 2 个 Sheet 页
        List<List<Object>> sheet2 = new ArrayList<>();
        List<Object> sheet2Head = new ArrayList<>();
        sheet2Head.add("姓名");
        sheet2Head.add("音乐");
        sheet2Head.add("美术");
        sheet2.add(sheet2Head);
        List<Object> row01 = new ArrayList<>();
        row01.add("Jack");
        row01.add(77);
        row01.add(66);
        sheet2.add(row01);
        List<Object> row02 = new ArrayList<>();
        row02.add("Marry");
        row02.add(99);
        row02.add(88);
        sheet2.add(row02);
        // 将两个 Sheet 页添加到集合中
        Map<String, List<List<Object>>> sheets = new LinkedHashMap<>();
        sheets.put("文化课", sheet1);
        sheets.put("艺术课", sheet2);
        // 导出数据
        ExcelUtils.exportManySheet(response, "学生成绩表", sheets);
    }

测试效果:

第一个 Sheet 页 

第二个 Sheet 页  

 


疑问解答

关于身份证导入显示科学计数法的问题

首先,当我们把身份证输入到表格中的时候,表格会默认为数字类型,由于数字太大,这时候Excel 会自动显示为科学计数法。

 那么这个时候,我们进行导入的话,确实会显示为科学计数法,如下图所示:

那么,如果要解决这个问题,需要从根源解决掉,即让 Excel 表格不能显示为科学计数法。 

这个时候,我们需要将该列设置为文本格式:

 然后设置为文本格式。

 当设置完成后,你再重新输入身份证后,就会原封不动的显示了。

 这个时候,你再导入就会完全显示了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/313955.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

web学习笔记(十五)

目录 1.Date对象 1.1日期对象的概念 1.2Date()方法的使用 1.3Date()常用方法汇总 1.4例题&#xff1a;用函数编写一个倒计时 2.函数 2.1函数的概念 2.2函数的使用 2.3函数的参数 2.4函数的声明 2.5函数的返回值 2.6异步函数 3特殊函数类型 3.1匿名函数 3.2箭头函数…

[每周一更]-(第82期):认识自然处理语言(NLP)

GPT的大火&#xff0c;带起了行业内大模型的爆发&#xff1b;国内外都开始拥有或者研发自己的大模型&#xff0c;下边我们从NLP来进一步深入了解大模型、AI。 一、什么是NLP&#xff1f; 自然语言处理&#xff08;英语&#xff1a;Natural Language Processing&#xff0c;缩…

【python】进阶--->MySQL数据库(一)

一、mysql数据库 关系型数据库 &#xff1a; 一些相关的表和其他数据库对象的集合。 表是由行和列组成。列包含一组命名的属性(也称为字段)。 行包含一组记录&#xff0c;行和列的交集称为数据项(也叫字段值)。 数据库(database) : 存储数据的仓库&#xff0c;本质上就是一个文…

业务向——基于多多进宝平台的CPS

业务向——基于多多进宝平台的CPS 导读小试牛刀商品活动推广商品详情获取频道推广订单获取及和用户绑定小结 导读 多多进宝是拼多多的开放平台&#xff0c;为广大商家和推广者提供了一个机会&#xff0c;通过推广拼多多的商品来实现收益。多多进宝的CPS&#xff08;按效果付费…

强化学习应用(一):基于Q-learning的无人机物流路径规划研究(提供Python代码)

一、Q-learning简介 Q-learning是一种强化学习算法&#xff0c;用于解决基于马尔可夫决策过程&#xff08;MDP&#xff09;的问题。它通过学习一个价值函数来指导智能体在环境中做出决策&#xff0c;以最大化累积奖励。 Q-learning算法的核心思想是通过不断更新一个称为Q值的…

【MYSQL】MYSQL 的学习教程(十三)之 MySQL的加锁规则

1. MySQL 加锁全局视角 MySQL 分成了 Server 层和存储引擎两部分&#xff0c;每当执行一个查询时&#xff0c;Server 层负责生成执行计划&#xff0c;然后交给存储引擎去执行。其整个过程可以这样描述&#xff1a; Server 层向 Innodb 获取到扫描区间的第 1 条记录Innodb 通过…

SGL-110型定时限过流继电器 额定电流5A 额定电压220V 交直流通用 板前接线

系列型号 LGY-110零序过电压继电器&#xff1b; LGL-110零序过电压继电器&#xff1b; LGL-110/AC零序过电压继电器&#xff1b; LGL-110零序过电流继电器 板前接线 &#xff11; 应用 LGL-110 型零序过电流继电器用作线路和电力设备的零序过电流保护。 LGY-110 型零序过…

WSL2-Ubuntu20.04-配置

WSL2-Ubuntu20.04-配置 安装wsl2安装Ubuntu20.04安装anacondaWSL2可视化&#xff08;VcXsrv&#xff09; 安装wsl2 wsl --install wsl -l -v # 版本查看 默认的都是 wsl2 &#xff08;如果是wsl1 就自行升级 wsl --update&#xff09; 官方教程 安装Ubuntu20.04 安装wsl2之后…

vcruntime140.dll已加载,但找不到入口点的处理方法分享

当遇到错误提示“vcruntime140.dll已加载&#xff0c;但找不到入口点”时&#xff0c;很多人可能会感到困惑&#xff0c;不知道如何去处理这个问题。不过没有必要紧张&#xff0c;在这里我会为大家详细解释 vcruntime140.dll 文件是什么&#xff0c;并指导大家如何高效地解决 v…

kafka除了作为消息队列还能做什么?

Kafka 最初是为大规模处理日志而构建的。它可以保留消息直到过期&#xff0c;并让各个消费者按照自己的节奏提取消息。 与其之前的竞品不同&#xff0c;Kafka 不仅仅是一个消息队列&#xff0c;它还是一个适用于各种情况的开源事件流平台。 让我们回顾一下流行的 Kafka 用例。 …

Win10子系统Ubuntu实战(一)

在 Windows 10 中安装 Ubuntu 子系统&#xff08;Windows Subsystem for Linux&#xff0c;简称 WSL&#xff09;有几个主要的用途和好处&#xff1a;Linux 环境的支持、跨平台开发、命令行工具、测试和验证、教育用途。总体而言&#xff0c;WSL 提供了一种将 Windows 和 Linux…

OpenAI 自带的检索功能好用吗?定量测评带你深度了解!

向量数据库的劲敌来了&#xff1f;又有一批赛道创业公司要倒下&#xff1f; …… 这是 OpenAI 上线 Assistant 检索功能后&#xff0c;技术圈传出的部分声音。原因在于&#xff0c;此功能可以为用户提供基于知识库问答的 RAG&#xff08;检索增强生成&#xff09; 能力。而此前…

Css样式制作图形倒影

该CSS样式是WebKit&#xff08;主要应用于Safari和其他基于WebKit的浏览器&#xff09;的特定前缀属性&#xff0c;用于实现元素内容的反射效果。具体解释如下&#xff1a; -webkit-box-reflect: 定义了一个盒反射效果&#xff0c;仅在支持WebKit的浏览器中生效。 below 15px&a…

springIoc依赖注入循环依赖三级缓存

springIoc的理解&#xff0c;原理和实现 控制反转&#xff1a; 理论思想&#xff0c;原来的对象是由使用者来进行控制&#xff0c;有了spring之后&#xff0c;可以把整个对象交给spring来帮我们进行管理 依赖注入DI&#xff1a; 依赖注入&#xff0c;把对应的属性的值注入到…

矩阵的秩-

一、定义、理解 非零子式的最高阶数。 如何理解&#xff1f;什么叫做非零子式的最高阶数&#xff1f;&#xff1f;&#xff1f; 举个例子&#xff1a;有一个5阶矩阵 首先什么叫子式&#xff1f; 例如2阶子式就是&#xff0c;任取某两行某两列组成的行列式&#xff0c;就叫…

【发票识别】支持pdf、ofd、图片格式的发票

背景 为了能够满足识别各种发票的功能&#xff0c;特地开发了当前发票识别的功能&#xff0c;当前的功能支持pdf、ofd、图片格式的发票识别&#xff0c;使用到的技术包括文本提取匹配、ocr识别和信息提取等相关的技术&#xff0c;用到机器学习和深度学习的相关技术。 体验 体…

论文精读:Vary: Scaling up the Vision Vocabulary for Large Vision-Language Models

Vary: Scaling up the Vision Vocabulary for Large Vision-Language Models Status: Reading Author: Chunrui Han, Haoran Wei, Jianjian Sun, Jinrong Yang, Jinyue Chen, Liang Zhao, Lingyu Kong, Xiangyu Zhang, Zheng Ge Institution: 中国科学院大学, 华中科技大学, 旷…

虚幻UE 材质-材质图层、材质图层混合

学习材质图层和材质图层混合的使用&#xff0c;便于节点扫盲。 文章目录 前言一、材质图层混合二、使用步骤总结 前言 材质混合我们之前用Bridge的插件进行混合过 而此次我们的材质混合使用UE自带的材质图层和材质图层混合来实现 一、材质图层混合 材质图层混合是一种允许将…

Vant4在Vue3.3中如何按需导入组件和样式

前言 最近我在Vue 3.3的项目中对Vant4做按需导入时&#xff0c;尽管按照Vant4的官方指南进行操作&#xff0c;但样式仍然无法正确加载。经过深入研究和多篇文章的比较&#xff0c;我终于找到了在Vue3中如何正确的按需导入Vant 4组件和样式的方法。由于Vue3.3和Vant4相对较新&am…

考古学家 - 华为OD统一考试

OD统一考试 分值: 200分 题解: Java / Python / C++ 题目描述 有一个考古学家发现一个石碑,但是很可惜发现时其已经断成多段。 原地发现N个断口整齐的石碑碎片,为了破解石碑内容,考古学家希望有程序能帮忙计算复原后的石碑文字组合数,你能帮忙吗? 备注: 如果存在石碑…
最新文章