美文网首页程序员
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