一個通用的Excel導入導出功能

前言

Excel表單導入,是將Excel文件中的數據導入到數據庫;而Excel表單導出,是將數據庫的數據導出到Excel文件中。
在本文中開發的功能是基於SpringBoot + JPA框架進行開發,而在系統中,數據庫表結構又分成了兩種情況,一種是單表結構,另一種是存在@OneToMany註解的主外鍵關聯的主輔表結構,所以本次開發目的是可以同時兼容兩種情況。
另外,因爲不同的數據表對應的Excel表單的數據和格式都不同,但是爲每一個數據表定製化開發Excel導入導出,工作重複度高,所以本次開發另一個目的是能夠做到所有數據表通用Excel表單導入導出

起步

SpringBoot和JPA的環境請讀者自行準備,在這裏引入poi依賴來協助開發,版本選擇4.0.1

<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi</artifactId>
    <version>${poi.version}</version>
</dependency>
 
<dependency>
    <groupId>org.apache.poi</groupId>
    <artifactId>poi-ooxml</artifactId>
    <version>${poi.version}</version>
</dependency>

開始

設計思路
在這裏插入圖片描述

功能設計

導入和導出功能都能通過傳入的服務參數,完成對指定表結構的導入或導出。
如:http://127.0.0.1/excel/{SERVER_NAME}/import (導入)
       http://127.0.0.1/excel/{SERVER_NAME}/export (導出)

一、Excel導出

1、導出目標

可以導出兩種形式的Excel文件:
(1)數據模版,用於數據填入後進行Excel導入功能,命名規則爲:Excel-‘name’-Template.xlsx(如:Excel-Project-Template.xlsx);
(2)指定時間區間的數據,命名規則爲:Excel-‘name’.xlsx(如:Excel-Project.xlsx)。

2、導出格式
將數據表的字段以字段名+‘-’的格式(如:id-),依次插入到表單的首行單元格。如果導出的是指定區間的數據,則會在首行行尾追加’createTime’和‘createBy’兩個字段,分別表示數據創建時間和數據創建人(關聯關係表結構中,只在主表中追加),再將數據逐行插入。
*:表頭字段的設計(如:id-),是爲了方便用戶在填入自己需要導入數據的過程中,可以在表頭字段後加上自己的註解(如:id-編號),並且不影響Excel表單的數據導入,數據填入過程中,請保留’-'符號

3、導出結構
單表結構:在Entity字段定義中,不存在@OneToMany註解,在該結構下,只有一張Excel工作簿。
關聯關係表結構:在Entity字段定義中,存在@OneToMany註解,在該結構下,需要新建Excel工作薄,再將字段或數據填入新的工作簿中。

二、Excel導入
1、導入規則
將數據表的字段以字段名+‘-’的格式(如:id-),依次插入到表單的首行單元格,再將數據逐行填入字段對應的同列單元格即可。表頭字段名後可以加上自己的註解(如:id-編號),不影響表單的正常導入。

*:爲避免用戶由於Excel表單格式的問題,數據導入失敗,請用戶先使用Excel導出功能,導出一份表單模板,再將數據填入模板後進行導入操作,保證數據可正常導入。

*:若導入的數據爲關聯關係表結構,則輔表工作簿中的關聯字段需要和主表工作薄中的主鍵保持一致

2、導入操作
根據@Id註解和@GeneratedValue註解判斷主鍵的類型,對主鍵做特殊處理,若是自增主鍵,會將主鍵置爲空,保證數據只做插入操作,不做更新操作;而若是非自增主鍵,不做處理,數據可以進行插入操作,或者對數據庫已經存在該主鍵的數據進行更新操作

3、導入反饋
在數據轉化後,會將數據逐條存入數據庫,而在這個過程中,可能會存在數據的錯誤或者別的問題導致部分數據的存儲失敗,但是部分數據的存儲失敗不會導致整個Excel表單數據存儲的失敗回滾,而是將存儲失敗的數據對應的Excel表單單元行和失敗原因反饋給用戶,如:導入數據總數,成功導入數據總數,失敗導入數據總數以及失敗導入原因

服務設計

  • 內部服務設計
    在這裏插入圖片描述

(1)RIA報表服務抽象接口

/**
 * RIA報表服務抽象接口
 * @author xinyao.zhang
 */
public interface BaseRiaExcelService {
    /**
     * 導入Excel文件數據
     * @param file 文件
     * @return JSONObject
     */
    default JSONObject importExcelData(MultipartFile file){return null;}

