优雅实现API接口开关:让你的应用更可控

环境:SpringBoot2.7.12

1. 概述

本文将介绍如何为API接口动态添加开关功能。通过这个功能,我们可以控制API接口的正常访问或显示提示信息。这有助于在开发和维护过程中更好地管理和控制API接口的行为。通过使用开关功能,我们可以轻松地在不同情况下调整接口的行为,提高系统的灵活性和可维护性。

为什么要给API接口添加开关功能呢?从以下几方面考虑:

  1. 灵活性和可扩展性:我们可以根据需要动态地控制API接口的行为。这使得在面对不同场景和需求时,可以更加灵活地调整接口的行为,而无需对代码进行修改。
  2. 安全性和控制:有时,我们可能不希望在特定情况下API接口被正常访问,例如在测试、维护或敏感数据访问等场景下。通过关闭开关并显示提示信息,我们可以对用户进行必要的通知和引导,确保接口不被未经授权的访问。
  3. 错误处理和日志记录:当API接口出现错误或异常时,关闭开关并显示提示信息可以帮助我们更好地追踪和记录问题。这对于后续的问题排查和系统优化非常有帮助。
  4. 系统监控和管理:通过监控开关状态的变化,我们可以了解系统的运行状况,以及用户对API接口的使用情况。这有助于进行系统性能优化和资源管理。
  5. 用户体验:在某些情况下,当API接口不可用或需要维护时,向用户显示友好的提示信息可以避免用户感到困惑或带来不必要的困扰。同时,提前通知用户也体现了对用户体验的关注。
  6. 合规性和隐私保护:在涉及敏感数据或受限制的API接口中,通过关闭开关并显示提示信息,可以确保遵守相关法规和隐私政策,对数据进行适当的管理和保护。

2. 实现方案

首先,定义一个AOP切面(Aspect),该切面负责控制接口(Controller)的开关行为。

在切面中,我们可以使用Spring AOP的切入点(Pointcut)来指定需要拦截的方法。一旦方法被拦截,我们可以在切面的通知(Advice)中定义相应的默认行为。接下来我们将一步一步的实现接口开关功能。

  • 自定义注解
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ApiSwitch {/**接口对应的key,通过可以该key查询接口是否关闭*/String key() default "" ;/**解析器beanName,通过具体的实现获取key对应的值*/String resolver() default "" ;/**开启后降级方法名*/String fallback() default "" ;}
  • 定义解析器接口
public interface SwitchResolver {boolean resolver(String key) ;public void config(String key, Integer onoff) ;}
  • 接口默认实现
@Component
public class ConcurrentMapResolver implements SwitchResolver {private Map<String, Integer> keys = new ConcurrentHashMap<>() ;@Overridepublic boolean resolver(String key) {Integer value = keys.get(key) ;return value == null ? false : (value == 1) ;}public void config(String key, Integer onoff) {keys.put(key, onoff) ;}
}
  • 基于redis实现
@Component
public class RedisResolver implements SwitchResolver {private final StringRedisTemplate stringRedisTemplate ;public RedisResolver(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate ;}@Overridepublic boolean resolver(String key) {String value = this.stringRedisTemplate.opsForValue().get(key) ;return !(value == null || "0".equals(value)) ;}@Overridepublic void config(String key, Integer onoff) {this.stringRedisTemplate.opsForValue().set(key, String.valueOf(onoff)) ;}
}

这里就提供两种默认的实现。

  • 定义切面
