APP的接口版本管理 实现多版本共存

2023-11-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;
    }
}


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

APP的接口版本管理 实现多版本共存 的相关文章

  • antisamy 解析器强制关闭标签

    我使用 Antisamy 来验证 HTML 我的政策允许 iframe 例如 YouTube 视频 问题是 如果标签为空 像这样 清洗后会是这样的 但它应该有正常的结束标签 这会破坏之后页面上的所有内容 我已经将指令设置为使用大部分 HTM
  • 如何允许另一个应用程序访问我的应用程序的数据目录?

    假设我有一个名为 A 的应用程序 其数据目录为 com example test 现在我想制作另一个名为 B 的应用程序来修改 com example test 中的某些内容 当然我知道两者必须共享相同的签名 但我还需要什么 基本上我正在尝
  • 应用程序在 JSON jparser 发出 http 请求时崩溃

    您好 我使用本教程连接到网络或本地的 mySQL 数据库 here http www androidhive info 2012 05 how to connect android with php mysql 虽然所有服务器端 php 文
  • 如何配置jackson属性命名策略?

    此代码不起作用 Configuration public class RepositoryRestMvcConfig extends RepositoryRestMvcConfiguration Bean Override public O
  • Android 应用安装验证

    我有一个应用程序 其中列出了用户可以安装并赚取积分的一些活动 应用程序列表 现在我主要关心的是安全性 一些用户从模拟器或VPN或其他东西安装应用程序 这样我的客户就无法在Google Play商店中安装应用程序 我见过一些应用程序 如现金海
  • 调用replace()方法后片段闪烁/闪烁

    我有一个MainActivity 应该在两个片段之间切换 内容和设置 扩展PreferenceFragmentCompat 一切工作正常 但最近我实施了Dagger 2依赖注入 我的设置片段开始闪烁 当您按下底部导航栏上的设置项时 有时会出
  • 将十六进制字节数组解码为特定代码页在随后编码时会产生错误结果

    我创建了一个简单的应用程序 如下所示 String stringValue new String new byte 0x00 0x00 0x00 0x25 273 byte valueEncoded Arrays copyOfRange s
  • cipher.update在java中做什么?

    我正在实施 DES CBC 我很困惑什么cipher init cipher update and cipher dofinal做 我只是使用 init 来设置密钥dofinal得到结果 我不使用更新 那是对的吗 另外使用时结果有什么不同U
  • 如何将 Java 字节数组转换为 Scala 字节数组?

    我是 Scala 新手 目前正在从事一个涉及 Java 和 Scala 模块的项目 现在我想使用 byte 类型的参数从 Java 调用 Scala 方法 Scala 方法的签名为 def foo data Array Byte Java
  • 如何在 Selenium 中定位具有特定文本的跨度? (使用Java)

    我在使用 java 查找 Selenium 中的 span 元素时遇到问题 HTML 看起来像 div class settings padding span Settings span div 我尝试了以下方法但没有成功 By xpath
  • 在 Android 中的计时器内运行异步任务

    我正在开发一个基本的聊天类型应用程序 目前我正在运行代码 如下所示 class GetMsgs extends AsyncTask
  • Android 是否可以同时使用前后摄像头[重复]

    这个问题在这里已经有答案了 我想同时使用设备的前置和后置摄像头 在我的应用程序中 屏幕的前半部分将显示后置摄像头的预览 屏幕的下半部分将显示前置摄像头的预览 我尝试过设置两个不同的相机预览 但是当我打开应用程序时 屏幕的前半部分 显示后置相
  • TextView ClickableSpan 按下状态的样式[重复]

    这个问题在这里已经有答案了 我对 ClickableSpan 进行子类化 以自定义 TextView 中链接的文本样式 private static class LinkSpan extends ClickableSpan Override
  • Android - 使用 HttpURLConnection 来 POST XML 数据

    我遇到了一些死胡同 需要一些帮助 请 我对 Android 开发 以及一般编码 非常陌生 基本上我需要使用 HttpURLConnection 将 XML 数据发布到 URL 但无法让它工作 我的应用程序从 GET 请求读取并传递 XML
  • GAE、JPA、XG 事务、实体组过多异常

    我知道 GAE 上的 XG 交易有 5 个实体组的限制 但我认为我在一项交易中仅使用 3 个组 商品 类别 商品类别 但仍然遇到此异常 引起原因 java lang IllegalArgumentException 在单个事务中对太多实体组
  • 遍历多行字符串

    我得到一些数据 def data some useless text n even more n finally interesting text 我怎样才能得到其中 有趣的部分 所以基本上所有行都不是以 开头的 Groovy 的一种选择是
  • 如何使用 Java 11 和 JavaFX 11 运行 ControlsFX 示例应用程序

    ControlFX 网站 http fxexperience com controlsfx says 如果您想使用 ControlsFX 示例应用程序 只需 下载 ControlsFX 版本并在上运行以下命令 命令提示符 请务必将 替换为实
  • GreenDao交易

    我在用着GreenDao存储大量数据 来自休息服务 我的很多实体都与关系相关 一切都很顺利 但明天我必须实施坚如磐石的工作流程 当我加载数据时我必须检查是否发生错误 如果是这样 我必须确保没有存储任何内容在 SQLite 数据库中 通常我会
  • 错误:java.lang.NoSuchMethodError:org/springframework/asm/ClassVisitor.(I)V

    我的 POM 中有这两个依赖项 我认为这是造成此问题的原因 但我尝试了许多不同的方法和更新的版本 但没有任何效果对我有用 有人可以帮忙吗 XML文件
  • Pinterest 喜欢自定义 GridView

    我是 Android 新手 我正在寻找网格视图的逻辑 例如为 iPhone 构建的 pinterest homescreen 应用程序 一个大号 图像来自服务器 我需要以以下形式显示并具有分页效果 即在滚动上加载图像 如果可以的话请回复 我

