如何设计对外API接口

如何设计对外API接口

先明确 API 设计的 "黄金三角" 原则

这是所有设计的基础,偏离了这些原则,API 就会变成 "没人愿意用的鸡肋" 👇

接口命名与规范 ✅

坚决使用 RESTful 风格,这是业界事实上的标准,能极大降低沟通成本:

  • 资源用名词复数/users/orders/products
  • HTTP 动词表示操作:
    • GET:查询资源
    • POST:创建资源
    • PUT:全量更新资源
    • PATCH:部分更新资源
    • DELETE:删除资源
  • 禁止使用动词:❌ /getUser、✅ /users/{id}
  • 路径分隔用短横线 :❌ /userInfo、✅ /user-info
  • 统一小写,避免大小写敏感问题

请求与响应设计 📦

这是最容易踩坑的地方,统一的格式比什么都重要!

1. 统一响应体结构

json 复制代码
{
  "code": 200,          // 业务状态码,≠HTTP状态码
  "message": "success", // 提示信息
  "data": {},           // 业务数据
  "timestamp": 1717888888888 // 服务器时间戳
}

2. 分页查询标准设计

所有列表接口必须支持分页,防止大数据量拖垮服务:

text 复制代码
GET /users?pageNum=1&pageSize=20&sort=createTime,desc

响应示例:

json 复制代码
{
  "code": 200,
  "message": "success",
  "data": {
    "total": 100,
    "list": [...],
    "pageNum": 1,
    "pageSize": 20,
    "pages": 5
  }
}

3. 关键注意事项 ⚠️

  • 参数校验 :所有入参必须在 Controller 层做校验(使用 JSR-380 规范:@NotNull@Size等)
  • 敏感字段脱敏:手机号、身份证、银行卡号等必须返回脱敏后的数据
  • 禁止返回内部异常栈:生产环境异常信息会泄露系统架构,给黑客可乘之机

安全设计 🛡️

对外 API 的安全是生命线,没有安全的 API 就是在裸奔!

核心安全措施:

  1. 强制 HTTPS:所有对外接口必须使用 HTTPS,禁止 HTTP 明文传输
  2. 签名机制:请求参数 + 时间戳 + 随机数 + 密钥生成签名,防止参数被篡改
  3. 防重放攻击:签名有效期一般设为 5-15 分钟,配合 nonce 随机数去重
  4. 身份认证:优先使用 JWT 无状态认证,涉及高敏感操作时增加二次验证
  5. 权限控制:基于 RBAC 模型,细粒度控制每个接口的访问权限

性能与可用性设计 🚀

好的 API 不仅能用,还要好用、耐用

问题 解决方案 实现要点
高并发 限流 令牌桶算法,基于 Redis 实现分布式限流
服务雪崩 熔断降级 使用 Sentinel/Hystrix,非核心接口直接降级
重复提交 幂等性 唯一请求 ID + 防重表,写操作必须保证幂等
慢查询 缓存 热点数据放入 Redis,设置合理的过期时间
大数据量 分批处理 导出、批量查询接口必须分批,避免 OOM

幂等性设计是重中之重:所有写操作(POST/PUT/DELETE)都必须保证幂等,防止重复下单、重复扣款等严重业务问题。

版本管理 🔄

API 一旦发布就不能随意修改,版本控制是保证兼容性的关键:

  • 推荐 URL 路径版本/api/v1/users/api/v2/users
  • 不推荐:参数版本(?version=1)、请求头版本
  • 旧版本至少保留3-6 个月,给调用方足够的迁移时间
  • 废弃的接口在响应头中添加DeprecationSunset字段提示

文档与监控 📊

  • 接口文档:强制使用 Swagger/OpenAPI 3.0,文档必须包含:

    • 接口功能说明
    • 请求参数、响应字段说明
    • 成功 / 失败示例
    • 错误码对照表
  • 监控告警:监控以下核心指标:

    • QPS、响应时间、错误率
    • 慢接口 TOP10
    • 异常调用次数
    • 限流熔断触发次数

最后总结 ✨

好的对外 API 接口应该像一个 "优雅的服务员":

  • 说话清晰(命名规范)
  • 态度友好(响应统一)
  • 安全可靠(防护到位)
  • 反应迅速(性能优秀)
  • 与时俱进(版本迭代)

它不仅是系统之间的桥梁,更是技术团队的 "脸面"。一个设计糟糕的 API 会让调用方痛苦不堪,而一个设计优秀的 API 则会大大提升开发效率和系统稳定性。

