From 959eb6077ed2ca824372827d264ab1fd70f1232d Mon Sep 17 00:00:00 2001 From: Jerry Yan <792602257@qq.com> Date: Fri, 13 Feb 2026 14:44:57 +0800 Subject: [PATCH] =?UTF-8?q?feat(printer):=20=E6=B7=BB=E5=8A=A0=E6=89=B9?= =?UTF-8?q?=E9=87=8F=E5=88=9B=E5=BB=BA=E8=99=9A=E6=8B=9F=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在PrinterTvController中新增printerService和orderService依赖注入 - 添加getPrinterListByScenicId接口获取景区下启用状态的打印机列表 - 新增createVirtualOrder接口支持批量创建虚拟用户订单 - 新增queryOrder接口用于查询订单支付状态 - 创建TvCreateVirtualOrderRequest请求参数类 - 在PrinterService中实现createBatchVirtualOrder批量创建订单逻辑 - 支持通过faceSampleIds自动查找关联照片素材聚合为一笔订单 - 支持是否需要实际支付的配置选项 - 实现订单价格计算和微信支付集成 --- .../printer/PrinterTvController.java | 63 ++++++ .../req/TvCreateVirtualOrderRequest.java | 44 ++++ .../basic/service/printer/PrinterService.java | 13 ++ .../printer/impl/PrinterServiceImpl.java | 188 ++++++++++++++++++ 4 files changed, 308 insertions(+) create mode 100644 src/main/java/com/ycwl/basic/model/printer/req/TvCreateVirtualOrderRequest.java diff --git a/src/main/java/com/ycwl/basic/controller/printer/PrinterTvController.java b/src/main/java/com/ycwl/basic/controller/printer/PrinterTvController.java index bfde2900..069fc136 100644 --- a/src/main/java/com/ycwl/basic/controller/printer/PrinterTvController.java +++ b/src/main/java/com/ycwl/basic/controller/printer/PrinterTvController.java @@ -9,12 +9,17 @@ import com.ycwl.basic.model.mobile.face.FaceRecognizeResp; import com.ycwl.basic.model.pc.face.entity.FaceEntity; import com.ycwl.basic.model.pc.faceSample.entity.FaceSampleEntity; import com.ycwl.basic.model.pc.mp.MpConfigEntity; +import com.ycwl.basic.model.pc.printer.resp.PrinterResp; import com.ycwl.basic.model.pc.scenic.req.ScenicReqQuery; import com.ycwl.basic.model.pc.source.entity.SourceEntity; +import com.ycwl.basic.model.printer.req.TvCreateVirtualOrderRequest; +import com.ycwl.basic.pay.entity.PayResponse; import com.ycwl.basic.repository.DeviceRepository; import com.ycwl.basic.repository.FaceRepository; import com.ycwl.basic.repository.ScenicRepository; import com.ycwl.basic.service.pc.FaceService; +import com.ycwl.basic.service.pc.OrderService; +import com.ycwl.basic.service.printer.PrinterService; import com.ycwl.basic.utils.ApiResponse; import com.ycwl.basic.utils.WxMpUtil; import jakarta.servlet.http.HttpServletResponse; @@ -22,6 +27,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; @@ -31,6 +37,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.OutputStream; import java.util.List; +import java.util.Map; @IgnoreToken // 打印机大屏对接接口 @@ -44,6 +51,8 @@ public class PrinterTvController { private final FaceRepository faceRepository; private final FaceService pcFaceService; private final SourceMapper sourceMapper; + private final PrinterService printerService; + private final OrderService orderService; /** * 获取景区列表 @@ -191,4 +200,58 @@ public class PrinterTvController { response.sendRedirect(face.getFaceUrl()); } + /** + * 获取景区下的打印机列表 + * + * @param scenicId 景区ID + * @return 启用状态的打印机列表 + */ + @GetMapping("/printer/list") + public ApiResponse> getPrinterListByScenicId(@RequestParam Long scenicId) { + return ApiResponse.success(printerService.listByScenicId(scenicId)); + } + + /** + * 批量创建虚拟用户订单 + * 传入faceSampleIds,自动查找关联的照片素材(type=2),聚合为一笔订单、一次支付 + * + * @param request 请求参数(含faceSampleIds列表) + * @return 聚合订单结果 + */ + @PostMapping("/createVirtualOrder") + public ApiResponse> createVirtualOrder(@RequestBody TvCreateVirtualOrderRequest request) { + if (request.getFaceSampleIds() == null || request.getFaceSampleIds().isEmpty()) { + return ApiResponse.fail("faceSampleIds不能为空"); + } + try { + List sources = sourceMapper.listByFaceSampleIdsAndType(request.getFaceSampleIds(), 2); + if (sources.isEmpty()) { + return ApiResponse.fail("未找到关联的照片素材"); + } + List sourceIds = sources.stream().map(SourceEntity::getId).toList(); + Map result = printerService.createBatchVirtualOrder( + sourceIds, + request.getScenicId(), + request.getPrinterId(), + request.getNeedEnhance(), + request.getPrintImgUrl(), + request.getNeedActualPayment() + ); + return ApiResponse.success(result); + } catch (Exception e) { + return ApiResponse.fail(e.getMessage()); + } + } + + /** + * 查询订单支付状态 + * + * @param orderId 订单ID + * @return 支付状态信息 + */ + @GetMapping("/order/query") + public ApiResponse queryOrder(@RequestParam("orderId") Long orderId) { + return ApiResponse.success(orderService.queryOrder(orderId)); + } + } diff --git a/src/main/java/com/ycwl/basic/model/printer/req/TvCreateVirtualOrderRequest.java b/src/main/java/com/ycwl/basic/model/printer/req/TvCreateVirtualOrderRequest.java new file mode 100644 index 00000000..d0792f3f --- /dev/null +++ b/src/main/java/com/ycwl/basic/model/printer/req/TvCreateVirtualOrderRequest.java @@ -0,0 +1,44 @@ +package com.ycwl.basic.model.printer.req; + +import lombok.Data; + +import java.util.List; + +/** + * 打印机大屏创建虚拟订单请求参数 + * 通过 faceSampleIds 自动查找关联的照片素材进行下单 + */ +@Data +public class TvCreateVirtualOrderRequest { + /** + * 人脸样本ID列表,系统自动查找这些样本关联的所有照片素材(type=2) + */ + private List faceSampleIds; + + /** + * 景区ID + */ + private Long scenicId; + + /** + * 打印机ID(可选) + */ + private Integer printerId; + + /** + * 是否需要图像增强(可选,默认不增强) + */ + private Boolean needEnhance; + + /** + * 打印图片URL(可选,如果提供则使用此URL进行打印) + */ + private String printImgUrl; + + /** + * 是否需要实际支付(可选,默认false) + * false/null: 创建0元虚拟订单,立即完成购买 + * true: 创建待支付订单(计算实际价格) + */ + private Boolean needActualPayment; +} diff --git a/src/main/java/com/ycwl/basic/service/printer/PrinterService.java b/src/main/java/com/ycwl/basic/service/printer/PrinterService.java index c936f5e6..702bb799 100644 --- a/src/main/java/com/ycwl/basic/service/printer/PrinterService.java +++ b/src/main/java/com/ycwl/basic/service/printer/PrinterService.java @@ -157,6 +157,19 @@ public interface PrinterService { */ Map createVirtualOrder(Long sourceId, Long scenicId, Integer printerId, Boolean needEnhance, String printImgUrl, Boolean needActualPayment); + /** + * 批量创建虚拟用户订单(多个sourceId聚合为一笔订单、一次支付) + * + * @param sourceIds source记录ID列表 + * @param scenicId 景区ID + * @param printerId 打印机ID(可选) + * @param needEnhance 是否需要图像增强(可选) + * @param printImgUrl 打印图片URL(可选) + * @param needActualPayment 是否需要实际支付 + * @return 订单信息 + */ + Map createBatchVirtualOrder(List sourceIds, Long scenicId, Integer printerId, Boolean needEnhance, String printImgUrl, Boolean needActualPayment); + /** * 根据accessKey获取打印机详情 * @param accessKey 打印机accessKey diff --git a/src/main/java/com/ycwl/basic/service/printer/impl/PrinterServiceImpl.java b/src/main/java/com/ycwl/basic/service/printer/impl/PrinterServiceImpl.java index af565d18..69b43346 100644 --- a/src/main/java/com/ycwl/basic/service/printer/impl/PrinterServiceImpl.java +++ b/src/main/java/com/ycwl/basic/service/printer/impl/PrinterServiceImpl.java @@ -1931,6 +1931,194 @@ public class PrinterServiceImpl implements PrinterService { return result; } + @Override + public Map createBatchVirtualOrder(List sourceIds, Long scenicId, Integer printerId, Boolean needEnhance, String printImgUrl, Boolean needActualPayment) { + if (sourceIds == null || sourceIds.isEmpty()) { + throw new BaseException("sourceIds不能为空"); + } + + // 1. 校验所有source并收集faceSample + List sources = new ArrayList<>(); + FaceSampleEntity firstFaceSample = null; + for (Long sourceId : sourceIds) { + SourceEntity source = sourceMapper.getEntity(sourceId); + if (source == null) { + throw new BaseException("Source记录不存在: " + sourceId); + } + if (!scenicId.equals(source.getScenicId())) { + throw new BaseException("Source记录不属于该景区: " + sourceId); + } + FaceSampleEntity faceSample = faceSampleMapper.getEntity(source.getFaceSampleId()); + if (faceSample == null) { + throw new BaseException("人脸样本不存在, sourceId=" + sourceId); + } + if (firstFaceSample == null) { + firstFaceSample = faceSample; + } + sources.add(source); + } + + // 2. 生成一个虚拟用户 + 一条人脸记录 + Long virtualMemberId = SnowFlakeUtil.getLongId(); + Long faceId = SnowFlakeUtil.getLongId(); + FaceEntity face = new FaceEntity(); + face.setId(faceId); + face.setScenicId(scenicId); + face.setMemberId(virtualMemberId); + face.setFaceUrl(firstFaceSample.getFaceUrl()); + face.setCreateAt(new Date()); + faceMapper.add(face); + log.info("批量下单 - 创建虚拟用户: virtualMemberId={}, faceId={}, sourceCount={}", virtualMemberId, faceId, sourceIds.size()); + + // 3. 为每个source创建member_print记录 + List memberPrintIds = new ArrayList<>(); + for (SourceEntity source : sources) { + String photoUrl = (printImgUrl != null && !printImgUrl.isEmpty()) ? printImgUrl : source.getUrl(); + Integer memberPrintId = addUserPhoto(virtualMemberId, scenicId, photoUrl, faceId, source.getId()); + if (memberPrintId == null) { + throw new BaseException("创建member_print记录失败, sourceId=" + source.getId()); + } + setPhotoQuantity(virtualMemberId, scenicId, memberPrintId.longValue(), 1); + memberPrintIds.add(memberPrintId); + } + + // 4. 验证打印机 + if (printerId == null) { + List printerList = printerMapper.listByScenicId(scenicId); + if (printerList.isEmpty()) { + throw new BaseException("该景区没有可用的打印机"); + } + if (printerList.size() != 1) { + throw new BaseException("请选择打印机"); + } + printerId = printerList.getFirst().getId(); + } + PrinterEntity printer = printerMapper.getById(printerId); + if (printer == null) { + throw new BaseException("打印机不存在"); + } + if (printer.getStatus() != 1) { + throw new BaseException("打印机已停用"); + } + if (!printer.getScenicId().equals(scenicId)) { + throw new BaseException("打印机不属于该景区"); + } + + // 5. 创建订单 + OrderEntity order = new OrderEntity(); + Long orderId = SnowFlakeUtil.getLongId(); + redisTemplate.opsForValue().set("printer_size:" + orderId, printer.getPreferPaper(), 60, TimeUnit.SECONDS); + order.setId(orderId); + order.setMemberId(virtualMemberId); + order.setFaceId(faceId); + order.setOpenId(""); + order.setScenicId(scenicId); + order.setType(3); + + batchSetUserPhotoListToPrinter(virtualMemberId, scenicId, printerId); + + List userPhotoList = getUserPhotoList(virtualMemberId, scenicId, faceId); + List orderItems = userPhotoList.stream().map(goods -> { + OrderItemEntity orderItem = new OrderItemEntity(); + orderItem.setOrderId(orderId); + orderItem.setGoodsId(Long.valueOf(goods.getId())); + orderItem.setGoodsType(3); + return orderItem; + }).collect(Collectors.toList()); + + boolean actualPayment = Boolean.TRUE.equals(needActualPayment); + + if (actualPayment) { + PriceCalculationRequest priceRequest = new PriceCalculationRequest(); + priceRequest.setUserId(virtualMemberId); + priceRequest.setScenicId(scenicId); + + List productItems = new ArrayList<>(); + ProductItem photoItem = new ProductItem(); + photoItem.setProductType(ProductType.PHOTO_PRINT); + photoItem.setProductId(scenicId.toString()); + photoItem.setQuantity(sourceIds.size()); + photoItem.setPurchaseCount(sourceIds.size()); + photoItem.setScenicId(scenicId.toString()); + productItems.add(photoItem); + + priceRequest.setProducts(productItems); + priceRequest.setAutoUseCoupon(false); + priceRequest.setPreviewOnly(false); + + PriceCalculationResult priceResult = priceCalculationService.calculatePrice(priceRequest); + + order.setPrice(priceResult.getFinalAmount()); + order.setSlashPrice(priceResult.getOriginalAmount()); + order.setPayPrice(priceResult.getFinalAmount()); + order.setStatus(OrderStateEnum.UNPAID.getState()); + log.info("批量下单 - 待支付订单: orderId={}, price={}, count={}", orderId, priceResult.getFinalAmount(), sourceIds.size()); + + if (needEnhance != null) { + redisTemplate.opsForValue().set("virtual_order_enhance:" + orderId, needEnhance.toString(), 24, TimeUnit.HOURS); + } + } else { + order.setPrice(BigDecimal.ZERO); + order.setSlashPrice(BigDecimal.ZERO); + order.setPayPrice(BigDecimal.ZERO); + order.setStatus(OrderStateEnum.PAID.getState()); + order.setPayAt(new Date()); + } + + orderMapper.add(order); + int addOrderItems = orderMapper.addOrderItems(orderItems); + if (addOrderItems == NumberConstant.ZERO) { + throw new BaseException("订单添加失败"); + } + + log.info("批量下单 - 订单创建成功: orderId={}, itemCount={}", orderId, orderItems.size()); + + Map result = new HashMap<>(); + result.put("orderId", orderId); + result.put("faceId", faceId); + result.put("virtualMemberId", virtualMemberId); + result.put("memberPrintIds", memberPrintIds); + result.put("sourceIds", sourceIds); + + if (actualPayment) { + if (order.getPayPrice().compareTo(BigDecimal.ZERO) <= 0) { + order.setStatus(OrderStateEnum.PAID.getState()); + order.setPayAt(new Date()); + orderMapper.updateOrder(order); + log.info("批量下单 - 价格为0直接完成: orderId={}", orderId); + result.put("needPay", false); + } else { + IPayAdapter payAdapter = scenicService.getScenicPayAdapter(scenicId); + if (payAdapter instanceof WxMpPayAdapter adapter) { + NativePayService nativePayService = new NativePayService.Builder().config(adapter.getConfig()).build(); + PrepayRequest prepayRequest = new PrepayRequest(); + prepayRequest.setAppid(adapter._config().getAppId()); + prepayRequest.setMchid(adapter._config().getMerchantId()); + prepayRequest.setDescription("照片打印 x" + sourceIds.size()); + prepayRequest.setOutTradeNo(String.valueOf(orderId)); + prepayRequest.setNotifyUrl("https://zhentuai.com/api/mobile/wx/pay/v1/" + scenicId + "/payNotify"); + Amount amount = new Amount(); + amount.setTotal(order.getPayPrice().multiply(new BigDecimal(100)).intValue()); + prepayRequest.setAmount(amount); + PrepayResponse prepayResponse = nativePayService.prepay(prepayRequest); + result.put("payCode", prepayResponse.getCodeUrl()); + } else { + throw new BaseException("该景区不支持 Native 支付"); + } + result.put("needPay", true); + result.put("price", order.getPayPrice()); + } + } else { + result.put("needPay", false); + } + + // 触发购买后逻辑(setUserIsBuyItem 内部遍历 orderItems 处理所有 memberPrint) + setUserIsBuyItem(virtualMemberId, memberPrintIds.getFirst().longValue(), orderId, needEnhance); + log.info("批量下单 - 购买后逻辑完成: orderId={}", orderId); + + return result; + } + @Override public PrinterEntity getByAccessKey(String accessKey) { if (accessKey == null) {