【后端必备】手把手带你用中间件模式,打造一个优雅的 VIP 订阅系统 ✨
哈喽,掘金的兄弟们!我是你们的老朋友,技术小卷。今天我们来聊一个老生常谈但又绝对重要的话题:怎么给自己的产品加上会员订阅功能? 特别是,如何只让付了钱的“爸爸”们看到 VIP 内容,同时把“白嫖怪”优雅地挡在门外?
别慌,不卷复杂架构。今天,咱们就用一个超优雅的姿势——中间件模式,来轻松拿捏这个需求!

🧊 破冰:为啥是中间件?先听我讲个故事
在撸代码之前,咱们先建立一个心智模型。
想象一下,你的 App/网站是个 高级会所。
- 大厅 = 免费内容,谁都能逛。
- VIP 包厢 = 付费内容,门口有保安守着。
这个 保安,就是我们今天的主角——中间件(Middleware) 。
这个保安不干别的,就干三件事:
- 看脸:确认你是谁。(用户认证)
- 查票:看你的 VIP 票有没有效、过没过期。(订阅检查)
- 放行/劝退:票没问题就让你进,有问题就礼貌地把你请走。
把权限检查这个脏活累活全权交给“保安”,包厢里的服务员(核心业务代码)就能专心搞服务,不用分心。这就是逻辑解耦,也是我们追求“优雅”的关键!
🚀 Show Me The Code! 核心实现三部曲
光说不练假把式,上代码!咱们用大家最熟悉的 Node.js + Express 来实操。
第一步:兵马未动,“粮草”先行(数据库设计)
保安查票得有个名单,这就是我们的 subscriptions 表。一个最简设计如下:
| 字段名 | 类型 | 描述 |
|---|---|---|
| id | INT / UUID | 订阅ID,主键 |
| userId | INT / UUID | 和用户表关联,这是谁的票 |
| plan | VARCHAR | 票的类型,比如:'pro' |
| status | ENUM | 灵魂字段:票的状态,'active' (有效) 最关键 |
| expiresAt | DATETIME | 灵魂字段:票的过期时间 |
第二步:编写保安的“行动纲领”(实现中间件)
这是整篇文章的 C 位代码。我们来创建一个 checkSubscription.js 文件。
// middlewares/checkSubscription.js
const db = require('../models'); // 你的数据库 ORM 实例
/**
* 订阅检查中间件 - 我们的“智能保安”
*
* 作用:检查当前登录用户是否拥有有效的 Pro 订阅
* 前提:必须在 authMiddleware (用户认证中间件) 之后使用!
*/
const checkSubscription = async (req, res, next) => {
// `req.user` 是上一个 authMiddleware 塞给我们的,包含了当前用户信息
const userId = req.user.id;
try {
// 保安开始拿小本本查记录了...
const subscription = await db.subscription.findFirst({
where: {
userId: userId,
// 灵魂两连问!
status: 'active', // 1. 票的状态对吗?
expiresAt: { gt: new Date() } // 2. 票过期了吗?(gt = greater than)
}
});
// 如果找不到符合条件的记录...
if (!subscription) {
// 直接把请求拦下,返回 403 Forbidden
return res.status(403).json({
message: '访问失败,该功能需要有效的 Pro 会员权限。',
code: 'INSUFFICIENT_SUBSCRIPTION'
});
}
// 检查通过!VIP 客户,里面请!
// 调用 next(),把请求控制权交给下一个中间件或路由处理器
next();
} catch (error) {
// 万一数据库崩了...
console.error('Subscription check middleware error:', error);
return res.status(500).json({ message: '服务器开小差了...' });
}
};
module.exports = checkSubscription;
第三步:让“保安”上岗!(在路由中使用)
保安训练好了,现在该派他去 VIP 包厢门口站岗了。
// routes/proRoutes.js
const express = require('express');
const router = express.Router();
// 把我们的保安和认证主管都请过来
const authMiddleware = require('../middlewares/authMiddleware'); // 认证主管
const checkSubscription = require('../middlewares/checkSubscription'); // VIP保安
// 定义一个需要 Pro 权限的 API
// 注意看这个数组,中间件会像流水线一样按顺序执行
router.get(
'/secret-data',
authMiddleware, // 关卡1:认证主管先验明身份
checkSubscription, // 关卡2:VIP保安再检查会员卡
(req, res) => { // 都通过了,才能进到这里,拿到数据
res.json({
success: true,
data: {
message: '恭喜!这是只有 Pro 用户才能看到的核能机密!🚀'
}
});
}
);
module.exports = router;
IGNORE_WHEN_COPYING_START
content_copy download
Use code with caution. JavaScript
IGNORE_WHEN_COPYING_END
看到没?业务代码 (req, res) => { ... } 里面干干净净,完全不用关心权限的事,太优雅了!
🗺️ 一张图搞懂请求流程
文字太苍白?安排!用 Mermaid 图把“请求被盘查”的全过程画出来。
graph TD
A[用户请求 /secret-data] --> B{认证主管: authMiddleware};
B -- Token有效 --> C{VIP保安: checkSubscription};
B -- Token无效 --> F1[💥 返回 401 Unauthorized];
C -- 查库: 订阅有效 --> D[✅ 执行核心业务];
C -- 查库: 订阅无效/过期 --> F2[🛡️ 返回 403 Forbidden];
D --> E[🎉 返回 200 OK + 机密数据];
IGNORE_WHEN_COPYING_START
content_copy download
Use code with caution. Mermaid
IGNORE_WHEN_COPYING_END
🏆 这个方案,到底“优雅”在哪?
总结一下,为啥大家都推荐这么干:
- 高内聚,低耦合:保安干保安的活,服务员干服务员的活,各司其职,互不打扰。
- DRY (Don't Repeat Yourself) :写一个保安,可以让他去守一百个门。代码复用性 Max!
- 维护性超强:以后想改规则,比如增加个 "Premium" 等级,只需要去升级你的保安就行了,其他地方代码一动不动。
- 绝对安全:检查在服务端强制执行,前端小伎俩(改个JS变量啥的)根本没用,安全感拉满。
✍️ 写在最后
用 中间件 + 实时数据库查询 的模式来做订阅系统,是后端开发中一个非常经典且实用的“套路”。它不仅逻辑清晰,而且扩展性极强,足以应对大部分业务场景。
好了,今天的分享就到这里。如果觉得这篇文章对你有帮助,不妨点赞、收藏、关注三连支持一下,这对我真的很重要!我们下期再见!Peace out! ✌️