核心代码实现与技术亮点 ✨

我会展示对外 API 设计中最核心、最有技术含量的 5 个模块代码,全部是生产环境可用的标准实现。

1. 统一响应体 + 全局异常处理(基础中的基础)

技术亮点:泛型封装 + 全局异常拦截,彻底消灭零散的响应构造代码,统一所有异常的返回格式

java 复制代码
// 1. 通用响应体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;
    private long timestamp;

    // 静态工厂方法,业务代码只需调用这几个方法
    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "success", data, System.currentTimeMillis());
    }

    public static <T> ApiResponse<T> fail(int code, String message) {
        return new ApiResponse<>(code, message, null, System.currentTimeMillis());
    }
}

// 2. 全局异常处理器
@RestControllerAdvice
public class GlobalExceptionHandler {

    // 业务异常
    @ExceptionHandler(BusinessException.class)
    public ApiResponse<Void> handleBusinessException(BusinessException e) {
        return ApiResponse.fail(e.getCode(), e.getMessage());
    }

    // 参数校验异常
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public ApiResponse<Void> handleValidationException(MethodArgumentNotValidException e) {
        String errorMsg = e.getBindingResult().getFieldErrors().stream()
                .map(FieldError::getDefaultMessage)
                .collect(Collectors.joining(", "));
        return ApiResponse.fail(400, "参数错误:" + errorMsg);
    }

    // 兜底异常(禁止返回异常栈)
    @ExceptionHandler(Exception.class)
    public ApiResponse<Void> handleException(Exception e) {
        log.error("系统异常", e); // 内部记录完整日志
        return ApiResponse.fail(500, "系统繁忙,请稍后重试");
    }
}

2. 签名验签工具类(安全核心)

技术亮点:支持防篡改 + 防重放攻击,生产环境标准实现

java 复制代码
@Component
public class SignUtils {
    private static final String SIGN_SECRET = "your-secret-key"; // 从配置中心读取
    private static final long SIGN_EXPIRE_TIME = 5 * 60 * 1000; // 签名有效期5分钟

    // 生成签名
    public String generateSign(Map<String, String> params, String timestamp, String nonce) {
        // 1. 参数按字典序排序
        String sortedParams = params.entrySet().stream()
                .sorted(Map.Entry.comparingByKey())
                .map(entry -> entry.getKey() + "=" + entry.getValue())
                .collect(Collectors.joining("&"));

        // 2. 拼接签名串:sortedParams + timestamp + nonce + secret
        String signStr = sortedParams + timestamp + nonce + SIGN_SECRET;

        // 3. MD5加密并转大写
        return DigestUtils.md5DigestAsHex(signStr.getBytes()).toUpperCase();
    }

    // 验证签名+防重放
    public boolean verifySign(Map<String, String> params, String timestamp, String nonce, String sign) {
        // 1. 检查时间戳是否过期
        long requestTime = Long.parseLong(timestamp);
        if (System.currentTimeMillis() - requestTime > SIGN_EXPIRE_TIME) {
            return false;
        }

        // 2. 检查nonce是否已存在(防重放)
        String redisKey = "nonce:" + nonce;
        if (redisTemplate.hasKey(redisKey)) {
            return false;
        }

        // 3. 验证签名
        String serverSign = generateSign(params, timestamp, nonce);
        if (!serverSign.equals(sign)) {
            return false;
        }

        // 4. 存入Redis,有效期同签名有效期
        redisTemplate.opsForValue().set(redisKey, "1", SIGN_EXPIRE_TIME, TimeUnit.MILLISECONDS);
        return true;
    }
}

3. 幂等性注解 + AOP 实现(无侵入设计)

技术亮点:注解驱动 + AOP,业务代码零侵入,支持自定义幂等有效期

java 复制代码
// 1. 幂等性注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    long expireTime() default 10 * 60 * 1000; // 默认10分钟
}

// 2. AOP切面实现
@Aspect
@Component
public class IdempotentAspect {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint point, Idempotent idempotent) throws Throwable {
        // 1. 获取唯一请求ID(建议从请求头传递,前端生成)
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        String requestId = request.getHeader("X-Request-Id");
        if (StringUtils.isEmpty(requestId)) {
            throw new BusinessException(400, "缺少请求唯一标识");
        }

        // 2. Redis原子操作判断是否已执行
        String redisKey = "idempotent:" + requestId;
        Boolean success = redisTemplate.opsForValue()
                .setIfAbsent(redisKey, "1", idempotent.expireTime(), TimeUnit.MILLISECONDS);

