系统整体架构与技术选型
DeLuxury 是一个典型的高端奢侈品在线租赁平台,用户可以在线浏览、预约、支付和归还名牌包袋、腕表等商品。为了承载高并发访问并保证数据一致性,后端核心采用 Node.js 搭配 Express 框架,利用其丰富的中间件生态快速搭建 RESTful API。数据库方面,商品信息、用户资料和租赁订单这类半结构化数据非常适合使用 MongoDB,其灵活的文档模型可以轻松存储每件商品的多种属性(品牌、成色、附件清单、高清图片集),并且支持嵌套文档来记录每次租赁的归还检查记录。
在缓存层面,引入 Redis 来缓解数据库压力。首页热门商品列表、商品详情页的基本信息都可以设置合理的 TTL(比如 5 分钟),通过 Node.js 的 ioredis 客户端实现读写穿透。对于高写入的租赁订单状态变更,采用消息队列 RabbitMQ 配合 amqplib,将状态变更事件异步写入数据库并触发通知,避免同步写入阻塞主线程。整个服务会运行在集群模式下,借助 PM2 的 fork 模式或多核 cluster 模块来充分利用 CPU,提高吞吐量。
身份认证与鉴权方面,使用 jsonwebtoken 签发 JWT,存储在客户端的 HttpOnly Cookie 中,API 层通过自定义中间件校验 Token 的有效性和权限。对于贵重物品的归还流程,系统还集成了后台管理端的操作员签名验证机制,确保每一步都被记录并可追溯。这样的选型组合既可以保证开发效率,又能在租赁核心业务上保持高性能。
商品管理与库存状态机设计
高端品的租赁不同于普通电商,每件商品通常是唯一的,无法用简单的库存数量来描述。DeLuxury 中,每件商品都是一个独立文档,包含 sku(唯一编号)、status(可用状态)、currentLease(当前租赁记录)等字段。状态机设计是防止超租的关键,我们定义了五种状态:available(可租)、reserved(已预约但未支付)、leased(租赁中)、returned_pending(待验收归还)、maintenance(维护中)。只有 available 状态的商品才能被用户加入购物车并创建租赁请求。
预约流程分为两步:首先用户提交租赁时间段,系统会发起一个原子更新操作,使用 MongoDB 的 findOneAndUpdate 配合条件 {status: 'available', reservedUntil: {$lt: new Date()}},将商品状态改为 reserved 并设置 reservedUntil 为当前时间 + 15 分钟。这一步必须保证原子性,避免两个用户同时预约同一件商品。如果更新成功,用户获得一个 15 分钟的支付窗口;如果失败,前端提示商品已被占用。之后支付成功,状态流转为 leased;支付超时,通过定时任务(node-cron)扫描并释放已过期预约的商品。
归还流程同样需要状态机保护。用户提交归还申请后,商品进入 returned_pending 状态,此时该商品不可再次出租。只有当后台操作员在验收页面确认商品无损坏,并上传签字照片后,状态才会更新为 available,同时将本次租赁的成色备注、验收图片存入 leaseHistory 数组。整个状态流转记录在商品的 statusLog 中,方便日后审计。下面是一段基于 Mongoose 的预约锁定实现代码示例:
// 预约锁定商品
const lockProduct = async (productId, userId, duration) => {
const session = await mongoose.startSession();
session.startTransaction();
try {
const product = await Product.findOneAndUpdate(
{
_id: productId,
status: 'available',
$or: [
{ reservedUntil: { $exists: false } },
{ reservedUntil: { $lt: new Date() } }
]
},
{
status: 'reserved',
reservedUntil: new Date(Date.now() + 15 * 60 * 1000),
reservedBy: userId
},
{ new: true, session }
);
if (!product) throw new Error('商品已被占用或不存在');
await session.commitTransaction();
return product;
} catch (error) {
await session.abortTransaction();
throw error;
} finally {
session.endSession();
}
};
租赁订单与价格计算引擎
租赁价格的动态计算是 DeLuxury 的核心业务逻辑。高端品的租金按天计算,但不同的商品类别(包袋、腕表、珠宝)有不同的基础费率,同时还会受到节假日溢价、长期租赁折扣、会员等级折扣等多重因素影响。因此,后端将价格计算抽离为一个独立的 PriceCalculator 模块,接收商品 ID、租赁起止日期、用户等级等参数,返回费用明细。
整个计算过程分为几个阶段:首先从商品文档获取基础日租金 baseDailyRate;然后判断租赁区间内是否包含周末或指定节假日,如果系统配置了溢价日历,就会对相应日期的费率进行上浮(例如 20%);接着计算租赁天数,如果超过 7 天享受 9 折,超过 30 天享受 8.5 折;最后根据用户的会员等级(普通、金卡、钻石)再叠加固定的折扣比例。所有规则通过配置化策略模式实现,方便运营人员后期调整。
订单生成后,价格快照会被保存在订单文档中,以便将来财务核算和退款处理。这样做的好处是即使以后商品基础租金或折扣规则发生变化,历史订单金额也不会受影响。同时,为了支持押金和尾款分离,订单中会分别记录 deposit(押金,通常为商品估值的 30%-50%)和 rentalFee(纯租金)。支付环节对接 Stripe 或支付宝,利用 stripe npm 包创建 PaymentIntent,并将支付状态通过 Webhook 异步回写订单状态。以下是一个简化的价格计算函数:
function calculateRentalPrice(product, startDate, endDate, userLevel) {
const dayjs = require('dayjs');
let total = 0;
let current = dayjs(startDate);
const end = dayjs(endDate);
while (current.isBefore(end)) {
let dailyRate = product.baseDailyRate;
// 节假日溢价
if (isHoliday(current)) {
dailyRate *= 1.2;
}
total += dailyRate;
current = current.add(1, 'day');
}
const days = end.diff(startDate, 'day');
// 长期折扣
if (days > 30) total *= 0.85;
else if (days > 7) total *= 0.9;
// 会员折扣
const memberDiscount = { normal: 1, gold: 0.95, diamond: 0.9 };
total *= memberDiscount[userLevel] || 1;
return Math.round(total * 100) / 100;
}
安全加固与性能优化实践
由于高端品租赁涉及高额交易和用户敏感信息,安全性是重中之重。API 层全面采用 HTTPS,并启用 Helmet 中间件设置各类安全头。对于用户输入的商品评价、归还备注等可能包含 XSS 攻击的内容,统一使用 xss 库进行过滤。Token 管理上,Access Token 有效期较短(15 分钟),并配合 Refresh Token 实现滑动窗口续期,Refresh Token 存储在 Redis 中并设置黑名单机制,一旦用户登出立即失效。
在高并发场景下,MongoDB 的查询性能容易成为瓶颈。我们对租赁订单表按 userId 和 productId 分别建立索引,并对历史订单按月分表(或使用 MongoDB 的 TTL 索引自动清理超期数据)。商品列表接口引入分页与游标,避免 skip 大偏移量时的性能陷阱。对于非实时性要求高的数据,例如“您可能喜欢的奢侈品”通过 Node.js 的 child_process 运行离线推荐算法,结果写入 Redis,API 直接读取。
此外,为了应对流量峰值,系统实施了限流与降级策略。通过 express-rate-limit 限制每个 IP 在单位时间内的下单次数,防止刷单;关键路径(如支付回调)采用断路器模式,使用 opossum 库,一旦下游支付网关响应超时便熔断,返回友好的等待提示,保证系统整体可用。图片等静态资源统一交由 CDN 分发,Node.js 服务只处理 JSON 数据,有效降低带宽压力。经过这些优化,DeLuxury 可以稳定支撑日均十万级租赁请求,为高端品共享经济提供坚实的技术底座。