feat: 完善订单生命周期幂等与日志追踪

This commit is contained in:
irving
2025-10-28 23:24:33 -04:00
parent 6dba6464f9
commit 7db9318a7b
25 changed files with 1143 additions and 1066 deletions

View File

@@ -2,6 +2,10 @@ package com.starry.admin.modules.custom.mapper;
import com.github.yulichang.base.MPJBaseMapper;
import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
import java.math.BigDecimal;
import java.util.Date;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
/**
* 顾客Mapper接口
@@ -11,4 +15,18 @@ import com.starry.admin.modules.custom.module.entity.PlayCustomUserInfoEntity;
*/
public interface PlayCustomUserInfoMapper extends MPJBaseMapper<PlayCustomUserInfoEntity> {
@Update({
"<script>",
"UPDATE play_custom_user_info",
"SET accumulated_consumption_amount = COALESCE(accumulated_consumption_amount, 0) + #{consumptionDelta},",
" last_purchase_time = #{completionTime},",
" first_purchase_time = CASE WHEN first_purchase_time IS NULL THEN #{completionTime} ELSE first_purchase_time END",
" <if test='weiChatCode != null and weiChatCode != \"\"'>, wei_chat_code = #{weiChatCode}</if>",
"WHERE id = #{userId}",
"</script>"
})
int applyOrderCompletionUpdate(@Param("userId") String userId,
@Param("consumptionDelta") BigDecimal consumptionDelta,
@Param("completionTime") Date completionTime,
@Param("weiChatCode") String weiChatCode);
}

View File

@@ -3,6 +3,7 @@ package com.starry.admin.modules.custom.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.starry.admin.modules.custom.module.entity.PlayCustomLevelInfoEntity;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import java.math.BigDecimal;
import java.util.List;
/**
@@ -100,4 +101,13 @@ public interface IPlayCustomLevelInfoService extends IService<PlayCustomLevelInf
* 删除最大等级
*/
void delMaxLevelByLevel(Integer level);
/**
* 根据累计消费金额匹配顾客等级。
*
* @param tenantId 租户ID
* @param totalConsumption 累计消费金额
* @return 匹配到的等级,未匹配则返回 {@code null}
*/
PlayCustomLevelInfoEntity matchLevelByConsumption(String tenantId, BigDecimal totalConsumption);
}

View File

@@ -172,4 +172,11 @@ public interface IPlayCustomUserInfoService extends IService<PlayCustomUserInfoE
* @author admin
**/
void saveOrderInfo(PlayOrderInfoEntity entity);
/**
* 处理订单完成后的顾客统计更新。
*
* @param entity 完成的订单实体
*/
void handleOrderCompletion(PlayOrderInfoEntity entity);
}

View File

@@ -9,8 +9,10 @@ import com.starry.admin.modules.custom.module.entity.PlayCustomLevelInfoEntity;
import com.starry.admin.modules.custom.service.IPlayCustomLevelInfoService;
import com.starry.admin.modules.system.module.entity.SysTenantEntity;
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
@@ -153,4 +155,39 @@ public class PlayCustomLevelInfoServiceImpl extends ServiceImpl<PlayCustomLevelI
queryWrapper.eq(PlayCustomLevelInfoEntity::getLevel, level);
this.baseMapper.delete(queryWrapper);
}
@Override
public PlayCustomLevelInfoEntity matchLevelByConsumption(String tenantId, BigDecimal totalConsumption) {
BigDecimal consumption = Objects.requireNonNullElse(totalConsumption, BigDecimal.ZERO);
LambdaQueryWrapper<PlayCustomLevelInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.orderByAsc(PlayCustomLevelInfoEntity::getLevel);
if (StrUtil.isNotBlank(tenantId)) {
lambdaQueryWrapper.eq(PlayCustomLevelInfoEntity::getTenantId, tenantId);
}
List<PlayCustomLevelInfoEntity> levels = this.baseMapper.selectList(lambdaQueryWrapper);
if (levels == null || levels.isEmpty()) {
return null;
}
PlayCustomLevelInfoEntity matched = null;
for (PlayCustomLevelInfoEntity level : levels) {
BigDecimal threshold = parseConsumptionAmount(level.getConsumptionAmount());
if (consumption.compareTo(threshold) >= 0) {
matched = level;
} else {
break;
}
}
return matched != null ? matched : levels.get(0);
}
private BigDecimal parseConsumptionAmount(String rawValue) {
if (StrUtil.isBlank(rawValue)) {
return BigDecimal.ZERO;
}
try {
return new BigDecimal(rawValue.trim());
} catch (NumberFormatException ex) {
return BigDecimal.ZERO;
}
}
}