    /**
     * Excel文件 轉List數據
     * @param file
     * @return
     */
    default List excelDataConvert(MultipartFile file){return null;}

    /**
     * 導出Excel文件數據
     * @param flag 0:導出Excel格式 1:導出指定時間區間數據
     * @param beginTime 開始時間
     * @param endTime   結束時間
     * @param response  返回數據
     */
    default void exportExcelData(Integer flag, Long beginTime, Long endTime, HttpServletResponse response){}

    /**
     * 數據構建Excel文件
     * @param flag
     * @param beginTime
     * @param endTime
     */
    default XSSFWorkbook dataToExcel(Integer flag, Long beginTime, Long endTime){return null;}

}

(2)RIA基礎服務類

/**
 * RIA基礎服務類
 * @author xinyao.zhang
 */
public interface BaseRiaService extends BaseService,BaseRiaExcelService{
}

(3)基礎服務接口

/**
 * 基礎服務接口
 * @author xinyao.zhang
 */
public interface BaseService<T, ID extends Serializable> {

    /**
     * 查找單個實例
     * @param id 實例ID
     * @return T
     */
    T find(ID id);
}

(4)基礎服務邏輯層

/**
 * 基礎服務邏輯層
 * @author xinyao.zhang
 */
public class BaseServiceImpl<T, ID extends Serializable> implements BaseService<T, ID> {

    private final Logger logger = LoggerFactory.getLogger(getClass());

    protected Class<T> entityClass = (Class<T>) GenericClassUtils.getSuperClassGenericType(getClass(), 0);

    protected Class<ID> idClass = (Class<ID>) GenericClassUtils.getSuperClassGenericType(getClass(), 1);

    protected BaseJpaRepository<T, ID> baseJpaRepository;

    public BaseServiceImpl(BaseJpaRepository baseJpaRepository){
        this.baseJpaRepository = baseJpaRepository;
    }

    @Override
    public T find(ID id){
        T optional = null;
        //ID類型轉換
        if(idClass.isInstance(id)){
            optional = baseJpaRepository.findOne(idClass.cast(id));
        }else{
            switch (idClass.getName()) {
                case "java.lang.String":
                    optional = baseJpaRepository.findOne(idClass.cast(String.valueOf(id)));
                    break;
                case "java.lang.Integer":
                    optional = baseJpaRepository.findOne(idClass.cast(Integer.valueOf(id.toString())));
                    break;
                case "java.lang.Long":
                    optional = baseJpaRepository.findOne(idClass.cast(Long.valueOf(id.toString())));
                    break;
                default:
                    break;
            }
        }
        if(null != optional){
            return optional;
        }
        return null;
    }
}

(5)泛型工具類

/**
 * 泛型工具類
 * @author xinyao.zhang
 */
@SuppressWarnings("rawtypes")
public class GenericClassUtils {

    public static Class getSuperClassGenericType(Class clazz, int index) {
        Type genType = clazz.getGenericSuperclass();
        if (!(genType instanceof ParameterizedType)) {
            return null;
        }

        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
        if (index >= params.length || index < 0) {
            return null;
        }

        if (!(params[index] instanceof Class)) {
            return null;
        }
        return (Class) params[index];
    }
}

(6)RIA基礎實現基類

/**
 * RIA基礎實現基類
 * @author xinyao.zhang
 * @param <T>
 * @param <ID>
 */
@SuppressWarnings("unchecked")
public abstract class BaseRiaServiceImpl<T, ID> extends BaseServiceImpl implements BaseRiaService {

    private static final Logger logger = LoggerFactory.getLogger(BaseRiaServiceImpl.class);

    private BaseRiaExcel<T,ID> tBaseRiaExcel;

    public BaseRiaServiceImpl(BaseJpaRepository baseJpaRepository) {
        super(baseJpaRepository);
        tBaseRiaExcel = new BaseRiaExcel(entityClass,idClass,baseJpaRepository);
    }
    
    protected BaseRiaExcel baseRiaExcel() {
        return tBaseRiaExcel;
    }
    
    @Override
    public JSONObject importExcelData(MultipartFile file) {
        return tBaseRiaExcel.importExcelData(excelDataConvert(file));
    }

    @Override
    public List excelDataConvert(MultipartFile file) {
        return tBaseRiaExcel.excelDataConvert(file);
    }

    @Override
    public void exportExcelData(Integer flag, Long beginTime, Long endTime, HttpServletResponse response) {
        tBaseRiaExcel.exportExcelData(dataToExcel(flag, beginTime, endTime), flag, response);
    }