@Component
@Aspect
public class ApiSwitchAspect implements ApplicationContextAware {private ApplicationContext context ;private final SwitchProperties switchProperties ;public static final Map<String, Class<? extends SwitchResolver>> MAPPINGS;static {// 初始化所有的解析器Map<String, Class<? extends SwitchResolver>> mappings = new HashMap<>() ;mappings.put("map", ConcurrentMapResolver.class) ;mappings.put("redis", RedisResolver.class) ;MAPPINGS = Collections.unmodifiableMap(mappings) ;}public ApiSwitchAspect(SwitchProperties switchProperties) {this.switchProperties = switchProperties ;}@Pointcut("@annotation(apiSwitch)")private void onoff(ApiSwitch apiSwitch) {}@Around("onoff(apiSwitch)")public Object ctl(ProceedingJoinPoint pjp, ApiSwitch apiSwitch) throws Throwable {// 对应接口开关的keyString key = apiSwitch.key() ;// 解析器bean的名称String resolverName = apiSwitch.resolver() ;// 降级方法名String fallback = apiSwitch.fallback() ;SwitchResolver resolver = null ;// 根据指定的beanName获取具体的解析器;以下都不考虑不存在的情况if (StringUtils.hasLength(resolverName)) {resolver = this.context.getBean(resolverName, SwitchResolver.class) ;} else {resolver = this.context.getBean(MAPPINGS.get(this.switchProperties.getResolver())) ;}// 解析器不存在则直接调用目标接口if (resolver == null || !resolver.resolver(key)) {return pjp.proceed() ;}// 调用降级的方法;关于降级的方法简单点,都必须在当前接口类中,同时还不考虑方法参数的情况if (!StringUtils.hasLength(fallback)) {// 未配置的情况return "接口不可用" ;}Class<?> clazz = pjp.getSignature().getDeclaringType() ;Method fallbackMethod = clazz.getDeclaredMethod(fallback) ;return fallbackMethod.invoke(pjp.getTarget()) ;}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {this.context = applicationContext ;}}
  • 修改开关接口
@GetMapping("/onoff/{state}")
public Object onoff(String key, @PathVariable("state") Integer state) {String resolverType = switchProperties.getResolver();if (!StringUtils.hasLength(resolverType)) {SwitchResolver bean = this.context.getBean(ApiSwitchAspect.MAPPINGS.get("map")) ;if (bean instanceof ConcurrentMapResolver resolver) {resolver.config(key, state) ;}} else {SwitchResolver resolver = this.context.getBean(ApiSwitchAspect.MAPPINGS.get(resolverType)) ;resolver.config(key, state) ;}return "success" ; 
}

通过该接口修改具体哪个接口的开关状态。(注意:这里有小问题,如果接口上指定了resolver类型且配置文件中指定的类型不一致,就会出现不生效问题。这个问题大家可以自行解决)