        if (Boolean.FALSE.equals(success)) {
            throw new BusinessException(409, "请勿重复提交");
        }

        // 3. 执行原方法
        try {
            return point.proceed();
        } catch (Exception e) {
            // 异常时删除key,允许重试
            redisTemplate.delete(redisKey);
            throw e;
        }
    }
}

// 3. 业务使用示例
@PostMapping("/orders")
@Idempotent(expireTime = 30 * 60 * 1000) // 订单30分钟内不允许重复提交
public ApiResponse<OrderVO> createOrder(@RequestBody @Valid CreateOrderRequest request) {
    Order order = orderService.createOrder(request);
    return ApiResponse.success(convert(order));
}

4. 分布式限流注解 + AOP 实现

技术亮点:基于 Redis 令牌桶算法,支持自定义限流规则,集群环境有效

java 复制代码
// 1. 限流注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RateLimit {
    int limit() default 100; // 每秒允许的请求数
    int burst() default 200; // 突发流量上限
}

// 2. AOP切面实现
@Aspect
@Component
public class RateLimitAspect {
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    private static final String LIMIT_SCRIPT = "local key = KEYS[1] " +
            "local limit = tonumber(ARGV[1]) " +
            "local burst = tonumber(ARGV[2]) " +
            "local now = tonumber(ARGV[3]) " +
            "local rate = limit / 1000 " +
            "local last_time = tonumber(redis.call('hget', key, 'last_time') or now) " +
            "local tokens = tonumber(redis.call('hget', key, 'tokens') or burst) " +
            "tokens = math.min(burst, tokens + (now - last_time) * rate) " +
            "if tokens >= 1 then " +
            "    redis.call('hset', key, 'tokens', tokens - 1) " +
            "    redis.call('hset', key, 'last_time', now) " +
            "    return 1 " +
            "else " +
            "    return 0 " +
            "end";

    @Around("@annotation(rateLimit)")
    public Object around(ProceedingJoinPoint point, RateLimit rateLimit) throws Throwable {
        String key = "rate_limit:" + point.getSignature().toShortString();
        List<String> keys = Collections.singletonList(key);
        Object[] args = {rateLimit.limit(), rateLimit.burst(), System.currentTimeMillis()};

        Long result = (Long) redisTemplate.execute(new DefaultRedisScript<>(LIMIT_SCRIPT, Long.class), keys, args);

        if (result == 0) {
            throw new BusinessException(429, "请求过于频繁,请稍后重试");
        }

        return point.proceed();
    }
}

技术难点与解决方案 🧩

我整理了对外 API 设计中最容易被面试官追问的 5 个技术难点,以及对应的生产级解决方案:

技术难点 具体挑战 最优解决方案 注意事项
分布式幂等性保证 ⚠️ 1. 并发场景下重复请求2. 网络超时导致的重试3. 业务回滚后允许重试 Redis 防重表 + 唯一请求 ID(推荐方案)备选:- 数据库唯一索引- 状态机控制 1. 所有写操作必须加幂等2. 请求 ID 必须由前端生成3. 异常时要删除 Redis Key
防重放攻击 🛡️ 1. 中间人截取请求重复发送2. 签名被破解后批量攻击 签名 + 时间戳 + nonce 随机数1. 签名有效期 5-15 分钟2. nonce 存入 Redis 去重3. 关键接口增加 IP 白名单 1. 禁止使用固定密钥2. 密钥定期轮换3. 监控异常签名请求
大流量限流熔断 🚀 1. 突发流量打垮服务2. 下游服务故障导致雪崩 Redis 分布式限流 + Sentinel 熔断1. 限流:令牌桶算法2. 熔断:慢调用比例 + 异常比例3. 降级:返回默认值或缓存数据 1. 限流阈值要压测得出2. 熔断后要有告警3. 核心接口不能降级
敏感数据脱敏 🔒 1. 不同字段脱敏规则不同2. 业务代码零散处理容易遗漏 Jackson 自定义序列化器 + 注解 java@Sensitive(SensitiveType.PHONE)private String phone; 1. 日志也要做脱敏2. 内部接口同样需要脱敏3. 禁止返回完整身份证号
API 版本兼容 🔄 1. 旧版本无法立即下线2. 多个版本并行维护成本高 URL 路径版本 + 灰度发布1. 版本号使用 v1、v2 整数2. 旧版本保留 3-6 个月3. 废弃接口添加告警 1. 版本内保证向下兼容2. 禁止在旧版本上新增功能3. 监控旧版本调用量