随机推荐

  • 计算机网络-应用层

    1 概述 定义 为应用进程的通信提供服务 主要功能 文件传输 访问和管理 电子邮件 虚拟终端 查询服务和远程作业登录 重要协议 FTP SMTP POP3 HTTP DNS 应用层的两种模型 客户服务器模型 C S P2P模型 Peer t
  • 为什么使用Lambda表达式

    视频地址 https b23 tv gfH0PjR 在使用Lambda表达式之前 先看一下 匿名内部类的一个使用 匿名内部类详情 http t csdn cn nMZpB 匿名内部类 就是没有名字的一种嵌套类 它是Java对类的定义方式之一
  • java倒叙遍历list

    for int i list size 1 i gt 0 i System out println list get i
  • MIDI 音乐程序设计 (可以自己编曲的快乐)

    乐器数字接口 MIDI 简介 乐器数字接口 Musical Instrument Digital Interface MIDI 是20世纪80年代初为解决电声乐器之间的通信问题而提出的 是电子乐器制造商们建立的通信标准 是电子乐器之间以及电
  • linux网站重启命令res,linux

    服务进程 实时观察cpu 进程 内存 top q键退出 load average 0 05 0 11 0 08 1分钟 5分钟 15分钟 平均负载 Cpu s 0 7 us 1 4 sy 0 0 ni 97 8 id 0 0 wa 0 0
  • unity 之 Particle 内置粒子系统二

    Emission module 这个模块影响发射速率和发射的时间 Properties Property Function Rate over Time 单位时间内发射的粒子数 Rate over Distance 每移动一单位距离所发射的
  • SpringBoot八种bean的加载方式

    目录 第一种bean的加载方式 配置文件 第二种加载bean方式 注解和扫描 创建第三方的bean对象 第三种加载bean方式 不使用配置文件 扩展 bean的加载方式扩展FactoryBean lt gt 扩展 ImportResourc
  • 华为云服务器,新用户福利!!0元免费体验云产品最长可达一年

    华为云官网最新活动 新用户完成个人实名认证 参与问卷调研后即可免费试用 云产品0元试用 最长可达一年 华为云服务立足于互联网领域 依托于华为公司雄厚的资本和强大的云计算研发实力 面向互联网增值服务运营商 大中小型企业 政府 科研院所等广大企
  • global::System.Runtime.Versioning.TargetFrameworkAttribute 特性重复问题处理

    今天编译程序遇到global System Runtime Versioning TargetFrameworkAttribute 特性重复错误 原因是编译时生成的 NETFramework Version v4 6 1 AssemblyA
  • 记一次MySql还原导致的无法连接

    问题 昨天同事使用服务器的MySql数据库还原了一个5GB 的备份 结果一直卡住 他就将数据库强行关闭 没再执行任何其他操作 结果今天客户反馈登录异常 逐步排查 定位到是数据库无法访问 于是远程查看服务器 发现 MySql服务是正在运行的
  • RTKlib软件源码学习(观测文件与星历文件读取)

    本文基于本人对rtklib源码的学习进行顺序汇总 为记录个人的学习与理解 并根据个人需要对部分代码对其进行注释 如有错误或者不完善的地方烦请提出建议或改正方法 目录 1 execses函数 2 readobsnav函数 3 readrnxt
  • 【JS逆向】之HOOK代码怎么写

    声明 本文只作学习研究 禁止用于非法用途 否则后果自负 如有侵权 请告知删除 谢谢 前言 我解释一下hook是什么玩意 hook的原意是钩子 我理解替换或者拦截原有方法去修改和处理 1 怎么去替换原来的方法 这里我自己写一个方法去替换 实例
  • 中关村归国留学人员联创中心揭牌仪式,Jina AI 受邀出席活动

    2022 年 9 月 25 日上午 中关村归国留学人员联创中心揭牌仪式在中关村创业大街顺利举行 Jina AI 联合创始人兼 COO 何烜彬现场进行企业项目分享 并面向现场各海外高校校友代表发出人才招募邀请 活动背景 为更好地服务国际化人才
  • matlab入门基础:矩阵操作(一)

    系列笔记目录 第二节 矩阵操作 一 创建特殊矩阵 矩阵的运算 文章目录 系列笔记目录 前言 一 创建特殊矩阵 二 矩阵运算 1 矩阵元素的修改 2 矩阵的变维 3 矩阵的变向 4 矩阵的抽取 练习 三 矩阵的数学运算 总结 前言 MATLA
  • C++ - Vector 计算 均值(mean) 和 方差(variance)

    C Vector 计算 均值 mean 和 方差 variance 代码 double sum std accumulate std begin resultSet std end resultSet 0 0 double mean sum
  • C++通讯录管理系统(简单版)

    这个通讯录管理系统是我听课后做的笔记 都是很基础的逻辑实现 第一次动手写了一个小案例感觉找到了一点方向 也希望能帮到一点忙 1 系统需求 通讯录是一个可以记录亲人 好友信息的工具 本教程主要利用C 来实现一个通讯录管理系统 系统中需要实现的
  • PowerDesigner将PDM导出生成WORD文档--温习老知识

    今天的温习老知识 是如何将一个PD设计的PDM来导出WORD文档 这是一个非常实用的功能 可以在软件过程的数据库设计文档编写中节省N多时间 那不废话了 我们就开始今天的讲解吧 第一步 点击Report Temlates 制作模板 第二步 时
  • 1. 创建和生成

    文章目录 创建和生成 从 python 列表或元组创建 使用 arange 生成 使用 linspace logspace 生成 np linspace 使用 ones zeros 创建 使用 random 生成 从文件读取 本教程内容旨在
  • Python:实现多个txt文本的简单合并,亲测简单好用,保姆教程

    声明 非原创 我是python废废 原博客连接为 Python批量合并多个txt文件 自由的小白的博客 CSDN博客 python 合并多个txt 我在这里只做分享 好博客要更多人看到嘛 一 成果展示 我希望实现简单的txt文本合并 合并前
  • APP的接口版本管理 实现多版本共存

    思路 用户请求url gt 拦截器拦截 gt 转发到真正处理类和方法 gt 返回结果 url注解类 package com jc app util apiVersion import java lang annotation 需要拦截的AP