|
日常平台在运营的过程中,为了促进用户消费,提高平台的营收,通常会提供优惠券的功能。后台可以配置优惠券的发放规则,用户进入小程序可以自动发放优惠券。结合线下的拉新推广活动,用户可以提供优惠券的兑换码进行线上领取。本篇我们介绍一下优惠券的具体的开发过程。 1 创建数据源我们设计两个表来实现优惠券信息的存取,一个是优惠券的配置表,一个是优惠券的领用表。 Table: coupons(优惠券定义表) Description: 存储优惠券的基本定义和规则 | | | |
|---|
id | | PRIMARY KEY, AUTO_INCREMENT | | coupon_name | | | | coupon_type | | | 优惠券类型 (RED_PACKET: 红包, DISCOUNT: 折扣券, FULL_REDUCTION: 满减券) | discount_value | | | | min_order_amount | | | | start_time | | | | end_time | | | | total_quantity | | | | redeemed_quantity | | | | per_user_limit | | | | is_stackable | | | | status | | | 优惠券状态 (ACTIVE: 活跃, INACTIVE: 未启用, EXPIRED: 已过期) | created_at | | | | updated_at | | | |
Table: user_coupons(用户优惠券表) Description: 记录用户持有的优惠券实例及其状态 | | | |
|---|
id | | PRIMARY KEY, AUTO_INCREMENT | | user_id | | | | coupon_id | | | 关联的优惠券定义ID (外键关联coupons表) | receive_time | | | | expire_time | | | | status | | | 优惠券状态 (UNUSED: 未使用, USED: 已使用, EXPIRED: 已过期) | order_id | | | | used_time | | | | created_at | | | | updated_at | | | |
2 创建API在我们的API模块,我们继续添加一个优惠券管理的API,来管理我们具体的方法
2.1 getAvailableUserCoupons (获取用户可用优惠券列表)入参,需要传入用户的ID和订单的金额,来查询是否有符合要求的优惠券
具体代码如下: // API 名称: getAvailableUserCoupons // 描述: 获取当前用户可用于新订单的优惠券列表 // 参数: // - userId: 用户ID (必填) // - orderAmount: 当前订单总金额 (用于判断满减条件)(可选,但在实际场景中非常有用) // - applicableServiceIds: 订单中包含的服务/商品ID列表 (可选,用于判断适用范围) const ErrorCode ={ SUCCESS: 0, PARAM_ERROR: 1001, NOT_FOUND: 1002, SYSTEM_ERROR: 1003, USER_NOT_EXISTS: 1005, MEMBER_NOT_EXISTS: 1006, // 沿用,但可能实际是 user_not_exists INVALID_AMOUNT: 1007, INSUFFICIENT_BALANCE: 1008, ORDER_NOT_EXISTS: 1009, ORDER_ALREADY_PAID: 1010, // 新增优惠券相关错误码 COUPON_NOT_EXISTS: 2001, COUPON_EXPIRED: 2002, COUPON_UNAVAILABLE: 2003, // 优惠券未到生效时间或已发完 COUPON_ALREADY_RECEIVED: 2004, COUPON_NOT_BELONGS_TO_USER: 2005, COUPON_ALREADY_USED: 2006, COUPON_APPLICABILITY_ERROR: 2007, // 优惠券不适用于当前订单 USER_COUPON_NOT_EXISTS: 2008, // 用户未持有该优惠券实例 MAX_RECEIVE_LIMIT_REACHED: 2009, // 达到领取上限 }; module.exports = async function(params, context){ console.log('获取用户可用优惠券API入参:', params);
const { userId, orderAmount =0}= params;
if(!userId){ return{ code: ErrorCode.PARAM_ERROR, message: '用户ID不能为空'}; }
try { // 1. 查询用户持有的未使用的、未过期的优惠券实例 const userCouponsResult = await context.callModel({ name: "user_coupons", methodName: "wedaGetRecordsV2", params: { filter: { where: { user_id: {$eq: userId }, status: {$eq:'1'}, // 未使用 expire_time: {$gt: Date.now()} // 未过期 } }, select: {$master:true} } });
if(!userCouponsResult ||!userCouponsResult.records || userCouponsResult.records.length ===0){ return{ code: ErrorCode.SUCCESS, data: [], message: '暂无可用优惠券,本身没领'}; }
const userCouponIds = userCouponsResult.records.map(uc => uc.coupon_id);
// 2. 查询这些优惠券的定义信息,确保优惠券本身处于活跃状态且在有效期内 const couponDefinitions = await context.callModel({ name: "coupons", methodName: "wedaGetRecordsV2", params: { filter: { where: { _id: {$in: userCouponIds }, status: {$eq:'1'}, // 优惠券定义本身必须是活跃的 start_time: {$lte: Date.now()}, // 优惠券已生效 end_time: {$gt: Date.now()} // 优惠券未过期 } }, select: {$master:true} } });
if(!couponDefinitions ||!couponDefinitions.records || couponDefinitions.records.length ===0){ return{ code: ErrorCode.SUCCESS, data: [], message: '暂无可用优惠券,状态不符合'}; }
const validCouponDefinitionsMap = new Map(couponDefinitions.records.map(c =>[c._id, c]));
const availableCoupons =[]; for(const uc of userCouponsResult.records){ const couponDef = validCouponDefinitionsMap.get(uc.coupon_id);
// 检查优惠券定义是否存在且有效 if(!couponDef){ continue; }
// 检查订单金额是否满足满减条件 (如果有传入 orderAmount) if(orderAmount >0&& couponDef.min_order_amount >0&& orderAmount < couponDef.min_order_amount){ continue; // 不满足满减条件,跳过 }
// 这里可以添加更复杂的适用性判断,例如: // - 优惠券是否适用于 `applicableServiceIds` 中的服务/商品 // 简化版本假设优惠券适用所有服务,或适用性判断在前端完成
availableCoupons.push({ userCouponId: uc._id, // 用户持有的优惠券实例ID couponId: couponDef._id, couponName: couponDef.coupon_name, couponType: couponDef.coupon_type, discountValue: couponDef.discount_value, minOrderAmount: couponDef.min_order_amount, isStackable: couponDef.is_stackable, expireTime: uc.expire_time // 使用用户持有的具体过期时间 }); }
return{ code: ErrorCode.SUCCESS, data: availableCoupons, message: '获取用户可用优惠券成功' };
} catch (error){ console.error('获取用户可用优惠券API错误:', error); return{ code: ErrorCode.SYSTEM_ERROR, message: `系统错误: ${error.message}` }; } };
2.2 receiveCoupon (领取优惠券)除了系统自动派发优惠券外,我们还允许用户通过促销活动页面领取优惠券,入参传入用户ID和优惠券ID
代码: // API 名称: receiveCoupon // 描述: 用户领取优惠券 // 参数: // - userId: 用户ID (必填) // - couponId: 要领取的优惠券定义ID (必填) const ErrorCode ={ SUCCESS: 0, PARAM_ERROR: 1001, NOT_FOUND: 1002, SYSTEM_ERROR: 1003, USER_NOT_EXISTS: 1005, MEMBER_NOT_EXISTS: 1006, // 沿用,但可能实际是 user_not_exists INVALID_AMOUNT: 1007, INSUFFICIENT_BALANCE: 1008, ORDER_NOT_EXISTS: 1009, ORDER_ALREADY_PAID: 1010, // 新增优惠券相关错误码 COUPON_NOT_EXISTS: 2001, COUPON_EXPIRED: 2002, COUPON_UNAVAILABLE: 2003, // 优惠券未到生效时间或已发完 COUPON_ALREADY_RECEIVED: 2004, COUPON_NOT_BELONGS_TO_USER: 2005, COUPON_ALREADY_USED: 2006, COUPON_APPLICABILITY_ERROR: 2007, // 优惠券不适用于当前订单 USER_COUPON_NOT_EXISTS: 2008, // 用户未持有该优惠券实例 MAX_RECEIVE_LIMIT_REACHED: 2009, // 达到领取上限 }; module.exports = async function(params, context){ console.log('领取优惠券API入参:', params);
const { userId, couponId }= params;
if(!userId ||!couponId){ return{ code: ErrorCode.PARAM_ERROR, message: '用户ID和优惠券ID不能为空'}; }
try { // 1. 查询优惠券定义 const couponDef = await context.callModel({ name: "coupons", methodName: "wedaGetItemV2", params: { filter: { where: { _id: {$eq: couponId }} }, select: {$master:true} } });
if(!couponDef ||!couponDef._id){ return{ code: ErrorCode.COUPON_NOT_EXISTS, message: '优惠券不存在'}; }
// 2. 检查优惠券状态和有效期 if(couponDef.status !=='1'){ return{ code: ErrorCode.COUPON_UNAVAILABLE, message: '优惠券当前不可领取'}; } if(couponDef.start_time > Date.now()|| couponDef.end_time < Date.now()){ return{ code: ErrorCode.COUPON_EXPIRED, message: '优惠券未到领取时间或已过期'}; } if(couponDef.total_quantity >0&& couponDef.redeemed_quantity >= couponDef.total_quantity){ return{ code: ErrorCode.COUPON_UNAVAILABLE, message: '优惠券已发完'}; }
// 3. 检查用户是否已达到领取上限 if(couponDef.per_user_limit >0){ const userReceivedCount = await context.callModel({ name: "user_coupons", methodName: "wedaGetRecordsV2", params: { filter: { where: { user_id: {$eq: userId }, coupon_id: {$eq: couponId } } }, getCount:true, pageSize:10, pagepageNumber:1
} });
if(userReceivedCount.total >0&& userReceivedCount.total >= couponDef.per_user_limit){ return{ code: ErrorCode.MAX_RECEIVE_LIMIT_REACHED, message: `您已达到此优惠券的领取上限(${couponDef.per_user_limit}张)`}; } }
// 4. 创建用户优惠券实例 const userCouponData ={ user_id: { _id: userId }, // 假设user_id在模型中是关联类型 coupon_id: { _id: couponId }, // 假设coupon_id在模型中是关联类型 receive_time: Date.now(), expire_time: couponDef.end_time, // 用户持有的优惠券过期时间继承自定义 status: '1' };
const newUserCoupon = await context.callModel({ name: "user_coupons", methodName: "wedaCreateV2", params: { data: userCouponData } });
// 5. 更新优惠券已发放数量 (可选,如果total_quantity需要精确控制) // 注意:在高并发场景下,这里需要乐观锁或分布式锁来确保 `redeemed_quantity` 的准确性 await context.callModel({ name: "coupons", methodName: "wedaUpdateV2", params: { data: { redeemed_quantity: couponDef.redeemed_quantity + 1}, filter: { where: { _id: {$eq: couponId }}} } });
return{ code: ErrorCode.SUCCESS, data: { userCouponId: newUserCoupon._id, couponName: couponDef.coupon_name, expireTime: userCouponData.expire_time }, message: '优惠券领取成功' };
} catch (error){ console.error('领取优惠券API错误:', error); return{ code: ErrorCode.SYSTEM_ERROR, message: `系统错误: ${error.message}` }; } };
3 后台功能有了表和API后,需要给管理员搭建一个后台功能,进行优惠券的录入。打开我们的后台应用,创建页面,选择我们的优惠券表,选择左侧导航布局
然后切换到页面布局,添加菜单
修改菜单的名称
配置筛选器
配置后的最终效果
总结本篇我们创建了优惠券相关的表和API,搭建了管理员的后台功能。
|