补充:面试加分项 🌟

如果面试官继续追问,可以补充这两点:

  1. 灰度发布:通过网关根据用户 ID 或流量比例切流到新版本 API,降低发布风险
  2. API 网关:将认证、限流、日志、监控等通用逻辑下沉到网关层,业务服务只关注核心逻辑

真实面试模拟

真实面试模拟

面试官 😊:

欢迎,咱们直入正题。今天这道场景设计题很常见:"如果让你从零设计一套对外的API接口,你会怎么考虑?" 就从你踩过的坑,想到哪说到哪。

候选人 🧑‍💻:

好的面试官,对外API就是一份合同,我会把它当成"法务条文"来设计。核心抓住七个点:接口契约、统一响应、安全三板斧、版本管理、流量控制与幂等、监控文档、异步处理。我按一条请求的生命周期展开说吧。

面试官 👂:

行,先从最直观的开始。你怎么定接口规范,让调用方一眼就能看懂

候选人 🧑‍💻:

我严格遵循 RESTful 风格,做到"望文生义":

  • URL 只用名词复数,层级不超过两层,如 /api/v1/orders/{orderId}/items,一看就知道在操作订单下的商品。
  • HTTP 方法语义固定:GET查、POST创、PUT全量改、PATCH部分改、DELETE删。
  • 状态码精打细磨:200/201成功,400参数错,401没认证,403没权限,404不存在,409冲突,429限流,500系统异常。保证调用方只抓状态码就能判断大类。

面试官 💬:

不错,那返回体结构你怎么统一? 我不希望每个接口格式五花八门。

候选人 🧑‍💻:

我强制所有接口都套一层统一响应信封,长这样:

json 复制代码
{
  "code": 200,
  "message": "success",
  "data": { ... },
  "traceId": "a1b2c3d4-xxxx"
}
  • code 是业务错误码,能唯一定位问题。
  • traceId 必须全链路透传,调用方查日志时直接甩这个ID过来,省得扯皮。
  • 错误时 data 为空,message 说人话,但绝不暴露数据库或堆栈信息。

面试官 🛡️:

统一格式是基本功。接下来是重头戏:对外接口的安全你怎么落地? 真怕被人一晚上打穿。

候选人 🧑‍💻:

这块必须上安全三板斧,我画个时序图更清晰:

具体落地:

  • 身份+防篡改 :给每个合作方发 AppKey + AppSecret,要求请求头带上 HMAC-SHA256 签名。把 timestampnonce(随机数)、请求体拼起来算签,服务端重算对比。
  • 防重放 :用 timestampnonce 联合约束。timestamp 偏离服务端时间超过5分钟直接拒掉;nonce 存入 Redis,有效期5分钟,出现重复直接视为重放攻击。
  • 传输:全站HTTPS,HSTS强制。

面试官 🤔:

挺严密。那我再挑个刺儿:如果调用方坚持自己生成幂等key,不信任你的,怎么防止key冲突?

候选人 🧑‍💻:

这是个常见的信任问题。我会在调用方传来的key前面强制加上AppKey前缀 ,变成 app_123:order_submit:20260609。这样既隔离了不同租户,也避免恶意伪造撞库。同时限制key总长度,防止撑爆Redis。

面试官 🔄:

安全过关。接下来,你怎么做版本管理,避免升级时被调用方骂?

候选人 🧑‍💻:

很简单,永不原地修改 。URL路径显式带版本号,如 /api/v1/。一旦有破坏性变更,直接发 /api/v2/,老版本 v1 保持可用至少一个季度,并提前发邮件定好日落时间。最忌讳的就是偷偷改字段类型,那是在制造线上事故💥。

面试官 ⚡:

线上突发流量怎么扛?比如双十一,下游慢怎么办?

候选人 🧑‍💻:

这要靠限流+降级+幂等组合拳。

  • 限流 :网关层按 AppKey 用令牌桶,每个key 100QPS。超了直接回 429,并带上 Retry-After 头。达到80%阈值就告警,准备扩容。
  • 熔断降级:内部服务慢了,熔断器打开,直接返回缓存降级数据,不死扛导致雪崩。
  • 写操作幂等 :所有创建、扣款接口,必须 要求调用方传 Idempotent-Key。我用 Redis 的 setnx 锁住这个key,处理成功就把结果缓存起来,重复请求直接返回缓存结果。这样就算网超重试,也只成功一次。
