我如何使用 Node.js、Express 和 MongoDB 构建星座兼容性 API

发布: (2026年1月7日 GMT+8 00:00)
9 min read
原文: Dev.to

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,如果包括蛇夫座)个标准星座。
  • 大小写规范化cancerCancerCANCER 被视为相同。
  • 一致的 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 在容器中运行,使其在不同环境中可移植。

部署栈

ComponentService
RuntimeNode.js (LTS)
DatabaseMongoDB Atlas (managed)
CacheRedis (managed or self‑hosted)
CI/CDGitHub Actions – 构建、测试、推送 Docker 镜像
OrchestrationDocker Compose (local) / Kubernetes (production)
ConfigEnvironment variables (dotenv)

通过在负载均衡器后进行滚动更新,实现零停机部署。

经验教训

  • 即使是“有趣”的功能也需要坚实的工程基础。
  • 确定性的逻辑建立用户信任。
  • 在大规模情况下,缓存是必不可少的。
  • 清晰的架构从长远来看能节省时间。
  • 实际使用会暴露出教程很少涉及的边缘情况。

该 API 的未来方向

计划改进:

  1. 个性化出生图分析 – 结合太阳、月亮和上升星座。
  2. 综合兼容性评分 – 融合短期和长期指标。
  3. 机器学习辅助调优 – 使用用户反馈来调整评分。
  4. 公开 API 文档 – 为外部开发者提供 Swagger/OpenAPI 规范。

结论

使用 Node.js、Express 和 MongoDB 构建星座匹配 API 展示了创意如何与严谨的工程相结合。最初作为一个小众功能的项目,已发展为支持每日真实用户的可扩展后端服务。

如果你对占星学、数据建模和后端开发的交叉点感兴趣,这个项目提供了一个实用的蓝图,供你将其适配到自己的想法中。

Back to Blog

相关文章

阅读更多 »

如何打造成功的家庭服务App

封面图片:如何构建成功的家庭服务应用 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A...