赋值问题

This commit is contained in:
2025-12-30 15:05:06 +08:00
parent 93b2309fcd
commit dc7146942b
10 changed files with 141 additions and 319 deletions

View File

@@ -1,11 +1,8 @@
package com.czg.task;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.czg.account.entity.ShopInfo;
import com.czg.account.entity.ShopUser;
import com.czg.account.service.ShopInfoService;
import com.czg.account.service.ShopUserService;
import com.czg.constant.TableValueConstant;
import com.czg.constants.SystemConstants;
import com.czg.exception.CzgException;
@@ -15,13 +12,11 @@ import com.czg.market.service.MkDistributionUserService;
import com.czg.market.service.OrderInfoService;
import com.czg.order.entity.OrderInfo;
import com.czg.service.market.enums.OrderStatusEnums;
import com.czg.utils.FunUtils;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal;
import java.time.LocalDateTime;

View File

@@ -9,7 +9,7 @@ import com.czg.order.entity.MqLog;
import com.czg.order.service.MqLogService;
import com.czg.order.service.OrderInfoCustomService;
import com.czg.order.service.OrderInfoRpcService;
import com.czg.service.order.utils.FunUtil;
import com.czg.service.RedisService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
@@ -31,7 +31,7 @@ public class OrderMqListener {
@Resource
private OrderInfoCustomService orderInfoCustomService;
@Resource
private FunUtil funUtil;
private RedisService redisService;
/**
* 订单上菜
@@ -44,7 +44,7 @@ public class OrderMqListener {
info = info.replace("UP_ORDER_DETAIL:", "");
log.info("接收到修改菜品状态mq, info: {}", info);
String finalInfo = info;
funUtil.debounce("UP_ORDER_DETAIL:" + info, 5, () -> {
redisService.debounce("UP_ORDER_DETAIL:" + info, 5, () -> {
orderInfoCustomService.updateOrderDetailStatus(Long.valueOf(finalInfo));
});

View File

@@ -6,8 +6,8 @@ import com.czg.config.RabbitConstants;
import com.czg.config.RedisCst;
import com.czg.order.entity.MqLog;
import com.czg.order.service.MqLogService;
import com.czg.service.RedisService;
import com.czg.service.order.print.PrinterHandler;
import com.czg.service.order.utils.FunUtil;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
@@ -27,7 +27,7 @@ public class PrintMqListener {
@Resource
private MqLogService mqLogService;
@Resource
private FunUtil funUtil;
private RedisService redisService;
@Lazy
@Resource
@@ -59,7 +59,7 @@ public class PrintMqListener {
throw new RuntimeException("订单打印失败未传递orderId");
}
Boolean printOrder = jsonObject.getBoolean("printOrder");
funUtil.runFunAndCheckKey(() -> {
redisService.runFunAndCheckKey(() -> {
printerHandler.handler(orderId, printOrder != null && !printOrder ? PrinterHandler.PrintTypeEnum.ONE : PrinterHandler.PrintTypeEnum.ONE_AND_ORDER);
return null;
}, RedisCst.getLockKey("orderPrint", orderId));

View File

@@ -6,14 +6,15 @@ import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @author GYJoker
@@ -650,4 +651,106 @@ public class RedisService {
}
return JSON.parseArray(jsonStr, type);
}
public static int retryCount = 5;
/**
* 执行任务并保证锁唯一
*
* @param supplier 业务逻辑
* @param lockKey Redis锁的Key
* @return 业务逻辑返回值
*/
public <T> T runFunAndCheckKey(Supplier<T> supplier, String lockKey) {
String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId());
try {
// 尝试获取锁,超时时间 5 秒,防止死锁
boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
int count = 0;
// 初始等待 10ms
int retryDelay = 10;
while (!lock) {
// 最多重试 10 次,大约 10 秒
if (count++ > 50) {
throw new RuntimeException("系统繁忙, 稍后再试");
}
Thread.sleep(retryDelay);
// 指数退避,最大等待 200ms
retryDelay = Math.min(retryDelay * 2, 200);
lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
}
// 执行任务
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("线程被中断", e);
} catch (Exception e) {
log.error("执行出错:{}", e.getMessage(), e);
throw e;
} finally {
// 释放锁(使用 Lua 脚本确保原子性)
unlock(lockKey, lockValue);
}
}
/**
* 使用 Lua 脚本确保释放锁的原子性
*
* @param lockKey 锁的 Key
* @param lockValue 当前线程的锁值
*/
private void unlock(String lockKey, String lockValue) {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(lockKey), lockValue);
}
public static <T, R> R runFunAndRetry(
Supplier<R> function,
Function<R, Boolean> check, Consumer<R> errFun) {
R result = function.get();
boolean flag = check.apply(result);
while (flag && retryCount-- > 0) {
result = function.get();
flag = check.apply(result);
}
if (flag) {
errFun.accept(result);
}
return result;
}
/**
* 防抖函数:在指定秒数内相同 Key 的任务只会执行一次
*
* @param key 防抖使用的 Redis Key
* @param seconds 防抖时间(秒)
* @param task 要执行的业务逻辑
* @return true 执行了任务false 在防抖期内被拦截
*/
public boolean debounce(String key, long seconds, Runnable task) {
try {
Boolean success = redisTemplate.opsForValue().setIfAbsent(
key, "1", seconds, TimeUnit.SECONDS
);
if (Boolean.TRUE.equals(success)) {
task.run();
return true;
}
return false;
} catch (Exception e) {
log.error("防抖函数执行失败 key={} err={}", key, e.getMessage(), e);
return false;
}
}
}

