通用導出excel服務

1. 一般的後管在分頁列表管理裏都有導出功能

我們的項目是先有分頁管理,後面需求要求所有的分頁再增加導出功用,所以後端採用統一導出的策略

根據不同業務場景,多次調用對應的分頁接口,然後統一輸出到excel裏

 

2. 對應的配置類(後期將配置轉化到數據庫裏)

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Pair;
import com.google.common.base.Strings;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import reactor.util.function.Tuple2;
import reactor.util.function.Tuples;

import javax.annotation.PostConstruct;
import java.util.*;

@Data
@ConfigurationProperties(prefix = "export.excel.manager")
public class ExportExcelManagerProperty {
    private Map<String, ExportExcelManagerTemplate> templates;

    @Data
    public static class ExportExcelManagerTemplate {
        /**
         * 導出的文件名
         */
        private String fileName;

        /**
         * 分頁列表接口對應的bean名稱
         */
        private String beanName;

        /**
         * 分頁列表接口對應的method名稱
         */
        private String methodName;

        /**
         * 分頁接口對應的請求類
         */
        private String requestBodyClassName;

        /**
         * 分頁接口對應的返回類(必須是PageInfo<?>類型)
         */
        private String responseBodyClassName;

        /**
         * 動態配置的header頭(由於不同國家輸出表格的header頭不一樣,所以不能直接在dto上打 @ExcelProperty來解決)
         */
        private List<Pair<String, String>> headerFiledMap;
        private String headerFiled;

        /**
         * 動態配置的輸出列及轉換公式
         */
        private Map<String, String> fieldValueMapping;
        private Map<String, Map<String, String>> fieldValueMappingFunction;
    }
    @PostConstruct
    private void init() {
        //
        templates.entrySet().stream()
                .filter(p -> !Strings.isNullOrEmpty(p.getValue().getHeaderFiled()))
                .forEach(p -> {
                    System.out.println(p.getValue().getHeaderFiled());
                    p.getValue().headerFiledMap = toListTuple(p.getValue().getHeaderFiled());
                });

        for (Map.Entry<String, ExportExcelManagerTemplate> entry : templates.entrySet()) {
            Map<String, Map<String, String>> rs = new HashMap<>();
            for (Map.Entry<String, String> entry2 : entry.getValue().getFieldValueMapping().entrySet()) {
                if (!Strings.isNullOrEmpty(entry2.getValue())) {
                    rs.put(entry2.getKey(), toMap(entry2.getValue()));
                }
            }
            entry.getValue().fieldValueMappingFunction = rs;
        }

        System.out.println(this);
    }

    private static List<Pair<String, String>> toListTuple(String str) {
        return toListTuple(str, "&", "=");
    }

    private static List<Pair<String, String>> toListTuple(String str, String itemSpliter, String keyValueSpliter) {
        List<Pair<String, String>> result = new ArrayList<>();
        Arrays.stream(str.split(itemSpliter)).forEach(p -> {
            String[] split = p.split(keyValueSpliter);
            result.add(Pair.of(split[0], split[1]));
        });

        return result;
    }

    private static Map<String, String> toMap(String str) {
        return toMap(str, "&", "=");
    }

    private static Map<String, String> toMap(String str, String itemSpliter, String keyValueSpliter) {
        Map<String, String> result = new TreeMap<>();
        Arrays.stream(str.split(itemSpliter)).forEach(p -> {
            String[] split = p.split(keyValueSpliter);
            result.put(split[0], split[1]);
        });

        return result;
    }
}

 

