前言

  • 本次提供Excel工具類匯入和匯出的功能,是本人在借鑑網上的部分程式碼的基礎上搭出一個公用化的工具。如有不妥之處,麻煩指明,本人不勝感激。

Excel匯出

實現思路
- 通過Apache提供POI包讀取Excel資訊,首先讀取指定欄位名錶頭,其次從指定行數開始讀取資料,然和對應的欄位名對映到Map,最後返回一個List。在獲取讀取結果後通過呼叫Map對映到實體類工具,將Map中對應的值對映到實體中。這樣子,只要規定好Excel模板和指定好表頭讀取行數和資料讀取開始索引,就可以達到一個公用化的目的了。

實現步驟
- 匯入Maven依賴

  <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-excelant</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-examples</artifactId>
            <version>3.17</version>
        </dependency>
  • 定義上傳Excel格式
    這裡寫圖片描述
  • 接收MultiPartFile型別的資料
  • 呼叫ExcelUtil 工具
ExcelUtils utils = new ExcelUtils();
//欄位名讀取開始索引
utils.setFieldReadIndex(4);
//資料讀取開始索引
utils.setDataReadIndex(6);
List<Map> maps = utils.getExcelInfo(file.getOriginalFilename(), file);
  • 接收到List結果在MapToBeanUtils中對映。
for(Map map :maps){
    //這裡Bean是要用來接收的自定義實體類
    Bean obj = MapToBeanUtils.getModel(map,Bean.class);
    //處理相關業務邏輯    
}

Excel匯出

實現思路
- 藉助自定義註解在匯出實體類的欄位標註該欄位對應的表頭名稱,再通過ExcelUtils工具解析實體類資料和其欄位註解來生成Excel。
實現步驟
- 匯入Maven依賴

  <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-excelant</artifactId>
            <version>3.17</version>
        </dependency>
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-examples</artifactId>
            <version>3.17</version>
        </dependency>
  • 自定義註解
@Target(ElementType.FIELD)
@Documented
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelColumn {

    String title();
}
  • 匯出實體類引用註解形式
@ExcelColumn(title="uuid")
    private String uuid;
  • 引用工具類
//這裡Bean是要用來匯出的自定義實體類
ExcelUtils.export(Bean.class, list, DateUtil.format(new Date(),DateUtil.INT_STRING_FORMAT), response);

原始碼

ExcelUtils.java

package com.demo.util;

import com.andacx.core.log.LogFactory;
import com.andacx.tool.Asserts;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hssf.util.HSSFColor;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellUtil;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.slf4j.Logger;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.lang.reflect.Field;
import java.util.*;

/**
 * <p>@description:【Excel寫入和匯出工具】</p>
 * <p>@author:【Boomer】</p>
 * <p>@date:【2018/1/11 11:18】</p>
 **/
public class ExcelUtils {


    private static final Logger LOGGER = LogFactory.get(ExcelUtils.class);

    /*** 總行數 **/
    private int totalRows = 0;
    /*** 總條數 **/
    private int totalCells = 0;
    /*** 資料讀取開始索引 **/
    private int dataReadIndex = 3;
    /*** 欄位名讀取開始索引 **/
    private int fieldReadIndex = 2;

    public ExcelUtils() {
    }


    public int getTotalRows() {
        return totalRows;
    }

    public int getTotalCells() {
        return totalCells;
    }


    public int getDataReadIndex() {
        return dataReadIndex;
    }

    public void setDataReadIndex(int dataReadIndex) {
        this.dataReadIndex = dataReadIndex;
    }

    public int getFieldReadIndex() {
        return fieldReadIndex;
    }

    public void setFieldReadIndex(int fieldReadIndex) {
        this.fieldReadIndex = fieldReadIndex;
    }

    /**
     * <p>Description:【驗證EXCEL檔案】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/11 14:07】</p>
     * @param filePath
     * @return
     **/
    public boolean validateExcel(String filePath) {
        Asserts.notNull(filePath, "檔名不是excel格式");
        Asserts.isTrue((isExcel2003(filePath) || isExcel2007(filePath)), "檔名不是excel格式");
        return true;
    }

    /**
     * <p>Description:【是否是2003的excel,返回true是2003】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/11 14:07】</p>
     * @param filePath
     * @return
     **/
    public static boolean isExcel2003(String filePath) {
        return filePath.matches("^.+.(xls)$");
    }

    /**
     * <p>Description:【是否是2007的excel,返回true是2007】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/11 14:07】</p>
     * @param filePath
     * @return
     **/
    public static boolean isExcel2007(String filePath) {
        return filePath.matches("^.+.(xlsx)$");
    }


