From 1cc030d0db362de508097c9581017fcda2378d81 Mon Sep 17 00:00:00 2001 From: Kroytz Date: Fri, 24 Jul 2026 15:59:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=8A=AF=E7=83=A8=E4=BA=91=E6=89=93?= =?UTF-8?q?=E5=8D=B0=E6=9C=BA=E6=8E=A5=E5=85=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../order/enums/ErrorCodeConstants.java | 5 + .../storeorder/StoreOrderController.java | 9 +- .../order/dal/redis/RedisKeyConstants.java | 3 + .../dal/redis/order/PrintOrderRedisDAO.java | 36 ++++++ .../print/xpyun/OrderReceiptBuilder.java | 98 ++++++++++++++++ .../order/print/xpyun/XpYunConfiguration.java | 9 ++ .../order/print/xpyun/XpYunPrintClient.java | 71 ++++++++++++ .../order/print/xpyun/XpYunPrintResponse.java | 29 +++++ .../order/print/xpyun/XpYunProperties.java | 33 ++++++ .../storeorder/AppStoreOrderServiceImpl.java | 13 +++ .../storeorder/AsynStoreOrderServiceImpl.java | 106 +++++++++++++++++- .../storeorder/AsyncStoreOrderService.java | 22 ++-- .../service/storeorder/StoreOrderService.java | 7 +- .../storeorder/StoreOrderServiceImpl.java | 9 ++ .../src/main/resources/application-dev.yaml | 5 + .../src/main/resources/application-local.yaml | 5 + .../src/api/mall/order/storeOrder/index.ts | 5 + .../src/views/mall/order/storeOrder/index.vue | 13 +++ .../src/views/mall/store/shop/ShopForm.vue | 4 + 19 files changed, 465 insertions(+), 17 deletions(-) create mode 100644 yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/order/PrintOrderRedisDAO.java create mode 100644 yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/OrderReceiptBuilder.java create mode 100644 yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunConfiguration.java create mode 100644 yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunPrintClient.java create mode 100644 yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunPrintResponse.java create mode 100644 yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunProperties.java diff --git a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/ErrorCodeConstants.java b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/ErrorCodeConstants.java index 222c7fc..6131aeb 100644 --- a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/ErrorCodeConstants.java +++ b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/ErrorCodeConstants.java @@ -41,6 +41,11 @@ public interface ErrorCodeConstants { ErrorCode WECHAT_SHIPPING_DELIVERY_NO_REQUIRED = new ErrorCode(1008007026, "请填写同城配送单号"); ErrorCode WECHAT_SHIPPING_UPLOAD_FAILED = new ErrorCode(1008007027, "微信发货信息上报失败"); + ErrorCode STORE_ORDER_PRINT_DISABLED = new ErrorCode(1008007028, "芯烨云打印未启用或未配置开发者凭证"); + ErrorCode STORE_ORDER_PRINT_NOT_PAID = new ErrorCode(1008007029, "订单未支付,无法打印"); + ErrorCode STORE_ORDER_PRINT_TYPE_NOT_SUPPORT = new ErrorCode(1008007030, "仅支持自取/外卖订单打印"); + ErrorCode STORE_ORDER_PRINT_SN_EMPTY = new ErrorCode(1008007031, "门店未配置打印机SN"); + ErrorCode STORE_ORDER_PRINT_FAILED = new ErrorCode(1008007032, "打印失败:{}"); } diff --git a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/StoreOrderController.java b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/StoreOrderController.java index 9362d38..b18d56b 100644 --- a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/StoreOrderController.java +++ b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/StoreOrderController.java @@ -157,6 +157,13 @@ public class StoreOrderController { return success(storeOrderService.orderNotice()); } - + @GetMapping("/printOrder") + @Operation(summary = "补打小票") + @Parameter(name = "id", description = "订单编号", required = true) + @PreAuthorize("@ss.hasPermission('order:store-order:update')") + public CommonResult printOrder(@RequestParam("id") Long id) { + storeOrderService.printStoreOrder(id); + return success(true); + } } diff --git a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/RedisKeyConstants.java b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/RedisKeyConstants.java index 06bfc5b..7037d3d 100644 --- a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/RedisKeyConstants.java +++ b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/RedisKeyConstants.java @@ -33,5 +33,8 @@ public interface RedisKeyConstants { "yshop_web_print_machine_cache:%s", // 参数为访问shopid STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); + RedisKeyDefine YSHOP_ORDER_PRINT_KEY = new RedisKeyDefine("订单打印幂等", + "yshop_order_print:%s", // 参数为 orderId + STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC); } diff --git a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/order/PrintOrderRedisDAO.java b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/order/PrintOrderRedisDAO.java new file mode 100644 index 0000000..1dfac2e --- /dev/null +++ b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/order/PrintOrderRedisDAO.java @@ -0,0 +1,36 @@ +package co.yixiang.yshop.module.order.dal.redis.order; + +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Repository; + +import jakarta.annotation.Resource; +import java.util.concurrent.TimeUnit; + +import static co.yixiang.yshop.module.order.dal.redis.RedisKeyConstants.YSHOP_ORDER_PRINT_KEY; + +/** + * 订单自动打印幂等(防止支付回调重复出票) + */ +@Repository +public class PrintOrderRedisDAO { + + private static final long TTL_HOURS = 24; + + @Resource + private StringRedisTemplate stringRedisTemplate; + + /** + * @return true 表示首次标记成功,可以打印;false 表示已打印过 + */ + public boolean tryMarkPrinted(String orderId) { + String redisKey = formatKey(orderId); + Boolean ok = stringRedisTemplate.opsForValue() + .setIfAbsent(redisKey, "1", TTL_HOURS, TimeUnit.HOURS); + return Boolean.TRUE.equals(ok); + } + + private static String formatKey(String orderId) { + return String.format(YSHOP_ORDER_PRINT_KEY.getKeyTemplate(), orderId); + } + +} diff --git a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/OrderReceiptBuilder.java b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/OrderReceiptBuilder.java new file mode 100644 index 0000000..74a8058 --- /dev/null +++ b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/OrderReceiptBuilder.java @@ -0,0 +1,98 @@ +package co.yixiang.yshop.module.order.print.xpyun; + +import cn.hutool.core.util.StrUtil; +import co.yixiang.yshop.module.order.dal.dataobject.storeorder.StoreOrderDO; +import co.yixiang.yshop.module.order.dal.dataobject.storeordercartinfo.StoreOrderCartInfoDO; +import co.yixiang.yshop.module.order.enums.OrderLogEnum; +import org.springframework.stereotype.Component; + +import java.math.BigDecimal; +import java.time.format.DateTimeFormatter; +import java.util.List; + +/** + * 自取 / 外卖小票内容组装(58mm) + */ +@Component +public class OrderReceiptBuilder { + + private static final DateTimeFormatter PAY_TIME_FMT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + private static final String LINE = "--------------------------------
"; + + public String build(StoreOrderDO order, List items) { + boolean takeout = OrderLogEnum.ORDER_TAKE_OUT.getValue().equals(order.getOrderType()); + String typeLabel = takeout ? "外卖" : "自取"; + + StringBuilder sb = new StringBuilder(); + sb.append("").append(nullToEmpty(order.getShopName())).append("
"); + sb.append("").append(typeLabel).append("
"); + if (order.getNumberId() != null) { + sb.append("#").append(order.getNumberId()).append("
"); + } + sb.append(LINE); + + sb.append("商品").append(pad("数量", 6)).append(pad("单价", 6)).append("
"); + sb.append(LINE); + if (items != null) { + for (StoreOrderCartInfoDO item : items) { + String name = nullToEmpty(item.getTitle()); + if (StrUtil.isNotBlank(item.getSpec())) { + name = name + "(" + item.getSpec() + ")"; + } + sb.append(name).append("
"); + Integer number = item.getNumber() == null ? 0 : item.getNumber(); + BigDecimal price = item.getPrice() == null ? BigDecimal.ZERO : item.getPrice(); + sb.append(" x").append(number) + .append(" ").append(price.toPlainString()) + .append("
"); + } + } + sb.append(LINE); + + if (order.getCouponPrice() != null && order.getCouponPrice().compareTo(BigDecimal.ZERO) > 0) { + sb.append("优惠:-").append(order.getCouponPrice().toPlainString()).append("
"); + } + if (takeout) { + BigDecimal freight = order.getPayPostage() != null ? order.getPayPostage() + : (order.getFreightPrice() != null ? order.getFreightPrice() : BigDecimal.ZERO); + sb.append("配送费:").append(freight.toPlainString()).append("
"); + } + BigDecimal payPrice = order.getPayPrice() == null ? BigDecimal.ZERO : order.getPayPrice(); + sb.append("实付:").append(payPrice.toPlainString()).append("元
"); + sb.append(LINE); + + if (StrUtil.isNotBlank(order.getRealName())) { + sb.append("姓名:").append(order.getRealName()).append("
"); + } + if (StrUtil.isNotBlank(order.getUserPhone())) { + sb.append("电话:").append(order.getUserPhone()).append("
"); + } + if (takeout && StrUtil.isNotBlank(order.getUserAddress())) { + sb.append("地址:").append(order.getUserAddress()).append("
"); + } + if (order.getPayTime() != null) { + sb.append("支付时间:").append(order.getPayTime().format(PAY_TIME_FMT)).append("
"); + } + if (StrUtil.isNotBlank(order.getMark())) { + sb.append("备注:").append(order.getMark()).append("
"); + } + sb.append("订单号:").append(nullToEmpty(order.getOrderId())).append("
"); + sb.append("

"); + return sb.toString(); + } + + private static String nullToEmpty(String s) { + return s == null ? "" : s; + } + + private static String pad(String text, int width) { + if (text == null) { + text = ""; + } + if (text.length() >= width) { + return text; + } + return " ".repeat(width - text.length()) + text; + } + +} diff --git a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunConfiguration.java b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunConfiguration.java new file mode 100644 index 0000000..274c2ef --- /dev/null +++ b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunConfiguration.java @@ -0,0 +1,9 @@ +package co.yixiang.yshop.module.order.print.xpyun; + +import org.springframework.boot.context.properties.EnableConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration(proxyBeanMethods = false) +@EnableConfigurationProperties(XpYunProperties.class) +public class XpYunConfiguration { +} diff --git a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunPrintClient.java b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunPrintClient.java new file mode 100644 index 0000000..4140ebb --- /dev/null +++ b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunPrintClient.java @@ -0,0 +1,71 @@ +package co.yixiang.yshop.module.order.print.xpyun; + +import cn.hutool.crypto.digest.DigestUtil; +import cn.hutool.http.HttpRequest; +import co.yixiang.yshop.framework.common.util.json.JsonUtils; +import jakarta.annotation.Resource; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.Map; + +/** + * 芯烨云小票打印客户端 + */ +@Component +@Slf4j +public class XpYunPrintClient { + + @Resource + private XpYunProperties properties; + + /** + * 打印小票 + * + * @param sn 打印机编号 + * @param content 打印内容(标签语法) + * @param copies 份数 + * @param voice 声音模式:0 取消 / 1 静音 / 2 来单 + * @param mode 0 检查在线;1 离线也入队 + * @return 云端响应 + */ + public XpYunPrintResponse print(String sn, String content, int copies, int voice, int mode) { + Map body = buildCommonParams(); + body.put("sn", sn); + body.put("content", content); + body.put("copies", copies); + body.put("voice", voice); + body.put("mode", mode); + + String url = properties.getBaseUrl() + "/xprinter/print"; + String json = JsonUtils.toJsonString(body); + log.info("[XpYun][print][sn({})] request={}", sn, json); + String respText = HttpRequest.post(url) + .header("Content-Type", "application/json;charset=UTF-8") + .body(json) + .timeout(15000) + .execute() + .body(); + log.info("[XpYun][print][sn({})] response={}", sn, respText); + return JsonUtils.parseObject(respText, XpYunPrintResponse.class); + } + + public boolean isReady() { + return Boolean.TRUE.equals(properties.getEnabled()) + && properties.getUser() != null && !properties.getUser().isBlank() + && properties.getUserKey() != null && !properties.getUserKey().isBlank(); + } + + private Map buildCommonParams() { + String timestamp = String.valueOf(System.currentTimeMillis()); + String sign = DigestUtil.sha1Hex(properties.getUser() + properties.getUserKey() + timestamp); + Map params = new HashMap<>(); + params.put("user", properties.getUser()); + params.put("timestamp", timestamp); + params.put("sign", sign); + params.put("debug", "0"); + return params; + } + +} diff --git a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunPrintResponse.java b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunPrintResponse.java new file mode 100644 index 0000000..117cc43 --- /dev/null +++ b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunPrintResponse.java @@ -0,0 +1,29 @@ +package co.yixiang.yshop.module.order.print.xpyun; + +import lombok.Data; + +/** + * 芯烨云通用响应 + */ +@Data +public class XpYunPrintResponse { + + /** + * 0 表示成功 + */ + private Integer code; + + private String msg; + + /** + * 成功时一般为打印订单号 + */ + private String data; + + private Long serverExecutedTime; + + public boolean isSuccess() { + return code != null && code == 0; + } + +} diff --git a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunProperties.java b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunProperties.java new file mode 100644 index 0000000..e4e9b6c --- /dev/null +++ b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/print/xpyun/XpYunProperties.java @@ -0,0 +1,33 @@ +package co.yixiang.yshop.module.order.print.xpyun; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; + +/** + * 芯烨云打印配置 + */ +@ConfigurationProperties(prefix = "yshop.xpyun") +@Data +public class XpYunProperties { + + /** + * 是否启用芯烨云打印 + */ + private Boolean enabled = false; + + /** + * 开发者 ID(邮箱或开发者 ID) + */ + private String user = ""; + + /** + * 开发者密钥 + */ + private String userKey = ""; + + /** + * OpenAPI 根地址 + */ + private String baseUrl = "https://open.xpyun.net/api/openapi"; + +} diff --git a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/AppStoreOrderServiceImpl.java b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/AppStoreOrderServiceImpl.java index 9768d40..0373e60 100644 --- a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/AppStoreOrderServiceImpl.java +++ b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/AppStoreOrderServiceImpl.java @@ -67,6 +67,8 @@ import org.redisson.client.codec.StringCodec; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; +import org.springframework.transaction.support.TransactionSynchronization; +import org.springframework.transaction.support.TransactionSynchronizationManager; import org.springframework.validation.annotation.Validated; import java.io.UnsupportedEncodingException; @@ -568,6 +570,17 @@ public class AppStoreOrderServiceImpl extends ServiceImpl() + .eq(StoreOrderDO::getOrderId, orderId)); + if (order == null) { + failOrSkip(force, STORE_ORDER_NOT_EXISTS, "订单不存在"); + return; + } + if (!OrderInfoEnum.PAY_STATUS_1.getValue().equals(order.getPaid())) { + failOrSkip(force, STORE_ORDER_PRINT_NOT_PAID, "订单未支付"); + return; + } + String orderType = order.getOrderType(); + if (!OrderLogEnum.ORDER_TAKE_IN.getValue().equals(orderType) + && !OrderLogEnum.ORDER_TAKE_OUT.getValue().equals(orderType)) { + failOrSkip(force, STORE_ORDER_PRINT_TYPE_NOT_SUPPORT, "订单类型不支持打印:" + orderType); + return; + } + if (order.getShopId() == null) { + failOrSkip(force, STORE_ORDER_PRINT_SN_EMPTY, "订单无门店"); + return; + } + StoreShopDO shop = storeShopMapper.selectById(order.getShopId()); + if (shop == null || StrUtil.isBlank(shop.getUniprintId())) { + failOrSkip(force, STORE_ORDER_PRINT_SN_EMPTY, "门店未配置打印机SN"); + return; + } + // 校验通过后再做幂等,避免空 SN / 未启用误占 key + if (!force && !printOrderRedisDAO.tryMarkPrinted(orderId)) { + log.info("[printOrder][orderId({}) 已打印过,跳过]", orderId); + return; + } + List items = storeOrderCartInfoMapper.selectList( + new LambdaQueryWrapper() + .eq(StoreOrderCartInfoDO::getOid, order.getId())); + String content = orderReceiptBuilder.build(order, items); + try { + // mode=1 离线入队;voice=2 来单播报 + XpYunPrintResponse resp = xpYunPrintClient.print(shop.getUniprintId(), content, 1, 2, 1); + if (resp == null || !resp.isSuccess()) { + String msg = resp == null ? "无响应" : StrUtil.blankToDefault(resp.getMsg(), "未知错误"); + if (force) { + throw exception(STORE_ORDER_PRINT_FAILED, msg); + } + log.error("[printOrder][orderId({}) 打印失败] {}", orderId, msg); + } else { + log.info("[printOrder][orderId({}) 打印成功] data={}", orderId, resp.getData()); + } + } catch (co.yixiang.yshop.framework.common.exception.ServiceException e) { + throw e; + } catch (Exception e) { + if (force) { + throw exception(STORE_ORDER_PRINT_FAILED, e.getMessage()); + } + log.error("[printOrder][orderId({}) 打印异常]", orderId, e); + } + } + + private void failOrSkip(boolean force, + co.yixiang.yshop.framework.common.exception.ErrorCode errorCode, + String logMsg) { + if (force) { + throw exception(errorCode); + } + log.warn("[printOrder][skip] {}", logMsg); + } } + diff --git a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/AsyncStoreOrderService.java b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/AsyncStoreOrderService.java index 836cb01..e8831b2 100644 --- a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/AsyncStoreOrderService.java +++ b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/AsyncStoreOrderService.java @@ -1,11 +1,5 @@ package co.yixiang.yshop.module.order.service.storeorder; -import co.yixiang.yshop.module.member.controller.app.user.vo.AppUserOrderCountVo; -import co.yixiang.yshop.module.order.controller.admin.storeorder.vo.ShoperOrderTimeDataVo; -import co.yixiang.yshop.module.order.controller.app.order.vo.AppStoreOrderQueryVo; -import co.yixiang.yshop.module.order.dal.dataobject.storeorder.StoreOrderDO; -import co.yixiang.yshop.module.order.service.storeorder.dto.OrderTimeDataDto; - /** * 异步订单 Service 接口 * @@ -16,17 +10,27 @@ public interface AsyncStoreOrderService { /** * 计算某个用户的订单统计数据 * @param uid uid>0 取用户 否则取所有 - * @return UserOrderCountVo */ void orderData(Long uid); - - /** * 异步后台数据统计 */ void getOrderTimeData(); + /** + * 支付成功后异步打印小票(失败只记日志,不抛异常) + * + * @param orderId 业务订单号 + */ + void printOrderAsync(String orderId); + /** + * 打印小票 + * + * @param orderId 业务订单号 + * @param force false=自动打印(幂等,校验失败跳过);true=补打(跳过幂等,校验失败抛业务异常) + */ + void printOrder(String orderId, boolean force); } diff --git a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/StoreOrderService.java b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/StoreOrderService.java index bfccba0..b15d31a 100644 --- a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/StoreOrderService.java +++ b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/StoreOrderService.java @@ -99,6 +99,11 @@ public interface StoreOrderService { */ Long orderNotice(); - + /** + * 补打小票(同步,跳过幂等) + * + * @param id 订单主键 + */ + void printStoreOrder(Long id); } diff --git a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/StoreOrderServiceImpl.java b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/StoreOrderServiceImpl.java index 531aa0a..ac55896 100644 --- a/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/StoreOrderServiceImpl.java +++ b/yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/StoreOrderServiceImpl.java @@ -453,6 +453,15 @@ public class StoreOrderServiceImpl implements StoreOrderService { } + @Override + public void printStoreOrder(Long id) { + StoreOrderDO storeOrderDO = storeOrderMapper.selectById(id); + if (storeOrderDO == null) { + throw exception(STORE_ORDER_NOT_EXISTS); + } + asyncStoreOrderService.printOrder(storeOrderDO.getOrderId(), true); + } + /** * 退回库存 * diff --git a/yshop-drink-boot3/yshop-server/src/main/resources/application-dev.yaml b/yshop-drink-boot3/yshop-server/src/main/resources/application-dev.yaml index 9d4bbf4..65cc435 100644 --- a/yshop-drink-boot3/yshop-server/src/main/resources/application-dev.yaml +++ b/yshop-drink-boot3/yshop-server/src/main/resources/application-dev.yaml @@ -170,6 +170,11 @@ yshop: pay: order-notify-url: http://yshop.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址 refund-notify-url: http://yshop.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址 + xpyun: # 芯烨云打印机 https://www.xpyun.net + enabled: true + user: neko_chocola@qq.com # 开发者 ID + user-key: 36c8251d0ff340888dd0f5fa62aa81ee # 开发者密钥 + base-url: https://open.xpyun.net/api/openapi demo: true # 开启演示模式 tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc file-path: /www/wwwroot/drink/file/ diff --git a/yshop-drink-boot3/yshop-server/src/main/resources/application-local.yaml b/yshop-drink-boot3/yshop-server/src/main/resources/application-local.yaml index 1fba9e8..b5c9f17 100644 --- a/yshop-drink-boot3/yshop-server/src/main/resources/application-local.yaml +++ b/yshop-drink-boot3/yshop-server/src/main/resources/application-local.yaml @@ -206,6 +206,11 @@ yshop: pay: order-notify-url: http://yshop.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址 refund-notify-url: http://yshop.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址 + xpyun: # 芯烨云打印机 https://www.xpyun.net + enabled: true + user: neko_chocola@qq.com # 开发者 ID + user-key: 36c8251d0ff340888dd0f5fa62aa81ee # 开发者密钥 + base-url: https://open.xpyun.net/api/openapi access-log: # 访问日志的配置项 enable: false demo: false # 关闭演示模式 diff --git a/yshop-drink-vue3/src/api/mall/order/storeOrder/index.ts b/yshop-drink-vue3/src/api/mall/order/storeOrder/index.ts index 84378a9..5681dcf 100644 --- a/yshop-drink-vue3/src/api/mall/order/storeOrder/index.ts +++ b/yshop-drink-vue3/src/api/mall/order/storeOrder/index.ts @@ -110,6 +110,11 @@ export const getOrderHtml = async (param1,param2) => { return await request.get({ url: `/order/store-order/printOrder?id=` + param1 + `&electId=` + param2}) } +/** 补打小票 */ +export const printStoreOrder = async (id: number) => { + return await request.get({ url: `/order/store-order/printOrder?id=` + id }) +} + export const getShopCount = async () => { return await request.get({ url: `/order/store-order/count`}) } diff --git a/yshop-drink-vue3/src/views/mall/order/storeOrder/index.vue b/yshop-drink-vue3/src/views/mall/order/storeOrder/index.vue index b37c3a8..d135de3 100644 --- a/yshop-drink-vue3/src/views/mall/order/storeOrder/index.vue +++ b/yshop-drink-vue3/src/views/mall/order/storeOrder/index.vue @@ -200,6 +200,10 @@