2. 服務層

    @Autowired
    private ExportExcelManagerProperty exportExcelManagerProperty;

    private static final Map<String, Class> CLASS_MAP = new ConcurrentHashMap<>(32);

    /**
     * 增加導出記錄
     *
     * @return
     */
    @Override
    public Pair<String, byte[]> exportExcel(String type, JSONObject req) {
        ExportExcelManagerProperty.ExportExcelManagerTemplate template = exportExcelManagerProperty
                .getTemplates().get(type);
        Assert.notNull(template, "不支持的導出類型,請聯繫後端開發配置該類型");
        Class requestBodyClassClass = getClass(template.getRequestBodyClassName());
        Class responseBodyClassClass = getClass(template.getResponseBodyClassName());
// 如果當前的response類已存在 @ExcelProperty 註解,則直接使用
boolean existEasyExcelProperty = hasEasyExcelProperty(responseBodyClassClass); Object pageDto = JSONObject.parseObject(req.toJSONString(), requestBodyClassClass); int i = 0; List<String> header = template.getHeaderFiledMap().stream() .map(p -> p.getKey()).collect(Collectors.toList()); List<String> fields = template.getHeaderFiledMap().stream() .map(p -> p.getValue()).collect(Collectors.toList()); List<Object> data = new LinkedList<>(); do { if (pageDto instanceof BasePageDto) { ((BasePageDto) pageDto).setPageNum(++i); ((BasePageDto) pageDto).setPageSize(1000); } else break; String sourceBeanName = template.getBeanName(); Object sourceBean = sourceBeanName.contains(".") ? SpringBeanUtil.getBean(getClass(sourceBeanName)) : SpringBeanUtil.getBean(sourceBeanName); if (null == sourceBean) { throw new RuntimeException("配置的bean不合法"); } Object result = InvokeMethodUtil.invokeMethod(sourceBean, template.getMethodName(), pageDto); if (null == result) { log.warn("調用bean方法異常! type:{}, req:{}", type, req); throw new RuntimeException("配置的bean方法不合法"); } PageInfo<Object> pageData = getRestResponseBody(result); // 這裏還需要根據header頭做映射 if (existEasyExcelProperty) { // 實際結構爲 List<Object> ,Object爲java類實例 data.addAll(pageData.getList()); } else { // 實際結構爲 List<List<Object>>,Object的原始類型 data.addAll(handleData(pageData.getList(), responseBodyClassClass, fields, template.getFieldValueMappingFunction())); } if (!pageData.isHasNextPage()) { break; } } while (i < 1000);
        if (existEasyExcelProperty) {
            return Pair.of(template.getFileName(), writeFile(responseBodyClassClass, data));
        } else {
            return Pair.of(template.getFileName(), writeFile(header, data));
        }
    }

    private static Class getClass(String className) {
        Class tClass = CLASS_MAP.get(className);
        if (tClass != null) {
            return tClass;
        }
        tClass = getClassImpl(className);
        CLASS_MAP.put(className, tClass);
        return tClass;
    }

    private static Class getClassImpl(String className) {
        Class<?> paramClass = null;
        try {
            paramClass = Class.forName(className);
            return paramClass;
        } catch (ClassNotFoundException e) {
            throw new RuntimeException("配置的ParamClassName無法識別");
        }
    }

    private boolean hasEasyExcelProperty(Class responseBodyClassClass) {
        Map<Field, ExcelProperty> allField = AnnotationExtentUtil.getAllField(responseBodyClassClass, ExcelProperty.class);
        return allField.size() > 0;
    }

    private static byte[] writeFile(Class responseBodyClassClass, List<Object> data) {
        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
            ExcelWriter excelWriter = EasyExcelFactory.write().head(responseBodyClassClass).file(os).build();
            WriteSheet writeSheet = EasyExcelFactory.writerSheet(0).build();
            excelWriter.write(data, writeSheet);
            excelWriter.finish();
            return os.toByteArray();
        } catch (IOException e) {
            //e.printStackTrace();
            log.error("生成 excel 異常!", e);
            throw new RuntimeException("生成 excel 異常!");
        }
    }
    private static byte[] writeFile(List<String> headerName, List<Object> data) {
        List<List<String>> header = headerName.stream()
                .map(p -> Collections.singletonList(p))
                .collect(Collectors.toList());
        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
            ExcelWriter excelWriter = EasyExcelFactory.write().head(header).file(os).build();
            WriteSheet writeSheet = EasyExcelFactory.writerSheet(0).build();
            excelWriter.write(data, writeSheet);
            excelWriter.finish();
            return os.toByteArray();
        } catch (IOException e) {
            //e.printStackTrace();
            log.error("生成 excel 異常!", e);
            throw new RuntimeException("生成 excel 異常!");
        }
    }

    /**
     * 對RestResponse做特殊處理,只取其data
     */
    public List<List<Object>> handleData(List<Object> data, Class responseBodyClass, List<String> fields, Map<String, Map<String, String>> functionMap) {
        
        List<List<Object>> result = new ArrayList<>(data.size());
        for (Object obj : data) {
            List<Object> item = new ArrayList<>(fields.size());
            for (String filed : fields) {
                Object fieldValue = ReflectionUtils.getFieldValue(obj, filed);
                Map<String, String> function = functionMap.get(filed);
                item.add(convertValue(fieldValue, function));
            }
            result.add(item);
        }
        return result;
    }

    private static Object convertValue(Object v, Map<String, String> function) {
        if (v == null) {
            return null;
        }
        if (function == null) {
            if (v instanceof List) {
                return String.join(",", (List) v);
            } else {
                return v;
            }
        }
        if (v instanceof List) {
            return ((List<?>) v).stream()
                    .map(p -> function.containsKey(p.toString())
                            ? function.get(p.toString())
                            : p.toString()
                    )
                    .collect(Collectors.joining(","));
        } else {
            return function.containsKey(v.toString())
                    ? function.get(v.toString())
                    : v;
        }
    }
    /**
     * 對RestResponse做特殊處理,只取其data
     */
    public PageInfo<Object> getRestResponseBody(Object result) {
        if (!(result instanceof RestResponse)) {
            throw new RuntimeException("返回類型必須是RestResponse結構");
        }
        RestResponse<?> restResponse = (RestResponse<?>) result;
        if (!RestResponse.SUCC_CODE.equalsIgnoreCase(restResponse.getResultCode())) {
            log.error("ApiOperationLogAspect.handleRestResponse resultCode not successful :{}", JSONObject.toJSONString(restResponse));
            return null;
        }
        Object data = restResponse.getData();
        if (null == data) {
            log.error("ApiOperationLogAspect.handleRestResponse RestResponse data is empty");
        }
        if (data instanceof PageInfo) {
            return (PageInfo) (data);
        }
        throw new RuntimeException("返回類型必須是RestResponse<PageInfo<*>>結構");
    }

 

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