APP的介面版本管理 實現多版本共存
阿新 • • 發佈:2019-02-16
思路:使用者請求url ---> 攔截器攔截 ---> 轉發到真正處理類和方法 ---> 返回結果
url註解類
package com.jc.app.util.apiVersion; import java.lang.annotation.*; /** * 需要攔截的API介面方法 * Created by jasonzhu on 2016/11/28. */ @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ApiVersion { /** * 指定執行操作的類名 */ Class targetClass(); /** * 指定執行操作的方法名字首 */ String methodPreName() default ""; }
執行處理方法引數註解類
package com.jc.app.util.apiVersion; import java.lang.annotation.*; /** * 處理方法的引數註解 * Created by jasonzhu on 2016/11/30. */ @Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ApiParam { /** * 引數名 */ String value(); /** * 是否必須有值 預設必須有 */ boolean required() default true; /** * 值非必須時 如果未傳值 預設值 */ DefaultValueEnum defaultValue() default DefaultValueEnum.DEFAULT; }
預設值列舉
package com.jc.app.util.apiVersion; /** * 預設引數值 * Created by jasonzhu on 2016/11/30. */ public enum DefaultValueEnum { /** * 按照型別賦預設值 * String "" * Boolean false * Integer 0 * Long 0 * 其他 null */ DEFAULT, NULL, STRING_EMPTY, ZERO, FALSE, TRUE }
異常
package com.jc.app.util.apiVersion;
/**
* 版本控制異常
* Created by jasonzhu on 2016/11/30.
*/
public class ApiVersionException extends RuntimeException {
public ApiVersionException(String message) {
super(message);
}
public ApiVersionException(String message, Throwable cause) {
super(message, cause);
}
}
版本攔截器
package com.jc.app.util.apiVersion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.PrintWriter;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.Map;
/**
* 介面版本攔截器
* Created by jasonzhu on 2016/11/28.
*/
public class ApiVersionInterceptor extends HandlerInterceptorAdapter {
/**
* 介面版本引數名
*/
final String API_VERSION = "av";
final String STRING_DEFAULT = "";
final Integer INTEGER_DEFAULT = 0;
final Long LONG_DEFAULT = 0L;
final Boolean BOOLEAN_DEFAULT = false;
@Autowired
private ApplicationContext context;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod method = (HandlerMethod) handler;
ApiVersion apiVersion = method.getMethodAnnotation(ApiVersion.class);
//判斷是否納入介面版本控制
if (apiVersion == null) {
return true;
}
Class cls = apiVersion.targetClass();
Object o;
try {
o = context.getBean(cls);
} catch (Exception e) {
throw new ApiVersionException("指定的處理類必須納入spring的bean管理", e);
}
String preName = apiVersion.methodPreName();
if (preName == null || preName.trim().isEmpty()) {
preName = method.getMethod().getName();
}
//介面版本號
String av = "1";
//引數列表
Map<String, String[]> requestParam = request.getParameterMap();
if (requestParam.get(API_VERSION) != null) {
av = requestParam.get(API_VERSION)[0];
}
Method[] methods = cls.getMethods();
if (methods == null) {
writeMsg(response, "未找到響應方法");
return false;
}
Method targetMethod = null;
//找到指定的處理方法
for (Method me : methods) {
if (me.getName().equals(preName + av)) {
targetMethod = me;
break;
}
}
if (targetMethod == null) {
writeMsg(response, "非法請求");
return false;
}
if (!targetMethod.getReturnType().equals(String.class)) {
throw new ApiVersionException("響應方法返回型別必須為String :" + targetMethod.getName());
}
//獲得方法的引數
Class<?>[] paramTypes = targetMethod.getParameterTypes();
Integer paramLength = paramTypes.length;
//調動方法的引數
Object[] paramList = new Object[paramLength];
Annotation[][] annotationss = targetMethod.getParameterAnnotations();
//總註解引數個數
for (int i = 0; i < annotationss.length; i++) {
Annotation[] annotations = annotationss[i];
if (annotations.length < 1)
throw new ApiVersionException("存在未新增@ApiParam註解引數的方法 :" + targetMethod.getName());
//是否存在ApiParam註解
boolean hasAnn = false;
for (int j = 0; j < annotations.length; j++) {
Annotation annotation = annotations[j];
if (annotation instanceof ApiParam) {
//為引數賦值
paramList[i] = getParam(requestParam, (ApiParam) annotation, paramTypes[i]);
hasAnn = true;
break;
}
}
if (!hasAnn)
throw new ApiVersionException("存在未新增@ApiParam註解引數的方法 :" + targetMethod.getName());
}
//反射方法呼叫
String result = (String) targetMethod.invoke(o, paramList);
writeMsg(response, result);
return false;
}
/**
* 輸出內容
*
* @param response
* @param msg
* @throws Exception
*/
private void writeMsg(HttpServletResponse response, String msg) throws Exception {
response.setContentType("application/json;charset=UTF-8");
PrintWriter out = response.getWriter();
out.println(msg);
out.flush();
out.close();
}
/**
* 獲得引數的值
*
* @param requestParam 請求引數
* @param apiParam 引數上註解
* @param paramType 引數型別
* @return 引數值
*/
private Object getParam(Map<String, String[]> requestParam, ApiParam apiParam, Class<?> paramType) {
String reqName = apiParam.value();
//如果有該引數
if (requestParam.get(reqName) != null) {
Object o = requestParam.get(reqName)[0];
try {
if (paramType.equals(String.class)) {
return String.valueOf(o);
}
if (paramType.equals(Boolean.class) || "boolean".equals(paramType.getName())) {
return Boolean.parseBoolean(o.toString());
}
if (paramType.equals(Integer.class) || "int".equals(paramType.getName())) {
return Integer.parseInt(o.toString());
}
if (paramType.equals(Long.class) || "long".equals(paramType.getName())) {
return Long.parseLong(o.toString());
}
if (paramType.equals(BigDecimal.class)) {
return new BigDecimal(o.toString());
}
} catch (Exception e) {
throw new ApiVersionException("引數格式化失敗 :" + reqName, e);
}
return o;
}
//如果引數必須
if (apiParam.required()) {
throw new ApiVersionException("缺少引數 :" + reqName);
}
//返回預設值
DefaultValueEnum dfe = apiParam.defaultValue();
if (DefaultValueEnum.DEFAULT.equals(dfe)) {
if (paramType.equals(String.class)) {
return STRING_DEFAULT;
}
if (paramType.equals(Boolean.class) || "boolean".equals(paramType.getName())) {
return BOOLEAN_DEFAULT;
}
if (paramType.equals(Integer.class) || "int".equals(paramType.getName())) {
return INTEGER_DEFAULT;
}
if (paramType.equals(Long.class) || "long".equals(paramType.getName())) {
return LONG_DEFAULT;
}
} else if (DefaultValueEnum.NULL.equals(dfe)) {
return null;
} else if (DefaultValueEnum.STRING_EMPTY.equals(dfe)) {
return "";
} else if (DefaultValueEnum.ZERO.equals(dfe)) {
return 0;
} else if (DefaultValueEnum.FALSE.equals(dfe)) {
return false;
} else if (DefaultValueEnum.TRUE.equals(dfe)) {
return true;
}
return null;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
}
}
spring mvc中增加的配置
<!-- 版本控制攔截器 -->
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**" />
<bean class="com.jc.app.util.apiVersion.ApiVersionInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>
測試類
controller
package com.jc.app.controller;
import com.jc.app.util.apiVersion.ApiVersion;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 測試版本控制介面
* Created by jasonzhu on 2016/12/1.
*/
@RestController
public class TestApiVersionController {
@ApiVersion(targetClass = TestApiVersionDo.class)
@RequestMapping("/api/test")
public void test(){}
@ApiVersion(targetClass = TestApiVersionDo.class,methodPreName = "test")
@RequestMapping("/api/testno")
public void testNo(){}
}
真正執行方法的類
package com.jc.app.controller;
import com.jc.app.util.apiVersion.ApiParam;
import com.jc.app.util.apiVersion.DefaultValueEnum;
import org.springframework.stereotype.Component;
import java.math.BigDecimal;
/**
* 版本攔截器之後真正執行的方法
* Created by jasonzhu on 2016/12/1.
*/
@Component
public class TestApiVersionDo {
public void test1(){}
private String test2(){return "呼叫成功 沒有引數";}
public String test3(){
return "呼叫成功 沒有引數";
}
public String test4(@ApiParam("a") String app){
return "呼叫成功 一個引數 app:"+app;
}
public String test5(@ApiParam("av") Integer av,@ApiParam("a") String app,@ApiParam(value = "b",required = false) String b){
return "呼叫成功 三個引數 app:"+app+" av:"+av+" b:"+b;
}
public String test6(@ApiParam("amount") BigDecimal amount, @ApiParam(value = "l",required = false) long l, @ApiParam(value = "b",required = false,defaultValue = DefaultValueEnum.TRUE) Boolean b){
return "呼叫成功 三個引數 amount:"+amount+" l:"+l+" b:"+b;
}
}