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.前端代码接口访问方式这边提供三种
- 使用<a href='下载地址' target='_blank'></a>下载
- location.href='下载地址'
- 用异步xhr下载(有限制大小,太大会报安全问题)、
网友评论