Open-API 前后端架构报告¶
💡 一句话概述
基于 Koa.js 的高性能 API 计费网关,支持按月/按量两种计费模式,采用 Redis + MongoDB 双存储架构实现实时计数与持久化。
🔑 核心概念¶
- 计费网关:RESTful API 网关,通过
X-API-Key认证,支持按月固定额度和按调用量两种计费模式 - 双存储架构:Redis 负责高频实时计数与速率限制,MongoDB 负责持久化存储与聚合统计
- Koa 洋葱模型:三层中间件链 — 错误处理 → 请求体解析 → 速率限制,层层穿透
📝 系统架构¶
分层设计¶
┌─────────────────────────────────────────────────┐
│ src/app.js (入口) │
├─────────────────────────────────────────────────┤
│ src/routes/ (路由层) │
│ ├── api.js (API 代理路由) │
│ └── billing.js (计费管理路由) │
├─────────────────────────────────────────────────┤
│ src/middleware/ (中间件层) │
│ ├── rateLimiter.js (速率限制) │
│ └── errorHandler.js (错误处理) │
├─────────────────────────────────────────────────┤
│ src/services/ (业务逻辑层) │
│ └── BillingService.js (计费服务) │
├─────────────────────────────────────────────────┤
│ src/models/ (数据模型层) │
│ ├── User.js (用户模型) │
│ └── Usage.js (使用记录模型) │
├─────────────────────────────────────────────────┤
│ src/utils/ (工具层) │
│ ├── redis.js (Redis 客户端) │
│ └── logger.js (日志工具) │
└─────────────────────────────────────────────────┘
数据流¶
┌──────────────┐ X-API-Key ┌──────────────┐
│ API 调用方 │ ──────────────→ │ Koa.js │
│ (客户端) │ ← ──────────── │ Gateway │
└──────────────┘ JSON Response └──────┬───────┘
│
┌─────────────────────┼─────────────────────┐
│ │ │
┌─────▼─────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ MongoDB │ │ Redis │ │ Winston │
│ (持久化) │ │ (缓存/限流) │ │ (日志) │
└───────────┘ └─────────────┘ └─────────────┘
🛣️ 路由设计¶
| 方法 | 路径 | 说明 | 认证 |
|---|---|---|---|
GET |
/billing/usage/:year/:month |
获取指定月份使用统计 | X-API-Key |
GET |
/billing/bill/:year/:month |
获取指定月份账单 | X-API-Key |
PUT |
/billing/plan |
更新计费方案 | X-API-Key |
* |
/api/* |
API 代理路由(引用缺失) | — |
⚡ 中间件层¶
速率限制 (rateLimiter.js)¶
基于 Redis 的滑动窗口速率限制:
| 参数 | 值 | 说明 |
|---|---|---|
| 窗口大小 | 60 秒 | RATE_LIMIT_WINDOW |
| 最大请求数 | 100 | MAX_REQUESTS |
| Redis Key | ratelimit:{apiKey} |
按 API Key 隔离 |
工作流程:提取 X-API-Key → Redis INCR + EXPIRE 原子计数 → 注入 X-RateLimit-* 响应头 → 超限返回 429。Redis 异常时降级放行。
错误处理 (errorHandler.js)¶
Koa 洋葱模型全局 try-catch,统一返回结构化错误:
💰 BillingService 核心逻辑¶
| 方法 | 功能 | 外部依赖 |
|---|---|---|
recordUsage(userId, endpoint) |
记录 API 调用使用量 | User, Usage, Redis |
getMonthlyUsage(userId, year, month) |
查询月度使用统计 | Usage |
generateMonthlyBill(userId, year, month) |
生成月度账单 | User, Usage |
recordUsage 流程:
查询用户 → 校验用户状态
├─ 按量计费(usage):cost = user.usageRate
└─ 按月计费(monthly):
├─ 聚合查询当月已用请求数
└─ 超出配额则抛出 'Monthly quota exceeded'
→ Redis HINCRBY 记录实时计数(7天过期)
→ MongoDB upsert 持久化使用记录
→ 按量计费时扣减用户余额
计费模式对比:
| 特性 | 按月计费 (monthly) | 按量计费 (usage) |
|---|---|---|
| 费用计算 | 固定月费 | 每次调用 × 费率 |
| 配额检查 | 有(月度请求上限) | 无 |
| 余额扣减 | 无 | 每次调用扣减 |
| 适用场景 | 稳定调用量 | 波动调用量 |
📊 数据模型¶
User 模型¶
| 字段 | 类型 | 说明 |
|---|---|---|
username |
String | 用户名(唯一) |
apiKey |
String | API 密钥(唯一) |
billingPlan |
String | 计费模式:monthly / usage |
monthlyQuota |
Number | 月度配额 |
usageRate |
Number | 单次调用费率 |
balance |
Number | 账户余额,默认 0 |
isActive |
Boolean | 账户激活状态 |
Usage 模型¶
| 字段 | 类型 | 说明 |
|---|---|---|
userId |
ObjectId | 关联用户 |
endpoint |
String | API 端点路径 |
requestCount |
Number | 请求次数 |
cost |
Number | 单次调用费用 |
billingPeriod.year |
Number | 计费年份 |
billingPeriod.month |
Number | 计费月份 |
索引:复合索引 { userId: 1, 'billingPeriod.year': 1, 'billingPeriod.month': 1 }
🔧 工具层¶
Redis 客户端 (utils/redis.js)¶
单例模式,封装 get/set/hincrby/expire 四个核心方法。通过 REDIS_URL 环境变量配置。
日志工具 (utils/logger.js)¶
Winston 框架,生产环境 info 级别,开发环境 debug 级别。输出至 error.log、combined.log 和控制台。
📦 核心依赖¶
| 依赖 | 版本 | 用途 |
|---|---|---|
koa |
^2.14.2 | HTTP 框架 |
koa-router |
^12.0.1 | 路由管理 |
mongoose |
^7.5.0 | MongoDB ODM |
redis |
^4.6.8 | Redis 客户端 |
winston |
^3.10.0 | 日志框架 |
joi |
^17.10.1 | 数据验证 |
dotenv |
^16.3.1 | 环境变量加载 |
🏗️ 性能优化策略¶
| 策略 | 实现方式 | 效果 |
|---|---|---|
| Redis 速率限制 | 原子 INCR + EXPIRE |
毫秒级限流判断 |
| Redis 使用量缓存 | HINCRBY 实时计数 |
避免高频 MongoDB 写入 |
| MongoDB 聚合索引 | 复合索引 {userId, year, month} |
加速月度统计查询 |
| 异步持久化 | Redis 先写 → MongoDB 后写 | 降低 API 响应延迟 |
| 连接池复用 | Mongoose/Redis 连接单例 | 降低连接开销 |
⚠️ 已知问题¶
待修复项
src/routes/api.js缺失 —app.js引用但文件不存在src/middleware/auth.js缺失 —billing.js引用validateApiKey但文件未定义- User 模型未导入 —
billing.js:32使用User.findByIdAndUpdate但未 import - mongoose 未导入 —
BillingService.js:89使用mongoose.Types.ObjectId但未 import
🗂️ 项目文件清单¶
Open-API/
├── README.md
├── package.json
└── src/
├── app.js # 应用入口
├── models/
│ ├── User.js # 用户数据模型
│ └── Usage.js # 使用记录数据模型
├── services/
│ └── BillingService.js # 计费业务逻辑
├── routes/
│ └── billing.js # 计费管理路由
├── middleware/
│ ├── rateLimiter.js # 速率限制中间件
│ └── errorHandler.js # 错误处理中间件
└── utils/
├── redis.js # Redis 客户端封装
└── logger.js # Winston 日志配置
🔗 相关链接¶
- Open-API GitHub 仓库 — 项目源码
- 简历技术要点 — 基于源码提炼的面试技术要点