    @Override
    public XSSFWorkbook dataToExcel(Integer flag, Long beginTime, Long endTime) {
        return tBaseRiaExcel.dataToExcel(flag, beginTime, endTime);
    }
}

(7)RIA報表服務類

/**
 * RIA報表服務類
 * @author xinyao.zhang
 * @param <T>
 * @param <ID>
 */
public class BaseRiaExcel<T,ID> extends BaseServiceImpl {

    private static final Logger logger = LoggerFactory.getLogger(BaseRiaExcel.class);

    private Class<T> entityClass;

    public BaseRiaExcel(Class<T> entityClass,Class<ID> idClass,BaseJpaRepository baseJpaRepository){
        super(baseJpaRepository);
        super.entityClass = entityClass;
        super.idClass = idClass;
        this.entityClass = entityClass;
    }

    /**
     * List數據導入數據庫
     * @param list
     * @return
     */
    public JSONObject importExcelData(List<T> list) {
        JSONObject result = new JSONObject();
        try{
            JSONArray error = null;
            int successCount = 0;
            int failCount = 0;

            for(int index = 0; index < list.size(); index++) {
                int rowNum = index + 2;

                //保存數據
                try{
                    T entity = list.get(index);

                    boolean isUpdate = false;
                    Field[] fields = entityClass.getDeclaredFields();
                    for(Field f: fields){

                        //判斷id主鍵是否置空
                        Id id = f.getAnnotation(Id.class);
                        if(null != id){
                            f.setAccessible(true);
                            Object idObject = f.get(entity);
                            if(null != idObject){
                                String idValue = String.valueOf(idObject);
                                T oldEntity = (T)find(idValue);

                                //id主鍵沒有置空且存在舊數據,進行更新操作
                                if(null != oldEntity){
                                    BeanCopyUtils.copyPropertiesIgnoreNull(oldEntity, entity);
                                    baseJpaRepository.saveAndFlush(oldEntity);
                                    isUpdate = true;
                                }
                            }
                            break;
                        }
                    }
                    if(!isUpdate){
                        baseJpaRepository.saveAndFlush(entity);
                    }

                    logger.info("單元行:{},數據保存成功", rowNum);
                    successCount++;
                }catch (Exception e){
                    if(null == error){
                        error = new JSONArray();
                    }
                    logger.error("單元行:{},數據保存失敗", rowNum, e);
                    error.add("單元行:" + rowNum + ",數據保存失敗," + e.getMessage());
                    failCount++;
                }
            }
            result.put("rowsCount", list.size());
            result.put("successCount", successCount);
            result.put("failCount", failCount);

            if(null != error){
                result.put("error", error);
            }

        } catch (Exception e){
            throw new BusinessException("Excel數據導入失敗");
        }
        return result;
    }