text 复制代码
客户端重试
              │
              ▼
  ┌──────────────────────┐
  │ 带 Idempotent-Key 请求│
  └──────────┬───────────┘
             ▼
      ┌──────────────┐
      │ Redis 存在key?│
      └──┬───────┬───┘
     存在 │       │ 不存在
         ▼       ▼
   返回缓存结果  执行业务
                 │
                 ▼
          结果缓存到Redis(锁定15天)
                 │
                 ▼
            返回结果

面试官 👨‍🏫:

嗯,这个图把幂等逻辑讲得很透。

面试官 📊:

接口上线后就是运营了。你怎么让调用方用得爽,自己排查快?

候选人 🧑‍💻:

两手抓:监控与文档

  • 可观测性:QPS、延迟p99、错误率全上Grafana大盘,错误率突增即时钉钉/飞书告警。
  • 在线文档 :用OpenAPI 3.0自动生成Swagger页面,调用方能在网页上直接试参数。文档里必须写清错误码、限流规则、幂等key用法,最好提供 Java/Python SDK,帮他们封装好签名、重试、幂等注入,十行代码搞定接入,对接方自然粘性高 🤝。

面试官 ⏳:

最后一个场景,如果接口需要导出大报表,耗时很长,怎么设计交互?

候选人 🧑‍💻:

绝不同步阻塞。接口立即返回 202 Accepted,body里给一个 taskId。调用方用 /api/v1/tasks/{taskId} 轮询进度。如果甲方有回调能力,我们支持提前注册 webhook,任务一完成主动通知。体验拉满,还不占用连接数。

面试官 🧐:

整体设计没问题,看得出工程经验。不过我好奇具体落地,能不能挑几个核心代码片段,展示下你的技术亮点?另外,总结一下这种对外API设计的几个硬骨头和你的解法

候选人 🧑‍💻:

没问题,我挑三段最能体现亮点的代码,然后梳理一张难点攻克表。

💻 核心代码 & 技术亮点

① 统一响应信封 + 全局异常翻译

不写死,用枚举管理错误码,并借助 @RestControllerAdvice 把异常自动翻译成标准信封,避免 try-catch 满天飞。

java 复制代码
// 统一响应体
@Data
@AllArgsConstructor
public class ApiResponse<T> {
    private int code;
    private String message;
    private T data;
    private String traceId;

    public static <T> ApiResponse<T> success(T data) {
        return new ApiResponse<>(200, "success", data, MDC.get("traceId"));
    }

    public static ApiResponse<Void> error(ErrorCode errorCode) {
        return new ApiResponse<>(errorCode.getCode(), errorCode.getMessage(), null, MDC.get("traceId"));
    }
}

// 全局异常处理,任何未捕获异常都会变成标准格式
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(BizException.class)
    public ApiResponse<Void> handleBizException(BizException e) {
        return ApiResponse.error(e.getErrorCode());
    }

    @ExceptionHandler(Exception.class)
    public ApiResponse<Void> handleException(Exception e) {
        log.error("系统异常", e);
        return ApiResponse.error(ErrorCode.SYSTEM_ERROR);
    }
}

🌟 亮点:利用 MDC 透传 traceId,响应里自动带出,配合日志系统,一条链路不漏。

② 签名验证拦截器 (HMAC-SHA256 + 防重放)

这是安全核心,抽成独立拦截器,不侵入业务代码。

java 复制代码
@Component
public class SignatureInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        String appKey = request.getHeader("X-AppKey");
        String timestamp = request.getHeader("X-Timestamp");
        String nonce = request.getHeader("X-Nonce");
        String clientSign = request.getHeader("X-Sign");

        // 1. 时间戳偏移校验 (5分钟窗口)
        if (Math.abs(System.currentTimeMillis() - Long.parseLong(timestamp)) > 300_000) {
            throw new BizException(ErrorCode.TIMESTAMP_EXPIRED);
        }

        // 2. nonce防重放 Redis setnx 原子排他
        String nonceKey = "nonce:" + appKey + ":" + nonce;
        Boolean locked = redisTemplate.opsForValue().setIfAbsent(nonceKey, "1", 5, TimeUnit.MINUTES);
        if (Boolean.FALSE.equals(locked)) {
            throw new BizException(ErrorCode.REPLAY_ATTACK);
        }

        // 3. 重算签名 (参数+body)
        String body = getRequestBody(request); // 缓存body的wrapper
        String sign = HmacUtils.hmacSha256Hex(appSecretMap.get(appKey),
                appKey + timestamp + nonce + body);
        if (!sign.equals(clientSign)) {
            throw new BizException(ErrorCode.SIGN_INVALID);
        }
        return true;
    }
}

