252 lines
9.5 KiB
Markdown
252 lines
9.5 KiB
Markdown
|
|
# 积分冻结与过期机制 — 未实现功能设计
|
|||
|
|
|
|||
|
|
> 本文档仅覆盖**尚未实现的功能**中需要用到积分冻结/过期机制的场景。
|
|||
|
|
> 已实现功能(订单冻结、活动冻结、批次过期等)不在本文档范围内。
|
|||
|
|
> 编写日期:2026-03-21
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 一、积分转赠(待实现功能 #26)
|
|||
|
|
|
|||
|
|
### 1.1 冻结机制
|
|||
|
|
|
|||
|
|
| 环节 | 操作 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| 发起转赠 | `freezePoints(senderId, amount, transferId)` | A 发起转赠→冻结 A 的积分 |
|
|||
|
|
| 对方接收 | `consumeFrozenPoints(senderId, amount, transferId)` + `earnPoints(receiverId, ...)` | B 确认→扣除 A 冻结积分,发放给 B |
|
|||
|
|
| 对方拒绝/超时 | `unfreezePoints(senderId, amount, transferId)` | 超过24小时未接收→自动解冻退回 A |
|
|||
|
|
|
|||
|
|
### 1.2 过期机制
|
|||
|
|
|
|||
|
|
- B 收到的转赠积分创建新批次,有效期建议 **90天**(防止积分通过转赠无限续期)
|
|||
|
|
- 转赠积分的有效期不应继承原批次剩余有效期(否则可通过互转刷新有效期)
|
|||
|
|
|
|||
|
|
### 1.3 风控要点
|
|||
|
|
|
|||
|
|
- 单日转赠上限:每人每日最多转出 500 积分
|
|||
|
|
- 手续费:转赠扣除 10% 手续费(A 转 100,B 收到 90)
|
|||
|
|
- 转赠积分不可再次转赠(标记 `source = 'transfer_in'`,转赠时校验批次来源)
|
|||
|
|
|
|||
|
|
### 1.4 数据库设计建议
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE points_transfers (
|
|||
|
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
sender_id BIGINT NOT NULL COMMENT '转出方用户ID',
|
|||
|
|
receiver_id BIGINT NOT NULL COMMENT '接收方用户ID',
|
|||
|
|
amount INT NOT NULL COMMENT '转赠积分(扣除手续费前)',
|
|||
|
|
fee INT NOT NULL DEFAULT 0 COMMENT '手续费积分',
|
|||
|
|
actual_amount INT NOT NULL COMMENT '实际到账积分',
|
|||
|
|
status ENUM('pending', 'accepted', 'rejected', 'expired', 'cancelled') DEFAULT 'pending',
|
|||
|
|
expired_at DATETIME NOT NULL COMMENT '接收截止时间(24小时)',
|
|||
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|||
|
|
INDEX idx_sender (sender_id),
|
|||
|
|
INDEX idx_receiver (receiver_id),
|
|||
|
|
INDEX idx_status (status)
|
|||
|
|
) COMMENT='积分转赠记录表';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 1.5 接口设计
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
POST /api/v1/points/transfer 发起转赠(冻结积分)
|
|||
|
|
POST /api/v1/points/transfer/{id}/accept 接收转赠(消费冻结+发放)
|
|||
|
|
POST /api/v1/points/transfer/{id}/reject 拒绝转赠(解冻退回)
|
|||
|
|
GET /api/v1/points/transfers 我的转赠记录
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 二、优惠券系统(待实现功能 #16)
|
|||
|
|
|
|||
|
|
### 2.1 冻结机制 — 积分兑换优惠券
|
|||
|
|
|
|||
|
|
| 环节 | 操作 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| 兑换优惠券 | `freezePoints(userId, amount, couponOrderId)` | 用户用积分兑换优惠券→先冻结 |
|
|||
|
|
| 兑换确认 | `consumeFrozenPoints(userId, amount, couponOrderId)` | 系统发放优惠券成功→消费冻结积分 |
|
|||
|
|
| 兑换失败 | `unfreezePoints(userId, amount, couponOrderId)` | 优惠券库存不足或系统异常→解冻退回 |
|
|||
|
|
|
|||
|
|
### 2.2 过期机制 — 优惠券与积分联动
|
|||
|
|
|
|||
|
|
- 优惠券本身有独立有效期,与积分过期无直接关系
|
|||
|
|
- 但如果优惠券过期未使用且支持退还积分,退还的积分建议有效期 **30天**(短周期促消费)
|
|||
|
|
|
|||
|
|
### 2.3 风控要点
|
|||
|
|
|
|||
|
|
- 同一优惠券每人限兑 1 张(防囤券)
|
|||
|
|
- 兑换冻结窗口 ≤ 5秒(快速确认,减少积分被长时间锁定)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 三、限时折扣/秒杀活动(待实现功能 #17)
|
|||
|
|
|
|||
|
|
### 3.1 冻结机制 — 积分参与秒杀
|
|||
|
|
|
|||
|
|
| 环节 | 操作 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| 参与秒杀 | `freezeForActivity(userId, amount, activityId, title)` | 用户参与积分秒杀→冻结积分 |
|
|||
|
|
| 秒杀成功 | `consumeFrozenForActivity(userId, amount, activityId, title)` | 中标→消费冻结积分,发放 Skill |
|
|||
|
|
| 秒杀失败 | `unfreezeForActivity(userId, amount, activityId, title)` | 未中→解冻退回 |
|
|||
|
|
|
|||
|
|
### 3.2 过期机制 — 活动赠送积分
|
|||
|
|
|
|||
|
|
- 限时活动赠送的积分建议有效期 **7-15天**(制造紧迫感,推动快速消费)
|
|||
|
|
- 活动积分批次 `source = 'activity'`,定时过期任务统一处理
|
|||
|
|
|
|||
|
|
### 3.3 关键设计
|
|||
|
|
|
|||
|
|
- 秒杀冻结必须原子操作(Redis + 乐观锁防超卖)
|
|||
|
|
- 活动结束后批量解冻所有未中标用户的冻结积分(定时任务 / 活动结束回调)
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 四、安全风控体系(待实现功能 #25)
|
|||
|
|
|
|||
|
|
### 4.1 冻结机制 — 异常行为冻结
|
|||
|
|
|
|||
|
|
| 场景 | 冻结操作 | 说明 |
|
|||
|
|
|------|---------|------|
|
|||
|
|
| 刷积分检测 | 冻结全部可用积分 | 检测到短时间大量签到/邀请奖励异常→冻结账户积分 |
|
|||
|
|
| 恶意退款检测 | 冻结退还积分 | 频繁购买-退款套利→冻结退还的积分待人工审核 |
|
|||
|
|
| 多账号关联 | 冻结关联账号积分 | 检测到同设备/IP多账号互相邀请→冻结所有关联账号 |
|
|||
|
|
|
|||
|
|
### 4.2 实现方式
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 风控冻结:与业务冻结区分,使用独立的 source 标记
|
|||
|
|
void freezeByRisk(Long userId, int amount, String riskType, String reason);
|
|||
|
|
void unfreezeByRisk(Long userId, int amount, Long auditId); // 管理员审核后解冻
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.3 数据库设计建议
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
CREATE TABLE risk_freeze_records (
|
|||
|
|
id BIGINT PRIMARY KEY AUTO_INCREMENT,
|
|||
|
|
user_id BIGINT NOT NULL,
|
|||
|
|
freeze_amount INT NOT NULL COMMENT '冻结积分数',
|
|||
|
|
risk_type VARCHAR(50) NOT NULL COMMENT '风险类型: point_abuse/refund_abuse/multi_account',
|
|||
|
|
reason VARCHAR(500) COMMENT '冻结原因',
|
|||
|
|
status ENUM('frozen', 'unfrozen', 'deducted') DEFAULT 'frozen',
|
|||
|
|
auditor_id BIGINT COMMENT '审核人ID',
|
|||
|
|
audit_note VARCHAR(500) COMMENT '审核备注',
|
|||
|
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|||
|
|
resolved_at DATETIME COMMENT '处理时间',
|
|||
|
|
INDEX idx_user (user_id),
|
|||
|
|
INDEX idx_status (status)
|
|||
|
|
) COMMENT='风控冻结记录表';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 4.4 风控规则示例
|
|||
|
|
|
|||
|
|
| 规则 | 触发条件 | 冻结策略 |
|
|||
|
|
|------|---------|---------|
|
|||
|
|
| 签到异常 | 同一设备 ≥3 个账号当天签到 | 冻结所有关联账号当天签到积分 |
|
|||
|
|
| 邀请刷量 | 24小时内邀请 ≥10 人且被邀请人无后续活跃 | 冻结邀请奖励积分 |
|
|||
|
|
| 退款套利 | 30天内退款 ≥3 次 | 冻结账户全部积分,人工审核 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 五、积分规则后台配置(待实现功能 #30)
|
|||
|
|
|
|||
|
|
### 5.1 过期机制 — 差异化有效期配置
|
|||
|
|
|
|||
|
|
当前 `DEFAULT_EXPIRE_DAYS = 365` 是硬编码。需要改为后台可配置:
|
|||
|
|
|
|||
|
|
```sql
|
|||
|
|
-- 在 points_rules 表新增字段
|
|||
|
|
ALTER TABLE points_rules ADD COLUMN expire_days INT DEFAULT 365 COMMENT '积分有效期(天),0=永不过期';
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 5.2 配置示例
|
|||
|
|
|
|||
|
|
| 积分来源 | 建议有效期 | 配置字段 |
|
|||
|
|
|---------|-----------|---------|
|
|||
|
|
| register | 30天 | `expire_days = 30` |
|
|||
|
|
| sign_in | 90天 | `expire_days = 90` |
|
|||
|
|
| invite | 180天 | `expire_days = 180` |
|
|||
|
|
| recharge | 365天 | `expire_days = 365` |
|
|||
|
|
| activity | 7-15天 | `expire_days = 7` (按活动单独配置) |
|
|||
|
|
| review | 90天 | `expire_days = 90` |
|
|||
|
|
| admin_add | 0(永不过期) | `expire_days = 0` |
|
|||
|
|
| refund | 180天 | `expire_days = 180` |
|
|||
|
|
| transfer_in | 90天 | `expire_days = 90` |
|
|||
|
|
|
|||
|
|
### 5.3 代码改造要点
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// PointsServiceImpl.createBatch() 改为从 rules 表读取 expire_days
|
|||
|
|
private void createBatch(Long userId, String source, int amount, Long relatedId, String relatedType) {
|
|||
|
|
int expireDays = getExpireDaysBySource(source); // 从 points_rules 表查询
|
|||
|
|
LocalDateTime expireAt = expireDays > 0
|
|||
|
|
? LocalDateTime.now().plusDays(expireDays)
|
|||
|
|
: LocalDateTime.of(2099, 12, 31, 23, 59, 59); // 0=永不过期
|
|||
|
|
// ... 创建批次
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 六、在线客服/工单系统(待实现功能 #22)
|
|||
|
|
|
|||
|
|
### 6.1 冻结机制 — 争议工单积分冻结
|
|||
|
|
|
|||
|
|
| 场景 | 操作 | 说明 |
|
|||
|
|
|------|------|------|
|
|||
|
|
| 用户提交争议工单 | 可选冻结争议订单关联积分 | 涉及积分纠纷的工单,客服可手动冻结相关积分 |
|
|||
|
|
| 工单解决 | 解冻或扣除 | 根据裁决结果解冻退回或扣除 |
|
|||
|
|
|
|||
|
|
### 6.2 实现方式
|
|||
|
|
|
|||
|
|
- 客服后台增加"冻结用户积分"操作按钮
|
|||
|
|
- 记录操作日志(关联工单ID),审计可追溯
|
|||
|
|
- 解冻需要高级客服或管理员权限
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 七、实现优先级建议
|
|||
|
|
|
|||
|
|
| 优先级 | 功能 | 冻结/过期关键点 | 依赖 |
|
|||
|
|
|--------|------|----------------|------|
|
|||
|
|
| **P1** | #30 积分规则后台配置 | 差异化有效期是所有过期策略的基础 | 无 |
|
|||
|
|
| **P1** | #25 风控体系 | 风控冻结是防刷核心手段 | 无 |
|
|||
|
|
| **P2** | #26 积分转赠 | 冻结防并发+过期防续期 | 无 |
|
|||
|
|
| **P2** | #16 优惠券系统 | 兑换冻结+退还过期 | 优惠券模块 |
|
|||
|
|
| **P2** | #17 秒杀活动 | 参与冻结+活动积分短过期 | 活动模块增强 |
|
|||
|
|
| **P3** | #22 客服工单 | 争议冻结 | 客服系统 |
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
## 八、通用技术要点
|
|||
|
|
|
|||
|
|
### 8.1 冻结与解冻的幂等性
|
|||
|
|
|
|||
|
|
所有冻结/解冻操作必须带业务ID(orderId/transferId/activityId),防止重复冻结:
|
|||
|
|
|
|||
|
|
```java
|
|||
|
|
// 冻结前检查是否已冻结
|
|||
|
|
boolean alreadyFrozen = recordRepo.exists(
|
|||
|
|
new LambdaQueryWrapper<PointsRecord>()
|
|||
|
|
.eq(PointsRecord::getRelatedId, bizId)
|
|||
|
|
.eq(PointsRecord::getRelatedType, bizType)
|
|||
|
|
.eq(PointsRecord::getPointsType, "freeze")
|
|||
|
|
);
|
|||
|
|
if (alreadyFrozen) return; // 幂等,跳过
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 8.2 过期批次的FIFO消费
|
|||
|
|
|
|||
|
|
消费积分时优先扣最早到期的批次(已实现 `consumeBatchesFIFO`),确保:
|
|||
|
|
- 用户总是先用"快过期"的积分
|
|||
|
|
- 减少过期浪费,提升用户体验
|
|||
|
|
|
|||
|
|
### 8.3 冻结积分不参与过期
|
|||
|
|
|
|||
|
|
被冻结的积分在冻结期间不应触发过期(过期定时任务需排除冻结状态的批次)。如果业务解冻后发现原批次已过期,应创建新批次并设合理有效期。
|
|||
|
|
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
*本文档随功能开发进度持续更新*
|