cn.mapway.document.parser.SpringParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of api-tools-doc Show documentation
Show all versions of api-tools-doc Show documentation
auto gen doc from api with ui
The newest version!
package cn.mapway.document.parser;
import cn.mapway.document.annotation.*;
import cn.mapway.document.helper.Markdowns;
import cn.mapway.document.helper.Scans;
import cn.mapway.document.module.*;
import cn.mapway.document.resource.Version;
import org.nutz.castor.Castors;
import org.nutz.json.Json;
import org.nutz.json.JsonFormat;
import org.nutz.lang.Files;
import org.nutz.lang.Lang;
import org.nutz.lang.Mirror;
import org.nutz.lang.Strings;
import org.nutz.lang.born.BorningException;
import org.nutz.log.Logs;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
/**
* 解析Spring 注解.
*
* @author zhangjianshe @gmail.com
*/
public class SpringParser {
/**
* The Constant log.
*/
private final static org.nutz.log.Log log = Logs.getLog(SpringParser.class);
/**
* The m context.
*/
GenContext mContext;
/**
* 类深度信息.
*/
Deeps deeps;
/**
* The main method.
*
* @param args the arguments
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws InstantiationException the instantiation exception
*/
public static void main(String[] args)
throws IllegalArgumentException, IllegalAccessException, InstantiationException {
SpringParser p = new SpringParser();
ApiDoc doc = p.parse(new GenContext(), "cn.mapway.doc2.test");
System.out.println(Json.toJson(doc));
}
public static Field[] getAllFields(Class c) {
List fieldList = new ArrayList<>();
while (c != null) {
fieldList.addAll(new ArrayList<>(Arrays.asList(c.getDeclaredFields())));
c = c.getSuperclass();
}
Field[] fields = new Field[fieldList.size()];
fieldList.toArray(fields);
return fields;
}
/**
* 解析包中的类.
*
* @param context the context
* @param packageNames 包名
* @return the api doc
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws InstantiationException the instantiation exception
*/
public ApiDoc parse(GenContext context, String... packageNames)
throws IllegalArgumentException, IllegalAccessException, InstantiationException {
mContext = context;
if (packageNames.length == 0) {
return new ApiDoc();
}
List packageList = new ArrayList<>();
for (String pk : packageNames) {
String[] pks = Strings.split(pk, false, false, ',', ';');
for (int i = 0; i < pks.length; i++) {
String s = pks[i];
s = Strings.trim(s);
if (!Strings.isBlank(s)) {
packageList.add(s);
}
}
}
log.info("scan package:" + Json.toJson(packageList));
ArrayList> clzs = new ArrayList>();
for (String pk : packageList) {
Set> clz = scanPackage(pk);
clzs.addAll(clz);
}
ApiDoc doc = new ApiDoc();
doc.author = context.getAuthor();
doc.basePath = context.getBasepath();
doc.title = context.getDocTitle();
doc.wordUrl = context.getWordURL();
doc.summary = context.getSubtitle();
doc.domain = context.getDomain();
doc.copyright = context.getCopyright();
doc.apiVersion = context.getApiVersion();
doc.logo = context.getLogobase64();
doc.homeUrl = context.getHomeUrl();
doc.cssStyle = context.getCssStyle();
if (Strings.isEmpty(doc.apiVersion)) {
doc.apiVersion = "1.0";
}
doc.copyright = doc.copyright + "-[" + Version.VERSION + "@" + Version.TIMESTAMP + "]";
boolean hasRestController = false;
try {
Class.forName("org.springframework.web.bind.annotation.RestController");
hasRestController = true;
} catch (ClassNotFoundException e) {
log.warn("doc-api is running in a container which does not support SPRING Rest Controller");
hasRestController = false;
}
for (Class> clz : clzs) {
boolean isParseRestController =
hasRestController ?
(clz.getAnnotation(RestController.class) != null)
: false;
if (clz.getAnnotation(Controller.class) != null || isParseRestController) {
parseClass(doc, clz);
}
}
doc.root.name = doc.title;
doc.root.fullName = "/" + doc.title;
doc.root.summary = "";
doc.sort();
return doc;
}
private Set> scanPackage(String pk) {
Set> clzs = Scans.getClasses(pk);
return clzs;
}
/**
* 解析某个类.
*
* @param document the document
* @param clz the clz
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws InstantiationException the instantiation exception
*/
private void parseClass(ApiDoc document, Class> clz)
throws IllegalArgumentException, IllegalAccessException, InstantiationException {
Doc doc = clz.getAnnotation(Doc.class);
if (doc == null) {
return;
}
populateGroup(document, clz);
}
/**
* 填充Group信息.
*
* @param document the document
* @param c the c
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws InstantiationException the instantiation exception
*/
private void populateGroup(ApiDoc document, Class> c)
throws IllegalArgumentException, IllegalAccessException, InstantiationException {
// 类Doc
Doc doc = c.getAnnotation(Doc.class);
String group_base_path = doc.group();
Group apiGroup = document.findGroup(group_base_path);
apiGroup.summary += doc.desc().length() > 0 ? doc.desc() : "";
apiGroup.summary += parseRef(c, doc.refs());
apiGroup.order = doc.order();
String basepath = "";
RequestMapping rm = c.getAnnotation(RequestMapping.class);
if (rm != null) {
String[] paths = rm.value();
if (paths == null || paths.length == 0) {
} else {
basepath = paths[0];
}
}
Method[] methods = c.getDeclaredMethods();
List list = new ArrayList();
for (int i = 0; i < methods.length; i++) {
Method m = methods[i];
RequestMapping rm1 = m.getAnnotation(RequestMapping.class);
if (rm1 != null) {
list.add(m);
}
}
for (int i = 0; i < list.size(); i++) {
Method m = list.get(i);
Entry entry = handleMethod(c, document, group_base_path, m);
if (entry != null) {
entry.parentClassName = c.getName();
entry.relativePath = concatUrl(basepath, entry.relativePath);
entry.url = concatUrl(mContext.getBasepath(), entry.relativePath);
}
}
}
/**
* 合并两个URL部件 ,确保中间有 /
*
* @param url1
* @param url1
* @return
*/
private String concatUrl(String url1, String url2) {
if (Strings.isBlank(url1)) {
return url2;
}
if (Strings.isBlank(url2)) {
return url1;
}
if (url1.endsWith("/")) {
if (url2.startsWith("/")) {
return url1 + url2.substring(1);
} else {
return url1 + url2;
}
} else {
if (url2.startsWith("/")) {
return url1 + url2;
} else {
return url1 + "/" + url2;
}
}
}
/**
* 解析方法,生成APIentry.
*
* @param document the document
* @param group_base_path the group base path
* @param m the m
* @return the entry
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws InstantiationException the instantiation exception
*/
private Entry handleMethod(Class> clsType, ApiDoc document, String group_base_path, Method m)
throws IllegalArgumentException, IllegalAccessException, InstantiationException {
// 如果没有添加注解 就不输出这个接口的文档
Doc docAnnotation = m.getAnnotation(Doc.class);
if (null == docAnnotation) {
return null;
}
Entry e = new Entry();
RequestMapping rm = m.getAnnotation(RequestMapping.class);
if (rm != null) {
// TODO 增加对多路径的支持
String[] paths = rm.value();
if (paths == null || paths.length == 0) {
} else {
e.relativePath = paths[0];
}
// 处理请求方法 支持对多种调用方式 POST GET PUT DELETE 等
RequestMethod[] ms = rm.method();
if (ms != null) {
for (int i = 0; i < ms.length; i++) {
RequestMethod rm0 = ms[i];
e.invokeMethods.add(rm0.name());
}
}
}
if (e.invokeMethods.size() == 0) {
e.invokeMethods.add("GET");
}
if (e.relativePath.length() == 0) {
return null;
}
e.methodName = m.getName();
Class retClz = null;
boolean isReturnList = false;
if (docAnnotation != null) {
e.title = docAnnotation.value();
e.summary = docAnnotation.desc() == null ? "" : docAnnotation.desc();
e.summary += parseRef(clsType, docAnnotation.refs());
e.order = docAnnotation.order();
e.author = docAnnotation.author();
e.state = transState(docAnnotation.state());
e.apiStyle = transApiStyle(docAnnotation.style());
// 处理方法的标签.
if (docAnnotation.tags() != null) {
for (String tag : docAnnotation.tags()) {
e.tags.add(tag);
}
}
if (docAnnotation.retClazz() != null && docAnnotation.retClazz().length > 0) {
retClz = docAnnotation.retClazz()[0];
}
if (docAnnotation.isReturnList() != null && docAnnotation.isReturnList().length > 0) {
isReturnList = docAnnotation.isReturnList()[0];
}
}
Class>[] ps = m.getParameterTypes();
Class> out = (Class>) m.getReturnType();
int i = 0;
for (Class> clz : ps) {
String name = clz.getSimpleName();
String pname = "";
if (clz.getPackage() != null) {
pname = clz.getPackage().getName();
}
if (name.startsWith("Http") || pname.startsWith("org.")) {
continue;
} else {
Annotation[][] parameterAnnotations = m.getParameterAnnotations();
Annotation[] ass = parameterAnnotations[i];
// 处理每一个参数
PathVariable pathVariable = null;
RequestParam queryVariable = null;
RequestBody isRequestBody = null;
ApiField paraDoc = null;
Size stringConstrain = null;
NotNull nullConstrain = null;
Min minConstrain = null;
Max maxConstrain = null;
for (Annotation a : ass) {
if (a instanceof PathVariable) {
pathVariable = (PathVariable) a;
} else if (a instanceof RequestParam) {
queryVariable = (RequestParam) a;
} else if (a instanceof ApiField) {
paraDoc = (ApiField) a;
} else if (a instanceof RequestBody) {
isRequestBody = (RequestBody) a;
} else if (a instanceof Size) {
stringConstrain = (Size) a;
} else if (a instanceof NotNull) {
nullConstrain = (NotNull) a;
} else if (a instanceof Min) {
minConstrain = (Min) a;
} else if (a instanceof Max) {
maxConstrain = (Max) a;
}
}
ObjectInfo p = null;
try {
p = handleParameter(clz, name);
} catch (Exception exc) {
log.warn("handle exception " + exc.toString());
}
if (p == null) {
continue;
}
if (paraDoc != null) {
p.example = paraDoc.example();
p.manditary = paraDoc.mandidate();
p.title = paraDoc.value();
// 处理注释,从ref中获取
p.summary = parseRef(clsType, paraDoc.refs());
}
// 长度约束
if (stringConstrain != null) {
p.minLength = stringConstrain.min();
p.maxLength = stringConstrain.max();
}
if (minConstrain != null) {
p.min = minConstrain.value();
}
if (maxConstrain != null) {
p.max = maxConstrain.value();
}
if (nullConstrain != null) {
p.manditary = true;
}
if (pathVariable != null) {
// 路径参数
if (Strings.isBlank(pathVariable.value())) {
p.name = "未知";
} else {
p.name = pathVariable.value();
}
e.pathParas.add(p);
} else if (queryVariable != null) {
p.name = queryVariable.value();
e.queryParas.add(p);
} else if (isRequestBody != null || paraDoc != null) {
e.input.add(p);
}
i++;
}
}
// 处理返回值注解
ApiField returnDoc = m.getAnnotation(ApiField.class);
e.output = handleParameter(out, "out");
if (retClz != null) {//返回值是一个模板类
Map replaceList = new HashMap<>();
for (int j = 0; j < e.output.fields.size(); j++) {
ObjectInfo f = e.output.fields.get(j);
if (f.type.equals("Object")) {
ObjectInfo replaceObjectInfo = handleParameter(retClz, f.name);
replaceList.put(j, replaceObjectInfo);
}
}
for (Map.Entry entry : replaceList.entrySet()) {
if(isReturnList) {
ObjectInfo oi= entry.getValue();
oi.type="List<"+oi.type+">";
}
e.output.fields.set(entry.getKey(), entry.getValue());
}
}
if (returnDoc != null) {
// 类型 解释 例子
if (returnDoc.example() != null && returnDoc.example().length() > 0) {
e.output.example = returnDoc.example();
}
}
String group_path = group_base_path + docAnnotation.group();
Group apiGroup = document.findGroup(group_path);
apiGroup.entries.add(e);
return e;
}
/**
* 将枚举值输出到文档中
*
* @param fi
* @param enums
* @return
*/
private void parseCodes(ObjectInfo fi, Class>[] enums) {
//只处理枚举类型值
if (enums == null) {
return;
}
for (Class> clz : enums) {
if (clz.isEnum()) {
Object[] enumConstants = clz.getEnumConstants();
StringBuilder sb = new StringBuilder();
for (Object obj : enumConstants) {
fi.codes.add(new FieldCode("", obj.toString()));
}
}
}
}
/**
* 解析ref html.
*
* @param refs
* @return
*/
private String parseRef(Class> type, String[] refs) {
if (refs == null || refs.length == 0) {
// 如果没有引用描述页面,则不处理
return "";
}
StringBuilder sb = new StringBuilder();
for (String ref : refs) {
if (Strings.isBlank(ref)) {
//移除空字符串
continue;
}
boolean isMarkdown = Files.getSuffixName(ref).equalsIgnoreCase("md");
String desc = Scans.readResource(type.getPackage().getName(), ref);
desc = desc.replaceAll("\\$\\{PACKAGE\\}", type.getPackage().getName());
if (isMarkdown) {
//转换markdown to html
desc = translateMarkdown(desc);
sb.append("").append(desc).append("");
} else {
sb.append(desc);
}
}
return sb.toString();
}
/**
* 转换Markdown文件格式.
*
* @param desc
* @return
*/
private String translateMarkdown(String desc) {
if (Strings.isBlank(desc)) {
return "";
}
return Markdowns.get().translate(desc);
}
/**
* 转换API样式
*
* @param style
* @return
*/
private String transApiStyle(ApiStyle style) {
return style.name();
}
/**
* Trans state.
*
* @param state the state
* @return the string
*/
private String transState(DevelopmentState state) {
if (state == DevelopmentState.FINISH) {
return "已完成";
} else if (state == DevelopmentState.OBSOLETED) {
return "已废弃";
} else if (state == DevelopmentState.PROCESS) {
return "开发中";
} else if (state == DevelopmentState.UNSTART) {
return "未开发";
}
return "";
}
/**
* 处理参数.
*
* @param clz the clz
* @param name the name
* @return the object info
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws InstantiationException the instantiation exception
*/
private ObjectInfo handleParameter(Class> clz, String name)
throws IllegalArgumentException, IllegalAccessException, InstantiationException {
ObjectInfo p = new ObjectInfo();
Doc doc = clz.getAnnotation(Doc.class);
p.name = name == null ? clz.getSimpleName() : name;
p.title = doc == null ? "" : doc.value();
if (isPrimitive(clz)) {
p.type = clz.getSimpleName();
} else {
p.type = clz.getName();
}
String sum = doc == null ? "" : (Strings.isBlank(doc.desc()) ? doc.value() : doc.desc());
// 循环处理父类中的解释
Class> superclazz = clz.getSuperclass();
while (superclazz != null) {
Doc summary1 = superclazz.getAnnotation(Doc.class);
if (sum.length() > 0) {
sum += "
";
}
sum += summary1 == null ? "" : summary1.desc();
superclazz = superclazz.getSuperclass();
}
if (doc != null) {
p.summary = sum + parseRef(clz, doc.refs());
}
deeps = new Deeps();
deeps.push(clz.getName(), deeps.getLevel());
Object instance = null;
try {
instance = newInstance(clz);
} catch (Exception e) {
log.info(e.getMessage());
}
StringBuilder ignore = new StringBuilder();
ignore.append("^(");
for (Field f : getAllFields(clz)) {
ObjectInfo fld = handleField(instance, f);
if (fld != null) {
p.fields.add(fld);
} else {
if (ignore.length() > 2) {
ignore.append("|");
}
ignore.append(f.getName());
}
}
ignore.append(")$");
if (instance != null) {
p.json = Json.toJson(instance, JsonFormat.full().setLocked(ignore.toString()));
} else {
p.json = "{}";
}
return p;
}
/**
* 处理字段.
*
* @param instance the instance
* @param f the f
* @return the object info
* @throws IllegalArgumentException the illegal argument exception
* @throws IllegalAccessException the illegal access exception
* @throws InstantiationException the instantiation exception
*/
private ObjectInfo handleField(Object instance, Field f)
throws IllegalArgumentException, IllegalAccessException, InstantiationException {
if (instance == null) {
return null;
}
deeps.incLevel();
ApiField wf = f.getAnnotation(ApiField.class);
Annotation[] ass = f.getAnnotations();
if (wf != null) {
ObjectInfo fi = new ObjectInfo();
fi.manditary = wf.mandidate();
fi.title = wf.value();
fi.example = wf.example();
fi.name = f.getName();
fi.type = f.getType().getName();
// 处理返回代码
Codes codes = f.getAnnotation(Codes.class);
if (codes != null) {
for (Code code : codes.value()) {
FieldCode fc = new FieldCode(code.value(), code.desc());
fi.codes.add(fc);
}
}
//处理引用枚举值
parseCodes(fi, wf.codes());
// 处理字段约束
Size stringConstrain = null;
NotNull nullConstrain = null;
Min minConstrain = null;
Max maxConstrain = null;
for (Annotation a : ass) {
if (a instanceof Size) {
stringConstrain = (Size) a;
} else if (a instanceof NotNull) {
nullConstrain = (NotNull) a;
} else if (a instanceof Min) {
minConstrain = (Min) a;
} else if (a instanceof Max) {
maxConstrain = (Max) a;
}
}
// 长度约束
if (stringConstrain != null) {
fi.minLength = stringConstrain.min();
fi.maxLength = stringConstrain.max();
}
if (minConstrain != null) {
fi.min = minConstrain.value();
}
if (maxConstrain != null) {
fi.max = maxConstrain.value();
}
if (nullConstrain != null) {
fi.manditary = true;
}
// 记录类型的循环次数
deeps.push(f.getType().getName(), deeps.getLevel());
// 处理字段
if (isMultipartFile(f.getType())) {
//这个是上传文件对象
tacleMultipartFile(instance, f, wf, fi);
} else if (isPrimitive(f.getType())) {
// String Integer Boolean Double
tacklePrimitive(instance, f, wf, fi);
} else if (isList(f)) {
// List