🌟 亮点:setIfAbsent 天然原子性,既防重放又避免锁竞争;签名材料包含 body,防请求体篡改。

③ 幂等注解 + AOP(切面自动处理)

不想每个方法都写重复逻辑,用声明式注解让业务方一个 @Idempotent 搞定。

java 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    String keyPrefix() default "";   // 业务前缀
    long lockTime() default 15;      // 结果缓存天数
}

@Aspect
@Component
public class IdempotentAspect {

    @Around("@annotation(idempotent)")
    public Object around(ProceedingJoinPoint point, Idempotent idempotent) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(
                RequestContextHolder.getRequestAttributes())).getRequest();
        String idempotentKey = request.getHeader("Idempotent-Key");
        if (StringUtils.isBlank(idempotentKey)) {
            throw new BizException(ErrorCode.MISSING_IDEMPOTENT_KEY);
        }

        String finalKey = request.getHeader("X-AppKey") + ":" + idempotent.keyPrefix() + ":" + idempotentKey;
        String result = redisTemplate.opsForValue().get(finalKey);
        if (result != null) {
            return JSON.parseObject(result, ApiResponse.class); // 直接返回缓存结果
        }

        Object proceed = point.proceed();
        redisTemplate.opsForValue().set(finalKey, JSON.toJSONString(proceed),
                idempotent.lockTime(), TimeUnit.DAYS);
        return proceed;
    }
}

🌟 亮点:AOP 零侵入,缓存完整响应对象,真正"第一次干活,后续全部秒返回";key 自动注入租户前缀,隔离安全。

🧗 技术难点 & 解决方案

对外 API 不是"能用就行",这些硬骨头啃不掉就会天天救火 🚒。

技术难点 核心风险 解决方案 落地关键点
高并发幂等性 网络重试导致重复扣款/重复下单 基于Redis原子SETNX + 结果缓存 强制要求Idempotent-Key,键名包含租户前缀防冲突
防重放与防篡改 攻击者抓包重放,数据被改 HMAC签名 + 时间窗口 + nonce一次性令牌 签名包含请求体、5分钟窗口、nonce Redis暂存
版本无缝升级 老接口废弃导致调用方瘫痪 URL路径版本号 + 日落期通知 v1/v2并存,至少保留一个季度,提前邮件周知
突发流量雪崩 双十一流量打垮下游,级联故障 网关令牌桶限流 + 业务熔断降级 超过阈值返回429,预警告警,降级返回兜底数据
大任务阻塞连接 导出报表等长耗时导致HTTP超时 异步化:202 Accepted + 轮询/Webhook 立即返回taskId,后台处理,支持回调URL主动通知
调用方接入痛苦 签名、幂等、重试逻辑复杂导致弃用 提供多语言SDK封装 + 在线交互式文档 OpenAPI生成文档,SDK仅10行代码完成完整调用
问题定位困难 跨系统调用链断裂,扯皮耗时 全链路traceId + 统一错误码字典 网关注入traceId,所有响应和日志携带,Grafana串联

最后再附一张防御体系全景图,方便记忆:

这个图把最核心的几道防线串起来了,每一层都在保护后面的系统 🤺。

面试官 🚀:

非常好,从代码到难点体系都讲得很实,看得出你是真刀真枪在线上扛过的。这一题咱们就完美收尾,准备进入下个领域。


公众号"Rain的Java大神之路"

个人博客"www.javadashen.com"

相关推荐
Rain的Java大神实战圈3 天前
如何保证接口幂等
场景设计题
Rain的Java大神实战圈4 天前
PC版网站被狂刷怎么处理
场景设计题
Rain的Java大神实战圈5 天前
接口被狂刷怎么处理
场景设计题
Rain的Java大神实战圈6 天前
线上在Docker中跑MySQL会怎样
场景设计题
Rain的Java大神实战圈7 天前
Docker搭建Redis集群完全指南
场景设计题
Rain的Java大神实战圈10 天前
短信接口被狂刷怎么处理
场景设计题
Rain的Java大神实战圈11 天前
如何避免订单重复提交
场景设计题
Rain的Java大神实战圈12 天前
MySQL误删数据如何恢复
场景设计题
Rain的Java大神实战圈13 天前
介绍一下分布式事务
场景设计题