product_gift_rules 表(赠送会员功能,本功能不复用);本功能新增 product_gift_configs / product_gift_items / product_order_gifts 三张表product_gift_rules 实现的是"购买→自动赠送会员等级"功能(无用户选择、无CF值、无赠品商品)。本功能是完全不同的业务场景,表结构和接口独立。
| 决策点 | 结论 |
|---|---|
| 等级粒度 | 商品 × 多个等级,各配一套赠品(同一商品可为 FAMILY/PREMIUM 分别配置不同赠品池) |
| 选择规则 | 用户可多选,每个赠品项最多选1次 |
| 等级判定 | 以家庭创建者(creatorId)的有效会员等级为准 |
| 赠品商品限制 | 无(赠品商品只要启用即可选,不要求购买者等级匹配赠品商品的 memberEligible) |
| CF 值赠品 | 类型 cf 的赠品,支付成功时调用 PlatformPointsService.earn(buyerId, cfValue, ...) 发放到购买者本人账户 |
| 实物赠品 | 扣减赠品商品库存,写入 product_order_gifts 记录,随主商品统一发货;不产生CF值变化 |
| 退款 | 本期不回收 CF 赠品,实物赠品库存不回滚(与现有逻辑一致) |
| 原价购买 | 不限,积分/优惠券购买都可享受赠品 |
product_gift_configs — 商品 × 会员等级 赠品配置(一商品可配多个等级)
CREATE TABLE IF NOT EXISTS product_gift_configs (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
product_id BIGINT NOT NULL COMMENT '关联products表',
level_code VARCHAR(50) NOT NULL COMMENT '会员等级编码(FAMILY/PREMIUM等)',
status TINYINT(1) DEFAULT 1 COMMENT '1=启用 0=停用',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
UNIQUE KEY uk_product_level (product_id, level_code),
INDEX idx_product_id (product_id),
INDEX idx_level_code (level_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品赠品配置(商品×等级)';
product_gift_items — 单个配置下的可选赠品项
CREATE TABLE IF NOT EXISTS product_gift_items (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
config_id BIGINT NOT NULL COMMENT '关联product_gift_configs.id',
gift_type VARCHAR(20) NOT NULL COMMENT 'product=赠品商品 cf=CF值',
gift_product_id BIGINT COMMENT '赠品商品ID(gift_type=product时必填)',
gift_product_name VARCHAR(200) COMMENT '赠品商品名快照',
gift_product_image VARCHAR(500) COMMENT '赠品商品图快照',
cf_value INT DEFAULT 0 COMMENT 'CF值数量(gift_type=cf时必填,单位分)',
sort_order INT DEFAULT 0 COMMENT '排序(值越小越靠前)',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_config_id (config_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品赠品项';
product_order_gifts — 订单选中的赠品记录(下单时写入,支付成功后发放)
CREATE TABLE IF NOT EXISTS product_order_gifts (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
order_id BIGINT NOT NULL COMMENT '关联product_orders.id',
order_no VARCHAR(64) NOT NULL COMMENT '订单号快照',
gift_type VARCHAR(20) NOT NULL COMMENT 'product=赠品商品 cf=CF值',
gift_product_id BIGINT COMMENT '赠品商品ID',
gift_product_name VARCHAR(200) COMMENT '赠品商品名快照',
gift_product_image VARCHAR(500) COMMENT '赠品商品图快照',
cf_value INT DEFAULT 0 COMMENT 'CF值数量(gift_type=cf时)',
quantity INT DEFAULT 1 COMMENT '数量(默认1,可选扩展)',
fulfilled TINYINT(1) DEFAULT 0 COMMENT '0=已选未发放 1=已发放(支付成功置1)',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
INDEX idx_order_id (order_id),
INDEX idx_order_no (order_no)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单赠品记录';
CreateProductOrderDTO 新增字段:
private List<Long> giftItemIds; // 选中的赠品项ID列表(来自 product_gift_items.id)
ProductDTO 新增字段:
// 赠品配置(当前用户等级对应的配置,如无匹配则为空数组)
private List<GiftConfigVO> giftConfigs;
新增内部 VO:
@Data
public static class GiftConfigVO {
private String levelCode;
private String levelName;
private Integer status; // 1启用 0停用
private List<GiftItemVO> items;
}
@Data
public static class GiftItemVO {
private Long id;
private String giftType; // product / cf
private Long giftProductId;
private String giftProductName;
private String giftProductImage;
private Integer cfValue;
private Integer sortOrder;
}
product_gift_configs.uk_product_level:同一商品同一等级只允许一条配置product_gift_items 无唯一约束(一配置下可含多个赠品项)product_order_gifts 允许重复(一个订单同一赠品可多次出现,由业务逻辑保证不重复选)| 文件 | 说明 |
|---|---|
entity/ProductGiftConfig.java |
配置实体 |
entity/ProductGiftItem.java |
赠品项实体 |
entity/ProductOrderGift.java |
订单赠品记录实体 |
mapper/ProductGiftConfigMapper.java |
Mapper |
mapper/ProductGiftItemMapper.java |
Mapper |
mapper/ProductOrderGiftMapper.java |
Mapper |
service/ProductGiftService.java |
赠品配置管理 + 赠品发放 |
dto/ProductDTO.GiftConfigVO.java |
内嵌 VO(已在 §2.2 定义) |
dto/ProductDTO.GiftItemVO.java |
内嵌 VO |
修改 dto/CreateProductOrderDTO.java |
新增 giftItemIds 字段 |
修改 dto/ProductDTO.java |
新增 giftConfigs 字段 |
管理端(继承现有 AdminProductGiftRuleController,或新增独立 Controller):
| 端点 | 说明 |
|---|---|
POST /api/admin/product-gift/save |
保存某商品某等级的赠品配置(含赠品项列表,幂等:先删后插) |
POST /api/admin/product-gift/list |
查询某商品的全部等级赠品配置 |
POST /api/admin/product-gift/delete |
删除某商品某等级的赠品配置(status=0 或直接删除,按业务选择) |
请求体(save):
{
"productId": 123,
"levelCode": "FAMILY",
"status": 1,
"items": [
{"id": 1, "giftType": "product", "giftProductId": 456, "sortOrder": 0},
{"id": 2, "giftType": "cf", "cfValue": 1000, "sortOrder": 1}
]
}
id 为 null 表示新增,有值表示更新现有项giftType=product)必须 status=enabled(启用状态)memberEligible(赠品不受等级限制)商品详情(复用现有 /api/product/detail,在 ProductService.detail() 中扩展):
详情组装时查询 product_gift_configs(status=1),按当前用户等级过滤:
giftConfigs 仅包含该等级配置giftConfigs 为空giftItemsProductOrderService.create(dto, buyerId) 新增赠品校验:
1. 查询 user 的家庭 creatorId
2. 调用 MembershipService.getMemberLevel(creatorId) 获取家庭等级
3. 查询 product_gift_configs(productId 匹配 && levelCode 匹配 && status=1)
4. 若存在有效配置(不强制选择,用户可选0个或多个赠品项,每项限1次):
- 若 dto.giftItemIds 非空:
- 校验每个 giftItemId 属于该配置下的 item(防越权)
- 校验赠品商品库存充足(giftType=product 时)
- 若 dto.giftItemIds 为空:无需赠品,跳过
5. 校验通过后,**立即写入 `product_order_gifts`**(fulfilled=0),记录用户选择的赠品项快照;
若未选赠品,则 product_order_gifts 无记录。
关键约束:giftItemId 必须属于当前用户等级对应的配置(防其他等级越权)。
在现有赠送会员逻辑之后追加赠品发放(第 706-720 行附近):
支付成功
→ 查询 product_order_gifts(order_id = paidOrder.id AND fulfilled=0)
→ 遍历每条记录:
if gift_type = 'cf':
platformPointsService.earn(buyerId, cfValue,
"product_gift", orderId, "购买商品赠品: " + giftProductName)
if gift_type = 'product':
decreaseStock(giftProductId, quantity)
inventoryService.recordOutbound(giftProductId, null, quantity,
"gift", orderId, null, "商品赠品:" + orderNo)
→ 置 fulfilled=1
→ 异常捕获:log.error,不抛错(与现有赠送会员失败处理一致)
为什么在 create 时写入、在支付成功时发放:
// 保存赠品配置(幂等:先删后插 items;config 行更新或插入)
@Transactional
public Result<Void> saveGiftConfig(Long productId, String levelCode, Integer status, List<GiftItemDTO> items)
// 查询商品全部赠品配置(管理端用,返回所有等级)
public List<GiftConfigVO> listConfigsByProduct(Long productId)
// 查询某商品某等级的可用赠品项(含库存校验,供商品详情页展示)
public List<GiftItemVO> getAvailableItems(Long productId, String levelCode)
// 下单时写入订单赠品记录(create 流程调用,非事务内)
public void recordGiftSelection(Long orderId, String orderNo, List<Long> giftItemIds)
// 支付成功时发放赠品
public void fulfillGifts(Long orderId, String orderNo, Long buyerId)
pages/discover-detail/product-detail/product-detail.vue
product.giftConfigs 非空,在价格区下方展示"购买即送"区块giftItemIds 字段CreateProductOrderDTO 接收 giftItemIds,下单时透传。
cfc-web/src/views/admin/ProductEdit.vue
membership_levels(过滤 FREE),显示各等级配置POST /api/admin/product-gift/save暂不需要独立管理页,赠品配置随商品编辑一并管理。后续如配置复杂可扩展独立页面。
| 场景 | 行为 |
|---|---|
| 家庭等级为 FREE | 不展示赠品选择区(无对应配置) |
| 赠品配置停用(status=0) | 不展示 |
| 赠品商品库存不足 | 下单校验失败,提示"赠品商品库存不足" |
| 赠品商品已下架 | 商品详情页不展示该赠品项 |
| 用户选中的赠品项不属于其等级配置 | 下单校验失败,拒绝(防越权) |
| 赠品发放异常(如库存扣减失败) | 记日志,订单支付状态不变,已发放的赠品不可逆(与退款不回收赠品一致) |
| 取消/退款 | 不回收 CF 赠品,不回滚实物赠品库存;product_order_gifts.fulfilled 已置1则保持不变 |
| 未选赠品(用户主动不选) | 下单正常完成,product_order_gifts 无记录 |
| 一商品多等级配置 | 用户按自身等级只看对应配置 |
// 迁移N: 创建 product_gift_configs 表(商品可选赠品配置)
try {
jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS product_gift_configs (" +
"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
"product_id BIGINT NOT NULL, " +
"level_code VARCHAR(50) NOT NULL, " +
"status TINYINT(1) DEFAULT 1, " +
"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
"UNIQUE KEY uk_product_level (product_id, level_code), " +
"INDEX idx_product_id (product_id), " +
"INDEX idx_level_code (level_code)" +
") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品赠品配置'");
log.info("已创建product_gift_configs表");
} catch (Exception e) {
// 表已存在,忽略
}
// 迁移N+1: 创建 product_gift_items 表(商品赠品项)
// 迁移N+2: 创建 product_order_gifts 表(订单赠品记录)
同步更新 schema.sql。
mvn clean compile 通过npm run build:mp-weixin 通过npm run build 通过product_order_gifts 有记录| 操作 | 文件 |
|---|---|
| 新增 | entity/ProductGiftConfig.java |
| 新增 | entity/ProductGiftItem.java |
| 新增 | entity/ProductOrderGift.java |
| 新增 | mapper/ProductGiftConfigMapper.java |
| 新增 | mapper/ProductGiftItemMapper.java |
| 新增 | mapper/ProductOrderGiftMapper.java |
| 新增 | service/ProductGiftService.java |
| 修改 | dto/CreateProductOrderDTO.java(新增 giftItemIds) |
| 修改 | dto/ProductDTO.java(新增 giftConfigs 及内嵌 VO) |
| 修改 | service/ProductService.java(详情组装赠品配置) |
| 修改 | service/ProductOrderService.java(create 校验赠品 + handlePaymentSuccess 发放赠品) |
| 修改 | controller/admin/AdminProductGiftRuleController.java 或新增 AdminProductGiftController.java |
| 修改 | config/DatabaseInitializer.java(三张建表迁移) |
| 修改 | resources/schema.sql(三张建表 SQL) |
| 操作 | 文件 |
|---|---|
| 修改 | pages/discover-detail/product-detail/product-detail.vue(展示赠品选择区) |
| 修改 | 下单相关页面/组件(提交 giftItemIds) |
| 修改 | utils/api.js(新增赠品配置查询接口封装) |
| 操作 | 文件 |
|---|---|
| 修改 | src/views/admin/ProductEdit.vue(赠品配置区块) |
| 修改 | src/api/admin.js(新增赠品管理接口封装) |
| 维度 | 赠送会员(已有) | 可选赠品(本次) |
|---|---|---|
| 表 | product_gift_rules |
product_gift_configs + product_gift_items |
| 触发 | 支付成功自动赠送 | 用户主动选择后支付成功发放 |
| 内容 | 会员等级 | 商品 或 CF值 |
| 选择 | 无选择,系统自动 | 用户从赠品池中多选 |
| 会员限制 | 商品→会员等级绑定 | 配置→会员等级绑定,赠品商品无等级限制 |
| CF值 | 不涉及 | 赠品项可为 CF 值类型 |