使用freemarker,導(dǎo)出制作好的ftl模板,并寫入數(shù)據(jù)
一、背景
1.1 項(xiàng)目背景
- 最近在開發(fā)一個(gè)項(xiàng)目,需要導(dǎo)出一些數(shù)據(jù),然后寫入到word文檔中,然后再導(dǎo)出到本地,這個(gè)需求是比較常見的,但是我在網(wǎng)上找了很多資料,都沒(méi)有找到一個(gè)比較好的解決方案,所以就自己寫了一個(gè),這里分享給大家,希望能幫助到大家。
- 項(xiàng)目中使用的技術(shù)棧:freemarker
- 項(xiàng)目中使用的依賴:
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.28</version>
</dependency>
二、實(shí)現(xiàn)
2.1 代碼實(shí)現(xiàn)
- 代碼實(shí)現(xiàn)比較簡(jiǎn)單,就是先制作好ftl模板,然后在模板中標(biāo)記好需要寫入的數(shù)據(jù)的位置,然后在代碼中將數(shù)據(jù)寫入到模板中,然后再導(dǎo)出到本地,具體代碼如下:
- 1.首先創(chuàng)建一個(gè)ftl模板,這里我創(chuàng)建了一個(gè)test.ftl模板,模板中的內(nèi)容如下:
<html>
<head>
<title>Welcome!</title>
</head>
<body>
<h1>Welcome ${user}!</h1>
<p>We have there animals:
<ul>
<list animals as animal>
<li>${animal.name} for ${animal.price} Euros
</list>
</ul>
<include "common_footer.html">
</body>
</html>
- 2.然后在查詢數(shù)據(jù)中定義數(shù)據(jù)對(duì)象,使用@ExportWordField注解標(biāo)記需要寫入的數(shù)據(jù),具體代碼如下:
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface ExportWordField {
//字段名稱
String fieldName() default "";
//是否是文件名稱字段,只有導(dǎo)出zip時(shí)才會(huì)用到
boolean isFileNameFiled() default false;
//文件名稱字段排序,只有導(dǎo)出zip時(shí)才會(huì)用到
int fileNameFiledSort() default 0;
}
//測(cè)試數(shù)據(jù)對(duì)象
@Data
public class Something{
@ExportWordField(fieldName = "user",isFileNameFiled = true,fileNameFiledSort = 1)
private String user;
@ExportWordField(fieldName = "animals")
private List<Animal> animals;
}
@Data
public class Animal{
@ExportWordField(fieldName = "name")
private String name;
@ExportWordField(fieldName = "price")
private String price;
}
- 3.然后在代碼中將數(shù)據(jù)寫入到模板中,具體代碼如下:
@Slf4j
public class ExportTemplateUtil {
private static final String DOC_SUFFIX = ".doc";
/**
* @Description: 導(dǎo)出word文件放在zip里面
* @param zipName 壓縮包名稱
* @param templateName 模板名稱,默認(rèn)取resources下的template文件夾
* @param dataList 數(shù)據(jù)集合
* @param clazz 數(shù)據(jù)類型
* @param <T> 泛型
*/
public static <T>void exportListFileOnZip(HttpServletResponse response, String zipName, String templateName, List<T> dataList,Class<T> clazz){
//所有數(shù)據(jù)的輸出流
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// 壓縮流
ZipOutputStream zipOutputStream = null;
try {
response.setContentType("application/octet-stream;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(zipName, "UTF-8") + ".zip");
zipOutputStream = new ZipOutputStream(response.getOutputStream());
Template template = getTemplate(templateName,clazz);
Map<String,Integer> nameMap = new HashMap<>();
for (T data : dataList) {
String fileName = getFileName(data);
if (nameMap.containsKey(fileName)) {
nameMap.put(fileName,nameMap.get(fileName) +1);
fileName = fileName+"(" + nameMap.get(fileName) +")";
}else {
nameMap.put(fileName,0);
}
ByteArrayOutputStream byteArrayOutputStream = generateOneFile(template,data);
outputStream.write(byteArrayOutputStream.toByteArray());
ZipEntry zipEntry = new ZipEntry(fileName+DOC_SUFFIX);
zipOutputStream.putNextEntry(zipEntry);
zipOutputStream.write(byteArrayOutputStream.toByteArray());
zipOutputStream.closeEntry();
}
zipOutputStream.write(outputStream.toByteArray());
zipOutputStream.closeEntry();
zipOutputStream.flush();
zipOutputStream.close();
} catch (Exception e) {
log.error("導(dǎo)出word文件放在zip里面異常",e);
}finally {
try {
outputStream.close();
zipOutputStream.close();
} catch (IOException e) {
log.error("導(dǎo)出word文件放在zip里面異常",e);
}
}
}
/**
* @Description: 導(dǎo)出word文件所有數(shù)據(jù)都放在一個(gè)文件里面
* @param fileName 文件名稱
* @param templateName 模板名稱,默認(rèn)取resources下的template文件夾
* @param dataList 數(shù)據(jù)集合
* @param clazz 數(shù)據(jù)類型
* @param <T> 泛型
*/
public static <T> void exportListOnOneFile(HttpServletResponse response,String fileName,String templateName, List<T> dataList,Class<T> clazz){
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
response.setContentType("application/msword;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".doc");
Template template = getTemplate(templateName,clazz);
for (T data : dataList) {
ByteArrayOutputStream byteArrayOutputStream = generateOneFile(template,data);
outputStream.write(byteArrayOutputStream.toByteArray());
}
response.getOutputStream().write(outputStream.toByteArray());
response.getOutputStream().flush();
response.getOutputStream().close();
} catch (Exception e) {
log.error("導(dǎo)出word文件失敗",e);
}finally {
try {
outputStream.close();
} catch (IOException e) {
log.error("關(guān)閉流失敗",e);
}
}
}
/**
* 導(dǎo)出提條數(shù)據(jù)放在一個(gè)word文件里面
* @param response 響應(yīng)
* @param fileName 文件名稱
* @param templateName 模板名稱,默認(rèn)取resources下的template文件夾
* @param data 數(shù)據(jù)
* @param clazz 數(shù)據(jù)類型
* @param <T> 泛型
* @throws Exception 異常
*/
public static <T> void exportOneFile(HttpServletResponse response, String fileName, String templateName, T data, Class<T> clazz) throws Exception{
Template template = getTemplate(templateName,clazz);
ByteArrayOutputStream outputStream = generateOneFile(template,data);
response.setContentType("application/msword;charset=UTF-8");
response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("UTF-8"), "ISO8859-1") + ".doc");
response.getOutputStream().write(outputStream.toByteArray());
response.getOutputStream().flush();
response.getOutputStream().close();
}
/**
* 生成一個(gè)word文件的字節(jié)流
* @param data 數(shù)據(jù)
* @param <T> 泛型
* @return 字節(jié)流
* @throws Exception 異常
*/
private static <T> ByteArrayOutputStream generateOneFile(Template template,T data) throws Exception{
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(outputStream);
HashMap<String, Object> dataMap = convertDataToHashMap(data);
if (null == dataMap || dataMap.size() == 0) {
log.error("數(shù)據(jù)為空");
throw new RuntimeException("數(shù)據(jù)為空");
}
template.process(dataMap, writer);
return outputStream;
}
/**
* 轉(zhuǎn)換數(shù)據(jù)為hashMap供freemarker使用
* @param data 數(shù)據(jù)
* @param <T> 泛型
* @return hashMap
*/
private static <T>HashMap<String,Object> convertDataToHashMap(T data){
//使用反射獲取類的屬性
Class<?> myClass = data.getClass();
Field[] fields = myClass.getDeclaredFields();
HashMap<String,Object> dataMap = new HashMap<>();
for (Field field : fields) {
if (field.isAnnotationPresent(ExportWordField.class)) {
//允許訪問(wèn)私有變量
field.setAccessible(true);
//如果是list類型的數(shù)據(jù)需要深度遍歷處理
if (field.getType().equals(List.class)) {
List<?> list = null;
try {
list = convertListDataToHashMap((List<?>) field.get(data));
} catch (IllegalAccessException e) {
log.error("獲取屬性值失敗",e);
throw new RuntimeException("獲取屬性值失敗");
}
dataMap.put(field.getName(),list);
continue;
}
ExportWordField exportWordField = field.getAnnotation(ExportWordField.class);
String fieldName = exportWordField.fieldName();
try {
if (fieldName != null && !"".equals(fieldName)) {
dataMap.put(fieldName, field.get(data) == null ? "" : field.get(data));
}else {
dataMap.put(field.getName(), field.get(data) == null ? "" : field.get(data));
}
} catch (IllegalAccessException e) {
log.error("獲取屬性值失敗",e);
throw new RuntimeException("獲取屬性值失敗");
}
}
}
return dataMap;
}
/**
* 如果fidel是list類型的數(shù)據(jù)則需要判斷此層是否有l(wèi)ist類型的如果有需要不停的往底層變量一直到最后一層然后取每一層符合ExportWordField的數(shù)據(jù)返回給上一層
* @param list list數(shù)據(jù)
* @param <T> 泛型
* @return hashMap
*/
private static <T> List<HashMap<String, Object>> convertListDataToHashMap(List<T> list) {
List<HashMap<String, Object>> listMap = new ArrayList<>();
if (!CollectionUtils.isEmpty(list)) {
for (T t : list) {
HashMap<String, Object> hashMap = convertDataToHashMap(t);
listMap.add(hashMap);
}
}
return listMap;
}
/**
* @Description: 讀取模板文件
* @param templateName 模板名稱,默認(rèn)取resources下的template文件夾
* @return freemarker.template.Template
*/
private static <T>Template getTemplate(String templateName,Class<T> clazz) throws IOException {
Configuration configuration = new Configuration(Configuration.getVersion());
configuration.setDefaultEncoding("UTF-8");
configuration.setClassForTemplateLoading(clazz, "/templates");
return configuration.getTemplate(templateName);
}
private static <T> String getFileName(T data){
Field[] fields = data.getClass().getDeclaredFields();
StringBuilder fileName = new StringBuilder();
Arrays.stream(fields).filter(field -> field.getAnnotation(ExportWordField.class) != null
&& field.getAnnotation(ExportWordField.class).isFileNameFiled()).sorted((o1, o2) -> {
ExportWordField annotation1 = o1.getAnnotation(ExportWordField.class);
ExportWordField annotation2 = o2.getAnnotation(ExportWordField.class);
return annotation1.fileNameFiledSort() - annotation2.fileNameFiledSort();
}).forEach(field -> {
field.setAccessible(true);
try {
fileName.append(field.get(data)+"-");
} catch (IllegalAccessException e) {
log.error("獲取文件名稱異常",e);
}
});
return fileName.toString();
}
}
3.使用
//導(dǎo)出壓縮包
ExportTemplateUtil.exportListFileOnZip(response,"測(cè)試導(dǎo)出","template2.0.ftl",somethingObj,Something.class);
//導(dǎo)出一個(gè)文件
ExportTemplateUtil.exportOneFile(response,"測(cè)試導(dǎo)出","template2.0.ftl",somethingObj,Something.class);
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-749631.html
文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-749631.html
到了這里,關(guān)于使用freemarker,導(dǎo)出制作好的ftl模板,并寫入數(shù)據(jù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!