    /**
     * Excel文件數據轉List
     * @param file
     * @return
     */
    public List<T> excelDataConvert(MultipartFile file){
        List<T> list;
        try{
            XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file.getInputStream());

            int sheetNumber = xssfWorkbook.getNumberOfSheets();

            if(sheetNumber > 1){
                list = multipleSheetDataConvert(xssfWorkbook);
            }else {
                list = singleSheetDataConvert(xssfWorkbook);
            }

        } catch (Exception e){
            throw new BusinessException("Excel數據導入失敗");
        }
        return list;
    }

    /**
     * 單工作薄數據處理
     * @param xssfWorkbook
     * @return
     */
    private List<T> singleSheetDataConvert(XSSFWorkbook xssfWorkbook){
        List<T> list = new ArrayList<>();
        //默認:第一頁工作薄
        XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0);
        //首行頭信息獲取
        XSSFRow firstRow = xssfSheet.getRow(0);

        for(int rowNum = 1; rowNum <= xssfSheet.getLastRowNum(); rowNum++) {

            //行數據獲取
            XSSFRow dataRow = xssfSheet.getRow(rowNum);
            try{
                T entity = convertToEntity(firstRow, dataRow);
                list.add(entity);
            }catch (Exception e){
                logger.error("Excel單元行:{},轉化Bean失敗", rowNum + 1, e);
            }
        }

        return list;
    }

    /**
     * 數據行轉化Bean
     * @param firstRow 首行頭信息
     * @param dateRow 行數據
     * @return
     */
    private T convertToEntity(XSSFRow firstRow, XSSFRow dateRow){
        T entity;
        try{
            JSONObject jsonObject = new JSONObject();
            for(int cellNum = 0; cellNum < dateRow.getLastCellNum(); cellNum++){
                //獲取單元格數據
                XSSFCell cell = dateRow.getCell(cellNum);
                String cellValue = getCellValue(cell);
                if(null == cellValue){
                    continue;
                }

                //獲取頭信息屬性
                XSSFCell headCell = firstRow.getCell(cellNum);
                String headCellValue = getCellValue(headCell);
                String field = headCellValue.split("-")[0];

                jsonObject.put(field, cellValue);

                //去除自增主鍵
                Id id = entityClass.getDeclaredField(field).getAnnotation(Id.class);
                if(null != id){
                    //自增主鍵判斷
                    GeneratedValue generatedValue = entityClass.getDeclaredField(field).getAnnotation(GeneratedValue.class);
                    if(null != generatedValue && GenerationType.SEQUENCE.equals(generatedValue.strategy())){
                        jsonObject.remove(field);
                    }
                }
            }

            entity = JSONObject.parseObject(jsonObject.toJSONString(), entityClass);
            return entity;
        }catch (Exception e){
            logger.error("Excel單元行轉化Bean失敗,{}", dateRow, e);
            return null;
        }
    }

    /**
     * 多工作薄,存在關聯數據
     * 轉化爲主表Bean List數據
     * @param xssfWorkbook
     * @return
     */
    private List<T> multipleSheetDataConvert(XSSFWorkbook xssfWorkbook){
        try{
            List<T> list = new ArrayList<>();

            Field[] fields = entityClass.getDeclaredFields();
            Field idField = null;

            //存儲關聯數據
            Map<String, Map> map = new HashMap<>();
            //存儲關聯表的關聯字段
            Map<String, String> columnMap = new HashMap<>();
            //存儲關聯表的類
            Map<String, Class> classMap = new HashMap<>();
            //存儲關聯表在主表中的屬性
            Map<String, Field> fieldMap = new HashMap<>();
            //存儲關聯表中的自增id
            Map<String, String> idMap = new HashMap<>();

            //遍歷主表屬性
            for(Field field: fields){
                Id id = field.getAnnotation(Id.class);
                if(null != id){
                    idField = field;
                    continue;
                }

                //判斷是否一對多映射
                OneToMany oneToMany = field.getAnnotation(OneToMany.class);
                if(null != oneToMany){
                    JoinColumn joinColumn = field.getAnnotation(JoinColumn.class);
                    if(null != joinColumn){
                        String fieldName = underlineToCamel(joinColumn.name());
                        Class clazz = oneToMany.targetEntity();
                        String entityName = clazz.getSimpleName();

                        //實例化map存儲關聯數據
                        Map<String, Collection> entityMap = new HashMap<>();
                        map.put(entityName, entityMap);

                        columnMap.put(entityName, fieldName);
                        classMap.put(entityName, clazz);
                        fieldMap.put(entityName, field);

                        Field[] entityFields = clazz.getDeclaredFields();
                        for(Field f: entityFields){
                            Id entityId = f.getAnnotation(Id.class);
                            if(null != entityId){
                                //自增主鍵判斷
                                GeneratedValue generatedValue = f.getAnnotation(GeneratedValue.class);
                                if(null != generatedValue && GenerationType.SEQUENCE.equals(generatedValue.strategy())){
                                    idMap.put(entityName, f.getName());
                                }
                                break;
                            }
                        }
                    }
                }
            }

            if(null == idField){
                logger.error("Bean沒有Id註解屬性");
                throw new BusinessException("Bean沒有Id註解屬性");
            }

            //遍歷工作簿
            for(int sheetNum = 0; sheetNum < xssfWorkbook.getNumberOfSheets(); sheetNum++){
                XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(sheetNum);
                String sheetName = xssfSheet.getSheetName();

                //首行頭信息獲取
                XSSFRow firstRow = xssfSheet.getRow(0);
                //行數據獲取
                for(int rowNum = 1; rowNum <= xssfSheet.getLastRowNum(); rowNum++){
                    XSSFRow xssfRow = xssfSheet.getRow(rowNum);
                    JSONObject jsonObject = new JSONObject();

                    String relationId = null;
                    //單元格數據獲取
                    for(int cellNum = 0; cellNum < xssfRow.getLastCellNum(); cellNum++){
                        //獲取單元格數據
                        XSSFCell cell = xssfRow.getCell(cellNum);
                        String cellValue = getCellValue(cell);
                        if(null == cellValue){
                            continue;
                        }

                        //獲取頭信息屬性
                        XSSFCell headCell = firstRow.getCell(cellNum);
                        String headCellValue = getCellValue(headCell);
                        String field = headCellValue.split("-")[0];

                        jsonObject.put(field, cellValue);

                        //判斷是否是關聯表中關聯字段
                        if(columnMap.containsKey(sheetName)){
                            String column = columnMap.get(sheetName);
                            if(field.equals(column)){
                                relationId = cellValue;
                            }
                        }
                    }

                    try{
                        //關聯數據處理
                        if(map.containsKey(sheetName) && classMap.containsKey(sheetName) && null != relationId){
                            Map<String, Collection> entityMap = map.get(sheetName);

                            if(null == entityMap.get(relationId)){
                                Field columnField = fieldMap.get(sheetName);
                                Collection collection = getFieldCollection(columnField);
                                entityMap.put(relationId, collection);
                            }

                            //去除自增主鍵
                            if(idMap.containsKey(sheetName)){
                                String id = idMap.get(sheetName);
                                jsonObject.put(id, null);
                            }

                            //去除關聯字段
                            String column = columnMap.get(sheetName);
                            jsonObject.put(column, null);

                            entityMap.get(relationId).add(JSONObject.parseObject(jsonObject.toJSONString(), classMap.get(sheetName)));

                        }else {
                            T entity = JSONObject.parseObject(jsonObject.toJSONString(), entityClass);
                            list.add(entity);
                        }
                    }catch (Exception e){
                        logger.error("Excel單元行:{},轉化Bean失敗", rowNum + 1, e);
                    }

                }
            }


            boolean idIsSequence = false;
            GeneratedValue generatedValue = idField.getAnnotation(GeneratedValue.class);
            if(null != generatedValue && GenerationType.SEQUENCE.equals(generatedValue.strategy())){
                idIsSequence = true;
            }
            //設置關聯數據
            for(int index = 0; index < list.size(); index++){
                int num = index + 2;
                try{
                    T t = list.get(index);
                    idField.setAccessible(true);
                    String id = String.valueOf(idField.get(t));
                    //遍歷關聯屬性
                    for(Map.Entry<String, Field> entry: fieldMap.entrySet()){
                        String entityName = entry.getKey();
                        Field field = entry.getValue();

                        Map<String, Collection> entityMap = map.get(entityName);
                        Collection entity = entityMap.get(id);

                        field.setAccessible(true);
                        field.set(t, entity);
                    }
                    //去除自增主鍵
                    if(idIsSequence){
                        idField.set(t, null);
                    }
                }catch (Exception e){
                    logger.error("Excel單元行:{},轉化Bean失敗", num, e);
                }
            }
            return list;
        }catch (Exception e){
            logger.error("Excel數據行轉化Bean失敗", e);
            throw new BusinessException("Excel數據行轉化Bean失敗");
        }
    }

    /**
     * 下劃線格式字符串轉換爲駝峯格式字符串
     * @param param
     * @return
     */
    private static String underlineToCamel(String param) {
        if (param == null || "".equals(param.trim())) {
            return "";
        }
        int len = param.length();
        StringBuilder sb = new StringBuilder(len);
        for (int i = 0; i < len; i++) {
            char c = param.charAt(i);
            if (c == '_') {
                if (++i < len) {
                    sb.append(Character.toUpperCase(param.charAt(i)));
                }
            } else {
                sb.append(c);
            }
        }
        return sb.toString();
    }

    /**
     * 獲取關聯屬性的存儲集合
     * @param field
     * @return
     */
    private Collection getFieldCollection(Field field){
        if(null == field){
            return null;
        }
        Collection collection = null;
        Class classType = field.getType();
        if(classType.isAssignableFrom(List.class)){
            collection = new ArrayList();
        } else if(classType.isAssignableFrom(Set.class)){
            collection = new ArrayList();
        }
        return collection;
    }

    /**
     * 獲取單元格數據
     * @param cell
     * @return
     */
    private String getCellValue(XSSFCell cell){
        if(null == cell){
            return null;
        }
        String result = null;
        switch (cell.getCellType()){
            case NUMERIC:
                if(DateUtil.isCellDateFormatted(cell)){
                    Date createDate = cell.getDateCellValue();
                    result = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(createDate);
                }else {
                    cell.setCellType(CellType.STRING);
                    result = cell.getStringCellValue();
                }
                break;
            case FORMULA:
                cell.setCellType(CellType.STRING);
                result = cell.getStringCellValue();
                break;
            case STRING:
                cell.setCellType(CellType.STRING);
                result = cell.getStringCellValue();
                break;
            case BOOLEAN:
                cell.setCellType(CellType.BOOLEAN);
                result = String.valueOf(cell.getBooleanCellValue());
                break;
            case _NONE:
                break;
            case BLANK:
                break;
            case ERROR:
                break;
            default:
                break;
        }
        return result;
    }

    /**
     * 導出Excel文件
     * @param xssfWorkbook
     * @param flag
     * @param response
     */
    public void exportExcelData(XSSFWorkbook xssfWorkbook, Integer flag, HttpServletResponse response) {
        if(null == xssfWorkbook){
            return;
        }
        OutputStream out = null;
        try{
            String name = entityClass.getSimpleName();

            if(flag.equals(0)){
                name += "-Template";
            }

            //設置response信息
            String title = "Excel-" + name + ".xlsx";
            String headStr = "attachment; filename=\"" + new String(title.getBytes("gb2312"), "UTF-8" ) + "\"";
            response.setContentType("octets/stream");
            response.setContentType("APPLICATION/OCTET-STREAM");
            response.setHeader("Content-Disposition", headStr);

            //導出Excel
            out = response.getOutputStream();
            xssfWorkbook.write(out);
        }catch (Exception e){
            logger.error("Excel文件導出失敗", e);
        }finally {
            if(null != out){
                try {
                    out.close();
                }catch (Exception e){
                    logger.error("OutputStream流關閉失敗", e);
                }
            }
        }
    }

    /**
     * 數據構建Excel文件
     * @param flag
     * @param beginTime
     * @param endTime
     * @return
     */
    public XSSFWorkbook dataToExcel(Integer flag, Long beginTime, Long endTime){
        if(!flag.equals(0) && !flag.equals(1)){
            return null;
        }
        String name = entityClass.getSimpleName();
        Field[] fields = entityClass.getDeclaredFields();

        //構建Excel
        XSSFWorkbook xssfWorkbook = new XSSFWorkbook();
        XSSFSheet xssfSheet = xssfWorkbook.createSheet(name);
        xssfSheet.setDefaultColumnWidth(20);

        //設置單元格樣式
        XSSFCellStyle style = xssfWorkbook.createCellStyle();
        style.setAlignment(HorizontalAlignment.CENTER);
        XSSFFont font = xssfWorkbook.createFont();
        font.setBold(true);
        font.setFontHeight(12);
        style.setFont(font);

        Map<String, String> entityNamesMap = new HashMap<>();
        Map<String, Field[]> entityFieldsMap = new HashMap<>();
        //首行插入字段
        XSSFRow firstRow = xssfSheet.createRow(0);
        for(int i = 0; i < fields.length; i++){
            Field field = fields[i];

            //判斷是否一對多映射
            OneToMany oneToMany = field.getAnnotation(OneToMany.class);
            if(null != oneToMany){
                Class clazz = oneToMany.targetEntity();

                //新建工作簿
                String entityName = clazz.getSimpleName();
                XSSFSheet entitySheet = xssfWorkbook.createSheet(entityName);
                entitySheet.setDefaultColumnWidth(20);

                Field[] entityFields = clazz.getDeclaredFields();
                entityFieldsMap.put(field.getName(), entityFields);
                entityNamesMap.put(field.getName(), entityName);

                //首行插入字段
                XSSFRow entityFirstRow = entitySheet.createRow(0);
                for(int cellNum = 0; cellNum < entityFields.length; cellNum++){
                    XSSFCell cell = entityFirstRow.createCell(cellNum);
                    cell.setCellValue(entityFields[cellNum].getName() + "-");
                    cell.setCellStyle(style);
                }
                continue;
            }
            XSSFCell xssfCell = firstRow.createCell(i);
            xssfCell.setCellValue(field.getName() + "-");
            xssfCell.setCellStyle(style);
        }

        //flag:1
        if(flag.equals(1)){
            //新增createTime字段單元格
            XSSFCell createTimeCell = firstRow.createCell(firstRow.getLastCellNum());
            createTimeCell.setCellValue("createTime-");
            createTimeCell.setCellStyle(style);
            //新增createBy字段單元格
            XSSFCell createByCell = firstRow.createCell(firstRow.getLastCellNum());
            createByCell.setCellValue("createBy-");
            createByCell.setCellStyle(style);

            //獲取時間區間數據
            List<T> list = baseJpaRepository.findAll((root,query,cb) -> {
                List<Predicate> predicateList = new ArrayList<>();

                predicateList.add(cb.between(root.get("createTime"), new Date(beginTime), new Date(endTime)));

                Predicate[] array = new Predicate[predicateList.size()];
                return cb.and(predicateList.toArray(array));
            });

            font.setBold(false);
            style.setFont(font);

            int dataIndex = 1;
            for (T e : list) {
                JSONObject jsonObject = JSONObject.parseObject(JSON.toJSONString(e));

                //構建新的單元行
                XSSFRow xssfRow = xssfSheet.createRow(dataIndex++);
                //插入常規數據
                for (int cellNum = 0; cellNum < fields.length; cellNum++) {
                    Field field = fields[cellNum];
                    String fieldName = field.getName();

                    //判斷是否爲關聯屬性
                    if(entityFieldsMap.containsKey(fieldName) && entityNamesMap.containsKey(fieldName)){
                        //關聯工作薄處理數據
                        XSSFSheet entitySheet = xssfWorkbook.getSheet(entityNamesMap.get(fieldName));


                        JSONArray entityArray = JSONArray.parseArray(String.valueOf(jsonObject.get(fieldName)));
                        entityArray.forEach(entity -> {
                            JSONObject entityJson = JSONObject.parseObject(JSON.toJSONString(entity));

                            //構建新的單元行
                            XSSFRow entityRow = entitySheet.createRow(entitySheet.getLastRowNum() + 1);

                            Field[] entityFields = entityFieldsMap.get(fieldName);
                            for(int entityCellNum = 0; entityCellNum < entityFields.length; entityCellNum++){
                                Field entityField = entityFields[entityCellNum];
                                String entityFieldName = entityField.getName();

                                XSSFCell cell = entityRow.createCell(entityCellNum);
                                cell.setCellValue(getFieldData(entityField, entityJson.get(entityFieldName)));
                                cell.setCellStyle(style);
                            }
                        });
                        continue;
                    }
                    XSSFCell xssfCell = xssfRow.createCell(cellNum);
                    xssfCell.setCellValue(getFieldData(field, jsonObject.get(fieldName)));
                    xssfCell.setCellStyle(style);
                }

                //插入createTime數據
                XSSFCell createTimeDataCell = xssfRow.createCell(xssfRow.getLastCellNum());
                createTimeDataCell.setCellValue(DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.CHINA).format(jsonObject.get("createTime")));
                createTimeDataCell.setCellStyle(style);
                //插入createBy數據
                XSSFCell createByDataCell = xssfRow.createCell(xssfRow.getLastCellNum());
                createByDataCell.setCellValue(String.valueOf(jsonObject.get("createBy")));
                createByDataCell.setCellStyle(style);
            }
        }
        return xssfWorkbook;
    }

    /**
     * 轉化屬性值爲String
     * @param field
     * @param object
     * @return
     */
    protected String getFieldData(Field field, Object object){
        if(null == field || null == object){
            return null;
        }
        String result;
        String type = field.getGenericType().toString();
        switch (type){
            case "class java.util.Date":
                result = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.CHINA).format(object);
                break;
            default:
                result = String.valueOf(object);
                break;
        }
        return result;
    }

}
  • 外部接口設計
    在這裏插入圖片描述
    (1)Excel服務接口
