# 订单服务开发文档 - Part 2(Service实现 + Controller) ## 四、Service 实现 ### OrderServiceImpl.java ```java package com.openclaw.service.impl; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.openclaw.constant.ErrorCode; import com.openclaw.dto.*; import com.openclaw.entity.*; import com.openclaw.exception.BusinessException; import com.openclaw.repository.*; import com.openclaw.service.*; import com.openclaw.util.IdGenerator; import com.openclaw.vo.*; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.math.BigDecimal; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; @Service @RequiredArgsConstructor public class OrderServiceImpl implements OrderService { private final OrderRepository orderRepo; private final OrderItemRepository itemRepo; private final OrderRefundRepository refundRepo; private final SkillRepository skillRepo; private final SkillDownloadRepository downloadRepo; private final PointsService pointsService; private final IdGenerator idGenerator; private static final BigDecimal POINTS_RATE = new BigDecimal("0.01"); // 1积分=0.01元 @Override @Transactional public OrderVO createOrder(Long userId, OrderCreateDTO dto) { List skills = new ArrayList<>(); BigDecimal total = BigDecimal.ZERO; for (Long skillId : dto.getSkillIds()) { Skill skill = skillRepo.selectById(skillId); if (skill == null || !"approved".equals(skill.getStatus())) throw new BusinessException(ErrorCode.SKILL_NOT_FOUND); if (downloadRepo.existsByUserIdAndSkillId(userId, skillId)) throw new BusinessException(ErrorCode.SKILL_ALREADY_OWNED); skills.add(skill); total = total.add(Boolean.TRUE.equals(skill.getIsFree()) ? BigDecimal.ZERO : skill.getPrice()); } // 积分抵扣计算 int pts = dto.getPointsToUse() == null ? 0 : dto.getPointsToUse(); BigDecimal ptsDed = BigDecimal.ZERO; if (pts > 0) { if (!pointsService.hasEnoughPoints(userId, pts)) throw new BusinessException(ErrorCode.POINTS_NOT_ENOUGH); ptsDed = new BigDecimal(pts).multiply(POINTS_RATE).min(total); } BigDecimal cash = total.subtract(ptsDed); // 创建订单主记录 Order order = new Order(); order.setOrderNo(idGenerator.generateOrderNo()); order.setUserId(userId); order.setTotalAmount(total); order.setCashAmount(cash); order.setPointsUsed(pts); order.setPointsDeductAmount(ptsDed); order.setStatus("pending"); order.setPaymentMethod(dto.getPaymentMethod()); order.setExpiredAt(LocalDateTime.now().plusMinutes(30)); orderRepo.insert(order); // 创建订单项(快照商品信息) for (Skill s : skills) { OrderItem item = new OrderItem(); item.setOrderId(order.getId()); item.setSkillId(s.getId()); item.setSkillName(s.getName()); item.setSkillCover(s.getCoverImageUrl()); BigDecimal price = Boolean.TRUE.equals(s.getIsFree()) ? BigDecimal.ZERO : s.getPrice(); item.setUnitPrice(price); item.setQuantity(1); item.setTotalPrice(price); itemRepo.insert(item); } // 冻结积分 if (pts > 0) pointsService.freezePoints(userId, pts, order.getId()); // 纯免费/纯积分支付直接完成,无需拉起支付 if (cash.compareTo(BigDecimal.ZERO) == 0) { handlePaySuccess(order.getOrderNo(), null); } return buildOrderVO(order); } @Override public OrderVO getOrderDetail(Long orderId, Long userId) { Order order = orderRepo.selectById(orderId); if (order == null || !order.getUserId().equals(userId)) throw new BusinessException(ErrorCode.ORDER_NOT_FOUND); return buildOrderVO(order); } @Override public IPage listOrders(Long userId, String status, int pageNum, int pageSize) { IPage page = orderRepo.selectPage( new Page<>(pageNum, pageSize), new LambdaQueryWrapper() .eq(Order::getUserId, userId) .eq(status != null, Order::getStatus, status) .orderByDesc(Order::getCreatedAt)); return page.convert(this::buildOrderVO); } @Override @Transactional public void cancelOrder(Long orderId, Long userId, String reason) { Order order = orderRepo.selectById(orderId); if (order == null || !order.getUserId().equals(userId)) throw new BusinessException(ErrorCode.ORDER_NOT_FOUND); if (!"pending".equals(order.getStatus())) throw new BusinessException(ErrorCode.ORDER_STATUS_ERROR); order.setStatus("cancelled"); order.setCancelReason(reason); orderRepo.updateById(order); // 解冻积分 if (order.getPointsUsed() != null && order.getPointsUsed() > 0) pointsService.unfreezePoints(userId, order.getPointsUsed(), orderId); } @Override @Transactional public void handlePaySuccess(String orderNo, String transactionId) { Order order = orderRepo.findByOrderNo(orderNo); if (order == null) throw new BusinessException(ErrorCode.ORDER_NOT_FOUND); if ("paid".equals(order.getStatus())) return; // 幂等处理 order.setStatus("paid"); order.setPaidAt(LocalDateTime.now()); orderRepo.updateById(order); // 消耗冻结积分(正式扣减) if (order.getPointsUsed() != null && order.getPointsUsed() > 0) pointsService.consumePoints(order.getUserId(), order.getPointsUsed(), order.getId(), "order"); // 授权Skill访问权限 String dlType = (order.getPointsUsed() != null && order.getPointsUsed() > 0 && order.getCashAmount().compareTo(BigDecimal.ZERO) == 0) ? "points" : "paid"; itemRepo.findByOrderId(order.getId()).forEach(item -> downloadRepo.grantAccess(order.getUserId(), item.getSkillId(), order.getId(), dlType)); } @Override @Transactional public void applyRefund(Long orderId, Long userId, RefundApplyDTO dto) { Order order = orderRepo.selectById(orderId); if (order == null || !order.getUserId().equals(userId)) throw new BusinessException(ErrorCode.ORDER_NOT_FOUND); if (!"paid".equals(order.getStatus()) && !"completed".equals(order.getStatus())) throw new BusinessException(ErrorCode.ORDER_STATUS_ERROR); order.setStatus("refunding"); orderRepo.updateById(order); OrderRefund refund = new OrderRefund(); refund.setOrderId(orderId); refund.setRefundNo(idGenerator.generateRefundNo()); refund.setRefundAmount(order.getCashAmount()); refund.setRefundPoints(order.getPointsUsed()); refund.setReason(dto.getReason()); if (dto.getImages() != null) refund.setImages(dto.getImages().toString()); refund.setStatus("pending"); refundRepo.insert(refund); } private OrderVO buildOrderVO(Order order) { OrderVO vo = new OrderVO(); vo.setId(order.getId()); vo.setOrderNo(order.getOrderNo()); vo.setTotalAmount(order.getTotalAmount()); vo.setCashAmount(order.getCashAmount()); vo.setPointsUsed(order.getPointsUsed()); vo.setPointsDeductAmount(order.getPointsDeductAmount()); vo.setStatus(order.getStatus()); vo.setStatusLabel(switch (order.getStatus()) { case "pending" -> "待支付"; case "paid" -> "已支付"; case "completed" -> "已完成"; case "cancelled" -> "已取消"; case "refunding" -> "退款中"; case "refunded" -> "已退款"; default -> order.getStatus(); }); vo.setPaymentMethod(order.getPaymentMethod()); vo.setCreatedAt(order.getCreatedAt()); vo.setPaidAt(order.getPaidAt()); vo.setItems(itemRepo.findByOrderId(order.getId()).stream().map(i -> { OrderItemVO iv = new OrderItemVO(); iv.setSkillId(i.getSkillId()); iv.setSkillName(i.getSkillName()); iv.setSkillCover(i.getSkillCover()); iv.setUnitPrice(i.getUnitPrice()); iv.setQuantity(i.getQuantity()); iv.setTotalPrice(i.getTotalPrice()); return iv; }).toList()); return vo; } } ``` ## 五、Controller ### OrderController.java ```java package com.openclaw.controller; import com.baomidou.mybatisplus.core.metadata.IPage; import com.openclaw.common.Result; import com.openclaw.dto.*; import com.openclaw.service.OrderService; import com.openclaw.util.UserContext; import com.openclaw.vo.*; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/v1/orders") @RequiredArgsConstructor public class OrderController { private final OrderService orderService; /** 创建订单 */ @PostMapping public Result createOrder(@Valid @RequestBody OrderCreateDTO dto) { return Result.ok(orderService.createOrder(UserContext.getUserId(), dto)); } /** 订单详情 */ @GetMapping("/{id}") public Result getDetail(@PathVariable Long id) { return Result.ok(orderService.getOrderDetail(id, UserContext.getUserId())); } /** 订单列表 */ @GetMapping public Result> listOrders( @RequestParam(required = false) String status, @RequestParam(defaultValue = "1") int pageNum, @RequestParam(defaultValue = "10") int pageSize) { return Result.ok(orderService.listOrders(UserContext.getUserId(), status, pageNum, pageSize)); } /** 取消订单 */ @PutMapping("/{id}/cancel") public Result cancelOrder( @PathVariable Long id, @RequestParam(required = false) String reason) { orderService.cancelOrder(id, UserContext.getUserId(), reason); return Result.ok(); } /** 申请退款 */ @PostMapping("/{id}/refund") public Result applyRefund( @PathVariable Long id, @Valid @RequestBody RefundApplyDTO dto) { orderService.applyRefund(id, UserContext.getUserId(), dto); return Result.ok(); } } ``` --- **文档版本**:v1.0 | **创建日期**:2026-03-16