打造轻量级接口API网页

从安卓到后台开发,自己心有体会,一个项目后台与前端联调接口,如果没有一个接口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

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值