日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長(zhǎng)提供免費(fèi)收錄網(wǎng)站服務(wù),提交前請(qǐng)做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點(diǎn)擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會(huì)員:747

環(huán)境:SpringBoot2.7.12

1. 概述

本文將介紹如何為API接口動(dòng)態(tài)添加開(kāi)關(guān)功能。通過(guò)這個(gè)功能,我們可以控制API接口的正常訪問(wèn)或顯示提示信息。這有助于在開(kāi)發(fā)和維護(hù)過(guò)程中更好地管理和控制API接口的行為。通過(guò)使用開(kāi)關(guān)功能,我們可以輕松地在不同情況下調(diào)整接口的行為,提高系統(tǒng)的靈活性和可維護(hù)性。

為什么要給API接口添加開(kāi)關(guān)功能呢?從以下幾方面考慮:

  1. 靈活性和可擴(kuò)展性:我們可以根據(jù)需要?jiǎng)討B(tài)地控制API接口的行為。這使得在面對(duì)不同場(chǎng)景和需求時(shí),可以更加靈活地調(diào)整接口的行為,而無(wú)需對(duì)代碼進(jìn)行修改。
  2. 安全性和控制:有時(shí),我們可能不希望在特定情況下API接口被正常訪問(wèn),例如在測(cè)試、維護(hù)或敏感數(shù)據(jù)訪問(wèn)等場(chǎng)景下。通過(guò)關(guān)閉開(kāi)關(guān)并顯示提示信息,我們可以對(duì)用戶進(jìn)行必要的通知和引導(dǎo),確保接口不被未經(jīng)授權(quán)的訪問(wèn)。
  3. 錯(cuò)誤處理和日志記錄:當(dāng)API接口出現(xiàn)錯(cuò)誤或異常時(shí),關(guān)閉開(kāi)關(guān)并顯示提示信息可以幫助我們更好地追蹤和記錄問(wèn)題。這對(duì)于后續(xù)的問(wèn)題排查和系統(tǒng)優(yōu)化非常有幫助。
  4. 系統(tǒng)監(jiān)控和管理:通過(guò)監(jiān)控開(kāi)關(guān)狀態(tài)的變化,我們可以了解系統(tǒng)的運(yùn)行狀況,以及用戶對(duì)API接口的使用情況。這有助于進(jìn)行系統(tǒng)性能優(yōu)化和資源管理。
  5. 用戶體驗(yàn):在某些情況下,當(dāng)API接口不可用或需要維護(hù)時(shí),向用戶顯示友好的提示信息可以避免用戶感到困惑或帶來(lái)不必要的困擾。同時(shí),提前通知用戶也體現(xiàn)了對(duì)用戶體驗(yàn)的關(guān)注。
  6. 合規(guī)性和隱私保護(hù):在涉及敏感數(shù)據(jù)或受限制的API接口中,通過(guò)關(guān)閉開(kāi)關(guān)并顯示提示信息,可以確保遵守相關(guān)法規(guī)和隱私政策,對(duì)數(shù)據(jù)進(jìn)行適當(dāng)?shù)墓芾砗捅Wo(hù)。

2. 實(shí)現(xiàn)方案

首先,定義一個(gè)AOP切面(Aspect),該切面負(fù)責(zé)控制接口(Controller)的開(kāi)關(guān)行為。

在切面中,我們可以使用Spring AOP的切入點(diǎn)(Pointcut)來(lái)指定需要攔截的方法。一旦方法被攔截,我們可以在切面的通知(Advice)中定義相應(yīng)的默認(rèn)行為。接下來(lái)我們將一步一步的實(shí)現(xiàn)接口開(kāi)關(guān)功能。

  • 自定義注解
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface ApiSwitch {


  /**接口對(duì)應(yīng)的key,通過(guò)可以該key查詢接口是否關(guān)閉*/
  String key() default "" ;
  
  /**解析器beanName,通過(guò)具體的實(shí)現(xiàn)獲取key對(duì)應(yīng)的值*/
  String resolver() default "" ;
  
  /**開(kāi)啟后降級(jí)方法名*/
  String fallback() default "" ;
  
}
  • 定義解析器接口
public interface SwitchResolver {


  boolean resolver(String key) ;
  
  public void config(String key, Integer onoff) ;
  
}
  • 接口默認(rèn)實(shí)現(xiàn)
 
@Component
public class ConcurrentMapResolver implements SwitchResolver {


  private Map<String, Integer> keys = new ConcurrentHashMap<>() ;
  