View File

@@ -16,6 +16,7 @@ import com.starry.admin.modules.custom.module.vo.PlayCustomRankingQueryVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomRankingReturnVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomUserQueryVo;
import com.starry.admin.modules.custom.module.vo.PlayCustomUserReturnVo;
import com.starry.admin.modules.custom.service.IPlayCustomLevelInfoService;
import com.starry.admin.modules.custom.service.IPlayCustomUserInfoService;
import com.starry.admin.modules.order.module.entity.PlayOrderInfoEntity;
import com.starry.admin.modules.order.service.impl.PlayOrderInfoServiceImpl;
@@ -23,6 +24,8 @@ import com.starry.admin.modules.personnel.service.IPlayBalanceDetailsInfoService
import com.starry.common.utils.IdUtils;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.*;
import java.util.stream.Collectors;
import javax.annotation.Resource;
@@ -49,6 +52,9 @@ public class PlayCustomUserInfoServiceImpl extends ServiceImpl<PlayCustomUserInf
@Resource
private IPlayBalanceDetailsInfoService playBalanceDetailsInfoService;
@Resource
private IPlayCustomLevelInfoService playCustomLevelInfoService;
@Override
public PlayCustomUserInfoEntity selectByOpenid(String openId) {
LambdaQueryWrapper<PlayCustomUserInfoEntity> lambdaQueryWrapper = new LambdaQueryWrapper<>();
@@ -369,6 +375,50 @@ public class PlayCustomUserInfoServiceImpl extends ServiceImpl<PlayCustomUserInf
return baseMapper.selectList(wrapper);
}
@Override
public void handleOrderCompletion(PlayOrderInfoEntity entity) {
if (entity == null || StrUtil.isBlank(entity.getPurchaserBy())) {
return;
}
PlayCustomUserInfoEntity userInfo = playCustomUserInfoMapper.selectById(entity.getPurchaserBy());
if (userInfo == null) {
log.warn("handleOrderCompletion skipped, userId={} missing, orderId={}", entity.getPurchaserBy(), entity.getId());
return;
}
BigDecimal finalAmount = Objects.requireNonNullElse(entity.getFinalAmount(), BigDecimal.ZERO);
Date completionTime = resolveCompletionTime(entity.getOrderEndTime());
int affected = playCustomUserInfoMapper.applyOrderCompletionUpdate(
userInfo.getId(),
finalAmount,
completionTime,
entity.getWeiChatCode());
if (affected == 0) {
log.warn("handleOrderCompletion update skipped for userId={}, orderId={}", userInfo.getId(), entity.getId());
return;
}
PlayCustomUserInfoEntity latest = playCustomUserInfoMapper.selectById(userInfo.getId());
if (latest == null) {
return;
}
PlayCustomLevelInfoEntity matchedLevel = playCustomLevelInfoService
.matchLevelByConsumption(latest.getTenantId(), latest.getAccumulatedConsumptionAmount());
if (matchedLevel != null && !StrUtil.equals(matchedLevel.getId(), latest.getLevelId())) {
this.update(Wrappers.<PlayCustomUserInfoEntity>lambdaUpdate()
.eq(PlayCustomUserInfoEntity::getId, latest.getId())
.set(PlayCustomUserInfoEntity::getLevelId, matchedLevel.getId()));
log.info("顾客{}消费累计达到{},自动调整等级为{}", latest.getId(), latest.getAccumulatedConsumptionAmount(), matchedLevel.getName());
}
}
private Date resolveCompletionTime(LocalDateTime orderEndTime) {
LocalDateTime time = orderEndTime != null ? orderEndTime : LocalDateTime.now();
return Date.from(time.atZone(ZoneId.systemDefault()).toInstant());
}
@Override
public void saveOrderInfo(PlayOrderInfoEntity entity) {
String id = entity.getPurchaserBy();