我如何使用 Node.js、Express 和 MongoDB 构建星座兼容性 API
Source: Dev.to
从一个简单的想法到生产就绪的 API
有没有想过约会应用或星座平台在后台是如何计算星座匹配度的?在前端看似简单的“匹配分数”,往往是经过精心构建的数据、设计良好的算法以及可扩展的后端架构共同作用的结果。
我在为一个真实的生产网站构建爱情匹配功能时正面临这个挑战。目标很直接:给定两个星座,返回有意义的匹配分数以及相应的背景洞察。实现过程中需要在数据建模、API 结构、缓存和性能等方面做出深思熟虑的设计选择。
在本指南中,你将看到我如何使用 Node.js、Express 和 MongoDB 构建 星座匹配 API——从最初的架构决策到部署考量。该 API 已在生产环境中实际使用,每天处理数千次请求,为真实的匹配检查提供动力。
阅读完本指南后,你将了解如何:
- 设计简洁的 REST API
- 实现确定性的匹配算法
- 为可扩展性组织项目结构
- 在生产环境中部署并监控服务
技术栈和先决条件
核心技术
- Node.js – 运行时环境
- Express.js – REST API 框架
- MongoDB – 用于兼容矩阵和元数据的数据存储
- Mongoose – MongoDB 的 ODM
- Redis(可选)– 缓存层
- External Astrology API(可选)– 用于每日星座运势的丰富
先决条件
您应熟悉:
- JavaScript(ES6+)
- RESTful API 概念
- 基本的 MongoDB 查询
- Express 中间件模式
项目架构和文件夹结构
干净的结构对长期可维护性至关重要。下面是我使用的布局:
/zodiac-compatibility-api
│
├─ src
│ ├─ controllers # Request handling logic
│ ├─ models # Mongoose schemas
│ ├─ routes # Express routers
│ ├─ services # Business logic (e.g., compatibility engine)
│ ├─ utils # Helper functions (validation, caching)
│ └─ index.js # Application entry point
│
├─ config # Environment‑specific settings
├─ tests # Unit / integration tests
└─ Dockerfile, docker‑compose.yml, etc.
这种分离使得每一层——路由、业务逻辑、数据和工具——能够独立演进。
定义核心 API 端点
API 设计时注重简洁性和可扩展性。
| 方法 | 端点 | 描述 |
|---|---|---|
GET | /compatibility/:sign1/:sign2 | 返回两星座的兼容性分数和描述 |
POST | /calculate-match | 接受包含星座(以及可选的用户数据)的 JSON 负载,并返回匹配结果 |
GET | /daily-horoscope/:sign (可选) | 用当天的星座运势丰富响应 |
GET | /health | 用于监控的简易健康检查 |
每个端点专注于单一职责,并返回可预测的 JSON 响应。
Source: …
在 MongoDB 中建模星座兼容性
与其将所有兼容性逻辑硬编码在代码里,我把兼容性矩阵存储在 MongoDB 中。这样可以在不重新部署应用的情况下进行后续调优。
模式示例(简化版):
// models/Compatibility.js
const { Schema, model } = require('mongoose');
const CompatibilitySchema = new Schema({
signA: { type: String, required: true, uppercase: true },
signB: { type: String, required: true, uppercase: true },
score: { type: Number, required: true }, // 0‑100
shortDescription: { type: String },
longDescription: { type: String },
// optional: { shortTermScore, longTermScore, ... }
}, { timestamps: true });
CompatibilitySchema.index({ signA: 1, signB: 1 }, { unique: true });
module.exports = model('Compatibility', CompatibilitySchema);
该集合支持:
- 双向查询 – 无论星座顺序如何,都能正常查询。
- 丰富描述 – 为 UI 显示提供简短和详细文本。
- 未来扩展 – 例如,分别为短期关系和长期关系设置分数。
基础评分函数
核心算法刻意保持确定性,且易于调优。
// services/compatibilityService.js
/**
* Calculate compatibility between two zodiac signs.
* @param {string} sign1 - First sign (case‑insensitive)
* @param {string} sign2 - Second sign (case‑insensitive)
* @returns {Promise} Compatibility document
*/
async function getCompatibility(sign1, sign2) {
const [a, b] = [sign1.toUpperCase(), sign2.toUpperCase()];
// Try direct order first, then reversed (bidirectional)
let result = await Compatibility.findOne({ signA: a, signB: b });
if (!result) {
result = await Compatibility.findOne({ signA: b, signB: a });
}
if (!result) {
throw new Error('Compatibility data not found for the given signs.');
}
return result;
}
确定性输出 → 用户每次都会看到相同的分数。
易于调优 → 可直接在数据库中修改 score 或描述。
Express 控制器实现
控制器验证输入,调用服务,并格式化响应。
// controllers/compatibilityController.js
const { getCompatibility } = require('../services/compatibilityService');
const { validateSign } = require('../utils/validation');
exports.getCompatibility = async (req, res) => {
try {
const { sign1, sign2 } = req.params;
// Input validation
if (!validateSign(sign1) || !validateSign(sign2)) {
return res.status(400).json({ error: 'Invalid zodiac sign supplied.' });
}
const data = await getCompatibility(sign1, sign2);
res.json({
signA: data.signA,
signB: data.signB,
score: data.score,
shortDescription: data.shortDescription,
longDescription: data.longDescription,
});
} catch (err) {
console.error(err);
res.status(500).json({ error: 'Internal server error.' });
}
};
使用缓存进行性能优化
兼容性结果很少变化,因而非常适合作为缓存对象。
Redis 策略
- 缓存键:
compatibility:{signA}:{signB}(星座名称使用大写) - TTL:24 小时
- 模式:Cache‑aside – 先查询 Redis,未命中时回退到 MongoDB,然后填充缓存。
// utils/cache.js
const redis = require('redis');
const client = redis.createClient({ url: process.env.REDIS_URL });
async function getCachedCompatibility(key) {
const cached = await client.get(key);
return cached ? JSON.parse(cached) : null;
}
async function setCachedCompatibility(key, value) {
await client.setEx(key, 86400, JSON.stringify(value)); // 24h TTL
}
在生产环境中,这在流量高峰期间将数据库负载降低了 70 % 以上。
错误处理与验证
星座用户可能会输入无效数据,因此防御性编码至关重要。
- 白名单验证 – 仅接受 12(或 13,如果包括蛇夫座)个标准星座。
- 大小写规范化 –
cancer、Cancer和CANCER被视为相同。 - 一致的 HTTP 状态码 – 客户端错误使用
400,未找到配对时使用404,服务器故障使用500。
// utils/validation.js
const VALID_SIGNS = [
'ARIES','TAURUS','GEMINI','CANCER','LEO','VIRGO',
'LIBRA','SCORPIO','SAGITTARIUS','CAPRICORN','AQUARIUS','PISCES'
];
function validateSign(sign) {
return VALID_SIGNS.includes(sign.toUpperCase());
}
module.exports = { validateSign };
部署策略
API 在容器中运行,使其在不同环境中可移植。
部署栈
| Component | Service |
|---|---|
| Runtime | Node.js (LTS) |
| Database | MongoDB Atlas (managed) |
| Cache | Redis (managed or self‑hosted) |
| CI/CD | GitHub Actions – 构建、测试、推送 Docker 镜像 |
| Orchestration | Docker Compose (local) / Kubernetes (production) |
| Config | Environment variables (dotenv) |
通过在负载均衡器后进行滚动更新,实现零停机部署。
经验教训
- 即使是“有趣”的功能也需要坚实的工程基础。
- 确定性的逻辑建立用户信任。
- 在大规模情况下,缓存是必不可少的。
- 清晰的架构从长远来看能节省时间。
- 实际使用会暴露出教程很少涉及的边缘情况。
该 API 的未来方向
计划改进:
- 个性化出生图分析 – 结合太阳、月亮和上升星座。
- 综合兼容性评分 – 融合短期和长期指标。
- 机器学习辅助调优 – 使用用户反馈来调整评分。
- 公开 API 文档 – 为外部开发者提供 Swagger/OpenAPI 规范。
结论
使用 Node.js、Express 和 MongoDB 构建星座匹配 API 展示了创意如何与严谨的工程相结合。最初作为一个小众功能的项目,已发展为支持每日真实用户的可扩展后端服务。
如果你对占星学、数据建模和后端开发的交叉点感兴趣,这个项目提供了一个实用的蓝图,供你将其适配到自己的想法中。