5 Commits
test ... prod

Author SHA1 Message Date
cb18aa5670 退款问题 2025-12-26 17:09:34 +08:00
da3447cd0b 异步执行退款额外问题 2025-12-26 16:21:59 +08:00
9e946443ec sql问题 2025-12-26 16:18:17 +08:00
71ffdede19 分销退款问题 2025-12-26 16:02:31 +08:00
353404dde4 显式抛出 2025-12-26 14:53:38 +08:00
40 changed files with 922 additions and 899 deletions

View File

@@ -4,18 +4,14 @@ import com.czg.account.dto.menu.MenuAddDTO;
import com.czg.account.dto.menu.MenuDelDTO; import com.czg.account.dto.menu.MenuDelDTO;
import com.czg.account.dto.menu.MenuEditDTO; import com.czg.account.dto.menu.MenuEditDTO;
import com.czg.account.entity.CashMenu; import com.czg.account.entity.CashMenu;
import com.czg.account.entity.QuickMenu;
import com.czg.account.entity.SysMenu; import com.czg.account.entity.SysMenu;
import com.czg.account.service.QuickMenuService;
import com.czg.account.service.SysMenuService; import com.czg.account.service.SysMenuService;
import com.czg.account.vo.MenuVO; import com.czg.account.vo.MenuVO;
import com.czg.annotation.SaAdminCheckPermission; import com.czg.annotation.SaAdminCheckPermission;
import com.czg.annotation.SaAdminCheckRole; import com.czg.annotation.SaAdminCheckRole;
import com.czg.resp.CzgResult; import com.czg.resp.CzgResult;
import com.czg.sa.StpKit; import com.czg.sa.StpKit;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -23,7 +19,6 @@ import java.util.List;
/** /**
* 菜单管理 * 菜单管理
*
* @author zs * @author zs
*/ */
@RestController @RestController
@@ -32,12 +27,9 @@ public class MenuController {
@Resource @Resource
private SysMenuService menuService; private SysMenuService menuService;
@Resource
private QuickMenuService quickMenuService;
/** /**
* 获取当前用户菜单列表 * 获取当前用户菜单列表
*
* @return 菜单结构 * @return 菜单结构
*/ */
@GetMapping @GetMapping
@@ -48,7 +40,6 @@ public class MenuController {
/** /**
* 收银机菜单 * 收银机菜单
*
* @return 所有菜单 * @return 所有菜单
*/ */
@GetMapping("/list/cash") @GetMapping("/list/cash")
@@ -58,7 +49,6 @@ public class MenuController {
/** /**
* 获取所有菜单 * 获取所有菜单
*
* @return 菜单结构 * @return 菜单结构
*/ */
@SaAdminCheckPermission(parentName = "菜单管理", value = "menu:list", name = "菜单列表") @SaAdminCheckPermission(parentName = "菜单管理", value = "menu:list", name = "菜单列表")
@@ -72,7 +62,6 @@ public class MenuController {
/** /**
* 菜单详情 * 菜单详情
*
* @return 菜单结构 * @return 菜单结构
*/ */
@SaAdminCheckRole("管理员") @SaAdminCheckRole("管理员")
@@ -84,7 +73,6 @@ public class MenuController {
/** /**
* 菜单添加 * 菜单添加
*
* @return 是否成功 * @return 是否成功
*/ */
@SaAdminCheckRole("管理员") @SaAdminCheckRole("管理员")
@@ -96,7 +84,6 @@ public class MenuController {
/** /**
* 菜单修改 * 菜单修改
*
* @return 是否成功 * @return 是否成功
*/ */
@SaAdminCheckRole("管理员") @SaAdminCheckRole("管理员")
@@ -108,15 +95,12 @@ public class MenuController {
/** /**
* 菜单删除 * 菜单删除
*
* @return 是否成功 * @return 是否成功
*/ */
@SaAdminCheckRole("管理员") @SaAdminCheckRole("管理员")
@SaAdminCheckPermission(parentName = "菜单管理", value = "menu:del", name = "菜单删除") @SaAdminCheckPermission(parentName = "菜单管理", value = "menu:del", name = "菜单删除")
@DeleteMapping() @DeleteMapping()
@Transactional
public CzgResult<Boolean> edit(@RequestBody @Validated MenuDelDTO menuDelDTO) { public CzgResult<Boolean> edit(@RequestBody @Validated MenuDelDTO menuDelDTO) {
quickMenuService.remove(QueryWrapper.create().eq(QuickMenu::getMenuId, menuDelDTO.getId()));
return CzgResult.success(menuService.removeById(menuDelDTO.getId())); return CzgResult.success(menuService.removeById(menuDelDTO.getId()));
} }

View File

@@ -1,67 +0,0 @@
package com.czg.controller.admin;
import cn.hutool.core.collection.CollUtil;
import com.czg.account.entity.QuickMenu;
import com.czg.account.service.QuickMenuService;
import com.czg.annotation.SaAdminCheckPermission;
import com.czg.resp.CzgResult;
import com.czg.sa.StpKit;
import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Set;
/**
* 悬浮窗/快捷菜单
*
* @author ww
* @description
*/
@RestController
@RequestMapping("/admin/quick")
@Slf4j
public class QuickMenuController {
@Resource
private QuickMenuService quickMenuService;
@SaAdminCheckPermission(parentName = "悬浮窗", value = "quick:list", name = "悬浮窗-列表")
@GetMapping
public CzgResult<List<QuickMenu>> getQuickList(Integer status) {
List<QuickMenu> list = quickMenuService.list(QueryWrapper.create()
.eq(QuickMenu::getShopId, StpKit.USER.getShopId())
.eq(QuickMenu::getStatus, status)
.orderBy(QuickMenu::getSort, true));
if (CollUtil.isEmpty(list)) {
list = quickMenuService.list(QueryWrapper.create()
.eq(QuickMenu::getShopId, 1)
.eq(QuickMenu::getStatus, status)
.orderBy(QuickMenu::getSort, true));
}
return CzgResult.success(list);
}
@SaAdminCheckPermission(parentName = "悬浮窗", value = "quick:list", name = "悬浮窗-新增")
@PostMapping
public CzgResult<Boolean> saveQuick(@RequestBody @Validated QuickMenu quickMenu) {
quickMenu.setShopId(StpKit.USER.getShopId());
return CzgResult.success(quickMenuService.save(quickMenu));
}
@SaAdminCheckPermission(parentName = "悬浮窗", value = "quick:list", name = "悬浮窗-修改")
@PutMapping
public CzgResult<Boolean> updateQuick(@RequestBody @Validated QuickMenu quickMenu) {
return CzgResult.success(quickMenuService.update(quickMenu, QueryWrapper.create()
.eq(QuickMenu::getId, quickMenu.getId()).eq(QuickMenu::getShopId, StpKit.USER.getShopId())));
}
@SaAdminCheckPermission(parentName = "悬浮窗", value = "quick:list", name = "悬浮窗-删除")
@DeleteMapping
public CzgResult<Boolean> deleteQuick(@RequestBody Set<Long> ids) {
return CzgResult.success(quickMenuService.remove(QueryWrapper.create().in(QuickMenu::getId, ids).eq(QuickMenu::getShopId, StpKit.USER.getShopId())));
}
}

View File

@@ -1,8 +1,11 @@
package com.czg.task; package com.czg.task;
import cn.hutool.core.collection.CollUtil; import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.DateUtil;
import com.czg.account.entity.ShopInfo; import com.czg.account.entity.ShopInfo;
import com.czg.account.entity.ShopUser;
import com.czg.account.service.ShopInfoService; import com.czg.account.service.ShopInfoService;
import com.czg.account.service.ShopUserService;
import com.czg.constant.TableValueConstant; import com.czg.constant.TableValueConstant;
import com.czg.constants.SystemConstants; import com.czg.constants.SystemConstants;
import com.czg.exception.CzgException; import com.czg.exception.CzgException;
@@ -12,11 +15,13 @@ import com.czg.market.service.MkDistributionUserService;
import com.czg.market.service.OrderInfoService; import com.czg.market.service.OrderInfoService;
import com.czg.order.entity.OrderInfo; import com.czg.order.entity.OrderInfo;
import com.czg.service.market.enums.OrderStatusEnums; import com.czg.service.market.enums.OrderStatusEnums;
import com.czg.utils.FunUtils;
import com.mybatisflex.core.query.QueryWrapper; import com.mybatisflex.core.query.QueryWrapper;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference; import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; 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.MqLogService;
import com.czg.order.service.OrderInfoCustomService; import com.czg.order.service.OrderInfoCustomService;
import com.czg.order.service.OrderInfoRpcService; import com.czg.order.service.OrderInfoRpcService;
import com.czg.service.RedisService; import com.czg.service.order.utils.FunUtil;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.annotation.RabbitListener;
@@ -31,7 +31,7 @@ public class OrderMqListener {
@Resource @Resource
private OrderInfoCustomService orderInfoCustomService; private OrderInfoCustomService orderInfoCustomService;
@Resource @Resource
private RedisService redisService; private FunUtil funUtil;
/** /**
* 订单上菜 * 订单上菜
@@ -44,7 +44,7 @@ public class OrderMqListener {
info = info.replace("UP_ORDER_DETAIL:", ""); info = info.replace("UP_ORDER_DETAIL:", "");
log.info("接收到修改菜品状态mq, info: {}", info); log.info("接收到修改菜品状态mq, info: {}", info);
String finalInfo = info; String finalInfo = info;
redisService.debounce("UP_ORDER_DETAIL:" + info, 5, () -> { funUtil.debounce("UP_ORDER_DETAIL:" + info, 5, () -> {
orderInfoCustomService.updateOrderDetailStatus(Long.valueOf(finalInfo)); orderInfoCustomService.updateOrderDetailStatus(Long.valueOf(finalInfo));
}); });

View File

@@ -6,12 +6,13 @@ import com.czg.config.RabbitConstants;
import com.czg.config.RedisCst; import com.czg.config.RedisCst;
import com.czg.order.entity.MqLog; import com.czg.order.entity.MqLog;
import com.czg.order.service.MqLogService; import com.czg.order.service.MqLogService;
import com.czg.service.RedisService;
import com.czg.service.order.print.PrinterHandler; import com.czg.service.order.print.PrinterHandler;
import com.czg.service.order.utils.FunUtil;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.function.Consumer; import java.util.function.Consumer;
@@ -27,8 +28,11 @@ public class PrintMqListener {
@Resource @Resource
private MqLogService mqLogService; private MqLogService mqLogService;
@Resource @Resource
private RedisService redisService; private FunUtil funUtil;
// 注入自定义线程池(建议单独配置,避免使用默认线程池)
@Resource
private ThreadPoolTaskExecutor asyncExecutor;
@Lazy @Lazy
@Resource @Resource
private PrinterHandler printerHandler; private PrinterHandler printerHandler;
@@ -59,11 +63,38 @@ public class PrintMqListener {
throw new RuntimeException("订单打印失败未传递orderId"); throw new RuntimeException("订单打印失败未传递orderId");
} }
Boolean printOrder = jsonObject.getBoolean("printOrder"); Boolean printOrder = jsonObject.getBoolean("printOrder");
redisService.runFunAndCheckKey(() -> { funUtil.runFunAndCheckKey(() -> {
printerHandler.handler(orderId, printOrder != null && !printOrder ? PrinterHandler.PrintTypeEnum.ONE : PrinterHandler.PrintTypeEnum.ONE_AND_ORDER); printerHandler.handler(orderId, printOrder != null && !printOrder ? PrinterHandler.PrintTypeEnum.ONE : PrinterHandler.PrintTypeEnum.ONE_AND_ORDER);
return null; return null;
}, RedisCst.getLockKey("orderPrint", orderId)); }, RedisCst.getLockKey("orderPrint", orderId));
}); });
// // 使用异步线程池执行延迟任务,不阻塞当前消费者线程
// CompletableFuture.runAsync(() -> {
// try {
// // 延迟3秒处理
// TimeUnit.SECONDS.sleep(3);
// // 执行核心打印逻辑
// invokeFun("orderPrint", "java.order", req, (data) -> {
// JSONObject jsonObject = JSONObject.parseObject(data);
// String orderId = jsonObject.getString("orderId");
// if (orderId == null) {
// throw new RuntimeException("订单打印失败未传递orderId");
// }
// Boolean printOrder = jsonObject.getBoolean("printOrder");
// funUtil.runFunAndCheckKey(() -> {
// printerHandler.handler(orderId, printOrder != null && !printOrder ? PrinterHandler.PrintTypeEnum.ONE : PrinterHandler.PrintTypeEnum.ONE_AND_ORDER);
// return null;
// }, RedisCst.getLockKey("orderPrint", orderId));
// });
// } catch (InterruptedException e) {
// Thread.currentThread().interrupt();
// // 记录中断日志
// log.warn("打印任务被中断req:{}", req, e);
// } catch (Exception e) {
// // 记录业务异常日志
// log.error("打印任务处理失败req:{}", req, e);
// }
// }, asyncExecutor);
} }
/** /**
@@ -75,7 +106,7 @@ public class PrintMqListener {
} }
/** /**
* 叫号打印 * 交班打印
*/ */
@RabbitListener(queues = {"${spring.profiles.active}-" + RabbitConstants.Queue.CALL_TABLE_PRINT_QUEUE}) @RabbitListener(queues = {"${spring.profiles.active}-" + RabbitConstants.Queue.CALL_TABLE_PRINT_QUEUE})
public void callTablePrint(String id) { public void callTablePrint(String id) {

View File

@@ -25,23 +25,7 @@ spring:
port: 5672 port: 5672
username: chaozg username: chaozg
password: chaozg123 password: chaozg123
# 关键优化解决MissedHeartbeatException 心跳超时问题
connection-timeout: 10000 # 连接超时时间10秒避免连接建立过慢
requested-heartbeat: 30 # 心跳间隔调整为30秒原60秒过长降低超时概率过短易误触发
# 自动重连配置Spring AMQP 自带,关键兜底)
publisher-returns: true
template:
retry:
enabled: true # 开启消息发送重试
max-attempts: 3 # 最大重试次数
initial-interval: 3000 # 首次重试间隔2秒
multiplier: 1.5 # 重试间隔倍增因子
listener:
simple:
retry:
enabled: true # 开启消费者重试
max-attempts: 3 # 消费者最大重试次数
acknowledge-mode: auto # 确认模式可根据业务改为manual
dubbo: dubbo:
application: application:
name: order-server name: order-server

View File

@@ -54,7 +54,7 @@ public class FilteredNacosRegistry extends NacosRegistry {
public void register(URL url) { public void register(URL url) {
// 1. 获取原始注册的方法列表 // 1. 获取原始注册的方法列表
String originalMethods = url.getParameter("methods"); String originalMethods = url.getParameter("methods");
// log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), originalMethods); log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), originalMethods);
if (originalMethods != null && !originalMethods.isEmpty()) { if (originalMethods != null && !originalMethods.isEmpty()) {
// 2. 过滤黑名单中的方法名 // 2. 过滤黑名单中的方法名
List<String> filteredMethods = Arrays.stream(originalMethods.split(",")) List<String> filteredMethods = Arrays.stream(originalMethods.split(","))
@@ -67,12 +67,12 @@ public class FilteredNacosRegistry extends NacosRegistry {
// 3. 处理过滤后的结果 // 3. 处理过滤后的结果
if (filteredMethods.isEmpty()) { if (filteredMethods.isEmpty()) {
// 若所有方法都被过滤,直接终止注册(可选:根据业务决定是否保留服务注册) // 若所有方法都被过滤,直接终止注册(可选:根据业务决定是否保留服务注册)
// log.info("【过滤提示】服务 {} 所有方法均被过滤,终止注册", url.getServiceInterface()); log.info("【过滤提示】服务 {} 所有方法均被过滤,终止注册", url.getServiceInterface());
return; return;
} else { } else {
// 替换 URL 中的 methods 参数为过滤后的列表 // 替换 URL 中的 methods 参数为过滤后的列表
url = url.addParameter("methods", String.join(",", filteredMethods)); url = url.addParameter("methods", String.join(",", filteredMethods));
// log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), filteredMethods); log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), filteredMethods);
} }
} }

View File

@@ -6,15 +6,14 @@ import jakarta.annotation.Resource;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import java.util.*; import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/** /**
* @author GYJoker * @author GYJoker
@@ -651,106 +650,4 @@ public class RedisService {
} }
return JSON.parseArray(jsonStr, type); 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

@@ -285,13 +285,4 @@ public class ShopInfoEditDTO {
*/ */
private Integer isCountStick; private Integer isCountStick;
/**
* 企业id
*/
private String weworkId;
/**
* 企业接入链接
*/
private String weworkUrl;
} }

View File

@@ -1,69 +0,0 @@
package com.czg.account.entity;
import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id;
import com.mybatisflex.annotation.KeyType;
import com.mybatisflex.annotation.Table;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.io.Serial;
import jakarta.validation.constraints.NotNull;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 悬浮窗配置 实体类。
*
* @author ww
* @since 2025-12-29
*/
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Table("tb_quick_menu")
public class QuickMenu implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@Id(keyType = KeyType.Auto)
private Integer id;
/**
* 店铺Id
*/
private Long shopId;
/**
* 菜单图标
*/
private String url;
/**
* 菜单Id
*/
@NotNull(message = "关联菜单不能为空")
private Long menuId;
/**
* 排序
*/
private Integer sort;
/**
* 状态 1-启用 0-禁用
*/
private Integer status;
@Column(onInsertValue = "now()")
private LocalDateTime createTime;
@Column(onInsertValue = "now()", onUpdateValue = "now()")
private LocalDateTime updateTime;
}

View File

@@ -135,13 +135,5 @@ public class ShopConfig implements Serializable {
private String dingAppKey; private String dingAppKey;
private String dingAppSecret; private String dingAppSecret;
/**
* 企业id
*/
private String weworkId;
/**
* 企业接入链接
*/
private String weworkUrl;
} }

View File

@@ -363,15 +363,4 @@ public class ShopInfo implements Serializable {
*/ */
private BigDecimal amount; private BigDecimal amount;
/**
* 企业id
*/
@Column(ignore = true)
private String weworkId;
/**
* 企业接入链接
*/
@Column(ignore = true)
private String weworkUrl;
} }

View File

@@ -1,14 +0,0 @@
package com.czg.account.service;
import com.mybatisflex.core.service.IService;
import com.czg.account.entity.QuickMenu;
/**
* 悬浮窗配置 服务层。
*
* @author ww
* @since 2025-12-29
*/
public interface QuickMenuService extends IService<QuickMenu> {
}

View File

@@ -1,6 +1,7 @@
package com.czg.market.service; package com.czg.market.service;
import com.czg.enums.ShopUserFlowBizEnum; import com.czg.enums.ShopUserFlowBizEnum;
import com.czg.exception.CzgException;
import com.czg.market.dto.MkShopRechargeDTO; import com.czg.market.dto.MkShopRechargeDTO;
import com.czg.market.vo.MkShopRechargeShopListVO; import com.czg.market.vo.MkShopRechargeShopListVO;
import com.czg.market.vo.MkShopRechargeVO; import com.czg.market.vo.MkShopRechargeVO;
@@ -21,7 +22,7 @@ import java.util.List;
*/ */
public interface MkShopRechargeService extends IService<MkShopRecharge> { public interface MkShopRechargeService extends IService<MkShopRecharge> {
MkShopRechargeVO detail(Long shopId); MkShopRechargeVO detail(Long shopId) throws CzgException;
MkShopRechargeVO detailApp(Long shopId); MkShopRechargeVO detailApp(Long shopId);

View File

@@ -0,0 +1,267 @@
package com.czg.order.dto;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import com.alibaba.fastjson2.annotation.JSONField;
import java.io.Serial;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 订单表 实体类。
*
* @author ww
* @since 2025-02-13
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class OrderInfoDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
private Long id;
/**
* 订单编号
* pc 收银机客户端 PC+雪花ID
* wechat 微信小程序 WX+雪花ID
* alipay 支付宝小程序 ALI+雪花ID
* admin-pc PC管理端 WEB+雪花ID
* admin-app APP管理端 APP+雪花ID
*/
private String orderNo;
/**
* 店铺Id
*/
private Long shopId;
/**
* 用户Id user_info表的id
*/
private Long userId;
/**
* 退单金额
*/
private BigDecimal refundAmount;
/**
* 订单原金额 不含折扣价格
*/
private BigDecimal originAmount;
/**
* 抹零金额 减免多少钱
*/
private BigDecimal roundAmount;
/**
* 优惠总金额
*/
private BigDecimal discountAllAmount;
/**
* 订单金额 (扣除各类折扣)
*/
private BigDecimal orderAmount;
/**
* 实际支付金额
*/
private BigDecimal payAmount;
/**
* 积分抵扣金额
*/
private BigDecimal pointsDiscountAmount;
/**
* 使用的积分数量
*/
private Integer pointsNum;
/**
* 商品优惠券抵扣金额
*/
private BigDecimal productCouponDiscountAmount;
/**
* 用户使用的卡券
*/
private String couponInfoList;
/**
* 满减活动抵扣金额
*/
private BigDecimal discountActAmount;
/**
* 折扣金额
*/
private BigDecimal discountAmount;
// /**
// * 折扣比例
// */
// private BigDecimal discountRatio;
/**
* 打包费
*/
private BigDecimal packFee;
/**
* 台桌Id
*/
private String tableCode;
/**
* 台桌名称
*/
private String tableName;
/**
* 订单类型-
* cash收银(除小程序以外 都属于收银)
* miniapp小程序
*/
private String orderType;
/**
* 平台类型 pc 收银机客户端 wechat 微信小程序 alipay 支付宝小程序 admin-pc PC管理端 admin-app APP管理端
*/
private String platformType;
/**
* 用餐模式 堂食 dine-in 外带 take-out 外卖 take-away
*/
private String dineMode;
/**
* 支付模式:
* 后付费 after-pay
* 先付费 before-pay
* 无桌码 no-table
*/
private String payMode;
/**
* 支付类型
* 主扫 main-scan
* 被扫 back-scan
* 微信小程序 wechat-mini
* 支付宝小程序 alipay-mini
* 会员支付 vip-pay
* 现金支付 cash-pay
*/
private String payType;
/**
* 状态: unpaid-待支付;in-production 制作中;wait_out 待取餐;;done-订单完成;refunding-申请退单;refund-退单;part_refund 部分退单;cancelled-取消订单
*/
private String status;
/**
* 折扣信息 json
*/
private String discountInfo;
/**
* 限时折扣信息 json
*/
private String limitRate;
/**
* 是否支持退款1支持退单 0不支持退单
*/
private Integer refundAble;
/**
* 支付时间
*/
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime paidTime;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime createTime;
@JSONField(format = "yyyy-MM-dd HH:mm:ss")
private LocalDateTime updateTime;
/**
* 支付订单号
* tb_order_payment.id
* tb_shop_user_flow.id
*/
private Long payOrderId;
/**
* 交易日期
*/
private String tradeDay;
/**
* 备注
*/
private String remark;
/**
* 取餐码
*/
private String takeCode;
/**
* 员工id
*/
private Long staffId;
/**
* 当前订单下单次数
*/
private Integer placeNum;
/**
* 用餐人数
*/
private Integer seatNum;
/**
* 餐位费
*/
private BigDecimal seatAmount;
/**
* 退款备注
*/
private String refundRemark;
/**
* 是否使用了霸王餐
*/
private Integer isFreeDine;
/**
* 是否等叫 0 否 1 等叫
*/
private Integer isWaitCall;
/**
* 挂账人id
*/
private Long creditBuyerId;
/**
* 是否回收站 0-否1回收站
*/
private Integer isDel;
private String failMsg;
}

View File

@@ -1,8 +1,6 @@
package com.czg.order.entity; package com.czg.order.entity;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.czg.order.dto.LimitRateDTO; import com.czg.order.dto.LimitRateDTO;
import com.mybatisflex.annotation.Column; import com.mybatisflex.annotation.Column;
import com.mybatisflex.annotation.Id; import com.mybatisflex.annotation.Id;
@@ -300,10 +298,8 @@ public class OrderInfo implements Serializable {
private Integer isDel; private Integer isDel;
private String failMsg; private String failMsg;
/**
* 打印状态 Json格式
*/
private String printStatus;
public String getRefundRemark() { public String getRefundRemark() {
@@ -346,41 +342,4 @@ public class OrderInfo implements Serializable {
// 如果需要加上抹零金额,可以取消下面这行注释 // 如果需要加上抹零金额,可以取消下面这行注释
// .add(this.getRoundAmount() != null ? this.getRoundAmount() : BigDecimal.ZERO); // .add(this.getRoundAmount() != null ? this.getRoundAmount() : BigDecimal.ZERO);
} }
private JSONArray getPrintStatusAsArray() {
if (StrUtil.isBlank(printStatus)) {
return new JSONArray();
}
try {
return JSONArray.parseArray(printStatus.trim());
} catch (Exception e) {
return new JSONArray();
}
}
public void upPrintStatus(JSONObject printJson, boolean isPrintSuccess) {
String currentDeviceId = printJson.getString("id");
JSONArray oldPrintStatusArray = getPrintStatusAsArray();
// 3. 初始化新的打印状态JSON数组用于存储处理后的结果
JSONArray newPrintStatusArray = new JSONArray();
// 场景1打印成功 - 移除原有数组中与当前设备ID一致的记录保留其余记录
if (oldPrintStatusArray != null && !oldPrintStatusArray.isEmpty()) {
for (int i = 0; i < oldPrintStatusArray.size(); i++) {
JSONObject deviceObj = oldPrintStatusArray.getJSONObject(i);
String deviceId = deviceObj.getString("id");
// 仅保留非当前设备ID的记录
if (currentDeviceId != null && !currentDeviceId.equals(deviceId)) {
newPrintStatusArray.add(deviceObj);
}
}
}
if (!isPrintSuccess) {
newPrintStatusArray.add(printJson);
}
if (!newPrintStatusArray.isEmpty()) {
printStatus = newPrintStatusArray.toJSONString();
} else {
printStatus = "";
}
}
} }

View File

@@ -81,7 +81,7 @@ public class OrderPayment implements Serializable {
*/ */
private String tradeNumber; private String tradeNumber;
@Column(onUpdateValue = "now()") // @Column(onUpdateValue = "now()")
private LocalDateTime payTime; private LocalDateTime payTime;
/** /**

View File

@@ -11,8 +11,6 @@ import com.czg.order.entity.PrintMachineLog;
* @since 2025-03-11 * @since 2025-03-11
*/ */
public interface PrintMachineLogService extends IService<PrintMachineLog> { public interface PrintMachineLogService extends IService<PrintMachineLog> {
void save(Long orderId, PrintMachine config, String bizType, String printContent, String respJson); void save(PrintMachine config, String bizType, String printContent, Object respJson);
void save(PrintMachine config, String bizType, String printContent, String respJson);
} }

View File

@@ -42,5 +42,6 @@ public class OrderDetailSmallVO implements Serializable {
private LocalDateTime dishOutTime; private LocalDateTime dishOutTime;
private LocalDateTime foodServeTime; private LocalDateTime foodServeTime;
private Integer isTemporary; private Integer isTemporary;
} }

View File

@@ -133,11 +133,6 @@ public class OrderInfoVo implements Serializable {
* 备注 * 备注
*/ */
private String remark; private String remark;
/**
* 打印状态 Json格式
* [{"id":"124","name":"111","time":"2025-12-29 11:05:18"},{"id":"111","name":"标签","time":"2025-12-29 11:05:30"}]
*/
private String printStatus;
/** /**
* 是否使用了霸王餐 * 是否使用了霸王餐

View File

@@ -1,14 +0,0 @@
package com.czg.service.account.mapper;
import com.mybatisflex.core.BaseMapper;
import com.czg.account.entity.QuickMenu;
/**
* 悬浮窗配置 映射层。
*
* @author ww
* @since 2025-12-29
*/
public interface QuickMenuMapper extends BaseMapper<QuickMenu> {
}

View File

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

View File

@@ -1,18 +0,0 @@
package com.czg.service.account.service.impl;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.czg.account.entity.QuickMenu;
import com.czg.account.service.QuickMenuService;
import com.czg.service.account.mapper.QuickMenuMapper;
import org.springframework.stereotype.Service;
/**
* 悬浮窗配置 服务层实现。
*
* @author ww
* @since 2025-12-29
*/
@Service
public class QuickMenuServiceImpl extends ServiceImpl<QuickMenuMapper, QuickMenu> implements QuickMenuService{
}

View File

@@ -253,6 +253,7 @@ public class ShopInfoServiceImpl extends ServiceImpl<ShopInfoMapper, ShopInfo> i
@Override @Override
@CacheEvict(key = "#shopInfoEditDTO.id") @CacheEvict(key = "#shopInfoEditDTO.id")
public Boolean edit(ShopInfoEditDTO shopInfoEditDTO) { public Boolean edit(ShopInfoEditDTO shopInfoEditDTO) {
shopInfoEditDTO.setIsMemberPrice(null);
ShopInfo shopInfo; ShopInfo shopInfo;
if (!StpKit.USER.isAdmin()) { if (!StpKit.USER.isAdmin()) {
shopInfo = queryChain().eq(ShopInfo::getId, StpKit.USER.getShopId()).one(); shopInfo = queryChain().eq(ShopInfo::getId, StpKit.USER.getShopId()).one();
@@ -319,8 +320,6 @@ public class ShopInfoServiceImpl extends ServiceImpl<ShopInfoMapper, ShopInfo> i
rabbitPublisher.sendOrderDetailStatusMsg(shopInfo.getId().toString(), "shopInfoUpdate"); rabbitPublisher.sendOrderDetailStatusMsg(shopInfo.getId().toString(), "shopInfoUpdate");
return true; return true;
} }
return false; return false;
} }

View File

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

View File

@@ -0,0 +1,100 @@
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

@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.czg.service.account.mapper.QuickMenuMapper">
</mapper>

View File

@@ -26,8 +26,6 @@ import com.czg.market.vo.InviteUserVO;
import com.czg.market.vo.MkDistributionConfigVO; import com.czg.market.vo.MkDistributionConfigVO;
import com.czg.order.dto.MkDistributionPayDTO; import com.czg.order.dto.MkDistributionPayDTO;
import com.czg.order.entity.OrderInfo; import com.czg.order.entity.OrderInfo;
import com.czg.market.service.OrderInfoService;
import com.czg.order.service.OrderPaymentService;
import com.czg.sa.StpKit; import com.czg.sa.StpKit;
import com.czg.service.market.enums.OrderStatusEnums; import com.czg.service.market.enums.OrderStatusEnums;
import com.czg.service.market.mapper.MkDistributionUserMapper; import com.czg.service.market.mapper.MkDistributionUserMapper;
@@ -685,22 +683,15 @@ public class MkDistributionUserServiceImpl extends ServiceImpl<MkDistributionUse
.ne(MkDistributionFlow::getStatus, TableValueConstant.DistributionFlow.Status.REFUND.getCode())); .ne(MkDistributionFlow::getStatus, TableValueConstant.DistributionFlow.Status.REFUND.getCode()));
list.forEach(item -> { list.forEach(item -> {
MkDistributionFlow refundFlow = BeanUtil.copyProperties(item, MkDistributionFlow.class);
refundFlow.setStatus(TableValueConstant.DistributionFlow.Status.REFUND.getCode());
refundFlow.setSourceId(item.getId());
refundFlow.setId(null);
refundFlow.setCreateTime(DateUtil.date().toLocalDateTime());
refundFlow.setUpdateTime(DateUtil.date().toLocalDateTime());
if (TableValueConstant.DistributionFlow.Status.PENDING.getCode().equals(item.getStatus())) { if (TableValueConstant.DistributionFlow.Status.PENDING.getCode().equals(item.getStatus())) {
item.setStatus(TableValueConstant.DistributionFlow.Status.SUCCESS.getCode()); item.setStatus(TableValueConstant.DistributionFlow.Status.REFUND.getCode());
// updateIncome(item.getRewardAmount().negate(), BigDecimal.ZERO, BigDecimal.ZERO, item.getDistributionUserId(), item.getUserId(), item.getShopUserId(), item.getShopId(), item.getLevel());
distributionFlowService.updateById(item); distributionFlowService.updateById(item);
mapper.updateIncome(item.getRewardAmount().negate(), null, null, item.getDistributionUserId(), item.getShopId());
} else { } else {
// 执行扣款 // 执行扣款
updateIncome(BigDecimal.ZERO, item.getRewardAmount().negate(), BigDecimal.ZERO, item.getDistributionUserId(), item.getUserId(), item.getShopUserId(), item.getShopId(), item.getLevel()); updateIncome(BigDecimal.ZERO, item.getRewardAmount().negate(), BigDecimal.ZERO, item.getDistributionUserId(), item.getUserId(), item.getShopUserId(), item.getShopId(), item.getLevel());
updateShopInfoAmount(item.getShopId(), item.getRewardAmount(), orderId, TableValueConstant.DistributionAmountFlow.Type.REFUND, "分销回退"); updateShopInfoAmount(item.getShopId(), item.getRewardAmount(), orderId, TableValueConstant.DistributionAmountFlow.Type.REFUND, "分销回退");
} }
distributionFlowService.save(refundFlow);
}); });
} }
@@ -738,6 +729,13 @@ public class MkDistributionUserServiceImpl extends ServiceImpl<MkDistributionUse
distributionDeliverService.save(deliver); distributionDeliverService.save(deliver);
} }
/**
* 分销金额修改
*
* @param pendingIncome 待入账金额
* @param receivedIncome 已入账
* @param withdrawIncome 已提现
*/
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public void updateIncome(BigDecimal pendingIncome, BigDecimal receivedIncome, BigDecimal withdrawIncome, Long id, Long userId, Long shopUserId, Long shopId, Integer isOne) { public void updateIncome(BigDecimal pendingIncome, BigDecimal receivedIncome, BigDecimal withdrawIncome, Long id, Long userId, Long shopUserId, Long shopId, Integer isOne) {

View File

@@ -72,7 +72,7 @@ public class MkShopRechargeServiceImpl extends ServiceImpl<MkShopRechargeMapper,
} }
@Override @Override
public MkShopRechargeVO detail(Long shopId) { public MkShopRechargeVO detail(Long shopId) throws CzgException{
shopId = shopInfoService.getMainIdByShopId(shopId); shopId = shopInfoService.getMainIdByShopId(shopId);
ShopInfo shopInfo = shopInfoService.getById(shopId); ShopInfo shopInfo = shopInfoService.getById(shopId);
if (shopInfo.getMainId() != null) { if (shopInfo.getMainId() != null) {

View File

@@ -45,36 +45,33 @@
order by a.create_time desc order by a.create_time desc
</select> </select>
<select id="totalAmount" resultType="java.math.BigDecimal"> <select id="totalAmount" resultType="java.math.BigDecimal">
select sum(a.amount) from mk_distribution_flow as a select sum(a.reward_amount)
left join mk_distribution_user as d on d.id=a.distribution_user_id from mk_distribution_flow as a
left join tb_shop_user as b on a.shop_user_id=b.id left join tb_shop_user as b on a.shop_user_id = b.id
left join tb_shop_user as c on c.id=a.shop_user_id
where a.shop_id=#{shopId} where a.shop_id=#{shopId}
<if test="id != null">
and a.shop_user_id=#{id}
</if>
<if test="userId != null">
and a.user_id=#{userId}
</if>
<if test="status != null and status != ''">
and a.status=#{status}
</if>
<if test="type != null and type != ''">
and a.type=#{type}
</if>
<if test="startTime != null"> <if test="startTime != null">
and a.create_time>=#{startTime} and a.create_time>=#{startTime}
</if> </if>
<if test="endTime != null"> <if test="endTime != null">
and a.create_time&lt;=#{endTime} and a.create_time&lt;=#{endTime}
</if> </if>
<if test="status != null and status != ''">
and a.status=#{status}
</if>
<if test="key != null and key != ''"> <if test="key != null and key != ''">
and ( and (
b.nick_name like concat('%',#{key},'%') b.nick_name like concat('%',#{key},'%')
or b.id like concat('%',#{key},'%') or b.id like concat('%',#{key},'%')
or c.id like concat('%',#{key},'%')
or c.nick_name like concat('%',#{key},'%')
) )
</if> </if>
<if test="id != null">
and d.id=#{id}
</if>
<if test="type != null and type != ''">
and a.type=#{type}
</if>
<if test="userId != null">
and d.user_id=#{userId}
</if>
</select> </select>
</mapper> </mapper>

View File

@@ -5,16 +5,24 @@
<mapper namespace="com.czg.service.market.mapper.MkDistributionUserMapper"> <mapper namespace="com.czg.service.market.mapper.MkDistributionUserMapper">
<update id="updateIncome"> <update id="updateIncome">
update mk_distribution_user update mk_distribution_user
set <set>
<if test="pendingIncome != null"> <if test="pendingIncome != null">
total_income = total_income + #{pendingIncome}, total_income = total_income + #{pendingIncome},
</if> </if>
<if test="receivedIncome != null"> <if test="receivedIncome != null">
total_income = total_income + #{receivedIncome}, total_income = total_income + #{receivedIncome},
</if> </if>
pending_income = pending_income + #{pendingIncome},
received_income = received_income + #{receivedIncome}, <if test="pendingIncome != null">
withdrawn_income = withdrawn_income + #{withdrawIncome} pending_income = pending_income + #{pendingIncome},
</if>
<if test="receivedIncome != null">
received_income = received_income + #{receivedIncome},
</if>
<if test="withdrawIncome != null">
withdrawn_income = withdrawn_income + #{withdrawIncome}
</if>
</set>
where id = #{id} and shop_id = #{shopId} where id = #{id} and shop_id = #{shopId}
</update> </update>

View File

@@ -1,18 +1,17 @@
package com.czg.service.order.print; package com.czg.service.order.print;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.text.UnicodeUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.czg.account.dto.HandoverRecordDTO; import com.czg.account.dto.HandoverRecordDTO;
import com.czg.account.entity.HandoverRecord;
import com.czg.account.entity.PrintMachine; import com.czg.account.entity.PrintMachine;
import com.czg.account.entity.ShopInfo; import com.czg.account.entity.ShopInfo;
import com.czg.account.service.ShopInfoService;
import com.czg.order.entity.OrderDetail; import com.czg.order.entity.OrderDetail;
import com.czg.order.entity.OrderInfo; import com.czg.order.entity.OrderInfo;
import com.czg.service.order.enums.OrderStatusEnums; import com.czg.service.order.enums.OrderStatusEnums;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.http.HttpEntity; import org.springframework.http.HttpEntity;
@@ -21,21 +20,24 @@ import org.springframework.http.MediaType;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
/** /**
* @author Administrator * @author Administrator
* 接口文档 <a href="https://help.feieyun.com/home/doc/zh;nav=1-1">
*/ */
@Component @Component
@Slf4j @Slf4j
public class FeiPrinter extends PrinterHandler implements PrinterImpl { public class FeiPrinter extends PrinterHandler implements PrinterImpl {
@Resource
private RestTemplate restTemplate;
@Resource
private ShopInfoService shopInfoService;
// API 地址 // API 地址
private static final String URL = "http://api.feieyun.cn/Api/Open/"; private static final String URL = "http://api.feieyun.cn/Api/Open/";
@@ -70,8 +72,10 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
String remark = orderDetail.getRemark(); String remark = orderDetail.getRemark();
String content = buildDishPrintData(false, getPickupNum(orderInfo), orderInfo.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), String content = buildDishPrintData(false, getPickupNum(orderInfo), orderInfo.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getNum(), remark, orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent()); orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getNum(), remark, orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
String o = sendPrintRequest(machine.getAddress(), content, null, "1"); Object o = sendPrintRequest(machine.getAddress(), content, null, "1");
printMachineLogService.save(orderInfo.getId(), machine, "新订单", content, o); printMachineLogService.save(machine, "新订单", content, o);
// printMachineLogService.save(machine, "新订单", , );
} }
@@ -80,8 +84,8 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
String remark = orderDetail.getRemark(); String remark = orderDetail.getRemark();
String content = buildDishPrintData(true, getPickupNum(orderInfo), orderInfo.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")), String content = buildDishPrintData(true, getPickupNum(orderInfo), orderInfo.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")),
orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getReturnNum(), remark, orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent()); orderDetail.getProductName(), orderDetail.getSkuName(), orderDetail.getReturnNum(), remark, orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
String o = sendPrintRequest(machine.getAddress(), content, null, "1"); Object o = sendPrintRequest(machine.getAddress(), content, null, "1");
printMachineLogService.save(orderInfo.getId(), machine, "退款单", content, o); printMachineLogService.save(machine, "退款单", content, o);
} }
@@ -102,8 +106,8 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
.setRemark(orderInfo.getRemark()) .setRemark(orderInfo.getRemark())
.setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString()); .setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString());
String string = buildOrderPrintData(printInfoDTO, detailList); String string = buildOrderPrintData(printInfoDTO, detailList);
String o = sendPrintRequest(machine.getAddress(), string, null, printerNum); Object o = sendPrintRequest(machine.getAddress(), string, null, printerNum);
printMachineLogService.save(orderInfo.getId(), machine, "结算", string, o); printMachineLogService.save(machine, "退款", string, o);
} }
@@ -130,24 +134,24 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
.setRemark(orderInfo.getRemark()) .setRemark(orderInfo.getRemark())
.setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString()); .setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString());
printInfoDTO.setPrintTitle(printInfoDTO.getPrintTitle()); printInfoDTO.setPrintTitle(printInfoDTO.getPrintTitle());
if (orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0) { if(orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0){
printInfoDTO.setSeatNum(orderInfo.getSeatNum().toString()); printInfoDTO.setSeatNum(orderInfo.getSeatNum().toString());
printInfoDTO.setSeatAmount(orderInfo.getSeatAmount().toPlainString()); printInfoDTO.setSeatAmount(orderInfo.getSeatAmount().toPlainString());
} }
if (orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0) { if(orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0){
printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString()); printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString());
} }
String string = buildOrderPrintData(printInfoDTO, detailList); String string = buildOrderPrintData(printInfoDTO, detailList);
String resp = sendPrintRequest(machine.getAddress(), string, null, printerNum); Object resp = sendPrintRequest(machine.getAddress(), string, null, printerNum);
printMachineLogService.save(orderInfo.getId(), machine, "结算单", string, resp); printMachineLogService.save(machine, "结算单", string, resp);
} }
@Override @Override
protected void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum, String codeUrl, LocalDateTime takeTime, String shopNote) { protected void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum, String codeUrl, LocalDateTime takeTime, String shopNote) {
String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一条新的排号记录\"}"; String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一条新的排号记录\"}";
String data = buildCallTicketData(shopName, tableName, callNum, preNum, codeUrl, shopNote, takeTime); String data = buildCallTicketData(shopName, tableName, callNum, preNum, codeUrl, shopNote, takeTime);
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, "1"); Object resp = sendPrintRequest(machine.getAddress(), data, voiceJson, "1");
printMachineLogService.save(machine, "叫号单", data, resp); printMachineLogService.save(machine, "叫号单", data, resp);
} }
@@ -166,7 +170,7 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
} }
@Override @Override
public String sendPrintRequest(String address, String metaPrintData, String voiceData, String printerNum) { public <R> R sendPrintRequest(String address, String metaPrintData, String voiceData, String printerNum) {
log.info("飞蛾打印机开始发送打印请求, 设备地址: {}, 元数据: {}", address, metaPrintData); log.info("飞蛾打印机开始发送打印请求, 设备地址: {}, 元数据: {}", address, metaPrintData);
String time = String.valueOf(System.currentTimeMillis() / 1000); String time = String.valueOf(System.currentTimeMillis() / 1000);
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>(); MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
@@ -185,68 +189,12 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers); HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
String result = restTemplate.postForObject(URL, requestEntity, String.class); String result = restTemplate.postForObject(URL, requestEntity, String.class);
log.info("飞鹅打印结果: {}", result); log.info("打印结果: {}", result);
return result; return (R) result;
} catch (Exception e) { } catch (Exception e) {
log.error("打印请求失败: {}", e.getMessage()); log.error("打印请求失败: {}", e.getMessage());
throw new RuntimeException("飞蛾打印请求失败", e); throw new RuntimeException("飞蛾打印请求失败", e);
} }
} }
/**
* 检查飞鹅打印机 打印任务是否已打印
*
* @param printOrderId 打印订单编号
* @return null-未知错误true-已打印false-未打印
*/
public Boolean checkFPrintStatus(String printOrderId) {
String time = String.valueOf(System.currentTimeMillis() / 1000);
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("user", USER);
paramMap.put("stime", time);
paramMap.put("sig", signature(time));
paramMap.put("apiname", "Open_queryOrderState");
paramMap.put("orderid", printOrderId);
Boolean ret;
try {
String resp = HttpUtil.post(URL, paramMap, 1000 * 5);
//成功 {"msg":"ok","ret":0,"data":true,"serverExecutedTime":4}
//失败 {"msg":"ok","ret":0,"data":false,"serverExecutedTime":4}
JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp));
log.info("飞鹅打印机 打印任务状态响应: {}", json);
ret = json.getBool("data");
} catch (Exception e) {
ret = null;
}
return ret;
}
/**
* 检查飞鹅打印机是否在线
*
* @param sn 设备编号
* @return 在线,工作状态正常。/离线。/未知错误
*/
public String checkOnline(String sn) {
String time = String.valueOf(System.currentTimeMillis() / 1000);
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("user", USER);
paramMap.put("stime", time);
paramMap.put("sig", signature(time));
paramMap.put("apiname", "Open_queryPrinterStatus");
paramMap.put("sn", sn);
String msg;
try {
String resp = HttpUtil.post(URL, paramMap, 1000 * 5);
//成功 开机 {"msg":"ok","ret":0,"data":"在线,工作状态正常。","serverExecutedTime":4}
//成功 离线 {"msg":"ok","ret":0,"data":"离线。","serverExecutedTime":7}
JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp));
log.info("飞鹅打印机状态响应: {}", json);
msg = json.getStr("data");
} catch (Exception e) {
msg = "未知错误";
}
return msg;
}
} }

