1.自定義注解3個
1.ExportEntity
/**
* @Author: chenxiaoqing9 微信:weixin1398858069
* @Date: Created in 2019/1/16
* @Description: 導(dǎo)出實(shí)體注解
* @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: 導(dǎo)出實(shí)體的字段注解
* @Modified by:
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExportParam {
String value();
int length() default 0; /*字段限制的長度*/
String pattern() default ""; /*正則過濾*/
String dateFormat() default ""; /*時間格式過濾*/
}
2.導(dǎo)出CSV工具類 CSVUtils.java
/**
* @Author: chenxiaoqing9
* @Date: Created in 2019/1/17
* @Description: 導(dǎo)出CSV工具類
* @Modified by:
*/
@Slf4j
public class CSVUtils {
/**
* 取得導(dǎo)出內(nèi)容的最終值
*
* @return 最終值
*/
public static String getCSVContent(CSVExportConfig exportConfig) {
boolean isFirstRow = isFirstRow(exportConfig);
StringBuilder sb = new StringBuilder();
if (isFirstRow) {//第一條數(shù)據(jù)添加頭部信息
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();
}
/**
* 獲取頭部信息并設(shè)置字段跟字段取值方法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);
//設(shè)置
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 內(nèi)容
*
* @return 行內(nèi)容
* @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=過濾數(shù)值
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 { // 簡單數(shù)據(jù)類型
try {
val = formatStringValue(invokeVal.toString(), fieldFormatter.get(fieldName));
} catch (Exception e) {
throw new ExportException("導(dǎo)出實(shí)體屬性只能是簡單類型或者時間類型");
}
}
}
// E=過濾數(shù)值
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("所需導(dǎo)出數(shù)據(jù)大小為" + 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;
}
/**
* 轉(zhuǎn)換第一個字母大寫
*
* @param value 值
* @return value的頭文字大寫值
*/
private static String firstOneToUpperCase(String value) {
return value.substring(0, 1).toUpperCase() + value.substring(1);
}
/**
* 根據(jù)csv內(nèi)容,通過流導(dǎo)出文件
*
* @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();
}
/**
* 重設(shè)響應(yīng)頭
*
* @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");
}
/**
* 導(dǎo)出CSV
* 1.驗(yàn)證配置
* 2.取得頭部內(nèi)容信息
* 3.導(dǎo)出內(nèi)容文件
*
* @param response 相應(yīng)對象,為了獲取響應(yīng)流
* @param clazz 導(dǎo)出的實(shí)體對象
* @param data 導(dǎo)出的實(shí)體對象數(shù)據(jù)集合
* @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 {
//驗(yàn)證配置完整性
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解析結(jié)果" + exportConfig.getHeader());
//獲取行數(shù)據(jù)
setRowDataByFieldMethods(exportConfig);
//重新設(shè)置response對象
resetResponse(response);
exportCSV(exportConfig, response.getOutputStream());
}
/**
* 驗(yàn)證配置信息
*
* @param data 數(shù)據(jù)集合
* @param clazz 導(dǎo)出實(shí)體
*/
private static void validateConfig(Object data, Class clazz) {
if (!(data instanceof Collection)) {
throw new ExportException("接口返回數(shù)據(jù)類型應(yīng)為集合");
}
if (null == clazz) {
throw new ExportException("未知導(dǎo)出實(shí)體類");
}
Annotation annotation = clazz.getAnnotation(ExportEntity.class);
if (null == annotation) {
throw new ExportException("導(dǎo)出實(shí)體類未標(biāo)示注解");
}
}
}
3.導(dǎo)出配置類
/**
* @Author: chenxiaoqing9
* @Date: Created in 2019/1/17
* @Description: 導(dǎo)出數(shù)據(jù)配置類
* @Modified by:
*/
public class CSVExportConfig {
public final int MAX_CACHE_ROW_COUNT = 5000;//最多緩存5000條刷新到Response里面再繼續(xù)執(zhí)行
private int cycleCount;//需要循環(huán)刷新幾次
/**
* 導(dǎo)出實(shí)體類
*/
private Class clazz;
/**
* 頭部信息
*/
private String header;
private List<String> rows = new LinkedList<>();
/**
* 數(shù)據(jù)內(nèi)容
*/
private Collection data;
/**
* 獲取帶注解的實(shí)體:字段名稱 -- 字段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("導(dǎo)出數(shù)據(jù)為空!");
}
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: 導(dǎo)出接口 切面
* @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 {
//獲取執(zhí)行結(jié)果
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)注解,并注明導(dǎo)出實(shí)體類。示例代碼如下:
@GetMapping("/exportList")
@ExportHandler(A.class)// 導(dǎo)出集合的實(shí)體
public List<A> export(AQuery query, Page<A> page) {
Page<A> data = AService.queryByPage(query, page);
return data.getRows();
}
2.導(dǎo)出的實(shí)體加上類注解跟需要導(dǎo)出的字段注解(a.實(shí)體注解:@ExportEntity;b.字段注解@ExportParam--注解內(nèi)屬性作用見下列詳情)
<!--示例代碼-->
@ExportEntity
public class A{
@ExportParam("名字")
private String name;
@ExportParam(value = "部門名稱")
private String deptName;
}
3.前端代碼接口訪問方式這邊提供三種
- 使用<a href='下載地址' target='_blank'></a>下載
- location.href='下載地址'
- 用異步xhr下載(有限制大小,太大會報安全問題)、