Files
number/后端架构设计/05-积分冻结与过期机制-未实现功能设计.md

252 lines
9.5 KiB
Markdown
Raw 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.

# 积分冻结与过期机制 — 未实现功能设计
> 本文档仅覆盖**尚未实现的功能**中需要用到积分冻结/过期机制的场景。
> 已实现功能(订单冻结、活动冻结、批次过期等)不在本文档范围内。
> 编写日期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 转 100B 收到 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 冻结与解冻的幂等性
所有冻结/解冻操作必须带业务IDorderId/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 冻结积分不参与过期
被冻结的积分在冻结期间不应触发过期(过期定时任务需排除冻结状态的批次)。如果业务解冻后发现原批次已过期,应创建新批次并设合理有效期。
---
*本文档随功能开发进度持续更新*