/**
 * Excel服務接口
 * @author xinyao.zhang
 */
public interface RiaExcelService {

    /**
     * 導入Excel文件數據
     * @param serviceName 服務名
     * @param file 文件
     * @return JSONObject
     */
     JSONObject importExcelData(String serviceName,MultipartFile file);

    /**
     * 導出Excel文件數據
     * @param serviceName 服務名
     * @param exportExcelDto 導出參數
     */
     void exportExcelData(String serviceName, ExportExcelDto exportExcelDto);
}

(2)Excel服務實現類

/**
 * Excel服務實現類
 * @author chenjianian
 */
@Service
public class RiaExcelServiceImpl extends RiaServiceImpl implements RiaExcelService {

    @Override
    public JSONObject importExcelData(String serviceName,MultipartFile file) {
        BaseRiaService baseRiaService = super.riaBaseService(serviceName);
        return baseRiaService.importExcelData(file);
    }

    @Override
    public void exportExcelData(String serviceName, ExportExcelDto exportExcelDto) {
        BaseRiaService baseRiaService = super.riaBaseService(serviceName);
        baseRiaService.exportExcelData(exportExcelDto.getFlag()
                ,exportExcelDto.getBeginTime(),exportExcelDto.getEndTime(),exportExcelDto.getResponse());
    }
}

