前言
由于笔者所在的团队属于最底层的服务提供者,所以我们日常的工作大都是基于业务提供对外接口api,但是因为我们项目年代比较久远,所以没有引入swagger之类的api工具。因此我们在封装完业务接口后都会提供一份完整的接口说明word文档出来,文档包括接口的出入参参数说明,还有参数类型,以及其他规范化的东西。每次写完接口写这些文档都是一个很令人生厌的事情,因此考虑做一个api生成工具。
思路
文档生成因为都是固定化的格式,因此准备使用模板工具,市面上的模板工具有很多,比如JSP、freemarker、velocit等。笔者选择freemarker模板引擎来实现文档的生成。
1、首先将api的word模板制作成freeMarker可以识别的模板。
2、使用占位符将模板中要动态生成的内容替换掉。
3、生成文件
代码实现
package pers.cz.freemarker;
import com.alibaba.fastjson.JSON;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.*;
/**
* @program: PostGirl-panent
* @description: FreeMarker
* @author: Cheng Zhi
* @create: 2022-04-09 15:47
**/
public class FreeMarkerUtils {
public static final String OUTPUT_FOLDER = "./api/";
/***
* 创建模板对象
* @param path 模板文件所在的位置
* @param ftl 生成的文件名
* @return
* @throws Exception
*/
public static Template loadTemplate(String path, String ftl) throws Exception{
// 第一步:创建一个Configuration对象,直接new一个对象。构造方法的参数就是freemarker对于的版本号。
Configuration configuration = new Configuration(Configuration.getVersion());
// 第二步:设置模板文件所在的路径。
configuration.setDirectoryForTemplateLoading(new File(path));
// 第三步:设置模板文件使用的字符集。一般就是utf-8.
configuration.setDefaultEncoding("utf-8");
// 第四步:加载一个模板,创建一个模板对象。
Template template = configuration.getTemplate(ftl);
return template;
}
/**
* 文件生成
* @param dataModel
* @param template
* @param fileName
* @throws IOException
* @throws TemplateException
*/
public static File writeFile(Map dataModel, Template template, String fileName) throws IOException, TemplateException {
try {
//文件夹不存在的话进行创建
File reportDir= new File(OUTPUT_FOLDER );
if(!reportDir.exists()&& !reportDir .isDirectory()){
reportDir.mkdir();
}
File file = new File(OUTPUT_FOLDER + "/" + fileName);
Writer out = new FileWriter(file);
// 调用模板对象的process方法输出文件。
template.process(dataModel, out);
// 关闭流。
out.close();
return file;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 解析json为Map
* @param json
* @return
*/
public static Map analyzeJson(String json) {
Map<String, Object> map = (Map) JSON.parse(json);
Map<String, Set<Field>> myMap = new HashMap<>();
Stack list = new Stack();
// 里面存放 key-List list<FirstInput>
iteratorMap(map, myMap, list);
return myMap;
}
/**
* 迭代解析Map获取类型
* @param map
* @param myMap
* @param list
*/
private static void iteratorMap(Map<String, Object> map, Map<String, Set<Field>> myMap, Stack<String> list) {
for (String key : map.keySet()) {
if (key.equals("success") ) {
continue;
}
if (map.get(key) instanceof Map) {
if (list.size() > 0) {
String s = list.peek();
Field field = new Field(key, "Map", key+"描述");
myMap.get(s).add(field);
}
if (myMap.get(key) == null) {
myMap.put(key,new HashSet<>());
list.push(key);
}
iteratorMap((Map<String, Object>) map.get(key), myMap, list);
if (list.size() > 0) {
list.pop();
}
}
if (map.get(key) instanceof List) {
if (list.size() > 0) {
String s = list.peek();
Field field = new Field(key, "List", key+"描述");
myMap.get(s).add(field);
}
if (myMap.get(key) == null) {
myMap.put(key,new HashSet<Field>());
list.push(key);
}
Set<String> strings = myMap.keySet();
Iterator iterator = ((List) map.get(key)).iterator();
while (iterator.hasNext()) {
try {
iteratorMap((Map<String, Object>) iterator.next(), myMap, list);
} catch (Exception e) {
}
}
if (list.size() > 0) {
list.pop();
}
}
if (map.get(key) instanceof String) {
if (list.size() > 0) {
String s = list.peek();
Field firstInput = new Field(key, "String", key+"描述");
myMap.get(s).add(firstInput);
}
}else if (map.get(key) instanceof Long) {
if (list.size() > 0) {
String s = list.peek();
Field firstInput = new Field(key, "Long", key+"描述");
myMap.get(s).add(firstInput);
}
}else if (map.get(key) instanceof Integer) {
if (list.size() > 0) {
String s = list.peek();
Field firstInput = new Field(key, "Integer", key+"描述");
myMap.get(s).add(firstInput);
}
}else if (map.get(key) instanceof Boolean) {
if (list.size() > 0) {
String s = list.peek();
Field firstInput = new Field(key, "Boolean", key+"描述");
myMap.get(s).add(firstInput);
}
} else if (map.get(key) instanceof Double){
if (list.size() > 0) {
String s = list.peek();
Field firstInput = new Field(key, "Double", key+"描述");
myMap.get(s).add(firstInput);
}
}
}
}
}
package pers.cz.freemarker;
/**
* @program: PostGirl-panent
* @description: Field
* @author: Cheng Zhi
* @create: 2022-04-09 15:52
**/
public class Field {
private String param;
private String paramType;
private String paramDes;
public Field(String param, String paramType, String paramDes) {
// 将大于号和小于号替换为 < > & '"
this.param = param.replace("<","<").replace(">",">").replace("&","&");
this.paramType = paramType.replace("<","<").replace(">",">").replace("&","&");
this.paramDes = paramDes.replace("<","<").replace(">",">").replace("&","&");
}
public String getParam() {
return param;
}
public void setParam(String param) {
this.param = param;
}
public String getParamType() {
return paramType;
}
public void setParamType(String paramType) {
this.paramType = paramType;
}
public String getParamDes() {
return paramDes;
}
public void setParamDes(String paramDes) {
this.paramDes = paramDes;
}
@Override
public int hashCode() {
return this.param.hashCode();
}
@Override
public boolean equals(Object obj) {
Field object = (Field) obj;
return this.param.equals(object.param);
}
}
使用:
package com.cz.freeMarker;
import freemarker.template.Template;
import java.io.File;
import java.io.FileWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @program: Reids
* @description: DocTest
* @author: Cheng Zhi
* @create: 2022-04-06 17:30
**/
public class DocTest {
public static void main(String[] args) {
try {
// 模板文件所在的位置 ControllerBuilder.class.getResource(templatePath).getPath()
String path = "D:\Java\Project\Reids\src\main\resources\templates\";
//Template template = FreeMarkerUtils.loadTemplate(path, "testDoc.xml");
Template template = FreeMarkerUtils.loadTemplate(path, "ImsApiTemplate.xml");
// 创建文件夹
/* File file = new File(savePath);
if(!file.exists()){
file.mkdirs();
}*/
Map<String,Object> dataMap = new HashMap<>();
dataMap.put("interfaceNameCh","消费流水查询");
dataMap.put("busiCode","7761");
dataMap.put("interfaceDec","消费流水查询");
dataMap.put("interfaceNameEn","do_queryBillExtNew");
dataMap.put("externalMethod","pers.cz.test.test1.testbusiness.billinvoiceExt.AsyncZzsfpRepealInvoiceBusiness.handleData(InvDataHandle)");
dataMap.put("interfaceNameEn","do_queryBillExtNew");
List<String> list = new ArrayList<>();
list.add("ext1");
list.add("ext2");
dataMap.put("firstInput", list);
List<FirstInput> list2 = new ArrayList<>();
list2.add(new FirstInput("freeResourceList", "ListFreeResource", "银行帐户号信息"));
list2.add(new FirstInput("freeResourceList1", "ListFreeResource", "银行帐户号信息"));
list2.add(new FirstInput("freeResourceList2", "ListFreeResource", "银行帐户号信息"));
dataMap.put("FirstInput", list2);
AnalyzeInput analyzeInput = new AnalyzeInput();
String input = "{"Public":{"so_date":"2021-12-10 02:18:13","so_mode":100,"org_id":0,"op_id":100000678920,"step_id":1,"busi_nbr":7761,"so_nbr":"20211210021813036167"},"req":{"productOfferingId":"82769203"}}";
dataMap.put("titleMap", analyzeInput.analyzeMap(input));
File directory = new File(""); // 设定为当前文件夹
String absolutePath = directory.getCanonicalPath();
// 创建一个Writer对象,一般创建一FileWriter对象,指定生成的文件名。
Writer out = new FileWriter(new File(absolutePath + "/" + "myDoc.doc"));
// 调用模板对象的process方法输出文件。
template.process(dataMap, out);
// 关闭流。
out.close();
} catch (Exception e) {
System.out.println("异常" + e);
}
}
}
注意事项
word模板制作首先需要将.docx或者.doc文件另存为.xml结尾的文件,然后使用编辑器将xml文件打开,将要替换的内容使用占位符标记。最终效果如下: