|
|
@ -1,14 +1,19 @@ |
|
|
package com.youlai.boot.mini.service.impl; |
|
|
package com.youlai.boot.mini.service.impl; |
|
|
|
|
|
|
|
|
|
|
|
import cn.hutool.core.bean.BeanUtil; |
|
|
|
|
|
import cn.hutool.core.util.StrUtil; |
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
|
|
import com.baomidou.mybatisplus.core.metadata.IPage; |
|
|
import com.baomidou.mybatisplus.core.metadata.IPage; |
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; |
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
|
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; |
|
|
|
|
|
import com.youlai.boot.common.constant.CommonConstants; |
|
|
import com.youlai.boot.common.exception.MsgException; |
|
|
import com.youlai.boot.common.exception.MsgException; |
|
|
import com.youlai.boot.framework.security.util.SecurityUtils; |
|
|
import com.youlai.boot.framework.security.util.SecurityUtils; |
|
|
|
|
|
import com.youlai.boot.mini.mapper.MiniPointAccountMapper; |
|
|
import com.youlai.boot.mini.mapper.MiniPointRecordMapper; |
|
|
import com.youlai.boot.mini.mapper.MiniPointRecordMapper; |
|
|
import com.youlai.boot.mini.model.entity.MiniPointAccount; |
|
|
import com.youlai.boot.mini.model.entity.MiniPointAccount; |
|
|
import com.youlai.boot.mini.model.entity.MiniPointRecord; |
|
|
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.model.form.AdjustUserPointForm; |
|
|
import com.youlai.boot.mini.model.query.MyPointRecordQuery; |
|
|
import com.youlai.boot.mini.model.query.MyPointRecordQuery; |
|
|
import com.youlai.boot.mini.model.query.PointRecordQuery; |
|
|
import com.youlai.boot.mini.model.query.PointRecordQuery; |
|
|
@ -16,13 +21,24 @@ import com.youlai.boot.mini.model.vo.MyPointRecordVO; |
|
|
import com.youlai.boot.mini.model.vo.PointRecordVO; |
|
|
import com.youlai.boot.mini.model.vo.PointRecordVO; |
|
|
import com.youlai.boot.mini.service.MiniPointAccountService; |
|
|
import com.youlai.boot.mini.service.MiniPointAccountService; |
|
|
import com.youlai.boot.mini.service.MiniPointRecordService; |
|
|
import com.youlai.boot.mini.service.MiniPointRecordService; |
|
|
|
|
|
import com.youlai.boot.mini.service.MiniPointRuleService; |
|
|
import lombok.RequiredArgsConstructor; |
|
|
import lombok.RequiredArgsConstructor; |
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
|
|
|
import org.springframework.data.redis.core.StringRedisTemplate; |
|
|
import org.springframework.stereotype.Service; |
|
|
import org.springframework.stereotype.Service; |
|
|
import org.springframework.transaction.annotation.Transactional; |
|
|
import org.springframework.transaction.annotation.Transactional; |
|
|
|
|
|
|
|
|
|
|
|
import java.time.DayOfWeek; |
|
|
|
|
|
import java.time.LocalDate; |
|
|
|
|
|
import java.time.LocalDateTime; |
|
|
|
|
|
import java.time.format.DateTimeFormatter; |
|
|
|
|
|
import java.time.temporal.TemporalAdjusters; |
|
|
|
|
|
import java.time.temporal.WeekFields; |
|
|
import java.util.Date; |
|
|
import java.util.Date; |
|
|
|
|
|
import java.util.Locale; |
|
|
|
|
|
import java.util.Optional; |
|
|
import java.util.UUID; |
|
|
import java.util.UUID; |
|
|
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
|
|
|
|
|
@Service |
|
|
@Service |
|
|
@RequiredArgsConstructor |
|
|
@RequiredArgsConstructor |
|
|
@ -30,6 +46,9 @@ import java.util.UUID; |
|
|
public class MiniPointRecordServiceImpl extends ServiceImpl<MiniPointRecordMapper, MiniPointRecord> implements MiniPointRecordService { |
|
|
public class MiniPointRecordServiceImpl extends ServiceImpl<MiniPointRecordMapper, MiniPointRecord> implements MiniPointRecordService { |
|
|
|
|
|
|
|
|
private final MiniPointAccountService miniPointAccountService; |
|
|
private final MiniPointAccountService miniPointAccountService; |
|
|
|
|
|
private final MiniPointRuleService ruleService; |
|
|
|
|
|
private final StringRedisTemplate redisTemplate; |
|
|
|
|
|
private final MiniPointAccountMapper miniPointAccountMapper; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
@Override |
|
|
@ -70,7 +89,9 @@ public class MiniPointRecordServiceImpl extends ServiceImpl<MiniPointRecordMappe |
|
|
record.setChangeAmount(form.getChangeAmount()); |
|
|
record.setChangeAmount(form.getChangeAmount()); |
|
|
record.setBalanceAfter(afterBalance); |
|
|
record.setBalanceAfter(afterBalance); |
|
|
record.setBizType(form.getBizType()); |
|
|
record.setBizType(form.getBizType()); |
|
|
record.setBizId(UUID.randomUUID().toString().replace("-", "")); // 业务唯一ID,保证幂等
|
|
|
// record.setRemark(form.getRemark());
|
|
|
|
|
|
// 优先用传进来的业务唯一ID(比如AI任务ID),没有的话自动生成,保证幂等
|
|
|
|
|
|
record.setBizId(StrUtil.isNotBlank(form.getBizId()) ? form.getBizId() : UUID.randomUUID().toString().replace("-", "")); |
|
|
record.setCreateBy(SecurityUtils.getUserId()); |
|
|
record.setCreateBy(SecurityUtils.getUserId()); |
|
|
record.setCreateTime(new Date()); |
|
|
record.setCreateTime(new Date()); |
|
|
record.setCreateTimestamp(System.currentTimeMillis()); |
|
|
record.setCreateTimestamp(System.currentTimeMillis()); |
|
|
@ -89,4 +110,303 @@ public class MiniPointRecordServiceImpl extends ServiceImpl<MiniPointRecordMappe |
|
|
return baseMapper.pageMyPointRecord(page, userId, query); |
|
|
return baseMapper.pageMyPointRecord(page, userId, query); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 简单封装通用周期校验 |
|
|
|
|
|
* @param ruleCode 规则编码 |
|
|
|
|
|
* @param userId 用户ID |
|
|
|
|
|
* @param bizPrefix 业务前缀,避免不同业务Key冲突 |
|
|
|
|
|
* @return 校验结果:[是否允许, 奖励积分, 计数Key, 过期天数, 周期类型, 超限提示, Redis是否正常] |
|
|
|
|
|
*/ |
|
|
|
|
|
private Object[] checkPeriodLimit(String ruleCode, Long userId, String bizPrefix) { |
|
|
|
|
|
// 1. 查询规则
|
|
|
|
|
|
MiniPointRule rule = ruleService.getOne(new LambdaQueryWrapper<MiniPointRule>() |
|
|
|
|
|
.eq(MiniPointRule::getRuleCode, ruleCode) |
|
|
|
|
|
.eq(MiniPointRule::getStatus, false) |
|
|
|
|
|
.last("LIMIT 1")); |
|
|
|
|
|
if (rule == null || rule.getPoints() <= 0) { |
|
|
|
|
|
return new Object[]{false, 0, null, 0L, null, "活动暂未开放", true}; |
|
|
|
|
|
} |
|
|
|
|
|
int rewardPoint = rule.getPoints(); |
|
|
|
|
|
String limitPeriod = rule.getLimitPeriod(); |
|
|
|
|
|
int limitCount = rule.getLimitCount(); |
|
|
|
|
|
|
|
|
|
|
|
LocalDate now = LocalDate.now(); |
|
|
|
|
|
|
|
|
|
|
|
// 2. 分情况处理周期逻辑
|
|
|
|
|
|
if ("ALL".equals(limitPeriod)) { |
|
|
|
|
|
// ALL周期:永久限制次数,无时间范围,适合注册送分这类一生一次的奖励
|
|
|
|
|
|
String countKey = String.format("reward:count:%s:all:%s:all", bizPrefix, userId); |
|
|
|
|
|
long currentCount = 0; |
|
|
|
|
|
boolean redisNormal = true; |
|
|
|
|
|
try { |
|
|
|
|
|
currentCount = Optional.ofNullable(redisTemplate.opsForValue().get(countKey)).map(Long::parseLong).orElse(0L); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
redisNormal = false; |
|
|
|
|
|
log.warn("Redis不可用,触发数据库计数兜底,ruleCode={}, userId={}", ruleCode, userId, e); |
|
|
|
|
|
currentCount = countRecordByPeriod(userId, bizPrefix, limitPeriod, now); |
|
|
|
|
|
} |
|
|
|
|
|
// 校验永久次数上限
|
|
|
|
|
|
if (currentCount >= limitCount) { |
|
|
|
|
|
return new Object[]{false, 0, null, 0L, limitPeriod, "该奖励仅可领取" + limitCount + "次", redisNormal}; |
|
|
|
|
|
} |
|
|
|
|
|
// ALL周期缓存设置10年过期,足够覆盖用户生命周期
|
|
|
|
|
|
return new Object[]{true, rewardPoint, countKey, 3650L, limitPeriod, null, redisNormal}; |
|
|
|
|
|
} |
|
|
|
|
|
// limit_period = null 才是完全无限制,不做任何次数校验
|
|
|
|
|
|
if (limitPeriod == null) { |
|
|
|
|
|
return new Object[]{true, rewardPoint, null, 0L, limitPeriod, null, true}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 3. 周期计算
|
|
|
|
|
|
String periodKey; |
|
|
|
|
|
long expireDays = 0; |
|
|
|
|
|
String limitTip = ""; |
|
|
|
|
|
switch (limitPeriod) { |
|
|
|
|
|
case "DAY": |
|
|
|
|
|
periodKey = now.format(DateTimeFormatter.ofPattern("yyyyMMdd")); |
|
|
|
|
|
expireDays = 1L; |
|
|
|
|
|
limitTip = "今日奖励已达上限,请明天再来"; |
|
|
|
|
|
break; |
|
|
|
|
|
case "WEEK": |
|
|
|
|
|
//用本周一日期作为周期key,解决跨年周计数分裂问题
|
|
|
|
|
|
LocalDate monday = now.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)); |
|
|
|
|
|
periodKey = monday.format(DateTimeFormatter.ofPattern("yyyyMMdd")); |
|
|
|
|
|
expireDays = 8L; |
|
|
|
|
|
limitTip = "本周奖励已达上限,请下周再来"; |
|
|
|
|
|
break; |
|
|
|
|
|
case "MONTH": |
|
|
|
|
|
periodKey = now.format(DateTimeFormatter.ofPattern("yyyyMM")); |
|
|
|
|
|
expireDays = 32L; |
|
|
|
|
|
limitTip = "本月奖励已达上限,请下月再来"; |
|
|
|
|
|
break; |
|
|
|
|
|
case "YEAR": |
|
|
|
|
|
periodKey = String.valueOf(now.getYear()); |
|
|
|
|
|
expireDays = 366L; |
|
|
|
|
|
limitTip = "本年奖励已达上限,请明年再来"; |
|
|
|
|
|
break; |
|
|
|
|
|
default: |
|
|
|
|
|
return new Object[]{true, rewardPoint, null, 0L, limitPeriod, null, true}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 4. 计数校验:优先读Redis,异常时走数据库兜底
|
|
|
|
|
|
String countKey = String.format("reward:count:%s:%s:%s:%s", bizPrefix, limitPeriod.toLowerCase(), userId, periodKey); |
|
|
|
|
|
long currentCount = 0; |
|
|
|
|
|
boolean redisNormal = true; |
|
|
|
|
|
try { |
|
|
|
|
|
currentCount = Optional.ofNullable(redisTemplate.opsForValue().get(countKey)).map(Long::parseLong).orElse(0L); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
// Redis异常,走数据库兜底
|
|
|
|
|
|
redisNormal = false; |
|
|
|
|
|
log.warn("Redis不可用,触发数据库计数兜底,ruleCode={}, userId={}", ruleCode, userId, e); |
|
|
|
|
|
currentCount = countRecordByPeriod(userId, bizPrefix, limitPeriod, now); |
|
|
|
|
|
} |
|
|
|
|
|
if (currentCount >= limitCount) { |
|
|
|
|
|
return new Object[]{false, 0, null, 0L, limitPeriod, limitTip, redisNormal}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return new Object[]{true, rewardPoint, countKey, expireDays, limitPeriod, null, redisNormal}; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
* 按周期从积分流水表统计用户领取次数,100%准确,作为Redis兜底 |
|
|
|
|
|
*/ |
|
|
|
|
|
private long countRecordByPeriod(Long userId, String bizType, String limitPeriod, LocalDate now) { |
|
|
|
|
|
// ALL周期:统计该用户该业务类型的所有流水,无时间范围
|
|
|
|
|
|
if ("ALL".equals(limitPeriod)) { |
|
|
|
|
|
return count(new LambdaQueryWrapper<MiniPointRecord>() |
|
|
|
|
|
.eq(MiniPointRecord::getUserId, userId) |
|
|
|
|
|
.eq(MiniPointRecord::getBizType, bizType.toUpperCase()) |
|
|
|
|
|
.eq(MiniPointRecord::getDeleted, 0)); |
|
|
|
|
|
} |
|
|
|
|
|
LocalDateTime startTime, endTime; |
|
|
|
|
|
switch (limitPeriod) { |
|
|
|
|
|
case "DAY": |
|
|
|
|
|
startTime = now.atStartOfDay(); |
|
|
|
|
|
endTime = now.plusDays(1).atStartOfDay().minusNanos(1); |
|
|
|
|
|
break; |
|
|
|
|
|
case "WEEK": |
|
|
|
|
|
LocalDate monday = now.with(TemporalAdjusters.previousOrSame(DayOfWeek.MONDAY)); |
|
|
|
|
|
startTime = monday.atStartOfDay(); |
|
|
|
|
|
endTime = monday.plusWeeks(1).atStartOfDay().minusNanos(1); |
|
|
|
|
|
break; |
|
|
|
|
|
case "MONTH": |
|
|
|
|
|
LocalDate firstDay = now.with(TemporalAdjusters.firstDayOfMonth()); |
|
|
|
|
|
startTime = firstDay.atStartOfDay(); |
|
|
|
|
|
endTime = firstDay.plusMonths(1).atStartOfDay().minusNanos(1); |
|
|
|
|
|
break; |
|
|
|
|
|
case "YEAR": |
|
|
|
|
|
LocalDate firstYearDay = LocalDate.of(now.getYear(), 1, 1); |
|
|
|
|
|
startTime = firstYearDay.atStartOfDay(); |
|
|
|
|
|
endTime = firstYearDay.plusYears(1).atStartOfDay().minusNanos(1); |
|
|
|
|
|
break; |
|
|
|
|
|
default: |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
// 统计当前周期内该业务类型的流水数量
|
|
|
|
|
|
return count(new LambdaQueryWrapper<MiniPointRecord>() |
|
|
|
|
|
.eq(MiniPointRecord::getUserId, userId) |
|
|
|
|
|
.eq(MiniPointRecord::getBizType, bizType.toUpperCase()) // 和流水的biz_type保持一致
|
|
|
|
|
|
.ge(MiniPointRecord::getCreateTime, startTime) |
|
|
|
|
|
.le(MiniPointRecord::getCreateTime, endTime) |
|
|
|
|
|
.eq(MiniPointRecord::getDeleted, 0)); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
@Transactional(rollbackFor = Exception.class) |
|
|
|
|
|
public Integer giveShareReward(Long userId) { |
|
|
|
|
|
// 调用通用周期校验方法
|
|
|
|
|
|
Object[] checkResult = checkPeriodLimit("SHARE_CONTENT", userId, "share"); |
|
|
|
|
|
boolean allow = (boolean) checkResult[0]; |
|
|
|
|
|
int rewardPoint = (int) checkResult[1]; |
|
|
|
|
|
String countKey = (String) checkResult[2]; |
|
|
|
|
|
long expireDays = (long) checkResult[3]; |
|
|
|
|
|
String limitPeriod = (String) checkResult[4]; |
|
|
|
|
|
String limitTip = (String) checkResult[5]; |
|
|
|
|
|
boolean redisNormal = (boolean) checkResult[6]; |
|
|
|
|
|
|
|
|
|
|
|
// 校验不通过直接抛异常
|
|
|
|
|
|
if (!allow) { |
|
|
|
|
|
throw new MsgException(limitTip); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 发放积分
|
|
|
|
|
|
AdjustUserPointForm adjustForm = new AdjustUserPointForm(); |
|
|
|
|
|
adjustForm.setUserId(userId); |
|
|
|
|
|
adjustForm.setBizType("SHARE_CONTENT"); |
|
|
|
|
|
adjustForm.setChangeAmount(rewardPoint); |
|
|
|
|
|
adjustPoint(adjustForm); |
|
|
|
|
|
|
|
|
|
|
|
// 有周期限制且Redis正常的情况下才更新缓存,Redis异常时跳过不报错
|
|
|
|
|
|
if (countKey != null && expireDays > 0 && redisNormal) { |
|
|
|
|
|
redisTemplate.opsForValue().increment(countKey, 1); |
|
|
|
|
|
// 日周期特殊存25小时,避免跨天服务器时间差问题
|
|
|
|
|
|
if ("DAY".equals(limitPeriod)) { |
|
|
|
|
|
redisTemplate.expire(countKey, 25, TimeUnit.HOURS); |
|
|
|
|
|
} else { |
|
|
|
|
|
redisTemplate.expire(countKey, expireDays, TimeUnit.DAYS); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return rewardPoint; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
@Transactional(rollbackFor = Exception.class) |
|
|
|
|
|
public Integer deductAiGenerateImagePoint(Long userId, String taskId) { |
|
|
|
|
|
try { |
|
|
|
|
|
// 1. 幂等校验:同一个任务ID只能扣一次费,防止重试重复扣费
|
|
|
|
|
|
String idempotentKey = String.format("reward:ai_image:idempotent:%s", taskId); |
|
|
|
|
|
boolean alreadyDeducted = false; |
|
|
|
|
|
try { |
|
|
|
|
|
alreadyDeducted = Boolean.TRUE.equals(redisTemplate.hasKey(idempotentKey)); |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
// Redis异常查数据库兜底:查bizId等于taskId的流水
|
|
|
|
|
|
long count = count(new LambdaQueryWrapper<MiniPointRecord>() |
|
|
|
|
|
.eq(MiniPointRecord::getBizType, "AI_GENERATE_IMAGE") |
|
|
|
|
|
.eq(MiniPointRecord::getBizId, taskId) |
|
|
|
|
|
.eq(MiniPointRecord::getDeleted, 0)); |
|
|
|
|
|
alreadyDeducted = count > 0; |
|
|
|
|
|
} |
|
|
|
|
|
if (alreadyDeducted) { |
|
|
|
|
|
throw new MsgException("任务已处理"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 2. 调用通用周期校验,规则编码AI_GENERATE_IMAGE,业务前缀ai_generate_image
|
|
|
|
|
|
Object[] checkResult = checkPeriodLimit("AI_GENERATE_IMAGE", userId, "ai_generate_image"); |
|
|
|
|
|
boolean allow = (boolean) checkResult[0]; |
|
|
|
|
|
int deductPoint = (int) checkResult[1]; // 规则配置的扣除积分数,应该是负数
|
|
|
|
|
|
String countKey = (String) checkResult[2]; |
|
|
|
|
|
long expireDays = (long) checkResult[3]; |
|
|
|
|
|
String limitPeriod = (String) checkResult[4]; |
|
|
|
|
|
boolean redisNormal = (boolean) checkResult[6]; |
|
|
|
|
|
|
|
|
|
|
|
// 校验不通过(次数超限/规则未配置)或者扣除的积分>=0(规则配置错误),返回0
|
|
|
|
|
|
if (!allow || deductPoint >= 0) { |
|
|
|
|
|
throw new MsgException("积分规则配置错误"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 3. 检查用户积分是否足够
|
|
|
|
|
|
LambdaQueryWrapper<MiniPointAccount> pointAccountQuery = new LambdaQueryWrapper<>(); |
|
|
|
|
|
pointAccountQuery.eq(MiniPointAccount::getUserId, userId); |
|
|
|
|
|
pointAccountQuery.last("LIMIT 1"); |
|
|
|
|
|
MiniPointAccount account = miniPointAccountMapper.selectOne(pointAccountQuery); |
|
|
|
|
|
// 账户不存在积分就是0,扣除后为负说明积分不足
|
|
|
|
|
|
if (account == null || account.getPoints() + deductPoint < 0) { |
|
|
|
|
|
throw new MsgException("用户积分不足"); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 4. 执行扣费
|
|
|
|
|
|
AdjustUserPointForm adjustForm = new AdjustUserPointForm(); |
|
|
|
|
|
adjustForm.setUserId(userId); |
|
|
|
|
|
adjustForm.setBizType("AI_GENERATE_IMAGE"); |
|
|
|
|
|
adjustForm.setChangeAmount(deductPoint); |
|
|
|
|
|
adjustForm.setBizId(taskId); |
|
|
|
|
|
adjustForm.setRemark("AI生成图片扣费,任务ID:" + taskId); |
|
|
|
|
|
adjustPoint(adjustForm); |
|
|
|
|
|
|
|
|
|
|
|
// 5. 更新周期计数缓存
|
|
|
|
|
|
if (countKey != null && expireDays > 0 && redisNormal) { |
|
|
|
|
|
redisTemplate.opsForValue().increment(countKey, 1); |
|
|
|
|
|
if ("DAY".equals(limitPeriod)) { |
|
|
|
|
|
redisTemplate.expire(countKey, 25, TimeUnit.HOURS); |
|
|
|
|
|
} else { |
|
|
|
|
|
redisTemplate.expire(countKey, expireDays, TimeUnit.DAYS); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 6. 写入幂等标记,存1年足够
|
|
|
|
|
|
if (redisNormal) { |
|
|
|
|
|
redisTemplate.opsForValue().set(idempotentKey, "1", 365, TimeUnit.DAYS); |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return deductPoint; // 返回扣除的积分数,负数表示扣除成功
|
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
log.error("用户{}AI生成图片任务{}扣费失败", userId, taskId, e); |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@Override |
|
|
|
|
|
@Transactional(rollbackFor = Exception.class) |
|
|
|
|
|
public Integer giveRegisterAnimalReward(Long userId, Long animalId) { |
|
|
|
|
|
try { |
|
|
|
|
|
// 1. 调用通用周期校验,规则编码REGISTER_ANIMAL,业务前缀register_animal
|
|
|
|
|
|
Object[] checkResult = checkPeriodLimit("REGISTER_ANIMAL", userId, "register_animal"); |
|
|
|
|
|
boolean allow = (boolean) checkResult[0]; |
|
|
|
|
|
int rewardPoint = (int) checkResult[1]; |
|
|
|
|
|
String countKey = (String) checkResult[2]; |
|
|
|
|
|
long expireDays = (long) checkResult[3]; |
|
|
|
|
|
String limitPeriod = (String) checkResult[4]; |
|
|
|
|
|
boolean redisNormal = (boolean) checkResult[6]; |
|
|
|
|
|
|
|
|
|
|
|
// 校验不通过(次数超限),返回0,不抛异常,不影响主登记流程
|
|
|
|
|
|
if (!allow || rewardPoint <= 0) { |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 2. 发放积分
|
|
|
|
|
|
AdjustUserPointForm adjustForm = new AdjustUserPointForm(); |
|
|
|
|
|
adjustForm.setUserId(userId); |
|
|
|
|
|
adjustForm.setBizType("REGISTER_ANIMAL"); |
|
|
|
|
|
adjustForm.setChangeAmount(rewardPoint); |
|
|
|
|
|
adjustPoint(adjustForm); |
|
|
|
|
|
|
|
|
|
|
|
// 3. 更新周期计数缓存
|
|
|
|
|
|
if (countKey != null && expireDays > 0 && redisNormal) { |
|
|
|
|
|
redisTemplate.opsForValue().increment(countKey, 1); |
|
|
|
|
|
if ("DAY".equals(limitPeriod)) { |
|
|
|
|
|
redisTemplate.expire(countKey, 25, TimeUnit.HOURS); |
|
|
|
|
|
} else { |
|
|
|
|
|
redisTemplate.expire(countKey, expireDays, TimeUnit.DAYS); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
return rewardPoint; |
|
|
|
|
|
} catch (Exception e) { |
|
|
|
|
|
// 积分发放失败打日志,不抛异常,不影响登记主流程
|
|
|
|
|
|
log.error("用户{}登记流浪动物{}发放积分失败", userId, animalId, e); |
|
|
|
|
|
return 0; |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
} |
|
|
} |
|
|
|