Files
shop-admin/docs/coupon-backend-upgrade.md
赵忠林 de93292fa2 feat(shop): 新增商城基础设置组件
- 新增商城基础信息配置界面支持店铺名称、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 错误传递问题,确保正确注册和使用
2026-06-16 12:52:30 +08:00

233 lines
7.9 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 优惠券模块后端改造方案
> 前端 Vue 改造已完成paopao-vue本文档为后端 Java 端对应的改动清单。
---
## 一、DDL — shop_coupon 表新增字段
```sql
-- ============================================================
-- 优惠券模块升级:新增发放对象控制 + 场地使用券支持
-- 执行前请先备份表数据!
-- ============================================================
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 | 具体场地 IDtype=50 时填写 |
| `use_count` | INT | `-1` | 可用次数,-1=不限制 |
| `use_duration` | INT | NULL | 使用时长(分钟) |
---
## 二、Java Entity — ShopCoupon.java 新增字段
在实体类中新增以下字段(与前端 model/index.ts 对齐):
```java
// ========== 以下是新增字段 ==========
/**
* 发放对象(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 中增加校验:
```java
/**
* 校验用户是否有资格领取该优惠券
*
* @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 用券/下单时的校验(可选增强)
下单抵扣时除了常规的金额/时间/商品范围校验外,可追加:
```java
/**
* 下单使用优惠券时的额外校验
*/
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 序列化/反序列化:
**写入(保存优惠券时):**
```java
// 前端传来的是 JSON 字符串 "[101,202,303]",直接存即可
coupon.setReceiveUserIds(receiveUserIdsJsonString);
```
**读取(校验时解析):**
```java
// 推荐使用 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 |
---
## 六、注意事项
1. **向后兼容**`receive_target` 默认值 `0` 表示全部用户,不影响已有优惠券的领用行为
2. **JSON 格式一致性**:前端提交时已序列化为 JSON 字符串,后端直接存储;读取时按 JSON 解析
3. **场地使用券 (type=50)** 是独立于普通商品券的类型,下单系统需单独适配其核销逻辑
4. **`gradeId` 判断会员**:依赖 `shop_user.grade_id` 字段,`grade_id > 0` 为会员,`=0``NULL` 为非会员
5. **建议先在测试环境执行 DDL 并验证完整流程后再上生产**