美文网首页程序员
Spring AOP + 自定义注解 实现千万Excel数据统一

Spring AOP + 自定义注解 实现千万Excel数据统一

作者: MyIreland | 来源:发表于2019-01-18 15:56 被阅读5次

    1.自定义注解3个

    1.ExportEntity

    /**
     * @Author: chenxiaoqing9  微信:weixin1398858069
     * @Date: Created in 2019/1/16
     * @Description: 导出实体注解
     * @Modified by:
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExportEntity {
    }
    

    2.ExportHandler

    /**
     * @Author: chenxiaoqing9
     * @Date: Created in 2019/1/15
     * @Description: 方法注解
     * @Modified by:
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExportHandler {
        Class value();
    }
    

    3.ExportParam

    /**
     * @Author: chenxiaoqing9
     * @Date: Created in 2019/1/16
     * @Description: 导出实体的字段注解
     * @Modified by:
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface ExportParam {
        String value();
        int length() default 0; /*字段限制的长度*/
        String pattern() default ""; /*正则过滤*/
        String dateFormat() default ""; /*时间格式过滤*/
    }
    

    2.导出CSV工具类 CSVUtils.java

    
    /**
     * @Author: chenxiaoqing9
     * @Date: Created in 2019/1/17
     * @Description: 导出CSV工具类
     * @Modified by:
     */
    @Slf4j
    public class CSVUtils {
    
        /**
         * 取得导出内容的最终值
         *
         * @return 最终值
         */
        public static String getCSVContent(CSVExportConfig exportConfig) {
    
            boolean isFirstRow = isFirstRow(exportConfig);
            StringBuilder sb = new StringBuilder();
            if (isFirstRow) {//第一条数据添加头部信息
                sb.append(exportConfig.getHeader()).append("\r\n");
            }
            List<String> rows = exportConfig.getRows();
            Iterator<String> iterator = rows.iterator();
            for (int i = 0; i < exportConfig.MAX_CACHE_ROW_COUNT; i++) {
                if (iterator.hasNext()) {
                    sb.append(iterator.next()).append("\r\n");
                    iterator.remove();
                } else {
                    break;
                }
            }
            return sb.toString();
        }
    
        public static boolean isFirstRow(CSVExportConfig exportConfig) {
            Collection data = exportConfig.getData();
            List<String> rows = exportConfig.getRows();
            return data.size() == rows.size();
        }
    
        /**
         * 获取头部信息并设置字段跟字段取值方法Map
         *
         * @return 头部信息
         * @throws NoSuchMethodException
         */
        public static void setHeaderAndFieldParams(CSVExportConfig exportConfig) throws NoSuchMethodException {
            Class clazz = exportConfig.getClazz();
            Field[] declaredFields = clazz.getDeclaredFields();
            Map<String, ExportParam> fieldFormatter = exportConfig.getFieldFormatter();
            Map<String, Method> fieldMethods = exportConfig.getFieldMethods();
    
            StringBuilder sb = new StringBuilder();
            for (Field each : declaredFields) {
                ExportParam exportParam = each.getAnnotation(ExportParam.class);
                if (null != exportParam) {
                    String fieldName = each.getName();
                    String eachHeader = exportParam.value();
                    String methodName = "get" + firstOneToUpperCase(fieldName);
                    //设置
                    fieldMethods.put(fieldName, clazz.getMethod(methodName));
                    fieldFormatter.put(fieldName, exportParam);
    
                    sb.append(eachHeader).append(",");
                }
            }
            //装配头部String
            String tempHeader = sb.toString();
            exportConfig.setHeader(tempHeader.substring(0, tempHeader.length() - 1));
        }
    
        /**
         * 获取row 内容
         *
         * @return 行内容
         * @throws InvocationTargetException
         * @throws IllegalAccessException
         */
        public static void setRowDataByFieldMethods(CSVExportConfig exportConfig) throws InvocationTargetException, IllegalAccessException {
            Collection list = exportConfig.getData();
            Map<String, Method> fieldMethods = exportConfig.getFieldMethods();
            Map<String, ExportParam> fieldFormatter = exportConfig.getFieldFormatter();
    
            List<String> exportRows = new LinkedList<>();
            for (Object each : list) {
                StringBuilder row = new StringBuilder();
                for (String fieldName : fieldMethods.keySet()) {
                    // S=过滤数值
                    Object invokeVal = fieldMethods.get(fieldName).invoke(each);
                    String val = null;
                    if (null != invokeVal) {
                        if (invokeVal instanceof Date) {//时间类型
                            Date dateVal = (Date) invokeVal;
                            val = formatDateValue(dateVal, fieldFormatter.get(fieldName));
                        } else { // 简单数据类型
                            try {
                                val = formatStringValue(invokeVal.toString(), fieldFormatter.get(fieldName));
                            } catch (Exception e) {
                                throw new ExportException("导出实体属性只能是简单类型或者时间类型");
                            }
                        }
                    }
                    // E=过滤数值
                    row.append(null == val ? "" : val).append(",");
                }
                String exportRowsStr = row.toString();
                log.info("row为===" + exportRowsStr);
                if (exportRowsStr.length() > 1) {
                    String substring = exportRowsStr.substring(0, exportRowsStr.length() - 1);
                    exportRows.add(substring);
                }
            }
            exportConfig.setRows(exportRows);
            log.info("所需导出数据大小为" + exportRows.size() + "行");
        }
    
        /**
         * 格式化时间类型
         *
         * @param dateVal
         * @param exportParam
         * @return
         */
        private static String formatDateValue(Date dateVal, ExportParam exportParam) {
            String formatter = exportParam.dateFormat();
    
            // 过滤时间格式
            if (StringUtils.isNotBlank(formatter)) {
                SimpleDateFormat sdf = new SimpleDateFormat(formatter);
                return sdf.format(dateVal);
            }
            return dateVal.toString();
        }
    
        /**
         * 值过滤,可迭代
         *
         * @param val         值
         * @param exportParam 过滤属性
         * @return 返回过滤完之后的值
         * @throws ParseException
         */
        public static String formatStringValue(String val, ExportParam exportParam) {
            int length = exportParam.length();
            String patternStr = exportParam.pattern();
    
            if (StringUtils.isNotBlank(val)) {
                // 过滤长度
                if (0 != length && val.length() > length) {
                    val = val.substring(0, length);
                }
                // 过滤正则
                if (StringUtils.isNotBlank(patternStr)) {
                    Pattern pattern = Pattern.compile(patternStr); //中文括号
                    Matcher matcher = pattern.matcher(val);
                    if (matcher.matches()) {
                        val = matcher.replaceAll("");//全替换成""
                    }
                }
            }
            return val;
        }
    
        /**
         * 转换第一个字母大写
         *
         * @param value 值
         * @return value的头文字大写值
         */
        private static String firstOneToUpperCase(String value) {
            return value.substring(0, 1).toUpperCase() + value.substring(1);
        }
    
        /**
         * 根据csv内容,通过流导出文件
         *
         * @param outputStream 流
         * @throws IOException
         */
        public static void exportCSV(CSVExportConfig exportConfig, OutputStream outputStream) throws IOException {
            /*S=处理乱码问题*/
            OutputStreamWriter out = new OutputStreamWriter(outputStream, Charset.forName("UTF-8"));
            out.write(new String(new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}));
            /*E=处理乱码问题*/
            int cycleCount = exportConfig.getCycleCount();
            for (int i = 0; i < cycleCount; i++) {
                String content = getCSVContent(exportConfig);
                out.write(content);
                out.flush();
            }
            out.close();
        }
    
        /**
         * 重设响应头
         *
         * @param response
         */
        public static void resetResponse(HttpServletResponse response) {
            response.reset();
            response.setHeader("Content-disposition", "attachment; filename=file.csv");
            response.setContentType("application/octet-stream; charset=UTF-8");
        }
    
        /**
         * 导出CSV
         * 1.验证配置
         * 2.取得头部内容信息
         * 3.导出内容文件
         *
         * @param response 相应对象,为了获取响应流
         * @param clazz    导出的实体对象
         * @param data     导出的实体对象数据集合
         * @throws InvocationTargetException
         * @throws IllegalAccessException
         * @throws NoSuchMethodException
         * @throws IOException
         */
        public static void exportCSV(HttpServletResponse response, Class clazz, Object data) throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, IOException, NoSuchFieldException, ParseException {
            //验证配置完整性
            validateConfig(data, clazz);
    
            Collection list1 = (Collection) data;
    
            List list = new ArrayList();
            for (int i = 0; i < 1000; i++) {
                list.addAll(list1);
            }
    
            CSVExportConfig exportConfig = new CSVExportConfig(clazz, list);
    
            setHeaderAndFieldParams(exportConfig);
            log.info("header解析结果" + exportConfig.getHeader());
    
            //获取行数据
            setRowDataByFieldMethods(exportConfig);
    
            //重新设置response对象
            resetResponse(response);
    
            exportCSV(exportConfig, response.getOutputStream());
        }
    
        /**
         * 验证配置信息
         *
         * @param data  数据集合
         * @param clazz 导出实体
         */
        private static void validateConfig(Object data, Class clazz) {
            if (!(data instanceof Collection)) {
                throw new ExportException("接口返回数据类型应为集合");
            }
            if (null == clazz) {
                throw new ExportException("未知导出实体类");
            }
            Annotation annotation = clazz.getAnnotation(ExportEntity.class);
            if (null == annotation) {
                throw new ExportException("导出实体类未标示注解");
            }
        }
    }
    

    3.导出配置类

    /**
     * @Author: chenxiaoqing9
     * @Date: Created in 2019/1/17
     * @Description: 导出数据配置类
     * @Modified by:
     */
    public class CSVExportConfig {
        public final int MAX_CACHE_ROW_COUNT = 5000;//最多缓存5000条刷新到Response里面再继续执行
    
        private int cycleCount;//需要循环刷新几次
        /**
         * 导出实体类
         */
        private Class clazz;
        /**
         * 头部信息
         */
        private String header;
    
        private List<String> rows = new LinkedList<>();
        /**
         * 数据内容
         */
        private Collection data;
    
        /**
         * 获取带注解的实体:字段名称 -- 字段get方法
         */
        private Map<String, Method> fieldMethods = new LinkedHashMap<>();
        /**
         * fieldFormatter:做过滤字段值处理
         */
        private Map<String, ExportParam> fieldFormatter = new HashMap<>();
    
        public CSVExportConfig(Class clazz, Collection data) {
            this.clazz = clazz;
            this.data = data;
        }
    
        public int getCycleCount() {
            if(this.data.size() == 0){
                throw new ExportException("导出数据为空!");
            }
            int result = this.data.size() / MAX_CACHE_ROW_COUNT;
            int remainder = this.data.size() % MAX_CACHE_ROW_COUNT;
            result = result + (remainder == 0 ? 0 : 1);
            return result;
        }
    
        public Class getClazz() {
            return clazz;
        }
    
        public void setClazz(Class clazz) {
            this.clazz = clazz;
        }
    
        public String getHeader() {
            return header;
        }
    
        public void setHeader(String header) {
            this.header = header;
        }
    
        public List<String> getRows() {
            return rows;
        }
    
        public void setRows(List<String> rows) {
            this.rows = rows;
        }
    
        public Collection getData() {
            return data;
        }
    
        public void setData(Collection data) {
            this.data = data;
        }
    
        public Map<String, Method> getFieldMethods() {
            return fieldMethods;
        }
    
        public void setFieldMethods(Map<String, Method> fieldMethods) {
            this.fieldMethods = fieldMethods;
        }
    
        public Map<String, ExportParam> getFieldFormatter() {
            return fieldFormatter;
        }
    
        public void setFieldFormatter(Map<String, ExportParam> fieldFormatter) {
            this.fieldFormatter = fieldFormatter;
        }
    }
    

    4.切面配置

    /**
     * @Author: chenxiaoqing9
     * @Date: Created in 2019/1/15
     * @Description: 导出接口 切面
     * @Modified by:
     */
    @Aspect
    @Slf4j
    @Component
    public class ExportHandlerAspect {
    
        @Pointcut("@annotation(***.***.***.ExportHandler)")
        public void pointcut(){}
    
        @Around("pointcut() && @annotation(handler)")
        public void doAround(ProceedingJoinPoint point, ExportHandler handler) throws Throwable {
            //获取执行结果
            Object data = point.proceed(point.getArgs());
    
            Class clazz = handler.value();
    
            HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();
    
            CSVUtils.exportCSV(response, clazz, data);
        }
    }
    

    5.使用步骤

    1.Controller加上 @ExportHandler(A.class)注解,并注明导出实体类。示例代码如下:

    @GetMapping("/exportList")
        @ExportHandler(A.class)// 导出集合的实体
        public List<A> export(AQuery query, Page<A> page) {
            Page<A> data = AService.queryByPage(query, page);
            return data.getRows();
        }
    

    2.导出的实体加上类注解跟需要导出的字段注解(a.实体注解:@ExportEntity;b.字段注解@ExportParam--注解内属性作用见下列详情)

    <!--示例代码-->
    @ExportEntity
    public class A{
    
        @ExportParam("名字")
        private String name;
    
        @ExportParam(value = "部门名称")
        private String deptName;
    }
    

    3.前端代码接口访问方式这边提供三种

    1. 使用<a href='下载地址' target='_blank'></a>下载
    2. location.href='下载地址'
    3. 用异步xhr下载(有限制大小,太大会报安全问题)、

    相关文章

      网友评论

        本文标题:Spring AOP + 自定义注解 实现千万Excel数据统一

        本文链接:https://www.haomeiwen.com/subject/ycmndqtx.html