13 changed files with 318 additions and 28 deletions
@ -0,0 +1,28 @@ |
|||||
|
package com.youlai.boot.common.event; |
||||
|
|
||||
|
import lombok.Getter; |
||||
|
import org.springframework.context.ApplicationEvent; |
||||
|
|
||||
|
/** |
||||
|
* 用户注册成功事件 |
||||
|
* 所有注册成功后需要执行的附加逻辑(送积分、送优惠券、发通知等)都监听这个事件即可 |
||||
|
*/ |
||||
|
@Getter |
||||
|
public class UserRegisterEvent extends ApplicationEvent { |
||||
|
|
||||
|
/** |
||||
|
* 新注册用户的ID |
||||
|
*/ |
||||
|
private final Long userId; |
||||
|
|
||||
|
/** |
||||
|
* 注册来源:MINI_PROGRAM小程序 / H5 / ADMIN后台等 |
||||
|
*/ |
||||
|
private final String registerSource; |
||||
|
|
||||
|
public UserRegisterEvent(Long userId, String registerSource) { |
||||
|
super(userId); |
||||
|
this.userId = userId; |
||||
|
this.registerSource = registerSource; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,77 @@ |
|||||
|
package com.youlai.boot.mini.listener; |
||||
|
|
||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
||||
|
import com.youlai.boot.common.event.UserRegisterEvent; |
||||
|
import com.youlai.boot.mini.model.entity.MiniPointRecord; |
||||
|
import com.youlai.boot.mini.model.entity.MiniPointRule; |
||||
|
import com.youlai.boot.mini.model.form.AdjustUserPointForm; |
||||
|
import com.youlai.boot.mini.service.MiniPointRecordService; |
||||
|
import com.youlai.boot.mini.service.MiniPointRuleService; |
||||
|
import lombok.RequiredArgsConstructor; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.context.event.EventListener; |
||||
|
import org.springframework.scheduling.annotation.Async; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
import org.springframework.transaction.annotation.Transactional; |
||||
|
|
||||
|
/** |
||||
|
* 用户注册成功监听器:赠送新用户注册积分 |
||||
|
* 完全独立,和登录逻辑解耦,后续加其他注册福利只需要加新的监听器即可 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Component |
||||
|
@RequiredArgsConstructor |
||||
|
public class UserRegisterPointListener { |
||||
|
|
||||
|
private final MiniPointRecordService pointRecordService; |
||||
|
private final MiniPointRuleService pointRuleService; |
||||
|
|
||||
|
/** |
||||
|
* 异步监听用户注册成功事件,赠送积分 |
||||
|
* 异步执行不影响主线登录流程,就算赠送积分失败也不会影响用户正常登录 |
||||
|
*/ |
||||
|
@Async |
||||
|
@Transactional(rollbackFor = Exception.class) |
||||
|
@EventListener(UserRegisterEvent.class) |
||||
|
public void handleUserRegisterGivePoint(UserRegisterEvent event) { |
||||
|
Long userId = event.getUserId(); |
||||
|
String source = event.getRegisterSource(); |
||||
|
log.info("监听到新用户注册成功,userId={}, 注册来源={}, 开始处理注册赠送积分逻辑", userId, source); |
||||
|
|
||||
|
try { |
||||
|
// ================= 1. 幂等校验:避免重复赠送积分 =================
|
||||
|
long count = pointRecordService.count(new LambdaQueryWrapper<MiniPointRecord>() |
||||
|
.eq(MiniPointRecord::getUserId, userId) |
||||
|
.eq(MiniPointRecord::getBizType, "REGISTER_GIFT") |
||||
|
.eq(MiniPointRecord::getDeleted, false)); |
||||
|
if (count > 0) { |
||||
|
log.info("用户{}已经领取过注册积分,无需重复赠送", userId); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
// ================= 2. 查询注册积分规则,可配置无需改代码 =================
|
||||
|
MiniPointRule registerRule = pointRuleService.getOne(new LambdaQueryWrapper<MiniPointRule>() |
||||
|
.eq(MiniPointRule::getRuleCode, "REGISTER_GIFT") |
||||
|
.eq(MiniPointRule::getStatus, false) // 0=启用状态
|
||||
|
.last("LIMIT 1")); |
||||
|
if (registerRule == null || registerRule.getPoints() <= 0) { |
||||
|
log.info("注册赠送积分规则未配置/已关闭/赠送积分为0,不执行赠送"); |
||||
|
return; |
||||
|
} |
||||
|
Integer giftPoint = registerRule.getPoints(); |
||||
|
|
||||
|
// ================= 3. 调用现有积分调整接口赠送积分 =================
|
||||
|
AdjustUserPointForm adjustForm = new AdjustUserPointForm(); |
||||
|
adjustForm.setUserId(userId); |
||||
|
adjustForm.setBizType("REGISTER_GIFT"); |
||||
|
adjustForm.setChangeAmount(giftPoint); |
||||
|
// 调用之前写的调整积分接口,自动创建积分账户、生成流水
|
||||
|
pointRecordService.adjustPoint(adjustForm); |
||||
|
|
||||
|
log.info("新用户{}注册赠送{}积分成功", userId, giftPoint); |
||||
|
} catch (Exception e) { |
||||
|
log.error("新用户{}注册赠送积分失败,请手动排查补发", userId, e); |
||||
|
// 这里可以加告警通知,比如钉钉/企业微信推送,不影响用户登录
|
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,25 @@ |
|||||
|
package com.youlai.boot.mini.model.query; |
||||
|
|
||||
|
import com.youlai.boot.common.base.BaseQuery; |
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import lombok.Data; |
||||
|
import java.time.LocalDateTime; |
||||
|
import java.util.Date; |
||||
|
|
||||
|
/** |
||||
|
* 用户端专属积分流水查询参数,完全独立,不和管理端复用 |
||||
|
*/ |
||||
|
@Data |
||||
|
@Schema(description = "用户端积分流水查询参数") |
||||
|
public class MyPointRecordQuery extends BaseQuery { |
||||
|
|
||||
|
@Schema(description = "业务类型筛选,比如SIGN_IN/AI_GENERATE等,不传查全部") |
||||
|
private String bizType; |
||||
|
|
||||
|
@Schema(description = "查询开始时间,yyyy-MM-dd HH:mm:ss") |
||||
|
private Date startTime; |
||||
|
|
||||
|
@Schema(description = "查询结束时间,yyyy-MM-dd HH:mm:ss") |
||||
|
private Date endTime; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
package com.youlai.boot.mini.model.vo; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonFormat; |
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import lombok.Data; |
||||
|
import java.util.Date; |
||||
|
|
||||
|
/** |
||||
|
* 用户端专属积分流水VO,完全独立,不和管理端复用,无数据库自增主键 |
||||
|
*/ |
||||
|
@Data |
||||
|
@Schema(description = "用户端积分流水信息VO") |
||||
|
public class MyPointRecordVO { |
||||
|
|
||||
|
@Schema(description = "流水唯一标识uuid,前端用这个作为唯一key,不用自增id") |
||||
|
private String uuid; |
||||
|
|
||||
|
@Schema(description = "积分变化值,正数为获得积分,负数为扣减积分") |
||||
|
private Integer changeAmount; |
||||
|
|
||||
|
@Schema(description = "本次变动后的积分余额") |
||||
|
private Integer balanceAfter; |
||||
|
|
||||
|
@Schema(description = "业务类型,比如SIGN_IN=签到、AI_GENERATE=AI生成消耗、SYSTEM_ADJUST=系统调整") |
||||
|
private String bizType; |
||||
|
|
||||
|
@Schema(description = "交易发生时间") |
||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
||||
|
private Date createTime; |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
package com.youlai.boot.mini.model.vo; |
||||
|
|
||||
|
import com.fasterxml.jackson.annotation.JsonFormat; |
||||
|
import io.swagger.v3.oas.annotations.media.Schema; |
||||
|
import lombok.*; |
||||
|
|
||||
|
import java.util.Date; |
||||
|
|
||||
|
|
||||
|
@Data |
||||
|
@Builder |
||||
|
@NoArgsConstructor |
||||
|
@AllArgsConstructor |
||||
|
@EqualsAndHashCode(callSuper = false) |
||||
|
@Schema(description = "用户端我的积分信息VO") |
||||
|
public class MyPointVO { |
||||
|
|
||||
|
@Schema(description = "当前积分余额") |
||||
|
private Integer points; |
||||
|
|
||||
|
// @Schema(description = "今日已获得积分,可扩展业务")
|
||||
|
// private Integer todayEarned;
|
||||
|
|
||||
|
@Schema(description = "积分最后更新时间") |
||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") |
||||
|
private Date updateTime; |
||||
|
|
||||
|
@Schema(description = "积分账户唯一标识uuid") |
||||
|
private String uuid; |
||||
|
|
||||
|
} |
||||
Loading…
Reference in new issue