(3)RIA服務實現類

/**
 * RIA服務實現類
 * @author xinyao.zhang
 */
public class RiaServiceImpl{

    /**
     * 獲取流程對應服務
     * @param riaServiceName 流程服務名
     * @return RiaBaseService
     */
    protected BaseRiaService riaBaseService(String riaServiceName){
        ApplicationContext applicationContext = ApplicationContextRegister.getApplicationContext();
        return (BaseRiaService) applicationContext.getBean(riaServiceName);
    }
}

(4)應用上下文配置

/**
 * 應用上下文配置
 * @author xinyao.zhang
 */
@Component
@Lazy(false)
public class ApplicationContextRegister implements ApplicationContextAware {
    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationContextRegister.class);
    private static ApplicationContext APPLICATION_CONTEXT;

    /**
     * 設置spring上下文  *  * @param applicationContext spring上下文  * @throws BeansException
     */
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        LOGGER.debug("ApplicationContext registed-->{}", applicationContext);
        APPLICATION_CONTEXT = applicationContext;
    }

    public static ApplicationContext getApplicationContext() {
        return APPLICATION_CONTEXT;
    }
}

(5)表單-控制層

/**
 * 表單-控制層
 * @author chenjianian
 */
@RestController
@RequestMapping("excel")
public class ExcelModelController {

