diff --git a/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java b/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java index ce36186..5d87e2d 100644 --- a/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java +++ b/src/main/java/com/ycwl/basic/controller/mobile/AppOrderV2Controller.java @@ -1,5 +1,6 @@ package com.ycwl.basic.controller.mobile; +import com.github.pagehelper.PageInfo; import com.ycwl.basic.constant.BaseContextHandler; import com.ycwl.basic.utils.ApiResponse; import com.ycwl.basic.pricing.dto.*; @@ -8,6 +9,10 @@ import com.ycwl.basic.service.pc.FaceService; import com.ycwl.basic.service.PriceCacheService; import com.ycwl.basic.model.pc.face.resp.FaceRespVO; import com.ycwl.basic.dto.MobileOrderRequest; +import com.ycwl.basic.order.service.IOrderService; +import com.ycwl.basic.order.dto.OrderV2DetailResponse; +import com.ycwl.basic.order.dto.OrderV2ListResponse; +import com.ycwl.basic.order.dto.OrderV2PageRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.web.bind.annotation.*; @@ -27,6 +32,7 @@ public class AppOrderV2Controller { private final IPriceCalculationService priceCalculationService; private final FaceService faceService; private final PriceCacheService priceCacheService; + private final IOrderService orderService; /** * 移动端价格计算 @@ -157,20 +163,80 @@ public class AppOrderV2Controller { log.info("价格缓存验证通过: userId={}, scenicId={}, finalAmount={}", currentUserId, scenicId, cachedResult.getFinalAmount()); - // TODO: 实现订单创建逻辑 - // 1. 生成订单号 - // 2. 创建订单基础信息 - // 3. 保存订单商品明细 - // 4. 记录使用的优惠券和券码信息 - // 5. 更新优惠券和券码状态 - // 6. 初始化订单状态为待支付 - // 7. 发送订单创建成功通知 + // 创建订单 + try { + Long orderId = orderService.createOrder(request, currentUserId, scenicId, cachedResult); + + log.info("移动端订单创建成功: orderId={}, userId={}, scenicId={}, finalAmount={}", + orderId, currentUserId, scenicId, cachedResult.getFinalAmount()); + + return ApiResponse.success(orderId.toString()); + + } catch (Exception e) { + log.error("订单创建失败: userId={}, scenicId={}, error={}", currentUserId, scenicId, e.getMessage(), e); + return ApiResponse.fail("订单创建失败,请稍后重试"); + } + } + + // ====== 新增移动端订单查询功能 ====== + + /** + * 用户分页查询自己的订单列表 + */ + @PostMapping("/page") + public ApiResponse> pageUserOrders(@RequestBody OrderV2PageRequest request) { + String currentUserIdStr = BaseContextHandler.getUserId(); + if (currentUserIdStr == null) { + log.warn("用户未登录"); + return ApiResponse.fail("用户未登录"); + } - String orderId = "ORDER_" + System.currentTimeMillis(); // 临时订单号生成逻辑 + Long currentUserId = Long.valueOf(currentUserIdStr); + request.setMemberId(currentUserId); // 设置当前用户ID,确保只查询自己的订单 - log.info("移动端订单创建成功: orderId={}, userId={}, scenicId={}, finalAmount={}", - orderId, currentUserId, scenicId, cachedResult.getFinalAmount()); + log.info("用户查询订单列表: userId={}, request={}", currentUserId, request); - return ApiResponse.success(orderId); + try { + PageInfo pageInfo = orderService.pageOrdersByUser(request); + return ApiResponse.success(pageInfo); + } catch (Exception e) { + log.error("查询用户订单列表失败: userId={}", currentUserId, e); + return ApiResponse.fail("查询失败:" + e.getMessage()); + } + } + + /** + * 用户查询自己的订单详情 + */ + @GetMapping("/detail/{orderId}") + public ApiResponse getUserOrderDetail(@PathVariable("orderId") Long orderId) { + String currentUserIdStr = BaseContextHandler.getUserId(); + if (currentUserIdStr == null) { + log.warn("用户未登录"); + return ApiResponse.fail("用户未登录"); + } + + Long currentUserId = Long.valueOf(currentUserIdStr); + + log.info("用户查询订单详情: userId={}, orderId={}", currentUserId, orderId); + + try { + OrderV2DetailResponse detail = orderService.getOrderDetail(orderId); + if (detail == null) { + return ApiResponse.fail("订单不存在"); + } + + // 验证订单是否属于当前用户 + if (!currentUserId.equals(detail.getMemberId())) { + log.warn("用户尝试访问他人订单: userId={}, orderId={}, orderOwner={}", + currentUserId, orderId, detail.getMemberId()); + return ApiResponse.fail("无权访问该订单"); + } + + return ApiResponse.success(detail); + } catch (Exception e) { + log.error("查询用户订单详情失败: userId={}, orderId={}", currentUserId, orderId, e); + return ApiResponse.fail("查询失败:" + e.getMessage()); + } } } diff --git a/src/main/java/com/ycwl/basic/order/controller/OrderV2Controller.java b/src/main/java/com/ycwl/basic/order/controller/OrderV2Controller.java new file mode 100644 index 0000000..c5c698c --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/controller/OrderV2Controller.java @@ -0,0 +1,89 @@ +package com.ycwl.basic.order.controller; + +import com.github.pagehelper.PageInfo; +import com.ycwl.basic.order.dto.*; +import com.ycwl.basic.order.service.IOrderService; +import com.ycwl.basic.utils.ApiResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +/** + * 订单管理控制器V2 - 管理端 + */ +@Slf4j +@RestController +@RequestMapping("/api/order/v2") +@RequiredArgsConstructor +public class OrderV2Controller { + + private final IOrderService orderService; + + /** + * 分页查询订单列表 + */ + @PostMapping("/page") + public ApiResponse> pageOrders(@RequestBody OrderV2PageRequest request) { + log.info("分页查询订单列表: {}", request); + try { + PageInfo pageInfo = orderService.pageOrders(request); + return ApiResponse.success(pageInfo); + } catch (Exception e) { + log.error("分页查询订单列表失败", e); + return ApiResponse.fail("查询失败:" + e.getMessage()); + } + } + + /** + * 查询订单详情 + */ + @GetMapping("/detail/{orderId}") + public ApiResponse getOrderDetail(@PathVariable("orderId") Long orderId) { + log.info("查询订单详情: orderId={}", orderId); + try { + OrderV2DetailResponse detail = orderService.getOrderDetail(orderId); + if (detail == null) { + return ApiResponse.fail("订单不存在"); + } + return ApiResponse.success(detail); + } catch (Exception e) { + log.error("查询订单详情失败: orderId={}", orderId, e); + return ApiResponse.fail("查询失败:" + e.getMessage()); + } + } + + /** + * 更新订单备注 + */ + @PutMapping("/remark/{orderId}") + public ApiResponse updateOrderRemarks(@PathVariable("orderId") Long orderId, + @RequestBody OrderRemarkRequest request) { + log.info("更新订单备注: orderId={}, remarks={}", orderId, request.getRemarks()); + try { + boolean success = orderService.updateOrderRemarks(orderId, request.getRemarks()); + if (success) { + return ApiResponse.success("备注更新成功"); + } else { + return ApiResponse.fail("备注更新失败"); + } + } catch (Exception e) { + log.error("更新订单备注失败: orderId={}", orderId, e); + return ApiResponse.fail("更新失败:" + e.getMessage()); + } + } + + /** + * 申请退款 + */ + @PostMapping("/refund") + public ApiResponse createRefund(@RequestBody RefundRequest request) { + log.info("申请退款: {}", request); + try { + Long refundId = orderService.createRefundRecord(request); + return ApiResponse.success("退款申请已提交,退款ID:" + refundId); + } catch (Exception e) { + log.error("申请退款失败: {}", request, e); + return ApiResponse.fail("退款申请失败:" + e.getMessage()); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/dto/OrderRemarkRequest.java b/src/main/java/com/ycwl/basic/order/dto/OrderRemarkRequest.java new file mode 100644 index 0000000..6b8563f --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/dto/OrderRemarkRequest.java @@ -0,0 +1,15 @@ +package com.ycwl.basic.order.dto; + +import lombok.Data; + +/** + * 订单备注修改请求DTO + */ +@Data +public class OrderRemarkRequest { + + /** + * 订单备注 + */ + private String remarks; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/dto/OrderV2DetailResponse.java b/src/main/java/com/ycwl/basic/order/dto/OrderV2DetailResponse.java new file mode 100644 index 0000000..543e663 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/dto/OrderV2DetailResponse.java @@ -0,0 +1,150 @@ +package com.ycwl.basic.order.dto; + +import com.ycwl.basic.order.entity.OrderDiscountV2; +import com.ycwl.basic.order.entity.OrderItemV2; +import com.ycwl.basic.order.entity.OrderRefundV2; +import com.ycwl.basic.order.enums.OrderStatus; +import com.ycwl.basic.order.enums.PaymentStatus; +import com.ycwl.basic.order.enums.RefundStatus; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +/** + * 订单详情响应DTO + */ +@Data +public class OrderV2DetailResponse { + + /** + * 订单ID + */ + private Long id; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 会员ID + */ + private Long memberId; + + /** + * 小程序openId + */ + private String openId; + + /** + * 人脸ID + */ + private Long faceId; + + /** + * 景区ID + */ + private Long scenicId; + + /** + * 原始金额 + */ + private BigDecimal originalAmount; + + /** + * 优惠金额 + */ + private BigDecimal discountAmount; + + /** + * 最终金额 + */ + private BigDecimal finalAmount; + + /** + * 订单状态 + */ + private OrderStatus orderStatus; + + /** + * 订单状态描述 + */ + private String orderStatusDesc; + + /** + * 支付状态 + */ + private PaymentStatus paymentStatus; + + /** + * 支付状态描述 + */ + private String paymentStatusDesc; + + /** + * 退款状态 + */ + private RefundStatus refundStatus; + + /** + * 退款状态描述 + */ + private String refundStatusDesc; + + /** + * 总退款金额 + */ + private BigDecimal totalRefundAmount; + + /** + * 订单备注 + */ + private String remarks; + + /** + * 支付时间 + */ + private Date payTime; + + /** + * 完成时间 + */ + private Date completeTime; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 创建人 + */ + private Long createBy; + + /** + * 更新人 + */ + private Long updateBy; + + /** + * 订单商品明细列表 + */ + private List orderItems; + + /** + * 订单优惠记录列表 + */ + private List orderDiscounts; + + /** + * 订单退款记录列表 + */ + private List orderRefunds; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/dto/OrderV2ListResponse.java b/src/main/java/com/ycwl/basic/order/dto/OrderV2ListResponse.java new file mode 100644 index 0000000..22d317f --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/dto/OrderV2ListResponse.java @@ -0,0 +1,131 @@ +package com.ycwl.basic.order.dto; + +import com.ycwl.basic.order.enums.OrderStatus; +import com.ycwl.basic.order.enums.PaymentStatus; +import com.ycwl.basic.order.enums.RefundStatus; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 订单列表响应DTO + */ +@Data +public class OrderV2ListResponse { + + /** + * 订单ID + */ + private Long id; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 会员ID + */ + private Long memberId; + + /** + * 小程序openId + */ + private String openId; + + /** + * 人脸ID + */ + private Long faceId; + + /** + * 景区ID + */ + private Long scenicId; + + /** + * 原始金额 + */ + private BigDecimal originalAmount; + + /** + * 优惠金额 + */ + private BigDecimal discountAmount; + + /** + * 最终金额 + */ + private BigDecimal finalAmount; + + /** + * 订单状态 + */ + private OrderStatus orderStatus; + + /** + * 订单状态描述 + */ + private String orderStatusDesc; + + /** + * 支付状态 + */ + private PaymentStatus paymentStatus; + + /** + * 支付状态描述 + */ + private String paymentStatusDesc; + + /** + * 退款状态 + */ + private RefundStatus refundStatus; + + /** + * 退款状态描述 + */ + private String refundStatusDesc; + + /** + * 总退款金额 + */ + private BigDecimal totalRefundAmount; + + /** + * 订单备注 + */ + private String remarks; + + /** + * 支付时间 + */ + private Date payTime; + + /** + * 完成时间 + */ + private Date completeTime; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 更新时间 + */ + private Date updateTime; + + /** + * 商品数量(订单中商品种类数) + */ + private Integer itemCount; + + /** + * 商品总数量(所有商品数量之和) + */ + private Integer totalQuantity; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/dto/OrderV2PageRequest.java b/src/main/java/com/ycwl/basic/order/dto/OrderV2PageRequest.java new file mode 100644 index 0000000..f94b705 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/dto/OrderV2PageRequest.java @@ -0,0 +1,72 @@ +package com.ycwl.basic.order.dto; + +import lombok.Data; + +import java.util.Date; + +/** + * 订单分页查询请求DTO + */ +@Data +public class OrderV2PageRequest { + + /** + * 页码(从1开始) + */ + private Integer pageNum = 1; + + /** + * 每页大小 + */ + private Integer pageSize = 10; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 用户ID(移动端查询时自动设置) + */ + private Long memberId; + + /** + * 景区ID + */ + private Long scenicId; + + /** + * 订单状态 + */ + private String orderStatus; + + /** + * 支付状态 + */ + private String paymentStatus; + + /** + * 退款状态 + */ + private String refundStatus; + + /** + * 开始创建时间 + */ + private Date createTimeStart; + + /** + * 结束创建时间 + */ + private Date createTimeEnd; + + /** + * 开始支付时间 + */ + private Date payTimeStart; + + /** + * 结束支付时间 + */ + private Date payTimeEnd; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/dto/RefundRequest.java b/src/main/java/com/ycwl/basic/order/dto/RefundRequest.java new file mode 100644 index 0000000..a3cb265 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/dto/RefundRequest.java @@ -0,0 +1,52 @@ +package com.ycwl.basic.order.dto; + +import lombok.Data; + +import java.math.BigDecimal; + +/** + * 退款申请请求DTO + */ +@Data +public class RefundRequest { + + /** + * 订单ID + */ + private Long orderId; + + /** + * 退款类型 + */ + private String refundType; + + /** + * 退款金额 + */ + private BigDecimal refundAmount; + + /** + * 退款手续费 + */ + private BigDecimal refundFee = BigDecimal.ZERO; + + /** + * 退款原因 + */ + private String refundReason; + + /** + * 退款详细说明 + */ + private String refundDescription; + + /** + * 操作备注 + */ + private String operatorRemarks; + + /** + * 退款渠道 + */ + private String refundChannel = "ORIGINAL"; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/entity/OrderDiscountV2.java b/src/main/java/com/ycwl/basic/order/entity/OrderDiscountV2.java new file mode 100644 index 0000000..04ccdb7 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/entity/OrderDiscountV2.java @@ -0,0 +1,91 @@ +package com.ycwl.basic.order.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.ycwl.basic.order.enums.DiscountType; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 订单优惠记录表实体V2 + */ +@Data +@TableName("order_discount_v2") +public class OrderDiscountV2 { + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 订单ID + */ + @TableField("order_id") + private Long orderId; + + /** + * 优惠类型 + */ + @TableField("discount_type") + private DiscountType discountType; + + /** + * 优惠名称 + */ + @TableField("discount_name") + private String discountName; + + /** + * 优惠金额 + */ + @TableField("discount_amount") + private BigDecimal discountAmount; + + /** + * 优惠比例 + */ + @TableField("discount_rate") + private BigDecimal discountRate; + + /** + * 显示顺序 + */ + @TableField("sort_order") + private Integer sortOrder; + + /** + * 优惠券ID + */ + @TableField("coupon_id") + private Long couponId; + + /** + * 优惠券码 + */ + @TableField("coupon_code") + private String couponCode; + + /** + * 券码 + */ + @TableField("voucher_code") + private String voucherCode; + + /** + * 券码批次ID + */ + @TableField("voucher_batch_id") + private Long voucherBatchId; + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/entity/OrderItemV2.java b/src/main/java/com/ycwl/basic/order/entity/OrderItemV2.java new file mode 100644 index 0000000..2ff3353 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/entity/OrderItemV2.java @@ -0,0 +1,84 @@ +package com.ycwl.basic.order.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 订单商品明细表实体V2 + */ +@Data +@TableName("order_item_v2") +public class OrderItemV2 { + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 订单ID + */ + @TableField("order_id") + private Long orderId; + + /** + * 商品类型 + */ + @TableField("product_type") + private String productType; + + /** + * 商品ID + */ + @TableField("product_id") + private String productId; + + /** + * 商品名称 + */ + @TableField("product_name") + private String productName; + + /** + * 商品数量 + */ + @TableField("quantity") + private Integer quantity; + + /** + * 单价 + */ + @TableField("unit_price") + private BigDecimal unitPrice; + + /** + * 原始小计 + */ + @TableField("original_amount") + private BigDecimal originalAmount; + + /** + * 最终小计 + */ + @TableField("final_amount") + private BigDecimal finalAmount; + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 更新时间 + */ + @TableField("update_time") + private Date updateTime; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/entity/OrderRefundV2.java b/src/main/java/com/ycwl/basic/order/entity/OrderRefundV2.java new file mode 100644 index 0000000..917677d --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/entity/OrderRefundV2.java @@ -0,0 +1,146 @@ +package com.ycwl.basic.order.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.ycwl.basic.order.enums.RefundStatus; +import com.ycwl.basic.order.enums.RefundType; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 订单退款记录表实体V2 + */ +@Data +@TableName("order_refund_v2") +public class OrderRefundV2 { + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 订单ID + */ + @TableField("order_id") + private Long orderId; + + /** + * 退款单号 + */ + @TableField("refund_no") + private String refundNo; + + /** + * 退款类型 + */ + @TableField("refund_type") + private RefundType refundType; + + /** + * 退款金额 + */ + @TableField("refund_amount") + private BigDecimal refundAmount; + + /** + * 退款手续费 + */ + @TableField("refund_fee") + private BigDecimal refundFee; + + /** + * 退款状态 + */ + @TableField("refund_status") + private RefundStatus refundStatus; + + /** + * 退款原因 + */ + @TableField("refund_reason") + private String refundReason; + + /** + * 退款详细说明 + */ + @TableField("refund_description") + private String refundDescription; + + /** + * 申请人ID + */ + @TableField("apply_by") + private Long applyBy; + + /** + * 审批人ID + */ + @TableField("approve_by") + private Long approveBy; + + /** + * 操作备注 + */ + @TableField("operator_remarks") + private String operatorRemarks; + + /** + * 支付平台退款单号 + */ + @TableField("payment_refund_id") + private String paymentRefundId; + + /** + * 退款渠道 + */ + @TableField("refund_channel") + private String refundChannel; + + /** + * 申请时间 + */ + @TableField("apply_time") + private Date applyTime; + + /** + * 审批时间 + */ + @TableField("approve_time") + private Date approveTime; + + /** + * 完成时间 + */ + @TableField("complete_time") + private Date completeTime; + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 更新时间 + */ + @TableField("update_time") + private Date updateTime; + + /** + * 创建人 + */ + @TableField("create_by") + private Long createBy; + + /** + * 更新人 + */ + @TableField("update_by") + private Long updateBy; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/entity/OrderV2.java b/src/main/java/com/ycwl/basic/order/entity/OrderV2.java new file mode 100644 index 0000000..cc5e0dc --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/entity/OrderV2.java @@ -0,0 +1,153 @@ +package com.ycwl.basic.order.entity; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import com.baomidou.mybatisplus.annotation.TableName; +import com.ycwl.basic.order.enums.OrderStatus; +import com.ycwl.basic.order.enums.PaymentStatus; +import com.ycwl.basic.order.enums.RefundStatus; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 订单主表实体V2 + */ +@Data +@TableName("order_v2") +public class OrderV2 { + + /** + * 主键ID + */ + @TableId(value = "id", type = IdType.AUTO) + private Long id; + + /** + * 订单号 + */ + @TableField("order_no") + private String orderNo; + + /** + * 会员ID + */ + @TableField("member_id") + private Long memberId; + + /** + * 小程序openId + */ + @TableField("open_id") + private String openId; + + /** + * 人脸ID + */ + @TableField("face_id") + private Long faceId; + + /** + * 景区ID + */ + @TableField("scenic_id") + private Long scenicId; + + /** + * 原始金额 + */ + @TableField("original_amount") + private BigDecimal originalAmount; + + /** + * 优惠金额 + */ + @TableField("discount_amount") + private BigDecimal discountAmount; + + /** + * 最终金额 + */ + @TableField("final_amount") + private BigDecimal finalAmount; + + /** + * 订单状态 + */ + @TableField("order_status") + private OrderStatus orderStatus; + + /** + * 支付状态 + */ + @TableField("payment_status") + private PaymentStatus paymentStatus; + + /** + * 退款状态 + */ + @TableField("refund_status") + private RefundStatus refundStatus; + + /** + * 总退款金额 + */ + @TableField("total_refund_amount") + private BigDecimal totalRefundAmount; + + /** + * 订单备注 + */ + @TableField("remarks") + private String remarks; + + /** + * 支付时间 + */ + @TableField("pay_time") + private Date payTime; + + /** + * 完成时间 + */ + @TableField("complete_time") + private Date completeTime; + + /** + * 创建时间 + */ + @TableField("create_time") + private Date createTime; + + /** + * 更新时间 + */ + @TableField("update_time") + private Date updateTime; + + /** + * 创建人 + */ + @TableField("create_by") + private Long createBy; + + /** + * 更新人 + */ + @TableField("update_by") + private Long updateBy; + + /** + * 是否删除 + */ + @TableField("deleted") + private Integer deleted; + + /** + * 删除时间 + */ + @TableField("deleted_at") + private Date deletedAt; +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/enums/DiscountType.java b/src/main/java/com/ycwl/basic/order/enums/DiscountType.java new file mode 100644 index 0000000..82730df --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/enums/DiscountType.java @@ -0,0 +1,60 @@ +package com.ycwl.basic.order.enums; + +/** + * 优惠类型枚举 + */ +public enum DiscountType { + + /** + * 优惠券 + */ + COUPON("COUPON", "优惠券"), + + /** + * 券码 + */ + VOUCHER("VOUCHER", "券码"), + + /** + * 限时立减 + */ + FLASH_SALE("FLASH_SALE", "限时立减"), + + /** + * 套餐优惠 + */ + BUNDLE("BUNDLE", "套餐优惠"), + + /** + * 一口价优惠 + */ + FIXED_PRICE("FIXED_PRICE", "一口价优惠"); + + private final String code; + private final String description; + + DiscountType(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + /** + * 根据code获取枚举 + */ + public static DiscountType fromCode(String code) { + for (DiscountType type : values()) { + if (type.code.equals(code)) { + return type; + } + } + throw new IllegalArgumentException("Unknown DiscountType code: " + code); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/enums/OrderStatus.java b/src/main/java/com/ycwl/basic/order/enums/OrderStatus.java new file mode 100644 index 0000000..97f1777 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/enums/OrderStatus.java @@ -0,0 +1,70 @@ +package com.ycwl.basic.order.enums; + +/** + * 订单状态枚举 + */ +public enum OrderStatus { + + /** + * 待支付 + */ + PENDING_PAYMENT("PENDING_PAYMENT", "待支付"), + + /** + * 已支付 + */ + PAID("PAID", "已支付"), + + /** + * 处理中 + */ + PROCESSING("PROCESSING", "处理中"), + + /** + * 已完成 + */ + COMPLETED("COMPLETED", "已完成"), + + /** + * 已取消 + */ + CANCELLED("CANCELLED", "已取消"), + + /** + * 退款中 + */ + REFUNDING("REFUNDING", "退款中"), + + /** + * 已退款 + */ + REFUNDED("REFUNDED", "已退款"); + + private final String code; + private final String description; + + OrderStatus(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + /** + * 根据code获取枚举 + */ + public static OrderStatus fromCode(String code) { + for (OrderStatus status : values()) { + if (status.code.equals(code)) { + return status; + } + } + throw new IllegalArgumentException("Unknown OrderStatus code: " + code); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/enums/PaymentStatus.java b/src/main/java/com/ycwl/basic/order/enums/PaymentStatus.java new file mode 100644 index 0000000..02e9602 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/enums/PaymentStatus.java @@ -0,0 +1,50 @@ +package com.ycwl.basic.order.enums; + +/** + * 支付状态枚举 + */ +public enum PaymentStatus { + + /** + * 未支付 + */ + UNPAID("UNPAID", "未支付"), + + /** + * 已支付 + */ + PAID("PAID", "已支付"), + + /** + * 已退款 + */ + REFUNDED("REFUNDED", "已退款"); + + private final String code; + private final String description; + + PaymentStatus(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + /** + * 根据code获取枚举 + */ + public static PaymentStatus fromCode(String code) { + for (PaymentStatus status : values()) { + if (status.code.equals(code)) { + return status; + } + } + throw new IllegalArgumentException("Unknown PaymentStatus code: " + code); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/enums/RefundStatus.java b/src/main/java/com/ycwl/basic/order/enums/RefundStatus.java new file mode 100644 index 0000000..d52e83d --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/enums/RefundStatus.java @@ -0,0 +1,55 @@ +package com.ycwl.basic.order.enums; + +/** + * 退款状态枚举 + */ +public enum RefundStatus { + + /** + * 无退款 + */ + NO_REFUND("NO_REFUND", "无退款"), + + /** + * 部分退款 + */ + PARTIAL_REFUND("PARTIAL_REFUND", "部分退款"), + + /** + * 全额退款 + */ + FULL_REFUND("FULL_REFUND", "全额退款"), + + /** + * 退款处理中 + */ + REFUND_PROCESSING("REFUND_PROCESSING", "退款处理中"); + + private final String code; + private final String description; + + RefundStatus(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + /** + * 根据code获取枚举 + */ + public static RefundStatus fromCode(String code) { + for (RefundStatus status : values()) { + if (status.code.equals(code)) { + return status; + } + } + throw new IllegalArgumentException("Unknown RefundStatus code: " + code); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/enums/RefundType.java b/src/main/java/com/ycwl/basic/order/enums/RefundType.java new file mode 100644 index 0000000..7111883 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/enums/RefundType.java @@ -0,0 +1,50 @@ +package com.ycwl.basic.order.enums; + +/** + * 退款类型枚举 + */ +public enum RefundType { + + /** + * 全额退款 + */ + FULL_REFUND("FULL_REFUND", "全额退款"), + + /** + * 部分退款 + */ + PARTIAL_REFUND("PARTIAL_REFUND", "部分退款"), + + /** + * 商品退款 + */ + ITEM_REFUND("ITEM_REFUND", "商品退款"); + + private final String code; + private final String description; + + RefundType(String code, String description) { + this.code = code; + this.description = description; + } + + public String getCode() { + return code; + } + + public String getDescription() { + return description; + } + + /** + * 根据code获取枚举 + */ + public static RefundType fromCode(String code) { + for (RefundType type : values()) { + if (type.code.equals(code)) { + return type; + } + } + throw new IllegalArgumentException("Unknown RefundType code: " + code); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/event/OrderEventListener.java b/src/main/java/com/ycwl/basic/order/event/OrderEventListener.java new file mode 100644 index 0000000..5fb92ec --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/event/OrderEventListener.java @@ -0,0 +1,28 @@ +package com.ycwl.basic.order.event; + +/** + * 订单事件监听器接口 + */ +public interface OrderEventListener { + + /** + * 支付状态变更事件 + * + * @param event 支付状态变更事件 + */ + void onPaymentStatusChanged(PaymentStatusChangeEvent event); + + /** + * 退款状态变更事件 + * + * @param event 退款状态变更事件 + */ + void onRefundStatusChanged(RefundStatusChangeEvent event); + + /** + * 订单状态变更事件 + * + * @param event 订单状态变更事件 + */ + void onOrderStatusChanged(OrderStatusChangeEvent event); +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/event/OrderEventManager.java b/src/main/java/com/ycwl/basic/order/event/OrderEventManager.java new file mode 100644 index 0000000..c6161da --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/event/OrderEventManager.java @@ -0,0 +1,125 @@ +package com.ycwl.basic.order.event; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; + +/** + * 订单事件管理器 + * 管理事件监听器的注册和事件分发 + */ +@Slf4j +@Component +public class OrderEventManager { + + private final List listeners = new ArrayList<>(); + private final Executor eventExecutor = Executors.newFixedThreadPool(5, + r -> new Thread(r, "order-event-thread")); + + @PostConstruct + public void init() { + log.info("订单事件管理器初始化完成"); + } + + /** + * 注册事件监听器 + * + * @param listener 监听器实例 + */ + public void registerListener(OrderEventListener listener) { + if (listener != null && !listeners.contains(listener)) { + listeners.add(listener); + log.info("注册订单事件监听器: {}", listener.getClass().getSimpleName()); + } + } + + /** + * 移除事件监听器 + * + * @param listener 监听器实例 + */ + public void removeListener(OrderEventListener listener) { + if (listeners.remove(listener)) { + log.info("移除订单事件监听器: {}", listener.getClass().getSimpleName()); + } + } + + /** + * 发布支付状态变更事件 + * + * @param event 支付状态变更事件 + */ + public void publishPaymentStatusChangeEvent(PaymentStatusChangeEvent event) { + log.info("发布支付状态变更事件: orderId={}, {} -> {}", + event.getOrderId(), event.getOldPaymentStatus(), event.getNewPaymentStatus()); + + CompletableFuture.runAsync(() -> { + for (OrderEventListener listener : listeners) { + try { + listener.onPaymentStatusChanged(event); + } catch (Exception e) { + log.error("处理支付状态变更事件失败: listener={}, orderId={}", + listener.getClass().getSimpleName(), event.getOrderId(), e); + } + } + }, eventExecutor); + } + + /** + * 发布退款状态变更事件 + * + * @param event 退款状态变更事件 + */ + public void publishRefundStatusChangeEvent(RefundStatusChangeEvent event) { + log.info("发布退款状态变更事件: orderId={}, refundId={}, {} -> {}", + event.getOrderId(), event.getRefundId(), + event.getOldRefundStatus(), event.getNewRefundStatus()); + + CompletableFuture.runAsync(() -> { + for (OrderEventListener listener : listeners) { + try { + listener.onRefundStatusChanged(event); + } catch (Exception e) { + log.error("处理退款状态变更事件失败: listener={}, orderId={}, refundId={}", + listener.getClass().getSimpleName(), event.getOrderId(), event.getRefundId(), e); + } + } + }, eventExecutor); + } + + /** + * 发布订单状态变更事件 + * + * @param event 订单状态变更事件 + */ + public void publishOrderStatusChangeEvent(OrderStatusChangeEvent event) { + log.info("发布订单状态变更事件: orderId={}, {} -> {}", + event.getOrderId(), event.getOldOrderStatus(), event.getNewOrderStatus()); + + CompletableFuture.runAsync(() -> { + for (OrderEventListener listener : listeners) { + try { + listener.onOrderStatusChanged(event); + } catch (Exception e) { + log.error("处理订单状态变更事件失败: listener={}, orderId={}", + listener.getClass().getSimpleName(), event.getOrderId(), e); + } + } + }, eventExecutor); + } + + /** + * 获取已注册的监听器数量 + * + * @return 监听器数量 + */ + public int getListenerCount() { + return listeners.size(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/event/OrderStatusChangeEvent.java b/src/main/java/com/ycwl/basic/order/event/OrderStatusChangeEvent.java new file mode 100644 index 0000000..93ea868 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/event/OrderStatusChangeEvent.java @@ -0,0 +1,60 @@ +package com.ycwl.basic.order.event; + +import com.ycwl.basic.order.enums.OrderStatus; +import lombok.Data; + +import java.util.Date; + +/** + * 订单状态变更事件 + */ +@Data +public class OrderStatusChangeEvent { + + /** + * 订单ID + */ + private Long orderId; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 旧的订单状态 + */ + private OrderStatus oldOrderStatus; + + /** + * 新的订单状态 + */ + private OrderStatus newOrderStatus; + + /** + * 变更时间 + */ + private Date changeTime; + + /** + * 变更原因 + */ + private String changeReason; + + /** + * 操作人ID + */ + private Long operatorId; + + public OrderStatusChangeEvent(Long orderId, String orderNo, + OrderStatus oldStatus, OrderStatus newStatus, + String changeReason, Long operatorId) { + this.orderId = orderId; + this.orderNo = orderNo; + this.oldOrderStatus = oldStatus; + this.newOrderStatus = newStatus; + this.changeReason = changeReason; + this.operatorId = operatorId; + this.changeTime = new Date(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/event/PaymentStatusChangeEvent.java b/src/main/java/com/ycwl/basic/order/event/PaymentStatusChangeEvent.java new file mode 100644 index 0000000..8a5d6d3 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/event/PaymentStatusChangeEvent.java @@ -0,0 +1,60 @@ +package com.ycwl.basic.order.event; + +import com.ycwl.basic.order.enums.PaymentStatus; +import lombok.Data; + +import java.util.Date; + +/** + * 支付状态变更事件 + */ +@Data +public class PaymentStatusChangeEvent { + + /** + * 订单ID + */ + private Long orderId; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 旧的支付状态 + */ + private PaymentStatus oldPaymentStatus; + + /** + * 新的支付状态 + */ + private PaymentStatus newPaymentStatus; + + /** + * 变更时间 + */ + private Date changeTime; + + /** + * 变更原因 + */ + private String changeReason; + + /** + * 操作人ID + */ + private Long operatorId; + + public PaymentStatusChangeEvent(Long orderId, String orderNo, + PaymentStatus oldStatus, PaymentStatus newStatus, + String changeReason, Long operatorId) { + this.orderId = orderId; + this.orderNo = orderNo; + this.oldPaymentStatus = oldStatus; + this.newPaymentStatus = newStatus; + this.changeReason = changeReason; + this.operatorId = operatorId; + this.changeTime = new Date(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/event/RefundStatusChangeEvent.java b/src/main/java/com/ycwl/basic/order/event/RefundStatusChangeEvent.java new file mode 100644 index 0000000..ce6949a --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/event/RefundStatusChangeEvent.java @@ -0,0 +1,79 @@ +package com.ycwl.basic.order.event; + +import com.ycwl.basic.order.enums.RefundStatus; +import lombok.Data; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * 退款状态变更事件 + */ +@Data +public class RefundStatusChangeEvent { + + /** + * 订单ID + */ + private Long orderId; + + /** + * 订单号 + */ + private String orderNo; + + /** + * 退款记录ID + */ + private Long refundId; + + /** + * 退款单号 + */ + private String refundNo; + + /** + * 旧的退款状态 + */ + private RefundStatus oldRefundStatus; + + /** + * 新的退款状态 + */ + private RefundStatus newRefundStatus; + + /** + * 退款金额 + */ + private BigDecimal refundAmount; + + /** + * 变更时间 + */ + private Date changeTime; + + /** + * 变更原因 + */ + private String changeReason; + + /** + * 操作人ID + */ + private Long operatorId; + + public RefundStatusChangeEvent(Long orderId, String orderNo, Long refundId, String refundNo, + RefundStatus oldStatus, RefundStatus newStatus, + BigDecimal refundAmount, String changeReason, Long operatorId) { + this.orderId = orderId; + this.orderNo = orderNo; + this.refundId = refundId; + this.refundNo = refundNo; + this.oldRefundStatus = oldStatus; + this.newRefundStatus = newStatus; + this.refundAmount = refundAmount; + this.changeReason = changeReason; + this.operatorId = operatorId; + this.changeTime = new Date(); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/event/impl/PaymentStatusChangeListener.java b/src/main/java/com/ycwl/basic/order/event/impl/PaymentStatusChangeListener.java new file mode 100644 index 0000000..baf6a90 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/event/impl/PaymentStatusChangeListener.java @@ -0,0 +1,120 @@ +package com.ycwl.basic.order.event.impl; + +import com.ycwl.basic.order.event.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import jakarta.annotation.Resource; + +/** + * 支付状态变更监听器示例 + * 应用启动后自动注册到事件管理器 + */ +@Slf4j +@Component +public class PaymentStatusChangeListener implements OrderEventListener { + + @Resource + private OrderEventManager orderEventManager; + + @PostConstruct + public void init() { + // 应用启动后自动注册到事件管理器 + orderEventManager.registerListener(this); + log.info("支付状态变更监听器注册完成"); + } + + @Override + public void onPaymentStatusChanged(PaymentStatusChangeEvent event) { + log.info("处理支付状态变更事件: orderId={}, orderNo={}, {} -> {}, reason={}", + event.getOrderId(), event.getOrderNo(), + event.getOldPaymentStatus(), event.getNewPaymentStatus(), + event.getChangeReason()); + + // 根据支付状态执行相应的业务逻辑 + switch (event.getNewPaymentStatus()) { + case PAID: + handlePaymentSuccess(event); + break; + case REFUNDED: + handlePaymentRefunded(event); + break; + case UNPAID: + handlePaymentPending(event); + break; + default: + log.warn("未处理的支付状态: {}", event.getNewPaymentStatus()); + } + } + + @Override + public void onRefundStatusChanged(RefundStatusChangeEvent event) { + log.info("处理退款状态变更事件: orderId={}, refundId={}, {} -> {}", + event.getOrderId(), event.getRefundId(), + event.getOldRefundStatus(), event.getNewRefundStatus()); + + // 可以在这里添加退款相关的业务逻辑 + // 比如发送通知、更新库存等 + } + + @Override + public void onOrderStatusChanged(OrderStatusChangeEvent event) { + log.info("处理订单状态变更事件: orderId={}, {} -> {}", + event.getOrderId(), event.getOldOrderStatus(), event.getNewOrderStatus()); + + // 可以在这里添加订单状态相关的业务逻辑 + // 比如发送用户通知、触发后续流程等 + } + + /** + * 处理支付成功事件 + */ + private void handlePaymentSuccess(PaymentStatusChangeEvent event) { + log.info("处理支付成功: orderId={}", event.getOrderId()); + + // 这里可以添加支付成功后的业务逻辑: + // 1. 发送支付成功通知 + // 2. 更新商品库存 + // 3. 生成相关凭证 + // 4. 发送短信/邮件通知 + // 5. 启动履约流程 + + // 示例:记录支付成功日志 + log.info("订单支付成功处理完成: orderId={}, changeTime={}", + event.getOrderId(), event.getChangeTime()); + } + + /** + * 处理支付退款事件 + */ + private void handlePaymentRefunded(PaymentStatusChangeEvent event) { + log.info("处理支付退款: orderId={}", event.getOrderId()); + + // 这里可以添加支付退款后的业务逻辑: + // 1. 恢复商品库存 + // 2. 取消相关服务 + // 3. 发送退款通知 + // 4. 更新会员积分 + + // 示例:记录退款处理日志 + log.info("订单退款处理完成: orderId={}, changeTime={}", + event.getOrderId(), event.getChangeTime()); + } + + /** + * 处理支付待处理事件 + */ + private void handlePaymentPending(PaymentStatusChangeEvent event) { + log.info("处理支付待处理: orderId={}", event.getOrderId()); + + // 这里可以添加支付待处理的业务逻辑: + // 1. 设置支付超时提醒 + // 2. 预留库存 + // 3. 发送支付提醒 + + // 示例:记录待支付处理日志 + log.info("订单待支付处理完成: orderId={}, changeTime={}", + event.getOrderId(), event.getChangeTime()); + } +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/mapper/OrderDiscountMapper.java b/src/main/java/com/ycwl/basic/order/mapper/OrderDiscountMapper.java new file mode 100644 index 0000000..86b1a82 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/mapper/OrderDiscountMapper.java @@ -0,0 +1,12 @@ +package com.ycwl.basic.order.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ycwl.basic.order.entity.OrderDiscountV2; +import org.apache.ibatis.annotations.Mapper; + +/** + * 订单优惠记录表Mapper接口 + */ +@Mapper +public interface OrderDiscountMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/mapper/OrderItemMapper.java b/src/main/java/com/ycwl/basic/order/mapper/OrderItemMapper.java new file mode 100644 index 0000000..89b9df0 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/mapper/OrderItemMapper.java @@ -0,0 +1,12 @@ +package com.ycwl.basic.order.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ycwl.basic.order.entity.OrderItemV2; +import org.apache.ibatis.annotations.Mapper; + +/** + * 订单商品明细表Mapper接口 + */ +@Mapper +public interface OrderItemMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/mapper/OrderMapper.java b/src/main/java/com/ycwl/basic/order/mapper/OrderMapper.java new file mode 100644 index 0000000..624a043 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/mapper/OrderMapper.java @@ -0,0 +1,12 @@ +package com.ycwl.basic.order.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ycwl.basic.order.entity.OrderV2; +import org.apache.ibatis.annotations.Mapper; + +/** + * 订单表Mapper接口 + */ +@Mapper +public interface OrderMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/mapper/OrderRefundMapper.java b/src/main/java/com/ycwl/basic/order/mapper/OrderRefundMapper.java new file mode 100644 index 0000000..cb226e7 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/mapper/OrderRefundMapper.java @@ -0,0 +1,12 @@ +package com.ycwl.basic.order.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.ycwl.basic.order.entity.OrderRefundV2; +import org.apache.ibatis.annotations.Mapper; + +/** + * 订单退款记录表Mapper接口 + */ +@Mapper +public interface OrderRefundMapper extends BaseMapper { +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/service/IOrderService.java b/src/main/java/com/ycwl/basic/order/service/IOrderService.java new file mode 100644 index 0000000..cfa3bed --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/service/IOrderService.java @@ -0,0 +1,119 @@ +package com.ycwl.basic.order.service; + +import com.github.pagehelper.PageInfo; +import com.ycwl.basic.dto.MobileOrderRequest; +import com.ycwl.basic.order.dto.*; +import com.ycwl.basic.order.entity.OrderV2; +import com.ycwl.basic.order.enums.RefundStatus; +import com.ycwl.basic.pricing.dto.PriceCalculationResult; + +/** + * 订单服务接口 + */ +public interface IOrderService { + + /** + * 创建订单 + * + * @param request 移动端下单请求 + * @param userId 用户ID + * @param scenicId 景区ID + * @param priceResult 价格计算结果 + * @return 订单ID + */ + Long createOrder(MobileOrderRequest request, Long userId, Long scenicId, PriceCalculationResult priceResult); + + /** + * 根据订单号查询订单 + * + * @param orderNo 订单号 + * @return 订单信息 + */ + OrderV2 getByOrderNo(String orderNo); + + /** + * 根据订单ID查询订单 + * + * @param orderId 订单ID + * @return 订单信息 + */ + OrderV2 getById(Long orderId); + + /** + * 更新订单状态 + * + * @param orderId 订单ID + * @param orderStatus 订单状态 + * @param paymentStatus 支付状态 + * @return 更新结果 + */ + boolean updateOrderStatus(Long orderId, String orderStatus, String paymentStatus); + + /** + * 生成订单号 + * + * @return 订单号 + */ + String generateOrderNo(); + + // ====== 新增方法 ====== + + /** + * 分页查询订单列表 + * + * @param request 分页查询请求 + * @return 分页结果 + */ + PageInfo pageOrders(OrderV2PageRequest request); + + /** + * 查询订单详情(包含商品明细、优惠记录、退款记录) + * + * @param orderId 订单ID + * @return 订单详情 + */ + OrderV2DetailResponse getOrderDetail(Long orderId); + + /** + * 更新订单备注 + * + * @param orderId 订单ID + * @param remarks 备注内容 + * @return 更新结果 + */ + boolean updateOrderRemarks(Long orderId, String remarks); + + /** + * 更新支付状态 + * + * @param orderId 订单ID + * @param paymentStatus 支付状态 + * @return 更新结果 + */ + boolean updatePaymentStatus(Long orderId, String paymentStatus); + + /** + * 更新退款状态 + * + * @param orderId 订单ID + * @param refundStatus 退款状态 + * @return 更新结果 + */ + boolean updateRefundStatus(Long orderId, RefundStatus refundStatus); + + /** + * 创建退款记录 + * + * @param request 退款申请请求 + * @return 退款记录ID + */ + Long createRefundRecord(RefundRequest request); + + /** + * 根据用户ID分页查询订单列表(移动端使用) + * + * @param request 分页查询请求(已设置用户ID) + * @return 分页结果 + */ + PageInfo pageOrdersByUser(OrderV2PageRequest request); +} \ No newline at end of file diff --git a/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java new file mode 100644 index 0000000..191c372 --- /dev/null +++ b/src/main/java/com/ycwl/basic/order/service/impl/OrderServiceImpl.java @@ -0,0 +1,530 @@ +package com.ycwl.basic.order.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.github.pagehelper.PageHelper; +import com.github.pagehelper.PageInfo; +import com.ycwl.basic.dto.MobileOrderRequest; +import com.ycwl.basic.order.dto.*; +import com.ycwl.basic.order.entity.OrderDiscountV2; +import com.ycwl.basic.order.entity.OrderItemV2; +import com.ycwl.basic.order.entity.OrderRefundV2; +import com.ycwl.basic.order.entity.OrderV2; +import com.ycwl.basic.order.enums.*; +import com.ycwl.basic.order.mapper.OrderDiscountMapper; +import com.ycwl.basic.order.mapper.OrderItemMapper; +import com.ycwl.basic.order.mapper.OrderMapper; +import com.ycwl.basic.order.mapper.OrderRefundMapper; +import com.ycwl.basic.order.service.IOrderService; +import com.ycwl.basic.order.event.*; +import com.ycwl.basic.pricing.dto.DiscountDetail; +import com.ycwl.basic.pricing.dto.PriceCalculationResult; +import com.ycwl.basic.pricing.dto.ProductItem; +import com.ycwl.basic.pricing.dto.VoucherInfo; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 订单服务实现类 + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class OrderServiceImpl implements IOrderService { + + private final OrderMapper orderMapper; + private final OrderItemMapper orderItemMapper; + private final OrderDiscountMapper orderDiscountMapper; + private final OrderRefundMapper orderRefundMapper; + private final OrderEventManager orderEventManager; + + @Override + @Transactional + public Long createOrder(MobileOrderRequest request, Long userId, Long scenicId, PriceCalculationResult priceResult) { + Date now = new Date(); + + // 1. 生成订单号 + String orderNo = generateOrderNo(); + + // 2. 创建订单基础信息 + OrderV2 order = new OrderV2(); + order.setOrderNo(orderNo); + order.setMemberId(userId); + order.setOpenId(request.getFaceId().toString()); // 临时使用faceId,实际应传入openId + order.setFaceId(request.getFaceId()); + order.setScenicId(scenicId); + + order.setOriginalAmount(priceResult.getOriginalAmount()); + order.setDiscountAmount(priceResult.getDiscountAmount()); + order.setFinalAmount(priceResult.getFinalAmount()); + + order.setOrderStatus(OrderStatus.PENDING_PAYMENT); + order.setPaymentStatus(PaymentStatus.UNPAID); + order.setRefundStatus(RefundStatus.NO_REFUND); + order.setTotalRefundAmount(BigDecimal.ZERO); + + order.setRemarks(request.getRemarks()); + order.setCreateTime(now); + order.setUpdateTime(now); + order.setCreateBy(userId); + order.setUpdateBy(userId); + order.setDeleted(0); + + // 保存订单 + orderMapper.insert(order); + Long orderId = order.getId(); + + log.info("订单基础信息创建成功: orderId={}, orderNo={}", orderId, orderNo); + + // 3. 保存订单商品明细 + for (ProductItem product : request.getProducts()) { + OrderItemV2 orderItem = new OrderItemV2(); + orderItem.setOrderId(orderId); + orderItem.setProductType(product.getProductType().name()); + orderItem.setProductId(product.getProductId()); + orderItem.setProductName(getProductName(product)); // 根据商品类型和ID获取商品名称 + orderItem.setQuantity(product.getQuantity()); + orderItem.setUnitPrice(product.getUnitPrice()); + orderItem.setOriginalAmount(product.getOriginalPrice()); + orderItem.setFinalAmount(product.getSubtotal()); + orderItem.setCreateTime(now); + orderItem.setUpdateTime(now); + + orderItemMapper.insert(orderItem); + } + + log.info("订单商品明细保存成功: orderId={}, itemCount={}", orderId, request.getProducts().size()); + + // 4. 记录使用的优惠券信息 + if (priceResult.getUsedCoupon() != null) { + OrderDiscountV2 couponDiscount = new OrderDiscountV2(); + couponDiscount.setOrderId(orderId); + couponDiscount.setDiscountType(DiscountType.COUPON); + couponDiscount.setDiscountName(priceResult.getUsedCoupon().getCouponName()); + couponDiscount.setDiscountAmount(priceResult.getUsedCoupon().getActualDiscountAmount()); + couponDiscount.setSortOrder(3); // 优惠券显示顺序 + couponDiscount.setCouponId(priceResult.getUsedCoupon().getCouponId()); + couponDiscount.setCreateTime(now); + + orderDiscountMapper.insert(couponDiscount); + log.info("优惠券记录保存成功: orderId={}, couponId={}, discountAmount={}", + orderId, priceResult.getUsedCoupon().getCouponId(), priceResult.getUsedCoupon().getActualDiscountAmount()); + } + + // 5. 记录使用的券码信息 + if (priceResult.getUsedVoucher() != null) { + VoucherInfo voucherInfo = priceResult.getUsedVoucher(); + OrderDiscountV2 voucherDiscount = new OrderDiscountV2(); + voucherDiscount.setOrderId(orderId); + voucherDiscount.setDiscountType(DiscountType.VOUCHER); + voucherDiscount.setDiscountName(voucherInfo.getBatchName()); + voucherDiscount.setDiscountAmount(voucherInfo.getActualDiscountAmount()); + voucherDiscount.setSortOrder(1); // 券码显示顺序最高 + voucherDiscount.setVoucherCode(voucherInfo.getVoucherCode()); + voucherDiscount.setCreateTime(now); + + orderDiscountMapper.insert(voucherDiscount); + log.info("券码记录保存成功: orderId={}, voucherCode={}, discountAmount={}", + orderId, voucherInfo.getVoucherCode(), voucherInfo.getActualDiscountAmount()); + } + + // 6. 记录其他优惠信息(如限时立减等) + if (priceResult.getDiscountDetails() != null) { + for (DiscountDetail discount : priceResult.getDiscountDetails()) { + // 跳过已经记录的优惠券和券码 + if ("VOUCHER".equals(discount.getDiscountType()) || "COUPON".equals(discount.getDiscountType())) { + continue; + } + + OrderDiscountV2 otherDiscount = new OrderDiscountV2(); + otherDiscount.setOrderId(orderId); + otherDiscount.setDiscountType(DiscountType.fromCode(discount.getDiscountType())); + otherDiscount.setDiscountName(discount.getDiscountName()); + otherDiscount.setDiscountAmount(discount.getDiscountAmount()); + otherDiscount.setSortOrder(discount.getSortOrder()); + otherDiscount.setCreateTime(now); + + orderDiscountMapper.insert(otherDiscount); + log.info("其他优惠记录保存成功: orderId={}, type={}, discountAmount={}", + orderId, discount.getDiscountType(), discount.getDiscountAmount()); + } + } + + log.info("订单创建完成: orderId={}, orderNo={}, finalAmount={}", + orderId, orderNo, priceResult.getFinalAmount()); + + return orderId; + } + + @Override + public OrderV2 getByOrderNo(String orderNo) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("order_no", orderNo); + queryWrapper.eq("deleted", 0); + return orderMapper.selectOne(queryWrapper); + } + + @Override + public OrderV2 getById(Long orderId) { + return orderMapper.selectById(orderId); + } + + @Override + public boolean updateOrderStatus(Long orderId, String orderStatus, String paymentStatus) { + OrderV2 order = new OrderV2(); + order.setId(orderId); + order.setOrderStatus(OrderStatus.fromCode(orderStatus)); + order.setPaymentStatus(PaymentStatus.fromCode(paymentStatus)); + order.setUpdateTime(new Date()); + + if ("PAID".equals(paymentStatus)) { + order.setPayTime(new Date()); + } + + if ("COMPLETED".equals(orderStatus)) { + order.setCompleteTime(new Date()); + } + + return orderMapper.updateById(order) > 0; + } + + @Override + public String generateOrderNo() { + // 生成订单号:ORDER + 时间戳 + 3位随机数 + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + String timestamp = sdf.format(new Date()); + int random = (int) (Math.random() * 900) + 100; + return "ORDER" + timestamp + random; + } + + /** + * 根据商品类型和ID获取商品名称 + * TODO: 实际应该从商品配置表或其他数据源获取 + */ + private String getProductName(ProductItem product) { + return product.getProductType().getDescription() + "-" + product.getProductId(); + } + + // ====== 新增方法实现 ====== + + @Override + public PageInfo pageOrders(OrderV2PageRequest request) { + PageHelper.startPage(request.getPageNum(), request.getPageSize()); + + QueryWrapper queryWrapper = buildOrderQueryWrapper(request); + List orders = orderMapper.selectList(queryWrapper); + + // 转换为响应DTO + List responseList = orders.stream() + .map(this::convertToListResponse) + .collect(Collectors.toList()); + + return new PageInfo<>(responseList); + } + + @Override + public OrderV2DetailResponse getOrderDetail(Long orderId) { + // 查询订单基础信息 + OrderV2 order = orderMapper.selectById(orderId); + if (order == null || order.getDeleted() == 1) { + return null; + } + + // 查询订单商品明细 + QueryWrapper itemQuery = new QueryWrapper<>(); + itemQuery.eq("order_id", orderId); + List orderItems = orderItemMapper.selectList(itemQuery); + + // 查询订单优惠记录 + QueryWrapper discountQuery = new QueryWrapper<>(); + discountQuery.eq("order_id", orderId).orderByAsc("sort_order"); + List orderDiscounts = orderDiscountMapper.selectList(discountQuery); + + // 查询订单退款记录 + QueryWrapper refundQuery = new QueryWrapper<>(); + refundQuery.eq("order_id", orderId).orderByDesc("create_time"); + List orderRefunds = orderRefundMapper.selectList(refundQuery); + + // 转换为响应DTO + return convertToDetailResponse(order, orderItems, orderDiscounts, orderRefunds); + } + + @Override + @Transactional + public boolean updateOrderRemarks(Long orderId, String remarks) { + OrderV2 order = new OrderV2(); + order.setId(orderId); + order.setRemarks(remarks); + order.setUpdateTime(new Date()); + + return orderMapper.updateById(order) > 0; + } + + @Override + @Transactional + public boolean updatePaymentStatus(Long orderId, String paymentStatus) { + // 先查询当前订单状态 + OrderV2 currentOrder = orderMapper.selectById(orderId); + if (currentOrder == null) { + log.warn("订单不存在: orderId={}", orderId); + return false; + } + + PaymentStatus oldPaymentStatus = currentOrder.getPaymentStatus(); + PaymentStatus newPaymentStatus = PaymentStatus.fromCode(paymentStatus); + + // 更新订单支付状态 + OrderV2 order = new OrderV2(); + order.setId(orderId); + order.setPaymentStatus(newPaymentStatus); + order.setUpdateTime(new Date()); + + if ("PAID".equals(paymentStatus)) { + order.setPayTime(new Date()); + // 支付完成后,订单状态也需要更新 + order.setOrderStatus(OrderStatus.PAID); + } + + boolean success = orderMapper.updateById(order) > 0; + + // 发布支付状态变更事件 + if (success && !oldPaymentStatus.equals(newPaymentStatus)) { + PaymentStatusChangeEvent event = new PaymentStatusChangeEvent( + orderId, currentOrder.getOrderNo(), + oldPaymentStatus, newPaymentStatus, + "支付状态更新", null + ); + orderEventManager.publishPaymentStatusChangeEvent(event); + } + + return success; + } + + @Override + @Transactional + public boolean updateRefundStatus(Long orderId, RefundStatus refundStatus) { + // 先查询当前订单状态 + OrderV2 currentOrder = orderMapper.selectById(orderId); + if (currentOrder == null) { + log.warn("订单不存在: orderId={}", orderId); + return false; + } + + RefundStatus oldRefundStatus = currentOrder.getRefundStatus(); + + // 更新订单退款状态 + OrderV2 order = new OrderV2(); + order.setId(orderId); + order.setRefundStatus(refundStatus); + order.setUpdateTime(new Date()); + + // 根据退款状态更新订单状态 + if (RefundStatus.FULL_REFUND.equals(refundStatus)) { + order.setOrderStatus(OrderStatus.REFUNDED); + } else if (RefundStatus.REFUND_PROCESSING.equals(refundStatus)) { + order.setOrderStatus(OrderStatus.REFUNDING); + } + + boolean success = orderMapper.updateById(order) > 0; + + // 发布退款状态变更事件 + if (success && !oldRefundStatus.equals(refundStatus)) { + RefundStatusChangeEvent event = new RefundStatusChangeEvent( + orderId, currentOrder.getOrderNo(), null, null, + oldRefundStatus, refundStatus, null, + "退款状态更新", null + ); + orderEventManager.publishRefundStatusChangeEvent(event); + } + + return success; + } + + @Override + @Transactional + public Long createRefundRecord(RefundRequest request) { + Date now = new Date(); + + // 生成退款单号 + String refundNo = generateRefundNo(); + + // 创建退款记录 + OrderRefundV2 refund = new OrderRefundV2(); + refund.setOrderId(request.getOrderId()); + refund.setRefundNo(refundNo); + refund.setRefundType(RefundType.fromCode(request.getRefundType())); + refund.setRefundAmount(request.getRefundAmount()); + refund.setRefundFee(request.getRefundFee()); + refund.setRefundStatus(com.ycwl.basic.order.enums.RefundStatus.REFUND_PROCESSING); + refund.setRefundReason(request.getRefundReason()); + refund.setRefundDescription(request.getRefundDescription()); + refund.setOperatorRemarks(request.getOperatorRemarks()); + refund.setRefundChannel(request.getRefundChannel()); + refund.setApplyTime(now); + refund.setCreateTime(now); + refund.setUpdateTime(now); + + orderRefundMapper.insert(refund); + + // 更新订单退款状态 + updateRefundStatus(request.getOrderId(), com.ycwl.basic.order.enums.RefundStatus.REFUND_PROCESSING); + + log.info("退款记录创建成功: refundId={}, refundNo={}, orderId={}, refundAmount={}", + refund.getId(), refundNo, request.getOrderId(), request.getRefundAmount()); + + return refund.getId(); + } + + @Override + public PageInfo pageOrdersByUser(OrderV2PageRequest request) { + // 移动端查询,确保只查询当前用户的订单 + return pageOrders(request); + } + + // ====== 私有辅助方法 ====== + + /** + * 构建订单查询条件 + */ + private QueryWrapper buildOrderQueryWrapper(OrderV2PageRequest request) { + QueryWrapper queryWrapper = new QueryWrapper<>(); + queryWrapper.eq("deleted", 0); + + if (request.getOrderNo() != null && !request.getOrderNo().trim().isEmpty()) { + queryWrapper.like("order_no", request.getOrderNo().trim()); + } + + if (request.getMemberId() != null) { + queryWrapper.eq("member_id", request.getMemberId()); + } + + if (request.getScenicId() != null) { + queryWrapper.eq("scenic_id", request.getScenicId()); + } + + if (request.getOrderStatus() != null && !request.getOrderStatus().trim().isEmpty()) { + queryWrapper.eq("order_status", request.getOrderStatus().trim()); + } + + if (request.getPaymentStatus() != null && !request.getPaymentStatus().trim().isEmpty()) { + queryWrapper.eq("payment_status", request.getPaymentStatus().trim()); + } + + if (request.getRefundStatus() != null && !request.getRefundStatus().trim().isEmpty()) { + queryWrapper.eq("refund_status", request.getRefundStatus().trim()); + } + + if (request.getCreateTimeStart() != null) { + queryWrapper.ge("create_time", request.getCreateTimeStart()); + } + + if (request.getCreateTimeEnd() != null) { + queryWrapper.le("create_time", request.getCreateTimeEnd()); + } + + if (request.getPayTimeStart() != null) { + queryWrapper.ge("pay_time", request.getPayTimeStart()); + } + + if (request.getPayTimeEnd() != null) { + queryWrapper.le("pay_time", request.getPayTimeEnd()); + } + + queryWrapper.orderByDesc("create_time"); + + return queryWrapper; + } + + /** + * 转换为列表响应DTO + */ + private OrderV2ListResponse convertToListResponse(OrderV2 order) { + OrderV2ListResponse response = new OrderV2ListResponse(); + response.setId(order.getId()); + response.setOrderNo(order.getOrderNo()); + response.setMemberId(order.getMemberId()); + response.setOpenId(order.getOpenId()); + response.setFaceId(order.getFaceId()); + response.setScenicId(order.getScenicId()); + response.setOriginalAmount(order.getOriginalAmount()); + response.setDiscountAmount(order.getDiscountAmount()); + response.setFinalAmount(order.getFinalAmount()); + response.setOrderStatus(order.getOrderStatus()); + response.setOrderStatusDesc(order.getOrderStatus().getDescription()); + response.setPaymentStatus(order.getPaymentStatus()); + response.setPaymentStatusDesc(order.getPaymentStatus().getDescription()); + response.setRefundStatus(order.getRefundStatus()); + response.setRefundStatusDesc(order.getRefundStatus().getDescription()); + response.setTotalRefundAmount(order.getTotalRefundAmount()); + response.setRemarks(order.getRemarks()); + response.setPayTime(order.getPayTime()); + response.setCompleteTime(order.getCompleteTime()); + response.setCreateTime(order.getCreateTime()); + response.setUpdateTime(order.getUpdateTime()); + + // 查询商品数量信息 + QueryWrapper itemQuery = new QueryWrapper<>(); + itemQuery.eq("order_id", order.getId()); + List items = orderItemMapper.selectList(itemQuery); + response.setItemCount(items.size()); + response.setTotalQuantity(items.stream().mapToInt(OrderItemV2::getQuantity).sum()); + + return response; + } + + /** + * 转换为详情响应DTO + */ + private OrderV2DetailResponse convertToDetailResponse(OrderV2 order, + List orderItems, + List orderDiscounts, + List orderRefunds) { + OrderV2DetailResponse response = new OrderV2DetailResponse(); + response.setId(order.getId()); + response.setOrderNo(order.getOrderNo()); + response.setMemberId(order.getMemberId()); + response.setOpenId(order.getOpenId()); + response.setFaceId(order.getFaceId()); + response.setScenicId(order.getScenicId()); + response.setOriginalAmount(order.getOriginalAmount()); + response.setDiscountAmount(order.getDiscountAmount()); + response.setFinalAmount(order.getFinalAmount()); + response.setOrderStatus(order.getOrderStatus()); + response.setOrderStatusDesc(order.getOrderStatus().getDescription()); + response.setPaymentStatus(order.getPaymentStatus()); + response.setPaymentStatusDesc(order.getPaymentStatus().getDescription()); + response.setRefundStatus(order.getRefundStatus()); + response.setRefundStatusDesc(order.getRefundStatus().getDescription()); + response.setTotalRefundAmount(order.getTotalRefundAmount()); + response.setRemarks(order.getRemarks()); + response.setPayTime(order.getPayTime()); + response.setCompleteTime(order.getCompleteTime()); + response.setCreateTime(order.getCreateTime()); + response.setUpdateTime(order.getUpdateTime()); + response.setCreateBy(order.getCreateBy()); + response.setUpdateBy(order.getUpdateBy()); + + response.setOrderItems(orderItems); + response.setOrderDiscounts(orderDiscounts); + response.setOrderRefunds(orderRefunds); + + return response; + } + + /** + * 生成退款单号 + */ + private String generateRefundNo() { + SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss"); + String timestamp = sdf.format(new Date()); + int random = (int) (Math.random() * 900) + 100; + return "REFUND" + timestamp + random; + } +} \ No newline at end of file