View File

@@ -34,7 +34,6 @@ import lombok.ToString;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboReference; import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.client.RestTemplate;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
@@ -50,9 +49,10 @@ public abstract class PrinterHandler {
@Setter @Setter
protected PrinterHandler nextPrinter; protected PrinterHandler nextPrinter;
protected String printerBrand; protected String printerBrand;
// 创建 ThreadLocal 变量
private static final ThreadLocal<String> ERR_MSG = ThreadLocal.withInitial(() -> "");
@Resource
protected RestTemplate restTemplate;
@Resource @Resource
protected OrderDetailService orderDetailService; protected OrderDetailService orderDetailService;
@Resource @Resource
@@ -84,11 +84,7 @@ public abstract class PrinterHandler {
@Getter @Getter
public enum PrintTypeEnum { public enum PrintTypeEnum {
HANDOVER("交班", "handover"), HANDOVER("交班", "handover"),
ORDER("订单", "order"), ORDER("订单", "order"), ONE("菜品", "one"), CALL("叫号", "call"), ONE_AND_ORDER("菜品和结算单同时打印", "oneAndOrder"), PRE_ORDER("预结算单", "preOrder");
ONE("菜品", "one"),
CALL("叫号", "call"),
ONE_AND_ORDER("菜品和结算单同时打印", "oneAndOrder"),
PRE_ORDER("预结算单", "preOrder");
private final String name; private final String name;
private final String code; private final String code;
@@ -161,7 +157,7 @@ public abstract class PrinterHandler {
if (StrUtil.isNotEmpty(printMethod)) { if (StrUtil.isNotEmpty(printMethod)) {
List<String> arrayList = switch (printMethod) { List<String> arrayList = switch (printMethod) {
case "all" -> Arrays.asList("one", "normal", "all"); case "all" ->Arrays.asList("one", "normal", "all");
case "one" -> Arrays.asList("one", "all"); case "one" -> Arrays.asList("one", "all");
case "normal" -> Arrays.asList("normal", "all"); case "normal" -> Arrays.asList("normal", "all");
default -> new ArrayList<>(); default -> new ArrayList<>();
@@ -177,6 +173,10 @@ public abstract class PrinterHandler {
wrapper.like(PrintMachine::getPrintType, printType); wrapper.like(PrintMachine::getPrintType, printType);
} }
List<PrintMachine> list = printMachineService.list(wrapper); List<PrintMachine> list = printMachineService.list(wrapper);
// for (PrintMachine item : list) {
// //实际打印以传递的参数为准
// item.setPrintMethod(printMethod);
// }
if (list.isEmpty()) { if (list.isEmpty()) {
log.error("店铺未配置打印机店铺id: {}", shopId); log.error("店铺未配置打印机店铺id: {}", shopId);
return list; return list;
@@ -188,8 +188,7 @@ public abstract class PrinterHandler {
/** /**
* 处理打印 * 处理打印
* * @param data 传递的数据
* @param data 传递的数据
* @param printTypeEnum order returnOrder preOrder one call handover * @param printTypeEnum order returnOrder preOrder one call handover
*/ */
public void handler(String data, PrintTypeEnum printTypeEnum) { public void handler(String data, PrintTypeEnum printTypeEnum) {
@@ -315,7 +314,7 @@ public abstract class PrinterHandler {
item.setNum(item.getNum().subtract(printDetailInfo.getPrintNum())); item.setNum(item.getNum().subtract(printDetailInfo.getPrintNum()));
item.setReturnNum(item.getReturnNum().subtract(printDetailInfo.getPrintReturnNum())); item.setReturnNum(item.getReturnNum().subtract(printDetailInfo.getPrintReturnNum()));
orderDetails.add(item); orderDetails.add(item);
} else { }else {
orderDetails.add(item); orderDetails.add(item);
} }
@@ -335,37 +334,34 @@ public abstract class PrinterHandler {
log.info("准备开始打印交班"); log.info("准备开始打印交班");
if (data instanceof HandoverRecordDTO record) { if (data instanceof HandoverRecordDTO record) {
handoverPrint(machine, record); handoverPrint(machine, record);
} else { }else {
throw new RuntimeException("传递数据类型有误"); throw new RuntimeException("传递数据类型有误");
} }
break; break;
case PrintTypeEnum.ORDER: case PrintTypeEnum.ORDER:
log.info("准备开始打印订单"); log.info("准备开始打印订单");
if (data instanceof OrderInfo orderInfo) { if (data instanceof OrderInfo orderInfo) {
redisService.set("order:print:" + orderInfo.getId(),"", 180);
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId())); List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
onlyFrontDesk(machine, false, orderInfo, orderDetailList); onlyFrontDesk(machine, false, orderInfo, orderDetailList);
} else { }else {
throw new RuntimeException("传递数据类型有误"); throw new RuntimeException("传递数据类型有误");
} }
break; break;
case PrintTypeEnum.PRE_ORDER: case PrintTypeEnum.PRE_ORDER:
log.info("准备开始打印预结算订单"); log.info("准备开始打印预结算订单");
if (data instanceof OrderInfo orderInfo) { if (data instanceof OrderInfo orderInfo) {
redisService.set("order:print:" + orderInfo.getId(),"", 180);
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId())); List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
onlyFrontDesk(machine, true, orderInfo, orderDetailList); onlyFrontDesk(machine, true, orderInfo, orderDetailList);
} else { }else {
throw new RuntimeException("传递数据类型有误"); throw new RuntimeException("传递数据类型有误");
} }
break; break;
case PrintTypeEnum.ONE: case PrintTypeEnum.ONE:
log.info("准备开始打印菜品单"); log.info("准备开始打印菜品单");
if (data instanceof OrderInfo orderInfo) { if (data instanceof OrderInfo orderInfo) {
redisService.set("order:print:" + orderInfo.getId(),"", 180);
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId())); List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
onlyKitchen(machine, orderInfo, orderDetailList); onlyKitchen(machine, orderInfo, orderDetailList);
} else { }else {
throw new RuntimeException("传递数据类型有误"); throw new RuntimeException("传递数据类型有误");
} }
break; break;
@@ -373,14 +369,13 @@ public abstract class PrinterHandler {
log.info("准备开始打印叫号单"); log.info("准备开始打印叫号单");
if (data instanceof CallQueue queue) { if (data instanceof CallQueue queue) {
onlyCallNumPrint(machine, queue); onlyCallNumPrint(machine, queue);
} else { }else {
throw new RuntimeException("传递数据类型有误"); throw new RuntimeException("传递数据类型有误");
} }
break; break;
case PrintTypeEnum.ONE_AND_ORDER: case PrintTypeEnum.ONE_AND_ORDER:
log.info("准备开始打印菜品以及结算单"); log.info("准备开始打印菜品以及结算单");
if (data instanceof OrderInfo orderInfo) { if (data instanceof OrderInfo orderInfo) {
redisService.set("order:print:" + orderInfo.getId(),"", 180);
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId())); List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
switch (machine.getPrintMethod()) { switch (machine.getPrintMethod()) {
case "all": case "all":
@@ -398,7 +393,7 @@ public abstract class PrinterHandler {
default: default:
throw new RuntimeException("打印方法有误"); throw new RuntimeException("打印方法有误");
} }
} else { }else {
throw new RuntimeException("传递数据类型有误"); throw new RuntimeException("传递数据类型有误");
} }
@@ -453,7 +448,17 @@ public abstract class PrinterHandler {
log.info("台位费商品,不打印"); log.info("台位费商品,不打印");
return; return;
} }
// ProdSku sku = prodSkuService.getById(item.getSkuId());
// if (isTemporary == 0 && sku == null) {
// log.error("商品不存在, id: {}", item.getSkuId());
// return;
// } else if (isTemporary == 1) {
// sku = new ProdSku();
// }
// String remark = StrUtil.isNotBlank(sku.getSpecInfo()) ? sku.getSpecInfo() : "";
// item.setRemark(remark);
if (item.getReturnNum().compareTo(BigDecimal.ZERO) > 0) { if (item.getReturnNum().compareTo(BigDecimal.ZERO) > 0) {
returnDishesPrint(orderInfo, item, machine); returnDishesPrint(orderInfo, item, machine);
} }
@@ -464,9 +469,8 @@ public abstract class PrinterHandler {
// 保存已打印信息 // 保存已打印信息
OrderDetail orderDetail = detailMap.get(item.getId()); OrderDetail orderDetail = detailMap.get(item.getId());
redisService.set(RedisCst.getPrintOrderDetailKey(orderInfo.getId(), item.getId()), redisService.set(RedisCst.getPrintOrderDetailKey(orderInfo.getId(), item.getId()), JSONObject.toJSONString(new PrintDetailInfo().setPrint(item.getIsPrint() == 1).setDetailId(item.getId())
JSONObject.toJSONString(new PrintDetailInfo().setPrint(item.getIsPrint() == 1).setDetailId(item.getId()) .setPrintNum(orderDetail.getNum()).setPrintReturnNum(orderDetail.getReturnNum())), 3600 * 24);
.setPrintNum(orderDetail.getNum()).setPrintReturnNum(orderDetail.getReturnNum())), 3600 * 24);
}); });

View File

@@ -9,6 +9,7 @@ import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.czg.account.dto.HandoverRecordDTO; import com.czg.account.dto.HandoverRecordDTO;
import com.czg.account.entity.HandoverRecord;
import com.czg.order.entity.OrderDetail; import com.czg.order.entity.OrderDetail;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
@@ -68,9 +69,10 @@ public interface PrinterImpl {
* @param metaPrintData 打印元数据 * @param metaPrintData 打印元数据
* @param voiceData 语音信息 * @param voiceData 语音信息
* @param printNum 打印联数 * @param printNum 打印联数
* @param <R> 返回数据类型
* @return 打印结果 * @return 打印结果
*/ */
String sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum); <R> R sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum);
/** /**
* 获取当前打印机标签信息 * 获取当前打印机标签信息
@@ -558,6 +560,12 @@ public interface PrinterImpl {
return str; return str;
} }
public static void main(String[] args) {
System.out.println("水煮肉片".length());
System.out.println(StrUtil.repeat(' ', 8));
System.out.println(StrUtil.fillAfter("水煮肉片", ' ', 21));
}
/** /**
* 获取填充字符串, 并且换行 * 获取填充字符串, 并且换行
* *

View File

@@ -4,10 +4,11 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import com.czg.account.dto.HandoverRecordDTO; import com.czg.account.dto.HandoverRecordDTO;
import com.czg.account.entity.HandoverRecord;
import com.czg.account.entity.PrintMachine; import com.czg.account.entity.PrintMachine;
import com.czg.account.entity.ShopInfo; import com.czg.account.entity.ShopInfo;
import com.czg.account.service.ShopInfoService;
import com.czg.order.entity.OrderDetail; import com.czg.order.entity.OrderDetail;
import com.czg.order.entity.OrderInfo; import com.czg.order.entity.OrderInfo;
import com.czg.service.order.enums.OrderStatusEnums; import com.czg.service.order.enums.OrderStatusEnums;
@@ -19,14 +20,16 @@ import org.springframework.http.MediaType;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap; import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.annotation.Resource;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.*; import java.util.*;
/** /**
* 博实结-云享印打印机 * 云享印打印机
* 接口文档 <a href="https://bsj2.yuque.com/bsj/izhmfn/rr8b5g?#ZbE6s"> *
* @author Administrator * @author Administrator
*/ */
@Slf4j @Slf4j
@@ -42,6 +45,12 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
//APPSECRET //APPSECRET
private static final String APP_SECRET = "2022bsjZF544GAH"; private static final String APP_SECRET = "2022bsjZF544GAH";
@Resource
private ShopInfoService shopInfoService;
@Resource
private RestTemplate restTemplate;
public YxyPrinter() { public YxyPrinter() {
super("云想印"); super("云想印");
} }
@@ -66,7 +75,7 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
@Override @Override
public String sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum) { public <R> R sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum) {
log.info("开始请求云享印,请求数据:{}, {}", voiceData, metaPrintData); log.info("开始请求云享印,请求数据:{}, {}", voiceData, metaPrintData);
//设备名称 //设备名称
//行为方式 1:只打印数据 2:只播放信息 3:打印数据并播放信息 //行为方式 1:只打印数据 2:只播放信息 3:打印数据并播放信息
@@ -78,9 +87,10 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
String time = String.valueOf(System.currentTimeMillis()); String time = String.valueOf(System.currentTimeMillis());
String uuid = UUID.randomUUID().toString(); String uuid = UUID.randomUUID().toString();
Map<String, String> param = getToken(time, uuid);
//参数 //参数
MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>(); MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
multiValueMap.add("token", getToken(time, uuid)); multiValueMap.add("token", param.get("TOKEN"));
multiValueMap.add("devName", address); multiValueMap.add("devName", address);
multiValueMap.add("actWay", 3); multiValueMap.add("actWay", 3);
multiValueMap.add("cn", printNum); multiValueMap.add("cn", printNum);
@@ -98,7 +108,7 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
httpEntity, String.class); httpEntity, String.class);
log.info("请求云享印成功,响应数据: {}", httpResponse); log.info("请求云享印成功,响应数据: {}", httpResponse);
return httpResponse; return (R) httpResponse;
} }
@Override @Override
@@ -106,8 +116,10 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
String buildDishPrintData = buildDishPrintData(false, getPickupNum(orderInfo), DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"), orderDetail.getProductName(), orderDetail.getSkuName(), String buildDishPrintData = buildDishPrintData(false, getPickupNum(orderInfo), DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"), orderDetail.getProductName(), orderDetail.getSkuName(),
orderDetail.getNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent()); orderDetail.getNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}"; String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
String resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1"); // String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
printMachineLogService.save(orderInfo.getId(), machine, "新订单", buildDishPrintData, resp); Object resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
// sendPrintRequest(voiceJson, 3, 1, machine.getAddress(), data);
printMachineLogService.save(machine, "新订单", buildDishPrintData, resp);
} }
@@ -115,9 +127,10 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
protected void returnDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine) { protected void returnDishesPrint(OrderInfo orderInfo, OrderDetail orderDetail, PrintMachine machine) {
String buildDishPrintData = buildDishPrintData(true, getPickupNum(orderInfo), DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"), orderDetail.getProductName(), orderDetail.getSkuName(), String buildDishPrintData = buildDishPrintData(true, getPickupNum(orderInfo), DateUtil.format(orderDetail.getCreateTime(), "yyyy-MM-dd HH:mm:ss"), orderDetail.getProductName(), orderDetail.getSkuName(),
orderDetail.getReturnNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent()); orderDetail.getReturnNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}"; String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
String resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1"); Object resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
printMachineLogService.save(orderInfo.getId(), machine, "退款单", buildDishPrintData, resp); printMachineLogService.save(machine, "退款单", buildDishPrintData, resp);
} }
@@ -128,17 +141,18 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
.setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.format(orderInfo.getCreateTime(), "yyyy-MM-dd HH:mm:ss")).setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString()) .setOrderNo(orderInfo.getOrderNo()).setTradeDate(DateUtil.format(orderInfo.getCreateTime(), "yyyy-MM-dd HH:mm:ss")).setOperator("【POS-1】001").setPayAmount(orderInfo.getPayAmount().toPlainString())
.setOriginalAmount(orderInfo.getOriginAmount().toPlainString()).setReturn(isReturn(orderInfo)) .setOriginalAmount(orderInfo.getOriginAmount().toPlainString()).setReturn(isReturn(orderInfo))
.setBalance(balance).setPayType((ObjectUtil.isEmpty(orderInfo.getPayType()) || ObjectUtil.isNull(orderInfo.getPayType()) ? "" : orderInfo.getPayType())).setIntegral("0") .setBalance(balance).setPayType((ObjectUtil.isEmpty(orderInfo.getPayType()) || ObjectUtil.isNull(orderInfo.getPayType()) ? "" : orderInfo.getPayType())).setIntegral("0")
.setOutNumber(orderInfo.getTakeCode()).setPrintTitle("退款") .setOutNumber(orderInfo.getTakeCode()).setPrintTitle("结算")
.setRemark(orderInfo.getRemark()).setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString()); .setRemark(orderInfo.getRemark()).setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString());
String data = buildOrderPrintData(printInfoDTO, detailList); String data = buildOrderPrintData(printInfoDTO, detailList);
String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}"; String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
String printerNum = "1"; String printerNum = "1";
if (StrUtil.isNotBlank(machine.getPrintQty())) { if (StrUtil.isNotBlank(machine.getPrintQty())) {
printerNum = machine.getPrintQty().split("\\^")[1]; printerNum = machine.getPrintQty().split("\\^")[1];
} }
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum); String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum);
printMachineLogService.save(orderInfo.getId(), machine, "退款", data, resp); printMachineLogService.save(machine, "新订", data, resp);
} }
@@ -159,38 +173,33 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
.setRemark(orderInfo.getRemark()) .setRemark(orderInfo.getRemark())
.setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString()); .setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString());
printInfoDTO.setPrintTitle(printInfoDTO.getPrintTitle()); printInfoDTO.setPrintTitle(printInfoDTO.getPrintTitle());
if (orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0) { if(orderInfo.getSeatNum() != null && orderInfo.getSeatAmount().compareTo(BigDecimal.ZERO) > 0){
printInfoDTO.setSeatNum(orderInfo.getSeatNum().toString()); printInfoDTO.setSeatNum(orderInfo.getSeatNum().toString());
printInfoDTO.setSeatAmount(orderInfo.getSeatAmount().toPlainString()); printInfoDTO.setSeatAmount(orderInfo.getSeatAmount().toPlainString());
} }
if (orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0) { if(orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0){
printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString()); printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString());
} }
String data = buildOrderPrintData(printInfoDTO, detailList); String data = buildOrderPrintData(printInfoDTO, detailList);
String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}"; String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
String printerNum = "1"; String printerNum = "1";
if (StrUtil.isNotBlank(machine.getPrintQty())) { if (StrUtil.isNotBlank(machine.getPrintQty())) {
printerNum = machine.getPrintQty().split("\\^")[1]; printerNum = machine.getPrintQty().split("\\^")[1];
} }
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum); String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum);
printMachineLogService.save(orderInfo.getId(), machine, "结算单", data, resp); // printMachineLogService.save(machine, printType, data, resp);
printMachineLogService.save(machine, "新订单", data, resp);
} }
/**
* 叫号单打印
*/
@Override @Override
protected void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum, String codeUrl, LocalDateTime takeTime, String shopNote) { protected void callNumPrint(PrintMachine machine, String callNum, String shopName, String tableName, String tableNote, String preNum, String codeUrl, LocalDateTime takeTime, String shopNote) {
String resp = buildCallTicketData(shopName, tableName, callNum, preNum, codeUrl, shopNote, takeTime); String resp = buildCallTicketData(shopName, tableName, callNum, preNum, codeUrl, shopNote, takeTime);
sendPrintRequest(machine.getAddress(), resp, null, "1"); sendPrintRequest(machine.getAddress(), resp, null, "1");
} }
/**
* 交班单打印
*/
@Override @Override
protected void handoverPrint(PrintMachine machine, HandoverRecordDTO record) { protected void handoverPrint(PrintMachine machine, HandoverRecordDTO record) {
String string = buildHandoverData(record); String string = buildHandoverData(record);
@@ -205,9 +214,9 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
* @param requestId 请求ID自定义 * @param requestId 请求ID自定义
* @return token信息 * @return token信息
*/ */
private static String getToken(String timestamp, String requestId) { private static Map<String, String> getToken(String timestamp, String requestId) {
StringBuilder token = new StringBuilder(); StringBuilder token = new StringBuilder();
// StringBuilder encode = new StringBuilder(); StringBuilder encode = new StringBuilder();
SortedMap<String, Object> map = new TreeMap<>(); SortedMap<String, Object> map = new TreeMap<>();
map.put("appId", APP_ID); map.put("appId", APP_ID);
map.put("timestamp", timestamp); map.put("timestamp", timestamp);
@@ -217,38 +226,13 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
String key = next.getKey(); String key = next.getKey();
Object value = next.getValue(); Object value = next.getValue();
token.append(key).append(value); token.append(key).append(value);
// encode.append(key).append("=").append(value).append("&"); encode.append(key).append("=").append(value).append("&");
} }
// Map<String, String> finalMap = new HashMap<>(); Map<String, String> finalMap = new HashMap<>();
// finalMap.put("ENCODE", encode.toString()); finalMap.put("ENCODE", encode.toString());
// finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase()); finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase());
// return finalMap; return finalMap;
return SecureUtil.md5(token + APP_SECRET).toUpperCase();
} }
/**
* 检查打印状态
*
* @param devName 设备名称,(唯一) 对应配置表中的address字段即IP地址/打印机编号)
* @param taskId 打印任务id用于复查打印状态云想印=orderId
*/
public String checkPrintStatus(String devName, String taskId) {
String time = String.valueOf(System.currentTimeMillis());
String uuid = UUID.randomUUID().toString();
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("devName", devName);
paramMap.put("orderId", taskId);
paramMap.put("token", getToken(time, uuid));
paramMap.put("appId", APP_ID);
paramMap.put("timestamp", time);
paramMap.put("requestId", uuid);
paramMap.put("userCode", USER_CODE);
String s = HttpUtil.get("https://ioe.car900.com/v1/openApi/dev/findOrder.json", paramMap, 1000 * 5);
log.info("云想印打印机:{},任务:{}状态:{}", devName, taskId, s);
return s;
}
} }

