|
|
@ -3,6 +3,7 @@ package com.youlai.boot.admin.service.impl; |
|
|
import cn.hutool.core.util.IdUtil; |
|
|
import cn.hutool.core.util.IdUtil; |
|
|
import cn.hutool.core.util.StrUtil; |
|
|
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.conditions.update.LambdaUpdateWrapper; |
|
|
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; |
|
|
@ -28,6 +29,7 @@ import com.youlai.boot.mini.service.MiniPointAccountService; |
|
|
import jodd.util.StringUtil; |
|
|
import jodd.util.StringUtil; |
|
|
import lombok.RequiredArgsConstructor; |
|
|
import lombok.RequiredArgsConstructor; |
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
import lombok.extern.slf4j.Slf4j; |
|
|
|
|
|
import org.springframework.dao.DuplicateKeyException; |
|
|
import org.springframework.stereotype.Service; |
|
|
import org.springframework.stereotype.Service; |
|
|
import org.springframework.transaction.annotation.Transactional; |
|
|
import org.springframework.transaction.annotation.Transactional; |
|
|
|
|
|
|
|
|
@ -138,50 +140,80 @@ public class PointManageServiceImpl extends ServiceImpl<MiniPointRuleMapper, Min |
|
|
throw new MsgException("积分调整值不能为0"); |
|
|
throw new MsgException("积分调整值不能为0"); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 2.根据用户主键userId查询积分账户
|
|
|
int changeAmount = form.getChangeAmount(); // 调整积分值
|
|
|
MiniPointAccount account = miniPointAccountService.getOne(new LambdaQueryWrapper<MiniPointAccount>() |
|
|
Long operateUserId = form.getUserId(); // 操作用户ID
|
|
|
.eq(MiniPointAccount::getUserId, form.getUserId()) |
|
|
boolean isDeduct = changeAmount < 0; // 是否为扣减
|
|
|
|
|
|
int updateCount; // 更新数量
|
|
|
|
|
|
MiniPointAccount account; |
|
|
|
|
|
|
|
|
|
|
|
if (isDeduct) { |
|
|
|
|
|
// 扣减场景:原子更新+积分校验,解决并发超扣
|
|
|
|
|
|
int deductAmount = -changeAmount; // 要扣的正数值
|
|
|
|
|
|
updateCount = miniPointAccountMapper.update(null, new LambdaUpdateWrapper<MiniPointAccount>() |
|
|
|
|
|
.eq(MiniPointAccount::getUserId, operateUserId) |
|
|
|
|
|
.eq(MiniPointAccount::getDeleted, false) |
|
|
|
|
|
// 核心:当前积分 >= 扣减金额,保证扣完不会为负,原子操作靠数据库行锁隔离并发
|
|
|
|
|
|
.ge(MiniPointAccount::getPoints, deductAmount) // 当前积分 >= 扣减金额
|
|
|
|
|
|
.setSql("points = points + " + changeAmount) |
|
|
|
|
|
.set(MiniPointAccount::getUpdateBy, SecurityUtils.getUserId()) |
|
|
|
|
|
.set(MiniPointAccount::getUpdateTime, new Date()) |
|
|
|
|
|
.set(MiniPointAccount::getUpdateTimestamp, System.currentTimeMillis()) |
|
|
|
|
|
); |
|
|
|
|
|
if (updateCount == 0) { |
|
|
|
|
|
// 更新行数为0:要么账户不存在(用户从来没获得过积分=0,扣减肯定失败),要么积分不足
|
|
|
|
|
|
throw new MsgException("积分不足,扣减后余额不能为负数"); |
|
|
|
|
|
} |
|
|
|
|
|
// 查询更新后的最新账户信息,用于流水记录
|
|
|
|
|
|
account = miniPointAccountService.getOne(new LambdaQueryWrapper<MiniPointAccount>() |
|
|
|
|
|
.eq(MiniPointAccount::getUserId, operateUserId) |
|
|
.eq(MiniPointAccount::getDeleted, false)); |
|
|
.eq(MiniPointAccount::getDeleted, false)); |
|
|
// 账户不存在自动创建,新用户第一次获得积分的时候自动初始化账户
|
|
|
} else { |
|
|
if (account == null) { |
|
|
// 增加积分场景:不需要校验余额,直接原子更新,避免并发少加
|
|
|
|
|
|
updateCount = miniPointAccountMapper.update(null, new LambdaUpdateWrapper<MiniPointAccount>() |
|
|
|
|
|
.eq(MiniPointAccount::getUserId, operateUserId) |
|
|
|
|
|
.eq(MiniPointAccount::getDeleted, false) |
|
|
|
|
|
.setSql("points = points + " + changeAmount) |
|
|
|
|
|
.set(MiniPointAccount::getUpdateBy, operateUserId) |
|
|
|
|
|
.set(MiniPointAccount::getUpdateTime, new Date()) |
|
|
|
|
|
.set(MiniPointAccount::getUpdateTimestamp, System.currentTimeMillis()) |
|
|
|
|
|
); |
|
|
|
|
|
if (updateCount == 0) { |
|
|
|
|
|
// 账户不存在,自动创建(兼容原有新用户首次获得积分自动开户逻辑)
|
|
|
account = new MiniPointAccount(); |
|
|
account = new MiniPointAccount(); |
|
|
account.setUuid(IdUtil.fastSimpleUUID()); |
|
|
account.setUuid(IdUtil.fastSimpleUUID()); |
|
|
account.setUserId(form.getUserId()); |
|
|
account.setUserId(operateUserId); |
|
|
account.setPoints(0); |
|
|
account.setPoints(changeAmount); // 新账户初始积分就是本次增加的
|
|
|
account.setCreateBy(form.getUserId()); |
|
|
account.setCreateBy(operateUserId); |
|
|
account.setCreateTime(new Date()); |
|
|
account.setCreateTime(new Date()); |
|
|
account.setCreateTimestamp(System.currentTimeMillis()); |
|
|
account.setCreateTimestamp(System.currentTimeMillis()); |
|
|
miniPointAccountService.save(account); |
|
|
miniPointAccountService.save(account); |
|
|
|
|
|
} else { |
|
|
|
|
|
// 更新成功,查询最新账户信息
|
|
|
|
|
|
account = miniPointAccountService.getOne(new LambdaQueryWrapper<MiniPointAccount>() |
|
|
|
|
|
.eq(MiniPointAccount::getUserId, operateUserId) |
|
|
|
|
|
.eq(MiniPointAccount::getDeleted, false)); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 4. 计算变更后余额,校验不能为负数
|
|
|
|
|
|
Integer afterBalance = account.getPoints() + form.getChangeAmount(); |
|
|
|
|
|
if (afterBalance < 0) { |
|
|
|
|
|
throw new MsgException("积分不足,扣减后余额不能为负数"); |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
// 5. 更新用户积分账户余额
|
|
|
// 6. 插入积分流水记录(用最新查询的账户余额,保证流水和实际完全一致)
|
|
|
MiniPointAccount updateAccount = new MiniPointAccount(); |
|
|
|
|
|
updateAccount.setId(account.getId()); |
|
|
|
|
|
updateAccount.setPoints(afterBalance); |
|
|
|
|
|
updateAccount.setUpdateBy(SecurityUtils.getUserId()); |
|
|
|
|
|
updateAccount.setUpdateTime(new Date()); |
|
|
|
|
|
updateAccount.setUpdateTimestamp(System.currentTimeMillis()); |
|
|
|
|
|
miniPointAccountService.updateById(updateAccount); |
|
|
|
|
|
|
|
|
|
|
|
// 6. 插入积分流水记录
|
|
|
|
|
|
MiniPointRecord record = new MiniPointRecord(); |
|
|
MiniPointRecord record = new MiniPointRecord(); |
|
|
record.setUuid(IdUtil.fastSimpleUUID()); |
|
|
record.setUuid(IdUtil.fastSimpleUUID()); |
|
|
record.setUserId(account.getUserId()); |
|
|
record.setUserId(account.getUserId()); |
|
|
record.setChangeAmount(form.getChangeAmount()); |
|
|
record.setChangeAmount(changeAmount); |
|
|
record.setBalanceAfter(afterBalance); |
|
|
record.setBalanceAfter(account.getPoints()); // 直接用最新的账户积分,不用自己计算,避免不一致
|
|
|
record.setBizType(form.getBizType()); |
|
|
record.setBizType(form.getBizType()); |
|
|
// 优先用传进来的业务唯一ID(比如AI任务ID),没有的话自动生成,保证幂等
|
|
|
// 优先用传进来的业务唯一ID,没有的话自动生成,保证幂等
|
|
|
record.setBizId(StrUtil.isNotBlank(form.getBizId()) ? form.getBizId() : UUID.randomUUID().toString().replace("-", "")); |
|
|
record.setBizId(StrUtil.isNotBlank(form.getBizId()) ? form.getBizId() : UUID.randomUUID().toString().replace("-", "")); |
|
|
record.setCreateBy(SecurityUtils.getUserId()); |
|
|
record.setCreateBy(operateUserId); |
|
|
record.setCreateTime(new Date()); |
|
|
record.setCreateTime(new Date()); |
|
|
record.setCreateTimestamp(System.currentTimeMillis()); |
|
|
record.setCreateTimestamp(System.currentTimeMillis()); |
|
|
|
|
|
try { |
|
|
miniPointRecordMapper.insert(record); |
|
|
miniPointRecordMapper.insert(record); |
|
|
|
|
|
} catch (DuplicateKeyException e) { |
|
|
|
|
|
// bizId 唯一索引冲突,说明是重复请求
|
|
|
|
|
|
log.warn("重复请求,bizId: {}", record.getBizId()); |
|
|
|
|
|
throw new MsgException("请勿重复提交"); |
|
|
|
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
@Override |
|
|
@Override |
|
|
|