我做的基础服务项目,是如何实现 API 安全与限流的(短信、邮件、文件上传、钉钉通知)
一、背景
最近我做了一个基础服务项目,主要对外提供短信、邮件、文件上传和钉钉通知等基础功能。这些接口是多个业务系统都要调用的,所以安全性、稳定性、限流控制必须得做好。
这篇文章就记录一下,我是怎么一步步设计这个系统,让它既能安全对接多个项目,又能抗住高并发调用的。
二、我遇到了什么问题?
这个系统上线初期,其实就碰到了几个问题:
- 接口被非法调用:没有权限的系统也能调用我们的短信接口,导致误发短信。
- 被刷接口:有个业务系统调用频繁,导致我们服务被打崩,日志里全是超时。
- 多项目对接混乱:不同项目调用同一接口,但权限、限流、日志都不一样,不好管理。
这些问题让我意识到,必须从架构层面统一解决 API 安全、限流、多项目对接等问题。
三、我是怎么做的?
我最终设计了一套统一的安全和限流机制,结合 Redis、拦截器、Lua 脚本等技术,解决了这些问题。
1. API 接口安全设计:API Key + Redis 校验
我给每个接入的项目分配一个唯一的 API Key
,所有请求都必须在 Header 中带上 X-API-Key
。
Key 信息存在 Redis 中,包括:
- 租户 ID(tenantId)
- 是否启用
- 过期时间
每次请求进来,拦截器会先校验 Key 是否有效。无效的直接返回 401。
实现亮点:
- 使用 Redis 存储 Key,读取快,不影响性能
- 拦截器统一处理,逻辑清晰
- 可结合租户 ID 做日志追踪、限流隔离等
示例拦截器代码:
@Component
public class ApiKeyInterceptor implements HandlerInterceptor {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
String apiKey = request.getHeader("X-API-Key");
if (StringUtils.isBlank(apiKey)) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Missing API Key");
return false;
}
String value = redisTemplate.opsForValue().get("api_key:" + apiKey);
if (value == null) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Invalid API Key");
return false;
}
JSONObject json = JSON.parseObject(value);
boolean enabled = json.getBoolean("enabled");
long expireTime = json.getLong("expireTime");
if (!enabled || System.currentTimeMillis() > expireTime) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "API Key expired or disabled");
return false;
}
return true;
}
}
2. 限流设计:Redis + Lua 实现滑动窗口限流
一开始我用的是 Guava 的限流组件,但发现它只适用于单机。后来我换成了 Redis + Lua 脚本,实现了分布式的滑动窗口限流。
Lua 脚本:
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local windowSize = tonumber(ARGV[2])
local now = tonumber(ARGV[3])
redis.call('ZREMRANGEBYSCORE', key, 0, now - windowSize * 1000)
local count = redis.call('ZCARD', key)
if count >= limit then
return false
else
redis.call('ZADD', key, now, now)
return true
end
Java 调用逻辑:
@Service
public class RateLimitService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
private static final Long LIMIT = 100L; // 每分钟最多100次
private static final Long WINDOW_SIZE = 60L; // 时间窗口60秒
public boolean isAllowed(String apiKey) {
String key = "rate_limit:" + apiKey;
long now = System.currentTimeMillis();
DefaultRedisScript<Boolean> script = new DefaultRedisScript<>(luaScript, Boolean.class);
return redisTemplate.execute(script,
Arrays.asList(key),
LIMIT.toString(), WINDOW_SIZE.toString(), String.valueOf(now));
}
}
然后我在拦截器中调用限流逻辑,拒绝超过限制的请求。
实现亮点:
- 支持分布式限流,适合多节点部署
- 滑动窗口比固定窗口更精准
- Lua 脚本保证原子性,避免并发问题
3. 多项目对接策略
为了支持多个项目接入,我做了以下几点:
- 所有 API Key 绑定租户 ID,便于日志追踪、限流隔离
- 统一网关(Nginx + Spring Cloud Gateway)做路由、鉴权、限流
- 用 Nacos 管理 API Key、限流规则等配置,支持运行时热更新
四、难点与亮点总结
难点:
如何在分布式环境下实现限流?
答案是 Redis + Lua 脚本,保证原子性,避免并发问题。如何防止非法调用?
API Key + Redis 校验机制,拦截器统一处理,简单有效。如何支持多项目对接?
给每个项目分配独立的 API Key,绑定租户信息,限流、日志、权限都按租户隔离。
亮点:
- 模块清晰,易于扩展:短信、邮件、文件上传等功能模块化,方便维护。
- 统一安全机制:API Key + 限流,保障系统安全。
- 支持高并发:Redis + Lua 脚本应对分布式限流,性能稳定。
- 可扩展性强:后续可接入 Nacos、Prometheus、日志系统等,形成完整生态。
五、效果如何?
这套方案在我们公司已经上线使用几个月了,日均调用量几万次,没出过安全问题,也没被刷崩过,效果还不错。
- 接口安全性大幅提升
- 限流机制有效防止了刷接口
- 多项目接入统一管理,效率提高
如果你也在做类似的平台,或者想搭建一个对外的基础服务系统,可以参考这套架构,亲测可用。