Commit f0a8beadad012f499e279e58980893aef7f31b5e
1 parent
487e54c6
增加单位积分查询功能
1、上报积分增加 2、退回积分扣减 3、查询列表增加当前派单任务积分字段
Showing
2 changed files
with
215 additions
and
134 deletions
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/redis/IntegralLockCoreRedisDAO.java
0 → 100644
| 1 | +package com.zteits.urbanops.module.garden.dal.redis; | ||
| 2 | + | ||
| 3 | +import jakarta.annotation.Resource; | ||
| 4 | +import lombok.extern.slf4j.Slf4j; | ||
| 5 | +import org.redisson.api.RLock; | ||
| 6 | +import org.redisson.api.RedissonClient; | ||
| 7 | +import org.springframework.stereotype.Component; | ||
| 8 | +import org.springframework.util.Assert; | ||
| 9 | + | ||
| 10 | +import java.util.concurrent.TimeUnit; | ||
| 11 | + | ||
| 12 | +/** | ||
| 13 | + * 积分操作分布式锁通用工具类 | ||
| 14 | + */ | ||
| 15 | +@Slf4j | ||
| 16 | +@Component | ||
| 17 | +public class IntegralLockCoreRedisDAO { | ||
| 18 | + | ||
| 19 | + // 积分操作锁前缀 | ||
| 20 | + private static final String INTEGRAL_LOCK_PREFIX = "integral:operate:%s:%s"; | ||
| 21 | + // 锁持有超时时间(30秒) | ||
| 22 | + public static final long INTEGRAL_LOCK_TIMEOUT_MILLIS = 30 * 1000L; | ||
| 23 | + | ||
| 24 | + @Resource | ||
| 25 | + private RedissonClient redissonClient; | ||
| 26 | + | ||
| 27 | + /** | ||
| 28 | + * 加锁执行积分操作逻辑 | ||
| 29 | + * @param unitId 单位ID | ||
| 30 | + * @param statDate 统计日期 | ||
| 31 | + * @param timeoutMillis 锁持有超时时间 | ||
| 32 | + * @param runnable 要执行的核心业务逻辑 | ||
| 33 | + */ | ||
| 34 | + public void lock(String unitId, String statDate, Long timeoutMillis, Runnable runnable) { | ||
| 35 | + // 空值校验 | ||
| 36 | + Assert.hasText(unitId, "单位ID不能为空"); | ||
| 37 | + Assert.hasText(statDate, "统计日期不能为空"); | ||
| 38 | + Assert.notNull(timeoutMillis, "锁超时时间不能为空"); | ||
| 39 | + Assert.notNull(runnable, "执行逻辑不能为空"); | ||
| 40 | + | ||
| 41 | + // 构建细粒度锁Key | ||
| 42 | + String lockKey = String.format(INTEGRAL_LOCK_PREFIX, unitId, statDate); | ||
| 43 | + RLock lock = redissonClient.getLock(lockKey); | ||
| 44 | + boolean lockAcquired = false; | ||
| 45 | + | ||
| 46 | + try { | ||
| 47 | + // 非阻塞获取锁:3秒等待超时,timeoutMillis自动过期 | ||
| 48 | + lockAcquired = lock.tryLock(3, timeoutMillis, TimeUnit.MILLISECONDS); | ||
| 49 | + if (!lockAcquired) { | ||
| 50 | + String errorMsg = String.format("获取积分操作锁失败,单位ID:%s,统计日期:%s", unitId, statDate); | ||
| 51 | + log.error(errorMsg); | ||
| 52 | + throw new RuntimeException(errorMsg); | ||
| 53 | + } | ||
| 54 | + log.info("成功获取积分操作锁,锁Key:{}", lockKey); | ||
| 55 | + | ||
| 56 | + // 执行核心业务逻辑 | ||
| 57 | + runnable.run(); | ||
| 58 | + | ||
| 59 | + } catch (InterruptedException e) { | ||
| 60 | + log.error("获取积分操作锁被中断,单位ID:{},统计日期:{}", unitId, statDate, e); | ||
| 61 | + Thread.currentThread().interrupt(); | ||
| 62 | + throw new RuntimeException("积分操作锁获取被中断,请重试", e); | ||
| 63 | + } finally { | ||
| 64 | + // 安全释放锁 | ||
| 65 | + if (lockAcquired && lock.isHeldByCurrentThread()) { | ||
| 66 | + try { | ||
| 67 | + lock.unlock(); | ||
| 68 | + log.info("成功释放积分操作锁,锁Key:{}", lockKey); | ||
| 69 | + } catch (Exception e) { | ||
| 70 | + log.error("释放积分操作锁异常,锁Key:{}", lockKey, e); | ||
| 71 | + } | ||
| 72 | + } | ||
| 73 | + } | ||
| 74 | + } | ||
| 75 | +} |
urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/unitintegraltotal/UnitIntegralServiceImpl.java
| 1 | package com.zteits.urbanops.module.garden.service.unitintegraltotal; | 1 | package com.zteits.urbanops.module.garden.service.unitintegraltotal; |
| 2 | 2 | ||
| 3 | +import cn.hutool.extra.spring.SpringUtil; | ||
| 3 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | 4 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
| 4 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; | 5 | import com.baomidou.mybatisplus.core.toolkit.Wrappers; |
| 5 | import com.zteits.urbanops.framework.common.util.object.BeanUtils; | 6 | import com.zteits.urbanops.framework.common.util.object.BeanUtils; |
| @@ -12,10 +13,12 @@ import com.zteits.urbanops.module.garden.dal.dataobject.unitintegraladd.UnitInte | @@ -12,10 +13,12 @@ import com.zteits.urbanops.module.garden.dal.dataobject.unitintegraladd.UnitInte | ||
| 12 | import com.zteits.urbanops.module.garden.dal.dataobject.unitintegraltotal.UnitIntegralTotalDO; | 13 | import com.zteits.urbanops.module.garden.dal.dataobject.unitintegraltotal.UnitIntegralTotalDO; |
| 13 | import com.zteits.urbanops.module.garden.dal.mysql.unitintegraladd.UnitIntegralAddMapper; | 14 | import com.zteits.urbanops.module.garden.dal.mysql.unitintegraladd.UnitIntegralAddMapper; |
| 14 | import com.zteits.urbanops.module.garden.dal.mysql.unitintegraltotal.UnitIntegralTotalMapper; | 15 | import com.zteits.urbanops.module.garden.dal.mysql.unitintegraltotal.UnitIntegralTotalMapper; |
| 16 | +import com.zteits.urbanops.module.garden.dal.redis.IntegralLockCoreRedisDAO; | ||
| 15 | import com.zteits.urbanops.module.garden.service.unitintegraladd.UnitIntegralAddService; | 17 | import com.zteits.urbanops.module.garden.service.unitintegraladd.UnitIntegralAddService; |
| 16 | import com.zteits.urbanops.module.garden.service.unitintegralsub.UnitIntegralSubService; | 18 | import com.zteits.urbanops.module.garden.service.unitintegralsub.UnitIntegralSubService; |
| 17 | import jakarta.annotation.Resource; | 19 | import jakarta.annotation.Resource; |
| 18 | import lombok.extern.slf4j.Slf4j; | 20 | import lombok.extern.slf4j.Slf4j; |
| 21 | +import org.apache.commons.lang3.ObjectUtils; | ||
| 19 | import org.apache.commons.lang3.StringUtils; | 22 | import org.apache.commons.lang3.StringUtils; |
| 20 | import org.redisson.api.RLock; | 23 | import org.redisson.api.RLock; |
| 21 | import org.redisson.api.RedissonClient; | 24 | import org.redisson.api.RedissonClient; |
| @@ -55,6 +58,9 @@ public class UnitIntegralServiceImpl implements UnitIntegralService{ | @@ -55,6 +58,9 @@ public class UnitIntegralServiceImpl implements UnitIntegralService{ | ||
| 55 | private UnitIntegralAddService unitIntegralAddService; | 58 | private UnitIntegralAddService unitIntegralAddService; |
| 56 | @Resource | 59 | @Resource |
| 57 | private RedissonClient redissonClient; | 60 | private RedissonClient redissonClient; |
| 61 | + @Resource | ||
| 62 | + private IntegralLockCoreRedisDAO integralLockCoreRedisDAO; | ||
| 63 | + | ||
| 58 | 64 | ||
| 59 | // 锁前缀(区分加/减积分锁) | 65 | // 锁前缀(区分加/减积分锁) |
| 60 | private static final String LOCK_PREFIX_INTEGRAL = "integral:"; | 66 | private static final String LOCK_PREFIX_INTEGRAL = "integral:"; |
| @@ -125,85 +131,78 @@ public class UnitIntegralServiceImpl implements UnitIntegralService{ | @@ -125,85 +131,78 @@ public class UnitIntegralServiceImpl implements UnitIntegralService{ | ||
| 125 | * @param reqVO 积分增加请求(注意:totalIntegral 为「本次增加积分」) | 131 | * @param reqVO 积分增加请求(注意:totalIntegral 为「本次增加积分」) |
| 126 | */ | 132 | */ |
| 127 | @Override | 133 | @Override |
| 128 | - @Transactional(rollbackFor = Exception.class) | ||
| 129 | public void addUnitIntegral(UnitIntegralSaveReqVO reqVO){ | 134 | public void addUnitIntegral(UnitIntegralSaveReqVO reqVO){ |
| 130 | // 1. 参数校验 | 135 | // 1. 参数校验 |
| 136 | + if (reqVO == null) { | ||
| 137 | + throw exception0(BAD_REQUEST.getCode(), "积分增加请求参数不能为空"); | ||
| 138 | + } | ||
| 131 | if (reqVO.getTotalIntegral() <= 0) { | 139 | if (reqVO.getTotalIntegral() <= 0) { |
| 132 | - throw exception0(BAD_REQUEST.getCode(),"本次增加积分必须为正数"); | 140 | + throw exception0(BAD_REQUEST.getCode(), "本次增加积分必须为正数"); |
| 141 | + } | ||
| 142 | + if (ObjectUtils.isEmpty(reqVO.getUnitId()) || ObjectUtils.isEmpty(reqVO.getStatDate())) { | ||
| 143 | + throw exception0(BAD_REQUEST.getCode(), "单位ID和统计日期不能为空"); | ||
| 133 | } | 144 | } |
| 134 | 145 | ||
| 135 | - // 2. 构建细粒度锁Key:单位ID + 统计日期 + 操作类型 | ||
| 136 | - String lockKey = buildLockKey(reqVO.getUnitId(), reqVO.getStatDate()); | ||
| 137 | - RLock lock = redissonClient.getLock(lockKey); | ||
| 138 | - | ||
| 139 | - // 3. 获取锁(非阻塞,避免线程等待;3秒获取超时,10秒自动过期防死锁) | ||
| 140 | - boolean lockAcquired = false; | ||
| 141 | - try { | ||
| 142 | - lockAcquired = lock.tryLock(3, 10, TimeUnit.SECONDS); | ||
| 143 | - if (!lockAcquired) { | ||
| 144 | - String errorMsg = String.format("获取积分增加锁失败,单位ID:%s,统计日期:%s(请稍后重试)", reqVO.getUnitId(), reqVO.getStatDate()); | ||
| 145 | - log.error(errorMsg); | ||
| 146 | - throw exception0(INTERNAL_SERVER_ERROR.getCode(), errorMsg); | ||
| 147 | - } | ||
| 148 | - | ||
| 149 | - // 2. 构建查询条件 | ||
| 150 | - LambdaQueryWrapper<UnitIntegralTotalDO> queryWrapper = Wrappers.lambdaQuery(UnitIntegralTotalDO.class) | ||
| 151 | - .eq(UnitIntegralTotalDO::getUnitType, reqVO.getUnitType()) | ||
| 152 | - .eq(UnitIntegralTotalDO::getStatDate, reqVO.getStatDate()) | ||
| 153 | - .eq(UnitIntegralTotalDO::getUnitId, reqVO.getUnitId()) | ||
| 154 | - .eq(UnitIntegralTotalDO::getDeleted, 0); | ||
| 155 | - | ||
| 156 | - // 3. 可加行锁(FOR UPDATE)防止多线程同时扣减,保证数据一致性 | ||
| 157 | - UnitIntegralTotalDO unitIntegralTotalDO = unitIntegralTotalMapper.selectOne(queryWrapper); | ||
| 158 | - | ||
| 159 | - // 4. 初始化变量(语义清晰,避免冗余) | ||
| 160 | - int beforeIntegral = 0; // 本次增加前的累计增加积分 | ||
| 161 | - int afterIntegral = 0; // 本次增加后的累计增加积分 | ||
| 162 | - int currentAdd = reqVO.getTotalIntegral(); // 本次增加积分(重命名,语义更准) | 146 | + // 2. 调用通用锁工具类,加锁执行核心逻辑(参考executeNotify的Runnable风格) |
| 147 | + integralLockCoreRedisDAO.lock( | ||
| 148 | + reqVO.getUnitId(), | ||
| 149 | + reqVO.getStatDate(), | ||
| 150 | + IntegralLockCoreRedisDAO.INTEGRAL_LOCK_TIMEOUT_MILLIS, | ||
| 151 | + () -> { | ||
| 152 | + // 3. 锁内二次校验(关键!避免分布式锁并发问题,参考executeNotify的dbTask校验) | ||
| 153 | + // 场景:两个线程同时通过前置校验,第一个执行完后,第二个拿到锁仍会执行,需校验数据库状态 | ||
| 154 | + LambdaQueryWrapper<UnitIntegralTotalDO> checkWrapper = Wrappers.lambdaQuery(UnitIntegralTotalDO.class) | ||
| 155 | + .eq(UnitIntegralTotalDO::getUnitId, reqVO.getUnitId()) | ||
| 156 | + .eq(UnitIntegralTotalDO::getStatDate, reqVO.getStatDate()) | ||
| 157 | + .eq(UnitIntegralTotalDO::getUnitType, reqVO.getUnitType()) | ||
| 158 | + .eq(UnitIntegralTotalDO::getDeleted, 0); | ||
| 159 | + UnitIntegralTotalDO dbTotalDO = unitIntegralTotalMapper.selectOne(checkWrapper); | ||
| 163 | 160 | ||
| 164 | - // 5. 新增/更新总积分表(核心逻辑) | ||
| 165 | - if (unitIntegralTotalDO != null) { | ||
| 166 | - // 5.1 已有记录:更新积分 | ||
| 167 | - beforeIntegral = unitIntegralTotalDO.getTotalAdd(); | ||
| 168 | - int newTotalAdd = beforeIntegral + currentAdd; | ||
| 169 | - // 更新累计增加积分 + 当前总积分(累计增加 - 累计扣减) | ||
| 170 | - unitIntegralTotalDO.setTotalAdd(newTotalAdd); | ||
| 171 | - unitIntegralTotalDO.setTotalIntegral(newTotalAdd - unitIntegralTotalDO.getTotalSub()); | ||
| 172 | - unitIntegralTotalDO.setUpdateTime(LocalDateTime.now()); | ||
| 173 | - unitIntegralTotalDO.setUpdater(String.valueOf(getLoginUserId())); | ||
| 174 | - unitIntegralTotalMapper.updateById(unitIntegralTotalDO); | ||
| 175 | - afterIntegral = newTotalAdd; | 161 | + // 4. 调用带事务的核心执行方法(自注入避免事务失效) |
| 162 | + getSelf().executeAddUnitIntegral0(reqVO, dbTotalDO); | ||
| 163 | + } | ||
| 164 | + ); | ||
| 165 | + } | ||
| 166 | + /** | ||
| 167 | + * 积分增加核心执行方法(带事务,内部调用) | ||
| 168 | + */ | ||
| 169 | + @Transactional(rollbackFor = Exception.class) | ||
| 170 | + public void executeAddUnitIntegral0(UnitIntegralSaveReqVO reqVO, UnitIntegralTotalDO unitIntegralTotalDO) { | ||
| 171 | + int beforeIntegral = 0; // 本次增加前的累计增加积分 | ||
| 172 | + int afterIntegral = 0; // 本次增加后的累计增加积分 | ||
| 173 | + int currentAdd = reqVO.getTotalIntegral(); // 本次增加积分(重命名,语义更准) | ||
| 176 | 174 | ||
| 177 | - log.info("更新单位积分成功,单位ID:{},统计日期:{},本次增加:{},累计增加:{}", | ||
| 178 | - reqVO.getUnitId(), reqVO.getStatDate(), currentAdd, newTotalAdd); | ||
| 179 | - } else { | ||
| 180 | - // 5.2 无记录:插入新记录 | ||
| 181 | - UnitIntegralTotalDO saveDO = new UnitIntegralTotalDO(); | ||
| 182 | - BeanUtils.copyProperties(reqVO, saveDO); | ||
| 183 | - saveDO.setTotalAdd(currentAdd); // 累计增加 = 本次增加 | ||
| 184 | - saveDO.setTotalSub(0); // 初始扣减为0 | ||
| 185 | - saveDO.setTotalIntegral(currentAdd); // 初始总积分 = 本次增加 | ||
| 186 | - unitIntegralTotalMapper.insert(saveDO); | ||
| 187 | - afterIntegral = currentAdd; | 175 | + // 5. 新增/更新总积分表(核心逻辑) |
| 176 | + if (unitIntegralTotalDO != null) { | ||
| 177 | + // 5.1 已有记录:更新积分 | ||
| 178 | + beforeIntegral = unitIntegralTotalDO.getTotalAdd(); | ||
| 179 | + int newTotalAdd = beforeIntegral + currentAdd; | ||
| 180 | + // 更新累计增加积分 + 当前总积分(累计增加 - 累计扣减) | ||
| 181 | + unitIntegralTotalDO.setTotalAdd(newTotalAdd); | ||
| 182 | + unitIntegralTotalDO.setTotalIntegral(newTotalAdd - unitIntegralTotalDO.getTotalSub()); | ||
| 183 | + unitIntegralTotalDO.setUpdateTime(LocalDateTime.now()); | ||
| 184 | + unitIntegralTotalDO.setUpdater(String.valueOf(getLoginUserId())); | ||
| 185 | + unitIntegralTotalMapper.updateById(unitIntegralTotalDO); | ||
| 186 | + afterIntegral = newTotalAdd; | ||
| 188 | 187 | ||
| 189 | - log.info("新增单位积分成功,单位ID:{},统计日期:{},本次增加:{}", | ||
| 190 | - reqVO.getUnitId(), reqVO.getStatDate(), currentAdd); | ||
| 191 | - } | ||
| 192 | - // 5. 新增积分明细 | ||
| 193 | - createAddDetail(reqVO, beforeIntegral, afterIntegral, currentAdd); | ||
| 194 | - log.info("积分增加成功,单位ID:{},本次增加:{}", reqVO.getUnitId(), currentAdd); | 188 | + log.info("更新单位积分成功,单位ID:{},统计日期:{},本次增加:{},累计增加:{}", |
| 189 | + reqVO.getUnitId(), reqVO.getStatDate(), currentAdd, newTotalAdd); | ||
| 190 | + } else { | ||
| 191 | + // 5.2 无记录:插入新记录 | ||
| 192 | + UnitIntegralTotalDO saveDO = new UnitIntegralTotalDO(); | ||
| 193 | + BeanUtils.copyProperties(reqVO, saveDO); | ||
| 194 | + saveDO.setTotalAdd(currentAdd); // 累计增加 = 本次增加 | ||
| 195 | + saveDO.setTotalSub(0); // 初始扣减为0 | ||
| 196 | + saveDO.setTotalIntegral(currentAdd); // 初始总积分 = 本次增加 | ||
| 197 | + unitIntegralTotalMapper.insert(saveDO); | ||
| 198 | + afterIntegral = currentAdd; | ||
| 195 | 199 | ||
| 196 | - } catch (InterruptedException e) { | ||
| 197 | - log.error("获取锁被中断,单位ID:{}", reqVO.getUnitId(), e); | ||
| 198 | - Thread.currentThread().interrupt(); // 恢复中断状态 | ||
| 199 | - throw exception0(INTERNAL_SERVER_ERROR.getCode(), "积分增加操作被中断,请重试"); | ||
| 200 | - } finally { | ||
| 201 | - // 6. 释放锁(仅当前线程持有锁时释放,避免误删) | ||
| 202 | - if (lockAcquired && lock.isHeldByCurrentThread()) { | ||
| 203 | - lock.unlock(); | ||
| 204 | - log.debug("释放积分增加锁,锁Key:{}", lockKey); | ||
| 205 | - } | 200 | + log.info("新增单位积分成功,单位ID:{},统计日期:{},本次增加:{}", |
| 201 | + reqVO.getUnitId(), reqVO.getStatDate(), currentAdd); | ||
| 206 | } | 202 | } |
| 203 | + // 5. 新增积分明细 | ||
| 204 | + createAddDetail(reqVO, beforeIntegral, afterIntegral, currentAdd); | ||
| 205 | + log.info("积分增加成功,单位ID:{},本次增加:{}", reqVO.getUnitId(), currentAdd); | ||
| 207 | } | 206 | } |
| 208 | /** | 207 | /** |
| 209 | * 封装增加明细创建逻辑 | 208 | * 封装增加明细创建逻辑 |
| @@ -222,81 +221,78 @@ public class UnitIntegralServiceImpl implements UnitIntegralService{ | @@ -222,81 +221,78 @@ public class UnitIntegralServiceImpl implements UnitIntegralService{ | ||
| 222 | * @param reqVO 积分扣减请求(totalIntegral 为「本次扣减积分」) | 221 | * @param reqVO 积分扣减请求(totalIntegral 为「本次扣减积分」) |
| 223 | */ | 222 | */ |
| 224 | @Override | 223 | @Override |
| 225 | - @Transactional(rollbackFor = Exception.class) | ||
| 226 | public void subUnitIntegral(UnitIntegralSaveReqVO reqVO){ | 224 | public void subUnitIntegral(UnitIntegralSaveReqVO reqVO){ |
| 227 | // 1. 前置参数校验 | 225 | // 1. 前置参数校验 |
| 226 | + if (reqVO == null) { | ||
| 227 | + throw exception0(BAD_REQUEST.getCode(), "积分扣减请求参数不能为空"); | ||
| 228 | + } | ||
| 228 | if (reqVO.getTotalIntegral() <= 0) { | 229 | if (reqVO.getTotalIntegral() <= 0) { |
| 229 | - throw exception0(BAD_REQUEST.getCode(),"本次增加积分必须为正数"); | 230 | + throw exception0(BAD_REQUEST.getCode(), "本次扣减积分必须为正数"); // 修复文案错误 |
| 231 | + } | ||
| 232 | + if (ObjectUtils.isEmpty(reqVO.getUnitId()) || ObjectUtils.isEmpty(reqVO.getStatDate())) { | ||
| 233 | + throw exception0(BAD_REQUEST.getCode(), "单位ID和统计日期不能为空"); | ||
| 230 | } | 234 | } |
| 231 | 235 | ||
| 232 | - // 2. 构建细粒度锁Key:单位ID + 统计日期 | ||
| 233 | - String lockKey = buildLockKey(reqVO.getUnitId(), reqVO.getStatDate()); | ||
| 234 | - RLock lock = redissonClient.getLock(lockKey); | 236 | + // 2. 调用通用锁工具类,加锁执行核心逻辑(参考executeNotify的Runnable风格) |
| 237 | + integralLockCoreRedisDAO.lock( | ||
| 238 | + reqVO.getUnitId(), | ||
| 239 | + reqVO.getStatDate(), | ||
| 240 | + IntegralLockCoreRedisDAO.INTEGRAL_LOCK_TIMEOUT_MILLIS, | ||
| 241 | + () -> { | ||
| 242 | + // 3. 锁内二次校验(关键!避免分布式锁并发问题,参考executeNotify的dbTask校验) | ||
| 243 | + LambdaQueryWrapper<UnitIntegralTotalDO> queryWrapper = Wrappers.lambdaQuery(UnitIntegralTotalDO.class) | ||
| 244 | + .eq(UnitIntegralTotalDO::getUnitType, reqVO.getUnitType()) | ||
| 245 | + .eq(UnitIntegralTotalDO::getStatDate, reqVO.getStatDate()) | ||
| 246 | + .eq(UnitIntegralTotalDO::getUnitId, reqVO.getUnitId()) | ||
| 247 | + .eq(UnitIntegralTotalDO::getDeleted, 0); // 排除已删除的积分记录 | ||
| 248 | + UnitIntegralTotalDO unitIntegralTotalDO = unitIntegralTotalMapper.selectOne(queryWrapper); | ||
| 249 | + // 4. 校验积分记录是否存在(锁内校验,避免并发删除场景) | ||
| 250 | + if (unitIntegralTotalDO == null) { | ||
| 251 | + String errorMsg = String.format("单位积分记录不存在,单位ID:%s,统计日期:%s", | ||
| 252 | + reqVO.getUnitId(), reqVO.getStatDate()); | ||
| 253 | + log.error(errorMsg); | ||
| 254 | + throw exception0(BAD_REQUEST.getCode(), errorMsg); | ||
| 255 | + } | ||
| 235 | 256 | ||
| 236 | - // 3. 获取锁(非阻塞,避免线程等待;3秒获取超时,10秒自动过期防死锁) | ||
| 237 | - boolean lockAcquired = false; | ||
| 238 | - try { | ||
| 239 | - lockAcquired = lock.tryLock(3, 10, TimeUnit.SECONDS); | ||
| 240 | - if (!lockAcquired) { | ||
| 241 | - String errorMsg = String.format("获取积分增加锁失败,单位ID:%s,统计日期:%s(请稍后重试)", reqVO.getUnitId(), reqVO.getStatDate()); | ||
| 242 | - log.error(errorMsg); | ||
| 243 | - throw exception0(INTERNAL_SERVER_ERROR.getCode(), errorMsg); | ||
| 244 | - } | ||
| 245 | - // 2. 构建查询条件 | ||
| 246 | - LambdaQueryWrapper<UnitIntegralTotalDO> queryWrapper = Wrappers.lambdaQuery(UnitIntegralTotalDO.class) | ||
| 247 | - .eq(UnitIntegralTotalDO::getUnitType, reqVO.getUnitType()) | ||
| 248 | - .eq(UnitIntegralTotalDO::getStatDate, reqVO.getStatDate()) | ||
| 249 | - .eq(UnitIntegralTotalDO::getUnitId, reqVO.getUnitId()) | ||
| 250 | - .eq(UnitIntegralTotalDO::getDeleted, 0); // 排除已删除的积分记录 | 257 | + // 5. 调用带事务的核心扣减方法 |
| 258 | + getSelf().executeSubUnitIntegral0(reqVO, unitIntegralTotalDO); | ||
| 259 | + } | ||
| 260 | + ); | ||
| 261 | + } | ||
| 262 | + /** | ||
| 263 | + * 扣减积分核心执行方法(带事务,内部调用) | ||
| 264 | + */ | ||
| 265 | + @Transactional(rollbackFor = Exception.class) | ||
| 266 | + public void executeSubUnitIntegral0(UnitIntegralSaveReqVO reqVO, UnitIntegralTotalDO unitIntegralTotalDO) { | ||
| 267 | + // 1. 初始化核心变量 | ||
| 268 | + int currentSub = reqVO.getTotalIntegral(); // 本次扣减积分 | ||
| 269 | + int beforeSub = unitIntegralTotalDO.getTotalSub(); // 扣减前累计扣减积分 | ||
| 270 | + int afterSub = beforeSub + currentSub; // 扣减后累计扣减积分 | ||
| 271 | + int currentTotalIntegral = unitIntegralTotalDO.getTotalAdd() - afterSub; // 扣减后总积分 | ||
| 251 | 272 | ||
| 252 | - // 3. 可加行锁(FOR UPDATE)防止多线程同时扣减,保证数据一致性 | ||
| 253 | - //UnitIntegralTotalDO unitIntegralTotalDO = unitIntegralTotalMapper.selectOne(queryWrapper.last("FOR UPDATE")); | ||
| 254 | - UnitIntegralTotalDO unitIntegralTotalDO = unitIntegralTotalMapper.selectOne(queryWrapper); | ||
| 255 | - // 4. 校验积分记录是否存在 | ||
| 256 | - if (unitIntegralTotalDO == null) { | ||
| 257 | - String errorMsg = String.format("单位积分记录不存在,单位ID:%s,统计日期:%s", | ||
| 258 | - reqVO.getUnitId(), reqVO.getStatDate()); | ||
| 259 | - log.error(errorMsg); | ||
| 260 | - throw exception0(BAD_REQUEST.getCode(),errorMsg); | ||
| 261 | - } | 273 | + // 2. 业务规则校验:扣减后总积分不能为负数 |
| 274 | + if (currentTotalIntegral < 0) { | ||
| 275 | + String errorMsg = String.format("单位积分扣减失败,扣减后总积分为负!单位ID:%s,当前累计增加:%s,本次扣减:%s,累计扣减:%s", | ||
| 276 | + reqVO.getUnitId(), unitIntegralTotalDO.getTotalAdd(), currentSub, beforeSub); | ||
| 277 | + log.error(errorMsg); | ||
| 278 | + throw exception0(BAD_REQUEST.getCode(),errorMsg); | ||
| 279 | + } | ||
| 262 | 280 | ||
| 263 | - // 5. 初始化核心变量 | ||
| 264 | - int currentSub = reqVO.getTotalIntegral(); // 本次扣减积分 | ||
| 265 | - int beforeSub = unitIntegralTotalDO.getTotalSub(); // 扣减前累计扣减积分 | ||
| 266 | - int afterSub = beforeSub + currentSub; // 扣减后累计扣减积分 | ||
| 267 | - int currentTotalIntegral = unitIntegralTotalDO.getTotalAdd() - afterSub; // 扣减后总积分 | 281 | + // 3. 更新积分总表(核心扣减逻辑) |
| 282 | + unitIntegralTotalDO.setTotalSub(afterSub); // 更新累计扣减积分 | ||
| 283 | + unitIntegralTotalDO.setTotalIntegral(currentTotalIntegral); // 更新当前总积分 | ||
| 284 | + unitIntegralTotalDO.setUpdateTime(LocalDateTime.now()); | ||
| 285 | + unitIntegralTotalDO.setUpdater(String.valueOf(getLoginUserId())); | ||
| 286 | + unitIntegralTotalMapper.updateById(unitIntegralTotalDO); | ||
| 268 | 287 | ||
| 269 | - // 6. 业务规则校验:扣减后总积分不能为负数 | ||
| 270 | - if (currentTotalIntegral < 0) { | ||
| 271 | - String errorMsg = String.format("单位积分扣减失败,扣减后总积分为负!单位ID:%s,当前累计增加:%s,本次扣减:%s,累计扣减:%s", | ||
| 272 | - reqVO.getUnitId(), unitIntegralTotalDO.getTotalAdd(), currentSub, beforeSub); | ||
| 273 | - log.error(errorMsg); | ||
| 274 | - throw exception0(BAD_REQUEST.getCode(),errorMsg); | ||
| 275 | - } | 288 | + log.info("单位积分扣减成功,单位ID:{},统计日期:{},本次扣减:{},扣减前累计扣减:{},扣减后累计扣减:{},扣减后总积分:{}", |
| 289 | + reqVO.getUnitId(), reqVO.getStatDate(), currentSub, beforeSub, afterSub, currentTotalIntegral); | ||
| 276 | 290 | ||
| 277 | - // 7. 更新积分总表(核心扣减逻辑) | ||
| 278 | - unitIntegralTotalDO.setTotalSub(afterSub); // 更新累计扣减积分 | ||
| 279 | - unitIntegralTotalDO.setTotalIntegral(currentTotalIntegral); // 更新当前总积分 | ||
| 280 | - unitIntegralTotalDO.setUpdateTime(LocalDateTime.now()); | ||
| 281 | - unitIntegralTotalDO.setUpdater(String.valueOf(getLoginUserId())); | ||
| 282 | - unitIntegralTotalMapper.updateById(unitIntegralTotalDO); | ||
| 283 | - | ||
| 284 | - log.info("单位积分扣减成功,单位ID:{},统计日期:{},本次扣减:{},扣减前累计扣减:{},扣减后累计扣减:{},扣减后总积分:{}", | ||
| 285 | - reqVO.getUnitId(), reqVO.getStatDate(), currentSub, beforeSub, afterSub, currentTotalIntegral); | ||
| 286 | - | ||
| 287 | - // 8. 新增扣减明细 | ||
| 288 | - // 新增减积分明细 | ||
| 289 | - createSubDetail(reqVO, beforeSub, afterSub, currentSub); | ||
| 290 | - log.info("单位{}减积分成功,本次减{},累计减{},剩余{}", | ||
| 291 | - reqVO.getUnitId(), currentSub, afterSub, currentSub); | ||
| 292 | - } catch (InterruptedException e) { | ||
| 293 | - Thread.currentThread().interrupt(); | ||
| 294 | - throw exception0(INTERNAL_SERVER_ERROR.getCode(), "积分操作被中断,请重试"); | ||
| 295 | - } finally { | ||
| 296 | - if (lockAcquired && lock.isHeldByCurrentThread()) { | ||
| 297 | - lock.unlock(); | ||
| 298 | - } | ||
| 299 | - } | 291 | + // 8. 新增扣减明细 |
| 292 | + // 新增减积分明细 | ||
| 293 | + createSubDetail(reqVO, beforeSub, afterSub, currentSub); | ||
| 294 | + log.info("单位{}减积分成功,本次减{},累计减{},剩余{}", | ||
| 295 | + reqVO.getUnitId(), currentSub, afterSub, currentSub); | ||
| 300 | } | 296 | } |
| 301 | 297 | ||
| 302 | /** | 298 | /** |
| @@ -313,4 +309,14 @@ public class UnitIntegralServiceImpl implements UnitIntegralService{ | @@ -313,4 +309,14 @@ public class UnitIntegralServiceImpl implements UnitIntegralService{ | ||
| 313 | log.info("新增积分扣减明细成功,单位ID:{},明细ID:{}", updateReqVO.getUnitId(), detailVO.getId()); | 309 | log.info("新增积分扣减明细成功,单位ID:{},明细ID:{}", updateReqVO.getUnitId(), detailVO.getId()); |
| 314 | } | 310 | } |
| 315 | 311 | ||
| 312 | + | ||
| 313 | + /** | ||
| 314 | + * 获得自身的代理对象,解决 AOP 生效问题 | ||
| 315 | + * | ||
| 316 | + * @return 自己 | ||
| 317 | + */ | ||
| 318 | + private UnitIntegralServiceImpl getSelf() { | ||
| 319 | + return SpringUtil.getBean(getClass()); | ||
| 320 | + } | ||
| 321 | + | ||
| 316 | } | 322 | } |