使用freemarker生成代码并打包下载
使用freemarker生成代码
FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。
使用freemarker生成实体类
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
工具类
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import org.springframework.stereotype.Component;
import org.springframework.util.ResourceUtils;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Component
public class FreeMarkerUtils {
//@Value("${gtyfreemarker.templatePath}")
private static String templatePath = "classpath:/freemarker/templates/";
private String packageName = "com.gty.domain";
public static void main(String[] args) throws IOException, TemplateException {
FreeMarkerUtils freeMarkerUtils = new FreeMarkerUtils();
freeMarkerUtils.generateJavaModelCode2File();
}
/**
* 将文件流直接压缩后返回客户端,可以避免在服务端生成文件.
* @param response
* @param zipFileName
* @throws Exception
*/
public void generateJavaCodeFile2Client(HttpServletResponse response,String zipFileName) throws Exception {
//构建模板引擎
Configuration configuration = buildFreemarkerTemplate();
Template temp = configuration.getTemplate("Model.ftl");
Map<String, Object> infoMap01 = generateData("test111111");
Map<String, Object> infoMap02 = generateData("test22222");
Map<String, Object> infoMap03 = generateData("test33333");
//使用字节流来合并模板,并且将文件输出流直接给压缩,多文件可以使用同一个流,只需要重置
ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream();
OutputStreamWriter writer = new OutputStreamWriter(arrayOutputStream);
temp.process(infoMap01, writer);
//将流转为字节数组,方便压缩输出
List<Map<String, byte[]>> compressInfoList = new ArrayList<>();
byte[] bytes01 = arrayOutputStream.toByteArray();
//组建压缩信息交给压缩工具类,这里可以使用一个对象,为了简单使用了map
Map<String, byte[]> compressInfo01 = new HashMap<>();
compressInfo01.put(infoMap01.get("className") + ".java", bytes01);
compressInfoList.add(compressInfo01);
//可以使用一个流完成,只需要清空即可
arrayOutputStream.reset();
writer.flush();
//组建第二个流信息
temp.process(infoMap02, writer);
byte[] bytes02 = arrayOutputStream.toByteArray();
Map<String, byte[]> compressInfo02 = new HashMap<>();
//同时可以携带文件路径
compressInfo02.put(generateFilePath()+infoMap02.get("className") + ".java", bytes02);
compressInfoList.add(compressInfo02);
arrayOutputStream.reset();
writer.flush();
//组建第三个流信息
temp.process(infoMap03, writer);
byte[] bytes03 = arrayOutputStream.toByteArray();
Map<String, byte[]> compressInfo03 = new HashMap<>();
compressInfo03.put(generateFilePath()+infoMap03.get("className") + ".java", bytes03);
compressInfoList.add(compressInfo03);
//以上可以使用循环完成,为了简单,不用了
//这里是进行文件的压缩
FileCompressUtils.downloadZipStream(response,compressInfoList,zipFileName);
writer.close();
}
/**
* 生成代码到文件里去
* @throws IOException
* @throws TemplateException
*/
public void generateJavaModelCode2File() throws IOException, TemplateException {
//构建模板引擎
Configuration configuration = buildFreemarkerTemplate();
Template temp = configuration.getTemplate("Model.ftl");
//构造k-v型数据
Map<String, Object> infoMap = generateData("test001");
//使用文件输出流输出成一个文件, 文件路径和文件名, 文件名同类名
File fileDir = new File("D://test/" + generateFilePath());
if (!fileDir.exists()) {
fileDir.mkdirs();
}
//文件完整路径
File outputFile = new File(fileDir,infoMap.get("className") + ".java");
FileOutputStream stream = new FileOutputStream(outputFile);
OutputStreamWriter writer = new OutputStreamWriter(stream);
temp.process(infoMap, writer);
//关闭流
writer.close();
stream.close();
System.out.println("生成代码成功");
}
/**
* 构建模板引擎
* @return
* @throws IOException
*/
public Configuration buildFreemarkerTemplate() throws IOException {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_23);
cfg.setDirectoryForTemplateLoading(new File(generateAbsoluteTemplatePath()));
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
return cfg;
}
/**
* 从包路径得到文件路径
*/
public String generateFilePath() {
return packageName.replaceAll("\\.", "\\/") + "\\/";
}
/**
* 从项目相对路径获取为磁盘绝对路径
*/
public static String generateAbsoluteTemplatePath() {
return FreeMarkerUtils.class.getClass().getResource("/").getFile() + templatePath.substring(ResourceUtils.CLASSPATH_URL_PREFIX.length() + 1);
}
/**
* 类名将下划线转为驼峰
* 例如 md_user_name -> MdUserName
*
* @param className
*/
private String generateClassName(String className) {
if (className != null) {
String[] oleStrs = className.split("\\_");
List<StringBuilder> newStrings = new ArrayList<>();
for (int i = 0; i < oleStrs.length; i++) {
char[] chars = oleStrs[i].toCharArray();
chars[0] -= 32;
newStrings.add(new StringBuilder(String.valueOf(chars)));
}
StringBuilder sb = new StringBuilder();
for (StringBuilder newString : newStrings) {
sb = sb.append(newString);
}
return String.valueOf(sb);
}
return null;
}
/**
* 生成测试数据
*/
public Map<String, Object> generateData(String salt) {
Map<String, Object> infoMap = new HashMap<>();
//类的一些信息,例如类名,包名,作者,时间戳之类的
infoMap.put("packageName", packageName);
infoMap.put("className", generateClassName(salt+"_md_cof_user_info_data"));
//信息少可以使用位于import javax.management.Attribute;包中是一个name-value为key键值对
//ArrayList<Attribute> fieldList = new ArrayList<>();
//信息较多,比如字段类型,名称,注释等信息,就只用Map了.
ArrayList<Map<String, String>> fieldList = new ArrayList<>();
HashMap<String, String> fieldInfoMap01 = new HashMap<>();
fieldInfoMap01.put("type", "String");
fieldInfoMap01.put("name", salt+"name");
fieldInfoMap01.put("note", "这是姓名");
fieldList.add(fieldInfoMap01);
HashMap<String, String> fieldInfoMap02 = new HashMap<>();
fieldInfoMap02.put("type", "Long");
fieldInfoMap02.put("name", salt+"id");
fieldInfoMap02.put("note", "这是编号");
fieldList.add(fieldInfoMap02);
HashMap<String, String> fieldInfoMap03 = new HashMap<>();
fieldInfoMap03.put("type", "Boolean");
fieldInfoMap03.put("name", salt+"flag");
fieldInfoMap03.put("note", "是否生效");
fieldList.add(fieldInfoMap03);
//字段信息统一放入map中
infoMap.put("fields", fieldList);
return infoMap;
}
}
文件模板
package ${packageName};
import java.util.Date;
import java.util.List;
import java.util.Map;
public class ${className} {
<#list fields as attr>
/**
* ${attr.note}
*/
private ${attr.type} ${attr.name};
</#list>
<#list fields as attr>
public void set${attr.name?cap_first}(${attr.type} ${attr.name}){
this.${attr.name} = ${attr.name};
}
public ${attr.type} get${attr.name?cap_first}(){
return this.${attr.name};
}
</#list>
}
压缩文件
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class FileCompressUtils {
private static final int BUFFER_SIZE = 2 * 1024;
private static final String FILE_END = ".zip";
/**
* 把字节流打成压缩包并输出到客户端浏览器中
*/
public static void downloadZipStream(HttpServletResponse response, List<Map<String, byte[]>> compressInfoList, String zipFileName) throws Exception {
response.reset(); //重置一下输出流
response.setCharacterEncoding("UTF-8");
response.setContentType("application/x-msdownload"); // 不同类型的文件对应不同的MIME类型
// 对文件名进行编码处理中文问题
zipFileName = new String(zipFileName.getBytes(), StandardCharsets.UTF_8);
// inline在浏览器中直接显示,不提示用户下载,attachment弹出对话框,提示用户进行下载保存本地,默认为inline方式
response.setHeader("Content-Disposition", "attachment;filename=" + zipFileName + FILE_END);
// --设置成这样可以不用保存在本地,再输出,通过response流输出,直接输出到客户端浏览器中。
ZipOutputStream zos = new ZipOutputStream(response.getOutputStream());
for (Map<String, byte[]> stringMap : compressInfoList) {
for (Map.Entry<String, byte[]> entry : stringMap.entrySet()) {
zos.putNextEntry(new ZipEntry(entry.getKey()));
zos.write(entry.getValue(), 0, entry.getValue().length);
}
}
zos.close();
}
public static void main(String[] args) throws Exception {
//将文件压缩到一个zip文件
ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("D://test//testZipFile.zip")));
File srcFile = new File("F:\\01小工具们\\Fish3.270221\\Fish-v327-0221");
toZip(srcFile,zos,true);
}
/**
* 把文件打成压缩包并输出到客户端浏览器中
*/
public static void downloadZipFiles(HttpServletResponse response, File srcFile, String zipFileName, boolean keepDirStructure) throws Exception {
response.reset(); //重置一下输出流
response.setCharacterEncoding("UTF-8");
response.setContentType("application/x-msdownload"); // 不同类型的文件对应不同的MIME类型
// 对文件名进行编码处理中文问题
zipFileName = new String(zipFileName.getBytes(), StandardCharsets.UTF_8);
// inline在浏览器中直接显示,不提示用户下载,attachment弹出对话框,提示用户进行下载保存本地,默认为inline方式
response.setHeader("Content-Disposition", "attachment;filename=" + zipFileName + FILE_END);
// --设置成这样可以不用保存在本地,再输出,通过response流输出,直接输出到客户端浏览器中。
ZipOutputStream zos = new ZipOutputStream(response.getOutputStream());
toZip(srcFile, zos, keepDirStructure);
}
/**
* 压缩文件或是文件夹为一个zip包
*
* @param srcFile 要压缩的文件或文件夹
* @param zos
* @param keepDirStructure
* @throws RuntimeException
*/
public static void toZip(File srcFile, ZipOutputStream zos, boolean keepDirStructure) throws Exception {
if (!srcFile.isFile()) {
//若路径是一个文件夹,则压缩文件夹下的所有文件,同时保持原文件结构
File[] files = srcFile.listFiles();
for (File file : files) {
compress(file, zos, file.getName(), keepDirStructure);
}
} else {
//如果路径是一个文件,则直接生成一个压缩包
compress(srcFile, zos, srcFile.getName(), keepDirStructure);
}
zos.close();
System.out.println("压缩完成");
}
/**
* 主要的递归压缩方法
*
* @param sourceFile 源文件
* @param zos zip输出流
* @param name 压缩后的名称
* @param KeepDirStructure 是否保留原来的目录结构,true:保留目录结构;
* false:所有文件跑到压缩包根目录下(注意:不保留目录结构可能会出现同名文件,会压缩失败)
*/
private static void compress(File sourceFile, ZipOutputStream zos, String name, boolean KeepDirStructure) throws Exception {
byte[] buf = new byte[BUFFER_SIZE];
if (sourceFile.isFile()) {
// 向zip输出流中添加一个zip实体,构造器中name为zip实体的文件的名字
zos.putNextEntry(new ZipEntry(name));
// copy文件到zip输出流中
int len;
FileInputStream in = new FileInputStream(sourceFile);
while ((len = in.read(buf)) != -1) {
zos.write(buf, 0, len);
}
// Complete the entry
zos.closeEntry();
in.close();
} else {
File[] listFiles = sourceFile.listFiles();
if (listFiles == null || listFiles.length == 0) {
// 需要保留原来的文件结构时,需要对空文件夹进行处理
if (KeepDirStructure) {
// 空文件夹的处理
zos.putNextEntry(new ZipEntry(name + "/"));
// 没有文件,不需要文件的copy
zos.closeEntry();
}
} else {
for (File file : listFiles) {
// 判断是否需要保留原来的文件结构
if (KeepDirStructure) {
// 注意:file.getName()前面需要带上父文件夹的名字加一斜杠,
// 不然最后压缩包中就不能保留原来的文件结构,即:所有文件都跑到压缩包根目录下了
compress(file, zos, name + "/" + file.getName(), KeepDirStructure);
} else {
compress(file, zos, file.getName(), KeepDirStructure);
}
}
}
}
}
}
测试类
@RestController
public class FileZipDownloadController {
@Autowired
private FreeMarkerUtils freeMarkerUtils;
private String zipFileName = "testZipFileName";
@RequestMapping("/download")
public void downloadZipFile(HttpServletResponse response) {
try {
freeMarkerUtils.generateJavaCodeFile2Client(response,zipFileName);
} catch (Exception e) {
e.printStackTrace();
}
}
}