知识付费小程序后端该怎么设计?
在知识付费行业产品形态日益趋同的当下,小程序凭借其轻量化、场景化优势成为创业者的首选载体。本文从后端架构设计视角出发,系统解析如何通过Node.js技术栈构建完整的知识付费小程序解决方案,涵盖核心模块设计、关键代码实现及工程化实践建议。
一、系统架构全景图
mermaid
graph TD |
|
A[客户端小程序] --> B[API网关层<br>(Node.js/Express)] |
|
B --> C[业务服务层<br>(内容/用户/支付服务)] |
|
C --> D[数据存储层<br>(MongoDB+对象存储)] |
1. 技术选型组合
- 开发框架:Express.js(轻量级RESTful API开发)
- 数据库:MongoDB(文档型数据库适配内容管理场景)
- 对象存储:阿里云OSS/腾讯云COS(音视频文件存储)
- 支付集成:微信支付商户平台SDK
2. 核心功能模块矩阵
| 模块 | 关键能力 | 典型场景 |
|---|---|---|
| 用户系统 | 多角色权限管理、JWT鉴权 | 游客浏览/付费用户解锁 |
| 内容系统 | 多媒体内容发布、分类标签体系 | 课程/专栏/直播回放管理 |
| 支付系统 | 预支付订单生成、异步通知处理 | 微信支付/企业分账 |
| 运营系统 | 优惠券核销、分销佣金计算 | 营销活动支撑 |
二、内容分发子系统设计
1. 核心数据模型(MongoDB)
javascript
// models/content.schema.js |
|
const mongoose = require('mongoose'); |
|
const { Schema } = mongoose; |
|
const ContentSchema = new Schema({ |
|
title: { type: String, required: true }, |
|
type: { |
|
type: String, |
|
enum: ['video', 'audio', 'article', 'live'], |
|
default: 'article' |
|
}, |
|
body: { |
|
type: String, |
|
validate: { |
|
validator: v => ['markdown', 'html'].includes(v), |
|
message: 'Invalid content format' |
|
} |
|
}, |
|
mediaUrl: { type: String, get: v => process.env.CDN_PREFIX + v }, |
|
isFree: { type: Boolean, default: false }, |
|
price: { type: Number, min: 0, default: 0 }, |
|
authorId: { type: Schema.Types.ObjectId, ref: 'User' }, |
|
tags: [{ type: String }], |
|
viewCount: { type: Number, default: 0 }, |
|
createdAt: { type: Date, default: Date.now }, |
|
updatedAt: { type: Date, default: Date.now } |
|
}, { |
|
toJSON: { getters: true }, |
|
timestamps: true |
|
}); |
|
module.exports = mongoose.model('Content', ContentSchema); |
2. 关键API实现
javascript
// routes/content.js |
|
const express = require('express'); |
|
const router = express.Router(); |
|
const Content = require('../models/content'); |
|
const { checkAccess } = require('../middlewares/permission'); |
|
// 1. 公开内容查询(无需鉴权) |
|
router.get('/free', async (req, res) => { |
|
const contents = await Content.find({ isFree: true }) |
|
.populate('authorId', 'nickname avatar') |
|
.sort({ createdAt: -1 }) |
|
.limit(20); |
|
res.json({ success: true, data: contents }); |
|
}); |
|
// 2. 付费内容访问(鉴权中间件) |
|
router.get('/:id', checkAccess, async (req, res) => { |
|
const content = await Content.findById(req.params.id) |
|
.populate({ |
|
path: 'authorId', |
|
select: 'nickname bio avatar' |
|
}); |
|
if (!content) return res.status(404).json({ error: '内容不存在' }); |
|
// 增加浏览计数(乐观锁) |
|
await Content.updateOne( |
|
{ _id: content._id }, |
|
{ $inc: { viewCount: 1 } } |
|
); |
|
res.json({ success: true, data: content }); |
|
}); |
三、权限控制中间件设计
1. 鉴权流程图
mermaid
sequenceDiagram |
|
participant Client as 客户端 |
|
participant API as API网关 |
|
participant Auth as 鉴权服务 |
|
participant DB as 数据库 |
|
Client->>API: 访问付费内容 |
|
API->>Auth: 调用权限校验中间件 |
|
Auth->>DB: 查询内容是否免费 |
|
alt 内容免费 |
|
Auth-->>API: 放行请求 |
|
else 内容收费 |
|
Auth->>DB: 查询用户购买记录 |
|
alt 已购买 |
|
Auth-->>API: 放行请求 |
|
else 未购买 |
|
Auth-->>API: 返回403错误 |
|
end |
|
end |
2. 核心鉴权中间件
javascript
// middlewares/permission.js |
|
const Order = require('../models/order'); |
|
const Content = require('../models/content'); |
|
module.exports = { |
|
checkAccess: async (req, res, next) => { |
|
try { |
|
const { id } = req.params; |
|
const userId = req.user._id; // JWT解析出的用户ID |
|
// 1. 查询内容信息 |
|
const content = await Content.findById(id).select('isFree price'); |
|
if (!content) return res.status(404).json({ error: '内容不存在' }); |
|
// 2. 免费内容直接放行 |
|
if (content.isFree) return next(); |
|
// 3. 付费内容校验订单 |
|
const order = await Order.findOne({ |
|
userId, |
|
contentId: id, |
|
status: 'paid', |
|
paidAt: { $exists: true } |
|
}); |
|
if (!order) { |
|
return res.status(403).json({ |
|
error: '无访问权限', |
|
code: 'PAYMENT_REQUIRED', |
|
data: { |
|
contentId: id, |
|
price: content.price |
|
} |
|
}); |
|
} |
|
next(); |
|
} catch (err) { |
|
console.error('权限校验失败:', err); |
|
res.status(500).json({ error: '服务器错误' }); |
|
} |
|
} |
|
}; |
四、支付系统深度实现
1. 微信支付流程时序图
mermaid
sequenceDiagram |
|
participant Client as 客户端 |
|
participant Server as 后端服务 |
|
participant Wechat as 微信支付 |
|
Client->>Server: 创建预支付订单 |
|
Server->>Wechat: 调用统一下单API |
|
Wechat-->>Server: 返回prepay_id |
|
Server-->>Client: 返回支付参数 |
|
Client->>Wechat: 调起支付 |
|
Wechat-->>Client: 支付结果页 |
|
Wechat->>Server: 异步通知支付结果 |
|
Server->>DB: 更新订单状态 |
|
Server-->>Wechat: 返回处理结果 |
|
Server->>Client: 支付结果轮询接口 |
2. 核心支付接口实现
javascript
// routes/payment.js |
|
const express = require('express'); |
|
const router = express.Router(); |
|
const { createUnifiedOrder, parseWxCallback } = require('../utils/wechat'); |
|
const Order = require('../models/order'); |
|
const Content = require('../models/content'); |
|
// 1. 创建预支付订单 |
|
router.post('/prepay', async (req, res) => { |
|
const { contentId } = req.body; |
|
const userId = req.user._id; |
|
// 参数校验 |
|
if (!mongoose.Types.ObjectId.isValid(contentId)) { |
|
return res.status(400).json({ error: '无效的内容ID' }); |
|
} |
|
// 查询内容信息 |
|
const content = await Content.findById(contentId).select('price title'); |
|
if (!content) return res.status(404).json({ error: '内容不存在' }); |
|
// 创建订单 |
|
const order = new Order({ |
|
userId, |
|
contentId, |
|
price: content.price, |
|
status: 'pending', |
|
outTradeNo: `ORD${Date.now()}${Math.floor(Math.random() * 1000)}` |
|
}); |
|
try { |
|
// 调用微信支付API |
|
const wxRes = await createUnifiedOrder({ |
|
out_trade_no: order.outTradeNo, |
|
body: content.title, |
|
total_fee: Math.round(content.price * 100), // 转换为分 |
|
openid: req.user.openid, |
|
trade_type: 'JSAPI', |
|
notify_url: `${process.env.APP_DOMAIN}/api/payment/notify` |
|
}); |
|
// 保存订单 |
|
await order.save(); |
|
// 返回支付参数 |
|
res.json({ |
|
success: true, |
|
data: { |
|
appId: process.env.WECHAT_APPID, |
|
timeStamp: String(Date.now()), |
|
nonceStr: wxRes.nonce_str, |
|
package: `prepay_id=${wxRes.prepay_id}`, |
|
signType: 'MD5', |
|
paySign: generatePaySign(wxRes.prepay_id, wxRes.nonce_str) |
|
} |
|
}); |
|
} catch (err) { |
|
console.error('支付下单失败:', err); |
|
await order.remove(); // 回滚订单 |
|
res.status(500).json({ error: '支付系统异常' }); |
|
} |
|
}); |
|
// 2. 支付结果异步通知 |
|
router.post('/notify', express.raw({ type: 'application/xml' }), async (req, res) => { |
|
try { |
|
// 解析XML数据 |
|
const callbackData = await parseWxCallback(req.body.toString()); |
|
const { out_trade_no, result_code, transaction_id } = callbackData; |
|
// 校验签名 |
|
if (!verifyWxSign(callbackData)) { |
|
return res.status(200).send('<xml><return_code><![CDATA[FAIL]]></return_code></xml>'); |
|
} |
|
// 查询订单 |
|
const order = await Order.findOne({ outTradeNo: out_trade_no }); |
|
if (!order || order.status !== 'pending') { |
|
return res.status(200).send('<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>'); |
|
} |
|
// 更新订单状态 |
|
await Order.updateOne( |
|
{ _id: order._id }, |
|
{ |
|
$set: { |
|
status: 'paid', |
|
paidAt: new Date(), |
|
transactionId, |
|
totalFee: callbackData.total_fee / 100 // 转换为元 |
|
} |
|
} |
|
); |
|
// 触发后续业务(如分账、通知等) |
|
await handlePaymentSuccess(order); |
|
res.status(200).send('<xml><return_code><![CDATA[SUCCESS]]></return_code></xml>'); |
|
} catch (err) { |
|
console.error('支付回调处理失败:', err); |
|
res.status(200).send('<xml><return_code><![CDATA[FAIL]]></return_code></xml>'); |
|
} |
|
}); |
五、知识付费小程序工程化实践建议
1. 性能优化方案
- CDN加速:音视频文件使用对象存储+CDN分发,配置7天缓存策略
- 数据库索引:为
content.isFree、order.userId+contentId等查询字段建立复合索引 - 读写分离:主库处理订单写入,从库处理内容查询
2. 安全防护体系
- 防盗链机制:音视频文件URL添加有效期参数(如
?expires=1625097600&signature=xxx) - 敏感数据:openid/unionid使用AES-256加密存储,JWT令牌设置短期有效期
- 支付安全:实现订单幂等处理,防止重复扣款
3. 运维监控方案
- 日志系统:使用Winston记录关键操作日志,按天切割存储
- 告警机制:对支付失败率、接口响应时间设置阈值告警
- 容灾方案:数据库主从同步,支付回调实现重试队列
热门文章
推荐文章