View File

@@ -1060,7 +1060,8 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
distributionUserService.costUpgradeLevelBefore(orderInfo.getUserId(), orderInfo.getShopId()); distributionUserService.costUpgradeLevelBefore(orderInfo.getUserId(), orderInfo.getShopId());
// 分销奖励 // 分销奖励
distributionUserService.distribute(orderInfo.getId(), orderInfo.getOrderNo(), payment.getAmount(), orderInfo.getUserId(), orderInfo.getShopId(), "order"); distributionUserService.distribute(orderInfo.getId(), orderInfo.getOrderNo(), payment.getAmount(), orderInfo.getUserId(), orderInfo.getShopId(), "order");
} else if (PayTypeConstants.SourceType.MEMBER_IN.equals(payment.getSourceType()) || PayTypeConstants.SourceType.FREE.equals(payment.getSourceType())) { }
else if (PayTypeConstants.SourceType.MEMBER_IN.equals(payment.getSourceType()) || PayTypeConstants.SourceType.FREE.equals(payment.getSourceType())) {
boolean isFree = PayTypeConstants.SourceType.FREE.equals(payment.getSourceType()); boolean isFree = PayTypeConstants.SourceType.FREE.equals(payment.getSourceType());
ShopUser shopUser = shopUserService.getById(payment.getSourceId()); ShopUser shopUser = shopUserService.getById(payment.getSourceId());
OrderInfo orderInfo = null; OrderInfo orderInfo = null;
@@ -1112,17 +1113,22 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
payment.getId(), payment.getSourceType(), bizEnum, orderInfo == null); payment.getId(), payment.getSourceType(), bizEnum, orderInfo == null);
} }
} }
} else if (PayTypeConstants.SourceType.MEMBER_PAY.equals(payment.getSourceType())) { }
else if (PayTypeConstants.SourceType.MEMBER_PAY.equals(payment.getSourceType())) {
//购买会员 //购买会员
ShopUser shopUser = shopUserService.getById(payment.getSourceId()); ShopUser shopUser = shopUserService.getById(payment.getSourceId());
memberConfigService.joinMember(payment.getShopId(), shopUser.getUserId(), payment.getRelatedId()); memberConfigService.joinMember(payment.getShopId(), shopUser.getUserId(), payment.getRelatedId());
} else if (PayTypeConstants.SourceType.DISTRIBUTION.equals(payment.getSourceType())) { }
else if (PayTypeConstants.SourceType.DISTRIBUTION.equals(payment.getSourceType())) {
distributionUserService.open(payment.getSourceId(), payment.getAmount(), payment.getShopId(), payment.getId()); distributionUserService.open(payment.getSourceId(), payment.getAmount(), payment.getShopId(), payment.getId());
} else if (PayTypeConstants.SourceType.POINT.equals(payment.getSourceType())) { }
else if (PayTypeConstants.SourceType.POINT.equals(payment.getSourceType())) {
goodPayService.payCallBack(payment.getSourceId(), payment.getId()); goodPayService.payCallBack(payment.getSourceId(), payment.getId());
} else if (PayTypeConstants.SourceType.WARE.equals(payment.getSourceType())) { }
else if (PayTypeConstants.SourceType.WARE.equals(payment.getSourceType())) {
gbOrderService.payCallBack(payment.getSourceId(), payment.getId()); gbOrderService.payCallBack(payment.getSourceId(), payment.getId());
} else if (PayTypeConstants.SourceType.PP.equals(payment.getSourceType())) { }
else if (PayTypeConstants.SourceType.PP.equals(payment.getSourceType())) {
ppPackageOrderService.paySuccess(payment.getSourceId(), payment.getId()); ppPackageOrderService.paySuccess(payment.getSourceId(), payment.getId());
} }
} }
@@ -1516,9 +1522,6 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
@Override @Override
public Boolean printOrder(Long shopId, OrderInfoPrintDTO orderInfoPrintDTO) { public Boolean printOrder(Long shopId, OrderInfoPrintDTO orderInfoPrintDTO) {
if (redisService.hasKey("order:print:" + orderInfoPrintDTO.getId())) {
throw new CzgException("网络打印机正在尝试打印中。如需重打,请稍后再试!");
}
OrderInfo orderInfo = orderInfoService.getOne(new QueryWrapper().eq(OrderInfo::getShopId, shopId).eq(OrderInfo::getId, orderInfoPrintDTO.getId())); OrderInfo orderInfo = orderInfoService.getOne(new QueryWrapper().eq(OrderInfo::getShopId, shopId).eq(OrderInfo::getId, orderInfoPrintDTO.getId()));
if (orderInfo == null) { if (orderInfo == null) {
throw new CzgException("订单信息不存在"); throw new CzgException("订单信息不存在");

View File

@@ -688,10 +688,7 @@ public class PayServiceImpl implements PayService {
if (orderInfo.getStatus().equals(OrderStatusEnums.CANCELLED.getCode())) { if (orderInfo.getStatus().equals(OrderStatusEnums.CANCELLED.getCode())) {
throw new CzgException("订单已过期不可退单"); throw new CzgException("订单已过期不可退单");
} }
boolean isFirstRefund = true; boolean isFirstRefund = orderInfo.getRefundAmount().compareTo(BigDecimal.ZERO) == 0;
if (orderInfo.getRefundAmount().compareTo(BigDecimal.ZERO) != 0) {
isFirstRefund = false;
}
ShopInfo shopInfo = shopInfoService.getById(orderInfo.getShopId()); ShopInfo shopInfo = shopInfoService.getById(orderInfo.getShopId());
Map<String, BigDecimal> returnProMap = new HashMap<>(); Map<String, BigDecimal> returnProMap = new HashMap<>();
boolean isPay = true; boolean isPay = true;
@@ -821,8 +818,8 @@ public class PayServiceImpl implements PayService {
if (!returnProMap.isEmpty()) { if (!returnProMap.isEmpty()) {
rabbitPublisher.sendOrderRefundMsg(JSONObject.toJSONString(Map.of("orderId", orderInfo.getId(), "returnProMap", returnProMap))); rabbitPublisher.sendOrderRefundMsg(JSONObject.toJSONString(Map.of("orderId", orderInfo.getId(), "returnProMap", returnProMap)));
} }
refundOrderAfter(orderInfo.getId(), orderInfo.getShopId(), orderInfo.getUserId(), orderInfo.getOrderNo(), FunUtils.asyncSafeRunVoid(() -> refundOrderAfter(orderInfo.getId(), orderInfo.getShopId(), orderInfo.getUserId(), orderInfo.getOrderNo(),
orderInfo.getPointsNum(), isFirstRefund, orderInfo.getStatus().equals(OrderStatusEnums.REFUND.getCode())); orderInfo.getPointsNum(), isFirstRefund, orderInfo.getStatus().equals(OrderStatusEnums.REFUND.getCode())));
return CzgResult.success(); return CzgResult.success();
} }
@@ -867,12 +864,11 @@ public class PayServiceImpl implements PayService {
} }
throw new CzgException(refund.getMsg()); throw new CzgException(refund.getMsg());
} else { } else {
paymentService.updateChain() OrderPayment uOrderPayment = new OrderPayment();
.eq(OrderPayment::getId, refundId) uOrderPayment.setPayTime(LocalDateTime.now());
.set(OrderPayment::getPayTime, refund.getData().getRefundTime()) uOrderPayment.setTradeNumber(refund.getData().getRefundOrderId());
.set(OrderPayment::getTradeNumber, refund.getData().getRefundOrderId()) uOrderPayment.setRespJson(JSONObject.toJSONString(refund.getData()));
.set(OrderPayment::getRespJson, JSONObject.toJSONString(refund.getData())) paymentService.update(uOrderPayment, QueryWrapper.create().eq(OrderPayment::getId, refundId));
.update();
} }
} }

