2026-07-09-family-membership-design.md(原会员体系设计)、2026-08-03-product-gift-membership-design.md现有会员体系通过「会员价 / 折扣」体现等级差异:
products.member_price(会员价字段)activities.member_price(会员价字段)member_discount_family / member_discount_premium(bps 折扣)MembershipService.applyMemberDiscount()、ActivityDTO.resolveMemberPrice()、ProductDTO.resolveMemberPrice()本次调整目标:
| 模块 | 现状 | 本次变更 |
|---|---|---|
| 会员等级 | membership_levels(FREE/FAMILY/PREMIUM/PROVIDER)+ user.member_level + family_memberships |
等级保留,权益从「价格折扣」改为「券数量」 |
| 商品 | products.price + products.member_price |
停用 member_price,统一 price |
| 活动 | activities.price + activities.member_price |
保留(活动继续支持会员价) |
| 优惠券 | coupon(type=FIXED、value、min_spend、applicable_to、有效期、总量)+ user_coupon(AVAILABLE/USED);CouponService(claim/apply/markUsed/revert/issueToUser) |
扩展券类型、绑定商品、积分价、发放规则 |
| 积分 | family_members.total_points + system_points + points_log;PointsExchangeService(兑换商品、日限次、最低积分) |
复用扣积分与限次校验 |
| 家庭成员 | family_members(family_id/user_id/昵称/积分);FamilyMemberService.addMember() |
新增成员触发人口券发放 |
product_id,NULL=全场通用),兑换积分额在券模板上配置。coupon 表扩展(迁移 + schema.sql 同步)| 新字段 | 类型 | 说明 |
|---|---|---|
coupon_type |
VARCHAR(16) | FIXED 满减 / CASH 无门槛 / DISCOUNT 折扣;默认 FIXED(兼容旧数据) |
discount_rate |
INT | 折扣券专用,千分比(9000 = 9 折) |
product_id |
BIGINT NULL | 绑定商品 ID;NULL = 全场通用 |
points_price |
INT | 积分兑换价;0 = 不可积分兑换(仅发放可得) |
grant_type |
VARCHAR(16) NULL | 发放规则类型:JOIN / PERIODIC / POPULATION;NULL = 不自动发放 |
grant_level_code |
VARCHAR(32) NULL | JOIN/PERIODIC 对应的会员等级 |
grant_period |
VARCHAR(16) NULL | PERIODIC 周期:MONTHLY / QUARTERLY |
grant_quantity |
INT | 每次发放数量(POPULATION 默认 4) |
说明:发放规则采用独立列而非 JSON(管理端表单友好、可索引、结构固定)。原方案 A 的
grant_ruleJSON 拆为 4 列。
现有 coupon 字段复用:type(FIXED 语义保留)、value 面额、min_spend 起用门槛、applicable_to(ALL/MEMBERSHIP)、有效期、total_count/used_count。
user_coupon 表不变。发放走 issueToUser()(不校验"每人每券限 1 张");claim() 的防重逻辑仅保留给「领取中心」场景。
coupon_grant_log 发券流水表CREATE TABLE coupon_grant_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id BIGINT NOT NULL COMMENT '收券用户ID',
coupon_id BIGINT NOT NULL COMMENT '券模板ID',
grant_type VARCHAR(16) NOT NULL COMMENT 'JOIN/PERIODIC/POPULATION/EXCHANGE',
period VARCHAR(16) COMMENT '周期标识 YYYY-MM(PERIODIC 防重用)',
quantity INT DEFAULT 1 COMMENT '发放数量',
source VARCHAR(64) COMMENT '触发来源描述(订单号/成员ID等)',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY uk_grant (user_id, coupon_id, grant_type, period),
INDEX idx_coupon (coupon_id),
INDEX idx_user (user_id)
) COMMENT='优惠券发放流水表';
(user_id, coupon_id, grant_type, period),period 存 YYYY-MM。位置:MembershipService.activateMembership()(付费回调统一入口)+ grantMembershipByGift()(赠送)。
逻辑:
grant_type=JOIN AND grant_level_code=开通等级 的券模板;couponService.issueToUser() 发 grant_quantity 张;coupon_grant_log(grant_type=JOIN,period=NULL)。试用(trial)不发:activateTrialMembership() 不触发发券,防零成本刷券。
位置:新建 CouponGrantTask(Spring 定时任务,每日执行;参考现有 MembershipScheduledTasks)。
逻辑:
grant_type=PERIODIC 的券模板;family_memberships:payment_status=paid 且 end_date > now,取家庭创建者 user_id,等级匹配 grant_level_code);grant_period=MONTHLY 则当月首次执行时发放;用 coupon_grant_log 唯一键 (user_id, coupon_id, PERIODIC, YYYY-MM) 防重;执行规则:每日跑一次,只补当月(或本季度)未发的用户;已发则跳过(防重由唯一键保证,插入冲突即跳过)。
位置:FamilyMemberService.addMember()(成员添加成功后)。
逻辑:
grant_type=POPULATION 的券模板(通常 1 个);family_member.user_id != null)→ 发到该用户;user_id == null)→ 发到家庭创建者账户(保证"每成员 4 张"不因缺账号丢失);grant_quantity 张(默认 4)+ 落流水(grant_type=POPULATION,source=成员ID)。移除成员不回收:已发放券保留至自然过期。
校验券模板存在 + 启用(status 上架)
校验 points_price > 0
日限次(两层叠加):
全局:复用 exchange_daily_limit(默认 5 次/日)
单模板:coupon_exchange_daily_limit(新配置,默认 1 张/券模板/日)
最低积分:复用 exchange_points_min(默认 100)
扣积分:pointsService.deductSystemPoints(familyMemberId, pointsPrice, "积分兑换优惠券: 券名")
发券:couponService.issueToUser()
落流水:coupon_grant_log(grant_type=EXCHANGE)
PointsExchangeService.exchangeProduct() 现有逻辑(childId 优先,否则按 userId 查)。points_exchange_records:product_id = 券模板 id、product_name = 券名、product_type = coupon(不新建 points_exchange_products 配置项,避免双份配置源;记录仅用于历史展示)。points_exchange_products 表支持 productType=coupon 的配置项(管理端新增):配置时关联 coupon_id,points_price 取券模板值。兑换中心分类展示。
实现简化:兑换中心直接展示
coupon.points_price > 0且启用的券列表,不强制复用points_exchange_products配置(避免双份配置源)。管理端在「优惠券管理」统一维护。
商品详情接口返回该商品绑定的券列表(product_id = 商品ID 且 points_price > 0),前端展示「XX 积分兑换」按钮,点击调同一兑换接口。
| 位置 | 改动 |
|---|---|
ProductDTO.resolveMemberPrice() |
直接返回 price(不再按等级计算) |
ProductRecommendationService |
memberPrice 字段返回 price |
| 小程序商品列表/详情页 | 移除「会员价」展示,仅显示统一价 |
| 管理端商品表单 | 隐藏 member_price 输入框 |
products.member_price 字段 |
保留不删(数据兼容),不再参与定价 |
活动侧(ActivityDTO.resolveMemberPrice、MembershipController.applyMemberDiscount、member_discount_*) |
完全不动 |
| 页面 | 改动 |
|---|---|
| 商品详情页 | 新增「积分兑换优惠券」区块:绑定该商品的券 + 兑换按钮 + 积分余额展示 |
| 积分兑换中心 | 新增「优惠券」分类(points_price>0 的券) |
| 我的优惠券 | 券卡片展示来源标签(会员赠送 / 积分兑换 / 家庭人口) |
| 会员中心 | 展示等级权益:加入赠送 X 张 / 每周期发放 X 张 |
| 家庭成员管理 | 新增成员成功提示「已发放 X 张优惠券」 |
| 页面 | 改动 |
|---|---|
| 优惠券管理 | 表单新增:coupon_type、discount_rate、product_id(商品选择器)、points_price、grant_type、grant_level_code、grant_period、grant_quantity |
| 商品管理 | 移除会员价输入 |
| 新增「发券记录」页面 | 按用户/券/类型查询 coupon_grant_log |
| 场景 | 处理 |
|---|---|
| 成员被移除 | 已发券不回收,自然过期 |
| 会员过期/退款 | JOIN/PERIODIC 已发券不回收;PERIODIC 停止补发 |
| 手动添加成员(无账号) | 人口券发到家庭创建者账户 |
| 券过期 | 现有 listAvailable() 已过滤;核销 apply() 校验有效期 |
| 折扣券核销 | CouponService.apply() 扩展:DISCOUNT 抵扣 = orderAmount * discount_rate / 10000,上限 = orderAmount |
| 无门槛券(CASH) | min_spend 忽略,直接抵扣 value |
| 商品退款 | 复用 couponService.revert() |
| 试用会员 | 不发 JOIN 券 |
| 重复续费(同等级) | JOIN 券照发(续费视为一次发放) |
DatabaseInitializer.runMigrations() 追加(编号接现有最大编号):
coupon 表添加 8 列(coupon_type/discount_rate/product_id/points_price/grant_type/grant_level_code/grant_period/grant_quantity),用 ensureColumn 逐列添加;coupon_grant_log 表(CREATE TABLE IF NOT EXISTS);coupon CREATE TABLE 补列 + 追加 coupon_grant_log 建表语句。CouponService.apply():FIXED/CASH/DISCOUNT 三类型抵扣计算、满减门槛、折扣上限;FamilyMemberManagementFlowTest 扩展新增成员发券断言。applyMemberDiscount 不受影响;商品下单全链路(统一价)。products.member_price 字段物理删除