    @Resource
    private RiaExcelService riaExcelService;

    /**
     * 導入Excel文件數據
     * @param file
     * @return ResultDataDto
     */
    @ApiOperation(value = "導入Excel數據", notes = "根據導入的Excel文件導入數據到數據庫")
    @RequestMapping(value = "/{MODEL_NAME}/import", method = RequestMethod.POST)
    @ResponseBody
    public ResultDataDto importExcelData(@PathVariable("MODEL_NAME") String serviceName,@RequestParam("file") MultipartFile file){
        String fileName = file.getOriginalFilename();
        if(!fileName.endsWith(".xls") && !fileName.endsWith(".xlsx")){
            throw new BusinessException(CommonConstants.EXCEPTION_STATUS, "非法文件格式");
        }
        try {
            JSONObject result = riaExcelService.importExcelData(serviceName,file);

            ResultDataDto resultDataDto = new ResultDataDto();
            resultDataDto.setCode(CommonConstants.SUCCESS_CODE);
            resultDataDto.setMessage("數據導入完成");
            resultDataDto.setStatus(CommonConstants.SUCCESS_STATUS);
            resultDataDto.setData(result);
            return resultDataDto;
        } catch (Exception e){
            throw new BusinessException(CommonConstants.EXCEPTION_STATUS, CommonConstants.EXCEPTION_MESSAGE + e.getMessage(), e);
        }
    }