  @Override
  public 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實(shí)現(xiàn)
@Component
public class RedisResolver implements SwitchResolver {


  private final StringRedisTemplate stringRedisTemplate ;
  
  public RedisResolver(StringRedisTemplate stringRedisTemplate) {
    this.stringRedisTemplate = stringRedisTemplate ;
  }
  
  @Override
  public boolean resolver(String key) {
    String value = this.stringRedisTemplate.opsForValue().get(key) ;
    return !(value == null || "0".equals(value)) ;
  }


  @Override
  public void config(String key, Integer onoff) {
    this.stringRedisTemplate.opsForValue().set(key, String.valueOf(onoff)) ;
  }
}

這里就提供兩種默認(rèn)的實(shí)現(xiàn)。

  • 定義切面
@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 {
    
    // 對(duì)應(yīng)接口開(kāi)關(guān)的key
    String key = apiSwitch.key() ;
    // 解析器bean的名稱
    String resolverName = apiSwitch.resolver() ;
    // 降級(jí)方法名
    String fallback = apiSwitch.fallback() ;
    
    SwitchResolver resolver = null ;
    // 根據(jù)指定的beanName獲取具體的解析器;以下都不考慮不存在的情況
    if (StringUtils.hasLength(resolverName)) {
      resolver = this.context.getBean(resolverName, SwitchResolver.class) ;
    } else {
      resolver = this.context.getBean(MAPPINGS.get(this.switchProperties.getResolver())) ;
    }
    // 解析器不存在則直接調(diào)用目標(biāo)接口
    if (resolver == null || !resolver.resolver(key)) {
      return pjp.proceed() ;
    }
    // 調(diào)用降級(jí)的方法;關(guān)于降級(jí)的方法簡(jiǎn)單點(diǎn),都必須在當(dāng)前接口類中,同時(shí)還不考慮方法參數(shù)的情況
    if (!StringUtils.hasLength(fallback)) {
      // 未配置的情況
      return "接口不可用" ;
    }
    Class<?> clazz = pjp.getSignature().getDeclaringType() ;
    Method fallbackMethod = clazz.getDeclaredMethod(fallback) ;
    return fallbackMethod.invoke(pjp.getTarget()) ;
  }


  @Override
  public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    this.context = applicationContext ;
  }
  
}
  • 修改開(kāi)關(guān)接口
@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" ; 
}

通過(guò)該接口修改具體哪個(gè)接口的開(kāi)關(guān)狀態(tài)。(注意:這里有小問(wèn)題,如果接口上指定了resolver類型且配置文件中指定的類型不一致,就會(huì)出現(xiàn)不生效問(wèn)題。這個(gè)問(wèn)題大家可以自行解決)

  • 接下來(lái)進(jìn)行測(cè)試
@GetMapping("/q1")
@ApiSwitch(key = "swtich$q1", fallback = "q1_fallback", resolver = "redisResolver")
public Object q1() {
  return "q1" ;
}


public Object q1_fallback() {
  return "接口維護(hù)中" ;
}

這是完整的配置示例,這里除了key必須外,其它的都可以不填寫(xiě)。

具體測(cè)試結(jié)果就不貼了,大家可以自行測(cè)試基于jvm內(nèi)存和redis的方式。

總結(jié):通過(guò)上述介紹,我們可以看到使用Spring AOP實(shí)現(xiàn)接口的開(kāi)關(guān)功能是一種非常有效的方法。通過(guò)定義AOP切面和切入點(diǎn),我們可以精確地?cái)r截需要控制的方法,并在通知中根據(jù)開(kāi)關(guān)狀態(tài)執(zhí)行相應(yīng)的邏輯。這種技術(shù)手段有助于提高代碼的可維護(hù)性和可擴(kuò)展性,同時(shí)提供更好的靈活性和控制性來(lái)管理接口的行為

分享到:
標(biāo)簽:API
用戶無(wú)頭像

網(wǎng)友整理

注冊(cè)時(shí)間:

網(wǎng)站:5 個(gè)   小程序:0 個(gè)  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會(huì)員

趕快注冊(cè)賬號(hào),推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨(dú)大挑戰(zhàn)2018-06-03

數(shù)獨(dú)一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過(guò)答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫(kù),初中,高中,大學(xué)四六

運(yùn)動(dòng)步數(shù)有氧達(dá)人2018-06-03

記錄運(yùn)動(dòng)步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績(jī)?cè)u(píng)定2018-06-03

通用課目體育訓(xùn)練成績(jī)?cè)u(píng)定