美文网首页
springmvc的restful api 增加版本参数

springmvc的restful api 增加版本参数

作者: 有时右逝 | 来源:发表于2019-06-23 00:04 被阅读0次

前言

最近在为一个客户端实现api接口,客户端提出一个问题,接口没有版本吗?我一时语塞,虽然知道应该有版本,但是实际应用中没实现有版本的情况。特此花费时间解决这个问题。

过程

当前写接口,是基于ssm结构来构建的。
以登录接口为例。
原本的路由是post /user/login
期望的路由是post /version/1/token

这里restful api要求接口通常是名词,因此这里一并纠正。以前的工作是多么的粗糙。

  • 编写注解
package com.wuwenfu.aily.base;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.Mapping;

@Target({ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
@Component
public @interface ApiVersion {

    /**
     * 标识版本号
     * @return
     */
    int value();
}

  • 引入注解
package com.wuwenfu.aily.controller;

import com.wuwenfu.aily.base.ApiVersion;
import com.wuwenfu.aily.base.BaseController;
import com.wuwenfu.aily.result.Response;
import com.wuwenfu.aily.service.UserService;
import com.wuwenfu.aily.service.YzmService;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

/**
 * @ClassName UserController
 * @Description TODO
 * @Author hi_php@163.com
 * @Date Tue Jun 11 13:35:03 CST 2019
 */
@Api(value = "用户接口", description = "")
@RestController
@RequestMapping("/version/{version}")
@Slf4j
public class TokenController extends BaseController {
    /**
     * 登录
     */
    @ApiOperation("登录")
    @ApiVersion(1)
    @RequestMapping(value = "/token", method = RequestMethod.POST)
    public Response login() {
        return new Response().success("");
    }

    /**
     * 登录-版本2
     */
    @ApiOperation("登录-版本2")
    @ApiVersion(2)
    @RequestMapping(value = "/token", method = RequestMethod.POST)
    public Response login2() {
        return new Response().success("");
    }
}




  • 路由转换

上面的注解要能生效,需要对访问路由进行识别。

package com.wuwenfu.aily.base;

import org.springframework.web.servlet.mvc.condition.RequestCondition;

import javax.servlet.http.HttpServletRequest;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class ApiVersionRequestCondition implements RequestCondition<ApiVersionRequestCondition> {
    // 用于匹配request中的版本号  v1 v2
    private static final Pattern VERSION_PATTERN = Pattern.compile("/version/(\\d+).*");
    // 保存当前的版本号
    private int version;
    // 保存所有接口的最大版本号
    private static int maxVersion = 1;

    public ApiVersionRequestCondition(int version) {
        this.version = version;
    }

    @Override
    public ApiVersionRequestCondition combine(ApiVersionRequestCondition other) {
        // 上文的getMappingForMethod方法中是使用 类的Condition.combine(方法的condition)的结果
        // 确定一个方法的condition,所以偷懒的写法,直接返回参数的版本,可以保证方法优先,可以优化
        // 在condition中增加一个来源于类或者方法的标识,以此判断,优先整合方法的condition
        return new ApiVersionRequestCondition(other.version);
    }

    @Override
    public ApiVersionRequestCondition getMatchingCondition(HttpServletRequest request) {
        // 正则匹配请求的uri,看是否有版本号 v1
        Matcher matcher = VERSION_PATTERN.matcher(request.getRequestURI());
        if (matcher.find()) {
            String versionNo = matcher.group(1);
            int version = Integer.valueOf(versionNo);
            // 超过当前最大版本号或者低于最低的版本号均返回不匹配
            if (version <= maxVersion && version >= this.version) {
                return this;
            }
        }
        return null;
    }

    @Override
    public int compareTo(ApiVersionRequestCondition other, HttpServletRequest request) {
        // 以版本号大小判定优先级,越高越优先
        return other.version - this.version;
    }

    public int getVersion() {
        return version;
    }

    public static void setMaxVersion(int maxVersion) {
        ApiVersionRequestCondition.maxVersion = maxVersion;
    }
}

package com.wuwenfu.aily.base;

import org.springframework.core.PriorityOrdered;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.servlet.mvc.condition.RequestCondition;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import java.lang.reflect.Method;

public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping  implements PriorityOrdered {

    private int latestVersion = 1;






    @Override
    protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
        // 判断是否有@ApiVersion注解,构建基于@ApiVersion的RequestCondition
        ApiVersionRequestCondition condition = buildFrom(AnnotationUtils.findAnnotation(handlerType, ApiVersion.class));
        // 保存最大版本号
        if (condition != null && condition.getVersion() > latestVersion) {
            ApiVersionRequestCondition.setMaxVersion(condition.getVersion());
        }
        return condition;
    }

    @Override
    protected RequestCondition<?> getCustomMethodCondition(Method method) {
        // 判断是否有@ApiVersion注解,构建基于@ApiVersion的RequestCondition
        ApiVersionRequestCondition condition =  buildFrom(AnnotationUtils.findAnnotation(method, ApiVersion.class));
        // 保存最大版本号
        if (condition != null && condition.getVersion() > latestVersion) {
            ApiVersionRequestCondition.setMaxVersion(condition.getVersion());
        }
        return condition;
    }

    private ApiVersionRequestCondition buildFrom(ApiVersion apiVersion) {
        return apiVersion == null ? null : new ApiVersionRequestCondition(apiVersion.value());
    }
}
  • 启用配置
    默认的spring-mvc的路由映射关系需要修改,否则无法调用上面自定义的映射逻辑。
package com.wuwenfu.aily.controller;

import com.wuwenfu.aily.base.CustomRequestMappingHandlerMapping;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurationSupport {

    @Override
    @Bean
    public RequestMappingHandlerMapping requestMappingHandlerMapping() {
        RequestMappingHandlerMapping handlerMapping = new CustomRequestMappingHandlerMapping();
        handlerMapping.setOrder(0);
        handlerMapping.setInterceptors(getInterceptors());
        return handlerMapping;
    }
}

  • 配置文件

我的项目存在3个配置文件。
web.xml 、spring.xml 、spring-mvc.xml
web.xml如下

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_5.xsd">

  <welcome-file-list>
    <welcome-file>index.html</welcome-file>
    <welcome-file>index.htm</welcome-file>
    <welcome-file>index.jsp</welcome-file>
  </welcome-file-list>

  <!-- 创建log4j日志监听 -->
  <listener>
    <listener-class>org.springframework.web.util.Log4jConfigListener
    </listener-class>
  </listener>

  <!-- log4jConfigLocation:log4j配置文件存放路径 -->
  <context-param>
    <param-name>log4jConfigLocation</param-name>
    <param-value>classpath:log4j.xml</param-value>
  </context-param>

  <!-- 创建spring监听器 -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener
    </listener-class>
  </listener>
  <!-- spring的监听器可以通过这个上下文参数来获取applicationContext.xml的位置 -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring.xml;classpath:spring-shiro.xml</param-value>
  </context-param>





  <servlet>
    <servlet-name>spring-mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet
    </servlet-class>
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>spring-mvc</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>


  <filter>
    <filter-name>httpPutFormcontentFilter</filter-name>
    <filter-class>org.springframework.web.filter.HttpPutFormContentFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>httpPutFormcontentFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>




  <filter>
    <filter-name>CharacterFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter
    </filter-class>

    <init-param>
      <param-name>encoding</param-name>
      <param-value>UTF-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CharacterFilter</filter-name>
    <url-pattern>*</url-pattern>
  </filter-mapping>

  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

spring.xml的配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:task="http://www.springframework.org/schema/task" xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <context:property-placeholder location="classpath*:*.properties" />

    <!--数据库配置-->
    <import resource="spring-mybatis.xml"/>
    <import resource="rabbitmq.xml"/>


    <!--扫描包-排除controller-->
    <context:component-scan base-package="com.wuwenfu.aily">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>


    <bean class="redis.clients.jedis.JedisPool" id="jedisPool">
        <constructor-arg name="host" value="${redis.host}"></constructor-arg>
        <constructor-arg name="port" value="${redis.port}"></constructor-arg>
        <constructor-arg name="password" value="${redis.password}"></constructor-arg>
        <constructor-arg name="timeout" value="${redis.timeout}"></constructor-arg>
        <constructor-arg name="database" value="${redis.database}"></constructor-arg>
        <constructor-arg name="poolConfig" ref="jedisPoolConfig"></constructor-arg>
    </bean>
    <bean class="redis.clients.jedis.JedisPoolConfig" id="jedisPoolConfig">
        <property name="maxIdle" value="${maxIdle}" />
        <property name="maxTotal" value="${maxTotal}" />
        <property name="maxWaitMillis" value="${maxWait}" />
        <property name="testOnBorrow" value="${testOnBorrow}" />
        <property name="blockWhenExhausted" value="${blockWhenExhausted}" />
    </bean>
    <task:annotation-driven/>

</beans>

spring-mvc.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
    http://www.springframework.org/schema/mvc
    http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--<context:property-placeholder location="classpath*:*.properties" />-->

    <context:annotation-config />
    <context:component-scan base-package="com.wuwenfu.aily" use-default-filters="false">
        <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        <context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.RestController" />
    </context:component-scan>
    <!--使用默认的Servlet来响应静态文件-->
    <mvc:default-servlet-handler />
    <bean
        class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/jsp/" />
        <property name="suffix" value=".jsp" />
    </bean>
    <!-- 上传文件拦截,设置最大上传文件大小   10M=10*1024*1024(B)=10485760 bytes -->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <property name="maxUploadSize" value="10485760" />
    </bean>
<!--执行切面拦截-->
    <aop:aspectj-autoproxy expose-proxy="true"></aop:aspectj-autoproxy>






    <!-- 引入swagger相关 -->
<bean class="com.wuwenfu.aily.doc.MySwaggerConfig"/>
<mvc:resources mapping="swagger-ui.html" location="classpath:/META-INF/resources/" />
    <mvc:resources location="classpath:/META-INF/resources/webjars/" mapping="/webjars/**"/>
    <bean class="springfox.documentation.swagger2.configuration.Swagger2DocumentationConfiguration" id="swagger2Config"/>
    <context:component-scan base-package="com.wuwenfu.aily.controller" ></context:component-scan>
</beans>

这里最重要的部分是不要启用
<mvc:annotation-driven></mvc:annotation-driven> 否则自定义的映射路由无法启动。

  • 测试

使用postman进行测试

image.png image.png

相关文章

网友评论

      本文标题:springmvc的restful api 增加版本参数

      本文链接:https://www.haomeiwen.com/subject/nbjbqctx.html