1. 程式人生 > >APP的介面版本管理 實現多版本共存

APP的介面版本管理 實現多版本共存

思路:使用者請求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;
    }
}