|
@@ -1,13 +1,13 @@
|
|
|
package com.ylx.point.service.impl;
|
|
package com.ylx.point.service.impl;
|
|
|
|
|
|
|
|
import cn.hutool.core.date.DateUtil;
|
|
import cn.hutool.core.date.DateUtil;
|
|
|
-import cn.hutool.core.util.ObjectUtil;
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
|
-import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
|
|
|
|
|
|
+import com.ylx.common.exception.ServiceException;
|
|
|
import com.ylx.point.domain.PointActivity;
|
|
import com.ylx.point.domain.PointActivity;
|
|
|
import com.ylx.point.domain.PointActivityExpirePolicy;
|
|
import com.ylx.point.domain.PointActivityExpirePolicy;
|
|
|
import com.ylx.point.domain.PointUserLog;
|
|
import com.ylx.point.domain.PointUserLog;
|
|
|
|
|
+import com.ylx.point.enums.ActivityNameEnum;
|
|
|
import com.ylx.point.enums.PointActivityExpirePolicyEnum;
|
|
import com.ylx.point.enums.PointActivityExpirePolicyEnum;
|
|
|
import com.ylx.point.service.IPointAccountService;
|
|
import com.ylx.point.service.IPointAccountService;
|
|
|
import com.ylx.point.service.IPointActivityExpirePolicyService;
|
|
import com.ylx.point.service.IPointActivityExpirePolicyService;
|
|
@@ -15,11 +15,12 @@ import com.ylx.point.service.IPointActivityService;
|
|
|
import com.ylx.point.service.IPointUserLogService;
|
|
import com.ylx.point.service.IPointUserLogService;
|
|
|
import org.springframework.stereotype.Service;
|
|
import org.springframework.stereotype.Service;
|
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
import org.springframework.transaction.annotation.Transactional;
|
|
|
-import springfox.documentation.annotations.Cacheable;
|
|
|
|
|
|
|
|
|
|
import javax.annotation.Resource;
|
|
import javax.annotation.Resource;
|
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalDateTime;
|
|
|
-import java.util.*;
|
|
|
|
|
|
|
+import java.util.Date;
|
|
|
|
|
+import java.util.List;
|
|
|
|
|
+import java.util.Optional;
|
|
|
|
|
|
|
|
@Service
|
|
@Service
|
|
|
public class PointAccountServiceImpl implements IPointAccountService {
|
|
public class PointAccountServiceImpl implements IPointAccountService {
|
|
@@ -68,37 +69,164 @@ public class PointAccountServiceImpl implements IPointAccountService {
|
|
|
|
|
|
|
|
boolean saveSuccess = pointUserLogService.save(log);
|
|
boolean saveSuccess = pointUserLogService.save(log);
|
|
|
if (!saveSuccess) {
|
|
if (!saveSuccess) {
|
|
|
- throw new RuntimeException("积分流水记录失败");
|
|
|
|
|
|
|
+ throw new ServiceException("积分流水记录失败");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
return newBalance;
|
|
return newBalance;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
/**
|
|
|
- * 获取用户当前可用积分余额
|
|
|
|
|
- * 逻辑:SUM(所有收入记录) - SUM(所有支出记录)
|
|
|
|
|
- * 或者更简单的:SUM(points),因为支出记录的 points 是负数
|
|
|
|
|
|
|
+ * 扣减用户积分(用于积分商品下单)
|
|
|
|
|
+ * 扣减顺序:先获取的先扣减(FIFO原则)
|
|
|
|
|
+ */
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public Integer deductPoints(String openId, int pointsToDeduct, String bizOrderNo, String activityName) {
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 获取当前余额
|
|
|
|
|
+ Integer currentBalance = getBalance(openId);
|
|
|
|
|
+ if (currentBalance < pointsToDeduct) {
|
|
|
|
|
+ throw new ServiceException("积分余额不足,当前余额:" + currentBalance + ",需要扣减:" + pointsToDeduct);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 查询用户所有未过期的收入记录,按获取时间升序排序(FIFO原则)
|
|
|
|
|
+ LambdaQueryWrapper<PointUserLog> queryWrapper = new LambdaQueryWrapper<>();
|
|
|
|
|
+ queryWrapper.eq(PointUserLog::getOpenId, openId)
|
|
|
|
|
+ .eq(PointUserLog::getOpType, 1) // 1-收入
|
|
|
|
|
+ .eq(PointUserLog::getIsExpired, 0) // 0-未过期
|
|
|
|
|
+ .and(wrapper -> wrapper.isNull(PointUserLog::getExpireTime)
|
|
|
|
|
+ .or()
|
|
|
|
|
+ .gt(PointUserLog::getExpireTime, new Date())) // 未过期或无过期时间
|
|
|
|
|
+ .orderByAsc(PointUserLog::getCreateTime); // 按获取时间升序
|
|
|
|
|
+
|
|
|
|
|
+ List<PointUserLog> availablePoints = pointUserLogService.list(queryWrapper);
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 按FIFO原则扣减积分
|
|
|
|
|
+ int remainingToDeduct = pointsToDeduct;
|
|
|
|
|
+ for (PointUserLog sourceLog : availablePoints) {
|
|
|
|
|
+ if (remainingToDeduct <= 0) {
|
|
|
|
|
+ break;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算这条记录可以扣减的积分
|
|
|
|
|
+ // 需要查询这条记录已经被扣减了多少
|
|
|
|
|
+ LambdaQueryWrapper<PointUserLog> deductQuery = new LambdaQueryWrapper<>();
|
|
|
|
|
+ deductQuery.eq(PointUserLog::getOpenId, openId)
|
|
|
|
|
+ .eq(PointUserLog::getOpType, 2) // 2-支出
|
|
|
|
|
+ .eq(PointUserLog::getSourceLogId, sourceLog.getId());
|
|
|
|
|
+ List<PointUserLog> deductLogs = pointUserLogService.list(deductQuery);
|
|
|
|
|
+
|
|
|
|
|
+ int alreadyDeducted = deductLogs.stream()
|
|
|
|
|
+ .mapToInt(PointUserLog::getPoints)
|
|
|
|
|
+ .map(Math::abs)
|
|
|
|
|
+ .sum();
|
|
|
|
|
+
|
|
|
|
|
+ int availableInThisRecord = sourceLog.getPoints() - alreadyDeducted;
|
|
|
|
|
+
|
|
|
|
|
+ if (availableInThisRecord <= 0) {
|
|
|
|
|
+ continue; // 这条记录已经完全扣减,跳过
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 计算本次需要从这条记录扣减的积分
|
|
|
|
|
+ int deductFromThisRecord = Math.min(remainingToDeduct, availableInThisRecord);
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 创建扣减记录
|
|
|
|
|
+ PointUserLog deductLog = new PointUserLog();
|
|
|
|
|
+ deductLog.setOpenId(openId);
|
|
|
|
|
+ deductLog.setPoints(-deductFromThisRecord); // 负数表示扣减
|
|
|
|
|
+ deductLog.setOpType(2); // 2-支出
|
|
|
|
|
+ deductLog.setBizOrderId(bizOrderNo);
|
|
|
|
|
+ deductLog.setSourceLogId(sourceLog.getId()); // 关联到收入记录
|
|
|
|
|
+ deductLog.setActivityName(activityName);
|
|
|
|
|
+ deductLog.setMonth(DateUtil.format(new Date(), "yyyyMM"));
|
|
|
|
|
+
|
|
|
|
|
+ // 计算扣减后的余额
|
|
|
|
|
+ Integer newBalance = currentBalance - deductFromThisRecord;
|
|
|
|
|
+ deductLog.setBalanceAfter(newBalance);
|
|
|
|
|
+
|
|
|
|
|
+ boolean saveSuccess = pointUserLogService.save(deductLog);
|
|
|
|
|
+ if (!saveSuccess) {
|
|
|
|
|
+ throw new ServiceException("积分扣减记录失败");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 更新剩余需要扣减的积分
|
|
|
|
|
+ remainingToDeduct -= deductFromThisRecord;
|
|
|
|
|
+ currentBalance = newBalance;
|
|
|
|
|
+
|
|
|
|
|
+ // 5. 检查源记录是否完全扣减,如果是则标记为已过期
|
|
|
|
|
+ if (availableInThisRecord == deductFromThisRecord) {
|
|
|
|
|
+ sourceLog.setIsExpired(1); // 1-已完全核销
|
|
|
|
|
+ pointUserLogService.updateById(sourceLog);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return currentBalance;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 退款积分(用于积分商品退款)
|
|
|
|
|
+ * 退回的积分没有过期时间
|
|
|
*/
|
|
*/
|
|
|
|
|
+ @Override
|
|
|
|
|
+ @Transactional(rollbackFor = Exception.class)
|
|
|
|
|
+ public Integer refundPoints(String openId, int pointsToRefund, String bizOrderNo, String activityName) {
|
|
|
|
|
+ if (pointsToRefund <= 0) {
|
|
|
|
|
+ return getBalance(openId);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 1. 获取当前余额 (变动前)
|
|
|
|
|
+ Integer currentBalance = getBalance(openId);
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 写入积分流水表 (point_user_log)
|
|
|
|
|
+ PointUserLog log = new PointUserLog();
|
|
|
|
|
+ log.setOpenId(openId);
|
|
|
|
|
+ log.setPoints(pointsToRefund);
|
|
|
|
|
+ log.setOpType(1); // 1-收入
|
|
|
|
|
+ log.setBizOrderId(bizOrderNo);
|
|
|
|
|
+ log.setSourceLogId(null);
|
|
|
|
|
+ log.setIsExpired(0); // 0-未过期
|
|
|
|
|
+ log.setActivityName(ActivityNameEnum.GOODS_REFUND.getInfo());
|
|
|
|
|
+ log.setExpireTime(null); // 退款积分没有过期时间
|
|
|
|
|
+ log.setMonth(DateUtil.format(new Date(), "yyyyMM"));
|
|
|
|
|
+
|
|
|
|
|
+ // 计算变动后余额 (快照)
|
|
|
|
|
+ Integer newBalance = currentBalance + pointsToRefund;
|
|
|
|
|
+ log.setBalanceAfter(newBalance);
|
|
|
|
|
+
|
|
|
|
|
+ boolean saveSuccess = pointUserLogService.save(log);
|
|
|
|
|
+ if (!saveSuccess) {
|
|
|
|
|
+ throw new RuntimeException("积分退款记录失败");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return newBalance;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* 获取用户当前可用积分余额
|
|
* 获取用户当前可用积分余额
|
|
|
- * 逻辑:SUM(points),支出记录的 points 为负数
|
|
|
|
|
|
|
+ * 逻辑:SUM(所有未过期的收入记录的points) - SUM(所有支出记录的points绝对值)
|
|
|
*/
|
|
*/
|
|
|
private Integer getBalance(String openId) {
|
|
private Integer getBalance(String openId) {
|
|
|
- // 使用 QueryWrapper (非 Lambda) 来执行原生 SQL 聚合函数
|
|
|
|
|
- QueryWrapper<PointUserLog> queryWrapper = new QueryWrapper<>();
|
|
|
|
|
- queryWrapper.select("SUM(points)");
|
|
|
|
|
- queryWrapper.eq("open_id", openId);
|
|
|
|
|
|
|
+ // 1. 查询所有未过期的收入记录总和
|
|
|
|
|
+ QueryWrapper<PointUserLog> incomeWrapper = new QueryWrapper<>();
|
|
|
|
|
+ incomeWrapper.select("SUM(points)");
|
|
|
|
|
+ incomeWrapper.eq("open_id", openId);
|
|
|
|
|
+ incomeWrapper.eq("op_type", 1); // 1-收入
|
|
|
|
|
+ incomeWrapper.ne("is_expired", 1); // 排除已过期的记录
|
|
|
|
|
|
|
|
- // 执行查询,获取单个 Object 结果
|
|
|
|
|
- PointUserLog pointUserLog = pointUserLogService.getOne(queryWrapper);
|
|
|
|
|
|
|
+ PointUserLog incomeLog = pointUserLogService.getOne(incomeWrapper);
|
|
|
|
|
+ int totalIncome = (incomeLog != null && incomeLog.getPoints() != null) ? incomeLog.getPoints() : 0;
|
|
|
|
|
|
|
|
- // 判空处理
|
|
|
|
|
- if (ObjectUtil.isNull(pointUserLog)) {
|
|
|
|
|
- return 0;
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ // 2. 查询所有支出记录总和(支出记录的points为负数,需要取绝对值)
|
|
|
|
|
+ QueryWrapper<PointUserLog> expenseWrapper = new QueryWrapper<>();
|
|
|
|
|
+ expenseWrapper.select("SUM(ABS(points))");
|
|
|
|
|
+ expenseWrapper.eq("open_id", openId);
|
|
|
|
|
+ expenseWrapper.in("op_type", 2, 3); // 2-支出 3-过期
|
|
|
|
|
+
|
|
|
|
|
+ PointUserLog expenseLog = pointUserLogService.getOne(expenseWrapper);
|
|
|
|
|
+ int totalExpense = (expenseLog != null && expenseLog.getPoints() != null) ? expenseLog.getPoints() : 0;
|
|
|
|
|
|
|
|
- // 类型转换并返回
|
|
|
|
|
- return pointUserLog.getPoints();
|
|
|
|
|
|
|
+ // 3. 返回可用余额
|
|
|
|
|
+ return totalIncome - totalExpense;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
|