家政维修平台实战32优惠券系统


日常平台在运营的过程中,为了促进用户消费,提高平台的营收,通常会提供优惠券的功能。后台可以配置优惠券的发放规则,用户进入小程序可以自动发放优惠券。结合线下的拉新推广活动,用户可以提供优惠券的兑换码进行线上领取。本篇我们介绍一下优惠券的具体的开发过程。

1 创建数据源

我们设计两个表来实现优惠券信息的存取,一个是优惠券的配置表,一个是优惠券的领用表。

Table: coupons(优惠券定义表)
Description: 存储优惠券的基本定义和规则

Column NameData TypeConstraintsDescription
idBIGINTPRIMARY KEY, AUTO_INCREMENT唯一标识符
coupon_nameVARCHAR(255)NOT NULL优惠券名称 (例如: 保姆初体验80元红包券)
coupon_typeENUMNOT NULL优惠券类型 (RED_PACKET: 红包, DISCOUNT: 折扣券, FULL_REDUCTION: 满减券)
discount_valueDECIMAL(10, 2)NOT NULL折扣值 (例如: 80, 100)
min_order_amountDECIMAL(10, 2)DEFAULT 0.00最低消费金额 (0表示无门槛)
start_timeDATETIMENOT NULL优惠券生效时间
end_timeDATETIMENOT NULL优惠券过期时间
total_quantityINTDEFAULT 0优惠券总发行量 (0表示不限制)
redeemed_quantityINTDEFAULT 0已核销数量
per_user_limitINTDEFAULT 1每用户限领/限用次数 (0表示不限制)
is_stackableBOOLEANDEFAULT FALSE是否可与其他优惠券叠加使用
statusENUMNOT NULL优惠券状态 (ACTIVE: 活跃, INACTIVE: 未启用, EXPIRED: 已过期)
created_atDATETIMENOT NULL创建时间
updated_atDATETIMENOT NULL最后更新时间

在这里插入图片描述

Table: user_coupons(用户优惠券表)
Description: 记录用户持有的优惠券实例及其状态

Column NameData TypeConstraintsDescription
idBIGINTPRIMARY KEY, AUTO_INCREMENT唯一标识符
user_idBIGINTNOT NULL, INDEX用户ID (外键关联用户表)
coupon_idBIGINTNOT NULL, INDEX关联的优惠券定义ID (外键关联coupons表)
receive_timeDATETIMENOT NULL用户领取时间
expire_timeDATETIMENOT NULL用户持有的优惠券过期时间
statusENUMNOT NULL优惠券状态 (UNUSED: 未使用, USED: 已使用, EXPIRED: 已过期)
order_idBIGINTNULL, INDEX如果已使用,关联的订单ID
used_timeDATETIMENULL如果已使用,使用时间
created_atDATETIMENOT NULL创建时间
updated_atDATETIMENOT NULL最后更新时间

在这里插入图片描述

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,搭建了管理员的后台功能,下一篇我们介绍一下用户登录系统后主动领券,在下单的时候使用优惠券的功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

低代码布道师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值