闸机扣费接口调整
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
package org.jeecg.common.util;
|
||||
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Component
|
||||
public class RedisLockUtil {
|
||||
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
// 锁的默认过期时间:1分钟
|
||||
private static final long DEFAULT_LOCK_EXPIRE = 60;
|
||||
// 锁的前缀,避免键名冲突
|
||||
private static final String LOCK_PREFIX = "food:lock:";
|
||||
|
||||
public RedisLockUtil(StringRedisTemplate stringRedisTemplate) {
|
||||
this.stringRedisTemplate = stringRedisTemplate;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分布式锁
|
||||
* @param key 业务键(这里使用菜品ID)
|
||||
* @return 锁标识(释放锁时需要),null表示获取锁失败
|
||||
*/
|
||||
public String tryLock(String key) {
|
||||
return tryLock(key, DEFAULT_LOCK_EXPIRE, TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取分布式锁
|
||||
* @param key 业务键
|
||||
* @param expire 过期时间
|
||||
* @param timeUnit 时间单位
|
||||
* @return 锁标识,null表示获取锁失败
|
||||
*/
|
||||
public String tryLock(String key, long expire, TimeUnit timeUnit) {
|
||||
// 生成唯一标识,用于释放锁时的验证
|
||||
String lockValue = UUID.randomUUID().toString();
|
||||
String lockKey = LOCK_PREFIX + key;
|
||||
|
||||
// 使用setIfAbsent实现分布式锁,原子操作
|
||||
Boolean success = stringRedisTemplate.opsForValue()
|
||||
.setIfAbsent(lockKey, lockValue, expire, timeUnit);
|
||||
|
||||
return Boolean.TRUE.equals(success) ? lockValue : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 释放分布式锁
|
||||
* @param key 业务键
|
||||
* @param lockValue 获取锁时返回的标识
|
||||
* @return 是否释放成功
|
||||
*/
|
||||
public boolean releaseLock(String key, String lockValue) {
|
||||
if (lockValue == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String lockKey = LOCK_PREFIX + key;
|
||||
String currentValue = stringRedisTemplate.opsForValue().get(lockKey);
|
||||
|
||||
// 验证是否是自己持有的锁
|
||||
if (lockValue.equals(currentValue)) {
|
||||
// 执行删除操作
|
||||
return Boolean.TRUE.equals(stringRedisTemplate.delete(lockKey));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
+2
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
package org.jeecg.payment.mapper;
|
||||
|
||||
import cn.hutool.core.date.DateTime;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
@@ -24,4 +25,5 @@ public interface StClientMemberLevelDeductRuleMapper extends BaseMapper<StClient
|
||||
*/
|
||||
List<StClientMemberLevelDeductRuleVo> selectStClientMemberLevelDeductRulePage(Page<StClientMemberLevelDeductRuleVo> page, @Param("vo") StClientMemberLevelDemotionTimeVO stClientMemberLevelDeductRule);
|
||||
|
||||
DateTime getMaxTimeByUserId(@Param("userId") String userId);
|
||||
}
|
||||
|
||||
+3
@@ -17,5 +17,8 @@
|
||||
</if>
|
||||
order by is_enable desc, create_time desc
|
||||
</select>
|
||||
<select id="getMaxTimeByUserId" resultType="cn.hutool.core.date.DateTime">
|
||||
SELECT MAX(create_time) FROM st_bbzx_order_info WHERE member_id = #{userId}
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
|
||||
+120
-14
@@ -16,6 +16,8 @@ import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.system.base.service.impl.JeecgServiceImpl;
|
||||
import org.jeecg.common.util.DateUtils;
|
||||
import org.jeecg.common.util.RedisLockUtil;
|
||||
import org.jeecg.common.util.RedisUtil;
|
||||
import org.jeecg.payment.entity.StBbzxOrderInfo;
|
||||
import org.jeecg.payment.entity.StClientMemberLevelDeductRule;
|
||||
import org.jeecg.payment.enums.DeductionTypeEnum;
|
||||
@@ -37,9 +39,7 @@ import org.springframework.web.bind.annotation.RequestBody;
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.math.RoundingMode;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -61,6 +61,8 @@ public class StClientMemberLevelDeductRuleServiceImpl extends JeecgServiceImpl<S
|
||||
|
||||
@Autowired
|
||||
private ISlBaseDinnerTimeService stBaseDinnerTimeService;
|
||||
@Resource
|
||||
private RedisLockUtil redisLockUtil;
|
||||
|
||||
|
||||
@Override
|
||||
@@ -232,23 +234,118 @@ public class StClientMemberLevelDeductRuleServiceImpl extends JeecgServiceImpl<S
|
||||
}
|
||||
|
||||
|
||||
// @Override
|
||||
// public Result<StClientMemberSpendVO> bindUserOfflineCardCode(@RequestBody StRfidoffLineVo stRfidoffLineVo){
|
||||
// String deviceCode = stRfidoffLineVo.getDeviceCode();
|
||||
// String restId = stRfidoffLineVo.getRestId();
|
||||
// List<StRfidVo> voListAll = stRfidoffLineVo.getVoList();
|
||||
// if(CollectionUtils.isEmpty(voListAll)){
|
||||
// return Result.error("扣费信息不能为空");
|
||||
// }
|
||||
// //扣费规则(扣费类型(1:固定扣费,2:固定+补贴扣费,3:按量扣费,4:自定义金额))
|
||||
// StClientMemberLevelDeductRuleVo levelDeductRuleVo = this.getDeductRuleByCanteenId(restId);
|
||||
// if(voListAll.size() > 1){//离线扣费
|
||||
// //加异步处理
|
||||
// CompletableFuture.runAsync(() -> {
|
||||
// unifiedDeductionInterface(voListAll,restId,deviceCode,levelDeductRuleVo,false);
|
||||
// });
|
||||
// } else {//在线扣费
|
||||
// return unifiedDeductionInterface(voListAll,restId,deviceCode,levelDeductRuleVo,true);
|
||||
// }
|
||||
// return Result.OK("扣费成功");
|
||||
// }
|
||||
/**
|
||||
* 过滤与当前时间相差小于1分钟的记录
|
||||
*/
|
||||
private static List<StRfidVo> filterRecentThanOneMinute(List<StRfidVo> dataList,long maxTime) {
|
||||
long oneMinuteMs = 60 * 1000; // 1分钟 = 60000毫秒
|
||||
|
||||
return dataList.stream()
|
||||
.filter(vo -> {
|
||||
// 跳过 dateTime 为空或格式错误的记录(视为无效数据,直接过滤)
|
||||
if (StringUtils.isBlank(vo.getDateTime())) {
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
long recordTime = Long.parseLong(vo.getDateTime()); // 记录时间戳(毫秒)
|
||||
long timeDiff = maxTime - recordTime; // 时间差(当前时间 - 记录时间)
|
||||
// 保留:时间差 >= 1分钟(或记录时间在当前时间之后,避免未来时间戳)
|
||||
return timeDiff >= oneMinuteMs || recordTime > maxTime;
|
||||
} catch (NumberFormatException e) {
|
||||
// dateTime 不是有效数字(如非时间戳字符串),视为无效数据
|
||||
return false;
|
||||
}
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除时间戳(dateTime)重复的记录(保留第一条)
|
||||
*/
|
||||
private static List<StRfidVo> filterDuplicateTimestamp(List<StRfidVo> dataList) {
|
||||
// 使用 LinkedHashMap 保留插入顺序,key=dateTime,value=StRfidVo(重复时覆盖,仅保留最后一次出现的记录)
|
||||
Map<String, StRfidVo> uniqueMap = new LinkedHashMap<>();
|
||||
for (StRfidVo vo : dataList) {
|
||||
String dateTime = vo.getDateTime();
|
||||
if (StringUtils.isNotBlank(dateTime)) { // 仅对 dateTime 非空的记录去重
|
||||
uniqueMap.put(dateTime, vo);
|
||||
} else {
|
||||
// dateTime 为空的记录直接保留(已通过步骤1过滤无效数据,此处可忽略)
|
||||
uniqueMap.put(UUID.randomUUID().toString(), vo); // 用UUID避免key冲突
|
||||
}
|
||||
}
|
||||
// 转换为 List 并返回
|
||||
return new ArrayList<>(uniqueMap.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* 食堂扣费主接口
|
||||
*/
|
||||
@Override
|
||||
public Result<StClientMemberSpendVO> bindUserOfflineCardCode(@RequestBody StRfidoffLineVo stRfidoffLineVo){
|
||||
public Result<StClientMemberSpendVO> bindUserOfflineCardCode(StRfidoffLineVo stRfidoffLineVo) {
|
||||
String deviceCode = stRfidoffLineVo.getDeviceCode();
|
||||
String restId = stRfidoffLineVo.getRestId();
|
||||
List<StRfidVo> voListAll = stRfidoffLineVo.getVoList();
|
||||
if(CollectionUtils.isEmpty(voListAll)){
|
||||
return Result.error("扣费信息不能为空");
|
||||
}
|
||||
//扣费规则(扣费类型(1:固定扣费,2:固定+补贴扣费,3:按量扣费,4:自定义金额))
|
||||
StClientMemberLevelDeductRuleVo levelDeductRuleVo = this.getDeductRuleByCanteenId(restId);
|
||||
if(voListAll.size() > 1){//离线扣费
|
||||
//加异步处理
|
||||
CompletableFuture.runAsync(() -> {
|
||||
unifiedDeductionInterface(voListAll,restId,deviceCode,levelDeductRuleVo,false);
|
||||
});
|
||||
} else {//在线扣费
|
||||
return unifiedDeductionInterface(voListAll,restId,deviceCode,levelDeductRuleVo,true);
|
||||
//去重
|
||||
voListAll = filterDuplicateTimestamp(voListAll);
|
||||
//过滤时间小于1分钟的记录
|
||||
voListAll = filterRecentThanOneMinute(voListAll,System.currentTimeMillis());
|
||||
|
||||
Map<String, List<StRfidVo>> userListMaps = voListAll.stream().collect(Collectors.groupingBy(StRfidVo::getUserId));
|
||||
for (Map.Entry<String, List<StRfidVo>> userListMap : userListMaps.entrySet()) {
|
||||
String userId = userListMap.getKey();
|
||||
// 尝试获取锁,超时时间1分钟
|
||||
String lockValue = redisLockUtil.tryLock(userId);
|
||||
if (lockValue == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
//根据用户获取之后一次扣费时间
|
||||
DateTime maxTime = baseMapper.getMaxTimeByUserId(userId);
|
||||
|
||||
if(CollectionUtils.isEmpty(voListAll)){
|
||||
return Result.error("扣费信息不能为空");
|
||||
}
|
||||
//扣费规则(扣费类型(1:固定扣费,2:固定+补贴扣费,3:按量扣费,4:自定义金额))
|
||||
StClientMemberLevelDeductRuleVo levelDeductRuleVo = this.getDeductRuleByCanteenId(restId);
|
||||
if(voListAll.size() > 1){//离线扣费
|
||||
//加异步处理
|
||||
List<StRfidVo> finalVoListAll = voListAll;
|
||||
CompletableFuture.runAsync(() -> {
|
||||
unifiedDeductionInterface(finalVoListAll,restId,deviceCode,levelDeductRuleVo,false,maxTime);
|
||||
});
|
||||
} else {//在线扣费
|
||||
return unifiedDeductionInterface(voListAll,restId,deviceCode,levelDeductRuleVo,true,maxTime);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
//释放锁
|
||||
redisLockUtil.releaseLock(userId, lockValue);
|
||||
}
|
||||
}
|
||||
return Result.OK("扣费成功");
|
||||
}
|
||||
@@ -263,7 +360,7 @@ public class StClientMemberLevelDeductRuleServiceImpl extends JeecgServiceImpl<S
|
||||
* @return
|
||||
*/
|
||||
private Result<StClientMemberSpendVO> unifiedDeductionInterface(List<StRfidVo> voListAll, String restId, String deviceCode,
|
||||
StClientMemberLevelDeductRuleVo levelDeductRuleVo, Boolean online) {
|
||||
StClientMemberLevelDeductRuleVo levelDeductRuleVo, Boolean online,DateTime maxTime) {
|
||||
long start = System.currentTimeMillis();
|
||||
//声明在线扣费余额信息
|
||||
StClientMemberSpendVO stClientMemberSpendVO = new StClientMemberSpendVO();
|
||||
@@ -272,6 +369,15 @@ public class StClientMemberLevelDeductRuleServiceImpl extends JeecgServiceImpl<S
|
||||
|
||||
// 存储错误信息
|
||||
String errorMessages = null;
|
||||
//用户最后一次扣费时间
|
||||
if(maxTime != null){
|
||||
//转毫秒级时间戳
|
||||
long timestampMs = maxTime.getTime();
|
||||
voListAll = voListAll.stream().filter(vo -> Long.parseLong(vo.getDateTime()) - timestampMs > 6000)
|
||||
.sorted(Comparator.comparing(a -> Long.parseLong(a.getDateTime()), Comparator.nullsLast(Comparator.naturalOrder())))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
// 将扣费信息按照餐次分组
|
||||
Map<String, List<StRfidVo>> stringListMap = groupByMeal(voListAll, restId);
|
||||
StMiniMineBalanceVo stMiniMineBalanceVoOnline = new StMiniMineBalanceVo();
|
||||
|
||||
Reference in New Issue
Block a user