从安卓到后台开发,自己心有体会,一个项目后台与前端联调接口,如果没有一个接口API文档,实在是不好联调,虽然有Swgger之类的框架可以用,但是总觉得Swgger嵌入好麻烦,不过功能确实很强大,我也是很喜欢这个框架,但是我还是想嵌入能简单点,而且想做一个自己的接口API文档框架。
正是因为有了这个想法,所以开始了我的实施,当然,功能肯定没有Swgger那个强大。。。
下面介绍我的框架的介绍,我也直接将源码贴出来,通过源码去介绍我的功能。
框架目录架构:
总体来说,这个框架很简单,主要就是利用bin.webimp.annotation.*的所有自定义注解,通过bin.webimp.WebImpHtml来进行生成网页代码。
源码与功能介绍
BinUrlController
package bin.webimp.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Contoller的标识
* @author 超Bin
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BinUrlController {
/**
* controller的根路径
*/
String url() default "";
/**
* controller的简要描述
*/
String desc() default "";
}
webHtmlImp通过BinUrlController去打印出对应Controller的根路径和简单描述。如果url为空,将引用其类名(首字母小写)。
BinUrlMethod
package bin.webimp.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* Controller的接口方法标识
* @author 超Bin
*
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface BinUrlMethod {
/**
* 接口方法路径
*/
String action() default "";
/**
* 接口方法参数
*/
BinUrlParam[] binUrlParams();
/**
* 接口方法描述
*/
String desc();
/**
* 接口方法参数映射类
*/
Class<?>[] mappedClass() default {};
}
webHtmlImp通过BinUrlMethod进行对Controller的接口方法映射,打印出接口方法路径,接口方法需要传的参数,接口方法的描述。mappedClass属性的用法是将普通JavaBean的属性映射成接口参数答应出来,毕竟接口参数很有可能就是业务层的普通JavaBean,通过映射这个JavaBean可以减轻后台程序员的工作负担。
BinUrlParam
package bin.webimp.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 接口参数
* @author 超Bin
*
*/
@Target({ElementType.METHOD,ElementType.FIELD,ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface BinUrlParam {
/**
* 参数名
*/
String key() default "";
/**
* 参数类型,没有指定默认使用String
*/
Class<?> type() default String.class;
/**
* 参数描述
*/
String desc() default "";
/**
* 参数的子参数
*/
BinUrlParamChild[] paramChilds() default {};
/**
* 参数的子参数映射类
*/
Class<?>[] mappedClass() default {};
}
webHtmlImpl通过BinUrlParam来打印出接口参数,参数类型,参数描述,参数的子参数,这里为啥会出现有参数的子参数,因为在平时做接口时候,经常是一个参数是传一串Json字符串,但是Json字符串接口也是键值对形式,所以做对一个参数的子参数有利于应对这种情况的问题,mappedClass属性与BinUrlMethod的mappedClass作用类似,区别在与该mappedClass映射的是参数的子参数.如果参数是必须,可以在参数名前加上 * 号,webHtmlImpl会对其参数标注为比必填参数。
BinUrlParamChild
package bin.webimp.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 接口参数的子参数
* @author 超Bin
*
*/
@Target({ElementType.FIELD,ElementType.METHOD,ElementType.LOCAL_VARIABLE})
@Retention(RetentionPolicy.RUNTIME)
public @interface BinUrlParamChild {
/**
* 参数名
*/
String key();
/**
* 参数类型
*/
Class<?> type() default String.class;
/**
* 参数描述
*/
String desc() default "";
}
WebHtmlImp通过BinUrlParamChild打印出参数的子参数的参数名,参数类型,参数描述
BinUrlMode
package bin.webimp.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 接口参数的映射类,通过反射自动生成接口参数,没有设置该类注解时,<br/>
* 或者没有设置注解中的mayWary方法,默认使用类书
* @author 超Bin
*
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface BinUrlMode {
/**
* 反射方式————类属性
*/
final static int MAP_FIELD=1;
/**
* 反射方式————get方法
*/
final static int MAP_GET_METHOD=2;
/**
* 反射方式————注解属性
*/
final static int MAP_ANNOTATION=3;
/**
* 指定反射方式,默认使用类属性反射
*/
int mapWay() default MAP_FIELD;
}
webHtmlImp通过BinUrlMode的注解去选择使用什么样的反射方式去获取接口参数,这里有得选择的方式是 当前类的所有属性,当前类的所有get方法(类方法名开头为get的方法),当前类的注解属性。
BinUrlModelKey
package bin.webimp.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 接口参数映射类的 属性注解或方法注解
* @author 超Bin
*
*/
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BinUrlModelKey {
/**
* 参数名
*/
String key() default "";
/**
* 参数描述
*/
String desc() default "";
}
如果BinUrlMode的选择的放射方式是通过注解形式,webHtmlImpl将通过BinUrlModelKey进行获取参数信息。
BinUrlModelNoKey
package bin.webimp.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 接口参数映射类的 属性注解或方法,表示忽略该属性或方法
* @author 超Bin
*
*/
@Target({ElementType.FIELD,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface BinUrlModelNoKey {
}
考虑到有些JavaBean上的属性或者get方法并不想成为接口参数,通过BinUrlModeNoKey可以让BinHtmlImpl过滤掉这些不需要的属性或者get方法。
BinHtmlImpl
package bin.webimp;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import bin.webimp.annotation.BinUrlController;
import bin.webimp.annotation.BinUrlMethod;
import bin.webimp.annotation.BinUrlMode;
import bin.webimp.annotation.BinUrlModelKey;
import bin.webimp.annotation.BinUrlModelNoKey;
import bin.webimp.annotation.BinUrlParam;
import bin.webimp.annotation.BinUrlParamChild;
/**
* 生成项目接口的api文档 类
* @author 超Bin
*
*/
public class WebImpHtml {
final String sqliteFlag = "\r\n";
final String SPACE_HTML = " ";
String mParamHtml = null;
/**
* 生成项目接口的api文档
* @param controllerList 带 @link(bin.webimp.annotation.BinUrlController)的Controller
* @return 网页html代码
*/
public String genHtml(Class<?>... controllerList) {
StringBuilder sql = new StringBuilder();
appendln(sql, "<!DOCTYPE html>");
appendln(sql, "<html>");
appendln(sql, "<head>");
appendln(sql, "<title></title>");
appendln(sql, "<style type=\"text/css\">");
appendln(sql, "*{font-family: 黑体}");
appendln(sql, "li{margin-top: 5px;}");
appendln(
sql,
".controller{background-color: blue;font-size: 18sp;color: white;margin-top: 10px;padding: 10px;font-weight: bold;}");
appendln(
sql,
".url{background-color: white;font-size: 18sp;color: black;border-bottom: 2px solid gray;padding-left: 10px;padding: 10px;}");
appendln(sql, ".url:hover{color: blue;cursor: pointer;}");
appendln(
sql,
".methodList{background-color: white;font-size: 18sp;color: black;border: 2px solid gray;}");
appendln(sql, ".divParam{display: none;padding: 5px}");
appendln(sql, ".spanRequired{color:#EE7942;}");
appendln(sql, "</style>");
appendln(sql, "<script type=\"text/javascript\">");
appendln(
sql,
"function taggerDivParam(pl_id) {let pl= pl_id.nextElementSibling;let style=window.getComputedStyle(pl);let disply=pl.style.display;pl.style.setProperty('display',disply=='none'||disply==''?'inline':'none');}");
appendln(sql, "</script>");
appendln(sql, "</head>");
appendln(sql, "<body>");
appendln(sql, "<ul>");
for (Class<?> controllerCls : controllerList) {
// 根接口
BinUrlController binUrlController = controllerCls
.getAnnotation(BinUrlController.class);
String binControllerDesc = binUrlController.desc();
String binControllerUrl = binUrlController.url();
binControllerUrl = isStrEmpty(binControllerUrl) ? "/"
+ toLowerCaseFirstOne(controllerCls.getSimpleName())
: binControllerUrl;
appendln(sql, "<li>");
appendln(sql, String.format("<p class='controller'>%s(%s)</p> ",
binControllerUrl, binControllerDesc));
// 接口方法
Method[] methods = controllerCls.getMethods();
appendln(sql, "<ul>");
for (Method method : methods) {
if (!method.isAnnotationPresent(BinUrlMethod.class))
continue;
BinUrlMethod binUrlMethod = method
.getAnnotation(BinUrlMethod.class);
String binMethodDesc = binUrlMethod.desc();
String binMethodAction = binUrlMethod.action();
binMethodAction = isStrEmpty(binMethodAction) ? method
.getName() : binMethodAction;
appendln(sql, "<li class='methodList'>");
appendln(sql,
"<div class= 'url' οnclick='taggerDivParam(this)'>");
appendln(sql, String.format("<span>%s %s (%s) </span>",
binMethodAction, SPACE_HTML, binMethodDesc));
appendln(sql, "</div>");
// 接口参数
appendln(sql, "<div class='divParam' >");
appendln(sql, "<span>参数列表</span>");
appendln(sql, "<ul>");
BinUrlParam[] binUrlParams = binUrlMethod.binUrlParams();
if (mParamHtml == null)
mParamHtml = "<li><span>%s %s (%s) %s -> %s</span></li>";
for (BinUrlParam binUrlParam : binUrlParams) {
String key = setkeyStyleWhenRequire(binUrlParam.key());
Class<?> typeCls = binUrlParam.type();
String desc = binUrlParam.desc();
if (checkBaseType(typeCls)) {
appendln(sql, String.format(mParamHtml, key,
SPACE_HTML, desc, SPACE_HTML,
typeCls.getSimpleName()));
} else {
appendln(sql, "<li>");
appendln(sql, String.format(
"<span>%s %s (%s) %s -> %s:</span>", key,
SPACE_HTML, desc, SPACE_HTML, SPACE_HTML,
typeCls.getSimpleName()));
appendln(sql, "<ul>");
// 类映射
Class<?>[] binPClsArray = binUrlParam.mappedClass();
mappCls(sql, binPClsArray);
// BinUrlParamChild
BinUrlParamChild[] binUrlParamChilds = binUrlParam
.paramChilds();
for (BinUrlParamChild binUrlParamChild : binUrlParamChilds) {
String bChildKey = setkeyStyleWhenRequire(binUrlParamChild.key());
String bChildType = binUrlParamChild.type()
.getSimpleName();
String bChildDesc = binUrlParamChild.desc();
appendln(sql, String.format(mParamHtml, bChildKey,
SPACE_HTML, bChildDesc, SPACE_HTML,
bChildType));
}
appendln(sql, "</ul>");
appendln(sql, "</li>");
}
}
Class<?>[] methodClsArray = binUrlMethod.mappedClass();
mappCls(sql, methodClsArray);
appendln(sql, "</ul>");
appendln(sql, "</div>");
appendln(sql, "</li>");
}
appendln(sql, "</ul>");
appendln(sql, "</li>");
}
appendln(sql, "</ul>");
appendln(sql, "</body>");
appendln(sql, "</html>");
// System.out.println(sql);
return sql.toString();
}
private void mappCls(StringBuilder sql, Class<?>[] clsArray) {
boolean lengthFlag = false;
String formKeyStr = "%s_%s";
if (clsArray.length > 1)
lengthFlag = true;
for (Class<?> binPCls : clsArray) {
String modeName = "";
if (lengthFlag)
modeName = toLowerCaseFirstOne(binPCls.getSimpleName());
BinUrlMode binUrlMode = binPCls.getAnnotation(BinUrlMode.class);
if (binUrlMode == null
|| binUrlMode.mapWay() == BinUrlMode.MAP_FIELD) {
Field[] fields = binPCls.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(BinUrlModelNoKey.class))
continue;
String fName = lengthFlag ? String.format(formKeyStr,
modeName, field.getName()) : field.getName();
String fType = field.getType().getSimpleName();
appendln(sql, String.format(mParamHtml, fName, SPACE_HTML,
SPACE_HTML, SPACE_HTML, fType));
}
} else {
switch (binUrlMode.mapWay()) {
case BinUrlMode.MAP_ANNOTATION:
Field[] fields = binPCls.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(BinUrlModelNoKey.class))
continue;
BinUrlModelKey binUrlModelKey = field
.getAnnotation(BinUrlModelKey.class);
String fName = isStrEmpty(binUrlModelKey.key()) ? lengthFlag ? String
.format(formKeyStr, modeName, field.getName())
: field.getName()
: lengthFlag ? String.format(formKeyStr,
modeName, binUrlModelKey.key())
: binUrlModelKey.key();
fName=setkeyStyleWhenRequire(fName);
String fType = field.getType().getSimpleName();
String fDesc = binUrlModelKey.desc();
appendln(sql, String.format(mParamHtml, fName,
SPACE_HTML, fDesc, SPACE_HTML, fType));
}
break;
case BinUrlMode.MAP_GET_METHOD:
Method[] modeMethods = binPCls.getMethods();
for (Method modeMethod : modeMethods) {
if (modeMethod
.isAnnotationPresent(BinUrlModelNoKey.class))
continue;
String mName = modeMethod.getName();
String nameGet = mName.substring(0, 3);
if (nameGet.equals("get")) {
String mkey = lengthFlag ? String.format(
formKeyStr, modeName, mName.substring(3))
: mName.substring(3);
String mType = modeMethod.getReturnType()
.getSimpleName();
appendln(sql, String.format(mParamHtml, mkey,
SPACE_HTML, SPACE_HTML, SPACE_HTML, mType));
}
}
break;
}
}
}
}
/**
* 添加回车
*/
private void appendln(StringBuilder sql, String sqlFrag) {
sql.append(sqlFrag).append("\r\n");
}
/**
* 判断是否为空
*/
private boolean isStrEmpty(String str) {
return str == null || str.equals("");
}
/**
* 首字母转小写
*/
private String toLowerCaseFirstOne(String s) {
if (Character.isLowerCase(s.charAt(0)))
return s;
else
return new StringBuilder()
.append(Character.toLowerCase(s.charAt(0)))
.append(s.substring(1)).toString();
}
/**
* 判断是否是基本java类型
*/
private boolean checkBaseType(Class<?> cls) {
return cls == Integer.class || cls == Double.class || cls == Long.class
|| cls == Float.class || cls == boolean.class
|| cls == String.class || cls == Character.class;
}
/**
* 设置参数样式为必填颜色,当时必填参数时
*/
private String setkeyStyleWhenRequire(String key){
return key=key.indexOf("*")!=-1?String.format("<span class='spanRequired'>%s</span>", key):key;
}
// public static void main(String[] args) {
// WebImpHtml wb = new WebImpHtml();
// String htmlStr = wb.genHtml(UserController.class);
// File file = new File("F:/binG/genHtml/gen.html");
// if (!file.getParentFile().exists()) {
// file.getParentFile().mkdirs();
// }
// OutputStream output = null;
// try {
// output = new FileOutputStream(file);
// output.write(htmlStr.getBytes());
//
// } catch (FileNotFoundException e) {
// e.printStackTrace();
// } catch (IOException e) {
// e.printStackTrace();
// } finally {
// if (output != null)
// try {
// output.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// }
}
这个类就是对上面所有注解的操作,该类我没有做什么单例模式之类的东西,因为我想该框架要适应各种项目的需求,所以这些东西还是让使用者自己去封装出来更好。该类其实只暴露出一个genHtml方法,参数是带BinUrlController注解的Controller类,这里我没有做通过搜索指定包路径去获取数据的功能,因为我认为不需要,或者之后我升级这个框架再做吧。方法返回的是这个html的源码,为啥是返回String类型,因为我想适应项目的各种需求,比如让接口文档网页转换文件,或者是直接在浏览器上出。输出文件的可以参照类上的main方法,如果是要在浏览器上看的:
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.http.HttpServletResponse;
import bin.webimp.WebImpHtml;
import cn.com.gzkit.oes.system.user.UserController;
import com.jfinal.aop.Clear;
import com.jfinal.core.Controller;
import com.jfinal.render.RenderException;
/**
*
* <p>
* Bin接口APi
* </p>
* @author czb
* @Date 2018年4月9日
* @Version
*
* @History
*/
public class BinImpController extends Controller{
@Clear
public void binImpApi(){
WebImpHtml webHtml=new WebImpHtml();
String html=webHtml.genHtml(UserController.class);
HttpServletResponse response = getResponse();
PrintWriter writer = null;
try {
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("text/html");
response.setCharacterEncoding("utf-8");
writer = response.getWriter();
writer.write( html);
writer.flush();
} catch (IOException e) {
throw new RenderException(e);
}
finally {
if (writer != null)
writer.close();
}
}
}
ps:该代码是在JFinal框架上的,直接复制粘贴会报错,请先对代码报错地方修改过后再引入。
Controller上的注解使用:
MapController
package cn.com.gzkit.oes.web;
import bin.webimp.annotation.BinUrlController;
import bin.webimp.annotation.BinUrlMethod;
import bin.webimp.annotation.BinUrlParam;
import bin.webimp.annotation.BinUrlParamChild;
import cn.com.gzkit.oes.common.config.MapBean;
import cn.com.gzkit.oes.common.model.TSUser;
import cn.com.gzkit.oes.service.MapService;
import com.alibaba.fastjson.JSONObject;
import com.jfinal.core.Controller;
import com.jfinal.plugin.redis.Redis;
/**
*
* <p>
* 地图 相关接口
* </p>
*
* @author czb
* @Date 2018年3月29日
* @Version
*
* @History
*/
@BinUrlController(desc="地图")
public class MapController extends Controller {
private final MapService mMapService = new MapService();
@BinUrlMethod(binUrlParams={},desc="测试",mappedClass=MapBean.class)
public void test(){
render("test");
}
@BinUrlMethod(binUrlParams={
@BinUrlParam(key="place",desc="场地",paramChilds={
@BinUrlParamChild(key="placeName",desc="场地名"),
@BinUrlParamChild(key="charger",desc="负责人")
},type=JSONObject.class)
},desc="地图场地经纬度列表")
public void getParentPlaceList() {
renderJson(mMapService.getParentPlaceList());
}
@BinUrlMethod(binUrlParams={
@BinUrlParam(desc="父级场地信息Id",key="*parentPlaceId"),
@BinUrlParam(desc="获取方式:(recent/today)",key="*getType")
},desc=" 获取父级场地信息")
public void getParentPlaceInfo(){
String parentPlaceId=getPara("parentPlaceId");
String getType=getPara("getType");
TSUser loginUser=Redis.use("token").get(getRequest().getHeader("token"));
renderJson(mMapService.getParentPlaceInfo(parentPlaceId,getType,loginUser));
}
}
MapBean
package cn.com.gzkit.oes.common.config;
import bin.webimp.annotation.BinUrlMode;
import bin.webimp.annotation.BinUrlModelNoKey;
@BinUrlMode(mapWay=BinUrlMode.MAP_FIELD)
public class MapBean {
private double lat;
private double lng;
private String address;
@BinUrlModelNoKey
private String ignore;
public double getLat() {
return lat;
}
public void setLat(double lat) {
this.lat = lat;
}
public double getLng() {
return lng;
}
public void setLng(double lng) {
this.lng = lng;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
BinImpController
package cn.com.gzkit.oes.web;
import bin.webimp.WebImpHtml;
import com.jfinal.aop.Clear;
import com.jfinal.core.Controller;
/**
*
* <p>
* Bin接口APi
* </p>
* @author czb
* @Date 2018年4月9日
* @Version
*
* @History
*/
public class BinImpController extends Controller{
@Clear
public void binImpApi(){
WebImpHtml webHtml=new WebImpHtml();
renderHtml(webHtml.genHtml(
MapController.class
));
}
}
网页效果:
框架下载路径:
https://download.csdn.net/download/qq_30321211/10482170