一个通用的Excel导入导出功能

前言

Excel表单导入,是将Excel文件中的数据导入到数据库;而Excel表单导出,是将数据库的数据导出到Excel文件中。
在本文中开发的功能是基于SpringBoot + JPA框架进行开发,而在系统中,数据库表结构又分成了两种情况,一种是单表结构,另一种是存在@OneToMany注解的主外键关联的主辅表结构,所以本次开发目的是可以同时兼容两种情况。
另外,因为不同的数据表对应的Excel表单的数据和格式都不同,但是为每一个数据表定制化开发Excel导入导出,工作重复度高,所以本次开发另一个目的是能够做到所有数据表通用Excel表单导入导出

起步

SpringBoot和JPA的环境请读者自行准备,在这里引入poi依赖来协助开发,版本选择4.0.1


    org.apache.poi
    poi
    ${poi.version}

 

    org.apache.poi
    poi-ooxml
    ${poi.version}

开始

设计思路
一个通用的Excel导入导出功能_第1张图片

功能设计

导入和导出功能都能通过传入的服务参数,完成对指定表结构的导入或导出。
如: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表单单元行和失败原因反馈给用户,如:导入数据总数,成功导入数据总数,失败导入数据总数以及失败导入原因

服务设计

  • 内部服务设计
    一个通用的Excel导入导出功能_第2张图片

(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 {

    /**
     * 查找单个实例
     * @param id 实例ID
     * @return T
     */
    T find(ID id);
}

(4)基础服务逻辑层

/**
 * 基础服务逻辑层
 * @author xinyao.zhang
 */
public class BaseServiceImpl implements BaseService {

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

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

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

    protected BaseJpaRepository 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 
 * @param 
 */
@SuppressWarnings("unchecked")
public abstract class BaseRiaServiceImpl extends BaseServiceImpl implements BaseRiaService {

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

    private BaseRiaExcel 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 
 * @param 
 */
public class BaseRiaExcel extends BaseServiceImpl {

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

    private Class entityClass;

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

    /**
     * List数据导入数据库
     * @param list
     * @return
     */
    public JSONObject importExcelData(List 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 excelDataConvert(MultipartFile file){
        List 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 singleSheetDataConvert(XSSFWorkbook xssfWorkbook){
        List 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 multipleSheetDataConvert(XSSFWorkbook xssfWorkbook){
        try{
            List list = new ArrayList<>();

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

            //存储关联数据
            Map map = new HashMap<>();
            //存储关联表的关联字段
            Map columnMap = new HashMap<>();
            //存储关联表的类
            Map classMap = new HashMap<>();
            //存储关联表在主表中的属性
            Map fieldMap = new HashMap<>();
            //存储关联表中的自增id
            Map 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 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 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 entry: fieldMap.entrySet()){
                        String entityName = entry.getKey();
                        Field field = entry.getValue();

                        Map 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 entityNamesMap = new HashMap<>();
        Map 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 list = baseJpaRepository.findAll((root,query,cb) -> {
                List 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;
    }

}
  • 外部接口设计
    一个通用的Excel导入导出功能_第3张图片
    (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 {
}

*:可重写RIA报表服务抽象接口BaseRiaExcelService,在通用的基础上完成定制化的导入导出开发。

演示

职员Employee
一、导出数据模版
一个通用的Excel导入导出功能_第4张图片
二、填入数据后导入
一个通用的Excel导入导出功能_第5张图片
数据导出部分在这里不做演示。

总结

本次的开发,实现了对单表结构和存在主外键关联的主辅表结构的通用Excel导入和导出功能,用户可以选择参数的传入控制相应数据表的导入和导出。
在功能支持上,自增主键表结构,仅支持对应数据的插入操作,不支持对数据的更新操作;非自增主键表结构,支持对应数据的插入操作,也支持对数据的更新操作。

你可能感兴趣的:(模块学习)