diff --git a/docs/project/openapi-billing-architecture.md b/docs/project/openapi-billing-architecture.md new file mode 100644 index 0000000..8c88860 --- /dev/null +++ b/docs/project/openapi-billing-architecture.md @@ -0,0 +1,241 @@ +# Open-API 前后端架构报告 + +!!! note "💡 一句话概述" + 基于 Koa.js 的高性能 API 计费网关,支持按月/按量两种计费模式,采用 Redis + MongoDB 双存储架构实现实时计数与持久化。 + +--- + +## 🔑 核心概念 + +1. **计费网关**:RESTful API 网关,通过 `X-API-Key` 认证,支持按月固定额度和按调用量两种计费模式 +2. **双存储架构**:Redis 负责高频实时计数与速率限制,MongoDB 负责持久化存储与聚合统计 +3. **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,统一返回结构化错误: + +```json +{ + "error": { + "message": "错误描述", + "status": 500, + "timestamp": "2026-08-27T00:00:00.000Z" + } +} +``` + +--- + +## 💰 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 连接单例 | 降低连接开销 | + +--- + +## ⚠️ 已知问题 + +!!! warning "待修复项" + - `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 仓库](https://github.com/kainy/Open-API) — 项目源码 +- [简历技术要点](openapi-billing-resume.md) — 基于源码提炼的面试技术要点 diff --git a/docs/project/openapi-billing-resume.md b/docs/project/openapi-billing-resume.md new file mode 100644 index 0000000..c40a73d --- /dev/null +++ b/docs/project/openapi-billing-resume.md @@ -0,0 +1,164 @@ +# Open-API 简历技术要点 + +!!! note "💡 一句话概述" + 基于项目源码实际实现提炼的 8 个核心技术要点,每个均可深入到函数级别展开,适合简历项目描述和面试准备。 + +--- + +## 🔑 核心概念 + +1. **高性能 API 计费网关**:Koa.js + Redis + MongoDB 双存储架构,支撑按月/按量两种计费模式 +2. **分布式速率限制**:Redis 滑动窗口算法 + 事务保证原子性,优雅降级策略保障可用性 +3. **数据一致性设计**:Redis 先写 → MongoDB 后写策略,在响应速度与数据可靠性间取得平衡 + +--- + +## 📝 技术要点详解 + +### 1. 高性能 API 计费网关设计 + +基于 Koa.js 构建 RESTful API 计费网关,支持按月固定额度和按调用量两种计费模式。 + +**Redis + MongoDB 双存储架构:** + +- **Redis**:通过 `HINCRBY` 原子操作实现实时使用量计数(高频写入场景) +- **MongoDB**:通过 `$match` + `$group` 聚合管道进行月度统计与账单生成(低频读取场景) +- **幂等写入**:使用 `findOneAndUpdate` upsert 操作保证使用记录的幂等性 +- **索引优化**:复合索引 `{userId, billingPeriod.year, billingPeriod.month}` 优化聚合查询 +- **缓存策略**:Redis Key 设置 7 天 TTL 自动过期,平衡内存与数据时效性 + +--- + +### 2. 基于 Redis 的分布式速率限制 + +采用滑动窗口算法实现 API 级别速率限制。 + +**实现细节:** + +- Redis `INCR` 原子递增 + `EXPIRE` 设置 60 秒窗口 +- 单 API Key 最大 100 次请求/分钟 +- Redis `MULTI` 事务保证计数与过期设置的原子性 +- 响应 Header 注入 `X-RateLimit-Limit` 和 `X-RateLimit-Remaining` + +**降级策略:** Redis 连接异常时自动放行请求(`await next()`),避免缓存层故障导致服务不可用。超限返回 `429 Too Many Requests`,与标准 HTTP 语义对齐。 + +--- + +### 3. 多层中间件链与全局错误处理 + +基于 Koa 洋葱模型设计三层中间件链: + +``` +errorHandler(全局异常捕获)→ bodyParser(JSON 解析)→ rateLimiter(速率限制 + 认证) +``` + +**错误处理机制:** + +- try-catch 包裹整个请求生命周期,统一捕获同步/异步异常 +- 返回结构化错误响应 `{message, status, timestamp}`,避免堆栈信息泄露 +- `ctx.app.emit('error', err, ctx)` 触发应用级错误事件,为监控告警提供扩展点 +- 速率限制中间件在 Redis 异常时降级放行,保证系统可用性优先 + +--- + +### 4. 计费服务核心逻辑与配额管理 + +`BillingService` 类封装计费核心逻辑,通过静态方法实现无状态服务设计。 + +**按月计费模式:** + +- MongoDB 聚合管道(`$match` + `$group`)实时计算当月已用请求数 +- 超出 `monthlyQuota` 配额时抛出异常拦截后续调用 + +**按量计费模式:** + +- 每次调用按 `usageRate` 费率扣减用户 `balance` 字段 +- `$inc` 原子操作保证并发安全 + +**账单生成:** 按月取固定费用,按量按端点维度聚合 `requestCount × cost`。 + +**数据一致性:** 所有写操作遵循"Redis 先写 → MongoDB 后写"策略,保证响应速度的同时确保数据最终一致性。 + +--- + +### 5. Redis 单例客户端封装 + +实现 `RedisClient` 静态工具类,采用懒加载单例模式管理 Redis 连接。 + +**设计要点:** + +- 首次调用 `connect()` 时创建连接并缓存至静态属性 `client`,后续直接复用 +- 封装 `get/set/hincrby/expire` 四个核心方法,屏蔽底层 API 差异 +- `set` 方法支持可选 `expireSeconds` 参数,内部自动选择 `setEx` 或 `set` +- 连接地址通过 `REDIS_URL` 环境变量配置,支持不同环境无缝切换 +- 连接生命周期事件(`error`/`connect`)接入 Winston 日志 + +--- + +### 6. Winston 多通道日志系统 + +基于 Winston 构建分级日志系统: + +| 配置 | 生产环境 | 开发环境 | +|------|---------|---------| +| 日志级别 | `info` | `debug` | +| 格式 | JSON + 时间戳 | JSON + 时间戳 | +| error.log | ✅ 仅错误级别 | ✅ 仅错误级别 | +| combined.log | ✅ 全部级别 | ✅ 全部级别 | +| 控制台输出 | ❌ | ✅ 带颜色高亮 | + +通过 `NODE_ENV` 环境变量自动切换日志策略,JSON 格式便于 ELK/日志平台采集解析。 + +--- + +### 7. MongoDB 数据模型与索引优化 + +**User 模型:** + +- `apiKey` 唯一索引支撑 API Key 认证 +- `billingPlan` 枚举字段区分计费模式 + +**Usage 模型:** + +- `billingPeriod` 嵌入式文档实现时间分区 +- 复合索引 `{userId, year, month}` 支撑高效聚合查询 +- `mongoose.Schema.Types.ObjectId` + `ref` 建立用户-使用记录关联,支持 `$lookup` 联表 +- `requestCount` 字段配合 `$inc` 原子递增,避免并发计数丢失 + +--- + +### 8. 环境变量驱动的配置管理 + +**安全隔离策略:** + +- `dotenv` 实现环境变量集中管理,敏感配置不硬编码于源码 +- `.env` 文件隔离不同环境配置,配合 `.gitignore` 防止敏感信息泄露 +- API 密钥通过 `X-API-Key` header 传递,避免 URL 参数暴露风险 +- 错误响应仅返回 `message` 和 `status`,不泄露内部堆栈或数据库查询细节 + +**核心配置项:** + +| 配置项 | 说明 | +|--------|------| +| `PORT` | 服务端口,默认 3000 | +| `MONGODB_URI` | MongoDB 连接地址 | +| `REDIS_URL` | Redis 连接地址 | +| `NODE_ENV` | 运行环境 | + +--- + +## ⚠️ 面试展开建议 + +!!! warning "准备要点" + - **计费模式**:能画出 `recordUsage` 的完整流程图,解释按月/按量的分支逻辑 + - **Redis vs MongoDB**:为什么高频写入用 Redis、统计聚合用 MongoDB?各自的优势场景 + - **速率限制降级**:Redis 挂了怎么办?为什么选择降级放行而不是阻断请求? + - **数据一致性**:Redis 先写 MongoDB 后写,如果 MongoDB 写入失败怎么处理? + - **原子操作**:`HINCRBY`、`$inc`、`MULTI` 分别在什么场景下使用? + +--- + +## 🔗 相关链接 + +- [Open-API GitHub 仓库](https://github.com/kainy/Open-API) — 项目源码 +- [前后端架构报告](openapi-billing-architecture.md) — 完整架构分析 diff --git a/mkdocs.yml b/mkdocs.yml index 3b8a1ea..b238f6b 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -86,6 +86,9 @@ nav: - 首页: index.md - 项目: - project/index.md + - Open-API 计费平台: + - project/openapi-billing-architecture.md + - 简历技术要点: project/openapi-billing-resume.md - 架构: - architecture/index.md - 缓存: