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

9.5 KiB
Raw Blame History

积分冻结与过期机制 — 未实现功能设计

本文档仅覆盖尚未实现的功能中需要用到积分冻结/过期机制的场景。
已实现功能(订单冻结、活动冻结、批次过期等)不在本文档范围内。
编写日期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 数据库设计建议

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 实现方式

// 风控冻结:与业务冻结区分,使用独立的 source 标记
void freezeByRisk(Long userId, int amount, String riskType, String reason);
void unfreezeByRisk(Long userId, int amount, Long auditId);  // 管理员审核后解冻

4.3 数据库设计建议

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 是硬编码。需要改为后台可配置:

-- 在 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 代码改造要点

// 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防止重复冻结

// 冻结前检查是否已冻结
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 冻结积分不参与过期

被冻结的积分在冻结期间不应触发过期(过期定时任务需排除冻结状态的批次)。如果业务解冻后发现原批次已过期,应创建新批次并设合理有效期。


本文档随功能开发进度持续更新