View File

@@ -377,7 +377,7 @@ public class OrderInfo implements Serializable {
if (!isPrintSuccess) {
newPrintStatusArray.add(printJson);
}
if (newPrintStatusArray.isEmpty()) {
if (!newPrintStatusArray.isEmpty()) {
printStatus = newPrintStatusArray.toJSONString();
}
}

View File

@@ -14,9 +14,9 @@ import com.czg.config.RedisCst;
import com.czg.constants.ParamCodeCst;
import com.czg.exception.CzgException;
import com.czg.resp.CzgResult;
import com.czg.service.RedisService;
import com.czg.service.account.mapper.CallQueueMapper;
import com.czg.service.account.mapper.CallTableMapper;
import com.czg.service.account.util.FunUtil;
import com.czg.service.account.util.WechatMiniMsgUtil;
import com.czg.system.dto.SysParamsDTO;
import com.czg.system.service.SysParamsService;
@@ -51,10 +51,10 @@ public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable
@Resource
private ShopUserService shopUserService;
@Resource
private FunUtil funUtil;
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private RedisService redisService;
@Resource
private ShopInfoService shopInfoService;
@Resource
private CallConfigService callConfigService;
@@ -228,13 +228,13 @@ public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable
}
private String getCallNumber(Long shopId, CallTable callTable) {
return funUtil.runFunAndCheckKey(() -> {
return redisService.runFunAndCheckKey(() -> {
String callNumKey = RedisCst.getTableCallNumKey(shopId, callTable.getId());
String value = stringRedisTemplate.opsForValue().get(callNumKey);
AtomicReference<String> newVal = new AtomicReference<>("");
// 初始化
if (StrUtil.isBlank(value)) {
Boolean setFlag = FunUtil.runFunAndRetry(() -> stringRedisTemplate.opsForValue().setIfAbsent(callNumKey, callTable.getStart().toString()), flag -> !flag,
Boolean setFlag = RedisService.runFunAndRetry(() -> stringRedisTemplate.opsForValue().setIfAbsent(callNumKey, callTable.getStart().toString()), flag -> !flag,
_ -> newVal.set(stringRedisTemplate.opsForValue().get(callNumKey)));
if (setFlag) {

View File

@@ -22,7 +22,6 @@ import com.czg.market.vo.InviteUserVO;
import com.czg.market.vo.MemberConfigVO;
import com.czg.order.entity.OrderInfo;
import com.czg.service.account.mapper.ShopUserMapper;
import com.czg.service.account.util.FunUtil;
import com.czg.utils.FunUtils;
import com.czg.utils.PageUtil;
import com.github.pagehelper.PageHelper;

View File

@@ -1,100 +0,0 @@
package com.czg.service.account.util;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @author Administrator
*/
@Slf4j
@Component
public class FunUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
public static int retryCount = 5;
/**
* 执行任务并保证锁唯一
* @param supplier 业务逻辑
* @param lockKey Redis锁的Key
* @return 业务逻辑返回值
*/
public <T> T runFunAndCheckKey(Supplier<T> supplier, String lockKey) {
String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId());
try {
// 尝试获取锁,超时时间 5 秒,防止死锁
boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
int count = 0;
// 初始等待 10ms
int retryDelay = 10;
while (!lock) {
// 最多重试 10 次,大约 10 秒
if (count++ > 50) {
throw new RuntimeException("系统繁忙, 稍后再试");
}
Thread.sleep(retryDelay);
// 指数退避,最大等待 200ms
retryDelay = Math.min(retryDelay * 2, 200);
lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
}
// 执行任务
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("线程被中断", e);
} catch (Exception e) {
log.error("执行出错:{}", e.getMessage(), e);
throw e;
} finally {
// 释放锁(使用 Lua 脚本确保原子性)
unlock(lockKey, lockValue);
}
}
/**
* 使用 Lua 脚本确保释放锁的原子性
* @param lockKey 锁的 Key
* @param lockValue 当前线程的锁值
*/
private void unlock(String lockKey, String lockValue) {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(lockKey), lockValue);
}
public static <T, R> R runFunAndRetry(
Supplier<R> function,
Function<R, Boolean> check, Consumer<R> errFun) {
log.info("工具类开始执行函数");
R result = function.get();
boolean flag = check.apply(result);
log.info("执行结果: {}", result);
while (flag && retryCount-- > 0) {
log.info("执行函数失败, 剩余尝试次数{}", retryCount);
result = function.get();
log.info("执行结果: {}", result);
flag = check.apply(result);
}
if (flag) {
errFun.accept(result);
}
return result;
}
}

View File

@@ -4,9 +4,9 @@ import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.czg.account.entity.PrintMachine;
import com.czg.config.RedisCst;
import com.czg.market.service.OrderInfoService;
import com.czg.order.entity.OrderInfo;
import com.czg.order.entity.PrintMachineLog;
@@ -247,18 +247,8 @@ public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMappe
} finally {
//仅当是最后一次任务时,才执行更新操作
if (isLastTask) {
redisService.del("order:print:" + orderId);
super.updateById(entity);
OrderInfo orderInfo = orderInfoService.getOne(query().select(OrderInfo::getPrintStatus).eq(OrderInfo::getId, orderId));
if (orderInfo == null) {
orderInfo = new OrderInfo();
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", config.getId());
jsonObject.put("name", config.getName());
jsonObject.put("time", LocalDateTime.now());
orderInfo.upPrintStatus(jsonObject, isPrintSuccess);
orderInfoService.update(orderInfo, query().eq(OrderInfo::getId, orderId));
updateOrderEntity(orderId, config, isPrintSuccess);
}
}
};
@@ -278,6 +268,26 @@ public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMappe
registerShutdownHookOnce();
}
/**
* 更新状态
*/
private void updateOrderEntity(Long orderId, PrintMachine config, boolean isPrintSuccess) {
redisService.runFunAndCheckKey(() -> {
OrderInfo orderInfo = orderInfoService.getOne(query().select(OrderInfo::getPrintStatus).eq(OrderInfo::getId, orderId));
if (orderInfo == null) {
orderInfo = new OrderInfo();
}
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", config.getId());
jsonObject.put("name", config.getName());
jsonObject.put("time", LocalDateTime.now());
orderInfo.upPrintStatus(jsonObject, isPrintSuccess);
orderInfoService.update(orderInfo, query().eq(OrderInfo::getId, orderId));
return orderInfo;
}, RedisCst.getLockKey("UP_ORDER_PRINT", orderId));
redisService.del("order:print:" + orderId);
}
/**
* 统一更新打印日志实体
*
@@ -300,66 +310,6 @@ public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMappe
}
}
// public void checkPrintStatus(PrintMachine config, PrintMachineLog entity) {
// // 云想印
// if ("云想印".equals(config.getContentType())) {
// String jsonStr = yxyPrinter.checkPrintStatus(config.getAddress(), entity.getTaskId());
// log.info("云想印打印状态查询结果:{}", jsonStr);
// JSONObject resp = JSONObject.parseObject(jsonStr);
// int code = resp.getIntValue("code");
// if (code == 0) {
// JSONObject data = resp.getJSONObject("data");
// boolean status = data.containsKey("status");
// if (!status) {
// return;
// }
// boolean success = data.getBooleanValue("status", false);
// if (entity.getFailFlag() == 0 && success) {
// entity.setFailFlag(0);
// entity.setRespMsg("打印成功");
// entity.setPrintTime(entity.getCreateTime());
// } else if (entity.getFailFlag() == 1 && success) {
// entity.setFailFlag(0);
// entity.setPrintTime(DateUtil.date().toLocalDateTime());
// entity.setRespMsg("打印成功");
// // 如果设备在线 and 休眠5秒后查询结果是未打印即视为设备已离线云端3分钟后才会同步到离线信息
// } else if (entity.getFailFlag() == 0 && !success) {
// entity.setFailFlag(1);
// entity.setPrintTime(null);
// entity.setRespMsg("0_离线(设备上线后自动补打)");
// } else {
// entity.setFailFlag(1);
// entity.setPrintTime(null);
// entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", entity.getRespMsg()));
// }
// }
// // 飞鹅云打印机
// } else if ("飞鹅".equals(config.getContentType())) {
// Boolean success = feiPrinter.checkFPrintStatus(entity.getTaskId());
// if (success == null) {
// entity.setFailFlag(1);
// entity.setRespMsg("打印失败,未知错误");
// } else if (success) {
// entity.setFailFlag(0);
// entity.setPrintTime(DateUtil.date().toLocalDateTime());
// entity.setRespMsg("打印成功");
// } else {
// String msg = feiPrinter.checkOnline(entity.getAddress());
// if (msg.indexOf("在线,工作状态正常") > 0) {
// entity.setFailFlag(0);
// entity.setPrintTime(DateUtil.date().toLocalDateTime());
// entity.setRespMsg("打印成功");
// } else {
// entity.setFailFlag(1);
// entity.setPrintTime(null);
// entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", msg));
// }
// }
// }
// super.updateById(entity);
// }
// 静态标识,确保关闭钩子仅注册一次
private static volatile boolean shutdownHookRegistered = false;
// 锁对象,保证线程安全

View File

@@ -1,125 +0,0 @@
package com.czg.service.order.utils;
import cn.hutool.core.lang.func.Func0;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* @author Administrator
*/
@Slf4j
@Component
public class FunUtil {
@Resource
private RedisTemplate<String, Object> redisTemplate;
public static int retryCount = 5;
/**
* 执行任务并保证锁唯一
* @param supplier 业务逻辑
* @param lockKey Redis锁的Key
* @return 业务逻辑返回值
*/
public <T> T runFunAndCheckKey(Supplier<T> supplier, String lockKey) {
String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId());
try {
// 尝试获取锁,超时时间 5 秒,防止死锁
boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
int count = 0;
// 初始等待 10ms
int retryDelay = 10;
while (!lock) {
// 最多重试 10 次,大约 10 秒
if (count++ > 50) {
throw new RuntimeException("系统繁忙, 稍后再试");
}
Thread.sleep(retryDelay);
// 指数退避,最大等待 200ms
retryDelay = Math.min(retryDelay * 2, 200);
lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
}
// 执行任务
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException("线程被中断", e);
} catch (Exception e) {
log.error("执行出错:{}", e.getMessage(), e);
throw e;
} finally {
// 释放锁(使用 Lua 脚本确保原子性)
unlock(lockKey, lockValue);
}
}
/**
* 使用 Lua 脚本确保释放锁的原子性
* @param lockKey 锁的 Key
* @param lockValue 当前线程的锁值
*/
private void unlock(String lockKey, String lockValue) {
String luaScript =
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
"return redis.call('del', KEYS[1]) " +
"else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),
Collections.singletonList(lockKey), lockValue);
}
public static <T, R> R runFunAndRetry(
Supplier<R> function,
Function<R, Boolean> check, Consumer<R> errFun) {
log.info("工具类开始执行函数");
R result = function.get();
boolean flag = check.apply(result);
log.info("执行结果: {}", result);
while (flag && retryCount-- > 0) {
log.info("执行函数失败, 剩余尝试次数{}", retryCount);
result = function.get();
log.info("执行结果: {}", result);
flag = check.apply(result);
}
if (flag) {
errFun.accept(result);
}
return result;
}
/**
* 防抖函数:在指定秒数内相同 Key 的任务只会执行一次
* @param key 防抖使用的 Redis Key
* @param seconds 防抖时间(秒)
* @param task 要执行的业务逻辑
* @return true 执行了任务false 在防抖期内被拦截
*/
public boolean debounce(String key, long seconds, Runnable task) {
try {
Boolean success = redisTemplate.opsForValue().setIfAbsent(
key, "1", seconds, TimeUnit.SECONDS
);
if (Boolean.TRUE.equals(success)) {
task.run();
return true;
}
return false;
} catch (Exception e) {
log.error("防抖函数执行失败 key={} err={}", key, e.getMessage(), e);
return false;
}
}
}