    /**
     * <p>Description:【讀EXCEL檔案,獲取客戶資訊集合】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/11 14:07】</p>
     * @param fileName         
     * @param Mfile 
     * @return
     **/
    public List<Map> getExcelInfo(String fileName, MultipartFile Mfile) {

        //把spring檔案上傳的MultipartFile轉換成CommonsMultipartFile型別
        CommonsMultipartFile cf = (CommonsMultipartFile) Mfile;
        //獲取本地儲存路徑
        File fileFolder = new File(Sysutils.getUploadFilePath());
        if (!fileFolder.exists()) {
            fileFolder.mkdirs();
        }

        //新建一個檔案
        File uploadFile = new File(Sysutils.getUploadFilePath() + System.currentTimeMillis() + ".xlsx");
        //將上傳的檔案寫入新建的檔案中
        try {
            cf.getFileItem().write(uploadFile);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //初始化資訊的集合
        List<Map> list = new ArrayList<>();
        //初始化輸入流
        InputStream is = null;
        try {
            //驗證檔名是否合格
            if (!validateExcel(fileName)) {
                return null;
            }
            //根據新建的檔案例項化輸入流
            is = new FileInputStream(uploadFile);
            //根據excel裡面的內容讀取客戶資訊
            list = getExcelInfo(is, isExcel2003(fileName));
            is.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return list;
    }

    /**
     * <p>Description:【根據excel裡面的內容讀取客戶資訊】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/11 14:07】</p>
     * @param is          輸入流
     * @param isExcel2003 excel是2003還是2007版本
     * @return
     * @throws IOException
     **/
    public List<Map> getExcelInfo(InputStream is, boolean isExcel2003) {
        List<Map> list;
        try {
            /** 根據版本選擇建立Workbook的方式 */
            Workbook wb;
            //當excel是2003時
            if (isExcel2003) {
                wb = new HSSFWorkbook(is);
            } else {//當excel是2007時
                wb = new XSSFWorkbook(is);
            }
            //讀取Excel裡面客戶的資訊
            list = readExcelValue(wb);
        } catch (IOException e) {
            throw new RuntimeException(e.getMessage());
        } catch (NumberFormatException e) {
            throw new RuntimeException("讀取錯誤,請確認Excel是否包含錯誤字元");
        }
        return list;
    }

    /**
     * <p>Description:【讀取Excel裡面客戶的資訊】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/11 14:07】</p>
     * @param wb
     * @return
     **/
    private List<Map> readExcelValue(Workbook wb) {
        //得到第一個shell
        Sheet sheet = wb.getSheetAt(0);

        //得到Excel的行數
        this.totalRows = sheet.getPhysicalNumberOfRows();

        //得到Excel的列數(前提是有行數)
        if (totalRows >= this.dataReadIndex && sheet.getRow(0) != null) {
            this.totalCells = sheet.getRow(0).getPhysicalNumberOfCells();
        }

        List<Map> list = new ArrayList<>();
        Map tempMap;
        //獲取列資料對應的欄位
        Row title = sheet.getRow(this.fieldReadIndex);
        List<String> header = new ArrayList<>();
        //迴圈Excel的列
        for (int c = 0; c < this.totalCells; c++) {
            Cell cell = title.getCell(c);
            header.add(cell.getStringCellValue());
        }

        //迴圈Excel行數,從第二行開始。標題不入庫
        for (int r = this.dataReadIndex; r < totalRows; r++) {
            Row row = sheet.getRow(r);
            if (row == null) {
                continue;
            }
            tempMap = new HashMap(this.totalCells);

            //迴圈Excel的列
            for (int c = 0; c < this.totalCells; c++) {
                Cell cell = row.getCell(c);
                if (null != cell) {
                    cell.setCellType(CellType.STRING);
                    tempMap.put(header.get(c), cell.getStringCellValue());
                }
            }
            //新增客戶
            list.add(tempMap);
        }
        return list;
    }

    /**
     * <p>Description:【匯出到file】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/11 14:07】</p>
     * @param clz
     * @param data
     * @param filePath
     * @param <T>
     * @throws IOException
     **/
    public static <T> void export(Class<T> clz, List<? extends T> data, String filePath) throws IOException {
        FileOutputStream out = new FileOutputStream(filePath);
        getBook(clz, data).write(out);
        out.close();
    }

    /**
     * <p>Description:【匯出到httpServletResponse】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/11 14:07】</p>
     * @param clz
     * @param data
     * @param fileName
     * @param response
     * @param <T>
     **/
    public static <T> void export(Class<T> clz, List<? extends T> data, String fileName, HttpServletResponse response) {
        try {
            response.setContentType("application/vnd.ms-excel");
            response.setHeader("Content-disposition", "attachment;filename=\"" + new String(fileName.getBytes("gb2312"), "ISO8859-1") + ".xlsx" + "\"");
            getBook(clz, data).write(response.getOutputStream());
        } catch (Exception e) {
            throw new RuntimeException("系統異常");
        } finally {
            try {
                response.getOutputStream().close();
            } catch (IOException e) {
                LOGGER.error("流關閉異常:" + e.getMessage());
            }
        }
    }

    /**
     * <p>Description:【生成Excel】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/11 14:07】</p>
     * @param clz
     * @param data
     * @return
     **/
    private static <T> SXSSFWorkbook getBook(Class<T> clz, List<? extends T> data) {
        if (null == clz || null == data || data.size() <= 0) {
            throw new RuntimeException("引數異常");
        }
        XSSFWorkbook workbook = new XSSFWorkbook();
        SXSSFWorkbook sxssfWorkbook = new SXSSFWorkbook(workbook, 100);
        //第一個工作本
        SXSSFSheet sheet = sxssfWorkbook.createSheet();
        sheet.trackAllColumnsForAutoSizing();
        //表頭
        Row header = sheet.createRow(0);
        //讀取類欄位、註解資訊
        Field[] fields = clz.getDeclaredFields();
        //寫入表頭
        int invalidFieldNum = 0;
        for (Field field : fields) {
            ExcelColumn excelColumn = field.getAnnotation(ExcelColumn.class);
            if (excelColumn == null) {
                continue;
            }
            Cell headerCell = header.createCell(invalidFieldNum++);
            headerCell.setCellStyle(headerCellStyle(sxssfWorkbook));
            headerCell.setCellValue(excelColumn.title());
        }
        //寫入資料
        for (int i = 0; i < data.size(); i++) {
            //第0行為表頭
            Row row = sheet.createRow(i + 1);
            //迴圈讀取欄位
            int cellIndex = 0;
            for (int j = 0; j < fields.length; j++) {
                Field field = fields[j];
                field.setAccessible(true);
                Object cellValue;
                ExcelColumn excelColumn = field.getAnnotation(ExcelColumn.class);
                if (null == excelColumn) {
                    continue;
                }
                Object o;
                try {
                    o = field.get(data.get(i));
                    cellValue = o;
                } catch (Exception e) {
                    throw new RuntimeException("系統異常");
                }

                CellUtil.createCell(row, cellIndex++, String.valueOf(cellValue));
            }
        }
        //自適應寬度
        for (int i = 0; i < invalidFieldNum; i++) {
            sheet.autoSizeColumn(i);
        }
        return sxssfWorkbook;
    }

    /**
     * <p>Description:【設定Excel表頭樣式】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/11 14:07】</p>
     * @param wb
     * @return
     **/
    public static CellStyle headerCellStyle(Workbook wb) {

        CellStyle style = wb.createCellStyle();
        Font font = wb.createFont();
        font.setFontName("宋體");
        //設定字型大小
        font.setFontHeightInPoints((short) 12);
        //加粗
        font.setBold(true);
        // 設定背景色
        style.setFillForegroundColor(HSSFColor.HSSFColorPredefined.SKY_BLUE.getIndex());
        style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
        //讓單元格居中
        style.setAlignment(HorizontalAlignment.CENTER_SELECTION);
        // 左右居中
        style.setAlignment(HorizontalAlignment.CENTER);
        // 上下居中
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        //設定自動換行
        style.setWrapText(true);
        style.setFont(font);
        return style;
    }

}

map轉實體工具類

package com.andacx.lbs.util;

import com.andacx.tool.StringUtil;
import org.apache.commons.collections.MapUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by Boomer on 2017/5/19.
 * @author Boomer
 */
public class MapToBeanUtils {

    /**
     * 獲取類型別的所有Field包括父類中的Field
     *
     * @param clazz 類型別
     * @return 返回類型別的所有Field包括父類中的Field
     */
    public static Field[] getAllFields(Class clazz) {
        Map<String, Field> map = new HashMap<String, Field>();
        for (Field field : clazz.getDeclaredFields()) {

            Class fieldType = field.getType();
            String fieldName = field.getName();
            if(isBaseType(fieldType)){
                try {
                    map.put(fieldName, field);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }

        while (clazz.getSuperclass() != null) {
            clazz = clazz.getSuperclass();
            if (clazz == Object.class) {
                break;
            }

            for (Field field : clazz.getDeclaredFields()) {
                if (!map.containsKey(field.getName())) {
                    map.put(field.getName(), field);
                }
            }
        }
        return map.values().toArray(new Field[map.size()]);
    }

    /**
     * <p>Description:【判斷是否是基本型別】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/8 16:13】</p>
     * @param fieldType
     * @return
     **/
    public static Boolean isBaseType(Class fieldType){

        if (fieldType == String.class) {
            return true;
        } else if (fieldType == double.class || fieldType == Double.class) {
            return true;
        } else if (fieldType == int.class || fieldType == Integer.class) {
            return true;
        } else if (fieldType == Boolean.class || fieldType == boolean.class) {
            return true;
        } else if (fieldType == Date.class) {
            return true;
        }

        return false;
    }

    /**
     * <p>Description:【將首字母轉換為大寫】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/5 15:54】</p>
     *
     * @param a
     * @return
     **/
    public static String firstCharToUpper(String a) {

        return (new StringBuilder()).insert(0, a.substring(0, 1).toUpperCase()).append(a.substring(1)).toString();
    }

    /**
     * <p>Description:【將map隱射到實體類】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/5 16:27】</p>
     *
     * @param map
     * @param model
     * @return
     **/
    public static <T> T getModel(Map map, Class<T> model) {

        Field[] fields = getAllFields(model);
        String value;
        Method method;
        T obj;
        try {
            obj = model.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        for (Field field : fields) {
            int modifiers = field.getModifiers();
            if (Modifier.isStatic(modifiers) || Modifier.isFinal(modifiers)) {
                continue;
            }

            //根據傳入的獲取str來判斷用哪個字串獲取引數
            value = MapUtils.getString(map, field.getName());
            if (StringUtil.isEmpty(value)) {
                if (field.getType() == String.class) {
                    try {
                        method = model.getMethod("get" + firstCharToUpper(field.getName()));
                        Object invokeValue = method.invoke(obj);
                        if (invokeValue != null) {
                            value = invokeValue.toString();
                        }
                    } catch (Exception e) {
                        if (field.getType() == Boolean.class || field.getType() == boolean.class) {
                            try {
                                String fieldName = field.getName();
                                method = model.getMethod("get" + firstCharToUpper(fieldName.startsWith("is") ? fieldName.replace("is", "") : fieldName));
                                Object invokeValue = method.invoke(obj);
                                if (invokeValue != null) {
                                    value = invokeValue.toString();
                                }
                            } catch (Exception e1) {
                                System.out.println(e1.getMessage());
                            }
                        } else {
                            System.out.println(e.getMessage());
                        }
                    }
                }
            }

            if (StringUtil.isNotEmpty(value)) {
                invokeMethod(model, obj, value, field.getName(), field.getType());
            }
        }

        return obj;
    }

    /**
     * <p>Description:【呼叫方法】</p>
     * <p>Author:【Boomer】</p>
     * <p>Date:【2018/1/5 16:26】</p>
     *
     * @param modelClass
     * @param object
     * @param value
     * @param fieldName
     * @param fieldType
     * @return
     **/
    private static <T> void invokeMethod(Class<T> modelClass, T object, String value, String fieldName, Class fieldType) {

        Method method = null;
        try {
            method = modelClass.getMethod("set" + firstCharToUpper(fieldName), fieldType);
        } catch (NoSuchMethodException e) {
            if (fieldType == Boolean.class || fieldType == boolean.class) {
                try {
                    method = modelClass.getMethod("set" + firstCharToUpper(fieldName.startsWith("is") ? fieldName.replace("is", "") : fieldName), fieldType);
                } catch (NoSuchMethodException e1) {
                }
            } else {
            }
        }
        try {
            if (null != method) {
                if (fieldType == String.class) {
                    method.invoke(object, value);
                } else if (fieldType == double.class || fieldType == Double.class) {
                    try {
                        method.invoke(object, Double.parseDouble(value));
                    } catch (NumberFormatException e) {
                        e.printStackTrace();
                    }
                } else if (fieldType == int.class || fieldType == Integer.class) {
                    try {
                        method.invoke(object, Integer.parseInt(value));
                    } catch (NumberFormatException e) {
                        e.printStackTrace();
                    }
                } else if (fieldType == Boolean.class || fieldType == boolean.class) {
                    try {
                        method.invoke(object, Boolean.parseBoolean(value));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                } else if (fieldType == Date.class) {
                    try {
                        SimpleDateFormat sdf = (value.length() == 10 ? new SimpleDateFormat("yyyy-MM-dd") : new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
                        method.invoke(object, sdf.parse(value));
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
  • 20180122
    這個工具也可以用以下方法實現
public static <T> List<T> mapToBean(List<Map> maps, Class<T> obj) throws IllegalAccessException, InstantiationException {
        List<T> list = new ArrayList<>();
        for (Map<String, Object> map : maps) {
            T module = obj.newInstance();
            BeanWrapper wapper = new BeanWrapperImpl(module);
            for (Map.Entry entry : map.entrySet()) {
                if (wapper.isWritableProperty(entry.getKey().toString())) {
                    wapper.setPropertyValue(entry.getKey().toString(), entry.getValue().toString());
                }
            }
            list.add(module);
        }
        return list;
    }

後語

  • 原始碼中可能摻雜一些個人工具類原始碼,各位可自行更改,不影響功能實現。如果問題請留言。