Compare commits
39 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6180cc6801 | ||
| 4d6715ac9e | |||
| 36a6a21987 | |||
| ec0e281cfa | |||
| 59932b00f4 | |||
| e8b2924d60 | |||
| f3c42856fc | |||
| 0b02f66fb5 | |||
| acf04b8534 | |||
| cc8be70559 | |||
| ee635dcc32 | |||
| dc7146942b | |||
| 93b2309fcd | |||
| 0684e0459f | |||
| a4fc02dcc3 | |||
| d854ff9e30 | |||
| befd2942a2 | |||
| 7384b67c50 | |||
| 329d9dfb3d | |||
| 0ca2a6c7f8 | |||
| 58104d2afa | |||
| 3d6061342a | |||
| 76f6e7776e | |||
|
|
87797e5812 | ||
| 057f851dcf | |||
| dc3be2f1d8 | |||
| 232bfe84df | |||
| ff09e8e92b | |||
| 5d9c01f427 | |||
| 5d7aaa2dca | |||
| 5595a8009b | |||
| 233c226dca | |||
| 76c6e12c72 | |||
| 5b030ea361 | |||
| 266e782fc0 | |||
| aa7483b1b1 | |||
| 7eeeb8a30f | |||
| 70307e3833 | |||
| 8bde6b15f6 |
@@ -1,39 +0,0 @@
|
||||
package com.czg.controller;
|
||||
|
||||
import com.czg.account.entity.ShopUser;
|
||||
import com.czg.account.service.ShopInfoService;
|
||||
import com.czg.account.service.ShopUserFlowService;
|
||||
import com.czg.account.service.ShopUserService;
|
||||
import com.czg.account.service.TestService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
/**
|
||||
* @author GYJoker
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/notify/test")
|
||||
public class TestController {
|
||||
@Resource
|
||||
private ShopUserFlowService shopUserFlowService;
|
||||
@Resource
|
||||
private ShopInfoService shopInfoService;
|
||||
@Resource
|
||||
private ShopUserService shopUserService;
|
||||
@Resource
|
||||
private TestService testService;
|
||||
|
||||
@RequestMapping("/hello")
|
||||
public String hello() {
|
||||
shopUserFlowService.list().forEach(item -> {
|
||||
ShopUser shopUserInfo = shopUserService.getShopUserInfo(item.getShopId(), item.getUserId());
|
||||
if (shopUserInfo != null) {
|
||||
item.setShopUserId(shopUserInfo.getId());
|
||||
shopUserFlowService.updateById(item);
|
||||
}
|
||||
});
|
||||
// return testService.insertData();
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,18 @@ import com.czg.account.dto.menu.MenuAddDTO;
|
||||
import com.czg.account.dto.menu.MenuDelDTO;
|
||||
import com.czg.account.dto.menu.MenuEditDTO;
|
||||
import com.czg.account.entity.CashMenu;
|
||||
import com.czg.account.entity.QuickMenu;
|
||||
import com.czg.account.entity.SysMenu;
|
||||
import com.czg.account.service.QuickMenuService;
|
||||
import com.czg.account.service.SysMenuService;
|
||||
import com.czg.account.vo.MenuVO;
|
||||
import com.czg.annotation.SaAdminCheckPermission;
|
||||
import com.czg.annotation.SaAdminCheckRole;
|
||||
import com.czg.resp.CzgResult;
|
||||
import com.czg.sa.StpKit;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@@ -19,6 +23,7 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* 菜单管理
|
||||
*
|
||||
* @author zs
|
||||
*/
|
||||
@RestController
|
||||
@@ -27,9 +32,12 @@ public class MenuController {
|
||||
|
||||
@Resource
|
||||
private SysMenuService menuService;
|
||||
@Resource
|
||||
private QuickMenuService quickMenuService;
|
||||
|
||||
/**
|
||||
* 获取当前用户菜单列表
|
||||
*
|
||||
* @return 菜单结构
|
||||
*/
|
||||
@GetMapping
|
||||
@@ -40,6 +48,7 @@ public class MenuController {
|
||||
|
||||
/**
|
||||
* 收银机菜单
|
||||
*
|
||||
* @return 所有菜单
|
||||
*/
|
||||
@GetMapping("/list/cash")
|
||||
@@ -49,6 +58,7 @@ public class MenuController {
|
||||
|
||||
/**
|
||||
* 获取所有菜单
|
||||
*
|
||||
* @return 菜单结构
|
||||
*/
|
||||
@SaAdminCheckPermission(parentName = "菜单管理", value = "menu:list", name = "菜单列表")
|
||||
@@ -62,6 +72,7 @@ public class MenuController {
|
||||
|
||||
/**
|
||||
* 菜单详情
|
||||
*
|
||||
* @return 菜单结构
|
||||
*/
|
||||
@SaAdminCheckRole("管理员")
|
||||
@@ -73,6 +84,7 @@ public class MenuController {
|
||||
|
||||
/**
|
||||
* 菜单添加
|
||||
*
|
||||
* @return 是否成功
|
||||
*/
|
||||
@SaAdminCheckRole("管理员")
|
||||
@@ -84,6 +96,7 @@ public class MenuController {
|
||||
|
||||
/**
|
||||
* 菜单修改
|
||||
*
|
||||
* @return 是否成功
|
||||
*/
|
||||
@SaAdminCheckRole("管理员")
|
||||
@@ -95,12 +108,15 @@ public class MenuController {
|
||||
|
||||
/**
|
||||
* 菜单删除
|
||||
*
|
||||
* @return 是否成功
|
||||
*/
|
||||
@SaAdminCheckRole("管理员")
|
||||
@SaAdminCheckPermission(parentName = "菜单管理", value = "menu:del", name = "菜单删除")
|
||||
@DeleteMapping()
|
||||
@Transactional
|
||||
public CzgResult<Boolean> edit(@RequestBody @Validated MenuDelDTO menuDelDTO) {
|
||||
quickMenuService.remove(QueryWrapper.create().eq(QuickMenu::getMenuId, menuDelDTO.getId()));
|
||||
return CzgResult.success(menuService.removeById(menuDelDTO.getId()));
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
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(@RequestParam(required = false) Integer status,
|
||||
@RequestParam(required = false, defaultValue = "0") Integer isEdit) {
|
||||
List<QuickMenu> list = quickMenuService.list(QueryWrapper.create()
|
||||
.eq(QuickMenu::getShopId, StpKit.USER.getShopId())
|
||||
.eq(QuickMenu::getStatus, status)
|
||||
.orderBy(QuickMenu::getSort, true));
|
||||
if (isEdit.equals(0)) {
|
||||
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())));
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import com.czg.market.vo.InviteUserVO;
|
||||
import com.czg.market.vo.MkDistributionConfigVO;
|
||||
import com.czg.resp.CzgResult;
|
||||
import com.czg.sa.StpKit;
|
||||
import com.czg.task.DistributionTask;
|
||||
import com.czg.utils.AssertUtil;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -37,6 +38,22 @@ public class UDistributionController {
|
||||
private MkDistributionWithdrawFlowService withdrawFlowService;
|
||||
@Resource
|
||||
private MkDistributionFlowService distributionFlowService;
|
||||
@Resource
|
||||
private DistributionTask distributionTask;
|
||||
|
||||
/**
|
||||
* 分销员中心-获取配置
|
||||
*/
|
||||
@GetMapping("/task")
|
||||
public CzgResult<String> task(@RequestParam Long shopId) {
|
||||
try {
|
||||
distributionTask.deliver(shopId);
|
||||
} catch (Exception e) {
|
||||
return CzgResult.failure(e.getMessage());
|
||||
}
|
||||
return CzgResult.success("任务执行成功");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 分销员中心-获取配置
|
||||
|
||||
@@ -6,6 +6,7 @@ import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* market服务 任务总调度
|
||||
*
|
||||
* @author ww
|
||||
*/
|
||||
@Component
|
||||
@@ -18,9 +19,9 @@ public class AAMarketTasks {
|
||||
|
||||
|
||||
// 分销延时发放
|
||||
@Scheduled(fixedRate = 30000)
|
||||
@Scheduled(cron = "0 0 0/2 * * ? ")
|
||||
public void distributionTask() {
|
||||
distributionTask.deliver();
|
||||
distributionTask.deliver(null);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,6 +30,7 @@ public class AAMarketTasks {
|
||||
public void birthdayGiftTask() {
|
||||
birthdayGiftTask.deliver();
|
||||
}
|
||||
|
||||
//会员生日弹窗提醒重置 每年1月1日
|
||||
@Scheduled(cron = "0 0 0 1 1 ?")
|
||||
public void birthdayGiftRemindTask() {
|
||||
@@ -39,6 +41,7 @@ public class AAMarketTasks {
|
||||
//优惠券 过期
|
||||
@Resource
|
||||
private CouponTask couponTask;
|
||||
|
||||
//每天每小时的30分 0秒 执行
|
||||
@Scheduled(cron = "0 30 * * * ? ")
|
||||
public void couponTask() {
|
||||
@@ -48,6 +51,7 @@ public class AAMarketTasks {
|
||||
//会员奖励发放
|
||||
@Resource
|
||||
private MemberTask memberTask;
|
||||
|
||||
//每天1点 0分 0秒 执行
|
||||
@Scheduled(cron = "0 0 1 * * ? ")
|
||||
public void memberTask() {
|
||||
@@ -57,6 +61,7 @@ public class AAMarketTasks {
|
||||
//满减活动/限时折扣 处理任务状态 定时任务
|
||||
@Resource
|
||||
private ActivityStatusTask activityStatusTask;
|
||||
|
||||
//每天0点 0分 1秒 执行
|
||||
@Scheduled(cron = "1 0 0 * * ? ")
|
||||
public void activityStatusTask() {
|
||||
@@ -67,6 +72,7 @@ public class AAMarketTasks {
|
||||
//月累计 发送条数 累计金额
|
||||
@Resource
|
||||
private SmsShopMoneyTask smsShopMoneyTask;
|
||||
|
||||
//每月1号 0点 0分 1秒 执行
|
||||
@Scheduled(cron = "1 0 0 1 * ?")
|
||||
public void smsShopMoneyTask() {
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
package com.czg.task;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import com.czg.account.entity.ShopUser;
|
||||
import com.czg.account.entity.ShopInfo;
|
||||
import com.czg.account.service.ShopInfoService;
|
||||
import com.czg.account.service.ShopUserService;
|
||||
import com.czg.constant.TableValueConstant;
|
||||
import com.czg.constants.SystemConstants;
|
||||
import com.czg.exception.CzgException;
|
||||
import com.czg.market.entity.MkDistributionFlow;
|
||||
import com.czg.market.service.MkDistributionFlowService;
|
||||
import com.czg.market.service.MkDistributionUserService;
|
||||
import com.czg.market.service.OrderInfoService;
|
||||
import com.czg.order.entity.OrderInfo;
|
||||
import com.czg.service.market.enums.OrderStatusEnums;
|
||||
import com.czg.utils.FunUtils;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
@@ -40,8 +39,6 @@ public class DistributionTask {
|
||||
private OrderInfoService orderInfoService;
|
||||
@DubboReference
|
||||
private ShopInfoService shopInfoService;
|
||||
@DubboReference
|
||||
private ShopUserService shopUserService;
|
||||
|
||||
List<String> list = List.of(OrderStatusEnums.REFUND.getCode(), OrderStatusEnums.PART_REFUND.getCode());
|
||||
|
||||
@@ -51,44 +48,55 @@ public class DistributionTask {
|
||||
*/
|
||||
// @Scheduled(cron = "0 0 0 * * ?")
|
||||
// @Scheduled(fixedRate = 30000)
|
||||
public void deliver() {
|
||||
// 1. 订单完成支付时(判断是否分销)产生流水记录。
|
||||
// 2. 判断入账时间。
|
||||
// 3. 如果是 0 天,再去判断商户余额是否足够。够则入账,不足则不管。
|
||||
// 4. 流水增加应该入账的时间(订单产生时带入)
|
||||
// 5. 定时任务 应该是一天执行一次。查询待入账状态和应入账时间小于当前时间的记录,循环处理:并且判断商户余额是否足够,余额不足忽略处理;余额足够变为已入账并扣除商户余额。
|
||||
// 6. 订单产生退款时,去流水表查询该订单的流水记录,如果未入账改为已入账,并插入一条退款扣钱的流水。
|
||||
|
||||
// shopInfo 查余额>0
|
||||
// 循环 shopId 查询 如果金额不足 终止
|
||||
//
|
||||
LocalDateTime localDateTime = DateUtil.date().toLocalDateTime();
|
||||
distributionFlowService.list(new QueryWrapper()
|
||||
.eq(MkDistributionFlow::getStatus, TableValueConstant.DistributionFlow.Status.PENDING.getCode()).le(MkDistributionFlow::getDeliverTime, localDateTime)).forEach(item -> {
|
||||
FunUtils.safeRunVoid(() -> {
|
||||
log.info("开始处理延时分账, id: {}, orderNo: {}, 类型: {}", item.getId(), item.getOrderNo(), item.getType());
|
||||
|
||||
OrderInfo orderInfo = orderInfoService.getOne(new QueryWrapper().eq(OrderInfo::getOrderNo, item.getOrderNo()));
|
||||
if (orderInfo == null) {
|
||||
item.setStatus(TableValueConstant.DistributionFlow.Status.FAIL.getCode());
|
||||
distributionFlowService.updateById(item);
|
||||
log.warn("订单不存在, 订单号: {}", item.getOrderNo());
|
||||
return;
|
||||
public void deliver(Long shopId) {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
List<ShopInfo> shopInfos = shopInfoService.list(QueryWrapper.create()
|
||||
.eq(ShopInfo::getIsDeleted, SystemConstants.OneZero.ZERO)
|
||||
.isNotNull(ShopInfo::getExpireTime)
|
||||
.lt(ShopInfo::getExpireTime, now)
|
||||
.gt(ShopInfo::getAmount, BigDecimal.ZERO)
|
||||
.eq(ShopInfo::getId, shopId)
|
||||
);
|
||||
if (CollUtil.isEmpty(shopInfos)) {
|
||||
log.info("分销延时分账 无符合条件的店铺,无需处理分账");
|
||||
return;
|
||||
}
|
||||
for (ShopInfo shopInfo : shopInfos) {
|
||||
boolean breakCurrentShopFlow = false;
|
||||
List<MkDistributionFlow> flowList = distributionFlowService.list(new QueryWrapper()
|
||||
.eq(MkDistributionFlow::getShopId, shopInfo.getId())
|
||||
.eq(MkDistributionFlow::getStatus, TableValueConstant.DistributionFlow.Status.PENDING.getCode())
|
||||
.le(MkDistributionFlow::getDeliverTime, now)
|
||||
.orderBy(MkDistributionFlow::getId, true)
|
||||
);
|
||||
for (MkDistributionFlow item : flowList) {
|
||||
if (breakCurrentShopFlow) {
|
||||
break;
|
||||
}
|
||||
if (list.contains(orderInfo.getStatus())) {
|
||||
log.warn("订单已退款, 订单号: {}", item.getOrderNo());
|
||||
distributionUserService.refund(orderInfo.getId(), orderInfo.getOrderNo());
|
||||
} else {
|
||||
item.setStatus(TableValueConstant.DistributionFlow.Status.SUCCESS.getCode());
|
||||
ShopUser shopUser = shopUserService.getById(item.getDistributionUserId());
|
||||
distributionUserService.updateShopInfoAmount(orderInfo.getShopId(), item.getRewardAmount().negate(), orderInfo.getId(), TableValueConstant.DistributionAmountFlow.Type.SUB, "分销扣减");
|
||||
distributionUserService.updateIncome(item.getRewardAmount().negate(), item.getRewardAmount(), BigDecimal.ZERO,
|
||||
item.getDistributionUserId(), shopUser.getUserId(), item.getShopUserId(), item.getShopId(), item.getLevel());
|
||||
distributionFlowService.updateById(item);
|
||||
try {
|
||||
log.info("分销延时分账, id: {}, orderNo: {}, 类型: {}", item.getId(), item.getOrderNo(), item.getType());
|
||||
OrderInfo orderInfo = orderInfoService.getOne(new QueryWrapper().eq(OrderInfo::getOrderNo, item.getOrderNo()));
|
||||
if (orderInfo == null) {
|
||||
item.setStatus(TableValueConstant.DistributionFlow.Status.FAIL.getCode());
|
||||
distributionFlowService.updateById(item);
|
||||
log.warn("分销延时分账。订单不存在, 订单号: {}", item.getOrderNo());
|
||||
continue;
|
||||
}
|
||||
if (list.contains(orderInfo.getStatus())) {
|
||||
log.warn("分销延时分账。订单已退款, 订单号: {}", item.getOrderNo());
|
||||
distributionUserService.refund(orderInfo.getId(), orderInfo.getOrderNo());
|
||||
} else {
|
||||
item.setStatus(TableValueConstant.DistributionFlow.Status.SUCCESS.getCode());
|
||||
distributionUserService.distributionUserAmount(item, orderInfo);
|
||||
}
|
||||
} catch (CzgException e) {
|
||||
log.error("店铺{}:{}分销延时分账异常:{}", shopInfo.getId(), shopInfo.getShopName(), e.getMessage());
|
||||
breakCurrentShopFlow = true;
|
||||
} catch (Exception e) {
|
||||
log.error("店铺{}:{}分销延时分账异常", shopInfo.getId(), shopInfo.getShopName(), e);
|
||||
breakCurrentShopFlow = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ public class NotifyController {
|
||||
@Resource
|
||||
private MkShopConsumeDiscountRecordService consumeDiscountRecordService;
|
||||
|
||||
|
||||
//新客立减清除数据 测试用
|
||||
@RequestMapping("clear")
|
||||
public String clear(@RequestParam Integer shopId) {
|
||||
consumeDiscountRecordService.remove(new QueryWrapper().eq(MkShopConsumeDiscountRecord::getShopId, shopId));
|
||||
|
||||
@@ -21,7 +21,7 @@ public class StatisticTaskController {
|
||||
private StatisticTask statisticTask;
|
||||
|
||||
/**
|
||||
* 基础统计
|
||||
* 基础统计 预留重置统计
|
||||
*
|
||||
* @param date 日期yyyy-MM-dd
|
||||
*/
|
||||
|
||||
@@ -4,14 +4,15 @@ import com.czg.annotation.Debounce;
|
||||
import com.czg.order.dto.MkDistributionPayDTO;
|
||||
import com.czg.resp.CzgResult;
|
||||
import com.czg.service.order.service.DistributionPayService;
|
||||
import com.czg.system.service.SysParamsService;
|
||||
import com.czg.utils.AssertUtil;
|
||||
import com.czg.utils.ServletUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@@ -26,16 +27,6 @@ import java.util.Map;
|
||||
public class DistributionPayController {
|
||||
@Resource
|
||||
private DistributionPayService payService;
|
||||
@DubboReference
|
||||
private SysParamsService paramsService;
|
||||
|
||||
|
||||
// @PostMapping("/cashPay")
|
||||
// @Debounce(value = "#payParam.checkOrderPay.orderId")
|
||||
// public CzgResult<Object> cashPayOrder(@RequestHeader Long shopId, @Validated @RequestBody MkDistributionPayDTO payParam) {
|
||||
// payParam.setShopId(shopId);
|
||||
// return payService.cashPayOrder(payParam);
|
||||
// }
|
||||
|
||||
/**
|
||||
* 小程序支付
|
||||
@@ -59,26 +50,4 @@ public class DistributionPayController {
|
||||
AssertUtil.isBlank(payParam.getCode(), "微信code不为空");
|
||||
return CzgResult.success(payService.mchRecharge(ServletUtil.getClientIP(request), payParam));
|
||||
}
|
||||
//
|
||||
// /**
|
||||
// * 正扫
|
||||
// */
|
||||
// @PostMapping("/scanPay")
|
||||
// @Debounce(value = "#payParam.checkOrderPay.orderId")
|
||||
// public CzgResult<Map<String, Object>> scanPayOrder(@RequestHeader Long shopId, HttpServletRequest request, @Validated @RequestBody MkDistributionPayDTO payParam) {
|
||||
// payParam.setShopId(shopId);
|
||||
// return payService.scanPayOrder(ServletUtil.getClientIP(request), payParam);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * 反扫
|
||||
// * authCode 必填 扫描码
|
||||
// */
|
||||
// @PostMapping("/microPay")
|
||||
// @Debounce(value = "#payParam.checkOrderPay.orderId")
|
||||
// public CzgResult<Map<String, Object>> microPayOrder(@RequestHeader Long shopId, @Validated @RequestBody MkDistributionPayDTO payParam) {
|
||||
// payParam.setShopId(shopId);
|
||||
// return payService.microPayOrder(payParam);
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import com.czg.order.entity.MqLog;
|
||||
import com.czg.order.service.MqLogService;
|
||||
import com.czg.order.service.OrderInfoCustomService;
|
||||
import com.czg.order.service.OrderInfoRpcService;
|
||||
import com.czg.service.order.utils.FunUtil;
|
||||
import com.czg.service.RedisService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
@@ -31,7 +31,7 @@ public class OrderMqListener {
|
||||
@Resource
|
||||
private OrderInfoCustomService orderInfoCustomService;
|
||||
@Resource
|
||||
private FunUtil funUtil;
|
||||
private RedisService redisService;
|
||||
|
||||
/**
|
||||
* 订单上菜
|
||||
@@ -44,7 +44,7 @@ public class OrderMqListener {
|
||||
info = info.replace("UP_ORDER_DETAIL:", "");
|
||||
log.info("接收到修改菜品状态mq, info: {}", info);
|
||||
String finalInfo = info;
|
||||
funUtil.debounce("UP_ORDER_DETAIL:" + info, 5, () -> {
|
||||
redisService.debounce("UP_ORDER_DETAIL:" + info, 5, () -> {
|
||||
orderInfoCustomService.updateOrderDetailStatus(Long.valueOf(finalInfo));
|
||||
|
||||
});
|
||||
|
||||
@@ -40,7 +40,7 @@ public class OTimeTask {
|
||||
@Resource
|
||||
private CashierCartService cartService;
|
||||
@Resource
|
||||
private OrderPaymentService orderPaymentService;
|
||||
private OrderPaymentService paymentService;
|
||||
@Resource
|
||||
private GbOrderService gbOrderService;
|
||||
@Resource
|
||||
@@ -118,7 +118,7 @@ public class OTimeTask {
|
||||
|
||||
LocalDateTime tenMinutesAgo = LocalDateTime.now().minusMinutes(10);
|
||||
LocalDateTime thirdDayAgo = LocalDateTime.now().minusDays(3);
|
||||
List<OrderPayment> list = orderPaymentService.list(QueryWrapper.create()
|
||||
List<OrderPayment> list = paymentService.list(QueryWrapper.create()
|
||||
.gt(OrderPayment::getUpdateTime, thirdDayAgo)
|
||||
.lt(OrderPayment::getUpdateTime, tenMinutesAgo)
|
||||
.in(OrderPayment::getSourceType, ware)
|
||||
|
||||
@@ -25,7 +25,23 @@ spring:
|
||||
port: 5672
|
||||
username: chaozg
|
||||
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:
|
||||
application:
|
||||
name: order-server
|
||||
|
||||
@@ -54,7 +54,7 @@ public class FilteredNacosRegistry extends NacosRegistry {
|
||||
public void register(URL url) {
|
||||
// 1. 获取原始注册的方法列表
|
||||
String originalMethods = url.getParameter("methods");
|
||||
log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), originalMethods);
|
||||
// log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), originalMethods);
|
||||
if (originalMethods != null && !originalMethods.isEmpty()) {
|
||||
// 2. 过滤黑名单中的方法名
|
||||
List<String> filteredMethods = Arrays.stream(originalMethods.split(","))
|
||||
@@ -67,12 +67,12 @@ public class FilteredNacosRegistry extends NacosRegistry {
|
||||
// 3. 处理过滤后的结果
|
||||
if (filteredMethods.isEmpty()) {
|
||||
// 若所有方法都被过滤,直接终止注册(可选:根据业务决定是否保留服务注册)
|
||||
log.info("【过滤提示】服务 {} 所有方法均被过滤,终止注册", url.getServiceInterface());
|
||||
// log.info("【过滤提示】服务 {} 所有方法均被过滤,终止注册", url.getServiceInterface());
|
||||
return;
|
||||
} else {
|
||||
// 替换 URL 中的 methods 参数为过滤后的列表
|
||||
url = url.addParameter("methods", String.join(",", filteredMethods));
|
||||
log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), filteredMethods);
|
||||
// log.info("【过滤提示】服务 {} 注册方法:{}", url.getServiceInterface(), filteredMethods);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.czg.config;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.mybatisflex.core.audit.AuditManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@@ -17,8 +18,24 @@ public class MybatisFlexConfig {
|
||||
//设置 SQL 审计收集器
|
||||
AuditManager.setMessageCollector(auditMessage ->
|
||||
log.info("[sql] time: {}, size: {}, sql:\n{}",
|
||||
auditMessage.getElapsedTime(), auditMessage.getQueryCount(), auditMessage.getFullSql())
|
||||
);
|
||||
auditMessage.getElapsedTime(), auditMessage.getQueryCount(), compressSql(auditMessage.getFullSql())));
|
||||
}
|
||||
|
||||
/**
|
||||
* 精简SQL:去除多余换行、制表符、连续空格,保留语法必需空格
|
||||
*
|
||||
* @param originalSql 原始带换行/空格的SQL
|
||||
* @return 精简后的SQL
|
||||
*/
|
||||
public static String compressSql(String originalSql) {
|
||||
if (StrUtil.isBlank(originalSql)) {
|
||||
return "";
|
||||
}
|
||||
// 1. 替换所有换行、制表符为单个空格
|
||||
String tempSql = originalSql.replaceAll("\\r\\n|\\r|\\n|\\t", " ");
|
||||
// 2. 替换多个连续空格为单个空格
|
||||
tempSql = tempSql.replaceAll("\\s+", " ");
|
||||
// 3. 去除首尾空格
|
||||
return tempSql.trim();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,14 +6,15 @@ import jakarta.annotation.Resource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author GYJoker
|
||||
@@ -650,4 +651,106 @@ public class RedisService {
|
||||
}
|
||||
return JSON.parseArray(jsonStr, type);
|
||||
}
|
||||
|
||||
|
||||
public static int retryCount = 5;
|
||||
|
||||
/**
|
||||
* 执行任务并保证锁唯一
|
||||
*
|
||||
* @param supplier 业务逻辑
|
||||
* @param lockKey Redis锁的Key
|
||||
* @return 业务逻辑返回值
|
||||
*/
|
||||
public <T> T runFunAndCheckKey(Supplier<T> supplier, String lockKey) {
|
||||
String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId());
|
||||
try {
|
||||
// 尝试获取锁,超时时间 5 秒,防止死锁
|
||||
boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
|
||||
int count = 0;
|
||||
// 初始等待 10ms
|
||||
int retryDelay = 10;
|
||||
|
||||
while (!lock) {
|
||||
// 最多重试 10 次,大约 10 秒
|
||||
if (count++ > 50) {
|
||||
throw new RuntimeException("系统繁忙, 稍后再试");
|
||||
}
|
||||
Thread.sleep(retryDelay);
|
||||
// 指数退避,最大等待 200ms
|
||||
retryDelay = Math.min(retryDelay * 2, 200);
|
||||
lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
// 执行任务
|
||||
return supplier.get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("线程被中断", e);
|
||||
} catch (Exception e) {
|
||||
log.error("执行出错:{}", e.getMessage(), e);
|
||||
throw e;
|
||||
} finally {
|
||||
// 释放锁(使用 Lua 脚本确保原子性)
|
||||
unlock(lockKey, lockValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 Lua 脚本确保释放锁的原子性
|
||||
*
|
||||
* @param lockKey 锁的 Key
|
||||
* @param lockValue 当前线程的锁值
|
||||
*/
|
||||
private void unlock(String lockKey, String lockValue) {
|
||||
String luaScript =
|
||||
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
|
||||
"return redis.call('del', KEYS[1]) " +
|
||||
"else return 0 end";
|
||||
redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),
|
||||
Collections.singletonList(lockKey), lockValue);
|
||||
}
|
||||
|
||||
public static <T, R> R runFunAndRetry(
|
||||
Supplier<R> function,
|
||||
Function<R, Boolean> check, Consumer<R> errFun) {
|
||||
R result = function.get();
|
||||
boolean flag = check.apply(result);
|
||||
|
||||
while (flag && retryCount-- > 0) {
|
||||
result = function.get();
|
||||
flag = check.apply(result);
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
errFun.accept(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 防抖函数:在指定秒数内相同 Key 的任务只会执行一次
|
||||
*
|
||||
* @param key 防抖使用的 Redis Key
|
||||
* @param seconds 防抖时间(秒)
|
||||
* @param task 要执行的业务逻辑
|
||||
* @return true 执行了任务;false 在防抖期内被拦截
|
||||
*/
|
||||
public boolean debounce(String key, long seconds, Runnable task) {
|
||||
try {
|
||||
Boolean success = redisTemplate.opsForValue().setIfAbsent(
|
||||
key, "1", seconds, TimeUnit.SECONDS
|
||||
);
|
||||
|
||||
if (Boolean.TRUE.equals(success)) {
|
||||
task.run();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("防抖函数执行失败 key={} err={}", key, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -285,4 +285,13 @@ public class ShopInfoEditDTO {
|
||||
*/
|
||||
private Integer isCountStick;
|
||||
|
||||
/**
|
||||
* 企业id
|
||||
*/
|
||||
private String weworkId;
|
||||
/**
|
||||
* 企业接入链接
|
||||
*/
|
||||
private String weworkUrl;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
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;
|
||||
|
||||
}
|
||||
@@ -135,5 +135,13 @@ public class ShopConfig implements Serializable {
|
||||
|
||||
private String dingAppKey;
|
||||
private String dingAppSecret;
|
||||
/**
|
||||
* 企业id
|
||||
*/
|
||||
private String weworkId;
|
||||
/**
|
||||
* 企业接入链接
|
||||
*/
|
||||
private String weworkUrl;
|
||||
|
||||
}
|
||||
|
||||
@@ -371,4 +371,15 @@ public class ShopInfo implements Serializable {
|
||||
*/
|
||||
private BigDecimal amount;
|
||||
|
||||
/**
|
||||
* 企业id
|
||||
*/
|
||||
@Column(ignore = true)
|
||||
private String weworkId;
|
||||
/**
|
||||
* 企业接入链接
|
||||
*/
|
||||
@Column(ignore = true)
|
||||
private String weworkUrl;
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
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> {
|
||||
|
||||
}
|
||||
@@ -7,10 +7,12 @@ import com.czg.exception.CzgException;
|
||||
import com.czg.market.dto.MkDistributionUserDTO;
|
||||
import com.czg.market.dto.MkDistributionWithdrawFlowDTO;
|
||||
import com.czg.market.entity.MkDistributionConfig;
|
||||
import com.czg.market.entity.MkDistributionFlow;
|
||||
import com.czg.market.entity.MkDistributionUser;
|
||||
import com.czg.market.vo.DistributionCenterShopVO;
|
||||
import com.czg.market.vo.InviteUserVO;
|
||||
import com.czg.order.dto.MkDistributionPayDTO;
|
||||
import com.czg.order.entity.OrderInfo;
|
||||
import com.mybatisflex.core.paginate.Page;
|
||||
import com.mybatisflex.core.service.IService;
|
||||
|
||||
@@ -123,6 +125,8 @@ public interface MkDistributionUserService extends IService<MkDistributionUser>
|
||||
|
||||
void refund(Long orderId, String orderNo);
|
||||
|
||||
void distributionUserAmount(MkDistributionFlow item, OrderInfo orderInfo);
|
||||
|
||||
/**
|
||||
* 发放分销奖励
|
||||
*
|
||||
|
||||
@@ -1,267 +0,0 @@
|
||||
|
||||
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;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.czg.order.entity;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.czg.order.dto.LimitRateDTO;
|
||||
import com.mybatisflex.annotation.Column;
|
||||
import com.mybatisflex.annotation.Id;
|
||||
@@ -298,8 +300,10 @@ public class OrderInfo implements Serializable {
|
||||
private Integer isDel;
|
||||
|
||||
private String failMsg;
|
||||
|
||||
|
||||
/**
|
||||
* 打印状态 Json格式
|
||||
*/
|
||||
private String printStatus;
|
||||
|
||||
|
||||
public String getRefundRemark() {
|
||||
@@ -342,4 +346,41 @@ public class OrderInfo implements Serializable {
|
||||
// 如果需要加上抹零金额,可以取消下面这行注释
|
||||
// .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 = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import com.czg.order.entity.PrintMachineLog;
|
||||
* @since 2025-03-11
|
||||
*/
|
||||
public interface PrintMachineLogService extends IService<PrintMachineLog> {
|
||||
void save(PrintMachine config, String bizType, String printContent, Object respJson);
|
||||
void save(Long orderId, PrintMachine config, String bizType, String printContent, String respJson);
|
||||
|
||||
void save(PrintMachine config, String bizType, String printContent, String respJson);
|
||||
|
||||
}
|
||||
|
||||
@@ -41,6 +41,6 @@ public class OrderDetailSmallVO implements Serializable {
|
||||
private LocalDateTime startOrderTime;
|
||||
private LocalDateTime dishOutTime;
|
||||
private LocalDateTime foodServeTime;
|
||||
|
||||
private Integer isTemporary;
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,11 @@ public class OrderInfoVo implements Serializable {
|
||||
* 备注
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* 是否使用了霸王餐
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
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> {
|
||||
|
||||
}
|
||||
@@ -14,9 +14,9 @@ import com.czg.config.RedisCst;
|
||||
import com.czg.constants.ParamCodeCst;
|
||||
import com.czg.exception.CzgException;
|
||||
import com.czg.resp.CzgResult;
|
||||
import com.czg.service.RedisService;
|
||||
import com.czg.service.account.mapper.CallQueueMapper;
|
||||
import com.czg.service.account.mapper.CallTableMapper;
|
||||
import com.czg.service.account.util.FunUtil;
|
||||
import com.czg.service.account.util.WechatMiniMsgUtil;
|
||||
import com.czg.system.dto.SysParamsDTO;
|
||||
import com.czg.system.service.SysParamsService;
|
||||
@@ -51,10 +51,10 @@ public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable
|
||||
@Resource
|
||||
private ShopUserService shopUserService;
|
||||
@Resource
|
||||
private FunUtil funUtil;
|
||||
@Resource
|
||||
private StringRedisTemplate stringRedisTemplate;
|
||||
@Resource
|
||||
private RedisService redisService;
|
||||
@Resource
|
||||
private ShopInfoService shopInfoService;
|
||||
@Resource
|
||||
private CallConfigService callConfigService;
|
||||
@@ -228,13 +228,13 @@ public class CallTableServiceImpl extends ServiceImpl<CallTableMapper, CallTable
|
||||
}
|
||||
|
||||
private String getCallNumber(Long shopId, CallTable callTable) {
|
||||
return funUtil.runFunAndCheckKey(() -> {
|
||||
return redisService.runFunAndCheckKey(() -> {
|
||||
String callNumKey = RedisCst.getTableCallNumKey(shopId, callTable.getId());
|
||||
String value = stringRedisTemplate.opsForValue().get(callNumKey);
|
||||
AtomicReference<String> newVal = new AtomicReference<>("");
|
||||
// 初始化
|
||||
if (StrUtil.isBlank(value)) {
|
||||
Boolean setFlag = FunUtil.runFunAndRetry(() -> stringRedisTemplate.opsForValue().setIfAbsent(callNumKey, callTable.getStart().toString()), flag -> !flag,
|
||||
Boolean setFlag = RedisService.runFunAndRetry(() -> stringRedisTemplate.opsForValue().setIfAbsent(callNumKey, callTable.getStart().toString()), flag -> !flag,
|
||||
_ -> newVal.set(stringRedisTemplate.opsForValue().get(callNumKey)));
|
||||
|
||||
if (setFlag) {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
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{
|
||||
|
||||
}
|
||||
@@ -99,7 +99,7 @@ public class ShopInfoServiceImpl extends ServiceImpl<ShopInfoMapper, ShopInfo> i
|
||||
if (shopInfo == null) {
|
||||
throw new CzgException("店铺不存在");
|
||||
}
|
||||
if (shopInfo.getExpireTime() != null && (DateUtil.date().toLocalDateTime().isAfter(shopInfo.getExpireTime()))) {
|
||||
if (shopInfo.getExpireTime() != null && (LocalDateTime.now().isAfter(shopInfo.getExpireTime()))) {
|
||||
throw new CzgException("店铺已过期,请联系商家");
|
||||
}
|
||||
if (SystemConstants.OneZero.ZERO == shopInfo.getOnSale() || shopInfo.getStatus() != SystemConstants.OneZero.ONE) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.czg.market.vo.InviteUserVO;
|
||||
import com.czg.market.vo.MemberConfigVO;
|
||||
import com.czg.order.entity.OrderInfo;
|
||||
import com.czg.service.account.mapper.ShopUserMapper;
|
||||
import com.czg.utils.FunUtils;
|
||||
import com.czg.utils.PageUtil;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
@@ -61,6 +62,7 @@ public class ShopUserServiceImpl extends ServiceImpl<ShopUserMapper, ShopUser> i
|
||||
private MemberLevelConfigService memberLevelConfigService;
|
||||
@DubboReference
|
||||
private TbMemberConfigService memberConfigService;
|
||||
|
||||
private ShopUser getUserInfo(Long shopUserId) {
|
||||
ShopUser shopUser = queryChain().eq(ShopUser::getId, shopUserId).one();
|
||||
if (shopUser == null) {
|
||||
@@ -240,9 +242,13 @@ public class ShopUserServiceImpl extends ServiceImpl<ShopUserMapper, ShopUser> i
|
||||
shopUser.setBirthDay(null);
|
||||
}
|
||||
shopUser.setNickName(userInfo.getNickName());
|
||||
if (shopUser.getJoinTime() == null) {
|
||||
shopUser.setJoinTime(LocalDateTime.now());
|
||||
// if (shopUser.getJoinTime() == null) {
|
||||
// shopUser.setJoinTime(LocalDateTime.now());
|
||||
// }
|
||||
boolean b = saveOrUpdate(shopUser);
|
||||
if (b) {
|
||||
FunUtils.transactionSafeRun(() -> memberConfigService.joinMemberByCondition(shopId, userId, shopUser));
|
||||
}
|
||||
return saveOrUpdate(shopUser);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import com.czg.market.entity.MkPointsUser;
|
||||
import com.czg.market.entity.MkShopCouponRecord;
|
||||
import com.czg.market.service.MkPointsUserService;
|
||||
import com.czg.market.service.MkShopCouponRecordService;
|
||||
import com.czg.market.service.TbMemberConfigService;
|
||||
import com.czg.resp.CzgResult;
|
||||
import com.czg.service.RedisService;
|
||||
import com.czg.service.account.mapper.ShopConfigMapper;
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
package com.czg.service.account.util;
|
||||
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FunUtil {
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
public static int retryCount = 5;
|
||||
|
||||
/**
|
||||
* 执行任务并保证锁唯一
|
||||
* @param supplier 业务逻辑
|
||||
* @param lockKey Redis锁的Key
|
||||
* @return 业务逻辑返回值
|
||||
*/
|
||||
public <T> T runFunAndCheckKey(Supplier<T> supplier, String lockKey) {
|
||||
String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId());
|
||||
try {
|
||||
// 尝试获取锁,超时时间 5 秒,防止死锁
|
||||
boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
|
||||
int count = 0;
|
||||
// 初始等待 10ms
|
||||
int retryDelay = 10;
|
||||
|
||||
while (!lock) {
|
||||
// 最多重试 10 次,大约 10 秒
|
||||
if (count++ > 50) {
|
||||
throw new RuntimeException("系统繁忙, 稍后再试");
|
||||
}
|
||||
Thread.sleep(retryDelay);
|
||||
// 指数退避,最大等待 200ms
|
||||
retryDelay = Math.min(retryDelay * 2, 200);
|
||||
lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
// 执行任务
|
||||
return supplier.get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("线程被中断", e);
|
||||
} catch (Exception e) {
|
||||
log.error("执行出错:{}", e.getMessage(), e);
|
||||
throw e;
|
||||
} finally {
|
||||
// 释放锁(使用 Lua 脚本确保原子性)
|
||||
unlock(lockKey, lockValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 Lua 脚本确保释放锁的原子性
|
||||
* @param lockKey 锁的 Key
|
||||
* @param lockValue 当前线程的锁值
|
||||
*/
|
||||
private void unlock(String lockKey, String lockValue) {
|
||||
String luaScript =
|
||||
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
|
||||
"return redis.call('del', KEYS[1]) " +
|
||||
"else return 0 end";
|
||||
redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),
|
||||
Collections.singletonList(lockKey), lockValue);
|
||||
}
|
||||
|
||||
public static <T, R> R runFunAndRetry(
|
||||
Supplier<R> function,
|
||||
Function<R, Boolean> check, Consumer<R> errFun) {
|
||||
log.info("工具类开始执行函数");
|
||||
R result = function.get();
|
||||
boolean flag = check.apply(result);
|
||||
|
||||
log.info("执行结果: {}", result);
|
||||
|
||||
while (flag && retryCount-- > 0) {
|
||||
log.info("执行函数失败, 剩余尝试次数{}", retryCount);
|
||||
result = function.get();
|
||||
log.info("执行结果: {}", result);
|
||||
flag = check.apply(result);
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
errFun.accept(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
<?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>
|
||||
@@ -98,7 +98,7 @@
|
||||
<if test="amount != null">
|
||||
AND a.amount >= #{amount}
|
||||
</if>
|
||||
|
||||
group by a.id
|
||||
ORDER BY a.create_time DESC
|
||||
</select>
|
||||
<select id="selectVipCard_COUNT" resultType="java.lang.Long">
|
||||
|
||||
@@ -85,8 +85,6 @@ public class MkDistributionUserServiceImpl extends ServiceImpl<MkDistributionUse
|
||||
private ShopUserService shopUserService;
|
||||
@DubboReference
|
||||
private UserInfoService userInfoService;
|
||||
@DubboReference
|
||||
private OrderPaymentService orderPaymentService;
|
||||
@Resource
|
||||
private OrderInfoService orderInfoService;
|
||||
@DubboReference
|
||||
@@ -706,6 +704,16 @@ public class MkDistributionUserServiceImpl extends ServiceImpl<MkDistributionUse
|
||||
});
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@Override
|
||||
public void distributionUserAmount(MkDistributionFlow item, OrderInfo orderInfo) {
|
||||
ShopUser shopUser = shopUserService.getById(item.getDistributionUserId());
|
||||
updateShopInfoAmount(orderInfo.getShopId(), item.getRewardAmount().negate(), orderInfo.getId(), TableValueConstant.DistributionAmountFlow.Type.SUB, "分销扣减");
|
||||
updateIncome(item.getRewardAmount().negate(), item.getRewardAmount(), BigDecimal.ZERO,
|
||||
item.getDistributionUserId(), shopUser.getUserId(), item.getShopUserId(), item.getShopId(), item.getLevel());
|
||||
distributionFlowService.updateById(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void distribute(Long sourceId, String orderNo, BigDecimal amount, Long sourceUserId, Long shopId, String type) {
|
||||
MkDistributionDeliver deliver = new MkDistributionDeliver().setSourceId(sourceId).setOrderNo(orderNo).setShopId(shopId).setType(type).setStatus("success");
|
||||
|
||||
@@ -192,7 +192,7 @@ public class MkPointsUserServiceImpl extends ServiceImpl<MkPointsUserMapper, MkP
|
||||
.shopId(pointsUser.getShopId())
|
||||
.shopUserId(pointsUser.getShopUserId())
|
||||
.floatType(PointsConstant.SUB.getValue())
|
||||
.floatPoints(floatPoints)
|
||||
.floatPoints(-floatPoints)
|
||||
.balancePoints(pointsUser.getPointBalance())
|
||||
.sourceId(orderId.toString())
|
||||
.content(reason)
|
||||
|
||||
@@ -175,9 +175,9 @@ public class MkShopRechargeServiceImpl extends ServiceImpl<MkShopRechargeMapper,
|
||||
|
||||
// 标准充值
|
||||
if (rechargeDetailId != null) {
|
||||
MkShopRechargeDetail rechargeDetail = shopRechargeDetailService.getById(rechargeDetailId);
|
||||
shopUserMoneyEditDTO.setMoney(rechargeDetail.getAmount());
|
||||
FunUtils.asyncSafeRunVoid(() -> {
|
||||
MkShopRechargeDetail rechargeDetail = shopRechargeDetailService.getById(rechargeDetailId);
|
||||
shopUserMoneyEditDTO.setMoney(rechargeDetail.getAmount());
|
||||
// 赠送金额
|
||||
ShopUserMoneyEditDTO shopUserMoneyEditRewardDTO = new ShopUserMoneyEditDTO()
|
||||
.setId(shopUserId)
|
||||
|
||||
@@ -361,6 +361,7 @@ public class TbMemberConfigServiceImpl extends ServiceImpl<TbMemberConfigMapper,
|
||||
memberExpFlowService.save(expFlow);
|
||||
|
||||
upShopUser.setExperience(shopUser.getExperience() + exp);
|
||||
shopUser.setExperience(upShopUser.getExperience());
|
||||
// 修改会员等级
|
||||
MemberLevelConfig nextConfig = levelConfigService.getOne(new QueryWrapper().eq(MemberLevelConfig::getShopId, shopUser.getMainShopId())
|
||||
.gt(MemberLevelConfig::getExperienceValue, levelVO.getExperienceValue()).orderBy(MemberLevelConfig::getExperienceValue, true).limit(1));
|
||||
|
||||
@@ -320,7 +320,7 @@ public interface ShopOrderStatisticMapper extends BaseMapper<ShopOrderStatistic>
|
||||
" `order`.shop_id = #{shopId} " +
|
||||
" and trade_day = #{tradeDay} " +
|
||||
" and paid_time is not null" +
|
||||
" order by detail.product_id, detail.sku_id")
|
||||
" group by detail.product_id, detail.sku_id")
|
||||
List<ProductCostAmountVO> getOrderDetailProduct(Long shopId, LocalDate tradeDay);
|
||||
|
||||
}
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package com.czg.service.order.print;
|
||||
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.text.UnicodeUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
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.entity.HandoverRecord;
|
||||
import com.czg.account.entity.PrintMachine;
|
||||
import com.czg.account.entity.ShopInfo;
|
||||
import com.czg.account.service.ShopInfoService;
|
||||
import com.czg.order.entity.OrderDetail;
|
||||
import com.czg.order.entity.OrderInfo;
|
||||
import com.czg.service.order.enums.OrderStatusEnums;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.springframework.http.HttpEntity;
|
||||
@@ -20,24 +21,21 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
* 接口文档 <a href="https://help.feieyun.com/home/doc/zh;nav=1-1">
|
||||
*/
|
||||
@Component
|
||||
@Slf4j
|
||||
public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
||||
@Resource
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
@Resource
|
||||
private ShopInfoService shopInfoService;
|
||||
|
||||
// API 地址
|
||||
private static final String URL = "http://api.feieyun.cn/Api/Open/";
|
||||
@@ -72,10 +70,8 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
||||
String remark = orderDetail.getRemark();
|
||||
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());
|
||||
Object o = sendPrintRequest(machine.getAddress(), content, null, "1");
|
||||
printMachineLogService.save(machine, "新订单", content, o);
|
||||
|
||||
// printMachineLogService.save(machine, "新订单", , );
|
||||
String o = sendPrintRequest(machine.getAddress(), content, null, "1");
|
||||
printMachineLogService.save(orderInfo.getId(), machine, "新订单", content, o);
|
||||
}
|
||||
|
||||
|
||||
@@ -84,8 +80,8 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
||||
String remark = orderDetail.getRemark();
|
||||
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());
|
||||
Object o = sendPrintRequest(machine.getAddress(), content, null, "1");
|
||||
printMachineLogService.save(machine, "退款单", content, o);
|
||||
String o = sendPrintRequest(machine.getAddress(), content, null, "1");
|
||||
printMachineLogService.save(orderInfo.getId(), machine, "退款单", content, o);
|
||||
|
||||
}
|
||||
|
||||
@@ -106,8 +102,8 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
||||
.setRemark(orderInfo.getRemark())
|
||||
.setDiscountAmount(orderInfo.getOriginAmount().subtract(orderInfo.getPayAmount()).toPlainString());
|
||||
String string = buildOrderPrintData(printInfoDTO, detailList);
|
||||
Object o = sendPrintRequest(machine.getAddress(), string, null, printerNum);
|
||||
printMachineLogService.save(machine, "退款单", string, o);
|
||||
String o = sendPrintRequest(machine.getAddress(), string, null, printerNum);
|
||||
printMachineLogService.save(orderInfo.getId(), machine, "结算单", string, o);
|
||||
|
||||
}
|
||||
|
||||
@@ -134,24 +130,24 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
||||
.setRemark(orderInfo.getRemark())
|
||||
.setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString());
|
||||
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.setSeatAmount(orderInfo.getSeatAmount().toPlainString());
|
||||
}
|
||||
if(orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0){
|
||||
if (orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0) {
|
||||
printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString());
|
||||
}
|
||||
|
||||
String string = buildOrderPrintData(printInfoDTO, detailList);
|
||||
Object resp = sendPrintRequest(machine.getAddress(), string, null, printerNum);
|
||||
printMachineLogService.save(machine, "结算单", string, resp);
|
||||
String resp = sendPrintRequest(machine.getAddress(), string, null, printerNum);
|
||||
printMachineLogService.save(orderInfo.getId(), machine, "结算单", string, resp);
|
||||
}
|
||||
|
||||
@Override
|
||||
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 data = buildCallTicketData(shopName, tableName, callNum, preNum, codeUrl, shopNote, takeTime);
|
||||
Object resp = sendPrintRequest(machine.getAddress(), data, voiceJson, "1");
|
||||
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, "1");
|
||||
printMachineLogService.save(machine, "叫号单", data, resp);
|
||||
}
|
||||
|
||||
@@ -170,7 +166,7 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public <R> R sendPrintRequest(String address, String metaPrintData, String voiceData, String printerNum) {
|
||||
public String sendPrintRequest(String address, String metaPrintData, String voiceData, String printerNum) {
|
||||
log.info("飞蛾打印机开始发送打印请求, 设备地址: {}, 元数据: {}", address, metaPrintData);
|
||||
String time = String.valueOf(System.currentTimeMillis() / 1000);
|
||||
MultiValueMap<String, String> formData = new LinkedMultiValueMap<>();
|
||||
@@ -189,12 +185,68 @@ public class FeiPrinter extends PrinterHandler implements PrinterImpl {
|
||||
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(formData, headers);
|
||||
|
||||
String result = restTemplate.postForObject(URL, requestEntity, String.class);
|
||||
log.info("打印结果: {}", result);
|
||||
return (R) result;
|
||||
log.info("飞鹅打印结果: {}", result);
|
||||
return result;
|
||||
} catch (Exception e) {
|
||||
log.error("打印请求失败: {}", e.getMessage());
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import lombok.ToString;
|
||||
import lombok.experimental.Accessors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.dubbo.config.annotation.DubboReference;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
@@ -49,10 +50,9 @@ public abstract class PrinterHandler {
|
||||
@Setter
|
||||
protected PrinterHandler nextPrinter;
|
||||
protected String printerBrand;
|
||||
// 创建 ThreadLocal 变量
|
||||
private static final ThreadLocal<String> ERR_MSG = ThreadLocal.withInitial(() -> "");
|
||||
|
||||
|
||||
@Resource
|
||||
protected RestTemplate restTemplate;
|
||||
@Resource
|
||||
protected OrderDetailService orderDetailService;
|
||||
@Resource
|
||||
@@ -84,7 +84,11 @@ public abstract class PrinterHandler {
|
||||
@Getter
|
||||
public enum PrintTypeEnum {
|
||||
HANDOVER("交班", "handover"),
|
||||
ORDER("订单", "order"), ONE("菜品", "one"), CALL("叫号", "call"), ONE_AND_ORDER("菜品和结算单同时打印", "oneAndOrder"), PRE_ORDER("预结算单", "preOrder");
|
||||
ORDER("订单", "order"),
|
||||
ONE("菜品", "one"),
|
||||
CALL("叫号", "call"),
|
||||
ONE_AND_ORDER("菜品和结算单同时打印", "oneAndOrder"),
|
||||
PRE_ORDER("预结算单", "preOrder");
|
||||
private final String name;
|
||||
private final String code;
|
||||
|
||||
@@ -157,7 +161,7 @@ public abstract class PrinterHandler {
|
||||
|
||||
if (StrUtil.isNotEmpty(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 "normal" -> Arrays.asList("normal", "all");
|
||||
default -> new ArrayList<>();
|
||||
@@ -173,10 +177,6 @@ public abstract class PrinterHandler {
|
||||
wrapper.like(PrintMachine::getPrintType, printType);
|
||||
}
|
||||
List<PrintMachine> list = printMachineService.list(wrapper);
|
||||
// for (PrintMachine item : list) {
|
||||
// //实际打印以传递的参数为准
|
||||
// item.setPrintMethod(printMethod);
|
||||
// }
|
||||
if (list.isEmpty()) {
|
||||
log.error("店铺未配置打印机,店铺id: {}", shopId);
|
||||
return list;
|
||||
@@ -188,7 +188,8 @@ public abstract class PrinterHandler {
|
||||
|
||||
/**
|
||||
* 处理打印
|
||||
* @param data 传递的数据
|
||||
*
|
||||
* @param data 传递的数据
|
||||
* @param printTypeEnum order returnOrder preOrder one call handover
|
||||
*/
|
||||
public void handler(String data, PrintTypeEnum printTypeEnum) {
|
||||
@@ -314,7 +315,7 @@ public abstract class PrinterHandler {
|
||||
item.setNum(item.getNum().subtract(printDetailInfo.getPrintNum()));
|
||||
item.setReturnNum(item.getReturnNum().subtract(printDetailInfo.getPrintReturnNum()));
|
||||
orderDetails.add(item);
|
||||
}else {
|
||||
} else {
|
||||
orderDetails.add(item);
|
||||
}
|
||||
|
||||
@@ -334,34 +335,37 @@ public abstract class PrinterHandler {
|
||||
log.info("准备开始打印交班");
|
||||
if (data instanceof HandoverRecordDTO record) {
|
||||
handoverPrint(machine, record);
|
||||
}else {
|
||||
} else {
|
||||
throw new RuntimeException("传递数据类型有误");
|
||||
}
|
||||
break;
|
||||
case PrintTypeEnum.ORDER:
|
||||
log.info("准备开始打印订单");
|
||||
if (data instanceof OrderInfo orderInfo) {
|
||||
redisService.set("order:print:" + orderInfo.getId(),"", 180);
|
||||
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
|
||||
onlyFrontDesk(machine, false, orderInfo, orderDetailList);
|
||||
}else {
|
||||
} else {
|
||||
throw new RuntimeException("传递数据类型有误");
|
||||
}
|
||||
break;
|
||||
case PrintTypeEnum.PRE_ORDER:
|
||||
log.info("准备开始打印预结算订单");
|
||||
if (data instanceof OrderInfo orderInfo) {
|
||||
redisService.set("order:print:" + orderInfo.getId(),"", 180);
|
||||
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
|
||||
onlyFrontDesk(machine, true, orderInfo, orderDetailList);
|
||||
}else {
|
||||
} else {
|
||||
throw new RuntimeException("传递数据类型有误");
|
||||
}
|
||||
break;
|
||||
case PrintTypeEnum.ONE:
|
||||
log.info("准备开始打印菜品单");
|
||||
if (data instanceof OrderInfo orderInfo) {
|
||||
redisService.set("order:print:" + orderInfo.getId(),"", 180);
|
||||
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
|
||||
onlyKitchen(machine, orderInfo, orderDetailList);
|
||||
}else {
|
||||
} else {
|
||||
throw new RuntimeException("传递数据类型有误");
|
||||
}
|
||||
break;
|
||||
@@ -369,13 +373,14 @@ public abstract class PrinterHandler {
|
||||
log.info("准备开始打印叫号单");
|
||||
if (data instanceof CallQueue queue) {
|
||||
onlyCallNumPrint(machine, queue);
|
||||
}else {
|
||||
} else {
|
||||
throw new RuntimeException("传递数据类型有误");
|
||||
}
|
||||
break;
|
||||
case PrintTypeEnum.ONE_AND_ORDER:
|
||||
log.info("准备开始打印菜品以及结算单");
|
||||
if (data instanceof OrderInfo orderInfo) {
|
||||
redisService.set("order:print:" + orderInfo.getId(),"", 180);
|
||||
List<OrderDetail> orderDetailList = orderDetailService.list(new QueryWrapper().eq(OrderDetail::getOrderId, orderInfo.getId()));
|
||||
switch (machine.getPrintMethod()) {
|
||||
case "all":
|
||||
@@ -393,7 +398,7 @@ public abstract class PrinterHandler {
|
||||
default:
|
||||
throw new RuntimeException("打印方法有误");
|
||||
}
|
||||
}else {
|
||||
} else {
|
||||
throw new RuntimeException("传递数据类型有误");
|
||||
}
|
||||
|
||||
@@ -448,17 +453,7 @@ public abstract class PrinterHandler {
|
||||
log.info("台位费商品,不打印");
|
||||
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) {
|
||||
returnDishesPrint(orderInfo, item, machine);
|
||||
}
|
||||
@@ -469,8 +464,9 @@ public abstract class PrinterHandler {
|
||||
|
||||
// 保存已打印信息
|
||||
OrderDetail orderDetail = detailMap.get(item.getId());
|
||||
redisService.set(RedisCst.getPrintOrderDetailKey(orderInfo.getId(), item.getId()), JSONObject.toJSONString(new PrintDetailInfo().setPrint(item.getIsPrint() == 1).setDetailId(item.getId())
|
||||
.setPrintNum(orderDetail.getNum()).setPrintReturnNum(orderDetail.getReturnNum())), 3600 * 24);
|
||||
redisService.set(RedisCst.getPrintOrderDetailKey(orderInfo.getId(), item.getId()),
|
||||
JSONObject.toJSONString(new PrintDetailInfo().setPrint(item.getIsPrint() == 1).setDetailId(item.getId())
|
||||
.setPrintNum(orderDetail.getNum()).setPrintReturnNum(orderDetail.getReturnNum())), 3600 * 24);
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.czg.account.dto.HandoverRecordDTO;
|
||||
import com.czg.account.entity.HandoverRecord;
|
||||
import com.czg.order.entity.OrderDetail;
|
||||
import lombok.Data;
|
||||
import lombok.experimental.Accessors;
|
||||
@@ -69,10 +68,9 @@ public interface PrinterImpl {
|
||||
* @param metaPrintData 打印元数据
|
||||
* @param voiceData 语音信息
|
||||
* @param printNum 打印联数
|
||||
* @param <R> 返回数据类型
|
||||
* @return 打印结果
|
||||
*/
|
||||
<R> R sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum);
|
||||
String sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum);
|
||||
|
||||
/**
|
||||
* 获取当前打印机标签信息
|
||||
@@ -560,12 +558,6 @@ public interface PrinterImpl {
|
||||
return str;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("水煮肉片".length());
|
||||
System.out.println(StrUtil.repeat(' ', 8));
|
||||
System.out.println(StrUtil.fillAfter("水煮肉片", ' ', 21));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取填充字符串, 并且换行
|
||||
*
|
||||
|
||||
@@ -4,11 +4,10 @@ import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import com.czg.account.dto.HandoverRecordDTO;
|
||||
import com.czg.account.entity.HandoverRecord;
|
||||
import com.czg.account.entity.PrintMachine;
|
||||
import com.czg.account.entity.ShopInfo;
|
||||
import com.czg.account.service.ShopInfoService;
|
||||
import com.czg.order.entity.OrderDetail;
|
||||
import com.czg.order.entity.OrderInfo;
|
||||
import com.czg.service.order.enums.OrderStatusEnums;
|
||||
@@ -20,16 +19,14 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.LinkedMultiValueMap;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 云享印打印机
|
||||
*
|
||||
* 博实结-云享印打印机
|
||||
* 接口文档 <a href="https://bsj2.yuque.com/bsj/izhmfn/rr8b5g?#ZbE6s">
|
||||
* @author Administrator
|
||||
*/
|
||||
@Slf4j
|
||||
@@ -45,12 +42,6 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
||||
//APPSECRET
|
||||
private static final String APP_SECRET = "2022bsjZF544GAH";
|
||||
|
||||
@Resource
|
||||
private ShopInfoService shopInfoService;
|
||||
|
||||
@Resource
|
||||
private RestTemplate restTemplate;
|
||||
|
||||
public YxyPrinter() {
|
||||
super("云想印");
|
||||
}
|
||||
@@ -75,7 +66,7 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
||||
|
||||
|
||||
@Override
|
||||
public <R> R sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum) {
|
||||
public String sendPrintRequest(String address, String metaPrintData, String voiceData, String printNum) {
|
||||
log.info("开始请求云享印,请求数据:{}, {}", voiceData, metaPrintData);
|
||||
//设备名称
|
||||
//行为方式 1:只打印数据 2:只播放信息 3:打印数据并播放信息
|
||||
@@ -87,10 +78,9 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
||||
String time = String.valueOf(System.currentTimeMillis());
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
|
||||
Map<String, String> param = getToken(time, uuid);
|
||||
//参数
|
||||
MultiValueMap<String, Object> multiValueMap = new LinkedMultiValueMap<>();
|
||||
multiValueMap.add("token", param.get("TOKEN"));
|
||||
multiValueMap.add("token", getToken(time, uuid));
|
||||
multiValueMap.add("devName", address);
|
||||
multiValueMap.add("actWay", 3);
|
||||
multiValueMap.add("cn", printNum);
|
||||
@@ -108,7 +98,7 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
||||
httpEntity, String.class);
|
||||
|
||||
log.info("请求云享印成功,响应数据: {}", httpResponse);
|
||||
return (R) httpResponse;
|
||||
return httpResponse;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,10 +106,8 @@ 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(),
|
||||
orderDetail.getNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
|
||||
String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
|
||||
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
|
||||
Object resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
|
||||
// sendPrintRequest(voiceJson, 3, 1, machine.getAddress(), data);
|
||||
printMachineLogService.save(machine, "新订单", buildDishPrintData, resp);
|
||||
String resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
|
||||
printMachineLogService.save(orderInfo.getId(), machine, "新订单", buildDishPrintData, resp);
|
||||
|
||||
}
|
||||
|
||||
@@ -127,10 +115,9 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
||||
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(),
|
||||
orderDetail.getReturnNum(), orderDetail.getRemark(), orderDetail.getProGroupInfo(), orderDetail.getId(), orderDetail.isUrgent());
|
||||
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
|
||||
String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
|
||||
Object resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
|
||||
printMachineLogService.save(machine, "退款单", buildDishPrintData, resp);
|
||||
String resp = sendPrintRequest(machine.getAddress(), buildDishPrintData, voiceJson, "1");
|
||||
printMachineLogService.save(orderInfo.getId(), machine, "退款单", buildDishPrintData, resp);
|
||||
|
||||
}
|
||||
|
||||
@@ -141,18 +128,17 @@ 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())
|
||||
.setOriginalAmount(orderInfo.getOriginAmount().toPlainString()).setReturn(isReturn(orderInfo))
|
||||
.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());
|
||||
|
||||
String data = buildOrderPrintData(printInfoDTO, detailList);
|
||||
String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
|
||||
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
|
||||
String printerNum = "1";
|
||||
if (StrUtil.isNotBlank(machine.getPrintQty())) {
|
||||
printerNum = machine.getPrintQty().split("\\^")[1];
|
||||
}
|
||||
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum);
|
||||
printMachineLogService.save(machine, "新订单", data, resp);
|
||||
printMachineLogService.save(orderInfo.getId(), machine, "退款单", data, resp);
|
||||
|
||||
}
|
||||
|
||||
@@ -173,33 +159,38 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
||||
.setRemark(orderInfo.getRemark())
|
||||
.setDiscountAmount((orderInfo.getOriginAmount().add(orderInfo.getSeatAmount()).add(orderInfo.getPackFee()).subtract(orderInfo.getPayAmount())).toPlainString());
|
||||
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.setSeatAmount(orderInfo.getSeatAmount().toPlainString());
|
||||
}
|
||||
if(orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0){
|
||||
if (orderInfo.getPackFee().compareTo(BigDecimal.ZERO) > 0) {
|
||||
printInfoDTO.setPackFee(orderInfo.getPackFee().toPlainString());
|
||||
}
|
||||
|
||||
String data = buildOrderPrintData(printInfoDTO, detailList);
|
||||
String voiceJson = "{\"PbizType\":\"2\",\"content\":\"您有一笔新的订单,请及时处理\"}";
|
||||
// String voiceJson = "{\"bizType\":\"2\",\"content\":\"\"}";
|
||||
String printerNum = "1";
|
||||
if (StrUtil.isNotBlank(machine.getPrintQty())) {
|
||||
printerNum = machine.getPrintQty().split("\\^")[1];
|
||||
}
|
||||
String resp = sendPrintRequest(machine.getAddress(), data, voiceJson, printerNum);
|
||||
// printMachineLogService.save(machine, printType, data, resp);
|
||||
printMachineLogService.save(machine, "新订单", data, resp);
|
||||
printMachineLogService.save(orderInfo.getId(), machine, "结算单", data, resp);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* 叫号单打印
|
||||
*/
|
||||
@Override
|
||||
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);
|
||||
sendPrintRequest(machine.getAddress(), resp, null, "1");
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 交班单打印
|
||||
*/
|
||||
@Override
|
||||
protected void handoverPrint(PrintMachine machine, HandoverRecordDTO record) {
|
||||
String string = buildHandoverData(record);
|
||||
@@ -214,9 +205,9 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
||||
* @param requestId 请求ID,自定义
|
||||
* @return token信息
|
||||
*/
|
||||
private static Map<String, String> getToken(String timestamp, String requestId) {
|
||||
private static String getToken(String timestamp, String requestId) {
|
||||
StringBuilder token = new StringBuilder();
|
||||
StringBuilder encode = new StringBuilder();
|
||||
// StringBuilder encode = new StringBuilder();
|
||||
SortedMap<String, Object> map = new TreeMap<>();
|
||||
map.put("appId", APP_ID);
|
||||
map.put("timestamp", timestamp);
|
||||
@@ -226,13 +217,38 @@ public class YxyPrinter extends PrinterHandler implements PrinterImpl {
|
||||
String key = next.getKey();
|
||||
Object value = next.getValue();
|
||||
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<>();
|
||||
finalMap.put("ENCODE", encode.toString());
|
||||
finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase());
|
||||
return finalMap;
|
||||
// Map<String, String> finalMap = new HashMap<>();
|
||||
// finalMap.put("ENCODE", encode.toString());
|
||||
// finalMap.put("TOKEN", SecureUtil.md5(token + APP_SECRET).toUpperCase());
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public class DistributionPayServiceImpl implements DistributionPayService {
|
||||
private final BigDecimal MONEY_RATE = new BigDecimal("100");
|
||||
|
||||
@Resource
|
||||
private OrderPaymentService orderPaymentService;
|
||||
private OrderPaymentService paymentService;
|
||||
@Resource
|
||||
private MkDistributionConfigService configService;
|
||||
@Resource
|
||||
@@ -71,7 +71,7 @@ public class DistributionPayServiceImpl implements DistributionPayService {
|
||||
.setPayType(PayTypeConstants.PayType.PAY)
|
||||
.setOrderNo(payParam.getPlatformType() + IdUtil.getSnowflakeNextId())
|
||||
.setAmount(isRecharge ? payParam.getAmount() : detail.getPayAmount());
|
||||
orderPaymentService.save(orderPayment);
|
||||
paymentService.save(orderPayment);
|
||||
|
||||
InitInfo initInfo = new InitInfo().setConfig(detail);
|
||||
if (isRecharge) {
|
||||
|
||||
@@ -1516,6 +1516,9 @@ public class OrderInfoCustomServiceImpl implements OrderInfoCustomService {
|
||||
|
||||
@Override
|
||||
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()));
|
||||
if (orderInfo == null) {
|
||||
throw new CzgException("订单信息不存在");
|
||||
|
||||
@@ -22,10 +22,10 @@ public class OrderPaymentServiceImpl extends ServiceImpl<OrderPaymentMapper, Ord
|
||||
@Override
|
||||
public BigDecimal countMemberInAmount(Long shopId, Long shopUserId) {
|
||||
return getOneAs(QueryWrapper.create().select("IFNULL(sum(amount), 0) as total_amount")
|
||||
.eq(OrderPayment::getShopId, 143)
|
||||
.eq(OrderPayment::getShopId, shopId)
|
||||
.eq(OrderPayment::getSourceType, PayTypeConstants.SourceType.MEMBER_IN)
|
||||
.eq(OrderPayment::getPayType, PayTypeConstants.PayType.PAY)
|
||||
.eq(OrderPayment::getSourceId, 127452)
|
||||
.eq(OrderPayment::getSourceId, shopUserId)
|
||||
.eq(OrderPayment::getPayStatus, PayTypeConstants.PayStatus.SUCCESS), BigDecimal.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,32 +2,36 @@ package com.czg.service.order.service.impl;
|
||||
|
||||
import cn.hutool.core.bean.BeanUtil;
|
||||
import cn.hutool.core.date.DateUtil;
|
||||
import cn.hutool.core.map.MapProxy;
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.text.UnicodeUtil;
|
||||
import cn.hutool.core.date.LocalDateTimeUtil;
|
||||
import cn.hutool.core.thread.ThreadUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.crypto.SecureUtil;
|
||||
import cn.hutool.http.HttpUtil;
|
||||
import cn.hutool.json.JSONObject;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.czg.account.entity.PrintMachine;
|
||||
import com.github.pagehelper.PageHelper;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import com.mybatisflex.core.query.QueryWrapper;
|
||||
import com.mybatisflex.spring.service.impl.ServiceImpl;
|
||||
import com.czg.config.RedisCst;
|
||||
import com.czg.market.service.OrderInfoService;
|
||||
import com.czg.order.entity.OrderInfo;
|
||||
import com.czg.order.entity.PrintMachineLog;
|
||||
import com.czg.order.service.PrintMachineLogService;
|
||||
import com.czg.service.RedisService;
|
||||
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.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.codec.digest.DigestUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Async;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.*;
|
||||
import java.time.LocalDateTime;
|
||||
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
|
||||
@@ -38,139 +42,47 @@ import java.util.*;
|
||||
@Slf4j
|
||||
@Service
|
||||
@RequiredArgsConstructor
|
||||
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";
|
||||
public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMapper, PrintMachineLog> implements PrintMachineLogService {
|
||||
|
||||
public static final String URL = "http://api.feieyun.cn/Api/Open/";//不需要修改
|
||||
@Resource
|
||||
private OrderInfoService orderInfoService;
|
||||
@Lazy
|
||||
@Resource
|
||||
private YxyPrinter yxyPrinter;
|
||||
@Lazy
|
||||
@Resource
|
||||
private FeiPrinter feiPrinter;
|
||||
@Resource
|
||||
private RedisService redisService;
|
||||
|
||||
public static final String USER = "chaozhanggui2022@163.com";//*必填*:账号名
|
||||
public static final String UKEY = "UfWkhXxSkeSSscsU";//*必填*: 飞鹅云后台注册账号后生成的UKEY 【备注:这不是填打印机的KEY】
|
||||
public static final String SN = "960238952";//*必填*:打印机编号,必须要在管理后台里添加打印机或调用API接口添加之后,才能调用API
|
||||
/**
|
||||
* 获取TOKEN值
|
||||
*
|
||||
* @param timestamp 时间戳,13位
|
||||
* @param requestId 请求ID,自定义
|
||||
* @return
|
||||
*/
|
||||
private static Map<String, String> getToken(String timestamp, String requestId) {
|
||||
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("&");
|
||||
Map<Integer, String> yxxStatusMap = Map.of(
|
||||
0, "离线(设备上线后自动补打)",
|
||||
1, "在线",
|
||||
2, "获取失败",
|
||||
3, "未激活",
|
||||
4, "设备已禁用");
|
||||
|
||||
@Async
|
||||
@Override
|
||||
public void save(PrintMachine config, String bizType, String printContent, String respJson) {
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
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;
|
||||
save(null, config, bizType, printContent, respJson);
|
||||
}
|
||||
/**
|
||||
* 检查打印状态
|
||||
*
|
||||
* @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 bizType 业务类型
|
||||
* @param printContent 打印内容
|
||||
* @param respJson 打印机响应结果
|
||||
*/
|
||||
@Async
|
||||
public void save(PrintMachine config, String bizType, String printContent, Object respJson) {
|
||||
@Override
|
||||
public void save(Long orderId, PrintMachine config, String bizType, String printContent, String respJson) {
|
||||
if (config == null) {
|
||||
return;
|
||||
}
|
||||
@@ -179,51 +91,41 @@ public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMappe
|
||||
int failFlag = 0;
|
||||
String respCode = "0";
|
||||
String respMsg = "打印中";
|
||||
|
||||
Map<Integer, String> yxxStatusMap = MapUtil.builder(0, "离线(设备上线后自动补打)").put(1, "在线").put(2, "获取失败").put(3, "未激活").put(4, "设备已禁用").build();
|
||||
JSONObject resp = JSONObject.parseObject(respJson);
|
||||
// 云想印
|
||||
if ("云享印".equals(config.getContentType())) {
|
||||
cn.hutool.json.JSONObject resp = JSONUtil.parseObj(respJson);
|
||||
int code = resp.getInt("code");
|
||||
cn.hutool.json.JSONObject data = resp.getJSONObject("data").getJSONObject("data");
|
||||
if ("云想印".equals(config.getContentType())) {
|
||||
int code = resp.getIntValue("code");
|
||||
JSONObject respData = resp.getJSONObject("data");
|
||||
JSONObject data = respData.getJSONObject("data");
|
||||
//设备状态,0: 离线, 1: 在线, 2: 获取失败, 3:未激活, 4:设备已禁用
|
||||
int status = data.getInt("status");
|
||||
int status = data.getIntValue("status");
|
||||
if (code != 0) {
|
||||
failFlag = 1;
|
||||
respCode = code + "";
|
||||
respMsg = resp.getStr("msg");
|
||||
respMsg = resp.getString("msg");
|
||||
} else if (status != 1) {
|
||||
failFlag = 1;
|
||||
respCode = code + "";
|
||||
|
||||
respMsg = status + "_" + yxxStatusMap.get(status);
|
||||
}
|
||||
if (code == 0) {
|
||||
String taskId = resp.getJSONObject("data").getStr("orderId");
|
||||
entity.setTaskId(taskId);
|
||||
entity.setTaskId(respData.getString("orderId"));
|
||||
}
|
||||
// 飞鹅云打印机暂时没有适配,先return不做打印记录
|
||||
} else if ("飞鹅".equals(config.getContentType())) {
|
||||
cn.hutool.json.JSONObject resp = JSONUtil.parseObj(respJson);
|
||||
int ret = resp.getInt("ret");
|
||||
int ret = resp.getIntValue("ret");
|
||||
if (ret != 0) {
|
||||
failFlag = 1;
|
||||
respCode = ret + "";
|
||||
respMsg = resp.getStr("msg");
|
||||
respMsg = resp.getString("msg");
|
||||
} else {
|
||||
String printOrderId = resp.getStr("data");
|
||||
entity.setTaskId(printOrderId);
|
||||
entity.setTaskId(resp.getString("data"));
|
||||
}
|
||||
} else {
|
||||
// 其他打印机暂时没有适配,先return不做打印记录
|
||||
return;
|
||||
}
|
||||
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.setCreateTime(DateUtil.date().toLocalDateTime());
|
||||
if (failFlag == 0) {
|
||||
@@ -233,66 +135,213 @@ public class PrintMachineLogServiceImpl extends ServiceImpl<PrintMachineLogMappe
|
||||
entity.setRespCode(respCode);
|
||||
entity.setRespMsg(respMsg);
|
||||
super.save(entity);
|
||||
ThreadUtil.execAsync(() -> checkPrintStatus(orderId, config, entity));
|
||||
}
|
||||
|
||||
// 云想印
|
||||
if ("云享印".equals(config.getContentType())) {
|
||||
// 延迟3ms,复查打印状态 (用户可以根据设备信息查询到当前设备的在线情况(注:该接口只能提供参考,设备的离线状态是在设备离线3分钟后才会生效))
|
||||
ThreadUtil.safeSleep(1000 * 5);
|
||||
String jsonStr = checkPrintStatus(config.getAddress(), entity.getTaskId());
|
||||
cn.hutool.json.JSONObject resp = JSONUtil.parseObj(jsonStr);
|
||||
int code = resp.getInt("code");
|
||||
if (code == 0) {
|
||||
cn.hutool.json.JSONObject data = resp.getJSONObject("data");
|
||||
boolean status = data.containsKey("status");
|
||||
if (!status) {
|
||||
/**
|
||||
* 类级别成员变量:基于虚拟线程的固定大小(5)定时线程池
|
||||
* // Java 21+ 虚拟线程工厂,支持命名
|
||||
*/
|
||||
private final ScheduledExecutorService virtualThreadScheduler = Executors.newScheduledThreadPool(
|
||||
5,
|
||||
Thread.ofVirtual().name("print-query-vt-", 0).factory()
|
||||
);
|
||||
|
||||
/**
|
||||
* 打印机状态查询(解决 retryFuture 爆红问题 + 虚拟线程 + 轮询重试)
|
||||
*
|
||||
* @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;
|
||||
}
|
||||
boolean success = data.getBool("status", false);
|
||||
if (entity.getFailFlag() == 0 && success) {
|
||||
entity.setFailFlag(0);
|
||||
entity.setRespMsg("打印成功");
|
||||
entity.setPrintTime(entity.getCreateTime());
|
||||
} else if (entity.getFailFlag() == 1 && success) {
|
||||
entity.setFailFlag(0);
|
||||
entity.setPrintTime(DateUtil.date().toLocalDateTime());
|
||||
entity.setRespMsg("打印成功");
|
||||
// 如果设备在线 and 休眠5秒后查询结果是未打印,即视为设备已离线,云端3分钟后才会同步到离线信息
|
||||
} else if (entity.getFailFlag() == 0 && !success) {
|
||||
entity.setFailFlag(1);
|
||||
entity.setPrintTime(null);
|
||||
entity.setRespMsg("0_离线(设备上线后自动补打)");
|
||||
} else {
|
||||
entity.setFailFlag(1);
|
||||
entity.setPrintTime(null);
|
||||
entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", entity.getRespMsg()));
|
||||
|
||||
// 4. 达到最大重试次数:取消后续轮询任务
|
||||
if (currentTimes >= maxRetryTimes) {
|
||||
isLastTask = true;
|
||||
ScheduledFuture<?> future = retryFutureRef.get();
|
||||
if (future != null && !future.isCancelled()) {
|
||||
future.cancel(false);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("第{}次打印机状态查询异常(虚拟线程:{})",
|
||||
currentTimes, Thread.currentThread().getName(), e);
|
||||
// 异常时达到最大重试次数,同样取消任务
|
||||
if (currentTimes >= maxRetryTimes) {
|
||||
ScheduledFuture<?> future = retryFutureRef.get();
|
||||
if (future != null && !future.isCancelled()) {
|
||||
boolean cancelSuccess = future.cancel(false);
|
||||
log.info("查询异常且达到最大重试次数,取消轮询任务:{}", cancelSuccess ? "成功" : "失败");
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
//仅当是最后一次任务时,才执行更新操作
|
||||
if (isLastTask) {
|
||||
super.updateById(entity);
|
||||
updateOrderEntity(orderId, config, isPrintSuccess);
|
||||
}
|
||||
super.updateById(entity);
|
||||
}
|
||||
// 飞鹅云打印机
|
||||
} else if ("飞鹅".equals(config.getContentType())) {
|
||||
ThreadUtil.safeSleep(1000 * 5);
|
||||
Boolean success = checkFPrintStatus(entity.getTaskId());
|
||||
if (success == null) {
|
||||
entity.setFailFlag(1);
|
||||
entity.setRespMsg("打印失败,未知错误");
|
||||
} else if (success) {
|
||||
entity.setFailFlag(0);
|
||||
entity.setPrintTime(DateUtil.date().toLocalDateTime());
|
||||
entity.setRespMsg("打印成功");
|
||||
};
|
||||
|
||||
// 修正:统一使用scheduleAtFixedRate,避免任务重复执行
|
||||
// 首次延迟10秒执行,后续每隔30秒执行一次(符合原逻辑的首次查询延迟,后续轮询间隔)
|
||||
ScheduledFuture<?> retryFuture = virtualThreadScheduler.scheduleAtFixedRate(
|
||||
printQueryTask,
|
||||
10,
|
||||
30,
|
||||
TimeUnit.SECONDS
|
||||
);
|
||||
|
||||
// 修正:先赋值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 {
|
||||
String msg = checkOnline(entity.getAddress());
|
||||
if (msg.indexOf("在线,工作状态正常") > 0) {
|
||||
entity.setFailFlag(0);
|
||||
entity.setPrintTime(DateUtil.date().toLocalDateTime());
|
||||
entity.setRespMsg("打印成功");
|
||||
} else {
|
||||
entity.setFailFlag(1);
|
||||
entity.setPrintTime(null);
|
||||
entity.setRespMsg(StrUtil.concat(true, "打印失败,", "_", msg));
|
||||
}
|
||||
entity.setRespMsg(entity.getRespMsg());
|
||||
}
|
||||
super.updateById(entity);
|
||||
}
|
||||
}
|
||||
|
||||
// 静态标识,确保关闭钩子仅注册一次
|
||||
private static volatile boolean shutdownHookRegistered = false;
|
||||
// 锁对象,保证线程安全
|
||||
private static final Object HOOK_LOCK = new Object();
|
||||
|
||||
/**
|
||||
* 统一注册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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ public class ShopOrderStatisticServiceImpl extends ServiceImpl<ShopOrderStatisti
|
||||
*/
|
||||
private BigDecimal getProductCostAmount(Long shopId, LocalDate day) {
|
||||
BigDecimal productCostAmount = BigDecimal.ZERO;
|
||||
////获取orderDetail信息 productId skuId 数量
|
||||
//获取orderDetail信息 productId skuId 数量
|
||||
List<ProductCostAmountVO> orderDetailProduct = mapper.getOrderDetailProduct(shopId, day);
|
||||
orderDetailProduct = orderDetailProduct.stream()
|
||||
.filter(Objects::nonNull)
|
||||
|
||||
@@ -14,10 +14,7 @@ import org.springframework.stereotype.Service;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.LocalDate;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -167,44 +164,99 @@ public class ShopProdStatisticServiceImpl extends ServiceImpl<ShopProdStatisticM
|
||||
* @return 合并后的数据
|
||||
*/
|
||||
private List<ShopProdStatistic> mergeProdStatistic(List<ShopProdStatistic> realTimeDataByDay, List<ShopProdStatistic> dateRange) {
|
||||
if (realTimeDataByDay == null) {
|
||||
realTimeDataByDay = new ArrayList<>();
|
||||
}
|
||||
if (dateRange == null) {
|
||||
dateRange = new ArrayList<>();
|
||||
}
|
||||
// 1. 使用防御性编程,确保非空列表
|
||||
List<ShopProdStatistic> realTimeData = Optional.ofNullable(realTimeDataByDay).orElseGet(ArrayList::new);
|
||||
List<ShopProdStatistic> rangeData = Optional.ofNullable(dateRange).orElseGet(ArrayList::new);
|
||||
|
||||
return Stream.concat(realTimeDataByDay.stream(), dateRange.stream())
|
||||
calculateValidData(realTimeData);
|
||||
calculateValidData(rangeData);
|
||||
|
||||
return Stream.concat(realTimeData.stream(), rangeData.stream())
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toMap(
|
||||
ShopProdStatistic::getProdId,
|
||||
Function.identity(),
|
||||
(stat1, stat2) -> {
|
||||
// 创建合并后的对象
|
||||
ShopProdStatistic merged = new ShopProdStatistic();
|
||||
merged.setId(stat1.getId());
|
||||
merged.setShopId(stat1.getShopId());
|
||||
merged.setProdId(stat1.getProdId());
|
||||
merged.setProductName(stat1.getProductName());
|
||||
|
||||
// 安全处理BigDecimal相加,处理null值
|
||||
merged.setSaleCount(safeAdd(stat1.getSaleCount(), stat2.getSaleCount()));
|
||||
merged.setSaleAmount(safeAdd(stat1.getSaleAmount(), stat2.getSaleAmount()));
|
||||
merged.setRefundCount(safeAdd(stat1.getRefundCount(), stat2.getRefundCount()));
|
||||
merged.setRefundAmount(safeAdd(stat1.getRefundAmount(), stat2.getRefundAmount()));
|
||||
|
||||
return merged;
|
||||
}
|
||||
this::mergeStatistics
|
||||
))
|
||||
.values()
|
||||
.stream()
|
||||
.peek(this::recalculateValidData)
|
||||
.toList();
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算有效销售数据
|
||||
*/
|
||||
private void calculateValidData(List<ShopProdStatistic> statistics) {
|
||||
if (statistics == null || statistics.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
statistics.forEach(stat -> {
|
||||
if (stat != null) {
|
||||
stat.setValidSaleCount(safeSubtract(stat.getSaleCount(), stat.getRefundCount()));
|
||||
stat.setValidSaleAmount(safeSubtract(stat.getValidSaleAmount(), stat.getRefundAmount()));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 合并两个统计对象
|
||||
*/
|
||||
private ShopProdStatistic mergeStatistics(ShopProdStatistic stat1, ShopProdStatistic stat2) {
|
||||
// 使用第一个非空对象作为基准
|
||||
ShopProdStatistic baseStat = Optional.ofNullable(stat1).orElse(stat2);
|
||||
ShopProdStatistic otherStat = stat1 == null ? null : stat2;
|
||||
|
||||
if (otherStat == null) {
|
||||
return baseStat;
|
||||
}
|
||||
|
||||
ShopProdStatistic merged = new ShopProdStatistic();
|
||||
|
||||
// 设置基本信息(优先使用非空值)
|
||||
merged.setId(baseStat.getId());
|
||||
merged.setShopId(baseStat.getShopId());
|
||||
merged.setProdId(baseStat.getProdId());
|
||||
merged.setProductName(
|
||||
Optional.ofNullable(baseStat.getProductName())
|
||||
.orElse(otherStat.getProductName())
|
||||
);
|
||||
|
||||
// 合并数值字段
|
||||
merged.setSaleCount(safeAdd(baseStat.getSaleCount(), otherStat.getSaleCount()));
|
||||
merged.setSaleAmount(safeAdd(baseStat.getSaleAmount(), otherStat.getSaleAmount()));
|
||||
merged.setRefundCount(safeAdd(baseStat.getRefundCount(), otherStat.getRefundCount()));
|
||||
merged.setRefundAmount(safeAdd(baseStat.getRefundAmount(), otherStat.getRefundAmount()));
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新计算合并后的有效数据
|
||||
*/
|
||||
private void recalculateValidData(ShopProdStatistic stat) {
|
||||
if (stat != null) {
|
||||
stat.setValidSaleCount(safeSubtract(stat.getSaleCount(), stat.getRefundCount()));
|
||||
stat.setValidSaleAmount(safeSubtract(stat.getSaleAmount(), stat.getRefundAmount()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全的BigDecimal加法(处理null值)
|
||||
*/
|
||||
private BigDecimal safeAdd(BigDecimal num1, BigDecimal num2) {
|
||||
BigDecimal safeNum1 = num1 != null ? num1 : BigDecimal.ZERO;
|
||||
BigDecimal safeNum2 = num2 != null ? num2 : BigDecimal.ZERO;
|
||||
BigDecimal safeNum1 = Optional.ofNullable(num1).orElse(BigDecimal.ZERO);
|
||||
BigDecimal safeNum2 = Optional.ofNullable(num2).orElse(BigDecimal.ZERO);
|
||||
return safeNum1.add(safeNum2);
|
||||
}
|
||||
|
||||
/**
|
||||
* 安全的BigDecimal减法(处理null值)
|
||||
*/
|
||||
private BigDecimal safeSubtract(BigDecimal num1, BigDecimal num2) {
|
||||
BigDecimal safeNum1 = Optional.ofNullable(num1).orElse(BigDecimal.ZERO);
|
||||
BigDecimal safeNum2 = Optional.ofNullable(num2).orElse(BigDecimal.ZERO);
|
||||
return safeNum1.subtract(safeNum2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,125 +0,0 @@
|
||||
package com.czg.service.order.utils;
|
||||
|
||||
import cn.hutool.core.lang.func.Func0;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.core.script.DefaultRedisScript;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
/**
|
||||
* @author Administrator
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class FunUtil {
|
||||
@Resource
|
||||
private RedisTemplate<String, Object> redisTemplate;
|
||||
public static int retryCount = 5;
|
||||
|
||||
/**
|
||||
* 执行任务并保证锁唯一
|
||||
* @param supplier 业务逻辑
|
||||
* @param lockKey Redis锁的Key
|
||||
* @return 业务逻辑返回值
|
||||
*/
|
||||
public <T> T runFunAndCheckKey(Supplier<T> supplier, String lockKey) {
|
||||
String lockValue = String.valueOf(System.nanoTime() + Thread.currentThread().threadId());
|
||||
try {
|
||||
// 尝试获取锁,超时时间 5 秒,防止死锁
|
||||
boolean lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
|
||||
int count = 0;
|
||||
// 初始等待 10ms
|
||||
int retryDelay = 10;
|
||||
|
||||
while (!lock) {
|
||||
// 最多重试 10 次,大约 10 秒
|
||||
if (count++ > 50) {
|
||||
throw new RuntimeException("系统繁忙, 稍后再试");
|
||||
}
|
||||
Thread.sleep(retryDelay);
|
||||
// 指数退避,最大等待 200ms
|
||||
retryDelay = Math.min(retryDelay * 2, 200);
|
||||
lock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
// 执行任务
|
||||
return supplier.get();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
throw new RuntimeException("线程被中断", e);
|
||||
} catch (Exception e) {
|
||||
log.error("执行出错:{}", e.getMessage(), e);
|
||||
throw e;
|
||||
} finally {
|
||||
// 释放锁(使用 Lua 脚本确保原子性)
|
||||
unlock(lockKey, lockValue);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 使用 Lua 脚本确保释放锁的原子性
|
||||
* @param lockKey 锁的 Key
|
||||
* @param lockValue 当前线程的锁值
|
||||
*/
|
||||
private void unlock(String lockKey, String lockValue) {
|
||||
String luaScript =
|
||||
"if redis.call('get', KEYS[1]) == ARGV[1] then " +
|
||||
"return redis.call('del', KEYS[1]) " +
|
||||
"else return 0 end";
|
||||
redisTemplate.execute(new DefaultRedisScript<>(luaScript, Long.class),
|
||||
Collections.singletonList(lockKey), lockValue);
|
||||
}
|
||||
|
||||
public static <T, R> R runFunAndRetry(
|
||||
Supplier<R> function,
|
||||
Function<R, Boolean> check, Consumer<R> errFun) {
|
||||
log.info("工具类开始执行函数");
|
||||
R result = function.get();
|
||||
boolean flag = check.apply(result);
|
||||
|
||||
log.info("执行结果: {}", result);
|
||||
|
||||
while (flag && retryCount-- > 0) {
|
||||
log.info("执行函数失败, 剩余尝试次数{}", retryCount);
|
||||
result = function.get();
|
||||
log.info("执行结果: {}", result);
|
||||
flag = check.apply(result);
|
||||
}
|
||||
|
||||
if (flag) {
|
||||
errFun.accept(result);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 防抖函数:在指定秒数内相同 Key 的任务只会执行一次
|
||||
* @param key 防抖使用的 Redis Key
|
||||
* @param seconds 防抖时间(秒)
|
||||
* @param task 要执行的业务逻辑
|
||||
* @return true 执行了任务;false 在防抖期内被拦截
|
||||
*/
|
||||
public boolean debounce(String key, long seconds, Runnable task) {
|
||||
try {
|
||||
Boolean success = redisTemplate.opsForValue().setIfAbsent(
|
||||
key, "1", seconds, TimeUnit.SECONDS
|
||||
);
|
||||
|
||||
if (Boolean.TRUE.equals(success)) {
|
||||
task.run();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} catch (Exception e) {
|
||||
log.error("防抖函数执行失败 key={} err={}", key, e.getMessage(), e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -153,6 +153,10 @@
|
||||
<foreach item="item" collection="idList" separator="," open="(" close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
or t1.sync_id in
|
||||
<foreach item="item" collection="idList" separator="," open="(" close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</if>
|
||||
order by t1.sort desc,t1.id desc
|
||||
</select>
|
||||
|
||||
Reference in New Issue
Block a user