You've already forked FrameTour-BE
feat(printer): 添加批量创建虚拟订单功能
- 在PrinterTvController中新增printerService和orderService依赖注入 - 添加getPrinterListByScenicId接口获取景区下启用状态的打印机列表 - 新增createVirtualOrder接口支持批量创建虚拟用户订单 - 新增queryOrder接口用于查询订单支付状态 - 创建TvCreateVirtualOrderRequest请求参数类 - 在PrinterService中实现createBatchVirtualOrder批量创建订单逻辑 - 支持通过faceSampleIds自动查找关联照片素材聚合为一笔订单 - 支持是否需要实际支付的配置选项 - 实现订单价格计算和微信支付集成
This commit is contained in:
@@ -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<List<PrinterResp>> getPrinterListByScenicId(@RequestParam Long scenicId) {
|
||||
return ApiResponse.success(printerService.listByScenicId(scenicId));
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量创建虚拟用户订单
|
||||
* 传入faceSampleIds,自动查找关联的照片素材(type=2),聚合为一笔订单、一次支付
|
||||
*
|
||||
* @param request 请求参数(含faceSampleIds列表)
|
||||
* @return 聚合订单结果
|
||||
*/
|
||||
@PostMapping("/createVirtualOrder")
|
||||
public ApiResponse<Map<String, Object>> createVirtualOrder(@RequestBody TvCreateVirtualOrderRequest request) {
|
||||
if (request.getFaceSampleIds() == null || request.getFaceSampleIds().isEmpty()) {
|
||||
return ApiResponse.fail("faceSampleIds不能为空");
|
||||
}
|
||||
try {
|
||||
List<SourceEntity> sources = sourceMapper.listByFaceSampleIdsAndType(request.getFaceSampleIds(), 2);
|
||||
if (sources.isEmpty()) {
|
||||
return ApiResponse.fail("未找到关联的照片素材");
|
||||
}
|
||||
List<Long> sourceIds = sources.stream().map(SourceEntity::getId).toList();
|
||||
Map<String, Object> 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<PayResponse> queryOrder(@RequestParam("orderId") Long orderId) {
|
||||
return ApiResponse.success(orderService.queryOrder(orderId));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Long> 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;
|
||||
}
|
||||
@@ -157,6 +157,19 @@ public interface PrinterService {
|
||||
*/
|
||||
Map<String, Object> 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<String, Object> createBatchVirtualOrder(List<Long> sourceIds, Long scenicId, Integer printerId, Boolean needEnhance, String printImgUrl, Boolean needActualPayment);
|
||||
|
||||
/**
|
||||
* 根据accessKey获取打印机详情
|
||||
* @param accessKey 打印机accessKey
|
||||
|
||||
@@ -1931,6 +1931,194 @@ public class PrinterServiceImpl implements PrinterService {
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> createBatchVirtualOrder(List<Long> sourceIds, Long scenicId, Integer printerId, Boolean needEnhance, String printImgUrl, Boolean needActualPayment) {
|
||||
if (sourceIds == null || sourceIds.isEmpty()) {
|
||||
throw new BaseException("sourceIds不能为空");
|
||||
}
|
||||
|
||||
// 1. 校验所有source并收集faceSample
|
||||
List<SourceEntity> 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<Integer> 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<PrinterResp> 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<MemberPrintResp> userPhotoList = getUserPhotoList(virtualMemberId, scenicId, faceId);
|
||||
List<OrderItemEntity> 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<ProductItem> 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<String, Object> 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) {
|
||||
|
||||
Reference in New Issue
Block a user