    /**
     * 導出Excel文件數據
     * @param serviceName 模塊名
     * @param exportExcelDto 導出參數
     */
    @ApiOperation(value = "導出Excel數據", notes = "將數據庫數據導出到Excel文件")
    @RequestMapping(value = "/{MODEL_NAME}/export", method = RequestMethod.POST)
    @ResponseBody
    public void exportExcelData(@PathVariable("MODEL_NAME") String serviceName, @RequestBody @Valid ExportExcelDto exportExcelDto, HttpServletResponse response){
        try {
            exportExcelDto.setResponse(response);
            riaExcelService.exportExcelData(serviceName,exportExcelDto);
        } catch (Exception e){
            throw new BusinessException(CommonConstants.EXCEPTION_STATUS, CommonConstants.EXCEPTION_MESSAGE + e.getMessage(), e);
        }
    }
}
  • 業務Service層設計

在業務Service層,只需繼承BaseRiaServiceImpl類,即擁有了通用的Excel功能,而接口SERVICE_NAME只需傳入服務的名字及可完成對應Excel功能的調用。在此引入一個例子,如:職員-服務EmployeeServiceImpl。

/**
 * 職員-服務實現層
 *
 * @author xinyao.zhang
 */
@Service("employee")
public class EmployeeServiceImpl extends BaseRiaServiceImpl<Employee, Long> {
}

*:可重寫RIA報表服務抽象接口BaseRiaExcelService,在通用的基礎上完成定製化的導入導出開發。

演示

職員Employee
一、導出數據模版
在這裏插入圖片描述在這裏插入圖片描述
二、填入數據後導入
在這裏插入圖片描述
數據導出部分在這裏不做演示。

總結

本次的開發,實現了對單表結構和存在主外鍵關聯的主輔表結構的通用Excel導入和導出功能,用戶可以選擇參數的傳入控制相應數據表的導入和導出。
在功能支持上,自增主鍵表結構,僅支持對應數據的插入操作,不支持對數據的更新操作;非自增主鍵表結構,支持對應數據的插入操作,也支持對數據的更新操作。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章