- 新增商城基础信息配置界面支持店铺名称、Logo、描述、电话、地址和开关配置 - 实现图片选择和删除功能,支持Logo的上传回显 - 集成表单校验和保存接口调用,提供保存状态反馈 - 优化响应式布局适配不同屏幕尺寸 fix(cms): 防止文章编辑内容的XSS攻击 - 在文章编辑组件中对动态HTML内容添加DOMPurify消毒 - 替换 v-html 渲染为安全消毒后的内容展现 - 确保富文本内容安全,防止跨站脚本漏洞 refactor(system-setting): 优化系统设置基本信息组件逻辑 - 替换ico文件上传组件,改用SelectFile实现图片选择和删除功能 - 简化图标上传流程,移除上传接口调用相关代码 - 统一表单数据处理,增强设置数据解析和回显兼容性 - 调整保存逻辑,支持根据是否存在主键调用新增或更新接口 - 改进watch数据响应逻辑,支持多种数据结构兼容 fix(system-setting): 修正清理设置组件数据重置逻辑 - 统一清理设置组件的 settingKey 值为 clear,避免混淆 - 优化数据监听回调,支持不同数据结构和空数据重置表单 - 确保组件初始化状态正确,避免遗留数据影响展示 fix(store): 修正 chat store 定义方式 - 按 pinia 官方规范简化 store 定义参数 - 修复 store id 错误传递问题,确保正确注册和使用
7.9 KiB
7.9 KiB
优惠券模块后端改造方案
前端 Vue 改造已完成(paopao-vue),本文档为后端 Java 端对应的改动清单。
一、DDL — shop_coupon 表新增字段
-- ============================================================
-- 优惠券模块升级:新增发放对象控制 + 场地使用券支持
-- 执行前请先备份表数据!
-- ============================================================
ALTER TABLE `shop_coupon`
-- 发放对象(0全部用户 1仅会员 2仅非会员 3指定用户)
ADD COLUMN `receive_target` TINYINT NOT NULL DEFAULT 0 COMMENT '发放对象(0全部用户 1仅会员 2仅非会员 3指定用户)' AFTER `enabled`,
-- 指定用户ID列表(JSON数组),receive_target=3 时使用
ADD COLUMN `receive_user_ids` VARCHAR(1000) DEFAULT NULL COMMENT '指定用户ID列表(JSON数组格式),receiveTarget=3时使用' AFTER `receive_target`,
-- 场地使用券相关字段 (type=50 时使用)
ADD COLUMN `venue_type` TINYINT DEFAULT NULL COMMENT '场地使用券-场地类型' AFTER `receive_user_ids`,
ADD COLUMN `venue_id` INT DEFAULT NULL COMMENT '场地使用券-指定场地ID' AFTER `venue_type`,
ADD COLUMN `use_count` INT NOT NULL DEFAULT -1 COMMENT '场地使用券-可用次数(-1无限制)' AFTER `venue_id`,
ADD COLUMN `use_duration` INT DEFAULT NULL COMMENT '场地使用券-使用时长(分钟)' AFTER `use_count`;
字段说明
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
receive_target |
TINYINT | 0 |
0=全部, 1=仅会员, 2=仅非会员, 3=指定用户 |
receive_user_ids |
VARCHAR(1000) | NULL | JSON 数组如 [101,202,303], receiveTarget=3 时有值 |
venue_type |
TINYINT | NULL | 场地类型,type=50 时填写 |
venue_id |
INT | NULL | 具体场地 ID,type=50 时填写 |
use_count |
INT | -1 |
可用次数,-1=不限制 |
use_duration |
INT | NULL | 使用时长(分钟) |
二、Java Entity — ShopCoupon.java 新增字段
在实体类中新增以下字段(与前端 model/index.ts 对齐):
// ========== 以下是新增字段 ==========
/**
* 发放对象(0全部用户 1仅会员 2仅非会员 3指定用户)
*/
@TableField("receive_target")
private Integer receiveTarget;
/**
* 指定用户ID列表(JSON数组格式),receiveTarget=3时使用
* 存储格式: ["101","202","303"] 或 [101,202,303]
*/
@TableField("receive_user_ids")
private String receiveUserIds;
/**
* 场地使用券-场地类型 (type=50时使用)
*/
@TableField("venue_type")
private Integer venueType;
/**
* 场地使用券-指定场地ID (type=50时使用)
*/
@TableField("venue_id")
private Integer venueId;
/**
* 场地使用券-可用次数(-1表示无限制)
*/
@TableField("use_count")
private Integer useCount;
/**
* 场地使用券-使用时长(分钟)
*/
@TableField("use_duration")
private Integer useDuration;
注意:如果项目使用了 Lombok
@Data或手动 getter/setter,确保新字段也有对应的访问方法。
三、领券/用券校验逻辑
3.1 领券接口校验(用户领取优惠券时)
在领券 Controller 或 Service 中增加校验:
/**
* 校验用户是否有资格领取该优惠券
*
* @param coupon 优惠券信息
* @param userId 当前用户ID
* @param userGradeId 用户会员等级ID (0=非会员, >0=会员)
* @throws BusinessException 校验不通过时抛出业务异常
*/
public void validateCouponReceiveTarget(ShopCoupon coupon, Long userId, Integer userGradeId) {
Integer target = coupon.getReceiveTarget();
// target=0 全部用户可领,直接通过
if (target == null || target == 0) {
return;
}
// target=1 仅会员可领
if (target == 1) {
if (userGradeId == null || userGradeId <= 0) {
throw new BusinessException("该优惠券仅限会员领取");
}
return;
}
// target=2 仅非会员可领
if (target == 2) {
if (userGradeId != null && userGradeId > 0) {
throw new BusinessException("该优惠券仅限非会员领取");
}
return;
}
// target=3 指定用户可领
if (target == 3) {
String receiveUserIds = coupon.getReceiveUserIds();
if (StringUtils.isBlank(receiveUserIds)) {
throw new BusinessException("该优惠券未设置指定用户");
}
// 解析 JSON 数组
List<Long> allowedUserIds = JsonUtils.parseArray(receiveUserIds, Long.class);
if (allowedUserIds == null || !allowedUserIds.contains(userId)) {
throw new BusinessException("您不在该优惠券的发放范围内");
}
return;
}
// 未知的 target 值,默认放行(向后兼容)
}
3.2 调用位置建议
用户点击"领取优惠券"
│
▼
┌──────────────────────┐
│ 1. 检查优惠券是否启用 │
│ 2. 检查是否已过期 │
│ 3. 检查发放数量是否已完 │
│ 4. 检查每人限领数量 │ ◄── 在此步骤之后、实际发券之前插入
│ ★5. 校验发放对象 ★ │ validateCouponReceiveTarget()
│ 6. 创建用户优惠券记录 │
└──────────────────────┘
3.3 用券/下单时的校验(可选增强)
下单抵扣时除了常规的金额/时间/商品范围校验外,可追加:
/**
* 下单使用优惠券时的额外校验
*/
public void validateCouponForOrder(ShopCoupon coupon, ShopUser user, OrderContext ctx) {
// ... 已有的金额/有效期/适用范围校验 ...
// 场地使用券(type=50)特殊校验
if (coupon.getType() != null && coupon.getType() == 50) {
// 验证订单是否包含场地服务
if (!ctx.hasVenueItem()) {
throw new BusinessException("该券仅可用于场地预订");
}
// 如果指定了场地类型/ID,校验是否匹配
if (coupon.getVenueType() != null && !coupon.getVenueType().equals(ctx.getVenueType())) {
throw new BusinessException("场地类型不匹配");
}
if (coupon.getVenueId() != null && !coupon.getVenueId().equals(ctx.getVenueId())) {
throw new BusinessException("指定场地不匹配");
}
}
}
四、JSON 工具方法说明
receive_user_ids 字段的读写需要 JSON 序列化/反序列化:
写入(保存优惠券时):
// 前端传来的是 JSON 字符串 "[101,202,303]",直接存即可
coupon.setReceiveUserIds(receiveUserIdsJsonString);
读取(校验时解析):
// 推荐使用 Jackson / Fastjson / Gson (项目已有的 JSON 库)
// 示例(Jackson):
ObjectMapper mapper = new ObjectMapper();
List<Long> userIds = mapper.readValue(coupon.getReceiveUserIds(),
new TypeReference<List<Long>>() {});
五、前端-后端字段对照表
| 前端字段 (TypeScript) | 后端字段 (Java/DB) | 类型 |
|---|---|---|
receiveTarget |
receive_target |
Integer/TINYINT |
receiveUserIds |
receive_user_ids |
String/VARCHAR |
venueType |
venue_type |
Integer/TINYINT |
venueId |
venue_id |
Integer/INT |
useCount |
use_count |
Integer/INT |
useDuration |
use_duration |
Integer/INT |
六、注意事项
- 向后兼容:
receive_target默认值0表示全部用户,不影响已有优惠券的领用行为 - JSON 格式一致性:前端提交时已序列化为 JSON 字符串,后端直接存储;读取时按 JSON 解析
- 场地使用券 (type=50) 是独立于普通商品券的类型,下单系统需单独适配其核销逻辑
gradeId判断会员:依赖shop_user.grade_id字段,grade_id > 0为会员,=0或NULL为非会员- 建议先在测试环境执行 DDL 并验证完整流程后再上生产