View File

@@ -2,36 +2,32 @@ package com.czg.service.order.service.impl;
import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil; import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil; import cn.hutool.core.map.MapProxy;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.text.UnicodeUtil;
import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject; import cn.hutool.crypto.SecureUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.czg.account.entity.PrintMachine; import com.czg.account.entity.PrintMachine;
import com.czg.config.RedisCst; import com.github.pagehelper.PageHelper;
import com.czg.market.service.OrderInfoService; import com.github.pagehelper.PageInfo;
import com.czg.order.entity.OrderInfo; import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.czg.order.entity.PrintMachineLog; import com.czg.order.entity.PrintMachineLog;
import com.czg.order.service.PrintMachineLogService; import com.czg.order.service.PrintMachineLogService;
import com.czg.service.RedisService;
import com.czg.service.order.mapper.PrintMachineLogMapper; import com.czg.service.order.mapper.PrintMachineLogMapper;
import com.czg.service.order.print.FeiPrinter;
import com.czg.service.order.print.YxyPrinter;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import jakarta.annotation.Resource;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy; import org.apache.commons.codec.digest.DigestUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.time.LocalDateTime; import java.util.*;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
/** /**
* 店铺小票打印记录ServiceImpl * 店铺小票打印记录ServiceImpl
@@ -42,47 +38,139 @@ import java.util.concurrent.atomic.AtomicReference;
@Slf4j @Slf4j
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMapper, PrintMachineLog> implements PrintMachineLogService { public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMapper, PrintMachineLog> implements PrintMachineLogService{
//请求地址
private static final String URL_STR = "https://ioe.car900.com/v1/openApi/dev/customPrint.json";
//APPID
private static final String APP_ID = "ZF544";
//USERCODE
private static final String USER_CODE = "ZF544";
//APPSECRET
private static final String APP_SECRET = "2022bsjZF544GAH";
@Resource public static final String URL = "http://api.feieyun.cn/Api/Open/";//不需要修改
private OrderInfoService orderInfoService;
@Lazy
@Resource
private YxyPrinter yxyPrinter;
@Lazy
@Resource
private FeiPrinter feiPrinter;
@Resource
private RedisService redisService;
Map<Integer, String> yxxStatusMap = Map.of( public static final String USER = "chaozhanggui2022@163.com";//*必填*:账号名
0, "离线(设备上线后自动补打)", public static final String UKEY = "UfWkhXxSkeSSscsU";//*必填*: 飞鹅云后台注册账号后生成的UKEY 【备注这不是填打印机的KEY】
1, "在线", public static final String SN = "960238952";//*必填*打印机编号必须要在管理后台里添加打印机或调用API接口添加之后才能调用API
2, "获取失败", /**
3, "未激活", * 获取TOKEN值
4, "设备已禁用"); *
* @param timestamp 时间戳13位
@Async * @param requestId 请求ID自定义
@Override * @return
public void save(PrintMachine config, String bizType, String printContent, String respJson) { */
if (config == null) { private static Map<String, String> getToken(String timestamp, String requestId) {
return; StringBuilder token = new StringBuilder();
StringBuilder encode = new StringBuilder();
SortedMap<String, Object> map = new TreeMap<>();
map.put("appId", APP_ID);
map.put("timestamp", timestamp);
map.put("requestId", requestId);
map.put("userCode", USER_CODE);
for (Map.Entry<String, Object> next : map.entrySet()) {
String key = next.getKey();
Object value = next.getValue();
token.append(key).append(value);
encode.append(key).append("=").append(value).append("&");
} }
save(null, config, bizType, printContent, respJson); System.out.println("token" + token);
Map<String, String> finalMap = new HashMap<>();
finalMap.put("ENCODE", encode.toString());
System.out.println("+++++++++++++++" + token + APP_SECRET);
finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase());
return finalMap;
} }
/**
* 检查打印状态
*
* @param devName 设备名称,(唯一) 对应配置表中的address字段即IP地址/打印机编号)
* @param taskId 打印任务id用于复查打印状态云想印=orderId
* @return
*/
public static String checkPrintStatus(String devName, String taskId) {
String time = String.valueOf(System.currentTimeMillis());
String uuid = UUID.randomUUID().toString();
Map<String, String> param = getToken(time, uuid);
String token = param.get("TOKEN");
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("devName", devName);
paramMap.put("orderId", taskId);
paramMap.put("token", token);
paramMap.put("appId", APP_ID);
paramMap.put("timestamp", time);
paramMap.put("requestId", uuid);
paramMap.put("userCode", USER_CODE);
return HttpUtil.get("https://ioe.car900.com/v1/openApi/dev/findOrder.json", paramMap, 1000 * 5);
}
private static String signature(String USER, String UKEY, String STIME) {
return DigestUtils.sha1Hex(USER + UKEY + STIME);
}
/**
* 检查飞鹅打印机打印任务是否已打印
*
* @param printOrderId 打印订单编号
* @return null-未知错误true-已打印false-未打印
*/
public static Boolean checkFPrintStatus(String printOrderId) {
String STIME = String.valueOf(System.currentTimeMillis() / 1000);
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("user", USER);
paramMap.put("stime", STIME);
paramMap.put("sig", signature(USER, UKEY, STIME));
paramMap.put("apiname", "Open_queryOrderState");
paramMap.put("orderid", printOrderId);
Boolean ret;
try {
String resp = HttpUtil.post(URL, paramMap, 1000 * 5);
//成功 {"msg":"ok","ret":0,"data":true,"serverExecutedTime":4}
//失败 {"msg":"ok","ret":0,"data":false,"serverExecutedTime":4}
JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp));
ret = json.getBool("data");
} catch (Exception e) {
ret = null;
}
return ret;
}
/**
* 检查飞鹅打印机是否在线
*
* @param sn 设备编号
* @return 在线,工作状态正常。/离线。/未知错误
*/
public static String checkOnline(String sn) {
String STIME = String.valueOf(System.currentTimeMillis() / 1000);
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("user", USER);
paramMap.put("stime", STIME);
paramMap.put("sig", signature(USER, UKEY, STIME));
paramMap.put("apiname", "Open_queryPrinterStatus");
paramMap.put("sn", sn);
String msg;
try {
String resp = HttpUtil.post(URL, paramMap, 1000 * 5);
//成功 开机 {"msg":"ok","ret":0,"data":"在线,工作状态正常。","serverExecutedTime":4}
//成功 离线 {"msg":"ok","ret":0,"data":"离线。","serverExecutedTime":7}
JSONObject json = JSONUtil.parseObj(UnicodeUtil.toString(resp));
msg = json.getStr("data");
} catch (Exception e) {
msg = "未知错误";
}
return msg;
}
/** /**
* 保存打印记录 * 保存打印记录
* *
* @param orderId 订单Id
* @param config 打印机配置 * @param config 打印机配置
* @param bizType 业务类型 * @param bizType 业务类型
* @param printContent 打印内容 * @param printContent 打印内容
* @param respJson 打印机响应结果 * @param respJson 打印机响应结果
*/ */
@Async @Async
@Override public void save(PrintMachine config, String bizType, String printContent, Object respJson) {
public void save(Long orderId, PrintMachine config, String bizType, String printContent, String respJson) {
if (config == null) { if (config == null) {
return; return;
} }
@@ -91,41 +179,51 @@ public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMappe
int failFlag = 0; int failFlag = 0;
String respCode = "0"; String respCode = "0";
String respMsg = "打印中"; String respMsg = "打印中";
JSONObject resp = JSONObject.parseObject(respJson);
Map<Integer, String> yxxStatusMap = MapUtil.builder(0, "离线(设备上线后自动补打)").put(1, "在线").put(2, "获取失败").put(3, "未激活").put(4, "设备已禁用").build();
// 云想印 // 云想印
if ("".equals(config.getContentType())) { if ("".equals(config.getContentType())) {
int code = resp.getIntValue("code"); cn.hutool.json.JSONObject resp = JSONUtil.parseObj(respJson);
JSONObject respData = resp.getJSONObject("data"); int code = resp.getInt("code");
JSONObject data = respData.getJSONObject("data"); cn.hutool.json.JSONObject data = resp.getJSONObject("data").getJSONObject("data");
//设备状态0: 离线, 1: 在线, 2: 获取失败, 3:未激活, 4:设备已禁用 //设备状态0: 离线, 1: 在线, 2: 获取失败, 3:未激活, 4:设备已禁用
int status = data.getIntValue("status"); int status = data.getInt("status");
if (code != 0) { if (code != 0) {
failFlag = 1; failFlag = 1;
respCode = code + ""; respCode = code + "";
respMsg = resp.getString("msg"); respMsg = resp.getStr("msg");
} else if (status != 1) { } else if (status != 1) {
failFlag = 1; failFlag = 1;
respCode = code + ""; respCode = code + "";
respMsg = status + "_" + yxxStatusMap.get(status); respMsg = status + "_" + yxxStatusMap.get(status);
} }
if (code == 0) { if (code == 0) {
entity.setTaskId(respData.getString("orderId")); String taskId = resp.getJSONObject("data").getStr("orderId");
entity.setTaskId(taskId);
} }
// 飞鹅云打印机暂时没有适配先return不做打印记录 // 飞鹅云打印机暂时没有适配先return不做打印记录
} else if ("飞鹅".equals(config.getContentType())) { } else if ("飞鹅".equals(config.getContentType())) {
int ret = resp.getIntValue("ret"); cn.hutool.json.JSONObject resp = JSONUtil.parseObj(respJson);
int ret = resp.getInt("ret");
if (ret != 0) { if (ret != 0) {
failFlag = 1; failFlag = 1;
respCode = ret + ""; respCode = ret + "";
respMsg = resp.getString("msg"); respMsg = resp.getStr("msg");
} else { } else {
entity.setTaskId(resp.getString("data")); String printOrderId = resp.getStr("data");
entity.setTaskId(printOrderId);
} }
} else { } else {
// 其他打印机暂时没有适配先return不做打印记录 // 其他打印机暂时没有适配先return不做打印记录
return; return;
} }
entity.setBizType(bizType); entity.setBizType(bizType);
// entity.setCreateUserId(config.getCurrentUserId());
// entity.setCreateUserName(config.getCurrentUserName());
// if (StrUtil.isNotBlank(config.getCurrentUserNickName())) {
// entity.setCreateUserName(StrUtil.concat(true, config.getCurrentUserNickName(), " | ", config.getCurrentUserName()));
// }
entity.setPrintContent(printContent); entity.setPrintContent(printContent);
entity.setCreateTime(DateUtil.date().toLocalDateTime()); entity.setCreateTime(DateUtil.date().toLocalDateTime());
if (failFlag == 0) { if (failFlag == 0) {
@@ -135,213 +233,66 @@ public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMappe
entity.setRespCode(respCode); entity.setRespCode(respCode);
entity.setRespMsg(respMsg); entity.setRespMsg(respMsg);
super.save(entity); super.save(entity);
ThreadUtil.execAsync(() -> checkPrintStatus(orderId, config, entity));
}
/** // 云想印
* 类级别成员变量基于虚拟线程的固定大小5定时线程池 if ("云享印".equals(config.getContentType())) {
* // Java 21+ 虚拟线程工厂,支持命名 // 延迟3ms复查打印状态 (用户可以根据设备信息查询到当前设备的在线情况该接口只能提供参考设备的离线状态是在设备离线3分钟后才会生效)
*/ ThreadUtil.safeSleep(1000 * 5);
private final ScheduledExecutorService virtualThreadScheduler = Executors.newScheduledThreadPool( String jsonStr = checkPrintStatus(config.getAddress(), entity.getTaskId());
5, cn.hutool.json.JSONObject resp = JSONUtil.parseObj(jsonStr);
Thread.ofVirtual().name("print-query-vt-", 0).factory() int code = resp.getInt("code");
); if (code == 0) {
cn.hutool.json.JSONObject data = resp.getJSONObject("data");
/** boolean status = data.containsKey("status");
* 打印机状态查询(解决 retryFuture 爆红问题 + 虚拟线程 + 轮询重试) if (!status) {
*
* @param orderId 订单Id
* @param config 打印机配置
* @param entity 打印日志实体
*/
public void checkPrintStatus(Long orderId, PrintMachine config, PrintMachineLog entity) {
// 最大重试次数
int maxRetryTimes = 5;
AtomicInteger executedTimes = new AtomicInteger(0);
// 原子引用包装ScheduledFuture用于后续取消轮询
AtomicReference<ScheduledFuture<?>> retryFutureRef = new AtomicReference<>();
// 核心查询任务(修正后,逻辑内聚)
Runnable printQueryTask = () -> {
int currentTimes = executedTimes.incrementAndGet();
boolean isPrintSuccess = false;
boolean isLastTask = false;
try {
// 1. 云想印打印机状态查询
if ("云想印".equals(config.getContentType())) {
String jsonStr = yxyPrinter.checkPrintStatus(config.getAddress(), entity.getTaskId());
log.info("云想印打印状态查询结果(第{}次,虚拟线程:{}{}",
currentTimes, Thread.currentThread().getName(), jsonStr);
JSONObject resp = JSONObject.parseObject(jsonStr);
int code = resp.getIntValue("code");
if (code == 0) {
JSONObject data = resp.getJSONObject("data");
if (data.containsKey("status")) {
isPrintSuccess = data.getBooleanValue("status", false);
updatePrintLogEntity(entity, isPrintSuccess);
}
}
}
// 2. 飞鹅云打印机状态查询
else if ("飞鹅".equals(config.getContentType())) {
Boolean success = feiPrinter.checkFPrintStatus(entity.getTaskId());
if (success == null) {
entity.setFailFlag(1);
entity.setRespMsg("打印失败,未知错误");
entity.setPrintTime(null);
} else if (success) {
isPrintSuccess = true;
updatePrintLogEntity(entity, true);
} else {
String msg = feiPrinter.checkOnline(entity.getAddress());
if (msg.indexOf("在线,工作状态正常") > 0) {
isPrintSuccess = true;
updatePrintLogEntity(entity, true);
} else {
isPrintSuccess = false;
entity.setFailFlag(1);
entity.setPrintTime(null);
entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", msg));
}
}
} else {
log.info("打印类型为其他类型,终止打印状态查询轮询任务");
ScheduledFuture<?> future = retryFutureRef.get();
if (future != null && !future.isCancelled()) {
boolean cancelSuccess = future.cancel(false); // 取消后续轮询(不中断当前任务)
log.info("其他打印类型,取消轮询任务:{}", cancelSuccess ? "成功" : "失败");
}
}
// 3. 打印成功:取消后续轮询任务
if (isPrintSuccess) {
isLastTask = true;
ScheduledFuture<?> future = retryFutureRef.get();
if (future != null && !future.isCancelled()) {
future.cancel(false); // 不中断当前任务,仅取消后续任务
}
return; return;
} }
boolean success = data.getBool("status", false);
// 4. 达到最大重试次数:取消后续轮询任务 if (entity.getFailFlag() == 0 && success) {
if (currentTimes >= maxRetryTimes) { entity.setFailFlag(0);
isLastTask = true; entity.setRespMsg("打印成功");
ScheduledFuture<?> future = retryFutureRef.get(); entity.setPrintTime(entity.getCreateTime());
if (future != null && !future.isCancelled()) { } else if (entity.getFailFlag() == 1 && success) {
future.cancel(false); entity.setFailFlag(0);
} entity.setPrintTime(DateUtil.date().toLocalDateTime());
} entity.setRespMsg("打印成功");
// 如果设备在线 and 休眠5秒后查询结果是未打印即视为设备已离线云端3分钟后才会同步到离线信息
} catch (Exception e) { } else if (entity.getFailFlag() == 0 && !success) {
log.error("第{}次打印机状态查询异常(虚拟线程:{}", entity.setFailFlag(1);
currentTimes, Thread.currentThread().getName(), e); entity.setPrintTime(null);
// 异常时达到最大重试次数,同样取消任务 entity.setRespMsg("0_离线(设备上线后自动补打)");
if (currentTimes >= maxRetryTimes) { } else {
ScheduledFuture<?> future = retryFutureRef.get(); entity.setFailFlag(1);
if (future != null && !future.isCancelled()) { entity.setPrintTime(null);
boolean cancelSuccess = future.cancel(false); entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", entity.getRespMsg()));
log.info("查询异常且达到最大重试次数,取消轮询任务:{}", cancelSuccess ? "成功" : "失败");
}
}
} finally {
//仅当是最后一次任务时,才执行更新操作
if (isLastTask) {
super.updateById(entity);
updateOrderEntity(orderId, config, isPrintSuccess);
} }
super.updateById(entity);
} }
}; // 飞鹅云打印机
} else if ("飞鹅".equals(config.getContentType())) {
// 修正统一使用scheduleAtFixedRate避免任务重复执行 ThreadUtil.safeSleep(1000 * 5);
// 首次延迟10秒执行后续每隔30秒执行一次符合原逻辑的首次查询延迟后续轮询间隔 Boolean success = checkFPrintStatus(entity.getTaskId());
ScheduledFuture<?> retryFuture = virtualThreadScheduler.scheduleAtFixedRate( if (success == null) {
printQueryTask, entity.setFailFlag(1);
10, entity.setRespMsg("打印失败,未知错误");
30, } else if (success) {
TimeUnit.SECONDS entity.setFailFlag(0);
); entity.setPrintTime(DateUtil.date().toLocalDateTime());
entity.setRespMsg("打印成功");
// 修正先赋值AtomicReference再让任务可能执行避免线程安全隐患
retryFutureRef.set(retryFuture);
// 修正:关闭钩子仅注册一次(通过静态代码块或类初始化时注册,避免重复注册)
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", LocalDateTimeUtil.formatNormal(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);
}
/**
* 统一更新打印日志实体
*
* @param entity 打印日志实体
* @param isPrintSuccess 是否打印成功
*/
private void updatePrintLogEntity(PrintMachineLog entity, boolean isPrintSuccess) {
if (isPrintSuccess) {
entity.setFailFlag(0);
entity.setRespMsg("打印成功");
entity.setPrintTime(entity.getFailFlag() == 0 ? entity.getCreateTime() : DateUtil.date().toLocalDateTime());
} else {
entity.setFailFlag(1);
entity.setPrintTime(null);
if (entity.getFailFlag() == 0) {
entity.setRespMsg("0_离线(设备上线后自动补打)");
} else { } else {
entity.setRespMsg(entity.getRespMsg()); String msg = checkOnline(entity.getAddress());
} if (msg.indexOf("在线,工作状态正常") > 0) {
} entity.setFailFlag(0);
} entity.setPrintTime(DateUtil.date().toLocalDateTime());
entity.setRespMsg("打印成功");
// 静态标识,确保关闭钩子仅注册一次 } else {
private static volatile boolean shutdownHookRegistered = false; entity.setFailFlag(1);
// 锁对象,保证线程安全 entity.setPrintTime(null);
private static final Object HOOK_LOCK = new Object(); entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", msg));
/**
* 统一注册JVM关闭钩子仅执行一次
*/
private void registerShutdownHookOnce() {
if (!shutdownHookRegistered) {
synchronized (HOOK_LOCK) {
// 双重校验锁,避免多线程下重复注册
if (!shutdownHookRegistered) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
if (virtualThreadScheduler != null && !virtualThreadScheduler.isShutdown()) {
virtualThreadScheduler.shutdown();
try {
if (!virtualThreadScheduler.awaitTermination(10, TimeUnit.SECONDS)) {
log.warn("虚拟线程调度器10秒内未关闭强制关闭...");
virtualThreadScheduler.shutdownNow();
}
} catch (InterruptedException e) {
log.error("等待虚拟线程调度器终止时被中断,强制关闭", e);
virtualThreadScheduler.shutdownNow();
Thread.currentThread().interrupt(); // 保留中断状态
}
}
}, "PrinterScheduler-ShutdownHook"));
shutdownHookRegistered = true;
} }
} }
super.updateById(entity);
} }
} }
} }

View File

@@ -0,0 +1,125 @@
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;
}
}
}

View File

@@ -153,10 +153,6 @@
<foreach item="item" collection="idList" separator="," open="(" close=")"> <foreach item="item" collection="idList" separator="," open="(" close=")">
#{item} #{item}
</foreach> </foreach>
or t1.sync_id in
<foreach item="item" collection="idList" separator="," open="(" close=")">
#{item}
</foreach>
</if> </if>
order by t1.sort desc,t1.id desc order by t1.sort desc,t1.id desc
</select> </select>