  • 接下来进行测试
@GetMapping("/q1")
@ApiSwitch(key = "swtich$q1", fallback = "q1_fallback", resolver = "redisResolver")
public Object q1() {return "q1" ;
}public Object q1_fallback() {return "接口维护中" ;
}

这是完整的配置示例,这里除了key必须外,其它的都可以不填写。

具体测试结果就不贴了,大家可以自行测试基于jvm内存和redis的方式。

总结:通过上述介绍,我们可以看到使用Spring AOP实现接口的开关功能是一种非常有效的方法。通过定义AOP切面和切入点,我们可以精确地拦截需要控制的方法,并在通知中根据开关状态执行相应的逻辑。这种技术手段有助于提高代码的可维护性和可扩展性,同时提供更好的灵活性和控制性来管理接口的行为

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/179487.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【攻防世界-misc】glance-50

1.得到一个动图 2.使用GIF动态图片分解&#xff0c;多帧动态图分解成多张静态图片_图片工具网页版&#xff0c;将图片定格组合&#xff0c; 由此得到flag值&#xff0c;拼写提交。

【数据结构/C++】栈和队列_链栈

链头 栈顶。 #include<iostream> using namespace std; // 链栈 typedef int ElemType; typedef struct Linknode {ElemType data;struct Linknode *next; } *LiStack; // 初始化 void InitLiStack(LiStack &S) {S (LiStack)malloc(sizeof(struct Linknode));S->…

【PyQt】(自定义类)阴影遮罩-升级版

这是之前发的代码(自定义类)阴影遮罩的升级版。 升级就升级在&#xff0c;优化了对非矩形控件的遮盖效果&#xff0c;例如圆角按钮&#xff0c;以及默认方法不满足时可以传入其他的遮盖方法。 自定义阴影遮罩Mask&#xff1a; class Mask(QWidget):__excludeNone__colorNonecl…

20 章 多线程

20.1线程简介. 20.2创建线程 2.1继承Thread类 Thread 类是java.lang包中的一个类&#xff0c;从这个类中实例化的对象代表线程&#xff0c;程序员启动一个新线程需要建立Thread 实例。Thread类中常用的两个构造方法如下: public Thread():创建一个新的线程对象。 public Thre…

数据安全:专业服务与您共同对抗.faust数字勒索的威胁

引言&#xff1a; 在数字世界的幕后&#xff0c;一股黑暗势力悄然崛起。.faust勒索病毒&#xff0c;如同数码时代的黑手党&#xff0c;通过其高度精密的加密技术&#xff0c;正在肆虐用户和组织的数据。本文将深入挖掘.faust的狡猾手法&#xff0c;为您揭示其隐藏在数字背后的…

v-model(双向数据绑定)自动收集数据

v-model 是 Vue 中一个常用的指令&#xff0c;用于实现表单元素与数据的双向绑定。 它的实现原理主要基于以下两个方面&#xff1a; 语法实现&#xff1a; v-model 实际上是 Vue 对 :value 和 input 两个属性的语法糖。当我们在组件中使用 v-model 指令时&#xff0c;Vue 会根…

C++ string类(二)

insert&#xff1a; erase&#xff1a; 常见用法&#xff1a; int main() {string s1("hello world");string s2("gm");s1.insert(5,"x");cout << s1 << endl;s1.insert(6,s1,0);cout << s1 << endl;s1.insert(0,&qu…

HTML中input标签的23种type类型

一、概述 随着html5的出现&#xff0c;input标签新增了多种类型&#xff0c;用以接收各种类型的用户输入。其中传统输入控件有10种&#xff0c;新增输入控件有13种。 二、传统类型 传统输入控件有10种&#xff0c;如下所示 text 定义单行文本输入框 password 定义…

Matlab 点云曲率计算(之二)

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 之前已经讨论过许多关于计算曲率的问题,这里使用一个通过拟合三次曲面方程的方式来计算曲率,计算过程如下图所示: 二、实现代码 %********

goweb入门教程

本文是作者自己学习goweb时写的笔记&#xff0c;分享给大家&#xff0c;希望能有些帮助 前言&#xff1a; 关于web&#xff1a;本质 ​ ​ web中最重要的就是浏览器和服务器的request(请求)和response(响应)&#xff1b; ​ 一个请求对应一个响应。 一个请求对应一个响应&…

11-28渗透

用nmap扫描靶机1进行主机发现 已知靶机1的主机在172.16.17.0/24下 扫描结果如下 根据扫描结果看开启的服务怀疑172.16.17.177是靶机1 浏览器访问172.16.17.177页面得到如下 我们知道织梦cms系统默认管理路径是dede&#xff0c;登陆管理后台可以通过地址172.16.17.177/dede/i…

map 和 flatMap 的区别

StreamAPI 中的 map 方法&#xff0c;实现是一种映射关系。类似数学中的 x -> f(x)。 而 flatMap 方法是在 map 的基础上做了一次 flat。 相信看了下面的例子大家会有更加直观的理解&#xff1a; public static void main(String[] args) {List<Integer> list1 Ar…

python爬虫防乱码方案

python爬虫防乱码方案 一、chardet库 ​ 自动检测编码&#xff1a;使用 Python 库 chardet 可以自动检测文本的编码&#xff0c;然后使用检测到的编码来解码文本。你可以尝试使用 chardet 库来检测编码&#xff0c;然后解码网页内容。 案例 import requests import chardet…

运算符展开、函数,对象,数组,字符串变化 集合

... 展开运算符 用于函数实参或者赋值号右边 console.log(...[1, 2, 3]) // 1,2,3console.log(Math.max(...[1, 2, 3]))//3 console.log(Math.max.apply(null, [1, 2, 3]))//3const o { a: 1, b: 2 }const obj { ...o, c: 3 }console.log(obj)//Object ... 剩余运算符 用于…

11月28日配置环境中的错误

1.pycharm中的anaconda环境 pycharm中出现Conda executable is not found 2.使用GPU进行训练时&#xff0c;dataloader的numwork2是在linux环境下进行子线程调用&#xff0c;但在windows中是只能单线程&#xff0c;所以num_work 0 才不报错。

隐式类型转化

1. 2. 3. 4. 5. 6. 7. 8.

Flutter开发type ‘Future<int>‘ is not a subtype of type ‘int‘ in type cast错误

文章目录 问题描述错误源码 问题分析解决方法修改后的代码 问题描述 今天有个同事调试flutter程序时报错&#xff0c;问我怎么解决&#xff0c;程序运行时报如下错误&#xff1a; type ‘Future’ is not a subtype of type ‘int’ in type cast 错误源码 int order Databas…

PTA-6-48 使用面向对象的思想编写程序描述动物

题目&#xff1a; 使用面向对象的思想编写程序描述动物&#xff0c;说明&#xff1a; &#xff08;1) 分析兔子和青蛙的共性&#xff0c;定义抽象的动物类&#xff0c;拥有一些动物共有的属性&#xff1a;名字、颜色、类别&#xff08;哺乳类、非哺乳类&#xff09;&#xff0c…

ES8语法async与await

async和await两种语法结合可以让异步代码像同步代码一样。 一、async函数 async函数的返回值为Promise对象promise对象的结果由async函数执行的返回值决定 async function fn() {// 返回一个字符串return 字符串&#xff1b;// 返回的结果不是一个Promise类型的对象&#xf…

气体电介质击穿机理及其模拟

摘 要 气体介质在电场作用下发生碰撞电离而导致电极间贯穿性放电的现象。气体介质击穿与很多因素有关&#xff0c;其中主要的影响因素为作用电压、电极形状、气体的性质及状态等。气体介质击穿常见的有直流电压击穿、工频电压击穿、冲击电压击穿、高气压电击穿、高真空电击穿、…